电子产业一站式赋能平台

PCB联盟网

搜索
查看: 15|回复: 0
收起左侧

UART协议逆向工程:基于FPGA实时解码串口数据

[复制链接]

630

主题

630

帖子

8071

积分

高级会员

Rank: 5Rank: 5

积分
8071
发表于 7 天前 | 显示全部楼层 |阅读模式
转自 | 电子森林
搞嵌入式,基本都会用到串口,串口的优点就是简单实用,但缺点也明显,就是通信中的数据相对容易被人抓取并破解。
今天就来讲讲UART协议逆向工程:基于FPGA实时解码串口数据的多种方法。
1.实验任务
  • 任务:基于 STEP-MAX10M08核心板 和 STEP BaseBoard V4.0底板 完成串口监视系统设计并观察调试结果。
  • 要求:设计串口监视系统,实时监控串口(UART)接收数据,并将数据显示在底板的8位数码管上(仅限数字0~9)。
  • 解析:通过FPGA编程驱动底板上的CH340串口通信模块,接收来自PC(串口调试助手)或其他串口设备的数据,经过处理,最后通过驱动8位扫描式数码管模块,将接收到的数据显示在底板数码管上。

    2.实验目的本实验主要学习串口(UART)总线工作原理、协议及相关知识,练习如何使用FPGA驱动CH340模块实现串口通信设计,同时复习上节中扫描式数码管模块的实例化应用。
  • 熟悉串口(UART)总线工作原理及通信协议
  • 完成基于FPGA的串口通信模块设计
  • 完成串口监视系统设计实现

    3.设计框图 根据前面的实验解析我们可以得知,该设计可以拆分成三个功能模块实现,
  • Uart_Bus: UART串口通信设计,实现串口通信数据传输。
  • Decoder:将UART模块接收到的数据转换成用于数码管显示的BCD码。
  • Segment_scan:通过驱动底板扫描式数码管将串口接收的数据显示出来。
    顶层模块Display_Ctl通过实例化两个子模块并将对应的信号连接,最终实现串口监视系统的总体设计。UART通信是全双工的,接收和发送是两个独立的设计,本实验只需要接收数据,串口通信有两个关键因素:传输格式和传输速率,我们可以用两个模块分别实现:
  • Baud:控制UART通信数据传输速率。
  • Uart_Rx:根据数据传输速率节拍控制UART通信数据格式。

    e1tkdyshc4k6404442306.png

    e1tkdyshc4k6404442306.png

    Top-Down层次设计

    nrp0mvs0lsg6404442406.png

    nrp0mvs0lsg6404442406.png

    模块结构设计

    4.实验原理4.1 UART接口介绍

    k2avirgll5f6404442506.png

    k2avirgll5f6404442506.png

    UART通信接口
    在嵌入式领域里说的串口一般就是说的UART接口,通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。

    5tvgwd04au46404442606.png

    5tvgwd04au46404442606.png

    RS232串行通信接口
    在系统或计算机中说的串口一般就是说的RS232接口,也叫COM口,也叫DB9,老式的电脑和台式机上一般都有这个接口,接口有9个引脚,最重要的三个引脚:TXD、RXD、GND,基本通信逻辑与UART完全一致,为了增加串口通信的抗干扰能力,RS232串行通信接口定义了自己的电平标准,采用负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平,相当于在UART的基础上增加驱动器,将原来UART通信电平标准调整为RS232的电平标准,通信原理如下:

    ddmu2izm0yz6404442706.png

    ddmu2izm0yz6404442706.png

    RS232串口通信

    随着技术的发展,各种通信接口种类越来越多,方案越来越稳定,成本越来越低,体积越来越小巧,RS232串口通信接口方案逐渐被抛弃,取而代之的是各种更高速,更稳定,更小巧的接口,USB就是其中应用较广的,为了实现UART通信,一种USB转UART的方案被广泛应用,常用的USB转UART方案有CP2102、FT232、CH340等等

    bu0odz4ciru6404442806.png

    bu0odz4ciru6404442806.png

    USB转UART通信

    我们STEP BaseBoard V4.0底板集成的UART通信模块就是采用CP2102方案,FPGA通过UART总线驱动CP2102实现USB和UART之间的数据通信,最终实现FPGA与电脑之间的数据传输,UART通信的时序如下。

    iihgkoc2fsf6404442906.png

    iihgkoc2fsf6404442906.png

    UART通信时序

  • 起始位:先发出一个逻辑 0 信号,表示传输字符的开始。
  • 数据位:可以是5~8位逻辑 0 或 1 。如ASCII码( 7位) ,扩展BCD码( 8位) 。
  • 校验位:数据位加上这一位后,使得 1 的位数应为偶数(偶校验)或奇数(奇校验)。
  • 停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
  • 空闲位:处于逻辑 1 状态,表示当前线路上没有资料传送。

    4.2 UART模块连接STEP BaseBoard V4.0底板上的基于CH340方案的UART通信模块电路图如下:

    ybfr0ms51ne6404443007.png

    ybfr0ms51ne6404443007.png

    UART通信模块电路

    上图为基于CH340方案的UART通信模块电路图,无需外置USB通信时钟晶体(内部集成),CH340芯片TXD和RXD分别与FPGA芯片RXD和TXD连接,同时两个信号都连接了LED灯,这样当UART通信时,随着数据传输对应LED灯也会快速闪烁,起到UART通信指示灯的作用。CH340芯片DTR和RTS通过两个三极管搭建流控电路,连接WIFI模块ESP8266-12F,使用UART模块烧写ESP8266模块的固件时就无需手动进入固件烧写模式了,这个会在后续涉及WIFI通信的实验中详细介绍,这里可以不用理会。

    4.3 UART驱动实现

    qouxgej33uo6404443107.png

    qouxgej33uo6404443107.png


    tmilm3ki1pt6404443207.png

    tmilm3ki1pt6404443207.png

    SPI总线连接及时序

    mec5jg3s1wg6404443307.png

    mec5jg3s1wg6404443307.png



    oi5mszvhjct6404443407.png

    oi5mszvhjct6404443407.png


    I2C总线连接及时序

    SPI、I2C、UART总线对比表:
    SPI总线
    I2C总线
    UART总线
    SS
    SCL
    TXD
    SCK
    SDA
    RXD
    MOSI/MISO


    对于SPI总线,通信双方在总线使能的情况下,通过SCK的上升沿或下降沿触发完成总线数据的采样,这样通信双方就可以准确的接收到对方传送的数据了。对于I2C总线,通信接收方通过SCL的高电平触发完成总线数据的采样。综上,SPI总线中的SCK和I2C总线中的SCL在通信中起到时钟的作用,接收方都是根据时钟的对应状态采样数据,最终保证通信能够正常进行。
    对于UART总线,TXD和RXD分别用于发送和接收数据,相当于两根独立工作的单线总线,没有了时钟线的配合,那么接收端应该怎样获取发送端传输的数据呢?其实也是有方法的,那就是通信双方需要约定好UART总线数据传输的通信速率和时序格式。
    通信速率:
    UART的数据传输速度用波特率来描述,也就是UART每秒接收或发送的数据位。例如9600波特率表示每秒钟发送或接收9600比特的数据,即发送端需要将发送的每个数据位保持对应的时间,计算如下:
  • 1s / 9600 = 1000000us / 9600 = 104.17us
    小脚丫硬件上使用12MHz的时钟晶振,如果以12MHz时钟信号作为系统时钟,使用计数器延时完成UART通信数据采样,那么计数器延时计数终值计算如下:
  • 12M / 9600 = 1250
    因为波特率是协议里约定的,为保证协议的通用性和灵活性,波特率参数有固定的选项,不可以随意设置(如果UART通信双方都是自己编程的,可以根据自己的要求定义自己需要的波特率,这种情况除外),波特率参数选项很多,大家可以打开串口调试助手工具找到波特率配置列表查看,我们比较常用的波特率值有以下几种:
    UART常用波特率:
    1200
    4800
    9600
    38400
    115200
    时序格式:
    关于时序格式在前面UART接口介绍部分也简单说了一下,通信过程中时序依次为:起始位、数据位、校验位、停止位、空闲位,其中数据位可以是5~8位,本设计我们使用8位数据,校验位可以省略,最后确定的时序格式如下:

    rnzg5eewlqs6404443507.png

    rnzg5eewlqs6404443507.png

    本实验UART通信时序
    前面所说的通信速率和时序格式其实就是UART通信中的两个重要的参数,需要传输的数据根据通信速率的节拍按照UART的时序格式输出,就可以实现UART通信了,可以按照下面三个步骤实现。
    1)将需要发送的数据与起始位和停止位组成10bit位宽的数据
    2)计数器计数延迟产生相应波特率需要的时序节拍
    3)数据按照(起始位—bit0~bit7—停止位)的时序串行输出
    例如,将8‘h73和8’h5a通过UART发送的时序,红色箭头为波特率对应的节拍点

    d3dfsir3mds6404443607.png

    d3dfsir3mds6404443607.png

    UART发送数据实例

    对于UART发送数据来说,波特率节拍是自己产生的,数据是自己主动发出的,逻辑相对简单,而当UART接收数据的时候,因为不确定对方什么时候发送数据,所以需要对RX信号持续检测,当检测到有数据传送时,根据约定的波特率节拍采样,可以按照下面三个步骤实现。
    1)检测UART的RXD信号的下降沿(自锁,完成接收后再解锁继续检测)
    2)接收采样时,采样点应该在计数器的中值点进行
    3)将采样后的数据按照UART时序的要求重新组成8bit的数据
    例如,当UART的RX端接收到数据8‘h73和8’h5a的时候,红色箭头为检测到数据传输的点,绿色箭头为对应的采样节拍点(采样点在数据中间最是稳定)。

    n2sih31trzd6404443707.png

    n2sih31trzd6404443707.png

    UART接收数据实例

    通过以上理论,我们了解了UART发送和接收数据的整个流程,两个过程中我们都需要波特率节拍,那么我们就可以设计一个节拍模块Baud,这样我们的发送和接收都可以实例化节拍模块用于产生对应波特率的节拍信号。
    节拍模块Baud设计实现:
    节拍模块Baud的端口程序实现如下:
  • module Baud #(parameter              BPS_PARA = 1250 //12MHz时钟1250对应9600波特率)(input                   clk,        //系统时钟input                   rst_n,      //系统复位,低有效input                   bps_en,     //接收或发送时钟使能output  reg             bps_clk     //接收或发送时钟输出);设计一个计数器用于分频产生对应波特率节拍信号,因为UART随时可能接收数据,所以节拍模块必须随时待命,保持计数器清零,当需要节拍信号时精准地输出。
    计数器设计程序实现如下:
  • //计数器计数满足波特率时钟要求always @ (posedge clk or negedge rst_n) begin    if(!rst_n)         cnt 1'b0;    else if((cnt >= BPS_PARA-1)||(!bps_en)) // bps_en=0时,计数器复位        cnt 1'b0;                            else // bps_en=1时,计数器工作,周期为BPS_PARA个系统时钟周期        cnt 1'b1;end当bps_en(高有效)使能,计数器计数周期由参数BPS_PARA来决定,前面数据接收时序部分了解到,从RX检测到下降沿开始计数器工作,到数据采样点需要半个节拍的时间,而数据发送时只要保证相邻两个节拍点之间的时间为一个计数器周期即可,所以我们可以在计数器计数到中值时产生一个脉冲信号充当节拍信号。
    节拍信号产生程序实现如下:
  • //产生相应波特率的时钟节拍,接收模块将以此节拍进行UART数据接收always @ (posedge clk or negedge rst_n) begin    if(!rst_n)         bps_clk 1'b0;    else if(cnt == (BPS_PARA>>1)) //中值数据稳定,做采样点        bps_clk 1'b1;        else         bps_clk 1'b0;end发送模块设计实现:
    前级电路通过tx_data_valid和tx_data_in将需要发送的数据传输进来,当tx_data_valid有脉冲信号时,tx_data_in信号为有效数据,拼接起始位和停止位后赋值给tx_data_r,同时控制节拍使能信号使能并自锁,然后等发送完10bit数据后解除使能。
    数据发送控制程序实现如下:
  • output  reg             bps_en,         //发送时钟使能input                   bps_clk,        //发送时钟输入input                   tx_data_valid,  //发送数据有效脉冲input           [7:0]   tx_data_in,     //要发送的数据output  reg             uart_tx         //UART发送输出
    reg             [9:0]   tx_data_r;    //融合了起始位和停止位的数据//根据接收数据的完成,驱动发送数据操作always @ (posedge clk or negedge rst_n) begin    if(!rst_n) begin        bps_en 1'b0;        tx_data_r 10'd0;    end else if(tx_data_valid && (!bps_en))begin            bps_en 1'b1;     //当需要发送数据时,使能节拍使能信号        tx_data_r 1'b1,tx_data_in,1'b0};        end else if(num==4'd10) begin           bps_en 1'b0;     //一次UART发送需要10个时钟信号,然后结束    endendUART数据发送时序程序实现如下:
  • //当处于工作状态中时,按照发送时钟的节拍发送数据always @ (posedge clk or negedge rst_n) begin    if(!rst_n) begin        num 1'b0;        uart_tx 1'b1;    end else if(bps_en) begin        if(bps_clk) begin                  //根据节拍发送数据            num 1'b1;            uart_tx //先发低位后发高位        end else if(num>=4'd10)             num 4'd0;        endend将节拍模块Baud和发送模块Uart_tx实例化并连接,完成发送功能的设计,如下

    0iwwjjyvs326404443807.png

    0iwwjjyvs326404443807.png

    UART发送功能设计实现

    接收模块Uart_Rx设计实现:
    首先对RX信号多级缓存消除亚稳态,同时检测下降沿,程序实现如下:
  • input                   uart_rx,        //UART接收输入
    reg uart_rx0,uart_rx1,uart_rx2; //多级延时锁存去除亚稳态always @ (posedge clk) begin    uart_rx0     uart_rx1     uart_rx2 end
    //检测UART接收输入信号的下降沿wire    neg_uart_rx = uart_rx2 & ~uart_rx1;当检测RX有下降沿后,使能节拍使能信号,同时自锁直到完成接收操作后再复位节拍使能信号。程序实现如下:
  • //接收时钟使能信号的控制always @ (posedge clk or negedge rst_n) begin    if(!rst_n)        bps_en 1'b0;    else if(neg_uart_rx && (!bps_en)) //当检测到传输,使能节拍使能信号        bps_en 1'b1;         else if(num==4'd9)              //完成UART接收操作,复位节拍使能信号        bps_en 1'b0;         end根据节拍信号完成UART总线的数据采样,得到8位有效数据,程序实现如下:
  • reg             [7:0]   rx_data;//当处于工作状态中时,按照接收时钟的节拍获取数据always @ (posedge clk or negedge rst_n) begin    if(!rst_n) begin        num 4'd0;        rx_data     end else if(bps_en) begin           if(bps_clk) begin                       num 1'b1;            if(num1] //先低位后高位        end else if(num == 4'd9) begin              num         end    end else begin        num 4'd0;    endend当UART接收操作完成后,将得到的8位有效数据输出给后级电路,程序实现如下:
  • //将接收的数据输出,同时控制输出有效信号产生脉冲always @ (posedge clk or negedge rst_n) begin    if(!rst_n) begin        rx_data_out  8'd0;        rx_data_valid     end else if(num == 4'd9) begin          rx_data_out         rx_data_valid     end else begin        rx_data_out  rx_data_out;        rx_data_valid  1'b0;    endend最后将节拍模块Baud和接收模块Uart_rx实例化并连接,完成发送功能的设计,如下

    fuw2y1igtrf6404443907.png

    fuw2y1igtrf6404443907.png

    UART接收功能设计实现

    整个UART驱动设计是由两个独立的功能组合而成:发送功能部分和接收功能部分。UART功能总体设计框图如下:

    irwynharlzq6404444007.png

    irwynharlzq6404444007.png

    当我们需要UART发送数据的时候只需要实例化发送功能部分设计,需要UART接收数据的时候只需要实例化接收功能部分设计,例如本设计中FPGA驱动UART模块接收电脑串口调试助手发出的数据,所以我们就只需要实例化接收功能部分设计即可。

    4.4 系统总体实现刚刚学习了UART通信模块,本设计只需要使用接收功能部分设计,每一次通信都会到一个8位数据,怎样将8位数据对应得数据显示在数码管上呢?我来先来了解一下UART接受到的8位数据与要显示数字的关系

    ft1crlu5jxj6404444107.png

    ft1crlu5jxj6404444107.png

    串口调试助手界面

    上图为电脑端串口调试助手的界面,当我们将硬件连接,在串口设置串口选定串口对应的端口,并按上图配置波特率、数据位、校验位、停止位、流控等,点击开始建立连接,接下来我们就可以在串口发送窗口输入要发送的数据,点击发送后数据传输出去。在发送设置有两个选项:字符串(ASCII)和Hex ,
  • 当选择字符串的时候,通过UART发出的数据是数据窗口中字符的ASCII码值,每个字符的ASCII码值都是8位数据,所以窗口中字符数量与UART传输的次数是相等的,同时数字的值与ASCII码值相差48,例如数字0的ASCII码值为48。
  • 当选择Hex的时候,通过UART发出的数据(必须是16进制数据)就是数据窗口中的数据本身,这样每次UART传输都会发送两个数字,如果只发送一个数字,则高位补零组成8位数据,例如发送数字1,实际UART传输的数据为8‘h01。
    我们设计一个32位的移位寄存器对应8位数码管,按照BCD码格式每4位表示一个数字,每次接收到UART数据都存到移位寄存器中,同时控制数码管显示相应的数码管位,Decoder程序实现如下:
  • `ifdef HEX_FORMAT  //如果`define定义过HEX_FORMAT,综合`else前面的程序    //采用16进制格式,接收到的数据等于数值本身wire [7:0] seg_data_r = rx_data_out;
        //移位寄存器,对应8位数码管数据BCD码    always @ (posedge rx_data_valid or negedge rst_n) begin        if(!rst_n) seg_data 1'b0;        else seg_data 23:0],seg_data_r};end
        //移位寄存器,对应8位数码管数据显示使能    always @ (posedge rx_data_valid or negedge rst_n) begin        if(!rst_n) data_en 1'b0;        else data_en 5:0],2'b11};    end`else                 //否则综合`else后面的程序    //采用字符格式,接收到的数据为字符ASCII码值,与数字值相差48wire [7:0] seg_data_r = rx_data_out - 8'd48;
        //移位寄存器,对应8位数码管数据BCD码    always @ (posedge rx_data_valid or negedge rst_n) begin        if(!rst_n) seg_data 1'b0;        else seg_data 27:0],seg_data_r[3:0]};end
        //移位寄存器,对应8位数码管数据显示使能    always @ (posedge rx_data_valid or negedge rst_n) begin        if(!rst_n) data_en 1'b0;        else data_en 6:0],1'b1};    end`endif上面程序中`ifdef……`else……`endif语句为预编译指令,与C预演类似。如果我们使用串口助手Hex(16进制)格式发送数据,需要在程序中使用define定义参数HEX_FORMAT,如果使用ASCII格式发送数据,则不需要定义。
  • `define HEX_FORMAT //串口助手使用Hex格式发送时定义HEX_FORMAT,否则不定义综合后的设计框图如下:

    0utatuhbhjk6404444207.png

    0utatuhbhjk6404444207.png


    5.实验步骤1)双击打开Quartus Prime工具软件;
    2)新建工程:File → New Project Wizard(工程命名,工程目录选择,设备型号选择,EDA工具选择);
    3)新建文件:File → New → Verilog HDL File,键入设计代码并保存;
    4)设计综合:双击Tasks窗口页面下的Analysis & Synthesis对代码进行综合;
    5)管脚约束:Assignments → Assignment Editor,根据项目需求分配管脚;
    6)设计编译:双击Tasks窗口页面下的Compile Design对设计进行整体编译并生成配置文件;
    7)程序烧录:点击Tools → Programmer打开配置工具,Program进行下载;
    8)观察设计运行结果。

    6.实验现象使用两根Micro-USB线同时连接核心板和底板的USB接口,将程序下载到FPGA中,数码管处于不显示的状态,打开电脑上的串口调试助手,按照前面图片配置相应参数,在数据发送窗口输入数字,点击发送观察底板数码管的变化,重新输入数字,点击发送再次观察底板数码管的变化。

  • 回复

    使用道具 举报

    发表回复

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

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条


    联系客服 关注微信 下载APP 返回顶部 返回列表