STM32 串列埠功能詳解

weixin_33766168發表於2017-12-07

姓名:周崇傑   學號:16140120059    專業:機械設計製造及其自動化

轉載自:http://blog.csdn.net/andylfg/article/details/24393419,有刪節

【嵌牛導讀】:微控制器與計算機系統最關鍵的莫過於資訊與資料,微控制器最有用的通訊工具莫過於串列埠,本文將會對STM32的串列埠以及其庫函式做出詳解。

【嵌牛鼻子】:STM32微控制器,串列埠,庫函式

【嵌牛提問】:資料傳輸時要從支援那些相關的標準?傳輸的速度?什麼時候開始?什麼時候結束?傳輸的內容?怎樣防止通訊出錯?資料量大的時候怎麼弄?

【嵌牛正文】:

資料傳輸時要從支援那些相關的標準?傳輸的速度?什麼時候開始?什麼時候結束?傳輸的內容?怎樣防止通訊出錯?資料量大的時候怎麼弄?硬體怎麼連線出發,當然對於stm32還要熟悉庫函式的功能

具起來rs232和485電平的區別硬體外圍晶片,波特率(反映傳一位的時間),起始位和停止位,資料寬度,校驗,硬體流控制,相應連線電腦時的介面怎麼樣的。配置,使用函式,中斷,查詢並結合通訊協議才算瞭解了串列埠使用。

以上是基礎,當然stm很多相關複用功能,支援同步單向通訊和半雙工單線通訊,支援區域性網際網路、智慧卡協議和紅外資料組織相關規範,以及調變解調器操作,執行多處理器通訊。同時可以使用DMA方式進行高速資料通訊。注意Print函式時間問題,嘗試通過DMA解決。

特點:全雙工,非同步,分數波特率發生器好處是速度快,位數8或9為,可配置1或2停止位,Lin協議,可提供同步時鐘功能。

硬體上

一般2個腳,RX和TX;同步模式需要SCLK時鐘腳,紅外IRDA需要irDA_RDI腳作為資料輸入和irDA_TDO輸出。

奇偶校驗通過usart_cr1pce位配置校驗(傳送生成奇偶位,接受時進行校驗)

LIN局域網際網路模式:通過設定USART_CR2中LINEN位配置,使用的時候需要外加專門的收發器才可以

同步模式:通過設定USART_CR2中CLKEN位配置

智慧卡模式:通過設定USART_CR3中SCEN位配置

DMA、硬體流控制作專門研究。

中斷有哪些事件?

傳送資料暫存器空傳送完成接受資料可讀奇偶校驗錯資料溢位CTS標誌 空閒標誌 斷開標誌 噪聲標誌

遺憾是沒有留有接受緩衝區,用查詢容易資料丟失

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

流程是時鐘配置---管腳配置(如重對映,管腳模式)----串列埠配置----中斷配置-----相應中斷-----開啟串列埠

上面是一些基礎知識點,下面從實際運用來了解串列埠功能

比較簡單些的應用吧:對usart進行初始化的工作

void COM1_Init( void)

{

//首先要初始化結構體:少不了對於的引腳,忘不了usart,更牽掛著中斷的配置結構體,定義空間來被塗鴉

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

//下面是對GPIO進行塗鴉

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//選擇管腳位

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式複用推輓輸出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//選擇管腳位

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//模式為輸入

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = 115200;//波特率

USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8資料位

USART_InitStructure.USART_StopBits = USART_StopBits_1;//1停止位

USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無資料流控制

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收發模式

USART_Init(USART1, &USART_InitStructure);

//使能串列埠中斷,並設定優先順序

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = USART1_IRQn_Priority;

NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);//將結構體丟到配置函式,即寫入到對應暫存器中

//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

//所有的工作都做好了,最後別忘了開啟串列埠

USART_Cmd(USART1, ENABLE);

}

//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);下面是中斷源

#define USART_IT_PE((uint16_t)0x0028)

#define USART_IT_TXE((uint16_t)0x0727)

#define USART_IT_TC((uint16_t)0x0626)

#define USART_IT_RXNE((uint16_t)0x0525)

#define USART_IT_IDLE((uint16_t)0x0424)

#define USART_IT_LBD((uint16_t)0x0846)

#define USART_IT_CTS((uint16_t)0x096A)

#define USART_IT_ERR((uint16_t)0x0060)

#define USART_IT_ORE((uint16_t)0x0360)

#define USART_IT_NE((uint16_t)0x0260)

#define USART_IT_FE((uint16_t)0x0160)

--------------------------------------------------------------------------------------

以上是初始化配置,下面還要構成最小的運用,就舉例輸出函式吧

