买了一个开发板学习FPGA,找到的各种东西就记录在这个博客里了,同时也方便把自己不会的问题找到的结果记录一下,都是自己手打,所以可能说的话不那么严谨,不那么精准,看到的人要带着自己的思考去看,记住尽信书不如无书,哈哈哈。。。。。。

 一、UART是什么?

UART是一种通用串行数据总线,也就是用于数据传输。是用于主机与辅助设备进行通信。这里的主机理解为计算机,计算机内部采用并行数据,辅助设备采用串行数据。中间需要设备进行数据转换,这也决定了UART工作原理是将传输数据的每个字符一位接一位地传输。UART采用异步传输模式,异步传输将比特分成小组进行传送,小组可以是8位的1个字符或更长。发送方可以在任何时刻发送这些比特组,而接收方从不知道它们会在什么时候到达。例如计算机键盘与主机的通信。UART用于远距离传输较为适合。可以数据同时发送和接收。

1,异步传输是面向字符的传输,而同步传输是面向比特的传输。

                                                2,异步传输的单位是字符同步传输的单位是帧。
                                                3,异步传输通过字符起始和停止码抓住再同步的机会,而同步传输则是在数据中抽取同步信息。
                                                4,异步传输对时序的要求较低,同步传输往往通过特定的时钟线路协调时序。
                                                5,异步传输相对于同步传输效率较低。
我的理解是对时序要求是采用UART的原因,数据到达需要给出到达信号也解释了UART采用8位1字符的串行数据输出模式,也解释了协议中起始位与停止位的重要性。
uart指通用异步收发传输器,本质上是硬件,用来异步传输数据。RS232是一种物理层协议,规定了特定的接口标准。https://www.zhihu.com/question/22632011 
 
进行数据传输时,需要考虑时钟同步问题,传输速率有一个很重要的概念。波特率是衡量资料传送速率的指标。表示每秒钟传送的符号数(symbol)。一个符号代表的信息量(比特数)与符号的阶数有关。例如资料传送速率为120字符/秒,传输使用256阶符号,每个符号代表8bit,则波特率就是120baud,比特率是120*8=960bit/s。位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。字节Byte:8个二进制位为一个字节(B),最常用的单位。
常用波特率9600,19200,38400,115200等。
串口UART学习笔记(一)-LMLPHP
其中各位的意义如下: 
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。 
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。 
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)。 
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。

     二、UART串口通信一般包括部分
    任何程序都包括一个主控制程序,因为是双向通信,还需要串口发送程序,串口接收程序,时钟控制程序。举例来分别解释四个程序的代码。
      这里采用50MHz的系统时钟,产生UART时钟信号产生和发送的波特率为9600bps。为了保证采样的时候不会发生误码或者滑码,我们对于一位数据不只采用一个时钟周期进行数据采集,而采用16个时钟周期。那么就需要对时钟进行分频处理。50,000,000/(16*9600),分频系数取整为326 。偶数分频比较简单,此处采用了326,至于奇数分频以及如何做出占空比为50%以后整理。
 
 
 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: clkdiv
