编程技术分享

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

0%

简介:本项目基于AI8051U芯片设计了两款开发板,一款是QFP48开发板,小巧玲珑,小才多智;另一款是DIP40拓展学习板,具有模块化插接灵活拓展能力,可堪大用。

好的,作为一名高级嵌入式软件开发工程师,我非常乐意为您详细阐述基于AI8051U芯片的嵌入式系统开发,并提供一套可靠、高效、可扩展的代码设计架构和具体的C代码实现。
关注微信公众号,提前获取相关推文

项目背景与需求分析

本项目旨在展示一个完整的嵌入式系统开发流程,从需求分析、系统设计、代码实现、测试验证到维护升级。基于AI8051U芯片,我们设计了两款开发板:

  • QFP48开发板(小才多智): 小巧精致,适用于快速原型验证、小型嵌入式应用开发,以及作为教学和学习的入门平台。其紧凑的设计方便集成到各种小型设备中。

  • DIP40拓展学习板(可堪大用): 采用DIP40封装,具有模块化插接能力,方便用户灵活扩展各种功能模块,例如传感器模块、通信模块、显示模块等。适用于教学、实验、项目开发,能够构建更复杂的嵌入式系统。

项目目标:

  1. 可靠性: 系统必须稳定可靠运行,能够长时间无故障工作,并具备一定的容错能力。
  2. 高效性: 代码执行效率高,资源利用率高,响应速度快,满足实时性要求。
  3. 可扩展性: 系统架构设计灵活,易于扩展新功能和模块,适应未来需求变化。
  4. 可维护性: 代码结构清晰,模块化设计,注释详尽,方便后期维护和升级。
  5. 易学习性: 代码结构和设计思想易于理解,方便初学者学习和掌握嵌入式系统开发技术。

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

为了实现上述目标,我推荐采用分层模块化架构。这种架构将系统划分为不同的层次和模块,每个层次和模块负责特定的功能,层次之间通过明确的接口进行通信。这种架构具有以下优点:

  • 高内聚低耦合: 每个模块内部功能高度相关,模块之间依赖性低,降低了模块间的相互影响,提高了代码的独立性和可维护性。
  • 易于理解和维护: 分层结构使得系统逻辑更加清晰,易于理解和维护。每个模块的代码量相对较小,更易于管理。
  • 代码复用性高: 模块化的设计使得代码复用性更高,可以将通用的功能模块在不同的项目中重复使用,减少开发工作量。
  • 易于扩展和升级: 当需要添加新功能或升级现有功能时,只需要修改或添加相应的模块,而不会对其他模块产生重大影响,降低了系统升级的风险和成本。
  • 硬件抽象: 通过硬件抽象层(HAL),将硬件相关的操作封装起来,使得上层应用代码可以独立于具体的硬件平台,提高了代码的可移植性。

分层架构具体划分:

基于嵌入式系统的特点和AI8051U芯片的资源限制,我们建议将系统划分为以下几个层次:

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

    • 功能: 直接与硬件交互,封装底层硬件操作,向上层提供统一的硬件访问接口。
    • 模块: GPIO驱动、定时器驱动、UART驱动、SPI驱动、I2C驱动、ADC驱动、DAC驱动、中断管理、Flash驱动、看门狗驱动等。
    • 特点: 与具体的硬件平台紧密相关,代码可移植性较差,但上层代码无需关心硬件细节。
  2. 板级支持包 (BSP - Board Support Package):

    • 功能: 针对具体的开发板(QFP48或DIP40)进行初始化配置,包括时钟配置、外设初始化、引脚配置等。
    • 模块: 时钟初始化、GPIO端口初始化、UART端口初始化、定时器初始化、中断向量表配置、系统启动代码等。
    • 特点: 与具体的开发板硬件相关,代码可移植性较差,但为上层应用提供了统一的硬件平台。
  3. 操作系统层 (OS - Operating System Layer) (可选,但本项目为了展示完整性,我们采用简化版的任务调度器):

    • 功能: 提供任务调度、资源管理、任务间通信等功能,提高系统的并发性和实时性。(对于资源有限的8051,可以采用简单的任务调度器,或者在简单应用中直接采用时间片轮询的方式)
    • 模块: 任务管理、任务调度、时间管理、互斥锁、信号量、消息队列(简化版)等。
    • 特点: 提高系统资源利用率和响应速度,简化多任务并发编程。
  4. 中间件层 (Middleware Layer) (可选,根据项目需求添加):

    • 功能: 提供一些通用的服务和功能,例如数据处理、通信协议栈、图形界面库等。
    • 模块: 数据解析模块、数据加密模块、Modbus协议栈、MQTT协议栈、GUI库(例如uGUI、emWin简化版)等。
    • 特点: 提高开发效率,减少重复开发,提供更高级的功能。
  5. 应用层 (Application Layer):

    • 功能: 实现具体的应用逻辑,例如数据采集、数据处理、用户界面、控制算法等。
    • 模块: 传感器数据采集模块、数据处理算法模块、显示界面模块、用户交互模块、控制逻辑模块等。
    • 特点: 根据具体的应用需求进行定制开发,是系统的核心部分。

