Full Version : Waveform Capture - Minimal Mass (ASM)
avr >>PROJECTS (AVR) >>Waveform Capture - Minimal Mass (ASM)


AVR_Admin- 04-28-2006
Minimum Mass Waveform Capture by Dick Cappel

Capturing repetitive waveforms at 1 million samples per second using PWM and a comparator.

Described are the waveform capture method, example firmware and hardware designs. This material formed the basis of an article that was first published in the October, 2003 issue of Circuit Cellar magazine.

The only components added to the operating Atmel AT90S2313 circuit

(one capacitor and two resistors) to allow waveform sampling with < 1 microsecond
resolution at 1 volt full scale, are inside the black outline.


The impetus for developing this technique came from my own need to capture repetitive waveforms using the least expensive and lowest part-count means possible. I wanted to be able to view the waveforms on either a liquid crystal display dedicated to the purpose or upload the waveform to a computer to manipulation on a spreadsheet.

The approaches using on-chip A-to-D converters on AVR, PIC, and Cypress controllers reached sample rates of up to about 60 kHz. Not really very useful for the sort of thing I was thinking about using this for: encoded data, radio control signals, A-to-D converter waveforms, checking the dynamic range of amplifiers and capturing audio waveforms for filtering and power calculations. I realized that the comparators in AVR devices were pretty fast with a response time of several hundred nanoseconds, and that the PWM (pulse width modulation) circuit could be made fairly responsive. If there was just some way to combine these to sample analog values quickly...

Eventually it became apparent that repetitive sampling was the only way to get high enough voltage and temporal sampling resolution using these on-chip components. Rather trying to sample and digitize the waveform as is comes in, this method finds out a little bit about the waveform using the relatively high speed comparator every time the waveform is repeated, building up a more and more detailed picture with each repetition by changing the relatively low speed PWM voltage each time.

It's all in the timing. Firmware timing loops set the interval between
samples in a burst of waveform samplings that starts with a trigger signal.
The Green dots represent voltage levels of the sampled signal at the time of sampling.


To capture a waveform, the Pulse Width Modulation D-to-A converter (PWM DAC) is set to its maximum output voltage. Then, using timing loops, the microcontroller looks at the voltage comparator output to determine whether the incoming voltage is higher than the PWM voltage at regularly spaced sampling times (1 microsecond in the illustration).


At each sampling time, if the incoming waveform is at a higher voltage than the PWM voltage, the PWM voltage is stored in a RAM array location corresponding to that sampling time relative to the start of the waveform. After all of the sample times have been tested against the PWM voltage , the PWM voltage is decrement and all of the sample times are compared with the PWM voltage again. This is repeated until the PWM voltage has been reduced to its minimum value, and each scan of the sample times starts by a trigger signal that is derived or in some way related to the incoming waveform.


The finer the voltage resolution, the longer the waveform capture takes. As my initial use of this is with an LCD display with 64 rows, the waveform capture circuit senses 64 different levels. To capture 100 points at 64 different levels, the total capture time is:


