BOOK

Table of Contents

Authors

IOT-OPEN.EU Reloaded Consortium partners proudly present the 2nd edition of the Introduction to the IoT book. The complete list of contributors is juxtaposed below.

ITT Group

TalTech

Riga Technical University

Silesian University of Technology

  • Piotr Czekalski, Ph. D., Eng.
  • Krzysztof Tokarz, Ph. D., Eng.

Tallinn University of Technology

IT Silesia

Technical Correction

  • Eryk Czekalski

 

Preface

This book and its offshoots were prepared to provide a guide on how to use VREL NextGen Remote Access laboratories.

It contains a technical description of the hardware, a software guide, and hands-on labs with detailed scenarios for various levels of IoT students.

We (Authors) assume that a person willing to use VREL NextGen labs is already familiar with IoT on the engineering level: understands IoT concepts, knows IoT MCUs, sensors and actuators, understands communication principles and, most of all, has programming skills in C++ and Python.

This book is instantly updated online to catch up with the latest developments in software libraries and tools, so its printed edition is considered a collection edition only. Please always refer to the latest online version on Dokuwiki.

Enjoy the power of experiencing real hardware touch, even if done remotely, from your favourite study place.

 

Project Information

This Book was implemented under the following projects:

  • Cooperation Partnerships in higher education, 2022, IOT-OPEN.EU Reloaded: Education-based strengthening of the European universities, companies and labour force in the global IoT market, project number: 2022-1-PL01-KA220-HED-000085090,
  • Horizon 2020 Research Innovation and Staff Exchange Programme (RISE) under the Marie Skłodowska-Curie Action, Programme H2020-EU.1.3.3. - Stimulating innovation by means of cross-fertilisation of knowledge, Grant Agreement No 871163: Reliable Electronics for Tomorrow’s Active Systems.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views only of the author, and the Commission cannot be held responsible for any use which may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded Consortium 2022-2025.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

CC BY-NC

In case of commercial use, please get in touch with IOT-OPEN.EU Reloaded Consortium representative.

VREL Next Gen Remote Labs

The VREL NextGen distant laboratory with remote access allows one to work with real hardware (not a simulator) and experience real engineering problems. The web browser is all that is needed to interface the lab. Programming the IoT devices is done in the browser, and firmware uploads, restarts, etc., are all handled in the web browser as well. The device's state can be observed directly via video streaming, almost in real-time, and indirectly via the network (figure 2). One or more devices can be booked exclusively for a user. While booked, it is unavailable for other users, but several devices exist. Booking time is limited. Users can choose which laboratory and device to access, then book and use it. Technical documentation and hands-on laboratory scenarios are integrated with the user interface.

Figure 2: VREL NextGen general idea and building components

The VREL NextGen solution comprises one or more hosting servers that provide a user interface via www and several laboratories with hardware located across Europe, currently in Poland, Estonia and Latvia. Public instances are announced via the https://iot-open.eu website. Besides public instances, there are private ones for consortium HE partner's students only. Those servers and related hardware (lab nodes) are not publicly accessible. Public instances sometimes also share resources with consortium HE partner's activities.

It is a rule of thumb that a single laboratory usually shares common space and services. One should refer to the technical description to understand capabilities and physical limitations. Scenarios requiring more than one device can be implemented locally within the limits of the single laboratory composed of many programmable IoT nodes or across space, transferring data through the internet. This enables virtually unlimited integration capabilities using physically separated devices worldwide and integrates other devices and solutions, such as cloud providers' services.

Note, even remotely, you're facing real, physical devices with actuators; thus, you always must consider in your experiments physical phenomena like time, friction, pendulum, and so on.

The following chapters provide a manual for software and hardware. Laboratory scenarios are provided as per laboratory. Integration and local services such as access points, gateways and related technical information (network configuration, credentials, etc) are described per laboratory in the hardware section.

VREL NextGen Management and IoT Developer Software

VREL NextGen software is a web-based, integrated solution for both IoT software developers (Users/Students) and system administrators (Administrators, Super Administrators). It can be used in one of the three aforementioned roles.
There are many public and private instances for internal purposes of the Consortium HE and SME Members. Use of the system requires user registration with a valid email address. A front-page view is present in figure 3.

Figure 3: VREL NextGen software front page

In the following chapters, there is a manual on how to use the system:

User's Guide

[pczekalski]Do a user's guide

Admins's Guide

[pczekalski]Do an admin's guide The system has two kinds of administrators: the Super Admin, a built-in account, and any number of Laboratory Admins. To understand activities and relations, it is essential to recognise system-building components (figure {ref>vrelsoftware2}).

Figure 4: VREL Nextgen building components

Super Admin

The Super Admin can create new Laboratory Admins by promoting the regular user's account or explicitly creating a new one.
The Super Admin can also create a laboratory (a group of administrators) and assign Administrator's (Administrators') accounts to it, remove admins and manage individual Users.

Admin

The Admin's role is to configure the system per laboratory and manage cohorts of students (Users) and individual devices. There can be many Admins in the system, and each can manage their own Users, Laboratories (called Group of Devices) and individual Devices.

Regular Admin has several scenarios:

  • User management:
    • editing User's data,
    • delete the User's project files (Cleanup).
  • Managing group of users:
    • creating and deleting user groups,
    • adding and removing users to and from the group.
  • Managing a Group of Users in the context of the devices:
    • assign existing devices to the Group, removing device assignments from the User's Group.
  • Managing Groups of Devices (Laboratory):
    • creating and deleting a Group of Devices (Laboratory),
    • managing individual Devices' assignment to the Group of Devices
  • Configuring and managing individual Devices:
    • creating and editing a Device with detailed technical documentation, deleting the device,
    • cleaning up devices (deletes compilation targets),
  • Managing User's bookings (per device).

Below there are some hints and important information:

  1. User Groups bind Users and Devices: Users can book Devices if both are in the same group. The User can be a member of many groups. Groups typically reflect student groups (such as the Dean's group or laboratory team), and devices assigned to the group reflect module-related resources that are needed to perform lab work.
  2. Admin-level access to the Bookings is possible via the Devices menu (then expand the group and the device and check for Bookings in the context menu).
  3. Users' projects (source codes) can be cleaned up in the Users menu.
  4. Laboratory compiled and temporary files can be cleaned up in the Devices' context (Devices menu).
  5. Compiler service uses a single source file on the input, which is PlatformIO-based. Thus, all source codes constituting a project are single cpp (or other) files + platformio.ini files.

Admin: device configuration

Device configuration is the most complex part of the administration process. It requires the correct configuration of the end node proxy service regarding the specification of the target IoT device it manages. It is a compilation service (figure 4) that executes compilation commands and execution.
The device configuration supports several configuration parameters, many of which may be redundant or unnecessary, to ensure flexibility among different IoT hardware. Commands to compile and upload firmware (or configuration) are Admin-defined. Besides common configuration parts such as name, location, and description, there are sections for:

  • Device reset (reboot, restart)
  • Code compilation, including check of the success with standardised return signalling to the system
  • Code execution (i.e. firmware upload)
Aforementioned actions can have more than one command, and each command can be an SSH or CMD (bash) command.
There are wildcards that represent some critical information, e.g. source file name, target location etc. Those can be used in commands and passed as parameters to the external scripts that can be executed (CMDs).

SUT ESP32 Laboratory Node Hardware Reference

Introduction

Each laboratory node is equipped with an ESP32-S3 double-core chip. Several peripherals, networks and network services are available for the user. The UI is necessary to observe results in the camera when programming remotely. Thus, a proper understanding of UI programming is essential to successfully using the devices.

Note that each node has a unique ID built into the chip, as well as unique MAC addresses for the WiFi and Bluetooth interfaces.

Hardware reference

The table 1 lists all hardware components of the SUT's ESP32-S3 node and hardware details such as connectivity, protocols, GPIOs, etc. Please note that some pins overlap because buses such as SPI and I2C are shared among multiple components.
The node is present in the figure 5 and reference numbers reflecting components in the table 1.

Figure 5: ESP32-S3 SUT Node
Table 1: ESP32-S3 SUT Node Hardware Details
Component ID Description Hardware model (controller) Control method GPIOs (as connected to the ESP32-S3) Remarks
1A 12V PWM controlled fan Pe60251b1-000u-g99 PWM FAN_PWM = 35 Fan blows air into the pressure chamber (yellow container) to stimulate air pressure changes.
1B Pressure and environmental sensor BME 280 I2C, address 0x76 SDA=5, SCL=4 Spinning of the fan causes air to blow inside the yellow chamber and thus causes air pressure to change.
2 Digital potentiometer DS1803-100 I2C, address 0x28 SDA=5, SCL=4, analog input (A/D)=7 Digital potententiometer's output is connected to the A/D input of the MCU.
3 Temperature and humidity sensor 1 DHT11 proprietary protocol, one GPIO control on GPIO 47
4 Temperature sensor 2 DS18B20 1-Wire 1-Wire interface on GPIO 6
5 2×16 LCD HD44780 Proprietary 4 bit control interface EN=1, RS=2, D4=39, D5=40, D6=41, D7=42 4-bit, simplified, one-directional (MCU→LCD) communication only
6 ePaper, B&W 2.13in, 250×122 pixels Pico-ePaper-2.13 SPI SPI_MOSI=15, SPI_CLK=18, SPI_DC=13, SPI_CS=10, SPI_RST=9, EPAPER_BUSY=8 Memory size is 64kB (65536ul)
7 OLED, RGB colourful 1.5in, 128×128 pixels SSD1351 SPI SPI_MOSI=15, SPI_CLK=18, SPI_DC=13, SPI_CS=11, SPI_RST=12 64k colours RGB (16bit)
8 RGB Smart LED stripe 8*WS2812B Proprietary protocol, one GPIO NEOPIXEL=34
9A Light intensity and colour sensor TCS 34725 I2C address 0x29 SDA=5, SCL=4, Interrupt=16 The sensor is illuminated by RGB LED (9A)
9B RGB LED PWM controlled PWM LED_R=33, LED_B=26, LED_G=21 Each colour can be independently controlled with PWM. The LED is integrated with another, illuminating the colour sensor (9B) so that controlling this RGB LED also directly impacts the other.
10 Standard miniature servo SG90 or similar PWM SERVO_PWM=37 Standard timings for micro servo: PWM 50Hz, duty cycle:
- 0 deg (right position): 1ms,
- 90 deg (up position): 1.5ms,
- 180 deg (left position): 2ms.

The MCU standing behind the laboratory node is a genuine ESP32-S3-DevKitM-1-N8 from Espressif [1], present in figure 6:

ESP32-S3-DevKitM-1-N8 development kit
Figure 6: ESP32-S3-DevKitM-1-N8 controlling the laboratory node

A suitable platformio.ini file for the correct code compilation is presented below. It does not contain libraries that need to be added regarding specific tasks and hardware used in particular scenarios. The code below presents only the typical section. Refer to the scenario description for details regarding case-specific libraries needed for the implementation:

platformio.ini
[env:esp32]
platform = espressif32
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
board_build.f_cpu = 240000000L
framework = arduino
platform_packages =
    toolchain-riscv32-esp @ 8.4.0+2021r2-patch5
lib_ldf_mode = deep+

Network configuration and services

Figure 7 represents SUT's VREL Next Gen IoT remote lab networking infrastructure and services. Details are described below.

If you're a fully remote student, you do not have access to the public part of the network (addresses 157.158.56.0/24); thus, you need to use only IoT devices and services available in the internal IoT WiFi network that IoT devices you're programming can access.

If you're a SUT student or can access the campus network, you can also use 157.158.56.0/24 addresses.

Figure 7: VREL Next Gen IoT remote lab networking infrastructure and services

Networking Layer

The WiFi network, separated (no routing to and from the Internet) for IoT experimentation is available for all nodes:

  • SSID: internal.IOT.NextGen
  • PASS: IoTlab32768

A public, wired (157.158.56.0/24) network is available only for on-site students and from the SUT's campus network.

It is important to distinguish the network context and use the correct address. Integration services usually have two interfaces: one is available from the IoT WiFi network so nodes can access it, and the other IP address (from the public campus network) is available only for students directly connected to it.

Application Layer Services

There are currently two application layer services available:

  • MQTT broker available on both addresses:
    • IP addresses: 192.168.91.5 (from internal IoT WiFi network), 157.158.56.57 (via the campus network);
    • Port: 1883 (TCP)
    • User: vrel
    • Pass: vrel2018
  • CoAP server with 2 endpoints:
    • IP addresses: 192.168.91.5 (from internal IoT WiFi network), 157.158.56.57 (via the campus network);
    • Port: 5683 (UDP)
    • Endpoints:
      • GET method for
        coap://<ipaddress>/

        that brings you a secret code in the message's payload,

      • GET method for
        coap://<ipaddress>/hello

        that brings you a hello world welcome message in the payload.

SUT ESP32 Laboratory Scenarios

The remote access lab will not let you use the most common approach towards tracing, as you're physically away from the device and do not have access to, e.g. its serial port or debugger. For this reason, understanding actuators (mostly displays) is essential because the only way to monitor execution is to observe the results remotely via the video stream.
Note that video streaming has limitations, such as the number of frames per second, resolution, common use of many devices (dynamic video colours problem) and stream quality. That impacts how you write the software, e.g., using larger fonts and not changing display contents rapidly because you may be unable to observe those changes remotely.

* [Scenario Short Code]: [Scenario name] Laboratory scenario template. To be removed in the final version

Know the hardware
The following scenarios explain the use of hardware components and services that constitute the laboratory node. It is intended to seamlessly introduce users to IoT scenarios where using sensors and actuators is an intermediate step, and the main goal is to use networking and communication. Besides IoT, those scenarios can be utilised as a part of the Embedded Systems Modules.

Advanced techniques
In the following scenarios, we will focus on advanced programming techniques, such as asynchronous programming and timers.

IoT programming
In the following scenarios, you will write programs interacting with other devices, services, and networks, which are pure IoT applications.

EMB5: Using LCD Display

Alphanumerical LCD is one of the most popular output devices in the Embedded and IoT. Using LCD with predefined line organisation (here, 2 lines, 16 characters each) is as simple as sending a character's ASCII code to the device. This is so much simpler than in the case of the use of dot-matrix displays, where it is necessary to use fonts. The fixed organisation LCD has limits; here, only 32 characters can be presented to the user simultaneously. ASCII presents a limited set of characters, but many LCDs can redefine character maps (how each letter, digit or symbol looks). This way, it is possible to introduce graphics elements (i.e. frames), special symbols and letters.

In this scenario, you will learn how to handle easily LCD to present information and retrieve it visually with a webcam.

Prerequisites

Familiarise yourself with a hardware reference: this LCD is controlled with 6 GPIOs as presented in the “Table 1: ESP32-S3 SUT Node Hardware Details” on the hardware reference page.
You are going to use a library to handle the LCD. It means you need to add it to your platformio.ini file. Use the template provided in the hardware reference section and extend it with the library definition:

lib_deps = adafruit/Adafruit LiquidCrystal@^2.0.2

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Draw “Hello World” in the upper line of the LCD and “Hello IoT” in the lower one.

Start

Check if you can see a full LCD in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Steps

= Step 1 = Include the library in your source code:

#include <Adafruit_LiquidCrystal.h>

= Step 2 = Declare GPIOs controlling the LCD, according to the hardware reference:

#define LCD_RS 2
#define LCD_ENABLE 1
#define LCD_D4 39
#define LCD_D5 40
#define LCD_D6 41
#define LCD_D7 42

= Step 3 = Declare a static instance of the LCD controller class and preconfigure it with appropriate control GPIOs:

static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

= Step 4 = Initialise class with display area configuration (number of columns, here 16 and rows, here 2):

lcd.begin(16,2); 

= Step 5 = Implement your algorithm. The most common class methods that will help you are listed below:

  • .clear() - clears all content;
  • .setCursor(x,y) - set cursor, writing will start there;
  • .print(contents) - prints text in the cursor location; note there are many overloaded functions, accepting various arguments, including numerical.
Result validation

You should be able to see “Hello World” and “Hello IoT” on the LCD now.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

EMB6: Using ePaper display

VREL NExtGen laboratory node is equipped with b/w, ePaper module. It is a dot matrix display with a native resolution of 250×122 pixels. It has 64kB display memory and is controlled via SPI. The ePaper display presents data even if powered off, so don't be surprised that finishing your application does not automatically clean up the display, even if you use some other code later. To clean up the display, one has to clear the screen explicitly.

The ePaper display is slow and flashes several times during the update. It is also possible to update part of the screen only so that it speeds up displaying and involves more ghosting effects.

Prerequisites

Familiarise yourself with a hardware reference: this ePaper is controlled with 6 GPIOs as presented in the “Table 1: ESP32-S3 SUT Node Hardware Details” on the hardware reference page.
You are going to use a library to handle the ePaper drawing. It means you need to add it to your platformio.ini file. Use the template provided in the hardware reference section and extend it with the library definition:

lib_deps = zinggjm/GxEPD2@^1.5.0

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Present an image on the screen and overlay the text “Hello World” over it.

Start

Check if you can see a full ePaper Display in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Prepare a small bitmap (e.g. 60×60 pixels) and convert it to the byte array with b/w settings.
Sample project favicon you can use is present in Figure 9:

Figure 9: IOT-OPEN.EU Reloaded favicon 60px x 60px
Steps

Remember to include the source array in the code when drawing an image.
The corresponding generated C array for the logo in Figure 9 (horizontal 1 bit per pixel, as suitable for ePaper Display) is present below:

// 'logo 60', 60x60px
const unsigned char epd_bitmap_logo_60 [] PROGMEM = {
0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xf0, 
0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0x01, 0xff, 0xf8, 0x0f, 0xff, 0xf0, 
0xff, 0xfc, 0x0f, 0xff, 0xff, 0x03, 0xff, 0xf0, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xc1, 0xff, 0xf0, 
0xff, 0xe0, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xf0, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xf0, 
0xff, 0x87, 0xff, 0xf0, 0xff, 0xfe, 0x1f, 0xf0, 0xff, 0x0f, 0xfe, 0x00, 0x07, 0xff, 0x0f, 0xf0, 
0xfe, 0x1f, 0xf8, 0x7f, 0xe1, 0xff, 0x87, 0xf0, 0xfc, 0x3f, 0xe3, 0xff, 0xfc, 0x7f, 0xc3, 0xf0, 
0xfc, 0x7f, 0x8f, 0xff, 0xff, 0x1f, 0xe3, 0xf0, 0xf8, 0xff, 0x3f, 0xff, 0xff, 0xcf, 0xf1, 0xf0, 
0xf1, 0xfe, 0x7f, 0xff, 0xff, 0xe7, 0xf8, 0xf0, 0xf1, 0xfc, 0xff, 0xff, 0xff, 0xf3, 0xf8, 0xf0, 
0xe3, 0xf9, 0xff, 0xfc, 0x7f, 0xf9, 0xfc, 0x70, 0xe3, 0xf3, 0xff, 0xfc, 0x0f, 0xfc, 0xfc, 0x70, 
0xc7, 0xf7, 0xff, 0xff, 0xc3, 0xfe, 0xfe, 0x30, 0xc7, 0xe7, 0xff, 0xff, 0xf1, 0xfe, 0x7e, 0x30, 
0xcf, 0xef, 0xff, 0xff, 0xfc, 0xff, 0x7f, 0x30, 0x8f, 0xcf, 0xff, 0xff, 0xfe, 0x7f, 0x3f, 0x10, 
0x8f, 0xdf, 0xff, 0xff, 0xff, 0x3f, 0xbf, 0x10, 0x9f, 0x9f, 0xff, 0xff, 0xff, 0x3f, 0x9f, 0x90, 
0x9f, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0x9f, 0x90, 0x1f, 0xbf, 0xff, 0xff, 0xff, 0x9f, 0xdf, 0x80, 
0x1f, 0xbf, 0xff, 0xf9, 0xff, 0xdf, 0xdf, 0x80, 0x1f, 0xbf, 0xff, 0xe0, 0x7f, 0xcf, 0xdf, 0x80, 
0x1f, 0x3f, 0xff, 0xe0, 0x7f, 0xcf, 0xcf, 0x80, 0x1f, 0x3f, 0xff, 0xc0, 0x3f, 0xcf, 0xcf, 0x80, 
0x1f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xf0, 
0x1f, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xf0, 
0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf0, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf0, 
0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xf0, 0x8f, 0xff, 0xff, 0xf9, 0xfc, 0x03, 0xf0, 0x10, 
0x8f, 0xff, 0xff, 0xf9, 0xf8, 0x01, 0xe0, 0x10, 0xcf, 0xff, 0xff, 0xf9, 0xf0, 0xf0, 0xf9, 0xf0, 
0xc7, 0xff, 0xff, 0xf9, 0xf3, 0xfc, 0xf9, 0xf0, 0xc7, 0xff, 0xff, 0xf9, 0xe3, 0xfc, 0x79, 0xf0, 
0xe3, 0xff, 0xff, 0xf9, 0xe3, 0xfc, 0x79, 0xf0, 0xe3, 0xff, 0xff, 0xf9, 0xe3, 0xfc, 0x79, 0xf0, 
0xf1, 0xff, 0xff, 0xf9, 0xe3, 0xfc, 0x79, 0xf0, 0xf1, 0xff, 0xff, 0xf9, 0xf3, 0xfc, 0x79, 0xf0, 
0xf8, 0xff, 0xff, 0xf9, 0xf1, 0xf8, 0xf9, 0xf0, 0xfc, 0x7f, 0xff, 0xf9, 0xf8, 0x61, 0xf8, 0xc0, 
0xfc, 0x3f, 0xff, 0xf9, 0xfc, 0x03, 0xf8, 0x00, 0xfe, 0x1f, 0xff, 0xf9, 0xff, 0x0f, 0xfe, 0x10, 
0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 
0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 
0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 
0xff, 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 
0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00
};
 
// Total bytes used to store images in PROGMEM = 496
const int epd_bitmap_allArray_LEN = 1;
const unsigned char* epd_bitmap_allArray[1] = {
	epd_bitmap_logo_60
};

= Step 1 = Include necessary libraries.

#include <SPI.h>
#include <GxEPD2.h>
#include <GxEPD2_BW.h>
//Fonts
#include <Fonts/FreeMonoBold12pt7b.h>

The code above also includes a font to draw text on the ePaper Display. There are many fonts one can use, and a non-exhaustive list is present below (files are located in the Adafruit GFX Library, subfolder Fonts):

   FreeMono12pt7b.h
   FreeMono18pt7b.h
   FreeMono24pt7b.h
   FreeMono9pt7b.h
   FreeMonoBold12pt7b.h
   FreeMonoBold18pt7b.h
   FreeMonoBold24pt7b.h
   FreeMonoBold9pt7b.h
   FreeMonoBoldOblique12pt7b.h
   FreeMonoBoldOblique18pt7b.h
   FreeMonoBoldOblique24pt7b.h
   FreeMonoBoldOblique9pt7b.h
   FreeMonoOblique12pt7b.h
   FreeMonoOblique18pt7b.h
   FreeMonoOblique24pt7b.h
   FreeMonoOblique9pt7b.h
   FreeSans12pt7b.h
   FreeSans18pt7b.h
   FreeSans24pt7b.h
   FreeSans9pt7b.h
   FreeSansBold12pt7b.h
   FreeSansBold18pt7b.h
   FreeSansBold24pt7b.h
   FreeSansBold9pt7b.h
   FreeSansBoldOblique12pt7b.h
   FreeSansBoldOblique18pt7b.h
   FreeSansBoldOblique24pt7b.h
   FreeSansBoldOblique9pt7b.h
   FreeSansOblique12pt7b.h
   FreeSansOblique18pt7b.h
   FreeSansOblique24pt7b.h
   FreeSansOblique9pt7b.h
   FreeSerif12pt7b.h
   FreeSerif18pt7b.h
   FreeSerif24pt7b.h
   FreeSerif9pt7b.h
   FreeSerifBold12pt7b.h
   FreeSerifBold18pt7b.h
   FreeSerifBold24pt7b.h
   FreeSerifBold9pt7b.h
   FreeSerifBoldItalic12pt7b.h
   FreeSerifBoldItalic18pt7b.h
   FreeSerifBoldItalic24pt7b.h
   FreeSerifBoldItalic9pt7b.h
   FreeSerifItalic12pt7b.h
   FreeSerifItalic18pt7b.h
   FreeSerifItalic24pt7b.h
   FreeSerifItalic9pt7b.h

= Step 2 = Declare GPIOs and some configurations needed to handle the ePaper display properly:

#define GxEPD2_DRIVER_CLASS GxEPD2_213_BN
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define USE_HSPI_FOR_EPD
#define ENABLE_GxEPD2_GFX 0
 
#define SPI_SCLK_PIN 18
#define SPI_MOSI_PIN 15
 
#define EPAPER_SPI_DC_PIN 13
#define EPAPER_SPI_CS_PIN 10
#define EPAPER_SPI_RST_PIN 9
#define EPAPER_BUSY_PIN 8
 
#define SCREEN_WIDTH 250
#define SCREEN_HEIGHT 122
#define MAX_DISPLAY_BUFFER_SIZE 65536ul 
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))

