编程技术分享

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

0%

简介:安信可科技VC系列离线语音模组烧录座

好的,作为一名高级嵌入式软件开发工程师,我将基于你提供的安信可科技VC系列离线语音模组烧录座图片,详细阐述一个适用于此类嵌入式产品的可靠、高效、可扩展的系统平台代码设计架构,并提供具体的C代码实现,确保内容超过3000行,并涵盖实践验证的技术和方法。
关注微信公众号,提前获取相关推文

项目背景:安信可科技VC系列离线语音模组烧录座

从图片和产品名称来看,这是一个用于烧录安信可VC系列离线语音模组的设备。其核心功能是:

  1. 连接PC: 通过USB或串口与上位机(PC)通信。
  2. 烧录固件: 将从PC接收到的固件数据写入VC系列模组的Flash存储器中。
  3. 控制与指示: 具备用户交互界面,例如按键、指示灯,用于控制烧录过程和显示状态。
  4. 电源管理: 为VC模组提供稳定的工作电源。
  5. 保护机制: 具备必要的保护电路,防止误操作或故障导致硬件损坏。

嵌入式系统开发流程

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

  1. 需求分析: 明确产品的功能需求、性能指标、可靠性要求、用户体验等。
  2. 系统设计: 包括硬件设计和软件设计。硬件设计选择合适的微控制器、存储器、外围器件等;软件设计确定系统架构、模块划分、接口定义等。
  3. 软件开发: 根据软件设计,编写、调试和测试代码。
  4. 硬件开发与集成: 制作硬件原型,将软件与硬件集成,进行系统联调。
  5. 测试验证: 对整个系统进行功能测试、性能测试、可靠性测试、兼容性测试等,确保满足需求。
  6. 维护升级: 发布产品,并根据用户反馈和市场需求,进行软件升级和硬件维护。

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

为了构建可靠、高效、可扩展的系统平台,我推荐采用分层模块化架构。这种架构将系统划分为若干个层次和模块,每个层次和模块负责特定的功能,层与层之间、模块与模块之间通过定义清晰的接口进行通信。

分层架构的优势:

  • 高内聚低耦合: 每个模块内部功能高度相关,模块之间依赖性低,易于维护和修改。
  • 代码复用性高: 通用模块可以在不同的项目或系统中复用。
  • 易于扩展: 新增功能或模块时,只需关注接口,对现有模块影响小。
  • 便于测试和调试: 可以逐层、逐模块进行测试和调试,降低复杂性。
  • 提高可读性: 清晰的模块划分使代码结构更清晰,易于理解。

针对VC系列离线语音模组烧录座,我设计的系统架构如下:

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
+-----------------------+
| 应用层 (Application Layer) | (烧录逻辑、命令处理、用户界面)
+-----------------------+
|
| 应用层接口 (Application Layer Interface)
V
+-----------------------+
| 命令处理层 (Command Processing Layer) | (命令解析、参数提取、命令分发)
+-----------------------+
|
| 命令处理层接口 (Command Processing Layer Interface)
V
+-----------------------+
| 硬件抽象层 (HAL - Hardware Abstraction Layer) | (GPIO、UART、SPI/Flash、Timer、USB)
+-----------------------+
|
| 硬件驱动接口 (Hardware Driver Interface)
V
+-----------------------+
| 板级支持包 (BSP - Board Support Package) | (芯片初始化、时钟配置、外设初始化)
+-----------------------+
|
| 底层硬件
V
+-----------------------+
| 底层硬件 (Microcontroller, Flash, Peripherals) |
+-----------------------+

