前言:本文为手把手教学的基于 STM32  PulseView 的逻辑分析仪项目教程,本教程使用 STM32F103ZET6 作为核心 MCU 搭配开源上位机 PulseView 实现逻辑分析仪的功能。逻辑分析仪是一种嵌入式开发中必不可少的数字电路信号分析器件,本项目中选用开源逻辑分析仪协议 SUMP 协议,利用 STM32 的 GPIO 引脚进行电平信号的采样,并通过 UART 串口通信将数据按照上位机 PulseView 的格式需求进行传输。该项目制作的逻辑分析仪更多的是一种技术练习,针对数字信号的分析能力很弱,但是个人认为还是很有学习的价值。希望这篇博文能给读者朋友的工程项目给予些许帮助,Respect!(篇末代码开源!)

硬件与软件:STM32F103ZET6、PulseView

项目结果图:

推荐网址

sigrok的官网:sigrok

SUMP协议官网:www.sump.org

PulseView上位机网站:Downloads - sigrok

一、逻辑分析仪概述

1.1 逻辑分析仪介绍

逻辑分析仪(Logic Analyzer)是一种测试和分析数字电路信号的仪器。它主要用于捕获、显示和分析数字信号的时序和波形数据,以便帮助工程师进行故障诊断、验证电路设计 和优化系统性能。与嵌入式工程经常使用的示波器相比,逻辑分析仪借助强大上位机可以清晰快捷的解码各类协议,包括但不限于:UART、SPI、I2C、USB、CAN、I2S、Modbus、PWM、MVB、OOK、SWIM、IR_RC5、IR_RC6、ADB、EMMC_SD 和 MIPI_DSI 等。

逻辑分析仪主要特点和功能如下:

 信号捕获:逻辑分析仪能够同时捕获多个数字信号通道的数据。它通常具有多个输入通 道,每个通道可以捕获一个独立的信号。这些信号可以是时钟信号、数据线、控制线等。

 高速采样:逻辑分析仪可以以高速采样率捕获信号,通常可以达到几百兆赫兹甚至更高 的采样率。高速采样能够准确捕获信号的快速变化和细节,有助于分析信号的时序关系 和逻辑问题。

 时序分析:逻辑分析仪能够显示捕获到的信号的时序图。时序图显示信号随时间的变化, 可以帮助工程师观察信号的时序关系、检测信号间的时间延迟、脉冲宽度等。

 波形分析:逻辑分析仪可以以波形图形式显示信号的波形。波形图显示信号的电平变化 和脉冲形状,可以帮助工程师观察信号的电平稳定性、噪音干扰、脉冲宽度等。

 触发功能:逻辑分析仪通常具有触发功能,可以设置触发条件来捕获特定的信号事件。 触发功能可以帮助工程师在大量的信号数据中定位和捕获感兴趣的信号。

 协议分析:逻辑分析仪还具有协议分析功能,可以解码和分析常见的通信协议,如 UART、 SPI、I2C、CAN 等。这种功能可以帮助工程师直观地理解和分析通信数据。

 存储和回放:逻辑分析仪通常具有内部存储器,可以存储捕获到的信号数据。存储的数 据可以随后回放和分析,方便离线分析和故障排除。

 软件支持:逻辑分析仪通常配备专用的软件,用于控制仪器、显示信号数据和提供分析 功能。

1.2 逻辑分析仪类型

市面上的逻辑分析仪 Logic Analyzer 有各种功能类型的,其包含的解码类型也存在一定差别。甚至市面上还有一些专用型的逻辑分析仪,例如:以太网分析仪、USB2.0 分析仪、USB3.0 分析仪与CAN 分析仪等等。但不管如何,在数字技术深度渗透各行业的 2025 年,逻辑分析仪已成为电子系统研发不可或缺的 “战略级工具”,其意义远超传统测试设备范畴,直接关联技术创新能力与产业竞争力(下图为支持 sigrok 的各种 Logic Analyzer,其实市面上很多 Logic Analyzer 都可以使用专门的驱动,从而使用超级逻辑分析仪上位机 PulseView)。

