编程技术分享

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

0%

简介:2023年电赛B题《同轴线缆长度与终端负载检测装置》,基于立创开发板天空星STM32F407VET6的方案

,针对2023年电赛B题《同轴线缆长度与终端负载检测装置》,并基于立创开发板天空星STM32F407VET6的方案,我将为您详细阐述最适合的代码设计架构,并提供具体的C代码实现。这个项目旨在构建一个可靠、高效、可扩展的嵌入式系统平台,实现同轴线缆长度和终端负载的精确检测。
关注微信公众号,提前获取相关推文

系统设计架构概述

针对同轴线缆长度与终端负载检测装置,最适合的代码设计架构是分层架构。分层架构能够将复杂的系统分解为多个独立的、职责明确的模块,从而提高代码的可维护性、可扩展性和可重用性。本项目可以划分为以下几个层次:

  1. **硬件抽象层 (HAL, Hardware Abstraction Layer)**:

    • 目的:封装底层硬件的差异,为上层提供统一的硬件访问接口。
    • 功能:
      • 初始化和配置STM32F407VET6的各种硬件资源,如GPIO、ADC、DAC、TIM、SPI、UART等。
      • 提供操作这些硬件资源的函数接口,例如GPIO的读写、ADC的采样、DAC的输出、定时器的配置、SPI和UART的通信等。
    • 优点:
      • 提高代码的可移植性:当更换硬件平台时,只需要修改HAL层代码,上层应用代码无需改动。
      • 简化上层开发:上层开发者无需关注底层硬件细节,只需调用HAL层提供的函数接口即可。
  2. **服务层 (Service Layer)**:

    • 目的:实现核心的业务逻辑功能,为应用层提供服务。
    • 功能:
      • **信号发生模块 (Signal Generation Module)**:产生用于线缆检测的激励信号,例如阶跃信号、脉冲信号或扫频信号。
      • **数据采集模块 (Data Acquisition Module)**:采集来自ADC的信号数据,进行预处理,例如滤波、放大等。
      • **信号处理模块 (Signal Processing Module)**:对采集到的信号进行分析和处理,提取线缆长度和终端负载信息。
      • **显示驱动模块 (Display Driver Module)**:驱动OLED显示屏,显示测量结果和系统状态。
      • **用户界面管理模块 (UI Management Module)**:处理用户输入,例如按键操作,实现菜单导航和参数配置。
      • **通信模块 (Communication Module)**:实现与其他设备的通信,例如通过UART进行调试信息输出或数据传输。
      • **校准模块 (Calibration Module)**:实现系统校准功能,提高测量精度。
    • 优点:
      • 模块化设计:每个模块负责特定的功能,易于开发、测试和维护。
      • 功能复用:服务层模块可以被多个应用场景复用。
  3. **应用层 (Application Layer)**:

    • 目的:构建用户应用程序,调用服务层提供的接口,实现特定的应用功能。
    • 功能:
      • **主程序流程控制 (Main Application Flow Control)**:控制程序的整体运行流程,例如系统初始化、主循环、任务调度等。
      • **用户交互逻辑 (User Interaction Logic)**:处理用户输入,调用相应的服务层模块,并将结果显示给用户。
      • **错误处理 (Error Handling)**:处理系统运行过程中可能出现的错误,并进行相应的处理,例如错误提示、系统重启等。
    • 优点:
      • 专注于应用逻辑:应用层开发者只需要关注业务逻辑的实现,无需关注底层硬件和服务层细节。
      • 快速开发:基于HAL层和服务层提供的接口,可以快速构建应用程序。

代码实现细节 (C语言)

以下代码实现会非常详细,包括必要的注释和解释。代码将按照分层架构进行组织,并逐步实现各个模块的功能。

(1) 硬件抽象层 (HAL)

hal_gpio.h:

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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include "stm32f4xx.h"

// 定义GPIO端口和引脚
typedef enum {
GPIO_PORT_A,
GPIO_PORT_B,
GPIO_PORT_C,
GPIO_PORT_D,
GPIO_PORT_E,
GPIO_PORT_F,
GPIO_PORT_G,
GPIO_PORT_H
} GPIO_Port;

typedef enum {
GPIO_PIN_0 = GPIO_PIN_0,
GPIO_PIN_1 = GPIO_PIN_1,
GPIO_PIN_2 = GPIO_PIN_2,
GPIO_PIN_3 = GPIO_PIN_3,
GPIO_PIN_4 = GPIO_PIN_4,
GPIO_PIN_5 = GPIO_PIN_5,
GPIO_PIN_6 = GPIO_PIN_6,
GPIO_PIN_7 = GPIO_PIN_7,
GPIO_PIN_8 = GPIO_PIN_8,
GPIO_PIN_9 = GPIO_PIN_9,
GPIO_PIN_10 = GPIO_PIN_10,
GPIO_PIN_11 = GPIO_PIN_11,
GPIO_PIN_12 = GPIO_PIN_12,
GPIO_PIN_13 = GPIO_PIN_13,
GPIO_PIN_14 = GPIO_PIN_14,
GPIO_PIN_15 = GPIO_PIN_15
} GPIO_Pin;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
GPIO_MODE_AF // Alternate Function
} GPIO_Mode;

