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

如何獲得一個(gè)絲滑的麥輪底盤(原理+代碼詳解)

這篇具有很好參考價(jià)值的文章主要介紹了如何獲得一個(gè)絲滑的麥輪底盤(原理+代碼詳解)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

? ? ? ? 本文將用最直白的方式講述麥輪底盤的控制原理,并且將附上全套stm32代碼。

目錄

一、準(zhǔn)備工作

1. 麥輪簡介

2. 安裝底盤

二、原理分析

1. 先從一個(gè)輪子開始

2. 再到整個(gè)底盤

三、運(yùn)動(dòng)學(xué)逆解

1. 繼續(xù)從整體分析

2. 最后回到一個(gè)輪子

四、離散化和PID

1. 數(shù)據(jù)離散化

2. 增量式PID

五、源碼詳解

1. 框架概述

2. 上位機(jī)協(xié)議

3. 第一層指令解析

4. 第二層指令解析

5. 底層電機(jī)驅(qū)動(dòng)函數(shù)

六、源碼下載地址


一、準(zhǔn)備工作

1. 麥輪簡介

? ? ? ? 麥輪絕對(duì)是一個(gè)神奇的發(fā)明。其由輪轂和固定在外周的許多小輥?zhàn)訕?gòu)成,輪軸和輥軸之間的夾角通常為 45°,每個(gè)輪子具有三個(gè)自由度,分別是繞輪軸轉(zhuǎn)動(dòng),沿垂直于與地面接觸的輥?zhàn)拥妮佪S方向移動(dòng),繞輪子和地面的接觸點(diǎn)轉(zhuǎn)動(dòng)。如圖就是一個(gè)麥輪:

麥輪,stm32

? ? ? ? 麥輪特殊的構(gòu)造使麥輪小車能有很多詭異的運(yùn)動(dòng)方式。其中最常見的就是全向移動(dòng)了,那么我們?cè)撊绾螌?shí)現(xiàn)呢?

2. 安裝底盤

? ? ? ? 首先我們要將麥輪底盤裝好。麥輪的安裝方式有很多講究,原理涉及很多力學(xué)和運(yùn)動(dòng)分解,這些具體的東西在下文再講,這里就直接放出正確的安裝方式了:

? ? ? ? 麥輪安裝主要看它上面的輥?zhàn)拥姆较?,下面這是正確安裝方向的俯視圖。

麥輪,stm32

? ? ? ? ?要注意下面這個(gè)圖是正確方向的地面映射圖,和上圖安裝方式是一樣的。

麥輪,stm32

二、原理分析

1. 先從一個(gè)輪子開始

? ? ? ? 如果你去看展開來分析麥輪單輪受力分析的文章,會(huì)發(fā)現(xiàn)其中的分解比較復(fù)雜。其實(shí)這些大部分還是高中的受力分解,高中的時(shí)候我們絕對(duì)能輕松應(yīng)付。但是我們大部分都已經(jīng)是大學(xué)生了,肯定已經(jīng)看不懂這些了。

? ? ? ? 但是不要慌,因?yàn)榈讓拥脑韺?duì)我們應(yīng)用來說并不十分關(guān)鍵,我們只需了解幾個(gè)結(jié)論:

麥輪,stm32

?麥輪,stm32

? ? ? ? ?這兩張圖就清楚展示了單個(gè)麥輪轉(zhuǎn)動(dòng)時(shí)的產(chǎn)生的速度方向。應(yīng)該還是很好理解的,只要記住它的合速度與輥?zhàn)臃较蚱叫芯托辛恕?/p>

? ? ? ? ?然后再進(jìn)行一個(gè)最簡單的速度分解:

麥輪,stm32

?麥輪,stm32

? ? ? ? ?這樣我們就將每個(gè)輪子產(chǎn)生的速度分解到了xy兩個(gè)方向,以便于下一步整車的運(yùn)動(dòng)分析。

2. 再到整個(gè)底盤

? ? ? ? 那么理解了單個(gè)輪子的運(yùn)動(dòng)分析,再到了整輛車就比較簡單了。

? ? ? ? 首先我們要理解整個(gè)底盤的運(yùn)動(dòng)是由四個(gè)麥輪共同帶動(dòng)的,所以整車的速度方向取決于四個(gè)麥輪速度方向的合成。而四個(gè)麥輪轉(zhuǎn)動(dòng)速度與轉(zhuǎn)動(dòng)方向的不同組合就可以使小車以各種不同方式運(yùn)動(dòng)。

? ? ? ? 下面先展示一下幾種基本的運(yùn)動(dòng)情況是如何產(chǎn)生的:

① 前進(jìn)后退

麥輪,stm32

麥輪,stm32

? ? ? ? ?這里麥輪之間的橫向分力互相抵消,豎向分力共同作用,就產(chǎn)生了前進(jìn)后退的效果。

② 左右平移

麥輪,stm32

?麥輪,stm32

? ? ? ? ?相反,這里麥輪之間的豎向分力互相抵消,橫向分力共同作用,就產(chǎn)生了左右平移的效果。

③ 斜向平移

麥輪,stm32

?麥輪,stm32

麥輪,stm32

?麥輪,stm32

? ? ? ? ?這里我的只讓兩個(gè)對(duì)角的麥輪轉(zhuǎn)動(dòng),甚至不需要速度分解,我們就能很清楚的看出它們的合速度是怎么驅(qū)動(dòng)小車斜向平移的。

④ 原地轉(zhuǎn)圈

麥輪,stm32

麥輪,stm32

? ? ? ? ?同樣這里也不需要速度分解,我們也能很清楚的看出它們的合速度是怎么驅(qū)動(dòng)小車轉(zhuǎn)圈的。

三、運(yùn)動(dòng)學(xué)逆解

1. 繼續(xù)從整體分析

? ? ? ? 了解了最簡單的原理,我們就可以考慮怎樣去運(yùn)用了。

? ? ? ? 其實(shí)上文的原理分析就是一個(gè)運(yùn)動(dòng)學(xué)正解過程,也就是從每個(gè)輪子的運(yùn)動(dòng)去分析小車的運(yùn)動(dòng)。但是,這種方法在實(shí)際運(yùn)用中用處是不大的。我們的需求是通過指定小車的運(yùn)動(dòng)方式,得到每個(gè)輪子的運(yùn)動(dòng)方式。這樣我們才能方便的控制小車,也就是所謂的運(yùn)動(dòng)學(xué)逆解。

