编程技术分享

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

0%

简介:**

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

本项目旨在展示一个完整的嵌入式系统开发流程,以立创·地阔星STM32F103C8T6开发板为硬件平台,构建一个基础但可扩展的嵌入式系统框架。该框架将涵盖硬件抽象层(HAL)、板级支持包(BSP)、操作系统抽象层(OSAL,可选,但为了通用性,我们这里会考虑)、中间件层(Middleware)以及应用层(Application)。我们将详细解释每一层的设计理念、功能以及具体的C代码实现,并探讨在实际项目开发中如何运用这些架构和技术。

1. 需求分析

任何嵌入式系统开发的第一步都是明确需求。对于一个通用的嵌入式系统平台,我们的核心需求可以概括为以下几点:

  • 可靠性: 系统必须稳定可靠地运行,避免崩溃、死机等异常情况,尤其是在资源受限的嵌入式环境中。
  • 高效性: 系统需要高效利用硬件资源,包括处理器、内存、外设等,以实现最佳性能和响应速度。
  • 可扩展性: 系统架构应易于扩展和维护,方便添加新功能、支持新硬件或进行系统升级。
  • 模块化: 代码应模块化设计,降低耦合度,提高代码的可读性、可维护性和可重用性。
  • 可移植性: 架构应尽可能考虑代码的可移植性,方便在不同硬件平台或操作系统之间迁移。
  • 实时性(根据具体应用场景): 虽然STM32F103C8T6并非严格意义上的实时处理器,但在某些应用场景下,需要考虑系统的实时响应能力。

基于以上需求,我们设计的系统平台将力求在资源有限的STM32F103C8T6上实现上述目标。

2. 系统设计与代码架构

为了满足上述需求,我们采用分层架构作为核心设计思想。分层架构是嵌入式系统开发中一种非常成熟且有效的架构模式,它将系统划分为多个层次,每一层只关注特定的功能,层与层之间通过明确定义的接口进行交互。 这种架构具有以下优点:

  • 模块化和解耦: 各层之间职责清晰,降低耦合度,方便模块独立开发和测试。
  • 可维护性: 修改某一层的代码对其他层的影响较小,易于维护和升级。
  • 可重用性: 底层模块(如HAL、BSP)可以在不同的项目或平台之间重用。
  • 可扩展性: 方便在现有架构上添加新的功能模块或层次。
  • 可移植性: 通过抽象硬件和操作系统,可以提高代码在不同平台之间的移植性。

我们设计的系统架构将包含以下层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer):
    • 目的: 屏蔽底层硬件的具体细节,提供统一的硬件访问接口,使上层代码无需关心具体的硬件寄存器操作。
    • 功能: 封装STM32F103C8T6的各种外设驱动,如GPIO、UART、SPI、I2C、ADC、Timer 等。
    • 实现: 使用标准C语言编写,直接操作STM32F103C8T6的寄存器,或者使用官方提供的HAL库(为了更贴近底层和深入理解原理,我们这里可以考虑部分手写HAL,部分使用HAL库)。
  • 板级支持包 (BSP - Board Support Package):
    • 目的: 提供特定开发板(立创·地阔星STM32F103C8T6开发板)相关的初始化和配置代码,例如时钟配置、GPIO引脚配置、外设初始化等。
    • 功能: 负责板级的硬件初始化,为上层提供板级硬件资源的访问接口,例如LED控制、按键读取等。
    • 实现: 基于HAL层,针对具体的硬件平台进行配置和初始化。
  • 操作系统抽象层 (OSAL - Operating System Abstraction Layer) (可选,但推荐):
    • 目的: 如果项目需要使用操作系统(例如 FreeRTOS),OSAL层可以屏蔽不同操作系统的API差异,提供统一的操作系统接口。即使当前项目不立即使用RTOS,预留OSAL层也为未来的扩展打下基础。
    • 功能: 封装操作系统相关的服务,例如任务管理、线程同步、消息队列、定时器等。
    • 实现: 定义一套通用的操作系统API接口,并针对不同的操作系统实现具体的适配层。 对于本项目,我们可以先考虑一个简化的OSAL,或者先不引入RTOS,直接在裸机环境下进行开发,但架构上预留OSAL的位置。
  • 中间件层 (Middleware):
    • 目的: 提供通用的软件组件和服务,供应用层使用,例如通信协议栈(UART、SPI、I2C协议处理)、数据处理算法、文件系统(如果需要)、日志系统等。
    • 功能: 实现一些通用的、与具体应用无关的功能模块。
    • 实现: 基于HAL层和OSAL层(如果存在),实现各种中间件组件。
  • 应用层 (Application):
    • 目的: 实现具体的应用逻辑,例如传感器数据采集、数据处理、用户界面(如果需要)、通信控制等。
    • 功能: 根据项目需求,实现具体的应用功能。
    • 实现: 基于中间件层提供的服务,实现具体的应用逻辑。

