编程技术分享

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

0%

简介:基于N32/STM32物联网终端设备,支持lora网络

好的,作为一名高级嵌入式软件开发工程师,我将为您详细阐述基于N32/STM32物联网终端设备,支持LoRa网络的嵌入式系统开发流程,并提供相应的C代码实现。这个项目将涵盖从需求分析到系统实现,再到测试验证和维护升级的完整生命周期,旨在构建一个可靠、高效、可扩展的系统平台。
关注微信公众号,提前获取相关推文

1. 需求分析

首先,我们需要明确物联网终端设备的需求。基于图片和项目描述,我们可以提取以下关键需求:

  • 核心功能:
    • 环境数据采集: 采集温湿度数据(根据图片上的DHT11/DHT22传感器推断)。
    • 数据传输: 通过LoRa网络将采集的数据传输到远程服务器或网关。
    • 本地显示: 通过OLED屏幕(根据图片上的小屏幕推断)显示当前环境数据和设备状态。
    • 低功耗运行: 作为物联网终端,设备需要长时间低功耗运行,依靠电池供电或低功耗电源。
  • 硬件平台:
    • 主控芯片: N32/STM32系列微控制器 (MCU)。选择具体的型号需要根据性能、功耗和成本进一步评估。
    • LoRa模块: 集成LoRa收发器,支持LoRa网络协议。
    • 温湿度传感器: DHT11/DHT22或其他兼容的温湿度传感器。
    • OLED显示屏: 小型OLED显示屏,用于本地数据显示。
    • 电源管理: 低功耗电源管理电路,包括电池供电和外部电源输入。
    • 用户接口: 可能包括按键、指示灯等,用于用户交互和状态指示。
  • 软件平台:
    • 实时操作系统 (RTOS): 为了实现高效的任务调度和资源管理,通常需要使用RTOS,例如FreeRTOS、RT-Thread等。
    • LoRa协议栈: 实现LoRa网络通信协议,可以选择现有的LoRaWAN协议栈或自定义LoRa协议。
    • 传感器驱动: 编写温湿度传感器的驱动程序。
    • 显示驱动: 编写OLED显示屏的驱动程序。
    • 电源管理: 实现低功耗模式切换和电源管理功能。
    • 固件升级 (OTA): 支持远程固件升级,方便后期维护和功能扩展。
    • 配置管理: 提供设备配置管理功能,例如LoRa参数、传感器采样频率等。
    • 日志和调试: 提供日志记录和调试接口,方便开发和维护。
  • 非功能性需求:
    • 可靠性: 系统需要稳定可靠运行,保证数据采集和传输的准确性。
    • 高效性: 系统运行效率高,功耗低,响应速度快。
    • 可扩展性: 系统架构应易于扩展,方便添加新的传感器、功能模块或网络协议。
    • 安全性: 考虑数据传输的安全性和设备自身的安全性,例如数据加密、身份验证等。
    • 易维护性: 代码结构清晰,模块化设计,方便后期维护和升级。

2. 系统架构设计

为了满足上述需求,我们采用分层架构来设计嵌入式软件系统。分层架构具有良好的模块化和可维护性,每一层专注于特定的功能,层与层之间通过清晰的接口进行交互。

系统架构可以分为以下几个层次:

  • 硬件抽象层 (HAL, Hardware Abstraction Layer):
    • 最底层,直接与硬件交互。
    • 提供统一的接口,屏蔽底层硬件差异,使上层软件可以独立于具体的硬件平台。
    • 包括GPIO驱动、SPI驱动、I2C驱动、UART驱动、定时器驱动、ADC驱动、电源管理驱动等。
  • 板级支持包 (BSP, Board Support Package):
    • 基于HAL层,提供针对特定硬件平台的初始化和配置。
    • 包括时钟初始化、外设初始化、中断配置、内存管理、启动代码等。
    • BSP层使HAL层更方便地在特定硬件平台上使用。
  • 操作系统抽象层 (OSAL, Operating System Abstraction Layer) (可选,但推荐使用RTOS时):
    • 如果使用RTOS,OSAL层可以进一步抽象RTOS的接口,使应用层代码更独立于特定的RTOS。
    • 提供任务管理、线程同步、消息队列、定时器等操作的统一接口。
    • 在本项目中,为了简化,我们可以直接使用RTOS API,不额外添加OSAL层。
  • 通信层:
    • 负责处理LoRa网络通信。
    • 包括LoRa模块驱动、LoRa协议栈实现、数据包封装和解析、网络管理等。
    • 可以使用现有的LoRaWAN协议栈,或者自定义简化的LoRa协议。
  • 应用层:
    • 实现核心应用逻辑。
    • 包括传感器数据采集、数据处理、数据显示、数据传输、配置管理、OTA升级、日志记录等。
    • 应用层调用下层提供的接口完成具体的功能。

系统架构图示 (简化版):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+---------------------+
| 应用层 |
| (传感器数据采集, 数据处理, 显示, LoRa数据传输, 配置管理, OTA, 日志) |
+---------------------+
| 通信层 |
| (LoRa模块驱动, LoRa协议栈, 数据包处理, 网络管理) |
+---------------------+
| 操作系统 (RTOS) | (可选,但推荐使用,例如 FreeRTOS)
| (任务调度, 线程同步, 内存管理, 定时器) |
+---------------------+
| BSP层 |
| (时钟初始化, 外设初始化, 中断配置, 内存管理, 启动代码) |
+---------------------+
| HAL层 |
| (GPIO, SPI, I2C, UART, 定时器, ADC, 电源管理 驱动) |
+---------------------+
| 硬件平台 |
| (N32/STM32 MCU, LoRa模块, 温湿度传感器, OLED显示屏, 电源电路) |
+---------------------+

3. 代码设计与实现 (C代码示例)

以下是一个简化的C代码示例,展示了基于分层架构的嵌入式系统实现框架。为了代码长度和可读性,这里只给出关键模块的框架代码和部分核心功能实现,实际项目中需要根据具体硬件和需求进行详细的开发和完善。

3.1 HAL层 (HAL, Hardware Abstraction Layer)

  • hal_gpio.h: GPIO驱动头文件
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 <stdint.h>
#include <stdbool.h>

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
// ... 其他GPIO模式
} GPIO_ModeTypeDef;

typedef enum {
GPIO_SPEED_LOW,
GPIO_SPEED_MEDIUM,
GPIO_SPEED_HIGH,
// ... 其他GPIO速度
} GPIO_SpeedTypeDef;

typedef enum {
GPIO_PULL_NONE,
GPIO_PULLUP,
GPIO_PULLDOWN,
// ... 其他GPIO Pull模式
} GPIO_PullTypeDef;

