第04章 定位器项目
约 10833 字大约 36 分钟
2026-01-17
4.1 公共层
4.1.1 调试模块
① CubeMX配置

② 代码
Com_Debug.h
#ifndef __COM_DEBUG_H__
#define __COM_DEBUG_H__
#include <stdio.h>
#include <string.h>
#include "usart.h"
#define DEBUG_MODE 1
#if DEBUG_MODE==1
// 如果路径中有 /, 取 / 后面的,如果没有 /, 直接取全部
#define BASENAME_PRE strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__
// 如果路径有 \, 取 \ 后面的,如果没有 \, 直接取全部
#define BASENAME strrchr(BASENAME_PRE, '\\') ? strrchr(BASENAME_PRE, '\\') + 1 : BASENAME_PRE
#define DEBUG_PRINT(fmt, ...) printf("[%s:%d] " fmt, BASENAME, __LINE__, ##__VA_ARGS__)
#define DEBUG_PRINTLN(fmt, ...) printf("[%s:%d] " fmt "\n", BASENAME, __LINE__, ##__VA_ARGS__)
#else
#define BASENAME_PRE
#define BASENAME
#define DEBUG_PRINT(fmt, ...)
#define DEBUG_PRINTLN(fmt, ...)
#endif
#endif /* __COM_DEBUG_H__ */Com_Debug.c
#include "Com_Debug.h"
// 重定义 fputc
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}4.1.2 公共配置模块
代码
Com_Config.h
#ifndef __COM_CONFIG_H__
#define __COM_CONFIG_H__
#include <stdint.h>
// 定义表示状态的枚举类型
typedef enum
{
COM_OK = 0,
COM_ERROR = 1
} Status_Typedef;
// 定义要传输的数据的结构体类型
typedef struct
{
// 唯一标识
uint8_t uuid[25];
// 步数
uint32_t steps;
// 纬度信息
float lat;
uint8_t lat_dir[2]; // N:北纬, S:南纬
// 经度信息
float lon;
uint8_t lon_dir[2]; // E:东经, W:西经
// 日期时间
uint8_t datetime[20]; // 格式: "YYYY-MM-DD HH:MM:SS"
} UploadData_Typedef;
#endif /* __COM_CONFIG_H__ */4.1.3 工具函数库
代码
Com_Util.h
#ifndef __COM_UTIL_H__
#define __COM_UTIL_H__
#include <time.h>
#include <stdio.h>
#include "main.h"
/**
* @brief 微秒级延时
*
* @param us 延时时间,单位:微秒
*/
void Com_Delay_us(uint16_t us);
/**
* @brief 毫秒级延时
*
* @param ms 延时时间,单位:毫秒
*/
void Com_Delay_ms(uint16_t ms);
/**
* @brief 秒级延时
*
* @param s 延时时间,单位:秒
*/
void Com_Delay_s(uint16_t s);
/**
* @brief 将UTC时间转换为本地时间
*
* @param utc_dt UTC时间,
* @param local_dt 本地时间存在此处
*/
void Com_UTC2Local(uint8_t *utc_dt, uint8_t *local_dt);
#endif /* __COM_UTIL_H__ */Com_Util.c
#include "Com_Util.h"
/**
* @brief 微秒级延时
*
* @param us 延时时间,单位:微秒
*/
void Com_Delay_us(uint16_t us)
{
uint32_t count = us * 72 / 7;
while (count--)
{
__NOP();
}
}
/**
* @brief 毫秒级延时
*
* @param ms 延时时间,单位:毫秒
*/
void Com_Delay_ms(uint16_t ms)
{
HAL_Delay(ms);
}
/**
* @brief 秒级延时
*
* @param s 延时时间,单位:秒
*/
void Com_Delay_s(uint16_t s)
{
for (uint16_t i = 0; i < s; i++)
{
HAL_Delay(1000);
}
}
/**
* @brief 将UTC时间转换为本地时间
*
* @param utc_dt UTC时间,
* @param local_dt 本地时间存在此处
*/
void Com_UTC2Local(uint8_t *utc_dt, uint8_t *local_dt)
{
// 1. 将UTC日期时间转为tm类型的结构体
struct tm utc_tm;
sscanf((char *)utc_dt, "%d-%d-%d %d:%d:%d", &utc_tm.tm_year, &utc_tm.tm_mon, &utc_tm.tm_mday, &utc_tm.tm_hour, &utc_tm.tm_min, &utc_tm.tm_sec);
utc_tm.tm_year -= 1900;
utc_tm.tm_mon -= 1;
// 2. 将UTC日期时间机构体转为时间戳
time_t timestamp = mktime(&utc_tm);
// 3. 将时间戳加8小时
timestamp += (8 * 60 * 60);
// 4. 将时间戳转为tm类型的机构体,再转为 YYYYY-MM-DD HH:MM:SS 格式字符串
struct tm *local_tm = localtime(×tamp);
sprintf((char *)local_dt, "%04d-%02d-%02d %02d:%02d:%02d", local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday, local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec);
}4.2 计步模块
4.2.1 原理图

4.2.2 CubeMX 设置
I2C 设置:

GPIO 设置:

4.2.3 DS3553 功能描述
① DS3553 从设备地址:

② DS3553 读写时序:

DS3553 三种工作模式:

DS3553 相关寄存器:
| 寄存器名称 | 读写属性 | 地址 | 默认值 | 功能描述 | 可复位 |
|---|---|---|---|---|---|
| CHIP_ID | 只读 | 0x01 | 0x13 | 芯片标识符,固定为 0x13H。 | 否 |
| USER_SET | 读写 | 0xC3 | 0x18 | 用户配置寄存器,用于设置工作模式 (如计步模式、中断使能等。 | 是 |
| STEP_CNT_L | 只读 | 0xC4 | - | 计步数据的低8位。 | 是 |
| STEP_CNT_M | 只读 | 0xC5 | - | 计步数据的中8位。 | 是 |
| STEP_CNT_H | 只读 | 0xC6 | - | 计步数据的高8位。 | 是 |

4.2.4 代码
Int_DS3553.h
#ifndef __INT_DS3553_H__
#define __INT_DS3553_H__
#include "i2c.h"
#include "Com_Util.h"
#include "Com_Debug.h"
// 定义宏:DS3553的I2C地址,将7个bit放在高位
#define DS3553_ADDR 0x4E
// 定义宏: 拉低、拉高片选信号
#define DS3553_CS_LOW HAL_GPIO_WritePin(DS3553_CS_GPIO_Port, DS3553_CS_Pin, GPIO_PIN_RESET)
#define DS3553_CS_HIGH HAL_GPIO_WritePin(DS3553_CS_GPIO_Port, DS3553_CS_Pin, GPIO_PIN_SET)
/**
* @brief 初始化DS3553
*
*/
void Int_DS3553_Init(void);
/**
* @brief 获取DS3553的步数
*
* @return uint32_t 步数
*/
uint32_t Int_DS3553_GetSteps(void);
#endif /* __INT_DS3553_H__ */Int_DS3553.c
#include "Int_DS3553.h"
// 静态函数:向DS3553寄存器写入数据
static void Int_DS3553_WriteReg(uint8_t reg_addr, uint8_t reg_data)
{
// 拉低片选信号, 并至少延时3ms,延时5ms
DS3553_CS_LOW;
Com_Delay_ms(5);
// I2C 通信, 数据长度是1
HAL_I2C_Mem_Write(&hi2c1, DS3553_ADDR, reg_addr, I2C_MEMADD_SIZE_8BIT, ®_data, 1, HAL_MAX_DELAY);
// 拉高片选信号,至少延时10ms,这里延时12ms
DS3553_CS_HIGH;
Com_Delay_ms(12);
}
// 静态函数:从DS3553寄存器读取数据
static uint8_t Int_DS3553_ReadReg(uint8_t reg_addr)
{
// 拉低片选信号, 并至少延时3ms,延时5ms
DS3553_CS_LOW;
Com_Delay_ms(5);
// 定义变量,用于保存接收到的数据
uint8_t reg_data = 0;
// I2C 通信,
HAL_I2C_Mem_Read(&hi2c1, DS3553_ADDR, reg_addr, I2C_MEMADD_SIZE_8BIT, ®_data, 1, HAL_MAX_DELAY);
// 拉高片选信号,至少延时10ms,这里延时12ms
DS3553_CS_HIGH;
Com_Delay_ms(12);
return reg_data;
}
/**
* @brief 初始化DS3553
*
*/
void Int_DS3553_Init(void)
{
// 1. 获取DS3553的ID
uint8_t chip_id = Int_DS3553_ReadReg(0x01);
DEBUG_PRINTLN("Chip ID: 0x%02X", chip_id);
// 2. 设置 Set 寄存器
// 2.1 定义变量,作为 Set 寄存器的值
uint8_t set_value = 0x18;
// 2.2 将第4位置0,关闭计数脉冲输出
set_value &= ~(1 << 4);
// 2.3 将第1位设置为1,第0位设置为0, 设置为计数器算法
set_value |= (1 << 1);
set_value &= ~(1 << 0);
// 2.4 使用I2C通信,将set_value写入Set寄存器
Int_DS3553_WriteReg(0xC3, set_value);
DEBUG_PRINTLN("DS3553 初始化完成 ...");
}
/**
* @brief 获取DS3553的步数
*
* @return uint32_t 步数
*/
uint32_t Int_DS3553_GetSteps(void)
{
// 从 STEP_CNT_H、STEP_CNT_M、STEP_CNT_L 寄存器读取数据
return (Int_DS3553_ReadReg(0xC6) << 16) | (Int_DS3553_ReadReg(0xC5) << 8) | Int_DS3553_ReadReg(0xC4);
}main.c (验证)
int main()
{
// 初始化 DS3553
Int_DS3553_Init();
while (1)
{
// 获取步数并打印
DEBUG_PRINTLN("步数: %d", Int_DS3553_GetSteps());
// 延时1s
Com_Delay_ms(1000);
}4.2.5 相关 HAL 库函数总结
HAL_I2C_Mem_Read()
HAL_I2C_Mem_Write()
HAL_I2C_Master_Transmit()
HAL_I2C_Master_Receive()4.3 GPS 模块
4.3.1 原理图




4.3.2 CubeMX 设置
USART2 设置:

GPIO 设置

4.3.3 AT6558R 功能描述
① 芯片框图:

② 电源工作模式:

全工作模式:所有电源正常供电,且 ON_OFF 管脚为高电平时,芯片处于全工作模式,进行正常的信号接收和解算。
休眠模式:所有电源正常供电,ON_OFF 管脚拉低,将关闭 DCDC 和内置 LDO,射频电路和基带电路停止工作,进入低功耗休眠状态。当 ON_OFF 管脚拉高后,芯片将自动恢复全工作模式(相当于热启动)。
电池备份模式:关闭除 VDD_BK 之外的所有电源,这时只需要极小的电流维持 RTC 时钟和备份 RAM 即可。电源恢复后,导航程序可以从备份 RAM 恢复,以实现快速的热启动。
③ 卫星模式:
芯片有两种方法进行模式配置。 1)通过 UART 发命令:可以将系统模式切换为 BDS/GPS/ GLONASS 的组合,比如 BDS+GPS双模,或者 GPS+GLONASS 双模,或者 GPS 单模等。
2)通过 GPIO8 设置:GPIO8 悬空或者高电平,芯片工作在 BDS+GPS 双模状态。GPIO8 低电平时,芯片工作在 GPS+GLONASS 双模状态。
④ UART 通信:

