当前位置: 首页 > article >正文

畜牧定位器

qs100.c

#include "qs100.h"

// 内部调用函数:发送AT命令和处理返回响应
static void QS100_SendCmd(uint8_t *cmd);
static void QS100_HandleResp(void);

// 串口接收中断回调
void QS100_UartCallback(uint16_t size);

// 定义接收数据的缓冲区和长度
static uint8_t rxBuff[NB_BUFF_SIZE]; // 一次中断接收的数据,最大128
static uint16_t rxSize;
static uint8_t respBuff[NB_RESP_SIZE]; // 一次完整响应接收的数据,最大256
static uint16_t respSize;

// 初始化
void QS100_Init(void)
{
    // 1. 串口3初始化
    MX_USART3_UART_Init();

    // 2. 配置使用中断方式接收变长数据,打开中断使能
    HAL_UARTEx_ReceiveToIdle_IT(&huart3, rxBuff, NB_BUFF_SIZE);

    // 3. 发送指令进行配置
    // 3.1 重启
    debug_printfln("开始重启NB-IoT模块...");
    QS100_SendCmd("AT+RB\r\n");
    debug_printfln("重启NB-IoT模块完成!");

    // 3.2 设置回显AT指令
    QS100_SendCmd("ATE1\r\n");
}

void QS100_UartCallback(uint16_t size)
{
    rxSize = size;

    // 重新调用 ReceiveToIdle_IT,打开中断使能
    HAL_UARTEx_ReceiveToIdle_IT(&huart3, rxBuff, NB_BUFF_SIZE);
}

static void QS100_SendCmd(uint8_t *cmd)
{
    HAL_UART_Transmit(&huart3, cmd, strlen((char *)cmd), 2000);

    // 中断方式接收响应数据,处理响应
    QS100_HandleResp();
}

static void QS100_HandleResp(void)
{
    // 先清空要保存数据的数组
    memset(rxBuff, 0, NB_BUFF_SIZE);
    rxSize = 0;
    memset(respBuff, 0, NB_RESP_SIZE);
    respSize = 0;

    // 阻塞轮询,等待接收响应
    // 为了处理没有OK和ERROR的情况,增加一个计数值,减到0就退出循环
    uint8_t cnt = 2;
    do
    {
        // 引入超时
        uint32_t timeout = 0xffffff;
        while (rxSize == 0 && timeout--)
        {
        }

        // HAL_Delay(1);

        // 接收到数据,就将 rxBuff 中的内容拼接到 respBuff 中
        memcpy(&respBuff[respSize], rxBuff, rxSize);
        respSize += rxSize;

        // 清零size
        rxSize = 0;
    } while (strstr((char *)respBuff, "OK") == NULL && strstr((char *)respBuff, "ERROR") == NULL && --cnt);

    // 打印输出收到的响应
    debug_printfln("AT命令响应:\n%.*s", respSize, respBuff);
    printf("=======================================\n");
}

// 向 TCP 服务器发送数据
static CommonStatus QS100_GetIP(void);
static CommonStatus QS100_OpenSocket(uint16_t port, uint8_t *socketID);
static CommonStatus QS100_TCP_ConnectServer(uint8_t socketID, uint8_t serverIP[], uint16_t serverPort);
static CommonStatus QS100_TCP_SendDataToServer(uint8_t socketID, uint8_t sequence, uint8_t data[], uint16_t dataLen);

CommonStatus QS100_TCP_SendData(uint8_t serverIP[], uint16_t serverPort, uint8_t data[], uint16_t dataLen)
{
    // 1. 联网
    uint8_t cnt = 60;
    while (QS100_GetIP() == COMMON_ERROR && --cnt)
    {
        Utils_Delay_s(1); // 1s查询一次联网状态
    }
    if (cnt == 0)
    {
        return COMMON_ERROR;
    }

    // 2. 打开Socket(TCP协议)
    cnt = 20;
    uint8_t socketID = 0;
    while (QS100_OpenSocket(0, &socketID) == COMMON_ERROR && --cnt)
    {
        Utils_Delay_s(1); // 1s查询一次联网状态
    }
    if (cnt == 0)
    {
        return COMMON_ERROR;
    }

    // 3. 连接 TCP 服务器
    cnt = 10;
    while (QS100_TCP_ConnectServer(socketID, TCP_SERVER_IP, TCP_SERVER_PORT) == COMMON_ERROR && --cnt)
    {
        Utils_Delay_s(1); // 1s查询一次联网状态
    }
    if (cnt == 0)
    {
        return COMMON_ERROR;
    }

    // 4. 向 TCP 服务器发送数据
    cnt = 10;
    while (QS100_TCP_SendDataToServer(socketID, 5, data, dataLen) == COMMON_ERROR && --cnt)
    {
        Utils_Delay_s(1); // 1s查询一次联网状态
    }
    if (cnt == 0)
    {
        return COMMON_ERROR;
    }

    return COMMON_OK;
}

