5 Star 57 Fork 19

立创开发板/基于立创梁山派的21年电赛F题智能送药小车

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

基于立创梁山派的21年电赛F题智能送药小车

⚙️简介::全国产化器件制作的基于立创梁山派的21年电赛F题智能送药小车

  • 硬件部分:扩展板由电机驱动,国产姿态传感器及磁力计,蜂鸣器驱动,按键和药物检测,ADC电压采集,CAN芯片等组成。国产蓝牙模块实现双车通讯,K210实现数字识别和巡红线。
  • 软件部分:国产RT-Thread,PID控制电机实现位置环和速度环,姿态解算,K210数字识别和红线识别,定义串口通讯协议并DMA接收+ringbufer缓存处理,各数据交换使用发布订阅机制。
  • 教程部分请点这里

medical_car

简要视频:

  1. 赛题分析
  2. 电路设计
  3. 代码编写

简要文章:

  1. 赛题分析
  2. 电路设计

精简文档:

  1. 00_单个小车BOM表
  2. 01_组装文档
  3. 02_编译文档
  4. 03_软件工具与调试工具介绍
  5. 04_建立RT-Thread工程模板
  6. 05_Finsh控制台的使用
  7. 06_如何计算小车轮子转速
  8. 07_电机驱动-舵机-蜂鸣器PWM配置
  9. 08_直流减速电机PID速度环与位置环调试
  10. 09_国产IMU移植及姿态解算
  11. 10_小车角度环的调试与实现
  12. 11_K210更换固件-运行基础颜色识别例程
  13. 12_K210的KPU数字识别训练
  14. 13_K210功能实现代码讲解
  15. 14_立创梁山派与K210串口通信协议框架搭建
  16. 15_小车寻红线环的调试与实现
  17. 16_按键检测的消抖和实现
  18. 17_送药小车数据的发布与订阅
  19. 18_送药小车实现思路

一 目录结构

  • 0_STL:小车上的3D打印文件。
  • 1_Hardware:小车扩展板PCB以及底板PCB结构件。
  • 2_Code:代码部分,包含梁山派MDK工程和K210程序。
  • 3_Tool:常用软件工具。
  • 4_Docs:文档说明。

二 使用说明

了解过 RT-Thread ENV 开发工具

  1. 克隆本仓库到本地。
  2. 进入2_Code目录,使用 ENV 工具执行 pkgs --update下载缺失的软件包。
  3. 使用 ENV 工具执行 scons --target=mdk5更新MDK5工程文件。
  4. 打开2_Code目录下的project.uvprojx即可正常编译使用了。

从来没用过RT-Thread

  1. RT-Thread文档中心了解了解。
  2. 进入仓库的发行版,下载附件2_code.zip
  3. 解压后打开目录下的project.uvprojx即可正常编译使用了。

三 代码介绍

所有自己添加的文件都在applications目录下。

梁山派GD32端

RTOS选择并建立模板

​ 为了配合梁山派的全国产化,本次的RTOS选择国内优秀的RT-Thread,RT-Thread 是一款完全由国内团队开发维护的嵌入式实时操作系统(RTOS),具有完全的自主知识产权。经过 16 个年头的沉淀,伴随着物联网的兴起,它正演变成一个功能强大、组件丰富的物联网操作系统。

​ 它与其他RTOS最大的区别是他有一个软件包库,有许多开源的大佬分享自己做的软件包。如果你选用支持RT-Thread的芯片来开发项目,就可以使用它丰富的软件包。比如说想读取MPU6050的数据,直接在ENV工具中选择MUP6050的软件包,重新生成工程,运行例程就可以获得数据了。比如想做一个按键驱动,在软件包中搜索 按钮,就会有数个软件包供你选择,你可以专注于做应用,底层的大部分驱动都是可以复用的。

​ 梁山派目前已经适配基础版RT-Thread,链接.

​ 建议不要选择master分支进行开发,因为主分支处于一直开发的状态,可能会有意想不到的BUG。一般要选一个稳定的版本。我这里选用的RT-Thread-V4.1.1。

BSP底层初始化编写

​ 当前梁山派还没有适配RT-Thread的PWM,ENCODER等外设资源,所以需要我们自己进行初始化配置。

​ 主要有蜂鸣器PWM的配置,电机驱动PWM的配置,正交编码器的配置,舵机PWM的配置。

​ 以蜂鸣器初始化为例。

