STM32+EC600E 4G模块 与华为云平台通信

news2025/4/19 19:55:55

前言

由于在STM32巡回研讨会上淘了一块EC600E4G模块以及刚办完电信卡多了两张副卡,副卡有流量刚好可以用一下,试想着以后画一块ESP32板子搭配这个4G模块做个随身WIFI,目前先用这个模块搭配STM32玩一下云平顺便记录一下。

实验目的

实现STM32通过4G模块与华为云进行通信,完成简单的LED控制。

硬件准备

材料清单

主控芯片
STM32F103ZET6 V3战舰
在这里插入图片描述

通信模块
移远的 EC600E
在这里插入图片描述

SIM卡
电信卡(支持4G网络)
天线 4G天线
模块原装
杜邦线
若干

硬件连接示意图

本人使用了串口2,串口可自行随意选择

STM32F103ZET6 <---> EC600E
PA2(TX)  <-----> RX
PA3(RX) <-----> TX
5V     <-----> VCC
GND      <-----> GND

实验思路与步骤

  1. 可以现将4G模块接TTL与电脑进行测试,使用串口助手等工具检测是否通信成功(发送AT 看是否能收到OK)
  2. 尝试将4G模块与STM32串口连接,使用STM32发送AT指令是否能接收成功
  3. 依据EC600E指导手册查找配置相关的AT指令,逐步配置4G模块
  4. 找一个云平台注册账号并创建服务以及设备,记录下自己的mqtt设备接入地址、以及mqtt连接参数(三元组)
  5. 在stm32上编写代码解析云平台下发的数据信息以及完成指定动作(开、关LED)

代码实现

串口通信配置

本人使用的是串口2,采用串口空闲中断+DMA+定时器超时判断;由于参考其他博主,了解到4G模块以及wifi模块接收数据不是特别连续以及会超时,担心空闲中断+ dma会导致数据丢失故再加了个定时器做超时判断;(目前工作上使用的GD32,好像GD32的某款中串口的空闲中断可以配置超时时间,没去试过,感觉还是挺方便的)
bsp_usart.c

#include "string.h"
#include "bsp_usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "comm_drv.h"

uint8_t uart2Recbuffer1[UART_RX_BUFFER_SIZE];
uint8_t uart2Recbuffer2[UART_RX_BUFFER_SIZE];

uint8_t *pActiveBuf = uart2Recbuffer1;
uint8_t *pReadyBuf = uart2Recbuffer2;

uint16_t g_ec600RecLen = 0;
uint16_t g_uart2RecStartFlag = 0;

extern SemaphoreHandle_t EC600RecSem;


static void Usart1Config(int baudRate)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = baudRate;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);	
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}
    
static void Usart2Config(int baudRate)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	DMA_InitTypeDef DMA_InitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	

	RCC_APB2PeriphClockCmd(USART_4G_MODULE_GPIO_CLK, ENABLE);
	RCC_APB1PeriphClockCmd(USART_4G_MODULE_CLK, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	/* 配置串口的 NVIC设置*/
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	GPIO_InitStructure.GPIO_Pin = USART_4G_MODULE_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(USART_4G_MODULE_TX_GPIO_PORT, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = USART_4G_MODULE_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(USART_4G_MODULE_RX_GPIO_PORT, &GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate = baudRate;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	USART_InitStructure.USART_HardwareFlowControl = 
	USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART_4G_MODULE, &USART_InitStructure);	
	
	// 配置DMA1通道6(USART2_RX)
    DMA_DeInit(DMA1_Channel6);
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)pActiveBuf;
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStruct.DMA_BufferSize = UART_RX_BUFFER_SIZE;
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; // 循环模式
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_Init(DMA1_Channel6, &DMA_InitStruct);
    DMA_Cmd(DMA1_Channel6, ENABLE);
	

    // 使能USART2 DMA接收和空闲中断
    USART_DMACmd(USART_4G_MODULE, USART_DMAReq_Rx, ENABLE);
    USART_ITConfig(USART_4G_MODULE, USART_IT_IDLE, ENABLE);
	
	// 使能串口
	USART_Cmd(USART_4G_MODULE, ENABLE);	    
}

