参考文章

LED 灯带效果发生器

PWM+DMA驱动灯带

STM32使用定时器实现微秒(us)级延时

颜色查看

注:因为原理图跟V1还是差不多的,所以此处就不详细记录基本的东西了,只记一下F1跟F4之间的移植遇到的问题等等,工程文件会上传到Github

框图

焊接

一开始焊完DCDC那上电后电解电容和DCDC芯片严重发热,后面我换了板子一个个元器件焊,最后焊完也是正常的,输出4.95V,可能是因为加热台不知道哪里虚焊或者短路了吧

焊完下载程序半天没效果,原因是拨码开关焊反了,导致地变高电平,正常是1,2丝印朝下,NO在上的,解决方法是把拨码开关全部打上面去,或者拆下来重新焊正

DAPLINK那个下载座不能直接插那个下载器,暂时找不到原因,只能杜邦线接(原因是RST引脚也接了,不要接RST就行)

ASR正常,但是一开始不知道为啥不行8002A一直发烫严重,最后尝试换一块板子焊,然后就正常可以使用了,可能还是哪里虚焊或者啥的吧,但是我都看了也没看到哪里,然后ASR的PA4引脚需要高电平使能才能下载程序,一开始不知道悬空了,所以硬件上只能通过飞线到3.3V了,所以这个教训告诉我要认真看数据手册,不要粗心大意略过

轮子的话一开始我是万向轮在前,两路电机在后的,但是后面调转向环在跑图时发现速度快点或者万向轮卡在地砖的缝的话会导致翻车因为阻力关系后轮速度一直匀速运动所以很危险,后面我尝试了改成前驱,万向轮放后面,电机带动它效果很好,速度上来了或者后轮卡缝里也丝毫不影响正常寻迹

资源分配

  • 引脚分配
引脚 引脚功能 用作
A7 TIM3_CH2 E_R_B
A6 TIM3_CH1 E_R_A
A5 TIM2_CH1 E_L_A
B3 TIM2_CH2 E_L_B
A4 ADC1_IN4 Power_ADC
B1 ADC1_IN9 Fire_ADC
B0 ADC1_IN8 MQ2_ADC
E2 上拉输入 L3
E3 上拉输入 L2
E4 上拉输入 L1
E5 上拉输入 M
E6 上拉输入 R1
C13 上拉输入 R2
C14 上拉输入 R3
C15 推挽输出 Buzzer
D0 推挽输出 L_AIN1
D1 推挽输出 L_AIN2
A1 TIM5_CH2 L_PWMA
A2 TIM5_CH3 R_PWMB
D2 推挽输出 R_BIN1
D3 推挽输出 R_BIN2
D4 开漏输出 MPU6050_SCL
D7 开漏输出 MPU6050_SDA
C5 开漏输出 ESP01S_RST
D5 USART2_TX BT_ESP01_TX
D6 USART2_RX BT_ESP01_RX
C6 USART6_TX ASR_TX
C7 USART6_RX ASR_RX
B10 USART3_TX openMV_TX
B11 USART3_RX openMV_RX
B8 开漏输出 SHT30_SCL
B9 开漏输出 SHT30_SDA
B12 开漏输出 VL53_SCL
B13 开漏输出 VL53_SDA
E0 上拉输入 K1
E1 上拉输入 K2
D12 TIM4_CH1 WS2812B
B15 推挽输出 LED_1
B14 推挽输出 LED_2
A3 TIM5_CH4 C_Motor
A0 推挽输出 LED_sys
A9 USART1_TX HMI_TX
A10 USART1_RX HMI_RX
C0 开漏输出 EEPROM_SCL
C1 开漏输出 EEPROM_SDA
B6 推挽输出 OLED_SCL
B7 推挽输出 OLED_SDA
  • 其他分配
名称 用途
TIM9 串口2WiFI接收中断计数用
TIM8 中断里MPU6050,编码器读取10ms
TIM12 裸机任务调度1ms
TIM13 us延时不需要中断,设置频率最大arr最大值
TIM10 按键检测,LED翻转等1ms
  • 中断分组

