正点原子FPGA学习笔记4.3——UART串口通信,基于达芬奇开发板 A7

目录

1.编写UART接收模块

1.先定义部分端口 + 捕捉rxd下降沿确定start_flag

2.1做好rx_flag——通过start_flag决定rx_flag,rx_flag要保持到第9位(停止位拉高半个波特率周期)才拉低——整个接收状态都是在rx_flag有效时才执行,因此下面所有操作都要首先判定rx_flag!

2.2然后实现两个计数器:clk_cnt + rx_cnt,clk_cnt是根据rx_flag拉高才开始计数

​编辑

2.3 实现rx_cnt——rx_flag拉高情况下,clk_cnt每数到434个时钟周期,rx_cnt + 1

3.赋值操作——uart_rxd赋值给uart_data

3.1在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据,防止数据丢失或者出现一些其他的亚稳态现象!

3.2最后得到输出uart_data——定义output uart_data,以及完成信号uart_done

 2. 编写UART发送模块——发送的对称结构

0.发送模块说明+先定义部分端口

1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag

2.做好tx_flag,并将数据保存到临时寄存器tx_data

3.1然后实现两个计数器:clk_cnt + tx_cnt——类似发送模块

3.2实现tx_cnt    

4.最后:将寄存器中的数据,一位一位送到UART发送端口,并转串

5.额外的:发送忙信号uart_tx_busy

3.至此,接收、发送模块完成

1.接收代码

2.发送代码


任务时序图:

1.编写UART接收模块

1.先定义部分端口 + 捕捉rxd下降沿确定start_flag

2.1做好rx_flag——通过start_flag决定rx_flag,rx_flag要保持到第9位(停止位拉高半个波特率周期)才拉低——整个接收状态都是在rx_flag有效时才执行,因此下面所有操作都要首先判定rx_flag!

其实CLK_cnt也应该位宽尽量的大,因为不同的波特率,就可能不止434了(暂时没动)


//为了修改波特率后,代码不用改动,额外定义parameter常量!

 //如果rx_flag未到9/未拉高0.5个波特率,就让rx_flag保持住

2.2然后实现两个计数器:clk_cnt + rx_cnt,clk_cnt是根据rx_flag拉高才开始计数

        CLK_cnt计数到434个时钟时,就清零

2.3 实现rx_cnt——rx_flag拉高情况下,clk_cnt每数到434个时钟周期,rx_cnt + 1

3.赋值操作——uart_rxd赋值给uart_data

3.1在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据,防止数据丢失或者出现一些其他的亚稳态现象!

因为如果不保存就直接输出,那接收模块的意义在哪?

//判断rx_cnt,去clk_cnt正中间,进行赋值

 

//异步通信,为了防止亚稳态,(因为不同时钟域)采用寄存两拍的值作为有效复制更好

异步通信延迟两拍: http://t.csdn.cn/iR8jJ

3.2最后得到输出uart_data——定义output uart_data,以及完成信号uart_done

临时寄存器不能作为真正的输出

最后将输出信号——uart_done以及uart_data完成

当到达停止位,说明寄存器中有一帧数据了,uart_done拉高 以及 uart_data输出!

 2. 编写UART发送模块——发送的对称结构

0.发送模块说明+先定义部分端口

端口:时钟、复位、uart_txd、uart_en(开始发送的指令)、uart_tx_busy(告诉环回模块此时的状态,同时连接到上位机,告诉外界在发送数据,此时不能接收数据)

 input                uart_en,    //环回模块的输出,告诉发送模块要发数据了

1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag

2.做好tx_flag,并将数据保存到临时寄存器tx_data

同样需要临时寄存器tx_data(对应接收端的rx_data),保存数据输入uart_din,防止数据丢失或者出现一些其他的亚稳态现象!

 根据上面的问题,所以我们又要定义tx_cnt,在tx_cnt计数到9的时候(并持续大于半个波特率周期(15/16个)拉低)——因为上位机时钟存在不稳定、数据线传输到上位机存在延迟等等原因

 