二、逻辑分析仪项目概述

2.1 Logic Analyzer 项目框架

2.1.1 MCU+FPGA 框架

商用级别的 Logic Analyzer 一般都是采用 MCU + FPGA  + DRAM 的硬件架构,该硬件架构的 Logic Analyzer 代表了当前中高端逻辑分析仪的主流技术路线,在性能、成本和灵活性之间取得了良好的平衡,能够满足从消费电子到通信设备等广泛领域的数字系统调试需求。

架构组成与功能分配

1、MCU (微控制器单元) 的核心作用:负责系统控制和人机交互界面 处理用户命令输入和结果显示;管理数据存储和传输;协调 FPGA 和 DRAM 的工作时序

2、FPGA (现场可编程门阵列) 的关键功能:高速数据采集和实时处理核心;实现触发逻辑和条件判断 ;数据预处理和压缩

3、DRAM (动态随机存取存储器) 的存储角色:作为高速大容量数据缓存;存储采集到的原始信号数据;支持深存储功能

综上所述,MCU + FPGA  + DRAM 的硬件架构,就是利用 MCU 与 PC 端进行数据传输,使用 FPGA 的高速数据采集能力去采集各种高速数字信号,DRAM 则是用来存储采集到的大量数字信号数据,帮助 Logic Analyzer 拥有更大的存储深度。

2.1.2 MCU 框架

使用一个 MCU 直接作为 Logic Analyzer 的话,硬件成本就会变得非常的低,但不可避免地该Logic Analyzer 的性能就会变得相对偏差。常规的 MCU 其时钟频率不会很高,这将导致采样频率相对较低。并且 MCU 的内存容量往往不会很大,其采样的存储深度亦会变得很低。当然,这个框架的  硬件成本那么低,还要什么自行车是吧!!!

本项目的 Logic Analyzer 使用的硬件结构就是直接使用 MCU 进行数字信号的采集(该方案一般性能偏弱,通常作为玩具使用)!

2.2 PulseView概述

PulseView 是 开源信号分析工具,隶属于 Sigrok 项目,主要作为逻辑分析仪和示波器的前端软件。其核心功能是 捕获、可视化和分析数字与模拟信号,适用于硬件调试、协议解析和逆向工程等领域。PulseView 的效能高度依赖硬件性能。例如,使用 Saleae Logic Pro 16 可同时捕获 16 通道 500MHz 信号,而低成本设备可能仅支持 24MHz 以下采样率。其开源特性也使得开发者能够深度定制功能,例如添加专有协议解析或集成到 CI/CD 流水线中。

1、跨平台支持:兼容 Windows、Linux、macOS。

2、开源免费:遵循 GPL 协议,无商业授权限制。

3、多类型信号采集:支持 数字信号(如 GPIO、SPI、I2C)和部分 模拟信号(需兼容硬件支持)。 提供高采样率(取决于硬件性能,如 Saleae Logic Pro 16 可达 500MS/s)。

4、协议解码:内置 50+ 协议解析器(如 UART、CAN、USB、HDMI),可自动解码二进制数据为可读格式。 支持用户自定义协议(通过 Lua 脚本扩展)。

5、可视化工具:波形缩放、测量、多通道同步分析。 时间轴标签与触发条件设置(如边沿触发、模式匹配)。

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

3.1 硬件结构

如下图所示是 Logic Analyzer 逻辑分析仪的最简单的硬件结构:

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

② 下位机使用GPIO采集数据

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

 本项目的硬件实物如下图所示:

本项目中  Logic Analyzer 逻辑分析仪使用 UART 串口与电脑端进行通信,当然这里借助了沁恒的 CH340 的 USB 转串口芯片,利用 USB 接口与电脑 PC 进行直连。当然,也可以直接使用 MCU 的 USB 模拟 CDC 虚拟串口与电脑 PC 连接,还能省下一颗 CH340 芯片。【作者也可以提供沁恒 CH32V307 制作的一体化 USB 逻辑分析仪代码】

