基于STM32的逻辑分析仪

文章目录

  • 一、逻辑分析仪体验
    • 1、使用示例
      • 1.1 逻辑分析仪
      • 1.2 开源软件PulseView
    • 2、核心技术
      • 2.1 技术方案
      • 2.2 信号采集与存储
      • 2.3 数据上传
    • 3、使用逻辑分析仪
    • 4、 SourceInsight 使用技巧
      • 4.1新建工程
      • 4.2 设置工程名及工程数据目录
      • 4.3 指定源码目录
      • 4.4 添加源码
      • 4.5 同步文件
      • 4.6 操作示例
        • 4.6.1 在工程中打开文件
        • 4.6.2 在文件中查看函数或变量的定义
        • 4.6.3 查找函数或变量的引用
        • 4.6.4 其他快捷键
  • 二、逻辑分析仪SUMP协议分析
    • 1、硬件结构
    • 2、SUMP协议
      • 2.1 上位机发出的命令及参数
      • 2.2 下位机发送的回复
        • 2.2.1 CMD_ID 的回复
        • 2.2.2 CMD_METADATA 命令的回复
        • 2.2.3 上报的采样数据
      • 2.3 上位机和下位机时序图
        • 2.3.1 扫描操作的时序图
        • 2.3.2 对时序图进行源码分析
          • (1)上位机
          • (2)下位机
        • 2.3.3 逻辑分析仪的设置操作
          • (1)设置采样数
          • (2)设置采样频率
          • (3)使能或禁止通道
          • (4)设置通道的触发条件
        • 2.3.4 设置操作的时序图
        • 2.3.5 采样操作的时序图
  • 三、实现逻辑分析仪
    • 1、软件设计方案
      • 1.1 如何实现最高的采样率
      • 1.2 汇编指令
      • 1.3 精确测量时间
        • 1.3.1 测量读GPIO操作的时间
        • 1.3.2 测量读写buffer的时间
        • 1.3.3 测量NOP指令的时间
        • 1.3.4 逻辑右移
        • 1.3.5 加法操作
        • 1.3.6 测量处理Tick中断函数的时间
    • 2、实现功能
      • 2.1 方案修订
      • 2.2 编写程序
        • 2.2.1 采集数据
        • 2.2.2 上报数据
      • 2.3 上机演示
    • 3、改进功能
      • 3.1 使用说明
      • 3.2 最终程序的结构
      • 3.3 提高采样率
      • 3.4 增加改进USB串口功能
        • 3.4.1 在STM32CubeMX 增加USB 串口功能
        • 3.4.2 USB 串口收发函数改造
        • 3.4.3 提高USB串口发送效率
      • 3.5 使用RLE提升重复数据的传输效率
        • 3.5.1 RLE功能
        • 3.5.2 上位机使能RLE
        • 3.5.3 代码解读

一、逻辑分析仪体验

1、使用示例

1.1 逻辑分析仪

逻辑分析仪是分析数字信号的仪器,简单地说就是采集引脚的高低电平,按照某些协议分析多个引脚的信息(比如I2C、SPI信号)。

有些逻辑分析仪也可以采集模拟信号,是简化版的示波器。

使用场景如下:

在这里插入图片描述

1.2 开源软件PulseView

软件下载:https://sigrok.org/wiki/Downloads

在这里插入图片描述

2、核心技术

2.1 技术方案

PC机软件:可以使用开源软件PulseView。

逻辑分析仪的方案有很多种,产品级别的方案如下:

在这里插入图片描述

它一般都使用FPGA进行数据采集(高速、并行),把结果保存在大容量的DRAM里。采集完毕后,再通过单片机上传到PC进行分析:这被称为buffer模式。这种模式支持的采样率比较高,但是采集的时长、数据量取决于DRAM大小。

还有stream模式,一边采集数据一边传输给PC,它支持的采样时间比较长,但是采样率受限于数据接口(网卡、USB口、串口)的传输效率。

很多开源的逻辑分析仪只需要一个带USB口的单片机即可实现,方案如下:

在这里插入图片描述

跟PC之间的接口,可以使用USB,也可以使用普通的串口。即使使用USB时,大多数情况下也是模拟为USB串口。

2.2 信号采集与存储

本教程使用瑞士军刀(STM32F103)制作一个demo版本的逻辑分析仪。

信号的采集主要涉及2部分:信号电平、高精度的时间。如下图所示,你要分析这些信号,就需要知道它们的电平变化时刻、电平值:
在这里插入图片描述

2.3 数据上传

该项目中,先使用串口上传数据,这个比较简单,但是效率低。然后再使用板子的USB口模拟一个串口来上传数据,这样数据传输效率比较高。

3、使用逻辑分析仪

启动PulseView,如下操作可以识别出逻辑分析仪:

在这里插入图片描述

点击如下图标,可以捕获数据:

在这里插入图片描述

4、 SourceInsight 使用技巧

4.1新建工程

运行source Insight,点击菜单“Project->New Project”,如下图所示:

在这里插入图片描述

4.2 设置工程名及工程数据目录

在弹出的 New Project 对话框中设置“New project name”(项目的名称),然后设置 Where do you want to store the project data file? (项目文件保存位置),点击 Browse 按 钮选择源码的目录即可,如下图:

在这里插入图片描述

4.3 指定源码目录

设置源码目录:Project Source Directory – the main location of your source files”() 点击红框左边“…”选择源码目录,点击OK,如下图所示:

在这里插入图片描述