代码目录结构建议:

为了更好地组织代码,我们建议采用以下目录结构:

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
Project/
├── Core/ // 核心代码,例如启动文件、中断向量表等 (通常由IDE自动生成)
├── Drivers/ // 驱动层
│ ├── HAL/ // 硬件抽象层 (HAL)
│ │ ├── inc/ // HAL层头文件
│ │ └── src/ // HAL层源文件
│ ├── BSP/ // 板级支持包 (BSP)
│ │ ├── inc/ // BSP层头文件
│ │ └── src/ // BSP层源文件
│ └── STM32F1xx_HAL_Driver/ // STM32官方HAL库 (如果使用)
│ ├── Inc/
│ └── Src/
├── Middlewares/ // 中间件层
│ ├── Communication/ // 通信协议栈
│ │ ├── UART/
│ │ ├── SPI/
│ │ └── I2C/
│ ├── DataProcessing/ // 数据处理
│ │ └── ...
│ └── Log/ // 日志系统
├── OSAL/ // 操作系统抽象层 (可选)
│ ├── inc/
│ └── src/
├── Application/ // 应用层
│ ├── inc/
│ └── src/
├── Libraries/ // 第三方库 (如果使用)
├── Inc/ // 项目全局头文件
├── Src/ // 项目全局源文件 (例如 main.c)
├── Documentation/ // 项目文档
├── Tools/ // 开发工具和脚本
├── Makefile // Makefile (如果使用Makefile构建)
└── README.md // 项目说明

3. 具体C代码实现 (精简示例,完整代码将超过3000行)

为了演示代码架构,我们以一个简单的LED闪烁示例和一个UART串口通信示例来展示各层代码的编写方式。

3.1 HAL层代码 (Drivers/HAL/)

Drivers/HAL/inc/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
78
79
80
81
82
83
84
85
86
87
88
89
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include "stm32f103xb.h" // 包含STM32F103C8T6的头文件 (根据具体芯片型号)

// 定义GPIO端口和引脚枚举
typedef enum {
GPIO_PORT_A = 0,
GPIO_PORT_B,
GPIO_PORT_C,
// ... 可以继续添加其他端口
} GPIO_Port_TypeDef;

typedef enum {
GPIO_PIN_0 = 0,
GPIO_PIN_1 = 1,
GPIO_PIN_2 = 2,
GPIO_PIN_3 = 3,
GPIO_PIN_4 = 4,
GPIO_PIN_5 = 5,
GPIO_PIN_6 = 6,
GPIO_PIN_7 = 7,
GPIO_PIN_8 = 8,
GPIO_PIN_9 = 9,
GPIO_PIN_10 = 10,
GPIO_PIN_11 = 11,
GPIO_PIN_12 = 12,
GPIO_PIN_13 = 13,
GPIO_PIN_14 = 14,
GPIO_PIN_15 = 15,
} GPIO_Pin_TypeDef;

