/* * $Revision: 1.9 $ * $Date: 2011-05-15 05:51:45 $ * $Author: higgins $ * $State: Exp $ */ #include #include #include #include "util.h" #include "static.h" // used to enable and disable timer1 compare output interrupt... const static int TIMER1_OUT_COMP_A_INT_ENABLE = _BV(OCIE1A); // the 10 LEDs are strobed to reduce current consumption. at most // 2 LEDs are on at any time. they are strobed every millsecond or // so (depends on if interrupt is enabled). originally the strobing // code was clear and easy to understand, but the interrupt service // routine took 500uS or so, mainly (it appeared) because of function // calls and the module (%) operator (need to check the latter again). // // one complication is that LED0,...,LED9 are split across different // bit ranges in two different ports. in pseudo-verilog, the LEDs // would look like (msb on left) // {LED9,...,LED0} = {PORTD[3:0], PORTB[7:2]} // // the strobing works as follows, set_led_value() specifies the // 10 bit value to be displayed. the interrupt routine then displays // two adjacent (roughly, i'm considering the lsb and msb to be // adjacent) bits each call (approx. every 1 mS). the following // call will display the two bits shifted one towards the msb. // explicitly, the first call will use a mask like 1'b00_0000_0011, // the next will be 1'b00_0000_0110, then 1'b00_0000_1100, etc. // the last in the cycle will be 1'b10_0000_0001. // to speed up the interrupt routine, set_led_value() splits the // 10 bits into the appropriate values for PORTB and PORTD. // the interrupt routine then uses the two bit masks display_mask_B // and display_mask_D (this represents a 10 bit mask split across // the two ports, eg, the first mask is display_mask_B[0] and // display_mask_D[0]). be careful to get the bits in the masks // correct. the interrupt routine just cycles through all the // pairs of bits. volatile uint8_t value_B; volatile uint8_t value_D; void set_led_value(uint16_t value) { // revisit to replace 16 bit ops by 8 bit ops... value_B = (value & 0x003f) << 2; value_D = (value & 0x03c0) >> 6; } // python snippet to generate relevant constants... // for i in range(0,10): // print "%02x" % (x<>8)&0xff)|tag); _delay_s(3.0); set_led_value((value&0xff)|tag); _delay_s(3.0); } void discharge(void) { uint8_t tmp = DDRD; tmp |= _BV(DISCHARGE); // drive low... tmp &= ~(_BV(CHARGE)); // Hi-Z DDRD = tmp; _delay_us(5*RC_DISCHARGE*1.0e6); // check comparator output low... while (ACSR&_BV(ACO)) { } } void charge(void) { uint8_t tmp = DDRD; tmp |= _BV(CHARGE); // drive high... tmp &= ~(_BV(DISCHARGE)); // Hi-Z DDRD = tmp; } // assumes discharged initially... // returns time in uS uint16_t read_time(void) { // disable timer interrupt as it could interfere with the timing // if it happened between the start_time capture and the start of // charging (because the timer is running). TIMSK &= ~TIMER1_OUT_COMP_A_INT_ENABLE; charge(); // capture the start time here as the capture interrupt has some // some delay too. may beed to determine an empirical offset to // get reasonable values near zero... uint16_t start_time = TCNT1; // could re-enable timer interrupt here as the comparator interrupt // captures the time, but i like reasonable repeatability and the // interrupt messes with the charge/discharge cycle a bit. it doesn't // affect functionality, but it looks ugly on the scope. // wait for conversion to complete... while (!(ACSR&_BV(ACO))) { } // for next time... discharge(); // re-enable timer interrupt... TIMSK |= TIMER1_OUT_COMP_A_INT_ENABLE; // figure out the answer... // care must be taken with timer wraparound, since the wraparound // is not 16 bit. uint16_t diff; if (stop_time>start_time) { diff = stop_time-start_time; } else { diff = stop_time+(COUNTER_TOP+1-start_time); } // originally the diff times were all over the place, so i displayed // these values to get a clue. it wasn't until i hooked the scope up // that i realised that the strobing interrupt was interfering with // the measurement in a big way... if (false) { debug_display(0x1, diff); debug_display(0x2, stop_time); debug_display(0x3, start_time); } return diff; } int main(void) { // for readability... // timer setup... const static int NO_PRESCALING = _BV(CS10); const static int MODE_4_CTC = _BV(WGM12); // comparator... const static int TIMER1_IN_CAPT_INT_ENABLE = _BV(ICIE1); const static int COMP_CAP_ENABLE = _BV(ACIC); const static int COMP_INT_RISING_EDGE = _BV(ACIS0)|_BV(ACIS1); const static int DIGITAL_INPUT_DISABLE = _BV(AIN0D)|_BV(AIN1D); // set up the strobing timer to fire every 1mS (COUNTER_TOP+1)... TCCR1B = MODE_4_CTC|NO_PRESCALING; TIMSK = TIMER1_OUT_COMP_A_INT_ENABLE|TIMER1_IN_CAPT_INT_ENABLE; OCR1A = COUNTER_TOP; PORTB = 0; // all off... PORTD = 0; // all off... // enable comparator to trigger timer capture on rising edge... ACSR = COMP_CAP_ENABLE|COMP_INT_RISING_EDGE; DIDR = DIGITAL_INPUT_DISABLE; // drive leds... // see comments above... DDRB |= _BV(LED0)|_BV(LED1)|_BV(LED2)|_BV(LED3)|_BV(LED4)|_BV(LED5); DDRD |= _BV(LED6)|_BV(LED7)|_BV(LED8)|_BV(LED9); // comparator... // comparator input... DDRB &= ~(_BV(AIN0)|_BV(AIN1)); // no pullups on comparator inputs... PORTB &= ~(_BV(AIN0)|_BV(AIN1)); // charge/discharge drive... // these drives never change... PORTD &= ~(_BV(DISCHARGE)); // low... PORTD |= _BV(CHARGE); // hi // debug... DDRD |= _BV(DBG); PORTD &= ~(_BV(DBG)); // low... // discharge initially... discharge(); sei(); // continually loop displaying result... while (1) { uint16_t time = read_time(); // to adjust, use debug_led_value(time) and experiment // to find the min and max range displayed (in this case // the range was 0x088-0x15f or so (136-351), and this // is split into 11 'buckets'. then comment the debug_led_value // line out and uncomment the set_led_value(value) line. // the formula used is then something of the form // (time-low_value)/(high_value-low_value) // except that it is preferable to use integer arithmetic // rather than floating point. uint16_t readout; if (time < 136) { readout = 0; } else if (time < 234) { readout = (10*(time-136))/195; } else { // attempt to be less sensitive at higher voltages... readout = 5+(10*(time-234))/300; } // turn on a contiguous set of leds, eg, readout=4 will // result in 0x00f... uint16_t value = (1<