Electronics > Projects, Designs, and Technical Stuff
Home Brew Analog Computer System
GK:
OK, thanks for the replies. The language is C; code for a PIC uC using "Custom computer services" (CCS) PIC-C compiler.
Yes, it for my "pretend pot" firmware of the 3-D projection unit. I've written a lot of PIC C firmware but I am far from a professional code writer. Here is the complete program. It's functional and I think relatively tidy, but those who program for a living may very well see things that could be done better.
Also attached is the complete schematic of the final unit.
--- Quote ---
// 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:
--- End quote ---
GK:
Here is a video of the unit in action, with a full 360 degrees of variable rotation of both X and Y axes. Once again I am using my Rössler Attractor signal generator as the source. I'm currently laying out a PCB for that one will post up the circuit details when done.
mamalala:
Hi Glen,
there are a few things you can do to improve the code wrt. the table-lookup, avoiding a lot of calculations and stuff. It is a tradeoff between ROM/FLASH usage for the table on one side, and codesize & execution speed on the other side. But since you are using the table a lot with lots of code calculating it, i guess the overall ROM/FLASH used will be less, plus a faster processing speed.
First, make the lookup-table 256 entries large. The native data size of the PIC is 8 bits, using that as table size allows for some really fast methods for reversing the lookup direction and inverting the value.
Then, the PIC has a 10-bit ADC. Use that! This will make things even simpler. To store the result, the PIC uses two registers, ADRESL and ADRESH. Configure the ADC to give you a right-justified result. (register ADCON1 bit ADFM set to 1). This will result in the lower 8 bits of the readout to be stored in ADRESL, and the upper 2 bits into the lower 2 bits of ADRESH.
The effect of this is that in ADRESH you get only the value 0, 1, 2 or 3 over the full range. Now, 4 values, 4 quadrants, a table with 256 entries and a ADRESL of 8 bits... I guess you can already start to see where this goes.
Now about how to access it using simple methods and the ADRESH bits of the result. The lookup table contains the first 90° of a sinewave here. If you invert the index value, you read that table backwards. That way you now have 180°. To decide wether to read forwards or backwwards, you use bit 0 of ADRESH. If it is set, you simply invert the index into the table.
In code this would look something like this:
--- Code: ---if (ADRESH & 0x01)
{
result = sintab[~ADRESL];
}
else
{
result = sintab[ADRESL];
}
--- End code ---
Now for the inverting of the read value of the lookup. You use bit 1 of ADRESL to do that. If it set to 1, you simply invert the value you just read from the lookup-table. Again, in code this looks like:
--- Code: ---if (ADRESH & 0x02)
{
result = ~result;
}
--- End code ---
OK, this was just for sine lookup from the table. The bits of ADRESH for those 4 quadrants in sequence look like this:
--- Code: ---00000000 - first sine quadrant
00000001 - second sine quadrant
00000010 - third sine quadrant
00000011 - fourth sine quadrant
--- End code ---
Which is, of course, also the same sequence you get in ADRESH in 10 bit ADC mode when going from 0 to maximum.
If you want to read a cosine instead, you simply add 1 to ADRESH. The resulting sequence of ADRESH will then be:
--- Code: ---00000001
00000010
00000011
00000100
--- End code ---
This is OK because only the lowest two bits are evaluated anyways. Now, if you want to have the full sine or cosine inverted, you simply add another 2 to ADRESH. Doing all the above allows you to write a single routine that does this. With a little more bit-magic the polarity bits that you use to control the actual output can also be set. You can also combine those bits into a single byte instead of spreading it over four int's. There is the nifty thing of #define, which allows you to define things with a name.
Attached is a textfile with some ad-hoc example code of all that with lots of comments. Of course not tested for bugs, just to show you the overall process. Hope that helps. If you have any questions, let me know.
Greetings,
Chris
mamalala:
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
woodchips:
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.
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version