Capture time = 100 x [sample interval] x 64 (+ 64 X ([ trigger latency]) + 68 ms ,


where is " trigger latency" is the average time the controller waits for the trigger edge after the last sample, and the 68 milliseconds comes from 1 millisecond settling time of the PWM circuit after each step, plus 5 milliseconds for initial settling.

When capturing waveforms with long periods, the total time needed to capture the waveform is dominated by the time it takes the waveform to make the requisite number of repetitions. For shorter periods, the total time is dominated by the settling times for the PWM. For example, for the example design to capture a waveform with 64 level resolution over a 100 microsecond interval, sampling at 1 microsecond intervals, it takes a little over 72 milliseconds. To capture a 1 second waveform at the same resolution, it takes a little over a minute.

The preceding suggests that the higher the sampling rate, the greater the possible reduction in sampling time by speeding up the DAC. A resistor network connected to some port pins could suffice for low resolution (6 bit) waveform capture. An integrated circuit DAC would probably be much better for higher resolution measurements.

The delay is implemented by firmware timing loops,
and the latch and gate are also functions of firmware.

The quality of the trigger signal is very important. The trigger signal must consistently appear at the same time with respect to the captured signal otherwise sever distortion will result . This means that a noisy trigger signal directly derived from the incoming waveform will give poor results . You'll get the best results with a digital trigger signal taken directly from the source of the signal if such a trigger source is available.

Unsynchronized signals, such as noise will not be represented accurately and be underrepresented in the captured waveform. This quality, which results from synchronous sampling, is sometimes a good thing in that it can effectively pull a signal out of the noise, an important property in applications such as ultra wideband and spread spectrum signal decoding.

Another aspect of sampled data systems, it susceptibility to aliasing. Aliasing is a phenomenon in which a signal appears to occur at a frequency other than that at which it actually occurs. For example, when a 250 kHz square wave is views with a 1 microsecond sampling interval it shows up as properly as two high samples followed by two low samples, but when captured at a 100 microsecond sampling interval, it appears as 625 Hz signal, or 1/400 the actual frequency. The way to prevent aliasing is to insert an analog filter in the signal path before the sampling point, in this case the comparator's input.

In the example circuit discussed here, the AT90S2313 samples the signal at 1 megasample per second. The on-chip comparator has a propagation delay of 500ns to 700 ns, providing inherent filtering for components of signals above about 800 kHz, thus restricting the range of frequencies that can be aliased from above the sampling rate down into those below the sampling rate. To reduce aliasing of signal components lower in frequency than the sampling rate, an additional external would need to be used.

Implementation

With a bare minimum of parts, this circuit is the same basic configuration used
for successive approximation A-to-D conversion, only the firmware is different.


This circuit includes a resistive divider to reduce the full scale voltage from the PWM output (PIN 15) to 1 volt, thus making it 1 volt full scale. In some applications, one might want to replace the 39k resistor with a fixed resistor in series with a variable resistor so that the full scale voltage can be adjusted for calibration.

An even simpler implementation would eliminate the 39K resistor and only write PWM values up to a maximum of 6 bits, but that would result in a slight reduction in noise immuity. Using a voltage divider gives a slightly improved immunity to noise in that ripple on the power supply pin of the AT90S2313 is divided by the divider ratio. This is important for ripple voltage fequency components well below the PWM frequency because at higher frequencies the PWM low pass filter would attenuate the ripple voltage sufficently.

If the 39k resistor was elimiated to increase the full scale voltage to 5 volts, note that this results in the equivalent resistance of the PWM low pass filter increasing by a factor of five (160k/31k), and this means that the capacitor should be reduced to .0068 uf so that the PWM voltage will settle in the expected time yet filter the 20 kHz PWM signal such that the ripple will be less than 1/2 lsb.

More About the Firmware

When capturing a waveform, the PWM circuit generates the maximum output voltage (here, corresponding to a decimal value of 63) and samples all time intervals starting from a trigger signal, taking care to keep the time between samples constant. Whenever a voltage at a sampled time exceeds the PWM voltage, the PWM voltage is stored in the RAM array location corresponding to that sample. In this way, the peak sampled at each time is stored in the RAM array.
The code below is the heart of the waveform capture firmware.

CODE

nextydelay:                 ;When not storing, branch here.
   inc     YL             ;Inc YL and see if array boundary has been exceeded.
   cpi     YL,arrayend+1
   breq    decpwm         ;If at end of RAM array, decrement PWM value
   nop                      Equalize time between samples for pwmval saved and not saved.
   nop
nexty:                        
   ijmp                 ;Indirect jump to delay routine
oneus:
   sbic    ACSR,5        ;CAPTURE COMPARATOR OUTPUT STATE
   rjmp    nextydelay          
   st       Y+,pwmval     ;Store and inc YL and see if array boundary has been exceeded.
   cpi     YL,arrayend+1
   breq    deccpwm       ;If end of RAM array, decrement the PWM value
   rjmp    nexty          
   
capturefinsihed:
    ret


The sampling loop, shown above, is the essence of the method. It requires 10 cycles per sample. Two clock cycles are taken up by the "ijmp" indirect jump instruction, which either jumps to the next byte or to a delay routine that returns to the next byte. Elimination of the ijmp (indirect jump) instruction would decrease the sampling interval to 8 cycles. Straight line coding, which would be quite a chore to write, take a lot of program memory, and be inflexible, could reduce the sampling interval to as little as 3 cycles (for a 5.333 million sample per second rate with a 16 Mhz clock) if storing the waveform in RAM, or just two 2 cycles (for a 8 million sample per second rate with a 16 MHz clock) if storing in registers, though only using registers would allow a very small number of samples before having to dump the contents.

After the actual waveform capture is completed, any RAM array locations with the value $FF remaining can be assumed to be above full scale, and treated accordingly. In the sample application, that of an waveform capture and display system, values of $FF and $00 are not transmitted, and if the count of either $FF or $00 exceeds some preset limit, out of range indicators for the signal being too high or two low are set in the LCD display.

Prior to the sampling loop being called, the program sits in a wait loop, waiting for a low-to-high or high-to-low transistion on the trigger input. Once the triggering event is detected, the sampling loop is called, and then decrements the PWM value, waits for the PWM low pass filter value to settle and then returns to wait for the next triggering event. This process continues until the lowest possible PWM value is tested.

Link to Site: http://cappels.org/dproj/wfc/wfc.html


AVR_Admin- 04-28-2006
CODE

;*********************************************************************
;* AT90S2313-10 2313 waveform capture & send to terminal
;* file: wfcao030326.asm
;* More information/updates may be posted at http://www.projects.cappels.org
;* Feedback welcome at " projects at cappels.org" (Please replace "at" with "@")
;*********************************************************************
;This is firmware for a minimum mass waveform capture system
; based on the Atmel AT90S2313-10 microcontroller.
;
;
;
; HOW TO USE THIS FIRMWARE;
;
; The controller needs to operated from a 10 MHz clock - the
; on-chip crystal oscillator was used in the prototype
; applications. Inverting EIA-232 buffers, such as the Maxim
; MAX232 are expected on pin 3 (TXD) and pin 2 (RXD). To obtain
; an input signal range of 0 to 1 volt D.C., connect one end a
; 330k resistor to pin 15, the PWM output, and connect the
; other end of the 330k resistor to pin 13 (the comparator
; inverting input), an 82k resistor to ground, and a .047 uf
; capacitor to ground. Connect the signal to be captured to
; pin 12 (the comparator's noninverting input) and the trigger
; signal to pin 11 (PD6). Remember to supply +5 volts and a
; decoupling capacitor to pin 20 and ground pin 10. Transient
; and over voltage protection on the input pins is
; recommended. Connect an EIA-232 ASCII terminal to the
; EIA-232 lines, switch on the power, and you are ready to
; capture waveforms. The terminal should be set to 9600 baud
; no parity, and either one or two stop bits are ok.
;
; Upon application of power, the chip will send a greeting
; message that looks very much like the one below:
; ----------------------------
; *
; Waveform capture 03.03.26   Dick Cappels, projects@cappels.org
;
; C..Capture & dump
; I..Increase sample time
; D..Decarese sample time
; T..Triggered mode
; F..Free run mode
; P..Trig. Polarity toggle
; V..Voltage res
; L..Settle time toggle
; E..Toggle line feed
; M..Save settings
; R..Recall saved settings
; !..Default settings
; S..Status
; ?..Menu & status
;
; 5 us 255 steps  F + Stl=5 Linefeed  = 1:
; ----------------------------
; Here is a line-by-line breakdown of the contents of the
; screen.
;
; *
; The asterisk, if present, indicates that previously stored
; operating parameters: sampling rate, trigger polarity,
; voltage resolution (6,7, or 8 bits), and PWM DAC settling
; time have been retrieved from the chips EEPROM and used to
; preset the system to the state that was last saved. A
; checksum of relevant EEPROM data is compared with the stored
; checksum before writing the operating parameters.
;
; Waveform capture 03.03.26   Dick Cappels, projects@cappels.org
; projects@cappels.org
; A heading will be typed. The date and version of the
; firmware is contained in the header, as well as the URL of
; my projects web site.
;
; The next 13 entries are the command menu:
;
; C..Capture & dump
; When C is received, the waveform capture cycle will be
; started. At the end of the cycle, the contents will be
; dumped to the screen in ASCII format. A terminal program
; with a text capture feature can capture the tab-delimited
; data for display on a spreadsheet. The cycle can be
; interrupted before completion by pressing any key after the
; cycle has started. If interrupted, the capture cycle will
; abort and the contents of the buffer will be dumped to the
; terminal.
;  
; I..Increase sample time
; The letter I increases the sampling time. The possible
; values are in a 1-2-5 sequence from 1 microsecond to 10
; milliseconds. After the maximum value of 10 milliseconds per
; sample is reached, it will not increment further.
;
; D..Decarese sample time
; The letter D decreases the sampling time. The possible
; values are in a 1-2-5 sequence from 1 microsecond to 10
; milliseconds. After the minimum, value of 1 microsecond per
; sample is reached, it will not decrement further.
;
; T..Triggered mode
; When T is received, the waveform capture cycle is
; synchronized with the  trigger signal on pin 11.
;
; F..Free run mode
; When F is received, the waveform capture cycle free runs
; without regard to the trigger signal. This is useful in
; checking DC level.
;
;
; P..Trig. Polarity toggle
; The P key toggles between triggering on the positive and
; negative edges of the trigger signal.
;
; V..Voltage res
; Voltage resolution cycles among the three choices, 64 step,
; 128 step, and 255 steps each time the V key is pressed. The
; cycling of these values depends on correct values being in
; memory. If, for some reason this data becomes corrupted,
; control can be regained by pressing ! to restore the
; default settings.
;
; L..Settle time toggle
; The L key toggles between 3 milliseconds per sample (1X) and
; 15 milliseonds (5X) settling times after each change of the
; PWM voltage.
;
; E..Toggle line feed
; Pressing E in response to the command prompt toggles the line
; feed turning the data dump on and off. When on (flag = 1), line
; feeds are sent after each data value and tabs are inserted between
; the sample number and the data value. When off (flag = 0), linefeeds
; are suppressed and spaces are inserted between the sample number and
; the data value. Linefeed on works well with most terminal programs
; and linefeed off makes the captured text file more compatible with
; spreadsheet programs.
;
; M..Save settings
; Pressing M in response to the command prompt causes the
; current operating parameters  (sampling rate, trigger
; polarity, voltage resolution (6,7, or 8 bits), and PWM DAC
; settling time) to be stored in EEPROM, overwriting any
; previously stored information.
;
; R..Recall saved settings
; Pressing the R key causes previously stored operating
; parameters: sampling rate, trigger polarity, voltage
; resolution (6,7, or 8 bits), and PWM DAC settling time have
; been retrieved from the chips EEPROM and used to preset the
; system to the state that was last saved. A checksum of
; relevant EEPROM values from the EEPROM will not be written
; to the operating parameters.
;
; !..Default settings
; Pressing ! causes the default operating parameters to be set
; and the operating parameters to be typed to the screen. The
; default parameters are 5 microsecond sampling time, 255 step
; voltage resolution, free run (DC measurements only),
; positive edge for triggering when triggering is selected,
; and settling time of 5 times.
;
; S..Status
; Displays the operating parameters (see Menu & status below)
;
; ?..Menu & status
; When the question mark is pressed, the menu will be
; displayed and the operating parameters will be typed. The
; operating parameters (status) display is shown below:
;
; 10 us  255 steps  T - Stl=5 :
;
; 10 us means the sampling period is 10 microseconds
; 255 steps means the voltage resolution is 8 bit (255 steps)
; T means waveform capture is synchronized with the trigger
; signal
;      (if F it would mean free run, not synchronized)
; - means that it will trigger on the negative edge of the
; trigger signal
;      (if + it would mean triggering will be on positive
; edge)
; stl=5 means that the steeling time is 5X (if it said stl=1
; then settling
;      time would be 1X)
;
; A header is sent at the start of each data dump.
; Waveform capture 03.03.26  Dick Cappels, projects@cappels.org
; 1 us,  MINIMUM 255 steps  T - Stl=5    Meas ID=1.52
;
; The header includes the version of the firmware, the operating parameters,
; and a measurement ID which is kept in EEPROM and incremented every time
; a waveform capture cycle is initiated. The digit on the left of the decimal point
; is the number of times the digit to the right overflowed beyond 255. The sample
; header above shows that this was this chip's 308th capture cycle.
;
; All characters sent to the terminal are deliberately delayed
; so as not to
; overrun input buffers on low performance computers when
; dumping data.
;
;Good luck in your experimentation. See my web site for
;contact information http://www.projects.cappels.org
;
;
; Start of Minimum Mass Waveform Capture demonstration program

.include "2313def.inc"  



;The main routines are:
; scanspan -This is routine that organizes the collection of the waveform.
; capture - Does the actual waveform capture.
;  MainLoop - Interprets control panel commands and returns status.
; dumparray - Formats and dumps data to terminal.


;UART baud rate calculation
.equ clock = 10000000;clock frequency
.equ baudrate = 9600 ;choose a baudrate
.equ baudconstant = (clock/(16*baudrate))-1

;Assign space for array of sample values in RAM
.equ arraystart = $60;First byte of data array
.equ arrayend = $C3;Last byte of data array

.equ defaultflags = $0F

;Assign working registers
 
.def delayselect = r02;index into list of sampling delays
.def dx1  = r03;delay counter
.def dx2  = r04;delay counter
.def dx3  = r05;delay counter


.def temp   = r16;general purpose variable
.def innerrloopc  = r17;delay counter, innermost loop
.def outerloopc  = r18;delay counter, outer loop
.def flagreg  = r19;Flags for control. See definitions below
.def pwmval  = r21;PWM value
.def H  = r22;Used to send decimal ascii
.def T  = r23;Used to send decimal ascii
.def U  = r24;Used to send decimal ascii
.def pwmstartval = r25;Intial value for voltage measurement.

;Assignment of flagreg bits
;bit 0 if set, linefeeds are sent during dump to screen and spaces ($20) instead of a tab char.
;bit 1 if set, selects for settling time to be 5X, if clear, 1X.
;bit 2 if set, selects for triggering on positive edge of trigger signal
;bit 3 if set, selects for free run (non-triggered operation)
;bit 4
;bit 5
;bit 6
;bit 7


;Assign EEPROM locations;Lowest bytes avoided to reduce chances of corruption.
.equ checksumE = $20
.equ pwmstartvalE = $21
.equ flagregE = $22
.equ delayselectE = $23
.equ serialhighE = $30
.equ seriallowE = $31


.org $000
init:    

 ldi     temp,low(ramend)
 out     spl,temp      ;Set spl.
 ldi     temp,baudconstant    
 out     ubrr,temp      ;Load baudrate.
 sbi ucr,rxen ;Enable WAUT receive
 sbi ucr,txen ;Enable UART Transmit
 ldi pwmval,$FF
 rcall pwm8setup ;Start up PWM DAC.
 ldi temp,$02 ;Default scan rate is 10 us per sample
 mov delayselect,temp
 ldi flagreg,defaultflags;Set flag to free run, trigger set to positive edge,
    ;and settling time of 5X (5 ms filter)
 ldi pwmstartval,255 ;Initialize voltage resolution
 rcall readsettings        
 rcall menu  ;Send Hello message
 rjmp MainLoop ;Main sampling control and I/O loop.


pwm8setup:   ;Set up AT90S2313 Timer 1 as 8 bit PWM.
 ldi temp,$81  
 out TCCR1A,temp ;Preload with $FF
 ldi temp,$01
 out TCCR1B,temp
 ldi temp,$00
 out OCR1AH,temp
 ldi temp,$FF
 out OCR1AL,temp ;Write PWM data
 sbi DDRB,3  ;Set PWM output pin as output
 ret   ;To update PWM value, write a byte to OCR1AL



assignsampledelay:;Set pointer to sample delay entry point  
 ldi ZH,high(2*delaylist);Load high part of byte address into ZH
 ldi ZL,low(2*delaylist);Load low part of byte address into ZL
 mov temp,delayselect
 lsl temp
 add ZL,temp
 brcc nca  ;If carry results, take care of it.
 inc ZH  
nca:
 lpm   ;Get address from pointer (r0) into ZH.ZL
 push r0
 adiw ZL,1
 lpm
 mov ZH,r0
 pop ZL
 ret



delaylist: ;Pointer table to entrances to generic delay routine. Used to select
  ;time scale for waveform sampling. The delay is delay time between
  ;individual samples.
 .dw delay1us
 .dw delay2us
 .dw delay5us
 .dw delay10us
 .dw delay20us
 .dw delay50us
 .dw delay100us
 .dw delay200us
 .dw delay500us
 .dw delay1000us
 .dw delay2000us
 .dw delay5000us
 .dw delay10000us

delay2us:;Total delay for 2.00 us (based on 10 mhz clock).
 nop
 nop
 ldi innerrloopc,$02
l2: dec innerrloopc
 brne l2
 rjmp delay1us


delay5us:;Total sample delay for 5.00 us (based on 10 mhz clock),
 nop;Before terminal priority accomodated two noops removed
 ldi innerrloopc,$01
 ldi outerloopc,$07
 rjmp genericdelay

delay10us:;Total sample delay for 10.0 us (based on 10 mhz clock).
 nop;For terminal priority accomodatioon 6 nops added
 nop
 nop
 nop
 nop
 nop
 nop
 ldi innerrloopc,$01;Before terminal priority accomodated was $01
 ldi outerloopc,$12 ;Before terminal priority accomodated was $12
 rjmp genericdelay

delay20us:;Total sample delay for 20.0 us (based on 10 mhz clock).
 nop;Before terminal priority accomodated was just one nop
 nop
 nop
 ldi innerrloopc,$01
 ldi outerloopc,$2C;Before terminal priority accomodated was $2D
 rjmp genericdelay

delay50us:;Total sample delay for 50.00 us (based on 10 mhz clock).
 nop;Before terminal priority accomodated was just one nop
 nop
 nop
 ldi innerrloopc,$01
 ldi outerloopc,$77;Before terminal priority accomodated was 78
 rjmp genericdelay


delay100us:;delay for 100.1 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$01
 ldi outerloopc,$F5;Before terminal priority accomodated was $F6
 rjmp genericdelay
 
delay200us:;delay for 199.4 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$02
 ldi outerloopc,$F6
 rjmp genericdelay

delay500us:;delay for 500.1 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$05
 ldi outerloopc,$F8
 rjmp genericdelay
 
 
delay1000us:;delay for 1006 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$0C
 ldi outerloopc,$D0
 rjmp genericdelay

delay2000us:;delay for 2010.4 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$18
 ldi outerloopc,$D0
 rjmp genericdelay


delay5000us:;delay for 5023.6 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$3C
 ldi outerloopc,$D0
 rjmp genericdelay



delay10000us:;delay for 10045.6 us (based on 10 mhz clock. Trash innerrloopc and outerloopc.
 ldi innerrloopc,$78
 ldi outerloopc,$D0
 rjmp genericdelay ;This instruction not really needed but kept for uniformity.


 
genericdelay:;Actual sampling delay routine as set up by calling routine
    ;Enter with outer loop value in innerrloopc and
    ;outer loop value in outerloopc.
outergeneric:
 sbic usr,rxc  ;Check for byte in UART from terminal
 rjmp delay1us ;and if char is waiting, shorten loop;
 mov temp,outerloopc
innergeneric:
 nop
 dec temp
 brne innergeneric
 dec innerrloopc
 brne outergeneric
 nop
 rjmp delay1us


wait1ms:;delay for 1.003 ms (based on 10 mhz clock). including call and return). Trash innerrloopc and outerloopc.
 ldi innerrloopc,$0D
outerloop1:
 ldi outerloopc,0
innerloop1:
 dec outerloopc
 brne innerloop1
 dec innerrloopc
 brne outerloop1
 ret


wait5ms:;delay for 5.0122 ms (based on 10 mhz clock). including call and return). Trash innerrloopc and outerloopc.
 ldi innerrloopc,$41
outerloop3:
 ldi outerloopc,0
innerloop3:
 dec outerloopc
 brne innerloop3
 dec innerrloopc
 brne outerloop3
 ret
 

wait1000ms:;delay for 1000ms (based on 10 mhz clock). including call and return). Trash innerrloopc and outerloopc.

 ldi temp,$36