= Step 3 = Declare hardware SPI controller and ePaper display controller:

static SPIClass hspi(HSPI);
 
static GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPAPER_SPI_CS_PIN, /*DC=*/ EPAPER_SPI_DC_PIN, /*RST=*/ EPAPER_SPI_RST_PIN, /*BUSY=*/ EPAPER_BUSY_PIN));

You can also declare a message to display as an array of characters:

static const char HelloWorld[] = "Hello IoT!";

= Step 4 = Initialise SPI and, on top of that, the ePaper controller class:

    hspi.begin(SPI_SCLK_PIN, -1, SPI_MOSI_PIN, -1);
    delay(100);
    pinMode(EPAPER_SPI_CS_PIN, OUTPUT);
    pinMode(EPAPER_SPI_RST_PIN, OUTPUT);
    pinMode(EPAPER_SPI_DC_PIN, OUTPUT);
    pinMode(EPAPER_BUSY_PIN,INPUT_PULLUP);
    delay(100);
    digitalWrite(EPAPER_SPI_CS_PIN,LOW);
    display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
    delay(100);
    display.init(115200);
    digitalWrite(EPAPER_SPI_CS_PIN,HIGH);    

= Step 5 = Set display rotation, font and text colour:

digitalWrite(EPAPER_SPI_CS_PIN,LOW);
display.setRotation(1);
display.setFont(&FreeMonoBold12pt7b);
display.setTextColor(GxEPD_BLACK);

then get the external dimensions of the string to be printed:

int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
 
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby;

= Step 6 = Then display contents of the image and the text in the ePaper display:

display.setFullWindow();
display.firstPage();
do
  {
    display.drawImage((uint8_t*)epd_bitmap_logo_60,0,0,60,60);
    display.setCursor(x, y);
    display.print(HelloWorld);
  }
while (display.nextPage());
digitalWrite(EPAPER_SPI_CS_PIN,HIGH);
Result validation

You should be able to see an image and a text on the ePaper Display.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

EMB7: Using OLED display

This scenario presents how to use the OLED display. Our OLED display is an RGB (16bit colour, 64k colours) 1.5in, 128×128 pixels. The OLED chip is SSD1351, and it is controlled over the SPI interface using the following pin configuration:

  • SPI_MOSI=15,
  • SPI_CLK=18,
  • SPI_DC=13,
  • SPI_CS=11,
  • SPI_RST=12.

Prerequisites

As usual, there is no need to program SPI directly; instead, it should be handled by a dedicated library. In addition to the protocol communication library and display library, we will use a graphic abstraction layer for drawing primitives such as lines, images, text, circles, and so on:

lib_deps = adafruit/Adafruit SSD1351 library@^1.2.8

Note that the graphics abstraction library (Adafruit GFX) is loaded automatically because of the

lib_ldf_mode = deep+

declaration in the platformio.ini. You can also add it explicitly, as below:

lib_deps = 
    adafruit/Adafruit SSD1351 library@^1.3.2
    adafruit/Adafruit GFX Library@^1.11.9

Suggested Readings and Knowledge Resources

To generate an array of bytes representing an image in 565 format, it is easiest to use an online tool, e.g.:

By default, this converter works for monochrome displays!
You need to change “Brightness / alpha threshold:” to “0” and “Draw mode:” to “Horizontal - 2 bytes per pixel (565)”.

Hands-on Lab Scenario

Task to be implemented

Draw a text on the OLED display and an image of your choice (small, to fit both text and image).

Start

Perhaps you will need to use an external tool to preprocess an image to the desired size (we suggest something no bigger than 100×100 pixels) and another tool (see hint above) to convert an image to an array of bytes.

Note that when using a conversion tool, the conversion should be done for the 64k colour (16bit) model, not RGB.
The 16-bit model is referenced as “2-bytes per pixel” or so-called “565”.

Check if you can see a full OLED Display in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Prepare a small bitmap and convert it to the byte array for 16-bit colour settings.
Sample project favicon you can use is present in Figure 9:

Figure 11: IOT-OPEN.EU Reloaded favicon 60px x 60px
Steps

Remember to include the source array in the code when drawing an image. The corresponding generated C array for the logo in Figure 11 is too extensive to present here in the textual form, so below it is just the first couple of pixels represented in the array, and full contents you can download here: ZIPed archive with a C file containing all pixel data of the image .

const uint16_t epd_bitmap_logo_60 [] PROGMEM = {
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf7be, 0xbdd7, 0x8430, 0x5aeb, 0x39c7, 0x2104, 0x1082, 0x0020, 0x0020, 0x1082, 
	0x2104, 0x39c7, 0x5aeb, 0x8430, 0xbdd7, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
....
 
....
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
 
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 3616)
const int epd_bitmap_allArray_LEN = 1;
const uint16_t* epd_bitmap_allArray[1] = {
	epd_bitmap_logo_60
};

= Step 1 = Include necessary libraries:

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1351.h>
#include <SPI.h>
//Fonts
#include <Fonts/FreeMono9pt7b.h>

The code above also includes a font to draw text on the OLED Display. There are many fonts one can use, and a non-exhaustive list is present below (files are located in the Adafruit GFX Library, subfolder Fonts):

   FreeMono12pt7b.h
   FreeMono18pt7b.h
   FreeMono24pt7b.h
   FreeMono9pt7b.h
   FreeMonoBold12pt7b.h
   FreeMonoBold18pt7b.h
   FreeMonoBold24pt7b.h
   FreeMonoBold9pt7b.h
   FreeMonoBoldOblique12pt7b.h
   FreeMonoBoldOblique18pt7b.h
   FreeMonoBoldOblique24pt7b.h
   FreeMonoBoldOblique9pt7b.h
   FreeMonoOblique12pt7b.h
   FreeMonoOblique18pt7b.h
   FreeMonoOblique24pt7b.h
   FreeMonoOblique9pt7b.h
   FreeSans12pt7b.h
   FreeSans18pt7b.h
   FreeSans24pt7b.h
   FreeSans9pt7b.h
   FreeSansBold12pt7b.h
   FreeSansBold18pt7b.h
   FreeSansBold24pt7b.h
   FreeSansBold9pt7b.h
   FreeSansBoldOblique12pt7b.h
   FreeSansBoldOblique18pt7b.h
   FreeSansBoldOblique24pt7b.h
   FreeSansBoldOblique9pt7b.h
   FreeSansOblique12pt7b.h
   FreeSansOblique18pt7b.h
   FreeSansOblique24pt7b.h
   FreeSansOblique9pt7b.h
   FreeSerif12pt7b.h
   FreeSerif18pt7b.h
   FreeSerif24pt7b.h
   FreeSerif9pt7b.h
   FreeSerifBold12pt7b.h
   FreeSerifBold18pt7b.h
   FreeSerifBold24pt7b.h
   FreeSerifBold9pt7b.h
   FreeSerifBoldItalic12pt7b.h
   FreeSerifBoldItalic18pt7b.h
   FreeSerifBoldItalic24pt7b.h
   FreeSerifBoldItalic9pt7b.h
   FreeSerifItalic12pt7b.h
   FreeSerifItalic18pt7b.h
   FreeSerifItalic24pt7b.h
   FreeSerifItalic9pt7b.h

= Step 2 = Add declarations for GPIOs, colours (to ease programming and use names instead of hexadecimal values) and screen height and width. To recall, the OLED display in our lab is square: 128×128 pixels, 16k colours (16-bit 565: RRRRRGGGGGGBBBBB colour model):

//Test configuration of the SPI
#define OLED_SPI_MOSI_PIN 15  //DIN
#define OLED_SPI_SCLK_PIN 18  //CLK
#define OLED_SPI_CS_PIN 11 
#define OLED_SPI_DC_PIN 13
#define OLED_SPI_RST_PIN 12
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 128
 
// Color definitions
#define	BLACK           0x0000
#define	BLUE            0x001F
#define	RED             0xF800
#define	GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0  
#define WHITE           0xFFFF

= Step 3 = Declare an SPI communication and OLED controller objects:

static SPIClass hspi(HSPI);
static Adafruit_SSD1351 tft = Adafruit_SSD1351(SCREEN_WIDTH, SCREEN_HEIGHT, &hspi, OLED_SPI_CS_PIN, OLED_SPI_DC_PIN, OLED_SPI_RST_PIN);

= Step 4 = Initialise the SPI communication object and the OLED controller object. Then clear the screen (write all black):

   pinMode(OLED_SPI_CS_PIN, OUTPUT);
   hspi.begin(OLED_SPI_SCLK_PIN, -1, OLED_SPI_MOSI_PIN, -1);
   delay(50);
   digitalWrite(OLED_SPI_CS_PIN,LOW);
   tft.begin();
   delay(100);
   tft.fillScreen(BLACK);

= Step 5 = Draw a bitmap around the centre part of the screen (screen is 128x128px); please mind that OLED_SPI_CS_PIN must be LOW (OLED SPI device controller selected) before executing the following code:

  tft.drawRGBBitmap(48,48, epd_bitmap_logo_60, 60, 60);

= Step 6 = Drop some additional text on the screen:

  tft.setFont(&FreeMono9pt7b);
  tft.setTextSize(1);
  tft.setTextColor(WHITE);
  tft.setCursor(0,10);
  tft.println("Hello IoT");

Some remarks regarding coordinates:

  • setFont sets the base font later used for printing. The font size is given in the font name, so in the case of the FreeMono9pt7b, the base font size is 9 pixels vertically,
  • setTextSize sets a relative font scaling; assuming the base font is 9 pixels, setTextSize(2) will scale it up to 200% (18 pixels); there is no fractal calling here :(,
  • setTextColor controls the colour of the text: as we have a black screen (fillScreen(BLACK)), we will use white here, but any other colour is valid,
  • setCursor(X,Y) sets the text location; note the upper-left corner is 0.0, but that relates to the lower-left corner of the first letter. So, to write in the first line, you need to offset it down (Y-coordinate) by at least font size (relative, also regarding text size calling, if any).
To speed up screen updating and avoid flickering, you may use a trick to clear the afore-written text: instead of clearing the whole or partial screen, write the same text in the same location but in the background colour.
Using println(…) to print the text is very handy as once executed, setCursor is automatically called to set the cursor in the next line so you can continue printing in a new line without a need to set the cursor's position explicitly. Use print(…) to continue printing in the current line.

Besides the functions presented above, the controller class has several other handy functions (among others):

  • drawPixel(x,y, colour) draws a pixel in x,y coordinates of the colour colour,
  • drawCircle(x,y, radius, colour) draws a circle in x,y coordinates with colour colour and specified radius (in pixels),
  • drawLine(x1,y1, x2,y2, colour) draws a line starting from x1,y1 and finished in x2,y2 with given colour - to draw straight (horizontal or vertical) lines there is a faster option:
    • drawFastHLine(x,y, w, colour) draws horizontal line that starts from x,y and of the length w with given colour,
    • drawFastVLine(x,y, h, colour) draws vertical line that starts from x,y and of the length h with given colour,
  • drawRect(x,y, w,h, colour) draws a rectange starting in x,y of the width and height w and h and with given colour (no fill),
  • drawTriangle(x1,y1, x2,y2, x3,y3, colour) draws a triangle using 3 vertexes and of given colour (no fill),
Result validation

You should see the image and the text in the video stream.

FAQ

The screen is black even if I write to it. What to do?: Check if you have initialised an SPI communication object and pulled the “chip select” GPIO down to LOW before drawing. Follow the code example in this manual: it does work!

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

EMB8: Controlling Smart LED stripe

A Smart LED stripe (also referenced as Digital LED or NEOPIXEL) is a chain of connected LEDs, commonly RGB, but other combinations such as RGBWW (Red+Green+Blue+Warm White+Cold White) or WWA (Warm White+Cold White+Amber) exist. They are controlled with just one pin/GPIO. GPIO drives a first LED in a chain and the LED relays configuration to the next one, and so on.
The most common type of LED stripes is WS2812B (RGB). Initially LED Stripes were powered with 5V, but that limits the length of the chain up to some 1-2m (because of the voltage drop), so nowadays LED stripes powered with 12V and even 24V are getting more and more popular.

In this scenario you will learn how to control a small LED RGB Stripe, composed of 8 Smart (Digital) LEDs.

Prerequisites

Familiarise yourself with a hardware reference: this LED Stripe is controlled with a single GPIO (GPIO 34), as presented in the “Table 1: ESP32-S3 SUT Node Hardware Details” on the hardware reference page. To control a WS2812B LED stipe, we will use a library:

lib_deps = freenove/Freenove WS2812 Lib for ESP32@^1.0.5
Note, this library can also control other RGB LED stripes than WS2812B.

There are at least two ways (algorithms) to implement this task:

  • using dummy blocking (delay(…);) calls in the void loop(); and,
  • using a timer (see advanced scenario below).

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Implement a rainbow of colours flowing through the LED Stripe.

Note, do not change colours too fast, as you won't be able to observe changes via the video stream. An update once per second is usually suitable.
Start

When booking a device, ensure the LED stripe is visible in the camera.

Due to the low camera dynamics, do not use the full brightness of the LEDs: off is equivalent to 0, and the full brightness of the colour is 255 (8-bit resolution, per colour). We suggest to use up to 60 or max 100.
Because of the camera and human eye characteristics, the same numerical brightness set for one colour can bring a much different brightness experience on another, so setting up 60 to channel Red will have a different overall experienced brightness than setting 60 to channel Blue or Green.
Steps

Below, we provide a sample colour configuration that does not reflect the desired effect that should result from the exercise. It is just for instructional purposes. You need to invent how to implement a rainbow effect yourself. = Step 1 = Include necessary library:

#include "Freenove_WS2812_Lib_for_ESP32.h"

= Step 2 = Declare configuration and a controller class:

#define WLEDS_COUNT    8
#define WLEDS_PIN     34
#define WLEDS_CHANNEL  0
 
static Freenove_ESP32_WS2812 stripe = Freenove_ESP32_WS2812(WLEDS_COUNT, WLEDS_PIN, WLEDS_CHANNEL, TYPE_GRB);

= Step 3 = To switch a particular LED, use the following function:

  stripe.setLedColorData(1,60,0,0); //light Red of the 2nd LED
  stripe.show(); //Writes colours to the LED stripe

Parameters are: setLedColorData(int index, u8 r, u8 g, u8 b);.

Note that the index is 0-based, so in the case of our device, valid indexes are 0…7.

If you want to set all LEDs in the stripe to the same colour, there is a handy function: setAllLedsColorData(u8 r, u8 g, u8 b);.
Remember to use the show(); function afterwards.

Result validation

Observe the flow of the colours via the stripe.

FAQ

I cannot see the colour via the video camera - everything looks white…: Try to lower the LED's brightness.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE —

EMB10: Controlling standard servo

You will learn how to control a standard miniature servo in this scenario. Standard miniature, so-called “analogue” servo is controlled with a PWM signal of 50Hz with a duty cycle between 1 ms (rotate to 0) and 2 ms (rotate to 180 degrees), where 1.5 ms corresponds to 90 degrees.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!

A servo has a red arrow presenting the gauge's current position.

Note that servos tend to have relatively high implementation inaccuracy. Moreover, as the arrow is not in the centre of view of the camera but instead to the corner, the reading may be subject to error because of the parallaxes and lens distortion.

The servo is an actuator. It requires a time to operate. So, you should give it time to operate between consecutive changes of the control PWM signal (requests to change its position). Moreover, because of the observation via camera, too quick rotation may not be observable at all depending on the video stream fps. A gap of 2s between consecutive rotations is usually a reasonable choice.

Prerequisites

To ease servo control, instead of use of ledc we will use a dedicated library:

ib_deps = dlloydev/ESP32 ESP32S2 AnalogWrite@^5.0.2

This library requires minimum setup but, on the other hand, supports, i.e. fine-tuning of the minimum and maximum duty cycle as some servos tend to go beyond 1ms and above 2ms to achieve a full 180-degree rotation range. It is usually provided in the technical documentation accompanying the servo.

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Rotate the servo to the following angles: 0, 90, 180, 135, 45 and back to 0 degrees.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!
Start

Check if the servo is in the camera view. The servo is controlled with GPIO 37.

Steps

Write your application all in the setup() function, leaving loop() empty.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!

= Step 1 = Include servo control library, specific for ESP32 and declare GPIO, minimum and maximum duty cycle values:

#include <Servo.h>
#define SRV_PIN 37

MG 90 servos that we use in our lab are specific. As mentioned above, to achieve a full 180-degree rotation range, their minimum and maximum duty cycle timings go far beyond standards. Here, we declare minimum and maximum values for the duty cycle (in microseconds) and a PWM control channel (2):

#define PWMSRV_Ch     2
#define srv_min_us  550
#define srv_max_us 2400

= Step 2 = Define a servo controller object:

static Servo srv;

= Step 3 = Initialise parameters (duty cycle, channel, GPIO):

srv.attach(SRV_PIN, PWMSRV_Ch,srv_min_us, srv_max_us);

50Hz frequency is standard, so we do not need to configure it.

= Step 4 = Rotating a servo is as easy as writing the desired angle to the controller class, e.g.:

srv.write(SRV_PIN,180);
delay(2000);

FAQ

How do I know minimum and maximum values for the timings for servo operation?: Those parameters are provided along with servo technical documentation, so you should refer to them. Our configuration reflects the servos we use (MG90/SG90), and as you can see, it goes far beyond the standard servo rotation control that is a minimum of 1000us and a maximum of 2000us. Using standard configuration, your servo won't rotate at full 180 degrees but at a shorter rotation range.

Result validation

Observe the red arrow to rotate accordingly. Remember to give the servo some time to operate.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE —

EMB2: Using a digital potentiometer

Digital potentiometer DS1803 is an I2C-controlled device that can digitally control the potentiometer.
Opposite to the physical potentiometers, there are no movable parts.
DS1803 has two digital potentiometers controlled independently. We use just one with the lower cardinal number (index 0). In our example, it is a 100k spread between GND and VCC, and its output is connected to the ADC (analogue to digital converter) input of the ESP32 MCU. This way, the potentiometer's wiper is controlled remotely via the I2C bus.
The device's I2C address is 0x28, and the ADC input GPIO pin is 7.
The digital potentiometer in our laboratory node forms then a loopback device: it can be set (also read) via I2C, and the resulting voltage can be measured on the separate PIN (ADC) 15. This way, it is possible, e.g. to draw a relation between the potentiometer setting and ADC readings to check whether it is linear or forms some other curve.

 Digital potentiometer DS1803 application in VREL Next Gen nodes
Figure 15: Digital potentiometer DS1803 application in VREL Next Gen nodes
The potentiometer has an 8-bit resolution, so the resistance step is 100k/256=~390 ohm.

Reading of the ADC is possible using the regular analogRead(pin) function.

In ESP32, ADC has by default a 12-bit resolution, so valid return values are 0…4095;

Prerequisites

To implement this scenario, it is advised to get familiar with at least one of the following scenarios first:

They enable you to present the data on the display (i.e. readings).

To handle communication with the DS1803 digital potentiometer, we use bare I2C programming. For this reason, we need to include only the I2C protocol library:

#include <Wire.h>
Also, remember to add the display handling library, as present in the scenarios. We suggest using OLED or ePaper to present the relation between the setting and reading as a graphic or even more than one display (e.g. LCD + OLED) to handle readings and the graph.

Below, we present a sample control library that you need to include in your code:

enum POT_LIST {POT_1 = 0xA9, POT_2=0xAA, POT_ALL=0xAF}; //We have only POT_1 connected
typedef enum POT_LIST POT_ID;
 
//Prototypes
void setPotentiometer(TwoWire&  I2CPipe, byte potValue, POT_ID potNumber);
byte readPotentiometer(TwoWire& I2CPipe, POT_ID potNumber);
 
//Implementation
void setPotentiometer(TwoWire& I2CPipe, byte potValue, POT_ID potNumber)
 {  
  I2CPipe.beginTransmission(DS1803_ADDRESS);
  I2CPipe.write(potNumber);
  I2CPipe.write(potValue);  
  I2CPipe.endTransmission(true);
 };
 
byte readPotentiometer(TwoWire& I2CPipe, POT_ID potNumber) //reads selected potentiometer
 {
  byte buffer[2];  
  I2CPipe.requestFrom(DS1803_ADDRESS,2);
  buffer[0]=I2CPipe.read();
  buffer[1]=I2CPipe.read();
  return (potNumber==POT_1?buffer[0]:buffer[1]);
 };
In the library above, the readPotentiometer(…) function returns a value previously set to the digital potentiometer, not an actual ADC voltage reading! It returns a set value by setPotentiometer(…), which is on the “digital” side of the DS1803 device. Actual ADC reading can be obtained using analogRead(pin).

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Iterate over the potentiometer settings, read related voltage readings via ADC, and present them in graphical form (as a plot). As the maximum resolution is 256, you can use a plot of 256 points or any other lower value covering all ranges. Present graph (plot) on either ePaper or OLED display, and while doing the readings, you should present data in the LCD (upper row for a set value, lower for a reading of the ADC).

Start

Check if you can see all the displays. Remember to use potentiometer 1 (index 0) because only this one is connected to the ADC input of the ESP32 MCU. In these steps, we present only how to handle communication with a digital potentiometer and how to read the ADC input of the MCU. Methods for displaying the measurements and plotting the graph are present in other scenarios. Remember to include the functions above in your code unless you want to integrate them with your solution.

Steps

Below, we assume that you have embedded functions handling operations on the digital potentiometer as defined above in your source file. Remember to add Wire.h include!

Note: Step 5 presents some stub code for displaying data on an OLED display.

= Step 1 = Define I2C bus GPIOs: clock (SCL) uses GPIO 4 and data (SDA) GPIO 5. ADC uses GPIO 7. Digital potentiometer chip DS1803 uses 0x28 I2C address. All definitions are present in the following code:

#define SCL 4
#define SDA 5
#define POT_ADC 7
#define DS1803_ADDRESS 0x28

= Step 2 = Declare an array of readings that fits an OLED display. Adjust for ePaper resolution (horizontal) if using it. OLED is 128×128 pixels:

static int16_t aGraphArray[128]; 

= Step 3 = Include functions present in the PREREQUISITES section. = Step 4 = Initialise the I2C bus and configure ADC's GPIO as input:

  Wire.begin(SDA,SCL);
  delay(100);
  pinMode(POT_ADC, INPUT);

= Step 4 = Read the loopback characteristics of the digital potentiometer to ADC loop and store it in the array:

for(byte i=0; i<128; i++)
  { 
    setPotentiometer(I2CPipe, 2*i, POT_1); 
    aGraphArray[i]=analogRead(POT_ADC);
  }

= Step 5 = Display on the OLED. Assume the following handler to the pointer to the display controller class:

SSD1306Wire& display

More information in the scenario EMB7: Using OLED display. Note, ADC measures in the 12-bit mode (we assume such configuration, adapt factor if using other sampling resolution), so values stored in an aGraphArray array are between 0 and 4095.

 float factor = 63./4095.;
 for(byte x=0;x<128;x++)
  {
    int16_t y=63-round(((float)aGraphArray[x])*factor);
    display.setPixel(x,y);
  }
  display.display();
Result validation

A relation between the potentiometer set value and ADC reading should be almost linear from 0V up to about 3V. It becomes horizontal because the ESP32 chip limits the ADC range to 3V, so going beyond 3V (and due to the electronic construction as in figure 15 it may go to about 3.3V) gives no further increase but rather a reading of the 4096 value (which means the input voltage is over the limit). For this reason, your plot may be finished suddenly with a horizontal instead of linearity decreasing function. It is by design. ADC input of the ESP32 can tolerate values between 3V and 3.3V. The linear correlation mentioned above is never perfect, either because of the devices' implementation imperfection (ESP32's ADC input and digital potentiometer output) or because of the electromagnetic noise. There are many devices in our lab room.