3.2 SUMP 协议解析

SUMP(Simple Universal Measurement Protocol)是开源逻辑分析仪领域广泛采用的通信协议,其核心目标是实现硬件设备与软件客户端之间的标准化数据交互。SUMP 协议与 Sigrok 深度整合,SUMP 设备可直接调用 Sigrok 的协议解码库(如CAN、LIN总线)。SUMP 协议拥有轻量级设计框架、多平台兼容性和活跃的社区支持,使其成为硬件开发者、教育者和工程师的首选工具。随着硬件性能提升和协议扩展,SUMP 将继续在低成本测试测量领域发挥关键作用。

3.2.1 上位机发送的命令

Logic Analyzer 的工作是严重依赖上位机的,Logic Analyzer 工作过程就是上位机与下位机之间相互交互的过程。这个过程往往是上位机与下位机一问一答的过程:1、上位机发送命令/命令;2、下位机应答命令/发送数据;

SUMP 协议下 PulseView 上位机与 Logic Analyzer 的应答关系如下:

SUMP命令

命令值

作用

SUMP_RESET

0x00

复位下位机

SUMP_ARM

0x01

开始数据采样

SUMP_QUERY

0x02

让下位机上报ID

SUMP_TEST

0x03

模拟测试指令

SUMP_GET_METADATA

0x04

让下位机上报参数

SUMP_RLE_FINISH

0x05

RLE解码结束命令

SUMP_SET_TRIGGER_MASK

0xC0

使能某个通道的触发功能
示例数值:0x01 0x02 0x00 0x00 表示channel0,9使能了触发功能

SUMP_SET_TRIGGER_VALUES

0xC1

设置通道的触发值
示例数值:0x01 0x00 0x00 0x00 表示channel 0的触发值为高电平 channel 9的触发值为低电平

SUMP_SET_TRIGGER_CONF

0xC2

最后一个字节的bit3为1表示启动触发功能
示例数值:0x00 0x00 0x00 0x08 最后一个字节的bit3表示启动触发功能

SUMP_SET_DIVIDER

0x80

根据用户设置的采样频率计算出分频系数注意:当采样频率大于100MHz时, 会"Enable demux mode", 让逻辑分析工作于200MHz, 分频系数=200MHz/采样频率 - 1 当采样频率小于100MHz时,分频系数=100MHz/采样频率-1

示例数值:0xF3 0x01 0x00 0x00 分配系数为: 0x01F3=499=100MHz/200KHz-1

SUMP_SET_READ_DELAY_COUNT

0x81

使用1个命令发送READCOUNT DELAYCOUNT两个参数
示例数值:0x0c 0x00 0x0c 0x00 前2字节表示要采样的次数为 0x0c * 4 = 48
后2字节表示要延迟的次数为 0x0c * 4 = 48

SUMP_SET_FLAGS

0x82

设置flag,比如使用启动demux模式
根据用户选择的通道,使能group (见后面注释)

SUMP_CAPTURE_DELAYCOUNT

0x83

示满足触发条件开始采样后, 延迟多少次采样,才保存数据
示例数值:0x0c 0x00 0x00 0x00 表示延迟次数为 0x0c * 4 = 48

SUMP_CAPTURE_READCOUNT

0x84

表示要采样的次数
示例数值:0x0c 0x00 0x00 0x00
采样次数为 0x0c * 4 = 48

#define SUMP_RESET                 0x00
#define SUMP_ARM                   0x01
#define SUMP_QUERY                 0x02
#define SUMP_TEST                  0x03
#define SUMP_GET_METADATA          0x04
#define SUMP_RLE_FINISH            0x05
#define SUMP_XON                   0x11
#define SUMP_XOFF                  0x13
#define SUMP_SET_TRIGGER_MASK      0xC0
#define SUMP_SET_TRIGGER_VALUES    0xC1
#define SUMP_SET_TRIGGER_CONF      0xC2
#define SUMP_SET_DIVIDER           0x80
#define SUMP_SET_READ_DELAY_COUNT  0x81
#define SUMP_SET_FLAGS             0x82
#define SUMP_CAPTURE_DELAYCOUNT    0x83
#define SUMP_CAPTURE_READCOUNT     0x84

