设计背景:
串口是串行接口的简称,也可称为串行通信接口。通信协议是指通信双方的一种约定。约定包括对数据格式、同步方式、传送速度、传送步骤、检纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守。串口通信的两种最基本的方式为:同步串行通信方式和异步串行通信方式。
同步串行通信是指SPI(Serial Peripheral interface)的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的全双工通信总线。封装芯片上总共有四根线,PCB布局布线也简单,所以现在很多芯片集成了这个协议。主要用于CPU和各种外围器件进行通信,TRM450是SPI接口。
异步串行通信是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART是一个并行输入成为串行输出的芯片,通常集成在主板上。UART包含TTL电平的串口和RS232电平的串口。RS232也称标准串口,也是最常用的一种串行通讯接口。RS-232-C 标准对两个方面作了规定,即信号电平标准和控制信号线的定义。RS-232-C 采用负逻辑规定逻辑电平,信号电平与通常的TTL电平也不兼容,RS-232-C 将-5V~-15V 规定为“1”,+5V~+15V 规定为“0”。
设计原理:
uart的示意图如下:
其端口对应的功能表如下:
在设计过程中只需要关心RS232_TXD和RS232_RXD两个信号, RS232_TXD是数据发送端口,RS232_RXD是数据接收端口。
本设计将通过串口建立起计算机和实验板(ZX_1)之间的通信和控制关系,也就是通常所说的上下位机通信。要实现这样的通信,首先需要用到一个外部的电平转换芯片MAX232,其具体配置电路原理图如下:
注解:
MAX232芯片是美信(MAXIM)公司专为RS-232标准串口设计的单电源电平转换芯片,使用+5v单电源供电。
主要特点:
1、符合所有的RS-232C技术标准;
2、只需要单一+5V电源供电;
3、片载电荷泵具有升压、电压极性反转能力,能够产生+10V和-10V电压V+、V-;
4、功耗低,典型供电电流5mA;
5、内部集成2个RS-232C驱动器;
6、高集成度,片外最低只需4个电容即可工作。
本设计还需要分析在通信过程中,UART所对应的数据格式,
起始位:线路空闲时为高电平,当截获第一个低电平比特时,则为起始位;
信息位:在起始位之后,按照低位首发原则,顺序发送信息位的最低位到最高位,信息位的宽度可以是4、5、6、7、8中的一个;
奇偶校验位:信息位之后则是一个可选的奇偶校验位,它可以是无校验(NONE)、奇校验(ODD)、偶校验(EVEN)中的任意一个,无校验时,信息位之后就是停止位。奇偶校验是,使得信息位和校验位的所有1的个数保持奇数或者偶数位;
停止位:停止位的长度可以是1、1.5或2中的任意一个,它为高电平;
空闲位:持续的高电平;
波特率:每秒传输的数据位(bit)数为波特率。RS-232-C的波特率可以是50、75、100、150、300、600、1200、2400、4800、9600、19200波特。
通过分析上述的数据格式,在本设计中,将波特率设置为9600,起始位设置为1比特,信息位设置为8比特,奇偶校验位设置为0比特,停止位设置为2比特,空闲位设置为1比特
因为在设计中只需要关注RS232_TXD和RS232_RXD这两个信号,,既然只有两条线,所以只需要关注其数据收发时序即可,时序图如下:
设计架构图:
设计架构图如下:
uart_pll模块是一个锁相环,通过50M的外部时钟(ref_clk),倍频得到100M的上游接口的100M系统时钟(sys_clk);divider模块为UART的分频模块,通过用100M的sys_clk作为输入,分频得到波特率为9600的uart_clk时钟。
transmitter模块为串口发送模块,并配合与其对应的trans_fifo发送数据缓存FIFO进行使用,将储存在FIFO中的数据通过RS232-C协议发送出去;
receiver模块为串口接收模块,并配合与其对应的rec_fifo接收数据缓存FIFO进行使用,将储存在FIFO中的数据通过RS232-C协议接收进来;
UART发送器(transmitter)设计
UART发送器的时序如下图:
根据对UART发送器时序的分析可以得到如下的状态转移表(SMF):
UART接收器(receiver)设计
根据对UART时序的分析可以得到如下的状态转移表(SMF):
设计代码:
根据上述的两个状态转移表则可得到如下的代码,transmitter模块代码:
0 //uart发送模块LSM(线性序列机)
1 module transmitter(clk, rst_n, empty, data, rdreq, txd);
2
3 input clk, rst_n; //输入时钟复位
4 input empty; //来自fifo的输入空标志信号
5 input [7:0] data; //来自fifo的输入数据
6 output reg rdreq; //输出到fifo的读请求
7 output reg txd; //输出发送线信号
8
9 reg [7:0] temp; //中间寄存器
10 reg [7:0] count; //8位计数
11
12 `define EP 192 //终止符
13
14 always @ (posedge clk or negedge rst_n)
15 begin : lsm_2s1 //线性序列机一段闭节点
16 if (!rst_n) //复位
17 count <= `EP;
18 else if ((count >= `EP) && !empty) //计数大于终止符和非空(empty=0)
19 count <= 0;
20 else if (count < `EP) //计数小于终止符
21 count <= count + 1;
22 end
23
24 always @ (posedge clk or negedge rst_n)
25 begin : lsm_2s2 //线性序列机一段闭节点
26 if (!rst_n) //复位
27 begin
28 txd <= 1; //发送线为高
29 rdreq <= 0; //读请求为0
30 temp <= 0; //中间寄存器为0
31 end
32 else if ((count >= `EP) && !empty) //计数大于终止符fifo为非空,读请求拉高
33 rdreq <= 1;
34 else
35 case (count)
36 0 : begin
37 rdreq <= 0; //读请求拉低
38 txd <= 0;
39 end
40 1 : temp[7:0] <= data[7:0]; //输入数据给中间寄存器
41 1*16 : txd <= temp[0]; //中间寄存器按位给发送线发送
42 2*16 : txd <= temp[1];
43 3*16 : txd <= temp[2];
44 4*16 : txd <= temp[3];
45 5*16 : txd <= temp[4];
46 6*16 : txd <= temp[5];
47 7*16 : txd <= temp[6];
48 8*16 : txd <= temp[7];
49 9*16 : txd <= 1; //拉高
50 endcase
51 end
52
53 endmodule
transmitter(发送)模块的测试代码:
0 `include "uart_lsm_head.v"
1
2 module transmitter_tb;
3
4 reg clk, rst_n;
5 reg empty;
6 reg [7:0] data;
7 wire rdreq;
8 wire txd;
9
10 reg [7:0] temp;
11
12 transmitter transmitter_dut(
13 .clk(clk),
14 .rst_n(rst_n),
15 .empty(empty),
16 .data(data),
17 .rdreq(rdreq),
18 .txd(txd)
19 );
20
21 initial begin
22 clk = 1;
23 rst_n = 0;
24 data = 0;
25 empty = 1;
26 temp = 0;
27 #200.1 rst_n = 1;
28
29 #200.1 empty=1;temp=8'h55;
30 #`TBAUD_RATE
31 data[0] = temp[0]; //发送第一个信息位(LSB)
32 #`TBAUD_RATE
33 data[1] = temp[1];
34 #`TBAUD_RATE
35 data[2] = temp[2];
36 #`TBAUD_RATE
37 data[3] = temp[3];
38 #`TBAUD_RATE
39 data[4] = temp[4];
40 #`TBAUD_RATE
41 data[5] = temp[5];
42 #`TBAUD_RATE
43 data[6] = temp[6];
44 #`TBAUD_RATE
45 data[7] = temp[7];
46 #`TBAUD_RATE
47 empty = 0;
48 #2000 $stop;
49 end
50
51 always #`TUART_CLK_HALF clk = ~clk;
52
53 endmodule
receiver模块代码:
0 `include "uart_lsm_head.v"
1
2 module receiver(clk, rst_n, data, wrreq, rxd); //uart接收模块LSM(线性序列机)
3
4 input clk, rst_n; //输入时钟复位
5 output reg [7:0] data; //输出数据
6 output reg wrreq; //输出写请求
7 input rxd; //输入接收线信号
8
9 reg [7:0] count;
10 //宏定义
11 `define EP 184 //终止符
12 `define GET0 24
13 `define GET1 `GET0+16
14 `define GET2 `GET1+16
15 `define GET3 `GET2+16
16 `define GET4 `GET3+16
17 `define GET5 `GET4+16
18 `define GET6 `GET5+16
19 `define GET7 `GET6+16
20 `define GETW `GET7+16 //wrreq=1
21 `define GLRW `GETW+1 //wrreq=0
22
23 always @ (posedge clk or negedge rst_n)
24 begin : lsm_2s1 //线性序列机一段闭节点
25 if(!rst_n)
26 count <= `EP;
27 else if((count >= `EP) && !rxd) //rxd=0
28 count <= 0;
29 else if (count < `EP)
30 count <= count + 1;
31 end
32
33 always @ (posedge clk or negedge rst_n)
34 begin : lsm_2s2 //线性序列机二段闭节点
35 if(!rst_n)
36 begin
37 data <= 0;
38 wrreq <= 0; //写请求为0
39 end
40 else
41 case(count)
42 `GET0 : data[0] <= rxd; //将接收的数据通过data输出
43 `GET1 : data[1] <= rxd;
44 `GET2 : data[2] <= rxd;
45 `GET3 : data[3] <= rxd;
46 `GET4 : data[4] <= rxd;
47 `GET5 : data[5] <= rxd;
48 `GET6 : data[6] <= rxd;
49 `GET7 : data[7] <= rxd;
50 `GETW : wrreq <= 1; //写请求拉高一拍,写进fifo
51 `GLRW : wrreq <= 0; //一拍后写请求为0
52 endcase
53 end
54
55 endmodule
receiver(接收)模块的测试代码:
0 `include "uart_lsm_head.v"
1
2 module receiver_tb;
3
4 reg clk, rst_n;
5 reg rxd;
6 wire [7:0] data;
7 wire wrreq;
8
9 reg [7:0] temp; //8位的中间寄存器,产生激励
10
11 receiver receiver_dut(
12 .clk(clk),
13 .rst_n(rst_n),
14 .data(data),
15 .wrreq(wrreq),
16 .rxd(rxd)
17 );
18
19 initial begin
20 clk = 1;
21 rst_n = 0;
22 temp = 0;
23 rxd = 1;
24 #`TEN_TUART_CLK //*代表异步 //10倍uart_clk周期
25 rst_n = 1;
26
27 #`TEN_TUART_CLK //启动一个停止位
28 rxd = 0;
29 temp = 8'h55;
30 #`TBAUD_RATE //数据使用波特率的周期
31 rxd = temp[0]; //发送一个信息位(LSB)
32 #`TBAUD_RATE
33 rxd = temp[1];
34 #`TBAUD_RATE
35 rxd = temp[2];
36 #`TBAUD_RATE
37 rxd = temp[3];
38 #`TBAUD_RATE
39 rxd = temp[4];
40 #`TBAUD_RATE
41 rxd = temp[5];
42 #`TBAUD_RATE
43 rxd = temp[6];
44 #`TBAUD_RATE
45 rxd = temp[7]; //发送最后一个信息位(HSB)
46 #`TBAUD_RATE
47 rxd = 1;
48
49 #`TUART_CLK100 $stop; //100倍uart_clk周期
50 end
51
52 always #`TUART_CLK_HALF clk = ~clk; // uart_clk 的时钟,使用uart_clk的半周期
53
54 endmodule
参数宏的头文件代码:
0 /////uart_lsm_head.v
1
2 //////////定义时标////////////
3 `timescale 1us/1ns
4
5 /////////定义设计参数/////////
6 `define BAUD_RATE 9600 //波特率=9600
7 `define SYS_CLK 100000000 //系统时钟sys_clk 频率=100M
8 `define REF_CLK 50000000 //系统时钟ref_clk频率=50M
9
10 //////////使用宏自动计算的诸参数////////////
11 `define TBAUD_RATE (1000000.0/`BAUD_RATE)//波特率周期
12 `define UART_CLK (16*`BAUD_RATE) //uart_clk 等于16倍波特率
13 `define TUART_CLK (1000000.0/`UART_CLK) //uart_clk周期
14 `define TEN_TUART_CLK (10.0*`TUART_CLK) //10倍uart_clk周期
15 `define TUART_CLK100 (100.0*`TUART_CLK) //100倍uart_clk周期
16
17 `define TUART_CLK_HALF (`TUART_CLK/2.0) //uart_clk半周期
18 `define TREF_CLK (1000000.0/`REF_CLK) //参考时钟周期
19 `define TREF_CLK_HALF (`TREF_CLK/2.0) //参考时钟半周期
20
21 //////////使用宏自动计算的分频数(占空比50%)////////////
22 `define DW (`SYS_CLK/(2*`UART_CLK))
顶层文件代码:
0 `include "uart_lsm_head.v"
1
2 module uart_lsm(ref_clk, global_reset,tdata, twrreq,
3 tfull, rdata, rrdreq, rempty, uart_txd, uart_rxd);
4
5 input ref_clk, global_reset; //全局时钟复位
6 input [7:0] tdata; //发送fifo输入数据
7 input twrreq; //发送fifo写请求
8 output tfull; //发送fifo输出写满
9 output [7:0] rdata; //接收fifo输出数据
10 input rrdreq; //接收fifo的输入读请求
11 output rempty; //接收fifo的输出入空
12 output uart_txd; //输出发送线信号
13 input uart_rxd; //输入接收线信号
14
15 wire trxd;
16
17 wire [7:0] tf_data, rf_data;
18 wire tf_rdreq, tf_empty, rf_wrreq;
19 wire sys_clk, uart_clk, rst_n;
20
21
22 assign rst_n = ~global_reset;
23
24 trans_fifo t_fifo( //发送fifo
25 .data(tdata),
26 .rdclk(uart_clk),
27 .rdreq(tf_rdreq),
28 .wrclk(sys_clk),
29 .wrreq(twrreq),
30 .q(tf_data),
31 .rdempty(tf_empty),
32 .wrfull(tfull)
33 );
34
35 transmitter trans( //发送模块
36 .clk(uart_clk),
37 .rst_n(rst_n),
38 .empty(tf_empty),
39 .data(tf_data),
40 .rdreq(tf_rdreq),
41 .txd(trxd)
42 );
43
44 rec_fifo r_fifo( //接收fifo
45 .data(rf_data),
46 .rdclk(sys_clk),
47 .rdreq(rrdreq),
48 .wrclk(uart_clk),
49 .wrreq(rf_wrreq),
50 .q(rdata),
51 .rdempty(rempty)
52 );
53
54 receiver rece( //接收模块
55 .clk(uart_clk),
56 .rst_n(rst_n),
57 .data(rf_data),
58 .wrreq(rfwrreq),
59 .rxd(trxd)
60 );
61
62 uart_pll u_pll( //锁相环产生系统时钟,作用于fifo、divider
63 .areset(global_reset),
64 .inclk0(ref_clk),
65 .c0(sys_clk)
66 );
67
68 divider_ebd_1s_mealy //分频模块分频uart_clk,作用于receiver transmitter
69 #(.HW(`DW), .LW(`DW))
70 div(
71 .clk_in(sys_clk),
72 .rst_n(rst_n),
73 .clk_out(uart_clk)
74 );
75
76 endmodule
仿真图:
分别为发送和接收做仿真测试,发送的仿真波形如下:
接收的仿真波形如下:
根据以上两个仿真波形,可以发现设计是正确的,之后则可利用串口猎人的上位机软件,实现自发自收。
全部0条评论
快来发表一下你的评论吧 !