Just seen this post on tekscopes about capacitor probems etc.
http://www.maximintegrated.com/app-notes/index.mvp/id/5663
Hi GK great thread, I also follow your work with great interest.
On the subject of front panels, have you considered laser cutting services? I was just looking and found quite a few that were reasonable, especially if you can get away with plastic. If shielding is an issue it may still be more viable to simply line the inside of a plastic panel with foil. Given that they can not only cut holes of whatever shape but may also be able to do 2 tone legend engraving
Just a thought
Well, finally, here is the Lorenz Attractor in 3-D projection with variable angles of rotation (the 3-D projection unit in action). I have to say that I am quite happy with the way the synthesized sine/cosine potentiometers worked out with the 8-bit digital pots and the sine/cosine look up table. The 1 degree step resolution and 8-bit accuracy gives a fluid variation in display that is, for all sakes and purposes, totally analog as far as I as the operator can discern. In all honesty the Lorenz Attractor probably isn't the best 3-D "object" to demonstrate the operation of the projection unit, as it is a bit complex and an interpretation of the display isn't intuitively obvious as it is with simpler shapes and objects, such as the assorted springs I posted screen photos of a few posts previously.
Here is a simplified schematic of the initial, prototype projection unit. It is based on the basic 3-D projection principle outlined in chapter 9 (Multi-Dimensional Displays) of Analog Computing At Ultra High Speed, D M.MacKay, M E. Fischer.
N-body problem.
BTW, could anyone suggest to me a more elegant way of converting a long integer (value ranging 0 to 360) into three seperate decimals (0 to 9) (hundreds [xh], tens [xt] and units [xu])? It should probably be obvious but it is getting close to 1am now.
unsigned int input = 276;
char hundreds = 0, tens = 0, units = 0;
while (input >= 100)
{
hundreds++;
input -= 100;
}
while (input >= 10)
{
tens++;
input -= 10;
}
units = input;
/* should now have hundreds = 2, tens = 7, units = 6 */
BTW, could anyone suggest to me a more elegant way of converting a long integer (value ranging 0 to 360) into three seperate decimals (0 to 9) (hundreds [xh], tens [xt] and units [xu])? It should probably be obvious but it is getting close to 1am now.
// 3-D PROJECTION UNIT
// DATE AUGUST/2013
// AUTHOR Glen Kleinschmidt
// VERSION 1.0
// DEVICE PIC16F874
// ROM used: 1533 (37%)
// Largest free fragment is 2048
// RAM used: 24 (13%) at main() level
// 131 (69%) worst case
#include <16f874.h>
#device ADC=10
#use delay(clock=4000000)
#use standard_io(A)
#use standard_io(B)
#use standard_io(C)
#use standard_io(D)
#FUSES HS,NOWDT,PUT,NOPROTECT,NOLVP,NOCPD,NOWRT,BROWNOUT
long int angle_of_rotation_X_axis;
long int angle_of_rotation_Y_axis;
int address;
int data;
int shift;
int xh;
int xt;
int xu;
int yh;
int yt;
int yu;
int sine_X;
int cosine_X;
int sine_Y;
int cosine_Y;
short int polarity_sine_X;
short int polarity_cosine_X;
short int polarity_sine_Y;
short int polarity_cosine_Y;
read_angle_of_rotation_potentiometers(void)
{
SET_ADC_CHANNEL(0); // Set ADC MUX to channel 0 (pin RA0)
delay_us(100); // Wait for ADC-input MUX to settle
angle_of_rotation_X_axis = (READ_ADC() / 2.8); // Read X-rotation potentiometer(s) position and scale
SET_ADC_CHANNEL(1); // Set ADC MUX to channel 1 (pin RA1)
delay_us(100); // Wait for ADC-input MUX to settle
angle_of_rotation_Y_axis = (READ_ADC() / 2.8); // Read Y-rotation potentiometer(s) position and scale
if (angle_of_rotation_X_axis > 360) // Limit X value (0-to-360 degrees)
angle_of_rotation_X_axis = 360; //
if (angle_of_rotation_Y_axis > 360) // Limit Y value (0-to-360 degrees)
angle_of_rotation_Y_axis = 360; //
}
seven_segment_display_decode(void)
{
int sevenseg[10] = {2,158,36,12,152,72,192,30,0,24}; // Look-up table for 7-segment LED display decode
xh = abs(angle_of_rotation_X_axis / 100); // Decode decimal values (hundreds, tens & units)
xt = angle_of_rotation_X_axis - (xh * 100); // for X-axis angle-of-rotation digital display
xu = xt - 10 * (abs(xt / 10)); //
xt = abs(xt / 10); //
yh = abs(angle_of_rotation_Y_axis / 100); // Decode decimal values (hundreds, tens & units)
yt = angle_of_rotation_Y_axis - (yh * 100); // for Y-axis angle-of-rotation digital display
yu = yt - 10 * (abs(yt / 10)); //
yt = abs(yt / 10); //
xh = sevenseg[xh]; // 3-digit 7-segment decode for X-axis angle-of-rotation display
xt = sevenseg[xt]; //
xu = sevenseg[xu]; //
yh = sevenseg[yh]; // 3-digit 7-segment decode for Y-axis angle-of-rotation display
yt = sevenseg[yt]; //
yu = sevenseg[yu]; //
output_low(pin_D4); // Digital display digit latch lines low
output_low(pin_D5); //
output_low(pin_D6); //
output_low(pin_D7); //
output_low(pin_B0); //
output_b(yu); // Set "units" digit latch for Y-axis angle-of-rotation
output_high(pin_B0); //
output_low(pin_B0); //
output_b(yt); // Set "tens" digit latch for Y-axis angle-of-rotation
output_high(pin_D7); //
output_low(pin_D7); //
output_b(yh); // Set "hundreds" digit latch for Y-axis angle-of-rotation
output_high(pin_D6); //
output_low(pin_D6); //
output_b(xu); // Set "units" digit latch for X-axis angle-of-rotation
output_high(pin_D5); //
output_low(pin_D5); //
output_b(xt); // Set "tens" digit latch for X-axis angle-of-rotation
output_high(pin_D4); //
output_low(pin_D4); //
output_b(xh); // Put "hundreds" digit on 7-segment data bus
}
sincostable(void)
{
int sincos[91] = {0, 4, 8, 13, 17, 22, 26, 31, 35, 39, // Look-up table for sine and cosine trig. function
44, 48, 53, 57, 61, 65, 70, 74, 78, 83,
87, 91, 95, 99, 103,107,111,115,119,123,
127,131,135,138,142,146,149,153,156,160,
163,167,170,173,177,180,183,186,189,192,
195,198,200,203,206,208,211,213,216,218,
220,223,225,227,229,231,232,234,236,238,
239,241,242,243,245,246,247,248,249,250,
251,251,252,253,253,254,254,254,254,254,
255};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (angle_of_rotation_X_axis <= 360) // Read sine(x) table value for 271-to-360 degree segment
{ // Inverting for this segment (0 = true)
sine_X = sincos[360 - angle_of_rotation_X_axis]; //
polarity_sine_X = 0; //
} //
if (angle_of_rotation_X_axis <= 270) // Read sine(x) table value for 181-to-270 degree segment
{ // Inverting for this segment (0 = true)
sine_X = sincos[angle_of_rotation_X_axis - 180]; //
polarity_sine_X = 0; //
} //
if (angle_of_rotation_X_axis <= 180) // Read sine(x) table value for 91-to-180 degree segment
{ // Non-inverting for this segment (1 = false)
sine_X = sincos[180 - angle_of_rotation_X_axis]; //
polarity_sine_X = 1; //
} //
if (angle_of_rotation_X_axis <= 90) // Read sine(x) table value for 0-to-90 degree segment
{ // Non-inverting for this segment (1 = false)
sine_X = sincos[angle_of_rotation_X_axis]; //
polarity_sine_X = 1; //
} //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (angle_of_rotation_X_axis <= 360) // Read cosine(x) table value for 271-to-360 degree segment
{ // Non-inverting for this segment (1 = false)
cosine_X = sincos[angle_of_rotation_X_axis - 270]; //
polarity_cosine_X = 1; //
} //
if (angle_of_rotation_X_axis <= 270) // Read cosine(x) table value for 181-to-270 degree segment
{ // Inverting for this segment (0 = true)
cosine_X = sincos[270 - angle_of_rotation_X_axis]; //
polarity_cosine_X = 0; //
} //
if (angle_of_rotation_X_axis <= 180) // Read cosine(x) table value for 91-to-180 degree segment
{ // Inverting for this segment (0 = true)
cosine_X = sincos[angle_of_rotation_X_axis - 90]; //
polarity_cosine_X = 0; //
} //
if (angle_of_rotation_X_axis <= 90) // Read cosine(x) table value for 0-to-90 degree segment
{ // Non-inverting for this segment (1 = false)
cosine_X = sincos[90 - angle_of_rotation_X_axis]; //
polarity_cosine_X = 1; //
} //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (angle_of_rotation_Y_axis <= 360) // Read sine(y) table value for 271-to-360 degree segment
{ // Inverting for this segment (0 = true)
sine_Y = sincos[360 - angle_of_rotation_Y_axis]; //
polarity_sine_Y = 0; //
} //
if (angle_of_rotation_Y_axis <= 270) // Read sine(y) table value for 181-to-270 degree segment
{ // Inverting for this segment (0 = true)
sine_Y = sincos[angle_of_rotation_Y_axis - 180]; //
polarity_sine_Y = 0; //
} //
if (angle_of_rotation_Y_axis <= 180) // Read sine(y) table value for 91-to-180 degree segment
{ // Non-inverting for this segment (1 = false)
sine_Y = sincos[180 - angle_of_rotation_Y_axis]; //
polarity_sine_Y = 1; //
} //
if (angle_of_rotation_Y_axis <= 90) // Read sine(y) table value for 0-to-90 degree segment
{ // Non-inverting for this segment (1 = false)
sine_Y = sincos[angle_of_rotation_Y_axis]; //
polarity_sine_Y = 1; //
} //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (angle_of_rotation_Y_axis <= 360) // Read cosine(y) table value for 271-to-360 degree segment
{ // Non-inverting for this segment (1 = false)
cosine_Y = sincos[angle_of_rotation_Y_axis - 270]; //
polarity_cosine_Y = 1; //
} //
if (angle_of_rotation_Y_axis <= 270) // Read cosine(y) table value for 181-to-270 degree segment
{ // Inverting for this segment (0 = true)
cosine_Y = sincos[270 - angle_of_rotation_Y_axis]; //
polarity_cosine_Y = 0; //
} //
if (angle_of_rotation_Y_axis <= 180) // Read cosine(y) table value for 91-to-180 degree segment
{ // Inverting for this segment (0 = true)
cosine_Y = sincos[angle_of_rotation_Y_axis - 90]; //
polarity_cosine_Y = 0; //
} //
if (angle_of_rotation_Y_axis <= 90) // Read cosine(y) table value for 0-to-90 degree segment
{ // Non-inverting for this segment (1 = false)
cosine_Y = sincos[90 - angle_of_rotation_Y_axis]; //
polarity_cosine_Y = 1; //
} //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
RDACclock(void) // Strobe digipot clock line
{
delay_us(100);
output_high(pin_C1);
delay_us(100);
output_low(pin_C1);
delay_us(100);
}
RDACaddress(void) // Shift the 3 address bits to digipot data line
{
for (shift=0; shift<3; shift++)
{
output_bit(pin_C2, bit_test(address, 2 - shift));
RDACclock();
}
}
RDACdata(void) // Shift the 8 data bits to the digipot data line
{
for (shift=0; shift<8; shift++)
{
output_bit(pin_C2, bit_test(data, 7 - shift));
RDACclock();
}
}
set_digital_potentiometers(void)
{
output_low(pin_C0); // !CS low to initiate data transfer
address = 0; // Set address for RDAC1
RDACaddress(); // Send the 3 address bits
data = cosine_Y; // Set data value to cosine_Y
RDACdata(); // Send the 8 data bits
RDACaddress(); // Send the next daisy-chained 3 address bits
data = cosine_X; // Set data value to cosine_X
RDACdata(); // Send the next daisy-chained 8 data bits
output_high(pin_C0); // !CS high to complete data transfer
delay_us(200);
output_low(pin_C0); // !CS low to initiate data transfer
address = 1; // Set address for RDAC2
RDACaddress(); // Send the 3 address bits
data = sine_Y; // Set data value to sine_Y
RDACdata(); // Send the 8 data bits
RDACaddress(); // Send the next daisy-chained 3 address bits
data = sine_X; // Set data value to sine_X
RDACdata(); // Send the next daisy-chained 8 data bits
output_high(pin_C0); // !CS high to complete data transfer
delay_us(200);
output_low(pin_C0); // !CS low to initiate data transfer
address = 2; // Set address for RDAC3
RDACaddress(); // Send the 3 address bits
data = sine_Y; // Set data value to sine_Y
RDACdata(); // Send the 8 data bits
RDACaddress(); // Send the next daisy-chained 3 address bits
data = sine_X; // Set data value to sine_X
RDACdata(); // Send the next daisy-chained 8 data bits
output_high(pin_C0); // !CS high to complete data transfer
delay_us(200);
output_low(pin_C0); // !CS low to initiate data transfer
address = 3; // Set address for RDAC4
RDACaddress(); // Send the 3 address bits
data = cosine_Y; // Set data value to cosine_Y
RDACdata(); // Send the 8 data bits
RDACaddress(); // Send the next daisy-chained 3 address bits
data = cosine_X; // Set data value to cosine_X
RDACdata(); // Send the next daisy-chained 8 data bits
output_high(pin_C0); // !CS high to complete data transfer
}
set_sinecos_polarity(void)
{
output_bit(pin_D1, bit_test(polarity_sine_X, 0)); // Connect polarity status to sine(x) amplifiers
output_bit(pin_D2, bit_test(polarity_cosine_X, 0)); // Connect polarity status to cosine(x) amplifiers
output_bit(pin_C3, bit_test(polarity_sine_Y, 0)); // Connect polarity status to sine(y) amplifier
output_bit(pin_D0, bit_test(polarity_cosine_Y, 0)); // Connect polarity status to cosine(y) amplifier
}
MAIN()
{
SETUP_ADC_PORTS(RA0_RA1_ANALOG_RA3_RA2_REF); // Define ADC inputs: RA0 & RA1=inputs, RA2= -Vref, RA3= +Vref)
SETUP_ADC(ADC_CLOCK_DIV_32); // Connect ADC internal clock
loop:
read_angle_of_rotation_potentiometers(); // This routine reads the X & Y angle-of-rotation potentiometers
seven_segment_display_decode(); // This routine sets the digital display of the angles of rotation
sincostable(); // This routine sets the sine and cosine values for the digipots
set_digital_potentiometers(); // This routine programs the digipots (daisy-chained serial)
set_sinecos_polarity(); // This routing sets the switchable inverting/non inverting
// amplifiers as required for 4-quadrant sine/cosine multiplication
goto loop; // Play it again Sam!
}
Here is the completed unit:
if (ADRESH & 0x01)
{
result = sintab[~ADRESL];
}
else
{
result = sintab[ADRESL];
}
if (ADRESH & 0x02)
{
result = ~result;
}
00000000 - first sine quadrant
00000001 - second sine quadrant
00000010 - third sine quadrant
00000011 - fourth sine quadrant
00000001
00000010
00000011
00000100
What i forgot to mention. In my example i do invert the readout value _and_ set the negative-polarity bit. I just assumed that when you toggle the output pin that the DAC output is inverted after the OpAmps, that is 5V input becomes 0V and 0V input becomes -5V output when negative, while for the positive half 5V in becomes 5V out, and 0V in becomes 0V out (just as an example). If that is not the case, simple comment out the "result = ~result" line there.
Also, note that due to the way the lookuptable is read, the first and last value in it are duplicated at each start/end of a quadrant. That is, for example, the zero-crossing at 180° will become 0 at an ADC value of 511 (end of second quadrant) but also 0 at a ADC value of 512 (start of third quadrant). There are two ways to eliminate that error. One is to create the table so there is no 0° and 90°. So the sequence instead of, for example, 8-4-0 0-4-8 becomes 10-6-2 2-6-10. Another way is to limit the maximum value in the table, and then add a fixed value if you go negative. So if the sequence from the table would be 8-4-0 0-4-8 you simply add 4 if negative, and get 8-4-0 4-8-12 instead. Of course this means that the maximum value can not be 255 but must be 251 instead, to avoid an overflow when adding 4.
Personally i would prefer the first method. This ensures that the generated quadrants are symetrical. And at 8 bits for one quadrant you have a resolution of roundabout 0.35°, so the small "error" of not having complete 0° or 90° is just roundabout +/- 0.175°.
Greetings,
Chris
Just got a copy of the Korn Electronic and Hybrid Analogue Computers book. I like it. Dates from 1963 so right at the very end of both designing with valves and analogue computers, a few years later both were history. And all those ECC83s that were junked!
What is really nice is that the book can be read as a guide to design, there is lots of useful and interesting information. If you like the MIT Rad Lab books then you will like this. So many, most, text books are, quite frankly, boring and a chore to read, but every now and then a real gem pops up. Others are ones by Scroggie, Cathode Ray of Wireless World, Steinmetz from the early 1900s.
Thanks for the suggestion.
Hi Chris,
Thanks for going through this in such detail - it's appreciated. I'd like to implement your recommendations and see how it compares to the current code, speed wise. However the electronic/firmware side of the project has been put aside for now as I work on installing and wiring it into its instrument case. Give me several days to get back to revising the code.
If you are still looking for equations like the Lorenz, have you seen the Cornu's spiral? Beyond me but looked a pretty picture in the book, Slater & Frank - Electromagnetism.
Hi Chris,
Thanks for going through this in such detail - it's appreciated. I'd like to implement your recommendations and see how it compares to the current code, speed wise. However the electronic/firmware side of the project has been put aside for now as I work on installing and wiring it into its instrument case. Give me several days to get back to revising the code.
No problem, you're welcome. Just as a side-note, if you plan to use a lot of table lookups in the future, you might want to consider to use PIC18 instead of the PIC16 series chips. They allow a more direct access to the program memory especially for stuff like tables, strings, etc.
On the PIC16, what actually happens is that you load the index in the W register and then call a subroutine. That will use the value in the W register to calculate the target address of a coputed goto, then jumps to that location, which in turn returns the actual value back in the W register.
On PIC18 you have a dedicated set of registers to do that. You simply load the index value into those registers, and can immediately read back the result from another register. Much faster, and allows to keep large tables simple as well (PIC16 would need some extra code to access tables larger than 256 entries).
It also has registers to directly access arrays and stuff in RAM. And all of those (for RAM and ROM) have various registers that define how the access happens. One register reads without changing the address pointed to. Another one increases the address, yet another one decreases it. And then there is one that allows you to access the address you set plus/minus an offset given by what is currently in W.
Makes working with memory blocks and tables _much_ more performant.
Greetings,
Chris
Finally, here is the completed 3-D Projection unit.
OK, thanks for the pointers! I'm currently using a ~ 10 year old version of the CCS compiler, so am currently restricted to a ~10 year old list of supported devices. However I'll soon be upgrading to the latest and greatest version and will also be looking for new candidates for my "preferred devices" inventory. I've been using the 16F87X series for a long time now but it's a bit old hat now and time to move on.