Page 1 of 1

ES32, ESP-IDF and UART

Posted: Tue Oct 08, 2019 12:22 pm
by semarin
Hello there,

I am using ESP-IDF to use UART to send and receive messages with an OBD-ii adapter. OBD-ii is a standard to read out information taken from automotive diagnostics. I am using this adapter. This adapter comes with an Arduino library. I tested it with this library and it works just fine. As I have to use ESP-IDF an Arduino library will not suffice so I translated it to C. I translated the UART-specific Arduino code to UART code of ESP-IDF. I then tested my library but unfortunately it did not work. It got into an infinite loop trying to read trying to process information and the problem appeared to be that it tried to process an empty value so the condition never exits because it has nothing to process. So, the problem was narrowed down to the fact that UART was not receiving any data. I thought this to be weird, as I had the exact same setting as the Arduino library and I followed the ESP-IDF UART guide to the bone. The mentioned Arduino library can be found here.

In examples/obd_uart_test/obd_uart_test.ino you see the following code:

Code: Select all

void setup()
{
  mySerial.begin(115200);
  while (!mySerial);
  
  for (;;) {
    delay(1000);
    byte version = obd.begin();
    mySerial.print("Freematics OBD-II Adapter ");
    if (version > 0) {
      mySerial.println("detected");
      mySerial.print("OBD firmware version ");
      mySerial.print(version / 10);
      mySerial.print('.');
      mySerial.println(version % 10);
      break;
    } else {
      mySerial.println("not detected");
    }
  }
  
  // some more setup code
  }
This code tries to retrieve the version of the OBD-ii adapter and when it does it says the adapter has been detected and prints out the version. But the method that actually gets the version (through UART) is this:

Code: Select all

byte version = obd.begin();
In OBD2UART.cpp the begin method has the following body:

Code: Select all

byte COBD::begin()
{
	long baudrates[] = {115200, 38400};
	byte version = 0;
	for (byte n = 0; n < sizeof(baudrates) / sizeof(baudrates[0]); n++) {
#ifndef ESP32
		OBDUART.begin(baudrates[n]);
#else
		OBDUART.begin(baudrates[n], SERIAL_8N1, 16, 17);
#endif
		version = getVersion();
		if (version != 0) break;
		OBDUART.end();		 
	}
	return version;	
}
OBDUART is a macro referring to Serial. So it says Serial.begin() there.

Code: Select all

OBDUART.begin(baudrates[n], SERIAL_8N1, 16, 17);
For the ESP32 it starts the serial like this. This pins are correct and corresponding to my rx (17) and tx (16) pins.

Code: Select all

version = getVersion();
Then it tries to get the version by running the getVersion method, the method is as follows:

Code: Select all

byte COBD::getVersion()
{
	byte version = 0;
	for (byte n = 0; n < 3; n++) {
		char buffer[32];
		if (sendCommand("ATI\r", buffer, sizeof(buffer), 200)) {
			char *p = strchr(buffer, ' ');
			if (p) {
				p += 2;
				version = (*p - '0') * 10 + (*(p + 2) - '0');
				break;
			}
		}
	}
	return version;
}
Here we see it uses the sendCommand function and that it sends "ATI\r" to the adapter. We go to the sendCommand function:

Code: Select all

byte COBD::sendCommand(const char* cmd, char* buf, byte bufsize, int timeout)
{
	write(cmd);
	idleTasks();
	return receive(buf, bufsize, timeout);
}
The write function just encapsulates Serial.write(); I do not know what idleTasks() is per say and I could not easily find it one the internet. After it writes the data using Serial.write(); it goes into the receive function which reads incoming data which the adapter should give now.

Code: Select all

int COBD::receive(char* buffer, int bufsize, unsigned int timeout)
{
	unsigned char n = 0;
	unsigned long startTime = millis();
	char c = 0;
	for (;;) {
		if (OBDUART.available()) {
			c = OBDUART.read();
			if (!buffer) {
			       n++;
			} else if (n < bufsize - 1) {
				if (c == '.' && n > 2 && buffer[n - 1] == '.' && buffer[n - 2] == '.') {
					// waiting siginal
					n = 0;
					timeout = OBD_TIMEOUT_LONG;
				} else {
					if (c == '\r' || c == '\n' || c == ' ') {
						if (n == 0 || buffer[n - 1] == '\r' || buffer[n - 1] == '\n') continue;
					}
					buffer[n++] = c;
				}
			}
		} else {
			if (c == '>') {
				// prompt char received
				break;
			}
			if ((int)(millis() - startTime) > timeout) {
			    // timeout
			    break;
			}
			idleTasks();
		}
	}
	if (buffer) {
		buffer[n] = 0;
	}
#ifdef DEBUG
	DEBUG.print(">>>");
	DEBUG.println(buffer);
#endif
	return n;
}
The data is put into the buffer that is supplied by the sendCommand method to the receive method. It then returns a value toe the sendCommand which does not represent the data per say. So, now we go back to the getVersion method:

Code: Select all

