📜 ⬆️ ⬇️

STM32 + PPP (GSM) + LwIP

Most GSM modules work on the UART interface via AT commands. But for serious projects, the use of AT commands carries certain difficulties:

• control and error handling
• the result of the command is returned with a long delay
• it is necessary to disassemble incoming lines on the fly

It is necessary to understand that with the result of the command execution, the URC code from an incoming call, SMS, received data, etc. can be included in the buffer. very different format. For these reasons, the use of AT introduces additional delay, it is practically impossible to eliminate it algorithmically, because the reason is in the module itself and the imperfection of its firmware.
')
In this example, I used the SIM800C. After looking at the specification and convinced of the support of PPP, I began to study ways of implementation. To use PPP, the module is switched by several configuration commands, then the AT mode becomes inaccessible and in fact communicates with the operator’s tower directly, bypassing the internal stack of the module, which significantly speeds up the data exchange.

Example PPP package:



Each PPP packet starts and ends with ~ (0x7E). The protocol supports connection authentication, encryption, and data compression, which makes it rather difficult to write your own solution. It is more logical to use a ready-made stack that supports PPP, for example LwIP. It supports PPPOS and PPPOE (Over serial and Ethernet), PAP and CHAP authentication protocols, has a good reputation and is widely distributed.

Demo project

Flow Chart:


Examples were developed for the STM32 microcontroller under FreeRTOS.

Starting the program, setting up peripherals, creating tasks
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); //  gsm,    LwIP InitGsmUart(); //    -     xTaskCreate(StartThread, "Start", configMINIMAL_STACK_SIZE*2, 0, tskIDLE_PRIORITY+1, &taskInitHandle); //   osKernelStart(NULL, NULL); while (1) {} } void StartThread(void * argument) { gsmTaskInit(); // , /  xTaskCreate(connectTask, "connectTask", configMINIMAL_STACK_SIZE*1, 0, tskIDLE_PRIORITY+1, NULL); //    vTaskDelete(NULL); } 


"Upper level. Connection setup, data reception and mirroring
 // - .    1,   ,   RAM    LwIP (  ) #define GSM_MAX_CONNECTION 1 //      typedef struct{ uint8_t *rxBuff; uint16_t rxLen; }sBuff[GSM_MAX_CONNECTION]; sBuff buff = {0}; void connectTask(void *pServiceNum) { bool connectState = false; eRetComm status = eError; uint16_t delay = 0; uint8_t serviceNum = *(uint8_t*)pServiceNum; xSemaphoreHandle xRxPppData; //     ppp xRxPppData = GsmLLR_GetRxSemphorePoint(serviceNum); for(;;) { /*    */ if(connectState == true) { //     while(GsmLLR_ConnectServiceStatus(serviceNum) == eOk) { //    buff[serviceNum].rxLen = getRxData(serviceNum, xRxPppData,&(buff[serviceNum].rxBuff)); if(buff[serviceNum].rxLen != 0) { //      if(GsmLLR_TcpSend(serviceNum, buff[serviceNum].rxBuff, buff[serviceNum].rxLen) == eOk) { printf("Connect:#%i SendData OK\r\n", serviceNum); }else{ printf("Connect:#%i SendData ERROR\r\n", serviceNum); connectState = false; } } } //   printf("Connect:#%i connection lost\r\n", serviceNum); GsmLLR_DisconnectService(serviceNum); connectState = false; delay = 1000; } else { //  ,  printf("Connect:#%i connecting...", serviceNum); //   if(GsmLLR_ConnectService(serviceNum) == eOk) { printf("Connect:#%i connected", serviceNum); connectState = true; } else { //    printf("Connect:#%i ERROR", serviceNum); delay = GSM_CONNECTION_ERROR_DELAY; connectState = false; } } vTaskDelay(delay/portTICK_RATE_MS); } } //   uint16_t getRxData(uint8_t serviceNum, xSemaphoreHandle xRxPppData, uint8_t **ppBufPacket) { uint16_t retLen = 0; uint16_t size = 0; if(xSemaphoreTake(xRxPppData, 1000/portTICK_PERIOD_MS) == pdTRUE) { size = gsmLLR_TcpGetRxCount(serviceNum); if(size > 1512) { retLen = 0; }else { retLen = GsmLLR_TcpReadData(serviceNum, ppBufPacket, size); } } return retLen; } 


