编程技术分享

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

0%

简介:以德州仪器(TI)设计的芯片MSPM0L1306🎞作为主控,制作的一款电赛推荐开发板。

好的,作为一名高级嵌入式软件开发工程师,我将根据你提供的 MSPM0L1306 电赛推荐开发板,详细阐述最适合的代码设计架构,并提供具体的 C 代码实现,同时涵盖项目开发流程中的关键技术和方法。
关注微信公众号,提前获取相关推文

项目背景与需求分析

首先,我们需要明确这个开发板的目标应用场景和潜在需求。作为一款电赛推荐开发板,它通常被用于各种电子设计竞赛项目,这些项目可能涵盖:

  • 传感器数据采集与处理: 例如温度、湿度、光照、加速度、陀螺仪等传感器数据的实时采集、滤波、校准和分析。
  • 执行器控制: 例如电机、舵机、LED 灯、显示屏等的精确控制。
  • 无线通信: 例如蓝牙、Wi-Fi、LoRa 等无线通信模块的集成和数据传输。
  • 人机交互: 例如按键、触摸屏、语音识别等用户输入,以及 LCD、OLED、LED 阵列等信息显示。
  • 低功耗设计: 针对电池供电的应用,需要优化功耗,延长续航时间。
  • 实时性要求: 某些应用可能需要实时响应,例如电机控制、快速数据采集等。

基于以上潜在需求,我们可以总结出对嵌入式系统平台的要求:

  1. 可靠性: 系统必须稳定可靠运行,避免崩溃、死机等问题,尤其是在竞赛环境中,稳定性至关重要。
  2. 高效性: 系统资源有限,代码需要高效运行,充分利用 MSPM0L1306 的性能,满足实时性需求。
  3. 可扩展性: 系统架构需要易于扩展,方便添加新的功能模块,适应不同的竞赛项目需求。
  4. 易维护性: 代码结构清晰,模块化设计,方便后期维护、调试和升级。
  5. 低功耗: 如果项目有低功耗要求,系统架构和代码需要考虑功耗优化。
  6. 实时性: 针对实时应用,系统架构需要支持实时任务调度和响应。

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

考虑到以上需求,特别是可靠性、高效性、可扩展性和实时性,我推荐采用 分层模块化架构 结合 实时操作系统 (RTOS) 的设计方案。这种架构能够有效地组织代码,提高代码的可重用性和可维护性,并为实时任务管理提供支持。

架构分层

我们将系统软件划分为以下几个层次:

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

    • 位于最底层,直接与 MSPM0L1306 硬件交互。
    • 封装 MSPM0L1306 的底层驱动,例如 GPIO、Timer、UART、SPI、I2C、ADC 等外设的寄存器操作。
    • 提供统一的、硬件无关的 API 接口给上层使用,屏蔽底层硬件差异,提高代码的可移植性。
    • 示例模块:hal_gpio.c, hal_timer.c, hal_uart.c, hal_spi.c, hal_i2c.c, hal_adc.c
  2. 板级支持包 (BSP - Board Support Package):

    • 位于 HAL 层之上,针对具体的 MSPM0L1306 开发板进行配置和初始化。
    • 包括时钟配置、GPIO 引脚配置、外设初始化、中断配置等板级相关的初始化工作。
    • 提供板级硬件资源的抽象,方便上层应用使用。
    • 示例模块:bsp.c, bsp_config.h
  3. 中间件层 (Middleware):

    • 位于 BSP 层之上,提供常用的软件组件和服务,例如:
      • 通信协议栈: 例如 UART 协议解析、Modbus 协议栈、MQTT 协议栈等。
      • 数据处理库: 例如滤波算法、数学运算库、数据结构库等。
      • 驱动框架: 例如传感器驱动框架、执行器驱动框架等。
      • 文件系统 (可选): 如果需要存储配置文件或数据,可以集成轻量级文件系统。
    • 中间件层可以极大地提高开发效率,减少重复造轮子。
    • 示例模块:protocol_uart.c, filter_kalman.c, data_queue.c, sensor_framework.c
  4. 应用层 (Application Layer):

    • 位于最顶层,实现具体的应用逻辑和功能。
    • 基于 HAL 层、BSP 层和中间件层提供的 API 接口,构建应用程序。
    • 应用层代码专注于业务逻辑,无需关心底层硬件细节。
    • 示例模块:app_sensor_task.c, app_motor_control.c, app_ui_display.c, main.c

