用FPGA实现2021电赛C题——双车跟随系统


前言

这学期数字电路课程设计是用Altera的主控芯片是Cyclone IV EP4CE6F17C8的FPGA实现2021年电子设计竞赛C题——双车跟随系统。三个人的电赛小队再加上一个人工专业的外援,四个人肝了五天(因为驱动坏了耽搁了一天多一点),最后做的效果还可以,40s左右的时间跑三圈,比起很多电赛的大佬团队还是差了很多。这块FPGA板子玩得还不够多,那些模块也是现调,然后也没用什么算法(主要是PID等控制算法会涉及到浮点运算,不知道怎么处理会得当一点),加上课设之后就是好几科的期末考,很多粗糙的地方实在没时间再调了,原题里面的专家等停指示也没时间搞了(老师的课设题目里面好像也没要求做)。

总体来说,这次项目虽然有不少遗憾,但也让我学到了不少东西,特别是在FPGA开发方面。希望以后有机会能继续深入,做得更好。


提示:以下是本篇文章正文内容,下面案例可供参考

一、题目介绍

我们课设的题目是根据原题改的,最主要的不同还是主控必须用限制的FPGA开发板和两车之间的通信必须要用红外对管(这部分完全是我通信的队友搞的,效果非常好,基本就是秒传秒启动^~^)。

设计一套小车跟随行驶系统,采用 FPGA 实验板,由一辆领头小车和一辆跟随小车组成,要求小车具有循迹功能,且速度在 0.3 ~ 1m/s 可调,能在指定 路径上完成行驶操作,行驶场地的路径如图 1 所示。

【图1】小车跟随行驶场地示意图

其中,路径上的 A 点为 领头小车每次行驶的起始点和终点。当小车完成一次行驶到达终点,领头小车和跟随小车要发出声音提示。领头小车和跟随小车既可以沿着 ABFDE 圆角矩形 ( 简称为内圈 )路径行驶,也可以沿着 ABCDE 的圆角矩形( 简称为外圈 ) 路径行驶。行驶在内圈 BFD 段时,小车要发出灯光指示。

任务一:从起始位置 A 点开始到停止位置 A 点为止,单独一辆小车能独立 一次性沿着图 1 跑道跑 3 圈,其中第 1、3 圈是内圈,第 2 圈是外圈,中途不能停车调整。

任务二:将领头小车放在路径的起始位置 A 点,跟随 小车放在其后 20cm 处,沿着外圈路径行驶一圈停止:

  1. 领头小车的平均速度误差不大于 10%;
  2. 跟随小车能跟随领头小车行驶,全程不能发生小车碰撞;
  3. 完成一圈行驶后领头小车到达 A 点处停车,跟随小车应及时停止,停止时间差不超过 1s,且与领头小车的间距为 20cm,误差不大于 6cm。

任务三:将领头小车放在路径的起始位置 A 点, 跟随小车放在其后 20cm 处,领头小车和跟随小车连续完成三圈路径的行驶。 第一圈领头小车和跟随小车都沿着外圈路径行驶。第二圈领头小车沿着外圈路径 行驶,跟随小车沿着内圈路径行驶,实现超车领跑。第三圈跟随小车沿着外圈路 径行驶,领头小车沿着内圈路径行驶,实现反超和再次领跑,要求:

  1. 全程两个小车行驶平稳,顺利完成两次超车,且不能发生小车碰撞;
  2. 完成三圈行驶后领头小车到达 A 点停止,跟随小车应及时停止,两车停止的时间差不超过 1s,且与领头小车的间距为 20cm,误差不大于 6cm;
  3. 小车行驶速度可自主设定,但不得低于 0.3m/s,且完成所规定的三圈轨 迹行驶所需时间越短越好;
  4. 跟随小车只有一个上电开关,接收到前车传来的红外信号才能启动。

由红外信号实现停止,领头小车抓起后,跟随小车依然不动。

二、FPGA和单片机的异同

