计算机与 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 =&gt; if RxD = '0' then 将受到故障的影响。

【讨论】:

非常感谢你们两位。确实,我可以使用通用时钟而不是两个不同的时钟。我试图删除两个时钟的异步重置,但在观察到的行为方面没有任何成功。你有关于这两个人字拖的互联网参考吗?有原理图什么的?因为我是 VHDL 的新手,我真的不明白你的意思,以及那些触发器的用途。将不胜感激!非常感谢! 只需谷歌“时钟域同步”或“两阶段同步器”

以上是关于计算机与 BASYS 3 FPGA 之间的 UART 通信的主要内容,如果未能解决你的问题,请参考以下文章

基于basys2驱动LCDQC12864B的verilog设计图片显示

FPGA按键防抖

FPGA按键防抖

FPGA按键防抖

基于Quartus II软件FPGA与PC之间的串行通信系统开发(9000+字)

FPGA开发基础————异步FIFO深度的计算与分析