? ? ? ? 我們最終的程序也是完成的這樣的一個(gè)過程。例如我們傳入小車前進(jìn)的指令,程序解析出四個(gè)麥輪應(yīng)運(yùn)動(dòng)的速度和方向,從而達(dá)到整車前進(jìn)的效果。

? ? ? ? 整個(gè)運(yùn)動(dòng)學(xué)逆解的推導(dǎo)過程還是比較復(fù)雜的。這里也展示一下吧:

?麥輪,stm32

? ? ? ? 相信你們也不想看這個(gè)。好消息是,這些也沒必要去看。我們只需理解這個(gè)結(jié)論:

 VA輪 = Vx+Vy-Vz*(H/2+W/2)
 VB輪 = Vx-Vy-Vz*(H/2+W/2)
 VC輪 = Vx+Vy+Vz*(H/2+W/2)
 VD輪 = Vx-Vy+Vz*(H/2+W/2)

參數(shù)說明:
VABCD輪-> 麥輪A、B、C、D 的線速度,單位m/s。
Vx-> 機(jī)器人前后移動(dòng)速度,前進(jìn)為正,單位:m/s。
Vy-> 機(jī)器人左右移動(dòng)速度,左移為正,單位:m/s。
Vz-> 機(jī)器人繞 O 點(diǎn)旋轉(zhuǎn)速度,逆時(shí)針為正,單位:rad/s
W-> 輪距,機(jī)器人左右麥輪的距離,單位:m。
H-> 軸距,機(jī)器人前后麥輪的距離,單位:m。

? ? ? ? 可以對(duì)照下面這個(gè)圖理解,總體還是比較簡單的。?

麥輪,stm32

? ? ? ? ?C語音實(shí)現(xiàn)示例:

// 整車移動(dòng)量轉(zhuǎn)換為單輪速度  x:前+后-  y:左+右-  z:逆+順-
void Move_Transfrom(double Vx,double Vy,double Vz)
{
	TargetA=Vx+Vy-Vz*(Car_H/2+Car_W/2);
	TargetB=Vx-Vy-Vz*(Car_H/2+Car_W/2);
	TargetC=Vx+Vy+Vz*(Car_H/2+Car_W/2);
	TargetD=Vx-Vy+Vz*(Car_H/2+Car_W/2);
}

? ? ? ? 這里還要注意一點(diǎn),(Car_H/2+Car_W/2)這個(gè)值可以當(dāng)作一個(gè)參數(shù)看待。在真正使用中沒有必要去過度糾結(jié)它的值,我們可以隨便的改變它,只要能達(dá)到最絲滑的旋轉(zhuǎn)效果就好。

2. 最后回到一個(gè)輪子

? ? ? ? 在上面的運(yùn)動(dòng)學(xué)逆解公式中,我們得到的是每個(gè)輪子的線速度,而且其單位是m/s。這個(gè)結(jié)果其實(shí)對(duì)我們而言并不是十分的友善。因?yàn)橐粋€(gè)m/s的線速度在我們這種規(guī)格的小車上并不是一個(gè)非常清晰的概念,況且電機(jī)的空載轉(zhuǎn)速與負(fù)載時(shí)的轉(zhuǎn)速是有巨大差異的,所以我們很難通過電機(jī)的額定轉(zhuǎn)速等信息推導(dǎo)出一個(gè)適合我們小車的車輪運(yùn)動(dòng)角速度。

? ? ? ? 這時(shí),我們可以使用一種全新的計(jì)量車輪速度的單位:編碼器數(shù)據(jù)。編碼器數(shù)據(jù)可以非常方便的直接讀取,十分有利于我們對(duì)車速的預(yù)估和測量。同時(shí)在后面我們用pid對(duì)電機(jī)進(jìn)行閉環(huán)控制時(shí)目標(biāo)值的單位也是編碼器數(shù)據(jù),這里做到了單位的統(tǒng)一,對(duì)我們寫程序也是十分友善的。

? ? ? ? 關(guān)于編碼器的原理和讀取數(shù)據(jù)的方法就不多說了,這東西資料還是很多的。下面主要說一下如何進(jìn)行編碼器數(shù)據(jù)離散化和電機(jī)pid閉環(huán)控制。

四、離散化和PID

1. 數(shù)據(jù)離散化

? ? ? ? 這名聽起來挻高端。但是我們依然不用管其中的原理,我就直接說如何運(yùn)用了。

? ? ? ? 我們離散化的目的就是用編碼器的數(shù)據(jù)時(shí)實(shí)表示輪子的速度值。首先我們要知道編碼器的數(shù)據(jù)是什么樣的。簡單來說,當(dāng)你往一個(gè)方向轉(zhuǎn)動(dòng)輪子,編碼器的數(shù)據(jù)會(huì)一直自增,往另一個(gè)方向轉(zhuǎn)動(dòng)輪子,數(shù)據(jù)會(huì)一直自減。顯然,這樣的數(shù)據(jù)是無法表示速度的。

? ? ? ? 但是我們?nèi)绻扛粢欢螘r(shí)間將這個(gè)數(shù)據(jù)取出來,然后讓下一段時(shí)間的數(shù)據(jù)從0開始增減,那么這個(gè)取出來的數(shù)據(jù)就可以直接表示我們的速度大小和方向了。(這一段是原理可以不看: 我們的數(shù)據(jù)離散化就是把無限個(gè)的編碼器數(shù)據(jù)映射到有限的空間內(nèi)??梢岳斫鉃槲覀儼言跁r(shí)間上連續(xù)的數(shù)據(jù),通過一定的采樣頻率采集,用以代表我們?cè)谶B續(xù)維度上的數(shù)據(jù)。)

? ? ? ? 所以,在我們的stm32程序中,只需要再開一個(gè)定時(shí)器,在其中斷函數(shù)中保存編碼器數(shù)據(jù)值,然后再清空編碼器計(jì)數(shù)器,用這個(gè)保存的數(shù)據(jù)代表實(shí)時(shí)速度,就可以實(shí)現(xiàn)所謂的離散化了。同時(shí),要注意這個(gè)定時(shí)器的頻率不易過低,實(shí)測10ms的間隔就是可以的。

2. 增量式PID

? ? ? ?有了上面的公式和編碼器數(shù)據(jù),我們現(xiàn)在就可以將一個(gè)整車的速度轉(zhuǎn)換為四個(gè)輪子各自的速度值了。但是這里又會(huì)有一個(gè)重要的問題,由于路面摩擦或電機(jī)本身等客觀條件的影響,輪子并不會(huì)精準(zhǔn)的依照我們給它的目標(biāo)值運(yùn)行。這將會(huì)導(dǎo)致小車的運(yùn)動(dòng)軌跡發(fā)生偏移,所以這里我們要為每個(gè)輪子添加PID閉環(huán)驅(qū)動(dòng)。

