GPRS模块通信-编程

实验目的

32单片机通过串口2发送AT指令控制SIM800C检测GPRS网络,连接TCP服务器,连接服务器成功后,通过Doit.am远程信息转发服务将上传至公网服务器的温湿度值传到局域网的TCP客户端上,可在本地客户端查看温湿度情况,并且TCP客户端可以发送指令控制实战板的继电器以及蜂鸣器

Doit.am远程信息转发服务说明

SIM800C通过手机卡联网,连接的是公网,自己在电脑上做实验的话,用的是局域网的网络调试助手,当在局域网内开启TCP服务器时,公网是找不到这个服务器地址的,所以需要借助一些手段,让这个内网里的服务器能被公网找到,目前知道的有两种方法

一、Doit.am远程信息转发服务

网址:http://tcp.doit.am/

转发原理:多个客户端连接服务器,一个客户端向服务器发送数据,服务器向其他客户端群发接受到的消息。

打开网址就有使用步骤介绍,其实方法很简单,这家公司提供了一个公网的服务器,TCP客户端1连接上这个服务器(IP:115.29.109.104,端口:6531),然后另一个客户端2也连接上这个服务器,注意端口要一致,这样客户端1发送信息到服务器,服务器就会往同样端口的客户端2转发信息;

这样,单片机通过SIM800C连接到了网络,相当于一个TCP客户端,发送AT指令控制SIM800C连接上这个公网的服务器(115.29.109.104),然后电脑上的网络调试助手设置为TCP客户端,也连接上这个服务器(115.29.109.104),就能使SIM800C与网络调试助手通信

在这里插入图片描述

注意:SIM800C连接的服务器端口号是6531,网络调试助手同样也是连接6531这个端口,两者端口号一样才能接收到另一方的信息;端口号不一定是6531,也可是6511,6512,6510

如果网络调试助手接收到其他信息,可能其他人也在使用此端口,建议更换。

这种方法不好的地方就是,大家都使用同一个服务器,如果网络调试助手连接了一个不确定的端口,就有可能接收到别人的信息,造成信息泄漏

二、内网穿透(花生壳)

网址:https://hsk.oray.com/

无需依赖公网IP、无需配置路由器,花生壳支持在客户端上添加端口映射,快速将内网服务发布到外网

这种方法简单粗暴,直接将内网的IP地址映射到公网上,也叫内网穿透,不过需要下载客户端;

这样就不经过公网服务器转发,可以将网络调试助手设置为TCP服务器,然后将IP地址映射到公网,让SIM800C连接

CubeMX配置

SIM800C硬件电路

在这里插入图片描述

GPIO配置

根据硬件电路,开关机信号的PF13配置为推挽输出,输出高电平三极管导通,输出低电平三极管截止;模块状态引脚PF15设置为下拉输入,因为模块刚开机时STATUS引脚状态不确定,如果PF15设置为浮空输入的话则无法判断,所以要下拉;TCP_Status为TCP服务器连接状态引脚,连接服务器时为低电平,断开连接时为高电平,所以PF14设置为外部中断上升沿,检测服务器是否断开连接

在这里插入图片描述

串口2配置

串口2配置与ESP-12S模块一样,实验板上通过插针切换WiFi和GPRS

在这里插入图片描述

使能串口2接收DAM和发送DMA

在这里插入图片描述

程序

SIM800C模块开机

在这里插入图片描述

SIM800C模块关机

在这里插入图片描述

因为SIM800C模块工作电压为4V,与单片机不是同一个电压,所以单片机按复位键重启时,可能模块还是启动状态,所以复位模块就要先关机,延时一会后再开机,如果模块一开始就是关机状态,则进行开机操作;开关机状态是通过STATUS引脚来判断,读取该引脚电平状态,如果是高电平,则是开机状态,如果是低电平,则是关机状态

关机后立马开机要按照手册说明至少延时800ms,这里延时1s

开机或者关机超过10s,则进行错误处理