记得课设前我学长就和我说用FPGA来做小车这样的控制系统,有点大材小用,毕竟FPGA成本更高,更适合用来处理高速实时的任务。也确实是这样的,电赛没怎么见过大佬用FPGA做控制题,隔壁信号题好像才是FPGA的主战场。学长用Xilinx的FPGA做的2024的信号题,效果就蛮好的。

但是从学习的角度讲吧,这个作为一个FPGA在嵌入式系统的小应用,我感觉收获还是蛮大的。本次课设使用了FPGA开发板,我们在实际课程设计中切身体会到了它的特点所在。具体可知FPGA(现场可编程门阵列)和单片机(MCU,微控制器)在结构和应用上有着明显的异同。

2.1 FPGA开发板与单片机的不同点

在工作原理上,FPGA通过硬件电路(逻辑门)并行执行任务,而单片机通过中央处理器(CPU)串行处理指令,具体表现为FPGA的灵活性强于单片机。例如单片机固定的UART外设在需要使用UART协议时,只能选用特定的接口。而FPGA则可以选用两个普通的IO口来模拟RX和TX。

在性能上,FPGA是并行处理,适合高速实时的任务,单片机串行处理,性能较低,适合低功耗任务。一个非常显著的区别在于,FPGA的逻辑是通过硬件电路并行执行的。例如,在用VHDL编程时,代码中的两个process(比如数码管驱动和LED驱动)即使书写上是一个在前,一个在后,但最终会被综合为独立的硬件模块。硬件电路中,这些模块在同一个时钟周期内可以同时工作,因此不会出现“数码管先显示数据再点亮LED”的现象,除非代码中明确加入了延时或同步控制。

而单片机的代码执行逻辑则是顺序执行的。它依赖于CPU逐条指令的执行,因此本质上是串行的逻辑控制。即使单片机具备中断功能(可以暂时中断当前任务处理紧急事件)或有独立的外设(如定时器、PWM),这些只是部分任务的并行化,CPU主程序的大部分运行依然是按照代码从上到下依次执行的。并行处理的特性决定了FPGA更适合高性能的实时应用处理,比如现在很热门的图像处理和通信协议解析,而单片机则适合复杂度较低的控制任务,如家电控制和LoT(物联网)中的传感器数据采集,还有当下单片机应用十分广泛的汽车行业。

2.2 FPGA开发板与单片机的相同点

相同点在于二者都可以应用于嵌入式开发系统,执行特定的任务。例如本次课程设计的《双车跟随系统》,就是一个典型的FPGA应用于嵌入式系统中的小项目。而在电子设计竞赛中要求用载TI芯片的单片机来完成跟随跑、分道跑、超车跑等指定的任务。

其次,两者都可以通过编程来实现功能,FPGA使用Verilog HDL/VHDL等硬件描述语言,单片机通常用C或者汇编语言来编程。

另外,FPGA和单片机开发和设计都是基于硬件的编程,需要硬件基础知识,涉及引脚配置和相关硬件电路的设计。超声波模块支持不同的模式,如GPIO/UART等,大部分单片机拥有UART外设,我们可以通过配置相关特定的引脚来使用超声波模块,也可以选用两个普通的IO口。而FPGA使用的UART资源是USB接口,需要通过数据线上位到电脑来观察数据和现象,在设计中选的是IO口的办法。

三、系统方案

右边两辆是我们用来验收的,双四驱。

【图2】小车组装图

3.1 小车跟随系统的设计方案

设计的整个系统分为主控模块、电机驱动模块、红外通信模块、循迹模块、超声波测距模块、电源模块等组成。

【图3】系统框架图

我们用3S 12V的锂电池给FPGA供电(我懒得去给FPGA配断电不丢失,所以烧录的时候也就是直接上电烧录了),经过驱动模块的稳压和控制后驱动霍尔编码器电机。两车之间的通过红外对管来通信,主要用到通信的地方有两个:一个是前车启动后车,另外一个就是前车停后车立马制动。在中途跑圈的过程中,通过灰度传感器传回来的指示状态控制不同的电机转向,此外,两车通过超声波模块来调整速度,进而调整距离。

3.2 各硬件模块

小车框架:用的是铝板的小车框架,用铜柱隔开两层,下面一层放电池和面包板,空间大点。

