• STM32物联网项目-GPRS模块通信编程


    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;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    优化

    优化原因:

    原本第一版的代码每个发送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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    调用

    /*
    * @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    优化结果:

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

    实验效果

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

  • 相关阅读:
    linux系统Jenkins的安装
    No servers available for service: renren…。 Gateway 网关报503错误 ,已解决
    拥抱数字化转型浪潮,供应链集采管理系统助力照明企业紧抓机遇,快速发展!
    【6.26更新】Win11 23H2 22631.3810镜像:免费下载!
    通过删除字母匹配到字典里最长单词
    Golang 手写一个并发任务 manager
    APP自动化之weditor工具
    Adobe奇葩续费机制被网友狂喷:一不留神就扣2500,按月付费还随时取订?长点心吧...
    Windows环境部署Hadoop-3.3.2和Spark3.3.2
    期货多空代码(期货多空趋势指标源码)
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126745568