4.4 添加源码

在新弹出的对话框中,点击“Add”或“Add All”。“Add”是手动选择需要添加的文 件,而“Add All”是添加所有文件。我们使用“Add All”,在弹出的提示框中选中 “Recursively add lower sub-directories”(递归添加下级的子目录)并点击OK。同样的 Remove File,Remove All是移除单个文件或者移除所有文件,如下图所示:

在这里插入图片描述

添加文件完成后会弹出下面窗口,点击“确定”即可:

在这里插入图片描述

此时界面会返回到主界面,如下图所示,点击“Close”:

在这里插入图片描述

4.5 同步文件

同步文件的意思是让Source Insight去解析源码,生成数据库,这样有助于以后阅读 源码。比如点击某个函数时就可以飞快地跳到它定义的地方。 先点击菜单“Project->Synchronize Files”,如下图错误!未找到引用源。所示:

在这里插入图片描述

在弹出的对话框中 选中“Force all files to be re-parsed”(强制解析所有文件),并点击 “Start”按钮开始同步,如下图所示:

在这里插入图片描述

在这里插入图片描述

4.6 操作示例

前面建立工程后,就会自动打开了工程。如果下次你想打开工程,启动Souce Insight 后,点击菜单“Project -> Open Porject”就可以在一个列表中选择以前建立的工程,如下 图所示:

在这里插入图片描述

4.6.1 在工程中打开文件

点击"P"图标打开文件列表,双击文件打开文件,也可以输入文件名查找文件,如下图 所示:

在这里插入图片描述

4.6.2 在文件中查看函数或变量的定义

打开文件后,按住ctrl键的同时,用鼠标点击函数、变量,就会跳到定义它的位置, 如下图所示:

在这里插入图片描述

4.6.3 查找函数或变量的引用

双击函数,右键点击弹出对话框选择“Lookup Reference”;或者双击函数后,使用 快捷键"ctrl+/"来查找引用,如下图所示:

在这里插入图片描述

4.6.4 其他快捷键

在这里插入图片描述

二、逻辑分析仪SUMP协议分析

根据使用流程分析上位机程序、下位机程序的交互过程,就可以弄清楚逻辑分析仪的 协议。逻辑分析仪的协议有很多种类型,我们使用的上位机程序,借用了“openbench logic-sniffer”逻辑分析仪的代码,它使用SUMP协议。SUMP协议网址:

https://www.sump.org/projects/analyzer/protocol/

1、硬件结构

下图是逻辑分析仪的最简单的硬件结构:

① 上位机、下位机之间使用串口通信

② 下位机使用GPIO采集数据

注意:商用的逻辑分析仪一般使用FPGA采集数据,使用USB跟上位机通信。

在这里插入图片描述

2、SUMP协议

2.1 上位机发出的命令及参数

上位机和下位机之间的交互过程为:

① 上位机发送命令、数据

② 下位机“可能”回复

完整的命令和参数说明如下:

命令命令值作用
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 最后一个字节的bit3表示启动触发 功能
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_SET_FLAGS,它的数值为32位的数据,含义如下(bit2bit5对于group14,比如bit2为0表示group 1使能):

#define CAPTURE_FLAG_RLEMODE1 (1 << 15)

#define CAPTURE_FLAG_RLEMODE0 (1 << 14)

#define CAPTURE_FLAG_RESERVED1 (1 << 13)

#define CAPTURE_FLAG_RESERVED0 (1 << 12)

#define CAPTURE_FLAG_INTERNAL_TEST_MODE (1 << 11)

#define CAPTURE_FLAG_EXTERNAL_TEST_MODE (1 << 10)

#define CAPTURE_FLAG_SWAP_CHANNELS (1 << 9)

#define CAPTURE_FLAG_RLE (1 << 8)

#define CAPTURE_FLAG_INVERT_EXT_CLOCK (1 << 7)

#define CAPTURE_FLAG_CLOCK_EXTERNAL (1 << 6)

#define CAPTURE_FLAG_DISABLE_CHANGROUP_4 (1 << 5)

#define CAPTURE_FLAG_DISABLE_CHANGROUP_3 (1 << 4)

#define CAPTURE_FLAG_DISABLE_CHANGROUP_2 (1 << 3)

#define CAPTURE_FLAG_DISABLE_CHANGROUP_1 (1 << 2)

#define CAPTURE_FLAG_NOISE_FILTER (1 << 1)

#define CAPTURE_FLAG_DEMUX (1 << 0)
其中CAPTURE_FLAG_DISABLE_CHANGROUP_1-4 其中有等于1的则该group被禁止 若为0则该group为使能状态 并且只有当所有channel被禁止才为1 若有部分channel被禁止而已则该group还是为0(使能状态)

2.2 下位机发送的回复

2.2.1 CMD_ID 的回复

上位机发送CMD_ID命令(“0x02”)给下位机后,下位机要回复4个字节“1ALS”。

2.2.2 CMD_METADATA 命令的回复

上位机发送CMD_ID命令(“0x04”)给下位机后,下位机要回复很多参数给上位机。参数格式为“1字节的数据类别,多个字节的数据”,说明如下:

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

对于我们借用的“openbench-logic-sniffer”逻辑分析仪协议,它上报的数据是:先上报最后一个采样的数据,最后上报第1个采样点的数据。

每一个采样的数据里,含有使能的 group 的数据,比如使能了 group1、group2、group4,没有使能 group3,那么上报的一个采样数据为 3 字节:第 1 字节对应 group1 的channel 0~7,第 2 字节对应 group2 的 channel 8~15,第 3 字节对应 group4 的 channel 24~31。

