/** * \file: icp.c * * \brief AVR135: Using Timer Capture to Measure PWM Duty Cycle * * Copyright (C) 2016 Atmel Corporation. All rights reserved. * * \page License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The name of Atmel may not be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. This software may only be redistributed and used in connection with an * Atmel microcontroller product. * * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * */ /* * Support and FAQ: visit Atmel Support */ #include #include #include "icp.h" #include "device.h" /** * icp_start_time, icp_stop_time, icp_period * * State variables for the Demodulator. * * start_time, stop_time and period all have the same type as the * respective TCNT register. */ /* same as TCNT1 */ typedef unsigned int icp_timer_t; icp_timer_t icp_start_time, icp_stop_time; icp_timer_t icp_period; /** * icp_rx_q[] * * Stores demodulated samples. * * The rx_q elements have the same type as the duty-cycle result. */ icp_sample_t icp_rx_q[ICP_RX_QSIZE]; /** * icp_rx_tail, icp_rx_head * * Queue state variables for icp_rx_q. * * The rx_head and rx_tail indices need to be wide enough to * accommodate [0:ICP_RX_QSIZE). Since QSIZE should generally * not be very large, these are hard-coded as single bytes, * which gets around certain atomicity concerns. */ /* icp_rx_q insertion index */ unsigned char icp_rx_tail; #if !ICP_ANALOG /* ICP_DIGITAL */ /* icp_rx_q retrieval index */ unsigned char icp_rx_head; #endif #if ICP_ANALOG /** * icp_total * * Used in computing a moving average for ICP_ANALOG samples. * * icp_total needs to be wide enough to accommodate the sum of * RX_QSIZE icp_sample_t's. */ icp_total_t icp_total; #endif /* ICP_ANALOG */ /** * icp_duty_compute() * * This function computes the value of (ICP_SCALE*pulsewidth)/period. * It is effectively a divide function, but uses a successive-approximation * (moral equivalent of long division) method which: * 1) Doesn't require 32-bit arithmetic (the numerator is 24+ bits nominal). * 2) For an 8-bit result, only needs 8 loops (instead of 16 for general 16/16). * 3) Compiles nicely from C. * We get away with this because we know that pulsewidth <= period, so * no more than log2(ICP_SCALE) bits are relevant. */ static icp_sample_t icp_duty_compute(icp_timer_t pulsewidth, icp_timer_t period) { icp_sample_t r, mask; mask = ICP_SCALE >> 1; r = 0; do { period >>= 1; if (pulsewidth >= period) { r |= mask; pulsewidth -= period; } mask >>= 1; } while (pulsewidth != 0 && mask != 0); return(r); } /** * icp_enq() * * Stores a new sample into the Rx queue. */ static void icp_enq(icp_sample_t sample) { unsigned char t; t = icp_rx_tail; #if ICP_ANALOG icp_total += sample - icp_rx_q[t]; #endif icp_rx_q[t] = sample; if (++t >= ICP_RX_QSIZE) t = 0; #if !ICP_ANALOG /* digital: Check for Rx overrun */ if (t != icp_rx_head) #endif icp_rx_tail = t; return; } /** * TIMER1_COMPA() * * ICP timer Output Compare ISR. * * The OC interrupt indicates that some edge didn't arrive as * expected. This happens when the duty cycle is either 0% (no * pulse at all) or 100% (pulse encompasses the entire period). */ ISR(TIMER1_COMPA_vect) { icp_sample_t sample; /* slide timeout window forward */ ICP_OCR += icp_period; /* assume 0% */ sample = 0; if ((ICP_CTL & (1 << ICP_SENSE)) != ICP_START_SENSE) /* 100% */ sample = ICP_SCALE - 1; icp_enq(sample); return; } /** * TIMER1_CAPT() * * ICP capture interrupt. */ ISR(TIMER1_CAPT_vect) { icp_timer_t icr, delta; unsigned char tccr1b; /* * Capture the ICR and then reverse the sense of the capture. * These must be done in this order, since as soon as the * sense is reversed it is possible for ICR to be updated again. */ /* capture timestamp */ icr = ICR1; do { tccr1b = ICP_CTL; /* reverse sense */ ICP_CTL = tccr1b ^ (1 << ICP_SENSE); /* * If we were waiting for a start edge, then this is the * end/beginning of a period. */ if ((tccr1b & (1 << ICP_SENSE)) == ICP_START_SENSE) { /* * Beginning of pulse: Compute length of preceding period, * and thence the duty cycle of the preceding pulse. */ /* Length of previous period */ icp_period = icr - icp_start_time; /* Length of previous pulse */ delta = icp_stop_time - icp_start_time; /* Start of new pulse/period */ icp_start_time = icr; /* * Update the timeout based on the new period. (The new period * is probably the same as the old, give or take clock drift.) * We add 1 to make fairly sure that, in case of competition, * the PWM edge takes precedence over the timeout. */ /* Move timeout window */ ICP_OCR = icr + icp_period + 1; /* Clear in case of race */ REG_TIMER_INTFLAG = (1 << ICP_OC_IF); /* * Compute the duty cycle, and store the new reading. */ icp_enq(icp_duty_compute(delta,icp_period)); /* * Check for a race condition where a (very) short pulse * ended before we could reverse the sense above. * If the ICP pin is still high (as expected) OR the IF is * set (the falling edge has happened, but we caught it), * then we won the race, so we're done for now. */ if ((ICP_PIN & (1 << ICP_BIT)) || (REG_TIMER_INTFLAG & (1 << ICP_IF))) break; } else { /* * Falling edge detected, so this is the end of the pulse. * The time is simply recorded here; the final computation * will take place at the beginning of the next pulse. */ /* Capture falling-edge time */ icp_stop_time = icr; /* * If the ICP pin is still low (as expected) OR the IF is * set (the transition was caught anyway) we won the race, * so we're done for now. */ if ((!(ICP_PIN & (1 << ICP_BIT))) || (REG_TIMER_INTFLAG & (1 << ICP_IF))) break; } /* * If we got here, we lost the race with a very short/long pulse. * We now loop, pretending (as it were) that we caught the transition. * The Same ICR value is used, so the effect is that we declare * the duty cycle to be 0% or 100% as appropriate. */ } while (1); return; } /** * icp_rx() * * Fetch a sample from the queue. For analog mode, this is a moving * average of the last QSIZE readings. For digital, it is the oldest * reading. */ icp_sample_t icp_rx(void) { icp_sample_t r; #if ICP_ANALOG /* moving average of last QSIZE samples */ r = icp_total / ICP_RX_QSIZE; #else /* ICP_DIGITAL */ unsigned char h; h = icp_rx_head; /* if head == tail, queue is empty */ if (h == icp_rx_tail) r = (icp_sample_t)-1; else { /* fetch next entry */ r = icp_rx_q[h]; /* increment head, modulo QSIZE */ if (++h >= ICP_RX_QSIZE) h = 0; icp_rx_head = h; } #endif /* ICP_DIGITAL */ return(r); } /** * icp_init() * * Set up the ICP timer. */ void icp_init(void) { /* * Nothing interesting to set in TCCR1A */ ICP_CTL_A = 0; /* * Setting the OCR (timeout) to 0 allows the full TCNT range for * the initial period. */ ICP_OCR = 0; /* * Set the interrupt sense and the prescaler */ ICP_CTL = (1 << ICES1) | ((0 << CS12) | (0 << CS11) | (1 << CS10)); /* * Enable both the Input Capture and the Output Capture interrupts. * The latter is used for timeout (0% and 100%) checking. */ ICP_TIMSK |= ICP_TIMSK_MASK; return; }