typedef enum {
GPIO_OUTPUT_TYPE_PP, // Push-Pull
GPIO_OUTPUT_TYPE_OD // Open-Drain
} GPIO_OutputType;

typedef enum {
GPIO_OUTPUT_SPEED_LOW,
GPIO_OUTPUT_SPEED_MEDIUM,
GPIO_OUTPUT_SPEED_FAST,
GPIO_OUTPUT_SPEED_VERY_FAST
} GPIO_OutputSpeed;

typedef enum {
GPIO_PULL_NONE,
GPIO_PULL_UP,
GPIO_PULL_DOWN
} GPIO_Pull;

// GPIO 初始化结构体
typedef struct {
GPIO_Port Port;
GPIO_Pin Pin;
GPIO_Mode Mode;
GPIO_OutputType OutputType;
GPIO_OutputSpeed OutputSpeed;
GPIO_Pull Pull;
uint8_t AlternateFunction; // 用于配置复用功能
} GPIO_InitTypeDef;

// 函数声明
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct);
void HAL_GPIO_WritePin(GPIO_Port Port, GPIO_Pin Pin, uint8_t PinState);
uint8_t HAL_GPIO_ReadPin(GPIO_Port Port, GPIO_Pin Pin);

#endif // HAL_GPIO_H

hal_gpio.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
#include "hal_gpio.h"

// 根据 GPIO_Port 枚举获取 GPIO 基地址
static GPIO_TypeDef* Get_GPIO_Port(GPIO_Port Port) {
GPIO_TypeDef* GPIOx = NULL;
switch (Port) {
case GPIO_PORT_A: GPIOx = GPIOA; break;
case GPIO_PORT_B: GPIOx = GPIOB; break;
case GPIO_PORT_C: GPIOx = GPIOC; break;
case GPIO_PORT_D: GPIOx = GPIOD; break;
case GPIO_PORT_E: GPIOx = GPIOE; break;
case GPIO_PORT_F: GPIOx = GPIOF; break;
case GPIO_PORT_G: GPIOx = GPIOG; break;
case GPIO_PORT_H: GPIOx = GPIOH; break;
default: break; // 错误处理
}
return GPIOx;
}

// 初始化 GPIO
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct) {
GPIO_TypeDef* GPIOx = Get_GPIO_Port(GPIO_InitStruct->Port);
uint32_t pin = GPIO_InitStruct->Pin;

// 使能 GPIO 时钟
if (GPIOx == GPIOA) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
else if (GPIOx == GPIOB) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
else if (GPIOx == GPIOC) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
else if (GPIOx == GPIOD) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
else if (GPIOx == GPIOE) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
else if (GPIOx == GPIOF) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
else if (GPIOx == GPIOG) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
else if (GPIOx == GPIOH) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH, ENABLE);

GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = pin;

// 配置 GPIO 模式
if (GPIO_InitStruct->Mode == GPIO_MODE_INPUT) {
GPIO_Config.GPIO_Mode = GPIO_Mode_IN;
} else if (GPIO_InitStruct->Mode == GPIO_MODE_OUTPUT) {
GPIO_Config.GPIO_Mode = GPIO_Mode_OUT;
if (GPIO_InitStruct->OutputType == GPIO_OUTPUT_TYPE_PP) {
GPIO_Config.GPIO_OType = GPIO_OType_PP;
} else {
GPIO_Config.GPIO_OType = GPIO_OType_OD;
}
if (GPIO_InitStruct->OutputSpeed == GPIO_OUTPUT_SPEED_LOW) {
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
} else if (GPIO_InitStruct->OutputSpeed == GPIO_OUTPUT_SPEED_MEDIUM) {
GPIO_Config.GPIO_Speed = GPIO_Speed_25MHz;
} else if (GPIO_InitStruct->OutputSpeed == GPIO_OUTPUT_SPEED_FAST) {
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
} else {
GPIO_Config.GPIO_Speed = GPIO_Speed_100MHz;
}
} else if (GPIO_InitStruct->Mode == GPIO_MODE_AF) {
GPIO_Config.GPIO_Mode = GPIO_Mode_AF;
GPIO_PinAFConfig(GPIOx, __builtin_ctz(pin), GPIO_InitStruct->AlternateFunction); // 配置复用功能
}

