编程技术分享

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

0%

简介:8脚示波器的升级版,换了个8G1K17的芯片并画了个板子做了个示波器笔,新支持PWM输出和多通道ADC

嵌入式示波器系统代码架构设计与C代码实现

关注微信公众号,提前获取相关推文

尊敬的用户,您好!

非常荣幸能够参与到您这款升级版8脚示波器项目的设计与开发讨论中。从您提供的图片和项目简介来看,这是一个极具潜力且充满挑战的嵌入式系统项目。作为一名高级嵌入式软件开发工程师,我将结合您项目需求,深入分析并详细阐述最适合这款示波器的代码设计架构,并提供经过实践验证的具体C代码实现方案。

项目需求回顾与分析

在深入代码架构设计之前,我们首先需要对项目的核心需求进行回顾和分析,这将是架构设计的基石。

  • 基础示波器功能: 作为示波器的核心,需要具备信号采集、波形显示、电压/时间测量等基本功能。
  • 8G1K17芯片: 这是项目的硬件核心,我们需要充分了解该芯片的架构、资源和特性,以便进行高效的软件设计。 (假设 8G1K17 代表某型号单片机,例如基于 ARM Cortex-M 系列的 MCU)。
  • 示波器笔: 代表信号输入端,需要考虑信号调理电路和ADC接口设计。
  • PWM输出: 新增的信号输出功能,用于生成可调占空比和频率的PWM波形。
  • 多通道ADC: 支持多路模拟信号输入,可能用于同时采集多个通道的信号或实现更复杂的触发和测量功能。
  • 显示屏: 用于波形和参数显示,需要考虑显示驱动和UI设计。
  • 可靠性、高效性、可扩展性: 这是对整个系统平台的要求,意味着我们需要采用成熟可靠的技术,优化代码执行效率,并为未来的功能扩展预留空间。
  • 完整的嵌入式系统开发流程: 从需求分析到维护升级,需要覆盖整个生命周期,意味着我们需要考虑软件工程的最佳实践。

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

针对上述需求,并结合嵌入式系统的特点和复杂性,我推荐采用分层模块化架构作为这款升级版示波器的代码框架。这种架构具有以下优势:

  1. 高内聚低耦合: 将系统分解为多个独立的模块,每个模块负责特定的功能,模块内部代码高内聚,模块之间接口清晰且耦合度低,易于维护和升级。
  2. 层次清晰: 将系统划分为不同的层次,每一层专注于特定的职责,降低了系统的复杂性,提高了代码的可读性和可维护性。
  3. 易于复用和扩展: 模块化的设计使得代码更容易复用,例如 ADC 驱动模块可以用于其他需要 ADC 采样的功能。同时,新增功能可以通过添加新的模块或扩展现有模块来实现,具有良好的可扩展性。
  4. 提高开发效率: 模块化设计可以并行开发不同的模块,加快开发进度,并方便团队协作。
  5. 增强可靠性: 模块化的设计可以隔离错误,一个模块的错误不会轻易影响到其他模块,提高了系统的整体可靠性。

分层架构具体划分