3.2.2 下位机发送的回复

作者将借助 BUSHound 软件针对 PulseView 进行交互的数据信息进行讲解,读者朋友也可以使用这种方法去解码各类逻辑分析仪的指令码是什么格式的内容,下面作者仅以核心的 SUMP 协议命令进行讲解,非核心的 SUMP 命令操作读者朋友可以自己进行尝试一下!

1、SUMP_RESET 命令(0x00)

作为 Logic Analyzer 下位机一般不进行操作即可;

2、SUMP_ARM 命令(0x01)

作为 Logic Analyzer 下位机开始采样数据,并在数据采样结束后进行上传数据;

3、SUMP_QUERY 命令(0x02)

让下位机 Logic Analyzer 上报ID,一般下位机要应答 4 个字节 “1ALS”;

4、SUMP_GET_METADATA 命令(0x04)

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

0x04 命令下达后,需要上报自己制作的 Logic Analyzer 的 ID 号,当然 ID 号也需要包含帧头和帧尾标志(帧头:0x01;帧尾:0x00),作者自己定义了一个名叫 ikun Logic Analyzer

0x21 命令之后紧跟着采样点数,0x23 命令之后紧跟着最大采样率 32MHz,0x40 命令之后紧跟着采样通道,0x41 命令之后紧跟着协议版本,作者上传的信息如下:

3.2.3 上位机和下位机时序图

搞懂上位机与下位机的时序图,最核心的方式肯定是去啃上位机与下位机的源码,作者这边给大家提供一下时序图的核心代码部分:

// 上位机 PulseView

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

// 下位机 Logic Analyzer 

LogicAnalyzer\HARDWARE\logic.c,SUMP_CMD_DEAL函数

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

重要核心:PulseView 上位机与 LogicAnalyzer 下位机的一应一答(这里用 BUSHOUND 来抓包,帮助读者朋友们理解内部操作机制)

1、上位机发送 SUMP_QUERY 命令(0x02),下位机需要应答 “1ALS”;

2、上位机发送 SUMP_GET_METADATA 命令(0x04),下位机需要根据实际情况,上报下位机的采样频率,采样通道数,最大采样点等信息 

3、上位机发送 SUMP_ARM 命令(0x01),下位机需要根据上位机发送的一系列的配置采样内容进行采样上报。特别需要注意:上传的数据数量一点要满足上位机发送的采样点个数,上位机才能正常实现采样波形!!!

四、STM32CubeMX 配置

1、RCC配置外部高速晶振(精度更高)——HSE;

2、SYS配置:Debug设置成Serial Wire否则可能导致芯片自锁);

3、UART1配置:借助CH340芯片将串口数据发送给上位机PulseView(开启中断);

4、GPIO配置:利用GPIO对外部数字信号进行采样(GPIOD0~7)

5、时钟树配置

6、工程配置

五、代码与解析

5.1 GPIO 数据采样代码

sample.c:

/********************************** (C) COPYRIGHT *******************************
* File Name          : sample.c
* Author             : 混分巨兽龙某某
* Version            : V1.0.0
* Date               : 2025/05/30
* Description        : The logic analyzer collects external analog signal functions
********************************************************************************/
#include "sample.h"
#include "main.h"

/* Variable */
#define SYSCLK 72000000
uint32_t SamplingPeriod;
uint32_t SampleNumber;
uint8_t Sample_Buffer[BUFFER_SIZE];

