目录
前言
技术实现
原理图
编辑
接线图
代码实现
技术要点
1.中断与异常
2.STM32中断
3.NVIC
4.EXTI
5.AFIO配置
6.EXTI部分函数
7.NVIC部分函数
8.中断函数
代码详解
问题记录
前言
旋转编码器:用来测量位置、速度、和旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息计科得知旋转轴的速度与方向。
类型:机械触点式/霍尔传感器式/光栅式
输出信号
A,B两相都输出方波,顺时针方向旋转时,A相超前B相90度;逆时针方向旋转,B相超前A相90度。
技术实现
原理图
接线图
代码实现
Encoder.c
#include "stm32f10x.h" // Device headerint16_t Encoder_Count; //旋转编码器计数/*** @brief 旋转编码器初始化* @param None* @retval None
*/
void Encoder_Init(void)
{/*高速总线APB2外围时钟设置*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //指定APB2外设为GPIOB对其时钟进行控制RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //指定APB2外设为AFIO对其时钟进行控制/*EXTI和NVIC的时钟是一直开启的,不需要手动配置开启时钟*//*GPIO初始化 *//*结构体初始化*/GPIO_InitTypeDef GPIO_InitStruct; //定义一个GPIO_InitTypeDef类型的结构体GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //配置为上拉输入,默认为高电平输入模式 GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //选择PB14引脚GPIO_Init(GPIOB,&GPIO_InitStruct);/*配置AFIO外部中断引脚选择*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); //将PB0设置为外部中断引脚 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); //将PB1设置为外部中断引脚 /*配置EXTI*//*结构体初始化*/EXTI_InitTypeDef EXTI_InitStruct;EXTI_InitStruct.EXTI_Line = EXTI_Line0 | EXTI_Line1 ; //选择PB14引脚为外部中断线EXTI_InitStruct.EXTI_LineCmd = ENABLE;EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式触发EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; //将触发活动边沿设置为上升沿触发EXTI_Init(&EXTI_InitStruct);/*配置NVIC*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先分组/*初始化结构体 */NVIC_InitTypeDef NVIC_InitStruct; //定义结构体NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; //指定要启用的IRQ通道NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //设置优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct); //嵌套中断向量初始化NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn; //指定要启用的IRQ通道NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStruct); //嵌套中断向量初始化
}/*** @brief 返回旋转编码器的计数值* @param None* @retval None
*/
int16_t Encoder_Get(void)
{int16_t Temp;Temp = Encoder_Count;Encoder_Count = 0;return Temp;
}/*** @brief 外部中断0* @param None* @retval None
*/
void EXTI0_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line0) == SET) //检测中断标志位{if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)Encoder_Count++; EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位}
}/*** @brief 外部中断1* @param None* @retval None
*/
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__#include "stm32f10x.h" // Device headerint16_t Encoder_Get(void);
void Encoder_Init(void);#endif
main.c
/**********************************************************
1.实验名称:旋转编码器
2.实验环境:STM32F103C8T6最小系统板
3.实验内容:通过旋转编码器正反向旋转触发外部中断计数,通过OLED显示旋转编码器的计数变化
4.作者;abai
5.实验时间:2025-3-8
**********************************************************/
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时函数
#include "OLED.h"
#include "Encoder.h"int16_t Num; //用于显示旋转编码器计数的变化值int main(void)
{/*OLED初始化*/OLED_Init();/* Encoder初始化*/Encoder_Init();OLED_ShowString(1,1,"Num:"); //OLED显示字符串while(1){Num += Encoder_Get();OLED_ShowNum(1,5,Num,5); //OLED显示数字}
}
OLED.c
#include "stm32f10x.h"
#include "OLED_Font.h"/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) //可更改引脚配置
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x)) //更改引脚时,改变参数GPIOx,GPIO_Pin_x/*引脚初始化*/
void OLED_I2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //将引脚的输出模式设置为开漏输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//更改引脚时,改变参数GPIOx,GPIO_Pin_xGPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_Init(GPIOB, &GPIO_InitStructure);//更改引脚时,改变参数GPIOx,GPIO_Pin_xGPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_Init(GPIOB, &GPIO_InitStructure);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief I2C开始* @param 无* @retval 无*/
void OLED_I2C_Start(void)
{OLED_W_SDA(1);OLED_W_SCL(1);OLED_W_SDA(0);OLED_W_SCL(0);
}/*** @brief I2C停止* @param 无* @retval 无*/
void OLED_I2C_Stop(void)
{OLED_W_SDA(0);OLED_W_SCL(1);OLED_W_SDA(1);
}/*** @brief I2C发送一个字节* @param Byte 要发送的一个字节* @retval 无*/
void OLED_I2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i++){OLED_W_SDA(!!(Byte & (0x80 >> i)));OLED_W_SCL(1);OLED_W_SCL(0);}OLED_W_SCL(1); //额外的一个时钟,不处理应答信号OLED_W_SCL(0);
}/*** @brief OLED写命令* @param Command 要写入的命令* @retval 无*/
void OLED_WriteCommand(uint8_t Command)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78); //从机地址OLED_I2C_SendByte(0x00); //写命令OLED_I2C_SendByte(Command); OLED_I2C_Stop();
}/*** @brief OLED写数据* @param Data 要写入的数据* @retval 无*/
void OLED_WriteData(uint8_t Data)
{OLED_I2C_Start();OLED_I2C_SendByte(0x78); //从机地址OLED_I2C_SendByte(0x40); //写数据OLED_I2C_SendByte(Data);OLED_I2C_Stop();
}/*** @brief OLED设置光标位置* @param Y 以左上角为原点,向下方向的坐标,范围:0~7* @param X 以左上角为原点,向右方向的坐标,范围:0~127* @retval 无*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{OLED_WriteCommand(0xB0 | Y); //设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}/*** @brief OLED清屏* @param 无* @retval 无*/
void OLED_Clear(void)
{ uint8_t i, j;for (j = 0; j < 8; j++){OLED_SetCursor(j, 0);for(i = 0; i < 128; i++){OLED_WriteData(0x00);}}
}/*** @brief OLED显示一个字符* @param Line 行位置,范围:1~4* @param Column 列位置,范围:1~16* @param Char 要显示的一个字符,范围:ASCII可见字符* @retval 无*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{ uint8_t i;OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容}
}/*** @brief OLED显示字符串* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param String 要显示的字符串,范围:ASCII可见字符* @retval 无*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i++){OLED_ShowChar(Line, Column + i, String[i]);}
}/*** @brief OLED次方函数* @retval 返回值等于X的Y次方*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1;while (Y--){Result *= X;}return Result;
}/*** @brief OLED显示数字(十进制,正数)* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~4294967295* @param Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++) {OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}/*** @brief OLED显示数字(十进制,带符号数)* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:-2147483648~2147483647* @param Length 要显示数字的长度,范围:1~10* @retval 无*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{uint8_t i;uint32_t Number1;if (Number >= 0){OLED_ShowChar(Line, Column, '+');Number1 = Number;}else{OLED_ShowChar(Line, Column, '-');Number1 = -Number;}for (i = 0; i < Length; i++) {OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');}
}/*** @brief OLED显示数字(十六进制,正数)* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~0xFFFFFFFF* @param Length 要显示数字的长度,范围:1~8* @retval 无*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i, SingleNumber;for (i = 0; i < Length; i++) {SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;if (SingleNumber < 10){OLED_ShowChar(Line, Column + i, SingleNumber + '0');}else{OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');}}
}/*** @brief OLED显示数字(二进制,正数)* @param Line 起始行位置,范围:1~4* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~1111 1111 1111 1111* @param Length 要显示数字的长度,范围:1~16* @retval 无*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i++) {OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');}
}/*** @brief OLED初始化* @param 无* @retval 无*/
void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++) //上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init(); //端口初始化OLED_WriteCommand(0xAE); //关闭显示OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8); //设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3); //设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40); //设置显示开始行OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA); //设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81); //设置对比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9); //设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4); //设置整个显示打开/关闭OLED_WriteCommand(0xA6); //设置正常/倒转显示OLED_WriteCommand(0x8D); //设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF); //开启显示OLED_Clear(); //OLED清屏
}
OLED.h
#ifndef __OLED_H
#define __OLED_Hvoid OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);#endif
OLED_Front.h
#ifndef __OLED_FONT_H
#define __OLED_FONT_H/*OLED字模库,宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16]=
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 00x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 10x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 20x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 30x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 40xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 50x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 60x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 70x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 80x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 90x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 100x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 110x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 120x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 130x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 140x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 150x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 160x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 170x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 180x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 190x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 200x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 210x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 220x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 230x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 240x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 250x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 260x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 270x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 280x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 290x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 300x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 310xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 320x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 330x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 340xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 350x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 360x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 370x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 380xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 390x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 400x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 410x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 420x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 430x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 440x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 450x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 460xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 470x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 480xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 490x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 500x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 510x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 520x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 530x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 540xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 550x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 560x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 570x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 580x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 590x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 600x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 610x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 620x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 630x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 640x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 650x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 660x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 670x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 680x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 690x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 700x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 710x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 720x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 730x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 740x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 750x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 760x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 770x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 780x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 790x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 800x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 810x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 820x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 830x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 840x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 850x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 860x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 870x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 880x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 890x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 900x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 910x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 920x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 930x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};#endif
技术要点
1.中断与异常
中断(Interrupts):当主程序中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续执行。中断通常是可以屏蔽的,在特定情况下CPU可以选择不响应这些请求。
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU会再次暂停当前中断程序,转而去处理新的中断程序,处理完成后一次进行返回。
异常 (Exceptions):异常通常是由CPU自身在执行指令时遇到的特殊情况触发的,异常不能屏蔽,一旦发生,CPU必须立即响应。
Cortex-M3支持大量的异常,包括16-4-1 = 11个异常和最多240个外部中断(简称为IRQ)。
在ARM Cortex-M3架构中,240个外部中断通常简称为IRQ(Interrupt Requests)。这些IRQ是Cortex-M3内核支持的异常类型的一部分,它们代表了可以由外部设备或内部模块触发的异步事件。每个IRQ都有一个特定的编号,并且与一个中断服务例程(ISR)相关联,当相应的中断发生时,处理器会执行这个ISR来处理中断请求。
具体来说,Cortex-M3支持总共256个中断向量,其中包括16个系统异常(例如复位、不可屏蔽中断NMI、硬件错误等),以及最多可达240个可编程的外部中断IRQ 。不过,实际上使用的IRQ数量取决于芯片制造商的设计选择,因为并不是所有的微控制器都会实现全部240个IRQ。例如,STM32系列微控制器可能会使用其中的一部分IRQ来对应其各种外设和功能 。
在Cortex-M3中,除了复位、NMI和硬故障这几个具有固定优先级的系统异常之外,其他的异常都可以通过NVIC(嵌套向量中断控制器)进行配置和管理,包括设置中断的使能状态、优先级以及挂起状态等 。
因此,当我们提到“IRQ”时,我们通常指的是那些可以通过NVIC管理和配置的外部中断源,而不是那些具有特殊用途的系统异常。IRQ机制为微控制器提供了灵活的方法来响应外部事件,从而提高了系统的实时性和响应速度。此外,通过IRQ机制,不同的外设能够以一种有序的方式通知处理器发生了需要立即关注的事件。
支持的异常是11个的原因
Cortex-M3(CM3)支持的16-4-1=11个异常实际上是基于对系统异常类型的计算。这里的“16”指的是从编号0到编号15,一共16个可能的系统异常编号。“4”是指其中四个编号被保留或不用于常见的系统异常,“1”则表示编号0实际上并不对应于一个具体的异常类型,而是作为向量表的一部分用来存储主堆栈指针(MSP)的初始值。
具体来说,以下是这11个系统异常的解释:
1. 编号0:没有实际对应的异常,而是用于存储复位后主堆栈指针(MSP)的初始值。
2. 编号1:复位(Reset),这是当处理器收到复位信号时触发的最高优先级异常。
3. 编号2:不可屏蔽中断(NMI),这是一个高优先级的中断,除非当前正在处理另一个NMI,否则不能被屏蔽。
4. 编号3:硬故障(Hard Fault),这是一个捕捉所有未被捕获的异常和错误的情况。
5. 编号4至5:内存管理故障(MemManage Fault)和总线故障(Bus Fault),这些是与内存访问相关的错误。
6. 编号6:用法故障(Usage Fault),涉及到非法指令执行或状态转换等程序执行中的错误。
7. 编号11:系统服务调用(SVC),用于请求操作系统提供的服务。
8. 编号12:调试监视器(Debug Monitor),涉及调试相关的异常。
9. 编号14:可挂起的系统调用(PendSV),可以用于在没有更高优先级任务需要处理时调度新的任务。
10. 编号15:系统滴答定时器(SysTick),通常用于操作系统的计时功能。编号7到10以及编号13是保留的,因此不计入上述的11个系统异常中 。
综上所述,“16-4-1=11”这个表达式实际上是在说明,尽管系统异常的编号范围是从0到15,但是由于某些编号被保留或者有特殊用途,并且编号0不是真正的异常,所以实际有效的系统异常数量为11个。这种安排使得Cortex-M3能够有效地管理和响应各种关键的系统事件,同时保持了足够的灵活性来适应不同的应用场景 。
2.STM32中断
拥有68个可屏蔽中断通道,包含EXTI,TIM,ADC,UART,SPI.I2C.RTC等多个外设
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
3.NVIC
NVIC(Nested Vectored Interrupt Controller):嵌套向量中断控制器,NVIC提供如下功能:
- 可嵌套中断支持
- 向量中断支持
- 动态优先级支持
- 中断延迟大大缩短
- 中断可屏蔽
NVIC基本结构
NVIC负责处理外设向CPU申请的中断,它根据中断优先级来决定哪一个外设优先进行中断。外设与它有N根通道相连,而NVIC只有一根通道相连。不仅避免的众多中断申请打断CPU的进程,而且节省了CPU的接口。
NVIC优先级分组
- NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高N位的抢占优先级(pre-emption priority)和4-N位抢占优先级(subpriority)
- pre_emption priority高的可以中断嵌套,subpriority可以优先排队,pre_emption priority and subpriority均相同的按中断号排队。
4.EXTI
- EXTI(Extern Interrupt)外部中断
- EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有的GPIO口,但相同的Pin不能同时触发
- 通道数:16个GPIO_Pin,外加PVD输出,RTC闹钟,USB唤醒,以太网唤醒
- 触发响应方式:中断响应/事件响应
EXTI基本结构
EXTI框图
(输入线为16根数据输入线)
AFIO复用IO口
- AFIO主要用于引脚复用功能的选择和重定义
- 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
(由图可知相同的PIN不能同时触发)
5.AFIO配置
在配置AFIO之前要先进行GPIO初始化,确定引脚,输入输出模式等
GPIO的输入模式由浮空输入,上拉输入,下拉输入。浮空输入在引脚空闲时引脚的状态不确定,易出现错误,上拉输入是在IO口使用弱上拉电阻将引脚的默认电平拉高,而下拉输入使用下拉电阻将IO口引脚的默认电平拉低。
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
GPIO_EXTILineConfig()函数用于选择GPIO的引脚作为EXTI通道,两个参数分别选择GPIO寄存器和GPIO引脚。
6.EXTI部分函数
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
EXTI_Init()用于初始化指定的EXTI外设,参数接受一个指向EXTI_InitTypeDef结构体的指针,该结构体有关于指定外部中断线引脚,外部中断线新状态, 中断触发模式,以及触发信号活动边沿。
7.NVIC部分函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
NVIC_PriorityGroupConfig()函数用配置优先级分组,可选择以下5种分组。
@param NVIC_PriorityGroup: specifies the priority grouping bits length. * This parameter can be one of the following values:* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority* 4 bits for subpriority* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority* 3 bits for subpriority* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority* 2 bits for subpriority* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority* 1 bits for subpriority* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority* 0 bits for subpriority 0 bits for subpriority
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
NVIC_Init()函数用于初始化NVIC外设接受指向NVIC_InitTypeDef的指针。该结构体分别用于指定要启用的IRQ通道,是否启用IRQ通道,pre_emption priority和subpriority的优先级
8.中断函数
中断函数的命名是固定的,要根据启动文件中的中断向量表中查找,每一个中断源对应一个特定的中断服务函数名。中断函数名是严格区分大小写的,必须严格按照启动文件中的定义来编写。
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
进入中断函数后要调用EXTI_GetITStaus()函数用于检测参数指定的EXTI线路是否有中断触发请求发生(标志位被置1),该函数可以精确判断中断的来源避免误触发。如果不需要区分中断线路,可以不使用该函数。
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
EXTI_ClearITPendingBit()函数用于在中断程序中处理完中断事件后清除中断标志位,防止中断服务重复触发。
代码详解
/*高速总线APB2外围时钟设置*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //指定APB2外设为GPIOB对其时钟进行控制RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //指定APB2外设为AFIO对其时钟进行控制
设置时钟不仅要开启GPIO时钟,同时要开启AFIO。它们共同附属于AP2高速总线但是EXTI和NVIC的时钟是时钟处于开启状态,此处无需开启EXTI和NVIC的时钟。
/*配置AFIO外部中断引脚选择*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); //将PB0设置为外部中断引脚 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); //将PB1设置为外部中断引脚
配置AFIO要同时将PB0和PB1都设置为外部中断引脚。因为此处要对旋转编码器旋转时同时检测两个引脚来判断顺时针旋转还是逆时针旋转从而执行相应的中断。
/*配置EXTI*//*结构体初始化*/EXTI_InitTypeDef EXTI_InitStruct;EXTI_InitStruct.EXTI_Line = EXTI_Line0 | EXTI_Line1 ; //选择PB14引脚为外部中断线EXTI_InitStruct.EXTI_LineCmd = ENABLE;EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式触发EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising; //将触发活动边沿设置为上升沿触发EXTI_Init(&EXTI_InitStruct);
配置EXTI选择PB0和PB1均为外部中断线,外部中断模式触发,边沿触发模式选择上升沿触发。
/*配置NVIC*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先分组/*初始化结构体 */NVIC_InitTypeDef NVIC_InitStruct; //定义结构体NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; //指定要启用的IRQ通道NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //设置优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct); //嵌套中断向量初始化NVIC_InitStruct.NVIC_IRQChannel = EXTI1_IRQn; //指定要启用的IRQ通道NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStruct); //嵌套中断向量初始化
配置NVIC优先分级组,配置为分组2。分别初始化NVIC外设PB0和PB1,指定要启用的IRQ通道。
/*** @brief 外部中断0* @param None* @retval None
*/
void EXTI0_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line0) == SET) //检测中断标志位{if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)Encoder_Count++; EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位}
}
外部中断0即PB0触发外部中断,发生上升沿触发,此时PB1为低电平,说明旋转编码器顺时针转,计数变量递增。
/*** @brief 外部中断1* @param None* @retval None
**/
void EXTI1_IRQHandler(void)
{if(EXTI_GetITStatus(EXTI_Line1) == SET) //检测中断标志位{if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0) Encoder_Count--;EXTI_ClearITPendingBit(EXTI_Line1); //清除中断标志位}
}
外部中断1即PB1触发外部中断,发生上升沿触发,此时PB0为低电平,说明旋转编码器逆时针旋转,计数变量递减。
问题记录
暂无