【FPGA——Cyclone Ⅳ学习笔记】九.串口收发实验(EP4CE6F17C8)

一.原理图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二.串口收发

1.串口收发流程图

在这里插入图片描述
串口的波特率是指每秒收/发的bit数,此代码设为115200bit/s。因此要将50MHz的系统时钟进行适当的分频。如上图,每计到CYCLE个时钟脉冲就传输一个位。因此CYCLE=50_000_000/115200。

下面的代码是对黑金的代码进行了一些更改和注释。

2.uart_rx.v

接收数据状态图:
在这里插入图片描述
串口接收模块

module uart_rx
#(
	parameter CLK_FRE = 50,      				   //系统时钟频率(Mhz)
	parameter BAUD_RATE = 115200 				   //串口波特率,bit/s
)
(
	input                        clk,              //系统时钟输入
	input                        rst_n,            //复位键
	output reg[7:0]              rx_data,          //串口接收到的数据输出
	output reg                   rx_data_valid,    //一个字节接收完成标志
	input                        rx_data_ready,    //准备接收数据标志
	input                        rx_pin            //接收引脚,空闲时保持高电平
);

//计算符合波特率的时钟频率
localparam                       CYCLE = CLK_FRE * 1000000 / BAUD_RATE;	

localparam                       S_IDLE      = 1;
localparam                       S_START     = 2;  //开始状态
localparam                       S_REC_BYTE  = 3;  //接收数据状态
localparam                       S_STOP      = 4;  //停止状态
localparam                       S_DATA      = 5;

reg[2:0]                         state;
reg[2:0]                         next_state;
reg                              rx_d0;            //delay 1 clock for rx_pin
reg                              rx_d1;            //delay 1 clock for rx_d0
wire                             rx_negedge;       //接收引脚下边沿判断
reg[7:0]                         rx_bits;          //暂存接收到的数据
reg[15:0]                        cycle_cnt;        //波特率计数器
reg[2:0]                         bit_cnt;          //位计数器

assign rx_negedge = rx_d1 && ~rx_d0;	//如果为1,说明接收到低电平的开始位

always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
	begin
		rx_d0 <= 1'b0;
		rx_d1 <= 1'b0;	
	end
	else
	begin
		rx_d0 <= rx_pin;	//非阻塞赋值
		rx_d1 <= rx_d0;
	end
end

//接收数据状态机
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		state <= S_IDLE;
	else
		state <= next_state;
end

