|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "STM32-外设驱动模块旋转编码器" |
| 4 | +date: 2025-09-23T08:47:31+0800 |
| 5 | +description: "旋转编码器是一种将旋转运动转换为电脉冲信号的传感器,广泛应用于工业控制、机器人技术和人机交互等领域。其核心原理是通过A相和B相两路90°相位差的信号判断旋转方向和步数,常见类型包括增量式和绝对式编码器。本文重点介绍了增量式编码器的工作原理、应用场景、关键参数(如分辨率PPR)以及与STM32单片机的硬件连接方法,并提供了示例代码说明如何读取编码器信号并计算旋转步数。通过简单的GPIO和外部中断配置,可实现对旋转编码器的高效检测,为精准控制提供可靠支持。" |
| 6 | +keywords: "STM32 外设驱动模块:旋转编码器" |
| 7 | +categories: ['单片机外设'] |
| 8 | +tags: ['嵌入式硬件', '单片机', 'Stm'] |
| 9 | +artid: "151987775" |
| 10 | +arturl: "https://blog.csdn.net/chenai886/article/details/151987775" |
| 11 | +image: |
| 12 | + path: https://api.vvhan.com/api/bing?rand=sj&artid=151987775 |
| 13 | + alt: "STM32-外设驱动模块旋转编码器" |
| 14 | +render_with_liquid: false |
| 15 | +featuredImage: https://bing.ee123.net/img/rand?artid=151987775 |
| 16 | +featuredImagePreview: https://bing.ee123.net/img/rand?artid=151987775 |
| 17 | +cover: https://bing.ee123.net/img/rand?artid=151987775 |
| 18 | +image: https://bing.ee123.net/img/rand?artid=151987775 |
| 19 | +img: https://bing.ee123.net/img/rand?artid=151987775 |
| 20 | +--- |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +# STM32 外设驱动模块:旋转编码器 |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +本文章已经生成可运行项目,一键运行 |
| 29 | + |
| 30 | +大家好,今天我们来深入探讨一下**旋转编码器**这个非常实用的单片机外设模块。旋转编码器在工业控制、机器人技术、精密测量和人机交互等领域得到了广泛应用。通过它,我们能够精准地检测旋转轴的转动角度和方向,是实现精准控制的关键组件。 |
| 31 | + |
| 32 | +### 1. 旋转编码器是什么? |
| 33 | + |
| 34 | +旋转编码器是一种将旋转运动转换为电脉冲信号的传感器。它通常由**光栅盘**和**红外发射/接收器**构成。当旋转轴转动时,光栅盘随之转动,红外光信号被交替阻挡或通过,从而产生一系列的电脉冲信号。这些脉冲信号反映了旋转轴的相对运动。 |
| 35 | + |
| 36 | + |
| 37 | + |
| 38 | +常见的旋转编码器有两种类型: |
| 39 | + |
| 40 | +* **增量式编码器**:输出的脉冲信号表示相对变化量,无法直接得出旋转的绝对位置。 |
| 41 | +* **绝对式编码器**:可以直接输出旋转轴的绝对位置。 |
| 42 | + |
| 43 | +本文将重点讨论**增量式编码器**,它的应用更为广泛,能够用于检测旋转的方向和步数。 |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +### 2. 旋转编码器的工作原理 |
| 48 | + |
| 49 | +增量式旋转编码器的核心原理是通过两路**A相**和**B相**的方波信号,来判断旋转的方向和计算旋转步数。具体来说: |
| 50 | + |
| 51 | +* **A相和B相**:两路信号之间的相位差为90°,通过检测哪一相先跳变(上升沿或下降沿),可以判断旋转方向。 |
| 52 | +* **Z相(索引信号)**:每当旋转轴完成一圈时,Z相会输出一个脉冲,用于定位旋转的零点,消除累计误差。 |
| 53 | + |
| 54 | +以下是旋转编码器的输出信号示意图: |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +### 3. 旋转编码器的应用 |
| 59 | + |
| 60 | +旋转编码器在多个领域都有广泛应用,特别是在以下几个方面: |
| 61 | + |
| 62 | +* **工业控制**:用于伺服电机和步进电机的速度和位置反馈。 |
| 63 | +* **人机交互**:比如音量旋钮、车载中控旋钮等,通过旋转编码器来识别用户输入。 |
| 64 | +* **精密测量**:用于精确测量微小位移。 |
| 65 | +* **机器人技术**:用于机器人关节的精确控制,确保动作的准确性。 |
| 66 | + |
| 67 | +### 4. 旋转编码器的参数 |
| 68 | + |
| 69 | +了解旋转编码器的一些基本参数非常重要,这些参数直接影响编码器的工作精度和性能: |
| 70 | + |
| 71 | +* **分辨率(PPR)**:每旋转一圈输出的脉冲数,分辨率越高,测量精度越高。例如,100 PPR的编码器每旋转一圈会输出100个脉冲。 |
| 72 | +* **相数**:常见的是2相输出(A相和B相),有些编码器会提供更多的相位输出。 |
| 73 | +* **输出类型**:包括集电极开路、推挽输出和差分线驱动输出等,适应不同的应用环境。 |
| 74 | + |
| 75 | +### 5. 旋转编码器硬件连接 |
| 76 | + |
| 77 | +在单片机系统中,旋转编码器的硬件连接非常简单。下面是以STM32为例的硬件连接图: |
| 78 | + |
| 79 | +| 旋转编码器 | STM32单片机 | |
| 80 | +| --- | --- | |
| 81 | +| **VCC** | 3.3V | |
| 82 | +| **GND** | GND | |
| 83 | +| **A相** | B0 | |
| 84 | +| **B相** | B1 | |
| 85 | + |
| 86 | +通过连接这些基本线路,STM32单片机便能读取到旋转编码器的信号,并进行处理和显示。 |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +### 6. 示例代码 |
| 93 | + |
| 94 | +以下是一个简单的示例代码,展示如何使用STM32读取旋转编码器的信号,并显示旋转次数。 |
| 95 | + |
| 96 | +#### 代码说明: |
| 97 | + |
| 98 | +1. 配置A相和B相的输入引脚。 |
| 99 | +2. 使用外部中断检测A相和B相的信号变化。 |
| 100 | +3. 计算旋转的步数和方向。 |
| 101 | +4. 将结果通过OLED显示屏输出。 |
| 102 | + |
| 103 | +```c |
| 104 | +#include "stm32f10x.h" // Device header |
| 105 | + |
| 106 | +int16_t Encoder_Count; //全局变量,用于计数旋转编码器的增量值 |
| 107 | + |
| 108 | + |
| 109 | +void Encoder_Init(void) |
| 110 | +{ |
| 111 | + /*开启时钟*/ |
| 112 | + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 |
| 113 | + RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟 |
| 114 | + |
| 115 | + /*GPIO初始化*/ |
| 116 | + GPIO_InitTypeDef GPIO_InitStructure; |
| 117 | + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; |
| 118 | + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; |
| 119 | + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
| 120 | + GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入 |
| 121 | + |
| 122 | + /*AFIO选择中断引脚*/ |
| 123 | + GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚 |
| 124 | + GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚 |
| 125 | + |
| 126 | + /*EXTI初始化*/ |
| 127 | + EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量 |
| 128 | + EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线 |
| 129 | + EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能 |
| 130 | + EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式 |
| 131 | + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发 |
| 132 | + EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设 |
| 133 | + |
| 134 | + /*NVIC中断分组*/ |
| 135 | + NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2 |
| 136 | + //即抢占优先级范围:0~3,响应优先级范围:0~3 |
| 137 | + //此分组配置在整个工程中仅需调用一次 |
| 138 | + //若有多个中断,可以把此代码放在main函数内,while循环之前 |
| 139 | + //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置 |
| 140 | + |
| 141 | + /*NVIC配置*/ |
| 142 | + NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量 |
| 143 | + NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线 |
| 144 | + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能 |
| 145 | + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1 |
| 146 | + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1 |
| 147 | + NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设 |
| 148 | + |
| 149 | + NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线 |
| 150 | + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能 |
| 151 | + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1 |
| 152 | + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2 |
| 153 | + NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设 |
| 154 | +} |
| 155 | + |
| 156 | + |
| 157 | +int16_t Encoder_Get(void) |
| 158 | +{ |
| 159 | + /*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/ |
| 160 | + /*在这里,也可以直接返回Encoder_Count |
| 161 | + 但这样就不是获取增量值的操作方法了 |
| 162 | + 也可以实现功能,只是思路不一样*/ |
| 163 | + int16_t Temp; |
| 164 | + Temp = Encoder_Count; |
| 165 | + Encoder_Count = 0; |
| 166 | + return Temp; |
| 167 | +} |
| 168 | + |
| 169 | +/** |
| 170 | + * 函 数:EXTI0外部中断函数 |
| 171 | + * 参 数:无 |
| 172 | + * 返 回 值:无 |
| 173 | + * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 |
| 174 | + * 函数名为预留的指定名称,可以从启动文件复制 |
| 175 | + * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 |
| 176 | + */ |
| 177 | +void EXTI0_IRQHandler(void) |
| 178 | +{ |
| 179 | + if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断 |
| 180 | + { |
| 181 | + /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/ |
| 182 | + if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) |
| 183 | + { |
| 184 | + if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向 |
| 185 | + { |
| 186 | + Encoder_Count --; //此方向定义为反转,计数变量自减 |
| 187 | + } |
| 188 | + } |
| 189 | + EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位 |
| 190 | + //中断标志位必须清除 |
| 191 | + //否则中断将连续不断地触发,导致主程序卡死 |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +/** |
| 196 | + * 函 数:EXTI1外部中断函数 |
| 197 | + * 参 数:无 |
| 198 | + * 返 回 值:无 |
| 199 | + * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 |
| 200 | + * 函数名为预留的指定名称,可以从启动文件复制 |
| 201 | + * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 |
| 202 | + */ |
| 203 | +void EXTI1_IRQHandler(void) |
| 204 | +{ |
| 205 | + if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断 |
| 206 | + { |
| 207 | + /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/ |
| 208 | + if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) |
| 209 | + { |
| 210 | + if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向 |
| 211 | + { |
| 212 | + Encoder_Count ++; //此方向定义为正转,计数变量自增 |
| 213 | + } |
| 214 | + } |
| 215 | + EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位 |
| 216 | + //中断标志位必须清除 |
| 217 | + //否则中断将连续不断地触发,导致主程序卡死 |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | + |
| 222 | +// 主程序 |
| 223 | +#include "stm32f10x.h" // Device header |
| 224 | +#include "Delay.h" |
| 225 | +#include "OLED.h" |
| 226 | +#include "Encoder.h" |
| 227 | + |
| 228 | + |
| 229 | +int16_t Num; //定义待被旋转编码器调节的变量 |
| 230 | + |
| 231 | +int main(void) |
| 232 | +{ |
| 233 | + /*模块初始化*/ |
| 234 | + OLED_Init(); //OLED初始化 |
| 235 | + Encoder_Init(); //旋转编码器初始化 |
| 236 | + |
| 237 | + /*显示静态字符串*/ |
| 238 | + OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num: |
| 239 | + |
| 240 | + while (1) |
| 241 | + { |
| 242 | + Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上 |
| 243 | + OLED_ShowSignedNum(1, 5, Num, 5); //显示Num |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | + |
| 248 | + |
| 249 | +``` |
| 250 | +
|
| 251 | +--- |
| 252 | +
|
| 253 | +本文已生成可运行项目一键运行 |
| 254 | +
|
| 255 | +生成项目 |
| 256 | + |
| 257 | +
|
| 258 | +
|
| 259 | +
|
0 commit comments