模块化设计

在每一层内部,我们都采用模块化设计思想,将功能划分为独立的模块。每个模块负责特定的功能,模块之间通过定义清晰的接口进行交互。模块化设计的好处包括:

  • 高内聚、低耦合: 模块内部功能紧密相关,模块之间依赖性低,易于理解和维护。
  • 代码重用性高: 模块可以被多个项目或多个应用场景复用。
  • 易于测试和调试: 可以单独测试和调试每个模块,降低整体调试难度。
  • 团队协作效率高: 团队成员可以并行开发不同的模块,提高开发效率。

实时操作系统 (RTOS)

为了满足系统的实时性和任务管理需求,我们引入 RTOS。RTOS 能够提供以下关键功能:

  • 任务管理: 将应用程序划分为多个独立的任务 (线程),RTOS 负责任务的创建、删除、调度和切换。
  • 任务调度: RTOS 采用优先级调度算法,确保高优先级任务能够及时得到执行,满足实时性要求。
  • 任务同步与通信: RTOS 提供信号量、互斥锁、消息队列、事件标志组等机制,用于任务之间的同步和通信。
  • 资源管理: RTOS 管理系统资源,例如内存、外设等,避免资源冲突。

FreeRTOS 是一个流行的开源 RTOS,非常适合资源受限的嵌入式系统,并且 MSPM0L1306 芯片也能够很好地支持 FreeRTOS。因此,我推荐使用 FreeRTOS 作为本项目的 RTOS 选型。

开发流程

基于上述架构和技术选型,我们的嵌入式系统开发流程可以概括为以下步骤:

  1. 需求分析: 明确项目的功能需求、性能指标、可靠性要求、功耗要求等。
  2. 硬件设计 (硬件工程师负责): 根据需求选择合适的 MSPM0L1306 开发板,并进行外围电路设计,例如传感器接口、执行器驱动电路、通信接口等。
  3. 软件架构设计: 确定软件的整体架构,例如分层模块化架构 + RTOS,定义各层和各模块的功能和接口。
  4. HAL 层开发: 编写 MSPM0L1306 的 HAL 驱动,包括 GPIO、Timer、UART、SPI、I2C、ADC 等外设驱动。
  5. BSP 层开发: 编写板级支持包,配置时钟、GPIO、外设、中断等,初始化硬件环境。
  6. 中间件层开发: 根据项目需求,选择或开发合适的中间件组件,例如通信协议栈、数据处理库、驱动框架等。
  7. 应用层开发: 基于 HAL 层、BSP 层和中间件层提供的 API,编写应用程序代码,实现具体的功能逻辑。
  8. 集成测试: 将各个模块集成起来进行整体测试,验证系统功能是否符合需求。
  9. 系统优化: 针对性能瓶颈进行代码优化,提高系统效率。如果需要低功耗,进行功耗优化。
  10. 测试验证: 进行全面的功能测试、性能测试、可靠性测试等,确保系统满足设计要求。
  11. 维护升级: 提供系统维护和升级方案,方便后期维护和功能扩展。

C 代码实现 (部分示例,完整代码超过 3000 行)

为了演示上述架构,我将提供 MSPM0L1306 开发板的 C 代码示例,代码将涵盖 HAL 层、BSP 层、中间件层 (简单示例) 和应用层,并集成 FreeRTOS。

