FPGA - RS232串口实现串口接收
FPGA集训第6天,尝试去完成用FPGA实现和串口助手完成接收上位机信息的指令过程。
串口接收的过程是 -串行数据转并行数据的过程
1帧=(1bit开始+8bit数据+1bit结束)
故输入除了时钟和复位之外,只需要一个接收数据线rx_data即可,输出则为一个8位并行输出uart_data和一个rx_done表示接收完成
本次实验是通过上位机去实现串口数据发送0000-1111来控制开发板的四个LED亮灭,故输出端口为4位LED端口。
module UART_RXD( input sysclk , input rst_n , input rx_data , /// 0 0010_1100 1 output reg [3:0] rx_led // output reg rx_done );
实验的难点在于根据时序图去设计代码来实现整个接收过程
跟随代码,我们来了解整个串口模块的设计流程
开发板的系统时钟为125MHZ,配置的波特率为115200,计算结果DELAY为该波特率每一位数据的发送时间
////////////参数 parameter SYSCLK = 125_000_000 ; parameter BAUD = 115_200 ; localparam DELAY = SYSCLK/BAUD ; //该波特率下最大不能超过的CNT值,超过则结果不对 125 000 000 ns / 115200 (bit/ns) = 1085ns,即每一位的最大发送时间为1085ns localparam MID = DELAY/2 ;
一些后面的代码需要的参数
reg [11:0] cnt ; //用来记录发送每一位数据的当前时间(ns为单位),最大值为 DELAY-1 reg [3:0] cnt_bit ; //bit数计数 0-9 记录接收了几位数据,总计10位 reg rx_flag ; ///使能信号 接收使能 ////////////////////////产生开始信号(二级寄存) ------二级寄存是用来判断上升沿和下降沿所使用的 reg rx_reg1 ; //记录当前rx_data的电平 reg rx_reg2 ; //记录上一个时刻rx_reg1的电平 wire start_flag ; //模块工作使能位 reg [7:0] uart_data ; ///串口的接收是从低位到高位的 如发送来的数据是 8h17 0001 0111 对应接收时接收顺序为 1110 1000 即从低位开始 对应uart_data从 uart_Data[0] -> uart_data[1] reg rx_done; //接收完成标志
只需要记住一点,Verilog中上升沿和下降沿的判断实现通过寄存器即可判断出来
//模块工作标志位设置(判断是否有数据进来,即有没有下降沿的到来) always@(posedge sysclk) if(!rst_n)begin rx_reg1 <= 1; rx_reg2 <= 1; end else begin rx_reg1 <= rx_data; rx_reg2 <= rx_reg1; end assign start_flag = ~rx_reg1 & rx_reg2; //识别到下降沿开始接收,识别到下降沿后模块开始工作,工作标志位拉高
//接收开始标志位,当模块开始工作时,开启接收(至于为什么要区分出来模块工作使能和接收使能,大概是因为后面还有发送使能,能够区分开吧
rx_flag拉高条件:start_flag为高
rx_flag拉低条件:接收满10bit且cnt计数达到中值
//接收开始标志位设置 always@(posedge sysclk) if(!rst_n) rx_flag <= 0; else if(start_flag) //模块工作,开始接收 rx_flag <= 1; else if(cnt_bit == 9 && cnt == MID - 1) //接收到10位数据时停止接收工作 , cnt计数达到MID处是因为此处数据比较稳定 rx_flag <= 0; else rx_flag <= rx_flag ; //其它时刻保持稳定
clk_cnt计数条件:当开始接收时开始计数停止计数
clk_cnt停止条件:当没在接收时停止计数清0,或者计数满DELAY值时也需要清0
/////cnt不断自增判断当前执行时间 always@(posedge sysclk) if(!rst_n) cnt <= 0; else if(rx_flag)begin if(cnt == DELAY - 1) cnt <= 0; else cnt <= cnt + 1; end else ///rx_flag == 0 cnt <= 0;
图里的rx_cnt对应的就是cnt_bit,即当前接收了几位数据
cnt_bit计数条件:当达到DELAY - 即计一个数的最大值
cnt_bit停止条件:当计满10个数据
///////cnt_bit:记录当前已经接收到几个数据 always@(posedge sysclk) if(!rst_n) cnt_bit <= 0; else if(rx_flag)begin if(cnt == DELAY - 1)begin //每达到最大时间认定为接收1位数据 if(cnt_bit == 9) //计满清0 cnt_bit <= 0; else cnt_bit <= cnt_bit + 1; end else cnt_bit <= cnt_bit; //如果还没到DELAY,保持不变 end else //如果没有在接收,清0 cnt_bit <= 0; ///0:起始位 1-8:数据位 9:停止位
在有数据到时接收数据到uart_data中,其它时刻都是0
//接收端串行转并行数据 always@(posedge sysclk) if(!rst_n) uart_data <= 0; else if(rx_flag)begin if(cnt_bit > 0 && cnt_bit < 9 && cnt == MID - 1) ///1-8 因为第0,9位分别是开始位和停止位,故不需要接收,我们只要1-8的数据位 uart_data[cnt_bit - 1] <= rx_data ; /////先传最低位 【0】 再到【1】,2,3.....【7】 else uart_data <= uart_data; //在开始位和停止位保持不变即可 end else uart_data <= 0; //没有接收时清0
uart_done对应rx_done,在接收到第9位即最后一位数据后拉高表示接收结束
//接收完成标志位设置 always@(posedge sysclk) if(!rst_n) rx_done <= 0; else if(rx_flag)begin if(cnt_bit == 9 && cnt == MID/2 - 1) //MID/2处置高还是因为该时刻数据稳定,且避免和前面rx_flag处冲突,这样会更稳定?我不太清楚....... rx_done <= 1; else rx_done <= 0; end else rx_done <= 0;
在接收完成之后,将接收到数据的前4位发给LED,控制LED的亮灭。主要用来判断接收是否实现。
always@(posedge sysclk)begin if(!rst_n) rx_led <= 4'd0; else if(rx_done) begin rx_led[0] <= uart_data[0]; rx_led[1] <= uart_data[1]; rx_led[2] <= uart_data[2]; rx_led[3] <= uart_data[3]; end else rx_led <= rx_led; end
完整代码如下:
`timescale 1ns / 1ps ///////串口接收端 module UART_RXD( input sysclk , input rst_n , input rx_data , /// 0 0010_1100 1 output reg [3:0] rx_led // output reg rx_done ); ////////////参数 parameter SYSCLK = 125_000_000 ; parameter BAUD = 115_200 ; localparam DELAY = SYSCLK/BAUD ; //该波特率下最大不能超过的CNT值,超过则结果不对 localparam MID = DELAY/2 ; reg [11:0] cnt ; reg [3:0] cnt_bit ; ////bit数计数 0-9 reg rx_flag ; ///使能信号 ////////////////////////产生开始信号(二级寄存) reg rx_reg1 ; reg rx_reg2 ; wire start_flag ; reg [7:0] uart_data ; ///8'h34 = 8'b0011_0100 reg rx_done; //模块工作标志位设置 always@(posedge sysclk) if(!rst_n)begin rx_reg1 <= 1; rx_reg2 <= 1; end else begin rx_reg1 <= rx_data; rx_reg2 <= rx_reg1; end assign start_flag = ~rx_reg1 & rx_reg2; //识别到上升沿开始接收 //接收开始标志位设置 always@(posedge sysclk) if(!rst_n) rx_flag <= 0; else if(start_flag) rx_flag <= 1; else if(cnt_bit == 9 && cnt == MID - 1) rx_flag <= 0; else rx_flag <= rx_flag ; /////cnt不断自增判断当前执行时间 always@(posedge sysclk) if(!rst_n) cnt <= 0; else if(rx_flag)begin if(cnt == DELAY - 1) cnt <= 0; else cnt <= cnt + 1; end else ///rx_flag == 0 cnt <= 0; ///////cnt_bit:记录当前已经接收到几个数据 always@(posedge sysclk) if(!rst_n) cnt_bit <= 0; else if(rx_flag)begin if(cnt == DELAY - 1)begin //每达到最大时间认定为接收1位数据 if(cnt_bit == 9) cnt_bit <= 0; else cnt_bit <= cnt_bit + 1; end else cnt_bit <= cnt_bit; end else cnt_bit <= 0; ///0:起始位 1-8:数据位 9:停止位 //////// 8 rx_data 1 数据的采集 uart_data[cnt_bit] <= rx_data ; //接收端串行转并行数据 always@(posedge sysclk) if(!rst_n) uart_data <= 0; else if(rx_flag)begin if(cnt_bit > 0 && cnt_bit < 9 && cnt == MID - 1) ///1-8 uart_data[cnt_bit - 1] <= rx_data ; /////先传最低位 else uart_data <= uart_data; end else uart_data <= 0; //接收完成标志位设置 always@(posedge sysclk) if(!rst_n) rx_done <= 0; else if(rx_flag)begin if(cnt_bit == 9 && cnt == MID/2 - 1) rx_done <= 1; else rx_done <= 0; end else rx_done <= 0; always@(posedge sysclk)begin if(!rst_n) rx_led <= 4'd0; else if(rx_done) begin rx_led[0] <= uart_data[0]; rx_led[1] <= uart_data[1]; rx_led[2] <= uart_data[2]; rx_led[3] <= uart_data[3]; end else rx_led <= rx_led; end endmodule
引脚约束文件
set_property PACKAGE_PIN M14 [get_ports {rx_led[3]}] set_property PACKAGE_PIN N16 [get_ports {rx_led[2]}] set_property PACKAGE_PIN P14 [get_ports {rx_led[1]}] set_property PACKAGE_PIN R14 [get_ports {rx_led[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {rx_led[0]}] set_property PACKAGE_PIN U5 [get_ports rst_n] set_property PACKAGE_PIN Y9 [get_ports rx_data] set_property PACKAGE_PIN H16 [get_ports sysclk] set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property IOSTANDARD LVCMOS33 [get_ports rx_data] set_property IOSTANDARD LVCMOS33 [get_ports sysclk]
实验结果
串口调试助手输入数字’7‘,成功点亮3个LED (0111)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了