STM32学习100步之第四十七-四十八步——旋转编码器驱动程序

旋转编码器

旋转编码器是一种可以左右旋转,同时也可以按下,也可以按下旋转的器件,通过左右旋转对应着内部不同开关的导通,同时按下也可以旋转,由此看来旋转编码器可以实现很复杂的功能,简单的通过左右旋转可以调节音量、亮暗等功能,按键可以发挥普通按键的作用,按下按键的同时左右旋转又可以区别普通旋转的按键,因此可以用一个旋转编码器同时调节音量和亮暗(举例),同时也可以通过不同的转速实现不同的功能,总之,功能很复杂,作为初学者,本次只介绍普通旋转和按键作用,同时分析卡死的问题。

开发板上的端口映射原理图如下:其中旋转编码器和模拟量摇杆线路复用,使用其中一个器件时,将对应的跳线帽断开即可。在这里插入图片描述

在这里插入图片描述

旋转编码器的具体原理图如下所示,我们开发板中的1和4接地,如上图所示,按键按下时,开关1闭合,1和2接通,当向左右旋转时,开关2和3以一定的先后顺序闭合,也可能同时闭合,时序电路如下图所示,另外卡死是由于旋转编码器内部的机械结构不灵敏,正常情况下,每旋转一次,旋转编码器都会正常的咔哒一声,但是极少数情况下,会卡在咔哒声之前,即旋钮停在了两个格段之间,使得K2一直保持低电平,单片机退不出循环,从而导致卡死。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体的程序如下,其中读取旋钮旋转的方向的方法有两种。1、同时读取两个按键的状态,如果同时读到K2是高电平、K3是低电平,则是方向1,如果同时读到K2是低电平、K3是高电平,则是方向2。2、只读取K2的状态,当K2的状态是低电平的时候,取一段及短的时间再判断K3的状态,若K3的状态是低电平,则是方向1,若K3的状态是高电平,则是方向2。卡死的状态变化通过标志位KUO以及累加COUT来判断。

读取程序如下:
u8 ENCODER_READ(void){ //接口初始化
	u8 a;//存放按键的值
	u8 kt;
	a=0;
	if(GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L))KUP=0;	//判断旋钮是否解除锁死
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&KUP==0){ //判断是否旋转旋钮,同时判断是否有旋钮锁死
		delay_us(100);
		kt=GPIO_ReadInputDataBit(ENCODER_PORT_B,ENCODER_R);	//把旋钮另一端电平状态记录
		delay_ms(3); //延时
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)){ //去抖
			if(kt==0){ //用另一端判断左或右旋转
				a=1;//右转
			}else{
				a=2;//左转
			}
			cou=0; //初始锁死判断计数器
			while(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_L)&&cou<60000){ //等待放开旋钮,同时累加判断锁死
				cou++;KUP=1;delay_us(20); //
			}
		}
	}
	if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)&&KUP==0){ //判断旋钮是否按下  
		delay_ms(20);
		if(!GPIO_ReadInputDataBit(ENCODER_PORT_A,ENCODER_D)){ //去抖动
			a=3;//在按键按下时加上按键的状态值
			//while(ENCODER_D==0);	等等旋钮放开
		}
	}
	return a;
} 
其中delay_us(100)是等待一小段时间,等待稳定之后检测K3的按键值,另外delay_ms(3)是消抖函数,因为在电平的变化时,会产生抖动,消抖之后如果仍然有效,则给相应的按键赋值。其中最后一段的锁死判断程序用了两个&&,即同时起作用,第一个条件是等待旋转之后的自动弹起,当弹起时,直接退出while语句这时KUP标志位为1,注意此时是正常的状态,通过单片机的不断扫描,下一次再进入读取函数后会通过第一个判断语句,将KUP清为0,如果真的是锁死状态则第一个条件恒为真,执行第二个为真的条件,这时延时大概1.2秒之后退出了循环(正常情况下,旋转的弹起时间必然小于1.2秒),这时仍然是锁死状态,单片机再次进入读取函数时KUP不会被清为0(因为锁死状态不满足if语句),因为一个KUP=1,所以以后的读取函数所有的语句都不被执行,这时单片机处于不被卡死的状态,即完成了卡死的程序处理。
主函数实例如下:
int main (void){//主程序
	u8 a=0,b=0,c=0x01;
	RCC_Configuration(); //系统时钟初始化 
	RTC_Config();  //RTC初始化

	ENCODER_Init(); //旋转编码器初始化

	TM1640_Init(); //TM1640初始化
	TM1640_display(0,a/10); //显示数值
	TM1640_display(1,a%10);
	TM1640_display(2,20);
	TM1640_display(3,20);
	TM1640_display(4,20);
	TM1640_display(5,20);
	TM1640_display(6,20);
	TM1640_display(7,20);

	while(1){
		b=ENCODER_READ();	//读出旋转编码器值	
		if(b==1){a++;if(a>99)a=0;} //分析按键值,并加减计数器值。
		if(b==2){if(a==0)a=100;a--;}
		if(b==3)a=0;
		if(b!=0){ //如果有旋转器的操作
			TM1640_display(0,a/10); //显示数值
			TM1640_display(1,a%10);
		}

//		TM1640_led(c); //与TM1640连接的8个LED全亮
//		c<<=1; //数据左移 流水灯
//		if(c==0x00)c=0x01; //8个灯显示完后重新开始
//		delay_ms(150); //延时
	}
}
这里需要注意的是,如果同时将下面的流水灯程序打开之后,因为延时函数的存在,会使得旋转按钮便的不灵敏,这是因为延时函数占用了CPU空间,从而使得扫描的频率变慢,使得灵敏度降低,解决的方法有两种。1、在延时函数中加入读取按键旋转的函数,同时扫描读取显示即可。2、将K2开关接入外部中断触发,即将读取函数放入中断函数,一旦旋转按钮,则直接从延时函数进入中断读取,进而判断扫描显示,这也是方法2的优势,只占用了一个中断资源。