代码实现 (C语言)

由于篇幅限制,我无法提供3000行完整的代码,但我将分层次、分模块地提供关键代码片段,并详细解释代码的设计思路和实现方法。您可以将这些代码片段组合起来,构建一个完整的嵌入式系统项目。

1. 硬件抽象层 (HAL)

  • GPIO 驱动 (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
// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>

// 定义GPIO端口和引脚
typedef enum {
GPIO_PORT_P0,
GPIO_PORT_P1,
GPIO_PORT_P2,
GPIO_PORT_P3
} GPIO_PortTypeDef;

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_PinTypeDef;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT_PP, // 推挽输出
GPIO_MODE_OUTPUT_OD // 开漏输出
// ... 可以根据需要添加其他模式
} GPIO_ModeTypeDef;

typedef enum {
GPIO_SPEED_LOW,
GPIO_SPEED_MEDIUM,
GPIO_SPEED_HIGH
// ... 可以根据需要添加其他速度等级
} GPIO_SpeedTypeDef;

// 初始化GPIO引脚
void HAL_GPIO_Init(GPIO_PortTypeDef port, GPIO_PinTypeDef pin, GPIO_ModeTypeDef mode, GPIO_SpeedTypeDef speed);

// 设置GPIO引脚输出高电平
void HAL_GPIO_SetPinHigh(GPIO_PortTypeDef port, GPIO_PinTypeDef pin);

// 设置GPIO引脚输出低电平
void HAL_GPIO_SetPinLow(GPIO_PortTypeDef port, GPIO_PinTypeDef pin);

// 读取GPIO引脚输入电平
uint8_t HAL_GPIO_ReadPin(GPIO_PortTypeDef port, GPIO_PinTypeDef pin);