FAQ

The ADC readings are changing slightly, but I have not changed the potentiometer value. What is going on?: The ADC in ESP32 is quite noisy, mainly when using WiFi parallelly. Refer to the Coursebook and ESP32 documentation on how to increase measurement time that will make internally many readings and return to you an average. Use the analogSetCycles(cycles) function to increase the number of readings for the averaging algorithm. The default is 8, but you can increase it up to 255. Note that the higher the cycles parameter value, the longer the reading takes, so tune your main loop accordingly, particularly when using an asynchronous approach (timer-based). Eventually, you can implement low-pass filters yourself (in the software).

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

EMB3: Use of integrated temperature and humidity sensor

In this scenario, we will introduce a popular DHT11 sensor. The DHT series covers DHT11, DHT22, and AM2302. Those sensors differ in accuracy and physical dimensions but can all read environmental temperature and humidity. This scenario can be run stand-alone to read weather data in the laboratory nodes' room. The DHT11 sensor is controlled with one GPIO (in all our laboratory nodes, it is GPIO 47) and uses a proprietary protocol.

Prerequisites

Air temperature and humidity can be easily read using a dedicated library. Actually, you need to include two of them, as presented below:

  lib_deps = 
             adafruit/DHT sensor library@^1.4.6
	     adafruit/Adafruit Unified Sensor@^1.1.9

Sensor readings can be sent over the network or presented on one of the node's displays (e.g. LCD), so understanding how to handle at least one of the displays is essential:

It is also possible to present the temperature as the LED colour changes with a PWM-controlled LED or LED stripe. Their usage is described here:

A good understanding of the hardware timers is essential if you plan to use asynchronous programming (see note below). Consider getting familiar with the following:

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

In this scenario, we only focus on reading the sensor (temperature and humidity). Information on how to display measurements is part of other scenarios that you should refer to to create a fully functional solution (see links above).

Task to be implemented

Present the current temperature, and humidity on any display (e.g. LCD). Remember to add units (C, %Rh).

Start

A general check to see if you can see the chosen display in the camera field of view is necessary. No other actions are required before starting development.

Steps

The steps below present only interaction with the sensor. Those steps should be supplied to present the data (or send it over the network) using other scenarios accordingly. = Step 1 = Include the DHT library and related sensor library.

#include <Adafruit_Sensor.h>
#include <DHT.h>

= Step 2 = Declare type of the sensor and GPIO pin:

#define DHTTYPE DHT11   // DHT 11
#define DHTPIN 47

= Step 3 = Declare controller class and variables to store data:

static DHT dht(DHTPIN, DHTTYPE,50);
static float hum = 0;
static float temp = 0;
static boolean isDHTOk = false;

= Step 4 = Initialise sensor (mind the delay(100); after initialisation as the DHT11 sensor requires some time to initialise before one can read the temperature and humidity:

  dht.begin();
  delay(100);

= Step 5 = Reat the data and check whether the sensor works OK. In the case of the DHT sensor and its controller class, we check the correctness of the readings once the reading is finished. The sensor does not return any status but checks if the reading is okay. This can be done by comparing the readings with the NaN (not a number) value, each separately:

   hum = dht.readHumidity();
   temp = dht.readTemperature();
   (isnan(hum) || isnan(temp))?isDHTOk = false:isDHTOk = true;
Do not read the sensor too frequently! Doing so will cause lots of NaN numbers. Please give it some 250ms, at least, between consecutive readings, whether you do it asynchronously or using a blocking call of delay(250); in the loop.
Result validation

The observed temperature is usually between 19 and 24C, with humidity about 40-70%, depending on the weather. On rainy days, it can even go higher.

If you ever notice the temperature going beyond 25C, please drop a note to the administrators: it means that our air conditioning is faulty and needs maintenance ;-).

FAQ

I've got NaN (Not a Number) readings. What to do?: Check if GPIO is OK (should be D22), check if you initialised controller class and most of all, give the sensor some recovery time (at least 250ms) between consecutive readings.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

EMB4: 1-Wire Temperature Sensor

The temperature-only sensor DS18B20 uses a 1-wire protocol. “1-wire” applies only to the bidirectional bus; power and GND are on separate pins. The sensor is connected to the MCU using GPIO 6 only. Many devices can be connected on a single 1-wire bus, each with a unique ID. DS18B20 also has a water-proof metal enclosure version (but here, in our lab, we use a plastic one) that enables easy monitoring of the liquid's temperature.

Prerequisites

To handle operations with DS18B20, we will use a dedicated library that uses a 1-wire library on a low level:

  lib_deps = 
             milesburton/DallasTemperature@^3.11.0

Sensor readings can be sent over the network or presented on one of the node's displays (e.g. LCD), so understanding how to handle at least one of the displays is essential:

A good understanding of the hardware timers is essential if you plan to use asynchronous programming (see note below). Consider getting familiar with the following:

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

In this scenario, we present how to interface the 1-wire sensor, DS18B20 (temperature sensor).

Task to be implemented

Read the temperature from the sensor and present it on the display of your choice. Show the reading in C. Note that the scenario below presents only how to use the DS18B20 sensor. How to display the data is present in other scenarios, as listed above. We suggest using an LCD (scenario EMB5: Using LCD Display).

Update reading every 10s. Too frequent readings may cause incorrect readings or faulty communication with the sensor. Remember, the remote video channel has its limits, even if the sensor can be read much more frequently.

Start

Check if your display of choice is visible in the FOV of the camera once the device is booked.

Steps

The steps below present only interaction with the sensor. Those steps should be supplied to present the data (or send it over the network) using other scenarios accordingly. = Step 1 = Include Dallas sensor library and 1-Wire protocol implementation library:

#include <OneWire.h>
#include <DallasTemperature.h>
Also, remember to add the LCD handling library, as present in the EMB9 scenario, unless you decide to use another output device.

= Step 2 = Declare the 1-Wire GPIO bus pin, 1-Wire communication handling object, sensor proxy and a variable to store readings:

#define ONE_WIRE_BUS 6
static OneWire oneWire(ONE_WIRE_BUS);
static DallasTemperature sensors(&oneWire);
static float tempDS;
Note, the sensors class represents a list of all sensors available on the 1-Wire bus. Obviously, there is just a single one in each of our laboratory nodes.

= Step 3 = Initialise sensors' proxy class:

  sensors.begin();

= Step 4 = [pczekalski][✓ ktokarz, 2024-04-22] check if you don't need to call sensors.requestTemperatures(); before the code below Read the data:

  if (sensors.getDeviceCount()>0)
  {
    tempDS = sensors.getTempCByIndex(0);
  }
  else
  {
    // Sensors not present (broken?) or 1-wire bus error
  }

Remember not to read the sensor too frequently. 10s between consecutive readings is just fine.
Devices in the 1-Wire bus are addressable either by index (as in the example above) or by their address.
Some useful functions are present below:

  • DallasTemperature::toFahrenheit(tempDS) - converts temperature in C to F,
  • sensors.getAddress(sensorAddress, index) - reads device address given by uint8_t index and stores it in DeviceAddress sensorAddress.

The library can make non-blocking calls, which can also be implemented using timers, as presented in the scenario ADV1: Using timers to execute code asynchronously.

Result validation

The observable temperature is usually within the range of 19-24C. If you find the temperature much higher, check your code, and if that is okay, please contact our administrator to inform us about the faulty AC.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

ADV1: Using timers to execute code asynchronously

It is advised to use timers that periodically execute a function to handle repeating tasks. Hardware timers work parallel to the CPU and consume few CPU resources. ESP32-S3 has 4 hardware timers, but each timer can execute multiple handlers. You can think about these handlers as they are interrupted handling functions, but instead of externally triggered interrupts, those are initiated internally by the hardware timer.
The idea of using the timer is to encapsulate a piece of compact code that can be run virtually asynchronously and executed is a precisely-defined time manner. In this scenario, we use a timer to update the LCD screen periodically. We choose a dummy example where Timer 1 is used to increment a value of the byte type, and Timer 2 reads this value and writes it on the LCD. Naturally, a collision may occur whenever two processes are to access a single memory block (variable). It is critical when both processes (tasks, asynchronous functions) are writing to it. However, in our example, the handler executed by Timer 1 only writes to the variable, while Timer 2 only reads from it. In this scenario, there is no need to use mutexes (semaphores). A scenario with a detailed description of when to use mutexes is beyond the scope of this example.

Prerequisites

To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first:

A standard LCD handling library is attached to the platformio.ini, and it is the only library needed. Timers are integrated into the Arduino framework for ESP32.

lib_deps = adafruit/Adafruit LiquidCrystal@^2.0.2 

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Present on the LCD current value of the byte variable; update the LCD every 3 seconds using a timer. Use another timer to increase this variable every 1 second.

Start

Check LCD visibility in the camera FOV. You may also disable LED if it interferes with the camera video stream, as described in the scenario EMB9A: Use of RGB LEDs.

Steps

We used to separate tasks, but for this case, complete code is provided in chunks, including LCD handling. It presents relations on where particular parts of the code should be located when using timers and how timing relates between components.

It is important, e.g. when LCD had to be set up and configured before asynchronous handlers execute writing to it, so set up order is meaningful!

= Step 1 = Include libraries:

#include <Arduino.h>
#include <Adafruit_LiquidCrystal.h>

= Step 2 = Define LCD configuration pins (see details and explanation in the scenario: EMB5: Using LCD Display):

#define LCD_RS 2
#define LCD_ENABLE 1
#define LCD_D4 39
#define LCD_D5 40
#define LCD_D6 41
#define LCD_D7 42

= Step 3 = Declare variables and classes (timers):

volatile byte i = 0;
hw_timer_t *Timer1 = NULL;
hw_timer_t *Timer2 = NULL;
static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

Above, we declare two separate timers: Timer1 and Timer2 as pointers. They are initialised later in the setup code.

= Step 4 = Declare constants that define how frequently timers will be calling handlers:

const int baseFrequency = 80; //MHz
const int interval1 = 1000000;  // Interval in microseconds = 1s
const int interval2 = 3000000;  // Interval in microseconds = 3s

The base frequency for the timers in ESP32 is 80 MHz. Each timer counts in microseconds, so we define 2 intervals: 1s (1000000us) for the counter increase and 3s (3000000us) for the LCD update routine.

= Step 5 = Declare and implement functions that are timer handles: timer calls the bound function every execution period:

void IRAM_ATTR onTimer1() //handler for Timer1
{
    i++;
}
 
void IRAM_ATTR onTimer2() //handler for Timer2
{
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(i);
}
In no case should you use float variables when using asynchronous calls such as timer routines (handlers) and ISR (interrupt functions)! This is because of the limitation of the ESP32 where type float is hardware implemented in FPU: FPU cannot share its resources among cores, and timer routines may switch from one core to another during consecutive executions, so in that case, your application can hang and throw an exception. An exception won't be seen on the screen as you're remote, so you may notice only the laboratory node becoming unresponsive. This kind of error is usually tough to trace.
If you do need to use floating point calculations in ISR or timer functions, you can use double as this type is not hardware accelerated in ESP32s and is software implemented. Note, however, that there is a significant performance drop between float (faster) and double (slower) calculations.
Timer handlers are marked with IRAM_ATTR to be kept in RAM rather than flash. It is because of the performance. Interrupt and Timer functions should be kept as simple as possible. A watchdog exception can be thrown if more complex tasks are to be done and handler execution takes too long, particularly when the previous execution is not finished before the next one starts. This sort of problem may require fine-tuning the timer frequency and eventually changing the algorithm to set up only a flag in the handler that is later detected and handled in the loop() function.

= Step 6 = Configure timers, start LCD and enable timers. Note the correct order: LCD have to be ready when the timer calls LCD.write(…);:

void setup(){
  //Timer1 config
  Timer1 = timerBegin(0, baseFrequency, true);
  timerAttachInterrupt(Timer1, &onTimer1, true);
  timerAlarmWrite(Timer1, interval1, true);
 
  //Timer2 config
  Timer2 = timerBegin(1, baseFrequency, true);
  timerAttachInterrupt(Timer2, &onTimer2, true);
  timerAlarmWrite(Timer2, interval2, true);
 
  //start LCD
  lcd.begin(16,2);
  lcd.clear();
 
  //start both timers
  timerAlarmEnable(Timer1);
  timerAlarmEnable(Timer2);
}

In the code above, Timer1 = timerBegin(0, baseFrequency, true); creates a new timer bound to the hardware timer 0 (first, timers are zero-indexed). The last parameter causes the timer to count up (false counts down) internally, as every timer has its counter (here 64-bit).
Following, timerAttachInterrupt(Timer1, &onTimer1, true); attaches function onTimer1 (by its address, so we use & operator) to the Timer1 to be executed periodically.
Then we define how frequently the execution of the function above will occur: timerAlarmWrite(Timer1, interval1, true);. The last parameter causes the timer to reload after each execution automatically. Timers can also trigger an action only once (you need to set up the last parameter to false). In our example, we wish continuous work, so we use true.
Note that at this moment, timers are not executing the handlers yet; the last step is required: timerAlarmEnable(Timer1);. This step is separated from the other configuration steps because the LCD has to be initialised before timers can use the LCD.

= Step 7 = This way, a main loop is empty: everything runs asynchronously, thanks to the timers. As suggested above, when timer handlers require long or unpredictable execution time (e.g. external communication, waiting for the reply), handlers should set a flag that is read in the main loop to execute the appropriate task and then clear the flag.

void loop()
{
 
}
Result validation

On the LCD screen, you should see values starting from number 3, then 6, 9, 12 and so on (=3 every 3 seconds). Note, as byte has a capacity of 256 (0…255), the sequence changes in the following increments once it overflows.

FAQ

How many timers can I use?: ESP32-S3 has 4 hardware timers. You may trick this limitation by using smart handlers that have, e.g., an internal counter and internally execute every N-th cycle. This helps to simulate more timers in a software way.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE —

SUT STM32 Laboratory Node Hardware Reference

Introduction

Each laboratory node is equipped with an STM32WB55 chip. Several peripherals, networks and network services are available for the user. The UI is necessary to observe results in the camera when programming remotely. Thus, a proper understanding of UI programming is essential to successfully using the devices.

Note that each node has a unique ID built into the chip, as well as unique MAC addresses for the Bluetooth interface. As STM32WB55 does not support WiFi, every nide has an additional ESP32-C3 mini module which works as a WiFi modem connected with the serial interface.

Hardware reference

The table 2 lists all hardware components of the SUT's STM32WB55 node and hardware details such as connectivity, protocols, GPIOs, etc. Please note that some pins overlap because buses such as SPI and I2C are shared among multiple components.
The node is present in the figure 5 and reference numbers reflecting components in the table 2.

Figure 20: STM32WB55 Node
Table 2: STM32WB55 Node Hardware Details
Component ID Description Hardware model (controller) Control method GPIOs (as connected to the STM32WB55, Arduino naming [Nucleo naming]) Remarks
1A 12V PWM controlled fan Pe60251b1-000u-g99 PWM FAN_PWM = A2 [PA_1] Fan blows air into the pressure chamber (yellow container) to stimulate air pressure changes.
1B Pressure and environmental sensor BME 280 I2C, address 0x76 SDA=D14 [PB_9], SCL=D15 [PB_8] Spinning of the fan causes air to blow inside the yellow chamber and thus causes air pressure to change.
2 Digital potentiometer DS1803-100 I2C, address 0x28 SDA=D14 [PB_9], SCL=D15 [PB_8], analog input (A/D)=A4 [PC_3] Digital potententiometer's output is connected to the A/D input of the MCU.
3 Temperature and humidity sensor 1 DHT11 proprietary protocol, one GPIO control on GPIO=D22 [PB_2]
4 Temperature sensor 2 DS18B20 1-Wire 1-Wire interface on GPIO=D0 [PA_3]
5 2×16 LCD HD44780 Proprietary 4 bit control interface EN=D16 [PB_11], RS=A14 [PC5], D4=D29 [PB_12], D5=D34 [PB_13], D6=D33 [PB_14], D7=D32 [PB_15] 4-bit, simplified, one-directional (MCU→LCD) communication only
6 ePaper, B&W 2.13in, 250×122 pixels Pico-ePaper-2.13 SPI SPI_MOSI=D11 [PA_7], SPI_CLK=D13 [PA_5], SPI_DC=D4 [PC_10], EPAPER_CS=D1 [PA_2], EPAPER_RST=D5 [PA_15], EPAPER_BUSY=D7 [PC_13] Memory size is 64kB (65536ul)
7 OLED, RGB colourful 1.5in, 128×128 pixels SSD1351 SPI SPI_MOSI=D11 [PA_7], SPI_CLK=D13 [PA_5], SPI_DC=D4 [PC_10], OLED_CS=D2 [PC_6], OLED_RST=D10 [PA_4] 64k colours RGB (16bit)
8 RGB Smart LED stripe 8*WS2812B Proprietary protocol, one GPIO NEOPIXEL=D8 [PC_12]
9A RGB LED PWM controlled PWM LED_R=D9 [PA_9], LED_B=D6 [PA_8], LED_G=D3 [PA_10] Each colour can be independently controlled with PWM. The LED is integrated with another, illuminating the colour sensor (9A) so that controlling this RGB LED also directly impacts the other.
9B Light intensity and colour sensor TCS 34725 I2C address 0x29 SDA=D14 [PB_9], SCL=D15 [PB_8] The sensor is illuminated by RGB LED (9B)
10 Standard miniature servo SG90 or similar PWM SERVO_PWM=A3 [PA_0] Standard timings for micro servo: PWM 50Hz, duty cycle:
- 0 deg (right position): 1ms,
- 90 deg (up position): 1.5ms,
- 180 deg (left position): 2ms.

A suitable platformio.ini file for the correct code compilation is presented below. It does not contain libraries that need to be added regarding specific tasks and hardware used in particular scenarios. The code below presents only the typical section. Refer to the scenario description for details regarding case-specific libraries needed for the implementation:

platformio.ini
[env:nucleo_wb55rg_p]
platform = ststm32
framework = arduino
board = nucleo_wb55rg_p
lib_ldf_mode = deep+

[ktokarz][✓ ktokarz, 2024-04-21]Rozważ dodanie do platformio.ini sekcji lib_ldf_mode = deep+

STM32 Laboratory Scenarios

The remote access lab will not let you use the most common approach towards tracing, as you're physically away from the device and do not have access to, e.g. its serial port or debugger. For this reason, understanding actuators (mostly displays) is essential because the only way to monitor execution is to observe the results remotely via the video stream.
Note that video streaming has limitations, such as the number of frames per second, resolution, common use of many devices (dynamic video colours problem) and stream quality. That impacts how you write the software, e.g., using larger fonts and not changing display contents rapidly because you may be unable to observe those changes remotely.

Know the hardware
The following scenarios explain the use of hardware components and services that constitute the laboratory node. It is intended to seamlessly introduce users to IoT scenarios where using sensors and actuators is an intermediate step, and the main goal is to use networking and communication. Besides IoT, those scenarios can be utilised as a part of the Embedded Systems Modules.

IoT programming
In the following scenarios, you will write programs interacting with other devices, services, and networks, which are pure IoT applications.

[ktokarz][✓ ktokarz, 2024-03-29]Prośba - zamień URLe do EMB9A i EMB9B żeby URL odpowiadał treści/tematowi zadania
Advanced techniques
In the following scenarios, we will focus on advanced programming techniques, such as asynchronous programing and timers.

[ktokarz]Add IoT scenarios

STM_5: Using LCD Display

Alphanumerical LCD is one of the most popular output devices in the Embedded and IoT. Using LCD with predefined line organisation (here, 2 lines, 16 characters each) is as simple as sending a character's ASCII code to the device. This is so much simpler than in the case of the use of dot-matrix displays, where it is necessary to use fonts. The fixed organisation LCD has limits; here, only 32 characters can be presented to the user simultaneously. ASCII presents a limited set of characters, but many LCDs can redefine character maps (how each letter, digit or symbol looks). This way, it is possible to introduce graphics elements (i.e. frames), special symbols and letters.

In this scenario, you will learn how to handle easily LCD to present information and retrieve it visually with a webcam.

Prerequisites