void UsartInit()
{
	Usart1Config(DEBUG_USART_BAUDRATE);
	Usart2Config(USART_4G_MODULE_BAUDRATE);
}

void GetUsart2Data()
{
	BaseType_t xHigherPriorityTaskWoken;
	
	DMA_Cmd(DMA1_Channel6, DISABLE);
	DMA_ClearFlag(DMA1_FLAG_GL3 | DMA1_FLAG_TC3 | DMA1_FLAG_HT3 | DMA1_FLAG_TE3);
	
	g_ec600RecLen = UART_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Channel6);
	
	if (g_ec600RecLen > 0)
	{
		pReadyBuf = pActiveBuf;	
	}
	
	DMA_SetCurrDataCounter(DMA1_Channel6, UART_RX_BUFFER_SIZE);
	DMA_Cmd(DMA1_Channel6, ENABLE);
	
	xSemaphoreGiveFromISR(EC600RecSem,&xHigherPriorityTaskWoken);
}

void Usart2SendStr(char *str)
{
	int i = 0;
	
	do 
	{
	  Usart_SendByte(USART_4G_MODULE, *(str + i));
	  i++;
	} while(*(str + i)!='\0');

	while(USART_GetFlagStatus(USART_4G_MODULE,USART_FLAG_TC)==RESET);
}

void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	USART_SendData(pUSARTx,ch);
		
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	  unsigned int k=0;
	  do 
	  {
		  Usart_SendByte( pUSARTx, *(str + k) );
		  k++;
	  } while(*(str + k)!='\0');
	  
	  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);	  
}


int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}


// USART2空闲中断处理
void USART2_IRQHandler(void) 
{
    if(USART_GetITStatus(USART_4G_MODULE, USART_IT_IDLE) != RESET) 
	{
		if (!g_uart2RecStartFlag)
		{
			USART_ReceiveData(USART_4G_MODULE); 
			TIM_Cmd(TIM2, ENABLE);
		}
		else
		{
			USART_ReceiveData(USART_4G_MODULE); 
			TIM_SetCounter(TIM2, 0);
		}     

    }
	USART_ClearITPendingBit(USART2, USART_IT_IDLE);
}


bsp_usart.h

#ifndef __USART_H
#define	__USART_H


#include "stm32f10x.h"
#include <stdio.h>

#define UART_RX_BUFFER_SIZE 256

	
// 串口1-USART1
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler


// 串口2-USART2
#define  USART_4G_MODULE                 	USART2
#define  USART_4G_MODULE_CLK                RCC_APB1Periph_USART2
#define  USART_4G_MODULE_APBxClkCmd         RCC_APB1PeriphClockCmd
#define  USART_4G_MODULE_BAUDRATE           115200
#define  USART_4G_MODULE_GPIO_CLK           (RCC_APB2Periph_GPIOA)
    
#define  USART_4G_MODULE_TX_GPIO_PORT       GPIOA   
#define  USART_4G_MODULE_TX_GPIO_PIN        GPIO_Pin_2
#define  USART_4G_MODULE_RX_GPIO_PORT       GPIOA
#define  USART_4G_MODULE_RX_GPIO_PIN        GPIO_Pin_3

#define  USART_IRQ                USART2_IRQn
#define  USART_IRQHandler         USART2_IRQHandler



void UsartInit(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);

void Usart2SendStr(char *str);

void GetUsart2Data(void);

extern uint16_t g_ec600RecLen;
extern uint8_t *pReadyBuf;

#endif /* __USART_H */

采用定时器2做超时判断(10ms)

#include "bsp_timer.h"
#include "bsp_usart.h"


static void Timer2Config(uint16_t pre ,int arr) 
{
    TIM_TimeBaseInitTypeDef TIM_InitStruct = {0};
    NVIC_InitTypeDef NVIC_InitStruct = {0};

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_InitStruct.TIM_Prescaler = pre - 1;
	TIM_InitStruct.TIM_Period = arr - 1;
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &TIM_InitStruct);

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 7;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, DISABLE);
}