static CommonStatus QS100_GetIP(void)
{
    // QS100_SendCmd("AT+CGATT=1\r\n");

    // 发送指令
    QS100_SendCmd("AT+CGATT?\r\n");

    // 根据指令响应,判断是否联网
    if (strstr((char *)respBuff, "+CGATT:1"))
    {
        return COMMON_OK;
    }
    return COMMON_ERROR;
}
static CommonStatus QS100_OpenSocket(uint16_t port, uint8_t *socketID)
{
    // 拼接命令
    uint8_t tmpCmd[50] = {0};
    sprintf((char *)tmpCmd, "AT+NSOCR=STREAM,6,%d,0\r\n", port);

    // 发送命令
    QS100_SendCmd(tmpCmd);

    // 如果响应OK,解析出socket 编号
    if (strstr((char *)respBuff, "OK"))
    {
        sscanf((char *)respBuff, "%*[^:]:%hhu", socketID);
        return COMMON_OK;
    }
    return COMMON_ERROR;
}
static CommonStatus QS100_TCP_ConnectServer(uint8_t socketID, uint8_t serverIP[], uint16_t serverPort)
{
    // 拼接命令
    uint8_t tmpCmd[50] = {0};
    sprintf((char *)tmpCmd, "AT+NSOCO=%d,%s,%d\r\n", socketID, serverIP, serverPort);

    // 发送命令
    QS100_SendCmd(tmpCmd);

    if (strstr((char *)respBuff, "OK"))
    {
        return COMMON_OK;
    }
    return COMMON_ERROR;
}
static CommonStatus QS100_TCP_SendDataToServer(uint8_t socketID, uint8_t sequence, uint8_t data[], uint16_t dataLen)
{
    // 格式 AT+NSOSD=0,2,4444,0x200,1

    // 1. 将数据转换成16进制字符串
    uint8_t hexData[2 * dataLen + 1];
    for (uint8_t i = 0; i < dataLen; i++)
    {
        sprintf((char *)&hexData[i * 2], "%02X", data[i]);
    }

    // 2. 拼接命令
    uint8_t tmpCmd[512] = {0};
    sprintf((char *)tmpCmd, "AT+NSOSD=%d,%d,%s,0x200,%d\r\n", socketID, dataLen, hexData, sequence);

    // 3. 发送命令
    QS100_SendCmd(tmpCmd);

    if (strstr((char *)respBuff, "OK"))
    {
        // 4. 查询数据是否发送成功
        // 4.1 清空命令缓冲区
        memset((char *)tmpCmd, 0, strlen((char *)tmpCmd));

        // 4.2 拼接命令
        sprintf((char *)tmpCmd, "AT+SEQUENCE=%d,%d\r\n", socketID, sequence);

        do
        {
            Utils_Delay_s(1);

            // 4.3 每隔1s发送一次命令进行查询
            QS100_SendCmd(tmpCmd);
        } while ( respBuff[19] == '2' );

        // 4.4  如果状态为1,表示发送成功
        if (respBuff[19] == '1')
        {
            return COMMON_OK;   
        }
    }
    return COMMON_ERROR;
}

// 进入低功耗模式(PSM模式)
void QS100_EnterLowPower(void)
{
    debug_printfln("NB-IoT设备进入低功耗模式...");
    QS100_SendCmd("AT+FASTOFF=0\r\n");
}
// 退出低功耗模式(用NB_WKUP引脚唤醒)
void QS100_ExitLowPower(void)
{
    debug_printfln("NB-IoT设备从睡眠中唤醒...");

    HAL_GPIO_WritePin(NB_WKUP_GPIO_Port, NB_WKUP_Pin, GPIO_PIN_SET);
    Utils_Delay_ms(150);
    HAL_GPIO_WritePin(NB_WKUP_GPIO_Port, NB_WKUP_Pin, GPIO_PIN_RESET);
}

