Download the schematic PDF for the ESP-WROOM-32 here.
Embedded
What is the default PlatformIO / Arduino ESP32 TIMER_BASE_CLK?
On PlatformIO / Arduino, by default the TIMER_BASE_CLK
is 80 MHz
(the maximum frequency the ESP32 can run at).
If you want to verify this yourself, use this firmware:
#include <Arduino.h> void setup() { Serial.begin(115200); Serial.println(getCpuFrequencyMhz()); } void loop() { }
What is the default PlatformIO / Arduino ESP32 CPU clock frequency?
On PlatformIO / Arduino, by default the ESP32 clock frequency is 80 MHz
(the default 240 MHz
CPU frequency divided by 4
)
If you want to verify this yourself, use this firmware:
#include <Arduino.h> void setup() { Serial.begin(115200); Serial.println(TIMER_BASE_CLK); } void loop() { }
How to fix PlatformIO serial monitor scrambled output
Problem:
When using the Monitor
function of platformIO, you see strange characters instead of strings being printed, for example:
)� �␜ܠ��J��1��1!y��!���!��
Solution:
This issue almost always appears due to the Monitor
function using the wrong UART speed. You can see from the log in our screenshot above:
--- Miniterm on /dev/ttyUSB0 9600,8,N,1 ---
that PlatformIO is using 9600
baud in this case – but your microcontroller is sending data at a faster speed (or, rarely at a slower speed).
Most firmwares using serial IO use 115200
baud, so that’s what I’d recommend to try first, but if that doesn’t work, look out for config options named baud rate or similar, or for lines of code like
Serial.begin(57600);
in the firmware.
In order to change the Monitor UART speed, open platformio.ini
and add
monitor_speed = 115200
Full platformio.ini
example for ESP32:
[env:esp32dev] platform = espressif32 board = esp32dev framework = arduino monitor_speed = 115200
After that, restart the Monitor function.
How to fix ESP32 PlatformIO error: ‘LED_BUILTIN’ was not declared in this scope
Problem:
When trying to compile your ESP32 firmware using PlatformIO, you see this error message:
src/main.cpp: In function 'void setup()': src/main.cpp:22:13: error: 'LED_BUILTIN' was not declared in this scope pinMode(LED_BUILTIN, OUTPUT);
Solution:
Important: Some ESP32 boards such as the ESP32-DevKitC have no builtin LED at all ! Either connect an external LED or find another method of doing whatever you are intending to do.
On most ESP32 boards that do have a builtin LED, the LED is connected to pin 2
– however, PlatformIO does not define LED_BUILTIN
p. In order to fix the issue, define LED_BUILTIN
yourself by using
#define LED_BUILTIN 2
at the top of every file where you see this error.
How to fix Linux ESP32 PlatformIO PermissionError: [Errno 13] Permission denied: ‘/dev/ttyUSB0’
Problem:
When trying to flash a ESP8266 or ESP32 board on Linux, you see an error message like
Warning! Please install `99-platformio-udev.rules`. More details: https://docs.platformio.org/page/faq.html#platformio-udev-rules Auto-detected: /dev/ttyUSB0 Uploading .pio/build/esp32dev/firmware.bin esptool.py v3.1 Serial port /dev/ttyUSB0 Traceback (most recent call last): File "/home/uli/.platformio/penv/lib/python3.8/site-packages/serial/serialposix.py", line 322, in open self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) PermissionError: [Errno 13] Permission denied: '/dev/ttyUSB0' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/uli/.platformio/packages/tool-esptoolpy/esptool.py", line 4582, in <module> _main() File "/home/uli/.platformio/packages/tool-esptoolpy/esptool.py", line 4575, in _main main() File "/home/uli/.platformio/packages/tool-esptoolpy/esptool.py", line 4074, in main esp = esp or get_default_connected_device(ser_list, port=args.port, connect_attempts=args.connect_attempts, File "/home/uli/.platformio/packages/tool-esptoolpy/esptool.py", line 120, in get_default_connected_device _esp = chip_class(each_port, initial_baud, trace) File "/home/uli/.platformio/packages/tool-esptoolpy/esptool.py", line 313, in __init__ self._port = serial.serial_for_url(port) File "/home/uli/.platformio/penv/lib/python3.8/site-packages/serial/__init__.py", line 90, in serial_for_url instance.open() File "/home/uli/.platformio/penv/lib/python3.8/site-packages/serial/serialposix.py", line 325, in open raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg)) serial.serialutil.SerialException: [Errno 13] could not open port /dev/ttyUSB0: [Errno 13] Permission denied: '/dev/ttyUSB0'
Solution:
As described at the top of the error message, install the PlatformIO udev rules:
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/master/scripts/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules
then restart udev
:
sudo systemctl restart udev
After that, unplug an re-plug your ESP32 board in order for the changes to take effect
Additionally, I recommend adding your user to the dialout group – the Linux group that owns most USB devices:
sudo usermod -a -G dialout $USER
In order for the changes to take effect, log out from your current session completely and log back in again (or reboot). While not strictly neccessary in order to fix this specific error message, it helps in preventing future USB permission issues.
How to implement 1MHz interrupt in PlatformIO / Arduino on STM32
In our previous post Minimal STM32 HardwareTimer PlatformIO / Arduino timer interrupt blink example we showed how to use HardwareTimer
to blink the onboard LED of our STM32F407 board using a timer interrupt.
In this post, we’ll provide an example of how to use HardwareTimer
and have a really fast interrupt which runs at 1 MHz – in other words: one million times per second.
#include <Arduino.h> HardwareTimer timer(TIM1); bool ledOn = false; void OnTimer1Interrupt() { ledOn = !ledOn; digitalWrite(PC13, ledOn ? HIGH : LOW); } void setup() { pinMode(PC13, OUTPUT); // Configure timer timer.setPrescaleFactor(21); // Set prescaler to 21 => timer frequency = 168/21 = 8 MHz (from prediv'd by 1 clocksource of 168 MHz) timer.setOverflow(8); // Set ARR to 8 => timer frequency = 1 MHz timer.attachInterrupt(OnTimer1Interrupt); timer.refresh(); // Make register changes take effect timer.resume(); // Start timre } void loop() { }
Note that when running such a quick interrupt, you can’t do all too much within the interrupt before the next time the interrupt will run.
Minimal STM32 HardwareTimer PlatformIO / Arduino timer interrupt blink example
This is a minimal example of using timer interrupts on PlatformIO / Arduino using HardwareTimer (which is a part of the PlatformIO STM32 Arduino installation – no need to install a library). Tested on the Olimex E407 board. It will run on almost any STM32 processor but you might need to adjust PC13
to the PIN connected to the LED. The example will blink the LED once per second.
#include <Arduino.h> HardwareTimer timer(TIM1); bool ledOn = false; void OnTimer1Interrupt() { ledOn = !ledOn; digitalWrite(PC13, ledOn ? HIGH : LOW); } void setup() { pinMode(PC13, OUTPUT); // Configure timer timer.setPrescaleFactor(2564); // Set prescaler to 2564 => timer frequency = 168MHz/2564 = 65522 Hz (from prediv'd by 1 clocksource of 168 MHz) timer.setOverflow(32761); // Set overflow to 32761 => timer frequency = 65522 Hz / 32761 = 2 Hz timer.attachInterrupt(OnTimer1Interrupt); timer.refresh(); // Make register changes take effect timer.resume(); // Start } void loop() { }
How to use STM32 _Msk and _Pos definitions to read and write registers
The STM32 HAL contains definitions like TIM_CR1_CKD_Msk
or TIM_CR1_CKD_Pos
which you can use to make it easier to read or write parts of a register.
Reading a part of a register
uint32_t ckd = (TIM1->CR1 & TIM_CR1_CKD_Msk) >> TIM_CR1_CKD_Pos;
Writing a part of a register
uint32_t new_ckd_value = TIM_CLOCKDIVISION_DIV4; // example TIM1->CR1 &= TIM_CR1_CKD_Msk; // Clear bits TIM1->CR1 |= new_ckd_value << TIM_CR1_CKD_Pos; // Set bits
How to fix PlatformIO “Start debugging” doing nothing
Problem:
When you click on Start debugging
, press F5
or click on the debug start triangle in PlatformIO
the firmware builds but then nothing happens:
Linking .pio/build/olimex_e407/firmware.elf Checking size .pio/build/olimex_e407/firmware.elf Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" RAM: [ ] 0.7% (used 936 bytes from 131072 bytes) Flash: [ ] 1.7% (used 18064 bytes from 1048576 bytes) Building .pio/build/olimex_e407/firmware.bin ======================================================================== [SUCCESS] Took 2.72 seconds ========================================================================
Solution:
You need to specify a debug_tool
in platformio.ini
. For STM32 processors, a typical choice is
debug_tool = stlink
A list of options for debug_tool
is available here.
Note that you can NOT debug many boards via the USB port. You can not debug boards attached via serial-to-USB converter (like many Arduino boards). Your board need to have a proper debugger on-board (like an stlink which is integrated on many STM32 eval boards) or you need to use an external debugger.
Minimal STM32 TimerInterrupt_Generic PlatformIO / Arduino timer interrupt blink example
Note: If you need more flexibility, see Minimal STM32 HardwareTimer PlatformIO / Arduino timer interrupt blink example where we show how to use HardwareTimer
instead of TimerInterrupt_Generic
. Note that TimerInterrupt_Generic
uses HardwareTimer
internally.
This is a minimal example of using timer interrupts on PlatformIO / Arduino using the TimerInterrupt_Generic library which runs on the Olimex E407. It will run on almost any STM32 processor but you might need to adjust PC13
to the PIN connected to the LED. The example will blink the LED once per second.
#include <Arduino.h> #include <TimerInterrupt_Generic.h> STM32Timer tim1(TIM1); bool ledOn = false; void OnTimer1Interrupt() { ledOn = !ledOn; digitalWrite(PC13, ledOn ? LOW : HIGH); } void setup() { pinMode(PC13, OUTPUT); // Enable TIM4 tim1.attachInterruptInterval(500000, OnTimer1Interrupt); } void loop() { }
Now add
lib_deps = khoih.prog/TimerInterrupt_Generic @ ^1.7.0
to your platformio.ini
. My full platformio.ini
looks like this:
[env:olimex_e407] platform = ststm32 board = olimex_e407 framework = arduino lib_deps = khoih.prog/TimerInterrupt_Generic @ ^1.7.0
PlatformIO Olimex E407 Arduino LED blink example
This code will make the Olimex E407 LED blink.
#include <Arduino.h> void setup() { pinMode(PC13, OUTPUT); } void loop() { digitalWrite(PC13, LOW); delay(500); digitalWrite(PC13, HIGH); delay(500); }
How to fix PlatformIO Olimex E407 LED_BUILTIN not working
Problem
You are trying to run a firmware on the Olimex E407 that blinks the builtin green status LED. You code uses LED_BUILTIN
similar to this:
#include <Arduino.h> void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, LOW); delay(500); digitalWrite(LED_BUILTIN, HIGH); delay(500); }
but when you upload the code onto the board, the LED does not blink and stays off.
Solution
Instead of LED_BUILTIN
, use PC13
– the pin the LED is connected to (which you can see on the Olimex E407 schematic:
#include <Arduino.h> void setup() { pinMode(PC13, OUTPUT); } void loop() { digitalWrite(PC13, LOW); delay(500); digitalWrite(PC13, HIGH); delay(500); }
How to fix PlatformIO STM32 Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
Problem:
While trying to program your STM32 board using stlink and PlatformIO (most programmers integrated onto a development board are STLink programmers), you see this error message:
xPack OpenOCD, x86_64 Open On-Chip Debugger 0.11.0-00155-ge392e485e (2021-03-15-16:43) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html debug_level: 1 hla_swd Error: libusb_open() failed with LIBUSB_ERROR_ACCESS Error: open failed in procedure 'program' ** OpenOCD init failed ** shutdown command invoked
Solution:
You need to setup the correct permissions for the STLink usb devices – in other words, install the correct stlink
udev rules files. On Ubuntu, install stlink-tools
using
sudo apt -y install stlink-tools sudo systemctl restart udev
After that, unplug your stlink (or development board) for 5 seconds and plugin it in again. This will cause the new device permissions to take effect.
Now you can retry uploading the firmware from PlatformIO.
How to rotate Raspberry Pi LCD by 180°
In order to rotate a DSI-connected LCD screen like the Raspberry Pi 7″ LCD screen by 180 degrees, append
lcd_rotate=2
to /boot/config.txt
PySerial minimal request-reply example
This example sends the M119
(print endstop status) command to an attached 3D printer and prints the response in a loop
#!/usr/bin/env python3 import serial ser = serial.Serial("/dev/ttyACM0") try: while True: ser.write(b"M119\n") response = ser.read_until(b"ok\n") print(response.decode("utf-8")) finally: ser.close()
Example output (in a loop):
Reporting endstop status x_max: open y_max: TRIGGERED z_max: TRIGGERED ok
How to set Raspberry Pi 4 USB-C to host mode
By default, the Raspberry Pi 4 USB-C port is not set to Host mode and therefore it’s not possible.
In order to set the USB-C port to host mode, add the following line to the end of /boot/config.txt
(in the [all]
section):
dtoverlay=dwc2,dr_mode=host
and
reboot
What is the difference between BIGTREE_OCTOPUS_V1 and BIGTREE_OCTOPUS_V1_USB
When building Marlin firmware for the BigTreeTech Octopus from the official BigTreeTech GitHub repository Marlin directory, you can see two different targets in PlatformIO:
- BIGTREE_OCTOPUS_V1
- BIGTREE_OCTOPUS_V1_USB
It is not immediately made clear what the difference between those is, but a short description can be found in ini/stm32f4.ini
:
BIGTREE_OCTOPUS_V1_USB
has support for using USB flashdrives directly on the board and serial-over-USB while BIGTREE_OCTOPUS_V1
has not.
BIGTREE_OCTOPUS_V1_USB
if printing via USB (e.g. via Octoprint) because the BIGTREE_OCTOPUS_V1
configuration does not allow printing via USB.BIGTREE_OCTOPUS_V1
in ini/stm32f4.ini
are:build_flags = ${stm32_variant.build_flags} -DSTM32F446_5VX -DUSE_USB_HS_IN_FS
whereas BIGTREE_OCTOPUS_V1_USB
enabled more USB-related features:
build_flags = ${stm_flash_drive.build_flags} -DSTM32F446_5VX -DUSE_USB_HS_IN_FS -DUSE_USBHOST_HS -DUSBD_IRQ_PRIO=5 -DUSBD_IRQ_SUBPRIO=6 -DUSBD_USE_CDC_MSC
How I fixed STM32CubeProgrammer CUBEPROGRAMMER_ERROR_NOT_SUPPORTED
I tried to flash the DFU bootloader on the BigTreeTech Octopus V1 3D printer mainboard. However, STM32CubeProgrammer v2.8.0 showed me the following error message when trying to connect:
CUBEPROGRAMMER_ERROR_NOT_SUPPORTED
I could only fix this by completely uninstalling STM32CubeProgrammer and then installing STM32CubeProgrammer v2.7.0 from the BigTreeTech Github Repo. Most likely downloading v2.7.0 from the ST homepage will also work but I didn’t verify this.
Beware of the STM32 LSI: Its tolerance is up to ±47%
The STM32 LSI oscillator might seem like an attractive choice for RTC, IWDG Watchdog etc – without external components and
But one fact is often overlooked: Since it is internally a RC oscillator, it has an extremely high tolerance.
By looking at the STM32F407 datasheet, for example, we can see that its tolerance is ±47%
In other words, the LSI clock can run half as slow or 1.5 times as fast as expected.
±47%
is equivalent to ±470 000 ppm
whereas any normal crystal has a tolerance of ±20 ppm
.
More recent STM32 families like the STM32H747XI have improved LSI accuracy:
This amounts to a tolerance of ±1.875 %
which is equivalent to ±18 750 ppm
– still orders of magnitude more than any crystal or even ceramic resonator.
Can you tune out the difference by RTC digital tuning?
The STM32 digital tuning only has a range of -487.1 ppm
to +488.5 ppm
– but even for the much more accurate STM32H747XI, you would need a tuning range of at least ±20 000 ppm
in order to compensate for initial inaccuracies and temperature coefficient.
What can you do to get better accuracy?
Typically, I recommend to just use a crystal for the RTC – or use an external RTC altogether.
Regarding the IWDG, you have no choice but to use the LSI. Typically you can just select a longer reset interval to avoid unintended watchdog resets if your LSI is running much faster than the standard 32 kHz, or you can just reset the watchdog more often. If you reset your watchdog in an interrupt, you should consider using a higher priority interrupt – and do global interrupt disables less frequently and try to avoid having periods where interrupts are disabled globally for a long time continously.