[其他] 【HC32L196PCTA测评】3.按键+定时器+PWM测试

[复制链接]
 楼主| yuyy1989 发表于 2023-8-6 14:22 | 显示全部楼层 |阅读模式
<
#申请原创# @21小跑堂  
3.按键+定时器+PWM测试
3.1按键轮询方式
开发板上有一个可编程按键,接在了PA07上,低电平触发,外接了上拉电阻
QQ截图20230806140631.png
按键轮询是间隔很短时间不断查询GPIO状态,从而得知是否有按键动作,按键在按下或释放的过程中,可能会伴随抖动,在抖动过程中,会产生多次高低电平,导致被识别为多次按键操作,因此为了避免误判需要进行去抖处理,软件上可以用延时来去抖
用轮询的方式写个按键程序,用按键控制LED的亮灭,代码实现

  1. #include "gpio.h"
  2. void led_init(void)
  3. {
  4.     stc_gpio_cfg_t stcGpioCfg;
  5.     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
  6.     stcGpioCfg.enDir = GpioDirOut;
  7.     stcGpioCfg.enPu = GpioPuDisable;
  8.     stcGpioCfg.enPd = GpioPdEnable;
  9.     Gpio_ClrIO(STK_LED_PORT, STK_LED_PIN);
  10.     Gpio_Init(STK_LED_PORT, STK_LED_PIN, &stcGpioCfg);
  11. }

  12. void key_init(void)
  13. {
  14.     stc_gpio_cfg_t stcGpioCfg;
  15.     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
  16.     stcGpioCfg.enDir = GpioDirIn;
  17.     stcGpioCfg.enPu = GpioPuDisable;
  18.     stcGpioCfg.enPd = GpioPdDisable;
  19.     Gpio_Init(STK_USER_PORT, STK_USER_PIN, &stcGpioCfg);
  20. }

  21. int32_t main(void)
  22. {
  23.     led_init();
  24.     key_init();

  25.     while(1)
  26.     {
  27.         if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
  28.         {
  29.             delay1ms(10);
  30.             while(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE);
  31.             Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
  32.         }
  33.     }
  34. }
编译烧录查看效果
WeChat_20230805203822 00_00_00-00_00_30.gif
3.2按键中断方式
与上面的轮询方法不同,这种方式并不用一直查询GPIO的状态,按键状态改变时会产生中断,程序在检测到中断后再判断按键状态
查看interrupts_hc32l19x.c这个文件,这是官方提供的一个统一中断入口文件
QQ截图20230803141000.png
可以看到在GPIOA的中断函数里调用了PortA_IRQHandler这个方法,这个方法面用了__WEAK声明
QQ截图20230803140917.png
按照interrupts_hc32l19x.h的注释说明,可以在需要的地方重新定义这个方法
QQ截图20230805210250.png
使用这种方式要把这里填上
QQ截图20230805212408.png
代码示例

  1. #include "gpio.h"
  2. void led_init(void)
  3. {
  4.     stc_gpio_cfg_t stcGpioCfg;
  5.     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
  6.     stcGpioCfg.enDir = GpioDirOut;
  7.     stcGpioCfg.enPu = GpioPuDisable;
  8.     stcGpioCfg.enPd = GpioPdEnable;
  9.     Gpio_ClrIO(STK_LED_PORT, STK_LED_PIN);
  10.     Gpio_Init(STK_LED_PORT, STK_LED_PIN, &stcGpioCfg);
  11. }

  12. void key_init(void)
  13. {
  14.     stc_gpio_cfg_t stcGpioCfg;
  15.     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
  16.     stcGpioCfg.enDir = GpioDirIn;
  17.     stcGpioCfg.enDrv = GpioDrvL;
  18.     stcGpioCfg.enPu = GpioPuDisable;
  19.     stcGpioCfg.enPd = GpioPdDisable;
  20.     Gpio_Init(STK_USER_PORT, STK_USER_PIN, &stcGpioCfg);
  21.     Gpio_EnableIrq(STK_USER_PORT, STK_USER_PIN, GpioIrqFalling);
  22.     EnableNvic(PORTA_IRQn, IrqLevel3, TRUE);
  23. }

  24. void PortA_IRQHandler(void)
  25. {
  26.     if(TRUE == Gpio_GetIrqStatus(STK_USER_PORT, STK_USER_PIN))
  27.     {            
  28.         if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
  29.         {
  30.             Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
  31.         }
  32.         Gpio_ClearIrq(STK_USER_PORT, STK_USER_PIN);   
  33.     }
  34. }

  35. int32_t main(void)
  36. {
  37.     led_init();
  38.     key_init();

  39.     while(1)
  40.     {
  41.         ;
  42.     }
  43. }