void TimerInit()
{
	//10ms
	Timer2Config(72,10000);
}

void TIM2_IRQHandler(void)
{
 
	if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{   
		GetUsart2Data();
		TIM_Cmd(TIM2, DISABLE);	
		
	}
	
	TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
}



AT指令配置

具体可参考 EC600E-CN&EC800E-CN AT 命令手册

AT
握手测试  正常返回:OK

AT<value>
1开启0关闭,开启关闭回显  正常返回:OK

AT+CPIN?
检测SIM卡是否插入   正常返回:+CPIN: READY  OK

AT+CREG?
查询网络的注册状态    正常返回: +CREG: 1,1  OK


AT+CGREG?\r\n
查询PS状态, 正常返回: +CGREG: 0,1 OK

云平台参数获取

本人查找了腾讯云以及阿里云,里面的物联网平台都不免费使用了,所以最终选择了华为云,能白嫖就白嫖嘛。具体创建的步骤可参考其他博文。网上有很多。

  1. 获取mqtt设备接入地址

在这里插入图片描述

  1. 获取三元组
    在这里插入图片描述
    在这里插入图片描述

代码

typedef enum 
{	
	EC600E_INIT_SUCCESS = 0,
	EC600E_AT_ERROR,
	EC600E_ATE_ERROR,
	EC600E_CPIN_ERROR,
	EC600E_CREG_ERROR,
	EC600E_CGREG_ERROR,	
	EC600E_QMTOPEN_ERRORAT,
	EC600E_QMTCONN_ERRORAT,
	
}EC600E_AT_ACK_STATUS;

typedef enum 
{	
	AT_TEST = 0,
	AT_ATE,
	AT_CPIN,
	AT_CREG,
	AT_CGREG,
	AT_QMTOPEN,
	AT_QMTCONN,
	
}EC600E_AT_CMD;

typedef struct {
    /* 要发送的AT命令 */
    char *cmd;
    /* 期望的应答数据,默认处理匹配到该字符串认为命令执行成功 */
    char *rsp;
    /* 得到应答的超时时间,达到超时时间为执行失败,单位ms*/
    uint32_t timeoutMs;
} AtCmdInfo_t;
char g_mqttUrl[] 		= {"自己的mqttUrl"};
char g_mqttClientId[] 	= {"自己的mqttClientId"};
char g_mqttUserName[] 	= {"自己的mqttUserName"};
char g_mqttPassword[] 	= {"自己的mqttPasswordl"};

static AtCmdInfo_t g_connectMqttCmdTable[] = {
	
	{
        .cmd = "AT\r\n",
        .rsp = "OK",
		.timeoutMs = 500,	
	},
	
	{
        .cmd = "ATE0\r\n",
        .rsp = "OK",
		.timeoutMs = 500,	
	},
	
	{
        .cmd = "AT+CPIN?\r\n",
        .rsp = "+CPIN: READY",
		.timeoutMs = 500,	
	},
	
	{
        .cmd = "AT+CREG?\r\n",
        .rsp = "+CREG: 0,1",
		.timeoutMs = 500,	
	},
	
	{
        .cmd = "AT+CGREG?\r\n",
        .rsp = "+CGREG: 0,1",
		.timeoutMs = 500,	
	},
	
	{
		.cmd = "AT+QMTOPEN=0,\"%s\",1883\r\n",
		.rsp = "OK",
		.timeoutMs = 2000,
	},
	
	{
		.cmd = "AT+QMTCONN=0,\"%s\",\"%s\",\"%s\"\r\n",
		.rsp = "OK",
		.timeoutMs = 2000,
	},
};

/***************************************************************
  *  @brief     发送数据至云平台
  *  @param   
  *  @return 
  *  @note      
  *  @Sample    usage:     
 **************************************************************/
void SendDataToCloud(char *data)
{	
	Usart2SendStr(data);	
}

