1.硬件准备和连接
(1)编码器电机(需要清楚的了解电机每分钟的转速、减速比还有编码器的线数(即编码器一周产生多少个脉冲))
(2)TB6612电机驱动模块
(3)STM32最小系统板+四针OLED
(4)硬件连接
编码器A相<——>PA6 编码器VCC<——>3.3V
编码器B相<——>PA7 编码器GND<——>GND
TB6612_AIN1<——>PA5 USB转TTLRX<——>PB10(USART3 TX)
TB6612_AIN2<——>PA4 USB转TTLTX<——>PB11(USART3 RX)
TB6612_PWMA<——>TIM2_CH3(PA2)
软件层面我们使用TIM4来配置编码器采样时间
2.PID调节原理
在学习调节的过程中,我曾无数次看到过PID调节的流程图,其实大家但凡要了解PID的话,这个图是必须要看的,但是只要了解一下就可以了,真正核心的是你要搞清楚PID增量式计算过后的值到底是一个什么东西。
经过PID计算后的值,通常被称为PID输出值或控制量,我的理解是我们现在要用增量式PID调控电机速度,那么能够影响到电机速度的控制量是什么呢?没错,就是PWM(脉宽调制)的值,所以在我下面提供的例程中经过PID计算计算后输出的值就是PWM。
我们使用PID调控电机是为了让电机跑的更稳?
那怎样理解跑的更稳呢?当我们给一个电机设定转速时,该电机就会以对应的速度跑起来,但是综合考虑电机跑起来后的负载和地面摩擦力的影响,实际的输出速度效果肯定达不到我们设定的转速,而想要解决这个问题我们就要使用PID算法了,这个算法可以根据我们传感器采集到的实际速度和我们期望的理论速度值进行计算,计算后输出的值就是我们上述提到的PWM(为了将系统当前状态调整至所需目标状态所需的控制量),我们将不断比对实际速度和期望速度经过PID计算后的PWM不断赋给电机,这样就能够对电机进行有反馈的闭环调控,如此一来,电机就能跑的更稳了。(以上为个人理解,可能不太严谨,如果哪里有错,请各位小伙伴多多指教)
3.程序基本结构
要想实现上述的PID调控,我们需要使用编码器采样计算当前的实际速度,和当前期望的理想速度,需要注意的是我们为了尽可能的做到精细控制,提高调控的分辨率,我们将上面说的当前的实际速度和期望的理想速度统一转化为在10ms时间内的脉冲数。
(为什么这里是10ms呢,因为这个是我们测速的闸门时间)
这个地方不懂得可以去哔哩哔哩看一下柴太郎讲的还有江科大stm32教程里面的编码器测速
(1)编码器接口配置和转速转换
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
TIM_Cmd(TIM3, ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
return Temp;
}
int My_abs(int encoder_cnt)
{
if(encoder_cnt>=0)
{
return encoder_cnt;
}
else
{
return -encoder_cnt;
}
}
/**************************************************************************
功 能: 计算实际转速
输 入: encoder_cnt:脉冲数;ppr:码盘数;ratio:减速比;cnt_time:计数时间(ms)
返回 值: 车轮转速 rpm
**************************************************************************/
float Moto_Speed(int encoder_cnt,uint16_t ppr,uint16_t ratio,uint16_t cnt_time)
{
encoder_cnt = My_abs(encoder_cnt);
return (encoder_cnt/ppr/4/ratio)*(1000/cnt_time)*60; //编码器一个计数周期会给4个信号,11线编码器转一圈44个脉冲信号
}
/**************************************************************************
功 能: 计算转数对应编码器脉冲数
输 入: num:转数;ppr:码盘数;ratio:减速比
返 回 值: 电机脉冲数
**************************************************************************/
long Num_Encoder_Cnt(float num,uint16_t ppr,float ratio)
{
return (num*ratio*ppr*4);
}
/**************************************************************************
功 能: 计算转速对应编码器脉冲数
输 入: rpm:转速;ppr:码盘数;ratio:减速比;cnt_time:计数时间(ms)
返 回 值: 电机脉冲数
**************************************************************************/
long Rpm_Encoder_Cnt(float rpm,uint16_t ppr,uint16_t ratio,uint16_t cnt_time)
{
return (rpm*ratio*ppr*4)/(60*1000/cnt_time);
}
这里我们只需要用到对stm32编码器接口的配置和最后一个计算转速对应编码器脉冲数的函数。
(2)PWM配置和电机驱动(能够让电机转起来)
PWM配置
#include "stm32f10x.h" // Device header
/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 7200 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 0; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable);
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
TIM_SetCompare3(TIM2,0);
}
电机驱动
#include "stm32f10x.h" // Device header
#include "PWM.h"
#include "motor.h"
#include "Encoder.h"
//融合工程中的数据
float Position_KP=-9.00,Position_KI=-0.000,Position_KD=-200.0; /* PID系数 */
float Incremental_KP=-1.80,Incremental_KI=-3.40,Incremental_KD=0.0; /* PID系数 *///-1.80 -3.50
float num = 0;
int rpm= 0;
int16_t speed_num = 0;
int Pwm_Out = 0;
/**
* 函 数:直流电机初始化
* 参 数:无
* 返 回 值:无
*/
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4和PA5引脚初始化为推挽输出
PWM_Init(); //初始化直流电机的底层PWM
GPIO_ResetBits(GPIOA,GPIO_Pin_4); //防止电机上电旋转
GPIO_ResetBits(GPIOA,GPIO_Pin_5); //防止电机上电旋转
}
void Motor_SetPWM(int PWM)
{
if(PWM>0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
//
// TIM_SetCompare3(TIM2,PWM+Dead_Voltage);
Pwm_Out=Motor_Xianfu(My_abs(PWM)+Dead_Voltage);
TIM_SetCompare3(TIM2,Pwm_Out);
}
if(PWM<0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
// TIM_SetCompare3(TIM2,-PWM+Dead_Voltage);
Pwm_Out=Motor_Xianfu(My_abs(PWM)+Dead_Voltage);
TIM_SetCompare3(TIM2,Pwm_Out);
}
if(PWM==0)
{
Pwm_Out=0;
TIM_SetCompare3(TIM2,0);//避免电机停转时两端仍有死区电压
}
}
void Moto_Speed_Read(void)
{
speed_num=Encoder_Get();
}
void Motor_Stop(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
TIM_SetCompare3(TIM2,0);
}
int Motor_Xianfu(int pwm)
{
if(pwm>7000) pwm=7000;
if(pwm<-7000) pwm=-7000;
return pwm;
}
void rpm_xianfu(void)
{
if (rpm>=170)
{
rpm=170;
}
if (rpm<=-170)
{
rpm=-170;
}
}
int Position_PID(int reality,int target)
{
static float Eorr,Pwm,Last_Eorr,Integral_Eorr=0;
Eorr=target-reality; /* 计算偏差 */
Integral_Eorr+=Eorr; /* 偏差累积 */
if(Integral_Eorr> 5000) Integral_Eorr = 5000; /* 积分限幅 */
if(Integral_Eorr<-5000) Integral_Eorr =-5000;
Pwm = (Position_KP*Eorr) /* 比例环节 */
+(Position_KI*Integral_Eorr) /* 积分环节 */
+(Position_KD*(Eorr-Last_Eorr)); /* 微分环节 */
Last_Eorr=Eorr; /* 保存上次偏差 */
return Pwm; /* 输出结果 */
}
int Incremental_PID(int reality,int target)
{
static float Eorr,Pwm,Last_Eorr=0,Prev_Eorr=0;
Eorr=target-reality; /* 计算偏差 */
// if(-1<Eorr<1)
// {Eorr=0;}
Pwm += (Incremental_KP*(Eorr-Last_Eorr)) /* 比例环节 */
+(Incremental_KI*Eorr) /* 积分环节 */
+(Incremental_KD*(Eorr-2*Last_Eorr+Prev_Eorr)); /* 微分环节 */
Prev_Eorr=Last_Eorr; /* 保存上上次偏差 */
Last_Eorr=Eorr; /* 保存上一次偏差 */
return Pwm; /* 输出结果 */
}
电机驱动里面的函数我们需要注意motor_xianfu( )和rpm_xianfu( ),因为我们这里给的PWM峰值是7200,且我的电机每分钟最大转速是170/min,所以我这里的限幅分别给7000和170,可以根据你电机的实际情况更改。
(3)串口配置和上位机(不需要理解,作用是和上位机通信)
串口配置
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
#include "Ano.h"
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;
void Serial_Init(void)//Uart3
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//PB10引脚——>TX
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//PB11引脚——>RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;//无校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位字长
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART3, ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART3, Byte);
while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
void USART3_IRQHandler(void)
{
if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART3);
// Serial_RxFlag = 1;
Ano_GetByte(Serial_RxData);
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
}
上位机协议
#include "stm32f10x.h"
#include "Ano.h"
#include "Motor.h"
/* 匿名V7上位机协议 */
uint8_t Data_Buff[32] ={0XAA,0XFF,0XF1}; /* 表示协议,F1表示自由帧ID */
uint8_t Data_Sent[32] ={0XAA,0XFF,0XE2}; /* E1表示数据读取ID */
uint8_t Data_Check[12]={0XAA,0XFF,0X00}; /* 00表示校验帧 */
uint8_t Data_Get[32];
/**************************************************************************
函数功能:发送上位机绘制波形函数
入口参数:发送的参数(32位,32位,32位,32位)
返回 值:无
**************************************************************************/
void Data_send(int32_t _a,int32_t _b,int32_t _c,int32_t _d)
{
uint8_t i,cnt=4; /* 计数 */
uint8_t sc=0,ac=0; /* 校验 */
Data_Buff[cnt++] = BYTE0(_a);
Data_Buff[cnt++] = BYTE1(_a);
Data_Buff[cnt++] = BYTE2(_a);
Data_Buff[cnt++] = BYTE3(_a);
Data_Buff[cnt++] = BYTE0(_b);
Data_Buff[cnt++] = BYTE1(_b);
Data_Buff[cnt++] = BYTE2(_b);
Data_Buff[cnt++] = BYTE3(_b);
Data_Buff[cnt++] = BYTE0(_c);
Data_Buff[cnt++] = BYTE1(_c);
Data_Buff[cnt++] = BYTE2(_c);
Data_Buff[cnt++] = BYTE3(_c);
Data_Buff[cnt++] = BYTE0(_d);
Data_Buff[cnt++] = BYTE1(_d);
Data_Buff[cnt++] = BYTE2(_d);
Data_Buff[cnt++] = BYTE3(_d);
Data_Buff[3]=cnt-4;
for(i=0;i<cnt;i++) /* 校验位计算 */
{
sc+=Data_Buff[i];
ac+=sc;
}
Data_Buff[cnt++] = sc;
Data_Buff[cnt++] = ac;
for( i = 0 ; i < cnt; i++)
{
while((USART3->SR&0X40)==0);
USART3->DR = Data_Buff[i];
}
}
/**************************************************************************
函数功能:发送数据,内部调用
入口参数:id,数据
返回 值:无
**************************************************************************/
void Ano_SentPar(uint16_t id,int32_t data)
{
uint8_t cnt=4,i=0,sc=0,ac=0;
Data_Sent[cnt++] = BYTE0(id);
Data_Sent[cnt++] = BYTE1(id);
Data_Sent[cnt++] = BYTE0(data);
Data_Sent[cnt++] = BYTE1(data);
Data_Sent[cnt++] = BYTE2(data);
Data_Sent[cnt++] = BYTE3(data);
Data_Sent[3]=cnt-4;
for(i=0;i<cnt;i++) /* 校验位计算 */
{
sc+=Data_Sent[i];
ac+=sc;
}
Data_Sent[cnt++] = sc;
Data_Sent[cnt++] = ac;
for( i = 0 ; i < cnt; i++)
{
while((USART3->SR&0X40)==0);
USART3->DR = Data_Sent[i];
}
}
/**************************************************************************
函数功能:返回给上位机的校验数据,接受到上位机数据写入时通信使用,内部调用
入口参数:id,和校验,附加校验
返回 值:无
**************************************************************************/
void Ano_SentCheck(uint8_t id,uint8_t _sc,uint8_t _ac) /* 校验帧 */
{
uint8_t cnt=4,i=0,sc=0,ac=0;
Data_Check[cnt++] = id;
Data_Check[cnt++] = _sc;
Data_Check[cnt++] = _ac;
Data_Check[3]=cnt-4;
for(i=0;i<cnt;i++) /* 校验位计算 */
{
sc+=Data_Check[i];
ac+=sc;
}
Data_Check[cnt++] = sc;
Data_Check[cnt++] = ac;
for( i = 0 ; i < cnt; i++)
{
while((USART3->SR&0X40)==0);
USART3->DR = Data_Check[i];
}
}
/**************************************************************************
函数功能:匿名协议数据解析函数
入口参数:数据包
id 号 :1 2 3 4
参 数:KP KI KD num
返 回 值:无
**************************************************************************/
void Ano_Anl(uint8_t *Data_Pack) /* 数据解析 */
{
uint8_t sc = 0,ac = 0;
for(uint8_t i=0;i<Data_Pack[3] + 4;i++)
{
sc += Data_Pack[i];
ac += sc;
}
if(sc != Data_Pack[Data_Pack[3]+4] || ac!= Data_Pack[Data_Pack[3]+5])
{
return;
}
if(Data_Pack[2] == 0xE1) /* 参数读取ID 0XE1 */
{
uint16_t id = Data_Pack[4] + ((uint16_t)Data_Pack[5]<<8);
switch(id)
{
case 1:
Ano_SentPar(id,Incremental_KP*10); /* Kp放大10倍发送 */
break;
case 2:
Ano_SentPar(id,Incremental_KI*10); /* Ki放大10倍发送 */
break;
case 3:
Ano_SentPar(id,Incremental_KD*10); /* Kd放大10倍发送 */
break;
case 4:
Ano_SentPar(id,rpm); /* 转速 */
break;
default:
Ano_SentPar(id,0);
break;
}
}
if(Data_Pack[2] == 0xE2) /* 参数写入ID 0XE2,接收到的是整数*/
{
uint16_t id = Data_Pack[4] + ((uint16_t)Data_Pack[5]<<8);
switch(id)
{
case 1:
Incremental_KP=(float)*(int *)(&Data_Pack[6])/10;
break;
case 2:
Incremental_KI=(float)*(int *)(&Data_Pack[6])/10;
break;
case 3:
Incremental_KD=(float)*(int *)(&Data_Pack[6])/10;
break;
case 4:
rpm=(int)*(int *)(&Data_Pack[6]); rpm_xianfu();//转速限幅
break;
}
}
Ano_SentCheck(Data_Pack[2],Data_Pack[Data_Pack[3]+4],Data_Pack[Data_Pack[3]+5]); /* 返回校验 */
}
/**************************************************************************
函数功能:匿名协议数据校验
入口参数:收到的一帧数据
返回 值:无
**************************************************************************/
void Ano_GetByte(uint8_t data)
{
static uint8_t sta=0,datalen=0,datacnt=0; /* 帧长度,接收到长度 */
if(sta==0)
{
datalen=0;
datacnt=0;
Data_Get[0]=data;
if(data==0xAA) /* 帧头 */
sta=1;
}
else if(sta==1) /* 地址 */
{
Data_Get[1]=data;
sta=2;
}
else if(sta==2) /* ID */
{
Data_Get[2]=data;
sta=3;
}
else if(sta==3) /* 数据长度 */
{
sta=4;
Data_Get[3]=data;
datalen=data;
}
else if(sta==4) /* 数据缓存 */
{
Data_Get[4+datacnt++]=data;
if(datacnt >= datalen)
{
sta=5;
}
}
else if(sta==5) /* 和校验 */
{
Data_Get[4+datacnt++]=data;
sta=6;
}
else if(sta==6) /* 附加校验 */
{
sta=0;
Data_Get[4+datacnt++]=data;
Ano_Anl(Data_Get); /* 数据解析 */
}
}
(4)测速周期配置(使用TIM4,这里我们配置的10ms采样计算一次)
#include "stm32f10x.h" // Device header
#include "Motor.h"
#include "Encoder.h"
#include "OLED.h"
int Target_Encoder = 0,Encoder_cnt = 0;
void Timer_INIT(void) //72MHz/36000/20=100Hz 10ms
{
//配置GPIO
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
//选择定时器
TIM_InternalClockConfig(TIM4);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=35999;
TIM_TimeBaseInitStructure.TIM_Prescaler=19;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
//为了避免刚一上电就立刻进入中断,需要在TIM_TimebaseInit的后面,进入中断的前面,手动调用TIM_ClearFlag来解决这个问题
TIM_ClearFlag(TIM4,TIM_FLAG_Update);
//配置中断输出控制
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_Initstructure;
NVIC_Initstructure.NVIC_IRQChannel=TIM4_IRQn;
NVIC_Initstructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_Initstructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_Initstructure);
TIM_Cmd(TIM4,ENABLE);
}
//10ms触发一次,1ms获取的编码器脉冲过少,但如果电机编码器线速很大就可忽略
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET)
{
static int PWM = 0;
Encoder_cnt=Encoder_Get();//10ms内获取的脉冲数
if(Target_Encoder==0&& (My_abs(Encoder_cnt)<=1)) //能有效消除0速抖动现象,但在静止情况轮胎不会特别硬(硬-转不动)
{
Motor_SetPWM(0); /* 停止输出 */
}
else
{
PWM=Incremental_PID(Encoder_cnt,Target_Encoder);
Motor_SetPWM(PWM);
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
}
}
⭐核心理解
核心理解(转速向脉冲的转换):
在程序结构部分中的第(1)的程序中Rpm_Encoder_Cnt(float rpm,uint16_t ppr,uint16_t ratio,uint16_t cnt_time)实现了我们期望转速到对应的脉冲值得转换
return (rpm*ratio*ppr*4)/(60*1000/cnt_time); rpm是我们期望要调的转速
cnt_time是我们的采样周期(测速时间) 这里在我们程序中设定的是10ms
rpm*ratio*ppr*4是每分钟rpm的转速下产生的脉冲数
(rpm*ratio*ppr*4)/(60*1000/cnt_time); 是每cnt_time ms下此转速下产生的理论脉冲数
而实际cnt_time此转速下产生的实际脉冲数被编码器记录
通过两者之间的PID计算的出来的值就是我们想要的赋给电机的PWM值
打包源码在最开始,有需要的小伙伴请自取,内容又不合适错误的地方还请各位多多指正
本代码参考了哔哩哔哩柴太狼还有一位csdn博主的代码(时间久了忘记博主叫啥了),感谢你们伟大的开源分享精神,致敬。