STM32的USART发送数据时如何使用TXE和TC标志

本文详细介绍了USART通过TXE和TC两种中断方式发送数据的过程及注意事项。对于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可以让程序知道发送结束的确切时间,有利于程序控制外部数据流的时序。

 

TXE--写寄存器DR清零

RXNE--读寄存器DR清零,也可软件手动清零

 TC--  读/写寄存器DR清零,也可软件手动清零

 

先说TC。即Transmission Complete。发送一个字节后才进入中断,这里称为“发送后中断”。和原来8051的TI方式一样,都是发送后才进中断,需要在发送函数中先发送一个字节触发中断。发送函数如下

/*******
功能:中断方式发送字符串.采用判断TC的方式.即 判断 发送后中断 位.
输入:字符串的首地址
输出:无
*******/
void USART_SendDataString( u8 *pData )
{
    pDataByte = pData;
  
    USART_ClearFlag(USART1, USART_FLAG_TC);//清除传输完成标志位,否则可能会丢失第1个字节的数据.网友提供.
   
    USART_SendData(USART1, *(pDataByte++) ); //必须要++,不然会把第一个字符t发送两次
}


中断处理函数如下
/********
* Function Name  : USART1_IRQHandler
* Description    : This function handles USART1 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*********/
void USART1_IRQHandler(void)
{
    if( USART_GetITStatus(USART1, USART_IT_TC) == SET  )
    {
        if( *pDataByte == '\0' )//TC需要 读SR+写DR 方可清0,当发送到最后,到'\0'的时候用个if判断关掉
            USART_ClearFlag(USART1, USART_FLAG_TC);//不然TC一直是set, TCIE也是打开的,导致会不停进入中断. clear掉即可,不用关掉TCIE
        else
            USART_SendData(USART1, *pDataByte++ );
    }

}

其中u8 *pDataByte;是一个外部指针变量

在中断处理程序中,发送完该字符串后,不用关闭TC的中断使能TCIE,只需要清掉标志位TC;这样就能避免TC == SET 导致反复进入中断了。

void USART_Config()
{
  ........................................

  USART_ITConfig(USART1, USART_IT_TC, ENABLE);//Tramsimssion Complete后,才产生中断. 开TC中断必须放在这里,否则还是会丢失第一字节

  USART_Cmd(USART1, ENABLE); //使能USART1
}

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

再说判断TXE。即Tx DR Empty,发送寄存器空。当使能TXEIE后,只要Tx DR空了,就会产生中断。所以,发送完字符串后必须关掉,否则会导致重复进入中断。这也是和TC不同之处。

发送函数如下:
/*******
功能:中断方式发送字符串.采用判断TC的方式.即 判断 发送后中断 位.
输入:字符串的首地址
输出:无
*******/
void USART_SendDataString( u8 *pData )
{
    pDataByte = pData;
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//只要发送寄存器为空,就会一直有中断,因此,要是不发送数据时,把发送中断关闭,只在开始发送时,才打开。
   
}

中断处理函数如下:

/********
* Function Name  : USART1_IRQHandler
* Description    : This function handles USART1 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
********/
void USART1_IRQHandler(void)
{
    if( USART_GetITStatus(USART1, USART_IT_TXE) == SET  )
    {
        if( *pDataByte == '\0' )//待发送的字节发到末尾NULL了
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//因为是 发送寄存器空 的中断,所以发完字符串后必须关掉,否则只要空了,就会进中断
        else
            USART_SendData(USART1, *pDataByte++ );
    }

}

在串口初始化函数中就不用打开TXE的中断了(是在发送函数中打开的)

 



 

