客户提出网站建设申请,益阳网站建设益阳,营销网站建设价格,贵阳做网站串口相关简介
UART 在发送或接收过程中的一帧数据由4部分组成#xff0c;起始位、数据位、奇偶校验位和停止位#xff0c;如图所示。其中#xff0c;起始位标志着一帧数据的开始#xff0c;停止位标志着一帧数据的结束#xff0c;数据位是一帧数据中的有效数据。
通常用…串口相关简介
UART 在发送或接收过程中的一帧数据由4部分组成起始位、数据位、奇偶校验位和停止位如图所示。其中起始位标志着一帧数据的开始停止位标志着一帧数据的结束数据位是一帧数据中的有效数据。
通常用的串口数据帧格式是8位数据位无校验位1位停止位。 所以一帧数据有10个bit1bit起始位8bit数据位1bit停止位。
关于串口波特率
串口波特率是指串口通信的速率它表示每秒传输二进制数据的位数单位是bps位 /秒常用的波特率有9600、19200、38400、57600以及115200等。
FPGA如何通过系统时钟来得到串口波特率呢 已知串口时钟为50Mhz 50000000hz生成串口波特率为9600。 那么我们需要在FPGA里构建两个计数器。一个系统时钟计数器用来计数系统时钟周期数一个串口时钟计数器用来计数对应串口波特率的时钟周期数。 系统时钟sys_clk系统时钟计数值sys_cnt。 串口时钟计数值tx_cnt。 当sys_cnt 50000000 / 9600时tx_cnt计数一次相当于串口时钟的一个周期。
数据是怎么发送出去的呢 由上一个问题的解答已经得到了【串口时钟】这么一个东西那么发送数据都是在该时钟下进行发送。 按照前面所提到的数据格式【1bit起始位8bit数据位1bit停止位】那么就可以知道发送一个8位的数据需要在10个串口时钟周期下将10bit的数据对应好时序一个个发出去。
数据是怎么接收的呢 类似于上个问题只需要按照时序检测起始位接收数据再检测停止位即可。
那么这样是否就能够搭建好串口模块呢 答案明显是不能的FPGA并不知道什么时候该发数据什么时候该收数据什么时候进入发送状态什么时候脱离发送状态。这时候还要添加其他信号来打辅助。比如添加发送使能信号来确定什么时候发数据添加状态信号来表示串口是空闲还是忙碌等一系列的状态。
串口发送子模块的搭建
串口发送子模块相关参数
module uart_send(input sys_clk, //系统时钟input sys_rst_n, //系统复位低电平有效//由其他模块输入input uart_en, //发送使能信号input [7:0] uart_din, //待发送数据//输出给其他模块output uart_tx_busy, //发送忙状态标志output reg tx_flag, //发送过程标志信号output reg [ 7:0] tx_data, //寄存发送数据output reg [ 3:0] tx_cnt, //发送数据计数器output reg uart_txd //UART发送端口即tx引脚);//parameter defineparameter CLK_FREQ 50000000; //系统时钟频率parameter UART_BPS 9600; //串口波特率//为得到指定波特率对系统时钟计数BPS_CNT次localparam BPS_CNT CLK_FREQ/UART_BPS; //reg definereg uart_en_d0; reg uart_en_d1; reg [15:0] clk_cnt; //系统时钟计数器//wire definewire en_flag;在该子模块中的参数可分为三个部分
系统时钟和复位。由其它模块发送来的使能信号和待发送的数据。本模块正在执行功能时的标志信号本模块的数据接口相关计数器以及UART串口TX的物理IO口。
根据这些参数可以大致知道串口发送1个bit数据的流程
首先模块通过uart_en收到发送使能信号则首先拉高tx_flag信号同时uart_tx_busy会接受tx_flag的值。当uart_en拉高后en_flag会获取一个脉冲当检测到这个脉冲后则说明串口进入发送状态。进入发送过程后要做三件事寄存待发送的数据启动系统时钟计数器启动发送数据计数器。发送数据计数器开始计数直到计数值等于9时会自动清零计数器一次计数循环即意味着一个8bit的数据发送完。在发送过程中[7:0]tx_data要将8个bit的数据从低位到高位传给[1:0]uart_txd首先发送一个低电平作为起始位然后发送tx_data的8个bit最后发送一个高电平表示停止位。经过这么一个过程一个8位数据就通过FPGA串口的TX引脚发送出去了。
发送子模块中的逻辑块
获取使能信号脉冲
//捕获uart_en上升沿得到一个时钟周期的脉冲信号
assign en_flag (~uart_en_d1) uart_en_d0;//对发送使能信号uart_en延迟两个时钟周期
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_en_d0 1b0; uart_en_d1 1b0;end else begin uart_en_d0 uart_en; uart_en_d1 uart_en_d0; end
end通过两次寄存和取反与等操作将高电平信号转化为一个脉冲信号作为使能信号。
进入发送过程和退出发送过程
//当脉冲信号en_flag到达时,寄存待发送的数据并进入发送过程
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin tx_flag 1b0;tx_data 8d0;end else if (en_flag) begin //检测到发送使能上升沿 tx_flag 1b1; //进入发送过程标志位tx_flag拉高tx_data uart_din; //寄存待发送的数据end//计数到停止位结束时停止发送过程并提前1/16个串口时钟else if ((tx_cnt 4d9) (clk_cnt BPS_CNT - (BPS_CNT/16))) begin tx_flag 1b0; //发送过程结束标志位tx_flag拉低tx_data 8d0;endelse begintx_flag tx_flag;tx_data tx_data;end
end两个计数器
计数器的作用是保证数据按照特定波特率被发送出去
//进入发送过程后启动系统时钟计数器
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) clk_cnt 16d0; else if (tx_flag) begin //如果处于发送过程if (clk_cnt BPS_CNT - 1)clk_cnt clk_cnt 1b1;elseclk_cnt 16d0; //系统时钟计数一个波特率周期后清零endelseclk_cnt 16d0; //发送过程结束
end//进入发送过程后启动发送数据计数器
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) tx_cnt 4d0;else if (tx_flag) begin //处于发送过程if (clk_cnt BPS_CNT - 1) //对系统时钟计数达一个波特率周期tx_cnt tx_cnt 1b1; //此时发送数据计数器加1elsetx_cnt tx_cnt; endelse tx_cnt 4d0; //发送过程结束
end将8bit数据按bit拆分并按顺序赋值给tx端口
//根据发送数据计数器来给uart发送端口赋值
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) uart_txd 1b1; else if (tx_flag)case(tx_cnt)4d0: uart_txd 1b0; //起始位 4d1: uart_txd tx_data[0]; //数据位最低位4d2: uart_txd tx_data[1];4d3: uart_txd tx_data[2];4d4: uart_txd tx_data[3];4d5: uart_txd tx_data[4];4d6: uart_txd tx_data[5];4d7: uart_txd tx_data[6];4d8: uart_txd tx_data[7]; //数据位最高位4d9: uart_txd 1b1; //停止位default: ;endcaseelse uart_txd 1b1; //空闲时发送端口为高电平
end发送字符串模块
子模块写好后可以再做一个顶层模块用来发送字符串。 发送字符串的思路 当满足某个条件后使能串口发送信号此时串口就开始发送数据了当在串口发送数据的时候串口就处在发送忙状态此时我们就可以更新一次待发送的数据。为什么可以在发送的时候更新待发送数据呢因为在进入发送过程后数据还没发送前的时间里待发送的数据就已经寄存在了子模块里的一个寄存器里了。这样就节约了数据更新的时间了。 相关代码如下 module uart_loopback_top(input sys_clk, //外部50M时钟input sys_rst_n, //外部复位信号低有效output uart_txd //UART发送端口);//parameter define
parameter CLK_FREQ 50000000; //定义系统时钟频率
parameter UART_BPS 115200; //定义串口波特率//wire define
reg [7:0] uart_send_data; //UART发送数据
wire uart_tx_busy; //UART发送忙状态标志
reg uart_send_en; //UART发送使能
reg [31:0] data_cnt;reg send_d0;
reg send_d1;
wire send_en;
reg string_end; //停止字符串发送reg [ 31:0] Data_Count; //字符计数器
parameter [31:0] Data_Len32d10; //字符串的长度
reg [7:0] arry [Data_Len-1:0]; //定义要发送的字符串//初始化字符串
initial beginarry[0] H;arry[1] e;arry[2] l; arry[3] l; arry[4] o; arry[5] W; arry[6] o; arry[7] r; arry[8] l;arry[9] d;
end//使能数据更新位获得串口数据更新的脉冲
assign send_en (~send_d1) send_d0;//数据更新标志寄存两次uart_send_en的数据为构成发送使能脉冲做准备
always (posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginsend_d0 1b0;send_d1 1b0;end else begin send_d0 uart_send_en;send_d1 send_d0; end
end//串口发送字符串
always (posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_send_en 1d0; //初始化串口发送使能data_cnt 32d0; //初始化数据计数器uart_send_data arry[0]; //初始化首字符string_end 1d0; //控制字符串发送结束end else begin//当串口处于空闲状态且数据计数器小于10的时候//条件data_cnt Data_Len 不要也可以//条件必须满足TX空闲字符串发送结束标志位0可在此基础上添加其它条件if((~uart_tx_busy) (data_cnt Data_Len) (string_end 1b0)) begin //使能串口发送使能寄存器uart_send_en 1b1; end//当数据计数器大于等于字符串长度时if(data_cnt Data_Len) beginstring_end 1b1;uart_send_en 1b0; //失能串口发送使能停止串口发送数据data_cnt 32b0;end//如果数据发送使能脉冲到来else if((send_en) (data_cnt Data_Len)) begin data_cnt data_cnt 32b1; //则数据计数器加一end//如果串口有数据在发送,并且结束位没有拉高else if((uart_tx_busy) (string_end 1b0)) begin//则失能串口发送使能为下一个数据的发送做准备uart_send_en 1b0; uart_send_data arry[data_cnt];endend
end//串口发送模块
uart_send #( .CLK_FREQ (CLK_FREQ), //设置系统时钟频率.UART_BPS (UART_BPS)) //设置串口发送波特率
u_uart_send( .sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.uart_en (uart_send_en),.uart_din (uart_send_data),.uart_tx_busy (uart_tx_busy),.uart_txd (uart_txd));//例化ILA IP核
//ila_0 your_instance_name (
// .clk(sys_clk), // input wire clk
//
// .probe0(uart_send_en), // input wire [0:0] probe0
// .probe1(data_cnt), // input wire [7:0] probe1
// .probe2(uart_tx_busy), // input wire [0:0] probe2
// .probe3(send_d0),
// .probe4(send_d1),
// .probe5(send_en),
// .probe6(uart_send_data),
// .probe7(string_end)
//);endmodule进一步的改进
如果要发送多个字符串可以在更新字符串数据时加入状态机进行不同字符串的转换。