// 定义GPIO模式枚举
typedef enum {
GPIO_MODE_INPUT = 0x00,
GPIO_MODE_OUTPUT = 0x01,
GPIO_MODE_AF_PP = 0x02, // Alternate function push-pull
GPIO_MODE_AF_OD = 0x03, // Alternate function open-drain
} GPIO_Mode_TypeDef;

// 定义GPIO输出类型枚举
typedef enum {
GPIO_OUTPUT_PP = 0x00, // Push-pull
GPIO_OUTPUT_OD = 0x01, // Open-drain
} GPIO_OutputType_TypeDef;

// 定义GPIO速度枚举
typedef enum {
GPIO_SPEED_LOW = 0x01,
GPIO_SPEED_MEDIUM = 0x02,
GPIO_SPEED_FAST = 0x03,
GPIO_SPEED_HIGH = 0x04,
} GPIO_Speed_TypeDef;

// 定义GPIO上下拉电阻枚举
typedef enum {
GPIO_PULL_NONE = 0x00,
GPIO_PULL_UP = 0x01,
GPIO_PULL_DOWN = 0x02,
} GPIO_Pull_TypeDef;


// GPIO 初始化结构体
typedef struct {
GPIO_Port_TypeDef Port;
GPIO_Pin_TypeDef Pin;
GPIO_Mode_TypeDef Mode;
GPIO_OutputType_TypeDef OutputType;
GPIO_Speed_TypeDef Speed;
GPIO_Pull_TypeDef Pull;
} GPIO_InitTypeDef;


// 初始化GPIO
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct);

// 设置GPIO引脚输出高电平
void HAL_GPIO_SetPinHigh(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin);

// 设置GPIO引脚输出低电平
void HAL_GPIO_SetPinLow(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin);

// 翻转GPIO引脚输出电平
void HAL_GPIO_TogglePin(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin);

// 读取GPIO引脚输入电平
uint8_t HAL_GPIO_ReadPin(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin);

#endif /* HAL_GPIO_H */

Drivers/HAL/src/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
#include "hal_gpio.h"

// GPIO 初始化函数
void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct) {
GPIO_TypeDef *port;
uint32_t pin_mask = (1 << GPIO_InitStruct->Pin);

// 1. 使能GPIO时钟
if (GPIO_InitStruct->Port == GPIO_PORT_A) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
} else if (GPIO_InitStruct->Port == GPIO_PORT_B) {
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
} else if (GPIO_InitStruct->Port == GPIO_PORT_C) {
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
} // ... 可以继续添加其他端口时钟使能

// 2. 获取GPIO端口基地址
if (GPIO_InitStruct->Port == GPIO_PORT_A) {
port = GPIOA;
} else if (GPIO_InitStruct->Port == GPIO_PORT_B) {
port = GPIOB;
} else if (GPIO_InitStruct->Port == GPIO_PORT_C) {
port = GPIOC;
} // ... 可以继续添加其他端口基地址

// 3. 配置GPIO模式
if (GPIO_InitStruct->Mode == GPIO_MODE_OUTPUT) {
port->CRL &= ~(0xF << (GPIO_InitStruct->Pin * 4)); // 清除配置位
port->CRL |= ((GPIO_InitStruct->Speed << 2) | GPIO_InitStruct->OutputType) << (GPIO_InitStruct->Pin * 4);
} else if (GPIO_InitStruct->Mode == GPIO_MODE_INPUT) {
port->CRL &= ~(0xF << (GPIO_InitStruct->Pin * 4)); // 清除配置位
port->CRL |= (GPIO_InitStruct->Pull << 2) << (GPIO_InitStruct->Pin * 4); // 配置输入模式和上下拉
} // ... 可以继续添加其他模式配置

// ... 可以添加更多GPIO配置,例如AF模式配置等
}