电机:四驱,用的是霍尔编码器电机,实际上没调闭环控制,中间的AB相接口没用到。

【图4】电机控制逻辑

主控模块:FPGA开发板。用的时候在Quartus里面把那些复用功能都关了,变成普通的IO口。

电源模块:3S 12V锂电池。实测的时候发现12V电源会比2S 7.4V的锂电池稳定很多。

循迹模块:用的是感为的八路灰度传感器,一直感觉蛮不错的,电赛用的也是这一款。详情看链接

八路灰度传感器 | 感为

测距模块:用的是HC-SR04,也是电赛的常客了。

通信模块:红外对管(38KHZ载波),这个是我们专业的一次课程任务,感觉都可以单独水一篇了。

小车组装的时候还是有用到面包板和挺多杜邦线的,其实这样还是蛮不好的,下次改到PCB上。

3.3 难点分析

1.第一个就是那个B点的处理,就是判断小车走内圈还是外圈的标志。原先是想着让小车要走外圈的时候,检测到A点指示灯全黑的状态,就开始加速直行;要走内圈的时候,就来个左转。实战发现,这和电量和车头与状态还有很大的关系,电量少一点,车头歪一点,还真不一定能走对圈。某个深夜,我实在受不了了,直接跪在地上,把两辆车经过B点指示灯的大多数状态给记录下来,写到状态机里面。要走外圈的时候,一检测到这些特定状态里面的某一种就直接直行;要走内圈的时候,一检测到就让他左转。虽然方法笨了一些,但是结果就是,内圈外圈一直都是走对的!

2.第二个就是A点的处理,就是判断小车走第几圈的标志。在原题里面是有声光提示这一功能的,但是老师在课设里面给省略了。但是我们还是加上去了,因为这样只要蜂鸣器一响就能知道是第几圈。新的一个问题就是有时候小车明明经过A标志点,但是蜂鸣器不响(没进入计圈状态),我们认为最可能的原因就是8个指示灯全灭的限制太强了(A点是用胶带贴的),直接把限制的数量减少一些,好了很多。还有就是如果小车跑三圈没停的话,要加一个消抖。

3.我队友为了精益求精,直接把超声波测算的距离精确到小数点后两位,很多时候停止时两车的距离是准之又准。原理就是把信号放大一百倍,然后再去计算各个位数(因为Quartus里面好像不支持浮点的运算,我们也没找有没有别人写好的,就算有感觉资源消耗也蛮大的)。

4.红外的话是完全脱手了,是真不知道遇到了啥问题。

四、程序设计

用FPGA来开发的话,我们是写好各个部分代码,然后生成芯片,连接顶层电路,最好再下载到FPGA上,进行调试。主要是三部分:PWM生成和循迹部分、超声波测距部分、红外通信部分。以下以第三问的后车部分代码为例:

4.1 PWM生成和循迹部分

4.1.1 电机控制逻辑

