打印
[其他]

【HC32L196PCTA测评】3.按键+定时器+PWM测试

[复制链接]
1127|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创# @21小跑堂  
3.按键+定时器+PWM测试
3.1按键轮询方式
开发板上有一个可编程按键,接在了PA07上,低电平触发,外接了上拉电阻

按键轮询是间隔很短时间不断查询GPIO状态,从而得知是否有按键动作,按键在按下或释放的过程中,可能会伴随抖动,在抖动过程中,会产生多次高低电平,导致被识别为多次按键操作,因此为了避免误判需要进行去抖处理,软件上可以用延时来去抖
用轮询的方式写个按键程序,用按键控制LED的亮灭,代码实现

#include "gpio.h"
void led_init(void)
{
    stc_gpio_cfg_t stcGpioCfg;
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    stcGpioCfg.enDir = GpioDirOut;
    stcGpioCfg.enPu = GpioPuDisable;
    stcGpioCfg.enPd = GpioPdEnable;
    Gpio_ClrIO(STK_LED_PORT, STK_LED_PIN);
    Gpio_Init(STK_LED_PORT, STK_LED_PIN, &stcGpioCfg);
}

void key_init(void)
{
    stc_gpio_cfg_t stcGpioCfg;
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    stcGpioCfg.enDir = GpioDirIn;
    stcGpioCfg.enPu = GpioPuDisable;
    stcGpioCfg.enPd = GpioPdDisable;
    Gpio_Init(STK_USER_PORT, STK_USER_PIN, &stcGpioCfg);
}

int32_t main(void)
{
    led_init();
    key_init();

    while(1)
    {
        if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
        {
            delay1ms(10);
            while(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE);
            Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
        }
    }
}
编译烧录查看效果

3.2按键中断方式
与上面的轮询方法不同,这种方式并不用一直查询GPIO的状态,按键状态改变时会产生中断,程序在检测到中断后再判断按键状态
查看interrupts_hc32l19x.c这个文件,这是官方提供的一个统一中断入口文件

可以看到在GPIOA的中断函数里调用了PortA_IRQHandler这个方法,这个方法面用了__WEAK声明

按照interrupts_hc32l19x.h的注释说明,可以在需要的地方重新定义这个方法

使用这种方式要把这里填上

代码示例

#include "gpio.h"
void led_init(void)
{
    stc_gpio_cfg_t stcGpioCfg;
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    stcGpioCfg.enDir = GpioDirOut;
    stcGpioCfg.enPu = GpioPuDisable;
    stcGpioCfg.enPd = GpioPdEnable;
    Gpio_ClrIO(STK_LED_PORT, STK_LED_PIN);
    Gpio_Init(STK_LED_PORT, STK_LED_PIN, &stcGpioCfg);
}

void key_init(void)
{
    stc_gpio_cfg_t stcGpioCfg;
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    stcGpioCfg.enDir = GpioDirIn;
    stcGpioCfg.enDrv = GpioDrvL;
    stcGpioCfg.enPu = GpioPuDisable;
    stcGpioCfg.enPd = GpioPdDisable;
    Gpio_Init(STK_USER_PORT, STK_USER_PIN, &stcGpioCfg);
    Gpio_EnableIrq(STK_USER_PORT, STK_USER_PIN, GpioIrqFalling);
    EnableNvic(PORTA_IRQn, IrqLevel3, TRUE);
}

void PortA_IRQHandler(void)
{
    if(TRUE == Gpio_GetIrqStatus(STK_USER_PORT, STK_USER_PIN))
    {            
        if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
        {
            Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
        }
        Gpio_ClearIrq(STK_USER_PORT, STK_USER_PIN);   
    }
}

int32_t main(void)
{
    led_init();
    key_init();

    while(1)
    {
        ;
    }
}
运行效果

3.3定时器测试
HC32L196有4个通用定时器、2个低功耗定时器和3个高级定时器,还有一个PCA(可编程计数器阵列)也可作为定时器使用
选用Timer0做一个每1ms触发一次的定时器,用它来控制LED闪烁,按键调节闪烁速度
首先设置一下系统时钟,不做任何更改时用的是内部时4M时钟

板子上有个32M的外部晶振,接下来使用它作为时钟源

void xth_init()
{
    //当使用的时钟源HCLK大于24M:设置FLASH 读等待周期为1 cycle
    Flash_WaitCycle(FlashWaitCycle1);   
    Sysctrl_SetXTHFreq(SysctrlXthFreq24_32MHz);
    Sysctrl_XTHDriverCfg(SysctrlXtalDriver3);
    Sysctrl_SetXTHStableTime(SysctrlXthStableCycle16384);
    Sysctrl_ClkSourceEnable(SysctrlClkXTH, TRUE);
    delay1ms(10);
    Sysctrl_SysClkSwitch(SysctrlClkXTH);
}
设置timer0参数,使用16位重载计数器模式,计数器会从设定的值开始计数到0xFFFF后产生中断