// 设置GPIO引脚输出高电平
void HAL_GPIO_SetPinHigh(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin) {
GPIO_TypeDef *port;
if (Port == GPIO_PORT_A) port = GPIOA;
else if (Port == GPIO_PORT_B) port = GPIOB;
else if (Port == GPIO_PORT_C) port = GPIOC;

port->BSRR = (1 << Pin); // Set bit
}

// 设置GPIO引脚输出低电平
void HAL_GPIO_SetPinLow(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin) {
GPIO_TypeDef *port;
if (Port == GPIO_PORT_A) port = GPIOA;
else if (Port == GPIO_PORT_B) port = GPIOB;
else if (Port == GPIO_PORT_C) port = GPIOC;

port->BRR = (1 << Pin); // Reset bit
}

// 翻转GPIO引脚输出电平
void HAL_GPIO_TogglePin(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin) {
GPIO_TypeDef *port;
if (Port == GPIO_PORT_A) port = GPIOA;
else if (Port == GPIO_PORT_B) port = GPIOB;
else if (Port == GPIO_PORT_C) port = GPIOC;

port->ODR ^= (1 << Pin); // Toggle bit
}

// 读取GPIO引脚输入电平
uint8_t HAL_GPIO_ReadPin(GPIO_Port_TypeDef Port, GPIO_Pin_TypeDef Pin) {
GPIO_TypeDef *port;
if (Port == GPIO_PORT_A) port = GPIOA;
else if (Port == GPIO_PORT_B) port = GPIOB;
else if (Port == GPIO_PORT_C) port = GPIOC;

return (uint8_t)((port->IDR >> Pin) & 0x01); // Read input data bit
}

Drivers/HAL/inc/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
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
#ifndef HAL_UART_H
#define HAL_UART_H

#include "stm32f103xb.h"

// 定义UART端口枚举
typedef enum {
UART_PORT_1 = 1,
UART_PORT_2,
UART_PORT_3,
} UART_Port_TypeDef;

// 定义UART波特率枚举 (可以根据需要扩展)
typedef enum {
UART_BAUDRATE_9600 = 9600,
UART_BAUDRATE_19200 = 19200,
UART_BAUDRATE_38400 = 38400,
UART_BAUDRATE_115200 = 115200,
} UART_BaudRate_TypeDef;

// 定义UART数据位枚举
typedef enum {
UART_WORDLENGTH_8B = 8,
UART_WORDLENGTH_9B = 9,
} UART_WordLength_TypeDef;

// 定义UART停止位枚举
typedef enum {
UART_STOPBITS_1 = 1,
UART_STOPBITS_2 = 2,
} UART_StopBits_TypeDef;

// 定义UART奇偶校验枚举
typedef enum {
UART_PARITY_NONE = 0,
UART_PARITY_EVEN = 1,
UART_PARITY_ODD = 2,
} UART_Parity_TypeDef;

// UART 初始化结构体
typedef struct {
UART_Port_TypeDef Port;
UART_BaudRate_TypeDef BaudRate;
UART_WordLength_TypeDef WordLength;
UART_StopBits_TypeDef StopBits;
UART_Parity_TypeDef Parity;
} UART_InitTypeDef;

// 初始化UART
void HAL_UART_Init(UART_InitTypeDef *UART_InitStruct);

// UART 发送一个字节数据
void HAL_UART_TransmitByte(UART_Port_TypeDef Port, uint8_t data);

// UART 发送字符串
void HAL_UART_TransmitString(UART_Port_TypeDef Port, char *str);

// UART 接收一个字节数据 (轮询方式,实际应用中通常使用中断或DMA)
uint8_t HAL_UART_ReceiveByte(UART_Port_TypeDef Port);

#endif /* HAL_UART_H */

Drivers/HAL/src/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
#include "hal_uart.h"