/***************************************************************
  *  @brief     发送AT指令并解析ACK
  *  @param   
  *  @return 
  *  @note      
  *  @Sample    usage:     
 **************************************************************/
static bool SendATCommand(char* cmd,char *ack,uint16_t timeOut) 
{
	BaseType_t err;
	bool ret = true;

	Usart2SendStr(cmd);	

	err = xSemaphoreTake(EC600RecSem,timeOut);
	
	if (err == pdTRUE)
	{
		if(strstr((char*)pReadyBuf, ack)) 
		{
			printf("rec ok\r\n");
			ret = true;
        } 
		else if(strstr((char*)pReadyBuf, "ERROR")) 
		{
			printf("rec error\r\n");
            ret =  false;
        }
		else 
		{
			printf("rec --------\r\n");
			ret =  false;
		}
	}
	else
	{
		printf("rec time out\r\n");
		ret =  false;
	}
	
	return ret;
}


/***************************************************************
  *  @brief     EC600E模块初始化
  *  @param   
  *  @return 
  *  @note      
  *  @Sample    usage:     
 **************************************************************/
EC600E_AT_ACK_STATUS EC600EInit(void)
{  	
	char cmdStrBuf[256] = {0};
	
	EC600JsonMutex = xSemaphoreCreateMutex();

	//测试AT通信
	if(!SendATCommand(g_connectMqttCmdTable[AT_TEST].cmd,g_connectMqttCmdTable[AT_TEST].rsp,g_connectMqttCmdTable[AT_TEST].timeoutMs))                                  
	{
		return EC600E_AT_ERROR;
	}
	vTaskDelay(1000);
	
	//关闭回显
	if(!SendATCommand(g_connectMqttCmdTable[AT_ATE].cmd,g_connectMqttCmdTable[AT_ATE].rsp,g_connectMqttCmdTable[AT_ATE].timeoutMs)) 
	{
		return EC600E_ATE_ERROR;
	}
	vTaskDelay(1000);
	
	// 查询SIM卡是否正常,返回ready则表示SIM卡正常
	if(!SendATCommand(g_connectMqttCmdTable[AT_CREG].cmd,g_connectMqttCmdTable[AT_CREG].rsp,g_connectMqttCmdTable[AT_CREG].timeoutMs))                  
	{
		return EC600E_CPIN_ERROR;
	}	
	vTaskDelay(1000);
	
	// 查询模组是否注册上GSM网络
	if(!SendATCommand(g_connectMqttCmdTable[AT_CGREG].cmd,g_connectMqttCmdTable[AT_CGREG].rsp,g_connectMqttCmdTable[AT_CGREG].timeoutMs))                     
	{
		return EC600E_CREG_ERROR;				
	}
	vTaskDelay(1000);
	
	sprintf(cmdStrBuf,g_connectMqttCmdTable[AT_QMTOPEN].cmd,g_mqttUrl);
	if(!SendATCommand(cmdStrBuf,g_connectMqttCmdTable[AT_QMTOPEN].rsp,g_connectMqttCmdTable[AT_QMTOPEN].timeoutMs)) 
	{
		return EC600E_QMTOPEN_ERRORAT;                   
	}
	vTaskDelay(1000);
	
	sprintf(cmdStrBuf,g_connectMqttCmdTable[AT_QMTCONN].cmd,g_mqttClientId,g_mqttUserName,g_mqttPassword);
	if(!SendATCommand(cmdStrBuf,g_connectMqttCmdTable[AT_QMTCONN].rsp,g_connectMqttCmdTable[AT_QMTCONN].timeoutMs)) 
	{
		return EC600E_QMTCONN_ERRORAT;                   
	}	
	
    return EC600E_INIT_SUCCESS;
}

经过以上配置4G模块就能连上云平台了,但是还未实现测试功能,需在云平台配置好相关指令下发,我这边是下发LED灯开关指令,指定value来确定LED亮灭
在这里插入图片描述
通过LedStatusChange这个值来决定LED亮灭;
其中下发的数据格式是json形式:

 {"service_id":"connectTest","command_name":"LedControl","paras":{"LedStatusChange":1}}

