资讯详情

基于RT-Thread开发智能视觉组智能车 - 温州大学 - 春华秋实

学校:温州大学团队名称:春华秋实参赛选手:陈俊达、程先春、倪世杰带队老师:王峰

简 介: 近年来,华为、特斯拉、谷歌、百度等大型互联网公司都在研究自动驾驶的话题。基于恩智浦的设计 RT1064芯片智能汽车 keil以开发环境为基础,利用环境 MT9V03X总钻风数字摄像头用作道路识别的传感器 S依靠3010转向舵控制汽车转向,依靠 RS380大电机和编码器构成闭环电路,控制小车速度。汽车模型使用摄像头收集轨道数据,根据差异和图像灰度跳点是轨道边缘,计算轨道中线,然后控制舵转向前进,同时使用 OpenArt mini摄像用于识别靶标图像,并使用它 RT-Thread创新使用操作系统,充分发挥芯片性能,提高车模完成任务的成功率。该操作系统功能强大,操作方便,学习方便,在提高车模性能和开发车模软件方面发挥了突出作用。

§01


1.1智能汽车生产情况

本文由全国大学生智能汽车竞赛官方指定 C车型模型为主体,采用两个直流电机驱动后轮,舵机驱动梯形转向机构完成前轮转向。在硬件电路周围 RT以1064微处理器为主控制单元, OpenARTmini主控制单元用于识别动物、水果和数字,其次包括 MT9V032摄像头采集,全桥驱动电路驱动直流电机,512线编码器作为速度反馈, LCD由屏幕和按钮组成的人机交互平台。其次,利用差异和像素阈值算法检测轨道边界线,然后通过图像处理获得中心边界线,路径识别和决策规划。最后,利用自动控制理论,建立相应的模型,设计跟踪控制器,包括 PID速度控制器和 PD方向控制器。通过模糊等大量测试数据优化控制器 PID控制,对 kp、kd动态变化值等。

1.2 RT-Thread技术概述

RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,其基本属性之一是支持多任务,允许多任务同时运行并不意味着处理器同时执行多任务。事实上,处理器的核心只能在某个时刻运行一个任务,因为每个任务的执行时间很短,任务和任务通过任务调度器快速切换(调度器根据优先级决定此时执行的任务),造成多个任务同时运行的错觉。在 RT-Thread通过线程实现系统中的任务, RT-Thread线程调度器是上述任务调度器。

RT-Thread主要采用 C语言编写简单易懂,移植方便。它将面向对象的设计方法应用到实时系统设计中,使代码风格优雅,架构清晰,系统模块化,割性好。针对资源有限的微控制器( MCU)通过方便易用的工具,用的工具切割 3KB Flash、1.2KB RAM内存资源的 NANO版本(NANO是 RT-Thread官方于 2017年 7月份发布的极简版核心);对于资源丰富的物联网设备, RT-Thread还可使用在线软件包管理工具,配合系统配置工具,实现直观快速的模块化切割,无缝导入丰富的软件功能包,实现类似 Android图形界面、触摸滑动效果、智能语音交互效果等复杂功能。

相较于 Linux操作系统, RT-Thread除此之外,体积小,成本低,功耗低,启动快 RT-Thread它还具有实时性高、占用资源少的特点,非常适合各种资源受限(如成本、功耗等)。虽然 32位 MCU它是它的主要运营平台,其实很多都有 MMU、基于 ARM9、ARM11甚至 Cortex-A系列级别 CPU也适用于特定应用场合的应用处理器 RT-Thread。

近年来,物联网( InternetOfThings,IoT)物联网市场发展迅速,嵌入式设备的联网已成为大势所趋。终端网络大大提高了软件的复杂性,传统 RTOS在这种情况下,物联网操作系统越来越难以满足市场需求( IoTOS)概念应运而生。物联网操作系统是指操作系统的核心(可以) RTOS、Linux等文件系统、图形库等相对完整的中间件组件为基础,具有低功耗、安全、通信协议支持和云连接能力的软件平台, RT-Thread就是一个 IoTOS。

RT-Thread与其他很多 RTOS如 FreeRTOS、uC/OS主要区别之一是,它不仅是一个实时核心,而且具有丰富的中间层组件,如下图所示。

▲ 图1.1 RT-Thread 中间层组件

我们采用 RT-Thread开发和运行代码大大提高了开发效率。任务调度的功能可以大大提高我们的利用率 CPU效率提高了摄像头的帧率,提高了车辆前进的上限,可以在各个方面轻松切换任务,真正减少了不必要的代码计算能力浪费和代码开发速度。

1.本文的概况和结构安排

  • 第一章描述了本设计的研究背景,阐述了智能汽车技术的发展,得出了本设计的主要研究工作和论文结构安排。
  • 第二章,智能车系统总体设计,先介绍设计思路,进一步简述本设计的系统结构,最后展示了智能车的整车布局情况。
  • 第三章,机械结构的设计结构,包括基于阿克曼转向原理的转向结构优化,
  • 获得合理的结构转向梯形结构,优化其他结构部分。第四章重点介绍了硬件电路的设计和优化 RT最小系统部分,电源部分,视频信号处理部分,驱动电路部分,速度检测部分。
  • 第五章,路径识别是视觉导航的关键技术。利用差异和算法检测轨道的两条黑色边界,通过特殊处理获得轨道中心边界,然后完成路径识别和决策规划。它还介绍了如何使用它 OpenArtmini实现功能和任务。
  • 第六章主要介绍速度和方向的控制 PID控制方法。第七章重点介绍了如何完成动物、水果和动物 Apriltag码的识别。
  • 第八章重点介绍了如何对待 RT-Thread移植和应用。
  • 第九章,重点介绍 RT-Thread创新使用操作系统
  • 第十章,总结。

§02 统总体设计


2.1系统概述

智能车的核心单元和 OpenARTmini恩智浦公司推出了核心单位 32位微控制器 RT1064。其采用 ARM-Cortex-M7芯片内核,芯片频率为 600Mhz,是 STM32F103系列的 8倍多,有 1M片内 SRAM和 4M片内 FLASH和丰富 IO口.

智能车系统的工作原理:

智能车系统的硬件分为摄像头自动跟踪识别 6个模块:主控制器模块、电源模块、轨道信息采集模块、智能视觉识别模块、方向控制模块、速度控制电路模块、摄像头自动跟踪智能汽车系统硬件整体设计框图 2.1所示。恩智浦公司推出的主控制器模块 RT1064系列的 32位微控制器 NXPRT作为智能车嵌入式系统的核心控制单元,1064是整个摄像头智能车"大脑",完成各项任务的分配和协调。电源模块负责提供各子模块所需的电压。通过摄像头采用轨道信息采集模块 MT9V03X通过路径识别算法提取偏差,将获得的数据存储在二维数组中。采用智能视觉识别模块 OpenARTmini上部署神经网络模型识别数字水果和动物,然后将识别信息传回车辆主控模块,然后判断下一步的行为.

方向控制模块使用 S3010转向舵机,转向灵活,反应速度快,扭矩大。舵机通过相应的控制算法灵活转向轨道中心线与汽车位置的偏差。 PID算法及灵活调整 PID参数,通过编码器读取的值来计算现场速度。车由摄像头采集图像内是否遇到斑马线然后判断黑白跳变条数来决定。

▲ 图2.1 系统结构框图

2.2整车布局

小车的布局本着轻量化的设计原则,尽可能降低重心以提高稳定性,具体包括具有以下特点

1 ) 舵机位置在前,立式安装,既保证响应速度又大多数符合阿克曼转角原理; 2 ) 电池用扎带固定在后方,平衡重心; 3 ) 摄像头通过碳杆、金属支架和 AB胶固定在舵机与主板中间,有效的采集赛道信息; 4 ) 云台在前,控制激光发射器在 15cm高度,将 OpenArt mini倒置安装减少打

靶的误差,同时后方的总钻风采用 33cm高度可以完美避开前方云台的视野遮挡。下图为详细的整车布局:

▲ 图2.2 整体布局

 

§03 械安装


3.1智能车整体参数调校

智能车的整体参数,都对整个智能车系统的稳定运行起着至关重要的作用,通过对小车的布局,尽量保证小车左右平衡,以及寻找一个合适的重心,保证小车既能够可靠地抓牢地面,又能够对前轮舵机,后轮电机有较快的响应。机械的变化可以提升小车的速度上限,另外,机械的稳定性非常重要,因此要尽量保证小车既能够可靠地抓牢地面,又能够对前轮舵机,后轮电机有较快的响应。

3.2前轮倾角调节

3.2.1主销内倾

主销在转向轴线上向内倾斜,主销轴线与地面垂直线在赛车横向断面内的夹角称为主销内倾角。主销内倾角也有使轮胎自动回正的作用,当汽车转向轮在外力作用下发生偏转时,由于主销内倾,则车轮将整辆车抬起,在外力消失后,车轮就会在重力作用下力图恢复到原来的中间位置,主要在低速时可利用内倾回复转向。

3.2.2主销后倾

主销后倾角的作用是当汽车直线行驶时,若转向轮受到外力作用而发生偏转,汽车会转向。这时,后倾角会使路面对车轮产生一个侧向反作用力,使车轮回正,从而保证在中高速行驶中汽车直线行驶的稳定性,适当的加大主销后倾角可以帮助转向轮自动回正,可有效扼制转向器的摆振。

3.2.3前轮前束调节

通过车轮中心的汽车横向平面与车轮平面的交线与地面垂线之间的夹角,称为前轮外倾角,前轮外倾可以抵消由于车的重力使车轮向内倾斜的趋势,减少赛车机件的磨损与负重,保证车辆行驶性能,前轮在滚动时,其惯性力自然将轮胎向内偏斜,抵消轮胎偏斜的力,从而减小磨损

3.2.4车轮外倾角

轮外倾角是指通过车轮中心的汽车横向平面与车轮平面的交线与地面垂线之间的夹角,对汽车的转向性能有直接影响,它的作用是提高前轮的转向安全性和转向操纵的轻便性。在汽车的横向平面内,轮胎呈 “八”字型时称为 “负外倾 ”,而呈现 “V”字形张开时称为正外倾。如果车轮垂直地面一旦满载就易产生变形,可能引起车轮上部向内倾侧,导致车轮联接件损坏。所以事先将车轮校偏一个正外倾角度,一般这个角度约在 1°左右,以减少承载轴承负荷,增加零件使用寿命,提高汽车的安全性能。

模型车提供了专门的外倾角调整配件,近似调节其外倾角。由于竞赛中模型主要用于竞速,所以要求尽量减轻重量,其底盘和前桥上承受的载荷不大,所以外倾角调整为 0°即可,并且要与前轮前束匹配。

3.3车模重心

在完成车模的搭建后,通过测量小车轮胎位置的质量测量出整个车模的质心分布。从而保证整个小车的稳定性,车体重心的位置对赛车加减速性能、转向性能和稳定性都有较大的影响,同时还应该尽量降低车模重心高度,防止车模行驶时发生侧翻

3.4舵机安装

舵机的安装方式有立式和卧式两种,经过分析,我们沿用经典的立式装法。舵机是具有较大延迟特性的器件,其延迟与其转角大小成正比,但如果能使舵机安装图机转过一个较小的角度而使车轮转过一个越大的角度,则会大大提高舵机过弯的响应速度。这不仅与舵机的安装方式有关,而且也与舵机输出臂的长度有关。

▲ 图3.1 舵机安装

安装时将舵机竖起,使得两条舵机拉杆几乎成一字型直线,某种程度上增长了拉杆的有效长度,可提高智能车转向控制的速度。

3.5摄像头安装

摄像头我们采用前后分配的安装模式,前方云台架起 15cm高的 OpenArt mini,尽可能的低的将摄像头高度降低,降低整车重心,因为云台虚位的原因,便在后方使用碳棒架起到一个更高的高度安装总钻风摄像头,并且调整俯视角度,尽量减少对于视野盲区的影响,同时保证了前方视野的前瞻来减少舵机的滞后性影响。

 

§04 路设计


4.1.总体结构图

▲ 图4.1.1 电路设计思路

硬件系统是整个智能车系统可靠运行的基础,更是软件程序得以稳定运行的基础,所以硬件系统的设计是非常重要的,我们的硬件系统主要包括电源管理模块,电机驱动模块,舵机驱动模块,电磁循迹模块,主要使用三块板子分别为母版,电机驱动以及电磁。

4.2.电源管理模块

▲ 图4.2 5V降压电路

图 4.2 5v降压电路首先是电源管理模块,我们采用的 7.2V2000mAh的供电电池。电机驱动需 要的就是 7.2v的电压,所以我们采用开关稳压芯片进行稳压输出,舵机工作电压范围为 4V到 6V,我们需要通过调节电位器使他的电压维持在 5v即可正常工作,单片机系统,电磁,以及下面的 oled等均使用的是 3.3V的电源。通过以上电路将 7.2v的 vccbat输入降到 5v进行输出。

▲ 图4.3.3 3.3V 降压电路

图 4.3 3.3v降压电路再通过如上电路将 5v降为 3.3v,其中 u6使用的是 rt9013-33gb

4.3单片机

▲ 图4.4 单片机电路

我们使用的是恩智浦公司推出的 RT1064系列的 32位微控制器 NXP.RT1064,更大的 RAM可以放更多的数组或者变量, RT1064通过快速 GPI0可以实现 150M的 I0翻转速度,由于 RT1064片内自带了 flash,因此 IDE (IAR、 MDK等)都会自带相应的下载算法,不需要自己手动添加,使用更加的方便, RT1064自带 ROMAPI可以非常方便的操作片内的 flash。

4.4电机驱动

将直接输入的电压先转化成 5v稳定电压,再通过单片机输入的 pwm波信号达到电机驱动目的。

▲ 图4.5 电机驱动原理图

▲ 图4.6 PWM 接口电路

▲ 图4.7 整体PCB电路板

4.5电磁模块

因为在路上有时仅凭摄像头会出现小误差导致偏移出赛道的情况,可能会冲撞导致损坏摄像头,所以我们使用电磁模块,来有效防止这一情况的出现,电路图如下:

▲ 图4.8 电磁模块原理图

▲ 图4.9 电磁整体PCB

第五章图像信息获取及处理 5.1赛道信息采集 赛道信息获取是智能车稳定快速运行的根本保障。赛道信息通过 MT9V03X总钻风摄像头进行采集, MT9V03X总钻风摄像头采集速率最高可达每秒 498帧,它具有 FPS可调,全局快门、高动态范围( High dynamic range)、可进行自动曝光等优点。同时 MT9V03X总钻风摄像头采集的是灰度图,具有更多的赛道信息。

图 5.1差比和寻线 (图片来自逐飞科技公众号,侵删 ) 往最长白列的左右开始扫描边界黑点,边界黑点的确认方式也采用差比和的方式,公式等于 abs(a-b)/(a+b)*100,与上面一样。例如我们现在需要找最近一行的左边界,我们取最近一行与最长白色列交点处的灰度值为 a,然后取该点左侧点的灰度值为 b,然后根据公式计算得出结果,如果差比和的值大于我们设置的阈值则认为 b点是边界黑点。如果不是我们将 b点的值赋值给 a,然后 b点左侧点的灰度值赋值给 b,再次计算并判断。

图 5.2差比和举例 (图片来自逐飞科技公众号,侵删 ) 上表列出了 4种情况 情况 1:黑点为 30,白点为 100,我们通过差比和公式计算并放大100倍,得到的结果为 53。 情况 2:白点 1为 90,白点 2为 100,我们通过差比和公式计算并放大 100倍,得到的结果为 5。 情况 3:黑点 1为 30,黑点 2为 35,我们通过差比和公式计算并放大 100倍,得到的结果为 7。 通过对这三种情况进行分析,我们可以看到当选取的两个点,像素值差距较大时计算出来的值比较大, 像素值差距较小的时候计算出来的值比较小,那么我们就可以设置一个阈值,当差比和计算出来的值超过这个阈值的时候(这个阈值就需要自己上赛道观察白点与黑点的值,然后再自己计算一下这个黑点与白点的差比和值是多少,然后计算出来的值乘以 0.7来作为阈值,也可以根据自己的测试对这个阈值再稍加调试),我们认为找到了一个黑点与一个白点。 情况 4:可以看得出来白点与黑点都比较暗,但是通过差比和计算出来的值却还是和正常图像计算出来的值差距不大, 这样我们就可以通过使用差比和提高我们的稳定性,不像二值化那样光线稍有变化二值化图像就会跟随着变化。所以,使用这样的方法也相当于提高了对光线的适应能力。 5.2消除寻线误判 对于利用差和比寻找赛道边界来说,判断出何处为赛道边界其实是容易误判的,举例说明,当寻找右边界时,假如处于弯道,或者车身不正进入直道时候,图片就会如下图 1所示,可以发现,左边界和有边界在远处一定距离已经被扫线到一处去了,因为差比和算法,每次都是从中间开始往两边扫线的,当赛道已经完全处于中心点外便会导致赛道误判。于是进行优化的方式是通过每次找到边界之后,对下一次起始的点做偏移修正,并且对差和比中加入方向的概念,通过判断像素值的大小一定是从大到小来进行做限制条件,这样同时可以减少运算工作量,同时完美解决了这种现象的发生,提高了寻找中线的稳定性。

图 5.3弯道错误寻找边界

图 5.4扫线效果图 5.3算法优化 简单使用差比和对每一行像素点扫线寻找赛道边界后会发现在弯道曲率较大的时候,会出现单边寻找赛道边线丢线的情况,如下图所示,左边边线已经丢失了,蓝色为计算出的赛道中线,导致中线,明显畸变,会导致计算出来的偏差位置过小,导致舵机打角过小,从而向外侧冲出赛道。解决办法有两个,一种为修改摄像头角度,另一种是通过算法修正。

图 5.5弯道扫线情况 我们采用半宽补线法对赛道中线进行修复,通过计算赛道一行的像素点长度,取一半对赛道中线开始丢线的地方进行修复,首先可以确定的是,这种情况下,只有单边丢线,那么计算中线的方式就是由右边边界减去半宽的赛道像素点长度。并且只要确定底部的第一行中线起点即可,后续行的中线可以根据右边界的变化来进行对应变化,以下是修复过中线的丢边界线的效果图。 对比可以发现,效果显著,并且配合赛道上边界的判断,可以很好的区分出赛道,不会受到其他外界赛道的干扰,扛干扰能力强,且降低了对外界地板颜色的依赖程度,一定程度上提高了容错率,适应性更强。 5.4修复结果

