mirror of https://github.com/rusefi/ChibiOS.git
ADC and PWM drivers for AT91SAM7.
git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@5228 35acf78f-673a-0410-8e92-d51de3d6d3f4
This commit is contained in:
parent
f5d55ccbc3
commit
0d21b75444
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
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 */
|
||||
|
||||
/** @} */
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
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.h
|
||||
* @brief AT91SAM7 ADC subsystem low level driver header.
|
||||
*
|
||||
* @addtogroup ADC
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef _ADC_LLD_H_
|
||||
#define _ADC_LLD_H_
|
||||
|
||||
#if HAL_USE_ADC || defined(__DOXYGEN__)
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver constants. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @name Trigger Sources
|
||||
* @{
|
||||
*/
|
||||
#define ADC_TRIGGER_SOFTWARE 0x8000 /**< @brief Software Triggering - Can be combined with another value */
|
||||
#define ADC_TRIGGER_TIMER 0x0001 /**< @brief TIO Timer Counter Channel */
|
||||
#define ADC_TRIGGER_EXTERNAL 0x0002 /**< @brief External Trigger */
|
||||
/** @} */
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver pre-compile time settings. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @name Configuration options
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief ADC1 driver enable switch.
|
||||
* @details If set to @p TRUE the support for ADC1 is included.
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(ADC_USE_ADC1) || defined(__DOXYGEN__)
|
||||
#define ADC_USE_ADC1 TRUE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ADC1 Timer to use when a periodic conversion is requested.
|
||||
* @details Should be set to 0..2
|
||||
* @note The default is 0
|
||||
*/
|
||||
#if !defined(AT91_ADC1_TIMER) || defined(__DOXYGEN__)
|
||||
#define AT91_ADC1_TIMER 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ADC1 Resolution.
|
||||
* @details Either 8 or 10 bits
|
||||
* @note The default is 10 bits.
|
||||
*/
|
||||
#if !defined(AT91_ADC1_RESOLUTION) || defined(__DOXYGEN__)
|
||||
#define AT91_ADC1_RESOLUTION 10
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ADC1 Clock
|
||||
* @details Maximum is 5MHz for 10bit or 8MHz for 8bit
|
||||
* @note The default is calculated from AT91_ADC1_RESOLUTION to give the fastest possible ADCClock
|
||||
*/
|
||||
#if !defined(AT91_ADC1_CLOCK) || defined(__DOXYGEN__)
|
||||
#if AT91_ADC1_RESOLUTION == 8
|
||||
#define AT91_ADC1_CLOCK 8000000
|
||||
#else
|
||||
#define AT91_ADC1_CLOCK 5000000
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ADC1 Sample and Hold Time
|
||||
* @details SHTM = RoundUp(ADCClock * SampleHoldTime). Range = RoundUp(ADCClock / 1,666,666) to 15
|
||||
* @note Default corresponds to the minimum sample and hold time (600nS from the datasheet)
|
||||
* @note Increasing the Sample Hold Time increases the ADC input impedance
|
||||
*/
|
||||
#if !defined(AT91_ADC1_SHTM) || defined(__DOXYGEN__)
|
||||
#define AT91_ADC1_SHTM 0
|
||||
#endif
|
||||
#if AT91_ADC1_SHTM < ((AT91_ADC1_CLOCK+1666665)/1666666)
|
||||
#undef AT91_ADC1_SHTM
|
||||
#define AT91_ADC1_SHTM ((AT91_ADC1_CLOCK+1666665)/1666666)
|
||||
#endif
|
||||
#if AT91_ADC1_SHTM > 15
|
||||
#undef AT91_ADC1_SHTM
|
||||
#define AT91_ADC1_SHTM 15
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ADC interrupt priority level setting.
|
||||
*/
|
||||
#if !defined(AT91_ADC_IRQ_PRIORITY) || defined(__DOXYGEN__)
|
||||
#define AT91_ADC_IRQ_PRIORITY (AT91C_AIC_PRIOR_HIGHEST - 2)
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Derived constants and error checks. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if !defined(AT91_DMA_REQUIRED)
|
||||
#define AT91_DMA_REQUIRED
|
||||
#endif
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver data structures and types. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @brief ADC sample data type.
|
||||
*/
|
||||
#if AT91_ADC1_RESOLUTION == AT91C_ADC_LOWRES_8_BIT
|
||||
typedef uint8_t adcsample_t;
|
||||
#else
|
||||
typedef uint16_t adcsample_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Channels number in a conversion group.
|
||||
*/
|
||||
typedef uint16_t adc_channels_num_t;
|
||||
|
||||
/**
|
||||
* @brief Possible ADC failure causes.
|
||||
* @note Error codes are architecture dependent and should not relied
|
||||
* upon.
|
||||
*/
|
||||
typedef enum {
|
||||
ADC_ERR_OVERFLOW = 0, /**< ADC overflow condition. Something is not working fast enough. */
|
||||
} adcerror_t;
|
||||
|
||||
/**
|
||||
* @brief Type of a structure representing an ADC driver.
|
||||
*/
|
||||
typedef struct ADCDriver ADCDriver;
|
||||
|
||||
/**
|
||||
* @brief ADC notification callback type.
|
||||
*
|
||||
* @param[in] adcp pointer to the @p ADCDriver object triggering the
|
||||
* callback
|
||||
* @param[in] buffer pointer to the most recent samples data
|
||||
* @param[in] n number of buffer rows available starting from @p buffer
|
||||
*/
|
||||
typedef void (*adccallback_t)(ADCDriver *adcp, adcsample_t *buffer, size_t n);
|
||||
|
||||
/**
|
||||
* @brief ADC error callback type.
|
||||
*
|
||||
* @param[in] adcp pointer to the @p ADCDriver object triggering the
|
||||
* callback
|
||||
* @param[in] err ADC error code
|
||||
*/
|
||||
typedef void (*adcerrorcallback_t)(ADCDriver *adcp, adcerror_t err);
|
||||
|
||||
/**
|
||||
* @brief Conversion group configuration structure.
|
||||
* @details This implementation-dependent structure describes a conversion
|
||||
* operation.
|
||||
* @note The use of this configuration structure requires knowledge of
|
||||
* STM32 ADC cell registers interface, please refer to the STM32
|
||||
* reference manual for details.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Enables the circular buffer mode for the group.
|
||||
*/
|
||||
bool_t circular;
|
||||
/**
|
||||
* @brief Number of the analog channels belonging to the conversion group.
|
||||
*/
|
||||
adc_channels_num_t num_channels;
|
||||
/**
|
||||
* @brief Callback function associated to the group or @p NULL.
|
||||
*/
|
||||
adccallback_t end_cb;
|
||||
/**
|
||||
* @brief Error callback or @p NULL.
|
||||
*/
|
||||
adcerrorcallback_t error_cb;
|
||||
/* End of the mandatory fields.*/
|
||||
/**
|
||||
* @brief Select the ADC Channels to read.
|
||||
* @details The number of bits at logic level one in this register must
|
||||
* be equal to the number in the @p num_channels field.
|
||||
*/
|
||||
uint16_t channelselects;
|
||||
/**
|
||||
* @brief Select how to trigger the conversion.
|
||||
*/
|
||||
uint16_t trigger;
|
||||
/**
|
||||
* @brief When in ADC_TRIGGER_TIMER trigger mode - what frequency?
|
||||
*/
|
||||
uint32_t frequency;
|
||||
} ADCConversionGroup;
|
||||
|
||||
/**
|
||||
* @brief Driver configuration structure.
|
||||
* @note It could be empty on some architectures.
|
||||
*/
|
||||
typedef struct {
|
||||
} ADCConfig;
|
||||
|
||||
/**
|
||||
* @brief Structure representing an ADC driver.
|
||||
*/
|
||||
struct ADCDriver {
|
||||
/**
|
||||
* @brief Driver state.
|
||||
*/
|
||||
adcstate_t state;
|
||||
/**
|
||||
* @brief Current configuration data.
|
||||
*/
|
||||
const ADCConfig *config;
|
||||
/**
|
||||
* @brief Current samples buffer pointer or @p NULL.
|
||||
*/
|
||||
adcsample_t *samples;
|
||||
/**
|
||||
* @brief Current samples buffer depth or @p 0.
|
||||
*/
|
||||
size_t depth;
|
||||
/**
|
||||
* @brief Current conversion group pointer or @p NULL.
|
||||
*/
|
||||
const ADCConversionGroup *grpp;
|
||||
#if ADC_USE_WAIT || defined(__DOXYGEN__)
|
||||
/**
|
||||
* @brief Waiting thread.
|
||||
*/
|
||||
Thread *thread;
|
||||
#endif
|
||||
#if ADC_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__)
|
||||
#if CH_USE_MUTEXES || defined(__DOXYGEN__)
|
||||
/**
|
||||
* @brief Mutex protecting the peripheral.
|
||||
*/
|
||||
Mutex mutex;
|
||||
#elif CH_USE_SEMAPHORES
|
||||
Semaphore semaphore;
|
||||
#endif
|
||||
#endif /* ADC_USE_MUTUAL_EXCLUSION */
|
||||
#if defined(ADC_DRIVER_EXT_FIELDS)
|
||||
ADC_DRIVER_EXT_FIELDS
|
||||
#endif
|
||||
/* End of the mandatory fields.*/
|
||||
};
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver macros. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* External declarations. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if ADC_USE_ADC1 && !defined(__DOXYGEN__)
|
||||
extern ADCDriver ADCD1;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void adc_lld_init(void);
|
||||
void adc_lld_start(ADCDriver *adcp);
|
||||
void adc_lld_stop(ADCDriver *adcp);
|
||||
void adc_lld_start_conversion(ADCDriver *adcp);
|
||||
void adc_lld_stop_conversion(ADCDriver *adcp);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HAL_USE_ADC */
|
||||
|
||||
#endif /* _ADC_LLD_H_ */
|
||||
|
||||
/** @} */
|
|
@ -2,10 +2,12 @@
|
|||
PLATFORMSRC = ${CHIBIOS}/os/hal/platforms/AT91SAM7/hal_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/pal_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/i2c_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/adc_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/ext_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/serial_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/spi_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/mac_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/pwm_lld.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/at91sam7_mii.c \
|
||||
${CHIBIOS}/os/hal/platforms/AT91SAM7/at91lib/aic.c
|
||||
|
||||
|
|
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
---
|
||||
|
||||
A special exception to the GPL can be applied should you wish to distribute
|
||||
a combined work that includes ChibiOS/RT, without being obliged to provide
|
||||
the source code for any proprietary components. See the file exception.txt
|
||||
for full details of how and when the exception can be applied.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file templates/pwm_lld.c
|
||||
* @brief PWM Driver subsystem low level driver source template.
|
||||
*
|
||||
* @addtogroup PWM
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#if HAL_USE_PWM || defined(__DOXYGEN__)
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver local definitions. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#ifdef UNUSED
|
||||
#elif defined(__GNUC__)
|
||||
# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
|
||||
#elif defined(__LCLINT__)
|
||||
# define UNUSED(x) /*@unused@*/ x
|
||||
#else
|
||||
# define UNUSED(x) x
|
||||
#endif
|
||||
|
||||
#define PWMC_M ((AT91S_PWMC *)AT91C_PWMC_MR)
|
||||
|
||||
#define PWM_MCK_MASK 0x0F00
|
||||
#define PWM_MCK_SHIFT 8
|
||||
|
||||
typedef struct pindef {
|
||||
uint32_t portpin; /* Set to 0 if this pin combination is invalid */
|
||||
AT91S_PIO *pio;
|
||||
AT91_REG *perab;
|
||||
} pindef_t;
|
||||
|
||||
typedef struct pwmpindefs {
|
||||
pindef_t pin[3];
|
||||
} pwmpindefs_t;
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver exported variables. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD1;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD2;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD3;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD4;
|
||||
#endif
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver local variables. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if (SAM7_PLATFORM == SAM7S64) || (SAM7_PLATFORM == SAM7S128) || \
|
||||
(SAM7_PLATFORM == SAM7S256) || (SAM7_PLATFORM == SAM7S512)
|
||||
|
||||
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP1 = {{
|
||||
{ AT91C_PA0_PWM0 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
||||
{ AT91C_PA11_PWM0, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
{ AT91C_PA23_PWM0, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP2 = {{
|
||||
{ AT91C_PA1_PWM1 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
||||
{ AT91C_PA12_PWM1, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
{ AT91C_PA24_PWM1, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP3 = {{
|
||||
{ AT91C_PA2_PWM2 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_ASR },
|
||||
{ AT91C_PA13_PWM2, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
{ AT91C_PA25_PWM2, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP4 = {{
|
||||
{ AT91C_PA7_PWM3 , AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
{ AT91C_PA14_PWM3, AT91C_BASE_PIOA, &AT91C_BASE_PIOA->PIO_BSR },
|
||||
{ 0, 0, 0 },
|
||||
}};
|
||||
#endif
|
||||
|
||||
#elif (SAM7_PLATFORM == SAM7X128) || (SAM7_PLATFORM == SAM7X256) || \
|
||||
(SAM7_PLATFORM == SAM7X512)
|
||||
|
||||
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP1 = {{
|
||||
{ AT91C_PB19_PWM0, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
||||
{ AT91C_PB27_PWM0, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
||||
{ 0, 0, 0 },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP2 = {{
|
||||
{ AT91C_PB20_PWM1, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
||||
{ AT91C_PB28_PWM1, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
||||
{ 0, 0, 0 },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP3 = {{
|
||||
{ AT91C_PB21_PWM2, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
||||
{ AT91C_PB29_PWM2, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
||||
{ 0, 0, 0 },
|
||||
}};
|
||||
#endif
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
static const pwmpindefs_t PWMP4 = {{
|
||||
{ AT91C_PB22_PWM3, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_ASR },
|
||||
{ AT91C_PB30_PWM3, AT91C_BASE_PIOB, &AT91C_BASE_PIOB->PIO_BSR },
|
||||
{ 0, 0, 0 },
|
||||
}};
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "serial lines not defined for this SAM7 version"
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD2;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD3;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
PWMDriver PWMD4;
|
||||
#endif
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver local functions. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver interrupt handlers. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if defined(__GNUC__)
|
||||
__attribute__((noinline))
|
||||
#endif
|
||||
/**
|
||||
* @brief Common IRQ handler.
|
||||
*/
|
||||
static void pwm_lld_serve_interrupt(void) {
|
||||
uint32_t isr;
|
||||
|
||||
isr = PWMC_M->PWMC_ISR;
|
||||
#if PWM_USE_PWM1
|
||||
if ((isr & 1) && PWMD1.config->channels[0].callback)
|
||||
PWMD1.config->channels[0].callback(&PWMD1);
|
||||
#endif
|
||||
#if PWM_USE_PWM2
|
||||
if ((isr & 2) && PWMD2.config->channels[0].callback)
|
||||
PWMD2.config->channels[0].callback(&PWMD2);
|
||||
#endif
|
||||
#if PWM_USE_PWM3
|
||||
if ((isr & 4) && PWMD3.config->channels[0].callback)
|
||||
PWMD3.config->channels[0].callback(&PWMD3);
|
||||
#endif
|
||||
#if PWM_USE_PWM4
|
||||
if ((isr & 8) && PWMD4.config->channels[0].callback)
|
||||
PWMD4.config->channels[0].callback(&PWMD4);
|
||||
#endif
|
||||
}
|
||||
|
||||
CH_IRQ_HANDLER(PWMIrqHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
pwm_lld_serve_interrupt();
|
||||
AT91C_BASE_AIC->AIC_EOICR = 0;
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver exported functions. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @brief Low level PWM driver initialization.
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_init(void) {
|
||||
|
||||
/* Driver initialization.*/
|
||||
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
||||
pwmObjectInit(&PWMD1);
|
||||
PWMD1.chbit = 1;
|
||||
PWMD1.reg = AT91C_BASE_PWMC_CH0;
|
||||
PWMD1.pins = &PWMP1;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
pwmObjectInit(&PWMD2);
|
||||
PWMD1.chbit = 2;
|
||||
PWMD1.reg = AT91C_BASE_PWMC_CH1;
|
||||
PWMD1.pins = &PWMP2;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
pwmObjectInit(&PWMD3);
|
||||
PWMD1.chbit = 4;
|
||||
PWMD1.reg = AT91C_BASE_PWMC_CH2;
|
||||
PWMD1.pins = &PWMP3;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
pwmObjectInit(&PWMD4);
|
||||
PWMD1.chbit = 8;
|
||||
PWMD1.reg = AT91C_BASE_PWMC_CH3;
|
||||
PWMD1.pins = &PWMP4;
|
||||
#endif
|
||||
|
||||
/* Turn on PWM in the power management controller */
|
||||
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_PWMC);
|
||||
|
||||
/* Setup interrupt handler */
|
||||
PWMC_M->PWMC_IDR = 0xFFFFFFFF;
|
||||
AIC_ConfigureIT(AT91C_ID_PWMC,
|
||||
AT91C_AIC_SRCTYPE_HIGH_LEVEL | AT91SAM7_PWM_PRIORITY,
|
||||
PWMIrqHandler);
|
||||
AIC_EnableIT(AT91C_ID_PWMC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures and activates the PWM peripheral.
|
||||
*
|
||||
* @param[in] pwmp pointer to the @p PWMDriver object
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_start(PWMDriver *pwmp) {
|
||||
uint32_t mode, mr, div, pre;
|
||||
|
||||
/* Steps:
|
||||
1. Turn the IO pin to a PWM output
|
||||
2. Configuration of Clock if DIVA or DIVB used
|
||||
3. Selection of the clock for each channel (CPRE field in the PWM_CMRx register)
|
||||
4. Configusration of the waveform alignment for each channel (CALG field in the PWM_CMRx register)
|
||||
5. Configuration of the output waveform polarity for each channel (CPOL in the PWM_CMRx register)
|
||||
6. Configuration of the period for each channel (CPRD in the PWM_CPRDx register). Writing in
|
||||
PWM_CPRDx Register is possible while the channel is disabled. After validation of the
|
||||
channel, the user must use PWM_CUPDx Register to update PWM_CPRDx
|
||||
7. Enable Interrupts (Writing CHIDx in the PWM_IER register)
|
||||
*/
|
||||
|
||||
/* Make sure it is off first */
|
||||
pwm_lld_disable_channel(pwmp, 0);
|
||||
|
||||
/* Configuration.*/
|
||||
mode = pwmp->config->channels[0].mode;
|
||||
|
||||
/* Step 1 */
|
||||
if (mode & PWM_OUTPUT_PIN1) {
|
||||
pwmp->pins->pin[0].perab[0] = pwmp->pins->pin[0].portpin; /* Select A or B peripheral */
|
||||
pwmp->pins->pin[0].pio->PIO_PDR = pwmp->pins->pin[0].portpin; /* Turn PIO into PWM output */
|
||||
pwmp->pins->pin[0].pio->PIO_MDDR = pwmp->pins->pin[0].portpin; /* Turn off PIO multi-drive */
|
||||
if (mode & PWM_DISABLEPULLUP_PIN1)
|
||||
pwmp->pins->pin[0].pio->PIO_PPUDR = pwmp->pins->pin[0].portpin; /* Turn off PIO pullup */
|
||||
else
|
||||
pwmp->pins->pin[0].pio->PIO_PPUER = pwmp->pins->pin[0].portpin; /* Turn on PIO pullup */
|
||||
}
|
||||
if (mode & PWM_OUTPUT_PIN2) {
|
||||
pwmp->pins->pin[1].perab[0] = pwmp->pins->pin[1].portpin;
|
||||
pwmp->pins->pin[1].pio->PIO_PDR = pwmp->pins->pin[1].portpin;
|
||||
pwmp->pins->pin[1].pio->PIO_MDDR = pwmp->pins->pin[1].portpin;
|
||||
if (mode & PWM_DISABLEPULLUP_PIN2)
|
||||
pwmp->pins->pin[1].pio->PIO_PPUDR = pwmp->pins->pin[1].portpin;
|
||||
else
|
||||
pwmp->pins->pin[1].pio->PIO_PPUER = pwmp->pins->pin[1].portpin;
|
||||
}
|
||||
if ((mode & PWM_OUTPUT_PIN3) && pwmp->pins->pin[2].portpin) {
|
||||
pwmp->pins->pin[2].perab[0] = pwmp->pins->pin[2].portpin;
|
||||
pwmp->pins->pin[2].pio->PIO_PDR = pwmp->pins->pin[2].portpin;
|
||||
pwmp->pins->pin[2].pio->PIO_MDDR = pwmp->pins->pin[2].portpin;
|
||||
if (mode & PWM_DISABLEPULLUP_PIN3)
|
||||
pwmp->pins->pin[2].pio->PIO_PPUDR = pwmp->pins->pin[2].portpin;
|
||||
else
|
||||
pwmp->pins->pin[2].pio->PIO_PPUER = pwmp->pins->pin[2].portpin;
|
||||
}
|
||||
|
||||
/* Step 2 */
|
||||
if ((mode & PWM_MCK_MASK) == PWM_MCK_DIV_CLKA) {
|
||||
if (!pwmp->config->frequency) {
|
||||
/* As slow as we go */
|
||||
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0xFFFF0000) | (10 << 8) | (255 << 0);
|
||||
} else if (pwmp->config->frequency > MCK) {
|
||||
/* Just use MCLK */
|
||||
mode &= ~PWM_MCK_MASK;
|
||||
} else {
|
||||
div = MCK / pwmp->config->frequency;
|
||||
if (mode & PWM_OUTPUT_CENTER) div >>= 1;
|
||||
for(pre = 0; div > 255 && pre < 10; pre++) div >>= 1;
|
||||
if (div > 255) div = 255;
|
||||
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0xFFFF0000) | (pre << 8) | (div << 0);
|
||||
}
|
||||
} else if ((mode & PWM_MCK_MASK) == PWM_MCK_DIV_CLKB) {
|
||||
if (!pwmp->config->frequency) {
|
||||
/* As slow as we go */
|
||||
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0x0000FFFF) | (10 << 24) | (255 << 16);
|
||||
} else if (pwmp->config->frequency > MCK) {
|
||||
/* Just use MCLK */
|
||||
mode &= ~PWM_MCK_MASK;
|
||||
} else {
|
||||
div = MCK / pwmp->config->frequency;
|
||||
if (mode & PWM_OUTPUT_CENTER) div >>= 1;
|
||||
for(pre = 0; div > 255 && pre < 10; pre++) div >>= 1;
|
||||
if (div > 255) div = 255;
|
||||
PWMC_M->PWMC_MR = (PWMC_M->PWMC_MR & 0x0000FFFF) | (pre << 24) | (div << 16);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 3 -> 5 */
|
||||
mr = (mode & PWM_MCK_MASK) >> PWM_MCK_SHIFT;
|
||||
if (mode & PWM_OUTPUT_CENTER) mr |= AT91C_PWMC_CALG;
|
||||
if (mode & PWM_OUTPUT_ACTIVE_HIGH) mr |= AT91C_PWMC_CPOL;
|
||||
pwmp->reg->PWMC_CMR = mr;
|
||||
|
||||
/* Step 6 */
|
||||
pwmp->reg->PWMC_CPRDR = pwmp->period;
|
||||
|
||||
/* Step 7 */
|
||||
if (pwmp->config->channels[0].callback)
|
||||
PWMC_M->PWMC_IER = pwmp->chbit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deactivates the PWM peripheral.
|
||||
*
|
||||
* @param[in] pwmp pointer to the @p PWMDriver object
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_stop(PWMDriver *pwmp) {
|
||||
/* Make sure it is off */
|
||||
pwm_lld_disable_channel(pwmp, 0);
|
||||
|
||||
/* Turn the pin back to a PIO pin - we have forgotten pull-up and multi-drive state for the pin though */
|
||||
if (pwmp->config->channels[0].mode & PWM_OUTPUT_PIN1)
|
||||
pwmp->pins->pin[0].pio->PIO_PER = pwmp->pins->pin[0].portpin;
|
||||
if (pwmp->config->channels[0].mode & PWM_OUTPUT_PIN2)
|
||||
pwmp->pins->pin[1].pio->PIO_PER = pwmp->pins->pin[1].portpin;
|
||||
if ((pwmp->config->channels[0].mode & PWM_OUTPUT_PIN3) && pwmp->pins->pin[2].portpin)
|
||||
pwmp->pins->pin[2].pio->PIO_PER = pwmp->pins->pin[2].portpin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Changes the period the PWM peripheral.
|
||||
* @details This function changes the period of a PWM unit that has already
|
||||
* been activated using @p pwmStart().
|
||||
* @pre The PWM unit must have been activated using @p pwmStart().
|
||||
* @post The PWM unit period is changed to the new value.
|
||||
* @note The function has effect at the next cycle start.
|
||||
* @note If a period is specified that is shorter than the pulse width
|
||||
* programmed in one of the channels then the behavior is not
|
||||
* guaranteed.
|
||||
*
|
||||
* @param[in] pwmp pointer to a @p PWMDriver object
|
||||
* @param[in] period new cycle time in ticks
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period) {
|
||||
pwmp->period = period;
|
||||
|
||||
if (PWMC_M->PWMC_SR & pwmp->chbit) {
|
||||
pwmp->reg->PWMC_CMR |= AT91C_PWMC_CPD;
|
||||
pwmp->reg->PWMC_CUPDR = period;
|
||||
} else {
|
||||
pwmp->reg->PWMC_CPRDR = period;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enables a PWM channel.
|
||||
* @pre The PWM unit must have been activated using @p pwmStart().
|
||||
* @post The channel is active using the specified configuration.
|
||||
* @note Depending on the hardware implementation this function has
|
||||
* effect starting on the next cycle (recommended implementation)
|
||||
* or immediately (fallback implementation).
|
||||
*
|
||||
* @param[in] pwmp pointer to a @p PWMDriver object
|
||||
* @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1)
|
||||
* @param[in] width PWM pulse width as clock pulses number
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_enable_channel(PWMDriver *pwmp,
|
||||
pwmchannel_t UNUSED(channel),
|
||||
pwmcnt_t width) {
|
||||
/*
|
||||
6. Configuration of the duty cycle for each channel (CDTY in the PWM_CDTYx register).
|
||||
Writing in PWM_CDTYx Register is possible while the channel is disabled. After validation of
|
||||
the channel, the user must use PWM_CUPDx Register to update PWM_CDTYx.
|
||||
7. Enable the PWM channel (Writing CHIDx in the PWM_ENA register)
|
||||
*/
|
||||
|
||||
/* Step 6 */
|
||||
if (PWMC_M->PWMC_SR & pwmp->chbit) {
|
||||
pwmp->reg->PWMC_CMR &= ~AT91C_PWMC_CPD;
|
||||
pwmp->reg->PWMC_CUPDR = width;
|
||||
} else {
|
||||
pwmp->reg->PWMC_CDTYR = width;
|
||||
PWMC_M->PWMC_ENA = pwmp->chbit;
|
||||
}
|
||||
|
||||
/* Step 7 */
|
||||
PWMC_M->PWMC_ENA = pwmp->chbit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disables a PWM channel.
|
||||
* @pre The PWM unit must have been activated using @p pwmStart().
|
||||
* @post The channel is disabled and its output line returned to the
|
||||
* idle state.
|
||||
* @note Depending on the hardware implementation this function has
|
||||
* effect starting on the next cycle (recommended implementation)
|
||||
* or immediately (fallback implementation).
|
||||
*
|
||||
* @param[in] pwmp pointer to a @p PWMDriver object
|
||||
* @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1)
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t UNUSED(channel)) {
|
||||
PWMC_M->PWMC_IDR = pwmp->chbit;
|
||||
PWMC_M->PWMC_DIS = pwmp->chbit;
|
||||
}
|
||||
|
||||
#endif /* HAL_USE_PWM */
|
||||
|
||||
/** @} */
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
---
|
||||
|
||||
A special exception to the GPL can be applied should you wish to distribute
|
||||
a combined work that includes ChibiOS/RT, without being obliged to provide
|
||||
the source code for any proprietary components. See the file exception.txt
|
||||
for full details of how and when the exception can be applied.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file templates/pwm_lld.h
|
||||
* @brief PWM Driver subsystem low level driver header template.
|
||||
*
|
||||
* @addtogroup PWM
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef _PWM_LLD_H_
|
||||
#define _PWM_LLD_H_
|
||||
|
||||
#if HAL_USE_PWM || defined(__DOXYGEN__)
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver constants. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver pre-compile time settings. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @brief Number of PWM channels per PWM driver.
|
||||
*/
|
||||
#define PWM_CHANNELS 1
|
||||
|
||||
/**
|
||||
* @brief PWM device interrupt priority level setting.
|
||||
*/
|
||||
#if !defined(AT91SAM7_PWM_PRIORITY) || defined(__DOXYGEN__)
|
||||
#define AT91SAM7_PWM_PRIORITY (AT91C_AIC_PRIOR_HIGHEST - 4)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PWMD1 driver enable switch.
|
||||
* @details If set to @p TRUE the support for PWMD1 is included.
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(PWM_USE_PWM1) || defined(__DOXYGEN__)
|
||||
#define PWM_USE_PWM1 TRUE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PWMD2 driver enable switch.
|
||||
* @details If set to @p TRUE the support for PWMD1 is included.
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(PWM_USE_PWM2) || defined(__DOXYGEN__)
|
||||
#define PWM_USE_PWM2 TRUE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PWMD3 driver enable switch.
|
||||
* @details If set to @p TRUE the support for PWMD1 is included.
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(PWM_USE_PWM3) || defined(__DOXYGEN__)
|
||||
#define PWM_USE_PWM3 TRUE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PWMD4 driver enable switch.
|
||||
* @details If set to @p TRUE the support for PWMD1 is included.
|
||||
* @note The default is @p TRUE.
|
||||
*/
|
||||
#if !defined(PWM_USE_PWM4) || defined(__DOXYGEN__)
|
||||
#define PWM_USE_PWM4 TRUE
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PWM left (count up) logic
|
||||
*/
|
||||
#define PWM_OUTPUT_LEFT 0x00000000
|
||||
|
||||
/**
|
||||
* @brief PWM center (count up-down) logic. Gives symetric waveform
|
||||
*/
|
||||
#define PWM_OUTPUT_CENTER 0x00000010
|
||||
|
||||
/**
|
||||
* @brief PWM Master Clock = MCK / 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, CLKA or CLKB. CLKA or CLKB uses the frequency field
|
||||
*/
|
||||
#define PWM_MCK_DIV_1 0x00000000
|
||||
#define PWM_MCK_DIV_2 0x00000100
|
||||
#define PWM_MCK_DIV_4 0x00000200
|
||||
#define PWM_MCK_DIV_8 0x00000300
|
||||
#define PWM_MCK_DIV_16 0x00000400
|
||||
#define PWM_MCK_DIV_32 0x00000500
|
||||
#define PWM_MCK_DIV_64 0x00000600
|
||||
#define PWM_MCK_DIV_128 0x00000700
|
||||
#define PWM_MCK_DIV_256 0x00000800
|
||||
#define PWM_MCK_DIV_512 0x00000900
|
||||
#define PWM_MCK_DIV_1024 0x00000A00
|
||||
#define PWM_MCK_DIV_CLKA 0x00000B00
|
||||
#define PWM_MCK_DIV_CLKB 0x00000C00
|
||||
|
||||
/**
|
||||
* @brief Which PWM output pins to turn on. PIN1 is the lowest numbered pin, PIN2 next lowest, and then on some packages PIN3.
|
||||
*/
|
||||
#define PWM_OUTPUT_PIN1 0x00001000
|
||||
#define PWM_OUTPUT_PIN2 0x00002000
|
||||
#define PWM_OUTPUT_PIN3 0x00004000
|
||||
|
||||
/**
|
||||
* @brief Which PWM output pins should have pullups disabled.
|
||||
*/
|
||||
#define PWM_DISABLEPULLUP_PIN1 0x00010000
|
||||
#define PWM_DISABLEPULLUP_PIN2 0x00020000
|
||||
#define PWM_DISABLEPULLUP_PIN3 0x00040000
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Derived constants and error checks. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver data structures and types. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/**
|
||||
* @brief PWM mode type.
|
||||
*/
|
||||
typedef uint32_t pwmmode_t;
|
||||
|
||||
/**
|
||||
* @brief PWM channel type.
|
||||
*/
|
||||
typedef uint8_t pwmchannel_t;
|
||||
|
||||
/**
|
||||
* @brief PWM counter type.
|
||||
*/
|
||||
typedef uint16_t pwmcnt_t;
|
||||
|
||||
/**
|
||||
* @brief PWM driver channel configuration structure.
|
||||
* @note Some architectures may not be able to support the channel mode
|
||||
* or the callback, in this case the fields are ignored.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Channel active logic level.
|
||||
*/
|
||||
pwmmode_t mode;
|
||||
/**
|
||||
* @brief Channel callback pointer.
|
||||
* @note This callback is invoked on the channel compare event. If set to
|
||||
* @p NULL then the callback is disabled.
|
||||
*/
|
||||
pwmcallback_t callback;
|
||||
/* End of the mandatory fields.*/
|
||||
} PWMChannelConfig;
|
||||
|
||||
/**
|
||||
* @brief Driver configuration structure.
|
||||
* @note Implementations may extend this structure to contain more,
|
||||
* architecture dependent, fields.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* @brief Timer clock in Hz.
|
||||
* @note The low level can use assertions in order to catch invalid
|
||||
* frequency specifications.
|
||||
*/
|
||||
uint32_t frequency;
|
||||
/**
|
||||
* @brief PWM period in ticks.
|
||||
* @note The low level can use assertions in order to catch invalid
|
||||
* period specifications.
|
||||
*/
|
||||
pwmcnt_t period;
|
||||
/**
|
||||
* @brief Periodic callback pointer.
|
||||
* @note This callback is invoked on PWM counter reset. If set to
|
||||
* @p NULL then the callback is disabled.
|
||||
*/
|
||||
pwmcallback_t callback;
|
||||
/**
|
||||
* @brief Channels configurations.
|
||||
*/
|
||||
PWMChannelConfig channels[PWM_CHANNELS];
|
||||
/* End of the mandatory fields.*/
|
||||
} PWMConfig;
|
||||
|
||||
/**
|
||||
* @brief Structure representing an PWM driver.
|
||||
* @note Implementations may extend this structure to contain more,
|
||||
* architecture dependent, fields.
|
||||
*/
|
||||
struct PWMDriver {
|
||||
/**
|
||||
* @brief Driver state.
|
||||
*/
|
||||
pwmstate_t state;
|
||||
/**
|
||||
* @brief Current configuration data.
|
||||
*/
|
||||
const PWMConfig *config;
|
||||
/**
|
||||
* @brief Current PWM period in ticks.
|
||||
*/
|
||||
pwmcnt_t period;
|
||||
#if defined(PWM_DRIVER_EXT_FIELDS)
|
||||
PWM_DRIVER_EXT_FIELDS
|
||||
#endif
|
||||
|
||||
/* End of the mandatory fields.*/
|
||||
|
||||
/**
|
||||
* @brief The PWM internal channel number as a bit mask (1, 2, 4 or 8).
|
||||
*/
|
||||
uint32_t chbit;
|
||||
/**
|
||||
* @brief Pointer to the PWMCx registers block.
|
||||
*/
|
||||
AT91S_PWMC_CH *reg;
|
||||
/**
|
||||
* @brief Pointer to the output pins descriptor.
|
||||
*/
|
||||
const struct pwmpindefs *pins;
|
||||
};
|
||||
|
||||
/*===========================================================================*/
|
||||
/* Driver macros. */
|
||||
/*===========================================================================*/
|
||||
|
||||
/*===========================================================================*/
|
||||
/* External declarations. */
|
||||
/*===========================================================================*/
|
||||
|
||||
#if PWM_USE_PWM1 && !defined(__DOXYGEN__)
|
||||
extern PWMDriver PWMD1;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM2 && !defined(__DOXYGEN__)
|
||||
extern PWMDriver PWMD2;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM3 && !defined(__DOXYGEN__)
|
||||
extern PWMDriver PWMD3;
|
||||
#endif
|
||||
|
||||
#if PWM_USE_PWM4 && !defined(__DOXYGEN__)
|
||||
extern PWMDriver PWMD4;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void pwm_lld_init(void);
|
||||
void pwm_lld_start(PWMDriver *pwmp);
|
||||
void pwm_lld_stop(PWMDriver *pwmp);
|
||||
void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period);
|
||||
void pwm_lld_enable_channel(PWMDriver *pwmp,
|
||||
pwmchannel_t channel,
|
||||
pwmcnt_t width);
|
||||
void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HAL_USE_PWM */
|
||||
|
||||
#endif /* _PWM_LLD_H_ */
|
||||
|
||||
/** @} */
|
Loading…
Reference in New Issue