// 配置上下拉
if (GPIO_InitStruct->Pull == GPIO_PULL_NONE) {
GPIO_Config.GPIO_PuPd = GPIO_PuPd_NOPULL;
} else if (GPIO_InitStruct->Pull == GPIO_PULL_UP) {
GPIO_Config.GPIO_PuPd = GPIO_PuPd_UP;
} else {
GPIO_Config.GPIO_PuPd = GPIO_PuPd_DOWN;
}

GPIO_Init(GPIOx, &GPIO_Config);
}

// 写入 GPIO 引脚电平
void HAL_GPIO_WritePin(GPIO_Port Port, GPIO_Pin Pin, uint8_t PinState) {
GPIO_TypeDef* GPIOx = Get_GPIO_Port(Port);
if (PinState == 0) {
GPIO_ResetBits(GPIOx, Pin);
} else {
GPIO_SetBits(GPIOx, Pin);
}
}

// 读取 GPIO 引脚电平
uint8_t HAL_GPIO_ReadPin(GPIO_Port Port, GPIO_Pin Pin) {
GPIO_TypeDef* GPIOx = Get_GPIO_Port(Port);
return GPIO_ReadInputDataBit(GPIOx, Pin);
}

hal_adc.h:

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
#ifndef HAL_ADC_H
#define HAL_ADC_H

#include "stm32f4xx.h"

typedef enum {
ADC_UNIT_1,
ADC_UNIT_2,
ADC_UNIT_3
} ADC_Unit;

typedef enum {
ADC_CHANNEL_0 = ADC_Channel_0,
ADC_CHANNEL_1 = ADC_Channel_1,
ADC_CHANNEL_2 = ADC_Channel_2,
ADC_CHANNEL_3 = ADC_Channel_3,
ADC_CHANNEL_4 = ADC_Channel_4,
ADC_CHANNEL_5 = ADC_Channel_5,
ADC_CHANNEL_6 = ADC_Channel_6,
ADC_CHANNEL_7 = ADC_Channel_7,
ADC_CHANNEL_8 = ADC_Channel_8,
ADC_CHANNEL_9 = ADC_Channel_9,
ADC_CHANNEL_10 = ADC_Channel_10,
ADC_CHANNEL_11 = ADC_Channel_11,
ADC_CHANNEL_12 = ADC_Channel_12,
ADC_CHANNEL_13 = ADC_Channel_13,
ADC_CHANNEL_14 = ADC_Channel_14,
ADC_CHANNEL_15 = ADC_Channel_15,
ADC_CHANNEL_16 = ADC_Channel_16, // 温度传感器通道
ADC_CHANNEL_17 = ADC_Channel_17, // VREFINT通道
ADC_CHANNEL_18 = ADC_Channel_18 // VBAT通道
} ADC_Channel;

typedef enum {
ADC_RESOLUTION_12B = ADC_Resolution_12b,
ADC_RESOLUTION_10B = ADC_Resolution_10b,
ADC_RESOLUTION_8B = ADC_Resolution_8b,
ADC_RESOLUTION_6B = ADC_Resolution_6b
} ADC_Resolution;

typedef enum {
ADC_SAMPLE_CYCLE_3 = ADC_SampleTime_3Cycles,
ADC_SAMPLE_CYCLE_15 = ADC_SampleTime_15Cycles,
ADC_SAMPLE_CYCLE_28 = ADC_SampleTime_28Cycles,
ADC_SAMPLE_CYCLE_56 = ADC_SampleTime_56Cycles,
ADC_SAMPLE_CYCLE_84 = ADC_SampleTime_84Cycles,
ADC_SAMPLE_CYCLE_112 = ADC_SampleTime_112Cycles,
ADC_SAMPLE_CYCLE_144 = ADC_SampleTime_144Cycles,
ADC_SAMPLE_CYCLE_480 = ADC_SampleTime_480Cycles
} ADC_SampleCycle;

typedef struct {
ADC_Unit Unit;
ADC_Channel Channel;
ADC_Resolution Resolution;
ADC_SampleCycle SampleCycle;
} ADC_InitTypeDef;

void HAL_ADC_Init(ADC_InitTypeDef *ADC_InitStruct);
void HAL_ADC_Start(ADC_Unit Unit);
uint16_t HAL_ADC_GetValue(ADC_Unit Unit);
void HAL_ADC_Stop(ADC_Unit Unit);

#endif // 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
#include "hal_adc.h"

static ADC_TypeDef* Get_ADC_Unit(ADC_Unit Unit) {
ADC_TypeDef* ADCx = NULL;
switch (Unit) {
case ADC_UNIT_1: ADCx = ADC1; break;
case ADC_UNIT_2: ADCx = ADC2; break;
case ADC_UNIT_3: ADCx = ADC3; break;
default: break; // 错误处理
}
return ADCx;
}

static uint32_t Get_ADC_Channel(ADC_Channel Channel) {
return (uint32_t)Channel;
}

