一、概念原理
异步通信(UART):按照自己的节奏发送、接收(发送端、接收端的时钟不一致)
同步通信(USART):在通信中的发送端和接收端,时钟能够保持一致。
IIC:为低速设备通信而生,传输速率比不上SPI。两线式串行总线,用于连接微控制器以及外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
注:IIC是半双工,判断哪个在控制时,要跟他&,
SPI:
这里补充 全双工、半双工、单工。
全双工:可以同时收发,互不干扰。例如,uart。
半双工:数据不可以同时收发,例如,rs232,485.iic
单工:只有一个方向,例如,spi
UART协议:(作图说明)
解释:奇偶校验位在这里
奇校验:时刻保持9bit里1为奇数个。例如,8个数据位,0000_0001,有1个1,则校验位为0;
若有偶数个1,则校验位为1;
偶校验:与奇校验正好相反,时刻保持9bit里1的个数为偶数个。
校验的方式通常是用异或来判断;相同为0,不同为1;
二、设计框图
注:频率=时钟个数*波特率。uart:一个时钟一个周期,所以这里频率就是9600;那么分频就是50MHZ/9600,取一个大致值即可。
三、UART通用模块思想
1.波特率可配置;
2.输入时钟;
3.数据位;
4.停止位
5.校验位
四、程序编写
第一部分:驱动部分(上层)
module uart_drive#(
parameter P_SYSTEM_CLK = 50_000_000 ,//时钟频率
parameter P_UART_BUADRATE = 9600 ,//波特率
parameter P_UART_DATA_WIDTH = 8 ,//数据宽度
parameter P_UART_STOP_WIDTH = 1 ,//停止位
parameter P_UART_CHECK = 0 //0为无校验,1为奇校验,2为偶校验
)(
input i_clk,
input i_rst,
input i_uart_rx,
output i_uart_tx,
input [P_UART_DATA_WIDTH-1:0] i_user_tx_data,
input i_user_tx_valid,
output o_user_tx_ready,
output [P_UART_DATA_WIDTH-1:0] o_user_rx_data,
output o_user_rx_valid
);
第一,顶层设计思想,通用模块配置好,顶层参数;第二,输入50MHZ,复位信号;第三,输入接收数据,输出发送数据;第四,用户数据、准备握手、传输数据
第二部分:接收模块
uart_rx#(
. P_SYSTEM_CLK (P_SYSTEM_CLK ) ,
. P_UART_BUADRATE (P_UART_BUADRATE ) ,
. P_UART_DATA_WIDTH (P_UART_DATA_WIDTH) ,//数据宽度
. P_UART_STOP_WIDTH (P_UART_STOP_WIDTH) //停止位:1or2
)
uart_rx_u0
(
. i_clk(w_uart_buadclk) ,
. i_rst(w_uart_buadclk_rst),
. i_uart_rx( i_uart_rx),
. o_user_rx_data(o_user_rx_data),
. o_user_rx_valid(o_user_rx_valid)
);
第一,顶层参数的设置, 但这里为啥缺少一个检查位? ;第二,端口定义声明,输入,复位,接收信号,接收数据,接收反馈;
rst_gen_module#(
. P_RST_CYCLE (1)
)
rst_gen_module_u0
(
. i_clk(w_uart_buadclk),
. o_rst(w_uart_buadclk_rst)
);
module rst_gen_module#(
parameter P_RST_CYCLE =1
)(
input i_clk,
output o_rst
);
reg ro_rst = 0;
reg [7:0] r_cnt;
assign o_rst=ro_rst;
always@(posedge i_clk)
begin
if(r_cnt == P_RST_CYCLE-1||P_RST_CYCLE == 0)
r_cnt<=r_cnt;
else
r_cnt<=r_cnt+1;
end
always@(posedge i_clk)
begin
if(r_cnt == P_RST_CYCLE-1||P_RST_CYCLE == 0)
ro_rst<='d0;
else
ro_rst<='d1;
end
endmodule
⚠️这里复位没有用板子的值,而是又重新分频了一个复位值rst_gen,原因就是不能跨时钟复位。
第三部分:发送模块设计
CLK_DIV_module#(
. p_CLK_DIV_CNT ( P_CLK_DIV_NUMBER)//16位宽:最大65535
)
CLK_DIV_module_u0
(
. i_clk(i_clk),
. i_rst(i_rst),
. o_clk_div(w_uart_buadclk)
);
uart_tx#(
. P_SYSTEM_CLK (P_SYSTEM_CLK ) ,
. P_UART_BUADRATE (P_UART_BUADRATE ) ,
. P_UART_DATA_WIDTH (P_UART_DATA_WIDTH) ,//数据宽度
. P_UART_STOP_WIDTH (P_UART_STOP_WIDTH) //停止位
)
uart_tx_u0
(
. i_clk(w_uart_buadclk),
. i_rst(w_uart_buadclk_rst),
. o_uart_tx( o_uart_tx),
. i_user_tx_data (i_user_tx_data ),
. i_user_tx_valid(i_user_tx_valid),
. o_user_tx_ready(o_user_tx_ready)
);
第三部分:发送模块设计
module uart_tx#(
parameter P_SYSTEM_CLK = 50_000_000 ,
parameter P_UART_BUADRATE = 9600 ,
parameter P_UART_DATA_WIDTH = 8 ,//数据宽度
parameter P_UART_STOP_WIDTH = 1 , //停止位
parameter P_UART_CHECK = 0 //0为无校验,1为奇校验,2为偶校验
)(
input i_clk,
input i_rst,
output o_uart_tx,
input [P_UART_DATA_WIDTH-1:0] i_user_tx_data,
input i_user_tx_valid,
output o_user_tx_ready
);
第一,顶层参数的设置;第二,声明端口以及定义端口,特别注意的是,o_uart_tx是往外输出的数据信号,随后依次是往外发送的数据,发送信号值以及时刻准备值(来张波形图说明下关系)
reg ro_uart_tx; //面对一个新的要求,不知该从何入手时的一个解决办法;记得整理下笔记
reg ro_user_tx_ready;
reg [15:0] r_cnt;//计数器位宽高于16bit时,组合逻辑的级数过高,请谨慎使用。
reg [P_UART_DATA_WIDTH-1:0] r_tx_data; //正常-1.现在+1,实则把校验位,停止位都包含在内。笔记
reg r_tx_check;
/***************wire******************/
wire w_tx_active;
/***************component*************/
/***************assign****************/
assign o_uart_tx = ro_uart_tx;
assign o_user_tx_ready = ro_user_tx_ready;
assign w_tx_active = i_user_tx_valid & o_user_tx_ready;
/***************always****************/
注意:我们在面对一个新的设计要求时,首先要从输出处入手,先设计一个寄存器,做赋值处理;
一旦有赋值就会有计数器的存在(也不一定)
always@(posedge i_clk,posedge i_rst) //准备 形式固定。
begin
if(i_rst)
ro_user_tx_ready <= 'd1;
else if(w_tx_active)
ro_user_tx_ready <= 'd0;
else if(r_cnt == 2 +P_UART_DATA_WIDTH + P_UART_STOP_WIDTH -1)
ro_user_tx_ready <= 'd1;
else
ro_user_tx_ready <= ro_user_tx_ready ;
end
老规矩:时序逻辑一上来,先降龙十八掌,if......else块搞上三个。然后填充内容。空闲位置为高电平,当握手成功,也就是value为1时,起始位的值为0;当计数器计满(这里没搞懂,在哪里计数),再拉高,停止位,最后进入空闲位。
always@(posedge i_clk,posedge i_rst) //握手
begin
if(i_rst)
r_cnt <= 'd0;
else if(r_cnt == 2 +P_UART_DATA_WIDTH + P_UART_STOP_WIDTH -1)
r_cnt <= 'd0;
else if(!ro_user_tx_ready)//作下图:vaule、ready、data 笔记
r_cnt <= r_cnt+ 'd1;
else
r_cnt <= r_cnt;
end
计数器:复位,计满都为清零,当准备信号下拉为0,开始传输值时,计数器开始计数。
always@(posedge i_clk,posedge i_rst) //接货
begin
if(i_rst)
r_tx_data <= 'd0;
else if(w_tx_active)//用户寄存
r_tx_data <= i_user_tx_data;//1.奇校验:
else if(!ro_user_tx_ready)
r_tx_data <= r_tx_data >> 1;//移位,方便下面送货。笔记
else
r_tx_data <= r_tx_data;
end
w_tx_active是与值,当为1时,就进入发送数据的状态。储存数据的寄存器开始将数据赋值,从低位开始传输。
always@(posedge i_clk,posedge i_rst)//送货
begin
if(i_rst)
ro_uart_tx <= 'd1;
else if(w_tx_active)
ro_uart_tx <= 'd0;
else if(r_cnt == P_UART_DATA_WIDTH )//倒数第三位校验位,会慢一拍
ro_uart_tx <= r_tx_check;
else if(r_cnt >= 3 +P_UART_DATA_WIDTH -2)//倒数第二位发停止位
r_tx_check <= 'd1;
else if(!ro_user_tx_ready)
ro_uart_tx <= r_tx_data[0];//先发低位
else
ro_uart_tx <= 'd1;
end
送货:在检查校验位,停止位;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_tx_check <= 'd0;
else if(!ro_user_tx_ready && P_UART_CHECK == 1)
r_tx_check <=~(r_tx_check ^ r_tx_data[0]);
else if(!ro_user_tx_ready && P_UART_CHECK == 2)
r_tx_check <=r_tx_check ^ r_tx_data[0];
else
r_tx_check <= r_tx_check;
end
endmodule
这个块在检查奇偶校验位,若是奇校验,;若是偶校验,。
module uart_rx#(
parameter P_SYSTEM_CLK = 50_000_000 ,
parameter P_UART_BUADRATE = 9600 ,
parameter P_UART_DATA_WIDTH = 8 ,//数据宽度
parameter P_UART_STOP_WIDTH = 1 , //停止位
parameter P_UART_CHECK = 0
)(
input i_clk,
input i_rst,
input i_uart_rx,//接收信号,关键!
output [P_UART_DATA_WIDTH-1:0] o_user_rx_data,
output o_user_rx_valid
);
/***************function**************/
/***************parameter*************/
/***************port******************/
/***************mechine***************/
/***************reg*******************/
reg [P_UART_DATA_WIDTH-1:0] ro_user_rx_data;
reg ro_user_rx_valid;
reg [1:0] r_uart_rx;//定义一个两级寄存器,准备打两拍
reg [1:0] r_cnt;//计数器是命脉;要是不用计数器,就得考虑状态机的使用。
reg r_rx_check;
/***************wire******************/
/***************component*************/
/***************assign****************/
assign o_user_rx_data = ro_user_rx_data;
assign o_user_rx_valid = ro_user_rx_valid;
/***************always****************/
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_uart_rx <= 'd0;
else
r_uart_rx <= {r_uart_rx[0],i_uart_rx};/*打两拍是无条件一直需要做的事情,故不需要条件触发;
信号不断地从低位读取进来,形成信号输入左移,构成了r_uart_rx[1]打两拍的情况。*/
end
always@(posedge i_clk,posedge i_rst) //计数器共有四个状态,上电清零;计满清零;计数;保持。输入数据,起始位来了 开始计数,用计数器获取数据。
begin
if(i_rst)
r_cnt <= 'd0;
else if(1+P_UART_DATA_WIDTH+1+P_UART_STOP_WIDTH-1)//从0开始,减去1是必要的
r_cnt <= 'd0;
else if(r_uart_rx[1] == 0 || r_cnt > 0)
r_cnt <= r_cnt +1;
else
r_cnt <= r_cnt;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_rx_data <= 'd0;
else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)
ro_user_rx_data <= {r_uart_rx[1],ro_user_rx_data[7:1]};//先发低位
else
ro_user_rx_data <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_rx_valid <= 'd0;
else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 0)
ro_user_rx_valid <= 'd1;//只持续一个周期
else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 1 && r_rx_check )
ro_user_rx_valid <= 'd1;
else if(r_cnt == P_UART_DATA_WIDTH && P_UART_CHECK == 2 && !r_rx_check)
ro_user_rx_valid <= 'd1;
else
ro_user_rx_valid <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_rx_check <= 'd0;
else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)//接收数据期间
r_rx_check <=r_rx_check^r_uart_rx[1];//异或判断接收的1是奇数还是偶数,奇数则为1,偶数则为0
else
r_rx_check <= r_rx_check;
end