当前位置: 首页 > news >正文

基于STM32的逻辑分析仪

目录

  • 制约性能因素
  • 协议
    • 命令
    • 下位机回复
      • CMD_ID的回复
      • CMD_METADATA命令的回复
      • 上报的采样数
    • 设置
    • 使用开源软件PulseView设置操作
      • 1.设置采样数
      • 2.设置采样频率
      • 3.使能或禁止通道
      • 4.设置通道的触发条件
  • 实现
    • 准备
      • 汇编指令
      • 精确测量时间
    • 程序
      • C语言初实现
        • 采集数据
        • 上报数据
      • 使用汇编提高采样率

制约性能因素

逻辑分析仪的方案有很多种,产品级别的一般都使用FPGA进行数据采集。

仅使用stm32比较简易,有以下制约因素

  • 内存大小
  • 数据采集速率
  • 上报速率

协议

使用SUMP协议,使用串口通信

命令

命令命令值作用
CMD_RESET0x00复位下位机
CMD_ID0x02让下位机上报ID
CMD_METADATA0x04让下位机上报参数
CMD_SET_BASIC_TRIGGER_MASK00xC0使能某个通道的触发功能
示例数值:0x01 0x02 0x00 0x00
表示channel0, 9 使能了触发功能
CMD_SET_BASIC_TRIGGER_VALUE00xC1设置通道的触发值
示例数值:0x01 0x00 0x00 0x00
表示channel 0的触发值为高电平
channel 9的触发值为低电平
CMD_SET_BASIC_TRIGGER_CONFIG00xC2最后一个字节的bit3为1表示启动触发功能
示例数值:0x00 0x00 0x00 0x08
CMD_SET_DIVIDER0x80根据用户设置的采样频率计算出分频系数
注意:
当采样频率大于100MHz时,会"Enable demux mode",让逻辑分析工作于200MHz,分频系数=200MHz/采样频率 - 1
当采样频率小于100MHz时,分频系数=100MHz/采样频率 - 1
示例数值:0xf3 0x01 0x00 0x00
0x01f3=499=100MHz/200KHz - 1
CMD_CAPTURE_SIZE0x81使用1个命令发送READCOUNT、DELAYCOUNT两个参数
示例数值:0x0c 0x00 0x0c 0x00
前2字节表示要采样的次数为0x0c * 4 = 48
后2字节表示要延迟的次数为0x0c * 4 = 48
CMD_SET_FLAGS0x82设置flag,比如使用启动demux模式,根据用户选择的通道,使能group(见后面注释)
CMD_CAPTURE_DELAYCOUNT0x83示满足触发条件开始采样后,延迟多少次采样,才保存数据
示例数值:0x0c 0x00 0x00 0x00
表示延迟次数为0x0c * 4 = 48
CMD_CAPTURE_READCOUNT0x84表示要采样的次数
示例数值:0x0c 0x00 0x00 0x00
表示采样次数为0x0c * 4 = 48

下位机回复

CMD_ID的回复

上位机发送CMD_ID后,下位机要回复ID

CMD_METADATA命令的回复

上报的数据类别上报的数据说明
0x01“name”名字
0x20大字节序的4字节最大采样通道数
0x21大字节序的4字节保存采样数据的buffer大小
0x22大字节序的4字节动态内存大小(未使用)
0x23大字节序的4字节最大采样频率
0x24大字节序的4字节协议版本
0x401字节最大采样通道数
0x411字节协议版本
0x00结束标记

上报的采样数

它上报的数据是:先上报最后一个采样的数据,最后上报第1个采样点的数据。

设置

  • 采样次数

  • 采样频率

  • 对引脚分组,如有32个引脚,可分为group1到4,group1:channel0~7,group2:8 ~ 15等等。一个组上报一个字节的数据
    如果只想使用某些引脚,需要使能或禁止通道。如果禁止group1,需上报3个字节的数据,如果禁止channel2,仍需上报4个字节(组中所有通道都被禁止了,组对应的字节才不需要采集)

  • 由于内存很小,都采集的话浪费内存,可以选择设置采集的触发条件

使用开源软件PulseView设置操作

1.设置采样数

在这里插入图片描述

2.设置采样频率

在这里插入图片描述

3.使能或禁止通道

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b03d74ee94a64b2d83900d33775cd94d.png
当groupn里8个通道都禁止的话,那么一次采样就可以少传输1字节。比如group3里的channe116~channel23都被禁止后,一次采样就可以得到3字节数据,bit16原来对应channel16,现在对应channel24,以此类推。

4.设置通道的触发条件

可以设置采样的触发条件(对于使能了触发的多个通道,只要有某个通道的值符合触发条件了,所有通道都会开始采样):在这里插入图片描述