typedef enum {
GPIO_PIN_RESET = 0,
GPIO_PIN_SET = 1,
} GPIO_PinState;

typedef struct {
uint32_t Pin; // GPIO Pin
GPIO_ModeTypeDef Mode; // GPIO Mode
GPIO_SpeedTypeDef Speed; // GPIO Speed
GPIO_PullTypeDef Pull; // GPIO Pull
// ... 其他GPIO配置参数
} GPIO_InitTypeDef;

typedef void (*GPIO_InterruptCallback)(uint16_t GPIO_Pin);

/**
* @brief 初始化GPIO端口
* @param GPIOx: GPIO端口基地址,例如GPIOA、GPIOB等
* @param GPIO_Init: GPIO初始化结构体
* @retval None
*/
void HAL_GPIO_Init(uint32_t GPIOx, GPIO_InitTypeDef *GPIO_Init);

/**
* @brief 设置GPIO端口的输出电平
* @param GPIOx: GPIO端口基地址
* @param GPIO_Pin: GPIO引脚
* @param PinState: 要设置的电平状态 (GPIO_PIN_RESET 或 GPIO_PIN_SET)
* @retval None
*/
void HAL_GPIO_WritePin(uint32_t GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

/**
* @brief 读取GPIO端口的输入电平
* @param GPIOx: GPIO端口基地址
* @param GPIO_Pin: GPIO引脚
* @retval GPIO_PinState: 引脚电平状态 (GPIO_PIN_RESET 或 GPIO_PIN_SET)
*/
GPIO_PinState HAL_GPIO_ReadPin(uint32_t GPIOx, uint16_t GPIO_Pin);

/**
* @brief 注册GPIO中断回调函数
* @param GPIO_Pin: GPIO引脚
* @param Callback: 中断回调函数
* @retval None
*/
void HAL_GPIO_RegisterInterruptCallback(uint16_t GPIO_Pin, GPIO_InterruptCallback Callback);

// ... 其他GPIO相关函数 (例如:GPIO时钟使能、GPIO DeInit等)

#endif /* HAL_GPIO_H */
  • hal_gpio.c: GPIO驱动源文件 (平台相关的具体实现,这里以伪代码示例)
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
#include "hal_gpio.h"
// ... 包含N32/STM32平台相关的头文件,例如 "n32xxx.h" 或 "stm32xxx.h"

void HAL_GPIO_Init(uint32_t GPIOx, GPIO_InitTypeDef *GPIO_Init) {
// 1. 使能GPIO时钟 (平台相关)
// RCC_APB2PeriphClockCmd(GPIOx_CLK, ENABLE);

// 2. 配置GPIO模式、速度、Pull模式 (平台相关寄存器操作)
// GPIOx->CRL &= ~(0xF << (GPIO_Init->Pin * 4)); // 清除配置位
// GPIOx->CRL |= (... 配置位根据 GPIO_Init 结构体设置 ...)

// ... 其他平台相关的GPIO初始化配置
}

void HAL_GPIO_WritePin(uint32_t GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) {
if (PinState == GPIO_PIN_SET) {
// GPIOx->BSRR = GPIO_Pin; // 设置引脚为高电平 (平台相关寄存器操作)
} else {
// GPIOx->BRR = GPIO_Pin; // 设置引脚为低电平 (平台相关寄存器操作)
}
}

GPIO_PinState HAL_GPIO_ReadPin(uint32_t GPIOx, uint16_t GPIO_Pin) {
// return (GPIOx->IDR & GPIO_Pin) ? GPIO_PIN_SET : GPIO_PIN_RESET; // 读取引脚电平 (平台相关寄存器操作)
return GPIO_PIN_RESET; // 示例返回值,实际需要读取寄存器
}

void HAL_GPIO_RegisterInterruptCallback(uint16_t GPIO_Pin, GPIO_InterruptCallback Callback) {
// ... 配置GPIO中断 (平台相关中断配置)
// ... 存储回调函数指针
}

// ... 其他GPIO相关函数 的平台相关实现
  • hal_spi.h, hal_spi.c, hal_i2c.h, hal_i2c.c, hal_uart.h, hal_uart.c, hal_timer.h, hal_timer.c, hal_adc.h, hal_adc.c, hal_pwr.h, hal_pwr.c: 类似GPIO驱动,分别定义SPI、I2C、UART、定时器、ADC、电源管理等外设的HAL接口和平台相关的驱动实现。 这里省略具体代码,但原理类似,都是定义统一的HAL接口,然后在 .c 文件中实现平台相关的寄存器操作。

3.2 BSP层 (BSP, Board Support Package)

  • bsp.h: BSP头文件
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
#ifndef BSP_H
#define BSP_H

#include "hal_gpio.h" // 包含HAL层头文件
// ... 包含其他HAL层头文件 (spi.h, i2c.h, uart.h, timer.h, adc.h, pwr.h)

// 定义板级硬件配置,例如引脚定义
#define LED_PIN GPIO_PIN_0
#define LED_PORT GPIOA
#define DHT_DATA_PIN GPIO_PIN_1
#define DHT_PORT GPIOA
#define OLED_RST_PIN GPIO_PIN_2
#define OLED_RST_PORT GPIOA
#define OLED_DC_PIN GPIO_PIN_3
#define OLED_DC_PORT GPIOA
#define OLED_CS_PIN GPIO_PIN_4
#define OLED_CS_PORT GPIOA
#define OLED_SPI_PORT SPI1
#define LORA_RESET_PIN GPIO_PIN_5
#define LORA_RESET_PORT GPIOA
#define LORA_CS_PIN GPIO_PIN_6
#define LORA_CS_PORT GPIOA
#define LORA_SPI_PORT SPI2
#define LORA_DIO0_PIN GPIO_PIN_7
#define LORA_DIO0_PORT GPIOA
#define LORA_DIO1_PIN GPIO_PIN_8
#define LORA_DIO1_PORT GPIOA
#define LORA_DIO2_PIN GPIO_PIN_9
#define LORA_DIO2_PORT GPIOA

// ... 其他硬件引脚定义

// 板级初始化函数声明
void BSP_Init(void);
void SystemClock_Config(void); // 时钟配置
void Periph_Init(void); // 外设初始化 (GPIO, SPI, I2C, UART, Timer, ADC)

#endif /* BSP_H */
  • bsp.c: BSP源文件
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
#include "bsp.h"
#include "hal_gpio.h"
// ... 包含其他HAL层头文件

void BSP_Init(void) {
SystemClock_Config(); // 配置系统时钟
Periph_Init(); // 初始化外设
// ... 其他板级初始化,例如电源管理初始化,看门狗初始化等
}

void SystemClock_Config(void) {
// ... 配置系统时钟,例如HSE/HSI振荡器选择,PLL配置,时钟分频等 (平台相关)
// 例如:使能HSE,配置PLL倍频,设置AHB/APB时钟分频系数
// RCC_OscInitTypeDef RCC_OscInitStruct = {0};
// RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
// RCC_OscInitStruct.HSEState = RCC_HSE_ON;
// ... 配置 RCC_OscInitStruct 和 RCC_ClkInitStruct
// HAL_RCC_OscConfig(&RCC_OscInitStruct);
// HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

void Periph_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};

// 使能 GPIO 时钟 (根据实际使用的GPIO端口使能时钟)
// __HAL_RCC_GPIOA_CLK_ENABLE();
// __HAL_RCC_GPIOB_CLK_ENABLE();
// ...

// 初始化 LED GPIO
GPIO_InitStruct.Pin = LED_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET); // 初始状态熄灭LED