该启动函数放到Peripheral_Set自定义初始化函数中,一上电就启动,不放在主循环里;放主循环里的作用是如果模块连接TCP服务器失败,就重启再连,直到成功为止

SIM800C.c

/*
* @name   StartUp
* @brief  模块启动
* @param  None
* @retval None   
*/
static void StartUp()
{
    //如果模块处于开机状态,则先关机
    if(READ_GPRS_STATUS == GPIO_PIN_SET) //如果STATUS引脚处于高电平,则是开机状态   
    {
        printf("The module of SIM800C shutdown!\r\n");
        //通过硬件电路,拉低PWRKEY引脚至少1.5秒关机
        SET_GPRS_PWRKEY;
        HAL_Delay(1000);
        HAL_Delay(1000);
        HAL_Delay(1000);
        CLR_GPRS_PWRKEY;
        
        //等待模块关机完成
        Timer6.usDelay_Timer = 0;
        while(READ_GPRS_STATUS == GPIO_PIN_SET) //如果STATUS引脚仍然是高电平,还是开机状态
        {
            //如果定时器计时到10s,说明模块10s内没关机成功,进行错误处理
            if(Timer6.usDelay_Timer >= TIMER_10s)
            {
                SIM800C.Error();
                break;
            }
        }
        //按手册提示,关机后迅速重启模块,至少延时800ms
		HAL_Delay(1000);
    }
    //如果模块处于关机状态,则开机
    if(READ_GPRS_STATUS == GPIO_PIN_RESET) //如果STATUS引脚处于低电平,则是关机状态   
    {
        printf("The module of SIM800C startup!\r\n");
        //通过硬件电路,拉低PWRKEY引脚至少1秒开机
        SET_GPRS_PWRKEY;
        HAL_Delay(1000);
        HAL_Delay(1000);
        CLR_GPRS_PWRKEY;
        
        //等待模块开机完成
        Timer6.usDelay_Timer = 0;
        while(READ_GPRS_STATUS == GPIO_PIN_RESET) //如果STATUS引脚仍然是低电平,还是关机状态
        {
            //如果定时器计时到10s,说明模块10s内没开机成功,进行错误处理
            if(Timer6.usDelay_Timer >= TIMER_10s)
            {
                SIM800C.Error();
                break;
            }
        }
    }
}

SIM800C.c

通过TCP连接服务器函数会被主函数调用,执行一系列发送AT指令操作,控制SIM800C模块连接TCP服务器

这里需要注意的点是:在连接TCP服务器成功之前的发送AT后接收模块的应答,用的都是HAL_UART_Receive_DMA这个函数,在连接TCP服务器成功后想要使用HAL_UARTEx_ReceiveToIdle_DMA函数接收服务器数据,就要先把串口2的DMA接收停止,再开启DMA接收至空闲中断函数;如果不停止,则网络调试助手发送的信息无法控制实验板外设

/*
* @name   TCP_Connect_Server
* @brief  通过TCP连接服务器
* @param  None
* @retval None   
*/
static void TCP_Connect_Server()
{   
    Moudle_Sync_Baud();                             //同步波特率
    SendAT((uint8_t*)ATE0,(uint8_t *)"OK");         //关闭回显
    Moudle_Check_SIM_Status();                      //检查SIM卡状态
    SendAT((uint8_t*)AT_CCID,(uint8_t*)"OK");       //显示ICCID
    Moudle_Check_Network_Register();                //检查网络状态
    Moudle_Check_Attach_GPRS_Service();             //检查GPRS服务
    SendAT((uint8_t*)AT_CIPMODE,(uint8_t*)"OK");    //设置链接模式为透传模式
    SendAT((uint8_t*)AT_CSTT,(uint8_t*)"OK");       //设置APN连接
    SendAT((uint8_t*)AT_CSQ,(uint8_t*)"OK");         //查看信号强度
    Moudle_Establish_Wireless_Connection();         //建立无线链路
    SendAT((uint8_t*)AT_CIFSR,(uint8_t*)".");       //获取本地IP地址
    Moudle_Connect_TCP_Server();                    //连接TCP服务器
    
    if(SIM800C.TCP_Connect_Status == TRUE)
    {
        //需要先关闭串口2的DMA,不然会收到上面DMA的影响,开启DMA接收至空闲中断后收不到服务器信息
        HAL_UART_DMAStop(&huart2);
        //__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
        //开启DMA接收至空闲中断
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2,UART2.pucRec_Buffer,PUCREC_BUFFER_LEN);
    }
}