mostouter:
 ldi innerrloopc,$0
outerloop31:
 ldi outerloopc,0
innerloop31:
 dec outerloopc
 brne innerloop31

 dec innerrloopc
 brne outerloop31
 dec temp
 brne mostouter
ret


 
FillArray:;Fill array with pwmstartval
 ldi YL,arraystart
 ldi YH,$00   ;Corrected -was ldi YH,$FF
 mov temp,pwmstartval
fillmore:
 st Y+,temp
 cpi YL,arrayend+1
 brne fillmore
 ret


scanspan:;Fill array with analog measurements
 rcall assignsampledelay
 rcall capture
 rcall dumparray
 rjmp MainLoop
 
       
capture:   ;Enter with ZH,ZL pointing to delay routine  
    ;For 1 microsecond sampling interval, point to oneus
    ;Values of samples are stored in RAM from location
    ;arratystart to arrayend.
     
 rcall FillArray ;Preset array.
 mov pwmval,pwmstartval;PWM = 64
 ldi temp,$00
 out OCR1AH,temp ;Zero high register
 mov temp,pwmval
 cpi pwmstartval,255 ;If 255 steps, don't shift left
 breq ds1
 lsl temp
 cpi pwmstartval,$128;If 128 steps, shift left once
 breq ds1
 lsl temp  ;If 64 steps, shift left twice