// 切换GPIO引脚输出电平
void HAL_GPIO_TogglePin(GPIO_PortTypeDef port, GPIO_PinTypeDef 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
80
81
82
83
// hal_gpio.c
#include "hal_gpio.h"
#include <AI8051U.h> // 包含AI8051U头文件,根据实际芯片选择

// 初始化GPIO引脚
void HAL_GPIO_Init(GPIO_PortTypeDef port, GPIO_PinTypeDef pin, GPIO_ModeTypeDef mode, GPIO_SpeedTypeDef speed) {
volatile unsigned char *port_reg = NULL;
volatile unsigned char *mode_reg = NULL; // 例如:PnM0, PnM1 寄存器控制模式

switch (port) {
case GPIO_PORT_P0: port_reg = &P0; /* mode_reg = &P0M0; */ break; // 假设P0没有模式配置寄存器,或者默认模式即可
case GPIO_PORT_P1: port_reg = &P1; /* mode_reg = &P1M0; */ break;
case GPIO_PORT_P2: port_reg = &P2; /* mode_reg = &P2M0; */ break;
case GPIO_PORT_P3: port_reg = &P3; /* mode_reg = &P3M0; */ break;
default: return; // 错误端口
}

// 配置GPIO模式 (需要根据AI8051U的具体寄存器配置)
if (mode == GPIO_MODE_OUTPUT_PP) {
// 配置为推挽输出模式 (具体配置需要查阅芯片手册)
// 例如: *mode_reg &= ~(pin); // 配置为推挽输出
} else if (mode == GPIO_MODE_OUTPUT_OD) {
// 配置为开漏输出模式
// 例如: *mode_reg |= (pin); // 配置为开漏输出
} else if (mode == GPIO_MODE_INPUT) {
// 配置为输入模式 (通常默认就是输入,无需特殊配置)
}

// 可以根据 speed 参数配置GPIO速度,如果AI8051U支持的话
(void)speed; // 避免编译警告,如果AI8051U不支持速度配置,则忽略 speed 参数
}

// 设置GPIO引脚输出高电平
void HAL_GPIO_SetPinHigh(GPIO_PortTypeDef port, GPIO_PinTypeDef pin) {
volatile unsigned char *port_reg = NULL;
switch (port) {
case GPIO_PORT_P0: port_reg = &P0; break;
case GPIO_PORT_P1: port_reg = &P1; break;
case GPIO_PORT_P2: port_reg = &P2; break;
case GPIO_PORT_P3: port_reg = &P3; break;
default: return;
}
*port_reg |= pin; // 设置对应位为1,输出高电平
}

// 设置GPIO引脚输出低电平
void HAL_GPIO_SetPinLow(GPIO_PortTypeDef port, GPIO_PinTypeDef pin) {
volatile unsigned char *port_reg = NULL;
switch (port) {
case GPIO_PORT_P0: port_reg = &P0; break;
case GPIO_PORT_P1: port_reg = &P1; break;
case GPIO_PORT_P2: port_reg = &P2; break;
case GPIO_PORT_P3: port_reg = &P3; break;
default: return;
}
*port_reg &= ~(pin); // 设置对应位为0,输出低电平
}

// 读取GPIO引脚输入电平
uint8_t HAL_GPIO_ReadPin(GPIO_PortTypeDef port, GPIO_PinTypeDef pin) {
volatile unsigned char *port_reg = NULL;
switch (port) {
case GPIO_PORT_P0: port_reg = &P0; break;
case GPIO_PORT_P1: port_reg = &P1; break;
case GPIO_PORT_P2: port_reg = &P2; break;
case GPIO_PORT_P3: port_reg = &P3; break;
default: return 0; // 错误端口,返回默认值
}
return (*port_reg & pin) ? 1 : 0; // 读取对应位,返回1或0
}

// 切换GPIO引脚输出电平
void HAL_GPIO_TogglePin(GPIO_PortTypeDef port, GPIO_PinTypeDef pin) {
volatile unsigned char *port_reg = NULL;
switch (port) {
case GPIO_PORT_P0: port_reg = &P0; break;
case GPIO_PORT_P1: port_reg = &P1; break;
case GPIO_PORT_P2: port_reg = &P2; break;
case GPIO_PORT_P3: port_reg = &P3; break;
default: return;
}
*port_reg ^= pin; // 异或操作,切换电平
}
  • 定时器驱动 (hal_timer.h / hal_timer.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
// hal_timer.h
#ifndef HAL_TIMER_H
#define HAL_TIMER_H

#include <stdint.h>

// 定义定时器通道 (根据AI8051U的定时器资源定义)
typedef enum {
TIMER_CHANNEL_0,
TIMER_CHANNEL_1,
TIMER_CHANNEL_2
// ... 根据实际情况添加
} TIMER_ChannelTypeDef;

typedef enum {
TIMER_MODE_TIMER, // 定时器模式
TIMER_MODE_COUNTER // 计数器模式
} TIMER_ModeTypeDef;

typedef enum {
TIMER_PRESCALER_1, // 1分频
TIMER_PRESCALER_8, // 8分频
TIMER_PRESCALER_12, // 12分频 (示例,根据实际芯片支持的分频系数定义)
TIMER_PRESCALER_16, // 16分频
TIMER_PRESCALER_32,
TIMER_PRESCALER_64,
TIMER_PRESCALER_128,
TIMER_PRESCALER_256
// ... 根据实际情况添加
} TIMER_PrescalerTypeDef;

typedef void (*TIMER_CallbackTypeDef)(void); // 定时器回调函数类型定义

// 初始化定时器
void HAL_TIMER_Init(TIMER_ChannelTypeDef channel, TIMER_ModeTypeDef mode,
TIMER_PrescalerTypeDef prescaler, uint16_t reload_value);

// 启动定时器
void HAL_TIMER_Start(TIMER_ChannelTypeDef channel);

// 停止定时器
void HAL_TIMER_Stop(TIMER_ChannelTypeDef channel);

// 设置定时器重载值
void HAL_TIMER_SetReloadValue(TIMER_ChannelTypeDef channel, uint16_t reload_value);

// 获取定时器当前计数值
uint16_t HAL_TIMER_GetCounterValue(TIMER_ChannelTypeDef channel);

// 使能定时器中断
void HAL_TIMER_EnableInterrupt(TIMER_ChannelTypeDef channel, TIMER_CallbackTypeDef callback);

// 禁用定时器中断
void HAL_TIMER_DisableInterrupt(TIMER_ChannelTypeDef channel);

#endif // HAL_TIMER_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
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
// hal_timer.c
#include "hal_timer.h"
#include <AI8051U.h> // 包含AI8051U头文件

// 定时器回调函数数组 (用于存储每个定时器通道的回调函数)
static TIMER_CallbackTypeDef timer_callbacks[3] = {NULL, NULL, NULL}; // 假设最多3个定时器

// 初始化定时器
void HAL_TIMER_Init(TIMER_ChannelTypeDef channel, TIMER_ModeTypeDef mode,
TIMER_PrescalerTypeDef prescaler, uint16_t reload_value) {
// 根据 channel 选择定时器寄存器组 (例如 TMOD, TCON, TLx, THx)
// 根据 mode 配置定时器模式 (例如 TMOD 寄存器)
// 根据 prescaler 配置分频系数 (例如 TMOD 或其他相关寄存器)
// 设置定时器重载值 (写入 TLx 和 THx 寄存器)

// 示例 (需要根据AI8051U的具体寄存器和位定义修改)
switch (channel) {
case TIMER_CHANNEL_0:
TMOD &= ~0x03; // 清零 Timer 0 模式位
if (mode == TIMER_MODE_TIMER) {
TMOD |= 0x01; // Timer 0 模式 1 (16位定时器)
} else if (mode == TIMER_MODE_COUNTER) {
TMOD |= 0x05; // Timer 0 模式 1 (16位计数器)
}
// 配置分频 (示例:假设使用 T2CON 寄存器配置 Timer 0 分频)
if (prescaler == TIMER_PRESCALER_1) {
// T2CON &= ~0x0F; // 清零分频位
// T2CON |= 0x00; // 1 分频
} else if (prescaler == TIMER_PRESCALER_8) {
// T2CON &= ~0x0F;
// T2CON |= 0x03; // 8 分频
} // ... 其他分频配置
TH0 = (uint8_t)(reload_value >> 8); // 设置高8位重载值
TL0 = (uint8_t)(reload_value); // 设置低8位重载值
break;
case TIMER_CHANNEL_1:
// ... Timer 1 初始化配置 (类似 Timer 0)
break;
case TIMER_CHANNEL_2:
// ... Timer 2 初始化配置 (如果AI8051U有 Timer 2)
break;
default: return; // 错误通道
}
}

// 启动定时器
void HAL_TIMER_Start(TIMER_ChannelTypeDef channel) {
switch (channel) {
case TIMER_CHANNEL_0: TR0 = 1; break; // 启动 Timer 0
case TIMER_CHANNEL_1: TR1 = 1; break; // 启动 Timer 1
case TIMER_CHANNEL_2: /* T2CON |= 0x04; */ break; // 启动 Timer 2 (示例,具体启动位需要查阅手册)
default: return;
}
}

// 停止定时器
void HAL_TIMER_Stop(TIMER_ChannelTypeDef channel) {
switch (channel) {
case TIMER_CHANNEL_0: TR0 = 0; break; // 停止 Timer 0
case TIMER_CHANNEL_1: TR1 = 0; break; // 停止 Timer 1
case TIMER_CHANNEL_2: /* T2CON &= ~0x04; */ break; // 停止 Timer 2 (示例,具体停止位需要查阅手册)
default: return;
}
}

// 设置定时器重载值
void HAL_TIMER_SetReloadValue(TIMER_ChannelTypeDef channel, uint16_t reload_value) {
switch (channel) {
case TIMER_CHANNEL_0:
TH0 = (uint8_t)(reload_value >> 8);
TL0 = (uint8_t)(reload_value);
break;
case TIMER_CHANNEL_1:
TH1 = (uint8_t)(reload_value >> 8);
TL1 = (uint8_t)(reload_value);
break;
case TIMER_CHANNEL_2:
// ... 设置 Timer 2 重载值 (如果AI8051U有 Timer 2,需要查阅手册)
break;
default: return;
}
}

// 获取定时器当前计数值
uint16_t HAL_TIMER_GetCounterValue(TIMER_ChannelTypeDef channel) {
uint16_t value = 0;
switch (channel) {
case TIMER_CHANNEL_0:
value = (uint16_t)TH0 << 8 | TL0; break;
case TIMER_CHANNEL_1:
value = (uint16_t)TH1 << 8 | TL1; break;
case TIMER_CHANNEL_2:
// ... 获取 Timer 2 计数值 (如果AI8051U有 Timer 2,需要查阅手册)
break;
default: return 0;
}
return value;
}

// 使能定时器中断
void HAL_TIMER_EnableInterrupt(TIMER_ChannelTypeDef channel, TIMER_CallbackTypeDef callback) {
// 存储回调函数
timer_callbacks[channel] = callback;

switch (channel) {
case TIMER_CHANNEL_0: ET0 = 1; break; // 使能 Timer 0 中断
case TIMER_CHANNEL_1: ET1 = 1; break; // 使能 Timer 1 中断
case TIMER_CHANNEL_2: /* ET2 = 1; */ break; // 使能 Timer 2 中断 (示例,具体中断使能位需要查阅手册)
default: return;
}
EA = 1; // 总中断使能
}

// 禁用定时器中断
void HAL_TIMER_DisableInterrupt(TIMER_ChannelTypeDef channel) {
switch (channel) {
case TIMER_CHANNEL_0: ET0 = 0; break; // 禁用 Timer 0 中断
case TIMER_CHANNEL_1: ET1 = 0; break; // 禁用 Timer 1 中断
case TIMER_CHANNEL_2: /* ET2 = 0; */ break; // 禁用 Timer 2 中断 (示例,具体中断禁用位需要查阅手册)
default: return;
}
}

// 定时器中断服务例程 (需要根据实际使用的定时器通道编写对应的 ISR)
void timer0_isr() __interrupt (TF0_VECTOR) { // Timer 0 中断向量号 (TF0_VECTOR 需要根据芯片手册定义)
if (timer_callbacks[TIMER_CHANNEL_0] != NULL) {
timer_callbacks[TIMER_CHANNEL_0](); // 调用注册的回调函数
}
// 清除中断标志位 (通常硬件自动清除,或者需要手动清除)
// TF0 = 0; // 示例,如果需要手动清除
}

// ... 其他定时器中断服务例程 (timer1_isr, timer2_isr, ...)
  • UART 驱动 (hal_uart.h / hal_uart.c) (示例代码框架,具体实现需要根据AI8051U的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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// hal_uart.h
#ifndef HAL_UART_H
#define HAL_UART_H

#include <stdint.h>

// 定义UART通道 (根据AI8051U的UART资源定义)
typedef enum {
UART_CHANNEL_0,
UART_CHANNEL_1
// ... 根据实际情况添加
} UART_ChannelTypeDef;

typedef enum {
UART_BAUDRATE_9600,
UART_BAUDRATE_19200,
UART_BAUDRATE_38400,
UART_BAUDRATE_115200
// ... 根据实际情况添加
} UART_BaudRateTypeDef;

typedef void (*UART_CallbackTypeDef)(uint8_t data); // UART接收回调函数类型定义

// 初始化UART
void HAL_UART_Init(UART_ChannelTypeDef channel, UART_BaudRateTypeDef baudrate);

// 发送一个字节数据
void HAL_UART_SendByte(UART_ChannelTypeDef channel, uint8_t data);

// 发送字符串
void HAL_UART_SendString(UART_ChannelTypeDef channel, const char *str);

// 接收一个字节数据 (阻塞方式)
uint8_t HAL_UART_ReceiveByte(UART_ChannelTypeDef channel);

// 使能UART接收中断
void HAL_UART_EnableReceiveInterrupt(UART_ChannelTypeDef channel, UART_CallbackTypeDef callback);

// 禁用UART接收中断
void HAL_UART_DisableReceiveInterrupt(UART_ChannelTypeDef channel);

#endif // 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
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
// hal_uart.c
#include "hal_uart.h"
#include <AI8051U.h>

// UART接收回调函数数组
static UART_CallbackTypeDef uart_rx_callbacks[2] = {NULL, NULL}; // 假设最多2个UART通道

// 初始化UART
void HAL_UART_Init(UART_ChannelTypeDef channel, UART_BaudRateTypeDef baudrate) {
// 根据 channel 选择 UART 寄存器组 (例如 SCON, SBUF, PCON)
// 配置波特率 (需要根据波特率计算定时器或波特率发生器的配置值)
// 配置 UART 工作模式 (例如 8位数据位,无校验,1位停止位)

// 示例 (需要根据AI8051U的具体寄存器和波特率计算方法修改)
switch (channel) {
case UART_CHANNEL_0:
SCON = 0x50; // UART 模式 1 (8位UART,可变波特率)
// 配置波特率 (假设使用 Timer 1 作为波特率发生器,需要根据 baudrate 计算 Timer 1 重载值)
if (baudrate == UART_BAUDRATE_9600) {
// 例如: 设置 Timer 1 为模式 2 (8位自动重载),并设置重载值
TMOD &= ~0xF0; // 清零 Timer 1 模式位
TMOD |= 0x20; // Timer 1 模式 2 (8位自动重载)
TH1 = 256 - (FOSC / 12 / 32 / baudrate); // 假设 FOSC 为系统时钟频率,计算重载值
TL1 = TH1;
PCON |= 0x80; // SMOD = 1,波特率加倍
} // ... 其他波特率配置
TR1 = 1; // 启动 Timer 1
break;
case UART_CHANNEL_1:
// ... UART 1 初始化配置 (如果AI8051U有 UART 1)
break;
default: return;
}
}

// 发送一个字节数据
void HAL_UART_SendByte(UART_ChannelTypeDef channel, uint8_t data) {
switch (channel) {
case UART_CHANNEL_0:
SBUF = data;
while (!(SCON & 0x02)); // 等待发送完成 (TI = 1)
SCON &= ~0x02; // 清除发送完成标志位 (TI = 0)
break;
case UART_CHANNEL_1:
// ... UART 1 发送字节 (如果AI8051U有 UART 1)
break;
default: return;
}
}

// 发送字符串
void HAL_UART_SendString(UART_ChannelTypeDef channel, const char *str) {
while (*str) {
HAL_UART_SendByte(channel, *str++);
}
}

// 接收一个字节数据 (阻塞方式)
uint8_t HAL_UART_ReceiveByte(UART_ChannelTypeDef channel) {
uint8_t data;
switch (channel) {
case UART_CHANNEL_0:
while (!(SCON & 0x01)); // 等待接收完成 (RI = 1)
data = SBUF;
SCON &= ~0x01; // 清除接收完成标志位 (RI = 0)
return data;
case UART_CHANNEL_1:
// ... UART 1 接收字节 (如果AI8051U有 UART 1)
return 0;
default: return 0;
}
}

// 使能UART接收中断
void HAL_UART_EnableReceiveInterrupt(UART_ChannelTypeDef channel, UART_CallbackTypeDef callback) {
uart_rx_callbacks[channel] = callback; // 存储回调函数
switch (channel) {
case UART_CHANNEL_0: ES = 1; break; // 使能 UART 0 中断
case UART_CHANNEL_1: /* ES1 = 1; */ break; // 使能 UART 1 中断 (示例,具体中断使能位需要查阅手册)
default: return;
}
EA = 1; // 总中断使能
}

// 禁用UART接收中断
void HAL_UART_DisableReceiveInterrupt(UART_ChannelTypeDef channel) {
switch (channel) {
case UART_CHANNEL_0: ES = 0; break; // 禁用 UART 0 中断
case UART_CHANNEL_1: /* ES1 = 0; */ break; // 禁用 UART 1 中断 (示例,具体中断禁用位需要查阅手册)
default: return;
}
}

// UART 中断服务例程 (需要根据实际使用的 UART 通道编写对应的 ISR)
void uart0_isr() __interrupt (SI0_VECTOR) { // UART 0 中断向量号 (SI0_VECTOR 需要根据芯片手册定义)
if (SCON & 0x01) { // 接收中断
uint8_t data = SBUF;
SCON &= ~0x01; // 清除接收标志位
if (uart_rx_callbacks[UART_CHANNEL_0] != NULL) {
uart_rx_callbacks[UART_CHANNEL_0](data); // 调用注册的回调函数
}
}
if (SCON & 0x02) { // 发送中断 (如果使能了发送中断,需要处理发送完成事件)
SCON &= ~0x02; // 清除发送标志位
}
}

// ... UART 1 中断服务例程 (uart1_isr, ...)

2. 板级支持包 (BSP)

  • bsp.h / bsp.c (针对 QFP48 开发板)
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
// bsp.h (针对 QFP48 开发板)
#ifndef BSP_QFP48_H
#define BSP_QFP48_H

#include "hal_gpio.h"
#include "hal_timer.h"
#include "hal_uart.h"
// ... 其他 HAL 头文件

// 定义 LED 引脚
#define LED1_PORT GPIO_PORT_P1
#define LED1_PIN GPIO_PIN_0
#define LED2_PORT GPIO_PORT_P1
#define LED2_PIN GPIO_PIN_1

// 定义 按键 引脚
#define KEY1_PORT GPIO_PORT_P3
#define KEY1_PIN GPIO_PIN_2
#define KEY2_PORT GPIO_PORT_P3
#define KEY2_PIN GPIO_PIN_3

// UART 调试端口
#define DEBUG_UART_CHANNEL UART_CHANNEL_0
#define DEBUG_UART_BAUDRATE UART_BAUDRATE_115200

// 系统初始化函数
void BSP_Init(void);

// LED 控制函数
void BSP_LED_Control(uint8_t led_num, uint8_t on_off); // led_num: 1或2, on_off: 0-灭, 1-亮

// 按键读取函数
uint8_t BSP_KEY_Read(uint8_t key_num); // key_num: 1或2, 返回值: 0-按下, 1-释放

// 调试打印函数 (使用 UART 输出)
void BSP_Debug_Printf(const char *fmt, ...);

#endif // BSP_QFP48_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
// bsp.c (针对 QFP48 开发板)
#include "bsp.h" // 假设文件名是 bsp.h,根据实际情况修改
#include <stdarg.h> // 用于支持可变参数的 printf

// 系统初始化函数
void BSP_Init(void) {
// 初始化时钟 (如果需要,根据AI8051U的时钟系统配置)

// 初始化 GPIO
HAL_GPIO_Init(LED1_PORT, LED1_PIN, GPIO_MODE_OUTPUT_PP, GPIO_SPEED_HIGH);
HAL_GPIO_Init(LED2_PORT, LED2_PIN, GPIO_MODE_OUTPUT_PP, GPIO_SPEED_HIGH);
HAL_GPIO_Init(KEY1_PORT, KEY1_PIN, GPIO_MODE_INPUT, GPIO_SPEED_LOW); // 按键通常配置为输入
HAL_GPIO_Init(KEY2_PORT, KEY2_PIN, GPIO_MODE_INPUT, GPIO_SPEED_LOW);

// 初始化 UART 调试端口
HAL_UART_Init(DEBUG_UART_CHANNEL, DEBUG_UART_BAUDRATE);

// ... 初始化其他外设 (例如 SPI, I2C, ADC, DAC 等,根据开发板硬件配置)

// 默认关闭 LED
BSP_LED_Control(1, 0);
BSP_LED_Control(2, 0);
}

// LED 控制函数
void BSP_LED_Control(uint8_t led_num, uint8_t on_off) {
if (led_num == 1) {
if (on_off) {
HAL_GPIO_SetPinHigh(LED1_PORT, LED1_PIN); // 点亮 LED1
} else {
HAL_GPIO_SetPinLow(LED1_PORT, LED1_PIN); // 熄灭 LED1
}
} else if (led_num == 2) {
if (on_off) {
HAL_GPIO_SetPinHigh(LED2_PORT, LED2_PIN); // 点亮 LED2
} else {
HAL_GPIO_SetPinLow(LED2_PORT, LED2_PIN); // 熄灭 LED2
}
}
}

// 按键读取函数
uint8_t BSP_KEY_Read(uint8_t key_num) {
uint8_t key_state = 1; // 默认按键释放状态
if (key_num == 1) {
key_state = HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN);
} else if (key_num == 2) {
key_state = HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN);
}
return key_state; // 返回 0 表示按下,1 表示释放 (根据实际硬件连接和按键逻辑调整)
}

// 调试打印函数 (使用 UART 输出)
void BSP_Debug_Printf(const char *fmt, ...) {
char buffer[256]; // 打印缓冲区
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args); // 格式化字符串到缓冲区
va_end(args);
HAL_UART_SendString(DEBUG_UART_CHANNEL, buffer); // 通过 UART 发送字符串
}
  • bsp_dip40.h / bsp_dip40.c (针对 DIP40 拓展学习板) (类似 QFP48 开发板的 BSP,但需要根据 DIP40 板的硬件配置进行修改,例如 LED 和按键的引脚可能不同,拓展模块的初始化等)

