ESP32 mirror serial monitor to LCD - Page 1
Forum section: electronics / general

author: PSR B1257
author_rank: Frequent Contributor
timestamp: July 11, 2023, 06:36:19 pm
content: Hello allI have a working code which generates a MODBUS-request string, reads the answer of the device and prints it to the serial monitor.So far so good.Only if I attempt to modify the code, it doesn't work anymore....where have I seen that beforeThe readout to the serial monitor changes completely and on the LCD is only FFFFFFF displayed.This is the code in question.Code:#include <LiquidCrystal_I2C.h>#include <Arduino.h>#define RX 16 // UART2 RX#define TX 17 // UART2 TX#define RTS_pin 5 // RS485 Direction control#define RS485Transmit HIGH#define RS485Receive LOW// set the LCD number of columns and rowsint lcdColumns = 16;int lcdRows = 2;//LCD SCL: Pin 22; SDA: Pin 21// set LCD address, number of columns and rowsLiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows);void setup(){// initialize LCDlcd.init();lcd.backlight();lcd.setCursor(0, 0);lcd.print("Test RS485");pinMode(RTS_pin, OUTPUT);digitalWrite(RTS_pin, RS485Receive);// Start the built-in serial port, for Serial MonitorSerial.begin(115200);Serial.println("Test RS485 Connection");// Start the Modbus serial PortSerial2.begin(9600, SERIAL_8N1, RX, TX);delay(1000);while(Serial2.available()){Serial.print(Serial2.read(), HEX);Serial.print(" ");}delay(1000);}String Hex2String(uint8_t num) {char hexCar;sprintf(hexCar, "0x%02X", num);return hexCar;}void loop(){byte request[] = {0x01 ,0x04 ,0x04 ,0x23 ,0x00 ,0x02 ,0x81 ,0x31};Serial.println("Wright data ....");for(uint8_t i=0; i<sizeof(request); i++){Serial.print(Hex2String(request));Serial.print(' ');}Serial.println("");digitalWrite(RTS_pin, RS485Transmit);Serial2.write(request, sizeof(request));Serial2.flush();digitalWrite(RTS_pin,RS485Receive);delay(100);Serial.println("Read data ....");while(Serial2.available()){Serial.print(Serial2.read(), HEX);Serial.print(" ");lcd.setCursor(0, 1);lcd.print(Serial2.read(), HEX);}Serial.println("");delay(2000);}Only if the last lcd.print-statementsCode:lcd.setCursor(0, 1);lcd.print(Serial2.read(), HEX);are deleted/commented the code is working.Any ideas, how to fix this?Disclosure: The Arduino IDE is complaining, that the LCD-library may not be compatible with the ESP32-architecture. However the first line is printed correctly.

author: liaifat85
author_rank: Regular Contributor
timestamp: July 12, 2023, 03:42:25 pm
content: You can check the I2C LCD part with this code:https://randomnerdtutorials.com/esp32-esp8266-i2c-lcd-arduino-ide/

author: PSR B1257
author_rank: Frequent Contributor
timestamp: July 12, 2023, 07:01:15 pm
content: The LCD itself is working.But any lines of code added to theCode:while(Serial2.available())loop seems to corrupt the whole program.Outside the loop nothing meaningful is displayed on the LCD either.I've looked at several examples of how to convert a HEX-number to a string (or whatever), which all don't work here, since they have already a 'neat' HEX-number stored in a variable.But I want to use theCode:Serial2.read()data.

