/**
* \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;
}