串口RS232

1. 串口简介

通用异步收发传输器,英文全称Universal Asynchronous Receiver/Transmitter,简称UART
UART
UART是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的数据转换成并行数据
UART是异步串行通信口,SPI是同步的,双方约定好使用频率一致的时钟,主机发送时钟
UART中,数据的收发都有独立的端口,所以它可以实现全双工通信

RS232传输距离比较近,传输速率也比较慢,数据线只有两条,可以节省IO口

2. 串口RS232接口

是UART串口的一种,没有时钟线只有两个数据线
在这里插入图片描述
在这里插入图片描述
注意:两个设备间的TXD和RXD应交叉相连

串口数据的发送和接收都是基于帧结构的,起始位+数据位+停止位,共10bit,空闲状态下rx和tx都保持高电平
在这里插入图片描述
波特率:来波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,其单位为波特(Baud),Bps

比特率:比特率是每秒传输的比特数。单位为比特(bps位/秒)

波特率源与比特率的关系为:比特率=波特率 * 单个调制状态对应的二进制位数

串口常用波特率有4800、9600、115200
9600Bps的串口比特率就是9600*1bps,串口发送或接收1bit数据的时间称为1个波特,也就是1/9600s。clk = 50MHz,T = 20ns,9600Bps下,传输一个波特需要的时钟个数,cnt = (1 * 10^9)ns / 9600 / 20ns 约等于5208,相当于每个bit的传输间隔需要50MHz时钟下的5208个clk

3. 代码实现

实现50MHz下9600波特率传输

波特率为9600Baud
一秒传输9600个波特
每个波特10bit数据,1bit起始位 + 8bit数据位 + 1bit结束位

每个码元传输需要的clk数 = ((1/9600)*10^9ns) / 20ns≈ 5208
串口传输,每个码元也就是一个bit,也可以理解为传递每个bit需要的clk数

在这里插入图片描述

UART接收模块

将上位机发送过来的串行数据进行接收,转换成并行数据

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

module uart_rx 
#(
	parameter	UART_BPS	=	'd9600			,
	parameter	CLK_FREQ	=	'd50_000_000	
)
(
	input	wire			sys_clk		,
	input	wire			sys_rst_n	,
	input	wire			rx			,
	
	output	reg		[ 7: 0]	po_data		,
	output	reg				po_flag
);

