RT-Thread 5.0.2基于STM32F407ZG处理器使用消息队列在线程之间传递数据

1、概述

        消息队列是线程间通信的一个重要手段之一,本人目前正在开发一款8轴的步进电机驱动器,已经完成了底层的步进电机驱动、RS485接口驱动、CAN总线驱动、USB虚拟CDC串口以及FreeMODBUS协议移植等各方面的工作,后续将进入到APP层面的软件开发。在开发APP的过程中,各个模块之间免不了需要传递各种数据,故而打算采用消息队列的方式来进行各模块之间进行数据传递。     

        下面是创建一个消息队列,并在二个线程之间传递消息的完整例程,在本例中,我所使用的处理器是STM32F407ZGT6。

2、消息队列创建过程记录   

        第1步,创建一个基于STM32F407ZG处理器的软件工程。

        启动RT-Thread Studio。

        点击File->New->RT-Thread Project进入项目配置界面。

        根据实际所使用的板卡配置软件工程。

         在软件工程配置界面中,设置好项目名称、RT-Thread SDK版本、芯片参数、控制字台使用的串口信息、调试器类型和数所使用的调试端口等信息,然后点击Finish返回。

                

                软件工程创建中,稍等片刻。

        软件工程创建完成,点击查看软件工程目录。

        在图2-1-7中可见到完整的软件工程目录。

        第2步,编译软件工程,确认软件工程是否可以被编译。

        点击选中软件工程,按下CTRL+B快捷键进行编译。

        编译出现报错信息,此为软件版本升级所带来的BUG,为缺少了RT_WEAK宏定义所导致,需自己手工添加一个宏定义。

        打开drinver->board.h文件,添加一条宏定义:

#define RT_WEAK             rt_weak

        再次编译软件工程。

        软件工程已经编译成功。

        第3步,下载代码到目标板中运行。

        点击图标进入Debug界面。

        代码下载成功,已经进入Debug界面,程序已经成功运行到了main()函数的入口断点处。

        点击图标全速运行代码。

        PC端启动Puttty工具软件,选中目标板的串口1在PC端虚拟的USB串口号,点击Open按钮进入下一步界面。

        在出现的界面中,可以见到目标板发送的LOG信息。

        至此软件工程已经可以正常工作,可以做下一步的编程测试了。

        第4步,编写消息队列测试代码。

        在application目录下创建一个新的文件usr_mq.c。

        输入以下的代码:

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-01-07     hubo       the first version
 */
/*
 * 程序清单:消息队列例程
 *
 * 这个程序会创建2个动态线程,一个线程会从消息队列中收取消息;一个线程会定时发消息
 */
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5
/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];

typedef struct __TypeDefMessage{
    rt_uint8_t msg_id;
    rt_uint16_t msg_info[16];
}TypeDefMessage;