ds3553.c

/*
 * @Author: wushengran
 * @Date: 2024-11-05 16:07:52
 * @Description: 
 * 
 * Copyright (c) 2024 by atguigu, All Rights Reserved. 
 */
#include "ds3553.h"

// 初始化
void DS3553_Init(void)
{
    MX_I2C1_Init();
}

// 定义内部静态函数,读取某个寄存器的值(传入地址)
static uint8_t DS3553_ReadReg(uint8_t regAddr)
{
    // 定义变量保存读取的值
    uint8_t result = 0;

    // 1. CS拉低
    DS3553_CS_L;
    Utils_Delay_ms(4);

    // 2. 调库I2C读取从设备数据
    // 2.1 假写
    HAL_I2C_Master_Transmit(&hi2c1, DS3553_ADDR_W, &regAddr, 1, 2000);

    // 2.2 真读
    HAL_I2C_Master_Receive(&hi2c1, DS3553_ADDR_R, &result, 1, 2000);

    // 3. CS拉高
    DS3553_CS_H;
    Utils_Delay_ms(11);

    return result;
}

// 读取芯片ID
uint8_t DS3553_ReadID(void)
{
    return DS3553_ReadReg(DS3553_REG_CHIP_ID);
}

// 读取计步数值(24位)
uint32_t DS3553_ReadCNT(void)
{
    uint8_t buff[3] = {0};
    buff[0] = DS3553_ReadReg(DS3553_REG_STEP_CNT_L);
    buff[1] = DS3553_ReadReg(DS3553_REG_STEP_CNT_M);
    buff[2] = DS3553_ReadReg(DS3553_REG_STEP_CNT_H);
    
    return buff[0] + (buff[1] << 8) + (buff[2] << 16);
}

at6558r.c

/*
 * @Author: wushengran
 * @Date: 2024-11-04 15:59:07
 * @Description:
 *
 * Copyright (c) 2024 by atguigu, All Rights Reserved.
 */
#include "at6558r.h"

static void AT6558R_SendCmd(uint8_t *cmd);

// 定义接收GPS数据的缓冲区和长度
static uint8_t rxBuff[GPS_INFO_BUFF_SIZE];
static uint16_t rxSize;

// 初始化
void AT6558R_Init(void)
{
    // 1. 初始化串口2
    MX_USART2_UART_Init();

    // 2. 配置使用中断方式接收变长数据,打开中断使能
    HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuff, GPS_INFO_BUFF_SIZE);

    // 3. 发送指令进行配置
    // 配置GPS工作模式
    debug_printfln("GPS定位模块配置:工作模式为 GPS + 北斗");
    AT6558R_SendCmd(GPS_MODE);

    // 设置定位更新频率
    debug_printfln("GPS定位模块配置:更新频率为 1Hz");
    AT6558R_SendCmd(GPS_UPDATE_FREQ);
}

// 发送命令函数,传入$和*之间的内容
static void AT6558R_SendCmd(uint8_t *cmd)
{
    // 计算校验和
    uint16_t cs = cmd[0];
    for (uint8_t i = 1; cmd[i] != '\0'; i++)
    {
        cs ^= cmd[i];
    }
    debug_printfln("cs = %X", cs);

    // 将cs拼接到完整命令中
    uint8_t tmpCmd[50] = {0};
    sprintf((char *)tmpCmd, "$%s*%X\r\n", cmd, cs);

    // 串口发送命令
    HAL_UART_Transmit(&huart2, tmpCmd, strlen((char *)tmpCmd), 2000);
    debug_printfln("tmpCmd = %s", tmpCmd);
}

void AT6558R_UartCallback(uint16_t size)
{
    // 获取接收到的数据长度
    rxSize = size;

    // 重新开启中断接收方式
    HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuff, GPS_INFO_BUFF_SIZE);
}

// 读取GPS数据
void AT6558R_ReadData(uint8_t data[], uint16_t *dataLen)
{
    // 先清空要保存数据的数组
    memset(data, 0, strlen((char *)data));
    *dataLen = 0;

    // 根据rxSize,判断是否接收到数据
    if (rxSize == 0)
    {
        return;
    }

    // 将接收到的数据复制到data中
    if (strstr((char *)rxBuff, GPS_INFO_BEGIN) && strstr((char *)rxBuff, GPS_INFO_END))
    {
        memcpy(data, rxBuff, rxSize);
        *dataLen = rxSize;
    }

    // 清零size
    rxSize = 0;
}