每一个字节数据里,bit0对应这个group里最低的通道的值

2.3 上位机和下位机时序图

2.3.1 扫描操作的时序图

要理解时序图,建议对比着上位机源码、下位机源码进行分析:

// 上位机

sigrok-util_src_patched\libsigrok\src\hardware\openbench-logic-sniffer\api.c,scan函数

// 下位机

LogicAnalyzer_F103\Core\Src\logicanalyzer.c,LogicalAnalyzerTask函数

启动PulseView,如下操作可以识别出逻辑分析仪:

在这里插入图片描述

其中涉及的操作、时序图,如下:

在这里插入图片描述

2.3.2 对时序图进行源码分析
(1)上位机

sacn函数

在这里插入图片描述

在这里插入图片描述

ols_get_metadata函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对应如下:

在这里插入图片描述

(2)下位机

logicanalyzer.c

在这里插入图片描述

在这里插入图片描述

2.3.3 逻辑分析仪的设置操作

SUMP 协议的逻辑分析仪,最大支持32个采样通道,分为4组:group 1、group 2、group 3、group 4,每组含有8个通道:channel 0~channel 7属于group 1,以此类推。

(1)设置采样数

如下可以设置采样数:

在这里插入图片描述

(2)设置采样频率

在这里插入图片描述

(3)使能或禁止通道

可以选择是否使能某个channel:

在这里插入图片描述

当32 个通道都使能时,一次采样得到32位数据,bit0 对应通道0,bit31 对应通道31。

当group n里8个通道都禁止的话,那么一次采样就可以少传输1字节。比如group 3里的channel 16~channel 23 都被禁止后,一次采样就可以得到3字节数据,bit16原来对应channel 16,现在对应channel 24,以此类推。

注意:只有当1个group中的所有8个通道都被禁止才少传输1字节,若1个group中的所有通道未全部被禁止,则还是传输原字节数

(4)设置通道的触发条件

可以设置采样的触发条件(对于使能了触发的多个通道,只要有某个通道的值符合触发条件了,就会开始采样):

在这里插入图片描述

2.3.4 设置操作的时序图

在启动采样之前,上位机会把前面设置的参数发给下位机,代码如下:

在这里插入图片描述

ols_prepare_acquisition 函数在如下文件中:

// 上位机 ols_prepare_acquisition函数

sigrok-util_src_patched\libsigrok\src\hardware\openbench-logic-sniffer\protocol.c

分析它的代码,得到如下时序图:

在这里插入图片描述

上位机source insight代码如下:

在这里插入图片描述

下位机source insight代码如下:

在这里插入图片描述

2.3.5 采样操作的时序图

当用户点击如下按钮后,上位机会先设置逻辑分析仪,最后发送启动命令开始采集数据;采集完毕后,上位机会放送停止命令。时序图如下:

在这里插入图片描述

三、实现逻辑分析仪

1、软件设计方案

商用的逻辑分析仪一般使用 FPGA 采集数据,并且有比较大的内存(比如 512MB)。FPGA 可以使用非常高的速率采集数据,然后存放在内存里。

对于单片机,内存小,速度慢,我们需要压榨它的性能。

1.1 如何实现最高的采样率

逻辑分析仪采样数据的示例代码如下:

// 1. 关闭中断 
// 2. 循环 
while (1) 
{ // 2.1 读GPIO // 2.2 写buffer // 2.3 延时 
} 
// 3. 开中断

为了达到最高的采样率,循环里面的“读GPIO”、“写buffer”操作使用汇编指令;循环里的“延时”代码要去除。在这种情况下,我们再去测量一次循环的耗时,就可以算出最高采样率。

当使用更低的采样率时,在上述代码里插入“耗时”操作。

这都需要我们事先知道“读GPIO”、“写buffer”、“延时指令”的耗时。

1.2 汇编指令

为了简化程序,我们只使用PB8PB15这8个引脚。要去读取这几个引脚的数值,需要读取GPIOB_IDR寄存器。这个寄存器的说明在“2_官方资料\2.0_STM32F103xx参考手册(英文原版)【重要】.pdf”里,寄存器的bit815分别对应PB8PB15引脚,对应上位机的channel07。寄存器如下:

在这里插入图片描述

注:LDR 表示读取四个字节 LDRH 表示读取两个字节 LDRB 表示读取一个字节

读GPIO 的汇编指令代码为:

LDR R1, =0x40010C08

LDR R0, [R1]

写buffer 的汇编指令代码为:

LDR R1, =buffer地址

STR R0, [R1]

在汇编里,我们可以在循环之间插入几条NOP指令来实现延时,比如:

NOP

NOP

1.3 精确测量时间

先列出结果,精确测量的时间列表如下:

操作汇编指令耗时
读取GPIO//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 中断处理10ns

我们需要测量读一次GPIO的精确时间,写一次buffer的精确时间,一条NOP指令执行的时间。

示例代码如下:

// 1. 关闭中断 
{ // 2.1 设置某个引脚为高电平 // 2.2 循环100万次读GPIO,或写Buffer,或执行NOP指令 // 2.3 设置某个引脚为低电平 
} 
// 3. 开中断 

我们借助外部工具监测GPIO引脚为高电平的时间,就可以算出每次操作的耗时。可以使用逻辑分析仪来测量GPIO为高电平的时间。

