STM32 + PPP(GSM)+ LwIP

ほとんどのGSMモジュールは、ATコマンドを介してUARTインターフェイスで動作します。 しかし、深刻なプロジェクトの場合、ATコマンドの使用には特定の困難が伴います。



•制御およびエラー処理

•コマンドの結果が長い遅延で返される

•着信ラインを即座に解析する必要があります



コマンドを実行した結果、着信コール、SMS、受信データなどからのURCコードをバッファに入力できることを理解する必要があります。非常に異なる形式。 これらの理由により、ATを使用すると追加の遅延が発生しますが、アルゴリズム自体を排除することはほとんど不可能です。理由は、モジュール自体とファームウェアの欠陥にあるためです。



この例では、SIM800Cを使用しました。 仕様を見て、PPPがサポートされていることを確認した後、実装方法の研究を始めました。 PPPを使用するには、いくつかの構成コマンドでモジュールを切り替えます。その後、ATモードが使用できなくなり、実際にはモジュールの内部スタックをバイパスしてオペレーターのタワーと直接通信し、データ交換を大幅に高速化できます。



PPPパッケージの例:







各PPPパケットは、記号〜(0x7E)で始まり、終わります。 このプロトコルは接続認証、暗号化、データ圧縮をサポートしているため、独自のソリューションを作成するのは非常に困難です。 LwIPなど、PPPをサポートする既製のスタックを使用する方が論理的です。 PPPOSおよびPPPOE(シリアルおよびイーサネット経由)、PAPおよびCHAP認証プロトコルをサポートし、評判が高く、広く配布されています。



デモプロジェクト



フローチャート:









例は、FreeRTOSでのSTM32マイクロコントローラー用に開発されました。



プログラムの開始、周辺機器のセットアップ、タスクの作成
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); }
      
      







「上位」レベル。 接続の確立、データの受信、ミラーリング
 // - .    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セットアップ、詳細
 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); } }
      
      







PPPの上昇。



セッションを開始するには、4つのコマンド-comPPP_0-4が使用されます。 それらがどのように送信され、答えを理解するかは考慮しません。これは別の記事のトピックです。 一般的な用語でのみ考慮してください:



非表示のテキスト
 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; }
      
      







vGsmTaskタスクコードから、「GsmLLR_StartPPP」が成功すると、pppModeEnableフラグが設定され、uartParcerStruct.uart.rxQueueキューがクリアされます。 pppModeEnableフラグは、現在のモジュールモードを表示します。 UART割り込みとスタック/パーサー間の交換-キューを通過します。



GSMレベルでのタスクPPPセッション
 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); } }
      
      







一般的なPPP機能



接続と切断、接続ステータスの読み取り、送信など
 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); }
      
      







LwIPのTCP接続に関連する機能、コールバック
 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"); } } }
      
      







LwIPレイヤーをUARTおよびGSMモジュールと接続する機能、非常に重要なポイント
 //      -    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; } } }
      
      







TCPターミナルを介してデータを送信します。







構造では、パッケージが到着したことがわかります。







まとめると。



ATコマンドを拒否すると、コマンドの解析と送信、応答の複雑な(信頼できない可能性がある)解析、データとURCコードの受信のためのコードを大幅に削減および簡素化することができました。 ATコマンドは、モデムの初期セットアップとAPNパラメーターの記録のために残りました。



詳細な調査のために、ロジックアナライザによって記録されたPPPセッションは、 ここで取得できます



All Articles