Embedded

How to print 32-bit uint32_t as eight hex digits in Arduino

When using Arduino, you often want to print the hex value of a 32-bit value such as a uint32_t, consisting of eight hex digits. For example, if you have uint32_t val = 9177025;, you intend to print 008C07C1.

In Arduino you can do that using Serial.printf() with %08lx as format specifier. Furthermore, you typically want to invert the byte order of the uint32_t using __builtin_bswap32()since it’s more inuitive to write the hex value MSB-first (big-endian) while most hardware platforms represent the uint32_t as LSB-first (little-endian):

Serial.printf("val = %08lx\r\n", __builtin_bswap32(val));

When using printf, %x means to format the value as hex whereas l tells printf() that the argument is a long (32 bit) as opposed to an int (16 bit). 08 means to pad the value with 0s up to a length of 8 digits. If you would format 9177025 using just %x, it would print 008C07C1  instead of 008C07C1. This is why you need to use %08lx instead.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

How to print 16-bit uint16_t as four hex digits in Arduino

When using Arduino, you often want to print the hex value of a 16-bit value such as a uint16_t, consisting of four hex digits. For example, if you have uint16_t val = 2022;, you intend to print 07E6.

In Arduino you can do that using Serial.printf() with %04x as format specifier.

Furthermore, you typically want to invert the byte order of the uint16_t using __builtin_bswap16()since it’s more inuitive to write the hex value MSB-first (big-endian) while most hardware platforms represent the uint16_t as LSB-first (little-endian):

Serial.printf("val = %04x\r\n", __builtin_bswap16(val));

When using printf, %x means to format the value as hex. 04 means to pad the value with 0s up to a length of 4 digits. If you would format 2022 using just %x, it would print 7E6  instead of 07E5. This is why you need to use %04x instead.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

How to print byte as two hex digits in Arduino

In embedded programming, you often want to print the hex value of a byte, consisting of two hex digits. For example, if you have uint8_t val = 14;, you intend to print 0x0E.

In Arduino you can do that using Serial.printf() with %02x as format specifier:

Serial.printf("val = %02x\r\n", val);

When using printf, %x means to format the value as hex. 02 means to pad the value with 0s up to a length of 2 digits. If you would format 14 using just %x, it would print E  instead of 0E. This is why you need to use %02x.

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

Arduino I2C: Wire.endTransmission() before or after Wire.requestFrom() ?

The correct order to call Wire commands in Arduino is:

Wire.beginTransmission(MY_I2C_ADDR);
Wire.write(addr);
Wire.endTransmission();
Wire.requestFrom(MY_I2C_ADDR, 1); // Request one byte
delay(5); // Wait for data to be available
uint8_t value = Wire.read();

So you call Wire.endTransmission() after Wire.write() and call Wire.requestFrom() directly after Wire.endTransmission()

Posted by Uli Köhler in Arduino, Electronics, Embedded, PlatformIO

What is the LAN9303 I2C slave address?

I experimentally determined that the LAN9303 uses the I2C slave address 0b0001010. This is listed in the datasheet in section 8.5.1.

Posted by Uli Köhler in Electronics, Embedded, Networking

Teensy 4.x Quadrature Encoder minimal example in PlatformIO/Arduino

This example uses PlatformIO and the Teensy-4.x-Quad-Encoder-Library to implement a hardware quadrature encoder on pins 0 and 1. Remember that the teensy will be destroyed if you use 5V encoder signals. You can only use 3.3V signals!

#include <Arduino.h>
#include <QuadEncoder.h>

QuadEncoder encoder(1, 0, 1, 0);

void setup() {
    Serial.begin(115200);
    encoder.setInitConfig();
    encoder.init();
}

void loop() {
    int32_t position = encoder.read();
    // Compute position in mm and print it
    constexpr float mm_per_count = 0.0012;
    float mm = position * mm_per_count;
    Serial.printf("Position: %+3.4f (count %ld)\r\n", mm, position);
    // Print every 50ms
    delay(50);
}
[env:teensy41]
platform = teensy
board = teensy41
framework = arduino
monitor_speed = 115200
lib_deps =
    git+https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library

 

Posted by Uli Köhler in C/C++, Electronics, Embedded, PlatformIO, Teensy

How to add GitHub repository to PlatformIO lib_deps

Use git+https like this:

lib_deps =
    git+https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library

 

Posted by Uli Köhler in Electronics, Embedded, PlatformIO

Which pins on the Teensy 4.1 are 5V-tolerant?