设好各个状态的状态机,电机控制逻辑,基础占空比

    -- 电机控制逻辑
    process(clk)
    begin
        if rising_edge(clk) then
            case direction is
                when "000" =>
                    -- 直行,电机同向转动
                    ForwardLeft_INT1 <= '1';
                    ForwardLeft_INT2 <= '0';
                    ForwardLeft_PWM <= pwm_signal_left;

                    ForwardRight_INT1 <= '1';
                    ForwardRight_INT2 <= '0';
                    ForwardRight_PWM <= pwm_signal_right;

                    BackLeft_INT1 <= '1';
                    BackLeft_INT2 <= '0';
                    BackLeft_PWM <= pwm_signal_left;

                    BackRight_INT1 <= '1';
                    BackRight_INT2 <= '0';
                    BackRight_PWM <= pwm_signal_right;

                when "001" | "100"  =>
                    -- 左转,左侧电机退后,右侧电机前进
                    ForwardLeft_INT1 <= '0';
                    ForwardLeft_INT2 <= '1';
                    ForwardLeft_PWM <= pwm_signal_left;

                    ForwardRight_INT1 <= '1';
                    ForwardRight_INT2 <= '0';
                    ForwardRight_PWM <= pwm_signal_right;

                    BackLeft_INT1 <= '0';
                    BackLeft_INT2 <= '1';
                    BackLeft_PWM <= pwm_signal_left;

                    BackRight_INT1 <= '1';
                    BackRight_INT2 <= '0';
                    BackRight_PWM <= pwm_signal_right;

                when "010" | "101" =>
                    -- 右转,右侧电机退后,左侧电机前进
                    ForwardLeft_INT1 <= '1';
                    ForwardLeft_INT2 <= '0';
                    ForwardLeft_PWM <= pwm_signal_left;

                    ForwardRight_INT1 <= '0';
                    ForwardRight_INT2 <= '1';
                    ForwardRight_PWM <= pwm_signal_right;

                    BackLeft_INT1 <= '1';
                    BackLeft_INT2 <= '0';
                    BackLeft_PWM <= pwm_signal_left;

                    BackRight_INT1 <= '0';
                    BackRight_INT2 <= '1';
                    BackRight_PWM <= pwm_signal_right;

                when others =>
                    -- 停止,停止电机
                    ForwardLeft_INT1 <= '0';
                    ForwardLeft_INT2 <= '0';
                    ForwardLeft_PWM <= '0';

                    ForwardRight_INT1 <= '0';
                    ForwardRight_INT2 <= '0';
                    ForwardRight_PWM <= '0';

                    BackLeft_INT1 <= '0';
                    BackLeft_INT2 <= '0';
                    BackLeft_PWM <= '0';

                    BackRight_INT1 <= '0';
                    BackRight_INT2 <= '0';
                    BackRight_PWM <= '0';
            end case;
        end if;
    end process;

4.1.2  循迹逻辑

循迹就根据实际情况调就可以了,我们的循迹代码蛮丑陋的,但是实际的效果还可以

							 if (sensors = "11100011" or sensors = "11010111" or sensors = "11101101" or sensors = "11011101" or
								 sensors = "10110011" or sensors = "11101110" or sensors = "10111101" or sensors = "11011110" or
								 sensors = "11011101" or sensors = "11010011" or sensors = "11011100" ) and (lap_counter = 3)
								  then
								  direction <= "100"; -- 左转

							 -- 检测直行(黑线在中间)
							 elsif 
									(sensors(3) = '0' and sensors(4) = '0') or
									 sensors = "11110111" or
									 sensors = "11101111" then
								  direction <= "000"; -- 直行

							 -- 检测左小转
							 elsif 
									 (sensors(2) = '0' and sensors(3) = '0') or
									 (sensors(1) = '0' and sensors(2) = '0') or
									 sensors = "11111101" then
								  direction <= "001"; -- 左小转

							 -- 检测左大转
							 elsif (sensors(0) = '0' and sensors(1) = '0') or
									 (sensors(0) = '0' and sensors(2) = '0') or
									 sensors = "11111110" then
								  direction <= "100"; -- 左大转

							 -- 检测右小转
							 elsif (sensors(4) = '0' and sensors(5) = '0') or
									 (sensors(5) = '0' and sensors(6) = '0') or
									 sensors = "10111111" then
								  direction <= "010"; -- 右小转

							 -- 检测右大转
							 elsif (sensors(6) = '0' and sensors(7) = '0') or
									 (sensors(5) = '0' and sensors(7) = '0') or
									 sensors = "01111111" then
								  direction <= "101"; -- 右大转

							 -- 检测无黑线 
							 elsif sensors = "11111111" then
								  direction <= "000"; -- 停止

							 -- 默认直行
							 else
								  direction <= "000"; -- 前进
							 end if;

