Lwip PPP 모뎀

ppposclient, RS485 장치를 동시에 사용하는 경우 (예제 포함)

장병남 2024. 5. 16. 01:17

ESP32 IoT보드에서 ppposclient 라이브러리 ( https://github.com/codezoo-ltd/ppposclient )와 RS485 라이브러리 ( https://github.com/4-20ma/ModbusMaster )를 동시에 사용할 경우 제대로 동작이 되지 않는다는 사용자의 질문 글이 며칠 전 개발자 커뮤니티에 올라왔습니다. 

 

각각의 라이브러리는 ESP32의 UART2, UART1에 각각 연결되어 동작되는데, 왜 동작이 되지 않는지 검토하고

코드를 개선해서 동작할 수 있도록 작업 했습니다. 

 

먼저 아래와 같이 하드웨어를 구성 했습니다. 

 

ESP32, LTE CATM1모뎀, RS485 to TTL컨버터 + Modbus RTU 온습도센서

 

< 사용조건 >

- LTE CATM1 모뎀은 ppposClient 라이브러리를 통해 Serial2(UART2)에 연결해서 ppp모드로 동작되는 상태입니다.

- 여기에 Serial1(UART1)에 연결한 RS485 컨버터를 이용해서 온습도센서 데이터를 읽으려고 하면 값이 읽히지 않고 통신이 블락됩니다.

< 원인 > 

- FreeRTOS 태스크 우선순위

 

일단 ppposClient 라이브러리는 PPPOS_init 함수를 호출할 때 내부에서 우선순위 5인 태스크를 호출합니다.

https://github.com/codezoo-ltd/ppposclient/blob/main/src/PPPOS.c : 170번 라인

 

void PPPOS_init(int txPin, int rxPin, int baudrate, int uart_number, char *user,

char *pass) {

  PPPOS_uart_num = uart_number;

  gpio_set_direction(txPin, GPIO_MODE_OUTPUT);

  gpio_set_direction(rxPin, GPIO_MODE_INPUT);

  gpio_set_pull_mode(rxPin, GPIO_PULLUP_ONLY);

  uart_config_t uart_config = {.baud_rate = baudrate,

  .data_bits = UART_DATA_8_BITS,

  .parity = UART_PARITY_DISABLE,

  .stop_bits = UART_STOP_BITS_1,

  .flow_ctrl = UART_HW_FLOWCTRL_DISABLE};

  uart_param_config(PPPOS_uart_num, &uart_config);

  uart_set_pin(PPPOS_uart_num, txPin, rxPin, UART_PIN_NO_CHANGE,

  UART_PIN_NO_CHANGE);

  uart_driver_install(PPPOS_uart_num, BUF_SIZE * 2, BUF_SIZE * 2, 0, NULL, 0);

  tcpip_adapter_init();

  PPP_User = user;

  PPP_Pass = pass;

  xTaskCreate(&pppos_client_task, "pppos_client_task", 10048, NULL, 5, NULL); //5 우선순위 (숫자가 클수록 우선순위 높음)

}

pppos_client_task가 내부에서 계속 동작되고 있는데, 이 태스크는 아두이노에서 호출되는 우선순위 0인 main 태스크 보다 우선순위가 높아서 자신이 가지고 있는 자원을 항상 먼저 독차지합니다.

 

- 붙잡고 있는 자원은 Serial 라이브러리에서 사용하는 UART API 함수들입니다.

- 자신이 붙잡고 있는 자원을 양보할 때는 아래 코드의 100ms 정도의 시간입니다.

  (vTaskDelay(100/ portTICK_PERIOD_MS);

- Modbus RTU 온도센서 읽기가 완료되지 않았는데 계속 자원을 빼앗기기 때문에 온도센서의 값을 제대로 읽어올 수 없습니다.

static void pppos_client_task(void *pvParameters) {

  char *data = (char *)malloc(BUF_SIZE);

  while (1) {

    while (PPPOS_started) {

      memset(data, 0, BUF_SIZE);

      int len = uart_read_bytes(PPPOS_uart_num, (uint8_t *)data, BUF_SIZE,

      10 / portTICK_RATE_MS);

      if (len > 0) {

        pppos_input_tcpip(ppp, (u8_t *)data, len);

    }

    vTaskDelay(100 / portTICK_PERIOD_MS);

  }

  vTaskDelay(100 / portTICK_PERIOD_MS);

  }

}

이제 ppposClient와 ModbusRTU가 동시에 동작할 수 있도록 코드를 작성해 보겠습니다.

#include <PPPOS.h>

#include <PPPOSClient.h>

#include "TYPE1SC.h"

#include <Arduino.h>

#include <U8x8lib.h>

#include <ModbusMaster.h>

#define SERIAL_BR 115200

#define GSM_SERIAL 2

#define GSM_RX 16

#define GSM_TX 17

#define GSM_BR 115200

#define PWR_PIN 5

#define RST_PIN 18

#define WAKEUP_PIN 19

#define EXT_ANT 4

#define DebugSerial Serial

#define M1Serial Serial2

char *ppp_user = "codezoo";

char *ppp_pass = "codezoo";

char *msg = "hello world ppp";

String APN = "simplio.apn";

#define TCP_SERVER "echo.mbedcloudtesting.com"

const uint16_t port = 7;

String buffer = "";

char *data = (char *)malloc(1024);

PPPOSClient ppposClient;

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE);

TYPE1SC TYPE1SC(M1Serial, DebugSerial, PWR_PIN, RST_PIN, WAKEUP_PIN);

#define U8LOG_WIDTH 16

#define U8LOG_HEIGHT 8

uint8_t u8log_buffer[U8LOG_WIDTH * U8LOG_HEIGHT];

U8X8LOG u8x8log;

/* EXT_ANT_ON 0 : Use an internal antenna.

* EXT_ANT_ON 1 : Use an external antenna.

*/

#define EXT_ANT_ON 0

float temp = 0.0F;

float humi = 0.0F;

uint8_t result;

void Modbus_task(void *pvParameters) {

HardwareSerial SerialPort(1); // use ESP32 UART1

ModbusMaster node;

result=node.ku8MBInvalidCRC;

/* Serial1 Initialization */

SerialPort.begin(9600, SERIAL_8N1, 33, 32); // RXD1 : 33, TXD1 : 32

// Modbus slave ID 1

node.begin(1, SerialPort);

// Callbacks allow us to configure the RS485 transceiver correctly

// Auto FlowControl - NULL

node.preTransmission(NULL);

node.postTransmission(NULL);

vTaskDelay(2000 / portTICK_PERIOD_MS);

while (1) {

// Request 2 registers starting at 0x0001

result = node.readInputRegisters(0x0001, 2);

if (result == node.ku8MBSuccess) {

temp = float(node.getResponseBuffer(0) / 10.00F);

humi = float(node.getResponseBuffer(1) / 10.00F);

// Get response data from sensor

}

vTaskDelay(1000 / portTICK_PERIOD_MS);

}

}

void extAntenna() {

if (EXT_ANT_ON) {

pinMode(EXT_ANT, OUTPUT);

digitalWrite(EXT_ANT, HIGH);

delay(100);

}

}

bool startPPPOS() {

PPPOS_start();

unsigned long _tg = millis();

while (!PPPOS_isConnected()) {

DebugSerial.println("ppp Ready...");

u8x8log.print("ppp Ready...\n");

if (millis() > (_tg + 30000)) {

PPPOS_stop();

return false;

}

delay(3000);

}

DebugSerial.println("PPPOS Started");

u8x8log.print("PPPOS Started\n");

return true;

}

void reconnect() {

// Loop until we're reconnected

while (!ppposClient.connected()) {

DebugSerial.print("Attempting TCP connection...");

u8x8log.print("\f"); // \f = form feed: clear the screen

u8x8log.print("Attempting TCP connection...");

// Attempt to connect

if (ppposClient.connect(TCP_SERVER, port)) {

DebugSerial.println("connected");

u8x8log.print("connected\n");

// Once connected, socket data send to server...

ppposClient.println("Device Ready.");

} else {

DebugSerial.print("socket failed");

DebugSerial.println(" try again in 5 seconds");

u8x8log.print("socket failed");

u8x8log.print(" try again in 5 seconds\n");

// Wait 5 seconds before retrying

delay(5000);

}

}

}

void setup() {

u8x8.begin();

u8x8.setFont(u8x8_font_chroma48medium8_r);

u8x8log.begin(u8x8, U8LOG_WIDTH, U8LOG_HEIGHT, u8log_buffer);

u8x8log.setRedrawMode(1); // 0: Update screen with newline, 1: Update screen for every char

// put your setup code here, to run once:

M1Serial.begin(SERIAL_BR);

DebugSerial.begin(SERIAL_BR);

DebugSerial.println("TYPE1SC Module Start!!!");

u8x8log.print("TYPE1SC Module Start!!!\n");

extAntenna();

/* TYPE1SC Module Initialization */

if (TYPE1SC.init()) {

DebugSerial.println("TYPE1SC Module Error!!!");

u8x8log.print("TYPE1SC Module Error!!!\n");

}

/* Network Regsistraiton Check */

while (TYPE1SC.canConnect() != 0) {

DebugSerial.println("Network not Ready !!!");

u8x8log.print("Network not Ready!!!\n");

delay(2000);

}

/* Get Time (GMT, (+36/4) ==> Korea +9hour) */

char szTime[32];

if (TYPE1SC.getCCLK(szTime, sizeof(szTime)) == 0) {

DebugSerial.print("Time : ");

DebugSerial.println(szTime);

u8x8log.print("Time : ");

u8x8log.print(szTime);

u8x8log.print("\n");

}

delay(1000);

int rssi, rsrp, rsrq, sinr;

for (int i = 0; i < 3; i++) {

/* Get RSSI */

if (TYPE1SC.getRSSI(&rssi) == 0) {

DebugSerial.println("Get RSSI Data");

u8x8log.print("Get RSSI Data\n");

}

/* Get RSRP */

if (TYPE1SC.getRSRP(&rsrp) == 0) {

DebugSerial.println("Get RSRP Data");

u8x8log.print("Get RSRP Data\n");

}

/* Get RSRQ */

if (TYPE1SC.getRSRQ(&rsrq) == 0) {

DebugSerial.println("Get RSRQ Data");

u8x8log.print("Get RSRQ Data\n");

}

/* Get SINR */

if (TYPE1SC.getSINR(&sinr) == 0) {

DebugSerial.println("Get SINR Data");

u8x8log.print("Get SINR Data\n");

}

delay(1000);

}

if (TYPE1SC.setPPP() == 0) {

DebugSerial.println("PPP mode change");

u8x8log.print("PPP mode change\n");

}

String RF_STATUS = "RSSI: " + String(rssi) + " RSRP:" + String(rsrp) + " RSRQ:" + String(rsrq) + " SINR:" + String(sinr);

DebugSerial.println("[RF_STATUS]");

DebugSerial.println(RF_STATUS);

u8x8log.print("[RF_STATUS]\n");

u8x8log.print(RF_STATUS);

u8x8log.print("\n");

DebugSerial.println("TYPE1SC Module Ready!!!");

u8x8log.print("TYPE1SC Module Ready!!!\n");

PPPOS_init(GSM_TX, GSM_RX, GSM_BR, GSM_SERIAL, ppp_user, ppp_pass);

DebugSerial.println("Starting PPPOS...");

u8x8log.print("Starting PPPOS...\n");

if (startPPPOS()) {

DebugSerial.println("Starting PPPOS... OK");

u8x8log.print("Starting PPPOS... OK\n");

u8x8log.print("\f"); // \f = form feed: clear the screen

} else {

DebugSerial.println("Starting PPPOS... Failed");

u8x8log.print("Starting PPPOS... Failed\n");

}

xTaskCreate(&Modbus_task, "Modbus_task", 2048, NULL, 6, NULL);

}

/* Test Property */

#define TEST_CNT 10

int cnt = 0;

void loop() {

if (PPPOS_isConnected()) {

if (!ppposClient.connected()) {

reconnect();

}

}

if (result == 0) {

DebugSerial.print("Temperature: ");

DebugSerial.println(String(temp, 2));

DebugSerial.print("Humidity: ");

DebugSerial.println(String(humi, 2));

}

// Data Receive with TCP Socket

// Read all the lines of the reply from Server and print them to Serial

while (ppposClient.available() > 0) {

String line = ppposClient.readStringUntil('\r');

DebugSerial.print(line);

u8x8log.print(line);

}

// Data Transfer with TCP Socket

if (cnt < TEST_CNT) {

ppposClient.println(msg);

delay(1000);

} else if (cnt == TEST_CNT) {

DebugSerial.println("socket Test complete.");

u8x8log.print("socket Test complete.\n");

}

cnt++;

}

빨간색 굵은 글자 내용만 주목해서 보시기 바랍니다.

1. Modbus로 동작시키는 부분만 Task로 묶습니다.

2. 태스크를 실행시킬 때 ppposClient 보다 우선순위를 하나 높여 둡니다. (읽는 동안 빼앗기지 않기 위해)

3. 읽기 성공한 값을 loop에서 읽거나 활용합니다.

태스크를 만들 때 주의하실 점은,

태스크 내부는 최대한 간결하게 만들고 print debug 구문은 모두 빼야 합니다. (자원 공유로 문제 일으킴)

 

동작하는 영상입니다.

ppposClient로 서버 접속 후 서버 데이터 전송, 수신, ModbusRTU 센서 데이터 획득이 동시에 동작됩니다.

10회 데이터 전송 후에는 ModbusRTU 센서 값만 표시합니다. 

 

 

Murata Type1SC 모듈 구매, 자료 관련 문의
(주)아성코리아
전지만 이사 010-5418-8812 mlcc@asung.com
박상인 수석 010-6556-5405 sipark@asung.com

LTE-CATM1 내장형 모뎀 구매 상담, 외주 개발, 협업 문의
(주)코드주
장병남 대표 010-8965-1323 rooney.jang@codezoo.co.kr

 

https://www.devicemart.co.kr/goods/view?no=14077527

 

LTE-CatM1 내장형 모뎀 / 사물인터넷 통신모듈

사물인터넷 개발을 위한 LTE 모듈입니다. / CodeZoo / 사물인터넷 통신모듈 / 유심은 상품상세의 링크에서 별도 구매가 필요합니다.

www.devicemart.co.kr

https://smartstore.naver.com/codezoo/products/8514695472

 

LTE CATM1 ESP32 IoT 보드 : codezoo

[codezoo] IoT Global SIM, IoT Connectivity, IoT Device

smartstore.naver.com

https://smartstore.naver.com/codezoo/products/7153689192

 

Vodafone Global IoT SIM Card : codezoo

[codezoo] IoT Global SIM, IoT Connectivity, IoT Device

smartstore.naver.com