byte COBD::getVersion()
{
	byte version = 0;
	for (byte n = 0; n < 3; n++) {
		char buffer[32];
		if (sendCommand("ATI\r", buffer, sizeof(buffer), 200)) {
			char *p = strchr(buffer, ' ');
			if (p) {
				p += 2;
				version = (*p - '0') * 10 + (*(p + 2) - '0');
				break;
			}
		}
	}
	return version;
}
In char buffer[32] now should be the retrieved data after sending the ATI command to the adapter and it processes this information and outputs it into the console. Now I explained this sequence and I want to implement the same thing in ESP-IDF but I could not get it to work.

Things to keep in mind
  • I tried using the Arduino-esp32 codebase as a component to ESP-IDF, however, in my whole application I will also be using ESP-ADF together with ESP-IDF and the code from the ADF lacks compatibility with the Arduino library. I get errors because it refuses the C code ADF uses. Yes, I use extern c but it does not accept the code. One way I might have that fixed is to make everything extern to be used in cpp but I do not want to touch something I may broke so I will not do so.
  • I will be setting pins with uart_set_pin, this is in another order than the Serial begin version, like rx first and tx after and the other way around.
  • I will not be using a send buffer, I know this might block application but it is not neccesary for what I am trying to do, this may all be fixed later.
  • I will not set the uart_mode assuming it will use the default, and if I do set it I will set if to UART_MODE. I tried with and without setting it with both the same result.
  • Hardware control will be disabled, I do not need it.
  • Serial configuration will be set according to SERIAL_8N1, which is 115200, 8 bit, no parity and 1 stop bit as the code in the Arduino library.
  • I will be using UART port 2 as pin 16 and 17 seem to be used there according to HardwareSerial.cpp of arduino-esp32.
  • I did not confuse the two pins I tried both ways of connecting.
Now for the ESP-IDF project

I starting a new ESP-IDF project based on the "hello world" example because that is the most basic one. I emptied the app_main() and added one by one the following:

Code: Select all

	uart_config_t uart_config = { 
		.baud_rate = 115200,
		.data_bits = UART_DATA_8_BITS,
		.parity = UART_PARITY_DISABLE,
		.stop_bits = UART_STOP_BITS_1,
		.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
	};
	
Here I specify the configuration for UART as described.

Code: Select all

	uart_param_config(UART_NUM_2, &uart_config);
	uart_set_pin(UART_NUM_2, 17, 16, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
	uart_driver_install(UART_NUM_2, 2048, 0, 0, NULL, 0);
	
Here I set the specified UART configuration to uart port 2, and set the corresponding pins. See uart_set_pin() here. As the default pins are 17 and 16 I could have just used UART_PIN_NO_CHANGE and I did with the same result. I then install the UART driver. I do not use a Queue, which is used for events and do not set any flags. That was the UART configuration part. Now for the communication.

Code: Select all

	while (1)
	{
		uart_write_bytes(UART_NUM_2, "ATI\r", 32);
		// Read data from the UART
		int len = uart_read_bytes(UART_NUM_2, data, 1, 20 / portTICK_PERIOD_MS);
		printf("Len is empty\n");
		// Write data back to the UART
		if(len > 0)
		{
			data[len] = "\0";
			printf("There is data \n");
			printf("%s \n", data);
			fflush(stdout);
		}
	}
Here it writes the ATI command to the adapter and then reads data given by the adapter continously. However, it never gets in the if (len > 0) condition.

Do you by chance see any errors I made? I thank you in advance.

Re: ES32, ESP-IDF and UART

Posted: Wed Dec 11, 2019 7:19 am
by Edgar1
I read only part of your post and maybe what I will write has nothing to do with what you want. But maybe …

I used an OBD adapter on my KTM motorcycle. The data was sent per Bluetooth from the OBD adapter to my mobile phone. On the phone I used Torque App to see the data.

And then I wanted to see more than just the OBD data and I learned about the CAN-Bus. The OBD adapter is plugged into the CAN-Bus.

I used first and Arduino and later an ESP32 (with ESP-IDF) code to read the information from the CAN-Bus. This included information which was part of OBD but it showed also more information.

In my case I can see i.e. the engine RPM in the Torque App via OBD.
I can see the same RPM value also directly on the CAN-Bus (with no OBD connected)
On the CAN-Bus I also see i.e. the RPM on each wheel of my bike and the brake pressure. I guess this data is used for the ABS system of the bike. But that data is not available per OBD. So the CAN-Bus data contains more information than you will see with OBD.

What OBD does which is not part of the CAN-Bus is the diagnostic part. If you are interested in that then I guess using OBD makes sense. But if you are "only" interested if current data from your vehicle then maybe you want to look at the data on the CAN-Bus directly.

The Arduino is too slow, at least on the 500k CAN-Bus of my bike, to receive and process all data. This is why I started with the ESP32 and ESP-IDF because that is fast enough.

If you are interested have a look this article: https://en.wikipedia.org/wiki/CAN_bus
And this: https://docs.espressif.com/projects/esp ... s/can.html

I can give you some more info if you are interested in this. But maybe you want to use OBD and not the CAN-Bus.
Let me know if you want more info.