stm32hal庫尋跡+藍牙智能車(STM32F103C8T6)

簡介:

? ? ? ? 這個小車的芯片是STM32F103C8T6,其他的芯片也可以照貓畫虎,基本配置差不多,要注意的就是,管腳復用,管腳的特殊功能,(這點不用擔心,hal庫每個管腳的功能都會給你羅列,很方便的.)由于我做的比較簡單,只是用到了幾個簡單外設.主要是由帶霍爾編碼器電機的車模,電機驅動tb6612,(注意:選擇驅動的時候不要選擇那種小紅色的驅動,那種最大負載電壓11v,如果到時候pwm調到最大,可能會燒穿,比較危險),一個七路灰度傳感器,隨便一個12v電源都可以,一個stlink.杜邦線若干.(這些材料我基本都是學校實驗室順的,有什么用什么,用的可能比較雜)

芯片:STM32F103C8T6的開發板

驅動:我用的是tb6612雙路驅動

七路尋跡模塊:

電源;我看網上基本都是左邊這種,這種可能另外需要的一個電源模塊,雖然起到了一定的保護作用,但資深老玩家認為沒什么必要.12v總不能把我電死.我用的是右邊這個.加個轉接.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

藍牙模塊:

小車的樣子,接線比較雜亂

?

小車的基本流程:

? ? ? ? 1. 環境的配置,這個就不過多贅述了,我看網上的資料基本都很全了.用的是keil+圖形化界面,之后在出一期博客,關于keil和STM32CubeMx的配置,這個環境配置的東西不少.

? ? ? ? 2. 串口通信設置設置.

? ? ? ? 3. pwm輸出設置.

? ? ? ? 4. 驅動電機設置.

? ? ? ? ?5. 編碼器設置.

? ? ? ? 6.?藍牙設置.

? ? ? ? 7. 尋跡設置.

(一些基本的io控制就不作闡述了)

一. 通過STM32CubeMx創建工程

1.1?安裝開發包

點擊"install"進行安裝。(安裝時需要登錄)

1.2 選擇開發的STM32芯片

1.3 配置時鐘

1.4 生成keil工程

之后通過Keil打開工程編譯.

二. USB串口通信

????????由于設備的局限性沒有轉串口設備,我直接用的是USB虛擬串口(建議大家不要選擇這種方式,這種方式會埋個雷的,由于正常的串口通信的串口中斷是一個獨立的中斷函數,但是USB虛擬串口需要再主函數不停地執行,進行輪巡,還是比較浪費資源的,而且在后面的藍牙控制也會埋雷,總之建議大家有條件的還是買一個轉串口設備)

2.1 hal庫配置

2.1.1 進入調試模式

2.1.2 選擇外部晶振

2.1.3 USB工作模式

2.1.4 中間節組件

2.1.4 生成

2.2 keil設置

首先會多出一些目

這里我是新建了一個文件夾stm32handler,所有的外設都會在這里面寫.

2.2.1 keil中串口的代碼

.c代碼