/*******************************************************************************
* Function Name  : Data_Sample
* Description    : The acquisition function for digital signals
* Input          : None
* Return         : None
*******************************************************************************/
void Data_Sample()
{
	uint32_t snum = SampleNumber;															/* The number of sampling points */
	
	if(SamplingPeriod == 5000){																/* 5MHz SAMPLE(Close but not really...) */
		for(int i = 0; i < snum; i++){	
			Sample_Buffer[i] = GPIOD->IDR;
			__asm volatile ( "NOP\nNOP\nNOP\nNOP\n");	
		}		
	}else if(SamplingPeriod == 2000){													/* 2MHz SAMPLE */
		for(int i = 0; i < snum; i++){	
			Sample_Buffer[i] = GPIOD->IDR;
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\n");	
		}	
	}else if(SamplingPeriod == 1000){													/* 1MHz SAMPLE */
		for(int i = 0; i < snum; i++){	
			Sample_Buffer[i] = GPIOD->IDR;
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");		
		}	
	}else if(SamplingPeriod == 500){													/* 500KHz SAMPLE */
		for(int i = 0; i < snum; i++){	
			Sample_Buffer[i] = GPIOD->IDR;
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");	
			__asm volatile ( " NOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\nNOP\n");
		}	
	}else{																										/* Others SamplingPeriod */
		uint32_t c = 1000/SamplingPeriod-1;
		for(int i = 0; i < snum; i++){	
			Sample_Buffer[i] = GPIOD->IDR;
			HAL_Delay_Us(c);
		}
	}
}

/*******************************************************************************
* Function Name  : HAL_Delay_Us
* Description    : The microsecond delay function created by the system clock
* Input          : Microsecond
* Return         : None
*******************************************************************************/
void HAL_Delay_Us(uint32_t us)
{
	uint32_t ticks;
	uint32_t told, tnow, tcnt = 0;

	/* The number of clocks required for calculation = the number of microseconds of delay * the number of clocks per microsecond */
	ticks = us * (SYSCLK / 1000000);

	told = SysTick->VAL;

	while (1)
	{
		tnow = SysTick->VAL;

		if (tnow != told)
		{
			if (tnow < told)
					tcnt += told - tnow;
			else
					tcnt += SysTick->LOAD - tnow + told;

			told = tnow;

			if (tcnt >= ticks)
					break;
		}
	}
}

/**************************** Read and Write + NOP TEST ****************************/
#if 0
uint8_t buffer[5000];
/* The test of the time required to read 50,000 GPIO */
void BUFFER_TEST()
{	
	/* 1.引脚低电平 */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
	HAL_Delay(100);

	/* 2.关闭中断 */
	__disable_irq();
	
	/* 3.1引脚高电平  */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
	
	/* 3.2读GPIO循环(每次读写160ns左右) */
	for(int i = 0; i < 5000; i++){
		buffer[i] = GPIOD->IDR;
	}

	/* 3.3引脚低电平  */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

	/* 4.开启中断 */
	__enable_irq();
}

/* The test on the time required to read 50,000 NOP */
void NOP_TEST()
{
	/* 1.引脚低电平 */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
	HAL_Delay(100);

	/* 2.关闭中断 */
	__disable_irq();
	
	/* 3.1引脚高电平  */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

		/* 3.2NOP(108)循环(每次14ns左右) */
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");
	__asm volatile ("NOP\nNOP\nNOP\nNOP\n");

	/* 3.3引脚低电平  */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);

	/* 4.开启中断 */
	__enable_irq();
}
#endif

LogicAnalyzer 逻辑分析仪的采样功能是此设备最为关键的功能没有之一,本项目因为使用的低端 MCU 的 GPIO 读取功能进行采样,故采样频率和精度都是偏差的。项目中使用 Sample_Buffer[i] = GPIOD->IDR 和 __asm volatile ( "NOP\nNOP\nNOP\nNOP\n") 函数进行模拟量的采集与频率的校准。NOP 函数是一种很常见的短机器周期的延迟操作!

1、GPIO 读取的时间测量(过程中可以借助其他高频分析仪或者示波器):

2、NOP 函数的时间测量(过程中可以借助其他高频分析仪或者示波器):

5.2 SUMP 协议解码代码

