买了一个开发板学习FPGA,找到的各种东西就记录在这个博客里了,同时也方便把自己不会的问题找到的结果记录一下,都是自己手打,所以可能说的话不那么严谨,不那么精准,看到的人要带着自己的思考去看,记住尽信书不如无书,哈哈哈。。。。。。
一、UART是什么?
UART是一种通用串行数据总线,也就是用于数据传输。是用于主机与辅助设备进行通信。这里的主机理解为计算机,计算机内部采用并行数据,辅助设备采用串行数据。中间需要设备进行数据转换,这也决定了UART工作原理是将传输数据的每个字符一位接一位地传输。UART采用异步传输模式,异步传输将比特分成小组进行传送,小组可以是8位的1个字符或更长。发送方可以在任何时刻发送这些比特组,而接收方从不知道它们会在什么时候到达。例如计算机键盘与主机的通信。UART用于远距离传输较为适合。可以数据同时发送和接收。
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)。
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。
二、UART串口通信一般包括部分
1 `timescale 1ns / 1ps
2 //
3 // Module Name: clkdiv
4 // 产生一个波特率9600的16倍频的时钟,9600*16= 153600
5 // 相当于50MHz的326分频,50000000/153600=326
6 //
7 module clkdiv(clk50, clkout);
8 input clk50; //系统时钟
9 output clkout; //采样时钟输出
10 reg clkout;
11 reg [15:0] cnt;
12
13 //分频进程,对50Mhz的时钟326分频
14 always @(posedge clk50)
15 begin
16 if(cnt == 16'd162)
17 begin
18 clkout <= 1'b1;
19 cnt <= cnt + 16'd1;
20 end
21 else if(cnt == 16'd325)
22 begin
23 clkout <= 1'b0;
24 cnt <= 16'd0;
25 end
26 else
27 begin
28 cnt <= cnt + 16'd1;
29 end
30 end
31 endmodule
分频比较简单,我写的另一个,
1 `timescale 1ns / 1ps
2
3 module clkdiv(clk50, clkout);
4 input clk50; //系统时钟
5 output clkout; //采样时钟输出
6 reg clkout;
7 reg [15:0] cnt;
8
9 always @(posedge clk50)
10 begin
11 if (cnt < 16'd162)
12 cnt <= cnt + 16'b1;
13 else if (cnt == 16'd162)
14 begin
15 clkout <= ~clkout;
16 cnt <= 16'b0;
17 end
18 end
三、串口发送程序
UART采用异步传输,就涉及起始位与停止位,下面是代码例子,我的总结用红笔标出。
1 `timescale 1ns / 1ps
2 //
3 // Module Name: uarttx
4 // 说明:16个clock发送一个bit,
5 //
6 module uarttx(clk, datain, wrsig, idle, tx);
7 input clk; //UART时钟
8 input [7:0] datain; //需要发送的数据
9 input wrsig; //发送命令,上升沿有效
10 output idle; //线路状态指示,高为线路忙,低为线路空闲
11 output tx; //发送数据信号
12 reg idle, tx;
13 reg send;
14 reg wrsigbuf, wrsigrise;
15 reg presult;
16 reg[7:0] cnt; //计数器
17 parameter paritymode = 1'b0;
18
19 //检测发送命令是否有效,判断wrsig的上升沿 //先检测发送命令是否有效,然后判断线路状态
20 always @(posedge clk)
21 begin
22 wrsigbuf <= wrsig;
23 wrsigrise <= (~wrsigbuf) & wrsig; //边沿检测,检测发送命令
24 end
25
26 always @(posedge clk)
27 begin
28 if (wrsigrise && (~idle)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
29 begin
30 send <= 1'b1;
31 end
32 else if(cnt == 8'd168) //一帧资料发送结束
33 begin
34 send <= 1'b0;
35 end
36 end
37
38 /
39 //使用168个时钟发送一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟// //停止位为8个时钟周期
40
41 always @(posedge clk)
42 begin
43 if(send == 1'b1) begin
44 case(cnt) //tx变低电平产生起始位,0~15个时钟为发送起始位
45 8'd0: begin
46 tx <= 1'b0;
47 idle <= 1'b1;
48 cnt <= cnt + 8'd1;
49 end
50 8'd16: begin
51 tx <= datain[0]; //发送数据位的低位bit0,占用第16~31个时钟
52 presult <= datain[0]^paritymode; //奇偶校验位的获取
53 idle <= 1'b1;
54 cnt <= cnt + 8'd1;
55 end
56 8'd32: begin
57 tx <= datain[1]; //发送数据位的第2位bit1,占用第47~32个时钟
58 presult <= datain[1]^presult;
59 idle <= 1'b1;
60 cnt <= cnt + 8'd1;
61 end
62 8'd48: begin
63 tx <= datain[2]; //发送数据位的第3位bit2,占用第63~48个时钟
64 presult <= datain[2]^presult;
65 idle <= 1'b1;
66 cnt <= cnt + 8'd1;
67 end
68 8'd64: begin
69 tx <= datain[3]; //发送数据位的第4位bit3,占用第79~64个时钟
70 presult <= datain[3]^presult;
71 idle <= 1'b1;
72 cnt <= cnt + 8'd1;
73 end
74 8'd80: begin
75 tx <= datain[4]; //发送数据位的第5位bit4,占用第95~80个时钟
76 presult <= datain[4]^presult;
77 idle <= 1'b1;
78 cnt <= cnt + 8'd1;
79 end
80 8'd96: begin
81 tx <= datain[5]; //发送数据位的第6位bit5,占用第111~96个时钟
82 presult <= datain[5]^presult;
83 idle <= 1'b1;
84 cnt <= cnt + 8'd1;
85 end
86 8'd112: begin
87 tx <= datain[6]; //发送数据位的第7位bit6,占用第127~112个时钟
88 presult <= datain[6]^presult;
89 idle <= 1'b1;
90 cnt <= cnt + 8'd1;
91 end
92 8'd128: begin
93 tx <= datain[7]; //发送数据位的第8位bit7,占用第143~128个时钟
94 presult <= datain[7]^presult;
95 idle <= 1'b1;
96 cnt <= cnt + 8'd1;
97 end
98 8'd144: begin
99 tx <= presult; //发送奇偶校验位,占用第159~144个时钟 //将计算结果作为奇偶校验码输出
100 presult <= datain[0]^paritymode; //无意思,此行计算结果无用,多余
101 idle <= 1'b1;
102 cnt <= cnt + 8'd1;
103 end
104 8'd160: begin
105 tx <= 1'b1; //发送停止位,占用第160~167个时钟
106 idle <= 1'b1;
107 cnt <= cnt + 8'd1;
108 end
109 8'd168: begin
110 tx <= 1'b1;
111 idle <= 1'b0; //一帧资料发送结束
112 cnt <= cnt + 8'd1;
113 end
114 default: begin
115 cnt <= cnt + 8'd1;
116 end
117 endcase
118 end
119 else begin
120 tx <= 1'b1;
121 cnt <= 8'd0;
122 idle <= 1'b0;
123 end
124 end
125 endmodule
四、串口接收程序
成为UART接收信号,将一位一位的串口数据转化为并行数据。
1 `timescale 1ns / 1ps
2 //
3 // Module name uartrx.v
4 // 说明: 16个clock接收一个bit,16个时钟采样,取中间的采样值
5 //
6 module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);
7 input clk; //采样时钟
8 input rx; //UART数据输入
9 output dataout; //接收数据输出
10 output rdsig; //接收数据有效,高说明接收到一个字节 ,来区分数据处于接收状态或者接收控制信号
11 output dataerror; //数据出错指示
12 output frameerror; //帧出错指示
13
14 reg[7:0] dataout;
15 reg rdsig, dataerror;
16 reg frameerror;
17 reg [7:0] cnt;
18 reg rxbuf, rxfall, receive;
19 parameter paritymode = 1'b0;
20 reg presult, idle;
21
22 always @(posedge clk) //检测线路rx的下降沿, 线路空闲的时候rx为高电平
23 begin
24 rxbuf <= rx;
25 rxfall <= rxbuf & (~rx); //下降沿检测,检测是否接收到接收信号
26 end
27
28 always @(posedge clk)
29 begin
30 if (rxfall && (~idle)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
31 begin
32 receive <= 1'b1; //开始接收数据
33 end
34 else if(cnt == 8'd168) //接收数据完成
35 begin
36 receive <= 1'b0;
37 end
38 end
39
40 /
41 //使用176个时钟接收一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟//
42
43 always @(posedge clk)
44 begin
45 if(receive == 1'b1)
46 begin
47 case (cnt)
48 8'd0: //0~15个时钟为接收第一个比特,起始位
49 begin
50 idle <= 1'b1;
51 cnt <= cnt + 8'd1;
52 rdsig <= 1'b0;
53 end
54 8'd24: //16~31个时钟为第1个bit数据,取中间第24个时钟的采样值 //在发送程序中,第0位数据在16个时钟周期后开始传输,接收过程中,
55 begin //从第24个时钟周期开始接收第0位数据,保证信号被采集。
56 idle <= 1'b1;
57 dataout[0] <= rx;
58 presult <= paritymode^rx;
59 cnt <= cnt + 8'd1;
60 rdsig <= 1'b0;
61 end
62 8'd40: //47~32个时钟为第2个bit数据,取中间第40个时钟的采样值
63 begin
64 idle <= 1'b1;
65 dataout[1] <= rx;
66 presult <= presult^rx;
67 cnt <= cnt + 8'd1;
68 rdsig <= 1'b0;
69 end
70 8'd56: //63~48个时钟为第3个bit数据,取中间第56个时钟的采样值
71 begin
72 idle <= 1'b1;
73 dataout[2] <= rx;
74 presult <= presult^rx;
75 cnt <= cnt + 8'd1;
76 rdsig <= 1'b0;
77 end
78 8'd72: //79~64个时钟为第4个bit数据,取中间第72个时钟的采样值
79 begin
80 idle <= 1'b1;
81 dataout[3] <= rx;
82 presult <= presult^rx;
83 cnt <= cnt + 8'd1;
84 rdsig <= 1'b0;
85 end
86 8'd88: //95~80个时钟为第5个bit数据,取中间第88个时钟的采样值
87 begin
88 idle <= 1'b1;
89 dataout[4] <= rx;
90 presult <= presult^rx;
91 cnt <= cnt + 8'd1;
92 rdsig <= 1'b0;
93 end
94 8'd104: //111~96个时钟为第6个bit数据,取中间第104个时钟的采样值
95 begin
96 idle <= 1'b1;
97 dataout[5] <= rx;
98 presult <= presult^rx;
99 cnt <= cnt + 8'd1;
100 rdsig <= 1'b0;
101 end
102 8'd120: //127~112个时钟为第7个bit数据,取中间第120个时钟的采样值
103 begin
104 idle <= 1'b1;
105 dataout[6] <= rx;
106 presult <= presult^rx;
107 cnt <= cnt + 8'd1;
108 rdsig <= 1'b0;
109 end
110 8'd136: //143~128个时钟为第8个bit数据,取中间第136个时钟的采样值
111 begin
112 idle <= 1'b1;
113 dataout[7] <= rx;
114 presult <= presult^rx;
115 cnt <= cnt + 8'd1;
116 rdsig <= 1'b1; //接收数据有效
117 end
118 8'd152: //159~144个时钟为接收奇偶校验位,取中间第152个时钟的采样值
119 begin
120 idle <= 1'b1;
121 if(presult == rx)
122 dataerror <= 1'b0;
123 else
124 dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错
125 cnt <= cnt + 8'd1;
126 rdsig <= 1'b1;
127 end
128 8'd168: //160~175个时钟为接收停止位,取中间第168个时钟的采样值
129 begin
130 idle <= 1'b1;
131 if(1'b1 == rx)
132 frameerror <= 1'b0;
133 else
134 frameerror <= 1'b1; //如果没有接收到停止位,表示帧出错
135 cnt <= cnt + 8'd1;
136 rdsig <= 1'b1;
137 end
138 default:
139 begin
140 cnt <= cnt + 8'd1;
141 end
142 endcase
143 end
144 else
145 begin
146 cnt <= 8'd0;
147 idle <= 1'b0;
148 rdsig <= 1'b0;
149 end
150 end
151 endmodule
五、控制程序
这里主要学习程序的调用,如何用总的控制程序完成UART数据传输。
1 `timescale 1ns / 1ps
2 //
3 // Module Name: uart_test
4 //
5 //
6 module uart_test(clk50, rx, tx, reset);
7 input clk50;
8 input reset;
9 input rx;
10 output tx;
11
12 wire clk; //clock for 9600 uart port
13 wire [7:0] txdata,rxdata; //串口发送数据和串口接收数据
14
15
16
17 //产生时钟的频率为16*9600
18 clkdiv u0 (
19 .clk50 (clk50), //50Mhz的晶振输入
20 .clkout (clk) //16倍波特率的时钟
21 );
22
23 //串口接收程序
24 uartrx u1 (
25 .clk (clk), //16倍波特率的时钟
26 .rx (rx), //串口接收
27 .dataout (rxdata), //uart 接收到的数据,一个字节
28 .rdsig (rdsig), //uart 接收到数据有效
29 .dataerror (),
30 .frameerror ()
31 );
32
33 //串口发送程序
34 uarttx u2 (
35 .clk (clk), //16倍波特率的时钟
36 .tx (tx), //串口发送
37 .datain (txdata), //uart 发送的数据
38 .wrsig (wrsig), //uart 发送的数据有效
39 .idle ()
40
41 );
54 endmodule
就像c语言里调用子函数一样,每个 模块是并行运行的, 各个模块连接完成整个系统需要一个顶层文件(top-module) 。 顶层文件 通过调用、连接低层模块的实例来实现复杂的功能。
学习UART通信,最主要还是理解异步和串口这两个东西,方便远距离传输,串口一位一位传输,牺牲了时间降低时序要求。