3.1然后实现两个计数器:clk_cnt + tx_cnt——类似发送模块

都是在tx_flag拉高的前提下,才能进行计数

3.2实现tx_cnt    

4.最后:将寄存器中的数据,一位一位送到UART发送端口,并转串

 //UART发送端口一般保持拉高,等起始位来就拉低,给上位机接收提醒
//发送必须从4'd0开始,形成完整的一个数据帧,要给接收端提示  //没有发送数据,uart_txd拉高

5.额外的:发送忙信号uart_tx_busy

发送忙信号uart_tx_busy直接用tx_flag即可,用于告诉环回模块正发送忙!

3.至此,接收、发送模块完成

1.接收代码

module	uart_recv(
	input				sys_clk,
	input				sys_rst_n,
	
	input				uart_rxd,
	output reg [7:0]	uart_data,		//因为要在always语句块中赋值reg
	output reg			uart_done
	
);


//增加常量
parameter	CLK_FREQ = 5000_0000;			//时钟频率常量
parameter	UART_BPS = 115200;				//波特率常量
localparam	BPS_cnt	 = CLK_FREQ/UART_BPS;	//局部参数,不能作为参数传递,BPS_cnt持续一个波特率周期(434)


//1.抓取uart_rxd的下降沿——得到start_flag
reg		uart_rxd_d0;
reg		uart_rxd_d1;
wire	start_flag;

assign	start_flag = ~uart_rxd_d0 & uart_rxd_d1;

always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		uart_rxd_d0 <= 1'b0;
		uart_rxd_d1 <= 1'b0;
	end
	else begin
		//双寄存器锁存,且d0 d1永远差一个时钟
		uart_rxd_d0 <= uart_rxd;
		uart_rxd_d1 <= uart_rxd_d0;
	end
end


//2.1做好rx_flag,
//2.2然后实现两个计数器:clk_cnt + rx_cnt
reg			rx_flag;
reg[3:0]	rx_cnt;		//数到9,位宽为4即可
reg[8:0]	clk_cnt;	//数到433<512对应位宽9位		

//2.1 做好rx_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		rx_flag <= 1'b0;
	end
	else begin
		if (start_flag)
			rx_flag <= 1'b1;
		//补一个拉低:在rx_cnt = 第9位数据时拉低
		//而且拉低的时间是在第九位持续:0.5个波特率周期后(434*0.5=217)
		//而且为了修改波特率后,代码不用改动,额外定义parameter常量!
		else if ((rx_cnt == 9) && clk_cnt == BPS_cnt/2)
			rx_flag <= 1'b0;
		else
			//如果rx_flag未到9/未拉高0.5个波特率,就让rx_flag保持住
			rx_flag <= rx_flag;	
	end
end


//2.2然后实现两个计数器:clk_cnt + rx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		clk_cnt <= 9'd0;
	//clk_cnt是根据rx_flag拉高才开始计数
	else if (rx_flag) begin
		if (clk_cnt < BPS_cnt - 1)	//434 - 1
			clk_cnt <= clk_cnt + 1'b1;
		else
			clk_cnt <= 9'd0;
	end
	//rx_flag没有拉高
	else
		clk_cnt <= 9'd0;
end		
			
			
//2.3 实现rx_cnt		
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		rx_cnt <= 4'b0;
	//首先rx_flag需要拉高
	else if (rx_flag) begin
		//其次clk_cnt=434个时钟周期,rx_cnt才+1
		if (clk_cnt == BPS_cnt - 1)
			rx_cnt <= rx_cnt + 1'b1;
		//没到434就保持
		else
			rx_cnt <= rx_cnt;
	end
	//最后如果没有rx_flag,清零等待状态
	else
		rx_cnt <= 4'b0;
end
	
			
//3.赋值操作——uart_rxd赋值给uart_data
//在真正赋值给uart_data输出前,需要定义一个临时的寄存器rx_data——保存接收的数据
//因为如果不保存就直接输出,那接收模块的意义在哪?
reg[7:0]	rx_data;	//和输出uart_data位宽一致