void PrintUart1(const u8 *Str)

{

while(*Str)

{

USART_SendData(USART1, (unsigned char)*Str++);

while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

}

}

傳送字元是通過查詢字串的狀態來來不斷的傳送下一個資料。

接受資料是通過中斷來實現的,把接受的資料放入緩衝區,來實現。有包含協議以後細講。

想玩電腦串列埠傳輸資料,通過printf()來直接在電腦視窗顯示是不是很爽?在usart.c函式中加入

//不使用半主機模式

#if 1 //如果沒有這段,則需要在target選項中選擇使用USE microLIB

#pragma import(__use_no_semihosting)

struct __FILE

{

int handle;

};

FILE __stdout;

_sys_exit(int x)

{

x = x;

}

#endif

int fputc(int ch, FILE *f)

{

USART_SendData(USART1, (unsigned char)ch);

while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

return ch;

}

上面資料來源可以通過串列埠,通過usb,通過無線等等。。。

printf函式有個缺陷,就是花費的時間太多了(毫秒級),總不至於讓CPU等幾個毫秒就來顯示串列埠吧,那再好的CPU也就費了,那腫麼辦?可以用DMA!!直接讓其它硬體來傳這些粗糙的工作。CPU玩重點的其它的活!

先接著講好串列埠接受,再說這個DMA,在韌體庫裡面有個檔案是專門用來放中斷處理函式的

裡面有個函式

void USART1_IRQHandler(void)

{

static u8 UartBuf[UART_BUF_LEN];//串列埠緩衝

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)

{

temp=USART_ReceiveData(USART1);

..............下面就是一些處理,可以用狀態機模式來玩,直到填充好串列埠緩衝資料,並校驗正確,不多說

}

}

上面是典型的中斷處理函式,結合狀態機功能就強大了。講到作業系統還要進一步的學會運用。

---------------------------------------------------------------------------------------------

通過上面的學習相信對基本的串列埠操作有了比較深入的理解了,前面提到printf太慢,這裡需要一些改進。

考慮到真正執行串列埠輸出(用DMA其實在幾毫秒後完成)比執行完pruntf(立即完成)這個時間點晚個幾毫秒對實際應用來說沒有任何影響,因此CPU可以在嘀嗒中斷中指揮DMA模組完成串列埠輸出的任務。(其實對系統還是有些影響的巨集觀講,串列埠輸出的資料其實很少,在大河中放入一杯水,幾乎可以忽略不計的)

再解決怎麼分配:

定義兩個全域性快取區

其中一個快取區以迴圈佇列的形式組織,每次執行fputc時向其隊尾加入一個元素。

另一個快取區直接以陣列的形式組織,作為DMA的源地址。

在嘀嗒中斷中按下列順序完成對DMA的操作

(1)判斷迴圈佇列是否為空,如果為空說明當前沒有字串需要通過串列埠輸出直接跳至(6)

(2)判斷DMA是否正在工作,如果DMA正在工作說明上次分配的任何沒幹完直接跳至(6)

(3)從迴圈佇列出隊N個字元到陣列快取

(4)告訴DMA本次需傳輸的位元組數N

(5)命令DMA開始傳輸

(6)結束操作

補充:

1.N的確定方法:若迴圈佇列中元素的個數大於或等於陣列快取區的長度(固定值),則將陣列快取區的

長度賦給N,若迴圈佇列中元素的個數小於陣列快取區的長度則將迴圈佇列元素的個數賦給N

2.迴圈佇列開闢得越大,能快取的字串就越多,因此是越大越好.

3.陣列快取區並不是開闢的越大越好,這個值可以做如下的計算得出,假設波特率為115200嘀嗒中斷

的週期為2毫秒則在這2毫秒時間內理論上最多可傳115200*0.002/10=23個位元組。因此把陣列快取區

的大小定到比23稍小一點即可,比如定為20.

程式碼可正常執行。經測試使用新方案printf一個包含了二十個字元的字串只需要25微秒的CPU耗時,

而老方案則需要1.76毫秒的CPU耗時。從而可以放心的使用printf除錯一些時序要求較高的函式了,

另外因為執行時間段從而printf被重入的概率大大減小。如果需要徹底防止printf被重入的話,可在呼叫printf之前關中斷,在printf執行完之後開中斷,代價也僅是可能發生的幾十微秒的中斷延時而已。

.........................................................................................

下面講一講我對DMA的理解

stm32DMA有8通道,0---7