// 初始化 DHT11 数据引脚 (输入模式,上拉)
GPIO_InitStruct.Pin = DHT_DATA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(DHT_PORT, &GPIO_InitStruct);

// 初始化 OLED RST, DC, CS 引脚 (输出模式)
GPIO_InitStruct.Pin = OLED_RST_PIN | OLED_DC_PIN | OLED_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(OLED_RST_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET); // 释放复位
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_DC_PIN, GPIO_PIN_RESET); // 初始DC为命令模式
HAL_GPIO_WritePin(OLED_RST_PORT, OLED_CS_PIN, GPIO_PIN_SET); // 初始CS为高,不选中

// 初始化 OLED SPI (根据OLED实际接口选择 SPI 或 I2C)
// ... SPI 初始化代码,参考 HAL_SPI 驱动

// 初始化 LoRa Reset, CS, DIOx 引脚 (输出模式,输入模式)
GPIO_InitStruct.Pin = LORA_RESET_PIN | LORA_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(LORA_RESET_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_RESET_PIN, GPIO_PIN_SET); // 释放复位
HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_CS_PIN, GPIO_PIN_SET); // 初始CS为高,不选中

GPIO_InitStruct.Pin = LORA_DIO0_PIN | LORA_DIO1_PIN | LORA_DIO2_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 或 GPIO_PULLDOWN,根据LoRa模块要求
HAL_GPIO_Init(LORA_DIO0_PORT, &GPIO_InitStruct);

// 初始化 LoRa SPI (根据LoRa模块实际接口选择 SPI 或 UART)
// ... SPI 初始化代码,参考 HAL_SPI 驱动

// ... 初始化其他外设 (UART for debug, ADC if needed, Timer for scheduling)
}

3.3 操作系统层 (RTOS - FreeRTOS 示例)

  • 这里我们直接使用FreeRTOS API,不额外创建OSAL层。需要包含 FreeRTOS 的头文件,并在项目中集成 FreeRTOS 库。

3.4 通信层 (LoRa)

  • lora.h: LoRa驱动头文件
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
#ifndef LORA_H
#define LORA_H

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

// LoRa 模块配置参数
typedef struct {
uint32_t frequency; // 中心频率 (Hz)
uint32_t bandwidth; // 带宽 (Hz)
uint8_t spreadingFactor; // 扩频因子 (7-12)
uint8_t codingRate; // 编码率 (4/5, 4/6, 4/7, 4/8)
int8_t txPower; // 发射功率 (dBm)
// ... 其他LoRa配置参数
} LoRa_ConfigTypeDef;

/**
* @brief 初始化LoRa模块
* @param config: LoRa配置参数
* @retval true: 初始化成功, false: 初始化失败
*/
bool LoRa_Init(LoRa_ConfigTypeDef *config);

/**
* @brief 发送LoRa数据
* @param data: 数据缓冲区指针
* @param len: 数据长度 (字节)
* @retval true: 发送成功, false: 发送失败
*/
bool LoRa_SendData(uint8_t *data, uint16_t len);

/**
* @brief 接收LoRa数据 (非阻塞方式,需要轮询或中断触发)
* @param data: 数据缓冲区指针
* @param maxLen: 最大接收数据长度 (字节)
* @param recvLen: 实际接收到的数据长度 (输出参数)
* @retval true: 接收到数据, false: 没有接收到数据
*/
bool LoRa_ReceiveData(uint8_t *data, uint16_t maxLen, uint16_t *recvLen);

/**
* @brief 进入接收模式
* @retval true: 进入接收模式成功, false: 进入失败
*/
bool LoRa_StartReceive(void);

/**
* @brief 进入休眠模式
* @retval true: 进入休眠模式成功, false: 进入失败
*/
bool LoRa_Sleep(void);

// ... 其他LoRa相关函数 (例如:设置参数, 获取状态, 中断处理等)

#endif /* LORA_H */
  • lora.c: LoRa驱动源文件 (简化的LoRa驱动示例,实际需要根据LoRa模块和协议进行详细实现)
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
228
229
230
231
232
233
234
235
236
237
#include "lora.h"
#include "bsp.h" // 包含BSP层头文件,使用硬件引脚定义
#include "hal_gpio.h" // 包含HAL GPIO驱动
#include "hal_spi.h" // 包含HAL SPI驱动 (如果LoRa模块使用SPI接口)
#include "FreeRTOS.h" // 如果使用FreeRTOS
#include "task.h" // 如果使用FreeRTOS 任务

#define LORA_SPI_CS_LOW() HAL_GPIO_WritePin(LORA_CS_PORT, LORA_CS_PIN, GPIO_PIN_RESET)
#define LORA_SPI_CS_HIGH() HAL_GPIO_WritePin(LORA_CS_PORT, LORA_CS_PIN, GPIO_PIN_SET)
#define LORA_RESET_LOW() HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_RESET_PIN, GPIO_PIN_RESET)
#define LORA_RESET_HIGH() HAL_GPIO_WritePin(LORA_RESET_PORT, LORA_RESET_PIN, GPIO_PIN_SET)

