387 lines
13 KiB
C
387 lines
13 KiB
C
|
/*
|
||
|
ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
|
||
|
2011,2012,2013 Giovanni Di Sirio.
|
||
|
|
||
|
This file is part of ChibiOS/RT.
|
||
|
|
||
|
ChibiOS/RT is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
ChibiOS/RT is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
/*
|
||
|
This file has been contributed by:
|
||
|
Andrew Hannam aka inmarket.
|
||
|
*/
|
||
|
/**
|
||
|
* @file AT91SAM7/adc_lld.c
|
||
|
* @brief AT91SAM7 ADC subsystem low level driver source.
|
||
|
*
|
||
|
* @addtogroup ADC
|
||
|
* @{
|
||
|
*/
|
||
|
|
||
|
#include "ch.h"
|
||
|
#include "hal.h"
|
||
|
|
||
|
#if HAL_USE_ADC || defined(__DOXYGEN__)
|
||
|
|
||
|
/**
|
||
|
* @brief ADC1 Prescaler
|
||
|
* @detail Prescale = RoundUp(MCK / 2 / ADCClock - 1)
|
||
|
*/
|
||
|
#if ((((MCK/2)+(AT91_ADC1_CLOCK-1))/AT91_ADC1_CLOCK)-1) > 255
|
||
|
#define AT91_ADC1_PRESCALE 255
|
||
|
#else
|
||
|
#define AT91_ADC1_PRESCALE ((((MCK/2)+(AT91_ADC1_CLOCK-1))/AT91_ADC1_CLOCK)-1)
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* @brief ADC1 Startup Time
|
||
|
* @details Startup = RoundUp(ADCClock / 400,000 - 1)
|
||
|
* @note Corresponds to a startup delay > 20uS (as required from the datasheet)
|
||
|
*/
|
||
|
#if (((AT91_ADC1_CLOCK+399999)/400000)-1) > 127
|
||
|
#define AT91_ADC1_STARTUP 127
|
||
|
#else
|
||
|
#define AT91_ADC1_STARTUP (((AT91_ADC1_CLOCK+399999)/400000)-1)
|
||
|
#endif
|
||
|
|
||
|
#if AT91_ADC1_RESOLUTION == 8
|
||
|
#define AT91_ADC1_MAINMODE (((AT91_ADC1_SHTM & 0x0F) << 24) | ((AT91_ADC1_STARTUP & 0x7F) << 16) | ((AT91_ADC1_PRESCALE & 0xFF) << 8) | AT91C_ADC_LOWRES_8_BIT)
|
||
|
#else
|
||
|
#define AT91_ADC1_MAINMODE (((AT91_ADC1_SHTM & 0x0F) << 24) | ((AT91_ADC1_STARTUP & 0x7F) << 16) | ((AT91_ADC1_PRESCALE & 0xFF) << 8) | AT91C_ADC_LOWRES_10_BIT)
|
||
|
#endif
|
||
|
|
||
|
#if AT91_ADC1_TIMER < 0 || AT91_ADC1_TIMER > 2
|
||
|
#error "Unknown Timer specified for ADC1"
|
||
|
#endif
|
||
|
|
||
|
/*===========================================================================*/
|
||
|
/* Driver exported variables. */
|
||
|
/*===========================================================================*/
|
||
|
|
||
|
#if !ADC_USE_ADC1
|
||
|
#error "You must specify ADC_USE_ADC1 if you have specified HAL_USE_ADC"
|
||
|
#endif
|
||
|
|
||
|
/** @brief ADC1 driver identifier.*/
|
||
|
ADCDriver ADCD1;
|
||
|
|
||
|
/*===========================================================================*/
|
||
|
/* Driver local variables. */
|
||
|
/*===========================================================================*/
|
||
|
|
||
|
#define ADCReg1 ((AT91S_ADC *)AT91C_ADC_CR)
|
||
|
|
||
|
#if AT91_ADC1_MAINMODE == 2
|
||
|
#define ADCTimer1 ((AT91S_TC *)AT91C_TC2_CCR)
|
||
|
#define AT91_ADC1_TIMERMODE AT91C_ADC_TRGSEL_TIOA2
|
||
|
#define AT91_ADC1_TIMERID AT91C_ID_TC2
|
||
|
#elif AT91_ADC1_MAINMODE == 1
|
||
|
#define ADCTimer1 ((AT91S_TC *)AT91C_TC1_CCR)
|
||
|
#define AT91_ADC1_TIMERMODE AT91C_ADC_TRGSEL_TIOA1
|
||
|
#define AT91_ADC1_TIMERID AT91C_ID_TC1
|
||
|
#else
|
||
|
#define ADCTimer1 ((AT91S_TC *)AT91C_TC0_CCR)
|
||
|
#define AT91_ADC1_TIMERMODE AT91C_ADC_TRGSEL_TIOA0
|
||
|
#define AT91_ADC1_TIMERID AT91C_ID_TC0
|
||
|
#endif
|
||
|
|
||
|
/*===========================================================================*/
|
||
|
/* Driver local functions. */
|
||
|
/*===========================================================================*/
|
||
|
|
||
|
#define adc_sleep() ADCReg1->ADC_MR = (AT91_ADC1_MAINMODE | AT91C_ADC_SLEEP_MODE | AT91C_ADC_TRGEN_DIS)
|
||
|
#define adc_wake() ADCReg1->ADC_MR = (AT91_ADC1_MAINMODE | AT91C_ADC_SLEEP_NORMAL_MODE | AT91C_ADC_TRGEN_DIS)
|
||
|
#define adc_disable() { \
|
||
|
ADCReg1->ADC_IDR = 0xFFFFFFFF; \
|
||
|
ADCReg1->ADC_PTCR = AT91C_PDC_RXTDIS | AT91C_PDC_TXTDIS; \
|
||
|
adc_wake(); \
|
||
|
ADCReg1->ADC_CHDR = 0xFF; \
|
||
|
}
|
||
|
#define adc_clrint() { \
|
||
|
uint32_t isr, dummy; \
|
||
|
\
|
||
|
isr = ADCReg1->ADC_SR; \
|
||
|
if ((isr & AT91C_ADC_DRDY)) dummy = ADCReg1->ADC_LCDR; \
|
||
|
if ((isr & AT91C_ADC_EOC0)) dummy = ADCReg1->ADC_CDR0; \
|
||
|
if ((isr & AT91C_ADC_EOC1)) dummy = ADCReg1->ADC_CDR1; \
|
||
|
if ((isr & AT91C_ADC_EOC2)) dummy = ADCReg1->ADC_CDR2; \
|
||
|
if ((isr & AT91C_ADC_EOC3)) dummy = ADCReg1->ADC_CDR3; \
|
||
|
if ((isr & AT91C_ADC_EOC4)) dummy = ADCReg1->ADC_CDR4; \
|
||
|
if ((isr & AT91C_ADC_EOC5)) dummy = ADCReg1->ADC_CDR5; \
|
||
|
if ((isr & AT91C_ADC_EOC6)) dummy = ADCReg1->ADC_CDR6; \
|
||
|
if ((isr & AT91C_ADC_EOC7)) dummy = ADCReg1->ADC_CDR7; \
|
||
|
}
|
||
|
#define adc_stop() { \
|
||
|
adc_disable(); \
|
||
|
adc_clrint(); \
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* We must keep stack usage to a minimum - the default AT91SAM7 isr stack size is very small.
|
||
|
* We sacrifice some speed and code size in order to achieve this by accessing the structure
|
||
|
* and registers directly rather than through the passed in pointers. This works because the
|
||
|
* AT91SAM7 supports only a single ADC device (although with 8 channels).
|
||
|
*/
|
||
|
static void handleint(void) {
|
||
|
uint32_t isr;
|
||
|
|
||
|
isr = ADCReg1->ADC_SR;
|
||
|
|
||
|
if (ADCD1.grpp) {
|
||
|
|
||
|
/* ADC overflow condition, this could happen only if the DMA is unable to read data fast enough.*/
|
||
|
if ((isr & AT91C_ADC_GOVRE)) {
|
||
|
_adc_isr_error_code(&ADCD1, ADC_ERR_OVERFLOW);
|
||
|
|
||
|
/* Transfer complete processing.*/
|
||
|
} else if ((isr & AT91C_ADC_RXBUFF)) {
|
||
|
if (ADCD1.grpp->circular) {
|
||
|
/* setup the DMA again */
|
||
|
ADCReg1->ADC_RPR = (uint32_t)ADCD1.samples;
|
||
|
if (ADCD1.depth <= 1) {
|
||
|
ADCReg1->ADC_RCR = ADCD1.grpp->num_channels;
|
||
|
ADCReg1->ADC_RNPR = 0;
|
||
|
ADCReg1->ADC_RNCR = 0;
|
||
|
} else {
|
||
|
ADCReg1->ADC_RCR = ADCD1.depth/2 * ADCD1.grpp->num_channels;
|
||
|
ADCReg1->ADC_RNPR = (uint32_t)(ADCD1.samples + (ADCD1.depth/2 * ADCD1.grpp->num_channels));
|
||
|
ADCReg1->ADC_RNCR = (ADCD1.depth - ADCD1.depth/2) * ADCD1.grpp->num_channels;
|
||
|
}
|
||
|
ADCReg1->ADC_PTCR = AT91C_PDC_RXTEN; // DMA enabled
|
||
|
}
|
||
|
_adc_isr_full_code(&ADCD1);
|
||
|
|
||
|
/* Half transfer processing.*/
|
||
|
} else if ((isr & AT91C_ADC_ENDRX)) {
|
||
|
_adc_isr_half_code(&ADCD1);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
/* Spurious interrupt - Make sure it doesn't happen again */
|
||
|
adc_disable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*/
|
||
|
/* Driver interrupt handlers. */
|
||
|
/*===========================================================================*/
|
||
|
|
||
|
/**
|
||
|
* @brief ADC interrupt handler.
|
||
|
*
|
||
|
* @isr
|
||
|
*/
|
||
|
CH_IRQ_HANDLER(ADC_IRQHandler) {
|
||
|
CH_IRQ_PROLOGUE();
|
||
|
|
||
|
handleint();
|
||
|
|
||
|
AT91C_BASE_AIC->AIC_EOICR = 0;
|
||
|
CH_IRQ_EPILOGUE();
|
||
|
}
|
||
|
|
||
|
/*===========================================================================*/
|
||
|
/* Driver exported functions. */
|
||
|
/*===========================================================================*/
|
||
|
|
||
|
/**
|
||
|
* @brief Low level ADC driver initialization.
|
||
|
*
|
||
|
* @notapi
|
||
|
*/
|
||
|
void adc_lld_init(void) {
|
||
|
/* Turn on ADC in the power management controller */
|
||
|
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_ADC);
|
||
|
|
||
|
/* Driver object initialization.*/
|
||
|
adcObjectInit(&ADCD1);
|
||
|
|
||
|
ADCReg1->ADC_CR = 0; // 0 or AT91C_ADC_SWRST if you want to do a ADC reset
|
||
|
adc_stop();
|
||
|
adc_sleep();
|
||
|
|
||
|
/* Setup interrupt handler */
|
||
|
AIC_ConfigureIT(AT91C_ID_ADC,
|
||
|
AT91C_AIC_SRCTYPE_HIGH_LEVEL | AT91_ADC_IRQ_PRIORITY,
|
||
|
ADC_IRQHandler);
|
||
|
AIC_EnableIT(AT91C_ID_ADC);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Configures and activates the ADC peripheral.
|
||
|
*
|
||
|
* @param[in] adcp pointer to the @p ADCDriver object
|
||
|
*
|
||
|
* @notapi
|
||
|
*/
|
||
|
void adc_lld_start(ADCDriver *adcp) {
|
||
|
|
||
|
/* If in stopped state then wake up the ADC */
|
||
|
if (adcp->state == ADC_STOP) {
|
||
|
|
||
|
/* Take it out of sleep mode */
|
||
|
/* We could stay in sleep mode provided total conversion rate < 44khz but we can't guarantee that here */
|
||
|
adc_wake();
|
||
|
|
||
|
/* TODO: We really should perform a conversion here just to ensure that we are out of sleep mode */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Deactivates the ADC peripheral.
|
||
|
*
|
||
|
* @param[in] adcp pointer to the @p ADCDriver object
|
||
|
*
|
||
|
* @notapi
|
||
|
*/
|
||
|
void adc_lld_stop(ADCDriver *adcp) {
|
||
|
if (adcp->state != ADC_READY) {
|
||
|
adc_stop();
|
||
|
adc_sleep();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Starts an ADC conversion.
|
||
|
*
|
||
|
* @param[in] adcp pointer to the @p ADCDriver object
|
||
|
*
|
||
|
* @notapi
|
||
|
*/
|
||
|
void adc_lld_start_conversion(ADCDriver *adcp) {
|
||
|
uint32_t i;
|
||
|
|
||
|
/* Make sure everything is stopped first */
|
||
|
adc_stop();
|
||
|
|
||
|
/* Safety check the trigger value */
|
||
|
switch(ADCD1.grpp->trigger & ~ADC_TRIGGER_SOFTWARE) {
|
||
|
case ADC_TRIGGER_TIMER:
|
||
|
case ADC_TRIGGER_EXTERNAL:
|
||
|
break;
|
||
|
default:
|
||
|
((ADCConversionGroup *)ADCD1.grpp)->trigger = ADC_TRIGGER_SOFTWARE;
|
||
|
ADCD1.depth = 1;
|
||
|
((ADCConversionGroup *)ADCD1.grpp)->circular = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Count the real number of activated channels in case the user got it wrong */
|
||
|
((ADCConversionGroup *)ADCD1.grpp)->num_channels = 0;
|
||
|
for(i=1; i < 0x100; i <<= 1) {
|
||
|
if ((ADCD1.grpp->channelselects & i))
|
||
|
((ADCConversionGroup *)ADCD1.grpp)->num_channels++;
|
||
|
}
|
||
|
|
||
|
/* Set the channels */
|
||
|
ADCReg1->ADC_CHER = ADCD1.grpp->channelselects;
|
||
|
|
||
|
/* Set up the DMA */
|
||
|
ADCReg1->ADC_RPR = (uint32_t)ADCD1.samples;
|
||
|
if (adcp->depth <= 1) {
|
||
|
ADCReg1->ADC_RCR = ADCD1.grpp->num_channels;
|
||
|
ADCReg1->ADC_RNPR = 0;
|
||
|
ADCReg1->ADC_RNCR = 0;
|
||
|
} else {
|
||
|
ADCReg1->ADC_RCR = ADCD1.depth/2 * ADCD1.grpp->num_channels;
|
||
|
ADCReg1->ADC_RNPR = (uint32_t)(ADCD1.samples + (ADCD1.depth/2 * ADCD1.grpp->num_channels));
|
||
|
ADCReg1->ADC_RNCR = (ADCD1.depth - ADCD1.depth/2) * ADCD1.grpp->num_channels;
|
||
|
}
|
||
|
ADCReg1->ADC_PTCR = AT91C_PDC_RXTEN;
|
||
|
|
||
|
/* Set up interrupts */
|
||
|
ADCReg1->ADC_IER = AT91C_ADC_GOVRE | AT91C_ADC_ENDRX | AT91C_ADC_RXBUFF;
|
||
|
|
||
|
/* Set the trigger */
|
||
|
switch(ADCD1.grpp->trigger & ~ADC_TRIGGER_SOFTWARE) {
|
||
|
case ADC_TRIGGER_TIMER:
|
||
|
// Set up the timer if ADCD1.grpp->frequency != 0
|
||
|
if (ADCD1.grpp->frequency) {
|
||
|
/* Turn on Timer in the power management controller */
|
||
|
AT91C_BASE_PMC->PMC_PCER = (1 << AT91_ADC1_TIMERID);
|
||
|
|
||
|
/* Disable the clock and the interrupts */
|
||
|
ADCTimer1->TC_CCR = AT91C_TC_CLKDIS;
|
||
|
ADCTimer1->TC_IDR = 0xFFFFFFFF;
|
||
|
|
||
|
/* Set the Mode of the Timer Counter and calculate the period */
|
||
|
i = (MCK/2)/ADCD1.grpp->frequency;
|
||
|
if (i < (0x10000<<0)) {
|
||
|
ADCTimer1->TC_CMR = (AT91C_TC_ASWTRG_CLEAR | AT91C_TC_ACPC_CLEAR | AT91C_TC_ACPA_SET | AT91C_TC_LDRA_RISING |
|
||
|
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_CLKS_TIMER_DIV1_CLOCK);
|
||
|
} else if (i < (0x10000<<2)) {
|
||
|
i >>= 2;
|
||
|
ADCTimer1->TC_CMR = (AT91C_TC_ASWTRG_CLEAR | AT91C_TC_ACPC_CLEAR | AT91C_TC_ACPA_SET | AT91C_TC_LDRA_RISING |
|
||
|
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_CLKS_TIMER_DIV2_CLOCK);
|
||
|
} else if (i < (0x10000<<4)) {
|
||
|
i >>= 4;
|
||
|
ADCTimer1->TC_CMR = (AT91C_TC_ASWTRG_CLEAR | AT91C_TC_ACPC_CLEAR | AT91C_TC_ACPA_SET | AT91C_TC_LDRA_RISING |
|
||
|
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_CLKS_TIMER_DIV3_CLOCK);
|
||
|
} else if (i < (0x10000<<6)) {
|
||
|
i >>= 6;
|
||
|
ADCTimer1->TC_CMR = (AT91C_TC_ASWTRG_CLEAR | AT91C_TC_ACPC_CLEAR | AT91C_TC_ACPA_SET | AT91C_TC_LDRA_RISING |
|
||
|
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_CLKS_TIMER_DIV4_CLOCK);
|
||
|
} else {
|
||
|
i >>= 9;
|
||
|
ADCTimer1->TC_CMR = (AT91C_TC_ASWTRG_CLEAR | AT91C_TC_ACPC_CLEAR | AT91C_TC_ACPA_SET | AT91C_TC_LDRA_RISING |
|
||
|
AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | AT91C_TC_CLKS_TIMER_DIV5_CLOCK);
|
||
|
}
|
||
|
|
||
|
/* RC is the period, RC-RA is the pulse width (in this case = 1) */
|
||
|
ADCTimer1->TC_RC = i;
|
||
|
ADCTimer1->TC_RA = i - 1;
|
||
|
|
||
|
/* Start the timer counter */
|
||
|
ADCTimer1->TC_CCR = (AT91C_TC_CLKEN |AT91C_TC_SWTRG);
|
||
|
}
|
||
|
|
||
|
ADCReg1->ADC_MR = AT91_ADC1_MAINMODE | AT91C_ADC_SLEEP_NORMAL_MODE | AT91C_ADC_TRGEN_EN | AT91_ADC1_TIMERMODE;
|
||
|
break;
|
||
|
|
||
|
case ADC_TRIGGER_EXTERNAL:
|
||
|
/* Make sure the ADTRG pin is set as an input - assume pull-ups etc have already been set */
|
||
|
#if (SAM7_PLATFORM == SAM7S64) || (SAM7_PLATFORM == SAM7S128) || (SAM7_PLATFORM == SAM7S256) || (SAM7_PLATFORM == SAM7S512)
|
||
|
AT91C_BASE_PIOA->PIO_ODR = AT91C_PA8_ADTRG;
|
||
|
#elif (SAM7_PLATFORM == SAM7X128) || (SAM7_PLATFORM == SAM7X256) || (SAM7_PLATFORM == SAM7X512)
|
||
|
AT91C_BASE_PIOB->PIO_ODR = AT91C_PB18_ADTRG;
|
||
|
#endif
|
||
|
ADCReg1->ADC_MR = AT91_ADC1_MAINMODE | AT91C_ADC_SLEEP_NORMAL_MODE | AT91C_ADC_TRGEN_EN | AT91C_ADC_TRGSEL_EXT;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
ADCReg1->ADC_MR = AT91_ADC1_MAINMODE | AT91C_ADC_SLEEP_NORMAL_MODE | AT91C_ADC_TRGEN_DIS;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Manually start a conversion if we need to */
|
||
|
if (ADCD1.grpp->trigger & ADC_TRIGGER_SOFTWARE)
|
||
|
ADCReg1->ADC_CR = AT91C_ADC_START;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Stops an ongoing conversion.
|
||
|
*
|
||
|
* @param[in] adcp pointer to the @p ADCDriver object
|
||
|
*
|
||
|
* @notapi
|
||
|
*/
|
||
|
void adc_lld_stop_conversion(ADCDriver *adcp) {
|
||
|
(void) adcp;
|
||
|
adc_stop();
|
||
|
}
|
||
|
|
||
|
#endif /* HAL_USE_ADC */
|
||
|
|
||
|
/** @} */
|