System.c

主函数中,一开始TCP连接标志位TCP_Connect_Status为FALSE,服务器重连定时器TCP_Reconnect_Timer为10s,所以一上电就会进行一次连接TCP服务器操作,如果连接成功,则TCP_Connect_Status标志位被置位,下次循环则不会再次进入判断中,直接执行传送SHT30温湿度数据到服务器的函数

/*
* @name   Run
* @brief  系统运行
* @param  None
* @retval None   
*/
static void Run()
{  
  /*
  省略调用SHT30获取温湿度函数和数码管显示函数
  */
//连接TCP服务器
  if(SIM800C.TCP_Connect_Status == FALSE)
  {
    if(SIM800C.TCP_Reconnect_Timer >= TIMER_10s)
    {
      //连接服务器
      SIM800C.TCP_Connect_Server();
    }
  }

  //传送SHT30温湿度数据
  SIM800C.Transfer_SHT30();

  //检测按键关闭SIM800C
  if(SIM800C.Press_Shutdown_Flag == TRUE)
  {
    //模块关机
    SIM800C.Moudle_Shutdown();
  }
  //延时
  HAL_Delay(1000);
}

SIM800C.c

在接收服务器返回数据函数中,接收完一组数据,先停止DMA接收,清除接收缓存,再调用HAL_UARTEx_ReceiveToIdle_DMA函数开启DMA接收,而不是HAL_UART_Receive_DMA函数

/*
* @name   Receive_Information
* @brief  接收服务器返回数据
* @param  None
* @retval None   
*/
static void Receive_Information()
{
    if(SIM800C.TCP_Connect_Status == TRUE)
    {       
        printf("%s\r\n",UART2.pucRec_Buffer);
        
        //切换继电器状态
        if(strstr((const char*)UART2.pucRec_Buffer,"Relay Flip") != NULL)
        {
            Relay.Relay_Flip();
        }

        //切换蜂鸣器状态
        if(strstr((const char*)UART2.pucRec_Buffer,"Buzzer Flip") != NULL)
        {
            Buzzer.Buzzer_Flip();
        }
        //串口2停止DMA接收
        HAL_UART_DMAStop(&huart2);
        //清除缓存
        Public.Memory_Clr(UART2.pucRec_Buffer,strlen((const char*)UART2.pucRec_Buffer));
        //重新开始串口2 DMA接收
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2,UART2.pucRec_Buffer,PUCREC_BUFFER_LEN);
    }
}

优化

优化原因:

原本第一版的代码每个发送AT指令的函数内容都差不多,比如检查网络状态,检查GPRS服务等,这些函数里不同的只是AT指令,应答,等待时间,除了个别函数如连接TCP服务器会有标志位的置位和定时器的清零等操作,这样相同的代码造成篇幅过长,所以进行修改优化

优化方法:

将各函数相同的代码抽取出来,写成一个独立的函数SendAT_DelayTimer,参数有待发送的AT指令,应答,以及等待时间,要发送什么样的AT指令直接调用这一个函数即可

