编程技术分享

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

0%

简介:便携式示波器显示模块

好的,作为一名高级嵌入式软件开发工程师,我将为您详细阐述便携式示波器显示模块的嵌入式系统开发流程、代码设计架构以及具体的C代码实现。本项目旨在构建一个可靠、高效、可扩展的嵌入式示波器平台,所有技术选型和方法均经过实践验证。
关注微信公众号,提前获取相关推文

项目概述:便携式示波器显示模块

本项目专注于便携式示波器的显示模块,该模块是示波器的核心组成部分,负责采集、处理和显示模拟信号波形。一个高效且用户友好的显示模块直接关系到示波器的性能和用户体验。我们的目标是设计一个嵌入式系统,能够实时、准确、清晰地显示来自模拟输入通道的波形,并提供必要的测量和控制功能。

嵌入式系统开发流程

一个完整的嵌入式系统开发流程通常包括以下几个阶段:

  1. 需求分析阶段

    • 功能需求:

      • 波形显示: 实时显示来自至少两个模拟通道(CH1, CH2)的输入波形。
      • 触发功能: 支持多种触发模式(边沿触发、脉宽触发等),确保稳定显示周期性信号。
      • 时基调节: 用户可调节水平时基(时间/格),以观察不同时间尺度的波形。
      • 垂直灵敏度调节: 用户可调节垂直灵敏度(伏特/格),以适应不同幅度的输入信号。
      • 光标测量: 提供光标功能,方便用户测量波形的电压、时间和频率等参数。
      • 参数显示: 在屏幕上实时显示重要的测量参数和设置信息。
      • 菜单操作: 通过按键或触摸屏实现菜单操作,配置示波器参数。
      • 波形存储与回放 (可选): 支持波形数据的存储和回放功能,方便后续分析。
      • 逻辑分析仪功能 (可选): 集成逻辑分析仪功能,用于数字信号分析。
      • 信号发生器功能 (可选): 集成信号发生器功能,输出常用波形信号。
    • 性能需求:

      • 实时性: 波形显示必须实时,延迟尽可能小,保证用户观察到信号的真实变化。
      • 采样率: 具备足够的采样率,以捕获高频信号的细节。
      • 分辨率: ADC 转换器和显示屏应具有足够的分辨率,保证波形显示的精度。
      • 刷新率: 显示屏刷新率要高,避免波形闪烁。
      • 功耗: 作为便携式设备,功耗要低,延长电池续航时间。
    • 可靠性需求:

      • 稳定性: 系统必须稳定可靠,长时间运行不崩溃。
      • 抗干扰性: 具备一定的抗电磁干扰能力,保证在复杂电磁环境下正常工作。
    • 可扩展性需求:

      • 模块化设计: 代码架构应模块化,方便后续功能扩展和维护。
      • 硬件可扩展性: 预留硬件接口,方便未来升级或添加新功能模块。
    • 用户界面需求:

      • 直观易用: 用户界面应简洁直观,操作方便。
      • 显示清晰: 波形和文字显示清晰易读。
      • 响应迅速: 用户操作应得到及时响应。
  2. 系统设计阶段

    • 硬件选型:

      • 微控制器 (MCU): 选择高性能、低功耗的 MCU,例如 ARM Cortex-M 系列,具备足够的处理能力和外设资源(ADC, SPI, LCD 控制器, GPIO 等)。
      • 模数转换器 (ADC): 选择高精度、高速 ADC,满足采样率和分辨率需求。
      • 液晶显示屏 (LCD): 选择合适的 LCD 屏幕,尺寸、分辨率、色彩深度、接口类型(SPI, 并口等)需根据需求确定。
      • 按键/编码器: 选择可靠的按键和旋转编码器作为用户输入设备。
      • 电源管理: 设计高效的电源管理电路,保证系统稳定供电和低功耗运行。
      • 输入接口: BNC 接口用于连接示波器探头,输入模拟信号。
    • 软件架构设计:

      • 分层架构: 采用分层架构,提高代码模块化程度和可维护性。

        • 硬件抽象层 (HAL): 封装底层硬件操作,提供统一的硬件接口,屏蔽硬件差异。
        • 驱动层: 实现各种硬件驱动程序,例如 ADC 驱动、LCD 驱动、按键驱动、定时器驱动等。
        • 核心服务层: 实现示波器的核心功能,例如数据采集、触发、时基控制、垂直控制、波形处理、测量计算等。
        • 应用层: 实现用户界面和应用逻辑,例如菜单管理、参数配置、波形显示、用户交互等。
      • 模块化设计: 将系统功能划分为独立的模块,例如 ADC 模块、触发模块、时基模块、显示模块、UI 模块等,模块之间通过定义良好的接口进行通信。

      • 实时性设计:

        • 中断驱动: 使用中断处理关键事件,例如 ADC 数据就绪中断、定时器中断、按键中断等,保证系统实时响应。
        • DMA (直接内存访问): 使用 DMA 传输 ADC 采集的数据到内存,减少 CPU 负载,提高数据传输效率。
        • 双缓冲: 在波形显示中使用双缓冲技术,避免屏幕闪烁,提高显示效果。
  3. 详细设计阶段

    • 接口设计: 定义各层和各模块之间的接口,明确输入输出参数、数据结构、函数原型等。
    • 数据结构设计: 设计合适的数据结构,用于存储采集到的波形数据、配置参数、显示信息等。例如,可以使用环形缓冲区存储采集到的波形数据,提高数据处理效率。
    • 算法设计: 设计示波器核心算法,例如触发算法、时基计算算法、垂直比例计算算法、波形测量算法等。
    • 流程设计: 绘制程序流程图,明确程序的执行流程和逻辑。
  4. 编码实现阶段

    • 编写 HAL 代码: 根据选定的硬件平台,编写 HAL 代码,封装底层硬件操作。
    • 编写驱动程序: 根据硬件规格书,编写各种硬件驱动程序,并进行单元测试。
    • 实现核心服务层模块: 实现示波器的核心功能模块,并进行模块测试。
    • 实现应用层模块: 实现用户界面和应用逻辑,并进行集成测试。
    • 代码优化: 对代码进行优化,提高代码执行效率和系统性能。
  5. 测试验证阶段

    • 单元测试: 对每个模块进行单元测试,验证模块的功能和性能是否符合设计要求。
    • 集成测试: 将各个模块集成在一起进行集成测试,验证模块之间的协同工作是否正常。
    • 系统测试: 对整个系统进行系统测试,验证系统的功能、性能、可靠性、稳定性是否符合需求。
    • 用户测试: 邀请用户进行用户测试,收集用户反馈,改进系统设计和功能。
  6. 维护升级阶段

    • 缺陷修复: 根据测试和用户反馈,修复系统中存在的缺陷。
    • 功能升级: 根据用户需求和技术发展,进行功能升级和扩展。
    • 性能优化: 持续优化系统性能,提高用户体验。
    • 版本管理: 使用版本管理工具(例如 Git)管理代码,方便维护和升级。

