基于RTThread操作系统的基础四轮组智能车设计与实践腾讯云开发者社区

学校:同济大学队伍名称:智行·龙卷风参赛队员:杨怡,韦炳宇,许泽华带队教师:张志明,余有灵

全国大学生智能汽车竞赛是以智能汽车为研究对象的创意性科技竞赛,是面向全国大学生的一种具有探索性工程实践活动,是教育部倡导的大学生科技竞赛之一。该竞赛以“立足培养,重在参与,鼓励探索,追求卓越”为指导思想,旨在促进高等学校素质教育,培养大学生的综合知识运用能力、基本工程实践能力和创新意识,激发大学生从事科学研究与探索的兴趣和潜能,倡导理论联系实际、求真务实的学风和团队协作的人文精神,为优秀人才的脱颖而出创造条件。

基础四轮组比赛是在PVC赛道上进行,官方比赛赛道示意图如图1.1和图1.2中所示,赛道采用黑色边线和电磁进行导引。

▲图1.1第十六届全国大学生智能车竞赛基础四轮组

▲全国大学生智能车竞赛基础四轮组赛场

RT-Thread主要采用C语言编写,容易理解,移植方便,同时具有良好的可剪裁性,其架构图如图1.3中所示。Nano版本(极简内核)仅需要3KBFlash、1.2KBRAM内存资源,适用于资源受限的微控制器(MCU)系统。完整版可以实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似Android的图形界面及触摸滑动效果、智能语音交互效果等复杂功能,适用于资源丰富的物联网设备。相较于Linux操作系统,RT-Thread体积小,成本低,功耗低、启动快速。除此以外RT-Thread还具有实时性高、占用资源小等特点,非常适用于各种资源受限(如成本、功耗限制等)的场合。

▲图1.3RT-Thread系统的架构

对于第十六届智能车竞赛来说,MM32SPIN27、CH32V103由于内存较小,因此主要适配RT-ThreadNano版本的,这样可以减少RAM的开销。RT1064、RT1021、MM32F3277在智能车系统开发过程中使用RT-Thread的好处在于一方面可以充分发挥不同芯片的性能,让智能车跑的更加顺畅,另一方面提供了更高程度的抽象,屏蔽了不同单片机底层硬件细节,使得代码逻辑更为清晰,编写调试效率更高,移植性也更好。

本技术报告的框架大致如图1.4所示。

▲图1.4技术报告框架结构

智能车的机械结构和控制电路对赛车的性能影响巨大。具备一个好的机械结构平台,车身转向的灵敏性、直道行驶的稳定性、较高速度的抓地性才能得到很好地实现。因此,我们在不违反比赛规则的情况下对小车的结构进行设计和改进以使其具有良好的机械性能。同时,我们在整个系统设计过程中严格按照竞赛规范进行,本着可靠、高效的原则,在满足各个要求的情况下,尽量使设计的电路简单,PCB的效果简洁。

第十六届全国大学生智能车竞赛基础四轮组指定采用新B车模,车架长28.5cm,宽16.5cm,高6.0cm;底盘采用高强度玻纤板,具有较强的弹性和刚性;前轮调整方式简单,全车滚珠轴承、前后轮轴高度可调。驱动电机为RS540,伺服电机为SD-5舵机;轮胎经过软化剂处理,增强其耐磨性和摩擦力,车模整体质量较轻,车模照片分别如图2.1、图2.2、图2.3所示:

▲图2.1四轮车模俯视图

▲图2.2车模侧视图与前视图

车模机械结构具有如下特点:

车模的驱动板安装在车身的后侧,主板安装在车身中心偏后方,为使得整个车身的重心尽量落在车身中心以减少因速度快而产生甩尾和侧滑的现象,将两个电磁信号板安装与主板左右对称处,电池安装于主板下方偏前处,无线串口和电磁信号板均采用直插式封装,方便拆装。具体如图2.4所示:

▲图2.4四轮组车模电路板及电池安装位置

为了进一步提高车的性能,在保证车身在直行稳定的情况下能够更加轻便的过弯,能够有较大的过弯角度,经过查阅资料和实践的检验,我们最终采用前轮外倾,主销后倾,前轮前束的机械结构.

(1)前轮外倾:是指前轮安装后,其上端向外倾斜,于是前轮的旋转平面与纵向垂直平面间形成一个夹角,称之为前轮外倾角。通过前轮外倾的调节,使得前轮外倾和主销内倾相配合,可以减小主销偏距,使转向轻便。

▲图2.5起来跑外倾示意图

(2)主销后倾:在车身纵向平面内,主销轴线上端略向后倾斜,这种现象称为主销后倾。在纵向垂直平面内,主销轴线与垂线之间的夹角叫主销后倾角。通过设置主销后倾,可以保持小车直线行驶时的稳定性,并使小车转弯后能自动回正。一般来说,后倾角越大,车速越高,车轮的稳定性越强。但是后倾角过大会造成转向沉重,所以主销后倾角不宜过大,通过实际的实践体验,我们最终将主销后倾角设置为2°~3°。

▲图2.6主销后倾示意图

(3)前轮前束:是指前轮前端面与后端面在汽车横向方向的距离差,也可指车身前进方向与前轮平面之间的夹角,此时也称前束角。通过选择适当的前束角,可使前束引起的侧向力与车轮外倾引起的侧倾推力相互抵消,从而避免了额外的轮胎磨耗和动力的消耗,同时前轮前束还可以保证小车稳定的直线行驶,使转向轮具有自动回正的效果。经过多次尝试,智能车前轮前束的调整如图2.7所示:

▲图2.7前轮前束示意图

▲图2.8调整后的四轮组车模前轮前束

▲图2.9电磁从梦境安装板是示意图

▲图1.10前侧采用三角形结构固定

硬件电路系统是智能车运动控制系统的核心组件,为保证智能车稳定运行的基础,需要一个良好、稳定的硬件环境才能使得小车能平稳快速的行驶在比赛赛道上。

核心单片机子系统采用英飞凌半导体公司设计生产的TC264D芯片。该芯片采用双核TriCore架构,最高主频为200MHz,高达2.5MB的闪存与240KB的RAM,完全满足智能车控制的算力需求。为方便使用与后续更换,我们使用了逐飞科技公司生产的TC264单片机系统板,原理图如图2.11所示:

