1 前言
從踏入嵌入式行業到如今已通過去了4年多,參與開發過的產品很多,有交換機、光端機以及光纖收發器,停車場出入繳費系統,二維碼掃碼槍,智能指紋鎖以及數字IC芯片開發等; 涉及產品中中既有STM和Nuvoton這類通用芯片,也有Nordic-52832,Nordic-52810,易兆微這種專用的藍牙芯片,還包含用於WIFI設備的ESP32芯片,以及專業的指紋/二維碼安全芯片,固然也參與過基於ARM9內核的Linux的嵌入式服務器開發和維護,更詳細的參與了異步雙核MCU的驗證工做和庫開發,雖然它們內核和性能參數各異,甚至開發工具也大不相同,可是通過工做積累,就會發現這些MCU的開發都有比較清晰的流程,難度每每並不在自己的驅動調試開發部分。協議/安全/穩定性,圖像/GUI/視頻處理,性能/電源管理/低功耗,行業相關需求,這些知識在產品開發中纔是最重要的。算法
在有了C語言基礎,熟悉常見的開發工具如keil,Iar或者arm-gcc,瞭解芯片的基本I/O和寄存器配置後,底層模塊的驅動在整個產品開發流程中實際上是佔比最少的一部分,而RTOS選用/移植,任務管理/通信,複雜協議如(TCP/IP, USB, BLE)等的移植運用,功能邏輯實現,軟/硬件功能調試,以及後期功能測試纔是項目的主要部分,而這些每每是初期很難了解,也不知道如何去掌握的知識,只有從豐富的項目開發經歷中才能總結出來。在我入門的過程當中,也是把重點放在模塊的學習上,從GPIO,Uart,中斷等一步步開了解芯片的構造,可當我學習到DCMI,ETH,LTDC時,一方面模塊複雜,另外一方面須要配合大量的軟件部分實現,每每在沒有成果反饋的狀況下就失去了學習鑽研的興趣,最後不得不半途而廢;在入職嵌入式行業, 處理過多個產品項目後才逐漸有些明白,這些模塊的複雜程度即便是熟練工,也須要花費必定時間才能去熟練掌握,基於模塊的應用開發更可能須要按星期算的時間,在沒有詳細目的驅使的狀況下,若是把重點放在去掌握整個模塊的基本功能上,即枯燥也很困難(熟練掌握是重要的,但應用纔是產品開發的核心,先學會用,在長時間的運用後熟練也是學習的一種方法)。在這份文檔中將摒棄以模塊爲核心的初期積累方式,將以應用爲核心,產品開發的思路爲嚮導,先規劃產品需求,在由簡入難,講訴如何將想法化做嵌入式產品的過程,以及其中遇到的困難和解決辦法。安全
嵌入式內部也根據行業有不少方向,如通信行業,涉及有交換機,路由器,視頻光端機,要掌握各類通信協議,如TCP/IP, 環網協議等; 安全行業, 涉及有監控系統,支付掃碼槍,指紋鎖,要掌握視頻,圖像相關的處理知識,也要了解國密算法如SM2, SM3, HASH等,還有電源行業,涉及有充電器,適配器,就要了解BC1.2,高通QC快充以及PD協議等,其它行業沒參與過,不太瞭解,但深刻各行業以後有點很清楚,對行業的理解纔是決定本身發展的最大限制,在學習和提升的過程當中,能夠選擇更全面,但在工做中,必定要選擇本身最合適的方向深刻耕耘。服務器
1.1 資料準備
工欲善其事,必先利其器。從事嵌入式開發的學習,首先選擇合適的開發芯片和開發工具固然是十分重要的,若是已經有開發板或者芯片模組,那麼直接使用便可,沒有的話建議選擇意法半導體的STM系列芯片,緣由有如下幾點。網絡
-
- 產品量大,比起飛思卡爾/TI的芯片,網絡上使用的人更多,遇到問題網絡上也更容易找到解決問題的辦法。
- ST做爲比較早進入中國的公司,對於資料方面中文化更全面(嵌入式開發英文很重要,但中文更適合入門)。
- 國內單片機方面的開發板也是以ST的居多,如比較出名的正點原子,野火等,也更好選擇,新人購買也建議選擇這些開發板,功能應用齊全。
固然目前由於手裏只有一塊以前購買ST的STM32F7-Discovery,使用STMF746G芯片,所以就以此爲核心進行後續應用的開發和總結,另外由於我的熟悉程度和經常使用開發,選擇MDK5做爲開發工具。作好準備後,就開始第一個課題:利用串口點亮/關閉LED燈,具體要求以下:數據結構
-
- 上位機帶軟件界面,有兩個按鍵分別控制開燈/關燈
- 下位機可根據按鍵控制LED,有必定的擴展性(後續支持其它功能?)
資料/設備(本文檔所在的資料文件內有附加工具/文檔):架構
-
- 開發板STM32F7-Discovery
- USB轉串口工具
- 筆記本一臺
- USB供電線,用於打印
- 文檔若干(stm32f7-discovery原理圖, STM32F7x參考手冊(中文), STM32F7數據手冊)
準備好上述工具,就能夠開始需求的正式的功能開發了,首先要肯定開發需求涉及到的知識點,上位機軟件由於有窗口和串口,那麼使用C#/或者Python+PyGUI均可以,我的擅長C#,所以選擇C#寫上位機軟件; 至於下位機由於要採集串口數據,並控制LED,所以涉及到USART-輸入/GPIO兩個模塊,考慮到會用到打印調試,所以USART-printf也最好實現,另外考慮到實時性和架構的需求,USART使用中斷模式爲佳,總結下整個開發就包含下面流程:app
-
- 下位機GPIO,USART,中斷模塊的驅動實現
- 上位機軟件開發
- 上位機/下位機交互的規則,以及對實際硬件的操做
1.1 硬件驅動實現
不過由於初期能夠用串口工具模擬上位機軟件,所以首先進行驅動實現和交互協議規則實現,驅動的實如今有必定單片機基礎後並不困難,首先確認對應的硬件的實際接口,具體以下:異步
LED -- GPIOI1函數
USART1 TX -– PA9工具
USART6 TX –- PC6, RX – PC7
具體參考《stm32f7-discovery原理圖》,詳細以下
圖 1 硬件原理圖
肯定了硬件以後,就開始驅動的編寫,這裏能夠簡化總結下竅門,對於涉及硬件但不涉及複雜協議的接口,如USART,I2c或者SPI等,硬件接口的實現通常包含如下幾部分:
- 使能模塊對應RCC的時鐘(包含對應GPIO模塊和應用模塊)
- 配置對應的硬件GPIO口,單純GPIO這一步結束,接口則配置爲相應的複用模式
- 配置硬件模塊,使能
- 若是開啓中斷,須要配置相應的優先級,並實現中斷函數。
根據上述說明,LED的初始化函數以下(具體配置說明參考STM32F7x參考手冊)
void led_gpio_init(void) { //使能GPIOI的時鐘 RCC->AHB1ENR |= 1<<8; //配置引腳爲輸出 GPIOI->MODER &= ~(0x3<<2); GPIOI->MODER |= 0x1<<2; //配置爲推輓模式 GPIOI->OTYPER &= ~(0x1<<1); //默認無上下拉 GPIOI->PUPDR &= ~(0x3<<2); }
這裏有一個知識點,引腳推輓/開漏,其中推輓表示內部有上拉/下拉電阻,可以對外部供電,開漏則只能輸出0和高阻態 , 輸出高電平須要外部上拉電阻(適合驅動大電流外設),本例中由於沒有外部上拉電阻只能使用推輓模式。
USART的初始化函數分紅兩部分, USART相應引腳初始化
void usart_gpio_init(void) { //使能GPIOC, GPIOA時鐘 RCC->AHB1ENR |= ((1<<0)|(1<<2)); //PA9引腳配置 MODIFY_REG(GPIOA->MODER, 0x3<<18, 0x2<<18); //複用模式 MODIFY_REG(GPIOA->OTYPER, 0x1<<9, 0); //推輓輸出 MODIFY_REG(GPIOA->PUPDR, 0x3<<18, 0x1<<18); //默認上拉 MODIFY_REG(GPIOA->OSPEEDR, 0x3<<18, 0x3<<18); //高速模式 MODIFY_REG(GPIOA->AFR[1], 0xf<<4, 0x7<<4); //複用模式USART1 //PC6引腳配置(輸出) MODIFY_REG(GPIOC->MODER, 0x3<<12, 0x2<<12); //複用模式 MODIFY_REG(GPIOA->OTYPER, 0x1<<6, 0); //推輓輸出 MODIFY_REG(GPIOC->PUPDR, 0x3<<12, 0x1<<12); //默認上拉 MODIFY_REG(GPIOC->OSPEEDR, 0x3<<12, 0x3<<12); //高速模式 MODIFY_REG(GPIOC->AFR[0], 0xf<<24, 0x8<<24); //複用模式USART6
//PC7引腳配置(輸入) MODIFY_REG(GPIOC->MODER, 0x3<<14, 0x2<<14); //複用模式 MODIFY_REG(GPIOC->PUPDR, 0x3<<14, 0<<14); //無上拉下拉 MODIFY_REG(GPIOC->OSPEEDR, 0x3<<14, 0x3<<14); //高速模式 MODIFY_REG(GPIOC->AFR[0], (uint32_t)0xf<<28, (uint32_t)0x8<<28); //複用模式USART6 }
這裏須要注意的就是複用模式的選擇,須要參考STM32F7數據手冊第三章Table12 STM32F745xx and STM32F746xx alternate function mapping便可,例程參考以下圖
圖 2 引腳重定義信息
USART模塊初始化及中斷函數則以下:
void usart_module_init(void) { //使能USART1和USART6 RCC->APB2ENR |= ((1<<4)|(1<<5)); //USART1 //配置爲1個起始位, 8個數據位, 1箇中止位) //16倍過採樣, 禁止奇/偶校驗, 不使用CTS/RTS //只支持輸出模式 //波特率 9600 USART1->CR1 = 0; USART1->CR2 = 0; USART1->BRR = Get_SystemFrequency(CLOCK_APB2)/9600; //根據模塊時鐘得到採樣率 USART1->CR1 |= ((1<<3) | (1<<0)); //使能USART和USART發送 sendstring(USART1, "USART1 Start OK!\r\n", strlen("USART1 Start OK!\r\n"));
//USART6 //配置爲1個起始位, 9個數據位, 1箇中止位) //16倍過採樣, 支持偶校驗,不使用CTS/RTS //波特率 115200 //支持輸入輸出模式, 輸入使用中斷模式 USART6->CR1 = ((1<<12) | (1<<10) | (1<<5)); USART6->CR2 = 0; USART6->RQR |= (1<<3); //清除接收標誌位 USART6->BRR = Get_SystemFrequency(CLOCK_APB2)/115200; //根據模塊時鐘得到採樣率 USART6->CR1 |= ((1<<3) | (1<<2) | (1<<0)); //使能USART、USART發送、USART接收 sendstring(USART6, "USART6 Start OK!\r\n", strlen("USART6 Start OK!\r\n")); //配置中斷寄存器, 開啓中斷 SCB->AIRCR = (VECTKEYSTAT | NVIC_PriorityGroup_4); NVIC_SetPriority(USART6_IRQn, 0); NVIC_EnableIRQ(USART6_IRQn); }
USART6中斷函數和簡單測試程序則以下:
void USART6_IRQHandler(void) { char ch; //讀取數據, 同時清除標誌位 ch = USART6->RDR; printf("%c", ch); }
上述涉及的知識點有串口參數(數據位,起始位,中止位,奇偶校驗,時鐘頻率/波特率),串口中斷,中斷向量表,中斷函數,串口接收/發送,其實這些東西不理解照樣能夠寫出成功運行的程序,不過理解以後才能更加系統的完善提升自身,固然這裏就不在贅述,想詳細瞭解也能夠參考以前的學習筆記中關於USART部分及中斷的說明,由於這些並非功能開發的重點。完成了硬件驅動部分,在依靠簡單的測試程序,實現簡單的串口輸入輸出檢測, 如圖 3所示。
圖 3 USART輸入輸出測試
1.2 協議制定和實現
完成硬件部分處理,下面就能夠進行交互協議和驅動的實現了,事實上,若是僅點亮、關閉LED燈,設計上僅須要簡單的定義0爲熄滅,1爲點亮就能夠輕鬆實現,不過這只是從簡單實現上考慮,而不是從一個成熟應用的角度考慮,對於涉及多設備的通信,可靠性和可擴展性都是不可或缺的,那麼使用自定義的私有協議就是比較承認的方式,如今主流的通信方式是以指令命令行爲核心的字符串控制協議,如藍牙模塊,wifi模塊使用的AT指令,優勢是支持直接的命令行操做,另外一種則是以數據結構組合/解析爲核心的二進制通信協議,如常見的TCP/IP協議,USB協議等,這裏考慮到後續的擴展性,以及純C實現的難度(可擴展字符串解析在不使用正則的狀況下使用C語言很複雜),我決定採用第二種方式,通信分爲請求幀/應答幀主從機模式。
請求幀格式
幀頭(1byte) |
設備號 (1byte) |
幀類型 (1byte) |
數據長度 (2byte) |
數據 (0~1000byte) |
校驗 (2byte) |
0x5a |
0x00~0xFE 表示設備號 0xFF 表示該位無效 |
0x01 設備控制幀 0x02 參數修改幀 0x03 其它 |
0~1000 |
具體數據 |
CRC16校驗碼 |
應答幀格式
幀頭(1byte) |
設備號 (1byte) |
響應狀態/數據提交 (1byte) |
數據長度 (2byte) |
數據 (0~1000byte) |
校驗 (2byte) |
0x5b |
與本機設備一致 |
0x00 成功 0xff 消息提交 其它 失敗 |
0~1000 |
具體數據 |
CRC16校驗碼 |
其中幀頭/校驗用來保證數據的完整性,設備號用於保證當有多個下位機是可以正常的管理,幀類型主要考慮到後續功能的完善,如添加下載功能,可以進行區分,數據則就是直接對底層硬件的處理信息,這裏暫時定義LED設備爲00,UART設備01。對於點亮LED耗時可能很短,但若是實現將上位機下發數據經過串口打印,則須要比較多的時間,這裏咱們採用接收經過USART進行,處理則經過主程序完成(固然處理任務比較多時,使用RTOS也是建議的選擇), 下面開始上述功能參考
圖 2‑4 串口協議接收/處理簡單流程
完成上述流程代碼後,經過測試工具發送二進制數據5a 01 01 00 02 00 01 xx xx和5a 01 01 00 02 00 00 xx xx就能夠分別點亮和關閉LED,並返回相應的響應,至於上位機由於和嵌入式部分關係並不大,所以這裏不說明具體實現思路(其實和下位機很類似,不過須要些C#知識),僅提供代碼和實現程序,固然這裏還有些知識點並無詳細說,如CRC的軟件和硬件實現,結構體的對齊機制,串口接收的錯誤處理機制等,由於自己資料不少,這裏不在贅述,但願瞭解的可自行檢索,另外這裏大體協議處理中核心的函數的實現方式, 如串口中斷數據接收函數。
void USART6_IRQHandler(void) { char c; static uint16_t repos = 0; static uint16_t total_len = 0; //讀取數據, 同時清除標誌位 if((USART6->ISR & (1<<5)) != 0) { c = USART6->RDR; if(USART_STATUS.reflag == 0) { if(repos == 0) { if(c == 0x5a) //起始位, 保證接收到一幀的開始 { total_len = 0; memset(USART_RX_BUFF, 0, USART_BUFF_SIZE); USART_RX_BUFF[repos++] = c; } } else if(repos == 5) //在當前長度下,能夠得到攜帶數據的長度len { USART_RX_BUFF[repos++] = c; total_len = (USART_RX_BUFF[3]<<8)+USART_RX_BUFF[4]+7; } else if(repos == total_len-1 || repos>=USART_BUFF_SIZE) //接收到完整幀,可能溢出 { USART_RX_BUFF[repos++] = c; USART_STATUS.reflag = 1; USART_STATUS.relen = repos; repos = 0; } else USART_RX_BUFF[repos++] = c; } else repos = 0; } //printf("%c", ch); }
另外具體代碼較多,詳見對應工程usart.c和UsartProtocol.c文件。實現了協議並經過測試,下面就能夠配合上位機軟件實現遠程點亮LED,如圖所示
1.3 總結和知識點
仔細體驗下來就會發現對於協議處理部分佔用的工做遠遠超過對驅動的處理,實現包含協議的制定,流程規劃,協議實現,數據測試,最後在將結果反饋到實際的硬件模塊上, 如點亮/關閉LED燈,這裏可能就有個疑問了,爲何使用如此複雜的協議,而不直接使用00/01來管理硬件了,這裏實際上是十分重要的問題,也是入門時最難理解的問題,這裏我我的根據工做經驗來整理本身的見解。
- 產品的可靠性,串口雖然是穩定成熟的接口,可在干擾或者硬件不穩定的狀況下,仍然有出錯的風險,對於點亮/關閉LED來講,可能只是指示的錯誤,影響並不大,但若是這些數據是安全操做開關,閥門的開啓/關閉,控制門鎖等,沒有協議頭和校驗,出錯影響的就是生命財產的安全。
- 將來的可擴展性,這裏咱們只經過串口實現了關閉/打開LED燈,若是之後咱們想經過它支持多設備,修改調試/自己串口的波特率,獲取設備的版本信息,下載固件,下載圖片,若是沒有一套完整的約束,後續仍是會出現管理問題。
雖然本例是使用PC的串口與芯片通信,事實上嵌入式開發中常見的是芯片與芯片之間的通信,它們之間的通信接口也不限於串口,更多的有I2C、SPI、CAN或USB等,雖然有各自的特色,但這套協議對於全部的接口是共通的,因此學會制定和實現自定義協議是嵌入式中十分重要的能力,也能夠爲後續學習複雜協議如TCP/IP, USB或BLE等打下基礎。至此,涉及GPIO,USART,RCC,NVIC,CRC/HASH等模塊的USART遠程管理LED代碼的程序就完成了,雖然開發還算順利,但在實現過程當中,回顧整理所學,仍是讓我受益不淺,不枉花費如此時間。
本章中咱們瞭解了底層模塊驅動實現的方案,另外也初步知曉了雙機通信的相關知識,闡述了協議的重要性以及實現的方法和思路,這些在通常項目中已經屬於軟件核心的一部分,值得親自實踐。下面先來看我曾經參加過的項目,其結構如所示
圖 5 停車場管理系統結構簡圖
上述停車場管理系統結構中,和嵌入式開發相關的有:
- LED標識裝置 -- 用於顯示當前剩餘車輛,與基站通信保持實時刷新
- 超聲波採集設備 -- 低功耗設備,覆蓋全部停車位,負責檢測車輛存在,並經過射頻提交基站
- wifi/RF基站 -- 整合車位信息,更新LED顯示,提交服務器數據,並接收/處理/轉發服務器管理數據
雖然這些模塊的通信接口各異,有射頻RFID,RS485以及wifi,但這些設備之間通信都進行了十分嚴格的約束,有簡單的自定義協議,也有複雜的通用網絡協議TCP/IP,上述模塊是由多我的員開發了,協議不只僅做爲通信的約束,也保證了多過人員開發時的配合有效性,所以協議在大型的工程多機聯合項目中基本上是不可或缺的,掌握協議開發也是嵌入式工程師的基礎能力之一,值得深刻學習。
另外本章節涉及的部分細節知識點並無詳細闡述,若是想了解可根據下面知識點自行檢索。
- RCC,GPIO,USART,CRC,NVIC寄存器配置,硬件操做
- 串口參數:起始位,數據位,中止位,奇偶校驗
- 中斷向量表,中斷函數的實現
- 硬件/軟件CRC校驗
- 數據結構對齊機制
本項目開發相關資料和代碼實現見附件:
連接:https://pan.baidu.com/s/1pXkKG3y8ItXWqXLC4ODuaw
提取碼:kzol