GSM setting, details
 void gsmTaskInit(void) { xTaskCreate(vGsmTask, "GSM", configMINIMAL_STACK_SIZE*2, 0, tskIDLE_PRIORITY+1, &gsmInitTaskId); while((!gsmState.init) || (!pppIsOpen)) {vTaskDelay(100/portTICK_PERIOD_MS);} } /*     GSM  */ void vGsmTask( void * pvParameters ) { //   GsmLLR_Init(); GsmLLR2_Init(); GsmPPP_Init(); //     while((gsmState.initLLR != true) && (gsmState.initLLR2 != true)){}; if(GsmLLR_PowerUp() != eOk) { GsmLLR_ModuleLost(); } for(;;) { //  if(gsmState.init == false) { //     if(gsmState.notRespond == true) { printf("GSM: INIT Module lost\r\n"); GsmLLR_ModuleLost(); continue; } //   if(GsmLLR_ATAT() != eOk) { gsmState.notRespond = true; continue; } //     if(GsmLLR_WarningOff() != eOk) { gsmState.notRespond = true; continue; } //   if(GsmLLR_FlowControl() != eOk) { gsmState.notRespond = true; continue; } //  IMEI if(GsmLLR_GetIMEI(aIMEI) != eOk) { gsmState.notRespond = true; continue; } DBGInfo("GSM: module IMEI=%s\r\n", aIMEI); //  IMSI if(GsmLLR_GetIMSI(aIMSI) != eOk) { gsmState.notRespond = true; continue; } printf("GSM: module IMSI=%s\r\n", aIMSI); //  Software if(GsmLLR_GetModuleSoftWareVersion(aVerionSoftware) != eOk) { gsmState.notRespond = true; continue; } //      (URC) if(GsmLLR_AtCREG() != eOk) { gsmState.notRespond = true; continue; } printf("GSM: CREG OK\r\n"); //    if(GsmLLR_UpdateCSQ(&gsmCsqValue) != eOk) { printf("GSM: Get CSQ ERROR, -RELOAD\r\n"); gsmState.notRespond = true; continue; }else{ printf("GSM: CSQ value %d\r\n", gsmCsqValue); //  SMS if(GsmLLR_SmsModeSelect(sms_TEXT) != eOk) { gsmState.notRespond = true; continue; } // sms vTaskDelay(DELAY_REPLY_INIT/portTICK_RATE_MS); if(GsmLLR_SmsClearAll() != eOk) { printf("GSM: clear SMS ERROR, -RELOAD\r\n"); gsmState.notRespond = true; continue; } printf("GSM: Clear SMS Ok\r\n"); printf("GSM: INIT PPPP\r\n"); if(GsmLLR_StartPPP(&connectionSettings.gsmSettings) == eOk) { printf("GSM: INIT PPPP - PPP RUN\r\n"); xQueueReset(uartParcerStruct.uart.rxQueue); uartParcerStruct.ppp.pppModeEnable = true; uartParcerStruct.uart.receiveState = true; gsmState.init = true; }else{ printf("GSM: INIT PPPP - PPP ERROR!!!\r\n"); gsmState.notRespond = true; continue; } } } vTaskDelay(1000/portTICK_RATE_MS); } } 


Raising PPP.

To start the session 4 commands are used - comPPP_0-4. How they are sent and the answer is analyzed we do not consider, this is a topic for a separate article. Consider only in general terms:

Hidden text
 char *comPPP_0[] = {"AT+CGDCONT=1,\"IP\","}; char *comPPP_2[] = {"AT+CGQMIN=1,0,0,0,0,0"}; char *comPPP_3[] = {"AT+CGQREQ=1,2,4,3,6,31"}; char *comPPP_4[] = {"ATD*99***1#"}; eRetComm GsmLLR_StartPPP(sGsmSettings *pSettings) { printf("StartPPP\r\n"); sResultCommand resultCommand; char **comPPP_Mass[3] = {comPPP_2, comPPP_3, comPPP_4}; uint8_t *pData = NULL; if(GsmLLR_GetMutex() == true) { pData = pvPortMalloc(GSM_MALLOC_COMMAND_SIZE); if(pData != NULL) { memset(pData, 0, GSM_MALLOC_COMMAND_SIZE); sprintf((char*)pData, "%s%s", comPPP_0[0], (char*)pSettings->gprsApn); RunAtCommand((char*)pData, &resultCommand); //  ,     uint8_t stepIndex = 0; while(stepIndex != (3)) { uint16_t len = strlen((char*)*comPPP_Mass[stepIndex]); sprintf((char*)pData, "%s", (char*)*comPPP_Mass[stepIndex]); RunAtCommand((char*)pData, &resultCommand); stepIndex++; } memset(pData, 0, GSM_MALLOC_COMMAND_SIZE); vPortFree(pData); } GsmLLR_GiveMutex(); } return eOk; } 


From the vGsmTask task code, it follows that in case of successful execution of “GsmLLR_StartPPP”, the pppModeEnable flag is set and the uartParcerStruct.uart.rxQueue queue is cleared. The pppModeEnable flag displays the current mode of the module. The exchange between the interruption of the UART and the stack / command parser goes through a queue.

Task Session PPP at GSM Level
 void GsmPPP_Tsk(void *pvParamter) { int timeout = 0; uint8_t i; bool stateInit = false; uint16_t tskStackInit; LwipStack_Init(); pppInit(); pppSetAuth(PPPAUTHTYPE_CHAP, connectionSettings.gsmSettings.gprsUser, connectionSettings.gsmSettings.gprsPass); sioWriteSemaphore = xSemaphoreCreateBinary(); for(i=0; i<GSM_MAX_CONNECTION; i++) { connectionPppStruct.semphr[i] = xSemaphoreCreateBinary(); connectionPppStruct.rxData[i].rxSemh = xSemaphoreCreateBinary(); } for(;;) { //      PPP     if(uartParcerStruct.ppp.pppModeEnable == true) { if(!pppIsOpen) { pppNumport = pppOverSerialOpen(0, linkStatusCB, &pppIsOpen); pppStop = 0; timeout = 0; stateInit = false; while(timeout < 300) { if(pppIsOpen) { printf("PPP init - OK\r\n"); lwip_stats.link.drop = 0; lwip_stats.link.chkerr = 0; lwip_stats.link.err = 0; stateInit = true; break; }else{ timeout ++; vTaskDelay(100/portTICK_RATE_MS); } } if(stateInit != true) { printf("PPP init - TIMEOUT-ERROR\r\n"); pppClose(pppNumport); pppIsOpen = false; uartParcerStruct.ppp.pppModeEnable = false; gsmState.init = false; gsmState.notRespond = true; } }else{ if((lwip_stats.link.drop !=0) || (lwip_stats.link.chkerr !=0)) { lwip_stats.link.drop = 0; lwip_stats.link.chkerr = 0; printf("GSMM: DROPING FAIL!!! RESTART PPP\r\n"); for(i=0; i<SERVERS_COUNT; i++) { GsmPPP_Disconnect(i); } pppClose(pppNumport); pppIsOpen = false; uartParcerStruct.ppp.pppModeEnable = false; gsmState.init = false; gsmState.notRespond = true; vTaskDelay(500/portTICK_PERIOD_MS); } } } vTaskDelay(500/portTICK_RATE_MS); } } 


Common functions working with PPP

Connect-Disconnect, read connection status, send, etc.
 bool GsmPPP_Connect(uint8_t numConnect, char *pDestAddr, uint16_t port) { struct ip_addr resolved = {0}; bool useDns = false; uint8_t ipCut[4] = {0}; if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } sscanf(pDestAddr, "%i.%i.%i.%i", &ipCut[0], &ipCut[1], &ipCut[2], &ipCut[3]); if((ipCut[0]!=0)&&(ipCut[1]!=0)&&(ipCut[2]!=0)&&(ipCut[3]!=0)) { IP4_ADDR(&connectionPppStruct.ipRemoteAddr[numConnect], ipCut[0],ipCut[1],ipCut[2],ipCut[3]); //31,10,4,158); useDns = false; }else{ useDns = true; } if(connectionPppStruct.connected[numConnect] == false) { connectionPppStruct.tcpClient[numConnect] = tcp_new(); // create tcpPcb tcp_recv(connectionPppStruct.tcpClient[numConnect], server_recv); if(useDns == true) { switch(dns_gethostbyname(pDestAddr, &resolved, destServerFound, &numConnect)) { case ERR_OK: // numeric or cached, returned in resolved connectionPppStruct.ipRemoteAddr[numConnect].addr = resolved.addr; break; case ERR_INPROGRESS: // need to ask, will return data via callback if(xSemaphoreTake(connectionPppStruct.semphr[numConnect], 10000/portTICK_PERIOD_MS) != pdTRUE) { while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); } connectionPppStruct.connected[numConnect] = false; printf("GSMPPP: dns-ERROR\r\n"); return false; }else{ } break; } } tcp_connect(connectionPppStruct.tcpClient[numConnect], &connectionPppStruct.ipRemoteAddr[numConnect], port, &TcpConnectedCallBack); if(xSemaphoreTake(connectionPppStruct.semphr[numConnect], 10000/portTICK_PERIOD_MS) == pdTRUE) { connectionPppStruct.connected[numConnect] = true; printf("GSMPPP: connected %s\r\n", inet_ntoa(connectionPppStruct.ipRemoteAddr)); return true; }else{ tcp_abort(connectionPppStruct.tcpClient[numConnect]);//tcp_close(connectionPppStruct.tcpClient[numConnect]); while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); } printf("GSMPPP: connectTimeout-ERROR\r\n"); return false; } }else{ if(GsmLLR_ConnectServiceStatus(numConnect) == eOk) { printf("GSMPPP: CONNECT-already connected %s\r\n", inet_ntoa(connectionPppStruct.ipRemoteAddr)); return true; }else{ printf("GSMPPP: CONNECT CLOSE!!!\r\n"); return false; } } return false; } bool GsmPPP_Disconnect(uint8_t numConnect) { if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } if(connectionPppStruct.tcpClient[numConnect] == NULL) { return false; } while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); } connectionPppStruct.connected[numConnect] = false; return true; } bool GsmPPP_ConnectStatus(uint8_t numConnect) { if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } if(connectionPppStruct.tcpClient[numConnect]->state == ESTABLISHED) { return true; } return false; } bool GsmPPP_SendData(uint8_t numConnect, uint8_t *pData, uint16_t len) { if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } if(tcp_write(connectionPppStruct.tcpClient[numConnect], pData, len, NULL) == ERR_OK) { return true; }else { while(tcp_close(connectionPppStruct.tcpClient[numConnect]) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); } connectionPppStruct.connected[numConnect] = false; connectionPppStruct.rxData[numConnect].rxBufferLen = 0; memset(connectionPppStruct.rxData[numConnect].rxBuffer,0, sizeof(connectionPppStruct.rxData[numConnect].rxBuffer)); } return false; } uint16_t GsmPPP_GetRxLenData(uint8_t numConnect) { if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } return connectionPppStruct.rxData[numConnect].rxBufferLen; } uint16_t GsmPPP_ReadRxData(uint8_t numConnect, uint8_t **ppData) { if(!pppIsOpen) { printf("GSMPPP: CONNECT ERROR - PPP closed\r\n"); return false; } if(connectionPppStruct.rxData[numConnect].rxBufferLen != 0) { *ppData = (uint8_t *) connectionPppStruct.rxData[numConnect].rxBuffer; uint16_t retLen = connectionPppStruct.rxData[numConnect].rxBufferLen; connectionPppStruct.rxData[numConnect].rxBufferLen = 0; return retLen; } return false; } static void destServerFound(const char *name, struct ip_addr *ipaddr, void *arg) { uint8_t *num = (uint8_t*)arg; if(*num < SERVERS_COUNT) { printf("GSMPPP: DEST FOUND %s\r\n", inet_ntoa(ipaddr->addr)); connectionPppStruct.ipRemoteAddr[*num].addr = ipaddr->addr; xSemaphoreGive(connectionPppStruct.semphr[*num]); }else{ printf("GSMPPP: DNS != SERVER%s\r\n", inet_ntoa(ipaddr->addr)); } } static err_t TcpConnectedCallBack(void *arg, struct tcp_pcb *tpcb, err_t err) { for(uint8_t i=0; i<SERVERS_COUNT; i++) { if(tpcb == connectionPppStruct.tcpClient[i]) { printf("GSMPPP: connected (callback)%s\r\n", inet_ntoa(tpcb->local_ip.addr)); xSemaphoreGive(connectionPppStruct.semphr[i]); break; } } } static err_t server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { LWIP_UNUSED_ARG(arg); if(err == ERR_OK && p != NULL) { tcp_recved(pcb, p->tot_len); printf("GSMPPP:server_recv(): pbuf->len %d byte\n [%s]", p->len, inet_ntoa(pcb->remote_ip.addr)); for(uint8_t i=0; i<SERVERS_COUNT; i++) { if(pcb->remote_ip.addr == connectionPppStruct.tcpClient[i]->remote_ip.addr) { printf("GSMPPP: server_recv (callback) [%s]\r\n", inet_ntoa(pcb->remote_ip.addr)); if(p->len < sizeof(connectionPppStruct.rxData[i].rxBuffer)) { memcpy(connectionPppStruct.rxData[i].rxBuffer, p->payload, p->len); connectionPppStruct.rxData[i].rxBufferLen = p->len; xSemaphoreGive(connectionPppStruct.rxData[i].rxSemh); printf("GSMPPP: server_recv (callback) GIVE SEMPH[%s][%d]\r\n", inet_ntoa(pcb->remote_ip.addr), p->len); }else{ printf("GSMPPP: server_recv p->len > sizeof(buf) -ERROR\r\n"); } } } pbuf_free(p); }else{ printf("\nserver_recv(): Errors-> "); if (err != ERR_OK) printf("1) Connection is not on ERR_OK state, but in %d state->\n", err); if (p == NULL) printf("2) Pbuf pointer p is a NULL pointer->\n "); printf("server_recv(): Closing server-side connection..."); pbuf_free(p); server_close(pcb); } return ERR_OK; } xSemaphoreHandle * GsmPPP_GetRxSemaphorePoint(uint8_t numService) { return (connectionPppStruct.rxData[numService].rxSemh); } 


Functions related to TCP connection in LwIP, callback-s
 static err_t server_poll(void *arg, struct tcp_pcb *pcb) { static int counter = 1; LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(pcb); printf("\nserver_poll(): Call number %d\n", counter++); return ERR_OK; } static err_t server_err(void *arg, err_t err) { LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(err); printf("\nserver_err(): Fatal error, exiting...\n"); return ERR_OK; } static void server_close(struct tcp_pcb *pcb) { tcp_arg(pcb, NULL); tcp_sent(pcb, NULL); tcp_recv(pcb, NULL); while(tcp_close(pcb) != ERR_OK) { vTaskDelay(100/portTICK_PERIOD_MS); } for(uint8_t i=0; i<SERVERS_COUNT; i++) { if(pcb == connectionPppStruct.tcpClient[i]) { printf("GSMPPP: server_close (callback)%s\r\n", inet_ntoa(pcb->local_ip.addr)); connectionPppStruct.connected[i] = false; }else{ printf("GSMPPP: server_recv p->len > sizeof(buf) -ERROR\r\n"); } } } 


Functions connecting the LwIP level with the UART and the GSM module, a very important point
 //      -    LwIP u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len) { unsigned long i = 0; if(uartParcerStruct.ppp.pppModeEnable) { while(xQueueReceive(uartParcerStruct.uart.rxQueue,&data[i], 0) == pdTRUE) { if(i==0) { printf("Reading PPP packet from UART\r\n"); } printf("%0.2x ", data[i]); i++; if (pppStop||(i==len)) { pppStop = false; return i; } } if (i>0) { printf("\n"); } } return i; } //   LwIP  UART (GSM) u32_t sio_write(sio_fd_t fd, u8_t *data, u32_t len) { u32_t retLen = 0; if(uartParcerStruct.ppp.pppModeEnable) { if(HAL_UART_Transmit_IT(&huart3, data, len) == HAL_OK) { xSemaphoreTake(sioWriteSemaphore, portMAX_DELAY); retLen = len; }else{ printf("HAL ERRROR WRITE [sio_write]\r\n"); } }else{ printf("sio_write not in PPP mode!\r\n"); } return retLen; } //  ,   void sio_read_abort(sio_fd_t fd) { pppStop = true; xQueueReset(uartParcerStruct.uart.rxQueue); } u32_t sys_jiffies(void) { return xTaskGetTickCount(); } //          static void linkStatusCB(void * ctx, int errCode, void * arg) { printf("GSMPP: linkStatusCB\r\n"); /* just wait */ bool *connected = (bool*)ctx; struct ppp_addrs * addrs = arg; switch (errCode) { case PPPERR_NONE: { /* We are connected */ printf("ip_addr = %s\r\n", inet_ntoa(addrs->our_ipaddr)); printf("netmask = %s\r\n", inet_ntoa(addrs->netmask)); printf("dns1 = %s\r\n", inet_ntoa(addrs->dns1)); printf("dns2 = %s\r\n", inet_ntoa(addrs->dns2)); *connected = 1; break; } case PPPERR_CONNECT: { printf("lost connection\r\n"); /* just wait */ *connected = 0; break; } default: { /* We have lost connection */ printf("connection error\r\n"); /* just wait */ *connected = 0; break; } } } 


We send data via TCP terminal:



In the structures you can see the incoming package:



Let's sum up.

Having abandoned AT commands, we managed to significantly reduce and simplify the code for parsing and sending commands, difficult (potentially unreliable) parsing of answers, receiving data and URC codes. AT commands remained for initial modem setup and recording APN parameters.

The PPP session recorded by the logic analyzer can be found here for detailed study.

Source: https://habr.com/ru/post/332742/


All Articles