STM32物联网项目-GPRS模块通信编程
原本第一版的代码每个发送AT指令的函数内容都差不多,比如检查网络状态,检查GPRS服务等,这些函数里不同的只是AT指令,应答,等待时间,除了个别函数如连接TCP服务器会有标志位的置位和定时器的清零等操作,这样相同的代码造成篇幅过长,所以进行修改优化。SIM800C通过手机卡联网,连接的是公网,自己在电脑上做实验的话,用的是局域网的网络调试助手,当在局域网内开启TCP服务器时,公网是找不到这个服务器
GPRS模块通信-编程
实验目的
32单片机通过串口2发送AT指令控制SIM800C检测GPRS网络,连接TCP服务器,连接服务器成功后,通过Doit.am远程信息转发服务将上传至公网服务器的温湿度值传到局域网的TCP客户端上,可在本地客户端查看温湿度情况,并且TCP客户端可以发送指令控制实战板的继电器以及蜂鸣器
Doit.am远程信息转发服务说明
SIM800C通过手机卡联网,连接的是公网,自己在电脑上做实验的话,用的是局域网的网络调试助手,当在局域网内开启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
如果网络调试助手接收到其他信息,可能其他人也在使用此端口,建议更换。
这种方法不好的地方就是,大家都使用同一个服务器,如果网络调试助手连接了一个不确定的端口,就有可能接收到别人的信息,造成信息泄漏
二、内网穿透(花生壳)
无需依赖公网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地址和端口号,都会收到其他客户端发送的信息
更多推荐
所有评论(0)