说说M451例程之PWM
/**************************************************************************//**
* @file main.c
* @version V3.00
* $Revision: 2 $
* $Date: 15/09/02 10:03a $
* @brief Demonstrate how to set GPIO pin mode and use pin data input/output control.
* @note
* Copyright (C) 2013~2015 Nuvoton Technology Corp. All rights reserved.
*
******************************************************************************/
#include "stdio.h"
#include "M451Series.h"
#include "NuEdu-Basic01.h"
#define PLL_CLOCK 72000000
void SYS_Init(void)
{
/*---------------------------------------------------------------------------------------------------------*/
/* Init System Clock */
/*---------------------------------------------------------------------------------------------------------*/
/* Enable HIRC clock (Internal RC 22.1184MHz) */
CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);
/* Wait for HIRC clock ready */
CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);
/* Select HCLK clock source as HIRC and and HCLK clock divider as 1 */
CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));
/* Enable HXT clock (external XTAL 12MHz) */
CLK_EnableXtalRC(CLK_PWRCTL_HXTEN_Msk);
/* Wait for HXT clock ready */
CLK_WaitClockReady(CLK_STATUS_HXTSTB_Msk);
/* Set core clock as PLL_CLOCK from PLL */
CLK_SetCoreClock(PLL_CLOCK);
/* Enable UART module clock */
CLK_EnableModuleClock(UART0_MODULE);
/* Select UART module clock source as HXT and UART module clock divider as 1 */
CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UARTSEL_HXT, CLK_CLKDIV0_UART(1));
/*---------------------------------------------------------------------------------------------------------*/
/* Init I/O Multi-function */
/*---------------------------------------------------------------------------------------------------------*/
/* Set PD multi-function pins for UART0 RXD(PD.6) and TXD(PD.1) */
SYS->GPD_MFPL &= ~(SYS_GPD_MFPL_PD6MFP_Msk | SYS_GPD_MFPL_PD1MFP_Msk);
SYS->GPD_MFPL |= (SYS_GPD_MFPL_PD6MFP_UART0_RXD | SYS_GPD_MFPL_PD1MFP_UART0_TXD);
}
void UART0_Init()
{
/*---------------------------------------------------------------------------------------------------------*/
/* Init UART */
/*---------------------------------------------------------------------------------------------------------*/
/* Reset UART module */
SYS_ResetModule(UART0_RST);
/* Configure UART0 and set UART0 baud rate */
UART_Open(UART0, 115200);
}
/*---------------------------------------------------------------------------------------------------------*/
/* Main Function */
/*---------------------------------------------------------------------------------------------------------*/
int32_t main(void)
{
uint32_t temp,temp1 = 0;
/* Unlock protected registers */
SYS_UnlockReg();
/* Init System, peripheral clock and multi-function I/O */
SYS_Init();
/* Lock protected registers */
SYS_LockReg();
/* Init UART0 for printf */
UART0_Init();
printf("\nNuEdu-SDK-M451 PWM-DAC\n");
Initial_PWM_LED();
Initial_PWM_DAC();
Initial_LED();
Open_ADC_Knob();
Write_PWMDAC(1,temp1);
while(1)
{
//Get Volume Knob Data
temp = Get_ADC_PWMDAC(); //Volume Range: 0 ~ 4095
Write_LED_Bar((temp * (8 + 1) / 4096));
Write_PWMDAC(1,temp1++);
if(temp1>100)
temp1 = 0;
CLK_SysTickDelay(10000);
}
}
以上是PWM的例程,今天主要讲讲PWM的发生
M451提供了两路PWM发生器。每路PWM支持6通道PWM输出或输入捕捉。有一个12位的预分频器把时钟源分频后输入给16位的计数器,另外还有一个16位的比较器。PWM计数器支持向上,向下,上下计数方式。PWM用比较器和计数器的比较来产生事件,这些事件用来产生PWM脉冲,中断,EADC/DAC转换触发信号。
PWM发生器支持两种标准PWM输出模式:独立模式和互补模式,它们的架构不同。标准输出模式又有两种输出功能:组功能和同步功能。组功能可以在独立模式和互补模式下使能。同步功能只有在互补模式下才可以被使能。互补模式,有两个比较器产生各种带12位死区时间的PWM脉宽,另外还有一个自由触发比较器来产生给EADC的触发信号。PWM输出控制单元,它支持极性输出,独立管脚屏蔽和刹车功能。
PWM也支持输入捕捉功能,当输入通道有向上跳变、向下跳变、或者两者都有的跳变时,锁存PWM计数器的值到相应的寄存器中。捕捉功能也支持通过PDMA把捕捉到的数据搬移到内存。
PWM功能特性 6.9.2.1
支持时钟频率最高达144MHz
支持两个PWM模块,每个模块提供6个输出通道
支持独立模式的PWM输出/输入捕捉
支持3组互补通道的互补模式
12位解析度的死区插入
相控制的同步功能
每个周期两个比较值
支持12位从1到4096的预分频
支持16位解析度的PWM计数器
向上,向下和上下计数操作类型
支持one-shot或自动装载计数器工作模式
支持组功能
支持同步功能
每个PWM管脚支持屏蔽功能和三态使能
支持刹车功能
刹车源来自管脚、模拟比较器和系统安全事件(时钟故障、SRAM奇偶校验错误、欠压监测和CPU锁住)
刹车源管脚噪声滤波器
通过边缘检测刹车源来控制刹车状态直到刹车中断清除
刹车条件解除后电平检测刹车源自动恢复功能
支持下列事件中断:
PWM计数器值为 0、周期值或比较值
发生刹车条件
支持下列事件触发EADC/DAC :
PWM计数器值为0、周期值或比较值
PWM 计数器匹配自由触发比较器比较值(仅EADC)
捕捉功能特性 6.9.2.2
支持12个16位解析度的输入捕捉通道
支持上升/下降沿捕捉条件
支持输入上升/下降沿 捕捉中断
支持计数器重载选项的上升/下降沿 捕捉
支持PWM 的所有通道PDMA数据搬移功能
PWM系统时钟可以设为等于或两倍的HCLK频率, 图 6.9-2, 寄存器设置细节请参考表 6.9-1.
每个PWM发生器有三个输入时钟源,每个时钟源可以从系统时钟或者4组定时器触发PWM输出,图
6.9-3,PWM_CLK0设置ECLKSRC0 (PWM_CLKSRC[2:0]),PWM_CLK2设置ECLKSRC2
(PWM_CLKSRC[10:8]),PWM_CLK4设置ECLKSRC4 (PWM_CLKSRC[18:16])
图 6.9-4 和图 6.9-5表示PWM独立模式和互补模式的架构。不管独立模式还是比较模式,一对通道
组(PWM_CH0 和PWM_CH1, PWM_CH2 和 PWM_CH3, PWM_CH4 和 PWM_CH5)计数器来自相
同的时钟源和预分频。当计数器的值等于0,PERIOD(PWM_PERIODn[15:0])或比较器值,将产生
事件。这些事件通过相应的发生器来产生PWM脉冲、中断信号、EADC/DAC的转换触发信号。输
出控制是用来改变PWM脉冲输出状态的。输出控制的刹车功能也能产生中断事件。在互补模式,
同步功能有效,偶数通道用奇数通道比较器产生事件,自由触发比较器事件只用于产生触发EADC
信号。
#include <stdio.h> #include "M451Series.h" #include "NuEdu-Basic01_PWMDAC.h" void Write_PWMDAC(unsigned char Enable, unsigned char ch0_dut) { /* set PWMB channel 0 output configuration */ PWM_ConfigOutputChannel(PWM1, 4, 1000, ch0_dut); // Start PWM COUNT PWM_Start(PWM1, 1 << 4); if(Enable == 0) /* Enable PWM Output path for PWMB channel 0 */ PWM_DisableOutput(PWM1, 1 << 4); else /* Diable PWM Output path for PWMB channel 0 */ PWM_EnableOutput(PWM1, 1 << 4); } void Initial_PWM_DAC(void) { GPIO_SetMode(PC, BIT13, GPIO_MODE_INPUT); //avoid to pwm dac out SYS->GPC_MFPH &= ~SYS_GPC_MFPH_PC13MFP_Msk ; SYS->GPC_MFPH |= SYS_GPC_MFPH_PC13MFP_PWM1_CH4; /* Enable PWM module clock */ CLK_EnableModuleClock(PWM1_MODULE); /* Select PWM module clock source */ CLK_SetModuleClock(PWM1_MODULE, CLK_CLKSEL2_PWM1SEL_PCLK1, 0); /* Reset PWM1 channel 0~5 */ SYS_ResetModule(PWM1_RST); }
/**************************************************************************//** * @file NuEdu-NuEdu-Basic01_RGBLED.c * @version V1.00 * $Revision: 5 $ * $Date: 15/09/02 10:02a $ * @brief NuEdu-Basic01_RGBLED driver source file for NuEdu-SDK-M451 * * @note * Copyright (C) 2014~2015 Nuvoton Technology Corp. All rights reserved. *****************************************************************************/ #include <stdio.h> #include "M451Series.h" #include "NuEdu-Basic01_RGBLED.h" /** @addtogroup M451_Library M451 Library @{ */ /** @addtogroup NuEdu-SDK-M451_Basic01 M451_Basic01 Library @{ */ /** @addtogroup M451_Basic01_FUNCTIONS RGB LED Functions @{ */ /** * @brief Set multi-function pins for PWM1 channel 0,1,2 * @return None */ void Initial_PWM_LED(void) { /* Set PC9~PC11 multi-function pins for PWM1 Channel0~2 */ SYS->GPC_MFPH &= ~(SYS_GPC_MFPH_PC9MFP_Msk | SYS_GPC_MFPH_PC10MFP_Msk | SYS_GPC_MFPH_PC11MFP_Msk); SYS->GPC_MFPH |= SYS_GPC_MFPH_PC9MFP_PWM1_CH0 | SYS_GPC_MFPH_PC10MFP_PWM1_CH1 | SYS_GPC_MFPH_PC11MFP_PWM1_CH2; /* Enable PWM module clock */ CLK_EnableModuleClock(PWM1_MODULE); /* Select PWM module clock source */ CLK_SetModuleClock(PWM1_MODULE, CLK_CLKSEL2_PWM1SEL_PCLK1, 0); /* Reset PWM1 channel 0~5 */ SYS_ResetModule(PWM1_RST); } /** * @brief Set PWM clock enable and HCLK as PWM clock source, * * @param[in] ch Channel numbers that will be enabled. * * @param[in] ch0_fre Channel 0 frequency. * * @param[in] ch0_dut Channel 0 duty. * * @param[in] ch1_fre Channel 1 frequency. * * @param[in] ch1_dut Channel 1 duty. * * @param[in] ch2_fre Channel 2 frequency. * * @param[in] ch2_dut Channel 2 duty. * * @return None */ void PWM_LED(unsigned char ch, unsigned int ch0_fre, unsigned int ch0_dut, unsigned int ch1_fre, unsigned int ch1_dut, unsigned int ch2_fre, unsigned int ch2_dut) { /* set PWMA channel 1 output configuration */ PWM_ConfigOutputChannel(PWM1, 0, ch0_fre, ch0_dut); PWM_ConfigOutputChannel(PWM1, 1, ch1_fre, ch1_dut); PWM_ConfigOutputChannel(PWM1, 2, ch2_fre, ch2_dut); /* Enable PWM Output path for PWMA channel 0 */ PWM_EnableOutput(PWM1, ch); // Start PWM_Start(PWM1, ch); } /*@}*/ /* end of group M451_Basic01_FUNCTIONS */ /*@}*/ /* end of group NuEdu-SDK-M451_Basic01 */ /*@}*/ /* end of group M451_Library */ /*** (C) COPYRIGHT 2013~2015 Nuvoton Technology Corp. **/
uint32_t PWM_ConfigCaptureChannel(PWM_T *pwm, uint32_t u32ChannelNum, uint32_t u32UnitTimeNsec, uint32_t u32CaptureEdge) { uint32_t u32Src; uint32_t u32PWMClockSrc; uint32_t u32NearestUnitTimeNsec; uint16_t u16Prescale = 1, u16CNR = 0xFFFF; if(pwm == PWM0) u32Src = CLK->CLKSEL2 & CLK_CLKSEL2_PWM0SEL_Msk; else//(pwm == PWM1) u32Src = CLK->CLKSEL2 & CLK_CLKSEL2_PWM1SEL_Msk; if(u32Src == 0) { //clock source is from PLL clock u32PWMClockSrc = CLK_GetPLLClockFreq(); } else { //clock source is from PCLK SystemCoreClockUpdate(); u32PWMClockSrc = SystemCoreClock; } u32PWMClockSrc /= 1000; for(u16Prescale = 1; u16Prescale <= 0x1000; u16Prescale++) { u32NearestUnitTimeNsec = (1000000 * u16Prescale) / u32PWMClockSrc; if(u32NearestUnitTimeNsec < u32UnitTimeNsec) { if(u16Prescale == 0x1000) //limit to the maximum unit time(nano second) break; if(!((1000000 * (u16Prescale + 1) > (u32NearestUnitTimeNsec * u32PWMClockSrc)))) break; continue; } break; } // convert to real register value // every two channels share a prescaler PWM_SET_PRESCALER(pwm, u32ChannelNum, --u16Prescale); // set PWM to down count type(edge aligned) (pwm)->CTL1 = ((pwm)->CTL1 & ~(PWM_CTL1_CNTTYPE0_Msk << (2 * u32ChannelNum))) | (1UL << (2 * u32ChannelNum)); // set PWM to auto-reload mode (pwm)->CTL1 &= ~(PWM_CTL1_CNTMODE0_Msk << u32ChannelNum); PWM_SET_CNR(pwm, u32ChannelNum, u16CNR); return (u32NearestUnitTimeNsec); } /** * @brief This function Configure PWM generator and get the nearest frequency in edge aligned auto-reload mode * @param[in] pwm The pointer of the specified PWM module * - PWM0 : PWM Group 0 * - PWM1 : PWM Group 1 * @param[in] u32ChannelNum PWM channel number. Valid values are between 0~5 * @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%... * @return Nearest frequency clock in nano second * @note Since every two channels, (0 & 1), (2 & 3), 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) { uint32_t u32Src; uint32_t u32PWMClockSrc; uint32_t i; uint16_t u16Prescale = 1, u16CNR = 0xFFFF; if(pwm == PWM0) u32Src = CLK->CLKSEL2 & CLK_CLKSEL2_PWM0SEL_Msk; else//(pwm == PWM1) u32Src = CLK->CLKSEL2 & CLK_CLKSEL2_PWM1SEL_Msk; if(u32Src == 0) { //clock source is from PLL clock u32PWMClockSrc = CLK_GetPLLClockFreq(); } else { //clock source is from PCLK SystemCoreClockUpdate(); u32PWMClockSrc = SystemCoreClock; } for(u16Prescale = 1; u16Prescale < 0xFFF; u16Prescale++)//prescale could be 0~0xFFF { i = (u32PWMClockSrc / u32Frequency) / u16Prescale; // If target value is larger than CNR, need to use a larger prescaler if(i > (0x10000)) continue; u16CNR = i; break; } // Store return value here 'cos we're gonna change u16Prescale & u16CNR to the real value to fill into register i = u32PWMClockSrc / (u16Prescale * u16CNR); // convert to real register value // every two channels share a prescaler PWM_SET_PRESCALER(pwm, u32ChannelNum, --u16Prescale); // set PWM to down count type(edge aligned) (pwm)->CTL1 = ((pwm)->CTL1 & ~(PWM_CTL1_CNTTYPE0_Msk << (2 * u32ChannelNum))) | (1UL << (2 * u32ChannelNum)); // set PWM to auto-reload mode (pwm)->CTL1 &= ~(PWM_CTL1_CNTMODE0_Msk << u32ChannelNum); PWM_SET_CNR(pwm, u32ChannelNum, --u16CNR); if(u32DutyCycle) { PWM_SET_CMR(pwm, u32ChannelNum, u32DutyCycle * (u16CNR + 1) / 100 - 1); (pwm)->WGCTL0 &= ~((PWM_WGCTL0_PRDPCTL0_Msk | PWM_WGCTL0_ZPCTL0_Msk) << (u32ChannelNum * 2)); (pwm)->WGCTL0 |= (PWM_OUTPUT_LOW << (u32ChannelNum * 2 + PWM_WGCTL0_PRDPCTL0_Pos)); (pwm)->WGCTL1 &= ~((PWM_WGCTL1_CMPDCTL0_Msk | PWM_WGCTL1_CMPUCTL0_Msk) << (u32ChannelNum * 2)); (pwm)->WGCTL1 |= (PWM_OUTPUT_HIGH << (u32ChannelNum * 2 + PWM_WGCTL1_CMPDCTL0_Pos)); } else { PWM_SET_CMR(pwm, u32ChannelNum, 0); (pwm)->WGCTL0 &= ~((PWM_WGCTL0_PRDPCTL0_Msk | PWM_WGCTL0_ZPCTL0_Msk) << (u32ChannelNum * 2)); (pwm)->WGCTL0 |= (PWM_OUTPUT_LOW << (u32ChannelNum * 2 + PWM_WGCTL0_ZPCTL0_Pos)); (pwm)->WGCTL1 &= ~((PWM_WGCTL1_CMPDCTL0_Msk | PWM_WGCTL1_CMPUCTL0_Msk) << (u32ChannelNum * 2)); (pwm)->WGCTL1 |= (PWM_OUTPUT_HIGH << (u32ChannelNum * 2 + PWM_WGCTL1_CMPDCTL0_Pos)); } return(i); } /** * @brief Start PWM module * @param[in] pwm The pointer of the specified PWM module * - PWM0 : PWM Group 0 * - PWM1 : PWM Group 1 * @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 * @details This function is used to start PWM module. */ void PWM_Start(PWM_T *pwm, uint32_t u32ChannelMask) { (pwm)->CNTEN |= u32ChannelMask; }
PWM原理
随着电子技术的发展,出现了多种PWM技术,其中包括:相电压控制PWM、脉宽PWM法、随机PWM、SPWM法、线电压控制PWM等,而在镍氢电池智能充电器中采用的脉宽PWM法,它是把每一脉冲宽度均相等的脉冲列作为PWM波形,通过改变脉冲列的周期可以调频,改变脉冲的宽度或占空比可以调压,采用适当控制方法即可使电压与频率协调变化。可以通过调整PWM的周期、PWM的占空比而达到控制充电电流的目的。
模拟信号的值可以连续变化,其时间和幅度的分辨率都没有限制。9V电池就是一种模拟器件,因为它的输出电压并不精确地等于9V,而是随时间发生变化,并可取任何实数值。与此类似,从电池吸收的电流也不限定在一组可能的取值范围之内。模拟信号与数字信号的区别在于后者的取值通常只能属于预先确定的可能取值集合之内,例如在{0V, 5V}这一集合中取值。
模拟电压和电流可直接用来进行控制,如对汽车收音机的音量进行控制。在简单的模拟收音机中,音量旋钮被连接到一个可变电阻。拧动旋钮时,电阻值变大或变小;流经这个电阻的电流也随之增加或减少,从而改变了驱动扬声器的电流值,使音量相应变大或变小。与收音机一样,模拟电路的输出与输入成线性比例。
尽管模拟控制看起来可能直观而简单,但它并不总是非常经济或可行的。其中一点就是,模拟电路容易随时间漂移,因而难以调节。能够解决这个问题的精密模拟电路可能非常庞大、笨重(如老式的家庭立体声设备)和昂贵。模拟电路还有可能严重发热,其功耗相对于工作元件两端电压与电流的乘积成正比。模拟电路还可能对噪声很敏感,任何扰动或噪声都肯定会改变电流值的大小。
通过以数字方式控制模拟电路,可以大幅度降低系统的成本和功耗。此外,许多微控制器和DSP已经在芯片上包含了PWM控制器,这使数字控制的实现变得更加容易了。
互补的意思就是当pwm1是高电平时,pwm2是低电平,如果pwm1是低电平时pwm2是高电平,总之是pwm1和pwm2不会同事变高或变低,总是不一样的。
一般这样的pwm输出用于控制由两个开关管组成的在电源和地之间的桥,两个同时接通的话会导致桥臂短路电源和地引起烧毁,互补的波形可避免同时导通。
开关管可不是瞬间就能开启或关闭的。
特别是推挽电路中,没有死区的话,有可能造成直通短路。
即使是boost 电路,为了限制最大占空比,也得设计个死区时间啊,
具体设置多少要看所用开关管的开启和关闭的延迟时间,
简单的说,比如你有5V电源,要控制一台灯的亮度,有一个传统办法,就是串联一个可调电阻,改变电阻,灯的亮度就会改变。
还有一个办法,就是PWM调节。不用串联电阻,而是串联一个开关。假设在1秒内,有0.5秒的时间开关是打开的,0.5秒关闭,那么灯就亮0.5秒,灭0.5秒。这样持续下去,灯就会闪烁。如果把频率调高一点,比如是1毫秒,0.5毫秒开,0.5毫秒灭,那么灯的闪烁频率就很高。我们知道,闪烁频率超过一定值,人眼就会感觉不到。所以,这时你看不到灯的闪烁,只看到灯的亮度只有原来的一半。
同理,如果1毫秒内,0.1毫秒开,0.9毫秒灭,那么,灯的亮度就只有原来的10分之一。
这就是PWM的基本原理。专业的说法百度一下就很多,我说了也不专业。但是道理就是这么简单,具体PWM还分几种,总的来说,都是保持一定的电压或电流不变,但改变一定周期内的导通和关断时间。这样等效于保持导通,但改变电压或电流大小。
这样的PWM控制方式,在数字控制电路上应用很方便。因为让电脑去控制一个可调电阻是比较困难的,而且可调电阻还有模拟电路固有的不稳定问题。