1. HAL 层 (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
// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>
#include <stdbool.h>

// GPIO 端口定义 (根据 MSPM0L1306 数据手册定义)
typedef enum {
GPIO_PORT_A,
GPIO_PORT_B,
// ... 其他端口
GPIO_PORT_MAX
} GPIO_Port_t;

// GPIO 引脚定义 (根据 MSPM0L1306 数据手册定义)
typedef enum {
GPIO_PIN_0 = (1 << 0),
GPIO_PIN_1 = (1 << 1),
GPIO_PIN_2 = (1 << 2),
GPIO_PIN_3 = (1 << 3),
GPIO_PIN_4 = (1 << 4),
GPIO_PIN_5 = (1 << 5),
GPIO_PIN_6 = (1 << 6),
GPIO_PIN_7 = (1 << 7),
GPIO_PIN_ALL = 0xFF
} GPIO_Pin_t;

// GPIO 方向定义
typedef enum {
GPIO_DIR_INPUT,
GPIO_DIR_OUTPUT
} GPIO_Dir_t;

// GPIO 输出类型定义
typedef enum {
GPIO_OTYPE_PUSH_PULL,
GPIO_OTYPE_OPEN_DRAIN
} GPIO_OutputType_t;

// GPIO 上下拉电阻定义
typedef enum {
GPIO_PUPD_NONE,
GPIO_PUPD_PULLUP,
GPIO_PUPD_PULLDOWN
} GPIO_PullUpDown_t;

// GPIO 初始化结构体
typedef struct {
GPIO_Port_t port;
GPIO_Pin_t pin;
GPIO_Dir_t direction;
GPIO_OutputType_t output_type;
GPIO_PullUpDown_t pull_up_down;
} GPIO_InitTypeDef;

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

// 设置 GPIO 输出电平
void HAL_GPIO_WritePin(GPIO_Port_t port, GPIO_Pin_t pin, bool PinState);

// 读取 GPIO 输入电平
bool HAL_GPIO_ReadPin(GPIO_Port_t port, GPIO_Pin_t pin);

// 切换 GPIO 输出电平
void HAL_GPIO_TogglePin(GPIO_Port_t port, GPIO_Pin_t pin);

#endif // 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
// hal_gpio.c
#include "hal_gpio.h"
#include "mspm0l130x.h" // 包含 MSPM0L1306 的头文件 (需要根据实际 SDK 路径调整)

void HAL_GPIO_Init(GPIO_InitTypeDef *GPIO_InitStruct) {
// 根据 GPIO_InitStruct 配置 MSPM0L1306 的 GPIO 寄存器
// 具体寄存器操作需要参考 MSPM0L1306 数据手册和 SDK 文档

// 示例 (假设 MSPM0L1306 使用 GPIO 模块 A 和 B)
if (GPIO_InitStruct->port == GPIO_PORT_A) {
// 使能 GPIO 模块 A 时钟 (如果需要)
// ...

// 配置 GPIO 方向
if (GPIO_InitStruct->direction == GPIO_DIR_OUTPUT) {
GPIOA->DIR |= GPIO_InitStruct->pin; // 设置为输出
} else {
GPIOA->DIR &= ~GPIO_InitStruct->pin; // 设置为输入
}

// 配置输出类型
if (GPIO_InitStruct->output_type == GPIO_OTYPE_OPEN_DRAIN) {
GPIOA->OD |= GPIO_InitStruct->pin; // 设置为开漏输出
} else {
GPIOA->OD &= ~GPIO_InitStruct->pin; // 设置为推挽输出
}

// 配置上下拉电阻
if (GPIO_InitStruct->pull_up_down == GPIO_PUPD_PULLUP) {
GPIOA->PU |= GPIO_InitStruct->pin; // 使能上拉
GPIOA->PD &= ~GPIO_InitStruct->pin; // 禁用下拉
} else if (GPIO_InitStruct->pull_up_down == GPIO_PUPD_PULLDOWN) {
GPIOA->PD |= GPIO_InitStruct->pin; // 使能下拉
GPIOA->PU &= ~GPIO_InitStruct->pin; // 禁用上拉
} else {
GPIOA->PU &= ~GPIO_InitStruct->pin; // 禁用上拉
GPIOA->PD &= ~GPIO_InitStruct->pin; // 禁用下拉
}
} else if (GPIO_InitStruct->port == GPIO_PORT_B) {
// 类似 GPIO_PORT_A 的配置,操作 GPIOB 寄存器
// ...
}
// ... 其他端口配置
}

void HAL_GPIO_WritePin(GPIO_Port_t port, GPIO_Pin_t pin, bool PinState) {
if (port == GPIO_PORT_A) {
if (PinState) {
GPIOA->OUT |= pin; // 设置为高电平
} else {
GPIOA->OUT &= ~pin; // 设置为低电平
}
} else if (port == GPIO_PORT_B) {
// 类似 GPIO_PORT_A 的操作,操作 GPIOB 寄存器
// ...
}
// ... 其他端口操作
}

bool HAL_GPIO_ReadPin(GPIO_Port_t port, GPIO_Pin_t pin) {
if (port == GPIO_PORT_A) {
return (GPIOA->IN & pin) ? true : false; // 读取输入电平
} else if (port == GPIO_PORT_B) {
// 类似 GPIO_PORT_A 的操作,操作 GPIOB 寄存器
// ...
}
// ... 其他端口操作
return false; // 默认返回 false
}

void HAL_GPIO_TogglePin(GPIO_Port_t port, GPIO_Pin_t pin) {
if (port == GPIO_PORT_A) {
GPIOA->OUT ^= pin; // 翻转输出电平
} else if (port == GPIO_PORT_B) {
// 类似 GPIO_PORT_A 的操作,操作 GPIOB 寄存器
// ...
}
// ... 其他端口操作
}

2. BSP 层 (bsp.h, bsp.c, bsp_config.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
// bsp.h
#ifndef BSP_H
#define BSP_H

#include <stdint.h>

// 板载 LED 定义 (根据开发板硬件连接确定)
#define LED_RED_PORT GPIO_PORT_A
#define LED_RED_PIN GPIO_PIN_0
#define LED_GREEN_PORT GPIO_PORT_A
#define LED_GREEN_PIN GPIO_PIN_1

// 板载按键定义 (根据开发板硬件连接确定)
#define KEY_USER_PORT GPIO_PORT_B
#define KEY_USER_PIN GPIO_PIN_0

// 初始化系统时钟
void BSP_SystemClock_Init(void);

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

// 控制板载红色 LED
void BSP_LED_Red_Control(bool on);

// 控制板载绿色 LED
void BSP_LED_Green_Control(bool on);

// 初始化板载按键
void BSP_Key_Init(void);

// 读取用户按键状态
bool BSP_Key_GetState(void);

#endif // BSP_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
// bsp.c
#include "bsp.h"
#include "hal_gpio.h"
#include "mspm0l130x.h" // 包含 MSPM0L1306 的头文件

// 系统时钟初始化 (需要根据 MSPM0L1306 的时钟系统配置)
void BSP_SystemClock_Init(void) {
// 配置 MSPM0L1306 的时钟源、分频系数等
// 具体配置需要参考 MSPM0L1306 数据手册和 SDK 文档

// 示例 (假设使用内部高速振荡器 HSI,并配置系统时钟为 48MHz)
// 使能 HSI
// ...
// 配置 PLL (如果需要倍频)
// ...
// 选择系统时钟源为 PLL 或 HSI
// ...
// 配置 AHB、APB 总线时钟分频系数
// ...

SystemCoreClockUpdate(); // 更新 SystemCoreClock 变量 (用于 SysTick 等外设)
}

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

// 初始化红色 LED
GPIO_InitStruct.port = LED_RED_PORT;
GPIO_InitStruct.pin = LED_RED_PIN;
GPIO_InitStruct.direction = GPIO_DIR_OUTPUT;
GPIO_InitStruct.output_type = GPIO_OTYPE_PUSH_PULL;
GPIO_InitStruct.pull_up_down = GPIO_PUPD_NONE;
HAL_GPIO_Init(&GPIO_InitStruct);
BSP_LED_Red_Control(false); // 初始状态熄灭

// 初始化绿色 LED
GPIO_InitStruct.port = LED_GREEN_PORT;
GPIO_InitStruct.pin = LED_GREEN_PIN;
GPIO_InitStruct.direction = GPIO_DIR_OUTPUT;
GPIO_InitStruct.output_type = GPIO_OTYPE_PUSH_PULL;
GPIO_InitStruct.pull_up_down = GPIO_PUPD_NONE;
HAL_GPIO_Init(&GPIO_InitStruct);
BSP_LED_Green_Control(false); // 初始状态熄灭
}

// 控制板载红色 LED
void BSP_LED_Red_Control(bool on) {
HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, on);
}

