学 校: 山东大学(威海)
队伍名称: 海韵二队参赛
队员:任佳麟 郭凯 王守超 苗淏溟带
队教师: 王小利

简 介: 在第十六届全国大学生智能汽车竞赛中,官方为不同的赛题组指定了不同的MCU类型,包括英飞凌 Infineon,灵动MindMotion,恩智浦NXP,宏晶科技STC,沁恒WCH。随着赛题任务越来越复杂,传统的裸机开发难度也在增大,且对一些需要精准控制时间的任务已无法胜任。在智能车控制系统开发过程中中引入RT-Thread实时嵌入式操作系统,不仅可以充分发挥不同芯片的性能,让智能车跑的更加顺畅,还在一定程度上屏蔽了不同单片机底层硬件细节,增强了程序的可移植性,提高了软件方面的开发效率。同时,使用 RT-Thread内核中的自动初始化、线程调度、事件集、邮箱等功能,还可以简化智能小车任务增强智能小车执行任务的实时性。

本论文对基于RT-Thread在双车接力组中三轮车模和直立车模上的应用进行了研究,首先分析并阐述了双车组规定使用的车模的结构特点以及我队智能汽车制作情况,并结合传统搭建方式和逐飞样车分析了我队车模搭建的创新之处和优势,简单介绍了智能汽车裸机开发和运用 RT-Thread在功能实现方式上的区别;介绍了RT-Thread实时嵌入式操作系统,说明了 RT-Thread官方和逐飞科技对灵动 MM 32芯片的移植支持,展示了移植后的 RT-Thread+逐飞开源库 MM32F3277工程目录;具体说明了我队RT-Thread在智能汽车开发中的应用,将运用RT-Thread进行智能汽车开发与智能汽车裸机开发对比,展示了RT¬Thread在智能汽车开发中实现的功能,即利用线程调度、邮箱和事件集来代替传统定时器实现信息采集、运动控制、按键扫描、屏幕显示等,说明了其在智能汽车开发中发挥出的独特作用;最后,通过实际调试小车,发现使用 RT¬Thread优化了程序在人机交互(屏幕显示、按键扫描、蜂鸣器提示)方面的使用,并且在信息采集和运动闭环控制等方面确保了时序的正确性,使得直立小车互补滤波的姿态结算有良好的动态特性,系统具有较高的稳定性和实时响应能力。

关键词RT-Thread智能车竞赛双车接力

§01 引言


一、智能车制作情况

1、三轮F车模结构特点

  该三轮系统智能车机械结构主要由一个前置双排万向轮和后置双 RS380马达驱动组成,该车模的机械结构特点是将将驱动轮和转向轮合并,通过左右两轮差速实现转向控制,双排万向轮起从动和支撑车模底板的作用。 [1]在调试该车模的时候我们总结出该车的一些控制特点:

▲ 图1.1 三轮F车模差速转弯结构模型

  1. 前瞻性较好,负责驱动和转向的左右两轮在整个车模的尾部,无论是使用摄像头或电感寻迹,传感器采集到的赛道信息都在两轮前方较远的距离,使得控制系统有良好的前瞻预测性。

  2. 在最初搭建并调试车模的时候,将电池、电路板、摄像头及其支架都固定在车轮前方的底板,小车整体重心靠前,由于速度较低,对两轮差速产生的转向扭矩的要求不高,当车速提高时,由于 F车两轮轮距距离较小(相较于 D车),重心靠前,两轮差速产生的转向扭矩较小时,很容易产生弯道侧滑和转向滞后的问题。所以三轮车系统对车模整体重心的要求比较高。

  三轮车前面的万向轮是被动转向,所有转向输出都是靠后轮差速实现,那么如果所有重量都放在电机前方,就相当于后轮在推着前面所有重量在走,转向会十分吃力,如果将部分重量放到轮轴之后,尽量保持前后重量平衡,那么它的转向就可以接近于直立车一样灵活。

2、三轮F车模制作情况

  车模搭建特点:根据上述三轮 F车的结构特点和调试经验,我们使用3D打印电池支架固定在车模尾部,将主控驱动运放一体板固定在支架和车模尾部上方位置,将摄像头杆固定在电机后方位置,并将摄像头、超声波模块、红外测距模块从上到下固定在摄像头杆上,电磁杆和 3D打印送球座固定在车模头部。车模整体重心靠后,有利于三轮差速转向控制系统。

▲ 图1.2 三轮F车模俯视图

▲ 图1.3 三轮F车模侧面图

  车模搭建创新之处:

  根据我们的交接策略,三轮车作为送球车,这样就必须在头部固定送球座,在加上电磁杆也因为前瞻性必须位于车模头部并考虑竞赛规则中对三轮 F车模长度的限制,尽管使用电池支架将主板和电池固定在车模尾部,车模的整体重心依然靠前,一般三轮车搭建都是将摄像头杆固定在车模中部的底板上,逐飞样车则是将摄像头杆固定在车模头部。为了使整车重心靠后,利于差速转向系统,我们将摄像头杆固定在两个电机的中间靠后的位置,虽然牺牲了一点点摄像头前瞻性,但使得车模整体重心靠后,而且摄像头本身前瞻就比较远,实际调试并没有很大影响。

▲ 图1.4 逐飞样车将摄像头干固定在车模头部

▲ 图1.5 电池支架和摄像头固定方式

▲ 图1.6 使用Solidworks建模电池支架

3、直立D车模结构特点

  直立我们使用的是 D车模,该车模由半个正常车模底盘的一半和后置双 RS380马达驱动,比一般车模(C车模和 F车模)的轮距大一些,有利于实现直立自平衡和转向。直立车模需要在保持自平衡的基础上利用两轮差速实现寻迹在高速转弯和坡道时也需要保持直立,这就需要车模的重心必须合理。

  首先我们需要尽量保持车模的重心尽可能的低,这样车模会比较稳定;其次结构上需要预留较大的俯仰角度,这样可以有较大的姿态调整空间,有利于过坡道,然后需要尽量保持重量集中,尽量保持在轮轴附近;最后要保证前后重量平衡,尽量不要出现需 要配重才能维持平衡的现象。

  在调试过程中,因为我们的主要寻迹方式是摄像头寻迹,所以摄像头杆的位置、运动过程中摄像头的角度及其变化范围,都会影响图像处理和控制效果同时直立车速度的变化会产生姿态角度的变化而导致摄像头角度变化,所以我们也多次尝试摄像头杆不同的搭建位置和角度。

