编程技术分享

分享编程知识,探讨技术创新

0%

简介:本项目为Aleksa在 https://hackaday.io/project/180090-thunderscope 上开源的项目「开源四通道示波器 ThunderScope」的复刻。

我很乐意为您详细阐述ThunderScope开源四通道示波器的嵌入式系统代码设计架构,并提供相应的C代码实现框架。我们将从需求分析出发,逐步深入到系统架构设计、模块划分、关键技术实现、测试验证以及维护升级等方面,力求构建一个可靠、高效、可扩展的嵌入式示波器平台。
关注微信公众号,提前获取相关推文

项目简介回顾:

ThunderScope是一个开源的四通道示波器项目,目标是提供一个经济实惠、功能全面的替代传统示波器的方案。其核心在于利用现代微控制器和高速ADC芯片,结合软件算法实现信号采集、处理和显示。开源的特性意味着我们可以深入了解其设计思想,并在此基础上进行复刻和改进。

一、需求分析

在任何嵌入式系统开发之初,需求分析都是至关重要的环节。对于ThunderScope这样的示波器项目,我们需要明确以下几个关键需求:

  1. 通道数量: 四个模拟输入通道,能够同时采集四个信号。
  2. 采样率和带宽: 需要确定示波器的最大采样率和带宽,这直接决定了其能够捕获的信号频率范围。例如,我们可能需要达到至少几十MHz的采样率和带宽,以满足通用示波器的需求。
  3. 垂直分辨率: ADC的位数决定了垂直分辨率,例如8位、12位甚至更高,这将影响信号的精度。
  4. 触发功能: 示波器必须具备多种触发模式,如边沿触发、脉宽触发、视频触发等,以便稳定捕获感兴趣的信号。
  5. 时基和垂直灵敏度调节: 用户需要能够调节时基(水平方向的时间刻度)和垂直灵敏度(垂直方向的电压刻度),以便观察不同幅度和频率的信号。
  6. 显示功能: 需要将采集到的波形数据以图形化的方式显示出来,通常是通过连接外部显示屏或使用嵌入式系统的LCD屏幕。
  7. 测量功能: 示波器应具备基本的测量功能,如峰峰值、均方根值、频率、周期、占空比等。
  8. 数据存储和导出(可选): 根据需求,可以考虑是否需要将采集到的波形数据存储到本地存储器或导出到上位机进行进一步分析。
  9. 用户界面: 需要设计用户友好的界面,方便用户操作和配置示波器。这可以通过按键、旋钮、触摸屏等方式实现。
  10. 可靠性和稳定性: 嵌入式系统必须保证长时间稳定运行,避免程序崩溃或数据采集错误。
  11. 可扩展性: 系统架构应具备良好的可扩展性,方便未来添加新的功能或改进性能。
  12. 低功耗(可选): 如果示波器需要便携式应用,则需要考虑功耗问题。

二、系统架构设计

