stm32 v3.5 库函数里面,对于串口USART有这样两个函数:
USART_ClearFlag()和USART_ClearITPendingBit()
查库函数定义,说一个是清除标志,一个是清除中断预处理位。然后我看了stm32f10x_usart.c文件,发现两个函数都操作的是USART->SR寄存器,但是这个寄存器只有一组标志位,没有什么中断预处理位。。
实际上两个函数实现的功能是一样的,都是清除相对应的标志位,只是标志位和中断位含义不一样,是标志位但
是不一定会产生中断。例如:
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
这是中断位,可以产生中断
#define USART_FLAG_TXE ((uint16_t)0x0080)
#define USART_FLAG_TC ((uint16_t)0x0040)
#define USART_FLAG_RXNE ((uint16_t)0x0020)
这是标志位,有的标志位不能产生中断
标志位在程序中可以作为判定条件,支持程序的运行,中断则是跳转到中断函数执行。两个函数实现的功能是一
样的,在中断程序中可以用两个中的任一个。我想区分两个函数是为了更清晰吧。
还有
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_FLAG_TC ((uint16_t)0x0040)
这两个数值不同是因为标志位只是为了清除标志位而设的,而中断位设置成这个值是因为在其他函数中这一位还
有其他用途。而且还要注意:
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_CLEAR_FLAG(USART_FLAG));
assert_param(IS_USART_PERIPH_FLAG(USARTx, USART_FLAG)); /* The CTS flag is not available for UART4 and UART5 */
USARTx->SR = (uint16_t)~USART_FLAG;
}
这一步 USARTx->SR = (uint16_t)~USART_FLAG; 似乎应该是 USARTx->SR &= (uint16_t)~USART_FLAG;
其实状态位只能有硬件置位,软件只能读和清零,所以这样写也是正确的。
没有很明白,所以转在这里,等我哪天灵光乍现了,再来加上我的理解
上面的内容是别人的,我略作修改,下面的内容是我原创。不知道本文到底是属于原创还是转载,但是......我的更简洁、精辟
-----------------------------------------------------------------------------------------------------------------------
我是在使用TC的时候遇见这个问题的,所以这里就只分析TC这个位
先研究简单点的USART_ClearFlag函数
//调用形式
USART_ClearFlag(USART3,USART_FLAG_TC);
//USART_FLAG_TC的定义
#define USART_FLAG_TC ((uint16_t)0x0040)
//USART_ClearFlag函数的原型
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_CLEAR_FLAG(USART_FLAG));
assert_param(IS_USART_PERIPH_FLAG(USARTx, USART_FLAG)); /* The CTS flag is not available for UART4 and UART5 */
USARTx->SR = (uint16_t)~USART_FLAG;
}
一目了然,最后USARTx->SR = ~(0100'0000);
再来研究复杂点的USART_ClearITPendingBit函数(注意,这两个函数的第二个参数,是不一样的)
//调用形式
USART_ClearITPendingBit(USART3,USART_IT_TC);
//USART_IT_TC的定义
#define USART_IT_TC ((uint16_t)0x0626)//=0000'0110'0010'0110
//USART_ClearITPendingBit的函数原型
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
{
uint16_t bitpos = 0x00, itmask = 0x00;
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_CLEAR_IT(USART_IT));
assert_param(IS_USART_PERIPH_IT(USARTx, USART_IT)); /* The CTS interrupt is not available for UART4 and UART5 */
bitpos = USART_IT >> 0x08; //=0000'0110
itmask = (uint16_t)((uint16_t)0x01 << bitpos); //=0100'0000
USARTx->SR = (uint16_t)~itmask; //=~(0100'0000)
}
可以看到最后还是USARTx->SR=~(0100'0000);
对比一下USART_ClearFlag和USART_ClearITPendingBit的参数
USART_ClearFlag的参数 | USART_ClearITPendingBit的参数 |
USART_FLAG_CTS = 0x0200 | USART_IT_CTS = 0x096A |
USART_FLAG_LBD = 0x0100 | USART_IT_LBD = 0x0846 |
USART_FLAG_TC = 0x0040 | USART_IT_TC = 0x0626 |
USART_FLAG_RXNE = 0x0020 | USART_IT_RXNE = 0x0525 |
这里可以发现一个规律USART_ClearFlag参数只有一个位是“1”,其位置正好等于USART_ClearITPendingBit的参数左移八位后的结果,所以这里可以非常非常肯定的讲:函数USART_ClearFlag和函数USART_ClearITPendingBit的功能totally一样
----------------------------------------------------------------------------------------------------------------------------
STM32的USART发送数据时如何使用TXE和TC标志
在USART的发送端有2个寄存器,一个是程序可以看到的寄存器——发送数据寄存器(通过USART_DR查看),另一个是程序看不到的寄存器——发送移位寄存器,对应的有两个USART数据发送标志,一个是TXE=发送数据寄存器空,另一个是TC=发送移位寄存器空。
(这粗箭头和这细箭头,简直不要太形象哦,粗箭头是八位八位的传,细箭头是一位一位的传)
当USART_DR中的数据传送到移位寄存器后,TXE被设置,此时移位寄存器开始向TX信号线按位传输数据,但因为TDR已经变空,所以程序可以把下一个要发送的字节(操作USART_DR)写入TDR中,而不必等到移位寄存器中所有位发送结束,所有位发送结束时(送出停止位后)硬件会设置TC标志。
另一方面,在刚刚初始化好USART还没有发送任何数据时,也会有TXE标志,因为这时发送数据寄存器是空的。TXEIE和TCIE的意义很简单,TXEIE允许在TXE标志为'1'时产生中断,而TCIE允许在TC标志为'1'时产生中断。
至于什么时候使用哪个标志,需要根据你的需要自己决定。但我认为TXE允许程序有更充裕的时间填写TDR寄存器,保证发送的数据流不间断。TC可以让程序知道发送结束的确切时间,有利于程序控制外部数据流的时序。
位7 | TXE:发送数据寄存器空 (Transmit data register empty) 当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果USART_CR1寄存器中的TXEIE为1,则产生中断。对USART_DR的写操作,将该位清零。 0:数据还没有被转移到移位寄存器;1:数据已经被转移到移位寄存器。 注意:单缓冲器传输中使用该位。 |
位6 | TC:发送完成 (Transmission complete) 当包含有数据的一帧发送完成后,并且TXE=1时,由硬件将该位置’1’。如果USART_CR1中的TCIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后写入USART_DR)。 TC位也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序。 0:发送还未完成;1:发送完成 |
位5 | RXNE:读数据寄存器非空 (Read data register not empty) 当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。 RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。 0:数据没有收到; 1:收到数据,可以读出。 |
/*******************************************************************************
* 函数名 : UART1_SendString
* 描述 : USART1发送字符串
* 输入 : *s字符串指针
* 注释 :0==RESET,表示发送还未完成
USART_FLAG_TC!=RESET,就是=SET,表示发送完成,此时执行USART_GetFlagStatus会把USART_FLAG_TC清零(未证实)
*******************************************************************************/
void UART1_SendString(u8* s)
{
while(*s)//检测字符串结束符
{
//USART_FLAG_TC==RESET时,表示发送还未完成。
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
USART_SendData(USART1 ,*s++);//发送当前字符
}
}
u8 FLAG_TC=0;//定义全局变量
/*******************************************************************************
* 函数名 : UART1_SendString
* 描述 : USART1发送字符串
* 输入 : *s字符串指针
*******************************************************************************/
void UART1_SendString(char* s)
{
FLAG_TC=0;//提前准备一下
while(*s)//检测字符串结束符
{
USART_SendData(USART1 ,*s++);//发送当前字符
while( FLAG_TC==0); //0:发送还未完成;1:发送完成
FLAG_TC=0;
}
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)//发送完成中断,= SET
{
USART_ClearITPendingBit(USART1,USART_IT_TC);
FLAG_TC=1;
}
}
开中断时,就不能通过简单的判断标志位USART_FLAG_TC的状态去决定能否发送下一个字符。比方说,使用
while(*s) { USART_SendData(USART1 ,*s++);//发送字符 while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); ... }
执行完发送字符的语句后,因为不可能这么快完成发送,所以程序接下来执行的是while语句。在等待期间,字符发送完毕,这时就会进入到中断,如果在中断里清除标志位,退出中断后,标志位USART_FLAG_TC仍然是RESET;如果不在中断里清除标志位,就不能退出中断。所以程序就会死在while里。
所以这里就需要额外设置一个标志。
//定义全局变量。也可以为了简化,把这四个参数结合起来包含在一个结构体里
u8 TxLength; //发送数据长度
u8 TxIndicator;//发送指示器,表示目前发送完成哪一位了,下面要发送的是第(TxIndicator+1)位
u8 TxBuff[256];//Data
u8 TxFnd; //发送完成标志
/*************************************************************************
*程序名称 : SendFirstByte
*功能 : 启动发送第一个字节
*@Notes : 鸡贼啊,剩下的都放在USART_FLAG_TC中断里面,因为这个中断是
发送完成中断,是利用“发送第一个字节”来“启动发送”
*@Notes : 需要注意,如果发送结果是乱码的话,一种供参考的解决方案是
把数据直接赋值给TxBuff,而不要通过函数的形参传递
*输入参数 : u8 txbuf[]: 需要发送的数据,u8 len : 数据中的字节数
*返回值 : 无
*************************************************************************/
void SendFirstByte( u8 txbuf[], u8 len )
{
TxBuff = txbuf;//需要发送的数据
TxLength = len ;//发送数据长度
TxIndicator = 1 ;//0已经发送,也是用来启动发送的
USART_SendData(USART1, txbuf[0]); /**@Notes:只发送了txbuf的第一个字节*/
}
/*************************************************************************
*程序名称 : USART1_IRQHandler
*功能 : 完成发送数据
*************************************************************************/
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
USART_ClearITPendingBit(USART1,USART_IT_TC);
if( TxIndicator < TxLength )//数组的索引max永远小于数组元素的个数
{
USART_SendData(USART1, TxBuff[TxIndicator++]);
}
else//最后一字节数据发送完成
{
TxFnd = 0;
TxIndicator = 0;
}
}
}
A:是因为发送的间隔太短了,可以在发送每个字符之前先判断一下上一个字符是否发送完成,可以参考上面的“三种发送方法”
A:解决方案是把判断能否发送的语句放在发送数据之前
这下算是功德圆满了吧