void timer0_init()
{
    stc_bt_mode0_cfg_t     stcBtBaseCfg;
    DDL_ZERO_STRUCT(stcBtBaseCfg);
    Sysctrl_SetPeripheralGate(SysctrlPeripheralBaseTim, TRUE); //Base Timer外设时钟使能
   
    stcBtBaseCfg.enWorkMode = BtWorkMode0;                  //定时器模式
    stcBtBaseCfg.enCT       = BtTimer;                      //定时器功能,计数时钟为内部PCLK
    stcBtBaseCfg.enPRS      = BtPCLKDiv32;                  //PCLK/32 32M/32=1M
    stcBtBaseCfg.enCntMode  = Bt16bitArrMode;               //自动重载16位计数器/定时器
    stcBtBaseCfg.bEnTog     = FALSE;
    stcBtBaseCfg.bEnGate    = FALSE;
    Bt_Mode0_Init(TIM0, &stcBtBaseCfg);                     //TIM0 的模式0功能初始化
   
    Bt_M0_ARRSet(TIM0, 0x10000-1000);                       //设置重载值
    Bt_M0_Cnt16Set(TIM0, 0x10000-1000);                     //设置计数初值
   
    Bt_ClearIntFlag(TIM0,BtUevIrq);                         //清中断标志   
    Bt_Mode0_EnableIrq(TIM0);                               //使能TIM0中断
    EnableNvic(TIM0_IRQn, IrqLevel3, TRUE);                 //TIM0中断使能
    Bt_M0_Run(TIM0);                                        //TIM0 运行。
}
中断处理
uint16_t timecount = 0;
uint16_t timecount_max = 500;
void Tim0_IRQHandler(void)
{
    //Timer0 模式0 溢出中断
    if(TRUE == Bt_GetIntFlag(TIM0, BtUevIrq))
    {
        if(timecount < timecount_max)
            timecount += 1;
        else
        {
            timecount = 0;
            Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
        }
        Bt_ClearIntFlag(TIM0,BtUevIrq); //中断标志清零
    }
}
void PortA_IRQHandler(void)
{
    if(TRUE == Gpio_GetIrqStatus(STK_USER_PORT, STK_USER_PIN))
    {            
        if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
        {
            if(timecount_max == 500)
                timecount_max = 200;
            else if(timecount_max == 200)
                timecount_max = 1000;
            else
                timecount_max = 500;
        }
        Gpio_ClearIrq(STK_USER_PORT, STK_USER_PIN);   
    }

}
main函数
int32_t main(void)
{
    xth_init();
    //时钟分频设置
    Sysctrl_SetHCLKDiv(SysctrlHclkDiv1);
    Sysctrl_SetPCLKDiv(SysctrlPclkDiv1);
    led_init();
    key_init();
    timer0_init();
    while(1)
    {
        ;
    }
}
运行效果

3.4PWM输出测试
4个通用定时器3个高级定时器和PCA都有输出PWM的能力,然而开发板上LED所接的PD14这个IO并不能输出PWM

因此外接2个LED来测试PWM,选用PA00和PA01作为Timer1的两个PWM通道输出

初始化为PWM输出

void pwm_led_init()
{
    stc_gpio_cfg_t stcLEDPort;
    DDL_ZERO_STRUCT(stcLEDPort);
   
    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    stcLEDPort.enDir  = GpioDirOut;
    Gpio_Init(GpioPortA, GpioPin0, &stcLEDPort);
    Gpio_SetAfMode(GpioPortA,GpioPin0,GpioAf5);            //PA00设置为TIM1_CHA
   
    Gpio_Init(GpioPortA, GpioPin1, &stcLEDPort);
    Gpio_SetAfMode(GpioPortA,GpioPin1,GpioAf5);            //PA01设置为TIM1_CHB
}
接下来看看手册上对于定时器PWM模式的描述

这里选用PWM2上计数,锯齿波单点比较,时钟32分频计数周期1000