各层功能详细说明:

  1. 板级支持包 (BSP - Board Support Package):

    • 功能: 负责芯片级的初始化和配置,包括:
      • 系统时钟初始化: 配置主频、外设时钟等。
      • 中断向量表配置: 设置中断入口地址。
      • 外设时钟使能: 使能GPIO、UART、SPI、Flash等外设的时钟。
      • 低功耗配置: 如果需要,进行低功耗模式配置。
    • 目标: 为上层提供统一的硬件操作接口,屏蔽底层硬件差异。
    • 特点: 高度依赖于具体的硬件平台(微控制器型号、开发板)。
  2. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 功能: 封装底层硬件驱动,提供统一的API接口,例如:
      • GPIO驱动: 控制GPIO的输入输出、电平设置。
      • UART驱动: 串口数据的发送和接收。
      • SPI驱动: SPI总线的数据传输,用于Flash通信。
      • Flash驱动: Flash的擦除、写入、读取操作。
      • Timer驱动: 定时器配置、定时中断处理。
      • USB驱动: USB通信的初始化、数据传输。
    • 目标: 实现硬件无关性,方便代码移植和维护。
    • 特点: 接口统一,但底层实现会调用BSP层提供的硬件初始化和配置函数。
  3. 命令处理层 (Command Processing Layer):

    • 功能: 负责接收上位机发送的命令,解析命令参数,并根据命令类型调用相应的应用层函数。
    • 目标: 解耦通信协议和应用逻辑,提高系统的灵活性和可扩展性。
    • 特点: 定义命令格式、命令ID、参数格式,实现命令解析和分发机制。
  4. 应用层 (Application Layer):

    • 功能: 实现产品的核心业务逻辑,例如:
      • 固件接收: 接收上位机发送的固件数据。
      • Flash烧录: 调用HAL层Flash驱动,将固件数据写入Flash。
      • 状态指示: 通过GPIO控制LED指示灯,显示烧录状态。
      • 用户交互: 处理按键输入,响应用户操作。
      • 错误处理: 处理烧录过程中的错误,并向上位机或用户报告。
    • 目标: 完成产品的功能需求,提供用户友好的操作界面。
    • 特点: 高度依赖于产品的功能需求,是系统软件的核心部分。

具体C代码实现 (超过3000行,包含详细注释)

为了展示代码量和细节,我将提供每个层次和模块的示例代码,并进行详细的注释说明。由于篇幅限制,这里不可能完全包含3000行代码,但我会尽可能详细地展示核心模块和关键功能的实现,并提供代码结构和框架,以便你理解整个系统的构建方式。

1. 板级支持包 (BSP - Board Support Package) - bsp.cbsp.h

假设我们使用的微控制器是基于ARM Cortex-M系列,例如STM32,并使用HAL库进行开发。

bsp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef __BSP_H__
#define __BSP_H__

#include <stdint.h>
#include <stdio.h> // For printf if needed for debugging

// 系统时钟配置宏定义 (根据具体芯片和时钟配置修改)
#define SYS_CLK_FREQ_HZ 72000000UL // 72MHz 系统时钟频率

// 函数声明
void bsp_init(void); // BSP初始化函数
void SystemClock_Config(void); // 系统时钟配置函数 (HAL库风格)
void Error_Handler(void); // 错误处理函数

#endif // __BSP_H__

bsp.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
#include "bsp.h"
#include "stm32f1xx_hal.h" // 假设使用STM32F1系列HAL库,根据实际芯片修改

// 时钟句柄 (HAL库风格)
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct;

/**
* @brief BSP 初始化函数
* @param None
* @retval None
*/
void bsp_init(void)
{
// HAL 库初始化
HAL_Init();

// 系统时钟配置
SystemClock_Config();

// 可以添加其他板级初始化,例如电源管理芯片初始化,外部晶振初始化等
// ...

printf("BSP Initialization completed.\r\n"); // 调试信息
}

/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 使用外部高速晶振
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 使能HSE
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // HSE * 9 = 72MHz
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

/** Initializes the CPU, AHB and APB busses clocks
*/
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_DIV2; // APB1 时钟 HCLK/2
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 时钟 HCLK
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) // Flash 延迟,根据频率调整
{
Error_Handler();
}

/**Configure the Systick interrupt time
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); // SysTick 1ms 中断

/**Configure the Systick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
printf("Error occurred!\r\n");
while(1)
{
// 可以添加错误指示灯闪烁,或者其他错误处理逻辑
}
/* USER CODE END Error_Handler_Debug */
}

2. 硬件抽象层 (HAL - Hardware Abstraction Layer)

为了简化代码展示,我只列出 GPIO 和 UART 的 HAL 代码示例,其他外设 (SPI/Flash, Timer, USB) 的 HAL 代码结构类似。