? ? ? ? PID閉環(huán)控制的原理比較繁瑣,這里也不多說了。同時(shí)PID的用途也非常廣泛,我們這里用到的是簡單的電機(jī)閉環(huán)控制,總結(jié)來說其作用就是讓電機(jī)盡可能精準(zhǔn)的按我們的預(yù)期運(yùn)行。

? ? ? ? 我們?cè)趹?yīng)用時(shí)只需要帶入PID的公式并了解傳入的參數(shù)和輸出的結(jié)果的意義就可以了。這里我使用的是增量式的PID,其通用公式如下:

Pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k):本次偏差
e(k-1):上一次的偏差
e(k-2):上上次的偏差
Kp:比例項(xiàng)參數(shù)
Ki:積分項(xiàng)參數(shù)
Kd:微分項(xiàng)參數(shù)
Pwm:代表增量輸出

? ? ? ? 而我們的系統(tǒng)比較簡單,電機(jī)數(shù)據(jù)突變值比較小,所以并沒有使用D值。于是我們的公式就簡化為了下面這個(gè)樣子:

Pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
e(k):本次偏差
e(k-1):上一次的偏差
Kp:比例項(xiàng)參數(shù)
Ki:積分項(xiàng)參數(shù)
Pwm:代表增量輸出

? ? ? ? 在程序中,我的PID計(jì)算函數(shù)是這個(gè)樣子:

//MA速度增量PID
int16_t Speed_PID_A(double Target)
{
	static float Bias,Pwm,Last_bias;//本次誤差,累加輸出,上次誤差
	Bias=Target-Encoder_Result[0]/Speed_Proportion; //計(jì)算偏差
	Pwm+=Speed_PID_P*(Bias-Last_bias)+Speed_PID_I*Bias;//PID
	Last_bias=Bias; //保存上一次偏差
	return Pwm;
}

//注意:
//Target代表傳入的目標(biāo)速度
//Encoder_Result[0]/Speed_Proportion是實(shí)時(shí)的編碼器測速值
//Pwm代表輸出的電機(jī)驅(qū)動(dòng)信號(hào)大小
//Speed_PID_P和Speed_PID_I是參數(shù)P和I的值

? ? ? ? 使用時(shí)我們需要不斷的調(diào)用這個(gè)函數(shù),當(dāng)你傳入一個(gè)新的目標(biāo)值時(shí),PID公式就會(huì)幫你精準(zhǔn)的控制每個(gè)輪子達(dá)到目標(biāo)速度了。

? ? ? ? 另外還要說一下,P和I的值是需要我們自己慢慢調(diào)節(jié)的,不同的情況下他們最合適的值都會(huì)不同,這就比較考驗(yàn)經(jīng)驗(yàn)了。在一般情況下,P的值對(duì)系統(tǒng)的影響最大,其值一般會(huì)大一點(diǎn),I值一般會(huì)小一點(diǎn)。而P或I過大容易造成系統(tǒng)強(qiáng)烈的震蕩,過小會(huì)延長系統(tǒng)的反應(yīng)時(shí)間??傊荒芸孔约郝恼{(diào),小車才能實(shí)現(xiàn)最絲滑的狀態(tài)。

? ? ? ? 那么麥輪的控制原理就差不多說完了??偨Y(jié)一下就是通過公式將小車的整體速度轉(zhuǎn)化為每一個(gè)輪子的速度,然后代入PID進(jìn)行驅(qū)動(dòng)??傮w還是挺簡單的。但是我們寫代碼時(shí)還要注意更多細(xì)節(jié),下面詳細(xì)說一下代碼。

五、源碼詳解

1. 框架概述

? ? ? ? 本套代碼是stm32f103rct6驅(qū)動(dòng)麥輪底盤的代碼。

? ? ? ? 首先來說我們的代碼大概分為四層。第一層是與上位機(jī)的通信協(xié)議,這一層代碼接收上位機(jī)發(fā)送的遙控指令包括移動(dòng)剎車調(diào)速等,并下發(fā)到下一層。

????????第二層代碼是對(duì)指令的第一層解析,得到小車的xyz方向目標(biāo)速度并下發(fā)到下一層。

????????第三層代碼是對(duì)指令的第二層解析,得到每個(gè)輪子的目標(biāo)速度然后帶入PID公式,將結(jié)果再次下發(fā)到下一層。

????????第四層是底層電機(jī)驅(qū)動(dòng),將PID結(jié)果轉(zhuǎn)換為驅(qū)動(dòng)電機(jī)的PWM占空比,完成小車的驅(qū)動(dòng)。

? ? ? ? 當(dāng)然,除了這些之外還有定時(shí)讀取編碼器值,接收藍(lán)牙數(shù)據(jù)等其他代碼共同起到作用。

? ? ? ? 框架流程圖:? ? ? ??麥輪,stm32

? ? ? ? ?其中底層驅(qū)動(dòng)代碼會(huì)因?yàn)椴煌闹骺匦吞?hào)和不同的電機(jī)驅(qū)動(dòng)芯片而不同,與上位機(jī)的通信協(xié)議也會(huì)有不同的地方,但中間最重要的解析過程是基本可以通用的。

2. 上位機(jī)協(xié)議

? ? ? ? 這一層在main中,指令格式是 @+指令+/E ,是小程序的控制界面發(fā)送的。這一部分不是重點(diǎn),沒啥好說的,因?yàn)椴煌捻?xiàng)目中通信協(xié)議都是有很大的區(qū)別的。具體指令內(nèi)容直接看源碼吧。

? ? ? ? 小程序操作界面:

麥輪,stm32

? ? ? ? main.c:?

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"  						//控制LDE
#include "Serial.h"						//串口1與上位機(jī)通信
#include "Key.h"  						//按鍵控制
#include "MyI2CRev.h"					//IIC接收指令
#include "Move.h"						//底層移動(dòng)控制
#include "Control.h"					//上層移動(dòng)控制
#include "Encoder.h"					//編碼器測速