实现

由于单片机内存小,速度慢,为了实现最高频率采样,采用汇编代码,并且要测量出汇编代码的执行时间,一条汇编指令耗费的时间。当使用较低的采样率时,可以加入延时

准备

汇编指令

  • 读指令(load):LDR(4bit)LDRH(2bit)LDRB(1bit)把数据从内存加载到寄存器中。

  • 写指令(store):STR(4bit)STRH(2bit)STRB(1bit)将寄存器中的数据存储到内存中

  • LDR R0,[R1] //去R1表示的地址读4个字节的数据到CPU的R0寄存器

  • LDR R1,=0x20000000 //伪指令:编译器会帮我们替换为真正的指令,标识:= 表示把后面的地址 0x20000000加载到 R1 寄存器中,也就是说 R1 现在存储的是一个内存地址

  • STR R0,[R1] //吧R0的数据写到R1所指示的位置

  • 读GPIO两条指令:
    LDR R1,=0x20000000
    LDR R0,[R1]

  • 延时指令
    NOP

精确测量时间

采用示波器或更高级的逻辑分析仪来测量每次操作的耗时(可通过测量GPIO引脚高电平的时间)

void MeasureTime(void)
{
	/* 先让引脚为低电平 */
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);
	HAL_Delay(100);
	/*关中断*/
	__disable_irq();
	
	/*汇编指令*/
	asm_measure();
	/*开中断*/
	__enable_irq();

	
}

                THUMB
                AREA    |.text|, CODE, READONLY

; asm_measure handler
asm_measure    PROC
                 EXPORT  asm_measure
    ; 设置PA15输出高电平
    LDR R1, =0X40010810
    LDR R0, =(1<<15)
    STR R0, [R1]

    LDR  R1, =0x40010C08
    LDR  R0, [R1]  ; 读GPIOB_IDR100次
    LDR  R0, [R1]  ; 读GPIOB_IDR100次
    LDR  R0, [R1]  ; 读GPIOB_IDR100次
	......
    LDR  R0, [R1]  ; 读GPIOB_IDR100次
    LDR  R0, [R1]  ; 读GPIOB_IDR100次     

    ; 设置PA15输出低电平
    LDR R1, =0X40010810
    LDR R0, =(1<<31)
    STR R0, [R1]

    BX LR
                 ENDP


使用汇编来操作GPIO,发现读100次GPIO,耗时4.36us
类似的,可以利用
LDR R1, =0x20000000 LDR R0, [R1] ;
重复执行若干次来测量读内存的时间,测得一次读内存约为15ns
写内存,NOP,逻辑左移右移,加法减法的操作也是类似
还可通过执行某一段代码,在其前面禁用中断,可测出中断函数的时间

精确测量的时间如下

操作汇编指令耗时
读取 GPIO0//R1 为 0x40010C08
LDR R0, [R1]
44ns
读内存//R1 为 0x20000000
LDR R0, [R1]
15ns
写内存//R1 为 0x20000000
STRB R0, [R1]
16ns
NOP 指令NOP15ns
逻辑右移LSR R0, #824ns
累加ADD R0, #123ns
Tick 中断处理10us

逻辑分析仪读取数据需以下几步

  • 读GPIO,逻辑右移
  • 写内存,累加地址
  • (是否)延时

去掉延时,循环一次耗时44+24+16+23=107ns,理论上最高的采样频率=1/107ns=9MHz
而STM32F103C8的内存为20K,即使全部用来保存采样的数据,也只能保存20*1024/9000000=0.002秒
在有限的内存里,我们需要提高内存的使用效率:不变的数据就不要保存了

  • 定义两个数组uint8_t data_buf[5000],uint8_t cnt_buf[5000]
  • 以比较高的、频率周期性地读取GPIO的值
  • 只有GPI0值发生变化了,才存入data_buf[i++];GPIO值无变化时,cnt_buf[i-1]累加

程序

C语言初实现

采集数据

①禁止中断:这是为了在采集数据时以最快的频率采集,不让中断干扰。但要保留串口中断
,原因在于:上位机可能发来命令停止采样。
②等待触发条件:用户可能设置触发采样的条件
③触发条件满足后,延时一会:没有必要
④循环:以最高频率采样
退出的条件有三:收到上位机发来的停止命令、采集完毕、数据buffer已经满
⑤恢复中断