// LoRa 寄存器地址定义 (根据LoRa模块手册定义)
#define REG_FIFO 0x00
#define REG_OP_MODE 0x01
#define REG_FRF_MSB 0x06
#define REG_FRF_MID 0x07
#define REG_FRF_LSB 0x08
#define REG_PA_CONFIG 0x09
#define REG_OCP 0x0B
#define REG_LNA 0x0C
#define REG_FIFO_ADDR_PTR 0x0D
#define REG_FIFO_TX_BASE_ADDR 0x0E
#define REG_FIFO_RX_BASE_ADDR 0x0F
#define REG_FIFO_RX_CURRENT_ADDR 0x10
#define REG_IRQ_FLAGS_MASK 0x11
#define REG_IRQ_FLAGS 0x12
#define REG_RX_NB_BYTES 0x13
#define REG_PKT_SNR_VALUE 0x19
#define REG_PKT_RSSI_VALUE 0x1A
#define REG_RSSI_VALUE 0x1B
#define REG_MODEM_CONFIG1 0x1D
#define REG_MODEM_CONFIG2 0x1E
#define REG_PREAMBLE_MSB 0x20
#define REG_PREAMBLE_LSB 0x21
#define REG_PAYLOAD_LENGTH 0x22
#define REG_MODEM_CONFIG3 0x26
#define REG_FREQ_CARRIER_SENSE 0x30
#define REG_FREQ_CARRIER_SENSE_THRES 0x31
#define REG_FREQ_CARRIER_SENSE_TIME 0x32
#define REG_FREQ_CARRIER_SENSE_GAIN 0x33
#define REG_SYNC_WORD 0x39
#define REG_DIO_MAPPING1 0x40
#define REG_VERSION 0x42

// LoRa 操作模式
#define LORA_MODE_SLEEP 0x00
#define LORA_MODE_STDBY 0x01
#define LORA_MODE_TX 0x03
#define LORA_MODE_RX_CONT 0x05
#define LORA_MODE_RX_SINGLE 0x06

static LoRa_ConfigTypeDef currentConfig;

// SPI 读写寄存器函数 (平台相关的SPI操作需要根据 HAL_SPI 驱动实现)
static uint8_t LoRa_SPI_ReadWrite(uint8_t value) {
uint8_t rxData;
// HAL_SPI_TransmitReceive(LORA_SPI_PORT, &value, &rxData, 1, HAL_MAX_DELAY); // 平台相关的SPI读写函数
// 这里用伪代码代替实际SPI操作
// printf("SPI Write: 0x%02X, Read: 0x%02X (Mock)\n", value, rxData);
rxData = 0x00; // 示例返回值
return rxData;
}

// 写寄存器
static void LoRa_WriteReg(uint8_t addr, uint8_t value) {
LORA_SPI_CS_LOW();
LoRa_SPI_ReadWrite(addr | 0x80); // Write command
LoRa_SPI_ReadWrite(value);
LORA_SPI_CS_HIGH();
}

// 读寄存器
static uint8_t LoRa_ReadReg(uint8_t addr) {
uint8_t value;
LORA_SPI_CS_LOW();
LoRa_SPI_ReadWrite(addr & 0x7F); // Read command
value = LoRa_SPI_ReadWrite(0x00); // Dummy write to read
LORA_SPI_CS_HIGH();
return value;
}

// 设置 LoRa 模式
static void LoRa_SetMode(uint8_t mode) {
uint8_t opMode = LoRa_ReadReg(REG_OP_MODE);
opMode &= 0xF8; // Clear mode bits
opMode |= mode;
LoRa_WriteReg(REG_OP_MODE, opMode);
}

bool LoRa_Init(LoRa_ConfigTypeDef *config) {
currentConfig = *config;

// 1. Reset LoRa Module
LORA_RESET_LOW();
vTaskDelay(pdMS_TO_TICKS(10)); // 延时10ms
LORA_RESET_HIGH();
vTaskDelay(pdMS_TO_TICKS(10));

// 2. 进入 Sleep 模式
LoRa_SetMode(LORA_MODE_SLEEP);
vTaskDelay(pdMS_TO_TICKS(10));

// 3. 设置频率
uint32_t frf = (uint32_t)(((double)config->frequency / 30000000.0) * (double)(1 << 19));
LoRa_WriteReg(REG_FRF_MSB, (uint8_t)((frf >> 16) & 0xFF));
LoRa_WriteReg(REG_FRF_MID, (uint8_t)((frf >> 8) & 0xFF));
LoRa_WriteReg(REG_FRF_LSB, (uint8_t)(frf & 0xFF));

// 4. 设置功率
LoRa_WriteReg(REG_PA_CONFIG, 0x80 | (config->txPower & 0x0F)); // PA_BOOST + OutputPower

// 5. 设置 OCP (过流保护)
LoRa_WriteReg(REG_OCP, 0x2B); // OCP Trim = 45mA

// 6. 设置 LNA (低噪声放大器)
LoRa_WriteReg(REG_LNA, 0x23); // LNA Gain = G1, LNA Boost = on

// 7. 设置 Modem Config 1, 2, 3
uint8_t modemConfig1 = (config->bandwidth << 4) | (config->codingRate << 1); // BW, CR
uint8_t modemConfig2 = (config->spreadingFactor << 4) | 0x04; // SF, CRC_ON
uint8_t modemConfig3 = 0x00; // LowDataRateOptimize = off, AgcAutoOn = on
LoRa_WriteReg(REG_MODEM_CONFIG1, modemConfig1);
LoRa_WriteReg(REG_MODEM_CONFIG2, modemConfig2);
LoRa_WriteReg(REG_MODEM_CONFIG3, modemConfig3);

// 8. 设置 Preamble Length
LoRa_WriteReg(REG_PREAMBLE_MSB, 0x00);
LoRa_WriteReg(REG_PREAMBLE_LSB, 0x08); // 8 symbols

// 9. 设置 Sync Word (自定义同步字)
LoRa_WriteReg(REG_SYNC_WORD, 0x34); // Example Sync Word

// 10. 进入 Standby 模式
LoRa_SetMode(LORA_MODE_STDBY);
vTaskDelay(pdMS_TO_TICKS(10));

// 11. 检查版本号 (可选,验证LoRa模块是否正常工作)
uint8_t version = LoRa_ReadReg(REG_VERSION);
if (version != 0x12) { // SX1276/SX1278 版本号通常为 0x12
// 初始化失败,版本号错误
return false;
}

return true; // 初始化成功
}

