|
Stm32 芯片目前具有一定的用戶,例程也很多。大多數僅僅是“ demo”的作用,往往只注重于功能的實現,意思就是說,看,這個usart 功能這樣就可以實現了。但應用于一個運行穩定的產品還需要很多技巧。
串口應用大概可以說在mcu 應用來說是最廣的,其主要作用就是收發,它在應用中又表現出兩種類型,客戶端( client )和服務端( server)。通信就是人與人聊天,人與人聊天的表現把人分為為問者和答者,或者說采訪者和受訪者。也可以說主從結構。先說客戶端,通常有兩者做法,一是無阻塞式,一種是有阻塞式。前者是發出命令后,做一全局變量標識,記下我前一次發的什么命令,然后等待收數據的消息,在消息處理過程中根據命令標識來對收到的數據進行相應處理。后者有阻塞式做法就是一個函數,發出命令后等待收到正確數據并解析。在等待的過程中可以用TickCount 做timeout,超過一定時間還沒收到數據可以退出返回false,解決死等的問題。因為操作系統有多線程,前者可以在主線程中寫出穩定的通信,后者在線程中應用比較多。
服務端的做法思想和客戶端不一樣,是受訪者,要做到有求必答。服務端重中之重的還是穩定第一。當然,收到正確的請求后到應答可以允許有一定的“思考”時間, 。我通常是這樣做,做一全局變量用于放接收到的數據,接收的地方只管往接收容器里裝,發現滿了就全部倒掉再裝,其它不用管。在主循環中解析收到的幀數據做出應答,解析過程中對不符合協議的數據可以根據協議做出錯誤回應或不做任何反應。完成后把收容器里的數據清空。下面的代碼實現了在stm32 的dma 的usart 的收發,體現了上述的一些思想。我用了systick 實現了超時的概念,有說uart 的idle (總線空閑)本身就是一個超時,我又想例如和一個口吃者“聊天“用它做超時是不是不太友善? 這一點我沒做深究。
代碼中含有485 方向的控制,一開始是在dma 的中斷控制的,但總是少兩個字節,我就在發送的時候多發2 個字節,也可以用。請讀者選擇吧。
下面是.h 文件輸出函數
void USART1_GPIOConfig(void);
void USART1_Config(void);
void USART1_NVICConfig(void);
void USART1_SendString( char* data); // 無阻塞發送字符串函數
void USART1_SendData(u8* data,u8 len);// 無阻塞發送函數
下面是.c 文件
#define DMA_USART1_BUFLEN 32 // 保存DMA 每次數據傳送的長度
u8 USART1_SendBuff[DMA_USART1_BUFLEN];
u8 USART1_TX_Finish=Co_TRUE; // USART 發送完成標志量
u8 USART1_ReceivBuff[DMA_USART1_BUFLEN];
void USART1_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; /* 復用推挽輸出模式*/
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; /* 輸出最大頻率為50MHz */
GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置USART1 Rx (PA10) */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; /* 浮空輸入模式*/
GPIO_Init(GPIOA, &GPIO_InitStruct);
/// 485 dir
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init( GPIOA, &GPIO_InitStruct);
GPIO_ResetBits( GPIOA, GPIO_Pin_8);// dir
}
void USART1_NVICConfig(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel=DMA1_Channel5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void USART1_Config(void)
{
USART_InitTypeDef USART_InitStruct;
DMA_InitTypeDef DMA_InitStructure;
/* USART and USART2 configured as follow:
- BaudRate = 9600 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled */
USART_InitStruct.USART_BaudRate = 9600;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStruct);
USART_ITConfig(USART1,USART_IT_IDLE, ENABLE);// 空閑中斷
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1,USART_FLAG_TC);///*CPU 的小缺陷:串口配置好,如果直接Send,則
第1 個字節發送不出去?如下語句解決第1 個字節無法正確發送出去的問題*/
////////////////////////////////// 發送dma////////////////////////////////////////////////
DMA_DeInit(DMA1_Channel4); //將DMA 的通道1 寄存器重設為缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&((USART_TypeDef*)USART1)->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_SendBuff; //DMA 內存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //數據傳輸方向,
DMA_InitStructure.DMA_BufferSize = DMA_USART1_BUFLEN; //DMA 通道的DMA 緩存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數據寬度為8 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 數據寬度為8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常緩存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道x 擁有中優先級
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道x 沒有設置為內存到內存傳輸
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_ITConfig(DMA1_Channel4,DMA_IT_TE,ENABLE);// 錯誤中斷
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);// 完成中斷
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
//接收dma///////////////////////////////////////////////
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)(&((USART_TypeDef*)USART1)->DR);
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)USART1_ReceivBuff;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize=DMA_USART1_BUFLEN;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority=DMA_Priority_High;
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;
DMA_Init(DMA1_Channel5,&DMA_InitStructure);
DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE);
DMA_ITConfig(DMA1_Channel5,DMA_IT_TE,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
DMA_Cmd(DMA1_Channel5,ENABLE);
}
void DMA1_Channel4_IRQHandler(void)
{
DMA_ClearITPendingBit(DMA1_IT_TC4);
DMA_ClearITPendingBit(DMA1_IT_TE4);
DMA_Cmd(DMA1_Channel4,DISABLE);
USART1_TX_Finish=Co_TRUE;
}
void DMA1_Channel5_IRQHandler(void){
DMA_ClearITPendingBit(DMA1_IT_TC5);
DMA_ClearITPendingBit(DMA1_IT_TE5);
DMA_Cmd(DMA1_Channel5,DISABLE);
pc_AppendData(USART1_ReceivBuff,DMA_USART1_BUFLEN);
DMA1_Channel5->CNDTR=DMA_USART1_BUFLEN;// 重裝填
DMA_Cmd(DMA1_Channel5,ENABLE);
}
void USART1_IRQHandler(void)
{
u16 alen;
if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET){// 如果為空閑總線中斷?
USART_ClearITPendingBit(USART1,USART_IT_IDLE);
DMA_Cmd(DMA1_Channel5,DISABLE);// 關閉DMA, 防止處理其間有數據
alen=DMA_USART1_BUFLEN-DMA_GetCurrDataCounter(DMA1_Channel5);
if(alen>0){
pc_AppendData(USART1_ReceivBuff,alen);
}
DMA_ClearFlag(DMA1_FLAG_GL5|DMA1_FLAG_TC5|DMA1_FLAG_TE5|DMA1_FLAG_HT5);// 清標
志?
DMA1_Channel5->CNDTR=DMA_USART1_BUFLEN;// 重裝填?
DMA_Cmd(DMA1_Channel5,ENABLE);// 處理完,重開DMA
//讀SR 后讀DR 清除Idle
alen=USART1->SR;
alen=USART1->DR;
}
if(USART_GetITStatus(USART1,USART_IT_PE|USART_IT_FE|USART_IT_NE)!=RESET){// 出錯
USART_ClearITPendingBit(USART1,USART_IT_PE|USART_IT_FE|USART_IT_NE);
}
//發送中斷處理
if(USART_GetITStatus(USART1, USART_IT_TC) != RESET){
USART_ClearITPendingBit(USART1, USART_IT_TC); //清除中斷標志
GPIO_ResetBits( GPIOA, GPIO_Pin_8);
}
}
void USART1_SendString(char* data)
{
USART1_SendData((u8*)data,strlen(data));
}
void USART1_SendData(u8* data,u8 len)
{
memcpy(USART1_SendBuff,data,len);
while(USART1_TX_Finish==0);
DMA_Cmd(DMA1_Channel4,DISABLE);
GPIO_SetBits( GPIOA, GPIO_Pin_8);
DMA1_Channel4->CNDTR=len;
USART1_TX_Finish=0;
DMA_Cmd(DMA1_Channel4,ENABLE);
}
上述代碼實現了stm32 的串口1 的收發功能。應用說明如下:
實現windows 操作中的TickCount 方法:
U32 sysTickCount;
void SysTick_Handler(void)
{
sysTickCount++;
}
實現接收容器:
#define PC_LEN 48
u8 pc_receivdata[IDCPC_LEN]; // 用來乘放接收數據
u8 pc_count=0; // 接收字節數
U32 pc_lastrec; // 最后一次接收的tickcount 值
void pc_AppendData(u8* value,u8 alen){
if((pc_count+alen)>PC_LEN)
pc_count=0;
if(alen<=PC_LEN){
memcpy(&pc_receivdata[pc_count],value,alen);
pc_count+=alen;
pc_lastrec = sysTickCount;
}
}
然后在主循環中檢測接收數據:
u32 tvalue;
if(pc_count>0){
tvalue = sysTickCount - pc_lastrec;
if(tvalue>3){ // 這里3 可以根據systickcount 的頻率修改適當,可以取20ms
⋯⋯⋯ ..//分析幀數據
USART4_SendData(..); 調用無阻塞發送應答
idcpc_count=0; // 清空容器
}
}
|
評分
-
查看全部評分
|