// 初始化 ADC
void HAL_ADC_Init(ADC_InitTypeDef *ADC_InitStruct) {
ADC_TypeDef* ADCx = Get_ADC_Unit(ADC_InitStruct->Unit);
uint32_t channel = Get_ADC_Channel(ADC_InitStruct->Channel);

// 使能 ADC 时钟
if (ADCx == ADC1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
else if (ADCx == ADC2) RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
else if (ADCx == ADC3) RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);

ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; // 根据需求调整分频
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);

ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_InitStruct->Resolution;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // 可配置外部触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADCx, &ADC_InitStructure);

ADC_RegularChannelConfig(ADCx, channel, 1, ADC_InitStruct->SampleCycle);

ADC_Cmd(ADCx, ENABLE);
}

// 启动 ADC 转换
void HAL_ADC_Start(ADC_Unit Unit) {
ADC_TypeDef* ADCx = Get_ADC_Unit(Unit);
ADC_SoftwareStartConv(ADCx);
}

// 获取 ADC 转换值
uint16_t HAL_ADC_GetValue(ADC_Unit Unit) {
ADC_TypeDef* ADCx = Get_ADC_Unit(Unit);
while (ADC_GetSoftwareStartConvStatus(ADCx)); // 等待转换完成
return ADC_GetConversionValue(ADCx);
}

// 停止 ADC
void HAL_ADC_Stop(ADC_Unit Unit) {
ADC_TypeDef* ADCx = Get_ADC_Unit(Unit);
ADC_Cmd(ADCx, DISABLE);
}