GPIO HAL - hal_gpio.hhal_gpio.c

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

#include <stdint.h>
#include "stm32f1xx_hal.h" // 假设使用STM32F1 HAL库

// GPIO 端口和引脚定义 (根据实际硬件连接修改)
#define LED_GREEN_PORT GPIOB
#define LED_GREEN_PIN GPIO_PIN_5
#define LED_RED_PORT GPIOB
#define LED_RED_PIN GPIO_PIN_6
#define BUTTON_USER_PORT GPIOA
#define BUTTON_USER_PIN GPIO_PIN_0

// GPIO 初始化结构体
typedef struct {
GPIO_TypeDef* Port; // GPIO 端口 (例如 GPIOA, GPIOB, ...)
uint16_t Pin; // GPIO 引脚 (例如 GPIO_PIN_0, GPIO_PIN_1, ...)
GPIO_InitTypeDef Init; // HAL库 GPIO 初始化结构体
} hal_gpio_t;

// 函数声明
void hal_gpio_init(hal_gpio_t *gpio); // GPIO 初始化
void hal_gpio_set_output(hal_gpio_t *gpio, uint8_t state); // 设置 GPIO 输出电平
uint8_t hal_gpio_get_input(hal_gpio_t *gpio); // 读取 GPIO 输入电平

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

/**
* @brief GPIO 初始化
* @param gpio: GPIO 结构体指针
* @retval None
*/
void hal_gpio_init(hal_gpio_t *gpio)
{
// 使能 GPIO 时钟 (根据端口选择时钟,例如 GPIOA 使能 RCC_APB2Periph_GPIOA)
if (gpio->Port == GPIOA) {
__HAL_RCC_GPIOA_CLK_ENABLE();
} else if (gpio->Port == GPIOB) {
__HAL_RCC_GPIOB_CLK_ENABLE();
} // ... 其他端口时钟使能

// 初始化 GPIO
HAL_GPIO_Init(gpio->Port, &gpio->Init);
}

/**
* @brief 设置 GPIO 输出电平
* @param gpio: GPIO 结构体指针
* @param state: 输出状态 (0: 低电平, 1: 高电平)
* @retval None
*/
void hal_gpio_set_output(hal_gpio_t *gpio, uint8_t state)
{
HAL_GPIO_WritePin(gpio->Port, gpio->Pin, (GPIO_PinState)state); // HAL库设置GPIO输出
}

/**
* @brief 读取 GPIO 输入电平
* @param gpio: GPIO 结构体指针
* @retval uint8_t: 输入电平状态 (0: 低电平, 1: 高电平)
*/
uint8_t hal_gpio_get_input(hal_gpio_t *gpio)
{
return (uint8_t)HAL_GPIO_ReadPin(gpio->Port, gpio->Pin); // HAL库读取GPIO输入
}

UART HAL - hal_uart.hhal_uart.c

hal_uart.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef __HAL_UART_H__
#define __HAL_UART_H__

#include <stdint.h>
#include "stm32f1xx_hal.h" // 假设使用STM32F1 HAL库

// UART 端口定义 (根据实际硬件连接修改)
#define UART_DEBUG_PORT USART1
#define UART_DEBUG_IRQn USART1_IRQn
#define UART_DEBUG_BAUDRATE 115200

// UART 句柄结构体
typedef struct {
UART_HandleTypeDef Handle; // HAL库 UART 句柄
} hal_uart_t;

// 函数声明
void hal_uart_init(hal_uart_t *uart); // UART 初始化
void hal_uart_transmit(hal_uart_t *uart, uint8_t *data, uint16_t size); // UART 发送数据
void hal_uart_receive_start_it(hal_uart_t *uart); // 启动 UART 接收中断
void hal_uart_register_rx_callback(hal_uart_t *uart, void (*callback)(uint8_t)); // 注册接收回调函数

#endif // __HAL_UART_H__

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

// 接收回调函数指针
static void (*uart_rx_callback)(uint8_t) = NULL;

