1

在之前的教程中,无线通信技术我们学习了蓝牙和 WiFi,今天我们要来学习 4G。

4G 模块在距离上有个突破,它不像蓝牙短距离,也不像 WiFi 只能在局域网,4G 模块可使用户无论在哪,只要有 4G 网络信号覆盖,就可以通讯。

4G 模块是一种用于无线通信的设备,它支持第四代移动通信网络(4G 网络)技术。简单来说,4G模块就像是手机里的一个小芯片,它可以连接到移动通信网络,使设备能够无线传输数据和进行通信。通过 4G 模块,这些设备可以与移动通信网络进行无线连接,并实现高速数据传输、语音通话、视频流媒体、在线游戏等功能。

4G 模块在物联网领域扮演着重要的角色。它们可以嵌入到各种物联网设备中,如智能家居设备、智能健康监测器、智能交通系统等,实现设备之间的互联互通,为物联网应用提供可靠的无线连接。

1. 源码下载及前置阅读

本文首发 良许嵌入式网https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/4g-tutorial.html

如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。

前期教程,没看过的小伙伴可以先看下。

作者简介
大家好,我是良许,博客里所有的文章皆为我的原创。
下面是我的一些个人介绍,欢迎交个朋友:
· 211工科硕士,国家奖学金获得者;
· 深耕嵌入式11年,前世界500强外企高级嵌入式工程师;
· 书籍《速学Linux作者》,机械工业出版社专家委员会成员;
· 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万;
· 靠自媒体连续年入百万,靠自己买房买车。

我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:

电子书链接:https://www.lxlinux.net/1024.html

2. 4G模块介绍

4G 模块市面上有很多厂家生产的不同型号,一般购买的时候厂家都会给你用户手册,大家可以看着使用。

本次我使用的是亿佰特公司的 E840-TTL(EC03-DNC),如果你是小白,跟着本文学一遍,其他 4G模块也就一通百通了。

官网链接,大家有需要可以去下载用户手册和工具软件:EC03-DNC 4G模块

2.1 EC03-DNC介绍

EC03-DNC 是亿佰特推出的 LTE CAT1 数传模块产品,该产品软件功能完善,覆盖绝大多数常规应用场景。支持移动、联通、电信 4G卡。

EC03-DNC 是为实现串口设备与网络服务器,通过网络相互传输数据而开发的产品,该产品是一款带分集接收功能的 LTE-FDD/LTE-TDD 无线通信数传模块,支持 LTE-FDD、LTE-TDD 网络数据连接,用户只需通过简单的设置,即可实现串口到网络服务器的双向数据透明传输。

EC03-DNC 工作模式分为透传模式和配置模式。

  • 透传模式:

上电后模块默认工作在透传模式,并自动开始网络连接,当与服务器建立连接后,串口收到的任意数据将被透传到服务端。同时也可以接收来自服务端的数据,收到服务端数据后模块将直接通过串口输出。

  • 配置(AT)模式:

该模式下串口数据均视为 AT 指令。透传模式下串口收到「+++」帧数据后,3秒内 RX 引脚收到任意 AT 指令,则模式切换到 AT 模式。AT 模式下,发送「AT+EXAT<CR><LF>」切换到透传模式。

2.2 功能特点

  • 采用最新 4G CAT1 方案;
  • 支持数据透明传输;
  • 支持 TCP、UDP 网络协议;
  • 支持心跳包、注册包功能最大支持64个字节数;
  • 支持 MQTT 协议,支持接入 OneNet 平台、百度云平台、阿里云平台的 MQTT 服务;
  • 支持2路 Socket 链路同时收发;
  • 支持 Modbus RTU 与 Modbus TCP 自动相互转换;
  • 支持网络 AT 指令,可以通过网络,远程配置设备;
  • 软件看门狗设计,系统稳定;
  • 支持 APN/VPN。

2.3 工作参数及引脚介绍

工作参数:

  • 工作电压:DC 5V~18V
  • 工作温度:-40℃~+85℃
  • 默认波特率:115200
  • 天线:1代 IPEX 接口
  • 耗流(受网络环境影响,仅供参考):驻网(连接基站):150mA@12v;入网静态:60mA@12v;数据传输:110mA@12v
  • SIM 卡座:使用MICRO自弹式SIM卡座