**(后续 HAL 层模块: hal_dac.h/c, hal_timer.h/c, hal_uart.h/c, hal_spi.h/c, hal_oled.h/c 将按照类似的方式进行定义和实现,这里为了篇幅省略具体代码,
(2) 服务层 (Service Layer)

service_signal_generation.h:

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
#ifndef SERVICE_SIGNAL_GENERATION_H
#define SERVICE_SIGNAL_GENERATION_H

#include "hal_dac.h"
#include "hal_gpio.h"
#include "hal_timer.h"

typedef enum {
SIGNAL_TYPE_STEP,
SIGNAL_TYPE_PULSE,
SIGNAL_TYPE_SWEEP_FREQUENCY // 后期扩展
} SignalType;

typedef struct {
SignalType Type;
uint16_t Amplitude; // 信号幅度
uint32_t PulseWidth; // 脉冲宽度 (us)
uint32_t FrequencyStart; // 扫频起始频率 (Hz)
uint32_t FrequencyEnd; // 扫频结束频率 (Hz)
uint32_t SweepDuration; // 扫频持续时间 (ms)
} SignalConfig;

void SignalGeneration_Init(void);
void SignalGeneration_GenerateSignal(SignalConfig *config);
void SignalGeneration_StopSignal(void);

#endif // SERVICE_SIGNAL_GENERATION_H

service_signal_generation.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
#include "service_signal_generation.h"

#define SIGNAL_GEN_DAC_UNIT DAC_UNIT_1
#define SIGNAL_GEN_TRIGGER_GPIO_PORT GPIO_PORT_D
#define SIGNAL_GEN_TRIGGER_GPIO_PIN GPIO_PIN_12
#define SIGNAL_GEN_TRIGGER_TIMER TIM6 // 例如使用 TIM6 作为触发定时器

static GPIO_InitTypeDef trigger_gpio_config;
static DAC_InitTypeDef dac_config;
static TIM_InitTypeDef timer_config;

void SignalGeneration_Init(void) {
// 初始化 DAC 用于信号输出
dac_config.Unit = SIGNAL_GEN_DAC_UNIT;
dac_config.Channel = DAC_CHANNEL_1; // 选择 DAC 通道
HAL_DAC_Init(&dac_config);

// 初始化 GPIO 用于触发信号同步 (可选,如果使用外部触发)
trigger_gpio_config.Port = SIGNAL_GEN_TRIGGER_GPIO_PORT;
trigger_gpio_config.Pin = SIGNAL_GEN_TRIGGER_GPIO_PIN;
trigger_gpio_config.Mode = GPIO_MODE_OUTPUT;
trigger_gpio_config.OutputType = GPIO_OUTPUT_TYPE_PP;
trigger_gpio_config.OutputSpeed = GPIO_OUTPUT_SPEED_VERY_FAST;
trigger_gpio_config.Pull = GPIO_PULL_NONE;
HAL_GPIO_Init(&trigger_gpio_config);

// 初始化定时器用于精确控制脉冲宽度和扫频 (可选,根据信号类型和精度需求)
// ... (定时器初始化代码,例如配置 TIM6) ...
}

void SignalGeneration_GenerateSignal(SignalConfig *config) {
if (config->Type == SIGNAL_TYPE_STEP) {
// 生成阶跃信号:DAC 输出一个直流电压
HAL_DAC_SetValue(SIGNAL_GEN_DAC_UNIT, DAC_CHANNEL_1, config->Amplitude);
} else if (config->Type == SIGNAL_TYPE_PULSE) {
// 生成脉冲信号:控制 DAC 输出高电平一段时间,然后恢复低电平
HAL_DAC_SetValue(SIGNAL_GEN_DAC_UNIT, DAC_CHANNEL_1, config->Amplitude);
// 延时 config->PulseWidth 微秒
// 使用 HAL_Delay 或更精确的定时器延时函数
// 例如:HAL_TIM_Delay_us(SIGNAL_GEN_TRIGGER_TIMER, config->PulseWidth);
// 实际延时函数需要根据 HAL_TIMER 模块实现
// 这里为了简化,假设有一个 HAL_Delay_us 函数
HAL_Delay_us(config->PulseWidth);
HAL_DAC_SetValue(SIGNAL_GEN_DAC_UNIT, DAC_CHANNEL_1, 0); // 恢复低电平
} else if (config->Type == SIGNAL_TYPE_SWEEP_FREQUENCY) {
// 生成扫频信号 (后期扩展,需要更复杂的实现,例如 DDS 或 PWM + 滤波器)
// ... (扫频信号生成代码) ...
}
}

void SignalGeneration_StopSignal(void) {
HAL_DAC_SetValue(SIGNAL_GEN_DAC_UNIT, DAC_CHANNEL_1, 0); // 停止信号,DAC 输出 0V
}

service_data_acquisition.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef SERVICE_DATA_ACQUISITION_H
#define SERVICE_DATA_ACQUISITION_H

#include "hal_adc.h"

#define ADC_DATA_BUFFER_SIZE 1024 // 定义 ADC 数据缓冲区大小

typedef struct {
uint16_t data[ADC_DATA_BUFFER_SIZE]; // ADC 数据缓冲区
uint32_t count; // 实际采集到的数据点数量
} ADC_DataBuffer;

void DataAcquisition_Init(void);
void DataAcquisition_StartSampling(void);
void DataAcquisition_StopSampling(void);
ADC_DataBuffer* DataAcquisition_GetDataBuffer(void);

#endif // SERVICE_DATA_ACQUISITION_H

service_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
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
#include "service_data_acquisition.h"

#define DATA_ACQ_ADC_UNIT ADC_UNIT_1
#define DATA_ACQ_ADC_CHANNEL ADC_CHANNEL_0
#define DATA_ACQ_ADC_RESOLUTION ADC_RESOLUTION_12B
#define DATA_ACQ_ADC_SAMPLE_CYCLE ADC_SAMPLE_CYCLE_84

static ADC_InitTypeDef adc_config;
static ADC_DataBuffer adc_data_buffer;
static volatile uint32_t adc_data_index = 0; // ADC 数据索引,用于环形缓冲区

void DataAcquisition_Init(void) {
// 初始化 ADC 用于数据采集
adc_config.Unit = DATA_ACQ_ADC_UNIT;
adc_config.Channel = DATA_ACQ_ADC_CHANNEL;
adc_config.Resolution = DATA_ACQ_ADC_RESOLUTION;
adc_config.SampleCycle = DATA_ACQ_ADC_SAMPLE_CYCLE;
HAL_ADC_Init(&adc_config);

// 初始化 ADC 数据缓冲区
adc_data_buffer.count = 0;
adc_data_index = 0;
memset(adc_data_buffer.data, 0, sizeof(adc_data_buffer.data));

// 配置 ADC 中断 (可选,如果使用中断驱动的 ADC 采集)
// ... (ADC 中断配置代码,例如 NVIC 配置,ADC_ITConfig) ...
// 在 ADC 中断处理函数中,将 ADC 数据存入 adc_data_buffer
}

void DataAcquisition_StartSampling(void) {
adc_data_index = 0; // 重置数据索引
adc_data_buffer.count = 0;
HAL_ADC_Start(DATA_ACQ_ADC_UNIT);
// 如果使用中断,则启动 ADC 并使能中断
// 如果不使用中断,则需要轮询或 DMA 方式采集数据
// 为了简化,这里假设使用轮询方式,单次采集
}

void DataAcquisition_StopSampling(void) {
HAL_ADC_Stop(DATA_ACQ_ADC_UNIT);
// 轮询方式采集数据:在 StartSampling 后,循环调用 HAL_ADC_GetValue 并存入 buffer
for (adc_data_index = 0; adc_data_index < ADC_DATA_BUFFER_SIZE; adc_data_index++) {
adc_data_buffer.data[adc_data_index] = HAL_ADC_GetValue(DATA_ACQ_ADC_UNIT);
adc_data_buffer.count++;
// 可以添加延时,控制采样率,或者使用定时器触发 ADC 采样
// HAL_Delay_us(采样间隔);
}
}

ADC_DataBuffer* DataAcquisition_GetDataBuffer(void) {
return &adc_data_buffer;
}

**(后续服务层模块: service_signal_processing.h/c, service_display_driver.h/c, service_ui_management.h/c, service_communication.h/c, service_calibration.h/c 将按照类似的方式进行定义和实现,提供线缆长度和阻抗计算、OLED 显示驱动、用户界面管理、UART 通信和校准功能。为了篇幅省略具体代码,
(3) 应用层 (Application Layer)

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
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include "stm32f4xx.h"
#include "hal_gpio.h"
#include "hal_adc.h"
#include "hal_dac.h"
#include "hal_timer.h"
#include "hal_uart.h" // 假设 HAL 层包含 UART 模块
#include "hal_oled.h" // 假设 HAL 层包含 OLED 模块
#include "service_signal_generation.h"
#include "service_data_acquisition.h"
#include "service_signal_processing.h" // 假设服务层包含信号处理模块
#include "service_display_driver.h" // 假设服务层包含显示驱动模块
#include "service_ui_management.h" // 假设服务层包含 UI 管理模块
#include "service_communication.h" // 假设服务层包含通信模块
#include "service_calibration.h" // 假设服务层包含校准模块

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

// 定义按键 GPIO (假设使用两个按键)
#define KEY1_PORT GPIO_PORT_A
#define KEY1_PIN GPIO_PIN_0
#define KEY2_PORT GPIO_PORT_A
#define KEY2_PIN GPIO_PIN_1

// 定义 LED GPIO (用于指示系统状态)
#define LED_PORT GPIO_PORT_D
#define LED_PIN GPIO_PIN_13

// 系统状态枚举
typedef enum {
SYSTEM_STATE_IDLE,
SYSTEM_STATE_MEASURING_LENGTH,
SYSTEM_STATE_MEASURING_IMPEDANCE,
SYSTEM_STATE_CALIBRATING
} SystemState;

SystemState current_state = SYSTEM_STATE_IDLE;

void System_Init(void);
void System_RunTask(void);
void Key_Process(void);

int main(void) {
System_Init(); // 系统初始化
HAL_OLED_WriteString(0, 0, "System Initialized"); // OLED 显示初始化信息
HAL_Delay(2000);
HAL_OLED_Clear();

while (1) {
System_RunTask(); // 系统主循环任务
}
}

void System_Init(void) {
// HAL 初始化
GPIO_InitTypeDef key_gpio_config;
key_gpio_config.Port = KEY1_PORT;
key_gpio_config.Pin = KEY1_PIN;
key_gpio_config.Mode = GPIO_MODE_INPUT;
key_gpio_config.Pull = GPIO_PULL_UP; // 上拉输入
HAL_GPIO_Init(&key_gpio_config);

key_gpio_config.Port = KEY2_PORT;
key_gpio_config.Pin = KEY2_PIN;
HAL_GPIO_Init(&key_gpio_config);

GPIO_InitTypeDef led_gpio_config;
led_gpio_config.Port = LED_PORT;
led_gpio_config.Pin = LED_PIN;
led_gpio_config.Mode = GPIO_MODE_OUTPUT;
led_gpio_config.OutputType = GPIO_OUTPUT_TYPE_PP;
led_gpio_config.OutputSpeed = GPIO_OUTPUT_SPEED_LOW;
led_gpio_config.Pull = GPIO_PULL_NONE;
HAL_GPIO_Init(&led_gpio_config);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, 0); // LED 初始状态熄灭

HAL_UART_Init(); // 初始化 UART 调试串口 (假设 HAL_UART_Init 已经实现)
HAL_OLED_Init(); // 初始化 OLED 显示屏 (假设 HAL_OLED_Init 已经实现)

// 服务层初始化
SignalGeneration_Init();
DataAcquisition_Init();
SignalProcessing_Init(); // 假设服务层包含 SignalProcessing_Init 函数
DisplayDriver_Init(); // 假设服务层包含 DisplayDriver_Init 函数
UIManagement_Init(); // 假设服务层包含 UIManagement_Init 函数
Communication_Init(); // 假设服务层包含 Communication_Init 函数
Calibration_Init(); // 假设服务层包含 Calibration_Init 函数

current_state = SYSTEM_STATE_IDLE; // 初始状态为空闲
}

void System_RunTask(void) {
Key_Process(); // 处理按键输入

switch (current_state) {
case SYSTEM_STATE_IDLE:
// 空闲状态,显示主菜单或待机界面
HAL_OLED_WriteString(0, 0, "Main Menu:");
HAL_OLED_WriteString(1, 0, "1. Length Measure");
HAL_OLED_WriteString(2, 0, "2. Impedance Measure");
HAL_OLED_WriteString(3, 0, "3. Calibration");
HAL_GPIO_WritePin(LED_PORT, LED_PIN, 0); // LED 熄灭
break;

case SYSTEM_STATE_MEASURING_LENGTH:
// 长度测量状态
HAL_OLED_Clear();
HAL_OLED_WriteString(0, 0, "Measuring Length...");
HAL_GPIO_WritePin(LED_PORT, LED_PIN, 1); // LED 点亮指示测量中

// 配置信号发生器,生成阶跃或脉冲信号
SignalConfig signal_config;
signal_config.Type = SIGNAL_TYPE_STEP; // 或 SIGNAL_TYPE_PULSE
signal_config.Amplitude = 2048; // DAC 输出幅度 (中间值,根据实际 DAC 范围调整)
signal_config.PulseWidth = 100; // 脉冲宽度 100us (如果使用脉冲信号)
SignalGeneration_GenerateSignal(&signal_config);

HAL_Delay(10); // 信号稳定延时

// 启动数据采集
DataAcquisition_StartSampling();
HAL_Delay(100); // 采集延时,根据采样率和缓冲区大小调整
DataAcquisition_StopSampling();

SignalGeneration_StopSignal(); // 停止信号发生

// 获取 ADC 数据缓冲区
ADC_DataBuffer *adc_buffer = DataAcquisition_GetDataBuffer();

// 信号处理,计算线缆长度 (假设 SignalProcessing_CalculateLength 函数已实现)
float cable_length = SignalProcessing_CalculateLength(adc_buffer);

// 显示测量结果
HAL_OLED_Clear();
char length_str[30];
sprintf(length_str, "Cable Length: %.2f m", cable_length);
HAL_OLED_WriteString(0, 0, length_str);
HAL_OLED_WriteString(7, 0, "Press KEY1 to return");

current_state = SYSTEM_STATE_IDLE; // 测量完成后返回空闲状态
break;

case SYSTEM_STATE_MEASURING_IMPEDANCE:
// 阻抗测量状态 (类似长度测量,但信号处理算法不同)
HAL_OLED_Clear();
HAL_OLED_WriteString(0, 0, "Measuring Impedance...");
HAL_GPIO_WritePin(LED_PORT, LED_PIN, 1);

// ... (配置信号发生器,数据采集,信号处理流程,与长度测量类似,但使用不同的信号和算法) ...
// 例如:可以使用扫频信号或分析反射信号的幅度来估计阻抗

// 假设 SignalProcessing_CalculateImpedance 函数已实现
float impedance = SignalProcessing_CalculateImpedance(adc_buffer);

// 显示阻抗测量结果
HAL_OLED_Clear();
char impedance_str[30];
sprintf(impedance_str, "Impedance: %.2f Ohm", impedance);
HAL_OLED_WriteString(0, 0, impedance_str);
HAL_OLED_WriteString(7, 0, "Press KEY1 to return");

current_state = SYSTEM_STATE_IDLE;
break;

case SYSTEM_STATE_CALIBRATING:
// 校准状态 (实现系统校准功能,例如零点校准,量程校准等)
HAL_OLED_Clear();
HAL_OLED_WriteString(0, 0, "Calibration Mode...");
HAL_GPIO_WritePin(LED_PORT, LED_PIN, 1);

// ... (校准流程,用户交互,参数调整等) ...

HAL_OLED_WriteString(7, 0, "Calibration Done, Press KEY1");
current_state = SYSTEM_STATE_IDLE;
break;

default:
current_state = SYSTEM_STATE_IDLE; // 异常状态,返回空闲
break;
}
}

void Key_Process(void) {
static uint8_t key1_pressed_last = 1; // 按键上次状态,1 表示未按下
static uint8_t key2_pressed_last = 1;

uint8_t key1_pressed = !HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN); // 低电平触发
uint8_t key2_pressed = !HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN);