#include "serial.h"void getserial_val()
{if(Recv_dlen)//判斷是否接收到數據,接收置位處理在static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)函數{USBD_CDC_SetTxBuffer(&hUsbDeviceFS, (uint8_t*)&UserRxBuffer, Recv_dlen);//把接收到的數據拷貝到發送USBD_CDC_TransmitPacket(&hUsbDeviceFS);//發送Recv_dlen=0;//長度清零}}

.h代碼

#ifndef __SERIAL_H_
#define __SERIAL_H_#include "main.h"
#include "usb_device.h"
#include <stdio.h>extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint32_t Recv_dlen;
extern uint8_t UserRxBuffer[1024];void getserial_val();#endif

(注意:每次寫完代碼要調用的時候,都要在keil中添加.h文件)

main函數

????????在while函數中調用,這步操作就是用來接收數據如果沒有這步操作,在串口助手發送數據,就會無法接收.

重定向usb_printf函數,使用這個函數,就跟C語言里面的printf函數一樣,打印到串口

void usb_printf(const char *format, ...)    // usb_printf()重定向 
{va_list args;uint32_t length;va_start(args, format);length = vsnprintf((char *)UserTxBufferFS, APP_TX_DATA_SIZE, (char *)format, args);va_end(args);CDC_Transmit_FS(UserTxBufferFS, length);
}

2.3 測試

大家可以任意地方使用usb_printf函數.進行測試.

三. pwm輸出設置

? ? ? ? 簡單介紹一下pwm,pwm就是一種周期波形,通過控制占空比,就是高電平占總周期的多少,占得越多,電壓越大,電壓越大,電機轉的越快.這就是很簡單的一種說法.

3.1 CubeMX配置

????????輸入的時鐘頻率為72MHZ

3.1.1 pwm定時器的設置

定時器1,由于我的是兩路驅動,只需要設置輸出兩路pwm.如果是四路可以另外設置

????????下面設置pwm的相關參數還是比較重要的.這里最終設置的輸出頻率一定要符合自己電機輸出的評率.不同的電機輸出頻率不一樣.大家最好問問客服.假如我的電機需要的頻率是20kHZ,你只輸出了10KHZ.這樣就會有一定的問題.不只體現在轉速上,還會振蕩,漂移,電機發熱一系列問題

? ? ? ? 電機的輸出所需頻率為20KHZ

? ? ? ? 所以:f=時鐘頻率/(預分頻+1)*(重載值+1)

????????這里的話 20k=72M(71+1)*(49+1)

? ? ? ? 大家也可以湊其他數,讓輸出頻率達到自己所需.

3.1.2配置參數說明
  1. Counter Settings(計數器設置)
    • Prescaler (PSC - 16 bit... 71)
      • 預分頻器值為 71。
    • Counter Mode Up
      • 計數器模式為向上計數模式。在這種模式下,計數器從 0 開始,每次時鐘脈沖到來時加 1,直到達到自動重裝載值。
    • Counter Period (AutoR... 49)
      • 自動重裝載值為 49。這決定了計數器的周期,即從 0 計數到這個值后重新開始計數。
    • Internal Clock Division (... No Division)
      • 內部時鐘不分頻。這意味著使用內部時鐘源時,不進行額外的分頻操作。
    • Repetition Counter (RC... 0)
      • 重復計數器值為 0。重復計數器用于在高級定時器中控制 PWM 信號的重復頻率。
    • auto - reload preload Enable
      • 自動重裝載預裝載使能。這意味著自動重裝載值可以在運行時更新。
  2. PWM Generation Channel 1(PWM 生成通道 1)
    • Mode PWM mode 1
      • PWM 模式 1。在這種模式下,當計數器小于比較值時為有效電平,當計數器大于等于比較值時為無效電平。
    • Pulse (16 bits value) 49
      • 脈沖值(比較值)為 49。這個值與自動重裝載值一起決定了 PWM 的占空比。
    • Output compare preload Enable
      • 輸出比較預裝載使能。允許在運行時更新比較值。
    • Fast Mode Disable
      • 快速模式禁用。
    • CH Polarity Low
      • 通道極性為低。這決定了 PWM 信號的初始電平。

????????在函數運行的時候通過下圖這個函數進行改變通道風比較值進行設置占空比.

????????

????????關于pwm和相關定時器的初始化,在生成的時候,已經初始化完成,所以不需要再keil中另外操作

四. 驅動電機設置

? ? ? ? 在配置之前需要了解這個驅動的相關知識

? ? ? ? 1. VCC
????????????????邏輯電平輸入端,給電機驅動模塊供電,3.3v或5v


????????2. STBY
????????????????使能引腳,高電平(3.3v)使能,低電平失能

????????3.GND
????????????????電機驅動模塊地端

????????4.PWMA
????????????????PWM輸入引腳,根據接收到的PWM信號的占空比,輸出電壓


????????5.AIN1與AIN2
????????????????電機控制模式輸入端,控制電機正反轉

????????兩路同理

4.1?CubeMX配置

????????配置AIN1 AIN2 BIN1 BIN2 STBY 五路輸出IO

? ? ? ?

沒有什么,特殊操作,簡單的五路輸出IO.設置同樣的其他四個.

4.2 keil中的設置

.c文件

????????要注意的就是方向,要知道速度不能為負,如果傳進來的速度為負,就好比是反向速度,這樣就要通過重新設置驅動的方向(方向是由驅動決定的.),然后對速度取反.

#include "motor.h"/*電機pwm啟動
*/
void Pwm_Init()
{HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
}
/*電機啟動
*/
void motor_on()
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
/*設置電機速度
*/
void motor_speed(int16_t speed_left,int16_t speed_right)
{if(speed_left>0){HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*speed_left));}else{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*(-speed_left)));}if(speed_right>0){HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_2,(50-0.5*speed_right));}else{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_2,(50-0.5*(-speed_right)));}
}/*按鍵中斷初始化
*/
void Button_Init()
{HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
void Morot_off()
{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);motor_speed(0,0);
}