既然DMA傳輸的是資料,當然有資料的寬度了,這需要配置。另外還要有地址吧,從哪個地址開始傳,還有傳到哪個地址,需要配置。還有傳輸普通的就直接傳完就好了,如果要高速不斷迴圈傳輸,這也可以配置。還有從ROM直接快速的載入到RAM(反過來不可以)也可以的。

之上就是一些基本需要配置的工作

DMA_DeInit(DMA1_Channel4);

上面這句是給DMA配置通道,根據ST提供的資料,STM3210Fx中DMA包含7個通道

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&((USART_TypeDef *)USART1)->DR);

上面是設定外設地址

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) USART_DMA_BUF;

上面這句很顯然是DMA要連線在Memory中變數的地址,上面設定儲存器地址;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

上面的這句是設定DMA的傳輸方向,就如前面我所說的,從儲存器到外設,也可以從外設到儲存器,:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

DMA_InitStructure.DMA_BufferSize = 0;

上面的這句是設定DMA在傳輸時緩衝區的長度,前面有定義過了buffer的起始地址:為了安全性和可靠性,一般需要給buffer定義一個儲存片區,這個引數的單位有三種型別:Byte、HalfWord、word,我設定的2個half-word(見下面的設定);32位的MCU中1個half-word佔16 bits。

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

上面的這句是設定DMA的外設遞增模式,如果DMA選用的通道(CHx)有多個外設連線,需要使用外設遞增模式:DMA_PeripheralInc_Enable;我的例子選用DMA_PeripheralInc_Disable

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

上面的這句是設定DMA的記憶體遞增模式,DMA訪問多個記憶體引數時,需要使用DMA_MemoryInc_Enable,當DMA只訪問一個記憶體引數時,可設定成:DMA_MemoryInc_Disable。

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

上面的這句是設定DMA在訪問時每次操作的資料長度。有三種資料長度型別,前面已經講過了,這裡不在敘述。

DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;

與上面雷同。在此不再說明。

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

上面的這句是設定DMA的傳輸模式:連續不斷的迴圈模式,若只想訪問一次後就不要訪問了(或按指令操作來反問,也就是想要它訪問的時候就訪問,不要它訪問的時候就停止),可以設定成通用模式:DMA_Mode_Normal

DMA_InitStructure.DMA_Priority = DMA_Priority_Low;

上面的這句是設定DMA的優先順序別:可以分為4級:VeryHigh,High,Medium,Low.

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

上面的這句是設定DMA的2個memory中的變數互相訪問的

DMA_Init(DMA_Channel1,&DMA_InitStructure);

前面那些都是對DMA結構體成員的設定,在次再統一對DMA整個模組做一次初始化,使得DMA各成員與上面的引數一致。

DMA_Cmd(DMA_Channel1,ENABLE);

ok上面的配置工作完成了,相當於設定了一根管道通過DMA把緩衝區中要傳送的資料傳送到串列埠中。當然要使得DMA與usart1相連線,在usart1中還要把usart的DMA功能開啟:USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);接著在程式中只需要監控,資料傳送的狀態,並適時的開啟或關閉DMA,留著下一次的串列埠傳送用。

函式過載的printf()函式如下

int fputc(int ch, FILE *f)

{

while(En_Usart_Queue(ch)==-1);

return ch;

}

起始就是往環形緩衝區中新增要串列埠列印的資料,這個動作是比較快的。因為cpu直接移動資料很快,而通過cpu來操作串列埠,等待收穫反映,有個while(..)是比較慢得,故有幾個毫秒的延時。現在好了,cpu ,通過printf只是移動資料到了緩衝區,緩衝區在一定的時候,cpu指揮dma來開始接下來的操作,然後dma操作,cpu接著做其他的事情,僅僅只要在下次空閒的時候來查一下dma有木有傳輸完成,或者有沒有傳輸錯誤,並改變環形佇列首位的位置,以及更改相應的狀態就可以了。這樣可以大大的節省很多的時間哦。

環形佇列的結構,大家可以看一些演算法,不是很難的。

下面是執行過程中,cpu操作查詢的函式。

void DMA_USART_Handler(void){

u16 num=0;

s16 read;

if(DMA_GetCurrDataCounter(DMA1_Channel4)==0){ //檢查DMA是否完成傳輸任務

DMA_Cmd(DMA1_Channel4, DISABLE);

while((read=De_Usart_Queue())!=-1){

USART_DMA_BUF[num]=read;

num++;

if(num==USART_DMA_BUF_SIZE)

break;

}

if(num>0){

((DMA_Channel_TypeDef *)DMA1_Channel4)->CNDTR = num;//數量暫存器清零

DMA_Cmd(DMA1_Channel4, ENABLE);

}

}

}

相關文章