ds1:      
 out OCR1AL,temp ;Set pwm value
 rcall wait5ms
 sbrs flagreg,1
 rjmp us2
 rcall wait5ms
 rcall wait5ms
 rcall wait5ms
 rcall wait5ms
us2:

decpwm: dec pwmval   ;PWM = PWM 1 1.
 cpi pwmval,$FF ;If PWM = -1 then finshed capturing.
 breq capturefinsihed  
 ldi temp,$00
 out OCR1AH,temp ;Zero high register
 mov temp,pwmval
 cpi pwmstartval,255;If 255 steps, don't shift left
 breq ds2
 lsl temp
 cpi pwmstartval,128;If 128 steps, shift left once
 breq ds2
 lsl temp  ;If 64 steps, shift left twice
ds2:
  out OCR1AL,temp ;Set pwm value
 rcall wait1ms  ;Wait for small step settling time
 sbrs flagreg,1
 rjmp us1
 rcall wait1ms
 rcall wait1ms
 rcall wait1ms
 rcall wait1ms
us1:
 ldi YH,$00  ;Y = array start.
 ldi YL,arraystart-1  
 rcall waitingfortrigger

nextydelay:   ;Includes NOPs come here if pwmval is not to be stored on this cycle
 inc YL  ;Inc YL and see if array boundary has been exceeded.
 cpi YL,arrayend+1
 breq decpwm  
 nop   ;Equalize time between samples for pwmval saved and not saved
 nop