// 产生一个波特率9600的16倍频的时钟,9600*16= 153600
// 相当于50MHz的326分频,50000000/153600=326
//////////////////////////////////////////////////////////////////////////////////
module clkdiv(clk50, clkout);
input clk50; //系统时钟
output clkout; //采样时钟输出
reg clkout;
reg [:] cnt; //分频进程,对50Mhz的时钟326分频
always @(posedge clk50)
begin
if(cnt == 'd162)
begin
clkout <= 'b1;
cnt <= cnt + 'd1;
end
else if(cnt == 'd325)
begin
clkout <= 'b0;
cnt <= 'd0;
end
else
begin
cnt <= cnt + 'd1;
end
end
endmodule

分频比较简单,我写的另一个,

  `timescale 1ns / 1ps

  module clkdiv(clk50, clkout);
input clk50; //系统时钟
output clkout; //采样时钟输出
reg clkout;
reg [:] cnt; always @(posedge clk50)
begin
if (cnt < 'd162)
cnt <= cnt + 'b1;
else if (cnt == 'd162)
begin
clkout <= ~clkout;
cnt <= 'b0;
end
end

三、串口发送程序

UART采用异步传输,就涉及起始位与停止位,下面是代码例子,我的总结用红笔标出。

 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: uarttx
// 说明:16个clock发送一个bit,
//////////////////////////////////////////////////////////////////////////////////
module uarttx(clk, datain, wrsig, idle, tx);
input clk; //UART时钟
input [:] datain; //需要发送的数据
input wrsig; //发送命令,上升沿有效
output idle; //线路状态指示,高为线路忙,低为线路空闲
output tx; //发送数据信号
reg idle, tx;
reg send;
reg wrsigbuf, wrsigrise;
reg presult;
reg[:] cnt; //计数器
parameter paritymode = 'b0; //检测发送命令是否有效,判断wrsig的上升沿 //先检测发送命令是否有效,然后判断线路状态
always @(posedge clk)
begin
wrsigbuf <= wrsig;
wrsigrise <= (~wrsigbuf) & wrsig; //边沿检测,检测发送命令
end always @(posedge clk)
begin
if (wrsigrise && (~idle)) //当发送命令有效且线路为空闲时,启动新的数据发送进程
begin
send <= 'b1;
end
else if(cnt == 'd168) //一帧资料发送结束
begin
send <= 'b0;
end
end /////////////////////////////////////////////////////////////////////////
//使用168个时钟发送一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟// //停止位为8个时钟周期
////////////////////////////////////////////////////////////////////////
always @(posedge clk)
begin
if(send == 'b1) begin
case(cnt) //tx变低电平产生起始位,0~15个时钟为发送起始位
'd0: begin
tx <= 'b0;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd16: begin
tx <= datain[]; //发送数据位的低位bit0,占用第16~31个时钟
presult <= datain[]^paritymode; //奇偶校验位的获取
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd32: begin
tx <= datain[]; //发送数据位的第2位bit1,占用第47~32个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd48: begin
tx <= datain[]; //发送数据位的第3位bit2,占用第63~48个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd64: begin
tx <= datain[]; //发送数据位的第4位bit3,占用第79~64个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd80: begin
tx <= datain[]; //发送数据位的第5位bit4,占用第95~80个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd96: begin
tx <= datain[]; //发送数据位的第6位bit5,占用第111~96个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd112: begin
tx <= datain[]; //发送数据位的第7位bit6,占用第127~112个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd128: begin
tx <= datain[]; //发送数据位的第8位bit7,占用第143~128个时钟
presult <= datain[]^presult;
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd144: begin
tx <= presult; //发送奇偶校验位,占用第159~144个时钟 //将计算结果作为奇偶校验码输出
presult <= datain[]^paritymode; //无意思,此行计算结果无用,多余
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd160: begin
tx <= 'b1; //发送停止位,占用第160~167个时钟
idle <= 'b1;
cnt <= cnt + 'd1;
end
'd168: begin
tx <= 'b1;
idle <= 'b0; //一帧资料发送结束
cnt <= cnt + 'd1;
end
default: begin
cnt <= cnt + 'd1;
end
endcase
end
else begin
tx <= 'b1;
cnt <= 'd0;
idle <= 'b0;
end
end
endmodule

四、串口接收程序

成为UART接收信号,将一位一位的串口数据转化为并行数据。

 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module name uartrx.v
// 说明: 16个clock接收一个bit,16个时钟采样,取中间的采样值
//////////////////////////////////////////////////////////////////////////////////
module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);
input clk; //采样时钟
input rx; //UART数据输入
output dataout; //接收数据输出
output rdsig; //接收数据有效,高说明接收到一个字节 ,来区分数据处于接收状态或者接收控制信号
output dataerror; //数据出错指示
output frameerror; //帧出错指示 reg[:] dataout;
reg rdsig, dataerror;
reg frameerror;
reg [:] cnt;
reg rxbuf, rxfall, receive;
parameter paritymode = 'b0;
reg presult, idle; always @(posedge clk) //检测线路rx的下降沿, 线路空闲的时候rx为高电平
begin
rxbuf <= rx;
rxfall <= rxbuf & (~rx); //下降沿检测,检测是否接收到接收信号
end always @(posedge clk)
begin
if (rxfall && (~idle)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
begin
receive <= 'b1; //开始接收数据
end
else if(cnt == 'd168) //接收数据完成
begin
receive <= 'b0;
end
end /////////////////////////////////////////////////////////////////////////
//使用176个时钟接收一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟//
////////////////////////////////////////////////////////////////////////
always @(posedge clk)
begin
if(receive == 'b1)
begin
case (cnt)
'd0: //0~15个时钟为接收第一个比特,起始位
begin
idle <= 'b1;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd24: //16~31个时钟为第1个bit数据,取中间第24个时钟的采样值 //在发送程序中,第0位数据在16个时钟周期后开始传输,接收过程中,
begin //从第24个时钟周期开始接收第0位数据,保证信号被采集。
idle <= 'b1;
dataout[] <= rx;
presult <= paritymode^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd40: //47~32个时钟为第2个bit数据,取中间第40个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd56: //63~48个时钟为第3个bit数据,取中间第56个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd72: //79~64个时钟为第4个bit数据,取中间第72个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd88: //95~80个时钟为第5个bit数据,取中间第88个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd104: //111~96个时钟为第6个bit数据,取中间第104个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd120: //127~112个时钟为第7个bit数据,取中间第120个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b0;
end
'd136: //143~128个时钟为第8个bit数据,取中间第136个时钟的采样值
begin
idle <= 'b1;
dataout[] <= rx;
presult <= presult^rx;
cnt <= cnt + 'd1;
rdsig <= 'b1; //接收数据有效
end
'd152: //159~144个时钟为接收奇偶校验位,取中间第152个时钟的采样值
begin
idle <= 'b1;
if(presult == rx)
dataerror <= 'b0;
else
dataerror <= 'b1; //如果奇偶校验位不对,表示数据出错
cnt <= cnt + 'd1;
rdsig <= 'b1;
end
'd168: //160~175个时钟为接收停止位,取中间第168个时钟的采样值
begin
idle <= 'b1;
if('b1 == rx)
frameerror <= 'b0;
else
frameerror <= 'b1; //如果没有接收到停止位,表示帧出错
cnt <= cnt + 'd1;
rdsig <= 'b1;
end
default:
begin
cnt <= cnt + 'd1;
end
endcase
end
else
begin
cnt <= 'd0;
idle <= 'b0;
rdsig <= 'b0;
end
end
endmodule

五、控制程序

这里主要学习程序的调用,如何用总的控制程序完成UART数据传输。

 `timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: uart_test
//
//////////////////////////////////////////////////////////////////////////////////
module uart_test(clk50, rx, tx, reset);
input clk50;
input reset;
input rx;
output tx; wire clk; //clock for 9600 uart port
wire [:] txdata,rxdata; //串口发送数据和串口接收数据 //产生时钟的频率为16*9600
clkdiv u0 (
.clk50 (clk50), //50Mhz的晶振输入
.clkout (clk) //16倍波特率的时钟
); //串口接收程序
uartrx u1 (
.clk (clk), //16倍波特率的时钟
.rx (rx), //串口接收
.dataout (rxdata), //uart 接收到的数据,一个字节
.rdsig (rdsig), //uart 接收到数据有效
.dataerror (),
.frameerror ()
); //串口发送程序
uarttx u2 (
.clk (clk), //16倍波特率的时钟
.tx (tx), //串口发送
.datain (txdata), //uart 发送的数据
.wrsig (wrsig), //uart 发送的数据有效
.idle () ); endmodule

就像c语言里调用子函数一样,每个 模块是并行运行的, 各个模块连接完成整个系统需要一个顶层文件(top-module) 。 顶层文件 通过调用、连接低层模块的实例来实现复杂的功能。

学习UART通信,最主要还是理解异步和串口这两个东西,方便远距离传输,串口一位一位传输,牺牲了时间降低时序要求。

 
 
 
 
 
 
 
 
 
 
05-11 15:18