​ 首先要知道确切的系统时钟和定时器时钟频率。打开GD32参考手册的第四章节:复位和时钟单元(RCU)里面的图4-2.时钟树。再找到keil工程下面Drivers里面的board.c里面的SystemClock_Config。找到系统时钟之后找一下APB2和APB1的时钟频率。以蜂鸣器的PWM配置为例。可以看到BEEP_TIMER就是TIMER12,它挂在APB1时钟下,/* APB1 = AHB/4 /就是说APB1是60M,TIMER时钟由AHB时钟分频获得,它的频率可以等于CK_APBx、CK_APBx的两倍或CK_APBx 的四倍。详细信息请参考RCU_CFG1寄存器的TIMERSEL位,因为我们是给APB分频了的。所以定时器时钟等于 APB 时钟的两倍,所以这里的TIMER12时钟是60M2=120Mhz,在这里的30-1,要-1是因为这里的计数是从0开始的。接下来就是简单的除法计算了。//120Mhz/30=4000000Hz

这里用到了自动初始化:RT-Thread总共有六个初始化顺序

1 INIT_BOARD_EXPORT(fn)

非常早期的初始化,此时调度器还未启动

2 INIT_PREV_EXPORT(fn)

主要是用于纯软件的初始化、没有太多依赖的函数

3 INIT_DEVICE_EXPORT(fn)

外设驱动初始化相关,比如网卡设备

4 INIT_COMPONENT_EXPORT(fn)

组件初始化,比如文件系统或者 LWIP

5 INIT_ENV_EXPORT(fn)

系统环境初始化,比如挂载文件系统

6 INIT_APP_EXPORT(fn)

应用初始化,比如 GUI 应用

bsp_beep

这段代码是一个嵌入式系统中的示例程序,主要用于初始化和控制蜂鸣器(beep)的功能。

代码的主要功能如下:

  1. 导入头文件:包含了一些需要使用的头文件,包括stdio.h(标准输入输出)、rtthread.h(RT-Thread操作系统)、board.h(板级支持包)、gd32f4xx_libopt.h(GD32F4xx外设库)、bsp_beep.h(蜂鸣器驱动头文件)等。
  2. beep_pwm_gpio_init函数:该函数用于初始化蜂鸣器的GPIO引脚。具体操作包括使能蜂鸣器所在的GPIO时钟、配置GPIO引脚为复用功能、设置GPIO输出选项等。
  3. beep_timer_init函数:该函数用于初始化蜂鸣器的定时器(timer)。具体操作包括使能蜂鸣器定时器所在的时钟、重置定时器、配置定时器参数(如计数模式、时钟分频、计数方向、周期和预分频值等)、初始化定时器、配置定时器通道输出参数(如极性、输出使能、闲置状态等)、使能定时器等。
  4. beep_pwm_pulse_set函数:该函数用于设置蜂鸣器的脉冲宽度。具体操作是通过配置定时器通道的脉冲值来控制蜂鸣器的输出。
  5. beep函数:该函数用于控制蜂鸣器发出声音。具体操作是设置蜂鸣器的脉冲宽度为500,延时一段时间后再将脉冲宽度设置为0,实现蜂鸣器的开关控制。
  6. beep_test函数:该函数是一个用于测试的函数,用于通过命令行输入参数来设置蜂鸣器的脉冲宽度。具体操作是通过输入参数获取脉冲宽度值,然后调用beep_pwm_pulse_set函数设置蜂鸣器的脉冲宽度。
  7. beep_init函数:该函数用于初始化蜂鸣器。具体操作是调用beep_pwm_gpio_init函数和beep_timer_init函数来初始化蜂鸣器的GPIO引脚和定时器。
  8. INIT_BOARD_EXPORT(beep_init):这是一个用于在系统初始化阶段自动执行的宏,将beep_init函数注册为系统初始化函数,以便在系统启动时自动初始化蜂鸣器。

总体来说,这段代码主要是用于初始化和控制蜂鸣器的功能,包括初始化GPIO引脚和定时器,以及设置蜂鸣器的脉冲宽度来控制声音的输出。

bsp_mootor

  1. motor_pwm_gpio_init():用于初始化电机驱动的PWM控制引脚。该函数通过配置引脚的模式和复用功能,将引脚设置为PWM输出模式。
  2. motor_timer_init():用于初始化电机驱动的硬件定时器。该函数通过配置定时器的参数,包括时钟分频、计数方向、周期和占空比等,设置定时器为PWM模式。
  3. motor1_in1_pwm_pulse_set()motor1_in2_pwm_pulse_set()motor2_in1_pwm_pulse_set()motor2_in2_pwm_pulse_set():分别用于设置电机1和电机2的PWM脉宽值。这些函数通过配置定时器的通道和脉冲值,控制对应引脚的PWM输出。
  4. motor1_pwm_value_set()motor2_pwm_value_set():用于调节电机1和电机2的PWM值,即控制电机的速度。通过调整对应引脚的PWM脉宽值来实现速度调节。
  5. motor_test():用于通过控制台输入命令来临时改变PWM值驱动电机转动。根据输入的命令和参数,调用相应的函数来控制电机的运动。

此外,代码还包括了一些初始化函数和命令导出,以便在系统启动时进行电机的初始化和在控制台中使用相应的命令来控制电机。

总体来说,这段代码实现了电机驱动的初始化和控制,通过配置硬件定时器和PWM引脚,可以实现对电机速度的控制。

bsp_encoder

  1. 引用了一些头文件,并定义了一些宏和结构体,包括编码器状态结构体(encoder_state_t)和主题定义(encoder_m1_topic、encoder_m2_topic)。
  2. encoder_gpio_init函数中,对编码器的GPIO进行初始化,包括设置引脚的工作模式和复用功能。
  3. encoder_timer_init函数中,对编码器的定时器进行初始化。这些定时器用于计算编码器的计数值和测量速度。函数中分别初始化了两个编码器(M1和M2)的定时器,并配置了输入捕获通道和编码器模式。还启用了定时器中断,并将相应的中断处理函数注册到中断向量表中。
  4. encoder_count_timer_init函数用于初始化一个用于计数的定时器。
  5. M1_TIMER_IRQHANDLERM2_TIMER_IRQHANDLER是编码器定时器的中断处理函数。在中断处理函数中,根据定时器的计数值和重载值来判断编码器的溢出情况,并更新编码器的计数值。然后清除定时器的中断标志位。
  6. ENCODER_COUNT_TIMER_IRQHANDLER是计数定时器的中断处理函数。在该函数中,根据定时器的中断标志位进行编码器计数值的更新,并计算编码器的速度。最后,清除计数定时器的中断标志位。
  7. set_encoder_struct_to_default函数用于将编码器状态结构体初始化为默认值。
  8. encoder_topic_echo函数是一个用于打印编码器状态的回调函数。它通过调用mcn_copy_from_hub函数从主题中获取编码器状态,并打印相关信息。
  9. encoder_init函数是编码器的初始化函数。在该函数中,调用了前面提到的各个初始化函数,并将编码器状态结构体初始化为默认值。然后,通过调用mcn_advertise函数将回调函数注册为主题的广播函数,以便在接收到编码器状态更新时能够打印相关信息。

总体而言,这段代码完成了对编码器的引脚和定时器的初始化配置,并通过中断处理函数和回调函数实现了编码器状态的更新和打印。

bsp_servo

代码的主要功能是初始化舵机的GPIO和定时器,并提供了设置舵机脉冲宽度的函数。通过控制定时器的输出脉冲宽度,可以控制舵机的角度。

下面是代码的主要结构和功能解释:

  1. 首先,代码包含了一些头文件,包括stdio.h、rtthread.h、board.h等,这些头文件提供了所需的库函数和定义。
  2. 然后定义了两个静态函数:servo_pwm_gpio_init()servo_timer_init()。这些函数用于初始化舵机的GPIO和定时器。
  3. servo_pwm_gpio_init()函数中,首先使能了舵机的GPIO时钟,然后配置了GPIO的模式和复用功能。最后,设置了输出选项,包括输出类型、输出速度等。
  4. servo_timer_init()函数中,首先使能了舵机所使用的定时器的时钟。然后进行了定时器的初始化设置,包括计数方向、计数模式、计数周期、预分频等参数。接着,配置了定时器的通道输出模式和脉冲值,并设置了定时器的输出极性和空闲状态。最后,使能了定时器和高级定时器的主输出。
  5. servo1_pwm_pulse_set()servo2_pwm_pulse_set()函数分别用于设置舵机1和舵机2的脉冲宽度。这些函数通过调用timer_channel_output_pulse_value_config()函数来设置定时器的通道输出脉冲值,从而控制舵机的角度。
  6. servo_test()函数是一个用于测试舵机控制的命令函数。通过命令行输入参数来选择要控制的舵机和脉冲宽度,然后调用相应的设置函数来控制舵机。
  7. 最后,定义了一个servo_init()函数,用于初始化舵机控制。该函数调用了servo_pwm_gpio_init()servo_timer_init()函数来进行初始化操作。

整体而言,这段代码通过初始化GPIO和定时器来控制舵机的角度。通过调用相应的设置函数,可以根据输入的脉冲宽度来改变舵机的位置。

FINSH控制台调试使用

​ RT-Thread控制台是我非常喜欢的一个功能,他可以让你和嵌入式设备产生交互,可以用来调试和查看系统信息。就有点像平时windos的cmd命令或者linux的命令控制台。

​ 可以让你自己定义控制台命令,比如说你配置好了电机PWM,想在运行中改变PWM的值,通过串口传入参数就可以修改了,如下面的代码块所示:

/**
 -  @brief  电机的控制室命令,可以通过控制台输入命令来临时改变PWM值驱动电机转动
 -  @note   None
 -  @param  argc:发给当前函数 命令行 总的参数的个数,他的值永远>=1。
            argv: 是个字符串数组,用来存放指向字符串参数的指针数组,每一个元素指向一个以空格为分割的参数。
                -如argv[0]指向程序运行的函数名称。
                -如argv[1]指向解析出来的第一个参数,argv[2]指向再接下来的一个参数。
 -  @retval None
   */
