目录
- 制约性能因素
- 协议
- 命令
- 下位机回复
- CMD_ID的回复
- CMD_METADATA命令的回复
- 上报的采样数
- 设置
- 使用开源软件PulseView设置操作
- 1.设置采样数
- 2.设置采样频率
- 3.使能或禁止通道
- 4.设置通道的触发条件
- 实现
- 准备
- 汇编指令
- 精确测量时间
- 程序
- C语言初实现
- 采集数据
- 上报数据
- 使用汇编提高采样率
制约性能因素
逻辑分析仪的方案有很多种,产品级别的一般都使用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.使能或禁止通道
当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 指令 | 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保持一致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