硬件电路的电源由18650锂电池提供(额定电压7.4V,容量2000mAh)。由于不同电路模块中所需要的工作电压和电流量各不相同,所以我们采用了三个稳压电路将电源电压转换成各模块需要的电压。

Vout=1.240*(R3/R5+1)(1)

电路如图2.12所示:

▲图2.12直流6V稳压电路原理图

▲图2.13直流5V稳压电路原理图

▲图2.14直流3.3V稳压电路原理图

对赛道上电磁信号的采集和处理是智能车最重要的模块之一,根据变化的磁场信号做出灵敏的检测对控制智能车在赛道上稳定运行起着至关重要的作用。

智能车比赛赛道铺设有中心电磁引导线,其中通有20kHz,100mA的交变电流。根据麦克斯韦电磁场理论,交变电流会在周围产生交变的电磁场。因此我们采用10mH的工字电感和6.8uF的小温差电容组成串联谐振电路,来实现对20kHz信号的选频和将赛道的电磁信号转换成电压,从而完成对信号的采集。接下来就是对收集到的电压信号进行滤波、放大、整流用于单片机ADC模块转换成数字量。放大电路和电感排布方案如图2.15中所示。

▲图2.15电磁信号放大电路和电感排布方案

在上图的电感排布方案中,水平电感1、7主要用于检测弯道,在小车进入弯道过程中,可以根据两端感应电动势值判断智能车与赛道中心线的偏离方向及偏差量。电感4用于检测赛道中心的电磁线,当智能车偏离赛道中心线不多时,该电感的变化程度较小,当偏离程度较大时,该电感的感应电动势值突然下降很快,因此可以根据该电感的变化情况更加精确地得出智能车与赛道中心线的偏离程度。电感3、4用于检测岔路。电感2、6主要用于引导智能车入环岛,在环岛路段,靠近环岛的那个竖直电感的感应电动势会比远离环岛的竖直电感的感应电动势大许多,可以使用这两个电感引导智能车进入环岛。

电机驱动电路采用双极性PWM全桥电路,可实现电机正反转以及可调占空比控制电机转速。该电路能控制电机正反转运行,具有启动快、调速精度高、动态性能好、调速静差小、调速范围大;能加速、减速、刹车、倒转;能在负载超过设定速度时,提供反向力矩、能克服电机轴承的静态摩擦力,产生非常低的转速等多方面优点。电路原理图如图2.16所示:

▲图2.16双极性PWM全桥电机驱动电路

我们的智能车使用龙邱智能科技的512线mini型编码器,使用减速齿轮和联轴器加载到小车的动力轮上,进行小车的测速,工作电压范围3.3V-5V。单片机通过读取编码器脉冲数来实现对智能车速度的测量。

设计各功能电路的PCB,打样后的PCB设计图和实物照片分别如下图中所示,包括:电路底板(图2.17),电磁信号放大板(图2.18),电机驱动板(图2.19),电磁信号放大板(图2.20)。

▲图2.17电路板的PCB与实物图

▲图2.18电磁信号放大版PCB与实物图

▲图2.19电机驱动板的PCB与实物图

▲图2.20电感安装板的PCB与实物

本章介绍基于RT-Thread的四轮组车模软件设计。

本章首先基于四轮车任务背景介绍软件算法,接下来分析裸机大while()+中断型开发模式的架构,并引出为什么要使用RT-Thread操作系统,后续部分从PID算法着手,相对全面地介绍基于RT-Thread的四轮车软件算法设计的各个方面。

四轮车组别的主要任务有数据采集类、信号处理算法类、人机交互类、控制类等,其中:

这样一来,一辆四轮车就具备了巡线的基本功能,再加上对特殊赛道元素的正确判断,就能初步具备完赛的能力。

▲图3.1四轮车组别主要任务

传统的裸机大while()+中断模式又称为前后台系统,前台指中断,后台指main()函数里的主循环while(1)。初学编程的时候老师会强调,循环一定要有退出的条件,不可以死循环。但是在嵌入式开发不用操作系统的情况下,一般都是用main函数里while(1)无限循环的方式去编程的。中断可以打断main()函数,保证一定的实时性,而中断也可以被优先级更高的中断打断。

▲图3.2裸机大while()+中断模式

在嵌入式编程发展的早期,是用裸机大while()+中断模式编程的。优点是上手容易,处理简单任务绰绰有余。而随着计算性能的提高,嵌入式编程的发展是从简单到复杂、从单任务到多任务,加上物联网的兴起,要处理的任务也是越来越复杂。这种模式渐渐显露出弊端。

当然,说了这么多,只用裸机大while()+中断模式能不能把车做好?答案是肯定的,历史上的许多神车都是用这种模式做出来的。但是,对于大多数同学,如果有一种效率更高,优势巨大的方法摆在面前,要不要用?答案也是肯定的。下面探讨一下相对于裸机大while()+中断模式,RT-Thread系统的巨大优势。

RT-Thread系统在1.3节已有详细介绍。这里着重对比传统裸机大while()+中断模式分析其优劣势。

优势有很多,正好克服了裸机大while()+中断模式的劣势。

更灵活的任务处理和更好的实时性。线程数量不受限制,优先级最大256个。首先RT-Thread系统先天就有着处理复杂任务、多任务并发的属性。可以把不同的任务拆分成不同的线程,根据优先级让系统自动调度,更好地可以对多任务进行区别对待。如果优先级配置得当,不同任务之间相互的影响可以降到最低。显著的优势在于,delay()时会将线程挂起,把cpu使用权交出去,这时候cpu可以处理其他任务,显著提高cpu的使用率。

更方便的模块化开发和团队合作。如果是团队协作开发,那么可以各自写各自的线程,最后汇总、配置优先级启动即可。模块化开发也是用了面向对象的观点,屏蔽了一些底层的实现细节,可以更专注于所要解决的任务上,代码逻辑更加清晰,后续的拓展和维护也相对省力。