给读者朋友们分享一下 SUMP 协议的各个命令码,如下:

/********************************** (C) COPYRIGHT *******************************
 * File Name          : sump_protocol.h
 * Author             : 混分巨兽龙某某
 * Version            : V1.0.0
 * Date               : 2025/05/30
 * Description        : Sump protocol command File.
 *********************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/
#ifndef __SUMP_PROTOCOL_H
#define __SUMP_PROTOCOL_H

#ifdef __cplusplus
extern "C" {
#endif 

/* SUMP PROTOCOL CMD */
#define CMD_RESET                     0x00
#define CMD_ARM_BASIC_TRIGGER         0x01
#define CMD_ID                        0x02
#define CMD_TEST                      0x03
#define CMD_GET_METADATA              0x04
#define CMD_FINISH_NOW                0x05
#define CMD_QUERY_INPUT_DATA          0x06 
#define CMD_QUERY_CAPTURE_STATE       0x07 
#define CMD_RETURN_CAPTURE_DATA       0x08 
#define CMD_ARM_ADVANCED_TRIGGER      0x0F 
#define CMD_XON                       0x11
#define CMD_XOFF                      0x13
#define CMD_SET_DIVIDER               0x80
#define CMD_CAPTURE_SIZE              0x81
#define CMD_SET_FLAGS                 0x82
#define CMD_CAPTURE_DELAYCOUNT        0x83 
#define CMD_CAPTURE_READCOUNT         0x84 
#define CMD_SET_ADVANCED_TRIG_SEL     0x9E 
#define CMD_SET_ADVANCED_TRIG_WRITE   0x9F 
#define CMD_SET_BASIC_TRIGGER_MASK0   0xC0 /* 4 stages: 0xC0, 0xC4, 0xC8, 0xCC */
#define CMD_SET_BASIC_TRIGGER_VALUE0  0xC1 /* 4 stages: 0xC1, 0xC5, 0xC9, 0xCD */
#define CMD_SET_BASIC_TRIGGER_CONFIG0 0xC2 /* 4 stages: 0xC2, 0xC6, 0xCA, 0xCE */

/* Metadata tokens */
#define METADATA_TOKEN_END                      0x0
#define METADATA_TOKEN_DEVICE_NAME              0x1
#define METADATA_TOKEN_FPGA_VERSION             0x2
#define METADATA_TOKEN_ANCILLARY_VERSION        0x3
#define METADATA_TOKEN_NUM_PROBES_LONG          0x20
#define METADATA_TOKEN_SAMPLE_MEMORY_BYTES      0x21
#define METADATA_TOKEN_DYNAMIC_MEMORY_BYTES     0x22
#define METADATA_TOKEN_MAX_SAMPLE_RATE_HZ       0x23
#define METADATA_TOKEN_PROTOCOL_VERSION_LONG    0x24
#define METADATA_TOKEN_CAPABILITIES             0x25 /* not implemented in Demon Core v3.07 */
#define METADATA_TOKEN_NUM_PROBES_SHORT         0x40
#define METADATA_TOKEN_PROTOCOL_VERSION_SHORT   0x41

/* Device config flags */
#define DEVICE_FLAG_IS_DEMON_CORE           (1 << 0)

/* Basic Trigger Config */
#define TRIGGER_START                       (1 << 3)

/* Bit mask used for "set flags" command (0x82) */
/* Take care about bit positions in diagrams, they are inverted. */
#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 context magic numbers */
#define OLS_NO_TRIGGER                      (-1)

#ifdef __cplusplus
}
#endif

#endif

5.3 Logic Analyzer 轮询函数

Logic Analyzer 逻辑分析仪的轮询函数核心是串口中断服务函数,在该中断服务中进行 PulseView 的数据解码与 Logic Analyzer 的工作状态切换。作者使用了一个状态机 State Machine 进行逻辑分析仪的功能控制,分为 3 种状态:正常模式 Normal、特殊模式1 Special_MODE1 和 特殊模式2 Special_MODE2。代码实现如下:

transmission.c:

/********************************** (C) COPYRIGHT *******************************
* File Name          : transmission.c
* Author             : 混分巨兽龙某某
* Version            : V1.0.0
* Date               : 2025/05/30
* Description        : The logic analyzer communicates with PulseView
********************************************************************************/
#include "transmission.h"
#include "sample.h"
//#include "logic.h"

/* State Machine Variables */
typedef enum {
	NORMAL_MODE,							//正常模式
	SPECIAL_MODE1_B1,					//特殊模式1(0x80)第1个字节
	SPECIAL_MODE1_B2,					//特殊模式1(0x80)第2个字节
	SPECIAL_MODE2_B1,					//特殊模式2(0x81)第1个字节
	SPECIAL_MODE2_B2					//特殊模式1(0x81)第2个字节	
} SpecialMode_t;

unsigned char Analyzer_Configuration1[] = {0x01,
                  0x69, 0x6B, 0x75, 0x6E, 0x20, 0x4C, 0x6F, 0x67, 0x69, 0x63, 0x20, 0x41, 0x6E, 0x61, 0x6C, 0x79, 0x7A, 0x65, 0x72,           /* ikun Logic Analyzer */
                  0x00,
                  0x21, 0x00, 0x00, 0x80, 0x00,                   //0x00, 0x00, 0x80, 0x00   = 32768 采样点数
                  0x22, 0x00, 0x00, 0x00, 0x00,                   //动态采样
                  0x23, 0x01, 0xE8, 0x48, 0x00,                   //0x01, 0xE8, 0x48, 0x00   = 32Mhz 最大采样率32MHz
                  0x40, 0x08,                                     //采样通道:8
                  0x41, 0x02,                                     //协议版本:0x02
                  0x00};                    											//END

volatile SpecialMode_t Transmission_Mode = NORMAL_MODE;  					//初始化传输模式

volatile uint8_t MODE1_BYTE1 = 0;  					//MODE1第1个字节
volatile uint8_t MODE1_BYTE2 = 0;  					//MODE1第2个字节
volatile uint8_t MODE2_BYTE1 = 0;  					//MODE2第1个字节
volatile uint8_t MODE2_BYTE2 = 0;  					//MODE2第2个字节
volatile uint32_t MODE1_NUM;
volatile uint32_t MODE2_NUM;

/* The Structure of a Logic Analyzer */
struct Logic_Analyzer{
	uint32_t Analyzer_Frequency;
	uint32_t Sampling_Points;
};
struct Logic_Analyzer myAnalyzer;

uint8_t USART1_RX_BUF[USART1_REC_LEN];

uint16_t USART1_RX_STA=0;
uint8_t USART1_NewData;

uint8_t Sample_FLAG = 0;
									
