计算机与 BASYS 3 FPGA 之间的 UART 通信
Posted
技术标签:
【中文标题】计算机与 BASYS 3 FPGA 之间的 UART 通信【英文标题】:UART communication between computer and BASYS 3 FPGA 【发布时间】:2017-08-06 13:52:23 【问题描述】:我有一个项目,我需要将数据从 Windows 10 计算机发送到 BASYS 3 板(ARTIX7 FPGA)。我使用UART来做到这一点。在 PuTTY 串行控制台中输入要发送的数据。
出于测试目的,我决定使用板上的 8 个 LED 显示接收到的数据。
我正在使用 Vivado 2016.4。
我遇到的问题是,我在 LED 上获得的数据与应有的数据完全不同。我想这是 PuTTY 的波特率和我的 VHDL 模块之间的同步问题。
请在下文中找到该项目的 .vhd 文件和 .xdc 文件:
.vhd 基于有限状态机 (FSM),有两个信号允许同步:
tick_UART :它每 10417 个时钟周期滴答一次。由于时钟周期为 10 ns,tick_UART 每秒上升 9600 次(我打算在 9600 波特下使用)。
double_tick_UART:两倍于tick_UART的频率,用于对中间的位进行采样。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity UART_RX is
Port ( RxD : in STD_LOGIC;
clk : in STD_LOGIC;
RAZ : in STD_LOGIC;
data_out : out STD_LOGIC_VECTOR (7 downto 0));
end UART_RX;
architecture Behavioral of UART_RX is
signal tick_UART : STD_LOGIC; -- Signal "top" passage d'un état à l'autre selon vitesse connexion série
signal double_tick_UART : STD_LOGIC; -- Signal précédent, fréquence * 2
signal compteur_tick_UART : integer range 0 to 10420; -- Compteur pour tick_UART
signal double_compteur_tick_UART : integer range 0 to 5210; -- Compteur pour demi-périodes
type state_type is (idle, start, demiStart, b0, b1, b2, b3, b4, b5, b6, b7, stop); -- Etats de la FSM
signal state :state_type := idle; -- Etat par défaut
signal RAZ_tick_UART : STD_LOGIC; -- RAZ du signal tick_UART;
begin
process(clk, RAZ, state, RAZ_tick_UART) -- Compteur classique (tick_UART)
begin
if (raz='1') or (state = idle) or (RAZ_tick_UART = '1') then
compteur_tick_UART <= 0;
tick_UART <= '0';
elsif clk = '1' and clk'event then
if compteur_tick_UART = 10417 then
tick_UART <= '1';
compteur_tick_UART <= 0;
else
compteur_tick_UART <= compteur_tick_UART + 1;
tick_UART <= '0';
end if;
end if;
end process;
process(clk, RAZ, state) -- Compteur demi-périodes (double_tick_UART car fréquence double)
begin
if (raz='1') or (state = idle) then
double_compteur_tick_UART <= 0;
double_tick_UART <= '0';
elsif clk = '1' and clk'event then
if double_compteur_tick_UART = 5209 then
double_tick_UART <= '1';
double_compteur_tick_UART <= 0;
else
double_compteur_tick_UART <= double_compteur_tick_UART + 1;
double_tick_UART <= '0';
end if;
end if;
end process;
fsm:process(clk, RAZ) -- Machine à état
begin
if (RAZ = '1') then
state <= idle;
data_out <= "00000000";
RAZ_tick_UART <= '1';
elsif clk = '1' and clk'event then
case state is
when idle => if RxD = '0' then -- Si front descendant de RxD et en idle
state <= start;
RAZ_tick_UART <= '1';
end if;
when start => if double_tick_UART = '1' then
state <= demiStart;
RAZ_tick_UART <= '0';
end if;
data_out <= "00000000";
when demiStart => if tick_UART = '1' then
state <= b0;
RAZ_tick_UART <= '0';
end if;
data_out(0) <= RxD; -- Acquisition bit 0
when b0 => if tick_UART = '1' then
state <= b1;
end if;
data_out(1) <= RxD; -- Acquisition bit 1
when b1 => if tick_UART = '1' then
state <= b2;
end if;
data_out(2) <= RxD; -- Acquisition bit 2
when b2 => if tick_UART = '1' then
state <= b3;
end if;
data_out(3) <= RxD; -- Acquisition bit 3
when b3 => if tick_UART = '1' then
state <= b4;
end if;
data_out(4) <= RxD; -- Acquisition bit 4
when b4 => if tick_UART = '1' then
state <= b5;
end if;
data_out(5) <= RxD; -- Acquisition bit 5
when b5 => if tick_UART = '1' then
state <= b6;
end if;
data_out(6) <= RxD; -- Acquisition bit 6
when b6 => if tick_UART = '1' then
state <= b7;
end if;
data_out(7) <= RxD; -- Acquisition bit 7
when b7 => if tick_UART = '1' then
state <= stop;
end if;
when stop => if tick_UART = '1' then
state <= idle; -- Renvoi en idle
end if;
end case;
end if;
end process;
end Behavioral;
XDC 文件:
## Clock signal
set_property PACKAGE_PIN W5 [get_ports clk]
set_property iosTANDARD LVCMOS33 [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform 0 5 [get_ports clk]
## LEDs
set_property PACKAGE_PIN U16 [get_ports data_out[0]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[0]]
set_property PACKAGE_PIN E19 [get_ports data_out[1]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[1]]
set_property PACKAGE_PIN U19 [get_ports data_out[2]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[2]]
set_property PACKAGE_PIN V19 [get_ports data_out[3]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[3]]
set_property PACKAGE_PIN W18 [get_ports data_out[4]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[4]]
set_property PACKAGE_PIN U15 [get_ports data_out[5]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[5]]
set_property PACKAGE_PIN U14 [get_ports data_out[6]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[6]]
set_property PACKAGE_PIN V14 [get_ports data_out[7]]
set_property IOSTANDARD LVCMOS33 [get_ports data_out[7]]
##Buttons
set_property PACKAGE_PIN T18 [get_ports RAZ]
set_property IOSTANDARD LVCMOS33 [get_ports RAZ]
##USB-RS232 Interface
set_property PACKAGE_PIN B18 [get_ports RxD]
set_property IOSTANDARD LVCMOS33 [get_ports RxD]
你发现有什么错误吗?
我还尝试使用另一个 .vhd(不是我自己编写的,应该可以工作)。 这也不起作用:https://www.nandland.com/vhdl/modules/module-uart-serial-port-rs232.html (我根据我的时钟和波特率很好地修改了通用 g_CLKS_PER_BIT)
问题可能来自 PuTTY,但我已将波特率设置为 9600 波特、8 个数据位、1 个停止位、无奇偶校验,所以我看不出有什么问题!
如果你有进一步的想法/cmets,因为我找不到问题所在!
非常感谢!
2017 年 3 月 16 日编辑:
按照 @J.H.Bonarius 和 @user1155120 的建议,我添加了一个 2 级触发器同步器,以将 RxD 输入信号与我的 100 MHz 时钟域同步。
我还修改了一些异步重置。 尽管如此,我仍然遇到同样的问题(LED 与通过 PuTTY 发送的内容不对应)。
在新的 .vhd 代码之后查找消息:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity UART_RX is
Port ( RxD_in : in STD_LOGIC;
clk : in STD_LOGIC;
RAZ : in STD_LOGIC;
data_out : out STD_LOGIC_VECTOR (7 downto 0));
end UART_RX;
architecture Behavioral of UART_RX is
signal tick_UART : STD_LOGIC; -- Signal "top" passage d'un état à l'autre selon vitesse connexion série
signal double_tick_UART : STD_LOGIC; -- Signal précédent, fréquence * 2
signal compteur_tick_UART : integer range 0 to 10420; -- Compteur pour tick_UART
signal double_compteur_tick_UART : integer range 0 to 5210; -- Compteur pour demi-périodes
type state_type is (idle, start, demiStart, b0, b1, b2, b3, b4, b5, b6, b7); -- Etats de la FSM
signal state :state_type := idle; -- Etat par défaut
signal RAZ_tick_UART : STD_LOGIC; -- RAZ du signal tick_UART;
signal RxD_temp : STD_LOGIC; -- RxD provisoire entre deux FF
signal RxD_sync : STD_LOGIC; -- RxD synchronisé sur l'horloge
begin
D_flip_flop_1:process(clk) -- Clock crossing
begin
if clk = '1' and clk'event then
RxD_temp <= RxD_in;
end if;
end process;
D_flip_flop_2:process(clk) -- Clock crossing
begin
if clk = '1' and clk'event then
RxD_sync <= RxD_temp;
end if;
end process;
tickUART:process(clk, RAZ, state, RAZ_tick_UART) -- Compteur classique (tick_UART)
begin
if clk = '1' and clk'event then
if (RAZ='1') or (state = idle) or (RAZ_tick_UART = '1') then
compteur_tick_UART <= 0;
tick_UART <= '0';
elsif compteur_tick_UART = 10417 then
tick_UART <= '1';
compteur_tick_UART <= 0;
else
compteur_tick_UART <= compteur_tick_UART + 1;
tick_UART <= '0';
end if;
end if;
end process;
doubleTickUART:process(clk, RAZ, state) -- Compteur demi-périodes (double_tick_UART car fréquence double)
begin
if clk = '1' and clk'event then
if (RAZ='1') or (state = idle) then
double_compteur_tick_UART <= 0;
double_tick_UART <= '0';
elsif double_compteur_tick_UART = 5209 then
double_tick_UART <= '1';
double_compteur_tick_UART <= 0;
else
double_compteur_tick_UART <= double_compteur_tick_UART + 1;
double_tick_UART <= '0';
end if;
end if;
end process;
fsm:process(clk, RAZ) -- Machine à état
begin
if (RAZ = '1') then
state <= idle;
data_out <= "00000000";
RAZ_tick_UART <= '1';
elsif clk = '1' and clk'event then
case state is
when idle => if RxD_sync = '0' then -- Si front descendant de RxD (= bit de start) et en idle
state <= start;
RAZ_tick_UART <= '1';
end if;
when start =>if double_tick_UART = '1' then -- Demi période écoulée (pour échantillonage)
state <= demiStart;
RAZ_tick_UART <= '0'; -- Le compteur tick_UART commence à compter
end if;
data_out <= "00000000"; -- Reset des anciennes données
when demiStart => if tick_UART = '1' then
state <= b0;
RAZ_tick_UART <= '0';
end if;
data_out(0) <= RxD_sync; -- Acquisition bit 0
when b0 => if tick_UART = '1' then
state <= b1;
end if;
data_out(1) <= RxD_sync; -- Acquisition bit 1
when b1 => if tick_UART = '1' then
state <= b2;
end if;
data_out(2) <= RxD_sync; -- Acquisition bit 2
when b2 => if tick_UART = '1' then
state <= b3;
end if;
data_out(3) <= RxD_sync; -- Acquisition bit 3
when b3 => if tick_UART = '1' then
state <= b4;
end if;
data_out(4) <= RxD_sync; -- Acquisition bit 4
when b4 => if tick_UART = '1' then
state <= b5;
end if;
data_out(5) <= RxD_sync; -- Acquisition bit 5
when b5 => if tick_UART = '1' then
state <= b6;
end if;
data_out(6) <= RxD_sync; -- Acquisition bit 6
when b6 => if tick_UART = '1' then
state <= b7;
end if;
data_out(7) <= RxD_sync; -- Acquisition bit 7
when b7 => if tick_UART = '1' then
state <= idle; -- state <= stop;
end if;
end case;
end if;
end process;
end Behavioral;
您对我的问题的根源有任何想法吗? 非常感谢!
【问题讨论】:
您的 uart_rx 成功模拟,并添加了每位时钟的通用(以缩短模拟中的时钟数)。 JHB 建议将触发器(两个,基于 100 MHz 时钟)与 rxd 对齐是有效的。 当您编辑问题时,我们不会收到更新。但无论如何:也许您还应该编写一个测试台,以模拟 RS232 输入信号行为。然后您可以查看您的代码是否错误,如果是:出了什么问题。在综合之前测试你的代码总是一个好主意...... 【参考方案1】:首先if (raz='1') or (state = idle) or (RAZ_tick_UART = '1')
然后不要在异步重置输入中放入太多东西。实际上:根本不要使用异步重置。他们会将逻辑引入时钟路径。
第二件事:在您的 UART RxD
上放置一些时钟域同步可能是个好主意。只是一个两级同步器。否则when idle => if RxD = '0' then
将受到故障的影响。
【讨论】:
非常感谢你们两位。确实,我可以使用通用时钟而不是两个不同的时钟。我试图删除两个时钟的异步重置,但在观察到的行为方面没有任何成功。你有关于这两个人字拖的互联网参考吗?有原理图什么的?因为我是 VHDL 的新手,我真的不明白你的意思,以及那些触发器的用途。将不胜感激!非常感谢! 只需谷歌“时钟域同步”或“两阶段同步器”以上是关于计算机与 BASYS 3 FPGA 之间的 UART 通信的主要内容,如果未能解决你的问题,请参考以下文章
基于basys2驱动LCDQC12864B的verilog设计图片显示