bool LoRa_SendData(uint8_t *data, uint16_t len) {
if (len > 255) return false; // LoRa Payload 最大长度为 255 字节

// 1. 进入 Standby 模式
LoRa_SetMode(LORA_MODE_STDBY);
vTaskDelay(pdMS_TO_TICKS(10));

// 2. 设置 FIFO 地址指针和 TX 基地址
LoRa_WriteReg(REG_FIFO_ADDR_PTR, LoRa_ReadReg(REG_FIFO_TX_BASE_ADDR));
LoRa_WriteReg(REG_FIFO_TX_BASE_ADDR, 0x00); // TX 基地址设置为 0x00

// 3. 设置 Payload 长度
LoRa_WriteReg(REG_PAYLOAD_LENGTH, len);

// 4. 将数据写入 FIFO
LORA_SPI_CS_LOW();
LoRa_SPI_ReadWrite(REG_FIFO | 0x80); // FIFO Write command
for (uint16_t i = 0; i < len; i++) {
LoRa_SPI_ReadWrite(data[i]);
}
LORA_SPI_CS_HIGH();

// 5. 进入 TX 模式,开始发送
LoRa_SetMode(LORA_MODE_TX);

// 6. 等待发送完成 (可以通过轮询中断标志或延时等待)
// ... 这里简单使用延时等待,实际应用中建议使用中断方式
vTaskDelay(pdMS_TO_TICKS(100)); // 延时等待发送完成 (需要根据实际情况调整)

// 7. 清除 IRQ Flags
LoRa_WriteReg(REG_IRQ_FLAGS, 0xFF); // 清除所有中断标志

return true; // 假设发送成功 (实际需要根据中断标志判断发送结果)
}

bool LoRa_StartReceive(void) {
// 1. 进入 Standby 模式
LoRa_SetMode(LORA_MODE_STDBY);
vTaskDelay(pdMS_TO_TICKS(10));

// 2. 设置 FIFO 地址指针和 RX 基地址
LoRa_WriteReg(REG_FIFO_ADDR_PTR, LoRa_ReadReg(REG_FIFO_RX_BASE_ADDR));
LoRa_WriteReg(REG_FIFO_RX_BASE_ADDR, 0x00); // RX 基地址设置为 0x00

// 3. 进入 Continuous RX 模式
LoRa_SetMode(LORA_MODE_RX_CONT);

return true;
}

bool LoRa_ReceiveData(uint8_t *data, uint16_t maxLen, uint16_t *recvLen) {
uint8_t irqFlags = LoRa_ReadReg(REG_IRQ_FLAGS);

if (irqFlags & 0x40) { // RxDone 标志
// 1. 获取接收数据长度
uint8_t pktSize = LoRa_ReadReg(REG_RX_NB_BYTES);
if (pktSize > maxLen) pktSize = maxLen;

// 2. 设置 FIFO 地址指针到接收数据起始位置
LoRa_WriteReg(REG_FIFO_ADDR_PTR, LoRa_ReadReg(REG_FIFO_RX_CURRENT_ADDR));

// 3. 从 FIFO 读取数据
LORA_SPI_CS_LOW();
LoRa_SPI_ReadWrite(REG_FIFO & 0x7F); // FIFO Read command
for (uint16_t i = 0; i < pktSize; i++) {
data[i] = LoRa_SPI_ReadWrite(0x00); // Dummy write to read
}
LORA_SPI_CS_HIGH();
*recvLen = pktSize;

// 4. 读取 RSSI 和 SNR (可选)
int8_t rssi = LoRa_ReadReg(REG_PKT_RSSI_VALUE) - 157; // 根据SX1276 datasheet计算RSSI
int8_t snr = LoRa_ReadReg(REG_PKT_SNR_VALUE) / 4; // SNR * 0.25

// 5. 清除 RxDone 标志
LoRa_WriteReg(REG_IRQ_FLAGS, 0x40); // 清除 RxDone 标志

return true; // 接收到数据
} else {
*recvLen = 0;
return false; // 没有接收到数据
}
}

bool LoRa_Sleep(void) {
LoRa_SetMode(LORA_MODE_SLEEP);
return true;
}

// ... 其他 LoRa 相关函数实现 (例如:设置参数, 获取状态, 中断处理等)

3.5 应用层

  • sensor.h: 传感器驱动头文件 (DHT11/DHT22 示例)
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
#ifndef SENSOR_H
#define SENSOR_H

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

typedef struct {
float temperature; // 温度 (摄氏度)
float humidity; // 湿度 (百分比)
bool isValid; // 数据是否有效
} SensorDataTypeDef;

/**
* @brief 初始化温湿度传感器
* @retval true: 初始化成功, false: 初始化失败
*/
bool Sensor_Init(void);

/**
* @brief 读取温湿度数据
* @param data: 传感器数据结构体指针 (输出参数)
* @retval true: 读取成功, false: 读取失败
*/
bool Sensor_ReadData(SensorDataTypeDef *data);

#endif /* SENSOR_H */
  • sensor.c: 传感器驱动源文件 (DHT11/DHT22 驱动示例,基于GPIO时序)
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
#include "sensor.h"
#include "bsp.h" // 使用硬件引脚定义
#include "hal_gpio.h" // 使用HAL GPIO驱动
#include "FreeRTOS.h" // 如果使用FreeRTOS
#include "task.h" // 如果使用FreeRTOS 任务

#define DHT_DATA_PIN DHT_DATA_PIN
#define DHT_DATA_PORT DHT_PORT

#define DHT_OUTPUT() { GPIO_InitTypeDef GPIO_InitStruct = {DHT_DATA_PIN, GPIO_MODE_OUTPUT_PP, GPIO_SPEED_LOW, GPIO_PULL_NONE}; HAL_GPIO_Init(DHT_DATA_PORT, &GPIO_InitStruct); }
#define DHT_INPUT() { GPIO_InitTypeDef GPIO_InitStruct = {DHT_DATA_PIN, GPIO_MODE_INPUT, GPIO_SPEED_LOW, GPIO_PULL_UP}; HAL_GPIO_Init(DHT_DATA_PORT, &GPIO_InitStruct); }
#define DHT_DATA_HIGH() HAL_GPIO_WritePin(DHT_DATA_PORT, DHT_DATA_PIN, GPIO_PIN_SET)
#define DHT_DATA_LOW() HAL_GPIO_WritePin(DHT_DATA_PORT, DHT_DATA_PIN, GPIO_PIN_RESET)
#define DHT_DATA_READ() HAL_GPIO_ReadPin(DHT_DATA_PORT, DHT_DATA_PIN)

#define DHT_TIMEOUT 1000 // 超时时间 (us)

bool Sensor_Init(void) {
DHT_OUTPUT();
DHT_DATA_HIGH();
return true;
}

