本帖最后由 yuyy1989 于 2023-8-30 10:17 编辑
#申请原创# @21小跑堂
5.按键检测与定时器
5.1按键检测
可以用2种方式来检测按键是否被按下,一种是轮询,即不断的查询按键IO的状态,另一种是中断,按键电平改变时触发中断,机械按键按下时会出现抖动,可以用延时来去抖
实验-用轮询方式检测按键,按键按下修改LED亮灭状态
用两个变量分别存储两个按键的状态,当检测到按键IO为低电平时,通过状态变量判断当前按键是否已经是按键状态,如果是则跳过,如果不是延时10ms再检测按键状态,如果仍然为低电平将状态变量改为按下状态,同时修改对应的LED状态,检测到按键IO为高电平时重置按键的状态变量
int32_t main(void)
{
uint8_t key1down = 0,key2down = 0;
RCC_HSE_16M_PLL64M_init();
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
led_init();
key_init();
while(1)
{
if(GPIO_ReadPin(CW_GPIOA,GPIO_PIN_4) == RESET)
{
if(key1down == 0)
{
yuyy_delay_ms(10);
if(GPIO_ReadPin(CW_GPIOA,GPIO_PIN_4) == RESET)
{
key1down = 1;
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
}
}
}
else
{
key1down = 0;
}
if(GPIO_ReadPin(CW_GPIOA,GPIO_PIN_5) == RESET)
{
if(key2down == 0)
{
yuyy_delay_ms(10);
if(GPIO_ReadPin(CW_GPIOA,GPIO_PIN_5) == RESET)
{
key2down = 1;
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_3);
}
}
}
else
{
key2down = 0;
}
}
}
效果
实验-用IO中断检测按键,按键按下修改LED亮灭状态
使用中断时可以使用的中断数字滤波器可对引脚上的输入信号进行数字滤波来实现去抖,两个按键都已上拉处理,这里通过下降沿中断检测按键是否按下,接下来按键1开启中断滤波,按键2不开启
void key_init()
{
GPIO_InitTypeDef gpiodef;
__RCC_GPIOA_CLK_ENABLE();
gpiodef.Pins = GPIO_PIN_4|GPIO_PIN_5;
gpiodef.IT = GPIO_IT_FALLING;
gpiodef.Mode = GPIO_MODE_INPUT;
GPIO_Init(CW_GPIOA,&gpiodef);
GPIO_ConfigFilter(CW_GPIOA,GPIO_PIN_4,GPIO_FLTCLK_HCLK2);
GPIOA_INTFLAG_CLR(GPIO_PIN_4| GPIO_PIN_5);
NVIC_EnableIRQ(GPIOA_IRQn);
}
void GPIOA_IRQHandler(void)
{
if (CW_GPIOA->ISR_f.PIN4)
{
GPIOA_INTFLAG_CLR(GPIO_PIN_4);
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
}
if (CW_GPIOA->ISR_f.PIN5)
{
GPIOA_INTFLAG_CLR(GPIO_PIN_5);
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_3);
}
}
int32_t main(void)
{
uint8_t key1down = 0,key2down = 0;
RCC_HSE_16M_PLL64M_init();
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
led_init();
key_init();
while(1)
{
}
}
效果
还可以同时开启多个中断,例如同时开启下降沿中断和上升沿中断,下面这段代码在按键的中断中翻转LED状态,由于同时开启了上升沿中断和下降沿中断按下和松开时都会翻转状态
void key_init()
{
GPIO_InitTypeDef gpiodef;
__RCC_GPIOA_CLK_ENABLE();
gpiodef.Pins = GPIO_PIN_4;
gpiodef.IT = GPIO_IT_FALLING|GPIO_IT_RISING;
gpiodef.Mode = GPIO_MODE_INPUT;
GPIO_Init(CW_GPIOA,&gpiodef);
GPIO_ConfigFilter(CW_GPIOA,GPIO_PIN_4,GPIO_FLTCLK_HCLK8);
GPIOA_INTFLAG_CLR(GPIO_PIN_4);
NVIC_EnableIRQ(GPIOA_IRQn);
}
void GPIOA_IRQHandler(void)
{
if (CW_GPIOA->ISR_f.PIN4)
{
GPIOA_INTFLAG_CLR(GPIO_PIN_4);
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
}
}
int32_t main(void)
{
RCC_HSE_16M_PLL64M_init();
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
led_init();
key_init();
while(1)
{
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_3);
yuyy_delay_ms(250);
}
}
效果
5.2定时器与计数器
CW32L083集成了1个高级定时器、4个通用定时器、3个基本定时器和1个低功耗定时器,都是16位,除基本定时器只能向上计数外,其它的都可以上下计数
定时器与计数器并没有什么不同,本质上都是计数器,只不过作为定时器使用时通过固定周期的时钟源进行计数,作为计数器使用时是通过外部事件触发计数
实验-使用定时器模式实现一个1ms的定时器
选用通用定时器1,让1个LED的闪烁周期为1秒,另外一个为0.5秒,系统时钟为64M,定时器预分频选择64,将计数重载值设为999开启溢出中断,在中断中处理LED的亮灭
void gtimer1_init()
{
GTIM_InitTypeDef GTIM_InitStruct = {0};
__RCC_GTIM1_CLK_ENABLE();
GTIM_InitStruct.Mode = GTIM_MODE_TIME;
GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV64;
GTIM_InitStruct.ReloadValue = 1000-1;
GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStruct);
GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE);
NVIC_EnableIRQ(GTIM1_IRQn);
GTIM_Cmd(CW_GTIM1, ENABLE);
}
uint16_t tim1count = 0;
void GTIM1_IRQHandler(void)
{
if (GTIM_GetITStatus(CW_GTIM1, GTIM_IT_OV))
{
GTIM_ClearITPendingBit(CW_GTIM1, GTIM_IT_OV);
if(tim1count % 500 == 0)
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
if(tim1count % 250 == 0)
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_3);
if(tim1count < 999)
tim1count += 1;
else
tim1count = 0;
}
}
编译的时候又发现了这个优先级的警告
和通用定时器的DMA判断有关,顺便改一下吧
效果
作为计数器时与其它定时器联合实现更长周期的定时效果,定时器还可以直接输出翻转电平,当发生溢出中断时翻转对应IO的电平,接下来结合gtimer1和gtimer2实现1秒的定时器
这里有个坑,计数器模式时分频值范围与定时器时不一样
原例程gtim_counter_itr里设置的计数器分频值是GTIM_PRESCALER_DIV1
这个宏定义的值是0,在计数器模式下是错误的,所以按照这个例程写完后并没有预期输出效果,原例程编译运行也没有readme里写的效果
改正后代码,gtimer1部分的代码不动,初始化gtimer2为计数器模式,PB12和PB13作为gtimer2的翻转输出
void gtimer2_toggleout_init()
{
GPIO_InitTypeDef gpiodef;
__RCC_GPIOB_CLK_ENABLE();
gpiodef.Pins = GPIO_PIN_12|GPIO_PIN_13;
gpiodef.IT = GPIO_IT_NONE;
gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(CW_GPIOB,&gpiodef);
PB12_AFx_GTIM2TOGN();
PB13_AFx_GTIM2TOGP();
}
void gtimer2_counter_init()
{
GTIM_InitTypeDef GTIM_InitStruct = {0};
__RCC_GTIM2_CLK_ENABLE();
GTIM_InitStruct.Mode = GTIM_MODE_COUNTER;
GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;;
GTIM_InitStruct.Prescaler = 1;
GTIM_InitStruct.ReloadValue = 1000-1;
GTIM_InitStruct.ToggleOutState = ENABLE;
GTIM_TimeBaseInit(CW_GTIM2, >IM_InitStruct);
GTIM2_ITRConfig(ITR_SOURCE_GTIM1);
GTIM_Cmd(CW_GTIM2, ENABLE);
}
效果
5.3PWM输出
利用定时器的输出比较功能可以实现PWM输出功能,通用定时器的输出有6种比较模式
对应到库文件中的宏定义
实验-使用PWM输出实现呼吸灯
板载的2个LED对应gtimer2的ch1和ch2
void led_pwm_init()
{
GPIO_InitTypeDef gpiodef;
__RCC_GPIOC_CLK_ENABLE();
gpiodef.Pins = GPIO_PIN_2|GPIO_PIN_3;
gpiodef.IT = GPIO_IT_NONE;
gpiodef.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init(CW_GPIOC,&gpiodef);
PC02_AFx_GTIM2CH2();
PC03_AFx_GTIM2CH1();
}
void gtimer2_pwm_init()
{
GTIM_InitTypeDef GTIM_InitStruct = {0};
__RCC_GTIM2_CLK_ENABLE();
GTIM_InitStruct.Mode = GTIM_MODE_TIME;
GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV64;
GTIM_InitStruct.ReloadValue = 1000 - 1;
GTIM_TimeBaseInit(CW_GTIM2, >IM_InitStruct);
GTIM_OCInit(CW_GTIM2, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);
GTIM_OCInit(CW_GTIM2, GTIM_CHANNEL2, GTIM_OC_OUTPUT_PWM_HIGH);
GTIM_SetCompare1(CW_GTIM2, 0);
GTIM_SetCompare2(CW_GTIM2, 0);
GTIM_Cmd(CW_GTIM2, ENABLE);
}
int32_t main(void)
{
uint16_t pwm = 0;
RCC_HSE_16M_PLL64M_init();
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
led_pwm_init();
gtimer2_pwm_init();
while(1)
{
if(pwm < 1000)
{
GTIM_SetCompare1(CW_GTIM2,pwm);
GTIM_SetCompare2(CW_GTIM2,1000 - pwm);
}
else
{
GTIM_SetCompare1(CW_GTIM2,2000-pwm);
GTIM_SetCompare2(CW_GTIM2,pwm-1000);
}
yuyy_delay_ms(1);
if(pwm < 1999)
pwm += 1;
else
pwm = 0;
}
}
效果
5.4输入捕获
利用输入捕获模式可以捕获外部输入电平的变化,可以用来计算脉冲宽度
实验-利用输入捕获检测按键,按键直接控制LED1,两次按下间隔如果不超过0.5秒切换LED2的状态
void key2_tim2ic_init()
{
GPIO_InitTypeDef gpiodef;
__RCC_GPIOA_CLK_ENABLE();
gpiodef.Pins = GPIO_PIN_5;
gpiodef.IT = GPIO_IT_NONE;
gpiodef.Mode = GPIO_MODE_INPUT;
GPIO_Init(CW_GPIOA,&gpiodef);
PA05_AFx_GTIM2CH1();
}
void gtimer2_inputcap_init()
{
GTIM_InitTypeDef GTIM_InitStruct = {0};
GTIM_ICInitTypeDef GTIM_ICInitStruct = {0};
__RCC_GTIM2_CLK_ENABLE();
GTIM_InitStruct.Mode = GTIM_MODE_TIME;
GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE;
GTIM_InitStruct.Prescaler = 64000-1;;
GTIM_InitStruct.ReloadValue = 0xFFFF;
GTIM_InitStruct.ToggleOutState = DISABLE;
GTIM_TimeBaseInit(CW_GTIM2, >IM_InitStruct);
GTIM_ICInitStruct.CHx = GTIM_CHANNEL1;
GTIM_ICInitStruct.ICFilter = GTIM_CHx_FILTER_PCLK_N2;
GTIM_ICInitStruct.ICInvert = GTIM_CHx_INVERT_OFF;
GTIM_ICInitStruct.ICPolarity = GTIM_ICPolarity_Falling;
GTIM_ICInit(CW_GTIM2, >IM_ICInitStruct);
GTIM_ITConfig(CW_GTIM2, GTIM_IT_CC1|GTIM_IT_OV, ENABLE);
GTIM_Cmd(CW_GTIM2, ENABLE);
NVIC_EnableIRQ(GTIM2_IRQn);
}
uint16_t lastkeycount = 0;
void GTIM2_IRQHandler(void)
{
uint16_t ccrtemp = 0;
if (GTIM_GetITStatus(CW_GTIM2, GTIM_IT_OV))
{
GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_OV);
}
if(GTIM_GetITStatus(CW_GTIM2, GTIM_IT_CC1))
{
ccrtemp = CW_GTIM2->CCR1;
GTIM_ClearITPendingBit(CW_GTIM2, GTIM_IT_CC1);
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_3);
if(ccrtemp > lastkeycount)
{
if(ccrtemp - lastkeycount < 500)
{
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
}
lastkeycount = ccrtemp;
}
else
{
if(0x10000-lastkeycount+ccrtemp < 500)
{
GPIO_TogglePin(CW_GPIOC,GPIO_PIN_2);
}
lastkeycount = ccrtemp;
}
}
}
效果
|