串口发送模块
模块设计
输入信号:
1、Clk、Rst_n:时钟、复位信号;
2、data_byte:待发送的数据,由外部模块传送进来;
3、Send_En:发送控制信号,表明待发送的数据已经稳定,可以进行发送;
4、baud_Set:波特率设置端口,增加模块的通用性,将波特率的计数值做成参数形式。
输出信号:
1、Rs232_Tx:发送出去的信号;
2、Tx_Done:一帧数据发送完毕的标志信号;
3、uart_state:串口模块处于发送数据状态or空闲态标志信号。
总体实现:按照约定的波特率,将一帧数据一位一位的发送出去。
如下图,BPS_CLK即为发送数据的波特率时钟,检测到一个脉冲,从起始位开始发送数据直至停止位。
所以有两个要实现的组件:波特率时钟产生组件、数据发送组件
1、发送波特率时钟产生;
若以9600波特率发送数据,每隔5208个系统时钟周期产生一次脉冲。
那么问题来了,具体从5208个时钟周期内哪一个周期开始发送第一位数据,然后接着又等待5208个周期发送第二位数据…实际上可以在这个区间上的任意值开始发送第一位数据【边界除外】,等到下一次计数到这个值,发送第二位数据,经历的间隔都是5208个系统时钟周期。
// 增加模块的通用性,将波特率的计数值做成参数形式
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_DR <= 16'd5207;
else begin
case(Baud_Set)
0:bps_DR <= 16'd5207;//9600bps
1:bps_DR <= 16'd2603;//19200bps
2:bps_DR <= 16'd1301;//38400bps
3:bps_DR <= 16'd867; //57600bps
4:bps_DR <= 16'd433; //115200bps
default:bps_DR <= 16'd5207;//9600bps
endcase
end
// 分频计数
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Div_cnt <= 16'd0;
else if(Div_cnt == bps_DR)
Div_cnt <= 16'd0;
else
Div_cnt <= Div_cnt + 1'b1;
// 产生波特率时钟,即BPS_CLK
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_clk <= 1'b0;
else if(Div_cnt == 16'd1) // 每当计数到1产生一个系统时钟周期的脉冲
bps_clk <= 1'b1;// 无论计数到几,传输数据满足对应的波特率就行【野火教程有详细说明】
else
bps_clk <= 1'b0;
2、数据发送组件
我们已经得到波特率时钟,每来一个脉冲就发送一位数据;由于一共发送一帧10位数据,如果知道当前脉冲是第几个,那么就发送第几位数据到Tx端,使用计数替代计时的思想:每来一个波特率时钟脉冲,进行计数一次
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_cnt <= 1'b0;
else if(bps_cnt == 4'd11)// 当计数到10,才开始发送停止位,所以计数到11才发送完整的一帧数据
bps_cnt <= 1'b0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
根据计数值bps_cnt可确定传输哪一位数据
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Rs232_Tx <= 1'b1; // 空闲状态
else begin
case(bps_cnt) // 传输10位数据:一帧
4'd0: Rs232_Tx <= 1'b1;//有它的作用,两帧数据之间留出的空闲位
4'd1: Rs232_Tx <= 1'b0;//起始位
4'd2: Rs232_Tx <= r_data_byte[0];//先传输低位
4'd3: Rs232_Tx <= r_data_byte[1];
4'd4: Rs232_Tx <= r_data_byte[2];
4'd5: Rs232_Tx <= r_data_byte[3];
4'd6: Rs232_Tx <= r_data_byte[4];
4'd7: Rs232_Tx <= r_data_byte[5];
4'd8: Rs232_Tx <= r_data_byte[6];
4'd9: Rs232_Tx <= r_data_byte[7];
4'd10: Rs232_Tx <= 1'b1;//停止位
default:Rs232_Tx <= 1'b1;
endcase
end
这里与野火教程区别是分频计数器在一开始就计数,没有触发使能计数条件,不知道数据是否处于稳态就开始计数,一般串口发送的数据都来自串口接收模块,在接收模块中自己会判断确定数据稳定后才能被外部模块使用。
波形分析
1、
在一开始复位后Div_cnt就开始计数,计数到1;到第一个系统时钟上升沿,检测到Div_cnt为1,拉高bps_clk;
到第二个上升沿,检测到bps_clk为高,bps_cnt开始计数;
等到第三个上升沿,检测bps_cnt为1;等到第四个上升沿,Rs232_Tx为0生效,即发送了起始位。
2、
第一个上升沿检测Div_cnt计数到1;
非阻塞赋值,第二个上升沿bps_clk才为高电平,且开始bps_cnt <= bps_cnt + 1’b1,只进行加1操作;
第三个上升沿才进行bps_cnt <= bps_cnt + 1’b1的赋值操作,bps_cnt为2,同时Rs232_Tx <= r_data_byte[0],下一个时钟沿才会被赋值;
第四个上升沿,Rs232_Tx <= r_data_byte[0]赋值操作,r_data_byte[0]为1,Rs232_Tx变为高电平。
3、
计数到11,在第二个时钟沿才赋值Tx_Done <= 1’b1,且赋值bps_cnt <= 1’b0,bps_cnt重新赋为0,uart_state为0,处于空闲状态
串口接收模块
前言
串口接收的往往是外部环境中数据,在工业环境存在强电干扰,导致通信线上的电平发送变化,对于数据的接收进行处理保证通信稳定。
方法一:
对接收的每位数据只在中间位置采样一次作为该数据的电平状态,如上图,产生波特率时钟BPS_CLK,计数到中间位置时,采样当前的电平。其中BPS_CLK产生原理与串口接收模块相同,只不过每次计数
方法二:
方法一只采样一次很可能恰好采样到被干扰的信号,导致出错。那就多采样几次求概率的方式进行状态判定!
如上图,对接收的每位数据都分成16份,其中舍去两边的10份,只采样中间的6份,对每次采样结果进行累加,高电平数量大于3,则采用结果为高电平,否则低电平。
这里有一个巧妙的处理,将累加结果存储到3位宽的寄存器中,取最高位,若最高位为1,说明采样结果为高电平,最高位为0,则为低电平。
模块设计
输入信号:
1、Clk、Rst_n
2、Baud_Set:波特率设置端口,增加模块的通用性,将波特率的计数值做成参数形式;
3、Rs232_Rx:外部输入信号,要进行接收的串口数据。
输出信号:
1、Data_Byte:并行数据输出;
2、Rx_Done:接收数据完成标志信号。
关键是如何正确的接收串行数据并将其转换为并行数据输出。
接收串行数据设计
与串口发送模块设计思想一样,计数替代计时,产生波特率时钟,每产生一个波特率时钟脉冲计数一次,然后采样一次当前的电平状态,唯一不同的是每位数据采样16次,取中间的16次采样结果,采样频率是发送模块的16倍。
设计流程:
1、接收的是外部信号,避免亚稳态,先进行同步处理:使用两级缓存
2、起始信号是低电平,检测下降沿,判断是否是数据帧的起始信号
3、产生波特率时钟,每产生一个脉冲,采样一次输入信号
4、对波特率时钟脉冲进行计数,根据计数值可知当前是第几次采样
5、根据计数值,将每位对应的6次结果累加,
1、同步处理
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
s0_Rs232_Rx <= 1'b0;
s1_Rs232_Rx <= 1'b0;
end
else begin
s0_Rs232_Rx <= Rs232_Rx;//第一级寄存器稳定输出概率70%~80%
s1_Rs232_Rx <= s0_Rs232_Rx;//第二级寄存器稳定输出的概率99%
end
2、起始位检测
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
tmp0_Rs232_Rx <= 1'b0;
tmp1_Rs232_Rx <= 1'b0;
end
else begin
tmp0_Rs232_Rx <= s1_Rs232_Rx; //只有s1_Rs232_Rx是稳定信号,所以数据稳定后再进行两级寄存得到边沿
tmp1_Rs232_Rx <= tmp0_Rs232_Rx;//两级寄存器
end
assign nedge = ~tmp0_Rs232_Rx & tmp1_Rs232_Rx; //满足起始位检测
3、产生波特率时钟脉冲bps_clk
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_DR <= 16'd324;
else begin
case(Baud_Set)
0: bps_DR <= 16'd324;//9600bps
1: bps_DR <= 16'd162;//19200bps
2: bps_DR <= 16'd80;//38400bps
3: bps_DR <= 16'd53;//57600bps
4: bps_DR <= 16'd26;//115200bps
default:bps_DR <= 16'd324;
endcase
end
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Div_cnt <= 16'd0;
else if(uart_state)begin
if(Div_cnt == bps_DR)
Div_cnt <= 16'd0;
else
Div_cnt <= Div_cnt + 1'b1;
end
else
Div_cnt <= 16'd0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_clk <= 1'b0;
else if(Div_cnt == 16'd1)
bps_clk <= 1'b1;//比如9600波特率来说,分频计数此时的1到下一次的1计数325次
else //在波特率时钟计数以此来计数,bps_clk来临16次才传输一位数据
bps_clk <= 1'b0;
4、波特率时钟脉冲计数
//波特率时钟计数
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_cnt <= 8'd0;
else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 &&(START_BIT > 2)))//还有考虑检测起始位发生错误,是一次干扰,并不是真正的数据传输
bps_cnt <= 8'd0; //波特率时钟计数清零,使接收数据模块停止下来,防止干扰后续的数据接收
else if(bps_clk) //而接收停止位错误不用处理
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
5、累加采样结果
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;//复位所有数据都清空,共8个存储单元
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
else if(bps_clk)begin//6次值中,每个值不止出现一次,只有在出现bps_clk高电平时对应的值才有效
case(bps_cnt) //bps_cnt的值并不持续一个时钟周期,6次值中,每个值不止出现一次
0:begin //bps_cnt计数到0时所以寄存器进入全0状态,方便下一次进行数据采集
START_BIT <= 3'd0;
r_data_byte[0] <= 3'd0;//复位所有数据都清空,共8个存储单元
r_data_byte[1] <= 3'd0;
r_data_byte[2] <= 3'd0;
r_data_byte[3] <= 3'd0;
r_data_byte[4] <= 3'd0;
r_data_byte[5] <= 3'd0;
r_data_byte[6] <= 3'd0;
r_data_byte[7] <= 3'd0;
STOP_BIT <= 3'd0;
end
6,7,8,9,10,11: START_BIT <= START_BIT + s1_Rs232_Rx;
22,23,24,25,26,27: r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;//接收第一个数据位,
38,39,40,41,42,43: r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;//此时根据波特率计数s1_Rs232_Rx已经处于第1个数据位区间
54,55,56,57,58,59: r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
70,71,72,73,74,75: r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
86,87,88,89,90,91: r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
102,103,104,105,106,107: r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
118,119,120,121,122,123: r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
134,135,136,137,138,139: r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
150,151,152,153,154,155: STOP_BIT <= STOP_BIT + s1_Rs232_Rx;//停止位
default:;
endcase
end
串转并数据输出
将每位的采样电平转化为并行输出
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Data_Byte <= 8'b0;
else if(bps_cnt == 8'd159)begin //虽然计数160次,但是接收的数据寄存在r_data_byte保持稳定不变。计数160表明一帧数据接收完毕
Data_Byte[0] <= r_data_byte[0][2];//6次采样叠加大于3,说明高电平多,大概率是高电平,只要大于3第3位就为,小于3为0,只需判断第3位就行
Data_Byte[1] <= r_data_byte[1][2];
Data_Byte[2] <= r_data_byte[2][2];
Data_Byte[3] <= r_data_byte[3][2];
Data_Byte[4] <= r_data_byte[4][2];
Data_Byte[5] <= r_data_byte[5][2];
Data_Byte[6] <= r_data_byte[6][2];
Data_Byte[7] <= r_data_byte[7][2];//r_data_byte是有8个存储单元,每个单元3位的二维存储器,第8个存储单元的第二位
end
else
Data_Byte <= Data_Byte;
参考
小梅哥教程:【新版学习教材】FPGA自学笔记——设计与验证公开版
野火教程:征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2021.7.10(上)