3. 操作系统层 (简化版任务调度器 - os_scheduler.h / os_scheduler.c)

为了简化,我们实现一个基于时间片轮询的简单任务调度器,不引入复杂的 RTOS。

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
// os_scheduler.h
#ifndef OS_SCHEDULER_H
#define OS_SCHEDULER_H

#include <stdint.h>

// 任务结构体
typedef struct {
void (*task_func)(void); // 任务函数指针
uint32_t tick_count; // 任务执行周期 (单位:系统tick)
uint32_t last_tick; // 上次执行时间
} Task_TypeDef;

#define MAX_TASKS 5 // 最大任务数量

// 初始化任务调度器
void OS_Scheduler_Init(uint32_t system_tick_ms); // system_tick_ms: 系统tick周期,例如 1ms

// 添加任务
uint8_t OS_Scheduler_AddTask(Task_TypeDef *task);

// 任务调度运行
void OS_Scheduler_Run(void);

// 系统 Tick 处理函数 (需要在定时器中断服务例程中调用)
void OS_Scheduler_SysTickHandler(void);

#endif // OS_SCHEDULER_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
// os_scheduler.c
#include "os_scheduler.h"

static Task_TypeDef tasks[MAX_TASKS];
static uint8_t task_count = 0;
static uint32_t system_tick_ms;
static volatile uint32_t current_ticks = 0; // 当前系统tick计数