// 初始化UART
void HAL_UART_Init(UART_InitTypeDef *UART_InitStruct) {
USART_TypeDef *uart;
uint32_t apb_clock;

// 1. 使能UART时钟
if (UART_InitStruct->Port == UART_PORT_1) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
apb_clock = 72000000; // APB2 clock for USART1 on STM32F103C8T6 (假设PCLK2=72MHz)
// 配置GPIO引脚,例如 USART1_TX (PA9), USART1_RX (PA10)
GPIO_InitTypeDef GPIO_InitStruct_UART;
GPIO_InitStruct_UART.Port = GPIO_PORT_A;
GPIO_InitStruct_UART.Pin = GPIO_PIN_9; // TX
GPIO_InitStruct_UART.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct_UART.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(&GPIO_InitStruct_UART);

GPIO_InitStruct_UART.Pin = GPIO_PIN_10; // RX
GPIO_InitStruct_UART.Mode = GPIO_MODE_INPUT; // 浮空输入或上拉输入
GPIO_InitStruct_UART.Pull = GPIO_PULL_UP;
HAL_GPIO_Init(&GPIO_InitStruct_UART);

uart = USART1;
} else if (UART_InitStruct->Port == UART_PORT_2) {
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
apb_clock = 36000000; // APB1 clock for USART2, USART3 on STM32F103C8T6 (假设PCLK1=36MHz)
// ... 配置USART2的GPIO引脚 (PA2, PA3)
uart = USART2;
} // ... 可以继续添加其他UART端口配置

// 2. 配置UART参数
// 波特率计算: Baud Rate = APB Clock / (16 * USARTDIV)
uint32_t usartdiv = apb_clock / UART_InitStruct->BaudRate;
uart->BRR = usartdiv;

// 数据位、停止位、奇偶校验配置 (示例,可以根据 UART_InitStruct 配置)
uart->CR1 &= ~(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS); // 清除数据位、奇偶校验位
if (UART_InitStruct->WordLength == UART_WORDLENGTH_9B) {
uart->CR1 |= USART_CR1_M; // 9位数据
}
if (UART_InitStruct->Parity != UART_PARITY_NONE) {
uart->CR1 |= USART_CR1_PCE; // 使能奇偶校验
if (UART_InitStruct->Parity == UART_PARITY_EVEN) {
// 偶校验,默认即为偶校验
} else if (UART_InitStruct->Parity == UART_PARITY_ODD) {
uart->CR1 |= USART_CR1_PS; // 奇校验
}
}

uart->CR2 &= ~USART_CR2_STOP; // 清除停止位
if (UART_InitStruct->StopBits == UART_STOPBITS_2) {
uart->CR2 |= USART_CR2_STOP_1; // 2个停止位
}

// 3. 使能 UART TX 和 RX (根据需要)
uart->CR1 |= USART_CR1_TE | USART_CR1_RE;

// 4. 使能 UART
uart->CR1 |= USART_CR1_UE;
}

// UART 发送一个字节数据
void HAL_UART_TransmitByte(UART_Port_TypeDef Port, uint8_t data) {
USART_TypeDef *uart;
if (Port == UART_PORT_1) uart = USART1;
else if (Port == UART_PORT_2) uart = USART2;

// 等待发送缓冲区空
while (!(uart->SR & USART_SR_TXE));
uart->DR = data;
}

// UART 发送字符串
void HAL_UART_TransmitString(UART_Port_TypeDef Port, char *str) {
while (*str) {
HAL_UART_TransmitByte(Port, *str++);
}
}

// UART 接收一个字节数据 (轮询方式)
uint8_t HAL_UART_ReceiveByte(UART_Port_TypeDef Port) {
USART_TypeDef *uart;
if (Port == UART_PORT_1) uart = USART1;
else if (Port == UART_PORT_2) uart = USART2;

// 等待接收数据就绪
while (!(uart->SR & USART_SR_RXNE));
return (uint8_t)(uart->DR & 0xFF);
}

3.2 BSP层代码 (Drivers/BSP/)

Drivers/BSP/inc/bsp_led.h:

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

#include "hal_gpio.h"

// 定义LED对应的GPIO端口和引脚 (根据立创·地阔星开发板原理图确定)
#define LED_GREEN_PORT GPIO_PORT_C
#define LED_GREEN_PIN GPIO_PIN_13

