⭐目的:使用VHDL实现串口通信
⭐使用芯片:EP1C3T144C8
⭐实现结果:两块FPGA通信板均可作为发送方和接收方。按下复位键后,发送方根据拨码开关显示选择发送的数据,按下发送键后发送;接收方立马接收到数据并显示(特别快)
一、具体步骤
1.数码管显示
这个地方个人使用的是测试法,大家也可以查看原理图一步到位。
- 分清位码/段码引脚,给位码引脚0/1判断0/1谁是使能点亮?各个位码对应哪个数码管?
- 给段码引脚0/1判断0/1谁是使能点亮?各个段码对应数码管的哪条边?
测试结果表明0是点亮。
--------------------数据输入及显示部分-----------------------------------------------------
process(Rx_Valid,CLK)
begin
if RESET='0' then
case button is
when "000"=>Datarx<="01000000";
when "001"=>Datarx<="01111001";
when "010"=>Datarx<="00100100";
when "011"=>Datarx<="00110000";
when "100"=>Datarx<="00011001";
when "101"=>Datarx<="00010010";
when "110"=>Datarx<="00000010";
when "111"=>Datarx<="01111000";
when others=>null;
end case;
Data00<='1'&Datarx&'0';
else
if rising_edge(Rx_Valid) then
Datarx<=Rx_Data;
end if;
end if;
end process;
--------------------数据输入及显示部分-----------------------------------------------------
2.波特率发生器
波特率发生器实质是设计一个分频器,用于产生和RS232通信同步的时钟。在系统中用一个计数器来完成这个功能,系统时钟为40MHz,9600波特率时钟的周期约等于4167个系统时钟周期,则需要计数器为4167时输出高电平,同时令计数器为0,计数器为其他值时输出时钟保持低电平不变。由于接收方隔16个接收时钟接收一次,所以接受方的频率应该是发送方的16倍,即接受方需生成16倍于波特率的时钟,4167/16≈260。
--------------------产生波特率-----------------------------------------------------
process(CLK,RESET,Rx_Hold) --时钟产生进程
variable ClockCount : integer range 0 to 4167:=0;
variable ClockCount_Rx : integer range 0 to 260:=0;
begin
if (rising_edge(CLK)) then
ClockCount:=ClockCount+1;
ClockCount_Rx:=ClockCount_Rx+1;
--产生9600时钟,1/40MHZ*4167=1/9600
if ClockCount=4167 then Clock9600<='1';ClockCount:=0;
else Clock9600<='0';
end if;
--4167/16=260
if ClockCount_Rx=260 then Clock3<='1';ClockCount_Rx:=0;
else Clock3<='0';
end if;
end if;
end process;
--------------------产生波特率-----------------------------------------------------
3.串口发送部分
发送器的设计较容易,设置发送使能Send_en以及发送结束Send_over标志位,当复位键按下时,发送不使能,发送信号为"1000000000";复位键未按下时,当Clock9600上升沿到来时,发送使能,并按照拨码开关模式选择发送信号。如果发送使能,则发送起始位‘1’,并开始计数发送Bit数目,当Clock9600上升沿到来时,依次发送数据。如果发送Bit计数器为9时,则发送结束位‘0’,同时复位发送Bit计数器,并使能发送结束Send_over标志位。
变量 | 功能说明 |
Send_count | 位发送计数器 |
Send_data | 发送数据 |
Send_en | 发送使能 |
Send_over | 发送结束标志位 |
UART0_TX | 发送信号 |
RESET | 复位信号 |
rx_begin_key | 开始发送按键 |
Clock9600 | 发送方波特率时钟 |
Data00 | 根据拨码开关所选择的发送数据 |
--------------------串口发送部分-----------------------------------------------------
process(Clock9600)
--variable Send_count:integer range 0 to 9 :=0; --位发送计数
begin
if Send_en='1' then
Send_count<=0;UART0_TX<='1';Send_over<='1';
elsif rising_edge(Clock9600) then
if Send_count=9 then
UART0_TX<=Send_data(9);Send_over<='0';
else
UART0_TX<=Send_data(Send_count);
Send_count<=Send_count+1;
end if;
end if;
end process;
process(rx_begin_key,Clock9600,RESET) --发送激励
begin
if RESET='0' then Send_en<='1';Send_data<="1000000000";
else
if (rx_begin_key='1') then
Send_en<='0'; Send_data<=Data00; --发送赋初值
end if;
if Send_over='0' then Send_en<='1'; end if;
end if;
end process;
--------------------串口发送部分-----------------------------------------------------
4.串口接收部分
串行数据帧和接收时钟是异步的,发送来的数据由高电平1变为低电平0可以视为一个数据帧的开始。接收器首先要捕捉起始位,然而,通信线上的噪音也极有可能使信号‘1’跳变到‘0’。所以接收器要以16倍的波特率对这种跳变进行检测,确定UART0_RX输入由‘1’到‘0’,低电平‘0’要保持8个Clock3(16倍的波特率时钟)周期,才是正常的起始位,而不是噪音引起的,其中若有依次采样得到高电平则认为起始信号无效,返回初始状态重新等待起始信号的到来。
采到正确的起始位后,就开始接收数据,最可靠的接收应该是接收时钟的出现时刻正好对着数据位的中央,因此设置Clock3上升沿计数器m,m为24时接收第一位有效bit,此时接收时刻正对数据位的中央。接受方设置接收使能标志位Rx_Hold,当Rx_Hold为‘1’时开始接收;Rx_Hold为‘0’接收方挂起,不进行数据接收。同时设置接收结束标志位Rx_Valid,当接收到8位信号后,Rx_Valid置高。如果Rx_Hold为‘1’便开始接收,设置Clock3上升沿计数器m,当m为16的倍数时,接收寄存器接收数据,接收到9位信号后,接收bit计数器m清零,Rx_Valid置低。
变量 | 功能说明 |
RESET | 复位 |
Rx_Valid | 接收有效信号 |
Rx_Hold | 接收方使能信号 |
Clock3 | 接收方时钟频率 |
m | 接收方时钟计数器 |
--------------------串口接收部分-----------------------------------------------------
process(RESET,Rx_Valid) --串口接收检测起始位
begin
if RESET='0' then Rx_Hold<='0';
else
if UART0_RX='0' and Rx_Hold='0' then Rx_Hold<='1'; --开始串口接收
elsif Rx_Valid'event and Rx_Valid='0' then Rx_Hold<='0';
end if;
end if;
end process;
process(RESET,Clock3)
variable m:integer range 0 to 168 :=0;
begin
if RESET='0' then Rx_Valid<='0';m:=0;
elsif rising_edge(Clock3) and Rx_Hold='1'
then
case m is
when 24 => Rx_Data(0)<=UART0_RX;
when 40 => Rx_Data(1)<=UART0_RX;
when 56 => Rx_Data(2)<=UART0_RX;
when 72 => Rx_Data(3)<=UART0_RX;
when 88 => Rx_Data(4)<=UART0_RX;
when 104 =>Rx_Data(5)<=UART0_RX;
when 120 =>Rx_Data(6)<=UART0_RX;
when 136=> Rx_Data(7)<=UART0_RX;
when 152 =>Rx_Valid<='1';
when 168=> m:=0;Rx_Valid<='0';
when others => null;
end case;
m:=m+1;
end if;
end process;
--------------------串口接收部分-----------------------------------------------------
二、完整代码
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity txrx is
port
( RESET :in std_logic; --复位信号
button : in std_logic_vector(2 downto 0);
CLK : in std_logic; --时钟输入,50MHZ
rx_begin_key : in std_logic; --发送使能按键
Send_count:buffer integer range 0 to 9 :=0; --位发送计数
--Data00 : buffer std_logic_vector(9 downto 0);
Datarx:buffer std_logic_vector(7 downto 0):="00000000";
UART0_RX : in std_logic:='0'; --接收的引脚
UART0_TX:out std_logic:='0' --发送的引脚
);
end txrx;
architecture behave of txrx is
Signal Clock9600 : std_logic:='0'; --9600波特率时钟,发送
Signal Clock3 : std_logic:='0'; --9600三倍频采样时钟,用于接收采用
signal Data00 :std_logic_vector(9 downto 0);
Signal Send_data : std_logic_vector(9 downto 0); --发送寄存器
Signal Send_en : std_logic:='0'; --串口发送使能,0有效
Signal Send_over : std_logic:='1'; --串口发送完成,0有效
Signal Rx_Hold : std_logic:='1'; --串口接收到起始位标志
Signal Rx_Valid : std_logic:='0'; --标志接收到一字节有效数据
Signal Rx_Data : std_logic_vector(7 downto 0); -接收寄存器
begin
--------------------产生波特率-----------------------------------------------------
process(CLK,RESET,Rx_Hold) --时钟产生进程
variable ClockCount : integer range 0 to 4167:=0;
variable ClockCount_Rx : integer range 0 to 260:=0;
begin
if (rising_edge(CLK)) then
ClockCount:=ClockCount+1;
ClockCount_Rx:=ClockCount_Rx+1;
--产生9600时钟,1/40MHZ*4167=1/9600
if ClockCount=4167 then Clock9600<='1';ClockCount:=0;
else Clock9600<='0';
end if;
--4167/16=260
if ClockCount_Rx=260 then Clock3<='1';ClockCount_Rx:=0;
else Clock3<='0';
end if;
end if;
end process;
--------------------产生波特率-----------------------------------------------------
--------------------串口发送部分-----------------------------------------------------
process(Clock9600)
--variable Send_count:integer range 0 to 9 :=0; --位发送计数
begin
if Send_en='1' then
Send_count<=0;UART0_TX<='1';Send_over<='1';
elsif rising_edge(Clock9600) then
if Send_count=9 then
UART0_TX<=Send_data(9);Send_over<='0';
else
UART0_TX<=Send_data(Send_count);
Send_count<=Send_count+1;
end if;
end if;
end process;
process(rx_begin_key,Clock9600,RESET) --发送激励
begin
if RESET='0' then Send_en<='1';Send_data<="1000000000";
else
if (rx_begin_key='1') then
Send_en<='0'; Send_data<=Data00; --发送赋初值
end if;
if Send_over='0' then Send_en<='1'; end if;
end if;
end process;
--------------------串口发送部分-----------------------------------------------------
--------------------串口接收部分-----------------------------------------------------
process(RESET,Rx_Valid) --串口接收检测起始位
begin
if RESET='0' then Rx_Hold<='0';
else
if UART0_RX='0' and Rx_Hold='0' then Rx_Hold<='1'; --开始串口接收
elsif Rx_Valid'event and Rx_Valid='0' then Rx_Hold<='0';
end if;
end if;
end process;
process(RESET,Clock3)
variable m:integer range 0 to 168 :=0;
begin
if RESET='0' then Rx_Valid<='0';m:=0;
elsif rising_edge(Clock3) and Rx_Hold='1'
then
case m is
when 24 => Rx_Data(0)<=UART0_RX;
when 40 => Rx_Data(1)<=UART0_RX;
when 56 => Rx_Data(2)<=UART0_RX;
when 72 => Rx_Data(3)<=UART0_RX;
when 88 => Rx_Data(4)<=UART0_RX;
when 104 =>Rx_Data(5)<=UART0_RX;
when 120 =>Rx_Data(6)<=UART0_RX;
when 136=> Rx_Data(7)<=UART0_RX;
when 152 =>Rx_Valid<='1';
when 168=> m:=0;Rx_Valid<='0';
when others => null;
end case;
m:=m+1;
end if;
end process;
--------------------串口接收部分-----------------------------------------------------
--------------------数据输入及显示部分-----------------------------------------------------
process(Rx_Valid,CLK)
begin
if RESET='0' then
case button is
when "000"=>Datarx<="01000000";
when "001"=>Datarx<="01111001";
when "010"=>Datarx<="00100100";
when "011"=>Datarx<="00110000";
when "100"=>Datarx<="00011001";
when "101"=>Datarx<="00010010";
when "110"=>Datarx<="00000010";
when "111"=>Datarx<="01111000";
when others=>null;
end case;
Data00<='1'&Datarx&'0';
else
if rising_edge(Rx_Valid) then
Datarx<=Rx_Data;
end if;
end if;
end process;
--------------------数据输入及显示部分-----------------------------------------------------
end behave;