基于以上需求分析,我们可以设计一个分层式的嵌入式系统架构,这种架构具有良好的模块化和可维护性。主要分为以下几个层次:

  1. 硬件层 (Hardware Layer):

    • 微控制器 (MCU): 作为系统的核心处理器,负责控制整个示波器的运行,包括数据采集、处理、显示和用户交互。我们需要选择一款性能足够强大,外设资源丰富的MCU,例如基于ARM Cortex-M系列的高性能MCU。
    • 模数转换器 (ADC): 负责将模拟输入信号转换为数字信号。我们需要选择高速、高分辨率、多通道的ADC芯片,例如四通道同步采样ADC。
    • 模拟前端 (AFE): 负责对输入信号进行调理,例如放大、衰减、滤波、阻抗匹配等,以满足ADC的输入范围和性能要求。
    • 显示屏接口 (Display Interface): 用于连接显示屏,例如SPI、Parallel RGB、LVDS等接口。
    • 输入设备接口 (Input Interface): 用于连接用户输入设备,例如按键、旋钮、触摸屏等,通常通过GPIO、SPI、I2C等接口实现。
    • 存储器 (Memory): 包括Flash存储器(用于存储程序代码和配置数据)和RAM存储器(用于程序运行和数据缓存)。
    • 电源管理 (Power Management): 负责系统的电源供应和管理,包括稳压、滤波、保护等。
    • 时钟源 (Clock Source): 提供系统时钟,确保各个模块的同步运行。
  2. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • HAL层位于硬件层之上,为上层软件提供统一的硬件访问接口。它将底层的硬件细节抽象出来,使得上层软件可以独立于具体的硬件平台进行开发和移植。
    • HAL层主要包括对MCU外设的驱动,例如GPIO驱动、ADC驱动、定时器驱动、SPI驱动、I2C驱动、UART驱动、显示屏驱动等。
    • HAL层的设计应遵循模块化和可配置化的原则,方便更换硬件平台或修改硬件配置。
  3. 驱动层 (Driver Layer):

    • 驱动层构建在HAL层之上,提供更高级别的硬件控制接口。例如,在ADC驱动之上,可以构建示波器专用的ADC采集驱动,封装ADC的初始化、启动、停止、数据读取等操作,并提供更友好的API。
    • 驱动层可以包括:
      • ADC采集驱动 (ADC Acquisition Driver): 负责配置ADC参数,启动ADC采集,读取ADC数据,并进行初步的数据处理(例如校准、滤波等)。
      • 触发驱动 (Trigger Driver): 实现各种触发模式,例如边沿触发、脉宽触发等,并控制ADC的采集启动。
      • 显示驱动 (Display Driver): 负责将波形数据转换为图形数据,并驱动显示屏进行显示。可以包括图形库的封装,例如GUI库或简单的图形绘制函数。
      • 输入设备驱动 (Input Device Driver): 处理用户输入事件,例如按键按下、旋钮旋转、触摸屏触摸等,并将事件传递给上层应用逻辑。
      • 时基控制驱动 (Timebase Control Driver): 控制示波器的时基,调整采样率和水平方向的显示范围。
      • 垂直灵敏度控制驱动 (Vertical Sensitivity Control Driver): 控制示波器的垂直灵敏度,调整信号的放大或衰减倍数。
  4. 核心逻辑层 (Core Logic Layer):

    • 核心逻辑层是示波器软件的核心部分,负责实现示波器的主要功能,例如数据采集、信号处理、触发控制、测量计算、数据管理等。
    • 核心逻辑层主要包括:
      • 数据采集模块 (Data Acquisition Module): 调用ADC采集驱动,负责从ADC获取原始数据,并存储到数据缓冲区中。
      • 触发模块 (Trigger Module): 根据用户设置的触发条件,检测触发事件,并控制数据采集模块的启动和停止。
      • 信号处理模块 (Signal Processing Module): 对采集到的数据进行处理,例如滤波、平均、插值、FFT等,以改善信号质量或提取信号特征。
      • 波形显示模块 (Waveform Display Module): 将数据缓冲区中的波形数据转换为屏幕坐标,并调用显示驱动进行绘制。
      • 测量模块 (Measurement Module): 根据用户选择的测量类型,对波形数据进行计算,得到测量结果,例如峰峰值、均方根值、频率、周期等。
      • 参数配置模块 (Parameter Configuration Module): 管理示波器的各种参数配置,例如采样率、时基、垂直灵敏度、触发模式等,并提供用户配置接口。
      • 数据存储模块 (Data Storage Module, 可选): 负责将采集到的波形数据存储到本地存储器或导出到外部设备。
  5. 应用层 (Application Layer):

    • 应用层是用户直接交互的界面,负责处理用户输入,调用核心逻辑层的功能,并将结果显示给用户。
    • 应用层主要包括:
      • 用户界面 (User Interface): 提供图形化的用户界面或命令行界面,方便用户操作和配置示波器。
      • 命令解析器 (Command Parser): 解析用户输入的命令,并调用相应的处理函数。
      • 状态管理 (State Management): 管理示波器的各种状态,例如运行状态、配置状态、测量状态等。