图 5.6修复后结果 可以发现,即使现在图像发生了严重的丢线情况,赛道中线也能被精准的定位。效果良好,极大的提高了弯道过弯时,舵机打角的准确性。 5.5灰度图中的八领域寻线应用 传统的八领域寻线都是将图像二值化之后再进行寻线,其基本思想是:遍历图像找到第一个非零像素点,那么这个点一定是边界点。设这个点为起始点 x,顺时针查找该点八邻域内遇到的第一个非零像素点 x’(点 x’也为边界点)。令x=x’,再进行邻域内的顺时针查找,查找的起点为刚刚 x到 x’过程中 x’前一个零点。

对图( a),在 X点时,按照 0->1->2->3->4…….的顺序进行查找。 6处为查找到的第一个非零像素点,则 6处为边界点, X’查找的起始位置是( a)中的 5,即 X’对应的 4,则 X’的查找顺序是 4->5->6->7->0->1->2->3。 由于我们并没有对图像进行二值化操作,所以我们在传统八领域寻线的基础上进行改进,使之适用于灰度图。 主要改进的点就是在二值化图中八领域寻线的赛道边界判断是判断该点在数组中存的值是 0还是 255,而在灰度图中的赛道边界判断就是采用上面所讲的差比和算法,如果该点和辅助点差比和的结果大于阈值,那该点就是赛道边界 而在顺时针(或逆时针)遍历八个邻近像素的时候,用于辅助判断该点是否为赛道边界的辅助点不能向上面讲解的一样选取右边(或左边)第五个点作为辅助点,而是也要随着当前点顺时针(或逆时针)旋转。 下面是我的方向数组和辅助点方向数组

图 5.7代码

图 5.8代码 第一个为当前点方向数组,第二个为顺时针八领域寻线当前点的辅助点方向数组,第三个为逆时针八领域寻线当前点的辅助点方向数组。在顺时针八领域和逆时针八领域的时候,顺时针时,方向数组加加,逆时针时方向数组减减即可。而赛道边界和赛道的相对位置是相反的,所以对应的 辅助点方向数组是刚好是对称的。 5.6车库图像 1 )在出库方面,由于是可以由参赛人员自由放置,因此我们采用了开环控制。对编码器进行积分就可以得到其运行距离,在多次测试之后设置一个固定打角、固定出库速度和固定行驶距离,行驶到固定距离时再恢复扫线,正常行驶。以上方法只要保证每次放小车的时候都大概在那个区域,就能完美出库。 2 )在入库方面,我们参考了杭电双车组三轮图像处理总结。因为我们的摄像头前瞻比较远,可以在很远判别到斑马线,但不稳定,有时候很远就能判别到,有时候较近才能判别到。,于是我将判别区域限制在图像 30行到 100行,当斑马线起始行或者总止行大于 35行时,开始采用灰度图八领域寻线找到车库入口的两个拐点,然后再进行拉线处理,具体拉线方式如下图

(图片来自卓大大公众号,侵删)而当左上大于一定行数时直接打死并且后轮加差速即可入库。 5.7总钻风检测赛道上 Apriltag码 赛道上加 Apriltag码是今年新增加的元素,这对于各个学校来说都是一个全新的挑战,而我们采用的方案是用识别斑马线的方法来识别赛道上的黑块。这样能在较远的地方准确且快速识别到。 Apriltag码一行有 2-6个黑块,一般都是 2-5个,当扫到某一行黑块大于 2小于 6并且间隔小于该行赛道的 2/3,并且这样的行有大于四个就判定为 Apriltag码,同时为了防止在斑马线哪里误判,当识别到斑马时不识别 Apriltag码。当一行黑块数大于 7个重新识别。 当识别到 Apriltag码后再用沿着赛道中线找 Apriltag码边界,找到起始点后用灰度图八领域将 Apriltag边框找到,然后边框上的点的 y坐标最大和 y坐标最小分别为 Apriltag码的上下边界,即使当 Apriltag码较远,图像上的点跳变,灰度图八领域无法将 Apriltag码边框完全找全,用这个方法也能大概确定找 Apriltag码的上下边界,从而帮助小车调整到一个合适位置,便于 OpenART在合适的距离识别 Apriltag码到底是奇数还是偶数,然后再控制云台将 OpenART拉直,向左(或向右),然后再由 OpenART识别靶标的蓝色边框,然后判断与小车的相对位置,通过串口中断发送回来控制小车前后移动,然后 OpenART继续识别动物还是水果。 第六章运动控制 6.1方向控制 摄像头智能车能够围绕着赛道中心线稳定运行依靠舵机转向功能。方向控制模块采用的是 S3010转向舵机,该舵机具体高扭矩、响应速度快的优点。舵机有三根接线,红色线接 5.5V电源、黑色线接 GND、白色线为舵机信号控制线,接 PWM输出端,通过单片机的控制信号来调节舵机的脉冲占空比来实现舵机的转向功能。系统框图如下图 6.1所示。 舵机采用位置式 PID进行控制,同时为了适应各种赛道元素,增加舵机响应速度,我们采用了模糊 pid进行改良,同时为了防止把舵机的齿轮打坏,我们对舵机的打角进行限幅处理。根据中线误差通过模糊 pid算法得到打角输出,有效而且快速的完成了对赛道正确的打角,能保证在 2.8m/s速度以下正确的进行转向,再快因为舵机滞后性的原因,采取了速度控制策略来使车辆略微减速,使舵机的滞后能够不至于小车冲出赛道。 6.2电磁控制 电磁传感器放置位置受限于摄像头视野范围,无法距离车头很远,不然会影响摄像头寻线,实际上我们直接安装在车头上。我们电磁传感器主要用于检测小车是否冲出赛道和是否遇到环岛。 当小车冲出赛道时,电磁信号归一化之后的值几乎在 10以下,因此用电磁来判断小车是否冲出赛道,快速又准确。 当小车运行到环岛区域,由于电磁线数量变成四根,电磁感应出来的数值会是普通赛道的两倍,实际测量中因为一些干扰,达不到两倍,但相对于普通赛道而言,电磁在一开始入环的区域感应出来的数值还是明显变大,因此可以通过这个特点来辅助摄像头进行对环岛的判断。 6.3速度控制 速度控制模块主要由电机、电机驱动电路、编码器等组成,与主控器一起 构成闭环控制系统。如果单片机输出的 PWM信号直接给到电机,会造成电机转动产生过大的电流可能将单片机烧毁,故需要通过电机驱动电路来控制电机。因为电机采用的 RS380直流大电机,电流较大,驱动电路主要芯片采用 DRV8701E和 TOH1R403NL。 后轮的速度,采取增量式的 PI,通过匿名科创的上位机进行调参,通过跑赛道采集数据,最终确立了合适的后轮 PI,后轮有着较好的响应速度,且不至于超调严重。

