基于FreeRTOS的USART1串口通信在STM32F103RCT6上的实现

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 并检测是否需要任务切换的重要函数。它可以确保所有任务得到合理的调度和执行,从而实现高效、稳定和可靠的多任务处理。

物联沃分享整理
物联沃-IOTWORD物联网 » 基于FreeRTOS的USART1串口通信在STM32F103RCT6上的实现

发表评论