// 初始化任务调度器
void OS_Scheduler_Init(uint32_t sys_tick_ms) {
system_tick_ms = sys_tick_ms;
current_ticks = 0;
task_count = 0;
for (int i = 0; i < MAX_TASKS; i++) {
tasks[i].task_func = NULL; // 初始化任务函数指针为空
}
}

// 添加任务
uint8_t OS_Scheduler_AddTask(Task_TypeDef *task) {
if (task_count < MAX_TASKS) {
tasks[task_count] = *task;
task_count++;
return 0; // 成功
} else {
return 1; // 任务队列已满
}
}

// 任务调度运行
void OS_Scheduler_Run(void) {
while (1) { // 主循环
OS_Scheduler_SysTickHandler(); // 手动模拟系统Tick (在实际系统中,应该在定时器中断中调用 SysTickHandler)
for (int i = 0; i < task_count; i++) {
if (tasks[i].task_func != NULL && (current_ticks - tasks[i].last_tick >= tasks[i].tick_count)) {
tasks[i].task_func(); // 执行任务
tasks[i].last_tick = current_ticks; // 更新上次执行时间
}
}
// 可以添加空闲任务或低功耗处理代码
}
}

// 系统 Tick 处理函数 (需要在定时器中断服务例程中调用)
void OS_Scheduler_SysTickHandler(void) {
current_ticks++;
// 在这里可以处理其他系统Tick相关的任务,例如时间管理、延时函数等
}