图 6.1方向控制流程图 第七章图像识别 7.1 OpenART mini整体代码思路 OpenARTmini兼容了几乎所有的 Openmv的库文件,对图像的处理只要调用相应的 api即可,十分的简介方便。智能视觉组赛题的识别要求包括三项,数字识别, Apriltag码识别以及水果动物的分类,这三个任务我们都基于官方建议的 OpenARTmini进行实现,早期也使用了龙邱的 openmv来实现,但由于仅支持 nncu神经网络模型并不支持 tf模型,并不如 OpenARTmini方便,于是后期更换了摄像头。 部署神经网络模型有两种方式,一种是官方介绍的,通过 nncu工具将 h5模型量化转换进行部署,还有一种就是通过 tensorflow将 h5模型转换为 tflite模型,再部署到 OpenARTmini上。 最后我们采用第二种方案,首先我们通过学习谷歌发布的 moblienetv3.0轻量型模型,改进了自己的 tensorflow模型,显著提高了模型的识别效率和准确率,之后通过爬虫从互联网上爬取了足够的动物水果的图片,通过大量的训练和参数调试,最终得到了良好的识别动物水果的模型,最后通过 tensorflow官方学习了模型量化的方法,转换为 tflite模型之后部署到了摄像头上,做到了即使是识别逐飞官方数据集之外的动物水果图片,在复杂背景之下也能做到对靶标类型正确识别。 相同的对数字靶标也做了相似的模型训练,训练了一个十一分类 tflite神经网络模型,同样取得成效。 7.2数字识别 首先会打开摄像头的打光灯来保证光线恒定,再通过 openmv的色块二值化函数 find_blobs,通过颜色的设置将紫色矩形边框二值化显示出来,同时运行 find_rects来寻找矩形框,因为二值化图形的处理非常迅速,整个寻找边框的过程效率大大提升达到了每秒近 40fps,再次通过对矩形边框的限制条件,如正方形和大小显著,来准确定位矩形位置,获得矩形边框位置后,再通过二值化之前的图像,定位矩形图片位置并且进行神经网络模型识别,通过识别之后向小车发送数字的信息。整个过程只需要大概 200ms,可以迅速的识别并通过三叉路口,唯一遗憾的是,真实赛场上因为光线的变化,在远离靶标的时候,摄像头的打光灯不能很好的照射到靶标上,于是识别三叉路口会存在找不到色块的问题,最终采用了停车策略,来保证稳定通过三叉路口,但在光线良好的赛场,通过三叉路口的速度将会很快。 7.3动物水果识别 同样的动物和水果的识别也和数字识别一样,但会是在识别边框到中心位置之前一直返回矩形边框的中心位置,直到到达图片的中心,发送指令让小车停下并识别靶标,识别完成后再通过,打靶或者直接通过。

图 7.1动物水果识别结果 7.4模型训练 因为模型训练的比较早,保存了两个版本,一种是爬虫爬下来互联网上的图片进行训练的模型,识别能力强,但为了保证没有意外,又针对官方发布的训练集训练了所需要的模型,图片分类模型的训练最重要的就是图片的训练集质量,于是我们为了提高模型的泛化能力,对数据集进行了增强,通过预处理图片,产生更多的图像来补充训练集,从而增强模型的鲁棒性,我们利用了翻转,旋转,缩放,移位,高斯模糊,噪声,运动模糊,以及各种亮度和对比度的数据增强方法。 7.5 Apriltag码识别 Apriltag的识别必须是全摄像头下的识别,暂时没能想到较好的改进办法,采用我们采用总钻风摄像头识别黑块,然后停下检测 Apriltag码内容,并返回给小车指令,小车收到指令后对云台进行控制转向,同时摄像头进入识别图片功能。

 

§08 RT-Thread移植应用


本部分移植演示是基于逐飞 RT1064的裸机开源库和 RT-Tread源码 rt-thread-v4.0.2文件进行的。逐飞 RT1064的裸机开源库可从 gitee上下载,如下图 8-1所示

图 8-2RT-Tread源码可从 RT-Tread官网->资源->下载处免费下载,如图 9-2所示

8.1移植

1)首先将 rt-tread源码放到逐飞工程目录的 Libraries里,如图 8-3所示

2)打开逐飞工程,打开 keil,在工程目录下建立 rttread_src、rttread _components、 rttread_inc、rttread_lib四个分组。如图 8-4所示

3)将 Libraries->rt-tread-> src中的 C文件全部添加进 rttread_src分组中,如图 8-5所示

4)将 Libraries->rt-tread-> include中的 .h头文件全部添加进 rttread_inc分组中,如图 8-6所示

▲ 图8.6

图 8-6将 Libraries->rt-tread->include->libc中的 .h头文件全部添加进 rttread_inc分组中,如图 8-7所示

5)将内核部分汇编支持的文件 Libraries->rt-tread->libcpu->arm->cortex-m7中的 c文件和 context_rvds.S(如果是用的 IAR则应该添加 context_iar.S)添加进 rttread_lib分组中,如图 8-8所示

6)加载组件文件 Libraries->rt-tread-> components-> finsh,将里面的 c文件全部添加到 rttread_componets分组中如图 9-8所示

图 8-9 7)将 Libraries->rt-tread-> bsp-> imxrt->imxrt1064-nxp-evk中的 rtconfig.h加入 user.h分组中,如图 8-9所示

图 8-10 8)将刚刚添加文件的头文件路径包含进来,如下图 8-10所示

…\Libraries\rt-thread\bsp\imxrt\imxrt1064-nxp-evk …\Libraries\rttherad_libraries\include\libc …\Libraries\rttherad_libraries\components\finsh …\Libraries\rttherad_libraries\include

图 8-10把汇编的头文件路径也包含进来如下图 8-11所示 …\Libraries\rt-thread\libcpu\arm\cortex-m7

图 8-11 9)将 RT-Tread头文件包含进来,如下图 9-12所示

