【平衡小车】学习日志(一)

移植并建立部分驱动文件

  • MPU6050的函数文件采用【正点原子】的MPU6050的移植文件
  • OLED屏幕的函数文件采用【江科大】的文件
  • SYSTEM 文件夹采用【正点原子】的文件

转接板原理图


编码器

有两个车轮,使用两个编码器定时器【输入捕获】功能测速
  • 编码器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和PA1
    GPIO_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通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好
    TIM_ICInit(TIM2,&TIM_ICInitStructure);//初始化输入捕获单元通道1
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//指定配置的TIM通道2
    TIM_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);
//第八步:打开定时器TIM2
    TIM_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和PA7
    GPIO_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通道1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置输入捕获的滤波器,数越大滤波效果越好
    TIM_ICInit(TIM3,&TIM_ICInitStructure);//初始化输入捕获单元通道1
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//指定配置的TIM通道2
    TIM_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);
//第八步:打开定时器TIM3
    TIM_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的通路
//第五步:配置NVIC
    NVIC_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显示左轮和右轮的速度(带符号值)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值