可移植CJson库来解析json数据以及发送,网上stm32移植CJson库文章很多,可自行参考。注意(由于本人工程带Freertos,需要将cjson初始化函数内malloc free 等函数改成freertos的api)
数据解析:本人采用了简单的方式就是找到第一个‘{’然后将后面的数据使用Cjson的相关API进行解析,经供参考,建议优化,按个人需求来!!!

void ParseCloudCommand(const char *json_str) 
{
    cJSON *root = cJSON_Parse(json_str);

    if (!root) 
	{
        printf("Parse failed: %s\n", cJSON_GetErrorPtr());
        return;
    }
	
	cJSON *paras1 = cJSON_GetObjectItem(root, "paras");

    // 检查服务ID和命令名称
    cJSON *service_id = cJSON_GetObjectItem(root, "service_id");
    cJSON *command_name = cJSON_GetObjectItem(root, "command_name");

    if (!cJSON_IsString(service_id) || !cJSON_IsString(command_name)) 
	{
        cJSON_Delete(root);
        return;
    }

    // 确认是目标服务
    if (strcmp(service_id->valuestring, "connectTest") != 0) 
	{
        cJSON_Delete(root);
        return;
    }

    // 处理命令
    if (strcmp(command_name->valuestring, "LedControl") == 0) 
	{
		cJSON *paras = cJSON_GetObjectItem(root, "paras");

        if (paras && cJSON_IsObject(paras)) 
		{
            cJSON *led_status = cJSON_GetObjectItem(paras, "LedStatusChange");

            if (cJSON_IsNumber(led_status)) 
			{
                int status = led_status->valueint;
                // 控制LED
				if (status) 
				{
					LED0 = 1;
				}
				else
				{
					LED0 = 0;
				}
                
            }
        }
    }

    cJSON_Delete(root);
}

// 生成成功响应
void send_success_response(int current_status) 
{
    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "service_id", "connectTest");
    cJSON_AddStringToObject(root, "command_name", "LedControl");
    cJSON_AddStringToObject(root, "response_name", "LedControl_ACK");
    cJSON_AddNumberToObject(root, "result_code", 200);
    
    cJSON *paras = cJSON_CreateObject();
    cJSON_AddNumberToObject(paras, "LedStatus", current_status);
    cJSON_AddItemToObject(root, "paras", paras);

    char *json_str = cJSON_PrintUnformatted(root);
    SendDataToCloud(json_str); // 通过4G模块发送
    
    cJSON_Delete(root);
    vPortFree(json_str);
} 

// 生成错误响应
void send_error_response(const char *error_code) 
{
    cJSON *root = cJSON_CreateObject();
    cJSON_AddStringToObject(root, "service_id", "connectTest");
    cJSON_AddStringToObject(root, "command_name", "LedControl");
    cJSON_AddStringToObject(root, "response_name", "LedControl_ACK");
    cJSON_AddNumberToObject(root, "result_code", 500);
    
    cJSON *paras = cJSON_CreateObject();
    cJSON_AddStringToObject(paras, "error_detail", error_code);
    cJSON_AddItemToObject(root, "paras", paras);

    char *json_str = cJSON_PrintUnformatted(root);
    SendDataToCloud(json_str);
    
    cJSON_Delete(root);
    vPortFree(json_str);
}

void EC600RecTask(void *parameter)
{
	uint8_t tempBuf[256];
	
	while (1)
	{
		if (g_startRec) 
		{
			xSemaphoreTake(EC600RecSem,portMAX_DELAY);
			
			xSemaphoreTake(EC600JsonMutex, portMAX_DELAY);
			memcpy(tempBuf,pReadyBuf,sizeof(tempBuf));
			xSemaphoreGive(EC600JsonMutex);

			char *json_start = strchr((char *)tempBuf, '{'); 
			
			if (json_start != NULL) 
			{
				// 去除可能的引号或空格
				ParseCloudCommand(json_start);
			}
		}
		
		vTaskDelay(500);
	}	
}

总结