4.3.4 NMEA 协议


设置定位更新率:

配置卫星工作模式:

查询产品信息:

4.3.5 代码
Int_AT6558R.h
#ifndef __INT_AT6558R_H__
#define __INT_AT6558R_H__
#include <string.h>
#include "usart.h"
#include "Com_Debug.h"
// 宏定义:MCU向AT655R发的NMEA命令:设置更新频率
#define NMEA_CMD_SET_UP_RATE_1HZ "PCAS02,1000"
#define NMEA_CMD_SET_UP_RATE_2HZ "PCAS02,500"
#define NMEA_CMD_SET_UP_RATE_5HZ "PCAS02,200"
// 宏定义: MCU向AT655R发的NMEA命令:查询产品信息
#define NMEA_CMD_QUERY_PRODUCT_INFO "PCAS06,0"
// 声明全局变量
// 全局变量:接收GPS信息的缓冲区
extern uint8_t g_rx_gps_buff[1024];
// 全局变量:接收GPS信息的缓冲区长度
extern uint16_t g_rx_gps_len;
/**
* @brief 初始化AT6558R模块
*
*/
void Int_AT6558R_Init(void);
/**
* @brief 获取AT6558R模块的GPS信息
*
*/
void Int_AT6558R_GetGPSInfo(void);
/**
* @brief 进入AT6558R模块的低功耗模式
*
*/
void Int_AT6558R_EnterLowPowerMode(void);
/**
* @brief 退出AT6558R模块的低功耗模式
*
*/
void Int_AT6558R_ExitLowPowerMode(void);
#endif /* __INT_AT6558R_H__ */Int_AT6558.c
#include "Int_AT6558R.h"
// 全局变量:向AT6558R模块发送NMEA命令的缓冲区
static uint8_t s_emea_cmd[64];
// 全局变量: 作为接收缓冲区
uint8_t g_rx_gps_buff[1024];
// 全局变量:实际接收的长度
uint16_t g_rx_gps_len = 0;
// 静态函数:向AT6558R模块发送NMEA命令
static void Int_AT6558R_SendNMEACommand(uint8_t *cmd)
{
// 1. 计算校验和
uint8_t checksum = cmd[0];
for (int i = 1; cmd[i] != '\0'; i++)
{
checksum ^= cmd[i];
}
// 2. 拼接完整的EMEA命令
memset(s_emea_cmd, 0, 64);
sprintf((char *)s_emea_cmd, "$%s*%02X\r\n", cmd, checksum);
// 3. 使用USART2向AT6558R模块发送NMEA命令
HAL_UART_Transmit(&huart2, s_emea_cmd, strlen((char *)s_emea_cmd), HAL_MAX_DELAY);
DEBUG_PRINTLN("向AT6558R发送的命令: %s", s_emea_cmd);
}
/**
* @brief 初始化AT6558R模块
*
*/
void Int_AT6558R_Init(void)
{
// 1. 退出低功耗
Int_AT6558R_ExitLowPowerMode();
// 2. 设置更新频率为1Hz
Int_AT6558R_SendNMEACommand(NMEA_CMD_SET_UP_RATE_1HZ);
// 3. 获取产品信息
// Int_AT6558R_SendNMEACommand(NMEA_CMD_QUERY_PRODUCT_INFO);
// // 使用串口USART2接收数据
// HAL_UARTEx_ReceiveToIdle(&huart2, g_rx_gps_buff, 1024, &g_rx_gps_len, HAL_MAX_DELAY);
// DEBUG_PRINTLN("\n从AT6558R接收的产品信息: %s长度: %d \n", g_rx_gps_buff, g_rx_gps_len);
}
/**
* @brief 获取AT6558R模块的GPS信息
*
*/
void Int_AT6558R_GetGPSInfo(void)
{
// 清空接收缓冲区
memset(g_rx_gps_buff, 0, 1024);
g_rx_gps_len = 0;
// 使用串口USART2接收数据
HAL_UARTEx_ReceiveToIdle(&huart2, g_rx_gps_buff, 1024, &g_rx_gps_len, HAL_MAX_DELAY);
// DEBUG_PRINTLN("从AT6558R接收的GPS信息: \n%s长度: %d \n", g_rx_gps_buff, g_rx_gps_len);
}
/**
* @brief 进入AT6558R模块的低功耗模式
*
*/
void Int_AT6558R_EnterLowPowerMode(void)
{
// 关闭处BK电源外所有的电源,只需拉低AT6558R_EN引脚,停止3V供电
HAL_GPIO_WritePin(AT6558R_EN_GPIO_Port, AT6558R_EN_Pin, GPIO_PIN_RESET);
}
/**
* @brief 退出AT6558R模块的低功耗模式
*
*/
void Int_AT6558R_ExitLowPowerMode(void)
{
// 恢复AT6558R_EN引脚为高电平,开启3V供电
HAL_GPIO_WritePin(AT6558R_EN_GPIO_Port, AT6558R_EN_Pin, GPIO_PIN_SET);
}main.c (数据处理):
int main(void)
{
/* 代码省略 ... */
DEBUG_PRINTLN("定时器项目 ----------------------- \n");
// 初始化 AT6558R
Int_AT6558R_Init();
// 获取有效GPS定位信息
uint8_t *gpsrmc = NULL; // 保存最小定位信息的字符串
uint8_t status = 0; // 保存定位状态
do
{
// 延时1s
Com_Delay_ms(1000);
// 调用函数,获取AT6558R的NMEA格式的GPS信息
Int_AT6558R_GetGPSInfo();
// 从GPS信息中提取RMC部分(推荐的最小定位信息) -----------------------------------------
// gpsrmc = (uint8_t *)strstr((const char *)g_rx_gps_buff, "$GNRMC");
// gpsrmc = "$GNRMC,084724.000,V,,,,,,,181125,,,N,V*2A";
// gpsrmc = "$GNRMC,,V,,,,,,,181125,,,N,V*2A";
gpsrmc = "$GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D";
if (gpsrmc == NULL)
{
DEBUG_PRINTLN("未获取到最小定位信息");
continue;
}
// DEBUG_PRINTLN("最小定位信息: %s", gpsrmc);
// 从RMC(最小定位信息中)获取到状态
/*
无效: $GNRMC,,V,,,,,,,,,,N,V*37
有效: $GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D
*/
sscanf((char *)gpsrmc, "$GNRMC,%*[^VA]%c", &status);
DEBUG_PRINTLN("获取到的定位状态:%c", status);
} while (status != 'A');
DEBUG_PRINTLN("定位成功!");
// 从RMC信息中提取经度、经度方向、纬度、纬度方向、日期、时间 --------------------------------
// 定义相关变量
float lat; // 纬度
uint8_t lat_dir; // 纬度方向,N表示北,S表示南
float lon; // 经度
uint8_t lon_dir; // 经度方向,E表示东,W表示西
uint8_t date[7]; // 日期, 格式: ddmmyy
uint8_t time[7]; // 时间, 格式: hhmmss
uint8_t datetime[20]; // 日期时间,格式: YYYY-MM-DD HH:MM:SS
// 从RMC字符串中提取数据
// $GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D
sscanf((char *)gpsrmc, "$GNRMC,%6c%*6c,%f,%c,%f,%c,%*f,%*f,%6c", (char *)time, &lat, &lat_dir, &lon, &lon_dir, (char *)date);
// 打印提取到的数据
// DEBUG_PRINTLN("时间: %s", time);
// DEBUG_PRINTLN("日期: %s", date);
// DEBUG_PRINTLN("纬度: %f, 纬度方向: %c", lat, lat_dir);
// DEBUG_PRINTLN("经度: %f, 经度方向: %c", lon, lon_dir);
// 处理经纬度信息 -----------------------------------------------
// 换算纬度, 使用度单位
lat = (uint8_t)(lat / 100) + (lat - (uint8_t)(lat / 100) * 100) / 60;
// 换算经度, 使用度单位
lon = (uint8_t)(lon / 100) + (lon - (uint8_t)(lon / 100) * 100) / 60;
DEBUG_PRINTLN("换算后的纬度: %f, 换算后的经度: %f", lat, lon);
// 处理日期、时间格式 ----------------------------------------------
sprintf((char *)datetime, "20%c%c-%c%c-%c%c %c%c:%c%c:%c%c", date[4], date[5], date[2], date[3], date[0], date[1], time[0], time[1], time[2], time[3], time[4], time[5]);
// 转为本地日期时间
Com_UTC2Local(datetime, datetime);
DEBUG_PRINTLN("处理后的日期时间: %s", datetime);
/* 代码省略 ... */
}4.3.6 相关 HAL 库函数总结
HAL_UART_Transmit()
HAL_UARTEx_ReceiveToIdle()4.4 NB-IOT 模块
4.4.1 原理图


