本文由 EMakeFun-軍弟 編寫.
隊伍名
項目名Github 地址鯤鵬戰隊鯤鵬戰車https://github.com/Eronwu/roc_robot以下是RT-Thread DIY 智能車賽1000元優勝獎得主——鯤鵬戰隊的作品分享!
附上完整作品視頻:
記得小時候大約10歲的時候那個時候家裡窮沒有玩具玩,某天發現老爸以前礦山裡挖煤留下的工具箱裡有幾個嶄新的軸承,如獲至寶,趕緊找來鋸子和木板製作了人生第一臺車,大概效果如下
當時帶著自製小車,在村口玩的時候,在小夥伴們裡的風靡程度,不亞於現在一臺跑車的回頭率
就這樣這輛小車陪伴我過了美好的童年,不過我永遠不會忘記當年山坡翻車那個場景,如今我手背那塊傷疤就是當年的記憶。最後一次見到那臺車,應該是14年回老家還看到老屋角落躺著我的那輛木製軸承車,後面就沒有了。。。。。放一張我想我當時玩的時候大概是這樣子做個紀念吧!!!
後來上大學選了自己最愛的電子專業,錯過了飛思卡爾智能車比賽(兩年一屆),但是那個時候還沒有Robomaster都沒機會再玩到車,非常的遺憾。工作5,6年後偶然看到RT-thread舉辦的戰車製作活動,就毫不猶豫就參加了,一路製作沒有遇到太多艱辛,只完成了最基礎的功能,有些遺憾,但是恰好30來臨之際,記錄一下做車的過程,算是給自己的生日禮物好了。
一. 器材選擇篇
在做這個車之前,有參考大量資料和車模,和隊友們一起商量,希望做一臺類似大疆步兵戰車,所以我們在器件選型的時候大量參考了RobotMaster的器件參數,但是考慮大疆配件昂貴,我們自己綜合了價格和性能進行選型。
MCU:STM32L475
主頻:80M
完善RT-Theard系統支持
板載AP6181
集成ST-Link非常方便調試
電機選擇
直接選用編碼電機參數如下:
有了前面電機參數,我們選擇驅動板就簡單得多了,需要關注參數為驅動電壓超過24V,驅動電流大於3A,功率要超過 3*24 = 72W.
淘寶找到如下一塊滿足要求
產品參數控制方式:
IN1IN2ENA1OUT1,OUT2輸出00X剎車11X懸空10PWM正轉調速01PWM反轉調速101全速正轉011全速反轉前面電機需要24V供電,所以需要串聯3組2S 7.4V鋰電池組(或者2組3S 11.1V電池組)我們選用3組1800mA 25C航模電池,最大放電 1.8A * 25 = 45A,大於 4個電機,峰值電流 4 * 9 =36A要求
我們購買了100mm 具體參數如下:
型號100mm直徑重量1.52kg負載能力40kg厚度50mm支撐輪抽直徑3mm支撐輪個數9個底盤由於我們器材都是自選,市面上底盤都無法安裝,所以只要自己畫個底盤,為了方便調試先做成亞克力,後面定型再做成鋁合金組合圖
二. 小車組裝篇
電池改造
為了獲取24V高電壓,我們需要將3節7.4V航模電池正負極串聯起來(操作過程千萬注意同一個電池正負不要碰到)並安裝一個撥動開關,改造之後的電池如下:
供電連接
我們先把所需的供電需求列出來:
模塊需要電壓供電方式IoT-Board+5V電池經過LM2596S降壓到5V電機驅動板電源接口+24V電池電壓直出電機驅動板5V引腳+3.3VloT-board的GPIO電平一致所以只能給3.3V否則pwm不能調速編碼電機5V+5V電池經過LM2596S降壓到5V接線如下:
主控板GPIO口引腳規劃如下
IoT-Board GPIO引腳名字GPIO引腳序號PWM namePWM channel電機驅動板引腳PD1259pwm4channel 1電機驅動板A 引腳EN1PD1360正面:
底面:
三. 軟體環境搭建篇
這部分直接參考官方給的環境搭建,非常完整,不在重複編寫STM32 運行:https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32
ENV工具可以通過以下連結獲取:https://pan.baidu.com/s/1cg28rk#list/path=%2F
四. PWM 板載wifi驅動移植篇
BSP code base選擇
由於購買是IoT-Board潘多拉主板有兩個bsp可以使用第一個是https://github.com/RT-Thread/rt-thread整個軟體比較龐大,支持非常多型號主板和晶片,架構也完善。另外一個是 https://github.com/RT-Thread/IoT_Board這個是專門針對潘多拉板子官方做得bsp,潘多拉板子上所有硬體的庫都完美支持,拿來即可用,比如板載wifi AP6181完全移植好,只需要實現tcp service即可完成wifi遙控。
一開始我這邊是用RT-Thread BSP移植好pwm,一切調試正常,後面移植wifi時發現,AP6181,lwip,wlan等網絡組件默認並沒有打開,一頓操作終於移植完成,最後移植編譯正常,發現無法連接wifi, 由於對wifi驅動了解不深,解決不了最後只好放棄這個方案。直接使用IoT-Board固件的wifi_manage工程,wifi問題解決,但是這個固件也不完美,這個板子是專門物聯網開發的,對於pwm配置工程裡面本身不包含配置選型,這樣就需要把pwm從驅動到應用到配置去打通,下面是移植簡單過程。
pwm驅動移植
本次製作的小車驅動方式為單pwm驅動方式,兩個IO控制電機方向,具體看之前驅動板的介紹。
早已習慣源碼簡單粗暴的開發方式 我並沒有去使用STM32官網先進的工具stm32CubeMX首先找到pwm驅動入口文件drivers\drv_pwm.cstm32_pwm_init(void)函數開始查看代碼根據之前配置好的pwm頻道
1#define LEFT_FORWARD_PWM "pwm4"
2#define LEFT_FORWARD_PWM_CHANNEL 1
3
4#define LEFT_BACKWARD_PWM "pwm4"
5#define LEFT_BACKWARD_PWM_CHANNEL 3
6
7#define RIGHT_FORWARD_PWM "pwm2"
8#define RIGHT_FORWARD_PWM_CHANNEL 1
9
10#define RIGHT_BACKWARD_PWM "pwm2"
11#define RIGHT_BACKWARD_PWM_CHANNEL 3
填充相關枚舉和結構體
1宏定義 PWM2_CONFIG PWM4_CONFIG
2static struct stm32_pwm stm32_pwm_obj[]
3最後rtconfig.h
4#define RT_USING_PWM
5
6#define BSP_USING_PWM
7#define BSP_USING_PWM2
8#define BSP_USING_PWM2_CH1
9#define BSP_USING_PWM2_CH2
10#define BSP_USING_PWM2_CH3
11#define BSP_USING_PWM2_CH4
12#define BSP_USING_PWM4
13#define BSP_USING_PWM4_CH1
14#define BSP_USING_PWM4_CH2
15#define BSP_USING_PWM4_CH3
16#define BSP_USING_PWM4_CH4
實現如下幾個函數,一定要注意時鐘的使能:
1void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
2void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
3static void pwm_get_channel(void)
4
在調試pwm的過程中 我們如果遇到電機不動,可以如下將drv_pwm的log打開,然後看log哪裡出錯,如果整個流程都通還不動,可以對照pwm裸機程序調試
1#define DBG_SECTION_NAME "drv.pwm"
2#define DBG_LEVEL DBG_LOG
3#include <rtdbg.h>
wifi tcp service收發數據
IoT-board板載wifi實在覺得另外接wifi或者其他控制方式沒有必要,所以只需要實現tcp service就可以了.
1void tcprecvserv(void *parameter)
2{
3 unsigned char *recv_data;
4 socklen_t sin_size;
5 int sock, bytes_received;
6 struct sockaddr_in server_addr, client_addr;
7 rt_bool_t stop = RT_FALSE;
8 int ret;
9 int nNetTimeout = 20;
10 recv_data = (unsigned char *)rt_malloc(BUFFER_SIZE);
11 rt_kprintf("tcpserv start .\n");
12 if (recv_data == RT_NULL)
13 {
14 rt_kprintf("No memory\n");
15 return;
16 }
17
18 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
19 {
20 rt_kprintf("Socket error\n");
21
22 rt_free(recv_data);
23 return;
24 }
25
26 server_addr.sin_family = AF_INET;
27 server_addr.sin_port = htons(5000);
28 server_addr.sin_addr.s_addr = INADDR_ANY;
29 rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
30 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(int));
31 if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
32 {
33 rt_kprintf("Unable to bind\n");
34 rt_free(recv_data);
35 return;
36 }
37
38 if (listen(sock, 5) == -1)
39 {
40 rt_kprintf("Listen error\n");
41
42
43 rt_free(recv_data);
44 return;
45 }
46
47 rt_kprintf("\nTCPServer Waiting for client on port 5000...\n");
48 while (stop != RT_TRUE)
49 {
50 sin_size = sizeof(struct sockaddr_in);
51
52 rt_kprintf("Listen start = %d\n", connected);
53 connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
54 rt_kprintf("Listen end = %d\n", connected);
55 if (connected < 0)
56 {
57 rt_kprintf("accept connection failed! errno = %d\n", errno);
58 continue;
59 }
60
61 rt_kprintf("I got a connection from (%s , %d)\n",
62 inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
63
64 while (1)
65 {
66 bytes_received = recv(connected, recv_data, BUFFER_SIZE, MSG_WAITALL);
67 if (bytes_received < 0)
68 {
69 closesocket(connected);
70 break;
71 } else if (bytes_received == 0) {
72 rt_kprintf("\nReceived warning,recv function return 0.\r\n");
73 closesocket(connected);
74 connected = -1;
75 break;
76 }
77 rt_ringbuffer_put_force(tcp_dat, (const rt_uint8_t *)recv_data, bytes_received);
78 }
79 }
80
81 closesocket(sock);
82 rt_ringbuffer_destroy(tcp_dat);
83 rt_free(recv_data);
84 return ;
85}
默認埠號為5000 ,這裡特別強調一下,wifi收數據時一開始收發不知道怎麼處理,正打算實現做一個ringbuffer,結果一看RT-thread有個rt_ringbuffer非常好用,使用也非常簡單,解決收數據,解析數據一大麻煩.
1rt_ringbuffer_create(2*BUFFER_SIZE);
2rt_ringbuffer_put_force(tcp_dat, (const rt_uint8_t *)recv_data, bytes_received);
然後主循環裡只需要判斷是否有數據就行
1while (rt_ringbuffer_data_len(tcp_dat) != 0) {
2 .
3 rt_ringbuffer_getchar(tcp_dat, &dat);
4
5 }
就可以了,完全不用擔心丟包問題好了wifi測試來一張圖
五. 軟體框架,控制協議、全向控制基礎原理篇
代碼目錄:application\roc_car\applications
代碼架構:
roc_car│├─docs│ *.md // 文檔介紹│├─applications│ main.c // 主入口│ main.h│ protocol.h // 協議頭文件│ protocol_parser.c // 協議解析文件│ protocol_parser.h│ roc_robot.c // 小車控制函數文件│ roc_robot.h│ tcprecv.c // tcp 接受數據函數│ wifi_connect.c│ ├─ports└─SConscript // RT-Thread 默認的構建腳本
對於電機的控制直接採用了 RT-Thread官方的rt-robot框架,我使用的單路pwm驅動方式,為了方便使用這個框架我對rt-robot再做了一層封裝
1typedef struct {
2 single_pwm_motor_t left_forward_motor, left_backward_motor, right_forward_motor, right_backward_motor;
3 ab_phase_encoder_t left_forward_encoder, left_backward_encoder, right_forward_encoder, right_backward_encoder;
4 inc_pid_controller_t left_forward_pid, left_backward_pid, right_forward_pid, right_backward_pid;
5 wheel_t left_forward_wheel, left_backward_wheel, right_forward_wheel, right_backward_wheel;
6
7 motor_t x_servo, y_servo;
8 kinematics_t c_kinematics;
9 E_ROC_ROBOT_STATUS status;
10 rt_int8_t speed ;
11 rt_int16_t degree;
12}ST_ROC_ROBOT;
使用的時候再封裝如下幾個操作函數:
1roc_robot_init()
2roc_robot_go_forward()
3roc_robot_go_backward()
4roc_robot_turn_right()
5roc_robot_turn_right_rotate()
6roc_robot_turn_left()
7roc_robot_turn_left_rotate()
8roc_robot_stop()
控制基礎原理
為了實現上面幾個函數我們需要了解一些最基本的原理,首先我們控制小車前進,後退,向左,向右,左旋,右旋這六個個基本功能。
旋轉原理
前面只是幾個基本動作的控制,如果我們要實現全相控制呢?那麼我們需要學習一下基礎原理知識通過這篇麥克納姆輪控制原理文章的講解我們知道,全向移動底盤是一個純線性系統,而剛體運動又可以線性分解為三個分量。那麼只需要計算出麥輪底盤在Vx「沿X軸平移」、Vy「沿Y軸平移」、w「繞幾何中心自轉」時,四個輪子的速度,就可以通過簡單的加法,計算出這三種簡單運動所合成的「平動+旋轉」運動時所需要的四個輪子的轉速。而這三種簡單運動時,四個輪子的速度可以通過簡單的測試,或是推動底盤觀察現象得出。
當底盤沿著 X 軸平移時:
1V左前 = +Vx
2V右前 = -Vx
3V左後 = - Vx
4V右後 = +Vx
當底盤沿著 Y 軸平移時:
1V左前 = Vy
2V右前 = Vy
3V左後 = Vy
4V右後 = Vy
當底盤繞幾何中心自轉時:
1V左前 = W
2V右前 = -W
3V左後 = W
4V右後 = -W
將以上三個方程組相加,得到的恰好是根據「傳統」方法計算出的角度,綜合到一起就是
1V左前 = +Vx + Vy + W
2V右前 = -Vx + Vy -W
3V左後 = - Vx + Vy + W
4V右後 = +Vx + Vy -W
由於 rt-robot 的全向控制和我遙控程序的坐標系不同所以重新實現了一下這個函數:
1void roc_robot_run(rt_int16_t x, rt_int16_t y, rt_int16_t rotate)
2{
3 rt_int16_t lf_speed = x + y + rotate;
4 rt_int16_t lb_speed = -x + y + rotate;
5 rt_int16_t rf_speed = -x + y -rotate;
6 rt_int16_t rb_speed = x + y - rotate;
7 single_pwm_motor_set_speed(roc_robot.left_forward_motor, lf_speed *10);
8 single_pwm_motor_set_speed(roc_robot.left_backward_motor, lb_speed *10);
9 single_pwm_motor_set_speed(roc_robot.right_forward_motor, rf_speed *10);
10 single_pwm_motor_set_speed(roc_robot.right_backward_motor, rb_speed *10);
11}
前面已經把基礎原理介紹了一遍,那我們到底怎麼來實現wifi遙控呢?
協議部分
1typedef struct
2{
3 rt_uint8_t start_code ;
4 rt_uint8_t len;
5 E_ROBOT_TYPE type;
6 rt_uint8_t addr;
7 E_CONTOROL_FUNC function;
8 rt_uint8_t *data;
9 rt_uint16_t sum;
10 rt_uint8_t end_code;
11} ST_PROTOCOL;
12
13typedef enum
14{
15 E_BATTERY = 1,
16 E_LED,
17 E_BUZZER,
18 E_INFO,
19 E_ROBOT_CONTROL_DIRECTION,
20 E_ROBOT_CONTROL_SPEED,
21 E_TEMPERATURE,
22 E_INFRARED_TRACKING,
23 E_ULTRASONIC,
24 E_INFRARED_REMOTE,
25 E_INFRARED_AVOIDANCE,
26 E_CONTROL_MODE,
27 E_BUTTON,
28 E_LED_MAXTRIX,
29 E_CMD_LINE,
30 E_VERSION,
31 E_UPGRADE,
32 E_PHOTORESISTOR,
33 E_SERVER_DEGREE,
34 E_CONTOROL_CODE_MAX,
35} E_CONTOROL_FUNC;
我們先來看下wifi遙控界面什麼樣子:
Android端APP界面示意圖
我們先建立如下一個xy 軸和0~360度的對應控制關係坐標系如下
假如這個時候我們從wifi獲取到角度 為 degree,由於apk設計原因,沒有做旋轉角度指盤那麼x軸和y軸的速度為如下:
1Vx = cos(degree) * speed
2Vy = sin(degree)*speed
上面這個計算公式,就可以很容易實現如下代碼:
1void roc_robot_drive(rt_uint16_t degree)
2{
3 LOG_D("roc_robot_drive %d", degree);
4 rt_int16_t x, y;
5
6 if (degree == 0XFFFF) {
7 roc_robot_stop();
8 } else {
9 x = cos(degree)*roc_robot.speed;
10 y = sin(degree)*roc_robot.speed;
11 roc_robot_run(x, y, 0);
12 }
13}
14整個程序流程如下:
15
16main.c:
17
18wifi_auto_connect();
19
20roc_robot_init(0);
21
22rt_thread_create("led_flash", led_flash...
23
24ret = rt_thread_create("wifi_control", wifi_control...
25
26tcprecvserv((void *)pareser_package.buffer);
六. 應用編寫,手機遙控器篇
開發環境
Android studio
需要的可以通過這裡下載android app
https://github.com/emakefun/hummer-bot/blob/Hummer-bot4.0/APP/Emakefun_Robot.apk
總結
一開始想做戰車類型,所以電機選型太大,驅動板功率很大,但是車上是亞克力,整個車容易「暴動」,需要裝減震器。
前期太注重軟體框架和wifi控制協議編寫,導致沒有時間做閉環控制調試,但是後面加入進去。
未能把攝像頭雲臺加進去。
後面有想法用樹莓派4或者jetson nano主板來做
最開心的是RT-Thread組織這個活動認識了一大幫做車的大牛,特別是指導老師吳博的知識淵博打開我的視野,另外一個吳鵬對技術的純粹執著的態度令我敬佩。
當然也要非常感謝阿波基友和其他隊友的支持
最後,恭喜鯤鵬戰隊全體成員,也感謝各位在文檔整理上的付出!RT-Thread線上活動
1、【RT-Thread開發者大會報名】2019年RT-Thread開發者大會將登入成都、上海、深圳與開發者們見面,還有RT-Thread在中高端智能領域的應用、一站式RTT開發工具、打造IoT極速開發模式等乾貨演講,期待您的參與!本次大會也設立了codelab動手實驗室活動,開發者可在現場體驗RT-Thread給開發帶來的便捷!
立即報名
2、RT-Thread能力認證考前線上培訓,將於11月25日全線截止報名,如果您有晉升、求職、尋找更好機會的需要,有深入學習和掌握RT-Thread的需求,請儘快垂詢/報考!學生優惠價:168/人
學生專屬報名通道
能力認證官網連結:https://www.rt-thread.org/page/rac.html(在外部瀏覽器打開)
立即報名(非學生)
#題外話# 喜歡RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star對我們來說非常重要!連結地址:https://github.com/RT-Thread/rt-thread
你可以添加微信17775983565為好友,註明:公司+姓名,拉進 RT-Thread 官方微信交流群
RT-Thread
讓物聯網終端的開發變得簡單、快速,晶片的價值得到最大化發揮。Apache2.0協議,可免費在商業產品中使用,不需要公布源碼,無潛在商業風險。