MYZR RZ/V2H EK320 CM33 Freertos实战入门


本文档记录了如何基于官方提供的工程便捷地对EK320开发板的cm33核进行Freertos开发。本博客的前置博客为“MYZR RZ/V2H EK320 CM33核配置UART串口并实现与外设通信”和“MYZR RZ/V2H EK320 CM33核配置GPT并产生PWM波”,请在学习本篇博客前完成前两篇博客的学习。

1. 了解Freertos

不了解Freertos的同学可以先通过下方视频链接简要的了解一下Freertos究竟是什么,我们为什么需要使用Freertos,以及它的便捷之处:

https://www.bilibili.com/video/BV1mhWqzFEzn

2. 配置PWM和UART线程

不同于之前两篇博客我们直接在main_task_entry中使用GPT和UART。在本博客中,我们新建两个任务来编写pwm和uart的代码,让两个需求分别由两个任务负责,这样我们就不需要在main_task_entry中编写繁杂的代码了。

进入e2 studio,选中项目,点击上方任务栏的项目->Open fsp Configuration,打开我们的fsp配置界面。在左侧Threads窗口点击New Thread,位置如下图:

新建完成后我们可以选中新建的线程,点击下方的属性可以看到线程的一些基本信息,我们可以对线程以及待会儿会生成的文件名进行修改,比如这里博主设置为pwm_thread,栈大小设置为1024bytes:

然后为我们的线程添加GPT模块,如果你完成了前两篇博客,那么在我们的HAL/Common这个线程下会有我们的gpt和uart模块,点击HAL/Common线程,在右边Stacks窗口选中gpt模块,按下快捷键ctrl+x,再选中我们的pwm_thread线程,点击右边的Stacks窗口,再按下快捷键ctrl+v,即可把模块搬移过来,如下图:

按照同样的方法,我们也可以对uart_thread完成设置。

配置完成后我们点击右上角的Generate Project Content,完成代码生成。

3. 编写uart_thread代码

回到资源管理器界面,打开src文件夹我们可以看到其中多了一个uart_thread_entry.c的文件,这个文件就相当于是我们这个线程的main.c。打开之后我们发现定义了一个函数void uart_thread_entry(void *pvParameters),这就相当于这个线程的main函数。右键include的文件uart_thread.h,打开声明可以看到在这个头文件中已经包含了我们需要使用的uart和Freertos相关的头文件,所以我们不需要再包含额外的头文件,非常的方便。

回到uart_thread_entry.c文件。我们的需求就是电脑通过串口连接开发板,通过串口助手发送一个字符或者数字达到控制舵机启停的效果。避免uart_thread在主循环中反复监看串口,我们这还是使用中断回调来接收串口信息。如果没有开启终端回调,可以参考上一篇博客。

void uart_thread_entry(void *pvParameters)函数前添加以下代码:

void user_uart_callback(uart_callback_args_t *p_args)
{
    // 如果硬件接收到了单个字符
    if (p_args->event == UART_EVENT_RX_CHAR)
    {
        g_rx_cmd = (uint8_t)p_args->data; // 把字符存起来
        flag = 1;
    }
}

这个回调函数用来接收发来的数据并将标志位置1。紧接着我们编写void uart_thread_entry(void *pvParameters)函数:

void uart_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    /* 2. 初始化 UART */
    R_SCI_B_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);

    while (1)
    {
        /* 3. 不断检查是否有新指令到来 */
        if (flag>=1)
        {
            static uint8_t test_char;
            test_char = g_rx_cmd;
            R_SCI_B_UART_Write(&g_uart0_ctrl, &test_char, 1);
            vTaskDelay(pdMS_TO_TICKS(10));
            flag = 0;
        }

        /* 4. 将cpu让出
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

标志位置1以后在主循环中将数据回传,查看串口是否正常工作。

为了完成uart_thread和pwm_thread之间的通信,我们再次前往dsp配置界面,在Threads窗口下方还有一个Objects,拖拽开点击右上角的New Object,选择Binary Semaphore,新建好后可以给我们的信号量取一个名字:

点击右上方绿箭头生成代码后回到void uart_thread_entry(void *pvParameters)函数,修改代码:

void uart_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    /* 2. 初始化 UART */
    R_SCI_B_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);

    while (1)
    {
        /* 3. 不断检查是否有新指令到来 */
        if (flag>=1)
        {
            static uint8_t test_char;
            test_char = g_rx_cmd;
            R_SCI_B_UART_Write(&g_uart0_ctrl, &test_char, 1);
            vTaskDelay(pdMS_TO_TICKS(10));
            flag = 0;
            xSemaphoreGive(uart_pwm);
        }

        /* 4. 将cpu让出
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

多出的函数意思是将信号量给give,当另一个task收到了give这个信号就可以返回一个值,通过返回值我们可以完成状态设置。

4. 编写pwm_thread代码

前往pwm_thread_entry.c文件,编写我们的主循环代码:

void pwm_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    uint8_t is_running = 0;
    uint32_t duty_0;
    uint32_t duty_180;
    //定义一些变量
    R_GPT_Open(&g_timer0_ctrl, &g_timer0_cfg);
    timer_info_t info;
    R_GPT_InfoGet(&g_timer0_ctrl, &info);
    uint32_t period= info.period_counts;

    // 计算好 0° 和 180° 的占空比边界
    duty_0   = (period * 25) / 1000;  // 0.5ms
    duty_180 = (period * 125) / 1000; // 2.5ms
    while (1)
    {
        if (xSemaphoreTake(uart_pwm, portMAX_DELAY) == pdPASS)
//如果没有收到信号量give那就一直阻塞,直到收到give
            {
                if(is_running==0)
                {
                    vTaskDelay(pdMS_TO_TICKS(10));
                    is_running=1;
                }
                else
                {
                    vTaskDelay(pdMS_TO_TICKS(10));
                    is_running=0;
                }

                if (is_running) {
                    R_GPT_Start(&g_timer0_ctrl);//启动gpt通道
                } else {
                    R_GPT_Stop(&g_timer0_ctrl);
                }
            }

            // 2. 如果开启了,就进入往返运动
            while (is_running)
            {
                // 0° -> 180°
                for (uint32_t i = duty_0; i <= duty_180; i += 800)
                {
                    R_GPT_DutyCycleSet(&g_timer0_ctrl, i, GPT_IO_PIN_GTIOCB);
                    vTaskDelay(pdMS_TO_TICKS(10));
//注意这里一定要有延时,否则cpu一直被当前线程占用,uart线程就无法接收和处理中断了
                    if (xSemaphoreTake(uart_pwm, 0) == pdPASS) {
                        is_running = 0; // 收到信号,立即关掉状态
                        R_GPT_Stop(&g_timer0_ctrl);
                        break; // 跳出 for 循环
                    }
                }
                // 在运动中检查:是否有新的信号进来要停止舵机

                if (!is_running) break; // 跳出内部 while(is_running)

                // 180° -> 0°
                for (uint32_t i = duty_180; i >= duty_0; i -= 800)
                {
                    R_GPT_DutyCycleSet(&g_timer0_ctrl, i, GPT_IO_PIN_GTIOCB);
                    vTaskDelay(pdMS_TO_TICKS(10));
                    if (xSemaphoreTake(uart_pwm, 0) == pdPASS) 
                    {
                        is_running = 0;
                        R_GPT_Stop(&g_timer0_ctrl);
                        break;
                    }
                }
            }
            vTaskDelay(pdMS_TO_TICKS(10));
    }
}

这里主要就是通过xSemaphoreTake函数来获取信号量的状态来对舵机的状态进行控制,其中一定尤其注意延时的应用,否则其他线程容易卡死,也可以设置不同的线程优先级,可以自行探索。在我们控制舵机的同时也可以在linux命令行中继续调用官方的rpmsg_sample_client示例。如此,我们就达到了三个任务并发进行。下面附上演示效果:

在开发过程中博主也发现当串口设置为开漏输出无上下拉时直接接串口模块也可以使用,可能是因为串口模块自带上拉。

以上便是本文档的全部内容。主要对Freertos在EK320开发板上的运用做了一个初步探索。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注