逻辑分析仪测量PA15,如下图接线:

在这里插入图片描述

1.3.1 测量读GPIO操作的时间

相关寄存器位置如下:

在这里插入图片描述

在汇编中引脚配置如下:

在这里插入图片描述

测试函数的代码为:

void MeaSureTime(void)
{/* 1.让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);HAL_Delay(100);/* 2.关中断 */__disable_irq();/* 3.1 让引脚输出高电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET);/* 3.2 执行汇编指令 *///for(int i = 0; i < 10000; i++)asm_measure();/* 3.3 让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);/* 4.开中断 */__enable_irq();
}

在汇编里读GPIO的代码为:

				THUMBAREA    |.text|, CODE, READONLY; asm_measure handler
asm_measure    PROCEXPORT  asm_measure; 设置PA15输出高电平; 使用汇编让PA15输出高电平供测量LDR  R1, =0x40010810LDRH  R0,= [1 << 15]STR  R0,  [R1]; 读GPIO 手写100条指令LDR  R1, =0x40010c08LDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDR    LDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDR    LDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDR    LDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDRLDRH  R0, [R1]  ; 读GPIOB_IDR; 设置PA15输出低电平; 使用汇编让PA15输出低电平供测量LDR  R1, =0x40010810LDRH  R0,= [1 << 31]STR  R0,  [R1]BX  LR  ; 返回ENDP

读100次GPIO,耗时:4.39us;读1次GPIO耗时约为44ns。

在这里插入图片描述

1.3.2 测量读写buffer的时间

在汇编里读内存的代码为:

				 THUMBAREA	 |.text|, CODE, READONLY; asm_measure_r_mem handler
asm_measure_r_mem	PROCEXPORT  asm_measure_r_mem; 设置PA15输出高电平LDR  R1, =0x40010810LDR  R0, =(1<<15)STR  R0, [R1]LDR  R1, =0x20000000LDRH  R0, [R1]  ; 读内存(100); 设置PA15输出低电平LDR  R1, =0x40010810LDR  R0,= (1 << 31)STR  R0,  [R1]BX  LRENDP

读100次内存,耗时:1.54us;读1次内存耗时约为15ns。

在这里插入图片描述

在汇编里写内存的代码为:

				 THUMBAREA	 |.text|, CODE, READONLY; asm_measure_r_mem handler
asm_measure_r_mem	PROCEXPORT  asm_measure_r_mem; 设置PA15输出高电平LDR  R1, =0x40010810LDR  R0, =(1<<15)STR  R0, [R1]LDR	R1, =0x20000000STRB  R0, [R1]	; 写内存(100); 设置PA15输出低电平LDR  R1, =0x40010810LDR  R0,= (1 << 31)STR  R0,  [R1]BX  LRENDP

写100次内存,耗时:1.58us;读1次内存耗时约为16ns。

在这里插入图片描述

1.3.3 测量NOP指令的时间

在汇编里写内存的代码为:

				 THUMBAREA	 |.text|, CODE, READONLY; asm_measure_r_mem handler
asm_measure_r_mem	PROCEXPORT  asm_measure_r_mem; 设置PA15输出高电平LDR  R1, =0x40010810LDR  R0, =(1<<15)STR  R0, [R1]NOP(写100次); 设置PA15输出低电平LDR  R1, =0x40010810LDR  R0,= (1 << 31)STR  R0,  [R1]BX  LRENDP

执行100次NOP指令,耗时:1.49us;执行1次NOP指令耗时约为15ns。
在这里插入图片描述

1.3.4 逻辑右移

在这里插入图片描述

在汇编里写内存的代码为:

				 THUMBAREA	 |.text|, CODE, READONLY; asm_measure_r_mem handler
asm_measure_r_mem	PROCEXPORT  asm_measure_r_mem; 设置PA15输出高电平LDR  R1, =0x40010810LDR  R0, =(1<<15)STR  R0, [R1]LSR R0, #8(写100次); 设置PA15输出低电平LDR  R1, =0x40010810LDR  R0,= (1 << 31)STR  R0,  [R1]BX  LRENDP

执行100次“LSR R0, #8”指令,耗时:2.4us;执行1次指令耗时约为24ns。

在这里插入图片描述

1.3.5 加法操作

在汇编里写内存的代码为:

				 THUMBAREA	 |.text|, CODE, READONLY; asm_measure_r_mem handler
asm_measure_r_mem	PROCEXPORT  asm_measure_r_mem; 设置PA15输出高电平LDR  R1, =0x40010810LDR  R0, =(1<<15)STR  R0, [R1]ADD R0, #1(写100次); 设置PA15输出低电平LDR  R1, =0x40010810LDR  R0,= (1 << 31)STR  R0,  [R1]BX  LRENDP

执行100次“ADD R0, #1”指令,耗时:2.33us;执行1次指令耗时约为23ns。

在这里插入图片描述

1.3.6 测量处理Tick中断函数的时间

测量关闭中断情况下一段代码的执行时间(约为10s):

void MeaSureTime(void)
{/* 1.让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);HAL_Delay(100);/* 2.关中断 */__disable_irq();/* 3.1 让引脚输出高电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET);/* 3.2 执行汇编指令 */for(int i = 0; i < 2277984; i++) /* 10s(执行一次asm_measure()的时间为4.39us 通过换算得到10s约需要执行2277984次) */asm_measure();/* 3.3 让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);/* 4.开中断 */__enable_irq();
}

在这里插入图片描述
在这里插入图片描述