// 控制板载绿色 LED
void BSP_LED_Green_Control(bool on) {
HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, on);
}

// 初始化板载按键
void BSP_Key_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.port = KEY_USER_PORT;
GPIO_InitStruct.pin = KEY_USER_PIN;
GPIO_InitStruct.direction = GPIO_DIR_INPUT;
GPIO_InitStruct.output_type = GPIO_OTYPE_PUSH_PULL; // 输入模式下此项无效
GPIO_InitStruct.pull_up_down = GPIO_PUPD_PULLUP; // 使用上拉电阻,默认高电平
HAL_GPIO_Init(&GPIO_InitStruct);
}

// 读取用户按键状态
bool BSP_Key_GetState(void) {
return !HAL_GPIO_ReadPin(KEY_USER_PORT, KEY_USER_PIN); // 按键按下时为低电平,取反
}
1
2
3
4
5
6
7
8
9
// bsp_config.h
// 此文件可以用于存放板级配置信息,例如外设引脚定义、时钟配置参数等
// 方便集中管理和修改板级配置
#ifndef BSP_CONFIG_H
#define BSP_CONFIG_H

// ... 板级配置宏定义 ...

#endif // BSP_CONFIG_H

3. 中间件层 (protocol_uart.h, protocol_uart.c) - 简单 UART 协议示例

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
// protocol_uart.h
#ifndef PROTOCOL_UART_H
#define PROTOCOL_UART_H

