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使用中斷模式為佳,總結下整個開發就包含下面流程:
-
- 下位機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