串口UART学习笔记(一)
买了一个开发板学习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通信,最主要还是理解异步和串口这两个东西,方便远距离传输,串口一位一位传输,牺牲了时间降低时序要求。