运行效果
WeChat_20230805212906 00_00_00-00_00_30.gif
3.3定时器测试
HC32L196有4个通用定时器、2个低功耗定时器和3个高级定时器,还有一个PCA(可编程计数器阵列)也可作为定时器使用
选用Timer0做一个每1ms触发一次的定时器,用它来控制LED闪烁,按键调节闪烁速度
首先设置一下系统时钟,不做任何更改时用的是内部时4M时钟
QQ截图20230804172848.png
板子上有个32M的外部晶振,接下来使用它作为时钟源

  1. void xth_init()
  2. {
  3.     //当使用的时钟源HCLK大于24M:设置FLASH 读等待周期为1 cycle
  4.     Flash_WaitCycle(FlashWaitCycle1);   
  5.     Sysctrl_SetXTHFreq(SysctrlXthFreq24_32MHz);
  6.     Sysctrl_XTHDriverCfg(SysctrlXtalDriver3);
  7.     Sysctrl_SetXTHStableTime(SysctrlXthStableCycle16384);
  8.     Sysctrl_ClkSourceEnable(SysctrlClkXTH, TRUE);
  9.     delay1ms(10);
  10.     Sysctrl_SysClkSwitch(SysctrlClkXTH);
  11. }
设置timer0参数,使用16位重载计数器模式,计数器会从设定的值开始计数到0xFFFF后产生中断
QQ截图20230805221609.png
  1. void timer0_init()
  2. {
  3.     stc_bt_mode0_cfg_t     stcBtBaseCfg;
  4.     DDL_ZERO_STRUCT(stcBtBaseCfg);
  5.     Sysctrl_SetPeripheralGate(SysctrlPeripheralBaseTim, TRUE); //Base Timer外设时钟使能
  6.    
  7.     stcBtBaseCfg.enWorkMode = BtWorkMode0;                  //定时器模式
  8.     stcBtBaseCfg.enCT       = BtTimer;                      //定时器功能,计数时钟为内部PCLK
  9.     stcBtBaseCfg.enPRS      = BtPCLKDiv32;                  //PCLK/32 32M/32=1M
  10.     stcBtBaseCfg.enCntMode  = Bt16bitArrMode;               //自动重载16位计数器/定时器
  11.     stcBtBaseCfg.bEnTog     = FALSE;
  12.     stcBtBaseCfg.bEnGate    = FALSE;
  13.     Bt_Mode0_Init(TIM0, &stcBtBaseCfg);                     //TIM0 的模式0功能初始化
  14.    
  15.     Bt_M0_ARRSet(TIM0, 0x10000-1000);                       //设置重载值
  16.     Bt_M0_Cnt16Set(TIM0, 0x10000-1000);                     //设置计数初值
  17.    
  18.     Bt_ClearIntFlag(TIM0,BtUevIrq);                         //清中断标志   
  19.     Bt_Mode0_EnableIrq(TIM0);                               //使能TIM0中断
  20.     EnableNvic(TIM0_IRQn, IrqLevel3, TRUE);                 //TIM0中断使能
  21.     Bt_M0_Run(TIM0);                                        //TIM0 运行。
  22. }
中断处理
  1. uint16_t timecount = 0;
  2. uint16_t timecount_max = 500;
  3. void Tim0_IRQHandler(void)
  4. {
  5.     //Timer0 模式0 溢出中断
  6.     if(TRUE == Bt_GetIntFlag(TIM0, BtUevIrq))
  7.     {
  8.         if(timecount < timecount_max)
  9.             timecount += 1;
  10.         else
  11.         {
  12.             timecount = 0;
  13.             Gpio_WriteOutputIO(STK_LED_PORT, STK_LED_PIN,!Gpio_ReadOutputIO(STK_LED_PORT, STK_LED_PIN));
  14.         }
  15.         Bt_ClearIntFlag(TIM0,BtUevIrq); //中断标志清零
  16.     }
  17. }
  18. void PortA_IRQHandler(void)
  19. {
  20.     if(TRUE == Gpio_GetIrqStatus(STK_USER_PORT, STK_USER_PIN))
  21.     {            
  22.         if(Gpio_GetInputIO(STK_USER_PORT, STK_USER_PIN) == FALSE)
  23.         {
  24.             if(timecount_max == 500)
  25.                 timecount_max = 200;
  26.             else if(timecount_max == 200)
  27.                 timecount_max = 1000;
  28.             else
  29.                 timecount_max = 500;
  30.         }
  31.         Gpio_ClearIrq(STK_USER_PORT, STK_USER_PIN);   
  32.     }

  33. }