/**
* @brief UART 初始化
* @param uart: UART 结构体指针
* @retval None
*/
void hal_uart_init(hal_uart_t *uart)
{
// 使能 UART 时钟 (例如 USART1 使能 RCC_APB2Periph_USART1)
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE(); // UART1 通常使用 GPIOA

// GPIO 配置 (根据 UART 端口选择GPIO引脚,例如 USART1_TX: PA9, USART1_RX: PA10)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_9; // TX
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_10; // RX
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上拉下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// UART 初始化
uart->Handle.Instance = UART_DEBUG_PORT; // UART 端口
uart->Handle.Init.BaudRate = UART_DEBUG_BAUDRATE; // 波特率
uart->Handle.Init.WordLength = UART_WORDLENGTH_8B; // 8位数据位
uart->Handle.Init.StopBits = UART_STOPBITS_1; // 1位停止位
uart->Handle.Init.Parity = UART_PARITY_NONE; // 无奇偶校验
uart->Handle.Init.Mode = UART_MODE_TX_RX; // 收发模式
uart->Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控
uart->Handle.Init.OverSampling = UART_OVERSAMPLING_16; // 16倍过采样
if (HAL_UART_Init(&uart->Handle) != HAL_OK)
{
Error_Handler();
}

// 使能 UART 接收中断
HAL_NVIC_SetPriority(UART_DEBUG_IRQn, 0, 0); // 中断优先级
HAL_NVIC_EnableIRQ(UART_DEBUG_IRQn);
}

/**
* @brief UART 发送数据
* @param uart: UART 结构体指针
* @param data: 数据缓冲区指针
* @param size: 数据大小
* @retval None
*/
void hal_uart_transmit(hal_uart_t *uart, uint8_t *data, uint16_t size)
{
HAL_UART_Transmit(&uart->Handle, data, size, HAL_MAX_DELAY); // HAL库发送数据
}

/**
* @brief 启动 UART 接收中断
* @param uart: UART 结构体指针
* @retval None
*/
void hal_uart_receive_start_it(hal_uart_t *uart)
{
HAL_UART_Receive_IT(&uart->Handle, (uint8_t*)&uart->Handle.pRxBuffPtr, 1); // 启动接收中断,每次接收1字节
}

/**
* @brief 注册 UART 接收回调函数
* @param uart: UART 结构体指针
* @param callback: 回调函数指针,参数为接收到的字节
* @retval None
*/
void hal_uart_register_rx_callback(hal_uart_t *uart, void (*callback)(uint8_t))
{
uart_rx_callback = callback;
}

/**
* @brief UART 中断处理函数 (需要添加到 stm32f1xx_it.c 文件中)
* @param None
* @retval None
*/
void USART1_IRQHandler(void) // 根据实际 UART 端口修改中断函数名
{
HAL_UART_IRQHandler(&uart_debug.Handle); // 调用 HAL 库 UART 中断处理函数
}

/**
* @brief HAL库 UART 接收完成回调函数 (需要在 stm32f1xx_hal_uart.c 文件中修改 HAL_UART_RxCpltCallback)
* @param huart: UART 句柄
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == UART_DEBUG_PORT) // 判断是哪个 UART 端口的中断
{
if (uart_rx_callback != NULL)
{
uart_rx_callback((uint8_t)huart->pRxBuffPtr[0]); // 调用注册的回调函数,传递接收到的字节
}
hal_uart_receive_start_it(&uart_debug); // 重新启动接收中断,等待下一个字节
}
}

3. 命令处理层 (Command Processing Layer) - command_process.hcommand_process.c

command_process.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
#ifndef __COMMAND_PROCESS_H__
#define __COMMAND_PROCESS_H__

#include <stdint.h>

// 命令ID 定义
typedef enum {
CMD_NONE = 0,
CMD_FLASH_ERASE,
CMD_FLASH_WRITE,
CMD_FLASH_READ,
CMD_GET_VERSION,
// ... 可以添加更多命令
CMD_MAX
} command_id_t;

// 命令结构体
typedef struct {
command_id_t id; // 命令ID
uint8_t *data; // 命令数据 (参数)
uint16_t data_len; // 数据长度
} command_t;

// 函数声明
void command_process_init(void); // 命令处理层初始化
void command_process_receive_byte(uint8_t byte); // 接收命令字节
void command_process_execute(command_t *cmd); // 执行命令

#endif // __COMMAND_PROCESS_H__

command_process.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
#include "command_process.h"
#include "application.h" // 假设应用层函数在 application.c/h 中定义
#include <string.h> // For memcpy

// 命令接收缓冲区
#define CMD_RX_BUF_SIZE 128
static uint8_t cmd_rx_buf[CMD_RX_BUF_SIZE];
static uint16_t cmd_rx_index = 0;
static command_t current_cmd;

/**
* @brief 命令处理层初始化
* @param None
* @retval None
*/
void command_process_init(void)
{
cmd_rx_index = 0;
memset(&current_cmd, 0, sizeof(command_t));
}