// 初始化LED
void BSP_LED_Init(void);

// LED 亮
void BSP_LED_On(void);

// LED 灭
void BSP_LED_Off(void);

// LED 翻转
void BSP_LED_Toggle(void);

#endif /* BSP_LED_H */

Drivers/BSP/src/bsp_led.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
#include "bsp_led.h"

// 初始化LED
void BSP_LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.Port = LED_GREEN_PORT;
GPIO_InitStruct.Pin = LED_GREEN_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.OutputType = GPIO_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Pull = GPIO_PULL_NONE; // 或 GPIO_PULL_UP
HAL_GPIO_Init(&GPIO_InitStruct);

BSP_LED_Off(); // 默认关闭LED
}

// LED 亮
void BSP_LED_On(void) {
HAL_GPIO_SetPinLow(LED_GREEN_PORT, LED_GREEN_PIN); // 低电平点亮LED (根据实际LED连接方式)
}

// LED 灭
void BSP_LED_Off(void) {
HAL_GPIO_SetPinHigh(LED_GREEN_PORT, LED_GREEN_PIN); // 高电平熄灭LED (根据实际LED连接方式)
}

// LED 翻转
void BSP_LED_Toggle(void) {
HAL_GPIO_TogglePin(LED_GREEN_PORT, LED_GREEN_PIN);
}

Drivers/BSP/inc/bsp_uart.h:

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

#include "hal_uart.h"

// 定义BSP层使用的UART端口 (例如,使用 UART1)
#define DEBUG_UART_PORT UART_PORT_1

// 初始化调试串口
void BSP_UART_Debug_Init(UART_BaudRate_TypeDef baudrate);

// 调试串口发送字符串
void BSP_UART_Debug_PrintString(char *str);

// 调试串口发送格式化字符串 (类似 printf)
void BSP_UART_Debug_Printf(const char *fmt, ...);

#endif /* BSP_UART_H */

Drivers/BSP/src/bsp_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
#include "bsp_uart.h"
#include <stdarg.h>
#include <stdio.h>

// 初始化调试串口
void BSP_UART_Debug_Init(UART_BaudRate_TypeDef baudrate) {
UART_InitTypeDef UART_InitStruct;

UART_InitStruct.Port = DEBUG_UART_PORT;
UART_InitStruct.BaudRate = baudrate;
UART_InitStruct.WordLength = UART_WORDLENGTH_8B;
UART_InitStruct.StopBits = UART_STOPBITS_1;
UART_InitStruct.Parity = UART_PARITY_NONE;
HAL_UART_Init(&UART_InitStruct);
}

// 调试串口发送字符串
void BSP_UART_Debug_PrintString(char *str) {
HAL_UART_TransmitString(DEBUG_UART_PORT, str);
}

// 调试串口发送格式化字符串 (类似 printf)
void BSP_UART_Debug_Printf(const char *fmt, ...) {
char buffer[256]; // 缓冲区大小可以根据需要调整
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args); // 格式化字符串到缓冲区
va_end(args);
BSP_UART_Debug_PrintString(buffer); // 发送缓冲区内容
}

3.3 应用层代码 (Application/)

Application/src/app_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
#include "bsp_led.h"
#include "bsp_uart.h"
#include "delay.h" // 假设有延时函数库

int main(void) {
SystemClock_Config(); // 系统时钟配置 (需要根据实际情况配置)
delay_init(); // 延时函数初始化
BSP_LED_Init(); // LED 初始化
BSP_UART_Debug_Init(UART_BAUDRATE_115200); // 调试串口初始化

BSP_UART_Debug_Printf("System Start!\r\n");

while (1) {
BSP_LED_Toggle(); // LED 翻转
BSP_UART_Debug_Printf("LED Toggled!\r\n");
delay_ms(500); // 延时 500ms
}
}