每一版都用,隔开

中断 主优先级 子优先级
DMA1 stream0(WS2812B) 3 0
TIM9(串口2WiFI接收中断计数用) 2 0
TIM2(左电机编码器) 3 0
TIM3(右电机编码器) 3 0
USART1 2 0
USART2 2 0
USART3 2 0
USART6 2 0
TIM12(裸机任务调度) 0 0
TIM8(MPU6050,编码器读取) 1 0
TIM10(其他外设计数需要) 1 0

主要模块

WS2812B

  • 数据手册

每一个灯的数据是24位的,高到低是 GRB

  • 发0码

0码的周期大概是假设高电平时间是 0.4us,低电平时间是 0.85us ,那周期就是 1.25us ,那发0的话高电平时间的占空比就是

0.41.25=0.32=32%\frac{0.4}{1.25}=0.32=32\%

那低电平的占空比就是 68%了,也就是 2/3是低电平,1/3是高电平

  • 发1码

跟发0码相反即可,就是高电平时间占空比是 68%,低电平时间占空比是 32%,也就是 2/3是高电平,1/3是低电平

因为我这里ARR设置是150,所以只需要计算 105的 1/3 和 2/3 即可

发送的话是一组一起发送的,就是灯有多少个就发多少

需要设置频率为800KHz

窗口看门狗

计算:设置了 8分频,窗口值是 100,重装载值是 127,APB1时钟频率是 42MHz

超时时间是:

(1/42000000)×4096×8×(63+1)us=49.93ms(1/42000000)\times4096\times8\times(63+1)us=49.93ms

中断优先级需要设置为最高,避免喂狗被打断

  • MX

ADC

电池的话充满是12.6V,低于9V就要充电了

MPU6050

陀螺仪有零漂,我采集100次计算平均值,后面减去零漂,得出的角速度会比较稳

不使用DMP库的读取数据函数,但是初始化还是需要的,要自检

10ms定时器读取角速度

us延时

MX在上面设置了,时钟分频看你是多少,72MHz就设置为72-1

cpp
/*
* @function: Public_Delay_us
* @param: us -> 需要延时的时间(us)
* @retval: None
* @brief: 系统us延时
*/
static void Public_Delay_us(uint16_t us)
{
	// Set timer period for desired delay in microseconds
	__HAL_TIM_SET_AUTORELOAD(&htim13, us - 1);//定时器响应时间为period*定时器频率
	HAL_TIM_Base_Start(&htim13);//start the timer
	//通过轮询的方式等待定时器的更新事件
	//当定时器溢出并计数器更新时,TIM_FLAG_UPDATE标志会被置位。
	while(__HAL_TIM_GET_FLAG(&htim13,TIM_FLAG_UPDATE)==RESET);
	__HAL_TIM_CLEAR_FLAG(&htim13,TIM_FLAG_UPDATE);//清楚更新标志位
	HAL_TIM_Base_Stop(&htim13);//Stop the timer
}

ESP01S

接收阿里云的消息的话需要把云产品流转停止了,因为之前开了跟APP连通

蓝牙

蓝牙模块一开始是HC-05,但是不知道什么原因后面坏了不能通信,所以换成BT04

  • 参数

默认调试波特率是9600,AT指令跟HC05也不一样,密码默认1234

进入AT模式不需要按着按键上电,直接插就行了,500ms间隔闪烁表示未连接,常亮表示已连接,按键是断开连接作用

设置波特率的话是用 AT+BAUDx 指令,x的话 (1~7),其中7是115200,3是9600,可能有的模块是(1~8)看手册反正,修改完断电重新插才生效,AT模式下的波特率跟通信的是同一个

电机PID调试

调试时启动的话按键启动两个电机就行了不需要给基础速度,给目标值就会跑

调试速度环

