-
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에 각각 연결되어 동작되는데, 왜 동작이 되지 않는지 검토하고
코드를 개선해서 동작할 수 있도록 작업 했습니다.
먼저 아래와 같이 하드웨어를 구성 했습니다.
< 사용조건 >
- 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.krhttps://www.devicemart.co.kr/goods/view?no=14077527
https://smartstore.naver.com/codezoo/products/8514695472
https://smartstore.naver.com/codezoo/products/7153689192
'Lwip PPP 모뎀' 카테고리의 다른 글
함께 도전하고 만들어갑니다. ESP-IDF(esp_modem) (0) 2024.07.02 HiveMQ TLS Free MQTT 클러스터와 LTE CATM1모뎀으로 어디서나 제어할 수 있는 스마트릴레이 만들기 (11) 2024.05.25 ESP32, LTE CATM1 모뎀으로 nano iot gateway 제작 (0) 2024.03.17 ppposclient 에서 TYPE1SC 라이브러리를 사용하고 싶어요 (0) 2022.05.10 CATM1, PPPOS, MQTT로 실외 MCU 디바이스 제어 (2) 2022.04.19