// KEY1 处理 (菜单选择/返回)
if (key1_pressed && !key1_pressed_last) {
HAL_Delay(50); // 消抖延时
if (key1_pressed) { // 再次确认按下
if (current_state == SYSTEM_STATE_IDLE) {
// 空闲状态,选择菜单项 1 (长度测量)
current_state = SYSTEM_STATE_MEASURING_LENGTH;
} else {
// 非空闲状态,返回空闲状态
current_state = SYSTEM_STATE_IDLE;
}
}
}
key1_pressed_last = key1_pressed;

// KEY2 处理 (菜单选择/确认)
if (key2_pressed && !key2_pressed_last) {
HAL_Delay(50); // 消抖延时
if (key2_pressed) { // 再次确认按下
if (current_state == SYSTEM_STATE_IDLE) {
// 空闲状态,选择菜单项 2 (阻抗测量)
current_state = SYSTEM_STATE_MEASURING_IMPEDANCE;
// 或者选择菜单项 3 (校准),根据实际菜单设计
// current_state = SYSTEM_STATE_CALIBRATING;
}
}
}
key2_pressed_last = key2_pressed;
}

// 假设的 HAL_Delay_us 函数 (如果 HAL 层没有提供微秒级延时,需要自行实现,例如使用 SysTick 定时器)
void HAL_Delay_us(uint32_t us) {
volatile uint32_t ticks = us * (SystemCoreClock / 1000000);
volatile uint32_t start = SysTick->VAL;
while ((start - SysTick->VAL) < ticks); // 粗略延时,实际应用中需要更精确的定时器
}

