我将为您详细阐述针对电子爱好者和学生学习数字示波器的基础项目,从代码架构设计到具体C代码实现,并深入探讨项目中采用的各种经过实践验证的技术和方法。关注微信公众号,提前获取相关推文
项目概述:
本项目旨在打造一款简易、实用、易于学习的数字示波器,采用单片机核心板作为主控,配合插件元器件,降低硬件门槛,使电子爱好者和学生能够轻松入门嵌入式系统开发和数字信号处理。该示波器将具备基本的波形采集、显示、测量功能,并预留扩展接口,方便后续的功能升级和二次开发。
代码设计架构:分层架构与模块化设计
为了构建可靠、高效、可扩展的系统平台,并考虑到学习的友好性,我将采用分层架构 与模块化设计 相结合的方式。这种架构将系统划分为若干个独立的层次和模块,每个层次和模块负责特定的功能,层次之间通过清晰的接口进行通信,模块内部高内聚,模块之间低耦合。
分层架构:
硬件抽象层 (HAL - Hardware Abstraction Layer): 这是最底层,直接与硬件打交道。HAL层封装了单片机具体的硬件操作,例如GPIO控制、ADC采集、定时器配置、SPI/I2C通信、LCD驱动等。HAL层的目的是屏蔽硬件差异,为上层提供统一的硬件访问接口,使得上层代码可以独立于具体的硬件平台。
驱动层 (Driver Layer): 驱动层构建在HAL层之上,是对HAL层功能的进一步封装和抽象。驱动层提供更高级、更易用的接口,例如ADC驱动负责ADC的初始化、采样配置、数据读取;LCD驱动负责LCD的显示初始化、字符/图形显示;按键驱动负责按键扫描、去抖动、事件处理等。驱动层的目的是简化上层应用对硬件的操作,提高代码的可读性和可维护性。
应用层 (Application Layer): 应用层是系统的核心逻辑层,负责实现示波器的具体功能,例如信号采集与处理、波形显示、参数测量、用户界面交互等。应用层调用驱动层提供的接口,实现业务逻辑。应用层应关注功能实现,而无需关心底层的硬件细节。
系统层 (System Layer): 系统层负责系统的初始化、任务调度、资源管理、错误处理等系统级功能。系统层为整个系统提供运行环境和支撑。
模块化设计:
在每个层次内部,我们进一步采用模块化设计,将功能分解为独立的模块。例如,在应用层,可以划分为:
数据采集模块 (Data Acquisition Module): 负责ADC采样、数据缓存、触发控制等。
信号处理模块 (Signal Processing Module): 负责数据预处理、滤波、FFT分析(可选)、参数计算(频率、幅度、周期等)。
显示模块 (Display Module): 负责波形绘制、界面元素显示、参数显示。
用户界面模块 (User Interface Module): 负责按键/旋钮输入处理、菜单管理、参数设置。
代码组织结构 (示例):
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 DigitalOscilloscope/ ├── Core/ // 单片机核心代码 (例如,基于STM32) │ ├── Inc/ // 核心头文件 │ │ ├── stm32fxxx_hal.h │ │ └── ... │ └── Src/ // 核心源文件 │ ├── stm32fxxx_it.c │ ├── system_stm32fxxx.c │ └── ... ├── Drivers/ // 驱动层 │ ├── BSP/ // 板级支持包 (Board Support Package),针对具体硬件平台 │ │ ├── Inc/ // BSP 头文件 │ │ │ ├── bsp_led.h │ │ │ ├── bsp_button.h │ │ │ ├── bsp_lcd.h │ │ │ ├── bsp_adc.h │ │ │ └── ... │ │ └── Src/ // BSP 源文件 │ │ ├── bsp_led.c │ │ ├── bsp_button.c │ │ ├── bsp_lcd.c │ │ ├── bsp_adc.c │ │ └── ... │ ├── HAL/ // 硬件抽象层 (如果使用了HAL库,则可以精简) │ │ ├── Inc/ // HAL 头文件 (根据需要自定义HAL层接口) │ │ │ ├── hal_gpio.h │ │ │ ├── hal_adc.h │ │ │ ├── hal_timer.h │ │ │ └── ... │ │ └── Src/ // HAL 源文件 (根据需要自定义HAL层实现) │ │ ├── hal_gpio.c │ │ ├── hal_adc.c │ │ ├── hal_timer.c │ │ └── ... │ └── Inc/ // 驱动层公共头文件 (可选) │ └── Src/ // 驱动层公共源文件 (可选) ├── Application/ // 应用层 │ ├── DataAcquisition/ // 数据采集模块 │ │ ├── Inc/ │ │ │ ├── data_acquisition.h │ │ │ └── ... │ │ └── Src/ │ │ ├── data_acquisition.c │ │ └── ... │ ├── SignalProcessing/ // 信号处理模块 │ │ ├── Inc/ │ │ │ ├── signal_processing.h │ │ │ └── ... │ │ └── Src/ │ │ ├── signal_processing.c │ │ └── ... │ ├── Display/ // 显示模块 │ │ ├── Inc/ │ │ │ ├── display.h │ │ │ └── ... │ │ └── Src/ │ │ ├── display.c │ │ └── ... │ ├── UI/ // 用户界面模块 │ │ ├── Inc/ │ │ │ ├── ui.h │ │ │ └── ... │ │ └── Src/ │ │ ├── ui.c │ │ └── ... │ ├── Inc/ // 应用层公共头文件 │ ├── Src/ // 应用层公共源文件 │ └── app.c // 应用层入口,例如任务调度、主循环 ├── System/ // 系统层 (可选) │ ├── Inc/ │ │ ├── system_config.h │ │ ├── system_error.h │ │ └── ... │ └── Src/ │ │ ├── system_init.c │ │ ├── system_error.c │ │ └── ... ├── Middlewares/ // 中间件 (例如,GUI库,文件系统,网络协议栈 - 可选) ├── Utilities/ // 工具库 (例如,环形缓冲区,数学库,字符串处理 - 可选) ├── User/ // 用户代码,通常 main.c 放在这里 │ ├── Inc/ │ │ ├── main.h │ │ └── ... │ └── Src/ │ ├── main.c │ └── ... ├── Config/ // 配置文件,例如时钟配置,外设配置 ├── Doc/ // 项目文档 ├── Lib/ // 外部库 (例如,第三方算法库) ├── build/ // 编译输出目录 ├── CMakeLists.txt // CMake 构建文件 (推荐使用 CMake 管理项目) ├── README.md // 项目说明文档 └── ...
C 代码实现 (核心模块示例):
由于代码量庞大,我将重点展示核心模块的代码实现,并提供详细的注释和说明。完整的代码实现会超出3000行,这里仅提供关键部分的代码框架和示例,以便您理解整体架构和实现思路。
1. HAL 层 (Hardware Abstraction Layer) 示例 (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 #ifndef HAL_ADC_H #define HAL_ADC_H #include "stdint.h" #include "stdbool.h" typedef enum { ADC_CHANNEL_1, ADC_CHANNEL_2, ADC_CHANNEL_COUNT } HAL_ADC_ChannelTypeDef; typedef enum { ADC_RESOLUTION_8BIT, ADC_RESOLUTION_10BIT, ADC_RESOLUTION_12BIT } HAL_ADC_ResolutionTypeDef; typedef struct { HAL_ADC_ChannelTypeDef Channel; HAL_ADC_ResolutionTypeDef Resolution; } HAL_ADC_InitTypeDef; bool HAL_ADC_Init (HAL_ADC_InitTypeDef *init) ;bool HAL_ADC_StartConversion (HAL_ADC_ChannelTypeDef channel) ;uint16_t HAL_ADC_GetValue (HAL_ADC_ChannelTypeDef channel) ;bool HAL_ADC_StopConversion (HAL_ADC_ChannelTypeDef channel) ;#endif
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 #include "hal_adc.h" #include "stm32fxxx_hal.h" static void ADC_Hardware_Config (HAL_ADC_InitTypeDef *init) { if (init->Channel == ADC_CHANNEL_1) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0 }; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } __HAL_RCC_ADC1_CLK_ENABLE(); ADC_HandleTypeDef hadc1; hadc1.Instance = ADC1; if (init->Resolution == ADC_RESOLUTION_8BIT) { hadc1.Init.Resolution = ADC_RESOLUTION_8B; } else if (init->Resolution == ADC_RESOLUTION_10BIT) { hadc1.Init.Resolution = ADC_RESOLUTION_10B; } else if (init->Resolution == ADC_RESOLUTION_12BIT) { hadc1.Init.Resolution = ADC_RESOLUTION_12B; } HAL_ADC_Init(&hadc1); } bool HAL_ADC_Init (HAL_ADC_InitTypeDef *init) { if (init == NULL ) { return false ; } ADC_Hardware_Config(init); return true ; } bool HAL_ADC_StartConversion (HAL_ADC_ChannelTypeDef channel) { ADC_HandleTypeDef hadc1; if (channel == ADC_CHANNEL_1) { ADC_ChannelConfTypeDef sConfig = {0 }; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); } HAL_ADC_Start(&hadc1); return true ; } uint16_t HAL_ADC_GetValue (HAL_ADC_ChannelTypeDef channel) { ADC_HandleTypeDef hadc1; HAL_ADC_PollForConversion(&hadc1, 10 ); if (HAL_ADC_GetState(&hadc1) == HAL_ADC_STATE_TIMEOUT) { return 0 ; } if (HAL_ADC_GetError(&hadc1) != HAL_ADC_ERROR_NONE) { return 0 ; } return (uint16_t )HAL_ADC_GetValue(&hadc1); } bool HAL_ADC_StopConversion (HAL_ADC_ChannelTypeDef channel) { ADC_HandleTypeDef hadc1; HAL_ADC_Stop(&hadc1); return true ; }
2. 驱动层 (Driver Layer) 示例 (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 #ifndef ADC_DRIVER_H #define ADC_DRIVER_H #include "stdint.h" #include "stdbool.h" #include "hal_adc.h" typedef struct { HAL_ADC_ChannelTypeDef Channel; HAL_ADC_ResolutionTypeDef Resolution; } ADC_Driver_InitTypeDef; bool ADC_Driver_Init (ADC_Driver_InitTypeDef *init) ;uint16_t ADC_Driver_GetVoltage_mV (void ) ;#endif
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 #include "adc_driver.h" #include "hal_adc.h" static ADC_Driver_InitTypeDef adc_config; bool ADC_Driver_Init (ADC_Driver_InitTypeDef *init) { if (init == NULL ) { return false ; } adc_config = *init; HAL_ADC_InitTypeDef hal_adc_init; hal_adc_init.Channel = init->Channel; hal_adc_init.Resolution = init->Resolution; return HAL_ADC_Init(&hal_adc_init); } uint16_t ADC_Driver_GetVoltage_mV (void ) { HAL_ADC_StartConversion(adc_config.Channel); uint16_t adc_value = HAL_ADC_GetValue(adc_config.Channel); uint32_t voltage_mV = (uint32_t )adc_value * 3300 / 4096 ; return (uint16_t )voltage_mV; }
3. 应用层 (Application Layer) - 数据采集模块 (data_acquisition.h, data_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 #ifndef DATA_ACQUISITION_H #define DATA_ACQUISITION_H #include "stdint.h" #include "stdbool.h" #define ADC_BUFFER_SIZE 1024 bool DataAcquisition_Init (void ) ;bool DataAcquisition_Start (void ) ;bool DataAcquisition_Stop (void ) ;uint16_t * DataAcquisition_GetBuffer (void ) ;uint16_t DataAcquisition_GetBufferSize (void ) ;#endif
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 #include "data_acquisition.h" #include "adc_driver.h" #include "circular_buffer.h" static uint16_t adc_buffer[ADC_BUFFER_SIZE]; static CircularBuffer_t adc_circular_buffer; static void DataAcquisition_Task (void ) { uint16_t voltage_mV = ADC_Driver_GetVoltage_mV(); CircularBuffer_Write(&adc_circular_buffer, &voltage_mV, sizeof (uint16_t )); } bool DataAcquisition_Init (void ) { ADC_Driver_InitTypeDef adc_init; adc_init.Channel = ADC_CHANNEL_1; adc_init.Resolution = ADC_RESOLUTION_12BIT; if (!ADC_Driver_Init(&adc_init)) { return false ; } CircularBuffer_Init(&adc_circular_buffer, adc_buffer, ADC_BUFFER_SIZE, sizeof (uint16_t )); return true ; } bool DataAcquisition_Start (void ) { return true ; } bool DataAcquisition_Stop (void ) { return true ; } uint16_t * DataAcquisition_GetBuffer (void ) { return adc_buffer; } uint16_t DataAcquisition_GetBufferSize (void ) { return CircularBuffer_DataCount(&adc_circular_buffer); } void App_MainLoop (void ) { while (1 ) { DataAcquisition_Task(); } }
4. 应用层 (Application Layer) - 显示模块 (display.h, display.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 #ifndef DISPLAY_H #define DISPLAY_H #include "stdint.h" #include "stdbool.h" #define LCD_WIDTH 160 #define LCD_HEIGHT 128 bool Display_Init (void ) ;void Display_ClearScreen (uint16_t color) ;void Display_DrawWaveform (uint16_t *waveform_data, uint16_t data_length, uint16_t color) ;void Display_DrawText (uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t bgcolor) ;#endif
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 #include "display.h" #include "bsp_lcd.h" bool Display_Init (void ) { if (!BSP_LCD_Init()) { return false ; } Display_ClearScreen(LCD_COLOR_BLACK); return true ; } void Display_ClearScreen (uint16_t color) { BSP_LCD_Clear(color); } void Display_DrawWaveform (uint16_t *waveform_data, uint16_t data_length, uint16_t color) { if (waveform_data == NULL || data_length == 0 ) { return ; } uint16_t y_scale = LCD_HEIGHT / 256 ; uint16_t y_offset = LCD_HEIGHT / 2 ; for (uint16_t i = 0 ; i < data_length - 1 && i < LCD_WIDTH - 1 ; i++) { uint16_t x1 = i; uint16_t y1 = y_offset - waveform_data[i] / 16 * y_scale; uint16_t x2 = i + 1 ; uint16_t y2 = y_offset - waveform_data[i + 1 ] / 16 * y_scale; BSP_LCD_DrawLine(x1, y1, x2, y2, color); } } void Display_DrawText (uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t bgcolor) { BSP_LCD_SetTextColor(color); BSP_LCD_SetBackColor(bgcolor); BSP_LCD_DisplayStringAt(x, y, (uint8_t *)text, LEFT_MODE); }
5. 应用层 (Application Layer) - 信号处理模块 (signal_processing.h, signal_processing.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef SIGNAL_PROCESSING_H #define SIGNAL_PROCESSING_H #include "stdint.h" #include "stdbool.h" bool SignalProcessing_Init (void ) ;float SignalProcessing_CalculateFrequency (uint16_t *waveform_data, uint16_t data_length, float sample_rate) ;uint16_t SignalProcessing_CalculatePeakToPeakVoltage (uint16_t *waveform_data, uint16_t data_length) ;#endif
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 #include "signal_processing.h" #include "math.h" bool SignalProcessing_Init (void ) { return true ; } float SignalProcessing_CalculateFrequency (uint16_t *waveform_data, uint16_t data_length, float sample_rate) { if (waveform_data == NULL || data_length < 2 || sample_rate <= 0 ) { return 0.0f ; } uint16_t zero_crossings = 0 ; for (uint16_t i = 1 ; i < data_length; i++) { if ((waveform_data[i - 1 ] < 2048 && waveform_data[i] >= 2048 ) || (waveform_data[i - 1 ] >= 2048 && waveform_data[i] < 2048 )) { zero_crossings++; } } if (zero_crossings < 2 ) { return 0.0f ; } float frequency = (float )zero_crossings / 2.0f / ((float )data_length / sample_rate); return frequency; } uint16_t SignalProcessing_CalculatePeakToPeakVoltage (uint16_t *waveform_data, uint16_t data_length) { if (waveform_data == NULL || data_length == 0 ) { return 0 ; } uint16_t min_value = 4095 ; uint16_t max_value = 0 ; for (uint16_t i = 0 ; i < data_length; i++) { if (waveform_data[i] < min_value) { min_value = waveform_data[i]; } if (waveform_data[i] > max_value) { max_value = waveform_data[i]; } } return max_value - min_value; }
6. 应用层 (Application Layer) - 用户界面模块 (ui.h, ui.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef UI_H #define UI_H #include "stdint.h" #include "stdbool.h" bool UI_Init (void ) ;void UI_MainLoop (void ) ;#endif
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 #include "ui.h" #include "bsp_button.h" #include "display.h" #include "data_acquisition.h" #include "signal_processing.h" #include "stdio.h" typedef enum { UI_STATE_WAVEFORM_DISPLAY, UI_STATE_MENU, UI_STATE_SETTINGS } UI_StateTypeDef; static UI_StateTypeDef current_ui_state = UI_STATE_WAVEFORM_DISPLAY; bool UI_Init (void ) { if (!BSP_Button_Init()) { return false ; } if (!Display_Init()) { return false ; } return true ; } void UI_MainLoop (void ) { while (1 ) { BSP_Button_Scan(); switch (current_ui_state) { case UI_STATE_WAVEFORM_DISPLAY: UI_WaveformDisplayState_Update(); break ; case UI_STATE_MENU: UI_MenuState_Update(); break ; case UI_STATE_SETTINGS: UI_SettingsState_Update(); break ; default : break ; } } } void UI_WaveformDisplayState_Update (void ) { Display_ClearScreen(LCD_COLOR_BLACK); uint16_t *waveform_data = DataAcquisition_GetBuffer(); uint16_t data_length = DataAcquisition_GetBufferSize(); if (waveform_data != NULL && data_length > 0 ) { Display_DrawWaveform(waveform_data, data_length, LCD_COLOR_GREEN); float frequency = SignalProcessing_CalculateFrequency(waveform_data, data_length, 100000.0f ); uint16_t peak_to_peak = SignalProcessing_CalculatePeakToPeakVoltage(waveform_data, data_length); char text_buffer[50 ]; sprintf (text_buffer, "Freq: %.2f Hz" , frequency); Display_DrawText(10 , 10 , text_buffer, LCD_COLOR_WHITE, LCD_COLOR_BLACK); sprintf (text_buffer, "Vpp: %d mV" , peak_to_peak * 3300 / 4096 ); Display_DrawText(10 , 30 , text_buffer, LCD_COLOR_WHITE, LCD_COLOR_BLACK); } if (BSP_Button_GetState(BUTTON_KEY1) == BUTTON_PRESSED) { current_ui_state = UI_STATE_MENU; } } void UI_MenuState_Update (void ) { Display_ClearScreen(LCD_COLOR_BLUE); Display_DrawText(LCD_WIDTH / 2 - 30 , LCD_HEIGHT / 2 - 10 , "Menu" , LCD_COLOR_WHITE, LCD_COLOR_BLUE); if (BSP_Button_GetState(BUTTON_KEY1) == BUTTON_PRESSED) { current_ui_state = UI_STATE_WAVEFORM_DISPLAY; } } void UI_SettingsState_Update (void ) { Display_ClearScreen(LCD_COLOR_YELLOW); Display_DrawText(LCD_WIDTH / 2 - 40 , LCD_HEIGHT / 2 - 10 , "Settings" , LCD_COLOR_BLACK, LCD_COLOR_YELLOW); if (BSP_Button_GetState(BUTTON_KEY1) == BUTTON_PRESSED) { current_ui_state = UI_STATE_WAVEFORM_DISPLAY; } }
7. main.c (用户代码入口):
1 2 3 4 5 6 7 8 9 10 #include "main.h" #include "system_init.h" #include "app.h" int main (void ) { System_Init(); App_Init(); App_MainLoop(); return 0 ; }
8. system_init.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 #include "system_init.h" #include "stm32fxxx_hal.h" static void SystemClock_Config (void ) { RCC_OscInitTypeDef RCC_OscInitStruct = {0 }; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0 }; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 4 ; RCC_OscInitStruct.PLL.PLLN = 168 ; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 7 ; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } } void System_Init (void ) { HAL_Init(); SystemClock_Config(); } void Error_Handler (void ) { __disable_irq(); while (1 ) { } }
9. 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 #include "app.h" #include "data_acquisition.h" #include "signal_processing.h" #include "display.h" #include "ui.h" bool App_Init (void ) { if (!DataAcquisition_Init()) { return false ; } if (!SignalProcessing_Init()) { return false ; } if (!Display_Init()) { return false ; } if (!UI_Init()) { return false ; } return true ; } void App_MainLoop (void ) { UI_MainLoop(); }
项目中采用的技术和方法 (实践验证):
分层架构与模块化设计: 提高代码可读性、可维护性、可扩展性,降低模块之间的耦合度,方便团队协作开发和后期功能升级。
硬件抽象层 (HAL): 屏蔽硬件差异,提高代码的可移植性,方便更换不同的单片机平台。
驱动层: 封装硬件操作,提供更高级、更易用的接口,简化应用层开发。
环形缓冲区 (Circular Buffer): 高效管理 ADC 采集的数据,避免数据丢失,适用于实时数据流处理。
定时器 (Timer) 触发 ADC 采样 (可选,更高效): 使用定时器定期触发 ADC 采样,实现精确的采样频率控制,提高采样精度和效率 (示例代码中使用的是轮询方式,实际项目中推荐使用定时器触发或 DMA + 中断方式)。
DMA (Direct Memory Access) 数据传输 (可选,更高效): 使用 DMA 将 ADC 采集的数据直接传输到内存缓冲区,无需 CPU 干预,进一步提高数据采集效率,降低 CPU 负载 (示例代码未使用 DMA,实际项目中可以考虑使用 DMA 方式)。
中断 (Interrupt) 处理 (例如按键中断): 使用中断处理按键事件,提高系统实时响应性,避免轮询方式的 CPU 资源浪费。
状态机 (State Machine) 设计 (UI 模块): 使用状态机管理 UI 的不同状态,例如波形显示状态、菜单状态、设置状态等,使 UI 逻辑清晰、易于维护。
基本信号处理算法: 采用过零点检测法计算频率、峰峰值计算等基本信号处理算法,满足示波器的基本测量需求 (可以根据需求扩展更复杂的信号处理算法,例如 FFT 频谱分析,数字滤波器等)。
LCD 图形显示: 使用 LCD 图形库或底层驱动函数,实现波形绘制、文本显示、界面元素显示,提供直观的用户界面。
C 语言编程: 采用 C 语言作为开发语言,C 语言是嵌入式系统开发中最常用的语言,具有高效、灵活、可移植性强等优点。
代码注释和文档: 代码中包含详细的注释,并编写项目文档,提高代码可读性和可维护性,方便学习和交流。
版本控制 (例如 Git): 使用版本控制工具管理代码,方便代码版本管理、团队协作、错误回溯。
调试工具和方法: 使用 JTAG/SWD 调试器进行硬件调试,使用串口打印、LCD 显示等方式进行软件调试,定位和解决问题。
测试验证: 进行单元测试、集成测试、系统测试,验证代码的功能和性能,确保系统的可靠性和稳定性。
维护升级:
模块化设计: 方便后续功能升级和扩展,例如添加新的测量功能、新的显示模式、新的通信接口等,只需在相应的模块中进行修改和添加,而无需修改整个系统。
预留扩展接口: 硬件设计上预留扩展接口 (例如 SPI/I2C/UART 等),方便后续扩展外围模块,例如信号发生器、频谱分析仪等。
固件升级机制 (OTA - Over-The-Air 可选): 设计固件在线升级机制,方便用户远程升级固件,修复 Bug,添加新功能。
总结:
本项目以分层架构和模块化设计为基础,采用经过实践验证的嵌入式系统开发技术和方法,旨在打造一款适合电子爱好者和学生学习的数字示波器基础项目。代码示例涵盖了 HAL 层、驱动层、应用层 (数据采集、信号处理、显示、UI) 的核心模块,并详细解释了代码结构和实现思路。通过学习和实践本项目,您可以掌握嵌入式系统开发的基本流程和关键技术,为后续更深入的学习和项目开发打下坚实的基础。
请注意:
上述代码示例仅为框架和示意,实际项目中需要根据具体的单片机型号、硬件电路、LCD 屏幕、按键等进行详细的硬件配置和驱动代码编写。
学习过程中遇到问题,建议参考单片机手册、HAL 库文档、LCD 驱动手册等技术资料,并进行实际的调试和验证。
希望这份详细的说明和代码示例能够帮助您成功构建您的数字示波器项目!