首先是在PID.h里面把速度环宏定义打开,其他3个环关闭

cpp
#define USE_Speed_Ring	1

硬件参数初始化函数里面把寻迹开关关闭

cpp
Motor_Ctrl.Track_Switch_Flag = FALSE;

参数上传的话,上传速度实际值即可(屏蔽一个轮子测另一个轮子那样)

cpp
MyHC05.BT_Upload_ActualValue((int)Motors[lb].Speed, CURVES_CH1);	// 上传实际值--左轮速度
MyHC05.BT_Upload_ActualValue((int)Motors[rb].Speed, CURVES_CH1);	// 上传实际值--右轮速度

目标值的话是基础速度就行

cpp
Motor_Ctrl.base_speed = actual_temp;
  • Ki超调现象

  • 加Kp抑制超调现象

  • 最终

  • 总结

先给积分,积分出现超调,先高后低,这两个幅度相比差不多4:1就可以调比例,调比例就是尽可能把超调抑制,速度环就是要响应速度快和快速稳定,出现基本成矩形就很好了。速度环给不给微分都不影响,单单使用积分+比例就很好了。速度环是内环,切记一定要稳,不然影响外面的环

转向环

首先是在PID.h里面把转向环宏定义打开,其他3个环关闭

cpp
#define USE_Turn_Ring	1

硬件参数初始化函数里面把寻迹开关打开,寻迹模式需要设置为左或者右

cpp
TrackMode_Flag.track_Mode = LEFT_FIRST;
Motor_Ctrl.Track_Switch_Flag = TRUE;

参数上传的话,上传灰度误差实际值即可

cpp
MyHC05.BT_Upload_ActualValue((int)Track.Track_Error, CURVES_CH1);	// 上传实际值--灰度err

目标值也不需要给,默认就是目标值是误差为0

首先测一下寻迹灯顺序对不对跟误差值,正常是有一个阶梯状的,从左到右

