十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32入门教程,第12课(下),旋转编码器计次

STM32入门教程,第12课(下),旋转编码器计次 目录1 接线图2 工程文件3 代码3.1 旋转编码器模块3.2 初始化函数3.3 中断函数3.4 功能代码3.5 本节课代码1 接线图第二个程序先看一下接线图。打开接线图的文件夹STM32入门\程序源码\STM32Project-有注释版\1-1 接线图打开【5-2 旋转编码器计次.jpg】的图片。右边还是OLED的电路左边这里插了一个旋转编码器模块。上面VCC和GND接正负极下面这两个AB相的输出引脚分别接到STM32的PB0和PB1两个引脚这就是硬件电路。我们在面包板上接一下。首先把旋转编码器模块插到面包板上因为这个模块的引脚间距并不是标准的距离所以要稍微别一下引脚才能插到面包板上。然后用面包板跳线接一下电源vcc接电源正极GND接电源负极。下面的两个零件用这个面包板飞线接一下A相输出接PB0B相输出接PB1这样硬件电路就完成了。2 工程文件找到工程文件夹复制一下【4-1 OLED显示屏】的工程改下名字叫【5-2 旋转编码器计次】。打开Project.uvprojx工程。先关掉无用的窗口。把这多余的代码删掉。编译试一下没有问题。下载看一下oled显示【HelloWorld!】有没有问题。3 代码3.1 旋转编码器模块在Keil中先把旋转编码器建一个模块。Hardware处右键添加新的文件。选择c文件起个名字叫【Encoder】路径加上反斜杠HardwareAdd。在Hardware处右键添加新文件。选择.h文件也叫【Encoder】路径加上反斜杠HardwareAdd。.h文件中写固定框架。然后.c文件右键添加一下头文件。3.2 初始化函数写一个初始化函数。初始化一下PB0和PB1两个GPIO口的外部中断。当然这里只初始化一个外部中断其实也是可以完成功能的。因为对于这个编码器而言正向旋转时A、B相输出的是这样的波形。上A下B。后同反向旋转时输出的是这样的波形。如果把一相的下降沿用作触发中断在中断时刻读取另一相的电平。正转就是高电平反转就是低电平这样就能区分旋转方向了。只不过这样在操作上有一些小瑕疵。比如你正转的时候由于A相先出现下降沿所以你刚开始动就进中断了。而反转是A相后出现下降沿所以就是你转到位了才进入终端。这样实际上也没问题就是有点不爽。所以我准备的就是A、B相都触发中断只有在B相下降沿和A相低电平时才判断为正转。在A相下降沿和B相低电平时才判断为反转。这样就能保证正转、反转都是转到位了才执行数字加减的操作。同时这样也可以给大家演示一下两个中断的初始化代码。那我们开始写代码。我们先把上个代码的初始化部分复制过来我们打开上一个工程Project——5-1 对射式红外传感器计次。然后把这些CountSensor_Init初始化代码复制一下。再回来。粘贴放在这里。然后改一下首先初始化时钟RCC_APB2Periph_GPIOB和RCC_APB2Periph_AFIO这些不用改。接着初始化GPIO我们用的是pb 0和pb 1所以这里改一下变成GPIO_Pin_0或上GPIO_Pin_1。然后是AFIO中断引脚选择我们把这个14改成0将第0个线路拨到GPIOB上然后复制粘贴一下这个代码再将第1个线路拨到GPIOB上这样AFIO就完成了。然后是EXTI部分我们把这个指定的中断线改成EXTI_Line0或者EXTI_Line1。EXTI_Trigger改为【EXTI_Trigger_Falling】。这样就能同时把第0条线路和第1条线路都初始化为中断模式、下降沿触发了。接着中断分组不用改。下面中断优先级要对两个通道分别设置优先级。我们复制一下下面的这部分代码。定义结构体变量的就不用复制了这个变量可以重复使用。然后改一下上面的改成【EXTI0_IRQn】优先级抢占和响应都还是1。把下面的改成【EXTI1_IRQn】就把这个响应优先级改低一些设置为2。3.3 中断函数这样同时初始化EXTI0和EXTI01的代码就改好了。然后我们把这两个中断的中断函数都写一下。双击打开启动文件Start——startup_stm32f10x_md.s。看到这里有这两个中断的中断函数。这里EXTI0和EXTI01是分别独占一个函数的所以先复制一下这个EXTI0_IRQHandler回到这里。写上void EXTI0_IRQHandler(void)在中断里我们也固定的检查一下中断标志位虽然这个函数只有EXTI0可以触发但是作为固定模式的代码我们还是写一下。在这里就写if (EXTI_GetITStatus(EXTI_Line0) SET)。如果是的话执行中断程序。最后清除中断标志位。然后复制一下这个函数改一下函数名这里是EXTI1_IRQHandler里面判断标志位改成EXTI_Line1清除标志位也是EXTI_Line1这样中断就完成了。这里这两个中断是进的两个函数。如果你使用的是9_5和15_10的这些中断那只能写一个中断函数这样就只需要把这两个if并列的放在一个函数里就行了。3.4 功能代码写实现功能的代码先在最上面定义一个变量。因为这里需要正反转所以就定义一个带符号的变量。接着在中断函数里EXTI0_IRQHandler里我们先判断一下另一个引脚的电平是不是等于等于0。如果是那就是反转就Encoder_Count --。当然这个是正转还是反转你可以自己定义正和反都是相对的。然后在EXTI1_IRQHandler里我们就if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) 0)如果是那就是正转就Encoder_Count 。这样中断对这个变量的加减就完成了。编译一下没有问题。然后我们需要再定义一个函数把这个变量返回回去所以在这个位置写上int16_t Encoder_Get(void)在这里面我就不直接返回【Encoder_Count】这个变量了。我打算返回每次调用这个Get函数之后返回count的变化值用于外部加减一个变量。所以在这里我需要返回count然后把count清零。那因为返回count之后函数就结束了没法清零了。所以我先定义一个临时变量Temp把Count先赋值给Temp再把Count清零最后返回Temp。这样就完成了。然后我们把这个Encoder_Get函数放在头文件声明一下上面这个Encoder_Init函数也声明一下。编译看看没有问题。在main.c里先#include Encoder.h然后Encoder_Init();假设这里有一个变Num需要调用Encoder_Get模块对它进行加减。那就可以在主循环里写Num Encoder_Get();因为这个Encoder_Get函数返回的是调用这个函数的间隔里旋转编码器产生的正负脉冲数所以这个返回值直接给Num就能对Num进行加减操作了。加一下OLED显示在这里调用OLED_ShowSignedNum在一行五列显示Num长度为5。上面的显示字符串也改一下一行一列显示Num:这样代码就完成了。编译看一下。下载。现在我们向右转编码器数字就增加。向左转数字减小。3.5 本节课代码Encoder.c#include stm32f10x.h // Device header int16_t Encoder_Count; void Encoder_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB,GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);//AFIO外设中断引脚选择配置 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//AFIO外设中断引脚选择配置 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line EXTI_Line0 | EXTI_Line1; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt;//中断模式 EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling;//下降沿都触发 EXTI_Init(EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_Init(NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 2; NVIC_Init(NVIC_InitStructure); } int16_t Encoder_Get(void) { int16_t Temp; Temp Encoder_Count; Encoder_Count 0; return Temp; } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) SET) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) 0)//判断另一个引脚的电平 { Encoder_Count --;//反转 } EXTI_ClearITPendingBit(EXTI_Line0); } } void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) SET) { if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) 0)//判断另一个引脚的电平 { Encoder_Count ;//正转 } EXTI_ClearITPendingBit(EXTI_Line1); } }Encoder.h#ifndef __ENCODER_H #define __ENCODER_H void Encoder_Init(void); int16_t Encoder_Get(void); #endifmain.c#include stm32f10x.h // Device header #include Delay.h #include OLED.h #include Encoder.h int16_t Num; int main(void) { OLED_Init(); Encoder_Init(); OLED_ShowString(1,1,Num:); while(1) { Num Encoder_Get(); OLED_ShowSignedNum(1,5,Num,5); } }这就是第二个代码的全部内容了。最后再给大家提几个中断编程的建议第一个就是在这个中断函数里最好不要执行耗时过长的代码中断函数要简短快速别刚进中段就执行一个Delay多少毫秒这样的代码因为中断是处理突发的事情如果你为了处理一个突发的事情待在中断里不出来了那主程序就会受到严重的阻塞。另外就是最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件尤其是硬件相关的函数。比如Oled显示函数。如果你既在主函数里调用Oled又在中断里调用OledOled就会显示错误。为什么呢因为你想在主程序里Oled刚显示一半他进中断了结果中断你还是Oled显示函数Oled就挪到其他地方显示了。这时还没有问题但当中断结束之后需要继续原来的显示这时候就出问题了因为硬件的显示位置被挪到其他地方所以再回来的时候继续显示的内容就会跟着跑到其他地方去这就会造成问题。虽然在中断进入和退出的时候会有保护现场和恢复现场但这只能保证cpu程序能正常返回不出问题。对于外部硬件的话并没有在进入中断时进行现场保护所以中断返回后就出问题了。那为了避免这样的问题就最好不要在主程序和中断程序里操作可能产生冲突的硬件。在实现功能的时候可以像我这样在中断里操作变量或者标志位。当中断返回时我在对这个变量进行显示和操作这样既能保证中断函数的简短快速又能保证不产生冲突的硬件操作。这就是中断程序设计的注意事项那在其他地方大家也都可以多用用变量或者标志位来减少代码之间的耦合性。让各部分代码相互独立仅使用变量标志位或者函数作为接口这样能让程序更加清晰代码更加强健。那本节外部中断的部分到这里就结束了我们下节再见。
返回列表