ARDUINO PROJECT: Analog Output / Signal Generator
Posted by kll on September 18 2013 05:57:04
in TEST BENCH we made ( as a by product ) software and hardware
to generate a analog voltage by 2 PWM outputs to a RC network.

here we follow that up by building a signal generator.
but back to the basics first:
a arduino UNO uses a ATMEL AVR ATmega328 cpu.
GPIO pins: 23 Programmable I/O Lines incl.
6-channel 10-bit ADC ( what can be used as digital I/O )
6-channel PWM
BUT NO DAC, ANALOG OUTPUT
So, one pin can have many function, but we want look at the
electrical state a pin can have:
first we all know:
digital out LOW ( drain )
digital out HIGH ( 5V source )
we also know it can be a
digital in ( but i did not see that as a electrical ( output ) state ( high input impedance ) until i read about Charlieplexing LEDs.
add there is one more electrical state:
digital in with pullup ( 20k resistor to 5V )
I want to use that 4 states with
Y: one channel to 470ohm,
X: and second channel to the 1kohm resistors.
( that resistor have been selected for a example where i needed to drive a LED,
for a pure signal generation, when no load is to expect, use factor 10 times. )

new wired:
N: direct to the RC ( Z point ), LED and its resistor not used
The feeding diodes from last circuit not used.
The capacitor can be disabled later for higher speeds
This will be our first signal generated for test,
// setpointlist:
// 0 digital output LOW
// 1 digital input
// 2 digital input pullup
// 3 digital output HIGH

signal setpoint already sorted to generate a Z ramp
this list is the setpoint list for the 3 channel output
AND the read back measuring Z signal amplitude ( of 1023 ) .
N,X,Y,Z,
1,0,0,0,
1,2,0,7,
1,0,2,13,
1,2,1,23,
1,1,2,24,
1,2,2,45,
1,3,0,245,
1,3,1,478,
1,3,2,485,
1,0,3,508,
1,1,3,661,
1,2,3,664,
1,3,3,754,
2,3,3,757,
3,1,1,992,
we see that the new setpoint INPUT with pullup has limited use here,
but that might depend on the selection of the other 3 resistor values.

do not get confused with the measuring graph of the X,Y output ( scope 2 C1, and 3 C2 ),
its not PWM anymore, in case the X, or Y have setpoint 1 ( are inputs )
the potential there equals the Z point level, because no current / no voltage drop
on the loading resistor.

eliminating the pullup setpoints and rearrange:
N,X,Y,Z,
1,0,0,0,
1,3,0,244,
1,3,1,479,
1,0,3,508,
1,1,3,661,
1,3,3,755,
3,1,1,993,
1,3,3,756,
1,1,3,662,
1,0,3,509,
1,3,1,480,
1,3,0,245,
1,0,0,0,


interesting to see that its the capacitor what makes the signal asymmetric.
what also is not nice, is the step height,
But i know if i want a fast signal i have to go that way.
one of the early use of arduino was a port of a video signal generation ( AV ) code
using 2 pins and 3 resistors, thats where i have the hardware idea from.

and using one pin (N) to have one more step, even it gives the highest signal, smells like wasted.
remove capacitor and
reducing to 4 step what seem to have more equal step size:
N,X,Y,Z,
1,0,0,0,
1,3,0,243,
1,0,3,510,
1,3,3,755,
3,1,1,964,
1,3,3,755,
1,0,3,509,
1,3,0,243,
1,0,0,0,


here now pushing it to the limits, cleanup,
delete all timer and communication,
but try to get a sinuously shape by tunable step length


and see problems in the arduino mode switching code.
spec.at the moment when the N is changed from output high back to input,
the voltage seems to be shorted out to 0.
( pls also note that we are already at my max scope sample rate 520kHz)



I save that version 1 and start to learn about that DDR PORT byte setting i would need to bypass the arduino routines for switching: pin mode and pin output for several pins
from the charlieplexing example i find that:
DDRD = matrix[setpoint][DDR_BYTE];
PORTD = matrix[setpoint][PORT_BYTE];
at arduino and Bit Math Tutorial
BUT beware!
// set pins 1 (serial transmit) and 2..7 as output,
// but leave pin 0 (serial receive) as input
// (otherwise serial port will stop working!) ...
DDRD = B11111110; // digital pins 7,6,5,4,3,2,1,0

// Turn off digital output pins 2..7 ...
PORTD &= B00000011; // turns off 2..7, but leaves pins 0 and 1 alone


i see that that ports are READ and WRITE!
i am bad ass, why try read manual to figure out what bit means what,
i first again do it old way, but read back / print that registers,
and later i use that readback values to set that registers? HA...
worked well:
signal looks better: ( loops=4 ) The step length is adjustable like loops*N here use 1,8,15

and thats the fastest i can do ( loops=0 ), with still look sinus like:
i use minimum 8 cycles to make one sinus, no waiting ( loops = 0 >> step length = 1 loop),
i see 13 sinus in 360 samples at 521739HZ: about 18840Hz??
one sinus what fits in the scope window would be a 1449Hz. ( about at loops = 4 )


just to compare it for scope with a switching program: 67 pulse / 97kHz?


testing a analog low pass filter for that signal, even it is clear that it change phase and amplitude of the signal, and it might fit only for one sample rate.


code used:

// V1.2 use port commands
const long ser0bd = 9600;
const int Xchannel = 5; // D5 PWM
const int Ychannel = 6; // D6 PWM
const int Nchannel = 7; // D7 switch
const int Zchannel = 0; // A0 input
// array for data
struct fourD
{
int N;
int X;
int Y;
unsigned long wait;
};
typedef struct fourD mrec;
mrec myVals[8];
// array for port settings
struct portbytes
{
byte DDR_BYTE;
byte PORT_BYTE;
unsigned long wait;
};
typedef struct portbytes SPmatrix;
SPmatrix matrix[8];
// array port
/* readback port commands
# 0 DDRD 01100000 PORTD 00000000
# 1 DDRD 01100000 PORTD 00100000
# 2 DDRD 01100000 PORTD 01000000
# 3 DDRD 01100000 PORTD 01100000
# 4 DDRD 10000000 PORTD 10000000
# 5 DDRD 01100000 PORTD 01100000
# 6 DDRD 01100000 PORTD 01000000
# 7 DDRD 01100000 PORTD 00100000
*/
const int arrayloop = 8;
int arraycount = 0;
int l=-1;
unsigned long loopcount =0;
unsigned long loops=4; // here adjust the sinus frequence, 0 : 15924Hz (no waits), 1 : 5071Hz( waits)
int top=15;
int med=8;
//__________________________________________________________________________________________________
// setup
void setup() {
// setpointlist:
// 0 digital output LOW
// 1 digital input
// 2 digital input pullup
// 3 digital output HIGH
/*
l=l+1; [l].N=1; myVals[l].X=0; myVals[l].Y=0; myVals[l].wait=loops*5;//0
l=l+1; myVals[l].N=1; myVals[l].X=3; myVals[l].Y=0; myVals[l].wait=loops*10;//245
l=l+1; myVals[l].N=1; myVals[l].X=0; myVals[l].Y=3; myVals[l].wait=loops*5;//508
l=l+1; myVals[l].N=1; myVals[l].X=3; myVals[l].Y=3; myVals[l].wait=loops;//754
l=l+1; myVals[l].N=3; myVals[l].X=1; myVals[l].Y=1; myVals[l].wait=loops*5;//992 // rewire to VOLT SIGNAL, no resistor
l=l+1; myVals[l].N=1; myVals[l].X=3; myVals[l].Y=3; myVals[l].wait=loops*10;//754
l=l+1; myVals[l].N=1; myVals[l].X=0; myVals[l].Y=3; myVals[l].wait=loops*5;//508
l=l+1; myVals[l].N=1; myVals[l].X=3; myVals[l].Y=0; myVals[l].wait=loops;//245
*/
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b00000000; matrix[l].wait=loops*med; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b00100000; matrix[l].wait=loops*top; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b01000000; matrix[l].wait=loops*med; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b01100000; matrix[l].wait=loops; //0
l=l+1; matrix[l].DDR_BYTE=0b10000000; matrix[l].PORT_BYTE=0b10000000; matrix[l].wait=loops*med; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b01100000; matrix[l].wait=loops*top; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b01000000; matrix[l].wait=loops*med; //0
l=l+1; matrix[l].DDR_BYTE=0b01100000; matrix[l].PORT_BYTE=0b00100000; matrix[l].wait=loops; //0
// Serial.begin(ser0bd);
// Serial.println(F("start"));
}
//__________________________________________________________________________________________________
// run
void loop() {
if (loopcount == 0)
{ //
//SPT_digit(Nchannel, myVals[arraycount].N);
//SPT_digit(Xchannel, myVals[arraycount].X);
//SPT_digit(Ychannel, myVals[arraycount].Y);
//Serial.print(" # "); Serial.print(arraycount);Serial.print(" DDRD ");Serial.print(DDRD,BIN);Serial.print(" ");Serial.print(" PORTD ");Serial.println(PORTD,BIN);
SPT_bin(arraycount);
arraycount = arraycount + 1;
if ( arraycount == arrayloop ) { arraycount = 0; }
}
loopcount = loopcount +1;
if (loopcount >= matrix[arraycount].wait ) { loopcount = 0; }
} // end loop
//_____________________________________________________________________________
void SPT_bin( byte steps ) {
DDRD = matrix[steps].DDR_BYTE;
PORTD = matrix[steps].PORT_BYTE;
}
//_____________________________________________________________________________
void SPT_digit(int channel, int SPT ) {
if ( SPT == 0 ) {
pinMode(channel, OUTPUT);
digitalWrite(channel, LOW);
}
if ( SPT == 1 ) {
pinMode(channel, INPUT); // high impedanz
digitalWrite(channel, LOW);
}
if ( SPT == 2 ) {
pinMode(channel, INPUT);
digitalWrite(channel, HIGH); // pullup
}
if ( SPT == 3 ) {
pinMode(channel, OUTPUT);
digitalWrite(channel, HIGH);
}
// ignore all other setpoint values
} // end SP_digit

circuit used here:



that was not bad, but as the circuit comes from a completely different job i think
i should start again, try with only 2 pins, and use different circuit and resistors.

in signal_generator_14 i try different resistors, a fix virtual zero with 10k to Vcc and GND
D5 (X) with 5k
D6 (Y) with 10k
D7 (N) not used/ not connected
N,X,Y,Z
1,0,0,186
1,1,0,264
1,1,1,320
1,0,3,373
1,1,3,528
1,3,0,559
1,3,1,636
1,3,3,786
and back down
( ports commands printed and copy to array )
and again the setpoint 2 ( use pullup ) not very useful.
but use more step, so its not too fast,
anyhow, with 2 pin and 4 resistor
i generated 16 , of that 8 usable different voltage levels.

compared to the LOW and HIGH we are used to, not bad.

about 7840Hz at loops=0;
( with a 100nF it is already completely filtered, because of the higher resistor values )

verify above signal with DMM 1505 Hz. at loops=5;
but when i want to filter this above with a low pass filter (C1) that already distorts the Z signal (C0).
Thats the disadvantage using high ohm resistors.

Rf= 4kohm, Cf=100nF. was the compromise for above problem.

NEXT:
start again using PWM in a other version, copy from original test bench again.
now i want mix the X = PWM 100% = HIGH => Z = Vcc/2
with a sinus setpoint for Y = PWM sinus( time ).

this you find under menu (5) where a counter input allows the sinus frequency to adjust,
and starts the sinus setpoint calc conti job.
(5) again stops it.
now at first the calc had some flaws ( sinus is +-1 , not 0..1 )


but now it looks good,
pls compare the signals with and without 10uF capacitor



above code
but check for the latest code of PMS3.
so we where able to generate a sinodual analog signal by 3 resistors and one capacitor
and actual ONE PWM OUTPUT ( called Y ).
the other PWM OUTPUT ( called X ) on HIGH is not doing anything in above example!
the R 1k could be connected to Vcc.

so actually i now changed that again, using the latest circuit
/*
D5 _ 5k _ Z _ PWM signal X
D6 _10k _ Z _ PWM signal Y
Vcc 10k _ Z _ virtual 2.5V
GND 10k _ Z
GND 100nF _ Z filter option
Z _ 10k _ MAX32 _ 10k _ GND voltage divider for scope
*/

make PWM sinus on X and Y with adjustable freq.

but that i want to hear and not look at the scope.

Thanks for your time, i had fun playing with this!

pls find PoorManScope3 more details.