4.4.2 CubeMX 配置
USART3 设置:

GPIO 设置:

4.4.3 QS-100 功能描述
① 复位和唤醒:

② 低功耗工作模式:


③ 串口:

4.4.4 AT 命令
| 命令描述 | 命令格式 |
|---|---|
| 产品序列号获取 | AT+CGSN[=<snt>]\r\n |
| 设备软重启 | AT+RB\r\n |
| 快速断电(进入PSM模式) | AT+FASTOFF[=<type>]\r\n |
| 串口回显 | ATE1\r\n |
| 附着(网络是否连接) | AT+CGATT=<state>\r\n |
| 创建客户端 | AT+NSOCR=<type>,<protocol>,<listenport>[,<receive <br/>control>,[af_type]]\r\n |
| 连接服务器 | AT++NSOCO=<socket>,<remote_addr>,<remote_port>\r\n |
| 向服务器发送数据 | AT+NSOSD=<socket>,<length>,<data>[,<flag>[,<sequence<br/>>]]\r\n |
| 查询发送状态 | AT+SEQUENCE=<socket>,<sequence>\r\n |
| 关闭连接 | AT+NSOCL=<socket>\r\n |
4.4.5 TCP/IP 网络模型

4.4.6 代码
Int_QS100.h
#ifndef __INT_QS100_H__
#define __INT_QS100_H__
#include <string.h>
#include <stdlib.h>
#include "usart.h"
#include "Com_Debug.h"
#include "Com_Util.h"
#include "Com_Config.h"
// 宏定义:SOCKET操作时每个步骤尝试的次数
#define QS100_SOCK_RETRY_COUNT 15
// 宏定义:发送数据的sequence id
#define QS100_SEQ_ID 1
/**
* @brief 初始化QS100模块
*
*/
void Int_QS100_Init(void);
/**
* @brief 使用QS100向服务器发送数据
*
* @param tx_data 要发送的数据
* @param tx_len 要发送的数据长度
* @return Status_Typedef 发送状态
*/
Status_Typedef Int_QS100_SendData(uint8_t *tx_data, uint16_t tx_len);
/**
* @brief 进入低功耗模式(PSM)
*
*/
void Int_QS100_EnterLowPowerMode(void);
/**
* @brief 退出低功耗模式(PSM)
*
*/
void Int_QS100_ExitLowPowerMode(void);
#endif /* __INT_QS100_H__ */Int_QS00.c
#include "Int_QS100.h"
// 全局变量:作为QS100完整响应内容的接收缓冲区
uint8_t g_qs100_rx_full_buffer[512];
// 全局变量:作为QS100完整响应内容的接收缓冲区长度
uint16_t g_qs100_rx_full_buffer_len = 0;
// 全局变量:作为QS100单词响应内容的接收缓冲区
uint8_t g_qs100_rx_buffer[128];
// 全局变量:作为QS100单词响应内容的接收缓冲区长度
uint16_t g_qs100_rx_buffer_len = 0;
// 全局变量:保存socket_id, 用于后续的操作
uint8_t g_socket_id = 0;
// 全局变量:用于拼接AT命令
uint8_t g_at_cmd[512];
// 全局变量:用于保存要发送的十六进制字符串
uint8_t g_hex_str[512];
// 静态函数:向QS100发送AT命令,接收响应
static void Int_QS100_SendATCommand(uint8_t *at_cmd)
{
DEBUG_PRINTLN("发送AT命令开始 --------------------------");
// 1. 使用USART3向QS100发送AT命令
HAL_UART_Transmit(&huart3, at_cmd, strlen((char *)at_cmd), 1000);
DEBUG_PRINTLN("已向QS100发送AT命令: %s", at_cmd);
// 2. 接收QS100100的响应
// 清空完整接收缓冲区
memset(g_qs100_rx_full_buffer, 0, 512);
g_qs100_rx_full_buffer_len = 0;
// 不断接收知道接收到完整的响应内容(有OK或ERROR)
do
{
// 2.1 接收1次响应
memset(g_qs100_rx_buffer, 0, 128);
g_qs100_rx_buffer_len = 0;
HAL_UARTEx_ReceiveToIdle(&huart3, g_qs100_rx_buffer, 128, &g_qs100_rx_buffer_len, HAL_MAX_DELAY);
// 2.2 如果能接收到响应内容,添加完整的接收缓冲区
if (g_qs100_rx_buffer_len > 0)
{
strcpy((char *)(g_qs100_rx_full_buffer + g_qs100_rx_full_buffer_len), (char *)g_qs100_rx_buffer);
g_qs100_rx_full_buffer_len += g_qs100_rx_buffer_len;
}
// 循环条件: 接收缓冲区既没有OK也没有ERROR,继续下一次循环
} while (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL && strstr((char *)g_qs100_rx_full_buffer, "ERROR") == NULL);
DEBUG_PRINTLN("接收QS100响应完成:\n%s\n长度:%d", g_qs100_rx_full_buffer, g_qs100_rx_full_buffer_len);
DEBUG_PRINTLN("发送AT命令结束 ---------------------------");
}
// 静态函数:软复位QS100
static void Int_QS100_SoftReset(void)
{
// 1. 使用USART3向QS100发送AT命令
HAL_UART_Transmit(&huart3, "AT+RB\r\n", 7, HAL_MAX_DELAY);
DEBUG_PRINTLN("已向QS100发送软复位命令");
// 2. 接收QS100100的响应
// 清空完整接收缓冲区
memset(g_qs100_rx_full_buffer, 0, 512);
g_qs100_rx_full_buffer_len = 0;
// 不断接收知道接收到完整的响应内容(有OK或ERROR)
do
{
// 2.1 接收1次响应
memset(g_qs100_rx_buffer, 0, 128);
g_qs100_rx_buffer_len = 0;
HAL_UARTEx_ReceiveToIdle(&huart3, g_qs100_rx_buffer, 128, &g_qs100_rx_buffer_len, HAL_MAX_DELAY);
// 2.2 如果能接收到响应内容,添加完整的接收缓冲区
if (g_qs100_rx_buffer_len > 0)
{
strcpy((char *)(g_qs100_rx_full_buffer + g_qs100_rx_full_buffer_len), (char *)g_qs100_rx_buffer);
g_qs100_rx_full_buffer_len += g_qs100_rx_buffer_len;
}
// 循环条件: 接收缓冲区既没有OK也没有ERROR,继续下一次循环
} while (strstr((char *)g_qs100_rx_full_buffer, "^SIMST") == NULL);
// DEBUG_PRINTLN("接收QS100响应完成:\n%s\n长度:%d", g_qs100_rx_full_buffer, g_qs100_rx_full_buffer_len);
if (strstr((char *)g_qs100_rx_full_buffer, "^SIMST:1"))
{
DEBUG_PRINTLN("QS100 软复位成功...");
}
else
{
DEBUG_PRINTLN("QS100 软复位失败, SIM卡状态异常 ...");
}
// 延时5s,等待QS100状态稳定
Com_Delay_ms(5000);
}
// 静态函数:检查网络状态
static Status_Typedef Int_QS100_CheckNetwork(void)
{
// 1. 发送AT命令: 附着
Int_QS100_SendATCommand("AT+CGATT=1\r\n");
// 2. 判断是否附着成功
return strstr((char *)g_qs100_rx_full_buffer, "OK") ? STATUS_OK : STATUS_ERROR;
}
// 静态函数:创建Client
static Status_Typedef Int_QS100_CreateClient(void)
{
// 1. 发送AT命令: 创建Client
Int_QS100_SendATCommand("AT+NSOCR=STREAM,6,0,1\r\n");
// 2. 如果创建失败
if (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL)
{
return STATUS_ERROR;
}
// 3. 从响应中提取socket_id
g_socket_id = (uint8_t)atoi(strstr((char *)g_qs100_rx_full_buffer, "+NSOCR:") + 7);
return STATUS_OK;
}
// 静态函数:连接服务器
static Status_Typedef Int_QS100_Connect(void)
{
// 拼接AT命令
memset(g_at_cmd, 0, 512);
sprintf((char *)g_at_cmd, "AT+NSOCO=%d,%s,%d\r\n", g_socket_id, NBIOT_SERVER_IP, NBIOT_SERVER_PORT);
// 2. 发送AT命令: 连接服务器
Int_QS100_SendATCommand(g_at_cmd);
// 3. 如果连接失败
if (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL)
{
return STATUS_ERROR;
}
return STATUS_OK;
}
// 静态函数:向服务器发送数据
static Status_Typedef Int_QS100_SendDataToServer(uint8_t *tx_data, uint16_t tx_len)
{
// 发出数据 -------------------------------------------
// 将要发送的字符串数据转为十六进制字符串的形式
memset(g_hex_str, 0, 512);
for (uint16_t i = 0; i < tx_len; i++)
{
sprintf((char *)g_hex_str + 2 * i, "%02X", tx_data[i]);
}
DEBUG_PRINTLN("要发送的十六进制字符串:%s", g_hex_str);
// 拼接AT命令
memset(g_at_cmd, 0, 512);
sprintf((char *)g_at_cmd, "AT+NSOSD=%d,%d,%s,0x200,%d\r\n", g_socket_id, tx_len, g_hex_str, QS100_SEQ_ID);
// DEBUG_PRINTLN("要发送的AT命令:%s", g_at_cmd);
// 发送AT命令
Int_QS100_SendATCommand(g_at_cmd);
if (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL)
{
return STATUS_ERROR;
}
// 查询接收状态 ---------------------------------------
// 拼接AT命令
memset(g_at_cmd, 0, 512);
sprintf((char *)g_at_cmd, "AT+SEQUENCE=%d,%d\r\n", g_socket_id, QS100_SEQ_ID);
// DEBUG_PRINTLN("要发送的AT命令:%s", g_at_cmd);
// 不断发送AT命令,直到获取到状态OK
uint8_t seq_status = 0;
do
{
Int_QS100_SendATCommand(g_at_cmd);
if (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL)
{
return STATUS_ERROR;
}
// 从响应中提取sequence status
seq_status = atoi(strstr((char *)g_qs100_rx_full_buffer, "OK") - 5);
DEBUG_PRINTLN("查询到的状态: %d ", seq_status);
// 延时10ms
Com_Delay_ms(100);
} while (seq_status != 0 && seq_status != 1);
if (seq_status == 0)
{
return STATUS_ERROR;
}
return STATUS_OK;
}
// 静态函数:关闭Client
// 静态函数:关闭Client
static Status_Typedef Int_QS100_CloseClient(void)
{
// 1. 拼接AT命令
memset(g_at_cmd, 0, 512);
sprintf((char *)g_at_cmd, "AT+NSOCL=%d\r\n", g_socket_id);
// 2. 发送AT命令
Int_QS100_SendATCommand(g_at_cmd);
// 3. 判断
if (strstr((char *)g_qs100_rx_full_buffer, "OK") == NULL)
{
return STATUS_ERROR;
}
return STATUS_OK;
}
/* ******************************************************** */
/**
* @brief 初始化QS100模块
*
*/
void Int_QS100_Init(void)
{
// 1. 唤醒QS100模块
Int_QS100_ExitLowPowerMode();
DEBUG_PRINTLN("QS100 被唤醒...");
// 2. 软复位
DEBUG_PRINTLN("\nQS100 软复位...");
Int_QS100_SoftReset();
// 3.设置回显
DEBUG_PRINTLN("\nQS100 回显设置 ...");
Int_QS100_SendATCommand("ATE1\r\n");
// 4. 获取QS100的序列号
DEBUG_PRINTLN("\nQS100 获取序列号 ...");
Int_QS100_SendATCommand("AT+CGSN=0\r\n");
}
/**
* @brief 使用QS100向服务器发送数据
*
* @param tx_data 要发送的数据
* @param tx_len 要发送的数据长度
* @return Status_Typedef 发送状态
*/
Status_Typedef Int_QS100_SendData(uint8_t *tx_data, uint16_t tx_len)
{
uint8_t try_count = 0;
Status_Typedef status = STATUS_ERROR;
// 1. 附着(检查网络状态) --------------------
do
{
status = Int_QS100_CheckNetwork();
try_count++;
// 延时1s
Com_Delay_ms(1000);
} while (status != STATUS_OK && try_count < QS100_SOCK_RETRY_COUNT);
if (status != STATUS_OK)
{
DEBUG_PRINTLN("QS100 附着失败, 重试次数超过最大次数 ...");
return STATUS_ERROR;
}
DEBUG_PRINTLN("QS100 附着成功 ...");
// 2. 创建 Client ---------------------------
try_count = 0;
do
{
status = Int_QS100_CreateClient();
try_count++;
// 延时1s
Com_Delay_ms(1000);
} while (status != STATUS_OK && try_count < QS100_SOCK_RETRY_COUNT);
if (status != STATUS_OK)
{
DEBUG_PRINTLN("QS100 创建Client失败, 重试次数超过最大次数 ...");
return STATUS_ERROR;
}
DEBUG_PRINTLN("QS100 创建Client成功, socket_id: %d ...", g_socket_id);
// 3. 连接服务器 ----------------------------
try_count = 0;
do
{
status = Int_QS100_Connect();
try_count++;
// 延时1s
Com_Delay_ms(1000);
} while (status != STATUS_OK && try_count < QS100_SOCK_RETRY_COUNT);
if (status != STATUS_OK)
{
DEBUG_PRINTLN("QS100 连接服务器失败, 重试次数超过最大次数 ...");
return STATUS_ERROR;
}
DEBUG_PRINTLN("QS100 连接服务器成功 ...");
// 4. 向服务发送数据 ------------------------
try_count = 0;
do
{
status = Int_QS100_SendDataToServer(tx_data, tx_len);
try_count++;
// 延时1s
Com_Delay_ms(1000);
} while (status != STATUS_OK && try_count < QS100_SOCK_RETRY_COUNT);
if (status != STATUS_OK)
{
DEBUG_PRINTLN("QS100 发送数据失败, 重试次数超过最大次数 ...");
return STATUS_ERROR;
}
DEBUG_PRINTLN("QS100 发送数据成功 ...");
// 5. 关闭连接 ------------------------------
try_count = 0;
do
{
status = Int_QS100_CloseClient();
try_count++;
// 延时1s
Com_Delay_ms(1000);
} while (status != STATUS_OK && try_count < QS100_SOCK_RETRY_COUNT);
if (status != STATUS_OK)
{
DEBUG_PRINTLN("QS100 关闭Client失败, 重试次数超过最大次数 ...");
return STATUS_ERROR;
}
DEBUG_PRINTLN("QS100 关闭Client成功 ...");
return STATUS_OK;
}
/**
* @brief 进入低功耗模式(PSM)
*
*/
void Int_QS100_EnterLowPowerMode(void)
{
// 发送AT命令,进入PSM模式
Int_QS100_SendATCommand("AT+FASTOFF=0\r\n");
}
/**
* @brief 退出低功耗模式(PSM)
*
*/
void Int_QS100_ExitLowPowerMode(void)
{
// 1. 拉高唤醒引脚,并持续 100us 到 5s, 这里延时1s
HAL_GPIO_WritePin(QS100_WKUP_GPIO_Port, QS100_WKUP_Pin, GPIO_PIN_SET);
Com_Delay_ms(1000);
// 2. 拉低唤醒引脚,并延时100ms用于稳定
HAL_GPIO_WritePin(QS100_WKUP_GPIO_Port, QS100_WKUP_Pin, GPIO_PIN_RESET);
Com_Delay_ms(1000);
}main.c (验证):
int main()
{
// 初始化QS100
Int_QS100_Init();
// 向服务发送数据
Int_QS100_SendData("Hello QS100!", 12);
}4.5 LoRa 模块
4.5.1 原理图