always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		rx_data <= 8'b0;
	else if (rx_flag) begin
		//判断rx_cnt,去clk_cnt正中间,进行赋值
		if (clk_cnt == BPS_cnt/2) begin
			case (rx_cnt)
				//异步通信,为了防止亚稳态,(因为不同时钟域)采用寄存两拍的值作为有效复制更好
				//从4'd1开始,就是从bit0——有效数据开始
				4'd1:	rx_data[0] <= uart_rxd_d1;
				4'd2:	rx_data[1] <= uart_rxd_d1;
				4'd3:	rx_data[2] <= uart_rxd_d1;
				4'd4:   rx_data[3] <= uart_rxd_d1;
				4'd5:	rx_data[4] <= uart_rxd_d1;
				4'd6:   rx_data[5] <= uart_rxd_d1;
				4'd7:	rx_data[6] <= uart_rxd_d1;
				4'd8:   rx_data[7] <= uart_rxd_d1;
				default:;
			endcase	
		end
		else 
			rx_data <= rx_data; //时钟计数器没到217时,寄存器数据不变
	end
	else
		rx_data <= 8'b0; //rx_flag没有拉高
end
 

//3.2最后将输出信号——uart_done以及uart_data完成
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		uart_data <= 8'b0;
		uart_done <= 1'b0;
	end
	else if (rx_cnt == 4'd9) begin
		uart_data <= rx_data;
		uart_done <= 1'b1; 
	end
	else begin
		uart_data <= 8'b0;
		uart_done <= 1'b0;
	end
end


endmodule

2.发送代码

module uart_send(
	input				sys_clk,
	input				sys_rst_n,

	input				uart_en,		//环回模块的输出,告诉发送模块要发数据了
	input[7:0]			uart_din,		//环回模块传过来的数据
	
	output reg			uart_txd,		//发送数据,和上位机通信(且并串转换)
	output 	  			uart_tx_busy	//告诉环回模块此时正在忙——发送数据
);

//增加常量
parameter	CLK_FREQ = 5000_0000;			//时钟频率常量
parameter	UART_BPS = 115200;				//波特率常量
localparam	BPS_cnt	 = CLK_FREQ/UART_BPS;	//局部参数,不能作为参数传递,BPS_cnt持续一个波特率周期(434)


//1.捕捉回环模块传过来使能信号en的上升沿——得到en_flag
reg		uart_en_d0;
reg		uart_en_d1;
wire	en_flag;

assign	en_flag = uart_en_d0 & ~uart_en_d1;

always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		uart_en_d0 <= 1'b0;
		uart_en_d1 <= 1'b0;
	end
	else begin
		//双寄存器锁存,且d0 d1永远差一个时钟
		uart_en_d0 <= uart_en;
		uart_en_d1 <= uart_en_d0;
	end
end


//2.1做好tx_flag,
//2.2然后实现两个计数器:clk_cnt + rx_cnt
reg			tx_flag;
reg[3:0]	tx_cnt;		//数到9,位宽为4即可
reg[8:0]	clk_cnt;	//数到433<512对应位宽9位	
	
//同样需要临时寄存器tx_data,保存数据输入uart_din
//防止数据丢失或者出现一些其他的亚稳态现象
reg[7:0]	tx_data;

//2.1 做好tx_flag,寄存数据tx_data
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n) begin
		tx_flag <= 1'b0;
		tx_data <= 8'b0;
	end
	else if (en_flag) begin
		tx_flag <= 1'b1;
		tx_data <= uart_din;
	end
	//定义tx_cnt,在tx_cnt计数到9的时候(并持续大于半个波特率周期(15/16个)拉低)
	//同时数据清零——因为数据
	else if ((tx_cnt == 4'd9) && clk_cnt == (BPS_cnt - BPS_cnt/16)) begin
		tx_flag <= 1'b0;
		tx_data <= 8'b0;
	end
	//其他情况——包括没到9就保持原状态
	else begin
		tx_flag <= tx_flag;
		tx_data <= tx_data;	//数据保持
	end
end


//3.1然后实现两个计数器:clk_cnt + tx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		clk_cnt <= 9'd0;
	//clk_cnt是根据tx_cnt拉高才开始计数
	else if (tx_flag) begin
		if (clk_cnt < BPS_cnt - 1)	//434 - 1
			clk_cnt <= clk_cnt + 1'b1;
		else
			clk_cnt <= 9'd0;
	end
	//tx_cnt没有拉高
	else
		clk_cnt <= 9'd0;
end		
			
			
//3.2实现tx_cnt		
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		tx_cnt <= 4'b0;
	//首先rx_flag需要拉高
	else if (tx_flag) begin
		//其次clk_cnt=434个时钟周期,rx_cnt才+1
		if (clk_cnt == BPS_cnt - 1)
			tx_cnt <= tx_cnt + 1'b1;
		//没到434就保持
		else
			tx_cnt <= tx_cnt;
	end
	//最后如果没有rx_flag,清零等待状态
	else
		tx_cnt <= 4'b0;
end


//4.最后一步:将寄存器中的数据,一位一位送到UART发送端口,并转串
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (!sys_rst_n)
		uart_txd <= 1'b1;	//UART发送端口一般保持拉高,等起始位来就拉低,给上位机接收提醒
	else (tx_flag) begin
		case (tx_cnt)
			//发送必须从4'd0开始,形成完整的一个数据帧,要给接收端提升
			4'd0:	uart_txd <= 1'b0;	//起始位——形成下降沿
			4'd1:	uart_txd <= tx_data[0];
			4'd2:	uart_txd <= tx_data[1];
			4'd3:	uart_txd <= tx_data[2];
			4'd4:   uart_txd <= tx_data[3];
			4'd5:	uart_txd <= tx_data[4];
			4'd6:   uart_txd <= tx_data[5];
			4'd7:	uart_txd <= tx_data[6];
			4'd8:   uart_txd <= tx_data[7];
			4'd9:	uart_txd <= 1'b1;	//停止位——拉高
			default: ;
		endcase
	end
	else
		//没有发送数据,uart_txd拉高
		uart_txd <= 1'b1;
end


//额外的,发送忙信号uart_tx_busy直接用tx_flag即可,用于告诉环回模块正发送忙!
assign	uart_tx_busy = tx_flag;


endmodule
  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
你好!感谢提问。下面是有关 K210 学习笔记五中串口通信的一些内容: 在 K210 开发板上,串口是一种常见的通信接口,用于将数据从一个设备传输到另一个设备。串口通信通常使用 UART (Universal Asynchronous Receiver/Transmitter) 协议。 要在 K210 上进行串口通信,首先需要使用两个引脚:一个用于接收数据(RX),一个用于发送数据(TX)。这些引脚可以通过 GPIO 端口与外部设备连接。 在 K210 的开发环境中,可以使用 MaixPy 或者 Kendryte Standalone SDK 进行串口编程。 在 MaixPy 中,可以使用 `uart` 模块来进行串口通信。例如,要初始化一个串口对象并设置波特率为 115200,可以使用以下代码: ```python from machine import UART uart = UART(UART.UART1, 115200) ``` 然后,可以使用 `uart.write()` 函数发送数据,使用 `uart.read()` 函数接收数据。例如: ```python uart.write("Hello, world!\n") data = uart.read(10) ``` 在 Kendryte Standalone SDK 中,可以使用 `uart.h` 头文件中的函数来进行串口通信。例如,要初始化一个串口对象并设置波特率为 115200,可以使用以下代码: ```c #include "uart.h" uart_init(UART_DEVICE_1, 115200); ``` 然后,可以使用 `uart_send_data()` 函数发送数据,使用 `uart_receive_data()` 函数接收数据。例如: ```c uart_send_data(UART_DEVICE_1, "Hello, world!\n", 14); char buffer[10]; uart_receive_data(UART_DEVICE_1, buffer, 10); ``` 以上是关于 K210 学习笔记五中串口通信的简要介绍。如果你有更具体的问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Sean--Lu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值