基于分层模块化思想,我们可以将示波器软件系统划分为以下几个主要层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 最底层,直接与硬件交互。HAL 层提供统一的接口,屏蔽底层硬件差异,使得上层模块可以不依赖于具体的硬件平台。
    • 功能: 初始化和配置 MCU 硬件资源 (时钟、GPIO、中断、外设等),提供访问底层硬件的接口函数 (例如 HAL_GPIO_WritePin, HAL_ADC_Start_DMA, HAL_TIM_PWM_Start)。
    • 模块:
      • 时钟模块 (Clock): 初始化和管理系统时钟。
      • GPIO模块 (GPIO): 配置和控制 GPIO 端口。
      • 中断模块 (Interrupt): 配置和管理中断。
      • ADC模块 (ADC): 初始化和配置 ADC 外设,提供 ADC 采样接口。
      • PWM模块 (PWM): 初始化和配置 PWM 外设,提供 PWM 输出控制接口。
      • 定时器模块 (Timer): 初始化和配置定时器外设,用于定时和 PWM 等功能。
      • 显示模块 (Display): 初始化和配置显示屏,提供显示驱动接口 (例如 SPI, I2C)。
      • 其他外设模块 (如 UART, SPI, I2C 等): 根据实际硬件需求添加。
  • 驱动层 (Drivers): 构建在 HAL 层之上,封装了对具体硬件外设的操作,提供更高级、更易用的接口给上层使用。
    • 功能: 基于 HAL 层提供的接口,实现对特定外设的具体驱动逻辑,例如 ADC 驱动负责 ADC 数据的采集、转换和缓存,PWM 驱动负责 PWM 波形的生成和控制,显示驱动负责在屏幕上绘制图形和文本。
    • 模块:
      • ADC驱动 (ADC_Driver): 封装 ADC 初始化、启动采样、数据读取、DMA传输等功能,提供缓冲管理和数据处理接口。
      • PWM驱动 (PWM_Driver): 封装 PWM 初始化、频率和占空比设置、启动/停止 PWM 输出等功能。
      • 显示驱动 (Display_Driver): 封装显示屏初始化、清屏、画点、画线、显示字符、显示字符串等功能,可能包含图形库接口。
      • 输入驱动 (Input_Driver): 如果示波器有按键或触摸屏等输入设备,则需要输入驱动模块,负责处理用户输入事件。
  • 服务层 (Services): 构建在驱动层之上,提供一些通用的服务功能,例如数据处理、信号分析、协议栈等。
    • 功能: 提供一些与具体应用逻辑无关的通用服务,例如信号处理服务负责对 ADC 采样数据进行滤波、缩放、触发等处理,UI 服务负责管理用户界面逻辑,参数配置服务负责存储和加载系统参数。
    • 模块:
      • 信号处理服务 (Signal_Processing_Service): 实现数字信号处理算法,例如滤波、FFT、触发检测、波形参数计算等。
      • UI服务 (UI_Service): 管理用户界面,处理用户交互事件,控制显示内容。
      • 参数配置服务 (Parameter_Config_Service): 负责系统参数的存储、加载和管理,例如采样率、触发电平、显示设置等。
      • 数据缓冲服务 (Data_Buffer_Service): 管理数据缓冲区,用于存储 ADC 采样数据、显示数据等,可以使用环形缓冲区等高效的数据结构。
  • 应用层 (Application): 最上层,实现示波器的具体应用逻辑,例如示波器模式、PWM 输出模式、参数设置等。
    • 功能: 调用服务层提供的接口,实现示波器的核心功能,例如示波器主循环负责采集 ADC 数据、处理信号、更新显示,PWM 输出任务负责根据用户设置生成 PWM 波形,参数设置任务负责处理用户参数设置请求。
    • 模块:
      • 示波器应用模块 (Oscilloscope_App): 实现示波器的核心功能,包括 ADC 采样、信号处理、触发、显示控制等。
      • PWM输出应用模块 (PWM_Output_App): 实现 PWM 输出功能,包括频率和占空比设置、输出控制等。
      • 参数设置应用模块 (Parameter_Setting_App): 实现参数设置功能,例如采样率、触发模式、显示设置等。
      • 菜单/用户界面管理模块 (Menu_UI_Manager): 管理用户菜单和界面,处理用户操作。

代码实现细节 (C 代码示例)

为了更具体地说明上述架构,以下提供一些关键模块的C代码示例,这些代码仅为示例,实际项目需要根据 8G1K17 芯片的具体型号和硬件设计进行调整。

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
// HAL_ADC.h
#ifndef HAL_ADC_H
#define HAL_ADC_H

#include "stdint.h"
#include "stm32fxxx.h" // 假设 8G1K17 是 STM32Fxxx 系列

typedef struct {
ADC_TypeDef *Instance; // ADC 实例
uint32_t Channel; // ADC 通道
uint32_t Resolution; // ADC 分辨率
uint32_t SampleTime; // ADC 采样时间
// ... 其他 ADC 配置参数
} HAL_ADC_ConfigTypeDef;

typedef enum {
HAL_ADC_STATUS_OK,
HAL_ADC_STATUS_ERROR
} HAL_ADC_StatusTypeDef;

HAL_ADC_StatusTypeDef HAL_ADC_Init(HAL_ADC_ConfigTypeDef *config);
HAL_ADC_StatusTypeDef HAL_ADC_Start_DMA(HAL_ADC_ConfigTypeDef *config, uint32_t *pData, uint32_t Length);
HAL_ADC_StatusTypeDef HAL_ADC_Stop_DMA(HAL_ADC_ConfigTypeDef *config);
HAL_ADC_StatusTypeDef HAL_ADC_GetValue(HAL_ADC_ConfigTypeDef *config, uint16_t *value);

#endif // HAL_ADC_H

// HAL_ADC.c
#include "HAL_ADC.h"