图 8-12 10)打开 seekfree_libraries分组下的 zf_systick.c文件,将 RT-Tread头文件 "rtthread.h"包含进来 ,再将 void systick_delay_ms(uint32 ms)函数里的内容替换为下图 8-13

图 8-13 11)修改 board分组下的 board.c文件将 <rthw.h>头文件和 <fsl_sdmmc_event.h>头文件包含进来,在 board.c文件中添加如图 8-14所示内容

图 8-14再修改 board_init(void)函数,将里面的内容修改为如下图 8-15所示

图 9-15 继续在 board.c文件中添加三个函数,如下图 8-16和 8-17所示

图 8-16

图 8-17在 board.h头文件中添加宏定义以及一些声明如下图 8-18所示

图 8-18 12) 打开 sdmmc分组下的" fsl_sdmmc_event.c"文件将 SysTick_Handler函数注释掉再打开 seekfree_libraries分组下的" common.c"和" common.h"将里面 HardFault_Handler( void)函数和 MemManage_Handler( void)函数以及 PendSV_Handler(void)函数的声明和定义都注释掉即可 打开 sdmmc分组下的 "fsl_sdmmc_event.c"文件,添加 void sdmmc_tick_handler(void)函数,在"fsl_sdmmc_event.h"头文件里面声明该函数, c文件里的实现方式如下图 8-19所示

图 8-19 13)再次编译之后发现还是会出现两个错误,这是分散加载文件错误导致的

图 8-20 只需点开这个 Edit,如图 9-21所示

图 8-21 进入编辑界面在图示位置加入下面两段代码即可,如图 8-22所示 #define RTT_HEAP_SIZE(m_data_size-ImageLength(RW_m_data)-ImageLength(ARM_LI B_HEAP)-ImageLength(ARM_LIB_STACK)) ;ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { ; Stack regiongrowing down ;} ARM_LIB_STACK+0 EMPTYStack_Size{};Stackregion growingdown RTT_HEAP+0EMPTYRTT_HEAP_SIZE{}

图 8-22 14)点击编译, 0错误 0警告,大功告成,连接开发板即可运行 main任务,然后就可以尽情使用 RT-Tread了。如图 8-23所示

8.2 RT-Tread的使用 使用 RT-Tread我们有如下任务 //main优先级为 10 //recognitionTag优先级为 18 //recognition斑马线优先级为 17 //buzzer优先级为 20 //send优先级为 21 //display优先级为 30 //out优先级为 13 //view优先级 16 //两个软件 pit定时器 //多个信号量和多个邮箱

图 8-24

图 8-25 RT-Tread作为一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,然后它还有定时器、信号量、邮箱、队列等可以使用。我们主要使用了它的多任务、信号量、邮箱和定时器。 1)main任务 我们将赛道寻线任务放在了 main()任务,这是我们最核心的任务,所以它的优先级最高为 10。在它里面对其它所有任务和软件定时器进行初始化,而在初始化的时候会执行 rt_sem_take(OUT_sem, RT_WAITING_FOREVER),语句来获取来自出库任务发来的信号量,当没有来自出库任务发来的有效信号量, main任务一直等待,等待出库任务完成。我们出库是开环出库,固定打角和速度,不需要对赛道识别。当出库完成,出库任务发送信号量给 main任务, main任务接收信号量,才继续往下执行。 在 main任务的 while(1)循环体里面还需要执行 rt_sem_take(camera_sem, RT_WAITING_FOREVER);语句,等待获取来自摄像头中断发来的信号量,每次摄像头拍完一次照,发送一个信号量给 main任务,任务一接收到该信号量就开始分别发送信号量给 ApriTag识别任务和斑马线识别任务,因为这两个任务优先级较低,所以这两个任务还是处于等待状态。然后 main任务就进行赛道识别。否则就等待,保证能及时分析赛道,且不会重复分析同一张图片。 2)out任务 出库任务,它的优先级是 13,我们出库是开环控制,当小车未完全出库 main任务因为未接收到信号量就一直等待。 而同时因为初始化了 display任务以及用于按键的软件 pit等其它功能,使得我们可以在屏幕上可视觉化的按按键调整参数,调整完毕,打开电机电源,当 ”out”出库任务判定出库成功即发送信号量给 main任务, main任务接收到信号量继续执行下一条 rt_thread_delete(tid_OUT),删除“ out”任务,因为它已经完成了它的使命。 3)smotor2舵机调整任务 当识别靶标时需要微调云台,让 openARTmini对准靶标,以提高识别准确率,当其它任务发送相应信号量,该任务接收到其它任务发来的信号量后,就可以根据 openARTmini发来的数字调整云台。因为只有当识别靶标的时候才需要运行该任务,所以这里加入信号量来控制它。 4)Apriltag码任务和识别车库斑马线任务这两个任务里面都各有一个获取信号量

rt_sem_take(camera_semMain2, RT_WAITING_FOREVER); rt_sem_take(camera_semMain, RT_WAITING_FOREVER); main任务里面以及提到,每次拍照完,获取拍照中断里发送的信号量之后,main任务会发送 camera_semMain2和 camera_semMain两个信号量,告诉这两个任务拍照完成,这里使用信号量是因为每一张图片只需识别一次,多次识别是没有意义的。 识别赛道上的 Apriltag码任务和识别车库斑马线任务,他们的任务优先级为 17、 18,相对靠后,这是因为识别赛道上的黑块要花费比较多的时间,优先级低一点就不会影响其它任务的执行。 5)view任务 该任务为处理 openARTmini发送过来的数据任务,优先级为 16,并且里面有一个获取信号量 rt_sem_take(view_sem, RT_WAITING_FOREVER)。该信号量由 rt1064负责和 OpenARTmini通信的串口接收中断发送, OpenARTmini每发送一个字节在串口接收中断中都会对其分析是否为结束符号,当串口接收中断判断 OpenARTmini发送过来一个结束字符,就发送信号量给 view任务, view任务开始执行下面的代码,根据 OpenARTmini发来的内容进行判断,进行模式选择。切换一些标志位,影响其它任务。使用信号量是因为该任务只有 OpenART mini向 rt1064发送一段完整信息时才需要执行,别的时候不需要执行在,这样就能方便其它低优先级任务处理信息。 6)buzzer任务 该任务是蜂鸣器任务,优先级为 20,并且里面有一个从消息邮箱获取消息的语句 rt_mb_recv(buzzer_mailbox,(rt_uint32_t*)&mb_data,RT_WAITING_FOREVER);否则就无限等待。别的任务需要使用该任务时,只需发送相应的消息到该消息邮箱,该任务接收到消息,根据消息内容,叫一段时间。使用邮箱而不是信号量是因为使用邮箱不仅可以控制蜂鸣器叫,还能控制蜂鸣器每一次叫多久。 7)send任务 该任务是无线串口发送任务,优先级为 21,当小车跑起来的时候,我们可以通过让无线串口发送一些信息给上位机的串口助手,从而更好的分析小车运行情况。该任务是一直执行的,但因为优先级不高,无需担心影响主要任务。 9)display任务

