Kalan's Blog

Kalan 頭像照片,在淡水拍攝,淺藍背景

四零二曜日電子報上線啦!訂閱訂起來

Software Engineer / Taiwanese / Life in Fukuoka
This blog supports RSS feed (all content), you can click RSS icon or setup through third-party service. If there are special styles such as code syntax in the technical article, it is still recommended to browse to the original website for the best experience.

Current Theme light

我會把一些不成文的筆記或是最近的生活雜感放在短筆記,如果有興趣的話可以來看看唷!

Please notice that currenly most of posts are translated by AI automatically and might contain lots of confusion. I'll gradually translate the post ASAP

Create an Air Quality Monitoring Application with Arduino and ESP32 (3) - Arduino

This article is the third in a series:

  1. Sensor Introduction - DHT11 and MH-Z14A
  2. Data Communication - UART
  3. Arduino Pitfalls
  4. (Unreleased) WiFi Part: To save debugging time, I purchased an ESP32 development board, which already has WiFi and Bluetooth capabilities.
  5. (Unreleased) MQTT Part: To send data to other devices, I used the lightweight and compact MQTT communication protocol.
  6. (Unreleased) Grafana / Web Part: When data is stored in a database, it should be displayed in a fancy way! Here, Grafana + Prometheus and Svelte are used to display the data.

This article will discuss some pitfalls encountered in Arduino and programming languages.

Different CPU Data Reading Orders (Big Endian vs Little Endian)

Due to different CPU instruction set architectures, the order of reading data may also differ. This is known as byte order or endianness. Reading data starting from the most significant byte is called Big-Endian, while reading data starting from the least significant byte is called Little Endian. The following diagram explains it clearly:

big-endian and little-endian difference

Arduino uses Little Endian as the data reading order, and you can find the answer here. Apart from memory, the terms Big Endian and Little Endian can be used to describe any difference in data reading order.

For the sake of memory management, the operating system divides memory into fixed sizes. So, if there is not enough space to store continuous data, it will be stored in the next available space and added up when accessing the values.

The reason for encountering this issue is that I cleverly assigned ppm as an unsigned short pointer, which should automatically calculate the correct value without manually writing bit shifts. An example is shown below:

struct Co2Result {
  byte startByte;
  byte command;
  byte high;
  byte low;
  unsigned short ppm;
};
Co2Result *result = malloc(sizeof(Co2Result));
result->startByte = response[0];
result->command = response[1];
result->high = response[2];
result->low = response[3];
result->ppm = &result->high;

Since the size of a short is 2 bytes, pointing the pointer to result->high would read 2 bytes from that position. The effect should be equivalent to result->high<<8 + result->low.

short pointer test result

After testing, it was found that the result was a very strange number, which I believe was due to the difference in bit reading order. After using bit shift, I was able to obtain the correct value:

result->ppm = (result->high << 8) + result->low;

C Data Structures Vary by Platform

For example, the size of int can be either 2 bytes or 4 bytes, and a regular long can be either 8 bytes or 4 bytes. In languages like Java, data types do not have different results on different platforms because their definitions come from the language implementation itself. In C/C++, you may encounter types like uint8_t that allow platforms to define data types of different sizes as needed.

Although it may be familiar to those who regularly write C, when calculating the size of data structures, sizeof should be used instead of assuming that data type sizes are fixed on every platform.

Make Good Use of the byte Data Type

Arduino provides a data type called byte, which is defined the same as unsigned char. However, there is often confusion between uint8_t, char, and int. Let's clarify here:

  1. byte and unsigned char are the same, both assigning 8 bits of memory to the variable. The only difference is that unsigned char has a broader meaning to the user, while byte clearly expresses that the number is between 0 and 255 (unsigned). Arduino recommends using byte for consistency.
  2. uint8_t, byte, and unsigned char are conceptually similar, like aliases. In C/C++, int may be defined as 8 bits or 16 bits on different platforms, so uint8_t is usually defined to express a more precise meaning.

Split Functions Using Header Files

When working on Arduino projects, the code structure is relatively small, and putting all the functionality in the loop and setup functions does not make the code too difficult to read. However, once the circuit becomes more complex, putting everything in a single file becomes messy.

Although the file extension in the Arduino IDE is .ino, it is essentially C++ and supports C++ syntax, except for using standard libraries. To write modules and use Arduino functions, simply include Arduino.h.

#include "Arduino.h"
#include <string.h>

If you are using VS Code, you may notice that the built-in Arduino library cannot be included. Although the code will compile without errors, red lines will appear in the editor. In this case, you can add .vscode/c_cpp_properties.json to let VS Code find the correct header file. Here's an example: (using macOS version, for Linux or Windows, you may need to find the Arduino installation location)

{
  "configurations": [
    {
      "name": "Mac",
      "includePath": [
        "${workspaceFolder}/**",
        "/Applications/Arduino.app/Contents/Java/hardware/**",
        "/Applications/Arduino.app/Contents/Java/hardware/arduino/**"
      ],
      "defines": [],
      "macFrameworkPath": [
        "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
      ],
      "compilerPath": "/usr/bin/clang",
      "cStandard": "c11",
      "cppStandard": "c++17",
      "intelliSenseMode": "clang-x64"
    }
  ],
  "version": 4
}

Other

  • noInterrupet() is the same as cli(). In Arduino.h, they are defined as follows:
#define interrupts() sei()
#define noInterrupts() cli()
  • Using std::string directly in Arduino may cause issues. It is better to avoid using std on Arduino, as all those wonderful C++ features are not available. You can refer to the discussion on the forum, where someone seems to be trying to make the std library work on Arduino. I'm not sure about the progress.

Through this experiment, I found that Arduino is a good way to learn various low-level knowledge. It provides a built-in Serial Port for debugging, and with a USB connection and IDE installation, uploading code becomes easy. The implementation details can also be found on GitHub. It also makes me want to brush up on my knowledge of C/C++ (I only had a course on it in university).

Furthermore, in this article, due to my unfamiliarity with Arduino, although I have tried my best to gather information and improve the explanations, there may still be incomplete or incorrect parts. I welcome any suggestions or corrections.

Prev

2020 Tang Fengtai University of Science Graduation Ceremony Speech Notes

Next

從咖啡遊牧地圖(Cafe Nomad)看商機

If you found this article helpful, please consider buy me a drink ☕️ It'll make my ordinary day shine✨

Buy me a coffee