Familiarise yourself with a hardware reference. LCD of this type can be connected to the microcontroller with 8 or 4 lines of data, RS - register select, R/#W - read/write, and EN - synchronisation line. In our lab equipment, the LCD is controlled with 6 GPIOs. We use 4 lines for data and because We don't read anything from the LCD the R/#W is connected to the ground. Details can be found in Table 1: STM32WB55 Node Hardware Details on the STM32 laboratory hardware reference page. You are going to use a library to handle the LCD. It means you need to add it to your platformio.ini file. Use the template provided in the hardware reference section and extend it with the library definition:

lib_deps = arduino-libraries/LiquidCrystal@^1.0.7

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Draw “Hello World” in the upper line of the LCD and “Hello IoT” in the lower one.

Start

Check if you can see a full LCD in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Steps

= Step 1 = Include the library in your source code:

#include <LiquidCrystal.h>

= Step 2 = Declare GPIOs controlling the LCD, according to the hardware reference:

const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15;

= Step 3 = Declare an instance of the LCD controller class and preconfigure it with appropriate control GPIOs:

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

= Step 4 = Initialise class with display area configuration (number of columns, here 16 and rows, here 2):

lcd.begin(16, 2);

= Step 5 = Implement your algorithm. The most common class methods that will help you are listed below:

  • .clear() - clears all content;
  • .setCursor(x,y) - set cursor, writing will start there;
  • .print(contents) - prints text in the cursor location; note there are many overloaded functions, accepting various arguments, including numerical.

A simple example can be the Hello World:

lcd.print("hello, world!");
Result validation

You should be able to see “Hello World” and “Hello IoT” on the LCD now.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

STM_6: Using ePaper display

VREL NExtGen laboratory node is equipped with b/w, ePaper module. It is a dot matrix display with a native resolution of 250×122 pixels. It has 64kB display memory and is controlled via SPI. The ePaper display presents data even if powered off, so don't be surprised that finishing your application does not automatically clean up the display, even if you use some other code later. To clean up the display, one has to clear the screen explicitly.

The ePaper display is slow and flashes several times during the update. It is also possible to update part of the screen only so that it speeds up displaying and involves more ghosting effects.

Prerequisites

Familiarise yourself with a hardware reference: this ePaper is controlled with 6 GPIOs as presented in the “Table 1: STM32WB55 Node Hardware Details” on the hardware reference page.
You are going to use a library to handle the ePaper drawing. It means you need to add it to your platformio.ini file. Use the template provided in the hardware reference section and extend it with the library definition:

lib_deps = zinggjm/GxEPD2@^1.5.0

Suggested Readings and Knowledge Resources

To generate an array of bytes representing an image, it is easiest to use an online tool, e.g.:

Hands-on Lab Scenario

Task to be implemented

Present an image on the screen and overlay the text “Hello World” over it.

Start

Check if you can see a full ePaper Display in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Prepare a small bitmap (e.g. 64×64 pixels) and convert it to the byte array with b/w settings.
Sample project favicon you can use is present in Figure 11:

Figure 22: IOT-OPEN.EU Reloaded favicon 64px x 64px
Steps

Remember to include the source array in the code when drawing an image.
The corresponding generated C array for the logo in Figure 22 (horizontal 1 bit per pixel, as suitable for ePaper Display) is present below:

// 'logo64', 64x64px
const unsigned char epd_bitmap_logo_64 [] PROGMEM = {
0xff, 0xff, 0xff, 0xf0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xff, 0xff, 
0xff, 0xff, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0x80, 0xff, 0xff, 
0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xfc, 0x1f, 0xff, 
0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xc3, 0xff, 
0xff, 0xf8, 0x7f, 0xff, 0xef, 0xff, 0xe1, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xe8, 0xff, 0xf0, 0xff, 
0xff, 0xe1, 0xff, 0xff, 0xfe, 0x5f, 0xf8, 0x7f, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xf7, 0xfc, 0x3f, 
0xff, 0xc7, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x3f, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xfe, 0xff, 0x1f, 
0xff, 0x1f, 0xff, 0xff, 0xff, 0xfe, 0xff, 0x8f, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x8f, 
0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xc7, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xc7, 
0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xe3, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xe3, 
0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf1, 
0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf1, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xf9, 
0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xf9, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf8, 
0xf1, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xfd, 0xf8, 0xf1, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xfd, 0xf8, 
0xf1, 0xff, 0xff, 0xfe, 0x07, 0xff, 0xfd, 0xf8, 0xf1, 0xfc, 0x00, 0x1c, 0x03, 0xff, 0xfc, 0xf8, 
0xf1, 0xfc, 0x00, 0x1c, 0x03, 0xfe, 0xfe, 0xf8, 0xf1, 0xff, 0xff, 0xfe, 0x07, 0xfe, 0xff, 0xf8, 
0xf1, 0xff, 0xff, 0xfe, 0x07, 0xfd, 0xfd, 0xf8, 0xf1, 0xff, 0xff, 0xff, 0x9f, 0xfd, 0xfd, 0xf8, 
0xf1, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfd, 0xf8, 0xf1, 0xff, 0xe1, 0xff, 0xff, 0xfb, 0xfd, 0xf9, 
0xf1, 0xff, 0x80, 0x7f, 0xff, 0xfb, 0xf9, 0xf9, 0xf1, 0xff, 0x00, 0x3f, 0xff, 0xf7, 0xfb, 0xf1, 
0xf1, 0xfe, 0x3f, 0x3f, 0xff, 0xef, 0xf3, 0xf1, 0xf1, 0xfe, 0x7f, 0x1f, 0xff, 0xcf, 0xff, 0xf3, 
0xf1, 0xfc, 0x7f, 0x9f, 0xff, 0x5f, 0xef, 0xe3, 0xf1, 0xfc, 0x7f, 0x9f, 0xfe, 0xbf, 0xcf, 0xe3, 
0xf1, 0xfc, 0xff, 0x9f, 0xe9, 0xff, 0xef, 0xc7, 0xf1, 0xfc, 0x7f, 0x9f, 0xef, 0xff, 0xbf, 0xc7, 
0xf1, 0xfe, 0x7f, 0x9f, 0xff, 0xff, 0x3f, 0x8f, 0xf1, 0xfe, 0x3f, 0x1f, 0xff, 0xfe, 0xff, 0x8f, 
0xf1, 0xff, 0x00, 0x3f, 0xff, 0xfe, 0xff, 0x1f, 0xf1, 0xff, 0x80, 0x7f, 0xff, 0xf9, 0xfe, 0x3f, 
0xf1, 0xff, 0xc1, 0xff, 0xff, 0xe7, 0xfc, 0x7f, 0xf1, 0xff, 0xff, 0xff, 0xfe, 0x9f, 0xf8, 0x7f, 
0xf1, 0xff, 0xff, 0xff, 0xe8, 0xff, 0xf0, 0xff, 0xf1, 0xff, 0xff, 0xbf, 0xef, 0xff, 0xe1, 0xff, 
0xf1, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xc3, 0xff, 0xf1, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0x07, 0xff, 
0xf1, 0xfe, 0x00, 0x03, 0xff, 0xfc, 0x1f, 0xff, 0xf1, 0xfc, 0x7f, 0x9f, 0xff, 0xf0, 0x3f, 0xff, 
0xf1, 0xfc, 0xff, 0x9f, 0xff, 0x80, 0xff, 0xff, 0xf1, 0xfc, 0xff, 0x9f, 0xc0, 0x03, 0xff, 0xff, 
0xf1, 0xfc, 0x7f, 0x9f, 0xc0, 0x1f, 0xff, 0xff, 0xf1, 0xfe, 0x7f, 0xff, 0xc0, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

= Step 1 = Include necessary libraries and the definition of the font.

// Epaper Display libraries
#include <GxEPD2.h>
#include <GxEPD2_BW.h>
// Fonts
#include <Fonts/FreeMonoBold12pt7b.h>

The code above includes a font to draw text on the ePaper Display. There are many fonts one can use, and a non-exhaustive list is present below (files are located in the Adafruit GFX Library, subfolder Fonts):

   FreeMono12pt7b.h
   FreeMono18pt7b.h
   FreeMono24pt7b.h
   FreeMono9pt7b.h
   FreeMonoBold12pt7b.h
   FreeMonoBold18pt7b.h
   FreeMonoBold24pt7b.h
   FreeMonoBold9pt7b.h
   FreeMonoBoldOblique12pt7b.h
   FreeMonoBoldOblique18pt7b.h
   FreeMonoBoldOblique24pt7b.h
   FreeMonoBoldOblique9pt7b.h
   FreeMonoOblique12pt7b.h
   FreeMonoOblique18pt7b.h
   FreeMonoOblique24pt7b.h
   FreeMonoOblique9pt7b.h
   FreeSans12pt7b.h
   FreeSans18pt7b.h
   FreeSans24pt7b.h
   FreeSans9pt7b.h
   FreeSansBold12pt7b.h
   FreeSansBold18pt7b.h
   FreeSansBold24pt7b.h
   FreeSansBold9pt7b.h
   FreeSansBoldOblique12pt7b.h
   FreeSansBoldOblique18pt7b.h
   FreeSansBoldOblique24pt7b.h
   FreeSansBoldOblique9pt7b.h
   FreeSansOblique12pt7b.h
   FreeSansOblique18pt7b.h
   FreeSansOblique24pt7b.h
   FreeSansOblique9pt7b.h
   FreeSerif12pt7b.h
   FreeSerif18pt7b.h
   FreeSerif24pt7b.h
   FreeSerif9pt7b.h
   FreeSerifBold12pt7b.h
   FreeSerifBold18pt7b.h
   FreeSerifBold24pt7b.h
   FreeSerifBold9pt7b.h
   FreeSerifBoldItalic12pt7b.h
   FreeSerifBoldItalic18pt7b.h
   FreeSerifBoldItalic24pt7b.h
   FreeSerifBoldItalic9pt7b.h
   FreeSerifItalic12pt7b.h
   FreeSerifItalic18pt7b.h
   FreeSerifItalic24pt7b.h
   FreeSerifItalic9pt7b.h

= Step 2 = Declare GPIOs and some configurations needed to handle the ePaper display properly:

// The epaper display's model is selected
#define GxEPD2_DRIVER_CLASS GxEPD2_213_BN 
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
 
// Definition of GPIOs except SPI pins
#define EPAPER_DC_PIN D4
#define EPAPER_CS_PIN D1
#define EPAPER_RST_PIN D5
#define EPAPER_BUSY_PIN D7
 
// Definition of the size of the buffer
#define MAX_DISPLAY_BUFFER_SIZE 65536ul 
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))

= Step 3 = Declare ePaper display controller:

static GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPAPER_CS_PIN, /*DC=*/ EPAPER_DC_PIN, /*RST=*/ EPAPER_RST_PIN, /*BUSY=*/ EPAPER_BUSY_PIN));

You can also declare a message to display as an array of characters:

static const char HelloWorldMsg[] = "Hello IoT World!";