该任务是屏幕显示任务,优先级为 30.该任务优先级最低,因为屏幕显示要消耗大量时间,将其设置为最低优先级,可使其不会对其它任务造成影响,即使后 期小车需要处理的东西越来越多,但也无需担心刷新屏幕的占用问题。 10)用判读按键是否按下的“ button” rt-tread软件定时器 该定时器 20ms进入一次,用于判断按键是否按下,不同按键按下发送不同的信号量,在 display任务里接收,从而实现设置一些参数和切换屏幕显示内容 11)“timer1” rt-tread软件定时器 该定时器 1ms进入一次,在任务里可以进行其它时间的定时。 每 10ms采集编码器信息并根据期望速度对电机控制 每 5ms采集电磁信息并处理以及根据所有任务处理的结果对小车前轮舵机 进行判断。 使用定时器是因为控制电机、控制舵机、获取电磁信号都需要周期执行,产生 pwm波和读取编码器数值等都需要占用一个 RT1064的硬件定时器。而硬件定时器的数量是固定的, RT-tread的软件定时器拓展了定时器的个数,可以很好的解决我们定时器不够用的苦恼。

 

§09 RT-Thread创新点


9.1利用 RT-Thread提高图像处理程序的稳定性

一开始裸机进行小车软件的开发时,发现实时性差,编写程序代码冗长,在赛道要素逐渐增多,最后编写完成时,甚至 RT1064600Mhz主频都会有一些延迟,这里的延迟体现在摄像头拍摄的处理帧明显下降了,我们通过研究代码发现,裸机运行时,为了调试方便,将图像打印呈现在 lcd屏幕上,这一过程耗费了许多的时间去运行代码,然而这一过程对于小车任务的完成并没有任何帮助,于是在研究讨论之后,决定使用 RT-THread操作系统来解决问题。

在智能车控制系统开发过程中引入实时嵌入式操作系统,不仅可以充分发挥不同芯片的性能,让智能车跑的更加顺畅;而且在一定程度上屏蔽了不同单片机底层硬件细节,提高控制软件开发效率。

首先,我们将摄像头拍摄作为一个任务,每当摄像头拍摄完成就会发送一个信号量给赛道图像处理任务,然后执行一次赛道处理任务,来保证拍摄一张图片,执行一次图像处理,解决了初期使用操作系统时的资源浪费。

其次,我们将 lcd屏幕显示图像单独作为了一个任务,将其优先级改为最低,可以避免 lcd屏幕任务的执行导致其他关键任务的运行被堵塞,同时我们可以观察 lcd屏幕的刷新速度来判断赛车在各个赛道元素上的程序运行帧率来改良代码,当我们观察到 lcd屏幕发生卡顿时,就说明此时操作系统的运行任务跳转变慢了很多,我们通过研究可以改进此时的代码,来提高代码运行效率。

▲ 图9.1 定时器中断

图 9.1定时器中断如图 9.1所示,我们建立了一个定时器中断,在进入任务前开启定时器中断,在任务完成后结束定时器中断,同时在定时器中断中进行计时,单位为 us,便可以轻松得到这一任务的执行时间,随后通过串口发送到上位机,通过分析得到哪一段代码运行速度可以得到提高。

这样做一个方面可以提高代码运行的稳定性和实时性,另一个方面可以对代码的开发提供一定的直观帮助。

9.2利用 RT-Thread减少开发代码的难度

关于 Apriltag码任务和识别车库斑马线任务,在裸机运行时,遍历图片,识别赛道上的黑块要花费比较多的时间,花费了 2ms左右的时间,而我们摄像头拍摄的间隔为 10ms一次,也就是说,留给其他赛道要素和任务的极限时间只有 8ms不到,导致对我们开发代码的运行速度要求较高,开发后期令人头疼。

我们决定使用 RT-Thread操作系统,很好的避免了识别斑马线和 Apriltag码可能造成其他如赛道边界识别等关键任务堵塞的烦恼,解决了我们代码开发的后顾之忧。

这两个任务里面都各有一个获取信号量

rt_sem_take(camera_semMain2,RT_WAITING_FOREVER); 
rt_sem_take(camera_semMain,RT_WAITING_FOREVER);

当一次拍照完成, main接收到摄像头任务中 csi_isr()里发送的信号量, main函数发送上面两个信号量,使用信号量是因为每一张图片只需识别一次,多次识别是没有意义的。

识别赛道上的 Apriltag码任务放在了 “recognitionTag”任务里,识别车库斑马线放在了“ recognitionGarage”任务里,他们的任务优先级为 17、18,相对靠后,优先级低一点就不会影响其它任务的执行。

9.3利用 RT-Thread提高车模软件开发速度

我们在初期车模软件开发过程中发现,当两个组员同时敲写代码时,往往组合到一起之后会发生很多的问题,例如运算顺序导致程序运行不下来,变量重复定义导致错误,必须要反复调整很多代码参数,会耗费大量的开发时间,而往往难以发现代码的错误,因为对某一赛道元素识别的处理顺序会直接影响到其他赛道元素。

后期我们使用了 RT-Thread来开发时,将每一个赛道元素都作为任务来开发,不用考虑某一元素对另一元素的影响,所有元素任务都对原始图像进行判断,避免了参数的调整,规避了很多细节问题,降低了两人同时开发软件代码并进行组合的难度,极大的提高了软件开发的速度。

9.4 RT-Thread社区贡献

在移植 RT-Thread到 RT1064过程中遇到了两个变量未定义的错误,在工程里搜索了,别的地方没有这个变量。在 RT-Thread社区中进行搜索遇到了有同样问题的人,如图 9-1所示