static void motor_test(int argc, char**argv)
{
    int16_t pwm_value = 0;
    /* 检查输入的变量是否有两个 */
    if (argc < 3)
    {
        rt_kprintf("Please input'motor_test <motor1|motor2> <value(-1000 ~ +1000)>'\n");
        return;
    }
    if (!rt_strcmp(argv[1], "motor1"))
    {
        pwm_value = atoi(argv[2]);
        motor1_pwm_value_set(pwm_value);
    }
    else if (!rt_strcmp(argv[1], "motor2"))
    {
        pwm_value = atoi(argv[2]);
        motor2_pwm_value_set(pwm_value);
    }
    else
    {/* 输入的是其他内容 */
        rt_kprintf("Please input'motor_test <motor1|motor2> <value(-1000 ~ +1000)>'\n");
    }
}
//导出命令到控制台
MSH_CMD_EXPORT(motor_test, motor test sample : motor_test motor1|motor2 in1|in2 pulse);

小车的PID控制

​ 一般的PID分为两种,增量式PID和位置式PID,它们的主要区别在于控制器输出的计算方式不同。

​ 位置式PID的输出是根据当前误差、过去误差的积分以及未来误差的预测计算出来的。这种方法需要存储过去的误差信息,并且需要进行积分和微分运算,计算成本较高,但具有较好的稳定性和精度。