#include <stdint.h>
#include <stdbool.h>

// UART 协议帧结构体 (示例)
typedef struct {
uint8_t start_byte; // 起始字节
uint8_t command_id; // 命令 ID
uint8_t data_length; // 数据长度
uint8_t data[20]; // 数据 (最大 20 字节)
uint8_t checksum; // 校验和
uint8_t end_byte; // 结束字节
} UART_Frame_t;

// 初始化 UART 协议模块
void Protocol_UART_Init(void);

// 发送 UART 协议帧
bool Protocol_UART_SendFrame(UART_Frame_t *frame);

// 接收 UART 协议帧 (非阻塞方式,需要轮询调用)
bool Protocol_UART_ReceiveFrame(UART_Frame_t *frame);

#endif // PROTOCOL_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
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
// protocol_uart.c
#include "protocol_uart.h"
#include "hal_uart.h" // 假设有 HAL UART 驱动

// 初始化 UART 协议模块
void Protocol_UART_Init(void) {
// 初始化 HAL UART 驱动 (需要根据 MSPM0L1306 的 UART 外设配置)
// HAL_UART_Init();
}

// 发送 UART 协议帧
bool Protocol_UART_SendFrame(UART_Frame_t *frame) {
// 组装 UART 协议帧,计算校验和
// ...

// 使用 HAL UART 驱动发送数据
// HAL_UART_SendData(frame->start_byte);
// HAL_UART_SendData(frame->command_id);
// HAL_UART_SendData(frame->data_length);
// HAL_UART_SendData(frame->data, frame->data_length);
// HAL_UART_SendData(frame->checksum);
// HAL_UART_SendData(frame->end_byte);

return true; // 假设发送成功
}