nexty:    
 ijmp   ;Jump to delay routine - address already loaded into Z reigster
delay1us:
 sbic ACSR,$05 ;CAPTURE COMPARITOR OUTPUT STATE
 rjmp nextydelay  
 st Y+,pwmval
 cpi  YL,arrayend+1
 breq decpwm
 rjmp nexty  
 
capturefinsihed:
 ret



waitingfortrigger:
 sbrc flagreg,3
 rjmp notriggerneeded
 sbrc flagreg,2
 rjmp waitingforpostrigger


waitingfornegtrigger:
waitforhigh1:
 sbic usr,rxc  ;Check for byte in UART from terminal
 rjmp quittrigger  ; and return if char waiting (no trigger)
 sbis pind,$06  
 rjmp waitforhigh1
waitforlow1:
 sbic usr,rxc  ;Check for byte in UART from terminal
 rjmp quittrigger  ; and return if char waiting (no trigger)
 sbic pind,$06
 rjmp waitforlow1
 ret


waitingforpostrigger:
waitforlow:
 sbic usr,rxc  ;Check for byte in UART from terminal
 rjmp quittrigger  ; and return if char waiting (no trigger)
 sbic pind,$06
 rjmp waitforlow
waitforhigh:
 sbic usr,rxc  ;Check for byte in UART from terminal
 rjmp quittrigger  ; and return if char waiting (no trigger)
 sbis pind,$06
 rjmp waitforhigh
 ret


notriggerneeded:;Come here when in free run
 ret

quittrigger: ;Quit trigger routine and  data collection
 ldi YL,arrayend ;Set YL to look like all points tried
 ret



MainLoop:
 ldi temp,':' ;Emit prompt
 rcall emitchar
mlw:
 sbis usr,rxc  ;Check for byte in UART from terminal
 rjmp mlw

     ;Get and handle incoming byte from terminal.
    ;Results in rage 0..$D  use as index to select sampling
    ;delay and results above $D interpret as command.
 in temp,udr ;Read byte
 rcall emitchar ;Echo byte
 andi  temp,$5F ;Upper-case character
 cpi temp,'C' ;Is it an "C"?
 brne nc2
 rjmp scanspan ;If C capture waveform
nc2:
 cpi temp,'I' ;Is it an "I"?
 brne nc3
 rjmp incrementds ;If I Increment delayselect
nc3:
 cpi temp,'D' ;Is it an "D"?
 brne nc4
 rjmp decrementds ;If D decrement delayselect
nc4:
 cpi temp,'P' ;Is it an "P"?
 brne nc5
 rjmp toggletrigger ;If P toggle trigger polarity
nc5:
 cpi temp,'F' ;Is it an "F"?
 brne nc6
 rjmp setfreerun ;If F set to free run
nc6:
 cpi temp,'V' ;Is it an "V"?
 brne nc7
 rjmp changeres ;If V change voltage resolution
nc7:
 cpi temp,'T' ;Is it an "T"?
 brne nc8
 rjmp settriggered ;If T set trigger/free run flag to triggered