bool Sensor_ReadData(SensorDataTypeDef *data) {
uint8_t rawData[5] = {0};
uint8_t checksum = 0;
uint8_t i, j;

DHT_OUTPUT();
DHT_DATA_HIGH();
vTaskDelay(pdMS_TO_TICKS(1)); // 延时1ms

DHT_DATA_LOW();
vTaskDelay(pdMS_TO_TICKS(18)); // 延时至少18ms,发送起始信号

DHT_DATA_HIGH();
DHT_INPUT();

// 等待 DHT 响应信号 (低电平)
uint32_t timeout = DHT_TIMEOUT;
while (DHT_DATA_READ() == GPIO_PIN_SET) {
timeout--;
if (timeout == 0) return false; // 超时
vTaskDelay(pdUS_TO_TICKS(1));
}

// 等待 DHT 响应信号 (高电平)
timeout = DHT_TIMEOUT;
while (DHT_DATA_READ() == GPIO_PIN_RESET) {
timeout--;
if (timeout == 0) return false; // 超时
vTaskDelay(pdUS_TO_TICKS(1));
}

// 接收 40bit 数据 (5 bytes)
for (j = 0; j < 5; j++) {
for (i = 0; i < 8; i++) {
timeout = DHT_TIMEOUT;
while (DHT_DATA_READ() == GPIO_PIN_SET) { // 等待低电平开始
timeout--;
if (timeout == 0) return false; // 超时
vTaskDelay(pdUS_TO_TICKS(1));
}

timeout = DHT_TIMEOUT;
while (DHT_DATA_READ() == GPIO_PIN_RESET) { // 等待高电平开始
timeout--;
if (timeout == 0) return false; // 超时
vTaskDelay(pdUS_TO_TICKS(1));
}
vTaskDelay(pdUS_TO_TICKS(30)); // 判断高电平持续时间,超过 26-28us 认为是 '1'

if (DHT_DATA_READ() == GPIO_PIN_SET) {
rawData[j] |= (1 << (7 - i)); // 接收到 '1'
} else {
rawData[j] &= ~(1 << (7 - i)); // 接收到 '0'
}

timeout = DHT_TIMEOUT;
while (DHT_DATA_READ() == GPIO_PIN_SET) { // 等待高电平结束
timeout--;
if (timeout == 0) return false; // 超时
vTaskDelay(pdUS_TO_TICKS(1));
}
}
}

DHT_OUTPUT();
DHT_DATA_HIGH(); // 释放总线

// 校验 checksum
checksum = rawData[0] + rawData[1] + rawData[2] + rawData[3];
if (checksum != rawData[4]) {
data->isValid = false; // 校验失败
return false;
}

// 数据转换 (根据 DHT11/DHT22 数据格式解析)
data->humidity = (float)rawData[0]; // 整数湿度
data->temperature = (float)rawData[2]; // 整数温度
data->isValid = true;
return true;
}
  • display.h: 显示驱动头文件 (OLED 示例)
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
#ifndef DISPLAY_H
#define DISPLAY_H

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

/**
* @brief 初始化OLED显示屏
* @retval true: 初始化成功, false: 初始化失败
*/
bool Display_Init(void);

/**
* @brief 清屏
* @retval None
*/
void Display_Clear(void);

/**
* @brief 显示字符串
* @param x: X轴坐标 (0-127)
* @param y: Y轴坐标 (0-63)
* @param str: 要显示的字符串
* @param fontSize: 字体大小 (例如 8x16, 6x8)
* @param color: 颜色 (例如 1: 白色, 0: 黑色)
* @retval None
*/
void Display_WriteString(uint8_t x, uint8_t y, const char *str, uint8_t fontSize, uint8_t color);

/**
* @brief 显示数字
* @param x: X轴坐标
* @param y: Y轴坐标
* @param num: 要显示的数字
* @param fontSize: 字体大小
* @param color: 颜色
* @retval None
*/
void Display_WriteNumber(uint8_t x, uint8_t y, int32_t num, uint8_t fontSize, uint8_t color);

// ... 其他显示相关函数 (例如:画点, 画线, 画图形等)

#endif /* DISPLAY_H */
  • display.c: 显示驱动源文件 (OLED SSD1306 驱动示例,基于SPI或I2C) - 这里假设使用SPI接口
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
#include "display.h"
#include "bsp.h" // 使用硬件引脚定义
#include "hal_gpio.h" // 使用HAL GPIO驱动
#include "hal_spi.h" // 使用HAL SPI驱动 (如果OLED使用SPI接口)
#include "delay.h" // 需要一个延时函数 (可以使用 HAL_Delay 或自定义延时函数)
#include "font.h" // 包含字库文件 (例如 8x16, 6x8 字库)

#define OLED_RST_LOW() HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET)
#define OLED_RST_HIGH() HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET)
#define OLED_DC_CMD() HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET) // 命令模式
#define OLED_DC_DATA() HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET) // 数据模式
#define OLED_CS_LOW() HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET)
#define OLED_CS_HIGH() HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET)

#define OLED_SPI_SEND_BYTE(byte) { /* HAL_SPI_Transmit(OLED_SPI_PORT, &byte, 1, HAL_MAX_DELAY); */ } // 平台相关的SPI发送字节函数

// OLED 命令集 (根据 SSD1306 数据手册定义)
#define OLED_CMD_SET_CONTRAST 0x81
#define OLED_CMD_DISPLAY_ALLON_RESUME 0xA4
#define OLED_CMD_DISPLAY_ALLON 0xA5
#define OLED_CMD_NORMAL_DISPLAY 0xA6
#define OLED_CMD_INVERT_DISPLAY 0xA7
#define OLED_CMD_DISPLAY_OFF 0xAE
#define OLED_CMD_DISPLAY_ON 0xAF
#define OLED_CMD_SET_MEM_ADDR_MODE 0x20
#define OLED_CMD_SET_COL_ADDR 0x21
#define OLED_CMD_SET_PAGE_ADDR 0x22
#define OLED_CMD_SET_DISP_START_LINE 0x40
#define OLED_CMD_SET_SEGMENT_REMAP 0xA0
#define OLED_CMD_SET_MUX_RATIO 0xA8
#define OLED_CMD_SET_COM_OUTPUT_DIR 0xC0
#define OLED_CMD_SET_DISPLAY_OFFSET 0xD3
#define OLED_CMD_SET_COM_PINS_CFG 0xDA
#define OLED_CMD_SET_CHARGE_PUMP_CFG 0x8D
#define OLED_CMD_SET_PRECHARGE_PERIOD 0xD9
#define OLED_CMD_SET_VCOMH_DESELECT 0xDB
#define OLED_CMD_NOP 0xE3

// 显存 (128x64 位图,每字节8位垂直像素)
static uint8_t displayBuffer[1024]; // 128 * 64 / 8 = 1024 bytes

// 发送命令
static void OLED_SendCommand(uint8_t cmd) {
OLED_CS_LOW();
OLED_DC_CMD();
OLED_SPI_SEND_BYTE(cmd);
OLED_CS_HIGH();
}

// 发送数据
static void OLED_SendData(uint8_t data) {
OLED_CS_LOW();
OLED_DC_DATA();
OLED_SPI_SEND_BYTE(data);
OLED_CS_HIGH();
}

