1. 在STM32F103RCT6 单片机上跑FreeRTOS 实时操作系统,使用串口USART1 通讯,发送 – 接收数据,实现上位机与下位机的通信
使用 FreeRTOS 提供的队列(Queue)机制来实现数据的接收和发送
2. USART1 配置:
TX - PA9
RX - PA10
波特率:9600
数据位:8bit
校验位:无
停止位:1bit
数据格式:
RX: 55 AA 06 00 06 31 02 24 01 FC 80
TX: 55 AA 06 00 06 32 01 24 01 B8 70
55 AA – 帧头
06 - 数据字节数,不包括帧头,不包括校验位
00 06 – 模块
31 02 24-- 数据方向:从上位机(安卓LCD显示屏)到下位机(STM32)
32 01 24-- 数据方向:从下位机(STM32) 到上位机(安卓LCD显示屏)
01 - payload 要发送的数据具体内容
FC 80 / B8 70 – CRC 16bit 校验方法计算出来的,用06 00 06 31 02 24 01 使用CRC计算器可以计算出来 FC 80
CRC在线计算网址:
http://www.ip33.com/crc.html
3. 实现代码:
#include "stm32f10x.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" #define USART1_BAUDRATE 9600 #define USART1_TX_PIN GPIO_Pin_9 #define USART1_RX_PIN GPIO_Pin_10 #define USART1_GPIO GPIOA #define USART1_GPIO_CLK RCC_APB2Periph_GPIOA #define USART1_CLK RCC_APB2Periph_USART1 #define MODBUS_SLAVE_ADDR 0x01 #define RX_BUF_SIZE 15 #define TX_BUF_SIZE 15 static QueueHandle_t rx_queue; static QueueHandle_t tx_queue; static TaskHandle_t task_handle; void USART1_Init() { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // Enable clocks RCC_APB2PeriphClockCmd(USART1_CLK | USART1_GPIO_CLK, ENABLE); // Configure USART1 pins GPIO_InitStructure.GPIO_Pin = USART1_TX_PIN | USART1_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(USART1_GPIO, &GPIO_InitStructure); // Configure USART1 USART_InitStructure.USART_BaudRate = USART1_BAUDRATE; 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_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // Enable USART1 interrupts USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // Enable USART1 USART_Cmd(USART1, ENABLE); // Initialize queues rx_queue = xQueueCreate(RX_BUF_SIZE, sizeof(uint8_t)); tx_queue = xQueueCreate(TX_BUF_SIZE, sizeof(uint8_t)); // Create task for handling USART1 data xTaskCreate(USART1_Task, "USART1 Task", 1024, NULL, 1, &task_handle); } void USART1_Task(void *pvParameters) { while (1) { // Wait for data to be received uint8_t data; while (xQueueReceive(rx_queue, &data, portMAX_DELAY) == pdFALSE); // Process received data here // ... // Send response data uint8_t resp_data[] = {MODBUS_SLAVE_ADDR, /* response data */}; uint16_t resp_len = sizeof(resp_data) / sizeof(uint8_t); xQueueSend(tx_queue, resp_data, resp_len * sizeof(uint8_t)); } } void USART1_IRQHandler(void) { portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); xQueueSendFromISR(rx_queue, &data, &xHigherPriorityTaskWoken); } if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) { uint8_t data; if (xQueueReceiveFromISR(tx_queue, &data, &xHigherPriorityTaskWoken) == pdTRUE) { USART_SendData(USART1, data); } else { USART_ITConfig(USART1, USART_IT_TXE, DISABLE); } } portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } int main(void) { // Initialize USART1 USART1_Init(); // Start the scheduler vTaskStartScheduler(); }
在代码中,首先使用 USART1_Init 函数初始化 USART1,并使用 FreeRTOS 提供的 xQueueCreate 函数创建两个队列,一个用于接收数据,一个用于发送数据。然后使用 xTaskCreate 函数创建一个任务(USART1_Task),用于处理 USART1 数据。在任务中,使用 xQueueReceive 函数不断等待接收数据,并使用 xQueueSend 函数发送响应数据。在 USART1_IRQHandler 中,使用 xQueueSendFromISR 和 xQueueReceiveFromISR 函数将接收到的数据和需要发送的数据加入相应的队列中,并启用或禁用 USART1 的 TXE 中断来控制数据的发送。
需要注意的是,在任务和中断中使用的队列必须定义为全局变量,以避免在栈上分配空间时出现问题。另外,由于 FreeRTOS 使用了抢占式调度方式,因此在任务和中断中使用的队列必须具有线程安全性,否则可能会导致数据丢失或者死锁等问题。
其中:
1.
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
portBASE_TYPE 是 FreeRTOS 中定义的一个数据类型,用于表示任务调度器是否需要进行任务切换。在 FreeRTOS 中,任务调度器采用抢占式策略来决定下一次执行哪个任务。当某个任务需要让出 CPU 时间时,会向任务调度器发送一个任务切换请求,请求调度器立即切换到较高优先级的任务。
在 ISR 中使用 portBASE_TYPE 变量是为了确保中断服务程序能够安全地与任务调度器进行交互,以避免数据竞争和死锁等问题。在 ISR 中声明和初始化一个名为 xHigherPriorityTaskWoken 的变量,用于指示是否需要通知任务调度器进行任务切换。如果变量被设置为 pdTRUE,则表明当前中断服务程序结束后需要调用 portEND_SWITCHING_ISR 函数以切换到更高优先级的任务;如果变量被设置为 pdFALSE,则表明系统无需进行任务切换,可以继续执行当前任务。
当在 ISR 中完成队列操作或其他可能导致任务切换的操作时,应该及时更新 xHigherPriorityTaskWoken 变量的值,以确保任务调度器能够及时响应任务切换请求。同时,在结束 ISR 之前,也应该调用 portEND_SWITCHING_ISR 函数来通知任务调度器进行任务切换,以确保多任务处理的正确性和稳定性。
2.
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART1); xQueueSendFromISR(rx_queue, &data, &xHigherPriorityTaskWoken); }
这段代码是在 USART1 的中断服务程序(IRQHandler)中处理 USART1 的接收中断(USART_IT_RXNE)的。
当 USART1 接收到新的数据时,会触发 USART1 的接收中断。在中断服务程序中,我们首先检查 RXNE(RX not empty)标志位是否被置位,如果被置位,则表明有新的数据已经接收到了。接着,我们调用 USART_ReceiveData 函数从数据缓冲区寄存器中读取一个字节的数据,并将其放入接收队列(rx_queue)中。
需要注意的是,在中断服务程序中,我们需要使用 xQueueSendFromISR 函数来往队列中发送数据,而不能使用常规的 xQueueSend 函数。这是因为在中断服务程序中调用队列操作函数时,需要确保线程安全性,以避免数据竞争和死锁等问题。同时,我们还需声明和初始化一个名为 xHigherPriorityTaskWoken 的变量,用来指示是否需要在中断服务程序结束后调用 portEND_SWITCHING_ISR 函数以切换到更高优先级的任务。
总之,这段代码用于在 USART1 中断服务程序中处理接收中断,其中涉及到了队列和线程安全相关的知识点。
3.
if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET) { uint8_t data; if (xQueueReceiveFromISR(tx_queue, &data, &xHigherPriorityTaskWoken) == pdTRUE) { USART_SendData(USART1, data); } else { USART_ITConfig(USART1, USART_IT_TXE, DISABLE); } }
这段代码是在 USART1 的中断服务程序(IRQHandler)中处理 USART1 的发送中断(USART_IT_TXE)的。
当 USART1 发送数据缓冲区寄存器空(TXE=1)时,会触发 USART1 的发送中断。在中断服务程序中,我们首先检查 TXE 标志位是否被设置,如果被设置,则表明数据缓冲区寄存器已经准备好接受新的数据进行发送。
接着,我们尝试从发送队列(tx_queue)中取出一个字节的数据,并使用 USART_SendData 函数将其发送出去。如果队列中没有数据需要发送,则需要禁用 USART1 的 TXE 中断,以避免不必要的中断响应。
需要注意的是,在中断服务程序中,我们需要使用 xQueueReceiveFromISR 函数来从队列中获取数据,而不能使用常规的 xQueueReceive 函数。这是因为在中断服务程序中调用队列操作函数时,需要确保线程安全性,以避免数据竞争和死锁等问题。同时,我们还需声明和初始化一个名为 xHigherPriorityTaskWoken 的变量,用来指示是否需要在中断服务程序结束后调用 portEND_SWITCHING_ISR 函数以切换到更高优先级的任务。
总之,这段代码用于在 USART1 中断服务程序中处理发送中断,其中涉及到了队列和线程安全相关的知识点。
4.
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
portEND_SWITCHING_ISR 是 FreeRTOS 提供的一个函数,用于结束 ISR 并检测是否需要任务切换。在中断服务程序结束时,我们需要根据变量 xHigherPriorityTaskWoken 的值来决定是否进行任务切换。
如果 xHigherPriorityTaskWoken 被设置为 pdTRUE,则表明当前中断服务程序结束后需要调用 portEND_SWITCHING_ISR 函数以切换到更高优先级的任务;如果 xHigherPriorityTaskWoken 被设置为 pdFALSE,则表明系统无需进行任务切换,可以继续执行当前任务。
当需要进行任务切换时,我们调用 portEND_SWITCHING_ISR 函数将控制权交回给任务调度器,并让调度器立即执行高优先级任务。具体来说,portEND_SWITCHING_ISR 函数会使用 pendSV 任务向处理器发送一个软件中断信号,触发任务切换流程。在此过程中,调度器会检查所有任务的状态,并根据任务的优先级、时间片和阻塞状态等因素来决定下一次执行哪个任务。
总之,portEND_SWITCHING_ISR 函数是在中断服务程序中用于结束 ISR 并检测是否需要任务切换的重要函数。它可以确保所有任务得到合理的调度和执行,从而实现高效、稳定和可靠的多任务处理。