​ 增量式PID则是根据当前误差和过去误差的变化率(即误差的一阶差分和二阶差分)计算出控制量。这种方法不需要存储过去的误差信息,只需要进行简单的加减运算,计算成本较低,但可能会出现积分饱和等问题。

​ 因此,位置式PID适用于对精度要求较高的系统,而增量式PID适用于对计算成本要求较高的系统。

​ 我就选择位置式PID了,梁山派性能是足够的。在位置式PID中,控制器的输出取决于当前时刻的误差、过去误差的积分以及未来误差的预测。这些因素通过三个控制参数(比例系数、积分时间常数和微分时间常数)进行调节,以实现系统的稳定性和响应速度的平衡。

​ 在这里就不讲原理了,网上有很多大佬讲得都很好,大家可以自行搜索观看。B站其实是一个学习网站哈。

​ 简单来说,PID就是测量出实际值,设定一个目标值,输出一个驱动值,让实际值尽量和目标值靠近。

实际程序

module目录中的pid目录里面

  1. 头文件引用:
    1. #include "positional_pid.h":包含了定义了位置式PID控制器参数和函数的头文件。
    2. <math.h>:包含了用于数学计算的函数的头文件。
  2. 宏定义:
    1. ABS(x):一个宏定义,用于返回给定值的绝对值。
  3. positional_pid_params_t 结构体:
    1. 定义了位置式PID控制器的参数和状态。
  4. positional_pid_params_init 函数:
    1. 初始化位置式PID控制器的参数。
  5. positional_pid_set_value 函数:
    1. 设置位置式PID控制器的比例系数(kp)、积分系数(ki)和微分系数(kd)。
  6. positional_pid_compute 函数:
    1. 计算位置式PID控制器的输出值。
    2. 首先检查控制器是否启用,如果启用,则执行以下操作:
      • 设置目标值和测量值。
      • 计算误差(目标值与测量值之差)。
      • 如果误差的绝对值大于设定的死区(dead_zone),则执行以下操作:
        • 计算比例输出项(p_out)。
        • 计算积分输出项(i_out),并将其累加到之前的积分输出值上。
        • 计算微分输出项(d_out),使用当前误差与上一次误差的差值。
        • 计算总输出值(output),将比例、积分和微分输出项相加。
      • 如果总输出值超过了设定的最大输出值(output_max),则将其限制为最大输出值。
      • 如果总输出值低于设定的最小输出值(output_min),则将其限制为最小输出值。
      • 更新上一次测量值、上一次输出值和上一次误差的记录。
      • 返回计算得到的输出值。
    3. 如果控制器未启用,则返回 0.0f。
  7. positional_pid_control 函数:
    1. 设置位置式PID控制器的状态(启用或禁用)。
  8. positional_pid_init 函数:
    1. 初始化位置式PID控制器。
    2. 设置函数指针,将各个函数与相应的结构体成员关联起来。
    3. 调用 positional_pid_params_init 函数初始化参数,并将控制器状态设置为启用状态(PID_ENABLE)。