4、直立D车模制作情况

  综合考虑交接策略(直立需要作为接球车在三岔路内等待接球)和调试经验,我们最后选择小车底盘朝向后方并上翘,使用现成金属电池支架固定电池摄像头杆固定于两轮电机中间的位置,主控驱动运放一体板的中间间隙穿过摄像头杆固定于小车电机上方,配合电磁杆小车整体呈 V字型结构。

▲ 图1.7 直立D车模的V字型结构

  车模搭建创新之处:

  摄像头杆在直立车模正常速度行驶时最好保持垂直地面,同时重心合理且结构紧凑配合合适的姿态控制算法会使直立车处于其他速度状态或者元素时例如坡道产生的姿态角度变化尽可能的小,这样对摄像头角度变化影响会降到最低,图像处理和转向控制会有良好的适应性。

  所以我们将主控、驱动、运放绘制成一体板,并做开孔处理,固定在 D车模电机上方,并将摄像头杆固定在两电机之间并从开孔中穿出,这样使得车模整体重心落在电机上,并且结构紧凑。

▲ 图1.8 一体板上的开孔处理

5、交接结构的制作

  我们使用三轮 F车为送球车,直立 D车为接球车,为了缩短交接时间,直立车模在三岔路内保持直立状态等待接球,交接的小球使用前后粘贴了不同体积和强度的磁铁的乒乓球,送球车使用3D打印送球座,座内使用磁铁固定乒乓球,接球车在车模底盘后面安装长条挡板并使用磁铁+魔术贴的方式固定球,这种固定方式使小球在传递过程和行驶过程中都不易掉落。

▲ 图1.9 前后粘贴磁铁的乒乓球

▲ 图1.10 使用SolidWorks建模的三轮F车的送球座

▲ 图1.11 直立车模底盘后面的接球结构

二、在智能汽车竞赛中运用 RT-Thread嵌入式系统

1、嵌入式系统简介

  在微处理器问世之后,嵌入式计算机得到了发展。1971年,算术运算器和控制器电路成功的被集成在一起,推出了第一款微处理器,其后各厂家陆续推出了8位、16位微处理器。以这些微处理器为核心所构成的系统广泛地应用于仪器仪表、医疗设备、机器人、家用电器等领域。微处理器的广泛应用形成了一个广阔的嵌入式应用市场。20世纪 80年代,随着微电子工艺水平的提高,集成电路制造商开始把嵌入式计算机应用中所需要的微处理器、I /O接口、A/D转换器、D/A转换器、串行接口,以及R AM、R OM等部件全部集成到一个 VLSI中,从而制造出面向I/O设计的微控制器,即俗称的单片机。单片机成为嵌入式计算机中异军突起的一支新秀。20世纪 9 0年代,在分布控制、柔性制造、数字化通信和信息家电等巨大需求的牵引下,嵌入式系统进一步快速发展。面向实时信号处理算法的 DSP产品向着高速、高精度、低功耗的方向发展。21世纪是一个网络盛行的时代,将嵌入式系统应用到各类网络中是其发展的重要方向。

2、实时操作系统

  实时操作系统〔Real Time Operating System)是指当外界事件或数据产 生时,可以承受并以足够快的速度予以处置,其处置的结果又能在规定的时间之内来对处理系统做出快速响应或控制生产过程,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统 ,提供及时响应和高可靠性是其主要特点。如果实时操作系统的时序和逻辑出现了偏差,将会对系统的运行产生严重的后果,所以要求实时操作系统具有非常严格的时序和逻辑实时操作系统分为硬实时系统和软实时系统两种类型。在硬实时系统中,不但要求任务响应要在任何条件下都要得到满足,而且要求在规定的时间内完成事件的处理。软实时系统仅仅要求事件响应是实时的 ,并不要求限定某一任务必须在多长时间内完成。不会造成灾难性的错误。通常,大多数实时系统是两者的结合。实时系统的关键技术是如何保证系统的实时性。实时性操作系统主要有以下三个特点:

  • 实时性 : 通常情况下,对实时性比较高的系统大多采用的是实时操作系统进行处理。能够实时对系统设备的动作进行检测控制。比如,应用于控制导弹发动机、医疗器械电机等嵌入式系统,对控制对象的必须具有严格的时序,还要有较快的反应速度,对多个控制对象具有良好的切换时序,不然可能造成机毁人亡的惨状。而非实时性的操作系统在这样的环境是无法完成任务的。因此嵌入式系统的核心就是具有RTOS实时操作系统 ,也是它最大的优点。
  • 可剪裁性:对于嵌入式系统环境的多样化,实时操作系统是面向对象.面向客户、面向产品、面向应用,必须要有较强的适应能力,开发人员可以根据系统的特点和要求。对操作系统进行灵活的剪裁和配置。操作系统的功能有很多但在一个确定的嵌入式设备中,对操作系统的功能要求也是确定的,因此只留下需要的功能即可。可剪裁性是通过软件配置的方法实现“即插即用”。
  • 可靠性:面对嵌入式系统运行环境多变、运行环境恶劣等特点。就要求嵌入式操作系统具有较强的可靠性。嵌入式设备设计好之后,系统一般是自动运行的。无人干预。这就不仅要求嵌入式操作系统具有较强的稳定性和可靠性,而且还要要求实时操作系统与应用软件具有较好的兼容性。

3、智能车裸机开发和运用RT-Thread在功能实现上的对比

  之前,我们进行智能汽车的软件开发往往是使用逐飞的开源库进行裸机开发,直接在裸机上写程序。此时,程序分为两部分:前台系统和后台系统,这样的程序包括一个死循环和若干个中断服务程序:应用程序是一个无限循环,循环中调用函数完成所需的操作,这个大循环就是后台系统。前台系统也就是中断服务程序,用于处理系统的异步事件。我们在智能汽车竞赛中经常使用中断来实现时序功能,比如PID计算、传感器获取数据、舵机电机PWM输出等等,通过不同中断定时进行,使用全局变量进行中断间数据的交换。

