前言
旋转编码器:用来测量位置、速度、和旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息计科得知旋转轴的速度与方向。
类型:机械触点式/霍尔传感器式/光栅式


输出信号
A,B两相都输出方波,顺时针方向旋转时,A相超前B相90度;逆时针方向旋转,B相超前A相90度。
技术实现
原理图

接线图

代码实现
main.c
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时函数
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
int16_t Speed;
int main(void)
{
/*
OLED初始化
*/
OLED_Init();
/*
定时器初始化
*/
Timer_Init();
/*
旋转编码器初始化
*/
Encoder_Init();
/*
OLED显示
*/
OLED_ShowString(1,1,"Speed:"); //在第一行第一列开始显示字符串
while(1)
{
OLED_ShowSignedNum(1,7,Speed,5);
}
}
Encoder.h
cpp
#ifndef __ENCODER_H__
#define __ENCODER_H__
#include "stm32f10x.h" // Device header
void Encoder_Init(void);
int16_t Encoder_Get(void);
#endif
Encoder.c
cpp
#include "Encoder.h"
/**@brief 初始化旋转编码器
*@param none
*@retval none
**/
void Encoder_Init(void)
{
/*
开启时钟
*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //开启TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA时钟
GPIO_InitTypeDef GPIO_InitStruct; //定义结构体
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //选择PA6
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //选择上拉输入模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*
配置时基单元
*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //定义一个TIM_TimeBaseInitTypeDef类型的结构体用于初始化时基单元
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频模式为不分
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //计数方式设置为向上计数模式
/*
输出的PWM波形的频率为1kHz,分辨率为1%
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
*/
TIM_TimeBaseInitStruct.TIM_Period = 65536-1; //ARR,自动加载重装寄存器,要写入自动重装值,将ARR的值设为最大值,防止溢出
TIM_TimeBaseInitStruct.TIM_Prescaler = 1-1; //PSC,预分频器的分频值,这里不分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //重复计数,只有高级定时器才会使用,通用定时器用不到
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct); //初始化时基单元
/*
初始化输入捕获单元
*/
TIM_ICInitTypeDef TIM_ICInitStruct; //定义结构体用于初始化输入捕获单元
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; //选择TIM3输入捕获通道1
TIM_ICInitStruct.TIM_ICFilter = 0xF; //输入滤波,滤除噪音,使信号更平滑,并不会改变输入信号的频率
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择上升沿触发,高低电平不翻转
TIM_ICInit(TIM3,&TIM_ICInitStruct);
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_2; //选择TIM3输入捕获通道1
TIM_ICInitStruct.TIM_ICFilter = 0xF; //输入滤波,滤除噪音,使信号更平滑,并不会改变输入信号的频率
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择上升沿触发,高低电平不翻转
TIM_ICInit(TIM3,&TIM_ICInitStruct);
/*
配置编码器接口
*/
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
/*
开启定时器
*/
TIM_Cmd(TIM3,ENABLE);
}
/**@brief 返回CNT的值
*@param none
*@retval CNT的值
**/
int16_t Encoder_Get(void)
{
int16_t temp;
temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0); //将CNT的值清零
return temp;
}
Timer.h
cpp
#ifndef __TIMER_H__
#define __TIMER_H__
#include "stm32f10x.h" // Device header
void Timer_Init(void);
extern int16_t Encoder_Get(void);
#endif
Timer.c
cpp
#include "Timer.h"
extern uint16_t Speed;
/**@brief 初始化通用定时器2
*@param none
*@retval none
**/
void Timer_Init(void)
{
/*
开启高速总线APB1时钟
*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
/*
选择时钟模式为内部时钟,定时器时钟频率为72MHz
*/
TIM_InternalClockConfig(TIM2); //系统默认时钟模式为内部时钟模式,不调用该函数并不会影响时钟模式配置为内部时钟模式
/*
配置时基单元
*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //选择分频模式为不分频
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //选择计数模式为向上计数
/*
预分频值与自动重装值计算
计数器溢出频率(计数器每秒记多少个数):CK_CNT_OV = 72MHz/(PSC + 1)/(ARR + 1)
*/
TIM_TimeBaseInitStruct.TIM_Period = 10000-1; //自动重装值(当计数器到达该预设值会将重装寄存器重新加载到计数寄存器中))
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200-1; //预分频值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter= 0; //重复计数器,只有高级定时器才有,通用定时器用不到,直接赋值为0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
/**
*@note:该程序在复位后计数变量的值为1,说明复位后中断函数在初始化后就立刻进入一次。
* 原因是:因为预分频器有个缓冲寄存器,我们写的值只有在更新事件时才会起作用,
* 为了使我们写的值立刻起作用,在TIM_TimeBaseInt()函数末尾手动生成了一个更新事件,
* 使预分频器的值有效。但会使更新事件和更新中断同时发生,更新中断会置更新中断标志位。
* //Generate an update event to reload the Prescaler and the Repetition counter
* //values immediately
* TIMx->EGR = TIM_PSCReloadMode_Immediate;
* TIM2_EGR寄存器中的UG位置一,重新初始化计数器,产生一个更新事件,然后会进入中断
**/
/*
清除更新中断标志位
*/
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //将更新标志位取反,取消更新事件
/*
使能中断输出控制
*/
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
/*
配置NVIC
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC优先级分组选择分组2
/*
NVIC初始化
*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; //中断通道选择TIM2全局中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //启用NVIC通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
/*
启动定时器
*/
TIM_Cmd(TIM2,ENABLE);
}
/**@brief 通用定时器2中断
*@param none
*@retval none
**/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) //获取TIM中断标志位
{
Speed = Encoder_Get();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除中断标志位
}
}
OLED.h
cpp
#ifndef __OLED_H
#define __OLED_H
void 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.c
cpp
#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_x
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//更改引脚时,改变参数GPIOx,GPIO_Pin_x
GPIO_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_Font.h
cpp
#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,// 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};
#endif
内容要点
1.TIMx 主要功能
通用TIMx(TIM2,TIM3,TIM4和TIM5)定时器功能包括:
- 16位向上,向下,向上/向下自动装载计数器
- 16位可编程(可实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值
- 4个独立通道:
--输入捕获
--输出比较
--PWM生成(边沿或中间对齐模式)
--单脉冲模式输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 如下事件发生时产生中断/DMA
--更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部/外部触发计数)
--触发事件(计数器启动,停止,初始化或者由内部/外部触发计数)
--输入捕获
--输出比较
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或按中期的电流管理