uint8_t IICRxData;//接收的數(shù)據(jù)

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中斷分組
	
	Delay_ms(1);//等待拓展板先初始化
	//LED_Init();
	//Key_Init();
	Serial_Init();
	OLED_Init();
	MyI2CRev_Init();
	Move_init();
	Encoder_Init();
	
	while(1)
	{	
		//IIC接收數(shù)據(jù)
		if(Get_I2CFlag()==1)
		{
			IICRxData=(uint16_t)Get_I2CData();
			if(IICRxData<=60)//小車全向移動(dòng)(0~60映射到0~360)
			{
				Control_Straight_Move(IICRxData);
				Move_Flag=1;
			}
			else if(IICRxData==62){Move_StopAll();Move_Flag=0;}//停止
			else if(IICRxData==63){Move_BrakeAll();Move_Flag=0;}//急剎
			else if(IICRxData<=68){Control_Set_Speed(IICRxData-63);}//控速
			else if(IICRxData==69){Control_Circlr_Move(1);Move_Flag=1;}//左轉(zhuǎn)
			else if(IICRxData==70){Control_Circlr_Move(0);Move_Flag=1;}//右轉(zhuǎn)
			else if(IICRxData<=71){Move_StopAll();Move_Flag=0;}//停止
			
			RevFlag=0;//清除標(biāo)志位
		}
	}
}

3. 第一層指令解析

????????這里面的Control_Straight_Move函數(shù)將上位機(jī)傳入的小車全向移動(dòng)的平移角度指令轉(zhuǎn)換為了xy兩個(gè)方向的速度。

????????Control_Circlr_Move函數(shù)將上位機(jī)傳入的小車旋轉(zhuǎn)指令轉(zhuǎn)換為了z方向的速度。

????????Control_Set_Speed函數(shù)提供了調(diào)速接口,要注意的是Max_Speed這個(gè)變量的值是通過實(shí)際測試得到的一些合適的速度值,采用的是和編碼器數(shù)據(jù)一樣的單位。

????????Control.c:

#include "stm32f10x.h"                  // Device header
#include "Move.h"
#include "Math.h"

uint16_t Speed_Gear=3;//速度擋位1~5
double Max_Speed;//整車最大速度

//根據(jù)速度擋位轉(zhuǎn)換整車最大速度
void Set_Max_Speed(void)
{
	//PWM=55編碼器數(shù)據(jù)->GMR:1380  HR:28
	//PWM=60編碼器數(shù)據(jù)->GMR:1720  HR:38
	//PWM=65編碼器數(shù)據(jù)->GMR:2045  HR:46
	//PWM=70編碼器數(shù)據(jù)->GMR:2360  HR:56
	//PWM=75編碼器數(shù)據(jù)->GMR:2570  HR:64
	switch(Speed_Gear)
	{
		case 1:Max_Speed=1380/2/Speed_Proportion;break;
		case 2:Max_Speed=1720/2/Speed_Proportion;break;
		case 3:Max_Speed=2045/2/Speed_Proportion;break;
		case 4:Max_Speed=2360/2/Speed_Proportion;break;
		case 5:Max_Speed=2570/2/Speed_Proportion;break;
	}
}

//傳入全向運(yùn)動(dòng)目標(biāo)角度0~60,逆時(shí)針為正
void Control_Straight_Move(uint16_t Straight_Target)
{
	Straight_Target=(double)Straight_Target/60*360;//0~60映射0~360
	
	double pi=acos(-1.0);//派
	double Target_Angle=(double)Straight_Target/360*2*pi;//角度轉(zhuǎn)弧度
	
	double Target_Speed_X=Max_Speed*cos(Target_Angle);//x方向目標(biāo)速度
	double Target_Speed_Y=Max_Speed*sin(Target_Angle);//y方向目標(biāo)速度
	
	Move_Transfrom(Target_Speed_X,Target_Speed_Y,0.0);//轉(zhuǎn)換為每個(gè)電機(jī)的速度
}

//傳入1或0,1左轉(zhuǎn)0右轉(zhuǎn)
void Control_Circlr_Move(uint16_t Circlr_Target)
{
	if(Circlr_Target==1)//左
	{
		Move_Transfrom(0.0,0.0,Max_Speed);//轉(zhuǎn)換為每個(gè)電機(jī)的速度
	}
	else//右
	{
		Move_Transfrom(0.0,0.0,-Max_Speed);//轉(zhuǎn)換為每個(gè)電機(jī)的速度
	}
}

//設(shè)置速度擋位
void Control_Set_Speed(uint16_t Set_Speed_Num)//傳入1~5
{
	if(Set_Speed_Num<=5)
	{
		Speed_Gear=Set_Speed_Num;//賦值
		Set_Max_Speed();//轉(zhuǎn)換最大速度
	}
}

?????????Control.h:?

#ifndef __CONTROL_H
#define __CONTROL_H

//根據(jù)速度擋位轉(zhuǎn)換整車最大速度
void Set_Max_Speed(void);
//傳入全向運(yùn)動(dòng)目標(biāo)角度0~360,逆時(shí)針為正
void Control_Straight_Move(uint16_t Straight_Target);
//傳入轉(zhuǎn)向運(yùn)動(dòng)目標(biāo)角度0~360,逆時(shí)針為正
void Control_Circlr_Move(uint16_t Circlr_Target);
//設(shè)置速度擋位
void Control_Set_Speed(uint16_t Set_Speed_Num);//傳入1~5

#endif

4. 第二層指令解析

? ? ? ? 這里面的Move_Transfrom函數(shù)接收上一層傳入的小車xyz方向速度,轉(zhuǎn)換為每個(gè)輪子的目標(biāo)速度。

????????Speed_PID_A,B,C,D函數(shù)是增量式PID計(jì)算公式。

????????Move_Motor是調(diào)用PID驅(qū)動(dòng)電機(jī)和限幅的函數(shù)。要注意的是這個(gè)函數(shù)在小車運(yùn)動(dòng)狀態(tài)下是要一直循環(huán)調(diào)用的,具體實(shí)現(xiàn)在定時(shí)器的中斷函數(shù)中,下面會(huì)展示。

????????Move_StopAll和Move_BrakeAll是停止和剎車的接口函數(shù)。

? ? ? ? Move.c:

#include "stm32f10x.h"                  // Device header
#include "Motor.h"
#include "Encoder.h"
#include "Control.h"

#define PWM_MAX    420 //PWM最大限幅(1020-600)

double Speed_PID_P=0.6;//P
double Speed_PID_I=0.01;//I

//電機(jī)分布:從左下順時(shí)針開始ABCD,對(duì)應(yīng)M1234

double Speed_Proportion=2.0;//空轉(zhuǎn)速度與負(fù)載速度比值

double Car_HW=1; //小車旋轉(zhuǎn)參數(shù)比例值

double TargetA; // A輪目標(biāo)速度
double TargetB; // B輪目標(biāo)速度
double TargetC; // C輪目標(biāo)速度
double TargetD; // D輪目標(biāo)速度