▲ 图1.12 智能车逻辑运行流程图

  但是,裸机开发存在实时性低、代码可读性较差等缺点。设想一个情景,单片机正在执行task1时,突然需要执行task2,但是前后台程序只能在ta sk1执行完毕后,再去执行task2,这样就极大地影响了MCU执行任务的实时性,不利于特殊任务(如入环、入库、摄像头补线等)的实时执行。

  而使用RT-Thread后,我们可以充分利用MCU资源,提高MCU执行任务实时性。我们将将各主要功能处理函数分别写到各个线程中,再使用邮箱、信号量功能实现线程间通信,将采集到或者计算出的数据发送给总线程,总线程识别处理收集到的数据,从而完成智能汽车运行任务。

▲ 图1.13 RT-Thread运行流程图

§02 RT-Thread嵌入式OS


一、RT-Thread简介

  RT-Thread全称Rea l Time-Thread,是一款完全由国内团队开发维护的嵌入式实时操作系统(RT OS),它诞生于 200 6年,具有完全的自主知识产权。经过近 1 5个年头的沉淀,伴随着物联网的兴起,它正演变成一个功能强大、组件丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。

  RT-Thread具备一个 IoT OS平台所需的所有关键组件,例如 GUI、网络协议栈、安全传输、低功耗组件等等。经过 11年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过 8亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。

  RT-Thread与其他 RTOS( FreeRTOS、 uC/OS)的主要区别是:RT-Thread自创建之初的定位就不仅仅是一个 RTOS内核,而是一个网络、文件系统、 GUI 界面等组件的中间件平台,它具有极强的扩展性。

  RT-Thread拥有良好的软件生态,支持市面上所有主流的编译工具如 GCC、 Keil、I AR等,工具链完善、友好,支持各类标准接口,如 POSIX、CMSIS、C++应用环境、Javascript执行环境等,方便开发者移植各类应用程序。商用支持所有主流 MCU架构,如 ARM Cortex-M/R/A, MIPS, X86, Xtensa, C-Sky, RISC-V,几乎支持市场上所有主流的 MCU和 Wi-Fi芯片。

二、RT-Thread架构

  RT-Thread与其他很多 RTOS如 F reeRTOS、 uC/OS的主要区别之一是,它不仅仅是一个实时内核,还具备丰富的中间层组件,如图所示。

▲ 图2.1 RT-Thread 系统架构

  它具体包括三部分,即内核层、组件与服务层、RT-Thread软件包。

  内核层包括RT-Thread内核和 libcpu/BSP。其中,RT-Thread内核与软件功能相关,是该嵌入式操作系统的核心部分,与包含了该系统中特点功能的实现,如多线程处理调度、消息队列、邮箱、信号量、动态内存管理等等。而 libcpu/BSP与硬件功能相关,包含了移植所需文件以及板级支持包,由外设驱动和CPU 移植构成。

  组件与服务层基于RT-Thread构建,包括虚拟文件系统、网络框架、FinSH等上层软件。其中的各部分均采用模块化构建,做到组件内部高内聚,组件之间低耦合,方便使用者单独调用其中特定的功能。

  RT-Thread软件包运行于 RT-Thread物联网操作系统平台上,包括不同领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是RT-Thread生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。

三、RT-Thread功能上的特点

1、线程调度以及空闲线程

  线程是实现任务的载体,它是RT-Thread中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级。在任务处理上相比于裸机开发,RT-Thread最大的特点便是多线程处理调度功能。

  在裸机开发逻辑中,当MCU调用de lay函数进行延时时,会暂停当前任务执行,并使MCU空转,一定程度而言浪费了这段时间MCU的算力。

  而当引入RT-Thread的线程调度功能后,在ta sk0延时的时候,RT OS可以调用优先级低于 task0的ta sk1、ta sk2来运行,在ta sk0延时结束后继续执行 task0,实现了对空闲MCU性能的利用,提高了MCU运行效率,在相同任务量的情况下减少了总体所需运行时间,如下图所示。

▲ 图2.2 逻辑和RTOS处理任务模式对比

  另外,为实现MCU性能最大化利用,RT-Thread还加入了空闲线程功能。空闲线程是一个比较特殊的系统线程,它具备最低的优先级,当系统中无其他就绪线程可运行时,RT-Thread调度器将调度到空闲线程。此外,空闲线程还负责一些系统资源回收以及将一些处于关闭态的线程从线程调度列表中移除的动作,它在形式上是一个无限循环结构,且永远不被挂起。

  在RT-Thread实时操作系统中,空闲线程向用户提供了钩子函数,空闲线程钩子函数可以让系统在空闲的时候执行一些非紧急事务,例如系统运行指示灯闪烁,CPU 使用率统计等等。

2、信号量、消息队列、邮箱

  在RT-Thread中,线程之间的通讯主要通过信号量、消息队列、邮箱模块实现。其中,信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的;消息队列是另一种常用的线程间通讯方式,它能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。邮箱是一种简单的线程间消息传递方式在RT-Thread操作系统的实现中能够一次传递 4字节邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数。

  在裸机开发逻辑中,我们通常使用全局变量来存放传感器采集的数据,计算输出时之间读取全局变量。比如,将ad c采集到的电感数据存放在ad _val数组中,之后经过运算转化成归一化后的数值,存放在 ad c_normalized数组里。需要进行特殊元素判断时直接读取当前数组内存放的数值,如下图所示。

▲ 图2.3 使用ad_val数据存放采集到的电感数值

▲ 图2.4 特殊元素判断直接读取数值

  在我们进行实际的智能汽车工程开发的时候,经常会涉及到一个线程更新某个全局变量值,然后另外一个线程去读取这个全局变量值,根据这个全局变量值的不同而去执行不同的操作。在RT-Thread操作系统 中,邮箱、消息队列、 信号量这些功能均可以作为工具帮助系统在不同的线程中间传递信息,为智能汽车运行提供了更多的实现方法。