2.中断与异常
中断(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个系统异常的解释:
编号0:没有实际对应的异常,而是用于存储复位后主堆栈指针(MSP)的初始值。
编号1:复位(Reset),这是当处理器收到复位信号时触发的最高优先级异常。
编号2:不可屏蔽中断(NMI),这是一个高优先级的中断,除非当前正在处理另一个NMI,否则不能被屏蔽。
编号3:硬故障(Hard Fault),这是一个捕捉所有未被捕获的异常和错误的情况。
编号4至5:内存管理故障(MemManage Fault)和总线故障(Bus Fault),这些是与内存访问相关的错误。
编号6:用法故障(Usage Fault),涉及到非法指令执行或状态转换等程序执行中的错误。
编号11:系统服务调用(SVC),用于请求操作系统提供的服务。
编号12:调试监视器(Debug Monitor),涉及调试相关的异常。
编号14:可挂起的系统调用(PendSV),可以用于在没有更高优先级任务需要处理时调度新的任务。
编号15:系统滴答定时器(SysTick),通常用于操作系统的计时功能。
编号7到10以及编号13是保留的,因此不计入上述的11个系统异常中 。
综上所述,"16-4-1=11"这个表达式实际上是在说明,尽管系统异常的编号范围是从0到15,但是由于某些编号被保留或者有特殊用途,并且编号0不是真正的异常,所以实际有效的系统异常数量为11个。这种安排使得Cortex-M3能够有效地管理和响应各种关键的系统事件,同时保持了足够的灵活性来适应不同的应用场景 。
3.STM32中断
拥有68个可屏蔽中断通道,包含EXTI,TIM,ADC,UART,SPI.I2C.RTC等多个外设
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级




4.定时器初始化
1.时钟设置
cpp
/*
开启高速总线APB1时钟
*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

TIM2属于APB1外围设备,应调用函数RCC_APB1PeriphClockCmd()这只APB1时钟。
2.选择计数器时钟
计数器时钟可由下列时钟源提供:
● 内部时钟(CK_INT)
● 外部时钟模式1:外部输入脚(TIx)
● 外部时钟模式2:外部触发输入(ETR)
● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时
器Timer1而作为另一个定时器Timer2的预分频器
cpp
/*
选择时钟模式为内部时钟,定时器时钟频率为72MHz
*/
TIM_InternalClockConfig(TIM2); //系统默认时钟模式为内部时钟模式,不调用该函数并不会影响时钟模式配置为内部时钟模式
调用TIM_InternalClockConfig()函数将TIM2定时器的时钟配置为内部时钟,此时时钟频率为72MHz
3.设置时基单元
时基单元
可编程通用定时器的主要部分是一个16位计数器和与其相关的自动装载寄存器,这个计数器可以向上计数,向下计数或者向上向下双向计数。此计数器时钟由预分频器分频得到。
时基单元包含:
- 计数器寄存器(TIMx_CNT)
- 预分频器寄存器(TIMx_PSC)
- 自动装载寄存器(TIMx_ARR)
cpp
/*
配置时基单元
*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //选择分频模式为不分频
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //选择计数模式为向上计数
/*
预分频值与自动重装值计算
计数器溢出频率(计数器每秒记多少个数):CK_CNT_OV = 72MHz/(PSC + 1)/(ARR + 1)
*/
TIM_TimeBaseInitStruct.TIM_Period = 10000-1; //自动重装值(当计数器到达该预设值会将重装寄存器重新加载到计数寄存器中))
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200-1; //预分频值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter= 0; //重复计数器,只有高级定时器才有,通用定时器用不到,直接赋值为0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
将分频模式配置为不分频模式,计数器的计数模式设置为向上计数。然后进行预分频值和自动重装值的的计算。计数器溢出频率(定时器每秒触发中断的次数):CK_CNT_OV = 72MHz/(PSC + 1)/(ARR + 1),这是要求每秒触发一次中断,CK_CNT_OV值为1Hz,PSC(预分频器的值)和ARR(自动重装值)分别设置为7200和10000。
(51单片机是一个机器周期(12个时钟周期)计一个数,而STM32是一个时钟周期记一个数) ,通用计数器用不到重复计数器,只有高级计数器才会用到,此处直接赋值为0.
1. 51单片机的计数机制
(1) 机器周期与指令周期
- 时钟周期:51单片机的晶振频率(如12MHz)决定了时钟周期(1/12 μs)。
- 机器周期 :
- 51单片机采用冯诺依曼架构 ,每条指令的执行需要 12个时钟周期(即1个机器周期)。
- 例如:若晶振为12MHz,则1个机器周期 = 1 μs(12个时钟周期)。
- 定时器计数 :
- 定时器(如Timer0)默认以机器周期为计数单位,即每过1个机器周期(1 μs)计数一次。
- 例如:设置定时器溢出值为1000时,溢出时间为 1000 × 1 μs = 1秒。
2. STM32的计数机制
(1) 指令周期与定时器时钟
- 时钟周期 :STM32采用哈佛架构 ,每个指令执行仅需 1个时钟周期(相比51的12周期效率更高)。
- 定时器计数时钟(CK_CNT) :
定时器的计数时钟由**预分频器(PSC)**分频而来,分频公式为: CK_CNT=系统时钟(如72MHz)/PSC+1
每个CK_CNT周期,计数器递增1次(无需等待指令周期)。
例如:若PSC=71,则CK_CNT = 72 MHz72=1 MHz7272MHz=1MHz,即每 1 μs 计数一次。
(2) 计数灵活性
- 直接基于时钟分频 :
STM32的计数器直接由CK_CNT驱动,无需绑定到指令周期,因此可以实现任意分频比(通过PSC和ARR的灵活配置)。- 高精度计数 :
例如:若系统时钟为72MHz,PSC=0(不分频),则CK_CNT=72MHz,计数器每 13.89 ns 递增一次,精度远高于51单片机。
3. 关键对比
特性 51单片机 STM32 架构 冯诺依曼架构(程序与数据共享总线) 哈佛架构(程序与数据独立总线) 指令周期 每条指令需12个时钟周期(1机器周期) 每条指令仅需1个时钟周期 定时器计数单位 机器周期(12时钟周期) 可配置的时钟分频(CK_CNT) 计数灵活性 固定为机器周期的分频(如12分频) 可通过PSC和ARR实现任意分频比 典型定时精度 1 μs(12MHz晶振时) 13.89 ns(72MHz系统时钟,不分频时)
4. 实际应用中的差异
(1) 定时1秒的配置
- 51单片机(12MHz晶振) :
- 定时器溢出值 = 1秒 / 1 μs = 1000(无需分频,直接使用机器周期)。
- STM32(72MHz系统时钟) :
- 需配置PSC和ARR:
- PSC = 71999 → 分频系数=72000 → CK_CNT = 72 MHz72000=1 kHz7200072MHz=1kHz
- ARR = 999 → 总计数周期 = 1000 → 1秒。
(2) PWM输出
- 51单片机 :
- PWM频率受限于机器周期,难以实现高频信号。
- STM32 :
- 可通过PSC和ARR灵活配置PWM频率,例如:
- PSC=0(CK_CNT=72MHz) → 高频PWM(如1MHz)。
5. 总结
- 51单片机:定时器基于固定机器周期(12时钟周期),计数灵活性较低,适合简单定时任务。
- STM32:定时器直接基于可编程的时钟分频(CK_CNT),每个时钟周期可计数一次,提供极高的灵活性和精度,适合复杂实时控制场景。
cpp
/*
清除更新中断标志位
*/
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //将更新标志位取反,取消更新事件
清除中断更新中断标志位,防止上电复位后立即进入中断。
该程序在复位后计数变量的值为1,说明复位后中断函数在初始化后就立刻进入一次。
原因是:因为预分频器有个缓冲寄存器,我们写的值只有在更新事件时才会起作用,
为了使我们写的值立刻起作用,在TIM_TimeBaseInt()函数末尾手动生成了一个更新事件,
使预分频器的值有效。但会使更新事件和更新中断同时发生,更新中断会置更新中断标志位。
* //Generate an update event to reload the Prescaler and the Repetition counter
* //values immediately
* TIMx->EGR = TIM_PSCReloadMode_Immediate;
TIM2_EGR寄存器中的UG位置一,重新初始化计数器,产生一个更新事件,然后会进入中断
4.输出中断控制
cpp
/*
使能中断输出控制
*/
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
选择TIM2外设,启用TIM更新中断源。
5.配置NVIC
cpp
/*
配置NVIC
*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC优先级分组选择分组2
/*
NVIC初始化
*/
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; //中断通道选择TIM2全局中断
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //启用NVIC通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
选择NVIC优先级分组为NVIC_PriorityGroup_2,将TIM2全局中断设置为中断通道,抢占优先级设置为2,响应优先级设置为1(在合法范围内灵活设置即可)
6.启用定时器
cpp
/*
启动定时器
*/
TIM_Cmd(TIM2,ENABLE);
调用TIM_Cmd()函数启用指定的TIMx外设。
9.中断函数
中断函数的命名是固定的,要根据启动文件中的中断向量表中查找,每一个中断源对应一个特定的中断服务函数名。中断函数名是严格区分大小写的,必须严格按照启动文件中的定义来编写。
cpp
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
进入中断函数后要调用EXTI_GetITStaus()函数用于检测参数指定的EXTI线路是否有中断触发请求发生(标志位被置1),该函数可以精确判断中断的来源避免误触发。如果不需要区分中断线路,可以不使用该函数。
cpp
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
EXTI_ClearITPendingBit()函数用于在中断程序中处理完中断事件后清除中断标志位,防止中断服务重复触发。
10.初始化旋转编码器
cpp
/**@brief 初始化旋转编码器
*@param none
*@retval none
**/
void Encoder_Init(void)
{
/*
开启时钟
*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //开启TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA时钟
GPIO_InitTypeDef GPIO_InitStruct; //定义结构体
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //选择PA6
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //选择上拉输入模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*
配置时基单元
*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //定义一个TIM_TimeBaseInitTypeDef类型的结构体用于初始化时基单元
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频模式为不分
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //计数方式设置为向上计数模式
/*
输出的PWM波形的频率为1kHz,分辨率为1%
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
*/
TIM_TimeBaseInitStruct.TIM_Period = 65536-1; //ARR,自动加载重装寄存器,要写入自动重装值,将ARR的值设为最大值,防止溢出
TIM_TimeBaseInitStruct.TIM_Prescaler = 1-1; //PSC,预分频器的分频值,这里不分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //重复计数,只有高级定时器才会使用,通用定时器用不到
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct); //初始化时基单元
/*
初始化输入捕获单元
*/
TIM_ICInitTypeDef TIM_ICInitStruct; //定义结构体用于初始化输入捕获单元
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; //选择TIM3输入捕获通道1
TIM_ICInitStruct.TIM_ICFilter = 0xF; //输入滤波,滤除噪音,使信号更平滑,并不会改变输入信号的频率
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择上升沿触发,高低电平不翻转
TIM_ICInit(TIM3,&TIM_ICInitStruct);
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_2; //选择TIM3输入捕获通道1
TIM_ICInitStruct.TIM_ICFilter = 0xF; //输入滤波,滤除噪音,使信号更平滑,并不会改变输入信号的频率
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择上升沿触发,高低电平不翻转
TIM_ICInit(TIM3,&TIM_ICInitStruct);
/*
配置编码器接口
*/
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
/*
开启定时器
*/
TIM_Cmd(TIM3,ENABLE);
}