/**
* @brief 接收命令字节
* @param byte: 接收到的字节
* @retval None
*/
void command_process_receive_byte(uint8_t byte)
{
cmd_rx_buf[cmd_rx_index++] = byte;

// 简化的命令解析示例:假设命令格式为 [命令ID][数据长度][数据]
if (cmd_rx_index >= 2) // 至少接收到命令ID和数据长度
{
current_cmd.id = (command_id_t)cmd_rx_buf[0];
current_cmd.data_len = cmd_rx_buf[1];

if (cmd_rx_index >= 2 + current_cmd.data_len) // 接收到完整命令
{
if (current_cmd.data_len > 0)
{
current_cmd.data = &cmd_rx_buf[2]; // 数据指针指向数据起始位置
} else {
current_cmd.data = NULL;
}
command_process_execute(&current_cmd); // 执行命令
command_process_init(); // 重置命令接收状态,准备接收下一条命令
}
}
}

/**
* @brief 执行命令
* @param cmd: 命令结构体指针
* @retval None
*/
void command_process_execute(command_t *cmd)
{
switch (cmd->id)
{
case CMD_FLASH_ERASE:
application_flash_erase(); // 调用应用层 Flash 擦除函数
break;

case CMD_FLASH_WRITE:
application_flash_write(cmd->data, cmd->data_len); // 调用应用层 Flash 写入函数
break;

case CMD_FLASH_READ:
application_flash_read(); // 调用应用层 Flash 读取函数
break;

case CMD_GET_VERSION:
application_get_version(); // 调用应用层 获取版本信息函数
break;

default:
printf("Unknown command ID: %d\r\n", cmd->id); // 未知命令
break;
}
}

4. 应用层 (Application Layer) - application.happlication.c

application.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef __APPLICATION_H__
#define __APPLICATION_H__

#include <stdint.h>

// 函数声明
void application_init(void); // 应用层初始化
void application_flash_erase(void); // Flash 擦除
void application_flash_write(uint8_t *data, uint16_t size); // Flash 写入
void application_flash_read(void); // Flash 读取
void application_get_version(void); // 获取版本信息

#endif // __APPLICATION_H__

application.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
#include "application.h"
#include "hal_gpio.h"
#include "hal_flash.h" // 假设 Flash HAL 在 hal_flash.c/h 中
#include "hal_uart.h"
#include <stdio.h> // For printf

// LED GPIO 实例
hal_gpio_t led_green = {LED_GREEN_PORT, LED_GREEN_PIN, {.Mode = GPIO_MODE_OUTPUT_PP, .Speed = GPIO_SPEED_FREQ_LOW}};
hal_gpio_t led_red = {LED_RED_PORT, LED_RED_PIN, {.Mode = GPIO_MODE_OUTPUT_PP, .Speed = GPIO_SPEED_FREQ_LOW}};

// UART 实例 (假设已经定义全局变量 uart_debug)
extern hal_uart_t uart_debug;

// 版本信息 (示例)
#define FIRMWARE_VERSION "V1.0.0"

/**
* @brief 应用层初始化
* @param None
* @retval None
*/
void application_init(void)
{
// 初始化 LED GPIO
hal_gpio_init(&led_green);
hal_gpio_init(&led_red);
hal_gpio_set_output(&led_green, 0); // 初始状态熄灭
hal_gpio_set_output(&led_red, 0); // 初始状态熄灭

printf("Application layer initialization completed.\r\n");
}

