驅動器軟件總體架構
先來看整個機器人系統的整體架構,不難看出,其實驅動器軟件的上下游是硬件外設和中間件軟件,那想要開發驅動器軟件就需要對上下游非常熟悉嗎?不一定,但是作為一個嵌入式工程師需要懂得如何和對應開發者進行交流。
首先對于硬件開發者,他們會給我們提供一份原理圖,以及一般性的我們需要知道芯片的使用手冊,這樣才能輔助我們去正確的使用硬件的各個IO,避免如最低級的正負級接反等問題;對于中間件軟件,我們則需要和他們一起制定一個協議,這個協議約定了雙方如何進行正確的數據交互。 ? 說到這里,大家就很明確對于此處的嵌入式開發者而言需要做什么事情了。一方面要正確的使用板卡資源和外設資源,并對其數據進行加工處理來做一些開發者企圖實現的功能。另一方面要將處理的數據與上游進行交互從而達到更高效的數據利用,當然了必要時也需要接收來自上游數據的指令。 ? 首先對于硬件開發者,他們會給我們提供一份原理圖,以及一般性的我們需要知道芯片的使用手冊,這樣才能輔助我們去正確的使用硬件的各個IO,避免如最低級的正負級接反等問題;對于中間件軟件,我們則需要和他們一起制定一個協議,這個協議約定了雙方如何進行正確的數據交互。 ? 說到這里,大家就很明確對于此處的嵌入式開發者而言需要做什么事情了。一方面要正確的使用板卡資源和外設資源,并對其數據進行加工處理來做一些開發者企圖實現的功能。另一方面要將處理的數據與上游進行交互從而達到更高效的數據利用,當然了必要時也需要接收來自上游數據的指令。 ? 此處舉一個例子來看
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
FreeRTOS 快速上手
搭建軟件環境 以Keil為例
? ? ? ? ? ? ?
https://www.keil.com/download/product
Stm32配置keil5編譯版本(https://j7h4nezmu0.feishu.cn/docx/UsbUdQdShoxhpLxFPTvceJsmnQb)
FreeRTOS使用
RTOS全稱為 Real Time Operation System,即實時操作系統。RTOS強調的是實時性,又分為硬實時和軟實時。硬實時要求在規定的時間內必須完成操作,不允許超時;而軟實時里對處理過程超時的要求則沒有很嚴格。RTOS的核心就是任務調度。 ? FreeRTOS是RTOS的一種,尺寸非常小,可運行于微控制器上。微控制器是尺寸小,資源受限的處理器,它在單個芯片上包含了處理器本身、用于保存要執行的程序的只讀存儲器(ROM或Flash)、所執行程序需要的隨機存取存儲器(RAM),一般情況下程序直接從只讀存儲器執行。 ? https://j7h4nezmu0.feishu.cn/docx/PD6iduyKfo49Kux41vAcTURsnrJ#KZlsdtiUFoE3Khx0VfScpejknXA
任務創建 關于任務創建,FreeRTOS 提供了 API 給我們使用。格式如下
代碼實例
?
?
//Task priority //任務優先級 #define START_TASK_PRIO 1 //Task stack size //任務堆棧大小 #define START_STK_SIZE 256 //Task handle //任務句柄 TaskHandle_t StartTask_Handler; //Task function //任務函數 void start_task(void *pvParameters); //Main function //主函數 int main(void) { systemInit(); //Hardware initialization //硬件初始化 //Create the start task //創建開始任務 xTaskCreate((TaskFunction_t )start_task, //Task function //任務函數 (const char* )"start_task", //Task name //任務名稱 (uint16_t )START_STK_SIZE, //Task stack size //任務堆棧大小 (void* )NULL, //Arguments passed to the task function //傳遞給任務函數的參數 (UBaseType_t )START_TASK_PRIO, //Task priority //任務優先級 (TaskHandle_t* )&StartTask_Handler); //Task handle //任務句柄 vTaskStartScheduler(); //Enables task scheduling //開啟任務調度 } //Start task task function //開始任務任務函數 void start_task(void *pvParameters) { taskENTER_CRITICAL(); //Enter the critical area //進入臨界區 //Create the task //創建任務 xTaskCreate(Balance_task, "Balance_task", BALANCE_STK_SIZE, NULL, BALANCE_TASK_PRIO, NULL); //Vehicle motion control task //小車運動控制任務 xTaskCreate(MPU6050_task, "MPU6050_task", MPU6050_STK_SIZE, NULL, MPU6050_TASK_PRIO, NULL); //IMU data read task //IMU數據讀取任務 xTaskCreate(show_task, "show_task", SHOW_STK_SIZE, NULL, SHOW_TASK_PRIO, NULL); //The OLED display displays tasks //OLED顯示屏顯示任務 xTaskCreate(led_task, "led_task", LED_STK_SIZE, NULL, LED_TASK_PRIO, NULL); //LED light flashing task //LED燈閃爍任務 xTaskCreate(pstwo_task, "PSTWO_task", PS2_STK_SIZE, NULL, PS2_TASK_PRIO, NULL); //Read the PS2 controller task //讀取PS2手柄任務 xTaskCreate(data_task, "DATA_task", DATA_STK_SIZE, NULL, DATA_TASK_PRIO, NULL); //Usartx3, Usartx1 and CAN send data task //串口3、串口1、CAN發送數據任務 vTaskDelete(StartTask_Handler); //Delete the start task //刪除開始任務 taskEXIT_CRITICAL(); //Exit the critical section//退出臨界區 }
?
?
運行狀態
? ? ? ? ? ?
運動控制與PID使用
PID 原理介紹
為了更好的控制機器人行走,電機控制算法通常使用PID算法,PID(proportion integration differentiation)其實就是指比例,積分,微分控制。當我們得到系統的輸出后,將輸出經過比例,積分,微分3種運算方式,重新疊加到輸入中,從而控制系統的行為,讓它能精確的到達我們指定的狀態。基本形態如下圖所示:
? 比例環節是對偏差瞬間作出反應,偏差只要產生,控制器立即產生控制作用, 使控制量向減少偏差的方向變化。
控制作用的強弱數值表示為誤差值與比例系數Kp的乘積,取決于比例系數Kp, 比例系數Kp越大,控制作用越強, 則過渡過程越快, 控制過程的靜態偏差也就越小;但Kp越大,也越容易產生振蕩, 就會破壞系統的穩定性。 ? 所以, 比例系數Kp選擇須恰當, 以期達到過渡時間少、靜態偏差小而又穩定的效果。 ? 積分部分的表達式為誤差積分值與比例系數Ki的乘積, 從式中可看出,只要存在偏差, 則它的控制作用就不斷的增加。只有在偏差e(t)=0時, 它的積分才是一個常數,控制作用才是一個不會增加的常數。可見,積分部分可以消除系統的偏差。
積分環節的調節作用雖然會消除靜態誤差,但也會降低系統的響應速度,增加系統的超調量。積分常數Ti越大,積分的積累作用越弱,這時系統在過渡時不會產生振蕩;但是增大積分常數Ti會減慢靜態誤差的消除過程,消除偏差所需的時間也較長, 但可以減少超調量,提高系統的穩定性。 ? 實際的控制系統中除了消除靜態誤差外,還要求加快調節過程。在偏差出現的瞬間,或在偏差變化的瞬間, 不但要對偏差量做出立即響應(比例環節的作用), 而且要根據偏差的變化趨勢預先適當的糾正。為了實現這一功能作用,須在 PI 控制器的基礎上加入微分環節,形成 PID 控制器。 ?
? 微分環節的作用是阻止偏差的變化。它是根據偏差的變化趨勢(變化速度)進行控制。偏差變化的越快,微分控制器的輸出就越大,并能在偏差值變大之前進行修正。微分作用的引入, 將有助于減小超調量, 克服振蕩, 使系統趨于穩定, 它加快了系統的跟蹤速度。 ?
大家可以通過以下案例更加直觀性的通過ROS的視角來看PID的原理和作用。 https://gitee.com/xiaobairisk/ros-pid-controller 參考鏈接:https://zhuanlan.zhihu.com/p/406496635
工程實例
在電機控制中,我們一般性的會將輪速轉變為PWM去計算,那么如何獲取PWM以及發布目標PWM變成了一個更加直接的問題。 origincar_controller 中使用了PI運算。通過獲取編碼器值(當前PWM值),以及設定的目標PWM值進行PI調速。 首先需要解決當前PWM值獲取。
?
// 獲取當前編碼器值 //編碼器原始數據轉換為車輪速度,單位m/s MOTOR_A.Encoder= Encoder_A_pr*CONTROL_FREQUENCY*Wheel_perimeter/Encoder_precision; MOTOR_B.Encoder= Encoder_B_pr*CONTROL_FREQUENCY*Wheel_perimeter/Encoder_precision; MOTOR_C.Encoder= Encoder_C_pr*CONTROL_FREQUENCY*Wheel_perimeter/Encoder_precision; MOTOR_D.Encoder= Encoder_D_pr*CONTROL_FREQUENCY*Wheel_perimeter/Encoder_precision;
?
?
對于阿克曼結構車輛而言,該如何獲取目標位置呢?其中需要對目標速度進行輪速解算,也就是計算出最終需要達到的PWM值。
?
// 結算目標PWM值 { float R, Ratio=636.56, AngleR, Angle_Servo; //對于阿克曼小車Vz代表右前輪轉向角度 AngleR=Vz; R=Axle_spacing/tan(AngleR)-0.5f*Wheel_spacing; //前輪轉向角度限幅(舵機控制前輪轉向角度),單位:rad AngleR=target_limit_float(AngleR,-0.49f,0.32f); //運動學逆解 if(AngleR!=0) { MOTOR_A.Target = Vx*(R-0.5f*Wheel_spacing)/R; MOTOR_B.Target = Vx*(R+0.5f*Wheel_spacing)/R; } else { MOTOR_A.Target = Vx; MOTOR_B.Target = Vx; } //舵機PWM值,舵機控制前輪轉向角度 Angle_Servo = -0.628f*pow(AngleR, 3) + 1.269f*pow(AngleR, 2) - 1.772f*AngleR + 1.573f; Servo=SERVO_INIT + (Angle_Servo - 1.572f)*Ratio; //車輪(電機)目標速度限幅 MOTOR_A.Target=target_limit_float(MOTOR_A.Target,-amplitude,amplitude); MOTOR_B.Target=target_limit_float(MOTOR_B.Target,-amplitude,amplitude); MOTOR_C.Target=0; //沒有使用到 MOTOR_D.Target=0; //沒有使用到 Servo=target_limit_int(Servo,800,2200); //舵機PWM值限幅 }
?
?
PI計算
?
?
int Incremental_PI (float Encoder,float Target) { static float Bias,Pwm,Last_bias; Bias=Target-Encoder; //計算偏差 Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias; if(Pwm>16700)Pwm=16700; if(Pwm<-16700)Pwm=-16700; Last_bias=Bias; //保存上一次偏差 return Pwm; }
?
?
人機顯示屏交互
人機交互在驅動板中的體現很多,比如LED等、蜂鳴器、用戶按鍵、繼電器等,但是其中最為直觀的外設當為OLED顯示屏。 首先介紹為什么要引入OLED顯示屏,從設計之初的考慮來看,最重要的一個原因是方便大家調試MCU代碼,如大家可以看到當前狀態下關鍵傳感器信息,如陀螺儀等;此外也可以顯示一些控制器傳下來的信息,如WIFI0的IP地址。 接下來介紹一下OLED的主要優點:
OLED 顯示屏具備主動發光的特點,幾乎沒有視角限制,視角一般可達到 170 度,具有較寬的視角,從側面也不會失真;
OLED 顯示屏低溫特性好,在零下 40 攝氏度都能正常顯示;
OLED 顯示屏的響應時間很快,大約是幾微秒到幾十微秒;
SSD1306驅動芯片實例介紹 以OriginCar使用的0.96寸SSD1306 OLED顯示屏為例,以下為其原理圖
如何開發顯示屏
此處給出參考鏈接:SSD1306開發介紹(http://t.csdnimg.cn/h9lJl),更關鍵的一點是在目前的調試開發中,大家該如何做二次開發。 在origincar_controller工程中,已經給大家封裝好了使用函數
?
//oled.h void OLED_WR_Byte(u8 dat,u8 cmd); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Refresh_Gram(void); void OLED_Init(void); void OLED_Clear(void); void OLED_DrawPoint(u8 x,u8 y,u8 t); void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode); void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size); void OLED_ShowString(u8 x,u8 y,const u8 *p);
?
?
接下來給出顯示實例
?
?
// show.c void oled_show(void) { ... //顯示屏第1行顯示內容// OLED_ShowString(0,0,"Akm "); //阿克曼、差速、四驅、履帶車顯示陀螺儀零點 OLED_ShowString(55,0,"BIAS"); if( Deviation_gyro[2]<0) OLED_ShowString(90,0,"-"),OLED_ShowNumber(100,0,-Deviation_gyro[2],3,12); //Zero-drift data of gyroscope Z axis else OLED_ShowString(90,0,"+"),OLED_ShowNumber(100,0, Deviation_gyro[2],3,12); //陀螺儀z軸零點漂移數據 ... OLED_Refresh_Gram(); }
?
?
實際開發中大家需要在類似本例中oled_show中使用OLED_ShowString/OLED_ShowNumber即可,注意在最后需要刷新一次OLED內容OLED_Refresh_Gram;
串口通信
串口通訊(Serial Communication)是一種設備間非常常用的串行通訊方式,因為它簡單便捷,因此大部分電子設備都支持該通訊方式,其通訊協議可分層為協議層和物理層。物理層規定通信協議中具有機械、電子功能的特性,從而確保原始數據在物理媒體的傳播;協議層主要規定通訊邏輯,統一雙方的數據打包、解包標準。 ? 物理層 RS232是一種串行數據傳輸形式,稱其為串行連接,最經典的標志就是 9 針孔的 DB9 電纜RS232電壓表示邏輯 1 ,0的范圍大極大的增強了容錯率,主要用于工業設備直接通信。 ? 兩個通訊設備的“DB9 接口”之間通過串口信號線建立起連接,串口信號線中使用“RS-232 標準”傳輸數據信號。由于 RS-232 電平標準的信號不能直接被控制器直接識別,所以這些信號會經過一個“電平轉換芯片”轉換成控制器能識別的“TTL 標準”的電平信號,才能實現通訊。
USB轉串口:主要用于設備(如STM32)與其他設備通信。 ? 電平轉換芯片一般有CH340、PL2303、CP2102、FT232,使用的時候電腦要按照電平轉換芯片的驅動(虛擬出一個串口)
? ? ? ? 協議層 串口通訊的協議層中,規定了數據包的內容,它由啟始位、主體數據、校驗位以及停止位組成,通訊雙方的數據包格式要約定一致(一樣的起始位 數據 校驗位 停止位)才能正常收發數據
通訊的起始和停止信號
串口通訊的一個數據包從起始信號開始,直到停止信號結束。數據包的起始信號由一個邏輯 0 的數據位表示,而數據包的停止信號可由 1 或 2 個邏輯 1 的數據位表示 1個停止位:停止位位數的默認值。 2個停止位:可用于常規USART模式、單線模式以及調制解調器模式。
有效數據
在數據包的起始位之后緊接著的就是要傳輸的主體數據內容,也稱為有效數據,有效數據的長度常被約定為 5、6、7 或 8 位長
數據校驗
偶校驗:校驗位使得一幀中的7或8個LSB數據以及校驗位中1的個數為偶數。 例如:數據=00110101,有4個1,如果選擇偶校驗(在USART_CR1中的PS=0),校驗位將是0,最后數據檢驗如果數據有偶數個1則數據傳輸沒有出錯(但不是絕對的,如果同時兩個數據為發送錯誤(0變成1)則還是偶數個1) 奇校驗:此校驗位使得一幀中的7或8個LSB數據以及校驗位中1的個數為奇數。例如:數據=00110101,有4個1,如果選擇奇校驗(在USART_CR1中的PS=1),校驗位將是1,最后數據檢驗如果數據有奇數個1則數據傳輸沒有出錯,但同樣不是絕對的(同時兩個1變成0) 實例說明 以origincar_controller為例,使用USB轉串口的方式,將OriginCar主板與電腦PC連接,并在電腦端打開串口助手,即可接收到來自OriginCar主板的如下數據,關于數據解析待下節分享。
此處大家應該思考一個問題,數據是如何發出來的?
?
?
void data_task(void *pvParameters) { u32 lastWakeTime = getSysTickCnt(); while(1) { vTaskDelayUntil(&lastWakeTime, F2T(RATE_20_HZ)); data_transition(); USART1_SEND(); //串口1發送數據 USART3_SEND(); //串口3(ROS)發送數據 USART5_SEND(); //串口5發送數據 CAN_SEND(); //CAN發送數據 } }
?
?
首先如上所示,OriginCar主板基于 FreeRTOS進行周期性的數據發送,即data_task 函數,接下來以USART3_SEND為例看數據如何發送。
?
void USART3_SEND(void) { unsigned char i = 0; for(i=0; i<24; i++) { usart3_send(Send_Data.buffer[i]); } }
?
?
如上,此處只是將Send_Data的數據給了usart3_send函數,那么Send_Data數據是什么呢?其實就是協議層封裝數據啦。再看usart3_send
?
void usart3_send(u8 data) { USART3->DR = data; while((USART3->SR&0x40)==0); }
?
?
到此處,大家直接思考的問題就變成了USART3是什么?USART3->DR、USART3->SR是什么? 此處其實使用到了STM32固件庫編程方式,大家可以通過查看固件庫手冊來對寄存器進行操作。
狀態寄存器:USARTX->SR 其用于描述USART的工作狀態,為編程者提供一個串口的實時狀態,一般而言,發送時需要判斷上一幀有沒有發送完畢;接收時需要判斷一幀數據有沒有接收完畢,二者需要一個標志位進行狀態表示,這其中的標志就在此寄存器中。 數據寄存器:USARTX->DR 發送和接收雖然是兩個動作,但是在單片機內部是一個數據寄存器,這兩個操作的唯一區別方法就是,執行寫操作就是發送數據寄存器(TDR),執行讀操作的時候就是接受數據寄存器(RDR)。這也就解釋了為什么上面的代碼中,讀和寫都是使用的DR寄存器。 ?
審核編輯:黃飛
?
評論
查看更多