void Move_init(void)
{	
	Motor_Init();
	
	Set_Max_Speed();//設(shè)置初始速度
}

// 整車移動(dòng)量轉(zhuǎn)換為單輪速度  x:前+后-  y:左+右-  z:逆+順-
void Move_Transfrom(double Vx,double Vy,double Vz)
{
	TargetA=Vx+Vy-Vz*Car_HW;
	TargetB=Vx-Vy-Vz*Car_HW;
	TargetC=Vx+Vy+Vz*Car_HW;
	TargetD=Vx-Vy+Vz*Car_HW;
}

//MA速度增量PID
int16_t Speed_PID_A(double Target)
{
	static float Bias,Pwm,Last_bias;//本次誤差,累加輸出,上次誤差
	Bias=Target-Encoder_Result[0]/Speed_Proportion; //計(jì)算偏差
	Pwm+=Speed_PID_P*(Bias-Last_bias)+Speed_PID_I*Bias;//PID
	Last_bias=Bias; //保存上一次偏差
	return Pwm;
}
//MB速度增量PID
int16_t Speed_PID_B(double Target)
{
	static float Bias,Pwm,Last_bias;//本次誤差,累加輸出,上次誤差
	Bias=Target-Encoder_Result[1]/Speed_Proportion; //計(jì)算偏差
	Pwm+=Speed_PID_P*(Bias-Last_bias)+Speed_PID_I*Bias;//PID
	Last_bias=Bias; //保存上一次偏差
	return Pwm;
}
//MC速度增量PID
int16_t Speed_PID_C(double Target)
{
	static float Bias,Pwm,Last_bias;//本次誤差,累加輸出,上次誤差
	Bias=Target-Encoder_Result[2]/Speed_Proportion; //計(jì)算偏差
	Pwm+=Speed_PID_P*(Bias-Last_bias)+Speed_PID_I*Bias;//PID
	Last_bias=Bias; //保存上一次偏差
	return Pwm;
}
//MD速度增量PID
int16_t Speed_PID_D(double Target)
{
	static float Bias,Pwm,Last_bias;//本次誤差,累加輸出,上次誤差
	Bias=Target-Encoder_Result[3]/Speed_Proportion; //計(jì)算偏差
	Pwm+=Speed_PID_P*(Bias-Last_bias)+Speed_PID_I*Bias;//PID
	Last_bias=Bias; //保存上一次偏差
	return Pwm;
}

// 控制電機(jī)轉(zhuǎn)動(dòng)
void Move_Motor(void)
{
	double PWM_A=Speed_PID_A(TargetA);//PID
	double PWM_B=Speed_PID_B(TargetB);
	double PWM_C=Speed_PID_C(TargetC);
	double PWM_D=Speed_PID_D(TargetD);
	
	//驅(qū)動(dòng)
	if(PWM_A>PWM_MAX){Motor1_Speed(PWM_MAX);}//限幅
	else if(PWM_A<-PWM_MAX){Motor1_Speed(-PWM_MAX);}//限幅
	else{Motor1_Speed((int16_t)PWM_A);}//正常輸出
	
	if(PWM_B>PWM_MAX){Motor2_Speed(PWM_MAX);}
	else if(PWM_B<-PWM_MAX){Motor2_Speed(-PWM_MAX);}
	else{Motor2_Speed((int16_t)PWM_B);}
	
	if(PWM_C>PWM_MAX){Motor3_Speed(PWM_MAX);}
	else if(PWM_C<-PWM_MAX){Motor3_Speed(-PWM_MAX);}
	else{Motor3_Speed((int16_t)PWM_C);}
	
	if(PWM_D>PWM_MAX){Motor4_Speed(PWM_MAX);}
	else if(PWM_D<-PWM_MAX){Motor4_Speed(-PWM_MAX);}
	else{Motor4_Speed((int16_t)PWM_D);}
}

//速度值清零
void Target_Clear(void)
{
	TargetA=0.0;
	TargetB=0.0;
	TargetC=0.0;
	TargetD=0.0;
}

//全部停止
void Move_StopAll(void)
{
	Motor1_Stop();
	Motor2_Stop();
	Motor3_Stop();
	Motor4_Stop();
	Target_Clear();
}

//全部剎車
void Move_BrakeAll(void)
{
	Motor1_Brake();
	Motor2_Brake();
	Motor3_Brake();
	Motor4_Brake();
	Target_Clear();
}

? ? ? ? Move.h:

#ifndef __MOVE_H
#define __MOVE_H

extern double Speed_Proportion;//空轉(zhuǎn)速度與負(fù)載速度比值

void Move_init(void);
void Move_Transfrom(double Vx,double Vy,double Vz);
void Move_Motor(void);
void Move_StopAll(void);
void Move_BrakeAll(void);

#endif

? ? ? ? 下面是編碼器的數(shù)據(jù)讀取函數(shù),其中也包含一個(gè)定時(shí)器的中斷函數(shù),在這個(gè)中斷中要讀取并清除編碼器數(shù)據(jù)并調(diào)用電機(jī)PID驅(qū)動(dòng)函數(shù)。讀取到的編碼器速度值將直接作為PID公式中的當(dāng)前速度值。

? ? ? ? Encoder.c:

#include "stm32f10x.h"                  // Device header
#include "Encoder_Timer.h"
#include "Move.h"

int16_t Encoder_Result[4]; //存儲(chǔ)編碼器實(shí)時(shí)數(shù)據(jù)
uint8_t Move_Flag=0;//是否運(yùn)動(dòng)標(biāo)志位,0靜止1運(yùn)動(dòng)

