12_基于FPGA的串口通信
实验原理
FPGA和上位机电脑之间的通信采用UART串口通信方式,首先介绍下九针串口硬件的结构:
它一共有9个引脚,但是最重要的3个引脚是:
引脚2: RxD (接收数据).
引脚3: TxD (发送数据).
引脚5: GND (地).
仅使用3跟电缆,你就可以发送和接收数据.
串口的通信协议:
首先要说波特率的概念:含义是每秒传送的二进制位数。通信双方的波特率一定要相等,比如常用的波特率有4800bps,9600bps,19200bps,115200bps等。
数据格式:1位起始位+8位数据位+1位校验位(可选)+1位停止位;RS-232是使用异步通讯协议。也就是说数据的传输没有时钟信号。接收端必须有某种方式,使之与接收数据同步。
对于RS-232来说,是这样处理的:
串行线缆的两端事先约定好串行传输的参数(传输速度(既是波特率)、传输格式等)
当没有数据传输的时候,发送端向数据线上发送"1" ;每传输一个字节之前,发送端先发送一个"0"来表示传输已经开始。这样接收端便可以知道有数据到来了。开始传输后,数据以约定的速度和格式传输,所以接收端可以与之同步每次传输完成一个字节之后,都在其后发送一个停止位("1") 让我们来看看0x55是如何传输的:
0x55的二进制表示为:01010101。
但是由于先发送的是最低有效位,所以发送序列是这样的: 1-0-1-0-1-0-1-0。
实验设计原理(发送模块,接收模块,波特率发生模块的分别设计原理)
波特率产生模块:原理就是对时钟分频得到波特率时钟,本程序是对50MHZ时钟进行650分频可近似得到9600bps*8倍的时钟来控制发送和接收模块。
发送模块:在波特率时钟的驱动下用状态机把要发送的数据传送给上位机;
接收模块:在波特率时钟驱动下一旦收到起始位启动接收状态机接收上位机的数据并通过数码管显示。
硬件原理图
图中MAX232是电平转换芯片,因为RS232电平:采用-12V到-3V,等价于逻辑"0",+3V到+12V的逻辑电平,等价于逻辑"1",但是FPGA的电平属于TTL电平,因此通信双方不能直接通信,需经过电平转换。
实验代码
/********************************版权声明************************************** ** 大西瓜团队 ** **----------------------------文件信息-------------------------- ** 文件名称: uart.v ** 创建日期: ** 功能描述:串口通信__FPGA和上位机通信(波特率:9600bps,10个bit是1位起始位,8个数据位,1个结束) ** 操作过程:按动key2,FPGA向PC发送"da xi gua"一次,KEY1是复位按键。 ** 字符串(串口调试工具设成字符格式接受和发送方式),FPGA接受(0到9)后显示在7段数码管上。 ** 硬件平台:大西瓜第三代开发板,http://daxiguafpga.taobao.com ** 版权声明:本代码属个人知识产权,本代码仅供交流学习. **---------------------------修改文件的相关信息---------------- ** 修改人: ** 修改日期: ** 修改内容: *******************************************************************************/ module uart(clk,rst,rxd,txd,en,seg_data,key_input);
input clk,rst; input rxd; //串行数据接收端 input key_input; //按键输入
output[7:0] en; output[7:0] seg_data; reg[7:0] seg_data; output txd; //串行数据发送端 ////////////////////inner reg//////////////////// reg[15:0] div_reg; //分频计数器,分频值由波特率决定。分频后得到频率8倍波特率的时钟 reg[2:0] div8_tras_reg; //该寄存器的计数值对应发送时当前位于的时隙数 reg[2:0] div8_rec_reg; //该寄存器的计数值对应接收时当前位于的时隙数 reg[3:0] state_tras; //发送状态寄存器 reg[3:0] state_rec; //接受状态寄存器 reg clkbaud_tras; //以波特率为频率的发送使能信号 reg clkbaud_rec; //以波特率为频率的接受使能信号 reg clkbaud8x; //以8倍波特率为频率的时钟,它的作用是将发送或接受一个bit的时钟周期分为8个时隙
reg recstart; //开始发送标志 reg recstart_tmp;
reg trasstart; //开始接受标志
reg rxd_reg1; //接收寄存器1 reg rxd_reg2; //接收寄存器2,因为接收数据为异步信号,故用两级缓存 reg txd_reg; //发送寄存器 reg[7:0] rxd_buf; //接受数据缓存 reg[7:0] txd_buf; //发送数据缓存
reg[2:0] send_state; //这是发送状态寄存器 reg[19:0] cnt_delay; //延时去抖计数器 reg start_delaycnt; //开始延时计数标志 reg key_entry1,key_entry2; //确定有键按下标志
//////////////////////////////////////////////// parameter div_par=16'h145; //分频参数,其值由对应的波特率计算而得,按此参数分频的时钟频率是波倍特率的8 //倍,此处值对应9600的波特率,即分频出的时钟频率是9600*8 (CLK 50M)
//////////////////////////////////////////////// assign txd=txd_reg; //assign lowbit=0;
assign en=0; //7段数码管使能信号赋值
always@(posedge clk ) begin if(!rst) begin cnt_delay<=0; start_delaycnt<=0; end else if(start_delaycnt) begin if(cnt_delay!=20'd800000) begin cnt_delay<=cnt_delay+1'b1; end else begin cnt_delay<=0; start_delaycnt<=0; end end else begin if(!key_input&&cnt_delay==0) start_delaycnt<=1; end end
always@(posedge clk) begin if(!rst) key_entry1<=1'b0; else begin if(key_entry2) key_entry1<=1'b0; else if(cnt_delay==20'd800000) begin if(!key_input) key_entry1<=1'b1; end end end
always@(posedge clk ) begin if(!rst) div_reg<=0; else begin if(div_reg==div_par-1'b1) div_reg<=0; else div_reg<=div_reg+1'b1; end end
always@(posedge clk)//分频得到8倍波特率的时钟 begin if(!rst) clkbaud8x<=0; else if(div_reg==div_par-1) clkbaud8x<=~clkbaud8x; end
always@(posedge clkbaud8x or negedge rst) begin if(!rst) div8_rec_reg<=0; else if(recstart)//接收开始标志 div8_rec_reg<=div8_rec_reg+1'b1;//接收开始后,时隙数在8倍波特率的时钟下加1循环 end
always@(posedge clkbaud8x or negedge rst) begin if(!rst) div8_tras_reg<=0; else if(trasstart) div8_tras_reg<=div8_tras_reg+1'b1;//发送开始后,时隙数在8倍波特率的时钟下加1循环 end
always@(div8_rec_reg) begin if(div8_rec_reg==7) clkbaud_rec=1;//在第7个时隙,接收使能信号有效,将数据打入 else clkbaud_rec=0; end
always@(div8_tras_reg) begin if(div8_tras_reg==7) clkbaud_tras=1;//在第7个时隙,发送使能信号有效,将数据发出 else clkbaud_tras=0; end
always@(posedge clkbaud8x or negedge rst) begin if(!rst) begin txd_reg<=1; trasstart<=0; txd_buf<=0; state_tras<=0; send_state<=0; key_entry2<=0; end else begin if(!key_entry2) begin if(key_entry1) begin key_entry2<=1; txd_buf<=8'd68; //"D" end end else begin case(state_tras) 4'b0000: begin //发送起始位 if(!trasstart&&send_state<7) trasstart<=1; else if(send_state<7) begin if(clkbaud_tras) begin txd_reg<=0; state_tras<=state_tras+1'b1; end end else begin key_entry2<=0; state_tras<=0; end end 4'b0001: begin //发送第1位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0010: begin //发送第2位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0011: begin //发送第3位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0100: begin //发送第4位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0101: begin //发送第5位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0110: begin //发送第6位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b0111: begin //发送第7位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b1000: begin //发送第8位 if(clkbaud_tras) begin txd_reg<=txd_buf[0]; txd_buf[6:0]<=txd_buf[7:1]; state_tras<=state_tras+1'b1; end end 4'b1001: begin //发送停止位 if(clkbaud_tras) begin txd_reg<=1; txd_buf<=8'h55; state_tras<=state_tras+1'b1; end end 4'b1111:begin if(clkbaud_tras) begin state_tras<=state_tras+1'b1; send_state<=send_state+1'b1; trasstart<=0; case(send_state) 3'b000: txd_buf<=8'd97;//"a" 3'b001: txd_buf<=8'd120;//"x" 3'b010: txd_buf<=8'd105;//"i" 3'b011: txd_buf<=8'd103;//"g" 3'b100: txd_buf<=8'd117;//"u" 3'b101: txd_buf<=8'd97;//"a" default: txd_buf<=0; endcase end end default: begin if(clkbaud_tras) begin state_tras<=state_tras+1'b1; trasstart<=1; end end endcase end end end
always@(posedge clkbaud8x or negedge rst)//接受PC机的数据 begin if(!rst) begin rxd_reg1<=0; rxd_reg2<=0; rxd_buf<=0; state_rec<=0; recstart<=0; recstart_tmp<=0; end else begin rxd_reg1<=rxd; rxd_reg2<=rxd_reg1; if(state_rec==0) begin if(recstart_tmp==1) begin recstart<=1; recstart_tmp<=0; state_rec<=state_rec+1'b1; end else if(!rxd_reg1&&rxd_reg2) //检测到起始位的下降沿,进入接受状态 recstart_tmp<=1; end else if(state_rec>=1&&state_rec<=8) begin if(clkbaud_rec) begin rxd_buf[7]<=rxd_reg2; rxd_buf[6:0]<=rxd_buf[7:1]; state_rec<=state_rec+1'b1; end end else if(state_rec==9) begin if(clkbaud_rec) begin state_rec<=0; recstart<=0; end end end end
always@(rxd_buf) //将接受的数据用数码管显示出来 begin case (rxd_buf) 8'h30: seg_data=8'b11000000; 8'h31: seg_data=8'b11111001; 8'h32: seg_data=8'b10100100; 8'h33: seg_data=8'b10110000; 8'h34: seg_data=8'b10011001; 8'h35: seg_data=8'b10010010; 8'h36: seg_data=8'b10000010; 8'h37: seg_data=8'b11111000; 8'h38: seg_data=8'b10000000; 8'h39: seg_data=8'b10010000; 8'h41: seg_data=8'b10001000; 8'h42: seg_data=8'b10000011; 8'h43: seg_data=8'b11000110; 8'h44: seg_data=8'b10100001; 8'h45: seg_data=8'b10000110; 8'h46: seg_data=8'b10001110; default: seg_data=8'b11111111; endcase end
endmodule |
实验操作
实验效果
大西瓜FPGA-->https://daxiguafpga.taobao.com
配套开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-24211932856.3.489d7241aCjspB&id=633897209972
博客资料、代码、图片、文字等属大西瓜FPGA所有,切勿用于商业! 若引用资料、代码、图片、文字等等请注明出处,谢谢!
每日推送不同科技解读,原创深耕解读当下科技,敬请关注微信公众号"科乎"。