/**
* @brief Flash 擦除
* @param None
* @retval None
*/
void application_flash_erase(void)
{
printf("Flash Erase command received.\r\n");
hal_gpio_set_output(&led_green, 1); // 绿灯亮,表示正在擦除
// 调用 HAL Flash 擦除函数 (假设为 hal_flash_erase_chip())
// hal_flash_erase_chip();
HAL_Delay(2000); // 模拟擦除延时
hal_gpio_set_output(&led_green, 0); // 擦除完成,绿灯灭
printf("Flash Erase completed.\r\n");

// 可以向上位机发送擦除完成应答
uint8_t response[] = "ERASE_OK";
hal_uart_transmit(&uart_debug, response, sizeof(response) - 1);
}

/**
* @brief Flash 写入
* @param data: 待写入数据缓冲区指针
* @param size: 待写入数据大小
* @retval None
*/
void application_flash_write(uint8_t *data, uint16_t size)
{
printf("Flash Write command received, size: %d bytes\r\n", size);
hal_gpio_set_output(&led_red, 1); // 红灯亮,表示正在写入
// 调用 HAL Flash 写入函数 (假设为 hal_flash_write())
// hal_flash_write(data, size, address); // 需要定义写入地址
HAL_Delay(size * 10); // 模拟写入延时,假设每字节10ms
hal_gpio_set_output(&led_red, 0); // 写入完成,红灯灭
printf("Flash Write completed.\r\n");

// 可以向上位机发送写入完成应答
uint8_t response[] = "WRITE_OK";
hal_uart_transmit(&uart_debug, response, sizeof(response) - 1);
}

/**
* @brief Flash 读取 (示例,实际应用中可能需要指定读取地址和长度)
* @param None
* @retval None
*/
void application_flash_read(void)
{
printf("Flash Read command received.\r\n");
// 调用 HAL Flash 读取函数 (假设为 hal_flash_read())
// uint8_t read_data[256]; // 假设读取 256 字节
// hal_flash_read(read_data, sizeof(read_data), address); // 需要定义读取地址
HAL_Delay(1000); // 模拟读取延时
printf("Flash Read completed.\r\n");

// 可以将读取到的数据通过 UART 发送给上位机
uint8_t response[] = "READ_DATA"; // 示例数据
hal_uart_transmit(&uart_debug, response, sizeof(response) - 1);
}

/**
* @brief 获取版本信息
* @param None
* @retval None
*/
void application_get_version(void)
{
printf("Get Version command received.\r\n");
printf("Firmware Version: %s\r\n", FIRMWARE_VERSION);

// 将版本信息通过 UART 发送给上位机
hal_uart_transmit(&uart_debug, (uint8_t*)FIRMWARE_VERSION, strlen(FIRMWARE_VERSION));
}

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
#include "bsp.h"
#include "hal_gpio.h"
#include "hal_uart.h"
#include "command_process.h"
#include "application.h"
#include <stdio.h>

// UART 实例 (全局变量)
hal_uart_t uart_debug;

/**
* @brief 接收数据回调函数 (UART 接收到字节时调用)
* @param byte: 接收到的字节
* @retval None
*/
void uart_rx_callback_handler(uint8_t byte)
{
command_process_receive_byte(byte); // 将接收到的字节传递给命令处理层
}

int main(void)
{
// BSP 初始化
bsp_init();

// 初始化 UART HAL
hal_uart_init(&uart_debug);
hal_uart_register_rx_callback(&uart_debug, uart_rx_callback_handler); // 注册接收回调函数
hal_uart_receive_start_it(&uart_debug); // 启动 UART 接收中断

// 应用层初始化
application_init();

printf("System started.\r\n");

/* Infinite loop */
while (1)
{
// 主循环中可以处理一些后台任务,或者保持空闲等待中断事件
// 例如:LED 状态指示,按键扫描等
HAL_Delay(100); // 适当延时,降低CPU占用
}
}

