移植并建立部分驱动文件
-
MPU6050的函数文件采用【正点原子】的MPU6050的移植文件
-
OLED屏幕的函数文件采用【江科大】的文件
-
SYSTEM 文件夹采用【正点原子】的文件
![](https://i-blog.csdnimg.cn/blog_migrate/e98fc9f7cda57793c3cdaf50e1999e02.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2006a09d3dfd987ea59018e5aedb2ab3.png)
转接板原理图
编码器
有两个车轮,使用两个编码器定时器【输入捕获】功能测速
-
编码器1——PA0/PA1---TIM2
-
编码器2——PA6/PA7---TIM3
1、完成驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【Encoder】配置文件放在【Hardware】文件夹里
2、在Encoder.c编写两个编码器初始化函数
Encoder
_Init
/***************************************编码器1——PA0/PA1---TIM2编码器2——PA6/PA7---TIM3***************************************//***************************************配置编码器1——定时器TIM2——用作【输入捕获】编码器用于【读取】定时器计数,因此不需要在GPIO配置【输出速度】***************************************/void Encoder_TIM2_Init(void){//第一步:开启TIM和GPIO的时钟//输入捕获通道选择【TIM2】RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//第二步:初始化GPIO口,把GPIO配置成输入模式,一般选择【上拉输入】或者【浮空输入】//【引脚定义表】TIM2的通道1和通道2分别对应PA0和PA1//本例选择用TIM2的通道1和通道2进行输入捕获,故初始化PA0和PA1GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//不需要选择时基单元的时钟,因为编码器接口会托管时钟//编码器接口就是一个带方向控制的外部时钟//第三步:配置时基单元(在stm32f10x_tim.h文件中查找函数)TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);//给结构体赋默认初始值,防止意外TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//指定计数器模式(此处选择向上计数)TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//指定要在下一次更新事件时加载到ARR自动重新加载寄存器中的周期值 ARR取值[0,65535]TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//指定用于划分TIM时钟的预分频器值 PSC取值[0,65535]TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//指定重复计数器的值(高级定时器才用得上,本项目给0)TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//将以上参数配置到TIM2的时基单元//此处ARR设置为最大,因为ARR(重装计数值)越大,输入捕获测量的频率越精准,而且方便换算为负数//PSC的值决定了测周法的标准频率fc=[72MHz/(PSC+1)]//此处给PSC=72-1,则fc=72MHz/72=1MHz//第四步:配置输入捕获单元(对于编码器接口只用配置滤波器、极性选择),此处只用选择通道和滤波器TIM_ICInitTypeDef TIM_ICInitStructure;//TIM_ICStructInit(&TIM_ICInitStructure);//给结构体赋一个默认初始值TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//指定配置的TIM通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好TIM_ICInit(TIM2,&TIM_ICInitStructure);//初始化输入捕获单元通道1TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//指定配置的TIM通道2TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好TIM_ICInit(TIM2,&TIM_ICInitStructure);//初始化输入捕获单元通道2//第五步:配置编码器接口TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//后两个参数与第四步的TIM配置的是同一个寄存器,可以只用这个函数配置【两个通道】的极性//保证Encoder配置函数在ICInit函数下面,不然ICInit给的默认初始值会覆盖Encoder函数的配置TIM_ClearFlag(TIM2,TIM_FLAG_Update);//计数溢出之后更新中断标志位置1,此处手动清除//第六步:配置输出中断控制,中断类型为【TIM_IT_Update】溢出更新TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//上电后计数值从0计(避免之前的值影响计数)TIM_SetCounter(TIM2,0);//第七步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//指定中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定IRQ通道的抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定IRQ通道的响应优先级NVIC_Init(&NVIC_InitStructure);//第八步:打开定时器TIM2TIM_Cmd(TIM2,ENABLE);//初始化之后,CNT就会随着编码器旋转而自增自减//如果想测量编码器的位置,直接读出CNT的值就行//如果想测量编码器的速度和方向,需要每隔一段固定的闸门时间,取出一次CNT,再把CNT清零(测频法)}/***************************************配置编码器2——定时器TIM3——用作【输入捕获】编码器用于【读取】定时器计数,因此不需要在GPIO配置【输出速度】***************************************/void Encoder_TIM3_Init(void){//第一步:开启TIM和GPIO的时钟//输入捕获通道选择【TIM3】RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//第二步:初始化GPIO口,把GPIO配置成输入模式,一般选择【上拉输入】或者【浮空输入】//【引脚定义表】TIM3的通道1和通道2分别对应PA6和PA7//本例选择用TIM3的通道1和通道2进行输入捕获,故初始化PA6和PA7GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//不需要选择时基单元的时钟,因为编码器接口会托管时钟//编码器接口就是一个带方向控制的外部时钟//第三步:配置时基单元(在stm32f10x_tim.h文件中查找函数)TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseStructInit(&TIM_TimeBaseInitStructure);//给结构体赋默认初始值,防止意外TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//指定计数器模式(此处选择向上计数)TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//指定要在下一次更新事件时加载到ARR自动重新加载寄存器中的周期值 ARR取值[0,65535]TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//指定用于划分TIM时钟的预分频器值 PSC取值[0,65535]TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//指定重复计数器的值(高级定时器才用得上,本项目给0)TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//将以上参数配置到TIM3的时基单元//此处ARR设置为最大,因为ARR(重装计数值)越大,输入捕获测量的频率越精准,而且方便换算为负数//PSC的值决定了测周法的标准频率fc=[72MHz/(PSC+1)]//此处给PSC=72-1,则fc=72MHz/72=1MHz//第四步:配置输入捕获单元(对于编码器接口只用配置滤波器、极性选择),此处只用选择通道和滤波器TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(&TIM_ICInitStructure);//给结构体赋一个默认初始值TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//指定配置的TIM通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好TIM_ICInit(TIM3,&TIM_ICInitStructure);//初始化输入捕获单元通道1TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//指定配置的TIM通道2TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好TIM_ICInit(TIM3,&TIM_ICInitStructure);//初始化输入捕获单元通道2//第五步:配置编码器接口TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//后两个参数与第四步的TIM配置的是同一个寄存器,可以只用这个函数配置【两个通道】的极性//保证Encoder配置函数在ICInit函数下面,不然ICInit给的默认初始值会覆盖Encoder函数的配置TIM_ClearFlag(TIM3,TIM_FLAG_Update);//计数溢出之后更新中断标志位置1,此处手动清除//第六步:配置输出中断控制,中断类型为【TIM_IT_Update】溢出更新TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//上电后计数值从0计(避免之前的值影响计数)TIM_SetCounter(TIM3,0);//第七步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;//指定中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定IRQ通道的抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定IRQ通道的响应优先级NVIC_Init(&NVIC_InitStructure);//第八步:打开定时器TIM3TIM_Cmd(TIM3,ENABLE);//初始化之后,CNT就会随着编码器旋转而自增自减//如果想测量编码器的位置,直接读出CNT的值就行//如果想测量编码器的速度和方向,需要每隔一段固定的闸门时间,取出一次CNT,再把CNT清零(测频法)}
3、在Encoder.c编写编码器读取速度函数
Read_Encoder
/***************************************编码器读取速度函数参数:指定读取编码器的定时器计数值(TIM2/TIM3)原理:每次读到计数值后先保存再清零————【(当前保存的计数值)-(前一个计数值0)】/时间 = 速度***************************************/int Read_Encoder(u8 TIMx){int Encoder_TIM;switch(TIMx){case 2: Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break;case 3: Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;break;default: Encoder_TIM=0;}return Encoder_TIM;}
4、在Encoder.c编写中断服务函数
TIMx_IRQHandler
/***************************************编码器只计数,用不到中断服务函数但程序需要有对应的中断服务函数以进行寻址***************************************///TIM2中断服务函数void TIM2_IRQHandler(void)//当定时器产生更新中断时,这个函数就会被自动执行{if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//检查中断标志位{TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除中断标志位}}//TIM3中断服务函数void TIM3_IRQHandler(void)//当定时器产生更新中断时,这个函数就会被自动执行{if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)//检查中断标志位{TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除中断标志位}}
5、在Encoder.h中声明两个编码器初始化函数
Encoder
_Init和编码器读取速度函数
Read_Encoder
void Encoder_TIM2_Init(void);void Encoder_TIM3_Init(void);int Read_Encoder(u8 TIMx);
6、在sys.h中
#includ
e "Encoder.h"
#include "Encoder.h"
TIMER
使用定时器功能以闸门时间产生更新中断,并在中断服务函数中向主函数传速度值
1、完成驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【Timer】配置文件放在【Hardware】文件夹里
2、在
Timer.c编写定时器初始化函数
void TIM4_Init(void)
void TIM4_Init(void){//第一步:开启时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//第二步:选择时基单元的时钟源TIM_InternalClockConfig(TIM4);//对于定时中断,这里选择为内部时钟源//时基单元就由内部时钟驱动了//第三步:配置时基单元(在stm32f10x_tim.h文件中查找函数)TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//指定计数器模式(此处选择向上计数)TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//指定要在下一次更新事件时加载到ARR自动重新加载寄存器中的周期值 ARR取值[0,65535]TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//指定用于划分TIM时钟的预分频器值 PSC取值[0,65535]TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//指定重复计数器的值(高级定时器才用得上,本项目给0)TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);//初始化时基单元//手动清除更新标志位,避免刚初始化完成就进中断TIM_ClearFlag(TIM4,TIM_FLAG_Update);//第四步:使能更新中断(在stm32f10x_tim.h文件中查找函数)TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//开启了更新中断到NVIC的通路//第五步:配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;//指定中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定IRQ通道的抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定IRQ通道的响应优先级NVIC_Init(&NVIC_InitStructure);//第六步:启动定时器TIM_Cmd(TIM4, ENABLE);//至此定时器就可以开始工作了,当产生更新时,就会触发中断}
3、在
Timer.c编写中断服务函数
TIMx_IRQHandler
void TIM4_IRQHandler(void) //TIM4中断{if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)//检查中断标志位{Encoder_Left = -Read_Encoder(2);Encoder_Right = Read_Encoder(3);TIM_ClearITPendingBit(TIM4, TIM_IT_Update);//清除中断标志位}}
4、在
Timer.h中声明定时器初始化函数
void TIM4_Init(void)和中断服务函数
TIMx_IRQHandler
void TIM4_Init(void);
extern int Encoder_Left,Encoder_Right;
5、在主程序sys.c中
#include "Timer.h"
#include "Timer.h"
6、【Timer】结合【Encoder】可实现两个编码器不断在闸门时间内测速
主程序:
#include "sys.h"int Encoder_Left,Encoder_Right;/***************************************编码器1——PA0/PA1---TIM2——左轮编码器2——PA6/PA7---TIM3——右轮***************************************/int main(void){OLED_Init();Encoder_TIM2_Init();//用作【输入捕获】Encoder_TIM3_Init();//用作【输入捕获】TIM4_Init(); //定时器TIM4定时中断作为闸门时间while(1){OLED_ShowString(1,1,"Speed_L:");OLED_ShowSignedNum(1,9,Encoder_Left,2);OLED_ShowString(3,1,"Speed_R:");OLED_ShowSignedNum(3,9,Encoder_Right,2);}}
实现功能:上电后初始化OLED、两个编码器、用于定时中断传速度值的TIM4,在OLED显示左轮和右轮的速度(带符号值)