日常·唠嗑:
最近有个项目,朋友需要用京微齐力的HME-P1P060读取MAX30102传感器的血氧数据,在做之前想了几种方案,最开始想用IIC读取传统的血氧模块数据,但是后面有看到优信电子新推出的串口类心率血氧模块,网上资料也基本没有,自己曾经也遇到这个难题,开源资料基本都是串口回环实验,综合类的串口很少开源,于是便想尝试一下。
这个项目为了具有可重构性,我将它拆成主要三个子项目:发送指令,接收指令,完整项目整合。
这样做的目的,为的就是以后这个项目,能移植到各个串口类的传感器项目上(很多时候,这类传感器的应用开发都是用嵌入式来完成,因为嵌入式拥有更成熟的各种库,做开发难度会简单很多),让大家更好的做项目。

实验结果
每按一次按键,发送一条9位的指令。

FPGA发送9位指令


一、硬件解析
1、国产FPGA:HMEP060



2、MAX30102心率传感器模块
MAX30102心率传感器模块使用UART协议进行传输数据,不做开发的时候,可以直接通过CH340插在电脑上看数据,但是如果想应用在项目里,就得对内部封装的协议进行解析。



指令表如下:本项目项要采集血氧数据,所以需要向FPGA 发送AT+SPO2\r\n 9位指令。



二、程序设计
1、波特率计算(25MHz时钟)

要做串口通信,绕不开的就是波特率计算,这里对于UART协议就不做介绍了,网上的文章或者视频解说很多,自己搜一下看看。
所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为 115200bps,则我们需要产生一个频率为 115200Hz 的时钟信号。
那么如何产生这样一个 115200Hz 的时钟信号呢?
这里,我们首先将115200Hz 时钟信号的周期计算出来,1 秒钟为 1000_000_000ns,因此波特率时钟的周期为:Tb=1000000000/115200=8680.6ns。
即 115200 信号的一个周期为 8680.6ns,那么,我们只需要设定我们的定时器定时时间为 8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。
HMEP060系统时钟频率为25MHz,即周期为 40ns(1÷25000000=0.00000004秒=40ns) ,那么,我们只需要计数 8680/40 个系统时钟,就可获得 8680ns 的定时,bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此相同。具体如下:

bps_DR = 1000000000/9600/40;

2、顶层模块
讲解,我写在代码里面,直接看代码, 这里发送的是9位数据,代码具有很强的一移植性,需要发送多位的,直接在发送数据那里增加字符,然后再状态机增加状态就行。同时代码仍在优化中,写的比较简陋,有心得的朋友,可以优化分享。


完整顶层模块代码:

//**************************************** Message ***********************************//
//技术交流:bumianzhe@126.com
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏
//All rights reserved                                   
//----------------------------------------------------------------------------------------
// File name:           uart_tx_data
// Last modified Date:  2022年5月22日14:31:09
// Last Version:        V1.1
// Descriptions:        uart发送9位指令
//----------------------------------------------------------------------------------------
//****************************************************************************************//

module uart_tx_data(
    Clk,
    Reset_n,
    Tx_Go,
    uart_tx,
    Trans_Done
);
    input Clk;
    input Reset_n;
    input Tx_Go; //添加一个KEY,每按一次,发送一次,因为很多传感器都是只接一次指令,就会回传数据,不用一直发
    output uart_tx;
    output reg Trans_Done; 
    //Trans_Done的作用是增加代码的可构性,发送完指令后,对Trans_Done拉高,这个接口主要用来给大家做扩展设计的。

    reg [7:0]Data;
    reg Send_Go;
    wire Tx_done;
    wire Trans_Go;
    reg [71:0]Data72;
    reg test_en_dly1;
    reg test_en_dly2;

    //按键信号打拍
    always@(posedge Clk)
    begin
        test_en_dly1 <= Tx_Go;
        test_en_dly2 <= test_en_dly1;
    end

  //控制发送使能
    assign Trans_Go = test_en_dly1 & !test_en_dly2;

   //需要发送的指令字符,读取血氧的指令为AT+SPO2\r\n
   //需要增加字符的,可以增加Data的位宽
    always@(*)
    begin
