ESM7000异构CPU实时应用之三
8路并行数采应用程序

 2021-5-14 16:41:19     作者:黄志超    

  英创公司推出了ESM7000异构CPU的实时应用,由高性能的Cortex-A7双核完成人机交互、数据处理、通讯管理等复杂运算,而对于高速的数据采集、中断事件响应等实时任务交由i.MX7D的Cotex-M4完成,具体的介绍可以参考文章《ESM7000异构CPU架构实时应用简介》。基于ESM7000的异构CPU平台,英创公司实现了多通道的高速数采方案,该方案最高支持100kSPS采样率的AD转换。


  关于M4端的AD操作的实现可以参考文章《ESM7000异构CPU实时应用之一 8通道并行100K 16bit AD采集》,本文主要介绍Linux系统中需要做的操作以及程序的实现,文章分为三个部分,第一部分是介绍如何将编译好的M4程序烧写到主板中,并设置自动启动。第二部分是介绍如何加载英创公司提供的RPMsg驱动,RPMsg驱动主要实现了ESM7000中Crotex-A7核心与M4核心之间的通讯。第三部分是介绍Linux系统端User app的实现和性能的测试结果,其中User app正是通过英创公司定义的RPMsg通讯协议来实现的,关于RPMsg和通讯协议具体的介绍可以参考文章《ESM7000异构CPU实时应用之二 基于rpmsg的通讯机制》


  本文测试硬件平台基于ESM2001工控机,感兴趣的客户可参考这里:EMX2000系列工控机


M4应用程序的烧写


  首先将编译好的M4应用程序(这里以英创公司提供的例程emx2001.bin为例)拷贝到主板中的,例如/mnt/mmc中,然后就可以通过英创公司提供的工具flash_opt将程序烧写到系统中,并设置好随系统自动启动。flash_opt工具已经集成在了主板中,可以直接在命令执行。


  M4的应用程序可以选择三个区域烧写,分别是TCM、OCRAM 或 DDR 中运行,在TCM中的运行速度最快,但是代码空间最小,OCRAM中运行速度会慢一些,代码空间相对更大,而DDR中代码运行速度最慢,但是代码空间最大。各个区域提供的程序代码空间具体数据如下表所示:


Configure文件名CODE Size
TCMemx2001_tcm.bin32K
OCRAMemx2001_ocram.bin128K
DDRemx2001_ddr.bin1MB


  在烧写前需要根据不同的烧写区域对程序重命名,如果需要烧写到TCM 中,则程序需要命名为 emx2001_tcm.bin,如果烧写到 OCRAM 中,则命名为 emx2001_ocram.bin,如果是烧写到 DDR,则为 emx2001_ddr.bin。重命名只是为了满足 flash_opt 对命令格式的要求,程序在编译时必须根据运行的位置选择对应的 ESM7000_M4_*.ld 文件。


  比如我们需要将emx2001.bin放在TCM中,则将程序命名为emx2001_tcm.bin,然后输入命令#>flash_opt m4 /mnt/mmc/ emx2001_tcm.bin,如下图:


ESM7000异构CPU实时应用之三 8路并行数采应用程序.png


  完成之后,每次主板重启就都会自动启动烧写进去的M4应用程序。关于程序放在不同区域的具体的说明和对比,可以参考《ESM7000 Cortex-M4技术开发参考手册》,这里不再赘述。


RPMsg驱动的加载


  RPMsg的驱动也同样集成在了系统中,可以直接通过命令加载,只需要一条命令#>modprobe emx_rpmsg_tty,加载后驱动会生成响应的设备节点/dev/ttyEMx同时会打印出关于AD的信息,如下图:


ESM7000异构CPU实时应用之三 8路并行数采应用程序.png


  从驱动打印的信息可以看到AD的详细参数信息。驱动加载完成后,就可以运行User app来进行采集了。


User app的实现和测试


  通过《ESM7000异构CPU实时应用之二 基于rpmsg的通讯机制》的介绍,我们已经了解了英创公司设计的基于RPMsg的通讯协议,本文介绍的User app正是使用这一套通讯协议来实现的。


  驱动emx_rpmsg_tty.ko会虚拟出串口设备,所以通信部分的代码都是使用的串口的标准操作函数来实现的。首先是向M4发送消息,设置好AD的采样率,然后启动AD,通过write函数就可以向M4发送数据,英创公司已经封装好了函数供客户参考:


int EM_Adc::setup_adc( int freq )  
{  
    int ret;  
    struct emx_rpmsg_t  *msg_t;  
  
    //打开虚拟串口ttyEMx,并建立好接收线程  
    ret = OpenPort();  
    if( ret<0 )  
    {  
        printf( "ttyEMx open fail\n");  
        return -1;  
    }  
  
    //发送复位指令  
    ret = Send_cmd(CMD_CODE_RESET, NULL, 0);  
    if( ret<0 )  
    {  
        printf( "send failed\n");  
    }  
      
    //发送设置指令,设置AD的采样率  
    ret = Send_cmd(CMD_CODE_SETUP, (void *)&freq, sizeof(freq));  
    if( ret<0 )  
    {  
        printf( "send failed\n");  
    }  
  
    //发送启动指令,开始采集  
    ret = Send_cmd(CMD_CODE_RUN, NULL, 0);  
    if( ret<0 )  
    {  
        printf( "send failed\n");  
    }  
    Dflags = 1;  
  
    return 0;  
}

 

  启动了AD之后,M4就会开始进行数据采集,因为多通过告诉采集的数据量十分庞大,所以采用了共享内存的方式来发送数据。例程定义了两块32KB大小的共享区域,当M4采集的数据量达到16K之后,就会将数据写入共享内存中,然后根据通讯协议发送消息通知User app去读取。我们还是通过一个线程来专门接收采集数据,具体代码如下:


int EM_Adc::PackagePro( char* Buf, int len )  
{  
    int         i1, num;  
    int         buf_len;  
    char        status_buf[100];  
  
    memcpy(&msg, (char *)Buf, sizeof(struct emx_rpmsg_t));  
    if(msg.cmd == CMD_CODE_DATA) {  
        Mlen = msg.len;  
        //判断数据存放在哪一块共享区域,然后将数据拷贝出来  
        if(msg.flags == 0) {  
            memcpy(MemBuf, (void *)base0, msg.len);  
        }  
        else {  
            memcpy(MemBuf, (void *)base1, msg.len);  
        }  
        //处理数据,这里是将每个通道的数据保存成文件  
        save_data(MemBuf, Mlen);  
    }  
  
    return 0;  
}

 

  例程中数据的处理主要是把每个通道的数据分离出来,分别保存在不同的文件中。为了保证写文件的速度,一开始是写在内存中,当文件大小超过1MB,就关闭AD,并且将文件拷贝到/mnt/mmc中储存,处理数据的代码如下:


int EM_Adc::save_data(int16_t *data, int len)  
{  
    FILE    *fp;  
    char filename[20];  
    int     i1, i2, ret;  
  
    for(i1=0; i1<8; i1++) {  
        sprintf( filename, "/tmp/ch%d", i1);  
        fp = fopen(filename, "ab+");  
        if( fp==NULL )  
        {  
                printf("open %s fail!\n", filename);  
                ret = -1;  
                return ret;  
        }  
  
        //将每个通道的数据分离出来,分别保存在文件中  
        for(i2=0; i2<(len/16); i2++) {  
            memcpy(&CHxBuf[i2], ((int16_t *)data + i1 + i2*8), sizeof(int16_t));  
        }  
        fwrite( CHxBuf, (i2-1)*sizeof(int16_t), 1, fp );  
  
        //如果保存数据已经大于1M,则关闭AD,将文件移至/mnt/mmc目录保存,然后退出程序  
        if(i1 == 7) {  
            fseek(fp, 0, SEEK_END);  
            long length  = ftell(fp);  
            if(length > 1024*1024) {  
                fclose(fp);  
                system("cp /tmp/ch* /mnt/mmc");  
                stop_adc();  
                return 0;  
            }  
        }  
        fclose(fp);  
    }  
    return 0;  
}

 

  在实际测试的时候,我们将两路正弦波信号分别接入通道1和通道2,当采集完成后使用波形绘制工具读取保存的文件,效果如下:


ESM7000异构CPU实时应用之三 8路并行数采应用程序.png


  从图中可以看到采集波形的效果,在实际应用中,客户可以根据需求自行修改对于数据处理的方式。


  M4端应用程序采用的方式是每采集16000字节发送一次数据,为了验证这套方案能够满足应用需求,我们对M4通过RPMsg发出消息,到Linux系统中User app读取到共享内存数据的时间做了详细的测试,响应时间如下表:


采样率平均时间(us)最长时间(us)
5ksps298495
10ksps300489
50ksps287488
100ksps287469


  按照100ksps的采样率计算,M4端每10us会采集一次,每个通道单次采集数据大小为2Byte,8个通道就为16Byte。所以在100Ksps采样率的情况下,每10ms就会发送一次数据。按照这个时间间隔来看,ESM7000异构CPU平台完全可以满足需求。


  对这套方案感兴趣的客户可以和英创公司的工程师联系,索取相关例程的代码。