3、事件集

  RT-Thread中的事件功能主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。事件另外一个特性是,接收线程可等待多种事件,即多个事件对应一个线程或多个线程。同时按照线程等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。

  RT-Thread 定义的事件有以下特点:

  事件只与线程相关,事件间相互独立:每个线程拥有32个事件标志,采用一个 32 bit无符号整型数进行记录,每一个 bit代表一个事件。若干个事件构成一个事件集;

  事件仅用于同步,不提供数据传输功能;

  事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

  在裸机开发逻辑中,我们常常使用全局变量来进行特殊元素标志位的表示。比如摄像头完成采集、环岛和坡道的各个位置阶段、冲出赛道停车、完成任务准备入库等等标志位。而使用 RT-Thread的事件集功能后,我们可以将相同类型的标志位写成一个事件集,里面包含不同的事件,读取时运用不同的参数,达到按照指定任务触发判断结果的目的,如在运行时遇到坡道的不同位置阶段加减速度,长直道加速等等。

▲ 图2.5 事件集判断坡道标志位

四、RT-Thread对灵动MM32芯片的移植支持

  1、RT-Thread移植到智能汽车芯片平台

  初期,逐飞在NXP、灵动、沁恒三家芯片厂商及RT-Thread公司的技术支持下移植好RT-Thread操作系统。RT-Thread工程师负责修改RT-Thread操作系统的底层文件,以适配 InfineonTriCore内核的单片机和 AURIX Development Studio IDE软件。逐飞科技将修改好的RT-Thread操作系统的底层文件移植到现有的英飞凌开源库中,并做好智能车应用的相关外设适配。初期主要支持下列芯片平台: RT1064、RT 1021、MM 32SPIN27、MM 32F3277、CH 32V103。

  之后,逐飞进一步做好了智能车外设需要的外设驱动, 2月底至 3月初陆续开放了基于RT-Thread的开源库,通过gitee并公开给所有参赛同学使用。

  逐飞科技还根据芯片的不同特点,为特殊芯片平台适配了 RT-Thread nano版本,如内存较小的MM32SPIN27、CH32V103。而 RT1064、RT 1021、MM 32F3277、TC 2644、TC 364、TC 377芯片资源相对丰富,所以适配了RT-Thread的完整版本。 6月 25日,逐飞科技推出了适配了英飞凌TC 264、TC 364、TC377平台的RT¬Thread操作系统,采用英飞凌单片机相关的三个组别(基础四轮组、AI电磁越野组、节能信标组)终于也可以用上RT-Thread操作系统。

  此外,逐飞科技还提供了10个内核示例便于参加智能汽车竞赛的同学快速上手,帮助没有接触过嵌入式开发操作系统的同学学习使用 RT-Thread这一强大工具。逐飞科技还特意编写了基于RT-Thread的智能车框架示例,避免参加智能汽车竞赛的同学们继续使用裸机思想构建整个框架。

2、RT-Thread+逐飞开源库MM32F3277工程介绍

  MM32F3277的RT-Thread开源库继续沿用逐飞科技其他开源库的代码风格,简洁易懂,容易上手。本次开源库使用官方 S DK作为最底层。然后将各个模块使用的寄存器进行二次封装,提升了其易用性。

▲ 图2.6 RT-Thread+逐飞开源库MM32F3277工程目录

  开源库的全部目录如上图所示。

  以下是该开源库的目录介绍:

  • Project/CODE文件夹下放置的是用户自己添加的代码文件;
  • Project/USER文件夹下放置的是 main.c、i sr.h、i sr.c文件;
  • Project/IAR文件夹下放置的是IAR的工程文件;
  • Project/MDK文件夹下放置的是MDK的工程文件;
  • Libraries/Device文件夹下放置的是官方的S DK底层库;
  • Libraries/doc文件夹下放置的是开源库版本、推荐引脚分配说明;
  • rtthread_libraries文件夹下放置的是RTT相关的源码文件;
  • Libraries/board文件夹下放置的是版本文档说明;
  • Libraries/seekfree_libraries文件夹下放置的是逐飞科技精心编写的底层驱动,底层驱动是用灵动微电子提供的MM 32F3277的S DK进行二次封装,以简化各个模块的使用步骤,使用更加方便;
  • Libraries/seekfree_peripheral文件夹下放置的是各类常用的模块驱动,当使用到这些模块时只需要调用函数即可实现相应的功能,非常的简单方便。目前实现线性 CC D、小钻风硬件二值化摄像头、总钻风全局快门灰度值摄像头、 1.8寸TFT、ICM 20602六轴陀螺仪、IIC通讯协议(模拟IO)、 1.14寸 IPS液晶屏、 2寸IPS液晶屏、MPU 6050六轴陀螺仪、OLED显示屏、虚拟示波器通讯协议、无线转串口模块。

3、智能汽车裸机工程移植到RT-Thread开源库过程

▲ 图2.7 RT-Thread开源库中的 Smart_Car_Demo工程

  上图是RT-Thread开源库中S mart_Car_Demo工程的 main.c文件,可以看出,开源库风格沿用了裸机开发时使用的逐飞开源库风格,方便了我们进行智能汽车工程移植。我们首先将旧工程中Pro ject/CODE下的.c和.h文件全部复制粘贴到新工程对应文件夹中,然后在M DK工程结构中添加了对应的.c和.h文件,并在headfi le.h中使用in clude将自己编写的.h文件包含在内,便完成了文件移植。

  移植后我们编译了整个工程,发现工程存在着 error,经过排查,我们发现function.中的多个库函数与 seekfree_fun.c中相重合,产生了重复定义, 于是我们将重复的库函数注释掉,解决了问题。

  同时,由于逐飞新库中将总钻风摄像头采集到的图像数组存放在了 mt9v03x_image而不是旧库中的i mage中,我们的图像处理文件i mage.c中报错显示变量未定义,因此我们利用M DK的查找替换工具,在 seekfree_peripheral\SEEKFREE_MT9V03X.c中将所有的 mt9v03x_image替换成 image,解决了这个问题。这样,我们便完成了工程移植。

  在工程移植后,我们尝试一点点加入RT-Thread内核的功能,比如标志位可以用事件集来代替,蜂鸣器可以用邮箱来控制鸣叫时长,过去的中断循环执行语句可以放在线程中利用线程调度来实现,等等。我们还结合使用了传统的定时器和线程,来实现智能汽车最佳运行状态。

§03 RT-Thread应用


  RT-Thread在双车接力组中的应用