// 进入低功耗模式,直接将 GPS_EN 拉低,断电
void AT6558R_EnterLowPower(void)
{
    debug_printfln("GPS 模块电源关闭...");
    HAL_GPIO_WritePin(GPS_EN_GPIO_Port, GPS_EN_Pin, GPIO_PIN_RESET);
}
// 退出低功耗模式
void AT6558R_ExitLowPower(void)
{
    debug_printfln("GPS 模块电源打开...");
    HAL_GPIO_WritePin(GPS_EN_GPIO_Port, GPS_EN_Pin, GPIO_PIN_SET);
}

communication.c

/*
 * @Author: wushengran
 * @Date: 2024-11-05 11:20:14
 * @Description:
 *
 * Copyright (c) 2024 by atguigu, All Rights Reserved.
 */
#include "communication.h"
#include "cJSON.h"

// 启动通讯模块
void Communication_Start(void)
{
    AT6558R_Init();
    QS100_Init();
    LoRa_Init();
}

// 接收GPS数据缓冲区
uint8_t gpsData[GPS_INFO_BUFF_SIZE];
uint16_t gpsDataLen;

// 接收 GPS 数据
void Communication_GetGpsInfo(UploadDataType *data)
{
    debug_printfln("读取 GPS 定位信息...");

    // 定义指针,指向 RMC 数据的位置
    char *rmcStart;

    // 轮询进行数据接收
    while (1)
    {
        // 接收GPS数据
        AT6558R_ReadData(gpsData, &gpsDataLen);

        if (gpsDataLen > 0)
        {
            // 有数据,找到 RMC 的起始位置
            // rmcStart = strstr((char *)gpsData, "$GNRMC");
            rmcStart = "$GNRMC,070822.000,A,4006.81888,N,11621.89413,E,0.81,359.02,020624,,,A,V*02";

            // 跳过不是A和V的所有字符,找到有效标记位
            uint8_t valid = 0;
            sscanf(rmcStart, "%*[^AV]%c", &valid);

            // 如果是A,就是有效数据,退出轮询进行数据包装
            if (valid == 'A')
            {
                debug_printfln("有效定位数据:\n%s", gpsData);
                break;
            }
            // 如果是V,无效数据,继续循环读取接收到的数据
            else
            {
                debug_printfln("无效定位数据:\n%s", gpsData);
            }
        }
    }

    // 解析 GPS 信息
    // 数据示例:$GNRMC,070822.000,A,4006.81888,N,11621.89413,E,0.81,359.02,020624,,,A,V*02

    // 定义保存时间和日期的字符串
    char time[7] = {0}; // 时分秒,格式 hhmmss
    char date[7] = {0}; // 日月年,格式 ddMMyy

    // 提取信息到对应变量中
    sscanf(rmcStart, "$GNRMC,%6c%*7c%lf,%c,%lf,%c,%lf,%*f,%6c",
           time,
           &data->lat, (char *)&data->latDir,
           &data->lon, (char *)&data->lonDir,
           &data->speed,
           date);

    // 合成dataTime,格式 yyyy-MM-dd hh:mm:ss
    sprintf((char *)data->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]);

    Utils_Time_UTC2Beijing(data->dateTime, data->dateTime);

    // 调整经纬度信息,保存成 xxx.xxx 形式,单位是度
    uint8_t lat_int = (uint8_t)(data->lat / 100); // 纬度的整数部分(单位度)
    data->lat = lat_int + (data->lat - lat_int * 100) / 60;
    uint8_t lon_int = (uint8_t)(data->lon / 100); // 经度的整数部分(单位度)
    data->lon = lon_int + (data->lon - lon_int * 100) / 60;
}

static void Communication_FormatDataToJson(UploadDataType *data);
static CommonStatus Communication_SendDataByNBIoT(UploadDataType *data);
static CommonStatus Communication_SendDataByLoRa(UploadDataType *data);

// 发送要上传的数据(通过NB-IoT 或 LoRa)
void Communication_SendData(UploadDataType *data)
{
    // 1. 将数据转换为JSON串
    Communication_FormatDataToJson(data);

    // 2. 通过NB-IoT发送
    CommonStatus sendStatus = Communication_SendDataByNBIoT(data);

    // 3. 如果发送失败,就再通过LoRa发送
    if (sendStatus == COMMON_ERROR)
    {
        debug_printfln("NB-IoT发送失败,将通过LoRa发送数据...");
        Communication_SendDataByLoRa(data);
    }
}

