玩轉(zhuǎn)stm32 usart 串口功能 庫函數(shù) 詳解和DMA 串口高級運用
數(shù)據(jù)傳輸時要從支持那些相關(guān)的標(biāo)準(zhǔn)?傳輸?shù)乃俣龋渴裁磿r候開始?什么時候結(jié)束?傳輸?shù)膬?nèi)容?怎樣防止通信出錯?數(shù)據(jù)量大的時候怎么弄?硬件怎么連接出發(fā),當(dāng)然對于stm32還要熟悉庫函數(shù)的功能
本文引用地址:http://m.butianyuan.cn/article/201611/320648.htm具起來rs232和485電平的區(qū)別硬件外圍芯片,波特率(反映傳一位的時間),起始位和停止位,數(shù)據(jù)寬度,校驗,硬件流控制,相應(yīng)連接電腦時的接口怎么樣的。配置,使用函數(shù),中斷,查詢并結(jié)合通信協(xié)議才算了解了串口使用。
以上是基礎(chǔ),當(dāng)然stm很多相關(guān)復(fù)用功能,支持同步單向通信和半雙工單線通信,支持局部互聯(lián)網(wǎng)、智能卡協(xié)議和紅外數(shù)據(jù)組織相關(guān)規(guī)范,以及調(diào)制解調(diào)器操作,運行多處理器通信。同時可以使用DMA方式進(jìn)行高速數(shù)據(jù)通信。注意Print函數(shù)時間問題,嘗試通過DMA解決。
特點:全雙工,異步,分?jǐn)?shù)波特率發(fā)生器好處是速度快,位數(shù)8或9為,可配置1或2停止位,Lin協(xié)議,可提供同步時鐘功能。
硬件上
一般2個腳,RX和TX;同步模式需要SCLK時鐘腳,紅外IRDA需要irDA_RDI腳作為數(shù)據(jù)輸入和irDA_TDO輸出。
奇偶校驗通過usart_cr1
LIN局域互聯(lián)網(wǎng)模式:通過設(shè)置USART_CR2中LINEN位配置,使用的時候需要外加專門的收發(fā)器才可以
同步模式:通過設(shè)置USART_CR2中CLKEN位配置
智能卡模式:
DMA、硬件流控制作專門研究。
中斷有哪些事件?
發(fā)送數(shù)據(jù)寄存器空
遺憾是沒有留有接受緩沖區(qū),用查詢?nèi)菀讛?shù)據(jù)丟失
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
流程是時鐘配置---管腳配置(如重映射,管腳模式)----串口配置----中斷配置-----相應(yīng)中斷-----打開串口
上面是一些基礎(chǔ)知識點,下面從實際運用來了解串口功能
比較簡單些的應(yīng)用吧:對usart進(jìn)行初始化的工作
void COM1_Init( void)
{
//首先要初始化結(jié)構(gòu)體:少不了對于的引腳,忘不了usart,更牽掛著中斷的配置結(jié)構(gòu)體,定義空間來被涂鴉
//下面是對GPIO進(jìn)行涂鴉
}
//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);下面是中斷源
#define USART_IT_PE
#define USART_IT_TXE
#define USART_IT_TC
#define USART_IT_RXNE
#define USART_IT_IDLE
#define USART_IT_LBD
#define USART_IT_CTS
#define USART_IT_ERR
#define USART_IT_ORE
#define USART_IT_NE
#define USART_IT_FE
以上是初始化配置,下面還要構(gòu)成最小的運用,就舉例輸出函數(shù)吧
void PrintUart1(const u8 *Str)
{
}
發(fā)送字符是通過查詢字符串的狀態(tài)來來不斷的發(fā)送下一個數(shù)據(jù)。
接受數(shù)據(jù)是通過中斷來實現(xiàn)的,把接受的數(shù)據(jù)放入緩沖區(qū),來實現(xiàn)。有包含協(xié)議以后細(xì)講。
想玩電腦串口傳輸數(shù)據(jù),通過printf()來直接在電腦窗口顯示是不是很爽?在usart.c函數(shù)中加入
//不使用半主機模式
#if 1 //如果沒有這段,則需要在target選項中選擇使用USE microLIB
#pragma import(__use_no_semihosting)
struct __FILE
{
};
FILE __stdout;
_sys_exit(int x)
{
}
#endif
int fputc(int ch, FILE *f)
{
}
上面數(shù)據(jù)來源可以通過串口,通過usb,通過無線等等。。。
printf函數(shù)有個缺陷,就是花費的時間太多了(毫秒級),總不至于讓CPU等幾個毫秒就來顯示串口吧,那再好的CPU也就費了,那腫么辦?可以用DMA!!直接讓其它硬件來傳這些粗糙的工作。CPU玩重點的其它的活!
先接著講好串口接受,再說這個DMA,在固件庫里面有個文件是專門用來放中斷處理函數(shù)的
里面有個函數(shù)
void USART1_IRQHandler(void)
{
..............下面就是一些處理,可以用狀態(tài)機模式來玩,直到填充好串口緩沖數(shù)據(jù),并校驗正確,不多說
}
上面是典型的中斷處理函數(shù),結(jié)合狀態(tài)機功能就強大了。講到操作系統(tǒng)還要進(jìn)一步的學(xué)會運用。
---------------------------------------------------------------------------------------------
通過上面的學(xué)習(xí)相信對基本的串口操作有了比較深入的理解了,前面提到printf太慢,這里需要一些改進(jìn)。
考慮到真正執(zhí)行串口輸出(用DMA其實在幾毫秒后完成)比執(zhí)行完pruntf(立即完成)這個時間點晚個幾毫秒對實際應(yīng)用來說沒有任何影響,因此CPU可以在嘀嗒中斷中指揮DMA模塊完成串口輸出的任務(wù)。(其實對系統(tǒng)還是有些影響的宏觀講,串口輸出的數(shù)據(jù)其實很少,在大河中放入一杯水,幾乎可以忽略不計的)
再解決怎么分配:
定義兩個全局緩存區(qū)
其中一個緩存區(qū)以循環(huán)隊列的形式組織,每次執(zhí)行fputc時向其隊尾加入一個元素。
另一個緩存區(qū)直接以數(shù)組的形式組織,作為DMA的源地址。
在嘀嗒中斷中按下列順序完成對DMA的操作
(1)判斷循環(huán)隊列是否為空,如果為空說明當(dāng)前沒有字符串需要通過串口輸出直接跳至(6)
(2)判斷DMA是否正在工作,如果DMA正在工作說明上次分配的任何沒干完直接跳至(6)
(3)從循環(huán)隊列出隊N個字符到數(shù)組緩存
(4)告訴DMA本次需傳輸?shù)淖止?jié)數(shù)N
(5)命令DMA開始傳輸
(6)結(jié)束操作
補充:
1.N的確定方法:若循環(huán)隊列中元素的個數(shù)大于或等于數(shù)組緩存區(qū)的長度(固定值),則將數(shù)組緩存區(qū)的
長度賦給N,若循環(huán)隊列中元素的個數(shù)小于數(shù)組緩存區(qū)的長度則將循環(huán)隊列元素的個數(shù)賦給N
2.循環(huán)隊列開辟得越大,能緩存的字符串就越多,因此是越大越好.
3.數(shù)組緩存區(qū)并不是開辟的越大越好,這個值可以做如下的計算得出,假設(shè)波特率為115200嘀嗒中斷
的周期為2毫秒則在這2毫秒時間內(nèi)理論上最多可傳115200*0.002/10=23個字節(jié)。因此把數(shù)組緩存區(qū)
的大小定到比23稍小一點即可,比如定為20.
代碼可正常運行。經(jīng)測試使用新方案printf一個包含了二十個字符的字符串只需要25微秒的CPU耗時,
而老方案則需要1.76毫秒的CPU耗時。從而可以放心的使用printf調(diào)試一些時序要求較高的函數(shù)了,
另外因為執(zhí)行時間段從而printf被重入的概率大大減小。如果需要徹底防止printf被重入的話,可在調(diào)用printf之前關(guān)中斷,在printf執(zhí)行完之后開中斷,代價也僅是可能發(fā)生的幾十微秒的中斷延時而已。
.........................................................................................
下面講一講我對DMA的理解
stm32
既然DMA傳輸?shù)氖菙?shù)據(jù),當(dāng)然有數(shù)據(jù)的寬度了,這需要配置。另外還要有地址吧,從哪個地址開始傳,還有傳到哪個地址,需要配置。還有傳輸普通的就直接傳完就好了,如果要高速不斷循環(huán)傳輸,這也可以配置。還有從ROM直接快速的加載到RAM(反過來不可以)也可以的。
之上就是一些基本需要配置的工作
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&((USART_TypeDef *)USART1)->DR);
上面是設(shè)置外設(shè)地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) USART_DMA_BUF;
上面這句很顯然是DMA要連接在Memory中變量的地址,上面設(shè)置存儲器地址;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
上面的這句是設(shè)置DMA的傳輸方向,就如前面我所說的,從存儲器到外設(shè),也可以從外設(shè)到存儲器,:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。
DMA_InitStructure.DMA_BufferSize = 0;
上面的這句是設(shè)置DMA在傳輸時緩沖區(qū)的長度,前面有定義過了buffer的起始地址:為了安全性和可靠性,一般需要給buffer定義一個儲存片區(qū),這個參數(shù)的單位有三種類型:Byte、HalfWord、word,我設(shè)置的2個half-word(見下面的設(shè)置);32位的MCU中1個half-word占16 bits。
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
上面的這句是設(shè)置DMA的外設(shè)遞增模式,如果DMA選用的通道(CHx)有多個外設(shè)連接,需要使用外設(shè)遞增模式:DMA_PeripheralInc_Enable;我的例子選用DMA_PeripheralInc_Disable
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
上面的這句是設(shè)置DMA的內(nèi)存遞增模式,DMA訪問多個內(nèi)存參數(shù)時,需要使用DMA_MemoryInc_Enable,當(dāng)DMA只訪問一個內(nèi)存參數(shù)時,可設(shè)置成:DMA_MemoryInc_Disable。
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
上面的這句是設(shè)置DMA在訪問時每次操作的數(shù)據(jù)長度。有三種數(shù)據(jù)長度類型,前面已經(jīng)講過了,這里不在敘述。
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
與上面雷同。在此不再說明。
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
上面的這句是設(shè)置DMA的傳輸模式:連續(xù)不斷的循環(huán)模式,若只想訪問一次后就不要訪問了(或按指令操作來反問,也就是想要它訪問的時候就訪問,不要它訪問的時候就停止),可以設(shè)置成通用模式:DMA_Mode_Normal
DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
上面的這句是設(shè)置DMA的優(yōu)先級別:可以分為4級:VeryHigh,High,Medium,Low.
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
上面的這句是設(shè)置DMA的2個memory中的變量互相訪問的
DMA_Init(DMA_Channel1,&DMA_InitStructure);
前面那些都是對DMA結(jié)構(gòu)體成員的設(shè)置,在次再統(tǒng)一對DMA整個模塊做一次初始化,使得DMA各成員與上面的參數(shù)一致。
DMA_Cmd(DMA_Channel1,ENABLE);
ok上面的配置工作完成了,相當(dāng)于設(shè)定了一根管道通過DMA把緩沖區(qū)中要發(fā)送的數(shù)據(jù)發(fā)送到串口中。當(dāng)然要使得DMA與usart1相連接,在usart1中還要把usart的DMA功能打開:
函數(shù)重載的
int fputc(int ch, FILE *f)
{
}
起始就是往環(huán)形緩沖區(qū)中添加要串口打印的數(shù)據(jù),這個動作是比較快的。因為cpu直接移動數(shù)據(jù)很快,而通過cpu來操作串口,等待收獲反映,有個while(..)是比較慢得,故有幾個毫秒的延時?,F(xiàn)在好了,cpu ,通過printf只是移動數(shù)據(jù)到了緩沖區(qū),緩沖區(qū)在一定的時候,cpu指揮dma來開始接下來的操作,然后dma操作,cpu接著做其他的事情,僅僅只要在下次空閑的時候來查一下dma有木有傳輸完成,或者有沒有傳輸錯誤,并改變環(huán)形隊列首位的位置,以及更改相應(yīng)的狀態(tài)就可以了。這樣可以大大的節(jié)省很多的時間哦。
環(huán)形隊列的結(jié)構(gòu),大家可以看一些算法,不是很難的。
下面是運行過程中,cpu操作查詢的函數(shù)。
void DMA_USART_Handler(void){
}
評論