/**************************************************************************//**
|
* @file PWM.c
|
* @version V1.00
|
* $Revision: 3 $
|
* $Date: 19/11/18 19:41 $
|
* @brief Panchip series PWM driver source file
|
*
|
* @note
|
* Copyright (C) 2016 Panchip Technology Corp. All rights reserved.
|
*****************************************************************************/
|
|
#include "PanSeries.h"
|
#include "pan_pwm.h"
|
#include "pan_clk.h"
|
|
/** @addtogroup Panchip_Device_Driver Panchip Device Driver
|
@{
|
*/
|
|
/** @addtogroup Panchip_PWM_Driver PWM Driver
|
@{
|
*/
|
|
|
/** @addtogroup Panchip_PWM_EXPORTED_FUNCTIONS PWM Exported Functions
|
@{
|
*/
|
|
/**
|
* @brief This function config PWM generator and get the nearest frequency in edge aligned auto-reload mode
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @param[in] u32Frequency Target generator frequency
|
* @param[in] u32DutyCycle Target generator duty cycle percentage. Valid range are between 0 ~ 100. 10 means 10%, 20 means 20%...
|
* @param[in] OperateType Target operation type.Valid value Edge-aligned,Center-Aligned,Precise Center-Aligned
|
* @return Nearest frequency clock in nano second
|
* @note Since every two channels, (0 & 1), (2 & 3), (4 & 5), (6 & 7) shares a prescaler. Call this API to configure PWM frequency may affect
|
* existing frequency of other channel.
|
*/
|
uint32_t PWM_ConfigOutputChannel (PWM_T *pwm,
|
uint32_t u32ChannelNum,
|
uint32_t u32Frequency,
|
uint32_t u32DutyCycle,
|
PWM_OperateTypeDef OperateType)
|
{
|
uint32_t i = SystemCoreClock / u32Frequency;
|
uint8_t u8Divider = 1, u8Prescale = 0xFF;
|
uint16_t u16period = 0xFFFF;
|
// uint32_t u32Hclk = SystemCoreClock;
|
uint32_t u32clk;
|
|
// Judge clock source
|
if (!(CLK->APB1_CLK_CTRL & (0x1ul << ((u32ChannelNum / 2) + CLK_APB1CLK_PWM01_CLK_SEL_Pos))))
|
{
|
u32clk = CLK_GetPCLK1Freq();
|
}
|
else
|
{
|
if (CLK->CLK_TOP_CTRL_3V & CLK_TOPCTL_32K_CLK_SEL_Msk_3v)
|
{
|
u32clk = FREQ_XTL;
|
}
|
else
|
{
|
u32clk = FREQ_RCL;
|
}
|
}
|
for (; u8Divider < 17; u8Divider <<= 1)
|
{ // clk divider could only be 1, 2, 4, 8, 16
|
/*u32Frequency = SystemCoreClock /((u8Prescale+1)*(u8Divider))/( PERIODn+1) ==>
|
(u8Prescale+1)*(PERIODn+1) = SystemCoreClock/u32Frequency/u8Divider */
|
i = (u32clk / u32Frequency) / u8Divider;
|
|
/* If target value is larger than (u16period+1)*(u8Prescale+1), need to use a larger divider*/
|
if (i > (0x10000 * 0x100))
|
continue;
|
|
/* u16period = 0xFFFF + 1, get a prescaler that u16period value is below 0xFFFF
|
Must first determine a variable value, now we choose u16period*/
|
if ((pwm->CLKPSC >> ((u32ChannelNum >> 1) * 8) & 0xff) > 0)
|
{
|
u8Prescale = (pwm->CLKPSC >> ((u32ChannelNum >> 1) * 8) & 0xff) + 1;
|
}
|
else
|
{
|
u8Prescale = (i + 0xFFFF) / 0x10000;
|
}
|
/* u8Prescale must at least be 2, otherwise the output stop*/
|
if (u8Prescale < 3)
|
u8Prescale = 2;
|
|
/* (PERIODn+1) = i / u8Prescale */
|
i /= u8Prescale;
|
|
/*u16period must less than 0xFFFF*/
|
if (i <= 0x10000)
|
{
|
if (i == 1)
|
{
|
u16period = 1; // Too fast, and PWM cannot generate expected frequency...
|
}
|
else
|
{
|
if (OperateType == OPERATION_EDGE_ALIGNED)
|
u16period = i;
|
else if (OperateType == OPERATION_CENTER_ALIGNED)
|
u16period = i / 2;
|
else if (OperateType == OPERATION_PRECISE_CENTER_ALIGNED)
|
u16period = i;
|
}
|
break;
|
}
|
}
|
/* Store return value here 'cos we're gonna change u8Divider & u8Prescale & u16period to the real value to fill into register*/
|
i = u32clk / (u8Prescale * u8Divider * u16period);
|
|
u8Prescale -= 1;
|
if(OperateType == OPERATION_PRECISE_CENTER_ALIGNED)
|
u16period = u16period;
|
else
|
u16period -= 1;
|
|
/* convert to real register value*/
|
if(u8Divider == 1)
|
u8Divider = 4;
|
else if (u8Divider == 2)
|
u8Divider = 0;
|
else if (u8Divider == 4)
|
u8Divider = 1;
|
else if (u8Divider == 8)
|
u8Divider = 2;
|
else // 16
|
u8Divider = 3;
|
|
/* every two channels share a prescaler*/
|
pwm->CLKPSC = (pwm->CLKPSC & ~(PWM_CLKPSC_CLKPSC01_Msk << ((u32ChannelNum >> 1) * 8))) | (u8Prescale << ((u32ChannelNum >> 1) * 8));
|
pwm->CLKDIV = (pwm->CLKDIV & ~(PWM_CLKDIV_CLKDIV0_Msk << (4 * u32ChannelNum))) | (u8Divider << (4 * u32ChannelNum));
|
/*set cnt mode as auto reload*/
|
/* PWM_SetCntMode(pwm,u32ChannelNum,PWM_CNTMODE_AUTO_RELOAD); */
|
|
/*set cnt type as edge align*/
|
PWM_SetAlignedType(pwm, u32ChannelNum, PWM_EDGE_ALIGNED);
|
|
if((OperateType == OPERATION_CENTER_ALIGNED)||(OperateType == OPERATION_PRECISE_CENTER_ALIGNED)){
|
/*set cnt type as center align*/
|
PWM_SetAlignedType(pwm, u32ChannelNum, PWM_CENTER_ALIGNED);
|
if(OperateType == OPERATION_PRECISE_CENTER_ALIGNED)
|
PWM_EnablePCA(pwm); //Precise center-aligned type Enabled.
|
else
|
PWM_DisablePCA(pwm);
|
}
|
|
/*update compare data register, Duty ratio = (CMPn+1)/(PERIODn+1).*/
|
if(u32DutyCycle == 0){
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->CMPDAT0)) + u32ChannelNum * 4))) = 0;
|
}else{
|
if(OperateType == OPERATION_EDGE_ALIGNED){
|
/*set cnt type as edge align compare data*/
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->CMPDAT0)) + u32ChannelNum * 4))) = u32DutyCycle * (u16period + 1) / 100 - 1;
|
}
|
else if(OperateType == OPERATION_CENTER_ALIGNED){
|
/*set cnt type as center align compare data*/
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->CMPDAT0)) + u32ChannelNum * 4))) = u16period-(u32DutyCycle * (u16period + 1) / 100) ;
|
}
|
else if(OperateType == OPERATION_PRECISE_CENTER_ALIGNED){
|
/*set cnt type as precise center align compare data*/
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->CMPDAT0)) + u32ChannelNum * 4))) = (u16period-(u16period*u32DutyCycle/100))/2-1 ;
|
}
|
}
|
/*update Counter data register*/
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->PERIOD0)) + (u32ChannelNum) * 4))) = u16period;
|
|
return(i);
|
}
|
|
/**
|
* @brief This function configures PWM generator to set period/duty cycles and output level mode.
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @param[in] u16PeriodCycle Target period cycles to set
|
* @param[in] u16PulseCycle Target pulse (high level) cycles to set
|
* @note IF u16PeriodCycle = 0 OR u16PulseCycle = 0, THEN the output is always low;
|
* ELSE IF u16PeriodCycle < u16PulseCycle, THEN the output is always high;
|
* ELSE the final output PWM waveform meets the following formulas:
|
* Actual period cycles = u16PeriodCycle + 1
|
* Actual high-level cycles = u16PulseCycle + 1
|
* Actual low-level cycles = u16PeriodCycle - u16PulseCycle
|
* Actual duty cycle = (u16PulseCycle + 1) / (u16PeriodCycle + 1)
|
* @return None
|
*/
|
void PWM_SetPeriodAndDuty(PWM_T *pwm, uint32_t u32ChannelNum, uint16_t u16PeriodCycle, uint16_t u16PulseCycle)
|
{
|
PWM_SetCNR(pwm, u32ChannelNum, u16PeriodCycle);
|
PWM_SetCMR(pwm, u32ChannelNum, u16PulseCycle);
|
}
|
|
/**
|
* @brief This function start PWM module
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
|
* Bit 0 is channel 0, bit 1 is channel 1...
|
* @return None
|
*/
|
void PWM_Start(PWM_T *pwm, uint32_t u32ChannelMask)
|
{
|
uint32_t u32Mask = 0, i;
|
for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
|
if(u32ChannelMask & (1 << i)) {
|
u32Mask |= (PWM_CTL_CNTEN0_Msk << (i * 4));
|
}
|
}
|
pwm->CTL |= u32Mask;
|
}
|
|
/**
|
* @brief This function stop PWM module
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
|
* Bit 0 is channel 0, bit 1 is channel 1...
|
* @return None
|
*/
|
void PWM_Stop(PWM_T *pwm, uint32_t u32ChannelMask)
|
{
|
uint32_t i;
|
for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
|
if(u32ChannelMask & (1 << i)) {
|
*((__IO uint32_t *)((((uint32_t) & ((pwm)->PERIOD0)) + (i) * 4))) = 0;
|
}
|
}
|
}
|
|
/**
|
* @brief This function stop PWM generation immediately by clear channel enable bit
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
|
* Bit 0 is channel 0, bit 1 is channel 1...
|
* @return None
|
*/
|
void PWM_ForceStop(PWM_T *pwm, uint32_t u32ChannelMask)
|
{
|
uint32_t u32Mask = 0, i;
|
for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
|
if(u32ChannelMask & (1 << i)) {
|
u32Mask |= (PWM_CTL_CNTEN0_Msk << (i * 4));
|
}
|
}
|
pwm->CTL &= ~u32Mask;
|
}
|
|
/**
|
* @brief This function enable output inverter of specified channel(s)
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel
|
* Bit 0 represents channel 0, bit 1 represents channel 1...
|
* @return None
|
* \hideinitializer
|
*/
|
void PWM_EnableOutputInverter(PWM_T *pwm, uint32_t u32ChannelMask)
|
{
|
//pwm->CTL &= ~(PWM_CTL_PINV0_Msk|PWM_CTL_PINV1_Msk|PWM_CTL_PINV2_Msk|PWM_CTL_PINV3_Msk|PWM_CTL_PINV4_Msk|PWM_CTL_PINV5_Msk|PWM_CTL_PINV6_Msk|PWM_CTL_PINV7_Msk);
|
uint32_t tmpCtlReg = pwm->CTL;
|
for(size_t i = 0; i < 8; i++)
|
{
|
if((u32ChannelMask) & (1 << i))
|
tmpCtlReg |= (1 << (PWM_CTL_PINV0_Pos + (i * 4)));
|
}
|
pwm->CTL = tmpCtlReg;
|
}
|
|
/**
|
* @brief This function disable output inverter of specified channel(s)
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelMask Combination of channels to disable. Each bit corresponds to a channel
|
* Bit 0 represents channel 0, bit 1 represents channel 1...
|
* @return None
|
* \hideinitializer
|
*/
|
void PWM_DisableOutputInverter(PWM_T *pwm, uint32_t u32ChannelMask)
|
{
|
uint32_t tmpCtlReg = pwm->CTL;
|
for(size_t i = 0; i < 8; i++)
|
{
|
if((u32ChannelMask) & (1 << i))
|
tmpCtlReg &= ~(1 << (PWM_CTL_PINV0_Pos + (i * 4)));
|
}
|
pwm->CTL = tmpCtlReg;
|
}
|
|
#if 0
|
/**
|
* @brief This function enable selected channel to trigger ADC
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @param[in] u32Condition The condition to trigger ADC. Combination of following conditions:
|
* - \ref PWM_TRIGGER_ADC_CNTR_IS_0
|
* - \ref PWM_TRIGGER_ADC_CNTR_IS_CMR_D
|
* - \ref PWM_TRIGGER_ADC_CNTR_IS_CNR
|
* - \ref PWM_TRIGGER_ADC_CNTR_IS_CMR_U
|
* @return None
|
*/
|
void PWM_EnableADCTrigger (PWM_T *pwm, uint32_t u32ChannelNum, uint32_t u32Condition)
|
{
|
if(u32ChannelNum < 4) {
|
pwm->ADCTCTL0 = (pwm->ADCTCTL0 & ~((PWM_TRIGGER_ADC_CNTR_IS_0 |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_D |
|
PWM_TRIGGER_ADC_CNTR_IS_CNR |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_U ) << (8 * u32ChannelNum))) | (u32Condition << (8 * u32ChannelNum));
|
} else {
|
pwm->ADCTCTL1 = (pwm->ADCTCTL1 & ~((PWM_TRIGGER_ADC_CNTR_IS_0 |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_D |
|
PWM_TRIGGER_ADC_CNTR_IS_CNR |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_U ) << (8 * (u32ChannelNum - 4)))) | (u32Condition << (8 * (u32ChannelNum - 4)));
|
}
|
}
|
|
/**
|
* @brief This function disable selected channel to trigger ADC
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @return None
|
*/
|
void PWM_DisableADCTrigger (PWM_T *pwm, uint32_t u32ChannelNum)
|
{
|
if(u32ChannelNum < 4) {
|
pwm->ADCTCTL0 = (pwm->ADCTCTL0 & ~((PWM_TRIGGER_ADC_CNTR_IS_0 |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_D |
|
PWM_TRIGGER_ADC_CNTR_IS_CNR |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_U ) << (8 * u32ChannelNum)));
|
} else {
|
pwm->ADCTCTL1 = (pwm->ADCTCTL1 & ~((PWM_TRIGGER_ADC_CNTR_IS_0 |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_D |
|
PWM_TRIGGER_ADC_CNTR_IS_CNR |
|
PWM_TRIGGER_ADC_CNTR_IS_CMR_U ) << (8 * (u32ChannelNum - 4))));
|
}
|
}
|
|
/**
|
* @brief This function clear selected channel trigger ADC flag
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @param[in] u32Condition PWM triggered ADC flag to be cleared.
|
* @return None
|
*/
|
void PWM_ClearADCTriggerFlag (PWM_T *pwm, uint32_t u32ChannelNum, uint32_t u32Condition)
|
{
|
if(u32ChannelNum < 4) {
|
pwm->ADCTSTS0 |= (u32Condition << (8 * u32ChannelNum));
|
} else {
|
pwm->ADCTSTS1 |= (u32Condition << (8 * (u32ChannelNum - 4)));
|
}
|
}
|
|
/**
|
* @brief This function get selected channel trigger ADC flag
|
* @param[in] pwm The base address of PWM module
|
* @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
|
* @param[in] u32Condition PWM triggered ADC flag to be selected.
|
* @return Get status of the selected channel trigger ADC
|
*/
|
uint32_t PWM_GetADCTriggerFlag (PWM_T *pwm, uint32_t u32ChannelNum, uint32_t u32Condition)
|
{
|
if(u32ChannelNum < 4) {
|
return(pwm->ADCTSTS0 & (u32Condition << (8 * u32ChannelNum)) ? 1 : 0);
|
} else {
|
return(pwm->ADCTSTS1 & (u32Condition << (8 * (u32ChannelNum - 4))) ? 1 : 0);
|
}
|
}
|
#endif
|
|
|
/*@}*/ /* end of group Panchip_PWM_EXPORTED_FUNCTIONS */
|
|
/*@}*/ /* end of group Panchip_PWM_Driver */
|
|
/*@}*/ /* end of group Panchip_Device_Driver */
|
|
/*** (C) COPYRIGHT 2016 Panchip Technology Corp. ***/
|