/*
* @name   SendAT_DelayTimer
* @brief  发送AT指令并等待回答,超时处理
* @param  AT_command:AT指令,Respond_Str:模块回应,TIMER_Value:超时时间
* @retval None   
*/
static void SendAT_DelayTimer(uint8_t* AT_Command,uint8_t* Respond_Str,TIMER_Value_t TIMER_Value)
{
    if(SIM800C_Connect_Error_Flag == FALSE)
    {
        Timer6.usDelay_Timer = 0;
        do
        {
            //DMA接收设置
            SIM800C.DMA_Receive_Set();
            //发送AT指令
            UART2.SendString((uint8_t *)AT_Command);
            //打印信息
            printf(AT_Command);
            //延时,等待接收完毕
            HAL_Delay(1000);
            //打印信息
            printf("%s",UART2.pucRec_Buffer);
            
            //超时处理
            if(Timer6.usDelay_Timer >= TIMER_Value)
            {
                SIM800C.Error();
                break;
            }
        } while (strstr((const char*)UART2.pucRec_Buffer,(const char*)Respond_Str) == NULL);
    }
}

调用

/*
* @name   TCP_Connect_Server
* @brief  通过TCP连接服务器
* @param  None
* @retval None   
*/
static void TCP_Connect_Server()
{   
    Moudle_Sync_Baud();                                                 //同步波特率
    SendAT_DelayTimer((uint8_t*)ATE0,(uint8_t *)"OK",TIMER_10s);        //关闭回显

    SendAT_DelayTimer((uint8_t*)AT_CPIN,(uint8_t*)"OK",TIMER_10s);      //检查SIM卡状态
    SendAT_DelayTimer((uint8_t*)AT_CCID,(uint8_t*)"OK",TIMER_10s);      //显示ICCID      
    SendAT_DelayTimer((uint8_t*)AT_CREG,(uint8_t*)"0,1",TIMER_2min);    //检查网络状态       
    SendAT_DelayTimer((uint8_t*)AT_CGATT,(uint8_t*)"1",TIMER_2min);     //检查GPRS服务

    SendAT_DelayTimer((uint8_t*)AT_CIPMODE,(uint8_t*)"OK",TIMER_10s);   //设置链接模式为透传模式
    SendAT_DelayTimer((uint8_t*)AT_CSTT,(uint8_t*)"OK",TIMER_10s);      //设置APN连接
    SendAT_DelayTimer((uint8_t*)AT_CSQ,(uint8_t*)"OK",TIMER_10s);       //查看信号强度

    Moudle_Establish_Wireless_Connection();                             //建立无线链路
    SendAT_DelayTimer((uint8_t*)AT_CIFSR,(uint8_t*)".",TIMER_10s);      //获取本地IP地址
    Moudle_Connect_TCP_Server();                                        //连接TCP服务器
    
    if(SIM800C.TCP_Connect_Status == TRUE)
    {
        //需要先关闭串口2的DMA,不然会收到上面DMA的影响,开启DMA接收至空闲中断后收不到服务器信息
        HAL_UART_DMAStop(&huart2);
        //__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
        //开启DMA接收至空闲中断
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2,UART2.pucRec_Buffer,PUCREC_BUFFER_LEN);
    }
}
优化结果:

能成功连接上服务器,实验效果与第一版相同

实验效果

系统上电打印初始化信息,然后单片机开始发送AT指令控制SIM8000C模块,同时也会将AT指令发送内容以及模块应答打印到串口1,方便查看调式

在这里插入图片描述

发送AT指令:AT+CIPSTART=TCP,115.29.109.104,6510到SIM800C模块,连接公网服务器,返回CONNECT则表示连接服务器成功,Relay Flip是网络调试助手发送的翻转实验板继电器的指令,说明服务器转发信息成功,两个不同IP地址的客户端已经成功通信

在这里插入图片描述

网络调试助手可以接收到实验板的SHT30温湿度数据,同时发送Relay Flip可以控制实验板继电器,还有蜂鸣器的指令没演示发送

在这里插入图片描述

用手机开启TCP客户端,连接公网服务器,同样的端口号,也会收到实验板的温湿度值,手机发送信息也能控制板子继电器,进一步验证了公网服务器的转发作用,任何客户端连接上这个IP地址和端口号,都会收到其他客户端发送的信息

在这里插入图片描述

更多推荐