按鍵中斷是為了當時好測試一點,可以不加.另外我是直接通過設置速度,沒有像什么常見的正轉,反轉,等等這樣的操作.如果想的話.可以這樣

.h文件

#ifndef __MOTOR_H_
#define __MOTOR_H_#include "main.h"
#include "tim.h"extern TIM_HandleTypeDef htim1;void Pwm_Init();
void motor_on();
void motor_speed(int16_t speed_left,int16_t speed_right);
void Button_Init();
void Morot_off();#endif

(注意,在執行代碼之前一定要打開motor_on,和初始化定時器)

4.3 測試

????????測試的時候就是通過改變通道的比較值來設定占空比從而設定速度.

????????即__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1,(50-0.5*speed_left));

????????上述這個函數.(50-0.5*speed_left),最大占空比是49/49=100;但是由于設置的初始比較值為49是滿占空比,所以這只電機速度的時候就要用49-(所需速度).(我這里是為了更好的設置速度,所以*0.5,這樣最大速度就是100,而不是50)

五. 編碼器設置

? ? ? ? 簡單介紹一下霍爾編碼器的工作原理.簡單來說用的這個雙通道計數,會有兩個方波.就是A相和B相.因為A相和B相的相位差90度,可以理解為B相比A相快一點.若同時捕獲 A、B 通道信號的上升沿和下降沿,A 相的上升沿、下降沿以及 B 相的上升沿、下降沿都能觸發計數.計幾次數就是幾倍頻.在一個信號周期內可以計數四次,實現 4 倍頻計數。以 4 倍頻為例,如果編碼器碼盤的分辨率為 N 線,那么電機轉一圈,計數器的計數值為 4N

? ? ? ? 關于方向,當 A 相檢測到上升沿脈沖時(由低電平變為高電平),如果此時 B 相為高電平,則判斷為正轉,計數器進行加 1 操作1? ? ? .當 A 相檢測到上升沿脈沖時,若 B 相為低電平,則判斷為反轉,計數器進行減 1 操作1

5.1?CubeMX配置

????????定時器的編碼器模式設置:(一個定時器的編碼器只能讀取一個輪子的計數)

左輪

右輪

????????這樣寫兩個定時器來讀取兩個輪子的計數,有點浪費資源.不過更簡單.還有另外一種方式,就是通過一個定時器來讀取兩個輪子的計數,通過定時器的定時中斷.

定時器二中斷

????????這個定時器的作用就是用來定時讀取編碼器的值,并且通過定時中斷函數來定時將編碼器的值歸零.因為編碼器的值不能無限大么一定要定時清零

使能中斷

5.2?keil設置

.c文件