author: PSR B1257
author_rank: Frequent Contributor
timestamp: July 14, 2023, 06:56:13 pm
content: Displaying the same content on the LCD as on the serial port is now working.Code:String LCDcontent ="";if (Serial2.available() > 0){// read the incoming string:LCDcontent = Serial2.readString();lcd.setCursor(0, 1);for(byte i=0; i<LCDcontent.length(); i++){Serial.print(LCDcontent,HEX);Serial.write(' ');lcd.print(Hex2String_LCD(LCDcontent));}}That was just for generic testing, without the need for a PC to read the serial port.What I really want, is extracting the data-bytes (according to counter i=3 and 4) of the whole response and convert it into binary. Until now I was not able to extract a specific byte from the string. Outside the for-loop the data seems not to existand inside the loop with something likeCode:lcd.print(Hex2String_LCD(LCDcontent));does not work either.Any suggestions how to fix this?

author: ajb
author_rank: Super Contributor
timestamp: July 15, 2023, 07:14:04 pm
content: I think you have a number of problems here. A couple of them are in here:Code:String Hex2String(uint8_t num) {char hexCar;sprintf(hexCar, "0x%02X", num);return hexCar;}The big problem is that you're using sprintf to write five characters ("0x__" plus terminating null) to a buffer that can only hold two characters. That means you're writing three characters outside of the array, which means potentially overwriting other variables. In this case, since the buffer has function scope and lifetime, it's probably just writing past the end of the stack rather than overwriting anything immediately important, but it's still a VERY bad idea. You need to either ensure that the buffer you pass to sprintf is large enough to hold any possible output, or use snprintf to limit the maximum number of characters written.In this case, if `hexCar` is allocated within the stack frame for `Hex2String`, then the call to `sprintf` creates a new stack frame somewhere after `hexCar`. When `sprintf` writes past the end of `hexCar`, it's likely overwriting its own stack frame and all sorts of weird things might happen. At a minimum it would corrupt the return data or pushed register values, causing strange behavior on return from `sprintf`, or if overwritten far enough, could corrupt the internal state of `snprintf` and cause things the function itself to go haywire. It all depends on the architecture, implementation, and how far past the end of the stack is written.Something else to consider: you are returning a pointer-to-char from a function that returns a String. That should be implicitly invoking a constructor for String that first allocates memory for a new String instance, and loads it with a copy of `hexCar`. The function then returns a reference to the new String object. This means that for every byte received from the serial port you are creating a new String object that consumes multiple bytes of memory (at least five for the string contents, plus however many bytes for the object's other properties). If you don't clean these up at some point, you will run out of memory.If you were to change the function signature to return `char *`, you would avoid the string construction and memory consumption, but have different problems. Since the `hexCar` array has function scope it is only valid within the function. You'd be getting around that by returning a pointer to it (because the name of an array is treated as a pointer to the first element in the array), except that the lifetime of `hexCar` is the lifetime of the function. Once the function exits, any data held by `hexCar` is functionally gone. If it's getting allocated on the stack, that memory is getting reused by the next new stack frame after the function exits and overwritten by who knows what. You could add the `static` keyword to hexCar to extend its lifetime, so that its value is retained after the function exits (this is often used to retain state between invocations of a function), but only until the function is called again, at which point `hexCar` may hold some other value. If you aren't calling `Hex2String` from other contexts (like ISRs), then that may 'work' for you, but it would generally be better to declare the buffer external to the function, or have `Hex2String` take a pointer to the buffer it should use.In your original code, you also have a problem here:Code:while(Serial2.available()){Serial.print(Serial2.read(), HEX);Serial.print(" ");lcd.setCursor(0, 1);lcd.print(Serial2.read(), HEX);}On the first call, `Serial.read()` returns the first byte available from the Serial object. On the second call, it returns the next byte available. So as written, the first byte goes out the serial port, the second to the lcd, the third to the serial port, the fourth to the lcd, etc.The way to fix this would be to only call Serial.read() once per char, something like:Code:while(Serial2.available()){char c = Serial2.read();Serial.print(c, HEX);Serial.print(" ");lcd.setCursor(0, 1);lcd.print(c, HEX);}

author: PSR B1257
author_rank: Frequent Contributor
timestamp: July 16, 2023, 07:48:11 am
content: Thank you for your comprehensive answerand sorry for the confusion about the Hex2String-subroutine. For the LCD I have added a modified version to the program.Code:String Hex2String_LCD(uint8_t num) {char hexCar_LCD;sprintf(hexCar_LCD, "%02X", num);return hexCar_LCD;}QuoteGood to know. That explains why the serial monitor as only shown half the data.QuoteWow, that's beyond my scope of programming.Can this be easily applied to the current program?The main-goal still is to extract the 'data-bytes' from the string.QuoteI tried that before and it does not work. The LCD is only showing the last byte of the string.

author: ozcar
author_rank: Frequent Contributor
timestamp: July 16, 2023, 07:54:08 pm
content: Quote from: PSR B1257 on July 16, 2023, 07:48:11 amHow about you do the lcd.setCursor() only once outside the loop?Code:lcd.setCursor(0, 1);while(Serial2.available()){char c = Serial2.read();Serial.print(c, HEX);Serial.print(" ");lcd.print(c, HEX);}(Perhaps put a space between the hex values on the LCD too.)