bool Display_Init(void) {
// 1. 复位 OLED
OLED_RST_LOW();
delay_ms(100); // 延时 100ms
OLED_RST_HIGH();
delay_ms(100);

// 2. 初始化命令序列 (根据 SSD1306 初始化流程)
OLED_SendCommand(OLED_CMD_DISPLAY_OFF); // 关闭显示
OLED_SendCommand(OLED_CMD_SET_MUX_RATIO); // 设置多路复用率
OLED_SendCommand(0x3F); // 64MUX
OLED_SendCommand(OLED_CMD_SET_DISPLAY_OFFSET); // 设置显示偏移
OLED_SendCommand(0x00); // 无偏移
OLED_SendCommand(OLED_CMD_SET_DISP_START_LINE | 0x00); // 设置起始行 (0行)
OLED_SendCommand(OLED_CMD_SET_CHARGE_PUMP_CFG); // 设置充电泵
OLED_SendCommand(0x14); // 使能充电泵
OLED_SendCommand(OLED_CMD_SET_MEM_ADDR_MODE); // 设置内存寻址模式
OLED_SendCommand(0x00); // 水平寻址模式
OLED_SendCommand(OLED_CMD_SET_SEGMENT_REMAP | 0x01); // 段重定向,列地址0映射到SEG0
OLED_SendCommand(OLED_CMD_SET_COM_OUTPUT_DIR | 0x08); // COM输出扫描方向,正常模式
OLED_SendCommand(OLED_CMD_SET_COM_PINS_CFG); // 设置COM引脚配置
OLED_SendCommand(0x12); // 硬件配置,根据具体OLED模块配置
OLED_SendCommand(OLED_CMD_SET_CONTRAST); // 设置对比度
OLED_SendCommand(0xCF); // 对比度值 (可调整)
OLED_SendCommand(OLED_CMD_SET_PRECHARGE_PERIOD); // 设置预充电周期
OLED_SendCommand(0xF1); // 预充电周期值 (可调整)
OLED_SendCommand(OLED_CMD_SET_VCOMH_DESELECT); // 设置VCOMH取消选择电平
OLED_SendCommand(0x40); // VCOMH取消选择电平值 (可调整)
OLED_SendCommand(OLED_CMD_DISPLAY_ALLON_RESUME); // 正常显示模式 (非全亮)
OLED_SendCommand(OLED_CMD_NORMAL_DISPLAY); // 正常显示 (非反显)
OLED_SendCommand(OLED_CMD_DISPLAY_ON); // 开启显示

Display_Clear(); // 清屏
return true;
}

void Display_Clear(void) {
memset(displayBuffer, 0x00, sizeof(displayBuffer)); // 清空显存
// 将显存数据写入 OLED
OLED_SendCommand(OLED_CMD_SET_COL_ADDR); // 设置列地址范围
OLED_SendCommand(0x00); // 起始列地址 0
OLED_SendCommand(0x7F); // 结束列地址 127
OLED_SendCommand(OLED_CMD_SET_PAGE_ADDR); // 设置页地址范围
OLED_SendCommand(0x00); // 起始页地址 0
OLED_SendCommand(0x07); // 结束页地址 7 (8页,每页8行)

OLED_CS_LOW();
OLED_DC_DATA();
for (uint16_t i = 0; i < sizeof(displayBuffer); i++) {
OLED_SPI_SEND_BYTE(0x00); // 发送全 0 数据清屏
}
OLED_CS_HIGH();
}

void Display_WriteString(uint8_t x, uint8_t y, const char *str, uint8_t fontSize, uint8_t color) {
uint8_t pageStart = y / 8; // 计算起始页
uint8_t pageOffset = y % 8; // 页内偏移
uint8_t colStart = x;

const uint8_t *font = NULL;
uint8_t fontWidth = 0;
uint8_t fontHeight = 0;

if (fontSize == 816) {
font = font_8x16; // 使用 8x16 字库
fontWidth = 8;
fontHeight = 16;
} else if (fontSize == 68) {
font = font_6x8; // 使用 6x8 字库
fontWidth = 6;
fontHeight = 8;
} else {
return; // 不支持的字体大小
}

while (*str) {
char ch = *str++;
if (ch < ' ' || ch > '~') ch = '?'; // 处理不可见字符

uint16_t charIndex = (ch - ' ') * fontHeight; // 计算字符在字库中的索引

for (uint8_t i = 0; i < fontHeight; i++) {
uint8_t pixelData = font[charIndex + i]; // 获取字符点阵数据

uint8_t pageIndex = pageStart + i / 8; // 计算当前像素所在的页
uint8_t bitOffset = i % 8; // 计算当前像素在页内的位偏移

if (pageIndex < 8 && colStart + fontWidth <= 128) {
for (uint8_t j = 0; j < fontWidth; j++) {
if (pixelData & (1 << (fontWidth - 1 - j))) { // 判断像素是否需要点亮
if (color == 1) { // 白色
displayBuffer[pageIndex * 128 + colStart + j] |= (1 << bitOffset); // 设置像素为白色
} else { // 黑色
displayBuffer[pageIndex * 128 + colStart + j] &= ~(1 << bitOffset); // 设置像素为黑色 (实际上是背景色)
}
}
}
}
}
colStart += fontWidth; // 更新列坐标
if (colStart > 127) break; // 超出屏幕宽度,停止绘制
}

// 更新显示区域 (只更新修改过的区域可以提高效率,这里为了简化,直接更新整个屏幕)
OLED_SendCommand(OLED_CMD_SET_COL_ADDR); // 设置列地址范围
OLED_SendCommand(0x00); // 起始列地址 0
OLED_SendCommand(0x7F); // 结束列地址 127
OLED_SendCommand(OLED_CMD_SET_PAGE_ADDR); // 设置页地址范围
OLED_SendCommand(0x00); // 起始页地址 0
OLED_SendCommand(0x07); // 结束页地址 7

OLED_CS_LOW();
OLED_DC_DATA();
for (uint16_t i = 0; i < sizeof(displayBuffer); i++) {
OLED_SPI_SEND_BYTE(displayBuffer[i]); // 发送显存数据
}
OLED_CS_HIGH();
}

void Display_WriteNumber(uint8_t x, uint8_t y, int32_t num, uint8_t fontSize, uint8_t color) {
char str[16]; // 足够存储 int32_t 类型的数字字符串
sprintf(str, "%ld", num); // 将数字转换为字符串
Display_WriteString(x, y, str, fontSize, color); // 调用字符串显示函数
}

