📄 myself_uart_vhdl.txt
字号:
总体设计思路:
1)预计实现效果:通过串口调试助手,由PC发送数据给小系统板,板子接收到的数据在数码管上即时显示
并且通过发送模块发送回PC,在串口调试助手上显示出来。
2)模块划分:六大模块,1:波特率发送模块(clock_tx);2:fpga_to_pc 发送模块 (uart_tx);3:采样时钟模块(uart_re);
4:pc_to_fpga接收模块(uart_re);5:数码管显示模块(segment);6:顶层模块(top_level).
3)基本设定:板上晶振频率40M,发送数据波特率为19200,数据格式为1bit起始位(0)+8bit有效数据+1bit停止位(1)构成一帧数据.
注:
1)一般编写程序在quartus中完成,在该目录下新建modelsim的仿真目录,
调用modelsim仿真时,添加quartus中的原始设计文件时,
若选择copy形式,则quartus与modelsim中文件互相不影响;若选择refenrence形式,
则quartus中设计文件改变时,会影响到modelsim中文件.
2)若要精确的指向电平跳变点,可以使用波形操作界面上的图标find next transition.
3)判断“=”需要USE IEEE.STD_LOGIC_UNSIGNED.ALL; '_'不能使用在STD_LOGIC_VECTOR中
--模块1:clock_tx:发送时钟模块,即波特率发生器.
1) 设定
预设发送数据波特率为19200,分频时钟需要利用计数器来实现,计数值为40M/19200=2084.
2) 设计
通过计数器来分频,在原始时钟周期下计数,判断满计数值一半时清零,同时对中间值取反,否则自加.则该中间值即为所分频率.
3) 仿真
在modelsim中进行功能仿真,原始时钟为40M(25000ns),分频周期为52150000ns。
4) 调试
下载程序,本部分没有出现问题.
--design file:clock_tx
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY clock_tx IS
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END clock_tx;
ARCHITECTURE behav OF clock_tx IS
SIGNAL clk_count: STD_LOGIC_VECTOR(15 DOWNTO 0);
SIGNAL clk_bit: STD_LOGIC;
BEGIN
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
clk_count<="0000000000000000";
clk_bit<='0';
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF(clk_count=1042) THEN
clk_count<="0000000000000000";
clk_bit<=not clk_bit;
ELSE
clk_count<=clk_count+1;
END IF;
END IF;
END PROCESS;
clk_out<=clk_bit;
END behav;
--testbench: tb1
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY tb1 IS
END tb1;
ARCHITECTURE be OF tb1 IS
COMPONENT clock_tx
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_in: STD_LOGIC;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_out: STD_LOGIC;
CONSTANT clk_period: time:=25 ns;
BEGIN
uut:clock_tx PORT MAP(clk_in=>clk_in,rst=>rst,clk_out=>clk_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_period/2;
clk_in<='1';
WAIT FOR clk_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR clk_period;
rst<='1';
WAIT ;
END PROCESS;
END be;
--模块2 uart_tx:发送模块
1) 设定
根据模块1生成的发送时钟波特率,按位将数据通过串口发送给PC机,在串口通信助手上显示出来.
2)设计
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY uart_tx IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
send_data: IN STD_LOGIC; --来自接收模块,表示一帧数据准备好,等待本模块接收.
data_in: IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data from uart_re
busy: OUT STD_LOGIC; --when high ,it means data is sending
data_out: OUT STD_LOGIC --parallel data in , serial data out
);
END uart_tx;
ARCHITECTURE behav OF uart_tx IS
COMPONENT clock_tx
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_tx: STD_LOGIC;
SIGNAL data_count: INTEGER RANGE 0 TO 10;
SIGNAL data_buf: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL data_temp: STD_LOGIC_VECTOR(7 DOWNTO 0);
signal clk_re: std_logic:='0';
signal num: std_logic_vector(2 downto 0);
BEGIN
uut: clock_tx PORT MAP(clk_in,rst,clk_tx);
uut_re:clock_re PORT MAP(rst,clk_in,clk_re);
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
data_temp<="11111111";
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF send_data='1' THEN --uart_re模块数据准备好,将其接收进来.
data_temp<=data_in;
else
data_temp<=data_temp;
END IF;
END IF;
END PROCESS;
process(rst,clk_re) --因为send_data=1只持续很短的时间,约为1个clk_re周期,所以敏感信号采样clk_re
begin
if rst='0' then
data_count<=10;
num<="000";
elsif clk_re'event and clk_re='1' then
if send_data='1' and data_count=10 then --数据接收进来,同时总线空闲.
data_count<=0; --重新开始一次新的发送
elsif data_count<10 then
num<=num+"001";
if num="111" then --8个clk_re周期构成一个clk_tx周期,在此周期下数据位加1
num<="000";
data_count<=data_count+1; --data_count为10后,不清0,保证接收一帧数据,发送一帧数据.
end if;
else
data_count<=data_count; --对应send_data/=1 and data_count=10
end if;
end if;
end process;
--之所以data_count要计数到10,是因为根据上一进程,计数归零是在接收进来数据的同时,
--而此时clk_tx并没有触发,参考modelsim仿真波形可知,发送第一位数据的起始,是在data_count
--数值为0和1处,所以,要发送完10位数据,停止位所占的时间在data_count为9时开始,在为10时
--才能结束,因此data_count必须计数到10,否则有可能检测不到停止位,从而产生错误.
process(rst,clk_tx)
begin
if rst='0' then
data_buf<="1111111111"; --保证总线空闲时被拉高
elsif clk_tx'event and clk_tx='1' then
if data_count=0 then --data_count为0时,装入待发送数据
data_buf<='1' & data_temp & '0';
elsif data_count<10 then --移位发送
data_buf<='1' & data_buf(9 downto 1); --高位为1,保证发送完一帧后,总线拉高.
elsif data_count=10 then --为10后,data_buf为全1,保证总线拉高.
data_buf<=data_buf;
end if;
end if;
end process;
busy<='1' WHEN data_count<10 ELSE '0';
data_out<=data_buf(0) ; --发送最低位.
END behav;
testbench: tb2
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY tb2 IS
END tb2;
ARCHITECTURE be OF tb2 IS
COMPONENT uart_tx IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
send_data: IN STD_LOGIC;
data_in: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
busy: OUT STD_LOGIC;
data_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_in: STD_LOGIC;
SIGNAL send_data: STD_LOGIC;
SIGNAL data_in: STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL busy: STD_LOGIC;
SIGNAL data_out: STD_LOGIC;
CONSTANT clk_in_period: time:=25ns;
CONSTANT clk_tx_period: time:=50064ns;
BEGIN
uut: uart_tx PORT MAP
(rst=>rst,clk_in=>clk_in,send_data=>send_data,data_in=>data_in,busy=>busy,data_out=>data_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_in_period/2;
clk_in<='1';
WAIT FOR clk_in_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR clk_tx_period;
rst<='1';
WAIT ;
END PROCESS;
others_gen: PROCESS
BEGIN
send_data<='0';
data_in<="00000000";
WAIT FOR clk_tx_period;
send_data<='1';
data_in<="10101010";
WAIT FOR clk_tx_period/8;
send_data<='0';
data_in<="11111111";
WAIT;
END PROCESS;
END be;
--模块3 接收时钟
1)设定:
接收时钟是波特率时钟的8倍频,即clk_re=clk_tx/8.
这样可以保证在发送时钟周期的特定时刻对数据进行采样.
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
--USE IEEE.NUMERIC_STD.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY clock_re IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END clock_re;
ARCHITECTURE behav OF clock_re IS
SIGNAL clk_count: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL clk_bit: STD_LOGIC;
BEGIN
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
clk_count<="0000000000";
clk_bit<='0';
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF(clk_count=130) THEN
clk_count<="0000000000";
clk_bit<=not clk_bit;
ELSE
clk_count<=clk_count+1;
END IF;
END IF;
END PROCESS;
clk_out<=clk_bit;
END behav;
--testbench:tb3
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY tb3 IS
END tb3;
ARCHITECTURE be OF tb3 IS
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_in: STD_LOGIC;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_out: STD_LOGIC;
CONSTANT clk_period: time:=25ns;
BEGIN
uut:clock_re PORT MAP(clk_in=>clk_in,rst=>rst,clk_out=>clk_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_period/2;
clk_in<='1';
WAIT FOR clk_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR 25ns;
rst<='1';
WAIT ;
END PROCESS;
END be;
--模块4 接收模块
1)设定
-- 在接收数据时,如果接收时钟与发送时钟相同的话,则只需要轻微的频率偏移,就能产生极大的误差,
--所以,为了提高接收的准确性,减少误码率,接收时钟一般都必须采用高频时钟,本例中,每一位数据都采用8倍频的
--波特率来对数据进行采样,即在每发送一位数据的时间内,接收模块已经进行了8次接收,这样保证了数据的正确性。
--接收数据起始位与停止位采用四次采样判断,即在第2,3,4,5个接收数据时钟周期,对数据进行判断,在起始位,若四次采样均为0,
--则判定该位为0,即为有效的起始位,在停止位,则四次为1,判定为1.
--接收数据有效部分采用三次采样判断,即在第4,5,6个接收数据时钟周期,对数据进行判断,也即是在第4,5,6次采样值上,
--判断接收到的数据的值,判断时采用多数表决原则,即三次采样中有两次为某个值,则判断为该值。
2)设计
采样状态机实现,分为四个状态,初始化,采样,得到数值,数据准备好.
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY uart_re IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
ser_in: IN STD_LOGIC;--来自外部(PC机)的串行输入数据
data_ready: INOUT STD_LOGIC;--一帧数据接收完毕,可以输出进行处理.
data_out: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)--有效数据8位并行输出
);
END uart_re;
ARCHITECTURE behav OF uart_re IS
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
COMPONENT clock_tx
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
--串口接收一般采用状态机实现,定义为:初始化,采样,得到数据,帧格式准备好.
TYPE uart_states IS (idle,sampling,get_data,data_ok);
SIGNAL state: uart_states;
SIGNAL clk_re: STD_LOGIC;
SIGNAL clk_tx: STD_LOGIC;
SIGNAL ser_buf1: STD_LOGIC; --级联D触发器,接收输入数据,根据跳变沿判断起始位到来.
SIGNAL ser_buf2: STD_LOGIC;
SIGNAL bit_count: STD_LOGIC_VECTOR(3 DOWNTO 0);--位计数器
SIGNAL data_buf: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL ser_sig2: STD_LOGIC;--采样值寄存器
SIGNAL ser_sig3: STD_LOGIC;
SIGNAL ser_sig4: STD_LOGIC;
SIGNAL ser_sig5: STD_LOGIC;
SIGNAL ser_sig6: STD_LOGIC;
SIGNAL sample: STD_LOGIC; --采样点处理所得结果值
SIGNAL sample_count: STD_LOGIC_VECTOR(2 DOWNTO 0);--采样个数计数值
BEGIN
uut_re:clock_re PORT MAP(rst,clk_in,clk_re);
uut_tx:clock_tx PORT MAP(rst,clk_in,clk_tx);
process(rst,clk_tx)
begin
if rst='0' then
ser_buf1<='1'; --对应总线空闲时为1
ser_buf2<='1';
elsif clk_tx'event and clk_tx='1' then --clk_tx触发,因为输入串行数据在clk_tx周期下变化,保证能够采样到变化值,
ser_buf1<=ser_in; --又保证了ser_buf1/2的值会保持足够长的时间,使得clk_re时钟下能够采样得到
ser_buf2<=ser_buf1; --所需要的正确值.
end if;
end process;
process(rst,clk_re) --clk_re触发,保证采样
begin
if rst='0' then
state<=idle; --复位下为初始状态
data_ready<='0';--不发送数据
sample_count<="000";--采样计数器清0
bit_count<="0000"; --位计数器清0
sample<='1'; --采样结果值置1,与sampling状态下第一个起始位采样值为0区分
ser_sig2<='1';--采样寄存器初始值全为1,保证了采样起始位时,根据要求,必须全变化为0方能得到代表起始位的0
ser_sig3<='1';
ser_sig4<='1';
ser_sig5<='1';
ser_sig6<='1';
data_buf<="0000000000";
elsif clk_re'event and clk_re='1' then
case state is
when idle=>
if ser_buf2='1' and ser_buf1='0' then --ser_buf1先变化,此处对应为接收到一个下跳变
state<=sampling; --检测到下跳变脉冲,启动采样
else
state<=idle;
end if;
data_ready<='0';
data_buf<="1111111111"; --对应总线空闲挂起为1
bit_count<="0000";
sample_count<="000";
when sampling=>
if bit_count="0000" then --start bit,代表起始位
--采样赋值来源是根据ser_buf1,个人理解也是针对先变化的这个值,
--参考一些程序是根据ser_buf2,
--总之可以先仿真,再根据仿真图上波形,来决定是ser_buf1/2能够得到正确采样结果.
--或者可以在实际运行时,分别比较两者的正确性.
if sample_count="001" then --第二个采样点
ser_sig2<=ser_buf1;
elsif sample_count="010" then --第三个采样点
ser_sig3<=ser_buf1;
elsif sample_count="011" then --第四个采样点
ser_sig4<=ser_buf1;
elsif sample_count="100" then --第五个采样点
ser_sig5<=ser_buf1;
end if;
--采样结果值采样或逻辑,即全为0时,sample为方为1
--采样结果值应该在本状态下给出,若在get_data中给出,因为信号赋值的延迟,
--将导致被判断的采样结果值不是当前值,而是前一个数值,导致错误.
sample<=ser_sig2 or ser_sig3 or ser_sig4 or ser_sig5;
elsif bit_count="1001" then --stop bit,停止位
if sample_count="001" then
ser_sig2<=ser_buf1;
elsif sample_count="010" then
ser_sig3<=ser_buf1;
elsif sample_count="011" then
ser_sig4<=ser_buf1;
elsif sample_count="100" then
ser_sig5<=ser_buf1;
end if;
sample<=ser_sig2 and ser_sig3 and ser_sig4 and ser_sig5;
else --有效数据位(8位)
if sample_count="011" then --第四个采样点
ser_sig4<=ser_buf1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -