VHDL实现UART串口通信

目录

前言

一、原理说明

接收机:(波特率取9600bps)

发射机:(波特率取9600bps)

二、顶层模块

三、UART通信接收机

四、UART通信发射机

五、引脚配置

六、实验演示

总结


前言

        这是我参照正点原子给的FPGA开发指南写的代码,所有的原理图也来源于指南。资料(指南+XCOM+CH340驱动)自取:
链接:使用夸克网盘打开
提取码:XRaz


一、原理说明

        

        图1  串口通信连接与时序图


        这次实验的通信双方分别是FPGA和电脑,关系是通过串口调试助手电脑发送数据到FPGA内部的接收机上,然后实时并行传送到FPGA内部的发射机,然后发射机再将数据原封不动的发射回到电脑,并由串口调试助手显示出来。(左图我画的不知是否正确)

(下面讲一下我理解的通信流程,可以不看)

接收机:(波特率取9600bps)

①复位键未按下时,不停通过边缘检测来寻找起始位,若检测到低电平(即起始位)则开始接收数据;(边缘检测见接收机的代码)

②内部的系统时钟计数器开始循环计数,因为波特率和系统时钟并不相同,所以通过计算式CLK_FREQ(系统时钟频率)/UART_BPS(波特率)可得一波特率对应的时钟计数。时钟每计数到5208次(50M÷9600)则使接收计数加1(当达到10时说明1bit起始位+8bit数据位+1bit停止位都接收完了),第1位到第8位的数据依次寄存到数据寄存器;

③当接收计数加到9并且停止位的时钟计数到5208/2时,即可认为接收机一帧数据接收结束。


补充:接收机内部的开始和停止都由对应的标识来确定,在运行过程中,需要时刻对它们进行监测,而数据是先存储到内部的寄存器(缓冲作用),所有数据全部传输完毕再一起输出。结束和复位时需要对它们进行清零和复位。

发射机:(波特率取9600bps)

①当发射机的使能端口为高电平时,将数据输入端的数据寄存到内部的寄存器,忙信号电平拉高(这次实验中busy不作用外部);

②然后内部的系统时钟计数器开始循环计数,使寄存器的数据依次通过输出端口输出,同时发送计数加1;

③当接收计数加到9且到停止位的时钟计数完成后时(这里要计数满,因为输出端口停止位时间要和其他位保持一致),即可认为发射机的一帧数据发送结束。

二、顶层模块

图2 顶层系统框图


代码如下:

library ieee;

use ieee.std_logic_1164.all;
--顶层设计
entity UART is
	port(
	clk:in std_logic;--系统时钟
	rst_n:in std_logic;--复位键(低电平有效,实验板对应为按下)
	uart_rxd:in std_logic;--UART串口通信接收模块
	uart_txd:out std_logic;--UART串口通信发送模块
	led:out std_logic_vector(1 downto 0)--提示灯
	);
end UART;

architecture behav of UART is
--子模块——接收模块
component uart_rx is--
port(
		clk:in std_logic;--系统时钟
		rst_n:in std_logic;--复位(低电平有效)
		uart_rxd:in std_logic;--数据输入端口
		uart_rx_done:out std_logic;--数据接收完成 输出端口
		led:out std_logic;----传输提示灯
		uart_rx_data:buffer std_logic_vector(7 downto 0));--数据输出端口,需要自持
	end component;
--子模块——发送模块
component uart_tx is
	port(
			clk:in std_logic;--系统时钟
			rst_n:in std_logic;--复位(低电平有效)
			uart_tx_en:in std_logic;--发送端使能(高电平有效,无n后缀都为高电平有效)
			uart_tx_data:in std_logic_vector(7 downto 0);--数据输入端口(并行->串行)
			uart_txd:out std_logic;--数据输出端口(串行->并行)
			led:out std_logic;--传输提示灯
			uart_tx_busy:buffer std_logic);--传输忙 输出端口
	end component;
signal uart_tx_en:std_logic;--内部连接线,这里使收发机直接连接
signal uart_data:std_logic_vector(7 downto 0);--内部连接线,这里使收发机直接连接
begin
	--端口映射
	RXD:uart_rx port map(clk=>clk, rst_n=>rst_n, uart_rxd=>uart_rxd, uart_rx_done=>uart_tx_en, uart_rx_data=>uart_data, led=>led(1));
	TXD:uart_tx port map(clk=>clk, rst_n=>rst_n, uart_txd=>uart_txd, uart_tx_en=>uart_tx_en, uart_tx_data=>uart_data, led=>led(0));
end behav;

三、UART通信接收机

图3  串口接收模块接口框图 & 串口接收模块端口与功能描述


代码如下:

library ieee;
use ieee.std_logic_1164.all;
--UART通信接收机
entity uart_rx is
	port(
		clk:in std_logic;--系统时钟
		rst_n:in std_logic;--复位(低电平有效)
		uart_rxd:in std_logic;--数据输入端口
		uart_rx_done:out std_logic;--数据接收完成 输出端口
		led:out std_logic;----传输提示灯
		uart_rx_data:buffer std_logic_vector(7 downto 0));--数据输出端口,需要自持
end uart_rx;

architecture behav of uart_rx is
constant CLK_FREQ: integer:=50_000_000;--系统时钟频率
constant UART_BPS:integer:=9_600;--通信波特率
constant BAUD_CNT_MAX:integer:=(CLK_FREQ/UART_BPS);--每波特率对应需要的系统时钟计数
signal uart_rxd_d0:std_logic:='1';--边缘检测用寄存器,这个其实是多余的,可以不用这个
signal uart_rxd_d1:std_logic:='1';--边缘检测用寄存器
signal uart_rxd_d2:std_logic:='1';--边缘检测用寄存器
signal rx_flag:std_logic:='0';--接收进行中标志
signal rx_cnt:integer range 0 to 9:=0;--已接收数据计数寄存器
signal baud_cnt:integer range 0 to BAUD_CNT_MAX:=0;--波特率的系统时钟计数寄存器
signal rx_data_t:std_logic_vector(7 downto 0):="00000000";--接收数据缓存寄存器
signal start_en:std_logic:='0';--开始接收使能