nc8:
 cpi temp,'S' ;Is it an "S"?
 brne nc9
 rcall status  ;If S display status
 rjmp MainLoop
nc9:
 cpi temp,'L'  
 brne nc10
 rjmp togglesettle ;If L toggle settling time between 1X and 5X
nc10:
 cpi temp,$1F ;If ? show menu and status
 brne nc11
 rcall menu
 rjmp MainLoop
nc11:
 cpi temp,'M' ;If M save settings
 brne nc12
 rjmp savesettings
nc12:
 cpi temp,$01 ;If ! restore default settings
 brne nc13
 rjmp restoredefault
nc13:
 cpi temp,'R' ;If R recall stored settings
 brne nc14
 rcall readsettings
 rcall status
 rjmp MainLoop
nc14:

 cpi temp,'E'  
 brne nc15
 rjmp togglelinefeed ;If E toggle linefeed during dump on/off
nc15:
 rcall crlf
 rjmp MainLoop


decrementds:
 rcall crlf
 mov temp,delayselect
 cpi temp,$00
 brne ns6
 rjmp ns7
ns6:
 dec delayselect
ns7:
 ldi     ZH,high(2*timebasemessage); Load high part of byte address into ZH
 ldi     ZL,low(2*timebasemessage); Load low part of byte address into ZL
 rcall  sendstring
 rcall sendtimebasestring
 rjmp MainLoop
 


incrementds:
 rcall crlf
 mov temp,delayselect
 cpi temp,$0C
 brne ns4
 rjmp ns5
ns4:
 inc delayselect
ns5:
 ldi     ZH,high(2*timebasemessage); Load high part of byte address into ZH
 ldi     ZL,low(2*timebasemessage); Load low part of byte address into ZL
 rcall  sendstring
 rcall sendtimebasestring
 rjmp MainLoop


togglesettle:;Toggle settling time between 1X and 5X
 rcall crlf
 sbrs flagreg,1
 rjmp nsx8
 andi flagreg,$FD
 rjmp nsx9
nsx8:
 ori flagreg,$02
nsx9: ldi     ZH,high(2*settletimemessage); Load high part of byte address into ZH
 ldi     ZL,low(2*settletimemessage); Load low part of byte address into ZL
 rcall  sendstring
 ldi temp,'5'
 sbrc flagreg,1
 rjmp notfive
 ldi temp,'1'
notfive:
 rcall emitchar
 ldi temp,$20
 rcall emitchar
 rjmp MainLoop


toggletrigger:;Topggle trigger polarity
 rcall crlf
 sbrs flagreg,2
 rjmp ns8
 andi flagreg,$FB
 rjmp ns9
ns8:
 ori flagreg,$04
ns9: ldi     ZH,high(2*triggermessage); Load high part of byte address into ZH
 ldi     ZL,low(2*triggermessage); Load low part of byte address into ZL
 rcall  sendstring
 ldi temp,'+'
 sbrc flagreg,2
 rjmp notminus
 ldi temp,'-'
notminus:
 rcall emitchar
 ldi temp,$20
 rcall emitchar
 rjmp MainLoop



togglelinefeed:;Topggle linefeed character during dump on/off
 rcall crlf
 sbrs flagreg,0
 rjmp nis8
 andi flagreg,$FE
 rjmp nis9
nis8:
 ori flagreg,$01
nis9: ldi     ZH,high(2*lfmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*lfmessage); Load low part of byte address into ZL
 rcall  sendstring
 ldi temp,'1'
 sbrc flagreg,0
 rjmp notminusi
 ldi temp,'0'
notminusi:
 rcall emitchar
 ldi temp,$20
 rcall emitchar
 rjmp MainLoop


setfreerun:;Set to Free Run
 rcall crlf
 ori flagreg,$08
 ldi     ZH,high(2*freerunmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*freerunmessage); Load low part of byte address into ZL
 rcall  sendstring
 rjmp MainLoop


settriggered:;Set to Free Run
 rcall crlf
 andi flagreg,$F7
 ldi     ZH,high(2*triggeredmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*triggeredmessage); Load low part of byte address into ZL
 rcall  sendstring
 rjmp MainLoop

 
emitchar:;Send character with two stop bits + extended delay
 push temp
 sbis usr,udre   ;wait until the register is cleared
 rjmp emitchar    
 cbi ucr,0  ;Set 9th bit to zero (second stop bit)
 sbi ucr,2  ;Set to send 9 data bits
 out udr,temp ;send the character
 ldi temp,$18 ;TIME DELAY ROUTINE STARTS HERE
 mov dx2,temp
ld2a:
 clr dx1  ;   (To reduce receiver buffer overrun)
ld1a:
 dec dx1
 brne ld1a
 dec dx2
 brne ld2a  ;TIME DELAY ROUTINE ENDS HERE
 pop temp
 ret
 

sendstring:   ; Call with location of string in Z
 push temp  ; Preserve temp in case its needed
morestring:
 lpm    ; Load byte from program memory into r0
 tst r0               ; Check if we've reached the end of the message
 breq finishsendstering     ; If so, return
 mov temp,r0
 rcall emitchar
 adiw ZL,1               ; Increment Z registers
 rjmp morestring
finishsendstering:
 pop temp
 ret


sendnumber:;Enter with value to be sent in temp, sends as three digit ascii
 ;via serial port.
 mov U,temp  ;Enter with 8 bit value in U, Exits with numerals in H,T,U
 clr H  ;(Hundreds, Tens, and Units).
 clr T
anotherh:
 subi  U,100  ;Find out how many hundreds in U.
 brcs hdone
 inc H
 rjmp anotherh
hdone:
 subi U,-100  ;Subtracted one too many -add back.
anothert:
 subi U,10
 brcs tdone
 inc T
 rjmp anotherT
tdone:
 subi U,-10  
 cpi H,0  
 breq dontsendh
 subi H,-48
 mov temp,H
 rcall emitchar
 cpi t,0
 brne dontsendh
 ldi temp,$30 ;If U=0 then don't emit this zero
 rcall emitchar
dontsendh:
 cpi t,0
 breq dontsendt
 subi T,-48
 mov temp,T
 rcall emitchar
