国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

基于STM32F103C8T6使用Arduino IDE編程閉環(huán)控制4個(gè)帶編碼器的有刷直流電機(jī)

這篇具有很好參考價(jià)值的文章主要介紹了基于STM32F103C8T6使用Arduino IDE編程閉環(huán)控制4個(gè)帶編碼器的有刷直流電機(jī)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

題記:標(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)什么樣子?

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

有人問(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ù)和選型參考:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

? ? ? ? 需要額外指出的是,本帖使用的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

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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版本,參考鏈接如下:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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板子接口圖如下,要是能找到更清楚的再更新:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

下面這張圖感覺(jué)更清楚,但引腳定義有點(diǎn)隨意。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide? ? ?

?圖中可以看到引腳存在復(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)參考鏈接的截圖:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

?正點(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博客arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide【精選】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)一下。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

8、電源怎么設(shè)計(jì)?

由于電機(jī)工作在24V下,建議采用DJI的?TB48S/TB47S航模電池(主要是有現(xiàn)成的)。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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ī)的等效模型如下:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

其中,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é)論類似:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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天貓

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

相關(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'。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

(2)在Data Format處選擇‘ASCII’,因?yàn)榭刂破魇褂玫氖莗rintf函數(shù),發(fā)出來(lái)的是字符串。通道數(shù)選‘2’,因?yàn)樾枰L制輸入和輸出2個(gè)數(shù)據(jù)曲線。分隔符用 “,” ,用于軟件分辨2個(gè)數(shù)據(jù)。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

(3)Plot處需要設(shè)置數(shù)據(jù)和顯示緩存的大小,設(shè)置為5000即可,Y軸Scale一定要設(shè)置為Auto。如下圖。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

(4)Command這里設(shè)置幾條測(cè)試指令,用于快速發(fā)送。如下圖,可根據(jù)程序理解指令的含義,需要更多的指令可自行添加。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

(5)Record這里在開始試驗(yàn)前一定要點(diǎn)擊Record。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

(6)log這里可以看到串口收發(fā)的實(shí)際數(shù)據(jù),由于速度很快,停止串口后才能檢查。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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)),文件格式如下圖:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

將18中處理好的CSV表格在simulink讀入,方法可見(jiàn)18中的參考鏈接。然后引入傳遞函數(shù)模塊,傳遞函數(shù)參數(shù)即為18中辨識(shí)好的參數(shù)。最終搭建的模型如下圖:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

其中From Spreadsheet的設(shè)置如下:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

傳遞函數(shù)模塊的設(shè)置如下:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

B、搭建閉環(huán)模型

在模型中增加1個(gè)sum模塊,雙擊后設(shè)置正確信號(hào)的sign,通過(guò)2個(gè)增益模塊反饋到sum模塊輸入和輸出中形成閉環(huán)反饋回路,不斷調(diào)試增益的大小,運(yùn)行后觀察示波器的信號(hào)變化。搭建的模型如下圖:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

C、閉環(huán)模型中增加PID控制器

在上面的模型中去掉增益模塊、增加PID模塊,搭建模型如下:

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

運(yùn)行后觀察示波器里的信號(hào)變化,修改PID參數(shù),觀察反饋的效果。注意,因?yàn)橛捎诘妆P的速度有最高值(對(duì)應(yīng)PWM值65535時(shí)的最高速度),因此要限制PID輸出的幅值,這里設(shè)置的是±200,需要根據(jù)底盤實(shí)際速度值進(jìn)行設(shè)置。

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

arduino開發(fā)stm32f103c8t6,stm32,嵌入式硬件,單片機(jī),ide

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,僅供參考)。