可重用性。这个是比较显著的优势。不同的平台编程逻辑可能有很大不同,就智能车而言,不同的组别平台就各有不同,同一个组别每一届的平台也可能会有变化。所以对于许多打算做两年或想换组别的同学来说,就免去了痛苦的从头开始的过程,直接一键无痛移植。对于其他的比赛或项目而言,如果RT-Thread系统对该平台有适配,则熟悉的编程逻辑和风格可以让同学更加游刃有余。

丰富的软件生态。这一优点可能在智能车竞赛中不那么突出,但是如果做物联网的一些比赛,丰富的第三方库会让人拍手称快。也许目前智能车面对的任务还不够复杂,但任务越来越复杂是大趋势,一些复杂的项目也是用RT-Thread系统处理的。通过做智能车熟悉了RT-Thread操作系统,也有利于未来自身嵌入式编程的发展。

将3.1软件算法进一步抽象,总体可以概括为:“一核心,两关键,多辅助”。“一核心”指运动控制器,“两关键”指赛道环境的采集(电磁采集和摄像头图像采集、编码器采集)和控制量的输出(舵机和电机的PWM输出),“多辅助”指的是通过按键、拨码开关、led灯、显示屏、蜂鸣器、串口等一系列手段增强调试效率。使用RT-Thread的线程调度、时钟管理、线程间同步、线程间通信等内核特性,有效解决了裸机大while()加中断形式带来的管理单一、无法处理复杂多任务的执行、调试不方便等痛点,在抽象层面更高的平台上,使得多任务运行更加得心应手,编程逻辑更加清晰,调试手段更加多样灵活。

由于小车速度很快,且感知、决策、控制存在必然的顺序,所以必须保证三者的周期性唤醒及执行的先后顺序。所以用RT-Thread操作系统提供的软定时器,timer1_pit_entry线程周期运行,周期为1个系统节拍。在定时器入口函数里面完成赛道环境的采集和处理,差比和计算,以及PWM波输出。

利用FinSH在实际小车运行过程中,我们使用ps命令列出系统中所有线程信息,包括线程优先级、状态、栈的最大使用量等,以此监测智能车的线程运行情况。

下图3.3为大while+中断控制逻辑,3.4为RTT多线程同步操作系统在四轮小车上的部署方案。

▲图3.3大while+中断算法逻辑

▲图3.4RT-Thread系统算法逻辑

一核心指的是运动控制器,本次比赛我们采用经典控制算法PID控制,如图3.5和图3.6中所示,根据系统输入与预定输出的偏差的大小运用比例、积分、微分计算出一个控制量,将这个控制量输入系统,获得输出量,通过反馈回路再次检测该输出量的偏差,循环上述过程,以使输出达到预定值。

▲图3.5使用PID控制器的经典负反馈算法

▲图3.6使用PID控制器的负反馈控制算法应用

位置式PID算法可由公式(2)表达:

而增量式PID算法可由位置式PID算法推导得到,如公式(3)中所示:

位置式PID特点主要是一方面控制的输出与整个过去的状态有关,用到了误差的累加值(即用误差累加代替积分),另一方面公式输出直接对应对象的输出,对系统影响大。

而增量式PID特点有不需要累加误差,控制增量仅与最近几次的误差有关,不容易产生误差累积,仅输出控制量增量,误动作影响小,不会严重影响系统。

两者的主要区别在于,位置式PID控制算法是一种非递推算法,其输出直接控制执行机构,输出的量与执行机构的位置(例如阀门的开关量)一一对应。增量式PID控制算法是一种递推算法,输出的只是控制量的增量,技术输出的增量对应的是本次执行机构位置的增量而不是实际位置。PID控制并不一定要三者都出现,也可以只是PI、PD控制,关键决定于控制的对象。

舵机通常采用位置式PD算法,对于舵机的控制,因为不需要记录之前的误差因此这里将积分环节去掉,这样公式就变为。舵机控制代码实现如下:差因此这里将积分环节去掉,这样公式就变为。舵机控制代码实现如下:

//保存上次误差last_elect_val=curr_elect_val;//计算本次误差curr_elect_val=SET_POSITION-position;elect_val_delta=curr_elect_val-last_elect_val;//电磁变化率smotor_duty=(int16)(__skp*curr_elect_val+__skd*elect_val_delta);//进行PD运算smotor_duty=(int32)limit_ab((int16)smotor_duty,LMAX_DUTY,RMAX_DUTY);//限幅,pwm_duty(SMOTOR_CHANNEL,SMOTOR_CENTER+smotor_duty);//控制舵机转动电机通常采用增量式PID算法,因为增量式PID不容易造成电机的正反转切换,对速度的调节更为平滑。电机控制代码实现如下:

ek2=ek1;//保存上上次误差ek1=ek;//保存上次误差set_speed=SPEED;//进行增量式PID计算out_increment=(int16)(kp*(ek-ek1)+ki*ek+kd*(ek-2*ek2+ek2));//计算增量out+=out_increment;//输出增量out=limit(out,GTM_ATOM0_PWM_DUTY_MAX/2);//输出限幅,不能超过占空比最大值motor_duty=(int32)out;//强制转换为整数后赋值给电机占空比变量if(motor_duty>=0)//前进{pwm_duty(MOTOR2_CHANNEL,0);pwm_duty(MOTOR1_CHANNEL,motor_duty);}else//后退{pwm_duty(MOTOR1_CHANNEL,0);pwm_duty(MOTOR2_CHANNEL,-motor_duty);}(注:这里只展示PID算法部分,变量定义及初始化略)

模拟数字转换器即A/D转换器,或简称ADC,通常是指一个将模拟信号转变为数字信号的电子元件。通常的模数转换器是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小。故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小。而输出的数字量则表示输入信号相对于参考信号的大小。

ADC转换过程通常有采样(取样),保持,量化,编码四个阶段。

▲图3.7ADC转换过程

通常以输出二进制或十进制数字的位数表示ADC分辨率的高低,因为位数越多,量化单位越小,对输入信号的分辨能力就越高。

例如:输入模拟电压的变化范围为0~5V,输出8位二进制数可以分辨的最小模拟电压为5V×2-8=20mV;而输出12位二进制数可以分辨的最小模拟电压为5V×2-12≈1.22mV。