= Step 4 = Configure GPIOs and initialise the ePaper controller class. It uses the standard SPI connection.

    pinMode(EPAPER_CS_PIN, OUTPUT);
    pinMode(EPAPER_RST_PIN, OUTPUT);
    pinMode(EPAPER_DC_PIN, OUTPUT);
    pinMode(EPAPER_BUSY_PIN,INPUT_PULLUP);
 
    digitalWrite(EPAPER_CS_PIN,LOW);
    display.epd2.selectSPI(SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
    \\ Delay here is to ensure a stable CS signal before SPI data sending
    delay(100);  
    display.init(115200);
    digitalWrite(EPAPER_CS_PIN,HIGH);    

= Step 5 = Set display rotation, font and text colour:

digitalWrite(EPAPER_SPI_CS_PIN,LOW);
display.setRotation(1);
display.setFont(&FreeMonoBold12pt7b);
display.setTextColor(GxEPD_BLACK);

then get the external dimensions of the string to be printed and calculate the starting point (x, y) for the centred text:

int16_t tbx, tby; uint16_t tbw, tbh;
display.getTextBounds(HelloWorldMsg, 0, 0, &tbx, &tby, &tbw, &tbh);
 
uint16_t x = ((display.width() - tbw) / 2) - tbx;  //center in x arrow
uint16_t y = ((display.height() - tbh) / 4) - tby; //one fourth from the top

= Step 6 = Then display the contents of the image and the text in the ePaper display:

display.setFullWindow();
display.fillScreen(GxEPD_WHITE);
display.setCursor(x, y);
display.print(HelloWorldMsg);
display.display(true);
display.drawImage((uint8_t*)epd_bitmap_logo_64,0,0,64,64);
digitalWrite(EPAPER_SPI_CS_PIN,HIGH);
Result validation

You should be able to see an image and a text on the ePaper Display.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

STM_7: Using OLED display

This scenario presents how to use the OLED display connected to the STM32WB55 SoC. Our OLED display is an RGB (16bit colour, 64k colours) 1.5in, 128×128 pixels. The OLED chip is SSD1351, and it is controlled over the SPI interface using the pin configuration as described in STM32 node Hardware Reference in Table 1 STM32WB55 Node Hardware Details.

Prerequisites

There is no need to program SPI because the display is connected to the hardware SPI directly, which is handled by a library built in the STMDuino. We will use an SSD1351 display library, and a graphic abstraction layer for drawing primitives such as lines, images, text, circles, and so on:

lib_deps =
  adafruit/Adafruit SSD1351 library@^1.3.2
  adafruit/Adafruit GFX Library@^1.11.9

Note that the graphics abstraction library (Adafruit GFX) can be loaded automatically if the

lib_ldf_mode = deep+

declaration in the platformio.ini is set.

Suggested Readings and Knowledge Resources

To generate an array of bytes representing an image in 565 format, it is easiest to use an online tool, e.g.:

By default, this converter works for monochrome displays!
You need to change “Brightness / alpha threshold:” to “0” and “Draw mode:” to “Horizontal - 2 bytes per pixel (565)”.

Hands-on Lab Scenario

Task to be implemented

Draw a text on the OLED display and an image of your choice (small, to fit both text and image).

Start

Perhaps you will need to use an external tool to preprocess an image to the desired size (we suggest something no bigger than 100×100 pixels) and another tool (see hint above) to convert an image to an array of bytes.

Note that when using a conversion tool, the conversion should be done for the 64k colour (16bit) model, not RGB.
The 16-bit model is referenced as “2-bytes per pixel” or so-called “565”.

Check if you can see a full OLED Display in your video stream. Book a device and create a dummy Arduino file with void setup()… and void loop()….

Prepare a small bitmap and convert it to the byte array for 16-bit colour settings.
Sample project favicon you can use is present in Figure 22:

Figure 24: IOT-OPEN.EU Reloaded favicon 64px x 64px
Steps

Remember to include the source array in the code when drawing an image. The corresponding generated C array for the logo in Figure 24 is too extensive to present here in the textual form, so below it is just the first couple of pixels represented in the array, and full contents you can download here: ZIPed archive with a file containing all pixel data of the image .

const uint16_t epd_bitmap_logo64 [] PROGMEM = {
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf7be, 0xbdd7, 0x8430, 0x5aeb, 0x39c7, 0x2104, 0x1082, 0x0020, 0x0020, 0x1082, 
	0x2104, 0x39c7, 0x5aeb, 0x8430, 0xbdd7, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
....
 
....
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

= Step 1 = Include necessary libraries:

// Libraries
#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1351.h>
 
// Fonts
#include <Fonts/FreeMono9pt7b.h>
 
// Here you can also include the file with the picture
#include "epd_bitmap_logo64.cpp"

The code above also includes a font to draw text on the OLED Display. There are many fonts one can use, and a non-exhaustive list is present below (files are located in the Adafruit GFX Library, subfolder Fonts):

   FreeMono12pt7b.h
   FreeMono18pt7b.h
   FreeMono24pt7b.h
   FreeMono9pt7b.h
   FreeMonoBold12pt7b.h
   FreeMonoBold18pt7b.h
   FreeMonoBold24pt7b.h
   FreeMonoBold9pt7b.h
   FreeMonoBoldOblique12pt7b.h
   FreeMonoBoldOblique18pt7b.h
   FreeMonoBoldOblique24pt7b.h
   FreeMonoBoldOblique9pt7b.h
   FreeMonoOblique12pt7b.h
   FreeMonoOblique18pt7b.h
   FreeMonoOblique24pt7b.h
   FreeMonoOblique9pt7b.h
   FreeSans12pt7b.h
   FreeSans18pt7b.h
   FreeSans24pt7b.h
   FreeSans9pt7b.h
   FreeSansBold12pt7b.h
   FreeSansBold18pt7b.h
   FreeSansBold24pt7b.h
   FreeSansBold9pt7b.h
   FreeSansBoldOblique12pt7b.h
   FreeSansBoldOblique18pt7b.h
   FreeSansBoldOblique24pt7b.h
   FreeSansBoldOblique9pt7b.h
   FreeSansOblique12pt7b.h
   FreeSansOblique18pt7b.h
   FreeSansOblique24pt7b.h
   FreeSansOblique9pt7b.h
   FreeSerif12pt7b.h
   FreeSerif18pt7b.h
   FreeSerif24pt7b.h
   FreeSerif9pt7b.h
   FreeSerifBold12pt7b.h
   FreeSerifBold18pt7b.h
   FreeSerifBold24pt7b.h
   FreeSerifBold9pt7b.h
   FreeSerifBoldItalic12pt7b.h
   FreeSerifBoldItalic18pt7b.h
   FreeSerifBoldItalic24pt7b.h
   FreeSerifBoldItalic9pt7b.h
   FreeSerifItalic12pt7b.h
   FreeSerifItalic18pt7b.h
   FreeSerifItalic24pt7b.h
   FreeSerifItalic9pt7b.h

= Step 2 = Add declarations for GPIOs, colours (to ease programming and use names instead of hexadecimal values) and screen height and width. To recall, the OLED display in our lab is square: 128×128 pixels, 16k colours (16-bit 565: RRRRRGGGGGGBBBBB colour model):

// Pins definition for OLED 
#define SCLK_PIN     D13 // It also works with STM numbering style PB_13
#define MOSI_PIN     D11 // It also works with STM numbering style PB_15
#define MISO_PIN     D12 // It also works with STM numbering style PB_14
#define OLED_DC_PIN  D4
#define OLED_CS_PIN  D2  // Doesn't work with STM numbering style
#define OLED_RST_PIN D10 //
 
// Colours definitions
#define	BLACK         0x0000
#define	BLUE          0x001F
#define	RED           0xF800
#define	GREEN         0x07E0
#define CYAN          0x07FF
#define MAGENTA       0xF81F
#define YELLOW        0xFFE0  
#define WHITE         0xFFFF
 
// Screen dimensions
#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 128

= Step 3 = Declare an OLED controller objects:

Adafruit_SSD1351 oled = Adafruit_SSD1351(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_CS_PIN, OLED_DC_PIN, OLED_RST_PIN);

= Step 4 = Initialise the OLED controller object. Then clear the screen (write all black):

  pinMode(OLED_CS_PIN, OUTPUT);
  pinMode(OLED_RST_PIN, OUTPUT);
  oled.begin();
  oled.fillRect(0, 0, 128, 128, BLACK);

= Step 5 = Draw a bitmap in the left top corner of the screen (screen is 128x128px). The OLED library handles the OLED_CS_PIN automatically.

  oled.drawRGBBitmap(0,0, epd_bitmap_logo64, 64, 64);

= Step 6 = Write some additional text in the middle of the screen:

  oled.setFont(&FreeMono9pt7b);
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(10,80);
  oled.println("Hello IoT");

Some remarks regarding coordinates:

  • setFont sets the base font later used for printing. The font size is given in the font name, so in the case of the FreeMono9pt7b, the base font size is 9 pixels vertically,
  • setTextSize sets a relative font scaling; assuming the base font is 9 pixels, setTextSize(2) will scale it up to 200% (18 pixels); there is no fractal calling here :(,
  • setTextColor controls the colour of the text: as we have a black screen (fillScreen(BLACK)), we will use white here, but any other colour is valid,
  • setCursor(X,Y) sets the text location; note the upper-left corner is 0.0, but that relates to the lower-left corner of the first letter. So, to write in the first line, you need to offset it down (Y-coordinate) by at least font size (relative, also regarding text size calling, if any).
To speed up screen updating and avoid flickering, you may use a trick to clear the afore-written text: instead of clearing the whole or partial screen, write the same text in the same location but in the background colour.
Using println(…) to print the text is very handy as once executed, setCursor is automatically called to set the cursor in the next line so you can continue printing in a new line without a need to set the cursor's position explicitly. Use print(…) to continue printing in the current line.

Besides the functions presented above, the controller class has several other handy functions (among others):

  • drawPixel(x,y, colour) draws a pixel in x,y coordinates of the colour colour,
  • drawCircle(x,y, radius, colour) draws a circle in x,y coordinates with colour colour and specified radius (in pixels),
  • drawLine(x1,y1, x2,y2, colour) draws a line starting from x1,y1 and finished in x2,y2 with given colour - to draw straight (horizontal or vertical) lines there is a faster option:
    • drawFastHLine(x,y, w, colour) draws horizontal line that starts from x,y and of the length w with given colour,
    • drawFastVLine(x,y, h, colour) draws vertical line that starts from x,y and of the length h with given colour,
  • drawRect(x,y, w,h, colour) draws a rectange starting in x,y of the width and height w and h and with given colour (no fill),
  • drawTriangle(x1,y1, x2,y2, x3,y3, colour) draws a triangle using 3 vertexes and of given colour (no fill),
Result validation

You should see the image and the text in the video stream.

FAQ

The screen is black even if I write to it. What to do?: Check if you have done all the steps shown in the example. Check if you used proper GPIOs to control the OLED display. Follow carefully the code example in this manual: it does work!

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

STM_8: Controlling Smart LED stripe

A Smart LED stripe is a chain of connected digital LEDs (also referenced as NEOPIXEL) which can be individually controlled. The stripe in our lab equipment consists of eight RGB LEDs. There exist also other colour configurations such as RGBWW (Red+Green+Blue+Warm White+Cold White) or WWA (Warm White+Cold White+Amber). They are controlled with just one pin/GPIO. GPIO sends the digital signal to the first LED in a chain and the LED passes data to the next one, and so on.
The most common type of LED stripes is WS2812B (RGB). Initially LED Stripes were powered with 5V, but that limits the length of the chain up to some 1-2m (because of the voltage drop), so nowadays LED stripes powered with 12V and even 24V are getting more and more popular.

In this scenario, you will learn how to control a small LED RGB Stripe, composed of 8 Smart (Digital) LEDs with STM32 SoC.

Sometimes such a chain of electronic elements is nicely called the Daisy Chain.

Prerequisites

Familiarise yourself with a hardware reference on the LED Stripe. It is controlled with a single GPIO (D8 in Arduino style naming, or PC_12 in Nucleo style naming), as presented in the “Table 1: STM32WB55 Node Hardware Details” on the hardware reference page. To control a WS2812B LED stipe, we will use a library:

adafruit/Adafruit NeoPixel@^1.12.0
Note, this library can also control other RGB LED stripes than WS2812B.

There are at least two ways (algorithms) to implement this task:

  • using the blocking (delay(…);) function calls in the void loop(); or,
  • using a timer (see advanced scenario).

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Implement a rainbow of colours flowing through the LED Stripe.

Note, do not change colours too fast, as you won't be able to observe changes via the video stream. An update once per second is usually suitable.
Start

When booking a device, ensure the LED stripe is visible in the camera.

Due to the low camera dynamics, do not use the full brightness of the LEDs: off is equivalent to 0, and the full brightness of the colour is 255 (8-bit resolution, per colour). We suggest to use up to 60 or max 100.
Because of the camera and human eye characteristics, the same numerical brightness set for one colour can bring a much different brightness experience on another, so setting up 60 to channel Red will have a different overall experienced brightness than setting 60 to channel Blue or Green.
Steps

Below, we provide a sample colour configuration that does not reflect the desired effect that should result from the exercise. It is just for instructional purposes. You need to invent how to implement a rainbow effect yourself. = Step 1 = Include necessary library:

#include "Adafruit_NeoPixel.h"

= Step 2 = Declare configuration and a controller class:

#define NEOPIXEL_PIN D8  //Arduino numbering D8, STM numbering PC_12
#define NUMPIXELS 8      //How many LEDs are attached
 
Adafruit_NeoPixel strip(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

The strip class definition contains the number of pixels, the pin for transmission, and the type of LEDs. In our laboratory LEDs use Green Red Blue format of data and 800kHz data transmission frequency. = Step 3 = To switch a particular LED, use the function setPixelColor();. It accepts parameters of different types.

void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
void setPixelColor(uint16_t n, uint32_t c);

If you use the first version you give the LED number as the first parameter and then three colour values for red, green and blue colour intensity as the number in the 0-255 range.
If you use the second version you give the LED number as the first parameter and a 32-bit version of the colour information. To convert three RGB bytes to 32-bit value you can use the function Color();:

static uint32_t Color(uint8_t r, uint8_t g, uint8_t b);
Note that the LED number is 0-based, so in the case of our laboratory equipment, valid indexes are 0…7.

The setPixelColor(); sets the appropriate data in the internal buffer of the class object. Sending it to the LED stripe requires the usage of the show(); function afterwards.
The different versions of the setPixelColor(); function can look like in the following code:

strip.setPixelColor(0, strip.Color(10, 0, 0)); // Red
strip.setPixelColor(1, 0, 10, 0);              // Green
strip.setPixelColor(2, 0, 0, 10);              // Blue
strip.setPixelColor(3, 0x000F0F0F);            // White
strip.show();                           // Update strip 
Result validation

Observe the flow of the colours via the stripe.

FAQ

I cannot see the colour via the video camera - everything looks white…: Try to lower the LED's brightness.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE —

STM_10: Controlling standard servo

You will learn how to control a standard miniature servo with the STM32 System on Chip. Standard miniature analogue servo is controlled with a PWM signal of frequency 50Hz with a duty cycle period between 1 ms (rotate to 0) and 2 ms (rotate to 180 degrees), where 1.5 ms corresponds to 90 degrees. Some servos have other duty cycle minimum and maximum values, always refer to the documentation.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!

A servo has a red arrow presenting the gauge's current position.

Note that servos tend to have relatively high implementation inaccuracy. Moreover, as the arrow is not in the centre of view of the camera but instead to the corner, the reading may be subject to error because of the parallaxes and lens distortion.

The servo is an example of a physical actuator. It requires some time to operate and change the position. Please give it time to set a new position between consecutive changes of the control PWM signal. Moreover, because of the observation via camera, too quick rotation may not be observable at all depending on the video stream fps. A gap of 2s between consecutive rotations is usually a reasonable choice.

Prerequisites

A good understanding of the PWM signal and duty cycle is necessary. In this scenario, we use built-in timers to control the PWM hardware channels of the STM32WB55 chip. In this case, we do not use any external library; instead, we use the hardware timer library built in the Arduino Core STM32 framework for STM32WB55 (stm32duino)[2] so that no additional external libraries will be included in the project.
Some servos tend to go below 1 ms and above 2 ms to achieve a full 180-degree rotation range. For example, the servo in our laboratory (MG90/SG90) accepts values starting from 500 ms, and ending at 2500 ms. If there is a need for a high accuracy of rotation, it is possible to fine-tune the minimum and maximum duty cycle values.

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Rotate the servo to the following angles: 0, 90, 180, 135, 45 and back to 0 degrees.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!
Start

In the laboratory equipment, the servo and the fan are connected to the same hardware timer instance. It means that both elements use the same base frequency of the PWM signal. If you use a fan and servo in the same project the frequency of the PMW signal needs to match the servo requirements.
The hardware timer library implements functions which allow us to control the duty cycle of the PWM signal and express it in different formats, including percentages. In the case of setting the PWM duty cycle expressed in percentages, the minimum value is 0, and the maximum is 100. We can also express the duty cycle in microseconds which is easier to calculate for the servo.

Steps

Write your application all in the setup() function, leaving loop() empty.

Do not keep the servo rotating when leaving the lab. Implement your code as non-repeating. A servo that remains rotating for a long time may easily wear out its gears, overheat and even burn!

= Step 1 = Include Arduino and timer libraries, specific to STM32. The servo is controlled with GPIO PA_10 (the PA_10 name is the STM Nucleo naming convention). Define the pin name constant.

#include "Arduino.h"
#include <HardwareTimer.h>
#define SERVO_PWM_PIN PA0

We will also need some variables:

// PWM variables definitions
TIM_TypeDef *SRVInstance;        //General hardware instance
uint32_t channelSRV;             //2 channel for the servo
HardwareTimer *MyTimServo;       //Hardware Timer class for Servo PWM

= Step 2 = The hardware timer library uses internal timer modules and allows us to define channels attached to the timer. Channels represent the output pins connected to the hardware elements. Our laboratory board uses the same timer to control the servo and fan. In this example, we will use one channel for the servo only setting the proper PWM frequency of the timer. The channel connected to the servo controls the PWM duty cycle.

Create the timer instance type based on the servo pin:

TIM_TypeDef *SRVInstance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(SERVO_PWM_PIN), PinMap_PWM);
The same timer is used for the fan in the scenario STM_1A.

Define the channel for servo:

channelSRV = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(SERVO_PWM_PIN), PinMap_PWM));

Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.

MyTimServo = new HardwareTimer(SRVInstance);

Configure and start PWM

MyTimServo->setPWM(channelSRV, SERVO_PWM_PIN, 50, 5); // 50 Hertz, 5% dutycycle.

= Step 3 = MG 90 servos that we use in our lab are specific. As mentioned above, to achieve a full 180-degree rotation range, their minimum and maximum duty cycle timings go far beyond standards. We will create a function for calculating the duty cycle for the servo with the angle as the input parameter.

void fSrvSet(int pAngle)
{
  if (pAngle<0) pAngle=0;        //check boudaries of angle
  if (pAngle>180) pAngle=180;
  int i_srv=500+(200*pAngle)/18; //minimal duty cycle is 0,5ms, maximal 2,5ms
  MyTimServo->setCaptureCompare(channelSRV, i_srv, MICROSEC_COMPARE_FORMAT);  //modify duty cycle
};

= Step 4 = Rotating a servo is as easy as calling our function with the desired angle as the parameter, e.g.:

fSrvSet(90);
delay(2000);

FAQ

How do I know minimum and maximum values for the timings for servo operation?: Those parameters are provided along with servo technical documentation, so you should refer to them. Our configuration reflects the servos we use (MG90/SG90), and as you can see, it goes far beyond the standard servo rotation control that is a minimum of 1000us and a maximum of 2000us. Using standard configuration, your servo won't rotate at full 180 degrees but at a shorter rotation range. Would it be possible to control the servo and fan with the same program?: Yes, but you have to remember that servo has very strict timing requirements. Because both elements share the same timer, they also share the same frequency which must be set according to the servo requirements.

Result validation

Observe the red arrow to rotate accordingly. Remember to give the servo some time to operate.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE —

STM_2: Using a digital potentiometer

Digital potentiometer DS1803 is an I2C-controlled device that digitally controls the resistance between the outputs as in a real turning potentiometer.
While in the turning potentiometers, there are wipers which are moving from minimal to a maximal value, in digital potentiometers there are no movable parts. Everything is implemented in a silicon.
DS1803 has two digital potentiometers controlled independently. We use just one with the lower cardinal number (index 0). In our example, it is a 100k spread between GND and VCC, and its output is connected to the ADC (analogue to digital converter) input of the STM32 SoC. This way, the potentiometer's electronic wiper is controlled remotely via the I2C bus.
The device's I2C address is 0x28, and the ADC input GPIO pin is 7.
The digital potentiometer in our laboratory node forms then a loopback device: it can be set (also read) via I2C, and the resulting voltage can be measured on the separate PIN (ADC) 15. This way, it is possible, e.g. to draw a relation between the potentiometer setting and ADC readings to check whether it is linear or forms some other curve.

 Digital potentiometer DS1803 application in VREL Next Gen nodes
Figure 28: Digital potentiometer DS1803 application in VREL Next Gen nodes
The potentiometer has an 8-bit resolution, so the resistance step is 100k/256=~390 ohm.

Reading of the ADC is possible using the regular analogRead(pin) function. In STM32WB55 there are 16 analogue inputs, and the potentiometer is connected to the pin A4 (PC_3 in Nucleo numbering).

In STM32, ADC has a 12-bit resolution but the AnalogRead() function uses a 10-bit resolution by default, so valid return values are 0…1023. If you want to have full 12-bit resolution use analogReadResolution(12); function call

Prerequisites

To implement this scenario, it is advised to get familiar with at least one of the following scenarios first:

They enable you to present the data on the display (i.e. readings).

To handle communication with the DS1803 digital potentiometer, we use bare I2C programming. For this reason, we need to include only the I2C protocol library:

#include <Wire.h>
Also, remember to add the display handling library, as present in the scenarios. We suggest using OLED or ePaper to present the relation between the setting and reading as a graphic or even more than one display (e.g. LCD + OLED) to handle readings and the graph.

Below, we present a sample control library that you need to include in your code:

enum POT_LIST {POT_1 = 0xA9, POT_2=0xAA, POT_ALL=0xAF}; //We have only POT_1 connected
typedef enum POT_LIST POT_ID;
 
//Prototypes
void setPotentiometer(TwoWire&  I2CDev, byte potValue, POT_ID potNumber);
byte readPotentiometer(TwoWire& I2CDev, POT_ID potNumber);
 
//Implementation
void setPotentiometer(TwoWire& I2CDev, byte potValue, POT_ID potNumber)
 {  
  I2CDev.beginTransmission(DS1803_ADDRESS);
  I2CDev.write(potNumber);
  I2CDev.write(potValue);  
  I2CDev.endTransmission(true);
 };
 
byte readPotentiometer(TwoWire& I2CDev, POT_ID potNumber) //reads selected potentiometer
 {
  byte buffer[2];  
  I2CDev.requestFrom(DS1803_ADDRESS,2);
  buffer[0]=I2CDev.read();
  buffer[1]=I2CDev.read();
  return (potNumber==POT_1?buffer[0]:buffer[1]);
 };
In the library above, the readPotentiometer(…) function returns a value previously set to the digital potentiometer, not an actual ADC voltage reading! It returns a set value by setPotentiometer(…), which is on the “digital” side of the DS1803 device. Actual ADC reading can be obtained using analogRead(pin).

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

Task to be implemented

Iterate over the potentiometer settings, read related voltage readings via ADC, and present them in graphical form (as a plot). As the maximum resolution of the potentiometer is 256, you can use a plot of 256 points or any other lower value covering all ranges. Present graph (plot) on either ePaper or OLED display, and while doing the readings, you should present data in the LCD (upper row for a set value, lower for a reading of the ADC).

Start

Check if you can see all the displays. Remember to use potentiometer 1 (index 0) because only this one is connected to the ADC input of the ESP32 MCU. In steps 1-3, we present how to handle communication with a digital potentiometer and how to read the ADC input of the MCU. Methods for displaying the measurements and plotting the graph are presented in steps 4 and 5. Remember to include the functions above in your code unless you want to integrate them with your solution.

Steps

Below, we assume that you have embedded functions handling operations on the digital potentiometer as defined above in your source file. Remember to include the Wire.h library.

Note: Steps 4 and 5 present some code for displaying data on an OLED display.

= Step 1 = Define ADC pin and Digital potentiometer chip DS1803 I2C address. All definitions are present in the following code:

#define POT_ADC A4
#define DS1803_ADDRESS 0x28

= Step 2 = Declare an array of readings that fits an OLED display. Adjust for ePaper resolution (horizontal) if using it. OLED is 128×128 pixels:

static int16_t aGraphArray[128]; 

= Step 3 = Include functions present in the PREREQUISITES section.

= Step 4 = Initialise the I2C bus and configure ADC's GPIO as input. Change the ADC resolution to 12-bits.

  Wire.begin();
  pinMode(POT_ADC, INPUT);
  analogReadResolution(12);

= Step 4 = Perform the loop that sets 128 values (scaled to the range 0 to 256) on the potentiometer's output and read the value back from the digital potentiometer via ADC input. Store readings in the array:

for(byte i=0; i<128; i++)
  { 
    setPotentiometer(Wire, 2*i, POT_1); 
    aGraphArray[i]=analogRead(POT_ADC);
  }
We use 128 values because the OLED display's resolution is 128×128 pixels.

= Step 5 = Display on the OLED. Assume the following handler to the pointer to the display controller class:

Adafruit_SSD1351 oled

More information on OLED display is in the scenario STM_7: Using OLED display. Note, ADC measures in the 12-bit mode (we assume such configuration, adapt factor if using other sampling resolution), so values stored in an aGraphArray array are between 0 and 4095.

 float factor = 128./4095.;
 for(byte x=0;x<128;x++)
  {
    int16_t y=128-round(((float)aGraphArray[x])*factor);
    display.setPixel(x,y);
  }
  display.display();
Result validation

A relation between the potentiometer set value and ADC reading should be almost linear from 0V up to the maximum. The linear correlation is never perfect, either because of the devices' implementation imperfection (STM32's ADC input and digital potentiometer output) or because of the electromagnetic noise. There are many devices in our lab room.

FAQ

The ADC readings are changing slightly, but I have not changed the potentiometer value. What is going on?: The ADC in ESP32 is quite noisy, mainly when using WiFi parallelly. Refer to the Coursebook and ESP32 documentation on how to increase measurement time that will make internally many readings and return to you an average. Use the analogSetCycles(cycles) function to increase the number of readings for the averaging algorithm. The default is 8, but you can increase it up to 255. Note that the higher the cycles parameter value, the longer the reading takes, so tune your main loop accordingly, particularly when using an asynchronous approach (timer-based). Eventually, you can implement low-pass filters yourself (in the software).

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

STM_3: Use of integrated temperature and humidity sensor

In this scenario, we will introduce a popular DHT11 sensor. The DHT series covers DHT11, DHT22, and AM2302. Those sensors differ in accuracy and physical dimensions but can all read environmental temperature and humidity. This scenario can be run stand-alone to read weather data in the laboratory nodes' room. The DHT11 sensor is controlled with one GPIO (in all our laboratory nodes, it is GPIO D22 or PB_2 in Nucleo-style numbering) and uses a proprietary protocol.

Prerequisites

Air temperature and humidity can be easily read using a dedicated library. You need to include two of them, as presented below:

  lib_deps = 
             adafruit/DHT sensor library@^1.4.6

Sensor readings can be sent over the network or presented on one of the node's displays (e.g. LCD), so understanding how to handle at least one of the displays is essential:

It is also possible to present the temperature as the LED colour change with a PWM-controlled LED or LED stripe. Their usage is described here:

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

In this scenario, we only focus on reading the sensor (temperature and humidity). Information on how to display measurements is part of other scenarios that you should refer to to create a fully functional solution (see links above).

Task to be implemented

Present the current temperature, and humidity on any display (e.g. LCD). Remember to add units (C, %Rh).

Start

A general check to see if you can see the chosen display in the camera field of view is necessary. No other actions are required before starting development.

Steps

The steps below present only interaction with the sensor. Those steps should be supplied to present the data (or send it over the network) using other scenarios accordingly.

= Step 1 = Include the DHT library and related sensor library.

#include <DHT.h>

= Step 2 = Declare type of the sensor and GPIO pin:

#define DHTTYPE DHT11   // DHT 11
#define DHTPIN 47

= Step 3 = Declare controller class and variables to store data:

static DHT dht(DHTPIN, DHTTYPE, 50);
static float hum = 0;
static float temp = 0;
static boolean isDHTOk = false;

= Step 4 = Initialise sensor (mind the delay(100); after initialisation as the DHT11 sensor requires some time to initialise before one can read the temperature and humidity:

  dht.begin();
  delay(100);

= Step 5 = Reat the data and check whether the sensor works OK. In the case of the DHT sensor and its controller class, we check the correctness of the readings once the reading is finished. If something is wrong the sensor library returns the reading as NaN. To check if the value read is OK, compare the readings with the NaN (not a number) text, each separately:

   hum = dht.readHumidity();
   temp = dht.readTemperature();
   (isnan(hum) || isnan(temp))?isDHTOk = false:isDHTOk = true;
Do not read the sensor too frequently! Doing so will cause lots of NaN numbers. Please give it some 250ms, at least, between consecutive readings, whether you do it asynchronously or using a blocking call of delay(250); in the loop.
Result validation

The observed temperature is usually between 19 and 24C, with humidity about 40-70%, depending on the weather. On rainy days, it can even go higher.

If you ever notice the temperature going beyond 25C, please drop a note to the administrators: it means that our air conditioning is faulty and needs maintenance ;-).

FAQ

I've got NaN (Not a Number) readings. What to do?: Check if GPIO is OK (should be D22), check if you initialised controller class and most of all, give the sensor some recovery time (at least 250ms) between consecutive readings.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

STM_4: 1-Wire Temperature Sensor

The temperature-only sensor DS18B20 uses a 1-wire protocol. “1-wire” applies only to the bidirectional bus; power and GND are on separate pins. The sensor is connected to the MCU using GPIO D0 only (PA_3 in Nucleo numbering). Many devices can be connected on a single 1-wire bus, each with a unique ID. Except plastic version, which we have in our laboratory (enclosure TO-92) DS18B20 also comes in a water-proof metal enclosure version that enables easy monitoring of the liquid's temperature.

Prerequisites

To handle operations with DS18B20, we will use a dedicated library that uses a 1-wire library on a low level:

  lib_deps = milesburton/DallasTemperature@^3.11.0

Sensor readings can be sent over the network or presented on one of the node's displays (e.g. LCD), so understanding how to handle at least one of the displays is essential:

Suggested Readings and Knowledge Resources

Hands-on Lab Scenario

In this scenario, we present how to interface the 1-wire sensor, DS18B20 (temperature sensor).

Task to be implemented

Read the temperature from the sensor and present it on the display of your choice. Show the reading in Celsius degrees. Note that the scenario below presents only how to use the DS18B20 sensor. How to display the data is present in other scenarios, as listed above. We suggest using an LCD (scenario STM_5: Using LCD Display).

Update reading every 10s. Too frequent readings may cause incorrect readings or faulty communication with the sensor. Remember, the remote video channel has its limits, even if the sensor can be read much more frequently.

Start

Check if your display of choice is visible in the FOV of the camera once the device is booked.

Steps

The steps below present only interaction with the sensor. Those steps should be supplied to present the data (or send it over the network) using other scenarios accordingly.

= Step 1 = Include Dallas sensor library and 1-Wire protocol implementation library:

#include <OneWire.h>
#include <DallasTemperature.h>
Also, remember to add the LCD handling library, as present in the STM_9 scenario, unless you decide to use another output device.

= Step 2 = Declare the 1-Wire GPIO bus pin, 1-Wire communication handling object, sensor proxy and a variable to store readings:

#define ONE_WIRE_BUS D0
static OneWire oneWire(ONE_WIRE_BUS);
static DallasTemperature sensors(&oneWire);
static float tempDS;
Note, the sensors class represents a list of all sensors available on the 1-Wire bus. There is just a single one in each of our laboratory nodes.

= Step 3 = Initialise sensors' proxy class:

  sensors.begin();

= Step 4 = Read the data:

  sensors.requestTemperatures();  
  if (sensors.getDeviceCount()>0)
  {
    tempDS = sensors.getTempCByIndex(0);
  }

Remember not to read the sensor too frequently. 10s between consecutive readings is just fine.
Devices in the 1-Wire bus are addressable either by index (as in the example above) or by their address.
Some useful functions are present below:

  • DallasTemperature::toFahrenheit(tempDS) - converts temperature in C to F,
  • sensors.getAddress(sensorAddress, index) - reads device address given by uint8_t index and stores it in DeviceAddress sensorAddress.
Result validation

The observable temperature is usually within the range of 19-24C. If you find the temperature much higher, check your code, and if that is okay, please contact our administrator to inform us about the faulty AC.

FAQ

I've got constant readings of value 85.0. What to do?: Check if GPIO is OK (should be D0), check if you initialised controller class and call the function sensors.requestTemperatures();. Give the OneWire bus and the sensor some recovery time (at least 250ms) between consecutive readings.

Project information


This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

— MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE — — MISSING PAGE —

ITT AVR Laboratory Node Hardware Reference

Controller module ATmega2561 v1.x

The main module is a controller development board (controller board) equipped with the AVR ATmega2561 microcontroller. In addition to the microcontroller, the board consists of several peripherals, voltage stabilizer, connectors, JTAG programmer, Ethernet, SD memory card slot. The controller board has the following features:

  • ATmega2561-16AU microcontroller
    • 8-channel 10-bit A/D converter
    • 256 kB Flash memory (program memory)
    • 4kB EEPROM memory (data memory)
    • 6 channel programmable PWM
  • Integrated JTAG programmer (based on FTDI2232)
  • 14,7456 MHz clock
  • Ethernet module with RJ45 connector
  • SD memory card slot
  • Status LED (PB7)and Power LED
  • Programmable button (PC2) and reset button
  • All Atmega signals available on three connectors (1: ports D, B, E; 2: ports G, C, A; 3: port F with ADC I/O lines)
  • 2,1 mm power socket
  • Automatic power switch USB or external power supply
  • Built-in voltage stabilizer, with 5 V and 3,3 V output
Controller module

The module is equipped with a AC/DC rectifier circuit and a LDO voltage stabilizer (with low dropout) -an external feeder with voltage stabilization is not needed. The module can be powered with a step down transformer with an output voltage which is greater than 6 volts and lower than 15 volts. In order to reduce power losses it is recommended to use power supply between 6-9V. The POWER LED signalizes a connected feed (“POWER” description on the board). All ATmega2561 signals are available on three connectors on the edge of the board. Connectors pin assignment is described in the next part of this instruction. It includes full descriptions of ATmega2561 pins and their alternative functions. The module is equipped with a microprocessor reset circuit (when power on) and a reset button for a microprocessor restart. A microprocessor can be programmed with an on-board JTAG programmer over USB or with an ISP interface. To the seventh pin of port B (named as PB7) the status LED (described as PB7 on the board) is connected. This LED can be used as a status indicator of application software. Low state on PB7 pin causes the status LED to be lit. The module is equipped with SD memory card slot, where it can be used as a standard microSD memory card. The memory card is connected to the microcontroller via the ISP interface and can be used to store data where data must be maintained even if the power supply is removed.