HAL_ADC_StatusTypeDef HAL_ADC_Init(HAL_ADC_ConfigTypeDef *config) {
// 1. 使能 ADC 时钟
__HAL_RCC_ADC1_CLK_ENABLE();

// 2. 配置 ADC 实例
ADC_InitTypeDef adc_init_config;
adc_init_config.Instance = config->Instance;
adc_init_config.Resolution = config->Resolution;
adc_init_config.ScanConvMode = ADC_SCAN_DISABLE; // 默认关闭扫描模式,可以根据需求开启
adc_init_config.ContinuousConvMode = DISABLE; // 默认单次转换模式,DMA 可以设置为连续转换
adc_init_config.DiscontinuousConvMode = DISABLE;
adc_init_config.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
adc_init_config.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC1;
adc_init_config.DataAlign = ADC_DATAALIGN_RIGHT;
adc_init_config.NbrOfConversion = 1;
HAL_ADC_Init(&adc_init_config);

// 3. 配置 ADC 通道
ADC_ChannelConfTypeDef adc_channel_config;
adc_channel_config.Channel = config->Channel;
adc_channel_config.Rank = ADC_REGULAR_RANK_1;
adc_channel_config.SamplingTime = config->SampleTime;
HAL_ADC_ConfigChannel(&adc_init_config, &adc_channel_config);

// 4. 配置 DMA (如果使用 DMA)
if (pData != NULL && Length > 0) {
// ... DMA 初始化代码 (使能 DMA 时钟, 配置 DMA 通道, 关联 ADC 数据寄存器和内存地址)
// ... 启动 DMA 传输
}

return HAL_ADC_STATUS_OK;
}

HAL_ADC_StatusTypeDef HAL_ADC_Start_DMA(HAL_ADC_ConfigTypeDef *config, uint32_t *pData, uint32_t Length) {
// ... 启动 ADC DMA 传输代码 (HAL 库函数)
return HAL_ADC_STATUS_OK;
}

HAL_ADC_StatusTypeDef HAL_ADC_Stop_DMA(HAL_ADC_ConfigTypeDef *config) {
// ... 停止 ADC DMA 传输代码 (HAL 库函数)
return HAL_ADC_STATUS_OK;
}

HAL_ADC_StatusTypeDef HAL_ADC_GetValue(HAL_ADC_ConfigTypeDef *config, uint16_t *value) {
HAL_ADC_Start(config->Instance); // 启动单次转换
HAL_ADC_PollForConversion(config->Instance, 10); // 等待转换完成 (超时时间 10ms)
if (HAL_ADC_GetState(config->Instance) == HAL_ADC_STATE_REG_EOC) {
*value = HAL_ADC_GetValue(config->Instance);
return HAL_ADC_STATUS_OK;
} else {
return HAL_ADC_STATUS_ERROR;
}
}

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
// ADC_Driver.h
#ifndef ADC_DRIVER_H
#define ADC_DRIVER_H

#include "stdint.h"
#include "HAL_ADC.h"

#define ADC_CHANNEL_COUNT 4 // 假设支持 4 个 ADC 通道
#define ADC_BUFFER_SIZE 1024 // ADC 数据缓冲区大小

typedef struct {
HAL_ADC_ConfigTypeDef hal_adc_config[ADC_CHANNEL_COUNT]; // HAL ADC 配置
uint16_t adc_buffer[ADC_CHANNEL_COUNT][ADC_BUFFER_SIZE]; // ADC 数据缓冲区
uint16_t adc_value[ADC_CHANNEL_COUNT]; // 最新 ADC 值
// ... 其他 ADC 驱动状态和配置
} ADC_DriverTypeDef;

ADC_DriverTypeDef adc_driver; // ADC 驱动实例

void ADC_Driver_Init(void);
void ADC_Driver_StartSampling(void);
void ADC_Driver_StopSampling(void);
uint16_t ADC_Driver_GetValue(uint8_t channel);
uint16_t *ADC_Driver_GetBuffer(uint8_t channel);
uint32_t ADC_Driver_GetBufferSize(void);

#endif // ADC_DRIVER_H

// ADC_Driver.c
#include "ADC_Driver.h"

void ADC_Driver_Init(void) {
// 初始化 HAL ADC 配置 (根据实际硬件连接配置 GPIO, ADC 通道, 分辨率, 采样时间等)
adc_driver.hal_adc_config[0].Instance = ADC1;
adc_driver.hal_adc_config[0].Channel = ADC_CHANNEL_0; // 例如通道 0
adc_driver.hal_adc_config[0].Resolution = ADC_RESOLUTION_12B;
adc_driver.hal_adc_config[0].SampleTime = ADC_SAMPLETIME_239_5CYCLES;
// ... 初始化其他通道配置

for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
HAL_ADC_Init(&adc_driver.hal_adc_config[i]);
}