//	parameter	BAUD_CNT_MAX	=	5208;	// 9600Bps		(1/9600s)/(1/50M) = F/Bps = 50M / 9600
	localparam	BAUD_CNT_MAX	=	CLK_FREQ / UART_BPS;

	reg				rx_reg1		;	// 同步到系统时钟下
	reg				rx_reg2		;
	reg				rx_reg3		;	// 打两拍,减小亚稳态危害
	reg				start_flag	;	// 开始一个数据帧的传输标志信号
	reg				work_en		;	// 标识数据采集范围,接收数据工作使能信号
	reg		[15: 0]	baud_cnt	;	// 计数1个码元需要的clk,0~8207
	reg				bit_flag	;	// 每bit数据稳定点拉高一拍
	reg		[ 3: 0]	bit_cnt		;	// 10bit数据计数器
	reg		[ 7: 0]	rx_data		;
	reg				rx_flag		;
	
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			begin
				rx_reg1	<=	1'b1;	// 空闲状态为高
				rx_reg2	<=	1'b1;
				rx_reg3	<=	1'b1;
			end
		else
			begin
				rx_reg1	<=	rx;
				rx_reg2	<=	rx_reg1;
				rx_reg3	<=	rx_reg2;
			end


	// start_flag:检测下降沿
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			start_flag	<=	1'b0;
		else	if (~rx_reg2 && rx_reg3 && work_en == 1'b0)	// 检测下降沿,并且现在不在数据采集范围内
			start_flag	<=	1'b1;
		else
			start_flag	<=	1'b0;


	// work_en
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			work_en	<=	1'b0;
		else	if (start_flag == 1'b1)
			work_en	<=	1'b1;
		else	if (bit_cnt == 4'd8 && bit_flag == 1'b1)
			work_en	<=	1'b0;
		else
			work_en	<=	work_en;


	// baud_cnt
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			baud_cnt	<=	16'd0;
		else	if ((baud_cnt == BAUD_CNT_MAX - 1'b1) ||(work_en == 1'b0) )
			baud_cnt	<=	160'd0;
		else
			baud_cnt	<=	baud_cnt + 1'b1;	


	// bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,拉高一个标志信号表示数据可以被采样
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			bit_flag	<=	1'b0;
		else	if  (baud_cnt == BAUD_CNT_MAX / 2 - 1'b1)	// 数据稳定的最中间位置
			bit_flag	<=	1'b1;
		else
			bit_flag	<=	1'b0;


	// bit_cnt
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			bit_cnt	<=	4'd0;
		else	if ((bit_cnt == 4'd8) && (bit_flag == 1'b1))
			bit_cnt	<=	4'd0;
		else	if (bit_flag == 1'b1)
			bit_cnt	<=	bit_cnt + 1'b1;
		else
			bit_cnt	<=	bit_cnt;


	// rx_data
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			rx_data	<=	8'b0;
		else	if (((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8)) && (bit_flag == 1'b1))
			rx_data	<=	{rx_reg3, rx_data[7:1]};
		else
			rx_data	<=	rx_data;


	// rx_flag
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			rx_flag	<=	1'b0;
		else	if ((bit_flag == 1'b1) && (bit_cnt == 4'd8))
			rx_flag	<=	1'b1;
		else
			rx_flag	<=	1'b0;


	// po_data
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			po_data	<=	8'b0;
		else	if (rx_flag == 1'b1)
			po_data	<=	rx_data;


	// po_flag
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			po_flag	<=	1'b0;
		else
			po_flag	<=	rx_flag;

endmodule
`timescale	1ns/1ns

module uart_rx_tb ();

	reg		sys_clk		;
	reg		sys_rst_n	;
	reg		rx			;
	
	wire	[ 7: 0]	po_data	;
	wire			po_flag	;
	
	
	uart_rx
	#(
		.UART_BPS	('d9600		),
		.CLK_FREQ	('d50_000_000	)
	)
	uart_rx_inst
	(
		.sys_clk	(sys_clk	),
		.sys_rst_n	(sys_rst_n	),
		.rx			(rx			),
		
		.po_data	(po_data	),
		.po_flag	(po_flag	)
	);

	always # 10	sys_clk = ~sys_clk;
	
	initial	begin
		sys_clk		=	1;
		sys_rst_n	=	0;
		rx	=	1;
		#10
		sys_rst_n	=	1;
	end
	
	// 模拟发送数据帧
	initial	begin
		#200
		rx_bit(8'd0);
		rx_bit(8'd1);
		rx_bit(8'd2);
		rx_bit(8'd3);
		rx_bit(8'd4);
		rx_bit(8'd5);
		rx_bit(8'd6);
		rx_bit(8'd7);
		rx_bit(8'b1100_1010);
	end




// 任务函数
task	rx_bit ;

	input			[ 7: 0]	data;
	
	integer	i;
	
	begin
		for (i = 0; i < 10 ; i = i + 1)	begin		// 这个begin不可缺少,不然设置的延时起不到循环内的延迟作用
			case (i)
				0:	rx	<=	1'b0;		// 起始位
				1:	rx	<=	data[0];	// 数据位
				2:	rx	<=	data[1];
				3:	rx	<=	data[2];
				4:	rx	<=	data[3];
				5:	rx	<=	data[4];
				6:	rx	<=	data[5];
				7:	rx	<=	data[6];
				8:	rx	<=	data[7];
				9:	rx	<=	1'b1;		// 结束位
			endcase
			#(5208 * 20)
			;
		end
	end
endtask

endmodule

在这里插入图片描述

UART发送模块

将FPGA内部的数据以固定的波特率,并转串,包装成数据帧,发送给上位机

module uart_tx 
#(
	parameter	UART_BPS	=	'd9600			,
	parameter	CLK_FREQ	=	'd50_000_000	
)
(
	input	wire			sys_clk		,
	input	wire			sys_rst_n	,
	input	wire	[ 7: 0]	pi_data		,
	input	wire			pi_flag		,
	
	output	reg				tx
);

	localparam	BAUD_CNT_MAX	=	CLK_FREQ / UART_BPS;

	reg				work_en		;
	reg		[15: 0]	baud_cnt	;
	reg				bit_flag	;
	reg		[ 3: 0]	bit_cnt		;
	
	
	// work_en
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			work_en	<=	1'b0;
		else	if ((bit_cnt == 4'd9) && (bit_flag == 1'b1))
			work_en	<=	1'b0;
		else	if (pi_flag == 1'b1)
			work_en	<=	1'b1;
		else
			work_en	<=	work_en;
			
	
	// baud_cnt
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)	
			baud_cnt	<=	16'd0;
		else	if ((work_en == 1'b0) || (baud_cnt == BAUD_CNT_MAX - 1'b1))
			baud_cnt	<=	16'd0;
		else	if (work_en == 1'b1)
			baud_cnt	<=	baud_cnt + 1'b1;
	
	
	// bit_flag
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			bit_flag	<=	1'b0;
		else	if (baud_cnt == 16'd1)	// 每一个波特计数周期,拉高一个bit_flag周期,相邻脉冲间隔为一个波特
			bit_flag	<=	1'b1;
		else
			bit_flag	<=	1'b0;
	
	
	// bit_cnt
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			bit_cnt	<=	4'd0;
		else	if ((bit_cnt == 4'd9) && (bit_flag == 1'b1))
			bit_cnt	<=	4'd0;
		else	if ((work_en == 1'b1) && (bit_flag == 1'b1))
			bit_cnt	<=	bit_cnt + 1'b1;
		else
			bit_cnt	<=	bit_cnt;


	// tx
	always @ (posedge sys_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			tx	<=	1'b1;
		else	if (bit_flag == 1'b1)
			case (bit_cnt)
				0:	tx	<=	1'b0;
				1:	tx	<=	pi_data[0];
				2:	tx	<=	pi_data[1];
				3:	tx	<=	pi_data[2];
				4:	tx	<=	pi_data[3];
				5:	tx	<=	pi_data[4];
				6:	tx	<=	pi_data[5];
				7:	tx	<=	pi_data[6];
				8:	tx	<=	pi_data[7];
				9:	tx	<=	1'b1;
				default:	tx	<=	1'b1;
			endcase

endmodule

在这里插入图片描述

顶层模块

在这里插入图片描述
模拟接收串行数据8’b1100_1010,随后逐比特发送
在这里插入图片描述

4. 下板验证

在这里插入图片描述

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
串口RS232)通信是一种通过串行接口(通常是DB9接口)进行数据传输的通信方式。 在编写串口通信的代码时,通常需要使用到串口的相关函数和库,如Windows下的WinAPI函数、Linux下的POSIX函数等。 下面是一个基本的串口通讯示例代码: C语言版本(Windows平台): ```c #include <windows.h> int main() { HANDLE hSerial; DCB dcbSerialParams = {0}; COMMTIMEOUTS timeouts = {0}; char data[] = "Hello, World!"; DWORD bytes_written; // 打开串口 hSerial = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSerial == INVALID_HANDLE_VALUE) { printf("Error opening serial port\n"); return 1; } // 配置串口参数 dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) { printf("Error getting serial port state\n"); CloseHandle(hSerial); return 1; } dcbSerialParams.BaudRate = CBR_9600; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; if (!SetCommState(hSerial, &dcbSerialParams)) { printf("Error setting serial port state\n"); CloseHandle(hSerial); return 1; } // 配置读写超时时间 timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; timeouts.WriteTotalTimeoutConstant = 50; timeouts.WriteTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(hSerial, &timeouts)) { printf("Error setting serial port timeouts\n"); CloseHandle(hSerial); return 1; } // 发送数据 if (!WriteFile(hSerial, data, sizeof(data) - 1, &bytes_written, NULL)) { printf("Error writing to serial port\n"); CloseHandle(hSerial); return 1; } // 关闭串口 CloseHandle(hSerial); return 0; } ``` 这个示例代码通过打开串口、配置串口参数、配置读写超时时间以及发送数据等步骤实现了基本的串口通讯功能。具体的串口操作函数和参数可以根据实际需求进行调整和补充。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值