None of the Teensy 4.1 IO pins are 5V tolerant. You can not connect any 5V signals to any of those pins, doing so will severely damage the hardware. You always need to use some kind of level shifter when connecting 5V signals to the Teensy.

Posted by Uli Köhler in Electronics, Teensy

How to set pin speed/alternate function in STM32 Arduino (PlatformIO)

You can use the STM32 HAL (STM32CubeMX) even when using Arduino as a framework for STM32 boards in PlatformIO:

GPIO_InitTypeDef pinInit = {
  .Pin = GPIO_PIN_8,
  .Mode = GPIO_MODE_AF_PP,
  .Pull = GPIO_NOPULL,
  .Speed = GPIO_SPEED_FREQ_VERY_HIGH,
  .Alternate = GPIO_AF1_TIM1
};
HAL_GPIO_Init(GPIOA, &pinInit);

Use

#include <stm32f4xx_hal_gpio.h>

in order to include the HAL functions.

Posted by Uli Köhler in Arduino, Electronics, PlatformIO, STM32

How to use HAL_GPIO_Init() in modern C++ (STM32)

In modern C++, you can directly initialize structs like a GPIO_InitTypeDef, making the code much prettier and less prone to errors. The following example configures PA8 of a STM32 in alternate function 1 mode (TIM1 output).

GPIO_InitTypeDef pinInit = {
  .Pin = GPIO_PIN_8,
  .Mode = GPIO_MODE_AF_PP,
  .Pull = GPIO_NOPULL,
  .Speed = GPIO_SPEED_FREQ_VERY_HIGH,
  .Alternate = GPIO_AF1_TIM1
};
HAL_GPIO_Init(GPIOA, &pinInit);

As opposed to the old, much more verbose way of doing that:

GPIO_InitTypeDef pinInit;
pinInit.Pin = GPIO_PIN_8;
pinInit.Mode = GPIO_MODE_AF_PP;
pinInit.Pull = GPIO_NOPULL;
pinInit.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
pinInit.Alternate = GPIO_AF1_TIM1;
HAL_GPIO_Init(GPIOA, &pinInit);

 

 

Posted by Uli Köhler in C/C++, Electronics, STM32

SN74LV245A as level shifter: How to use the ~OE pin

The ~OE pin on the SN74LV245A defines whether to enable the outputs on the chip. Which pins are inputs and which are outputs is defined by the DIR pins, see SN74LV245A as level shifter: How to use DIR pin for more information.

  • If ~OE is low (i.e. connected to GND), all output pins are enabled
  • If ~OE is high (i.e. connected to Vcc), all output pins are disabled

Note that there is no way to disable individual output pins for the SN74LV245A. You can only enable or disable all the pins together.

In most applications as a level shifter, you want to always enable the output: In order to do that, connect the ~OE pin to GND.

Posted by Uli Köhler in Electronics, Embedded

SN74LV245A as level shifter: How to use the DIR pin

The DIR pin on the SN74LV245A defines whether the A pins or the B pins are the input pins.

  • If DIR is low (i.e. connected to GND), all B pins are inputs and all A pins are outputs.
  • If DIR is high (i.e. connected to Vcc), all A pins are inputs and all B pins are outputs.
Posted by Uli Köhler in Electronics, Embedded

How I fixed tuya-convert Nous A1 SmartConfig complete. Resending SmartConfig Packets loop

Problem:

While trying to flash a device using tuya-convert, you see an error message like

======================================================
Starting smart config pairing procedure
Waiting for the device to install the intermediate firmware
Put device in EZ config mode (blinking fast)
Sending SSID                  vtrust-flash
Sending wifiPassword          
Sending token                 00000000
Sending secret                0101
................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
.................
SmartConfig complete.
Resending SmartConfig Packets
................
SmartConfig complete.
Resending SmartConfig Packets
..............
Timed out while waiting for the device to (re)connect

and the flash procedure fails.

Solution:

You can see the actual error log in scripts/smarthack-psk.log. In my case the error was the one described in our previous post How I fixed tuya-convert Nous A1 could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher

The current version of ./install_reqs.sh does not install the sslpsk module correctly (the error message is somewhat hidden at the top of the log). Install it manually using

sudo -H python3 -m pip install --upgrade sslpsk

 

Posted by Uli Köhler in Electronics, Embedded

How I fixed tuya-convert Nous A1 could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher

Problem:

While trying to flash a device using tuya-convert, you see an error message like

Traceback (most recent call last):
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 6, in <module>
    import sslpsk
