DIY智能战车制作教程,开启你的造车之旅!

原标题:DIY智能战车制作教程,开启你的造车之旅!

No.1

前言

最近有幸参加了一期RT-Thread官方发起的RT-Robot Car DIY活动,跟着大神们的步伐我也成功的做出了一辆麦克纳姆轮PS2遥控车,心里非常的Happy,特意记录了这个制作过程用作给小白们借鉴。不多逼逼了,来开始我们造车之旅。

关于选材

本文由RT-Robot Car活动参与者SnowStorm战队队长吴鹏编写,共8698字,感谢他!

初次探索智能车本着节约成本和最低风险的原则,我们尽量选用现成的硬件材料。在探索成功后,学会了理解了其中的原理,再根据自己的需求完全设计自己的小车。(以下所有图片,点击即可查看大图

小车制作仓:https://github.com/bluesky-ryan/snowstorm_car

官方Robot-car连接:https://github.com/RT-Thread-packages/rt-robot

以上链接请复制至外部浏览器打开

关注公众号回复“入群”加入智能车DIY交流群

1、主控芯片:我们选用淘宝成品主控板(主控芯片STM32F103RCT6)(如下方左图所示)

2、底盘:麦克纳姆轮底座,某宝多的是自行选购 (如下方右图所示)

3、电机:买底座基本都带电机,我们选用带AB编码器的1:30减速电机 (如下方左图所示)

4、遥控:普通SONY PS2遥控,30-40块钱 (如下方右图所示)

5、电池:选用3S 11V航模电池(如下方左图所示)

6、线:USB转串口线一根 (如下方右图所示)

No.2

核心知识点

1.RT-Thread bsp移植。

2.STM32-CubeMXs使用。

3.RTOS使用。

4.PID控制理论。

5.麦克拉姆拉控制理论。

6.简单运动模型

开发环境

  • 使用Keil V5作为编译器
  • 使用 rt-thread 最新版本
  • 使用自己移植的 bsp

No.3

详细步骤

  • BSP移植
  • console串口:UART2
  • 系统LED灯:PD2
  • 电机控制
  • 主控板电机驱动芯片为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*/

8staticvoidmoto_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{

32Error_Handler;

33}

34sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

35if(HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)

36{

37Error_Handler;

38}

39if(HAL_TIM_PWM_Init(&htim1) != HAL_OK)

40{

41Error_Handler;

42}

43sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

44sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

45if(HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)

46{

47Error_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{

58Error_Handler;

59}

60if(HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)

61{

62Error_Handler;

63}

64if(HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)

65{

66Error_Handler;

67}

68if(HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)

69{

70Error_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{

81Error_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.

");

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*/

10voidmotor_pwm_set(motor_chx ch, int16_tspeed)

11{

12

13/* 反转 */

14if( 0> speed)

15{

16if(-MOTOR_PWM_MAX > speed)

17speed = -MOTOR_PWM_MAX;

18motor_pwm_control(ch, MOTOR_DIR_REVERSE, -speed);

19}

20/* 正转 */

21elseif( 0< speed)

22{

23if(MOTOR_PWM_MAX < speed)

24speed = MOTOR_PWM_MAX;

25motor_pwm_control(ch, MOTOR_DIR_FORWARD, speed);

26}

27/* 停止 */

28else

29{

30motor_pwm_control(ch, MOTOR_DIR_STOP, speed);

31}

32}

第三步:把主要函数加入Finsh控制台命令中,通过命令调试控制效果

1/* FINSH 调试函数 */

2#ifdefRT_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#ifdefFINSH_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.

");

第三步:加入Finsh调试函数,旋转轮子查看编码值是否准确。(输入 motor_test -ge实时查看编码器值)

1MSH_CMD_EXPORT(motor_test, motor_test -ge/-q);

1![ 1565968589135](https: //github.com/bluesky-ryan/snowstorm_car/blob/master/Image/motor_test)

  • PID
  • 有了编码器作为反馈器,有了PWM作为控制器,那我们就可以加入PID控制器了。加入PID控制器的目的是精确控制轮子的速度,提供轮子的控制达到一致。
  • PID原理知识自行百度网上资料一大把,个人理解是:
    • 比例Kp: 粗调,大幅度调节控制量让测量值逼近理论值,但是由于单位较大无法精确到达理论值,有响应快,调节尺度大的特点。
    • 微分Kd: 状态预测,Kd控制的是速度的斜率相当于预测下一步速度的趋势,可以加快调节速度。
    • 积分Ki:细调, 通过微小的积分累加,让测量值不断逼近理论值,细调控制量让测量值逼近理论值。
  • PID框图:
  • 比例Kp: 粗调,大幅度调节控制量让测量值逼近理论值,但是由于单位较大无法精确到达理论值,有响应快,调节尺度大的特点。
  • 微分Kd: 状态预测,Kd控制的是速度的斜率相当于预测下一步速度的趋势,可以加快调节速度。
  • 积分Ki:细调, 通过微小的积分累加,让测量值不断逼近理论值,细调控制量让测量值逼近理论值。

  • PID公式:

理论知识有了,按照公式做个具体实现就好了!

第一步:实现增量PID刷新公式,具体查看源码(pid.c)

1floatpid_update(pid_control_t* pid, floatmeasure_value)

2

第二步:将测量值输出到虚拟波形器软件上面便于观测各个值的当前情况,作者使用的是《山外多功能调试助手》,直接将数据输出到控制台串口。数据发送接口实现如下:

1/* 输出数据到虚拟波形软件 */

2rt_err_tsend_waveform_fomate( void*buf, uint32_tsize)

3{

4constcharstart[ 2] = { 0x03, 0xfc};

5constcharend[ 2] = { 0xfc, 0x03};

6rt_device_tconsole = rt_console_get_device;

7

8rt_device_write(console, -1, start, 2); //发送起始字符

9rt_device_write(console, -1, buf, size); //发送通道数据

10rt_device_write(console, -1, end, 2); //发送结束字符

11

12returnRT_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[i]); /* 根据速度设置PID参数 */

作者样车PID参数:

1#defineCHX_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 (链接:https://github.com/bluesky-ryan/snowstorm_car/blob/master/%E8%B5%84%E6%96%99%E6%96%87%E6%A1%A3/ps2%E8%A7%A3%E7%A0%81%E9%80%9A%E8%AE%AF%E6%89%8B%E5%86%8CV1.5.pdf)
  • PS2时序图:

PS2我们只需要读取遥控数据,一个扫描函数搞定,定期刷新一个按键值即可,具体代码参照(ps2.c):

1intps2_scan(ps2_ctrl_data_t*pt)

  • 遥控功能
  • 最后只剩遥控功能了,我们只需要将PS2遥控的当前值映射到对应控制值,再将对应控制值映射到轮子即可。
  • 车子方向控制图:

遥控映射到控制值:

1/* PS映射car cmd表格,组合键命令在头添加,单键命令放后面 */

2car_ps2_cmd_tps2_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_tcmd_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控制环节速度应尽量使用瞬时速度,也就是说在保证精度的情况下刷新时间要尽量的短。

彩蛋:之后还会有智能战车相关高级功能实现的教程连载,敬请期待!

END

RT-Thread线上活动

1、【RT-Thread能力认证考试12月——RCEA】经过第一次考试的验证,!如果您有晋升、求职、寻找更好机会的需要,有深入学习和掌握RT-Thread的需求,欢迎垂询/报考!

能力认证官网链接:https://www.rt-thread.org/page/rac.html(在外部浏览器打开)

立即报名

#题外话#喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread

你可以添加微信18917005679为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群

RT-Thread

让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

看这里,求赞!求转发!

点击进入Github返回搜狐,查看更多

责任编辑:

声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
免费获取
今日搜狐热点
今日推荐