• STM32实战总结:HAL之触摸按键


    之前学过机械按键,可参考:

    51单片机外设篇:按键_路溪非溪的博客-CSDN博客

    外设篇:按键和CPU的中断系统_路溪非溪的博客-CSDN博客

    中断的意义:高效处理紧急程序,不会一直占用CPU资源。

    中断分为硬中断和软中断,其分类依据是实现机制,而不是触发机制,比如CPU硬中断,它是由CPU这个硬件实现的中断机制,但它的触发可以通过外部硬件,也可以通过软件的 INT 指令。

    类似地,软中断是由软件实现的中断,是纯粹由软件实现的一种类似中断的机制,实际上是模仿硬件,在内存中存储着一组软中断的标志位,然后由内核的一个守护线程不断轮询这些标志位,如果有哪个标志位有效,则再去执行这个软中断对应的中断处理程序。

    原理图

    触摸按键也是一种按键,我们先看其电路连接:

    四个触摸按键。

    TTP224N-BSB是一个触摸按键芯片,四个输出,四个输入,具体查看芯片数据手册。

    触摸按键的输出端连接到了PE0~PE3。PE0~PE3是EXTI0~EXTI3。

    触摸按键的原理跟触摸屏有点像,手指按上去的时候,会引起电容改变,从而引起电路改变,芯片通过检测这种细微变化来识别是否按下了按键。

    在以上TTP224N-BSB芯片中,TOG和OD引脚是浮空的,即默认值。其中,AHLB、VDD和LPMB引脚被连接到了电源上;SM、VSS和MOT0接地。

    这些引脚什么意思呢?

    以下附TTP224N-BSB芯片的关键特性:

    ******************************************************************************************************

    TTP224是一款使用电容式感应原理设计的触摸IC,其稳定的感应方式可以应用到各种不同电子类产品,面板介质可以是完全绝源的材料,专为取代传统的机械结构开关或普通按键而设 计。提供4个触摸输入端口及4个直接输出端口。

    工作电压 2.4V~5.5V

    可以由外部Option选择是否启用内部稳压电路功能

    工作电流@VDD=3V无负载时:

            低功耗模式下典型值2.5uA

            快速模式下典型值9uA

    @VDD=3V时,在快速模式下KEY最快响应时间为100mS,低功耗模式下为200mS.

    各KEY灵敏度可以由外部电容进行调节(0~50pF).

    提供LPMB端口选择快速模式或低功耗模式.

    提供直接输出模式,触发模式,开漏输出, CMOS高电平有效或低电平有效输出, 经由 TOG/AHLB/OD端口选择.

    提供两个无二极管保护的输出端口TPQ0D,TPQ2D仅限于低电平有效.

    提供MOT1, MOT0端口选择最大输出时间:120秒/64秒/16秒/无穷大

    上电后约有0.5秒的系统稳定时间,在此期间内不要触摸Touch PAD,且触摸功能无效

    有自动校准功能,当无按键被触摸时,系统重新校准周期约为4.0秒

    ******************************************************************************************************

    以上电路中,出去电源和地,还有另外6个引脚:

    TOG——0——直接模式,直接模式还是触发模式有何区别?直接模式就是按下是一种状态,不按是一种状态,固定的,比如按下是高电平,不按就是低电平;触发模式就是按一下就变一下,比如一开始是0,按一下就变成了1,再按一下就变成了0……如此循环。

    OD——1——CMOS 输出

    AHLB——1——低电平有效,这里的高电平有效还是低电平有效不好理解,指的是,当按下时,输出什么电平,低电平有效,那么当按下按键就输出低电平,高电平有效,那么当按下按键就输出高电平;

    LPMB——1——快速模式

    SM——0——单键模式

    MOT0——0——最长输出时间16秒

    ******************************************************************************************************

    更多细节参考:按键模块TTP224N-BSB - 百度文库

    要想写程序,得弄明白按键按下时,会有什么变化,才能触发外部中断。

    根据上面的各引脚电平状态可知,按下时是低电平,那么不按时就是高电平,所以就是按下按键时,输出就从高电平变成了低电平,可以通过低电平/下降沿来触发中断。

    这个芯片说是输出时低电平有效,我还想着没法输出高电平。

    后来看到输出口是推挽输出,我还在想,又没接上拉电阻,哪来的高电平,难道是要把对应的GPIO口设置成上拉?

    后来过了一晚,早上起床突然想到,推挽不就是既能输出高电平又能输出低电平吗?

    我竟然还在想哪来的高电平,真是一时没反应过来。

    低电平有效,应该是指,默认是高电平的,按下按键时,输出的就是低电平。 

    外部中断

    一直搞不太明白,EXTI不就是外部中断吗?可明明除了系统异常,其他都属于外部中断呀?可是在所有的外部中断里,为什么有几个单独的EXTI中断呢?这个外部,到底是指什么?是否可以理解成,除了系统异常,其他都是外设中断,但是外设中断又分为内部外设和外部外设,而EXTI就是外部外设,需要通过IO口来触发。换个角度来说,其实,EXTI属于外部中断,相对于内部而言的,内部的比如RCC、定时器、DMA等,这些都是芯片设计层面的中断。而如果想通过外部的信号来触发中断,这时就要用到EXTI。 

    STM32小中大容量的19个外部中断:

    注,以下这段内容来自正点原子:

    第1讲 什么是中断?_哔哩哔哩_bilibili

    外部中断,即触发信号从外部来,既然从外部来,那么肯定就要经过GPIO,然后经过AFIO,之后到达EXTI,即外部中断控制器,之后进入NVIC,即中断总控制器,最终通知CPU进行中断处理。

     整个流程如下:

    EXTI模块:

    这里的输入线就是EXTI线。

    脉冲发生器通常输出到具体的外设。

    先弄清楚一个概念:

    中断和事件的区别:

    STM32之中断与事件---中断与事件的区别_无痕幽雨的博客-CSDN博客_stm32中断和事件

    一句话总结:

    具体查阅参考手册。此处仅贴上外部中断映像图:

    每个外部中断只能选择一个引脚作为中断线。比如选择了PE0作为外部中断0的中断线,那么其他端口PA/PB/PC/PD/PF/PG就不能配置EXT0了。

    记住:

    中断0对应引脚0,中断1对应引脚1,中断2对应引脚2,……

    MX初始化

    先配置继电器(这里是因为板上有四个触摸按键,但只有3个LED灯,所以就借用这里的D6来作为一个LED使用,也能感受下直接驱动和使用中断驱动的区别):

    配置接继电器的PG13引脚:

    配置PE0~PE3为外部中断(下降沿触发):

    注意,因为默认是高电平,所以必须选择下降沿触发,否则无法实现长按功能。如果根据高电平来检测长按,因为默认就是高电平,所以无法判断。

    初始化对应中断:

    关于MX中断的配置,有必要说明下:

    stm32的优先级配置

    STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作“亚优先级”或“副优先级”或“从优先级”,每个中断源都需要被指定这两种优先级。

    • 高抢占优先级的中断可以打断低抢占优先级的中断(抢占式优先级可以打断)
    • 相同抢占优先级,高响应优先级无法打断低响应优先级的中断(响应优先级无法打断)
    • 相同抢占优先级,两个中断同时触发时,优先执行高响应优先级的中断
    • 若所有优先级都相同,谁先触发执行对应中断
    • 抢占优先级和响应优先级都相同时,自然优先级高的先执行,自然优先级就是向量表中的优先级
    • 优先级数字越低代表优先级越高


    抢占式优先级和响应优先级通过中断优先级组进行分配
    中断优先级组在stm32中一般可分为0-4共5组,分组配置在寄存器SCB->AIRCR中:

    中断优先级组

    由此可知,STM32的响应优先级可以是0位。

    • 组0就是4位都用来设置成响应优先级,2^4=16位都是响应优先级
    • 组1分为2^1两个抢占优先级,在这两个抢占优先级里面还分别有2^3八个响应优先级
    • 组2分为2^2四个抢占优先级,在这四个抢占优先级里面还分别有2^2四个响应优先级
    • 组3分为2^3八个抢占优先级,在这八个抢占优先级里面还分别有2^1两个响应优先级
    • 组4分为2^4十六个都是抢占优先级


    在MX中,就是这样的原理。组数字和优先级的数字是不同的,组数字是一种分组,而优先级数字是有优先级意义的,一定要注意。

     

    接下来,要实现一个功能。 

    点击一下按键,就能亮灯;

    长按按键,灯就会闪烁;

    这里的思路是这样的:按下按键,就能通过下降沿触发外部中断,中断服务函数中,点亮相应的灯,并输出相应的串口数据;LED的驱动已经写过了;要增加一个继电器的灯的驱动。

    点击可以通过下降沿触发中断;那么长按怎么解决呢?通过低电平触发吗?但是已经设置成了下降沿触发,怎么还能用低电平触发呢?

    下降沿能触发中断,但是中断里可以通过检测多长时间的低电平后再执行相关代码。

    搭建框架

    先根据我自己的思路去搭建框架。

    初始化后生成代码,之前的LED灯和串口的代码不变。

    生成代码后先编译一遍看有没有问题。根据初始化可知,3个LED灯是亮的,继电器的灯是灭的,按键能触发中断,但是此时不会产生什么影响。

    GPIO的初始化代码有增加:

    1. void MX_GPIO_Init(void)
    2. {
    3. GPIO_InitTypeDef GPIO_InitStruct = {0};
    4. /* GPIO Ports Clock Enable */
    5. __HAL_RCC_GPIOE_CLK_ENABLE();
    6. __HAL_RCC_GPIOC_CLK_ENABLE();
    7. __HAL_RCC_GPIOA_CLK_ENABLE();
    8. __HAL_RCC_GPIOG_CLK_ENABLE();
    9. /*Configure GPIO pin Output Level */
    10. HAL_GPIO_WritePin(GPIOE, LED1_Pin|LED2_Pin|LED3_Pin, GPIO_PIN_SET);
    11. /*Configure GPIO pin Output Level */
    12. HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_RESET);
    13. /*Configure GPIO pins : PEPin PEPin PEPin PEPin */
    14. GPIO_InitStruct.Pin = KEY2_Pin|KEY3_Pin|KEY0_Pin|KEY1_Pin;
    15. GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    16. GPIO_InitStruct.Pull = GPIO_NOPULL;
    17. HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    18. /*Configure GPIO pins : PEPin PEPin PEPin */
    19. GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin|LED3_Pin;
    20. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    21. GPIO_InitStruct.Pull = GPIO_NOPULL;
    22. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    23. HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    24. /*Configure GPIO pin : PtPin */
    25. GPIO_InitStruct.Pin = Relay_Pin;
    26. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    27. GPIO_InitStruct.Pull = GPIO_NOPULL;
    28. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    29. HAL_GPIO_Init(Relay_GPIO_Port, &GPIO_InitStruct);
    30. /* EXTI interrupt init*/
    31. HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
    32. HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    33. HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 0);
    34. HAL_NVIC_EnableIRQ(EXTI1_IRQn);
    35. HAL_NVIC_SetPriority(EXTI2_IRQn, 1, 0);
    36. HAL_NVIC_EnableIRQ(EXTI2_IRQn);
    37. HAL_NVIC_SetPriority(EXTI3_IRQn, 1, 0);
    38. HAL_NVIC_EnableIRQ(EXTI3_IRQn);
    39. }

    跟上面的配置是一致的。每次配置MX后类似的内容都是一样的,后续不再赘述。

    开始搭建框架

    先写继电器的代码,建立两个文件,relay.c和relay.h

    relay.h

    1. #ifndef _RELAY_H_
    2. #define _RELAY_H_
    3. //确定要实现的led功能
    4. typedef struct
    5. {
    6. //打开继电器
    7. void (*relayOpen)(void);
    8. //关闭继电器
    9. void (*relayClose)(void);
    10. //转换继电器状态
    11. void (*relaySwitch)(void);
    12. } relay_t;
    13. //将结构体声明出去
    14. extern relay_t relayObj;
    15. #endif

    relay.c

    1. #include "myapplication.h"
    2. static void RelayOpen(void);
    3. static void RelayClose(void);
    4. static void RelaySwitch(void);
    5. relay_t relayObj =
    6. {
    7. RelayOpen,
    8. RelayClose,
    9. RelaySwitch
    10. };
    11. static void RelayOpen(void)
    12. {
    13. HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_SET);
    14. }
    15. static void RelayClose(void)
    16. {
    17. HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_RESET);
    18. }
    19. static void RelaySwitch(void)
    20. {
    21. HAL_GPIO_TogglePin(Relay_GPIO_Port, Relay_Pin);
    22. }

    这里只是操作GPIO口,和LED的操作是一模一样的。

    外部中断代码

    因为配置了外部中断,所以在中断文件stm32f1xx_it.c中,会有相应的中断服务函数:

    1. /**
    2. * @brief This function handles EXTI line0 interrupt.
    3. */
    4. void EXTI0_IRQHandler(void)
    5. {
    6. /* USER CODE BEGIN EXTI0_IRQn 0 */
    7. /* USER CODE END EXTI0_IRQn 0 */
    8. HAL_GPIO_EXTI_IRQHandler(KEY0_Pin);
    9. /* USER CODE BEGIN EXTI0_IRQn 1 */
    10. /* USER CODE END EXTI0_IRQn 1 */
    11. }
    12. /**
    13. * @brief This function handles EXTI line1 interrupt.
    14. */
    15. void EXTI1_IRQHandler(void)
    16. {
    17. /* USER CODE BEGIN EXTI1_IRQn 0 */
    18. /* USER CODE END EXTI1_IRQn 0 */
    19. HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
    20. /* USER CODE BEGIN EXTI1_IRQn 1 */
    21. /* USER CODE END EXTI1_IRQn 1 */
    22. }
    23. /**
    24. * @brief This function handles EXTI line2 interrupt.
    25. */
    26. void EXTI2_IRQHandler(void)
    27. {
    28. /* USER CODE BEGIN EXTI2_IRQn 0 */
    29. /* USER CODE END EXTI2_IRQn 0 */
    30. HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
    31. /* USER CODE BEGIN EXTI2_IRQn 1 */
    32. /* USER CODE END EXTI2_IRQn 1 */
    33. }
    34. /**
    35. * @brief This function handles EXTI line3 interrupt.
    36. */
    37. void EXTI3_IRQHandler(void)
    38. {
    39. /* USER CODE BEGIN EXTI3_IRQn 0 */
    40. /* USER CODE END EXTI3_IRQn 0 */
    41. HAL_GPIO_EXTI_IRQHandler(KEY3_Pin);
    42. /* USER CODE BEGIN EXTI3_IRQn 1 */
    43. /* USER CODE END EXTI3_IRQn 1 */
    44. }

    根据调用的函数继续往下走,跳转到了stm32f1xx_hal_gpio.c中:

    1. void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
    2. {
    3. /* EXTI line interrupt detected */
    4. if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
    5. {
    6. __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    7. HAL_GPIO_EXTI_Callback(GPIO_Pin);
    8. }
    9. }
    10. /**
    11. * @brief EXTI line detection callbacks.
    12. * @param GPIO_Pin: Specifies the pins connected EXTI line
    13. * @retval None
    14. */
    15. __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    16. {
    17. /* Prevent unused argument(s) compilation warning */
    18. UNUSED(GPIO_Pin);
    19. /* NOTE: This function Should not be modified, when the callback is needed,
    20. the HAL_GPIO_EXTI_Callback could be implemented in the user file
    21. */
    22. }

    上面的中断处理函数又调用了下面的那个回调函数。

    同理,我们要重写这个函数。

    1. //重写外部中断函数
    2. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    3. {
    4. switch(GPIO_Pin)
    5. {
    6. case KEY0_Pin :
    7. printf("first key is running.\n\r");
    8. led_operater_middle.ledMiddle(LED1, LedExtinguish);
    9. break;
    10. case KEY1_Pin :
    11. printf("second key is running.\n\r");
    12. led_operater_middle.ledMiddle(LED2, LedExtinguish);
    13. break;
    14. case KEY2_Pin :
    15. printf("third key is running.\n\r");
    16. led_operater_middle.ledMiddle(LED3, LedExtinguish);
    17. break;
    18. case KEY3_Pin :
    19. printf("forth key is running.\n\r");
    20. relayObj.relayOpen();
    21. break;
    22. default:
    23. printf("key fault!please click right key.\n\r");
    24. }
    25. }

    这能够完成点击触发中断的功能。

    那么,按键长按怎么解决呢?

    长按可以在触发中断后的处理函数中解决,下降沿之后,判断是短暂的低电平,还是有个持续的低电平,如果是短暂的低电平,那么就是单击;如果是连续的低电平,那么就认为是长按。判断是否是高低电平,有个读引脚状态的函数HAL_GPIO_ReadPin(……);

    时间关系,此处暂时不写了,后面有空再补充吧。

    注意 

    外部中断函数其实在GPIO的库文件中就有定义。

    处理函数是各EXTI各自的。根据中断向量表来的。

    1. /**
    2. * @brief This function handles EXTI line0 interrupt.
    3. */
    4. void EXTI0_IRQHandler(void)
    5. {
    6. HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
    7. }
    8. /**
    9. * @brief This function handles EXTI line1 interrupt.
    10. */
    11. void EXTI1_IRQHandler(void)
    12. {
    13. HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
    14. }
    15. /**
    16. * @brief This function handles EXTI line2 interrupt.
    17. */
    18. void EXTI2_IRQHandler(void)
    19. {
    20. HAL_GPIO_EXTI_IRQHandler(KEY3_Pin);
    21. }
    22. /**
    23. * @brief This function handles EXTI line3 interrupt.
    24. */
    25. void EXTI3_IRQHandler(void)
    26. {
    27. HAL_GPIO_EXTI_IRQHandler(KEY4_Pin);
    28. }

    但是所有的EXTI共用回调函数,即各EXTI都是调用同一个回调函数。

    所以,需要在重写的回调函数中判断下到底是来自于哪个端口。

    注意,跳转到中断处理函数是CPU的自动行为,但是具体如何处理,就需要用户自定义了。

    EXTI映射引脚设置完成后,别忘了在NVIC中使能,要不然不生效。

    别只设置了引脚复用,然后每在NVIC使能,还傻傻的不知道哪里出了问题。

    中断失效的一种情况

    在写中断函数时,发现之前配置好的中断不生效了。

    找了半天,才发现,main函数中没有加while死循环。

    关于main函数中的while死循环。

    主函数一定要有while(1)吗?

    是的。

    这是各种单片机自身决定的。有的单片机程序内如果没有循环程序,那它会从头执行到最后,然后返回最开始继续执行。而有的单片机在执行一次之后,就会停止。有的则是执行完后可能会进入无序运行状态,程序就跑飞了。main函数不能终止,要一直循环。

    所以,在写或者移植单片机程序的时候,最好是加上while循环,即便程序只运行一次,也要在最后加上while(1);目的是为了让程序一直保持在我们需要运行的情况下,使其一直指向这个语句而不会出现误操作。在程序末尾加一句“while(1);”,程序显示完全正常。如果不加,程序会不稳定,有时候会在main()里面循环,有时候会乱码。在我们写的C语言后转换成汇编,再观察单片机的代码区,你会发现没有写程序的部分例如全1或者全0区域,程序运行到这里,就会有可能造成意料不到的结果。

    比如出现HardFault。

    补充:按键的单击、双击、连续按、短按和长按实现思路

    更多参考:按键的长按和短按 - 知乎

    清除中断标志位

    遇到一个问题,是因为在中断里判断了如果锁定了,则不执行中断里的任何程序。

    结果,导致程序回不到主程序了。

    为什么?

    因为如果不执行中断,那么中断内的业务函数虽然没执行,但是清除中断标志位也没执行。

    不清除中断标志位会发生什么?

    中断标志位不清除, 结果是完成中断处理程序后, 它就继续再进中断, 根本不会回到主程序。

    这里的清除中断标志位的一行代码被我注释掉了,所以并没有清除中断标志位。这样的话会发生什么呢?按下KEY2时,完成中断处理程序后仍然进入中断,中断服务函数里的内容会被一直重复执行,直到松开KEY2。这样的话并不能控制LED0的翻转了,所以中断结束之后一定要清除中断标志位。

    拨码开关的常用逻辑

    定义一个之前的状态,一个当前的状态,之后判断,如果两个状态不相等,就执行某些操作。

    1. void lock_task(void)
    2. {
    3. static uint32_t s_PreStatus = 0xffffffff; //之前的锁状态
    4. uint32_t CurStatus; //当前的锁状态
    5. if(s_PreStatus != CurStatus)
    6. {
    7. s_PreStatus = CurStatus;
    8. //执行某种操作
    9. }
    10. }

    关于中断中要做的事

    通常,我们会在中断里设置相应的标志位,然后在主程序中去根据标志位做各种各样的判断。为什么要这么做呢?

    一来是因为中断最好快进快出,不要在里面执行复杂的业务逻辑,要不然容易导致时序错误;

    二是因为中断发生时,会跳转去执行中断,之后再回来执行主程序,这样,中断里就一定可以将标志位设置好,回到主程序时就可以根据标志位做出正确的判断。

    在嵌入式编程中,经常会使用标志位,设置某种标志位,然后根据标志位不同的值,执行不同的动作,也就是调用相应的函数。

    补充:编码器开关

    一种根据旋转方向,输出严格时序脉冲的电子元器件。

    最近用到编码器旋转开关,不会判断是顺时针旋转还是逆时针旋转。

    旋转编码器有三个引脚(不带按键),分为A端、G接地端和B端,A端和B端可以分别接单片机引脚。看资料说是,旋转时,AB端分别会输出时序严格的波形,根据二者的波形关系来判断旋转方向,所以可以在中断中判断另一端的电平高低,以此得出往哪边转,结果我在AB两端的中断里都做了中断判断,结果没有实现功能。

    后来继续查资料,发现自己理解错了。

    旋转编码开关,你不懂,编程方法和结构原理吗?拆给你看!_哔哩哔哩_bilibili

    原来要以一端为基准,只用触发其中一端的中断,在中断中,判断另一端是高电平还是低电平,以此得出是顺时针还是逆时针。

    举个例子,设置A端为下降沿触发(也可以上升沿),之后,在中断里,判断B端此时是高电平还是低电平,如果是高电平,则是顺时针旋转,如果是低电平,则为逆时针旋转。(此处只是示例,具体电平高低和方向的对应关系还需要看对应的手册中波形是如何)

    也就是,根据AB两端的波形关系来判断是顺时针还是逆时针。

    比如日本ALPS阿尔卑斯EC12E2420301贯通轴编码器

    其波形输出如下:

    CW即顺时针旋转(Clock Wise)的方向

    与CW反方向旋转时为CCW (Counter Clock Wise)。

    以A为参考点,CW时,B是这样的波形,那么CCW时,B的相位就会偏移。

    顺时针时

    逆时针时

    其实就是CW旋转方向中,通常增量型为A相比B相先进行相位输出;

    在CCW旋转方向中,通常增量型为B相比A相先进行相位输出。

    今天遇到个问题,就是编码器转一次,不断生效,后面经排查发现不是编码器的问题。
    我在主循环中不断判断编码器转动的标志位,以此来执行一些动作。
    我转了编码器之后,这个动作不断执行,我以为是编码器的问题,结果是因为我执行完一次动作之后,没有将编码器旋转的标志位给清除,导致一直都是处于判断有效状态。
    所以,需要在执行完相应的动作后,将编码器旋转标志位清除。

    所以,常常需要注意标志位或者状态位的置位和复位操作。

    消抖

    旋转编码器的软件消抖

    编码器在使用时,会在旋转过程中出现抖动,导致效果很差,要么是转一下就生效好多次,要么就是往右转然后又往左转就好像没转一样,等等问题。

    我们想要的理想结果是,往左转就是左转触发一次,往右转就是往右转触发一次,因此,消抖十分重要。

    可以直接参考这篇文章:

    江协科技STM32——旋转编码器计次(软件消抖)_stm32 软件消抖-CSDN博客

    参照的思路是第三种:增加判断正、反转的条件,读取一个周期内的电平变化再进行判断。首先将最小系统板的PB0引脚与A相连接,触发方式选择上升/下降沿触发,用A相的输出信号来触发中断,然后在A相下降沿触发第一次中断后读取B相电平,紧接着A相上升沿触发第二次中断后读取B相电平,结合两次读取到的电平来判断是正转还是反转,这种检测方法和第二种方法的原理相同,即从A相的下降沿触发到上升沿触发期间,若B相电平发生了变化,则判定编码器转动,反之未转动,波形抖动时B相的电平保持不变,能够实现消抖。

    本人参考所实现的程序如下:

    1. void EXTI9_5_IRQHandler(void)
    2. {
    3. static uint8_t firstLevel, irqCount;
    4. if(EXTI_GetITStatus(EXTI_Line6) != RESET)//判断中断是否发生
    5. {
    6. //一端下降沿触发第一次中断
    7. if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0 && irqCount == 0)
    8. {
    9. //计数值加一,表示已经触发了第一次中断
    10. irqCount++;
    11. //读取另外一端的电平,若为高电平则firstLevel置1,反之保持0
    12. firstLevel = 0;
    13. if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == 1)
    14. {
    15. firstLevel = 1;
    16. }
    17. }
    18. //A相上升沿触发第二次中断
    19. if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 1 && irqCount == 1)
    20. {
    21. irqCount = 0;//计数清零
    22. if(firstLevel == 1 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == 0)
    23. {
    24. knobState = KNOB_RIGHT;;//正转
    25. }
    26. if(firstLevel == 0 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == 1)
    27. {
    28. knobState = KNOB_LEFT;//反转
    29. }
    30. }
    31. //清除 LINE 上的中断标志位
    32. EXTI_ClearITPendingBit(EXTI_Line6);
    33. }
    34. }

    要注意:此时编码器是双边沿触发。结合两次边沿的判断,得出正确的周期,消除误判。

    机械按键的软件消抖

    按键的消抖相对简单,就是通过延时,但是不能直接用delay来延时,而是要用定时器来实现延时。

    思路就是:当按键的外部中断触发后,在中断触发函数中先不着急给按键赋予按下状态,而是置个标志位,表示当前已经按下了按键。

    同时,在10ms定时器里,根据该标志位来进行判断,是否处于按下状态的电平,如果是,再给按键赋予按下状态。

    经验证,可以解决抖动的问题。

  • 相关阅读:
    day37-IO流04
    OpenFOAM: twoPhaseEulerFoam解读
    论文解读:Rectifying the Shortcut Learning of Background for Few-Shot Learning
    哈希索引和自适应哈希索引
    CSS的概念和基本用法
    股票交易接口dll代码分享
    TensorFlow 2.10.0 已发布
    基于JAVA水果销售管理网站计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    一命通关双指针
    【LeetCode刷题】--9.回文数
  • 原文地址:https://blog.csdn.net/qq_28576837/article/details/126787965