使用 FreeRTOS 提供的队列(Queue)机制来实现数据的接收和发送
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
#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 使用了抢占式调度方式,因此在任务和中断中使用的队列必须具有线程安全性,否则可能会导致数据丢失或者死锁等问题。
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 函数来通知任务调度器进行任务切换,以确保多任务处理的正确性和稳定性。
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 中断服务程序中处理接收中断,其中涉及到了队列和线程安全相关的知识点。
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 中断服务程序中处理发送中断,其中涉及到了队列和线程安全相关的知识点。
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 并检测是否需要任务切换的重要函数。它可以确保所有任务得到合理的调度和执行,从而实现高效、稳定和可靠的多任务处理。