一、智能汽车竞赛双车接力组组别特点概述

  在第十六届全国大学生智能汽车竞赛双车接力组组别中,每个参赛队伍由最多 5人构成,需要制作三轮(F车模)、直立(D,E车模)两辆智能汽车。在比赛中,两辆智能汽车均需要完成自己的赛道元素任务,并在 16届新元素——三岔路口内完成小球的传递。

  具体来说,比赛开始时,一辆车模在三岔路口一条岔道中间停止。一辆车模从车库出发,到达中间车模位置时,将车上的一个尺寸不小于 40毫米见方的球体传递给第二辆车模,然后停止在原地等待。

  第二辆车模带着球体重新绕赛道一周停在三岔路口的另外一条岔道上,只要距离三岔路口一米距离以上之后。第一辆车模便可以重新启动返回到车库。

  相比其他组别,由于双车接力组中需要在寻迹的基础上实现两辆车之间的传接球,机械结构的作用被放大,合理的机械结构是传接球的关键,加上三轮和直立的车模运动姿态和控制特点不同,接球车和送球车需要完成的寻迹任务也不同,所以根据其特点选用传接策略对完成比赛任务很重要。

二、通过RT-Thread的自动初始化功能简化代码

1、RT-Thread的启动流程简介

  RT-Thread 的启动流程,大致可以分为四个部分:

  1. 初始化与系统相关的硬件;
  2. 初始化系统内核对象,例如定时器、调度器、信号;
  3. main线程,在 main线程中对各类模块依次进行初始化;
  4. 初始化定时器线程、空闲线程,并启动调度器。

  如下图所示:

▲ 图3.1 RT-Thread启动流程图

  一般来说,在系统里添加新的功能模块的时候,在使用前都必须先初始化,通常的做法是在主程序运行前手动添加调用初始化函数。而RT-Thread提供了另一种低耦合高内聚的初始化方式,它不需要我们再手动添加调用初始化函数,它能在系统运行前自动完成各模块的初始化。

2、自动初始化功能的使用

  在传统智能汽车工程开发中,我们通常在小车上电运行后的 main函数中开端调用各部件初始化函数,来实现小车功能的初始化。

▲ 图3.2 传统逻辑开发的初始化函数

▲ 图3.3 逻辑开发中在main函数调用初始化函数

  使用RT-Thread后,我们可以应用其自动初始化功能来实现各部件的自动初始化,无需在main中直接调用函数。
  使用自动初始化功能,需要调用RT-Thread的INIT宏接口。

  在RT-Thread中,共有六个INIT宏接口,如下图所示。

▲ 图3.4 RT-Thread中六个INIT宏接口

  运用时,只需要调用对应的宏接口,将函数写在 init函数下方,系统运行时即可完成自动初始化,如下图所示。

▲ 图3.5 引用宏达到自动初始化的效果

  相比于将所有的 init函数放在 main函数中的传统做法,这样无疑可以减少主函数中调用的函数量,让代码更简洁直观。

  但是,各初始化函数的调用需要有一定顺序,比如 board_init,即核心板初始化应该是最先被执行的,其次才是外设部件,最后是应用部分。此时,我们可以通过调用不同的INIT宏接口,来实现从底层到外设再到应用的顺序初始化。

  在实际操作中,我们使用 INIT _BOARD_EXPORT(board_init),将核心板初 始化放在1的顺序,即硬件初始化,而将其他初始化均放在 3的顺序(如摄像头、屏幕显示、按键、ADC模块、编码器、串口),即外设驱动初始化。

  这样,在我们使用RT-Thread后,可以简化 main函数中的代码,同时又能实现各部件的顺序初始化操作。

三、RT-Thread线程调度在三轮差速控制、直立姿态调节中的应用

1、三轮F车模控制特点概述

  三轮 F车控制策略是转向使用角速度PI --中线偏差PD串级,速度使用增量式PI控制,使用串级 PID控制和陀螺仪采集的转向轴角速度作为转向内环的输入可以使得转向闭环控制系统响应更快,更稳定。由于我们使用摄像头作为主要的寻迹方式,而摄像头处理图像信息的时间一般较长,所以协调好各部分的运行时序很重要,在平时调试时我们发现三轮 F车因为自身结构的特点,两个电机需要及时根据赛道中线偏差调整差速实现高速转向,所以需要摄像头处理赛道信息及时更新,加之闭环控制的运算量少,所以处理图像函数的优先级应该高于控制闭环函数的优先级 。

▲ 图3.6 三轮F车模PID控制流程图

2、直立D车模控制特点概述

  直立 D车姿态控制方面我们使用较为成熟的串级 PID,即姿态速度控制使用“角速度环-角度环-速度环”,串级 PID一共有三个环节,一个是速度环、一个是角度环、最后一个是角速度环,它们之间的连接关系是,速度环的输出表示期望角度然后送入角度环,角度环的输出表示期望角速度然后送入角速度环,角速度环的输出直接用于控制电机。串级 PID有便于理解以及调试方便等优势,可以很快的调试出比单环PID好的效果。转向使用同三轮 F车相同的控制方法,偏差外环和角速度内环串级。

  在平时调试过程中我们发现,因为直立比三轮控制闭环数多且需要使用陀螺仪进行高频率的数据采集和姿态解算,而因为其直立结构重心的特点,转向更为灵活,对赛道偏差更新速度要求不高,所以姿态数据采集和控制闭环处理函数的优先级应大于图像处理函数的优先级。

▲ 图3.7 直立D车模PID控制流程图

3、利用线程调度功能实现三轮差速控制

  相比于之前使用逐飞开源库进行裸机开发智能汽车控制程序时使用 PIT定时器来完成智能汽车的按键扫描、传感器信息采集、输出计算、输出控制,使用RT-Thread后,我们使用线程 +定时器的方式代替传统PIT定时将智能汽车运行时的每个任务模块化,将其分为 ADC电磁采集+红外测距模块采集线程、陀螺仪转向角速度信息采集线程、编码器两轮速度采集线程、五轴按键扫描线程、屏幕显示图像参数线程五个小线程,并将数据处理+差速输出函数放在单独的定时器中,以实现时序精确控制。

▲ 图3.8 逻辑开发程序流程图

  使用RT-Thread之前,我们队伍的工程将 run_realize()运动控制处理函数放在一个周期为 1ms的PIT中断中,通过全局变量 cnt_1ms的自加和清零来实现各闭环PID的时序控制,达到智能汽车按一定时序执行任务的目标。