引脚介绍:

EC03-DNC说明
RST模块复位,拉低有效
IO_RT低电平持续 3~10S,模块参数将恢复出厂设置,并立即重启
LIKASocketA 链路连接状态指示引脚,
高:SocketA 与网络服务器连接成功;
低:SocketA 未成功连接到网络服务器;
LIKBSocketB 链路连接状态指示引脚,
高:SocketB 与网络服务器连接成功;
低:SocketB 未成功连接到网络服务器;
DATA数据收发指示引脚,当网络接收到数据或者串口接收到数据(50ms 高/10ms低)
STAT设备状态指示引脚,
低:设备上电到正在搜寻 SIM 卡;
1800ms 低,200ms高:设备检查到正确的 SIM 卡,正在附着网络;
高:设备附着网络成功;
RXD数据接收引脚,默认 3.3V,可兼容 5V 通信电平
TXD数据发送引脚,默认 3.3V,可兼容 5V 通信电平
VEF驱动电平供电引脚,如需要实现串口通信和 LED 指示为 5V 驱动电平时需要在此引脚输入 5V 电平
MOD/SLE/PA11/PA10/ENNC,暂未开放,默认悬空
4V2锂电池电源供电引脚,供电范围:3.8V~4.3V。该引脚禁止反接、禁止与 VCC 一起供电
GND接地
VCCDC 电源供电引脚,供电范围:5V~18V。该引脚禁止反接、禁止与 4V2 一起供电
VD外接 SIM 卡电源引脚,若使用板载 SIM 卡座则该引脚 NC 即可
RS外接 SIM 卡复位引脚,若使用板载 SIM 卡座则该引脚 NC 即可
DA外接 SIM 卡数据引脚,若使用板载 SIM 卡座则该引脚 NC 即可
CL外接 SIM 卡时钟引脚,若使用板载 SIM 卡座则该引脚 NC 即可
PWR电源指示灯
天线天线接口
WORK工作状态指示灯,正常工作时亮起
DATA数据收发指示灯 ,有数据收发时亮起
LINKB网络连接指示灯B ,连接时亮起
LINKA网络连接指示灯A ,连接时亮起

3. AT指令

3.1 AT指令集

AT 指令集如下,详细的介绍大家可以看官网的 AT 指令手册。

AT指令说明
REBT重启模块
CPIN查询版本号
EXAT退出 AT 指令模式
RESTORE恢复出厂设置
UART设置/查询串口参数
IMEI查询模块 IMEI
LINKSTA查询 SOCK 连接状态
LINKSTA1查询 SOCK1 连接状态
SOCK设置/查询 SOCK 参数
SOCK1设置/查询 SOCK1 参数
REGMOD设置/查询注册包模式
REGINFO设置/查询自定义注册包信息(ASCII)
REGINFONEW设置/查询自定义注册包信息(16 进制)
HEARTMOD设置/查询心跳包模式
HEARTINFO设置/查询自定义心跳包信息(ASCII)
HEARTINFONEW设置/查询自定义心跳包信息(16 进制)
HEARTM设置/查询心跳包时间
SHORTM设置/查询短连接时间
CREG查询是否注册到网络
CSQ查询信号强度
CPIN查询 SIM 卡状态
POTOCOL查询/设置是否开启协议传输
MODBUS设置/查询 ModbusTCP/RTU 转换功能
MTCPID设置/查询 ModbusTCP 事件标识符
NETHEAD设置/查询网络 AT 指令头
MQTTMODE设置/查询 MQTT 模式
MQTT_ADDRESS设置/查询物联网平台地址、端口
MQTT_CONNECT设置/查询接入物联网平台的参数
MQTT_SUBSCRIBE_TOPIC设置/查询订阅消息的 topic、消息等级
MQTT_PUBLISH_TOPIC设置/查询发布消息的 topic、消息等级
MQTT_ALIAUTH设置/查询阿里云三要素
APN查询/设置 APN 信息
APN_ENABLE查询/设置 APN 使能
WORK_MODE查询/设置工作模式