uint8_t FLAG = 0;
/*******************************************************************************
* Function Name  : HAL_UART_RxCpltCallback
* Description    : UART RxCpltCallback.
* Input          : None
* Return         : None
*******************************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)
{
	if(huart == &huart1)
	{
			switch(Transmission_Mode)			//模式状态机Transmission Mode State Machine
			{
				case SPECIAL_MODE1_B1:
						MODE1_BYTE1 = USART1_NewData;
						Transmission_Mode = SPECIAL_MODE1_B2;
					break;
				case SPECIAL_MODE1_B2:
						MODE1_BYTE2 = USART1_NewData;
				
						//MODE1数据进行处理(采样率)
						MODE1_NUM = MODE1_BYTE1 | (MODE1_BYTE2 << 8);
						myAnalyzer.Analyzer_Frequency = 100000/(MODE1_NUM + 1);
						SamplingPeriod = myAnalyzer.Analyzer_Frequency;
						Transmission_Mode = NORMAL_MODE;
					break;
			
				case SPECIAL_MODE2_B1:
						MODE2_BYTE1 = USART1_NewData;
						Transmission_Mode = SPECIAL_MODE2_B2;
					break;
				
				case SPECIAL_MODE2_B2:
						MODE2_BYTE2 = USART1_NewData;						
				
						//MODE2数据进行处理(采样点个数)
						MODE2_NUM = MODE2_BYTE1 | (MODE2_BYTE2 << 8);
						myAnalyzer.Sampling_Points = (MODE2_NUM + 1)*4;
						SampleNumber = myAnalyzer.Sampling_Points;
						Transmission_Mode = NORMAL_MODE;						
					break;
				
				case NORMAL_MODE:
						if(USART1_NewData == 0x02){
							printf("1ALS");
						}else if(USART1_NewData == 0x04){
							//NAME
							PrintfChar(0x01);
							printf("iKun Logic Analyzer");
							PrintfChar(0x00);
							
							//SAMPLE MEM
							PrintfChar(0x21);
							PrintfuInt(32768);
							
							//DYNAMIC MEM
							PrintfChar(0x22);
							PrintfuInt(0);
							
							//SAMPLE RATE
							PrintfChar(0x23);
							PrintfuInt(32000000);
							
							//Number of Probes
							PrintfChar(0x40);
							PrintfChar(0x08);
							
							//Protocol Version
							PrintfChar(0x41);
							PrintfChar(0x02);

							//END
							PrintfChar(0x00);
							
						}else if(USART1_NewData == 0x01){
							Sample_FLAG = 1;
						}else if(USART1_NewData == 0x80){
							Transmission_Mode = SPECIAL_MODE1_B1;
						}else if(USART1_NewData == 0x81){
							Transmission_Mode = SPECIAL_MODE2_B1;
						}
						
					break;
			}
	
	//Restart the interrupted reception
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&USART1_NewData, 1);
	}	
}

/*******************************************************************************
* Function Name  : PrintfChar
* Description    : Print character function
* Input          : None
* Return         : None
*******************************************************************************/
void PrintfChar(uint8_t CMD)
{
	printf("%c",CMD);
}

/*******************************************************************************
* Function Name  : PrintfuInt
* Description    : Print numerical function
* Input          : None
* Return         : None
*******************************************************************************/
void PrintfuInt(uint32_t value) {

	uint8_t bytes[4] = {
			(value >> 24) & 0xFF,  
			(value >> 16) & 0xFF,
			(value >> 8)  & 0xFF,
			 value        & 0xFF  
	};
 
	for (int i = 0; i < 4; i++) {
			printf("%c", bytes[i]); 
	}
}

5.4 main 函数

main 函数中核心就两点:1、进行数据采样 Sample;2、进行数据传输 Data transmission;当上位机发送开始采样的 SUMP_ARM(0x01)命令后,在串口中断函数中将采样标志位 Sample_FALG 赋值 1,并开始持续数据采样。采样完成后将数据进行上报处理即可(PulseView的 SUMP 协议是 1 个字节的数据上报,每个 bit 位代表某一个 GPIO 上的高低电平)。

man.c:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint8_t Sampling_Complete;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_RX_BUF,1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		if(Sample_FLAG == 1){
			Data_Sample();
			Sample_FLAG = 0;
			Sampling_Complete = 1;
		}
		
		if(Sampling_Complete == 1){
			Sampling_Complete = 0;
			for(int i = 0; i < SampleNumber; i++){
				printf("%c",Sample_Buffer[i]);
			}
		}
  }
  /* USER CODE END 3 */
}

六、Logic Analyzer 使用

1、打开 PulseView 上位机选择 CONNECT TO DEVICE,选择开源的 SUMP COMPATIBLES 下位机固件;

2、选择 Serial Port 中的 Logic Analyzer 对应的串口号,再点击 Scan for devices 即可;

3、设置 Logic Analyzer 下位机逻辑分析仪的采样点个数与采样频率;

4、点击 RUN 进行数据采样与数据上报;

七、代码开源

代码地址: 【免费】基于STM32与PulseView的逻辑分析仪项目代码资源-CSDN下载

如果积分不够的朋友,点波关注评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