STM32 + PPP (GSM) + LwIP +23


Большинство GSM модулей работают по интерфейсу UART, посредством AT-команд. Но для серьезных проектов, использование AT команд несет определенные трудности:

• контроль и обработка ошибок
• результат выполнения команды возвращается с длительной задержкой
• необходимо разбирать входящие строки налету

Нужно понимать, что с результатом выполнения команды, в буфер может попасть URC-код от входящего звонка, SMS, принятые данные и пр. Входной буфер с принятыми строками, приходится разбирать опираясь лишь на символы переносов и «эхо» команды, а сами команды зачастую сильно отличаются форматом. По этим причинам, использование AT вносит дополнительную задержку, алгоритмически ее устранить практически невозможно, ведь причина находится в самом модуле и несовершенстве его встроенного ПО

В этом примере я использовал SIM800C. Посмотрев спецификацию и убедившись в поддержке PPP, стал изучать способы реализации. Для использования PPP, модуль переключается несколькими настроечными командами, после этого режим AT становится недоступным и фактически идет общение с вышкой оператора напрямую, минуя внутренний стек модуля, что позволяет значительно ускорить обмен данными.

Пример PPP-пакета:



Каждый пакет PPP начинается и заканчивается символом ~ (0x7E). Протокол поддерживает аутентификацию соединения, шифрование и сжатие данных, по этому довольно сложен для написания собственного решения. Логичнее использовать готовый стек, поддерживающий PPP, например LwIP. Он поддерживает PPPOS и PPPOE (Over serial и Ethernet), протоколы аутентификации PAP и CHAP, имеет хорошую репутацию и широко распространен.

Демо проект

Блок-схема:


Примеры разрабатывались для микроконтроллера STM32 под FreeRTOS.

Старт программы, настройка периферии, создание задач
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 и стеком/разборщиком команд — идет через очередь.

Задача сессия PPP на уровне GSM
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

Connect-Disconnect, чтение статуса соединения, отправка и т.п.
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);
}


Функции связанные с соединением TCP в 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");
		}
	}
}


Функции соединяющие уровень 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, для подробного изучения, можно взять здесь.
-->


К сожалению, не доступен сервер mySQL