测量开中断情况下,同一段代码的执行时间:
在这里插入图片描述

void MeaSureTime(void)
{/* 1.让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);HAL_Delay(100);/* 2.关中断 *///__disable_irq();/* 3.1 让引脚输出高电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET);/* 3.2 执行汇编指令 */for(int i = 0; i < 2277984; i++) /* 10s(执行一次asm_measure()的时间为4.39us 通过换算得到10s约需要执行2277984次)*/asm_measure();/* 3.3 让引脚输出低电平 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);/* 4.开中断 *///__enable_irq();
}

相差:10.1939508(开启中断) - 10.0943512(关闭中断) = 0.0995996s,对应 10000次 tick(一个Tick1ms执行一次 10s对应10000个Tick) 中断,每次tick 中断耗时:0.0995996/10000=0.00995996ms=9.95996us,约为 10us。

2、实现功能

2.1 方案修订

精确测量的时间列表如下:

操作汇编指令耗时
读取GPIO//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

对于如下代码:

// 1. 关闭中断 
// 2. 循环 
while (1) 
{ // 2.1 读GPIO、逻辑右移 // 2.2 写buffer、累加地址 // 2.3 延时 
} 
// 3. 开中断 

去掉延时,循环一次耗时 44+24+16+23=107ns,理论上最高的采样频率=1/107ns=9MHz。而 STM32F103C8 的内存为 20K,即使全部用来保存采样的数据,也只能保存20*1024/9000000=0.002 秒,没有任何实用价值。

即使降低采样频率,比如降到100KHz(I2C 频率一般为100KHz,再低的话就没有实用价值了),20K内存全部用来保存采样数据,能保存20*1024/100000=0.2048秒,也没有什l么使用价值。

瓶颈在于:用来保存采样数据的内存太小了。看看商用的逻辑分析仪,它的内存是巨大的:

在这里插入图片描述

在有限的内存里,我们需要提高内存的使用效率:不变的数据就不要保存了。新方案如下:

① 定义两个数组:uint8_t data_buf[5000]、uint8_t cnt_buf[5000]

② 以比较高的、频率周期性地读取GPIO的值(可以启动定时器,但不能使能中断,因为Tick中断会耗时)

③ 只有GPIO值发生变化了,才存入data_buf[i++];GPIO 值无变化时,cnt_buf[i-1]累加

④ 以后,根据data_buf、cnt_buf恢复各个采样点的数据,上报给上位机

假设data_buf大小为5000,能记录5000个变化的数据,这足够我们日常使用了。

其他考虑:使用新方案后,能记录很长时间的数据,在程序运行期间,要判断是否收到“上位机发来的CMD_XOFF停止命令”,所以:串口接收中断要打开。

2.2 编写程序

核心代码为“Core\Src\logicanalyzer.c”,主要有2大功能:

① 采集数据:读取GPIO数据、保存起来

② 上报数据:把数据发给上位机

当下位机收到“CMD_ARM_BASIC_TRIGGER”命令后,启动采集、上报:

在这里插入图片描述

2.2.1 采集数据

start 函数采集数据,功能为:

① 禁止中断:这是为了在采集数据时以最快的频率采集,不让中断干扰。 除了串口中断之外,其他中断都禁止。下位机只有tick中断、串口中断,所以只需要 禁止tick中断。 保留串口中断的原因在于:上位机可能发来命令停止采样。

② 等待触发条件:用户可能设置触发采样的条件

③ 触发条件满足后,延时一会:没有必要

④ 循环:以最高频率采样 退出的条件有三:收到上位机发来的停止命令、采集完毕、数据buffer已经满

⑤ 恢复中断

关键功能是采集、记录数据:

/* 记录第一个数据 */
data = (*data_reg) >> 8;
g_rxdata_buf[0] = data;
g_rxcnt_buf[0] = 1;
g_cur_sample_cnt = 1;
pre_data = data;/* 4. 以最高的频率采集数据 */
#ifdef USE_ASM_TO_SAMPLE
sample_function();
#else
while (1)
{        *pa15_reg = (1<<15); /* PA15输出高电平 *//* 4.1 读取数据 */data = (*data_reg) >> 8;/* 4.2 保存数据 */        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;/* 4.3 串口收到停止命令 */if (get_stop_cmd)break;/* 4.4 采集完毕? */if (g_cur_sample_cnt >= g_convreted_sample_count)break;/* 4.5 buffer满? */if (g_cur_pos >= BUFFER_SIZE)break;/* 4.6 加入这些延时凑出1MHz,加入多少个nop需要使用示波器或逻辑分析仪观察、调整 */__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );__asm volatile( "nop" );*pa15_reg = (1UL<<31); /* PA15输出低电平 */
}
2.2.2 上报数据

采集数据时是以最大频率采集的,比如以 1MHz 采集。如果上位机要求的采样频率是 200KHz:1MHz/200KHz=5,采集到的数据量是上报数据量的5倍。我们只需要每隔5个数据 上报一个即可。

static void upload (void)
{int32_t i = g_cur_pos;uint32_t j;uint32_t rate = MAX_FREQUENCY / g_samplingRate;int cnt = 0;for (; i >= 0; i--){for (j = 0; j < g_rxcnt_buf[i]; j++){cnt++;  /* 我们以最大频率采样, 假设最大频率是1MHz* 上位机想以200KHz的频率采样* 那么在得到的数据里, 每5个里只需要上报1个*/if (cnt == rate) {uart_send(&g_rxdata_buf[i], 1, 10);cnt = 0;}}}
}