<think> 我们使用STM32标准外设库(Standard Peripheral Library)进行USART数据发送。标准库与HAL库同,它更接近寄存器操作,但提供了封装函数。以下是实现步骤: ### 步骤1: 配置USARTGPIO 首先需要配置USART所用到的GPIO引脚(TX)USART外设本身。 #### GPIO配置 - USART的TX引脚需要配置为**复用推挽输出**模式(Alternate Function Push-Pull)。 - 以USART1为例,TX通常位于PA9(具体取决于芯片型号,请查阅对应芯片的数据手册)。 #### USART配置 - 设置波特率、数据位、停止位、校验位等。 - 使能USART对应GPIO钟。 代码示例(以USART1为例): ```c #include "stm32f10x.h" void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能USART1GPIOA钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置PA9为复用推挽输出(TX) 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); // 配置USART1 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; // 发送模式 USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE); } ``` ### 步骤2: 阻塞方式发送数据(轮询) 在标准库中,我们通过轮询状态寄存器(SR)的标志位来判断发送数据寄存器(DR)是否为空。当数据寄存器为空,可以写入下一个数据。 发送一个字节的函数: ```c void USART1_SendByte(uint8_t byte) { // 等待发送数据寄存器为空(即上一个数据已经发送完成) while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 发送数据 USART_SendData(USART1, byte); } ``` 发送一个字符串: ```c void USART1_SendString(char *str) { while (*str) { USART1_SendByte(*str++); } } ``` ### 步骤3: 非阻塞方式发送数据(中断) 使用中断方式发送数据,可以避免CPU在等待发送完成的资源浪费。 #### 配置中断 - 使能USART发送完成中断(TC中断)或发送数据寄存器空中断(TXE中断)。通常,我们使用TXE中断来实现连续发送。 - 配置NVIC(嵌套向量中断控制器)以允许USART1中断。 代码示例(初始化中断): ```c void USART1_Interrupt_Init(void) { // ... 前面的GPIOUSART配置与上面相同,省略 // 使能USART1的发送数据寄存器空中断(TXEIE) USART_ITConfig(USART1, USART_IT_TXE, ENABLE); // 配置NVIC NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } ``` #### 中断服务函数 在中断服务函数中,当发送数据寄存器为空(TXE标志置位),我们可以写入下一个数据。当数据全部发送完成后,需要关闭中断(否则会一直进入中断)。 ```c // 定义一个全局变量用于存储待发送数据状态 uint8_t txBuffer[100]; // 发送缓冲区 uint16_t txIndex = 0; // 当前发送位置 uint16_t txLen = 0; // 待发送数据长度 // 启动中断发送函数 void USART1_StartSendString(uint8_t *str, uint16_t len) { // 复制数据到缓冲区(或者直接使用指针,注意数据在发送过程中能被修改) for (int i = 0; i < len; i++) { txBuffer[i] = str[i]; } txIndex = 0; txLen = len; // 使能发送中断(一旦使能,当发送数据寄存器为空就会进入中断) USART_ITConfig(USART1, USART_IT_TXE, ENABLE); } // USART1中断服务函数 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) { // 发送数据寄存器为空,可以写入下一个数据 if (txIndex < txLen) { USART_SendData(USART1, txBuffer[txIndex++]); } else { // 发送完成,关闭发送中断(防止一直进入中断) USART_ITConfig(USART1, USART_IT_TXE, DISABLE); // 如果需要,可以在这里调用发送完成回调函数 } } } ``` ### 注意事项 1. **波特率计算**:标准库中波特率的计算通常由库函数内部完成,但需确保系统钟配置正确(例如,如果使用外部晶振,需要在代码中正确配置系统钟)。 2. **错误处理**:在中断方式中,还可以处理其他中断标志(如错误中断)以增强鲁棒性。 3. **数据缓冲**:中断发送,需要确保发送缓冲区在数据发送完成前被修改。可以使用双缓冲区或DMA来避免数据冲突。 4. **资源冲突**:在中断发送过程中,如果主程序也要发送数据,需要等待前一次发送完成或使用队列缓冲。 ### 相关问题 1. 如何使用标准库配置USART的接收功能(轮询中断)? 2. 在标准库中,如何实现USART的DMA发送? 3. USART通信中常见的错误标志有哪些?如何处理? 4. 如何利用printf重定向到USART输出调试信息? 通过上述方法,您可以使用STM32标准外设库实现USART的数据发送。如有具体硬件型号或特殊需求,请提供更多信息。 </think> ### 使用STM32标准库实现USART数据发送的方法 以下是基于STM32标准外设库(Standard Peripheral Library)的USART数据发送完整实现流程,包含轮询中断两种方式: #### 步骤1: USART初始化配置 ```c #include "stm32f10x.h" #include "stm32f10x_usart.h" void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 使能钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置TX引脚(PA9)为复用推挽输出 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); // 3. USART参数配置 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_Tx; // 仅发送模式 // 4. 应用配置 USART_Init(USART1, &USART_InitStructure); // 5. 使能USART USART_Cmd(USART1, ENABLE); } ``` #### 步骤2: 轮询方式发送数据(阻塞式) ```c // 发送单个字符 void USART1_SendChar(uint8_t ch) { // 等待发送数据寄存器为空 while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 写入数据寄存器 USART_SendData(USART1, ch); // 可选:等待发送完成 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); } // 发送字符串 void USART1_SendString(char *str) { while(*str) { USART1_SendChar(*str++); } } // 发送数据数组 void USART1_SendBuffer(uint8_t *buffer, uint16_t len) { for(uint16_t i = 0; i < len; i++) { USART1_SendChar(buffer[i]); } } ``` #### 步骤3: 中断方式发送数据(非阻塞) ```c // 全局变量 uint8_t txBuffer[100]; // 发送缓冲区 uint16_t txIdx = 0; // 当前发送位置 uint16_t txLen = 0; // 待发送数据长度 // 中断配置 void USART1_IT_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; // 配置USART发送中断 USART_ITConfig(USART1, USART_IT_TXE, ENABLE); // 配置NVIC NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } // 启动发送 void USART1_StartSend(uint8_t *data, uint16_t length) { // 复制数据到缓冲区 for(int i = 0; i < length; i++) { txBuffer[i] = data[i]; } txIdx = 0; txLen = length; // 触发首次发送 USART_SendData(USART1, txBuffer[txIdx++]); } // 中断服务函数 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) { // 仍有数据待发送 if(txIdx < txLen) { USART_SendData(USART1, txBuffer[txIdx++]); } // 所有数据发送完成 else { USART_ITConfig(USART1, USART_IT_TXE, DISABLE); // 关闭中断 // 可在此添加发送完成回调函数 } USART_ClearITPendingBit(USART1, USART_IT_TXE); } } ``` #### 使用示例 ```c int main(void) { // 初始化系统钟等... USART1_Config(); USART1_IT_Config(); // 如果使用中断方式 // 轮询方式发送 USART1_SendString("Hello via Polling!\r\n"); // 中断方式发送 uint8_t data[] = "Hello via Interrupt!\r\n"; USART1_StartSend(data, sizeof(data)-1); // -1排除结束符 while(1) { // 主循环 } } ``` ### 关键点说明 1. **状态标志检查**(轮询方式): - `USART_FLAG_TXE`:发送数据寄存器空(可写入新数据) - `USART_FLAG_TC`:发送完成(所有数据已传输) 2. **中断控制**: - 使能`USART_IT_TXE`中断实现连续发送 - 发送完成后需手动禁用中断 - 在中断服务函数中清除中断标志 3. **波特率计算**: 标准库使用以下公式计算波特率: $$ baud = \frac{f_{CLK}}{16 \times USARTDIV} $$ 其中$f_{CLK}$为外设钟频率,$USARTDIV$为分频值[^1]。 ### 注意事项 1. **钟配置**:确保系统APB总线钟已正确配置(参考`SystemInit()`函数) 2. **引脚复用**:检查芯片手册确认USART引脚复用功能 3. **缓冲区管理**:中断发送需确保数据缓冲区在发送期间被修改 4. **中断优先级**:实性要求高的场景需合理配置NVIC优先级 5. **错误处理**:建议添加对`USART_FLAG_ORE`(溢出错误)等状态的检查 如需使用DMA方式发送,需要配置: 1. DMA通道(通常USART1_TX对应DMA1_Channel4) 2. 设置传输方向为内存到外设 3. 配置循环/单次传输模式 4. 使能USART的DMA发送请求 ### 相关问题 1. 如何实现USART接收数据的轮询中断处理? 2. USART通信中出现数据错误应如何检测处理? 3. 在低功耗模式下如何优化USART通信? 4. 如何实现多USART接口的协同工作? 5. USART的硬件流控制(RTS/CTS)如何配置? [^1]: 发送过程包括初始化USART、使能发送、写入数据寄存器等待发送完成标志[^1] [^2]: 确保TX/RX引脚正确连接,波特率、数据位等参数匹配[^2]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值