怎么用呢?

  1. 在代码中包含 "positional_pid.h" 头文件。
  2. 创建一个 positional_pid_params_t 类型的变量来存储 PID 控制器的参数和状态。
  3. 使用 positional_pid_init 函数初始化 PID 控制器,传递比例系数 kp、积分系数 ki、微分系数 kd,死区 dead_zone,输出上限 output_max 和输出下限 output_min
  4. 使用 positional_pid_set_value 函数设置 PID 控制器的参数(可选)。
  5. 使用 positional_pid_control 函数控制 PID 控制器的使能状态(可选)。
  6. 在需要计算 PID 控制器输出的地方,使用 positional_pid_compute 函数,并传递目标值和测量值作为参数,它将返回 PID 控制器的输出值。

速度环

​ 在这个送药小车题目中,最先要解决的就是小车轮子的速度。这个问题是一个非常关键的问题。这是因为小车电机的速度直接影响了小车的行驶速度和精度,而行驶速度和精度又是小车比赛中获胜的关键因素之一。如果小车电机的速度不稳定,那么小车的行驶速度和精度就会受到影响,导致小车无法在比赛中达到最佳表现。因此,在进行智能车比赛之前,需要先解决小车电机速度问题,保证小车电机的速度稳定、准确,并且能够根据需要进行调整。并不是说给一个确定的电压或者确定的PWM值就能让电机保持到一个准确的转速,比如每个电机的绕线,轴承,机械性能,负载能力,各个接触点的摩擦力这些都有差别,小车轮子所受到的摩擦力的不同也会影响小车的前进速度。

​ 在前面的BSP底层驱动初始化中我们已经解决了改变PWM值从而控制电机速度和获取编码器计数的问题。通过编码器我们可以进行电机的转速测量,通过改变PWM值我们可以让电机提高或者降低转速。在这个条件下,我们的目标值是让小车电机稳定在一个转速,测量值是小车当前由编码器测量得到的实际速度,输出值是电机的PWM值。通过调节PID的三个参数,让目标值变化时,实际值可以又快又准的接近目标值。

位置环

​ 依靠编码器累积下来的数值确定轮子具体转了多少各脉冲,根据这个脉冲来计算小车电机转过的位置。位置环的PID,测量值就实际的编码器脉冲,目标值是想要控制电机选择多少脉冲,输出值是速度环的速度。

角度环

​ 依靠IMU解算出来的航向角,角度环的PID,测量值是当前角度,目标值是想要转到的角度,输出值是电机的速度,当然我实际中是还没用角度环的,小车的旋转是依靠位置环来进行的。

巡红线环

​ 依靠K210返回来的巡线中线偏移,测量值是红线相较屏幕中线的偏移,目标值是想让红线偏移为0,输出值是电机的速度。

通讯协议实现

为了解决通讯的粘包,分包,校验问题,采用开源的RT-Thread Upacker软件包来实现。他的仓库实现了,C,C++,java,python的实现方式。

数据格式如下所示:

    Header    4BYTE                                                            Load
    ----------------------------------------------------------------------
    D0[7:0] |D1[7:0]  |D2[5:0]   |D2[7:6]         |D3[1:0]        |D3[7:2]
    ----------------------------------------------------------------------
    包头    |包长(8) |包长(6) |Header校验[3:2] |Header校验[5:4] |check[7:2] |data
    ----------------------------------------------------------------------
    0x55    |0XFF     |0X3F      |0X0C            |0X30           |0XFC       |XXXXX

