// Arduino O-Scope program V1.4.4 // by Mark Bauer // Professor of Practice // University of Nebraska Lincoln // mark@engr.unl.edu // You are free to use this for what ever, // Send me a note and tell me where it ended up. // created 7/8/2020 // update 7/12/20 (sent v1.0 version to people) // update 7/13/20 version changed to V1.1 // changes // added clock frequency to display line // updated settings on UART, it didn't work with my Offical Uno. Xtal off by a bit // update 7/15/20 // added Sn command which divides the 10,000 sample clock by n. S1000 will only sample once per second. // n over 50 // added min and max values on headder for analog inputs // update 7/16/20 // added timer 1 to give another clock output. It is a 16 bit timer. // update 7/22/20 // added ? to show version information // added state 100 to look at trigger mode and flip to proper state to start. // When th5 (or any other n) is entered it will force a start // update 1.1.1 7/25/20 // TH command didn't work if trigger is don't care fixed // update 1.2.0 7/26/20 // changed the clocks to CA CB and CC with common functions // added the two other digital ports // unified commands for the clocks as much as I can // fixed output line // added what for unknown commands. // update 1.2.1 7/28/20 // change the pretrigger command from N to P now that P isn't used // update 1.2.2 7/29/20 // time between trigger and measure didn't work at slower modes, fixed. // update 1.2.3 7/30/20 // only draw trigger and measure levels if source is A0 or A1. // update 1.2.4 8/1/20 // can now set vref to 1.1 vs the Vcc. (both channels must be same, hardware issue) // update 1.2.5 8/4/20 // status line added H and L to indicate what range has been selected. // updated 1.2.6 8/7/20 // fixed when you come out of scroll mode, bad state // added optional level when you set rise or fall trigger // update 1.3.0 8/9/20 // First attempt to get FFT installed // we only get the fft of a 256 points (not enough ram) // after fft that is 128 data points at 19.53Hz per point // double each point on the output so we get 256 points wide // DC term is at first division this would make the divisions // 976.5 at first division, 1,953 at second. // update 1.3.1 8/15/20 // fft works now, I get good plots with square waves in. // 200Hz input would get peaks at // 600Hz // 1000Hz // 1400Hz // 1800Hz // update 1.3.2 8/16/20 // added f2 option that only takes half the waveform vs the average // this doubles the frequency. // update 1.3.3 9/2/20 // changed default of measure mode to MX // added command to set freq // update 1.3.4 12/24/20 // needed to fix trigger and measure level to work with scaled voltages // I think calibrated voltage now works // frequency input now allows 2.34K or 0.12M // update 1.4.0 12/26/20 // think it is all up and running with all features // did add comments after that but no code changes // update 1.4.1 1/2/21 // lots of comments added // separated out dumpLine from dumpLineFFT // separated out dumbHeadder from dumpHeadderFFT // reduced serial input ring buffer to 16 bytes (was 64) // update 1.4.2 1/3/21 // added eeprom read and write routines to save vCal through reboot // update 1.4.3 1/4/21 // fixed the digital plot lines, high wasn't high enough. // update 1.4.4 1/14/21 // added logic functions for clock D12 output High Low and Pulse // changed how the LED blinks, fast while waiting for trigger, slow when waiting for user // Commands that are useful (case insensitive) /* Quick command list: • Just the return key by itself should cause a single sample • P 400 Pre trigger to 400 samples. Range 0 → 65535 • TL 2000 Trigger level to 2000mV. Range 0→5500 (Vcc actually) • TF Trigger on falling edge (optional set level also) • TR Trigger on rising edge (optional set level also) • TX Trigger to free run • TS 0 Trigger source 0 → 8 • TH 5 Trigger hold off time to 5 seconds 0 is off. Range 1→ 60 • ML 1000 Measure level to 1000mV • MF Measure on falling edge (optional set level) • MR Measure on rising edge (optional set level) • MX Measure off (not sure this is useful) • MS Measure source 0 → 8 • SC 10 Drop the sample rate from 10K samples per second to 1K SC 2 would give us 5K SC 50 and slower will flip to scroll mode without trigger SC 1000 would give us 10 samples per second in scroll mode • SN Change to normal trigger mode. • SS Change to smooth scroll mode. Only works with SC 50 or greater • CA clock A output is on D10 • CAF 1000 set the clock frequency to 1KHz 1 -> 8000000 square wave output • CAD 2 set clock divider to 1uS per tick 0 = off 1 = 125nS per tick 2 = 1uS per tick 3 = 8uS per tick 4 = 32uS per tick 5 = 128uS per tick • CAP 50 set clock period to 50 in number of ticks • CAH 4 set the number of ticks output is high to 4 per period • CB clock B output is on D5 • CBF 2.5K set the clock frequency to 2,500 Hz 30Hz -> 4Mhz • CBD 3 set clock divider to 8uS per tick 0 = off 1 = 125nS per tick 2 = 1uS per tick 3 = 8uS per tick 4 = 32uS per tick 5 = 128uS per tick • CC clock C output is on D12 fixed divider at 100uS • CCF 30 set the clock frequency to 30Hz limited range 1 -> 1000 • CCP 10K set the clock period to 1 second, range is 2->65535 • CCH 100 set number of ticks high in each cycle, this would be 10mS must be less than CCPn 1->CCPn-1 • CSF 200 set sine wave to 200Hz. Frequency limit of 10->2000Hz. Uses clock both A and B • F 0 Normal mode (default) • F 1 FFT only use the first 256 points of waveform magnitude only • F 2 FFT only use the first 256 points of waveform show phase and magnitude • V 5125 Set calibration voltage in mV to 5.125V only allows 4500 -> 5500 (saved in EEPROM) Logic output is on the same pin as clock C D12 • LH set logic output to high 1 works also • LL set logic output to low 0 works also • LPn put a pulse out low high low n is the number of 100uS clicks in width default 10mS or 100x100uS • L put a pulse out with previous n. Source num Desc Arduino pin 0 Analog port 0 A0 1 Analog port 1 A1 2 digital port 2 D2 3 digital port 3 D3 4 digital port 4 D4 5 digital port 5 D6 6 Clock A Output D10 7 Clock B Output D5 8 Clock C Output D12 */ // You can give it a command while it is looking for the trigger. TX will say ignore the trigger and just go. // timer 0 is clock B (8 bit timer) // timer 1 is clock A (16 bit timer) // timer 2 generates the 10Khz periodic interrupt that runs everything. // PORT Arduino smt-pin function what we use it for // label // PORTB.0 0x01 8 12 CLKO/ICP1 10K pulse sync with conversions // PORTB.1 0x02 9 13 OC1A N/C // PORTB.2 0x04 10 14 OC1B clock A output (timer 1 driven) // PORTB.3 0x08 11 15 MOSI/OC2A N/C // PORTB.4 0x10 12 16 MISO clock C output (software clock) // PORTB.5 0x20 13 17 SCK LED1 // PORTB.6 0x40 7 XTAL1 // PORTB.7 0x80 8 XTAL2 #define SETUP_PORTB 0x3F // 0011 1111 #define SETUP_PORTB_OUT 0x00 #define LED1 0x20 // PORTC.0 0x01 A0 23 ADC0 Scope channel 0 CH0 // PORTC.1 0x02 A1 24 ADC1 Scope channel 1 CH1 // PORTC.2 0x04 A2 25 ADC2 N/C // PORTC.3 0x08 A3 26 ADC3 N/C // PORTC.4 0x10 A4 27 ADC4 N/C // PORTC.5 0x20 A5 28 ADC5 N/C // PORTC.6 0x40 29 RESET #define SETUP_PORTC 0x3C // 0011 1100 #define SETUP_PORTC_OUT 0x00 // PORTD.0 0x01 0 30 Rx RX SERIAL DATA // PORTD.1 0x02 1 31 Tx TX SERIAL DATA // PORTD.2 0x04 2 32 INT0 digital in CH2 // PORTD.3 0x08 3 1 OC2B/INT1 digital in CH3 // PORTD.4 0x10 4 2 XCK/T0 digital in CH4 // PORTD.5 0x20 5 9 OC0B/T1 clock B output (timer 0) // PORTD.6 0x40 6 10 OC0A/AIN0 digital in CH5 // PORTD.7 0x80 7 11 AIN1 N/C #define SETUP_PORTD 0xA0 // 1010 0000 #define SETUP_PORTD_OUT 0x00 #define LED2 0x80 // ADC6 19 ADC6 // ADC7 22 ADC7 volatile uint8_t ticks; // only updated in the interrupt routine once every 100uS volatile uint8_t blinkCount; // only updated in the interrupt routine once every 25.6mS uint8_t ticksX; // only updated outside the interrupt routine uint8_t blinkRate; // #define BUF_SIZE 512 // size of the ring buffers #define BUF_MASK 0x1FF // used to keep us in the buffer // Gets messy here with the ring buffers!!!! // Normal operation needs 2 ring buffers each 512 bytes in size // but I need 2 16 bit arrays to deal with FFT. // The solution used here assigns the 512 byte array and the 256 element // signed 16 bit array to the same memory space. For FFT we drop to 256 // samples and just be really careful about how we fill it. union dataBlockX { volatile uint8_t u8[BUF_SIZE]; volatile int16_t s16[BUF_SIZE/2]; } dataBlock; union dataBlockX b0; // will be both Analog channel 0 and the imaginary part of FFT union dataBlockX b1; // will be both Analog channel 1 and the real part of FFT volatile uint8_t adBufD2[BUF_SIZE/8]; // bit buffer for logic volatile uint8_t adBufD3[BUF_SIZE/8]; volatile uint8_t adBufD4[BUF_SIZE/8]; volatile uint8_t adBufD5[BUF_SIZE/8]; const uint8_t bits[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; const uint8_t cbits[] = {~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80}; // we need a special version of the sin() function it uses this lookup table const uint16_t slt32[33] = { 0x0001,0x0649,0x0C8D,0x12CA,0x18FB,0x1F1C,0x252B,0x2B23, 0x30FF,0x36BE,0x3C5B,0x41D3,0x4722,0x4C46,0x5139,0x55FC, 0x5A89,0x5EDE,0x62F9,0x66D7,0x6A76,0x6DD1,0x70EB,0x73BE, 0x764A,0x788C,0x7A86,0x7C33,0x7D91,0x7EA7,0x7F68,0x7FE3,0x8000}; // arctan2() lookup table const int16_t iatanTab[] = { 331, 974,1616,2245,2858,3453,4024,4570, 5091,5588,6057,6499,6916,7310,7679,8024}; uint8_t fftMode; // 0 is off, 1 is just magnitude, 2 is mag and phase volatile uint16_t adBufPtr; // this is where the next a/d convert point goes into adBufX volatile uint16_t adCount; // how many points are left to sample counts down after trigger // if we load adCount with BUF_SIZE/2 it will show pre trigger volatile uint16_t adBufPtrX; // used in scroll mode volatile uint8_t adSampling; // 0 is off 1 is sampling volatile uint16_t adSkipF; // default to 1 to sample every 100uS volatile uint16_t adSkipFR; // reload value for adSkipF volatile uint8_t adTrig; // state machine for trigger stuff uint8_t adTrigMode; // mode user selected for trigger // 0 rising // 1 falling // 2 unknown, prints X volatile uint16_t adTrigPre; // 0 is all pre 511 is all after uint16_t adTrigLevel; // for display, converted to mV level volatile uint8_t adTrigLevel8b; // for level tests we are 8 bit here volatile uint8_t adTrigSource; // 0 is AD0, 1 is AD1, 2 is D2, 3 is D3 ... 8 clock C uint8_t adTrigHold; // 0 is off n is the number of seconds before re-trigger uint8_t adMeasMode; // user mode for measure trigger // 0 rising // 1 falling // 2 unknown, prints X volatile uint8_t adMeasSource; // 0 is AD0, 1 is AD1, 2 is D2, 3 is D3 ... 8 clock C volatile uint8_t adRead; // which channel is being read. volatile uint16_t adTP; // trigger point in the ring buffer volatile uint8_t adMeas; // state machine for making measurements uint16_t adMeasLevel; // for display, it is in mV volatile uint8_t adMeasLevel8b; // for use it is 8 bit to match A/D converter volatile uint16_t adMP; // measure point in the ring buffer, to calc how long volatile uint8_t txBuf[64]; // serial output ring buffer volatile uint8_t txBufInPtr; // where print routines put stuff in volatile uint8_t txBufOutPtr; // where UART take things out volatile uint8_t rxBuf[16]; // serial input ring buffer volatile uint8_t rxBufInPtr; // where next character we type goes in to ring buffer volatile uint8_t rxBufOutPtr; // where program gets the character we just typed in volatile uint16_t clockCTicks; // clock C is just based on 10Khz interrupts volatile uint16_t clockCPeriod; // set by user, 10,000 is 1 second volatile uint16_t clockCHigh; // how long it is high volatile uint8_t clockCType; // 0 manual H and L, 1 single pulse, 2 repeating char cmdBuf[32]; // characters from keyboard out of ring buffer uint8_t cmdBufPtr; // uint8_t unknownCmd; // flag to let them know we have no idea what they want uint32_t clockAFreq; // if we set a clock to a frequency, it is here uint32_t clockBFreq; uint32_t clockCFreq; uint8_t clockAMode; // 0 is off, 1 is freq mode, 2 is pulse mode uint8_t clockBMode; uint8_t clockCMode; volatile uint16_t timerX; // 100uS tick count down to time seconds volatile uint8_t timerSec; // second count down timer uint8_t a0Min; // minimum value on a0 (just 8 bit A/D value) uint8_t a0Max; // maximum value on a0 uint8_t a1Min; // minimum value on a1 uint8_t a1Max; // maximum value on a1 uint16_t vCal; // this is Vcc in mV uint16_t fftPeak; // where the peak of the FFT was uint16_t fftPeakV; // how big the peak was of FFT // ********************************************************* // ********************************************************* // Interrupt routines // ********************************************************* // ********************************************************* ISR(TIMER2_COMPA_vect) { // interrupt every 100uS ticks++; if(ticks == 0){ blinkCount++; if(blinkCount > blinkRate){ blinkCount = 0; PINB = 0x20; // this toggles the LED } } ADMUX = 0x60; // select A0 for conversion ADCSRA = 0xCD; // always start a conversion even if we don't use the data adRead = 0; // clock C output. switch(clockCType){ case 0: // manual bit changed in command break; case 1: // single pulse if(clockCTicks < 0xFFFF){ clockCTicks++; } if(clockCTicks < clockCHigh){ PORTB |= 0x10; } else { PORTB &= ~0x10; } break; case 2: clockCTicks++; if(clockCTicks >= clockCPeriod){ clockCTicks = 0; } if(clockCTicks < clockCHigh){ PORTB |= 0x10; } else { PORTB &= ~0x10; } break; } // timer countdown for allowing retrigger if(timerX){ timerX--; } else { timerX = 10000; if(timerSec){ timerSec--; } } PORTB |= 0x01; } // The A/D converter interrupt. // The A/D converter runs above the 200Khz limit, but we only want the top 8 bits. // We sample continuously and stop n points after trigger // samples are kept in circular buffer that holds 512 samples, limited by the memory in the system. // Individual conversions take 30uS, channel 0 triggers channel 1 so the are as close as I can get them // we start a new set of conversions every 100uS. There are 512 points in the buffer so we only get 51.2uS of signal // The A/D converter is started in the 100uS interrupt routine and this interrupt occurs when it is done with A0. // this routine restarts the A/D conversion on A1 and this occurs again at the end of it. SIGNAL(ADC_vect){ // this signals the A/D converter is done, called 20,000 times/sec static uint8_t v0,v1,dx; uint8_t trig; uint8_t meas; uint8_t j; if(adRead){ // are we done with the A1 channel? (the false happens first) v1 = ADCH; if(adSampling){ // we need to save all 4 channels of data to the ring buffers and update the pointer adSkipF--; // If we want to go slower than 10K samp/sec adSkip is used to count how many we skip if(adSkipF == 0){ // ok we really want to save the data adSkipF = adSkipFR; b0.u8[adBufPtr] = v0; // dump it into the ring buffer b1.u8[adBufPtr] = v1; // all 4 digital ports are in dx, that was loaded on the first A/D interrupt if(dx & 0x04){ adBufD2[adBufPtr >> 3] |= bits[adBufPtr & 0x07]; } else { adBufD2[adBufPtr >> 3] &= cbits[adBufPtr & 0x07]; } if(dx & 0x08){ adBufD3[adBufPtr >> 3] |= bits[adBufPtr & 0x07]; } else { adBufD3[adBufPtr >> 3] &= cbits[adBufPtr & 0x07]; } if(dx & 0x10){ adBufD4[adBufPtr >> 3] |= bits[adBufPtr & 0x07]; } else { adBufD4[adBufPtr >> 3] &= cbits[adBufPtr & 0x07]; } if(dx & 0x40){ adBufD5[adBufPtr >> 3] |= bits[adBufPtr & 0x07]; } else { adBufD5[adBufPtr >> 3] &= cbits[adBufPtr & 0x07]; } adBufPtr++; // manage the A/D converter ring buffers adBufPtr &= BUF_MASK; // don't let it walk off the end if(adCount){ // keep track of how many samples we need after a trigger adCount--; } if(adTrig){ // fast blink to let them know we are waiting for trigger // or have triggered blinkRate = 1; } else { // slow blink to let them know waiting for user blinkRate = 10; } } // end of if(adSkipF == 0) } switch(adTrigSource){ // figure out where we get our trigger from case 0: trig = v0; break; // analog port 0 case 1: trig = v1; break; // analog port 1 case 2: if(PIND & 0x04){ trig = 255; } else { trig = 0; } break; // digital port 2 case 3: if(PIND & 0x08){ trig = 255; } else { trig = 0; } break; // digital port 3 case 4: if(PIND & 0x10){ trig = 255; } else { trig = 0; } break; // digital port 4 case 5: if(PIND & 0x40){ trig = 255; } else { trig = 0; } break; // digital port 5 case 6: if(PINB & 0x04){ trig = 255; } else { trig = 0; } break; // clock A case 7: if(PIND & 0x20){ trig = 255; } else { trig = 0; } break; // clock B case 8: if(PINB & 0x10){ trig = 255; } else { trig = 0; } break; // clock C default: trig = 200; } switch(adMeasSource){ // figure out where we get our point to measure to from case 0: meas = v0; break; // analog port 0 case 1: meas = v1; break; // analog port 1 case 2: if(PIND & 0x04){ meas = 255; } else { meas = 0; } break; // digital port 2 case 3: if(PIND & 0x08){ meas = 255; } else { meas = 0; } break; // digital port 3 case 4: if(PIND & 0x10){ meas = 255; } else { meas = 0; } break; // digital port 4 case 5: if(PIND & 0x20){ meas = 255; } else { meas = 0; } break; // digital port 5 case 6: if(PINB & 0x04){ meas = 255; } else { meas = 0; } break; // clock A case 7: if(PIND & 0x20){ meas = 255; } else { meas = 0; } break; // clock B case 8: if(PINB & 0x10){ meas = 255; } else { meas = 0; } break; // clock C default: meas = 0; } switch(adTrig){ case 0: // don't do anything until someone tells us to break; case 1: // waiting for rising trigger to not be there if(trig < adTrigLevel8b){ adTrig = 2; } break; // start the process case 2: // waiting for rising trigger if(trig >= adTrigLevel8b){ // we hit the trigger point adMeas = 0; // clear the adMeas state adTP = adBufPtr; // remember where the trigger point was adTrig = 13; // adCount = adTrigPre; adSkipF = 1; } break; case 11: // wait for falling triger to be false if(trig > adTrigLevel8b){ adTrig = 12; } break; case 12: // waiting for falling trigger if(trig <= adTrigLevel8b){ adMeas = 0; adTP = adBufPtr; adTrig = 13; adCount = adTrigPre; adSkipF = 1; } break; case 13: // triggered, waiting for count if(adCount == 0){ // we got all the counts we wanted adTrig = 90; adSampling = 0; // stop the sampling } break; case 20: // this is for scroll mode adBufPtrX = adBufPtr; adTrig = 21; break; case 21: // scroll mode, we check for this in main() while(1) break; case 90: // sampling has been stopped, wait for data dump before we go on. (in main loop) break; case 91: if(adTrigHold){ timerSec = adTrigHold; adTrig = 92; } else { adTrig = 0; } break; case 92: if(timerSec == 0){ adTrig = 100;} break; case 100: // just start things up, be we need to know if trigger is R, F, or X adSampling = 1; switch(adTrigMode){ case 0: adTrig = 1; break; // rising trigger 'R' case 1: adTrig = 11; break; // falling trigger 'F' case 2: adTrig = 13; // just fake like we got a trigger 'X' adMeas = 0; adTP = adBufPtr; adCount = adTrigPre; break; } break; } switch(adMeas){ // this is just like the adTrig modes but used to figure measurement point case 0: switch(adMeasMode){ case 0: adMeas = 1; break; // want rising to trig case 1: adMeas = 11; break; // want falling to trig case 2: adMeas = 99; break; // don't want anything } break; // rising selection wait here until case 1: // the condition must be false before we see a true if(meas < adMeasLevel8b){ adMeas = 2; } break; // start the process case 2: if(meas >= adMeasLevel8b){ adMP = adBufPtr; adMeas = 99; // just sits there, don't need anything else } break; case 11: if(meas > adMeasLevel8b){ adMeas = 12; } break; // start the process case 12: if(meas <= adMeasLevel8b){ adMP = adBufPtr; adMeas = 99; } break; } PORTB &= ~0x01; adRead = 2; } else { // we haven't read the A1 channel yet, get that started and return from interrupt dx = PIND; // this is where we sample all 4 digital ports at once v0 = ADCL; // it is left justified, but want us to read both parts v0 = ADCH; if(adSampling == 1){ b0.u8[adBufPtr] = v0; // save it in the ring buffer } ADMUX = 0x60 | 0x01; ADCSRA = 0xCD; // start a conversion of the other port PORTB |= 0x08; adRead = 1; } } SIGNAL(USART_UDRE_vect){ // serial output interrupt must have room in the UART if(txBufInPtr == txBufOutPtr){ // are there any characters to output? // no characters to output UCSR0B &= ~0x20; // turn off TX interrupt } else { UDR0 = txBuf[txBufOutPtr++]; // get the character to output txBufOutPtr &= 0x3F; // ring buffer pointer management } } SIGNAL(USART_RX_vect){ // someone sent us a character rxBuf[rxBufInPtr++] = UDR0; // get it from the UART, put in ring buffer rxBufInPtr &= 0x0F; // ring buffer management } // ********************************************************* // ********************************************************* // FFT and the things to make it work with integers // For FFT work, we need some fancy fixed point stuff // floating point would be overkill // Trig functions are a bit different also, it isn't 0->360 degrees // it is 0->65535 to go around the circle. Makes things much easier. // ********************************************************* // ********************************************************* int16_t scale16(int16_t v,uint16_t sf){ // v is the value to be scaled, sf is the scale factor // sf of 0x8000 = 1.0000 int16_t rv; uint8_t i; i = 0; if(v){ while((v & 0xC000) == 0){ v = v << 1; i++; } } rv = 0; while(sf){ if(sf & 0x8000){ rv += v; } v = v >> 1; sf = sf << 1; } rv = rv >> i; return rv; } int16_t iSin(int16_t a){ uint16_t i,x,y,z; uint8_t signX; int16_t r; // things are semetrical, flip it to get us to 0->89.999 if(a < 0){ signX = 1; if(a == -32768){ a = 32767; } else { a = - a; } } else { signX = 0; } // we now only need to worry about 0 -> 179.999 degrees if(a & 0x4000){ // this would be 90 -> 179.999 a = 0x8000 - a; } if(a == 0x4000){ r = 0x7FFF; // sin(90) = 1.0 this is our almost 1.0 } else { // this would be 0 -> 89.99999, so top two bits are already zero i = a >> 9; // we have 14 bits left, only want 5 r = slt32[i]; // get that from the lookup table // interpolate here if needed. // use lower 9 bits of "a" to get to the next step size. x = slt32[i+1] - r; y = a & 0x01FF; // only keep the 9 bits we haven't used y = y << 6; z = scale16(x,y); r += z; } if(signX){ // if it was negative to start with, put it back. r = -r; } return r ; } int16_t iCos(int16_t a){ // this is the same as a rotated sin() return(iSin(a + 0x4000)); } int16_t iatan2X(int16_t y,int16_t x){ // y < x (not equal) int16_t i,j,k; if(y >= x){ k = 8192; } else { j = 0; k=0; for(i=0;i<4;i++){ k = k << 1; y = y << 1; if(y >= x){ y = y - x; k=k|1; } } k = iatanTab[k]; } return(k); } int16_t iatan2(int16_t xi,int16_t xq){ // y is first arg .... x is second, same as atan2(); // returned angle is between -32768 and +32768 2^16 circle int16_t quad,i; quad = 0; if(xq < 0){ quad += 1; xq = -xq; } if(xi < 0){ quad += 2; xi = -xi; } if(xi > xq){ i = xi; xi = xq; xq = i; quad += 4; } // we are now down to one eigth of the circle i = iatan2X(xi,xq); switch(quad){ case 0: break; case 1: i = 32787 - i; break; case 2: i = -i; break; case 3: i = i - 32767; break; case 4: i = 16384 - i; break; case 5: i = i + 16384; break; case 6: i = i - 16384; break; case 7: i = -(i+16384); break; } return(i); } void runFFT(){ int16_t i,j,k,aStep,iAng,t,l,m; int32_t x,a,b,aLast,itr,iti,iwr,iwi; // everything is in the wrong spot for this to work. // we are only doing spectral on A0 // we can only do 256 data points so we will just add all odd points into the even one // move the A0 data 8 bit unsigned into the real part 16 bit signed // we just sum two data points into one. // test data insert // data is in the ring buffer, so location 0 isn't the start j = 0; i = adBufPtr; // data is in the ring buffer, so location 0 isn't the start while(j < 256){ b1.s16[j] = b0.u8[i++]; i &= 0x01FF; b1.s16[j] += -128; // offset so a mid A/D will end up at zero j++; } for(i=0;i<256;i++){ b0.s16[i] = 0;} // clear out the imaginary part j=0; for(i=0;i<(256-1);i++){ /* reverse data */ if(i < j){ itr=b1.s16[j]; b1.s16[j] = b1.s16[i]; b1.s16[i] = itr; } k=128; while(k <= j){ j=j-k; k=k/2; } j+=k; } for(i=0;i<8;i++){ j=2<>1; aStep = 32768/k; iAng = 0; for(l=0;l m){ m = a;} // remember the largest // put in the phase information } b0.s16[i] = iatan2(itr,iti); // this is an angle in my units of -32K->+32K } // scale it and put it back into unsigned 8 bit // only 0->128 are needed and we want to center it a bit // things are in messy places at this point. All of the data we need // has been located at 0->128 of both of the 16 bit arrays. // We will only display half of it so we need to // so convert to magnitude and phase in the 128->255 of the 16 bit array, // but we need it in the 8 bit array so it will be 256->511. fftPeakV = m; j = 256; for(i=0;i<=128;i++){ a = b1.s16[i]; a = (a * 255) / m; if(fftMode & 0x02){ b = b0.s16[i]; // phase information b = (b * 360) / 65536; } else { b = 0; } b0.u8[j] = a; b1.u8[j++] = b; b0.u8[j] = a; // double wide b1.u8[j++] = b; j &= 0x01FF; // j will wrap on last value } for(i=0;i<256;i++){ b0.u8[i] = 0; b1.u8[i] = 0; } adBufPtr = 128; } // ********************************************************* // ********************************************************* // serial I/O routines // ********************************************************* // ********************************************************* void outChar(char c){ // all serial output goes out here uint8_t i; // we need to not outrun our buffer while(((txBufInPtr+1) & 0x3F) == txBufOutPtr); // this will hang until we have room. txBuf[txBufInPtr++] = c; txBufInPtr &= 0x3F; UCSR0B |= 0x20; // turn on the interrupts even if they were already on } void outSpace(){ // I do this enough thought it would save code space outChar(' '); } void crlf(){ // again I use this enough outChar(0x0D); outChar(0x0A); } void printString(char *s){ while(s[0]){ outChar(s[0]); s++; } } void printHex4(uint8_t n){ // one nibble n &= 0x0F; n += '0'; if(n > '9'){ n += 7; } outChar(n); } void printHex8(uint8_t n){ // one byte printHex4(n>>4); printHex4(n); } void printHex16(uint16_t n){ // 16 bit in hex printHex8(n>>8); printHex8(n); } void printDec2Dig(uint8_t n){ // 2 decimal digits uint8_t i1; i1 = n / 10; n = n - (i1*10); outChar(i1+'0'); outChar(n+'0'); } void printDec8(uint8_t n){ // 3 decimal digits uint8_t i1,i2; i2=n / 100; n = n - (i2*100); i1 = n / 10; n = n - (i1*10); outChar(i2+'0'); outChar(i1+'0'); outChar(n+'0'); } void printDec16x4(uint16_t n){ uint16_t i1,i2,i3; i3 = n / 1000; n = n - (i3 * 1000); i2 = n / 100; n = n - (i2 * 100); i1 = n / 10; n = n - (i1*10); outChar(i3+'0'); outChar(i2+'0'); outChar(i1+'0'); outChar(n+'0'); } void printDec16(uint16_t n){ uint16_t i1,i2,i3,i4; i4 = n / 10000; n = n - (i4 * 10000); i3 = n / 1000; n = n - (i3 * 1000); i2 = n / 100; n = n - (i2 * 100); i1 = n / 10; n = n - (i1*10); outChar(i4+'0'); outChar(i3+'0'); outChar(i2+'0'); outChar(i1+'0'); outChar(n+'0'); } void printSDec16(int16_t n){ if(n < 0){ outChar('-'); n = -n; } printDec16(n); } void printDec32z(uint32_t n){ // skip leading zeros. uint8_t i,j; char x[10]; i=0; do{ j = n % 10; n = n / 10; x[i++] = j + '0'; }while(n != 0); i--; while(i < 20){ // will roll over to 255 outChar(x[i]); i--; } } void printDec32x(uint32_t n){ // this lets us look like we are doing floating point // only want ' ' 'K' 'M' 'G' 12345 would be 12.34K 1000000 would be 1.000M char s[10]; char ex; uint8_t i,j,k,m; uint8_t dg; ex=' '; i = 10; do{ i--; s[i] = (n % 10) + '0'; n = n / 10; }while(n != 0); dg = 10-i; m = dg; if(dg > 3){ ex = 'K'; m = dg - 3;} if(dg > 6){ ex = 'M'; m = dg - 6;} if(dg > 9){ ex = 'G'; m = dg - 9;} j = 0; for(k=0;k<4;k++){ if(i < 10){ outChar(s[i++]); } m--; if(m == 0 && ex > ' '){ outChar('.'); m = 100; } } if(ex > ' '){ outChar(ex); } } void printDec16Time(uint16_t n){ // assume that each count is 100uS uint16_t i1,i2,i3,i4; i4 = n / 10000; n = n - (i4 * 10000); i3 = n / 1000; n = n - (i3 * 1000); i2 = n / 100; n = n - (i2 * 100); i1 = n / 10; n = n - (i1*10); outChar(i4+'0'); outChar('.'); outChar(i3+'0'); outChar(i2+'0'); outChar(i1+'0'); outChar(n+'0'); } void showVersion(){ printString("A-O-Scope_V1.4.4 "); crlf(); } uint8_t getArg8(uint8_t i){ uint8_t r; r = 0; while(cmdBuf[i] == ' '){ i++; } // skip white space while(cmdBuf[i] >= '0' && cmdBuf[i] <= '9'){ r = r * 10; r += cmdBuf[i] - '0'; i++; } return r; } uint8_t getHexArg8(uint8_t i){ uint8_t j; uint8_t r; r = 0; while(cmdBuf[i] == ' '){ i++; } while(cmdBuf[i] >= '0'){ r = r << 4; j = cmdBuf[i] - '0'; if(j > 9){ j = j - 7;} r += j; i++; } return r; } uint16_t getArg16(uint8_t i){ uint16_t r; r = 0; while(cmdBuf[i] == ' '){ i++; } // skip white space while(cmdBuf[i] >= '0' && cmdBuf[i] <= '9'){ r = r * 10; r += cmdBuf[i] - '0'; i++; } return r; } // generic input routine // if leads with 0x it will look for hex (upper case only) // allows some engineering notation 1M 1.000M will be 1000000 // 1K is 1000 1.234K is 1234 uint32_t getArgX(uint8_t k){ uint32_t r,rx; int8_t i,j,d; char *x; x = &cmdBuf[k]; while(x[0] == ' '){ x++; } // kill leading white space if(x[0] == '0' && x[1] == 'x'){ r = 0; i=2; while((x[i] >= '0' && x[i] <= '9') || (x[i] >= 'A' && x[i] <= 'F')){ d = x[i] - '0'; if(d > 9){ d += -7;} r = (r << 4) + d; i++; } } else { r = 0; i=0; d=-1; // location of decimal point while((x[i] >= '0' && x[i] <= '9') || x[i] == '.' ){ if(x[i] == '.'){ d = i; } else { r = (r * 10)+x[i]-'0'; } i++; } if(x[i] > 0){ if(d != -1){ j = i - (d+1); } else { j = 0; } switch(x[i]){ case 'K': j = j - 3; break; case 'M': j = j - 6; break; case 'G': j = j - 9; break; } if(j){ if(j > 0){ while(j){ r = r / 10; j--; } } else { while(j){ r = r * 10; j++; } } } } else { if(d != -1){ j = i - (d+1); while(j){ r = r / 10; j--; } } } } return r; } // ********************************************************* // ********************************************************* // We can save things through a power cycle by saving to EEPROM // at the moment only vCal is saved // ********************************************************* // ********************************************************* uint8_t eepromReadByte(uint16_t a){ uint8_t i; i = eeprom_read_byte((uint8_t *)a); return i; } void eepromWriteByte(uint16_t a,uint8_t v){ eeprom_write_byte((uint8_t *)a,v); } void eepromWriteWord(uint16_t a,uint16_t v){ eepromWriteByte(a,v >> 8); eepromWriteByte(a+1,v & 0xFF); } uint16_t eepromReadWord(uint16_t a){ uint16_t r; r = eepromReadByte(a); a++; r = r << 8; r += eepromReadByte(a); return r; } void loadDefaults(){ vCal = eepromReadWord(2); // leave location 0 empty if(vCal < 4500 || vCal > 5500){ vCal = 5000; } } // ********************************************************* // ********************************************************* // Routines to drive the serial plotter of the Arduino environment // ********************************************************* // ********************************************************* void printDivider(uint8_t n){ // divider for the A and B clock. (clock C is always 100uS) switch(n & 0x0F){ case 0: printString("off"); break; case 1: printString("125nS"); break; case 2: printString("1uS"); break; case 3: printString("8uS"); break; case 4: printString("32uS"); break; case 5: printString("128uS"); break; } } void dumpHeadderFFT(){ // the headder for FFT modes uint16_t k; uint32_t x; printString(". . . "); switch(clockAMode){ case 0: printString("CAoff"); break; case 1: printString("CAF_"); printDec32x(clockAFreq); break; case 2: printString("CAd"); printDivider(TCCR1B); printString("_p"); printDec32z(OCR1A); printString("_h"); printDec32z(OCR1B); break; } outSpace(); switch(clockBMode){ case 0: printString("CBoff"); break; case 1: printString("CBF_"); printDec32x(clockBFreq); break; case 2: printString("CBd"); printDivider(TCCR0B); printString("_p"); printDec32z(OCR0A); printString("_h"); printDec32z(OCR0B); break; } outSpace(); switch(clockCMode){ case 0: printString("CCoff"); break; case 1: printString("CCF_"); printDec32x(clockCFreq); break; case 2: printString("CCd100uS_p"); printDec32z(clockCPeriod); printString("_h"); printDec32z(clockCHigh); break; } // outSpace(); printString(" SC"); printDec16Time(adSkipFR); outSpace(); printString("FFT_mode_"); printHex4(fftMode); // can only be 1 or 2 printString(" FFTPeak_"); x = fftPeak - 256; x = x * 977; x = x / adSkipFR; x = x / 50; printDec32x(x); printString("Hz maxV_"); printDec16(fftPeakV); crlf(); } void dumpHeadder(){ // the headder changes depending on modes uint16_t k; uint32_t x; outChar('T'); // trigger switch(adTrigMode){ case 0: outChar('R'); break; case 1: outChar('F'); break; case 2: outChar('x'); break; } if(adTrigMode < 2){ outChar('_'); outChar('S'); outChar(adTrigSource+'0'); if(adTrigSource < 2){ outChar('_'); outChar('L'); printDec16x4(adTrigLevel); } } if(adTrigHold){ outChar('_'); outChar('H'); printDec2Dig(adTrigHold); } outSpace(); outChar('M'); // trigger switch(adMeasMode){ case 0: outChar('R'); break; case 1: outChar('F'); break; case 2: outChar('x'); break; } if(adMeasMode < 2){ outChar('_'); outChar('S'); outChar(adMeasSource+'0'); if(adMeasSource < 2){ outChar('_'); outChar('L'); printDec16x4(adMeasLevel); } k = BUF_MASK & (adMP-adTP); // how many samples between trigger and measure points outChar('_'); outChar('T'); printDec16Time(k * adSkipFR); } printString(" PostT"); // pre trigger settings number is more like samples past post printDec32z(adTrigPre); outSpace(); switch(clockAMode){ case 0: printString("CAx"); break; case 1: printString("CAf"); printDec32x(clockAFreq); break; case 2: printString("CAd"); printDivider(TCCR1B); printString("_p"); printDec32z(OCR1A); printString("_h"); printDec32z(OCR1B); break; } outSpace(); switch(clockBMode){ case 0: printString("CBx"); break; case 1: printString("CBf"); printDec32x(clockBFreq); break; case 2: printString("CBd"); printDivider(TCCR0B); printString("_p"); printDec32z(OCR0A); printString("_h"); printDec32z(OCR0B); break; } outSpace(); switch(clockCType){ case 0: printString("CC_MAN_"); if(PINB & 0x10){ outChar('1'); } else { outChar('0'); } break; case 1: printString("CC_PULSE_"); printDec32z(clockCHigh); break; case 2: switch(clockCMode){ case 0: printString("CCx"); break; case 1: printString("CCF_"); printDec32x(clockCFreq); break; case 2: printString("CCd100uS_p"); printDec32z(clockCPeriod); printString("_h"); printDec32z(clockCHigh); break; } } outSpace(); printString(" SC"); printDec16Time(adSkipFR); outSpace(); printString(" A0_mn"); x = a0Min; x = (x * vCal) >> 8; printDec32z(x); printString("_mx"); x = a0Max; x = (x * vCal) >> 8; printDec32z(x); printString(" A1_mn"); x = a1Min; x = (x * vCal) >> 8; printDec32z(x); printString("_mx"); x = a1Max; x = (x * vCal) >> 8; printDec32z(x); printString(" vCal"); printDec16x4(vCal); crlf(); } // the lines for the digital ports are at fixed locations // separated by 100 counts. We add 50 more counts to it when // it is high. #define D2_LINE 5900 #define D3_LINE 5800 #define D4_LINE 5700 #define D5_LINE 5600 // this has spikes on it to show where the trigger happens // large spike up is trigger point small spike is measurement point // small spikes down are 100 counts apart #define T_LINE 5500 void dumpLineFFT(uint16_t n){ // just one line (out of the 512) // always need to print 10 values uint16_t i,j; uint32_t x; // the first several are all uint8 or limited to 0->255 x = b0.u8[n]; x = x * vCal; x = x >> 8; i = x; printDec16(i); // line for analog A0 port outChar(' '); x = b1.u8[n]; x = x * vCal; x = x >> 8; i = x; printDec16(i); // line for analog A1 port printString(" 6000 0 0 0 0 0 0 0"); crlf(); } void dumpLine(uint16_t n){ // just one line (out of the 512) // we always want to print 10 values uint16_t i,j; uint32_t x; // the first several are all uint8 or limited to 0->255 x = b0.u8[n]; x = x * vCal; x = x >> 8; i = x; printDec16(i); // line for analog A0 port outChar(' '); x = b1.u8[n]; x = x * vCal; x = x >> 8; i = x; printDec16(i); // line for analog A1 port printString(" 0 "); // force at least one line at 0, high side is always covered if(adTrigMode < 2){ // only draw trigger level if we are using it. switch(adTrigSource){ case 0: case 1: printDec16x4(adTrigLevel); break; case 2: printDec16(D2_LINE+3); break; case 3: printDec16(D3_LINE+3); break; case 4: printDec16(D4_LINE+3); break; case 5: printDec16(D5_LINE+3); break; default: printDec8(0); break; } } else { outChar('0'); } outSpace(); if(adMeasMode < 2){ // only draw the measure line when we are using it. switch(adMeasSource){ case 0: case 1: printDec16x4(adMeasLevel); break; case 2: printDec16(D2_LINE+50); break; case 3: printDec16(D3_LINE+50); break; case 4: printDec16(D4_LINE+50); break; case 5: printDec16(D5_LINE+50); break; default: printDec8(0); break; } } else { outChar('0'); } outSpace(); i = T_LINE; // center line that has trigger, measure, and timer ticks j = 1000 + adTP - n; // how many points are we away from trig point, +1000 is to keep it positive j = j % 100; // tick every 100. if(j == 0){ i = T_LINE - 30; } if(adMeasMode != 2){ if(adMP == n){ i = T_LINE + 30;} } if(adTP == n){ i = T_LINE + 60;} printDec16(i); outSpace(); i = D2_LINE; if(adBufD2[n >> 3] & bits[n & 0x07]){ // digital line for the D2 logic input i += 60; } printDec16(i); outSpace(); i = D3_LINE; if(adBufD3[n >> 3] & bits[n & 0x07]){ // digital line for the D3 logic input i += 60; } printDec16(i); outSpace(); i = D4_LINE; if(adBufD4[n >> 3] & bits[n & 0x07]){ // digital line for the D4 logic input i += 60; } printDec16(i); outSpace(); i = D5_LINE; if(adBufD5[n >> 3] & bits[n & 0x07]){ // digital line for the D4 logic input i += 60; } printDec16(i); crlf(); } void dumpTraces(){ // it is time to dump a new set of traces to the plotter // A/D converter should be stopped at this point uint16_t i,j; if(fftMode == 0){ a0Min = 255; a1Min = 255; a0Max = 0; a1Max = 0; for(i=0;i<512;i++){ // figure out min and max for each trace if(b0.u8[i] > a0Max) a0Max = b0.u8[i]; if(b0.u8[i] < a0Min) a0Min = b0.u8[i]; if(b1.u8[i] > a1Max) a1Max = b1.u8[i]; if(b1.u8[i] < a1Min) a1Min = b1.u8[i]; } j = adBufPtr; // start at the trigger point dumpHeadder(); j = adBufPtr; for(i=0;i<512;i++){ dumpLine(j); j++; j = j & BUF_MASK; } } else { // FFT mode runFFT(); j = 0; a0Max = 0; for(i=0;i<512;i++){ if(b0.u8[i] > a0Max){ a0Max = b0.u8[i]; fftPeak = i; } } dumpHeadderFFT(); j = adBufPtr; for(i=0;i<512;i++){ dumpLineFFT(j); j++; j = j & BUF_MASK; } } } // ********************************************************* // ********************************************************* // Here is where we figure out what the user wants // commands get processed and arguments get saved into vars // ********************************************************* // ********************************************************* void clockASetFreq(uint32_t f){ uint32_t x; clockAFreq = f; clockAMode = 1; if(f == 0){ clockAMode = 0; TCCR1B = 0x10; } else if(f < 2){ // just go for 1hz TCCR1B = 0x14; x = 31250; } else if(f < 16){ TCCR1B = 0x13; x = 125000L / f; } else if(f < 123){ TCCR1B = 0x12; // scale factor of 8 x = 1000000L / f; } else { // 123Hz+ use scale factor of 1 TCCR1B = 0x11; x = 8000000L / f; } OCR1A = x; OCR1B = x/2; } void clockBSetFreq(uint32_t f){ uint32_t x; // min freq is 30 hz // max freq is 4Mhz // it will pick the divider to get us as close as we can. // it is only an 8 bit counter with 5 different dividers. clockBFreq = f; TCCR0A = 0x21; clockBMode = 1; if(f == 0){ TCCR0B = 0x00; // just turn it off clockBMode = 0; } else if(f < 30){ TCCR0B = 0x0D; x = 0xFF; // this is a slow as we go 15.318 Hz. } else if(f < 122){ TCCR0B = 0x0D; // 0000 1101 clock divide by 1024 128uS/tick x = 7812 / f; } else if(f < 492){ TCCR0B = 0x0C; // 0000 1100 clock divide by 256 32uS/tick x = 31250L / f; } else if(f < 3922){ TCCR0B = 0x0B; // 0000 1011 clock divide by 64 8uS/tick x = 125000L / f; } else if(f < 31374){ TCCR0B = 0x0A; // 0000 1010 clock divide by 8 1uS/tick x = 1000000L / f; } else { TCCR0B = 0x09; // 0000 1001 clock divide by 1 125nS/tick x = 8000000L / f; } OCR0A = x; // we only get the low 8 bits. OCR0B = x>>1; } void clockCSetFreq(uint32_t f){ clockCFreq = f; clockCMode = 1; if(f == 0){ clockCMode = 0; } else { clockCPeriod = 10000 / f; clockCHigh = clockCPeriod / 2; } } void clockASetup(){ uint32_t i; switch(cmdBuf[2]){ case 'd': case 'D': TCCR1B = 0x10 + getArg8(3); clockAMode = 2; break; // prescale case 'c': case 'C': TCCR1C = getHexArg8(3); break; // just debug case 'p': case 'P': OCR1A = getArg16(3); clockAMode = 2; break; // period case 'h': case 'H': OCR1B = getArg16(3); clockAMode = 2; break; // pulse width case 'f': case 'F': i = getArgX(3); clockASetFreq(i); break; default: unknownCmd = 1; break; } } void clockBSetup(){ uint32_t i; TCCR0A = 0x21; switch(cmdBuf[2]){ case 'd': case 'D': TCCR0B = 0x08 + getArg8(3); clockBMode = 2; break; case 'p': case 'P': OCR0A = getArg8(3); clockBMode = 2; break; // period case 'h': case 'H': OCR0B = getArg8(3); clockBMode = 2; break; // pulse width case 'f': case 'F': i = getArgX(3); clockBSetFreq(i); break; default: unknownCmd = 1; break; } } void clockCSetup(){ uint32_t i; clockCType = 2; // normal repeating pulses switch(cmdBuf[2]){ case 'p': case 'P': clockCPeriod = getArg16(3); clockCMode = 2; break; // period case 'h': case 'H': clockCHigh = getArg16(3); clockCMode = 2; break; // pulse width case 'f': case 'F': i = getArgX(3); clockCSetFreq(i); break; default: unknownCmd = 1; break; } } void clockSineSetup(){ // sine wave output needs MAX7426 uint32_t j; // takes both clock A and B uint8_t i; i = 2; if(cmdBuf[2] == 'f' || cmdBuf[2] == 'F'){ i++; } j = getArgX(i); clockASetFreq(j); j = j * 96; clockBSetFreq(j); } void logicMode(){ uint16_t i; if(adTrig != 21){ // unless we are in scroll mode // we assume the trigger condition is already false // so we sample as soon as it is true. switch(adTrigMode){ case 0: adTrig = 2; break; // rising trigger 'R' case 1: adTrig = 12; break; // falling trigger 'F' case 2: adTrig = 13; // just fake like we got a trigger 'X' adMeas = 0; adTP = adBufPtr; adCount = adTrigPre; break; } } switch(cmdBuf[1]){ case 'l': case 'L': case '0': clockCType = 0; // flip to logic mode PORTB &= ~0x10; break; case 'h': case 'H': case '1': clockCType = 0; // flip to logic mode PORTB |= 0x10; break; case 'p': case 'P': i = getArg16(2); if(i){ // only update if bigger than zero clockCHigh = i; } clockCType = 1; clockCTicks = 0; break; default: clockCType = 1; clockCTicks = 0; break; } } void processCommand(){ uint16_t i; uint32_t j; unknownCmd = 0; // assume we got this if(cmdBufPtr == 0){ // this is when you just hit the return key without any actual command. adTrig = 100; // force a trigger now } else { switch(cmdBuf[0]){ case 'c': case 'C': // set one of the clocks switch(cmdBuf[1]){ case 'a': case 'A': clockASetup(); break; // each clock has their unique setup case 'b': case 'B': clockBSetup(); break; case 'c': case 'C': clockCSetup(); break; case 's': case 'S': clockSineSetup(); break; default: unknownCmd = 1; break; } break; case 'f': case 'F': // fft modes 0 is off, 1 is just magnitude, 2 is phase and magnitude fftMode = getArg8(1); if(fftMode > 2)fftMode = 2; break; case 'l': case 'L': logicMode(); break; case 'm': case 'M': // set measurement point switch(cmdBuf[1]){ case 'l': case 'L': adMeasLevel = getArg16(2); j = adMeasLevel; j = (j << 8) / vCal; adMeasLevel8b = j; break; case 'f': case 'F': adMeasMode = 1; if(cmdBuf[2] >= ' '){ adMeasLevel = getArg16(2); j = adMeasLevel; j = (j << 8) / vCal; adMeasLevel8b = j; } break; case 'r': case 'R': adMeasMode = 0; if(cmdBuf[2] >= ' '){ adMeasLevel = getArg16(2); j = adMeasLevel; j = (j << 8) / vCal; adMeasLevel8b = j; } break; case 'x': case 'X': adMeasMode = 2; break; // doesn't do anything case 's': case 'S': adMeasSource = getArg8(2); break; default: unknownCmd = 1; break; } break; case 'p': case 'P': adTrigPre = getArg16(1); // set pre trigger level break; case 's': case 'S': switch(cmdBuf[1]){ case 's': case 'S': adTrigMode = 2; // no trigger adTrig = 20; // force to scroll mode if(adSkipFR < 50){ adSkipFR = 50; } break; case 'n': case 'N': adTrigMode = 0; adTrig = 0; break; case 'c': case 'C': i = getArg16(2); if(i > 0){ adSkipFR = i; } else { adSkipFR = 1; } break; } break; case 't': case 'T': switch(cmdBuf[1]){ case 'l': case 'L': adTrigLevel = getArg16(2); j = adTrigLevel; j = (j << 8) / vCal; adTrigLevel8b = j; break; case 'f': case 'F': adTrigMode = 1; if(cmdBuf[2] >= ' '){ adTrigLevel = getArg16(2); } break; case 'r': case 'R': adTrigMode = 0; if(cmdBuf[2] >= ' '){ adTrigLevel = getArg16(2); j = adTrigLevel; j = (j << 8) / vCal; adTrigLevel8b = j; } break; case 'x': case 'X': adTrigMode = 2; break; // just trigger now case 's': case 'S': adTrigSource = getArg8(2); break; case 'h': case 'H': adTrigHold = getArg8(2); adTrig = 100; break; default: unknownCmd = 1; break; } break; case 'v': case 'V': vCal = getArg16(1); if(vCal >=4500 && vCal <= 5500){ eepromWriteWord(2,vCal); } break; case '?': showVersion(); break; default: unknownCmd = 1; break; } } if(unknownCmd){ printString("Unknown Command"); crlf(); } else { j = adTrigLevel; // always reset trigger level. j = (j << 8) / vCal; adTrigLevel8b = j; if(cmdBuf[0] != '?'){ if(fftMode == 0){ dumpHeadder(); } else { dumpHeadderFFT(); } } } } void processChar(){ char c; c = rxBuf[rxBufOutPtr++]; rxBufOutPtr &= 0x0F; // only 16 bytes in the input ring buffer if(c == 0x0D){ cmdBuf[cmdBufPtr] = 0; // add null termination processCommand(); cmdBufPtr = 0; cmdBuf[0] = 0; } else { cmdBuf[cmdBufPtr++] = c; if(cmdBufPtr > 15){ cmdBufPtr = 15; } } } int main(){ TCCR1A = 0x21; TCCR1B = 0x10; // stop the timer // this is the sample tick interrupt, setup for 100uS TCCR2A = 2; // was 2 // clear timer on compare, OCR2 TCCR2B = 2; // 16M/8 = 2M T2 clock ticks every 500nS OCR2A = 199; // interrupt = 0.0005*200 = 0.1 msec TIMSK2 = 2; // set the ISR COMPA vect // uart setup UBRR0H = 0; // setup baud rate to 115,200 look at page 201 UBRR0L = 16; // use 207 for 9600, 51 for 38.4K, 34 for 57.6K 16 for 115.2K UCSR0A = 0x02; // turn on double freq U2X0 = 1; UCSR0B = 0x08 | 0x01 | 0x80 | 0x10; // with interrupts // setup A/D converter ADMUX = 0x60; // 0110 0000 vref = Vcc AD0 selected use 0x60 for left justified ADCSRA = 0x8D; // 1100 1101 16Mhz / 32 = 500Khz (above the 200K for max rez) ADCSRB = 0x00; DIDR0 = 0x03; // swipe the A0 and A1 for analog to digital converter blinkRate = 10; adBufPtr = 0; adSampling = 1; // make it go adTrigLevel = 2500; adTrigLevel8b = 0x80; adTrigPre = 400; adTrigMode = 1; // adTrigHold = 0; adTrigSource = 8; // just use the c clock so we have something adMeasLevel = 1000; adMeasMode = 2; // this is off adMP = 0; adSkipF = 1; // full 10Khz default adSkipFR = 1; fftMode = 0; clockASetFreq(0); clockBSetFreq(100); clockCTicks = 0; clockCPeriod = 10000; // one second clockCHigh = 90; // 9mS clockCMode = 2; clockCType = 2; loadDefaults(); // get vCal from EEPROM DDRB = SETUP_PORTB; // setup I/O ports to be what we want PORTB = SETUP_PORTB_OUT; DDRC = SETUP_PORTC; PORTC = SETUP_PORTC_OUT; DDRD = SETUP_PORTD; PORTD = SETUP_PORTD_OUT; txBufInPtr = 0; txBufOutPtr = 0; rxBufInPtr = 0; rxBufOutPtr = 0; sei(); // let the interrupts start showVersion(); while(1){ // loop forever if(adTrig == 90){ // adTrig state machine says we are ready to dump traces dumpTraces(); adSampling = 1; // start it filling the ring buffer adTrig = 91; // waiting for trigger } if(adTrig == 21){ // this is scroll mode, we just keep outputing data when we have it if(adBufPtr != adBufPtrX){ // we have data to output dumpLine(adBufPtrX); adBufPtrX++; adBufPtrX &= BUF_MASK; // keep it in the ring buffer } } if(ticks != ticksX){ // we had at least one tick interrupt ticksX++; // might want to use this for something 10K/sec } if(rxBufInPtr != rxBufOutPtr){ // we had a character from the serial port come in processChar(); // better do something with it } } // end of while(1) }