// 接收 UART 协议帧 (非阻塞方式,需要轮询调用)
bool Protocol_UART_ReceiveFrame(UART_Frame_t *frame) {
static uint8_t rx_state = 0; // 接收状态机
static uint8_t rx_buffer[sizeof(UART_Frame_t)];
static uint8_t rx_index = 0;

// 使用 HAL UART 驱动接收数据 (非阻塞方式)
// if (HAL_UART_ReceiveDataAvailable()) {
// uint8_t data = HAL_UART_ReceiveData();

// switch (rx_state) {
// case 0: // 等待起始字节
// if (data == FRAME_START_BYTE) { // 假设 FRAME_START_BYTE 是起始字节宏定义
// rx_buffer[rx_index++] = data;
// rx_state = 1;
// }
// break;
// case 1: // 接收命令 ID
// rx_buffer[rx_index++] = data;
// rx_state = 2;
// break;
// case 2: // 接收数据长度
// rx_buffer[rx_index++] = data;
// rx_state = 3;
// break;
// case 3: // 接收数据
// rx_buffer[rx_index++] = data;
// if (rx_index >= sizeof(UART_Frame_t) - 2) { // 接收完数据和校验和、结束字节
// rx_state = 4;
// }
// break;
// case 4: // 接收校验和
// rx_buffer[rx_index++] = data;
// rx_state = 5;
// break;
// case 5: // 接收结束字节
// rx_buffer[rx_index++] = data;
// rx_state = 0; // 复位状态机
// rx_index = 0;

// // 校验数据
// if (rx_buffer[sizeof(UART_Frame_t) - 1] == FRAME_END_BYTE && // 假设 FRAME_END_BYTE 是结束字节宏定义
// CalculateChecksum(rx_buffer, sizeof(UART_Frame_t) - 2) == rx_buffer[sizeof(UART_Frame_t) - 2]) { // 假设 CalculateChecksum 是校验和计算函数
// // 数据校验成功,将接收到的数据拷贝到 frame 结构体
// memcpy(frame, rx_buffer, sizeof(UART_Frame_t));
// return true; // 接收成功
// } else {
// // 数据校验失败
// return false; // 接收失败
// }
// break;
// default:
// rx_state = 0; // 状态错误,复位状态机
// rx_index = 0;
// break;
// }
// }

return false; // 没有接收到完整帧
}

4. 应用层 (app_led_task.c, app_key_task.c, 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
// app_led_task.c
#include "app_led_task.h"
#include "bsp.h"
#include "FreeRTOS.h"
#include "task.h"

// LED 闪烁任务
void LED_Task(void *pvParameters) {
TickType_t xLastWakeTime;
const TickType_t xFrequency = pdMS_TO_TICKS(500); // 500ms 周期

xLastWakeTime = xTaskGetTickCount();

while (1) {
BSP_LED_Red_Control(true); // 点亮红色 LED
vTaskDelayUntil(&xLastWakeTime, xFrequency); // 延时 500ms

BSP_LED_Red_Control(false); // 熄灭红色 LED
vTaskDelayUntil(&xLastWakeTime, xFrequency); // 延时 500ms
}
}

// 创建 LED 任务
void App_LED_Task_Create(void) {
xTaskCreate(LED_Task, // 任务函数
"LED Task", // 任务名称
128, // 任务堆栈大小 (需要根据实际情况调整)
NULL, // 任务参数
1, // 任务优先级 (可以根据实际情况调整)
NULL); // 任务句柄 (不需要句柄时可以为 NULL)
}
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
// app_key_task.c
#include "app_key_task.h"
#include "bsp.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h" // FreeRTOS 消息队列头文件

// 按键事件类型
typedef enum {
KEY_EVENT_PRESS,
KEY_EVENT_RELEASE
} KeyEvent_t;

// 按键事件消息队列句柄
QueueHandle_t xKeyEventQueue;

// 按键扫描任务
void Key_Scan_Task(void *pvParameters) {
TickType_t xLastWakeTime;
const TickType_t xFrequency = pdMS_TO_TICKS(20); // 20ms 扫描周期
bool last_key_state = false; // 上一次按键状态

xLastWakeTime = xTaskGetTickCount();

while (1) {
bool current_key_state = BSP_Key_GetState(); // 读取当前按键状态

if (current_key_state != last_key_state) { // 按键状态发生变化
if (current_key_state == true) { // 按键按下
KeyEvent_t key_event = KEY_EVENT_PRESS;
xQueueSend(xKeyEventQueue, &key_event, 0); // 发送按键按下事件到消息队列
} else { // 按键释放
KeyEvent_t key_event = KEY_EVENT_RELEASE;
xQueueSend(xKeyEventQueue, &key_event, 0); // 发送按键释放事件到消息队列
}
last_key_state = current_key_state; // 更新上一次按键状态
}

vTaskDelayUntil(&xLastWakeTime, xFrequency); // 延时 20ms
}
}

// 按键处理任务
void Key_Process_Task(void *pvParameters) {
KeyEvent_t key_event;

while (1) {
if (xQueueReceive(xKeyEventQueue, &key_event, portMAX_DELAY) == pdTRUE) { // 从消息队列接收按键事件,阻塞等待
if (key_event == KEY_EVENT_PRESS) {
BSP_LED_Green_Control(true); // 按下按键,点亮绿色 LED
// ... 处理按键按下事件 ...
} else if (key_event == KEY_EVENT_RELEASE) {
BSP_LED_Green_Control(false); // 释放按键,熄灭绿色 LED
// ... 处理按键释放事件 ...
}
}
}
}

// 创建按键相关任务和消息队列
void App_Key_Task_Create(void) {
// 创建按键事件消息队列
xKeyEventQueue = xQueueCreate(5, sizeof(KeyEvent_t)); // 队列长度为 5,消息大小为 KeyEvent_t
if (xKeyEventQueue == NULL) {
// 队列创建失败处理
return;
}

// 创建按键扫描任务
xTaskCreate(Key_Scan_Task, // 任务函数
"Key Scan Task", // 任务名称
128, // 任务堆栈大小
NULL, // 任务参数
2, // 任务优先级 (比 LED 任务优先级高)
NULL); // 任务句柄

// 创建按键处理任务
xTaskCreate(Key_Process_Task, // 任务函数
"Key Process Task", // 任务名称
128, // 任务堆栈大小
NULL, // 任务参数
1, // 任务优先级
NULL); // 任务句柄
}
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
// main.c
#include "mspm0l130x.h"
#include "bsp.h"
#include "app_led_task.h"
#include "app_key_task.h"
#include "FreeRTOS.h"
#include "task.h"

int main(void) {
// 初始化系统时钟
BSP_SystemClock_Init();

// 初始化板载 LED
BSP_LED_Init();

// 初始化板载按键
BSP_Key_Init();

// 创建 LED 任务
App_LED_Task_Create();

// 创建按键相关任务
App_Key_Task_Create();

// 启动 FreeRTOS 任务调度器
vTaskStartScheduler();

// 理论上不会运行到这里,因为任务调度器会接管控制权
while (1) {
}
}

// FreeRTOS 钩子函数 (可选,用于调试和错误处理)
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
// 堆栈溢出错误处理
while (1);
}