通过以上代码就能初步实现云平台通过4G模块控制STM32LED灯亮灭,可继续进行拓展。
优化点:

  1. 优化4G模块初始配置,考虑AT指令失败后续处理
  2. 优化云平台下发数据处理,
  3. 可实现接收云平台数据后做响应数据,
  4. 可实现灯状态主动上报
  5. ……
    代码水平不足,上述可能有表达错误, 代码仅供参考!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进行性核上性麻痹患者,饮食 “稳” 健康

进行性核上性麻痹作为一种复杂且罕见的神经系统退行性疾病&#xff0c;给患者的身体机能和日常生活带来严重挑战。在积极接受专业治疗的同时&#xff0c;合理的饮食安排对于维持患者营养状况、缓解症状及提升生活质量起着关键作用。以下为患者提供一些健康饮食建议。 首先&…

【数据结构 · 初阶】- 顺序表

目录 一、线性表 二、顺序表 1.实现动态顺序表 SeqList.h SeqList.c Test.c 问题 经验&#xff1a;free 出问题&#xff0c;2种可能性 解决问题 &#xff08;2&#xff09;尾删 &#xff08;3&#xff09;头插&#xff0c;头删 &#xff08;4&#xff09;在 pos 位…

NHANES指标推荐:aMED

文章题目&#xff1a;The moderating effect of alternate Mediterranean diet on the association between sedentary behavior and insomnia in postmenopausal women DOI&#xff1a;10.3389/fnut.2024.1516334 中文标题&#xff1a;替代性地中海饮食对绝经后女性久坐行为与…

Spring Cloud 远程调用

4.OpenFeign的实现原理是什么&#xff1f; 在使用OpenFeign的时候&#xff0c;主要关心两个注解&#xff0c;EnableFeignClients和FeignClient。整体的流程分为以下几个部分&#xff1a; 启用Feign代理&#xff0c;通过在启动类上添加EnableFeignClients注解&#xff0c;开启F…

力扣 — — 最长公共子序列

力扣 — — 最长公共子序列 最长公共子序列 题源&#xff1a;1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 分析&#xff1a; 一道经典的题目&#xff1a;最长公共子序列(LCS) 题目大意&#xff1a;求两个字符串的最长公共序列。 算法&…

当一个 HTTP 请求发往 Kubernetes(K8s)部署的微服务时,整个过程流转时怎样的?

以下是一个简单的示意图来展示这个过程&#xff1a; 1. 请求发起 客户端&#xff08;可以是浏览器、移动应用或者其他服务&#xff09;发起一个 HTTP 请求到目标微服务的地址。这个地址可以是服务的域名、IP 地址或者 Kubernetes 服务的 ClusterIP、NodePort 等。 2. 外部流量…

蓝桥杯-蓝桥幼儿园(Java-并查集)

并查集的核心思想 并查集主要由两个操作构成&#xff1a; Find&#xff1a;查找某个元素所在集合的根节点。并查集的特点是&#xff0c;每个元素都指向它自己的父节点&#xff0c;根节点的父节点指向它自己。查找过程中可以通过路径压缩来加速后续的查找操作&#xff0c;即将路…

C++蓝桥杯填空题(攻克版)

片头 嗨~小伙伴们&#xff0c;咱们继续攻克填空题&#xff0c;先把5分拿到手~ 第1题 数位递增的数 这道题&#xff0c;需要我们计算在整数 1 至 n 中有多少个数位递增的数。 什么是数位递增的数呢&#xff1f;一个正整数如果任何一个数位不大于右边相邻的数位。比如&#xf…

JS 构造函数实现封装性

通过构造函数实现封装性&#xff0c;构造函数生成的对象独立存在互不影响 创建实例对象时&#xff0c;其中函数的创建会浪费内存

一站式云分账系统!智能虚拟户分账系统成电商合规“刚需”

电商智能分账解决&#xff1a;电商一站式破解多平台资金管理难题集中管理分账&#xff0c;分账后秒到&#xff0c;并为针对电商行业三大核心痛点提供高效应对策略&#xff1a; 1. 票据合规困境 智能对接上下游交易数据流&#xff0c;构建自动化票据协同机制&#xff0c;有效规…