#include "encoder.h"int32_t leftSpeed;
int32_t rightSpeed;void Encoder_Init()
{HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_1); // 開啟編碼器AHAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_2); // 開啟編碼器BHAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_1); // 開啟編碼器AHAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_2); // 開啟編碼器BHAL_TIM_Base_Start_IT(&htim2);                // 使能定時器2中斷
}void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{static unsigned char i = 0;if (htim == (&htim2)){//1.獲取電機速度yleftSpeed = (short) __HAL_TIM_GET_COUNTER(&htim3)*3.125;  rightSpeed = (short) __HAL_TIM_GET_COUNTER(&htim4)*3.125; // TIM4計數器獲得電機脈沖,該電機在10ms采樣的脈沖/18則為實際轉速的rpm__HAL_TIM_SET_COUNTER(&htim3,0);  // 計數器清零__HAL_TIM_SET_COUNTER(&htim4,0);  // 計數器清零i++;if(i>20){// 打印定時器4的計數值,short(-32768——32767)usb_printf("leftSpeed = %d ,rightSpeed = %d \r\n",leftSpeed,rightSpeed);	i=0;}		//Control();}}

可以通過輸出編碼器到串口的值來測試是否正確.(這樣的輸出可能會非常快)

.h文件

#ifndef __ENCODER_H_
#define __ENCODER_H_#include "main.h"
#include "tim.h"
#include "usb_device.h"
#include "trace.h"extern TIM_HandleTypeDef htim4;
extern TIM_HandleTypeDef htim3;
extern TIM_HandleTypeDef htim2;void Encoder_Init();extern int32_t leftSpeed;
extern int32_t rightSpeed;extern int32_t Speed_Middle;          // 中值速度
extern int32_t max;											//輸出最大值
extern int32_t min;									//輸出最小值
extern int32_t Motor_Left, Motor_Right;		//輸出左右輪速度#endif

5.3 測試

一定要初始化編碼器相關定時器

????????我給大家提個建議這個問題是我最早用TI芯片發現的但是現在也不知道是什么原因導致的.就是在定時中斷的時候不要使用串口輸出的函數,在這里面就是uart_printf.如果使用就是跳出定時中斷函數.這個問題我找了一些資料但是還是沒有發現原因.大家如果知道可以給我說說

????????我這里輸出是因為設置leftSpeed ?,rightSpeed 為全局變量,在Control中輸出的.

六.藍牙控制小車設置

? ? ? ? 之前給大家說的使用USB虛擬串口的雷就是在這里.

6.1 藍牙模塊的基本介紹

? ? ? ? 這里使用串口助手的波特率一定要與藍牙模塊的波特率一致,9600.

本來使用正常的串口轉接口是要進行測試的

在正常的串口是使用一個串口中斷識別不同的串口,

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,
uint16_t Size)
{
if(huart == &huart1){
HAL_UART_Transmit(&huart3, (const
uint8_t*)uart1_rx_buf, Size, HAL_MAX_DELAY);
}else if(huart == &huart3){
bluetooth_control_car(uart3_rx_buf);
HAL_UART_Transmit(&huart1, (const
uint8_t*)uart3_rx_buf, Size, HAL_MAX_DELAY);
}
uart_interrupt_init();
}
進行測試,發送AT指令
????????但是由于設備的局限性,我試了一下,發送指令沒什么用.就直接控制小車了.測試不了.

6.2?CubeMX配置

串口uart設置

使能串口中斷

6.3keil設置

#include "bluetooth.h"uint8_t USART2_RX_BUF[USART2_REC_LEN];//接收緩沖,最大USART_REC_LEN個字節.
uint16_t USART2_RX_STA=0;//接收狀態標記//bit15:接收完成標志,bit14~0:接收到的有效字節數目
uint8_t USART2_NewData;//當前串口中斷接收的1個字節數據的緩存char uart2_rx_buf[1024]={0};
int rev;
int speed=20;//藍牙串口初始化
void Blue_Init()
{HAL_UARTEx_ReceiveToIdle_IT(&huart2, (uint8_t*)uart2_rx_buf, sizeof(uart2_rx_buf)); //開啟串口2的接收中斷
}
//串口中斷函數,用于接受數據
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size)
{if(huart == &huart2){//HAL_UART_Transmit(&hUsbDeviceFS, (const uint8_t*)uart2_rx_buf, Size, HAL_MAX_DELAY);rev=uart2_rx_buf[0]-48;Blue_control(rev);usb_printf("uart2_rx_buf=%d\r\n",uart2_rx_buf[0]);Blue_Init();}
}//藍牙控制函數
void Blue_control(int res)
{switch (res){case 0: motor_speed(speed,speed); break;	//直行case 1: motor_speed(-speed,-speed); break;	//反行case 2: motor_speed(speed,0); break;		//左轉case 3: motor_speed(0,speed); break;	//右轉case 4: motor_speed(speed,-speed); break;	//正自身旋轉;case 5: motor_speed(-speed,speed); break;	//反自身旋轉;case 6: speed+=10; break;	//速度增加10case 7: speed-=10; break;	//速度減小10case 8: motor_speed(0,0);;	//停止default: motor_speed(0,0); break;}
}

????????至于為什么? rev=uart2_rx_buf[0]-48;? 它返回來的數據要-48,這是因為我發送的時候發送0,它給我返回來的就是48.所以就進行了這一步操作.

.h文件

#ifndef __BLUETOOTH_H_
#define __BLUETOOTH_H_#include "main.h"
#include "motor.h"
#include "usb_device.h"extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint32_t Recv_dlen;
extern uint8_t UserRxBuffer[1024];extern UART_HandleTypeDef huart2;//聲明USART2的HAL庫結構體#define USART2_REC_LEN  200//定義USART2最大接收字節數extern uint8_t  USART2_RX_BUF[USART2_REC_LEN];//接收緩沖,最大USART_REC_LEN個字節.末字節為校驗和
extern uint16_t USART2_RX_STA;//接收狀態標記
extern uint8_t USART2_NewData;//當前串口中斷接收的1個字節數據的緩存void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);
void Blue_Init();
void Blue_control(int res);
#endif

6.4 測試

????????微信搜索串口藍牙助手小程序

找到對應的藍牙

發送數據進行測試

藍牙控制小車

七. 尋跡小車設置

? ? ? ? 尋跡小車,主要依靠七路灰度傳感器,進行尋跡.

7.1?CubeMX配置

? ? ? ? 只需要設置七路普通的輸入IO即可

其他六路同理

7.2 keil設置

? ? ? ? 這里的尋跡,涉及到了一個知識點就是pid控制算法.如果剛開始弄可以不加pid,直接多對速度進行操作.pid也就是對速度進行了一個控制.

? ? ? ? 簡單講一下pid.pid就是三個參數,p-比例,i-積分,d-微分.p就是直接控制響應速度變化的量.聯系積分和微分的物理.積分就是累積量.根據過去的速度對當前速度的調整,就是i.微分就是變化率,也就是預判未來速度,對當前速度做出的調整.

? ? ? ? 一般初學者都會面臨pid的調參,我建議大家剛開始選擇增量式pid.增量式pid的參數相比較于位置式的參數還是好調的.因為增量式的誤差大多只會對前兩個誤差進行保留,而位置式是會對誤差一直進行累計.所以就要對積分進行限幅.但是位置式的pid響應更好,效果更卓越.

? ? ? ? pid我覺的也是一門博大精深的學問,很值得好好學習一下.這里有一個網站,在我學習pid的時候,這個網站,我認為還是比較全而且,總結的很好的網站.

PID控制器開發筆記.md · Lfj/PID控制算法 - Gitee.com

? ? ? ? 這個博客時讓初學者借鑒一下,所以pid的代碼我雖然會寫,但是執行的時候沒有用到,直接對速度加減來操作的.

.c文件

#include "trace.h"int32_t Speed_Middle = 40;          // 中值速度
int32_t max=80;											//輸出最大值
int32_t min=-80;										//輸出最小值
int32_t Motor_Left, Motor_Right;		//輸出左右輪速度的中間值
int32_t	Motor_Left_speed,Motor_Right_speed;//輸出左右輪速度float Kp = 1;float Ki = 0.1;float Kd = 0;float integral = 0;		//累計誤差float prev_error = 0;	//上次誤差float prev_integral = 0;//尋跡函數
int Incremental_Quantity() {int value = 0;if (!P1) // 檢測到最右端value += 36;if (!P2)value += 24;if (!P3)value += 12;if (!P4)value += 0;if (!P5)value -= 12;if (!P6)value -= 24;if (!P7)value -= 36;return value;
}//控制小車
void Control()
{int32_t leftTarget,rightTarget;	//目標設置左右輪速度int32_t bias;										//巡線偏差bias=Incremental_Quantity();leftTarget = Speed_Middle-bias;rightTarget = Speed_Middle+bias;//usb_printf("leftTarget=%d\r\n",leftTarget);//usb_printf("rightTarget=%d\r\n",rightTarget);Motor_Left  = Limit(leftTarget, max, min);Motor_Right = Limit(rightTarget, max,min);//usb_printf("Motor_Left=%d\r\n",Motor_Left);//usb_printf("Motor_Right=%d\r\n",Motor_Right);//Motor_Left_speed = (int32_t)PID_Compute(leftSpeed,Motor_Left);//Motor_Right_speed = (int32_t)PID_Compute(rightSpeed,Motor_Right);Motor_Left_speed = (int32_t)PID_Update(leftSpeed,Motor_Left);Motor_Right_speed = (int32_t)PID_Update(rightSpeed,Motor_Right);usb_printf("Motor_Left_speed=%d\r\n",Motor_Left_speed);usb_printf("Motor_Right_speed=%d\r\n",Motor_Right_speed);usb_printf("leftspeed=%d\r\n",leftSpeed);usb_printf("rightSpeed=%d\r\n",rightSpeed);motor_speed(Motor_Left,Motor_Right);//motor_speed(50,50);
}int32_t Limit(int32_t IN, int32_t limit, int32_t limiter)
{int32_t OUT = IN;if (OUT > limit)OUT = limit;if (OUT < limiter)OUT = limiter;return OUT;
}// 初始化PID控制器
void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float setpoint) {pid->Kp = Kp;pid->Ki = Ki;pid->Kd = Kd;pid->setpoint = setpoint;pid->integral = 0.0;pid->prev_error = 0.0;
}// 位置式PID計算
float PID_Compute(float current_value,float setpoint) 
{float error = setpoint - current_value;  // 計算當前誤差integral += error;  // 累積誤差float derivative = error - prev_error;  // 計算誤差的微分// 計算PID輸出float output = Kp * error + Ki * integral + Kd * derivative;prev_error = error;  // 更新上一次的誤差return output;
}
//增量式pid計算
float PID_Update( float measured_value,float setpoint) 
{float error = setpoint - measured_value; // 當前誤差float integral = prev_integral + error; // 積分項float derivative = error - prev_error; // 微分項// 計算控制增量float delta_output = Kp * error + Ki * integral +Kd * derivative;// 更新狀態prev_error = error;prev_integral = integral;return delta_output;
}

????????這里的Control函數我是直接在讀取編碼器的那個定時函數里面執行的.

.h文件

#ifndef __TRACE_H_
#define __TRACE_H_#include "main.h"
#include "motor.h"#define P1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define P2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define P3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define P4 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10)
#define P5 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)
#define P6 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_12)
#define P7 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_13)// 定義PID結構體
typedef struct {float Kp;  // 比例系數float Ki;  // 積分系數float Kd;  // 微分系數float setpoint;  // 設定值float integral;  // 誤差積分float prev_error;  // 上一次的誤差
}	PIDController;extern int32_t Speed_Middle;          // 中值速度
extern int32_t max;											//輸出最大值
extern int32_t min;									//輸出最小值
extern int32_t Motor_Left, Motor_Right;		//輸出左右輪速度extern PIDController pid;
extern int32_t leftSpeed;
extern int32_t rightSpeed;extern int Incremental_Quantity();
extern void Control();
extern int32_t Limit(int32_t IN, int32_t limit, int32_t limiter);
extern void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float setpoint);
extern float PID_Compute(float current_value,float setpoint);
extern float PID_Update( float measured_value,float setpoint);#endif

