STM32串口相关(一)–发送接收基础性问题
【注】STM32串口相关,总共分为三个部分:①发送接收基础性问题。②最优的串口使用方式及说明。③串口发送接收数据的一般算法。
1、普通的中断接收模式
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_GetITStatus(USART1, USART_IT_RXNE)
打开接收中断后,当接收到数据式,中断标志位就会置为1,从而进入中断。
R_Dat =USART_ReceiveData(USART2); //读取接收到的数据//(USART1->DR)读取DR
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
/* Receive Data */
return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}
进入中断后,我们通过USART_ReceiveData函数读取数据,而从USART_ReceiveData函数内部实现方式可以知道,这个数据存储在DR寄存器中的。
void USART2_IRQHandler(void) //这个函数是接收不定长数据自己写的程序,非寄存器标志
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
R_Dat =USART_ReceiveData(USART2); //读取接收到的数据//(USART1->DR)读取DR
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(R_Dat!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(R_Dat==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=R_Dat ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
这里我们贴了一块正点原子的中断接收程序,这个程序如果只是“单字节”数据传输,那么可以沈略帧尾0X0D 0X0A,直接进入中断读取DR寄存器即可,如果接收数据为不定长的数组,那么我们使用正点原子这个程序可以实现,不过帧尾一定要是0X0D 0X0A。同时不定长的数组,最后接收存储在USART_RX_BUF[]中,当然这个数组也是有限数组,如下,长度可以根据实际情况改变。
#define USART_REC_LEN 200//定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA; //接收状态标记
当不定长时,我们读取数据的代码如下:
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
for(t=0;t<len;t++)
{
buf[t]=USART_RX_BUF[t];
}
}
我们在程序中可以看到,进入中断之后,中断标志位在程序的最后并没有清除,这是为什么呢?
这里我们说明一点知识点:
串口接收发送的一般过程为:
①接收:RX -> 接收移位寄存器(SR) -> 接收数据寄存器(DR)-> CPU
②发送:CUP -> 发送数据寄存器(DR)-> 发送移位寄存器(SR)-> TX
其中,DR是我们需要读取或者写入的寄存器区。写入后移位寄存器会硬件自动发送,同样,接收到数据后,移位寄存器也会硬件自动将数据存到DR寄存器中供我们读取。
有些中断标志位需要我们软件清除,但是这个串口的接收标志位优点特殊:
TXE:写寄存器DR时会自动清零
RXNE:读寄存器DR时也会自动清零,也可以手动清零
TC:读/写DR寄存器,会自动清零,也可以手动清零
手动清零的代码:
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //增加--清除RXNE标志位
USART_GetFlagStatus(USART2,USART_FLAG_TC);
这里再说明一下,TC和TXE标志位,当DR中的数据转移到SR中时TXE置1,如果有数据写入DR时就能将TXE置0;如果SR中的数据全部通过TXDpin移出并且没有数据进入DR,则TC置1。并且需要注意TXE只能通过写DR来置0,不能直接将其清零,而TC可以直接将其写1清零。也就是:
TXE = 1:表示DR寄存器已经清空,这个时候可以将下一帧数据写入
TXE = 0:表示DR寄存器仍然有数据,不可以写数据,若忽略这个标志位,很可能,上一个数据还未发送,新值已经送进来,使得上一个数据被覆盖丢失,使得通信错误。
TC = 1:表示SR寄存器中的值已经完全移出,此时数据传输完成。
TC = 0:表示上一个数据还未传输完成。
那么该如何看待这两个标志位的应用场景呢?其实仔细看我上面关于这两个标志位的说明就可以找到答案。显然,对于USART_FLAG_TXE来说,只是说明数据寄存器中的数据已经被发送移位寄存器取走了(但发送移位寄存器中可能还没有启动发送过程),通过中断就可以提醒CPU可以往数据寄存器中填充数据了,发送移位寄存器中的数据往外发送的过程其实还是比较耗时的,相对于C语言代码执行时间来说,这个过程的耗时完全算得上是一个宏观的数据,所以每次发送数据寄存器中的数据被发送移位寄存器取走后,都应该产生中断来提醒CPU对该寄存器更新数据;而对于USART_FLAG_TC来说,没必要每次当发送移位寄存器中的数据发送完成后都发生中断,而应该是整个串口数据帧全部发送完毕,包括最后一个字节也发送出去之后才应该开中断,这代表的就是一个数据帧发送完成事件了。
我们在实际的使用过程中,可以直接利用TC = 1来判断是否给发送数据寄存器DR赋新值。
2、中断发送
我们一般在串口发送的时候,如果是调试的话,直接用printf就行,如果是设备间通信,发送单个字符直接以查询的方式发送。
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
/*******************************************************/
void USART1_IRQHandler(void)
{
unsigned int i;
/*接收中断*/
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
/* Read one byte from the receive data register */
RxBuffer1[RxCounter1++] = USART_ReceiveData(USART1);
if(RxCounter1 == NbrOfDataToRead1) //接收数据达到需要长度,则将数据复制到发送数组中,并置标志
{
/* Disable the USART1 Receive interrupt */
//USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
for(i=0; i< RxCounter1; i++) TxBuffer1[i] = RxBuffer1[i];
rec_f=1;
RxCounter1=0;
TxCounter1=0;
USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //打开发送中断,这句是关键
}
}
/*发送中断*/
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
USART_SendData(USART1, TxBuffer1[TxCounter1++]);
if(TxCounter1 == NbrOfDataToTransfer1)//发送数据完成
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); //关闭发送中断
}
}
}
移位状态寄存器SR,复位值为0X00C0H,也就是第七位TXE、第六位TC为1,TXE = 1,表示发送寄存器为空,TC = 1,表示发送已经完成。而在配置时,只要使能IT_txe,此时TXE = 1就会进入中断,发送相关的数据。所以我们一般在函数初始化时不要启动TXE中断,
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
而是在主程序,什么时候需要发送数据,再去打开,发送完成之后,再关闭中断。