static void start (void)
{
    uint8_t data;
    uint8_t pre_data;
    volatile uint16_t *data_reg = (volatile uint16_t *)0x40010C08; /* GPIOB_IDR */

    g_convreted_sample_count = g_sampleNumber * (MAX_FREQUENCY / g_samplingRate);
    get_stop_cmd = 0;
    g_cur_pos = 0;
    g_cur_sample_cnt = 0;
    
    (void)pre_data;
    (void)pa15_reg;
    
    /* 除了串口中断,其他中断都禁止 */
    Disable_TickIRQ();

    memset(g_rxcnt_buf, 0, sizeof(g_rxcnt_buf));

    /* 等待触发条件 */
    if (g_triggerState && g_triggerMask)
    {
        while (1)
        {
            data = (*data_reg) >> 8;
            
            if (data & g_triggerMask & g_triggerValue)
                break;
            
            if (~data & g_triggerMask & ~g_triggerValue)
                break;
            
            if (get_stop_cmd)
                return;
        }
    }

    data = (*data_reg) >> 8;
    g_rxdata_buf[0] = data;
    g_rxcnt_buf[0] = 1;
    g_cur_sample_cnt = 1;
    pre_data = data;

    /* 采集数据 */
    while (1)
    {        
        /* 读取数据 */
        data = (*data_reg) >> 8;

        /* 保存数据 */        
        g_cur_pos += (data != pre_data)? 1 : 0; /* 数据不变的话,写位置不变 */
        g_rxdata_buf[g_cur_pos] = data;         /* 保存数据 */
        g_rxcnt_buf[g_cur_pos]++;               /* 增加"相同的数据"个数 */
        g_cur_sample_cnt++;                     /* 累加采样个数 */
        pre_data = data;

        /* 停止条件 */
        /* 串口收到停止命令 */
        if (get_stop_cmd)
            break;

        /* 采集完毕 */
        if (g_cur_sample_cnt >= g_convreted_sample_count)
            break;

        /* buffer满 */
        if (g_cur_pos >= BUFFER_SIZE)
            break;

        /* 根据实际情况加入延时凑出1MHz */
        __asm volatile( "nop" );
        __asm volatile( "nop" );
       ......
        __asm volatile( "nop" );
    }

    /* 使能被禁止的中断 */
    Enable_TickIRQ();
}
上报数据
static void upload (void)
{
    int32_t i = g_cur_pos;
    uint32_t j;
    uint32_t rate = MAX_FREQUENCY / g_samplingRate;
    int cnt = 0;
    uint8_t pre_data;
    uint8_t data;
    uint8_t rle_cnt = 0;
    
	for (; i >= 0; i--)
	{
        for (j = 0; j < g_rxcnt_buf[i]; j++)
        {
            cnt++;  
            if (cnt == rate) 
            {
                if (g_flags & CAPTURE_FLAG_RLE)
                {
                    /* RLE : Run Length Encoding, 将连续出现的相同数据,用该数据的值以及出现的次数来表示, 在传输重复的数据时可以提高效率,但会少一个通道 */
                    
                    data = g_rxdata_buf[i] & ~0x80; /* 使用RLE时数据的最高位要清零 */;
                    
                    if (rle_cnt == 0)
                    {
                        pre_data = data;
                        rle_cnt = 1;
                    }
                    else if (pre_data == data)
                    {
                        rle_cnt++; /* 数据相同则累加个数 */
                    }
                    else if (pre_data != data)
                    {
                        /* 数据不同则上传前面的数据 */
                    
                        if (rle_cnt == 1) /* 如果前面的数据只有一个,则无需RLE编码 */
                            uart_send(&pre_data, 1, 100, 0);
                        else
                        {
                            /* 如果前面的数据大于1个,则使用RLE编码 */
                            rle_cnt = 0x80 | (rle_cnt - 1);
                            uart_send(&rle_cnt, 1, 100, 0);
                            uart_send(&pre_data, 1, 100, 0);
                        }
                        pre_data = data;
                        rle_cnt = 1;
                    }

                    if (rle_cnt == 128)
                    {
                        /* 对于只有8个通道的逻辑分析仪, 只使用1个字节表示长度,最大长度为128,当相同数据个数累加到128个时,就先上传 */
                        rle_cnt = 0x80 | (rle_cnt - 1);
                        uart_send(&rle_cnt, 1, 100, 0);
                        uart_send(&pre_data, 1, 100, 0);
                        rle_cnt = 0;
                    }
                }
                else
                {
                    /* 上位机没有起到RLE功能则直接上传 */
                    uart_send(&g_rxdata_buf[i], 1, 100, 0);
                }
                
                cnt = 0;
            }
        }
	}

使用汇编提高采样率

使用汇编采集数据,可使得最高采样率达到2MHz

BUFFER_SIZE equ 3100  ; 注意这个数值要跟logicanalyzer.c中的BUFFER_SIZE保持一致


                THUMB
                AREA    |.text|, CODE, READONLY

; sample_function handler
sample_function    PROC
                 EXPORT  sample_function
                IMPORT g_rxdata_buf
                IMPORT g_rxcnt_buf
                IMPORT g_cur_pos
                IMPORT g_cur_sample_cnt
                IMPORT get_stop_cmd
                IMPORT g_convreted_sample_count
                 
    PUSH     {R4, R5, R6, R7, R8, R9, R10, R11, R12, LR}
    LDR R0, =g_rxdata_buf  ; 得到这些变量的地址
    LDR R1, =g_rxcnt_buf   ; 得到g_rxcnt_buf变量的地址
    LDR R2, =g_cur_pos     ; 得到g_cur_pos变量的地址
    LDR R2, [R2]           ; 得到g_cur_pos变量的值
    LDR R3, =g_cur_sample_cnt
    LDR R3, [R3]
    LDR R4, =get_stop_cmd
    LDR R5, =g_convreted_sample_count
    LDR R5, [R5]

    LDR R8, [R0]  ; pre_data
    LDR R10, =BUFFER_SIZE

    LDR  R6, =0x40010C08

    LDR LR, =(1<<31)
Loop  
  
    LDRH R7, [R6]  ; 读GPIOB_IDR
    LSR R7, #8    ; data = (*data_reg) >> 8;
    CMP R7, R8
    ADDNE R2, #1  ; g_cur_pos += (data != pre_data)? 1 : 0;
    STRB R7, [R0, R2] ; g_rxdata_buf[g_cur_pos] = data;    
    MOV R8, R7        ; pre_data = data
    LDR R7, [R1, R2, LSL #2] ; R7 = g_rxcnt_buf[g_cur_pos]
    ADD R7, #1
    STR R7, [R1, R2, LSL #2] ; g_rxcnt_buf[g_cur_pos]++;
    ADD R3, #1    ; g_cur_sample_cnt++;

    CMP R3, R5    ; if (g_cur_sample_cnt >= g_convreted_sample_count) break;
    BGE LoopDone

    LDR R7, [R4]  ; R7 = get_stop_cmd
    CMP R7, #0    ; if (get_stop_cmd) break;
    BNE LoopDone

    CMP R2, R10    ; if (g_cur_pos >= BUFFER_SIZE) break;
    BGE LoopDone

    NOP
    NOP         ; 延时, 凑出2MHz
    
        
    B Loop
    
LoopDone
    LDR R0, =g_cur_pos     ; 得到g_cur_pos变量的地址,并不是得到它的值
    STR R2, [R0]           ; 保存g_cur_pos变量的值
    LDR R0, =g_cur_sample_cnt
    STR R3, [R0]           ; 保存g_cur_sample_cnt变量的值
    
    POP     {R4, R5, R6, R7, R8, R9, R10, R11, R12, PC}
    ENDP

相关文章:

  • MoonSharp 文档二
  • 蓝桥杯FPGA-ds1302驱动
  • 九点标定和十二点标定的区别
  • 【问题记录】如何编译nv_peer_memory模块?依赖OFED的4个目录和2类文件?如何解决没有rdma/peer_mem.h文件?
  • Python 远程抓取服务器日志最后 1000行
  • Vue3 路由的历史记录 如何不允许浏览器前进后退 在函数中使用路由切换组件 路由的重定向
  • 鸿基智启:东土科技为具身智能时代构建确定性底座
  • 英国赫瑞瓦特大学激光雷达领域研究概述2025.3.11
  • 计算机毕业设计:公寓管理系统
  • Ubuntu本地部署Open manus(完全免费可用)
  • 【OpenCV C++】存图,如何以时间命名,“年月日-时分秒“产生唯一的文件名呢?“年月日-时分秒-毫秒“ 自动检查存储目录,若不存在自动创建存图
  • FB投广探秘:为何Facebook广告账户不消耗
  • Unity安卓Android从StreamingAssets加载AssetBundle
  • Redis的高可用
  • 深入解析K8s VolumeMounts中的subPath字段及其应用
  • 怎么使用数据集微调大模型LLM
  • DeepSeek技术名词全解析:一场属于中国AI的“觉醒时刻”
  • Manus演示案例: 英伟达财务估值建模 解锁投资洞察的深度剖析
  • Trae IDE:解锁 AI 驱动的高效编程体验
  • 网络安全之RSA算法
  • 免费外链发布平台在线/百度seo营销推广多少钱
  • 做网站怎么实现在线支付/营销推广计划
  • 免费单页网站在线制作/线上推广策划方案
  • 做国外网站做什么内容/百度指数快刷软件
  • 网站进不去怎么解决/网站推广代理
  • 做网站玩玩/站长之家是干什么的