7.4 測試

尋跡小車

總結

? ? ? ? 這篇博客主要是對剛學習單片機的初學者,想要做個小東西,沒有什么方向,做一個借鑒.主要是藍牙控制和七路灰度傳感器的尋跡.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71164.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71164.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71164.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SQL命令詳解之操作數據庫

操作數據庫 SQL是用于管理和操作關系型數據庫的標準語言。數據庫操作是SQL的核心功能之一&#xff0c;主要用于創建、修改和刪除數據庫對象&#xff0c;如數據庫、表、視圖和索引等。以下是SQL中常見的數據庫操作命令及其功能簡介&#xff1a; 1. 查詢數據庫 查詢所有的數據庫…

Go紅隊開發—編解碼工具

文章目錄 開啟一個項目編解碼工具開發Dongle包Base64編解碼摩斯密碼URL加解密AES加解密 MD5碰撞工具開發 開啟一個項目 這作為補充內容&#xff0c;可忽略直接看下面的編解碼&#xff1a; 一開始用就按照下面的步驟即可 1.創建一個文件夾&#xff0c;你自己定義名字(建議只用…

Starrocks入門(二)

1、背景&#xff1a;考慮到Starrocks入門這篇文章&#xff0c;安裝的是3.0.1版本的SR&#xff0c;參考&#xff1a;Starrocks入門-CSDN博客 但是官網的文檔&#xff0c;沒有對應3.0.x版本的資料&#xff0c;卻有3.2或者3.3或者3.4或者3.1或者2.5版本的資料&#xff0c;不要用較…