begin
		start_en<=uart_rxd_d2 and (not uart_rxd_d1) and (not rx_flag);
		--翻译:在未开始接收时(因为rx_flag未使能),如uart_rxd_d2这个寄存器为'1',而uart_rxd_d1这个寄存器为'0',则开始接收(原理看原理图)
	process(clk, rst_n)--边缘检测,可检测某一信号是否发生改变
		begin
			if(rising_edge(clk)) then
				if(rst_n = '0') then
					uart_rxd_d0<='1';
               uart_rxd_d1<='1';
					uart_rxd_d2<='1';--
				else--信号经过三级寄存
					uart_rxd_d0<=uart_rxd;
               uart_rxd_d1<=uart_rxd_d0;
					uart_rxd_d2<=uart_rxd_d1;
				end if;
			end if;
		end process;
		
	process(clk, rst_n, rx_cnt, baud_cnt)--给接收标志赋值
		begin
			if(rising_edge(clk)) then
				if(rst_n = '0') then--复位时不接收
					rx_flag<='0';
				elsif(start_en='1') then--开始使能,只需一个系统时钟周期即可,后续无修改则rx_flag的值会一直保存
					rx_flag<='1';
				elsif(rx_cnt=9 and baud_cnt=BAUD_CNT_MAX/2-1) then--当数据接收到第九个,第十个的baud计数到一半时,rx_flag置位
					rx_flag<='0';
				else rx_flag<=rx_flag;--接收过程或未开始时flag状态保持,没有也可以(默认保持),最好有
				end if;
			end if;
		end process;
		
	process(clk, rst_n, rx_flag)--波特率计数器赋值
		begin
			if(rising_edge(clk)) then
				if(rst_n='0') then--复位时,baud计数置为0
					baud_cnt<=0;
				elsif(rx_flag='1') then--数据接收过程,baud以BAUD_CNT_MAX为周期计数,每周期代表接收一个数据
					if(baud_cnt<BAUD_CNT_MAX) then
						baud_cnt<=baud_cnt+1;
					else baud_cnt<=0;--一周期结束下周期开始
					end if;
				else baud_cnt<=0;--接收未开始时保持为0
				end if;
			end if;
		end process;
		
	process(clk, rst_n, baud_cnt) --接收数据计数器赋值
		begin
			if(rising_edge(clk)) then
			if(rst_n='0') then--复位时,接收的数据置为0
					rx_cnt<=0;
				elsif(rx_flag='1') then--在接收状态才计数
					if(baud_cnt=BAUD_CNT_MAX-1) then
						rx_cnt<=rx_cnt+1;
					else rx_cnt<=rx_cnt;--没计数到BAUD_CNT_MAX都算一个数据
					end if;
				else rx_cnt<=0;--非接收状态则不计数
				end if;
			end if;
		end process;	
		
	process(clk, rst_n, baud_cnt)--数据寄存器赋值
		begin
			if(rising_edge(clk)) then
				if(rst_n='0') then--复位接受数据置'0'
					rx_data_t<="00000000";
					led<='0';
				elsif(rx_flag='1') then
					led<='1';--接收过程中指示灯亮
					if(baud_cnt=BAUD_CNT_MAX/2 - 1) then--在计数到中间时刻存入数据,此时数据最稳定!
						case rx_cnt is--第0个cnt为起始位,故不存入数据寄存器
							when 1=> rx_data_t(0)<=uart_rxd_d2;--第1~8个cnt是数据位,依次存入数据寄存器
							when 2=> rx_data_t(1)<=uart_rxd_d2;
							when 3=> rx_data_t(2)<=uart_rxd_d2;
							when 4=> rx_data_t(3)<=uart_rxd_d2;
							when 5=> rx_data_t(4)<=uart_rxd_d2;
							when 6=> rx_data_t(5)<=uart_rxd_d2;
							when 7=> rx_data_t(6)<=uart_rxd_d2;
							when 8=> rx_data_t(7)<=uart_rxd_d2;
							when others=> rx_data_t<=rx_data_t;--确保覆盖所有情况,否则报错
						end case;
					else rx_data_t<=rx_data_t;
					end if;
				else rx_data_t<="00000000";led<='0';--未接收时,保持数据寄存器为'0'
				end if;
			end if;
		end process;
		
	process(clk, rst_n, baud_cnt, rx_cnt)--给接收完成信号和接收到的数据赋值
		begin
			if(rising_edge(clk)) then
				if(rst_n='0') then
					uart_rx_done<='0';
					uart_rx_data<="00000000";
				--结束一帧接收条件,在BAUD_CNT_MAX/2 -1处是因为此时数据源已经接收完毕,同时可以给检测下一帧数据的起始位留出充足时间
				--预留出时间可以使数据寄存器的值尽快清零等等,但在BAUD_CNT_MAX内任意合理时间都行,但要注意rx_flag的置位时间
				elsif(rx_cnt=9 and baud_cnt=BAUD_CNT_MAX/2 - 1) then
					uart_rx_done<='1';--结束输出信号
					uart_rx_data<=rx_data_t;--数据寄存器的数据输出给其他设备或存储器
				else	uart_rx_done<='0';
				    uart_rx_data<=uart_rx_data;--没结束时数据保持
				end if;
			end if;
		end process;
		
end behav;

四、UART通信发射机

图4  串口发送模块接口框图 & 串口发送模块端口与功能描述


代码如下:

library ieee;
use ieee.std_logic_1164.all;
--UART通信发射机
entity uart_tx is
	port(
			clk:in std_logic;--系统时钟
			rst_n:in std_logic;--复位(低电平有效)
			uart_tx_en:in std_logic;--发送端使能(高电平有效,无n后缀都为高电平有效)
			uart_tx_data:in std_logic_vector(7 downto 0);--数据输入端口(并行->串行)
			uart_txd:out std_logic;--数据输出端口(串行->并行)
			led:out std_logic;--传输提示灯
			uart_tx_busy:buffer std_logic);--传输忙 输出端口
end uart_tx;

