串口与中断关系详解及使用方法
串口(Serial Port)与中断(Interrupt) 的关系
1. 数据传输的异步性: 串口通信是一种异步通信方式,发送端和接收端的时钟不同步。这就意味着当有数据到达时,处理器不一定会立即处理它们,需要在适当的时候对数据进行处理。
2. 轮询 vs 中断: 为了检测串口是否有数据到达,你可以使用两种主要的方法:轮询和中断。在轮询方式中,处理器会定期地检查串口状态以确定是否有新的数据到达。但这种方式可能会浪费处理器资源,因为处理器可能会在没有数据到达时也不停地检查串口状态。而中断则允许处理器在数据到达时立即被通知,从而可以及时处理数据而不需要不断地轮询。
3. 中断处理: 当串口接收到新数据时,它会产生一个中断请求。处理器在执行当前任务的同时,会检测到这个中断请求,然后跳转到预先设定好的中断服务函数(ISR),这个函数会处理串口接收到的数据。这样就实现了在数据到达时立即处理数据的目的,而不需要不断地轮询串口状态。
4. 中断优先级: 在一些情况下,系统中可能会有多个中断源,因此需要根据其重要性设置中断的优先级。对于串口通信来说,如果串口数据接收的实时性很重要,你可能会将串口中断的优先级设置得比其他中断更高,以确保及时处理接收到的数据。
因此,串口通信中使用中断可以提高系统的响应速度和效率,特别是在需要处理实时数据的情况下。
也会是说,使用串口要有中断服务函数
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
// 串口接收到了数据,可以进行相应的处理
}
比如,下面这个函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
{
USART_ClearFlag(USART1, USART_FLAG_RXNE);
}
}
这段代码是针对STM32微控制器中USART1串口的中断处理函数。
– `USART1_IRQHandler(void)`: 这是一个中断处理函数,当USART1(串口1)接收或发送数据时触发中断,处理函数便会被调用。
– `if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)`: 这行代码检查串口1是否接收到了新的数据。(使用串口,一定会有这行代码)
`USART_GetITStatus()`函数用于检查特定的USART中断标志位,此处使用的是`USART_IT_RXNE`,表示接收寄存器非空中断(Receive Data register Not Empty)。如果接收寄存器非空(即接收到了新的数据),则条件成立,执行相应的操作。
– `USART_ClearFlag(USART1, USART_FLAG_RXNE)`: 这行代码用于清除接收中断标志位,以便能够再次检测到新的接收数据。(这行代码也会有)
清除标志位后,可以进行接收数据的处理。
这个函数的作用是处理USART1串口接收中断。在实际应用中,你可以在这个函数中添加相应的处理代码,比如读取接收到的数据并进行处理,或者发送数据等操作。(当串口收到信息,就会去执行其中的代码)
USART串口在嵌入式系统中通常用于与外部设备进行通信,如与电脑、传感器、其他微控制器等进行数据交换。通过中断机制,可以在数据到达时及时进行处理,而不需要轮询地检查串口状态。
再比如,下面代码,在中断服务函数中,实现–如果串口收到结束标志':'时,就把数据存储到某位置
void USART1_IRQHandler(void)
{
//若是非空,则返回值为1,与RESET(0)判断,不相等则判断为真
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
/* DR读取接受到的数据*/
buffer[rx_count++] = USART_ReceiveData(USART1); //先赋值再加
if(buffer[rx_count-1] == ':') //判断是否接收到结束标志
{
for(rx_i=0; rx_i<rx_count-1; rx_i++)//为什么要多弄个循环和数据存储数组?因为把接收到的数据存储,而不要显示结束标志在新的数据存储数组
{
rx_buffer[rx_i] = buffer[rx_i]; //将数据存储在rx_buffer数组中
}
rx_flag = 1; //rx_flag = 1表示接收字符串完成
rx_count = 0;
memset(buffer, 0, sizeof(buffer));//清空存储数据的数组buffer[]
}
//判断为真后,为下次中断做准备,则需要对中断的标志清零
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
上面的函数清除接收标志使用了
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
与上面第一个函数的清除标志不同,根据下面的函数说明可知,这个函数也能作为清除标志。
/**
* @brief Clears the USARTx's interrupt pending bits.
* @param USARTx: where x can be 1, 2, 3, 4, 5, 6, 7 or 8 to select the USART or
* UART peripheral.
* @param USART_IT: specifies the interrupt pending bit to clear.
* This parameter can be one of the following values:
* @arg USART_IT_CTS: CTS change interrupt (not available for UART4 and UART5)
* @arg USART_IT_LBD: LIN Break detection interrupt
* @arg USART_IT_TC: Transmission complete interrupt.
* @arg USART_IT_RXNE: Receive Data register not empty interrupt.
*
* @note PE (Parity error), FE (Framing error), NE (Noise error), ORE (OverRun
* error) and IDLE (Idle line detected) pending bits are cleared by
* software sequence: a read operation to USART_SR register
* (USART_GetITStatus()) followed by a read operation to USART_DR register
* (USART_ReceiveData()).
* @note RXNE pending bit can be also cleared by a read to the USART_DR register
* (USART_ReceiveData()).
* @note TC pending bit can be also cleared by software sequence: a read
* operation to USART_SR register (USART_GetITStatus()) followed by a write
* operation to USART_DR register (USART_SendData()).
* @note TXE pending bit is cleared only by a write to the USART_DR register
* (USART_SendData()).
*
* @retval None
*/
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));/* The CTS interrupt is not available for UART4 and UART5 */
if (USART_IT == USART_IT_CTS)
{
assert_param(IS_USART_1236_PERIPH(USARTx));
}
bitpos = USART_IT >> 0x08;
itmask = ((uint16_t)0x01 << (uint16_t)bitpos);
USARTx->SR = (uint16_t)~itmask;
}
在上两篇的按键中断(exti.c),也有用到中断服务函数
//6、 编写中断服务函数。 这个函数在startup_stm32f40_41.s中查阅
//不需要自己调用,中断响应后,CPU自动去执行的函数
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4) == SET) //判断中断线0是否置1
{
GPIO_ToggleBits(GPIOA, GPIO_Pin_6);//置1,灯亮
}
//7、清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line4);
}
当按键触发了中断之后,就会去执行这个函数里的代码,以实现功能。
使用串口,是否可以没有中断服务函数?
在许多嵌入式系统中,串口通信通常需要中断服务函数来处理接收和发送数据。然而,并不是所有的串口通信都必须使用中断服务函数,而这在某些情况下取决于具体的硬件和软件设计。
1. **Polling(轮询)方式**:除了使用中断服务函数之外,另一种处理串口通信的方式是轮询。在轮询方式中,程序会周期性地查询串口接收缓冲区是否有新的数据到达,并发送数据时也会检查发送缓冲区是否准备好。尽管轮询方式会消耗一定的处理器资源,但对于某些简单的应用或者资源受限的系统而言,这是一种简单且有效的处理方式。
2. **硬件辅助**:有些串口控制器(如一些型号的UART或USART)内部具有缓冲区和状态寄存器,在数据到达或发送完成时会产生相应的标志位。这些标志位可以由主程序中的轮询或定时器中断进行检查和处理,而不需要专门的串口中断服务函数。
对于使用ESP8266连接串口的情况,ESP8266模块通常使用UART与微控制器或其他设备进行串口通信。ESP8266的UART可以通过轮询方式或者检查标志位的方式进行数据的接收和发送,而不一定需要使用中断服务函数。这主要取决于你的应用需求、硬件平台以及软件设计。
我遇到的问题
使用esp8266连接串口,并没有使用中断服务函数,这也可以。但因为没有连接好esp8266,导致其他外部设备要用到的函数代码也没有执行。
主程序`main()`中包含了一个无限循环`while(1)`,在循环内部有一系列的操作。在这种情况下,只要程序开始执行`main()`函数,它就会进入这个无限循环,直到出现某种终止条件才会退出。
在你描述的情况下,如果ESP8266没有连接好(可能是硬件连接问题或者初始化失败等原因),导致了在 `ESP8266_Init()` 函数中的初始化不成功,而且 `ESP8266_SendCmd()` 函数在检查连接状态时也一直返回失败,那么程序就会一直停留在 `while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT")) DelayXms(500);` 这个循环中。因为 `ESP8266_SendCmd()` 函数返回失败,导致 `while` 循环条件一直为真,程序就会一直在这个循环中等待,而不会继续执行后面的代码。
解决这个问题的方法是检查 ESP8266 模块的连接情况和初始化是否正确,并确保 `ESP8266_Init()` 和 `ESP8266_SendCmd()` 函数正常工作。另外,建议在程序中添加一些错误处理的逻辑,例如设置最大重试次数或者超时计数器,以防止程序陷入无限循环。
作者:OneCatFish_