/* ChibiOS - Copyright (C) 2006..2016 Giovanni Di Sirio Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* This driver is based on the work done by Matteo Serva available at http://github.com/matteoserva/ChibiOS-AVR */ /** * @file AVR/pwm_lld.c * @brief AVR PWM driver subsystem low level driver. * * @addtogroup PWM * @{ */ #include "hal.h" #if HAL_USE_PWM || defined(__DOXYGEN__) /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ typedef struct { volatile uint8_t *tccra; volatile uint8_t *tccrb; volatile uint8_t *ocrah; volatile uint8_t *ocral; volatile uint8_t *ocrbh; volatile uint8_t *ocrbl; volatile uint8_t *ocrch; volatile uint8_t *ocrcl; volatile uint8_t *tifr; volatile uint8_t *timsk; } timer_registers_t; static timer_registers_t regs_table[]= { #if AVR_PWM_USE_TIM1 || defined(__DOXYGEN__) #if defined(OCR1C) {&TCCR1A, &TCCR1B, &OCR1AH, &OCR1AL, &OCR1BH, &OCR1BL, &OCR1CH, &OCR1CL, &TIFR1, &TIMSK1}, #else {&TCCR1A, &TCCR1B, &OCR1AH, &OCR1AL, &OCR1BH, &OCR1BL, NULL, NULL, &TIFR1, &TIMSK1}, #endif #endif #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) {&TCCR2A, &TCCR2B, &OCR2A, &OCR2A, &OCR2B, &OCR2B, NULL, NULL, &TIFR2, &TIMSK2}, #endif #if AVR_PWM_USE_TIM3 || defined(__DOXYGEN__) {&TCCR3A, &TCCR3B, &OCR3AH, &OCR3AL, &OCR3BH, &OCR3BL, &OCR3CH, &OCR3CL, &TIFR3, &TIMSK3}, #endif #if AVR_PWM_USE_TIM4 || defined(__DOXYGEN__) {&TCCR4A, &TCCR4B, &OCR4AH, &OCR4AL, &OCR4CH, &OCR4CL, &OCR4CH, &OCR4CL, &TIFR4, &TIMSK4}, #endif #if AVR_PWM_USE_TIM5 || defined(__DOXYGEN__) {&TCCR5A, &TCCR5B, &OCR5AH, &OCR5AL, &OCR5BH, &OCR5BL, &OCR5CH, &OCR5CL, &TIFR5, &TIMSK5}, #endif }; /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ /** @brief PWM driver identifiers.*/ #if AVR_PWM_USE_TIM1 || defined(__DOXYGEN__) PWMDriver PWMD1; #endif #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) PWMDriver PWMD2; #endif #if AVR_PWM_USE_TIM3 || defined(__DOXYGEN__) PWMDriver PWMD3; #endif #if AVR_PWM_USE_TIM4 || defined(__DOXYGEN__) PWMDriver PWMD4; #endif #if AVR_PWM_USE_TIM5 || defined(__DOXYGEN__) PWMDriver PWMD5; #endif /*===========================================================================*/ /* Driver local variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Driver local functions. */ /*===========================================================================*/ static void config_channel(volatile uint8_t *tccra, uint8_t com1, uint8_t com0, pwmmode_t mode) { *tccra &= ~((1 << com1) | (1 << com0)); if (mode == PWM_OUTPUT_ACTIVE_HIGH) *tccra |= ((1 << com1) | (0 << com0)); /* non inverting mode */ else if (mode == PWM_OUTPUT_ACTIVE_LOW) *tccra |= (1 << com1) | (1 << com0); /* inverting mode */ } static uint8_t timer_index(PWMDriver *pwmp) { uint8_t index = 0; #if AVR_PWM_USE_TIM1 || defined(__DOXYGEN__) if (pwmp == &PWMD1) return index; else index++; #endif #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) if (pwmp == &PWMD2) return index; else index++; #endif #if AVR_PWM_USE_TIM3 || defined(__DOXYGEN__) if (pwmp == &PWMD3) return index; else index++; #endif #if AVR_PWM_USE_TIM4 || defined(__DOXYGEN__) if (pwmp == &PWMD4) return index; else index++; #endif #if AVR_PWM_USE_TIM5 || defined(__DOXYGEN__) if (pwmp == &PWMD5) return index; else index++; #endif /* This is an error! */ return index; } /*===========================================================================*/ /* Driver interrupt handlers. */ /*===========================================================================*/ /* * interrupt for compare1&2 and clock overflow. pwmd1 & pwmd2 */ #if AVR_PWM_USE_TIM1 || defined(__DOXYGEN__) OSAL_IRQ_HANDLER(TIMER1_OVF_vect) { OSAL_IRQ_PROLOGUE(); PWMD1.config->callback(&PWMD1); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER1_COMPA_vect) { OSAL_IRQ_PROLOGUE(); PWMD1.config->channels[0].callback(&PWMD1); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER1_COMPB_vect) { OSAL_IRQ_PROLOGUE(); PWMD1.config->channels[1].callback(&PWMD1); OSAL_IRQ_EPILOGUE(); } #if PWM_CHANNELS > 2 OSAL_IRQ_HANDLER(TIMER1_COMPC_vect) { OSAL_IRQ_PROLOGUE(); PWMD1.config->channels[2].callback(&PWMD1); OSAL_IRQ_EPILOGUE(); } #endif #endif #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) OSAL_IRQ_HANDLER(TIMER2_OVF_vect) { OSAL_IRQ_PROLOGUE(); PWMD2.config->callback(&PWMD2); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER2_COMPA_vect) { OSAL_IRQ_PROLOGUE(); PWMD2.config->channels[0].callback(&PWMD2); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER2_COMPB_vect) { OSAL_IRQ_PROLOGUE(); PWMD2.config->channels[1].callback(&PWMD2); OSAL_IRQ_EPILOGUE(); } #endif #if AVR_PWM_USE_TIM3 || defined(__DOXYGEN__) OSAL_IRQ_HANDLER(TIMER3_OVF_vect) { OSAL_IRQ_PROLOGUE(); PWMD3.config->callback(&PWMD3); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER3_COMPA_vect) { OSAL_IRQ_PROLOGUE(); PWMD3.config->channels[0].callback(&PWMD3); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER3_COMPB_vect) { OSAL_IRQ_PROLOGUE(); PWMD3.config->channels[1].callback(&PWMD3); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER3_COMPC_vect) { OSAL_IRQ_PROLOGUE(); PWMD3.config->channels[2].callback(&PWMD3); OSAL_IRQ_EPILOGUE(); } #endif #if AVR_PWM_USE_TIM4 || defined(__DOXYGEN__) OSAL_IRQ_HANDLER(TIMER4_OVF_vect) { OSAL_IRQ_PROLOGUE(); PWMD4.config->callback(&PWMD4); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER4_COMPA_vect) { OSAL_IRQ_PROLOGUE(); PWMD4.config->channels[0].callback(&PWMD4); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER4_COMPB_vect) { OSAL_IRQ_PROLOGUE(); PWMD4.config->channels[1].callback(&PWMD4); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER4_COMPC_vect) { OSAL_IRQ_PROLOGUE(); PWMD4.config->channels[2].callback(&PWMD4); OSAL_IRQ_EPILOGUE(); } #endif #if AVR_PWM_USE_TIM5 || defined(__DOXYGEN__) OSAL_IRQ_HANDLER(TIMER5_OVF_vect) { OSAL_IRQ_PROLOGUE(); PWMD5.config->callback(&PWMD5); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER5_COMPA_vect) { OSAL_IRQ_PROLOGUE(); PWMD5.config->channels[0].callback(&PWMD5); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER5_COMPB_vect) { OSAL_IRQ_PROLOGUE(); PWMD5.config->channels[1].callback(&PWMD5); OSAL_IRQ_EPILOGUE(); } OSAL_IRQ_HANDLER(TIMER5_COMPC_vect) { OSAL_IRQ_PROLOGUE(); PWMD5.config->channels[2].callback(&PWMD5); OSAL_IRQ_EPILOGUE(); } #endif /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ /** * @brief Low level PWM driver initialization. * * @notapi */ void pwm_lld_init(void) { #if AVR_PWM_USE_TIM1 || defined(__DOXYGEN__) pwmObjectInit(&PWMD1); PWMD1.channels = PWM_CHANNELS; TCCR1A = (1 << WGM11) | (1 << WGM10); TCCR1B = (0 << WGM13) | (1 << WGM12); #endif #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) pwmObjectInit(&PWMD2); PWMD2.channels = PWM_CHANNELS; TCCR2A = (1 << WGM21) | (1 << WGM20); TCCR2B = (0 << WGM22); #endif #if AVR_PWM_USE_TIM3 || defined(__DOXYGEN__) pwmObjectInit(&PWMD3); PWMD3.channels = PWM_CHANNELS; TCCR3A = (1 << WGM31) | (1 << WGM30); TCCR3B = (0 << WGM33) | (1 << WGM32); #endif #if AVR_PWM_USE_TIM4 || defined(__DOXYGEN__) pwmObjectInit(&PWMD4); PWMD4.channels = PWM_CHANNELS; TCCR4A = (1 << WGM41) | (1 << WGM40); TCCR4B = (0 << WGM43) | (1 << WGM42); #endif #if AVR_PWM_USE_TIM5 || defined(__DOXYGEN__) pwmObjectInit(&PWMD5); PWMD5.channels = PWM_CHANNELS; TCCR5A = (1 << WGM51) | (1 << WGM50); TCCR5B = (0 << WGM53) | (1 << WGM52); #endif } /** * @brief Configures and activates the PWM peripheral. * * @param[in] pwmp pointer to the @p PWMDriver object * * @notapi */ void pwm_lld_start(PWMDriver *pwmp) { if (pwmp->state == PWM_STOP) { #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) if (pwmp == &PWMD2) { TCCR2B &= ~((1 << CS22) | (1 << CS21)); TCCR2B |= (1 << CS20); if (pwmp->config->callback != NULL) TIMSK2 |= (1 << TOIE2); return; } #endif uint8_t i = timer_index(pwmp); /* TODO: support other prescaler options */ *regs_table[i].tccrb &= ~(1 << CS11); *regs_table[i].tccrb |= (1 << CS12) | (1 << CS10); if (pwmp->config->callback != NULL) *regs_table[i].timsk = (1 << TOIE1); } } /** * @brief Deactivates the PWM peripheral. * * @param[in] pwmp pointer to the @p PWMDriver object * * @notapi */ void pwm_lld_stop(PWMDriver *pwmp) { uint8_t i = timer_index(pwmp); *regs_table[i].tccrb &= ~((1 << CS12) | (1 << CS11) | (1 << CS10)); *regs_table[i].timsk = 0; } /** * @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) { } /** * @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 channel, pwmcnt_t width) { uint16_t val = width; if (val > MAX_PWM_VALUE) val = MAX_PWM_VALUE; #if AVR_PWM_USE_TIM2 || defined(__DOXYGEN__) if (pwmp == &PWMD2) { config_channel(&TCCR2A, 7 - 2*channel, 6 - 2*channel, pwmp->config->channels[channel].mode); TIMSK2 |= (1 << (channel + 1)); /* Timer 2 is 8 bit */ if (val > 0xFF) val = 0xFF; if (pwmp->config->channels[channel].callback) { switch (channel) { case 0: OCR2A = val; break; case 1: OCR2B = val; break; } } return; } #endif uint8_t i = timer_index(pwmp); config_channel(regs_table[i].tccra, 7 - 2*channel, 6 - 2*channel, pwmp->config->channels[channel].mode); volatile uint8_t *ocrh, *ocrl; switch (channel) { case 1: ocrh = regs_table[i].ocrbh; ocrl = regs_table[i].ocrbl; break; case 2: ocrh = regs_table[i].ocrch; ocrl = regs_table[i].ocrcl; break; default: ocrh = regs_table[i].ocrah; ocrl = regs_table[i].ocral; } *ocrh = val >> 8; *ocrl = val & 0xFF; *regs_table[i].tifr |= (1 << (channel + 1)); if (pwmp->config->channels[channel].callback != NULL) *regs_table[i].timsk |= (1 << (channel + 1)); } /** * @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 channel) { uint8_t i = timer_index(pwmp); config_channel(regs_table[i].tccra, 7 - 2*channel, 6 - 2*channel, PWM_OUTPUT_DISABLED); *regs_table[i].timsk &= ~(1 << (channel + 1)); } /** * @brief Enables the periodic activation edge notification. * @pre The PWM unit must have been activated using @p pwmStart(). * @note If the notification is already enabled then the call has no effect. * * @param[in] pwmp pointer to a @p PWMDriver object * * @notapi */ void pwm_lld_enable_periodic_notification(PWMDriver *pwmp) { uint8_t i = timer_index(pwmp); *regs_table[i].timsk |= (1 << TOIE1); } /** * @brief Disables the periodic activation edge notification. * @pre The PWM unit must have been activated using @p pwmStart(). * @note If the notification is already disabled then the call has no effect. * * @param[in] pwmp pointer to a @p PWMDriver object * * @notapi */ void pwm_lld_disable_periodic_notification(PWMDriver *pwmp) { uint8_t i = timer_index(pwmp); *regs_table[i].timsk &= ~(1 << TOIE1); } /** * @brief Enables a channel de-activation edge notification. * @pre The PWM unit must have been activated using @p pwmStart(). * @pre The channel must have been activated using @p pwmEnableChannel(). * @note If the notification is already enabled then the call has no effect. * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...channels-1) * * @notapi */ void pwm_lld_enable_channel_notification(PWMDriver *pwmp, pwmchannel_t channel) { uint8_t i = timer_index(pwmp); *regs_table[i].timsk |= (1 << (channel + 1)); } /** * @brief Disables a channel de-activation edge notification. * @pre The PWM unit must have been activated using @p pwmStart(). * @pre The channel must have been activated using @p pwmEnableChannel(). * @note If the notification is already disabled then the call has no effect. * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...channels-1) * * @notapi */ void pwm_lld_disable_channel_notification(PWMDriver *pwmp, pwmchannel_t channel) { uint8_t i = timer_index(pwmp); *regs_table[i].timsk &= ~(1 << (channel + 1)); } #endif /* HAL_USE_PWM */ /** @} */