stm32f407igh6学习笔记
?本文將記錄我的學習歷程,基于rm機甲大師實驗室,主要包括各種函數的應用及相應硬件模塊的介紹。閱讀需要有51單片機和基本的寄存器基礎。
前言:
stm32cubemx時鐘樹配置
12 HSE /6 x168 /2 PLLCLK /4 /2
c語言基礎
一 定義結構體 struct Books {char title[50];char author[50];char subject[100];int book_id; } book;//此聲明聲明了擁有四個變量的結構體,同時聲明了結構體變量book,它的類型(標簽)是struct Booksstruct Books book2,book3[20],*book4;//用struct Books標簽又聲明了三個結構體變量//也可以用typedef創建新類型 typedef struct {char title[50];char author[50];char subject[100];int book_id; } Books2; //現在可以用Books2作為類型聲明新的結構體變量 Books2 book5,book6[20],*book7;結構體的成員可以包含其他結構體,也可以包含指向自己結構體類型的指針(在代碼中常見) //此結構體的聲明包含了其他的結構體 struct COMPLEX {char string[100];struct Books book; };//此結構體的聲明包含了指向自己類型的指針 struct NODE {char string[100];struct NODE *next_node; };二 結構體變量的初始化 #include <stdio.h>struct Books {char title[50];char author[50];char subject[100];int book_id; } book = {"C 語言", "RUNOOB", "編程語言", 123456};int main() {printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id); }三 訪問結構成員 非指針用“.”,指針用“->” 如struct Books {char title[50];char author[50];char subject[100];int book_id; } book book.title book.author ...... 如struct Books {char title[50];char author[50];char subject[100];int book_id; } *book2 book2->title book2->author ....... book2=&book一 點亮LED
1 原理:三個 LED 燈的引腳為 PH10,PH11,PH12。在user label 填寫命名LED_B LED_G LED_R 對應引腳輸出高電平點亮。
2 代碼:
點亮LED HAL_GPIO_WritePin(LED_R_GPIO_Port, LED_R_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_G_GPIO_Port, LED_G_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_B_GPIO_Port, LED_B_Pin, GPIO_PIN_SET); 或 HAL_GPIO_WritePin(GPIOH, GPIO_PIN_12, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOH, GPIO_PIN_11, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOH, GPIO_PIN_10, GPIO_PIN_SET); LED閃爍 HAL_GPIO_TogglePin(GPIOH,GPIO_PIN_10); HAL_Delay(500); HAL_GPIO_TogglePin(GPIOH,GPIO_PIN_11); HAL_Delay(500); HAL_GPIO_TogglePin(GPIOH,GPIO_PIN_12); HAL_Delay(500); 參數:端口、引腳 功能:對應端口的引腳電平翻轉 藍色-青色(藍色和綠色混合)-白色(紅藍綠混合)-重灰色(紅綠混合)-紅色-滅 GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) //讀取對應引腳的電平并返回二 flash LED
三 定時器閃爍LED
1 原理:
定時器:分配+計數+重載
(1)預分頻寄存器TIMx_PSC:時鐘源的信號經過TIMx_PSC按其中的分頻比Prescaler進行分頻(頻率除以Prescaler)
(2)自動重裝載寄存器 TIMx_ARR :計數器寄存器TIMx_CNT 根據時鐘的頻率向上計數,直到 等于TIMx_ARR的自動重裝載值Counter Period,產生一個定時中斷觸發信號,TIMx_CNT 被清空, 并重新從 0 開始向上計數。
定時器周期=[(分頻值+1)(重載值+1)]/時鐘源頻率
中斷:當多個中斷發生時,先根據搶占優先級判斷哪個中斷分組能夠優先響應,再到這個中斷分組 中根據各個中斷的響應優先級判斷哪個中斷優先響應。
it.c文件中有我們配置好使能的中斷類型,找到這個中斷服務函數。 里面有中斷回調函數,我們在主文件中對此進行重新編寫。1 HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)? 參數:*htim 定時器的句柄指針,如定時器 1 就輸 入&htim1,定時器 2 就輸入&htim2 作用:中斷服務函數2 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)? 參數:&htim1... 作用:中斷回調函數。 我們可以在別處重寫中斷回調函數,一般我們需要在中斷回調函數中判斷中斷來源并執行相應的用戶操作。 (如定時器1啊等等 if(htim == &htim1))3?HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim) 參數:略 HAL_StatusTypeDef是HAL 庫定義的幾種狀態, 如果成功使定時器開始工作,則返回 HAL_OK 作用:使定時器開始工作4?HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) 參數:略 作用:使對應的定時器開始工作,并使能其定時中斷定時器配置-使能定時器中斷-定時器計數值滿進入中斷-中斷內部先進入中斷服務函數再進入中斷回調函數2 代碼
main.c文件中: 主函數: 各種初始化(包含定時器的初始化)//此部分內容配置好了,不用自己寫 HAL_TIM_Base_Start_IT(&htim1);//開啟定時器1的中斷模式,開始工作中斷回調函數: void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {if(htim == &htim1){//500ms triggerbsp_led_toggle();} }/*主函數開啟定時器1以后,每到達時間就會進入中斷服務函數中的中斷回調函數。 中斷回調函數在hal庫的別的文件下有虛定義__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim),我們需要在主文件下重寫它。 進入主文件下的中斷回調函數以后,首先判斷是不是定時器1,再實現LED燈的翻轉。*/四. PWM 控制 LED 的亮度
1 原理:
脈沖調制有兩個重要的參數:
1 輸出頻率,頻率越高,數字信號代替模擬信號的效果越好。
2 占空比。占空比就是改變輸出模擬效果的電壓大小。占空比越大則模擬出的電壓越大。
算法原理:
1 顏色:16進制對應4位。32位的aRGB變量每8位分別代表了 alpha(透明度)Red(紅色)Green(綠色)和 Blue(藍色)四個要素。
如純紅色表示為 0xFFFF0000(透明度位置FF,紅色位置FF,其他位置0),純綠色表示為 0xFF00FF00,純藍色表示為 0xFF0000FF。黃色由藍色和綠色合成,所以可以表示為 0xFF00FFFF。
通過移位操作獲得每個顏色表示的八位變量,如red = ((RGB_flow_color[i] & 0x00FF0000) >> 16)。然后將這個值作為定時器通道的比較值控制LED燈的亮度調節。
//以上這段非人話,個人認為例程的這段代碼寫復雜了,沒必要。
2 定時器:定時器設置為PWM模式。配置定時器中的比較寄存器TIMx_CCRx,計數寄存器的值不斷增加,小于比較值的時候PWM輸出高電平,大于比較值的時候PWM輸出低電平。
占空比 𝑃 =( 𝑇𝐼𝑀_𝐶𝐶𝑅𝑥 ? 1 )/𝑇𝐼𝑀_𝐴𝑅𝑅 ? 100%。
3 定時器的pwm模式:
每一個定時器的pwm模式有4個通道,每一個通道都有對應標號的比較寄存器,比如定時器5的 1 號通道對應的比較寄存器為 TIM5_CCR1。
可以注意到5號定時器三個通道對應的引腳正是之前的實驗中使用的 LED 引腳,所以如果對5號定時器的通道的比較值進行配置,就能夠控制每一個LED引腳的電壓大小,從而改變燈的亮度。
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, blue); 參數:哪個定時器,哪個通道,比較的值。blue為變量,可變。 作用:通過這個函數將比較值賦值給對應定時器通道的比較寄存器? ? 比較寄存器的值一直在變,輸出電壓一直在變,燈亮度改變。2 代碼:
main.c文件中: 主函數: //開啟定時器 HAL_TIM_Base_Start(&htim5); //開啟PWM通道,分別對應三個LED燈的引腳。LED和定時器硬件都連在這一個引腳上,定時器控制的電壓變化那么燈亮度就變化。 HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_3); 其他略 __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, blue); __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, green); __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, red);五. 蜂鳴器&舵機
1 原理:
蜂鳴器: 輸入方波頻率的不同產生不同的音調。蜂鳴器使用的引腳為 PD14,為定時器 4 的通道 3。Pulse可以設置比較寄存器的初始值。
時鐘的頻率是72MHZ,可以理解為一秒鐘STM32會自己數72M次;分頻系數是72,則該時鐘的頻率會變成72MHZ/72=1MHZ,一秒鐘STM32會自己數1M次,但是在設置的時候要注意,數值應該是72-1;需要定時1ms,由于1ms=1us*1000,那么預裝載值就是1000-1。在波形圖上分頻系數決定了多久來一個點(太快了所以連點成線),重載值決定了波形周期,比較值決定了高電平的時間。
__HAL_TIM_PRESCALER(&htim4, psc)//設置定時器4的分屏系數,HAL庫中重載值也可以設置 __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, pwm);//設置定時器4的通道3的比較值 //psc和pwm都是一個變化的值,所以蜂鳴器連接引腳的電壓一直在變化,發出音調變化舵機:舵機的引腳為定時器1的通道1、2、3、4和定時器8的通道1、2、3。
時鐘源/(分頻+1)/(重載值+1)=50
舵機使用的 PWM 信號一般為頻率 50Hz,舵機的控制一般需要一個20ms的時基脈沖,該脈沖的高電平部分一般為0.5ms~2.5ms范圍內的角度控制脈沖部分,以180度電機為例:0.5ms————–0度;1.0ms————45度;1.5ms————90度;2.0ms———–135度;2.5ms———–180度。
MG995舵機,根據輸入pwm占空比的不同調節轉動的角度。如果我們想要用遙控器操縱舵機,那么首先遙控器初始化(包含DMA初始化),對遙控器遙感變動時候相應變動的寄存器進行編寫(當遙控器寄存器大于小于xx值的時候,pwm咋樣咋樣),實現控制舵機的效果。具體見例程PWM_SNALL。
2 代碼:設置占空比就行,略。
六. 按鍵的外部中斷
1 原理:
外部中斷通常是 GPIO 的電平跳變引起的中斷。在 stm32 中,每一個 GPIO 都可以作為外 部中斷的觸發源,外部中斷一共有 16 條線,對應著 GPIO 的 0-15 引腳,每一條外部中斷 都可以與任意一組的對應引腳相連。上升沿中斷、下降沿中斷、上升沿和下降沿中斷。
配置:將相應引腳設置為外部中斷模式,然后記得使能外部中斷。
外部中斷回調函數HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin),在主文件中重寫,首先判斷是哪個引腳產生的外部中斷看看對不對。每當產生一次外部中斷就進入中斷服務函數先對中斷寄存器進行處理然后進入中斷回調函數。
注意消抖。
2 代碼:
過幾天自己寫一個七 ADC采樣
1 原理:
(1)概念:分辨率=量程/2的n次方,代表一格數字量所反映的電壓等模擬輸入量。
(2)過程:先采樣,再保持,再量化(變成能編碼的量。如輸入電壓量程為4v,位數為3位,那么分辨率是0.5,若采樣到的信號大小是3.6v無法編碼,所以需要量化為3.5v,這樣才能表示為111),最后編碼成數字量給單片機。
(3)怎么測我們需要的地方的電壓:ADC3的通道8連接電源,可以測電源的輸出電壓;其他通道連接其他地方,可以測其他內部元件的輸出電壓。通過ADC,可以將電機、各種傳感器等的模擬量轉換為單片機處理的數字量。
(4)程序思路:從程序角度阻塞式先開啟ADC,然后判斷是否轉化完成,最后讀取通道中ADC的值;非阻塞式的話開啟ADC,然后去中斷函數里面讀取ADC的值。
非中斷: HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc)?開啟ADC的采樣,如果是adc1就輸入&hadc1,adc2就輸入&adc2,adc3就輸入&hadc3 HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout)?等待 ADC轉換結束 uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc)?獲取ADC值中斷: HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc) 開啟ADC的采樣 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) ADC的中斷回調函數,我們在此之中寫代碼2 代碼:
過幾天自己寫一個八 串口接收與發送
1 原理:
串口通訊,收發雙方要遵從同樣的協議才能完成數據傳輸,同時波特率也要相等,波特率可以設置為115200,38400,9600 等。
//使能接收中斷和空閑中斷 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //receive interrupt __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //idle interrupt __HAL_UART_ENABLE_IT(&huart6, UART_IT_RXNE); //receive interrupt __HAL_UART_ENABLE_IT(&huart6, UART_IT_IDLE); //idle interrupt發送函數 HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); //如串口1就輸入&huart1;發送的數據的首地址,比如要發送buf[]=”Helloword”則輸入buf;要發送的數據大小,可以用sizeof;等待的時間接收函數 HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);發送中斷函數 HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 接收中斷函數 HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);串口接收中斷即每當串口完成一次接收之后觸發一次中斷;串口空閑中斷即每當串口接收完一幀數據后又過了一個字節的時間沒有接收到任何數據則觸發一次中斷。
當串口發生接收中斷或者空閑中斷時,都會進入 USARTx_IRQHandler 中斷處理函數。在中 斷處理函數中通過串口的狀態寄存器來判斷產生中斷的是接收中斷還是空閑中斷,然后進入相應的回調函數處理。
即在HAL中有中斷服務函數void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
這個庫函數幫我們完成了中斷類型判斷和清除標志位,我們只需要在具體的函數中寫邏輯即可。上面這個庫函數判斷出不同的類型,然后調用不同的回調函數,我們處理接收中斷回調函數HAL_UART_TxCpltCallback即可。?
回調函數如下: 1 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); 2 void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); 3 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); 4 void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); 5 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart); 6 void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart); 7 void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart); 8 void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart);2 代碼:
九 遙控器&串口的DMA模式
1 原理:
DMA:DMA 是在使用串口進行通訊時常用的一個功能,使用該功能能夠完成串口和內存之間直接的數據傳送,而不需要 CPU 進行控制,從而節約 CPU 的處理時間。
緩沖區的作用是:數據通過DMA串口傳進來以后我們要先在緩沖區中保存,然后進行一系列操作處理判斷檢驗接收到的數據是否為我們需要的那個數據(判斷對不對,通過比較位數是否相等的方式),若正確再放在內存中。緩沖區是DMA模式下內存中的一片區域,我們理解為數據進入內存存儲前的一片區域即可。
注意:串口DMA的初始化代碼是要自己寫的,可以從例程中復制。
代碼思路:RC_init(內含USART3的DMA的初始化:使能DMA串口接收-使能空閑中斷(空閑中斷要信號觸發后過一個字節的時間才能空閑中斷標志位置1,所以時間足夠底下我們的初始化程序全部完成以后才會進中斷服務函數對應的空閑中斷觸發部分)-失效DMA-配置緩沖區(緩沖區的位置在哪,能容納的數據長度多少)-使能緩沖區-使能DMA)
遙控器寄存器的值是從-660到660(在代碼里已經減去相應的量了)。
右遙控器前后是通道1,前正后負;左右是通道0,左負右正
左遙控器左右是通道3,前正后負;左遙控器左右是通道2,左負右正
左邊開關上中下對應s1 1,3,2
右邊開關上中下對應s0 1,3,2
?串口1的DMA用于發送數據,即單片機向電腦發送數據(顯示一些信息);串口3的DMA用于接收數據,即遙控器外設發送數據單片機的存儲區域接收。
??2 代碼:?
const RC_ctrl_t *local_rc_ctrl;//local_rc_ctrl是RC_ctrl_t變量類型的指針結構體變量名 remote_control_init();//紅外遙控的初始化,內含接收串口3的DMA初始化 usart1_tx_dma_init();//發送串口1的DMA初始化 //以上這些初始化代碼都要自己寫,紅外遙控初始化拷貝相應文件,發送串口1初始化見后。 local_rc_ctrl = get_remote_control_point();//獲取紅外遙控器的指針,它就可以指向紅外遙控器的各種寄存器。 //local_rc_ctrl->rc.ch[0], local_rc_ctrl->rc.ch[1], local_rc_ctrl->rc.ch[2]等//發送串口1初始化 void usart1_tx_dma_init(void) {//enable the DMA transfer for the receiver request//使能DMA串口接收SET_BIT(huart1.Instance->CR3, USART_CR3_DMAT); } void usart1_tx_dma_enable(uint8_t *data, uint16_t len) {//disable DMA//失效DMA__HAL_DMA_DISABLE(&hdma_usart1_tx);while(hdma_usart1_tx.Instance->CR & DMA_SxCR_EN){__HAL_DMA_DISABLE(&hdma_usart1_tx);}//clear flag//清除標志位__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_HISR_TCIF7);__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_HISR_HTIF7);//set data address//設置數據地址hdma_usart1_tx.Instance->M0AR = (uint32_t)(data);//set data length//設置數據長度hdma_usart1_tx.Instance->NDTR = len;//enable DMA//使能DMA__HAL_DMA_ENABLE(&hdma_usart1_tx); }關于通信協議:
主從通信:主機輪(流)詢(問),從機應答。發送指令給從機,讓它執行什么操作;向從機要數據。
每一次通信都必須由主機發起,從機不可以主動向主機發送數據。系統上電以后所有的設備都處于接收狀態,所以應當先將主機調成發送模式,發送數據包;?主機轉成接收模式,接收從機發送的應答;
地址碼就是從機的地址,我們需要和哪一臺設備建立聯系(即ID)
功能碼就是我們需要對從機執行什么操作(讀/寫,單個位/多個寄存器等等)
數據碼就是我們往寄存器/位里面塞這樣一個數,它本身就代表了某種信息(控制指令)
?
?
?
十 flash讀寫
個人計算機都均有內存和硬盤(外存),開發板芯片 stm32 同樣具有內存 192Kbytes 的 SRAM 和 1Mbytes 的外存 flash。flash是閃存外設,可以把數據寫進去存儲,也可以從里面讀取數據。
flash的優點是容量較大,掉電不會丟失數據;缺點是寫入需要先擦除,讀取寫入速度較慢。
注意:1. 為了保護數據的安全性,flash 有專門的鎖寄存器,每次要對 flash 頁面進行修改時首先 要通過鎖寄存器對頁面進行解鎖,修改完成后要進行加鎖。2. flash 是不支持在保存原有數據的情況下進行修改的,因此要改變 flash 頁面數據時,需 要對這個頁面進行擦除,擦除之后再寫入新的數據。
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError //flash擦除函數,擦除指定的flash頁面。 //參數:擦除 flash 時使用的結構體指針,需要創建一個 FLASH_EraseInitTypeDef 類型的結構體 flash_erase, 需要賦予這個結構體以下參數:Sector(要擦除的頁面的首地址),TypeErase(擦除方式),VoltageRange(電壓范圍),NbSectors(待擦除頁面數),最后我們將&flash_erase 作為參數輸入函數; 如果本次 flash 擦除產生了錯誤,則發生擦除錯誤的頁面號存儲在 SectorError 中HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data) //flash寫入函數,以指定的方式,向 flash 中的一頁寫入數據 參數:uint32_t TypeProgram 選擇寫入的數據格式,可以選擇8位字節FLASH_TYPEPROGRAM_BYTE , 16 位半字FLASH_TYPEPROGRAM_HALFWORD , 32 位 字FLASH_TYPEPROGRAM_WORD ,或者 64 位雙字FLASH_TYPEPROGRAM_DOUBLEWORD 需要寫入數據的地址;需要寫入的數據void flash_read(uint32_t address, uint32_t *buf, uint32_t len) {memcpy(buf, (void*)address, len *4); } //從 flash 讀取數據 //參數:Flash 地址;讀取后的存儲變量地址;字節長度 #define USER_FLASH_ADDRESS ADDR_FLASH_SECTOR_11 #define FLASH_DATA_LENGHT 12 //ADDR_FLASH_SECTOR_11即0x080E0000,是12個flash葉分區的第一個 uint8_t after_erase_data[FLASH_DATA_LENGHT]; uint8_t write_data[FLASH_DATA_LENGHT] = "RoboMaster\r\n"; uint8_t after_write_data[FLASH_DATA_LENGHT];//擦除flash頁 flash_erase_address(USER_FLASH_ADDRESS, 1); //read data from flash, before writing data //在寫數據之前,從flash讀取數據 flash_read(USER_FLASH_ADDRESS, (uint32_t *)after_erase_data, (FLASH_DATA_LENGHT + 3) / 4); //write data to flash //往flash寫數據 flash_write_single_address(USER_FLASH_ADDRESS, (uint32_t *)write_data, (FLASH_DATA_LENGHT + 3) / 4); //read data from flash, after writing data //在寫數據之后,從flash讀取數據 flash_read(USER_FLASH_ADDRESS, (uint32_t *)after_write_data, (FLASH_DATA_LENGHT + 3) / 4);十一 I2C?
串行同步通訊總線協議--I2C,使用 I2C可以配置和讀取 IST8310 磁力計的數據,還可以用于溫度傳感器,氣壓傳感器,多路 ADC 模塊等多種傳感器。
IST8310 磁力計:測量地球磁場強度,用于計算機器人的朝向。
SCL、SDA分別為I2C的時鐘線和數據線,RSTN為IST8310 的 RESET,低電平重啟 IST8310,DRDY為IST8310 的數據準備(data ready)。
程序開始先進行 HAL 庫自帶的初始化,包括時鐘,GPIO,I2C3 的初始化;之后完成配置 IST8310,IST8310 的 DRDY 引腳會產生 200Hz 的周期信號;當 DRDY 下降沿,會引起單 片機的下降沿外部中斷;在外部中斷回調函數中,調用 ist8310 的讀取函數,便可以讀取磁 場數據。
I2C通信協議
I2C 有兩根信號線,一根數據線 SDA,另一根是時鐘線 SCL。I2C 總線允許掛載多個主設備,但總線時鐘同一時刻只能由一個主設備產生,并且要求每個 連接到總線上的器件都有唯一的 I2C 地址,從設備可以被主設備尋址。
整個過程便是如同到學校的快遞柜(從機 I2C 地址),對第幾號柜箱(寄存器地址), 進行寄出或者簽收快遞(數據)的過程。具體時序略。?
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) //從 I2C 設備的寄存器讀取數據 //參數: I2C句柄;I2C從機地址;寄存器地址;寄存器地址增加大小-I2C_MEMADD_SIZE_8BIT:增加八位,I2C_MEMADD_SIZE_16BIT:增加十六位;數據指針;數據長度;超時時間HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) //往 I2C 設備的寄存器寫入數據?十二 OLED
發送 I2C 地址后,發送數據類型再發送數據,其中 數據類型為 0x00 表示控制指令,0x40 表示數據指令。
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) //向某個 I2C 設備傳輸數據 //參數:I2C 句柄;I2C 從機地址;數據指針;數據長度;超時時間根據 OLED 的通信方式,發送從機地址后需要在第一個字節中指明之后的數據類型,如果是控制指令 則需要發送 0x00,如果是數據指令則需要發送 0x40。函數原型如下:/*** @brief 寫數據或者指令到 OLED, 如果使用的是 SPI,請重寫這個函數* @param[in] dat: 要寫入的字節* @param[in] cmd: OLED_CMD 代表寫入的字節是指令; OLED_DATA 代表寫入的字節是數據* @retval none*/ oid oled_write_byte(uint8_t dat, uint8_t cmd) {static uint8_t cmd_data[2];if(cmd == OLED_CMD){cmd_data[0] = 0x00;}else{cmd_data[0] = 0x40;}cmd_data[1] = dat;HAL_I2C_Master_Transmit(&hi2c2, OLED_I2C_ADDRESS, cmd_data, 2, 10); }OLED_init 函數,該函數主要配置 OLED 參數,通過調用 oled_write_byte 傳入 OLED_CMD,傳輸控制指令完成配置OLED_display_on和OLED_display_off 函數分別是用來關閉OLED 顯示和開啟OLED 顯示OLED_draw_point 對(x,y)坐標的一個像素點進操作 OLED_draw_line 從(x1,y1)到(x2,y2)的直線經過的像素點進行操作 OLED_show_char 顯示一個字符 OLED_show_string 顯示一個字符串 OLED_printf Printf 函數功能 OLED_LOGO 顯示 Robomaster LOGOOLED_operate_gram(pen_typedef pen) 函數是操作 OLED_GRAM[128][8]數組的,我們對整個數組進行 操作,操作完成后再通過 OLED_refresh_gram 函數整體刷新 OLED 內部的 GRAM。作用是打開OLED顯示. 參數:PEN_WRITE 是將數組都設置為 0xff,對于 OLED 屏幕即為全亮;PEN_CLEAR 是將數組都設置為 0x00,對于 OLED 屏幕即為全滅;PEN_INVERSION 是將數組的值全部反轉,通過與 0xff 相減來實現。OLED_refresh_gram 函數功能是將內部的GRAM[8][128]傳輸到OLED模塊的GRAM, 這樣 OLED 就會顯示圖像。1. OLED 模塊的初始化,調用 OLED_init
2. 通過畫圖函數,對 stm32 內的 GRAM 數組進行操作
3. 調用 OLED_refresh_gram 函數將 GRAM 數據傳輸到 OLED 模塊的 GRAM 進行顯示。
其中2、3步都是OLED_LOGO 函數完成的,內集成畫圖函數將 GRAM 刷新成 LOGO 的數據,以及最后調用了 OLED_refresh_gram 函數顯示。
十三 SPI?BMI088 傳感器
BMI088 是一種高性能慣性測量單元 (IMU),集成了 16 位 ADC 精度的三軸?螺儀和三軸加速度計。
陀螺儀能測量在三個正交方向上旋轉的角速度,也可以用于估算在三個方向上的旋轉角度,原理是將旋轉的角速度轉化為電容的變化即轉變為電信號。
加速度計能夠測量三個正交方向上的加速度,原理是加速度改變力改變電容改變轉化為電信號。
?我們實際上是對BMI088中的各種寄存器進行操作。
oid BMI088_read(fp32 gyro[3], fp32 accel[3], fp32 *temperate) BMI088_read(gyro, accel, &temp) //讀取角速度、加速度、溫度值SPI通信協議:
SPI 的通信過程如下:
1. 主設備將要進行通訊的從設備的 SS/CS 片選拉低,
2. 主設備通過 SCK 向從設備提供同步通訊所需要的時鐘信號?
3. 主設備通過 MOSI 向從設備發送 8 位數據,同時通過 MISO 接收從設備發來的 8 位數 據。
4. 通信結束,主設備拉高 SS/CS 片選。
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) //通過 SPI 進行主機和從機的通信 //參數1:SPI_HandleTypeDef *hspi 即 SPI 的句柄指針,如果是 SPI1 就輸入&hspi1,SPI2 就輸入&hspi2 參數 2 uint8_t *pTxData 待發送數據的首地址指針 參數 3 uint8_t *pRxData 接收數據的區域的首地址 參數 4 uint16_t Size 待發送的數據長度 參數 5 uint32_t Timeout 最大發送時長十四?can總線
電機?
can發送:
?具體在CAN_cmd_chassis 函 數 和 CAN_cmd_gimbal 函數中,先將ID、數據位等信息賦值到我們定義的結構體變量中,再在內部調用基于HAL 庫的CAN 發送函數 HAL_CAN_AddTXMessag將這些結構體變量的值發送到內部寄存器,就獲取了主機要發送給電機的信息。時序啊這些都是HAL庫內部的東西,已經封裝好了。
CAN_cmd_chassis(int16_t motor1, int16_t motor2, int16_t motor3, int16_t motor4) //CAN_cmd_chassis 函數的輸入為電機 1 到電機 4 的驅動電流期望值 motor1 到 motor4, 函數會將期望值拆分成高八位和第八位,放入 8Byte 的 CAN 的數據域中,然后添加 ID (CAN_CHASSIS_ALL_ID 0x200),幀格式,數據長度等信息,形成一個完整的 CAN 數據幀,發送給各個電調。CAN_cmd_gimbal(int16_t yaw, int16_t pitch, int16_t shoot, int16_t rev) //CAN_cmd_gimbal 函數的的功能為向云臺電機和發射機構電機發送控制信號, 輸入參數為 yaw 軸電機,pitch 軸電機,發射機構電機的驅動電流期望值 yaw,pitch,shoot(rev 為保留值), 函數會將期望值拆分成高八位和第八位,放入 8Byte 的 CAN 的數據域中,然后添 加 ID(CAN_GIMBAL_ALL_ID 0x1FF),幀格式,數據長度等信息,形成一個完整的 CAN 數據幀,發送給各個電調。//HAL 庫提供了實現 CAN 發送的函數 HAL_CAN_AddTXMessag。CAN_cmd_chassis 函數和AN_cmd_gimbal 函數內部封裝了發送函數。 HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox) //將一段數據通過 CAN 總線發送 參數:&hcan1;待發送的 CAN 數據幀信息的結構體指針,包含了 CAN 的 ID,格式等重要信息; 裝載了待發送的數據的數組名稱;用于存儲 CAN 發送所使用的郵箱號?can接收:
?CAN 的接收中斷函數HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)每當 CAN 完成一幀數據的接收時,就會觸發一次 CAN 接收中斷處理函數,接收中斷函數完成一些寄存器的處理之后會調用 CAN 接收中斷回調函數。在中斷回調函數中首先判斷接收對象的 ID,是否是需要的接收的電調發來的數據。完成判斷之后,進行解碼,將對應的電機的數據裝入電機信息數組 motor_chassiss各個對應的位中。接收中斷函數里接收時調用了 HAL 庫提供的接收函數 HAL_CAN_GetRxMessage。
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]) 接收 CAN 總線上發送來的數據 參數:&hcan1;接收時使用的 CAN 接收 FIFO 號,一般為 CAN_RX_FIFO0; 存儲接收到的 CAN 數據幀信息的結構體指針,包含了 CAN 的 ID,格式等重要信息; 存儲接收到的數據的數組名稱?can總線協議:主機發送信息給can總線上掛的設備(從機),每一個從機有一個對應的ID(地址),每當一個設備發送一幀數據時,總線其他設備會檢查這 個 ID 是否是自己需要接收數據的對象,如果是則接收本幀數據,如果不是則忽略。若判斷可以接收以后,則發送控制位,規定了本幀數據的長度;再數據位,即 8 個 8 位數據,CAN總線的一個數據幀中所要傳輸的有效數 據實際上就是這 8Byte......
?數據位:如果要發送數據給 1 號到 4 號電調,控制電機的輸出電流,從而控制電機轉速時,則置標識符(ID)為0x200,則8位數據位分別是4個電調的電流值高八位低八位(4x(1+1)),幀格式和 DLC 也按手冊規定置,完成數據發送。
而當要接收電調發送來的數據時,首先根據接收到的 ID 判斷究竟接收到的是哪個電調發送來的數據(0x200+電調ID,如電調1則為0x201),再對電調發送來的數據進行解碼(數據位的8位分別代表:轉子機械角度高低八位,轉子轉速高低八位,實際轉矩電流高低八位,電機溫度,null)獲取相關信息。
變量聲明應寫在.c文件里,結構體變量寫在.h文件里,.h文件里想用.c聲明的變量得加extern
如.h文件里:
typedef struct
{
? ? uint16_t ecd;
? ? int16_t speed_rpm;
? ? int16_t given_current;
? ? uint8_t temperate;
? ? int16_t last_ecd;
} motor_measure_t;//電機數據結構體
.c文件里:
motor_measure_t motor_chassis[7];
那么.h文件里:
extern motor_measure_t motor_chassis[7];
舵機
開啟定時器 開啟定時器的pwm模式 獲取遙控器數據指針 設置舵機對應引腳的初電平值(剛開始轉過的角度) 當遙控器對應的寄存器值為多少時設置不同的電平,即舵機轉過的角度。PID
//位置式pid float pidUpdate(PidObject* pid, const float error) {float output;pid->error = error;if(abs(pid->error)<pid->Limit){pid->integ += pid->error; //誤差累加,積分項,iif (pid->integ > pid->iLimit){pid->integ = pid->iLimit;//限幅}else if (pid->integ < pid->iLimitLow){pid->integ = pid->iLimitLow;//限幅}}pid->deriv = pid->error - pid->prevError;//此次誤差減去上次誤差,微分項,dpid->outP = pid->kp * pid->error;pid->outI = pid->ki * pid->integ;pid->outD = pid->kd * pid->deriv;output = pid->outP + pid->outI + pid->outD;pid->prevError = pid->error;return output; }void chassis_pid(void) {motor.Set_motor_speed[0] = 200;//(rc_ctrl.rc.ch[3]+rc_ctrl.rc.ch[2]+rc_ctrl.rc.ch[0])*3*rc_ctrl.rc.s[0]-spin*98; motor.Actual_motor_speed[0] = motor_chassis[0].speed_rpm; motor.Out_motor_speed[0] = pidUpdate(&motor.pid_motor_speed[0] , motor.Set_motor_speed[0] - motor.Actual_motor_speed[0]);motor.Out_motor_speed[0] = limit_ab(motor.Out_motor_speed[0],-20000,20000);CAN_cmd_chassis(motor.Out_motor_speed[0],motor.Out_motor_speed[1],motor.Out_motor_speed[2],motor.Out_motor_speed[3]); }主函數中 : init_pid_all();//pid初始化 while(1) {chassis_pid();//不斷更新pid并給電機發送數據HAL_Delay(10); } //pid結構體 typedef struct {float error;float integ;float iLimit;float iLimitLow;float Limit;float deriv;float outP;float outI;float outD;float kp;float ki;float kd;float prevError; } PidObject;//電機結構體 typedef struct {s16 Out_Car_turn;s16 Set_motor_speed[8];s16 Actual_motor_speed[8]; s16 Out_motor_speed[8];s16 Out_Claw_Round[2];s16 Actual_Claw_round[2];PidObject pid_motor_speed[8];//電機轉速的結構體變量PidObject pid_car_turn;PidObject pid_claw_round[2];PidObject pid_x_speed;PidObject pid_z_speed;PidObject pid_motor[4];} Motor;十六. IMU 溫度控制
使用 PID 控制算法對 IMU 進行溫度控制
零漂現象是指當物理量輸入為零,傳感器測量的輸出量不為零的現象。即 IMU 沒有任何運動,陀螺儀和加速度計(這倆都是傳感器)也會讀取到一定大小的數據,并將其當作是由 IMU 運動產生的。
PID:𝑈(𝑡) = 𝐾𝑝 ? 𝑒𝑟𝑟(𝑡) + 𝐾𝑖 ? ∫ 𝑒𝑟𝑟(𝑡)𝑑𝑡 + 𝐾𝑑 ? 𝑑𝑒𝑟𝑟(𝑡)/?𝑑𝑡
err(t)即誤差值,Kp,Ki,Kd 分別為比例,積分,微分三項的系數。
單片機系統是離散的,所以位置式pid公式:𝑢(𝑘) = 𝐾𝑝 ? 𝑒(𝑘) + 𝐾𝑖 ? ∑(i=0到k)𝑒(𝑖) ?+ 𝐾𝑑 ? [𝑒(𝑘) ? 𝑒(𝑘 ? 1)];
增量式pid公式:𝛥𝑢(𝑘) = 𝐾𝑝 ? [𝑒(𝑘) ? 𝑒(𝑘 ? 1)] + 𝐾𝑖 ? 𝑒(𝑘) + 𝐾𝑑 ? [𝑒(𝑘) ? 2 ? 𝑒(𝑘 ? 1) + 𝑒(𝑘 ? 2)]
實際上也可以理解為我們在t0、t1、t2...每隔相等的時間(如1s)進行一次采樣,計算這一次輸出量的值和上一次的值作為誤差。
pid算法中,以比例環節為例,控制器=k*eu,而非最終得到的輸出量=k*eu。例如我們給水壺加熱,那么最終需要的量是溫度,但是pid控制的是加熱功率:如我們要加熱到80度,現在75度,則eu=5,加熱功率就是5k,然后溫度上升;當加熱功率小于散熱的時候溫度下降,通過這種改變加熱功率的方式才是pid調節。
以我們的車子追蹤一個會動的物體為例:error為車子和物體之間的距離。比例算法中,由于物體在動,距離增大,那么error增大,控制車子速度的量增大,某一瞬間to車速等于物體速度,設此時距離為x0;此后車子和物體速度相同,error不變,那么它們將會永遠間隔這樣一個距離,稱為穩態誤差。
引入積分算法后,控制車子速度的量不僅是error乘上比例系數k,還有error對時間的積分(乘以積分系數)。t0時刻車速等于物體速度,由于時間永遠在增加,所以積分項一直增加,車子速度肯定會增加,那么車子將繼續追趕物體;此后車子與物體的距離減小,那么比例項的作用減弱,而積分項的作用一直增加,直到某一刻它們的作用扯平使得車子速度又等于物體速度(所以車子速度在第一次相等后經歷了增加再減小的過程);然后發現由于時間增加積分項又增加,比例項又減小,無線逼近。
若振蕩(車子超過物體了),采用微分控制。控制變化曲線的斜率。
十七 底盤控制任務
主文件: can_filter_init(); init_pid_all();//pid的ki、kp、kd等參數初始化 remote_control_init(); RC_ctrl_t * local_rc_ctrl = get_remote_control_point(); while(1) {chassis_pid();//pid參數更新,并將更新的數據(電流值、轉速等)發送HAL_Delay(10); }pid.c文件: float pidUpdate(PidObject* pid, const float error) //pid參數更新函數,輸入變量為pid結構體和error {error賦值到pid結構體內部的error中去誤差累加得到積分項對積分項進行限幅德塔誤差=上次誤差-這次誤差,得到微分項pid->outP = pid->kp * pid->error;pid->outI = pid->ki * pid->integ;pid->outD = pid->kd * pid->deriv;output = pid->outP + pid->outI + pid->outD;//更新pid參數并返回輸出值上次誤差=這次誤差返回輸出值 } void chassis_pid(void) //調用更新的pid參數輸出值,并發送到電機 {vx=local_rc_ctrl->rc.ch[0]/6.6*50;//車速與遙控器成正比vy=-local_rc_ctrl->rc.ch[1]/6.6*50;wz=-local_rc_ctrl->rc.ch[2]/6.6*50;v1=-vx-vy-wz;v2=vx-vy+wz;v3=vx+vy-wz;v4=-vx+vy+wz;/* 底盤四個電機的PID計算并輸出 *///電機1:motor.Set_motor_speed[0] = v1; motor.Actual_motor_speed[0] = motor_chassis[0].speed_rpm; motor.Out_motor_speed[0] = pidUpdate(&motor.pid_motor_speed[0] , motor.Set_motor_speed[0] - motor.Actual_motor_speed[0]);//更新后的電流值賦值給motor.Out_motor_speed變量,輸入是轉速,輸出motor.Out_motor_speed變量是電流值。motor.Out_motor_speed[0] = limit_ab(motor.Out_motor_speed[num],-10000,10000); //對電流值進行限幅CAN_cmd_chassis(motor.Out_motor_speed[0],motor.Out_motor_speed[1],motor.Out_motor_speed[2],motor.Out_motor_speed[3]); //將得到的電流值發送給各個電機 }總結
以上是生活随笔為你收集整理的stm32f407igh6学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: autojs批量删除好友源码,安卓免ro
- 下一篇: Windows下搭建Tomcat集群的配