void vApplicationMallocFailedHook(void) {
// 内存分配失败错误处理
while (1);
}

代码说明:

  • HAL 层: 提供了 GPIO 的基本操作函数,例如初始化、读写、切换电平等。实际项目中需要根据 MSPM0L1306 的数据手册和 SDK 完善其他外设的 HAL 驱动。
  • BSP 层: 对板载硬件资源进行了抽象和初始化,例如 LED 和按键。BSP_SystemClock_Init() 函数需要根据 MSPM0L1306 的时钟系统进行详细配置。
  • 中间件层: protocol_uart.c/h 提供了一个简单的 UART 协议示例,实际项目中可以根据需要选择或开发更复杂的中间件组件。
  • 应用层:
    • app_led_task.c 创建了一个 LED 闪烁任务,演示了 FreeRTOS 任务的创建和延时功能。
    • app_key_task.c 创建了按键扫描任务和按键处理任务,使用了 FreeRTOS 消息队列进行任务间通信,实现了按键按下点亮绿色 LED 的功能。
    • main.c 是主函数,初始化 BSP,创建 LED 任务和按键任务,然后启动 FreeRTOS 任务调度器。
  • FreeRTOS 集成: 代码中包含了 FreeRTOS 的头文件,并使用了 FreeRTOS 的 API 函数,例如 xTaskCreate(), vTaskDelayUntil(), xQueueCreate(), xQueueSend(), xQueueReceive(), vTaskStartScheduler() 等。
  • 错误处理: vApplicationStackOverflowHook()vApplicationMallocFailedHook() 是 FreeRTOS 的钩子函数,用于处理堆栈溢出和内存分配失败等错误情况。