4.5.2 CubeMX 配置
SPI 设置:

GPIO 设置:

4.5.3 LoRa 通信概述
① 常见物联网通信
(1)远距离高速率的传输协议
典型协议包括蜂窝网络通信技术,如3G、4G、5G相关技术等,这是我们目前移动通信使用的典型技术。
(2)近距离高速率传输技术,
WiFi、传统蓝牙等,这些技术传输距离在几十到几百米级别,主要用在家庭环境和日常应用中,使用非常广泛。
(3)近距离低功耗传输技术
传统物联网中ZigBee、RFID、低功耗蓝牙。上面三类技术大都要求较高的信噪比,对障碍的穿透性较小,无法在复杂环境中实现远距离低功耗传输。
低功耗广域网(LPWAN)技术填补这一技术空白。
② 低功耗广域网 (LPWAN)
LPWAN(Low Power Wide Area Network)指的是低功耗广域网,其特点在于极低功耗,长距离以及海量连接,适用于物联网万物互联的场景。
LPWAN 不只是一种技术,而是代表了一族有着各种形式的低功耗广域网技术,其中LoRa使用的是一种扩频技术,NB-IoT使用的是窄带技术,这是两种有代表性的低功耗广域网技术。
③ LoRa 介绍
LoRa 是 Long Range Communication的简称,狭义上的LoRa指的是一种物理层的信号调制方式,是 Semtech (先科)公司定义的一种基于Chirp扩频技术的物理层调制方式,可达到-148 dBm的接收灵敏度,以偏小的数据速率(0.3-50kbps)换取更高的通讯距离(市内3km,郊区15km)和低功耗(电池供电在特定条件下可以工作长达10年)。
从系统角度看,LoRa也指由终端节点、网关、网络服务器、应用服务器所组成的一种网络系统架构:LoRa定义了不同设备在系统中的分工与作用,规定了数据在系统中流动与汇聚的方式。
从应用角度看,LoRa为物联网应用提供了一种低成本、低功耗、远距离的数据传输服务:LoRa在使用10mW射频输出功率的情况下,可以提供超过15km传输距离,从而支持大量广域低功耗物联网应用。④ LoRa 特点
注意: 标准 LoRa 通信技术采用半双工(Half-Duplex)模式
1)远距离
在乡村地区能以长达 30 英里的距离为间隔连接设备,可穿透建筑物密集的市区或进深较长的室内环境。
2)低功耗
仅需极少能源,电池使用寿命长达 10 年,将电池更换成本降到最低。
3)安全
采用端到端 AES128 加密、双向认证等技术,实现完整性保护和保密性。
4)高性能
每个基站可处理逾百万条消息,完全满足公共网络运营商为大型市场提供服务的需求。
5)低成本
减少了基础设施的投资、电池更换支出和最终运营费用⑤ LoRa 应用
相比于Wi-Fi、蓝牙、ZigBee等传统无线局域网,LoRa可以实现更远距离的通信,有效扩展了网络的覆盖范围.
而相比于移动蜂窝网络,LoRa具有更低的硬件部署成本和更长的节点使用寿命,单个LoRa节点可以在电池供电的情况下连续工作数年。
LoRa具有低数据率、远距离和低功耗的性质,因此非常适合与室外的传感器及其他物联网设备进行通信或数据交互。
LoRa在全球范围内进行了大量的应用部署,在智能仪表(如智能水表、智能电表)、智慧城市、智能交通数据采集、野生动物监控等众多物联网场景中都可以看到LoRa的应用。⑥ LoRa 组网架构