main函数
  1. int32_t main(void)
  2. {
  3.     xth_init();
  4.     //时钟分频设置
  5.     Sysctrl_SetHCLKDiv(SysctrlHclkDiv1);
  6.     Sysctrl_SetPCLKDiv(SysctrlPclkDiv1);
  7.     led_init();
  8.     key_init();
  9.     timer0_init();
  10.     while(1)
  11.     {
  12.         ;
  13.     }
  14. }
运行效果
WeChat_20230805222106 00_00_00-00_00_30.gif
3.4PWM输出测试
4个通用定时器3个高级定时器和PCA都有输出PWM的能力,然而开发板上LED所接的PD14这个IO并不能输出PWM
QQ截图20230802224133.png
因此外接2个LED来测试PWM,选用PA00和PA01作为Timer1的两个PWM通道输出
QQ截图20230806135513.png
初始化为PWM输出

  1. void pwm_led_init()
  2. {
  3.     stc_gpio_cfg_t stcLEDPort;
  4.     DDL_ZERO_STRUCT(stcLEDPort);
  5.    
  6.     Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
  7.     stcLEDPort.enDir  = GpioDirOut;
  8.     Gpio_Init(GpioPortA, GpioPin0, &stcLEDPort);
  9.     Gpio_SetAfMode(GpioPortA,GpioPin0,GpioAf5);            //PA00设置为TIM1_CHA
  10.    
  11.     Gpio_Init(GpioPortA, GpioPin1, &stcLEDPort);
  12.     Gpio_SetAfMode(GpioPortA,GpioPin1,GpioAf5);            //PA01设置为TIM1_CHB
  13. }
接下来看看手册上对于定时器PWM模式的描述
QQ截图20230806135725.png QQ截图20230806135800.png
这里选用PWM2上计数,锯齿波单点比较,时钟32分频计数周期1000

  1. void pwm_timer1_init()
  2. {
  3.     stc_bt_mode23_cfg_t        stcBtBaseCfg;
  4.     stc_bt_m23_compare_cfg_t   stcBtPortCmpCfg;

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

  13.     while(1)
  14.     {
  15.         if(pwm<2000)
  16.             pwm += 1;
  17.         else
  18.             pwm = 0;
  19.         if(pwm < 1000)
  20.         {
  21.             Bt_M23_CCR_Set(TIM1, BtCCR0A, pwm);
  22.             Bt_M23_CCR_Set(TIM1, BtCCR0B, 1000-pwm);
  23.         }
  24.         else
  25.         {
  26.             Bt_M23_CCR_Set(TIM1, BtCCR0A, 2000 - pwm);
  27.             Bt_M23_CCR_Set(TIM1, BtCCR0B, pwm - 1000);
  28.         }
  29.         delay1ms(2);
  30.     }
  31. }
运行效果
WeChat_20230806140108 00_00_00-00_00_30.gif
tpgf 发表于 2024-6-3 13:07 | 显示全部楼层
我觉得可以通过检测按键的频率来调整pwm的频率
heimaojingzhang 发表于 2024-6-3 13:53 | 显示全部楼层
这种形式的pwm 感觉还不如使用普通定时器控制io的功能更节省资源
wakayi 发表于 2024-6-3 14:33 | 显示全部楼层
是否可以考虑能动态调整输出的频率以及占空比呢
renzheshengui 发表于 2024-6-3 21:20 | 显示全部楼层
这三种都是可以独立实现该功能的
paotangsan 发表于 2024-6-3 21:51 | 显示全部楼层
在轮询方式中 如何做好按键的防抖呢
keaibukelian 发表于 2024-6-3 22:22 | 显示全部楼层
按键和定时器是不是有点画蛇添足了呢
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:同飞软件研发工程师
简介:制冷系统单片机软件开发,使用PID控制温度

161

主题

815

帖子

10

粉丝
快速回复 在线客服 返回列表 返回顶部
认证:同飞软件研发工程师
简介:制冷系统单片机软件开发,使用PID控制温度

161

主题

815

帖子

10

粉丝
快速回复 在线客服 返回列表 返回顶部