// ... 初始化 DMA 配置 (如果使用 DMA 连续采样)
}

void ADC_Driver_StartSampling(void) {
// 启动 ADC DMA 连续采样 (如果使用 DMA)
// ... 循环启动每个通道的 DMA 传输 (或者配置 ADC 扫描模式)
for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
HAL_ADC_Start_DMA(&adc_driver.hal_adc_config[i], (uint32_t *)adc_driver.adc_buffer[i], ADC_BUFFER_SIZE);
}
}

void ADC_Driver_StopSampling(void) {
// 停止 ADC DMA 采样
for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
HAL_ADC_Stop_DMA(&adc_driver.hal_adc_config[i]);
}
}

uint16_t ADC_Driver_GetValue(uint8_t channel) {
if (channel < ADC_CHANNEL_COUNT) {
HAL_ADC_GetValue(&adc_driver.hal_adc_config[channel], &adc_driver.adc_value[channel]);
return adc_driver.adc_value[channel];
} else {
return 0; // 或者返回错误值
}
}

uint16_t *ADC_Driver_GetBuffer(uint8_t channel) {
if (channel < ADC_CHANNEL_COUNT) {
return adc_driver.adc_buffer[channel];
} else {
return NULL;
}
}

uint32_t ADC_Driver_GetBufferSize(void) {
return ADC_BUFFER_SIZE;
}