解释一下上面这个包的意思,一个字节是八个位,上面D0后面的[0:7]就表示他的八个字节全部用来表示包头。上面的D2[5:0]是用了位域,指的是用数据包的第2个字节中的0到5位,一个字节是8位。位域是一种数据结构,可以让数据占用更少的存储空间。在C语言里,位域可以用来存储一些只需要占用一个或几个二进制位的信息。他经常用在一些需要频繁操作数据的场合中,减少数据长度,提升数据打包效率。

  • D0[7:0]: 数据包的包头,该字段的值为固定的0x55,用于标识数据包的起始。

  • D1[7:0]: 数据包的长度的低8位,表示数据包的总长度。在给定的示例中,该字段的值为0xFF,表示数据包的总长度为255个字节。

  • D2[5:0]: 数据包的长度的高6位,表示数据包的总长度的高位。在给定的示例中,该字段的值为0x3F,表示数据包的总长度的高6位为0x3F。

  • D2[7:6]: Header校验的第2和第3位。在给定的示例中,该字段的值为0x0C。

  • D3[1:0]: Header校验的第4和第5位。在给定的示例中,该字段的值为0x30。

  • D3[7:2]: 校验位和数据字段。在给定的示例中,该字段的值为0xFC。

    数据包的具体含义和数据字段的结构可能根据实际应用而有所不同。根据给定的示例,包头为0x55,包长为255个字节,校验位分布在Header校验和check字段中。这个数据结构的目的是在通信或数据传输中定义数据包的格式和内容,以便发送方和接收方能够正确解析和处理数据。

在移植的时候,只需要实现发送数据的函数和解包成功后的处理回调函数就可以了。

为什么呢要用ringbufer呢

使用ringbufer主要有以下几个作用的原因和作用:

  1. 数据缓存:环形缓冲区可以作为一个数据缓存区域,用于存储串口接收或发送的数据。当数据到达时,可以将其存储在环形缓冲区中,而不必立即处理数据。这种缓存机制可以确保数据的可靠接收和传输,而不会丢失任何数据。
  2. 解决数据速率不匹配:环形缓冲区可以解决串口接收和处理之间的数据速率不匹配问题。例如,当串口接收数据的速度比处理数据的速度快时,环形缓冲区可以临时存储多个接收到的数据,以供后续处理。这样可以避免数据丢失或接收溢出的情况发生。
  3. 异步通信:环形缓冲区允许异步的数据传输。串口通信通常是异步的,发送端和接收端的速率可能不同。通过使用环形缓冲区,可以在发送和接收之间建立一个缓冲区,使得数据可以在不同的时刻被发送和接收,而不需要发送和接收方同时处于活动状态。
  4. 提高系统响应性:使用环形缓冲区可以提高系统的响应性能。当有新的数据到达时,它可以立即被存储在环形缓冲区中,而不需要等待处理。这允许系统能够更快地响应其他任务或中断,而不会因为串口数据的到达而阻塞。
  5. 简化数据处理:环形缓冲区提供了一种简化数据处理的机制。通过使用适当的读取和写入指针,可以方便地从环形缓冲区中读取和写入数据。这样的机制使得数据处理的代码更加简洁和高效。

需要注意的是,在使用环形缓冲区时,需要合理设计和管理读取和写入指针,以确保数据的正确性和完整性。此外,当环形缓冲区已满或已空时,需要适当处理这些条件,以避免数据的丢失或阻塞系统。

有了upacker,我们可以只专注于payload,也就是上图中的data部分。

暂定通讯协议如下所示:

K210toMCU

负载包 含义 数据所对应的意义
payload[0] K210当前工作模式 0:巡线模式
1:数字识别模式
payload[1] 当前路口识别结果 0:啥也没识别到
1:门口区域
payload[2:3] 顶部巡线色块中心点相较屏幕中心的偏移像素,有正负 以像素点为单位
payload[4:5] 中间巡线色块中心点相较屏幕中心的偏移像素,有正负 以像素点为单位
payload[6:7] 左边巡线色块中心点相较屏幕中心的偏移像素,有正负 以像素点为单位
payload[8:9] 右边巡线色块中心点相较屏幕中心的偏移像素,有正负 以像素点为单位
payload[10] 最左边的数字(由K210计算坐标得出) 识别到的数字,可以是1,2,3,4,5,6,7,8
payload[11] 最右边的数字(由K210计算坐标得出) 识别到的数字,可以是1,2,3,4,5,6,7,8

MCUtoK210

负载包 含义 数据所对应的意义
payload[0] 设置K210工作模式 0:将K210切换至巡线模式
1:将K210切换至数字识别模式

小车地图位置对应信息

//具体位置信息分配如下所示,请对照实际地图理解
/*
           f                                       g
         ------                                 ------
           |                                       |
           |                                       |
           |                                       |
           |                                       |
           |4__________________3__________________5|
           |                   |                   |
           |                   |                   |
           |                   |                   |
           |                   |                   |
         __|___                |                 __|___
           e                   |                   h
                               |
                               |
                     |         |          |
                    c|_________2__________|d
                     |         |          |
                               |
                               |
                               |
                               |
                               |
                               |
                               |
                     |         |          |
                    a|.........1..........|b
                     |         |          |
                               |
                               |
                               |
                               |
                               |
                               |
                            .......
                               0
*/