4.5.4 LLCC68 功能描述
① 功能框图

② 引脚说明


③ SPI 接口

4.5.5 移植 LLCC68 官方驱动
LLCC68 官方驱动下载地址:https://github.com/libdriver/llcc68/
(1) 把 下面 4个文件copy到项目的 Interface/LoRa 目录下, 并把文件 driver_llcc68_interface_template.c 改名为 driver_llcc68_interface.c


(2) 把 driver_llcc68_interface.c 中的各个函数完成。
driver_llcc68_interface.h 中添加头文件引用
#include <stdarg.h>
#include "spi.h"driver_llcc68_interface.c 完善如下函数
/**
* Copyright (c) 2015 - present LibDriver All rights reserved
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @file driver_llcc68_interface_template.c
* @brief driver llcc68 interface template source file
* @version 1.0.0
* @author Shifeng Li
* @date 2023-04-15
*
* <h3>history</h3>
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2023/04/15 <td>1.0 <td>Shifeng Li <td>first upload
* </table>
*/
#include "driver_llcc68_interface.h"
/* 开发者代码 start */
// 定义宏:片选信号拉低
#define LORA_CS_LOW HAL_GPIO_WritePin(LORA_CS_GPIO_Port, LORA_CS_Pin, GPIO_PIN_RESET);
// 定义宏:片选信号拉高
#define LORA_CS_HIGH HAL_GPIO_WritePin(LORA_CS_GPIO_Port, LORA_CS_Pin, GPIO_PIN_SET);
/* 开发者代码 end */
/**
* @brief interface spi bus init
* @return status code
* - 0 success
* - 1 spi init failed
* @note none
*/
uint8_t llcc68_interface_spi_init(void)
{
// main.c 已经调用 MX_SPI_Init() 初始化了 SPI,因此这里不需要再次初始化
return 0;
}
/**
* @brief interface spi bus deinit
* @return status code
* - 0 success
* - 1 spi deinit failed
* @note none
*/
uint8_t llcc68_interface_spi_deinit(void)
{
/* 开发者代码 start */
return HAL_SPI_DeInit(&hspi1) == HAL_OK ? 0 : 1;
/* 开发者代码 end */
}
/**
* @brief interface spi bus write read
* @param[in] *in_buf points to a input buffer
* @param[in] in_len is the input length
* @param[out] *out_buf points to a output buffer
* @param[in] out_len is the output length
* @return status code
* - 0 success
* - 1 write read failed
* @note none
*/
uint8_t llcc68_interface_spi_write_read(uint8_t *in_buf, uint32_t in_len,
uint8_t *out_buf, uint32_t out_len)
{
/* 开发者代码 start */
// 片选信号拉低
LORA_CS_LOW;
// 如果 in_len 不为 0,则发送数据
if (in_len > 0)
{
// 发送数据
if (HAL_SPI_Transmit(&hspi1, in_buf, in_len, 1000) != HAL_OK)
{
// 片选信号拉高
LORA_CS_HIGH;
// 返回
return 1;
}
}
// 如果 out_len 不为 0,则接收数据
if (out_len > 0)
{
// 接收数据
if (HAL_SPI_Receive(&hspi1, out_buf, out_len, 1000) != HAL_OK)
{
// 片选信号拉高
LORA_CS_HIGH;
// 返回
return 1;
}
}
// 片选信号拉高
LORA_CS_HIGH;
/* 开发者代码 end */
return 0;
}
/**
* @brief interface reset gpio init
* @return status code
* - 0 success
* - 1 init failed
* @note none
*/
uint8_t llcc68_interface_reset_gpio_init(void)
{
// HAL 库已经初始化了 GPIO,因此这里不需要再次初始化
return 0;
}
/**
* @brief interface reset gpio deinit
* @return status code
* - 0 success
* - 1 deinit failed
* @note none
*/
uint8_t llcc68_interface_reset_gpio_deinit(void)
{
// 省略
return 0;
}
/**
* @brief interface reset gpio write
* @param[in] data is the written data
* @return status code
* - 0 success
* - 1 write failed
* @note none
*/
uint8_t llcc68_interface_reset_gpio_write(uint8_t data)
{
/* 开发者代码 start */
// 修改 LORA_RST 引脚的状态
HAL_GPIO_WritePin(LORA_RST_GPIO_Port, LORA_RST_Pin, data);
/* 开发者代码 end */
return 0;
}
/**
* @brief interface busy gpio init
* @return status code
* - 0 success
* - 1 init failed
* @note none
*/
uint8_t llcc68_interface_busy_gpio_init(void)
{
// HAL 库已经初始化了 GPIO,因此这里不需要再次初始化
return 0;
}
/**
* @brief interface busy gpio deinit
* @return status code
* - 0 success
* - 1 deinit failed
* @note none
*/
uint8_t llcc68_interface_busy_gpio_deinit(void)
{
// 省略
return 0;
}
/**
* @brief interface busy gpio read
* @param[out] *value points to a value buffer
* @return status code
* - 0 success
* - 1 read failed
* @note none
*/
uint8_t llcc68_interface_busy_gpio_read(uint8_t *value)
{
/* 开发者代码 start */
// 读取 LORA_BUSY 引脚的状态
*value = HAL_GPIO_ReadPin(LORA_BUSY_GPIO_Port, LORA_BUSY_Pin);
/* 开发者代码 end */
return 0;
}
/**
* @brief interface delay ms
* @param[in] ms
* @note none
*/
void llcc68_interface_delay_ms(uint32_t ms)
{
/* 开发者代码 start */
HAL_Delay(ms);
/* 开发者代码 end */
}
/**
* @brief interface print format data
* @param[in] fmt is the format data
* @note none
*/
void llcc68_interface_debug_print(const char *const fmt, ...)
{
va_list args; // 定义一个 va_list 类型的变量来存储可变参数
va_start(args, fmt); // 初始化 args,使其指向 fmt 之后的第一个参数
// 使用 vprintf 来格式化输出(你也可以用 vsnprintf 等)
vprintf(fmt, args);
va_end(args); // 清理 args
}
/**
* @brief interface receive callback
* @param[in] type is the receive callback type
* @param[in] *buf points to a buffer address
* @param[in] len is the buffer length
* @note none
*/
void llcc68_interface_receive_callback(uint16_t type, uint8_t *buf, uint16_t len)
{
switch (type)
{
case LLCC68_IRQ_TX_DONE:
{
llcc68_interface_debug_print("llcc68: irq tx done.\n");
break;
}
case LLCC68_IRQ_RX_DONE:
{
llcc68_interface_debug_print("llcc68: irq rx done.\n");
break;
}
case LLCC68_IRQ_PREAMBLE_DETECTED:
{
llcc68_interface_debug_print("llcc68: irq preamble detected.\n");
break;
}
case LLCC68_IRQ_SYNC_WORD_VALID:
{
llcc68_interface_debug_print("llcc68: irq valid sync word detected.\n");
break;
}
case LLCC68_IRQ_HEADER_VALID:
{
llcc68_interface_debug_print("llcc68: irq valid header.\n");
break;
}
case LLCC68_IRQ_HEADER_ERR:
{
llcc68_interface_debug_print("llcc68: irq header error.\n");
break;
}
case LLCC68_IRQ_CRC_ERR:
{
llcc68_interface_debug_print("llcc68: irq crc error.\n");
break;
}
case LLCC68_IRQ_CAD_DONE:
{
llcc68_interface_debug_print("llcc68: irq cad done.\n");
break;
}
case LLCC68_IRQ_CAD_DETECTED:
{
llcc68_interface_debug_print("llcc68: irq cad detected.\n");
break;
}
case LLCC68_IRQ_TIMEOUT:
{
llcc68_interface_debug_print("llcc68: irq timeout.\n");
break;
}
default:
{
llcc68_interface_debug_print("llcc68: unknown code.\n");
break;
}
}
}(3) 给 driver_llcc68.c 添加一行代码,用来处理发送之后的状态。