void Encoder_Init(void)
{
	//初始化定時(shí)器7,10ms采集頻率
	Encoder_Timer_Init();
	
	//Timer2的編碼器接口(PB3,PA15)remap
	//Timer3的編碼器接口(PA6,PA7)
	//Timer4的編碼器接口(PB6,PB7)
	//Timer5的編碼器接口(PA0,PA1)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//開啟GPIOA時(shí)鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//開啟GPIOB時(shí)鐘
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//開啟Timer2時(shí)鐘->M1
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//開啟Timer3時(shí)鐘->M4
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//開啟Timer4時(shí)鐘->M2
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);//開啟Timer5時(shí)鐘->M3
	
	GPIO_InitTypeDef GPIO_InitStructure;//初始化GPIO
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉輸入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_15;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//Timer2的輸出通道1,2重映射在PA15,PB3
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//開啟AFIO時(shí)鐘
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);//引腳重映射
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//解除調(diào)試功能
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//時(shí)基單元初始化
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//濾波器不分頻
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//向上計(jì)數(shù)(此參數(shù)沒有用,由編碼器控制)
	TIM_TimeBaseInitStruct.TIM_Period=65536-1;//ARR自動(dòng)重裝器(65536滿量程計(jì)數(shù),防止溢出,方便換算為負(fù)數(shù))
	TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;//PSC預(yù)分頻器(不分頻,編碼器時(shí)鐘直接驅(qū)動(dòng)計(jì)數(shù)器)
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重復(fù)計(jì)數(shù)器
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStruct);
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStruct);
	
	TIM_ICInitTypeDef TIM_ICInitStruct;//初始化輸入捕獲單元
	TIM_ICStructInit(&TIM_ICInitStruct);//賦初始值
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1;//選擇輸入捕獲通道1
	TIM_ICInitStruct.TIM_ICFilter=0xF;//濾波器
	TIM_ICInit(TIM2, &TIM_ICInitStruct);//初始化通道1
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	TIM_ICInit(TIM4, &TIM_ICInitStruct);
	TIM_ICInit(TIM5, &TIM_ICInitStruct);
	
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_2;//選擇輸入捕獲通道2
	TIM_ICInitStruct.TIM_ICFilter=0xF;//濾波器
	TIM_ICInit(TIM2, &TIM_ICInitStruct);//初始化通道2
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	TIM_ICInit(TIM4, &TIM_ICInitStruct);
	TIM_ICInit(TIM5, &TIM_ICInitStruct);
	
	//配置編碼器接口
	//在TI1和TI2都計(jì)數(shù)  通道1不反向  通道2不反向
	TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
	TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	TIM_EncoderInterfaceConfig(TIM5, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
	
	TIM_Cmd(TIM2,ENABLE);//開啟定時(shí)器
	TIM_Cmd(TIM3,ENABLE);
	TIM_Cmd(TIM4,ENABLE);
	TIM_Cmd(TIM5,ENABLE);
}

void Encoder_Get(void)//更新編碼器數(shù)據(jù)
{
	Encoder_Result[0]=TIM_GetCounter(TIM2);//Timer2->M1
	TIM_SetCounter(TIM2, 0);//CNT清零,方便下次讀取速度
	
	Encoder_Result[1]=TIM_GetCounter(TIM4);//Timer4->M2
	TIM_SetCounter(TIM4, 0);//CNT清零,方便下次讀取速度
	
	Encoder_Result[2]=TIM_GetCounter(TIM5);//Timer5->M3
	TIM_SetCounter(TIM5, 0);//CNT清零,方便下次讀取速度
	
	Encoder_Result[3]=TIM_GetCounter(TIM3);//Timer3->M4
	TIM_SetCounter(TIM3, 0);//CNT清零,方便下次讀取速度
}

void TIM7_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM7, TIM_IT_Update)==SET)//判斷標(biāo)志位是否正確
	{
		Encoder_Get();//更新編碼器數(shù)據(jù)
		if(Move_Flag==1)
		{
			Move_Motor();//定時(shí)驅(qū)動(dòng)電機(jī)
		}
		TIM_ClearITPendingBit(TIM7, TIM_IT_Update);//清除標(biāo)志位
	}
}

????????Encoder.h:

#ifndef __ENCODER_H
#define __ENCODER_H

extern uint8_t Move_Flag;//是否運(yùn)動(dòng)標(biāo)志位,0靜止1運(yùn)動(dòng)

extern int16_t Encoder_Result[];

void Encoder_Init(void);

#endif

? 5. 底層電機(jī)驅(qū)動(dòng)函數(shù)

? ? ? ? 由于底層驅(qū)動(dòng)代碼會(huì)因?yàn)椴煌闹骺匦吞?hào)和不同的電機(jī)驅(qū)動(dòng)芯片而不同,而且這里涉及原理不是本文的重點(diǎn),所以就不多說啥了。

? ? ? ? Motor.c:

#include "stm32f10x.h"                  // Device header

void Motor_Init(void)
{
	//Timer1,8輸出PWM(頻率30kHZ) 初始化Motor1(PC6,PC7),Motor2(PC8,PC9),Motor3(PA8,PA11),Motor4(PB0,PB1)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//開啟GPIOA時(shí)鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//開啟GPIOB時(shí)鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//開啟GPIOC時(shí)鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//開啟Timer1時(shí)鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);//開啟Timer8時(shí)鐘
	
	GPIO_InitTypeDef GPIO_InitStructure;//初始化GPIO
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//一定要用復(fù)用推挽輸出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//Timer1 CH1 Motor3
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//Timer1 CH2 Motor4
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//Timer1 CH3 Motor4
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//Timer1 CH4 Motor3
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//Timer8 CH1 Motor1
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;//Timer8 CH2 Motor1
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//Timer8 CH3 Motor2
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//Timer8 CH4 Motor2
 	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM1);//選擇內(nèi)部時(shí)鐘為時(shí)鐘源
	TIM_InternalClockConfig(TIM8);//選擇內(nèi)部時(shí)鐘為時(shí)鐘源
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//時(shí)基單元初始化(30KHZ)
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//濾波器不分頻
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//向上計(jì)數(shù)
	TIM_TimeBaseInitStruct.TIM_Period=1200-1;//ARR自動(dòng)重裝器 600~1200為有效驅(qū)動(dòng)值
	TIM_TimeBaseInitStruct.TIM_Prescaler=2-1;//PSC預(yù)分頻器
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重復(fù)計(jì)數(shù)器
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
	TIM_TimeBaseInit(TIM8, &TIM_TimeBaseInitStruct);
	
	TIM_OCInitTypeDef TIM_OCInitStruct;//初始化輸出比較
	TIM_OCStructInit(&TIM_OCInitStruct);//先給結(jié)構(gòu)體賦初始值,防止使用高級(jí)定時(shí)器時(shí)參數(shù)配置不完全導(dǎo)致無法正常輸出PWM
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;//PWM模式1(常用)
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;//TIM_OCPolarity_High REF極性不翻轉(zhuǎn)
	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//輸出使能
	TIM_OCInitStruct.TIM_Pulse=0;//設(shè)置CCR的值(占空比)
	//Timer8通道初始化
	TIM_OC1Init(TIM8, &TIM_OCInitStruct);//初始化TIM8輸出比較單元1 Motor1
	TIM_OC2Init(TIM8, &TIM_OCInitStruct);//初始化TIM8輸出比較單元2 Motor1
	TIM_OC3Init(TIM8, &TIM_OCInitStruct);//初始化TIM8輸出比較單元3 Motor2
	TIM_OC4Init(TIM8, &TIM_OCInitStruct);//初始化TIM8輸出比較單元4 Motor2
	//Timer1通道初始化
	TIM_OCInitStruct.TIM_OutputNState=TIM_OutputNState_Enable;//特殊配置Timer1 CHN2,CHN3
	TIM_OCInitStruct.TIM_OCNPolarity=TIM_OCNPolarity_Low;
	TIM_OCInitStruct.TIM_OCNIdleState=TIM_OCNIdleState_Set;
	TIM_OC1Init(TIM1, &TIM_OCInitStruct);//初始化TIM1輸出比較單元1 Motor3
	TIM_OC4Init(TIM1, &TIM_OCInitStruct);//初始化TIM1輸出比較單元4 Motor3
	TIM_OC2Init(TIM1, &TIM_OCInitStruct);//初始化TIM1輸出比較單元2 Motor4
	TIM_OC3Init(TIM1, &TIM_OCInitStruct);//初始化TIM1輸出比較單元3 Motor4
	
	//Timer1的輸出通道2,3的引腳重映射在PB0,PB1上)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//開啟AFIO時(shí)鐘
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM1, ENABLE);//引腳重映射
	
	TIM_CtrlPWMOutputs(TIM1, ENABLE);
	TIM_CtrlPWMOutputs(TIM8, ENABLE);
	
	TIM_Cmd(TIM1, ENABLE);//使能計(jì)數(shù)器
	TIM_Cmd(TIM8, ENABLE);//使能計(jì)數(shù)器
}