数组 array

1、数组定义 是一种用于存储多个相同类型数据的存储模型。 2、数组格式 &#xff08;1&#xff09;数据类型[ ] 变量名&#xff08;比较常见这种格式&#xff09; 例如&#xff1a; int [ ] arr0&#xff0c;定义了一个int类型的数组&#xff0c;数组名是arr0&#xff1b; &am…

linux命令六

逻辑卷 作用: 整合分散空间 空间支持扩大 步骤一:建立卷组&#xff08;VG&#xff09; 格式&#xff1a;vgcreate 卷组名 设备路径……. 如果分区不是卷组,则会先调用pvcreat 组建物理卷,再将其组建成组卷 Successfully:成功 example&#xff1a;例子 在man帮助中可以使用examp…

深度学习总结(8)

模型工作流程 模型由许多层链接在一起组成&#xff0c;并将输入数据映射为预测值。随后&#xff0c;损失函数将这些预测值与目标值进行比较&#xff0c;得到一个损失值&#xff0c;用于衡量模型预测值与预期结果之间的匹配程度。优化器将利用这个损失值来更新模型权重。 下面是…

基于docker搭建redis集群环境

在redis目录下创建redis-cluster目录&#xff0c;创建docker-compose.yml文化和generate.sh文件 【配置generate.sh文件】 for port in $(seq 1 9); \ do \ mkdir -p redis${port}/ touch redis${port}/redis.conf cat << EOF > redis${port}/redis.conf port 6379 …

分治-归并系列一>翻转对

目录 题目&#xff1a;解析&#xff1a;策略一&#xff1a; 代码&#xff1a;策略二&#xff1a; 代码&#xff1a; 题目&#xff1a; 链接: link 这题和逆序对区别点就是&#xff0c;要找到前一个元素是后一个元素的2倍 先找到目标值再&#xff0c;继续堆排序 解析&#xff1…

FFMPEG大文件视频分割传输教程,微信不支持1G文件以上

如下是一个2.77g的文件分割教程 . 前言 FFmpeg 是一个用于处理视频、音频等多媒体文件的开源工具包。它支持几乎所有的多媒体格式转换、剪辑和编辑&#xff0c;是开发者和多媒体工作者必备的工具。本文详细讲解如何在 Windows 系统上安装 FFmpeg 并进行基本配置。 2. 下载 FF…

MySQL5.7数据库部署和安装

1. 准备系统环境 Vmawre安装CentOS7 略… 2. 下载MySQL5.7安装包 下载地址&#xff1a; https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz 3. 卸载系统自带的MariaDB sudo yum remove $(rpm -qa | grep mariadb)4. 解压安…

Java学习手册:JVM、JRE和JDK的关系

在Java生态系统中&#xff0c;JVM&#xff08;Java虚拟机&#xff09;、JRE&#xff08;Java运行时环境&#xff09;和JDK&#xff08;Java开发工具包&#xff09;是三个核心概念。它们共同构成了Java语言运行和开发的基础。理解它们之间的关系对于Java开发者来说至关重要。本文…

如何用 esProc 实现 Oracle 和 MySQL 的混合运算

逻辑数仓可以实现多源混算&#xff0c;但需要配置视图、预处理数据&#xff0c;结构太沉重。duckdb 是轻量级的方案&#xff0c;但没有内置 Oracle 的 connector&#xff0c;自己开发难度又太高。同为轻量级方案&#xff0c;esProc 支持 JDBC 公共接口&#xff0c;可以实现任何…

Linux 第二讲 --- 基础指令(二)

前言 这是基础指令的第二部分&#xff0c;但是该部分的讲解会大量使用到基础指令&#xff08;一&#xff09;的内容&#xff0c;为了大家的观感&#xff0c;如果对Linux的一些基本指令不了解的话&#xff0c;可以先看基础指令&#xff08;一&#xff09;&#xff0c;同样的本文…