always@(*)
begin
	case(state)
		S_IDLE:
			if(rx_negedge)					//有低电平信号,开始接收
				next_state <= S_START;
			else
				next_state <= S_IDLE;
		S_START:
			if(cycle_cnt == CYCLE - 1)		//等待一个CYCLE周期
				next_state <= S_REC_BYTE;
			else
				next_state <= S_START;
		S_REC_BYTE:					//开始接收
			if(cycle_cnt == CYCLE - 1  && bit_cnt == 3'd7)  //接收到8位数据后进入停止状态
				next_state <= S_STOP;
			else
				next_state <= S_REC_BYTE;
		S_STOP:
			if(cycle_cnt == CYCLE/2 - 1)	//等待半个CYCLE周期,不能太长,防止错过下一次数据接收
				next_state <= S_DATA;
			else
				next_state <= S_STOP;
		S_DATA:
			if(rx_data_ready)    //数据接收结束
				next_state <= S_IDLE;
			else
				next_state <= S_DATA;
		default:
			next_state <= S_IDLE;
	endcase
end

always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		rx_data_valid <= 1'b0;
	else if(state == S_STOP && next_state != state)	//可以正常进入S_DATA状态说明接收数据有效
		rx_data_valid <= 1'b1;	//发出接收有效信号
	else if(state == S_DATA && rx_data_ready)
		rx_data_valid <= 1'b0;	//接收有效信号已发出,可以复位
end

always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		rx_data <= 8'd0;
	else if(state == S_STOP && next_state != state)	//可以正常进入S_DATA状态说明接收数据有效
		rx_data <= rx_bits;	//输出接收到的数据
end

//波特率相关计数
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		cycle_cnt <= 16'd0;
	else if((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)	
		cycle_cnt <= 16'd0;		//计完一个CYCLE周期或者状态改变则计数复位
	else
		cycle_cnt <= cycle_cnt + 16'd1;	
end

//换下一位接收
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		begin
			bit_cnt <= 3'd0;
		end
	else if(state == S_REC_BYTE)
		if(cycle_cnt == CYCLE - 1)	//正好一个CYCLE周期
			bit_cnt <= bit_cnt + 3'd1;	//位数加1(因为先接收数据的低位)
		else
			bit_cnt <= bit_cnt;
	else
		bit_cnt <= 3'd0;
end

//接收数据
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		rx_bits <= 8'd0;
	else if(state == S_REC_BYTE && cycle_cnt == CYCLE/2 - 1)	//处于接收状态且正好半个CYCLE周期
		rx_bits[bit_cnt] <= rx_pin;	//接收一位数据
	else
		rx_bits <= rx_bits; 
end
endmodule 

3.uart_tx.v

发送与接收类似(更简单一点),此不过多说明。

module uart_tx
#(
	parameter CLK_FRE = 50,      //系统时钟频率(MHz)
	parameter BAUD_RATE = 115200 //波特率
)
(
	input                        clk,              //系统时钟输入
	input                        rst_n,            //复位
	input[7:0]                   tx_data,          //串口要发送数据
	input                        tx_data_ready,    //准备发送数据标志
	output reg                   tx_data_valid,    //一个字节发送完成标志
	output                       tx_pin            //发送引脚
);
localparam                       CYCLE = CLK_FRE * 1000000 / BAUD_RATE;

localparam                       S_IDLE       = 1;
localparam                       S_START      = 2;//开始状态
localparam                       S_SEND_BYTE  = 3;//发送字节数据状态
localparam                       S_STOP       = 4;//停止状态

reg[2:0]                         state;
reg[2:0]                         next_state;
reg[15:0]                        cycle_cnt; 		 //cycle循环计数器(与波特率有关)
reg[2:0]                         bit_cnt;			 //当前发送的位号
reg[7:0]                         tx_data_latch;  //暂存要发送的数据
reg                              tx_reg; 			 //暂存当前要发送的位

assign tx_pin = tx_reg;	//按位发送数据

//发送数据状态机
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		state <= S_IDLE;
	else
		state <= next_state;
end

always@(*)
begin
	case(state)
		S_IDLE:
			if(tx_data_ready == 1'b1)
				next_state <= S_START;
			else
				next_state <= S_IDLE;
		S_START:
			if(cycle_cnt == CYCLE - 1)
				next_state <= S_SEND_BYTE;
			else
				next_state <= S_START;
		S_SEND_BYTE:
			if(cycle_cnt == CYCLE - 1  && bit_cnt == 3'd7)
				next_state <= S_STOP;
			else
				next_state <= S_SEND_BYTE;
		S_STOP:
			if(cycle_cnt == CYCLE - 1)
				next_state <= S_IDLE;
			else
				next_state <= S_STOP;
		default:
			next_state <= S_IDLE;
	endcase
end

//一个字节发送完成信号
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		begin
			tx_data_valid <= 1'b0;
		end
	else if(state == S_IDLE)
		if(tx_data_ready == 1'b1)
			tx_data_valid <= 1'b0;
		else
			tx_data_valid <= 1'b1;
	else if(state == S_STOP && cycle_cnt == CYCLE - 1)	//一个字节发送结束
			tx_data_valid <= 1'b1;	//给出发送有效信号
end

//把要发送的字节存入寄存器等待发送
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		begin
			tx_data_latch <= 8'd0;
		end
	else if(state == S_IDLE && tx_data_ready == 1'b1)
			tx_data_latch <= tx_data;
		
end

//切换到下一个要发送的位号
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		begin
			bit_cnt <= 3'd0;
		end
	else if(state == S_SEND_BYTE)
		if(cycle_cnt == CYCLE - 1)		//一个CYCLE周期结束(即一个位发送完)
			bit_cnt <= bit_cnt + 3'd1;	
		else
			bit_cnt <= bit_cnt;
	else
		bit_cnt <= 3'd0;
end

//波特率相关计数
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		cycle_cnt <= 16'd0;
	else if((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)
		cycle_cnt <= 16'd0;	//计完一个CYCLE周期或者状态改变则计数复位
	else
		cycle_cnt <= cycle_cnt + 16'd1;	
end

//给出当前要发送的位数据
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
		tx_reg <= 1'b1;
	else
		case(state)
			S_IDLE,S_STOP:
				tx_reg <= 1'b1; 	//发送高电平结束位
			S_START:
				tx_reg <= 1'b0; 	//发送低电平起始位
			S_SEND_BYTE:
				tx_reg <= tx_data_latch[bit_cnt];
			default:
				tx_reg <= 1'b1; 
		endcase
end

endmodule 

三.主模块

此实验效果是循环发送“HELLO ALINX"。同时,如果接收到上位机发送的数据,即立刻把接收到的数据再发送出去。

module uart_test(
	input                        clk,
	input                        rst_n,
	
	input                        uart_rx,
	output                       uart_tx
);

parameter                        CLK_FRE = 50;	//Mhz
localparam                       IDLE =  0;
localparam                       SEND =  1;   	//发送HELLO ALINX\r\n状态
localparam                       WAIT =  2;   	//等待1s然后发送接收到的数据状态

reg[7:0]                         tx_data;			//暂存要发送的字节数据
reg[7:0]                         tx_str;			
reg                              tx_data_ready; //准备发送数据标志
wire                             tx_data_valid;	//一个字节发送完成标志
reg[7:0]                         tx_cnt;
wire[7:0]                        rx_data;
wire                             rx_data_valid;	//一个字节接收完成标志
wire                             rx_data_ready;	//准备接收数据标志
reg[31:0]                        wait_cnt;
reg[3:0]                         state;

assign rx_data_ready = 1'b1;	//接收模式一直保持打开,
										//如果数据正在发送,则丢弃接收到的数据

//
always@(posedge clk or negedge rst_n)
begin
	if(rst_n == 1'b0)
	begin
		wait_cnt <= 32'd0;
		tx_data <= 8'd0;
		state <= IDLE;
		tx_cnt <= 8'd0;
		tx_data_ready <= 1'b0;
	end
	else
	case(state)
		IDLE:
			state <= SEND;
		SEND:
		begin
			wait_cnt <= 32'd0;
			tx_data <= tx_str;

			if(tx_data_ready == 1'b1 && tx_data_valid == 1'b1 && tx_cnt < 8'd12)//处于发送状态且未发送完
			begin
				tx_cnt <= tx_cnt + 8'd1; //发送的字节计数
			end
			else if(tx_data_ready && tx_data_valid)	//最后一字节发送完
			begin
				tx_cnt <= 8'd0;
				tx_data_ready <= 1'b0;
				state <= WAIT;
			end
			else if(~tx_data_ready)	//前一次发送完清零后再一次打开,进行新一轮发送
			begin
				tx_data_ready <= 1'b1;
			end
		end
		WAIT:
		begin
			wait_cnt <= wait_cnt + 32'd1;

			if(rx_data_valid == 1'b1)	//如果接收到数据则打开发送
			begin
				tx_data_ready <= 1'b1;
				tx_data <= rx_data;   // 发送串口刚接收到的数据
			end
			else if(tx_data_ready && tx_data_valid)
			begin
				tx_data_ready <= 1'b0;
			end
			else if(wait_cnt >= CLK_FRE * 1000000) // 等待一秒再进入下一次发送(1s内均可接收数据)
				state <= SEND;
		end
		default:
			state <= IDLE;
	endcase
end

//需要发送的数据
always@(*)
begin
	case(tx_cnt)	//依据发送字节计数器切换下一个发送的字节
		8'd0 :  tx_str <= "H";
		8'd1 :  tx_str <= "E";
		8'd2 :  tx_str <= "L";
		8'd3 :  tx_str <= "L";
		8'd4 :  tx_str <= "O";
		8'd5 :  tx_str <= " ";
		8'd6 :  tx_str <= "A";
		8'd7 :  tx_str <= "L";
		8'd8 :  tx_str <= "I";
		8'd9 :  tx_str <= "N";
		8'd10:  tx_str <= "X";
		8'd11:  tx_str <= "\r";
		8'd12:  tx_str <= "\n";
		default:tx_str <= 8'd0;
	endcase
end

//实例化接收模块
uart_rx#
(
	.CLK_FRE(CLK_FRE),
	.BAUD_RATE(115200)
) uart_rx_inst
(
	.clk                        (clk                      ),
	.rst_n                      (rst_n                    ),
	.rx_data                    (rx_data                  ),
	.rx_data_valid              (rx_data_valid            ),
	.rx_data_ready              (rx_data_ready            ),
	.rx_pin                     (uart_rx                  )
);

//实例化发送模块
uart_tx#
(
	.CLK_FRE(CLK_FRE),
	.BAUD_RATE(115200)
) uart_tx_inst
(
	.clk                        (clk                      ),
	.rst_n                      (rst_n                    ),
	.tx_data                    (tx_data                  ),
	.tx_data_ready              (tx_data_ready            ),
	.tx_data_valid              (tx_data_valid            ),
	.tx_pin                     (uart_tx                  )
);
endmodule
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默默无闻小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值