1.开启GPIOA和TIM3时钟。
2.初始化PA5和PA6引脚为上拉输入模式。
3.配置时基单元,将初始化时基单元的结构体成员TIM_Prescaler设置为1,即不分频,使用系统时钟驱动。
4.初始化输入捕获单元,初始化输入捕获通道1和通道2,捕获旋转编码器A相和B相的输出。极性选择为电平不翻转。
6. 配置编码器接口,两个通道均配置为边沿触发有效,当一个通道边沿触发时,CNT是否计数取决于另一输入的电平状态。两个通道的极性均配置为不翻转即高电平有效。
7.启动TIM3定时器
11.CNT读取
cpp
/**@brief 返回Speed
*@param none
*@retval 旋转编码器的速度
**/
int16_t Encoder_Get(void)
{
int16_t temp;
temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0); //将CNT的值清零
return temp;
}
将CNT读取功能封装 成函数int16_t Encoder_Get(void),声明一个静态变量用于存储CNT的值,方便读取CNT后将CNT清零。函数返回CNT的值,由于我们使用的为测频法,每秒执行一次中断,每秒对调函数TIM_GetCounter()读取CNT的值,将其赋给临时变量temp,然后调用TIM_SetCounter()设置CNT的值为0,执行清零操作。
实验结果
问题记录
暂无