本文记录了利用FPGA实现倒车雷达的基本思路以及具体实现。
硬件描述语言选择VHDL,该工程在Cyclone II型芯片上进行验证。
12.12 更新模块的设计框图,更容易理解。
12.14 Important 扬声器模块设计修改!
12.16 扬声器模块设计完善 原理完善完毕
12.16 部分原理更新完毕
1.17 公开源代码
=============================================
无论是设计什么东西,最先要思考的问题就是:要实现什么功能,如何去实现。
倒车雷达的功能其实很直白,测量出当前位置到障碍物的距离,根据这个距离,在LED上显现距离,同时根据这个距离,让扬声音器发出不同的声音。
测量距离–>利用超声波测距模块HC-SR04模块测距
HC-SR04的工作原理很容易理解,HC-SR04一共有四个管脚,如下图所示
最左边的为Vcc,最右边为 GND。这两个管脚是为该超声波模块供电,在单片机上分别接上Vcc和GND即可。中间的两个管脚为触发管脚trig和回馈信号管脚echo,这两个管教分别控制超声波模块是否工作(trig)和输出时间信息(echo)。
超声波测距,就是发射一个声波信号,然后接收其回声信号,再利用其间的时间间隔,计算出与障碍物之间的距离。
利用超声波测距模块HC-SR04模块测距–>如何提供让其工作的TRIG信号
提供TRIG信号有两个问题。
利用超声波测距模块HC-SR04模块测距–>如何接受ECHO信号让其工作
如之前的分析,echo端变为高电平的时间就是发射到接收到回声信号的时间。如何记录这个高电平持续的时间呢?
既然和时间有关,很自然的想到了计数器,计数器可以记录一段时间内,引入时钟信号的上升沿次数。无论是定制的计数器还是自己写的计数器,都应该提供这几个端口,一个是控制该计数器是否工作的enable端,一个是异步清零(所谓同步还是异步,就是看这个功能是否更具时钟信号上升沿而触发),一个是引入时钟信号,还有数据输出的端口,以及是否溢出的端口。
如果想获知其持续时间,只需要在echo端高电平的时段,计数一共有多少个时钟信号上升沿即可。乍一听,这个功能和enable很像,那么把echo端接在enable端就可以达成这个功能了。但是echo隔一段时间,又会来,也就是说,计数不是一个一次性行为,那么就要将异步清零信号和之前的刷新周期联系一下,或者和trig信号,或者和echo联系一下就可以解决了。解决方法很多,选一条喜欢的方式即可,但是会不会受到噪声信号的影响也是要考虑的因素之一。
接收到了ECHO信号–>如何将其转化为距离
我们刚刚利用了计数器接收了时钟信号,但是获得的只是ECHO为高电平时,一共有多少个时钟上升沿,这和我们想要的距离数据还差一道转换呢。
假设我们之前的那个计数器的频率为100K,那么其周期为0.01ms。假设收到的数据为N,即在ECHO为高电平时,有N个时钟信号上升沿,那么代表着其时间长度为N*0.01ms。在这段时间内,声音从我们所在的位置,跑到了障碍物,撞了墙,又跑了回来。奔跑的距离为N*0.01ms*340m/s。(注意单位换算)将其除以二就是距离咯。
这个地方就体现出VHDL的优越性了,提供了conv_to_interger()(将二进制转换为十进制)等函数。
计算部分的语言规范网上资料很多,就是要注意整数库里面不会提供整数和小数相乘这种东西的,可以通过先乘后除的方法曲线救国(0.113=113/1000)
已经知道了距离–>如何在LED显示
如果单纯是LED显示,相信每一个单片机入门的教材/课程都会大讲特讲。
但是我们现在收到的是数据,而不是具体到每一个LED上面要显示什么数。那么接收到距离信息还需要通过一定的处理,再给多路选择器。这个处理就是分位,比如把216分成2、1、6,然后再传递给多路选择器。利用其他语言的分位的函数大多采用除法后转换为整型(216/100=2)以及取余(216%10=6)等来实现,VHDL里面也可以用类似的方法,取余计算符为rem。
简单说一下LED显示的原理,通过模计数器获取位选信号,位选信号一边给多路选择器,选择要显示的数,一边给单片机(如果没有自带38译码器的话,还要通过一个38译码器)。要显示的数通过译码器翻译成abcdefgh要显示信息后,分配到相应管脚即可。
已经知道了距离–>如何用扬声器播放相应的声音
扬声器的工作原理很简单,根据输入的频率高低发出不同音色的声音。根据输入波形的占空比不同而产生不同的音量。
那么我们大可根据这个需求写一个专门对应的模块,判断距离的远近,然后通过分频器,调整以上参数。
最开始思路是这样的:(这个思路已经被否决了,可以跳过这里!)
=========================已被否决=========================
可以把这个功能分成三个小模块。
最核心的模块就是,一个可以根据输入频率参数,占空比参数,和标准时钟信号,转换为参数指定的频率,占空比的信号。
第二个模块就是根据不同的单片机的时钟信号,产生标准的时钟信号(这个是上一个模块指定的)
第三个模块就是根据距离不同,给第一个模块不同的频率和占空比参数。
然后发现高估了单片机计算能力,假若标准时钟频率为100k,那我们就要计算以下两个式子
F = 1000000/N 和 D = (F*din)/100
当加上计算这个数的模块,编译工程时出现了
(之前整个工程不会超过100个warnings)
于是,第二种思路如下,第二种思路分位两个模块。
首先我们要知道可变分频器的原理。可变分频器其实就是计数器,其根据时钟信号上升沿计数,并用一个临时变量来保存。如果该变量小于第一个参数(占空比参数),那么就输出1。如果该变量大于第一个参数,小于第二个参数(周期放大倍数),就输出0,并将该临时变量清零。
begin
process(count,fin,din,clk)
begin
if(clk'event and clk = '1') then
if(countthen
count <= count+1;
else count <= 0;
end if;
end if;
end process;
fout<='1' when count < din else
'0';
end arch_divider;
其中count为临时变量,fin为放大参数,din为占空比参数。
于是两个模块的思路就成型了。
第一个模块是根据引进的距离参数,计算出来周期放大参数,以及占空比参数。
假设引进时钟信号频率为N0,则其周期为1/N0,经过分频器后频率为N1,其周期为1/N1,那么周期放大倍数就为(1/N1)/(1/N0)=N0/N1。
占空比参数为放大倍数*占空比。
第二个模块就是根据这两个参数,输出分频后的信号。
除此之位,还有第三种思路:
为了达成这个功能,总共分为三个模块。
第一个模块是模式选择模块,根据输入的距离输出0000,0001,0010等等。
第二个模块为分频模块,不过这个模块分出来的信号为占空比为50%的标准信号。而且是目标频率的十倍。(为第三个模块服务,第三个模块计数10才是一个输出周期)
第三个模块为调整占空比模块。这个模块其本质也为计数器,引入的时钟信号为模块二的输出信号。其根据时钟信号上升沿计数,并用一个临时变量来保存。占空比参数为0-10,当变量小于该参数时,输出为1,当大于该参数小于10时,输出为0,并将临时变量清零。
第三种思路与第二种思路相似,不过把占空比和频率分为两个不同的模块,互不影响。当要更改数据的比第二种思路更加方便。而且还用到了模式选择的思维,只用传递一个较小的数,而不用传递完整信息。
至此,倒车雷达的原理分析,以及模块拆分结束。
可定制的分频器
分频器除了可以用VHDL代码实现,还可以通过定制,生成占空比为50%的信号。
其原理是,计数器的最高位就是周期放大倍数为该计数器最大值+1。
例如,2^3计数器的输出结果如下:000,001,010,011,100,101,110,111。
不难发现,我们以一次完整的计数周期为输出信号的周期,最高位为0与最高位为1分别为该周期的一半。故该计数器的最高位可以当作分频输出信号。
模计数器
计数器在定制的时候可以在以下选项中选择模计数。
多选一选择器
多选一选择器也可以通过定制实现。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
USE IEEE.STD_LOGIC_SIGNED.ALL;
entity cal is
port(
data:in std_logic_vector (13 downto 0);
en:in std_logic;
qout:buffer std_logic_vector(13 downto 0));
end cal;
architecture rt1 of cal is
signal tn:integer;
signal ans:integer;
begin
process(en,tn,ans,qout)
begin
if(en = '1') then
tn<=conv_integer(data);
if(tn>7 and tn<3676) then
ans<=((272)*(tn))/2000;
end if;
qout<=CONV_STD_LOGIC_VECTOR(ans,14);
else qout<=qout;
end if;
end process;
end rt1;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
USE IEEE.STD_LOGIC_SIGNED.ALL;
entity fenwei is
port(
data:in std_logic_vector (13 downto 0);
q2,q1,q0:out std_logic_vector (3 downto 0));
end fenwei;
architecture rt1 of fenwei is
signal A:integer ;
signal tmp1:integer;
signal tmp2:integer;
signal tmp3:integer;
begin
A <= conv_integer(data);
tmp1<=A/100;
q2<=CONV_STD_LOGIC_VECTOR(tmp1,4);
tmp2<=(A-tmp1*100)/10;
q1<=CONV_STD_LOGIC_VECTOR(tmp2,4);
tmp3<=A rem 10;
q0<=CONV_STD_LOGIC_VECTOR(tmp3,4);
end rt1;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_signed.all;
entity anydis is
port(
q:in std_logic_vector (13 downto 0);
qout_F:out integer range 0 to 1023;
qout_Z:out integer range 0 to 1023
);
end anydis;
architecture rtl of anydis is
signal A:integer;
begin
process(q)
begin
A<=conv_integer(q);
qout_F<=7600/(A+10)+262;
qout_Z<=(20+600/(A+10)); --(80-240/(A+40))
end process;
end rtl;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_signed.all;
use ieee.std_logic_arith.all;
entity test1216 is
port(
fin:in integer range 0 to 1023;
din:in integer range 0 to 1023;
clk:in std_logic;--ask for 100k clk
fout:out std_logic);
end test1216;
architecture rt1 of test1216 is
signal count:integer range 0 to 1023;
begin
process(count,fin,din,clk)
begin
if(clk'event and clk = '1') then
if(count<(125000/fin/2)) then
count <= count+1;
else count <= 0;
end if;
end if;
end process;
fout<='1' when count < din else
'0';
end rt1;
library IEEE;
use IEEE.std_logic_1164.all;
entity decoder is
port(din:in STD_LOGIC_VECTOR (3 downto 0);
dout:out STD_LOGIC_VECTOR(7 downto 0));
end decoder;
architecture rt1 of decoder is
begin
dout <= "11111100" when din = x"0" else
"01100000" when din = x"1" else
"11011010" when din = x"2" else
"11110010" when din = x"3" else
"01100110" when din = x"4" else
"10110110" when din = x"5" else
"10111110" when din = x"6" else
"11100000" when din = x"7" else
"11111110" when din = x"8" else
"11110110" when din = x"9" else
"11101110" when din = x"A" else
"11111110" when din = x"B" else
"10011100" when din = x"C" else
"11111100" when din = x"D" else
"10011110" when din = x"E" else
"10001110" when din = x"F";
end rt1;