工程化與框架系列(10)--微前端架構

微前端架構 &#x1f3d7;? 微前端是一種將前端應用分解成更小、更易管理的獨立部分的架構模式。本文將詳細介紹微前端的核心概念、實現方案和最佳實踐。 微前端概述 &#x1f31f; &#x1f4a1; 小知識&#xff1a;微前端的核心理念是將前端應用分解成一系列獨立部署、松耦…

SwiftUI之狀態管理全解析

文章目錄 引言一、`@State`1.1 基本概念1.2 初始化與默認值1.3 注意事項二、`@Binding`2.1 基本概念2.2 初始化與使用2.3 注意事項三、`@ObservedObject`3.1 基本概念3.2 初始化與使用3.3 注意事項四、`@EnvironmentObject`4.1 基本概念4.2 初始化與使用4.3 注意事項五、`@Stat…

Redis 高可用性:如何讓你的緩存一直在線,穩定運行?

&#x1f3af; 引言&#xff1a;Redis的高可用性為啥這么重要&#xff1f; 在現代高可用系統中&#xff0c;Redis 是一款不可或缺的分布式緩存與數據庫系統。無論是提升訪問速度&#xff0c;還是實現數據的高效持久化&#xff0c;Redis 都能輕松搞定。可是&#xff0c;當你把 …

面試題:說一下你對DDD的了解?

面試題:說一下你對DDD的了解? 在面試中,關于 DDD(領域驅動設計,Domain-Driven Design) 的問題是一個常見的技術考察點。DDD 是一種軟件設計方法論,旨在通過深入理解業務領域來構建復雜的軟件系統。以下是一個清晰、詳細的回答模板,幫助你在面試中脫穎而出: DDD 的定義…

Redis---緩存穿透,雪崩,擊穿

文章目錄 緩存穿透什么是緩存穿透&#xff1f;緩存穿透情況的處理流程是怎樣的&#xff1f;緩存穿透的解決辦法緩存無效 key布隆過濾器 緩存雪崩什么是緩存雪崩&#xff1f;緩存雪崩的解決辦法 緩存擊穿什么是緩存擊穿&#xff1f;緩存擊穿的解決辦法 區別對比 在如今的開發中&…

Android Logcat 高效調試指南

工具概覽 Logcat 是 Android SDK 提供的命令行日志工具&#xff0c;支持靈活過濾、格式定制和實時監控&#xff0c;官方文檔詳見 Android Developer。 基礎用法 命令格式 [adb] logcat [<option>] ... [<filter-spec>] ... 執行方式 直接調用&#xff08;通過ADB守…

【定昌Linux系統】部署了java程序,設置開啟啟動

將代碼上傳到相應的目錄&#xff0c;并且配置了一個.sh的啟動腳本文件 文件內容&#xff1a; #!/bin/bash# 指定JAR文件的路徑&#xff08;如果JAR文件在當前目錄&#xff0c;可以直接使用文件名&#xff09; JAR_FILE"/usr/local/java/xs_luruan_client/lib/xs_luruan_…

Java 8 中,可以使用 Stream API 和 Comparator 對 List 按照元素對象的時間字段進行倒序排序

文章目錄 引言I 示例對象II List 按時間字段倒序排序: 使用 `Stream` 和 `Comparator` 排序方法 1:使用 `Comparator.comparing`方法 2:使用 `Comparator.reversed`方法 3:自定義 `Comparator`輸出結果III 注意事項**時間字段類型**:**空值處理**:IV 總結引言 案例:在線用…

jvm內存模型,類加載機制,GC算法,垃圾回收器,jvm線上調優等常見的面試題及答案

JVM內存模型 JVM內存模型包括哪些區域 答案&#xff1a;JVM內存模型主要包括以下區域&#xff1a; 程序計數器&#xff1a;是一塊較小的內存空間&#xff0c;它可以看作是當前線程所執行的字節碼的行號指示器&#xff0c;用于記錄正在執行的虛擬機字節碼指令的地址。Java虛擬機…

git clone的時候出現出現error

報錯如下&#xff1a; Collecting githttps://github.com/haotian-liu/LLaVA.git Cloning https://github.com/haotian-liu/LLaVA.git to /tmp/pip-req-build-360q6tt1 Running command git clone --filterblob:none --quiet https://github.com/haotian-liu/LLaVA.git /t…

Minio搭建并在SpringBoot中使用完成用戶頭像的上傳

Minio使用搭建并上傳用戶頭像到服務器操作,學習筆記 Minio介紹 minio官網 MinIO是一個開源的分布式對象存儲服務器&#xff0c;支持S3協議并且可以在多節點上實現數據的高可用和容錯。它采用Go語言開發&#xff0c;擁有輕量級、高性能、易部署等特點&#xff0c;并且可以自由…

vue3中ref和reactive響應式數據、ref模板引用(組合式和選項式區別)、組件ref的使用

目錄 Ⅰ.ref 1.基本用法&#xff1a;ref響應式數據 2.ref模板引用 3.ref在v-for中的模板引用 ?4.ref在組件上使用 ?5.TS中ref數據標注類型 Ⅱ.reactive 1.基本用法&#xff1a;reactive響應式數據 2.TS中reactive標注類型 Ⅲ.ref和reactive的使用場景和區別 Ⅳ.小結…

javascript實現雪花飄落效果

本文實現雪花飄落效果的 JavaScript 網頁設計案例&#xff0c;代碼實現如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, init…

項目準備(flask+pyhon+MachineLearning)- 3

目錄 1.商品信息 2. 商品銷售預測 2.1 機器學習 2.2 預測功能 3. 模型評估 1.商品信息 app.route(/products) def products():"""商品分析頁面"""data load_data()# 計算當前期間和上期間current_period data[data[成交時間] > data[成…

FPGA開發,使用Deepseek V3還是R1(3):系統級與RTL級

以下都是Deepseek生成的答案 FPGA開發&#xff0c;使用Deepseek V3還是R1&#xff08;1&#xff09;&#xff1a;應用場景 FPGA開發&#xff0c;使用Deepseek V3還是R1&#xff08;2&#xff09;&#xff1a;V3和R1的區別 FPGA開發&#xff0c;使用Deepseek V3還是R1&#x…

實現 Leaflet 多類型點位標記與聚合功能的實戰經驗分享

在現代的地理信息系統&#xff08;GIS&#xff09;應用中&#xff0c;地圖功能是不可或缺的一部分。無論是展示商業網點、旅游景點還是公共服務設施&#xff0c;地圖都能以直觀的方式呈現數據。然而&#xff0c;當數據量較大時&#xff0c;地圖上可能會出現大量的標記點&#x…

企微審批中MySQL字段TEXT類型被截斷的排查與修復實踐

在MySQL中&#xff0c;TEXT類型字段常用于存儲較大的文本數據&#xff0c;但在一些應用場景中&#xff0c;當文本內容較大時&#xff0c;TEXT類型字段可能無法滿足需求&#xff0c;導致數據截斷或插入失敗。為了避免這種問題&#xff0c;了解不同文本類型&#xff08;如TEXT、M…