4.1.3 圈数更新逻辑

	   -- 圈数更新逻辑
		process(clk, reset)
		begin
			 if reset = '1' then
				  lap_counter <= 0; -- 复位圈数计数器
				  lap_detected <= '0'; -- 清除圈检测标志位
				  stop_flag <= '0'; -- 清除停车标志位
				  debounce_counter <= 0; -- 复位去抖动计时器
				  debounce_active <= '0'; -- 清除去抖动标志位
				  Buzzer <= '1';
			 elsif rising_edge(clk) then
				  -- 去抖动逻辑
				  if debounce_active = '1' then
						if debounce_counter < 5000000 then -- 等待指定的时钟周期数
							 debounce_counter <= debounce_counter + 1;
						else
							 debounce_counter <= 0; -- 计时器复位
							 debounce_active <= '0'; -- 清除去抖动标志位
						end if;
				  else
						-- 如果没有检测到圈并且传感器条件满足,则增加圈数并设置标志位
						if lap_detected = '0' and sensors = "00000000" then
							 if lap_counter < 4 then -- 只有当圈数小于4时才增加圈数
								  lap_counter <= lap_counter + 1;
								  lap_detected <= '1'; -- 设置圈检测标志位
								  debounce_active <= '1'; -- 激活去抖动
								  Buzzer <= '0';
							 end if;
						-- 当传感器不再满足条件时,清除标志位
						elsif not(sensors(1) = '0' and sensors(2) = '0' and sensors(3) = '0' and sensors(4) = '0' and sensors(5) = '0' and sensors(6) = '0') then
							 lap_detected <= '0';
							 Buzzer <= '1';
						end if;
				  end if;

				  -- 达到三圈后设置停车标志
				  if lap_counter = 4 then
						stop_flag <= '1';
						Buzzer <= '0'; 
						lap_counter <= lap_counter + 1;
				  end if;
				  if lap_counter = 5 then
						lap_counter_out<='0';
				  else
						lap_counter_out<='1';
				  end if;
			 end if;
		end process;

4.1.4 动态调整逻辑

这部分就根据与20cm距离的远近来调整占空比就可以了,超声波计算得到两车距离,反馈给主控进行速度控制。距离超过20cm,后车加速,小于20cm,后车减速。

4.2 超声波测距部分

超声波部分在上一篇已经介绍了

4.3 红外通信模块

这一部分完全是队友做的,没征得同意不好发出来,有机会再更一篇原理分析+代码


### 关于智能车竞中的双车跟随 #### 智能车竞概述 智能车竞是一项集成了控制理论、传感器技术、路设计等多个学科领域的综合性事。比道是在室内道上进行,道上铺设有磁线。计时系统有两个计时线圈,分别安放在两个起跑线下,分别对车模出发和返回进行计时。再对车模是否罚时进行计算,得出最终车模的运行时间[^1]。 #### 双车跟随的概念与挑战 双车跟随是指两辆或多辆车按照一定的规则保持相对位置行驶的技术。这种技术不仅考验车辆自身的导航能力,还需要解决多车之间的通信协调问。为了实现这一目标,通常会采用无线通信模块来实现实时数据交换,并通过算法调整各车辆的速度和方向以维持队形稳定。 #### 主要组成部分及其实现方法 - **硬件部分** - 使用高性能微控制器作为核心处理器; - 配备多种类型的传感器(如红外测距仪、超声波雷达等),用于检测周围环境变化以及前后车间的距离测量; - 安装可靠的无线通讯设备,确保车队成员间的信息传递畅通无阻; - **软件编程** - 编写路径规划程序,使前导车能够自主识别并沿预定路线前进; - 设定合理的间距参数,在保证安全的前提下尽可能提高整体效率; - 开发基于PID或其他先进控制策略的速度调节机制,让后续车辆可以根据实际情况灵活响应前方动态; ```c++ // 基础 PID 控制器伪代码示例 double error = target_distance - current_distance; integral += error * dt; // 积分项累加误差乘以采样周期dt derivative = (error - prev_error) / dt; // 微分项求解当前时刻与上次时刻之间差值除以间隔时间 output = Kp*error + Ki*integral + Kd*derivative; prev_error = error; ``` #### 技术难点分析 由于实际环境中存在诸多不确定因素影响着跟驰效果的好坏,因此如何克服这些干扰成为研究的重点之一。比如路面状况不佳可能导致定位精度下降;外界信号源可能造成无线传输误码率上升等问都需要特别关注处理方案的设计优化工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值