注意寻迹灯顺序,还有误差值(寻迹灯读取函数里面是方向 L3-L2-L1-M-R1-R2-R3,误差对应是 3,2,1,0,-1,-2,-3

还有车的结构,转向环PID的 max_out 不要设置得太大,最好差不多就行了可能是因为我车底板是10x10有点挤

  • 总结

转向环就给个寻迹地图给他跑,它在地图上跑的又快又稳就ok了,先给一个比较慢的速度,调一下转向环的参数,然后就开始调整基础速度,因为差速跟基础速度在1.2米以下的时候还是成正比的,所以我差速这个地方是乘上基础速度的:

cpp
// 1. 转向环PID结构体 2. 灰度实际误差 3. 灰度期望误差(0)
PidTarget.DifferentialSpeed_target = -PID_Ops.PID_Calc(&pid_Turn, Track.Track_Error, 0) * (Motor_Ctrl.base_speed) * 0.04;

这个0.04的参数也是试出来的,调好PID后把基础速度加上去,调整这个比例,让它跑的又快又好就行。转向环其实单单用比例也能很好的,我不用积分,用积分的话震荡比较厉害,所以我是先调比例,比例太大时如果速度上来了也会出现抖动现象,给个微分抑制一下抖动

角速度环

首先是在PID.h里面把角速度环宏定义打开,其他3个环关闭

cpp
#define USE_Angle_Ring	1

硬件参数初始化函数里面把寻迹开关关闭,打开转向环开关

cpp
Motor_Ctrl.TurnRing_Switch_Flag = TRUE;	// 打开转向环
Motor_Ctrl.Track_Switch_Flag = FALSE;	// 关闭寻迹

参数上传的话,上传实际角速度

cpp
MyHC05.BT_Upload_ActualValue((int)myMPU6050.gyz_RealAngleSpeed, CURVES_CH1);	// 上传实际值--角速度

目标值是目标角速度

cpp
PidTarget.AnaleSpeedRing_target = actual_temp;	// 目标角速度

然后在中断里面需要屏蔽角度环只需要角速度环:

cpp
#if	USE_AngleSpeed_Ring
// 1.角速度环PID结构体 2. 期望角速度(即角度环的输出) 3. 实际角速度
Motor_Ctrl.Set_Turn_Speed(Motor_Ctrl.AngleSpeed_PID_Realize(&pid_AnaleSpeed, -PidTarget.AnaleSpeedRing_target, (myMPU6050.gyro_z - bias) * 2000 / 32768));
#else
// 1. 角度环PID结构体 2. 期望角度 3. 实际角度(偏航角)
PidTarget.AnaleSpeedRing_target = Motor_Ctrl.Angle_PID_Realize(&pid_Angle, PidTarget.AnaleRing_target, myMPU6050.gyz_integral);
// 1.角速度环PID结构体 2. 期望角速度(即角度环的输出) 3. 实际角速度
Motor_Ctrl.Set_Turn_Speed(Motor_Ctrl.AngleSpeed_PID_Realize(&pid_AnaleSpeed, -PidTarget.AnaleSpeedRing_target, myMPU6050.gyz_RealAngleSpeed));
#endif

首先测一下角速度正不正常,静止时是0,然后分别左转和右转,然后注意正负问题,如果在上位机发送正值但是实际值是负数的话就需要去程序里改,要发送跟实际一样,我改的是这里,把角速度目标值加个负号:

cpp
// 1.角速度环PID结构体 2. 期望角速度(即角度环的输出) 3. 实际角速度
Motor_Ctrl.Set_Turn_Speed(Motor_Ctrl.AngleSpeed_PID_Realize(&pid_AnaleSpeed, -PidTarget.AnaleSpeedRing_target, (myMPU6050.gyro_z - bias) * 2000 / 32768));

  • 总结

跟速度环类似,让他原地转圈圈,首先调的是比例,慢慢加从0.01开始,积分这里我没用(因为我一加积分就轮子一直加速),微分也先不给,调到比例某个值时如果误差变大了就不要再加了开始减小,直到一个稳定的值,然后慢慢加微分,也是从0.01开始,差不多就可以了

角度环

首先是在PID.h里面把角度环宏定义打开,其他3个环关闭

cpp
#define USE_AngleSpeed_Ring	1

硬件参数初始化函数里面把寻迹开关关闭,打开转向环开关

cpp
Motor_Ctrl.TurnRing_Switch_Flag = TRUE;
Motor_Ctrl.Track_Switch_Flag = FALSE;

参数上传的话,上传实际偏航角

cpp
MyHC05.BT_Upload_ActualValue((int)myMPU6050.gyz_integral, CURVES_CH1);	// 上传实际值--偏航角

目标值是目标角度

cpp
PidTarget.AnaleRing_target = actual_temp;	// 目标角度

中断里面也用到角速度和角度环

  • 总结

角速度环调稳定后就可以调整角度环,角度环也是用比例就差不多了,再加点微分也可以,我是直接用微分的

移植RTOS

见另一篇文章

问题

下载程序的话这样设置

F4的ADC校准函数应该是取消了,故不需要校准

结构体有个问题就是结构体本身里面的函数指针如果参数是自己的类型的话会报警告,解决方法是在typedef struct后面加一个结构体名称然后形参也需要加struct像这样:

cpp
// 这个是报警告的
typedef struct
{
    void (*Set)(AA *a);
}AA;

// 这个是没有警告的
typedef struct AA
{
    void (*set)(struct AA *a);
}AA;

因为画原理图分配引脚时忘记考虑定时器的ARR最大范围问题,所以左轮的编码器定时器最大值是0xFFFFFFFF,在程序里的话还是使用-65536,试了换成-0xFFFFFFFF,但是效果好像差点,MX的话还是设置最大值

注意数据类型,尤其是在循环里,比如for循环里的i如果是递减的话最好把i定义为int8_t不要定义成uint8_t,否则会导致程序莫名其妙的Bug