基于STM32的逻辑分析仪

发布于:2025-03-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

制约性能因素

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

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

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

协议

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

命令

命令 命令值 作用
CMD_RESET 0x00 复位下位机
CMD_ID 0x02 让下位机上报ID
CMD_METADATA 0x04 让下位机上报参数
CMD_SET_BASIC_TRIGGER_MASK0 0xC0 使能某个通道的触发功能
示例数值:0x01 0x02 0x00 0x00
表示channel0, 9 使能了触发功能
CMD_SET_BASIC_TRIGGER_VALUE0 0xC1 设置通道的触发值
示例数值:0x01 0x00 0x00 0x00
表示channel 0的触发值为高电平
channel 9的触发值为低电平
CMD_SET_BASIC_TRIGGER_CONFIG0 0xC2 最后一个字节的bit3为1表示启动触发功能
示例数值:0x00 0x00 0x00 0x08
CMD_SET_DIVIDER 0x80 根据用户设置的采样频率计算出分频系数
注意:
当采样频率大于100MHz时,会"Enable demux mode",让逻辑分析工作于200MHz,分频系数=200MHz/采样频率 - 1
当采样频率小于100MHz时,分频系数=100MHz/采样频率 - 1
示例数值:0xf3 0x01 0x00 0x00
0x01f3=499=100MHz/200KHz - 1
CMD_CAPTURE_SIZE 0x81 使用1个命令发送READCOUNT、DELAYCOUNT两个参数
示例数值:0x0c 0x00 0x0c 0x00
前2字节表示要采样的次数为0x0c * 4 = 48
后2字节表示要延迟的次数为0x0c * 4 = 48
CMD_SET_FLAGS 0x82 设置flag,比如使用启动demux模式,根据用户选择的通道,使能group(见后面注释)
CMD_CAPTURE_DELAYCOUNT 0x83 示满足触发条件开始采样后,延迟多少次采样,才保存数据
示例数值:0x0c 0x00 0x00 0x00
表示延迟次数为0x0c * 4 = 48
CMD_CAPTURE_READCOUNT 0x84 表示要采样的次数
示例数值: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字节 协议版本
0x40 1字节 最大采样通道数
0x41 1字节 协议版本
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 指令 NOP 15ns
逻辑右移 LSR R0, #8 24ns
累加 ADD R0, #1 23ns
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