前言:本题不是难题,难的是怎么理解实验指导要你干什么,以及实验报告如何压缩至6页,可以说当时断断续续花了1周,纠结于实验指导和实验报告怎么写。本次实验分为三个部分,分别是:
- 用Protest设计一个测距系统的电路: 测距电路
- 在QuartusII上,设计一个每1秒启动的距离连续测量与计算系统电路:测量计算电路
- 在VS2010上用C#设计一个上位机图形软件:上位机软件
初步设计一个复杂数字系统-传感器数字采集系统,理解扩展接口的超声波测距和通用USB-TTL串口电路原理;实现其实作模块功能验证;分层次编写VHDL完成系统模块优化方案,验证平台,FPGA串口与PC上位机软件(可选),并验证调试。
初步设计一个复杂数字系统-传感器数字采集系统,理解扩展接口的超声波测距和通用USB-TTL串口电路原理;实现其实作模块功能验证;分层次编写VHDL完成系统模块优化方案,验证平台,FPGA串口与PC上位机软件(可选),并验证调试:
① 测距原理:
倒车雷达超声波模块向四周发送超声波,超声波接触到障碍物时反射,被超声波模块所接收,模块根据超声波发送和返回时间差以及超声波传输的速度,就能计算岀车体和障碍物之间的实际距离。对于不同的距离,产生不同的声音作为距离提示。
② 超声波测距过程:
trig引脚产生一个大于10s的高电平信号,模块开始工作,自动发送8个40kHz的方波,并检测是否有信号返回,一旦接收到返回信号,echo引脚由高电平自动变成低电平;高电平持续的时候则为超声波传输的时间,超声波在空气中传输的速度(340m/s),测试距离=(echo高电平时间 * 声速(340m/s)/2,如图:
计算过程:第一步要计数echo回响信号的时间,通过读取示波器上面的刻度数,乘以每个刻度的周期长度得到;第二步代入式子测试距离=(echo高电平时间 * 声速(340m/s)/2即可得到结果。
① 图像1:
黄色波形是trig信号值为26us,大于20us;蓝色波是echo回响信号,读取echo的刻度数目为11.6个格子,每个方格代表500.0us,所以echo高电平的时间为11.6500us=5800us。代入公式:测试距离=(5800 * 340100010^-6)/2=986mm;从图中读取的距离是1000mm,与986几乎一致(读数造成了误差),可见计算过程是正确的。
② 图像2:
黄色波形是trig信号值为26us;蓝色波是echo回响信号,读取echo的刻度数目为8.7个格子,每个方格代表200.0us,所以echo高电平的时间为8.7200us=1740us。代入公式:测试距离=(1740* 340100010^-6)/2=296mm;从图中读取的距离是300mm,几乎一致(读数造成了误差),可见计算过程是正确的。
为了使得采样结果就是实际物理距离,可以建立如下表达式,得到采样频率为f=17kHZ,这样echo的采样值就是实际距离
电路由三部分组成,第一部分是图中红色框,计数器部分,用于计数echo的高电平数。第二部分是图中绿色框,数码管显示部分,用于将BCD码动态的显示在数码管上;第三部分是蜂鸣器部分,根据采样值、发出多种不同频率组合声音。如下图:
①计数器部分:(见图中红色框)
计数器用于给echo计数,将echo作为计数器的使能端,时钟上升沿计数;计数器分为BCD采样部分和8位采样部分;BCD采样部分用4518模块相互级联,低位的Q0和Q3通过与门产生一个进位,存在一个D触发器中连接到高位的时钟端。级联产生个十百位的计数值,用于数码管显示;8位采样部分由两个74161计数器级联组成,产生8位采样值。
②数码管显示部分:(见绿色框图)
数码管显示部分,用于将BCD码动态的显示在数码管上;由一个计数器产生信号q0,q1,q2,作为两个二选一MUX的选择端选择一个BCD计数值,通过七段译码器译码产生七段码;同时74HC138用于产生位选信号,选择一位采样值显示在数码管上。
③蜂鸣器部分(见黄色框图)
蜂鸣器部分用于根据不同的采样结果发出不同单音间隔的声音。分为选择器组和声源选通两部分。
选择器组:
我将30~250划分为区间(29,50),[50,120),[120,251),因此四组比较器的输入端分别为29=0001 1101,50=0011 0010,120=0111 1000,251=1111 1011,搭配逻辑门即可产生选择信号。
声源选通:
在③中已经得到了3个时钟选择信号,所以最后只需要将不同的时钟通过三态门选通即可达到蜂鸣器的不同频率发声,我设置了三个时钟激励以及一个低电平信号,F1到F4距离逐渐增大,分别别选择频率1,频率2,频率3改变单音间歇时间以调节蜂鸣器音效的缓急程度,将时钟的模式改为pulse,统一width为250m,频率分别设置为2,1,0.5.
①设置clk时钟频率为17kHZ,设置SRF0距离为40cm,数码管显示结果为40,发出急促的蜂鸣声。
②设置SRF0距离为100cm,数码管显示结果为100,发出较为急促的蜂鸣声。
③设置SRF0距离为250cm,数码管显示结果为250,发出缓和蜂鸣声。
在QuartusII上,设计一个每1秒启动的距离连续测量与计算系统电路
①第一步:设计两个参数化的分频器;
②第二步:确定分频的结果,利用公因数关系做出树状时钟图。
③第三步:调用2个参数化模块,根据树状时钟图完成模块的设计,验证。
为减少VHDL设计复杂度,参考教材中的分频器,我用generic类属语句设计了两个参数化的分频器paraEqualDiv和paramCycle,分别用于占空比为50%的频率分频和可调占空比频率分频。paraEqualDiv(n)实现初频f–>f/2*n的分频,paramCycle(n2,n1)实现占空比为n2/n1的可调分频:
占空比为50%的频率分频
entity paraEqualDiv is
generic(n:integer:=2);
port(clkIn0,En0:in std_logic;clkOut0:out std_logic);
end entity;
architecture rtl of paraEqualDiv is
signal temp:integer range n-1 downto 0;
signal q:std_logic:='0';
begin
process(clkIn0,temp,En0)
begin
if En0='1' then
if rising_edge(clkIn0) then
if temp=n-1 then --0~n-1计数
temp<=0;
q<=not q;
else
temp<=temp+1;
end if;
end if;
else
temp<=0;
q<='0';
end if;
end process;
clkOut0<=q;
end architecture;
可调占空比频率分频
entity paramCycle is
generic(
n1:integer:=10; --占空比分母
n2:integer:=7); --占空比分子
port(clkIn0,En0:in std_logic;clkOut0:out std_logic);
end entity;
architecture rtl of paramCycle is
signal temp:integer range n1-1 downto 0:=0;
begin
process(clkIn0,temp,En0)
begin
if En0='1' then
if rising_edge(clkIn0) then
if temp=n1-1 then --计数0~n1-1
temp<=0;
else
temp<=temp+1;
end if;
end if;
else
temp<=0; --清零
end if;
end process;
clkOut0<='1' when temp<n2 else '0';--翻转
end architecture;
如图,节点表示分频模块,边表示时钟频率,F1(n)表示占空比为50%的频率分频器paraEqualDiv(n),F2(n1,n2)表示占空比为n2/n1的可调分频器paramCycle(n2,n1)
首先将24HZ时钟初步分频为100kHZ,然后trig模块将100kHZ分频到周期为100ms,高电平时间为20us的trig信号,同时引出100kHZ的echo计数信号;数码管分频模块将100KHZ分频为40Hz;然后二次分频模块将40HZ分频到4Hz;state状态机分频模块将4Hz降为0.5Hz;最后蜂鸣器分频模块将4HZ的时钟分为250ms/500ms,250ms/1s,250ms/2s的时钟;
将24HZ时钟初步分频为100kHZ,参数N=24M/100K/2=120,所以调用paraEqualDiv(120)。(注意,图中的二次分频模块与此类似,只是参数改为N=5)
entity firstDiv is
port(
clkIn,En:in std_logic;clkOut:out std_logic);
end entity;
architecture struct of firstDiv is
component paraEqualDiv
generic(n:integer);
port(clkIn0, En0:in std_logic;clkOut0:out std_logic);
end component;
begin
gate:paraEqualDiv generic map(n=>120) --分频参数设置为120
port map(clkIn0=>clkIn,En0=>En,clkOut0=>clkOut);
end architecture;
仿真分析:设置时钟输入为24MHZ,firstDiv模块将24MHZ的时钟分频为100kHZ,在10.0us处恰好为第一个时钟周期结束,所以周期为10us频率是100kHZ,分频正确。
将100kHZ分频为周期100ms高电平时间20us,占空比2/10000的时钟,参数N1=10000,参数N2=2,调用模块paramCycle(100,2)
entity trigDiv is
port(clkIn,En:in std_logic; --时钟输入 --使能
clkTrig,clkEcho:out std_logic;);--Trig信号 --echo时钟
end entity;
architecture struct of trigDiv is
component paramCycle
generic(n1:integer;n2:integer);
port(clkIn0,En0:in std_logic;clkOut0:out std_logic);
end component;
begin
gate:paramCycle generic map(n1=>10000,n2=>2)--参数为(10000,2)
port map(clkIn0=>clkIn,En0=>En,clkOut0=>clkTrig);
clkEcho<=clkIn;
end architecture;
仿真分析:设置时钟输入为10us,模块将100kHZ的时钟分频为周期100ms高电平时间20us,占空比2/10000的时钟trig,在100.0us处恰好第一个周期结束,且高电平时间为20us,分频正确
将100KHZ分频为40Hz,模块内部用两个分频器级联,第一级将100kHZ降为2000Hz,N=100k/2000/2=25,调用模块paraEqualDiv(25);第二级将2000Hz降为40Hz,N=2000Hz/40/2=25,调用paraEqualDiv(25).
entity digitalTubDiv is
port(
clkIn,En:in std_logic;
clkOut:out std_logic);
end entity;
architecture struct of digitalTubDiv is
signal tempClk:std_logic:='0';
component paraEqualDiv
generic(n:integer);
port(
clkIn0,En0:in std_logic;
clkOut0:out std_logic);
end component;
begin
gate1:paraEqualDiv generic map(n=>25)--第一级将100kHZ降为2000Hz
port map(clkIn0=>clkIn,En0=>En,clkOut0=>tempclk);
gate2:paraEqualDiv generic map(n=>50)--第二级将2000Hz降为40Hz
port map(clkIn0=>tempClk,En0=>En,clkOut0=>clkOut);
end architecture;
仿真分析:
设置时钟输入周期10us,digitalTubDiv模块将100kHZ的时钟分频为40HZ,在24.855ms处恰好为第一个时钟周期结束,所以clkOut的周期结果为25ms,频率为40HZ分频正确。
将4Hz降为0.5Hz,参数N=4/0.5/2=4,所以调用模块paraEqualDiv(4),达到1s切换一次状态的目的
entity stateDiv is
port(
clkIn:in std_logic; --时钟输入
En:in std_logic; --使能
clkOut:out std_logic); --时钟输出
end entity;
architecture struct of stateDiv is
component paraEqualDiv
generic(n:integer);
port(
clkIn0:in std_logic;
En0:in std_logic;
clkOut0:out std_logic);
end component;
begin
gate:paraEqualDiv generic map(n=>4)--参数为4
port map(clkIn0=>clkIn,En0=>En,clkOut0=>clkOut);
end architecture;
仿真分析:设置时钟输入周期250ms,stateDiv模块将4HZ的时钟分频为0.5HZ,在s5处恰好为第一个时钟周期结束,所以clkOut的周期结果为2s,分频正确。
要得到3个时钟源分别为250ms/500ms,250ms/1s,250ms/2s的时钟,保证单音间隔变化,高电平时间维持250ms,而4Hz的时钟周期为250ms所以占空比分别是1/2,1/4.,1/8,分别调用模块paramCycle (2,1), paramCycle (4,1),FparamCycle(8/1)
entity buzzerDiv is
port(
clkIn:in std_logic; --时钟输入
En:in std_logic; --使能
buzzer1,buzzer2,buzzer3:out std_logic--三个声源输出
);
end entity;
architecture struct of buzzerDiv is
component paramCycle
generic(n1:integer;n2:integer);
port(clkIn0:in std_logic;En0:in std_logic;clkOut0:out std_logic);
end component;
begin
gate1:paramCycle generic map(n1=>2,n2=>1)--参数为(2,1)
port map(clkIn0=>clkIn,En0=>En,clkOut0=>buzzer1);
gate2:paramCycle generic map(n1=>4,n2=>1)--参数为(4,1)
port map(clkIn0=>clkIn,En0=>En,clkOut0=>buzzer2);
gate3:paramCycle generic map(n1=>8,n2=>1)--参数为(8,1)
port map(clkIn0=>clkIn,En0=>En,clkOut0=>buzzer3);
end architecture;
仿真分析:设置时钟输入周期250ms,buzzerDiv模块将4HZ的时钟分频为高电平/周期=250ms/500ms,250ms/1s,250ms/2s的三组时钟信号时钟。在仿真图中,声源1为250ms/500ms,声源2为250ms/1s,声源2为250ms/2s,由此可见仿真结果与预期一致。
超声波测量模块用于测量echo高电平的周期数,我采用的是取平均的方法来滤波处理,在1s的时间内会得到多个测量结果,先用nLast存储第i-1次测量结果,在测量得到第i次结果nNow之后取平均值,nlast<=(nLast+nNow)/2,为了确保初始数据0被正确处理,需要额外判断nLast是否为0,是则nLast<=nNow,实现如下:
entity echoCounter is
port(echoClk,echoClr,echo,EN:in std_logic;
echoN:out std_logic_vector(11 downto 0):="000000000000");
end entity;
architecture bhv of echoCounter is
signal nLast:integer range 4095 downto 0:=0; --保存采样值
signal nNow:integer range 4095 downto 0:=0; --新记录采样计数值
begin
process(echoClk,echoClr,echo)
begin
if EN='1' then
if rising_edge(echoClk) then
if echoClr='0' then --清零
if nLast=0 then nLast<=nNow; --直接赋值
elsif nNow/=0 then
nlast<=(nLast+nNow)/2; --求均值
end if;
nNow<=0; --清零
else --计数
if(echo='1') then
if nNow=4095 then --清零
nNow<=0;
else nNow<=nNow+1;
end if;
end if;
end if;
end if;
else nLast<=0; NnOW<=0;--全部清零
end if;end process;
echoN<=conv_std_logic_vector(nLast,12);--转化成二进制
end architecture;
仿真分析:
在0~100ns使能位低清零,100ns开始计数;550ns时echoClr拉低,输出采样值004;600ns后继续计数,在1.250us时echoClr拉低,输出采样值,由于第二次采样结果为006,所以做均值处理,得到结果为004+006/2=005,与结果吻合,仿真正确。
对C#上位机开发不太熟悉的可以参考:手把手教你C#上位机
我选择的是上位机图形软件,设计细节有很多,2页我必定讲不完,选择几个关键点。
构架参考了实验指导文档,新增功能:16进制接收和发送模式,串口信号指示灯,采样频率设置框,曲线显示图表增强数据值。
①VS2010中可以通过自带的 串口控件来实现串口连接。
②设置串口的属性,在事件 被点击的时候开始执行,这时将从左边的下拉框中获取用户设置的串口号,波特率,数据位,校验位,以及停止位,然后一一赋值给serialPort1,核心代码如下:
string[] ports = System.IO.Ports.SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
comboBox1.SelectedIndex = comboBox1.Items.Count > 0 ? 0 : -1;
serialPort1.BaudRate = int.Parse(comboBox2.Text);¨º
serialPort1.DataBits = int.Parse(comboBox3.Text);//设¦¨¨置?数ºy据Y位?
if (comboBox5.Text == "1") { serialPort1.StopBits = StopBits.One; }
else if (comboBox5.Text == "1.5") { serialPort1.StopBits = StopBits.OnePointFive; }
else if (comboBox5.Text == "2") { serialPort1.StopBits = StopBits.Two; }
else serialPort1.StopBits = StopBits.One;
if (comboBox4.Text == "无T") { serialPort1.Parity = Parity.None; }
else if (comboBox4.Text == "奇校验") { serialPort1.Parity = Parity.Odd; }
else if (comboBox4.Text == "偶校验") { serialPort1.Parity = Parity.Even; }
else serialPort1.Parity = Parity.None;
要把握住一点:串口间传送的数据是按照ASCII码表传输的对应的16进制传输,在此基础上才有明确的进制转换:
①首先把数据读取到数组buff,serialPort1.Read(buff, 0, len);
②对于10进制数据,我采用的是Encoding.Default.GetString(buff)函数将一个数据集合直接转换为字符串,原封不动的显示。
③16进制,则需要将其转换为16进制的字符串:returnStr += bytes[i].ToString(“X2”);
④以上进制问题转换完了后,直接将获取的数据替换到富文本框,设置属性text为数据字符串
①数据发送用serialPort.Write()方法发发送数据。其中直接发送可以获取文本框的字符串直接发送。
②16进制发送则还需要经过转化,默认把文本框每隔两个字符作为一个16进制数,如果文本框中的字符个数是奇数个,则默认先把前面每隔两个作为一个16进制数,最后一个数前面补零,以下是算法的核心代码:
if ((hexString.Length % 2) != 0)//奇数
{
byte[] returnBytes = new byte[(hexString.Length + 1) / 2];
try
{
for (i = 0; i < (hexString.Length - 1) / 2; i++)
{
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
returnBytes[returnBytes.Length - 1] = Convert.ToByte(hexString.Substring(hexString.Length - 1, 1).PadLeft(2, '0'), 16);
}}
else
{
byte[] returnBytes = new byte[(hexString.Length) / 2];
try
{
for (i = 0; i < returnBytes.Length; i++)
{
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
}
}
private void checkBox2_CheckedChanged(object sender, EventArgs e)
{
if (checkBox2.Checked)
F = new StreamWriter("data.txt");
else
F.Close();
}
关于数据保存,那一定是保存在文件中,我的思路是当事件 被勾选的一瞬间,获取当前接收文本框中的字符串,以txt文档的形式保存起来。实现代码如下:
private void checkBox2_CheckedChanged(object sender, EventArgs e)
{
if (checkBox2.Checked)//如果被选中,说明保存文件
{
F = new StreamWriter("data.txt");//写入
}
else
{
F.Close();
}
}
设置表格规格是纵坐标0250cm,横坐标030s。图像曲线选择平滑曲线属性,数据点的标记形式选择Circle点标记,isValueAsLab属性设置为True,显示数据值。
数据展示发生在事件 接收数据时,每当串口接收到一个数据,就将这个数据由字符串的形式转化为Int32整型,获取当前时间,得到点对(x,y),用addPoint(x, y)方法将(x,y)加入serial曲线中。
溢出处理:当时间足够长的时候,数据将会溢出,图表。为了防止数据溢出图表,我设置一个下标index,当下标超出30s的时候,将index重置为0,并且清除图线。然后继续添加点对。这样的处理方式将会使得用户体验较好,看到数据远远不断地画上图表周而复始
新增的频率输入框可以输入采样的频率frequency,因此,在得到采样值之后,计算物理距离s=声速34000cm/s*计数值y/ frequency /2,即可换算成物理距离最后显示。
验证分析:
首先打串口COM2,采样频率设置为17000HZ,和串口助手连接;然后设置串口助手为多条发送,为了便于显示,我设置的是一组正弦波数据;然后再在串口软件中发送数据AA,BB,CC,FF。实验现象:串口软件正确显示串口助手发送的数据集,换算成物理距离后,绘制图形呈现正弦波趋势。串口软件发送数据AA BB CC FF,在串口助手中接收,并且正确显示。在debug目录下出现data.txt文件,保存接收到的数据。
结论:串口软件可以正确接收并显示采样数据并保存,处理数据完成绘制,正确发送数据给串口助手,满足设计要求。
实验中难免会遇到一些问题,和其他同学在私下一起交流,可以很快得到解决。同样老师也会在群里为大家细心解答,有些问题在群里就能找到答案。很感谢为我们解答的老师和一起交流的同学。另外我也积极给其他同学帮助,力所能及地帮助了他人。
经过本学期的数字系统实验,我学到了很多知识点,最重要的有三点:第一点是数码管动态显示的原理,通过循环产生位选信号来动态选择不同的位显示在数码管上,从而节省开销;第二点是IIC 串行总线时序和串口通信的原理;第三点是本实验中的参数化分频器的设计。以上就是我学到的最重要的三个知识点。
本学期我学到的最大的技能有三点:第一点是在proteus中如何根据实验需求寻找合适的芯片,查找测试出各个端口的功能,设计出满足需求的电路,这个基本能力贯穿了整个实验,我想这是我学到的最大的技能;第二点是单片机的编程设计,在本次实验中我的任务一第一个思路是用单片机编程,我在网上找到了单片机设计倒车雷达的方案,学习慕课,学习如何编程,如何生成Hex文件,最后装载,这些都是我学到的,虽然最后我没有采用这个方案;第三点则是VS2010上位机的设计,让我记忆深刻的是VS2010的布局可以直接用实例化的控件,而不是代码搭建布局,VS2010(严格来说是微软)这一思路免去了复杂的构架布局设计,加速了搭建过程,这是一个很值得学习的思路。以上就是我学到最重要的三点技能。