找回密码
 立即注册
查看: 449|回复: 0

[VF-P22K176]基于紫光同创 FPGA 与 PC 通信的 UART 串口设计

[复制链接]

6

主题

2

回帖

58

积分

管理员

积分
58
发表于 2024-3-10 13:21:00 | 显示全部楼层 |阅读模式
1.1  串口介绍
     串口,全称串行接口,也称串行通信接口(通常指 COM 接口),是采用串行通信方式的扩展接口。与串口相对的是并口,数据以并行传输,但是由于通信线的增加,需要的成本更高。
顾名思义,串行接口意思就是说数据逐位按顺序传送,每次只发送 1Bit。串口出现在 1980 年前后,设计为了实现与外部设备通信。初期串口一般用来连接鼠标、外置 Modem、老式摄像头、写字板等设备。串口也可以应用于两台计算机(或设备)之间的互联及数据传输。串口有以下特点:
① 通信线路简单,只要一对传输线就可以实现双向通信。
② 布线简单,成本低。
③ 能实现几米到几千米的通信距离。
但串口有一个缺点就是传送速度较慢。当然由于传输速度与距离成反比,这
一缺点反而给当年串口远距离通信带来了优势。
1.2  串口电路的设计
串口按照电气标准及协议,可以分为 RS232、RS422、RS485 等。其中 RS232是PC机与工业控制应用中最为广泛的一种接口,被定义为低速串行通讯中增加通讯距离的单端标准。数据采用单端传输,即所谓不平衡传输。而 RS422、RS485采用所谓的平衡传输,即每个信号采用一对双绞线差分传输。不过这三者,对于控端的驱动而言,完全一样,因此我们只需要关心串口协议的实现即可。此外,传统的串口传输,要求负逻辑电平,这使得电平的不兼容,无法正常实现通信。因此,为了实现负逻辑电平,我们需要在电路中通过转换,实现电平的过渡。过去我们使用 MAX232、MAX3232 类串口电平转换芯片来实现 TTL→串口电平的转换,然后将信号传输给设备、PC 等。
采用 MAX232/3232 将 TTL 转换为负逻辑电平,继而通过 USB 转串口线,将信号转换为 USB 信号,要不是接口的局限,实在是自讨没趣。那是否可以直接将 TTL 信号转换为 USB 信号呢???答案是必然的。。
USB 转 UART 芯片有很多。。。一般由以下几种方案:
(1) 大部分 USB 转串口线,使用的是 PL2303 转换方案
(2) 南京沁恒公司推出的 CH340系列,同样支持 USB2UART 功能。该芯片支持 50~921600、1500000、2000000 的波特率,误差小于 0.3%,串口接收信号的允许波特率误差不小于 2%,并且价格低廉,是低成本
产品的最佳选择。
(3) Silicon Labs 公司的 CP2102 转换方案,CP2102 支持从 300 到 921600波特率,在速度上较 CH340 要低,不过比 CH340 要稳定,电路更加简单。在成本不敏感,同时对系统稳定性要求高的场合,可以采用
CP2102 来实现 USB 转 UART。
在本开发板中才有方案2,使用南京沁恒公司推出的CH340N,电路简单,稳定可靠。电路图如下

USB 转 UART 电路,在插入 PC 机时候,均需要安装相关的驱动。板卡资料提供了驱动文件,如下所示。用 户 直 接 安 装驱动即可使用。

1.3 UART 接收模块的设计
这里,在 PC 端发送 9600 波特率的数据,在 FPGA 中以 9600*16 的波特率接收数据。。我们将接收的过程分为了 4 个状态,如下所示:

这里新建 uart_receiver.v 文件,开始设计 UART 数据的接收电路。
(1)在 0 状态时,我们等到 UART 低电平起始信号,一旦检测到起始信号,则跳转到 1 状态,如下所示:
case(rxd_state)
R_IDLE: //Wait for start bit
begin
rxd_cnt <= 0;
smp_cnt <= 0;
if(rxd_sync == 1'b0) //uart rxd start bit
rxd_state <= R_START;
else
rxd_state <= R_IDLE;
end
(2)在 1 状态时,我们判断 0 状态得到的起始信号是否为抖动,如果计数达到了 8,即 8 个 9600*16 的时间点,则意味着信号有效。反之,则返回 0 状态,如下所示:
R_START:
begin
if(clken_16bps == 1) //clk_bps * 16
begin
smp_cnt <= smp_cnt + 1'b1;
if(smp_cnt == SMP_CENTER && rxd_sync != 1'b0) //invalid
data
begin
rxd_cnt <= 0;
rxd_state <= R_IDLE;
end
else if(smp_cnt == SMP_TOP) //Count for 16 clocks
begin
rxd_cnt <= 1;
rxd_state <= R_SAMPLE; //start mark bit is over
end
else
begin
rxd_cnt <= 0;
rxd_state <= R_START; //wait start mark bit over
end
end
else //invalid data
begin
smp_cnt <= smp_cnt;
rxd_state <= rxd_state;
end
end
(3)此时已经进入了正常的接受流程,在这个状态中我们重复进行 8 次
(0~15)的计数,即 8 次数据的采集。这个过程中,数据时钟在数据的中点采样。8 次 0~15 计数完后,进入 3 状态。HDL 的实现如下所示:
R_SAMPLE: //Sample 8 bit of Uart: {LSB, MSB}
begin
if(clken_16bps == 1) //clk_bps * 16
begin
smp_cnt <= smp_cnt + 1'b1;
if(smp_cnt == SMP_TOP)
begin
if(rxd_cnt < 4'd8) //Totally 8 data
begin
rxd_cnt <= rxd_cnt + 1'b1;
rxd_state <= R_SAMPLE;
end
else
begin
rxd_cnt <= 4'd9; //Turn of stop bit
rxd_state <= R_STOP;
end
end
else
begin
rxd_cnt <= rxd_cnt;
rxd_state <= rxd_state;
end
end
else
begin
smp_cnt <= smp_cnt;
rxd_cnt <= rxd_cnt;
rxd_state <= rxd_state;
end
end
由于每次计数到 8(中点)时,数据最稳定,因此在此时进行 LSB→MSB 8bit串行数据的接收,相关实现如下所示:
//----------------------------------
//uart data receive in center point
reg [7:0] rxd_data_r;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rxd_data_r <= 0;
else if(rxd_state == R_SAMPLE)
begin
if(clken_16bps == 1 && smp_cnt == SMP_CENTER) //sample center
point
case(rxd_cnt)
4'd1: rxd_data_r[0] <= rxd_sync;
4'd2: rxd_data_r[1] <= rxd_sync;
4'd3: rxd_data_r[2] <= rxd_sync;
4'd4: rxd_data_r[3] <= rxd_sync;
4'd5: rxd_data_r[4] <= rxd_sync;
4'd6: rxd_data_r[5] <= rxd_sync;
4'd7: rxd_data_r[6] <= rxd_sync;
4'd8: rxd_data_r[7] <= rxd_sync;
default:;
endcase
else
rxd_data_r <= rxd_data_r;
end
else if(rxd_state == R_STOP)
rxd_data_r <= rxd_data_r;
else
rxd_data_r <= 0;
end
(4)由于在 2 状态中,第 8 次计数停留在了中点,因此在 3 状态中继续计数,直到计数到 15,返回 0 状态,如下所示:
R_STOP:
begin
if(clken_16bps == 1) //clk_bps * 16
begin
smp_cnt <= smp_cnt + 1'b1;
if(smp_cnt == SMP_TOP)
begin
rxd_state <= R_IDLE;
rxd_cnt <= 0; //Stop data bit is done
end
else
begin
rxd_cnt <= 9; //Stop data bit
rxd_state <= R_STOP;
end
end
else
begin
smp_cnt <= smp_cnt;
rxd_cnt <= rxd_cnt;
rxd_state <= rxd_state;
end
end
此外,每次接受完毕一次信号后,外部模块需要有一个触发读取的信号,这里每次在停止位时输出接受完毕信号。同时,在该时刻进行数据串转并数据的更新,如下所示:
//----------------------------------
//update uart receive data and receive flag signal
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rxd_data <= 0;
rxd_flag <= 0;
end
else if(clken_16bps == 1 && rxd_cnt == 4'd9 && smp_cnt == SMP_TOP)
//Start + 8 Bit + Stop Bit
begin
rxd_data <= rxd_data_r;
rxd_flag <= 1;
end
else
begin
rxd_data <= rxd_data;
rxd_flag <= 0;
end
end
因此,外部模块每次接受到 rxd_flag 触发信号时,可以采集当前捕获的
rxd_data 信号。在 Modelsim 的仿真中,由于没法输入数据源,因此我们通过 precise_divider模块生成 9600Hz 的时钟源,同时采Testbench 编写任务,模拟发送串口信号。如下所示:
//uart data txd simulate
wire uart_bps_en = divide_clken2;
task task_uart_txd;
input [7:0] uart_data;
begin
//8'hCB = 8'b1100_1011
@(posedge uart_bps_en); fpga_rxd = 0; //Start
@(posedge uart_bps_en); fpga_rxd = uart_data[0];
@(posedge uart_bps_en); fpga_rxd = uart_data[1];
@(posedge uart_bps_en); fpga_rxd = uart_data[2];
@(posedge uart_bps_en); fpga_rxd = uart_data[3];
@(posedge uart_bps_en); fpga_rxd = uart_data[4];
@(posedge uart_bps_en); fpga_rxd = uart_data[5];
@(posedge uart_bps_en); fpga_rxd = uart_data[6];
@(posedge uart_bps_en); fpga_rxd = uart_data[7];
@(posedge uart_bps_en); fpga_rxd = 1; //End
#78;
end
endtask
最后,在 initial 中模拟发送 4 次 9600 波特率的串口数据,如下所示:
//---------------------------------------
//testbench of the RTL
initial
begin
task_sysinit;
task_reset;
#12345;
task_uart_txd(8'hCB);
#12345;
task_uart_txd(8'h0A);
#12345;
task_uart_txd(8'h09);
#12345;
task_uart_txd(8'h0F);
end
最后,例化 uart_receiver.v,编译仿真,得到的 Modelsim 仿真波形如下所示,
数据的接受完全满足我们的设计。从理论分析,UART 接收模块设计正确。UART发送模块设计部分请看资料附带的源码与注释。
效果请看视频号。


欢迎关注公众号

视频号

淘宝网址:https://chinafpga.taobao.com/



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|CrazyFPGA ( 粤ICP备2023025753号 )

GMT+8, 2024-5-13 09:09 , Processed in 0.042776 second(s), 19 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表