三、代码设计架构详解

我们将采用模块化、事件驱动、分层设计的架构来实现ThunderScope的代码。

1. 模块化设计:

将系统划分为独立的模块,每个模块负责特定的功能,模块之间通过定义清晰的接口进行通信。这提高了代码的可读性、可维护性和可重用性。

模块划分示例:

  • hal_adc.c/h: HAL层ADC驱动模块
  • hal_gpio.c/h: HAL层GPIO驱动模块
  • drv_adc_acq.c/h: ADC采集驱动模块
  • drv_trigger.c/h: 触发驱动模块
  • drv_display.c/h: 显示驱动模块
  • ui_input.c/h: 用户输入处理模块
  • core_acq.c/h: 核心数据采集模块
  • core_trigger.c/h: 核心触发逻辑模块
  • core_process.c/h: 核心信号处理模块
  • core_display.c/h: 核心波形显示模块
  • core_measure.c/h: 核心测量模块
  • app_main.c: 主应用程序模块
  • config.h: 系统配置头文件

2. 事件驱动:

采用事件驱动的机制来处理用户输入、定时器事件、ADC数据就绪等事件。当事件发生时,系统会调用相应的事件处理函数进行处理。这使得系统能够高效地响应外部事件,并保持实时性。

事件类型示例:

  • EVENT_BUTTON_PRESSED: 按键按下事件
  • EVENT_ENCODER_ROTATED: 旋钮旋转事件
  • EVENT_ADC_DATA_READY: ADC数据采集完成事件
  • EVENT_TIMER_TICK: 定时器tick事件
  • EVENT_TRIGGER_DETECTED: 触发事件检测到

事件处理流程示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 事件处理函数指针类型
typedef void (*EventHandler)(Event event);

// 事件结构体
typedef struct {
EventType type;
// ... 事件数据 ...
} Event;

// 事件处理函数注册表
EventHandler eventHandlers[NUM_EVENTS];

// 添加事件处理函数
void registerEventHandler(EventType type, EventHandler handler) {
eventHandlers[type] = handler;
}

// 事件分发函数
void dispatchEvent(Event event) {
if (eventHandlers[event.type] != NULL) {
eventHandlers[event.type](event);
}
}

// 示例事件处理函数 - 按键按下
void handleButtonPress(Event event) {
// ... 处理按键按下事件 ...
}

// 初始化事件系统
void eventSystemInit() {
// 初始化事件处理函数注册表
for (int i = 0; i < NUM_EVENTS; i++) {
eventHandlers[i] = NULL;
}
// 注册事件处理函数
registerEventHandler(EVENT_BUTTON_PRESSED, handleButtonPress);
// ... 注册其他事件处理函数 ...
}

// 主循环中处理事件
int main() {
eventSystemInit();
// ... 初始化其他模块 ...

while (1) {
// ... 检测事件 ...
Event event = detectEvent(); // 假设的检测事件函数
if (event.type != EVENT_NONE) {
dispatchEvent(event); // 分发事件
}
// ... 其他任务 ...
}
return 0;
}

3. 分层设计:

如前所述,采用硬件层、HAL层、驱动层、核心逻辑层、应用层的分层架构。每一层负责特定的功能,层与层之间通过定义清晰的接口进行通信。这降低了系统的复杂性,提高了代码的可维护性和可移植性。

四、具体C代码实现框架 (关键模块示例)

由于3000行代码的篇幅限制,我们无法提供完整的、可编译运行的示波器代码。这里我们重点展示关键模块的代码框架和核心逻辑,以说明设计思路。

