找回密码
 立即注册
首页 业界区 业界 用低成本FPGA实现FSMC接口的多串口(UART)缓冲控制器 ...

用低成本FPGA实现FSMC接口的多串口(UART)缓冲控制器

里豳朝 6 小时前
尽管目前工业以太网已经相当普及,但在工控领域仍然存在大量使用UART通过RS485和RS422组网的设备和控制器,导致含有多UART的嵌入式系统仍有较大市场需求。意法半导体和兆易创新等主流微控制器(MCU)厂商都有10个以上UART的器件,但在很多场景下仍然无法覆盖所有应用场景。另外,对于主控单元是微处理器(MPU,能运行Linux)的嵌入式系统,UART口一般较少,就不得不使用16C550/16C554这类扩展芯片来实现多UART。
用FPGA来实现可定制的多UART口扩展是一种不错的解决方案。其中,在Zynq上通过AXI总线扩展多个UART难度不高,但限制了主控单元的使用,且成本较高。我趁着这个国庆长假在低成本的国产FPGA/CPLD上实现了一种基于FSMC接口(GD32上称为EXMC接口)的多串口控制器,适用于所有兼容ISA总线接口的嵌入式MCU/MPU系统。可以根据应用场景任意裁剪UART的个数、FIFO缓冲的深度。
以下原创内容欢迎网友转载,但请注明出处: https://www.cnblogs.com/helesheng
一、整体结构

我设计的多UART控制器采用外设地址映射模式思路,FPGA模拟成一个8位的FSMC设备和处理器(如STM32)通信。其中,每个UART口占用数据接收寄存器地址(UARTx_RX_Reg_Addr,小写x可以代表任意串口编号的阿拉伯数字,下同)、接收缓冲长度寄存器地址(UART1_RX_LEN_Reg_Addr)、数据发送寄存器地址(UARTx_TX_Reg_Addr)以及发送缓冲长度寄存器地址(UARTx_TX_LEN_Reg_Addr)共四个字节的地址;另外,每个UART口还占用了发送状态寄存器(STAT_TX_Addr)和接收状态寄存器(STAT_RX_Addr)中的各一个位,来表示该UART当前是否处于发送或接收状态。
其中,每个UART的数据发送和接收寄存器在逻辑概念上虽然只占用的一个字节的地址,但在物理上却映射到一组接收FIFO和发送FIFO的一个端口。以接收FIFO为例:每当UART口收到一个字节的数据A时,FPGA就会将该数据压入接收FIFO中,而处理器也不需要立即将刚收到的数据读走,因为即使处理器没有及时地在下次收到数据B之前读走该数据,FIFO还是能够缓存这个新数据。而当处理器腾出时间处理该UART口的接收数据时,还是能从同一个地址(UARTx_RX_Reg_Addr)按照收到这些数据的顺序A->B->C....依次读取他们。这种方式的优势有二:
1、相对于嵌入式处理器,UART属于低速设备,对于接收而言,增加了FIFO缓冲的UART控制器无需处理器过于频繁的查询,只需要间隔相当一段时间查看一下FIFO中是否缓存了数据即可;对于发送而言,增加了FIFO缓冲的控制器也可以一次缓冲多个需要发送的字节,降低处理器关注的频率。
2、发送和接收缓冲分别都只占用一个字节的地址空间,即可实现任意长度数据包的收发。当然,代价是处理器只能顺序访问缓冲区,而无法实现随机访问,但这对于需要严格按顺序读写的UART口而言,这并不是问题。
下面的代码是我在作为处理器的STM32代码中定义的UART相关寄存器地址:
1.gif
2.gif
  1. 1 #define STAT_RX_Addr    ((unsigned int)0x60000000)   //FPGA模拟的uart口接收状态寄存器地址
  2. 2 #define STAT_TX_Addr    ((unsigned int)0x60010000)   //FPGA模拟的uart口接发送态寄存器地址
  3. 3 #define UART1_RX_Reg_Addr    ((unsigned int)0x60020000)   //fpga模拟的uart1口接收数据地址
  4. 4 #define UART1_TX_Reg_Addr    ((unsigned int)0x60030000)   //fpga模拟的uart1口发送数据地址
  5. 5 #define UART1_RX_LEN_Reg_Addr    ((unsigned int)0x60040000)   //fpga模拟的uart1口接收缓冲区中数据长度的地址
  6. 6 #define UART1_TX_LEN_Reg_Addr    ((unsigned int)0x60050000)   //fpga模拟的uart1口发送缓冲区中数据长度的地址
  7. 7 #define UART2_RX_Reg_Addr    ((unsigned int)0x60060000)   //fpga模拟的uart2口接收数据地址
  8. 8 #define UART2_TX_Reg_Addr    ((unsigned int)0x60070000)   //fpga模拟的uart2口发送数据地址
  9. 9 #define UART2_RX_LEN_Reg_Addr    ((unsigned int)0x60080000)   //fpga模拟的uart2口接收缓冲区中数据长度的地址
  10. 10 #define UART2_TX_LEN_Reg_Addr    ((unsigned int)0x60090000)   //fpga模拟的uart2口发送缓冲区中数据长度的地址