小车1to小车2

负载包 含义 数据所对应的意义 对应代码中的结构体
payload[0] 小车1要去的药房号 识别到的数字:1,2,3,4,5,6,7,8 car1_to_car2_info_t
payload[1] 病房 a 的病房号 固定为1
payload[2] 病房 b 的病房号 固定为2
payload[3] 病房 c 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[4] 病房 d 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[5] 病房 e 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[6] 病房 f 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[7] 病房 g 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[8] 病房 h 的病房号 1,2,3,4,5,6,7,8(0:表示还不知道)
payload[9] 小车1位置信息 可选:a,b,c,d,e,f,g,h,0,1,2,3,4,5(对照上图注释)(字符类型'a','1')
payload[10] 控制小车2继续运行 0x00:小车2等着别动
0x01:小车2可以继续运动了(发挥部分2用)

小车2to小车1

负载包 含义 数据所对应的意义 对应代码中的结构体
payload[0] 小车2位置信息(也算替代一个心跳包) 可选:a,b,c,d,e,f,g,h,0,1,2,3,4,5(对照上图注释)

小车姿态解算

加速度计用于测量物体在三个空间轴上的加速度,而陀螺仪用于测量物体在三个空间轴上的角速度(旋转速率)。磁力计用于测量物体在三个空间轴上的磁场强度。

姿态解算的目标是根据IMU提供的数据,计算出物体相对于某个参考坐标系的旋转姿态,通常以欧拉角(如俯仰角、横滚角和偏航角)或四元数的形式表示。

用开源的Fusion来实现姿态解算。如果只有IMU的话,姿态解算后的就偏航角一直在往一个方向偏,单纯的三轴加速度计+三轴角速度计是没办法解决这个问题的。因为偏航角的平面与重力相互垂直,重力在偏航角平面的投影为0,所以无法观测偏航角,即使偏航角发生变化,加速度计测出的值也不会改变。但是,加上磁力计以后就能解决偏航角的漂移问题了,因为磁力计可以观测到偏航角。不过,在地球南北极等地方,由于磁力线与地球重力方向重合,磁力计也无法观测到偏航角。每个地方的磁场都是不一样的,所以需要校准。

各个数据在系统中的发布与订阅

​ 可能有人会说数据直接用全局变量传递不就行了。

​ 在实时操作系统(RTOS)中,不推荐或不建议使用全局变量来传递信息的原因主要有以下几点:

  1. 竞争条件:在多任务环境下,全局变量可能会面临竞争条件的问题。当多个任务同时读写同一个全局变量时,会导致数据的不一致性和错误的结果。这是因为任务的执行是并发的,无法控制它们的执行顺序。
  2. 数据共享和保护:全局变量被所有任务共享,这意味着多个任务可以同时访问和修改该变量。如果没有正确的数据保护机制,可能会导致数据损坏或冲突。
  3. 可维护性和调试困难:使用全局变量传递信息可能导致代码的可维护性和调试的困难。由于全局变量可以被任何任务修改,追踪问题的根源和调试错误可能会变得更加困难。

​ 为了避免上述问题,RTOS提供了一些机制来进行任务间的通信和数据传递,例如:

  1. 消息队列:任务可以通过消息队列来发送和接收消息。每个任务有自己的私有消息队列,通过发送和接收消息来进行通信,避免了全局变量的竞争条件和数据共享问题。
  2. 信号量:信号量可以用于同步任务的执行和共享资源的访问。任务可以通过申请和释放信号量来控制对共享资源的访问,并确保任务之间的互斥性和同步性。
  3. 事件标志组:事件标志组可以用于任务之间的通知和事件触发。一个任务可以等待一个或多个事件标志的状态变化,并在事件发生时被唤醒执行相应的操作。

​ 通过使用这些RTOS提供的通信机制,可以更安全地在任务之间传递信息,避免竞争条件和数据共享问题,并提高代码的可维护性和调试效率。

​ 采用RT-Thread软件包uMCN,uMCN (Micro Multi-Communication Node) 提供了一种基于发布/订阅模式的安全跨线程/进程的通信方式。在系统中,uMCN 被广泛应用于任务和模块间的数据通信。使用发布-订阅(Publish-Subscribe)机制可以提供更灵活和高效的任务间通信方式,支持一对一、一对多、多对一和多对多的通信模式。这种机制可以解决使用全局变量传递信息可能带来的竞争条件和数据共享问题。