2.3 上机演示

为了采集数据,设置下位机的PB3输出周期为1ms、占空比为50%的PWM波。让PB8连接到PB3,PB8是channel 0,就可以再上位机观察channel 0的波形。 如下图接线(串口也要接):

在这里插入图片描述
在这里插入图片描述

3、改进功能

3.1 使用说明

PB8~PB15 是通道 0~7,可以用来连接要测试的引脚。注意:测量其他电子设备时要共地。

PB3 输出周期1ms、50%占空比的PWM波,可以用来验证功能是否正常。

如下图连接:

在这里插入图片描述

采样示例:

在这里插入图片描述

3.2 最终程序的结构

在这里插入图片描述

3.3 提高采样率

使用汇编采集数据,把最大采样频率提高一倍,达到2MHz。

在工程的“Core\Src\logicanalyzer.h”中定义“USE_ASM_TO_SAMPLE”,就可以使用汇编代码采集数据,达到2MHz的采样频率。

在这里插入图片描述

汇编代码在“Core\Src\operation.S”中,如下:

BUFFER_SIZE equ 2700 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; 设置PA15的值备用 LDR R11, =0X40010810 LDR R12, =(1<<15) LDR LR, =(1<<31) 
Loop     ; 设置PA15输出高电平 STR R12, [R11] 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 ; 设置PA15输出高电平 STR LR, [R11] 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     

3.4 增加改进USB串口功能

3.4.1 在STM32CubeMX 增加USB 串口功能

第1步:使能USB功能,如下图操作:

在这里插入图片描述

第2步:设置时钟,确保CPU频率为最大的72MHz,USB频率为48MHz,如下图:

在这里插入图片描述

第3步:添加USB串口第3方组件,如下图:

在这里插入图片描述

烧录程序后,使用 USB 线连接开发板和 PC,可以在 PC 的设备管理器看到新的串口设备:

在这里插入图片描述

注意:由于硬件设计问题,每次烧写程序后都要重新接插USB线。要调试USB 串口功能的话,每次启动调试之后也要重新接插USB线。

硬件问题在于 USB 口的使能引脚常拉高,导致无法让 PC 重新识别 USB 设备(只能重插):

在这里插入图片描述

3.4.2 USB 串口收发函数改造

当下位机通过USB口接收到数据时,它的如下函数被调用:

在这里插入图片描述

当下位机想通过USB口发送数据时,使用如下函数:

在这里插入图片描述

发送函数需要改造,如下:

在这里插入图片描述

代码如下:

/** * @brief  Data received over USB OUT endpoint are sent over CDC interface *         through this function. * *         @note *         This function will issue a NAK packet on any OUT packet received on *         USB endpoint until exiting this function. If you exit this function *         before transfer is complete on CDC interface (ie. using DMA controller) *         it will result in receiving more data while previous ones are still *         not sent. * * @param  Buf: Buffer of data to be received * @param  Len: Number of data received (in bytes) * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL */ 
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) 
{ /* USER CODE BEGIN 6 */ for (uint32_t i = 0; i < *Len; i++) { circle_buf_write(&g_uart_rx_bufs, Buf[i]); } USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); USBD_CDC_ReceivePacket(&hUsbDeviceFS); return (USBD_OK); /* USER CODE END 6 */ 
} /* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */ 
uint8_t usb_send(uint8_t *datas, int len, int timeout) 
{ USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData; while(1) { if (hcdc->TxState == 0) { break; } if (timeout--) { mdelay(1); } else { return HAL_BUSY; } } return CDC_Transmit_FS(datas, len); 
}
3.4.3 提高USB串口发送效率

虽然USB速度远高于UART,但是如果使用USB传输数据时是一个字节一个字节地传输,那么效率极低。我们需要根据USB的特性,一次尽可能传输更多数据。STM32F103的USB传输,一次能传输最多64字节的数据。上位机怎么知道当前数据传输完毕了呢?下位机可以 传输少于 64 字节的数据,上位机就知道当前传输完毕了(没传完你干嘛不传输 64 字节 呢?);如果下位机刚好要传输64字节的数据,那么USB驱动还要额外传输一个“零包” 给上位机,为了避免传输零包,我们尽量每次传输63字节。
在这里插入图片描述
代码如下:

/********************************************************************** 
* 函数名称: uart_save_in_buf_and_send 
* 功能描述: 使用USB传输时,一个一个字节地传输效率非常低,尽量一次传输64字节 
* 输入参数: datas - 保存有要发送的数据 
*            
len - 数据长度 
*            
timeout - 超时时间(ms) 
*            
flush - 1(即刻发送), 0(可以先缓存起来) 
* 输出参数: 无 
* 返 回 值: 无 ***********************************************************************/ 
static void uart_save_in_buf_and_send(uint8_t *datas, int len, int timeout, int flush) 
{ static uint8_t buf[64]; static int32_t cnt = 0; for (int32_t i = 0; i < len; i++) { buf[cnt++] = datas[i]; /* 先存入buf, 凑够63字节再发送 */ if (cnt == 63) { /* 对于USB传输,它内部发送64字节数据后还要发送一个零包 * 所以我们只发送63字节以免再发送零包 */ uart_send(buf, cnt, timeout); cnt = 0; } } /* 如果指定要"flush"(比如这是最后要发送的数据了), 则发送剩下的数据 */ if (flush && cnt) { uart_send(buf, cnt, timeout); cnt = 0; } 
}