rt_align(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程1入口函数 */
static void thread1_entry(void *parameter)
{
    rt_ssize_t rev_len;
    TypeDefMessage msg;
    rt_uint8_t cnt = 0;
    rt_uint16_t i;
    while (1)
    {
        /* 从消息队列中接收消息 , 并返回接收消息的长度*/
        rev_len = rt_mq_recv(&mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
        if ( rev_len == sizeof(msg) )
        {
            rt_kprintf("thread1: received msg_id=%d\r\n", msg.msg_id);
            for( i = 0 ; i < 16 ; i ++ )
            {
                rt_kprintf("thread1: received msg_info[%d]=%d\r\n" ,i,msg.msg_info[i]);
            }

            if (cnt == 19)
            {
                break;
            }
        }
        /* 延时50ms */
        cnt++;
        rt_thread_mdelay(300);
    }
    rt_kprintf("thread1: detach mq \r\n");
    rt_mq_detach(&mq);
}

rt_align(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *parameter)
{
    int result, i;
    TypeDefMessage msg;
    rt_uint8_t cnt = 0;
    msg.msg_id = 50;
    for( i = 0 ; i < 16 ; i ++ )
    {
        msg.msg_info[i] = i;
    }
    while (1)
    {
        if (cnt >= 20)/* 发送20次消息之后退出 */
        {
            rt_kprintf("message queue stop send, thread2 quit!\r\n");
            break;
        }
        else
        {
            /* 发送消息到消息队列中 */
            result = rt_mq_send(&mq, &msg, sizeof(msg));
            if (result != RT_EOK)
            {
                rt_kprintf("rt_mq_send ERR\n");
            }
            rt_kprintf("thread2: sent messages:%d\r\n", msg.msg_id);
        }
        msg.msg_id++;
        cnt++;
        /* 延时5ms */
        rt_thread_mdelay(500);
    }
}
/* 消息队列示例的初始化 */
int msgq_sample(void)
{
    rt_err_t result;
    /* 初始化消息队列 */
    result = rt_mq_init(&mq, "mqt", &msg_pool[0],   /* 内存池指向msg_pool */
                        sizeof(TypeDefMessage),     /* 每个消息的大小*/
                        sizeof(msg_pool),           /* 内存池的大小是msg_pool的大小 */
                        RT_IPC_FLAG_FIFO);          /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
    if (result != RT_EOK)
    {
        rt_kprintf("init message queue failed.\n");
        return -1;
    }

    result = rt_thread_init(&thread1, "thread1", thread1_entry,
                            RT_NULL, &thread1_stack[0], sizeof(thread1_stack),
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (result != RT_EOK)
    {
         rt_kprintf("Init thread1 failed.\n");
         return -1;
    }

    result = rt_thread_startup(&thread1);
    if (result != RT_EOK)
    {
         rt_kprintf("Start thread1 failed.\n");
         return -1;
    }

    result = rt_thread_init(&thread2, "thread2", thread2_entry,
                            RT_NULL, &thread2_stack[0], sizeof(thread2_stack),
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (result != RT_EOK)
    {
         rt_kprintf("Init thread2 failed.\n");
         return -1;
    }

    result = rt_thread_startup(&thread2);
    if (result != RT_EOK)
    {
         rt_kprintf("Start thread2 failed.\n");
         return -1;
    }

    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(msgq_sample, msgq sample);

        编译下载运行一下,看看结果如何。

        程序运行结果符合预期。

        本次消息队列的格式为一个结构体,格式定义如下:

typedef struct __TypeDefMessage{
    rt_uint8_t msg_id;
    rt_uint16_t msg_info[16];
}TypeDefMessage;

        采用结构体形式的消息队列,可以定义任何形式的结构体,极大地方便了消息传递的灵活性。

3、程序调试中遇到的问题

(1)ALIGN宏编译报错

        在本次消息发送的实验的过程中,发现了以下的版本兼容问题:

        官方的网站的参考例程中使用了宏:

ALIGN(RT_ALIGN_SIZE)ALIGN(RT_ALIGN_SIZE)

         但在RT-Thread 5.0.2的SDK中,没有这个宏,需要使用下面这个宏替换:

rt_align(RT_ALIGN_SIZE)

(2)​​​​​​​rt_mq_recv函数返回值类型与老版本不同

        在老版本中,rt_mq_recv函数返回的值为RT_EOK。但在RT-Thread 5.0.2 SDK中的返回值为接收到的消息的长度值。

        如果使用老版本的消息发送示范代码参考代码在5.0.2中运行,将会导致程序无法按照预想的流程运行,这一点需要引起注意。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,需要在 `sdkconfig` 文件中启用 SPI 驱动程序。找到以下行并取消注释: ``` CONFIG_ESP32C3_SPI ``` 然后,需要通过 `spi_bus_initialize()` 函数初始化 SPI 总线。可以使用以下代码: ```c spi_bus_config_t bus_cfg = { .miso_io_num = PIN_NUM_MISO, .mosi_io_num = PIN_NUM_MOSI, .sclk_io_num = PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 0, }; spi_bus_initialize(VSPI_HOST, &bus_cfg, DMA_CHAN); ``` 其中,`PIN_NUM_MISO`,`PIN_NUM_MOSI`,和 `PIN_NUM_CLK` 是 MISO,MOSI 和 SCLK 的引脚号,`DMA_CHAN` 是 DMA 通道的编号,可以设置为 1。 接下来,需要通过 `spi_device_interface_config_t` 结构体配置 SPI 设备。可以使用以下代码: ```c spi_device_interface_config_t dev_cfg = { .command_bits = 0, .address_bits = 0, .dummy_bits = 0, .mode = 0, .duty_cycle_pos = 0, .cs_ena_pretrans = 0, .cs_ena_posttrans = 0, .clock_speed_hz = 1000000, .input_delay_ns = 0, .spics_io_num = PIN_NUM_CS, .flags = 0, .queue_size = 1, .pre_cb = NULL, .post_cb = NULL, }; spi_device_handle_t spi_handle; spi_bus_add_device(VSPI_HOST, &dev_cfg, &spi_handle); ``` 其中,`PIN_NUM_CS` 是片选引脚的引脚号,可以设置为任何 GPIO 引脚。 现在,可以使用 `spi_device_transmit()` 函数来传输数据。例如,要写入一个字节,可以使用以下代码: ```c uint8_t data = 0xAB; spi_transaction_t tx = { .length = 8, .tx_buffer = &data, }; spi_device_transmit(spi_handle, &tx); ``` 要读取一个字节,可以使用以下代码: ```c uint8_t data = 0; spi_transaction_t tx = { .length = 8, .rx_buffer = &data, }; spi_device_transmit(spi_handle, &tx); ``` 注意,在 `spi_transaction_t` 结构体中,如果 `tx_buffer` 不为 `NULL`,则将执行写操作;如果 `rx_buffer` 不为 `NULL`,则将执行读操作;如果两者都不为 `NULL`,则将执行读写操作。 需要注意的是,在使用 SPI 设备之前,必须对 SPI 总线进行初始化,并且在完成传输之后,必须调用 `spi_bus_remove_device()` 函数来删除设备。代码示例如下: ```c spi_bus_initialize(VSPI_HOST, &bus_cfg, DMA_CHAN); spi_device_interface_config_t dev_cfg = {...}; spi_device_handle_t spi_handle; spi_bus_add_device(VSPI_HOST, &dev_cfg, &spi_handle); uint8_t data = 0xAB; spi_transaction_t tx = {...}; spi_device_transmit(spi_handle, &tx); spi_bus_remove_device(spi_handle); spi_bus_free(VSPI_HOST); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值