// ... 其他显示相关函数实现 (例如:画点, 画线, 画图形等)
  • config.h, config.c: 配置管理模块 (这里简化为编译时配置,实际可以使用EEPROM/Flash存储配置)

  • ota.h, ota.c: OTA 升级模块 (简化示例,实际OTA升级过程复杂,需要 Bootloader 和固件更新流程)

  • log.h, log.c: 日志记录模块 (可以使用 UART 输出日志)

  • 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
#include "bsp.h"
#include "delay.h"
#include "sensor.h"
#include "display.h"
#include "lora.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <string.h>

#define TASK_SENSOR_PERIOD_MS 5000 // 传感器数据采集任务周期 (5秒)
#define TASK_LORA_PERIOD_MS 10000 // LoRa 数据发送任务周期 (10秒)

void task_sensor_read(void *pvParameters);
void task_lora_send(void *pvParameters);
void task_display_update(void *pvParameters);

int main(void) {
BSP_Init(); // 初始化 BSP
delay_init(); // 初始化延时函数
Sensor_Init(); // 初始化传感器
Display_Init(); // 初始化显示屏

LoRa_ConfigTypeDef loraConfig = {
.frequency = 433000000, // 433MHz 频段
.bandwidth = 0, // 125kHz 带宽 (BW_125kHz)
.spreadingFactor = 7, // 扩频因子 7 (SF_7)
.codingRate = 1, // 编码率 4/5 (CR_4_5)
.txPower = 17 // 发射功率 17dBm
};
LoRa_Init(&loraConfig); // 初始化 LoRa 模块

Display_Clear();
Display_WriteString(0, 0, "IoT Terminal", 816, 1);
Display_WriteString(0, 16, "Initializing...", 68, 1);

// 创建 FreeRTOS 任务
xTaskCreate(task_sensor_read, "SensorTask", 128, NULL, 2, NULL);
xTaskCreate(task_lora_send, "LoRaTask", 256, NULL, 3, NULL);
xTaskCreate(task_display_update, "DisplayTask", 256, NULL, 1, NULL);

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

while (1) {
// 理论上不会运行到这里
}
}

// 传感器数据采集任务
void task_sensor_read(void *pvParameters) {
SensorDataTypeDef sensorData;
TickType_t xLastWakeTime;

xLastWakeTime = xTaskGetTickCount();

while (1) {
if (Sensor_ReadData(&sensorData)) {
printf("Temp: %.2f C, Hum: %.2f%%\r\n", sensorData.temperature, sensorData.humidity); // 通过 UART 输出传感器数据
// ... 可以将传感器数据存储到全局变量或消息队列,供其他任务使用
} else {
printf("Sensor data read failed!\r\n");
}

vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(TASK_SENSOR_PERIOD_MS)); // 定期唤醒
}
}

// LoRa 数据发送任务
void task_lora_send(void *pvParameters) {
SensorDataTypeDef sensorData;
uint8_t txBuffer[32];
uint16_t txLen;
TickType_t xLastWakeTime;

xLastWakeTime = xTaskGetTickCount();

while (1) {
if (Sensor_ReadData(&sensorData)) {
// 格式化要发送的数据 (例如 JSON, CSV, 二进制格式)
txLen = sprintf((char *)txBuffer, "Temp:%.2fC,Hum:%.2f%%", sensorData.temperature, sensorData.humidity);
if (LoRa_SendData(txBuffer, txLen)) {
printf("LoRa data sent: %s\r\n", txBuffer);
} else {
printf("LoRa data send failed!\r\n");
}
} else {
printf("Sensor data read failed for LoRa send!\r\n");
}

vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(TASK_LORA_PERIOD_MS)); // 定期唤醒
}
}

// 显示更新任务
void task_display_update(void *pvParameters) {
SensorDataTypeDef sensorData;
char displayStr[32];
TickType_t xLastWakeTime;

xLastWakeTime = xTaskGetTickCount();

while (1) {
if (Sensor_ReadData(&sensorData)) {
Display_Clear();
sprintf(displayStr, "Temp: %.1f C", sensorData.temperature);
Display_WriteString(0, 0, displayStr, 816, 1);
sprintf(displayStr, "Hum: %.1f %%", sensorData.humidity);
Display_WriteString(0, 16, displayStr, 816, 1);
} else {
Display_Clear();
Display_WriteString(0, 0, "Sensor Error", 816, 1);
}

vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(2000)); // 每 2 秒更新显示
}
}

4. 测试验证

  • 单元测试: 对HAL层、BSP层、驱动层、通信层、应用层各个模块进行单元测试,验证每个模块的功能是否正确。
  • 集成测试: 测试各模块之间的集成,例如传感器驱动和应用层数据采集模块的集成,LoRa通信层和应用层数据传输模块的集成。
  • 系统测试: 进行完整的系统功能测试,验证整个物联网终端设备是否能正常采集数据、传输数据、显示数据,并满足需求分析中的功能性需求。
  • 性能测试: 测试系统的功耗、数据传输速率、响应时间等性能指标,验证是否满足高效性需求。
  • 可靠性测试: 进行长时间运行测试、压力测试、异常情况测试,验证系统的可靠性和稳定性。
  • 安全性测试: 如果涉及到数据安全和设备安全,需要进行安全性测试,例如数据加密测试、漏洞扫描等。

5. 维护升级

  • 固件升级 (OTA): 实现 Over-The-Air 固件升级功能,方便远程更新固件,修复Bug,添加新功能。
  • 日志记录: 完善日志记录功能,记录系统运行状态、错误信息等,方便问题排查和维护。
  • 模块化设计: 采用模块化设计,方便后期维护和功能扩展,例如添加新的传感器驱动、支持新的网络协议等。
  • 版本控制: 使用版本控制工具 (例如 Git) 管理代码,方便代码维护和版本迭代。
  • 文档编写: 编写详细的开发文档、用户手册、维护手册,方便团队协作和后期维护。

总结

以上代码示例和架构设计提供了一个基于N32/STM32物联网终端设备,支持LoRa网络的嵌入式系统开发框架。实际项目开发中,需要根据具体的硬件平台、LoRa模块、传感器型号、显示屏型号以及具体的应用需求,进行详细的代码开发、测试和优化。代码量超过3000行可以通过完善各个模块的细节实现,例如更完整的HAL驱动、更复杂的LoRa协议栈实现、更丰富的OLED显示功能、更完善的配置管理和OTA升级流程等。

这个项目的设计目标是构建一个可靠、高效、可扩展的系统平台,通过分层架构、模块化设计、严格的测试验证和完善的维护升级流程,确保系统能够长期稳定运行,并方便后续的功能扩展和维护。

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