architecture behav2 of uart_tx is
constant CLK_FREQ: integer:=50_000_000;--系统时钟频率
constant UART_BPS:integer:=9_600;--通信波特率
constant BAUD_CNT_MAX:integer:=(CLK_FREQ/UART_BPS);--每波特率对应需要的系统时钟计数
signal tx_cnt:integer range 0 to 9:=0;--已发送数据计数寄存器
signal baud_cnt:integer range 0 to BAUD_CNT_MAX:=0;--波特率的系统时钟计数寄存器
signal tx_data_t:std_logic_vector(7 downto 0):="00000000";--待发送数据寄存器
begin
	process(clk, rst_n, uart_tx_en)--忙信号和待发送数据寄存器赋值
		begin
			if(rising_edge(clk)) then
				if(rst_n = '0') then
					tx_data_t<="00000000";
					uart_tx_busy<='0';
				elsif(uart_tx_en='1') then--允许发送信号使能时,待发送数据寄存器赋值,忙信号拉高
					tx_data_t<=uart_tx_data;
					uart_tx_busy<='1';
				elsif(tx_cnt=9 and baud_cnt=BAUD_CNT_MAX- 1) then--当发射数据完毕停止位发送到一定程度时待发送数据寄存器置为'0',忙信号置位为'0'
					tx_data_t<="00000000";
					uart_tx_busy<='0';
				else tx_data_t<=tx_data_t;--未结束/开始发送时,待发送数据寄存器数据保持,忙信号保持
					uart_tx_busy<=uart_tx_busy;
				end if;
			end if;
		end process;
		
		process(clk, rst_n, uart_tx_en, uart_tx_busy)--波特率计数器赋值,同uart_rx
			begin
				if(rising_edge(clk)) then
					if(rst_n='0') then
						baud_cnt<=0;
					elsif(uart_tx_en='1') then
						baud_cnt<=0;
					elsif(uart_tx_busy='1') then
						if(baud_cnt<BAUD_CNT_MAX-1) then
							baud_cnt<=baud_cnt+1;
						else baud_cnt<=0;
						end if;
					else baud_cnt<=0;
					end if;
				end if;
			end process;
			
	process(clk, rst_n, baud_cnt) --发送数据计数器赋值,同样同uart_rx
		begin
			if(rising_edge(clk)) then
				if(rst_n='0') then
					tx_cnt<=0;
				elsif(uart_tx_en='1') then
					tx_cnt<=0;
				elsif(uart_tx_busy='1') then
					if(baud_cnt=BAUD_CNT_MAX-1) then
						tx_cnt<=tx_cnt+1;
					else tx_cnt<=tx_cnt;
					end if;
				else tx_cnt<=0;
				end if;
			end if;
		end process;	
		
	process(clk, rst_n, baud_cnt)--数据发送端发送数据
		begin
			if(rising_edge(clk)) then
				if(rst_n='0') then--复位时,输出一直为高电平
					uart_txd<='1';
					led<='0';
				elsif(uart_tx_busy='1') then
					led<='1';--发送数据时指示灯亮
					case tx_cnt is
						when 0=> uart_txd<='0';--一帧中的开始位为'0'
						when 1=> uart_txd<=tx_data_t(0);--8位数据位
						when 2=> uart_txd<=tx_data_t(1);
						when 3=> uart_txd<=tx_data_t(2);
						when 4=> uart_txd<=tx_data_t(3);
						when 5=> uart_txd<=tx_data_t(4);
						when 6=> uart_txd<=tx_data_t(5);
						when 7=> uart_txd<=tx_data_t(6);
						when 8=> uart_txd<=tx_data_t(7);
						when 9=> uart_txd<='1';--停止位为'1'
						when others=> uart_txd<='0';
					end case;
				else uart_txd<='1';led<='0';--未发送数据时,输出为高电平
				end if;
			end if;
		end process;
	end behav2;

五、引脚配置

板子是正点原子的新起点V2,芯片型号:EP4CE10F17C8N

六、实验演示

(具体的串口调试操作见资源)

操作视频链接:点击这里—B站


总结

欢迎各位分享自己的看法和指导批评,谢谢各位了>_<

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值