在翻看讨论后,发现其说明了是工程的分散加载文件,但没有详细提出解答方案,于是在我翻阅资料查阅解决问题后,将自己的详细解决方案发布在下面,希望让下次遇到此问题的人能及时解决。 只需点开这个 Edit,如图 10-2所示

进入编辑界面在图示位置加入下面两段代码即可,如图 10-3所示

#define RTT_HEAP_SIZE(m_data_size-ImageLength(RW_m_data)-ImageLength(ARM_LI B_HEAP)-ImageLength(ARM_LIB_STACK)) 
;ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { 
         ; Stack regiongrowing down 
;} ARM_LIB_STACK+0 EMPTYStack_Size{ 
        };Stackregion growingdown RTT_HEAP+0EMPTYRTT_HEAP_SIZE{ 
        }

9.5 RT-Thread对相关辅助开发功能

在调试小车的过程中,我们都会用到屏幕显示和无线串口发送这两个功能。以往裸机开发中由于任务是顺序执行,这两个任务不可避免的会占用其它任务处理赛道的时间,而这两个任务对于赛道处理毫无用处,只是给开发人员看。但加上了 RT-Thread操作系统,只要调整好任务优先级的高低就可以完美避免这个问题,只有当单片机处理完其它任务处于空闲的时候才会被这两个任务占用,我们全程都可以放心的添加各种辅助程序。

9.6 RT-Thread对竞赛任务完成的帮助

今年我们智能视觉组加入了许多新元素,例如识别赛道上的 Apritag码。根据比赛要求这是一个放置位置随机,可能放在在直道或者弯道上,但又极其重要的元素。因为一旦识别不到就不能进行后续对水果打靶或者在动物区停留 3秒的任务。而一旦错过就是加时 15s。但在实际开发中,我们采用用总钻风摄像头识别赛道上的黑块个数来判别是否有 Apritag码,经过测试后,发现这个任务会占用较多的时间,大概有 1-2毫秒。同时为了识别车库我们采用识别斑马线判定为车库的方法。而这两次识别会占用较多时间,虽然它们在赛道上占比不多,但识别它们又非常有必要,所以得全程识别。在裸机开发上,就很难做到既能一直识别这两个元素,又不会对其它主要任务造成影响。而引入 RT-Thread就完全无需担心这种问题,可以全程识别,很好的帮助我们完成了比赛所有内容。

 

§10


10.1 RT-Thread主要功能应用要点

我们主要使用了 RT-Thread的多任务、信号量、邮箱和定时器。多任务对我们视觉组组别来说真的是非常便捷,我们只要把主要的任务安排在高优先级,而把一些次要任务依次往下排列,就可以兼顾所有任务的识别,并且无需担心因为一些任务耗费大量时间耽误主要赛道的识别,所以我们在整个备赛到比赛工程中,我们都无需因为屏幕刷新要耗费大量时间将屏幕摘下,也无需因为识别赛道上的黑块需要耗费大量时间会拖慢赛道边界识别的速度而苦恼。同时信号量和邮箱的存在即可以让某些任务一些等待,又能使其不占用 CPU,以及控制每次任务对每张图片只识别一次。而 RT-Thread自带的软件定时器更是让我们想用多少定时器就用多少,无需为硬件问题发愁。

10.2 RT-Thread使用过程中的主要问题

  1. 邮箱和队列重合性过高,其实保留一个队列就够了,邮箱能完成的,队列也能完成。
  2. 在运行过程中经常会因为数组越界问题或者信号量注释之后忘记取消,信号量未初始化,其它地方发送信号量导致 RT-Thread卡死,但有时候并不能一下子知道是因为什么卡死的,希望 RT-Thread能优化一些系统,在因为不同原因,系统卡死之后跳转到不同地方,并给出一些通俗易懂的可能卡死的原因和程序中可能导致卡死的地方。如果已经有这个功能了,希望团队能专门出一个教程来教一下我们。
  3. 在小车上电的时候多次遇到系统可能因为某个任务初始化失败,导致系统一开始就卡死,得重新上电的情况。我们的任务都是创建的动态线程,不知道是不是这个原因导致初始化经常失败。
  4. 希望团队能多努力,适配一些主流的单片机,然后写出一些通用 API接口,让我们绕过单片机底层的配置,直接调用 RT-Thread内的函数就能实现 gpio口、硬件定时器、 spi、iic等的使用,这样我想能大大提升用 RT-Thread写的代码的通用性。

10.3 智能车主要技术参数

智能车主要技术参数包括物理尺寸、电路指标等 ,具体参数见表 1表 1智能车主要参数表

注①:由于设计报告书写期间智能车仍然在进行改进 ,因此有些数据未能更新 .之后的内容中涉及到的参数、 程序、图片也有类似情况 ,不做逐一说明

10.4 感想

智能车竞赛是一个多学科、综合性的比赛,其中设计涉及了控制、传感技术、电子信息、模式识别、汽车电子、机械等多个学科,在整个准备的过程中我们不仅仅把所学的理论知识应用于实际,还自学了大量的新知识。不仅开拓了我们的视野,同时也使我们的动手能力、运用知识的能力、分析解决问题的能力也有了很大的提高。

这个小车总体而言还是比较有挑战性的,面对的是全新的单片机 NXP.RT1064和全新的操作系统 RT-Thread.不过好在有之前学习 STM32和 UCOS操作系统的基础,上手之后,逐渐克服了硬件上的麻烦。而随之而来的是更为棘手的算法问题。图像处理涉及到非常多方面的知识,摄像头将灰度图像采集过来,我们要做的就是告诉单片机如何处理这些灰度图。还有就是对一个系统来说,稳定很重要,不然调试就是白费功夫。

参考文献

[1]王晓明.电动机的单片机控制[M].北京.北京航空航天大学出版社.2002 [2]臧杰,阎岩.汽车构造[M].北京.机械工业出版社.2005 [3]安鹏,马伟.S12单片机模块应用及程序调试[J].电子产品世界.2006.第 211期. 162-163 [4]童诗白,华成英.模拟电子技术基础[M].北京.高等教育出版社.2000 [5]沈长生.常用电子元器件使用一读通[M].北京.人民邮电出版社.2004 [6]宗光华.机器人的创意设计与实践[M].

标签: 600v2000uf折机电容传感器线色白色系列拉线传感器v2000传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台