題記:標(biāo)題有點(diǎn)長(zhǎng)了,純粹為了方便被檢索到~~~本貼主要用于支持南方科技大學(xué)SDIM學(xué)院工業(yè)設(shè)計(jì)專業(yè)大三綜合項(xiàng)目移動(dòng)底盤學(xué)習(xí),也是我自己按照費(fèi)曼學(xué)習(xí)方法的一次嘗試,用從底層搭建一個(gè)機(jī)器人底盤來(lái)復(fù)習(xí)自動(dòng)控制原理。
? ? ? ? 由于工業(yè)設(shè)計(jì)專業(yè)沒(méi)有開設(shè)嵌入式課程,多數(shù)同學(xué)不具備使用Keil或STM32CubeIDE的基礎(chǔ)。鑒于Arduino開發(fā)的友好性(主要是參考資料多),特使用支持Arduino環(huán)境的STM32F103C8T6作為底盤控制核心。已經(jīng)會(huì)使用stm32單片機(jī)的同學(xué)推薦直接使用官方推薦的編程方式,Arduino的性能和資源豐富性確實(shí)不如CubeIDE。
? ? ? 言歸正轉(zhuǎn),以下是一些項(xiàng)目設(shè)計(jì)解讀:
1、底盤長(zhǎng)什么樣子?
有人問(wèn)模型能給嗎?當(dāng)然可以,上圖的模型必須給到位:
鏈接:https://pan.baidu.com/s/1zzqOA6rm-wdzWZ0YmT_UCA?pwd=obkx?
提取碼:obkx?
————其他所有的電子資料會(huì)及時(shí)更新在這個(gè)位置。
2、為何要采用四輪獨(dú)立電機(jī)驅(qū)動(dòng)?三輪、阿克曼、麥克納姆底盤行不?
? ? ? ?四輪獨(dú)立驅(qū)動(dòng)的好處是底盤驅(qū)動(dòng)能力相比三輪、阿克曼都要強(qiáng),承載能力比麥克納姆強(qiáng),還可以實(shí)現(xiàn)原地轉(zhuǎn)向,車輛通過(guò)性也優(yōu)于其他三種底盤。缺點(diǎn)是輪胎損耗高,效率不如三輪和阿克曼,同時(shí)控制4個(gè)電機(jī)對(duì)硬件資源要求高。今年的大三項(xiàng)目中,機(jī)器人需要在毛坯建筑中移動(dòng),相比而言四輪獨(dú)立驅(qū)動(dòng)的底盤比較合適。
3、電機(jī)如何選型?
? ? ? ?商用機(jī)器人四輪移動(dòng)底盤一般選用無(wú)刷輪轂電機(jī),但是價(jià)格通常要超過(guò)1500元單只,性能對(duì)該項(xiàng)目來(lái)說(shuō)也有點(diǎn)過(guò)剩。淘寶的科研教學(xué)用途底盤通常使用有刷電機(jī),成本優(yōu)勢(shì)明顯,加上也能匹配SDIM的控制課程實(shí)驗(yàn)要求(直流有刷電機(jī)模型比無(wú)刷電機(jī)要簡(jiǎn)單),項(xiàng)目就直接確實(shí)使用直流有刷電機(jī)。
? ? ? ? 淘寶常見(jiàn)的機(jī)器人底盤喜歡使用TT電機(jī)或36直流有刷減速電機(jī),通常電機(jī)尾端自帶光電、霍爾或GMR編碼器,方便速度閉環(huán)控制。然而這2類電機(jī)的承載能力實(shí)在無(wú)法滿足項(xiàng)目要求(負(fù)載10公斤),必須尋找承載能力足夠的電機(jī)才行。
? ? ? ? 感謝萬(wàn)能的淘寶,我找到了2款大功率的帶減速器的直流電機(jī)。此類電機(jī)主要使用GMR編碼器,分辨率達(dá)500線/轉(zhuǎn),還配套了聯(lián)軸器、獨(dú)立懸掛、輪胎等組件,單電機(jī)承載能力在5kg以上,完全滿足項(xiàng)目要求。
? ? ? ? 采購(gòu)鏈接如下:底盤輪組模塊平行四邊形獨(dú)立懸掛避震器光電編碼器行星減速電機(jī)-淘寶網(wǎng) (taobao.com)https://item.taobao.com/item.htm?spm=a1z0k.7628869.0.0.39eb37delSzkhs&id=618634863947&_u=t2dmg8j26111
? ? ? 兩款電機(jī)均滿足項(xiàng)目要求,本貼是基于MD60和8寸充氣輪胎,裝配起來(lái)發(fā)現(xiàn)站個(gè)人上去都沒(méi)有問(wèn)題,考慮到成本,建議選擇MD36的電機(jī)。另外MD36的編碼器還有霍爾類型的,這種分辨率雖然差一點(diǎn),但也能滿足速度閉環(huán)的要求,使用Arduino的Mega2560板子也能處理(霍爾一圈只有13個(gè)脈沖,相比而言GMR有500個(gè)脈沖,由于脈沖需要觸發(fā)中斷,Mega2560無(wú)法勝任GMR的高速驅(qū)動(dòng)要求,霍爾就沒(méi)問(wèn)題)。下面是MD36電機(jī)的一些性能參數(shù)和選型參考:
? ? ? ? 需要額外指出的是,本帖使用的STM32F107板子在標(biāo)準(zhǔn)72MHz主頻下可以勝任4個(gè)GMR編碼器電機(jī)的反饋要求,但是其引腳數(shù)量有限,增加巡線傳感器后就必須額外增加一個(gè)上位主控,從節(jié)約成本和滿足要求的角度綜合考慮,選用霍爾編碼器的MD36電機(jī)最適合本項(xiàng)目。
? ? ? ? ?如在推薦鏈接中采購(gòu)MD36,需要選擇減速比為1:51、霍爾編碼器、獨(dú)立懸掛,對(duì)應(yīng)的輪轂法蘭內(nèi)孔要與電機(jī)出軸一致,輪胎尺寸最大可選擇8.5寸的。
4、電機(jī)驅(qū)動(dòng)器選型
? ? ? 2款直流電機(jī)的需求最大電流不同,淘寶商家分別給出了1款驅(qū)動(dòng)器可覆蓋2種電機(jī)的需求,每個(gè)可驅(qū)動(dòng)2個(gè)電機(jī),鏈接如下:
D50A大功率MOS雙路直流有刷電機(jī)驅(qū)動(dòng)模塊12A大電流24V驅(qū)動(dòng)器-淘寶網(wǎng) (taobao.com)https://item.taobao.com/item.htm?spm=a1z10.3-c-s.w4002-15726392041.19.4768143evpiwvb&id=569591305325
5、底盤如何設(shè)計(jì)?
? ? ? 加上幾根型材、擰幾個(gè)螺絲即可。: )SW的模型已經(jīng)提供在百度網(wǎng)盤文件包里了,如選用36的電機(jī)可以自行根據(jù)商家圖紙簡(jiǎn)易繪制。
? ? ? 外殼在周鼎老師的課上按要求進(jìn)行即可,注意輪子的尺寸和輪距、軸距可能影響外觀。
6、STM32F103控制板長(zhǎng)什么樣子?
圖上的板子是常見(jiàn)的樣子,價(jià)格含運(yùn)費(fèi)不高于15元,比UNO還要便宜??!本項(xiàng)目中建議買不焊接排針的C8T6版本,參考鏈接如下:
STM32F103C8T6最小系統(tǒng)板C6T6STM32單片機(jī)開發(fā)板核心板板江協(xié)科技-tmall.com天貓https://detail.tmall.com/item.htm?abbucket=6&id=739127102803&ns=1&skuId=5097382116547&spm=a21n57.1.0.0.5583523cqTJBZI板子接口圖如下,要是能找到更清楚的再更新:
下面這張圖感覺(jué)更清楚,但引腳定義有點(diǎn)隨意。
? ? ?
?圖中可以看到引腳存在復(fù)用情況,要參考以下鏈接根據(jù)物理引腳的實(shí)際需求選用合適的引腳,比如PWM引腳只有10-13、16~19、29~32、42~46可以,因?yàn)檫@4組引腳背后對(duì)應(yīng)的是4個(gè)硬件定時(shí)器。再比如,STM32單片機(jī)的核心電壓為3.3V,部分引腳不可以輸入5V電壓,能輸入5V電壓的引腳,做輸出引腳使用時(shí)也無(wú)法直接推挽輸出5V,要匹配5V的模塊時(shí)需留意使用外接上拉電阻的開漏輸出才行。
有關(guān)引腳的詳細(xì)功能定義,可參考如下鏈接(雖然鏈接中的Maple板子跟我們的不太一樣,芯片也有一點(diǎn)型號(hào)上的差異,但是不妨礙參考,有關(guān)軟硬件的內(nèi)容仍然具有可讀性):
docs.leaflabs.com/docs.leaflabs.com/index.htmlhttp://docs.leaflabs.com/docs.leaflabs.com/index.html放一點(diǎn)參考鏈接的截圖:
?正點(diǎn)原子有款STM32F103RCT6的板子,比上面的C8T6板子接口要豐富,價(jià)格也能接受,也可以選擇這個(gè),下載程序時(shí)記得把芯片型號(hào)換一換。使用方法見(jiàn)下面的鏈接:
【精選】STM32如何使用arduino_ide進(jìn)行開發(fā)_stm32調(diào)用 arduino 讀外部脈沖長(zhǎng)度_正點(diǎn)原子的博客-CSDN博客【精選】STM32如何使用arduino_ide進(jìn)行開發(fā)_stm32調(diào)用 arduino 讀外部脈沖長(zhǎng)度_正點(diǎn)原子的博客-CSDN博客
7、底盤怎么接線?
所用電機(jī)驅(qū)動(dòng)板是兼容3.3V驅(qū)動(dòng)的,隨便接合適的引腳即可。用于電機(jī)轉(zhuǎn)角反饋的GMR編碼器理論上也是兼容的5V/3.3V。不過(guò)考慮信號(hào)線挺長(zhǎng)的,還是給編碼器用了5V電源,因此A/B相的引腳就只能選擇5V兼容引腳。此外還要預(yù)留出串口通訊和USB下載引腳,PC13用于LED指示也需要預(yù)留。
借用上面的接口圖修改的圖如下,其中LF指左前方電機(jī)、LR指左后方電機(jī)、RF指有前方電機(jī)、RR指右后方電機(jī)。
驅(qū)動(dòng)板和電機(jī)之間接線參考下圖(11月16日更新,為了騰出3號(hào)串口(PB10、PB11),更換了LR電機(jī)的A、B相接線位置至PB8、PB9,程序中也做了更新)。注意圖中的A、B相可能與代碼不太一樣,可能會(huì)接反,如果發(fā)現(xiàn)閉環(huán)控制速度為0時(shí),輪子稍作轉(zhuǎn)動(dòng)就無(wú)法停止,就需要將AB相反轉(zhuǎn)一下。
8、電源怎么設(shè)計(jì)?
由于電機(jī)工作在24V下,建議采用DJI的?TB48S/TB47S航模電池(主要是有現(xiàn)成的)。
9、如何使用Arduino IDE給STM32F103開發(fā)板下載代碼?
? ? ? ? ?有3種方式可以給開發(fā)板下載代碼,分別是通過(guò)USB接口(PA11、PA12)、UART1接口(PA9、PA10)和默認(rèn)的SWD接口。
?A、通過(guò)USB接口下載
可參考以下鏈接配置,優(yōu)點(diǎn)是不用管BOOT跳線位置,使用體驗(yàn)約等于UNO,也不需要額外的硬件(其實(shí)下載bin文件還是需要USB轉(zhuǎn)串口模塊)。此種方法只能使用Arduino1.8的版本,新版本會(huì)報(bào)錯(cuò)。另外一般最高頻率只能到72MHz,在本項(xiàng)目中足夠使用。
STM32F103C8T6在Arduino IDE里編程_stm32f103c8t6 燒錄米思齊bin文件-CSDN博客https://blog.csdn.net/bobo184/article/details/84349184
B、通過(guò)UART1接口下載(本帖推薦)
這是網(wǎng)上最常用的方法,優(yōu)點(diǎn)是在安裝上面鏈接的開發(fā)板庫(kù)后,用串口下載可將103芯片超頻到128MHz使用(USB就不可以),Arduino IDE最新版也可以使用。缺點(diǎn)嘛,就是每次下載前后要對(duì)BOOT進(jìn)行跳線,這個(gè)有點(diǎn)煩,而且還需要準(zhǔn)備一個(gè)USB轉(zhuǎn)UART的轉(zhuǎn)換器。參考鏈接如下:
STM32F103C8T6使用aduino環(huán)境編程_interface serial_w32: 115200 8e1-CSDN博客https://blog.csdn.net/qq_38288618/article/details/90553252?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169684745916800215067511%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169684745916800215067511&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-90553252-null-null.142%5Ev95%5Einsert_down28v1&utm_term=STM32F103C8T6%20Arduino&spm=1018.2226.3001.4187
?C、通過(guò)SWD接口下載
首先你得有個(gè)ST-link,如果有這東西的話大概率你已經(jīng)會(huì)用stm自己的IDE了。參考鏈接如下:Getting Started · stm32duino/Arduino_Core_STM32 Wiki · GitHubhttps://github.com/stm32duino/Arduino_Core_STM32/wiki/Getting-Started上面的鏈接是一個(gè)官方支持STM32duino的網(wǎng)頁(yè),幾乎支持所有stm32的芯片,需要中文支持的話可以參考以下2個(gè)鏈接:
Arduino借助STM32Duino開發(fā)STM32教程-(2023年8月)-CSDN博客https://blog.csdn.net/m0_46236949/article/details/132381810?ops_request_misc=&request_id=&biz_id=102&utm_term=stm32duino%20arduino&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-132381810.142%5Ev96%5Epc_search_result_base7&spm=1018.2226.3001.4187ArduinoIDE + STM32Link燒錄調(diào)試_arduino燒錄stm32_BobBobBao的博客-CSDN博客STM32,Arduino燒錄調(diào)試總結(jié)_arduino燒錄stm32https://blog.csdn.net/sinat_22081411/article/details/125206320?ops_request_misc=&request_id=&biz_id=102&utm_term=STM32F103C8T6%20Arduino%20SWD&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-9-125206320.142%5Ev95%5Einsert_down28v1&spm=1018.2226.3001.4187使用ST-link下載可能會(huì)報(bào)錯(cuò)“STM32CubeProgrammer not found (STM32_Programmer_CLI.exe).”,解決辦法如下:
使用官方Arduino板支持包開發(fā)全系列STM32_stm32官方支持arduino_笑春風(fēng)oO的博客-CSDN博客https://blog.csdn.net/qcmyqcmy/article/details/128278285
10、有速度閉環(huán)控制的參考代碼嗎?
我使用的控制器是STM32F103C8T6,接線圖參考上面第7節(jié)。
以下的代碼使用串口控制電機(jī)的速度,速度為閉環(huán)控制。此代碼不能用于系統(tǒng)參數(shù)辨識(shí),辨識(shí)的代碼會(huì)在后面提供。另外需要注意:如果遷移到Mega2560,要注意PWM相關(guān)代碼要做修改,原因是stm32的PWM范圍是0~65535,Mega2560是0~255,此外引腳定義也有差異,具體的參考以下代碼的注釋。
//PID速度閉環(huán)控制4個(gè)電機(jī)
#define LED PC13 //調(diào)試用的LED
//以下為左前方電機(jī)引腳定義
#define LF_Motor_IN1 PA5 //LF電機(jī)使能引腳1
#define LF_Motor_IN2 PA4 //LF電機(jī)使能引腳2
#define LF_PWM PA0 //LF電機(jī)調(diào)速引腳
#define LF_ENCODER_A PB7 //LF編碼器A相引腳
#define LF_ENCODER_B PB6 //LF編碼器B相引腳
//以下為右前方電機(jī)引腳定義
#define RF_Motor_IN1 PB0 //RF電機(jī)使能引腳1
#define RF_Motor_IN2 PB1 //RF電機(jī)使能引腳2
#define RF_PWM PA2 //RF電機(jī)調(diào)速引腳
#define RF_ENCODER_A PB12 //RF編碼器A相引腳
#define RF_ENCODER_B PB13 //RF編碼器B相引腳
//以下為左后方電機(jī)引腳定義
#define LR_Motor_IN1 PA7 //LR電機(jī)使能引腳1
#define LR_Motor_IN2 PA6 //LR電機(jī)使能引腳2
#define LR_PWM PA1 //LR電機(jī)調(diào)速引腳
#define LR_ENCODER_A PB8 //LR編碼器A相引腳
#define LR_ENCODER_B PB9 //LR編碼器B相引腳
//以下為右后電機(jī)引腳定義
#define RR_Motor_IN1 PC14 //RR電機(jī)使能引腳1
#define RR_Motor_IN2 PC15 //RR電機(jī)使能引腳2
#define RR_PWM PA3 //RR電機(jī)調(diào)速引腳
#define RR_ENCODER_A PB14 //RR編碼器A相引腳
#define RR_ENCODER_B PB15 //RR編碼器B相引腳
volatile int i = 0; //調(diào)試用的公共變量
volatile int LF_Velocity = 0, LF_Count = 0; //左前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RF_Velocity = 0, RF_Count = 0; //左后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int LR_Velocity = 0, LR_Count = 0; //右前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RR_Velocity = 0, RR_Count = 0; //右后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
String Target_Value; //串口獲取的速度字符串變量
volatile int LF_value,RF_value,LR_value,RR_value; //用于存儲(chǔ)通過(guò)PI控制器計(jì)算得到的用于調(diào)整電機(jī)轉(zhuǎn)速的PWM值,最大值65535
float KP = 200, KI = 20; //PI參數(shù),此處調(diào)整會(huì)影響啟動(dòng)電流,低速時(shí)可能引起震蕩
volatile float LF_Target=0,RF_Target=0,LR_Target=0,RR_Target=0; //電機(jī)轉(zhuǎn)速目標(biāo)值,5ms定時(shí)器最大可用范圍±280,2ms定時(shí)器,最大可用范圍±120
///*********** 限幅************
// 以下兩個(gè)參數(shù)讓輸出的PWM在一個(gè)合理區(qū)間
// 當(dāng)輸出的PWM小于1500時(shí)電機(jī)不轉(zhuǎn) 所以要設(shè)置一個(gè)啟始PWM
// STM32單片機(jī)的PWM不能超過(guò)65535 所以 PWM_Restrict 起到限制上限的作用
//*****************************/
int startPWM = 1500; //克服死區(qū)的啟始PWM
int PWM_Restrict = 64000; //startPW+PWM_Restric=65500<65535
/**********外部中斷觸發(fā)計(jì)數(shù)器函數(shù)(4個(gè)電機(jī)需要獨(dú)立的外部中斷處理函數(shù))************
根據(jù)轉(zhuǎn)速的方向不同我們將計(jì)數(shù)器累計(jì)為正值或者負(fù)值(計(jì)數(shù)器累計(jì)為正值為負(fù)值為計(jì)數(shù)器方向)
只有方向累計(jì)正確了才可以實(shí)現(xiàn)正確的調(diào)整,否則會(huì)出現(xiàn)逆方向滿速旋轉(zhuǎn)
※※※※※※超級(jí)重點(diǎn)※※※※※※
所謂累計(jì)在正確的方向即
(1)計(jì)數(shù)器方向
(2)電機(jī)輸出方向(控制電機(jī)轉(zhuǎn)速方向的接線是正著接還是反著接)
(3)PI 控制器 里面的誤差(Basi)運(yùn)算是目標(biāo)值減當(dāng)前值(Target-Encoder),還是當(dāng)前值減目標(biāo)值(Encoder-Target)
三個(gè)方向只有對(duì)應(yīng)上才會(huì)有效果否則你接上就是使勁的朝著一個(gè)方向(一般來(lái)說(shuō)是反方向)滿速旋轉(zhuǎn),出現(xiàn)這種問(wèn)題,需要將AB相的線調(diào)過(guò)來(lái),或改下引腳定義
我例子里是我自己對(duì)應(yīng)好的,如果其他驅(qū)動(dòng)單片機(jī)在自己嘗試的時(shí)候出現(xiàn)滿速旋轉(zhuǎn)就是三個(gè)方向沒(méi)對(duì)應(yīng)上
下列函數(shù)中由于在A相上升沿觸發(fā)時(shí),B相是低電平,和A相下降沿觸發(fā)時(shí)B是高電平是一個(gè)方向,在這種觸發(fā)方式下,我們將count累計(jì)為正,另一種情況將count累計(jì)為負(fù)
********************************************/
void LF_READ_ENCODER_A() //左前方電機(jī)A相中斷
{
if (digitalRead(LF_ENCODER_A) == HIGH)
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count++; //根據(jù)另外一相電平判定方向
else
LF_Count--;
}
else
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count--; //根據(jù)另外一相電平判定方向
else
LF_Count++;
}
}
void RF_READ_ENCODER_A() //右前方電機(jī)A相中斷
{
if (digitalRead(RF_ENCODER_A) == HIGH)
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count++; //根據(jù)另外一相電平判定方向
else
RF_Count--;
}
else
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count--; //根據(jù)另外一相電平判定方向
else
RF_Count++;
}
}
void LR_READ_ENCODER_A() //左后方電機(jī)A相中斷
{
if (digitalRead(LR_ENCODER_A) == HIGH)
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count++; //根據(jù)另外一相電平判定方向
else
LR_Count--;
}
else
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count--; //根據(jù)另外一相電平判定方向
else
LR_Count++;
}
}
void RR_READ_ENCODER_A() //右后方電機(jī)A相中斷
{
if (digitalRead(RR_ENCODER_A) == HIGH)
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count++; //根據(jù)另外一相電平判定方向
else
RR_Count--;
}
else
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count--; //根據(jù)另外一相電平判定方向
else
RR_Count++;
}
}
/**********定時(shí)器中斷觸發(fā)函數(shù)(只需要1個(gè)定時(shí)器)*********/
HardwareTimer timer(3);//聲明使用3號(hào)定時(shí)器
void control()
{ // cli(); //關(guān)閉所有中斷,此處嘗試不加也行
//把采用周期(內(nèi)部定時(shí)中斷周期)所累計(jì)的脈沖下降沿的個(gè)數(shù),賦值給速度
LF_Velocity = LF_Count;
RF_Velocity = RF_Count;
LR_Velocity = LR_Count;
RR_Velocity = RR_Count;
//脈沖計(jì)數(shù)器清零
LF_Count = 0;
RF_Count = 0;
LR_Count = 0;
RR_Count = 0;
//以下為4個(gè)電機(jī)同時(shí)計(jì)算PID參數(shù)
LF_value = LF_Incremental_PI(LF_Velocity, LF_Target); //通過(guò)目標(biāo)值和當(dāng)前值在PID函數(shù)下算出我們需要調(diào)整用的PWM值
RF_value = RF_Incremental_PI(RF_Velocity, RF_Target);
LR_value = LR_Incremental_PI(LR_Velocity, LR_Target);
RR_value = RR_Incremental_PI(RR_Velocity, RR_Target);
//以下為4個(gè)電機(jī)同時(shí)輸出PWM值
LF_Set_PWM(LF_value);
RF_Set_PWM(RF_value);
LR_Set_PWM(LR_value);
RR_Set_PWM(RR_value);
//以下為調(diào)試代碼,調(diào)試完成需要?jiǎng)h除,避免浪費(fèi)CPU資源
Serial1.print(LF_value);//輸出左前輪的PWM值
Serial.print(",");
Serial1.println(LF_Velocity);//輸出左前輪的轉(zhuǎn)速
// sei(); //打開所有中斷,此處嘗試不加也行
}
/***********PI控制器****************/
int LF_Incremental_PI(int LF_Encoder, float LF_Target1)
{
static float LF_Bias, LF_MPWM = 0, LF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LF_Bias = LF_Target1 - LF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LF_MPWM += KP * (LF_Bias - LF_Last_bias) + KI * LF_Bias; //增量式PI控制計(jì)算
if (LF_MPWM > PWM_Restrict)
LF_MPWM = PWM_Restrict; //限幅
if (LF_MPWM < -PWM_Restrict)
LF_MPWM = -PWM_Restrict; //限幅
LF_Last_bias = LF_Bias; //保存上一次偏差
return LF_MPWM; //增量輸出
}
int RF_Incremental_PI(int RF_Encoder, float RF_Target1)
{
static float RF_Bias, RF_MPWM = 0, RF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RF_Bias = RF_Target1 - RF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RF_MPWM += KP * (RF_Bias - RF_Last_bias) + KI * RF_Bias; //增量式PI控制計(jì)算
if (RF_MPWM > PWM_Restrict)
RF_MPWM = PWM_Restrict; //限幅
if (RF_MPWM < -PWM_Restrict)
RF_MPWM = -PWM_Restrict; //限幅
RF_Last_bias = RF_Bias; //保存上一次偏差
return RF_MPWM; //增量輸出
}
int LR_Incremental_PI(int LR_Encoder, float LR_Target1)
{
static float LR_Bias, LR_MPWM = 0, LR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LR_Bias = LR_Target1 - LR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LR_MPWM += KP * (LR_Bias - LR_Last_bias) + KI * LR_Bias; //增量式PI控制計(jì)算
if (LR_MPWM > PWM_Restrict)
LR_MPWM = PWM_Restrict; //限幅
if (LR_MPWM < -PWM_Restrict)
LR_MPWM = -PWM_Restrict; //限幅
LR_Last_bias = LR_Bias; //保存上一次偏差
return LR_MPWM; //增量輸出
}
int RR_Incremental_PI(int RR_Encoder, float RR_Target1)
{
static float RR_Bias, RR_MPWM = 0, RR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RR_Bias = RR_Target1 - RR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RR_MPWM += KP * (RR_Bias - RR_Last_bias) + KI * RR_Bias; //增量式PI控制計(jì)算
if (RR_MPWM > PWM_Restrict)
RR_MPWM = PWM_Restrict; //限幅
if (RR_MPWM < -PWM_Restrict)
RR_MPWM = -PWM_Restrict; //限幅
RR_Last_bias = RR_Bias; //保存上一次偏差
return RR_MPWM; //增量輸出
}
/**********電機(jī)驅(qū)動(dòng)函數(shù)*********/
void LF_Set_PWM(int LF_motora)
{
if (LF_motora > 0) //如果算出的PWM為正
{
digitalWrite(LF_Motor_IN1, 1);
digitalWrite(LF_Motor_IN2, 0);
pwmWrite(LF_PWM, LF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整,此處的PWM輸出函數(shù)跟Mega2560不同
} else if (LF_motora == 0) //如果PWM為0停車
{
digitalWrite(LF_Motor_IN2, 0);
digitalWrite(LF_Motor_IN1, 0);
} else if (LF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LF_Motor_IN1, 0);
digitalWrite(LF_Motor_IN2, 1);
pwmWrite(LF_PWM, -LF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RF_Set_PWM(int RF_motora)
{
if (RF_motora > 0) //如果算出的PWM為正
{
digitalWrite(RF_Motor_IN1, 1);
digitalWrite(RF_Motor_IN2, 0);
pwmWrite(RF_PWM, RF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RF_motora == 0) //如果PWM為0停車
{
digitalWrite(RF_Motor_IN2, 0);
digitalWrite(RF_Motor_IN1, 0);
} else if (RF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RF_Motor_IN1, 0);
digitalWrite(RF_Motor_IN2, 1);
pwmWrite(RF_PWM, -RF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void LR_Set_PWM(int LR_motora)
{
if (LR_motora > 0) //如果算出的PWM為正
{
digitalWrite(LR_Motor_IN1, 1);
digitalWrite(LR_Motor_IN2, 0);
pwmWrite(LR_PWM, LR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (LR_motora == 0) //如果PWM為0停車
{
digitalWrite(LR_Motor_IN2, 0);
digitalWrite(LR_Motor_IN1, 0);
} else if (LR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LR_Motor_IN1, 0);
digitalWrite(LR_Motor_IN2, 1);
pwmWrite(LR_PWM, -LR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RR_Set_PWM(int RR_motora)
{
if (RR_motora > 0) //如果算出的PWM為正
{
digitalWrite(RR_Motor_IN1, 1);
digitalWrite(RR_Motor_IN2, 0);
pwmWrite(RR_PWM, RR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RR_motora == 0) //如果PWM為0停車
{
digitalWrite(RR_Motor_IN2, 0);
digitalWrite(RR_Motor_IN1, 0);
} else if (RR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RR_Motor_IN1, 0);
digitalWrite(RR_Motor_IN2, 1);
pwmWrite(RR_PWM, -RR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void setup()
{
Serial1.begin(115200); //打開串口
Serial1.println("/*****開始驅(qū)動(dòng)*****/");
delay(1000);
pinMode(LED, OUTPUT); //調(diào)試用的閃爍LED,PC13
pinMode(LF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LF_ENCODER_B, INPUT);
pinMode(LF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(LF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LF_PWM, PWM_OPEN_DRAIN);
pinMode(RF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RF_ENCODER_B, INPUT);
pinMode(RF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(RF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RF_PWM, PWM_OPEN_DRAIN);
pinMode(LR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LR_ENCODER_B, INPUT);
pinMode(LR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(LR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LR_PWM, PWM_OPEN_DRAIN);
pinMode(RR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RR_ENCODER_B, INPUT);
pinMode(RR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(RR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RR_PWM, PWM_OPEN_DRAIN);
//下面是外部中斷的初始化
attachInterrupt(LF_ENCODER_A, LF_READ_ENCODER_A, FALLING); //開啟對(duì)應(yīng)A相引腳的外部中斷,觸發(fā)方式為FALLING 即下降沿都觸發(fā),觸發(fā)的中斷函數(shù)為 LF_ENCODER_A
attachInterrupt(RF_ENCODER_A, RF_READ_ENCODER_A, FALLING);
attachInterrupt(LR_ENCODER_A, LR_READ_ENCODER_A, FALLING);
attachInterrupt(RR_ENCODER_A, RR_READ_ENCODER_A, FALLING);
//下面是定時(shí)器的初始化,Mega2560的用法與此處有差異,參考引用庫(kù)函數(shù)才行
timer.pause();// Pause the timer while we're configuring it
timer.setPeriod(5000); // Set up period in microseconds,5000us=5ms
timer.setChannel1Mode(TIMER_OUTPUT_COMPARE);// Set up an interrupt on channel 1
timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update
timer.attachCompare1Interrupt(control);//定時(shí)中斷函數(shù)名聲明
timer.refresh();// Refresh the timer's count, prescale, and overflow
timer.resume();// Start the timer counting
}
void loop()
{
while (Serial1.available() > 0) //檢測(cè)串口是否接收到了數(shù)據(jù)
{
Target_Value = Serial.readString(); //讀取串口字符串
i = Target_Value.toFloat(); //將字符串轉(zhuǎn)換為浮點(diǎn)型,并將其賦給目標(biāo)值
if(i==1) //底盤前進(jìn)
{
LF_Target=100;
LR_Target=100;
RF_Target=100;
RR_Target=100;
}
else if(i==0)//底盤停止
{ LF_Target=0;
LR_Target=0;
RF_Target=0;
RR_Target=0;
}
else if(i==2)//底盤原地拐彎
{ LF_Target=-100;
LR_Target=-100;
RF_Target=100;
RR_Target=100;
}
Serial.print("Target:"); //串口打印出設(shè)定的目標(biāo)轉(zhuǎn)速
Serial.println(LF_Target);
}
}
? ? ? 代碼中,通過(guò)串口接收的數(shù)據(jù)(0、1、2)來(lái)控制底盤的停止、前進(jìn)和原地拐彎。如果需要通過(guò)串口直接控制每個(gè)電機(jī)的轉(zhuǎn)速,需要確定通訊協(xié)議,通過(guò)解析通訊協(xié)議里的代碼來(lái)獲取每個(gè)電機(jī)目標(biāo)速度值。
11、怎樣通過(guò)識(shí)別機(jī)器人底盤的控制參數(shù)?
? ? ? ?先假設(shè)地盤的模型為二階模型(理想的小車為一階慣性模型,考慮到我們的底盤是充氣輪胎、帶有獨(dú)立懸架,將其假定為二階模型)。
? ? ? 然后將電機(jī)的PWM作為輸入、電機(jī)速度(編碼器反饋)作為輸出,程序中讓4個(gè)電機(jī)的PWM一樣,輸入為PWM階躍響應(yīng),底盤直線加速運(yùn)動(dòng)作為輸出,通過(guò)無(wú)線串口獲取輸入輸出數(shù)據(jù)。
? ? ? 再然后將多次實(shí)驗(yàn)獲得的輸入輸出數(shù)據(jù)(保留1、2組數(shù)據(jù)不用)導(dǎo)入到MATLAB中,計(jì)算出二階模型的各參數(shù)。
? ? ? 最后將剛才保留的數(shù)據(jù)輸入計(jì)算出來(lái)的模型中,將實(shí)際輸出與計(jì)算輸出進(jìn)行比較,觀察模型準(zhǔn)確性。如果實(shí)際輸出與計(jì)算輸出接近,證明實(shí)驗(yàn)?zāi)P徒⑦€算成功。
12、系統(tǒng)輸入(PWM)與輸出(電機(jī)轉(zhuǎn)速)之間的關(guān)系
如4中提及到的電機(jī)驅(qū)動(dòng)器,我們能控制的只有PWM。假設(shè)驅(qū)動(dòng)器電源電壓不變,經(jīng)過(guò)驅(qū)動(dòng)器脈寬調(diào)制(PWM)后輸入到電機(jī)的等效電壓與PWM占空比是線性關(guān)系,則輸入量PWM實(shí)際上控制的是電機(jī)輸入端的電壓Ea。直流有刷電機(jī)的等效模型如下:
其中,Ea:電源電壓,Ia:電機(jī)電流,R:電機(jī)的等效電阻,L:電機(jī)的等效電感,Ec:電機(jī)旋轉(zhuǎn)反電動(dòng)勢(shì)。
在該等效電路中的直流關(guān)系為:Ea=Ia×R+Ec ……(1)
電機(jī)反電動(dòng)勢(shì)Ec與轉(zhuǎn)速成正比,因此可以表達(dá)為:Ec=Ke×N ……(2)
※N:轉(zhuǎn)速 [rpm],Ke:反電動(dòng)勢(shì)常數(shù) [V/rpm]
電機(jī)轉(zhuǎn)矩T [N.m]與電機(jī)電流成正比,因此可以表達(dá)為:T=Kt×Ia ……(3)
※Kt:轉(zhuǎn)矩常數(shù) [N.m/A]
將公式(2)、(3)代入(1)中可以得到轉(zhuǎn)速N與轉(zhuǎn)矩T的關(guān)系:
N=Ea/Ke-R/(Kt×Ke)×T ……(4)
當(dāng)?shù)妆P勻速運(yùn)動(dòng)時(shí),T只取決于底盤4個(gè)電機(jī)的阻力矩,分析公式(4)會(huì)發(fā)現(xiàn)N與Ea線性相關(guān)。即,通過(guò)PWM控制了Ea,就控制了底盤勻速運(yùn)動(dòng)時(shí)電機(jī)的轉(zhuǎn)速N。
以下是來(lái)自胡壽松第7版《自動(dòng)控制原理》P25中對(duì)此問(wèn)題的建模過(guò)程,結(jié)論類似:
13、通過(guò)輸入PWM直接控制底盤前進(jìn)的代碼
此代碼的輸入?yún)?shù)是電機(jī)的PWM,輸出是電機(jī)的轉(zhuǎn)速。由于4個(gè)電機(jī)是速度一致,可視為整個(gè)底盤的PWM和速度。
#define LED PC13 //調(diào)試用的LED
//以下為左前方電機(jī)引腳定義
#define LF_Motor_IN1 PA5 //LF電機(jī)使能引腳1
#define LF_Motor_IN2 PA4 //LF電機(jī)使能引腳2
#define LF_PWM PA0 //LF電機(jī)調(diào)速引腳
#define LF_ENCODER_A PB7 //LF編碼器A相引腳
#define LF_ENCODER_B PB6 //LF編碼器B相引腳
//以下為右前方電機(jī)引腳定義
#define RF_Motor_IN1 PB0 //RF電機(jī)使能引腳1
#define RF_Motor_IN2 PB1 //RF電機(jī)使能引腳2
#define RF_PWM PA2 //RF電機(jī)調(diào)速引腳
#define RF_ENCODER_A PB12 //RF編碼器A相引腳
#define RF_ENCODER_B PB13 //RF編碼器B相引腳
//以下為左后方電機(jī)引腳定義
#define LR_Motor_IN1 PA7 //LR電機(jī)使能引腳1
#define LR_Motor_IN2 PA6 //LR電機(jī)使能引腳2
#define LR_PWM PA1 //LR電機(jī)調(diào)速引腳
#define LR_ENCODER_A PB8 //LR編碼器A相引腳
#define LR_ENCODER_B PB9 //LR編碼器B相引腳
//以下為右后電機(jī)引腳定義
#define RR_Motor_IN1 PC14 //RR電機(jī)使能引腳1
#define RR_Motor_IN2 PC15 //RR電機(jī)使能引腳2
#define RR_PWM PA3 //RR電機(jī)調(diào)速引腳
#define RR_ENCODER_A PB14 //RR編碼器A相引腳
#define RR_ENCODER_B PB15 //RR編碼器B相引腳
volatile int i = 0; //調(diào)試用的公共變量
volatile int LF_Velocity = 0, LF_Count = 0; //左前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RF_Velocity = 0, RF_Count = 0; //左后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int LR_Velocity = 0, LR_Count = 0; //右前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RR_Velocity = 0, RR_Count = 0; //右后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
String Target_Value; //串口獲取的速度字符串變量
volatile int LF_value,RF_value,LR_value,RR_value; //用于存儲(chǔ)通過(guò)PI控制器計(jì)算得到的用于調(diào)整電機(jī)轉(zhuǎn)速的PWM值,最大值65535
float KP = 200, KI = 20; //PI參數(shù),此處調(diào)整會(huì)影響啟動(dòng)電流或引起震蕩
volatile float LF_Target=0,RF_Target=0,LR_Target=0,RR_Target=0; //電機(jī)轉(zhuǎn)速目標(biāo)值,5ms定時(shí)器最大可用范圍±280,2ms定時(shí)器,最大可用范圍±120
///*********** 限幅************!?。?!在此次的實(shí)驗(yàn)中不使用,所以startPWM設(shè)置為0。
// 以下兩個(gè)參數(shù)讓輸出的PWM在一個(gè)合理區(qū)間
// 當(dāng)輸出的PWM小于1500時(shí)電機(jī)不轉(zhuǎn) 所以要設(shè)置一個(gè)啟始PWM
// STM32單片機(jī)的PWM不能超過(guò)65535 所以 PWM_Restrict 起到限制上限的作用
//*****************************/
int startPWM = 0; //初始PWM
int PWM_Restrict = 65535; //startPW+PWM_Restric=65535<=65535
/**********外部中斷觸發(fā)計(jì)數(shù)器函數(shù)(4個(gè)電機(jī)需要獨(dú)立的外部中斷處理函數(shù))************
根據(jù)轉(zhuǎn)速的方向不同我們將計(jì)數(shù)器累計(jì)為正值或者負(fù)值(計(jì)數(shù)器累計(jì)為正值為負(fù)值為計(jì)數(shù)器方向)
只有方向累計(jì)正確了才可以實(shí)現(xiàn)正確的調(diào)整,否則會(huì)出現(xiàn)逆方向滿速旋轉(zhuǎn)
※※※※※※超級(jí)重點(diǎn)※※※※※※
所謂累計(jì)在正確的方向即
(1)計(jì)數(shù)器方向
(2)電機(jī)輸出方向(控制電機(jī)轉(zhuǎn)速方向的接線是正著接還是反著接)
(3)PI 控制器 里面的誤差(Basi)運(yùn)算是目標(biāo)值減當(dāng)前值(Target-Encoder),還是當(dāng)前值減目標(biāo)值(Encoder-Target)
三個(gè)方向只有對(duì)應(yīng)上才會(huì)有效果否則你接上就是使勁的朝著一個(gè)方向(一般來(lái)說(shuō)是反方向)滿速旋轉(zhuǎn),出現(xiàn)這種問(wèn)題,需要將AB相的線調(diào)過(guò)來(lái),或改下引腳定義
我例子里是我自己對(duì)應(yīng)好的,如果其他驅(qū)動(dòng)單片機(jī)在自己嘗試的時(shí)候出現(xiàn)滿速旋轉(zhuǎn)就是三個(gè)方向沒(méi)對(duì)應(yīng)上
下列函數(shù)中由于在A相上升沿觸發(fā)時(shí),B相是低電平,和A相下降沿觸發(fā)時(shí)B是高電平是一個(gè)方向,在這種觸發(fā)方式下,我們將count累計(jì)為正,另一種情況將count累計(jì)為負(fù)
********************************************/
void LF_READ_ENCODER_A() //左前方電機(jī)A相中斷
{
if (digitalRead(LF_ENCODER_A) == HIGH)
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count++; //根據(jù)另外一相電平判定方向
else
LF_Count--;
}
else
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count--; //根據(jù)另外一相電平判定方向
else
LF_Count++;
}
}
void RF_READ_ENCODER_A() //右前方電機(jī)A相中斷
{
if (digitalRead(RF_ENCODER_A) == HIGH)
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count++; //根據(jù)另外一相電平判定方向
else
RF_Count--;
}
else
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count--; //根據(jù)另外一相電平判定方向
else
RF_Count++;
}
}
void LR_READ_ENCODER_A() //左后方電機(jī)A相中斷
{
if (digitalRead(LR_ENCODER_A) == HIGH)
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count++; //根據(jù)另外一相電平判定方向
else
LR_Count--;
}
else
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count--; //根據(jù)另外一相電平判定方向
else
LR_Count++;
}
}
void RR_READ_ENCODER_A() //右后方電機(jī)A相中斷
{
if (digitalRead(RR_ENCODER_A) == HIGH)
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count++; //根據(jù)另外一相電平判定方向
else
RR_Count--;
}
else
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count--; //根據(jù)另外一相電平判定方向
else
RR_Count++;
}
}
/**********定時(shí)器中斷觸發(fā)函數(shù)(只需要1個(gè)定時(shí)器)*********/
HardwareTimer timer(3);//聲明使用3號(hào)定時(shí)器
void control()
{ // cli();
LF_Velocity = LF_Count; //把采用周期(內(nèi)部定時(shí)中斷周期)所累計(jì)的脈沖下降沿的個(gè)數(shù),賦值給速度
RF_Velocity = RF_Count;
LR_Velocity = LR_Count;
RR_Velocity = RR_Count;
LF_Count = 0; //脈沖計(jì)數(shù)器清零
RF_Count = 0;
LR_Count = 0;
RR_Count = 0;
//此實(shí)驗(yàn)中速度不閉環(huán),所以不執(zhí)行PID計(jì)算
LF_Set_PWM(LF_value); //將串口接收到的PWM值直接輸出給4個(gè)電機(jī)
RF_Set_PWM(RF_value);
LR_Set_PWM(LR_value);
RR_Set_PWM(RR_value);
Serial1.print(LF_value); //輸出左前輪電機(jī)當(dāng)前的PWM值。由于4個(gè)輪子的速度基本相同,取任意一個(gè)電機(jī)的轉(zhuǎn)速均可
Serial.print(",");
Serial1.println(LF_Velocity);//輸出左前輪的轉(zhuǎn)速
// sei();
}
/***********PI控制器****************/
int LF_Incremental_PI(int LF_Encoder, float LF_Target1)
{
static float LF_Bias, LF_MPWM = 0, LF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LF_Bias = LF_Target1 - LF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LF_MPWM += KP * (LF_Bias - LF_Last_bias) + KI * LF_Bias; //增量式PI控制計(jì)算
if (LF_MPWM > PWM_Restrict)
LF_MPWM = PWM_Restrict; //限幅
if (LF_MPWM < -PWM_Restrict)
LF_MPWM = -PWM_Restrict; //限幅
LF_Last_bias = LF_Bias; //保存上一次偏差
// Serial1.println(LF_MPWM);
// Serial1.print(" ");
// Serial1.println(LF_Encoder);
return LF_MPWM; //增量輸出
}
int RF_Incremental_PI(int RF_Encoder, float RF_Target1)
{
static float RF_Bias, RF_MPWM = 0, RF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RF_Bias = RF_Target1 - RF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RF_MPWM += KP * (RF_Bias - RF_Last_bias) + KI * RF_Bias; //增量式PI控制計(jì)算
if (RF_MPWM > PWM_Restrict)
RF_MPWM = PWM_Restrict; //限幅
if (RF_MPWM < -PWM_Restrict)
RF_MPWM = -PWM_Restrict; //限幅
RF_Last_bias = RF_Bias; //保存上一次偏差
//Serial.println(RF_MPWM);
//Serial.print(" ");
//Serial.println(RF_Encoder);
return RF_MPWM; //增量輸出
}
int LR_Incremental_PI(int LR_Encoder, float LR_Target1)
{
static float LR_Bias, LR_MPWM = 0, LR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LR_Bias = LR_Target1 - LR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LR_MPWM += KP * (LR_Bias - LR_Last_bias) + KI * LR_Bias; //增量式PI控制計(jì)算
if (LR_MPWM > PWM_Restrict)
LR_MPWM = PWM_Restrict; //限幅
if (LR_MPWM < -PWM_Restrict)
LR_MPWM = -PWM_Restrict; //限幅
LR_Last_bias = LR_Bias; //保存上一次偏差
//Serial.println(LR_MPWM);
//Serial.print(" ");
//Serial.println(LR_Encoder);
return LR_MPWM; //增量輸出
}
int RR_Incremental_PI(int RR_Encoder, float RR_Target1)
{
static float RR_Bias, RR_MPWM = 0, RR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RR_Bias = RR_Target1 - RR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RR_MPWM += KP * (RR_Bias - RR_Last_bias) + KI * RR_Bias; //增量式PI控制計(jì)算
if (RR_MPWM > PWM_Restrict)
RR_MPWM = PWM_Restrict; //限幅
if (RR_MPWM < -PWM_Restrict)
RR_MPWM = -PWM_Restrict; //限幅
RR_Last_bias = RR_Bias; //保存上一次偏差
//Serial.println(RR_MPWM);
//Serial.print(" ");
//Serial.println(RR_Encoder);
return RR_MPWM; //增量輸出
}
/**********電機(jī)驅(qū)動(dòng)函數(shù)*********/
void LF_Set_PWM(int LF_motora)
{
if (LF_motora > 0) //如果算出的PWM為正
{
digitalWrite(LF_Motor_IN1, 1);
digitalWrite(LF_Motor_IN2, 0);
pwmWrite(LF_PWM, LF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (LF_motora == 0) //如果PWM為0停車
{
digitalWrite(LF_Motor_IN2, 0);
digitalWrite(LF_Motor_IN1, 0);
} else if (LF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LF_Motor_IN1, 0);
digitalWrite(LF_Motor_IN2, 1);
pwmWrite(LF_PWM, -LF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RF_Set_PWM(int RF_motora)
{
if (RF_motora > 0) //如果算出的PWM為正
{
digitalWrite(RF_Motor_IN1, 1);
digitalWrite(RF_Motor_IN2, 0);
pwmWrite(RF_PWM, RF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RF_motora == 0) //如果PWM為0停車
{
digitalWrite(RF_Motor_IN2, 0);
digitalWrite(RF_Motor_IN1, 0);
} else if (RF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RF_Motor_IN1, 0);
digitalWrite(RF_Motor_IN2, 1);
pwmWrite(RF_PWM, -RF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void LR_Set_PWM(int LR_motora)
{
if (LR_motora > 0) //如果算出的PWM為正
{
digitalWrite(LR_Motor_IN1, 1);
digitalWrite(LR_Motor_IN2, 0);
pwmWrite(LR_PWM, LR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (LR_motora == 0) //如果PWM為0停車
{
digitalWrite(LR_Motor_IN2, 0);
digitalWrite(LR_Motor_IN1, 0);
} else if (LR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LR_Motor_IN1, 0);
digitalWrite(LR_Motor_IN2, 1);
pwmWrite(LR_PWM, -LR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RR_Set_PWM(int RR_motora)
{
if (RR_motora > 0) //如果算出的PWM為正
{
digitalWrite(RR_Motor_IN1, 1);
digitalWrite(RR_Motor_IN2, 0);
pwmWrite(RR_PWM, RR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RR_motora == 0) //如果PWM為0停車
{
digitalWrite(RR_Motor_IN2, 0);
digitalWrite(RR_Motor_IN1, 0);
} else if (RR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RR_Motor_IN1, 0);
digitalWrite(RR_Motor_IN2, 1);
pwmWrite(RR_PWM, -RR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void setup()
{
Serial1.begin(115200); //打開串口
Serial1.println("/*****開始驅(qū)動(dòng)*****/");
delay(1000);
pinMode(LED, OUTPUT); //調(diào)試用的閃爍LED,PC13
pinMode(LF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LF_ENCODER_B, INPUT);
pinMode(LF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式
pinMode(LF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LF_PWM, PWM_OPEN_DRAIN);
pinMode(RF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RF_ENCODER_B, INPUT);
pinMode(RF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式
pinMode(RF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RF_PWM, PWM_OPEN_DRAIN);
pinMode(LR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LR_ENCODER_B, INPUT);
pinMode(LR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式
pinMode(LR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LR_PWM, PWM_OPEN_DRAIN);
pinMode(RR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RR_ENCODER_B, INPUT);
pinMode(RR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式
pinMode(RR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RR_PWM, PWM_OPEN_DRAIN);
//下面是外部中斷的初始
attachInterrupt(LF_ENCODER_A, LF_READ_ENCODER_A, FALLING); //開啟對(duì)應(yīng)A相引腳的外部中斷,觸發(fā)方式為FALLING 即下降沿都觸發(fā),觸發(fā)的中斷函數(shù)為 READ_ENCODER_A
attachInterrupt(RF_ENCODER_A, RF_READ_ENCODER_A, FALLING);
attachInterrupt(LR_ENCODER_A, LR_READ_ENCODER_A, FALLING);
attachInterrupt(RR_ENCODER_A, RR_READ_ENCODER_A, FALLING);
//下面是定時(shí)器的初始化
timer.pause();// Pause the timer while we're configuring it
timer.setPeriod(5000); Set up period in microseconds,5000us=5ms
timer.setChannel1Mode(TIMER_OUTPUT_COMPARE);// Set up an interrupt on channel 1
timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update
timer.attachCompare1Interrupt(control);//定時(shí)中斷函數(shù)名聲明
timer.refresh();// Refresh the timer's count, prescale, and overflow
timer.resume();// Start the timer counting
}
void loop()
{
while (Serial1.available() > 0) //檢測(cè)串口是否接收到了數(shù)據(jù)
{
Target_Value = Serial.readString(); //讀取串口字符串
i = Target_Value.toFloat(); //將字符串轉(zhuǎn)換為浮點(diǎn)型,并將其賦給目標(biāo)值
LF_value=i;
LR_value=i;
RF_value=i;
RR_value=i;
delay(2000); //啟動(dòng)后延時(shí)運(yùn)行2s
i=0;
LF_value=0;
LR_value=0;
RF_value=0;
RR_value=0;
}
}
14、Arduino中串口函數(shù)如何使用?
調(diào)試均通過(guò)串口通訊完成,有關(guān)的函數(shù)使用辦法如下:
Arduino串口函數(shù)詳解-CSDN博客https://blog.csdn.net/u014421313/article/details/125421394?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169761775616800211527313%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169761775616800211527313&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-125421394-null-null.142%5Ev96%5Epc_search_result_base7&utm_term=%E4%B8%B2%E5%8F%A3%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90%20Arduino&spm=1018.2226.3001.4187
15、如何接收處理串口指令?
第10節(jié)中提供的代碼每次只能有效接收1個(gè)字節(jié)的指令。如果我們希望通過(guò)串口1次發(fā)送控制4個(gè)電機(jī)的速度,或通過(guò)1次發(fā)送控制底盤的線速度和偏航角,同時(shí)還希望提高串口通訊的可靠性,我們就需要使用串口指令控制底盤。簡(jiǎn)單說(shuō)來(lái),指令是按一定規(guī)則(通訊協(xié)議)編制的一系列字符串,通常由幀頭、數(shù)據(jù)和幀尾組成。幀頭用于識(shí)別指令類型、告訴接收端指令起始、觸發(fā)解析指令的函數(shù);數(shù)據(jù)就是指令包含的有效數(shù)據(jù),以字節(jié)為單位,根據(jù)指令協(xié)議有規(guī)律的放置數(shù)據(jù);幀尾用于告知接收端指令發(fā)送完成,也可能包含用于校驗(yàn)通訊可靠性的校驗(yàn)位。
簡(jiǎn)單的串口協(xié)議發(fā)送、接收辦法如下,需要靈活應(yīng)用串口函數(shù):
Arduino解析串口數(shù)據(jù)超簡(jiǎn)單方法-CSDN博客
函數(shù)sscanf可以幫助直接解析接收到的字符串:
sscanf函數(shù)使用詳解_faihung的博客-CSDN博客
如果需要復(fù)雜的自定義協(xié)議,可以參考下面:
Arduino自定義通信協(xié)議解析_arduino 自定義通信協(xié)議-CSDN博客
16、怎樣使用無(wú)線串口連接底盤?
? ? ?由于底盤在實(shí)驗(yàn)時(shí)處于移動(dòng)狀態(tài),為及時(shí)獲取數(shù)據(jù),需要將有線的串口UART更改為無(wú)線連接。再次感謝萬(wàn)能的淘寶,用于Arduino開發(fā)的藍(lán)牙串口套件參見(jiàn)下面的鏈接:
七星蟲藍(lán)牙 無(wú)線下載器 適用于arduino 無(wú)線下載 自動(dòng)復(fù)位-tmall.com天貓
相關(guān)的資料見(jiàn):https://pan.baidu.com/s/1ZjvOgEXduiqbXBLxzm5tnA 提取碼:ye54
連接時(shí)要注意:
(1)藍(lán)牙從機(jī)模塊(上圖左下角的模塊)的RX接控制板的TX(PA9)引腳、藍(lán)牙模塊的TX接控制板的RX(PA10)引腳。
(2)藍(lán)牙主機(jī)模塊(上圖左上角的模塊)直接接入電腦USB口,裝驅(qū)動(dòng)后會(huì)被識(shí)別為一個(gè)串口,默認(rèn)波特率應(yīng)該使用115200。如果模塊燈不停閃爍,就拔下重插。
(3)主機(jī)和從機(jī)都上電后二者會(huì)自行匹配連接,連接完成后兩者的led燈均常亮。正常使用距離為無(wú)遮擋10m范圍內(nèi)。
(4)本帖使用的stm32無(wú)法使用無(wú)線模塊的下載功能,由于此串口與下載程序用的串口一樣,所以實(shí)際上需要先用串口模塊(上圖右下角的模塊)燒錄好程序后再將藍(lán)牙模塊重新接入引腳。其他的串口已經(jīng)被控制電機(jī)的引腳占用了,所以不得不復(fù)用。Arduino官方的UNO和2560使用這款無(wú)線串口,可以實(shí)現(xiàn)無(wú)線下載。
17、如何使用串口上位機(jī)采集數(shù)據(jù)?
使用自帶串口記錄功能的上位機(jī)都能采集具體數(shù)據(jù),這里介紹SerialPlot這個(gè)軟件。
(1)首先根據(jù)藍(lán)牙主機(jī)實(shí)際的串口編號(hào)(通過(guò)硬件管理器查詢),設(shè)置波特率115200,其他部分默認(rèn)即可,然后點(diǎn)擊'open'。
(2)在Data Format處選擇‘ASCII’,因?yàn)榭刂破魇褂玫氖莗rintf函數(shù),發(fā)出來(lái)的是字符串。通道數(shù)選‘2’,因?yàn)樾枰L制輸入和輸出2個(gè)數(shù)據(jù)曲線。分隔符用 “,” ,用于軟件分辨2個(gè)數(shù)據(jù)。
(3)Plot處需要設(shè)置數(shù)據(jù)和顯示緩存的大小,設(shè)置為5000即可,Y軸Scale一定要設(shè)置為Auto。如下圖。
(4)Command這里設(shè)置幾條測(cè)試指令,用于快速發(fā)送。如下圖,可根據(jù)程序理解指令的含義,需要更多的指令可自行添加。
(5)Record這里在開始試驗(yàn)前一定要點(diǎn)擊Record。
(6)log這里可以看到串口收發(fā)的實(shí)際數(shù)據(jù),由于速度很快,停止串口后才能檢查。
18、如何處理獲取的數(shù)據(jù)?
上面獲取的數(shù)據(jù),輸入為PWM值(最大值65535),輸出為0.002s(定時(shí)中斷的時(shí)長(zhǎng))內(nèi)電機(jī)轉(zhuǎn)過(guò)的脈沖數(shù)。由12中可知PWM與直流電機(jī)的等效電壓一致,等效電壓由決定了電機(jī)勻速的最高轉(zhuǎn)速,因此可將PWM值與單位定時(shí)中斷內(nèi)產(chǎn)生脈沖數(shù)映射,進(jìn)而可將PWM經(jīng)過(guò)線性變化后轉(zhuǎn)換為目標(biāo)脈沖數(shù)。脈沖數(shù)與電機(jī)轉(zhuǎn)速是線性關(guān)系,在不講究真實(shí)速度的情況下,可將脈沖數(shù)視為電機(jī)實(shí)際的轉(zhuǎn)速。
經(jīng)過(guò)以上線性變換處理,數(shù)據(jù)存儲(chǔ)在CSV文件中,第一列為變換后的目標(biāo)“速度”(電機(jī)穩(wěn)定時(shí)的速度平均值即為目標(biāo)轉(zhuǎn)速),第二列為實(shí)際“轉(zhuǎn)速”。由于后面做反饋控制,需要將輸入與反饋?zhàn)霾钪?,用于喂給PID控制器,因此以上的變換是必須了。
以上數(shù)據(jù)經(jīng)過(guò)MATLAB的“系統(tǒng)辨識(shí)工具箱”處理后,就可以獲得系統(tǒng)傳遞函數(shù),具體可參考:MATLAB 系統(tǒng)辨識(shí) + PID 自動(dòng)調(diào)參_matlab參數(shù)辨識(shí)代碼-CSDN博客
需要提醒下,實(shí)驗(yàn)獲得的多組數(shù)據(jù)建議保留1組不用于系統(tǒng)辨識(shí),等辨識(shí)完成后用這組數(shù)據(jù)驗(yàn)證辨識(shí)的傳遞函數(shù)準(zhǔn)確性(擬合度?)。
19、使用Simulink搭建離線仿真模型
A、搭建開環(huán)模型
為了方便下面離線仿真,先將18中的CSV文件,需要將做好的CSV文件增加1列時(shí)間戳,間隔為0.002S(定時(shí)中斷的時(shí)長(zhǎng)),文件格式如下圖:
將18中處理好的CSV表格在simulink讀入,方法可見(jiàn)18中的參考鏈接。然后引入傳遞函數(shù)模塊,傳遞函數(shù)參數(shù)即為18中辨識(shí)好的參數(shù)。最終搭建的模型如下圖:
其中From Spreadsheet的設(shè)置如下:
傳遞函數(shù)模塊的設(shè)置如下:
B、搭建閉環(huán)模型
在模型中增加1個(gè)sum模塊,雙擊后設(shè)置正確信號(hào)的sign,通過(guò)2個(gè)增益模塊反饋到sum模塊輸入和輸出中形成閉環(huán)反饋回路,不斷調(diào)試增益的大小,運(yùn)行后觀察示波器的信號(hào)變化。搭建的模型如下圖:
C、閉環(huán)模型中增加PID控制器
在上面的模型中去掉增益模塊、增加PID模塊,搭建模型如下:
運(yùn)行后觀察示波器里的信號(hào)變化,修改PID參數(shù),觀察反饋的效果。注意,因?yàn)橛捎诘妆P的速度有最高值(對(duì)應(yīng)PWM值65535時(shí)的最高速度),因此要限制PID輸出的幅值,這里設(shè)置的是±200,需要根據(jù)底盤實(shí)際速度值進(jìn)行設(shè)置。
D、使用PID Tuner對(duì)PID參數(shù)進(jìn)行調(diào)試
雙擊PID模塊,點(diǎn)擊“tune…”按鈕,設(shè)定一定的上升時(shí)間和超調(diào)量,參考以下鏈接對(duì)PID參數(shù)進(jìn)行調(diào)試。
simulink中的PID模塊的使用_simulinkpid模塊怎么用-CSDN博客
如何在 Simulink 中使用 PID Tuner 進(jìn)行 PID 調(diào)參?-CSDN博客
20、在線閉環(huán)PID控制直流有刷電機(jī)
A、PID對(duì)電機(jī)速度控制的意義
通過(guò)以上的離線仿真實(shí)驗(yàn)可以大概認(rèn)識(shí)到PID控制的好處。相比簡(jiǎn)單的反饋控制,PID可以有效減少誤差,我們可以通過(guò)調(diào)整比例、積分、微分三個(gè)參數(shù)來(lái)改變系統(tǒng)的反應(yīng)速度、穩(wěn)態(tài)誤差,對(duì)于容易震蕩的系統(tǒng),我們還可以通過(guò)調(diào)整三個(gè)參數(shù)來(lái)抑制震蕩的問(wèn)題。
PID主要分為位置式和增量式,對(duì)速度閉環(huán)控制而言,增量式PID比較合適;對(duì)巡線的位置閉環(huán)控制而言,位置式PID更合適。以下是2者的詳細(xì)解釋。
電機(jī)控制進(jìn)階——PID速度控制 - 知乎 (zhihu.com)
以下的參考鏈接可以簡(jiǎn)要說(shuō)明直流電機(jī)PID閉環(huán)控制速度的原理:
如何使你的直流電機(jī)閉環(huán)?(PID講解)_電機(jī)閉環(huán)控制_憨豬在度假的博客-CSDN博客s
B、有刷直流電機(jī)速度閉環(huán)控制的軟硬件框架
軟件的參考代碼件本帖子第10節(jié),4個(gè)電機(jī)的控制部分是獨(dú)立的(其實(shí)可以將復(fù)用的函數(shù)合并,為了容易理解,也因?yàn)閼卸杈筒缓喜⒘耍?。每個(gè)電機(jī)控制部分可分為電機(jī)驅(qū)動(dòng)和電機(jī)速度反饋2個(gè)部分。
? ? ? ?電機(jī)驅(qū)動(dòng)是XX_Set_PWM函數(shù)管理,用于控制電機(jī)驅(qū)動(dòng)板,輸入的形參是PWM值。PWM值由PID控制函數(shù)XX_Incremental_PI(PID控制器實(shí)際上只使用了PI,微分D的貢獻(xiàn)太小被省略)產(chǎn)生。電機(jī)驅(qū)動(dòng)控制的部分在3號(hào)定時(shí)中斷中執(zhí)行。
? ? ? 電機(jī)速度反饋是有4個(gè)獨(dú)立的外部中斷函數(shù)XX_READ_ENCODER_A獲取,在1個(gè)定時(shí)中斷周期內(nèi)不斷累加,被讀取時(shí)會(huì)馬上清零。注意,此處的外部中斷僅使用上升沿或下降沿觸發(fā)即可,畢竟電機(jī)一圈已經(jīng)有500個(gè)脈沖。
? ? ? ?電機(jī)硬件的連接件本帖子第7節(jié),其中,每個(gè)電機(jī)需要stm32提供3個(gè)控制引腳(1個(gè)PWM引腳,2個(gè)數(shù)字輸出引腳)和2個(gè)速度反饋引腳(A\B相信號(hào)輸入引腳,其中A相的輸入引腳要設(shè)置為外部中斷,4個(gè)電機(jī)的A相引腳要使用不同的外部中斷源,詳細(xì)的可以參考第6節(jié))。接線時(shí)要注意,給A、B相供電要使用可使用5V或3.3V,給驅(qū)動(dòng)板供電只能使用3.3V(因?yàn)榻泳€圖中接入驅(qū)動(dòng)板的引腳只能耐受3.3V)。
C、PI參數(shù)的實(shí)際調(diào)整實(shí)驗(yàn)
第19節(jié)中獲取的PID參數(shù)(我調(diào)試出來(lái)的D參數(shù)過(guò)小,已經(jīng)被忽略)可以直接填入第10節(jié)的閉環(huán)代碼中PID參數(shù)。填入后,串口輸入的事4個(gè)電機(jī)的目標(biāo)速度,輸出的是某個(gè)電機(jī)的當(dāng)前速度參數(shù)。通過(guò)實(shí)驗(yàn)獲取數(shù)據(jù)并適當(dāng)處理后,將開環(huán)控制、閉環(huán)離線仿真和閉環(huán)控制的數(shù)據(jù)進(jìn)行比較,驗(yàn)證PID控制的效果。
如離線仿真的PID參數(shù)不甚理想,可以微調(diào)參數(shù)逐步調(diào)試到滿意為止。
也可以自行按照上一周付敏躍教授講解的PID調(diào)試方法,手動(dòng)調(diào)試出合用的PID參數(shù)。
參考代碼已經(jīng)在第10節(jié)中提供。
21、做一個(gè)簡(jiǎn)單的線控底盤
? ? ? ? 前面的內(nèi)容完整地講解了如何實(shí)現(xiàn)小車的閉環(huán)速度控制,但是參考代碼只寫了前進(jìn)和后退的內(nèi)容。為提高底盤控制的適應(yīng)性,通過(guò)適當(dāng)修改串口指令,通過(guò)串口輸入兩側(cè)輪子的速度值,可實(shí)現(xiàn)底盤的停止、拐彎(含原地轉(zhuǎn)向)、前進(jìn)、倒車等動(dòng)作。假如你準(zhǔn)備用另外的控制器(樹莓派、另外的單片機(jī)等)控制底盤的運(yùn)動(dòng),可以用此串口指令控制底盤的動(dòng)作。
? ? ? ? ?使用的控制串口還是1號(hào)串口(PA9、PA10),波特率115200,由6個(gè)連續(xù)發(fā)送的byte組成:
? ? ? ? ?指令中的第1個(gè)字節(jié)為幀頭,始終為0xFF;
? ? ? ? ?第2個(gè)字節(jié)為左邊2個(gè)輪子的速度正反轉(zhuǎn)符號(hào),正轉(zhuǎn)為0x10,反轉(zhuǎn)為0x01;
? ? ? ? ?第3個(gè)字節(jié)為左邊2個(gè)輪子的速度值,范圍為0x00~0xF0,對(duì)應(yīng)的速度值為0~240pulse每定時(shí)中斷周期(此處的中斷為專門為速度閉環(huán)應(yīng)用的定時(shí)中斷,周期設(shè)定為0.005s,如果定時(shí)周期變短,則對(duì)應(yīng)的速度最大允許值會(huì)相應(yīng)變小。最大速度是當(dāng)PWM最大時(shí)的轉(zhuǎn)速脈沖獲取值)。
? ? ? ? ?第4個(gè)字節(jié)為右邊2個(gè)輪子的速度正反轉(zhuǎn)符號(hào),正轉(zhuǎn)為0x10,反轉(zhuǎn)為0x01;
? ? ? ? ?第5個(gè)字節(jié)為右邊2個(gè)輪子的速度值,范圍為0x00~0xF0。
? ? ? ? ?第6個(gè)字節(jié)為幀尾,始終為0xFE。
? ? ? ? ?為避免指令傳輸錯(cuò)漏,對(duì)收到數(shù)據(jù)需要幀頭和幀尾的校驗(yàn),確定幀頭和禎尾沒(méi)問(wèn)題后才可以將指令解析并將兩邊輪子的目標(biāo)速度傳遞給電機(jī)控制函數(shù)。
? ? ? ? ?參考的代碼如下(由于調(diào)試不多,可能會(huì)存在bug,僅供參考)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-754673.html
? ? ? ? 調(diào)試時(shí)不能使用Arduino的串口助手(因?yàn)橹荒馨l(fā)送字符串,不能發(fā)送16進(jìn)制字節(jié)),需要使用其他類型的串口助手,如之前的SerialPlot。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-754673.html
//PID速度閉環(huán)控制4個(gè)電機(jī),使用串口指令控制底盤運(yùn)動(dòng)
#define LED PC13 //調(diào)試用的LED
//以下為左前方電機(jī)引腳定義
#define LF_Motor_IN1 PA5 //LF電機(jī)使能引腳1
#define LF_Motor_IN2 PA4 //LF電機(jī)使能引腳2
#define LF_PWM PA0 //LF電機(jī)調(diào)速引腳
#define LF_ENCODER_A PB7 //LF編碼器A相引腳
#define LF_ENCODER_B PB6 //LF編碼器B相引腳
//以下為右前方電機(jī)引腳定義
#define RF_Motor_IN1 PB0 //RF電機(jī)使能引腳1
#define RF_Motor_IN2 PB1 //RF電機(jī)使能引腳2
#define RF_PWM PA2 //RF電機(jī)調(diào)速引腳
#define RF_ENCODER_A PB12 //RF編碼器A相引腳
#define RF_ENCODER_B PB13 //RF編碼器B相引腳
//以下為左后方電機(jī)引腳定義
#define LR_Motor_IN1 PA7 //LR電機(jī)使能引腳1
#define LR_Motor_IN2 PA6 //LR電機(jī)使能引腳2
#define LR_PWM PA1 //LR電機(jī)調(diào)速引腳
#define LR_ENCODER_A PB8 //LR編碼器A相引腳
#define LR_ENCODER_B PB9 //LR編碼器B相引腳
//以下為右后電機(jī)引腳定義
#define RR_Motor_IN1 PC14 //RR電機(jī)使能引腳1
#define RR_Motor_IN2 PC15 //RR電機(jī)使能引腳2
#define RR_PWM PA3 //RR電機(jī)調(diào)速引腳
#define RR_ENCODER_A PB14 //RR編碼器A相引腳
#define RR_ENCODER_B PB15 //RR編碼器B相引腳
volatile int i = 0; //調(diào)試用的公共變量
volatile byte Command[6]; //串口控制指令存儲(chǔ),6個(gè)字節(jié)
volatile int LF_Velocity = 0, LF_Count = 0; //左前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RF_Velocity = 0, RF_Count = 0; //左后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int LR_Velocity = 0, LR_Count = 0; //右前方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
volatile int RR_Velocity = 0, RR_Count = 0; //右后方電機(jī)編碼器,Count計(jì)數(shù)變量 Velocity存儲(chǔ)設(shè)定時(shí)間內(nèi)A相下降沿的個(gè)數(shù),與實(shí)際轉(zhuǎn)速正相關(guān)
String Target_Value; //串口獲取的速度字符串變量
volatile int LF_value,RF_value,LR_value,RR_value; //用于存儲(chǔ)通過(guò)PI控制器計(jì)算得到的用于調(diào)整電機(jī)轉(zhuǎn)速的PWM值,最大值65535
float KP = 200, KI = 20; //PI參數(shù),此處調(diào)整會(huì)影響啟動(dòng)電流,低速時(shí)可能引起震蕩
volatile float LF_Target=0,RF_Target=0,LR_Target=0,RR_Target=0; //電機(jī)轉(zhuǎn)速目標(biāo)值,5ms定時(shí)器最大可用范圍±280,2ms定時(shí)器,最大可用范圍±120
///*********** 限幅************
// 以下兩個(gè)參數(shù)讓輸出的PWM在一個(gè)合理區(qū)間
// 當(dāng)輸出的PWM小于1500時(shí)電機(jī)不轉(zhuǎn) 所以要設(shè)置一個(gè)啟始PWM
// STM32單片機(jī)的PWM不能超過(guò)65535 所以 PWM_Restrict 起到限制上限的作用
//*****************************/
int startPWM = 1500; //克服死區(qū)的啟始PWM
int PWM_Restrict = 64000; //startPW+PWM_Restric=65500<65535
/**********外部中斷觸發(fā)計(jì)數(shù)器函數(shù)(4個(gè)電機(jī)需要獨(dú)立的外部中斷處理函數(shù))************
根據(jù)轉(zhuǎn)速的方向不同我們將計(jì)數(shù)器累計(jì)為正值或者負(fù)值(計(jì)數(shù)器累計(jì)為正值為負(fù)值為計(jì)數(shù)器方向)
只有方向累計(jì)正確了才可以實(shí)現(xiàn)正確的調(diào)整,否則會(huì)出現(xiàn)逆方向滿速旋轉(zhuǎn)
※※※※※※超級(jí)重點(diǎn)※※※※※※
所謂累計(jì)在正確的方向即
(1)計(jì)數(shù)器方向
(2)電機(jī)輸出方向(控制電機(jī)轉(zhuǎn)速方向的接線是正著接還是反著接)
(3)PI 控制器 里面的誤差(Basi)運(yùn)算是目標(biāo)值減當(dāng)前值(Target-Encoder),還是當(dāng)前值減目標(biāo)值(Encoder-Target)
三個(gè)方向只有對(duì)應(yīng)上才會(huì)有效果否則你接上就是使勁的朝著一個(gè)方向(一般來(lái)說(shuō)是反方向)滿速旋轉(zhuǎn),出現(xiàn)這種問(wèn)題,需要將AB相的線調(diào)過(guò)來(lái),或改下引腳定義
我例子里是我自己對(duì)應(yīng)好的,如果其他驅(qū)動(dòng)單片機(jī)在自己嘗試的時(shí)候出現(xiàn)滿速旋轉(zhuǎn)就是三個(gè)方向沒(méi)對(duì)應(yīng)上
下列函數(shù)中由于在A相上升沿觸發(fā)時(shí),B相是低電平,和A相下降沿觸發(fā)時(shí)B是高電平是一個(gè)方向,在這種觸發(fā)方式下,我們將count累計(jì)為正,另一種情況將count累計(jì)為負(fù)
********************************************/
void LF_READ_ENCODER_A() //左前方電機(jī)A相中斷
{
if (digitalRead(LF_ENCODER_A) == HIGH)
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count++; //根據(jù)另外一相電平判定方向
else
LF_Count--;
}
else
{
if (digitalRead(LF_ENCODER_B) == LOW)
LF_Count--; //根據(jù)另外一相電平判定方向
else
LF_Count++;
}
}
void RF_READ_ENCODER_A() //右前方電機(jī)A相中斷
{
if (digitalRead(RF_ENCODER_A) == HIGH)
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count++; //根據(jù)另外一相電平判定方向
else
RF_Count--;
}
else
{
if (digitalRead(RF_ENCODER_B) == LOW)
RF_Count--; //根據(jù)另外一相電平判定方向
else
RF_Count++;
}
}
void LR_READ_ENCODER_A() //左后方電機(jī)A相中斷
{
if (digitalRead(LR_ENCODER_A) == HIGH)
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count++; //根據(jù)另外一相電平判定方向
else
LR_Count--;
}
else
{
if (digitalRead(LR_ENCODER_B) == LOW)
LR_Count--; //根據(jù)另外一相電平判定方向
else
LR_Count++;
}
}
void RR_READ_ENCODER_A() //右后方電機(jī)A相中斷
{
if (digitalRead(RR_ENCODER_A) == HIGH)
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count++; //根據(jù)另外一相電平判定方向
else
RR_Count--;
}
else
{
if (digitalRead(RR_ENCODER_B) == LOW)
RR_Count--; //根據(jù)另外一相電平判定方向
else
RR_Count++;
}
}
/**********定時(shí)器中斷觸發(fā)函數(shù)(只需要1個(gè)定時(shí)器)*********/
HardwareTimer timer(3);//聲明使用3號(hào)定時(shí)器
void control()
{ // cli(); //關(guān)閉所有中斷,此處嘗試不加也行
//把采用周期(內(nèi)部定時(shí)中斷周期)所累計(jì)的脈沖下降沿的個(gè)數(shù),賦值給速度
LF_Velocity = LF_Count;
RF_Velocity = RF_Count;
LR_Velocity = LR_Count;
RR_Velocity = RR_Count;
//脈沖計(jì)數(shù)器清零
LF_Count = 0;
RF_Count = 0;
LR_Count = 0;
RR_Count = 0;
//以下為4個(gè)電機(jī)同時(shí)計(jì)算PID參數(shù)
LF_value = LF_Incremental_PI(LF_Velocity, LF_Target); //通過(guò)目標(biāo)值和當(dāng)前值在PID函數(shù)下算出我們需要調(diào)整用的PWM值
RF_value = RF_Incremental_PI(RF_Velocity, RF_Target);
LR_value = LR_Incremental_PI(LR_Velocity, LR_Target);
RR_value = RR_Incremental_PI(RR_Velocity, RR_Target);
//以下為4個(gè)電機(jī)同時(shí)輸出PWM值
LF_Set_PWM(LF_value);
RF_Set_PWM(RF_value);
LR_Set_PWM(LR_value);
RR_Set_PWM(RR_value);
//以下為調(diào)試代碼,調(diào)試完成需要?jiǎng)h除,避免浪費(fèi)CPU資源
Serial1.print(LF_value);//輸出左前輪的PWM值
Serial.print(",");
Serial1.println(LF_Velocity);//輸出左前輪的轉(zhuǎn)速
// sei(); //打開所有中斷,此處嘗試不加也行
}
/***********PI控制器****************/
int LF_Incremental_PI(int LF_Encoder, float LF_Target1)
{
static float LF_Bias, LF_MPWM = 0, LF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LF_Bias = LF_Target1 - LF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LF_MPWM += KP * (LF_Bias - LF_Last_bias) + KI * LF_Bias; //增量式PI控制計(jì)算
if (LF_MPWM > PWM_Restrict)
LF_MPWM = PWM_Restrict; //限幅
if (LF_MPWM < -PWM_Restrict)
LF_MPWM = -PWM_Restrict; //限幅
LF_Last_bias = LF_Bias; //保存上一次偏差
return LF_MPWM; //增量輸出
}
int RF_Incremental_PI(int RF_Encoder, float RF_Target1)
{
static float RF_Bias, RF_MPWM = 0, RF_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RF_Bias = RF_Target1 - RF_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RF_MPWM += KP * (RF_Bias - RF_Last_bias) + KI * RF_Bias; //增量式PI控制計(jì)算
if (RF_MPWM > PWM_Restrict)
RF_MPWM = PWM_Restrict; //限幅
if (RF_MPWM < -PWM_Restrict)
RF_MPWM = -PWM_Restrict; //限幅
RF_Last_bias = RF_Bias; //保存上一次偏差
return RF_MPWM; //增量輸出
}
int LR_Incremental_PI(int LR_Encoder, float LR_Target1)
{
static float LR_Bias, LR_MPWM = 0, LR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
LR_Bias = LR_Target1 - LR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
LR_MPWM += KP * (LR_Bias - LR_Last_bias) + KI * LR_Bias; //增量式PI控制計(jì)算
if (LR_MPWM > PWM_Restrict)
LR_MPWM = PWM_Restrict; //限幅
if (LR_MPWM < -PWM_Restrict)
LR_MPWM = -PWM_Restrict; //限幅
LR_Last_bias = LR_Bias; //保存上一次偏差
return LR_MPWM; //增量輸出
}
int RR_Incremental_PI(int RR_Encoder, float RR_Target1)
{
static float RR_Bias, RR_MPWM = 0, RR_Last_bias = 0; //定義全局靜態(tài)浮點(diǎn)型變量 PWM,Bias(本次偏差),Last_bias(上次偏差)
RR_Bias = RR_Target1 - RR_Encoder; //計(jì)算偏差,目標(biāo)值減去當(dāng)前值
RR_MPWM += KP * (RR_Bias - RR_Last_bias) + KI * RR_Bias; //增量式PI控制計(jì)算
if (RR_MPWM > PWM_Restrict)
RR_MPWM = PWM_Restrict; //限幅
if (RR_MPWM < -PWM_Restrict)
RR_MPWM = -PWM_Restrict; //限幅
RR_Last_bias = RR_Bias; //保存上一次偏差
return RR_MPWM; //增量輸出
}
/**********電機(jī)驅(qū)動(dòng)函數(shù)*********/
void LF_Set_PWM(int LF_motora)
{
if (LF_motora > 0) //如果算出的PWM為正
{
digitalWrite(LF_Motor_IN1, 1);
digitalWrite(LF_Motor_IN2, 0);
pwmWrite(LF_PWM, LF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整,此處的PWM輸出函數(shù)跟Mega2560不同
} else if (LF_motora == 0) //如果PWM為0停車
{
digitalWrite(LF_Motor_IN2, 0);
digitalWrite(LF_Motor_IN1, 0);
} else if (LF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LF_Motor_IN1, 0);
digitalWrite(LF_Motor_IN2, 1);
pwmWrite(LF_PWM, -LF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RF_Set_PWM(int RF_motora)
{
if (RF_motora > 0) //如果算出的PWM為正
{
digitalWrite(RF_Motor_IN1, 1);
digitalWrite(RF_Motor_IN2, 0);
pwmWrite(RF_PWM, RF_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RF_motora == 0) //如果PWM為0停車
{
digitalWrite(RF_Motor_IN2, 0);
digitalWrite(RF_Motor_IN1, 0);
} else if (RF_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RF_Motor_IN1, 0);
digitalWrite(RF_Motor_IN2, 1);
pwmWrite(RF_PWM, -RF_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void LR_Set_PWM(int LR_motora)
{
if (LR_motora > 0) //如果算出的PWM為正
{
digitalWrite(LR_Motor_IN1, 1);
digitalWrite(LR_Motor_IN2, 0);
pwmWrite(LR_PWM, LR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (LR_motora == 0) //如果PWM為0停車
{
digitalWrite(LR_Motor_IN2, 0);
digitalWrite(LR_Motor_IN1, 0);
} else if (LR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(LR_Motor_IN1, 0);
digitalWrite(LR_Motor_IN2, 1);
pwmWrite(LR_PWM, -LR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void RR_Set_PWM(int RR_motora)
{
if (RR_motora > 0) //如果算出的PWM為正
{
digitalWrite(RR_Motor_IN1, 1);
digitalWrite(RR_Motor_IN2, 0);
pwmWrite(RR_PWM, RR_motora + startPWM); //讓PWM在設(shè)定正轉(zhuǎn)方向(我們認(rèn)為的正轉(zhuǎn)方向)正向輸出調(diào)整
} else if (RR_motora == 0) //如果PWM為0停車
{
digitalWrite(RR_Motor_IN2, 0);
digitalWrite(RR_Motor_IN1, 0);
} else if (RR_motora < 0) //如果算出的PWM為負(fù)
{
digitalWrite(RR_Motor_IN1, 0);
digitalWrite(RR_Motor_IN2, 1);
pwmWrite(RR_PWM, -RR_motora + startPWM); //讓PWM在設(shè)定反轉(zhuǎn)方向反向輸出調(diào)整
}
}
void setup()
{
Serial1.begin(115200); //打開串口
Serial1.println("/*****開始驅(qū)動(dòng)*****/");
delay(1000);
pinMode(LED, OUTPUT); //調(diào)試用的閃爍LED,PC13
pinMode(LF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LF_ENCODER_B, INPUT);
pinMode(LF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(LF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LF_PWM, PWM_OPEN_DRAIN);
pinMode(RF_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RF_ENCODER_B, INPUT);
pinMode(RF_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(RF_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RF_PWM, PWM_OPEN_DRAIN);
pinMode(LR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(LR_ENCODER_B, INPUT);
pinMode(LR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(LR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(LR_PWM, PWM_OPEN_DRAIN);
pinMode(RR_ENCODER_A, INPUT); //設(shè)置兩個(gè)相線為輸入模式
pinMode(RR_ENCODER_B, INPUT);
pinMode(RR_Motor_IN1, OUTPUT_OPEN_DRAIN); //設(shè)置兩個(gè)驅(qū)動(dòng)引腳為輸出模式,由于stm32的引腳接收5V作為輸出時(shí)需要工作在開漏輸出模式下,這與Mega2560函數(shù)定義是有差別的,Mega2560可以直接推挽輸出5V,引腳直接配置為OUTPUT即可
pinMode(RR_Motor_IN2, OUTPUT_OPEN_DRAIN);
pinMode(RR_PWM, PWM_OPEN_DRAIN);
//下面是外部中斷的初始化
attachInterrupt(LF_ENCODER_A, LF_READ_ENCODER_A, FALLING); //開啟對(duì)應(yīng)A相引腳的外部中斷,觸發(fā)方式為FALLING 即下降沿都觸發(fā),觸發(fā)的中斷函數(shù)為 LF_ENCODER_A
attachInterrupt(RF_ENCODER_A, RF_READ_ENCODER_A, FALLING);
attachInterrupt(LR_ENCODER_A, LR_READ_ENCODER_A, FALLING);
attachInterrupt(RR_ENCODER_A, RR_READ_ENCODER_A, FALLING);
//下面是定時(shí)器的初始化,Mega2560的用法與此處有差異,參考引用庫(kù)函數(shù)才行
timer.pause();// Pause the timer while we're configuring it
timer.setPeriod(5000); // Set up period in microseconds,5000us=5ms
timer.setChannel1Mode(TIMER_OUTPUT_COMPARE);// Set up an interrupt on channel 1
timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update
timer.attachCompare1Interrupt(control);//定時(shí)中斷函數(shù)名聲明
timer.refresh();// Refresh the timer's count, prescale, and overflow
timer.resume();// Start the timer counting
}
void loop()
{
///串口指令接收處理函數(shù)
while (Serial1.available() >= 6) //檢測(cè)串口是否接收到了不少于6個(gè)數(shù)據(jù)
{
Command[0] = Serial1.read(); //獲取第一個(gè)數(shù)據(jù)
while (Command[0]!=0xFF) //等待幀頭為0xFF
{
Command[0] = Serial1.read(); //獲取串口緩存中的字節(jié)數(shù)
i=Serial1.available();
if(i <= 0) //超過(guò)5個(gè)指令依舊不對(duì)后退出等待幀頭的循環(huán)
break;
}
i=Serial1.available();
if (i>=5)
{
Command[1] = Serial1.read();
Command[2] = Serial1.read();
Command[3] = Serial1.read();
Command[4] = Serial1.read();
Command[5] = Serial1.read();
if (Command[5] == 0xFE) //幀尾校驗(yàn)
{
if(Command[1]==0x10) //左輪正轉(zhuǎn)
{LF_Target=(float)Command[2];LR_Target=LF_Target;}
if(Command[1]==0x01) //左輪反轉(zhuǎn)
{LF_Target=(0-(float)Command[2]);LR_Target=LF_Target;}
if(Command[3]==0x10) //右輪正轉(zhuǎn)
{RF_Target=(float)Command[4];RR_Target=LF_Target;}
if(Command[3]==0x01) //右輪反轉(zhuǎn)
{RF_Target=(0-(float)Command[4]);RR_Target=LF_Target;}
}
while(Serial3.read() >= 0){} //清空串口緩存
}
else
{
delay(10); //延時(shí),完成后面的數(shù)據(jù)接收(不超過(guò)10個(gè)字節(jié)的時(shí)間)
i=Serial1.available();
if (i>=5)
{
Command[1] = Serial1.read();
Command[2] = Serial1.read();
Command[3] = Serial1.read();
Command[4] = Serial1.read();
Command[5] = Serial1.read();
if (Command[5] == 0xFE) //幀尾校驗(yàn)
{
if(Command[1]==0x10) //左輪正轉(zhuǎn)
{LF_Target=(float)Command[2];LR_Target=LF_Target;}
if(Command[1]==0x01) //左輪反轉(zhuǎn)
{LF_Target=(0-(float)Command[2]);LR_Target=LF_Target;}
if(Command[3]==0x10) //右輪正轉(zhuǎn)
{RF_Target=(float)Command[4];RR_Target=LF_Target;}
if(Command[3]==0x01) //右輪反轉(zhuǎn)
{RF_Target=(0-(float)Command[4]);RR_Target=LF_Target;}
}
}
while(Serial3.read() >= 0){} //清空串口緩存
}
}
}
到了這里,關(guān)于基于STM32F103C8T6使用Arduino IDE編程閉環(huán)控制4個(gè)帶編碼器的有刷直流電機(jī)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!