技术和方法实践

在本项目开发过程中,我们将采用以下技术和方法,这些都是经过实践验证的有效方法:

  1. 开发工具:

    • IDE: 推荐使用 **Code Composer Studio (CCS)**,这是 TI 官方推荐的 MSPM0 系列芯片的开发 IDE,提供了完善的编译、调试和仿真功能。
    • 编译器: CCS 自带 TI 编译器,也可以使用 GCC 等其他编译器。
    • 调试器: CCS 集成了 J-Link 或 XDS 系列调试器,可以进行硬件调试。
    • 版本控制: 使用 Git 进行代码版本控制,方便代码管理、团队协作和版本回溯。
    • 文档工具: 使用 Doxygen 等工具自动生成代码文档,提高代码可读性和可维护性。
  2. 编码规范:

    • 遵循 MISRA CGoogle C++ Style Guide 等编码规范,提高代码质量和可读性。
    • 代码注释清晰、完整,解释代码的功能和逻辑。
    • 变量和函数命名规范,易于理解其含义。
    • 避免使用魔术数字,使用宏定义或枚举代替。
    • 模块化设计,高内聚、低耦合。
  3. 测试与验证:

    • 单元测试: 针对每个模块进行单元测试,验证模块功能的正确性。可以使用 CMockaUnity 等单元测试框架。
    • 集成测试: 将各个模块集成起来进行整体测试,验证模块之间的协同工作是否正常。
    • 硬件在环测试 (HIL - Hardware-in-the-Loop): 使用仿真器或实际硬件进行系统级测试,验证系统在真实硬件环境下的运行情况。
    • 代码审查: 进行代码审查,及时发现代码中的潜在问题和错误。
  4. 调试技术:

    • 在线调试: 使用 CCS 调试器进行在线调试,单步调试、断点调试、查看变量值等。
    • 日志输出: 在代码中添加日志输出,记录程序运行状态和错误信息,方便问题定位。可以使用 UART 或其他方式输出日志。
    • 示波器/逻辑分析仪: 使用示波器或逻辑分析仪观察硬件信号,例如 GPIO 电平、总线波形等,辅助硬件问题排查。
    • 静态代码分析: 使用 CoverityPVS-Studio 等静态代码分析工具,检测代码中的潜在缺陷和安全漏洞。
  5. 低功耗设计 (如果需要):

    • 时钟管理: 根据系统需求动态调整时钟频率,降低功耗。
    • 电源管理: 使用 MSPM0L1306 的低功耗模式 (例如 Sleep 模式、Deep Sleep 模式),在系统空闲时进入低功耗模式。
    • 外设管理: 关闭不使用的外设时钟和电源。
    • 代码优化: 优化代码执行效率,减少 CPU 运行时间。
    • 低功耗外设: 选择低功耗的传感器、执行器和通信模块。
  6. 维护与升级:

    • 模块化设计: 模块化设计方便后期功能扩展和代码维护。
    • 版本控制: 使用 Git 进行版本控制,方便版本管理和升级回滚。
    • 固件升级方案: 设计可靠的固件升级方案,例如 OTA (Over-The-Air) 升级或通过 UART/USB 接口升级。
    • 文档维护: 及时更新代码文档和设计文档,方便后期维护和升级。

总结

本项目基于 MSPM0L1306 开发板,采用 分层模块化架构 + FreeRTOS 的软件设计方案,能够构建一个可靠、高效、可扩展的嵌入式系统平台。 代码示例涵盖了 HAL 层、BSP 层、中间件层和应用层,并集成了 FreeRTOS。同时,我们还详细介绍了在项目开发过程中需要采用的关键技术和方法,包括开发工具、编码规范、测试验证、调试技术、低功耗设计以及维护升级等方面。

这个架构和代码示例提供了一个良好的起点,你可以根据具体的项目需求进行扩展和完善。 实际项目中,代码量会远超 3000 行,涵盖更完善的 HAL 驱动、更丰富的中间件组件、更复杂的应用逻辑以及更全面的测试和验证代码。 希望这份详细的方案和代码示例能够帮助你更好地进行嵌入式系统开发。

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