▲ 图3.9 智能车逻辑开发推文僧啊全局变量作为计数器实现时序控制

  这种方法应该是被智能汽车竞赛选手一直以来沿用的,但是这种方法存在诸多缺点:

  程序可读性不强,需要较多注释辅助理解程序结构。

  各闭环 PID计算任务之间是顺序执行,没有优先级概念,当执行一般任务时如需要紧急执行某个任务(如摄像头补线、特殊元素识别),任务的实时性便会大打折扣,可能造成小车识别出错,甚至冲出赛道。

  未实现程序模块化,当程序某一部分出现问题时需要一点点调试寻找,且会直接影响小车整体任务执行。

  使用RT-Thread的线程调度功能后,可以将各任务模块化,拆分成一个个可单独操作的线程,不仅增强了程序的可读性,且大大增加了小车整体程序的实时性。

▲ 图3.10 三轮F车模线程划分

  上图是使用RT-Thread后三轮小车的线程划分,一目了然,程序可读性很好,当某个模块出现问题时,可以单独直接开关线程,不影响其他功能的正常执行。同时,每个线程有自己的时序ti ck,设置好各功能的优先级,便可以由RT-Thread内核实现各线程的自动调度。

  下图为使用RT-Thread后三轮小车的系统流程图。

▲ 图3.11 使用RT-Thread后小车运行流程图

4、利用线程调度功能实现直立姿态调节

  使用RT-Thread之前,我们的工程是将转向控制函数 turn_realize()、电机输出 pwm函数 motor_pwm_Calculate(Speed_L_Result, Speed_R_Result)单独放在一个定时器中断里,并将姿态解算函数 AD_Calculate()、转速计算函数 Speed_Calculate(g_fCarAngle,angle_dot)放在另一个定时器中断中,而电感、红外和陀螺仪传感器的数据采集也放在这两个中断中,由 cnt_Av以及 cnt_speed的增减与清零来实现时序控制,以实现直立车姿态控制、速度控制、转向控制等功能。

  对于直立车的姿态控制,类似于三轮控制思路,使用 PIT定时器来完成小车的信息采集以及输出控制,使用 RT-Thread后,我们使用线程将每个任务模块化并分为ADC电磁采集+红外测距模块采集线程、陀螺仪信息采集以及互补滤波线程、编码器两轮速度采集线程、五轴按键扫描线程、屏幕显示图像参数线程、摄像头处理图像线程六个小线程,并将数据处理与输出函数放置在两个不同的定时器中来执行,以实现合理而又准确的时序控制。

▲ 图3.12 直立D车的线程划分

四、通过RT-Thread的邮箱功能配合线程功能实现蜂鸣器指定

1、时长发声

  在智能汽车调试时,广为参赛选手所使用的方法是在识别到特殊元素或者某个标志位成立的时候使蜂鸣器发声,实时观察小车是否识别到特殊元素以及识别特殊元素的位置。即在相应赛道信息处理函数中使用 if函数,判断标志位的数值是否满足要求,然后调用gpio_set()函数来操作蜂鸣器。

  但是,当存在多个特殊元素需要识别时,如三岔路口、环岛,我们无法根据蜂鸣器的发声情况来得知识别到了哪个特殊元素。由于系统周期比较短,传统方法很难做到使用延时函数让蜂鸣器发声指定时长,而且在摄像头处理函数中使用延时函数实现蜂鸣器定长发声发生会造成很多时序问题,只能实现如在标志位置 1时鸣叫,在标志位置 0时不鸣叫,这就给使用蜂鸣器区分多种不同元素带来了很大困难。

  通过RT-Thread的邮箱功能,我们可以在需要使用蜂鸣器时调用蜂鸣器线程,此时蜂鸣器线程向邮箱发送邮件,蜂鸣器开始鸣叫。在指定时长后,将该线程挂起,此时邮件发送停止,蜂鸣器停止鸣叫,这样就实现了蜂鸣器指定时长发声的功能,创建蜂鸣器单独线程。

▲ 图3.13 蜂鸣器线程

五、通过RT-Thread的事件集功能实现标志位功能

  在智能汽车裸机开发时,我们常常使用全局变量作为特殊元素的标志位,例如根据采集到的赛道信息特征判断环岛、坡道等元素的不同阶段,分别为相应标志位赋予 0123等特定数值,然后在运动控制函数中根据标志位的不同状态,来控制小车执行出入环岛、直弯道、上下坡道的速度控制、摄像头前瞻设定、蜂鸣器提示等特殊操作,从而顺利完成赛道运行任务。

  使用RT-Thread后,我们可以利用事件集功能,将环岛、坡道等元素的标志位归类到同一个事件集中。

▲ 图3.14 满足不同条件后出发时间几种不同事件

▲ 图3.15 检测到坡道事件2 后调整三轮小车的打脚行

  在读取状态时,我们可以根据使用场合的不同,修改事件触发的条件。比如,在智能汽车进入正式运行模式之前,我们往往是将小车拿在手中,用屏幕和五轴按键进行参数调节和预先设置,但此时小车实际上各传感器已经初始化完成且进入了运行模式,如果不做特殊处理,小车很可能在调参时进入某个特殊元素的标志位,导致小车运行过程出现问题冲出赛道。

  对于这种情况,我们便可以使用RT-Thread的事件集功能进行解决。例如,对于坡道识别,我们使用了坡道状态事件集,并将该事件集的创建语句放在小车切换到正式运行模式之后,即当小车正式开始运行后,才正常进行坡道的识别输出处理,如果小车没有运行,即使超声波测距满足特定阈值时也不会触发坡道识别。这样,我们便通过 RT-Thread的事件集功能实现了标志位功能,而且相比于单独使用全局变量作为标志位的方法,无疑发挥出了更好的作用。

§04 总结


一、研究工作总结

  由于我队之前没有接触过两轮差速转向系统和自平衡系统,在初次搭建车模之前参考了传统三轮车模和直立车模的搭建方式,之后根据实际调试和交接任务需求,多次调整搭建方式,电路板我队自主设计了主控驱动运放一体板,并根据实际需要使用3D打印制作了相关的零件结构,并尝试有创新性的搭建方式,根据车模结构特点和软件控制特点及时调整硬件系统,搭建的车模硬件系统可以很好的克服原有车模的硬件缺点,进一步提高了车模上限。

  软件方面,备赛初期,直立控制是难点,我队查阅和学习了智能车论坛开源优秀直立工程、逐飞科技直播培训的互补滤波方案、CS DN优秀直立控制博文,使用了串级 PID方案控制姿态和转向,三轮车模的差速转向闭环控制系统也借鉴模仿直立车模的转向控制,并根据实际调试和车模结构特点使用分段参数、指数P等PI D控制思路,最后我队通过对双车交接任务的调试、摄像头算法的完善、PI D参数的整定,实现了基本竞赛任务。

  在传统裸机调试能够较为顺畅完成竞赛任务后,我队使用了逐飞科技提供的移植了RT-Thred的MM 32F3277开源库,通过研究相关培训视频、阅读了 RT-Thread API参考手册和相关技术文档、查阅和收集了有关RT-Thread操作系统的文献和和使用了该操作系统的类似工程代码,了解了 RT-Thread操作系统的基本特点、内核架构、线程管理、时钟管理、线程间同步和通讯的特点以及基本操作。之后我们参考了工程样例和类似开源工程将裸机代码工程移植到 RT¬Thread开源库中吗,并根据三轮车和直立车控制系统特点,给信息采集、蜂鸣器、人机交互(按键扫描、屏幕显示)、运动控制等任务分配了不同的线程和定时器,通过调试线程栈、时间片和优先级,基本实现了裸机开发的全部功能。

二、RT-Thread在智能车制作中的作用总结

  根据第十六届全国大学生智能汽车竞赛双车接力组中规定使用的三轮车模和直立车模的结构特点和控制特点,直立车模的闭环 PID控制任务多、控制量多,对传感器获取姿态和赛道等信息和姿态结算的实时性要求高,同时串级 PID还需要控制任务安照精准的时序来执行,三轮车模对闭环控制函数执行的频率要求较高,同时要求图像处理算法及时更新赛道信息,便于闭环控制,传统裸机开发,只能使用定时器中断实现各种任务,需要系统对定时器个数、中断周期和优先级的有合理设置,这一定程度上增加了裸机开发的难度。所以使用RT-Tread操作系统开发,对于智能车系统这种任务多且独立、对时序的精准实现要求比较高的系统更有优势。

  使用了RT-Thread操作系统后,利用线程划分配合定时器执行信息采集、姿态结算、运动控制和人机交互(调试模式下的按键扫描+屏幕显示信息)、蜂鸣器提示等不同任务,根据实际调试情况,我们发现相比裸机开发,人机交互任务的实现有明显优化,相比裸机开发,显示图像和调试信息会占用一些时序资源,这会导致图像处理和摄像头采集时序滞后甚至混乱,以至于无法正常寻迹,但是给人机交互任务和图像处理、运动分配不同的线程和定时器之后,确保了优先级和时序的正确性和精准性,便于后续的参数整定和程序调试。

  根据实际调试情况,当前研究工作基本满足竞赛任务。

三、当前研究工作的不足

  RT-Thread内核的主要功能是向下管理所有硬件资源,向上为应用程序提供 A PI接口和软件服务,所有任务在内核的管理、同步和调度下有序运行。而此次本队伍使用 RT-Thread时虽然建立了有关传感器采集的线程,也使用了RT-Thread的定时器功能来运行数据处理功能,但对 CPU的资源调度方面略有欠缺,在分配资源时存在一些不太合理的地方,在线程规划、代码书写方面不够熟练。软件设计时存在一些较多的重复和遍历,对 CPU时钟配置方面不够友好。同时,RT-Thread应用在智能车系统时,其在物联网方面的优势没有得到较好地体现。此外,我们使用 RT-Thread相对偏重于每辆车单独的系统控制,两车信息传输的方式使用的依旧是传统的串口中断来接收数据,虽然本次项目中我们要传输内容非常少,但在未来数据较多的场合下通信方面的优化就显得更有必要。

四、工作展望

  第十六届全国大学生智能汽车竞赛设立RT-Thread专项奖,吸引了广大智能汽车竞赛参赛选手使用 RT-Thread进行智能汽车工程开发。未来,随着 RT¬Thread被更多的选手使用,智能汽车的工程将更加的标准化、统一化、模块化,同时可移植性和代码可读性也大大增加。

  智能汽车工程的模块化,有利于参赛选手对工程进行部分开源;而工程的标准统一化,有利于广大参赛选手对开源工程的代码结构理解,从而提高智能汽车参赛选手之间互相交流与分享的效率,提升智能汽车参赛选手的整体水平这样,运用RT-Thread后,在降低智能汽车竞赛入门门槛的同时,又提高了智能汽车运行上限。

  未来,随着智能汽车任务的日益多元化、复杂化,RT-Thread嵌入式开发系统将会有更多功能被参赛选手挖掘出来,应用到智能汽车竞赛上,帮助智能汽车运行得更快更好。

※ 参考文献 ※


[1] 魏磊 ,李兴旭,高琴,张猛.基于电磁三轮系统智能车的传感器排布方案与控制策略 [J].科技与创新,2019(02):21-23.

[2] 王振运.基于 RT-Thread和STM32的双轮自平衡机器人的设计与实现[D].中北大学,2016.

[3] RT-Thread. RT-Thread API参考手册 [DB/OL]. https://www.rt-thread.org/document/api/index.html


● 相关图表链接:

  • 图1.1 三轮F车模差速转弯结构模型
  • 图1.2 三轮F车模俯视图
  • 图1.3 三轮F车模侧面图
  • 图1.4 逐飞样车将摄像头干固定在车模头部
  • 图1.5 电池支架和摄像头固定方式
  • 图1.6 使用Solidworks建模电池支架
  • 图1.7 直立D车模的V字型结构
  • 图1.8 一体板上的开孔处理
  • 图1.9 前后粘贴磁铁的乒乓球
  • 图1.10 使用SolidWorks建模的三轮F车的送球座
  • 图1.11 直立车模底盘后面的接球结构
  • 图1.12 智能车逻辑运行流程图
  • 图1.13 RT-Thread运行流程图
  • 图2.1 RT-Thread 系统架构
  • 图2.2 逻辑和RTOS处理任务模式对比
  • 图2.3 使用ad_val数据存放采集到的电感数值
  • 图2.4 特殊元素判断直接读取数值
  • 图2.5 事件集判断坡道标志位
  • 图2.6 RT-Thread+逐飞开源库MM32F3277工程目录
  • 图2.7 RT-Thread开源库中的 Smart_Car_Demo工程
  • 图3.1 RT-Thread启动流程图
  • 图3.2 传统逻辑开发的初始化函数
  • 图3.3 逻辑开发中在main函数调用初始化函数
  • 图3.4 RT-Thread中六个INIT宏接口
  • 图3.5 引用宏达到自动初始化的效果
  • 图3.6 三轮F车模PID控制流程图
  • 图3.7 直立D车模PID控制流程图
  • 图3.8 逻辑开发程序流程图
  • 图3.9 智能车逻辑开发推文僧啊全局变量作为计数器实现时序控制
  • 图3.10 三轮F车模线程划分
  • 图3.11 使用RT-Thread后小车运行流程图
  • 图3.12 直立D车的线程划分
  • 图3.13 蜂鸣器线程
  • 图3.14 满足不同条件后出发时间几种不同事件
  • 图3.15 检测到坡道事件2 后调整三轮小车的打脚行