智能车ADC转换分辨率由板载ADC的具体型号决定,一般有8bit,12bit等。TC264D一般电磁循迹用8bit足够了。

首先要读取电磁信号,然后滤波,归一化处理。

读取电磁信号调用官方的库即可。这里注意adc句柄和通道号要与所画pcb的管脚定义相对应。

rt_adc_read(ADC_0,ADC0_CH0_A0);滤波方法有很多,常用的有均值滤波,平滑滤波,卡尔曼滤波等等。这里注意的是,不要贪多求全,适合自己的才是最好的。听上去很厉害的滤波方法可能效果不一定那么好,有的方法有可能是不收敛的。所以最好对自己使用的滤波方法心里有数。我们在加上三大滤波方法之后,发现滤波效果反而变差了,所以就只用了基本的均值滤波。其实现代码也很简单。

sum=0;for(i=0;i

需要注意的是:如果有限幅操作(1-100),则记录归一化数值时需要把车放在整个赛道上电感值最大的地方(如环岛),否则可能造成电感“饱和”的假象。如果没有限幅操作,则放在直道上读数即可。

for(inti=0;i<7;i++){AD_G_S[i]=((AD_data[i]-AD_min[i])*1.0/(s_AD_max[i]-AD_min[i])*100);if(AD_G_S[i]>100)AD_G_S[i]=100;elseif(AD_G_S[i]<1)AD_G_S[i]=1;}3.6利用定时器实现巡线循迹一般有电磁循迹和摄像头循迹两种方案。前者简单易上手,稳定性高,不容易受到赛道环境的影响。后者获取的信息量更大,上限更高,稳定性较低,受场地阳光等影响较大。由于今年四轮组要求摄像头高度不超过不容易受到赛道环境的影响。后者获取的信息量更大,上限更高,稳定性较低,受场地阳光等影响较大。由于今年四轮组要求摄像头高度不超过不容易受到赛道环境的影响。后者获取的信息量更大,上限更高,稳定性较低,受场地阳光等影响较大。由于今年四轮组要求摄像头高度不超过不容易受到赛道环境的影响。后者获取的信息量更大,上限更高,稳定性较低,受场地阳光等影响较大。由于今年四轮组要求摄像头高度不超过

循迹算法常用的是差比和算法,差比和偏差曲线与理想偏差曲线如图3.8中所示。

▲图3.8差比和偏差曲线与理想偏差曲线

算法基本思路如下:

Position=(a-b)/(a+b)(a和b是左右电感的值)计算出的数值绝对值大小表示偏离赛道的程度,在一定范围内车模偏离赛道越远计算出来的值越大。下面利用这个数据就可以控制舵机,来使得车模一直沿着赛道中心线前进了。

//差比和__S_diff=(elect_L-elect_R)*100;__S_sum=(elect_L+elect_R);S_position=__S_diff/__S_sum;//丢线保护if(__S_sum<__LOSELINE){if(elect_L>=elect_R){pwm_duty(SMOTOR_CHANNEL,SMOTOR_CENTER-L_DUTY);//左打死}else{pwm_duty(SMOTOR_CHANNEL,SMOTOR_CENTER-R_DUTY);//右打死}}else{//计算本次误差curr_elect_val=SET_POSITION-position;}这样一来就可以实现电磁巡线的效果。

把这一系列电感采集与处理、pid计算、pwm输出放在一起,就可以实现初步的智能小车巡线了。下面展示这一功能的实现过程。放在定时器进程里面运行,来保证执行周期是固定的。创建定时器和创建线程的方法类似,创建线程的方法放在3.4.1部分详细说明。

rt_timer_create("timer1",timer1_pit_entry,RT_NULL,1,RT_TIMER_FLAG_PERIODIC);这里timer1表示定时器的名称,限制在8个字符以内。

RT_NULL表示不需要传递参数,

RT_TIMER_FLAG_PERIODIC表示定时器以周期运行,如果设置为RT_TIMER_FLAG_ONE_SHOT则只会运行一次。

这些内容可以通过RTT官方文档和API手册来了解。

voidtimer_pit_init(void){rt_timer_ttimer;//创建一个定时器周期运行timer=rt_timer_create("timer1",timer1_pit_entry,RT_NULL,1,RT_TIMER_FLAG_PERIODIC);//启动定时器if(RT_NULL!=timer){rt_timer_start(timer);}}创建好定时器以后,就可以编写定时器超时函数了。可以理解为周期性执行的线程,而之前无限次执行的线程一般周期性不能得到保证。

每进函数一次,time++。

if(0==(time%5))保证5个周期进一次。然后依次执行,电磁信号采集、归一化、舵机PID控制,采集编码器数据和电机的PID控制。

voidtimer1_pit_entry(void*parameter){staticuint32time;time++;if(0==(time%5)){//电磁信号采集、归一化、PID控制elec_calculate();//采集编码器数据encoder_get();//控制电机转动motor_control();}}这样小车就基本具备自主巡线的功能,同时拥有舵机的方向环和电机的速度环。

显示屏可以在车上直接看数据,如图3.9中所示,相比串口通信调试更加直观好方便,显示信息更加丰富,由于采用了spi协议,速度也更快。

▲图3.9辅具调试显示屏

显示屏显示作为一个独立的线程,首先需要初始化。初始化由以下几部分组成,创建线程句柄,初始化外设(这个直接调厂商的库函数),然后创建显示线程并启动,优先级设置为31。这里注意的是,我们做车一般32个优先级就够了,0为最高的优先级(不同的厂家对优先级的顺序处理可能不一样,RTT和ST都是0最高)。为什么显示线程的优先级是最后一个呢?这是因为,相对于信号的采集、处理、计算,以及pwm输出等操作,显示部分只影响我们读数,不影响小车的正常运转,所以显示就不那么重要。因此,显示的线程最低,为31。调试过程中发现,显示屏显示和串口传输是非常消耗资源的一件事,如果把这些任务放在中断里则无法保证中断的实时性和周期性。统一放在main()的while(1)里面则许多任务无法区分优先级。

这时候,RT-Thread的线程概念就完美解决了这个问题。把不同的任务放在不同的线程,并分配好相应的优先级。则低优先级任务不会影响高优先级任务的执行,高优先级任务挂起的时候,也可以很好的利用处理器的空闲资源来处理低优先级任务。

下面是线程创建的具体操作。创建好之后,就需要启动显示线程。由于是本文中首次接触线程的创建,所以带有详细的注释讲解。

voiddisplay_entry(void*parameter){while(1){if(gpio_get(P14_4)){if(0){for(int16i=0;i<7;i++){lcd_showuint16(5,i,signals_long[i]);//lcd_showint8(60,i,send_buff[i+2]);lcd_showuint16(120,i,signals_short[i]);}rt_kprintf("point_num:%d\n",point_sum);}if(1){for(int16i=0;i<7;i++){lcd_showuint16(5,i,AD_G_L[i]);lcd_showuint16(120,i,AD_G_S[i]);}if(ShortSmotorFlag==0){lcd_showstr(120,7,"");lcd_showuint16(5,7,0);}else{lcd_showstr(5,7,"");lcd_showuint16(120,7,1);}virtual_Osc_Test();rt_kprintf("point_num:%d\n",point_sum);}}rt_thread_mdelay(10);}}入口函数是在哪,被谁调用的?答案是,入口函数是被系统内核调用的,只需要创建好并启动,系统就会自动调用入口函数。实际上,由于我们的线程是无限循环模式,即限循环模式,即限循环模式,即限循环模式,即rt_thread_mdelay(10);

这样做的意义在于:在实时操作系统中,线程中不能陷入死循环操作,必须要有让出CPU使用权的动作,如循环中调用延时函数或者主动挂起。

而放在while(1)的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。与之相对应的就是顺序执行或有限次循环模式,如简单的顺序语句、dowhile()或for()循环等,此类线程不会循环或不会永久循环,可谓是“一次性”线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。

在做一些赛道元素判断的时候,我们需要知道到底有没有触发条件。如果调试结果不如预期,就需要知道到底是车模运动的问题,还是判断的问题。而车在跑的过程中,看显示屏是不现实的,串口传输也很不方便。所以在某些元素位置满足条件让蜂鸣器响,是方便高效的调试办法。

和显示屏一样,蜂鸣器也需要创建线程,启动线程,编写入口函数。这里优先级设置为20,比显示屏更高,理由是蜂鸣器需要更及时的响应。

这里有三种操作蜂鸣器方式的比较:

if(ForkRoadstate){forkroadcount++;//pwm_duty(MOTOR1_CHANNEL,3000);//减速//发个消息让蜂鸣器响rt_mb_send(buzzer_mailbox,BBFORK);}3.7.3摄像头与信号量的保护同样的,摄像头部分也有类似的操作。这里摄像头就不再单独创建一个线程,而是直接放在main()函数的while(1)里。

为什么要等待摄像头采集完毕才开始处理摄像头图像呢?因为双方使用的数组是同一个,属于公共资源。如果不做这个处理,就有可能出现图像处理的过程中摄像头改写了相应的数组,使得这一张是由前一张的一半和下一张的一半拼接而成,也有可能造成流水线等待的迟滞。所以当摄像头采集完毕之后,会释放信号量,rt_sem_take(camera_sem,RT_WAITING_FOREVER);语句得到信号量之后继续执行下面的语句,否则持续等待并释放CPU控制权。信号量的英文是semaphore,可以理解成信号旗,只有得到了信号,才做相应的反应。这里的应用可以说非常直观了。

while(1){//等待摄像头采集完毕rt_sem_take(camera_sem,RT_WAITING_FOREVER);//开始处理摄像头图像DealGarage();//处理完成需要将标志位置0mt9v03x_finish_flag=0;//翻转LED,闪灯表示程序正在运行没有死机time++;if(time==500){time=0;gpio_toggle(P20_8);gpio_toggle(P20_9);}}这样以后,图像处理就完成了。

这里有个小Tips,就是可以在while(1)里翻转LED的GPIO引脚,闪灯则表示程序正在运行没有死机。

▲图3.10摄像头调试

voidbutton_init(void){rt_timer_ttimer1;gpio_init(KEY_1,GPI,GPIO_HIGH,PULLUP);//初始化为GPIO浮空输入默认上拉高电平gpio_init(KEY_2,GPI,GPIO_HIGH,PULLUP);key1_sem=rt_sem_create("key1",0,RT_IPC_FLAG_FIFO);//创建按键的信号量key2_sem=rt_sem_create("key2",0,RT_IPC_FLAG_FIFO);timer1=rt_timer_create("button",button_entry,RT_NULL,20,RT_TIMER_FLAG_PERIODIC);if(RT_NULL!=timer1){rt_timer_start(timer1);}}那么在入口函数这里编写自己需要的功能。一般是检测到按键按下之后并放开,释放一次信号量,然后再需要的位置接收信号量即可。

▲图3.11上位机显示的虚拟示波器图像

▲图3.12利用虚拟示波器和显示屏调试

同样的,这里将示波器也作为一个独立的线程。由于和显示屏的地位一样,所以优先级、大小等设置和显示屏线程是一样的。

voidvirtual_Osc_init(void){rt_thread_ttid;//创建示波器线程优先级设置为31tid=rt_thread_create("virtual_Osc",virtual_Osc_entry,RT_NULL,256,31,25);//启动示波器线程if(RT_NULL!=tid){rt_thread_startup(tid);}}同样的,编写线程入口函数,同样采用无限循环模式。

在实际小车运行过程中,我们使用ps命令列出系统中所有线程信息,包括线程优先级、状态、栈的最大使用量等,以此监测智能车的线程运行情况。

▲图3.14使用PS命令检测线程信息

另外,FinSH还提供了自定义命令的功能,使用者根据需要自定义命令。比如,我们编写了如,我们编写了如,我们编写了如,我们编写了

学习过程主要分为三部分,先分析小车裸机中断+大while()模式的架构,然后了解RT-Thread基本的特性,学习RT-Thread的内核部分。最后是迁移部分。

学习路径:通过RTT官方文档和逐飞的开源库进行学习,加以一些参考书作为辅助。

//————————————————————

这里的线程1也可以扩展为多个线程。例如,共有三个线程,线程1检测并发送按键状态,线程2检测并发送ADC采样信息,线程3则根据接收的信息类型不同,执行不同的操作。

线程1负责接收邮件,线程2负责发送邮件。

信号量工作机制:信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。信号量工作示意图如下图4.1中所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目。假如信号量值为5,则表示共有5个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。

▲图4.1线程间铜箔-信号量机制

当选择RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量。

▲图5.2FinSH的中断、线程间同步示意图

信号量的值初始为0,当FinSH线程试图取得信号量时,因为信号量值是0,所以它会被挂起。当console设备有数据输入时,产生中断,从而进入中断服务例程。在中断服务例程中,它会读取console设备的数据,并把读得的数据放入UARTbuffer中进行缓冲,而后释放信号量,释放信号量的操作将唤醒shell线程。在中断服务例程运行完毕后,如果系统中没有比shell线程优先级更高的就绪线程存在时,shell线程将持有信号量并运行,从UARTbuffer缓冲区中获取输入的数据。

整体迁移方案是将原来的单机大while()形式迁移到RTT操作系统下面。

具体的大while()+中断形式代码分析见3.2.

迁移思路如图4.3中所示,将前述智能车任务分解为多线程并发、系统自动调度机制下的RT-Thread系统模式。

▲图4.3迁移思路

现象:只有当图像处理结束以后,摄像头才能获取下一张。当采集结束以后,才开始处理。类似于adc转换的采样-保持-量化-编码阶段的关系。之前是使用全局变量的方式通信的,中断里放采集+大while()放处理模式。为什么要用信号量处理摄像头获取和处理图像的关系?

原因有二:一是因为原来用全局变量的方式去进行通信是不太好的习惯,,因为全局变量可能会增加系统的不确定性。所以我们对于全局变量的使用都是持“少用,慎用,不用”的态度。但是中断+大while()编程是离不开全局变量的。这也让我一直十分疑惑,不知道怎么解决这个矛盾的问题。而系统级别的通信方式就解决了这个问题。按照RT-Thread的特性,用信号量处理这个问题就十分方便。二是因为如果询问方式采用了while()等待的方式,那么如果条件不满足,cpu就会一直在while()里空转,直到条件满足。这样可能会造成cpu的无谓等待,降低cpu的利用率。

具体操作过程:

先创建摄像头的信号量,

camera_sem=rt_sem_create("camera",0,RT_IPC_FLAG_FIFO);选择RT_IPC_FLAG_FIFO(先进先出)方式,使得等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量。这样可以保证图像处理的完整性和一贯性。在while(1)里面轮询,采用无限等待的方式获取信号量,在获得信号量以后说明摄像头采集完毕,这时可以开始处理摄像头图像,

while(1){//等待摄像头采集完毕rt_sem_take(camera_sem,RT_WAITING_FOREVER);//开始处理摄像头图像DealGarage();//处理完成需要将标志位置0mt9v03x_finish_flag=0;}在MT9V03X摄像头场中断里,判断图像数组是否使用完毕,如果未使用完毕则不开始采集,避免出现访问冲突。如果图像数组使用完毕,则开启下一轮图像传输。图像传输采用DMA模式。

//---------------------------------------------------------------//@briefMT9V03X摄像头场中断//@paramNULL//@returnvoid//@sincev1.0//Sampleusage:此函数在isr.c中被eru(GPIO中断)中断调用//---------------------------------------------------------------voidmt9v03x_vsync(void){CLEAR_GPIO_FLAG(MT9V03X_VSYNC_PIN);mt9v03x_dma_int_num=0;if(!mt9v03x_finish_flag)//查看图像数组是否使用完毕,如果未使用完毕则不开始采集,避免出现访问冲突{if(1==link_list_num){//没有采用链接传输模式重新设置目的地址DMA_SET_DESTINATION(MT9V03X_DMA_CH,camera_buffer_addr);}dma_start(MT9V03X_DMA_CH);}}在MT9V03X摄像头DMA完成中断里面,如果采集完成,就释放摄像头信号量。这时候while(1)里面就开始处理图像了。摄像头部分就结束了。

具体操作:

为什么要用软定时器?由于小车感知、决策、控制存在必然的顺序,且要保证执行周期是固定的,所以要放在定时器线程里面运行。所以用RT-Thread操作系统提供的软定时器,timer1_pit_entry线程周期运行,周期为1个系统节拍。在定时器入口函数里面完成赛道环境的采集和处理,差比和计算,以及PWM波输出。

创建一个定时器周期运行,这里周期是一个系统节拍(1ms),可以保证精度。

timer=rt_timer_create("timer1",timer1_pit_entry,RT_NULL,1,RT_TIMER_FLAG_PERIODIC);然后在时钟线程里面,加入感知、决策、控制的代码。进一次线程time++,然后用(0==(time%5))保证5个系统周期。

详细的操作过程在3.6部分已有详细说明。按键和虚拟示波器线程前文已详述,此处不再赘述。

带来的好处:软定时器起到了代替原来PIT中断的作用,更多的是RT-Thread系统整体使用带来的一系列好处。软定时器带来的好处最明显的是可以有许多个定时器线程同时工作,不受硬件平台的限制,也提高了代码可移植性。

为什么要使用FinSH有了shell,就像在我们和小车之间架起了一座沟通的桥梁,开发者能很方便的获取系统的运行情况,并通过命令控制系统的运行。特别是在调车过程中,利用shell,除了能更快的定位到问题之外,也能利用shell调用测试函数,改变测试函数的参数,减少代码的烧录次数,提高小车的调试效率。FinSH是RT-Thread的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。

▲图4.14FinSH中断示意图

▲图4.5文件数

voidrt_hw_board_init(){get_clk();//获取时钟频率务必保留rt_hw_systick_init();/*USARTdriverinitializationisopenbydefault*/#ifdefRT_USING_SERIALrt_hw_usart_init();#endif/*Settheshellconsoleoutputdevice*/#ifdefRT_USING_CONSOLE//rt_console_set_device(RT_CONSOLE_DEVICE_NAME);#endif#ifdefRT_USING_HEAP//rt_system_heap_init(buf,(void*)(buf+sizeof(buf)));rt_system_heap_init(rt_heap_begin_get(),rt_heap_end_get());#endif/*Boardunderlyinghardwareinitialization*/#ifdefRT_USING_COMPONENTS_INITrt_components_board_init();#endifIfxSrc_init(&SRC_GPSR_GPSR0_SR0,IfxSrc_Tos_cpu0,SERVICE_REQUEST_PRIO);IfxSrc_enable(&SRC_GPSR_GPSR0_SR0);uart_mb=rt_mb_create("uart_mb",10,RT_IPC_FLAG_FIFO);}然后实现控制台的输出,需要在这里将串口输出函数放在这里(调用逐飞包装的英飞凌底层库)。

voidrt_hw_console_output(constchar*str){uart_putstr(DEBUG_UART,str);}这里我们在main()开头写一句测试函数,看控制台输出是否成功。我们可以看出,控制台成功输出了看出,控制台成功输出了看出,控制台成功输出了看出,控制台成功输出了看出,控制台成功输出了看出,控制台成功输出了

*Commandshell*/#defineRT_USING_FINSH#defineFINSH_THREAD_NAME"tshell"#defineFINSH_USING_HISTORY#defineFINSH_HISTORY_LINES5#defineFINSH_USING_SYMTAB#defineFINSH_USING_DESCRIPTION#defineFINSH_THREAD_PRIORITY20#defineFINSH_THREAD_STACK_SIZE4096#defineFINSH_CMD_SIZE80#defineFINSH_USING_MSH#defineFINSH_USING_MSH_DEFAULT#defineFINSH_ARG_MAX10既可以打印也能输入命令进行调试,控制台已经实现了打印功能,现在还需要在board.c中对接控制台输入函数,实现字符输入。接收字符有两种方式,一种是查询方式,一种是中断方式。这里我们用中断方式。

在英飞凌的isr.c中断文件页面,在串口中断函数里面我们加入串口处理程序。注意,这里我们用的是无线串口模块,所以应该把函数放在uart2的中断服务函数里面。另外这里是接收部分,所以是在IFX_INTERRUPT(uart2_rx_isr,0,UART2_RX_INT_PRIO)里。由于这里有个无线串口回调函数,仿照HAL库的编程逻辑,我把处理操作放在了回调函数里面。不过把处理操作放在这里更加直观。

//串口2默认连接到无线转串口模块IFX_INTERRUPT(uart2_tx_isr,0,UART2_TX_INT_PRIO){enableInterrupts();//开启中断嵌套IfxAsclin_Asc_isrTransmit(&uart2_handle);}IFX_INTERRUPT(uart2_rx_isr,0,UART2_RX_INT_PRIO){enableInterrupts();//开启中断嵌套IfxAsclin_Asc_isrReceive(&uart2_handle);wireless_uart_callback();}在wireless_uart_callback(void)函数里,先调用uart_getchar()函数接收数据,然后将数据放在邮件里发出去。这里没有使用全局变量的方式,也是为了避免while()查询条件的一直等待,从而可以不满足条件自动挂起线程,释放cpu,提高cpu的利用率。

voidwireless_uart_callback(void){//while(uart_query(WIRELESS_UART,&wireless_rx_buffer));//读取收到的所有数据externrt_mailbox_tuart_mb;uint8dat;enableInterrupts();//开启中断嵌套IfxAsclin_Asc_isrReceive(&uart2_handle);uart_getchar(DEBUG_UART,&dat);rt_mb_send(uart_mb,dat);//发送邮件}在rt_hw_console_getchar(void)函数里接收邮件,使用RT_WAITING_FOREVER的等待方式。如果接收到,则会返回dat.

charrt_hw_console_getchar(void){uint32dat;//等待邮件rt_mb_recv(uart_mb,&dat,RT_WAITING_FOREVER);//uart_getchar(DEBUG_UART,&dat);return(char)dat;}而在shell.c文件里面,finsh_getchar(void)函数会调用rt_hw_console_getchar(void)函数,从而完成通讯。

staticintfinsh_getchar(void){#ifdefRT_USING_DEVICE#ifdefRT_USING_POSIXreturngetchar();#elsecharch=0;RT_ASSERT(shell!=RT_NULL);while(rt_device_read(shell->device,-1,&ch,1)!=1)rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER);return(int)ch;#endif#elseexterncharrt_hw_console_getchar(void);returnrt_hw_console_getchar();#endif}其实FinSH是作为一个线程去查询接收的,这个线程在FinSH初始化的时候会被创建。移植效果如下图所示。注意,发送的命令后面需要加一个回车。

▲图4.7控制台实现输入输出

通过help命令可以看到可以调用的命令。这里显示了内置的命令和自定义命令。如果需要自定义命令,在自己写的函数下面加一句MSH_CMD_EXPORT(hello,sayhellotoRT-Thread);即可。第一个参数是命令的名称,第二个参数是命令的描述。(这里只用了无参命令)

过程中也出现了一些意想不到的问题,比如如果1ms改一次电机的PWM,程序会死机,分析原因可能是1ms一次太频繁,程序还没执行结束下一个中断又开始了,造成“栈爆”的结果。所以改为5ms一次后就解决了。还有一些其他的问题,也一一解决了。经检验,移植效果很好。

▲图5.1基础四轮组华东赛区初赛

▲图5.2华东赛区决赛基础四轮组赛场

▲图5.3华东赛区决赛颁奖现场图片

重要的是参考RTT官方的文档和逐飞的库。站在巨人的肩膀上可以更快的达成目的,完成移植。具体而言,即首先创建需要的线程及其初始化,创建时钟,然后把自己编写的函数.c和.h文件都复制到对应的文件目录,包含头文件和进行函数调用。然后就可以调试了。

THE END
1.转向节主销详解:它在车辆中扮演着怎样的角色?转向节主销是汽车结构中的关键组件,它犹如桥梁,联结着前桥与转向节,确保车辆在行驶时的稳定性与方向传递的精准性。其独特的结构,尤其是"羊角"般的设计,赋予了汽车转向系统不可或缺的灵活性。以下是转向节主销及其在汽车维修中的拆装步骤: 拆卸与保护: 首先,为了防止灰尘和水分进入,需要拆下拉杆的防尘套,用工具确保https://www.autohome.com.cn/ask/10756117.html
2.年底才是燃油车的春天?朗逸追平秦L,途观/荣放/CR另外,尤其值得一提的是,由于今年春节较早,再加上本轮汽车报废补贴以及“以旧换新”置换补贴即将于今年年底结束,所以为了赶上补贴,2025年1月份的购车需求有望集中到今年12月份释放。 这也就意味着,在返乡潮的推动下,没有里程焦虑和补能焦虑,且品质和质量经过了市场多年沉淀和考验的燃油车,更是有可能在12月份迎来https://www.163.com/dy/article/JIQQOP9Q0553TDSE.html
3.年底才是燃油车的春天?朗逸追平秦L,途观/荣放/CRV销量上涨这一系列数据事实上就在一定程度上说明了,我国汽车消费潜力仍然巨大,燃油车也仍然还有大量的市场需求。 另外,尤其值得一提的是,由于今年春节较早,再加上本轮汽车报废补贴以及“以旧换新”置换补贴即将于今年年底结束,所以为了赶上补贴,2025年1月份的购车需求有望集中到今年12月份释放。 https://www.dongchedi.com/article/7444830543851586074
4.智能车竞赛技术报告智能车视觉中国矿业大学它的作用是保障智能车直线运行时的稳定性,使其转向轻便并减少轮胎的磨损。前轮是转向轮,它的安装位置由主销内倾、主销后倾、前轮内倾和前轮前束等四个项目决定,反映了转向轮、主销和前轴等三者在车架上的位置关系。 1.1.1主销后倾角 在汽车纵向平面内,主销轴线上端略向后倾斜,这种现象称为主销后倾。在纵向垂直https://www.eefocus.com/component/503552
5.为什么骑自行车转弯后不需要回正车头,而汽车需要?所以,因为自行车、摩托车拥有更大的主销后倾角,且行驶速度较慢,摩擦力与惯性远远低于汽车(自行车回正更快)所以人们很难察觉。汽车主销后倾角一般不超过3°,且且行驶速度更快,摩擦力与惯性远大于自行车摩托车(汽车回正更慢)导致人们更容易察觉。 辅助原因: https://www.yoojia.com/ask/17-11854237715616370514.html
6.探店海狮05DMi:做工用料很下本,主销车型有现车凤凰网视频2024-10-13 15:17:4906:450来自重庆 车壹圈 透视汽车 悦享生活https://fo.ifeng.com/c/8deQSua4hCp
7.汽车前轮定位参数有哪些?3、主销内倾角同样在设计转向桥时,主销在汽车的横向平面内向内倾斜一个b角,即主销轴线和地面垂直线在汽车横向断面内的夹角。主销内倾角也有使车轮自动回正的作用,还可以使转向操纵轻便。https://auto.china.com/mip/332714.html
8.四轮定位包含什么?底盘调校参数之主销篇图:一般前轮或后轮驱动的汽车由于前轮是主动轮,因此主销后倾角会偏小以降低扭力转向的现象。 图:后轮驱动的汽车会使用较大的主销后倾角增加前轮(从动轮)的直线指向性。 主销后倾也有其他特性。当你打转向过弯时,内侧转向轮角度越大,倾角就会向正倾角摆动。而外侧转向轮的倾角就会加大负倾角的趋势,从而能够在弯中https://news.yiche.com/hao/wenzhang/32946804/
9.智能车竞赛技术报告智能车视觉中国矿业大学在纵向垂直平面内,主销轴线与垂线之间的夹角叫主销后倾角,其值大小对汽车转向与操纵性能密切相关。设置主销后倾角后,主销中心线的接地点与车轮中心的地面投影点之间产生距离,使车轮的接地点位于转向主销延长线的后端,车轮就靠行驶中的滚动阻力被向后拉,使车轮的方向自然朝向行驶方向。设定很大的主销后倾角可提高直线https://blog.csdn.net/zhuoqingjoking97298/article/details/120093315
10.皮卡车垂直门户网站,皮卡车价格及图片大全皮卡车优惠皮卡改装皮卡网由《中国汽车报》社主管主办。提供最新最全皮卡车价格行情,皮卡车权威资讯、皮卡图片、皮卡经销商、皮卡政策、皮卡视频、皮卡改装、皮卡社群等。https://m.cnpickups.com/
11.汽车维修检测工复习题(九)多项选择题E、 主销后倾角可以是负值 * 4.悬架的功用是()。 【多选题】 A、转向 B、缓冲 C、导向 D、减震 E、制动 * 5.检测电动汽油泵时,若无运转声,则应检查()。 【多选题】 A、电路熔丝 B、继电器 C、线路接触状况 D、进油管接头 E、出油管接头 * 6. 汽车的自诊断系统具有()功能。 【多选题】 Ahttps://www.wjx.cn/jq/95909927.aspx
12.马可波罗网主站汽车散热器 汽车水泵 汽车风扇 风扇离合器 水箱/膨胀箱 汽车水管 节温器/调温器 排气/转向系统 转向机总成 方向盘 助力器 转向节 转向拉杆 转向节主销 三元催化器 汽车消声器 EGR阀 制动系统 刹车蹄/刹车片 刹车盘/刹车鼓 制动器总成 驻车制动器 制动泵 汽配空压机 ABS防抱死制动系统 行走系统 前桥、前轴http://makepolo.com/
13.汽修实训报告实训内容(十四篇)有些同学的实习单位离学校很远,他们每天需坐车一个多小时或者更长时间赶到实习单位,上个月我们结束了汽车中级工的考试,在考前培训的时候,每周二次,下班后我们都要去学校进行复习训练,整整三个月,虽然有些辛苦但是我们没有怨言对于我们来说是值得的。 在这次实习阶段,我以一名企业员工的身份去对待,遵守企业的规章制http://cooco.net.cn/zuowen/2694492.html