//         Data72 <= "n\r\2OPS+TA";    
         Data72[7:0]  <= "A";
         Data72[15:8] <= "T";
         Data72[23:16]<= "+";
         Data72[31:24]<= "S";
         Data72[39:32]<= "P";
         Data72[47:40]<= "O";
         Data72[55:48]<= "2";
         Data72[63:56]<= "\r";
         Data72[71:64]<= "\n";         
    end


    //通过状态机的方式,将数据发送出去,每次发8位,总共9个数据
    reg [3:0]state;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        state <= 0;
        Data <= 0;
        Send_Go <= 0; 
        Trans_Done <= 0;
    end
    else begin
        case(state)
            0:
            begin
                Trans_Done <= 0;
                if(Trans_Go)begin
                    Data <= Data72[7:0];
                    Send_Go <= 1; 
                    state <= 1;     
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0; 
                    state <= 0;         
                end
           end

           1:
            begin
                if(Tx_done)begin
                    Data <= Data72[15:8];
                    Send_Go <= 1;
                    state <= 2;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 1;
                end
            end

            2:
            begin
                if(Tx_done)begin
                    Data <= Data72[23:16];
                    Send_Go <= 1;
                    state <= 3;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 2;
                end
            end 

            3:
            begin
                if(Tx_done)begin
                    Data <= Data72[31:24];
                    Send_Go <= 1;
                    state <= 4;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 3;
                end
            end  

            4:
            begin
                if(Tx_done)begin
                    Data <= Data72[39:32];
                    Send_Go <= 1;
                    state <= 5;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 4;
                end
            end  

            5:
            begin
                if(Tx_done)begin
                    Data <= Data72[47:40];
                    Send_Go <= 1;
                    state <= 6;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 5;
                end
            end              

            6:
            begin
                if(Tx_done)begin
                    Data <= Data72[55:48];
                    Send_Go <= 1;
                    state <= 7;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 6;
                end
            end             

            7:
            begin
                if(Tx_done)begin
                    Data <= Data72[63:56];
                    Send_Go <= 1;
                    state <= 8;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 7;
                end
            end           

            8:
            begin
                if(Tx_done)begin
                    Data <= Data72[71:64];
                    Send_Go <= 1;
                    state <= 9;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 8;
                end
            end     

            9:
            begin
                if(Tx_done)begin
                    Send_Go <= 0;
                    Trans_Done <= 1;
                    state <= 0;
                end
                else begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 9;
                end
            end 
            default:   
                begin
                    Data <= Data;
                    Send_Go <= 0;
                    state <= 0;
                end
        endcase
    end

    //例化子模块
     uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Data(Data),
        .Send_Go(Send_Go),
        .Baud_set(0),
        .uart_tx(uart_tx),
        .Tx_done(Tx_done)
    );

endmodule

3、子模块

//**************************************** Message ***********************************//
//技术交流:bumianzhe@126.com
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏 
//All rights reserved                                   
//----------------------------------------------------------------------------------------
// File name:           uart_byte_tx
// Last modified Date:  2022年5月22日15:20:10
// Last Version:        V1.1
// Descriptions:        uart协议
//----------------------------------------------------------------------------------------
//****************************************************************************************//

module uart_byte_tx(
    Clk,
    Reset_n,
    Data,
    Send_Go,
    Baud_set,
    uart_tx,
    Tx_done
);
    input Clk;
    input Reset_n;
    input [7:0]Data;
    input Send_Go;
    input [2:0]Baud_set;
    output reg uart_tx;
    output reg Tx_done;

    //系统时钟25MHZ
    //Baud_set = 0   就让波特率 = 9600;
    //Baud_set = 1   就让波特率 = 19200
    //Baud_set = 2   就让波特率 = 38400;
    //Baud_set = 3   就让波特率 = 57600;   
    //Baud_set = 4   就让波特率 = 115200; 

    reg [17:0]bps_DR;
    always@(*)
        case(Baud_set)
            0:bps_DR = 1000000000/9600/40;
            1:bps_DR = 1000000000/19200/40;
            2:bps_DR = 1000000000/38400/40;
            3:bps_DR = 1000000000/57600/40;
            4:bps_DR = 1000000000/115200/40;
            default:bps_DR = 1000000000/9600/40;
         endcase

    reg Send_en;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)       
        Send_en <= 0;
    else if(Send_Go)
        Send_en <= 1;
    else if(Tx_done)
        Send_en <= 0;

    reg [7:0]r_Data;
    always@(posedge Clk)
    if(Send_Go)
        r_Data <= Data;
    else
        r_Data <= r_Data;     

    wire bps_clk;
    assign bps_clk = (div_cnt == 1);

    reg [17:0]div_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        div_cnt <= 0;
    else if(Send_en)begin
        if(div_cnt == bps_DR - 1)
            div_cnt <= 0;
        else 
            div_cnt <= div_cnt + 1'b1;
    end
    else
        div_cnt <= 0;

    reg [3:0]bps_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bps_cnt <= 0;
    else if(Send_en)begin
        if(bps_clk)begin
            if(bps_cnt == 11)
                bps_cnt <= 0;
            else
                bps_cnt <= bps_cnt + 1'b1;
        end
    end
    else
        bps_cnt <= 0;

    //并转串,每算一个波特率时钟,就发送一位数据
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) begin
        uart_tx <= 1'b1;
    end
    else begin
        case(bps_cnt)
            1:uart_tx <= 0;
            2:uart_tx <= r_Data[0];
            3:uart_tx <= r_Data[1];
            4:uart_tx <= r_Data[2];
            5:uart_tx <= r_Data[3];
            6:uart_tx <= r_Data[4];
            7:uart_tx <= r_Data[5];
            8:uart_tx <= r_Data[6];
            9:uart_tx <= r_Data[7]; 
            10:uart_tx <= 1;
            11:begin uart_tx <= 1;end
            default:uart_tx <= 1;
        endcase
     end

     //发送完成后,对Tx_done进行拉高
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        Tx_done <= 0;
    else if((bps_clk == 1)  && (bps_cnt == 10))
        Tx_done <= 1;
    else
        Tx_done <= 0;

endmodule

三、工程获取
等项目完成了,会上传项目工程,暂时没时间整理。读者也可以自己建立工程,把这两个模块放进去就能看到代码。