Components on the Controller board

Connector Pins and Functions

NrPinAlternative function / Description
1VCC- +5 V
2GND- GND
3REFAREFAnalog Reference Voltage For ADC
4GND- GND
5PF0ADC0ADC Input Channel 0
6GND-GND
7PF1ADC1ADC Input Channel 1
8GND-GND
9PF2ADC2ADC Input Channel 2
10GND-GND
11PF3ADC3ADC Input Channel 3
12GND-GND

 

NrPinAlternative function / Description
1 PD7T2 Timer/Counter2 Clock Input
2 PD6T1 Timer/Counter1 Clock Input
3 PD5XCK1 USART1 External Clock Input/Output
4 PD4IC1 Timer/Counter1 Input Capture Trigger
5 PD3INT3/TXD1 External Interrupt3 Input or UART1 Transmit Pin
6 PD2INT2/RXD1 External Interrupt2 Input or UART1 Receive Pin
7 PD1INT1/SDA External Interrupt1 Input or TWI Serial Data
8 PD0INT0/SCL External Interrupt0 Input or TWI Serial Clock
9 VCC- +5V
10GND- GND
11PB7OC0A/OC1C/PCINT7Output Compare and PWM Output A for Timer/Counter0, Output Compare and PWM Output C for Timer/Counter1 or Pin Change Interrupt 7
12PB6OC1B/PCINT6Output Compare and PWM Output B for Timer/Counter1 or Pin Change Interrupt 6
13PB5OC1A/PCINT5Output Compare and PWM Output A for Timer/Counter1 or Pin Change Interrupt 5
14PB4OC2A/PCINT4Output Compare and PWM Output A for Timer/Counter2 or Pin Change Interrupt 4
15PB3MISO/PCINT3SPI Bus Master Input/Slave Output or Pin Change Interrupt 3
16PB2MOSI/PCINT2SPI Bus Master Output/Slave Input or Pin Change Interrupt 2
17PB1SCK/PCINT1SPI Bus Serial Clock or Pin Change Interrupt 1
18PB0SS/PCINT0SPI Slave Select input or Pin Change Interrupt 0
19PE7INT7/IC3/CLK0 External Interrupt 7 Input, Timer/Counter3 Input Capture Trigger or Divided System Clock
20PE6INT6/T3 External Interrupt 6 Input or Timer/Counter3 Clock Input
21PE5INT5/OC3CExternal Interrupt 5 Input or Output Compare and PWM Output C for Timer/Counter3
22PE4INT4/OC3BExternal Interrupt4 Input or Output Compare and PWM Output B for Timer/Counter3
23PE3AIN1/OC3AAnalog Comparator Negative Input or Output Compare and PWM Output A for Timer/Counter3
24PE2AIN0/XCK0Analog Comparator Positive Input or USART0 external clock input/output
25PE1PDO/TXD0 ISP Programming Interface Data Output or USART0 Transmit Pin
26PE0PDI/RXD0/INT8 ISP Programming Interface Data Input, USART0 Receive Pin or Pin Change Interrupt 8

NrPinAlternative function / Description
1 GND- Gnd
2 VCC- +5 V
3 PA0AD0External memory interface address and data bit 0
4 PA1AD1External memory interface address and data bit 1
5 PA2AD2External memory interface address and data bit 2
6 PA3AD3External memory interface address and data bit 3
7 PA4AD4External memory interface address and data bit 4
8 PA5AD5External memory interface address and data bit 5
9 PA6AD6External memory interface address and data bit 6
10PA7AD7External memory interface address and data bit 7
11PG4TOSC1RTC Oscillator Timer/Counter2
12PG5OC0BOutput Compare and PWM Output B for Timer/Counter0
13PG2ALEAddress Latch Enable to external memory
14PG3TOSC2RTC Oscillator Timer/Counter2
15PC6A14External Memory interface address bit 14
16PC7A15External Memory interface address bit 15
17PC4A12External Memory interface address bit 12
18PC5A13External Memory interface address bit 13
19PC2A10External Memory interface address bit 10
20PC3A11External Memory interface address bit 11
21PC0A8 External Memory interface address bit 8
22PC1A9 External Memory interface address bit 9
23PG0WR Write strobe to external memory
24PG1RD Read strobe to external memory
25GND- GND
263V3- +3,3 V

User Interface module

The User Interface module is designed for simple tasks and basic process control. Module has three push-buttons and three LEDs, which can be used as digital inputs and outputs of microcontroller. Additionally to simple LEDs the module is equipped with 7-segment indicator, graphical LCD display, alphanumeric LCD outputs and a buzzer. User Interface module is handy to use along with other modules enabling to control the output device behavior, like motors and display the sensor readings.

Module features:

  • Three LEDs: green, yellow and red;
  • Three push-buttons;
  • 7-segment indicator;
  • Graphical LCD;
  • Alphanumeric LCD connector;
  • Buzzer;

Electrical connections

User Interface module is connected to Controller module to ports PA/PC/PG, which includes the 8-pin ports PA and PC and 6-pin port PG.

User Interface module is equipped with three buttons S1, S2, S3 which are connected to ports PC0, PC1, PC2 accordingly. The other end of buttons are connected through the resistors to ground (logical 0). LED1, LED2 and LED3 on the module are connected to the ports PC3, PC4, PC5 accordingly. The anodes of LEDs are connected to the supply (logical 1).

Schematics of buttons and LEDs

User Interface module is equipped with 7-segment indicator, which is connected to the microcontroller ports through the driver A6275. Driver data bus (S-IN) is connected to pin PC6, clock signal (CLK) to the pin PC7 and latch signal (LATCH) to pin PG2.

Schematics of 7-segment indicator

The graphical LCD display on the module is connected to port PA. In parallel the same port has external connector where all pins are aligned according to the standard 2 x 16 alphanumeric LCD 4 bit control. The user can choose whether to use the graphical or alphanumeric LCD. It is not possible to use both at the same time. The graphical LCD's background lighting can be logically controlled by the software. The back-light intensity of the alphanumeric LCD is regulated with on-board resistor and graphical LCD back-light can be controlled by the software. Both LCD displays are connected to port PA but only one at a time can be used. When selecting the alphanumeric LCD the jumper should be removed to avoid random control of graphical LCD background light.

Schematics of LCD

The buzzer is connected with controller board's pin PG5 and with Vcc. It's possible to generate sound with Robotic Homelab library or with your own software.

Schematics of buzzer

 

Combo module

Combo module is used in this remote Laboratory to power the motors and connect external sensors.

Combo module is able to drive following motors:

  • 4 x DC motors
  • 1 x Stepper
  • 2 x Servomotor



Connect following sensors:

  • 4 x analog
  • 4 x digital
  • 2 x coder
  • 8x digital inputs trough parallel to serial converter



Use the following communication interfaces:

  • UART (3,3 or 5 V )
  • ZigBee / Bluetooth / RFID / WiFi / GPS wireless communication module
Combo board with XBee module

Electrical connections

Combo board is connected with controller using PA-PB-PC-PD-PE-PF ports. DC and stepper motor feeds come from external power cord, servo motors feed comes trough 5V voltage regulator also from external power cord. Motors power circuit is separated from controller's power circuit. If external power feed is correctly connected to the Combo board the green PWR led will lit.

DC motors

DC motors is connected to the DC group of pins. Every pair of pins can control 1 dc motor, thus it's possible to manipulate 4 dc motors. Combo board uses bd6226fp H-bridge to control dc motors. It's possible to manipulate some other device, what can be controlled digitally and it's current is smaller than 1 A and voltage does not exceed 18 V, beside dc motor with DC pins (Piezoelectric generator, relay etc).

DC motor connection scheme
AVR pin Signal AVR pin Signal
PB4 Motor 1 A PD6 Motor 3 A
PB7 Motor 1 B PD7 Motor 3 B
PD0 Motor 2 A PD4 Motor 4 A
PD1 Motor 2 B PD5 Motor 4 B

Stepper motor

Stepper connection scheme

Servomotor

Servo is connected to Servo group of pins. Ground wire is connected to the GND pin which is the pin nearest to the board edge. It's possible to use 2 servomotors at once. Signal pins on the Combo module is directly connected to the controller's timer's output pins.

Servomotor connection scheme
AVR pin Signal Socket
PB5(OC1A) PWM1 Top
PB6(OC1B) PWM2 Bottom

Sensors

ADC sensor inputs

Combo module has 4 ADC input pins, every ADC input pin forms a group with Vcc (+5 V) and ground which is marked as GND, that makes them ideal to use to connect analog sensors.

Analog sensor inputs scheme
AVR pin Signal Socket
PF0(ADC0) ADC0 Top
PF1(ADC1) ADC1 Bottom
PF2(ADC2) ADC2 Top
PF3(ADC3) ADC3 Bottom

Digital inputs

Combo module has 4 groups of pins where to connect digital sensors. Every group consist of +5 V also called Vcc, ground also called GND and signal pin.

Digital sensor connecting scheme
AVR pin Signal Socket
PE2(XCK0/AIN0) PE2 Top
PE3(OC3A/AIN1) PE3 Bottom
PE4(OC3B/INT4) PE4 Top
PE5(OC3C/INT5) PE5 Bottom

Digital serial input

Combo module has with 8 input pins parallel in serial out shift register 74HC/HCT165, what is able to read in 8 digital signals and convert it to 8 bit digital number. Those inputs are suitable for line follower applications.

Shift register connection scheme, odd pins are on top
AVR pin Signal
PA7(AD7) Q7
PC7(A15) CP
PA6(AD6) PL
GND CE

Coder

Coder connection scheme
AVR pin Signal
PE6 Coder 1
PE7 Coder 2

AVR Laboratory Scenarios

 

Light-emitting Diode

Theory

5 mm legged LED

A light-emitting diode is a semiconductor which emits light when forward voltage is applied. The acronym for light-emitting diode is LED. There are different color combination of diodes and the diodes, which can also emit white light. Like a normal diode, the LED has two contacts – anode and cathode. On drawings the anode is marked as “+” and cathode as “-“.

Schematic symbol of LED and it's polarity

When forward voltage is applied, an LED’s anode is connected to the positive voltage and the cathode to the negative voltage. The voltage of the LED depends on the LED’s color: longer wavelength (red) ~2 V, shorter wavelength (blue) ~3 V. Usually the power of a LED is no more than a couple of dozen milliwatts, which means electrical current must be in the same range. When applying greater voltage or current a LED may burn out.

If the LEDs are used specially to illuminate, it is wise to use special electronic circuits which would regulate current and voltage suited for LEDs. However LEDs are quite often used as indicators and they are supplied directly from microcontroller’s pins. Since the supply voltage for microcontrollers is usually higher than the voltage for LEDs, there must be a resistor connected into series with the LED, which limits current and creates the necessary voltage drop. Instructions to calculate proper resistor can be found in the electronics chapter.

LEDs are produced in a variety of casings. Most common LEDs with feet have 3 mm or 5 mm diameter round shell and two long metal connector pins. Longer pin is the anode, the shorter one is the cathode. Surface mounted casing LEDs (SMD – Surface Mounted Device) have a T-shaped symbol on the bottom to indicate the polarity, where the roof of T stands for the location of the anode and the pole marks the cathode.

Polarity of legged and SMD LED's

HomeLab Practice

The HomeLab controller control module has one single indicator LED, whose anode is connected through resistor to a power supply and the cathode is connected to the controllers pin. In order to switch on and off this LED, LED pin should be defined as the output and set low or high accordingly. Which means if the pin is set high, the LED is turned off and if the pin is set low, the LED is turned on. Basically it would be possible to connect the LED also so that the anode is connected to the pin of microcontroller, and the cathode is connected to the earth (somewhere there has to be a resistor too) – in that case when the pin is set as high, the LED shines and when the pin is set as low the LED is switched off.

All practical examples for the HomeLab kit, LED switching included, use HomeLab’s pin library. Pin library includes data type pin, which contains addresses of pin related registers and pin bitmask. If to create a pin type variable in the program and then initialize it by using macro function PIN, the pin can be used freely with this variable (pin) through whole program without being able to use registers. Here are 2 example programs, which are doing exactly the same thing, but one is created on the basis of HomeLab’s library, the other is not. The debug LED, led_debug in HomeLab library, has been described as PB7 (HomeLab I & II) and PQ2 (HomeLab III). The Debug LED is physically located in the Controller module.

// HomeLab Controller module LED test program, which
// is based on HomeLab library
#include <homelab/pin.h>
 
// LED pin configuration.
pin led_debug = PIN(Q,2);
 
// Main program
int main(void)
{
	// Configuring LED pin as an output
	pin_setup_output(led_debug);
 
	// Lighting up LED
	pin_clear(led_debug);	
}
// HomeLab II Controller module LED test program, which
// accesses  registers directly
#include <avr/io.h>
 
// Main program
int main(void)
{	
	// Configuring LED pin as an output
	DDRB |= (1 << 7);	
 
	// Lighting up LED
	PORTB &= ~(1 << 7);
}

First example uses pins’ library (pin.h file). First a pin-type variable named debug led is created in the program, which holds information about LED pin. In the main program this pin will be set as output by using pin_setup_output function. After that the pin is set as low by function pin_clear. As the result LED will glow. In the second example variables are not used, setting LED output and lighting it will be done by changing port B data direction and output registers values. The reader who knows more about AVR notices, that in both examples there is no need to give command to light LED, because the default output value of the AVR is 0 anyway, but here it is done by the means of correctness.

What is the difference between the use of the library and the registers? The difference is in the comfort – library is easier, because you do not need to know the registers’ names and their effects. Most important benefit of library is adaptability. Using registers, you must change registers’ names and bitmasks through entire program in order to change pin. When using library, it must be done only in the beginning of the program where pin variable is initialized. Using registers has one deceptive advantage – usage of pin is direct and it is not done through program memory and time consuming functions. However, newer AVR-GCC compiler versions are so smart that they transform library’s functions to exactly same direct commands for manipulating registers like it would have been done directly in program. Must be said that compilers can optimize the code only when it deals with constant single variables not with volatile variables that are changing during work and with arrays.

The next program code is partial pin operations library. Its purpose is to explain the procedures with pin variables. It might not be understandable for the beginners as it uses C language pointers which are not covered in this book, but a lot of materials about pointers can be found from books and internet.

// Defining the Pins inside the pin struct
// pin name = PIN(PORT LETTER, PIN NUMBER IN PORT);
pin led_green = PIN(H,5);
 
// Configuring pin as output
inline void pin_setup_output(pin pin){
	bitmask_set(*pin.ddr, pin.mask);
}
 
// Setting pin high
inline void pin_set(pin pin){
	bitmask_set(*pin.port, pin.mask);
}
 
 
// Setting pin low
inline void pin_clear(pin pin){
	bitmask_clear(*pin.port, pin.mask);
}

In addition to the Controller module, LEDs are also located on the User interface module board. They are connected electrically in the same way as Controller module’s LED, which means cathode is connected to the AVR pin. For more information see the modules hardware guide. In addition to pin_set and pin_clear commands one can use led_on and led_off commands to control LED pins. The following table shows LEDs constants which are described in the library and the corresponding Controller module pins. Green, yellow and red LEDs are located in the user interface module.

Constant nameAlternative name HomeLab I & II pinHomeLab III pinDescription
led_debugLED0PB7PQ2 Blue LED on the Controller module
led_greenLED1PC3PH5 Green LED
led_yellowLED2PC4PH4 Yellow LED
led_redLED3PC5PH3 Red LED

HomeLab library based example program which uses LEDs constants looks as follows:

// LED test program for HomeLab User interface module
#include <homelab/pin.h>
 
// Main program
int main(void)
{
	// Configuring LED pins as an output
	pin_setup_output(led_red);
	pin_setup_output(led_yellow);
	pin_setup_output(led_green);	
 
	// Lighting up red and green LED
        led_on(led_red);
	led_on(led_green);
        // Turn off yellow LED
	led_off(led_yellow);
}

 

7-segment LED display

Necessary knowledge: [HW] User Interface Module, [LIB] 7-segment LED Display,
[LIB] Delay [PRT] Light-emitting Diode

Theory

7-segment

7-segmented LED number-indicator is a display which consists of 7 LEDs positioned in the shape of number 8. By lighting or switching off the corresponding LEDs (segments), it is possible to display numbers from 0 to nine as well as some letters.

Electrically all anodes of the LEDs are connected to one anode pin ca. LEDs are lit by switching their cathodes (a, b, c…). Exists also reversed connections, where the indicators have a common cathode cc. Generally several number-indicators are used for displaying multi digit numbers - for this purpose the indicators are equipped with coma (point) segment dp. All in all one indicator has 8 segments, but they are still called 7-segmented according to the number of number-segments.

Positioning of the LED indicator's segments and electrical scheme
The build-up of the LED driver's shift-index with corresponding segments of the indicator.

LED number-indicators are easy to use, they can be controlled directly from the pins of the microcontroller, but there are also special drivers, which able to control number-indicators using fewer pins of the microcontroller. There are different colors of LED number indicators, which can be very bright and very large. For displaying the entire Latin alphabet exist indicators with extra segments. There are different drivers, but common drivers using a serial interface, which is similar to the SPI, where both clock signal and data signal are used. Different from SPI the chip-select is not used there, and is replaced with latch function. The above mentioned three lines are connected to the controller pins.

  • Latch-signal
  • Clock-signal
  • Data-signal

Practice

There is one 7-segment LED number-indicator on the Digital i/o module. It is controlled through a driver with serial interface. For displaying the numbers on the HomeLabs Digital i/o module indicator, the following functionality is written to the library of the HomeLab.

// Marking card
// The bits are marking the segments. Lower ranking is A, higher ranking is DP
const unsigned char __attribute__ ((weak)) segment_char_map[11] = 
{
	0b00111111,
	0b00000110,
	0b01011011,
	0b01001111,
	0b01100110,
	0b01101101,
	0b01111100,
	0b00000111,
	0b01111111,
	0b01100111,
	0b01111001   // E like Error
};
 
// Start-up of the 7-segment indicator
void segment_display_init(void)
{
	// Set latch, data out and clock pins as output
	pin_setup_output(segment_display_latch);
	pin_setup_output(segment_display_data_out);
	pin_setup_output(segment_display_clock);
}
 
// Displaying number on the 7-segment indicator
void segment_display_write(unsigned char digit)
{
	unsigned char map;
	signed char i;
 
	// Check-up of the number
	if (digit > 9)
	{
		digit = 10;
	}
 
	// Number as the card of the segments
	map = segment_char_map[digit];
 
	// Latch-signal off
	pin_clear(segment_display_latch);
 
	// Sending he bits. Higher ranking goes first
	for (i = 7; i >= 0; i--)
	{
		// Setting the pin according to the value of the bit of the card
		pin_set_to(segment_display_data_out, bit_is_set(map, i));
 
		// The clock-signal as high for a moment
		pin_set(segment_display_clock);
		_delay_us(1);
 
		// The clock-signal as low
		pin_clear(segment_display_clock);
		_delay_us(1);
	}
 
	// Latch-signal on
	pin_set(segment_display_latch);
}

For displaying numbers and the letter “E”, is created a “weak” constant array segment_char_map, where lighting of all 8 segments is marked with bit 1 and switch off is market with bit 0. The bits form lower to higher (from right to left in binary form) are marking segments A, B, C, D, E, F, G ja DP. The control interface of the driver is realized through software SPI, i.e. by using a software for controlling the data communication pins in the program. All three pins are set as output with segment_display_init function. segment_display_write is for displaying the function, which finds the segment-card of the mark from the array and transmits bit by bit all values of the segments to the driver. The frequency of the clock signal with the software delays is now approximately 500 kHz. When a user defines a variable segment_char_map its own code, it is possible to create other characters on the screen (eg, text, etc.)

The following is a more concrete example of a program for using the number-indicator. Previously described function of the library is described in the program. The program counts numbers from 0 to 9 with approximate interval of 1 second and then displays letter E, because two-digit numbers is not possible to show on the one digit indicator.

// The example program of the 7-segment LED indicator of the HomeLab's
#include <homelab/module/segment_display.h>
#include <homelab/delay.h>
 
// Main program
int main(void)
{
	int counter = 0;
 
	// Set-up of the 7-segment indicator
	segment_display_init();
 
	// Endless loop
	while (true)
	{
		// Displaying the values of the counter
		segment_display_write(counter % 10);
 
		// Counting up to 10
		counter++;
		if (counter>19) counter=0;
 
		// Delay for 1 second
		sw_delay_ms(1000);
	}
}

 

LCD screen

Necessary knowledge: [HW] User Interface Module, [LIB] Alphanumeric LCD, [LIB] Graphic LCD,
[LIB] Delay

Theory

The graphical LCD element
The picture formed of pixels of a graphic LCD

LCD screens, which are used with microcontrollers, can be divided mainly into a two parts: a text or alphanumeric LCD and a graphic LCD. Alphanumeric LCD is liquid crystal display, with the purpose of displaying letters and numbers. In basic LCD is used liquid crystal which is placed between transparent electrodes, and which changes the polarization of the passing light in electrical field. The electrodes are covered by polarization filters, which assure that only one way polarized light can pass the screen. If the liquid crystal changes its polarity due to an electrical field, the light can not pass the screen or part (segment) of it and it looks dark.

Main characteristic of alphanumerical LCD is the placing of its segments. The screen is divided into many indicators. Each indicator has either enough segments for displaying letters and numbers or it is formed from matrix of little square segments (pixels). For example, a matrix of 5×7 pixels is enough to display all numbers, and letters of Latin alphabet. There are usually 1 – 4 rows of indicators and 8 – 32 columns. Each indicator has a small difference similar to the differences of the letters in text.

Besides the screen Alphanumerical LCD has also controller which controls the segments of the screen according to the commands from the communication interface. A controller has a preprogrammed card of letters, where each letter, number or symbol has its own index. Displaying the text on the screen is basically done by sending the indexes to the controller. In reality there must be more control orders sent to the controller before anything can be displayed. It is important to get familiarize with each LCD data-sheet, because there are many different types of LCDs and they all are controlled differently.

Graphical LCD liquid crystal display is a display which allows displaying pictures and text. Its construction is similar to the alphanumerical LCD, with a difference that on the graphic display all pixels are divided as one large matrix. If we are dealing with a monochrome LCD, then a pixel is one square segment. Color displays’ one pixel is formed of three subpixels. Each of the three subpixels lets only one colored light pass (red, green or blue). Since the subpixels are positioned very close to each other, they seem like one pixel.

Monochrome graphic displays have usually passive matrix, large color displays including computer screens have active matrix. All information concerning the color of the background and the pixels of the graphic LCDs are similar to alphanumerical LCDs. Similar to the alphanumerical displays, graphic displays are also equipped with separate controller, which takes care of receiving information through the communication interface and generates the electrical field for the segments.

Practice