3.5 使用RLE提升重复数据的传输效率

3.5.1 RLE功能

假设要传输9个相同的数据,比如9个0x12,那么常规方法就要发送9个0x12。如果规定发送的数据里,某一个数据表示“相同的数据个数”,后面跟着这个数据,不就只需要发送2个字节的数据了吗?比如“0x09 0x12”。我们怎么分辨一个数据是长度,还是数据本身?可以使用最高位来分辨:比如0x89表示要传输9个数据(SUMP协议里表示要传输10个数据),0x12表示数据本身。缺点是:数据里最高位必须清为0。

RLE: Run Length Encoding,在数据里嵌入长度。在传输重复的数据时可以提高效率。

SUMP协议里规定:

① 传输长度:最高位为1,去掉最高位的数值为n,表示有(n+1)个数据

② 传输数据:数据的最高位必须为0

例子1:对于8通道的数据,channel 7就无法使用了。要传输10个数据0x12时,只需要传输2字节:0x89 0x12。0x89的最高位为1,表示有(9+1)个相同的数据,数据为0x12。

例子2:对于32通道的数据,channel 31就无法使用了。要传输10个数据 0x12345678时,只需要传输8字节:0x09 0x00 0x00 0x80 0x78 0x56 0x34 0x12 “0x09 0x00 0x00 0x80"的最高位为1,表示有(9+1)个相同的数据,数据为"0x78 0x56 0x34 0x12”

3.5.2 上位机使能RLE

如下设置:

在这里插入图片描述

3.5.3 代码解读

代码如下:

/********************************************************************** * 函数名称: upload * 功能描述: 上报数据 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ 
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++;   /* 我们以最大频率采样, 假设最大频率是1MHz * 上位机想以200KHz的频率采样 * 那么在得到的数据里, 每5个里只需要上报1个 */ if (cnt == rate)  { if (g_flags & CAPTURE_FLAG_RLE) { /* RLE : Run Length Encoding, 在数据里嵌入长度, 在传输重复的数据时可以提高效率 * 先传输长度: 最高位为1表示长度, 去掉最高位的数值为n, 表示有(n+1)个数据 * 再传输数据本身 (数据的最高位必须为0) * 例子1: 对于8通道的数据, channel 7就无法使用了 * 要传输10个数据 0x12时, 只需要传输2字节: 0x89 0x12 * 0x89的最高位为1, 表示有(9+1)个相同的数据, 数据为0x12 *  * 例子2: 对于32通道的数据, channel 31就无法使用了 * 要传输10个数据 0x12345678时, 只需要传输8字节: 0x09 0x00 0x00 0x80 0x78 0x56 0x34 0x12 * "0x09 0x00 0x00 0x80"的最高位为1, 表示有(9+1)个相同的数据, 数据为"0x78 0x56 0x34 0x12" */ 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_save_in_buf_and_send(&pre_data, 1, 100, 0); else { /* 如果前面的数据大于1个,则使用RLE编码 */ rle_cnt = 0x80 | (rle_cnt - 1); uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0); uart_save_in_buf_and_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_save_in_buf_and_send(&rle_cnt, 1, 100, 0); uart_save_in_buf_and_send(&pre_data, 1, 100, 0); rle_cnt = 0; } } else { /* 上位机没有起到RLE功能则直接上传 */ uart_save_in_buf_and_send(&g_rxdata_buf[i], 1, 100, 0); } cnt = 0; } } } /* 发送最后的数据 */ if ((g_flags | CAPTURE_FLAG_RLE) && rle_cnt) { if (rle_cnt == 1) uart_save_in_buf_and_send(&pre_data, 1, 100, 0); else { rle_cnt = 0x80 | (rle_cnt - 1); uart_save_in_buf_and_send(&rle_cnt, 1, 100, 0); uart_save_in_buf_and_send(&pre_data, 1, 100, 0); } } /* 为了提高USB上传效率,我们"凑够一定量的数据后才发送", * 现在都到最后一步了,剩下的数据全部flush、上传 */ uart_save_in_buf_and_send(NULL, 0, 100, 1); 
} 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在windows上使用Docker部署一个简易的web程序

使用Docker部署一个python的web服务&#x1f680; 由于是从事算法相关工作&#xff0c;之前在项目中&#xff0c;需要将写完的代码服务&#xff0c;部署在docker上&#xff0c;以此是开始接触了Docker这个工具&#xff0c;由于之前也没系统学习过&#xff0c;之后应该可能还会用…

视频压缩大小怎么压缩?几种简单视频压缩方法教给你

现如今&#xff0c;视频已成为我们生活和工作中不可或缺的一部分。然而&#xff0c;高清视频往往伴随着庞大的文件体积&#xff0c;这给存储和传输带来了不小的挑战。这时候我们就需要对视频进行压缩处理&#xff0c;方便储存和发送&#xff0c;那么怎么有效压缩视频呢&#xf…

java之回合制游戏以及如何优化