​ 发布-订阅机制基于事件驱动的思想,其中包含两个角色:发布者(Publisher)和订阅者(Subscriber)。发布者负责发布事件或消息,而订阅者则注册对感兴趣的事件或消息进行订阅。他提供了一种松散耦合的通信方式,允许任务或模块之间通过发布和订阅消息来进行通信,而不需要直接知道彼此的存在。

​ 在使用发布-订阅模式获取数据时,通常涉及以下角色和操作:

  1. 发布者(Publisher):负责生成和发布数据或事件。发布者将数据发送到一个或多个特定的主题(Topic),而不关心具体的订阅者。
  2. 订阅者(Subscriber):订阅者对特定的主题感兴趣,并通过订阅该主题来接收与之相关的数据或事件。
  3. 主题(Topic):主题是数据或事件的分类或标识符,发布者根据主题将数据发送到相应的通道,而订阅者根据主题来选择订阅的数据源。

​ 使用发布-订阅模式获取数据的好处在于,系统中的任务或模块之间解耦合,发布者和订阅者之间不直接依赖于彼此的存在,从而提高了系统的可扩展性和灵活性。此外,发布-订阅模式还能够支持多对多的通信,允许多个订阅者同时接收相同的数据或事件,实现了信息的分发和共享。

​ 那他的优势有哪些呢:

  1. 松耦合性:发布-订阅机制使任务之间的通信更加松耦合。发布者不需要直接知道订阅者的存在,也不需要关心具体的订阅者数量和位置。订阅者只需要订阅感兴趣的事件或消息,从而实现任务之间的解耦。
  2. 灵活性:发布-订阅机制支持多对多的通信模式,一个发布者可以有多个订阅者,一个订阅者也可以订阅多个发布者的事件或消息。这种灵活性使得任务之间可以方便地建立复杂的通信关系。
  3. 扩展性:通过发布-订阅机制,可以方便地扩展系统,添加新的发布者或订阅者,而不需要修改现有的任务逻辑。这种扩展性使得系统更具可维护性和可扩展性。

题目实现步骤

  1. 实现小车的前进后退旋转。
  2. 实现小车红线循迹和药房区域识别并停车。
  3. 实现数字识别并传递给GD32。
  4. 实现近端病房送药并返回。
  5. 实现中远端病房送药并返回。
  6. 实现双车送药。

这个每个人有不同的写法,大致都是这样,这部分没什么好说的,按照想出来的逻辑写出来就行。

K210端

红色循迹线识别与门口区域识别

​ 巡线就是寻找红色色块,设置好阈值,划分好ROI(感兴趣区),就可以进行红色循迹线的识别。门口区域是一片小黑色方框,可以划分一个ROI,让K210去计算这个ROI内部是否有足够多的小色块。

  1. 导入所需的模块和库:导入了一些必要的模块和库,包括摄像头模块、图像处理模块、显示模块等。
  2. 进行初始化设置:对摄像头进行了一些初始化设置,包括设置摄像头的频率、曝光、像素格式和帧大小等。
  3. 打印内存分配情况:使用了一些函数来打印当前的内存分配情况,包括堆内存和栈内存的可用空间。
  4. 程序运行选择和状态设置:定义了一些变量用于设置程序的运行模式和状态,例如巡线模式、数字识别模式等。
  5. 串口配置:配置了一个串口用于与其他设备进行通信,设置了串口的波特率和缓冲区长度。
  6. 按键和蜂鸣器配置:注册了一个GPIO引脚作为按键输入,并通过中断来检测按键的状态。同时,通过定时器配置了一个PWM通道来控制蜂鸣器的频率和占空比。
  7. 数据传输配置:定义了一个数据传输类,用于存储要传输给其他设备的数据。
  8. 感兴趣区配置:定义了一些感兴趣区域的位置和参数,用于在图像中标定特定区域。
  9. 寻找色块区配置:定义了一些色块位置信息和阈值,用于在图像中寻找特定颜色的色块。
  10. 色块连线区配置:定义了一些参数和函数,用于在图像中绘制连接色块的线条。

数字识别

就是那老三样:

  1. 采集数据集
  2. 对数据集进行标注
  3. 开始训练

串口发送和接收

和GD32端一样,采用Upacker进行数据的解包,押包和发送。

联系人信息

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

基于立创梁山派的21年电赛F题-智能送药小车 展开 收起
Apache-2.0
取消

发行版 (1)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C
1
https://gitee.com/lcsc/medical_car.git
git@gitee.com:lcsc/medical_car.git
lcsc
medical_car
基于立创梁山派的21年电赛F题智能送药小车
master

搜索帮助

Cb406eda 1850385 E526c682 1850385