neulab/tldr huggingface dataset:
Conclusion Now we have a project template for the ESP32-DevKitC board that we can use as boilerplate for later projects.
Get started with ESP-IDF and ESP32-DevKitC: debugging, unit testing, project analysis The goal of this tutorial is to demonstrate how simple it is to use ide_vscode to develop, run and debug a simple Wi-Fi project with the framework_espidf framework for the ESP32-DevKitC board.
• Level: Intermediate
• An external debug adapter (e.g. debugging_tool_olimex-arm-usb-ocd)
• Project Inspection
Setting Up the Project 1. Click on "PlatformIO Home" button on the bottom PlatformIO Toolbar: [image]
2. Click on "New Project", select ESP32-DevKitC as the development board, framework_espidf as the framework and a path to the project location (or use the default one): [image]
Adding Code to the Generated Project 1. Create a new file main.c in projectconf_pio_src_dir folder and add the following code:
/* WiFi softAP Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "nvs_flash.h"
#include "lwip/err.h" #include "lwip/sys.h"
#define EXAMPLE_ESP_WIFI_SSID      "mywifissid" #define EXAMPLE_ESP_WIFI_PASS      "mywifipass" #define EXAMPLE_MAX_STA_CONN       (3)
static const char *TAG = "wifi softAP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_id == WIFI_EVENT_AP_STACONNECTED) { wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data; ESP_LOGI(TAG, "station "MACSTR" join, AID=%d", MAC2STR(event->mac), event->aid); } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data; ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d", MAC2STR(event->mac), event->aid); } }
void wifi_init_softap() { tcpip_adapter_init(); ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
wifi_config_t wifi_config = { .ap = { .ssid = EXAMPLE_ESP_WIFI_SSID, .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID), .password = EXAMPLE_ESP_WIFI_PASS, .max_connection = EXAMPLE_MAX_STA_CONN, .authmode = WIFI_AUTH_WPA_WPA2_PSK }, }; if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) { wifi_config.ap.authmode = WIFI_AUTH_OPEN; }
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s", EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); }
void app_main() { //Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); wifi_init_softap(); }
WARNING: Make sure this new file main.c is registered as source file using idf_component_register function in src/CMakeLists.txt file:
idf_component_register(SRCS "main.c")
2. To compile the project use one of the following options:
• Build option from the Project Tasks menu
• Build button in ide_vscode_toolbar
• Task Menu Tasks: Run Task... > PlatformIO: Build or in ide_vscode_toolbar
• Command Palette View: Command Palette > PlatformIO: Build
• Hotkeys cmd-alt-b / ctrl-alt-b: [image]
3. If everything went well, we should see a successful result message in the terminal window: [image]
4. To upload the firmware to the board we can use the following options:
• Upload option from the Project Tasks menu
• Upload button in ide_vscode_toolbar
• Command Palette View: Command Palette > PlatformIO: Upload
• Task Menu Tasks: Run Task... > PlatformIO: Upload
• Hotkeys cmd-alt-u / ctrl-alt-u: [image]
5. Connect the board to your computer and update the default monitor speed to 115200 in platformio.ini file:
[env:esp32dev] platform = espressif32 board = esp32dev framework = espidf monitor_speed = 115200
6. Open Serial Monitor to observe the output from the board: [image]
7. If everything went well, the board should be visible as a WiFi access point: [image]
Debugging the Firmware Setting Up the Hardware In order to use piodebug, we need to connect an external JTAG probe and the board using the following pins:
1. Specify projectconf_debug_tool in projectconf. In this tutorial, debugging_tool_olimex-arm-usb-ocd-h debug probe is used:
[env:esp32dev] platform = espressif32 board = esp32dev framework = espidf monitor_speed = 115200 debug_tool = olimex-arm-usb-ocd-h
2. To start the debug session we can use the following methods:
• Debug: Start debugging in the top menu
• Start Debugging option in the Quick Access menu
• Hotkey button F5: [image]
3. Walk through the code using control buttons, set breakpoints, and add variables to the Watch window: [image]
Writing Unit Tests NOTE: Functions setUp and tearDown are used to initialize and finalize test conditions. Implementations of these functions are not required for running tests but if you need to initialize some variables before you run a test, you use the setUp function and if you need to clean up variables you use tearDown function.
For the sake of simplicity, let's create a small library called calculator, implement several basic functions addition, subtraction, multiplication, division and test them using unit_testing engine.
1. Create a new folder calculator in the projectconf_pio_lib_dir folder and add two new files calculator.h and calculator.c with the following contents: calculator.h:
#ifndef _CALCULATOR_H_ #define _CALCULATOR_H_
#ifdef __cplusplus extern "C" { #endif
int addition (int a, int b); int subtraction (int a, int b); int multiplication (int a, int b); int division (int a, int b);
#ifdef __cplusplus } #endif
#endif // _CALCULATOR_H_
calculator.c:
#include "calculator.h"
int addition(int a, int b) { return a + b; }
int subtraction(int a, int b) { return a - b; }
int multiplication(int a, int b) { return a * b; }
int division(int a, int b) { return a / b; }
2. Create a new file test_calc.c to the folder projectconf_pio_test_dir and add basic tests for the calculator library:
#include <calculator.h> #include <unity.h>
void test_function_calculator_addition(void) { TEST_ASSERT_EQUAL(32, addition(25, 7)); }
void test_function_calculator_subtraction(void) { TEST_ASSERT_EQUAL(20, subtraction(23, 3)); }
void test_function_calculator_multiplication(void) { TEST_ASSERT_EQUAL(50, multiplication(25, 2)); }
void test_function_calculator_division(void) { TEST_ASSERT_EQUAL(32, division(100, 3)); }
void main() { UNITY_BEGIN();
RUN_TEST(test_function_calculator_addition); RUN_TEST(test_function_calculator_subtraction); RUN_TEST(test_function_calculator_multiplication); RUN_TEST(test_function_calculator_division);
UNITY_END(); }
3. Let's run tests on the board and check the results. There should be a problem with test_function_calculator_division test: [image]
4. Let's fix the incorrect expected value and run tests again. After processing the results should be correct: [image]
Project Inspection For illustrative purposes, let's imagine we need to find a function with the biggest memory footprint. Also, let's introduce a bug to our project so piocheck can report it.
1. Open PlatformIO Home and navigate to Inspect section, select the current project and press Inspect button: [image]
2. Project statistics: [image]
3. The biggest function: [image]
4. Possible bugs: [image]
STM32Cube HAL and Nucleo-F401RE: debugging and unit testing The goal of this tutorial is to demonstrate how simple it is to use ide_atom to develop, run and debug a basic blink project with framework_stm32cube framework for STM32 Nucleo-F401RE board.
• Downloaded and installed ide_atom
• Install drivers for debugging_tool_stlink debug tool
• Nucleo-F401RE development board
• Project Source Code
Setting Up the Project At first step, we need to create a new project using PlatformIO Home Page (to open this page just press Home icon on the toolbar): [image]
On the next step, we need to select ST Nucleo-F401RE as a development board, framework_stm32cube as a framework and a path to the project location (or use the default one): [image]
Processing the selected project may take some amount of time (PlatformIO will download and install all required packages) and after these steps, we have a fully configured project that is ready for developing code with framework_stm32cube framework.
Adding Code to the Generated Project Let's add some actual code to the project. Firstly, we create two main files main.c and main.h in the projectconf_pio_src_dir folder. Right click on the src in the project window: [image]
Add next content to main.h:
#ifndef MAIN_H #define MAIN_H
#include "stm32f4xx_hal.h"
#define LED_PIN                                GPIO_PIN_5 #define LED_GPIO_PORT                          GPIOA #define LED_GPIO_CLK_ENABLE()                  __HAL_RCC_GPIOA_CLK_ENABLE()
#endif // MAIN_H
Add this code to main.c:
#include "main.h"
void LED_Init();
int main(void) { HAL_Init(); LED_Init();
while (1) { HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN); HAL_Delay(1000); } }
void LED_Init() { LED_GPIO_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct); }
void SysTick_Handler(void) { HAL_IncTick(); }
After this step, we created a basic blink project that is ready for compiling and uploading.
Compiling and Uploading the Firmware Now we can build the project. To compile firmware we can use next options: Build option on the Project Tasks menu, Build button on ide_vscode_toolbar, using Command Palette View: Command Palette > PlatformIO: Build, using Task Menu Tasks: Run Task... > PlatformIO: Build or via hotkeys cmd-alt-b / ctrl-alt-b: [image]
If everything went well, we should see the successful result in the terminal window: [image]
To upload the firmware to the board we can use next options: Upload option on the Project Tasks menu, Upload button on ide_vscode_toolbar, using Command Palette View: Command Palette > PlatformIO: Upload, using Task Menu Tasks: Run Task... > PlatformIO: Upload or via hotkeys cmd-alt-u / ctrl-alt-u: [image]
After successful uploading, the green LED2 should start blinking.
Debugging the Firmware piodebug offers the easiest way to debug your board. To start debugging session you can use Start debugging option in PlatformIO Quick Access menu, Debug: Start debugging from the top menu or hotkey button F5: [image]
We need to wait some time while PlatformIO is initializing debug session and when the first line after the main function is highlighted we are ready to debug: [image]
We can walk through the code using control buttons, set breakpoints, see peripheral registers, add variables to Watch window: [image]
Writing Unit Tests Now let’s write some tests using unit_testing feature that can help us test code directly on the target board. unit_testing engine by default supports only three frameworks: framework_arduino, framework_espidf, framework_mbed, and framework_mbed. Since we decided to use framework_stm32cube we need to implement a custom projectconf_test_transport to print testing results and specify that condition in projectconf:
[env:nucleo_f401re] platform = ststm32 board = nucleo_f401re framework = stm32cube test_transport = custom
Also, we need to create a new folder test where the tests and custom projectconf_test_transport implementation (described next) will be located: [image]
We will use USART2 on ST Nucleo-F401RE board because it's directly connected to the STLink debug interface and in OS it can be visible as a Virtual Com Port, so we don't need any additional USB-UART converter. To implement the custom projectconf_test_transport we need to create two files unittest_transport.h and unittest_transport.c and put them in the projectconf_pio_test_dir in the root folder of our project. In these files we need to implement the next four functions:
void unittest_uart_begin(); void unittest_uart_putchar(char c); void unittest_uart_flush(); void unittest_uart_end();
Implementation of unittest_transport.h:
#ifndef UNITEST_TRANSPORT_H #define UNITEST_TRANSPORT_H
#endif // UNITEST_TRANSPORT_H
Implementation of unittest_transport.c:
#include "unittest_transport.h" #include "stm32f4xx_hal.h"
#define USARTx                           USART2 #define USARTx_CLK_ENABLE()              __HAL_RCC_USART2_CLK_ENABLE() #define USARTx_CLK_DISABLE()             __HAL_RCC_USART2_CLK_DISABLE() #define USARTx_RX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_TX_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE() #define USARTx_RX_GPIO_CLK_DISABLE()     __HAL_RCC_GPIOA_CLK_DISABLE() #define USARTx_TX_GPIO_CLK_DISABLE()     __HAL_RCC_GPIOA_CLK_DISABLE()
#define USARTx_FORCE_RESET()             __HAL_RCC_USART2_FORCE_RESET() #define USARTx_RELEASE_RESET()           __HAL_RCC_USART2_RELEASE_RESET()
#define USARTx_TX_PIN                    GPIO_PIN_2 #define USARTx_TX_GPIO_PORT              GPIOA #define USARTx_TX_AF                     GPIO_AF7_USART2 #define USARTx_RX_PIN                    GPIO_PIN_3 #define USARTx_RX_GPIO_PORT              GPIOA #define USARTx_RX_AF                     GPIO_AF7_USART2
static UART_HandleTypeDef UartHandle;
void unittest_uart_begin() { GPIO_InitTypeDef  GPIO_InitStruct;
USARTx_TX_GPIO_CLK_ENABLE(); USARTx_RX_GPIO_CLK_ENABLE();
USARTx_CLK_ENABLE();
GPIO_InitStruct.Pin       = USARTx_TX_PIN; GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull      = GPIO_PULLUP; GPIO_InitStruct.Speed     = GPIO_SPEED_FAST; GPIO_InitStruct.Alternate = USARTx_TX_AF;
HAL_GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = USARTx_RX_PIN; GPIO_InitStruct.Alternate = USARTx_RX_AF;
HAL_GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStruct); UartHandle.Instance          = USARTx;
UartHandle.Init.BaudRate     = 115200; UartHandle.Init.WordLength   = UART_WORDLENGTH_8B; UartHandle.Init.StopBits     = UART_STOPBITS_1; UartHandle.Init.Parity       = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl    = UART_HWCONTROL_NONE; UartHandle.Init.Mode         = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
if(HAL_UART_Init(&UartHandle) != HAL_OK) { while(1){} }
void unittest_uart_putchar(char c) { HAL_UART_Transmit(&UartHandle, (uint8_t*)(&c), 1, 1000); }
void unittest_uart_flush(){}
void unittest_uart_end() { USARTx_CLK_DISABLE(); USARTx_RX_GPIO_CLK_DISABLE(); USARTx_TX_GPIO_CLK_DISABLE(); }
Now we need to add some test cases. Tests can be added to a single C file that may include multiple tests. First of all, we need to add three default functions: setUp, tearDown and main. setUp and tearDown are used to initialize and finalize test conditions. Implementations of these functions are not required for running tests but if you need to initialize some variables before you run a test, you use the setUp function and if you need to clean up variables you use tearDown function. In our example, we will use these functions to accordingly initialize and deinitialize LED.  main function acts as a simple program where we describe our test plan.
Let's add a new file test_main.c to the folder test. Next basic tests for blinking routine will be implemented in this file:
• test_led_builtin_pin_number ensures that LED_PIN has the correct value
• test_led_state_high  tests functions HAL_GPIO_WritePin and HAL_GPIO_ReadPin with GPIO_PIN_SET value
• test_led_state_low  tests functions HAL_GPIO_WritePin and HAL_GPIO_ReadPin with GPIO_PIN_RESET value
NOTE:
• 2 sec delay is required  since the board doesn't support software resetting  via Serial.DTR/RTS
#include "../src/main.h" #include <unity.h>
void setUp(void) { LED_GPIO_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct); }
void tearDown(void) { HAL_GPIO_DeInit(LED_GPIO_PORT, LED_PIN); }
void test_led_builtin_pin_number(void) { TEST_ASSERT_EQUAL(GPIO_PIN_5, LED_PIN); }
void test_led_state_high(void) { HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET); TEST_ASSERT_EQUAL(GPIO_PIN_SET, HAL_GPIO_ReadPin(LED_GPIO_PORT, LED_PIN)); }
void test_led_state_low(void) { HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_RESET); TEST_ASSERT_EQUAL(GPIO_PIN_RESET, HAL_GPIO_ReadPin(LED_GPIO_PORT, LED_PIN)); }
int main() { HAL_Init();         // initialize the HAL library HAL_Delay(2000);    // service delay UNITY_BEGIN(); RUN_TEST(test_led_builtin_pin_number);
for (unsigned int i = 0; i < 5; i++) { RUN_TEST(test_led_state_high); HAL_Delay(500); RUN_TEST(test_led_state_low); HAL_Delay(500); }
UNITY_END(); // stop unit testing
while(1){} }
Now we are ready to upload tests to the board. To do this we can use Test option from the Project Tasks menu, Tasks: Run Task... > PlatformIO Test option from the top menu or Test button on ide_vscode_toolbar: [image]
After processing we should see a detailed report about the testing results: [image]
Congratulations! As we can see from the report, all our tests went successfully!
Conclusion Now we have a decent template that we can improve for our next more complex projects.
Project Source Code The source code of this tutorial is available at https://github.com/platformio/platformio-examples/tree/develop/unit-testing/stm32cube
Arduino and Nordic nRF52-DK: debugging and unit testing The goal of this tutorial is to demonstrate how simple it is to use ide_vscode to develop, run and debug a simple project with framework_arduino framework for Nordic nRF52-DK board.
• Install drivers for debugging_tool_jlink debug tool
• Nordic nRF52-DK development board
On the next step we need to select Nordic nRF52-DK as a development board, framework_arduino as a framework and a path to the project location (or use the default one): [image]
Processing the selected project may take some amount of time (PlatformIO will download and install all required packages) and after these steps, we have a fully configured project that is ready for developing code with framework_arduino framework.
Adding Code to the Generated Project Let's add some actual code to the project. Firstly, we open a default main file main.cpp in the projectconf_pio_src_dir folder and replace its contents with the following:
void setup() { pinMode(LED_BUILTIN, OUTPUT); }
void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); } [image]
After this step, we created a basic blink project ready for compiling and uploading.
Compiling and Uploading the Firmware Now we can build the project. To compile firmware we can use next options: Build option from the Project Tasks menu, Build button in ide_vscode_toolbar, Task Menu Tasks: Run Task... > PlatformIO: Build or in ide_vscode_toolbar, Command Palette View: Command Palette > PlatformIO: Build or via hotkeys cmd-alt-b / ctrl-alt-b: [image]
If everything went well, we should see a successful result message in the terminal window: [image]
To upload the firmware to the board we can use next options: Upload option from the Project Tasks menu, Upload button in ide_vscode_toolbar, Command Palette View: Command Palette > PlatformIO: Upload, using Task Menu Tasks: Run Task... > PlatformIO: Upload or via hotkeys cmd-alt-u / ctrl-alt-u: [image]
After successful uploading, the green LED1 should start blinking.
Debugging the Firmware piodebug offers the easiest way to debug the board. Firstly, we need to specify projectconf_debug_tool in projectconf. Since the board has an on-board JLink debug probe we can directly declare it in projectconf:
[env:nrf52_dk] platform = nordicnrf52 board = nrf52_dk framework = arduino debug_tool = jlink
To start the debug session we can use next options: Debug: Start debugging from the top menu, Start Debugging option from Quick Access menu or hotkey button F5: [image]
We need to wait some time while PlatformIO is initializing the debug session and when the first line after the main function is highlighted we are ready to debug: [image]
We can walk through the code using control buttons, set breakpoints, add variables to Watch window: [image]
Writing Unit Tests Test cases can be added to a single file that may include multiple tests. First of all, in this file, we need to add four default functions: setUp, tearDown, setup and loop. Functions setUp and tearDown are used to initialize and finalize test conditions. Implementations of these functions are not required for running tests but if you need to initialize some variables before you run a test, you use the setUp function and if you need to clean up variables you use tearDown function. In our example we will use these functions to accordingly initialize and deinitialize LED.  setup and loop functions act as a simple Arduino program where we describe our test plan.
Let's create test folder in the root of the project and add a new file test_main.cpp to this folder. Next basic tests for String class will be implemented in this file:
• test_string_to_upper_case tests upper-case conversion of the string
• 2 sec delay is required since the board doesn't support software resetting via Serial.DTR/RTS
Now we are ready to upload tests to the board. To do this we can use next options: Test button on ide_vscode_toolbar, Test option from the Project Tasks menu or Tasks: Run Task... > PlatformIO Test from the top menu: [image]
Adding Bluetooth LE features To add the basic BLE functionality to our project we need to define the SoftDevice version and install a library called BLEPeripheral.  Both these modifications can be specified in projectconf:
[env:nrf52_dk] platform = nordicnrf52 board = nrf52_dk framework = arduino debug_tool = jlink ; SoftDevice version build_flags = -DNRF52_S132 lib_deps = BLEPeripheral
Now let's create a basic application that can interact with other BLE devices (e.g phone) For example, next code declares a BLE characteristic that controls the state of the LED1.
#include <Arduino.h> #include <SPI.h> #include <BLEPeripheral.h>
BLEPeripheral ledPeripheral = BLEPeripheral();
BLEService ledService = BLEService("19b10000e8f2537e4f6cd104768a1214"); BLECharCharacteristic ledCharacteristic = BLECharCharacteristic("19b10001e8f2537e4f6cd104768a1214", BLERead | BLEWrite);
void setup() { pinMode(LED_BUILTIN, OUTPUT);
ledPeripheral.setAdvertisedServiceUuid(ledService.uuid()); ledPeripheral.addAttribute(ledService); ledPeripheral.addAttribute(ledCharacteristic); ledPeripheral.setLocalName("Nordic NRF52 DK"); ledPeripheral.begin(); }
void loop() { BLECentral central = ledPeripheral.central();
if (central) { while (central.connected()) { if (ledCharacteristic.written()) { if (ledCharacteristic.value()) { digitalWrite(LED_BUILTIN, HIGH); } else{ digitalWrite(LED_BUILTIN, LOW); } } } } }
Now we can compile and upload this program to the board as described in previous sections.  To verify that our application works as expected, we can use any Android smartphone with BLE feature and Nordic nRF Connect tool.
At first, we need to scan all advertising BLE devices and connect to the device called Nordic NRF52 DK.  After a successful connection to the board, we should see one "Unknown Service" with one "Unknown Characteristic" fields: [image]
To switch the LED on or off we just need write 0 or 1 as UINT8 to the BLE characteristic: [image]
Conclusion Now we have a project template for Nordic nRF52-DK board that we can use as a boilerplate for the next projects.
Zephyr and Nordic nRF52-DK: debugging, unit testing, project analysis The goal of this tutorial is to demonstrate how simple it is to use ide_vscode to develop, run and debug a simple Bluetooth project using framework_zephyr framework for the Nordic nRF52-DK board.
2. Click on "New Project", select Nordic nRF52-DK as the development board, framework_zephyr as the framework and a path to the project location (or use the default one): [image]
// // Copyright (c) 2015-2016 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 //
#include <zephyr/types.h> #include <stddef.h> #include <sys/printk.h> #include <sys/util.h>
#include <bluetooth/bluetooth.h> #include <bluetooth/hci.h>
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
// Set Advertisement data. Based on the Eddystone specification: // https://github.com/google/eddystone/blob/master/protocol-specification.md // https://github.com/google/eddystone/tree/master/eddystone-url
static const struct bt_data ad[] = { BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR), BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe), BT_DATA_BYTES(BT_DATA_SVC_DATA16, 0xaa, 0xfe, 0x10, // Eddystone-URL frame type 0x00, // Calibrated Tx power at 0m 0x00, // URL Scheme Prefix http://www. 'z', 'e', 'p', 'h', 'y', 'r', 'p', 'r', 'o', 'j', 'e', 'c', 't', 0x08) // .org };
// Set Scan Response data static const struct bt_data sd[] = { BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN), };
static void bt_ready(int err) { if (err) { printk("Bluetooth init failed (err %d)\n", err); return; }
printk("Bluetooth initialized\n");
// Start advertising err = bt_le_adv_start(BT_LE_ADV_NCONN, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); if (err) { printk("Advertising failed to start (err %d)\n", err); return; }
printk("Beacon started\n"); }
void main(void) { int err;
printk("Starting Beacon Demo\n");
// Initialize the Bluetooth Subsystem err = bt_enable(bt_ready); if (err) { printk("Bluetooth init failed (err %d)\n", err); } }
2. By default Bluetooth feature is disabled, we can enable it by creating a new file prj.conf in zephyr folder and adding the following lines:
CONFIG_BT=y CONFIG_BT_DEBUG_LOG=y CONFIG_BT_DEVICE_NAME="Test beacon"
Compiling and Uploading the Firmware 1. To compile the project use one of the following options:
2. If everything went well, we should see a successful result message in the terminal window: [image]
3. To upload the firmware to the board we can use the following options:
4. Connect the board to your computer and update the default monitor speed to 115200 in platformio.ini file:
[env:hifive1-revb] platform = sifive board = hifive1-revb framework = zephyr monitor_speed = 115200
5. Open Serial Monitor to observe the output from the board: [image]
6. If everything went well, the board should be visible as a beacon: [image]
Debugging the Firmware Since Nordic nRF52-DK includes an onboard debug probe we can use piodebug without any configuration.
1. To start a debug session we can use the following options:
• Debug: Start debugging from the top menu
• Start Debugging option from Quick Access menu
2. We can walk through the code using control buttons, set breakpoints, add variables to Watch window: [image]
For the sake of simplicity, let's create a small library called calculator, implement several basic functions add, sub, mul, div and test them using unit_testing engine.
1. PlatformIO uses a unit testing framework called Unity. Unity is not compatible with C library implemented in the framework. Let's enable standard version of newlib C library in prj.conf file using the following config:
CONFIG_NEWLIB_LIBC=y
2. Create a new folder calculator in the lib folder and add two new files calculator.h and calculator.c with the following contents: calculator.h:
int add (int a, int b); int sub (int a, int b); int mul (int a, int b); int div (int a, int b);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
3. Create a new file `test_calc.c to the folder test and add basic tests for calculator library:
void test_function_calculator_addition(void) { TEST_ASSERT_EQUAL(32, add(25, 7)); }
void test_function_calculator_subtraction(void) { TEST_ASSERT_EQUAL(20, sub(23, 3)); }
void test_function_calculator_multiplication(void) { TEST_ASSERT_EQUAL(50, mul(25, 2)); }
void test_function_calculator_division(void) { TEST_ASSERT_EQUAL(32, div(100, 3)); }
4. Let's run tests on the board and check the results. There should be a problem with test_function_calculator_division test: [image]
5. Let's fix the incorrect expected value, run tests again. After processing the results should be correct: [image]
Conclusion Now we have a project template for Nordic Nordic nRF52-DK board that we can use as a boilerplate for the next projects.
RISC-V ASM Video Tutorial An introduction to using platform_sifive and Assembly language on the SiFive board_sifive_hifive1 by Martin Fink, Chief Technology Officer at Western Digital.
Source Files A demo source code is published on Github: https://github.com/martin-robert-fink/superBlink.git
It is already pre-configured PlatformIO project:
• Clone it or download
• Open in ide_vscode
• Happy coding and debugging!
Video Collection .INDENT 0.0
• Part 1 of 12 | Introduction
• Part 2 of 12 | Setting Up
• Part 3 of 12 | Tour PlatformIO
• Part 4 of 12 | C Code Wrapper
• Part 5 of 12 | HiFive Docs
• Part 6 of 12 | Understanding GPIO
• Part 7 of 12 | setupGPIO
• Part 8 of 12 | Debug setupGPIO
• Part 9 of 12 | setLED
• Part 10 of 12 | Debug setLED
• Part 11 of 12 | Delay
• Part 12 of 12 | Final and Conclusion
Project Examples Pre-configured projects with source code are located in PlatformIO Examples repository.
Community Projects • PlatformIO DIY Projects & Tutorials at Hackster.io
Community Video Tutorials • PlatformIO Video Collection on YouTube
• Next-generation IDE for your RISC-V Product in 20 Minutes by CEO of PlatformIO
• Use the PlatformIO Debugger on the ESP32 Using an ESP-prog
• RISC-V ASM Tutorial
• PlatformIO for Arduino, ESP8266, and ESP32 Tutorial
• Free Inline Debugging for ESP32 and Arduino Sketches
• PlatformIO или прощай, Arduino IDE
• Отладка ESP32 в PlatformIO
• A Better Arduino IDE - Getting Started with PlatformIO
• PlatformIO - Using External Libraries
platformio.ini (Project Configuration File) Each PlatformIO project has a configuration file named platformio.ini in the root directory for the project. This is a INI-style file.
platformio.ini has sections (each denoted by a [header]) and key / value pairs within the sections. Lines beginning with ; are ignored and may be used to provide comments.
Multiple value options can be specified in two ways:
1. Split values with ", " (comma + space)
2. Multi-line format, where each new line starts with at least two spaces
There are two required sections:
• piocore settings: projectconf_section_platformio
• Environment settings: projectconf_section_env
The other sections are optional to include. Here are the allowed sections and their allowed contents:
Section [platformio] • Generic options
• description
• default_envs
• extra_configs
• Directory options
• core_dir
• globallib_dir
• platforms_dir
• packages_dir
• cache_dir
• build_cache_dir
• workspace_dir
• build_dir
• libdeps_dir
• include_dir
• src_dir
• lib_dir
• data_dir
• test_dir
• boards_dir
• shared_dir
The platform.ini platformio section is used for overriding the default configuration options for piocore.
NOTE: Relative path is allowed for directory option:
• ~ will be expanded to user's home directory
• ../ or ..\ go up to one folder
There is a $PROJECT_HASH template variable. You can use it in a directory path. It will by replaced by a SHA1[0:10] hash of the full project path.  This is very useful to declare a global storage for project workspaces.  For example, /tmp/pio-workspaces/$PROJECT_HASH (Unix) or $[sysenv.TEMP}/pio-workspaces/$PROJECT_HASH (Windows).  You can set a global workspace directory using the system environment variable PLATFORMIO_WORKSPACE_DIR.
See the available directory ***_dir options below.
Generic options description Type: String | Multiple: No
Short description of the project. PlatformIO uses it for piohome in the multiple places.
default_envs Type: String | Multiple: Yes
The cmd_run command processes all environments [env:***] by default if the platformio run --environment option is not specified.  default_envs allows one to define which environments that should be processed by default.
Also, piodebug checks this option when looking for debug environment.
This option can also be configured by the global environment variable PLATFORMIO_DEFAULT_ENVS.
[platformio] default_envs = uno, nodemcu
[env:nodemcu] platform = espressif8266 framework = arduino board = nodemcu
[env:lpmsp430g2553] platform = timsp430 framework = arduino board = lpmsp430g2553 build_flags = -D LED_BUILTIN=RED_LED
extra_configs New in version 4.0.
Type: String (Pattern) | Multiple: Yes
This option allows extending a base projectconf with extra configuration files. The format and rules are the same as for the projectconf.  A name of the configuration file can be any.
extra_configs can be a single path to an extra configuration file or a list of them. Please note that you can use Unix shell-style wildcards:
NOTE: If you declare the same pair of "group" + "option" in an extra configuration file which was previously declared in a base projectconf, it will be overwritten with a value from extra configuration.
Base "platformio.ini"
[platformio] extra_configs = extra_envs.ini extra_debug.ini
; Global data for all [env:***] [env] platform = espressif32 framework = espidf
; Custom data group ; can be use in [env:***] via ${common.***} [common] debug_flags = -D RELEASE lib_flags = -lc -lm
[env:esp-wrover-kit] board = esp-wrover-kit build_flags = ${common.debug_flags}
"extra_envs.ini"
[env:esp32dev] board = esp32dev build_flags = ${common.lib_flags} ${common.debug_flags}
[env:lolin32] platform = espressif32 framework = espidf board = lolin32 build_flags = ${common.debug_flags}
"extra_debug.ini"
# Override base "common.debug_flags" [common] debug_flags = -D DEBUG=1
[env:lolin32] build_flags = -Og
After a parsing process, configuration state will be the next:
[common] debug_flags = -D DEBUG=1 lib_flags = -lc -lm
[env:esp-wrover-kit] platform = espressif32 framework = espidf board = esp-wrover-kit build_flags = ${common.debug_flags}
[env:esp32dev] platform = espressif32 framework = espidf board = esp32dev build_flags = ${common.lib_flags} ${common.debug_flags}
[env:lolin32] platform = espressif32 framework = espidf board = lolin32 build_flags = -Og
Directory options core_dir New in version 4.0.
Type: DirPath | Multiple: No
The core_dir variable points out the directory used for all development platform packages (toolchains, frameworks, SDKs, upload and debug tools), global libraries for ldf, and other PlatformIO Core service data. The size of this folder will depend on the number of installed development platforms.
The default value is the user's home directory:
• Unix ~/.platformio
• Windows %HOMEPATH%\.platformio
This option can also be configured by the global environment variable PLATFORMIO_CORE_DIR.
[platformio] core_dir = /path/to/custom/pio-core/storage
globallib_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "core_dir/lib"
Global library storage for PlatfrmIO projects and librarymanager where ldf looks for dependencies.
This option can also be configured by the global environment variable PLATFORMIO_GLOBALLIB_DIR.
platforms_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "core_dir/platforms"
Global storage where PlatformIO Package Manager installs platforms.
This option can also be configured by the global environment variable PLATFORMIO_PLATFORMS_DIR.
packages_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "core_dir/packages"
Global storage where PlatformIO Package Manager installs platforms dependencies (toolchains, frameworks, SDKs, upload and debug tools).
This option can also be configured by the global environment variable PLATFORMIO_PACKAGES_DIR.
cache_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "core_dir/cache"
piocore uses this folder to store caching information (requests to PlatformIO Registry, downloaded packages and other service information).
To reset a cache, please run cmd_update command.
This option can also be configured by the global environment variable PLATFORMIO_CACHE_DIR.
build_cache_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: None (Disabled)
piocore uses this folder to store derived files from a build system (objects, firmwares, ELFs). These files are shared between all build environments. To speed up a build process, you can use the same cache folder between different projects if they depend on the same development platform and framework.
This option can also be configured by the global environment variable PLATFORMIO_BUILD_CACHE_DIR.
The example of projectconf below instructs PlatformIO Build System to check build_cache_dir for already compiled objects for framework_stm32cube and project source files. The cached object will not be used if the original source file was modified or build environment has a different configuration (new build flags, etc):
[platformio] ; Set a path to a cache folder build_cache_dir =
; Examples: ; (Unix) build_cache_dir = /path/to/cache/folder ; (Windows) build_cache_dir = C:/path/to/cache/folder
[env:bluepill_f103c6] platform = ststm32 framework = stm32cube board = bluepill_f103c6
[env:nucleo_f411re] platform = ststm32 framework = stm32cube board = nucleo_f411re
workspace_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "Project/.pio"
The path to a project workspace directory where PlatformIO keeps by default compiled objects, static libraries, firmwares, and external library dependencies. It is used by these options:
• libdeps_dir.
The default value is .pio and means that folder is located in the root of project.
This option can also be configured by the global environment variable PLATFORMIO_WORKSPACE_DIR.
build_dir WARNING: PLEASE DO NOT EDIT FILES IN THIS FOLDER. PlatformIO will overwrite your changes on the next build. THIS IS A CACHE DIRECTORY.
Type: DirPath | Multiple: No | Default: "workspace_dir/build"
PlatformIO Build System uses this folder for project environments to store compiled object files, static libraries, firmwares and other cached information. It allows PlatformIO to build source code extremely fast!
You can delete this folder without any risk! If you modify projectconf, then PlatformIO will remove this folder automatically. It will be created on the next build operation.
This option can also be configured by the global environment variable PLATFORMIO_BUILD_DIR.
NOTE: If you have any problems with building your project environments which are defined in projectconf, then TRY TO DELETE this folder. In this situation you will remove all cached files without any risk. Also, you can use "clean" target for platformio run --target command.
libdeps_dir Type: DirPath | Multiple: No | Default: "workspace_dir/libdeps"
Internal storage where librarymanager will install project dependencies (projectconf_lib_deps).
This option can also be configured by the global environment variable PLATFORMIO_LIBDEPS_DIR.
include_dir Type: DirPath | Multiple: No | Default: "Project/include"
The path to project's default header files. PlatformIO uses it for the cmd_run command. The default value is include meaning an include directory located under the root directory of the project. This path will be added to CPPPATH of the build environment.
If you need to add extra include directories to CPPPATH scope, please use projectconf_build_flags with -I /path/to/extra/dir option.
This option can also be configured by the global environment variable PLATFORMIO_INCLUDE_DIR.
src_dir Type: DirPath | Multiple: No | Default: "Project/src"
The path to the project's directory with source code. PlatformIO uses it for the cmd_run command. The default value is src meaning a src directory located in the root directory of the project.
This option can also be configured by the global environment variable PLATFORMIO_SRC_DIR.
NOTE: This option is useful for people who migrate from Arduino IDE where the source directory should have the same name as the main source file.  See example project with own source directory.
lib_dir Type: DirPath | Multiple: No | Default: "Project/lib"
You can put your own/private libraries here. The source code of each library should be placed in separate directory, like lib/private_lib/[here are source files]. This directory has the highest priority for ldf.
The default value is lib, meaning a lib directory located in the root of the project.
This option can also be configured by the global environment variable PLATFORMIO_LIB_DIR.
For example, see how the Foo and Bar libraries are organized:
|--lib |  |--Bar |  |  |--docs |  |  |--examples |  |  |--src |  |     |- Bar.c |  |     |- Bar.h |  |--Foo |  |  |- Foo.c |  |  |- Foo.h |- platformio.ini |--src |- main.c
Then in src/main.c you should use:
#include <Foo.h> #include <Bar.h>
// rest of H/C/CPP code
PlatformIO will find your libraries automatically, configure the preprocessor's include paths and build them.
data_dir Type: DirPath | Multiple: No | Default: "Project/data"
Data directory to store contents and platform_espressif_uploadfs.  The default value is data that means that folder is located in the root of the project.
This option can also be configured by the global environment variable PLATFORMIO_DATA_DIR.
test_dir Type: DirPath | Multiple: No | Default: "Project/test"
The directory where unit_testing engine will look for the tests.  The default value is test, meaning a test directory located in the root of the project.
This option can also be configured by the global environment variable PLATFORMIO_TEST_DIR.
boards_dir Type: DirPath | Multiple: No | Default: "Project/boards"
The location of project-specific board definitions. Each project may choose a suitable directory name.  The default value is boards, meaning a "boards" directory located in the root of the project.
By default, PlatformIO looks for boards in this order:
1. Project boards_dir (as defined by this setting)
2. Global core_dir/boards
3. Development platform core_dir/platforms/*/boards.
This option can also be configured by the global environment variable PLATFORMIO_BOARDS_DIR.
shared_dir New in version 4.0.
Type: DirPath | Multiple: No | Default: "Project/shared"
pioremote uses this folder to synchronize extra files between remote machine. For example, you can share projectconf_extra_scripts.
Please note that these folders are automatically shared between remote machine with platformio remote run --force-remote or platformio remote test --force-remote commands:
The default value is shared, meaning a directory named "shared" located in the root of the project.
This option can also be configured by the global environment variable PLATFORMIO_SHARED_DIR.
Section [env] • Common [env]
• Environment [env:NAME]
Each project may have multiple configuration environments defining the available project tasks for building, programming, debugging, unit testing, device monitoring, library dependencies, etc. The configuration environments are declared using [env] sections in projectconf.
The allowed options are listed under Options.
Common [env] New in version 4.0.
An optional configuration environment with common options that will be shared between all [env:NAME] environments in the platform.ini file. It is very useful if the configuration file has a lot of environments [env:NAME] and they share common settings.
[env] platform = ststm32 framework = stm32cube board = nucleo_l152re lib_deps = Dep1, Dep2
[env:release] build_flags = -D RELEASE lib_deps = ${env.lib_deps} Dep3
[env:debug] build_type = debug build_flags = -D DEBUG lib_deps = DepCustom
In this example we have two configuration environments release and debug. This is equivalent to duplicating all options as shown below:
[env:release] platform = ststm32 framework = stm32cube board = nucleo_l152re build_flags = -D RELEASE lib_deps = Dep1, Dep2, Dep3
[env:debug] platform = ststm32 framework = stm32cube board = nucleo_l152re build_type = debug build_flags = -D DEBUG lib_deps = DepCustom
Environment [env:NAME] A section with an env: prefix defines a working environment for cmd_run, cmd_test, cmd_check, cmd_debug and other commands.  Multiple [env:NAME] environments with different NAME are allowed. Every project must define at least one working environment.
Each environment must have a unique NAME. The valid chars for NAME are letters a-z, numbers 0-9,  special char _ (underscore).  For example, [env:hello_world].
If you have multiple working environments and you need to process only a few of them, the commands mentioned above accept the -e, --environment option to select a subset of the working environments to process.  The [platformio] projectconf_pio_default_envs option can be used to define a default set of working environments for the commands to process.
Options Platform options • platform
• platform_packages
• framework
• board
• board_build.mcu
• board_build.f_cpu
• board_build.ldscript
• More options
platform Type: String | Multiple: No
platforms name.
PlatformIO allows one to use specific version of platform using Semantic Versioning (X.Y.Z=MAJOR.MINOR.PATCH) or VCS (Git, Mercurial and Subversion).
Version specifications can take any of the following forms:
• 1.2.3: an exact version number. Use only this exact version
• ^1.2.3: any compatible version (exact version for 1.x.x versions)
• ~1.2.3: any version with the same major and minor versions, and an equal or greater patch version
• >1.2.3: any version greater than 1.2.3. >=, <, and <= are also possible
• >0.1.0,!=0.2.0,<0.3.0: any version greater than 0.1.0, not equal to 0.2.0 and less than 0.3.0
Other forms are the same as for the  cmd_platform_install command.
[env:the_latest_version] platform = atmelavr
[env:exact_version] platform = atmelavr@1.2.3
[env:specific_major_version] platform = atmelavr@^1.2.3
[env:specific_major_and_minor_version] platform = atmelavr@~1.2.3
[env:development_verion_by_git] platform = https://github.com/platformio/platform-ststm32.git
[env:custom_git_branch] platform = https://github.com/platformio/platform-espressif8266.git#feature/stage
[env:specific_git_commit] platform = https://github.com/platformio/platform-espressif8266.git#921855a9c530082efddb5d48b44c3f4be0e2dfa2
platform_packages New in version 4.0.
Type: String | Multiple: Yes
Configure custom packages per a build environment. You can also override default packages by platforms using the same name. Packages will be installed in projectconf_pio_packages_dir.
[env:override_default_toolchain] platform = atmelavr platform_packages = ; use GCC AVR 5.0+ toolchain-gccarmnoneeabi@>1.50000.0
[env:override_framework] platform = espressif8266 platform_packages = ; use upstream Git version framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git
[env:external_package] platform = ststm32 platform_packages = ; latest openOCD from PlatformIO Package Registry tool-openocd
; source code of ST-Link tool-stlink-source @ https://github.com/texane/stlink.git
framework Type: String | Multiple: Yes
frameworks name.
board Type: String (ID) | Multiple: No
PlatformIO has pre-configured settings for the most popular boards:
• build configuration
• upload configuration
• debugging configuration
• connectivity information, etc.
You can find a valid  board ID in boards catalog, Boards Explorer or cmd_boards command.
board_build.mcu Type: String | Multiple: No
board_build.mcu is a microcontroller(MCU) type that is used by compiler to recognize MCU architecture. The correct type of board_build.mcu depends on platform library. For example, the list of board_build.mcu for "megaAVR Devices" is described here.
The full list of board_build.mcu for the popular embedded platforms you can find in Boards section of platforms. See "Microcontroller" column.
board_build.f_cpu Type: Integer | Multiple: No
The option board_build.f_cpu is used to define MCU frequency (Hertz, Clock). A format of this option is C-like long integer value with L suffix. The 1 Hertz is equal to 1L, then 16 MHz (Mega Hertz) is equal to 16000000L.
The full list of board_build.f_cpu for the popular embedded platforms you can find in Boards section of platforms. See "Frequency" column. You can overclock a board by specifying a board_build.f_cpu value other than the default.
board_build.ldscript Type: String | Multiple: No