项目中采用的各种技术和方法 (实践验证):

  • 分层模块化架构: 如上所述,提高了代码可维护性、可扩展性和复用性。
  • HAL库 (Hardware Abstraction Layer): 例如 STM32 HAL 库,简化了底层硬件驱动开发,提高了代码可移植性。
  • 中断驱动: UART 接收采用中断方式,提高了系统响应速度和效率。
  • 回调函数: UART 接收回调函数机制,实现了事件驱动编程,使代码结构更清晰。
  • 状态机: 命令处理层可以使用状态机来解析复杂的命令协议(示例代码中简化了命令格式)。
  • 错误处理机制: Error_Handler() 函数和 HAL 库的错误检查机制,提高了系统可靠性。
  • 版本控制 (Git): 代码版本管理,方便代码维护和协作开发。
  • 调试工具 (JTAG/SWD): 在线调试,方便代码调试和问题定位。
  • 单元测试: 对各个模块进行单元测试,例如 HAL 层的 GPIO 和 UART 驱动,确保模块功能正确。
  • 集成测试: 将各个模块集成起来进行测试,例如命令处理层和应用层集成测试。
  • 系统测试: 对整个系统进行功能测试、性能测试、可靠性测试等,验证系统是否满足需求。
  • 代码审查: 团队成员互相审查代码,提高代码质量。
  • 静态代码分析: 使用静态代码分析工具,检查代码潜在的错误和缺陷。
  • 代码注释: 详细的代码注释,提高代码可读性和可维护性。

代码扩展和增强方向 (为了达到3000行以上的代码量,可以进一步扩展以下方面):

  • Flash HAL 模块: 实现更完善的 Flash HAL 驱动,包括:
    • 支持不同类型的 Flash 芯片。
    • 实现 Flash 擦除 (扇区擦除、块擦除、芯片擦除)。
    • 实现 Flash 写入 (页编程、字编程)。
    • 实现 Flash 读取 (字节读取、页读取)。
    • 实现 Flash ECC 校验和错误处理。
    • 实现 Flash 加密和安全存储功能。
  • USB 通信模块: 如果烧录座需要通过 USB 与 PC 通信,需要添加 USB HAL 和 USB 设备驱动代码。
    • 支持 USB CDC (虚拟串口) 或 USB HID 等协议。
    • 实现 USB 命令传输和数据传输。
  • 更复杂的命令协议: 设计更健壮、更灵活的命令协议,例如:
    • 添加命令校验和 (CRC) 或数字签名,提高通信可靠性和安全性。
    • 支持命令分包和重传机制,处理大数据传输。
    • 支持命令流控制,防止数据溢出。
  • 用户界面增强:
    • 添加 LCD 或 OLED 显示屏,显示烧录进度、状态信息和错误提示。
    • 添加更多按键,实现更丰富的人机交互功能。
    • 添加蜂鸣器,进行声音提示。
  • 错误处理和日志记录:
    • 实现更完善的错误处理机制,例如:
      • 详细的错误码定义。
      • 错误日志记录功能,方便问题追溯。
      • 错误恢复机制,提高系统鲁棒性。
  • OTA (Over-The-Air) 升级: 如果需要支持远程固件升级,需要添加 OTA 升级模块。
    • 实现固件下载、校验、升级流程。
    • 支持安全 OTA 升级,防止恶意固件注入。
  • 安全功能: 对于需要安全性的应用,可以添加安全模块,例如:
    • 固件加密存储和解密。
    • 安全启动 (Secure Boot) 功能。
    • 防克隆和防篡改机制。
  • 多线程或 RTOS: 对于更复杂的系统,可以考虑使用 RTOS (实时操作系统) 或多线程技术,提高系统并发性和实时性。
    • 例如使用 FreeRTOS 或 uC/OS 等 RTOS。
    • 将不同的功能模块分配到不同的任务或线程中运行。

总结

以上代码和架构设计提供了一个构建可靠、高效、可扩展的嵌入式系统平台的框架。通过分层模块化架构、HAL 库的应用、实践验证的技术和方法,可以有效地开发出满足需求的嵌入式产品。 为了达到3000行以上的代码量,需要根据具体的产品需求和功能进行更详细的模块实现和功能扩展,例如完善 Flash HAL 驱动、添加 USB 通信、增强用户界面、实现 OTA 升级等等。 在实际项目中,还需要根据硬件平台和具体需求进行代码调整和优化。

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