In the HomeLab III set is a 128×160 pixels and 1,8 inch full color TFT LCD screen. Sitronixi ST7735 controller is attached to the display which can be communicated through SPI serial interface. The background lighting of the display module is separately controlled, but without the background light, it is not possible to use the screen. Communicating with the display is not very difficult, but due to the large amount of the functions it is not explained here. Home-Labs library has functions for using it.

Main difference compared to from HomeLab III to HomeLab II is a 84×48 pixels monochrome graphic LCD. It is the same display as used in Nokia 3310 mobile phones. Philips PCD8544 controller is attached to the display which can be communicated through SPI-like serial interface. The background lighting of the display module is separately controlled.

First, the graphical LCD screen must be started with lcd_gfx_init function. There is a letter map in side of the library with full Latin alphabet, numbers and with most common signs written. To display a letter or text, first its position must be determined by using function lcd_gfx_goto_char_xy. For displaying s letter is lcd_gfx_write_char function and for displaying text lcd_gfx_write_string function.

The following is an example of time counter. The program counts seconds (approximately), minutes and hours. For converting time to text sprintf function is used.

// Example of using the graphic LCD of the HomeLab
// Time of day is displayed on LCD since the beginning of the program
#include <stdio.h>
#include <homelab/module/lcd_gfx.h>
#include <homelab/delay.h>
 
// Main program
int main(void)
{
	int seconds = 0;
	char text[16];
 
	// Set-up of the LCD
	lcd_gfx_init();
 
	// Cleaning the screen
	lcd_gfx_clear();
 
	// Switching on the background light
	lcd_gfx_backlight(true);	
 
	// Displaying the name of the program
	lcd_gfx_goto_char_xy(1, 1);
	lcd_gfx_write_string("Time counter");
 
	// Endless loop	
	while (true)
	{
		// Converting the seconds to the form of clock
		// hh:mm:ss
		sprintf(text, "%02d:%02d:%02d",
			(seconds / 3600) % 24,
			(seconds / 60) % 60,
			 seconds % 60);
 
		// Displaying the clock text
		lcd_gfx_goto_char_xy(3, 3);
		lcd_gfx_write_string(text);
 
		// Adding one second
		seconds++;
 
		// Hardware delay for 1000 ms
		hw_delay_ms(1000);
	}
}

Thermistor

Theory

NTC thermistor

A thermistor is a type of resistor which resistance varies with temperature. There are two types of thermistors: positive temperature coefficient of resistance and negative temperature coefficient of resistance. The resistance of thermistors with positive temperature coefficient of resistance is increasing when the temperature grows and with negative the resistance decreases. The respective abbreviations are PTC (positive temperature coefficient) and NTC (negative temperature coefficient).

The thermistors resistances' dependence of the temperature is not linear and this complicates the usage of it. For accurate temperature measurements in wider temperature flotation the Steinhart-Hart third-order exponential equation is used as the thermistors resistance is linear only in small temperature range. The following simplified Steinhart-Hart equation with B-parameter exists for NTC thermistors:

The relation between temperature and resistance of a NTC thermistor.

where:

  • T0 - nominal temperature, usually 25 °C.
  • R0 - resistance at nominal temperature.
  • B - parameter B.

Parameter B is a coefficient, which is usually given in the datasheet of the thermistor. But it is stable enough constant only in a certain ranges of temperature, for example at ranges 25–50 °C or 25–85 °C. If the temperature range measured is wider , the data sheet should be used for retrieving the equation.

Usually a voltage-divider is used for measuring the resistance of a thermistor, where one resistor is replaced with a thermistor and the input voltage is constant. The output voltage of the voltage-divider is measured, which changes according to the change of the resistance of the thermistor. If the voltage is applied, current goes through the thermistor which heats up the thermistor due to thermistors resistance and therefore alters again the resistance. The fault caused by heating up of the thermistor can be compensated with calculations, but it is easier to use a thermistor that has higher resistance and therefore heats up less.

With restricted resources and with less demands on accuracy, previously calculated charts and tables for temperatures are used. Generally the tables have ranges of temperatures and respective values of resistance, voltage or analogue-digital converters. All exponential calculations are already done and the user needs to only find the correct row and read the temperature given.

Practice

The Sensor module of the HomeLab is equipped with a NTC type thermistor which has 10 kΩ nominal resistance. At temperatures 25-50 °C the parameter B of the thermistor is 3900. One pin of the thermistor is connected to supply and the other one is connected to the analogue-digital converter (HomeLab II channel 2 and HomeLab III channel 14). A typical 10 kΩ resistor is also connected with the same pin of the microcontroller and earth and together with the thermistor forms a voltage divider. Since we are dealing with a NTC thermistor, which resistance decreases as the temperature grows; the output voltage of the voltage divider is increasing repectively with growing temperature.

While using the AVR it is practical to use a conversion table of values of temperature and analogue-digital converter to find the correct temperature. It is wise to find corresponding value of analogue-digital converter for each temperature degree of desired range of temperature because reverse table will be too large due to the amount of 10 bit ADC values. It is recommended to use any kind of spreadsheet program (MS Excel, LibreOffice Calc, etc.) to make the table. Steinhart-Hart formula which is customized for the mentioned NTC thermistors able's to find the resistance of the thermistor which corresponds to the temperature. Derived from the resistance, is possible to calculate the output voltage of the voltage divider and using this output voltage to calculate the value of the ADC. Calculated values can be inserted to the program as follows:

// Table for converting temperature values to ADC values
// Every element of the array marks one Celsius degree
// Elements begin from -20 degree and end at 100 degree
// There are 121 elements in the array
const signed short min_temp = -20;
const signed short max_temp = 100;
 
const unsigned short conversion_table[] =
{                           
	91,96,102,107,113,119,125,132,139,146,153,
	160,168,176,184,192,201,210,219,228,238,247,
	257,267,277,288,298,309,319,330,341,352,364,
	375,386,398,409,421,432,444,455,467,478,489,
	501,512,523,534,545,556,567,578,588,599,609,
	619,629,639,649,658,667,677,685,694,703,711,
	720,728,736,743,751,758,766,773,780,786,793,
	799,805,811,817,823,829,834,839,844,849,854,
	859,863,868,872,876,880,884,888,892,896,899,
	903,906,909,912,915,918,921,924,927,929,932,
	934,937,939,941,943,945,947,949,951,953,955
};

Following algorithm may be used to find the temperature which corresponds to the parameters of the ADC:

// Converting the ADC values to Celsius degrees:
signed short thermistor_calculate_celsius(unsigned short adc_value)
{
	signed short celsius;
 
	// Covering the table backwards:
	for (celsius = max_temp - min_temp; celsius >= 0; celsius--)
	{
		// If the value in the table is the same or higher than measured 
		// value, then the temperature is at least as high as the 
		// temperature corresponding to the element
		if (adc_value >= conversion_table[celsius]))
		{
			// Since the table begins with 0 but values of the elements 
			// from -20, the value must be shifted
			return celsius + min_temp;
		}
	}
 
	// If the value was not found the minimal temperature is returned
	return min_temp;
}

The algorithm searches range from the table where the ADC value is and acquires the lower ranking number of this range. The ranking number marks degrees, adding the primary temperature to this a temperature with accuracy of 1 degree is reached.

This conversion table and function are already in the library of the HomeLab, therefore there is no need to write them for this exercise. In the library the conversion function is named thermistor_calculate_celsius. Must be considered, that the conversion is valid only when used on the thermistor on the Sensors module of the HomeLab. For using other thermistors, a conversion table needs to be created and more complex function described in the manual of the library must be used. Example program of this exercise is a thermometer, which measures temperature in Celsius scale and displays it on an alphabetical LCD.

// Example program of the thermistor of Sensors module
// The temperature is displayed on the LCD
#include <stdio.h>
#include <homelab/adc.h>
#include <homelab/module/sensors.h>
#include <homelab/module/lcd_gfx.h>
#include <homelab/delay.h>
 
// Robotic Homelab II
//#define ADC_CHANNEL 2
 
// Robotic Homelab III
#define ADC_CHANNEL 14
 
// Main program
int main(void)
{
	unsigned short value;
	signed short temperature;	
	char text[16];
 
	// Initialization of LCD
	lcd_gfx_init();
 
	// Clearing the LCD and setting backlight
	lcd_gfx_clear();
        lcd_gfx_backlight(true);
 
	// Name of the program
        lcd_gfx_goto_char_xy(1, 1);
	lcd_gfx_write_string("Thermometer");
 
	// Setting the ADC
	adc_init(ADC_REF_AVCC, ADC_PRESCALE_8);
 
	// Endless loop
	while (true)
	{
		// Reading the 4 times rounded values of the voltage of the 
		// thermistor
		value = adc_get_average_value(ADC_CHANNEL, 4);
 
		// Converting the values of ADC into celsius scale
		temperature = thermistor_calculate_celsius(value);
 
		// Converting the temperature in to text
		// To display the degree sign, the octal variable is 56
		sprintf(text, "%d\56C   ", temperature);
 
		// Displaying the text in the beginning of the third row of the LCD
		lcd_gfx_goto_char_xy(5, 3);
		lcd_gfx_write_string(text);
 
                hw_delay_ms(1000);
	}
        return 0;
}

Photoresistor

Theory

Electrical symbol for a photoresistor
A photoresistor

A photoresistor is a sensor which electrical resistance is altered depending on the light intensity falling on it. The more intense is the light the more free carriers are formed and therefore the lower gets the resistance of the element. Two exterior metal contacts of the photoresistor are reaching through the ceramic base material to the light sensitive membrane, which determines the electrical resistance properties with its geometry and material properties. Since photo sensitive material itself has high resistance, with narrow, curvy track between the electrodes, low total resistance at average light intensity is gained. Similarly to the human eye, the photoresistor is sensitive at certain range of wavelengths and needs to be considered when selecting a photo element, otherwise it may not react to the light source used in the application. Following is simplified list of wavelengths of visible light segmented by colours:

Colour Range of wavelength (nm)
Purple 400 – 450
Blue 450 – 500
Green 500 – 570
Yellow 570 – 590
Orange 590 – 610
Red 610 – 700

A range of working temperature is set for photoresistor. Wishing the sensor to work at different temperatures, precising conversions must be executed, because the resisting properties of the sensors are depending on the temperature of the ambient.

For characterizing light intensiveness physical concept called light intensity (E) is used, this shows the quantity of light reaching any given surface. Measuring unit is lux (lx), where 1 lux represents, the even flow of light 1 lumen, falling on a surface of 1 m2. Hardly ever in reality falls light (living area) on a surface evenly and therefore light intensity is reached generally as a average number. Below are few examples of light intensity for comparison:

Environment Intensity of light (lx)
Full moon 0,1
Dusk 1
Auditorium 10
Class room 30
Sunset or sunrise 400
Operating room (hospital) 500 - 1000
Direct sun light 10000

Practice

The HomeLab is equipped with VT935G photoresistor. One pin of the photoresistor is connected to power supply and second pin to the analogue-digital converter (HomeLab II channel 1, HomeLab III channel 13). Between this pin and the ground resistor is also connected, which forms a voltage divider with the photoresistor. Since the electrical resistance of the photoresistor is decreasing as the light intensity falling on it grows, the measured voltage on the pin of the microcontroller grows as light intensity grows. It is worth to take into account that the photoresistor used in the HomeLab reacts most on orange and yellow light.

The sensor VT935G is not meant to be a specific measuring device. It is meant to be more a device to specify overall lighting conditions – is there a lighted lamp in the room or not. In this case one has to just measure the resistance of the sensor in the half dark room, note it in the program and compare measured values – is it lighter or darker.

The exercise here is a little bit more complex as the light intensity is measured also in lux. For doing this, exists an approximate formula and floating-point variables. In the C-language are floating-point variables float- and double-type variables, which can be used to present fractions. Their flaw is high demand of resources. Computers have special hardware to calculate floating-point variables, in the 8-bit AVR microcontroller calculations are executed in software which demands a lot of memory and time. If the flaws are not critical, the floating-point variables are worth using.

Relationship between resistance (R) of VT935G and intensity of light (E)

There is an approximate formula showing the relationship between the intensity of light and electrical resistance in the sensor datasheet. As seen on the graph (on the right), with using logarithm scale, the resistance and intensity of light are almost in linear relationship and form a in-line formula, because following conversion applies:

log(a/b) = log(a) - log(b)

The relation is characterised by the ascent of the factor γ (ascend of the line), which is 0,9 on VT935G sensor. We have also data on one of the points on that line: resistance 18.5 kΩ (RA) at 10 lx intensity of light (EA). Hence we have the coordinates of one point as well as the ascent of the line and for calculating any other point, we only need one coordinate. Meaning, if sensors' resistance (RB) is measured, it is possible to calculate from the equation of line, the intensity of light EB) that falls on the sensor. Finding EB from the equation of line:

log(EB) = log(RA/RB) / γ + log(EA)

EB = 10log(RA/RB) / γ + log(EA)

This gives the formula for calculating the intensity of light when the resistance is known. The resistance can not be measured directly with microcontroller. For this the photoresistor is in the voltage divider. The output voltage of this voltage divider is converted to a specific variable by the analogue-digital converter (ADC). To find the resistance, the output voltage (U2) of the voltage divider must be calculated first, using the ADC value, also comparison voltage (Uref) of the converter must be taken into account: The formula is following:

U2 = Uref * (ADC / 1024)

From the formula for voltage divider(check the chapter on voltage divider) the resistance of the upper photoresistor (R1) can be found:

R1 = (R2 * U1) / U2 - R2

In the following calculation of voltage and resistance, the known factors are replaced with numbers and indexes have been removed:

U = 5 * (ADC / 1024)

R = (10 * 5) / U - 10

For finding the intensity of light, simplifying conversions can be done:

E = 10log(18.5/R) / 0.9 + 1 = 10log(18.5/R) * 10/9 * 101 =

= 10log18.5*10/9 - logR*10/9 * 10 = (10log18.5*10/9 / 10logR*10/9) * 10 =

= (18.510/9 / R10/9) * 10 = 18.510/9 * 10 * R-10/9

By calculating the constant in front of the variable of the field R, the expression remains follows:

E = 255,84 * R-10/9

These formulas help only if the photoresistor on the module of the HomeLab is used. If circuit is used equipped with different components, respective variables need to be changed. Next, source code of the example program is presented, which measures and calculates using ADC and displays the intensity of light on the LCD.

In the example program variables of voltage, resistance and intensity are defined using type double of floating-point variables. The variables which should be used as floating-point variables must always contain a decimal point (it can be also just 0, because then the compiler understands it correctly).

// HomeLab photoresistor demonstration
// LCD screen displays the approximate illuminance in lux
#include <stdio.h>
#include <math.h>
#include <homelab/module/lcd_gfx.h>
#include <homelab/adc.h>
#include <homelab/delay.h>
 
// Main program
int main(void)
{
	char text[16];
	unsigned short adc_value;
	double voltage, resistance, illuminance;
 
	// Initializing the LCD
	lcd_gfx_init();
 
        // Setting LCD backlight to work
        lcd_gfx_backlight(true);
 
	// Clearing the LCD.
	lcd_gfx_clear();
 
	//Cursor on the position
	lcd_gfx_goto_char_xy(3, 2);
 
	// Name of the program
	lcd_gfx_write_string("Luxmeter");
 
	// Setting the ADC
	adc_init(ADC_REF_AVCC, ADC_PRESCALE_8);
 
	// Endless loop.
	while (1)
	{
		// Reading the average value of the photoresistor
		adc_value = adc_get_average_value(13, 10);
		// HomeLab II
		//adc_value = adc_get_average_value(1, 10);
 
		// Calculating the voltage in the input of the ADC
		// HomeLab II 		
		//voltage = 5.0 * ((double)adc_value / 1024.0);
		// HomeLab III
		voltage = 2.0625 * ((double)adc_value / 2048.0);
 
		// Calculating the resistance of the photoresistor 
                // in the voltage divider
		// HomeLab II
		//resistance = (10.0 * 5.0) / voltage - 10.0;
		// HomeLab III
		resistance = (33.0) / voltage - 10.0;
 
		// Calculating the intensity of light in lux		
		illuminance = 255.84 * pow(resistance, -10/9);
		// Dividing variable into two integer variable
		// to display it on the screen
		int8_t illu = illuminance;
    		int16_t illudp = trunc((illuminance - illu) * 1000);
 
		// Converting the intensity of light to text		
		sprintf(text, "%3u.%3u lux   ", illu,illudp); 
 
		// Displaying it on the LCD
		lcd_gfx_goto_char_xy(3, 3);
		lcd_gfx_write_string(text);
 
		// Delay 500 ms
		sw_delay_ms(500);
	}
}

Infrared distance sensor

Theory

Sharp GP2Y0A21YK

For measuring the distance to an object there are optical sensors using triangulation measuring method. Company “Sharp” produces most common infra-red (IR) wavelength using distance sensors which have analogue voltage output. The sensors made by “Sharp” have IR LED equipped with lens, which emits narrow light beam. After reflecting from the object, the beam will be directed through the second lens on a position-sensible photo detector (PSD). The conductivity of this PSD depends on the position where the beam falls. The conductivity is converted to voltage and if the voltage is digitalized by using analogue-digital converter, the distance can be calculated. The route of beams reflecting from different distance is presented on the drawing next to the text

The route of the light beam from an IR distance sensor

The output of distance sensors by “Sharp” is inversely proportional, this means that when the distance is growing the output is decreasing (decreasing is gradually slowing). Exact graph of the relation between distance and output is usually on the data-sheet of the sensor. All sensors have their specific measuring range where the measured results are creditable and this range depends on the type of the sensor. Maximum distance measured is restricted by two aspects: the amount of reflected light is decreasing and inability of the PSD registering the small changes of the location of the reflected ray. When measuring objects which are too far, the output remains approximately the same as it is when measuring the objects at the maximum distance. Minimum distance is restricted due to peculiarity of Sharp sensors, meaning the output starts to decrease (again) sharply as the distance is at certain point (depending on the model 4-20 cm). This means that to one value of the output corresponds two values of distance. This problem can be avoided by noticing that the object is not too close to the sensor.

Practice

Diagram of voltage-distance of IR distance sensor

The HomeLab set of sensors includes IR distance sensor SHARP GP2Y0A21YK. Measuring range of the sensor is 10 cm – 80 cm. The output voltage of this sensor is, depending on the distance measured, up to 3 V. The distance sensor can be connected to any ADC (the analogue-digital converter) channel of the HomeLab module. On the basis of previous exercises of sensors, it is easy to write a program which measures the output voltage of the distance sensors, but in addition, this exercise includes converting this output voltage to distance.

On the datasheet of the GP2Y0A21YK is graph of relation between its output voltage and measured distance. This graph is not a linear one, however the graph of inverse values of output voltage and distance almost is, and from that is quite easy to find the formula for converting voltage to distance. To find the formula, the points of the same graph must be inserted to any kind of spreadsheet application and then generate a new graph. In spreadsheet programs is possible to calculate automatically the trend-line. Next, the graph of GP2Y0A21YK corrected output voltage inverse value’s relation to the corrected inverse value of measured distance with linear trend-line is presented. To simplify, the output voltage is already converted to 10 bit +5 V values of analogue-digital converter with comparison voltage.

The graph of linearizing ADC value and distance

As seen on the graph, the trend-line (blue) overlaps quite precisely with the points of the graph. Such overlapping is achieved by using the help of the corrective constant. This corrective constant is discovered by using the trial-and-error method – many variables were tested until such was found which made the graph overlap the trend-line the most. This corrective constant of present graph is +2; this means that to all real distances +2 is added. This way the graph is very similar to the linear trend line and a generalization can be made and say that the relation between the distance and the voltage is following:

1 / (d + k) = a * ADC + b

where

  • d - distance in centimeters.
  • k - corrective constant (fund using tial-and-error method)
  • ADC - digitalized value of voltage.
  • a - linear member (value is determined by the trend line equation)
  • b - free member(value is determined by the trend line equation)

Distance d can be expressed from the formula:

d = (1 / (a * ADC + B)) - k

Now it is basically possible to calculate the distance by using this formula, but this requires floating-point calculations, since while dividing fractions will occur. Because the microcontroller operates using integers, the formula must be simplified and converted to larger ratios. Then when dividing the quotient with a linear-member it will look as follows:

d = (1 / a) / (ADC + B / a) - k

When introducing the corrective constant to the formula and also the linear-member and the free-member from the trend-line equation, the formula for calculating the distance will be:

d = 5461 / (ADC - 17) - 2

This formula is computable with 16-bit integers and completely suitable to AVR. Before calculating, must be ensured that the value of the ADC is over 17, otherwise dividing with 0 or negative distance may occur.

Following is the function for converting the values of ADC to centimeters, it is written in the library of the HomeLab. Linear- and free-member and corrective constant are not stiffly written into the function, they are fed with the structure object parameters of the IR distance sensor. By holding the parameters in separate constant, it is easy to add new IR distance sensors to the program.

// The structure of the parameters of the IR distance sensors
typedef const struct
{
	const signed short a;
	const signed short b;
	const signed short k;
}
ir_distance_sensor;
 
// The object of the parameters of GP2Y0A21YK sensor
const ir_distance_sensor GP2Y0A21YK = { 5461, -17, 2 };
 
// Converting the values of the IR distance sensor to centimeters
// Returns -1, if the conversion did not succeed
signed short ir_distance_calculate_cm(ir_distance_sensor sensor,
	unsigned short adc_value)
{
	if (adc_value + sensor.b <= 0)
	{
		return -1;
	}
 
	return sensor.a / (adc_value + sensor.b) - sensor.k;
}

To make the conversion the function ir_distance_calculate_cm must be engaged. The first parameter of this function is the object of the parameters of the IR distance sensor, second is the value of the ADC. The function returns the calculated distance in centimeters. If the operation is wrong (unnatural value of the ADC) the returned value is -1. Following program demonstrates the use of IR distance sensor and conversion function. Graphical LCD is used, where measured results are displayed. If the distance is unnatural “?” is displayed.

// The example program of the IR distance sensor of the HomeLab
// Measured results in centimeters is displayed on the LCD
#include <stdio.h>
#include <homelab/adc.h>
#include <homelab/delay.h>
#include <homelab/module/sensors.h>
#include <homelab/module/lcd_gfx.h>
 
#define ADC_CHANNEL 0
 
// Main program
int main(void)
{	
	signed short value, distance;	
	char text[16];
 
	// Robotic HomeLab II external sensors pin of Sensor module 
	//pin ex_sensors = PIN(G, 0);
	//pin_setup_output(ex_sensors);
	//pin_set(ex_sensors);
 
	// Initialization of LCD
	lcd_gfx_init();	
	lcd_gfx_clear();	
	lcd_gfx_goto_char_xy(1,2);	
	lcd_gfx_write_string("Distance sensor");
 
	// Setup of the ADC
	adc_init(ADC_REF_AVCC, ADC_PRESCALE_8);
 
	// Endless loop
	while (1)
	{
		// Reading the 4 times rounded value of output voltage
		value = adc_get_average_value(ADC_CHANNEL, 4);		
 
		// Conversing ADC value to distance
		distance = ir_distance_calculate_cm(GP2Y0A21YK, value);
 
                lcd_gfx_goto_char_xy(1,3);
 
		// Was the calculation successful?
		if (distance >= 0)
		{			
			// Conversing distance to text
			sprintf(text, "%3d cm   ", distance);
		}
		else
		{		
			// Creating the text for unknown distance
			sprintf(text, "Error   ");
		}
 
		lcd_gfx_goto_char_xy(1,3);
		lcd_gfx_write_string(text);
		sw_delay_ms(500);
	}
}

 

