前言
这是一个基于c8t6的简单pid闭环编码电机调速了;有以下功能: 1.按键控制电机速度以及 正反转
2.按键控制停止与开始
3. 按键控制开始与停止
4.oled屏幕显示当前转速和目标转速 5.匿名者上位机实现波形显示
一、cubemx外设配置
这里一个用到了6个外设;分别是:外部按键中断、两个gpio口输出、oled屏幕显示、usart串口传输、TIM1用于计时和pwm输出、TIM2用于捕获编码电机转速
1.外部按键中断:
这里选择开启PA3、PA4、PA5、PA6的外部中断,都为下降沿触发方式,并开启相应的中断;
2.TIM1定时器设置:
这里主要给tim1干了两件事:首先是能定时器1通道1的pwm输出,并设置频率为72000000/720/200=500Hz ;然后打开内部时钟源开启自动重转载值并打开tim1的更新中断,定时器1的作用是驱动电机,定时器2的作用是做编码作用,用来检测电机转速。如下设置:
首先是打开编码模式,然后设置滤波系数为6。至此,外设部分完成。
二、相关主要代码
1.自编延时代码
代码如下(示例):
void delay_ms(uint16_t time)
{
uint16_t i=0;
while(time--)
{
i=12000; //自己定义
while(i--) ;
}
}
这里的延时代码是用来按键中断消抖的,为什么不用HAL库自带的延时函数HAL_Delay()呢,,经测试,在按键中断回调函数中,无法用HAL_Delay()函数,具体为什么,我这里也不清楚,就自己编写一个延迟函数。
2.按键中断回调
代码如下(示例):
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)//按键的中断回调函数
{
/***************************按键1和2控制速度***************************/
if( GPIO_Pin ==KEY_1_Pin)//判断按键几发生中断
{
delay_ms(20);
direction++;
if(direction>1) //限幅(200mA-1000mA)
{
direction=1;
}
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);//定时器1通道1
}
if(GPIO_Pin==KEY_2_Pin)//判断按键几发生中断
{
delay_ms(20);
set_Speed-=25;
pidMotor1Speed.target_val -=25;
if(set_Speed<-310) //限幅
{
set_Speed=-310;
}
//HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_2);
}
/***************************按键3和4控制方向***************************/
if(GPIO_Pin==KEY_3_Pin)//判断按键几发生中断
{
delay_ms(20);
printf("3\r\n");
direction--;
if(direction<0) //限幅(200mA-1000mA)
{
direction=0;
}
HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_1);
Motor1Speed=0.0;
}
if(GPIO_Pin==KEY_4_Pin)//判断按键几发生中断
{
delay_ms(20);
set_Speed+=25;
pidMotor1Speed.target_val +=25;
if(set_Speed>310) //限幅(200mA-1000mA)
{
set_Speed=310;
}
}
}
这里就是四个按键,一个控制舵机开始和停止,另一个就是加速和减速 。
3.屏幕显示主代码
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);
/******************oled显示*************************/
sprintf(v1_1, "Sv:%.1fr/min", set_Speed);//显示设定速度
sprintf(v2_2, "Acv:%.1fr/min", Motor1Speed);//显示实际速度
sprintf(v3_3, " Direction:%d", direction);//显示方向
OLED_ShowString(2,0,v1_1,1);//这个是oled驱动里面的,是显示位置的一个函数,
OLED_ShowString(2,2,v2_2,1);//这个是oled驱动里面的,是显示位置的一个函数
OLED_ShowString(0,4.,v3_3,2);//这个是oled驱动里面的,是显示位置的一个函数,
/***************************串口调试********************************/
具体的驱动代码在文件led.c里面。
4.pid主要代码
#include "pid.h"
#include "main.h"
extern float set_Speed;
//定义一个结构体类型变量
tPid pidMotor1Speed;//电机1速度PID闭环参数
//给结构体类型变量赋初值
void PID_init()
{
pidMotor1Speed.actual_val=0.0;
pidMotor1Speed.target_val = set_Speed;//要用一个volatile 型定义啊的变量,用于中断控制
pidMotor1Speed.err=0.0;
pidMotor1Speed.err_last=0.0;
pidMotor1Speed.err_sum=0.0;
pidMotor1Speed.Kp=0.5;
pidMotor1Speed.Ki=0.2;
pidMotor1Speed.Kd=0.05;
}
float PID_realize(tPid * pid,float actual_val)
{
pid->actual_val = actual_val;//传递真实值
pid->err = pid->target_val - pid->actual_val;当前误差=目标值-真实值
pid->err_sum += pid->err;//误差累计值 = 当前误差累计和
//使用PID控制 输出 = Kp*当前误差 + Ki*误差累计值 + Kd*(当前误差-上次误差)
pid->actual_val = pid->Kp*pid->err + pid->Ki*pid->err_sum + pid->Kd*(pid->err - pid->err_last);
//保存上次误差: 这次误差赋值给上次误差
pid->err_last = pid->err;
return pid->actual_val;
}
这里也不赘述,
5.上位机波形显示
#include "shangweiji.h"
#include "main.h"
#include "usart.h"
uint8_t data_to_send[100];
//閫氳繃F4甯у彂閫?涓猽int16绫诲瀷鐨勬暟鎹?
void ANO_DT_Send_F1(uint16_t _a, uint16_t _b, uint16_t _c, uint16_t _d)
{
uint8_t _cnt = 0; //璁℃暟鍊?
uint8_t sumcheck = 0; //鍜屾牎楠?
uint8_t addcheck = 0; //闄勫姞鍜屾牎楠?
uint8_t i = 0;
data_to_send[_cnt++] = 0xAA;//甯уご
data_to_send[_cnt++] = 0xFF;//鐩爣鍦板潃
data_to_send[_cnt++] = 0xF1;//鍔熻兘鐮?
data_to_send[_cnt++] = 8; //鏁版嵁闀垮害
//鍗曠墖鏈轰负灏忕妯″紡-浣庡湴鍧€瀛樻斁浣庝綅鏁版嵁锛屽尶鍚嶄笂浣嶆満瑕佹眰鍏堝彂浣庝綅鏁版嵁锛屾墍浠ュ厛鍙戜綆鍦板潃
data_to_send[_cnt++] = BYTE0(_a);
data_to_send[_cnt++] = BYTE1(_a);
data_to_send[_cnt++] = BYTE0(_b);
data_to_send[_cnt++] = BYTE1(_b);
data_to_send[_cnt++] = BYTE0(_c);
data_to_send[_cnt++] = BYTE1(_c);
data_to_send[_cnt++] = BYTE0(_d);
data_to_send[_cnt++] = BYTE1(_d);
for ( i = 0; i < data_to_send[3]+4; i++)
{
sumcheck += data_to_send[i];//鍜屾牎楠?
addcheck += sumcheck;//闄勫姞鏍¢獙
}
data_to_send[_cnt++] = sumcheck;
data_to_send[_cnt++] = addcheck;
HAL_UART_Transmit(&huart1,data_to_send,_cnt,0xFFFF);//杩欓噷鏄覆鍙e彂閫佸嚱鏁?
}
//通过f2发送四个无符号数
void ANO_DT_Send_F2(int16_t _a, int16_t _b, int16_t _c, int16_t _d) //F2帧 4个 int16 参数
{
uint8_t _cnt = 0;
uint8_t sumcheck = 0; //和校验
uint8_t addcheck = 0; //附加和校验
uint8_t i=0;
data_to_send[_cnt++] = 0xAA;
data_to_send[_cnt++] = 0xFF;
data_to_send[_cnt++] = 0xF2;
data_to_send[_cnt++] = 8; //数据长度
//单片机为小端模式-低地址存放低位数据,匿名上位机要求先发低位数据,所以先发低地址
data_to_send[_cnt++] = BYTE0(_a);
data_to_send[_cnt++] = BYTE1(_a);
data_to_send[_cnt++] = BYTE0(_b);
data_to_send[_cnt++] = BYTE1(_b);
data_to_send[_cnt++] = BYTE0(_c);
data_to_send[_cnt++] = BYTE1(_c);
data_to_send[_cnt++] = BYTE0(_d);
data_to_send[_cnt++] = BYTE1(_d);
for ( i = 0; i < data_to_send[3]+4; i++)
{
sumcheck += data_to_send[i];
addcheck += sumcheck;
}
data_to_send[_cnt++] = sumcheck;
data_to_send[_cnt++] = addcheck;
HAL_UART_Transmit(&huart1,data_to_send,_cnt,0xFFFF);//这里是串口发送函数
}
//通过F3帧发送2个int16类型和1个int32类型的数据
void ANO_DT_Send_F3(int16_t _a, int16_t _b, int32_t _c ) //F3帧 2个 int16 参数 1个 int32 参数
{
uint8_t _cnt = 0;
uint8_t sumcheck = 0; //和校验
uint8_t addcheck = 0; //附加和校验
uint8_t i=0;
data_to_send[_cnt++] = 0xAA;
data_to_send[_cnt++] = 0xFF;
data_to_send[_cnt++] = 0xF3;
data_to_send[_cnt++] = 8; //数据长度
//单片机为小端模式-低地址存放低位数据,匿名上位机要求先发低位数据,所以先发低地址
data_to_send[_cnt++] = BYTE0(_a);
data_to_send[_cnt++] = BYTE1(_a);
data_to_send[_cnt++] = BYTE0(_b);
data_to_send[_cnt++] = BYTE1(_b);
data_to_send[_cnt++] = BYTE0(_c);
data_to_send[_cnt++] = BYTE1(_c);
data_to_send[_cnt++] = BYTE2(_c);
data_to_send[_cnt++] = BYTE3(_c);
for ( i = 0; i < data_to_send[3]+4; i++)
{
sumcheck += data_to_send[i];
addcheck += sumcheck;
}
data_to_send[_cnt++] = sumcheck;
data_to_send[_cnt++] = addcheck;
HAL_UART_Transmit(&huart1,data_to_send,_cnt,0xFFFF);//这里是串口发送函数
}
这里的作用就是把当前速度,显示在匿名者上位机上。
我们打开匿名或者上位机v7版本
三、调试准备
最后就是需要用匿名者上位机调试pid参数了
1.首先打开软件 点击连接设置,选择自己对应的串口号:
2.点击数据协议,然后在右端这么设置:
3.点击数据波形,在左边这么设置:
四、实物图以及调试波形
五、演示视频
VID20240118121655
六、 工程所有源文件
链接:https://pan.baidu.com/s/1UbDIQxB3R923cD_Thn1s2Q?pwd=7777
提取码:7777
--来自百度网盘超级会员V4的分享
七、参考B站博主
详细教学可在小破站上博主 @好家伙VCC