ModuleNotFoundError: No module named 'sslpsk'
Traceback (most recent call last):
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 6, in <module>
    import sslpsk
ModuleNotFoundError: No module named 'sslpsk'
new client on port 443 from 10.42.42.10:49144
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49150
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49154
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49156
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49160
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49162
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49164
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49172
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49174
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49176
could not establish sslpsk socket: [SSL: NO_SHARED_CIPHER] no shared cipher (_ssl.c:1123)
don't panic this is probably just your phone!
new client on port 443 from 10.42.42.10:49178
could not establish sslpsk socket: EOF occurred in violation of protocol (_ssl.c:1123)
Traceback (most recent call last):
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 61, in new_client
    ssl_sock = sslpsk.wrap_socket(s1,
  File "/usr/local/lib/python3.9/dist-packages/sslpsk/sslpsk.py", line 110, in wrap_socket
    sock.do_handshake()
  File "/usr/lib/python3.9/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:1123)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 113, in <module>
    main()
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 109, in main
    p.data_ready_cb(s)
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 80, in data_ready_cb
    self.new_client(_s)
  File "/home/pi/tuya-convert/scripts/./psk-frontend.py", line 72, in new_client
    if "NO_SHARED_CIPHER" in e.reason or "WRONG_VERSION_NUMBER" in e.reason or "WRONG_SSL_VERSION" in e.reason:
TypeError: argument of type 'NoneType' is not iterable

and the flash procedure fails.

Solution:

The current version of ./install_reqs.sh does not install the sslpsk module correctly (the error message is somewhat hidden at the top of the log). Install it manually using

sudo -H python3 -m pip install --upgrade sslpsk

 

Posted by Uli Köhler in Electronics, Embedded

How to fix tuya-convert ModuleNotFoundError: No module named ‘Cryptodome’

Problem:

While trying to flash a device using tuya-convert, you see an error message like

======================================================
Starting smart config pairing procedure
Waiting for the device to install the intermediate firmware
Traceback (most recent call last):
  File "/home/pi/tuya-convert/scripts/./smartconfig/main.py", line 15, in <module>
    from smartconfig import smartconfig
  File "/home/pi/tuya-convert/scripts/smartconfig/smartconfig.py", line 43, in <module>
    from multicast import multicast_head, encode_multicast_body
  File "/home/pi/tuya-convert/scripts/smartconfig/multicast.py", line 12, in <module>
    from Cryptodome.Cipher import AES
ModuleNotFoundError: No module named 'Cryptodome'
.........................................

and the flash procedure fails.

Solution:

The current version of ./install_reqs.sh does not install the pycryptodome module. Install it manually using

sudo -H python3 -m pip install --upgrade pycryptodome

 

Posted by Uli Köhler in Electronics, Embedded

How to pass firewall using PlatformIO espota ArduinOTA upload

ArduinoOTA’s protocol tries to connect to the host which is trying to program the device on a randomly chosen port. This often leads to the packets being filtered in a firewall since no rule exists to pass the packet and they are not related to an existing connection.

You could create a firewall rule to pass all traffic from the ESP8266/ESP32 to the programming host, but that is extremely insecure since it allows a hacked IoT device to hack your devices.

In order to fix it, add a fixed host port in platformio.ini using

upload_flags = --host_port=55910

and add these firewall rules:

allow from <programming host> to <IoT device> port 55910 TCP
allow from <IoT device> to <programming host> port 55190 TCP

Complete platformio.ini example:

[env:d1_mini_ota]
extends = env:d1_mini
upload_protocol = espota
upload_port = 192.168.178.25
upload_flags = --host_port=55910

 

Posted by Uli Köhler in Embedded, ESP8266/ESP32, PlatformIO

How to use FlexPWM NanoEdge Placer on the Teensy 4

You can not use the NanoEdge placer block on the Teensy 4.0 / Teensy 4.1 because the NanoEdge placer hardware unit is not built into the i.MX RT1062 processor the Teensy uses.

Source: Teensy Forums

Posted by Uli Köhler in Electronics, Teensy

How to use ESP32 as USB-to-UART converter in PlatformIO

The ESP32 can easily be used as USB-to-UART converter. Note that the ESP32 does not feature an USB interface by itself and ESP32 boards with an onboard USB connector just use an USB-to-UART converter (this is a separate chip on the board). Hence, from the ESP32 perspective, it

You can map UART TX and RX to any GPIO pin on the ESP32. While there are very, very slight performance advantages to using a set of predefined pins, this does not really matter in practice. In this example, we will use pin GPIO2 for UART RX and pin GPIO4 for UART TX.

Basically, the code is just copying bytes between Serial2 and Serial (connected to USB):

// Copy bytes incoming via PC serial
while (Serial.available() > 0) {
  Serial2.write(Serial.read());
}
// Copy bytes incoming via UART serial
while (Serial2.available() > 0) {
  Serial.write(Serial2.read());
}

Full example

#include <Arduino.h>

#define UART_RX_PIN 2 // GPIO2
#define UART_TX_PIN 4 // GPIO4

void setup() {
  // Serial connects to the computer
  Serial.begin(115200);
  // Serial2 is the hardware UART port that connects to external circuitry
  Serial2.begin(115200, SERIAL_8N1,
                UART_RX_PIN,
                UART_TX_PIN);
}

void loop() {
  // Copy byte incoming via PC serial
  while (Serial.available() > 0) {
    Serial2.write(Serial.read());
  }
  // Copy bytes incoming via UART serial
  while (Serial2.available() > 0) {
    Serial.write(Serial2.read());
  }
}

Regarding platformio.ini, we just need to set the monitor_speed to match the value in Serial.begin(115200);:

[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200

 

Posted by Uli Köhler in C/C++, Electronics, ESP8266/ESP32, PlatformIO

ESPAsyncWebserver: How to run code in main loop thread

When working with ESPAsyncWebserver, you will soon encounter one of its most fundamental limitations: Its code will always run in interrupts, hence you can’t use delay() or any function that uses delay() or similar functions internally.

However, there’s a simple programming pattern that will enable you to execute code in the main thread if requested by the webserver. Note: In this example, we will only cover how to activate code in the main loop from the webserver, but the response will be sent before the code in the main loop is run – hence, the response is just {"status": "ok"} and it doesn’t depend on whatever is happening on the main loop.

The idea is to have a global flag:

bool request_serial_output = false;

which is set in the webserver handler code, whereas the main loop checks the flag repeatedly:

void loop() {
  if(request_serial_output) {
    request_serial_output = false; // Clear flag
    // TODO Add handling code here
  }
}

Full example

This example uses PlatformIO and should work on any ESP32 board.

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>

AsyncWebServer server(80);

bool request_serial_output = false;

void setup() { 
  Serial.begin(115200);
  // Connect Wifi, restart if not connecting
  // https://techoverflow.net/2021/01/21/how-to-fix-esp32-not-connecting-to-the-wifi-network/
  WiFi.begin("MyWifi", "mywifipassword");
  uint32_t notConnectedCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
      delay(100);
      Serial.println("Wifi connecting...");
      notConnectedCounter++;
      if(notConnectedCounter > 50) { // Reset board if not connected after 5s
          Serial.println("Resetting due to Wifi not connecting...");
          ESP.restart();
      }
  }
  Serial.print("Wifi connected, IP address: ");
  Serial.println(WiFi.localIP());

  // Initialize webserver URLs
  server.on("/api/test", HTTP_GET, [](AsyncWebServerRequest *request) {
      // Send request to main loop
      request_serial_output = true;
      // Send JSON "ok" response.
      // NOTE: The response will be sent BEFORE the main loop has finished
      // processing the request
      AsyncResponseStream *response = request->beginResponseStream("application/json");
      DynamicJsonDocument json(1024);
      json["status"] = "ok";
      serializeJson(json, *response);
      request->send(response);
  });

  // Start webserver
  server.begin();
}

void loop() {
  if(request_serial_output) {
    request_serial_output = false; // Clear flag
    // Output serial
    Serial.println("First message. Now waiting one second...");
    // Wait 1s. You can't do that in the webserver handler!
    delay(1000);
    // Output another message on Serial
    Serial.println("Second message!");
  }
}
[env:nodemcu-32s]
platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200
lib_deps =
    ESP Async [email protected]
    [email protected]

 

Posted by Uli Köhler in ESP8266/ESP32, PlatformIO

How to enter serial bootloader on the ESP32

In order to enter the serial bootloader on the ESP32, connect both GPIO0 and GPIO2 to GND while the ESP32 is starting up.

A typical hardware sequence to enter the bootloader is:

  1. Connect GPIO0 and GPIO2 to GND
  2. Connect the EN or RST pin to GND for at least 100ms, then connect to 3.3V

The second step will restart the ESP32.

Posted by Uli Köhler in ESP8266/ESP32