正常发送指令后会收到带有「OK」的返回值,如果你没收到,那就是哪里有错误,可以参照下面这个指令错误码:

错误码说明
-1无效的命令格式
-2无效的命令
-3无效的操作符
-4无效的参数
-5操作不允许

3.2 串口调试示例

3.2.1 硬件连接

我们首先插好天线和 SIM 卡,SIM 卡最好带卡套插,不然很容易插不准,而且不易取出。

接线如下:

EC03-DNCUSB转TTL
VCC5V
GNDGND
RXDTXD
TXDRXD

连好上电后,等五秒左右初始化,「WORK」指示灯亮,表示正常工作。如果「WORK」不亮或闪烁可以换一张 SIM 卡试试。

3.2.2 串口调试

上电后模块默认工作在透传模式,我们需要在串口发送「+++」(不带回车),3秒内发送任意 AT 指

令,进入 AT 模式。

以下是串口调试示例,我查询了心跳包的一些数据,并将心跳包内容配置成「LX_4G」,配置好后需要发送「AT+REBT」,重启生效。大家做好配置后都需要重启生效。

我们可以发现每次发送 AT 指令,「DATA」灯会闪烁。如果不闪是不正常的哦。

4. 内网穿透

4G 模块需要公网通信,我们需要进行一下内网穿透(内网 IP 穿透),也就是为局域网的设备提供一个外网可访问的地址和端口。

这里我们使用花生壳进行内网穿透,虽然花生壳有些功能收费,但是本教程在免费范围内,放心,不收钱。

如果你家的路由器很牛,自带内外穿透的功能,就不用花生壳了,直接用路由器内网穿透。

电脑下载花生壳,花生壳官网:贝锐花生壳内网穿透

下载好后按照指引在手机上注册登录。

登录成功后如下操作:

点击确定后效果如下:

再开一个网络调试助手,协议类型选择「TCP Client」,远程主机地址填我们花生壳的映射地址。

连接后,发送消息,服务器可以收到。

虽然我们现在是在一台电脑上做测试,但是「TCP Client」访问的是公网 IP 地址。

顺带一提,此时服务器既可以通过公网地址连接通讯,也可以通过局域网地址连接通讯。

5. 编程实战

5.1 通信示意图

实现目标是 4G 模块控制 LED 灯。我们有一个三色 LED 灯,4G 模块连到服务器,服务器下发指令:「green」 则绿灯亮,再次发送「green」则绿灯灭,黄灯和红灯的关键词是「yellow」、「red」 ,效果相同。

5.2 4G模块配置

我们需要通过 AT 指令配置 4G 模块联连接服务器。

接线如下,和刚刚串口调制一样:

EC03-DNCUSB转TTL
VCC5V
GNDGND
RXDTXD
TXDRXD
  1. 将天线、SIM 卡、USB转TTL 等硬件连接好。
  2. 观察「WORK」灯是否亮起,亮起表示正常工作。
  3. 打开串口调试助手进入配置(AT)模式。默认波特率115200,发送「+++」(不带回车),3秒内发送任意 AT 指令,进入 AT 模式。
  4. 可以发送「AT+CPIN」,回复1表示有检测到 SIM 卡;「AT+CSQ」查看信号强度;「AT+ICCID」查询 SIM 卡信息等等检测 SIM 卡是否正常工作。
  5. 发送「AT+SOCK=TCPC,<IP地址>,<端口> 」连接 socket 服务器。大家看着自己的花生壳发送,我这里发送的是「AT+SOCK=TCPC,8g66h75462.vicp.fun,19142 」,也可以发送「AT+SOCK=TCPC,115.236.153.174,19142 」。

  1. 发送「AT+LINKSTA 」查看连接状态,串口返回「+OK=Connect」表示连接成功
  2. 发送「AT+REBT」重启生效,服务器就可以收到我们前面设置的「LX_4G」心跳包。

整体效果如下:

注意:AT 指令要以带回车,全英文符号,别带有多余空格。

5.3 硬件接线

本教程使用的硬件如下:

  • 单片机:STM32F103C8T6
  • 4G模块:EC03-DNC
  • 小灯:三色 LED 灯模块
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2
EC03-DNCLEDSTM32USB 转 TTL
VCC 3.3
RXD A2
TXD A3
GND G
RA5
YA6
GA7
GNDG
A10TX
A9RX
GGND

烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法】。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下图:

5.4 LED逻辑代码

LED 灯的代码简简单单,只要进行一下三个灯的初始化就行。

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED1_GPIO_CLK_ENABLE();                                 /* LED1时钟使能 */
    LED2_GPIO_CLK_ENABLE();                                 /* LED2时钟使能 */
    LED3_GPIO_CLK_ENABLE();                                 /* LED3时钟使能 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引脚 */

    gpio_init_struct.Pin = LED2_GPIO_PIN;                   /* LED2引脚 */
    HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);       /* 初始化LED2引脚 */
    
    gpio_init_struct.Pin = LED3_GPIO_PIN;                   /* LED3引脚 */
    HAL_GPIO_Init(LED3_GPIO_PORT, &gpio_init_struct);       /* 初始化LED3引脚 */

    LED1(0);                                                /* 关闭 LED1 */
    LED2(0);                                                /* 关闭 LED2 */
    LED3(0);                                                /* 关闭 LED3 */
}

LED 的 .h文件:

#ifndef _LED_H
#define _LED_H
#include "sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED1_GPIO_PORT                  GPIOA
#define LED1_GPIO_PIN                   GPIO_PIN_7
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED2_GPIO_PORT                  GPIOA
#define LED2_GPIO_PIN                   GPIO_PIN_6
#define LED2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED3_GPIO_PORT                  GPIOA
#define LED3_GPIO_PIN                   GPIO_PIN_5
#define LED3_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PB口时钟使能 */

/******************************************************************************************/
/* LED端口定义 */
#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED2(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED3(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

/* LED取反定义 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻转LED1 */
#define LED2_TOGGLE()   do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_GPIO_PIN); }while(0)        /* 翻转LED2 */
#define LED3_TOGGLE()   do{ HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_GPIO_PIN); }while(0)        /* 翻转LED3 */

/******************************************************************************************/
/* 外部接口函数*/
void led_init(void);                                                                            /* LED初始化 */

#endif

5.5 4G模块初始化

4G 模块初始化代码如下:

UART_HandleTypeDef _4g_uart_handle;

void _4g_init(uint32_t baudrate)
{
    _4g_uart_handle.Instance          = _4G_INTERFACE;                /* 4G */
    _4g_uart_handle.Init.BaudRate     = baudrate;                     /* 波特率 */
    _4g_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 数据位 */
    _4g_uart_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */
    _4g_uart_handle.Init.Parity       = UART_PARITY_NONE;             /* 校验位 */
    _4g_uart_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收发模式 */
    _4g_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 无硬件流控 */
    _4g_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 过采样 */
    HAL_UART_Init(&_4g_uart_handle);                                  /* 使能4G */
}

5.6 4G收发

4G 模块通过串口与 MCU 进行通讯,所以第一步需要先做好串口的配置。

关于串口的配置,我写过一篇文章手把手教你玩串口,大家可以移步下文查看:

STM32串口接收不定长数据(接收中断+超时判断)

4G 接收具体代码如下:

uint8_t _4g_uart_rx_buf[_4G_RX_BUF_SIZE];
uint8_t _4g_uart_tx_buf[_4G_TX_BUF_SIZE];
uint16_t _4g_uart_rx_len = 0;


void _4g_rx_clear(void)
{
    memset(_4g_uart_rx_buf, 0, sizeof(_4g_uart_rx_buf));             //清空接收缓冲区
    _4g_uart_rx_len = 0;                                             //接收计数器清零
}

void _4G_IRQHandler(void)
{
    uint8_t receive_data = 0;

    if(__HAL_UART_GET_FLAG(&_4g_uart_handle, UART_FLAG_RXNE) != RESET){      //获取接收RXNE标志位是否被置位
        if(_4g_uart_rx_len >= sizeof(_4g_uart_rx_buf))                       //如果接收的字符数大于接收缓冲区大小,
            _4g_uart_rx_len = 0;                                             //则将接收计数器清零
        HAL_UART_Receive(&_4g_uart_handle, &receive_data, 1, 1000);          //接收一个字符
        _4g_uart_rx_buf[_4g_uart_rx_len++] = receive_data;                   //将接收到的字符保存在接收缓冲区
    }

    if (__HAL_UART_GET_FLAG(&_4g_uart_handle, UART_FLAG_IDLE) != RESET)      //获取接收空闲中断标志位是否被置位
    {
        printf("recv: %s\r\n", _4g_uart_rx_buf);                             //将接收到的数据打印出来
        control_led();                                                       //检测是否有LED关键词
        _4g_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&_4g_uart_handle);                         //清除UART总线空闲中断
    }
}