4. 应用层 (示例应用 - 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
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
// app.c
#include "bsp.h" // 根据实际使用的开发板选择 bsp.h 或 bsp_dip40.h
#include "os_scheduler.h"

// 任务函数:LED 闪烁任务
void LED_Blink_Task(void) {
static uint8_t led_state = 0;
led_state = !led_state;
BSP_LED_Control(1, led_state); // 控制 LED1 闪烁
BSP_Debug_Printf("LED Blink Task Running, LED State: %d\r\n", led_state); // 调试打印
}

// 任务函数:按键检测任务
void Key_Scan_Task(void) {
static uint8_t last_key1_state = 1; // 上次按键状态,默认释放
uint8_t current_key1_state = BSP_KEY_Read(1); // 读取 KEY1 状态

if (current_key1_state == 0 && last_key1_state == 1) { // 按键按下检测
BSP_Debug_Printf("Key 1 Pressed!\r\n");
BSP_LED_Control(2, 1); // 按下时点亮 LED2
} else if (current_key1_state == 1 && last_key1_state == 0) { // 按键释放检测
BSP_Debug_Printf("Key 1 Released!\r\n");
BSP_LED_Control(2, 0); // 释放时熄灭 LED2
}
last_key1_state = current_key1_state; // 更新上次按键状态
}

int main() {
BSP_Init(); // 板级初始化
OS_Scheduler_Init(10); // 初始化任务调度器,系统Tick周期 10ms

// 创建 LED 闪烁任务
Task_TypeDef led_task;
led_task.task_func = LED_Blink_Task;
led_task.tick_count = 50; // 50个Tick周期执行一次,即 500ms (50 * 10ms)
led_task.last_tick = 0;
OS_Scheduler_AddTask(&led_task);

// 创建 按键检测任务
Task_TypeDef key_task;
key_task.task_func = Key_Scan_Task;
key_task.tick_count = 20; // 20个Tick周期执行一次,即 200ms (20 * 10ms)
key_task.last_tick = 0;
OS_Scheduler_AddTask(&key_task);

BSP_Debug_Printf("System Initialized, Starting Scheduler...\r\n");
OS_Scheduler_Run(); // 启动任务调度器

return 0; // 理论上不会执行到这里
}

// 定时器中断服务例程 (例如使用 Timer 0 作为系统Tick定时器)
void timer0_isr() __interrupt (TF0_VECTOR) {
OS_Scheduler_SysTickHandler(); // 调用系统 Tick 处理函数
// 清除中断标志位 (如果需要手动清除)
// TF0 = 0;
}

编译和构建项目

  1. 选择合适的 8051 编译器和开发环境: 例如 Keil C51, SDCC 等。
  2. 创建项目工程: 在开发环境中创建新的项目,选择 AI8051U 芯片型号。
  3. 添加源文件:hal_gpio.c, hal_timer.c, hal_uart.c, bsp.c (或 bsp_dip40.c), os_scheduler.c, app.c 等源文件添加到项目中。
  4. 配置编译选项: 根据芯片和开发环境配置编译选项,例如头文件包含路径、库文件路径、优化级别等。
  5. 编译项目: 编译项目,生成可执行文件 (例如 .hex 文件)。
  6. 下载和调试: 使用仿真器或下载器将可执行文件下载到 AI8051U 开发板上,进行调试和验证。

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

  • 分层模块化架构: 如上所述,经过实践验证,分层模块化架构是构建可靠、可维护、可扩展嵌入式系统的有效方法。
  • 硬件抽象层 (HAL): HAL 层的设计可以有效隔离硬件差异,提高代码的可移植性和复用性。在实际项目中,HAL 层可以根据不同的硬件平台进行适配,而上层应用代码可以保持不变。
  • 板级支持包 (BSP): BSP 层针对具体的开发板进行初始化和配置,简化了硬件平台的配置过程,提高了开发效率。
  • 任务调度器 (简化版): 对于资源有限的 8051 平台,简化版的任务调度器可以在一定程度上提高系统的并发性和实时性,同时避免了引入复杂 RTOS 的开销。
  • 事件驱动编程: 在 HAL 层中,例如 UART 驱动,我们使用了中断回调函数来实现事件驱动的接收方式,提高了系统的响应速度和效率。
  • 代码注释和编码规范: 代码中添加了详细的注释,并遵循一定的编码规范,提高了代码的可读性和可维护性。
  • 调试打印 (使用 UART): 通过 BSP_Debug_Printf 函数,可以使用 UART 端口进行调试打印,方便在开发过程中进行程序调试和状态监控。
  • 版本控制 (建议使用): 在实际项目中,强烈建议使用版本控制系统 (例如 Git) 来管理代码,方便代码的版本管理、团队协作和回溯。

维护升级

  • 模块化升级: 由于采用了模块化架构,当需要升级或修改某个功能时,只需要修改或替换相应的模块,而不会对其他模块产生重大影响。
  • 接口稳定: 分层架构中,各层之间通过明确的接口进行通信,保证了接口的稳定性。在升级或修改底层模块时,只要保证接口不变,上层应用代码就可以继续正常工作。
  • 版本管理: 使用版本控制系统可以方便地管理代码版本,进行版本回滚和分支管理,支持系统的持续维护和升级。

总结

这套基于分层模块化架构的代码设计方案,结合具体的C代码实现,以及实践验证的技术和方法,可以帮助您构建一个可靠、高效、可扩展的嵌入式系统平台,并充分利用AI8051U芯片的资源,完成您的嵌入式项目。

请注意,以上代码示例是框架性的,具体实现需要根据您使用的 AI8051U 芯片的具体型号、开发板硬件配置以及项目需求进行调整和完善。 建议您参考 AI8051U 芯片的手册、开发板的原理图和示例代码,并进行充分的测试和验证,以确保系统的稳定性和可靠性。

希望以上详细的说明和代码示例能够帮助您理解和实践嵌入式系统开发。 如果您有任何其他问题,欢迎继续提问。

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