//Motor1:
void Motor1_Speed(int16_t Compare)//調(diào)用函數(shù)可以更改占空比(傳入-600~600)
{
	if(Compare>=0)//前進(jìn)
	{
		TIM_SetCompare2(TIM8, 0);
		TIM_SetCompare1(TIM8, Compare+600);
	}
	else//后退
	{
		TIM_SetCompare1(TIM8, 0);
		TIM_SetCompare2(TIM8, -Compare+600);
	}
}

void Motor1_Stop(void)//自然停止
{
	TIM_SetCompare1(TIM8, 0);
	TIM_SetCompare2(TIM8, 0);
}

void Motor1_Brake(void)//急剎
{
	TIM_SetCompare1(TIM8, 1200);
	TIM_SetCompare2(TIM8, 1200);
}


//Motor2:
void Motor2_Speed(int16_t Compare)//調(diào)用函數(shù)可以更改占空比(傳入-600~600)
{
	if(Compare>=0)//前進(jìn)
	{
		TIM_SetCompare4(TIM8, 0);
		TIM_SetCompare3(TIM8, Compare+600);
	}
	else//后退
	{
		TIM_SetCompare3(TIM8, 0);
		TIM_SetCompare4(TIM8, -Compare+600);
	}
}

void Motor2_Stop(void)//自然停止
{
	TIM_SetCompare4(TIM8, 0);
	TIM_SetCompare3(TIM8, 0);
}

void Motor2_Brake(void)//急剎
{
	TIM_SetCompare4(TIM8, 1200);
	TIM_SetCompare3(TIM8, 1200);
}


//Motor3:
void Motor3_Speed(int16_t Compare)//調(diào)用函數(shù)可以更改占空比(傳入-600~600)
{
	if(Compare>=0)//前進(jìn)
	{
		TIM_SetCompare4(TIM1, 0);
		TIM_SetCompare1(TIM1, Compare+600);
	}
	else//后退
	{
		TIM_SetCompare1(TIM1, 0);
		TIM_SetCompare4(TIM1, -Compare+600);
	}
}

void Motor3_Stop(void)//自然停止
{
	TIM_SetCompare4(TIM1, 0);
	TIM_SetCompare1(TIM1, 0);
}

void Motor3_Brake(void)//急剎
{
	TIM_SetCompare4(TIM1, 1200);
	TIM_SetCompare1(TIM1, 1200);
}


//Motor4:
void Motor4_Speed(int16_t Compare)//調(diào)用函數(shù)可以更改占空比(傳入-600~600)
{
	if(Compare>=0)//前進(jìn)
	{
		TIM_SetCompare2(TIM1, 0);
		TIM_SetCompare3(TIM1, Compare+600);
	}
	else//后退
	{
		TIM_SetCompare3(TIM1, 0);
		TIM_SetCompare2(TIM1, -Compare+600);
	}
}

void Motor4_Stop(void)//自然停止
{
	TIM_SetCompare2(TIM1, 0);
	TIM_SetCompare3(TIM1, 0);
}

void Motor4_Brake(void)//急剎
{
	TIM_SetCompare2(TIM1, 1200);
	TIM_SetCompare3(TIM1, 1200);
}

????????Motor.h:

#ifndef __MOTOR_H
#define __MOTOR_H

void Motor_Init(void);

void Motor1_Speed(int16_t Compare);
void Motor2_Speed(int16_t Compare);
void Motor3_Speed(int16_t Compare);
void Motor4_Speed(int16_t Compare);

void Motor1_Stop(void);
void Motor1_Brake(void);
void Motor2_Stop(void);
void Motor2_Brake(void);
void Motor3_Stop(void);
void Motor3_Brake(void);
void Motor4_Stop(void);
void Motor4_Brake(void);

#endif

六、源碼下載地址

https://download.csdn.net/download/2303_76380160/87878664

文件包包含stm32的全部源碼和小程序藍(lán)牙操作界面的源碼。?文章來源地址http://www.zghlxwxcb.cn/news/detail-752926.html