通过这几个函数,我们就可以读取 4G 返回的数据,并保存在数组 bt_uart_rx_buf 里。

如果需要通过串口向 4G 模块发送数据,可以使用下面函数:

void _4g_send(char *fmt, ...)
{
    va_list ap;
    uint16_t len;

    va_start(ap, fmt);
    vsprintf((char *)_4g_uart_tx_buf, fmt, ap);
    va_end(ap);

    len = strlen((const char *)_4g_uart_tx_buf);
    HAL_UART_Transmit(&_4g_uart_handle, _4g_uart_tx_buf, len, HAL_MAX_DELAY);
}

其实是否向 4G 模块发送数据并不影响我们的实现效果,留着的目的一方面为了让大家复习一下,另一方面可以看出 4G 模块是否在正常工作。

至此,4G 模块的初始化、发送、接收部分就做好了。

5.7 LED控制

检测 4G 模块是否接收到 LED 指令,如果有就反转 LED 灯状态。

void control_led()
{
    if(strstr((const char *)_4g_uart_rx_buf, "green") != NULL)          //如果接收到关键词"green"
        LED1_TOGGLE();                                                  // 翻转LED1
    if(strstr((const char *)_4g_uart_rx_buf, "yellow") != NULL)         //如果接收到关键词"yellow"
        LED2_TOGGLE();                                                  // 翻转LED2
    if(strstr((const char *)_4g_uart_rx_buf, "red") != NULL)            //如果接收到关键词"red"
        LED3_TOGGLE();                                                  // 翻转LED3
}

5.8 主函数

在 main 函数里,我们可以先调用 _4g_init() 函数进行初始化,然后调用 _4g_send() 函数发送数据。

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    usart_init(115200);                         /* 串口1波特率设为115200 */
    _4g_init(115200);                           /* 串口2波特率设为115200 */
    led_init();

    printf("4G控制灯……\r\n");

    while(1)
    {
        _4g_send("4G send\r\n");
        delay_ms(1000);
    }
}

6. 运行过程

将硬件连好,把串口插到电脑 USB 口。

接着我们打开电脑串口软件。设置串口助手波特率 115200(你们不一定要用我这款,随便的串口助手都可以),选择串口号,最后打开串口开始准备接收数据。

这个串口工具接收的是 MCU 串口 1 的数据,也就是 log 。4G 模块接收到数据后,我们使用串口 1 打印到下面的串口助手里。

烧录代码,打开串口调试助手和网络调试助手,上电。

比如我们用服务器(网络调试助手)给 4G 模块发送数据「red」、「yellow」、「green」。

可以看到串口助手成功接收到了「red」 、「yellow」、「green」。

我们的三个小灯也打开了。

再次发送关键词即可关对应的灯。当然,一次发送 「green yellow red」,就可以控制三个小灯一起反转。

细心的同学可能发现了,不需要串口调试助手、USB 转 TTL,也可以实现我们的目标,我的目的是让大家的更清楚 4G 模块的收发过程,大家可以自行删减。

7. 总结

4G 模块在无线通信领域扮演着重要的角色,它为我们提供了便捷、高效的无线网络连接,改变了我们与世界的交流方式,并推动了移动通信和物联网技术的发展,从 3G 到 4G,速度的提升带来了短视频的爆发、全民直播的兴起、手机支付的流行。同时 5G 时代的来临更加让人期待,5G 的上传下载是 4G 的十倍,相信 5G 带来我们的绝不只是速度的提升。

希望本文能帮助您了解和使用 4G 模块。感谢各位看官,peace and love!

另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!


良许
1k 声望1.8k 粉丝