(4) 给 driver_llcc68.h 中结构体 llcc68_handle_t 添加一个成员 receive_len 表示接收到数据的长度。

(3) 给 driver_llcc68.c 中函数 llcc68_irq_handler 中添加一行代码用来给长度赋值

4.5.6 自定义代码
新建 Int_LLCC68.h 文件和 Int_LLCC68.c 文件
可以参考官方驱动中 example/driver_llcc68_lora.h和 example/driver_llcc68_lora.c
Int_LLCC68.h
#ifndef __INT_LLCC68_H__
#define __INT_LLCC68_H__
#include "driver_llcc68_interface.h"
/**
* @brief llcc68 lora example default definition
*/
#define LLCC68_LORA_DEFAULT_STOP_TIMER_ON_PREAMBLE LLCC68_BOOL_FALSE /**< disable stop timer on preamble */
#define LLCC68_LORA_DEFAULT_REGULATOR_MODE LLCC68_REGULATOR_MODE_DC_DC_LDO /**< only ldo */
#define LLCC68_LORA_DEFAULT_PA_CONFIG_DUTY_CYCLE 0x02 /**< set +17dBm power */
#define LLCC68_LORA_DEFAULT_PA_CONFIG_HP_MAX 0x03 /**< set +17dBm power */
#define LLCC68_LORA_DEFAULT_TX_DBM 17 /**< +17dBm */
#define LLCC68_LORA_DEFAULT_RAMP_TIME LLCC68_RAMP_TIME_10US /**< set ramp time 10 us */
#define LLCC68_LORA_DEFAULT_SF LLCC68_LORA_SF_9 /**< sf9 */
#define LLCC68_LORA_DEFAULT_BANDWIDTH LLCC68_LORA_BANDWIDTH_125_KHZ /**< 125khz */
#define LLCC68_LORA_DEFAULT_CR LLCC68_LORA_CR_4_5 /**< cr4/5 */
#define LLCC68_LORA_DEFAULT_LOW_DATA_RATE_OPTIMIZE LLCC68_BOOL_FALSE /**< disable low data rate optimize */
#define LLCC68_LORA_DEFAULT_RF_FREQUENCY 480000000U /**< 480000000Hz */
#define LLCC68_LORA_DEFAULT_SYMB_NUM_TIMEOUT 0 /**< 0 */
#define LLCC68_LORA_DEFAULT_SYNC_WORD 0x3444U /**< public network */
#define LLCC68_LORA_DEFAULT_RX_GAIN 0x94 /**< common rx gain */
#define LLCC68_LORA_DEFAULT_OCP 0x38 /**< 140 mA */
#define LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH 12 /**< 12 */
#define LLCC68_LORA_DEFAULT_HEADER LLCC68_LORA_HEADER_EXPLICIT /**< explicit header */
#define LLCC68_LORA_DEFAULT_BUFFER_SIZE 255 /**< 255 */
#define LLCC68_LORA_DEFAULT_CRC_TYPE LLCC68_LORA_CRC_TYPE_ON /**< crc on */
#define LLCC68_LORA_DEFAULT_INVERT_IQ LLCC68_BOOL_FALSE /**< disable invert iq */
#define LLCC68_LORA_DEFAULT_CAD_SYMBOL_NUM LLCC68_LORA_CAD_SYMBOL_NUM_2 /**< 2 symbol */
#define LLCC68_LORA_DEFAULT_CAD_DET_PEAK 24 /**< 24 */
#define LLCC68_LORA_DEFAULT_CAD_DET_MIN 10 /**< 10 */
#define LLCC68_LORA_DEFAULT_START_MODE LLCC68_START_MODE_WARM /**< warm mode */
#define LLCC68_LORA_DEFAULT_RTC_WAKE_UP LLCC68_BOOL_TRUE /**< enable rtc wake up */
/**
* @brief 初始化LLCC68
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Init(void);
/**
* @brief 设置LLCC68为发送模式
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_SetSendMode(void);
/**
* @brief 设置LLCC68为接收模式
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_SetReceiveMode(void);
/**
* @brief 发送数据
*
* @param tx_buf 发送数据缓冲区
* @param tx_len 发送数据长度
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Send(uint8_t *tx_buf, uint16_t tx_len);
/**
* @brief 接收数据
*
* @param rx_buf 接收数据缓冲区
* @param rx_len 将接收到长度保存此处
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Receive(uint8_t *rx_buf, uint16_t *rx_len);
#endif /* __INT_LLCC68_H__ */Int_LLCC68.c
#include "Int_LLCC68.h"
static llcc68_handle_t gs_handle; /**< llcc68 handle */
/**
* @brief 初始化LLCC68
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Init(void)
{
uint8_t res;
uint32_t reg;
uint8_t modulation;
uint8_t config;
/* link interface function */
DRIVER_LLCC68_LINK_INIT(&gs_handle, llcc68_handle_t);
DRIVER_LLCC68_LINK_SPI_INIT(&gs_handle, llcc68_interface_spi_init);
DRIVER_LLCC68_LINK_SPI_DEINIT(&gs_handle, llcc68_interface_spi_deinit);
DRIVER_LLCC68_LINK_SPI_WRITE_READ(&gs_handle, llcc68_interface_spi_write_read);
DRIVER_LLCC68_LINK_RESET_GPIO_INIT(&gs_handle, llcc68_interface_reset_gpio_init);
DRIVER_LLCC68_LINK_RESET_GPIO_DEINIT(&gs_handle, llcc68_interface_reset_gpio_deinit);
DRIVER_LLCC68_LINK_RESET_GPIO_WRITE(&gs_handle, llcc68_interface_reset_gpio_write);
DRIVER_LLCC68_LINK_BUSY_GPIO_INIT(&gs_handle, llcc68_interface_busy_gpio_init);
DRIVER_LLCC68_LINK_BUSY_GPIO_DEINIT(&gs_handle, llcc68_interface_busy_gpio_deinit);
DRIVER_LLCC68_LINK_BUSY_GPIO_READ(&gs_handle, llcc68_interface_busy_gpio_read);
DRIVER_LLCC68_LINK_DELAY_MS(&gs_handle, llcc68_interface_delay_ms);
DRIVER_LLCC68_LINK_DEBUG_PRINT(&gs_handle, llcc68_interface_debug_print);
DRIVER_LLCC68_LINK_RECEIVE_CALLBACK(&gs_handle, llcc68_interface_receive_callback);
/* init the llcc68 */
res = llcc68_init(&gs_handle);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: init failed.\n");
return 1;
}
/* enter standby */
res = llcc68_set_standby(&gs_handle, LLCC68_CLOCK_SOURCE_XTAL_32MHZ);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set standby failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set stop timer on preamble */
res = llcc68_set_stop_timer_on_preamble(&gs_handle, LLCC68_LORA_DEFAULT_STOP_TIMER_ON_PREAMBLE);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: stop timer on preamble failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set regulator mode */
res = llcc68_set_regulator_mode(&gs_handle, LLCC68_LORA_DEFAULT_REGULATOR_MODE);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set regulator mode failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set pa config */
res = llcc68_set_pa_config(&gs_handle, LLCC68_LORA_DEFAULT_PA_CONFIG_DUTY_CYCLE, LLCC68_LORA_DEFAULT_PA_CONFIG_HP_MAX);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set pa config failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* enter to stdby xosc mode */
res = llcc68_set_rx_tx_fallback_mode(&gs_handle, LLCC68_RX_TX_FALLBACK_MODE_STDBY_XOSC);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set rx tx fallback mode failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
// /* set dio irq */
// res = llcc68_set_dio_irq_params(&gs_handle, 0x03FF, 0x03FF, 0x0000, 0x0000);
// if (res != 0)
// {
// llcc68_interface_debug_print("llcc68: set dio irq params failed.\n");
// (void)llcc68_deinit(&gs_handle);
// return 1;
// }
// /* clear irq status */
// res = llcc68_clear_irq_status(&gs_handle, 0x03FF);
// if (res != 0)
// {
// llcc68_interface_debug_print("llcc68: clear irq status failed.\n");
// (void)llcc68_deinit(&gs_handle);
// return 1;
// }
/* set lora mode */
res = llcc68_set_packet_type(&gs_handle, LLCC68_PACKET_TYPE_LORA);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set packet type failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set tx params */
res = llcc68_set_tx_params(&gs_handle, LLCC68_LORA_DEFAULT_TX_DBM, LLCC68_LORA_DEFAULT_RAMP_TIME);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set tx params failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set lora modulation params */
res = llcc68_set_lora_modulation_params(&gs_handle, LLCC68_LORA_DEFAULT_SF, LLCC68_LORA_DEFAULT_BANDWIDTH,
LLCC68_LORA_DEFAULT_CR, LLCC68_LORA_DEFAULT_LOW_DATA_RATE_OPTIMIZE);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set lora modulation params failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* convert the frequency */
res = llcc68_frequency_convert_to_register(&gs_handle, LLCC68_LORA_DEFAULT_RF_FREQUENCY, (uint32_t *)®);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: convert to register failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set the frequency */
res = llcc68_set_rf_frequency(&gs_handle, reg);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set rf frequency failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set base address */
res = llcc68_set_buffer_base_address(&gs_handle, 0x00, 0x00);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set buffer base address failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set lora symb num */
res = llcc68_set_lora_symb_num_timeout(&gs_handle, LLCC68_LORA_DEFAULT_SYMB_NUM_TIMEOUT);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set lora symb num timeout failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* reset stats */
res = llcc68_reset_stats(&gs_handle, 0x0000, 0x0000, 0x0000);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: reset stats failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* clear device errors */
res = llcc68_clear_device_errors(&gs_handle);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: clear device errors failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set the lora sync word */
res = llcc68_set_lora_sync_word(&gs_handle, LLCC68_LORA_DEFAULT_SYNC_WORD);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set lora sync word failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* get tx modulation */
res = llcc68_get_tx_modulation(&gs_handle, (uint8_t *)&modulation);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: get tx modulation failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
modulation |= 0x04;
/* set the tx modulation */
res = llcc68_set_tx_modulation(&gs_handle, modulation);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set tx modulation failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set the rx gain */
res = llcc68_set_rx_gain(&gs_handle, LLCC68_LORA_DEFAULT_RX_GAIN);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set rx gain failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* set the ocp */
res = llcc68_set_ocp(&gs_handle, LLCC68_LORA_DEFAULT_OCP);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set ocp failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
/* get the tx clamp config */
res = llcc68_get_tx_clamp_config(&gs_handle, (uint8_t *)&config);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: get tx clamp config failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
config |= 0x1E;
/* set the tx clamp config */
res = llcc68_set_tx_clamp_config(&gs_handle, config);
if (res != 0)
{
llcc68_interface_debug_print("llcc68: set tx clamp config failed.\n");
(void)llcc68_deinit(&gs_handle);
return 1;
}
// 默认设置为接收状态
Int_LLCC68_SetReceiveMode();
return 0;
}
/**
* @brief 设置LLCC68为发送模式
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_SetSendMode(void)
{
// 设置TXEN为1
HAL_GPIO_WritePin(LLCC68_TXEN_GPIO_Port, LLCC68_TXEN_Pin, GPIO_PIN_SET);
// 设置RXEN为0
HAL_GPIO_WritePin(LLCC68_RXEN_GPIO_Port, LLCC68_RXEN_Pin, GPIO_PIN_RESET);
/* set dio irq */
if (llcc68_set_dio_irq_params(&gs_handle, LLCC68_IRQ_TX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED,
LLCC68_IRQ_TX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED,
0x0000, 0x0000) != 0)
{
return 1;
}
/* clear irq status */
if (llcc68_clear_irq_status(&gs_handle, 0x03FFU) != 0)
{
return 1;
}
return 0;
}
/**
* @brief 设置LLCC68为接收模式
*
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_SetReceiveMode(void)
{
// 设置TXEN为0
HAL_GPIO_WritePin(LLCC68_TXEN_GPIO_Port, LLCC68_TXEN_Pin, GPIO_PIN_RESET);
// 设置RXEN为1
HAL_GPIO_WritePin(LLCC68_RXEN_GPIO_Port, LLCC68_RXEN_Pin, GPIO_PIN_SET);
uint8_t setup;
/* set dio irq */
if (llcc68_set_dio_irq_params(&gs_handle, LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED,
LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED,
0x0000, 0x0000) != 0)
{
return 1;
}
/* clear irq status */
if (llcc68_clear_irq_status(&gs_handle, 0x03FFU) != 0)
{
return 1;
}
/* set lora packet params */
if (llcc68_set_lora_packet_params(&gs_handle, LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH,
LLCC68_LORA_DEFAULT_HEADER, LLCC68_LORA_DEFAULT_BUFFER_SIZE,
LLCC68_LORA_DEFAULT_CRC_TYPE, LLCC68_LORA_DEFAULT_INVERT_IQ) != 0)
{
return 1;
}
/* get iq polarity */
if (llcc68_get_iq_polarity(&gs_handle, (uint8_t *)&setup) != 0)
{
return 1;
}
#if LLCC68_LORA_DEFAULT_INVERT_IQ == LLCC68_BOOL_FALSE
setup |= 1 << 2;
#else
setup &= ~(1 << 2);
#endif
/* set the iq polarity */
if (llcc68_set_iq_polarity(&gs_handle, setup) != 0)
{
return 1;
}
/* start receive */
if (llcc68_continuous_receive(&gs_handle) != 0)
{
return 1;
}
return 0;
}
/**
* @brief 发送数据
*
* @param tx_buf 发送数据缓冲区
* @param tx_len 发送数据长度
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Send(uint8_t *tx_buf, uint16_t tx_len)
{
// 设置为发送状态
Int_LLCC68_SetSendMode();
/* send the data */
if (llcc68_lora_transmit(&gs_handle, LLCC68_CLOCK_SOURCE_XTAL_32MHZ,
LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH, LLCC68_LORA_DEFAULT_HEADER,
LLCC68_LORA_DEFAULT_CRC_TYPE, LLCC68_LORA_DEFAULT_INVERT_IQ,
(uint8_t *)tx_buf, tx_len, 0) != 0)
{
// 恢复成接收状态
Int_LLCC68_SetReceiveMode();
return 1;
}
// 恢复成接收状态
Int_LLCC68_SetReceiveMode();
return 0;
}
/**
* @brief 接收数据
*
* @param rx_buf 接收数据缓冲区
* @param rx_len 将接收到长度保存此处
* @return uint8_t 0是成功 1是失败
*/
uint8_t Int_LLCC68_Receive(uint8_t *rx_buf, uint16_t *rx_len)
{
if (llcc68_irq_handler(&gs_handle) != 0)
{
return 1;
}
// 将接收到的数据保存至接收缓冲区
strcpy((char *)rx_buf, (char *)gs_handle.receive_buf);
*rx_len = gs_handle.receive_len;
// 手动在接收缓冲区添加字符串结束标记
rx_buf[*rx_len] = '\0';
// 清空结构体中的成员receive_buf
memset(gs_handle.receive_buf, 0, 256);
// 清空结构体中的成员receive_len
gs_handle.receive_len = 0;
return 0;
}mian.c (演示):
// 初始LLCC68
Int_LLCC68_Init();
// 发送数据
Int_LLCC68_SendData("How Are You?", 12);4.6 应用层:获取GPS信息和步数并通过 NB-IOT 或 LoRA上传
4.6.1 JSON 处理
cJSON 是一个轻量级的 C 语言 JSON 解析和生成库,适用于嵌入式系统和资源受限环境,GitHub地址:https://github.com/DaveGamble/cJSON
从 cJSON 资料包中将 cJSON.c 和 cJSON.h 复位到项目的 Com 层中。
4.6.2 代码
App_Uploader.h
#ifndef __APP_UPLOADER_H__
#define __APP_UPLOADER_H__
#include <string.h>
#include "Com_Config.h"
#include "Com_Util.h"
#include "Com_Debug.h"
#include "cJSON.h"
#include "Int_DS3553.h"
#include "Int_AT6558R.h"
#include "Int_QS100.h"
#include "Int_LLCC68.h"
/**
* @brief 初始化Uploader应用
*
*/
void App_Uploader_Init(void);
/**
* @brief 获取步数和GPS信息并通过NB-IoT、Lora上传
*
*/
void App_Uploader_Upload(void);
#endif /* __APP_UPLOADER_H__ */App_Uploader.c
#include "App_Uploader.h"
// 全局变量:结构体变量,用于保存要上传的数据
UploadData_Typedef g_upload_data;
// 全局变量:保存要上传的数据字符串
uint8_t g_upload_data_str[256];
// 全局变量:保存要上传的数据字符串的长度
uint32_t g_upload_data_str_len = 0;
// 静态函数:处理获取到GPS信息
static void App_Uploader_ProcessGPSInfo(void)
{
// 获取有效GPS定位信息
uint8_t *gpsrmc = NULL; // 保存最小定位信息的字符串
uint8_t status = 0; // 保存定位状态
do
{
// 延时1s
Com_Delay_ms(1000);
// 调用函数,获取AT6558R的NMEA格式的GPS信息
Int_AT6558R_GetGPSInfo();
// 从GPS信息中提取RMC部分(推荐的最小定位信息) -----------------------------------------
// gpsrmc = (uint8_t *)strstr((const char *)g_rx_gps_buff, "$GNRMC");
// gpsrmc = "$GNRMC,084724.000,V,,,,,,,181125,,,N,V*2A";
// gpsrmc = "$GNRMC,,V,,,,,,,181125,,,N,V*2A";
gpsrmc = "$GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D";
if (gpsrmc == NULL)
{
DEBUG_PRINTLN("未获取到最小定位信息");
continue;
}
// DEBUG_PRINTLN("最小定位信息: %s", gpsrmc);
// 从RMC(最小定位信息中)获取到状态
/*
无效: $GNRMC,,V,,,,,,,,,,N,V*37
有效: $GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D
*/
sscanf((char *)gpsrmc, "$GNRMC,%*[^VA]%c", &status);
DEBUG_PRINTLN("获取到的定位状态:%c", status);
} while (status != 'A');
DEBUG_PRINTLN("定位成功!");
// 从RMC信息中提取经度、经度方向、纬度、纬度方向、日期、时间 --------------------------------
// 定义相关变量
float lat; // 纬度
uint8_t lat_dir; // 纬度方向,N表示北,S表示南
float lon; // 经度
uint8_t lon_dir; // 经度方向,E表示东,W表示西
uint8_t date[7]; // 日期, 格式: ddmmyy
uint8_t time[7]; // 时间, 格式: hhmmss
uint8_t datetime[20]; // 日期时间,格式: YYYY-MM-DD HH:MM:SS
// 从RMC字符串中提取数据
// $GNRMC,060312.000,A,3028.10847,N,11423.53584,E,3.74,32.25,181125,,,A,V*3D
sscanf((char *)gpsrmc, "$GNRMC,%6c%*6c,%f,%c,%f,%c,%*f,%*f,%6c", (char *)time, &lat, &lat_dir, &lon, &lon_dir, (char *)date);
// 打印提取到的数据
// DEBUG_PRINTLN("时间: %s", time);
// DEBUG_PRINTLN("日期: %s", date);
// DEBUG_PRINTLN("纬度: %f, 纬度方向: %c", lat, lat_dir);
// DEBUG_PRINTLN("经度: %f, 经度方向: %c", lon, lon_dir);
// 处理经纬度信息 -----------------------------------------------
// 换算纬度, 使用度单位
g_upload_data.lat = (uint8_t)(lat / 100) + (lat - (uint8_t)(lat / 100) * 100) / 60;
// 换算经度, 使用度单位
g_upload_data.lon = (uint8_t)(lon / 100) + (lon - (uint8_t)(lon / 100) * 100) / 60;
// 结构体中添加纬度方向和经度方向
g_upload_data.lat_dir[0] = lat_dir;
g_upload_data.lon_dir[0] = lon_dir;
// 处理日期、时间格式 ----------------------------------------------
sprintf((char *)datetime, "20%c%c-%c%c-%c%c %c%c:%c%c:%c%c", date[4], date[5], date[2], date[3], date[0], date[1], time[0], time[1], time[2], time[3], time[4], time[5]);
// 转为本地日期时间
Com_UTC2Local(datetime, g_upload_data.datetime);
// DEBUG_PRINTLN("处理后的日期时间: %s", datetime);
}
// 静态函数:将待上传的数据处理成JSON字符串
static void App_Uploadder_ProcessJSON(void)
{
// 创建JSON对象
cJSON *json = cJSON_CreateObject();
if (json == NULL)
{
DEBUG_PRINTLN("创建JSON对象失败");
return;
}
// 添加字段:纬度、纬度方向、经度、经度方向、日期时间、步数、UUID
cJSON_AddNumberToObject(json, "lat", g_upload_data.lat);
cJSON_AddStringToObject(json, "lat_dir", (char *)g_upload_data.lat_dir);
cJSON_AddNumberToObject(json, "lon", g_upload_data.lon);
cJSON_AddStringToObject(json, "lon_dir", (char *)g_upload_data.lon_dir);
cJSON_AddStringToObject(json, "datetime", (char *)g_upload_data.datetime);
cJSON_AddNumberToObject(json, "steps", g_upload_data.steps);
cJSON_AddStringToObject(json, "uuid", (char *)g_upload_data.uuid);
// 转换为字符串
// char *json_str = cJSON_Print(json);
char *json_str = cJSON_PrintUnformatted(json);
if (json_str == NULL)
{
DEBUG_PRINTLN("转换JSON对象为字符串失败");
cJSON_Delete(json);
return;
}
// 将json_str复制到全局变量g_upload_data_str中
strcpy((char *)g_upload_data_str, json_str);
// 保存json_str的长度
g_upload_data_str_len = strlen(json_str);
// 打印JSON字符串
DEBUG_PRINTLN("JSON字符串: %s", json_str);
DEBUG_PRINTLN("JSON字符串长度: %d", strlen(json_str));
// 释放JSON字符串内存
cJSON_free(json_str);
// 释放JSON对象内存
cJSON_Delete(json);
}
/**
* @brief 初始化Uploader应用
*
*/
void App_Uploader_Init(void)
{
// 初始化DS3553(计步芯片)
Int_DS3553_Init();
// 初始化AT6558R(GPS芯片)
Int_AT6558R_Init();
// 初始化QS100(NB-IoT模块)
Int_QS100_Init();
// 初始化LLCC68(Lora模块),如果NB-Iot上传失败,再初始化Lora芯片
// Int_LLCC68_Init();
}
/**
* @brief 获取步数和GPS信息并通过NB-IoT、Lora上传
*
*/
void App_Uploader_Upload(void)
{
// 1. 获取当前 MCU 的 UUID
sprintf((char *)g_upload_data.uuid, "%08X%08X%08X", HAL_GetUIDw2(), HAL_GetUIDw1(), HAL_GetUIDw0());
// 2. 获取步数
g_upload_data.steps = Int_DS3553_GetSteps();
// 3. 获取 GPS 信息并进行处理
App_Uploader_ProcessGPSInfo();
// // 打印要上传的数据
// DEBUG_PRINTLN("纬度:%f; 纬度方向:%s \n", g_upload_data.lat, g_upload_data.lat_dir);
// DEBUG_PRINTLN("经度:%f; 经度方向:%s \n", g_upload_data.lon, g_upload_data.lon_dir);
// DEBUG_PRINTLN("日期时间:%s \n", g_upload_data.datetime);
// DEBUG_PRINTLN("步数:%d \n", g_upload_data.steps);
// DEBUG_PRINTLN("UUID:%s \n", g_upload_data.uuid);
// 4. 将待上传数据处理成JSON字符串
App_Uploadder_ProcessJSON();
// 5. 先使用NB-Iot上传数据,如果失败再使用Lora上传
Status_Typedef status = Int_QS100_SendData(g_upload_data_str, g_upload_data_str_len);
// Status_Typedef status = STATUS_ERROR;
if (status == STATUS_OK)
{
DEBUG_PRINTLN("NB-Iot上传成功!");
}
else
{
DEBUG_PRINTLN("NB-Iot上传失败,尝试使用Lora上传");
// 初始化LLCC68(Lora模块)
Int_LLCC68_Init();
// 使用Lora上传数据
if (Int_LLCC68_Send(g_upload_data_str, g_upload_data_str_len) == 0)
{
DEBUG_PRINTLN("Lora上传成功");
}
else
{
DEBUG_PRINTLN("Lora上传失败");
}
}
}4.7 应用层:低功耗
4.7.1 CubeMX 配置