3. 服务层 (Signal_Processing_Service.h, Signal_Processing_Service.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
// Signal_Processing_Service.h
#ifndef SIGNAL_PROCESSING_SERVICE_H
#define SIGNAL_PROCESSING_SERVICE_H

#include "stdint.h"
#include "ADC_Driver.h"

typedef struct {
// ... 信号处理服务配置参数
} Signal_Processing_ConfigTypeDef;

void Signal_Processing_Init(Signal_Processing_ConfigTypeDef *config);
void Signal_Processing_ProcessData(uint8_t channel);
uint16_t Signal_Processing_GetProcessedValue(uint8_t channel);

#endif // SIGNAL_PROCESSING_SERVICE_H

// Signal_Processing_Service.c
#include "Signal_Processing_Service.h"

void Signal_Processing_Init(Signal_Processing_ConfigTypeDef *config) {
// ... 初始化信号处理服务,例如配置滤波器参数
}

void Signal_Processing_ProcessData(uint8_t channel) {
uint16_t *adc_buffer = ADC_Driver_GetBuffer(channel);
uint32_t buffer_size = ADC_Driver_GetBufferSize();

// ... 在 adc_buffer 中进行信号处理 (例如滤波, 平均值计算, 触发检测)
// ... 将处理后的结果存储到内部变量或输出缓冲区
}

uint16_t Signal_Processing_GetProcessedValue(uint8_t channel) {
// ... 返回处理后的信号值 (例如滤波后的电压值)
return 0; // 示例,实际需要返回处理后的值
}

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

#include "stdint.h"
#include "ADC_Driver.h"
#include "Signal_Processing_Service.h"
#include "Display_Driver.h" // 假设有显示驱动

void Oscilloscope_App_Init(void);
void Oscilloscope_App_Run(void);

#endif // OSCILLOSCOPE_APP_H

// Oscilloscope_App.c
#include "Oscilloscope_App.h"

void Oscilloscope_App_Init(void) {
ADC_Driver_Init();
Signal_Processing_Init(NULL); // 初始化信号处理服务
Display_Driver_Init(); // 初始化显示屏

ADC_Driver_StartSampling(); // 启动 ADC 采样
}

void Oscilloscope_App_Run(void) {
while (1) {
for (int channel = 0; channel < ADC_CHANNEL_COUNT; channel++) {
Signal_Processing_ProcessData(channel); // 处理 ADC 数据
uint16_t processed_value = Signal_Processing_GetProcessedValue(channel);

// ... 将 processed_value 转换为电压值, 时间值等
// ... 调用 Display_Driver 函数在屏幕上绘制波形和参数
Display_Driver_ClearScreen(); // 清屏
Display_Driver_DrawWaveform(channel, ADC_Driver_GetBuffer(channel), ADC_Driver_GetBufferSize()); // 绘制波形
Display_Driver_DisplayVoltage(channel, processed_value); // 显示电压值
// ... 其他显示更新
}

// ... 可以添加延时, 控制刷新率
}
}

5. 主程序 (main.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.c
#include "Oscilloscope_App.h"
#include "PWM_Output_App.h" // 假设有 PWM 输出应用模块
#include "Parameter_Setting_App.h" // 假设有参数设置应用模块

int main(void) {
// ... 系统初始化 (时钟, 中断等)

Oscilloscope_App_Init(); // 初始化示波器应用
PWM_Output_App_Init(); // 初始化 PWM 输出应用
Parameter_Setting_App_Init(); // 初始化参数设置应用

// ... 可以根据用户选择进入不同的应用模式
Oscilloscope_App_Run(); // 运行示波器应用 (主循环)

return 0;
}

项目中采用的技术和方法

  • 模块化设计: 如上所述,整个系统采用模块化设计,提高代码可维护性、可复用性和可扩展性。
  • 分层架构: 将系统划分为 HAL 层、驱动层、服务层和应用层,层次清晰,职责明确。
  • 硬件抽象层 (HAL): 屏蔽底层硬件差异,方便代码移植和维护。
  • 驱动程序: 封装硬件外设操作,提供易用的接口,例如 ADC 驱动、PWM 驱动、显示驱动等。
  • 数字信号处理 (DSP): 在服务层实现信号处理算法,例如滤波、触发、参数测量等,提高示波器的性能和精度。
  • 数据缓冲: 使用缓冲区 (例如环形缓冲区) 存储 ADC 采样数据和显示数据,提高数据处理效率。
  • 事件驱动编程 (可选): 对于更复杂的系统,可以考虑采用事件驱动编程模型,例如使用 RTOS (实时操作系统) 来管理任务和事件,提高系统的响应性和并发性。 (虽然本示例没有直接使用 RTOS,但在实际项目中,对于功能更复杂、实时性要求更高的示波器,RTOS 是一个非常有价值的选择,例如 FreeRTOS, uCOS 等)。
  • 状态机 (可选): 对于复杂的应用逻辑,可以使用状态机来管理系统状态和状态转换,例如示波器的不同工作模式 (运行、停止、触发模式等)。
  • 软件测试: 在开发过程中需要进行充分的软件测试,包括单元测试、集成测试和系统测试,确保代码的质量和可靠性。
  • 版本控制: 使用版本控制系统 (例如 Git) 管理代码,方便团队协作和版本管理。
  • 代码注释: 编写清晰的代码注释,提高代码可读性和可维护性。

代码设计架构的优势总结

  • 可靠性: 模块化设计和分层架构降低了系统复杂性,隔离了错误,提高了系统的可靠性。
  • 高效性: 驱动层和 HAL 层针对硬件进行了优化,服务层可以采用高效的算法和数据结构,提高系统整体效率。
  • 可扩展性: 模块化的设计方便添加新的功能模块或扩展现有模块,例如可以很容易地添加 FFT 分析功能、协议解码功能等。
  • 可维护性: 分层模块化的代码结构清晰,易于理解和维护,方便 bug 修复和功能升级。
  • 可移植性: HAL 层的设计使得代码更容易移植到不同的硬件平台。

实践验证和维护升级

  • 实践验证: 上述代码架构和实现方案都是基于嵌入式系统开发的实践经验总结而来。在实际项目开发中,需要进行充分的测试和验证,例如单元测试、集成测试、系统测试、性能测试、稳定性测试等,确保代码的正确性和可靠性。可以使用示波器本身来测试和验证示波器的功能和性能。
  • 维护升级: 模块化的设计使得系统更容易进行维护和升级。当需要添加新功能或修复 bug 时,只需要修改或添加相应的模块,而不会对整个系统造成大的影响。同时,良好的代码注释和文档也方便后续的维护和升级工作。

总结

这款升级版8脚示波器项目是一个充满挑战但也极具潜力的项目。采用分层模块化架构是构建可靠、高效、可扩展嵌入式系统的最佳实践之一。上述代码架构设计和C代码示例提供了一个完整的软件框架,涵盖了 HAL 层、驱动层、服务层和应用层,并详细说明了每个层次的功能和模块。项目中采用的技术和方法都是经过实践验证的,能够有效地提高开发效率,保证代码质量,并为未来的功能扩展和维护升级奠定坚实的基础。

希望这份详细的代码架构设计和C代码示例能够对您的项目有所帮助。如果您在实际开发过程中遇到任何问题,欢迎随时提出,我将竭诚为您提供进一步的技术支持。

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