基于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.rhkb.cn/news/385924.html

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

相关文章

如何在调整节拍时间的过程中保持生产流程的稳定性?

在快节奏的工业生产领域&#xff0c;节拍时间&#xff08;Takt Time&#xff09;——即完成一个完整产品所需的标准时间&#xff0c;是维持生产效率和流程稳定性的关键指标。然而&#xff0c;市场需求的波动、技术升级或是生产线的微调&#xff0c;都可能要求我们对节拍时间进行…

云计算实训13——DNS域名解析、ntp时间服务器配置、主从DNS配置、多区域DNS搭建

一、DNS域名解析 1.正向解析 将域名解析为IP地址 DNS正向解析核心配置 (1)安装bind [rootdns ~]# yum -y install bind (2)编辑配置文件 编辑named.conf文件&#xff0c;限定访问权限 [rootdns ~]# vim /etc/named.conf 编辑named.rfc文件&#xff0c;指定要访问的域名 [ro…

VulnHub:colddbox easy

靶机下载地址 信息收集 主机发现 攻击机网段192.168.31.0/24&#xff0c;扫描同网段存活主机。 nmap 192.168.31.0/24 -Pn -T4 发现靶机&#xff0c;IP为192.168.31.176。 端口扫描 扫描靶机开放端口。 nmap 192.168.31.176 -A -p- -T4 开放了80,4512端口&#xff0c;注…

无人机之安全防护篇

在当今社会&#xff0c;园区的安全防护至关重要。无人机以其独特的视角、高效的作业能力和灵活的部署方式&#xff0c;为园区安防工作带来了前所未有的好处&#xff0c;显著提升了安全防护水平和管理效率。 一、全方位无死角的监控 与传统的固定摄像头相比&#xff0c;它不受位…

【Python Web】Flask扩展开发指南

Flask是一个轻量级的Python Web框架&#xff0c;它提供了丰富的扩展库和工具&#xff0c;可以帮助开发者快速构建Web应用。本篇博客将介绍如何进行Flask扩展开发&#xff0c;包括扩展的创建、配置、使用等方面的内容。 目录 Flask扩展开发指南 一、Flask扩展简介 二、创建Fl…

苹果CMS:资源采集站如何设置定时采集详细教程讲解

我们搭建好站点之后&#xff0c;会自定义一些采集&#xff0c;但是需要每天去手动执行&#xff0c;有时候甚至会忘记&#xff0c;那我们如何处理呢&#xff1f;今天我们就来介绍一下如何设置定时器。 如果按照官方例子来设置定时器会遇到一个问题就是采集的资源未绑定类型&…

[240728] Wikidata 介绍 | 微软与 Lumen 合作提升人工智能算力

目录 Wikidata 介绍微软与 Lumen 合作提升人工智能算力 Wikidata 介绍 中文&#xff1a; 文言: 粤语&#xff1a; 来源&#xff1a; https://www.wikidata.org/wiki/Wikidata:Introduction/zh 微软与 Lumen 合作提升人工智能算力 为了满足人工智能工作负载不断增长的需求&am…

查看路由表 netstat -r

“Kernel IP routing table” 是Linux系统中用于展示和配置IP路由的表。它告诉操作系统如何将数据包从一个网络接口发送到另一个网络或主机。下面是对您给出的路由表条目的解释&#xff1a; Destination&#xff1a;目的地地址&#xff0c;可以是具体的IP地址&#xff0c;也可…

「12月·长沙」人工智能与网络安全国际学术会议(ISAICS 2024)

人工智能与网络安全国际学术会议(ISAICS 2024)将于2024年12月20日-2024年12月22日在湖南长沙召开。会议中发表的文章将会被收录,并于见刊后提交EI核心索引。会议旨在在为国内与国际学者搭建交流平台,推进不同学科领域的融合发展&#xff0c;就当今人工智能与网络安全范畴内各学…

视觉-语言大模型应用

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

MySQL练习05

题目 步骤 触发器 use mydb16_trigger; #使用数据库create table goods( gid char(8) primary key, name varchar(10), price decimal(8,2), num int);create table orders( oid int primary key auto_increment, gid char(10) not null, name varchar(10), price decima…

蚓链数字化生态平台:构建城市智能商业,引领协同发展新潮流

​在当今数字化飞速发展的时代&#xff0c;城市商业的运行模式正在经历着数字化变革。蚓链数字化生态平台应运而生&#xff0c;以其强大的功能和创新的理念&#xff0c;成为构建城市智能商业枢纽中心的关键力量&#xff0c;推动着平台互通、业务贯通、管理协同的全新发展格局。…

这代码,给我看破防了……

之前有读者问&#xff0c;自己一直不明白如何写出合理的代码注释。 这也是不少程序员一直头疼的问题&#xff0c;比如接手新代码时&#xff0c;没有注释&#xff0c;完全搞不清逻辑&#xff1b;自己写的注释&#xff0c;跟不上代码修改&#xff0c;成了误导&#xff1b;复杂逻…

《Java初阶数据结构》----7.<优先级队列PriorityQueue>

前言 大家好&#xff0c;我目前在学习java。之前也学了一段时间&#xff0c;但是没有发布博客。时间过的真的很快。我会利用好这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区…

Postman测试工具详细解读

目录 一、Postman的基本概念二、Postman的主要功能1. 请求构建2. 响应查看3. 断言与自动化测试4. 环境与变量5. 集合与文档化6. 与团队实时协作 三、Postman在API测试中的重要性1. 提高测试效率2. 保障API的稳定性3. 促进团队协作4. 生成文档与交流工具 四、Postman的使用技巧1…

buu做题(9)

[MRCTF2020]PYWebsite 有个二维码 扫了一下啊二维码 function enc(code){hash hex_md5(code);return hash;}function validate(){var code document.getElementById("vcode").value;if (code ! ""){if(hex_md5(code) "0cd4da0223c0b280829dc3ea4…

【SpringCloud】企业认证、分布式事务,分布式锁方案落地-1

目录 HR企业入驻 HR企业入驻 - 认证流程解析 HR企业入驻 - 查询企业是否存在 HR企业入驻 - 上传企业logo与营业执照 HR企业入驻 - 新企业&#xff08;数据字典与行业tree结构解析&#xff09; 行业tree 行业tree - 创建节点 行业tree - 查询一级分类 行业tree - 查询子分…

FOC笔记(一)电角度零点校准

当电机上电时&#xff0c;它处于位置的电角度未知。如果按上图U4&#xff08;100&#xff09;通电&#xff0c;也会让电角度为0,但是这样力量很大。 简单的方法是只控制d角度的磁场大小&#xff0c;转矩磁场q为0,生成一个定向磁场指向电角度为0。 foc->sin_sita 0;foc->…

全国区块链职业技能大赛样题第9套后端源码

后端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746050 前端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746216 智能合约+数据库表设计:https://blog.csdn.net/Qhx20040819/article/details/140746646 项目预览 登录 用户管理

X用户最多的国家排名统计报告

数据为DataReportal发布的Twitter在各个国家的用户数统计。 2022年&#xff0c;Twitter用户最多的国家是美国&#xff0c;有7690万用户。 数据统计单位为&#xff1a;万人 数据说明&#xff1a; 数据截止时间为2022年1月 Twitter在各个国家的用户情况 2022年&#xff0c;Twit…