欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 基于STM32的逻辑分析仪

基于STM32的逻辑分析仪

2025/3/11 6:25:36 来源:https://blog.csdn.net/2401_85904908/article/details/145967475  浏览:    关键词:基于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();}
THUMBAREA    |.text|, CODE, READONLY; asm_measure handler
asm_measure    PROCEXPORT  asm_measure; 设置PA15输出高电平LDR R1, =0X40010810LDR R0, =(1<<15)STR R0, [R1]LDR  R1, =0x40010C08LDR  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, =0X40010810LDR R0, =(1<<31)STR R0, [R1]BX LRENDP

使用汇编来操作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保持一致THUMBAREA    |.text|, CODE, READONLY; sample_function handler
sample_function    PROCEXPORT  sample_functionIMPORT g_rxdata_bufIMPORT g_rxcnt_bufIMPORT g_cur_posIMPORT g_cur_sample_cntIMPORT get_stop_cmdIMPORT g_convreted_sample_countPUSH     {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_cntLDR R3, [R3]LDR R4, =get_stop_cmdLDR R5, =g_convreted_sample_countLDR R5, [R5]LDR R8, [R0]  ; pre_dataLDR R10, =BUFFER_SIZELDR  R6, =0x40010C08LDR LR, =(1<<31)
Loop  LDRH R7, [R6]  ; 读GPIOB_IDRLSR R7, #8    ; data = (*data_reg) >> 8;CMP R7, R8ADDNE 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 = dataLDR R7, [R1, R2, LSL #2] ; R7 = g_rxcnt_buf[g_cur_pos]ADD R7, #1STR 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 LoopDoneLDR R7, [R4]  ; R7 = get_stop_cmdCMP R7, #0    ; if (get_stop_cmd) break;BNE LoopDoneCMP R2, R10    ; if (g_cur_pos >= BUFFER_SIZE) break;BGE LoopDoneNOPNOP         ; 延时, 凑出2MHzB LoopLoopDoneLDR R0, =g_cur_pos     ; 得到g_cur_pos变量的地址,并不是得到它的值STR R2, [R0]           ; 保存g_cur_pos变量的值LDR R0, =g_cur_sample_cntSTR R3, [R0]           ; 保存g_cur_sample_cnt变量的值POP     {R4, R5, R6, R7, R8, R9, R10, R11, R12, PC}ENDP

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com