dontsendt:
 subi U,-48
 mov temp,U
 rcall emitchar
 ret


dumparray:;Dump contents of array: Y location, comma, data value.

 ldi     ZH,high(2*idmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*idmessage); Load low part of byte address into ZL
 rcall  sendstring
 rcall status  ;Dump status
 ldi temp,$20 ;Send a space
 rcall emitchar
 ldi ZL,seriallowE ;Get sequence number from EEPROM int Y register
 rcall LoadEEByte
 mov YL,temp  
 ldi ZL,serialhighE
 rcall LoadEEByte
 mov YH,temp
 adiw YL,1  ;Increment sequence number
 mov temp,YH  ;Save sequence number in EEPROM
 rcall SaveEEByte
 ldi ZL,seriallowE
 mov temp,YL
 rcall SaveEEByte
 ldi     ZH,high(2*sequencemessage); Load high part of byte address into ZH
 ldi     ZL,low(2*sequencemessage); Load low part of byte address into ZL
 rcall  sendstring
 mov temp,YH  ;Print new sequence number to screen
 rcall sendnumber
 ldi temp,'.'
 rcall emitchar
 mov temp,YL
 rcall sendnumber
 ldi pwmval,1 ;Sample number for output.
 ldi YL,arraystart ;Pointer to data.
 ldi YH,$00

dumpmore1:
 ldi temp,$0D
 rcall emitchar
 ldi temp,$0A
 sbrc flagreg,0
 rcall emitchar ;Send line feed if flagreg 0 is set
 mov temp,pwmval ;Send sample number.
 rcall  sendnumber
 
 ldi temp,$20
 sbrc flagreg,0 ;If linefeed flag is clear, don't send space
 rcall emitchar
 ldi temp,$09
 sbrs flagreg,0  
     rcall emitchar ;If linefeed flag is set, don't send tab
 ld temp,Y+
 rcall sendnumber
 inc pwmval
 cpi YL,arrayend+1 ;For this to work, array has to be completely within one page
 brne dumpmore1
 rcall crlf
 ret
 
 
crlf:  ;Send return and linefeed
 ldi temp,$0A ;Send return and line feed for each point
 rcall emitchar
 ldi temp,$0D
 rcall emitchar
 ret

changeres:;Change voltage resolution
    ;Effectively interpret pwmstartval and
    ;change based on its current content.
    ;Allowed values:64, 128, and 255
 rcall crlf
 cpi pwmstartval,64
 brne not64
 ldi pwmstartval,128
 ldi ZH,high(2*msg128); Load high part of byte address into ZH
 ldi     ZL,low(2*msg128); Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres
not64:
 cpi pwmstartval,128
 brne not128
 ldi pwmstartval,255
 ldi ZH,high(2*msg255); Load high part of byte address into ZH
 ldi     ZL,low(2*msg255); Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres
not128:

 cpi pwmstartval,255
 brne not255
 ldi pwmstartval,64
 ldi ZH,high(2*msg64); Load high part of byte address into ZH
 ldi     ZL,low(2*msg64); Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres
not255:
 

donechangeres:
 rjmp MainLoop

msg64:
 .db "64 steps"
 .db 00,00

msg128:
 .db "128 steps "
 .db 00,00

msg255:
 .db "255 steps "
 .db 00,00


showdelaylist:;Pointer into strings
 .dw showdelay1us
 .dw showdelay2us
 .dw showdelay5us
 .dw showdelay10us
 .dw showdelay20us
 .dw showdelay50us
 .dw showdelay100us
 .dw showdelay200us
 .dw showdelay500us
 .dw showdelay1000us
 .dw showdelay2000us
 .dw showdelay5000us
 .dw showdelay10000us

showdelay1us:
 .db "1 us,  MINIMUM"
 .db 00,00


showdelay2us:
 .db "2 us"
 .db 00,00

showdelay5us:
 .db "5 us"
 .db 00,00

showdelay10us:
 .db "10 us "
 .db 00,00

showdelay20us:
 .db "20 us "
 .db 00,00

showdelay50us:
 .db "50 us "
 .db 00,00

showdelay100us:
 .db "100 us"
 .db 00,00

showdelay200us:
 .db "200 us"
 .db 00,00

showdelay500us:
 .db "500 us"
 .db 00,00

showdelay1000us:
 .db "1 ms"
 .db 00,00

showdelay2000us:
 .db "2 ms"
 .db 00,00


showdelay5000us:
 .db "5 ms"
 .db 00,00


showdelay10000us:
 .db "10 ms,  MAXIMUM "
 .db 00,00


idmessage:
 .db $0A,$0D
 .db     "Waveform capture 03.03.26   Dick Cappels, projects@cappels.org"
 .db $00,$00

hellomessage:
 .db $0A,$0D
 .db $0A,$0D
 .db "C..Capture & dump "
 .db $0A,$0D
 .db "I..Increase sample time "
 .db $0A,$0D
 .db "D..Decarese sample time "
 .db $0A,$0D
 .db "T..Triggered mode "
 .db $0A,$0D
 .db "F..Free run mode"
 .db $0A,$0D
 .db "P..Trig. Polarity toggle"
 .db $0A,$0D
 .db "V..Voltage res"
 .db $0A,$0D
 .db "L..Settle time toggle "
 .db $0A,$0D
 .db "E..Toggle line feed "
 .db $0A,$0D
 .db "M..Save settings"
 .db $0A,$0D
 .db "R..Recall saved settings"
 .db $0A,$0D
 .db "!..Default settings "
 .db $0A,$0D
 .db "S..Status "
 .db $0A,$0D
 .db "?..Menu & status"
 .db $0A,$0D
 .db      00,00


lfmessage:
 .db     "Linefeed  = "
 .db      00,00


timebasemessage:
 .db     "Timebase  = "
 .db      00,00

 
triggermessage:
 .db     "Trigger polarity  = "
 .db      00,00

triggeredmessage:
 .db     "Triggerred"
 .db      00,00

freerunmessage:
 .db     "Free Run"
 .db      00,00


settletimemessage:
 .db     "Settling time = "
 .db      00,00

shsetmsg:
 .db     "Stl="
 .db      00,00

