1 常见协议种类
UART - 通用串口异步协议
IIC - 双向两线总线
SPI - 串口外行外围总线
USB2.0/3.0 通用串行总线
Ethernet 以太网
本次以UART (通用串行总线协议来)说明程序设计和基础原理
2 RS232 通信标准
RS-232标准中,最常用的配置是8N1(8个数据位,无奇偶校验位,一个停止位)
下图为uart中发送一个字节的时序图
3设计框图
端口信号设计见下图,图中左边为输入信号,右侧是输出信号
系统子模块的结构图,绿色部分表示单一的寄存器
各个子模块之间的描述和作用
波特率时钟生成模块
波特率是如何计算: 举例 :假设系统时钟频率为50mhz 系统的周期就是20ns,假定波特率为9600hz 波特率周期则为104167ns,波特率分频计数值 104167 / 20 = 5208 从0开始计数 ,也就是9600hz波特率下面, 要传输1bit的信号,50mhz的系统时钟需要计数 5208 -1 次 ,下图为四种常见的波特率时钟的计数方法。
LUT查找表模块
/LUT查找表模块,查找匹配的波特率分频计数最大值,根据波特率确定每1s钟传输的最大为比特数,选择波特率
always@(posedge clk or posedge rst_n)
if ( rst_n ) begin
bps_DR <= 16'd5207 ;
end
else begin
case ( baud_set)
0 : bps_DR <= 16'd5207 ;
1 : bps_DR <= 16'd2603 ;
2 : bps_DR <= 16'd1301 ;
3 : bps_DR <= 16'd867 ;
4 : bps_DR <= 16'd433 ;
default : bps_DR <= 16'd5207 ;
endcase
end
计数器生成波特率时钟模块
本模块设计:先进行分频计数模块,确定对应的波特率之后,计数到相对应的波特率的最大值计数
bps_DR ; 然后设计波特率时钟更新模块。div_cnt计数器梅重新计数到1的时候波特率时钟进行更新
// 分频计数模块
always @ (posedge clk or posedge rst_n) begin
if (rst_n) begin
div_cnt <= 16'd0 ;
end
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 ;
end
//bps_clk_reflash module
always @ (posedge clk or posedge rst_n) begin
if (rst_n) begin
bps_clk = 0;
end
else if (div_cnt == 16'd1 ) begin
bps_clk = 1 ; //开始计数的时候更新时钟上升沿
end
else
bps_clk = 0 ; //其他状态均为下降沿
end
数据输出模块设计
数据传输计数模块 ,一共传输10个数,其中包括 start stop 还有 8位的data数据位。每到一个bps_clk时钟的上升沿,计数加一 。tx_done发送完成,在计数值到11的时候,说明发送完成此时拉高tx_done电平。发送状态,当send_en使能的时候表示数据正处在发送状态,当bps_cnt计数值更新到11的时候表明数据停止发送,此时数据发送态uart_state拉低 。数据寄存一拍是等待数据达到稳定的状态。
//bps counter module
always@(posedge clk or posedge rst_n) begin
if(rst_n) begin
bps_cnt <= 4'd0 ;
end
else if ( bps_cnt == 4'd11) //计数到最后一个数的值
bps_cnt <= 4'd0 ;
else if ( bps_clk)
bps_cnt <= bps_cnt + 1 ;
else
bps_cnt <= bps_cnt ;
end
// tx_done module
always @ (posedge clk or posedge rst_n) begin
if (rst_n) begin
tx_done <= 1'b0 ;
end
else if ( bps_cnt == 4'd11 ) begin
tx_done <= 1'b1 ; //等于11的时候说明发送完成
end
else begin
tx_done <= 1'b0 ;
end
end
//传输模块,发送状态位模块
always@(posedge clk or posedge rst_n)
if(rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)
uart_state <= 1'b0;
else
uart_state <= uart_state;
//寄存一拍数据等待稳定
always@(posedge clk or posedge rst_n)
if (rst_n ) begin
data_byte_reg <= 8'b0 ;
end
else if (send_en ) begin
data_byte_reg <= data_byte ; //寄存一拍
end
else begin
data_byte_reg <= data_byte_reg ;
end
数据传输状态控制模块
从系统总体设计框图可以看出,当数据寄存一拍稳定后,通过一个十选一多路选择器,控制选择数据,根据bps_cnt的值来选择对应的传输数据。个人认为也可以认为是一个串并转换模块。
//uart_tx module 串并转换
always @ (posedge clk or posedge rst_n) begin
if (rst_n) begin
uart_tx <= 1'b1 ;
end
else begin
case (bps_cnt)
0:uart_tx <= 1'b1;
1:uart_tx <= START_BIT;
2:uart_tx <= data_byte_reg[0];
3:uart_tx <= data_byte_reg[1];
4:uart_tx <= data_byte_reg[2];
5:uart_tx <= data_byte_reg[3];
6:uart_tx <= data_byte_reg[4];
7:uart_tx <= data_byte_reg[5];
8:uart_tx <= data_byte_reg[6];
9:uart_tx <= data_byte_reg[7];
10:uart_tx <= STOP_BIT;
default : uart_tx <= 1'b1;
endcase
end
综上上述所用的子模块都设计完成
4.电路图网表对应
可见设计与综合出来的网表一致。
5.testbench仿真测试
`define CLK_PERIOD 20 //时钟周期20ns
module uart_byte_tx_tb(
);
//input signal
reg clk ; //时钟
reg reset_n ; //复位信号
reg send_en ; //发送使能
reg [7:0] data_byte ; //等待传输的8bit数据
reg [2:0] baud_set ;
//output signal
wire uart_tx ; //串口发送信号输出
wire tx_done ; //发送完毕
wire uart_state ;
//例化模块
uart_byte_tx uart_byte_tx_inst1(
.send_en ( send_en ) ,
.data_byte ( data_byte ) ,
.baud_set ( baud_set ) ,
.clk ( clk ) ,
.reset_n ( reset_n ) ,
.uart_tx ( uart_tx ) ,
.tx_done ( tx_done ) ,
.uart_state ( uart_state)
);
initial clk = 1 ;
always # (`CLK_PERIOD / 2 ) clk = ~clk ;
initial begin
send_en = 1'b0 ;
data_byte = 8'b0 ;
baud_set = 3'd4 ;
reset_n = 1'b0 ;
#(`CLK_PERIOD *500 + 1) //延时500个时钟周期后
reset_n = 1'b1 ; //将复位信号拉高
#(`CLK_PERIOD *50 ) ;
//send frist byte
data_byte = 8'haa ;
send_en = 1'b1 ;
#`CLK_PERIOD
send_en = 1'b0 ;
@(posedge tx_done )
#(`CLK_PERIOD * 5000 ) ;
//send second byte
data_byte = 8'hbb ;
send_en = 1'b1 ;
#`CLK_PERIOD ;
send_en = 1'b0 ;
@(posedge tx_done )
#(`CLK_PERIOD * 5000) ;
$stop ;
end
endmodule
仿真结果图
从仿真结果图可以看出testbench,定义一个时钟周期为20ns,可以看出当发送aa ,bb 能接收到数据。仿真结果表明仿真正确。
总结:这里运用了分频和线性序列表的思想实现了字节的发送,设计思想为先查波特率LUT查找表选择出适合的波特率,然后设计出计数器计数到相对应每个bit对应的最大周期数,计数器计数到1时候更新一次波特率时钟,每次波特率时钟的上升沿发送1bit的数据。