日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于stm32的自平衡小车

發布時間:2024/1/1 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于stm32的自平衡小车 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 引言
  • 1、系統概述
    • 1.1、設計任務
    • 1.2、設計要求
  • 2、方案設計與論證
    • 2.1、芯片選擇方案
    • 2.2 、系統概述
    • 2.3、設計要求
    • 2.4、系統總體設計
    • 2.5、各功能模塊程序實現原理分析
      • 2.5.1、MPU6050模塊的介紹
      • 2.5.2、OLED12864顯示屏
      • 2.5.3、LN298N電機驅動塊
  • 3、單片機的選擇及硬件設計介紹
    • 3.1、單片機選擇
    • 3.2、電路設計
  • 4、系統程序
    • 4.1、主程序
      • 4.1.1主程序設計如下
      • 4.1.2主程序流程圖
    • 4.2、主程序代碼
    • 4.3、模塊程序代碼
  • 5、系統調試及分析
    • 5.1、系統調試
    • 5.2、調試現象及分析
    • 5.3、測試結果
  • 6、參考借鑒內容

引言

1、系統概述

1.1、設計任務

利用stm32做一輛自平衡小車

1.2、設計要求

利用IIC和MPU6050、OLED12864進行通信,使用pid算法到自平衡,熟練掌握PID算法

2、方案設計與論證

2.1、芯片選擇方案

芯片可以選擇stm32和arduino,基于學習目的,使用stm32

stm32是一個低功耗,高性能32位單片機,片內含4k Bytes ISP(In-system programmable)的可反復擦寫1000次的Flash只讀程序存儲器。主要性能有:與MCS-51單片機產品兼容、全靜態操作:0Hz~33Hz、 三級加密程序存儲器、32個可編程I/O口線、三個16位定時器/計數器、八個中斷源、全雙工UART串行通道、掉電后中斷可喚醒、看門狗定時器、雙數據指針、掉電標識符、易編程。

2.2 、系統概述

本設計是一個具有自動調節平衡功能的兩輪小車。由MPU6050、12864OLED顯示屏、電機驅動塊、電機、供電電路等模塊組成。本項目研究一種使用單片機PID算法的自平衡方案。這種方案后續可以制作成為自平衡代步工具,自平衡自行車等等。