(后续需要完成的代码部分)

  1. 完善 HAL 层:
    • 实现 hal_dac.c/h, hal_timer.c/h, hal_uart.c/h, hal_spi.c/h, hal_oled.c/h 模块,提供 DAC 输出、定时器控制、UART 通信、SPI 通信 (如果需要)、OLED 显示驱动等功能。
  2. 完善服务层:
    • 实现 service_signal_processing.c/h, service_display_driver.c/h, service_ui_management.c/h, service_communication.c/h, service_calibration.c/h 模块。
      • 信号处理模块 (service_signal_processing): 实现线缆长度和阻抗计算的算法,例如 TDR (时域反射法) 分析、频域分析等。需要根据具体的硬件电路和信号类型选择合适的算法。
      • 显示驱动模块 (service_display_driver): 封装 OLED 显示屏的驱动,提供显示字符、数字、图形等功能。
      • 用户界面管理模块 (service_ui_management): 实现菜单导航、参数配置等用户交互逻辑。
      • 通信模块 (service_communication): 实现 UART 调试信息输出、数据传输等功能。
      • 校准模块 (service_calibration): 实现系统校准功能,例如零点校准、量程校准等,提高测量精度。
  3. 完善应用层 (main.c):
    • 实现更完善的菜单系统,支持更多的功能选项。
    • 实现更精细的状态机,处理各种系统状态和事件。
    • 添加错误处理机制,提高系统的鲁棒性。
    • 优化代码结构和性能。
  4. 信号处理算法实现 (重点)
    • 线缆长度测量: 可以使用 TDR 方法,发送阶跃或脉冲信号,通过测量反射信号的时间延迟来计算线缆长度。需要考虑信号的传播速度、线缆的特性阻抗等因素。
    • 终端负载检测: 可以分析反射信号的幅度、形状、频域特性等,来估计终端负载的阻抗值。可以使用频域反射法 (FDR) 或时域反射法结合频域分析。
  5. 测试与验证:
    • 编写单元测试代码,测试各个模块的功能。
    • 进行系统集成测试,验证整个系统的功能和性能。
    • 使用实际的同轴线缆和负载进行测试,评估测量精度和稳定性。
  6. 维护与升级:
    • 代码注释清晰,方便后续维护和升级。
    • 采用模块化设计,方便添加新功能或修改现有功能。
    • 预留升级接口,例如通过 UART 或 USB 进行固件升级。
  • 详细注释: 对每一行代码、每一个函数、每一个模块都进行详细的注释,解释代码的功能和实现原理。
  • 模块化设计: 将系统划分为更多的模块,例如可以将信号处理模块细分为 TDR 模块、FDR 模块、阻抗计算模块、长度计算模块等,每个模块的代码量都会增加。
  • 功能扩展: 在基本功能的基础上,扩展更多的功能,例如:
    • 支持多种信号类型 (阶跃、脉冲、扫频)。
    • 支持多种测量模式 (单次测量、连续测量、平均值测量)。
    • 添加数据存储和导出功能 (例如通过 SD 卡或 UART)。
    • 添加图形化界面 (如果使用更大的显示屏)。
    • 实现更复杂的校准算法。
    • 添加错误诊断和报警功能。
  • 代码优化: 对代码进行性能优化,例如使用查表法、位操作、DMA 等技术,虽然优化代码本身可能不会增加很多行数,但是相关的测试代码、性能分析代码可以增加代码量。
  • 详细的文档和说明: 除了代码注释,还可以编写详细的设计文档、用户手册、测试报告等,这些文档也可以算作项目的一部分。

总结

以上代码框架和实现思路为 2023 年电赛 B 题《同轴线缆长度与终端负载检测装置》提供了一个完整的设计方案。通过分层架构的设计,可以构建一个结构清晰、易于维护和扩展的嵌入式系统。具体的代码实现需要根据硬件电路设计、信号处理算法选择、以及功能需求进行详细的编写和调试。
请注意,以上代码只是一个框架示例,实际的项目开发需要根据具体情况进行调整和完善。为了完成一个高质量的电赛作品,还需要进行充分的理论分析、硬件设计、软件编码、测试验证和文档撰写。

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