复制代码
UART寄存器地址(STM32侧)FPGA代码中定义的UART相关寄存器地址:

3.gif
4.gif
  1. 1 // 定义地址
  2. 2 parameter ADDR_RX_STATE = 5'd0;          //状态寄存器高字节地址,对应ARM地址0x60000000
  3. 3 parameter ADDR_TX_STATE = 5'd1;          //状态寄存器低字节地址,对应ARM地址0x60010000
  4. 4 parameter ADDR_UART1_RX = 5'd2;          //uart1接收数据端口地址,对应ARM地址0x60020000
  5. 5 parameter ADDR_UART1_TX = 5'd3;          //uart1发送数据端口地址,对应ARM地址0x60030000                     
  6. 6 parameter ADDR_UART1_RX_LEN = 5'd4;          //uart1接收缓冲中待读取数据数量的读取端口地址,对应ARM地址0x60040000
  7. 7 parameter ADDR_UART1_TX_LEN = 5'd5;          //uart1发送缓冲中待发送数据数量的读取端口地址,对应ARM地址0x60050000
  8. 8 parameter ADDR_UART2_RX = 5'd6;          //uart2接收数据端口地址,对应ARM地址0x60060000
  9. 9 parameter ADDR_UART2_TX = 5'd7;          //uart2发送数据端口地址,对应ARM地址0x60070000
  10. 10 parameter ADDR_UART2_RX_LEN = 5'd8;          //uart1接收缓冲中待读取数据数量的读取端口地址,对应ARM地址0x60080000
  11. 11 parameter ADDR_UART2_TX_LEN = 5'd9;          //uart1发送缓冲中待发送数据数量的读取端口地址,对应ARM地址0x60090000
复制代码
UART寄存器地址(FPGA侧) 
二、FPGA中八位并行接口(FSMC)设备端的实现

1、读写时钟控制信号的产生

下图是我在STM32手册中截取的配置成SRAM的读写模式时FSMC接口的读写时序。
5.png

图1 FSMC(SRAM模式)读时序
6.png

图2 FSMC(SRAM模式)写时序
 对于100pin的STM32而言,并不拥有全部26根地址线,而只有A16-A23共8根,以及一根自动产生的片选线NCE1/NCE2(控制地址范围达到全部4G寻址空间的四分之一)。
FPGA侧为了实现FSMC的设备端接口,核心要求是能够在写使能NWE和读使能NOE控制下在FPGA中实现数据的锁存和输出。有两种合理的技术路线:其一,使用NWE或NOE作为数据寄存器的锁存时钟;其二,用FPGA系统时钟作为数据寄存器的锁存时钟,NWE或NOE作锁存器读写的使能信号。
第一种思路最直接,但问题是缓冲FIFO的存储时钟只能由STM32的读、写动作产生,但一般FIFO IP在真正进行读写之前也需要时钟信号来完成初始化。如果采用第一种技术路线,势必需要STM32作几组无意义的“空读写”,以产生必要的初始化时钟。另外,也可能由于STM32到FPGA之间的PCB走线造成时序约束难度的增加,并提升外部高频信号干扰的可能,因此我没有选择这条技术路线。第二种思路的难点在于NWE或NOE信号低电平期间,可能产生多个FPGA的系统时钟,从而造成对同字节数据被缓冲FIFO看做多个数据,进而被多次读或写。
为实现第一种技术路线,我用如下代码将长度不确定的NWE或NOE信号转换为一个长度为1个系统时钟的正脉冲作为缓冲FIFO的读写使能控制信号。完美的解决了单次STM32读写造成多次缓冲FIFO读写的问题。
7.gif
8.gif
[code] 1 /////读端口控制线////// 2 assign en_fsmc_oe = (fsmc_a[21] == 1'b0) && (rd_n == 1'b0) && (cs_n == 1'b0); 3 wire rd_uart1_rx_fifo;//用于mcu读取接收缓冲fifo的使能信号 4 assign rd_uart1_rx_fifo = (fsmc_a[20:16] == ADDR_UART1_RX) && en_fsmc_oe;//MCU发出的读取uart1接收数据的使能信号 5 reg[1:0] prv_rd_uart_rx_fifo_reg; 6 always @(posedge clk_in_50 or negedge rst_n)//串口模块接收标志信号是个正脉冲,在下降沿数据才完全准备好。对其进行延迟 7 begin 8     if(!rst_n) 9         prv_rd_uart1_rx_fifo_reg[1:0]
您需要登录后才可以回帖 登录 | 立即注册