static void Communication_FormatDataToJson(UploadDataType *data)
{
    // 0. 将UID补充到数据中(16进制字符形式)
    sprintf((char *)data->uid, "%08X%08X%08X", HAL_GetUIDw2(), HAL_GetUIDw1(), HAL_GetUIDw0());

    // 1. 创建一个JSON对象
    cJSON *cjson = cJSON_CreateObject();

    // 2. 向JSON对象中逐个添加键值对
    cJSON_AddStringToObject(cjson, "uid", (char *)data->uid);
    cJSON_AddNumberToObject(cjson, "lon", data->lon);
    cJSON_AddStringToObject(cjson, "lonDir", (char *)data->lonDir);
    cJSON_AddNumberToObject(cjson, "lat", data->lat);
    cJSON_AddStringToObject(cjson, "latDir", (char *)data->latDir);
    cJSON_AddNumberToObject(cjson, "speed", data->speed);
    cJSON_AddStringToObject(cjson, "dateTime", (char *)data->dateTime);
    cJSON_AddNumberToObject(cjson, "stepNum", data->stepNum);

    // 3. 生成JSON字符串
    char *out = cJSON_PrintUnformatted(cjson);
    data->jsonLen = strlen(out);
    memcpy(data->json, out, data->jsonLen);

    // 4. 释放cJSON对象(堆空间)
    cJSON_Delete(cjson);

    printData(data);
}

// --- 以NB-IoT方式发送数据
static CommonStatus Communication_SendDataByNBIoT(UploadDataType *data)
{
    return QS100_TCP_SendData(TCP_SERVER_IP, TCP_SERVER_PORT, data->json, data->jsonLen);
}

// ---- 以LoRa方式发送数据
static CommonStatus Communication_SendDataByLoRa(UploadDataType *data)
{
    return (CommonStatus)LoRa_SendData(data->json, data->jsonLen);
}

// 打印输出要上传的数据
void printData(UploadDataType *data)
{
    printf("GPS 信息:\n");
    printf("  经度:%lf, 方向:%s\n", data->lon, data->lonDir);
    printf("  纬度:%lf, 方向:%s\n", data->lat, data->latDir);
    printf("  对地速度:%lf 节\n", data->speed);
    printf("  定位时间:%s\n", data->dateTime);

    printf("计步信息:\n");
    printf("  运动步数:%u\n", data->stepNum);

    printf("上传的数据:\n");
    printf("  数据长度:%d\n", data->jsonLen);
    printf("  数据:%.*s\n", data->jsonLen, data->json);
}


http://www.kler.cn/a/399057.html

相关文章:

  • STM32保护内部FLASH
  • AI驱动的桌面笔记应用Reor
  • Python酷库之旅-第三方库Pandas(221)
  • 【C++】构造函数
  • Amazon Web Services (AWS)
  • 【CANOE】【学习】【DecodeString】字节转为中文字符输出
  • Linux 硬链接和软链接的使用场景有哪些?
  • [C/C++] 定位新表达式 placement new
  • Android 中的 Zygote 和 Copy-on-Write 机制详解
  • React 中如何解析字符串中的 html 结构
  • SpringBoot整合FreeMarker生成word表格文件
  • [Admin] Dashboard Filter for Mix Report Types
  • 27.<Spring博客系统③(实现用户退出登录接口+发布博客+删除/编辑博客)>
  • 使用OpenCV(C++)通过鼠标点击操作获取图像的像素坐标和像素值
  • 利用TinyML和IoT技术预测沙漠地区光伏电站清洁方法
  • Java LinkedList 详解
  • Git 搭建远程仓库、在 IDEA 工具中的配置和使用
  • wx小程序turf.js判断点是否位于该多边形内部
  • 跨平台WPF框架Avalonia教程 十一
  • idea 弹窗 delete remote branch origin/develop-deploy
  • MATLAB和Python及R瑞利散射
  • Halcon HImage 与 Qt QImage 的相互转换(修订版)
  • 7z 解压器手机版与解压专家:安卓解压工具对决
  • MIT6.5840 Lab 1: MapReduce(6.824)
  • RHCE-DNS域名解析服务器
  • 第8章 利用CSS制作导航菜单