1. HAL层 (hal_adc.h/c, hal_gpio.h/c - 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// hal_adc.h
#ifndef HAL_ADC_H
#define HAL_ADC_H

#include <stdint.h>

typedef enum {
ADC_CHANNEL_1,
ADC_CHANNEL_2,
ADC_CHANNEL_3,
ADC_CHANNEL_4,
NUM_ADC_CHANNELS
} ADC_Channel;

typedef enum {
ADC_RESOLUTION_8BIT,
ADC_RESOLUTION_12BIT,
// ... 其他分辨率 ...
} ADC_Resolution;

typedef enum {
ADC_SAMPLING_RATE_1MSPS,
ADC_SAMPLING_RATE_10MSPS,
ADC_SAMPLING_RATE_50MSPS,
// ... 其他采样率 ...
} ADC_SamplingRate;

typedef struct {
ADC_Resolution resolution;
ADC_SamplingRate samplingRate;
// ... 其他配置参数 ...
} ADC_Config;

// 初始化ADC模块
void hal_adc_init(ADC_Config *config);

// 启动ADC转换
void hal_adc_start();

// 停止ADC转换
void hal_adc_stop();

// 读取ADC数据
uint16_t hal_adc_read_channel(ADC_Channel channel);

// 设置ADC通道使能
void hal_adc_enable_channel(ADC_Channel channel, bool enable);

// ... 其他HAL层ADC函数 ...

#endif // HAL_ADC_H


// hal_adc.c
#include "hal_adc.h"
// ... 包含具体的硬件头文件,例如 STM32 的头文件 ...

void hal_adc_init(ADC_Config *config) {
// ... 初始化ADC硬件寄存器,根据 config 参数配置 ...
// 例如:配置ADC分辨率、采样率、时钟、DMA等
// 使能ADC时钟
// 配置ADC引脚为模拟输入
// 配置ADC分辨率和采样率
// ...
}

void hal_adc_start() {
// ... 启动ADC转换,例如使能ADC转换启动位 ...
}

void hal_adc_stop() {
// ... 停止ADC转换,例如禁用ADC转换启动位 ...
}

uint16_t hal_adc_read_channel(ADC_Channel channel) {
// ... 读取指定通道的ADC数据寄存器 ...
// 例如:选择通道,读取DR寄存器
return 0; // 返回读取到的ADC值
}

void hal_adc_enable_channel(ADC_Channel channel, bool enable) {
// ... 使能或禁用指定通道,例如配置ADC通道使能寄存器 ...
}

// ... 其他HAL层ADC函数实现 ...


// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
// ... 其他GPIO引脚 ...
NUM_GPIO_PINS
} GPIO_Pin;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
GPIO_MODE_AF, // Alternate Function
// ... 其他GPIO模式 ...
} GPIO_Mode;

typedef enum {
GPIO_OUTPUT_TYPE_PP, // Push-Pull
GPIO_OUTPUT_TYPE_OD, // Open-Drain
// ... 其他输出类型 ...
} GPIO_OutputType;

typedef enum {
GPIO_PULL_NONE,
GPIO_PULL_UP,
GPIO_PULL_DOWN,
// ... 其他上下拉配置 ...
} GPIO_Pull;

typedef enum {
GPIO_SPEED_LOW,
GPIO_SPEED_MEDIUM,
GPIO_SPEED_HIGH,
GPIO_SPEED_VERY_HIGH,
// ... 其他速度配置 ...
} GPIO_Speed;

typedef struct {
GPIO_Mode mode;
GPIO_OutputType outputType;
GPIO_Pull pull;
GPIO_Speed speed;
// ... 其他GPIO配置参数 ...
} GPIO_Config;

// 初始化GPIO引脚
void hal_gpio_init(GPIO_Pin pin, GPIO_Config *config);

// 设置GPIO引脚输出电平
void hal_gpio_write_pin(GPIO_Pin pin, bool value);

// 读取GPIO引脚输入电平
bool hal_gpio_read_pin(GPIO_Pin pin);

// ... 其他HAL层GPIO函数 ...

#endif // HAL_GPIO_H


// hal_gpio.c
#include "hal_gpio.h"
// ... 包含具体的硬件头文件,例如 STM32 的头文件 ...