sequencemessage:
 .db     "  Meas ID="
 .db      00,00

sendtimebasestring:
 push temp  ; Preserve temp in case its needed

 ldi ZH,high(2*showdelaylist);Load high part of byte address into ZH
 ldi ZL,low(2*showdelaylist);Load low part of byte address into ZL
 mov temp,delayselect
 lsl temp
 add ZL,temp
 brcc nca1  ;If carry results, take care of it.
 inc ZH  
nca1:
 lpm   ;Get address from pointer (r0) into ZH.ZL
 push r0
 adiw ZL,1  
 lpm
 mov ZH,r0
 pop ZL  ;Now, ZL should be pointing to the string
 lsl ZL  ;Move left one position to point to word
 rol ZH
 
moestring:
 lpm    ; Load byte from program memory into r0
 tst r0               ; Check if we've reached the end of the message
 breq finishsendstering1    ; If so, return
 mov temp,r0
 rcall emitchar
 adiw ZL,1               ; Increment Z registers
 rjmp moestring
finishsendstering1:
 pop temp
 ret

menu: ;Show commands and status
 ldi     ZH,high(2*idmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*idmessage); Load low part of byte address into ZL
 rcall  sendstring
 ldi     ZH,high(2*hellomessage); Load high part of byte address into ZH
 ldi     ZL,low(2*hellomessage); Load low part of byte address into ZL
 rcall  sendstring
 rcall status
 ret


status:
 rcall crlf
 rcall sendtimebasestring
 ldi temp,$20
 rcall emitchar
 cpi pwmstartval,64
 brne not641
 ldi ZH,high(2*msg64); Load high part of byte address into ZH
 ldi     ZL,low(2*msg64) ; Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres1
not641:
 cpi pwmstartval,128
 brne not1281
 ldi ZH,high(2*msg128); Load high part of byte address into ZH
 ldi     ZL,low(2*msg128); Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres1
not1281:
 cpi pwmstartval,255
 brne not2551
 ldi ZH,high(2*msg255); Load high part of byte address into ZH
 ldi     ZL,low(2*msg255); Load low part of byte address into ZL
 rcall  sendstring
 rjmp donechangeres1
not2551:
donechangeres1:
 ldi temp,$20
 rcall emitchar
 ldi temp,'F'
 sbrs flagreg,3
 ldi temp,'T'
 rcall emitchar
 ldi temp,$20
 rcall emitchar
 ldi temp,'+'
 sbrs flagreg,2
 ldi temp,'-'
 rcall emitchar
 ldi temp,$20
 rcall emitchar
 ldi     ZH,high(2*shsetmsg); Load high part of byte address into ZH
 ldi     ZL,low(2*shsetmsg); Load low part of byte address into ZL
 rcall  sendstring
 ldi temp,'5'
 sbrs flagreg,1
 ldi temp,'1'
 rcall emitchar
 ldi temp,$20
 rcall emitchar
    ; Status of Linefeed flag
 ldi     ZH,high(2*lfmessage); Load high part of byte address into ZH
 ldi     ZL,low(2*lfmessage); Load low part of byte address into ZL
 rcall  sendstring
 ldi temp,'1'
 sbrs flagreg,0
 ldi temp,'0'
 rcall emitchar
 ret
 
 
savesettings:
 rcall crlf
 mov temp,delayselect;Store sampling rate
 ldi ZL,delayselectE
 rcall saveEEByte
 mov temp,pwmstartval
 ldi ZL,pwmstartvalE
 rcall saveEEByte ;Store pwmstartval (voltage resolution).
 mov temp,flagreg ;Store flagreg.
 ldi ZL,flagregE
 rcall saveEEByte
 mov temp,pwmstartval;Create checksum by subtracting flagreg then samplerate from pwmstartval without carry.
 sub temp,flagreg
 sub temp,delayselect
 ldi ZL,checksumE
 rcall saveEEByte ;Store checksum.
 ldi temp, $20
 rcall emitchar ;Send "OK" to terminal
 ldi temp,'O'
 rcall emitchar
 ldi temp,'K'
 rcall emitchar
 rjmp MainLoop



readsettings:;Restore machine state if saved and contents of EEPROM are ok.

 ldi ZL,pwmstartvalE
 rcall loadEEByte ;Read pwmstartval (voltage resolution).
 mov YL,temp

 ldi ZL,flagregE
 rcall loadEEByte
 mov YH,temp  ;Read flagreg.
 
 ldi ZL,delayselectE
 rcall loadEEByte
 mov XH,temp  ;Read delay select register ..
 
 mov XL,YL
 sub XL,YH  ;Data checksum is in XL.
 sub XL,XH
 
 ldi ZL,checksumE
 rcall loadEEByte ;Read stored checksum.
 cp temp,XL  ;If checksum is good,then store otherwise, skip.
 brne dontload

 mov pwmstartval,YL
 mov flagreg,YH
 mov delayselect,XH
 
 ldi temp,'*' ;Type asterisk to terminal after loading.
 rcall emitchar
dontload:
 ret


restoredefault:
 ldi temp,$02 ;Default scan rate is 10 us per sample.
 mov delayselect,temp
 ldi flagreg,defaultflags;Set flag to free run, trigger set to positive edge,
    ;and settling time of 5X (5 ms filter)
 ldi pwmstartval,255 ;Initialize voltage resolution
 rcall status
 rjmp MainLoop

SaveEEByte:;Save byte to EEPROM
    ;Enter with data in temp and address in ZL.
wrat1: sbic eecr,eewe ;Wait for EEPROM write to not be busy
 rjmp wrat1
 out eear,ZL    
 out eedr,temp ;Set up the  write data
 sbi eecr,eemwe
 sbi eecr,eewe ;Trigger the write
 dec ZL
 ret

LoadEEByte:;Read byte from EEPROM
    ;Enter with address in ZL. Exits with data in temp
 
wratz1: sbic eecr,eewe ;Wait for EEPROM write to not be busy
 rjmp wratz1
 out eear,ZL  ;move to EEPROM address register
 sbi eecr,eere ;Trigger the read
 in temp,eedr ;Get the data into temp
 ret


Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.