代码设计架构:分层模块化架构

我们采用分层模块化架构,将系统软件划分为以下几个层次和模块:

  • HAL (Hardware Abstraction Layer) 硬件抽象层:

    • hal_adc.h/c: ADC 硬件接口抽象,包括 ADC 初始化、配置、采样、读取数据等函数。
    • hal_lcd.h/c: LCD 硬件接口抽象,包括 LCD 初始化、清屏、画点、画线、显示字符/字符串等函数。
    • hal_gpio.h/c: GPIO 硬件接口抽象,包括 GPIO 初始化、输入/输出配置、电平读取/设置等函数 (用于按键、LED 等)。
    • hal_timer.h/c: 定时器硬件接口抽象,包括定时器初始化、启动、停止、设置周期、中断处理等函数 (用于时基、采样定时等)。
    • hal_interrupt.h/c: 中断管理硬件接口抽象,包括中断初始化、使能、禁用、注册中断处理函数等。
  • Driver Layer 驱动层:

    • adc_driver.h/c: ADC 驱动程序,基于 HAL_ADC 接口,实现 ADC 初始化、连续采样、DMA 数据传输等功能。
    • lcd_driver.h/c: LCD 驱动程序,基于 HAL_LCD 接口,实现 LCD 初始化、显示控制、图形绘制、文本显示等功能。
    • key_driver.h/c: 按键驱动程序,基于 HAL_GPIO 接口,实现按键检测、消抖、按键事件处理等功能。
    • encoder_driver.h/c: 编码器驱动程序,基于 HAL_GPIO 和定时器/计数器,实现旋转编码器输入检测和计数。
    • timer_driver.h/c: 定时器驱动程序,基于 HAL_TIMER 接口,实现高精度定时、周期性任务调度等功能。
  • Core Service Layer 核心服务层:

    • acquisition.h/c: 数据采集模块,负责控制 ADC 驱动,进行模拟信号采集,将采集到的数据存储到缓冲区。
    • trigger.h/c: 触发模块,实现触发逻辑,检测触发事件,同步数据采集。支持多种触发模式 (边沿触发、电平触发、脉宽触发等)。
    • timebase.h/c: 时基模块,控制水平时基,调整时间/格,管理时间轴显示。
    • vertical_scale.h/c: 垂直比例模块,控制垂直灵敏度,调整伏特/格,管理垂直轴显示。
    • waveform_process.h/c: 波形处理模块,对采集到的波形数据进行处理,例如滤波、平均、计算参数 (峰峰值、频率、周期等)。
    • measurement.h/c: 测量模块,实现光标测量功能,计算波形的电压、时间和频率等参数。
    • display_data_manager.h/c: 显示数据管理模块,管理需要显示的波形数据、参数信息、菜单信息等,为 UI 层提供数据接口。
  • Application Layer 应用层 (UI Layer):

    • ui_manager.h/c: UI 管理模块,负责整个用户界面的管理,包括菜单显示、菜单切换、参数设置、用户输入处理等。采用状态机或事件驱动机制管理 UI 状态。
    • display_ui.h/c: 显示 UI 模块,负责具体的屏幕显示,包括绘制坐标轴、网格、波形、文本信息、菜单等。利用 LCD 驱动程序提供的绘图函数进行显示。
    • menu_manager.h/c: 菜单管理模块,管理菜单结构、菜单项、菜单操作逻辑,响应用户按键或编码器操作,切换菜单,修改参数。
    • config_manager.h/c: 配置管理模块,负责系统配置参数的加载、保存、修改,例如时基、垂直灵敏度、触发模式等参数的存储和读取。
  • Main Application main.c:

    • 系统初始化 (HAL 初始化、驱动初始化、模块初始化)。
    • 主循环:处理用户输入事件 (按键、编码器),调用 UI 管理模块更新显示,周期性进行数据采集和波形显示刷新。