2.3、設計要求

  • IIC通信
  • PID算法的調節,要求抖動不得超過1cm’
  • 小車可以正常達到自平衡
  • oled顯示偏航/俯仰/滾動角的數據
  • 2.4、系統總體設計

    利用stm32和MPU6050進行通信,實時獲取mpu6050發送過來的數據,并且再oled上面顯示,利用mpu6050的數據,結合PID算法,控制電機驅動塊去控制電機的正方轉,以達到自平衡的目的。

    2.5、各功能模塊程序實現原理分析

    2.5.1、MPU6050模塊的介紹

    MPU6050內部整合了三軸MEMS陀螺儀、三軸MEMS加速度計以及一個可擴展的數字運動處理器DMP(Digital Motion Processor),而且還可以連接一個第三方數字傳感器(如磁力計),這樣的話,就可以通過IIC接口輸出一個9軸信號(鏈接第三方數字傳感器才可以輸出九軸信號,否則只有六軸信號)。更加方便的是,有了DMP,可以結合InvenSense公司提供的運動處理資料庫,實現姿態解算。通過自帶的DMP,可以通過IIC接口輸出9軸融合演算的數據,大大降低了運動處理運算對操作系統的負荷,同時也降低了開發難度。其實,簡單一句話說,陀螺儀就是測角速度的,加速度傳感器就是測角加速度的,二者數據通過算法就可以得到PITCH、YAW、ROLL角了。

    陀螺儀知識點介紹:

    陀螺儀是用高速回轉體的動量矩敏感殼體相對慣性空間繞正交于自轉軸的一個或二個軸的角運動檢測裝置。利用其他原理制成的角運動檢測裝置起同樣功能的也稱陀螺儀。
    從力學的觀點近似的分析陀螺的運動時,可以把它看成是一個剛體,剛體上有一個萬向支點,而陀螺可以繞著這個支點作三個自由度的轉動,所以陀螺的運動是屬于剛體繞一個定點的轉動運動。更確切地說,一個繞對稱鈾高速旋轉的飛輪轉子叫陀螺。將陀螺安裝在框架裝置上,使陀螺的自轉軸有角轉動的自由度,這種裝置的總體叫做陀螺儀。
    陀螺儀的原理就是,一個旋轉物體的旋轉軸所指的方向在不受外力影響時,是不會改變的。人們根據這個道理,用它來保持方向,制造出來的東西就叫陀螺儀。我們騎自行車其實也是利用了這個原理。輪子轉得越快越不容易倒,因為車軸有一股保持水平的力量。陀螺儀在工作時要給它一個力,使它快速旋轉起來,一般能達到每分鐘幾十萬轉,可以工作很長時間。然后用多種方法讀取軸所指示的方向,并自動將數據信號傳給控制系統。

    2.5.2、OLED12864顯示屏

    OLED 屏幕作為一種新型的顯示技術,其自身可以發光,亮度,對比度高,功耗低,在當下備受追捧。而在我們正常的顯示調整參數過程中,我們越來越多的使用這種屏幕。我們使用的一般是分辨率為 128×64 ,屏幕尺寸為 0.96 寸。由于其較小的尺寸和比較高的分辨率,讓它有著很好的顯示效果和便攜性。

    2.5.3、LN298N電機驅動塊

    L298N是專用驅動集成電路,屬于H橋集成電路,與L293D的差別是起輸出電流增大,功率增強。其輸出電流為2A,最高電流4A,最高工作電壓50V,可以驅動感性負載,如大功率直流電機,步進電機,電磁閥等等,特別是其輸入端可以與單片機直接相連,從而很方便地受單片機控制。當驅動直流電機時,可以直接控制步進電機,并可以實現電機的正轉和反轉,實現此功能只需要改變輸入端的邏輯電平。為了避免電機對單片機的干擾,本模塊加入光耦,進行光電隔離,從而使系統能夠穩定可靠的工作。

    3、單片機的選擇及硬件設計介紹

    3.1、單片機選擇

    出于實驗目的選擇STM32F103ZET6,性能足,完善功能強大,管腳也夠,后續完善功能將改為STM32F103C8T6。

    3.2、電路設計



    4、系統程序

    4.1、主程序

    4.1.1主程序設計如下

    由mpu6050采集角度信息,并發送到STM32上,STM32進行PID的調節,反饋給電機驅動塊,電機驅動塊調整電機轉動方向和速度,以達到調整角度的目的,電機驅動塊和mpu6050閉環。

    4.1.2主程序流程圖

    4.2、主程序代碼

    #include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "key.h"#include "mpu6050.h" #include "inv_mpu.h" #include "inv_mpu_dmp_motion_driver.h" #include "oled.h" #include "stdio.h" #include <string.h>#include "pid.h" #include "motor.h"extern int Moto1,Moto2; float pitch,roll,yaw; //歐拉角short aacx,aacy,aacz; //加速度傳感器原始數據short gyrox,gyroy,gyroz; //陀螺儀原始數據int temp1; //溫度u8 stop_flag=0; //小車停止標志位void calculation(void);//void pid_pingheng();//串口1發送1個字符 //c:要發送的字符 void usart1_send_char(u8 c) { while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)==RESET){};//循環發送,直到發送完畢 HAL_UART_Transmit(&UART1_Handler,&c,1,1000); } int main(void) { HAL_Init(); //初始化HAL庫 Stm32_Clock_Init(RCC_PLL_MUL9); //設置時鐘,72Mdelay_init(72); //初始化延時函數MiniBalance_PWM_Init(20000-1,72-1); //初始化PWM 72m/72=1m 1m/20000=50hz 1/50hz=0.02sPID_Init();uart_init(115200); //初始化串口 LED_Init(); //初始化LED KEY_Init(); //初始化按鍵MPU_Init(); //初始化MPU6050for(int i=0;i<10;i++){OLED_Init(); delay_ms(20);}OLED_ColorTurn(0);//0正常顯示,1 反色顯示OLED_DisplayTurn(0);//0正常顯示 1 屏幕翻轉顯示if(mpu_dmp_init()!=0) {printf(".");OLED_ShowString(50,20,"wait",12,1);OLED_Refresh(); } if(mpu_dmp_init()!=0) OLED_ShowString(50,20," ",12,1);OLED_ShowString(0,0,"pitch",16,1);OLED_ShowString(0,16,"err",16,1);OLED_ShowString(0,32,"L_err",16,1);OLED_ShowString(0,48,"PID",16,1);// int i=0;while(1){while(mpu_dmp_get_data(&pitch,&roll,&yaw)!=0){ // temp=MPU_Get_Temperature(); //得到溫度值MPU_Get_Accelerometer(&aacx,&aacy,&aacz); //得到加速度傳感器數據MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz); //得到陀螺儀數據//mpu6050_send_data(aacx,aacy,aacz,gyrox,gyroy,gyroz);//用自定義幀發送加速度和陀螺儀原始數據//usart1_report_imu(aacx,aacy,aacz,gyrox,gyroy,gyroz,(int)(roll*100),(int)(pitch*100),(int)(yaw*10));LED0=!LED0;//LED閃爍} // printf("roll:%d pitch:%d yaw:%d \r\n",(int)(roll*100),(int)(pitch*100),(int)(yaw*10));calculation(); //整體計算PID,讀取角度值等等if(pitch<0){OLED_ShowString(70,0,"-",16,1);OLED_ShowNum(90,0,-pitch,2,16,1);} else {OLED_ShowString(70,0," ",16,1);OLED_ShowNum(90,0,pitch,2,16,1);}if(err<0){OLED_ShowString(40,16,"-",16,1);OLED_ShowNum(60,16,-err,5,16,1);} else {OLED_ShowString(40,16," ",16,1);OLED_ShowNum(60,16,err,5,16,1);}if(last_err<0){OLED_ShowString(40,32,"-",16,1);OLED_ShowNum(60,32,-llast_err,5,16,1);} else {OLED_ShowString(40,32," ",16,1);OLED_ShowNum(60,32,llast_err,5,16,1);}if(temp1<0){OLED_ShowString(40,48,"-",16,1);OLED_ShowNum(60,48,-temp1,5,16,1);} else {OLED_ShowString(40,48," ",16,1);OLED_ShowNum(60,48,temp1,5,16,1);} OLED_Refresh();delay_ms(50);} } /************************************************************************** 函數功能:整體計算PID,讀取角度值等等 入口參數:未知 返回 值:無本應該不寫在這,但是因為各種原因只能放這 **************************************************************************/ void calculation(void) { if(mpu_dmp_get_data(&pitch,&roll,&yaw)==0){ pid.Pv = (int)pitch*100; //角度*十倍 // if(Turn_Off(pid.Pv)) stop_flag=1; //檢測是否小車異常,異常就停止 // else stop_flag=0;if(pid.Pv<-3000||pid.Pv>3000){ //===傾角大于40度關閉電機IN1=0; IN2=0;IN3=0;IN4=0;stop_flag=1; }else{stop_flag=0;}Moto1 = balance(pid.Pv) ; //獲得PWM輸出值Moto2 = balance(pid.Pv);temp1 = balance(pid.Pv);Xianfu_Pwm(); //對PWM進行限幅Set_Pwm(Moto1,Moto2); //設置PWM} }/* HAL_GPIO_WritePin(GPIOA,GPIO_PIN_9,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);delay_ms(1000); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET); delay_ms(1000); */

    4.3、模塊程序代碼

    電機驅動塊:

    #include "motor.h"int Moto1,Moto2; //電機PWM變量void Error_Handler(void) {/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */ }/************************************************************************** 函數功能:電機驅動輸出IO初始化 入口參數:無 返回 值:無 **************************************************************************/ void MiniBalance_Motor_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口時鐘__HAL_RCC_GPIOB_CLK_ENABLE(); //使能PB端口時鐘GPIO_InitStructure.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15; //端口配置GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; //推挽輸出GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; //50MHAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //根據設定參數初始化GPIOB } /************************************************************************** 函數功能:電機驅動PWM輸出IO口初始化 入口參數:無 返回 值:無 **************************************************************************/ TIM_HandleTypeDef htim1; //tim1結構體初始化 void MiniBalance_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; //引腳結構體初始化TIM_OC_InitTypeDef sConfigOC = {0}; //輸出比較結構體初始化MiniBalance_Motor_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); //使能PA端口時鐘__HAL_RCC_TIM1_CLK_ENABLE(); // 使能TIM1端口時鐘 TIM1 TIM8在APB2//設置該引腳為復用輸出功能,輸出TIM1 CH1 CH4的PWM脈沖波形GPIO_InitStructure.Pin= GPIO_PIN_8|GPIO_PIN_11; //TIM_CH1 //TIM_CH4GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; //復用推挽輸出GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); //根據設定參數初始化GPIOAhtim1.Instance = TIM1;htim1.Init.Prescaler = psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 不分頻htim1.Init.Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 htim1.Init.ClockDivision = 0; //設置時鐘分割:TDTS = Tck_timhtim1.Init.CounterMode = TIM_COUNTERMODE_UP;//TIM向上計數模式htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //disable自動重裝載if (HAL_TIM_PWM_Init(&htim1) != HAL_OK){Error_Handler();}if (HAL_TIM_OC_Init(&htim1) != HAL_OK){Error_Handler();}sConfigOC.OCMode = TIM_OCMODE_PWM1; //選擇定時器模式:TIM脈沖寬度調制模式1sConfigOC.Pulse = 0; //設置待裝入捕獲比較寄存器的脈沖值sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;//輸出極性:TIM輸出比較極性高sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; //指定互補輸出極性。sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;/**使能通道1**/if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){Error_Handler();}/**使能通道2**/if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK){Error_Handler();}sConfigOC.OCMode = TIM_OCMODE_TIMING; //清除模式配置 方便下一個通道使用HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4);__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 5000);__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, 5000);}/************************************************************************** 函數功能:獲得整數絕對值函數 入口參數:整數 返回 值:絕對值 **************************************************************************/ int myabs(int a) { int temp;if(a<0) temp=-a; else temp=a;return temp; } /************************************************************************** 函數功能:最后設置PWM函數,并檢測是否關閉電機 入口參數:無 返回 值:無 **************************************************************************/ void Set_Pwm(int moto1,int moto2) {if(stop_flag==0){if(moto1>0) IN1=1, IN2=0;else IN1=0, IN2=1;PWMA=myabs(moto1);if(moto2>0) IN3=1, IN4=0;else IN3=0, IN4=1;PWMB=myabs(moto2);}} /************************************************************************** 函數功能:PWM限幅函數 入口參數:無 返回 值:無 **************************************************************************/ void Xianfu_Pwm(void) { int Amplitude=10000; //===PWM滿幅是20000 限制在10000if(Moto1 < -Amplitude) Moto1=-Amplitude; if(Moto1 > Amplitude) Moto1=Amplitude; if(Moto2 < -Amplitude) Moto2=-Amplitude; if(Moto2 > Amplitude) Moto2=Amplitude; } /************************************************************************** 函數功能:電機異常關閉函數 入口參數:角度 返回 值:1:關閉,0:不關閉 **************************************************************************/ u8 Turn_Off(signed int angle) {u8 temp=0;if(angle<-300||angle>300){ //===傾角大于40度關閉電機temp=1; //===Flag_Stop置1關閉電機IN1=0; IN2=0;IN3=0;IN4=0;}return temp; }

    PID.C

    #include "pid.h" #include "usart.h" #include "motor.h"PID pid; signed int err; signed int last_err; signed int llast_err; /************************************************************************** 函數功能:PID數據初始化 入口參數:無 返回 值:無 **************************************************************************/ void PID_Init() {/*平衡PID環控制參數初始化*/pid.Sv = 0; pid.Kp = 10;pid.Ki = 0.001; pid.Kd = 0.05; }/************************************************************************** 函數功能:以下三個函數,分別計算各個環的PID值,并返回 入口參數:未知 返回 值:無 **************************************************************************//*小車平衡環部分,微分+比例控制 微分變量為直接讀取的加速度*/ extern short pitch; int leijia; //累加 int balance(float Angle) { int balance;if(stop_flag==0){// Angle=pid.Pv;err=(0-Angle);leijia+=last_err;balance=pid.Kp*last_err+pid.Ki*(leijia)+pid.Kd*(last_err-llast_err);llast_err=last_err;last_err=err;printf("Angle:%.2f balance:%d pid.Kp:%d Bias:%d \r\n",Angle,balance,pid.Kp,err);}return balance; }

    OLED.C是使用中景園的代碼:

    #include "oled.h" #include "stdlib.h" #include "stdint.h" #include "oledfont.h" #include "delay.h"#define OLED_ADDRESS 0x78uint8_t OLED_GRAM[144][8];//反顯函數 void OLED_ColorTurn(uint8_t i) {if(i==0){OLED_WR_Byte(0xA6,OLED_CMD);//正常顯示}if(i==1){OLED_WR_Byte(0xA7,OLED_CMD);//反色顯示} }//屏幕旋轉180度 void OLED_DisplayTurn(uint8_t i) {if(i==0){OLED_WR_Byte(0xC8,OLED_CMD);//正常顯示OLED_WR_Byte(0xA1,OLED_CMD);}if(i==1){OLED_WR_Byte(0xC0,OLED_CMD);//反轉顯示OLED_WR_Byte(0xA0,OLED_CMD);} }//延時 void IIC_delay(void) {uint8_t t=3;while(t--); }//起始信號 void I2C_Start(void) {OLED_SDA_Set();OLED_SCL_Set();IIC_delay();OLED_SDA_Clr();IIC_delay();OLED_SCL_Clr();IIC_delay(); }//結束信號 void I2C_Stop(void) {OLED_SDA_Clr();OLED_SCL_Set();IIC_delay();OLED_SDA_Set(); }//等待信號響應 void I2C_WaitAck(void) //測數據信號的電平 {OLED_SDA_Set();IIC_delay();OLED_SCL_Set();IIC_delay();OLED_SCL_Clr();IIC_delay(); }//寫入一個字節 void Send_Byte(uint8_t dat) {uint8_t i;for(i=0;i<8;i++){if(dat&0x80)//將dat的8位從最高位依次寫入{OLED_SDA_Set();}else{OLED_SDA_Clr();}IIC_delay();OLED_SCL_Set();IIC_delay();OLED_SCL_Clr();//將時鐘信號設置為低電平dat<<=1;} }//發送一個字節 //mode:數據/命令標志 0,表示命令;1,表示數據; void OLED_WR_Byte(uint8_t dat,uint8_t mode) {I2C_Start();Send_Byte(0x78);I2C_WaitAck();if(mode){Send_Byte(0x40);}else{Send_Byte(0x00);}I2C_WaitAck();Send_Byte(dat);I2C_WaitAck();I2C_Stop(); }//開啟OLED顯示 void OLED_DisPlay_On(void) {OLED_WR_Byte(0x8D,OLED_CMD);//電荷泵使能OLED_WR_Byte(0x14,OLED_CMD);//開啟電荷泵OLED_WR_Byte(0xAF,OLED_CMD);//點亮屏幕 }//關閉OLED顯示 void OLED_DisPlay_Off(void) {OLED_WR_Byte(0x8D,OLED_CMD);//電荷泵使能OLED_WR_Byte(0x10,OLED_CMD);//關閉電荷泵OLED_WR_Byte(0xAE,OLED_CMD);//關閉屏幕 }//更新顯存到OLED void OLED_Refresh(void) {uint8_t i,n;for(i=0;i<8;i++){OLED_WR_Byte(0xb0+i,OLED_CMD); //設置行起始地址OLED_WR_Byte(0x00,OLED_CMD); //設置低列起始地址OLED_WR_Byte(0x10,OLED_CMD); //設置高列起始地址I2C_Start();Send_Byte(0x78);I2C_WaitAck();Send_Byte(0x40);I2C_WaitAck();for(n=0;n<128;n++){Send_Byte(OLED_GRAM[n][i]);I2C_WaitAck();}I2C_Stop();} } //清屏函數 void OLED_Clear(void) {uint8_t i,n;for(i=0;i<8;i++){for(n=0;n<128;n++){OLED_GRAM[n][i]=0;//清除所有數據}}OLED_Refresh();//更新顯示 }//畫點 //x:0~127 //y:0~63 //t:1 填充 0,清空 void OLED_DrawPoint(uint8_t x,uint8_t y,uint8_t t) {uint8_t i,m,n;i=y/8;m=y%8;n=1<<m;if(t){OLED_GRAM[x][i]|=n;}else{OLED_GRAM[x][i]=~OLED_GRAM[x][i];OLED_GRAM[x][i]|=n;OLED_GRAM[x][i]=~OLED_GRAM[x][i];} }//畫線 //x1,y1:起點坐標 //x2,y2:結束坐標 void OLED_DrawLine(uint8_t x1,uint8_t y1,uint8_t x2,uint8_t y2,uint8_t mode) {uint16_t t; int xerr=0,yerr=0,delta_x,delta_y,distance;int incx,incy,uRow,uCol;delta_x=x2-x1; //計算坐標增量 delta_y=y2-y1;uRow=x1;//畫線起點坐標uCol=y1;if(delta_x>0)incx=1; //設置單步方向 else if (delta_x==0)incx=0;//垂直線 else {incx=-1;delta_x=-delta_x;}if(delta_y>0)incy=1;else if (delta_y==0)incy=0;//水平線 else {incy=-1;delta_y=-delta_x;}if(delta_x>delta_y)distance=delta_x; //選取基本增量坐標軸 else distance=delta_y;for(t=0;t<distance+1;t++){OLED_DrawPoint(uRow,uCol,mode);//畫點xerr+=delta_x;yerr+=delta_y;if(xerr>distance){xerr-=distance;uRow+=incx;}if(yerr>distance){yerr-=distance;uCol+=incy;}} } //x,y:圓心坐標 //r:圓的半徑 void OLED_DrawCircle(uint8_t x,uint8_t y,uint8_t r) {int a, b,num;a = 0;b = r;while(2 * b * b >= r * r) {OLED_DrawPoint(x + a, y - b,1);OLED_DrawPoint(x - a, y - b,1);OLED_DrawPoint(x - a, y + b,1);OLED_DrawPoint(x + a, y + b,1);OLED_DrawPoint(x + b, y + a,1);OLED_DrawPoint(x + b, y - a,1);OLED_DrawPoint(x - b, y - a,1);OLED_DrawPoint(x - b, y + a,1);a++;num = (a * a + b * b) - r*r;//計算畫的點離圓心的距離if(num > 0){b--;a--;}} }//在指定位置顯示一個字符,包括部分字符 //x:0~127 //y:0~63 //size1:選擇字體 6x8/6x12/8x16/12x24 //mode:0,反色顯示;1,正常顯示 void OLED_ShowChar(uint8_t x,uint8_t y,uint8_t chr,uint8_t size1,uint8_t mode) {uint8_t i,m,temp,size2,chr1;uint8_t x0=x,y0=y;if(size1==8)size2=6;else size2=(size1/8+((size1%8)?1:0))*(size1/2); //得到字體一個字符對應點陣集所占的字節數chr1=chr-' '; //計算偏移后的值for(i=0;i<size2;i++){if(size1==8){temp=asc2_0806[chr1][i];} //調用0806字體else if(size1==12){temp=asc2_1206[chr1][i];} //調用1206字體else if(size1==16){temp=asc2_1608[chr1][i];} //調用1608字體else if(size1==24){temp=asc2_2412[chr1][i];} //調用2412字體else return;for(m=0;m<8;m++){if(temp&0x01)OLED_DrawPoint(x,y,mode);else OLED_DrawPoint(x,y,!mode);temp>>=1;y++;}x++;if((size1!=8)&&((x-x0)==size1/2)){x=x0;y0=y0+8;}y=y0;} }//顯示字符串 //x,y:起點坐標 //size1:字體大小 //*chr:字符串起始地址 //mode:0,反色顯示;1,正常顯示 void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t size1,uint8_t mode) {while((*chr>=' ')&&(*chr<='~'))//判斷是不是非法字符!{OLED_ShowChar(x,y,*chr,size1,mode);if(size1==8)x+=6;else x+=size1/2;chr++;} }//m^n uint32_t OLED_Pow(uint8_t m,uint8_t n) {uint32_t result=1;while(n--){result*=m;}return result; }//顯示數字 //x,y :起點坐標 //num :要顯示的數字 //len :數字的位數 //size:字體大小 //mode:0,反色顯示;1,正常顯示 void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size1,uint8_t mode) {uint8_t t,temp,m=0;if(size1==8)m=2;for(t=0;t<len;t++){temp=(num/OLED_Pow(10,len-t-1))%10;if(temp==0){OLED_ShowChar(x+(size1/2+m)*t,y,'0',size1,mode);}else {OLED_ShowChar(x+(size1/2+m)*t,y,temp+'0',size1,mode);}} }//顯示漢字 //x,y:起點坐標 //num:漢字對應的序號 //mode:0,反色顯示;1,正常顯示 void OLED_ShowChinese(uint8_t x,uint8_t y,uint8_t num,uint8_t size1,uint8_t mode) {uint8_t m,temp;uint8_t x0=x,y0=y;uint16_t i,size3=(size1/8+((size1%8)?1:0))*size1; //得到字體一個字符對應點陣集所占的字節數for(i=0;i<size3;i++){if(size1==16){temp=Hzk1[num][i];}//調用16*16字體else if(size1==24){temp=Hzk2[num][i];}//調用24*24字體else if(size1==32) {temp=Hzk3[num][i];}//調用32*32字體else if(size1==64){temp=Hzk4[num][i];}//調用64*64字體else return;for(m=0;m<8;m++){if(temp&0x01)OLED_DrawPoint(x,y,mode);else OLED_DrawPoint(x,y,!mode);temp>>=1;y++;}x++;if((x-x0)==size1){x=x0;y0=y0+8;}y=y0;} }//num 顯示漢字的個數 //space 每一遍顯示的間隔 //mode:0,反色顯示;1,正常顯示 void OLED_ScrollDisplay(uint8_t num,uint8_t space,uint8_t mode) {uint8_t i,n,t=0,m=0,r;while(1){if(m==0){OLED_ShowChinese(128,24,t,16,mode); //寫入一個漢字保存在OLED_GRAM[][]數組中t++;}if(t==num){for(r=0;r<16*space;r++) //顯示間隔{for(i=1;i<144;i++){for(n=0;n<8;n++){OLED_GRAM[i-1][n]=OLED_GRAM[i][n];}}OLED_Refresh();}t=0;}m++;if(m==16){m=0;}for(i=1;i<144;i++) //實現左移{for(n=0;n<8;n++){OLED_GRAM[i-1][n]=OLED_GRAM[i][n];}}OLED_Refresh();} }//x,y:起點坐標 //sizex,sizey,圖片長寬 //BMP[]:要寫入的圖片數組 //mode:0,反色顯示;1,正常顯示 void OLED_ShowPicture(uint8_t x,uint8_t y,uint8_t sizex,uint8_t sizey,uint8_t BMP[],uint8_t mode) {uint16_t j=0;uint8_t i,n,temp,m;uint8_t x0=x,y0=y;sizey=sizey/8+((sizey%8)?1:0);for(n=0;n<sizey;n++){for(i=0;i<sizex;i++){temp=BMP[j];j++;for(m=0;m<8;m++){if(temp&0x01)OLED_DrawPoint(x,y,mode);else OLED_DrawPoint(x,y,!mode);temp>>=1;y++;}x++;if((x-x0)==sizex){x=x0;y0=y0+8;}y=y0;}} } //OLED的初始化 void OLED_Init(void) { // GPIO_InitTypeDef GPIO_InitStructure; // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG|RCC_APB2Periph_GPIOD, ENABLE); //使能A端口時鐘 // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //推挽輸出 // GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz // GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化GPIOG12 // GPIO_SetBits(GPIOG,GPIO_Pin_12); // // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //推挽輸出 // GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz // GPIO_Init(GPIOD, &GPIO_InitStructure); //初始化GPIOD1,5,15 // GPIO_SetBits(GPIOD,GPIO_Pin_5); // // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出 // GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz // GPIO_Init(GPIOD, &GPIO_InitStructure); //初始化GPIOD1,5,15 // GPIO_SetBits(GPIOD,GPIO_Pin_4); MX_GPIO_Init(); OLED_RES_Clr();HAL_Delay(200);OLED_RES_Set();OLED_WR_Byte(0xAE,OLED_CMD);//--關閉oled面板OLED_WR_Byte(0x00,OLED_CMD);//---設置低列地址OLED_WR_Byte(0x10,OLED_CMD);//---設置高列地址OLED_WR_Byte(0x40,OLED_CMD);//--設置映射RAM顯示起始線(0x00~0x3F)OLED_WR_Byte(0x81,OLED_CMD);//--設置對比度控制寄存器OLED_WR_Byte(0xCF,OLED_CMD);// 設置SEG輸出電流亮度OLED_WR_Byte(0xA1,OLED_CMD);//--賽格/列映射 0xa0左右反置 0xa1正常OLED_WR_Byte(0xC8,OLED_CMD);//設置“COM/Row掃描方向” 0xc0上下反置 0xc8正常OLED_WR_Byte(0xA6,OLED_CMD);//--設置正常顯示OLED_WR_Byte(0xA8,OLED_CMD);//--設置復用比例(1 ~ 64)OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 dutyOLED_WR_Byte(0xD3,OLED_CMD);//-設置顯示偏移移位映射RAM計數器(0x00~0x3F)OLED_WR_Byte(0x00,OLED_CMD);//-not offsetOLED_WR_Byte(0xd5,OLED_CMD);//--設置顯示時鐘分頻比/振蕩器頻率OLED_WR_Byte(0x80,OLED_CMD);//--設置分割比率,設置時鐘為100幀/秒OLED_WR_Byte(0xD9,OLED_CMD);//--設置pre-charge時期OLED_WR_Byte(0xF1,OLED_CMD);//設置預充電為15個時鐘&放電為1個時鐘OLED_WR_Byte(0xDA,OLED_CMD);//--設置com引腳硬件配置OLED_WR_Byte(0x12,OLED_CMD);OLED_WR_Byte(0xDB,OLED_CMD);//--設置vcomhOLED_WR_Byte(0x40,OLED_CMD);//設置VCOM取消選擇級別OLED_WR_Byte(0x20,OLED_CMD);//-設置頁面尋址模式(0x00/0x01/0x02)OLED_WR_Byte(0x02,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disableOLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disableOLED_WR_Byte(0xA4,OLED_CMD);// 禁用整個顯示打開(0xa4/0xa5)OLED_WR_Byte(0xA6,OLED_CMD);// 禁用逆顯示開關(0xa6/a7)OLED_Clear();OLED_WR_Byte(0xAF,OLED_CMD); }

    5、系統調試及分析

    5.1、系統調試

    需要調整PID參數和定時器CCR的參數,還是比較麻煩的

    5.2、調試現象及分析

    小車的pid會不穩定,會前后顛倒,傾斜角度不得大于30,否則GG

    5.3、測試結果

    心累

    6、參考借鑒內容

    https://www.bilibili.com/video/BV1AZ4y1V7wt?p=27&spm_id_from=pageDriver

    https://blog.csdn.net/best_xiaolong/article/details/105153978

    https://wenku.baidu.com/view/3e5fc765bb4cf7ec4bfed00b.html

    總結

    以上是生活随笔為你收集整理的基于stm32的自平衡小车的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。