void hal_gpio_init(GPIO_Pin pin, GPIO_Config *config) {
// ... 初始化GPIO硬件寄存器,根据 config 参数配置 ...
// 例如:配置GPIO模式、输出类型、上下拉、速度等
// 使能GPIO时钟
// 配置GPIO引脚模式、输出类型、上下拉、速度
// ...
}

void hal_gpio_write_pin(GPIO_Pin pin, bool value) {
// ... 设置GPIO引脚输出电平,例如写入ODR寄存器 ...
}

bool hal_gpio_read_pin(GPIO_Pin pin) {
// ... 读取GPIO引脚输入电平,例如读取IDR寄存器 ...
return false; // 返回读取到的GPIO输入电平
}

// ... 其他HAL层GPIO函数实现 ...

2. 驱动层 - ADC采集驱动 (drv_adc_acq.h/c - 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// drv_adc_acq.h
#ifndef DRV_ADC_ACQ_H
#define DRV_ADC_ACQ_H

#include <stdint.h>
#include <stdbool.h>
#include "hal_adc.h"

#define ADC_BUFFER_SIZE 1024 // ADC数据缓冲区大小

typedef struct {
uint16_t buffer[NUM_ADC_CHANNELS][ADC_BUFFER_SIZE]; // 四通道数据缓冲区
uint32_t bufferIndex; // 缓冲区索引
ADC_SamplingRate currentSamplingRate; // 当前采样率
// ... 其他采集驱动状态 ...
} ADC_AcquisitionContext;

extern ADC_AcquisitionContext adcAcqContext; // ADC采集上下文

// 初始化ADC采集驱动
bool drv_adc_acq_init(ADC_SamplingRate samplingRate);

// 启动ADC采集
bool drv_adc_acq_start();

// 停止ADC采集
bool drv_adc_acq_stop();

// 获取ADC数据缓冲区
uint16_t* drv_adc_acq_get_buffer(ADC_Channel channel);

// 获取当前缓冲区索引
uint32_t drv_adc_acq_get_buffer_index();

// 设置采样率
bool drv_adc_acq_set_sampling_rate(ADC_SamplingRate samplingRate);

// ... 其他ADC采集驱动函数 ...

#endif // DRV_ADC_ACQ_H


// drv_adc_acq.c
#include "drv_adc_acq.h"
#include "hal_adc.h"
// ... 其他需要的头文件 ...

ADC_AcquisitionContext adcAcqContext; // 定义ADC采集上下文

bool drv_adc_acq_init(ADC_SamplingRate samplingRate) {
ADC_Config adcConfig;
adcConfig.resolution = ADC_RESOLUTION_12BIT; // 例如设置为12位分辨率
adcConfig.samplingRate = samplingRate;
// ... 其他ADC配置 ...

hal_adc_init(&adcConfig); // 初始化HAL层ADC

adcAcqContext.bufferIndex = 0;
adcAcqContext.currentSamplingRate = samplingRate;
// ... 初始化其他采集上下文 ...

return true; // 初始化成功
}

bool drv_adc_acq_start() {
hal_adc_start(); // 启动HAL层ADC
// ... 启动数据采集相关的定时器或DMA ...
return true;
}

bool drv_adc_acq_stop() {
hal_adc_stop(); // 停止HAL层ADC
// ... 停止数据采集相关的定时器或DMA ...
return true;
}

uint16_t* drv_adc_acq_get_buffer(ADC_Channel channel) {
if (channel < NUM_ADC_CHANNELS) {
return adcAcqContext.buffer[channel];
} else {
return NULL; // 无效通道
}
}

uint32_t drv_adc_acq_get_buffer_index() {
return adcAcqContext.bufferIndex;
}

bool drv_adc_acq_set_sampling_rate(ADC_SamplingRate samplingRate) {
if (adcAcqContext.currentSamplingRate != samplingRate) {
ADC_Config adcConfig;
adcConfig.resolution = ADC_RESOLUTION_12BIT; // 保持分辨率不变
adcConfig.samplingRate = samplingRate;
hal_adc_init(&adcConfig); // 重新初始化HAL层ADC
adcAcqContext.currentSamplingRate = samplingRate;
// ... 更新定时器或DMA配置以匹配新的采样率 ...
return true;
}
return false; // 采样率未改变
}

// ... 其他ADC采集驱动函数实现 ...

// 假设的ADC数据中断处理函数 (在 HAL 层或驱动层实现,取决于硬件和RTOS)
void ADC_DataReady_IRQHandler() {
for (int channel = 0; channel < NUM_ADC_CHANNELS; channel++) {
adcAcqContext.buffer[channel][adcAcqContext.bufferIndex] = hal_adc_read_channel((ADC_Channel)channel);
}
adcAcqContext.bufferIndex++;
if (adcAcqContext.bufferIndex >= ADC_BUFFER_SIZE) {
adcAcqContext.bufferIndex = 0; // 缓冲区溢出回卷
// ... 可以触发事件通知上层应用数据已准备好 ...
}
}

3. 核心逻辑层 - 数据采集模块 (core_acq.h/c - 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// core_acq.h
#ifndef CORE_ACQ_H
#define CORE_ACQ_H

#include <stdint.h>
#include <stdbool.h>
#include "drv_adc_acq.h"

#define WAVEFORM_BUFFER_SIZE 2048 // 波形数据缓冲区大小

typedef struct {
uint16_t waveformData[NUM_ADC_CHANNELS][WAVEFORM_BUFFER_SIZE]; // 四通道波形数据
uint32_t waveformDataSize; // 波形数据大小
// ... 其他波形数据信息 ...
} WaveformData;

extern WaveformData waveform; // 波形数据

// 初始化核心数据采集模块
bool core_acq_init();

// 开始采集波形数据
bool core_acq_start();

// 停止采集波形数据
bool core_acq_stop();

// 获取波形数据
WaveformData* core_acq_get_waveform_data();

// 设置采样率 (通过驱动层设置)
bool core_acq_set_sampling_rate(ADC_SamplingRate samplingRate);

// ... 其他核心数据采集函数 ...

#endif // CORE_ACQ_H


// core_acq.c
#include "core_acq.h"
#include "drv_adc_acq.h"
// ... 其他需要的头文件 ...

WaveformData waveform; // 定义波形数据

bool core_acq_init() {
// ... 初始化核心采集模块 ...
return drv_adc_acq_init(ADC_SAMPLING_RATE_1MSPS); // 默认采样率
}

bool core_acq_start() {
// ... 开始核心采集流程 ...
return drv_adc_acq_start();
}

bool core_acq_stop() {
// ... 停止核心采集流程 ...
return drv_adc_acq_stop();
}

WaveformData* core_acq_get_waveform_data() {
// ... 从ADC驱动获取数据,并进行必要的处理,例如数据拷贝 ...
uint32_t bufferIndex = drv_adc_acq_get_buffer_index();
uint16_t* adcBuffer[NUM_ADC_CHANNELS];
for(int i=0; i<NUM_ADC_CHANNELS; ++i) {
adcBuffer[i] = drv_adc_acq_get_buffer((ADC_Channel)i);
}

// 假设简单的缓冲区拷贝,实际可能需要更复杂的处理,例如触发点对齐等
uint32_t copySize = WAVEFORM_BUFFER_SIZE < ADC_BUFFER_SIZE ? WAVEFORM_BUFFER_SIZE : ADC_BUFFER_SIZE;
for (int channel = 0; channel < NUM_ADC_CHANNELS; channel++) {
for (uint32_t i = 0; i < copySize; i++) {
waveform.waveformData[channel][i] = adcBuffer[channel][(bufferIndex - copySize + i + ADC_BUFFER_SIZE) % ADC_BUFFER_SIZE]; // 环形缓冲区读取
}
}
waveform.waveformDataSize = copySize;

return &waveform;
}

bool core_acq_set_sampling_rate(ADC_SamplingRate samplingRate) {
return drv_adc_acq_set_sampling_rate(samplingRate);
}

// ... 其他核心数据采集函数 ...

4. 应用层 - 主应用程序 (app_main.c - 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// app_main.c
#include "core_acq.h"
#include "core_display.h"
#include "ui_input.h"
// ... 其他应用层模块头文件 ...

int main() {
// 初始化系统各个模块
core_acq_init();
core_display_init();
ui_input_init();
// ... 初始化其他模块 ...

core_acq_start(); // 启动数据采集

while (1) {
ui_input_process_events(); // 处理用户输入事件

WaveformData* waveformData = core_acq_get_waveform_data();
if (waveformData != NULL) {
core_display_draw_waveform(waveformData); // 绘制波形
// ... 其他显示更新 ...
}

// ... 其他应用层任务,例如测量计算、参数配置等 ...

// 可以添加延时,降低CPU占用率 (如果不是RTOS环境)
// 例如:delay_ms(10);
}
return 0;
}

五、项目中采用的各种技术和方法

  1. 分层架构: 提高代码模块化、可维护性、可移植性。
  2. 模块化设计: 将系统划分为独立模块,降低复杂性,提高代码重用性。
  3. 事件驱动: 高效响应外部事件,保证系统实时性。
  4. 硬件抽象层 (HAL): 隔离硬件差异,方便代码移植。
  5. 驱动层: 封装硬件操作,提供更高级别的API。
  6. 环形缓冲区: 高效管理ADC数据,避免数据丢失。
  7. 双缓冲显示 (可选): 消除显示闪烁,提高显示效果。
  8. 查表法 (LUT): 加速数学运算,例如三角函数、FFT等 (如果性能要求高)。
  9. 数字信号处理 (DSP) 算法: 滤波、平均、FFT等,提高信号质量和分析能力。
  10. 状态机: 管理系统状态,简化控制逻辑。
  11. 单元测试和集成测试: 保证代码质量和系统功能正确性。
  12. 版本控制 (例如 Git): 管理代码版本,方便团队协作和代码维护。

六、测试验证和维护升级

  1. 测试验证:

    • 单元测试: 针对每个模块进行单元测试,验证模块功能的正确性。可以使用C语言的单元测试框架,例如 CheckUnity
    • 集成测试: 测试模块之间的集成,验证模块协同工作的正确性。
    • 系统测试: 对整个示波器系统进行功能测试、性能测试、稳定性测试、可靠性测试等,验证系统是否满足需求。
    • 用户验收测试: 邀请用户进行测试,收集用户反馈,进一步完善系统。
  2. 维护升级:

    • 模块化设计: 方便模块的替换和升级,降低维护成本。
    • 清晰的代码注释和文档: 方便理解代码,降低维护难度。
    • 版本控制: 方便代码版本管理和回溯。
    • 固件升级机制 (OTA - Over-The-Air, 可选): 支持远程固件升级,方便用户获取最新功能和修复bug。
    • 错误日志和调试接口: 方便定位和解决问题。

总结

ThunderScope开源四通道示波器项目是一个典型的嵌入式系统开发案例。通过采用分层架构、模块化设计、事件驱动等方法,我们可以构建一个可靠、高效、可扩展的示波器平台。 上述C代码框架示例展示了关键模块的设计思路,实际项目中还需要根据具体的硬件平台和功能需求进行详细的编码和优化。 整个开发过程需要经过严格的测试验证,并持续进行维护升级,才能最终交付一个高质量的嵌入式产品。

希望这个详细的架构设计和代码框架能对您有所帮助。由于篇幅限制,无法提供3000行完整的可编译代码,但以上内容已经涵盖了嵌入式示波器软件设计的核心思想和关键技术点。在实际开发中,您需要根据具体的硬件平台和功能需求,进行更详细的设计和编码实现。

欢迎关注我的其它发布渠道