// 系统时钟配置函数 (示例,需要根据 STM32CubeMX 或手动配置)
void SystemClock_Config(void) {
// ... 时钟配置代码,例如配置 HSE 晶振,PLL,时钟源选择等
// ... 具体配置参考 STM32F103C8T6 的时钟树和 RCC 寄存器说明
}

3.4 其他重要文件

  • Inc/main.h: 项目全局头文件,可以包含一些全局宏定义、类型定义、函数声明等。
  • Src/main.c: 包含 main() 函数,是程序的入口点。
  • Core/Startup/startup_stm32f103xb.s: 启动文件,汇编代码,负责初始化堆栈、向量表、调用 SystemInit()main() 函数 (通常由IDE自动生成)。
  • Core/Src/system_stm32f1xx.c: SystemInit() 函数的实现,负责系统初始化,例如配置时钟 (通常由IDE自动生成或需要根据需求配置)。
  • delay.h/delay.c: 延时函数库,可以使用 SysTick 定时器实现精确延时,或者简单的循环延时。

4. 项目中采用的技术和方法

  • 分层架构: 如上所述,采用HAL、BSP、OSAL(可选)、Middleware、Application分层架构,提高代码的模块化、可维护性、可扩展性和可移植性。
  • 模块化设计: 将系统功能划分为独立的模块,例如GPIO驱动模块、UART驱动模块、LED控制模块、串口调试模块等。
  • 抽象化: 通过HAL层抽象硬件细节,通过OSAL层抽象操作系统API,提高代码的通用性。
  • 事件驱动编程 (可以考虑在后续扩展中引入): 在更复杂的系统中,可以采用事件驱动编程模型,提高系统的响应性和效率。例如,使用中断处理事件、使用消息队列传递事件等。
  • 中断处理: 对于实时性要求较高的应用,需要合理使用中断,例如UART接收中断、定时器中断等。
  • 状态机 (可以考虑在后续扩展中引入): 对于复杂的应用逻辑,可以使用状态机模型来管理系统的状态和状态转换,提高代码的可读性和可维护性。
  • 版本控制 (Git): 使用Git进行代码版本控制,方便团队协作、代码管理和版本回溯。
  • 代码注释: 编写清晰、详细的代码注释,提高代码的可读性和可维护性。
  • 代码风格规范: 遵循统一的代码风格规范,例如命名规范、缩进风格、注释风格等,提高代码的可读性和一致性。
  • 单元测试 (可以考虑在后续扩展中引入): 对于重要的模块,可以编写单元测试用例,进行单元测试,确保模块功能的正确性。
  • 集成测试: 在模块开发完成后,进行集成测试,验证模块之间的协作是否正常。
  • 调试技巧: 掌握常用的嵌入式系统调试技巧,例如使用调试器 (例如 ST-Link 和 GDB)、串口打印调试信息、示波器等。
  • 代码审查: 进行代码审查,由团队成员互相审查代码,提高代码质量,尽早发现潜在问题。
  • 持续集成 (CI) 和持续交付 (CD) (可以考虑在更大型项目中引入): 在更大型的项目中,可以考虑引入CI/CD流程,自动化代码构建、测试和部署过程,提高开发效率和软件质量。

5. 实践验证的技术和方法

上述代码架构和技术方法都是经过大量实践验证的,在嵌入式系统开发领域被广泛采用。

  • 分层架构: 几乎所有的现代操作系统和大型软件系统都采用分层架构,嵌入式系统也不例外。分层架构是构建复杂软件系统的有效方法。
  • HAL层: 各种硬件厂商都会提供HAL库或类似的硬件抽象层,例如STM32的HAL库、NXP的HAL库等。手写HAL层可以更深入理解硬件原理,但使用官方HAL库可以提高开发效率。
  • BSP层: BSP层是嵌入式系统开发中必不可少的一部分,用于适配不同的硬件平台。
  • 模块化设计: 模块化设计是软件工程的基本原则,适用于各种规模的软件项目。
  • 抽象化: 抽象化是提高软件可重用性和可移植性的关键技术。
  • 版本控制 (Git): Git已经成为软件开发领域的标准版本控制工具。
  • 代码注释和代码风格规范: 良好的代码注释和代码风格规范是提高代码可读性和可维护性的重要保证。
  • 测试和调试技巧: 掌握测试和调试技巧是嵌入式软件开发工程师必备的技能。

