=== 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** * Ingmar Sell ** Tallinn University of Technology ** * Raivo Sell, Ph. D., ING-PAED IGIP **Riga Technical University** * Agris Nikitenko, Ph. D., Eng. * Karlis Berkolds, M. sc., Eng. **Silesian University of Technology** * Piotr Czekalski, Ph. D., Eng. * Krzysztof Tokarz, Ph. D., Eng. **IT Silesia** * Łukasz Lipka, M. sc., Eng. **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 co-funded by the European Union. Views and opinions expressed are, however, those of the author or authors only and do not necessarily reflect those of the European Union or the Foundation for the Development of the Education System. Neither the European Union nor the entity providing the grant can be held responsible for them. **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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{ en:iot-open:ccbync.png?100 |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 {{ref>vrelnextgen1}}). 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.
{{ :en:iot-open:practical:text8096-0-3.png?400 |}} 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. Using the system requires registration with a valid email address. A front-page view is present in figure {{ref>vrelsoftware1}}.
{{ :en:iot-open:practical:screenshot_from_2024-01-27_20-34-12.png?600 |}} VREL NextGen software front page
In the following chapters, there is a manual on how to use the system: * [[en:iot-open:practical:software:users]], * [[en:iot-open:practical:software:admins]]. ===== User's Guide ===== Students book a device (or multiple devices) exclusively. Each device has specific hardware and programming features, which are provided in the documentation.\\ There is usually a limited time for device booking, e.g. 2 hours per booking. Students author the code in the web-based editor; depending on the platform, this may also require authoring some configuration files (e.g., ''platformio.ini'', ''makefile'', etc.).\\ Once the code is ready, it can be compiled, and if the compilation is successful, it can be uploaded to the device.\\ Results can be observed via the web camera, presenting device close to real time.\\ Several instances of this software are implemented across consortium partners (details are on [[https://iot-open.eu|IOT-OPEN.EU and IOT-OPEN.EU Reloaded Main Page]], but perhaps the one you may want to start from is an instance implemented in SUT, shared with TalTech, itSilesia and ITT Group: [[https://iot.aei.polsl.pl|SUT's VREL NextGen]]. ==== How to Start ==== Student needs to create an account, virtually as in any other web application (figure {{ref>yb1}}):
{{ :en:iot-open:practical:software:screenshot_from_2025-03-11_12-39-07.png?600 |}} VREL NextGen lab, account creation page
Once the account is created, check your mailbox for an activation link. Activate your account and log in to the system. ==== Devices' Availability ==== Devices are booked exclusively. A limited number of devices is available for everyone. Other devices are provided solely for consortium members. Don't hesitate to contact the supervisor of your labs if you are a consortium student using this laboratory equipment during your regular course and you cannot see other devices than public: you were not added correctly to the student's group. ==== Booking a device ==== The device booking process is straightforward. You can book now, and in the future. The process is described below: - Log in to the system. - Click Bookings button, figure {{ref>yb3}}. - Select the device from the list. Note that you may have access to multiple laboratories, devices, and technologies. Each device represents specific features, so check carefully with the documentation and scenarios you will implement to determine the necessary hardware components for your lab work. In case of doubt, contact the supervisor. A sample list with ESP32 laboratory devices is present in the figure {{ref>yb4}}. - Select the date and time; you can move among dates and book a device in advance! Respect other students and refrain from overbooking more than necessary or as instructed. {{ref>yb5}} - Once booked, switch back to "My Devices" and select a device (figure {{ref>yb6}}) - you can work only during the booked period, and you will be logged off automatically!
{{ :en:iot-open:practical:software:screenshot_from_2025-08-31_15-52-28.png?600 |Device booking process step 1 - go to the list of available devices}} Devices menu - empty booking list
{{ :en:iot-open:practical:software:screenshot_from_2025-08-31_15-52-40.png?600 |Device booking process step 2 - choose a device to book}} Available devices
{{ :en:iot-open:practical:software:screenshot_from_2025-08-31_15-53-07.png?600 |Device booking process step 3 - choose booking time}} Book the device for a specific period
{{ :en:iot-open:practical:software:screenshot_from_2025-08-31_15-53-51.png?600 |}} List of bookings
When the code editing icon is greyed out (<>) as in the figure {{ref>yb6}}, it means that your booking time has passed or it is in advance. ===== Admins'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}).
{{ :en:iot-open:practical:software:vrel.drawio.png?600 |}} 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: - 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. - 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). - Users' projects (source codes) can be cleaned up in the Users menu. - Laboratory compiled and temporary files can be cleaned up in the Devices' context (Devices menu). - 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 {{ref>vrelsoftware2}}) 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 obligatory sections for: * Device reset (reboot, restart) * Commands copying from the frontend/backend to the compiler service: define commands that transfer files to the compilation space. The defaults are for PlatformIO: * copies ''platformio.ini'' to the root of the project, * copies ''main.cpp'' to ''src'' subfolder. * Code compilation, including check of the success with standardised return signalling to the system: * remember to remove previous compilation results before subsequent compilation so that if compilation fails, the existing compiled code won't be mistaken for the new one. * Verification of the successful compilation - enables Upload button - the command should check if compilation was successful and return 1 otherwise 0. * Code execution (i.e. firmware upload) The 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 {{ref>esp32sutnodehardware}} 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 depicted in the figure {{ref>esp32sutnode1}} and referenced by component numbers in the table {{ref>esp32sutnodehardware}}.
{{:en:iot-open:practical:hardware:sut:vrel_nextgen_sut_motherboard.png?560|}} ESP32-S3 SUT Node
^ 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 | 2x16 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, 250x122 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, 128x128 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. |
ESP32-S3 SUT Node Hardware Details
The MCU working behind the laboratory node is ESP32-S3-DevKitM-1-N8 made by Espressif ((https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html)), present in figure {{ref>esp32s3minidevkit}}:
{{:en:iot-open:practical:hardware:sut:20231213_154426.jpg?200|ESP32-S3-DevKitM-1-N8 development kit}} 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: [env:vrelnextgen] 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 {{ref>sutvrelnextgeninfrastructure}} 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. You may still access MQTT messaging via the 2nd MQTT Broker bridged, but it requires specific topic organisation as not all topics are forwarded. Refer to the documentation below. If you're a SUT student or can access the campus network, you can also use 157.158.56.0/24 addresses. Openthread IP6 network with short addressing (up to 65k devices) is bound to the campus network (UDP forwarding, bidirectional) via Openthread BorderRouter. Refer to the diagram below (figure {ref>sutvrelnextgeninfrastructure}) for details.
{{:en:iot-open:practical:hardware:sut:sut_network_infrastructure-new.drawio.png?600|}} 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 four application layer services available: * MQTT broker 1, available on private WiFi network and on the campus LAN network: * IP addresses: 192.168.91.5 (from internal IoT WiFi network), 157.158.56.57 (via the campus network); * Port: 1883 (TCP) * Security: plain text authentication * User: vrel * Pass: vrel2018 * Notice: all messages with a topic starting with public/ will be routed to the public broker via the MQTT bridge (MQTT broker 2, details on the broker below) * MQTT broker 2, in the cloud segment, available virtually from everywhere: * IP addresses: mqtt.iot-open.eu - do not use IP but rather DNS resolution as IP address may change without prior notice; * Port: 8883 (TCP) * Security: SSL/TLS * User: vrel * Pass: vrel2018 * Notice: all messages with topics starting with vrel/ or sut/ will be routed to the MQTT broker in the campus network via the MQTT bridge (MQTT broker 1, details on the broker above) * CoAP server with two 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:/// that brings you a secret code in the message's payload, * GET method for coap:///hello that brings you a hello world welcome message in the payload. * OpenThread border router, capable of routing messages from the OpenThread network to the campus LAN: * Management IP address: 192.168.88.53 (from internal wired network), 157.158.56.57 (via the campus network); * Port for monitoring/management 8888 (WEB GUI interface / ESP32 Openthread Border Router API), URL is http://157.158.56.57:8888/index.html * OpenThread network details: * Channel: 15 * Wake-up Channel: 21 * Channel Mask: 0x07fff800 * Ext PAN ID: dead00beef00cafe * Mesh Local Prefix: fc00:db8:a0:0::/64 * Network Key: 00112233445566778899aabbccddeeff * Network Name: OpenThread-VREL * PAN ID: 0x1234 * PSKc: 104810e2315100afd6bc9215a6bfac53 ===== 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 avoiding rapid changes to display contents, as 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 part of the Embedded Systems Modules. * [[en:iot-open:practical:hardware:sut:esp32:emb5_1]] How do you use LCD (component 5)? * [[en:iot-open:practical:hardware:sut:esp32:emb6_1]] How do you use the ePaper display (component 6)? * [[en:iot-open:practical:hardware:sut:esp32:emb7_1]] How do you use an OLED display (component 7)? * [[en:iot-open:practical:hardware:sut:esp32:emb8_1]] How do you use the Smart LED stripe (component 8)? * [[en:iot-open:practical:hardware:sut:esp32:emb9A_1]] How do you use RGB LED (component 9A)? * [[en:iot-open:practical:hardware:sut:esp32:emb9B_1]] How do you use light and colour intensity sensors (component 9B)? * [[en:iot-open:practical:hardware:sut:esp32:emb10_1]] How do you control a standard miniature servo (component 10)? * [[en:iot-open:practical:hardware:sut:esp32:emb1A_1]] How do you control a FAN (component 1A)? * [[en:iot-open:practical:hardware:sut:esp32:emb1B_1]] How do you use the pressure and environmental integrated sensor (component 1B)? * [[en:iot-open:practical:hardware:sut:esp32:emb2_1]] How do you use a digital potentiometer (component 2)? * [[en:iot-open:practical:hardware:sut:esp32:emb3_1]] How do you use temperature and humidity integrated sensors (component 3)? * [[en:iot-open:practical:hardware:sut:esp32:emb4_1]] How do you use a temperature-only sensor (component 4)? ** Advanced techniques **\\ In the following scenarios, we will focus on advanced programming techniques, such as asynchronous programming and timers. * [[en:iot-open:practical:hardware:sut:esp32:adv1_1]] Using timers to handle the asynchronously periodic data display. ** IoT programming **\\ In the following scenarios, you will write programs interacting with other devices, services, and networks, which are pure IoT applications. * [[en:iot-open:practical:hardware:sut:esp32:iot_1]] Presenting MAC address of the WiFi interface * [[en:iot-open:practical:hardware:sut:esp32:iot_2]] Connecting to the WiFi Access Point and presenting IP * [[en:iot-open:practical:hardware:sut:esp32:iot_3]] Setting-up BT Beacon * [[en:iot-open:practical:hardware:sut:esp32:iot_4]] Scanning nearby BT devices * [[en:iot-open:practical:hardware:sut:esp32:iot_5]] Connecting to the MQTT broker and publishing data * [[en:iot-open:practical:hardware:sut:esp32:iot_6]] Connecting to the MQTT broker and subscribing to the data * [[en:iot-open:practical:hardware:sut:esp32:iot_7]] Publishing a CoAP service * [[en:iot-open:practical:hardware:sut:esp32:iot_8]] Connecting to the CoAP service ==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === 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 = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] To generate an array of bytes representing an image, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] === 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. 60x60 pixels) and convert it to the byte array with b/w settings.\\ Sample project favicon you can use is present in Figure {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:esp32:logo_60.jpg?60 |}} 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 {{ref>iotopenfavicon}} (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 #include #include //Fonts #include 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 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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, 128x128 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] To generate an array of bytes representing an image in 565 format, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] 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 100x100 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 {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:esp32:logo_60.jpg?60 |}} 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 {{ref>iotopenfavicon}} 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: {{ :en:iot-open:practical:hardware:sut:esp32:logo_60.zip | 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 #include #include #include //Fonts #include 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: 128x128 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]] === 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB9A: Use of RGB LEDs === This scenario presents how to handle the brightness control of the tri-coloured LEDs. One is observable via camera, as presented in the figure (component 9A), while another is hidden inside the black enclosure and lights a colour sensor (component 9B). Both LEDs are electrically bound and cannot be controlled independently. Those LEDs have 3 colour channels, controlled independently: R (Red), G (Green) and B (Blue). Mixing of those colours creates other ones, such as pink and violet. Each R G B channel can be controlled with a separate GPIO to switch it on or off or control brightness using a PWM signal, as presented in this tutorial. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the PWM hardware channels of the ESP32 chip. In this case, we do not use an external library; instead, we use built-in tools in the Arduino framework for ESP32 so that no additional libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will light LEDs consecutively with R, G, and B. Use 50% of the maximum brightness. Use a PWM signal to control each GPIO for R, G and B, each colour separately to let you easily observe it. == Start == Assuming you will use 8-bit PWM resolution, the minimum value is 0, and the max (full brightness) is 255. Note that full brightness may be too bright for the observation camera, so consider using a range between 0 and 60 (eventually up to 100). == Steps == To use PWM in ESP32, it is best to use built-in ''ledc*'' functions [[https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/ledc.html|LEDC documentation on the ESP32 manufacturer's page]]. The ''leds'' use timers and have channels attached to the timer. We will use 1 channel per colour (R, G and B, so 3 in total). A PWM frequency is controlled with the timer to be shared for all 3 R, G and B channels. Channels control the PWM duty cycle. = Step 1 = Define some parameters, including channel numbers, PWM resolution (here 8-bit) and PWM frequency (5000Hz): #define RGBLED_B_PIN 26 #define RGBLED_G_PIN 21 #define RGBLED_R_PIN 33 #define PWM1_Ch 5 #define PWM2_Ch 6 #define PWM3_Ch 7 #define PWM_Res 8 #define PWM_Freq 5000 GPIO pins controlling LEDS are 33 (Red), 21 (Green) and 26 (Blue), respectively. = Step 2 = Initialise 3 channels for PWM and make them dark: ledcSetup(PWM1_Ch, PWM_Freq, PWM_Res); ledcSetup(PWM2_Ch, PWM_Freq, PWM_Res); ledcSetup(PWM3_Ch, PWM_Freq, PWM_Res); ledcAttachPin(RGBLED_R_PIN, PWM1_Ch); ledcAttachPin(RGBLED_G_PIN, PWM2_Ch); ledcAttachPin(RGBLED_B_PIN, PWM3_Ch); delay(100); ledcWrite(PWM1_Ch,0); ledcWrite(PWM2_Ch,0); ledcWrite(PWM3_Ch,0); To control the LED (via PWM), use ''ledcWrite(PWM_Channel, duty_cycle_value);''. = Step 3 = Write a loop for each colour (R, G, then B) to light the colour from dark to max value (60 or 100, give it a test). Full duty cycle (255) will be too bright for the remote access video camera to handle it. Use some reasonable range such as 0..60 or 0..100 is strongly advised. Mind to compose code to increase and decrease each colour. A hint is below (PWM Channel 3 so that controls Blue): // Increase brightness for (int dutyCycle = 0; dutyCycle <= 100; dutyCycle++) { // Gradually increase duty cycle for Red LED ledcWrite(PWM3_Ch, dutyCycle); delay(20); // Delay for smooth transition } delay(100); // Decrease brightness for (int dutyCycle = 100; dutyCycle >= 0; dutyCycle--) { // Gradually decrease duty cycle for Red LED ledcWrite(PWM3_Ch, dutyCycle); delay(20); // Delay for smooth transition } There is a number of handy functions in the ''ledc'' library, including (among others): * ''analogWrite(pin,value);'' to keep compatibility with genuine Arduino, * ''ledcFade(pin, start_dutycycle, end_dutycycle, fade_time_ms);'' that you can use instead of the loop above, * ''ledcDetach(pin);'' to detach a pin from the channel (and thus release the PWM channel), * ''ledcWriteNote(pin, note, octave);'' where ''note'' is one of the values: NOTE_C, NOTE_Cs, ... (and so on, music notes, up to NOTE_B) - it is useful when PWM controls a speaker, to generate a perfect musical note, but we do not use speakers here, sorry. == Result validation == You should be able to observe the pulsing colours of the RGB LED, increasing and decreasing brightness linearly. === FAQ === **What if I bind a PWM channel to more than one GPIO?**: As you control the duty cycle writing to the channel rather than the GPIO, you will control those GPIOs simultaneously (in parallel) with the same signal. That can be handy to control parallelly separate devices that should behave the same way, i.e. servos. \\ **What is the maximum number of channels?**: the MCU we use here is ESP32-S3, so it has 8 PWM channels. You can use timers and software implementation of the PWM signal if you need more, but that is a bit tricky and may not be as precise as hardware PWM implementation. \\ **What is the maximum bit resolution for PWM?**: it is between 1 and 14 bits in this particular MCU. \\ **What PWM frequency should I use?**: there is no straightforward answer to this question: assuming you observe LED remotely with a camera, even 50Hz would be enough. But it would give a severe flickering experience to the live user, on the other hand. In the example above, we propose 5kHz, which this MCU can easily handle. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB9B: Reading colour sensor === A colour sensor (TCS 34725) can detect the brightness and colour of the light emitted. It works with the I2C; in our laboratory, each sensor has a fixed 0x29 address in the I2C bus. The sensor is in the black enclosure, ensuring no ambient light impacts readings. The only light source is an RGB LED, controlled as described in the scenario [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. This is another LED connected parallel to the one you can observe in the camera. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. To simplify TCS sensor use, we will use a dedicated library: lib_deps = adafruit/Adafruit TCS34725@^1.4.2 Also, remember to add the LCD handling library, as present in the EMB9 scenario, unless you decide to use another output device. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[en:iot-open:hardware2:sensors_optical|]] * [[https://cdn-shop.adafruit.com/datasheets/TCS34725.pdf|TCS sensor documentation]] === Hands-on Lab Scenario === == Task to be implemented == Create a solution where you control an RGB LED and read its colour and brightness using a TCS sensor. Present results on the LCD, e.g. in the form of "Colour: " where "value" refers to the colour intensity. You should change LED brightness at least 10 times (10 different brightness) for each colour while continuously reading the TCS sensor and presenting data in the LCD. Experiment with mixing colours and check either the sensor can distinguish brightness separately, when i.e. both RGD LED's colours are on: Red+Green or Red+Blue (or any other combination you wish). There are at least two ways to handle this task: * simple: a dummy loop where you set RGB LED values, read from TCS and display on LCD, * advanced: where the data is read and presented on the LCD in one or two asynchronous routines run by timers. == Start == Check if you can see the LCD and RGB LED in the camera view. Remember not to use the full brightness of the RGB LED because you won't be able to observe other elements due to the camera's low dynamics. Assuming you use 8-bit PWM to control LED, we suggest a range between 0 and 60 (eventually 0 and 100), but never up to 255. == Steps == In the steps below, we present only the part for reading from the TCS sensor. Control of RGB LED with PWM and handling LCD is presented in the other lab scenarios that you need to follow accordingly and merge with the code below. = Step 1 = Included necessary libraries: #include #include "Adafruit_TCS34725.h" = Step 2 = Declare and instantiate the TCS controller class and related variables for readings: #define SCL 4 #define SDA 5 static Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_300MS, TCS34725_GAIN_1X); uint16_t r, g, b, c, colorTemp, lux; static bool isTCSOk = false; A word of explanation regarding the parameters: * ''TCS34725_INTEGRATIONTIME_300MS'' is the time in ms while the TCS sensor is exposed for the light to grab readings, * ''TCS34725_GAIN_1X'' is the sensor's gain - it helps reading in low light, but that is not true in our lab as our RGB LED light sensor is very bright, even in low duty cycle. Refer to the Q&A section for the ranges. = Step 3 = Initialise the sensor (in ''void Setup()'') and check it is OK: Wire.begin(SDA,SCL); delay(100); ... isTCSOk = tcs.begin(); You communicate with the TCS sensor via the I2C interface. In standard and typical configurations, there is no need to instantiate the ''Wire'' I2C communication stack explicitly, nor provide a default I2C address of the TCS sensor (0x29), so ''begin'' is parameterless. Other overloaded functions for the ''begin'' let you provide all technical parameters. You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Besides reading raw values for channels R, G, B and C, the ''tcs'' controller class provides additional functions to calculate colour temperature (in K) and colour intensity in Luxes. To read, use the following code: if(isTCSOk) { tcs.getRawData(&r, &g, &b, &c); colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c); lux = tcs.calculateLux(r, g, b); } R, G, and B reflect filtered values for Red, Green and Blue, respectively, while C (clear) is a non-filtered read that reflects brightness. Please be aware that there is no straightforward relation between R, G, B and C channels. == Result validation == Driving R, G and B channels for RGB LED that lights the sensors, you should be able to observe changes in the readings, accordingly. === FAQ === **What is the range of the values for the //integration time// of the TCS sensor?**: #define TCS34725_INTEGRATIONTIME_2_4MS \ (0xFF) /**< 2.4ms - 1 cycle - Max Count: 1024 */ #define TCS34725_INTEGRATIONTIME_24MS \ (0xF6) /**< 24.0ms - 10 cycles - Max Count: 10240 */ #define TCS34725_INTEGRATIONTIME_50MS \ (0xEB) /**< 50.4ms - 21 cycles - Max Count: 21504 */ #define TCS34725_INTEGRATIONTIME_60MS \ (0xE7) /**< 60.0ms - 25 cycles - Max Count: 25700 */ #define TCS34725_INTEGRATIONTIME_101MS \ (0xD6) /**< 100.8ms - 42 cycles - Max Count: 43008 */ #define TCS34725_INTEGRATIONTIME_120MS \ (0xCE) /**< 120.0ms - 50 cycles - Max Count: 51200 */ #define TCS34725_INTEGRATIONTIME_154MS \ (0xC0) /**< 153.6ms - 64 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_180MS \ (0xB5) /**< 180.0ms - 75 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_199MS \ (0xAD) /**< 199.2ms - 83 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_240MS \ (0x9C) /**< 240.0ms - 100 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_300MS \ (0x83) /**< 300.0ms - 125 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_360MS \ (0x6A) /**< 360.0ms - 150 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_401MS \ (0x59) /**< 400.8ms - 167 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_420MS \ (0x51) /**< 420.0ms - 175 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_480MS \ (0x38) /**< 480.0ms - 200 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_499MS \ (0x30) /**< 499.2ms - 208 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_540MS \ (0x1F) /**< 540.0ms - 225 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_600MS \ (0x06) /**< 600.0ms - 250 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_614MS \ (0x00) /**< 614.4ms - 256 cycles - Max Count: 65535 */ **What is the range of the values for the //gain// of the TCS sensor?**: typedef enum { TCS34725_GAIN_1X = 0x00, /* No gain */ TCS34725_GAIN_4X = 0x01, /* 4x gain */ TCS34725_GAIN_16X = 0x02, /* 16x gain */ TCS34725_GAIN_60X = 0x03 /* 60x gain */ } tcs34725Gain_t; **C channel reading is 0, and R, G or B readings are unreasonable.** It is possibly because of overdriving the sensor with a light source (too bright). Consider lowering the RGB LED's intensity or changing the sensor's integration time and gain (shorter time, lower gain). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] * [[en:iot-open:hardware2:actuators_motors|]] === 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 #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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB1A: Controlling a FAN with PWM === This scenario presents how to handle the rotation speed of the fan. It is done using a PWM signal, as presented in this tutorial. The fan pumps the air into the yellow pressure chamber with a pressure sensor inside. Because it is pretty hard to monitor rotation speed with the camera, this scenario should be run along with scenario following scenario [[en:iot-open:practical:hardware:sut:esp32:emb1b_1]] that will indirectly measure rotation speed using pressure changes. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the ESP32 chip's PWM hardware channels. In this case, we do not use an external library; instead, we use built-in tools in the Arduino framework for ESP32, so no additional libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will spin the fan using a PWM signal. Execute your experiment once, e.g. in the ''setup()'' function, but not infinitely, in the ''loop()''. We appreciate letting you keep your fan off when finishing your experiments. They are very noisy, and our servers love silence! == Start == Assuming you will use 8-bit PWM resolution, the minimum value is 0, and the max (full speed) is 255. == Steps == To use PWM in ESP32, it is best to use built-in ''ledc*'' functions [[https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/ledc.html|LEDC documentation on the ESP32 manufacturer's page]]. The "leds" use timers and have channels attached to the timer. We will use one channel numbered 0. The channel controls the PWM duty cycle. Do not be misled by the "leds" functions: they generate a PWM signal, and it is meaningless whether you control an LED, bulb, or fan (as here). = Step 1 = Define some parameters, including channel numbers, PWM resolution (here 8-bit) and PWM frequency (1000Hz): #define FAN_PIN 35 #define FAN_PWM_Ch 0 #define FAN_PWM_FREQ 1000 #define FAN_PWM_RESOLUTION 8 GPIO pins controlling the fan is 35. = Step 2 = Initialise fan PWM channel and make it to stop (duty cycle 0): ledcSetup(FAN_PWM_Ch, FAN_PWM_FREQ, FAN_PWM_RESOLUTION); //Initialise channel ledcAttachPin(FAN_PIN, FAN_PWM_Ch); //Bind it to the PWM delay(100); ledcWrite(FAN_PWM_Ch,0); //Write to CHANNEL, not to PIN! = Step 3 = To control the fan rotation speed (via PWM), use ''ledcWrite(FAN_PWM_Ch, duty_cycle_value);''.\\ Note you write to **channel**, not to the **pin**!\\ A common source code mistake causes the fan not to operate correctly. A ''ledcWrite'' with a ''duty_cycle_value'' equal to 0 causes the fan to stop.\\ The maximum ''duty_cycle_value'' is determined by the ''FAN_PWM_RESOLUTION'', which is 8-bit in our case, so maximum is 255. In that case, the fan operates at full speed. There is a number of handy functions in the ''ledc'' library, including (among others): * ''analogWrite(pin,value);'' to keep compatibility with genuine Arduino, * ''ledcFade(pin, start_dutycycle, end_dutycycle, fade_time_ms);'' that you can use instead of the loop to slow down rotation gently, * ''ledcDetach(pin);'' to detach a pin from the channel (and thus release the PWM channel), * ''ledcWriteNote(pin, note, octave);'' where ''note'' is one of the values: NOTE_C, NOTE_Cs, ... (and so on, music notes, up to NOTE_B) - it is useful when PWM controls a speaker, to generate a perfect musical note, but we do not use speakers here, sorry. == Result validation == You can observe rotation change by measuring air pressure in the chamber only. To distinguish whether the fan is stopped or rotating, you can observe it via the video stream. === FAQ === **The fan rotates itself. Why?**: It is connected via the MOS FET transistor that tends to saturate when the GPIO (35) is not controlling it. So, to ensure the fan is stopped, bind a PWM channel to it and force the duty cycle set to 0. **The fan is not rotating?**: Besides possible configuration and programming errors, it is essential to understand that setting duty cycle, e.g. to 1, won't start spinning: a minimum value (a threshold) causes rotation. Moreover, the relation between rotation speed, air pressure and duty cycle controlling the fan is not linear. You may also be a victim of the common coding mistake of using a GPIO number instead of the channel number (first parameter) in the ''ledcWrite'' function: use the PWM channel number! \\ **What is the maximum number of channels?**: the MCU we use here is ESP32-S3, so it has 8 PWM channels. You can use timers and software implementation of the PWM signal if you need more, but that is a bit tricky and may not be as precise as hardware PWM implementation. \\ **What is the maximum bit resolution for PWM?**: In this particular MCU, it is between 1 and 14 bits. \\ **What PWM frequency should I use?**: there is no straightforward answer to this question, but setting too low a frequency will cause the inability to control the fan: 1000Hz (1kHz) seems reasonable and has been tested with this configuration. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB1B: Reading environmental data with a Bosch integrated sensor === We will read environmental data using a BME 280 sensor in this scenario. It is one of the most popular sensors in weather stations. It integrates a single chip's digital thermometer, hygrometer (air humidity), and air pressure meter. This sensor is located inside the yellow pressure chamber, under the fan in our laboratory nodes.\\ The sensor communicates with the microcontroller using I2C. In all our laboratory nodes, it uses the I2C bus on GPIOs 5 (SDA) and 4 (SCL) and is visible under the I2C address 0x76.\\ This scenario can be run stand-alone to read weather data in the laboratory nodes' room. Still, it is also complementary to the scenario EMB1A [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]] and may enable you to monitor the results of the fan operation that should induce changes in the air pressure. === Prerequisites === The static temperature, humidity and air pressure values can be read using a dedicated library: lib_deps = adafruit/Adafruit BME280 Library@^2.2.2 To observe air pressure changes over a short time, it is necessary to implement fan PWM control as described in the [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]].\\ 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. To implement monitoring of the air pressure changes, understanding how to control a fan with PWM is necessary: * [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]]. Technical documentation for the BME 280 sensor is available here: * [[https://www.mouser.com/datasheet/2/783/BST-BME280-DS002-1509607.pdf|BME 280]] Besides BME 280, there is another sensor in the Bosch family: BMP 280. The former one does not measure humidity, just temperature and air pressure. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] === Hands-on Lab Scenario === In this scenario, we only focus on reading the sensor. 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, air pressure, and humidity on any display (e.g. LCD). Remember to add units (C, %Rh, hPa). == Start == For static measurements, ensure the fan is stopped. Note that the fan tends to spin up on itself (explained in EMB1A) when the GPIO controlling the fan is not configured, so it is better to ensure it is set to output and low (0) to keep the fan stopped. Refer to the [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]] for details on controlling the fan. == 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, and also with a scenario EMB1A presenting no instructions on controlling the fan that can change the air pressure in the yellow pressure chamber. = Step 1 = Include a BME 280 control library: #include = Step 2 = Declare BME's address, sensor controller class and variables to store readings: #define SCL 4 #define SDA 5 static const int BME280_addr = 0x76; //I2C address static bool isBMEOk = false; static float temperature; static float pressure; static float humidity; static Adafruit_BME280 bme280; //controller class = Step 3 = Initialise the I2C bus and the controller class: Wire.begin(SDA,SCL); delay(100); ... isBMEOk = bme280.begin(BME280_addr); If ''begin'' returns ''false'', then initialisation failed. This may be due to an invalid I2C address provided as a parameter of the ''begin'' function, a broken sensor, or broken connections between the MCU and the sensor. You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Read environmental data (one at a time): temperature = bme280.readTemperature(); pressure = bme280.readPressure() / 100.0F; humidity = bme280.readHumidity(); The temperature is Celsius, air pressure is in Pascals (so we divide it by float 100 to obtain the hPa reading), and relative air humidity is in % (frequently referenced as %Rh). Note that the controller class has an exciting function of trading Altitude based on the sea-level pressure (needed to have a correct reading: float altitude = bme280.readAltitude(1013.00F); You need to know the sea level pressure (a parameter, here, 1013hPa). It uses ''readPressure()'' internally and returns the altitude level. Note that due to the non-linear characteristics of the air pressure drop with increasing altitude, it does not work correctly at high altitudes. The library also has a mathematical calculation function that returns the current sea level pressure if only altitude and local air pressure are known. It does not read the sensor itself, however: float seaLevelPressure = bme280.seaLevelForAltitude(230, bme280.readPressure()); In the example above, the first parameter is the altitude (230m). == Result validation == The observable temperature is usually within the range of 19-24C, with humidity about 40-70%, strongly depending on the weather. On rainy days, it can even go higher. Air pressure depends on the current weather (assuming the fan is off) and is usually low to 890hPa (when low-pressure area) and even up to 1040hPa (when high-pressure area comes, usually during the summer). Spinning the fan may easily change air pressure by at least 1-2Pa up relative to the static pressure, depending on the fan's rotation speed. === FAQ === **I've got NaN (Not a Number) readings. What to do?**: Check if GPIO is OK (should be 47), check if you initialised the controller class and most of all, give the sensor some recovery time (at least 250ms) between consecutive readings. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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) {{ref>figuredigipot}}. 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.
{{ :en:iot-open:practical:hardware:sut:esp32:screenshot_from_2024-03-21_22-03-39.png?600 | Digital potentiometer DS1803 application in VREL Next Gen nodes }} 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[https://www.analog.com/en/products/ds1803.html| DS1803 documentation (external link)]] === 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) uses 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 128x128 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); You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Read the loopback characteristics of the digital potentiometer to the 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 [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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 {{ref>figuredigipot}} 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:emb8_1]] * [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === 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 #include = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:1wire|]] === 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 [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]). 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 #include 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 = 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 [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. == 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]. 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/timer.html|ESP32 Hardware Timers]] === 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 [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. == 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 #include = Step 2 = Define LCD configuration pins (see details and explanation in the scenario: [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]): #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 can circumvent this limitation by using smart handlers that have, for example, an internal counter and execute every Nth cycle internally. This helps simulate multiple timers in a software-based manner. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT1: Reading MAC address of the WiFi === Each network IP card is supposed to have a unique MAC address. ESP32 chip has built-in MAC. MAC can be used to identify devices, but note that it is not a "strong" ID: it can be programmatically changed and easily discovered. In the following scenario, we only present how to read the MAC address. A part regarding displaying on the selected screen is up to the developer. You can refer to the appropriate scenario, as listed below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. A WiFi library is already included in the Arduino framework for ESP32, so there is no need to add it to the ''platformio.ini'' explicitly. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Present a MAC address on the selected display. The steps below present only the reading part, not a display. Handling a display is presented in the EMBx scenarios, as listed above. == 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 WiFi management library in your source code: #include The WiFi library automatically initialises a singleton class ''WiFi'' that you can use to set up working mode, read MAC, and perform many other operations. = Step 2 = Reading the MAC as a ''String'' is as easy as simply calling: WiFi.macAddress(); == Result validation == Using another node should change the MAC read. Book another device and discover its MAC. === FAQ === **Can I change MAC?**: Actually, yes, you can. It is not advised, however, because you may accidentally generate an overlapping address that will collide with another device in the same network. You must first explicitly configure the ESP32 chip to work as an AP (Access Point, Server) or STA (WiFi Client) to do it. Sample stub code (for STA) may look as follows: #include #include uint8_t newMAC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE}; //Array of bytes with new MAC void setup() { WiFi.mode(WIFI_STA); esp_wifi_set_mac(WIFI_IF_STA, &newMAC[0]); } === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT2: Reading IP address of the WiFi === On the networking level, IP devices are identified by MAC. In the case of our laboratory and network, you will obtain the IP address from the DHCP server integrated with the WiFi access point. To connect to the WiFi network, one needs to use credentials that are present in the general laboratory description, available here: In this scenario, you will learn how to set up your device in STA (client to the AP) mode, connect to the AP, and read the obtained IP address. This scenario does not contain a UI part; it is up to the developer to implement it using one of the scenarios presented below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. A WiFi library is already included in the Arduino framework for ESP32, so there is no need to add it to the ''platformio.ini'' explicitly. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Connect to the "internal IoT" WiFI access point. Present the obtained IP address on the selected display. Handle critical situations such as no access and present an appropriate message on the screen (e.g., an error message). The steps below present only the reading part, not a display. Handling a display is presented in the EMBx scenarios, as listed above. == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. == Steps == = Step 1 = Include the WiFi management library in your source code: #include The WiFi library automatically initialises a singleton class, ''WiFi,'' which you can use to set up working mode, read MAC and IP, and perform many other operations. = Step 2 = Declare some constants, including AP SSID and passphrase and a variable to store IP: const char* ssid = "REPLACE_WITH_CORRECT_SSID"; const char* pass = "REPLACE_WITH_CORRENT_PASSPHRASE"; IPAddress localIP; Both ''ssid'' and ''pass'' are to be obtained from the hardware reference, available here: [[en:iot-open:practical:hardware:sut:esp32|]]. = Step 3 = Set your device in the STA mode and connect to the WiFi AP: WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { //Drop some info on display about connecting delay(1000); } The ''WiFi.begin(...)'' returns the following statuses (value, enumerator: meaning): * 0, ''WL_IDLE_STATUS:'' temporary status assigned when WiFi.begin() is called * 1, ''WL_NO_SSID_AVAIL:'' when no SSID are available * 2, ''WL_SCAN_COMPLETED:'' scan networks is completed * 3, ''WL_CONNECTED:'' when connected to a WiFi network * 4, ''WL_CONNECT_FAILED:'' when the connection fails for all the attempts * 5, ''WL_CONNECTION_LOST:'' when the connection is lost * 6, ''WL_DISCONNECTED:'' when disconnected from a network = Step 4 = Reading the IP as a ''String'' is as easy as simply calling: localIP = WiFi.localIP(); The ''IPAddress'' class contains the ''toString()'' method which formats the IP and returns in standard, dot-separated, four-octet text format. = Step 5 = Explicitly disconnect from the WiFi AP to free resources: WiFi.disconnect(); Some useful WiFi functions are listed below: * ''WiFi.reconnect()'' - reconnects the connection that was dropped, note it uses ''ssid'' and ''pass'' previously specified in the ''WiFi.begin(...)'', here you do not provide one. * ''WiFi.RSSI()'' - once connected, you can get signal strength as ''int8_t'' integer. * ''WiFi.setHostname(const char * hostname)'' - set host name for the ESP32 chip. Remember to use this function before connecting to the AP. == Result validation == You should be able to connect to the WiFi and present the dynamically assigned IP address by the DHCP server. Note that due to the dynamic nature of the lab, IP addresses can change both during connection (on lease refresh) or between consecutive connects. === FAQ === **I set a hostname, but it does appear on the router level.**: There are many possible reasons; one is an active registration in the router (AP) that keeps an old IP address, so you need to wait until it expires; another reason is you have changed the hostname when you were connected to the AP.\\ **Can I use a manually configured IP?**: Actually, you can, but we strongly discourage it. You may accidentally overlap this IP address with some other device, router, or infrastructure, blocking its operation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT3: Connecting to the MQTT and publishing data ==== In the following scenario, you will learn how to connect to the MQTT broker and publish a message. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]. There are many implementations of the MQTT protocol, but we will use the following library: lib_deps = knolleary/PubSubClient@^2.8 === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]] * [[en:iot-open:networking2:applicationnetworkprotocols|]]. === Hands-on Lab Scenario === Note - this scenario can be used in pair with [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]] to build a publish-subscribe solution using two devices (sender and receiver). You need to book two devices then and develop them in parallel. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then publish an MQTT message of your choice. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ == Steps == = Step 1 = Once the device is booked, check if your display of choice is visible in the camera's FOV.\\ Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server. = Step 2 = Include the MQTT implementation library header in your code: #include = Step 3 = Declare necessary addresses, constants, etc.: IPAddress mqttServer(127,0,0,1); //change it to the MQTT broker IP #define mqtt_user "mqtt user" #define mqtt_password "mqtt password" #define mqtt_client_id "some_unique_client_id" #define mqtt_topic "/sample/topic/comes/here/change/it/please" #define mqtt_payload "sample payload" Refer to the technical documentation (nodes) or the supervisor's guidance if working in the interactive mode to obtain the ''mqttServer'' IP address, the ''mqtt_user'' and ''mqtt_password''.\\ **Remember to choose some unique client ID and topic!** = Step 4 = Declare WiFi communication client and MQTT communication client: WiFiClient espClient; PubSubClient client(espClient); Note that your clients are not yet online! = Step 5 = Set MQTT client's configuration (proposed in the ''void setup()'' function: ... client.setServer(mqttServer,1883); //default port is 1883, change if needed ... = Step 6 = Finally, connect the MQTT client to the MQTT broker and publish a message (sample code, adjust to your case): while (!client.connected()) { if (client.connect(mqtt_client_id,mqtt_user,mqtt_password)) { // Drop some info on the display that the MQTT broker is connected client.publish(mqtt_topic,mqtt_payload); } else { int status = client.state(); //read error code //present it on the display to trace/troubleshoot } } In the case, the client does not connect to the MQTT broker, the ''client.state();'' returns an int that can be interpreted as below: // Possible values for client.state() #define MQTT_CONNECTION_TIMEOUT -4 #define MQTT_CONNECTION_LOST -3 #define MQTT_CONNECT_FAILED -2 #define MQTT_DISCONNECTED -1 #define MQTT_CONNECTED 0 #define MQTT_CONNECT_BAD_PROTOCOL 1 #define MQTT_CONNECT_BAD_CLIENT_ID 2 #define MQTT_CONNECT_UNAVAILABLE 3 #define MQTT_CONNECT_BAD_CREDENTIALS 4 #define MQTT_CONNECT_UNAUTHORIZED 5 In many code samples, including those provided along with this MQTT client library, there is a ''client.loop();'' function executed in the main ''void loop()'' body. While it is obligatory in the case of the use of asynchronous sending and receiving of the MQTT messages, in the simple case as above, it can be abandoned because ''client.publish(topic,payload);'' makes a blocking call and ensures sending of the message to the MQTT broker. == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then publish a message (once or periodically). Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a subscriber (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to observe messages that you publish. === FAQ === **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **How do I observe messages that I send?**: Use a software client, such as [[http://mqtt-explorer.com/| MQTT Explorer]], if you're able to access the "internal IoT" network (you're in the range of the network). If you're remote, the only way is to book another device and implement a client subscribing to your message, as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]]. Our MQTT broker is also visible in the campus network on the wired interfaces so that you can access it, e.g. via EduVPN or from the laboratory computers. Refer to the supervisor for IP and credentials.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT4: Connecting to the MQTT and subscribing to the messages ==== In the following scenario, you will learn how to connect to the MQTT broker and subscribe to the message. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]. There are many implementations of the MQTT protocol, but we will use the following library: lib_deps = knolleary/PubSubClient@^2.8 === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === Note—this scenario can be used in tandem with [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]] to build a publish-subscribe solution using two devices (two laboratory nodes: publisher and subscriber). You need to book two devices then and develop them in parallel. Watch the result validation section for more options. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then subscribe to the MQTT message of your choice, identified by the topic (may include wildcards). Present the message payload on the display. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ == Steps == = Step 1 = Once the device is booked, check if your display of choice is visible in the camera's FOV.\\ Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server.\\ Note - as you're consuming messages here, you either need to have implemented and running publisher as a separate device (as described in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]) or to connect to the MQTT broker using some software, as described in the results validation section below. = Step 2 = Include the MQTT implementation library header in your code: #include = Step 3 = Declare necessary addresses, constants, etc.: IPAddress mqttServer(127,0,0,1); //change it to the MQTT broker IP #define mqtt_user "mqtt user" #define mqtt_password "mqtt password" #define mqtt_client_id "some_unique_client_id" #define mqtt_topic "/sample/topic/comes/here/change/it/please" Refer to the technical documentation (nodes) or the supervisor's guidance if working in the interactive mode to obtain the ''mqttServer'' IP address, the ''mqtt_user'' and ''mqtt_password''.\\ **Remember to choose some unique client ID and topic corresponding to the one that is being published to the MQTT broker!** You can use wildcards when subscribing to the broker. Still, please note that using a single star to subscribe to ALL MQTT messages is not a good option as there are many technical topics published by the broker internally, so the number of messages incoming every second to your solution can quickly overwhelm your device's resources. = Step 4 = Declare WiFi communication client and MQTT communication client: WiFiClient espClient; PubSubClient client(espClient); Note that your clients are not yet online! = Step 5 = Declare an asynchronous handler function that will be called every time the client receives the MQTT message: void callback(char* topic, byte* payload, unsigned int length) { //prepare a code to display the message (payload) to the display } Note that the payload is a byte array, so you may need to copy it to process it outside the callback function (e.g. using ''memcpy'').\\ Also, note that you may distinguish which message you're handling if you subscribe using wildcards: the ''topic'' parameter provided the exact topic as it was published to the MQTT broker by the publisher. = Step 6 = Set MQTT client's configuration (proposed in the ''void setup()'' function: ... client.setServer(mqttServer,1883); //default port is 1883, change if needed client.setCallback(callback); //a callback function to handle incoming messages //as declared in Step 5 ... = Step 7 = Finally, connect the MQTT client to the MQTT broker and publish a message (sample code, adjust to your case): while (!client.connected()) { if (client.connect(mqtt_client_id,mqtt_user,mqtt_password)) { // Drop some info on the display that the MQTT broker is connected client.subscribe(mqtt_topic); } else { int status = client.state(); //read error code //present it on the display to trace/troubleshoot } } In the case, the client does not connect to the MQTT broker, the ''client.state();'' returns an int that can be interpreted as below: // Possible values for client.state() #define MQTT_CONNECTION_TIMEOUT -4 #define MQTT_CONNECTION_LOST -3 #define MQTT_CONNECT_FAILED -2 #define MQTT_DISCONNECTED -1 #define MQTT_CONNECTED 0 #define MQTT_CONNECT_BAD_PROTOCOL 1 #define MQTT_CONNECT_BAD_CLIENT_ID 2 #define MQTT_CONNECT_UNAVAILABLE 3 #define MQTT_CONNECT_BAD_CREDENTIALS 4 #define MQTT_CONNECT_UNAUTHORIZED 5 = Step 8 = Run client handling routine in the loop to receive messages: void loop() { if (!client.connected()) { //reconnect the client (and eventually the WiFi if gone) } client.loop(); //process incoming MQTT messages } == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then subscribe to the message (identified by topic). Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a publisher on another laboratory node (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to send messages that you subscribe to. === FAQ === **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **How do I send messages to which I am subscribed?**: Use a software client, such as [[http://mqtt-explorer.com/| MQTT Explorer]], if you're able to access the "internal IoT" network (you're in the range of the network). If you're remote, the only way is to book another device and implement a client publishing a message with the appropriate topic, as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]. Our MQTT broker is also visible in the campus network on the wired interfaces so that you can access it, e.g. via EduVPN or from the laboratory computers. Refer to the supervisor for IP and credentials.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT5: Setting up a CoAP service ==== The following scenario will show you how to create a CoAP server on the IoT end node device.\\ A CoAP service is an endpoint that provides information. It is UDP-based, and the protocol configuration (confirmable and non-confirmable messages) requires more than one handler to be implemented in complex and fully implemented scenarios. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]], * [[en:iot-open:practical:hardware:sut:esp32:emb9a_1]] - we use this device to present CoAP via the remote video stream; you can choose other scenarios as well, but understanding how to control at least one of the actuators is essential. There are many implementations of the CoAP protocol, but we will use the following library: lib_deps = hirotakaster/CoAP simple library === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]], * [[en:iot-open:networking2:applicationnetworkprotocols|]]. === Hands-on Lab Scenario === Note—this scenario can be used in tandem with [[[en:iot-open:practical:hardware:sut:esp32:iot_6|]] to build a client-server solution using two devices (CoAP server and CoAP client). You need to book two devices then and develop them in parallel. We suggest connecting them over the AP rather than setting up an access point on the ESP32 device. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Then, set up a CoAP server, sending on its endpoint a "Hello World" message or any customized message of your choice; note to keep it short in case you visualise it on another node's display with limited capabilities. Another endpoint should present any data on the selected sensor (e.g., temperature and humidity, as in the EMBx scenarios) or control any actuator, as we present in this scenario. This example uses an RGB LED to switch it on and off (as presented in the scenario [[en:iot-open:practical:hardware:sut:esp32:emb9a_1]]).\\ The following endpoints should be implemented: * endpoint 1 coap://ip_address_of_the_node/helloworld * method GET - should return a "Hello World" or other string of our choice, * endpoint 2 coap://ip_address_of_the_node/light * method GET - should return the current status of the LED light ("ON" or "OFF"), * method PUT - should set the LED on and off, with the expected payload being "1" for on and "0" for off. == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ = Step 1 = Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server.\\ It is essential to note and present (using a display of your choice) the node's IP address, as you will later need to refer to it with a client to use your service. = Step 2 = Include the WiFi UDP and CoAP implementation libraries headers in your code: #include #include WiFi UDP is part of the Arduino for the ESP32 framework, so you do not need to add it explicitly to the ''platformio.ini'' file. = Step 3 = Declare necessary constants, etc.: bool LEDSTATE; //Keep LED's state. = Step 4 = Declare communication objects: WiFiUDP udp; //UDP Communication class Coap coap(udp); //CoAP Communication class = Step 5 = Declare function prototypes (not necessary if you implement them in the correct order): void callback_response(CoapPacket &packet, IPAddress ip, int port); // CoAP client response callback // CoAP server endpoint URL callback for GET and PUT methods void callback_led(CoapPacket &packet, IPAddress ip, int port); = Step 6 = Implement them: void callback_led(CoapPacket &packet, IPAddress ip, int port) { // send response char p[packet.payloadlen + 1]; memcpy(p, packet.payload, packet.payloadlen); p[packet.payloadlen] = NULL; String message(p); //for GET if (message.equals("0")) LEDSTATE = false; else if(message.equals("1")) LEDSTATE = true; //for PUT if (LEDSTATE) { coap.sendResponse(ip, port, packet.messageid, "1"); } else { digitalWrite(RGBLED_R_PIN, LOW) ; coap.sendResponse(ip, port, packet.messageid, "0"); } } void callback_response(CoapPacket &packet, IPAddress ip, int port) { char p[packet.payloadlen + 1]; memcpy(p, packet.payload, packet.payloadlen); p[packet.payloadlen] = NULL; //Do something with payload, e.g. print it to the display } = Step 7 = Setup CoAP services: coap.server(callback_led, "light"); coap.response(callback_response); coap.start(); = Setup 8 = Process CoAP services in the ''void loop()'': delay(1000); coap.loop(); If you plan to make frequent requests, decrease the time above (''delay()''). == Result validation == You should be able to connect to the WiFi and set up a CoAP service. Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a CoAP client on another laboratory node (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_6|]]) or use some other client (such as NodeRED or even command line client): note, your client must be present in the same WiFi network as your CoAP service (laboratory node). Sample command line client (Linux/MacOS) syntax to make a PUT request to the service (192.168.91.14) is presented below (bash): $ coap-client -e "0" -m put coap://192.168.91.14/light Sample command line client (Linux/MacOS) syntax to make a GET request to the service (192.168.91.14) is presented below (bash): $ coap-client -m get coap://192.168.91.14/light === FAQ === **How do I implement a client to my CoAP service?**: If you're fully remote, the only option is to implement a client on your own - use another laboratory node and scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_6|]] pointing to your service. For this reason, we suggest printing the IP address on the display, as you need to explicitly implement the client to connect to this IP (given by the DHCP server). If you're on a laptop or mobile within the WiFi network range to which the laboratory nodes are connected, you can connect to it with your laptop and use any CoAP client. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT6: Connecting to the CoAP service ==== The following scenario will show you how to connect to the existing CoAP server. The server can be either an implementation on another node as present in the scenario [[en:iot-open:practical:hardware:sut:esp32:iot_5|]] or a software implementation present in the network.\\ A CoAP service is an endpoint that provides information. It is UDP-based. To connect to the service, you need to know its coap URI (or coaps, for secure connections). === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], * [[en:iot-open:practical:hardware:sut:esp32:iot_5|]], if you plan to connect to another node and implement a service yourself, and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]. There are many implementations of the CoAP protocol, but we will use the following library: lib_deps = hirotakaster/CoAP simple library@1.3.28 === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]], * [[en:iot-open:networking2:applicationnetworkprotocols|]]. === Hands-on Lab Scenario === Note—this scenario can be used in tandem with [[[en:iot-open:practical:hardware:sut:esp32:iot_5|]] to build a client-server solution using two devices (CoAP server and CoAP client). You need to book two devices then and develop them in parallel. We suggest connecting them over the AP rather than setting up an access point on the ESP32 device. You can also choose to use an existing service (e.g., NodeRed implementation). Note that the service must be present in the WiFi network to which you connect your node. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Connect to the CoAP server using the given URI. The server's address must be present in the internal IoT network to which your device is connected. If you choose to use the CoAP service implemented already in your network as a network service, refer to the technical documentation of the infrastructure to find its URL and credentials. == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ = Step 1 = Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server.\\ It is essential to note and present (using a display of your choice) the node's IP address, as you will later need to refer to it with a client to use your service. = Step 2 = Include the WiFi UDP and CoAP implementation libraries headers in your code: #include #include WiFi UDP is part of the Arduino for the ESP32 framework, so you do not need to add it explicitly to the ''platformio.ini'' file. = Step 3 = Declare an IP address of the CoAP server endpoint. Adjust ''a, b, c d'' as needed for your context; see node's technical documentation for integration services details: #define CoAPport 5683 #define CoAPpath "" IPAddress coapExtSeviceIP(a,b,c,d); The ''CoAPpath'' is just a path part of the URI, e.g. if your CoAP service URI is: coap://your_coap_server:9876/path_to_the_service/and_another_path then your ''CoAPpath'' should be declared as: #define CoAPpath "path_to_the_service/and_another_path" = Step 4 = Declate communication objects: WiFiUDP udp; //UDP Communication class Coap coap(udp); //CoAP Communication class = Step 5 = Declare function prototypes (not necessary if you implement them in the correct order): void callback_response(CoapPacket &packet, IPAddress ip, int port); // CoAP client response callback = Step 5 = Implement response handler for ''GET'' method: void callback_response(CoapPacket &packet, IPAddress ip, int port) { char p[packet.payloadlen + 1]; memcpy(p, packet.payload, packet.payloadlen); p[packet.payloadlen] = NULL; //Add your code to represent information on the selected display } = Step 6 = Register response callback and start CoAP client: coap.response(callback_response); coap.start(); = Step 7 = Make a call (GET) request to the service. Remember to provide the correct URI part.\\ Mind that the last argument of the function is a "path" part of the URI. The laboratory technical documentation provides a list of the services, their URIs (including paths), methods, and IP ports. int msgid = coap.get(coapExtSeviceIP,CoAPport,CoAPpath); = Step 8 = Process CoAP services in the ''void loop()'': delay(1000); coap.loop(); If you plan to make frequent requests, decrease the time above (''delay()''). == Result validation == You should be able to connect to WiFi and a CoAP service of your choice (either another node or a software service present in the network): depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a CoAP server on another laboratory node (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_5|]]) or use some other service (such as NodeRED or even command line): For self implementation, your CoAP service must be present in the same WiFi network as your CoAP client (laboratory node). === FAQ === **How do I implement a server to my CoAP client?**: If you're fully remote, there are two options: * you need to implement a service (CoAP server) on your own - use another laboratory node and scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_5|]] * or you can use a service available in the network (refer to the node's technical documentation for integration services). If you're on a laptop or mobile within the WiFi network range to which the laboratory nodes are connected, you can connect to it with your laptop and use any CoAP client/server. In Linux environments, you can use a dummy CoAP service that is installed along with ''libcoap'' package: ~$ coap-server then you can check its availability with the following command: ~$ coap-client -m get coap://127.0.0.1 This is a test server made with libcoap (see https://libcoap.net) Copyright (C) 2010--2019 Olaf Bergmann and others === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT7: BLE Beacon === This scenario presents how to create the Bluetooth Low Energy beacon device which periodically broadcasts a small amount of information, and the client device which can receive packets sent by the beacon. Beacons are usually used for sending useful information (eg. the web address of the owner, a link to the page with tourist information). In some cases, they simply send the identification number recognised by a dedicated mobile application allowing the users to localise themselves. BLE Beacons just broadcast the information, they are not capable of receiving any information from the users nearby. To do this scenario two stations are needed, one for the beacon and one for the client device. === Prerequisites === We will use the advertising process in this scenario so an understanding of the principles of the Bluetooth Low Energy protocol is necessary. It's good to understand the Eddystone data format used to encode URLs in advertising frames. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * Interesting article on Beacons with a description of Eddystone, and iBeacon packets((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)) === Hands-on Lab Scenario === This scenario is intended to be implemented using two laboratory nodes. One node will work as the beacon device, while the second will receive the beacon's packets and display information on the LCD. == Task to be implemented == **Task 1** Implement a program that operates as the BLE beacon which advertises itself with the link to the website. This can be done with the Eddystone beacon format. **Task 2** Implement the receiver of advertising frames broadcasted by the beacon device capable of displaying information on the LCD screen. == Start == We need to implement both parts of the software to be able to observe the results (steps 1-4). **The Task 1** we implement in steps 1 and 2. **The Task 2** we implement in steps 3 and 4. At the end, we return to **Task 1** with step 5. == Steps == We can go through the lab in a few steps. In the beginning, we will write a simple program which advertises the device with the default parameters set in the BLE library. As the payload of the advertising frame, we will use the Eddystone format to send the chosen URL address. = Step 1 = Let's begin with including Arduino and BLE libraries. #include "Arduino.h" #include "BLEDevice.h" #include "BLEAdvertising.h" We need one variable only which holds the pointer to the advertising class. BLEAdvertising *pAdvertising; The setup function creates and initialises the BLE device instance with a chosen name. If we add our text in the BLEDevice::init() function we will see this text as the device name in the advertising frame. It will also appear as the value of the Device Name characteristic in the Generic Access service. If we don't define the name it will use the default "ESP32". While the device is ready we can get the pointer to its class using it to configure the advertising packet. After configuration, we can start the advertising process. void setup(){ BLEDevice::init("SUT BLE device"); pAdvertising = BLEDevice::getAdvertising(); ... // We will implement this part in step 2 pAdvertising->start(); }; The loop function can remain empty or call the delay() function. void loop(){ delay(5000); }; = Step 2 = The advertisement frame is composed of fields. Every field starts with its length (1 byte, including the field type byte). The second byte of the field encodes the field type. The selected field codes used in the default advertising frame defined in the ESP32 BLE library are presented in the table below. ^ Field length ^ Field code ^ Field type ^ Default value ^ | 0x02 | 0x01 | Flags | 0x06 | | 0x05 | 0x12 | Peripheral Connection Interval Range | 0x20004000 | | 0x06 | 0x09 | Complete Local Name | "ESP32" | | 0x02 | 0x0A | Tx Power Level | 0x09 | Except for the text information as the device name, it is possible to send other data such as the web address which directs us to additional information. There are some formats defined for use in beacon devices. One of them is Eddystone invented by Google. It is used in the advertisement frames for simplification of the URL encoding. It requires the presence of the field "List of 16-bit UUIDs" (code 0x03) which shows the UUIDs of the Eddystobe service (0xAAFE value), and the service data field (code 0x16) compatible with Eddystone format. These additional fields are shown in the table below. ^ Field length ^ Field code ^ Field type ^ Value ^ | 3 | 0x03 | Complete list of 16-bit service UUIDs | 0xAAFE | | 14 | 0x16 | Service data | Eddystone format | The Eddystone field starts with service UUID (0xAAFE), next specifies the content type (0x10 for URL), then the transmitting power in [dBm], and then the compressed URL. The UUID of Eddystone appears twice. Once as the element of the list of available services, the second time as the identifier of the service field. To compress URLs some default prefixes and suffixes were defined as non-ASCII characters. They are presented in the tables below. ^ Decimal ^ Hex ^ Prefix ^ | 0 | 0x00 | %%http://www.%% | | 1 | 0x01 | %%https://www.%% | | 2 | 0x02 | %%http://%% | | 3 | 0x03 | %%https://%% | ^ Decimal ^ Hex ^ Suffix ^ | 0 | 0x00 | .com/ | | 1 | 0x01 | .org/ | | 2 | 0x02 | .edu/ | | 3 | 0x03 | .net/ | | 4 | 0x04 | .info/ | | 5 | 0x05 | .biz/ | | 6 | 0x06 | .gov/ | | 7 | 0x07 | .com | | 8 | 0x08 | .org | | 9 | 0x09 | .edu | | 10 | 0x0a | .net | | 11 | 0x0b | .info | | 12 | 0x0c | .biz | | 13 | 0x0d | .gov | The Eddystone payload can be created as the std::string class object as required by the function addData() from the BLEAdvertisementData class. So first we need to declare the BLEAdvertismentData class object, and string object with the placeholder for the payload. BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); std::string eddystone_content (" "); //16 spaces for advertising packet In the setup() function, we need to fill in the empty spaces with details of the advertising packet. eddystone_content[0] = 0x02; //Length of FLags field eddystone_content[1] = 0x01; //Flags ID eddystone_content[2] = 0x06; //Flags value eddystone_content[3] = 0x03; //Length of service field eddystone_content[4] = 0x03; //List of 16-bit UUIDs eddystone_content[5] = 0xAA; //UUID of Google Eddystone eddystone_content[6] = 0xFE; eddystone_content[7] = 8; //length of Eddystone field eddystone_content[8] = 0x16; //Service data eddystone_content[9] = 0xAA; //Eddystone UUID eddystone_content[10] = 0xFE; eddystone_content[11] = 0x10; //URL Eddystone frame type eddystone_content[12] = 0xF4; //Tx power [dBm] eddystone_content[13] = 0x00; //prefix: "http://www." eddystone_content[14] = '2'; //URL eddystone_content[15] = 0x07; //suffix: ".com" We created a very short name for the URL to fit in one line of LCD screen (16 characters). The resulting text would be "http://www.2.com" The created payload can be used as the payload data of the advertising packet. oAdvertisementData.addData(eddystone_content); pAdvertising->setAdvertisementData(oAdvertisementData); = Step 3 = It is the time to receive the advertising packet. We will do it with another device starting with including the libraries required. #include "Arduino.h" #include "BLEDevice.h" #include "Adafruit_LiquidCrystal.h" There can be a lot of BLE devices in the range of our receiver. We should find the one that we are interested in so we define the UUID of the Eddystone service. #define SERVICE_UUID "feaa" Our program will use objects of two classes and some variables: static BLEAdvertisedDevice* myDevice; // Class for remote BLE device BLEScan* pBLEScan; // Class for local scanner device uint8_t * advPayload; // Pointer to the payload of incoming adv packet char eddystoneUrl[20]; // Buffer for text to display String advName; // Remote device name int advLength; // Length of payload of adv. packet int advIndex; // Index of the byte we proceed int advEddystoneLength; // Length of the Eddystone field int i; The received information will be displayed on the LCD, so we need to configure it (similar to the scenario EMB5) // LCD display pins and class declaration #define LCD_RS 2 #define LCD_ENABLE 1 #define LCD_D4 39 #define LCD_D5 40 #define LCD_D6 41 #define LCD_D7 42 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); The setup function initialises the display and the Bluetooth, sets up and starts the scanning process. An important step is registering the callback function. The callback function is called every time the scanner receives the advertising frame. void setup() { // Initialise LCD lcd.begin(16, 2); delay(1000); lcd.print("BLE CLient"); // Initialise the Bluetooth BLEDevice::init(""); // Retrieve the pointer to the scan module pBLEScan = BLEDevice::getScan(); // Register callback for incoming advertising pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // Start scan parameters with active scan mode pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); // Start the scan for 5 seconds pBLEScan->start(5, false); } In the loop function, we restart scanning every 5 seconds with 1 second delay. The start function of the BLEScan class is blocking, so it stops program execution for the time of scanning. void loop() { delay(1000); // Repeat scanning pBLEScan->start(5,false); } = Step 4 = The callback function checks if the device which sent the advertising frame implements the Eddystone service. This is how we filter out all other BLE devices available in the range. if we have more than one Eddystone device nearby we would have to implement additional filtering. After a successful search for a device with Eddystone service, we can display its name and proceed with displaying the content of the Eddystone field. Because the code is quite complex we present the whole callback function below. Notice that we decode chosen compressed fields only (prefix = 0x00, suffice = 0x07). You would need to decode other values for compatibility with the Eddystone format. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // We have found a device, let's see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { // Display the name of the remote device lcd.setCursor(0,0); lcd.print(advertisedDevice.getName().c_str()); // Get the length of the payload and the pointer to it advLength = advertisedDevice.getPayloadLength(); advPayload = advertisedDevice.getPayload(); // Search for the Eddystone field (field ID = 0x16) advIndex = 0; while (advIndex<=advLength) { if (advPayload[advIndex+1]==0x16) { // Eddystone field found, get the length of it advEddystoneLength = advPayload[advIndex]; // Display the Eddystone field lcd.setCursor(0,1); // Prefix decoding if (advPayload[advIndex+6]==0x00) { lcd.print("http://www."); } // ULR name for(i=0; i Additional filtering of remote devices can be done with their names. We can add in the "MyAdvertisedDeviceCallbacks()" function the comparison of the remote device name with the constant string. It should be done if the Eddystone service was found. String name; name = advertisedDevice.getName().c_str(); if (name.equals("SUT BLE device")) { // here the internal code of the MyAdvertisedDeviceCallbacks() } = Step 5 = Let's return to the beacon device. The advertisement frame is quite short so normally additional information is sent upon Scan Request with Scan Response frame. It can contain the device name which can differ from the name we specified in step 1 (in the case of normal operation we can read it as the value of the Generic Access Service). To specify different names in advertising frame and service characteristics we can use the setScanResponseData() function from BLEAdvertising class. oScanResponseData.setName("SUT Beacon"); pAdvertising->setScanResponseData(oScanResponseData); == Result validation == After the implementation of steps 1-4, you should be able to see the name of the beacon device in the first line of LCD and the URL from the Eddystone field in the second line of LCD. Implementation of step 5 should result in a change of the device name. == Further work == You can try to implement the beacon device compatible with iBeacon. The description can be found on the website ((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)). === FAQ === **What is the maximum length of the advertising frame?**: It depends on the BLE version. In previous versions, it used to be 31 bytes only, but in newer versions, even 255 bytes are allowed. \\ **How often should the device send advertising frames?**: The delay between advertising frames can vary depending on the device. Beacon devices should send them every 100-200 ms. Other devices usually advertise every 2 seconds. Notice that advertising consumes energy, so the battery-powered devices advertise with lower frequency. \\ **What if I need to send some info other than in Eddystone or iBeacon format?**: You can send the text or even some data as part of the advertising frame. Some devices use the advertising frames to send simple 1-2 bytes of measurement data. More data can be sent after establishing the connection between the primary and secondary devices with the use of characteristics. \\ **What is the purpose of the transmitting power data presence in the advertising frame?**: The value sent as the transmitting power should represent real power at a 0-meter distance from the beacon. It can be used to estimate the actual distance of the user from the beacon. The distance needs to be calculated by application on the mobile phone with knowledge of the transmitting power of the beacon and actual measurement of received signal strength (known as RSSI). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT8: BLE Communication with characteristics === This scenario presents how to create the Bluetooth Low Energy server device and the corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control by writing the data to. The client connects to a server and reads the data. This scenario presents the use of the concept of services and characteristics. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the simplest implementation to be extended in further scenarios. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. \\ **Task 2.** Implement a client device, to read the exemplary data from a server. == Start == You can use the beacon and simple client programs from [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] as the starting point. == Steps == We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. The program returns automatically to the advertisement procedure after disconnecting. = Step 1 = Let's begin with a simple program which advertises a single service. The code should start with including Arduino and BLE libraries. #include #include "BLEDevice.h" #include "BLEUtils.h" #include "BLEServer.h" We need variables to hold the class pointers for advertising, the server instance, service, and characteristics. BLEAdvertising *pAdvertising; //class for the advertising data BLEServer *pServer; //class for the BLE Server device BLEService *pService; //class for the BLE Service in the Server BLECharacteristic *pCharacteristic; //class for the characteristic We will also need two boolean variables to control the restarting of the advertising. In case of establishing the connection from the remote client, the server device stops advertising, so it will not be searchable anymore. If we lose the connection with the client we need to restart advertising. bool deviceConnected = false; bool advStarted = true; Any service or characteristic is identified with a unique UUID. If you use the standard service, like eg. Health Thermometer or Binary Sensor Service you can use a 16-bit UUID defined by Bluetooth SIG in the document ((https://www.bluetooth.com/specifications/assigned-numbers/)). The same document defines the UUIDs for characteristics. Example of the standard UUIDs for service and characteristic: Health Thermometer Service - 0x1809 Temperature Measurement Characteristic - 0x2A1C While none of the standard UUIDs fits your device you need to define your own UUID. It must be 128-bit long and can be randomly generated with the tool available on the website https://www.uuidgenerator.net/((https://www.uuidgenerator.net/)). The service UUID and characteristic UUID must differ. Although they can be completely different in many implementations the UUIDs of characteristics grouped under one service differ on one digit. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" #define CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" Before we implement the setup() function, we have to create the callback function executed in case of connection and disconnection events. It allows us to control the advertising process with the deviceConnected flag. class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; The setup() function creates and initialises the BLE device instance with the name "SUT BLE device", and creates the server and sets the callback function. It also creates the service within the server. // Initialise the BLE device BLEDevice::init("SUT BLE device"); // Create BLE Server instance pServer = BLEDevice::createServer(); // Set callback function for handling server events pServer->setCallbacks(new MyServerCallbacks()); // Create the service in the server pService = pServer->createService(SERVICE_UUID); Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "BLE onboard" text. // Create the characteristic in the service pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); // Set the initial value of the characteristic. // We will be able to read it by client pCharacteristic->setValue("BLE onboard"); In the advertising packet, we can add the service UUID. // Get the pointer to the advertising object pAdvertising = BLEDevice::getAdvertising(); // Add the service UUID to the advertising pAdvertising->addServiceUUID(SERVICE_UUID); // Enable reading extended information with scan response packet pAdvertising->setScanResponse(true); Due to the limited size of the advertisement packet, not all service UUIDs can be broadcast. Even more, service UUID does not have to appear in the advertising frame, but its presence makes it possible to scan nearby devices by their functionality, not by names only. After preparing all the elements we can close the setup() function by starting the service and beginning advertising. // Starting the service and advertising process pService->start(); pAdvertising->start(); In the loop function, we need to handle the restarting of advertising in case of disconnection of the client. void loop(){ if (!deviceConnected && !advStarted) { pServer->startAdvertising(); // restart advertising advStarted = true; } if (deviceConnected){ advStarted = false; } delay(500); }; = Step 2 = In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client_char.drawio.svg?500 |The client algorithm}} = Step 3 = While we have analysed the client's behaviour we can start implementation. Let's begin with the libraries needed. #include "Arduino.h" #include "BLEDevice.h" #include "Adafruit_LiquidCrystal.h" Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. // The remote service we wish to connect to. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" // The characteristic of the remote service we are interested in. #define REMOTE_CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" Some global variables will be needed for our software. // Variables static boolean doConnect = false; static boolean connected = false; static boolean doScan = false; static BLERemoteCharacteristic* pRemoteCharacteristic; static BLEAdvertisedDevice* myDevice; We need to add and configure the LCD to be able to observe the results. // LCD display pins and class declaration #define LCD_RS 2 #define LCD_ENABLE 1 #define LCD_D4 39 #define LCD_D5 40 #define LCD_D6 41 #define LCD_D7 42 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); Two callback functions will be defined. The first callback is for advertising. It is called whenever the advertising frame is received, no matter which device sends it. Inside the callback, we search for the device nearby which presents the service we would like to use. If we find one, we can create the instance of the remote device class and pass the signal to the main program to establish the connection. // Scan for BLE servers and find the first one that advertises // the service we are looking for. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // We will print the asterix for every device found lcd.print("*"); // We have found a device, let's see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { // Our device has the service we need. We can stop scanning. BLEDevice::getScan()->stop(); // Create the instance of remote device myDevice = new BLEAdvertisedDevice(advertisedDevice); // Pass information to other part of the program to connect to the device doConnect = true; doScan = true; } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks The second callback class helps to inform the other parts of the program about the connection close. It will additionally inform us about the current state of the program. class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { lcd.setCursor(0,0); lcd.print("Connected "); } void onDisconnect(BLEClient* pclient) { lcd.setCursor(0,0); lcd.print("Disconnected "); lcd.setCursor(0,1); connected = false; } }; The setup function initialises the Bluetooth, registers the advertising callback function, and starts the scan to look for nearby devices. void setup() { // Initialise the LCD and print the welcome message lcd.begin(16, 2); delay(1000); lcd.setCursor(0,0); lcd.print("BLE Client "); // Initialise the Bluetooth BLEDevice::init(""); // Retrieve the pointer to the scan module BLEScan* pBLEScan = BLEDevice::getScan(); // Register callback for incoming advertising pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // Start scan parameters with active scan mode pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); // Print the message on the LCD lcd.setCursor(0,0); lcd.print("Scanning "); lcd.setCursor(0,1); // Start the scan for 30 seconds pBLEScan->start(30, false); } In the loop() function, we wait for the information that the server with the service UUID we were interested in was found. It is signalled with the use of "doConnect" flag. If it's true we can connect to this server. Once we are connected the "connected" flag is set to be true in the connectToServer() function. We will show this function in a while. \\ In a loop() function we will periodically read the characteristic value. It is a good practice to check if the characteristic is properly retrieved and readable before reading the value. void loop() { if (doConnect == true) { // Establish the connection to the server connectToServer(); doConnect = false; } if (!connected) { if(doScan){ // Start scan again after disconnect BLEDevice::getScan()->start(0); } } if (connected) { lcd.setCursor(0,1); // Is the characteristic properly retrieved? if(pRemoteCharacteristic != nullptr) // Is the characteristic readable? if(pRemoteCharacteristic->canRead()) // We can safely read the value lcd.print(pRemoteCharacteristic->readValue().c_str()); } delay(1000); // Delay a second between loops. } The connection to the server is executed in some steps: - Creation of the client object. - Setting the callback class for handling disconnection. - Connection to the remote device. - Setting parameters of the connection. - Getting the reference to the service. - Getting the reference to the characteristic. - Informing the main program with the "connected" flag. bool connectToServer() { BLEClient* pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback()); // Connect to the remote BLE Server. pClient->connect(myDevice); // Obtain a reference to the service in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(BLEUUID(SERVICE_UUID)); // Obtain a reference to the characteristic of the chosen service. pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(REMOTE_CHARACTERISTIC_UUID)); connected = true; return true; } == Result validation == You should be able to observe messages describing the process of searching for the server on the first line of LCD and during scanning asterisks appearing on the second line. After establishing the connection, the characteristic value should appear on the display's second line. === FAQ === **What types of data can be sent with the characteristics?**: The BLE characteristics can send any value as a single byte or a sequence of bytes. BLE does not interpret the data in characteristic in any way. Interpretation lies on the side of the application. \\ **What if I need to read or write more data than a single text?**: It is possible to implement many services in one device and add many characteristics to one service. Theoretically, the maximum number of characteristics in one service is 512, but the implementation and the memory available for the BLE library limit it. \\ **What is the maximum length of the data in the characteristics?**: The maximum length of the data in the characteristics in the BLE specification is 512 bytes. \\ === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT9: BLE Communication with notify/indicate === This scenario presents how to extend the Bluetooth Low Energy server and client devices with a notification/indication mechanism for sending data automatically. If enabled, notifications or indications are sent at any time while the data in the server is updated. A difference between them is that a notification is an unacknowledged message while an indication is an acknowledged message. While one of them is enabled by the client, the server decides on the time of the message sent. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. Notification and indication methods of data transmission should be known. We will use in this scenario the knowledge of the services and characteristics so making the [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the extension of the scenario implemented in [[en:iot-open:practical:hardware:sut:esp32:IoT_8]]. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. After a successful connection, it handles the notify descriptor modification and sends the data automatically if notifications are enabled. \\ **Task 2.** Implement a client device, capable of connecting to the server, enabling notifications and displaying the exemplary data coming from a server as notification packets. == Start == You can use the simple client and server programs from [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] as the starting point. == Steps == We will pass through the lab in a few steps. We will add the second characteristic to the example from lab [[en:iot-open:practical:hardware:sut:esp32:IoT_8]]. We will configure this characteristic as notify/indicate capable. It allows us to establish a connection and, if successfully connected, enable the indication or notification feature. With this feature enabled, the peripheral device initiates data transmission. It can be used for periodic measurement reading or updating information on value change. = Step 1 = We start with the program written during the laboratory [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] by including the BLE2902.h library which handles the Client Characteristic Configuration Descriptor (CCCD). The descriptor of this type is used to enable or disable the notification and indication feature. ^ Descriptor UUID ^ Bits 1 and 0 ^ CCCD value ^ Function ^ | 0x2902 | 00 | 0 | notify/indicate disabled | | ::: | 01 | 1 | enable notification | | ::: | 10 | 2 | enable indication | #include "BLE2902.h" Next, we add another characteristic to the service and variable required to hold the pointer to the characteristic class. We need to define the UUID for this characteristic. BLECharacteristic *pNotifyCharacteristic; //class for the characteristic #define NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" We create an additional characteristic with reading, writing, notify and indicate enabled, and the initial text "00" as a placeholder for the data. We also add the descriptor to the characteristic. // Create a BLE Characteristic pNotifyCharacteristic = pService->createCharacteristic( NOTIFY_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); pNotifyCharacteristic->setValue("0"); // Create a BLE Descriptor pNotifyCharacteristic->addDescriptor(new BLE2902()); Any characteristic can be configured as notify or indicate enabled. We can enable this mechanism for more than one characteristic. After these modifications, it would be possible to observe an additional characteristic in the service with the possibility of reading, writing, and enabling notification and indication. = Step 2 = At this step, we implement periodical data sending. We add two variables, an integer for holding the value incremented every loop pass and a text value converted from an integer to be sent within the indication/notification packet. int int_value=0; char text_value[] = "000000"; In the loop() function we modify the part executed if the connection was established. We'll add conversion from integer to text with atoi(), an update of characteristic value, and an incrementation of integer value. void loop(){ if (!deviceConnected && !advStarted) { pServer->startAdvertising(); // restart advertising advStarted = true; } if (deviceConnected){ advStarted = false; itoa(int_value,text_value,10); pNotifyCharacteristic->setValue(text_value); pNotifyCharacteristic->notify(); int_value++; } delay(500); }; = Step 3 = In this step, we'll analyse the communication between the server and the client. The client software is much more complex than the server. Some parts of the client software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client.drawio.svg?500 |The client algorithm}} = Step 4 = We have to extend the client software from scenario [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] with some elements. Let's start with adding another remote characteristic which is responsible for receiving notifications. #define REMOTE_NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" In the function for connecting to the server, we have to add another characteristic and register the callback function. It is also good to clear the LCD after establishing a connection. // Obtain a reference to the notify characteristic of the chosen service. pRemoteNotifyCharacteristic = pRemoteService->getCharacteristic(BLEUUID(REMOTE_NOTIFY_CHARACTERISTIC_UUID)); if(pRemoteNotifyCharacteristic->canNotify()) pRemoteNotifyCharacteristic->registerForNotify(notifyCallback); lcd.clear(); Next, we add the callback function which will be called every time the server sends a new notification packet. static void notifyCallback( BLERemoteCharacteristic* pBLERemoteNotifyCharacteristic, uint8_t* pData, size_t length, bool isNotify) { lcd.setCursor(0,0); pData[length]=0; //limit the length of the printed string lcd.print((char*)pData); } You can leave a periodical reading of the second characteristic in the loop(), but you will observe that sometimes its value appears together with the notification in the first line of the LCD. This is because notification callback is called as the interrupt handler, and can be executed between setting the cursor and displaying the characteristic value. The only way to avoid it is to move displaying of incoming notifications to the mail loop. == Result validation == You should be able to establish a connection and read the non-notification characteristic data. After enabling the notification we will observe periodic incrementation of the value sent with notify data packets. You can observe that sometimes devices do not connect. This can happen because if you upload the new version of the program to one of the devices the second one remains in a connected state. In such a situation you need to restart both devices. === FAQ === **What is the difference between notification and indication?**: As it was mentioned in the beginning the notification is an unacknowledged message while an indication is an acknowledged message. It means that if the indication message remains unacknowledged, the peripheral device won't send another message for this characteristic. **Is the notification/indication mechanism similar to interrupts?**: You are right. They work in a similar manner as interrupts in the microprocessor. They allow sending the data asynchronously, and the peripheral decides about the time of the message sent like the peripheral decides on the time of signalling interrupt. Data transmission when the central device sends the packet with a characteristic read command is similar to the polling method used in microprocessors. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
====== 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 {{ref>stm32sutnodehardware}} 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 {{ref>esp32sutnode1}} and reference numbers reflecting components in the table {{ref>stm32sutnodehardware}}.
{{:en:iot-open:practical:hardware:sut:vrel_nextgen_sut_motherboard.png?560|}} STM32WB55 Node
^ 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 | 2x16 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, 250x122 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, 128x128 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. |
STM32WB55 Node Hardware Details
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: [env:vrelnextgen] platform = ststm32 framework = arduino board = nucleo_wb55rg_p 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 part of the Embedded Systems Modules. * [[en:iot-open:practical:hardware:sut:stm32:emb5_1]] How do you use LCD (component 5)? * [[en:iot-open:practical:hardware:sut:stm32:emb6_1]] How do you use the ePaper display (component 6)? * [[en:iot-open:practical:hardware:sut:stm32:emb7_1]] How do you use an OLED display (component 7)? * [[en:iot-open:practical:hardware:sut:stm32:emb8_1]] How do you use the Smart LED stripe (component 8)? * [[en:iot-open:practical:hardware:sut:stm32:emb9A_1]] How do you use RGB LED (component 9A)? * [[en:iot-open:practical:hardware:sut:stm32:emb10_1]] How do you control a standard miniature servo (component 10)? * [[en:iot-open:practical:hardware:sut:stm32:emb1A_1]] How do you control a FAN (component 1A)? * [[en:iot-open:practical:hardware:sut:stm32:emb1B_1]] How do you use the pressure and environmental integrated sensor (component 1B)? * [[en:iot-open:practical:hardware:sut:stm32:emb2_1]] How do you use a digital potentiometer (component 2)? * [[en:iot-open:practical:hardware:sut:stm32:emb3_1]] How do you use temperature and humidity integrated sensors (component 3)? * [[en:iot-open:practical:hardware:sut:stm32:emb4_1]] How do you use a temperature-only sensor (component 4)? * [[en:iot-open:practical:hardware:sut:stm32:emb9B_1]] How do you use light and colour intensity sensors (component 9B)? ** IoT programming **\\ In the following scenarios, you will write programs interacting with other devices, services, and networks, which are pure IoT applications. * [[en:iot-open:practical:hardware:sut:stm32:iot_1]] Presenting MAC address of the WiFi interface * [[en:iot-open:practical:hardware:sut:stm32:iot_2]] Connecting to the WiFi Access Point and presenting IP * [[en:iot-open:practical:hardware:sut:stm32:iot_3]] Connecting to the MQTT broker and publishing data * [[en:iot-open:practical:hardware:sut:stm32:iot_4]] Connecting to the MQTT broker and subscribing to the data * [[en:iot-open:practical:hardware:sut:stm32:iot_5]] Publishing a CoAP service * [[en:iot-open:practical:hardware:sut:stm32:iot_6]] Connecting to the CoAP service * [[en:iot-open:practical:hardware:sut:stm32:iot_7]] Setting-up BT Beacon ==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:stm32|]] === 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 = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:stm32|]] To generate an array of bytes representing an image, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] === 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. 64x64 pixels) and convert it to the byte array with b/w settings.\\ Sample project favicon you can use is present in Figure {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:stm32:logo64.png?64 |}} 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 {{ref>iotopenfavicon}} (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 #include // Fonts #include 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 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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, 128x128 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:stm32|]] To generate an array of bytes representing an image in 565 format, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] 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 100x100 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 {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:stm32:logo64.png?64 |}} 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 {{ref>iotopenfavicon}} 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: {{ :en:iot-open:practical:hardware:sut:stm32:epd_bitmap_logo64.zip | 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 #include #include // Fonts #include // 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: 128x128 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:hardware2:actuators_light|]] === 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_9A: Use of RGB LEDs === This scenario presents how to handle the brightness control of the tri-coloured LEDs. One is observable via camera, as presented in the figure (component 9A), while another is hidden inside the black enclosure and lights a colour sensor (component 9B). Both LEDs are electrically bound and cannot be controlled independently. Those LEDs have 3 colour channels, controlled independently: R (Red), G (Green) and B (Blue). Mixing of those colours creates other ones, such as pink and violet. Each R G B channel can be controlled with a separate GPIO to switch it on or off or control brightness using a PWM signal, as presented in this tutorial. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the PWM hardware channels of the STM32WB55 chip. In this case, we do not use an external library; instead, we use the hardware timer library built in the Arduino Core STM32 framework (stm32duino)(("stm32duino", https://github.com/stm32duino)) for STM32WB55 so that no additional external libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will light LEDs consecutively with R, G, and B. Use 50% of the maximum brightness. Use a PWM signal to control each GPIO for R, G and B, each colour separately to let you easily observe it. You can also experiment with various duty cycle settings for all channels to obtain different LED colours. == Start == 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 laboratory equipment, all three LEDs are connected to one hardware timer instance. Thanks to this LEDs operate as three separate channels of a single timer. In the case of setting the PWM duty cycle expressed in percentages, the minimum value is 0, and the max (full brightness) is 100. Note that full brightness may be too bright for the observation camera, so consider using a range between 0 and 50%. == Steps == To use PWM in STM32WB55, it is best to use a built-in hardware timer library. #include The hardware timer uses internal timer modules and allows us to define channels attached to the timer. We will use 1 channel per colour (R, G and B, so 3 in total). A PWM frequency is controlled with the timer to be shared for all 3 R, G and B channels. Channels control the PWM duty cycle. = Step 1 = Include the library, and define PIN assignments to channels and PWM frequency (100Hz): #include // Pins definition for RGB LED #define LED_PWM_GREEN D3 //Arduino numbering D3, STM numbering PA_10 #define LED_PWM_BLUE D6 //Arduino numbering D6, STM numbering PA_8 #define LED_PWM_RED D9 //Arduino numbering D9, STM numbering PA_9 #define PWM_freq 100 GPIO pins controlling LEDS are D9 (Red), D3 (Green) and D6 (Blue), respectively. = Step 2 = Define variables for the timer object and handlers of channels. // PWM variables definitions HardwareTimer *MyTimLED; //Hardware Timer for PWM uint32_t channelR, channelG, channelB; //RGB channels Instantiate the timer object and initialise the 3 channels for PWM and make them dark (1% duty cycle): //Create Timer instance type based on the chosen LED pin (the same timer is used for all LEDs). TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(LED_PWM_RED), PinMap_PWM); //Define three separate channels for LEDs channelR = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(LED_PWM_RED), PinMap_PWM)); channelG = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(LED_PWM_GREEN), PinMap_PWM)); channelB = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(LED_PWM_BLUE), PinMap_PWM)); // Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. MyTimLED = new HardwareTimer(Instance); // Configure and start PWM MyTimLED->setPWM(channelR,LED_PWM_RED,PWM_freq,1); /* 100 Hertz, 1% dutycycle. */ MyTimLED->setPWM(channelG,LED_PWM_GREEN,PWM_freq,1); /* 100 Hertz, 1% dutycycle. */ MyTimLED->setPWM(channelB,LED_PWM_BLUE,PWM_freq,1); /* 100 Hertz, 1% dutycycle. */ To modify the intensity of the chosen LED after initialisation use the setCaptureCompare function. In the following example, the duty_cycle_value can vary between 0 and 100. MyTimLED->setCaptureCompare(channelR, duty_cycle_value, PERCENT_COMPARE_FORMAT); //modify duty cycle = Step 3 = Write a loop for each colour (R, G, then B) to light the colour from dark to max value. Full duty cycle (100%) will be too bright for the remote access video camera to handle it. Use some reasonable range such as 0..50% is strongly advised. Mind to compose code to increase and decrease each colour. A hint is below (for channelR): // Increase brightness for (int duty_cycle_value = 0; duty_cycle_value <= 50; duty_cycle_value++) { // Gradually increase duty cycle for Red LED MyTimLED->setCaptureCompare(channelR, duty_cycle_value, PERCENT_COMPARE_FORMAT); delay(20); // Delay for smooth transition } delay(100); // Decrease brightness for (int duty_cycle_value = 50; duty_cycle_value >= 0; duty_cycle_value--) { // Gradually decrease duty cycle for Red LED MyTimLED->setCaptureCompare(channelR, duty_cycle_value, PERCENT_COMPARE_FORMAT); delay(20); // Delay for smooth transition } == Result validation == You should be able to observe the pulsing colours of the RGB LED, increasing and decreasing brightness linearly. === FAQ === **What is the maximum number of channels?**: the MCU we use here is STM32WB55 on the Nucleo board. The pins available are connected to timer 1 with three PWM channels (connected to our RGB LED) and timer 2 with 4 PWM channels (with two of them connected to the servo and fan described in other scenarios). A single timer can generate PWM signals with independent duty cycles and identical frequency. \\ **What is the maximum bit resolution for PWM?**: Maximum resolution is 16 bits. Note that we can express the duty cycle in a variety of formats. In the presented example we expressed it in percentage so it varies between 1 and 100. It can be also expressed in ticks, Hertz, microseconds, and 1-16 bits numbers. \\ **What PWM frequency should I use?**: there is no straightforward answer to this question: assuming you observe LED remotely with a camera, even 50Hz would be enough. But it would give a severe flickering experience to the live user, on the other hand. In the example above, we propose 100Hz, but this MCU can easily handle higher frequencies. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_9B: Reading colour sensor === A colour sensor (TCS 34725) can detect the brightness and colour of the light emitted. It works with the I2C bus. In our laboratory, the sensor has a fixed 0x29 address. The sensor is in the black enclosure, ensuring no ambient light impacts readings. The only light source is an RGB LED, controlled as described in the scenario [[en:iot-open:practical:hardware:sut:stm32:emb9a_1|]]. This is another LED connected parallel to the one you can observe in the camera. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]], and obligatory: * [[en:iot-open:practical:hardware:sut:stm32:emb9a_1|]]. To simplify TCS sensor use, we will use a dedicated library: lib_deps = adafruit/Adafruit TCS34725@^1.4.4 Also, remember to add the LCD handling library, as present in the EMB9 scenario, unless you decide to use another output device. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[en:iot-open:hardware2:sensors_optical|]] * [[https://cdn-shop.adafruit.com/datasheets/TCS34725.pdf|TCS sensor documentation]] === Hands-on Lab Scenario === == Task to be implemented == Create a solution where you control an RGB LED and read its colour and brightness using a TCS sensor. Present results on the LCD, e.g. in the form of "Colour: " where "value" refers to the colour intensity. You should change LED brightness at least 10 times (10 different brightness) for each colour while continuously reading the TCS sensor and presenting data in the LCD. Experiment with mixing colours and check either the sensor can distinguish brightness separately, when i.e. both RGD LED's colours are on: Red+Green or Red+Blue (or any other combination you wish). There are at least two ways to handle this task: * simple: a dummy loop where you set RGB LED values, read from TCS and display on LCD, * advanced: where the data is read and presented on the LCD in one or two asynchronous routines run by timers. == Start == Check if you can see the LCD and RGB LED in the camera view. Remember not to use the full brightness of the RGB LED because you won't be able to observe other elements due to the camera's low dynamics. Assuming you express duty cycle in percents, we suggest a range between 0 and 50. == Steps == In the steps below, we present only the part for reading from the TCS sensor. Control of RGB LED with PWM and handling LCD is presented in the other lab scenarios that you need to follow accordingly and merge with the code below. = Step 1 = Included necessary libraries: #include #include "Adafruit_TCS34725.h" = Step 2 = Declare and instantiate TCS controller class and related variables for readings: static Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_300MS, TCS34725_GAIN_1X); uint16_t r, g, b, c, colorTemp, lux; static bool isTCSOk = false; A word of explanation regarding the parameters: * ''TCS34725_INTEGRATIONTIME_300MS'' is time in ms while the TCS sensor is exposed for the light to grab readings, * ''TCS34725_GAIN_1X'' is the sensor's gain - it helps reading in low light, but that is not true in our lab as our RGB LED light sensor is very bright, even in low duty cycle. Refer to the Q&A section for the ranges. = Step 3 = Initialise the sensor (in ''void Setup()'') and check it is OK: isTCSOk = tcs.begin(); You communicate with the TCS sensor via the I2C interface. In standard and typical configurations there is no need to instantiate the ''Wire'' I2C communication stack explicitly, nor provide the default I2C address of the TCS sensor (0x29) so ''begin'' is parameterless. Other overloaded functions for the ''begin'' let you provide all technical parameters. = Step 4 = Besides reading raw values for channels R, G, B and C, the ''tcs'' controller class provides additional functions to calculate colour temperature (in K) and colour intensity in Luxes. To read, use the following code: if(isTCSOk) { tcs.getRawData(&r, &g, &b, &c); colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c); lux = tcs.calculateLux(r, g, b); } R, G, and B reflect filtered values for Red, Green and Blue, respectively, while C (clear) is a non-filtered read that reflects brightness. Please be aware that there is no straightforward relation between R, G, B and C channels. == Result validation == Driving R, G and B channels for RGB LED that lights the sensors, you should be able to observe changes in the readings, accordingly. === FAQ === **What is the range of the values for the integration time of the TCS sensor?**: #define TCS34725_INTEGRATIONTIME_2_4MS \ (0xFF) /**< 2.4ms - 1 cycle - Max Count: 1024 */ #define TCS34725_INTEGRATIONTIME_24MS \ (0xF6) /**< 24.0ms - 10 cycles - Max Count: 10240 */ #define TCS34725_INTEGRATIONTIME_50MS \ (0xEB) /**< 50.4ms - 21 cycles - Max Count: 21504 */ #define TCS34725_INTEGRATIONTIME_60MS \ (0xE7) /**< 60.0ms - 25 cycles - Max Count: 25700 */ #define TCS34725_INTEGRATIONTIME_101MS \ (0xD6) /**< 100.8ms - 42 cycles - Max Count: 43008 */ #define TCS34725_INTEGRATIONTIME_120MS \ (0xCE) /**< 120.0ms - 50 cycles - Max Count: 51200 */ #define TCS34725_INTEGRATIONTIME_154MS \ (0xC0) /**< 153.6ms - 64 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_180MS \ (0xB5) /**< 180.0ms - 75 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_199MS \ (0xAD) /**< 199.2ms - 83 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_240MS \ (0x9C) /**< 240.0ms - 100 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_300MS \ (0x83) /**< 300.0ms - 125 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_360MS \ (0x6A) /**< 360.0ms - 150 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_401MS \ (0x59) /**< 400.8ms - 167 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_420MS \ (0x51) /**< 420.0ms - 175 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_480MS \ (0x38) /**< 480.0ms - 200 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_499MS \ (0x30) /**< 499.2ms - 208 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_540MS \ (0x1F) /**< 540.0ms - 225 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_600MS \ (0x06) /**< 600.0ms - 250 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_614MS \ (0x00) /**< 614.4ms - 256 cycles - Max Count: 65535 */ **What is the range of the values for the gain of the TCS sensor?**: typedef enum { TCS34725_GAIN_1X = 0x00, /* No gain */ TCS34725_GAIN_4X = 0x01, /* 4x gain */ TCS34725_GAIN_16X = 0x02, /* 16x gain */ TCS34725_GAIN_60X = 0x03 /* 60x gain */ } tcs34725Gain_t; **C channel reading is 0 and R, G or B readings are not reasonable**: It is possibly because of overdrive of the sensor with a light source (too bright). Consider lowering the RGB LED's intensity or changing the sensor's integration time and gain (shorter time, lower gain). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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)(("stm32duino", https://github.com/stm32duino)) 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32]] * [[en:iot-open:hardware2:actuators_motors|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === 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 #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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_1A: Use of fan === This scenario presents how to control the fan's rotation speed with a PWM signal. You can observe the rotation via the camera, but it would be rather hard to notice the change in the rotation speed. The fan is mounted on the top of the pressure tube and blows the air onto the pressure sensor. You can observe changes in sensor readings according to different fan speeds. How to use the sensor is shown in another scenario. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the PWM hardware channels of the STM32WB55 chip. In this case, we do not use an external library; instead, we use the hardware timer library built in the Arduino Core STM32 framework (stm32duino)(("stm32duino", https://github.com/stm32duino)) for STM32WB55 so that no additional external libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32]] * [[en:iot-open:hardware2:actuators_motors|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will change the fan's rotation speed up from 0% to 100% and down from 100% to 0%. Cotrol the rotation speed with the PWM signal. == Start == 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 laboratory equipment, the fan is connected to the hardware timer instance shared with the servo motor. 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 need to match the servo requirements. If you use the fan only the frequency can be freely chosen. In the case of setting the PWM duty cycle expressed in percentages, the minimum value is 0, and the max (full brightness) is 100. Note that low values of the duty cycle values do not provide enough energy to the fan motor so it does not start rotating from 1%. == Steps == To use PWM in STM32WB55, it is best to use a built-in hardware timer library. #include The hardware timer library uses internal timer modules and allows us to define channels attached to the timer. In this example, we will use 1 channel only for the fan. A PWM frequency is controlled with the timer shared with the servo motor. Channels control the PWM duty cycle. = Step 1 = Include the library, and define PIN assignments to channels and PWM frequency (50Hz): #include // Pin definition for the fan #define FAN_PWM_PIN PA1 //Arduino numbering A2, STM numbering PA_1 #define PWM_fan_freq 50 The GPIO pin controlling the fan is A2 (PA_1 in STM-type numbering). = Step 2 = Define variables for the timer object and handlers of channels. // PWM variables definitions HardwareTimer *MyTimFan; //Hardware Timer class for Fan uint32_t channelFAN; //1 channel Instantiate the timer object and initialise the fan channel for PWM, no rotation (0%): //Create Timer instance type based on the fan pin (the same timer is used for servo if needed). TIM_TypeDef *FanInstance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(FAN_PWM_PIN), PinMap_PWM); //Define the channel for fan channelFAN = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(FAN_PWM_PIN), PinMap_PWM)); //Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished. MyTimFan = new HardwareTimer(FanInstance); //Configure and start PWM MyTimFan->setPWM(channelFAN, FAN_PWM_PIN, 50, 0); // 50 Hertz, 0% dutycycle. To modify the fan's rotation speed after initialisation use the setCaptureCompare function. In the following example, the duty_cycle_value can vary between 0 and 100. MyTimFan->setCaptureCompare(channelFAN, duty_cycle_value, PERCENT_COMPARE_FORMAT); //modify duty cycle }; = Step 3 = Write a loop to change the rotation from stop to max, and then from max to stop. Too low duty cycle (1%) will not cause the fan to rotate. You can do some experiments on the minimal duty cycle which starts the fan to rotate. Mind to compose the code for cyclic speed changes up and down. A hint is below (for increasing the speed): // Increase fan speed for (int duty_cycle_value = 0; duty_cycle_value <= 100; duty_cycle_value++) { // Gradually increase duty cycle for fan speed MyTimFan->setCaptureCompare(channelFAN, duty_cycle_value, PERCENT_COMPARE_FORMAT); delay(20); // Delay for smooth transition } == Result validation == You should be able to observe the changes in the fan's rotation speed. It is advisable to connect this scenario with the scenario for reading the pressure measurements. === FAQ === **What is the maximum number of channels?**: the MCU we use here is STM32WB55 on the Nucleo board. The pins available are connected to timer 1 with three PWM channels (connected to RGB LED described in another scenario) and timer 2 with 4 PWM channels (with two of them connected to the servo and fan). A single timer can generate PWM signals with independent duty cycles and identical frequency. \\ **What is the maximum bit resolution for PWM?**: Maximum resolution is 16 bits. Note that we can express the duty cycle in a variety of formats. In the presented example we expressed it in percentage so it varies between 1 and 100. It can be also expressed in ticks, microseconds, and 1-16 bits numbers. \\ **What PWM frequency should I use?**: It depends on the signal use. For the fan or LEDs, no specific frequency is required, but for the servo, the frequency needs to be precisely 50Hz. If you use a servo and fan in the same project set the timer frequency at 50Hz. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_1B: Reading environmental data with a Bosch integrated sensor === We will read environmental data using a BME 280 sensor in this scenario. It is one of the most popular sensors in weather stations. It integrates a single chip's digital thermometer, hygrometer (air humidity), and air pressure meter. In our laboratory equipment, this sensor is located inside the yellow pressure chamber, under the fan which can blow the air onto it.\\ The sensor communicates with the microcontroller using the I2C bus. In all our laboratory nodes, the I2C bus uses two GPIOs: a D14 (PB_9 in Nucleo numbering) pin for the SDA line, and a D15 (PB_8 in Nucleo numbering) pin for SCL. Every integrated circuit connected to the I2C bus has its own address. BME 280 is visible under the address 0x76. For the details please refer to Table 1: STM32WB55 Node Hardware Details in [[en:iot-open:practical:hardware:sut:stm32|]]\\ This scenario can be run stand-alone to read weather data in the laboratory nodes' room. Still, it is also complementary to the scenario [[en:iot-open:practical:hardware:sut:stm32:emb1A_1|]] and may enable you to monitor the results of the fan operation that should induct changes in the air pressure. === Prerequisites === The static temperature, humidity and air pressure values can be read using a dedicated library: lib_deps = adafruit/Adafruit BME280 Library@^2.2.2 To observe air pressure changes over a short time, it is necessary to implement fan PWM control as described in the [[en:iot-open:practical:hardware:sut:stm32:emb1a_1|]].\\ 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: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. To implement monitoring of the air pressure changes, understanding how to control a fan with PWM is necessary: * [[en:iot-open:practical:hardware:sut:stm32:emb1a_1|]]. Technical documentation for the BME 280 sensor is available here: * [[https://www.mouser.com/datasheet/2/783/BST-BME280-DS002-1509607.pdf|BME 280]] Besides BME 280, there is another sensor in the Bosch family: BMP 280. The former one does not measure humidity, just temperature and air pressure. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] === Hands-on Lab Scenario === In this scenario, we only focus on reading the sensor. 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, air pressure, and humidity on any display (e.g. LCD). Remember to add units (C, %Rh, hPa). == Start == For statical measurements, ensure the fan is stopped. Note that the fan tends to spin up on itself (explained in STM1A) when the GPIO controlling the fan is not configured, so it is better to ensure it is set to output and low (0) to keep the fan stopped. Refer to the [[en:iot-open:practical:hardware:sut:stm32:emb1a_1|]] for details on controlling the fan. == 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, and also with a scenario STM_1A presenting instructions on controlling the fan that can change the air pressure in the yellow pressure chamber. = Step 1 = Include a BME 280 control library: #include = Step 2 = Declare BME's address, sensor controller class and variables to store readings: static const int BME280_addr = 0x76; //I2C address static bool isBMEOk = false; static float temperature; static float pressure; static float humidity; static Adafruit_BME280 bme280; //controller class = Step 3 = Initialise controller class: isBMEOk = bme280.begin(BME280_addr); If ''begin'' returns ''false'', then initialisation failed. This may be due to an invalid I2C address provided as a parameter of the ''begin'' function, a broken sensor, or broken connections between the MCU and the sensor. = Step 4 = Read environmental data (one at a time): temperature = bme280.readTemperature(); pressure = bme280.readPressure() / 100.0F; humidity = bme280.readHumidity(); The temperature is Celsius, air pressure is in Pascals (so we divide it by float 100 to obtain the hPa reading), and relative air humidity is in % (frequently referenced as %Rh). Note that the controller class has an exciting function of trading the altitude based on the sea-level pressure. We need to provide as the argument the current value of the sea-level pressure to have a correct altitude reading: float altitude = bme280.readAltitude(1013.00F); Note that due to the non-linear characteristics of the air pressure drop with increasing altitude, it does not work correctly at high altitudes. The library also has a mathematical calculation function that returns current sea level pressure if only altitude and local air pressure are known. It does not read the sensor itself, however: float seaLevelPressure = bme280.seaLevelForAltitude(230, bme280.readPressure()); In the example above first parameter it is the altitude (230m). == Result validation == The observable temperature is usually within the range of 19-24C, with humidity about 40-70%, strongly depending on the weather. On rainy days, it can even go higher. Air pressure depends on the current weather (assuming the fan is off) and is usually low to 890hPa (when low-pressure area) and even up to 1040hPa (when high-pressure area comes, usually during the summer). Spinning the fan may easily change air pressure by at least 1-2Pa up relative to the static pressure, depending on the fan's rotation speed. === FAQ === **I've got NaN (Not a Number) readings. What to do?**: Check if the I2C address corresponds to the BME280 sensor (should be 0x76), check if you initialised controller class and most of all, give the sensor some recovery time (at least 250ms) between consecutive readings. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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) {{ref>figuredigipot}}. 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.
{{ :en:iot-open:practical:hardware:sut:esp32:screenshot_from_2024-03-21_22-03-39.png?600 | Digital potentiometer DS1803 application in VREL Next Gen nodes }} 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: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. 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 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[https://www.analog.com/en/products/ds1803.html| DS1803 documentation (external link)]] === 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 128x128 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 128x128 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 [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]], 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: * [[en:iot-open:practical:hardware:sut:stm32:emb8_1]], * [[en:iot-open:practical:hardware:sut:stm32:emb9a_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] === 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 = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * [[en:iot-open:embeddedcommunicationprotocols2:1wire|]] === 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 [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]]). 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 #include 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_AT: Programming of the WiFi interface with AT commands === The STM32WB55 SoC doesn't have a WiFi network controller so our STM laboratory stands have the WiFi module based on ESP32-C3 SoC connected by serial port and controlled with AT commands. In this scenario, you will learn how to use these commands. === Prerequisites === To implement this scenario, it is necessary to get familiar with the LCD scenario first: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]]. It is also possible to use other displays if you prefer. Please refer to the appropriate chapter to learn how to do it. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:stm32|]], * [[en:iot-open:practical:hardware:sut:stm32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == This introductory scenario shows how to use AT commands to control the ESP32-C3 WiFi module connected to the STM laboratory stand. In this scenario, we will use simple AT commands and try to display if their execution finished properly. We will use the LCD to observe the behaviour of the device. You can use this as the template for the single step of the procedure of establishing a WiFi connection. == 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 == In this scenario, we just send one command and display the result. In further scenarios, you will learn how to connect to the WiFi access point, connect to the MQTT broker, subscribe to the MQTT topic, and publish the message on a specific topic. = Step 1 = Include the LiquidCrystal library in your source code to control LCD: #include "LiquidCrystal.h" = Step 2 = We will connect to the ESP32-C3 WiFi module with a hardware serial port. We need to instantiate the object of the ''HardwareSerial'' class. The ''LiquidCrystal'' object allows us to display data on LCD. // Pins definition for Hardware Serial #define RxD_PIN PC_0 //STM numbering #define TxD_PIN PC_1 //STM numbering HardwareSerial WiFiSerial(RxD_PIN, TxD_PIN, NC, NC); // LCD class const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); We will also declare two strings for comparison with the responses from the WiFi module String compOK; String compERROR; = Step 3 = In the ''setup();'' function we need to initialise both devices: WiFiSerial.begin(115200); lcd.begin(16, 2); And set the two strings for comparison: compOK = "OK"; compERROR = "ERROR"; = Step 4 = The AT commands are sent via a serial port connected to the ESP32-C3 module. At the beginning, we can send an empty command - "AT". The module should respond "OK" if everything works properly or "ERROR" if something goes wrong. // Display the message that the software has started lcd.setCursor(0,0); lcd.print("Started"); delay(1000); // Send the "AT" command to the module WiFiSerial.println("AT"); // Display the command on 1-st line of LCD lcd.setCursor(0,0); lcd.print("AT "); do { // Read the response till the LF character response = WiFiSerial.readStringUntil(0x0A); // Display the response on 2-nd line of LCD lcd.setCursor(0,1); lcd.print(response); // Repeat until "OK" comes } while (!(response.startsWith(compOK))); This code allows us to observe what AT command has been sent and all the responses. It waits until the "OK" comes which is the text sent by the module after every successful command. If something goes wrong we will see the AT command on the first line and "ERROR" on the second line of LCD, and the program stops. This can help with debugging. You can use this part of the code in the following scenarios for every AT command to observe their behaviour and easily debug possible errors. == Result validation == You should be able to see the "AT" displayed on the upper line of the LCD, and "OK" on the lower line of the LCD. If any error in the communication with the ESP32-C3 module occurs the second line shows "ERROR". Because LCD can't properly display some non-visible characters the presented code sometimes shows additional, non-letter characters. It is out of the scope of this scenario to filter these characters out. We leave the task of making visual improvements to your invention. === FAQ === **Do I need to write the program for the WiFi module?** No you don't have to. The ESP32-C3 module is flashed with the firmware from Espressif which implements all functionality for WiFi and MQTT. There are other modules which offer similar functionality.\\ **Can I use a similar WiFi module in my own IoT projects?** Certainly! The universal serial port connects the module so the module can be used with almost every microcontroller as the WiFi network controller.\\ **Are AT commands the same for all modules available?** The basing AT command set is generally the same, but advanced functionality such as WiFi, MQTT and others can use different syntax for modules from different vendors. Always refer to the original documentation for details. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_1: Reading MAC address of the WiFi === Each network card is supposed to have a unique physical address called a MAC address. MAC abbreviation stands for Medium Access Control protocol, which provides access to the physical link in the network layer. The STM32WB55 SoC doesn't have a WiFi network controller so our STM laboratory stands have the WiFi module based on ESP32-C3 SoC connected by serial port additional ESP32-C3 module and controlled with AT commands. To learn how to use these commands please refer to the STM_IoT_Intro chapter. \\ ESP32 chip has built-in MAC. MAC can be used to identify devices, but note that it is not a "strong" ID: it can be programmatically changed and easily discovered. In the following scenario, we present how to read and display the MAC address on LCD. Displaying on other display than LCD is up to the developer. You can refer to the appropriate scenario, as listed below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]], * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:stm32|]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:stm32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Present a MAC address on the selected display. The steps below present the reading part and display it on LCD. This display has 16 characters per line, but the commonly used format for MAC addresses requires 17 characters. For seeing the full MAC address modify the example and use the display other than LCD. The MAC addresses usually are expressed as six hexadecimal numbers separated by colons eg. "84:fc:e6:88:69:d5". == 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 LCD library in your source code: #include "LiquidCrystal.h" Create objects of the LCD and Hardware Serial classes: // Serial port class and configuration (Constructor uses STM port numbering) HardwareSerial WiFiSerial(RxD_PIN, TxD_PIN, NC, NC); // LCD class const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); Declare two variables with the strings to be compared with the responses from the WiFi module. String compOK; String compERROR; = Step 2 = In the Setup() function initialise the serial port, display, and strings. WiFiSerial.begin(115200); lcd.begin(16, 2); compOK = "OK"; compERROR = "ERROR"; We will use the code template from [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] to communicate with the WiFi module. The first command we will send "AT" to confirm that everything works properly: WiFiSerial.println("AT"); lcd.setCursor(0,0); lcd.print("AT "); do { response = WiFiSerial.readStringUntil(0x0A); lcd.setCursor(0,1); lcd.print(response); } while (!(response.startsWith(compOK))); delay(1000); The next command can be the "AT+CIPSTAMAC?" which returns the MAC address in the response message which looks like this: +CIPSTAMAC:"84:fc:e6:88:63:d1" We can implement the part of displaying the MAC by repeating the block of the code similar to the one presented above with two modifications. Change the "AT" command, and add inside the code the following lines: if (response.startsWith("+CIPSTAMAC")) { response.remove(0,12); lcd.setCursor(0,0); lcd.print(response); } == Result validation == You should be able to see the MAC address of the ESP32-C3 module. Using another node should change the MAC read. Book another device and discover its MAC. Because LCD can't properly display some non-visible characters the presented code sometimes shows additional, non-letter characters. It is out of the scope of this scenario to filter these characters out. We leave the task of making visual improvements to your invention. === FAQ === **Can I change MAC?**: Actually, yes, you can. It is not advised, however, because you may accidentally generate an overlapping address that will collide with another device in the same network. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_2: Connecting to the WiFi Access Point and presenting IP === Each computer connected to the Internet is identified with the IP address. IP abbreviation stands for Internet Protocol, which is responsible for transmitting data packets between computers in the whole global web - the Internet. The same mechanism is used for addressing and transmitting packets among IP-capable IoT devices. The most popular local networks which support IP addressing are Ethernet and WiFi. Currently, the transition from traditional IPv4 to the new IPv6 version is occurring. Our laboratory supports IPv4. \\ The STM32WB55 SoC doesn't have Ethernet or WiFi network controller so our STM laboratory stands have the WiFi module based on ESP32-C3 SoC. It is connected by a serial port and controlled with AT commands. To learn how to use these commands please refer to the STM_IoT_Intro chapter. \\ ESP32 chip gets the IP address from the DHCP server after establishing the connection with the WiFi access point. In this scenario, we present how to connect to a WiFi network and read and display the IP address on an LCD. Displaying on other display than LCD is up to the developer. You can refer to the appropriate scenario, as listed below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]], * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:stm32|]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:stm32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Join the WiFi network. Present an IP address on the selected display. The commonly used format for IPv4 addresses requires 15 characters, so LCD having 16 characters is sufficient to present the address. The steps below show the starting part of the software. How to implement the full software please refer to the previous scenarios: * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]] The IPv4 addresses are usually expressed as four decimal numbers ranging from 0 to 255 separated by dots eg. "192.168.1.100". == 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 = The beginning of the code is the same as in the previous scenario, so make a copy of it: * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]] We will use the code template from [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] repeated for every AT command. WiFiSerial.println("AT"); lcd.setCursor(0,0); lcd.print("AT "); do { response = WiFiSerial.readStringUntil(0x0A); lcd.setCursor(0,1); lcd.print(response); } while (!(response.startsWith(compOK))); delay(1000); = Step 2 = The procedure of connecting to the WiFi requires some steps. Below we present the AT commands only, your task is to implement the full code which sends the command and waits for the response for each command. // Test if module is available "AT" // Reset the module "AT+RST" // Prevent from storing the WiFi join parameters in non-volatile memory "AT+SYSSTORE=0" // Start module in station mode (which can join the access point) "AT+CWMODE=1" // Join the access point. Use SSID and password. "AT+CWJAP=\"SSID\",\"password\"" // Get the IP address "AT+CIPSTA?" Texts sent as part of the message are delimited with double quotation marks. In C++ we need to mark them with backslash characters inside the string constants. Examples above include these markings. Some explanation can be needed for the "AT+RST" and "AT+SYSSTORE=0" commands. If we don't use the "AT+SYSSTORE=0" command our module stores the WiFi credentials in non-volatile memory and automatically connects to the network after powering on. In our laboratory, it is not recommended because the next commands can return "ERROR" if we try to connect the network and MQTT broker if we are already connected. If we don't use the "AT+RST" command our module will keep the WiFi connection active, even if we upload the new version of the software. It would result in the same error in the case of WiFi or MQTT reconnecting. = Step 3 = The command for receiving the IP address is "AT+CIPSTA?". It returns in the response message the IP address, IP address of the gateway and IP address mask: +CIPSTA:ip:"192.168.1.117" +CIPSTA:gateway:"192.168.1.1" +CIPSTA:netmask:"255.255.255.0" While LCD use we have to filter out unwanted elements of the response. We can do it by displaying the answer containing "+CIPSTA:IP:" only removing the first 12 characters. Look into the following code: if (response.startsWith("+CIPSTA:ip:")){ response.remove(0,12); lcd.setCursor(0,0); lcd.print(response); } == Result validation == You should be able to see the IP address of the ESP32-C3 module. Using another node ar even the same node another time can change the IP read. You can book another device and discover its IP. Because LCD can't properly display some non-visible characters the presented code sometimes shows additional, non-letter characters. It is out of the scope of this scenario to filter these characters out. We leave the task of making visual improvements to your invention. === FAQ === **Can I change the IP address?**: Normally IP addresses are assigned by the server known as DHCP. In some situations, you can use static IP assigned manually in the station. It is not advised, however, because you may accidentally generate an overlapping address that will collide with another device in the same network. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_3: Connecting to the MQTT broker and publishing data === In the following scenario, you will learn how to connect to the MQTT broker and publish a message. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. The requirement is to pass the scenarios * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]], * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]], * [[en:iot-open:practical:hardware:sut:stm32:IoT_2]]. To be able to connect to the WiFi network. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:stm32|]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:stm32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === Note - this scenario can be used in pair with [[[en:iot-open:practical:hardware:sut:stm32:iot_4|]] to build a publish-subscribe solution using two devices (sender and receiver). You need to book two devices then and develop them in parallel. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[en:iot-open:practical:hardware:sut:stm32:iot_2]]—present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then publish an MQTT message of your choice. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! The steps below show the principles of the software operation. How to implement the full software please refer to the previous scenarios: * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]] * [[en:iot-open:practical:hardware:sut:stm32:iot_2]] == 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 = The beginning of the code is the same as in the previous scenario, so make a copy of it: * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]] We will use the code template from [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] repeated for every AT command. WiFiSerial.println("AT"); lcd.setCursor(0,0); lcd.print("AT "); do { response = WiFiSerial.readStringUntil(0x0A); lcd.setCursor(0,1); lcd.print(response); } while (!(response.startsWith(compOK))); delay(1000); = Step 2 = In the firmware for the ESP32-C3 board, there are AT commands to work with the MQTT protocol. In this scenario we will use three of them: * "AT+MQTTUSERCFG..." - to configure MQTT c * "AT+MQTTCONN..." - to establish the connection to the broker * "AT+MQTTPUB..." - to publish the message on the topic Below we briefly describe the commands mentioning only the parameters important to us. Please refer to the Espressif documentation((https://docs.espressif.com/projects/esp-at/en/latest/esp32c3/index.html)) for the details of commands. If the parameter is taken within quotation marks it should be in such a form sent in a command.\\ Please refer to the MQTT description chapter for the meaning of some protocol details. **AT+MQTTUSERCFG** This command accepts the list of parameters: AT+MQTTUSERCFG=,,<"client_id">,<"username">,<"password">,,,<"path"> * LinkID - currently should be 0 * scheme - 1 (MQTT over TCP) * "client_id" - the unique ID of the MQTT client * "username" - user to connect to the broker * "password" - password for the user * cert_key-ID - should be 0 * CA_ID - should be 0 * "path" - should remain empty In our case, the command can look like this: AT+MQTTUSERCFG=0,1,\"STM32#0001\",\"username\",\"password\",0,0,\"\" **AT+MQTTCONN** This command accepts the list of parameters: AT+MQTTCONN=,<"host">,, * LinkID - currently should be 0 * "host" - the IP address or URL of the MQTT broker * port - the TCP port number, 1883 for most of the brokers * reconnect - 1 for automatic reconnection (recommended in our case) The command can look like this: AT+MQTTCONN=0,\"192.168.1.100\",1883,1 **AT+MQTTPUB** AT+MQTTPUB=,<"topic">,<"data">,, The list of parameters: * LinkID - currently should be 0 * "topic" - MQTT topic * "data" - payload of the message * qos - mode of the quality of service, 0, 1 or 2, default 0. * retain - retain flag, 0 or 1. = Step 3 = Implement the MQTT configuration and connection to the broker. Use the template for the "AT" command from the first step. Ensure that your node is successfully connected. = Step 4 = Implement publishing of the MQTT message. You can do it just once in the "setup();" function, or periodically in the "loop();". While working periodically be sure not to send messages too often. Reasonable period is 1-5 seconds. You can send the content of the counter to observe value changes in the concurrent messages. == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then publish a message (once or periodically). Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a subscriber (as present in the scenario [[[en:iot-open:practical:hardware:sut:stm32:iot_4|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to observe messages that you publish. Because LCD can't properly display some non-visible characters the presented code sometimes shows additional, non-letter characters. It is out of the scope of this scenario to filter these characters out. We leave the task of making visual improvements to your invention. === FAQ === **Can I publish messages on different topics?**: Certainly you can. You can publish the readings of the temperature sensor with one topic, and readings of the humidity with another. The limit of the number of different topics comes only from the available resources of the MQTT broker.\\ **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_4: Connecting to the MQTT broker and subscribing to the topic === In the following scenario, you will learn how to connect to the MQTT broker and subscribe to the chosen topic to receive messages. === Prerequisites === To implement this scenario, it is necessary to get familiar with the LED controlling scenario: * [[en:iot-open:practical:hardware:sut:stm32:emb9A_1]], and at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:stm32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:stm32:emb7_1|]]. The requirement is to pass the scenarios * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]], * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]], * [[en:iot-open:practical:hardware:sut:stm32:IoT_2]], * [[en:iot-open:practical:hardware:sut:stm32:IoT_3]]. To be able to connect to the WiFi network and MQTT broker. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:stm32|]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:stm32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === Note - this scenario can be used in pair with [[[en:iot-open:practical:hardware:sut:stm32:iot_3|]] to build a publish-subscribe solution using two devices (sender and receiver). You need to book two devices then and develop them in parallel. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[en:iot-open:practical:hardware:sut:stm32:iot_2]] — present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then subscribe to the MQTT topic of your choice. Present incoming data on the chosen display or control other actuators. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! The steps below show the principles of the software operation. How to implement the full software please refer to the previous scenarios: * [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] * [[en:iot-open:practical:hardware:sut:stm32:IoT_1]] * [[en:iot-open:practical:hardware:sut:stm32:iot_2]] * [[en:iot-open:practical:hardware:sut:stm32:iot_3]] == 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 = The big part of the code is the same as in the previous scenario, so make a copy of it: * [[en:iot-open:practical:hardware:sut:stm32:IoT_3]] We will use the code template from [[en:iot-open:practical:hardware:sut:stm32:IoT_AT]] repeated for every AT command. WiFiSerial.println("AT"); lcd.setCursor(0,0); lcd.print("AT "); do { response = WiFiSerial.readStringUntil(0x0A); lcd.setCursor(0,1); lcd.print(response); } while (!(response.startsWith(compOK))); delay(1000); = Step 2 = In the firmware for the ESP32-C3 board, there are AT commands to work with the MQTT protocol. In this scenario we will use three of them: * "AT+MQTTUSERCFG..." - to configure MQTT c * "AT+MQTTCONN..." - to establish the connection to the broker * "AT+MQTTSUB..." - to subscribe to the message on the topic Below we briefly describe the commands mentioning only the parameters important to us. Please refer to the Espressif documentation((https://docs.espressif.com/projects/esp-at/en/latest/esp32c3/index.html)) for the details of commands. If the parameter is taken within quotation marks it should be in such a form sent in a command.\\ Please refer to the MQTT description chapter for the meaning of some protocol details. **AT+MQTTUSERCFG** This command accepts the list of parameters: AT+MQTTUSERCFG=,,<"client_id">,<"username">,<"password">,,,<"path"> * LinkID - currently should be 0 * scheme - 1 (MQTT over TCP) * "client_id" - the unique ID of the MQTT client * "username" - user to connect to the broker * "password" - password for the user * cert_key-ID - should be 0 * CA_ID - should be 0 * "path" - should remain empty In our case, the command can look like this: AT+MQTTUSERCFG=0,1,\"STM32#0001\",\"username\",\"password\",0,0,\"\" **AT+MQTTCONN** This command accepts the list of parameters: AT+MQTTCONN=,<"host">,, * LinkID - currently should be 0 * "host" - the IP address or URL of the MQTT broker * port - the TCP port number, 1883 for most of the brokers * reconnect - 1 for automatic reconnection (recommended in our case) The command can look like this: AT+MQTTCONN=0,\"192.168.1.100\",1883,1 **AT+MQTTSUB** AT+MQTTSUB=,<"topic">, The list of parameters: * LinkID - currently should be 0 * "topic" - MQTT topic * qos - mode of the quality of service, 0, 1 or 2, default 0. The command can look like this in the example below: AT+MQTTSUB=0,\"topic\",0 After a successful subscription, the ESP32-C3 module will send messages in the following form: +MQTTSUBRECV:,<"topic">,,data * LinkID - always 0 * "topic" - MQTT topic * data_length - number of bytes of the following data packet * data - message payload = Step 3 = Implement the MQTT configuration, connection to the broker, and subscription to the chosen topic. Use the template for the "AT" command from the first step. Ensure that your node is successfully connected and the topic subscribed. = Step 4 = Implement handling of the MQTT messages. Here we present how to control LED with messages sent with the topic "topic". If the message payload is 0 LED will be off, if it is 1 LED will be on. You can use it as the template for more advanced control of any device in the laboratory equipment.\\ Declare variables to store the strings for comparison and define LED pin. #define LED_GREEN D3 String compend0, compend1; String response; Prepare string variables and set the mode of the LED pin. compend0 = "+MQTTSUBRECV:0,\"topic\",1,0"; compend1 = "+MQTTSUBRECV:0,\"topic\",1,1"; pinMode(LED_GREEN,OUTPUT); In the "loop();" implement periodic reads of the hardware serial port, and handle incoming messages. response = WiFiSerial.readStringUntil(0x0A); if (response.startsWith(compend0)) { digitalWrite(LED_GREEN,0); }; if (response.startsWith(compend1)) { digitalWrite(LED_GREEN,1); }; == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then subscribe to the topic. After a successful subscription, you should receive messages published on the topic of your choice. Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a publisher (as present in the scenario [[[en:iot-open:practical:hardware:sut:stm32:iot_3|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to publish messages. Because LCD can't properly display some non-visible characters the presented code sometimes shows additional, non-letter characters. It is out of the scope of this scenario to filter these characters out. We leave the task of making visual improvements to your invention. === FAQ === **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **Can I subscribe to more than one topic?**: Yes, you can. For every incoming message you would receive the information on the topic of this specific message, so you would be able to recognise them.\\ **How do I send messages to which I am subscribed?**: Use a software client, such as [[http://mqtt-explorer.com/| MQTT Explorer]], if you're able to access the "internal IoT" network (you're in the range of the network). If you're remote, the only way is to book another device and implement a client publishing a message with the appropriate topic, as presented in the scenario [[[en:iot-open:practical:hardware:sut:stm32:iot_3|]]. Our MQTT broker is also visible in the campus network on the wired interfaces so that you can access it, e.g. via EduVPN or from the laboratory computers. Refer to the supervisor for IP and credentials.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_5: BLE Beacon === This scenario presents how to create the Bluetooth Low Energy beacon device which periodically broadcasts a small amount of information, and the client device which can receive packets sent by the beacon. Beacons are usually used for sending useful information (eg. the web address of the owner, a link to the page with tourist information). In some cases, they simply send the identification number recognised by a dedicated mobile application allowing the users to localise themselves. BLE Beacons just broadcast the information, they are not capable of receiving any information from the users nearby. To do this scenario two stations are needed, one for the beacon and one for the client device. At the time of writing this book the Bluetooth Low Energy library for STM32WB55 didn't support reading the remote advertising raw data. That's why we can implement the Eddystone beacon device and simple reader which confirms the presence of the device with Eddystone UUID. You can use the beacon receiver build with ESP32 if both SoCs are available in the same physical laboratory you work with. === Prerequisites === We will use the advertising process in this scenario so an understanding of the principles of the Bluetooth Low Energy protocol is necessary. It's good to understand the Eddystone data format used to encode URLs in advertising frames. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] * Interesting article on Beacons with a description of Eddystone, and iBeacon packets((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)) === Hands-on Lab Scenario === This scenario is intended to be implemented using two laboratory nodes. One node will work as the beacon device, while the second will receive the beacon's packets and display information on the LCD. == Task to be implemented == **Task 1** Implement a program that operates as the BLE beacon which advertises itself with the link to the website. This can be done with the Eddystone beacon format. **Task 2** Implement the receiver of advertising frames broadcasted by the beacon device capable of displaying simple information on the LCD screen. == Start == We need to implement both parts of the software to be able to observe the results (steps 1-3). **The Task 1** we implement in steps 1 and 2. **The Task 2** we implement in step 3. == Steps == We can go through the lab in a few steps. In the beginning, we will write a simple program which advertises the device with the default parameters set in the BLE library. As the payload of the advertising frame, we will use the Eddystone format to send the chosen URL address. = Step 1 = Let's begin with including Arduino and BLE libraries. #include "Arduino.h" #include "STM32duinoBLE.h" #include "BLEAdvertisingData.h" We need some variables to store the class objects. The eddystone_content stores the bytes of the full Eddystobe advertising packet. The advData variable is used to pass this data to the BLE device class instance. HCISharedMemTransportClass HCISharedMemTransport; BLELocalDevice BLEObj(&HCISharedMemTransport); BLELocalDevice& BLE = BLEObj; BLEAdvertisingData advData; uint8_t eddystone_content[16]; The setup function creates and initialises the BLE device instance with a chosen name. If we add our text in the BLE.setLocalName() function we will see this text as the device name in the advertising frame. It will also appear as the value of the Device Name characteristic in the Generic Access service. If we don't define the name it will remain empty so it will be hard to find our device in the BLE neighbourhood. Next, we will add the Eddystone data to the Eddystone packet (this part will be explained in step 2). In the end, we will set our device as not connectable and start advertising. Beacon devices usually do not accept connections. They only advertise themselves. void setup(){ // begin initialization BLE.begin(); // set advertised local name and service UUID: BLE.setLocalName("STM32 Beacon"); ... // We will implement this part in step 2 // set our device as not connectable BLE.setConnectable(false); // start advertising BLE.advertise(); }; The loop function can remain empty or call the delay() function. void loop(){ delay(5000); }; = Step 2 = The advertisement frame is composed of fields. Every field starts with its length (1 byte, including the field type byte). The second byte of the field encodes the field type. The selected field codes used in example advertising frame are presented in the table below. ^ Field length ^ Field code ^ Field type ^ Default value ^ | 0x02 | 0x01 | Flags | 0x06 | | 0x05 | 0x12 | Peripheral Connection Interval Range | 0x20004000 | | 0x0D | 0x09 | Complete Local Name | "STM32 Beacon" | | 0x02 | 0x0A | Tx Power Level | 0x09 | Except for the text information as the device name, it is possible to send other data such as the web address which directs us to additional information. There are some formats defined for use in beacon devices. One of them is Eddystone invented by Google. It is used in the advertisement frames for simplification of the URL encoding. It requires the presence of the field "List of 16-bit UUIDs" (code 0x03) which shows the UUIDs of the Eddystobe service (0xAAFE value), and the service data field (code 0x16) compatible with Eddystone format. These additional fields are shown in the table below. ^ Field length ^ Field code ^ Field type ^ Value ^ | 3 | 0x03 | Complete list of 16-bit service UUIDs | 0xAAFE | | 14 | 0x16 | Service data | Eddystone format | The Eddystone field starts with service UUID (0xAAFE), next specifies the content type (0x10 for URL), then the transmitting power in [dBm], and then the compressed URL. The UUID of Eddystone appears twice. Once as the element of the list of available services, the second time as the identifier of the service field. To compress URLs some default prefixes and suffixes were defined as non-ASCII characters. They are presented in the tables below. ^ Decimal ^ Hex ^ Prefix ^ | 0 | 0x00 | %%http://www.%% | | 1 | 0x01 | %%https://www.%% | | 2 | 0x02 | %%http://%% | | 3 | 0x03 | %%https://%% | ^ Decimal ^ Hex ^ Suffix ^ | 0 | 0x00 | .com/ | | 1 | 0x01 | .org/ | | 2 | 0x02 | .edu/ | | 3 | 0x03 | .net/ | | 4 | 0x04 | .info/ | | 5 | 0x05 | .biz/ | | 6 | 0x06 | .gov/ | | 7 | 0x07 | .com | | 8 | 0x08 | .org | | 9 | 0x09 | .edu | | 10 | 0x0a | .net | | 11 | 0x0b | .info | | 12 | 0x0c | .biz | | 13 | 0x0d | .gov | The Eddystone payload can be created as the table of bytes (uint8_t), so first we need to fill the byte table with details of the advertising packet. The code presented below should go to the setup() function. eddystone_content[0] = 0x02; //Length of FLags field eddystone_content[1] = 0x01; //Flags ID eddystone_content[2] = 0x06; //Flags value eddystone_content[3] = 0x03; //Length of service field eddystone_content[4] = 0x03; //List of 16-bit UUIDs eddystone_content[5] = 0xAA; //UUID of Google Eddystone eddystone_content[6] = 0xFE; eddystone_content[7] = 8; //length of Eddystone field eddystone_content[8] = 0x16; //Service data eddystone_content[9] = 0xAA; //Eddystone UUID eddystone_content[10] = 0xFE; eddystone_content[11] = 0x10; //URL Eddystone frame type eddystone_content[12] = 0xF4; //Tx power [dBm] eddystone_content[13] = 0x00; //prefix: "http://www." eddystone_content[14] = '2'; //URL eddystone_content[15] = 0x07; //suffix: ".com" The created payload can be used as the payload data of the advertising packet. advData.setRawData(eddystone_content,16); BLE.setAdvertisingData(advData); We created a very short name for the URL to fit in one line of LCD screen (16 characters). The resulting text would be "http://www.2.com" = Step 3 = It is the time to receive the advertising packet. We will do it with another device starting with including the libraries required. #include "Arduino.h" #include "STM32duinoBLE.h" #include "LiquidCrystal.h" There can be a lot of BLE devices in the range of our receiver. We should find the one that we are interested in so we define the UUID of the Eddystone service. #define REMOTE_SERVICE_UUID "feaa" Our program will use objects of two classes and some variables: HCISharedMemTransportClass HCISharedMemTransport; BLELocalDevice BLEObj(&HCISharedMemTransport); BLELocalDevice& BLE = BLEObj; The received information will be displayed on the LCD, so we need to configure it (similar to the scenario EMB5) // LCD class (Constructor uses STM port numbering) const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); The setup function initialises the display and the Bluetooth, sets up and starts the scanning process. An important step is registering the callback function. The callback function is called every time the scanner receives the advertising frame. void setup() { lcd.begin(16, 2); lcd.clear(); lcd.setCursor(0,0); lcd.print("Searching for"); lcd.setCursor(0,1); lcd.print("Beacon device"); // initialize the BLE hardware BLE.begin(); // start first scanning for peripherals int ret = 1; do { ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); } In the loop function, we wait for the scanned device and display info if the Eddystone device is present. Using STM32WB55 SoC you can't see the detailed content of Eddystone advertising frame. You can use either SoC (like ESP32), if available in the same physical laboratory. void loop() { // check if a peripheral has been discovered BLEDevice peripheral = BLE.available(); if (peripheral) { lcd.clear(); if (peripheral.localName() == "STM32 Beacon") { // display remote name lcd.setCursor(0,0); lcd.print(peripheral.localName()); } if (peripheral.advertisedServiceUuid() == REMOTE_SERVICE_UUID){ lcd.setCursor(0,1); lcd.print(REMOTE_SERVICE_UUID); } } } == Result validation == After the implementation of steps 1-3, you should be able to see the name of the beacon device on the first line of LCD and the Eddystone UUID on the second line of LCD. == Further work == You can try to implement the beacon device compatible with iBeacon. The description can be found on the website ((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)). === FAQ === **What is the maximum length of the advertising frame?**: It depends on the BLE version. In previous versions, it used to be 31 bytes only, but in newer versions, even 255 bytes are allowed. \\ **How often should the device send advertising frames?**: The delay between advertising frames can vary depending on the device. Beacon devices should send them every 100-200 ms. Other devices usually advertise every 2 seconds. Notice that advertising consumes energy, so the battery-powered devices advertise with lower frequency. \\ **What if I need to send some info other than in Eddystone or iBeacon format?**: You can send the text or even some data as part of the advertising frame. Some devices use the advertising frames to send simple 1-2 bytes of measurement data. More data can be sent after establishing the connection between the primary and secondary devices with the use of characteristics. \\ **What is the purpose of the transmitting power data presence in the advertising frame?**: The value sent as the transmitting power should represent real power at a 0-meter distance from the beacon. It can be used to estimate the actual distance of the user from the beacon. The distance needs to be calculated by application on the mobile phone with knowledge of the transmitting power of the beacon and actual measurement of received signal strength (known as RSSI). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_6: BLE Communication with characteristics === This scenario presents how to create the Bluetooth Low Energy server device and corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control writing the data to. The client connects to a server and reads the data. This scenario presents the use of the concept of services and characteristics. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the simplest implementation to be extended in further scenarios. The Nucleo STM32WB55 development boards which are used in our STM laboratory don't have the BLE firmware flashed by default. If you would like to test this scenario on your own board please refer to the STM documentation on the flashing process ((https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Connectivity:STM32WB_FUS)). Read and follow the documentation carefully because of the danger of "bricking" your device. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. \\ **Task 2.** Implement a client device, to read the exemplary data from a server. == Start == You can use the beacon and simple client programs from [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] as the starting point. == Steps == We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. The program returns automatically to the advertisement procedure after disconnecting. = Step 1 = Let's begin with a simple program which advertises a single service. The code should start with including Arduino and BLE libraries. #include #include We need variables for the BLE device class object and data transport class object. HCISharedMemTransportClass HCISharedMemTransport; BLELocalDevice BLEObj(&HCISharedMemTransport); BLELocalDevice& BLE = BLEObj; Any service or characteristic is identified with a unique UUID. If you use the standard service, like eg. Health Thermometer or Binary Sensor Service you can use a 16-bit UUID defined by Bluetooth SIG in the document ((https://www.bluetooth.com/specifications/assigned-numbers/)). The same document defines the UUIDs for characteristics. Example of the standard UUIDs for service and characteristic: Health Thermometer Service - 0x1809 Temperature Measurement Characteristic - 0x2A1C While none of the standard UUIDs fits your device you need to define your own UUID. It must be 128-bit long and can be randomly generated with the tool available on the website https://www.uuidgenerator.net/((https://www.uuidgenerator.net/)). The service UUID and characteristic UUID must differ. Although they can be completely different in many implementations the UUIDs of characteristics grouped under one service differ on one digit. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" #define CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" We create the service and characteristic objects with defined UUIDs // BLE Service - custam 128-bit UUID BLEService pService(SERVICE_UUID); // BLE Characteristic - custom 128-bit UUID, read and writable by central BLECharacteristic pCharacteristic(CHARACTERISTIC_UUID, BLERead | BLEWrite, 16); Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "SUT STM" text. All is done in the setup() function. The function creates and initialises the BLE device instance named "SUT STM BLE". It creates a service with the characteristic, sets the characteristic's initial value and finally starts advertising. void setup() { // begin initialization BLE.begin(); // set advertised local name and service UUID: BLE.setLocalName("SUT STM BLE"); BLE.setAdvertisedService(pService); // add the characteristic to the service pService.addCharacteristic(pCharacteristic); // add service BLE.addService(pService); // set the initial value for the characeristic: pCharacteristic.writeValue("SUT STM"); // start advertising BLE.advertise(); } Although it is possible to have many services in one device, due to the limited size of the advertisement packet, not all service UUIDs can be broadcast. Even more, service UUID does not have to appear in the advertising frame, but its presence makes it possible to scan nearby devices by their functionality, not by names only. In the loop function, we need to call a function waiting for the connection of the client (central device). void loop() { // listen for BLE central device to connect: BLE.central(); } = Step 2 = In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client_char.drawio.svg?500 |The client algorithm}} = Step 3 = While we have analysed the client's behaviour we can start implementation. Let's begin with the libraries needed. Add the libraries to the platformio.ini. lib_deps = stm32duino/STM32duinoBLE @ 1.2.6 arduino-libraries/LiquidCrystal@^1.0.7 And import libraries into the cpp file. #include "Arduino.h" #include "STM32duinoBLE.h" #include "LiquidCrystal.h" Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. // The remote service we wish to connect to. #define REMOTE_SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" // The characteristic of the remote service we are interested in. #define REMOTE_CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" We intentionally specified here the same UUIDs as in ESP32 BLE scenarios. If both platforms are physically available in the same laboratory, you can try to connect different hardware platforms with BLE. Some global class objects and variables will be needed for our software. // Variables HCISharedMemTransportClass HCISharedMemTransport; BLELocalDevice BLEObj(&HCISharedMemTransport); BLELocalDevice& BLE = BLEObj; BLECharacteristic remoteCharacteristic; We need to add and configure the LCD to be able to observe the results. // LCD class (Constructor uses STM port numbering) const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; LiquidCrystal lcd(rs, en, d4, d5, d6, d7); In the setup() function we initialise LCD and Bluetooth and start scanning for the device having the service UUID we want to connect. void setup() { // initialise LCD lcd.begin(16, 2); lcd.clear(); // initialise the BLE hardware BLE.begin(); // start scanning for peripherals int ret = 1; do { ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); } In a loop() function we check if a peripheral with UUID we are interested in was discovered. If so we check its name. If the remote device's name is as we expect we print its name, stop scanning, and call the function which reads and displays the value of the characteristic. While the function returns, we restart scanning. void loop() { // check if a peripheral has been discovered BLEDevice peripheral = BLE.available(); if (peripheral) { if (peripheral.localName() == "SUT STM BLE") { // display remote name lcd.setCursor(0,0); lcd.print(peripheral.localName()); // stop scanning int ret = 1; do { ret = BLE.stopScan(); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); // display the characteristic value display_characteristic(peripheral); } // peripheral disconnected, start scanning again int ret = 1; do { ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); } } The function reads the characteristic value in some steps: - Connects to the remote device. - Discovers attributes of the remote service. - Retrieves the characteristic with specified UUID. - Checks if the remote device is properly connected. - Checks if the remote characteristic can be read. - Reads the text and displays it on LCD. - Disconnects from the remote server. void display_characteristic(BLEDevice peripheral) { // connect to the peripheral if (peripheral.connect()) { // discover peripheral attributes if (peripheral.discoverAttributes()) { // retrieve the remote characteristic to read remoteCharacteristic = peripheral.characteristic(REMOTE_CHARACTERISTIC_UUID); if (remoteCharacteristic) { // if the peripheral is connected display the value if (peripheral.connected()) { // check if the characteristic can be read if (remoteCharacteristic.canRead()){ char char_text[16]; remoteCharacteristic.readValue(char_text,16); int i=remoteCharacteristic.valueLength(); lcd.setCursor(0,1); lcd.write(char_text,i); } // if (remoteCharacteristic.canRead()) } // if (peripheral.connected()) } // if (remoteCharacteristic) peripheral.disconnect(); } // if (peripheral.discoverAttributes()) } // if (peripheral.connect()) } == Result validation == You should be able to read the name of the remote device in the first line of the LCD. After establishing the connection, the characteristic value should appear on the display's second line. === FAQ === **What types of data can be sent with the characteristics?**: The BLE characteristics can send any value as a single byte or a sequence of bytes. BLE does not interpret the data in characteristic in any way. Interpretation lies on the side of the application. \\ **What if I need to read or write more data than a single text?**: It is possible to implement many services in one device and add many characteristics to one service. Theoretically, the maximum number of characteristics in one service is 512, but the implementation and the memory available for the BLE library limit it. \\ **What is the maximum length of the data in the characteristics?**: The maximum length of the data in the characteristics in the BLE specification is 512 bytes. \\ **Can I send data between SoCs coming from different vendors?**: That's what the specifications of the network protocols are defined for. If devices are in their transmission range (i.e. in one physical laboratory site) you can implement a BLE server on one type of SoC and a BLE client on another. \\ === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== STM_IoT_7: BLE Communication with indications === This scenario presents how to extend the Bluetooth Low Energy server and client devices with a notification or indication mechanism for sending data automatically. If enabled, notifications or indications are sent at any time while the data in the server is updated. A difference between them is that a notification is an unacknowledged message while an indication is an acknowledged message. While one of them is enabled by the client, the server decides on the time of the message sent. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the [[en:iot-open:practical:hardware:sut:stm32:IoT_6]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:stm32|]] * [[en:iot-open:practical:hardware:sut:stm32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the extension of the scenario implemented in [[en:iot-open:practical:hardware:sut:stm32:IoT_6]] The Nucleo STM32WB55 development boards which are used in our STM laboratory don't have the BLE firmware flashed by default. If you would like to test this scenario on your own board please refer to the STM documentation on the flashing process ((https://wiki.stmicroelectronics.cn/stm32mcu/wiki/Connectivity:STM32WB_FUS)). Read and follow the documentation carefully because of the danger of "bricking" your device. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. We should be able to subscribe to indications of the characteristic. \\ **Task 2.** Implement a client device, capable of subscribing to the characteristic and reading the exemplary data from a server with a indication mechanism. == Start == It is advised to use the server and client programs from [[en:iot-open:practical:hardware:sut:stm32:IoT_6]] as the starting point. == Steps == We will pass through the lab in a few steps. We will add the second characteristic to the example from lab [[en:iot-open:practical:hardware:sut:stm32:IoT_6]]. We will configure this characteristic as capable of transmitting data with indication. It allows us to establish a connection and, if successfully connected, enable the indication feature. With this feature enabled, the peripheral device initiates data transmission. It can be used for periodic measurement reading or updating information on value change. = Step 1 = We start with the program written during the laboratory [[en:iot-open:practical:hardware:sut:stm32:IoT_6]] by adding another characteristic to the service and variable required to hold the pointer to the characteristic class. We need to define the UUID for this characteristic. #define INDICATE_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" // BLE Characteristic - custom 128-bit UUID, read and notify enabled BLEByteCharacteristic pIndicateCharacteristic(INDICATE_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLEIndicate); In the setup() function we add a new characteristic to the service and set its initial value. // add the characteristic to the service pService.addCharacteristic(pIndicateCharacteristic); // set the initial value for the characteristic: pIndicateCharacteristic.setValue(1); After these modifications, it would be possible to observe an additional characteristic in the service with the possibility of reading, writing, and enabling notifications. = Step 2 = At this step, we implement periodical data sending. We add the counter variable, an integer for holding the value incremented every loop pass. We will increment this counter with use of millis() function to do not block the loop() with delay(); int counter = 1; int delay_millis; In the main loop() we implement the incrementation of the counter and updating of the characteristic. void loop() { // listen for BLE peripherals to connect: BLE.central(); if (millis() > delay_millis +1000){ delay_millis = millis(); pIndicateCharacteristic.setValue(counter); counter++; } } = Step 3 = In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client.drawio.svg?500 |The client algorithm}} = Step 4 = While we have analysed the client's behaviour we can start implementation. Let's begin with modifying the remote characteristic for receiving notifications. Notice its UUID they must match the one defined in the server. #define REMOTE_NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" We intentionally specified here the same UUIDs as in ESP32 BLE scenarios. If both platforms are physically available in the same laboratory, you can try to connect different hardware platforms with BLE. In a loop() function we check if a peripheral with UUID we are interested in was discovered. If so we check its name. If the remote device's name is as we expect we print its name, stop scanning, and call the function which reads and displays the value of the characteristic. This function uses the polling method of reading the updated characteristic value. While the function returns, we restart scanning. At the time of writing this book the mechanism available for automatic update of the characteristic value was indication only. Notifications couldn't be subscribed. We will present the full code of the function to subscribe and receive the indications. The function reads the characteristic value in some steps: - Connects to the remote device. - Discovers attributes of the remote service. - Retrieves the characteristic with specified UUID. - Checks if the remote device is properly connected. - Checks if the remote characteristic can be subscribed. - Subscribes to the characteristic with indication mechanism. - Waits for the characteristic value update. - Reads the value and displays it on LCD. void display_characteristic(BLEDevice peripheral) { // connect to the peripheral if (peripheral.connect()) { // discover peripheral attributes if (peripheral.discoverAttributes()) { // retrieve the remote characteristic to read remoteCharacteristic = peripheral.characteristic(REMOTE_NOTIFY_CHARACTERISTIC_UUID); if (remoteCharacteristic) { // check if the characteristic can be read if (remoteCharacteristic.canSubscribe()){ remoteCharacteristic.subscribe(); // if the peripheral is connected display the value while (peripheral.connected()) { // check if the value was updated if (remoteCharacteristic.valueUpdated()) { // yes, get the value, characteristic is 1 byte so use byte value byte value = 0; remoteCharacteristic.readValue(value); lcd.setCursor(0,1); lcd.print(value); } // if (remoteCharacteristic.valueUpdated()) } // while (peripheral.connected()) } // if (remoteCharacteristic.canSubscribe()) } // if (remoteCharacteristic) peripheral.disconnect(); } // if (peripheral.discoverAttributes()) } // if (peripheral.connect()) } == Result validation == You should be able to read the name of the remote device in the first line of the LCD. After establishing the connection, the characteristic value should appear on the display's second line. === FAQ === **What is the difference between notification and indication?**: As it was mentioned in the beginning the notification is an unacknowledged message while an indication is an acknowledged message. It means that if the indication message remains unacknowledged, the peripheral device won't send another message for this characteristic. \\ **Is the notification/indication mechanism similar to interrupts?**: You are right. They work in a similar manner as interrupts in the microprocessor. They allow sending the data asynchronously, and the peripheral decides about the time of the message sent like the peripheral decides on the time of signalling interrupt. Data transmission when the central device sends the packet with a characteristic read command is similar to the polling method used in microprocessors. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
====== ItSilesia ESP32 Mobile Laboratory Node Hardware Reference ====== In general, these laboratory nodes share the same physical configuration as the [[en:iot-open:practical:hardware:sut:esp32|ESP32 laboratory of the SUT]] but differ in IP addressing. For this reason, all laboratory hands-on-lab scenarios for SUT are valid for ItSilesia's mobile lab, still keep in mind different services and addressing. ===== 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 {{ref>esp32itsilesianodehardware}} lists all hardware components of ItSilesia'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 depicted in the figure {{ref>esp32itsilesianode1}} and referenced by component numbers in the table {{ref>esp32itsilesianodehardware}}.
{{:en:iot-open:practical:hardware:sut:vrel_nextgen_sut_motherboard.png?560|}} ESP32-S3 ItSilesia Node
^ 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 | 2x16 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, 250x122 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, 128x128 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. |
ESP32-S3 ItSilesia Node Hardware Details
The MCU working behind the laboratory node is ESP32-S3-DevKitM-1-N8 made by Espressif ((https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html)), present in figure {{ref>esp32s3minidevkit}}:
{{:en:iot-open:practical:hardware:sut:20231213_154426.jpg?200|ESP32-S3-DevKitM-1-N8 development kit}} 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: [env:vrelnextgen] 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 ===== Users connect to the WiFi Access Point provided by the laboratory module. This WiFi is shared for both users (NATed to the Internet) as well as for IoT devices and services. Figure {{ref>itsilesiavrelnextgeninfrastructure}} represents ItSilesia's VREL Next Gen IoT remote lab networking infrastructure and services. Details are described below.
{{ :en:iot-open:practical:hardware:itsilesia:vrel_mobile-networking.drawio.png?600 |VREL Mobile Lab network configuration}} VREL Next Gen IoT remote lab networking infrastructure and services
==== Networking Layer ==== The WiFi network for users and for IoT experimentation is available for all nodes: * SSID: VRELlab * PASS: VRELlab123 All clients get an IP address from the range of 192.168.0.100 to 192.168.0.200 Gateway is 192.168.0.1, and it is also the address of the IoT VREL management service for accessing devices: [[http://192.168.0.1]]. Note, even if available, the system WILL NOT work using the public IP address. In this laboratory module, for simplicity and portability of the solution, all clients connect to a single, private, NATed network, including IoT devices and clients (developers/students). All IoT integration services are also available via this network. ==== Application Layer Services ==== There are currently 2 application-layer services available, accessible for IoT nodes when connected to the VRELlab network: * MQTT broker: * IP addresses: 192.168.0.1; * Port: 1883 (TCP) * Security: plain text authentication * User: vrel * Pass: vrel1234 * CoAP server with two endpoints: * IP addresses: 192.168.0.1; * Port: 5683 (UDP) * Endpoints: * GET method for coap://192.168.0.1/ that brings you a secret code in the message's payload, * GET method for coap://192.168.0.1/hello that brings you a hello world welcome message in the payload. ===== ItSilesia 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. You can also observe results locally if you're near the venue. 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 avoiding rapid changes to display contents, as 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, with the main goal being to utilise networking and communication. Besides IoT, those scenarios can be utilised as part of the Embedded Systems Modules. * [[en:iot-open:practical:hardware:sut:esp32:emb5_1]] How do you use LCD (component 5)? * [[en:iot-open:practical:hardware:sut:esp32:emb6_1]] How do you use the ePaper display (component 6)? * [[en:iot-open:practical:hardware:sut:esp32:emb7_1]] How do you use an OLED display (component 7)? * [[en:iot-open:practical:hardware:sut:esp32:emb8_1]] How do you use the Smart LED stripe (component 8)? * [[en:iot-open:practical:hardware:sut:esp32:emb9A_1]] How do you use RGB LED (component 9A)? * [[en:iot-open:practical:hardware:sut:esp32:emb9B_1]] How do you use light and colour intensity sensors (component 9B)? * [[en:iot-open:practical:hardware:sut:esp32:emb10_1]] How do you control a standard miniature servo (component 10)? * [[en:iot-open:practical:hardware:sut:esp32:emb1A_1]] How do you control a FAN (component 1A)? * [[en:iot-open:practical:hardware:sut:esp32:emb1B_1]] How do you use the pressure and environmental integrated sensor (component 1B)? * [[en:iot-open:practical:hardware:sut:esp32:emb2_1]] How do you use a digital potentiometer (component 2)? * [[en:iot-open:practical:hardware:sut:esp32:emb3_1]] How do you use temperature and humidity integrated sensors (component 3)? * [[en:iot-open:practical:hardware:sut:esp32:emb4_1]] How do you use a temperature-only sensor (component 4)? ** Advanced techniques **\\ In the following scenarios, we will focus on advanced programming techniques, such as asynchronous programming and timers. * [[en:iot-open:practical:hardware:sut:esp32:adv1_1]] Using timers to handle the asynchronously periodic data display. ** IoT programming **\\ In the following scenarios, you will write programs interacting with other devices, services, and networks, which are pure IoT applications. * [[en:iot-open:practical:hardware:sut:esp32:iot_1]] Presenting MAC address of the WiFi interface * [[en:iot-open:practical:hardware:sut:esp32:iot_2]] Connecting to the WiFi Access Point and presenting IP * [[en:iot-open:practical:hardware:sut:esp32:iot_3]] Setting-up BT Beacon * [[en:iot-open:practical:hardware:sut:esp32:iot_4]] Scanning nearby BT devices * [[en:iot-open:practical:hardware:sut:esp32:iot_5]] Connecting to the MQTT broker and publishing data * [[en:iot-open:practical:hardware:sut:esp32:iot_6]] Connecting to the MQTT broker and subscribing to the data * [[en:iot-open:practical:hardware:sut:esp32:iot_7]] Publishing a CoAP service * [[en:iot-open:practical:hardware:sut:esp32:iot_8]] Connecting to the CoAP service ==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === 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 = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] To generate an array of bytes representing an image, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] === 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. 60x60 pixels) and convert it to the byte array with b/w settings.\\ Sample project favicon you can use is present in Figure {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:esp32:logo_60.jpg?60 |}} 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 {{ref>iotopenfavicon}} (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 #include #include //Fonts #include 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 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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, 128x128 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] To generate an array of bytes representing an image in 565 format, it is easiest to use an online tool, e.g.: * [[https://javl.github.io/image2cpp/]] 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 100x100 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 {{ref>iotopenfavicon}}:
{{ :en:iot-open:practical:hardware:sut:esp32:logo_60.jpg?60 |}} 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 {{ref>iotopenfavicon}} 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: {{ :en:iot-open:practical:hardware:sut:esp32:logo_60.zip | 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 #include #include #include //Fonts #include 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: 128x128 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]] === 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB9A: Use of RGB LEDs === This scenario presents how to handle the brightness control of the tri-coloured LEDs. One is observable via camera, as presented in the figure (component 9A), while another is hidden inside the black enclosure and lights a colour sensor (component 9B). Both LEDs are electrically bound and cannot be controlled independently. Those LEDs have 3 colour channels, controlled independently: R (Red), G (Green) and B (Blue). Mixing of those colours creates other ones, such as pink and violet. Each R G B channel can be controlled with a separate GPIO to switch it on or off or control brightness using a PWM signal, as presented in this tutorial. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the PWM hardware channels of the ESP32 chip. In this case, we do not use an external library; instead, we use built-in tools in the Arduino framework for ESP32 so that no additional libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:hardware2:actuators_light|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will light LEDs consecutively with R, G, and B. Use 50% of the maximum brightness. Use a PWM signal to control each GPIO for R, G and B, each colour separately to let you easily observe it. == Start == Assuming you will use 8-bit PWM resolution, the minimum value is 0, and the max (full brightness) is 255. Note that full brightness may be too bright for the observation camera, so consider using a range between 0 and 60 (eventually up to 100). == Steps == To use PWM in ESP32, it is best to use built-in ''ledc*'' functions [[https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/ledc.html|LEDC documentation on the ESP32 manufacturer's page]]. The ''leds'' use timers and have channels attached to the timer. We will use 1 channel per colour (R, G and B, so 3 in total). A PWM frequency is controlled with the timer to be shared for all 3 R, G and B channels. Channels control the PWM duty cycle. = Step 1 = Define some parameters, including channel numbers, PWM resolution (here 8-bit) and PWM frequency (5000Hz): #define RGBLED_B_PIN 26 #define RGBLED_G_PIN 21 #define RGBLED_R_PIN 33 #define PWM1_Ch 5 #define PWM2_Ch 6 #define PWM3_Ch 7 #define PWM_Res 8 #define PWM_Freq 5000 GPIO pins controlling LEDS are 33 (Red), 21 (Green) and 26 (Blue), respectively. = Step 2 = Initialise 3 channels for PWM and make them dark: ledcSetup(PWM1_Ch, PWM_Freq, PWM_Res); ledcSetup(PWM2_Ch, PWM_Freq, PWM_Res); ledcSetup(PWM3_Ch, PWM_Freq, PWM_Res); ledcAttachPin(RGBLED_R_PIN, PWM1_Ch); ledcAttachPin(RGBLED_G_PIN, PWM2_Ch); ledcAttachPin(RGBLED_B_PIN, PWM3_Ch); delay(100); ledcWrite(PWM1_Ch,0); ledcWrite(PWM2_Ch,0); ledcWrite(PWM3_Ch,0); To control the LED (via PWM), use ''ledcWrite(PWM_Channel, duty_cycle_value);''. = Step 3 = Write a loop for each colour (R, G, then B) to light the colour from dark to max value (60 or 100, give it a test). Full duty cycle (255) will be too bright for the remote access video camera to handle it. Use some reasonable range such as 0..60 or 0..100 is strongly advised. Mind to compose code to increase and decrease each colour. A hint is below (PWM Channel 3 so that controls Blue): // Increase brightness for (int dutyCycle = 0; dutyCycle <= 100; dutyCycle++) { // Gradually increase duty cycle for Red LED ledcWrite(PWM3_Ch, dutyCycle); delay(20); // Delay for smooth transition } delay(100); // Decrease brightness for (int dutyCycle = 100; dutyCycle >= 0; dutyCycle--) { // Gradually decrease duty cycle for Red LED ledcWrite(PWM3_Ch, dutyCycle); delay(20); // Delay for smooth transition } There is a number of handy functions in the ''ledc'' library, including (among others): * ''analogWrite(pin,value);'' to keep compatibility with genuine Arduino, * ''ledcFade(pin, start_dutycycle, end_dutycycle, fade_time_ms);'' that you can use instead of the loop above, * ''ledcDetach(pin);'' to detach a pin from the channel (and thus release the PWM channel), * ''ledcWriteNote(pin, note, octave);'' where ''note'' is one of the values: NOTE_C, NOTE_Cs, ... (and so on, music notes, up to NOTE_B) - it is useful when PWM controls a speaker, to generate a perfect musical note, but we do not use speakers here, sorry. == Result validation == You should be able to observe the pulsing colours of the RGB LED, increasing and decreasing brightness linearly. === FAQ === **What if I bind a PWM channel to more than one GPIO?**: As you control the duty cycle writing to the channel rather than the GPIO, you will control those GPIOs simultaneously (in parallel) with the same signal. That can be handy to control parallelly separate devices that should behave the same way, i.e. servos. \\ **What is the maximum number of channels?**: the MCU we use here is ESP32-S3, so it has 8 PWM channels. You can use timers and software implementation of the PWM signal if you need more, but that is a bit tricky and may not be as precise as hardware PWM implementation. \\ **What is the maximum bit resolution for PWM?**: it is between 1 and 14 bits in this particular MCU. \\ **What PWM frequency should I use?**: there is no straightforward answer to this question: assuming you observe LED remotely with a camera, even 50Hz would be enough. But it would give a severe flickering experience to the live user, on the other hand. In the example above, we propose 5kHz, which this MCU can easily handle. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB9B: Reading colour sensor === A colour sensor (TCS 34725) can detect the brightness and colour of the light emitted. It works with the I2C; in our laboratory, each sensor has a fixed 0x29 address in the I2C bus. The sensor is in the black enclosure, ensuring no ambient light impacts readings. The only light source is an RGB LED, controlled as described in the scenario [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. This is another LED connected parallel to the one you can observe in the camera. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. To simplify TCS sensor use, we will use a dedicated library: lib_deps = adafruit/Adafruit TCS34725@^1.4.2 Also, remember to add the LCD handling library, as present in the EMB9 scenario, unless you decide to use another output device. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[en:iot-open:hardware2:sensors_optical|]] * [[https://cdn-shop.adafruit.com/datasheets/TCS34725.pdf|TCS sensor documentation]] === Hands-on Lab Scenario === == Task to be implemented == Create a solution where you control an RGB LED and read its colour and brightness using a TCS sensor. Present results on the LCD, e.g. in the form of "Colour: " where "value" refers to the colour intensity. You should change LED brightness at least 10 times (10 different brightness) for each colour while continuously reading the TCS sensor and presenting data in the LCD. Experiment with mixing colours and check either the sensor can distinguish brightness separately, when i.e. both RGD LED's colours are on: Red+Green or Red+Blue (or any other combination you wish). There are at least two ways to handle this task: * simple: a dummy loop where you set RGB LED values, read from TCS and display on LCD, * advanced: where the data is read and presented on the LCD in one or two asynchronous routines run by timers. == Start == Check if you can see the LCD and RGB LED in the camera view. Remember not to use the full brightness of the RGB LED because you won't be able to observe other elements due to the camera's low dynamics. Assuming you use 8-bit PWM to control LED, we suggest a range between 0 and 60 (eventually 0 and 100), but never up to 255. == Steps == In the steps below, we present only the part for reading from the TCS sensor. Control of RGB LED with PWM and handling LCD is presented in the other lab scenarios that you need to follow accordingly and merge with the code below. = Step 1 = Included necessary libraries: #include #include "Adafruit_TCS34725.h" = Step 2 = Declare and instantiate the TCS controller class and related variables for readings: #define SCL 4 #define SDA 5 static Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_300MS, TCS34725_GAIN_1X); uint16_t r, g, b, c, colorTemp, lux; static bool isTCSOk = false; A word of explanation regarding the parameters: * ''TCS34725_INTEGRATIONTIME_300MS'' is the time in ms while the TCS sensor is exposed for the light to grab readings, * ''TCS34725_GAIN_1X'' is the sensor's gain - it helps reading in low light, but that is not true in our lab as our RGB LED light sensor is very bright, even in low duty cycle. Refer to the Q&A section for the ranges. = Step 3 = Initialise the sensor (in ''void Setup()'') and check it is OK: Wire.begin(SDA,SCL); delay(100); ... isTCSOk = tcs.begin(); You communicate with the TCS sensor via the I2C interface. In standard and typical configurations, there is no need to instantiate the ''Wire'' I2C communication stack explicitly, nor provide a default I2C address of the TCS sensor (0x29), so ''begin'' is parameterless. Other overloaded functions for the ''begin'' let you provide all technical parameters. You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Besides reading raw values for channels R, G, B and C, the ''tcs'' controller class provides additional functions to calculate colour temperature (in K) and colour intensity in Luxes. To read, use the following code: if(isTCSOk) { tcs.getRawData(&r, &g, &b, &c); colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c); lux = tcs.calculateLux(r, g, b); } R, G, and B reflect filtered values for Red, Green and Blue, respectively, while C (clear) is a non-filtered read that reflects brightness. Please be aware that there is no straightforward relation between R, G, B and C channels. == Result validation == Driving R, G and B channels for RGB LED that lights the sensors, you should be able to observe changes in the readings, accordingly. === FAQ === **What is the range of the values for the //integration time// of the TCS sensor?**: #define TCS34725_INTEGRATIONTIME_2_4MS \ (0xFF) /**< 2.4ms - 1 cycle - Max Count: 1024 */ #define TCS34725_INTEGRATIONTIME_24MS \ (0xF6) /**< 24.0ms - 10 cycles - Max Count: 10240 */ #define TCS34725_INTEGRATIONTIME_50MS \ (0xEB) /**< 50.4ms - 21 cycles - Max Count: 21504 */ #define TCS34725_INTEGRATIONTIME_60MS \ (0xE7) /**< 60.0ms - 25 cycles - Max Count: 25700 */ #define TCS34725_INTEGRATIONTIME_101MS \ (0xD6) /**< 100.8ms - 42 cycles - Max Count: 43008 */ #define TCS34725_INTEGRATIONTIME_120MS \ (0xCE) /**< 120.0ms - 50 cycles - Max Count: 51200 */ #define TCS34725_INTEGRATIONTIME_154MS \ (0xC0) /**< 153.6ms - 64 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_180MS \ (0xB5) /**< 180.0ms - 75 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_199MS \ (0xAD) /**< 199.2ms - 83 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_240MS \ (0x9C) /**< 240.0ms - 100 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_300MS \ (0x83) /**< 300.0ms - 125 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_360MS \ (0x6A) /**< 360.0ms - 150 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_401MS \ (0x59) /**< 400.8ms - 167 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_420MS \ (0x51) /**< 420.0ms - 175 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_480MS \ (0x38) /**< 480.0ms - 200 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_499MS \ (0x30) /**< 499.2ms - 208 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_540MS \ (0x1F) /**< 540.0ms - 225 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_600MS \ (0x06) /**< 600.0ms - 250 cycles - Max Count: 65535 */ #define TCS34725_INTEGRATIONTIME_614MS \ (0x00) /**< 614.4ms - 256 cycles - Max Count: 65535 */ **What is the range of the values for the //gain// of the TCS sensor?**: typedef enum { TCS34725_GAIN_1X = 0x00, /* No gain */ TCS34725_GAIN_4X = 0x01, /* 4x gain */ TCS34725_GAIN_16X = 0x02, /* 16x gain */ TCS34725_GAIN_60X = 0x03 /* 60x gain */ } tcs34725Gain_t; **C channel reading is 0, and R, G or B readings are unreasonable.** It is possibly because of overdriving the sensor with a light source (too bright). Consider lowering the RGB LED's intensity or changing the sensor's integration time and gain (shorter time, lower gain). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] * [[en:iot-open:hardware2:actuators_motors|]] === 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 #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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB1A: Controlling a FAN with PWM === This scenario presents how to handle the rotation speed of the fan. It is done using a PWM signal, as presented in this tutorial. The fan pumps the air into the yellow pressure chamber with a pressure sensor inside. Because it is pretty hard to monitor rotation speed with the camera, this scenario should be run along with scenario following scenario [[en:iot-open:practical:hardware:sut:esp32:emb1b_1]] that will indirectly measure rotation speed using pressure changes. === Prerequisites === A good understanding of the PWM signal and duty cycle is necessary. We also use built-in timers to control the ESP32 chip's PWM hardware channels. In this case, we do not use an external library; instead, we use built-in tools in the Arduino framework for ESP32, so no additional libraries will be included in the project. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:pwm|]] === Hands-on Lab Scenario === == Task to be implemented == Implement a program that will spin the fan using a PWM signal. Execute your experiment once, e.g. in the ''setup()'' function, but not infinitely, in the ''loop()''. We appreciate letting you keep your fan off when finishing your experiments. They are very noisy, and our servers love silence! == Start == Assuming you will use 8-bit PWM resolution, the minimum value is 0, and the max (full speed) is 255. == Steps == To use PWM in ESP32, it is best to use built-in ''ledc*'' functions [[https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/ledc.html|LEDC documentation on the ESP32 manufacturer's page]]. The "leds" use timers and have channels attached to the timer. We will use one channel numbered 0. The channel controls the PWM duty cycle. Do not be misled by the "leds" functions: they generate a PWM signal, and it is meaningless whether you control an LED, bulb, or fan (as here). = Step 1 = Define some parameters, including channel numbers, PWM resolution (here 8-bit) and PWM frequency (1000Hz): #define FAN_PIN 35 #define FAN_PWM_Ch 0 #define FAN_PWM_FREQ 1000 #define FAN_PWM_RESOLUTION 8 GPIO pins controlling the fan is 35. = Step 2 = Initialise fan PWM channel and make it to stop (duty cycle 0): ledcSetup(FAN_PWM_Ch, FAN_PWM_FREQ, FAN_PWM_RESOLUTION); //Initialise channel ledcAttachPin(FAN_PIN, FAN_PWM_Ch); //Bind it to the PWM delay(100); ledcWrite(FAN_PWM_Ch,0); //Write to CHANNEL, not to PIN! = Step 3 = To control the fan rotation speed (via PWM), use ''ledcWrite(FAN_PWM_Ch, duty_cycle_value);''.\\ Note you write to **channel**, not to the **pin**!\\ A common source code mistake causes the fan not to operate correctly. A ''ledcWrite'' with a ''duty_cycle_value'' equal to 0 causes the fan to stop.\\ The maximum ''duty_cycle_value'' is determined by the ''FAN_PWM_RESOLUTION'', which is 8-bit in our case, so maximum is 255. In that case, the fan operates at full speed. There is a number of handy functions in the ''ledc'' library, including (among others): * ''analogWrite(pin,value);'' to keep compatibility with genuine Arduino, * ''ledcFade(pin, start_dutycycle, end_dutycycle, fade_time_ms);'' that you can use instead of the loop to slow down rotation gently, * ''ledcDetach(pin);'' to detach a pin from the channel (and thus release the PWM channel), * ''ledcWriteNote(pin, note, octave);'' where ''note'' is one of the values: NOTE_C, NOTE_Cs, ... (and so on, music notes, up to NOTE_B) - it is useful when PWM controls a speaker, to generate a perfect musical note, but we do not use speakers here, sorry. == Result validation == You can observe rotation change by measuring air pressure in the chamber only. To distinguish whether the fan is stopped or rotating, you can observe it via the video stream. === FAQ === **The fan rotates itself. Why?**: It is connected via the MOS FET transistor that tends to saturate when the GPIO (35) is not controlling it. So, to ensure the fan is stopped, bind a PWM channel to it and force the duty cycle set to 0. **The fan is not rotating?**: Besides possible configuration and programming errors, it is essential to understand that setting duty cycle, e.g. to 1, won't start spinning: a minimum value (a threshold) causes rotation. Moreover, the relation between rotation speed, air pressure and duty cycle controlling the fan is not linear. You may also be a victim of the common coding mistake of using a GPIO number instead of the channel number (first parameter) in the ''ledcWrite'' function: use the PWM channel number! \\ **What is the maximum number of channels?**: the MCU we use here is ESP32-S3, so it has 8 PWM channels. You can use timers and software implementation of the PWM signal if you need more, but that is a bit tricky and may not be as precise as hardware PWM implementation. \\ **What is the maximum bit resolution for PWM?**: In this particular MCU, it is between 1 and 14 bits. \\ **What PWM frequency should I use?**: there is no straightforward answer to this question, but setting too low a frequency will cause the inability to control the fan: 1000Hz (1kHz) seems reasonable and has been tested with this configuration. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== EMB1B: Reading environmental data with a Bosch integrated sensor === We will read environmental data using a BME 280 sensor in this scenario. It is one of the most popular sensors in weather stations. It integrates a single chip's digital thermometer, hygrometer (air humidity), and air pressure meter. This sensor is located inside the yellow pressure chamber, under the fan in our laboratory nodes.\\ The sensor communicates with the microcontroller using I2C. In all our laboratory nodes, it uses the I2C bus on GPIOs 5 (SDA) and 4 (SCL) and is visible under the I2C address 0x76.\\ This scenario can be run stand-alone to read weather data in the laboratory nodes' room. Still, it is also complementary to the scenario EMB1A [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]] and may enable you to monitor the results of the fan operation that should induce changes in the air pressure. === Prerequisites === The static temperature, humidity and air pressure values can be read using a dedicated library: lib_deps = adafruit/Adafruit BME280 Library@^2.2.2 To observe air pressure changes over a short time, it is necessary to implement fan PWM control as described in the [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]].\\ 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. To implement monitoring of the air pressure changes, understanding how to control a fan with PWM is necessary: * [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]]. Technical documentation for the BME 280 sensor is available here: * [[https://www.mouser.com/datasheet/2/783/BST-BME280-DS002-1509607.pdf|BME 280]] Besides BME 280, there is another sensor in the Bosch family: BMP 280. The former one does not measure humidity, just temperature and air pressure. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] === Hands-on Lab Scenario === In this scenario, we only focus on reading the sensor. 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, air pressure, and humidity on any display (e.g. LCD). Remember to add units (C, %Rh, hPa). == Start == For static measurements, ensure the fan is stopped. Note that the fan tends to spin up on itself (explained in EMB1A) when the GPIO controlling the fan is not configured, so it is better to ensure it is set to output and low (0) to keep the fan stopped. Refer to the [[en:iot-open:practical:hardware:sut:esp32:emb1a_1|]] for details on controlling the fan. == 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, and also with a scenario EMB1A presenting no instructions on controlling the fan that can change the air pressure in the yellow pressure chamber. = Step 1 = Include a BME 280 control library: #include = Step 2 = Declare BME's address, sensor controller class and variables to store readings: #define SCL 4 #define SDA 5 static const int BME280_addr = 0x76; //I2C address static bool isBMEOk = false; static float temperature; static float pressure; static float humidity; static Adafruit_BME280 bme280; //controller class = Step 3 = Initialise the I2C bus and the controller class: Wire.begin(SDA,SCL); delay(100); ... isBMEOk = bme280.begin(BME280_addr); If ''begin'' returns ''false'', then initialisation failed. This may be due to an invalid I2C address provided as a parameter of the ''begin'' function, a broken sensor, or broken connections between the MCU and the sensor. You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Read environmental data (one at a time): temperature = bme280.readTemperature(); pressure = bme280.readPressure() / 100.0F; humidity = bme280.readHumidity(); The temperature is Celsius, air pressure is in Pascals (so we divide it by float 100 to obtain the hPa reading), and relative air humidity is in % (frequently referenced as %Rh). Note that the controller class has an exciting function of trading Altitude based on the sea-level pressure (needed to have a correct reading: float altitude = bme280.readAltitude(1013.00F); You need to know the sea level pressure (a parameter, here, 1013hPa). It uses ''readPressure()'' internally and returns the altitude level. Note that due to the non-linear characteristics of the air pressure drop with increasing altitude, it does not work correctly at high altitudes. The library also has a mathematical calculation function that returns the current sea level pressure if only altitude and local air pressure are known. It does not read the sensor itself, however: float seaLevelPressure = bme280.seaLevelForAltitude(230, bme280.readPressure()); In the example above, the first parameter is the altitude (230m). == Result validation == The observable temperature is usually within the range of 19-24C, with humidity about 40-70%, strongly depending on the weather. On rainy days, it can even go higher. Air pressure depends on the current weather (assuming the fan is off) and is usually low to 890hPa (when low-pressure area) and even up to 1040hPa (when high-pressure area comes, usually during the summer). Spinning the fan may easily change air pressure by at least 1-2Pa up relative to the static pressure, depending on the fan's rotation speed. === FAQ === **I've got NaN (Not a Number) readings. What to do?**: Check if GPIO is OK (should be 47), check if you initialised the controller class and most of all, give the sensor some recovery time (at least 250ms) between consecutive readings. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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) {{ref>figuredigipot}}. 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.
{{ :en:iot-open:practical:hardware:sut:esp32:screenshot_from_2024-03-21_22-03-39.png?600 | Digital potentiometer DS1803 application in VREL Next Gen nodes }} 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:twi|]] * [[https://www.analog.com/en/products/ds1803.html| DS1803 documentation (external link)]] === 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) uses 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 128x128 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); You need to initialise the I2C bus only once. If you're using other I2C devices in parallel, do not call ''Wire.begin(...)'' multiple times. = Step 4 = Read the loopback characteristics of the digital potentiometer to the 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 [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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 {{ref>figuredigipot}} 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:emb8_1]] * [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === 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 #include = 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. 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: * [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[en:iot-open:embeddedcommunicationprotocols2:1wire|]] === 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 [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]). 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 #include 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 = 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 [[en:iot-open:practical:hardware:sut:esp32:adv1_1|]]. == 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 === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== 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: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]. 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 === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * [[https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/timer.html|ESP32 Hardware Timers]] === 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 [[en:iot-open:practical:hardware:sut:esp32:emb9a_1|]]. == 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 #include = Step 2 = Define LCD configuration pins (see details and explanation in the scenario: [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]]): #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 can circumvent this limitation by using smart handlers that have, for example, an internal counter and execute every Nth cycle internally. This helps simulate multiple timers in a software-based manner. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT1: Reading MAC address of the WiFi === Each network IP card is supposed to have a unique MAC address. ESP32 chip has built-in MAC. MAC can be used to identify devices, but note that it is not a "strong" ID: it can be programmatically changed and easily discovered. In the following scenario, we only present how to read the MAC address. A part regarding displaying on the selected screen is up to the developer. You can refer to the appropriate scenario, as listed below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. A WiFi library is already included in the Arduino framework for ESP32, so there is no need to add it to the ''platformio.ini'' explicitly. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Present a MAC address on the selected display. The steps below present only the reading part, not a display. Handling a display is presented in the EMBx scenarios, as listed above. == 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 WiFi management library in your source code: #include The WiFi library automatically initialises a singleton class ''WiFi'' that you can use to set up working mode, read MAC, and perform many other operations. = Step 2 = Reading the MAC as a ''String'' is as easy as simply calling: WiFi.macAddress(); == Result validation == Using another node should change the MAC read. Book another device and discover its MAC. === FAQ === **Can I change MAC?**: Actually, yes, you can. It is not advised, however, because you may accidentally generate an overlapping address that will collide with another device in the same network. You must first explicitly configure the ESP32 chip to work as an AP (Access Point, Server) or STA (WiFi Client) to do it. Sample stub code (for STA) may look as follows: #include #include uint8_t newMAC[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE}; //Array of bytes with new MAC void setup() { WiFi.mode(WIFI_STA); esp_wifi_set_mac(WIFI_IF_STA, &newMAC[0]); } === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT2: Reading IP address of the WiFi === On the networking level, IP devices are identified by MAC. In the case of our laboratory and network, you will obtain the IP address from the DHCP server integrated with the WiFi access point. To connect to the WiFi network, one needs to use credentials that are present in the general laboratory description, available here: In this scenario, you will learn how to set up your device in STA (client to the AP) mode, connect to the AP, and read the obtained IP address. This scenario does not contain a UI part; it is up to the developer to implement it using one of the scenarios presented below. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]]. A WiFi library is already included in the Arduino framework for ESP32, so there is no need to add it to the ''platformio.ini'' explicitly. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === == Task to be implemented == Connect to the "internal IoT" WiFI access point. Present the obtained IP address on the selected display. Handle critical situations such as no access and present an appropriate message on the screen (e.g., an error message). The steps below present only the reading part, not a display. Handling a display is presented in the EMBx scenarios, as listed above. == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. == Steps == = Step 1 = Include the WiFi management library in your source code: #include The WiFi library automatically initialises a singleton class, ''WiFi,'' which you can use to set up working mode, read MAC and IP, and perform many other operations. = Step 2 = Declare some constants, including AP SSID and passphrase and a variable to store IP: const char* ssid = "REPLACE_WITH_CORRECT_SSID"; const char* pass = "REPLACE_WITH_CORRENT_PASSPHRASE"; IPAddress localIP; Both ''ssid'' and ''pass'' are to be obtained from the hardware reference, available here: [[en:iot-open:practical:hardware:sut:esp32|]]. = Step 3 = Set your device in the STA mode and connect to the WiFi AP: WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { //Drop some info on display about connecting delay(1000); } The ''WiFi.begin(...)'' returns the following statuses (value, enumerator: meaning): * 0, ''WL_IDLE_STATUS:'' temporary status assigned when WiFi.begin() is called * 1, ''WL_NO_SSID_AVAIL:'' when no SSID are available * 2, ''WL_SCAN_COMPLETED:'' scan networks is completed * 3, ''WL_CONNECTED:'' when connected to a WiFi network * 4, ''WL_CONNECT_FAILED:'' when the connection fails for all the attempts * 5, ''WL_CONNECTION_LOST:'' when the connection is lost * 6, ''WL_DISCONNECTED:'' when disconnected from a network = Step 4 = Reading the IP as a ''String'' is as easy as simply calling: localIP = WiFi.localIP(); The ''IPAddress'' class contains the ''toString()'' method which formats the IP and returns in standard, dot-separated, four-octet text format. = Step 5 = Explicitly disconnect from the WiFi AP to free resources: WiFi.disconnect(); Some useful WiFi functions are listed below: * ''WiFi.reconnect()'' - reconnects the connection that was dropped, note it uses ''ssid'' and ''pass'' previously specified in the ''WiFi.begin(...)'', here you do not provide one. * ''WiFi.RSSI()'' - once connected, you can get signal strength as ''int8_t'' integer. * ''WiFi.setHostname(const char * hostname)'' - set host name for the ESP32 chip. Remember to use this function before connecting to the AP. == Result validation == You should be able to connect to the WiFi and present the dynamically assigned IP address by the DHCP server. Note that due to the dynamic nature of the lab, IP addresses can change both during connection (on lease refresh) or between consecutive connects. === FAQ === **I set a hostname, but it does appear on the router level.**: There are many possible reasons; one is an active registration in the router (AP) that keeps an old IP address, so you need to wait until it expires; another reason is you have changed the hostname when you were connected to the AP.\\ **Can I use a manually configured IP?**: Actually, you can, but we strongly discourage it. You may accidentally overlap this IP address with some other device, router, or infrastructure, blocking its operation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT3: Connecting to the MQTT and publishing data ==== In the following scenario, you will learn how to connect to the MQTT broker and publish a message. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]. There are many implementations of the MQTT protocol, but we will use the following library: lib_deps = knolleary/PubSubClient@^2.8 === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]] * [[en:iot-open:networking2:applicationnetworkprotocols|]]. === Hands-on Lab Scenario === Note - this scenario can be used in pair with [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]] to build a publish-subscribe solution using two devices (sender and receiver). You need to book two devices then and develop them in parallel. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then publish an MQTT message of your choice. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ == Steps == = Step 1 = Once the device is booked, check if your display of choice is visible in the camera's FOV.\\ Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server. = Step 2 = Include the MQTT implementation library header in your code: #include = Step 3 = Declare necessary addresses, constants, etc.: IPAddress mqttServer(127,0,0,1); //change it to the MQTT broker IP #define mqtt_user "mqtt user" #define mqtt_password "mqtt password" #define mqtt_client_id "some_unique_client_id" #define mqtt_topic "/sample/topic/comes/here/change/it/please" #define mqtt_payload "sample payload" Refer to the technical documentation (nodes) or the supervisor's guidance if working in the interactive mode to obtain the ''mqttServer'' IP address, the ''mqtt_user'' and ''mqtt_password''.\\ **Remember to choose some unique client ID and topic!** = Step 4 = Declare WiFi communication client and MQTT communication client: WiFiClient espClient; PubSubClient client(espClient); Note that your clients are not yet online! = Step 5 = Set MQTT client's configuration (proposed in the ''void setup()'' function: ... client.setServer(mqttServer,1883); //default port is 1883, change if needed ... = Step 6 = Finally, connect the MQTT client to the MQTT broker and publish a message (sample code, adjust to your case): while (!client.connected()) { if (client.connect(mqtt_client_id,mqtt_user,mqtt_password)) { // Drop some info on the display that the MQTT broker is connected client.publish(mqtt_topic,mqtt_payload); } else { int status = client.state(); //read error code //present it on the display to trace/troubleshoot } } In the case, the client does not connect to the MQTT broker, the ''client.state();'' returns an int that can be interpreted as below: // Possible values for client.state() #define MQTT_CONNECTION_TIMEOUT -4 #define MQTT_CONNECTION_LOST -3 #define MQTT_CONNECT_FAILED -2 #define MQTT_DISCONNECTED -1 #define MQTT_CONNECTED 0 #define MQTT_CONNECT_BAD_PROTOCOL 1 #define MQTT_CONNECT_BAD_CLIENT_ID 2 #define MQTT_CONNECT_UNAVAILABLE 3 #define MQTT_CONNECT_BAD_CREDENTIALS 4 #define MQTT_CONNECT_UNAUTHORIZED 5 In many code samples, including those provided along with this MQTT client library, there is a ''client.loop();'' function executed in the main ''void loop()'' body. While it is obligatory in the case of the use of asynchronous sending and receiving of the MQTT messages, in the simple case as above, it can be abandoned because ''client.publish(topic,payload);'' makes a blocking call and ensures sending of the message to the MQTT broker. == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then publish a message (once or periodically). Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a subscriber (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to observe messages that you publish. === FAQ === **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **How do I observe messages that I send?**: Use a software client, such as [[http://mqtt-explorer.com/| MQTT Explorer]], if you're able to access the "internal IoT" network (you're in the range of the network). If you're remote, the only way is to book another device and implement a client subscribing to your message, as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_4|]]. Our MQTT broker is also visible in the campus network on the wired interfaces so that you can access it, e.g. via EduVPN or from the laboratory computers. Refer to the supervisor for IP and credentials.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IOT4: Connecting to the MQTT and subscribing to the messages ==== In the following scenario, you will learn how to connect to the MQTT broker and subscribe to the message. === Prerequisites === To implement this scenario, it is necessary to get familiar with at least one of the following scenarios first: * [[en:iot-open:practical:hardware:sut:esp32:emb5_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb6_1|]], * [[en:iot-open:practical:hardware:sut:esp32:emb7_1|]], and obligatory: * [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]. There are many implementations of the MQTT protocol, but we will use the following library: lib_deps = knolleary/PubSubClient@^2.8 === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]], * [[en:iot-open:hardware2:esp32|]], * [[en:iot-open:practical:hardware:sut:esp32|]], * [[en:iot-open:iotprogramming2:espressif_networking|]]. === Hands-on Lab Scenario === Note—this scenario can be used in tandem with [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]] to build a publish-subscribe solution using two devices (two laboratory nodes: publisher and subscriber). You need to book two devices then and develop them in parallel. Watch the result validation section for more options. == Task to be implemented == Connect to the "internal IoT" WiFI access point as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]]—present connection status on display. Once connected to the networking layer (WiFi), connect the MQTT client to the MQTT broker and present the connection status on the display, then subscribe to the MQTT message of your choice, identified by the topic (may include wildcards). Present the message payload on the display. MQTT clients are identified by their name, so use a unique one, e.g., the end of the IP address assigned, your unique name, etc. It is essential because if you accidentally use someone else's name, then you will mess with messages, and your MQTT client will be instantly disconnected when another one with the same name connects! == Start == Check if you can clearly see a full display (of your choice) in your video stream. Book a device and create a dummy Arduino file with ''void setup()...'' and ''void loop()...''. \\ Implement a connection to the "internal IoT" network as a client. Refer to the supervisor or the technical documentation on credentials (SSID, passphrase). We do not provide the exact code on how to connect to the WiFi as it is a part of [[[en:iot-open:practical:hardware:sut:esp32:iot_2|]] scenario. \\ == Steps == = Step 1 = Once the device is booked, check if your display of choice is visible in the camera's FOV.\\ Refer to the hardware documentation and ensure an understanding of the network infrastructure you're interfacing with.\\ Implement the code to display on the selected device.\\ Connect to the WiFi in the STA mode (as a client) and ensure the connection is OK and you got an IP from the DHCP server.\\ Note - as you're consuming messages here, you either need to have implemented and running publisher as a separate device (as described in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]) or to connect to the MQTT broker using some software, as described in the results validation section below. = Step 2 = Include the MQTT implementation library header in your code: #include = Step 3 = Declare necessary addresses, constants, etc.: IPAddress mqttServer(127,0,0,1); //change it to the MQTT broker IP #define mqtt_user "mqtt user" #define mqtt_password "mqtt password" #define mqtt_client_id "some_unique_client_id" #define mqtt_topic "/sample/topic/comes/here/change/it/please" Refer to the technical documentation (nodes) or the supervisor's guidance if working in the interactive mode to obtain the ''mqttServer'' IP address, the ''mqtt_user'' and ''mqtt_password''.\\ **Remember to choose some unique client ID and topic corresponding to the one that is being published to the MQTT broker!** You can use wildcards when subscribing to the broker. Still, please note that using a single star to subscribe to ALL MQTT messages is not a good option as there are many technical topics published by the broker internally, so the number of messages incoming every second to your solution can quickly overwhelm your device's resources. = Step 4 = Declare WiFi communication client and MQTT communication client: WiFiClient espClient; PubSubClient client(espClient); Note that your clients are not yet online! = Step 5 = Declare an asynchronous handler function that will be called every time the client receives the MQTT message: void callback(char* topic, byte* payload, unsigned int length) { //prepare a code to display the message (payload) to the display } Note that the payload is a byte array, so you may need to copy it to process it outside the callback function (e.g. using ''memcpy'').\\ Also, note that you may distinguish which message you're handling if you subscribe using wildcards: the ''topic'' parameter provided the exact topic as it was published to the MQTT broker by the publisher. = Step 6 = Set MQTT client's configuration (proposed in the ''void setup()'' function: ... client.setServer(mqttServer,1883); //default port is 1883, change if needed client.setCallback(callback); //a callback function to handle incoming messages //as declared in Step 5 ... = Step 7 = Finally, connect the MQTT client to the MQTT broker and publish a message (sample code, adjust to your case): while (!client.connected()) { if (client.connect(mqtt_client_id,mqtt_user,mqtt_password)) { // Drop some info on the display that the MQTT broker is connected client.subscribe(mqtt_topic); } else { int status = client.state(); //read error code //present it on the display to trace/troubleshoot } } In the case, the client does not connect to the MQTT broker, the ''client.state();'' returns an int that can be interpreted as below: // Possible values for client.state() #define MQTT_CONNECTION_TIMEOUT -4 #define MQTT_CONNECTION_LOST -3 #define MQTT_CONNECT_FAILED -2 #define MQTT_DISCONNECTED -1 #define MQTT_CONNECTED 0 #define MQTT_CONNECT_BAD_PROTOCOL 1 #define MQTT_CONNECT_BAD_CLIENT_ID 2 #define MQTT_CONNECT_UNAVAILABLE 3 #define MQTT_CONNECT_BAD_CREDENTIALS 4 #define MQTT_CONNECT_UNAUTHORIZED 5 = Step 8 = Run client handling routine in the loop to receive messages: void loop() { if (!client.connected()) { //reconnect the client (and eventually the WiFi if gone) } client.loop(); //process incoming MQTT messages } == Result validation == You should be able to connect to the WiFi and MQTT broker (verified by the status present on the selected display) and then subscribe to the message (identified by topic). Depending on whether you're fully remote or able to access our networks with an additional device, you need to implement a publisher on another laboratory node (as present in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]) or use MQTT Explorer (or any other application capable of connecting to our MQTT Broker) to send messages that you subscribe to. === FAQ === **My MQTT client disconnects randomly**: The most common reason is you're using a non-unique MQTT client name. Please change it to some other (even random generated) and give it another try.\\ **How do I send messages to which I am subscribed?**: Use a software client, such as [[http://mqtt-explorer.com/| MQTT Explorer]], if you're able to access the "internal IoT" network (you're in the range of the network). If you're remote, the only way is to book another device and implement a client publishing a message with the appropriate topic, as presented in the scenario [[[en:iot-open:practical:hardware:sut:esp32:iot_3|]]. Our MQTT broker is also visible in the campus network on the wired interfaces so that you can access it, e.g. via EduVPN or from the laboratory computers. Refer to the supervisor for IP and credentials.\\ **Do I need to authorise to publish and subscribe?**: Yes, you do. The supervisor provides the user and password on demand, also presented in the Node's technical documentation. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT7: BLE Beacon === This scenario presents how to create the Bluetooth Low Energy beacon device which periodically broadcasts a small amount of information, and the client device which can receive packets sent by the beacon. Beacons are usually used for sending useful information (eg. the web address of the owner, a link to the page with tourist information). In some cases, they simply send the identification number recognised by a dedicated mobile application allowing the users to localise themselves. BLE Beacons just broadcast the information, they are not capable of receiving any information from the users nearby. To do this scenario two stations are needed, one for the beacon and one for the client device. === Prerequisites === We will use the advertising process in this scenario so an understanding of the principles of the Bluetooth Low Energy protocol is necessary. It's good to understand the Eddystone data format used to encode URLs in advertising frames. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] * Interesting article on Beacons with a description of Eddystone, and iBeacon packets((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)) === Hands-on Lab Scenario === This scenario is intended to be implemented using two laboratory nodes. One node will work as the beacon device, while the second will receive the beacon's packets and display information on the LCD. == Task to be implemented == **Task 1** Implement a program that operates as the BLE beacon which advertises itself with the link to the website. This can be done with the Eddystone beacon format. **Task 2** Implement the receiver of advertising frames broadcasted by the beacon device capable of displaying information on the LCD screen. == Start == We need to implement both parts of the software to be able to observe the results (steps 1-4). **The Task 1** we implement in steps 1 and 2. **The Task 2** we implement in steps 3 and 4. At the end, we return to **Task 1** with step 5. == Steps == We can go through the lab in a few steps. In the beginning, we will write a simple program which advertises the device with the default parameters set in the BLE library. As the payload of the advertising frame, we will use the Eddystone format to send the chosen URL address. = Step 1 = Let's begin with including Arduino and BLE libraries. #include "Arduino.h" #include "BLEDevice.h" #include "BLEAdvertising.h" We need one variable only which holds the pointer to the advertising class. BLEAdvertising *pAdvertising; The setup function creates and initialises the BLE device instance with a chosen name. If we add our text in the BLEDevice::init() function we will see this text as the device name in the advertising frame. It will also appear as the value of the Device Name characteristic in the Generic Access service. If we don't define the name it will use the default "ESP32". While the device is ready we can get the pointer to its class using it to configure the advertising packet. After configuration, we can start the advertising process. void setup(){ BLEDevice::init("SUT BLE device"); pAdvertising = BLEDevice::getAdvertising(); ... // We will implement this part in step 2 pAdvertising->start(); }; The loop function can remain empty or call the delay() function. void loop(){ delay(5000); }; = Step 2 = The advertisement frame is composed of fields. Every field starts with its length (1 byte, including the field type byte). The second byte of the field encodes the field type. The selected field codes used in the default advertising frame defined in the ESP32 BLE library are presented in the table below. ^ Field length ^ Field code ^ Field type ^ Default value ^ | 0x02 | 0x01 | Flags | 0x06 | | 0x05 | 0x12 | Peripheral Connection Interval Range | 0x20004000 | | 0x06 | 0x09 | Complete Local Name | "ESP32" | | 0x02 | 0x0A | Tx Power Level | 0x09 | Except for the text information as the device name, it is possible to send other data such as the web address which directs us to additional information. There are some formats defined for use in beacon devices. One of them is Eddystone invented by Google. It is used in the advertisement frames for simplification of the URL encoding. It requires the presence of the field "List of 16-bit UUIDs" (code 0x03) which shows the UUIDs of the Eddystobe service (0xAAFE value), and the service data field (code 0x16) compatible with Eddystone format. These additional fields are shown in the table below. ^ Field length ^ Field code ^ Field type ^ Value ^ | 3 | 0x03 | Complete list of 16-bit service UUIDs | 0xAAFE | | 14 | 0x16 | Service data | Eddystone format | The Eddystone field starts with service UUID (0xAAFE), next specifies the content type (0x10 for URL), then the transmitting power in [dBm], and then the compressed URL. The UUID of Eddystone appears twice. Once as the element of the list of available services, the second time as the identifier of the service field. To compress URLs some default prefixes and suffixes were defined as non-ASCII characters. They are presented in the tables below. ^ Decimal ^ Hex ^ Prefix ^ | 0 | 0x00 | %%http://www.%% | | 1 | 0x01 | %%https://www.%% | | 2 | 0x02 | %%http://%% | | 3 | 0x03 | %%https://%% | ^ Decimal ^ Hex ^ Suffix ^ | 0 | 0x00 | .com/ | | 1 | 0x01 | .org/ | | 2 | 0x02 | .edu/ | | 3 | 0x03 | .net/ | | 4 | 0x04 | .info/ | | 5 | 0x05 | .biz/ | | 6 | 0x06 | .gov/ | | 7 | 0x07 | .com | | 8 | 0x08 | .org | | 9 | 0x09 | .edu | | 10 | 0x0a | .net | | 11 | 0x0b | .info | | 12 | 0x0c | .biz | | 13 | 0x0d | .gov | The Eddystone payload can be created as the std::string class object as required by the function addData() from the BLEAdvertisementData class. So first we need to declare the BLEAdvertismentData class object, and string object with the placeholder for the payload. BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); std::string eddystone_content (" "); //16 spaces for advertising packet In the setup() function, we need to fill in the empty spaces with details of the advertising packet. eddystone_content[0] = 0x02; //Length of FLags field eddystone_content[1] = 0x01; //Flags ID eddystone_content[2] = 0x06; //Flags value eddystone_content[3] = 0x03; //Length of service field eddystone_content[4] = 0x03; //List of 16-bit UUIDs eddystone_content[5] = 0xAA; //UUID of Google Eddystone eddystone_content[6] = 0xFE; eddystone_content[7] = 8; //length of Eddystone field eddystone_content[8] = 0x16; //Service data eddystone_content[9] = 0xAA; //Eddystone UUID eddystone_content[10] = 0xFE; eddystone_content[11] = 0x10; //URL Eddystone frame type eddystone_content[12] = 0xF4; //Tx power [dBm] eddystone_content[13] = 0x00; //prefix: "http://www." eddystone_content[14] = '2'; //URL eddystone_content[15] = 0x07; //suffix: ".com" We created a very short name for the URL to fit in one line of LCD screen (16 characters). The resulting text would be "http://www.2.com" The created payload can be used as the payload data of the advertising packet. oAdvertisementData.addData(eddystone_content); pAdvertising->setAdvertisementData(oAdvertisementData); = Step 3 = It is the time to receive the advertising packet. We will do it with another device starting with including the libraries required. #include "Arduino.h" #include "BLEDevice.h" #include "Adafruit_LiquidCrystal.h" There can be a lot of BLE devices in the range of our receiver. We should find the one that we are interested in so we define the UUID of the Eddystone service. #define SERVICE_UUID "feaa" Our program will use objects of two classes and some variables: static BLEAdvertisedDevice* myDevice; // Class for remote BLE device BLEScan* pBLEScan; // Class for local scanner device uint8_t * advPayload; // Pointer to the payload of incoming adv packet char eddystoneUrl[20]; // Buffer for text to display String advName; // Remote device name int advLength; // Length of payload of adv. packet int advIndex; // Index of the byte we proceed int advEddystoneLength; // Length of the Eddystone field int i; The received information will be displayed on the LCD, so we need to configure it (similar to the scenario EMB5) // LCD display pins and class declaration #define LCD_RS 2 #define LCD_ENABLE 1 #define LCD_D4 39 #define LCD_D5 40 #define LCD_D6 41 #define LCD_D7 42 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); The setup function initialises the display and the Bluetooth, sets up and starts the scanning process. An important step is registering the callback function. The callback function is called every time the scanner receives the advertising frame. void setup() { // Initialise LCD lcd.begin(16, 2); delay(1000); lcd.print("BLE CLient"); // Initialise the Bluetooth BLEDevice::init(""); // Retrieve the pointer to the scan module pBLEScan = BLEDevice::getScan(); // Register callback for incoming advertising pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // Start scan parameters with active scan mode pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); // Start the scan for 5 seconds pBLEScan->start(5, false); } In the loop function, we restart scanning every 5 seconds with 1 second delay. The start function of the BLEScan class is blocking, so it stops program execution for the time of scanning. void loop() { delay(1000); // Repeat scanning pBLEScan->start(5,false); } = Step 4 = The callback function checks if the device which sent the advertising frame implements the Eddystone service. This is how we filter out all other BLE devices available in the range. if we have more than one Eddystone device nearby we would have to implement additional filtering. After a successful search for a device with Eddystone service, we can display its name and proceed with displaying the content of the Eddystone field. Because the code is quite complex we present the whole callback function below. Notice that we decode chosen compressed fields only (prefix = 0x00, suffice = 0x07). You would need to decode other values for compatibility with the Eddystone format. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // We have found a device, let's see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { // Display the name of the remote device lcd.setCursor(0,0); lcd.print(advertisedDevice.getName().c_str()); // Get the length of the payload and the pointer to it advLength = advertisedDevice.getPayloadLength(); advPayload = advertisedDevice.getPayload(); // Search for the Eddystone field (field ID = 0x16) advIndex = 0; while (advIndex<=advLength) { if (advPayload[advIndex+1]==0x16) { // Eddystone field found, get the length of it advEddystoneLength = advPayload[advIndex]; // Display the Eddystone field lcd.setCursor(0,1); // Prefix decoding if (advPayload[advIndex+6]==0x00) { lcd.print("http://www."); } // ULR name for(i=0; i Additional filtering of remote devices can be done with their names. We can add in the "MyAdvertisedDeviceCallbacks()" function the comparison of the remote device name with the constant string. It should be done if the Eddystone service was found. String name; name = advertisedDevice.getName().c_str(); if (name.equals("SUT BLE device")) { // here the internal code of the MyAdvertisedDeviceCallbacks() } = Step 5 = Let's return to the beacon device. The advertisement frame is quite short so normally additional information is sent upon Scan Request with Scan Response frame. It can contain the device name which can differ from the name we specified in step 1 (in the case of normal operation we can read it as the value of the Generic Access Service). To specify different names in advertising frame and service characteristics we can use the setScanResponseData() function from BLEAdvertising class. oScanResponseData.setName("SUT Beacon"); pAdvertising->setScanResponseData(oScanResponseData); == Result validation == After the implementation of steps 1-4, you should be able to see the name of the beacon device in the first line of LCD and the URL from the Eddystone field in the second line of LCD. Implementation of step 5 should result in a change of the device name. == Further work == You can try to implement the beacon device compatible with iBeacon. The description can be found on the website ((https://www.silabs.com/whitepapers/developing-beacons-with-bluetooth-low-energy-technology)). === FAQ === **What is the maximum length of the advertising frame?**: It depends on the BLE version. In previous versions, it used to be 31 bytes only, but in newer versions, even 255 bytes are allowed. \\ **How often should the device send advertising frames?**: The delay between advertising frames can vary depending on the device. Beacon devices should send them every 100-200 ms. Other devices usually advertise every 2 seconds. Notice that advertising consumes energy, so the battery-powered devices advertise with lower frequency. \\ **What if I need to send some info other than in Eddystone or iBeacon format?**: You can send the text or even some data as part of the advertising frame. Some devices use the advertising frames to send simple 1-2 bytes of measurement data. More data can be sent after establishing the connection between the primary and secondary devices with the use of characteristics. \\ **What is the purpose of the transmitting power data presence in the advertising frame?**: The value sent as the transmitting power should represent real power at a 0-meter distance from the beacon. It can be used to estimate the actual distance of the user from the beacon. The distance needs to be calculated by application on the mobile phone with knowledge of the transmitting power of the beacon and actual measurement of received signal strength (known as RSSI). === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT8: BLE Communication with characteristics === This scenario presents how to create the Bluetooth Low Energy server device and the corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control by writing the data to. The client connects to a server and reads the data. This scenario presents the use of the concept of services and characteristics. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the simplest implementation to be extended in further scenarios. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. \\ **Task 2.** Implement a client device, to read the exemplary data from a server. == Start == You can use the beacon and simple client programs from [[en:iot-open:practical:hardware:sut:esp32:IoT_7]] as the starting point. == Steps == We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. The program returns automatically to the advertisement procedure after disconnecting. = Step 1 = Let's begin with a simple program which advertises a single service. The code should start with including Arduino and BLE libraries. #include #include "BLEDevice.h" #include "BLEUtils.h" #include "BLEServer.h" We need variables to hold the class pointers for advertising, the server instance, service, and characteristics. BLEAdvertising *pAdvertising; //class for the advertising data BLEServer *pServer; //class for the BLE Server device BLEService *pService; //class for the BLE Service in the Server BLECharacteristic *pCharacteristic; //class for the characteristic We will also need two boolean variables to control the restarting of the advertising. In case of establishing the connection from the remote client, the server device stops advertising, so it will not be searchable anymore. If we lose the connection with the client we need to restart advertising. bool deviceConnected = false; bool advStarted = true; Any service or characteristic is identified with a unique UUID. If you use the standard service, like eg. Health Thermometer or Binary Sensor Service you can use a 16-bit UUID defined by Bluetooth SIG in the document ((https://www.bluetooth.com/specifications/assigned-numbers/)). The same document defines the UUIDs for characteristics. Example of the standard UUIDs for service and characteristic: Health Thermometer Service - 0x1809 Temperature Measurement Characteristic - 0x2A1C While none of the standard UUIDs fits your device you need to define your own UUID. It must be 128-bit long and can be randomly generated with the tool available on the website https://www.uuidgenerator.net/((https://www.uuidgenerator.net/)). The service UUID and characteristic UUID must differ. Although they can be completely different in many implementations the UUIDs of characteristics grouped under one service differ on one digit. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" #define CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" Before we implement the setup() function, we have to create the callback function executed in case of connection and disconnection events. It allows us to control the advertising process with the deviceConnected flag. class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; The setup() function creates and initialises the BLE device instance with the name "SUT BLE device", and creates the server and sets the callback function. It also creates the service within the server. // Initialise the BLE device BLEDevice::init("SUT BLE device"); // Create BLE Server instance pServer = BLEDevice::createServer(); // Set callback function for handling server events pServer->setCallbacks(new MyServerCallbacks()); // Create the service in the server pService = pServer->createService(SERVICE_UUID); Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "BLE onboard" text. // Create the characteristic in the service pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); // Set the initial value of the characteristic. // We will be able to read it by client pCharacteristic->setValue("BLE onboard"); In the advertising packet, we can add the service UUID. // Get the pointer to the advertising object pAdvertising = BLEDevice::getAdvertising(); // Add the service UUID to the advertising pAdvertising->addServiceUUID(SERVICE_UUID); // Enable reading extended information with scan response packet pAdvertising->setScanResponse(true); Due to the limited size of the advertisement packet, not all service UUIDs can be broadcast. Even more, service UUID does not have to appear in the advertising frame, but its presence makes it possible to scan nearby devices by their functionality, not by names only. After preparing all the elements we can close the setup() function by starting the service and beginning advertising. // Starting the service and advertising process pService->start(); pAdvertising->start(); In the loop function, we need to handle the restarting of advertising in case of disconnection of the client. void loop(){ if (!deviceConnected && !advStarted) { pServer->startAdvertising(); // restart advertising advStarted = true; } if (deviceConnected){ advStarted = false; } delay(500); }; = Step 2 = In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client_char.drawio.svg?500 |The client algorithm}} = Step 3 = While we have analysed the client's behaviour we can start implementation. Let's begin with the libraries needed. #include "Arduino.h" #include "BLEDevice.h" #include "Adafruit_LiquidCrystal.h" Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. // The remote service we wish to connect to. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" // The characteristic of the remote service we are interested in. #define REMOTE_CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" Some global variables will be needed for our software. // Variables static boolean doConnect = false; static boolean connected = false; static boolean doScan = false; static BLERemoteCharacteristic* pRemoteCharacteristic; static BLEAdvertisedDevice* myDevice; We need to add and configure the LCD to be able to observe the results. // LCD display pins and class declaration #define LCD_RS 2 #define LCD_ENABLE 1 #define LCD_D4 39 #define LCD_D5 40 #define LCD_D6 41 #define LCD_D7 42 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); Two callback functions will be defined. The first callback is for advertising. It is called whenever the advertising frame is received, no matter which device sends it. Inside the callback, we search for the device nearby which presents the service we would like to use. If we find one, we can create the instance of the remote device class and pass the signal to the main program to establish the connection. // Scan for BLE servers and find the first one that advertises // the service we are looking for. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // We will print the asterix for every device found lcd.print("*"); // We have found a device, let's see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { // Our device has the service we need. We can stop scanning. BLEDevice::getScan()->stop(); // Create the instance of remote device myDevice = new BLEAdvertisedDevice(advertisedDevice); // Pass information to other part of the program to connect to the device doConnect = true; doScan = true; } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks The second callback class helps to inform the other parts of the program about the connection close. It will additionally inform us about the current state of the program. class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { lcd.setCursor(0,0); lcd.print("Connected "); } void onDisconnect(BLEClient* pclient) { lcd.setCursor(0,0); lcd.print("Disconnected "); lcd.setCursor(0,1); connected = false; } }; The setup function initialises the Bluetooth, registers the advertising callback function, and starts the scan to look for nearby devices. void setup() { // Initialise the LCD and print the welcome message lcd.begin(16, 2); delay(1000); lcd.setCursor(0,0); lcd.print("BLE Client "); // Initialise the Bluetooth BLEDevice::init(""); // Retrieve the pointer to the scan module BLEScan* pBLEScan = BLEDevice::getScan(); // Register callback for incoming advertising pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // Start scan parameters with active scan mode pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); // Print the message on the LCD lcd.setCursor(0,0); lcd.print("Scanning "); lcd.setCursor(0,1); // Start the scan for 30 seconds pBLEScan->start(30, false); } In the loop() function, we wait for the information that the server with the service UUID we were interested in was found. It is signalled with the use of "doConnect" flag. If it's true we can connect to this server. Once we are connected the "connected" flag is set to be true in the connectToServer() function. We will show this function in a while. \\ In a loop() function we will periodically read the characteristic value. It is a good practice to check if the characteristic is properly retrieved and readable before reading the value. void loop() { if (doConnect == true) { // Establish the connection to the server connectToServer(); doConnect = false; } if (!connected) { if(doScan){ // Start scan again after disconnect BLEDevice::getScan()->start(0); } } if (connected) { lcd.setCursor(0,1); // Is the characteristic properly retrieved? if(pRemoteCharacteristic != nullptr) // Is the characteristic readable? if(pRemoteCharacteristic->canRead()) // We can safely read the value lcd.print(pRemoteCharacteristic->readValue().c_str()); } delay(1000); // Delay a second between loops. } The connection to the server is executed in some steps: - Creation of the client object. - Setting the callback class for handling disconnection. - Connection to the remote device. - Setting parameters of the connection. - Getting the reference to the service. - Getting the reference to the characteristic. - Informing the main program with the "connected" flag. bool connectToServer() { BLEClient* pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback()); // Connect to the remote BLE Server. pClient->connect(myDevice); // Obtain a reference to the service in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(BLEUUID(SERVICE_UUID)); // Obtain a reference to the characteristic of the chosen service. pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(REMOTE_CHARACTERISTIC_UUID)); connected = true; return true; } == Result validation == You should be able to observe messages describing the process of searching for the server on the first line of LCD and during scanning asterisks appearing on the second line. After establishing the connection, the characteristic value should appear on the display's second line. === FAQ === **What types of data can be sent with the characteristics?**: The BLE characteristics can send any value as a single byte or a sequence of bytes. BLE does not interpret the data in characteristic in any way. Interpretation lies on the side of the application. \\ **What if I need to read or write more data than a single text?**: It is possible to implement many services in one device and add many characteristics to one service. Theoretically, the maximum number of characteristics in one service is 512, but the implementation and the memory available for the BLE library limit it. \\ **What is the maximum length of the data in the characteristics?**: The maximum length of the data in the characteristics in the BLE specification is 512 bytes. \\ === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
==== IoT9: BLE Communication with notify/indicate === This scenario presents how to extend the Bluetooth Low Energy server and client devices with a notification/indication mechanism for sending data automatically. If enabled, notifications or indications are sent at any time while the data in the server is updated. A difference between them is that a notification is an unacknowledged message while an indication is an acknowledged message. While one of them is enabled by the client, the server decides on the time of the message sent. === Prerequisites === It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. Notification and indication methods of data transmission should be known. We will use in this scenario the knowledge of the services and characteristics so making the [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] exercise is recommended. === Suggested Readings and Knowledge Resources === * [[en:iot-open:introductiontoembeddedprogramming2:cppfundamentals]] * [[en:iot-open:hardware2:esp32|]] * [[en:iot-open:practical:hardware:sut:esp32|]] === Hands-on Lab Scenario === This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the extension of the scenario implemented in [[en:iot-open:practical:hardware:sut:esp32:IoT_8]]. == Task to be implemented == **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. After a successful connection, it handles the notify descriptor modification and sends the data automatically if notifications are enabled. \\ **Task 2.** Implement a client device, capable of connecting to the server, enabling notifications and displaying the exemplary data coming from a server as notification packets. == Start == You can use the simple client and server programs from [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] as the starting point. == Steps == We will pass through the lab in a few steps. We will add the second characteristic to the example from lab [[en:iot-open:practical:hardware:sut:esp32:IoT_8]]. We will configure this characteristic as notify/indicate capable. It allows us to establish a connection and, if successfully connected, enable the indication or notification feature. With this feature enabled, the peripheral device initiates data transmission. It can be used for periodic measurement reading or updating information on value change. = Step 1 = We start with the program written during the laboratory [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] by including the BLE2902.h library which handles the Client Characteristic Configuration Descriptor (CCCD). The descriptor of this type is used to enable or disable the notification and indication feature. ^ Descriptor UUID ^ Bits 1 and 0 ^ CCCD value ^ Function ^ | 0x2902 | 00 | 0 | notify/indicate disabled | | ::: | 01 | 1 | enable notification | | ::: | 10 | 2 | enable indication | #include "BLE2902.h" Next, we add another characteristic to the service and variable required to hold the pointer to the characteristic class. We need to define the UUID for this characteristic. BLECharacteristic *pNotifyCharacteristic; //class for the characteristic #define NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" We create an additional characteristic with reading, writing, notify and indicate enabled, and the initial text "00" as a placeholder for the data. We also add the descriptor to the characteristic. // Create a BLE Characteristic pNotifyCharacteristic = pService->createCharacteristic( NOTIFY_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); pNotifyCharacteristic->setValue("0"); // Create a BLE Descriptor pNotifyCharacteristic->addDescriptor(new BLE2902()); Any characteristic can be configured as notify or indicate enabled. We can enable this mechanism for more than one characteristic. After these modifications, it would be possible to observe an additional characteristic in the service with the possibility of reading, writing, and enabling notification and indication. = Step 2 = At this step, we implement periodical data sending. We add two variables, an integer for holding the value incremented every loop pass and a text value converted from an integer to be sent within the indication/notification packet. int int_value=0; char text_value[] = "000000"; In the loop() function we modify the part executed if the connection was established. We'll add conversion from integer to text with atoi(), an update of characteristic value, and an incrementation of integer value. void loop(){ if (!deviceConnected && !advStarted) { pServer->startAdvertising(); // restart advertising advStarted = true; } if (deviceConnected){ advStarted = false; itoa(int_value,text_value,10); pNotifyCharacteristic->setValue(text_value); pNotifyCharacteristic->notify(); int_value++; } delay(500); }; = Step 3 = In this step, we'll analyse the communication between the server and the client. The client software is much more complex than the server. Some parts of the client software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server. {{ en:iot-open:practical:hardware:sut:esp32:ble_client.drawio.svg?500 |The client algorithm}} = Step 4 = We have to extend the client software from scenario [[en:iot-open:practical:hardware:sut:esp32:IoT_8]] with some elements. Let's start with adding another remote characteristic which is responsible for receiving notifications. #define REMOTE_NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" In the function for connecting to the server, we have to add another characteristic and register the callback function. It is also good to clear the LCD after establishing a connection. // Obtain a reference to the notify characteristic of the chosen service. pRemoteNotifyCharacteristic = pRemoteService->getCharacteristic(BLEUUID(REMOTE_NOTIFY_CHARACTERISTIC_UUID)); if(pRemoteNotifyCharacteristic->canNotify()) pRemoteNotifyCharacteristic->registerForNotify(notifyCallback); lcd.clear(); Next, we add the callback function which will be called every time the server sends a new notification packet. static void notifyCallback( BLERemoteCharacteristic* pBLERemoteNotifyCharacteristic, uint8_t* pData, size_t length, bool isNotify) { lcd.setCursor(0,0); pData[length]=0; //limit the length of the printed string lcd.print((char*)pData); } You can leave a periodical reading of the second characteristic in the loop(), but you will observe that sometimes its value appears together with the notification in the first line of the LCD. This is because notification callback is called as the interrupt handler, and can be executed between setting the cursor and displaying the characteristic value. The only way to avoid it is to move displaying of incoming notifications to the mail loop. == Result validation == You should be able to establish a connection and read the non-notification characteristic data. After enabling the notification we will observe periodic incrementation of the value sent with notify data packets. You can observe that sometimes devices do not connect. This can happen because if you upload the new version of the program to one of the devices the second one remains in a connected state. In such a situation you need to restart both devices. === FAQ === **What is the difference between notification and indication?**: As it was mentioned in the beginning the notification is an unacknowledged message while an indication is an acknowledged message. It means that if the indication message remains unacknowledged, the peripheral device won't send another message for this characteristic. **Is the notification/indication mechanism similar to interrupts?**: You are right. They work in a similar manner as interrupts in the microprocessor. They allow sending the data asynchronously, and the peripheral decides about the time of the message sent like the peripheral decides on the time of signalling interrupt. Data transmission when the central device sends the packet with a characteristic read command is similar to the polling method used in microprocessors. === Project information === {{:en:iot-open:logo_iot_200_px.png?200|}}\\ 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 [[https://en.wikipedia.org/wiki/Creative_Commons_license|Creative Commons Licence]], free for Non-Commercial use.
{{:en:iot-open:ccbync.png?100|}}
====== ITT AVR Laboratory Node Hardware Reference ====== ===== Introduction ===== Each laboratory node is equipped with an ATmega2561 chip. Several peripherals, sensors and motors 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. {{ ::en:iot-open:practical:hardware:ittgroup:avr_kauglabor.png?580 |}} ===== Hardware reference ===== 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
{{ :kit:atmega2561.jpg?580 |}} Controller module
==== Connector Pins and Functions ==== All ATmega2561 signals are available on three connectors on the edge of the board. {{:kit:pf_2561.png?450 |}} ~~CL~~ ^Nr^Pin^Alternative function / Description^^ |1|VCC|- |+5 V | |2|GND|- |GND | |3|REF|AREF|Analog Reference Voltage For ADC | |4|GND|- |GND | |5|PF0|ADC0|ADC Input Channel 0 | |6|GND|-|GND | |7|PF1|ADC1|ADC Input Channel 1 | |8|GND|-|GND | |9|PF2|ADC2|ADC Input Channel 2 | |10|GND|-|GND | |11|PF3|ADC3|ADC Input Channel 3| |12|GND|-|GND | {{:kit:pe-pb-pd.png?450 |}} ~~CL~~ ^Nr^Pin^Alternative function / Description^^ |1 |PD7|T2 |Timer/Counter2 Clock Input | |2 |PD6|T1 |Timer/Counter1 Clock Input | |3 |PD5|XCK1 |USART1 External Clock Input/Output| |4 |PD4|IC1 |Timer/Counter1 Input Capture Trigger| |5 |PD3|INT3/TXD1 |External Interrupt3 Input or UART1 Transmit Pin | |6 |PD2|INT2/RXD1 |External Interrupt2 Input or UART1 Receive Pin | |7 |PD1|INT1/SDA |External Interrupt1 Input or TWI Serial Data | |8 |PD0|INT0/SCL |External Interrupt0 Input or TWI Serial Clock | |9 |VCC|- |+5V | |10|GND|- |GND | |11|PB7|OC0A/OC1C/PCINT7|Output Compare and PWM Output A for Timer/Counter0, Output Compare and PWM Output C for Timer/Counter1 or Pin Change Interrupt 7| |12|PB6|OC1B/PCINT6|Output Compare and PWM Output B for Timer/Counter1 or Pin Change Interrupt 6| |13|PB5|OC1A/PCINT5|Output Compare and PWM Output A for Timer/Counter1 or Pin Change Interrupt 5| |14|PB4|OC2A/PCINT4|Output Compare and PWM Output A for Timer/Counter2 or Pin Change Interrupt 4| |15|PB3|MISO/PCINT3|SPI Bus Master Input/Slave Output or Pin Change Interrupt 3| |16|PB2|MOSI/PCINT2|SPI Bus Master Output/Slave Input or Pin Change Interrupt 2| |17|PB1|SCK/PCINT1|SPI Bus Serial Clock or Pin Change Interrupt 1| |18|PB0|SS/PCINT0|SPI Slave Select input or Pin Change Interrupt 0| |19|PE7|INT7/IC3/CLK0 |External Interrupt 7 Input, Timer/Counter3 Input Capture Trigger or Divided System Clock| |20|PE6|INT6/T3 |External Interrupt 6 Input or Timer/Counter3 Clock Input| |21|PE5|INT5/OC3C|External Interrupt 5 Input or Output Compare and PWM Output C for Timer/Counter3| |22|PE4|INT4/OC3B|External Interrupt4 Input or Output Compare and PWM Output B for Timer/Counter3| |23|PE3|AIN1/OC3A|Analog Comparator Negative Input or Output Compare and PWM Output A for Timer/Counter3| |24|PE2|AIN0/XCK0|Analog Comparator Positive Input or USART0 external clock input/output| |25|PE1|PDO/TXD0 |ISP Programming Interface Data Output or USART0 Transmit Pin| |26|PE0|PDI/RXD0/INT8 |ISP Programming Interface Data Input, USART0 Receive Pin or Pin Change Interrupt 8| {{:kit:pa-pc_2561.png?450 |}} ~~CL~~ ^Nr^Pin^Alternative function / Description^^ |1 |GND|- |Gnd | |2 |VCC|- |+5 V | |3 |PA0|AD0|External memory interface address and data bit 0 | |4 |PA1|AD1|External memory interface address and data bit 1 | |5 |PA2|AD2|External memory interface address and data bit 2 | |6 |PA3|AD3|External memory interface address and data bit 3 | |7 |PA4|AD4|External memory interface address and data bit 4 | |8 |PA5|AD5|External memory interface address and data bit 5 | |9 |PA6|AD6|External memory interface address and data bit 6 | |10|PA7|AD7|External memory interface address and data bit 7 | |11|PG4|TOSC1|RTC Oscillator Timer/Counter2 | |12|PG5|OC0B|Output Compare and PWM Output B for Timer/Counter0| |13|PG2|ALE|Address Latch Enable to external memory | |14|PG3|TOSC2|RTC Oscillator Timer/Counter2 | |15|PC6|A14|External Memory interface address bit 14 | |16|PC7|A15|External Memory interface address bit 15 | |17|PC4|A12|External Memory interface address bit 12 | |18|PC5|A13|External Memory interface address bit 13 | |19|PC2|A10|External Memory interface address bit 10 | |20|PC3|A11|External Memory interface address bit 11 | |21|PC0|A8 |External Memory interface address bit 8 | |22|PC1|A9 |External Memory interface address bit 9 | |23|PG0|WR |Write strobe to external memory | |24|PG1|RD |Read strobe to external memory | |25|GND|- |GND | |26|3V3|- |+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 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; * Buzzer;
{{:examples:ui:UI_v53.jpg?450|}} User Interface module module
=== Electrical connections === The User Interface module is connected to the Controller module to ports PA/PC/PG, which includes the 8-pin ports PA and PC and the 6-pin port PG. The User Interface module is equipped with three buttons, S1, S2, and S3, which are connected to ports PC0, PC1, and PC2, respectively. The other ends of the buttons are connected through the resistors to ground (logical 0). LED1, LED2, and LED3 on the module are connected to the ports PC3, PC4, and PC5, respectively. The anodes of LEDs are connected to the supply (logical 1).
{{ :examples:ui:ui_button_led_schematics_v51.png?580 |}} Schematics of buttons and LEDs
~~CL~~ The User Interface module is equipped with a 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.
{{ :examples:digi:digi_io_7seg_schematics_v51.png?580 |}} Schematics of 7-segment indicator
~~CL~~ The graphical LCD display on the module is connected to port PA. LCD's background lighting can be logically controlled by the software.
{{:examples:ui:ui_lcd_schematics_v51.png?580|}} Schematics of LCD
==== Combo module ==== The combo module is used in this remote Laboratory to power the motors and connect external sensors. The combo module can drive the following motors: * 4 x DC motors * 1 x Stepper * 2 x Servomotor \\ \\ Connect the following sensors: * 4 x analog * 4 x digital * 2 x coder * 8x digital inputs through a parallel-to-serial converter \\ \\
{{:kit:combo:combo_xbeega.jpg?600|}} Combo board with XBee module
=== Electrical connections === The combo board is connected with the controller using PA-PB-PC-PD-PE-PF ports. DC and stepper motor feeds come from an external power cord; the servo motor feed comes through a 5V voltage regulator, also from an external power cord. The motor's power circuit is separated from the controller's power circuit. === DC motors === DC motors are 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. The combo board uses {{:datasheets:datasheet_h_sild_bd6226fp.pdf|bd6226fp}} H-bridge to control DC motors. It's possible to manipulate a device that can be controlled digitally, with a current of less than 1 A and a voltage of no more than 18 V, such as a DC motor with DC pins (e.g., Piezoelectric generator, relay).
{{:en:iot-open:practical:hardware:ittgroup:h-sild2.jpg?640|}} DC motor connection scheme
~~CL~~ ^ 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 ===
{{:en:iot-open:practical:hardware:ittgroup:steper2.jpg?250|}} Stepper connection scheme
~~CL~~ === Servomotor === The servo motor 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 two servomotors at once. Signal pins on the Combo module are directly connected to the controller's timer output pins.
{{:en:iot-open:practical:hardware:ittgroup:servo2.jpg?100|}} Servomotor connection scheme
~~CL~~ ^ AVR pin ^ Signal ^ Socket ^ | PB5(OC1A) | PWM1 | Top | | PB6(OC1B) | PWM2 | Bottom | ===== AVR Laboratory Scenarios ===== The remote access lab does not provide direct access to the serial port or debugger. For this reason, understanding actuators (mostly displays and LEDs) is essential, because the only way to monitor execution is to observe the results remotely via the video stream. Video streaming has limitations, including frame rate, resolution, and colour rendering. This impacts how you write the software; for example, avoid rapid screen updates or subtle LED changes that may not be visible through the video stream. ** Know the hardware **\\ The following scenarios illustrate the utilisation of hardware components and services that comprise the ITT AVR laboratory node. They introduce students to embedded systems by starting with simple actuators and sensors before moving on to more complex control tasks. * [[en:iot-open:practical:hardware:itt:avr:led|How do you use a light-emitting diode (LED)?]] * [[en:iot-open:practical:hardware:itt:avr:segment_display|How do you use a 7-segment LED display?]] * [[en:iot-open:practical:hardware:itt:avr:lcd|How do you use an LCD screen?]] * [[en:iot-open:practical:hardware:itt:avr:thermistor|How do you measure temperature with a thermistor?]] * [[en:iot-open:practical:hardware:itt:avr:photoresistor|How do you use a photoresistor?]] * [[en:iot-open:practical:hardware:itt:avr:ir_distance|How do you use an infrared distance sensor?]] * [[en:iot-open:practical:hardware:itt:avr:dc|How do you control a DC motor?]] * [[en:iot-open:practical:hardware:itt:avr:servo|How do you control a servo motor?]] * [[en:iot-open:practical:hardware:itt:avr:stepper|How do you control a stepper motor?]] ** Next steps **\\ Once you are familiar with these components, you can combine them into integrated applications (e.g., measuring temperature with a thermistor and displaying it on the LCD, or using an infrared sensor to trigger motor movement). These exercises form the foundation for more advanced IoT and automation projects. ==== Light-emitting Diode ==== === Theory === [{{ :examples:digi:led:led_picture.jpg?150|5 mm legged LED}}] A light-emitting diode (LED) is a semiconductor device that emits light when a forward voltage is applied. The acronym for light-emitting diode is LED. There are different color combinations of diodes, and the diodes can also emit white light. Like a standard diode, the LED has two contacts: an anode and a cathode. On drawings, the anode is marked as “+” and the cathode as “-“. [{{ :examples:digi:led:led_designator.png?150|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 an LED is no more than a couple of dozen milliwatts, which means the 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 that would regulate current and voltage suited for LEDs. However, LEDs are quite often used as indicators, and they are supplied directly from the microcontroller’s pins. Since the supply voltage for microcontrollers is usually higher than the voltage for LEDs, there must be a resistor connected in series with the LED, which limits current and creates the necessary voltage drop. Instructions to calculate the proper resistor can be found in the electronics chapter. LEDs are produced in a variety of casings. Most common LEDs with feet have a 3 mm or 5 mm diameter round shell and two long metal connector pins. The 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. [{{ :examples:digi:led:led_pin_markings.png?200|Polarity of legged and SMD LED's}}] === HomeLab Practice === The HomeLab controller control module has one single indicator LED, whose anode is connected through a resistor to a power supply, and the cathode is connected to the controller's pin. In order to switch on and off this LED, the 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, including LED switching, utilize HomeLab’s pin library. Pin library includes data type //pin//, which contains addresses of pin-related registers and pin bitmask. If a pin-type variable is created in the program and then initialized by using the macro function PIN, the pin can be used freely with this variable (pin) throughout the whole program without being able to use registers. Here are two example programs that do exactly the same thing, but one is created based on HomeLab’s library, while the other is not. The debug LED, led_debug in the HomeLab library, has been described as PB7. The Debug LED is physically located in the Controller module. // HomeLab Controller module LED test program, which // is based on HomeLab library #include // LED pin configuration. pin led_debug = PIN(B,7); // 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 // Main program int main(void) { // Configuring LED pin as an output DDRB |= (1 << 7); // Lighting up LED PORTB &= ~(1 << 7); } The first example uses the pins library (//pin.h// file). First, a pin-type variable named //debug led// is created in the program, which holds information about the 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 the function //pin_clear//. As a result, the LED will glow. In the second example, variables are not used; setting the LED output and lighting it will be done by changing the 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 a command to the light LED, because the default output value of the AVR is zero anyway, but here it is done by means of correctness. What is the difference between the use of the library and the registers? The difference is in the comfort – the library is easier, because you do not need to know the registers’ names and their effects. The most important benefit of a library is adaptability. Using registers, you must change the register names and bitmasks throughout the entire program in order to change the pin. When using the library, it must be done only at the beginning of the program, where the pin variable is initialized. Using registers has one deceptive advantage – usage of pins 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 the library’s functions to exactly the same direct commands for manipulating registers as if they were done directly in the program. It 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. 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 the Controller module’s LED, which means the cathode is connected to the AVR pin. For more information, see the module's hardware reference. 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 the LED constants that are described in the library and the corresponding Controller module pins. Green, yellow, and red LEDs are located in the user interface module. ^Constant name^Alternative name ^ HomeLab I & II pin^Description^ |led_debug|LED0|PB7| Blue LED on the Controller module| |led_green|LED1|PC3| Green LED| |led_yellow|LED2|PC4| Yellow LED| |led_red|LED3|PC5| Red LED| HomeLab library-based example program, which uses LED constants, looks as follows: // LED test program for HomeLab User interface module #include // 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); } == Task to be implemented == - Make a blinking RED LED with a frequency of 1 Hz - Simulate a standard traffic light for cars. ==== 7-segment LED display ==== === Theory === [{{ :examples:display:segment_display:7-seg.jpg?100|7-segment}}] A 7-segmented LED number-indicator is a display that consists of 7 LEDs positioned in the shape of the 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...//). There also exist 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 a comma (point) segment //dp//. All in all, one indicator has 8 segments, but they are still called 7-segmented according to the number of segments. [{{ :examples:display:segment_display:segment_display_leds.png?300|Positioning of the LED indicator's segments and electrical scheme}}] [{{ :examples:display:segment_display:segment_display_driver_logic.png?300|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 are 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, there exist indicators with extra segments. There are different drivers, but common drivers using a serial interface, which is similar to the SPI, where both the clock signal and the data signal are used. Different from SPI, the chip-select is not used there, and is replaced with a 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. 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 a 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 the bits. The 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 is high for a moment pin_set(segment_display_clock); _delay_us(1); // The clock signal is low pin_clear(segment_display_clock); _delay_us(1); } // Latch-signal on pin_set(segment_display_latch); } For displaying numbers and the letter “E”, a "weak" constant array //segment_char_map//is created, where the lighting of all 8 segments is marked with bit 1 and switching off is marked 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 in their 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. The 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 #include // 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); } } == Task to be implemented == - Present numbers in the hexadecimal system randomly on the 7-segment display. The frequency is 1 Hz. - Light in circular sequence six outside segments on the 7-segment indicator with a period of 500 ms. ==== LCD screen ==== === Theory === [{{ :examples:display:lcd:lcd_element.jpg?250|The graphical LCD element}}] /*[{{ :examples:display:lcd_alphanumeric:lcd_alphanumeric_abc.png?200|Alfabeetilise LCD pikslite maatriksitest moodustatud tekst}}]*/ [{{ :examples:display:lcd_graphic:lcd_graphic_smiley.png?200|The picture formed of pixels of a graphic LCD}}] A graphical LCD liquid crystal display is a display that allows displaying pictures and text. Its construction is similar to the alphanumeric LCD, with a difference that on the graphic display, all pixels are divided into a single large matrix. If we are dealing with a monochrome LCD, then a pixel is one square segment. In a color display, a 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 usually have a passive matrix, and large color displays, including computer screens, have an active matrix. All information concerning the color of the background and the pixels of the graphic LCDs is similar to that of alphanumeric LCDs. Similar to the alphanumeric displays, graphic displays are also equipped with a separate controller, which takes care of receiving information through the communication interface and generating the electrical field for the segments. === Practice === HomeLab User Interface is an 84×48-pixel monochrome graphic LCD. It is the same display as used in Nokia 3310 mobile phones. The Philips PCD8544 controller is attached to the display, which can be communicated through a 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 inside the library with the full Latin alphabet, numbers, and the most common signs written. To display a letter or text, first its position must be determined by using the function //lcd_gfx_goto_char_xy//. For displaying a letter is //lcd_gfx_write_char// function, and for displaying text //lcd_gfx_write_string// function. The following is an example of a time counter. The program counts seconds (approximately), minutes, and hours. For converting time to text, the 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 #include #include // 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 a 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); } } == Task to be implemented == - Combine with LED tasks (traffic light) and show on the LCD screen, which LED is currently active. ==== Thermistor ==== === Theory === [{{ ::examples:sensor:thermistor:ntc.jpg?200|NTC thermistor}}] A thermistor is a type of resistor whose 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 a positive temperature coefficient of resistance increases when the temperature grows, and with a negative coefficient, the resistance decreases. The respective abbreviations are PTC (//positive temperature coefficient//) and NTC (//negative temperature coefficient//). The thermistor's resistance dependence on temperature is not linear, and this complicates its usage. For accurate temperature measurements in wider temperature fluctuations, the Steinhart-Hart third-order exponential equation is used as the thermistor's resistance is linear only in a small temperature range. The following simplified Steinhart-Hart equation with B-parameter exists for NTC thermistors: {{:examples:sensor:thermistor:sensor_ntc_equation.png?100|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 range of temperature, for example, in the 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 in the resistance of the thermistor. If the voltage is applied, current goes through the thermistor, which heats up the thermistor due to the thermistor's resistance and therefore alters the resistance again. The fault caused by the heating up of the thermistor can be compensated with calculations, but it is easier to use a thermistor that has a higher resistance and therefore heats up less. With restricted resources and with fewer 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 an NTC-type thermistor, which has a 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 the supply, and the other one is connected to the analogue-digital converter channel two. 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 an NTC thermistor, whose resistance decreases as the temperature increases, the output voltage of the voltage divider increases with growing temperature. While using the AVR, it is practical to use a conversion table of values of temperature and an analogue-digital converter to find the correct temperature. It is wise to find the corresponding value of an analogue-digital converter for each temperature degree of the desired range of temperature because the 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, is able to find the resistance of the thermistor that corresponds to the temperature. Derived from the resistance, it is possible to calculate the output voltage of the voltage divider and use this output voltage to calculate the value of the ADC. Calculated values can be inserted into 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 degrees and end at 100 degrees // 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 }; The conversion table and function are already in the library of the HomeLab. In the library, the conversion function is named //thermistor_calculate_celsius//. An example program of this exercise is a thermometer, which measures temperature in the Celsius scale and displays it on an LCD. // The temperature is displayed on the LCD #include #include #include #include #include // Sensor //#define ADC_CHANNEL 2 // 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 the 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 the Celsius scale temperature = thermistor_calculate_celsius(value); // Converting the temperature into 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; } == Task to be implemented == - Switch every 3 seconds the unit between Kelvin (K), Fahrenheit (F), and Celsius (C). The temperature must be displayed in correct values, units, and symbols. ==== Photoresistor ==== === Theory === [{{ :examples:sensor:photoresistor:sensor_photoresistor.jpg?150|A photoresistor}}] A photoresistor is a sensor whose electrical resistance is altered depending on the light intensity falling on it. The more intense the light, the more free carriers are formed and therefore the lower the resistance of the element. Two exterior metal contacts of the photoresistor reach 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 a narrow, curvy track between the electrodes, low total resistance at average light intensity is gained. Similar to the human eye, the photoresistor is sensitive to a 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. The following is a 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 temperatures is set for the photoresistor. Wishing the sensor to work at different temperatures, precise conversions must be executed, because the resisting properties of the sensors depend on the temperature of the ambient. For characterizing light intensity, a physical concept called light intensity (E) is used, which shows the quantity of light reaching any given surface. The 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 does light (living area) fall on a surface evenly, and therefore, light intensity is generally reached as an average number. Below are a 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 a VT935G photoresistor. One pin of the photoresistor is connected to the power supply, and the second pin to the analogue-digital converter channel 1. 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 decreases as the light intensity falls on it grows, the measured voltage on the pin of the microcontroller increases as the light intensity increases. It is worth taking into account that the photoresistor used in the HomeLab reacts most to 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 a half-dark room, note it in the program, and compare the measured values – is it lighter or darker? The exercise here is a little bit more complex, as the light intensity is also measured in lux. For doing this, there exists an approximate formula and floating-point variables. In the C language, there are floating-point variables //float-// and //double//-type variables, which can be used to represent fractions. Their flaw is a high demand for 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. The example source code measures the light intensity, calculates it 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 also be just 0, because then the compiler understands it correctly). // HomeLab photoresistor demonstration // LCD screen displays the approximate illuminance in lux #include #include #include #include #include // 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(1, 10); // Calculating the voltage in the input of the ADC voltage = 5.0 * ((double)adc_value / 1024.0); // Calculating the resistance of the photoresistor // in the voltage divider resistance = (10.0 * 5.0) / voltage - 10.0; // Calculating the intensity of light in lux illuminance = 255.84 * pow(resistance, -10/9); // Dividing a variable into two integer variables // 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); } } == Task to be implemented == - Make a three-level light indicator, either dark, normal, or intense light, and show the result with three LEDs (green, yellow, and red). ==== Infrared distance sensor ==== === Theory === [{{ :examples:sensor:ir_distance:sensor_ir_distance_gp2y0a21yk_picture.jpg?150|Sharp GP2Y0A21YK}}] For measuring the distance to an object, there are optical sensors using the triangulation measuring method. The company “Sharp” produces the most common infra-red (IR) wavelength using distance sensors, which have an analogue voltage output. The sensors made by “Sharp” have an IR LED equipped with a lens, which emits a narrow light beam. After reflecting from the object, the beam will be directed through the second lens to a position-sensitive 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 digitized by using an analogue-digital converter, the distance can be calculated. The route of beams reflecting from different distances is presented on the drawing next to the text [{{ :examples:sensor:ir_distance:sensor_ir_distance_principle.png?200|The route of the light beam from an IR distance sensor}}] The output of distance sensors by "Sharp" is inversely proportional, which means that when the distance is growing, the output is decreasing (decreasing is gradually slowing). The 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 credible, and this range depends on the type of sensor. The maximum distance measured is restricted by two aspects: the amount of reflected light is decreasing, and the inability of the PSD to register the small changes in the location of the reflected ray. When measuring objects that 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 for one value of the output, there are two values of distance. This problem can be avoided by noticing that the object is not too close to the sensor. === Practice === [{{ :examples:sensor:ir_distance:sensor_ir_distance_graph.png?200|Diagram of voltage-distance of IR distance sensor}}] The HomeLab set of sensors includes an IR distance sensor, SHARP GP2Y0A21YK. The 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 with sensors, it is easy to write a program that 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 a graph of the 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, it is quite easy to find the formula for converting voltage to distance. // 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, and the 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. The following program demonstrates the use of an IR distance sensor and the conversion function. A 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 are displayed on the LCD #include #include #include #include #include #define ADC_CHANNEL 0 // Main program int main(void) { signed short value, distance; char text[16]; // 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 an unknown distance sprintf(text, "Error "); } lcd_gfx_goto_char_xy(1,3); lcd_gfx_write_string(text); sw_delay_ms(500); } } == Task to be implemented == - Show on the LCD the direction, whether the distance increases or decreases. For changing the distance, use a Servo motor (use the following scenario and instructions for servo motor control). ==== DC motor ==== === Theory === [{{ :examples:motor:dc:motor_dc_picture.jpg?200|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 a transmission (to output a lower speed and higher torque). Permanent magnet DC motors have a relatively simple construction, and their control is straightforward. Although controlling is easy, their speed is not precisely determined by the control signal because it depends on several factors, primarily on the torque applied to the shaft and the feeding current. The relationship between torque and speed of an ideal DC motor is linear, which means: the higher the load on the shaft, the lower the speed of the shaft, and the higher the current through the coil. Brushed DC motors use 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 rotation 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 a relay or some other simple connection. If the motor has to revolve in both directions, then an electronic circuit called an 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 of applying both directional polarities to the motor. An H-bridge can also change the direction of rotation and the rotation speed of the motor. There also exist integrated H-bridges for conducting smaller currents, for higher currents, special power MOSFETs are used. The H-bridge with other electronics is called a motor controller or driver. 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 and off. The total motor power is something in between standing and full speed. The time of the entire PWM period when the transistor is opened, called the duty cycle, is denoted by a percentage. 0% means that the transistor is constantly closed and does not conduct, 100% means that the transistor is open and conducts. The PWM frequency should be high enough to prevent vibration of the motor shaft. At low frequencies, the motor produces noise and is therefore used with a modulating frequency above 20 kHz. However, the 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 they require only a single digital output, and there is no need for a complicated digital-to-analog converter. The digital control is also more efficient because less energy is converted into heat. === Practice === The HomLab uses a combined chip to drive DC motors, which includes two integrated H-bridges and circuit-breaking diodes. The motor is controlled with three digital signals, one of them is the operation enabling signal //enable//, and the other two are determining the state of the transistors in the H-bridge. It can never 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 the only option that can be chosen is which transistor (upper or lower) of one side of the H-bridge (of “semi-bridge”) is opened. In other words, the polarity is selected using two driving signals, which are 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 an 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 the same value, then the motor is stopped; if they have different values, 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 | Each motor that is connected to the H-bridge is operated by two of the digital outputs of the microcontroller. The motor speed is controlled by timers that generate a continuous PWM signal to the H-bridge, and the direction of rotation of the motor is controlled by the second terminal. Motor speed is controlled by relative values from 0 to 255, where zero means that the motor is standing and 255 is the maximum moving speed of the motor. The following code describes a function which are described in the HomeLab II 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 the 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 The controlling pins of four motor-controllers are determined with the array dcmotor_pins in the library. Before controlling the motors, the 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, which determines the frequency of the PWM signal. The dcmotor_drive_pwm function is used for controlling motor speed. This function needs three input values: motor number, direction (-1, 0, +1), where -1 is the rotation in one direction, +1 is the other direction, and 0 is 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 the minimum and maximum 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 that controls the DC motor. // Robotic HomeLab DC motor driving example program #include // Main program int main(void) { dcmotor_drive_pwm_init(1, TIMER2_NO_PRESCALE); dcmotor_drive_pwm(2, 1, 128); } == Task to be implemented == - Make a smooth start of the DC motor and after 10 seconds of rotating at maximum speed, slow down to a full stop. ==== Servomotor ==== === Theory === [{{ :examples:motor:servo:motor_servomotor.jpg?200|RC servo motor}}] [{{ :examples:motor:servo:motor_servo_signal_position.png?200|The relationship between the width of the signal and the position of the Servo PWM.}}] RC (//radio-controlled//) servo-motors are very common actuator devices in robotics and model building. RC servo motors consist of a small DC motor, a reduction gear, and a 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 on the control signal received by the servo motor. Depending on the type of 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. The controlling signal of the servo motor is a specific pulse with a modulated signal (PWM), where the 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. A traditional RC servo motor is also known as an analogue servo motor. It is because in the last decade, so-called digital servo motors have become common. The difference between those two is that in an analogue servo motor, the motor is controlled by the same 50 Hz PWM input signal. In a digital servo motor, the motor is controlled by a microcontroller with a much higher frequency signal. The input signal is the same in the digital servo motor, but a higher modulation frequency of the motor enables much more precise and faster position determination. === Practice === On the HomeLab module, there are two plugs for connecting RC servo motors. The PWM ends of the plugs are connected to the pins of the microcontroller, whose alternative functions are outputs of comparing units of the timer. The timer is capable of producing a PWM signal, and due to that, the control of motors is very simple in the program. The only difficulty is the setup of the timer. The timer must be set up in PWM production mode, where the maximum value of the timer is determined with the 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, the lengths of both high semi-periods of the PWM signal can be determined. The timers have special comparing units that monitor the value of the counter, and in case it remains equal to the value of the comparison register, they change the output value of the 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 the F_CPU constant, which marks the clock rate of the microcontroller. When using macros, there is no need to calculate the parameters of the timer for different clock rates, and the compiler converts the operations with macros to constants anyway, so the program memory does not grow and does not demand more time. The following example of a 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 by // 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 the 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 comparison 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 the 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 the described functions of the library of the HomeLab. At the beginning of the program, the servo motor’s PWM signal generator is started with the //servomotor_init// function. The value of the position is defined. // Testing program of the servo motor #include // Main program int main(void) { // Set-up of the motor servomotor_init(1); // Determining the position of the servo motor servomotor_position(1, 50); } == Task to be implemented == - Use a servo motor to block and unblock the distance sensor range and present the measured distance on the LCD (use previous scenarios and instructions for the sensor and LCD) ==== Stepper motor ==== === Theory === [{{ :examples:motor:stepper:stepper.jpg?200|Stepper-motor}}] Stepper motors are widely used in applications that demand accuracy. Unlike DC motors, stepper motors do not have brushes or a 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. Variable reluctance stepper motors have toothed windings and a toothed iron rotor. The largest pulling force is when the teeth of both sides are covering each other. In a Permanent magnet stepper motor, just like the name hints, there are permanent magnets that orient 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 hundreds 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 especially accurate and smooth control. === Practice === The Combo Module has an H-bridge to control bipolar stepper motors and a transistor matrix for unipolar stepper motors. There are functions //bipolar_init// and //unipolar_init// in the library of the HomeLab, which set the pins as output, and functions //bipolar_halfstep// and //unipolar_halfstep//execute revolving by determined half steps. The commutation is done by the table of half steps, but more complex bit operations are used. The following code section is the HomeLab II 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 breaks 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 // 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); } } == Task to be implemented == - Drive the stepper as a clock’s second hand: rotate 6° (one step chunk) every second to complete one full revolution per minute. - Simulate an analog thermometer: map the measured temperature linearly to a fixed arc and position the stepper ‘needle’ accordingly (use previous scenarios and instructions for temperature sensor). ====== TalTech Arduino Laboratory Hardware Reference ====== {{ :en:iot-open:practical:hardware:taltech:arduino_kauglabor.jpg?600 |}} ===== Introduction ===== Each laboratory node is equipped with an **Arduino Uno** board. This board is widely used for prototyping and educational purposes due to its ease of use, extensive compatibility with sensors and modules, and a simple programming environment. The hardware setup includes several sensors, actuators, and interfaces designed for various practical lab exercises. ===== Hardware reference ===== * **Microcontroller:** ATmega328P * **Operating Voltage:** 5 V * **Digital I/O Pins:** 14 (6 pins provide PWM output) * **Analog Input Pins:** 6 * **Clock Speed:** 16 MHz * **USB Interface:** Yes (for programming and serial communication) * **Power Supply:** USB connection or external power supply ==== Hardware Components and Pin Assignments ==== ^ Component ^ Model/Description ^ Pins ^ Remarks ^ | DC Motor | Micromotors Brushed Geared DC Motor, 12V, 20Ncm, 5rpm | Digital pins: AIN1=12, AIN2=11, PWM=3 | Controlled via Pololu TB6612FNG Dual Motor Driver | | Servo Motor | SG90 or similar | PWM pin: 10 | Controlled via Arduino Servo library | | Fan | 12V DC Fan | Digital pin: 2 | Controlled via digital on/off | | Thermistor | Steinhart-Hart Thermistor | Analog pin: A5 | Used for precise temperature measurement | | Photoresistor (LDR) | Chanzon GL5549, 10Ω LDR | Analog pin: A2 | Used for ambient light sensing | | LCD Screen | Alphanumeric LCD 16x2 (HD44780) | Pins: RS=8, EN=9, D4=4, D5=5, D6=6, D7=7, Buttons input: A0 | Used for displaying text and sensor values | ==== Additional Hardware ==== * **Motor Driver:** Pololu TB6612FNG Dual Motor Driver Carrier * Supports two DC motors independently * Motor voltage range: 4.5 – 13.5 V * Continuous current per channel: 1.2 A (peak 3 A) * Built-in protection: thermal shutdown and undervoltage lockout * Control inputs: Logic-level PWM and direction signals (2.7–5.5 V compatible) ==== Wiring Diagram ==== {{:en:iot-open:practical:hardware:taltech:wiring_schematic.png?600|}} ==== Suggested Knowledge Resources ==== * Arduino programming fundamentals * Basic understanding of PWM, analog/digital I/O * Use of common Arduino libraries: * ''Servo.h'' – Servo motor control * ''LiquidCrystal.h'' – LCD display handling * Standard Arduino analog/digital I/O functions ==== Development Environment Configuration ==== Typical ''platformio.ini'' file configuration for Arduino Uno: [env:uno] platform = atmelavr framework = arduino board = uno lib_ldf_mode = deep+ ===== TalTech AVR 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 TalTech Arduino laboratory node. They are intended to introduce students to embedded systems and IoT programming by first working with basic actuators and sensors. * [[en:iot-open:practical:hardware:taltech:arduino:scenarios:lcd|How do you use the LCD display?]] * [[en:iot-open:practical:hardware:taltech:arduino:scenarios:dc|How do you control a DC motor?]] * [[en:iot-open:practical:hardware:taltech:arduino:scenarios:servo|How do you control a servo motor?]] * [[en:iot-open:practical:hardware:taltech:arduino:scenarios:thermistor|How do you use a thermistor to measure temperature?]] ** Next steps **\\ After learning to use individual components, students are encouraged to combine multiple elements into integrated projects (e.g., displaying sensor readings on the LCD, or controlling actuators based on sensor inputs). These building blocks prepare for IoT-oriented programming, networking, and system integration in advanced modules. ==== Using LCD Display ==== {{:en:iot-open:practical:hardware:taltech:arduino:scenarios:lcd.jpg?300|}} An alphanumeric LCD is a straightforward and widely used output device in embedded systems and IoT applications. The LCD used here has a fixed organization of 2 lines and 16 characters per line (2x16). This scenario guides you through displaying text on the LCD using Arduino. === Prerequisites === * Familiarize yourself with the Arduino hardware reference. * Install the LiquidCrystal library (built-in Arduino library). === Hardware Connections === ^ LCD Pin ^ Arduino Pin ^ | RS | 8 | | EN | 9 | | D4 | 4 | | D5 | 5 | | D6 | 6 | | D7 | 7 | | Buttons Input | A0 | === Task === Display the text "Hello World" on the first line and "Hello IoT" on the second line of the LCD. === Steps === = Step 1: Include Library = Add the LCD library to your Arduino sketch: #include = Step 2: Declare GPIO Pins = Define GPIO pins connected to LCD control lines: const int rs = 8, en = 9, d4 = 4, d5 = 5, d6 = 6, d7 = 7; = Step 3: Initialize LCD = Create an instance of the LCD: LiquidCrystal lcd(rs, en, d4, d5, d6, d7); Set up the display dimensions (2 rows, 16 columns): lcd.begin(16, 2); = Step 4: Display Text = Write text to the LCD screen: lcd.setCursor(0, 0); // top left corner lcd.print("Hello World"); lcd.setCursor(0, 1); // second line lcd.print("Hello IoT"); === Validation === The LCD should clearly display "Hello World" on line 1 and "Hello IoT" on line 2. === Useful Methods === * ''lcd.clear()'' – clears all text from the LCD. * ''lcd.setCursor(col, row)'' – positions the cursor. * ''lcd.print(data)'' – prints data to the display. === Troubleshooting === If the LCD shows incorrect characters or nothing: - Double-check wiring and pin assignments. - Verify correct initialization (`lcd.begin(16,2)`). - Adjust the contrast potentiometer on the LCD, if available. ==== Controlling a DC motor ==== This scenario demonstrates how to control a DC motor's rotation direction and speed using Arduino with the Pololu TB6612FNG Dual Motor Driver. {{:en:iot-open:practical:hardware:taltech:arduino:scenarios:dc.jpg?100|}} === Prerequisites === * Familiarize yourself with the Arduino hardware reference. * Understand basic PWM concepts. * Familiarity with the Pololu TB6612FNG motor driver. === Hardware Connections === ^ Motor Driver Pin ^ Arduino Pin ^ | AIN1 | 12 | | AIN2 | 11 | | PWMA | 3 (PWM) | **Motor specifications:** * Voltage: 12 V DC * Speed: 5 rpm * Torque: 20 N·cm * Shaft Diameter: 4 mm === Task === Implement a program that rotates the DC motor forward at full speed for 5 seconds, pauses for 2 seconds, reverses at half speed for 5 seconds, and then pauses again for 2 seconds, repeating this sequence. === Steps === = Step 1: Define Pins = const int AIN1 = 12; const int AIN2 = 11; const int PWMA = 3; = Step 2: Setup = Configure pin modes: void setup() { pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT); pinMode(PWMA, OUTPUT); } = Step 3: Control Loop = Implement motor control logic: void loop() { // Rotate Forward at full speed digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW); analogWrite(PWMA, 255); // Full speed delay(5000); // 5 seconds // Stop motor digitalWrite(AIN1, LOW); digitalWrite(AIN2, LOW); delay(2000); // 2 seconds pause // Rotate Reverse at half speed digitalWrite(AIN1, LOW); digitalWrite(AIN2, HIGH); analogWrite(PWMA, 128); // Half speed delay(5000); // 5 seconds // Stop motor again digitalWrite(AIN1, LOW); digitalWrite(AIN2, LOW); delay(2000); // 2 seconds pause } === Validation === Observe the motor rotating in both directions with clear speed differences. Verify pauses between rotations. === Troubleshooting === If the motor doesn’t rotate or behaves erratically: * Check power supply (motor needs 12V DC). * Confirm motor driver connections. * Verify Arduino pin assignments match the code. ==== Controlling a Servo motor ==== This scenario demonstrates how to control the position of a servo motor using Arduino. Servo motors are used for precise angular positioning in robotics and automation. In this lab, a thermistor sensor is attached to the servo motor arm in a way that it can be moved on the front of the fan (DC motor). By triggering the fan, it is possible to change the temperature and so initiate output changes on thermistor readings. {{:en:iot-open:practical:hardware:taltech:arduino:scenarios:servo.jpg?200|}} === Prerequisites === * Familiarize yourself with the Arduino hardware reference. * Install the built-in Arduino Servo library. * Understand basic PWM concepts for servo control. === Hardware Connections === ^ Component ^ Arduino Pin ^ | Servo Signal | 10 (PWM) | Servo specifications: * Standard miniature servo (SG90 or similar) * Voltage: Typically 5V * PWM frequency: 50 Hz * Rotation range: ~0° to 180° === Task === Write a program that rotates the servo sequentially to 0°, 90°, and 180°, pausing for one second at each position. === Steps === = Step 1: Include Servo Library = #include = Step 2: Initialize Servo Object = Declare a servo object and assign it a pin: Servo myServo; const int servoPin = 10; = Step 3: Setup = Attach the servo object to the servo pin: void setup() { myServo.attach(servoPin); } = Step 4: Control Loop = Program the servo rotation sequence: void loop() { myServo.write(0); // Move to 0 degrees delay(1000); // Wait 1 second myServo.write(90); // Move to 90 degrees delay(1000); // Wait 1 second myServo.write(180); // Move to 180 degrees delay(1000); // Wait 1 second } === Validation === Observe the servo motor rotate precisely to the angles 0°, 90°, and 180°, pausing clearly at each position. === Troubleshooting === If the servo does not move or if jittering occurs: * Check the servo connections (signal to pin 10, power, and ground). * Ensure power supply is stable (servo typically requires 5V). * Verify servo library inclusion and pin assignment. ==== Temperature Measurement Using Thermistor ==== This scenario demonstrates how to measure temperature using a thermistor (temperature-sensitive resistor) connected to an Arduino Uno. The temperature is calculated using the Steinhart-Hart equation. === Prerequisites === * Familiarize yourself with the Arduino hardware reference. * Basic understanding of analog sensor readings and voltage dividers. * Install LiquidCrystal library for LCD display. === Hardware Connections === ^ Component ^ Arduino Pin ^ | Thermistor | A5 (Analog) | | LCD | RS=8, EN=9, D4=4, D5=5, D6=6, D7=7 | === Task === Measure temperature using a thermistor and display the results (ADC reading, voltage, resistance, and temperature in Celsius) on the LCD. === Steps === = Step 1: Include Libraries = #include #include = Step 2: Initialize LCD = Declare LCD pins and initialize: LiquidCrystal lcd(8, 9, 4, 5, 6, 7); const int sensorPin = A5; = Step 3: Setup = Set LCD dimensions and initial message: void setup() { lcd.begin(16, 2); lcd.print("Temperature"); delay(1000); } = Step 4: Main Loop = Read sensor value, calculate temperature, and display on LCD: void loop() { readThermistor(analogRead(sensorPin)); delay(1000); lcd.clear(); } void readThermistor(int RawADC) { double Temp; long Resistance; // Calculate resistance Resistance = ((10240000 / RawADC) - 10000); // Display ADC and voltage lcd.setCursor(0, 0); lcd.print("AD="); lcd.print(RawADC); lcd.setCursor(8, 0); lcd.print("U="); lcd.print(((RawADC * 5.0) / 1024.0), 3); // Display resistance lcd.setCursor(0, 1); lcd.print("R="); lcd.print(Resistance); // Calculate temperature (Steinhart-Hart) Temp = log(Resistance); Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp)) * Temp); Temp = Temp - 273.15; // Convert Kelvin to Celsius // Display temperature lcd.setCursor(8, 1); lcd.print("T="); lcd.print(Temp); } === Validation === LCD displays accurate ADC values, voltage readings, resistance, and temperature in Celsius. Typical room temperature should range between 19-24 °C. === Troubleshooting === If temperature readings are incorrect or unstable: * Check wiring and verify analog input connection (A5). * Verify the correct resistor value in the voltage divider. * Ensure accurate Steinhart-Hart constants are used. ====== RTU Nordic Semiconductors nRF Laboratory Hardware Reference ====== ===== nRF Laboratory Scenarios ===== ====== RobotNest Nordic Semiconductors nRF Laboratory Hardware Reference ====== ===== nRF Laboratory Scenarios ===== ==== Use of RGB LEDs === This scenario presents how to handle the brightness control of the tri-coloured LEDs. Both LEDs are electrically bound and cannot be controlled independently. Those LEDs have 3 colour channels, controlled independently: R (Red), G (Green) and B (Blue). Mixing of those colours creates other ones, such as pink and violet. Each R G B channel can be controlled with a separate GPIO to switch it on or off or control brightness using a PWM signal, as presented in this tutorial. ==== Step 1 === Define the necessary pins and functions: /* RGB LEDS */ #define RED_PIN 14 #define GREEN_PIN 15 #define BLUE_PIN 16 void setRGB(int red, int green, int blue); ==== Step 2 === Define pins as output: void setup() { Serial.begin(152000); pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT); } ==== Step 3 === Add a function at the end of the code: void setRGB(int red, int green, int blue) { analogWrite(RED_PIN, green); analogWrite(GREEN_PIN, blue); analogWrite(BLUE_PIN, red); } ==== Step 4 === Write some code for the LED's (circle through red, green, blue light): void loop() { setRGB(255, 0, 0); delay(1000); setRGB(0, 255, 0); delay(1000); setRGB(0, 0, 255); delay(1000); } ==== Example of NRF52 with OLED === Reference: https://www.waveshare.com/wiki/1.5inch_OLED_Module #include #include #define LED_PIN 12 #define LED_COUNT 8 Adafruit_NeoPixel strip(LED_COUNT, LED_PIN); void setup() { Serial.begin(115200); strip.begin(); strip.show(); } void loop() { strip.clear(); strip.setPixelColor(1,0,0,255); strip.show(); delay(1000); strip.clear(); strip.setPixelColor(6,0,0,255); strip.show(); delay(1000); } ==== NRF52 Neopixel === Reference: https://github.com/adafruit/Adafruit_NeoPixel #include #include #include #define LED_PIN 17 #define LED_COUNT 8 Adafruit_NeoPixel strip(LED_COUNT, LED_PIN); void setup() { Serial.begin(115200); strip.begin(); strip.show(); } void loop() { strip.clear(); strip.setPixelColor(1,0,0,255); strip.show(); delay(1000); strip.clear(); strip.setPixelColor(6,0,0,255); strip.show(); delay(1500); } ====== RobotNest ESP32 Laboratory Hardware Reference ====== ===== ESP32 Laboratory Scenarios ===== ==== Use of RGB LEDs === This scenario presents how to handle the brightness control of the tri-coloured LEDs. Both LEDs are electrically bound and cannot be controlled independently. Those LEDs have 3 colour channels, controlled independently: R (Red), G (Green) and B (Blue). Mixing of those colours creates other ones, such as pink and violet. Each R G B channel can be controlled with a separate GPIO to switch it on or off or control brightness using a PWM signal, as presented in this tutorial. ==== Step 1 === Define the necessary pins and functions: /* RGB LEDS */ #define RED_PIN 9 #define GREEN_PIN 10 #define BLUE_PIN 11 void setRGB(int red, int green, int blue); ==== Step 2 === Define pins as output: void setup() { Serial.begin(152000); pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT); } ==== Step 3 === Add a function at the end of the code: void setRGB(int red, int green, int blue) { analogWrite(RED_PIN, green); analogWrite(GREEN_PIN, blue); analogWrite(BLUE_PIN, red); } ==== Step 4 === Write some code for the LED's (circle through red, green, blue light): void loop() { setRGB(255, 0, 0); delay(1000); setRGB(0, 255, 0); delay(1000); setRGB(0, 0, 255); delay(1000); } ==== ESP32 Neopixel === Reference: https://github.com/adafruit/Adafruit_NeoPixel #include #include #define LED_PIN 12 #define LED_COUNT 8 Adafruit_NeoPixel strip(LED_COUNT, LED_PIN); void setup() { Serial.begin(115200); strip.begin(); strip.show(); } void loop() { strip.clear(); strip.setPixelColor(1,0,0,255); strip.show(); delay(1000); strip.clear(); strip.setPixelColor(6,0,0,255); strip.show(); delay(1500); } ==== ESP32 LCD 16x2 === This is an example of the lcd running, check the reference for more things to do: https://github.com/adafruit/Adafruit_LiquidCrystal === Prequisits == Platform.ini lib_deps = adafruit/Adafruit LiquidCrystal@^2.0.2 === Example == #include #include #define LCD_RS 48 #define LCD_ENABLE 47 #define LCD_D4 34 #define LCD_D5 33 #define LCD_D6 26 #define LCD_D7 21 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); void setup() { if (!lcd.begin(16, 2)) { Serial.println("Could not init LCD"); while(1); } Serial.println("LCD ready."); // Print a message to the LCD. lcd.print("IOT-OPEN"); } void loop() { lcd.setCursor(0, 1); // print the number of seconds since reset: lcd.print(millis()/1000); delay(1000); } ==== ESP32 Fan control === Example of how to turn the fan on and off #include #define FAN_PIN 18 void setup() { pinMode(FAN_PIN, OUTPUT); } void loop() { digitalWrite(18, HIGH); delay(2500); digitalWrite(18, LOW); delay(5000); } ==== ESP32 DS18B20 === This is an example of the ds18b20 running and showcasing temperature using the 16x2 lcd. === Prequisits == Platform.ini lib_deps = adafruit/Adafruit LiquidCrystal@^2.0.2 milesburton/DallasTemperature@^3.11.0 === Example == #include #include #include /* DS18B20 */ #define ONE_WIRE_BUS 14 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); #define LCD_RS 48 #define LCD_ENABLE 47 #define LCD_D4 34 #define LCD_D5 33 #define LCD_D6 26 #define LCD_D7 21 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); void setup() { if (!lcd.begin(16, 2)) { Serial.println("Could not init LCD"); while(1); } Serial.println("LCD ready."); // Print a message to the LCD. lcd.print("IOT-OPEN"); /* ds18b20 setup */ sensors.begin(); } void loop() { sensors.requestTemperatures(); lcd.setCursor(0, 1); // print the number of seconds since reset: lcd.print(sensors.getTempCByIndex(0)); delay(1000); } ==== ESP32 Servo === This is an example of the servo running sweep program. === Prequisits == Platform.ini lib_deps = deneyapkart/Deneyap Servo@^1.1.1 === Example == #include /* * ServoMotor örneği, * D9 pinine bağlanan servo motorun mili 60 derece dönmektedir. */ #include // Deneyap Servo kütüphanesi eklenmesi Servo myservo; // Servo için class tanımlanması int pos = 0; void setup() { myservo.attach(13); // Servo motorun D9 pinine bağlanması /*attach(pin, channel=0, freq=50, resolution=12) olarak belirlenmiştir. Kullandığınız motora göre değiştirebilirsiniz */ } void loop() { for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees // in steps of 1 degree myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees myservo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } // Servo motorun milinin 60 derece dönmesi } ==== ESP32 DHT11 === This is an example of the dht reading and displaying on screen. === Prequisits == Platform.ini lib_deps = adafruit/DHT sensor library@^1.4.6 === Example == #include #include #include /* DHT11 */ #define DHTPIN 15 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); #define LCD_RS 48 #define LCD_ENABLE 47 #define LCD_D4 34 #define LCD_D5 33 #define LCD_D6 26 #define LCD_D7 21 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); void setup() { Serial.begin(152000); if (!lcd.begin(16, 2)) { Serial.println("Could not init LCD"); while(1); } Serial.println("LCD ready."); // Print a message to the LCD. lcd.print("IOT-OPEN"); /* dht11 setup */ dht.begin(); } void loop() { //dht11 code float h = dht.readHumidity(); float t = dht.readTemperature(); lcd.setCursor(0, 0); // First line lcd.print("H: "); lcd.print(h); lcd.print("%"); lcd.setCursor(0, 1); // Second line lcd.print("T: "); lcd.print(t); lcd.print("C"); delay(1000); } ==== ESP32 BMP280 === This is an example of the BMP280 reading and displaying on screen. === Prequisits == There is no necessary prequisits, the wire library will be pulled in automatically. === Example == #include #include #include #include /* BMP280 */ Adafruit_BMP280 bmp; // I2C #define SDA_PIN 17 #define SCL_PIN 16 #define LCD_RS 48 #define LCD_ENABLE 47 #define LCD_D4 34 #define LCD_D5 33 #define LCD_D6 26 #define LCD_D7 21 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); void setup() { Serial.begin(152000); // Initialize I2C on custom pins Wire.begin(SDA_PIN, SCL_PIN); // Initialize LCD if (!lcd.begin(16, 2)) { Serial.println("Could not init LCD"); while (1); } Serial.println("LCD ready."); lcd.print("IOT-OPEN"); // Initialize BMP280 with custom I2C (Wire) instance if (!bmp.begin(0x76)) { Serial.println(F("Could not find a valid BMP280 sensor, check wiring or address!")); while (1) delay(10); } bmp.setSampling(Adafruit_BMP280::MODE_FORCED, Adafruit_BMP280::SAMPLING_X2, Adafruit_BMP280::SAMPLING_X16, Adafruit_BMP280::FILTER_X16, Adafruit_BMP280::STANDBY_MS_500); } void loop() { float h = bmp.readPressure(); float t = bmp.readTemperature(); if (bmp.takeForcedMeasurement()) { Serial.print(F("Temperature = ")); Serial.print(bmp.readTemperature()); Serial.println(" *C"); Serial.print(F("Pressure = ")); Serial.print(bmp.readPressure()); Serial.println(" Pa"); Serial.print(F("Approx altitude = ")); Serial.print(bmp.readAltitude(1013.25)); // Adjust to local forecast Serial.println(" m"); } lcd.setCursor(0, 0); lcd.print("P: "); lcd.print(h / 100.0); // Convert Pa to hPa lcd.print("hPa"); lcd.setCursor(0, 1); lcd.print("T: "); lcd.print(t); lcd.print("C"); delay(1000); } } ==== ESP32 TCS34725 === This is an example of the TCS34725 reading rgb color code from neopixel, and displaying it on lcd === Prequisits == lib_deps = adafruit/Adafruit LiquidCrystal@^2.0.2 === Example == #include #include #include #include #include /* NEOPIXEL */ void colorWipe(uint32_t color, int wait); #define LED_PIN 12 #define LED_COUNT 8 Adafruit_NeoPixel strip(LED_COUNT, LED_PIN); #define SDA_PIN 17 #define SCL_PIN 16 #define LCD_RS 48 #define LCD_ENABLE 47 #define LCD_D4 34 #define LCD_D5 33 #define LCD_D6 26 #define LCD_D7 21 static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); /* TCS34725 */ #define commonAnode true byte gammatable[256]; Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X); void setup() { Serial.begin(152000); // Initialize I2C on custom pins Wire.begin(SDA_PIN, SCL_PIN); // Initialize LCD if (!lcd.begin(16, 2)) { Serial.println("Could not init LCD"); while (1); } Serial.println("LCD ready."); lcd.print("IOT-OPEN"); strip.begin(); strip.show(); /* TCS34725 setup */ if (tcs.begin()) { //Serial.println("Found sensor"); } else { Serial.println("No TCS34725 found ... check your connections"); while (1); // halt! } // it helps convert RGB colors to what humans see for (int i=0; i<256; i++) { float x = i; x /= 255; x = pow(x, 2.5); x *= 255; if (commonAnode) { gammatable[i] = 255 - x; } else { gammatable[i] = x; } //Serial.println(gammatable[i]); } } back-ref-format : none void loop() { strip.clear(); colorWipe(strip.Color(0, 0, 255), 50); strip.show(); //tcs code float red, green, blue; tcs.setInterrupt(false); delay(60); // takes 50ms to read tcs.getRGB(&red, &green, &blue); tcs.setInterrupt(true); lcd.setCursor(0, 1); lcd.print("r: "); lcd.print(int(red)); lcd.print("g: "); lcd.print(int(green)); lcd.print("b: "); lcd.print(int(blue)); delay(3000); strip.clear(); colorWipe(strip.Color(255, 0, 0), 50); strip.show(); tcs.setInterrupt(false); delay(60); // takes 50ms to read tcs.getRGB(&red, &green, &blue); tcs.setInterrupt(true); lcd.setCursor(0, 1); lcd.print("r: "); lcd.print(int(red)); lcd.print("g: "); lcd.print(int(green)); lcd.print("b: "); lcd.print(int(blue)); delay(3000); } //for neopixel void colorWipe(uint32_t color, int wait) { for(int i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, color); strip.show(); delay(wait); } }