ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ppposclient, RS485 장치를 동시에 사용하는 경우 (예제 포함)
    Lwip PPP 모뎀 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

     

     

     

Designed by Tistory.