图中所有图片出自洋桃电子。

  • 6
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
驱动数码管和旋转编码器代码需要使用 STM32F103C6T6 的引脚和寄存器进行配置和操作。 首先,我们需要确定数码管的引脚连接。假设我们使用了一个 4 位共阳数码管,将其引脚连接到 STM32F103C6T6 的 PB0、PB1、PB2、PB3 引脚上。 下面是使用 C 语言进行驱动数码管的代码: ```c #include "stm32f10x.h" // 定义数码管的段选信号 uint8_t digitSegments[4] = {0x3F, 0x06, 0x5B, 0x4F}; // 数码管显示函数 void displayNumber(uint32_t number) { // 分离个位、十位、百位和千位数字 uint32_t digits[4] = { number % 10, (number % 100) / 10, (number % 1000) / 100, number / 1000 }; // 配置数码管引脚为输出模式 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // 逐位显示数字 for (int i = 0; i < 4; i++) { // 点亮当前位的数码管显示 GPIO_Write(GPIOB, digitSegments[digits[i]] << i); // 添加适当的延时 for (int j = 0; j < 2000; j++); // 关闭数码管显示 GPIO_Write(GPIOB, 0x00); } } int main(void) { // 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 无限循环显示数字 while (1) { for (uint16_t number = 0; number < 10000; number++) { displayNumber(number); } } } ``` 接下来是旋转编码器的代码。假设编码器的 A 和 B 信号连接到了 STM32F103C6T6 的 PA0 和 PA1 引脚上。 ```c #include "stm32f10x.h" // 记录编码器当前状态 uint8_t encoderState = 0; // 编码器状态转换矩阵 const int8_t encoderTable[16] = { 0, +1, -1, 0, -1, 0, 0, +1, +1, 0, 0, -1, 0, -1, +1, 0 }; // 旋转编码器中断处理函数 void EXTI0_IRQHandler(void) { // 清除中断标志 if (EXTI_GetITStatus(EXTI_Line0) != RESET) { EXTI_ClearITPendingBit(EXTI_Line0); } // 读取编码器当前状态 uint8_t currentState = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) << 1); // 根据编码器状态转换矩阵更新旋转方向 int8_t direction = encoderTable[encoderState * 4 + currentState]; // 更新编码器当前状态 encoderState = (encoderState << 2) | currentState; // 处理旋转方向 if (direction > 0) { // 顺时针旋转 } else if (direction < 0) { // 逆时针旋转 } } int main(void) { // 初始化时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置编码器引脚为输入模式 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置外部中断 EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); // 配置中断向量表偏移量 NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); // 配置中断优先级 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); // 无限循环监听旋转编码器 while (1) { // ... } } ``` 以上是使用 STM32F103C6T6 驱动数码管和旋转编码器的基础代码,具体的细节和功能需求可以根据实际情况进行调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值