public class Role {private String name;private int blood;//空参public Role() {}//包含全部参数的构造public Role(String name, int blood) {this.name name;this.blood blood;}public String getName() {return name;}public void setName(String name) {this.name na…

提交高通量测序原始数据到 SRA --- 操作流程

❝ 写在前面 由于最近在提交课题数据到 NCBI 数据库&#xff0c;整理了相关笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. 提交高通量测序数据到 GEO --- 说明书 目录 1 注册 NCBI 账…

【C++】关联容器探秘:Map与Multimap详解

目录 1.映射类 map 0. 引入 pair&#xff1a; 1.定义 2.插入 3. 遍历 4.❗operator[]的实现 5. 插入 运用 2.Multimap 类 0. 引入&#xff1a;不去重的 Multi 1. Multimap 不支持 Operator[] 2. Multimap 的删除 1.映射类 map 0. 引入 pair&#xff1a; 在C中&…

1 go语言环境的搭建

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;由浅入深讲解Go语言&#xff0c;希望大家都能够从中有所收获&#xff0c;也请大家多多支持。 查看相关资料与知识库 专栏地址:Go专栏 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;大家一起学习&#xff0c;…

软件测试---测试需求分析

课程目标 什么是软件测试需求 软件测试需求的必要性 如何对软件测试需求进行分析&#xff08;重点&#xff09; 课程补充 灰度测试&#xff08;基于功能&#xff09;&#xff1a;先发布部分功能&#xff0c;然后看用户的反馈&#xff0c;再去发布另外一部分的功能更新。 A/B测…

运筹学笔记

计算的时间问题&#xff01;计算机解决了计算量的问题&#xff01; 计算机的发展对运筹学研究起到了极大的促进作用。 运筹学的一个特征之一是它常常会考虑寻求问题模型的最佳解决方案&#xff08;称为最优解&#xff09;。 没有人能成为运筹学所有方面的专家。 分析学越来越流…

C++学习笔记04-补充知识点(问题-解答自查版)

前言 以下问题以Q&A形式记录&#xff0c;基本上都是笔者在初学一轮后&#xff0c;掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系&#xff0c;也适合做查漏补缺和复盘。 本文对读者可以用作自查&#xff0c;答案在后面&#xff0…

国内微短剧系统平台抖音微信付费小程序app开发源代码交付

微短剧作为当下热门的内容&#xff0c;结合抖音平台的广泛用户基础&#xff0c;开发微短剧付费小程序APP具有显著的市场潜力&#xff0c;用户对于短剧内容的需求旺盛&#xff0c;特别是在言情、总裁、赘婿等热门题材方面&#xff0c;接下来给大家普及一下微短剧小程序系统。 顺…

rce漏洞-ctfshow(50-70)

Web51 if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\|\%|\x09|\x26/i", $c)){ system($c." >/dev/null 2>&1"); } Nl&#xff0c;绕过tac&#xff0c;cat&#xff0c;绕…

Pytest进阶之fixture的使用(超详细)

目录 Fixture定义 Fixture使用方式 作为参数使用 Fixture间相互调用(作为参数调用) 作为conftest.py文件传入 Fixture作用范围Scope function class module session Fixture中params和ids Fixture中autouse Fixture中Name 总结 pytest fixture 是一种用来管理测试…

回溯算法(相关解题):

求子集序列&#xff1a; 解题思路&#xff1a; 已知原集合的数据位数为N&#xff0c;则可以通过二进制比对原来集合&#xff0c;二进制位为1则输出集合上的该位数据&#xff0c;为0则空&#xff0c;二进制的01排序规律与子集的输出一致由集合的位数可以判断出二进制的范围 0 ~…

P3-AI产品经理-九五小庞

AI产品的数据流向 美团外卖&#xff0c;实时只能调度 美团28分钟送达需求的分析 AI产品常用的算法 常用算法 常见的AI算法解析 自然语言生成NLG语音识别&#xff1a;科大讯飞&#xff0c;通义千问 虚拟现实机器学习平台 决策管理系统生物特征识别技术 RPA(机器人流程自动…

UE4-光照重建

当我们拉入新的光源和模型到我们的场景中后&#xff0c;会产生这样的情况&#xff1a; Preview:预览 表示此时由于光照物体所产生的阴影都是预览级别的并不是真正的效果。 方法一&#xff1a; 或者也可以在世界大纲中选中我们的光源&#xff0c;然后将我们的光源改变为可以…

判断字符串,数组方法

判断字符串方法 在JavaScript中&#xff0c;可以使用typeof操作符来判断一个变量是否为字符串。 function isString(value) {return typeof value string; } 判断数组 在JavaScript中&#xff0c;typeof操作符并不足以准确判断一个变量是否为数组&#xff0c;因为typeof会…

《昇思25天学习打卡营第25天|第27天》

今天是学习的第二十七天&#xff0c;今天学习的是应用实践篇中计算机视觉中ShuffleNet图像分类。 从对ShuffleNet网络介绍开始学习&#xff0c;模型架构&#xff08;Pointwise Group Convolution&#xff0c;Channel Shuffle&#xff0c;ShuffleNet模块&#xff0c;构建Shuffl…

# Redis 入门到精通(九)-- 主从复制(1)

Redis 入门到精通&#xff08;九&#xff09;-- 主从复制&#xff08;1&#xff09; 一、redis 主从复制 – 主从复制简介 1、互联网“三高”架构 高并发高性能高可用 2、你的“Redis”是否高可用&#xff1f; 1&#xff09;单机 redis 的风险与问题 问题1.机器故障  现…

C++ 鼠标轨迹API【神诺科技SDK】

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;使得神诺科技 能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.…

java之对称字符串以及如何优化(链式编程)

import java.util.Scanner;public class ScannerDemo12 {public static void main(String[] args) {Scanner sc new Scanner(System.in);System.out.println("请输入一个字符串");String str sc.next();//链式编程StringBuilder sbnew StringBuilder();sb.append(s…