;
DMA_SetCurrDataCounter(DMA2_Stream7, cnt);
DMA_ITConfig(DMA2_Stream7 , DMA_IT_TC , ENABLE);
DMA_Cmd(DMA2_Stream7, ENABLE);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)!=1); //发送完成退出
}
2. 串口接收
串口接收,个人觉得比发送要难,因为涉及到中断,还要判断什么时候接收完成,相当于处于被动的地位,无法掌控对数据的操作。
最基本的接收方式就是:串口接收中断置位,USART_GetITStatus(USART1, USART_IT_RXNE)=SET
则利用串口接收函数接收字符,
Res =USART_ReceiveData(USART1);
然后就可以存储到字符串当中,关键的方式就是,如何判断接收完成。然后进行处理。
最常见的串口接收函数应该就是正点原子的接收函数,很经典,也很方便,直接拿来就用,是根据最后的结束标志位(0x0D 0x0A) 来判断一帧数据接收完成的。
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
我自己也写过一个简单版的,附在下面,也比较清晰一点。(使用0x0A做结尾或者接收达到上限)
void USART2_IRQHandler()
{
u8 TempData;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
TempData = USART_ReceiveData(USART2);
if((TempData == 0X0A)||(RX_Index == RX_Num))
{
ReceiveFlag = 1;
}
else
{
RX_Buffer[RX_Index++] = TempData;
}
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
}
}
上面的函数存在一个问题是(正点原子例程),通信的帧数据必须以0x0D 0x0A为结尾,否则会出现无法判断接收完成的情况。
所以这样就引出一个问题,如何接收任意的数据呢?即不定长而且不以任何特定的字符为结尾的数据。
在数据通信完成之后,串口就会出现空闲的状态,是否可以根据这样的状态来判断数据接收完成呢,答案当然是可以的。
串口中提供了一个可以获取串口是否空闲的函数:
USART_GetITStatus(USART3,USART_IT_IDLE);
根据这样的一个函数就可以判断串口是否空闲,从而来判断数据帧是否结束。
当然如果直接采用中断的话,对CPU的占用率比较大,采用DMA接收串口的数据的话,可以有效缓解CPU的压力。
设计思路是:先开启串口接收中断,当接收开始时,这是开启DMA接收,当接收完成之后,进入空闲中断,此时判断帧结束,数据接收完成。
所以这样一个串口接收函数就新鲜出炉了:
void USART3_IRQHandler(void) //串口3中断服务程序
{
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //通过接收中断来开启DMA接收
{
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);
USART_ITConfig(USART3,USART_IT_IDLE,ENABLE);
DMA_Cmd(DMA1_Stream1,ENABLE);
}
else if(USART_GetITStatus(USART3,USART_IT_IDLE)!=RESET)//再通过空闲中断来判断数据帧结束。
{
USART_ClearITPendingBit(USART3,USART_IT_IDLE);
Usart3_DataLength=RXNum-DMA_GetCurrDataCounter(DMA1_Stream1); //DMA Counter可以判断接收到多少个字节。
DMA_Cmd(DMA1_Stream1,DISABLE);
USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
USART_ITConfig(USART3,USART_IT_IDLE,DISABLE);
Usart_RecFlag=1; //接收完成标志位
}
}
还有一种思路是,不使用串口空闲来判断,而是通过定时器来实现,如果一定时间内没有数据接收,则认为接收完成,通常时间设置的是5ms。
同样使用DMA来接收,DMA是循环模式,通过DMACounter的值来判断是否开始接收和接收完成。
DMAcounter值指示的待发送或者接收到的数据量,如果完成,则Counter值变为0。
oid UART1Poll()
{
unsigned short dmacnt;
dmacnt=DMA_GetCurrDataCounter(DMA2_Stream5);
if(USARTRecStartFlag==0)
{
if(dmacnt==USART_LEN); //如果发生变化,则数据通信开始,
else
{
USARTRecStartFlag=1;
TIM_Cmd(TIM5,ENABLE);
USARTDataInTime = Systemtime; // 记下时间
}
}
else //开始接收数据,
{
if(dmacnt==DMALastCnt)
{
USARTCurrTime = Systemtime;
if(USARTCurrTime-USARTDataInTime>5)//时间片5*1ms 超过一定时间,则认为接收完成
{
USART_FrameFlag=1;
DMALastCnt=USART_LEN;
USART_Len = USART_LEN - DMA_GetCurrDataCounter(DMA2_Stream5);
USARTRecStartFlag=0;
DMA_Cmd(DMA2_Stream5,DISABLE); //停止使能 才能修改计数器
DMA_SetCurrDataCounter(DMA2_Stream5, USART_LEN);
DMA_Cmd(DMA2_Stream5,ENABLE); //停止使能 才能修改计数器
TIM_Cmd(TIM5,DISABLE);
TIM_SetCounter(TIM5,0);
}
}
else
{
DMALastCnt=dmacnt;
USARTDataInTime = Systemtime; //GetSystemtime(); // Systemtime;
}
}
}
void TIM5_IRQHandler(void) //1000HZ 定时器计时。
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
Systemtime++;
// led output
if(Systemtime == 1000)
{
Systemtime = 0;
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
}
串口通信(UART)在通信当中尤其是在低速率占用很重要的地位, 通信 速度虽然比不上SPI通信,但是由于其简单,对通信双方的时钟要求不是很高,受到很广泛的使用,很多嵌入式程序猿(媛) 都倾向于串口通信。
1. 串口发送
串口发送函数非常简单,直接调用串口的API函数
void USART_SendData(USART_TypeDef USARTx, uint16_t Data);*
即可发送出去,举个简单的实例:
void Usart1_SendData(u8 *Str) //Str存储发送的数据
{
u8 i=0;
while(Str!=0)
{
USART_SendData(USART1,Str);
while(!USART_GetFlagStatus(USART1,USART_FLAG_TXE));
i++;
}
}
上面的函数存在一个问题,当数据当中有0x00字节 时,就会跳出while循环,停止发送数据,所以存在bug,需要进行改良。
很多朋友会问无缘无故为啥会发0x00字节呢?这是因为在项目中,尤其是一些协议通讯,经常会遇到0x00 ,代表数据正常 或者一定的含义 ,如果还是调用上面的函数,就会出现发送不完整的情况。
所以要发送完全,就要加上数据的长度dateLength ,这样就万无一失了。
void Usart3_SendData(u8 *Str,u8 Datalength)
{
u8 i=0;
while(i
{
USART_SendData(USART3,Str);
while(!USART_GetFlagStatus(USART3,USART_FLAG_TXE));
i++;
}
}
当然如果为了减少CPU的压力,可以使用DMA去发送数据。
思路是:首先设置DMA的Counter值=数据长度,然后启动DMA流,就开始发送了。可以设置DMA发送完成中断,也可以无需设置,最后判断一下串口是否发送完成即可。
void UART1_SentMsgL(unsigned char *data, u16 cnt)
{
u16 i;
USART_ClearFlag(USART1, USART_FLAG_TC); //清除UART1发送完成标志位
for(i=0;i
USART_TX_BUF=data;
DMA_SetCurrDataCounter(DMA2_Stream7, cnt);
DMA_ITConfig(DMA2_Stream7 , DMA_IT_TC , ENABLE);
DMA_Cmd(DMA2_Stream7, ENABLE);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)!=1); //发送完成退出
}
2. 串口接收
串口接收,个人觉得比发送要难,因为涉及到中断,还要判断什么时候接收完成,相当于处于被动的地位,无法掌控对数据的操作。
最基本的接收方式就是:串口接收中断置位,USART_GetITStatus(USART1, USART_IT_RXNE)=SET
则利用串口接收函数接收字符,
Res =USART_ReceiveData(USART1);
然后就可以存储到字符串当中,关键的方式就是,如何判断接收完成。然后进行处理。
最常见的串口接收函数应该就是正点原子的接收函数,很经典,也很方便,直接拿来就用,是根据最后的结束标志位(0x0D 0x0A) 来判断一帧数据接收完成的。
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
我自己也写过一个简单版的,附在下面,也比较清晰一点。(使用0x0A做结尾或者接收达到上限)
void USART2_IRQHandler()
{
u8 TempData;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
TempData = USART_ReceiveData(USART2);
if((TempData == 0X0A)||(RX_Index == RX_Num))
{
ReceiveFlag = 1;
}
else
{
RX_Buffer[RX_Index++] = TempData;
}
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
}
}
上面的函数存在一个问题是(正点原子例程),通信的帧数据必须以0x0D 0x0A为结尾,否则会出现无法判断接收完成的情况。
所以这样就引出一个问题,如何接收任意的数据呢?即不定长而且不以任何特定的字符为结尾的数据。
在数据通信完成之后,串口就会出现空闲的状态,是否可以根据这样的状态来判断数据接收完成呢,答案当然是可以的。
串口中提供了一个可以获取串口是否空闲的函数:
USART_GetITStatus(USART3,USART_IT_IDLE);
根据这样的一个函数就可以判断串口是否空闲,从而来判断数据帧是否结束。
当然如果直接采用中断的话,对CPU的占用率比较大,采用DMA接收串口的数据的话,可以有效缓解CPU的压力。
设计思路是:先开启串口接收中断,当接收开始时,这是开启DMA接收,当接收完成之后,进入空闲中断,此时判断帧结束,数据接收完成。
所以这样一个串口接收函数就新鲜出炉了:
void USART3_IRQHandler(void) //串口3中断服务程序
{
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //通过接收中断来开启DMA接收
{
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);
USART_ITConfig(USART3,USART_IT_IDLE,ENABLE);
DMA_Cmd(DMA1_Stream1,ENABLE);
}
else if(USART_GetITStatus(USART3,USART_IT_IDLE)!=RESET)//再通过空闲中断来判断数据帧结束。
{
USART_ClearITPendingBit(USART3,USART_IT_IDLE);
Usart3_DataLength=RXNum-DMA_GetCurrDataCounter(DMA1_Stream1); //DMA Counter可以判断接收到多少个字节。
DMA_Cmd(DMA1_Stream1,DISABLE);
USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
USART_ITConfig(USART3,USART_IT_IDLE,DISABLE);
Usart_RecFlag=1; //接收完成标志位
}
}
还有一种思路是,不使用串口空闲来判断,而是通过定时器来实现,如果一定时间内没有数据接收,则认为接收完成,通常时间设置的是5ms。
同样使用DMA来接收,DMA是循环模式,通过DMACounter的值来判断是否开始接收和接收完成。
DMAcounter值指示的待发送或者接收到的数据量,如果完成,则Counter值变为0。
oid UART1Poll()
{
unsigned short dmacnt;
dmacnt=DMA_GetCurrDataCounter(DMA2_Stream5);
if(USARTRecStartFlag==0)
{
if(dmacnt==USART_LEN); //如果发生变化,则数据通信开始,
else
{
USARTRecStartFlag=1;
TIM_Cmd(TIM5,ENABLE);
USARTDataInTime = Systemtime; // 记下时间
}
}
else //开始接收数据,
{
if(dmacnt==DMALastCnt)
{
USARTCurrTime = Systemtime;
if(USARTCurrTime-USARTDataInTime>5)//时间片5*1ms 超过一定时间,则认为接收完成
{
USART_FrameFlag=1;
DMALastCnt=USART_LEN;
USART_Len = USART_LEN - DMA_GetCurrDataCounter(DMA2_Stream5);
USARTRecStartFlag=0;
DMA_Cmd(DMA2_Stream5,DISABLE); //停止使能 才能修改计数器
DMA_SetCurrDataCounter(DMA2_Stream5, USART_LEN);
DMA_Cmd(DMA2_Stream5,ENABLE); //停止使能 才能修改计数器
TIM_Cmd(TIM5,DISABLE);
TIM_SetCounter(TIM5,0);
}
}
else
{
DMALastCnt=dmacnt;
USARTDataInTime = Systemtime; //GetSystemtime(); // Systemtime;
}
}
}
void TIM5_IRQHandler(void) //1000HZ 定时器计时。
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
Systemtime++;
// led output
if(Systemtime == 1000)
{
Systemtime = 0;
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
}
举报