到了這里,關(guān)于如何獲得一個(gè)絲滑的麥輪底盤(原理+代碼詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(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)文章

  • Android打造絲滑的Activity recreate重建(主題切換)過渡動(dòng)畫

    Android打造絲滑的Activity recreate重建(主題切換)過渡動(dòng)畫

    當(dāng)應(yīng)用程序支持多種語言或主題時(shí),切換語言或主題通常需要重新啟動(dòng) Activity 以重新加載配置。雖然 recreate 是一種常用的重建 Activity 方法,但它不支持像在 Activity 之間切換時(shí)那樣使用過渡動(dòng)畫。特別是在切換 淺色/深色 主題時(shí),由于缺乏過渡動(dòng)畫而顯得很生硬。為了提升改

    2024年02月22日
    瀏覽(21)
  • 【ComfyUI進(jìn)階1】5分鐘制作絲滑的AI視頻-+AnimateDiff

    【ComfyUI進(jìn)階1】5分鐘制作絲滑的AI視頻-+AnimateDiff

    用AnimateDiff Prompt Travel video-to-video搭配ComfyUI制作AI視頻,效果絲滑 Ai跳舞教學(xué)案例視頻 AnimateDiff可以搭配擴(kuò)散模型算法(Stable Diffusion)來生成高質(zhì)量的動(dòng)態(tài)視頻,其中動(dòng)態(tài)模型(Motion Models)用來實(shí)時(shí)跟蹤人物的動(dòng)作以及畫面的改變。 這里我們使用ComfyUI來搭配AnimateDiff做視頻轉(zhuǎn)

    2024年02月05日
    瀏覽(64)
  • Android應(yīng)用-Flutter實(shí)現(xiàn)絲滑的滑動(dòng)刪除、移動(dòng)排序等-Dismissible控件詳解

    Android應(yīng)用-Flutter實(shí)現(xiàn)絲滑的滑動(dòng)刪除、移動(dòng)排序等-Dismissible控件詳解

    Dismissible 是 Flutter 中用于實(shí)現(xiàn)可滑動(dòng)刪除或拖拽操作的一個(gè)有用的小部件。主要用于在用戶對(duì)列表項(xiàng)或任何其他可滑動(dòng)的元素執(zhí)行刪除或拖動(dòng)操作時(shí),提供一種簡便的實(shí)現(xiàn)方式。 列表項(xiàng)刪除: 允許用戶在列表中通過滑動(dòng)手勢刪除某個(gè)項(xiàng)。 左右滑動(dòng): 提供可自定義的背景,當(dāng)

    2024年02月04日
    瀏覽(22)
  • 游戲有延遲?如何獲得一個(gè)好的Ping

    游戲有延遲?如何獲得一個(gè)好的Ping

    在多人游戲世界中,玩家要想獲得良好的游戲體驗(yàn),需要做很多事情——尤其是如果這種良好的體驗(yàn)取決于你的想法。 在線多人游戲,如FPS(第一人稱射擊游戲)、賽車和一些MMORPG類型的游戲,尤其取決于玩家對(duì)游戲反饋的及時(shí)有效反應(yīng)。這個(gè)反饋和反應(yīng)鏈有很多環(huán)節(jié),其中

    2023年04月27日
    瀏覽(19)
  • 分享一個(gè)菜單標(biāo)簽頁動(dòng)畫,切換絲滑無比

    分享一個(gè)菜單標(biāo)簽頁動(dòng)畫,切換絲滑無比

    先上效果圖: 代碼如下,復(fù)制粘貼大法拿走即可使用:

    2023年04月26日
    瀏覽(16)
  • 非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼?(本人就是有點(diǎn)不絲滑)

    非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼?(本人就是有點(diǎn)不絲滑)

    自學(xué) 報(bào)班 有師傅帶 游戲開發(fā)?后臺(tái)研發(fā)?爬蟲工程師?前端程序員?數(shù)據(jù)分析師? 或者 僅僅是想做一個(gè)【程序員】?? 或者被影視所影響,感覺程序員好酷、好牛逼 所以你要想清楚?。?! C語言?C++?C#?Java?Python?H5?JS?甚至是Mysql? 所以你要考慮清楚,當(dāng)然我還是推薦P

    2024年02月10日
    瀏覽(13)
  • Java:定義一個(gè)學(xué)生類(Student),屬性包括:學(xué)號(hào),姓名,性別,年齡;方法包括:獲得學(xué)號(hào),獲得姓名,獲得性別,獲得年齡,修改學(xué)號(hào),修改姓名,修改性別,修改年齡。定義并創(chuàng)建一個(gè)學(xué)生數(shù)組對(duì)象。

    Java:定義一個(gè)學(xué)生類(Student),屬性包括:學(xué)號(hào),姓名,性別,年齡;方法包括:獲得學(xué)號(hào),獲得姓名,獲得性別,獲得年齡,修改學(xué)號(hào),修改姓名,修改性別,修改年齡。定義并創(chuàng)建一個(gè)學(xué)生數(shù)組對(duì)象。

    ? ?定義一個(gè)學(xué)生類(Student),屬性包括:學(xué)號(hào),姓名,性別,年齡;方法包括:獲得學(xué)號(hào),獲得姓名,獲得性別,獲得年齡,修改學(xué)號(hào),修改姓名,修改性別,修改年齡。定義并創(chuàng)建一個(gè)學(xué)生數(shù)組對(duì)象,長度可自定,最后在控制臺(tái)輸出學(xué)生信息。 結(jié)果: ?

    2024年02月11日
    瀏覽(30)
  • WouoUI-PageVersion 一個(gè)用于快速構(gòu)建具有絲滑OLED_UI動(dòng)畫的項(xiàng)目

    WouoUI-PageVersion 一個(gè)用于快速構(gòu)建具有絲滑OLED_UI動(dòng)畫的項(xiàng)目

    簡介致謝 Air001的TestUI例子的b站的演示視頻 Air001的LittleClock例子的b站演示視頻: https://www.bilibili.com/video/BV1J6421g7H1/ Stm32的TestUI例子的b站演示視頻: https://www.bilibili.com/video/BV1mS421P7CZ/ 所有演示的工程文件都使用zip壓縮包上傳在對(duì)應(yīng)的文件夾下。 本項(xiàng)目的Github鏈接為:https://githu

    2024年02月22日
    瀏覽(19)
  • 非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼?

    非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼?

    轉(zhuǎn)碼,也就轉(zhuǎn)行為程序員,已成為當(dāng)今數(shù)字化時(shí)代的一種重要技能。隨著科技的發(fā)展,越來越多的人開始意識(shí)到掌握編程技能的重要性,而非計(jì)算機(jī)科班出身的朋友們,想要絲滑轉(zhuǎn)碼,也許可以從以下幾個(gè)方面入手。 在開始學(xué)習(xí)轉(zhuǎn)碼之前,首先需要明確自己的目標(biāo)和動(dòng)機(jī)。為

    2024年02月13日
    瀏覽(37)
  • 非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼

    近年來,很多人想要從其他行業(yè)跳槽轉(zhuǎn)入計(jì)算機(jī)領(lǐng)域。非計(jì)算機(jī)科班如何絲滑轉(zhuǎn)碼? 對(duì)于非計(jì)算機(jī)科班的人來說,想要在計(jì)算機(jī)領(lǐng)域?qū)崿F(xiàn)順利的轉(zhuǎn)碼并不是一件容易的事情,但也并非不可能。以下是一些建議和觀點(diǎn): 學(xué)習(xí)編程基礎(chǔ):計(jì)算機(jī)領(lǐng)域的核心是編程。對(duì)于非科班背

    2024年02月12日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

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

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包