author: ajb
author_rank: Super Contributor
timestamp: July 16, 2023, 08:38:25 pm
content: Quote from: PSR B1257 on July 16, 2023, 07:48:11 amYou're still writing past the end of hexCar_LCD, because sprintf will write two characters for the hex value plus a terminating null. You need to make `hexCar_LCD` at least three bytes, or skip sprintf and use a String constructor directly, like `String(num, HEX)`. Either way, you need to deal with all of those String objects you'd be creating.QuoteAhh, yes, the problem is that `lcd.setCursor` call, since it is called before `lcd.print` the characters are always written in the same spot. I missed that you had corrected that in your later version, my mistake.QuoteI think you will want to take a step back and re-evaluate your approach. Before you can extract any data from a message, you first need to identify a complete and correct message. If you just do this:Code:String LCDcontent ="";if (Serial2.available() > 0){LCDcontent = Serial2.readString();// do things with message here}You are not guaranteed to have a complete message by the time you get to the 'do things' line. That's because `Serial2.available() > 0` will be true as soon as one byte is received. So you will enter the `if` block, and then immediately read whatever is available, which might be one or two bytes. It might be more, depending on how frequently your main loop runs, but it is never guaranteed to be a complete message. If you know that your messages are consistent in length, then you could change the `if` statement to `if( Serial2.available() >= 12 )`, but this will fail if a partial message is received before a complete message, or if a byte is missed from the message. To handle those conditions, as well as variable length messages, things get a bit more complicated. If you're using MODBUS ASCII, then you can look for the starting character ':' to find the beginning of a message. If you're using MODBUS RTU, then you should be looking for the idle period that marks the end of a message. The latter is a bit more complicated, because you need to monitor the line state in a way that is not directly accessible from the Arduino Serial API (afaik), but you could examine the message contents and infer framing that way *.I would recommend that before you worry about displaying the message correctly on the LCD, you focus on ensuring you are receiving and interpreting the message correctly and not just receiving the correct bytes in the correct order. Start by printing the received message back to the serial port only when your device has identified a complete message, and/or printing an error message when it receives a message it can't interpret.There are two basic approaches to doing this. One is to look for the start condition, and once that occurs, start capturing bytes into a buffer. Continue capturing bytes until the end condition occurs. When it does, you should have a valid message that you can process. Here's what that might look like for MODBUS ASCII:Code:#define MSG_LEN_MAX 32 // or whateveruint8_t msgBuf;uint8_t bytesReceived = 0;bool inMsg = false;void processMsg(uint8_t * msg, uint8_t len){// check LRC, send response, etc}// inside the main loop:while( Serial.available() ){uint8_t b = Serial.read();if( b == ':' ){ // check for start charinMsg = true;}else if( b == '\r'){ // check for end charprocessMsg(msgBuf, bytesReceived);bytesReceived = 0;inMsg = false;}else if( inMsg ){ // capture received byte if between start and end char, otherwise ignore it// should also validate that received char is 0-9,a-fmsgBuf[bytesReceived++] = b;}if( bytesReceived > MSG_LEN_MAX ) { // error, buffer would overruninMsg = false;bytesReceived = 0;}}The other option is to continually capture all bytes into a circular buffer, and periodically scan through that buffer to see if it contains any valid messages. This is a bit more complicated, because a message may start anywhere in the buffer and may wrap from the end back around to the beginning, but might be the way to go if you can't monitor for the idle breaks in MODBUS RTU for some reason.---* Since the second byte is the Function Field, which can only be one of a limited number of values, you could scan through the received bytes until you find one that represents a valid function code. When you do, go to the byte before it (which would be the address byte), and begin calculating the CRC from there one byte at a time. If at any point the computed CRC matches the next two bytes in the message, then youprobablyhave a correctly framed message. You can further validate it by checking that the address is valid, and the payload is valid for the given function code. It is possible for the payload of a MODBUS message to itself be a valid MODBUS message, since payloads can be arbitrary, but statistically unlikely (unless you have a system that is proxying MODBUS messages or something). Looking at the actual framing conditions is still a better solution.

author: PSR B1257
author_rank: Frequent Contributor
timestamp: July 18, 2023, 11:53:24 am
content: QuoteAh, o.k. It yet works thou. The LCD displays the 16 HEX-characters without any nulls.QuoteThat would be an programmers-approachI would like to keep it as simple as possible. The expected answer from the device is pretty well predictable and therefore any funny business easily spotted.QuoteYes, I do.So basicly I would need something like thisCode:void processMsg(uint8_t * msg, uint8_t len){// check LRC, send response, etcto get the data from the serial2.read to be stored 'permanent' and perform the little computation?To be specific the HEX-bytes 3 and 4 have to be read from the string, converted into decimal and divided by 10.I.e. 01 04 0403 E8+ whatever CRC --> 100.0