? ? ? ? 調(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • STM32f103c8t6模板的搭建-基于正點(diǎn)例程

    STM32f103c8t6模板的搭建-基于正點(diǎn)例程

    ? ? ? ?筆者認(rèn)為正點(diǎn)編寫的官方例程結(jié)構(gòu)較為整潔,可以便于后期的例程開發(fā),如果開發(fā)者對(duì)于項(xiàng)目開發(fā)中芯片要求較高的話,有很多人會(huì)選擇正點(diǎn)的開發(fā)板,但是通常大多數(shù)是stm32初學(xué)者會(huì)選擇用價(jià)格更為便宜的c8t6來(lái)進(jìn)行學(xué)習(xí),而正點(diǎn)選用的教程開發(fā)板大多都是些RC、ZE、

    2024年02月06日
    瀏覽(99)
  • 基于stm32f103c8t6的fft頻率計(jì)

    之前項(xiàng)目中需要用到正弦信號(hào)的頻率測(cè)量,也參考了幾個(gè)大佬的博客(鏈接如下),但可能是由于stm32的型號(hào)不匹配,雖然也在網(wǎng)上查了一些需要修改的地方,但結(jié)果一直不太對(duì),后來(lái)經(jīng)過(guò)自己摸索結(jié)果終于對(duì)了,在這里給大家分享下,具體原理不在贅述。 參考的部分大佬博

    2024年02月14日
    瀏覽(49)
  • 基于STM32F103C8T6的HC-06藍(lán)牙通信

    基于STM32F103C8T6的HC-06藍(lán)牙通信

    如果朋友們 遇到了如下問(wèn)題 ,可以仔細(xì)借鑒本文章和另一篇專門講解 藍(lán)牙通信問(wèn)題 的文章,一定能夠解決你在藍(lán)牙通信時(shí)遇到的諸多困難 1.在調(diào)試藍(lán)牙模塊AT指令時(shí)無(wú)返回值 2.身邊 無(wú)USB轉(zhuǎn)TTL模塊 可以直接調(diào)試藍(lán)牙模塊(本人就是由于無(wú)模塊花了了整整一天才調(diào)試成功)

    2024年02月03日
    瀏覽(34)
  • 基于STM32F103C8T6ADC檢測(cè)交流電壓

    基于STM32F103C8T6ADC檢測(cè)交流電壓

    上篇文章寫了硬件部分的實(shí)現(xiàn)思路,通過(guò)采樣電阻的到小電壓后經(jīng)過(guò)二級(jí)放大電路得到單片機(jī)可處理的交流電壓,此文介紹了如何采用單片機(jī)采集交流電壓以及stm32ADC外設(shè)的使用。首先是硬件電路部分。 ?電路沒(méi)有采用核心板,而是直接將芯片焊接到主板上,采用type-c接口供

    2024年02月12日
    瀏覽(35)
  • HX711壓力傳感器(基于STM32F103C8T6)

    HX711模塊是我們目前比較常見(jiàn)的壓力傳感器模塊,主要的作用是用來(lái)做壓力檢測(cè),重量監(jiān)測(cè)等等。博主的這篇博文主要實(shí)現(xiàn)功能為,在對(duì)重量或者壓力進(jìn)行監(jiān)測(cè)的同時(shí),可以累加或者清零數(shù)值,在此基礎(chǔ)上就可以對(duì)比如飲水量進(jìn)行統(tǒng)計(jì)等等。 HX711模塊是市面上比較常見(jiàn)的模塊

    2024年02月11日
    瀏覽(19)
  • 基于stm32f103c8t6的定時(shí)器詳解(持續(xù)更新)

    基于stm32f103c8t6的定時(shí)器詳解(持續(xù)更新)

    先聲明:stm32f103c8t6中沒(méi)有基本定時(shí)器、只有TIM1-TIM4:分別是高級(jí)定時(shí)器和通用定時(shí)器(對(duì)照下圖請(qǐng)自行閱讀stm32f103x的datasheet) 1、定時(shí)器功能:定時(shí)、輸出比較、輸入捕獲、互補(bǔ)輸出,其中基本定時(shí)器只有定時(shí)功能、通用定時(shí)器只沒(méi)有互補(bǔ)輸出功能、高級(jí)定時(shí)器具有所有功能

    2023年04月24日
    瀏覽(28)
  • [STM32F103C8T6]基于stm32的循跡,跟隨,避障智能小車

    [STM32F103C8T6]基于stm32的循跡,跟隨,避障智能小車

    目錄 1.小車驅(qū)動(dòng)主要是通過(guò)L9110S模塊來(lái)驅(qū)動(dòng)電機(jī) motor.c 2.我們可以加入串口控制電機(jī)驅(qū)動(dòng)(重寫串口接收回調(diào)函數(shù),和重定向printf) Uart.c main.c? 3.點(diǎn)動(dòng)功能 uart.c main.c 為什么使用的是HAL_Delay()要設(shè)置滴答定時(shí)器的中斷優(yōu)先級(jí)呢? 4.小車PWM調(diào)速,? 6.跟隨功能 7.避障功能 超聲波測(cè)距

    2024年02月13日
    瀏覽(43)
  • 基于STM32F103C8T6的UAV飛控板硬件設(shè)計(jì)

    基于STM32F103C8T6的UAV飛控板硬件設(shè)計(jì)

    一、主控單元: ????????主控單元基于意法半導(dǎo)體公司的STM32F103C8T6單片機(jī)進(jìn)行設(shè)計(jì)。STM32F103C8T6DE 內(nèi)核為ARM Cortex-M3;最大主頻:72MHz ;工作電壓范圍:2V~3.6V ;程序存儲(chǔ)容量:64KB; 程序存儲(chǔ)器類型:FLASH ;RAM總?cè)萘浚?0KB; GPIO端口數(shù)量:37 ;封裝為L(zhǎng)QFP-48;串行單線調(diào)試(

    2024年02月08日
    瀏覽(23)
  • 基于stm32f103c8t6及AS608-----指紋鎖項(xiàng)目

    基于stm32f103c8t6及AS608-----指紋鎖項(xiàng)目

    ? ? ? ? ? 博主純小白, 本文適合于初學(xué)者,大佬還請(qǐng)勿噴,歡迎提出意見(jiàn),有紕漏之處將及時(shí)糾正。 在淺學(xué)了stmf103c8t6后,想著依據(jù)現(xiàn)在所擁有的知識(shí)和能力做一個(gè)小項(xiàng)目。 注:工程代碼在文章末尾。 掌握C語(yǔ)言基礎(chǔ)....這個(gè)最基礎(chǔ)啦... 接觸過(guò)類似單片機(jī),稍微看得懂芯片

    2023年04月09日
    瀏覽(19)
  • 舵機(jī)控制(STM32F103C8T6)

    舵機(jī)控制(STM32F103C8T6)

    ? ? ? ? 本文是以 STM32F103C8T6 作為主控芯片,通過(guò)PB6端口輸出PWM,實(shí)現(xiàn)控制180°舵機(jī)。 (一)概述 ? ? ? ? 舵機(jī)是一種位置伺服驅(qū)動(dòng)器器,是一種帶有輸出軸的小裝置。當(dāng)我們向伺服器發(fā)送一個(gè)控制信號(hào)時(shí),輸出軸就可以轉(zhuǎn)到特定的位置。只在控制信號(hào)持續(xù)不變,伺服機(jī)構(gòu)就

    2023年04月09日
    瀏覽(25)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包