RT-Thread在16届智能车竞赛双车接力组中的应用相关推荐

  1. 第16届智能车竞赛双车接力组—直立车经验语录

    第16届智能车竞赛双车接力组-直立车经验语录 前言 直立环核心控制算法-串级PID 转向环控制算法 算法框架 搭车方法 波形拟合 调车方法 角速度环整定方法 角度环整定方法 速度环整定方法 转向环整定 ...

  2. 第十七届智能汽车竞赛-多车编队组入门讲解

      从单车到双车追逐,从双车追逐到双车超越,从双车超越到双车接力,从双车接力到多车编队,智能车变革从未停下脚步,参赛同学们又该如何高效准备并顺利完成比赛任务呢?

  3. 【第17届智能汽车竞赛】极速越野组——处理GPS点位的一种方法(Python-matplotlib实现手动鼠标移动坐标点的应用)

    GPS点位的修改是一个比较麻烦的过程,需要来回采集点位和修改程序,给调试工作带来了一定的工作量.下面这种方法可以在一定程度上方便GPS参数的修改与调试,先说一下实现的效果,可以绘制出GPS轨迹图.一键 ...

  4. 全国大学生智能车竞赛双车接力组芯片申请汇总

    §01 申请情况汇总   各参赛学校的参赛同学们你们好,截止到2021年6月1日,灵动MM32SPIN27.MM32F3277样片申请审批通过的学校名单如下,申请成功的都进行了邮件回复,未通过审核的申 ...

  5. 第16届智能车竞赛参赛队员提问-05-24

    简 介: 本文收集了参加第16届智能车竞赛同学在5月20号之后的一些提问. 关键词: 智能车竞赛,提问 §01 阳光的眷顾 卓大大,请问今年的室内组,会有这样的光吗? ▲ 上帝之光照射在赛场上 回复: ...

  6. 第16届智能车智能视觉组-上海交通大学AuTop战队算法分享

    简 介: 参加了第十六届智能车竞赛室内视觉AI组别同学留下的对于智能车竞赛参赛感悟与建议.并对于自己参赛过程中学习研究的算法进行开源并给出了详细的介绍. 关键词: 智能车竞赛,SJTU-AuTop,视 ...

  7. 智能车竞赛,AI视觉组赛题浅析

    逐飞科技 2021-01-07 Thursday ▌01 前言   各位车友好, 第十六届全国大学生智能车竞赛竞速组规则 发布后,大家已经注意到由恩智浦赞助的 AI视觉组 是最具有综合性的一个组,感谢 ...

  8. 第16届智能小车用AURIX™ 资料汇总

    Hi,同学们!第16届智能小车用英飞凌AURIX™ 32位单片机TC212, TC264, TC364, TC377所需的 各类官方资料汇总 在此. 请大家持续关注,我们会陆续在此更新. ➤大赛简介及 ...

  9. 第十六届全国大学生智能汽车竞赛总决赛 AI视觉组线上赛图片显示软件发布及线上赛注意事项

    简 介: 本文对于第十六届全国大学生智能车竞赛视觉AI组线上比赛的识别任务软件以及相关比赛流程注意事项进行总结. 关键词: 智能车竞赛,视觉AI组 §01 积分分值   根据 第十六届全国大学生智能车 ...

最新文章

  1. python一加到二十_46 python学习笔记
  2. Python 数据类型之字典
  3. Wdcp在安装memcached出现错误的解决办法
  4. Only the original thread that created a view hierarchy can touch its views——Handler的使用
  5. 演示教学法在计算机基础课程中的应用,演示教学法在《计算机基础》课程中的应用...
  6. 携带token的ajax请求方法封装
  7. 基于AJAX的自动完成
  8. 关于移动安全的一点总结
  9. [译]理解Node.js事件驱动机制
  10. UOJ#211. 【UER #6】逃跑 (Dynamic Programming)
  11. 中文翻译The Django Book
  12. 依赖注入框架 ----Dagger2 使用详解及源码分析
  13. 数据透视表练习表格_将高级电子表格导出与PHP结合起来以创建数据透视表
  14. linux 查看链接文件,Linux下的链接文件详解
  15. 3D打印机打印中途停止且显示挤出头温度过低
  16. 剖析Android shape标签的绘制
  17. 5G聚合路由器有哪些优势?能应用在哪些场景?
  18. A 股历年三大财务报表
  19. js 让鼠标右下角有一排小字_JS实现跟随鼠标的链接文字提示框效果
  20. 别再说不会分析多选题了!这6种方法解决你的烦恼!

热门文章

  1. Java 基础算法 短板问题 : 你正在使用一堆木板建造跳水板。有两种类型的木板,其中长度较短的木板长度为shorter,长度较长的木板长度为longer, 你必须正好使用 k 块
  2. 共享出行化解城市交通难题(中)
  3. swagger中paramType请求类型为body
  4. httpf发送 json_Java用HttpClient4发送http/https协议get/post请求,发送map,json,xml,txt数据...
  5. 阿里云发布的数加是什么鬼
  6. 001] 智能手机操作系统介绍
  7. 动态规划+状态压缩思路解决旅行者问题
  8. User-Agent(用户代理)是什么
  9. python---字符串函数
  10. 【无为则无心Python基础】— 18、Python字符串的格式化输出