1)主控芯片STM32F103C8T6
2)编码电机
型号MG513 p30 12v;转动一圈390个脉冲;减速比1比30。通过获取每100毫秒的脉冲个数,就可以计算出速度的大小
3)7针0.96寸oled显示屏
4)舵机:看着挺高级的,马力大,不过控制代码与那种两块钱的一样,模拟控制,PWM控制,用来控制方向
5)红外传感器模块:4个红外对管,一块模拟转数字模块
6)电源18650+电池座:经济实惠
7)锂电池充电器:一个模块加一个电池座,加起来不到2块钱
8)旋转编码器,用来调节时间的,按下小车就可以跑了
8)小车底盘
购买链接:https://m.tb.cn/h.4MEu0RC?sm=51a10c
其实编码电机与舵机小车上面都有,直接买一个小车车身。买来是散装的,装了好久装好:
02
原理图设计
首先就是要进行硬件设计,连接好了硬件,软件就好办了。
然后开始连接硬件。
连接好了硬件,写程序就可以调试了…
硬件与程序调试用了两天时间总算搞定了接下来开始测量数据。
03
数据测量
1)功耗测量:
设置全程1.5m走的时间:
2)OLED速度监测
显示屏数据说明:
V1、V2左右轮实时速度
PIDOUT:输出PWM值
time:定时间
speed:通过设定时间计算得到的速度,因为路程固定
running/stop运行状态
可以看到PID算法起到了良好的效果,很好的把速度恒定在指定范围之内
平地测量场景:
记录数据:
04
程序设计
1)主程序设计
主函数是执行了整个程序的入口点。
首先初始设置定时器参数,设置TIM1 16位定时器递增计数,设置定时器时间10MS,主要用来在定时器中断测量小车速度与PID算法调用。
配置TIM4为PWM模式,设置周期为30ms,298能够反应过来,周期太大小车会有抖动,通过脉宽来调节小车速度,脉宽越大,小车运动越快,设置计数器为1000向上计数,也就是重装载值设置1000小车最快,设置重装载值0小车不运动。
配置TIM3为PWM模式,根据舵机的特性,配置周期为30MS,向上计数模式,计数器设置为3000,当脉宽长度为28MS时,舵机刚好再0度位置,所以初始化重装载寄存器为2800,上电默认向前方向。
配置红外传感器4个输入引脚为输入模式,来检测输入的电平情况
配置旋钮编码器为3个引脚输入模式,用来检测是否旋转与按下
配置LED引脚为推挽输出模式,来驱动LED闪烁
配置串口与滴答定时器延时,串口主要用来调试,延时用来消耗某些时刻需要消耗的时间
循环:循环检测旋钮状态,如果旋转了就根据旋转的方向与角度设定时间的大小,如果旋钮按键按下,那么前进状态变成1
//主函数
int main(void)
{
u8 state;
int ang,i;
uart_init(9600);//蓝牙串口初始化
delay_init(); //延时初始化
SteerinMotor_Init(); //舵机初始化
EncodeMotor_Init();//编码电机初始化
OLED_Init();//oled初始化
LED_Init();//led初始化
TIM1_Int_Init(100,7200);//定时器初始化10ms
Encodeing_Init();//旋钮编码器初始化
//测试
printf(" this is Ramp patorl trolley by HUAZUOCHEN! ^_^ rn");
while (1) {
OLED_update();//更新OLED显示
encodeing_scan();//编码器扫描
//获取红外线传感器传回的数据
state = Get_Infrared_Sensor();
ang = 0;
switch (state)//循迹转弯设置
{
case 0x00:ang = 0;
break;
case 0x01:ang = -55;
break;
case 0x03:ang = -45;
break;
case 0x02:ang = -10;
break;
case 0x06:set_steerMotor_ang(0);//设置舵机角度 0
break;
case 0x04:ang = 10;
break;
case 0x0c:ang = 45;
break;
case 0x08:ang = 55;
break;
case 0x0f: //识别到杂乱信号角度设置为0
case 0x09:
case 0x05:
case 0x0a:
case 0x0e:
case 0x0b:
run_flag = 0;//停止
break;
default:ang = 0;
break;
}
if(ang != 0)
{
set_steerMotor_ang(ang);//设置舵机角度
delay_ms(100);
}
state = 0;
}
}
定时器1中断,10ms调用一次,调用时计数10次调用,也就是100ms进行一次速度测量,V = X/t ,速度等于位移处以时间,位移等一编码个数乘以编码单位长度,小车轮的直径为6.4厘米,周长为20.12cm,得到一个脉冲计数,大约是0.258毫米,得出左轮速度V_left = pause_cnt_left2.58;右轮速度V_right = pause_cnt_right2.58;当按下旋转编码器的按键run_flag = 1;使得PID算法开始运行,小车开始按照计算好的速度与当前测量到的速度做比较,进行闭环PID调节。
u16 time_cnt,run_cnt;
//100ms计算一次速度
void TIM1_UP_IRQHandler(void)
{
if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源
time_cnt ++;
if(time_cnt >= 10)
{
V_left = pause_cnt_left*2.58;
V_right = pause_cnt_right*2.58;
pause_cnt_left=0;
pause_cnt_right=0;
time_cnt =0;
LED = !LED;
}
if(run_flag)
{
speed = 1200/time; //总路程/时间
Set_Temp1 = Set_Temp = speed;
set_pid_speed();
run_cnt++;
if(run_cnt == time*200)
{
run_flag = 0;
run_cnt = 0;
}
}
else
{
PID_OUT = 0;
PID_OUT1= 0;
//run_cnt = 0;
car_go_forward(0,0);//停止
}
}
}
霍尔编码器脉冲获取采用中断方式,中断设置上升沿下降沿都触发,电机传动轴与编码器相连,编码器感应论上有13个感应线条,从而使得编码器齿轮转动一圈,或编码器能识别到13个脉冲。
减速箱使得小车轮子转动一圈,电机轴转动30圈,使得编码器齿轮也转动30圈,得到,小车轮子转动一圈,编码器输出13×30,等于390个脉冲,又因为一个脉冲有一个上升,沿有一个下降沿,单片机通过检测上升沿和下降沿,可以得到有780个计数。
double V_left,V_right;//定时器计算
u16 pause_cnt_left,pause_cnt_right;//脉冲计数
//外部中断2服务程序
void EXTI2_IRQHandler(void)
{
pause_cnt_left++;
EXTI_ClearITPendingBit(EXTI_Line2); //清除LINE2上的中断标志位
}
//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
pause_cnt_right++;
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}
速度平衡算法,给定一个速度,听过与编码电机反馈的速度进行比对,然后输出一个算出来的PWM值返回给电机调速,达成闭环速度调节。
void PID_calc(float V_1, float V_2) //PID算法
{
/***********************左轮**************************/
float Rate;//误差变化率
float Rate1;//误差变化率
Current_Error = Set_Temp - V_1;//当前误差
Sum_Error += Current_Error; //误差积分
Prev_Error = Last_Error;//存储误差积分
Last_Error = Current_Error;//存储误差分析
Rate = Current_Error - Last_Error; //变化速率计算
if (Rate > 10) //不让ta大于5也不让ta小于5
Rate = 10;
if (Rate < -10)
Rate = -10;
P_OUT = P * Gain * Current_Error; //比列项
I_OUT = I * Gain * Sum_Error; //积分项
//积分限幅处理
if (I_OUT > PID_I_MAX) I_OUT = PID_I_MAX; //不能超过最大值不能低于最小值
if (I_OUT < PID_I_MIN) I_OUT = PID_I_MIN;
//微分输出处理
D_OUT = D * Gain * Rate;
PID_OUT = P_OUT + I_OUT + D_OUT ;
if (PID_OUT >= V_DATA_MAX) PID_OUT = V_DATA_MAX;
if (PID_OUT <= V_DATA_MIN) PID_OUT = V_DATA_MIN;
/***********************右轮********************************/
Current_Error1 = Set_Temp1 - V_2;//当前误差
Sum_Error1 += Current_Error1; //误差积分
Prev_Error1 = Last_Error1;//存储误差积分
Last_Error1 = Current_Error1;//存储误差分析
Rate1 = Current_Error1 - Last_Error1; //变化速率计算
if (Rate1 > 10) //不让ta大于5也不让ta小于5
Rate1 = 10;
if (Rate1 < -10)
Rate1 = -10;
P_OUT1 = P * Gain1 * Current_Error1; //比列项
I_OUT1 = I * Gain1 * Sum_Error1; //积分项
//积分限幅处理
if (I_OUT1 > PID_I_MAX1) I_OUT1 = PID_I_MAX1; //不能超过最大值不能低于最小值
if (I_OUT1 < PID_I_MIN1) I_OUT1 = PID_I_MIN1;
//微分输出处理
D_OUT1 = D * Gain * Rate1;
PID_OUT1 = P_OUT1 + I_OUT1 + D_OUT1 ;
if (PID_OUT1 >= V_DATA_MAX1) PID_OUT1 = V_DATA_MAX1;
if (PID_OUT1 <= V_DATA_MIN1) PID_OUT1 = V_DATA_MIN1;
/*******************************************************/
}
获取红外传感器值,红外对管用一个发射管与一个接收管组成,红外发射管发射出去的红外线遇到白色物体会反射回红外接收管,使之导通,反之遇到黑色物体不反射,使之截止。接收到的红外线越强,导通电流越大,输出电压越大,输出的电压经过逻辑电路转换为数字信号传回到单片机进行识别处理。单片机引出4个引脚对应接收电平状态,4个引脚配置为输入模式,循环检测电平的变化,电平为1则对应红外对管识别到黑色,电平0对应红外对管识别到白色。
//获取传感器状态
u8 Get_Infrared_Sensor(void)
{
u8 state=0;
if(PBin(4) == 1)
state |= 0x08; //IN4
if(PBin(5) == 1)
state |= 0x04; //IN3
if(PBin(6) == 1)
state |= 0x02; //IN2
if(PBin(7) == 1)
state |= 0x01; //IN1
return state;
}