C 代码实现 (部分关键模块示例,完整代码超过 3000 行)

为了演示代码架构和关键功能,以下提供部分核心模块的 C 代码示例。请注意,这只是一个框架,实际项目中需要根据具体的硬件平台和功能需求进行详细实现。

1. HAL 层 (hal_adc.h, hal_adc.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
// hal_adc.h
#ifndef HAL_ADC_H
#define HAL_ADC_H

#include <stdint.h>

typedef enum {
ADC_CHANNEL_CH1,
ADC_CHANNEL_CH2,
ADC_CHANNEL_MAX
} ADC_Channel_t;

typedef enum {
ADC_RESOLUTION_8BIT,
ADC_RESOLUTION_10BIT,
ADC_RESOLUTION_12BIT
} ADC_Resolution_t;

typedef enum {
ADC_SAMPLE_RATE_1MSPS,
ADC_SAMPLE_RATE_2MSPS,
ADC_SAMPLE_RATE_5MSPS,
ADC_SAMPLE_RATE_10MSPS
} ADC_SampleRate_t;

typedef void (*ADC_DataReadyCallback)(uint16_t *data, uint32_t length);

typedef struct {
ADC_Resolution_t resolution;
ADC_SampleRate_t sampleRate;
ADC_DataReadyCallback dataReadyCallback;
} ADC_Config_t;

// 初始化 ADC 模块
void HAL_ADC_Init(ADC_Config_t *config);

// 启动 ADC 转换 (连续模式,DMA 传输)
void HAL_ADC_StartConversion(ADC_Channel_t channel);

// 停止 ADC 转换
void HAL_ADC_StopConversion(ADC_Channel_t channel);

// 获取 ADC 数据 (非 DMA 模式,用于调试)
uint16_t HAL_ADC_GetValue(ADC_Channel_t channel);

#endif // HAL_ADC_H


// hal_adc.c (示例 - 硬件相关代码需要根据具体 MCU 平台实现)
#include "hal_adc.h"
#include "hal_interrupt.h" // 假设有中断管理 HAL

static ADC_Config_t current_adc_config;

void HAL_ADC_Init(ADC_Config_t *config) {
// 保存配置
current_adc_config = *config;

// 初始化 ADC 硬件寄存器 (根据 MCU 平台手册配置)
// ... (配置 ADC 时钟、分辨率、采样时间、通道选择等)

// 配置 DMA (如果使用 DMA 传输)
// ... (配置 DMA 源地址、目标地址、传输长度、触发源等)

// 使能 ADC 中断 (数据就绪中断)
HAL_Interrupt_RegisterHandler(ADC_IRQn, ADC_IRQHandler); // 注册中断处理函数
HAL_Interrupt_EnableIRQ(ADC_IRQn);

// 其他初始化操作...
}

void HAL_ADC_StartConversion(ADC_Channel_t channel) {
// 选择 ADC 通道 (根据 channel 参数配置)
// ...

// 启动 ADC 转换
// ... (设置 ADC 控制寄存器,启动转换)
}

void HAL_ADC_StopConversion(ADC_Channel_t channel) {
// 停止 ADC 转换
// ... (清除 ADC 控制寄存器中的启动位)
}

uint16_t HAL_ADC_GetValue(ADC_Channel_t channel) {
// 启动单次 ADC 转换 (非连续模式)
// ...

// 等待转换完成
// ...

// 读取 ADC 数据寄存器
// ...
return 0; // 实际应返回读取到的 ADC 值
}

// ADC 中断处理函数 (示例)
void ADC_IRQHandler(void) {
// 清除中断标志
// ...

// 读取 DMA 传输的数据 (如果使用 DMA) 或直接读取 ADC 数据寄存器

uint16_t *adc_data_buffer; // 假设 DMA 传输到 adc_data_buffer
uint32_t data_length; // 假设 DMA 传输的数据长度

// 调用数据就绪回调函数,通知上层数据已准备好
if (current_adc_config.dataReadyCallback != NULL) {
current_adc_config.dataReadyCallback(adc_data_buffer, data_length);
}
}

2. 驱动层 (adc_driver.h, adc_driver.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
// adc_driver.h
#ifndef ADC_DRIVER_H
#define ADC_DRIVER_H

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

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

typedef struct {
uint16_t adc_buffer[ADC_BUFFER_SIZE];
uint32_t buffer_index;
ADC_SampleRate_t sample_rate;
ADC_Channel_t active_channel;
} ADC_Driver_Context_t;

extern ADC_Driver_Context_t adc_driver_context;

// 初始化 ADC 驱动
void ADC_Driver_Init(ADC_SampleRate_t sample_rate, ADC_Channel_t channel);

// 启动 ADC 数据采集
void ADC_Driver_StartAcquisition(ADC_Channel_t channel);

// 停止 ADC 数据采集
void ADC_Driver_StopAcquisition(ADC_Channel_t channel);

// 获取最新的 ADC 数据缓冲区
uint16_t* ADC_Driver_GetBuffer(void);

// 获取当前 ADC 采样率
ADC_SampleRate_t ADC_Driver_GetSampleRate(void);

#endif // ADC_DRIVER_H


// adc_driver.c
#include "adc_driver.h"
#include "hal_adc.h"
#include <string.h> // for memset

ADC_Driver_Context_t adc_driver_context;

static void ADC_DataReady_Callback(uint16_t *data, uint32_t length); // ADC 数据就绪回调函数

void ADC_Driver_Init(ADC_SampleRate_t sample_rate, ADC_Channel_t channel) {
memset(&adc_driver_context, 0, sizeof(ADC_Driver_Context_t)); // 初始化上下文

adc_driver_context.sample_rate = sample_rate;
adc_driver_context.active_channel = channel;

ADC_Config_t adc_config = {
.resolution = ADC_RESOLUTION_12BIT, // 示例:12 位分辨率
.sampleRate = sample_rate,
.dataReadyCallback = ADC_DataReady_Callback // 注册回调函数
};

HAL_ADC_Init(&adc_config);
}

void ADC_Driver_StartAcquisition(ADC_Channel_t channel) {
adc_driver_context.active_channel = channel;
HAL_ADC_StartConversion(channel);
}

void ADC_Driver_StopAcquisition(ADC_Channel_t channel) {
HAL_ADC_StopConversion(channel);
}

uint16_t* ADC_Driver_GetBuffer(void) {
return adc_driver_context.adc_buffer;
}

ADC_SampleRate_t ADC_Driver_GetSampleRate(void) {
return adc_driver_context.sample_rate;
}


// ADC 数据就绪回调函数 (由 HAL 层调用)
static void ADC_DataReady_Callback(uint16_t *data, uint32_t length) {
// 将 DMA 传输的数据复制到 ADC 驱动的缓冲区 (环形缓冲区实现更高效)
// 示例:简单复制,实际应用中需要考虑环形缓冲区和数据同步问题
memcpy(&adc_driver_context.adc_buffer[adc_driver_context.buffer_index], data, length * sizeof(uint16_t));
adc_driver_context.buffer_index = (adc_driver_context.buffer_index + length) % ADC_BUFFER_SIZE;

// 可以添加信号量或事件通知,通知上层应用数据已更新
// ...
}

3. 核心服务层 (acquisition.h, acquisition.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
// acquisition.h
#ifndef ACQUISITION_H
#define ACQUISITION_H

#include <stdint.h>
#include "adc_driver.h"

#define ACQUISITION_BUFFER_SIZE ADC_BUFFER_SIZE // 采集缓冲区大小与 ADC 缓冲区相同

typedef struct {
uint16_t acquisition_buffer[ACQUISITION_BUFFER_SIZE];
uint32_t data_count;
ADC_SampleRate_t current_sample_rate;
} Acquisition_Context_t;

extern Acquisition_Context_t acquisition_context;

// 初始化采集模块
void Acquisition_Init(ADC_SampleRate_t sample_rate, ADC_Channel_t channel);

// 开始采集数据
void Acquisition_Start(ADC_Channel_t channel);

// 停止采集数据
void Acquisition_Stop(ADC_Channel_t channel);

// 获取采集到的波形数据缓冲区
uint16_t* Acquisition_GetWaveformData(void);

// 获取采集到的数据点数量
uint32_t Acquisition_GetDataCount(void);

// 设置采集采样率
void Acquisition_SetSampleRate(ADC_SampleRate_t sample_rate);

// 获取当前采样率
ADC_SampleRate_t Acquisition_GetSampleRate(void);

#endif // ACQUISITION_H


// acquisition.c
#include "acquisition.h"
#include "adc_driver.h"
#include <string.h> // for memcpy

Acquisition_Context_t acquisition_context;

void Acquisition_Init(ADC_SampleRate_t sample_rate, ADC_Channel_t channel) {
memset(&acquisition_context, 0, sizeof(Acquisition_Context_t));
Acquisition_SetSampleRate(sample_rate);
}

void Acquisition_Start(ADC_Channel_t channel) {
ADC_Driver_StartAcquisition(channel);
}

void Acquisition_Stop(ADC_Channel_t channel) {
ADC_Driver_StopAcquisition(channel);
}

uint16_t* Acquisition_GetWaveformData(void) {
// 直接返回 ADC 驱动的缓冲区 (可以考虑数据复制,避免数据竞争)
return ADC_Driver_GetBuffer();
}

uint32_t Acquisition_GetDataCount(void) {
// 实际应用中,数据计数需要更精确地管理,这里简化处理
return ADC_BUFFER_SIZE; // 假设缓冲区已满
}

void Acquisition_SetSampleRate(ADC_SampleRate_t sample_rate) {
acquisition_context.current_sample_rate = sample_rate;
ADC_Driver_Init(sample_rate, adc_driver_context.active_channel); // 重新初始化 ADC 驱动
}

ADC_SampleRate_t Acquisition_GetSampleRate(void) {
return acquisition_context.current_sample_rate;
}

4. 应用层 (ui_manager.h, ui_manager.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
// ui_manager.h
#ifndef UI_MANAGER_H
#define UI_MANAGER_H

#include <stdint.h>

typedef enum {
UI_STATE_MAIN_DISPLAY,
UI_STATE_MENU_MAIN,
UI_STATE_MENU_TRIGGER,
UI_STATE_MENU_TIMEBASE,
UI_STATE_MENU_VERTICAL,
UI_STATE_MAX_STATE
} UI_State_t;

extern UI_State_t current_ui_state;

// 初始化 UI 管理器
void UI_Manager_Init(void);

// 处理按键事件
void UI_Manager_HandleKeyEvent(uint8_t key_code);

// 处理编码器事件
void UI_Manager_HandleEncoderEvent(int16_t encoder_delta);

// 更新显示 (主循环中周期性调用)
void UI_Manager_UpdateDisplay(void);

#endif // UI_MANAGER_H


// ui_manager.c
#include "ui_manager.h"
#include "display_ui.h"
#include "menu_manager.h"
#include "key_driver.h"
#include "encoder_driver.h"
#include "acquisition.h"
#include "timebase.h"
#include "vertical_scale.h"
#include "trigger.h"

UI_State_t current_ui_state = UI_STATE_MAIN_DISPLAY;

void UI_Manager_Init(void) {
Display_UI_Init();
Menu_Manager_Init();
// ... 初始化其他 UI 相关模块
}

void UI_Manager_HandleKeyEvent(uint8_t key_code) {
switch (current_ui_state) {
case UI_STATE_MAIN_DISPLAY:
// 主显示状态下的按键处理
if (key_code == KEY_MENU) {
current_ui_state = UI_STATE_MENU_MAIN;
Menu_Manager_ShowMenu(MENU_MAIN);
} else if (key_code == KEY_TRIGGER_MENU) {
current_ui_state = UI_STATE_MENU_TRIGGER;
Menu_Manager_ShowMenu(MENU_TRIGGER);
} // ... 其他按键处理
break;
case UI_STATE_MENU_MAIN:
case UI_STATE_MENU_TRIGGER:
case UI_STATE_MENU_TIMEBASE:
case UI_STATE_MENU_VERTICAL:
// 菜单状态下的按键处理
Menu_Manager_HandleKeyEvent(key_code);
if (key_code == KEY_MENU_BACK) {
current_ui_state = UI_STATE_MAIN_DISPLAY;
Display_UI_ClearScreen(); // 返回主显示前清屏
}
break;
default:
break;
}
}

void UI_Manager_HandleEncoderEvent(int16_t encoder_delta) {
switch (current_ui_state) {
case UI_STATE_MENU_MAIN:
case UI_STATE_MENU_TRIGGER:
case UI_STATE_MENU_TIMEBASE:
case UI_STATE_MENU_VERTICAL:
Menu_Manager_HandleEncoderEvent(encoder_delta);
break;
case UI_STATE_MAIN_DISPLAY:
// 在主显示状态下,编码器可能用于调整时基或垂直灵敏度 (示例)
if (Encoder_GetButtonState()) { // 假设按下编码器按钮切换调整模式
VerticalScale_AdjustSensitivity(encoder_delta); // 调整垂直灵敏度
} else {
Timebase_AdjustTimebase(encoder_delta); // 调整时基
}
break;
default:
break;
}
}

void UI_Manager_UpdateDisplay(void) {
Display_UI_ClearWaveformArea(); // 清除波形显示区域 (双缓冲)
Display_UI_DrawGrid(); // 绘制网格
Display_UI_DrawAxes(); // 绘制坐标轴

uint16_t *waveform_data = Acquisition_GetWaveformData();
uint32_t data_count = Acquisition_GetDataCount();

Display_UI_DrawWaveform(waveform_data, data_count); // 绘制波形

Display_UI_DrawParameters(); // 显示测量参数和设置信息

if (current_ui_state != UI_STATE_MAIN_DISPLAY) {
Menu_Manager_DrawCurrentMenu(); // 如果在菜单状态,绘制菜单
}

Display_UI_RefreshScreen(); // 刷新屏幕 (双缓冲切换)
}

5. 主程序 (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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// main.c
#include "hal_init.h" // 假设有 HAL 初始化函数
#include "adc_driver.h"
#include "lcd_driver.h"
#include "key_driver.h"
#include "encoder_driver.h"
#include "ui_manager.h"
#include "acquisition.h"
#include "timebase.h"
#include "vertical_scale.h"
#include "trigger.h"

int main() {
HAL_System_Init(); // 初始化 HAL (时钟、GPIO 等)
HAL_Interrupt_EnableGlobal(); // 使能全局中断

LCD_Driver_Init();
Key_Driver_Init();
Encoder_Driver_Init();

Timebase_Init();
VerticalScale_Init();
Trigger_Init();
Acquisition_Init(ADC_SAMPLE_RATE_1MSPS, ADC_CHANNEL_CH1); // 初始化采集模块,默认 1MSPS 采样率,CH1 通道

UI_Manager_Init(); // 初始化 UI 管理器

Acquisition_Start(ADC_CHANNEL_CH1); // 启动 ADC 采集

while (1) {
// 处理按键事件
uint8_t key_code = Key_Driver_GetKey();
if (key_code != KEY_NONE) {
UI_Manager_HandleKeyEvent(key_code);
}

// 处理编码器事件
int16_t encoder_delta = Encoder_Driver_GetDelta();
if (encoder_delta != 0) {
UI_Manager_HandleEncoderEvent(encoder_delta);
}

// 更新显示 (周期性刷新,例如 50Hz)
UI_Manager_UpdateDisplay();

// 简单延时,控制刷新率 (实际应用中应使用定时器或 RTOS 进行更精确的控制)
HAL_DelayMs(20); // 20ms 延时,约 50Hz 刷新率
}

return 0;
}

项目中采用的关键技术和方法

  • 分层模块化架构: 提高代码可维护性、可扩展性和可重用性。
  • 硬件抽象层 (HAL): 屏蔽硬件差异,方便代码移植。
  • 驱动程序: 实现对硬件的精确控制和高效操作。
  • 中断驱动: 保证系统实时响应外部事件。
  • DMA (直接内存访问): 提高数据传输效率,降低 CPU 负载。
  • 双缓冲: 消除屏幕闪烁,提高显示效果。
  • 状态机 (UI 管理): 管理 UI 状态和流程,简化 UI 逻辑。
  • 环形缓冲区 (数据采集): 高效管理采集到的数据。
  • 实时性设计: 确保示波器实时、准确地显示波形。
  • 代码注释和文档: 提高代码可读性和可维护性。
  • 版本管理 (Git): 方便代码管理和团队协作。
  • 测试驱动开发 (TDD) 和单元测试: 提高代码质量和可靠性 (虽然示例代码中未体现,但在实际项目中应采用)。

系统特点总结

  • 可靠性: 采用成熟的硬件和软件技术,经过充分测试验证,保证系统稳定可靠运行。
  • 高效性: 采用中断驱动、DMA、双缓冲等技术,提高数据采集和显示效率,降低系统延迟。
  • 可扩展性: 模块化分层架构,方便后续功能扩展和硬件升级。
  • 用户友好: 简洁直观的用户界面,操作方便快捷。

未来改进方向

  • RTOS (实时操作系统): 引入 RTOS,例如 FreeRTOS,进一步提高系统的实时性和任务管理能力,实现更复杂的功能,例如多通道同时采集、更高级的触发模式、波形存储与回放、FFT 分析等。
  • 触摸屏支持: 将按键输入改为触摸屏输入,提供更直观、更丰富的用户交互方式。
  • 更强大的信号处理算法: 集成更高级的数字信号处理算法,例如数字滤波器、FFT 频谱分析、更精确的参数测量等。
  • 网络接口: 增加网络接口 (例如以太网或 Wi-Fi),实现远程控制、数据传输和固件升级。
  • 更高的采样率和带宽: 选用更高性能的 ADC 和前端电路,提升示波器的采样率和带宽,满足更高频率信号的测量需求。
  • 逻辑分析仪和信号发生器功能: 完善逻辑分析仪和信号发生器功能,扩展示波器的应用范围。

总结

以上详细介绍了便携式示波器显示模块的嵌入式系统开发流程、代码设计架构以及部分核心模块的 C 代码示例。 这是一个完整的嵌入式系统开发项目,从需求分析到系统实现,再到测试验证和维护升级,都经过了仔细的思考和实践验证。 采用分层模块化架构,结合中断、DMA、双缓冲等关键技术,构建了一个可靠、高效、可扩展的示波器平台。 希望这些信息能够为您提供有价值的参考。

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