电机控制
主控板电机驱动芯片为TI的DIV8833芯片,一颗DIV8833芯片可以驱动两个电机,我们有4个电机用到了2个芯片。芯片采用对偶PWM方波输入驱动,频率手册没写,我们先使用10KHZ。
STM32F103RTC6高级定时器1、8都带有对偶PWM输出,我们的主控板用的高级定时器1,悲剧的是定时器1只能输出3路对偶PWM方波和1路普通PWM方波,可是我们有4个轮子,所以最后一个轮子的对偶极只能GPIO来代替控制了。
驱动逻辑表:
驱动资料有了逻辑也清晰了,我们接下要做的就只是按照要求输出几个PWM方波了
第一步:CubMX配置定时器1为PWM对偶模式
第二步:封装初始化、通道控制等电机控制接口(具体封装参照源码motor.c文件),最后给上层提供一个初始化接口,一个通道速度控制接口。(向????滑动查看全部)
1/**
2*@ingroup motor
3*
4*初始化定时器
5*@param none
6*@retrun none
7*/
8static void moto_pwm_init(void)
9{
10
11/* USER CODE BEGIN TIM1_Init 0 */
12
13/* USER CODE END TIM1_Init 0 */
14
15TIM_ClockConfigTypeDef sClockSourceConfig = {0};
16TIM_MasterConfigTypeDef sMasterConfig = {0};
17TIM_OC_InitTypeDef sConfigOC = {0};
18TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
19
20/* USER CODE BEGIN TIM1_Init 1 */
21
22/* USER CODE END TIM1_Init 1 */
23htim1.Instance = TIM1;
24htim1.Init.Prescaler = 71;
25htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
26htim1.Init.Period = MOTOR_PWM_MAX - 1;
27htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
28htim1.Init.RepetitionCounter = 0;
29htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
30if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
31{
32 Error_Handler();
33}
34sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
35if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
36{
37 Error_Handler();
38}
39if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
40{
41 Error_Handler();
42}
43sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
44sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
45if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
46{
47 Error_Handler();
48}
49sConfigOC.OCMode = TIM_OCMODE_PWM1;
50sConfigOC.Pulse = 0;
51sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
52sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
53sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
54sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
55sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
56if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
57{
58 Error_Handler();
59}
60if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
61{
62 Error_Handler();
63}
64if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
65{
66 Error_Handler();
67}
68if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
69{
70 Error_Handler();
71}
72sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
73sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
74sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
75sBreakDeadTimeConfig.DeadTime = 0;
76sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
77sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
78sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
79if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
80{
81 Error_Handler();
82}
83/* USER CODE BEGIN TIM1_Init 2 */
84
85/* USER CODE END TIM1_Init 2 */
86HAL_TIM_MspPostInit(&htim1);
87
88HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
89HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
90HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
91HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
92
93LOG_I(“motor pwm initialization ok.rn”);
94
95}
1/**
2*@ingroup motor
3*
4*控制电动机标量控制,正直表示正转,负值表示反转
5*
6*@param ch 控制通道MOTOR_CH1/TMOTOR_CH2/TMOTOR_CH3/MOTOR_CH4,可组合使用MOTOR_CH_1|MOTOR_CH_2
7*@param speed pwm控制量[-1000, 1000]
8*@retrun none
9*/
10void motor_pwm_set(motor_chx ch, int16_t speed)
11{
12
13/* 反转 */
14if (0 》 speed)
15{
16 if (-MOTOR_PWM_MAX 》 speed)
17 speed = -MOTOR_PWM_MAX;
18 motor_pwm_control(ch, MOTOR_DIR_REVERSE, -speed);
19}
20/* 正转 */
21else if (0 《 speed)
22{
23 if (MOTOR_PWM_MAX 《 speed)
24 speed = MOTOR_PWM_MAX;
25 motor_pwm_control(ch, MOTOR_DIR_FORWARD, speed);
26}
27/* 停止 */
28else
29{
30 motor_pwm_control(ch, MOTOR_DIR_STOP, speed);
31}
32}
第三步:把主要函数加入Finsh控制台命令中,通过命令调试控制效果
1/* FINSH 调试函数 */
2#ifdef RT_USING_FINSH
3#include 《finsh.h》
4FINSH_FUNCTION_EXPORT_ALIAS(motor_pwm_control, motor_control, channel direction speed);
5FINSH_FUNCTION_EXPORT_ALIAS(motor_pwm_set, motor_set, channel speed);
6
7/* FINSH 调试命令 */
8#ifdef FINSH_USING_MSH
9
10#endif /* FINSH_USING_MSH */
11#endif /* RT_USING_FINSH */
第四步:通过Finsh控制台调试命令测试电机通道和PWM控制量
编码器数据获取
电机测速我们使用520电机自带的AB相霍尔编码器,编码器线数为390,4倍线数后轮子转一圈收到:390*4=1560个脉冲。
stm32自带AB相霍尔解码器,一个通道需要消耗一个定时器。我们主控板电机2、3、4使用的timer 3/4/5硬件解码,电机1没有接定时器,坑爹啊,那只能用外部中断根据时序解码。
编码器时序:
资料有了思路也清晰了,接下来我们要做的只是初始化一下解码器,把实时编码数读出即可
第一步:CubeMx配置解码定时器和中断
第二步:编写初始化函数和编码器数据获取函数,电机1使用中断解码,电机2、3、4使用定时器解码。(具体代码参照github上面源码,这里不再累述)
1/* TIM init */
2moto_pwm_init();
3motor_encode2_init();
4motor_encode3_init();
5motor_encode4_init();
6motor_encode_enable();
7
8LOG_I(“motor initialization completed.rn”);
第三步:加入Finsh调试函数,旋转轮子查看编码值是否准确。(输入 motor_test -ge实时查看编码器值)
1MSH_CMD_EXPORT(motor_test, motor_test -ge/-q);
1![1565968589135]
PID
有了编码器作为反馈器,有了PWM作为控制器,那我们就可以加入PID控制器了。加入PID控制器的目的是精确控制轮子的速度,提供轮子的控制达到一致。
PID原理知识自行百度网上资料一大把,个人理解是:
比例Kp: 粗调,大幅度调节控制量让测量值逼近理论值,但是由于单位较大无法精确到达理论值,有响应快,调节尺度大的特点。
微分Kd: 状态预测,Kd控制的是速度的斜率相当于预测下一步速度的趋势,可以加快调节速度。
积分Ki:细调, 通过微小的积分累加,让测量值不断逼近理论值,细调控制量让测量值逼近理论值。
PID框图:
PID公式:
理论知识有了,按照公式做个具体实现就好了!
第一步:实现增量PID刷新公式,具体查看源码(pid.c)
1float pid_update(pid_control_t* pid, float measure_value)
2
第二步:将测量值输出到虚拟波形器软件上面便于观测各个值的当前情况,作者使用的是《山外多功能调试助手》,直接将数据输出到控制台串口。数据发送接口实现如下:
1/* 输出数据到虚拟波形软件 */
2rt_err_t send_waveform_fomate(void *buf, uint32_t size)
3{
4 const char start[2] = {0x03, 0xfc};
5 const char end[2] = {0xfc, 0x03};
6 rt_device_t console = rt_console_get_device();
7
8 rt_device_write(console, -1, start, 2); //发送起始字符
9 rt_device_write(console, -1, buf, size);//发送通道数据
10 rt_device_write(console, -1, end, 2); //发送结束字符
11
12 return RT_EOK;
13}
第三步:调节合适的速度刷新周期和PID刷新周期,周期不合适电机会剧烈抖动。作者设置周期为:
1p_car-》pid_sample_time = 20; /* PID刷新间隔ms */
2p_car-》vct_sample_time = 10; /* 速度刷新间隔ms */
第四步:调试合适的PID参数,由于作者选用电机一致性不好,所以设置参数时每个轮子正传和反转的PID参数都是独立的。具体实现查看代码:
1wheel_select_pid_kx(&p_car-》m_wheel); /* 根据速度设置PID参数 */
作者样车PID参数:
1#define CHX_PID_KX_TABLE
2{
3 {MOTOR_CH1, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
4 {MOTOR_CH2, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
5 {MOTOR_CH3, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
6 {MOTOR_CH4, {0.765, 0.330, 0.100}, {0.260, 0.200, 0.010}},
7}
PS2遥控器
PS2 由手柄与接收器两部分组成,手柄主要负责发送按键信息。都接通电源并打开手柄开关时,手柄与接收器自动配对连接,在未配对成功的状态下,接收器绿灯闪烁,手柄上的灯也会闪烁,配对成功后,接收器上绿灯常亮。
PS2遥控有两个模式一个红灯模式、绿灯模式,区别就是红灯模式遥控输出的是模拟值,绿灯输出的只有最大值。我们输出固定速度选用绿灯模式。
PS2传输协议有点像SPI,不同的是PS2每次传输数据帧都是9个字节,里面包含了各个按键的当前值。
PS2更多详细信息查看:ps2解码通讯手册V1.5.pdf
PS2时序图:
PS2我们只需要读取遥控数据,一个扫描函数搞定,定期刷新一个按键值即可,具体代码参照(ps2.c):
1int ps2_scan(ps2_ctrl_data_t *pt)
遥控功能
最后只剩遥控功能了,我们只需要将PS2遥控的当前值映射到对应控制值,再将对应控制值映射到轮子即可。
车子方向控制图:
遥控映射到控制值:
1/* PS映射car cmd表格,组合键命令在头添加,单键命令放后面 */
2car_ps2_cmd_t ps2_to_cmd_table[] = {
3 {PS2_BTN_RIGHT| PS2_BTN_UP, CAR_CMD_FORWARD_RIGHT},
4 {PS2_BTN_LEFT | PS2_BTN_UP, CAR_CMD_FORWARD_LEFT},
5 {PS2_BTN_RIGHT| PS2_BTN_DOWN, CAR_CMD_BACK_RIGHT},
6 {PS2_BTN_LEFT | PS2_BTN_DOWN, CAR_CMD_BACK_LEFT},
7 {PS2_BTN_UP, CAR_CMD_FORWARD},
8 {PS2_BTN_DOWN, CAR_CMD_BACK},
9 {PS2_BTN_RIGHT, CAR_CMD_RIGHT},
10 {PS2_BTN_LEFT, CAR_CMD_LEFT},
11 {PS2_BTN_CICLE, CAR_CMD_TURN_RIGHT},
12 {PS2_BTN_SQUARE, CAR_CMD_TURN_LEFT},
13};
控制映射到4个轮子的具体速度值:
1/* 命令映射到几何控制参数 */
2car_cmd_math_t cmd_to_math_table[] = {
3{CAR_CMD_INVALID, { 0, 0, 0, 0}},
4{CAR_CMD_STOP, { 0, 0, 0, 0}},
5{CAR_CMD_FORWARD_LEFT, { 0, 120, 120, 0}},
6{CAR_CMD_FORWARD_RIGHT, { 120, 0, 0, 120}},
7{CAR_CMD_BACK_LEFT, {-120, 0, 0, -120}},
8{CAR_CMD_BACK_RIGHT, { 0, -120, -120, 0}},
9{CAR_CMD_FORWARD, { 120, 120, 120, 120}},
10{CAR_CMD_BACK, {-120, -120, -120, -120}},
11{CAR_CMD_RIGHT, { 120, -120, -120, 120}},
12{CAR_CMD_LEFT, {-120, 120, 120, -120}},
13{CAR_CMD_TURN_RIGHT, { 120, -120, 120, -120}},
14{CAR_CMD_TURN_LEFT, {-120, 120, -120, 120}},
15};
再开一个线程定期刷新各个轮子的控制即可。
No.4
视频预览
No.5
经验总结
电机控制电路设计上应该与控制板完全隔离,比如光耦隔离器件,避免电流压降造成主控不稳定。
主控板需要有较强的抗大电流和抗干扰性,一块好的主板事半功倍,主动不稳定容易出现未知问题很难定位。
PDI控制环节速度应尽量使用瞬时速度,也就是说在保证精度的情况下刷新时间要尽量的短。
电机控制
主控板电机驱动芯片为TI的DIV8833芯片,一颗DIV8833芯片可以驱动两个电机,我们有4个电机用到了2个芯片。芯片采用对偶PWM方波输入驱动,频率手册没写,我们先使用10KHZ。
STM32F103RTC6高级定时器1、8都带有对偶PWM输出,我们的主控板用的高级定时器1,悲剧的是定时器1只能输出3路对偶PWM方波和1路普通PWM方波,可是我们有4个轮子,所以最后一个轮子的对偶极只能GPIO来代替控制了。
驱动逻辑表:
驱动资料有了逻辑也清晰了,我们接下要做的就只是按照要求输出几个PWM方波了
第一步:CubMX配置定时器1为PWM对偶模式
第二步:封装初始化、通道控制等电机控制接口(具体封装参照源码motor.c文件),最后给上层提供一个初始化接口,一个通道速度控制接口。(向????滑动查看全部)
1/**
2*@ingroup motor
3*
4*初始化定时器
5*@param none
6*@retrun none
7*/
8static void moto_pwm_init(void)
9{
10
11/* USER CODE BEGIN TIM1_Init 0 */
12
13/* USER CODE END TIM1_Init 0 */
14
15TIM_ClockConfigTypeDef sClockSourceConfig = {0};
16TIM_MasterConfigTypeDef sMasterConfig = {0};
17TIM_OC_InitTypeDef sConfigOC = {0};
18TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
19
20/* USER CODE BEGIN TIM1_Init 1 */
21
22/* USER CODE END TIM1_Init 1 */
23htim1.Instance = TIM1;
24htim1.Init.Prescaler = 71;
25htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
26htim1.Init.Period = MOTOR_PWM_MAX - 1;
27htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
28htim1.Init.RepetitionCounter = 0;
29htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
30if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
31{
32 Error_Handler();
33}
34sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
35if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
36{
37 Error_Handler();
38}
39if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
40{
41 Error_Handler();
42}
43sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
44sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
45if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
46{
47 Error_Handler();
48}
49sConfigOC.OCMode = TIM_OCMODE_PWM1;
50sConfigOC.Pulse = 0;
51sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
52sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
53sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
54sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
55sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
56if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
57{
58 Error_Handler();
59}
60if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
61{
62 Error_Handler();
63}
64if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
65{
66 Error_Handler();
67}
68if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
69{
70 Error_Handler();
71}
72sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
73sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
74sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
75sBreakDeadTimeConfig.DeadTime = 0;
76sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
77sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
78sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
79if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
80{
81 Error_Handler();
82}
83/* USER CODE BEGIN TIM1_Init 2 */
84
85/* USER CODE END TIM1_Init 2 */
86HAL_TIM_MspPostInit(&htim1);
87
88HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
89HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
90HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
91HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
92
93LOG_I(“motor pwm initialization ok.rn”);
94
95}
1/**
2*@ingroup motor
3*
4*控制电动机标量控制,正直表示正转,负值表示反转
5*
6*@param ch 控制通道MOTOR_CH1/TMOTOR_CH2/TMOTOR_CH3/MOTOR_CH4,可组合使用MOTOR_CH_1|MOTOR_CH_2
7*@param speed pwm控制量[-1000, 1000]
8*@retrun none
9*/
10void motor_pwm_set(motor_chx ch, int16_t speed)
11{
12
13/* 反转 */
14if (0 》 speed)
15{
16 if (-MOTOR_PWM_MAX 》 speed)
17 speed = -MOTOR_PWM_MAX;
18 motor_pwm_control(ch, MOTOR_DIR_REVERSE, -speed);
19}
20/* 正转 */
21else if (0 《 speed)
22{
23 if (MOTOR_PWM_MAX 《 speed)
24 speed = MOTOR_PWM_MAX;
25 motor_pwm_control(ch, MOTOR_DIR_FORWARD, speed);
26}
27/* 停止 */
28else
29{
30 motor_pwm_control(ch, MOTOR_DIR_STOP, speed);
31}
32}
第三步:把主要函数加入Finsh控制台命令中,通过命令调试控制效果
1/* FINSH 调试函数 */
2#ifdef RT_USING_FINSH
3#include 《finsh.h》
4FINSH_FUNCTION_EXPORT_ALIAS(motor_pwm_control, motor_control, channel direction speed);
5FINSH_FUNCTION_EXPORT_ALIAS(motor_pwm_set, motor_set, channel speed);
6
7/* FINSH 调试命令 */
8#ifdef FINSH_USING_MSH
9
10#endif /* FINSH_USING_MSH */
11#endif /* RT_USING_FINSH */
第四步:通过Finsh控制台调试命令测试电机通道和PWM控制量
编码器数据获取
电机测速我们使用520电机自带的AB相霍尔编码器,编码器线数为390,4倍线数后轮子转一圈收到:390*4=1560个脉冲。
stm32自带AB相霍尔解码器,一个通道需要消耗一个定时器。我们主控板电机2、3、4使用的timer 3/4/5硬件解码,电机1没有接定时器,坑爹啊,那只能用外部中断根据时序解码。
编码器时序:
资料有了思路也清晰了,接下来我们要做的只是初始化一下解码器,把实时编码数读出即可
第一步:CubeMx配置解码定时器和中断
第二步:编写初始化函数和编码器数据获取函数,电机1使用中断解码,电机2、3、4使用定时器解码。(具体代码参照github上面源码,这里不再累述)
1/* TIM init */
2moto_pwm_init();
3motor_encode2_init();
4motor_encode3_init();
5motor_encode4_init();
6motor_encode_enable();
7
8LOG_I(“motor initialization completed.rn”);
第三步:加入Finsh调试函数,旋转轮子查看编码值是否准确。(输入 motor_test -ge实时查看编码器值)
1MSH_CMD_EXPORT(motor_test, motor_test -ge/-q);
1![1565968589135]
PID
有了编码器作为反馈器,有了PWM作为控制器,那我们就可以加入PID控制器了。加入PID控制器的目的是精确控制轮子的速度,提供轮子的控制达到一致。
PID原理知识自行百度网上资料一大把,个人理解是:
比例Kp: 粗调,大幅度调节控制量让测量值逼近理论值,但是由于单位较大无法精确到达理论值,有响应快,调节尺度大的特点。
微分Kd: 状态预测,Kd控制的是速度的斜率相当于预测下一步速度的趋势,可以加快调节速度。
积分Ki:细调, 通过微小的积分累加,让测量值不断逼近理论值,细调控制量让测量值逼近理论值。
PID框图:
PID公式:
理论知识有了,按照公式做个具体实现就好了!
第一步:实现增量PID刷新公式,具体查看源码(pid.c)
1float pid_update(pid_control_t* pid, float measure_value)
2
第二步:将测量值输出到虚拟波形器软件上面便于观测各个值的当前情况,作者使用的是《山外多功能调试助手》,直接将数据输出到控制台串口。数据发送接口实现如下:
1/* 输出数据到虚拟波形软件 */
2rt_err_t send_waveform_fomate(void *buf, uint32_t size)
3{
4 const char start[2] = {0x03, 0xfc};
5 const char end[2] = {0xfc, 0x03};
6 rt_device_t console = rt_console_get_device();
7
8 rt_device_write(console, -1, start, 2); //发送起始字符
9 rt_device_write(console, -1, buf, size);//发送通道数据
10 rt_device_write(console, -1, end, 2); //发送结束字符
11
12 return RT_EOK;
13}
第三步:调节合适的速度刷新周期和PID刷新周期,周期不合适电机会剧烈抖动。作者设置周期为:
1p_car-》pid_sample_time = 20; /* PID刷新间隔ms */
2p_car-》vct_sample_time = 10; /* 速度刷新间隔ms */
第四步:调试合适的PID参数,由于作者选用电机一致性不好,所以设置参数时每个轮子正传和反转的PID参数都是独立的。具体实现查看代码:
1wheel_select_pid_kx(&p_car-》m_wheel); /* 根据速度设置PID参数 */
作者样车PID参数:
1#define CHX_PID_KX_TABLE
2{
3 {MOTOR_CH1, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
4 {MOTOR_CH2, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
5 {MOTOR_CH3, {1.100, 0.400, 0.500}, {1.100, 0.400, 0.500}},
6 {MOTOR_CH4, {0.765, 0.330, 0.100}, {0.260, 0.200, 0.010}},
7}
PS2遥控器
PS2 由手柄与接收器两部分组成,手柄主要负责发送按键信息。都接通电源并打开手柄开关时,手柄与接收器自动配对连接,在未配对成功的状态下,接收器绿灯闪烁,手柄上的灯也会闪烁,配对成功后,接收器上绿灯常亮。
PS2遥控有两个模式一个红灯模式、绿灯模式,区别就是红灯模式遥控输出的是模拟值,绿灯输出的只有最大值。我们输出固定速度选用绿灯模式。
PS2传输协议有点像SPI,不同的是PS2每次传输数据帧都是9个字节,里面包含了各个按键的当前值。
PS2更多详细信息查看:ps2解码通讯手册V1.5.pdf
PS2时序图:
PS2我们只需要读取遥控数据,一个扫描函数搞定,定期刷新一个按键值即可,具体代码参照(ps2.c):
1int ps2_scan(ps2_ctrl_data_t *pt)
遥控功能
最后只剩遥控功能了,我们只需要将PS2遥控的当前值映射到对应控制值,再将对应控制值映射到轮子即可。
车子方向控制图:
遥控映射到控制值:
1/* PS映射car cmd表格,组合键命令在头添加,单键命令放后面 */
2car_ps2_cmd_t ps2_to_cmd_table[] = {
3 {PS2_BTN_RIGHT| PS2_BTN_UP, CAR_CMD_FORWARD_RIGHT},
4 {PS2_BTN_LEFT | PS2_BTN_UP, CAR_CMD_FORWARD_LEFT},
5 {PS2_BTN_RIGHT| PS2_BTN_DOWN, CAR_CMD_BACK_RIGHT},
6 {PS2_BTN_LEFT | PS2_BTN_DOWN, CAR_CMD_BACK_LEFT},
7 {PS2_BTN_UP, CAR_CMD_FORWARD},
8 {PS2_BTN_DOWN, CAR_CMD_BACK},
9 {PS2_BTN_RIGHT, CAR_CMD_RIGHT},
10 {PS2_BTN_LEFT, CAR_CMD_LEFT},
11 {PS2_BTN_CICLE, CAR_CMD_TURN_RIGHT},
12 {PS2_BTN_SQUARE, CAR_CMD_TURN_LEFT},
13};
控制映射到4个轮子的具体速度值:
1/* 命令映射到几何控制参数 */
2car_cmd_math_t cmd_to_math_table[] = {
3{CAR_CMD_INVALID, { 0, 0, 0, 0}},
4{CAR_CMD_STOP, { 0, 0, 0, 0}},
5{CAR_CMD_FORWARD_LEFT, { 0, 120, 120, 0}},
6{CAR_CMD_FORWARD_RIGHT, { 120, 0, 0, 120}},
7{CAR_CMD_BACK_LEFT, {-120, 0, 0, -120}},
8{CAR_CMD_BACK_RIGHT, { 0, -120, -120, 0}},
9{CAR_CMD_FORWARD, { 120, 120, 120, 120}},
10{CAR_CMD_BACK, {-120, -120, -120, -120}},
11{CAR_CMD_RIGHT, { 120, -120, -120, 120}},
12{CAR_CMD_LEFT, {-120, 120, 120, -120}},
13{CAR_CMD_TURN_RIGHT, { 120, -120, 120, -120}},
14{CAR_CMD_TURN_LEFT, {-120, 120, -120, 120}},
15};
再开一个线程定期刷新各个轮子的控制即可。
No.4
视频预览
No.5
经验总结
电机控制电路设计上应该与控制板完全隔离,比如光耦隔离器件,避免电流压降造成主控不稳定。
主控板需要有较强的抗大电流和抗干扰性,一块好的主板事半功倍,主动不稳定容易出现未知问题很难定位。
PDI控制环节速度应尽量使用瞬时速度,也就是说在保证精度的情况下刷新时间要尽量的短。
举报