6. 代码扩展和完善方向 (为达到3000行代码量,可以进一步扩展以下方面)

为了使代码量达到3000行以上,并进一步完善系统平台,我们可以从以下几个方面进行扩展:

  • 完善HAL层驱动: 除了GPIO和UART,可以添加SPI、I2C、ADC、Timer等常用外设的HAL层驱动。每个外设驱动都包含头文件和源文件,实现初始化、数据读写等功能。
  • 添加更完善的BSP层: 除了LED和Debug UART,可以添加按键、蜂鸣器、LCD、传感器等板载资源的BSP层驱动。
  • 引入OSAL层和RTOS (例如 FreeRTOS): 添加OSAL层,并基于FreeRTOS实现任务管理、线程同步、消息队列、定时器等服务。这将大大提高系统的并发性和实时性。
  • 开发中间件层组件:
    • 通信协议栈: 实现更复杂的通信协议,例如SPI、I2C的主从模式驱动,CAN总线驱动,甚至TCP/IP协议栈 (如果资源允许)。
    • 数据处理算法: 实现一些常用的数据处理算法,例如滤波算法、PID控制算法、数据加密算法等。
    • 文件系统 (如果需要): 如果需要存储大量数据,可以考虑移植一个轻量级的文件系统,例如FatFs。
    • 日志系统: 实现更完善的日志系统,支持日志级别、日志输出到不同目标 (例如串口、Flash) 等功能。
  • 开发更复杂的应用层示例:
    • 传感器数据采集和显示: 使用ADC采集温湿度传感器、光照传感器等数据,并在LCD上显示。
    • 电机控制: 使用PWM控制电机转速,实现简单的电机控制系统。
    • 无线通信 (如果开发板支持): 使用WiFi或蓝牙模块进行无线数据传输。
    • GUI界面 (如果开发板支持LCD): 使用GUI库 (例如 emWin, LittlevGL) 开发简单的图形用户界面。
  • 添加更多的配置选项和宏定义: 在代码中添加更多的配置选项和宏定义,方便用户根据不同的硬件和应用场景进行定制。
  • 编写更详细的注释和文档: 对代码进行更详细的注释,并编写更完善的项目文档,包括系统架构文档、API文档、用户手册等。
  • 添加更多的测试用例: 编写更多的单元测试用例和集成测试用例,提高代码的测试覆盖率和质量。

通过以上扩展,我们可以构建一个功能更加完善、代码量超过3000行的嵌入式系统平台,更全面地展示嵌入式系统开发的完整流程和技术方法。

总结:

本项目以立创·地阔星STM32F103C8T6开发板为平台,详细阐述了嵌入式系统开发的分层架构和代码实现。我们从需求分析出发,选择了分层架构作为核心设计思想,并详细介绍了HAL层、BSP层、OSAL层(可选)、中间件层和应用层的功能和实现方式。通过LED闪烁和UART串口通信的示例,展示了各层代码的编写方法。同时,我们还探讨了项目中采用的各种实践验证的技术和方法,例如模块化设计、抽象化、版本控制、代码注释、测试和调试技巧等。 最后,我们提出了代码扩展和完善的方向,以便构建一个功能更加强大、代码量更充足的嵌入式系统平台。 希望这个详细的说明和代码示例能够帮助您理解嵌入式系统开发,并为您在实际项目开发中提供参考。
Error executing command: /bin/sh: -c: line 1: unexpected EOF while looking for matching ``’
/bin/sh: -c: line 2: syntax error: unexpected end of file

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