void pwm_timer1_init()
{
    stc_bt_mode23_cfg_t        stcBtBaseCfg;
    stc_bt_m23_compare_cfg_t   stcBtPortCmpCfg;

    DDL_ZERO_STRUCT(stcBtBaseCfg);
    DDL_ZERO_STRUCT(stcBtPortCmpCfg);
    Sysctrl_SetPeripheralGate(SysctrlPeripheralBaseTim, TRUE);  
    stcBtBaseCfg.enWorkMode    = BtWorkMode2;              //锯齿波模式
    stcBtBaseCfg.enCT          = BtTimer;                  //定时器功能,计数时钟为内部PCLK
    stcBtBaseCfg.enPRS         = BtPCLKDiv32;              //PCLK
    stcBtBaseCfg.enCntDir      = BtCntUp;                  //向上计数,在三角波模式时只读
    stcBtBaseCfg.enPWMTypeSel  = BtIndependentPWM;         //独立输出PWM
    stcBtBaseCfg.enPWM2sSel    = BtSinglePointCmp;         //单点比较功能
    stcBtBaseCfg.bOneShot      = FALSE;                    //循环计数
    stcBtBaseCfg.bURSSel       = FALSE;                    //上下溢更新
    Bt_Mode23_Init(TIM1, &stcBtBaseCfg);                   //TIM0 的模式23功能初始化
   
    Bt_M23_ARRSet(TIM1, 1000-1, TRUE);                  //设置重载值,并使能缓存
    Bt_M23_CCR_Set(TIM1, BtCCR0A, 0);         //设置比较值A
    Bt_M23_CCR_Set(TIM1, BtCCR0B, 0);         //设置比较值B
   
    stcBtPortCmpCfg.enCH0ACmpCtrl   = BtPWMMode2;          //OCREFA输出控制OCMA:PWM模式2
    stcBtPortCmpCfg.enCH0APolarity  = BtPortPositive;      //正常输出
    stcBtPortCmpCfg.bCh0ACmpBufEn   = TRUE;                //A通道缓存控制
    stcBtPortCmpCfg.enCh0ACmpIntSel = BtCmpIntNone;        //A通道比较控制:无
   
    stcBtPortCmpCfg.enCH0BCmpCtrl   = BtPWMMode2;          //OCREFB输出控制OCMB:PWM模式2
    stcBtPortCmpCfg.enCH0BPolarity  = BtPortPositive;      //正常输出
    stcBtPortCmpCfg.bCH0BCmpBufEn   = TRUE;                //B通道缓存控制使能
    stcBtPortCmpCfg.enCH0BCmpIntSel = BtCmpIntNone;        //B通道比较控制:无
   
    Bt_M23_PortOutput_Cfg(TIM1, &stcBtPortCmpCfg);         //比较输出端口配置
                                                            //事件更新周期设置,0表示锯齿波每个周期更新一次,每+1代表延迟1个周期
    Bt_M23_SetValidPeriod(TIM1,0);             //间隔周期设置
    Bt_M23_Cnt16Set(TIM1, 0);                    //设置计数初值
    Bt_M23_EnPWM_Output(TIM1, TRUE, FALSE);    //TIM0 端口输出使能
    Bt_M23_Run(TIM1);
}
两个LED交替呼吸
int32_t main(void)
{
    uint16_t pwm = 0;
    xth_init();
    //时钟分频设置
    Sysctrl_SetHCLKDiv(SysctrlHclkDiv1);
    Sysctrl_SetPCLKDiv(SysctrlPclkDiv1);
    led_init();
    key_init();
    timer0_init();
    pwm_led_init();
    pwm_timer1_init();

    while(1)
    {
        if(pwm<2000)
            pwm += 1;
        else
            pwm = 0;
        if(pwm < 1000)
        {
            Bt_M23_CCR_Set(TIM1, BtCCR0A, pwm);
            Bt_M23_CCR_Set(TIM1, BtCCR0B, 1000-pwm);
        }
        else
        {
            Bt_M23_CCR_Set(TIM1, BtCCR0A, 2000 - pwm);
            Bt_M23_CCR_Set(TIM1, BtCCR0B, pwm - 1000);
        }
        delay1ms(2);
    }
}
运行效果

使用特权

评论回复
沙发
tpgf| | 2024-6-3 13:07 | 只看该作者
我觉得可以通过检测按键的频率来调整pwm的频率

使用特权

评论回复
板凳
heimaojingzhang| | 2024-6-3 13:53 | 只看该作者
这种形式的pwm 感觉还不如使用普通定时器控制io的功能更节省资源

使用特权

评论回复
地板
wakayi| | 2024-6-3 14:33 | 只看该作者
是否可以考虑能动态调整输出的频率以及占空比呢

使用特权

评论回复
5
renzheshengui| | 2024-6-3 21:20 | 只看该作者
这三种都是可以独立实现该功能的

使用特权

评论回复
6
paotangsan| | 2024-6-3 21:51 | 只看该作者
在轮询方式中 如何做好按键的防抖呢

使用特权

评论回复
7
keaibukelian| | 2024-6-3 22:22 | 只看该作者
按键和定时器是不是有点画蛇添足了呢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

147

主题

700

帖子

6

粉丝