注意: 我们电路板上没有安装低速外部晶振!

4.7.2 代码
App_Lowpower.h
#ifndef __APP_LOWPOWER_H__
#define __APP_LOWPOWER_H__
#include "rtc.h"
#include "Int_AT6558R.h"
#include "Int_QS100.h"
/**
* @brief 进入低功耗模式
*
*/
void App_Lowpower_Enter(void);
/**
* @brief 退出低功耗模式
*
*/
void App_Lowpower_Exit(void);
#endif /* __APP_LOWPOWER_H__ */App_Lowpower.c
#include "App_Lowpower.h"
// 静态函数:设置闹钟时间间隔
static void App_Lowpower_SetAlarm(uint32_t seconds)
{
// 1. 定义结构体,用于保存获取到计数时间
RTC_TimeTypeDef sTime;
// 2. 先获取RTC当前的计数时间
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
// 3. 定义闹钟的结构体
sTime.Seconds += (seconds - 1);
RTC_AlarmTypeDef sAlarm = {
sTime,
1};
// 4. 设置闹钟
HAL_RTC_SetAlarm(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
/**
* @brief 进入低功耗模式
*
*/
void App_Lowpower_Enter(void)
{
// 1. 进入AT6558R模块的低功耗模式
Int_AT6558R_EnterLowPowerMode();
DEBUG_PRINTLN("AT6558R 进入低功耗模式...");
// 2. 进入QS100模块的低功耗模式
Int_QS100_EnterLowPowerMode();
DEBUG_PRINTLN("QS100 进入低功耗模式...");
// 3. MCU进入低功耗模式 -----
// 3.1 清楚清楚标志位
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 3.2 设置闹钟时间是10s
App_Lowpower_SetAlarm(10);
// 3.3 进入低功耗模式(standby模式)
DEBUG_PRINTLN("MCU 进入低功耗模式...");
HAL_PWR_EnterSTANDBYMode();
}
/**
* @brief 退出低功耗模式
*
*/
void App_Lowpower_Exit(void)
{
// 1. 退出AT6558R模块的低功耗模式
Int_AT6558R_ExitLowPowerMode();
DEBUG_PRINTLN("AT6558R 退出低功耗模式...");
// 2. 退出QS100模块的低功耗模式
Int_QS100_ExitLowPowerMode();
DEBUG_PRINTLN("QS100 退出低功耗模式...");
}main.c
int main(void)
{
/* 前面代码省略... */
/* USER CODE BEGIN 2 */
DEBUG_PRINTLN("定时器项目 ----------------------- \n");
// 判断是唤醒还是上电复位
if (__HAL_PWR_GET_FLAG(PWR_FLAG_WU) == SET)
{
printf("Wakeup from Standby mode ...\n");
}
else
{
printf("Power on reset ...\n");
}
// 退出低功耗
App_Lowpower_Exit();
// 初始化Uploader应用
App_Uploader_Init();
// 获取步数、GPS信息并上传
App_Uploader_Upload();
// 进入低功耗
App_Lowpower_Enter();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}