From 0d21b754442c7b27e0b6df9db8f747ca4621df3b Mon Sep 17 00:00:00 2001 From: gdisirio Date: Sun, 17 Feb 2013 14:20:07 +0000 Subject: ADC and PWM drivers for AT91SAM7. git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@5228 35acf78f-673a-0410-8e92-d51de3d6d3f4 --- os/hal/platforms/AT91SAM7/adc_lld.c | 386 +++++++++++++++++++++++++++ os/hal/platforms/AT91SAM7/adc_lld.h | 307 ++++++++++++++++++++++ os/hal/platforms/AT91SAM7/platform.mk | 2 + os/hal/platforms/AT91SAM7/pwm_lld.c | 474 ++++++++++++++++++++++++++++++++++ os/hal/platforms/AT91SAM7/pwm_lld.h | 292 +++++++++++++++++++++ 5 files changed, 1461 insertions(+) create mode 100644 os/hal/platforms/AT91SAM7/adc_lld.c create mode 100644 os/hal/platforms/AT91SAM7/adc_lld.h create mode 100644 os/hal/platforms/AT91SAM7/pwm_lld.c create mode 100644 os/hal/platforms/AT91SAM7/pwm_lld.h (limited to 'os/hal/platforms/AT91SAM7') diff --git a/os/hal/platforms/AT91SAM7/adc_lld.c b/os/hal/platforms/AT91SAM7/adc_lld.c new file mode 100644 index 000000000..43325d16b --- /dev/null +++ b/os/hal/platforms/AT91SAM7/adc_lld.c @@ -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 . +*/ +/* + 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 */ + +/** @} */ diff --git a/os/hal/platforms/AT91SAM7/adc_lld.h b/os/hal/platforms/AT91SAM7/adc_lld.h new file mode 100644 index 000000000..c99879e78 --- /dev/null +++ b/os/hal/platforms/AT91SAM7/adc_lld.h @@ -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 . +*/ +/* + 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_ */ + +/** @} */ diff --git a/os/hal/platforms/AT91SAM7/platform.mk b/os/hal/platforms/AT91SAM7/platform.mk index a460b8532..40a71afc9 100644 --- a/os/hal/platforms/AT91SAM7/platform.mk +++ b/os/hal/platforms/AT91SAM7/platform.mk @@ -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 diff --git a/os/hal/platforms/AT91SAM7/pwm_lld.c b/os/hal/platforms/AT91SAM7/pwm_lld.c new file mode 100644 index 000000000..f2314e6a3 --- /dev/null +++ b/os/hal/platforms/AT91SAM7/pwm_lld.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 . + + --- + + 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 */ + +/** @} */ diff --git a/os/hal/platforms/AT91SAM7/pwm_lld.h b/os/hal/platforms/AT91SAM7/pwm_lld.h new file mode 100644 index 000000000..cd4a85b2b --- /dev/null +++ b/os/hal/platforms/AT91SAM7/pwm_lld.h @@ -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 . + + --- + + 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_ */ + +/** @} */ -- cgit v1.2.3