DC motor

Theory

DC motor

Permanent magnet DC motors are very common in different applications, where small dimensions, high power and low price are essential. Due to their fairly high speed, they are used together with transmission (to output lower speed and higher torque).

The perfect graph of relationship between speed (V), current (I), power (P), efficiency (η)and torque (T)of a DC motor.

Permanent magnet DC motors have quite simple construction and their controlling is quite elementary. Although controlling is easy, their speed is not precisely determined by the control signal because it depends on several factors, primarily of the torque applied on the shaft and feeding current. The relationship between torque and speed of a ideal DC motor is linear, which means: the higher is the load on the shaft the lower is the speed of the shaft and the higher is the current through the coil.

Brushed DC motors are using DC voltage and basically do not need special control electronics because all necessary communication is done inside the motor. When the motor is operating, two static brushes are sliding on the revolving commutator and holding the voltage on the coils. The direction of revolving of the motor is determined by the polarity of the current. If the motor must revolve in only one direction, then the current may come through relay or some other simple connection. If the motor has to revolve in both directions, then an electronic circuit called H-bridge is used.

In the H-bridge are four transistors (or four groups) directing the current for driving the motor. The electrical scheme of the H-bridge is similar to the letter H and that is where it gets its name. The peculiarity of the H-bridge is the possibility to apply both directional polarities to the motor. Picture on the side shows the principal scheme of the H-bridge based on the example of the switches. If two diagonal switches are closed, the motor starts operating. The direction of the revolving of the motor depends on in which diagonal the switches are closed. In the real H-bridge the switches are replaced with transistors which are selected according to the current of the motor and voltage.

The working principle of H-bridge used on switches.

H-bridge can also change the direction of rotation than the rotation speed of the motor. There exist also integrated H-bridges, for conducting smaller currents. For higher currents special power MOSFET-s are used. The H-bridge with other electronics is called motor controller or driver.

While the speed of the DC motor is easy to control, there is no guarantee that the desired speed is reached after all. The actual velocity depends on many factors, primarily torque on the output shaft of the motor, current and other motor characteristics. The speed and the output torque of the ideal motor is linearly dependent, i.e. the larger is the output torque, the lower is the speed of the motor, and it consumes more current. This depends on the exact type of motor in case of real motor.

A direct current (DC) motor can be controlled with analog as well as digital signals.

Normally, the motor speed is dependent on the applied voltage at the terminals of the motor. If the motor feed a nominal voltage, it rotates a nominal speed. If the voltage given to the motor is reduced, the motor speed and torque are reduced as well. This type of speed control is also called as analog control. This can be implemented, for example, using a transistor or a rheostat.

DC motors are controlled by microcontrollers, and because microcontrollers are digital devices, it is also reasonable to control the motors digitally. This is achieved by using pulse width modulation (PWM), by switching transistors quickly on - off. The total motor power is something in between standing and full speed. The time of the entire PWM period when transistor is opened, called duty cycle, which is denoted by percent. 0% means that transistor is constantly closed and not conduct, 100% means that transistor is opened and conducts. The PWM frequency should be high enough to prevent vibration of the motor shaft. At low frequencies the motor produces a noise and is therefore used modulating frequency above 20 kHz mostly. However, transistors efficiency is suffering from very high frequencies.

Compared to the analog control a digital control has a number of advantages. The main advantage of microcontroller-controlled systems is that it requires only a single digital output and there is no need for complicated digital-to-analog converter. The digital controlling is also more efficient because less energy is converted into heat.

A simplified control scheme is shown in the next drawing. The control voltage Vc is coming to the microcontroller output pin and switch the transistor Q on-off at a frequency of approximately 20 kHz. When the transistor Q is switched on, then the total current I is going through the motor M. In this case, the transistor behaves as a closed switch and a voltage drop Vq is near 0, and the entire input voltage Vdd remains the engine.

The total power which is passing the transistor can be calculated by the formula:

P = I * V

P = I * Vq, and when Vq ~ 0, then P ~ 0 W

This means that the transistor spend almost no energy in the open state. Similar situation is also the case when the transistor is in the closed state. In this case, there is no current flow through the transistor or the motor. Now the power which is going through the transistor, is calculated as follows:

P = I * Vq, and when I = 0, then P = 0 W

In conclusion, we can say that if the transistor is a switch element on the scheme, then the system efficiency is high and the power used by transistors is low. Compared with a linear (analog) system, where the transistor consumes of the half-open state the same amount of power than the motor, it is a very big energy savings. In practice, there is no lossless system and in fact, the losses occur when the transistor switch one state to other. Therefore, higher losses are occurring when the transistors are switched at higher frequencies.

Practice

The HomLab uses a combined ships to drive DC motors, which includes 2 integrated H-bridges and circuit breaking diodes. The motor is controlled with three digital signals, one of them is operation enabling signal enable and the other two are determining the state of the transistors in the H-bridge. Never can occur that two vertical transistors are opened, because this would short-circuit the power source. This means that the driver is designed as foolproof and only option that can be chosen is which transistor (upper or bottom) of one side of the H-bridge (of “semi-bridge”) is opened. In other words the polarity is selected using two driving signals which is applied to the two ends of the coil of the motor.

The Combo Board of the HomeLab allows connecting up to four DC motors. Basically, for every motor there is a H-bridge which is controlled with two digital output pins of the microcontroller, because the enable pin is constantly high. If both controlling pins have same value, then the motor is stopped if different then it revolves in the corresponding direction. The state of the H-bridge is described in the following table:

Input A Input B Output A Output B Result
0 0 - - The motor is stopped
1 1 + + The motor is stopped
1 0 + - The motor revolves in direction 1
0 1 - + The motor revolves in direction 2

For each motor that is connected to the H-bridge is operated by two of the digital output of the microcontroller. The motor speed is is controlled by timers that generate a continuous PWM signals to the H-bridge, the direction of rotation of the motor is controlled to the second terminal. Motor speed is controlled a relative values from 0 to 255, where 0 means that the motor is standing and 255 is the maximum moving speed of the motor. The following code describes a function’s, which are described in the HomeLab II (ATmega2561) library to control DC motors.

// The setup of the pins driving pins
static pin dcmotor_pins[4][2] =
{
	{ PIN(B, 7), PIN(B, 4) },
	{ PIN(D, 1), PIN(D, 0) },
	{ PIN(D, 7), PIN(D, 6) },
	{ PIN(D, 5), PIN(D, 4) }
};
static int motorindex[4][2] =
{
	{ 0, 1 },
	{ 2, 3 },
	{ 4, 5 },
	{ 6, 7 }
};
// Initializing a PWM to chosen motor
void dcmotor_drive_pwm_init(unsigned char index, timer2_prescale prescaler)
{
  	unsigned char i, pwm;
 
	pin_setup_output(dcmotor_pins[index][0]);
	pin_setup_output(dcmotor_pins[index][1]);
 
	motor[index] = 1;
  	pwm = PWMDEFAULT;
 
  	// Starting all channels
  	for(i=0 ; i<CHMAX ; i++)
  	{
    	  // PWM state variable initialization
    	  compare[i] = pwm;           
    	  compbuff[i] = pwm;          
  	}
 
  	// Starting Timer 2 to normal mode
  	timer2_init_normal(prescaler);
  	// Allow Timer 2 interrupt
  	timer2_overflow_interrupt_enable(true);
 
  	// Enable global interrupts
  	sei();
}
// Generating a PWM for chosen motor
void dcmotor_drive_pwm(unsigned char index, signed char direction, 
unsigned char speed) 
{
	if(direction == -1)
	{
		compbuff[motorindex[index][0]] = 0x00;
		compbuff[motorindex[index][1]] = speed;
	}
	if(direction == 1)
	{
		compbuff[motorindex[index][0]] = speed;
		compbuff[motorindex[index][1]] = 0x00;
	} 
}

The controlling pins of four motor-controllers are determined with the array dcmotor_pins in the library. Before controlling the motors, function dcmotor_drive_pwm_init with the number of the motor-controller (0 – 3) must be called out. It sets the pins as output. It should also set the timer prescaler, for HomeLab II timer2_prescale and for HomeLab III timer_prescale, which determines the frequency of the PWM signal. In case of HomeLab II, as the program does not have functions which are using timer, it is appropriate for the value TIMER2_NO_PRESCALE. When for example an ultrasound sensor are used, then should be chosen TIMER2_PRESCALE 8, otherwise the controller performance may not be sufficient and the sensor readings may be corrupted. This is not applying in the HomeLab III. Higher values of the prescaler are not recommended, because it makes the motor rotation intermittent, and generates vibration.

Function dcmotor_drive_pwm is for control motor speed. This function need three input values: motor number, direction (-1, 0, +1), where -1 is the rotation in one direction, +1 other direction and 0 for stop and thirdly, the speed range of 0-255. The speed value is not linked to a specific rotational speed, it is the relative value between minimal and maximal motor speed. Motor actual speed depends on the motor type, load and the supply voltage. Motor speed accuracy is 8-bits, which means that the minimum control accuracy is 1/255 of the maximum engine speed.

The following is an example program which controls first and second DC motor so that first motor rotates half of the speed and the second motor speed is controlled by a potentiometer.

// Robotic HomeLab DC motor driving example program
#include <homelab/module/motors.h>
#include <homelab/adc.h>
 
// Main program
int main(void)
{
	// Variable of speed
	int speed;
 
	// Start of ADC
	adc_init(ADC_REF_AVCC, ADC_PRESCALE_8);
 
	// DC1 & DC2 motor initialization (without timer prescaler)
	// HomeLab II
	//dcmotor_drive_pwm_init(1, TIMER2_NO_PRESCALE);
	//dcmotor_drive_pwm_init(2, TIMER2_NO_PRESCALE);
	// HomeLab III
	dcmotor_drive_pwm_init(1, TIMER_NO_PRESCALE);
	dcmotor_drive_pwm_init(2, TIMER_NO_PRESCALE);
 
 
	// Endless loop
	while (true)
	{
	   // Reading potentiometer value (average of 4)
	   speed = adc_get_average_value(15, 4);
	   // ADC value is 12-bit but DC motor input is 8-bit
	   // conversion can be ether dividing the value with 8 or
	   // make bit shifting to right 3 times (>>3)
	   dcmotor_drive_pwm(1, 1, speed/8);
	   dcmotor_drive_pwm(2, 1, 128);
	}
}

Servomotor

Theory

RC servo motor
The relationship between width of the signal and position of Servo PWM.

RC (radio-controlled) servo-motors are very common actuator devices in robotics and model building. RC servo motors are consisting of small DC motor, reduction gear and control logic device. Usually the rotor of the servo motor moves to a certain position and tries to maintain that position. The position of the rotor depends of the control signal received by the servo motor. Depending on the type of the motor, the maximum revolving angle of the motor may differ. Servo motors that revolve constantly are rare. In this case, the control signal determines not the revolving angle but the speed of revolving. Servo motor “hack” is also quite common. This makes the position determining servo motor to constantly revolving servo. In this case the feedback potentiometer is replaced by two fixed resistors and the mechanical resistor that prevents full rotations is removed from the gear. A very important characteristic of servo motors is its power-weight ratio.

The controlling signal of servo motor is specific pulse with modulated signal (PWM), where width of the pulse determines the position of the rotor. The period of the signal is 20 ms (50 Hz) and the width of the high period is 1 ms – 2 ms. 1 ms marks one extreme position and 2 ms marks the second one. 1,5 ms marks the middle position of the servo motor’s rotor.

Traditional RC servo motor is also known as analogue-servo motor. It is because in the last decade so called digital servo motors were becoming common. The difference between those two is that in analogue servo motor the motor is controlled by the same 50 Hz PWM input signal. In digital servo motor the motor is controlled by a microcontroller with much higher frequency signal. The input signal is the same in the digital servo motor but higher modulation frequency of the motor enables much more precise and faster position determining.

Practice

On the board of module of motors of the HomeLab are two or four plugs for connecting RC servo motors. The PWM ends of the plugs are connected to the pins of the microcontroller, which alternative functions are outputs of comparing units of the timer. Timer is capable of producing PWM signal and due to that the control of motors is very simple in the program. Only difficulty is set-up of the timer.

The timer must be set up in PWM production mode, where the maximum value of the timer is determined with ICR register. With the maximum value changed in the program and in the pace divider of the timer, the precise PWM frequency for controlling the servo motor can be determined. With the comparison register of the timer, lengths of both high semi periods of PWM signal can be determined. The timers have special comparing units which are monitoring the value of the counter and in case it remains equal with the value of the comparison register they change the output value of comparing units. The following is the program code of the servo motor control library of the HomeLab. For the purpose of functionality, it uses parameters for timers which are determined with macro functions. For example, the period is found using F_CPU constant, which marks the clock rate of the microcontroller. When using macros, there is no need to calculate the parameters of timer for different clock rates and the compiler converts the operations with macros to constants anyway, so the program memory is not growing and does not demand more time. The following example of library is for HomeLab II (ATmega2561).

// The value of the timer (20 ms)for achieving the full period of PWM
// F_CPU is the clock rate of the microcontroller which is divided with 
// 50 Hz and 8
#define PWM_PERIOD      (F_CPU / 8 / 50)
 
// Middle position of PWM servo (5 ms / 20 ms)
// Middle position is 15/200 of full period
#define PWM_MIDDLE_POS  (PWM_PERIOD * 15 / 200)
 
// Factor for converting the percents (-100% to 100%)to periods
// +1 is added to ensure that semi periods would reach to the boundaries 
// of 1 ms and 2 ms or // a little over
#define PWM_RATIO       (PWM_PERIOD / 20 / 2 / 100 + 1)
 
// Set-up of the pins
static pin servo_pins[2] =
{
	PIN(B, 5), PIN(B, 6)
};
 
// Preparing the servo motor for working
void servomotor_init(unsigned char index)
{
	// The pin of PWM signal for output
	pin_setup_output(servo_pins[index]); 
 
	// Setup of timer 1
	// Prescaler = 8
	// Fast PWM mode, where TOP = ICR
	// OUTA and OUTB to low in comparisson
	timer1_init_fast_pwm(
		TIMER1_PRESCALE_8,
		TIMER1_FAST_PWM_TOP_ICR,
		TIMER1_FAST_PWM_OUTPUT_CLEAR_ON_MATCH,
		TIMER1_FAST_PWM_OUTPUT_CLEAR_ON_MATCH,
		TIMER1_FAST_PWM_OUTPUT_DISABLE);
 
	// Determining the period by maximum value
	timer1_set_input_capture_value(PWM_PERIOD);	
}
 
// Determining the position of the servo motor
// The parameter of the position is from -100% to +100%.
void servomotor_position(unsigned char index, signed short position)
{	
	switch (index)
	{
		case 0:			
			timer1_set_compare_match_unitA_value(
				PWM_MIDDLE_POS + position * PWM_RATIO);
			break;
 
		case 1:
			timer1_set_compare_match_unitB_value(
				PWM_MIDDLE_POS + position * PWM_RATIO);
			break;
	}
}

The example program uses described functions of the library of the HomeLab. In the beginning of the program the first servo motor’s PWM signal generator is started with the servomotor_init function. The value of the position of the servo motor is obtained from the channel of the analogue-digital converter, where a potentiometer on the board of sensors is connected. To get the range -100 % - +100 % necessary for controlling the servo motor, half of the maximum (512) is subtracted of the ADC value and the result is divided with 5. The result is +/- 102, but small inaccuracy does not count because servo motors also differ by the relation of the PWM signal and revolving angle. Final PWM‘s semi period’s width in applications has to be determined using test-and-error method. Also the remote controls of RC models have corresponding opportunities for precise setup (trim function). When the program is started the rotors position of the servomotor is changed according to the position of the potentiometer.

// Testing program of the motors module of the HomeLab kit
#include <homelab/adc.h>
#include <homelab/module/motors.h>
 
// Main program
int main(void)
{
	short position;
 
	// Set-up of the ADC
	adc_init(ADC_REF_AVCC, ADC_PRESCALE_8);
 
	// Set-up of the motor
	servomotor_init(1);
 
	// Endless loop
	while (1)
	{
		// Reading the position of the potentiometer and 
		// converting the range of
		// the servo motor
		// For HomeLab II ADC must be read for the corresponding channel, 
		// and use the following formula:
		// position = ((short)adc_get_value(3) - (short)512) / (short)5;
		position = ((short)adc_get_value(15) / 10) - 102 ;
 
		// Determining the position of the servo motor
		servomotor_position(1, position);
	}
}

Stepper motor

Necessary knowledge: [HW] Combo module, [AVR] Digital Inputs/Outputs, [LIB] Motors,
[LIB] Delay

Theory

Stepper-motor

Stepper-motors are widely used in applications which demand accuracy. Unlike DC motors, stepper motors do not have brushes nor commutator – they have several independent coils, which are commutated with exterior electronics (drivers). Rotating the rotor is done by commutating coils step by step, without feedback. This is one of the faults in stepper motors – in case of mechanical overloading, when the rotor is not rotating, the steps will be mixed up and movement becomes inaccurate. Two types of stepper motors are distinguished by coils: unipolar and bipolar stepper motors. By construction three additional segments are considered:

  • Variable Reluctance Stepper (high accuracy, low torque, low price)
  • Permanent Magnet Stepper (low accuracy, high torque, low price)
  • Hybrid Synchronous Stepper (high accuracy, high torque, high price)

Variable reluctance stepper motors have toothed windings and toothed iron rotor. The largest pulling force is when the teeth of both sides are covering each other. In Permanent magnet stepper motor,just like the name hints, are permanent magnets which orientate according to the polarity of the windings. In hybrid synchronous steppers both technologies are used.

Depending on the model of stepper motor, performing one full rotation (360 degrees) of the rotor, demands hundredths of steps of commutations. For stable and smooth movement, appropriate control electronics are used which control the motor according to its parameters (inertia of the rotor, torque, resonance etc.). In addition to control electronics different commutating methods may be applied. Commutating one winding in a row is called Full Step Drive and if the drive is alternated between one and two windings it is called Half Stepping. Cosine micro stepping is also used, allowing specially accurate and smooth controlling.

Unipolar stepper-motor

Unipolar-stepper motor has 5 or 6 leads. According to the scheme of the motor only ¼ of the windings is activated. Vcc lines are usually connected to the positive power supply. During commutation the ends of windings 1a, 1b, 2a and 2b are connected through transistors only to the ground and that makes their control electronics fairly simple.

Bipolar stepper-motor

The windings of an unipolar-stepper motor
The windings of a bipolar stepper-motor.

Bipolar stepper motor differs from unipolar stepper motor by having the polarity of the windings altered during the commutation. Half of the windings are activated together, this allows to gain higher efficiency than unipolar stepper motors. Bipolar stepper motors have four leads, each connected to a different half-bridge. During commutation half-bridges are applying either positive or negative voltage to the ends of the windings. Unipolar motors can be started using bipolar driver: just connect lines 1a, 1b, 2a and 2b of the windings (Vcc will be not connected).

The commutation necessary for controlling stepper-motors with windings at full step mode and half step mode is displayed in the table below. Since in drivers for uni-polar stepper motors only opening of the transistors takes place, the steps are marked by 0 and 1. Controlling of bipolar stepper motors may need more signals and therefore the steps are marked using the polarity of the driver outputs:

Unipolar Bipolar
Step 1A 2A 1B 2B 1A 2A 1B 2B
Full step
1 1 0 0 0 + - - -
2 0 1 0 0 - + - -
3 0 0 1 0 - - + -
4 0 0 0 1 - - - +
Half step
1 1 0 0 0 + - - -
2 1 1 0 0 + + - -
3 0 1 0 0 - + - -
4 0 1 1 0 - + + -
5 0 0 1 0 - - + -
6 0 0 1 1 - - + +
7 0 0 0 1 - - - +
8 1 0 0 1 + - - +

Practice

The Combo Module has a H-bridges to control bipolar stepper motors and the transistor matrix for unipolar stepper motor.

There are functions bipolar_init and unipolar_init in the library of the HomeLab which sets the pins as output and functions bipolar_halfstep and unipolar_halfstep executes revolving by determined half steps. The commutation is done by the table of half steps, but more complex bit operations are used. Unipolar stepper motor is connected to a separate connector Unipolar Stepper, bipolar stepper motor is connected to a DC motor connector, where one of the bipolar motor occupies driver pins of two DC motor. The following code section is HomeLab II (ATmega2561) library functions.

// Preparing for controlling the bipolar stepper motor
void bipolar_init(void)
{
	DDRB |= 0x0F;
	PORTB &= 0xF0;
}
 
// Moving the bipolar stepper motor by half steps
void bipolar_halfstep(signed char dir,
	unsigned short num_steps, unsigned char speed)
{
	unsigned short i;
	unsigned char pattern, state1 = 0, state2 = 1;
 
	// Insuring the direction +- 1
	dir = ((dir < 0) ? -1 : +1);
 
	// Execution of half-steps.
	for (i = 0; i < num_steps; i++)
	{		
		state1 += dir;
		state2 += dir;
 
		// Creating the pattern
		pattern = (1 << ( (state1 % 8) >> 1) ) |
		          (1 << ( (state2 % 8) >> 1) );
 
		// Setting the output.
		PORTB = (PORTB & 0xF0) | (pattern & 0x0F);
 
		// Taking a break to wait for executing the step
		sw_delay_ms(speed);
	}
 
	// Stopping the motor
	PORTB &= 0xF0;
}

Usage of the functions is demonstrated by the example program which rotates the motor alternately to one direction and then to the other direction 200 half steps. The speed of rotating the motor is determined by the length of the brakes made between the steps. If the break is set to be too short, the motor can not accomplish the turn due to the inertia of the rotor and the shaft does not move.

// The test program for the stepper motor of the HomeLab
#include <homelab/module/motors.h>
 
// Main program
int main(void)
{
	// Set up of the motor
	unipolar_init(0);
 
	// Endless loop
	while (true)
	{
		// Turning the rotor 200 half steps to one direction 
		// at speed of 30 ms/step.
		unipolar_halfstep(0,+1, 2000, 30);
 
		// Turning 200 half steps to the other direction 
		// at speed 30 ms/step.
		unipolar_halfstep(0,-1, 2000, 30);
	}
}

TalTech AVR Laboratory Hardware Reference

RTU Nordic Semiconductors nRF Laboratory Hardware Reference

nRF Laboratory Scenarios

RobotNest Nordic Semiconductors nRF Laboratory Hardware Reference

nRF Laboratory Scenarios

RobotNest ESP32 Laboratory Hardware Reference

ESP32 Laboratory Scenarios