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.
This page keeps track of the content reviews and versions done as a continuous maintenance process
This book and its offshoots were prepared to provide comprehensive information about the Internet of Things on the engineering level.
Its goal is to introduce IoT to bachelor students, master students, technology enthusiasts and engineers willing to extend their current knowledge with the latest hardware and software achievements in the scope of the Internet of Things.
This book is also designated for teachers and educators willing to prepare a course on IoT.
We (Authors) assume that persons willing to study this content possess some general knowledge about IT technology, i.e. understand what an embedded system is, know the general idea of programming (in C/C++) and are aware of wired and wireless networking as it exists nowadays.
This book constitutes a comprehensive manual for IoT technology; however, it is not a complete encyclopedia nor exhausts the market. The reason for it is pretty simple – IoT is so rapidly changing technology that new devices, ideas and implementations appear daily. Once you read this book, you can quickly move over the IoT environment and market, easily chasing ideas and implementing your IoT infrastructure.
We also believe this book will help adults that took their technical education some time ago to update their knowledge.
We hope this book will let you find brilliant ideas in your professional life, see a new hobby, or even start an innovative business.
Playing with real or virtual hardware and software is always fun, so keep going!
ThisBook was implemented under the Erasmus+ KA2 projects:
Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views only of the author, and the Commission cannot be held responsible for any use which may be made of the information contained therein.
Copyright Notice
This content was created by the IOT-OPEN.EU Consortium: 2016–2019 and IOT-OPEN.EU Reloaded Consortium 2022-2025.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.
In case of commercial use, please contact IOT-OPEN.EU Reloaded Consortium representative.
Here comes the Internet of Things. The name that recently makes red-hot people in business, researchers, developers, geeks and … students. The name that non-technology related people consider a kind of magic and even a danger to their privacy. The EU set the name as one of the emerging technologies and estimated the worldwide market will hit well over 500 billion US dollars in 2022, while the number of IoT devices in 2030 is expected to be over 3.2 billion.
What is IoT (Internet of Things), then? Surprisingly, the answer is not straightforward.
To simplify the selection of different topics, a simple colour coding was introduced, indicating the skills required to cover particular topics. Colour codes are organized in the form of colour bars enclosing chapter titles.
Explanation:
Let us roll back to the 1970s first. In 1973 the first RFID device was patented. This device was the key enabling technology even if it does not look nor remind modern IoT devices. The low power (actually here passive) solution with a remote antenna large enough to collect energy from the electromagnetic field and power the device brought an idea of uniquely identifiable items. That somehow mimics well-known EAN barcodes and the evolution used nowadays, like QR codes, but every single thing has a different identity here. In contrast, EAN barcodes present a class of products, not an individual one. The possibility to identify a unique identity remotely became fundamental to the IoT as it's known today. RFID is not the only technology standing behind IoT. In the 1990s, the rapid expansion of wireless networks, including broadband solutions like cellular-based data transfers with their consequent generations, enabled connecting devices in various, even distant geographical locations. Parallelly an exponential increase in the number of devices connected to the global Internet network was observed, including the smartphone revolution that started around mid the first decade of the XXI century. On the hardware level, microchips and processors became physically smaller and more energy efficient yet offering growing computing capabilities and memory size increase, along with significant price drops. All those facts drove the appearance of small, network-oriented, cheap and energy-efficient electronic devices. In recent years, the development of efficient AI technologies even boosted IoT applications.
The phrase “Internet of Things” was used for the first time in 1999 by Kevin Ashton – an expert on digital innovation. Formally IoT was introduced by the International Telecommunication Union (ITU) in the ITU Internet report in 2005 [1]. The understanding and definitions of IoT changed over the years, but now all agree that this cannot be seen as a technology issue only. According to IEEE “Special Report: Internet of Things” [2] released in 2014, IoT is:
IEEE Definition of IoT |
---|
A network of items – each embedded with sensors – connected to the Internet. |
It relates to the physical aspects of IoT only. The Internet of Things also addresses other aspects that cover many areas [3]:
IEEE, as one of the most prominent standardisation organisations, also works on standards related to the IoT. The primary document is IEEE P2413™ [4]. It covers the technological architecture of IoT as three-layered: sensing at the bottom, networking and data communication in the middle, and applications on the top. It is essential to understand that IoT systems are not only small, local-range systems. ITU-T has defined IoT as:
ITU-T Definition of IoT |
---|
A global infrastructure for the information society, enabling advanced services by interconnecting (physical and virtual) things based on existing and evolving interoperable information and communication technologies. |
In the book [5] by European Commission, we can read a similar description of what IoT is: “The IoT is the network of physical objects that contain embedded technology to communicate and sense or interact with their internal states or the external environment.” IoT impacts many areas of human activity: manufacturing, transportation, logistics, healthcare, home automation, media, energy saving, environment protection and many more. In this course, we will consider the technical aspects mainly.
In the IoT world, the “thing” is always equipped with some electronic element that can be as simple as the RFID tag, an active sensor sending data to the global network, or an autonomous device that can react to environmental changes. In CERP-IoT book “Visions and Challenges” [6] in the context of “Internet of Things” a “thing” could be defined as:
CERP-IoT Definition of “Thing” |
---|
A real/physical or digital/virtual entity that exists and moves in space and time and can be identified. Assigned identification numbers, names and location addresses commonly identify things. |
It is quite easy to find other terms used in the literature like “smart object”, “device”, or “nodes” [7].
One can imagine that almost everything in our surroundings is tagged with an RFID element. They do not need a power supply; they respond with a short message, usually containing the identification number. Modern RFID can achieve 6 to 7 meters of the range. Using the active RFID reader, we can quickly locate lost keys and know if we still have the butter in the fridge and in which wardrobe there is our favourite t-shirt.
If the “thing” includes the sensor, it can send interesting data about current conditions. We can sense environmental parameters like temperature, humidity, air pollution, pressure, localisation data, water level, light, noise, and movement. Using different methods and protocols, this data can be sent to the central collector that connects to the Internet and the database or cloud. There the data can be processed, and Artificial Intelligence algorithms can be used to decide actions that could be taken in different situations. Active things can also receive control signals from the central controller to control the environment: turn on/off the heating or light, water flowers, and turn on the washing machine when there is enough sunlight to generate the required electricity or charge your electric car.
This thing does not even require the controller to realise the proper decision. An autonomous vacuum cleaner can clean our house when it detects that we aren't home and the floor needs cleaning. The fridge can order our favourite beverage once the last bottle is almost empty.
Sensor Networks are a subset of the IoT devices used as a collaborative solution to grab data and send it for further processing. Opposite to the general IoT devices, Sensor Network devices do not have any actuators that can apply an action to the external world. The data flow is unidirectional, then.
IoT systems and embedded systems share almost the same domain. They frequently use the same microcontrollers, sensors and actuators, development software and even programming models. What differs between IoT and embedded systems is that IoT, on its principles, uses communication to send and receive data outside of its instance, while embedded systems do not have to. Embedded systems do not have to be network-enabled, and they do not have a unique identity frequently, while IoT devices do. Moreover, IoT systems are complex and multilayered, often introducing cloud-based parts, while embedded systems are stand-alone devices. Shortly we can say that an IoT device is network enabled embedded system.
In this chapter, there is an approach to describe modern technologies that appeared in the last few years, enabling the idea of IoT to be widely implementable. In the [8] one can read that “The confluence of efficient wireless protocols, improved sensors, cheaper processors and a wave of startups and established companies made the concept of the IoT mainstream”. Similar analysis has been done in [9] where authors write that “the latest developments in RFID, smart sensors, communication technologies and Internet protocols enable the IoT”. RFID and smart sensors need the microprocessor system to read, convert the data into digital format, and send it to the Internet using the communication protocol. This process can be done by small- and medium-scale computer (embedded) systems. These are essential elements of technologies used in IoT systems.
In recent years one can observe rapid growth in the field of microprocessors. It includes not only the powerful desktop processors but also microcontrollers – elements that are used in small-scale embedded systems. We can also notice the popularity of microprocessor systems that can be easily integrated with other factors, like sensors, and actuators, connected to the network. Essential is also the availability of programming tools and environments supported by different companies and communities. An excellent example of such a system is Arduino. Those devices are low-power, constrained devices, usually battery-powered and, in most cases, communicating wirelessly.
The same growth can be observed in the advanced constructions comparable to low-end computers. They have more powerful processors, memory and networking connectivity built-in than small-scale computer systems. They can work under the control of multitasking operating systems like Linux and Windows and embedded or real-time operating systems like FreeRTOS. Having many libraries, they can successfully work as hubs for local storage, local controllers and gateways to the Internet. Raspberry Pi and the nVidia Jetson series are examples of such systems. This category of devices frequently contains hardware accelerated (such as GPU) AI-capable solutions, i.e. nVidia Jetson Nano or Xavier series. Those devices can be battery or mains powered. Often, they are green energy powered: i.e. with a larger backup battery and energy harvesting solution (such as solar panel).
Nowadays, the Internet is (almost) everywhere. There are lots of wireless networks available in private and public places. The price of cellular access (3G/4G/5G) is low, offering a suitable data transfer performance. Connecting the “thing” to the Internet has never been so easy.
The primary paradigm of IoT is that every unit can be individually addressed. With the addressing scheme used in IPv4, it wouldn't be possible. IPv4 address space delivers “only” 4 294 967 296 of unique addresses (2^32). If you think it's a considerable number, imagine that every person in the world has one IP-connected device – IPv4 covers about half of the human population. The answer is IPv6 with a 128-bit addressing scheme that gives 3.4 × 10^38 addresses. It will be enough even if everyone has a billion devices connected to the Internet.
IoT devices generate the data to be stored and processed somewhere. If there is a couple of sensors, the amount of data is not very big, but if there are thousands of sensors generating data hundreds of times every second. The cloud can handle it – the massive place for the data with tools and applications ready to help with data processing. Some big, global clouds are available for rent, offering not only storage but also Business Intelligence tools, Artificial Intelligence analytic algorithms. There are also smaller private clouds created to cover the needs of one company only. Many universities have their own High-Performance Computing Centre.
Many people want to be connected to the global network everywhere, anytime, having their “digital twin” with them. It is possible now with small, powerful mobile devices like smartphones. Smartphones are also elements of the IoT world, being together sensors, user interfaces, data collectors, wireless gateways to the Internet, and everything with mobility features.
The technologies we mentioned here are the most recognisable. Still, there are many others, more minor, described only in the technical language in some standard description document, hidden under the colourful displays between large data centres, making our IoT world operable. In this book, we will describe some of them.
Technology development instantly shifts devices between categories. A border between Fog and Edge class devices is conventional; many can share both worlds. It depends on their purpose, application and performance configuration; thus, i.e. Raspberry Pi can be an end-node (Edge) class device and a Fog class, working as a data aggregator and analytical device.
IoT has already been defined as a network of physical things or devices that might include sensors or simple data processing units, complex actuators, and significant hybrid computing power. Today IoT systems have transitioned from being perceived as sensor networks to smart-networked systems capable of solving complex tasks in mass production, public safety, logistics, medicine and other domains, requiring a broader understanding and acceptance of current technological advancements, including advanced data processing that includes AI.
Since the very beginning of sensor networks, one of the main challenges has been data transport and data processing, where significant efforts have been put by the ICT community towards service-based system architectures. However, The current trend already provides considerable computing power even in small mobile devices. Therefore, the concepts of future IoT already shifted towards smarter and more accessible IoT devices, and data processing has become possible closer to the Fog and Edge.
Cloud-based computing is a relatively well-known and adequately employed paradigm where IoT devices can interact with remotely shared resources such as data storage, data processing, data mining and other services are unavailable to them locally because of the constrained hardware resources (CPU, ROM, RAM) or energy consumption limits. Although the cloud computing paradigm can handle vast amounts of data from IoT clusters, the transfer of extensive data to and from cloud computers presents a challenge due to limited bandwidth[10]. Consequently, there is a need to process data near data sources, employing the increasing number of smart devices with enormous processing power and a rising number of service providers available for IoT systems.
Fog computing addressed the bottlenecks of cloud computing regarding data transport while providing the needed services to IoT systems.
It is a new trend in computing that aims to process the data near the data source. Fog computing pushes applications, services, data, computing power, and decision-making away from the centralised nodes to the logical extremes of a network. Fog computing significantly decreases the data volume that must be moved between end devices and the cloud.
Fog computing enables data analytics and knowledge generation at the data source. Furthermore, the dense geographic distribution of fog helps to attain a better-localised accuracy for many applications than the cloud processing of the data [11].
The recent development of energy-efficient hardware with AI acceleration enters the fog class of the devices, putting Fog Computing in the middle of the interest of IoT application development and extending new horizons to them. Fog Computing is more energy efficient than raw data transfer to the cloud and back, and in the current scale of the IoT devices, the application is meant for the future of the planet Earth. Fog Computing usually also brings a positive impact on IoT security, i.e. sending to the cloud preprocessed and depersonalised data and providing distributed computing capabilities that are more attack resistant.
Recent development in hardware, power efficiency and a better understanding of the IoT data nature, including such aspects as, i.e. privacy and security, led to solutions where data is being processed and preprocessed right to their source in the Edge class devices. Edge data processing on end-node IoT devices is crucial in systems where privacy is essential and sensitive data is not to be sent over the network (i.e. biometric data in a raw form). Moreover, distributed data processing can be considered more energy efficient in some scenarios where, i.e. extensive, power-consuming processing can be performed during green energy availability.
According to [12], Cognitive IoT, besides a proper combination of hardware, sensors and data transport, comprises cognitive computing, which consists of the following main components:
Usually, cognitive IoT systems or C-IoT are expected to add more resilience to the solution. Resilience is a complex term and is differently explained under different contexts; however, there are standard features for all resilient systems. As a part of their resilience, C-IoT should be capable of self-failure detection and self-healing that minimises or gradually degrades the system's overall performance. In this respect, the non-resilient system fails or degrades in a step-wise manner. In case of security issues, that system should be able to change its security keys, encryption algorithms and take other measures to cope with the detected threats. Self-optimisation abilities are often considered part of the C-IoT feature list to provide more robust solutions. Recent development in the Fog and Edge class devices and the efficient software leverage cognitive IoT Systems to a new level.
All three approaches, from cloud to cognitive systems, focus on adding value to IoT devices, system users and related systems on-demand. Since market and technology acceptance of mobile devices is still growing, and the amount of produced data from those devices is growing exponentially, mobility as a phenomenon is one of the main driving forces of the technological advancements of the near future.
Data management is a critical task in IoT. Due to the high number of devices (things) already available, that is tens of billions, and considering the data traffic generated by each of them through, i.e. sensor networks, infotainment (soft news) or surveillance systems, mobile social network clients, and so on, we are now even beyond the ZettaByte (ZB 2^70, 10^21 bytes) era. This opened up several new challenges in (IoT) data management, giving rise to data sciences and big data technologies. Such challenges have not to be considered as main issues to solve but also as significant opportunities fuelling the digital economy with new directions such as Cloudonomics [13] and IoTonomics, where data can be considered as a utility, a commodity to manage, curate, store, and trade appropriately. Therefore, properly managing data in IoT contexts is not only critical but also of strategic importance for business players as well as for users, evolving into prosumers (producers-consumers).
From a technological perspective, the main aspects of dealing with IoT data management are:
Application domains of the Internet of Things solutions are vast. Most prominent applications include (among others) [16]:
Smart Homes are one of the first examples that come to mind when discussing Internet of Things domain applications. Smart home benefits include reduced energy wastage, the quality and reliability of devices, system security, reduced cost of basic needs, etc. Some home automation examples are environmental control systems that monitor and control heating, ventilation, air conditioning and sunscreens; electrical charging of vehicles; solar panels for electrical power and hot water; ambient lighting control, smart lighting for aquaria; home cooking and food ordering; access control (doors, garage, gate); smart plant irrigation systems (both indoors and outdoors); baby monitoring; timed pet food dispensers; monitoring perishable goods (for example, in the refrigerator); household items remote monitoring (for instance, of washer cycle status); tracking and proactive maintenance scheduling (such as, i.e. electric car charging); event-triggered task execution. Home security also plays a significant role in smart homes. Examples of applications are automatic door locks, sensors for opening doors and windows, pressure, motion and infrared sensors, security cameras, notifications about security (to the owner or the police) and fitness-related applications.
In Smart City, multiple IoT-based services are applied to different areas of urban settings. The aim of the smart city is the best use of public resources, improvement of the quality of resources provided to people and reduction of operating costs of public administration [17]. A smart city can include many solutions like smart buildings, smart grids for improving energy management, smart tourism, monitoring of the state of the roads and occupation of parking lots, public transportation optimisation, public safety, environment monitoring, automatic street lighting, signalling with smart power devices, control of water levels for hydropower or flood warnings, electricity-generating devices like solar panels and wind turbines, weather monitoring stations. Transportation in smart cities may include aviation, monitoring and forecasting of traffic slowdowns, timetables and current status, navigation and route planning, as well as vehicle diagnostics and maintenance reports, remote maintenance services, traffic accident information collection, fleet management using digital tachographs, smart parking, car/bicycle sharing services [18]. IoT in transportation makes cars interconnected, particularly in the approaching autonomous vehicles era.
Smart Grid is a digital power distribution system. This system gathers information using smart meters, sensors and other devices. After these data are processed, power distribution can be adapted accordingly. Smart grids deliver sustainable, economical and secure electricity supplies efficiently.
In Precision Agriculture and Smart Farming IoT solutions can be used to monitor the moisture of the soil and conditions of the plants, control microclimate conditions and monitor the weather conditions to improve farming [19]. The goal of using IoT in agriculture is maximising the harvest, reducing operational costs, being more efficient, and reducing environmental pollution using low-cost automated solutions. An interaction between the farmer and the systems can be done using a human-machine interface. In the future smart precision farming can be a solution for such challenges as increasing worldwide demand for food, a changing climate, and a limited supply of water and fossil fuels [20].
Internet of Food integrates many of the aforementioned techniques and encompasses different stages of the food delivery chain, including smart farming, food processing, transportation, storage, retail, and consumption. It provides more safety and improved efficiency at each food production and consumption stage, including reduced waste and increased transparency.
Similar to precision agriculture, which is part of IoT in industry, Smart Factories also tend to improve manufacturing by monitoring pollutant gas emissions, locating employees and with many other solutions.
Industrial IoT and smart factories are part of the Industry 4.0 revolution. In this model, modern factories can automate complex manufacturing tasks, thanks to the Machine-To-Machine communication model, which provides more flexibility in the manufacturing process to enable personalised, short-volume product manufacturing easily.
In the healthcare and wellness, IoT applications can monitor and diagnose patients and manage people and medical resources. It allows remote and continuous monitor the vital signs of patients to improve medical care and wellness of patients [21]. An essential part of smart welfare is wearables, including wristbands and smartwatches that monitor the activity level, heart rate and other parameters. Smart healthcare includes remote monitoring, care of patients, self-monitoring, smart pills, smart home care, Real-Time Health Systems (RTHS) and many more. Medical robotics can also be part of the healthcare IoT system that includes medical robots in precision surgery or distance surgery; some robots are used in rehabilitation and hospitals (for example, Panasonic HOSPI [22]) for delivering medication, drinks, etc. to patients.
Wearables used in IoT applications should be highly energy efficient, ultra-low power and small-sized. Wearables are installed with sensors and software for data and information collected about the user. Devices used in daily life like Fitbit [23] are used to track people's health and exercise progress in previously impossible ways, and smartwatches allow to access smartphones using this device on the wrist. But wearables are not limited only to wearing them on the wrist. They can also be glasses equipped with a camera, a sports bundle attached to the shoes or a camera attached to the helmet or as a necklace [24].
[ktokarz][✓ ktokarz, 2023-08-23]Describe in general IoT MCUs: CPU, FPU, RAM, Storage (flash), GPIO, Interrupts system, DMA, built-in radio interfaces (if any).
IoT device is, in almost all cases, based on microcontroller. A microcontroller, often called a single-chip computer, is an integrated circuit that incorporates all units required to function as the computer. It includes a central processing unit (CPU), memory for programs, memory for data, inputs, outputs, timers, serial communication ports and other peripherals. Complex microcontrollers, called embedded processors, can include more processor cores, display controllers, advanced internal data transfer mechanisms (like DMA), programmable connections between modules, specialized coprocessors for ciphering and deciphering, compression and decompression, video and audio coding and decoding, and other modules. Microcontrollers are even more complex in the IoT world due to wireless networking capability. A complex microcontroller equipped with an internal radio communication module is also known as a System on Chip (SoC).
The typical microcontroller includes general-purpose units like:
Embedded Processor or System on Chip can contain also:
The CPU core is the unit that executes the main program. It controls program flow, executes general-purpose instructions, calculates addresses, and processes integer values. For fast floating point calculations, an FPU coprocessor is built-in. It executes instructions that perform calculations on real numbers and advanced mathematical functions. The program instructions are fetched from program memory, usually implemented as internal or external flash memory. Data is stored in internal data memory implemented as static RAM. If more memory is needed, some microcontrollers have a memory management unit that allows them to connect external DRAM memory. Flash memory is often used as a place for file storage. Timers and counters are units that help to generate pulses of specified length and square signals of specified frequency. They can also measure delays and synchronise the work of other modules like serial ports, converters, and displays. Timers can generate pulse width modulated signals to control the speed of motors and light brightness. Microcontrollers have digital input and output ports to connect other elements of the systems. Connecting external sensors to collect information from the surroundings and output devices to manipulate environmental parameters is possible. Analogue inputs can read the voltage value generated by simple sensors. Serial communication ports are used to connect more complex sensors and displays to communicate with the user or another computer system. An interrupt controller is a unit that automatically executes subroutines responsible for handling tasks specific to the hardware that signalled the situation that needs the processor's attention. The processor doesn’t have to waste execution time by periodically checking if there is a need to take care of the device. It helps to make the code more efficient and reliable. Supervisory units help to recover from some abnormal situations. Watchdog resets the processor in case the software hangs up. Brownout detector constantly monitors the power supply voltage. It stops the processor if the voltage is too low for proper operation to avoid execution errors, flash write errors, and other malfunctions. Supervisory interfaces like JTAG allow writing the programs into flash memory and debugging the code. Direct Memory Access (DMA) module performs memory operations without processor intervention. It is usually used for copying data blocks between memory and other peripheral units. For example, data from the network unit is stored automatically in the buffer, and the CPU is informed while the data transfer is complete.
Details of the internal construction and operation of many internal modules of popular microcontrollers are described in further chapters of this book.
IoT systems share programming paradigms with embedded systems. Each microcontroller manufacturer has its own set of tools (usually called SDK or Development Framework) that frequently contain an IDE dedicated to the platform. There are some cross-platform solutions and frameworks, however.
Programming languages include:
IoT device programming can be done on a variety of levels. Below we present the most popular models and briefly discuss their pros and cons.
The bare metal programming model is where the software developer builds firmware (usually from scratch or based on a stub generated by the SDK) and flashes it to the MCU. The MCU usually does not contain software other than technical ones necessary for starting and updating the device, i.e. a bootloader. The developer must implement all algorithms, communication, interfacing, storage, etc., on a low level. They may use 3rd party libraries to implement it, which speeds up development significantly. There is no operating system running in the background. Eventually, it comes with the firmware as part of it, as included by the developer, i.e. FreeRTOS [25].
Bare metal programming requires a good understanding of the hardware configuration of the IoT device as well as the configuration of the software development toolchain. The MCU manufacturer usually provides SDK and related tools, but there do exist middleware solutions (such as PlatformIO [26]) that significantly simplify installation.
In most cases, source code is written in C or C++ language or their combination (e.g. in the case of the STM). The development process for bare metal programming is present in the following figure 2 and its features are discussed in table 2. In short, it requires developing, compiling and uploading the firmware to the device's flash memory. Programming uses a programmer (physical or Over The Air - OTA, virtual interface). The bare metal model usually provides the capability of hardware development.
The bare metal programming model is considered the only one to enable developers to have absolute control over the hardware on a very low level. On the one hand, it brings opportunities to implement non-standard solutions and optimal code in terms of compactness and efficiency; on the other, it increases time-to-market delivery. Recent advances in development supporting tools (i.e. AI-based code generation), wide availability of the libraries, standardisation of their presence and automated management, such as, e.g. in PlatformIO Library Management [27] significantly lower this time.
Opposite to bare metal programming, script programming does not involve compilation or firmware burning into the flash memory. This programming model uses interpreted languages such as Python (actually Micropython: an edition of Python for microcontrollers dedicated to constrained devices), NodeJS, Javascript, Java, C#, etc. A virtual machine middleware (programming language interpreter) running bare metal (installed as firmware) or as a part of the operating system (if any), and the developer prepares an algorithm as a script, usually in a textual form, later uploaded and executed on the device. The middleware brings an overhead on execution; thus, this solution is intended for not so constrained IoT devices, still acceptable for Edge and quite common for Fog class. It requires much more CPU, RAM and storage than bare metal programming, has limitations from the interpreter implementation and only indirectly accesses hardware. It is not suitable for real-time solutions.
The development process for scripting programming is present in the following figure 3 and its features are discussed in table 3. In short, it requires limited SDK (or none), but debugging is complex, if possible.
This programming model is suitable wherever standard solutions are implemented and where code execution efficiency is not critical, and there is no demand for real-time operations; eventually, the IoT device is unconstrained, providing developers with decent CPU (e.g. modern ARM), RAM and storage. Note those solutions are usually less energy efficient than bare metal programming; still, they offer great flexibility in algorithm implementation, far beyond a predefined list of choices or limited configuration as presented in the following section. On the other hand, it speeds up delivery time to the market because of the ease of implementation, the lack of need to install the complex software development environment and the high level of abstraction.
Several middleware (IoT frameworks) are available for various IoT devices. This development model focuses on reconfiguring the sealed firmware delivered “as is” using some configuration interface or script (or both). Eventually, modifying and recompiling it yourself is possible if it is open source. Still, the process is usually very complex, and understanding all relations and development toolchain sometimes is more complicated than developing a solution from scratch as a bare metal. Proprietary frameworks do not bring this opportunity at all and are delivered “as is” with a predefined set of features. The development is limited to reconfiguring the elements from simply switching them on and off through setting up access and credentials, even up to the GPIO assignment. This usually does not bring capabilities to modify the algorithm, eventually to choose a behaviour from the predefined list proposed by the middleware author. Such a model does not bring debugging capabilities; finally, simple tracking with error codes and log files (if at all). Moreover, in many scenarios, middleware is dependent on some external resources (i.e. authorisation via a cloud or firmware updates delivered with this channel).
The development process for the middleware configuration model is present in the following figure 4 and its features are discussed in table 4.
Configuration range varies among IoT frameworks but commonly requires compatible hardware. Proprietary firmware provides sealed configuration software and encryption; thus, it virtually excludes any non-standard modifications or makes them very complex. IoT hardware used to be compatible with more than one firmware, and proprietary ones can be replaced with another open source, i.e. Tasmota [28]. Configuration in proprietary middleware scenarios can be provided indirectly via a cloud solution that raises serious questions about privacy (i.e. configuring your private WiFi router credentials via a public or 3rd party cloud, not directly to the device).
In the beginning, it is essential to distinguish an IoT Framework that is a set of tools, firmware for a variety of devices, sometimes also hardware, delivered as is and providing developers with configuration capabilities on the high abstraction level from the Programming Framework that is related to the low-level programming, here in C/C++, referred to as an SDK. SDK tends to be a narrower definition than a programming framework as the former one contains not only SDK but also tools, development toolchain and code organisation rules.
This chapter presents and discusses programming frameworks (SDKs and source code organisation) that define how the IoT code is organised on the low level in the Bare Metal programming model for Edge class devices.
Almost every MCU (microchip/microcontroller) vendor develops its own SDK, providing programmers with a specific programming framework. It is worth nothing to mention that, in many cases, it follows the general programming construction of the source code for C or C++, such as below:
int main() { std::cout << "Hello IoT!"; return 0; }
A common approach is to use a GUI to automate the generation of the source code stub that contains the hardware-specific configuration, i.e. timers, GPIOs, and interrupts, to avoid monotonous and complex tasks and speed up time to market.
Still, as hardware differs, it is particular for each platform, and usually, software development requires a rigorous approach to inject user-specific code only in predefined locations. Otherwise, it may break source code or even delete it when re-generating configuration using SDK tools and automation. Sample main()
function for the STM32 MCU is presented below. Developers are intended to fill their code only in predefined areas, such as starting from USER CODE BEGIN Init
and finishing before USER CODE END Init
; otherwise, the source code will be gone when updating the configuration:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ MX_GPIO_Init(); MX_LPUART1_UART_Init(); MX_NVIC_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { nUARTBufferLen = sprintf((char*)tUARTBuffer, "Hello World!\n\r"); HAL_UART_Transmit_IT(&hlpuart1, tUARTBuffer, nUARTBufferLen); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Studying specific platforms is time-consuming, and as each vendor has its own approach, knowledge and source codes are usually not portable between microcontrollers.
Specific frameworks for hardware vendors are (among others):
Typical C++ code, as presented above, is a single-pass execution. On the other hand, IoT devices used to work infinitely, handling their duties such as reading sensors, communicating over the network, sending and receiving data, routing messages and so on, thus, requiring setting up an infinite while (1)
loop for processing. Many tasks need to be done in parallel, so it is common to include a task scheduling mechanism to run multiple tasks asynchronously. A common is to use the FreeRTOS [29] or its modified versions for the specific hardware platform provided by the hardware vendor, i.e. as in the case of the ESP32 [30] to provide support for multicore MCUs.
Observing the list of software frameworks above, one can easily find that many platforms have common frameworks, but the Arduino framework is present for all of them. Arduino framework is a cross-platform approach providing a slightly higher level of abstraction over dedicated software frameworks, and it is the most popular among hobbyists, students, professionals and even researchers at the moment. Arduino Framework is a reasonable balance between uniform code organisation and elements of cross-hardware HAL, still bringing opportunities to access hardware on a low level and get the advantage of the advanced features of modern IoT microcontrollers such as, e.g. power management. Most hardware vendors support this framework natively, and it has become almost an industry standard. Some advanced hardware control may require integration or other native frameworks, anyway. Still, the Arduino framework has real-time capacity. It is powerful and flexible enough to handle most IoT-related tasks, and most of all, it has excellent community support with dozens of software libraries, examples and applications worldwide.
A dummy C/C++ code for the Arduino framework looks as follows:
void setup() { } void loop() { }
The void setup()
function is executed only once after the microcontroller reboots. Its purpose is to initialise, instantiate objects, read configuration, check working conditions, and so on: generally, all tasks that are to be executed only once in a work cycle of the IoT device.
The void loop()
function is executed in a loop automatically and infinitely once a single pass is finished. Its purpose is to implement repeating tasks such as periodical reading of a sensor and sending the data to the cloud. There is no need to implement a dummy while(1)
inside the loop()
; moreover, it is usually not advised or even forbidden. It is because, for every execution of the loop()
statement, many other tasks, such as handling communication, may be executed once. Making a single pass of the loop()
function infinite (i.e. with implementing an infinite while(1)
loop, could cause starvation of the other underlying processes the framework handles, such as network communication, embedded protocols handling, etc.).
The book presents code and examples in the Arduino framework context for Edge class devices but also for Fog class devices (scripting). Wherever other framework is used, it will be clearly stated. Note, following introduction to the C and C++ programming and task handling contents, are universal and can be applied to the other frameworks, whether directly or indirectly, with some adaptation on the code level.
Software development in the bare metal model requires a development toolchain installed on the developer's computer. The vendor of the MCU usually provides a set of tools. This set frequently includes a dedicated compiler, linker, library management tools, configuration tools, debugger software, etc. These tools are command lines in most cases. Using a GCC [31] C/C++ compiler is also quite common. On top of it, a GUI with a rich UI interface is built to simplify software development. Some vendors provide their own GUIs, such as, e.g. STMicroelectronics' STM32CubeIDE (image 5), while others use already available universal code editing solutions and integrate with them, i.e. in the form of plugins or extensions.
Because documentation for the command line tools composing SDK is usually available, there are also universal solutions that enable developers to use a single GUI environment for various tasks and microcontrollers, switching among them quickly, such as Visual Studio Code (figure 6). Each platform requires its dedicated toolchain, anyway, and integration with universal code editors such as the aforementioned VS Code may be tricky. Luckily, there are tools to help with the automated installation of all required components, such as PlatformIO [33], that we describe below.
As the Arduino programming framework became a cross-platform standard, vendors provided low-level libraries implementing standard functionalities such as embedded communication protocols (Serial, SPI, I2C, 1Wire) and networking communication. Arduino, a manufacturer of popular development boards, provides an IDE (figure 7: Arduino IDE [34]) that is intended to be an entry-level development environment. It can be extended beyond genuine Arduino boards, i.e. with the Espressif toolchain for ESP8266 and ESP32. This software, however, is very limited in features and is suitable only for simple projects.
A number of additional tools usually come with the development toolchain provided by the hardware vendors. They include programmers (flashers, injecting firmware into the IoT device), configuration tools, power consumption calculators, etc. Installation is not always straightforward, and updating is tricky. Developers who use a variety of platforms (MCUs) struggle with instant updates, browsing the web for tools and sources. Moreover, handling libraries they use for development is time-consuming and involves instant monitoring of changes, manual copy-paste operations on files, etc. And it has to be done for every project individually.
The solution is a developer's middleware that integrates with selected IDE and helps to install, configure and maintain toolchains for hardware, software development libraries, and also contains a set of additional tools (i.e. serial port monitor, JTAG debugger, code repository integration, collaboration tools, remote development, etc.). As mentioned above, one example of a handy middleware for IoT and embedded development is PlatformIO. It is a command-line toolset that provides a whole ecosystem for virtually any hardware platform; it still uses the vendor's proprietary toolchains. It perfectly integrates with Visual Studio Code (among others) via VS Code's extension (plugin) systems. VS Code works also as a GUI for PlatformIO. In the following figures, we present its look and UI when integrated with Visual Studio Code (figures 8, 9 and 10).
A PlatformIO-enabled IoT project is a set of files with a platformio.ini
file in the root folder and main.cpp
in the ./src/
subfolder (as, i.e. in the figure 8, project folder tree is to the left)). The platformio.ini
file describes all technical parts of the project: the hardware platform, the method of uploading the firmware (usually via a serial port), software libraries that are included in the code and should be automatically pulled from the libraries repository during compilation and many other options [35]. Sample platformio.ini
file is presented in the code below:
[env:d1_mini] platform = espressif8266 board = d1_mini framework = arduino upload_port = /dev/ttyUSB0 upload_speed = 9600 monitor_port = /dev/ttyUSB0 lib_deps = arduino-libraries/LiquidCrystal@^1.0.7 adafruit/Adafruit Unified Sensor@^1.1.7 adafruit/DHT sensor library@^1.4.4
This code configures the ESP8266 (Espressif) hardware project, with specific developer board D1 Mini and programming done in the “Arduino” framework development model.
Communication with the IoT device is via serial port (here /dev/ttyUSB0
for Linux or, i.e. COM3
for Windows) and uses the same port for monitoring (serial port monitor for tracing messages from the MCU and code).
It uses three libraries registered in the library registry for PlatformIO: LiquidCrystal
, Adafruit Unified Sensor
and DHT sensor library
, with explicit versions. PlatformIO's Library Manager automatically checks for updates and proposes to update libraries to the latest available if a version is not explicitly stated. The Library Registry in the PlatformIO is a repository of the Gitlab project, available online [36]. Libraries are currently held per project instead of shared between projects.
At the start of the PlatformIO GUI and then periodically, it checks for PlatformIO updates and development toolchain updates, proposing to update them when a new version is available.
The following sub-chapters cover programming fundamentals in C/C++, which comply with most C/C++ notations. Those who feel comfortable in programming will find these chapters somewhat introductory, while for those having no or little experience, it is highly recommended to cover this introduction. This chapter and its sub-chapters target the basics and general syntax of C/C++ programming for different platforms, including Arduino, Espressif, Nordic, STM32, and partially for Raspberry Pi devices; however, in any case, the programming environment configuration is different for every platform. The Arduino programming framework is common for many MCU manufacturers of the IoT Edge class devices in bare metal programming mode, even if bringing some overhead and does not let the developer push the devices to their limits. Thus we refer mostly to this one. To enjoy full power, efficiency and control of the specific device, one needs to use a dedicated SDK and Framework, but for teaching purposes and many even professional applications, Arduino Framework is suitable and a good balance between the cost of the development and the result.
Almost every computer program manipulates the data. Data representation in the program is variable. In C/C++, the variable needs to be defined before using it, giving it some name and assigning chosen type, dependent on the kind of data. Here we show some common data types and how to use variables.
Data type specifies how it is encoded and represented in the computer memory. For example, integer numbers are binary-encoded, the texts are represented as a series of ASCII-encoded characters, and real numbers have a particular encoding scheme that consists of two binary numbers - mantissa and exponent. Other data types are tables that consist of elements of the same type or structures with elements of different types. There are plenty of different data types, some of them are predefined, but user-own types can be defined based on existing ones. Creating a variable requires specifying its type, which determines its place in the memory of the microcontroller and also the way how it can be used. Further will be viewed the most used ones together with examples of defining variables.
byte exampleVariable;
The example above defines a variable, reserves its memory, and assigns the memory address to the variable name. It is also possible to give the variable the initial value as below:
byte exampleVariable = 123;
int exampleVariable = 12300;
float exampleVariable = 12300.546;
int firstArray[] = {12,-3,8,15};
Square brackets of the array can be used to access some value in the array by index. In the following example, the element with index 1 (that is –3) is assigned to the secondVariable variable.
int secondVariable = firstArray[1];
An array can be quickly processed in the loop. The following example shows how to calculate the sum of all elements from the previously defined array (for statement will be explained in detail in the following chapters).
//The loop that repeats 4 times int sum = 0; for(int i = 0; i < 4; i = i + 1){ sum = sum + firstArray[i]; }
The loop in the example starts with index 0 (i = 0) and increases it by 1 while smaller than 4 (not including). That means the index value will be 3 in the last cycle because when the i equals 4, the inequality i < 4 is not true, and the loop stops working.
Data type conversion can be done using multiple techniques – casting or data type conversion using specific functions.
int i; float f=4.7; i = (int) f; //Now it is 4
int i = int(123.45); //The result will be 123
String string = "123fkm"; float f = string.toFLoat(); //The result will be 123.00
String string = "123fkm"; int i = string.toInt(); //The result will be 123
A typedef
specifier can give another name for existing types or declares a new one. Renaming types is possible, but software development frameworks used have a number of aliases already. It is helpful, however, when combined with enumerations, classes and structures to give them reasonable names and re-use them later in the code to improve their readability. We present more details on structures in the chapter Structures and Classes, but here is an example presenting a reasonable use of the typedef
specifier.
typedef struct {int x; int y;} tWaypoint; //Declare complex type named waypoint ... //Declare a variable of the type of tWaypoint tWaypoint wp1;
Enumerations are helpful to give meaning to the integer values and present some logic in a code instead of putting numbers into it. It can be, i.e., the device's state, error code, etc.
In the case a new enumeration is needed, it is possible to declare one using the enum
keyword and specifying a list:
enum errorcodes {ER_OK, ER_DOWNLOAD, ER_UPLOAD, ER_NOWIFI}; //define enumeration ... errorcodes Errorcode; //declare a variable ... Errorcode = ER_DOWNLOAD; //assign a value
The default numbering starts with 0 (ER_OK=0
) and increases by 1 with every next item on the enumeration list. However, explicitly defining values represented by the item labels is possible.
enum errorcodes {ER_OK=0, ER_DOWNLOAD=3, ER_UPLOAD=4, ER_NOWIFI=1};
Operators represent mathematical, relational, bitwise, conditional, or logical data manipulations. There are many operators in the C/C++ language. In this chapter, the most important are presented. Logical operators will be shown in the next chapter as they are used together with conditional statements.
Arithmetic operations are used to do mathematical calculations with numbers or numerical variables. The arithmetic operators are the following.
int result = 1 + 2; //The result of the addition operation will be 3
int result = 3 - 2; //The result of the subtraction operation will be 1
int result = 2 * 3; //The result of the multiplication operation will be 6
//The result of the division operation will be 3 //(Only the whole part of the division result) int result = 7 / 2; //The result of the division operation will be 3.5 float result2 = 7.0 / 2.0;
//The result of the modulo operation will be 1, //Because if 7 is divided by 3, the remaining is 1 int result = 7 % 3;
Bitwise operators perform operations on bits in the variable. Among them, there exist bitwise logic operations. It means the same logic function is applied to every pair of bits in two arguments. Bitwise or ( | ) means that if at least one bit is “1” at the chosen bit position, the resulting bit will also be “1”. Bitwise and ( & ) means that if at least one bit is “0”, the resulting bit is “0”. Bitwise operators shouldn't be confused with Logic Operators ( || ), ( && ), which operate on a single boolean logic value.
byte result = 5 | 8; ; //The operation in numbers gives the result of 13 ; //in bits can be shown as follows ; // 00000101b ; // 00001000b ; // --------- ; // 00001101b
byte result = 5 & 1; ; //The operation in numbers gives the result of 1 ; //in bits can be shown as follows ; // 00000101b ; // 00000001b ; // --------- ; // 00000001b
Bitwise operators also allow shifting data left ( « ) or right ( » ) chosen number of bit positions. Shifting is often used in embedded programming to access the bit at a specific position. Shifting data one bit left gives the result of multiplication by 2 while shifting one bit right gives the effect of dividing by 2.
byte result = 5 << 1; ; //The operation in numbers gives the result of 10 ; //in bits can be shown as follows ; // 00000101b ; // 00001010b
Compound operators in C/C++ are a short way of writing down the arithmetic operations with variables. All of these operations are done on integer variables. These operands are often used in the loops when it is necessary to manipulate the same variable in each cycle iteration. The compound operators are the following.
int a = 5; a++; //The operation a = a + 1; the result will be 6
int a = 5; a--; //The operation a = a – 1; the result will be 4
int a = 5; a+=2; //The operation a = a + 2; the result will be 7
int a = 5; a-=3; //The operation a = a – 3; the result will be 2
int a = 5; a*=3; //The operation a = a × 3; the result will be 15
int a = 6; a/=3; //The operation a = a / 3; the result will be 2
int a = 5; //The result will be the remaining //Part of the operation a/2; it results in 1 a%=2;
int a = 5; a|=2; //The operation a=a|2; the result will be 7
int a = 6; a&=; //The operation a=a&2; the result will be 2
Simple and complex types can be referred to with the use of pointer variables. A pointer is a variable that holds the address of the variable. The length of the pointer is equivalent to the length of the memory address (usually 16, 32 or 64 bits). A pointer does not contain a value but rather points to the variable (a memory) where the value is stored. A pointer variable must be initialised and dereferenced with Address-Of and Dereferencing operators.
The following example presents a simple type declaration and the use of a pointer variable.
&
operator returns an address of a variable.
*
operator dereferences a variable (it provides access to a value that the pointer variable points to).
int n = 10; //Declare a variable of type int and initialize it with 10 int *ptr; //Declare a pointer variable. //At this point, *ptr does not contain any address yet, //rather some random address or null. ptr = &n; //Assign to the pointer ptr an address of the variable n //ptr contains now an address of the memory where variable n is located, //not a value 10 int k; //Declare another variable k = *ptr; //Assign k a value that is pointed by ptr
Simple type variables such as int, double, float
and so on are passed to the function arguments as values, so the original value is copied, and a copy is presented to the function code (more on functions one can find in the Sub-programs, Functions). Modifications to the argument do not change the original value but just a copy. This is not the case when passing a complex type, such as an array, as an argument.
The importance of pointers is not to be underestimated in this case: you simply declare a pointer pointing to the array's first element and pass it to the function. Then modifying the pointer value (an address), it is possible to refer to the following elements of the array. In this case, any modification to the referred array element modifies an original one, so the change in the value is instant. It does not need a return variable from a function.
It is essential to understand that if no statements change the normal program flow, the microcontroller executes instructions one by one in the order they appear in the source code (from the top - to the down direction). Control statements modify normal program flow by skipping or repeating parts of the code. Often to decide if the part of the code should be executed or to choose one of the number of possible execution paths, conditional statements are used. For repeating the part of the code, loop statements can be used.
if is a statement that checks the condition and executes the following statement if the condition is TRUE. There are multiple ways how to write down the if statement:
//1st example if (condition) statement; //2nd example if (condition) statement; //3rd example if (condition) { statement; } //4th example if (condition) { statement; }
The version with curly braces is used when there is a need to execute part of the code that consists of more than a single statement. Many statements taken together with pair of curly braces are treated as a single statement in such cases. When both TRUE and FALSE cases of the condition should be viewed, the else part should be added to the if statement in the following ways:
if (condition) { statement1; //Executes when the condition is true } else { statement2; //Executes when the condition is false }
If more conditions should be viewed, the else if part is added to the if statement:
if (condition1) { statement1; //Executes when the condition1 is true } else if (condition2) { statement2; //Executes when the condition2 is true } else { statement3; //Executes in all other cases }
For example, when the x variable is compared and when it is higher than 10, the digitalWrite() method executes.
if (x>10) { //Statement is executed if the x > 10 expression is true digitalWrite(LEDpin, HIGH) }
To allow checking different conditions, logical operators are widely used with the condition statement if described above.
Comparison Operators
There are multiple comparison operators used for comparing variables and values. All of these operators compare the variable's value on the left to the value on the right. Comparison operators are the following:
Examples:
if (x==y){ //Equal //Statement } if (x!=y){ //Not equal //Statement } if (x<y){ //Less than //Statement } if (x<=y){ //Less than or equal //statement } if (x>y){ //Greater than //Statement } if (x>=y){ //Greater than or equal //Statement }
Boolean Operators
The Boolean logical operators in C/C++ are the following:
Examples:
//Logical NOT if (!a) { //The statement inside if will execute when the a is FALSE b = !a; //The reverse logical value of a is assigned to the variable b } //Logical AND //The statement inside if will execute when the //Values both of the a and b are TRUE if (a && b){ //Statement } //Logical OR //The statement inside if will execute when at least one of the //a and b values are TRUE if (a || b){ //Statement }
A switch statement similar to the if statement controls the flow of a program. The code inside switch is executed in various conditions. A switch statement compares the values of a variable to the specified values in the case statements. Allowed data types of the variable are int and char. The break keyword exits the switch statement.
Examples:
switch (x) { case 0: //Executes when the value of x is 0 // statements break; //Goes out of the switch statement case 1: //Executes when the value of x is 1 // statements break; //Goes out of the switch statement default: //Executes when none of the cases above is true // statements break; //Goes out of the switch statement }
Check Yourself
1. Which code part is the correct one?
2. What is the output of the next code part?
int x = 0; switch(x) { case 1: cout << "One"; case 0: cout << "Two"; case 2: cout << "Hello, world!"; }
3. In which case should theswitch structure be used?
Loops are critical to control flow structures in programming. They allow executing statements or some part of the program repeatedly to process elements of data tables and texts, making iterative calculations and data analysis. In the world of microcontrollers, where sometimes there is no operating system, the whole software works in the main loop called a super loop. It means the program never ends and works until the power is off. This is clearly visible in the Arduino programming model, with one part of the code executed once after power-on setup() , and another executed repeatedly loop() . In C/C++, there are three loop statements shown in this chapter.
for is a loop statement that allows specifying the number of times of execution statements inside it. Each time all statements in the loop's body are executed is called an iteration. In this way, the loop is one of the basic programming techniques used for all programs and automation in general.
The construction of a for loop is the following:
for (initialization ; condition ; operation with the cycle variable) { //The body of the loop }
Three parts of the for construction are the following:
The example of the for loop:
for (int i = 0; i < 4; i = i + 1) { digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); }
On the initialisation of the for loop, the cycle variable i = 0 is defined. The condition states that the for loop will be executed while the variable i value will be less than 4 (i < 4). In operation with the cycle variable, it is increased by 1 each time when the loop is repeated.
In the example above, Arduino function digitalWrite is used. It sets the logical state high or low at the chosen pin. If a LED is connected to pin 13 of the Arduino board, it will turn on/off four times.
while loop statement is similar to the for statement but does not contain the cycle variable. Because of this, the while loop allows executing a previously unknown number of iterations. The loop management is realised using only condition that needs to be TRUE for the next cycle to execute.
The construction of the while loop is the following:
while (condition is TRUE) { //The body of the loop }
That way, the while loop can be used as a good instrument for the execution of a previously unpredictable program. For example, if it is necessary to wait until the signal from pin 2 reaches the defined voltage level = 100, the following code can be used:
int inputVariable = analogRead(2); while (inputVariable < 100) { digitalWrite(13, HIGH); delay(10); digitalWrite(13, LOW); delay(10); inputVariable = analogRead(2); }
In the loop above, the LED that is connected to pin 13 of the Arduino board will be turned on/off until the signal reaches the specified level.
The do…while loop works similarly to the while loop. The difference is that in the while loop, the condition is checked before entering the loop, but in the do…while, the condition is checked after the execution of the statements in the loop, and then if the condition is TRUE the loop repeats. As a result, the statements inside the loop will execute at least once, even if the test condition is FALSE.
The construction of a do while loop is the following:
do { //The body of the loop } while (a condition that is TRUE);
If the same code is taken from the while loop example and used in the do…while loop, the difference is that the code will execute at least once, even if the inputVariable value is more than or equal to 100. The example code:
int inputVariable = analogRead(2); do { digitalWrite(13, HIGH); delay(10); digitalWrite(13, LOW); delay(10); inputVariable = analogRead(2); } while (inputVariable < 100);
In many cases, the program grows to a size that becomes hardly manageable as a single unit. It is quite difficult to navigate through the code that occupies many screens. In such a situation, subprograms can help. Subprograms are named functions in C, and in C++, while they are associated with an object, they are named methods (in this chapter, the name function will be used). The function contains a set of statements that usually form some logical part of the code that can be separately tested and verified, making the whole program easy to manage. It is possible to group many functions by creating a library that is stored in a separate file. This is how external libraries are created.
Functions are the set of statements that are always executed when the function is called. A function can accept arguments as its input data and return the resulting value. Two functions from the Arduino programming model that were mentioned before are already known – setup() and loop(). The programmer usually tries to make several functions containing all the statements and then calls them in the setup() or loop() functions.
The structure of the function is following:
type functionName(arguments) //A return type, name, and arguments of the function { //The body of a function – statements to execute }
For example, a function that periodically turns on and off the LED can look like this:
void exampleFunction() { digitalWrite(13, HIGH); //the LED is ON delay(1000); digitalWrite(13, LOW); //the LED is OFF delay(1000); }
The example above shows that the return type of aexampleFunction function is void, which means the function does not return any value. This function also does not have any arguments because the brackets are empty.
This function should be called inside the loop() function in the following way:
void loop() { exampleFunction(); //the call of the defined function inside loop() }
The whole code in the Arduino environment looks like this:
void loop() { exampleFunction(); //the call of the defined function inside loop() } void exampleFunction() { digitalWrite(13, HIGH); //the LED is ON delay(1000); digitalWrite(13, LOW); //the LED is OFF delay(1000); }
It can be seen that the function is defined outside the loop() or setup() functions.
When some specific result must be returned as a result of a function, then the function return type should be indicated, for example:
//the return type is "int" int sumOfTwoNumbers(int x, int y) { //the value next to the "return" should have the "int" type; //this is what will be returned as a result. return (x+y); }
In the loop(), this function would be called in the following way:
void loop() { //the call of the defined function inside the loop() int result = sumOfTwoNumbers(2, 3); }
Every programming SDK, including Arduino IDE, comes with several ready-made functions that help develop applications, significantly reducing the effort and time of writing programs. These functions are written to handle inputs and outputs, process texts, communicate using serial ports, manipulate bits and bytes, and perform mathematical calculations. Refer to Arduino or other SDK documentation for details.
The popularity of microcontrollers and embedded programming caused the growth of communities of enthusiasts who create a vast of useful software. This usually comes in the form of a set of functions created to handle some specific tasks, e.g. interfacing with a family of graphical displays or communicating using the chosen protocol. Functions created for one purpose are grouped together, forming the library. The number of libraries and their different version is so big that software developers use a special library manager to ensure that libraries are up-to-date or keep them in stable versions.
In the MCU world, is is common to use libraries that require a user (software developer) to implement a specific part of the code that is later automatically called by the library routines. Those functions are frequently called handlers and enable developers to inject their actions for a predefined set of activities without needing to modify library code. For this reason, the library contains a placeholder variable that can be assigned an executable code (a function body). Obviously, this is handled with the use of pointers. A sample function handler variable is presented in the following code, along with the user function definition, assignment to the handler variable and a call to the handler:
int (*hUserImplementedFunction)(int); //Function handler variable //(no code is here; it is just a pointer to the code, //currently NULL, pointing to "nowhere" ... int fMulx2(int a) { //User's implementation of the function. return (2*a); //Multiply the argument 'a' value by 2 and return it to the callee. } //Note: argument types and return types must match with the variable above ... hUserImplementedFunction = fMulx2; //assign a function to the handler //starting from now, hUserImplementedFunction contains an address of the //fMulx2 function ... int j; if (hUserImplementedFunction!=NULL) //check if the handler is not null to avoid NULL pointer exception and code hang j = hUserImplementedFunction(10); //call a handler, j is 20 now
fMulx2
simply represents an address where the code starts.
Structures and classes present complex data types, definable by the developer. Not all C/C++ programming environments provide support for classes (i.e., STM32 in HAL framework mode does not), but luckily, the Arduino framework supports it. Structures, conversely, are part of the C language definition and are present in almost every implementation of software frameworks for IoT microcontrollers.
In C and C++, a structure is a user-defined data type that allows you to combine different types of variables under a single name. A structure primarily groups related variables, forming a complex data type. A custom data structure (type) that can hold multiple variables, each with its own data type. These variables, called members or fields, can be of any built-in or user-defined type, including other structures. The sample named structure (equivalent to the complex type), variable declaration and use of member fields are presented below:
struct address { String city; String PO; String street; double longitude; double latitude; }; ... address adr1;
Note it is also possible to declare a structure variable directly without defining a type:
struct { String city; String PO; String street; double longitude; double latitude; } adr2, adr3;
Structures with type definitions are common when authoring libraries to let library users be able to declare new variables on their own, simply using a type.
Access to the fields of the structure's member variables (short: members, fields) is possible using the “.” (dot) operator.
adr1.city = "Gliwice"; adr2.city = "Oslo"; adr3.street = "Rodney";
The structure's data can be initialised member by a member or at once using the simplified syntax. Order is meaningful, and types need to fit the definition (C++ only):
adr3 = {"Liverpool", "L1 9EX", "27 Rodney", -2.973083901947872, 53.401615049766406 };
In C++, structures can also have member functions that manipulate the data (in C, they cannot). That is not so far from the Classes idea described in the following chapter. In the case of using C (or poor implementation of C++ that does not support classes nor member functions, i.e. STM32), it is common to prepare a set of data handling functions that operate on the structure referenced with a pointer. A common rule of thumb is the structure is the first argument in the function:
struct calcdata { double x,y; } args; //Adds x and y of the "arguments" structure double fCalcDataAdd(calcdata *arguments){ return (arguments->x + arguments->y); } //Multilies x and y of the "arguments" structure double fCalcDataMul(calcdata *arguments){ return ((arguments->x)*(arguments->y)); } //Sets x and y of the "arguments" structure void fCalcDataSet(calcdata *arguments, double px, double py){ arguments->x = px; arguments->y = py; }
In the examples above, we use a “→” dereference operator to access the member fields by using the pointer to the structure rather than the structure itself.
Sample use of the functions is then:
args = {2,7}; //initialise structure x=2, y=7 fCalcDataSet(&args, 12,12); //reinitialise structure x=12, y=12 int z = fCalcDataAdd(&args); //z equals to 24 now
Classes were introduced in C++ to extend structures encapsulating data and methods (functions) to process this data. A method presented above in the structure context brings an overhead with a need to pass a pointer to the structure for each call. Moreover, it makes access levels tricky, i.e. when you do not want to expose some functions but rather use them for internal data processing. Thus classes can be considered as an extension of the structures.
Sample class definition is presented below:
class Calculator { public: //you can access this part int x,y; Calculator() { //Default constructor clear(); } Calculator(int px, int py) { //Another constructor x=px; y=py; } ~Calculator(){} //This is dummy destructor int Add(){ return x+y; } int Mul(){ return x*y; } void setX(int px){ x=px; } void setY(int py){ y=py; } private: //that part is private, and you cannot access it void clear(){ x=0; y=0; } };
The code above declares a new type, Calculator
, with member fields (members in short) x
and y
and methods (functions) Calculator, Add, Mul, setX
and setY
. Some are marked as private:
and accessible only from the code of the functions (methods) within the class; some are exposed to external users when marked as public:
.
Constructors
There are “special” functions whose name is equivalent to the class name in this example above. Those are called constructors and are executed once the object of the class type is instantiated:
Calculator calc1=Calculator(2,15);
The above code instantiates an object calc1
of the class Calculator
and calls the constructor explicitly Calculator(int px, int py)
. The other constructor, Calculator()
, is the default one, and if not explicitly called by the code developer, it is automatically called when the object is instantiated.
There can be multiple constructors, and the one executed is selected based on the arguments set.
Destructor
A destructor is called automatically when an object's lifetime is to end. It allows, i.e. to release resources, disconnect open connections, and, in general, do some cleanup before the object is gone. The destructor function in the example above does nothing and is not obligatory in the code. Destructor name starts with a ~ sign (tilde) and has the same name as a class (or constructor):
~Calculator(){} //This is dummy destructor
Members
Member fields can be of any type. When marked as private
they are accessible only from the code of the constructors, destructor and methods within the class. When public
, one can reference them using a “.” (dot) operator, as in the case of the structures. When using a pointer to the class instance (object) rather than an instance itself (quite common), a “→” operator works as in the case of structures.
Methods
A method can have any name other than reserved (i.e. for constructors and destructor). Methods marked as public
are available for the object user and are referenced similarly to member fields (“.” and “→” operators). private
methods are not exposed externally; their purpose is to be called from another method internally. Sample use of methods is presented below:
//continuing initialisation above: calc1.x=2, calc1.y=15 int z = calc1.Add(); //z=17 calc1.setX(10); //calc1.x=10 calc1.setY(20); //calc1.y=20 z = calc1.Mul(); //z=200
Class inheritance
Classes can be inherited. This mechanism enables the real power of C++, where existing models (classes) can be extended with new logic without a need to rewrite and fork existing source code. In the example above, the Calculator
class misses some features, such as i.e. subtracting.
A code below defines a new type BetterCalculator
that inherits from the Calculator
class, using “:” operator:
class BetterCalculator:public Calculator { public: BetterCalculator() { } BetterCalculator(int px, int py):Calculator(px,py) { } int Sub(){return x-y;} };
Members x
and y
are in the Calculator
class. Inheritance before C++ release 11 requires explicit constructor definitions, as in the example above. We use public inheritance to give access to all public methods in the base Calculator
class available from within the level of the BetterCalculator
class. Note the public
keyword in the class definition: class BetterCalculator:public Calculator
.
Instantiation and use are similar to the presented ones in the previous examples:
BetterCalculator calc2=BetterCalculator(10,6); //BetterCalculator->Calculator->x=10, y=6 ... z = calc2.Sub(); //z=4 z = calc2.Add(); //z=16 - you can use the underlying code in the Calculator //class without a need to rewrite it again
The description above does not deplete all features of C++ Object Oriented Programming. Please note, however, that in the case of the embedded C++, their implementation can be limited and may not contain all the features of the modern, standard C++ patterns.
A special note on the libraries with separate definition (header) and implementation (body)
Many libraries come with a class definition in the header file (.h) and its implementation in the code file (.cpp). This is convenient for separating use patterns and implementations. A special operator, “::” (double colon), is used in the implementation to refer the code to the definition in the header file.
The sample header file myclass.h
with the aforementioned Calculator class is present below. It contains only the class definition but does not contain any implementing code.
#ifndef h_MYCLASS #define h_MYCLASS class Calculator{ public: //you can access this part int x,y; Calculator(); //Default constructor Calculator(int px, int py); //Another constructor ~Calculator(); //This is dummy destructor int Add(); int Mul(); void setX(int px); void setY(int py); private: //that part is private, and you cannot access it void clear(); }; #endif
The implementation code refers to the class definition in the header:
#include "myclass.h" Calculator::Calculator() {} Calculator::Calculator(int px, int py) { x=px; y=py; } Calculator::~Calculator(){} int Calculator::Add(){ return x+y;} int Calculator::Mul(){ return x*y; } void Calculator::setX(int px){ x=px; } void Calculator::setY(int py){ y=py; } void Calculator::clear(){ x=0; y=0; }
Writing code that handles interrupts that come from internal peripherals, for example, timers, is possible but depends strongly on the hardware. Because this chapter presents just an introduction to programming, some essential timing functions will be shown.
The simplest solution to make functions work for a particular time is to use the delay()
[37] function.
delay()
function halts program execution for the time specified as the argument (in milliseconds).
The blinking LED code is a simple demonstration of delay functionality:
digitalWrite(LED_BUILTIN, HIGH); //Turn the LED on delay(1000); //Stop program for a second digitalWrite(LED_BUILTIN, LOW); //Turn the LED off delay(1000); //Stop program for a second
Using delay()
is convenient but has a severe drawback: the algorithm is halted, and only interrupts (or tasks in the background) are executed. The main algorithm is present in the figure 11. Some of the tasks, like, i.e. receiving serial transmissions, networking, and outputting set PWM values, continue to work as background tasks, using interrupts or task management (such as FreeRTOS).
The alternative to using delay is to switch to the non-blocking method, based on timing with the use of millis()
as presented below.
millis()
[38] returns the number in milliseconds since MCU began running the current program. Note it has nothing to do with a real-time clock, as most microcontrollers and development boards simply do not have one. The readings are 32-bit and will roll over in approximately 49 days. millis()
can be used to replace delay()
but needs some additional coding. Instead of blocking the algorithm, here we check if the desired time has passed. Meanwhile, we can handle other tasks instead of blocking execution, as presented in the algorithm in figure 12.
Here is an example code of blinking LED using millis()
. Millis is used as a timer. Every new cycle time is calculated since the last LED state change. If the time passed is equal to or greater than the threshold value, the LED is switched:
//Unsigned long should be used to store time values as the millis() returns a 32-bit unsigned number //Store value of current millis reading unsigned long currentTime = 0; //Store value of time when last time the LED state was switched unsigned long previousTime = 0; bool ledState = LOW; //Variable for setting LED state const int stateChangeTime = 1000; //Time at which switch LED states void setup() { pinMode (LED_BUILTIN, OUTPUT); //LED setup } void loop() { currentTime = millis(); //Read and store current time //Calculate passed time since the last state change //If more time has passed than stateChangeTime, change the state of the LED if (currentTime - previousTime >= stateChangeTime) { previousTime = currentTime; //Store new LED state change time ledState = !ledState; //Change LED state to oposite digitalWrite(LED_BUILTIN, ledState); //Write current state to LED } }
Some IoT-dedicated microcontrollers have special features such as sleep modes that hold program execution for a predefined time or unless an external trigger occurs. This can be used for periodic, time-based activities. Its side effect is energy efficiency. The model of this behaviour and its features are very vendor-specific and vary much: i.e. Espressif MCUs have the only option to restart the code. At the same time, STM32 can hold execution and then continue. Because of the variety of models, modes and features, we do not present here any specific solution but rather a general idea.
/data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/introductiontoembeddedprogramming2/cppfundamentals/digital_IO.txt— MISSING PAGE — /data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/introductiontoembeddedprogramming2/cppfundamentals/analog_IO.txt— MISSING PAGE —
Interrupt is a signal that stops the normal execution of a program in the processor and starts the function assigned to a specific source of it. Such a function usually is called Interrupt Service Routine (ISR) or interrupt handler. The ISR can be recognized as a task with higher priority than the main program. Interrupt signals can be generated by the external source, like a change of value on the pin, and by the internal source, like a timer or any other peripheral device. When the interrupt signal is received, the processor stops executing the code and starts the ISR. After completing the interrupt handler, the processor returns to the normal program execution state.
ISR should be as short as possible; good practice is avoiding delays and long code sequences. Suppose there is a need to trigger the execution of a long part of the code with an incoming interrupt signal. In that case, the good practice is to define the synchronization variable, modify this variable in the ISR with a single instruction, and handle all other steps in the main program. The interrupt handler does not have arguments and does not return any value, so its type is void
. To ensure fast execution of the programs, some of the Arduino functions do not work or behave differently in the ISR; for example, the delay()
function does not work inside the ISR. Variables used in the ISR must be declared as volatile.
Interrupts are used to detect important real-time events which occur during normal code execution of the code. ISR is executed only when there is a need to do it.
Interrupts can help in efficient data transmission. Using interrupts and checking if some situation occurred periodically is unnecessary. Such continuous checking is named polling. For example, a serial port interrupt is executed only when new data comes without polling the incoming buffer in a loop. This approach saves the processor time and, in many situations, creates code that is more energy efficient.
Because interrupts need support from the hardware layer of the microcontroller, the availability of specific interrupt sources depends heavily on the microcontroller model. For example, different Arduino models have different external interrupt pin availability. In most Arduino boards, pins numbered 2 and 3 can be used for interrupts; in Arduino Uno, only these two, while in ESP32 and STM32, almost any digital pin is valid.
Very often, interrupts are used together with hardware timers to generate stable frequency signals. It ensures accurate timing independent of the main loop content and delays. Because internal peripherals are very different for different microcontrollers in this chapter, the example for the external interrupt is shown.
The function attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
is called to attach an interrupt to the handler. This function has 3 arguments.
pin
– the pin number where the interrupt signal-generating device will be attached.ISR
– the name of an ISR function.mode
– defines when an interrupt signal is triggered. There are five basic mode
values:LOW
– interrupt is triggered when the pin value is LOW
,HIGH
– interrupt is triggered when the pin value is HIGH
, RISING
– interrupt is triggered when the pin value is changed from LOW
to HIGH
,FALLING
– interrupt is triggered when the pin value is changed from HIGH
to LOW
,CHANGE
– interrupt is triggered when the pin value is changed in any direction.The example program that uses external interrupt:
volatile bool button_toggle = 0; //A variable to pass the information from ISR to the main program void setup() { //Define LED pin pinMode(13,OUTPUT); //Define button pin pinMode(2,INPUT_PULLUP); //Attach interrupt to button pin attachInterrupt(digitalPinToInterrupt(2),ButtonIRS,FALLING); } void ButtonIRS() { //IRS function button_toggle =!button_toggle; } void loop() { digitalWrite (13,button_toggle); }
In this example, the code needed to handle the interrupt signal is just one instruction. Still, it shows how to use the synchronization variable to pass information from ISR to the main program keeping the ISR very short.
This chapter presents some programming templates and fragments of the code that are common in embedded systems. Some of those patterns, such as i.e. non-blocking algorithms, which do not use delay(x)
to hold program execution but use a timer-based approach instead. It has also been discussed in other chapters, such as in the context of timers
Timing or interrupts Interrupts.
Almost any MCU has a hardware debugging capability. This technique is complex and usually requires an external debugger such as JTAG. Setting up hardware and software for simple projects may not be worth a penny; thus, the most frequent case is tracing over debugging. Tracing uses a technique where the Developer explicitly sends some data to the external device (usually a terminal, over a serial port, and eventually a display) that visualises it. The Developer then knows the variables' values and how the algorithm runs. The use of the serial port is common because this is the one that is most frequently used for programming. Thus, it can be used in reverse for tracing. For this reason, Arduino Framework implements a singleton object Serial
present in every code. It is implemented by each Arduino Framework vendor at the level of the general library with Arduino Framework.
Note to use a Serial
it is obligatory to initialise it with the use of the Serial.begin(x)
method, providing the correct bps, where x
is a transmission speed (rate) that suits the rate configured in the terminal. The most common rates are 9600 (default) and 115200, but other options are possible. On the terminal side, configuration is usually done in the menu or a configuration file, such as in the case of the platformio.ini
file. Calling Serial.begin(x)
is usually done as one of the first actions implemented in the Setup()
function of the application code:
void setup(){ delay(100); Serial.begin(115200); Serial.println(); ... }
delay(100)
at the beginning of the code and drop one or two “new line” characters to scroll garbage up using dummy println()
call (once or twice is usually enough).
The Serial
object has a number of handy methods that can help represent various variable types in a textual form to be sent via a serial port to the terminal. The most common are:
Serial.print(x)
where x is any simple type available in the Arduino Framework, such as integers and floats, but also visualises arrays of characters and String
objects.Serial.println(x)
prints as above but additionally adds the end of line/newline character by the end of the transmission. Note that the Linux style is used in Arduino, so only ASCII 13 character is sent.
The serial port and a class Serial
handling the communication are bi-directional. It means that you can send a message from MCU to the terminal and the opposite. This can be used as a simple user interface. All aforementioned configuration steps to ensure seamless cooperation of the MCU serial interface and terminal (application) are also in charge here. As data is streamed byte by byte, it is usually necessary to buffer it. Technically, the serial port notifies MCU every time a character comes to the serial port with the use of the interrupts. Luckily part of the job is done by the Serial
class: all characters are buffered in an internal buffer, and one can check their availability using Serial.available()
. This function returns the number of bytes received so far from the external device (here, i.e. a terminal) connected to the corresponding serial port.
Data in the serial port are sent as bytes; thus, it is up to the developer to handle the correct data conversion. Reading a single byte of the data is done using Serial.read()
: it gets another character from the FIFO queue that stands behind the serial port software buffer. As most of the communication is done textual way, the Serial
class has support to ease the reading of the strings: Serial.readString()
, but use involves some extra logic such as the function may timeout. Also, it may contain the END-OF-LINE / NEXT-LINE characters that should be trimmed before use [39].
Hardware buttons tend to vibrate when switching. This physical effect causes bouncing of the state forth and back, generating, in fact, a number of pulses instead of a single edge during switching. Getting rid of this is called debouncing. In most cases, switches (buttons) short to 0 (GND) and use pull-up resistors, as in the figure 13.
The switch, when open, results in VCC through R1 driving the GPIO2 (referenced as HIGH), and when short, 0 is connected to it, so it becomes LOW:
Some MCUs offer internal pull-ups and pull-downs, configurable from the software level. The transition state between HIGH and LOW causes bouncing.
A dummy debouncing mechanism only checks periodically for a press/release of the button. The common period for debouncing is between 50ms and 200ms. The code below shows an example that has been provided for presentation purposes. Yet, it is not flexible nor pragmatic due to the exhausting use of the loop()
function and extensive use of delay()
. An internal pull-up resistor is in use in this example:
#define BUTTON_GPIO 2 bool bButtonPressed=false; void setup() { Serial.begin(9600); pinMode(BUTTON_GPIO, INPUT_PULLUP); } void loop() { if (digitalRead(BUTTON_GPIO)==LOW && !bButtonPressed) { Serial.println("Button pressed"); delay(200); bButtonPressed=true; } if (bButtonPressed && digitalRead(BUTTON_GPIO)==HIGH) { Serial.println("Button released"); bButtonPressed=false; delay(200); } }
A more advanced technique for complex handling of the buttons is presented below in the context of the State Machines.
A Finite State Machine (FSM) idea represents states and flow conditions between the states that reflect how the software is built for the selected system or its component. An example of button handling with the use of the FSM is present here. The FSM reflects the physical state of the device, sensor or system on the software level, becoming a digital twin of a real device.
For the simple case (without detecting double-click or long press), 3 different button states can be distinguished: released, debouncing and pushed. An enumerator is a good choice to model those states (it is easily expandable):
typedef enum { RELEASED = 0, DEBOUNCING, PRESSED } tButtonState;
A flow between the states can be then described in the following diagram (figure 14).
RELEASED
state, there is waiting until the button is pressed (LOW, for the pull-up model). The time is noted when it occurs, and the state changes to the DEBOUNCING
.DEBOUNCING
state, if debouncing time passes and the button is still pressed (LOW) machine changes its state to PRESSED
. If the button in DEBOUNCING
becomes released (HIGH), then the machine returns to the state RELEASED
.PRESSED
state, it transits to the RELEASED
whenever the button goes HIGH.
The state machine is implemented as a simple class and has 2 additional fields that store handlers for functions that are called when the state machine enters PRESSED
or RELEASED
. Those functions are called callbacks. There are 2 public functions for callback registration as callback handlers class members are private. fButtonAction()
is intended to be called in a loop()
, as many times as possible to “catch” all pushes of the button:
class PullUpButtonHandler{ private: tButtonState buttonState=RELEASED; uint8_t ButtonPin; unsigned long tDebounceTime; unsigned long DTmr; void(*ButtonPressed)(void); //On button pressed callback void(*ButtonReleased)(void); //On button relased callback void btReleasedAction() { //Action to be done when current state is RELEASED if(digitalRead(ButtonPin)==LOW) { buttonState = DEBOUNCING; DTmr = millis(); } } void btDebouncingAction() { //Action to be done when current state is DEBOUNCING if(millis()-DTmr > tDebounceTime) if(digitalRead(ButtonPin)==LOW) { buttonState = PRESSED; if(ButtonPressed!=NULL) ButtonPressed(); } else buttonState=RELEASED; } void btPressedAction() { //Action to be done when current state is PRESSED if(digitalRead(ButtonPin)==HIGH) { buttonState=RELEASED; if(ButtonReleased!=NULL) ButtonReleased(); } } public: PullUpButtonHandler(uint8_t pButtonPin, unsigned long pDebounceTime) { //Constructor ButtonPin = pButtonPin; tDebounceTime = pDebounceTime; } void fRegisterBtPressCalback(void (*Callback)()) { //Function registering a On PRESSED callback ButtonPressed = Callback; } void fRegisterBtReleaseCalback(void (*Callback)()) { //Function registering a On RELEASED callback ButtonReleased = Callback; } void fButtonAction() //Main, non blocking loop. Handles state machine logic { //along with private functions above switch(buttonState) { case RELEASED: btReleasedAction(); break; case DEBOUNCING: btDebouncingAction(); break; case PRESSED: btPressedAction(); break; default: break; } } };
Sample use looks as follows:
#define BUTTON_GPIO 2 PullUpButtonHandler bh = PullUpButtonHandler(BUTTON_GPIO, 200); void onButtonPressed() { Serial.println("Button pressed"); } void onButtonReleased() { Serial.println("Released"); } void setup() { Serial.begin(9600); pinMode(BUTTON_GPIO, INPUT_PULLUP); bh.fRegisterBtPressCalback(onButtonPressed); bh.fRegisterBtReleaseCalback(onButtonReleased); } void loop() { bh.fButtonAction(); }
PullUpButtonHandler
is instantiated with a 200ms deboucing time. That defines a minimum press time to let the machine recognize the button press correctly. That time is quite long for most applications and use cases and can be easily shortened.
The great feature of this FSM is that it can be easily extended with new functions, such as i.e. detection of the double click or long button press.
Some generic programming techniques and patterns mentioned above require adaptation for different hardware platforms. It may occur whenever hardware-related aspects are in charge, i.e., accessing GPIOs, ADC conversion, timers, interrupts, multitasking (task scheduling and management), multicore management, power saving extensions and most of all, integrated communication capabilities (if any). It can be different for almost every single MCU or MCU family.
It is common for hardware vendors to provide rich examples, either in the form of documentation and downloadable samples (i.e. STM) or via Github (Espressif), presenting specific C/C++ code for microcontrollers.
Some MCUs use specific setups. Analogue input may work out of the box. Still, low-level control usually brings better results and higher flexibility (i.e. instead of changing the input voltage to reflect the whole measurement range, you can regulate internal amplification and sensitivity.
Please note implementation varies even between the ESP32 chips family, and not all chips provide all of the functions, so it is essential to refer to the technical documentation [40].
ESP32 has 15 channels exposed (18 total) of the up to 12-bit resolution ADCs. Reading the raw data (12-bit resolution is the default, 8 samples per measure as default) using the analogRead()
function is easy.
Technically, under the hood on the hardware level, there are two ADCs (ADC1 and ADC2). ADC 1 uses GPIOs 32 through 39. ADC2 GPIOs 0,2,4, 12-15 and 25-27. Note that ADC2 is used for WiFi, so you cannot use it when WiFi communication is enabled.
Just execute analogRead(GPIO)
.
Several useful functions are here (not limited to):
analogReadResolution(res)
- where res
is a value between 9 and 12 (default 12). For 9-bit resolution, you get 0..511 values; for 12-bit resolution, it is 0..4095 respectively. analogSetCycles(ccl)
- where ccl
is number of cycles per ADC sample. The default is 8: the valid number is between 1 and 255.analogSetClockDiv(divider)
- sets base clock divider for the ADC. That has an impact on the speed of conversion.analogSetAttenuation(a)
and analogSetPinAttenuation(GPIO, a)
- sets input attenuation (for all channels or selected channels). The default is ADC_11db
. This parameter reflects the dynamic scaling of the input value:ADC_0db
- no attenuation (1V on input = 1088 reading on ADC), so full scale is 0..1.1V,ADC_2_5db
- 1.34 (1V on input = 2086 reading on ADC), so full scale is 0..1.5V,ADC_6db
- 1.5 (1V on input = 2975 reading on ADC), so full scale is 0..2.2V,ADC_11db
- 3.6 (1V on input = 3959 reading on ADC), so full scale is 0..3.9V.analogRead()
. As technically all channels use the same two registers (ADC1 and ADC2), you need to give it some time to sample (i.e. delay(100)
between consecutive reads on different channels).
PWM frequently controls analogue-style, efficient voltage on the GPIO pin. Instead of using a resistance driver, PWM uses pulses to change the adequate power delivered to the actuator. It applies to motors, LEDs, bulbs, heaters and indirectly to the servos (but that works another way).
The classical analogWrite
method, known from Arduino (Uno, Mega) and ESP8266, does not work for ESP32.
ESP32 has up to sixteen (0 to 15) PWM channels (controllers) that can be freely bound to any of the regular GPIOs.
The exact number of PWM channels depends on the family member of the ESP chips, i.e. ESP32-S2 and S3 series have only 8 independent PWM channels while ESP32-C3 has only 6. In the Arduino software framework for ESP32, it is referred to as ledc
. ESP32 can use various resolutions of the PWM, from 1 to 20 bits, while regular Arduino uses only 8-bit one. Note - there is a strict relation between resolution and frequency: i.e. with high PWM frequency, you cannot go with a resolution too high as the internal frequency of the ESP32 chip is limited.
To use PWM in ESP32, one must perform the following steps:
OUTPUT
,More information and detailed references can be found in the technical documentation for the ESP32 chips family [41].
Sample code controlling an LED on GPIO 26 with 5kHz frequency and 8-bit resolution is presented below:
#include "Arduino.h" ... #define RGBLED_R 26 #define PWM1_Ch 5 #define PWM_Res 8 #define PWM_Freq 5000 ... ledcSetup(PWM1_Ch, PWM_Freq, PWM_Res); //Instantiate timer-based PWM -> PWM channel ledcAttachPin(RGBLED_R, PWM1_Ch); //Bind a PWM channel to the GPIO ledcWrite(PWM1_Ch,255); //Full on: control via the PWM channel, not via the GPIO ...
This technique can be easily adapted to control, e.g. standard and digital servos. PWM signal specification to control servos is presented in the chapter hardware actuators.
Arduino boards used to have a limited set of GPIOs to trigger interrupts. In other MCUs, it is a rule of thumb that almost all GPIOs (but those used, i.e. for external SPI flash) can trigger an interrupt; thus, there is much higher flexibility in, i.e., the use of user interface devices such as buttons.
Suppose the interrupt routine (function handler) uses any variables or access flash memory. In that case, it is necessary to use some tagging of the ISR function because of the specific, low-level memory management. A use of IRAM_ATTR
is necessary (part of the code present in Interrupts:
void IRAM_ATTR ButtonIRS() { //IRS function button_toggle =!button_toggle; }
volatile
for the variable should be enough.
float
type (hardware accelerated floating point) will cause the application to hang, throwing a panic error and immediate restart of the MCU. It is due to the specific construction of the MCU and FPU. Do not use the float
type in interrupt handling. If floating point operations are needed, use double
as this one is calculated the software way.
The number of hardware timers, their features, and specific configuration is per MCU. Even single MCU families have different numbers of timers, i.e., in the case of the STM32 chips, the ESP32, and many others. Those differences, unfortunately, also affect Arduino Framework as there is no uniform HAL (Hardware Abstraction Layer) for all MCUs so far.
The number of hardware timers varies between family members. Most ESP32s have 4, but ESP32-C3 has only two [42]. A timer is usually running at some high speed. The most common is 80MHz and requires a prescaller to be useful. Timers periodically call an interrupt (a handler) written by the developer and bound to the timer during the configuration. Because interrupt routines can run asynchronously to the main code and, most of all, because ESP32s (most) are double core, it is necessary to take care of the deadlocks that can appear during the parallel access to the shared memory values, such as service flags, counters etc.
Special techniques using the critical section, muxes and semaphores are needed when more than one routine writes to the shared variable between processes (usually main code and an interrupt handler). However, It is unnecessary in the scenario where the interrupt handler writes to the variable and some other code (i.e. in the loop()
section reads it without writing, as in the case of the example presented below.
In this example, the base clock for the timer in the ESP32 chip is 80MHz, and the timer (tHBT
- short from Hear Beat Timer) runs at the 1MHz speed (PRESCALLER is 80) and counts up to 2 000 000. So, the interrupt handler is effectively called once every 2 seconds. This code runs separate from the loop()
function, asynchronously calling the onHBT()
interrupt handler.
onHBT()
interrupt handler swaps the boolean value every two seconds. The value then is translated by the main loop()
code to drive an LED on the ESP32 development board (here it is GPIO 0), switching it on and off. The onHBT()
handler function could directly drive the GPIO to turn the LED on and off. Still, we present a more complex example with a volatile
variable LEDOn
just for education purposes.
#include "esp32-hal-timer.h" #define LED_GPIO 0 //RED LED on GPIO 0 - vendor-specific #define PRESCALLER 80 //80MHz->1MHz #define COUNTER 2000000 //2 million us = 2s volatile bool LEDOn = false; hw_timer_t *tHBT = NULL; //Heart Beat Timer void IRAM_ATTR onHBT(){ //Heart Beat Timer interrupt handler LEDOn = !LEDOn; //Change true to false and opposite; every call } void setup() { Serial.begin(9600); pinMode(LED_GPIO, OUTPUT); tHBT = timerBegin(0, PRESCALLER, true); //Instantiate a timer 0 (first) // Most ESP32s (but ESP32-C3) have 4 timers (0-3), and ESP32-C3 has only two (0-1). if (tHBT==NULL) //Check timer is created OK, NULL otherwise { Serial.println("Timer creation error! Rebooting..."); delay(1000); ESP.restart(); } timerAttachInterrupt(tHBT, &onHBT, true); //Attach interrupt to the timer timerAlarmWrite(tHBT, COUNTER, true); //Configure to run every 2s (2000000us) and repeat forever timerAlarmEnable(tHBT); } //Loop function only reads LEDOn value and updates GPIO accordingly void loop() { digitalWrite(LED_GPIO, LEDOn); }
Timers can also be used to implement a Watchdog. Regarding the example above, it is usually a “one-time” triggered action instead of a periodic one. All one needs to do is to change the last parameter of the timerAlarmWrite
function from true
to false
.
Several programming models for IoT script programming are available. Depending on the hardware model used (SoC or OS-based MCU), it may involve single script execution (e.g. Raspberry Pi Pico RP2040, Edge-class IoT) or multithreaded, parallel, multiple scripts, doing multiple tasks (e.g. Raspberry Pi 4, Fog-class IoT). The idea and model of the scripting programming for SoC class devices (edge) were presented in the chapter Script Programming with Middleware.
In the case of far more powerful, Fog-class IoT devices that are OS-based devices, a variety of programming languages and, thus, scripting interpreters are available.
Among others, the most common scripting languages for fog class devices are :
As Bash scripting is well covered by many manuals for Linux, in the following chapters, we focus on two others: Python and C#. Moreover, accessing the GPIO in the case of the bash requires installing external tools; thus, it does not apply to IoT programming straightforwardly but rather as a supplementary tool to automate tasks other than core programming.
Python programming for IoT devices is dual:
csharpfundamentals When writing this publication, the .NET framework with C# interpreter is available only for Raspberry Pi devices as a part of the Windows IoT operating system [43].
[pczekalski]Add existing pages with C# and Python for RPI [pczekalski]Author a course on Micropython
A program in Python is stored in text files on the device's file system, as Python's source code is interpreted, not compiled, opposite to C++. A typical file extension for programs in Python is .py
.
In the context of IoT programming, both Python and Micropython share the same syntax and mostly the same libraries, so source code, in many cases, is portable. General hardware-related libraries like GPIO handling or timers are shared between those two Python worlds, and hardware-specific differences are minor compared to the Arduino framework.
Python is simple and efficient in programming the not-so-complex IoT algorithms but does not offer the level of control needed in real-time applications. It can be easily used for prototyping, testing hardware and implementing simple tasks.
Nowadays, Python interpreter usually comes with OS (Linux) preinstalled.
The sample installation procedure for Raspbian OS is presented in the manual maintained by the Raspberry Pi manufacturer [44].
In the case of the popular Raspbian or Ubuntu for Raspberry Pi, there are usually 2 versions of Pythons preinstalled: Python 2 and Python 3, because of the historical differences between implementations. Many OS applications are written in Python.
Python version can be started from the terminal simply by calling:
~$ python --version
Python 3.8.10
In the case one needs to use a specific version, you can start the interpreter explicitly referring to Python 2 or Python 3:
~$ python2 Python 2.7.18 (default, Jul 29 2022, 09:29:52) [GCC 9.4.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> quit() ~$ python3 Python 3.8.10 (default, May 26 2023, 14:05:08) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> quit()
Python can be executed via a desktop graphical interface (in the graphical terminal), in a text-based Linux installation via terminal, or remotely via ssh. As it is possible to write applications with visual GUI, starting them in a non-graphical installation of the Linux OS will throw an error. To execute a Python script (program), one needs to execute the following:
~$ python mypythoniotapp.py
Linux, Windows and Mac systems used to bind a .py
file extension with a default Python interpreter, so it is possible to run Python script directly, either with the use of file manager or execute it from the command line:
~$ ./mypythoniotapp.py
In the following chapters, we present Python coding elements that are specific to the microcontrollers. A full Python course is beyond the scope of this publication, but it can be easily obtained online (links presented by the end of the chapter).
A dozen of IDEs can be used to program in Python. The most common are:
The following subchapters present some IoT and embedded systems-specific Python programming and a very basic introduction:
For in-depth Python courses and more, follow the links:
[pczekalski][✓ pczekalski, 2023-08-26] Port info from the old manuals subpages in the namespace
[pczekalski]Add a simple app in Python. Check if Micropython has printing support over serial for tracing
Python aims to be consistent and straightforward in the design of its syntax. The best advantage of this language is that it can dynamically set the variable types depending on values types which are set for variables.
Python has a wide range of data types, like many simple programming languages:
Standard Python methods are used to create the numbers:
var = 1234 #Creates Integer number assignment var = 'George' #Creates String type var
Python can automatically convert types of the number from one type to another. Type can be also defined explicitly.
int a = 10 long a = 123L float a = 12.34 complex a = 3.23J <code> ==String== To define Strings use eclosing characters in quotes. Python uses single quotes ', double " and triple """ to denote strings. <code Python> Name = "George' lastName = "Smith" message = """this is the string message which is spanning across multiple lines."""
List contains a series of values. To declare list variables uses brackets [].
A = [] #Blank list variable B = [1, 2, 3] #List with 3 numbers C = [1, 'aa', 3] #List with different types
List are zero-based indexed. Data can be assigned to a specific element of the list using an index into the list.
mylist[0] = 'sasa' mylist[1] = 'wawa' print mylist[1]
List aren't limited to a single dimension.
myTable = [[],[]]
In two-dimensional array the first number is always the rows number, when the second is the columns number.
Python Tuples are defined as a group of values like a list and can be processed in similar ways. When assigned Tuples got the fixed size. In Python, the fixed size is immutable. The lists are dynamic and mutable. To define Tuples, parenthesis () must be used.
TestSet = ('Piotr', 'Jan', 'Adam')
To define the Dictionaries in the Python the lists of key–value pairs are used. This datatype is used to hold related information that can be associated through Keys. The Dictionary is used to extract a value based on the key name. Lists use the index numbers to access its members when dictionaries use a key. Dictionaries generally are used to sort, iterate and compare data.
To define the Dictionaries the braces ({}) are used with pairs separated by a comma (,) and the key values associated with a colon (:). Dictionaries Keys must be unique.
box_nbr = {'Alan': 111, 'John': 222} box_nbr['Alan'] = 222 #Set the associated 'Alan' key to value 222' print (box nbr['John']) #Print the 'John' key value box_nbr['Dave'] = 111 #Add a new key 'Dave' with value 111 print (box_nbr.keys()) #Print the keys list in the dictionary print ('John' in box_nbr) #Check if 'John' is in the dictionary #This returns true
All variables in Python hold references to objects, and are passed to functions. Function can't change the value of variable references in its body. The object's value may be changed in the called function with the “alias”.
>>> alist = ['a', 'b', 'c'] >>> def myfunc(al): al.append('x') print al >>> myfunc(alist) ['a', 'b', 'c', 'x'] >>> alist ['a', 'b', 'c', 'x']
If an expression returns TRUE statements are carried out. Otherwise they aren't.
if expression:
statements
Sample:
no = 11 if no >10: print ("Greater than 10") if no <=30 printf ("Between 10 and 30")
Output:
>>> Greater than 10 Between 10 and 30 >>>
An else statement follows an if statement and contains code that is called when the if statement is FALSE.
x = 2 if x == 6 printf ("Yes") else: printf ("No")
The elif (shortcut of else if) statement is used when changing if and else statements. A series of if…elif statements can have a final else block, which is called if none of the if or elif expression is TRUE.
num = 12 if num == 5: printf ("Number = 5") elif num == 4: printf ("Number = 4") elif num == 3: printf ("Number = 3") else: printf ("Number = 12")
Output:
>>> Number = 12 >>>
Python uses logic operators like AND, OR and NOT.
The AND operator uses two arguments, and evaluates to TRUE if, and only if, both of the arguments are TRUE. Otherwise, it evaluates to FALSE.
>>> 1 == 1 and 2 == 2 True >>> 1 == 1 and 2 == 3 False >>> 1 != 1 and 2 == 2 False >>> 4 < 2 and 2 > 6 False >>>
Boolean operator or uses two arguments, and evaluates as TRUE if either (or both) of its arguments are TRUE, and FALSE if both arguments are FALSE.
The result of NOT TRUE is FALSE, and NOT FALSE goes to TRUE.
>>> not 2 == 2 False >>> not 6 > 10 True >>>
Operator Precedence uses mathematical idea of operation order, e.g. multiplication begin performed before addition.
>>> False == False or True True >>> False == (False or True) False >>> (False == False) or True >>>True >>>
An if statement is run once if its condition evaluates to TRUE, and never if it evaluates to FALSE.
A while statement is similar, except that it can be run more than once. The statements inside it are repeatedly executed, as long as the condition holds. Once it evaluates to FALSE, the next section of code is executed.
i = 1 while i<=4: print (i) i+=1 print ('End')
Output:
>>> 1 2 3 4 End >>>
The infinite loop is a particular kind of the while loop, it never stops running. Its condition always remains TRUE.
while 1 == 1: print ('in the loop')
To end the while loop prematurely, the break statement can be used. When encountered inside a loop, the break statement causes the loop to finish immediately.
i = 0 while 1==1: print (i) i += 1 if i >=3: print('breaking') break; print ('finished')
Output:
>>> 0 1 2 3 breaking finished >>>
Another statement that can be used within loops is continue.
Unlike break, continue jumps back to the top of the loop, rather than stopping it.
i = 0 while True: i+=1 if i == 2: printf ('skipping 2') continue if i == 5: print ('breaking') break print (i) print ('finished')
Output:
>>> 1 skipping 2 3 4 breaking finished >>>
n = 9 for i in range (1,5): ml = n * i print ("{} * {} = {}".format (n, i, ml))
Output:
>>> 9 * 1 = 9 9 * 2 = 18 9 * 3 = 27 9 * 4 = 36 >>>
One of the most important in mathematics concept is to use functions. Functions in computer languages implement mathematical functions. The executing function produces one or more results, which are dependent by the parameters passed to it.
In general, a function is a structuring element in the programming language which groups a set of statements so they can be called more than once in a program. Programming without functions will need to reuse code by copying it and changing its different context. Using functions enhances the comprehensibility and quality of the program. It also lowers the memory usage, development cost and maintenance of the software.
Different naming is used for functions in programming languages, e.g. as subroutines, procedures or methods.
Python language defines function by a def statement. The function syntax looks:
def function-name(Parameter list): statements, i.e. the function body
Function bodie can contain one or more return statement. It can be situated anywhere in the function body. A return statement ends the function execution and returns the result, i.e. to the caller. If the return statement does not contain expression, the value None is returned.
def Fahrenheit(T_in_celsius): """ returns the temperature in degrees Fahrenheit """ return (T_in_celsius * 9 / 5) + 32 for t in (22.6, 25.8, 27.3, 29.8): print(t, ": ", fahrenheit(t))
Output:
>>> 22.6 : 72.68 25.8 : 78.44 27.3 : 81.14 29.8 : 85.64 >>>
Functions can be called with optional parameters, also named default parameters. If function is called without parameters the default values are used. The following code greets a person. If no person name is defined, it greets everybody:
def Hello(name="everybody"): """ Say hello to the person """ print("Hello " + name + "!") Hello("George") Hello()
Output:
>>> Hello George! Hello everybody! >>>
The string is usually the first statement in the function body, which can be accessed with function_name.doc. This is Docstring statement.
def Hello(name="everybody"): """ Say hello """ print("Hello " + name + "!") print("The docstring of the function Hello: " + Hello.__doc__)
Output:
>>> The function Hello docstring: Say hello >>>
The alternative way to make function calls is to use keyword parameters. The function definition stay unchanged.
def sumsub(a, b, c=0, d=0): return a - b + c - d print(sumsub(12,4)) print(sumsub(42,15,d=10))
Only keyword parameters are valid, which are not used as positional arguments. If keyword parameters don't exist, the next call to the function will need all four arguments, even if the c needs just the default value:
print(sumsub(42,15,0,10))
In above examples, the return statement exist in sumsub but not in Hello function. The return statement is not mandatory. If explicitly return statement doesn't exist in the sample code it will not show any result:
def no_return(x,y): c = x + y res = no_return(4,5) print(res)
Any result will not be displayed in:
>>>
Executing this script, the None will be printed. If a function doesn't contain expression the None will also be returned:
def empty_return(x,y): c = x + y return res = empty_return(4,5) print(res)
Otherwise the expression value following return will be returned. In this example 11 will be printed:
def return_sum(x,y): c = x + y return c res = return_sum(6,5) print(res)
Output:
>>> 9 >>>
Any function can return only one object. An object can be a numerical value – integer, float, list or a dictionary. To return i.e. three integer values, we can return a list or a tuple with these three integer values. It means that function can indirectly return multiple values. This following example calculates the Fibonacci boundary for a positive number, returns a 2-tuple. The Largest Fibonacci Number smaller than x is the first and the Smallest Fibonacci Number larger than x is next. The return value is stored via unpacking into the variables lub and sup:
def fib_intervall(x): """ returns the largest Fibonacci number, smaller than x and the lowest Fibonacci number, higher than x""" if x < 0: return -1 (old, new, lub) = (0,1,0) while True: if new < x: lub = new (old,new) = (new,old+new) else: return (lub, new) while True: x = int(input("Your number: ")) if x <= 0: break (lub, sup) = fib_intervall(x) print("Largest Fibonacci Number < than x: " + str(lub)) print("Smallest Fibonacci Number > than x: " + str(sup))
The following code presents a sample Python application that flashes an LED connected to Raspberry Pi's GPIO pin 5. One must build a circuit (LED + resistor of a proper value) and connect it to the GPIO before running the code.
This example uses a dedicated GPIO handling library (specific for hardware): RPi.GPIO
. For other IoT platforms, this may vary, i.e. Micropython uses a Machine
library instead that covers all microcontroller's hardware.
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time #Blinking function def blink(pin): GPIO.output(pin,GPIO.HIGH) time.sleep(1) GPIO.output(pin,GPIO.LOW) time.sleep(1) return #Use the Raspberry Pi GPIO pin numbers GPIO.setmode(GPIO.BOARD) #Set up GPIO output channel GPIO.setup(5, GPIO.OUT) #Blink GPIO5 5 times for i in range(0,5): blink(5) GPIO.cleanup()
Similarly to the GPIO, interrupts are hardware-specific; thus, libraries may differ among platforms. The samples present below are for Raspberry Pi.
Following Python rules for working with signals and their handlers are listed below.
The example program is shown below. It uses the alarm() function to limit the time spent waiting to open a file; very useful if the file needs to be transmitted over a serial device that may not be turned on, which would typically cause the os.open() to hang indefinitely. The best solution is to make a 5-second alarm before opening the file; if the operation takes too long, the alarm signal will be sent, and the handler will raise an exception.
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time state = 0 GPIO.setmode(GPIO.BCM) GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_UP) def interrupt_handler(channel): global state print("interrupt handler") if channel == 19: if state == 1: state = 0 print("state reset by event on pin 19") elif channel == 26: if state == 0: state = 1 print("state set by event on pin 26") GPIO.add_event_detect(26, GPIO.RISING, callback=interrupt_handler, bouncetime=200) GPIO.add_event_detect(19, GPIO.RISING, callback=interrupt_handler, bouncetime=200) while (True): time.sleep(0)
/data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/scriptingprogramming/micropythonfundamentals.txt— MISSING PAGE — /data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/scriptingprogramming/csharpfundamentals.txt— MISSING PAGE —
IoT systems and related data flows are typically structured into three basic layers 15, eventually into five 16, which is less popular and used mostly in advanced research
[46]
[47].
The lowest layer is the Perception (physical, acquisition) Layer, the intermediate is the Network Layer, and the higher is the Application Layer. The function of the Perception layer is to keep in contact with the physical environment. Devices working in this layer are designed as embedded systems with a network module. The modern embedded device includes a microcontroller, sensors, and actuators. External memories and other typical microcomputer peripherals are usually built into the microcontroller, so they do not require a special connection. Sensors are elements that convert a value of some physical parameter into an electrical signal, while actuators are elements that control environmental parameters. Sensors and actuators are interfaced with the microcontroller using different connection types, including simple digital or analogue connections or much more complex communication links and protocols. IoT nodes in the Perception layer communicate with higher layers using much more complex data transmission methods. The wire and wireless transmission protocols used between the Perception layer and other layers are described in communications_and_communicating_sut.
This chapter describes some popular internal protocols used to communicate between microcontrollers and other electronic elements called “embedded protocols”.
The embedded protocol that can be used in specific implementation depends mainly on the type of peripheral element. The method of connection and data exchange strictly depends on the kind of element. Some parts are analogue sensors that should be connected to an analogue-digital converter; some can be connected to digital pins working as inputs (for sensors) or outputs (for actuators).
Simple sensors do not implement the conversion and communication logic, and the output is just the analogue signal – voltage level, depending on the value of the measured parameter. It needs to be further converted into a digital representation; this process can be made by the Analogue to Digital Converters (ADC), implemented as the internal part of a microcontroller or separate integrated circuit. Examples of sensors with analogue output are a photoresistor, thermistor, potentiometer, and resistive touchscreen. ADC conversion is a process of conversion of the continuous-time signal into a discrete one. It has 2 crucial parameters to consider:
It is worth noting to mention that each ADC has its useable input range (voltage), and the input and analogue signal should be altered accordingly. In real applications, input signal adaptation requires external electronics; thus, many ADC converters provide the ability to amplify the input signal, and it can be programmed.
Simple, true/false information can be processed via digital I/O. Most devices use positive logic, where, i.e. +5 V (TTL) or +3.3 V (the most popular, yet other voltage standards exist) presents a logical one, also referenced as HIGH. In contrast, 0V presents a logical zero, referenced as LOW. In real systems, this bounding is fuzzy. It brings some tolerance, simplifying, e.g. communication from 3.3 V output to 5 V input, without a need for the conversion (note, the reverse conversion is usually not so straightforward, as 3.3 V inputs driven by the 5V output may burn quickly). A sample sensor providing binary data is a button (On/Off).
Alternating HIGH and LOW constitutes a square wave signal, usually used as a clock signal (when symmetrical) or used to control the power delivered to the external devices with means of so-called PWM.
Elements that need more data to be transferred (e.g. displays) usually use some digital data transmission protocol. It is often a serial protocol, meaning that data is transmitted bit by bit. Serial communication can be done in three modes.
Serial data transmission can be done synchronously or asynchronously. In synchronous data transmission, bits are synchronized with a clock signal common to the transmitter and receiver. Examples of synchronous protocols are TWI (Two Wire Interface) and SPI (Serial Peripheral Interface). Asynchronous data transmission does not need any separate synchronization signal, but the transmitter and receiver must use the exact timings and synchronization information must be included in the information transmitted. Examples of asynchronous interfaces implemented in microcontrollers are 1-Wire and UART (Universal Asynchronous Receiver Transmitter).
The PWM signal controls the energy delivered to the device, usually a DC motor, LED light, bulb, etc.
To control voltage, instead of using inefficient resistance-based voltage dividers (where the remaining part of the voltage is distracted as heat), PWM is based on approximating the energy delivered to the device with periodical switching on and off (HIGH and LOW). Only two voltages are delivered to the device: low (0V) and HIGH (Vcc, e.g. +5V). One can easily observe how PWM works, e.g. when dimming the LED, if recorded with a high fps camera: the LED light flashes with the PWM signal frequency.
PWM controls, in fact, the ratio between HIGH and LOW signals in one period: the higher the ratio, the more energy is being delivered to the device. It is called a duty cycle. A perfect square wave signal, usually referenced as a clock signal, has a duty cycle of 50% (or 0.5); thus, its energy is half of the energy that can be carried when the signal is HIGH all the time. An LED light with a duty cycle of 100% will be fully bright, and with a duty cycle of 0 will be off.
PWM signal is then characterised by the following:
In microcontrollers, PWM used to be generated with timers and interrupts to ensure asynchronous operation and stability of the operation. Due to the digital nature of the signal generation, a duty cycle generation precision is given by the PWM timer resolution. An 8-bit resolution splits a period into 256 chunks, and a single chunk defines the minimum time one can increment or decrement the duty cycle. Modern MCUs provide developers with much higher resolution, even up to 14-bit.
A frequency of 5kHz is equivalent to 0.2ms period that can be controlled in steps of 0.2/256 ms ~= 781 ns
.
Sample visualisation of the 5kHz PWM signal (3.3V) is presented in the following figures, with a duty cycle of, respectively:
A voltage delivered to the device powered with a PWM signal can be calculated as an integral of the PWM signal over time: e.g., a 50% duty cycle of the 5V signal is equivalent to the delivery of the constant 2.5V.
One of the most popular interfaces to connect different external devices is SPI (Serial Peripheral Interface). It is a synchronous serial interface and protocol that can transmit data with speeds up to 20 Mbps. SPI is used to communicate microcontrollers with one or more peripheral devices over short distances – usually internally in the device. High transmission speed enables SPI to be suitable even for sending animated video data to colourful displays. In SPI connection, there is always one master device, in most cases the microcontroller (μC) that controls the transmission, and one or more slave devices – peripherals. To communicate, SPI uses three lines common to all connected devices and one enabling line for every slave element.
The MISO line is intended to send bits from slave to master, the MOSI wire transmits data from master to slave, and the SCK line sends clock pulses that synchronize data transmission. The master device always generates the clock signal. Every SPI-compatible device has the SS (Slave Select) input that enables communication in this specific device. Master is responsible for generating this enable signal – separately for every slave in the system.
SPI is used in many electronic elements:
Due to different hardware implementations, there are four SPI protocol operation modes. The mode used in the master must fit the mode implemented in the slave device.
It results in different timings of the clock signal concerning the data sent. Clock polarity = 0 means that the idle state of the SCK is 0, so every data bit is synchronised with the pulse of logic 1. Clock polarity = 1 reverses these states. Output edge (rising/falling) says at which edge of active SCK signal sender puts a bit on the data line. The data capture edge says at what edge of SCK signal data should be captured by the receiver.
Even if a 20MHz frequency ensures good transmission speed, it can be too slow for some use. Some modern microcontrollers use external flash memory for program storage and execute programs from internal SRAM memory, downloading executable code in chunks as required. This requires a higher data rate to avoid stalls in program execution. A QSPI (Quad-SPI) link was developed to achieve higher transmission speed. It has four bidirectional data lines instead of two unidirectional to increase speed four times. Additionally, it supports higher clock frequency, increasing speed even higher, currently more than 100MBps. Operation of QSPI requires a special protocol with a set of commands, so hardware implementation is much more complex than the original SPI.
TWI (Two Wire Interface) is one of embedded systems' most popular communication links and protocols. Philips has designed it as an I2C (Inter-Integrated Circuit) for audio-video appliances controlled by the microprocessor. Many chips can be connected to the processor with this interface, including:
TWI, as the name says, uses two wires for communication. One is the data line (SDA); the second is the clock line (SCL). Both lines are common to all circuits connected to the one TWI bus. The method of communication of TWI is the master-slave synchronous serial transmission. It means that data is sent bit after bit synchronised with the clock signal. The SCL line is always controlled by the master unit (usually the microcontroller); the signal on the SDA line is generated by the master or one of the slaves – depending on the direction of communication. The frequency rate of the transmission is up to 100 kHz for most of the chips; for some, it can be higher – up to 400 kHz. The new implementation allows an even higher frequency rate, reaching 5 MHz. At the output side of units, the lines have the open-collector or open-drain circuit. This means that external pullup resistors are needed to ensure the proper operation of the TWI bus. The value of these resistors depends on the number of connected elements, the speed of transmission, and the power supply voltage. It can be calculated with the formulas presented, e.g. in the Texas Instrument Application Report [48]. Usually, it is assumed between 1 kΩ and 4.7 kΩ.
The data is sent using frames of bytes. Every frame begins with a sequence of signals called the start condition. Slaves detect this sequence, which causes them to collect the next eight bits that form the address byte – unique for every circuit on the bus. If one of the slaves recognises its address remains active until the end of the communication frame, others become inactive. To inform the master that some unit has been appropriately addressed slave responses with the acknowledge bit – it generates one bit of low level on the SDA line (the master generates clock pulse). After sending the proper address, data bytes are sent. The direction of the data bytes is controlled by the last bit of the address; for 0, data is transmitted by the master (Write), and for 1, data is sent by the slave (Read). The receiving unit must acknowledge every full byte (eight bits). There is no limitation on the number of data bytes in the frame; for example, samples from the AD converter can be read continuously byte after byte. At the end of the frame, another special sequence is sent by the master–stop condition. It is also possible to generate another start condition without the stop condition. It is called a repeated start condition.
Address byte only activates one chip on the bus, so every unit must have a unique physical address. This byte usually consists of three elements: a 4-bit field fixed by the producer. This 3-bit field can be set by connecting three pins of the chip to 0 (ground) or 1 (power supply line), a 1-bit field for setting the direction of communication (R/#W). Some elements (e.g. EEPROM memory chips) use the 3-bit field for internal addressing, so only one such circuit can be connected to one bus. There are no special rules for the data bytes. The first data byte sent by the master can be used to configure the slave chip. In memory units, it is used for setting the internal address of the memory for writing or reading in multi-channel AD converters to choose the analogue input. Detailed information on the meaning of every bit of the transmission is present in the documentation of the specific integrated circuit. The I2C standard also defines the multi-master mode, but in most small projects, there is one master device only.
1-Wire is a master-slave communication asynchronous bus interface designed formerly by Dallas Semiconductor Corp[49]. It can transmit data at long distances at the cost of transmission speed. The typical data speed of the 1-Wire interface is about 16.3 kbit/s, and the maximum length is approx. 300m. Name 1-Wire comes from the feature that the data line can directly power elements connected to the bus. A network chain of 1-Wire devices consists of one master device and many slave devices. Such a chain is called a MicroLAN. 1-Wire devices may be a part of a product's circuit board, a single component device such as a temperature probe, or a remote device for monitoring purposes.
Each 1-Wire device must contain a logic unit to operate on the bus. A dedicated bus converter is needed to connect a 1-wire bus to a PC. The most popular PC/1-Wire converters use a USB plug to connect to the PC and the RJ11 connectors (telephones 6P2C/6P4C modular plugs) for MicroLAN. 1-Wire devices can also be connected directly to the microcontroller boards.
Within the MicroLAN, there is always one master device, typically a PC or a microcontroller unit. The master always initiates activity on the bus to avoid collisions on the network chain. If a collision occurs, the master device retries the communication. In the 1-Wire network, many devices can share the same bus line. To identify devices in the MicroLAN, each connected device has a unique 64-bit ID number. The ID number's least significant byte defines the type of the device (temperature, voltage, etc.). The most significant byte represents a standard 8-bit CRC. The 1-Wire protocol description contains several broadcast commands and commands used to address the selected device. The master sends a selection command, then the address of the selected slave device. This way, the following command is executed only by the addressed device. The 1-Wire bus implements an enumeration procedure that allows the master to get information about the ID numbers of all connected slave devices to the MicroLAN network. The device address includes the device type, identifying what type of slaves are connected to the network chain. The 64-bit address space is searched as a binary tree. It makes it possible to find up to 75 devices per second.
The physical implementation of the 1-Wire network is based on an open drain master device connected to one or more open drain slaves. One single pull-up resistor for all devices pulls the bus up to 3/5 V and can be used to power the slave devices. 1-Wire communication starts when a master or slave sets the bus to low voltage (connects the pull-up resistor to ground through its output MOSFET).
The 1-Wire protocol allows for bursting the communication speed up by 10 factors. In this case, the master starts a transmission with a reset pulse, pulling down the data line to 0 volts for at least 480 µs. It resets all slave devices in the network chain bus. Then, any slave device shows it exists, generating the “presence” pulse. It holds the data line low for at least 60 µs after the master releases the bus. To send a “1”, the bus master sends a 1–15 µs low pulse. To send a “0”, the master sends a 60 µs low pulse. The negative edge of the pulse is used to start a slave's monostable multivibrator. The slave's multivibrator clocks to read the data bus about 30 µs after the falling edge. The slave's multivibrator has analogue tolerances that affect its timing accuracy, for the “0” pulses are 60 µs long, and “1” pulses are limited to a max of 15 µs. When the designed solution doesn't contain a dedicated 1-Wire interface peripheral, a UART can be used as a 1-Wire master. Dallas also offers Serial or USB “bridge” chips, which are very useful when the distance between devices is long (greater than 100 m). For longer, up to 300 m buses, the simple twisted pair telephone cable can be used. It will require adjustment of pull-up resistances from 5 kΩ to 1 kΩ. The basic sequence is a reset pulse followed by an 8-bit command, and after it, data can be sent/received in groups of 8-bits. In the case of transmission errors, the weak data protection 8-bit CRC checking procedure can be used.
To find the devices, the enumeration broadcast command must be sent by a master. The slave device responds with all ID bits to the master, and at the end, it returns a 0.
The Dallas/Maxim integrated 1-Wire devices list contains a wide range of implementations. The 1-Wire protocol can be quickly implemented into the current IoT boards; most manufacturers share the software libraries, allowing developers to include them in their projects in C, C++, and assembly languages. The 1-Wire sensors (temperature, humidity, pressure, etc.) are factory calibrated and reading the physical measurements follows the International System of Units (SI). 1-Wire products can be grouped as follows:
UART name is an abbreviation of Universal Asynchronous Receiver Transmitter. It is one of the most often used communication methods, traditionally named serial interface or serial port. In contrast to previously presented interfaces, UART uses direct point-to-point communication. UART is the communication unit implemented in microcontrollers rather than the communication protocol. It sends the series of bits via the TxD pin and receives a stream of bits with the RxD pin. It is important to remember that pin TxD from one device should be connected to pin RxD in another device. This is a general rule, but please always check the documentation for some non-standard markings.
The transmission speed and bit duration must be the same at the transmitter and receiver to properly transmit data. Although the transmission speed can be freely chosen, some standard, commonly used baud rates exist. They differ from 300 to 115200 bits per second. Higher baud rates are also available in modern microcontrollers, like 230400, 250000, 500000, 1M, 2M or 3Mbps. In UART, data is sent in frames. The frame begins with the start bit of value “zero”. Next, from five to eight data bits are transmitted. Next, an optional parity bit can appear. The frame is finished with the stop bit of value “one”. Stop bit can be prolonged to 1.5 or 2 times the standard bit duration. After at least one stop bit, the next frame can be sent, beginning with a start bit. Start and stop bits are used to synchronise the receiver and transmitter.
UART, namely Serial Port, is used in many modern microcontrollers to upload the executable program, debug, and as the standard input/output for the user interface. For example, in Arduino, functions that operate on the serial port are included in a common set of built-in functions.
IoT hardware infrastructure is mainly inherited from the embedded systems of the SoC type for Edge class IoT devices and from PCs for Fog class. As IoT devices are by their nature network-enabled, many of the existing embedded platforms evolved towards network-enabled solutions, sometimes indirectly through delivering network communication module (wired or wireless) as an external device yet integrated on the development board (e.g. Arduino Uno with Ethernet Networking shield, GSM shield, etc.), sometimes a new system, integrating networking capabilities in one SoC (e.g. Espressif SoCs). More advanced devices that require OS to operate preliminarily benefited from externally connected peripheral network interfaces via standard wired ports like USB (i.e. early versions of the Raspberry Pi, where WiFi card was delivered as USB stick), currently, usually integrate most of the network interfaces in a single board (e.g. RPi 4, including Ethernet, WiFi and Bluetooth). Still, in the case of the Fog class devices, those are separate chips to the CPU, and they communicate over, e.g. PCI or ISB protocol.
A microcontroller with network capabilities is the key, but not the only element forming an IoT node device. Additional elements, including sensors and actuators, are needed to keep in touch with the environment.
In the following chapters, there is a description of the families of popular microcontrollers, sensors and actuators:
Finally, in the last sub-chapter, there is an introduction to the powering of IoT devices:
The IoT market is an emerging one. New hardware solutions appear almost daily, while others disappear quickly. At the moment of writing the first version of this book (2016-2019), some hardware solutions that seemed to be prominent for at least a couple of years existed. After a few years, while the 2nd edition of the publication is being prepared (2023–2025), most of the hardware solutions described previously are still present on the market, even strengthening their position and having modernized and improved versions (e.g. ESP32 as the successor of ESP8266). However, some other platforms increased their popularity, mainly because of their appearance in the VSCode programming environment with PlatformIO, and what is even more important, the possibility of writing programs in the Arduino model. In the following sections, a short review of these platforms is provided.
The following chapters are dedicated to the families of the devices, describing their main features:
A sensor is an element that can turn a physical outer stimulus into an output signal, which can then be used for further analysis, management, or making decisions. People also use sensors like eyes, ears and skin to gain information about the outer world and act according to their aims and needs. Sensors can be divided into many categories according to the measured parameter of the environment.
Usually, every natural phenomenon – temperature, weight, speed, etc. – needs specially customised sensors that can change phenomena into electric signals, usually the voltage, that microprocessors or other devices could use. Sensors can be divided into many groups according to the physical nature of their operations – touch, light, an electrical characteristic, proximity and distance, angle, environment and other sensors.
A pushbutton is an electromechanical sensor that connects or disconnects two points in a circuit when force is applied. The button output discrete value is either HIGH or LOW.
A microswitch, also called a miniature snap-action switch, is an electromechanical sensor that requires very little physical force and uses a tipping-point mechanism. Microswitch has three pins, two of which are connected by default. When the force is applied, the first connection breaks and one of the pins is connected to the third pin.
The most common use of a pushbutton is as an input device. Both force solutions can be used as simple object detectors or as end switches in industrial devices. The button can be connected to any available digital pin that must be configured as input or input with a pullup. In the configuration presented in the figure below, a pull-up resistor is connected externally, so enabling it in the microcontroller is unnecessary.
An example code:
int buttonPin = 2; //Initialization of a push button pin number int buttonState = 0; //A variable for reading the push button status void setup() { Serial.begin(9600); //Begin serial communication pinMode(buttonPin, INPUT); //Initialize the push button pin as an input } void loop() { //Read the state of the pin where a button is connected //it is LOW if a button is pressed, HIGH otherwise //the buttonState variable holds the compliment state of the buttonPin buttonState = !digitalRead(buttonPin); //Check if the push button is pressed. If it is, the buttonState variable is HIGH if (buttonState == HIGH) { //Print out text in the console Serial.println("The button state variable is HIGH - it is pressed."); } else { Serial.println("The button state variable is LOW - it is not pressed."); } delay(10); //Delay in between reads for stability }
A force sensor predictably changes resistance depending on the applied force to its surface. Force-sensing resistors are manufactured in different shapes and sizes, and they can measure not only direct force but also tension, compression, torsion and other types of mechanical forces. Because the force sensor changes its resistance linearly, it should be connected to the analogue input. It is also required o connect another resistor to form the voltage divider, as shown in the figure below. The voltage is measured by the internal ADC of the microcontroller.
Force sensors are used as control buttons, object presence detectors, or to determine weight in electronic scales.
An example code:
//Force Sensitive Resistor (FSR) is connected to the analogue 0 pin int fsrPin = A0; //The analog reading from the FSR resistor divider int fsrReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the FSR analogue pin as an input pinMode(fsrPin, INPUT); } void loop(void) { //Read the resistance value of the FSR fsrReading = analogRead(fsrPin); //Print Serial.print("Analog reading = "); Serial.println(fsrReading); delay(10); }
Capacitive sensors are a range of sensors that use capacitance to measure changes in the surrounding environment. A capacitive sensor consists of a capacitor that is charged with a certain amount of current until the threshold voltage. A human finger, liquids or other conductive or dielectric materials that touch the sensor can influence the sensor's charge time and voltage level. Measuring charge time and voltage level gives information about changes in the environment. Ready-to-use sensors include an electronic element that performs measurements and returns digital information at the output so that they can be connected directly to a digital input pin.
Capacitive sensors are used as input devices and can measure proximity, humidity, fluid level and other physical parameters or serve as an input for electronic device control.
//Capacitive sensor is connected to the digital 2 pin int touchPin = 2; //The variable that stores digital value read from the sensor boolean touchReading = LOW; //The variable that stores the previous state of the sensor boolean lastState = LOW; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the capacitive sensor analogue pin as an input pinMode(touchPin, INPUT); } void loop() { //Read the digital value of the capacitive sensor touchReading = digitalRead(touchPin); //If the new touch has appeared if (currentState == HIGH && lastState == LOW){ Serial.println("Sensor is pressed"); delay(10); //short delay } //Save the previous state to see relative changes lastState = currentState; }
A photoresistor is a sensor that perceives light waves from the environment. The resistance of the photoresistor is changing depending on the intensity of light. The higher the intensity of the light, the lower the sensor's resistance. A light level is determined by applying a constant voltage through the resistor to the sensor forming a voltage divider, and measuring the resulting voltage. Photoresistors are cheap, but the resulting resistance is influenced by temperature and changes slowly, so they are used in applications where speed and accuracy are not crucial. Photoresistors are often used in energy-effective street lighting control.
Photoresistor connected, as it is shown in the figure above, gives a lower voltage level while the light is more intense. Results can be read with the following example code. The value will be just a number not expressed in any units, e.g. Lux. To express light intensity in luxes, additional calculations must be encoded in the program.
//Define an analog A0 pin for photoresistor int photoresistorPin = A0; //The analogue reading from the photoresistor int photoresistorReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin of a photoresistor as an input pinMode(photoresistorPin, INPUT); } void loop() { //Read the value of the photoresistor photoresistorReading = analogRead(photoresistorPin); //Print out the value of the photoresistor reading to the serial monitor Serial.println(photoresistorReading); delay(10); //Short delay }
A photodiode is a sensor that converts light energy into electrical current. A current in the sensor is generated by exposing a p-n junction of a semiconductor to the light. Information about the light intensity can be determined by measuring a voltage level. Photodiodes react to changes in light intensity very quickly, so they can be used ad receivers of light-based data transmission systems (e.g. fibre data communication). Solar cells are just large photodiodes.
Photodiodes are used as precise light-level sensors, receivers for remote control, electrical isolators (optocouplers), and proximity detectors.
Although the photodiode can generate current, the schematic in the figure above shows its connection similar to the photoresistor in the previous example. In such a circuit, the photodiode changes its current according to a change in light intensity, resulting in the voltage change at the microcontroller's analogue input. As in the example for a photoresistor, the higher the light intensity, the lower the voltage. An example code:
//Define an analog A0 pin for photodiode int photodiodePin = A0; //The analogue reading from the photodiode int photodiodeReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin of a photodiode as an input pinMode(photodiodePin, INPUT); } void loop() { //Read the value of the photodiode photodiodeReading = analogRead(photodiodePin); //Print out the value of the photodiode reading to the serial monitor Serial.println(photodiodeReading); delay(10); //Short delay }
The phototransistor is a normal bipolar transistor with a transparent enclosure that exposes the base-emitter junction to light. In a bipolar transistor, the current that passes through the collector and emitter depends on the base current. In the phototransistor, the collector-emitter current is controlled with light. A phototransistor is slower than a photodiode but can conduct more current; additionally, it amplifies the incoming signal. In specific conditions, if the light is completely off or intense enough to make the output current maximal, a phototransistor can be considered a light-controlled electronic switch (e.g. in optocouplers which are usually connected to digital inputs of the microcontroller).
Phototransistors are used as optical switches, proximity sensors and electrical isolators.
An example code:
//Define an analog A1 pin for phototransistor int phototransistorPin = A1; //The analogue reading from the phototransistor int phototransistorReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin of a phototransistor as an input pinMode(phototransistorPin, INPUT); } void loop() { //Read the value of the phototransistor phototransistorReading = analogRead(phototransistorPin); //Print out the value of the phototransistor reading to the serial monitor Serial.println(phototransistorReading); delay(10); //short delay }
An optocoupler is a device that combines light-emitting and receiving devices in one package. Mostly it is a combination of the infrared light-emitting diode (LED) and a phototransistor. There are three main types of optocouplers:
An example code:
int optoPin = A0; //Initialize an analogue A0 pin for optocoupler int optoReading; //The analogue value reading from the optocoupler int objecttreshold = 1000; //Object threshold definition int whitetreshold = 150; //White colour threshold definition void setup () { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin of the optocoupler as an input pinMode(optoPin, INPUT); } void loop () { optoReading = analogRead(optoPin); //Read the value of the optocoupler Serial.print ("The reading of the optocoupler sensor is: "); Serial.println(optoReading); //When the reading value is lower than the object threshold if (optoReading < objecttreshold) { Serial.println ("There is an object in front of the sensor!"); //When the reading value is lower than the white threshold if (optoReading < white threshold) { Serial.println ("Object is in white colour!"); } else { //When the reading value is higher than the white threshold Serial.println ("Object is in dark colour!"); } } else { //When the reading value is higher than the object threshold Serial.println ("There is no object in front of the sensor!"); } delay(500); //Short delay }
This type of sensor gives information about the colour of the light illuminating the sensor surface. Because computers often use RGB (red, green, blue) colour scheme sensor returns three values representing the intensity of three components. Colour sensors usually contain white LEDs to illuminate the surface, which colour should be distinguished by them. The colour sensor uses SPI or TWI interface to send readings. Some models of colour sensors include an additional gesture detector which recognises simple gestures (up, down, left, right).
#include <Wire.h> #include "Adafruit_TCS34725.h" // Example code for the TCS34725 library by Adafruit // Sensor class Adafruit_TCS34725 rgb_sensor = Adafruit_TCS34725(); void setup(void) { Serial.begin(9600); if (rgb_sensor.begin()) { //Initialise RGB sensor Serial.println("RGB sensor present"); } else { Serial.println("No TCS34725 found"); while (1); } } void loop(void) { uint16_t r, g, b, unfiltered, lux; rgb_sensor.getRawData(&r, &g, &b, &unfiltered); //read RGB and unfiltered light intensity lux = rgb_sensor.calculateLux(r, g, b); //calculate illuminance in Lux Serial.print("Lux: "); //print calculated Lux value Serial.print(lux, DEC); Serial.print(" - "); Serial.print("R: "); //print red component value Serial.print(r, DEC); Serial.print(" "); Serial.print("G: "); //print green component value Serial.print(g, DEC); Serial.print(" "); Serial.print("B: "); //print blue component value Serial.print(b, DEC); Serial.print(" "); Serial.print("C: "); //print unfiltered sensor value Serial.print(unfiltered, DEC); Serial.println(" "); delay(1000); }
Electrical characteristic sensors are used to measure the voltage and amperage of the electric current. When the voltage and current sensors are used concurrently, the consumed power of the device can be determined. Electrical characteristic sensors can determine whether the device's circuit is working properly. Different sensor circuits must be used to measure direct current (DC) and alternating current (AC). If the parameters of the mains are to be measured, it must be done using transformers for safety reasons.
A voltage sensor is a device or circuit for voltage measurement. A simple DC (direct current) voltage sensor consists of a voltage divider circuit with an optional amplifier for a very small voltage measure. For measuring the AC (alternating current), the input is connected to the rectifier diode or bridge to rectify AC to DC and a capacitor to flatten the voltage. The resulting voltage can be measured with an analogue, digital converter of the microcontroller. For safety, while measuring the mains voltage, an optoelectrical isolator should be added at the output, or a transformer to additionally lower the voltage at the input.
A voltage sensor can detect a power failure and measure if the voltage is in the range required. IoT applications include monitoring appliances, power lines, and power supplies.
The example code:
//Define an analogue A1 pin for voltage sensor int voltagePin = A1; //The result of the analogue reading from the voltage sensor int voltageReading; float vout = 0.0; float vin = 0.0; float R1 = 30000.0; // 30 kΩ resistor float R2 = 7500.0; // 7.5 kΩ resistor void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin as an input pinMode(voltagePin, INPUT); } void loop() { //Read the value of the voltage sensor voltageReading = analogRead(voltagePin); vout = (voltageReading * 5.0) / 1024.0; vin = vout / (R2/(R1+R2)); Serial.print("Voltage is: "); //Print out the value of the voltage to the serial monitor Serial.println(vin); delay(10); //Short delay }
A current sensor is a device or a circuit for current measurement. A simple DC sensor consists of a high-power resistor with low resistance. The current value is obtained by measuring the voltage on the resistor and applying a formula derived from Ohm's law. Other non-invasive measurement methods involve hall effect sensors for DC and AC and inductive coils (current transformer) for AC. Current sensors are used to determine the power consumption and to detect whether the device is turned on or shorted.
Make a photo of the current transformer.
The example code:
//Define an analogue A0 pin for current sensor const int currentPin = A0; //Scale factor of the sensor use 100 for 20 A Module and 66 for 30 A Module int mVperAmp = 185; int currentReading; int ACSoffset = 2500; double Voltage; double Current; void setup(){ Serial.begin(9600); } void loop(){ currentReading = analogRead(currentPin); Voltage = (currentReading / 1024.0) * 5000; //Gets you mV Current = ((Voltage - ACSoffset) / mVperAmp); //Calculating current value Serial.print("Raw Value = " ); //Shows pre-scaled value Serial.print(currentReading); Serial.print("\t Current = "); //Shows the voltage measured //The '3' after current allows to display 3 digits after the decimal point Serial.println(Current,3); delay(1000); //Short delay
An infrared (IR) proximity sensor is used to detect objects and to measure the distance to them without any physical contact. IR sensor consists of an infrared emitter, a receiving sensor or array of sensors and a signal processing logic. The output of a sensor differs depending on the type – simple proximity detection sensor outputs HIGH or LOW level when an object is in its sensing range, but sensors which can measure distance output an analogue signal or use some communication protocol, like I2C to send sensor measuring results. IR sensors are used in robotics to detect obstacles located a few millimetres to several meters from the sensor and in mobile phones to help detect accidental screen touching.
An example code:
int irPin = A0; //Define an analogue A0 pin for IR sensor int irReading; //The result of an analogue reading from the IR sensor void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analogue pin of an IR sensor as an input pinMode(irPin, INPUT); } void loop() { //Read the value of the IR sensor irReading = analogRead(irPin); //Print out the value of the IR sensor reading to the serial monitor Serial.println(irReading); delay(10); //Short delay }
The ultrasonic sensor measures the distance to objects by emitting short ultrasound sound pulse and measuring its returning time. The sensor consists of an ultrasonic emitter and receiver; sometimes, they are combined into a single device for emitting and receiving. Ultrasonic sensors can measure greater distances and cost less than infrared sensors, but are more imprecise and interfere which each other measurement if more than one is used. Simple sensors have a trigger pin and an echo pin; when the trigger pin is set high for a small amount of time, ultrasound is emitted, and on the echo pin, response time is measured. Ultrasonic sensors are used in car parking sensors and robots for proximity detection.
An example code:
int trigPin = 2; //Define a trigger pin D2 int echoPin = 4; //Define an echo pin D4 void setup() { Serial.begin(9600); //Begin serial communication pinMode(trigPin, OUTPUT); //Set the trigPin as an Output pinMode(echoPin, INPUT); //Set the echoPin as an Input } void loop() { digitalWrite(trigPin, LOW); //Clear the trigPin delayMicroseconds(2); //Set the trigPin on HIGH state for 10 μs digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); //Read the echoPin, return the sound wave travel time in microseconds duration = pulseIn(echoPin, HIGH); //Calculating the distance distance = duration*0.034/2; //Printing the distance on the Serial Monitor Serial.print("Distance: "); Serial.println(distance); }
The motion detector is a sensor that detects moving objects, mostly people. Motion detectors use different technologies, like passive infrared sensors, microwaves and the Doppler effect, video cameras, and ultrasonic and IR sensors. Passive IR (PIR) sensors are the simplest motion detectors that sense people by detecting changes in IR radiation that is emitted through the skin. When the motion is detected, the output of a motion sensor is a digital HIGH/LOW signal.
Motion sensors are used in security alarm systems, automated lights and door control. As an example in IoT, the PIR motion sensor can be used to detect motion in security systems in a house or any building.
An example code:
//Passive Infrared (PIR) sensor output is connected to the digital 2 pin int pirPin = 2; //The digital reading from the PIR output int pirReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the PIR digital pin as an input pinMode(pirPin, INPUT); } void loop(void) { //Read the digital value of the PIR motion sensor pirReading = digitalRead(pirPin); //Print out Serial.print("Digital reading = "); Serial.println(pirReading); if(pirReading == HIGH) { //Motion was detected Serial.println("Motion Detected"); } delay(10); }
A level sensor detects the level of fluid or fluidised solid. Level sensors can be divided into two groups:
Fluid level sensors can be used for smart waste management, for measuring tank levels, diesel fuel gauging, liquid assets inventory, chemical manufacturing, high or low-level alarms, and irrigation control.
An example code:
int levelPin = 6; //Liquid level sensor output is connected to the digital 6 pin int levelReading; //Stores level sensor detection reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(levelPin, INPUT); //Initialize the level sensor pin as an input } void loop(void) { levelReading = digitalRead(levelPin); //Read the digital value of the level sensor Serial.print("Level sensor value: "); //Print out Serial.println(levelReading); delay(10); //Short delay }
A potentiometer is a type of resistor, the resistance of which can be adjusted using a mechanical lever. The device consists of three terminals. The resistor between the first and the third terminal has a fixed value, but the second terminal is connected to the lever. Whenever the lever is turned, a slider of the resistor is moved; it changes the resistance between the second terminal and side terminals. Variable resistance causes the change of the voltage, which can be measured to determine the position of the lever. Thus, the potentiometer output is an analogue value.
Potentiometers are commonly used as a control level, for example, a volume level for the sound and joystick position. They can also be used to determine the angle in feedback loops with motors, such as servo motors.
An example code:
//Potentiometer sensor output is connected to the analogue A0 pin int potentioPin = A0; //The analogue reading from the potentiometer output int potentioReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the potentiometer analogue pin as an input pinMode(potentioPin, INPUT); } void loop(void) { //Read the analogue value of the potentiometer sensor potentioReading = analogRead(potentioPin); Serial.print("Potentiometer reading = "); //Print out Serial.println(potentioReading); delay(10); }
An IMU is an electronic device consisting of an accelerometer, gyroscope and sometimes a magnetometer. The combination of these sensors returns the object's orientation in 3D space. IMU sensors can present the object's current position and movement, expressed with at most six values called the DOF (Degrees Of Freedom). Three values express the linear movements that can be measured by the accelerometer:
Another three values present the rotation in three axes that can be measured by gyroscope:
A gyroscope is a sensor that measures the angular velocity. The sensor is made with microelectromechanical system (MEMS) technology and is integrated into the chip. The sensor output can be analogue or digital, using I2C or SPI interface. Gyroscope microchips can vary in the number of axes they can measure. The available number of the axis is 1, 2 or 3 axes in the gyroscope. A gyroscope is commonly used with an accelerometer to precisely determine the device's orientation, position and velocity. Gyroscope sensors are used in aviation, navigation and motion control.
An accelerometer measures the acceleration of the object. The sensor uses microelectromechanical system (MEMS) technology, where capacitive plates are attached to springs. When acceleration force is applied to the plates, the capacitance is changed; thus, it can be measured. Accelerometers can have 1 to 3 axis. The 3-axis accelerometer can detect the device's orientation, shake, tap, double tap, fall, tilt, motion, positioning, shock or vibration. Outputs of the sensor are usually digital interfaces like I2C or SPI. The accelerometer is often used with a gyroscope for precise measurement of the object's movement and orientation in space. Accelerometers are used to measure objects' vibrations, including cars, industrial devices, and buildings, and to detect volcanic activity. In IoT applications, it can also be used for accurate motion detection for medical and home appliances, portable navigation devices, augmented reality, smartphones and tablets.
A magnetometer is a sensor that can measure the device's orientation to the Earth's magnetic field. A magnetometer is used as a compass in outdoor navigation for mobile devices, robots, and quadcopters.
There are different elements available that allow measuring linear accelerations, angular accelerations, and magnetic fields, all in three axes. There exist elements that combine two (called 6-axis or 6-DOF) or all (9-axis, 9-DOF) measurement units. Popular integrated circuits are MPU6050 (3-axes gyro + 3-axes accelerometer), MPU9250, and BNO055 (3-axes gyro, 3-axes accelerometer, 3-axes magnetometer). All of them can be programmed in an Arduino environment with the use of dedicated libraries. The latter automatically calculates additional information like gravity vector, and absolute orientation expressed as an Euler vector or as a quaternion.
Take a photo of MPU6050.
Take a photo of MPU9250.
The example code:
//Library for I2C communication #include <Wire.h> //Downloaded from https://github.com/adafruit/Adafruit_Sensor #include <Adafruit_Sensor.h> //Downloaded from https://github.com/adafruit/Adafruit_BNO055 #include <Adafruit_BNO055.h> #include <utility/imumaths.h> Adafruit_BNO055 bno = Adafruit_BNO055(55); void setup(void) { bno.setExtCrystalUse(true); } void loop(void) { //Read sensor data sensors_event_t event; bno.getEvent(&event); //Print X, Y And Z orientation Serial.print("X: "); Serial.print(event.orientation.x, 4); Serial.print("\tY: "); Serial.print(event.orientation.y, 4); Serial.print("\tZ: "); Serial.print(event.orientation.z, 4); Serial.println(""); delay(100); }
A temperature sensor is a device that is used to determine the temperature of the surrounding environment. Most temperature sensors work on the principle that the material's resistance changes depending on its temperature. The most common temperature sensors are:
The main difference between sensors is the measured temperature range, precision and response time. Temperature sensor usually outputs the analogue value, but some existing sensors have a digital interface [50]. The thermistor can have a positive (PTC) or negative (NTC) thermal coefficient. For PTC, resistance rises with rising temperature, while resistance decreases in higher temperatures for NTC. An analogue thermistor must calculate the value read if the result should be presented in known units. Digital temperature sensors usually express the result in Celsius degrees or other units.
Temperature sensors are most commonly used in environmental monitoring devices and thermoelectric switches. In IoT applications, the sensor can be used for greenhouse temperature monitoring, warehouse temperature monitoring to avoid freezing, fire suppression systems and tracking the temperature of the soil, water and plants.
An example code:
//Thermistor sensor output is connected to the analogue A0 pin int thermoPin = 0; //The analogue reading from the thermistor output int thermoReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the thermistor analogue pin as an input pinMode(thermoPin, INPUT); } void loop(void) { //Read the analogue value of the thermistor sensor thermoReading = analogRead(thermoPin); Serial.print("Thermistor reading = "); //Print out Serial.println(thermoReading); delay(10); }
Digital temperature sensors automatically convert the temperature reading into some known unit, e.g. Celsius, Fahrenheit or Kelvin Degrees. Digital thermometers use one of the popular communication links. An example of a digital thermometer is DS18B20 by Dallas Semiconductors. It uses a 1-Wire communication protocol.
Take DS18B20 photo DS18B20 photo Draw DS18B20 connection DS18B20 connection
#include <OneWire.h> // library for 1-Wire protocol #include <DallasTemperature.h> //library for Dallas DS18B20 digital thermometer const int SENSOR_PIN = 13; //DS18B20 pin OneWire oneWire(SENSOR_PIN); //oneWire class DallasTemperature tempSensor(&oneWire); // connect oneWire to DallasTemperature library float tempCelsius; // temperature in Celsius degrees void setup() { Serial.begin(9600); //initialize serial port tempSensor.begin(); //initialize DS18B20 } void loop() { tempSensor.requestTemperatures(); //command to read temperatures tempCelsius = tempSensor.getTempCByIndex(0); //read temperature (in Celsius) Serial.print("Temp: "); Serial.print(tempCelsius); // print the temperature Serial.println(" C"); delay(1000); }
A humidity sensor (hygrometer) is a sensor that detects the amount of water or water vapour in the environment. The most common principle of air humidity sensors is the change of capacitance or resistance of materials that absorb moisture from the environment. Soil humidity sensors measure the resistance between the two electrodes. The resistance between electrodes is influenced by soluble salts and water amounts in the soil. The output of a humidity sensor is an analogue signal value or digital value sent with some popular protocols [51].
Examples of IoT applications are monitoring of humidors, greenhouse humidity, agriculture, art gallery and museum environment.
An example code [52]:
#include <dht.h> dht DHT; #define DHT_PIN 7 void setup(){ Serial.begin(9600); } void loop() { int chk = DHT.read11(DHT_PIN); Serial.print("Humidity = "); Serial.println(DHT.humidity); delay(1000); }
A sound sensor is a sensor that detects vibrations in a gas, liquid or solid environment. At first, the sound wave pressure makes mechanical vibrations, which transfer to changes in capacitance, electromagnetic induction, light modulation or piezoelectric generation to create an electric signal. The electrical signal is then amplified to the required output levels. Sound sensors can be used to record sound and detect noise and its level.
Sound sensors are used in drone detection, gunshot alert, seismic detection and vault safety alarms.
An example code:
//Sound sensor output is connected to the digital 7 pin int soundPin = 7; //Stores sound sensor detection readings int soundReading = HIGH; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the sound detector module pin as an input pinMode(soundPin, INPUT); } void loop(void) { //Read the digital value whether the sound has been detected soundReading = digitalRead(soundPin); if (soundPin==LOW) { //When sound detector detected the sound Serial.println("Sound detected!"); //Print out } else { //When the sound is not detected Serial.println("Sound not detected!"); //Print out } delay(10); }
Gas sensors are a sensor group that can detect and measure the concentration of certain gasses in the air. The working principle of electrochemical sensors is to absorb the gas and create current from an electrochemical reaction. For process acceleration, a heating element can be used. For each type of gas, different kind of sensor needs to be used. Multiple types of gas sensors can also be combined in a single device. The single gas sensor output is an analogue signal, but devices with multiple sensors used have a digital interface. The smoke or air pollution sensors usually use LED or laser that emits light and a detector which is normally shaded from the light. If there are particles of smoke or polluted air inside the sensor, the light is reflected by them, which can be observed by the detector.
Gas sensors are used for safety devices, air quality control, and manufacturing equipment. IoT applications include air quality control management in smart buildings and smart cities or toxic gas detection in sewers and underground mines.
An example code:
int gasPin = A0; //Gas sensor output is connected to the analog A0 pin int gasReading; //Stores gas sensor detection reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(gasPin, INPUT); //Initialize the gas detector pin as an input } void loop(void) { gasReading = analogRead(gasPin); //Read the analog value of the gas sensor Serial.print("Gas detector value: "); //Print out Serial.println(gasReading); delay(10); //Short delay }
The smoke sensors usually emit LED light, and a detector is normally shaded from the light. If there are particles of smoke present inside the sensor, the light is reflected by them, which can be observed by the detector. Smoke detectors are used in fire alarm systems. The air pollution sensors usually use a laser directed onto the detector. Between the laser and detector, the thin stream of air flows and pollution particles create shades on the detector. Thus the detector can distinguish the sizes of particles and count the number of them. Air pollution sensors are used in air purifiers and in air quality measurement stations to monitor current air conditions, mainly in cities. Because the air pollution sensor generates more data, the serial connection is often used for reading measurement results. An example of an air pollution sensor that can count particles of PM1.0, PM2.5, and PM10 is PMS5003.
Draw PMS5003 connection PMS5003 photo Draw PMS5003 connection PMS5003 connection
An example code that uses the PMS5003 sensor:
#define length 31 //Size of the data frame from the sensor //(does not include the start indicator - 0x42) unsigned char buffer[length]; int PM_1_0=0; //variale to hold PM1.0 value of the air sensor int PM_2_5=0; //variale to hold PM2.5 value of the air sensor int PM_10=0; //variale to hold PM10 value of the air sensor void setup() { //Initialise the serial port for 9600 b/s (should be compatible with the sensor type) Serial.begin(9600); } void loop() { if(Serial.find(0x42)) //detecting 0x42 means start of data frame from sensor { Serial.readBytes(buffer,length); if(buffer[0] == 0x4d) { PM_1_0=((buffer[3]<<8) + buffer[4]); //count PM1.0 value of the air detector module PM_2_5=((buffer[5]<<8) + buffer[6]);//count PM2.5 value of the air detector module PM_10 =((buffer[7]<<8) + buffer[8]); //count PM10 value of the air detector module Serial.print("PM1.0: "); Serial.print(PM_1_0); Serial.println(" ug/m3"); Serial.print("PM2.5: "); Serial.print(PM_2_5); Serial.println(" ug/m3"); Serial.print("PM10: "); Serial.print(PM_10); Serial.println(" ug/m3"); } } }
Air pressure sensors can measure the absolute pressure in the surrounding environment. Some popular sensors use a piezo-resistive sensing element which is then connected to the amplifier and analogue, digital converter. Frint-end uses the logic to interface the microcontroller. Usually, barometric sensor readings depend on the temperature, so they include the temperature sensor for temperature compensation of the pressure. Popular examples of barometric sensors are BME280 and BMP280. Both include barometric sensors and temperature sensors built in for compensation and possible measurement, while BME280 also includes a humidity sensor. Communication with these sensors is done with I2C or SPI bus.
Barometric sensors are used in weather stations, home automation for heating, venting, air conditioning (HVAC), and airflow measurement. Because air pressure varies with altitude, they are often used in altimeters.
Take BME280 photo BME280 photo Draw BME280 connection BME280 connection
An example code of BME280 use is below [53]:
#include <Arduino.h> #include <Bme280.h> Bme280TwoWire sensor; void setup() { Serial.begin(9600); Wire.begin(D2, D1); Serial.println(); sensor.begin(Bme280TwoWireAddress::Primary); sensor.setSettings(Bme280Settings::indoor()); } void loop() { auto temperature = String(sensor.getTemperature()) + " °C"; auto pressure = String(sensor.getPressure() / 100.0) + " hPa"; auto humidity = String(sensor.getHumidity()) + " %"; String measurements = temperature + ", " + pressure + ", " + humidity; Serial.println(measurements); delay(10000); }
A Hall effect sensor detects strong magnetic fields, their polarities and the relative strength of the field. In the Hall effect sensors, a magnetic force influences current flow through the semiconductor material and creates a measurable voltage on the sides of the semiconductor. Sensors with analogue output can measure the strength of the magnetic field, while digital sensors give HIGH or LOW output value, depending on the presence of the magnetic field.
Hall effect sensors are used in magnetic encoders for speed and rotation measurements. They can replace mechanical switches in keyboards and proximity switches because they do not require contact, which ensures high reliability. An example application can be sensing the position of rotary valves.
The example code:
int hallPin = A0; //Hall sensor output is connected to the analogue A0 pin int hallReading; //Stores Hall sensor reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(hallPin, INPUT); //Initialize the Hall sensor pin as an input } void loop(void) { hallReading = analogRead(hallPin); //Read the analogue value of the Hall sensor Serial.print("Hall sensor value: "); //Print out Serial.println(hallReading); delay(10); //Short delay }
A GPS receiver is a device that can receive information from a global navigation satellite system and calculate its position on the Earth. A GPS receiver uses a constellation of satellites and ground stations to compute position and time almost anywhere on Earth. GPS receivers are used for navigation only in the outdoor area because they need to receive signals from the satellites, which is difficult inside the buildings. The GPS location's precision can vary depending on the number of visible satellites, weather conditions, and current satellites' placement. Often the GPS receiver is connected to a microcontroller with a serial communication port and sends information according to the NMEA scheme.
A GPS receiver is used for device location tracking. Real applications might be, e.g., pet, kid or personal belonging location tracking.
The example code [54]:
#include <SoftwareSerial.h> SoftwareSerial SoftSerial(2, 3); unsigned char buffer[64]; //Buffer array for data receive over serial port int count=0; //Counter for buffer array void setup() { SoftSerial.begin(9600); //The SoftSerial baud rate Serial.begin(9600); //The Serial port of Arduino baud rate. } void loop() { if (SoftSerial.available()) //If data is coming from software serial port // ==> Data is coming from SoftSerial shield { while(SoftSerial.available()) //Reading data into char array { buffer[count++]=SoftSerial.read(); //Writing data into array if(count == 64)break; } Serial.write(buffer,count); //If no data transmission ends, //Write buffer to hardware serial port clearBufferArray(); //Call clearBufferArray function to clear //The stored data from the array count = 0; //Set the counter of the while loop to zero } if (Serial.available()) //If data is available on hardware serial port // ==> Data is coming from a PC or notebook SoftSerial.write(Serial.read()); //Write it to the SoftSerial shield } void clearBufferArray() //Function to clear buffer array { for (int i=0; i<count;i++) { buffer[i]=NULL; } //Clear all content of an array with NULL }
An output device is a unit that changes an electrical signal coming from the microcontroller into the physical parameter. It can generate or modify light, sound, force, pressure and other physical values that influence other devices nearby or the surrounding environment. Some output elements can be connected directly to the microcontroller's pins, and some require higher voltage or current, so they need an additional electronic circuit called the driver. Output devices can be divided into groups based on the physical phenomenon they control. Popular output devices include LEDs, displays, motors (actuators), speakers, and buzzers.
Unlike the other diodes, the light-emitting diode, also called LED, is a special type that emits light. LED has a completely different body which is made of transparent plastic that protects the diode and lets it emit light. Like the other diodes, LED conducts the current in only one way, so connecting it to the scheme is essential. There are two safe ways how to determine the direction of the diode:
The LED is one of the most efficient light sources. Unlike incandescent light bulbs, LED transforms most of the power into light, not warmth; it is more durable, works for a more extended period and can be manufactured in a smaller size.
The LED colour is determined by the semiconductors material. Diodes are usually made from silicon, then LEDs are made from elements like gallium phosphate, silicon carbide and others. Because the semiconductors used are different, the voltage needed for the LED to shine is also different.
When the LED is connected to the voltage and turned on, a huge current starts to flow through it, and it can damage the diode. That is why all LEDs have to be connected in series with a current-limiting resistor.
Current limiting resistors resistance is determined by three parameters:
To calculate the resistance needed for a diode, this is what you have to do.
An example of the blinking LED code:
int ledPin = 8;//Defining the pin of the LED void setup() { pinMode(ledPin,OUTPUT); //The LED pin is set to output } void loop() { //Set pin output signal to HIGH – LED is working digitalWrite(ledPin,HIGH); //Belay of 1000 ms delay(1000); //Set pin output signal to LOW – LED is not working digitalWrite(ledPin,LOW); //Delay of 1000 ms delay(1000); }
LED's brightness can be controlled with a PWM signal as described in the “Manipulating analogue signals” chapter. There exist LEDs with more than one light-emitting chip in one enclosure. They are made as two-coloured or RGB elements with coloured controlled separately. There are two internal configurations of such elements:
[✓ ktokarz, 2023-08-22]picture with common anode and common cathode LEDs
Digital RGB LED Digital LED does not have anode or cathode connections available externally. They have power supply pins and two pins for data transmission, one for input, second for output. The input accepts the digital signal from the microcontroller to set the brightness of all three internal LEDs. Output is used to connect the input of another LED to form a series of LEDs. Digital LEDS are available as single elements but also as strips, rings or matrices that can be controlled by a microcontroller with one pin only. What is important every LED can shine in different colours allowing it to create interesting visual effects. An example of a popular digital LED is WS2812. A special protocol is used to transmit data. One LED requires 24 bits (1 byte for red, 1 for green, and 1 for blue) to set the colour. After receiving its data LED resends any further byte to the following LEDs in the chain. There are software libraries for Arduino and other platforms available.
Using a display is a quick way to get feedback information from the device. There are many display technologies. For IoT solutions, low-power, easy-to-use displays are used:
7-segment LED display
[✓ ktokarz, 2023-08-22] 7-segment LED display
The seven-segment LED display is built with seven LEDs forming the shape that makes it possible to display symbols similar to digits, and even some letters. Usually, the eighth LED is added as the decimal point. 7-segment displays can have similar internal connections as RGB LEDs, common anode or common cathode. If there is more than one digit in the element all the same segments are also connected together. Such displays need special controllers or the part of the software that displays separate digits in a sequence one by one. To avoid unnecessary blinking or difference in the brightness of digits, software for sequential displays is written with the use of timers and interrupts. As for the RGB LEDs, 7-segment displays need a separate resistor for every segment.
LED matrix display
[✓ ktokarz, 2023-08-22] LED matrix display
LED matrix displays offer the possibility of displaying not only digits and letters but also some pictograms and symbols. The most popular versions have 8 rows and 8 columns, or 7 rows and 5 columns, but it is possible to find other configurations. As for the 7-segment displays, there are common anode and common cathode configurations. For a common anode, all anodes in one row and all cathodes in one column are connected together. For a common cathode, all cathodes in one row and all anodes in one column are connected together. Modern LED matrix displays have built-in controllers or are made with the use of digital RGB LEDs which makes them possible to display pictures and even videos.
photo of P6 matrix display
Liquid-Crystal Display (LCD)
Monochrome LCD uses modulating properties of liquid crystal to block the passing-through light. Thus when a voltage is applied to a pixel, it is dark. A display consists of layers of electrodes, polarising filters, liquid crystals and a reflector or back-light. Liquid crystals do not emit light directly but through reflection or backlight. Because of this reason, they are more energy efficient. Small, monochrome LCDs are widely used in devices to show a little numerical or textual information like temperature, time, device status etc. The most popular LCD device is an alphanumerical 2×16 characters display based on the HD44780 controller. There also exist graphic monochrome and colour TFT displays that use LCD technology. LCD modules commonly come with an onboard control circuit and are controlled through parallel or serial interfaces.
The example code:
#include <LiquidCrystal.h> //include LCD library //Define LCD pins const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Create an LCD object with predefined pins LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); //Set up the LCD's number of columns and rows lcd.print("hello, world!"); //Print a message to the LCD } void loop() { //Set the cursor to column 0, line 1 – line 1 is the second row //Since counting begins with 0 lcd.setCursor(0, 1); //Print the number of seconds since the reset lcd.print(millis() / 1000); }
Organic Light-Emitting Diode Display (OLED)
OLED display uses electroluminescent materials that emit light when the current passes through these materials. The display consists of two electrodes and a layer of an organic compound. OLED displays are thinner than LCDs, have higher contrast, and can be more energy efficient depending on usage. OLED displays are commonly used in mobile devices like smartwatches and cell phones, and they are replacing LCDs in other devices. OLED displays come as monochrome or RGB colour devices. Small OLED display modules usually have an onboard control circuit that uses digital interfaces like I2C or SPI.
//Add libraries to ensure the functioning of OLED #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); void setup() { //Setting up initial OLED parameters display.begin(SSD1306_SWITCHCAPVCC, 0x3C, false); display.setTextSize(1); //Size of the text display.setTextColor(WHITE); //Colour of the text – white void loop() { //Print out on display output sensor values display.setCursor(0, 0); display.clearDisplay(); display.print("Test of the OLED"); //Print out the text on the OLED display.display(); delay(100); display.clearDisplay(); }
Electronic Ink Display (E-Ink)
E-ink display uses charged particles to create a paper-like effect. The display comprises transparent microcapsules filled with oppositely charged white and black particles between electrodes. Charged particles change their location depending on the orientation of the electric field; thus, individual pixels can be either black or white. The image does not need power to persist on the screen; power is used only when the image is changed. Thus e-ink display is very energy efficient. It has high contrast and viewing angle but a low refresh rate. E-ink displays are commonly used in e-readers, smartwatches, outdoor signs, and electronic shelf labels.
#include <SmartEink.h> #include <SPI.h> E_ink Eink; void setup() { //BS LOW for 4 line SPI pinMode(8,OUTPUT); digitalWrite(8, LOW); Eink.InitEink(); Eink.ClearScreen();//Clear the screen Eink.EinkP8x16Str(14,8,"IoT e-ink example"); Eink.EinkP8x16Str(10,8,"IoT-open.eu"); Eink.EinkP8x16Str(6,8,"0123456789"); Eink.EinkP8x16Str(2,8,"9876543210"); Eink.RefreshScreen(); } void loop() { }
[pczekalski]Add paper colourfully technologies
Relays are electromechanical devices that use electromagnets to connect or disconnect the plates of a switch. Relays are used to control high-power circuits with low-power circuits. Circuits are electrically isolated and thus protect logic control. Relays are used in household appliance automation, lighting and climate control. Although the electromagnet's coil of the relay requires relatively low power compared to the power capability of the output circuit, it cannot be connected directly to the microcontroller's pin. Creating the transistor driver or using a relay module with the driver built-in is possible.
The example code:
#define relayPin 4 //Define the relay pin void setup() { Serial.begin(9600); pinMode(relayPin, OUTPUT); //Set relayPin to output } void loop() { digitalWrite(relayPin,0); //Turn relay on Serial.println("Relay ON"); //Output text delay(2000); // Wait 2 seconds digitalWrite(relayPin,1); //Turns relay off Serial.println("Relay OFF"); delay(2000); }
Solenoids are devices that use electromagnets to pull or push iron or steel core. They are used as linear actuators for locking mechanisms indoors, pneumatic and hydraulic valves and in-car starter systems.
Solenoids and relays both use electromagnets, and connecting them to Arduino is very similar. Coils need a lot of power, and they are usually attached to the circuit's power source with the use of a transistor driver. Turning the coil's power off makes the electromagnetic field collapse and creates a very high voltage. A shunt diode is used to channel the overvoltage for the semiconductor devices' protection. For extra safety, an optoisolator can be used.
The example code:
#define solenoidPin 4 //Define the solenoid pin void setup() { Serial.begin(9600); pinMode(solenoidPin, OUTPUT); //Set solenoidPin to output } void loop() { digitalWrite(solenoidPin,0); //Turn solenoid on Serial.println("Solenoid ON"); //Output text delay(2000); //Wait 2 seconds digitalWrite(solenoidPin,1); //Turns solenoid off Serial.println("Solenoid OFF"); delay(2000); }
Speakers are electroacoustic devices that convert electrical signals into sound waves. A speaker uses a permanent magnet and a coil attached to the membrane. Sound signal, flowing through the coil, creates the electromagnetic field with variable strength; coil attracts to magnet according to the strength of the field, thus making a membrane vibrate and creating a sound wave. Another widely used speaker technology, called piezo speaker, uses piezoelectric materials instead of magnets. Speakers are used to create an audible sound for human perception and ultrasonic sound for sensors and measurement equipment. Some speakers are created to generate acoustic tones of a single, fixed frequency. Such elements are called buzzers and have a built-in generator to emit sound if the voltage is on. Elements without built-in generators should be controlled with the frequency signal coming from the microcontroller. Sound-generating devices require more power than LED, so there is a need to check if the operating current is lower than the maximum current of the microcontroller's pin as specified in technical documentation. If yes, the additional amplifying element is required, e.g. transistor.
const int speakerPin = 9; //Define the piezo speaker pin void setup() { pinMode(speakerPin, OUTPUT); //Set spekaer pin as an output } void loop() { tone(speakerrPin, 1000); //Send 1 kHz sound signal delay(1000); //For 1 s noTone(speakerPin); //Stop sound delay(1000); //For 1 s }
Actuators are devices that can do a physical action to the surrounding world. Most actuators are based on one of the forms of electric motors, sometimes directly, sometimes using a gearbox and advanced control logic.
An electric motor is an electromechanical device which can turn electrical energy into mechanical energy. The motor turns because the electricity that flows in its winding generates a magnetic field that inducts the mechanical force between the winding and the magnet. Electric motors are made in many variants, of which the simplest is the permanent-magnet DC motor.
DC motor is a device which converts direct current into mechanical rotation. DC motor consists of permanent magnets in the stator and coils in the rotor. Applying the current to coils creates an electromagnetic field, and the rotor tries to align itself to the magnetic field. Each coil is connected to a commutator, which in turn supplies coils with current, thus ensuring continuous rotation. Some motors have a tachometer functionality as the loopback signal that generates a pulse train of frequency proportional to the rotation speed. Tacho signal can be connected to a digital or interrupt input of a microcontroller allowing for determining actual rotation speed. DC motors are widely used in power tools, toys, electric cars, robots, etc.
void setup () { pinMode(5,OUTPUT); //Digital pin 5 is set to output //The function for turning on the motor is defined #define motON digitalWrite(5,HIGH) //The function for turning off the motor is defined #define motOFF digitalWrite(5,LOW) } void loop () { motON; //Turn on the motor }
The H-bridge has earned its name because of its resemblance to the capital ‘H’ wherein all the corners are switches, and the electric motor is in the middle. This bridge is usually used for operating permanent-magnet DC motors, electromagnets and other similar elements because it allows operating with significantly bigger current devices using a small current. By switching the switches, it is possible to change the motor direction. It is important to remember that the switches must be turned on and off in pairs.
When all of the switches are turned off, the motor is in free movement. It is not always acceptable, so two solutions can be implemented. If both positive or negative switches are turned on at the top or at the bottom, then the motor coil is shorted, not allowing it to have a free rotation – it is slowed down faster. The fastest option to stop the motor is to turn the H-bridge in the opposite direction for some period of time. Remember! Neither of these braking mechanisms is good for the H-bridge or the power source because of excessive current appearance. That is why this action is unacceptable without a particular reason because it can damage the switches or the power source. The motor management can be reflected in the table.
The complicated part is the realisation of switches, usually implemented as relays or appropriate power transistors. The biggest drawback of relays is that they can only turn the engine on or off. Transistors must be used if the rotation speed needs to be regulated using the pulse width modulation. The MOSFET-type transistors should be used to ensure a large amount of power. Nowadays, the stable operation of the bridge is ensured by adding extra elements. All elements can be encapsulated in a single integrated circuit, e.g. L293D.
The L293D microchip consists of two H-bridges and is made for managing two motors. It has separate control pins for the left and right branches avoiding the power short circuit if connected properly. An example schematic diagram of connecting the chip to the Arduino Uno board can be seen in the following figure. Notice that using a PWM signal allows to control the rotation speed.
The example code:
int dirPin1 = 7; //1st direction pin int dirPin2 = 8; //2nd direction pin int speedPin = 5; //Pin responsible for the motor speed void setup () { pinMode (dirPin1,OUTPUT); //1st direction pin is set to output pinMode (dirPin2,OUTPUT); //2nd direction pin is set to output pinMode (speedPin,OUTPUT); //Speed pin is set to output } void loop () { analogWrite(speedPin, 100); //Setting motor speed //Speed value can be from 0 to 255 int motDirection = 1; //Motor direction can be either 0 or 1 if (motDirection) //Setting motor direction {//If 1 digitalWrite(dirPin1,HIGH); digitalWrite(dirPin2,LOW); } else {//If 0 digitalWrite(dirPin1,LOW); digitalWrite(dirPin2,HIGH); } }
A bidirectional DC motor, usually controlled with an H-bridge and equipped with thread gear, can be used to implement the linear actuators.
Linear actuators used to be equipped with end position detectors such as switches, or eventually, their end positions can be detected with overload detection. A simple linear actuator is present in figure 107.
Stepper motors can be moved by a certain angle or step. The full rotation of the motor is divided into small, equal steps. Stepper motor has many individually controlled electromagnets; turning them on or off makes a motor shaft rotate by one step. Changing the switching speed or order can precisely control the rotation's angle, direction or speed. Because of their exact control ability, they are used in CNC machines, 3D printers, scanners, hard drives etc. An example of use can be found in the source [55].
The example code:
#include <Stepper.h> //Include library for stepper motor int in1Pin = 12; //Defining stepper motor pins int in2Pin = 11; int in3Pin = 10; int in4Pin = 9; //Define a stepper motor object Stepper motor(512, in1Pin, in2Pin, in3Pin, in4Pin); void setup() { pinMode(in1Pin, OUTPUT); //Set stepper motor control pins to output pinMode(in2Pin, OUTPUT); pinMode(in3Pin, OUTPUT); pinMode(in4Pin, OUTPUT); Serial.begin(9600); motor.setSpeed(20); //Set the speed of stepper motor object } void loop() { motor.step(5); //Rotate 5 steps }
The servomotor includes the internal closed-loop position feedback mechanism that precisely controls its position angle. To set the angle, the PWM technique is used. Additionally, it is possible to control the speed of angle change, acceleration and deceleration of the rotation. Servomotors have limited rotation angles depending on their type, e.g. 90, 180 or 270 degrees. A typical servo is 180 degrees (usually a bit lower). Servo powering depends on size; micro servos are typically between 4.8V and 6V. Larger servos require higher voltage and more current to operate. There are two standards for controlling servos, so-called “analogue” and “digital”. Analogue servos are controlled with a PWM signal of 50Hz (20ms period), while digital servos, even if backwards compatible with analogue, can be controlled with a PWM signal up to 250Hz. A duty cycle (a ratio between HIGH and LOW) controls servo position. We focus below on analogue servomotors, controlled with a 50Hz PWM signal, but the transition to digital ones is straightforward. Note digital servos used to have individual configurations of the control signals, and it is necessary to refer to the documentation of the particular model for correct timings. From the image 110, it can be seen that the length of the servomotor impulse cycle is 20 ms, but the impulse length itself is 1 ms or 2 ms. These signal characteristics are true for most enthusiast-level servomotors but should be verified for each module in the manufacturer specification, e.g. to obtain a full rotation of 180 degrees, it may be necessary to go beyond standard 1ms↔2ms duty cycle. The servomotor management chain meets the impulse every 20 ms, but the pulse width shows the position that the servomotor has to reach. For example, 1 ms corresponds to the 0° position but 2 ms – to the 180° position against the starting point. When entering the defined position, the servomotor will keep it and resist any outer forces that are trying to change the current position. The graphical representation is in the image 110.
Just like other motors, servomotors have different parameters, where the most important one is the time of performance – the time that is necessary to change the position to the defined position. The best enthusiast-level servomotors do a 60° turn in 0.09 s. There are three types of servomotors:
Unfortunately, using Arduino, the servomotor is not as easily manageable as the DC motor. For this purpose, a special servomotor management library Servo.h
has been created. Using PWM signal in other MCUs may involve the use of hardware or software timers and may impact other features as the number of hardware timers used to be limited. Thus Servo.h
implementation may vary between microcontrollers and SDKs.
The example code:
#include <Servo.h> //Include Servo library Servo servo; //Define a Servo object void setup () { servo.attach(6); //Connect servo object to pin D6 servo.write(90); //Set position of servo to 90° Serial.begin(9600); } void loop () { servo.write(110); //Set position of servo to 110° delay(200); //wait for 200 ms servo.write(70);//Set position of servo to 70° delay(200); //Wait for 200 ms }
/data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/hardware2/powering.txt— MISSING PAGE — /data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/networking2.txt— MISSING PAGE — /data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/iotprogramming2.txt— MISSING PAGE — /data01/virt87891/domeenid/www.robolabor.ee/htdocs/homelab/data/pages/en/iot-open/frameworkstools2.txt— MISSING PAGE —
It is worth nothing to mention that even every second, there grow new IoT ideas, hardware, software and applications. Because of that, technical, specific knowledge, mostly on hardware and software, becomes rapidly outdated. Moreover, due to the amount of information related to embedded systems development and IoT development, it is impossible to assemble all information regarding the IoT world.
The IOT-OPEN.EU project is instantly evolving and always brings new content, but it cannot be the only source of knowledge in the current stage of development. We distribute all content via a single starting point, the website http://iot-open.eu, but we suggest navigating to the online resources presented below. Those projects, websites and resources are not related to our project. Still, we consider them a valuable source.
Please also note even if the IOT-OPEN.EU project is CC BY-NC licenced, resources juxtaposed below may need an access fee, registration and so on.
Many online platforms provide online courses by different universities about relevant topics like the Internet of Things, embedded systems, programming languages, connectivity and security, robotics, big data, computer vision and many more. Some of the most popular platforms are Coursera [56], edX [57], Udacity [58], Udemy [59], Skillshare [60]. Some of these courses are free of charge, and at the end of these courses, a certificate about skills can be acquired (often for an additional price).
Electronics Tutorials website [61] offers multiple basic electronics tutorials topics, including AC and DC circuit theory, amplifiers, semiconductors, filters, Boolean algebra, capacitors, power electronics, transistors, operational amplifiers, sequential logic, and many more. It contains an extensive description of the theory with graphics and explanations.
Embedded Experts website [62] focuses on commercial, certified courses mainly related to the embedded platforms. It may be helpful for studying technologies related to the Edge Class and Fog Class devices that are fundamental for IoT and bare metal IoT development.
Instructables [63] is a project platform with plenty of Internet of Things projects for different knowledge levels. It is also possible to enrol on other classes with many lessons that teach about specific related topics that are not limited only to electronics but also cover issues such as sewing, food, craft, 3D printing, etc. One section of the Instructables website offers multiple contests and challenges about the related topic with valuable prizes.
Tinkercad is a simple, online 3D design and 3D platform that also allows to model and test circuits [64]. With Tinkercad, it is possible to program and simulate a virtual Arduino board online using different libraries and serial monitors. There are also plenty of already existing starter examples.
Wokwi [65] is an online (in-browser) IoT device simulator. You can implement some simple and limited approaches still going beyond embedded systems. Note it is not a distant lab (such as our IOT-OPEN.EU VREL lab [66]) but rather a software simulation of the IoT hardware development boards, so your experience will be limited.
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.
This page keeps track of the content reviews and versions done as a continuous maintenance process
This book and its offshoots were prepared to provide comprehensive information about the Internet of Things on the engineering level.
Its goal is to introduce IoT to bachelor students, master students, technology enthusiasts and engineers willing to extend their current knowledge with the latest hardware and software achievements in the scope of the Internet of Things.
This book is also designated for teachers and educators willing to prepare a course on IoT.
We (Authors) assume that persons willing to study this content possess some general knowledge about IT technology, i.e. understand what an embedded system is, know the general idea of programming (in C/C++) and are aware of wired and wireless networking as it exists nowadays.
This book constitutes a comprehensive manual for IoT technology; however, it is not a complete encyclopedia nor exhausts the market. The reason for it is pretty simple – IoT is so rapidly changing technology that new devices, ideas and implementations appear daily. Once you read this book, you can quickly move over the IoT environment and market, easily chasing ideas and implementing your IoT infrastructure.
We also believe this book will help adults that took their technical education some time ago to update their knowledge.
We hope this book will let you find brilliant ideas in your professional life, see a new hobby, or even start an innovative business.
Playing with real or virtual hardware and software is always fun, so keep going!
ThisBook was implemented under the Erasmus+ KA2 projects:
Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views only of the author, and the Commission cannot be held responsible for any use which may be made of the information contained therein.
Copyright Notice
This content was created by the IOT-OPEN.EU Consortium: 2016–2019 and IOT-OPEN.EU Reloaded Consortium 2022-2025.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.
In case of commercial use, please contact IOT-OPEN.EU Reloaded Consortium representative.
Here comes the Internet of Things. The name that recently makes red-hot people in business, researchers, developers, geeks and … students. The name that non-technology related people consider a kind of magic and even a danger to their privacy. The EU set the name as one of the emerging technologies and estimated the worldwide market will hit well over 500 billion US dollars in 2022, while the number of IoT devices in 2030 is expected to be over 3.2 billion.
What is IoT (Internet of Things), then? Surprisingly, the answer is not straightforward.
To simplify the selection of different topics, a simple colour coding was introduced, indicating the skills required to cover particular topics. Colour codes are organized in the form of colour bars enclosing chapter titles.
Explanation:
Let us roll back to the 1970s first. In 1973 the first RFID device was patented. This device was the key enabling technology even if it does not look nor remind modern IoT devices. The low power (actually here passive) solution with a remote antenna large enough to collect energy from the electromagnetic field and power the device brought an idea of uniquely identifiable items. That somehow mimics well-known EAN barcodes and the evolution used nowadays, like QR codes, but every single thing has a different identity here. In contrast, EAN barcodes present a class of products, not an individual one. The possibility to identify a unique identity remotely became fundamental to the IoT as it's known today. RFID is not the only technology standing behind IoT. In the 1990s, the rapid expansion of wireless networks, including broadband solutions like cellular-based data transfers with their consequent generations, enabled connecting devices in various, even distant geographical locations. Parallelly an exponential increase in the number of devices connected to the global Internet network was observed, including the smartphone revolution that started around mid the first decade of the XXI century. On the hardware level, microchips and processors became physically smaller and more energy efficient yet offering growing computing capabilities and memory size increase, along with significant price drops. All those facts drove the appearance of small, network-oriented, cheap and energy-efficient electronic devices. In recent years, the development of efficient AI technologies even boosted IoT applications.
The phrase “Internet of Things” was used for the first time in 1999 by Kevin Ashton – an expert on digital innovation. Formally IoT was introduced by the International Telecommunication Union (ITU) in the ITU Internet report in 2005 [67]. The understanding and definitions of IoT changed over the years, but now all agree that this cannot be seen as a technology issue only. According to IEEE “Special Report: Internet of Things” [68] released in 2014, IoT is:
IEEE Definition of IoT |
---|
A network of items – each embedded with sensors – connected to the Internet. |
It relates to the physical aspects of IoT only. The Internet of Things also addresses other aspects that cover many areas [69]:
IEEE, as one of the most prominent standardisation organisations, also works on standards related to the IoT. The primary document is IEEE P2413™ [70]. It covers the technological architecture of IoT as three-layered: sensing at the bottom, networking and data communication in the middle, and applications on the top. It is essential to understand that IoT systems are not only small, local-range systems. ITU-T has defined IoT as:
ITU-T Definition of IoT |
---|
A global infrastructure for the information society, enabling advanced services by interconnecting (physical and virtual) things based on existing and evolving interoperable information and communication technologies. |
In the book [71] by European Commission, we can read a similar description of what IoT is: “The IoT is the network of physical objects that contain embedded technology to communicate and sense or interact with their internal states or the external environment.” IoT impacts many areas of human activity: manufacturing, transportation, logistics, healthcare, home automation, media, energy saving, environment protection and many more. In this course, we will consider the technical aspects mainly.
In the IoT world, the “thing” is always equipped with some electronic element that can be as simple as the RFID tag, an active sensor sending data to the global network, or an autonomous device that can react to environmental changes. In CERP-IoT book “Visions and Challenges” [72] in the context of “Internet of Things” a “thing” could be defined as:
CERP-IoT Definition of “Thing” |
---|
A real/physical or digital/virtual entity that exists and moves in space and time and can be identified. Assigned identification numbers, names and location addresses commonly identify things. |
It is quite easy to find other terms used in the literature like “smart object”, “device”, or “nodes” [73].
One can imagine that almost everything in our surroundings is tagged with an RFID element. They do not need a power supply; they respond with a short message, usually containing the identification number. Modern RFID can achieve 6 to 7 meters of the range. Using the active RFID reader, we can quickly locate lost keys and know if we still have the butter in the fridge and in which wardrobe there is our favourite t-shirt.
If the “thing” includes the sensor, it can send interesting data about current conditions. We can sense environmental parameters like temperature, humidity, air pollution, pressure, localisation data, water level, light, noise, and movement. Using different methods and protocols, this data can be sent to the central collector that connects to the Internet and the database or cloud. There the data can be processed, and Artificial Intelligence algorithms can be used to decide actions that could be taken in different situations. Active things can also receive control signals from the central controller to control the environment: turn on/off the heating or light, water flowers, and turn on the washing machine when there is enough sunlight to generate the required electricity or charge your electric car.
This thing does not even require the controller to realise the proper decision. An autonomous vacuum cleaner can clean our house when it detects that we aren't home and the floor needs cleaning. The fridge can order our favourite beverage once the last bottle is almost empty.
Sensor Networks are a subset of the IoT devices used as a collaborative solution to grab data and send it for further processing. Opposite to the general IoT devices, Sensor Network devices do not have any actuators that can apply an action to the external world. The data flow is unidirectional, then.
IoT systems and embedded systems share almost the same domain. They frequently use the same microcontrollers, sensors and actuators, development software and even programming models. What differs between IoT and embedded systems is that IoT, on its principles, uses communication to send and receive data outside of its instance, while embedded systems do not have to. Embedded systems do not have to be network-enabled, and they do not have a unique identity frequently, while IoT devices do. Moreover, IoT systems are complex and multilayered, often introducing cloud-based parts, while embedded systems are stand-alone devices. Shortly we can say that an IoT device is network enabled embedded system.
In this chapter, there is an approach to describe modern technologies that appeared in the last few years, enabling the idea of IoT to be widely implementable. In the [74] one can read that “The confluence of efficient wireless protocols, improved sensors, cheaper processors and a wave of startups and established companies made the concept of the IoT mainstream”. Similar analysis has been done in [75] where authors write that “the latest developments in RFID, smart sensors, communication technologies and Internet protocols enable the IoT”. RFID and smart sensors need the microprocessor system to read, convert the data into digital format, and send it to the Internet using the communication protocol. This process can be done by small- and medium-scale computer (embedded) systems. These are essential elements of technologies used in IoT systems.
In recent years one can observe rapid growth in the field of microprocessors. It includes not only the powerful desktop processors but also microcontrollers – elements that are used in small-scale embedded systems. We can also notice the popularity of microprocessor systems that can be easily integrated with other factors, like sensors, and actuators, connected to the network. Essential is also the availability of programming tools and environments supported by different companies and communities. An excellent example of such a system is Arduino. Those devices are low-power, constrained devices, usually battery-powered and, in most cases, communicating wirelessly.
The same growth can be observed in the advanced constructions comparable to low-end computers. They have more powerful processors, memory and networking connectivity built-in than small-scale computer systems. They can work under the control of multitasking operating systems like Linux and Windows and embedded or real-time operating systems like FreeRTOS. Having many libraries, they can successfully work as hubs for local storage, local controllers and gateways to the Internet. Raspberry Pi and the nVidia Jetson series are examples of such systems. This category of devices frequently contains hardware accelerated (such as GPU) AI-capable solutions, i.e. nVidia Jetson Nano or Xavier series. Those devices can be battery or mains powered. Often, they are green energy powered: i.e. with a larger backup battery and energy harvesting solution (such as solar panel).
Nowadays, the Internet is (almost) everywhere. There are lots of wireless networks available in private and public places. The price of cellular access (3G/4G/5G) is low, offering a suitable data transfer performance. Connecting the “thing” to the Internet has never been so easy.
The primary paradigm of IoT is that every unit can be individually addressed. With the addressing scheme used in IPv4, it wouldn't be possible. IPv4 address space delivers “only” 4 294 967 296 of unique addresses (2^32). If you think it's a considerable number, imagine that every person in the world has one IP-connected device – IPv4 covers about half of the human population. The answer is IPv6 with a 128-bit addressing scheme that gives 3.4 × 10^38 addresses. It will be enough even if everyone has a billion devices connected to the Internet.
IoT devices generate the data to be stored and processed somewhere. If there is a couple of sensors, the amount of data is not very big, but if there are thousands of sensors generating data hundreds of times every second. The cloud can handle it – the massive place for the data with tools and applications ready to help with data processing. Some big, global clouds are available for rent, offering not only storage but also Business Intelligence tools, Artificial Intelligence analytic algorithms. There are also smaller private clouds created to cover the needs of one company only. Many universities have their own High-Performance Computing Centre.
Many people want to be connected to the global network everywhere, anytime, having their “digital twin” with them. It is possible now with small, powerful mobile devices like smartphones. Smartphones are also elements of the IoT world, being together sensors, user interfaces, data collectors, wireless gateways to the Internet, and everything with mobility features.
The technologies we mentioned here are the most recognisable. Still, there are many others, more minor, described only in the technical language in some standard description document, hidden under the colourful displays between large data centres, making our IoT world operable. In this book, we will describe some of them.
Technology development instantly shifts devices between categories. A border between Fog and Edge class devices is conventional; many can share both worlds. It depends on their purpose, application and performance configuration; thus, i.e. Raspberry Pi can be an end-node (Edge) class device and a Fog class, working as a data aggregator and analytical device.
IoT has already been defined as a network of physical things or devices that might include sensors or simple data processing units, complex actuators, and significant hybrid computing power. Today IoT systems have transitioned from being perceived as sensor networks to smart-networked systems capable of solving complex tasks in mass production, public safety, logistics, medicine and other domains, requiring a broader understanding and acceptance of current technological advancements, including advanced data processing that includes AI.
Since the very beginning of sensor networks, one of the main challenges has been data transport and data processing, where significant efforts have been put by the ICT community towards service-based system architectures. However, The current trend already provides considerable computing power even in small mobile devices. Therefore, the concepts of future IoT already shifted towards smarter and more accessible IoT devices, and data processing has become possible closer to the Fog and Edge.
Cloud-based computing is a relatively well-known and adequately employed paradigm where IoT devices can interact with remotely shared resources such as data storage, data processing, data mining and other services are unavailable to them locally because of the constrained hardware resources (CPU, ROM, RAM) or energy consumption limits. Although the cloud computing paradigm can handle vast amounts of data from IoT clusters, the transfer of extensive data to and from cloud computers presents a challenge due to limited bandwidth[76]. Consequently, there is a need to process data near data sources, employing the increasing number of smart devices with enormous processing power and a rising number of service providers available for IoT systems.
Fog computing addressed the bottlenecks of cloud computing regarding data transport while providing the needed services to IoT systems.
It is a new trend in computing that aims to process the data near the data source. Fog computing pushes applications, services, data, computing power, and decision-making away from the centralised nodes to the logical extremes of a network. Fog computing significantly decreases the data volume that must be moved between end devices and the cloud.
Fog computing enables data analytics and knowledge generation at the data source. Furthermore, the dense geographic distribution of fog helps to attain a better-localised accuracy for many applications than the cloud processing of the data [77].
The recent development of energy-efficient hardware with AI acceleration enters the fog class of the devices, putting Fog Computing in the middle of the interest of IoT application development and extending new horizons to them. Fog Computing is more energy efficient than raw data transfer to the cloud and back, and in the current scale of the IoT devices, the application is meant for the future of the planet Earth. Fog Computing usually also brings a positive impact on IoT security, i.e. sending to the cloud preprocessed and depersonalised data and providing distributed computing capabilities that are more attack resistant.
Recent development in hardware, power efficiency and a better understanding of the IoT data nature, including such aspects as, i.e. privacy and security, led to solutions where data is being processed and preprocessed right to their source in the Edge class devices. Edge data processing on end-node IoT devices is crucial in systems where privacy is essential and sensitive data is not to be sent over the network (i.e. biometric data in a raw form). Moreover, distributed data processing can be considered more energy efficient in some scenarios where, i.e. extensive, power-consuming processing can be performed during green energy availability.
According to [78], Cognitive IoT, besides a proper combination of hardware, sensors and data transport, comprises cognitive computing, which consists of the following main components:
Usually, cognitive IoT systems or C-IoT are expected to add more resilience to the solution. Resilience is a complex term and is differently explained under different contexts; however, there are standard features for all resilient systems. As a part of their resilience, C-IoT should be capable of self-failure detection and self-healing that minimises or gradually degrades the system's overall performance. In this respect, the non-resilient system fails or degrades in a step-wise manner. In case of security issues, that system should be able to change its security keys, encryption algorithms and take other measures to cope with the detected threats. Self-optimisation abilities are often considered part of the C-IoT feature list to provide more robust solutions. Recent development in the Fog and Edge class devices and the efficient software leverage cognitive IoT Systems to a new level.
All three approaches, from cloud to cognitive systems, focus on adding value to IoT devices, system users and related systems on-demand. Since market and technology acceptance of mobile devices is still growing, and the amount of produced data from those devices is growing exponentially, mobility as a phenomenon is one of the main driving forces of the technological advancements of the near future.
It is worth nothing to mention that even every second, there grow new IoT ideas, hardware, software and applications. Because of that, technical, specific knowledge, mostly on hardware and software, becomes rapidly outdated. Moreover, due to the amount of information related to embedded systems development and IoT development, it is impossible to assemble all information regarding the IoT world.
The IOT-OPEN.EU project is instantly evolving and always brings new content, but it cannot be the only source of knowledge in the current stage of development. We distribute all content via a single starting point, the website http://iot-open.eu, but we suggest navigating to the online resources presented below. Those projects, websites and resources are not related to our project. Still, we consider them a valuable source.
Please also note even if the IOT-OPEN.EU project is CC BY-NC licenced, resources juxtaposed below may need an access fee, registration and so on.
Many online platforms provide online courses by different universities about relevant topics like the Internet of Things, embedded systems, programming languages, connectivity and security, robotics, big data, computer vision and many more. Some of the most popular platforms are Coursera [79], edX [80], Udacity [81], Udemy [82], Skillshare [83]. Some of these courses are free of charge, and at the end of these courses, a certificate about skills can be acquired (often for an additional price).
Electronics Tutorials website [84] offers multiple basic electronics tutorials topics, including AC and DC circuit theory, amplifiers, semiconductors, filters, Boolean algebra, capacitors, power electronics, transistors, operational amplifiers, sequential logic, and many more. It contains an extensive description of the theory with graphics and explanations.
Embedded Experts website [85] focuses on commercial, certified courses mainly related to the embedded platforms. It may be helpful for studying technologies related to the Edge Class and Fog Class devices that are fundamental for IoT and bare metal IoT development.
Instructables [86] is a project platform with plenty of Internet of Things projects for different knowledge levels. It is also possible to enrol on other classes with many lessons that teach about specific related topics that are not limited only to electronics but also cover issues such as sewing, food, craft, 3D printing, etc. One section of the Instructables website offers multiple contests and challenges about the related topic with valuable prizes.
Tinkercad is a simple, online 3D design and 3D platform that also allows to model and test circuits [87]. With Tinkercad, it is possible to program and simulate a virtual Arduino board online using different libraries and serial monitors. There are also plenty of already existing starter examples.
Wokwi [88] is an online (in-browser) IoT device simulator. You can implement some simple and limited approaches still going beyond embedded systems. Note it is not a distant lab (such as our IOT-OPEN.EU VREL lab [89]) but rather a software simulation of the IoT hardware development boards, so your experience will be limited.
Data management is a critical task in IoT. Due to the high number of devices (things) already available, that is tens of billions, and considering the data traffic generated by each of them through, i.e. sensor networks, infotainment (soft news) or surveillance systems, mobile social network clients, and so on, we are now even beyond the ZettaByte (ZB 2^70, 10^21 bytes) era. This opened up several new challenges in (IoT) data management, giving rise to data sciences and big data technologies. Such challenges have not to be considered as main issues to solve but also as significant opportunities fuelling the digital economy with new directions such as Cloudonomics [90] and IoTonomics, where data can be considered as a utility, a commodity to manage, curate, store, and trade appropriately. Therefore, properly managing data in IoT contexts is not only critical but also of strategic importance for business players as well as for users, evolving into prosumers (producers-consumers).
From a technological perspective, the main aspects of dealing with IoT data management are:
Application domains of the Internet of Things solutions are vast. Most prominent applications include (among others) [93]:
Smart Homes are one of the first examples that come to mind when discussing Internet of Things domain applications. Smart home benefits include reduced energy wastage, the quality and reliability of devices, system security, reduced cost of basic needs, etc. Some home automation examples are environmental control systems that monitor and control heating, ventilation, air conditioning and sunscreens; electrical charging of vehicles; solar panels for electrical power and hot water; ambient lighting control, smart lighting for aquaria; home cooking and food ordering; access control (doors, garage, gate); smart plant irrigation systems (both indoors and outdoors); baby monitoring; timed pet food dispensers; monitoring perishable goods (for example, in the refrigerator); household items remote monitoring (for instance, of washer cycle status); tracking and proactive maintenance scheduling (such as, i.e. electric car charging); event-triggered task execution. Home security also plays a significant role in smart homes. Examples of applications are automatic door locks, sensors for opening doors and windows, pressure, motion and infrared sensors, security cameras, notifications about security (to the owner or the police) and fitness-related applications.
In Smart City, multiple IoT-based services are applied to different areas of urban settings. The aim of the smart city is the best use of public resources, improvement of the quality of resources provided to people and reduction of operating costs of public administration [94]. A smart city can include many solutions like smart buildings, smart grids for improving energy management, smart tourism, monitoring of the state of the roads and occupation of parking lots, public transportation optimisation, public safety, environment monitoring, automatic street lighting, signalling with smart power devices, control of water levels for hydropower or flood warnings, electricity-generating devices like solar panels and wind turbines, weather monitoring stations. Transportation in smart cities may include aviation, monitoring and forecasting of traffic slowdowns, timetables and current status, navigation and route planning, as well as vehicle diagnostics and maintenance reports, remote maintenance services, traffic accident information collection, fleet management using digital tachographs, smart parking, car/bicycle sharing services [95]. IoT in transportation makes cars interconnected, particularly in the approaching autonomous vehicles era.
Smart Grid is a digital power distribution system. This system gathers information using smart meters, sensors and other devices. After these data are processed, power distribution can be adapted accordingly. Smart grids deliver sustainable, economical and secure electricity supplies efficiently.
In Precision Agriculture and Smart Farming IoT solutions can be used to monitor the moisture of the soil and conditions of the plants, control microclimate conditions and monitor the weather conditions to improve farming [96]. The goal of using IoT in agriculture is maximising the harvest, reducing operational costs, being more efficient, and reducing environmental pollution using low-cost automated solutions. An interaction between the farmer and the systems can be done using a human-machine interface. In the future smart precision farming can be a solution for such challenges as increasing worldwide demand for food, a changing climate, and a limited supply of water and fossil fuels [97].
Internet of Food integrates many of the aforementioned techniques and encompasses different stages of the food delivery chain, including smart farming, food processing, transportation, storage, retail, and consumption. It provides more safety and improved efficiency at each food production and consumption stage, including reduced waste and increased transparency.
Similar to precision agriculture, which is part of IoT in industry, Smart Factories also tend to improve manufacturing by monitoring pollutant gas emissions, locating employees and with many other solutions.
Industrial IoT and smart factories are part of the Industry 4.0 revolution. In this model, modern factories can automate complex manufacturing tasks, thanks to the Machine-To-Machine communication model, which provides more flexibility in the manufacturing process to enable personalised, short-volume product manufacturing easily.
In the healthcare and wellness, IoT applications can monitor and diagnose patients and manage people and medical resources. It allows remote and continuous monitor the vital signs of patients to improve medical care and wellness of patients [98]. An essential part of smart welfare is wearables, including wristbands and smartwatches that monitor the activity level, heart rate and other parameters. Smart healthcare includes remote monitoring, care of patients, self-monitoring, smart pills, smart home care, Real-Time Health Systems (RTHS) and many more. Medical robotics can also be part of the healthcare IoT system that includes medical robots in precision surgery or distance surgery; some robots are used in rehabilitation and hospitals (for example, Panasonic HOSPI [99]) for delivering medication, drinks, etc. to patients.
Wearables used in IoT applications should be highly energy efficient, ultra-low power and small-sized. Wearables are installed with sensors and software for data and information collected about the user. Devices used in daily life like Fitbit [100] are used to track people's health and exercise progress in previously impossible ways, and smartwatches allow to access smartphones using this device on the wrist. But wearables are not limited only to wearing them on the wrist. They can also be glasses equipped with a camera, a sports bundle attached to the shoes or a camera attached to the helmet or as a necklace [101].
IoT hardware infrastructure is mostly inheriting from the embedded systems of the SoC type. As IoT devices are by its nature network-enabled, many of the existing embedded platforms evolved towards network-enabled solutions, sometimes indirectly through delivering network processor (wired or wireless) as a peripheral device yet integrated on the development board (i.e. Arduino Uno with Ethernet Networking shield, GSM shield, etc.), sometimes a new system, integrating networking capabilities in one SoC (i.e. Espriff SoCs). More advanced devices that require OS to operate preliminarily benefited from externally connected peripheral network interfaces via common wired ports like USB (i.e. early versions of the Raspberry Pi, where WiFi card was delivered as USB stick), currently, usually integrate most of the network interfaces in a single board (i.e. RPi 3B, including Ethernet, WiFi and Bluetooth).
IoT market is an emerging one. New hardware solutions appear almost daily, while others disappear quick. At the moment of writing this publication (2016–2019), there are some core hardware solutions that seem to be prominent for at least a couple of years, however. We've provided a short review of those platforms in the following sections:
Understanding the principals of communication are essential for further reading on hardware and programming. Most microcontrollers (including SoCs) can communicate in the protocols juxtaposed below right “out of the box”. Interfaces can be implemented in hardware or (recently) in software. Some microcontrollers may require an external, dedicated protocol converter (a chip or a module).
IoT systems are typically structured into three basic layers [102]. The lowest layer is the Perception (physical, acquisition) Layer, the intermediate is the Network Layer, and the higher is the Application Layer. The function of the perception layer is to keep contact with the physical environment. Devices working in this layer are designed as embedded systems. They include the microprocessor or microcontroller, memory, communication unit, and interfaces – sensors or actuators. Sensors are elements that convert a value of some physical parameter into an electrical signal, while actuators are elements that control environment parameters. Sensors and actuators are interfaced with the microcontroller using different connection types. This chapter describes some internal protocols used to communicate between microcontrollers and other electronic elements that can be named “embedded protocols”. Description of the protocols used for wire and wireless transmission between the perception layer and higher layers is present in communications_and_communicating_sut The embedded protocol that can be used in specific implementation depends mainly on the type of the peripheral element. Some of them use an analogue signal that the microcontroller must convert to digital internally, some directly implement digital communication protocol.
Simple sensors do not implement the conversion and communication logic, and the output is just the analogue signal – voltage level depending on the value of the parameter that is measured. It needs to be further converted into a digital representation; this process can be made by analogue to digital converters (ADC) implemented as the internal part of a microcontroller or separate integrated circuit. Examples of the sensors with analogue output are a photoresistor, thermistor, potentiometer, resistive touchscreen.
Dummy, true/false information can be processed via digital I/O. Most devices use positive logic, where, i.e. +5 V (TTL) or +3.3 V (those are the most popular, yet there do exist other voltage standards) presents a logical one, while 0V presents logical zero. In real systems this bounding is fuzzy and brings some tolerance, simplifying, i.e. communication from 3.3 V output to 5 V input, without a need of the conversion (note, the reverse conversion is usually not so straightforward, as 3.3 V inputs driven by 5V output may burn easily). A sample of the sensor providing binary data is a button (On/Off).
One of the most popular interfaces to connect the sensor is SPI (Serial Peripheral Interface). It is a synchronous serial interface and protocol that can transmit data with speed up to 20 Mbps. SPI is used to communicate microcontrollers with one or more peripheral devices over short distances – usually internally in the device. In SPI connection there is always one master device, in most cases the microcontroller (μC) that controls the transmission, and one or more slave devices – peripherals. To communicate SPI uses three lines common to all of the connected devices, and one enabling line for every slave element.
MISO is intended to send bits from slave to master, MOSI transmits data from master to slave. SCK line is used for sending clock pulses which synchronize data transmission. The clock signal is always generated by the master device. Every SPI compatible device has the SS (Slave Select) input that enables communication in this specific device. Master is responsible to generate this enable signal – separately for every slave in the system.
SPI is used in many electronic elements like analogue to digital converters (ADC), real-time clocks (RTC), EEPROMs, LCD displays, communication interfaces (e.g. Ethernet, WiFi) and many others. Due to different hardware implementations, there are four modes of operation of the SPI protocol. The mode used in master must fit the mode that is implemented in the slave device.
It results in different timings of the clock signal concerning the data sent. Clock polarity = 0 means that the idle state of the SCK is 0, so every data bit is synchronised with the pulse of logic 1. Clock polarity = 1 reverses these states. Output edge (rising/falling) says at which edge of active SCK signal sender puts a bit on the data line. Data capture edge says at what edge of SCK signal data should be captured by the receiver.
TWI (Two Wire Interface) is one of the most popular communication protocol used in embedded systems. It has been designed by Philips as I2C (Inter-Integrated Circuit) for using in the audio-video appliances controlled by the microprocessor. There are many chips that can be connected to the processor with this interface, including:
TWI, as the name says, uses two wires for communication. One is the data line (SDA); the second is the clock line (SCL). Both lines are common to all circuits connected to the one TWI bus. The method of the communication of TWI is the master-slave synchronous serial transmission. It means that data is sent bit after bit synchronised with the clock signal. SCL line is always controlled by the master unit (usually the processor), the signal on the SDA line is generated by the master or one of the slaves – depending on the direction of communication. The frequency rate of the communication is up to 100 kHz for most of the chips, for some can be higher – up to 400 kHz. The new implementation allows even higher frequency rate is reaching 5 MHz. At the output side of units, the lines have the open-collector or open-drain circuit. It means that there are external pull-up resistors needed to ensure proper operation of the TWI bus. Value of these resistors depends on the number of connected elements, speed of transmission and the power supply voltage and can be calculated with the formulas presented in Texas Instrument Application Report [103]. Usually, it is assumed between 1 kΩ and 4.7 kΩ.
The data is sent using frames of bytes. Every frame begins with the sequence of signals that is called the start condition. This sequence is detected by slaves and causes them to collect the next eight bits that form the address byte – unique for every circuit on the bus. If one of the slaves recognises its address remains active until the end of the communication frame, others become inactive. To inform the master that some unit has been appropriately addressed slave responses with the acknowledge bit – it generates one bit of low level on the SDA line (the master generates clock pulse). After sending the proper address, data bytes are sent. The direction of the data bytes is controlled by the last bit of the address, for 0 data is transmitted by the master (Write), for 1 data is sent by the slave (Read). The receiving unit must acknowledge every full byte (eight bits). There is no limitation on the number of data bytes in the frame, for example, samples from the AD converter can be read byte continuously after byte. At the end of the frame, another special sequence is sent by the master – stop condition. It is also possible to generate another start condition without the stop condition. It is called a repeated start condition.
Address byte activates one chip on the bus only, so every unit must have a unique physical address. This byte usually consists of three elements: 4-bit field fixed by the producer, 3-bit field that can be set by connecting three pins of the chip to 0 (ground) or 1 (positive supply line), 1-bit field for setting the direction of communication (R/#W). Some elements (e.g. EEPROM memory chips) uses the 3-bit field for internal addressing so there can be only one such circuit connected to one bus. There are no special rules for the data bytes. First data byte sent by the master can be used for configuration of the slave chip. In memory units, it is used for setting the internal address of the memory for writing or reading, in multi-channel AD converters to choose the analogue input. The detailed information on the meaning of every bit of the transmission is present in the documentation of the specific integrated circuit. The I2C standard also defines the multi-master mode, but in most of the small projects, there is one master device only.
1-Wire is a master-slave communication bus system designed formerly by Dallas Semiconductor Corp[104] ensuring low data transmission speed, signalling and can be powered directly by data line signals. The 1-Wire concept is similar to I²C transmission standard, but can transmit data in longer distances then I²C but with lower speed. The implementation area is very wide and typically 1-Wire protocol is used to share data between small, inexpensive devices such as a digital thermometer, humidity or pressure sensors or actuator systems. A network chain of 1-Wire devices consists of one master device and many slave devices. Such a chain is called a MicroLAN. 1-Wire devices may be a part of the circuit board within a product, could be a single component device such as temperature probe, or may be attached to a remote device for monitoring purposes. Typical data acquisition and laboratory networks use CAT-5 cables to connect 1-Wire devices together, can be mounted in a socket of small PCB boards, attached to the device which must be monitored. In such implementations, the RJ11 connectors (telephones 6P2C/6P4C modular plugs) are very popular. Each 1-Wire device must contain logic unit to operate on the bus. The 1-Wire products include temperature, voltage, current sensors, loggers, timers, battery monitors, memory and many more. To connect them to a PC the special bus converter is needed. The most popular PC/1-Wire converters use USB, RS-232 serial, and parallel port interfaces allowing connect the MicroLAN to the host PC. 1-Wire devices can also be connected directly to the microcontroller boards.
Within the MicroLAN, there is always one master device, which may be a PC or a microcontroller unit. The master always initiates activity on the bus to avoid collisions on the network chain. If a collision occurs, the master device retries the communication. In the 1-Wire network, many devices can share the same bus line. To identify devices in the MicroLAN, each connected device has a unique 64-bit ID number. The least significant byte of the ID number defines the type of the device (temperature, voltage etc. sensors). The most significant byte represents a standard 8-bit CRC. The 1-Wire protocol description contains several broadcast commands and commands used to address the selected device. The master sends a selection command, then the address of a slave selected device. This way, the next command is executed only by the addressed device. The 1-Wire bus implements enumeration procedure which allows the master to get information about ID numbers of all connected slave devices to the MicroLAN network. Device address includes the device type, and a CRC allows to identify what type of slaves are currently connected to the network chain for inventory purposes. The 64-bit address space is searched as a binary tree. It allows to find up to 75 devices per second.
The physical implementation of the 1-Wire network is based on an open drain master device connected to one or more open drain slaves. One single pull-up resistor for all devices pull the bus up to 3/5 V and can be used to power the slave devices. 1-Wire communication starts when a master or slave sets the bus to low voltage (connects the pull-up resistor to ground through its output MOSFET). Typical data speed of the 1-Wire interface is about 16.3 kbit/s.
1-Wire protocol allows for bursting the communication speed up by 10 factor. In this case, the master starts a transmission with a reset pulse pulling down the data line to 0 volts for at least 480 µs. It resets all slave devices in the network chain bus. Then, any slave device shows that it exists generating the “presence” pulse. It holds the data line low for at least 60 µs after the master releases the bus. To send a “1”, the bus master sends a 1–15 µs low pulse. To send a “0”, the master sends a 60 µs low pulse. The negative edge of the pulse is used to start a slave's monostable multivibrator. The slave's multivibrator clocks to read the data bus about 30 µs after the falling edge. The slave's multivibrator has analogue tolerances that affect its timing accuracy, for the “0” pulses are 60 µs long, and “1” pulses are limited to max 15 µs. When the designed solution doesn't contain a dedicated 1-Wire interface peripheral, a UART can be used as a 1-Wire master. Dallas also offers the Serial or USB “bridge” chips, very useful when the distance between devices is long (greater than 100 m). For longer, up to 300 m buses, the simple twisted pair telephone cable can be used. It will require adjustment of pull-up resistances from 5 kΩ to 1 kΩ. The basic sequence is a reset pulse followed by an 8-bit command, and after it, data can be sent/received in groups of 8-bits. In the case of transmission errors, the weak data protection 8-bit CRC checking procedure can be used.
To find the devices, the enumeration broadcast command must be sent by a master. The slave device response with all ID bits to the master and at the end it returns a 0.
The DS9490B is a USB bridge and holder for a single F5-size iButton. The DS9490R is a USB bridge with 1-Wire RJ11 interface to accommodate 1-Wire receptacles and networks.
The bridge is based on the DS2490 chip developed by Dallas company, which allows to interconnect USB interface with 1-Wire bus. This required programming and electrical conversion between two different protocols in bidirectional way. The electrical wiring are present on Figure 9.
The appropriate 1-Wire cable pinout uses RJ11 telephone connectors.
The list of Dallas/Maxim integrated 1-Wire devices contains a wide range of industrial implementations. The 1-Wire sensors and switches devices are very popular in the developer's community due to ease implementation. 1-Wire protocol can be fast implemented into the current IoT boards; most of the manufacturers share the software libraries allowing developers to include them in their projects in C, C++, assembly languages. The 1-Wire sensors (temperature, humidity, pressure, etc.) are factory calibrated and reading the physical measurements follows the International System of Units (SI). 1-Wire products can be grouped as follows:
No doubt, Arduino became the most widespread SoC, particularly among enthusiasts, educators, amateurs, and hobbyists, driving de-facto the embedded systems market for years.
Using cheap Atmel AVR microcontrollers, delivered along with development board and peripherals of almost any kind, including sensors and actuators, where you do not need to develop your PCB nor solder to obtain the fully functional device, all that triggered a new era where almost anyone can afford to have a development set and start playing the way only professionals used to do. Moreover, Arduino was not only the hardware but also the programming idea, delivering a simple development environment that is easy to use for beginners. Perhaps the most important impact of the Arduino on daily use was to spread the idea of taking automation control from the industry and bringing it on a massive scale to regular life, homes, cars, and toys to automate daily life.
The beginnings of the Arduino are dated to the year 2003 in Italy. Their most popular development board was delivered to the market in the fall of 2010. While AVRs microcontrollers are considered to be embedded systems more than IoT, and most of the early Arduino boards didn't offer any network interface, even then, it is essential to understand the idea of how to work with SoCs, so we start our guide here. However, many extension boards are present, suitable for the standard development boards (so-called shields) that offer wired and wireless networking for Arduino. Some of the Arduino development boards nowadays do integrate networking SoC into one board, i.e. Arduino Yun. Also, their clones, mostly made by Chinese manufacturers, evolved into more sophisticated products, integrating, i.e. Arduino Mega 2560 and ESP8266 SoC into one development board.
The following chapters present the Arduino hardware overview, peripherals and programming as universal basics for IoT systems development using advanced processors like ESP:
Arduino is an open-source platform based on easy-to-use hardware and software [105]. The Arduino project was started at the Ivrea Interaction Design Institute in Italy. Initially, the board aimed at students without a background in electronics and programming, but now boards are suitable for different IoT applications, wearable, embedded environments and other.
The Arduino board works by reacting on inputs that are received from various sensors and, after executing a set of instructions, an output is generated to respond to the environment. Input can be received by pressing a button, hearing the noise, perceiving an image of the situation using a camera and many other. The output actions on the environment are done using output sensors like actuator, blinking LED, audio device and other. The set of instructions are created using the Arduino programming language that is based on an open-source programming framework called Wiring and the Arduino Software (IDE) that is based on Processing.
Arduino microcontrollers can be used both in research and everyday applications. It is easy to use for people with different backgrounds, from students to experts. The Arduino Forum [106] is the place where users of Arduino can share their knowledge and get help and new ideas for developing their project.
Arduino boards can be divided into six sections depending on their specifications – entry level, enhanced features, Internet of things, education, wearable, and 3D printing boards.
The most common boards of Arduino are Uno, Leonardo, Micro, Nano (entry level), Mega, Pro Mini (enhanced features). Each of the board has different specifications and therefore, can have different applications.
Digital input/output (I/O) pins are contacts on the Arduino board that can receive or transmit a digital signal. The status of the pin can be set either to 0 that represents LOW signal or to 1 – HIGH signal. The maximum current of the pin output is 40 mA.
Pulse Width Modulation (PWM) is a function of a pin to generate a square wave signal, with a variable length of the HIGH level of the output signal. The PWM is used for digital pins to simulate the analogue output.
Analog pins convert the analogue input value to a 10-bit number, using Analog Digital Converter (ADC). This function maps the input voltage between 0 and the reference voltage to numbers between 0 and 1023.
By default, the reference voltage is set to a microcontroller operating voltage. Usually, it is 5 V or 3.3 V. Also, other internal or external reference sources can be used, for example, AREF pin.
Power pins on the Arduino board connect the power source to the microcontroller and/or voltage regulators. They can also be used as a power source to the external components and devices.
The VIN pin is used to connect the external power source to the internal regulator, to provide the regulated 5 V output. The input voltage of the board must be within the specific range, mostly between 7 V and 12 V.
The 5V pin is used to supply a microcontroller with the regulated 5 V from the external source or is used as a power source for the external components in the case when the board is already powered using the USB interface or the VIN pin.
The 3V3 pin provides the regulated 3.3 V output for the board components and external devices. The GND (ground pin) is where the negative terminal of the power supply is applied.
The Reset pin and the reset button are used to reset the Arduino board and the program. Resetting using the reset pin is done by connecting it to the GND pin.
There are three different types of memory on the Arduino board: flash memory, SRAM and EEPROM.
The flash memory stores the Arduino code, and it is a non-volatile type of memory. That means the information in the memory is not deleted when the power is turned off.
The SRAM (static random access memory) is used for storing values of variables when the program of Arduino is running. This is the volatile memory that keeps information only until the power is turned off, or the board is reset.
The EEPROM (electrically erasable programmable read-only memory) is a non-volatile type of memory that can be used as the long-term memory storage.
Communication interfaces for Arduino are used to send and receive information to and from other external devices. Standard interfaces for Arduino are USB, UART, I2C (two wire interface), SPI, Ethernet and WiFi.
Arduino microcontrollers have different dimensions of the board, depending on the components that are located on the board.
Arduino shields are the extension boards that can be plugged on top of the Arduino board extending its capabilities. The shields can give additional functionality to the Arduino board. There are multiple categories of the Arduino shields [107] – prototyping, improving connectivity, displays and cameras, sound and motor driver shields.
Prototyping shields – are shields that do not give Arduino the additional functionality, but help with the wiring. Some example prototyping shields are ProtoShield, ProtoScrew Shield, Go-Between Shield, LiPower Shield, Danger Shield, Joystick Shield and microSD Shield.
Connectivity shields – are shields that can add new functionalities to the Arduino board like Ethernet, WiFi, Wireless, GPS, etc. Example shields are Arduino Ethernet Shield, WiFly Shield, Arduino Wi-Fi Shield, Electric Imp Shield, XBee Shield, Cellular Shield SM5100B and GPS Shield.
Displays and camera shields – can provide Arduino with an LCD screen or add a camera. Example shields are Color LCD Shield, EL Escudo and CMUcam.
Sound shields – give the functionality to Arduino to play MP3 files, add speakers, listen to audio and sort it into different frequencies, etc. Example shields are MP3 Player Shield, Music Instrument Shield, Spectrum Shield and VoiceBox Shield.
Motor driver shields – allow Arduino to control DC motors, Servo motors, Stepper motors. Examples are Ardumoto Motor Driver Shield, Monster Moto Shield and PWM Shield.
A sensor is an element which can turn a physical outer stimulus into an output signal which then can be used for further analysis, the management or decision making. People also use sensors like eyes, ears and skin for gaining information about the outer world and act accordingly to their aims and needs. Sensors can be divided into multiple categories by the parameter that is perceived from the environment.
Usually, every natural phenomenon – temperature, weight, speed, etc. – needs specially customised sensors which can change every phenomenon into electronic signals that could be used by microprocessors or other devices. Sensors can be divided into many groups according to the physical nature of their operations – touch, light, an electrical characteristic, proximity and distance, angle, environment and other sensors.
A pushbutton is an electromechanical sensor that connects or disconnects two points in a circuit when the force is applied. Button output discrete value is either HIGH or LOW.
A microswitch, also called a miniature snap-action switch, is an electromechanical sensor that requires a very little physical force and uses tipping-point mechanism. Microswitch has three pins, two of which are connected by default. When the force is applied, the first connection breaks and one of the pins is connected to the third pin.
The most common use of a pushbutton is as an input device. Both force solutions can be used as simple object detectors, or as end switches in the industrial devices.
An example code:
int buttonPin = 2; //Initialization of a push button pin number int buttonState = 0; //A variable for reading the push button status void setup() { Serial.begin(9600); //Begin serial communication pinMode(buttonPin, INPUT); //Initialize the push button pin as an input } void loop() { //Read the state of the push button value buttonState = !digitalRead(buttonPin); //Check if the push button is pressed. If it is, the buttonState is HIGH if (buttonState == HIGH) { //Print out text in the console Serial.println("The button state is HIGH - it is pressed."); } else { Serial.println("The button state is LOW - it is not pressed."); } delay(10); //Delay in between reads for stability }
A force sensor predictably changes resistance, depending on the applied force to its surface. Force-sensing resistors are manufactured in different shapes and sizes, and they can measure not only direct force but also the tension, compression, torsion and other types of mechanical forces. The voltage is measured by applying and measuring constant voltage to the sensor.
Force sensors are used as control buttons or to determine weight.
An example code:
//Force Sensitive Resistor (FSR) is connected to the analog 0 pin int fsrPin = A0; //The analog reading from the FSR resistor divider int fsrReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the FSR analog pin as an input pinMode(fsrPin, INPUT); } void loop(void) { //Read the resistance value of the FSR fsrReading = analogRead(fsrPin); //Print Serial.print("Analog reading = "); Serial.println(fsrReading); delay(10); }
Capacitive sensors are a range of sensors that use capacitance to measure changes in the surrounding environment. A capacitive sensor consists of a capacitor that is charged with a certain amount of current until the threshold voltage. A human finger, liquids or other conductive or dielectric materials that touch the sensor, can influence a charge time and a voltage level in the sensor. Measuring charge time and a voltage level gives information about changes in the environment.
Capacitive sensors are used as input devices and can measure proximity, humidity, fluid level and other physical parameters or serve as an input for electronic device control.
//Capacitive sensor is connected to the digital 2 pin int touchPin = 2; //The digital reading value from the sensor boolean touchReading = LOW; //The variable that stores the previous state value boolean lastState = LOW; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the capacitive sensor analog pin as an input pinMode(touchPin, INPUT); } void loop() { //Read the digital value of the capacitive sensor touchReading = digitalRead(touchPin); //If the new touch has appeared if (currentState == HIGH && lastState == LOW){ Serial.println("Sensor is pressed"); delay(10); //short delay } //Save previous state to see relative changes lastState = currentState; }
A photoresistor is a sensor that perceives light waves from the environment. The resistance of the photoresistor is changing depending on the intensity of light. The higher is the intensity of the light; the lower is the resistance of the sensor. A light level is determined by applying a constant voltage sensor and measuring it. Photodiodes, compared to photoresistors, are slower and more influenced by temperature; thus, they are more imprecise.
Photoresistors are often used in the energy effective street lightning.
An example code:
//Define an analog A0 pin for photoresistor int photoresistorPin = A0; //The analog reading from the photoresistor int photoresistorReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of a photoresistor as an input pinMode(photoresistorPin, INPUT); } void loop() { //Read the value of the photoresistor photoresistorReading = analogRead(photoresistorPin); //Print out value of the photoresistor reading to the serial monitor Serial.println(photoresistorReading); delay(10); //Short delay }
A photodiode is a sensor that converts the light energy into electrical current. A current in the sensor is generated by exposing a p-n junction of a semiconductor to the light. Information about the light intensity can be determined by measuring a voltage level. Photodiodes are reacting to the changes in the light intensity very quickly. Solar cells are just large photodiodes.
Photodiodes are used as precise light level sensors, receivers for remote control, electrical isolators and proximity detectors.
An example code:
//Define an analog A0 pin for photodiode int photodiodePin = A0; //The analog reading from the photodiode int photodiodeReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of a photodiode as an input pinMode(photodiodePin, INPUT); } void loop() { //Read the value of the photodiode photodiodeReading = analogRead(photodiodePin); //Print out the value of the photodiode reading to the serial monitor Serial.println(photodiodeReading); delay(10); //Short delay }
A phototransistor is a light controlled electrical switch. In the exposed Base pin received light level, changes the amount of current, that can pass between two phototransistor pins – a collector and an emitter. A phototransistor is slower than the photodiode, but it can conduct more current.
Phototransistors are used as the optical switches, proximity sensors and electrical isolators.
An example code:
//Define an analog A1 pin for phototransistor int phototransistorPin = A1; //The analog reading from the phototransistor int phototransistorReading; void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of a phototransistor as an input pinMode(phototransistorPin, INPUT); } void loop() { //Read the value of the phototransistor phototransistorReading = analogRead(phototransistorPin); //Print out the value of the phototransistor reading to the serial monitor Serial.println(phototransistorReading); delay(10); //short delay }
Electrical characteristic sensors are used to determine whether the circuit of the device is working properly. When the voltage and current sensors are used concurrently, the consumed power of the device can be determined.
A voltage sensor is a device or circuit for voltage measurement. A simple DC (direct current) voltage sensor consists of a voltage divider circuit with the optional amplifier for very small voltage occasions. For measuring the AC (alternating current), a transformer is added to a lower voltage; then it is connected to the rectifier to rectify AC to DC, and finally, an optoelectrical isolator is added for measuring circuit safety.
A voltage sensor can measure electrical load and detect a power failure. Examples of IoT applications are monitoring of appliance, line power, power coupling, power supply and sump pump.
The example code:
//Define an analog A1 pin for voltage sensor int voltagePin = A1; //The analog reading from the voltage sensor int voltageReading; float vout = 0.0; float vin = 0.0; float R1 = 30000.0; // 30 kΩ resistor float R2 = 7500.0; // 7.5 kΩ resistor void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of a voltage sensor as an input pinMode(voltagePin, INPUT); } void loop() { //Read the value of the voltage sensor voltageReading = analogRead(voltagePin); vout = (voltageReading * 5.0) / 1024.0; vin = vout / (R2/(R1+R2)); Serial.print("Voltage is: "); //Print out the value of the voltage to the serial monitor Serial.println(vin); delay(10); //Short delay }
A current sensor is a device or a circuit for current measurement. A simple DC sensor consists of a high power resistor with low resistance. The current is obtained by measuring the voltage on the resistor and applying formula proportional to the voltage. Other non-invasive measurement methods involve hall effect sensors for DC and AC and inductive coils for AC. Current sensors are used to determine the power consumption, to detect whether the device is turned on, short circuits.
The example code:
//Define an analog A0 pin for current sensor const int currentPin = A0; //Scale factor of the sensor use 100 for 20 A Module and 66 for 30 A Module int mVperAmp = 185; int currentReading; int ACSoffset = 2500; double Voltage; double Current; void setup(){ Serial.begin(9600); } void loop(){ currentReading = analogRead(currentPin); Voltage = (currentReading / 1024.0) * 5000; //Gets you mV Current = ((Voltage - ACSoffset) / mVperAmp); //Calculating current value Serial.print("Raw Value = " ); //Shows pre-scaled value Serial.print(currentReading); Serial.print("\t Current = "); //Shows the voltage measured //The '3' after current allows to display 3 digits after decimal point Serial.println(Current,3); delay(1000); //Short delay
An optocoupler is a device that combines light emitting and receiving devices. Mostly it is a combination of the infrared light-emitting diode (LED) and a phototransistor. Other optical semiconductors can be a photodiode and a photoresistor. There are two main types of optocouplers:
An example code:
int optoPin = A0; //Initialize an analog A0 pin for optocoupler int optoReading; //The analog value reading from the optocoupler int objecttreshold = 1000; //Object threshold definition int whitetreshold = 150; //White colour threshold definition void setup () { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of the optocoupler as an input pinMode(optoPin, INPUT); } void loop () { optoReading = analogRead(optoPin); //Read the value of the optocoupler Serial.print ("The reading of the optocoupler sensor is: "); Serial.println(optoReading); //When the reading value is lower than the object threshold if (optoReading < objecttreshold) { Serial.println ("There is an object in front of the sensor!"); //When the reading value is lower than the white colour threshold if (optoReading < white threshold) { Serial.println ("Object is in white colour!"); } else { //When the reading value is higher than the white colout threshold Serial.println ("Object is in dark colour!"); } } else { //When the reading value is higher than the object thershold Serial.println ("There is no object in front of the sensor!"); } delay(500); //Short delay }
Infrared (IR) proximity sensor is used to detect objects and to measure the distance to them, without any physical contact. IR sensor consists of an infrared emitter, a receiving sensor or array of sensors and a signal processing logic. The output of a sensor differs depending on the type – simple proximity detection sensor outputs HIGH or LOW level when an object is in its sensing range, but sensors which can measure distance outputs an analogue signal or use some communication protocol, like I2C to send sensor measuring results. IR sensors are used in robotics to detect obstacles starting from few millimetres to several meters and in mobile phones to help detect accidental button touching.
An example code:
int irPin = A0; //Define an analog A0 pin for IR sensor int irReading; //The analog reading from the IR sensor void setup() { //Begin serial communication Serial.begin(9600); //Initialize the analog pin of a IR sensor as an input pinMode(irPin, INPUT); } void loop() { //Read the value of the IR sensor irReading = analogRead(irPin); //Print out the value of the IR sensor reading to the serial monitor Serial.println(irReading); delay(10); //Short delay }
Ultrasound (ultrasonic) sensor measures the distance to objects by emitting ultrasound and measuring its returning time. The sensor consists of an ultrasonic emitter and receiver; sometimes, they are combined in a single device for emitting and receiving. Ultrasonic sensors can measure greater distances and cost less than infrared sensors, but are more imprecise and interfere which each other measurement if more than one is used. Simple sensors have trigger pin and echo pin, when the trigger pin is set high for the small amount of time ultrasound is emitted and on echo pin, response time is measured. Ultrasonic sensors are used in car parking sensors and robots for proximity detection.
Examples of IoT applications are robotic obstacle detection and room layout scanning.
An example code:
int trigPin = 2; //Define a trigger pin D2 int echoPin = 4; //Define an echo pin D4 void setup() { Serial.begin(9600); //Begin serial communication pinMode(trigPin, OUTPUT); //Set the trigPin as an Output pinMode(echoPin, INPUT); //Set the echoPin as an Input } void loop() { digitalWrite(trigPin, LOW); //Clear the trigPin delayMicroseconds(2); //Set the trigPin on HIGH state for 10 μs digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); //Read the echoPin, return the sound wave travel time in microseconds duration = pulseIn(echoPin, HIGH); //Calculating the distance distance= duration*0.034/2; //Printing the distance on the Serial Monitor Serial.print("Distance: "); Serial.println(distance); }
The motion detector is a sensor that detects moving objects, most people. Motion detectors use different technologies, like passive infrared sensors, microwaves and Doppler effect, video cameras and previously mentioned ultrasonic and IR sensors. Passive IR sensors are the simplest motion detectors that sense people trough detecting IR radiation that is emitted through the skin. When the motion is detected, the output of a motion sensor is a digital HIGH/LOW signal.
Motion sensors are used for security purposes, automated light and door systems. As an example in IoT, the PIR motion sensor can be used to detect motion in security systems a house or any building.
An example code:
//Passive Infrared (PIR) sensor output is connected to the digital 2 pin int pirPin = 2; //The digital reading from the PIR output int pirReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the PIR digital pin as an input pinMode(pirPin, INPUT); } void loop(void) { //Read the digital value of the PIR motion sensor pirReading = digitalRead(pirPin); //Print out Serial.print("Digital reading = "); Serial.println(pirReading); if(pirReading == HIGH) { //Motion was detected Serial.println("Motion Detected"); } delay(10); }
A potentiometer is a type of resistor, the resistance of which can be adjusted using a mechanical lever. The device consists of three terminals. The resistor between the first and the third terminal has fixed value, but the second terminal is connected to the lever. Whenever the lever is turned, a slider of the resistor is moved, it changes the resistance between the second terminal and side terminals. Variable resistance causes the change of the voltage variable, and it can be measured to determine the position of the lever. Thus, potentiometer output is an analogue value.
Potentiometers are commonly used as a control level, for example, a volume level for the sound and joystick position. They can also be used for angle measurement in feedback loops with motors, for example, in servo motors.
An example code:
//Potentiometer sensor output is connected to the analog A0 pin int potentioPin = A0; //The analog reading from the potentiometer output int potentioReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the potentiometer analog pin as an input pinMode(potentioPin, INPUT); } void loop(void) { //Read the analog value of the potentiometer sensor potentioReading = analogRead(potentioPin); Serial.print("Potentiometer reading = "); //Print out Serial.println(potentioReading); delay(10); }
An IMU is an electronic device, that consist of accelerometer, gyroscope and sometimes also a magnetometer. Combination of these sensors returns the orientation of the object in 3D space.
A gyroscope is a sensor that measures the angular velocity. The sensor is made of the microelectromechanical system (MEMS) technology and is integrated into the chip. The output of the sensor can be either analogue or digital value of information, using I2C or SPI interface. Gyroscope microchips can vary in the number of axes they can measure. The available number of the axis is 1, 2 or 3 axes in the gyroscope. For gyroscopes with 1 or 2 axes, it is essential to determine which axis the gyroscope measures and to choose a device according to the project needs. A gyroscope is commonly used together with an accelerometer, to determine the orientation, position and velocity of the device precisely. Gyroscope sensors are used in aviation, navigation and motion control.
A magnetometer is the sensor, that can measure the orientation of the device to the magnetic field of the Earth. A magnetometer is used in outdoor navigation for mobile devices, robots, quadcopters.
An accelerometer measures the acceleration of the object. The sensor uses a microelectromechanical system (MEMS) technology, where capacitive plates are attached to springs. When acceleration force is applied to the plates, the capacitance is changed; thus, it can be measured. Accelerometers can have 1 to 3 axis. On 3-axis, the accelerometer can detect orientation, shake, tap, double tap, fall, tilt, motion, positioning, shock or vibration of the device. Outputs of the sensor are usually digital interfaces like I2C or SPI. For precise measurement of the object movement and orientation in space, the accelerometer is often used together with a gyroscope. Accelerometers are used for measuring vibrations of cars, industrial devices, buildings and to detect volcanic activity. In IoT applications, it can be used as well for accurate motion detection for medical and home appliances, portable navigation devices, augmented reality, smartphones and tablets.
The example code:
//Library for I2C communication #include <Wire.h> //Downloaded from https://github.com/adafruit/Adafruit_Sensor #include <Adafruit_Sensor.h> //Downloaded from https://github.com/adafruit/Adafruit_BNO055 #include <Adafruit_BNO055.h> #include <utility/imumaths.h> Adafruit_BNO055 bno = Adafruit_BNO055(55); void setup(void) { bno.setExtCrystalUse(true); } void loop(void) { //Read sensor data sensors_event_t event; bno.getEvent(&event); //Print X, Y And Z orientation Serial.print("X: "); Serial.print(event.orientation.x, 4); Serial.print("\tY: "); Serial.print(event.orientation.y, 4); Serial.print("\tZ: "); Serial.print(event.orientation.z, 4); Serial.println(""); delay(100); }
A temperature sensor is a device that is used to determine the temperature of the surrounding environment. Most temperature sensors work on the principle that the resistance of the material is changed depending on its temperature. The most common temperature sensors are:
The main difference between sensors is the measured temperature range, precision and response time. Temperature sensor usually outputs the analogue value, but some existing sensors have a digital interface [108].
The temperature sensors most commonly are used in environmental monitoring devices and thermoelectric switches. In IoT applications, the sensor can be used for greenhouse temperature monitoring, warehouse temperature monitoring to avoid frozen fire suppression systems and tracking temperature of the soil, water and plants.
An example code:
//Thermistor sensor output is connected to the analog A0 pin int thermoPin = 0; //The analog reading from the thermistor output int thermoReading; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the thermistor analog pin as an input pinMode(thermoPin, INPUT); } void loop(void) { //Read the analog value of the thermistor sensor thermoReading = analogRead(thermoPin); Serial.print("Thermistor reading = "); //Print out Serial.println(thermoReading); delay(10); }
A humidity sensor (hygrometer) is a sensor that detects the amount of water or water vapour in the environment. The most common principle of the air humidity sensors is the change of capacitance or resistance of materials that absorb the moisture from the environment. Soil humidity sensors measure the resistance between the two electrodes. The resistance between electrodes is influenced by soluble salts and water amount in the soil. The output of a humidity sensor is usually an analogue signal value [109].
Example IoT applications are monitoring of humidor, greenhouse temperature and humidity, agriculture, art gallery and museum environment.
An example code [110]:
#include <dht.h> dht DHT; #define DHT_PIN 7 void setup(){ Serial.begin(9600); } void loop() { int chk = DHT.read11(DHT_PIN); Serial.print("Humidity = "); Serial.println(DHT.humidity); delay(1000); }
A sound sensor is a sensor that detects vibrations in a gas, liquid or solid environments. At first, the sound wave pressure makes mechanical vibrations, who transfers to changes in capacitance, electromagnetic induction, light modulation or piezoelectric generation to create an electric signal. The electrical signal is then amplified to the required output levels. Sound sensors, can be used to record sound, detect noise and its level.
Sound sensors are used in drone detection, gunshot alert, seismic detection and vault safety alarm.
An example code:
//Sound sensor output is connected to the digital 7 pin int soundPin = 7; //Stores sound sensor detection readings int soundReading = HIGH; void setup(void) { //Begin serial communication Serial.begin(9600); //Initialize the sound detector module pin as an input pinMode(soundPin, INPUT); } void loop(void) { //Read the digital value whether the sound has been detected soundReading = digitalRead(soundPin); if (soundPin==LOW) { //When sound detector detected the sound Serial.println("Sound detected!"); //Print out } else { //When the sound is not detected Serial.println("Sound not detected!"); //Print out } delay(10); }
Gas sensors are a sensor group, that can detect and measure a concentration of certain gasses in the air. The working principle of electrochemical sensors is to absorb the gas and to create current from an electrochemical reaction. For process acceleration, a heating element can be used. For each type of gas, different kind of sensor needs to be used. Multiple different types of gas sensors can be combined in a single device as well. The single gas sensor output is an analogue signal, but devices with multiple sensors used to have a digital interface.
Gas sensors are used for safety devices, to control air quality and for manufacturing equipment. Examples of IoT applications are air quality control management in smart buildings and smart cities or toxic gas detection in sewers and underground mines.
An example code:
int gasPin = A0; //Gas sensor output is connected to the analog A0 pin int gasReading; //Stores gas sensor detection reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(gasPin, INPUT); //Initialize the gas detector pin as an input } void loop(void) { gasReading = analogRead(gasPin); //Read the analog value of the gas sensor Serial.print("Gas detector value: "); //Print out Serial.println(gasReading); delay(10); //Short delay }
A level sensor detects the level of fluid or fluidised solid. Level sensors can be divided into two groups:
Level sensors can be used as smart waste management, for measuring tank levels, diesel fuel gauging, liquid assets inventory, chemical manufacturing high or low-level alarms and irrigation control.
An example code:
int levelPin = 6; //Liquid level sensor output is connected to the digital 6 pin int levelReading; //Stores level sensor detection reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(levelPin, INPUT); //Initialize the level sensor pin as an input } void loop(void) { levelReading = digitalRead(levelPin); //Read the digital value of the level sensor Serial.print("Level sensor value: "); //Print out Serial.println(levelReading); delay(10); //Short delay }
A Hall effect sensor detects strong magnetic fields, their polarities and the relative strength of the field. In the Hall effect sensors, a magnetic force influences current flow through the semiconductor material and creates a measurable voltage on the sides of the semiconductor. Sensors with analogue output can measure the strength of the magnetic field, while digital sensors give HIGH or LOW output value, depending on the presence of the magnetic field.
Hall effect sensors are used in magnetic encoders for speed measurements and magnetic proximity switches because it does not require contact, and it ensures high reliability. Example application can be sensing the position of rotary valves.
Thw example code:
int hallPin = A0; //Hall sensor output is connected to the analog A0 pin int hallReading; //Stores hallsensor detection reading void setup(void) { Serial.begin(9600); //Begin serial communication pinMode(hallPin, INPUT); //Initialize the hallsensor pin as an input } void loop(void) { hallReading = analogRead(hallPin); //Read the analog value of the hall sensor Serial.print("Hall sensor value: "); //Print out Serial.println(hallReading); delay(10); //Short delay }
A GPS receiver is a device, that can receive information from a global navigation satellite system and calculate its position on the Earth. GPS receiver uses a constellation of satellites and ground stations to compute position and time almost anywhere on the Earth. GPS receivers are used for navigation only in the outdoor area because it needs to receive signals from the satellites. The precision of the GPS location can vary.
A GPS receiver is used for device location tracking. Real world applications might be, i.e., pet, kid or personal belonging location tracking.
The example code [111]:
#include <SoftwareSerial.h> SoftwareSerial SoftSerial(2, 3); unsigned char buffer[64]; //Buffer array for data receive over serial port int count=0; //Counter for buffer array void setup() { SoftSerial.begin(9600); //The SoftSerial baud rate Serial.begin(9600); //The Serial port of Arduino baud rate. } void loop() { if (SoftSerial.available()) //If date is coming from software serial port // ==> Data is coming from SoftSerial shield { while(SoftSerial.available()) //Reading data into char array { buffer[count++]=SoftSerial.read(); //Writing data into array if(count == 64)break; } Serial.write(buffer,count); //If no data transmission ends, //Write buffer to hardware serial port clearBufferArray(); //Call clearBufferArray function to clear //The stored data from the array count = 0; //Set counter of while loop to zero } if (Serial.available()) //If data is available on hardware serial port // ==> Data is coming from PC or notebook SoftSerial.write(Serial.read()); //Write it to the SoftSerial shield } void clearBufferArray() //Function to clear buffer array { for (int i=0; i<count;i++) { buffer[i]=NULL; } //Clear all index of array with command NULL }
The light-emitting diode also called LED is a special type of diodes which emits light, unlike the other diodes. LED has a completely different body which is made of transparent plastic that protects the diode and lets it emit light. Like the other diodes LED conducts the current in only one way, so it is essential to connect it to the scheme correctly. There are two safe ways how to determine the direction of the diode:
The LED is one of the best light sources. Unlike incandescent light bulb LED transforms most of the power into light, not warmth; it is more durable, works for a more extended period and can be manufactured in a smaller size.
The LED colour is determined by the semiconductors material. Diodes are usually made from silicon then LEDs are made from elements like gallium phosphate, silicon carbide and others. Because the semiconductors used are different, the voltage needed for the LED to shine is also different. In the table, you can see with which semiconductor you can get a specific colour and the voltage required to turn on the LED.
When LED is connected to the voltage and turned on a huge current starts to flow through it, and it can damage the diode. That is why all LEDs have to be connected to current limiting resistor.
Current limiting resistors resistance is determined by three parameters:
To calculate the resistance needed for a diode, this is what you have to do.
The example of the blinking LED code:
int ledPin = 8;//Defining the pin of the LED void setup() { pinMode(ledPin,OUTPUT); //The LED pin is set to output } void loop() { //Set pin output signal to HIGH – LED is working digitalWrite(ledPin,HIGH); //Belay of 1000 ms delay(1000); //Set pin output signal to LOW – LED is not working digitalWrite(ledPin,LOW); //Delay of 1000 ms delay(1000); }
Using display is a quick way to get feedback information from the device. There are many display technologies compatible with Arduino. For IoT solutions, low power, easy to use and monochrome displays are used:
Liquid-Crystal Display (LCD)
LCD uses modulating properties of liquid crystal light to block the incoming light. Thus when a voltage is applied to a pixel, it has a dark colour. A display consists of layers of electrodes, polarising filters, liquid crystals and reflector or back-light. Liquid crystals do not emit the light directly; they do it through reflection or backlight. Because of this reason, they are more energy efficient. Small, monochrome LCDs are widely used in devices to show a little numerical or textual information like temperature, time, device status etc. LCD modules commonly come with an onboard control circuit and are controlled through parallel or serial interface.
The example code:
#include <LiquidCrystal.h> //include LCD library //Define LCD pins const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; //Create and LCD object with predefined pins LiquidCrystal lcd(rs, en, d4, d5, d6, d7); void setup() { lcd.begin(16, 2); //Set up the LCD's number of columns and rows lcd.print("hello, world!"); //Print a message to the LCD } void loop() { //Set the cursor to column 0, line 1 – line 1 is the second row //Since counting begins with 0 lcd.setCursor(0, 1); //Print the number of seconds since reset lcd.print(millis() / 1000); }
Organic Light-Emitting Diode Display (OLED)
OLED display uses electroluminescent materials that emit light when the current passes through these materials. The display consists of two electrodes and a layer of an organic compound. OLED displays are thinner than LCDs, they have higher contrast, and they can be more energy efficient depending on usage. OLED displays are commonly used in mobile devices like smartwatches, cell phones and they are replacing LCDs in other devices. Small OLED display modules usually have an onboard control circuit that uses digital interfaces like I2C or SPI.
//Add libraries to ensure the functioning of OLED #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); void setup() { //Setting up initial OLED parameters display.begin(SSD1306_SWITCHCAPVCC, 0x3C, false); display.setTextSize(1); //Size of the text display.setTextColor(WHITE); //Colour of the text – white void loop() { //Print out on display output sensor values display.setCursor(0, 0); display.clearDisplay(); display.print("Test of the OLED"); //Print out the text on the OLED display.display(); delay(100); display.clearDisplay(); }
Electronic Ink Display (E-Ink)
E-ink display uses charged particles to create a paper-like effect. The display consists of transparent microcapsules filled with oppositely charged white and black particles between electrodes. Charged particles change their location, depending on the orientation of the electric field, thus individual pixels can be either black or white. The image does not need the power to persist on the screen; power is used only when the image is changed. Thus e-ink display is very energy efficient. It has high contrast and viewing angle, but it has a low refresh rate. E-ink displays are commonly used in e-riders, smartwatches, outdoor signs, electronic shelf labels.
#include <SmartEink.h> #include <SPI.h> E_ink Eink; void setup() { //BS LOW for 4 line SPI pinMode(8,OUTPUT); digitalWrite(8, LOW); Eink.InitEink(); Eink.ClearScreen();//Clear the screen Eink.EinkP8x16Str(14,8,"NOA-Labs.com"); Eink.EinkP8x16Str(10,8,"smart-prototyping.com"); Eink.EinkP8x16Str(6,8,"0123456789"); Eink.EinkP8x16Str(2,8,"ABCDEFG abcdefg"); Eink.RefreshScreen(); } void loop() { }
Relays are electromechanical devices that use electromagnets to connect or disconnect plates of a switch. Relays are used to control high power circuits with low power circuits. Circuits are mechanically isolated and thus protect logic control. Relays are used in household appliance automation, lighting and climate control.
The example code:
#define relayPin 4 //Define the relay pin void setup() { Serial.begin(9600); pinMode(relayPin, OUTPUT); //Set relayPin to output } void loop() { digitalWrite(relayPin,0); //Turn relay on Serial.println("Relay ON"); //Output text delay(2000); // Wait 2 seconds digitalWrite(relayPin,1); //Turns relay off Serial.println("Relay OFF"); delay(2000); }
Solenoids are devices that use electromagnets to pull or push iron or steel core. They are used as linear actuators for locking mechanisms indoors, pneumatic and hydraulic valves and in-car starter systems.
Solenoids and relays both use electromagnets and connecting them to Arduino is very similar. Coils need a lot of power, and they are usually attached to the power source of the circuit. Turning the power of the coil off makes the electromagnetic field to collapse and creates very high voltage. For the semiconductor devices protection, a shunt diode is used to channel the overvoltage. For extra safety, optoisolator can be used.
The example code:
#define solenoidPin 4 //Define the solenoid pin void setup() { Serial.begin(9600); pinMode(solenoidPin, OUTPUT); //Set solenoidPin to output } void loop() { digitalWrite(solenoidPin,0); //Turn solenoid on Serial.println("Solenoid ON"); //Output text delay(2000); //Wait 2 seconds digitalWrite(solenoidPin,1); //Turns solenoid off Serial.println("Solenoid OFF"); delay(2000); }
Speakers are electroacoustic devices that convert the electrical signal into sound waves. A speaker uses a permanent magnet and a coil attached to the membrane. Sound signal, flowing through the coil, creates the electromagnetic field with variable strength, coil attracts to magnet according to the strength of the field, thus making a membrane to vibrate and creating a sound wave. Other widely used speaker technology, called Piezo speaker, uses piezoelectric materials instead of magnets. Speakers are used to creating an audible sound for human perception and ultrasonic sound for sensors and measurement equipment.
const int speakerPin = 9; //Define the buzzer pin void setup() { pinMode(speakerPin, OUTPUT); //Set buzzer as an output } void loop() { tone(speakerrPin, 1000); //Send 1 kHz sound signal delay(1000); //For 1 s noTone(speakerPin); //Stop sound delay(1000); //For 1 s }
An electric motor is an electro-technical device which can turn electrical energy into mechanical energy; motor turns because of the electricity that flows in its winding. Electric motors have seen many technical solutions over the year from which the simplest is the permanent-magnet DC motor.
DC motor is a device which converts direct current into the mechanical rotation. DC motor consists of permanent magnets in stator and coils in the rotor. By applying the current to coils, the electromagnetic field is created, and the rotor tries to align itself to the magnetic field. Each coil is connected to a commutator, which in turns supplies coils with current, thus ensuring continuous rotation. DC motors are widely used in power tools, toys, electric cars, robots, etc.
void setup () { pinMode(5,OUTPUT); //Digital pin 5 is set to output //The function for turning on the motor is defined #define motON digitalWrite(5,HIGH) //The function for turning off the motor is defined #define motOFF digitalWrite(5,LOW) } void loop () { motON; //Turn on the motor }
The H-bridge has earned its name because of its resemblance to the capital ‘H’ wherein all the corners there are switches and in the middle – the electric motor. This bridge is usually used for operating permanent-magnet DC motor, electromagnets and other similar elements, because it allows operating with significantly bigger current devices, using a small current. By switching the switches, it is possible to change the motor direction. It is important to keep in mind that the switches need to be turned on and off in pairs.
When all of the switches are turned off, the engine is in the free movement. To slow down faster, the H-bridge is turned on in the opposite direction.
If both positive or both negative switches are turned on at the top or at the bottom, then the engine stops, not allowing to have a free rotation – it is slowed down. The management can be reflected in Table 10.
When all of the switches are turned off, the engine is in the free movement. Not always it is enough for robotics, so sometimes the H-bridge is turned on in the opposite direction to slow the motor down faster – the opposite direction is turned on rapidly.
Remember! Neither of these braking mechanisms is good for the H-bridge or the power source. That is why this action is unacceptable without a particular reason because it can damage the switches or the power source.
The complicated part is the realisation of switches – if the switches don’t work usually relays or appropriate power transistors are used. The biggest drawback for relays is that they can only turn the engine on or off. If the rotation speed needs to be regulated using the impulse width modulation, then transistors have to be used. MOSFET type transistors should be used for ensuring a large amount of power. Nowadays, the stable operation of the bridge is ensured by adding extra elements. The manufactured bridges have one body, for example, the one that is included in the constructor – L293D.
The L293D microchip consists of two H-bridges and is made for managing two motors. Each pin of the microchip has its function; that is why it is very important not to mix them up; otherwise, the microchip can be damaged. All pins of the microchip have assigned a number. The enumeration begins with the special mark on the body: a split, a dot, a cracked edge, etc., and continues counter-clockwise. When creating a scheme, it is important to take into account pin numbers and the ones shown in the scheme. If some additional information about the microchip is necessary, it can be found in the datasheet of the microchip. Remember that the datasheet can be found by writing the number of the device (written on the body) and adding the word “datasheet” in the browser.
The example code:
int dirPin1 = 7; //1st direction pin int dirPin2 = 8; //2nd direction pin int speedPin = 5; //Pin responsible for the motor speed void setup () { pinMode (dirPin1,OUTPUT); //1st direction pin is set to output pinMode (dirPin2,OUTPUT); //2nd direction pin is set to output pinMode (speedPin,OUTPUT); //Speed pin is set to output } void loop () { analogWrite(speedPin, 100); //Setting motor speed //Speed value can be from 0 to 255 int motDirection = 1; //Motor direction can be either 0 or 1 if (motDirection) //Setting motor direction {//If 1 digitalWrite(dirPin1,HIGH); digitalWrite(dirPin2,LOW); } else {//If 0 digitalWrite(dirPin1,LOW); digitalWrite(dirPin2,HIGH); } }
Stepper motors are motors, that can be moved by a certain angle or step. Full rotation of the motor is divided into small, equal steps. Stepper motor has many individually controlled electromagnets, by turning them on or off, a motor shaft rotates by one step. Changing switching speed or direction can precisely control turn angle, direction or full rotation speed. Because of exact control ability, they are used in CNC machines, 3D printers, scanners, hard drives etc. Example of use can be found in the source [112].
The example code:
#include <Stepper.h> //Include library for stepper motor int in1Pin = 12; //Defining stepper motor pins int in2Pin = 11; int in3Pin = 10; int in4Pin = 9; //Define a stepper motor object Stepper motor(512, in1Pin, in2Pin, in3Pin, in4Pin); void setup() { pinMode(in1Pin, OUTPUT); //Set stepper motor control pins to output pinMode(in2Pin, OUTPUT); pinMode(in3Pin, OUTPUT); pinMode(in4Pin, OUTPUT); Serial.begin(9600); motor.setSpeed(20); //Set the speed of stepper motor object } void loop() { motor.step(5); //Rotate 5 steps }
Unlike the simple DC motor, the servomotor is a particular management chain which allows effortless control over the speed or position of the motor. The management of the engine is realised using three connections – positive (usually red) and negative connection (brown or black) of the current as well as the connection for management (orange or yellow).
For the management, the pulse width technique is used.
From the image, it can be seen that the length of the servomotor impulse cycle is 20 ms, but the impulse length itself is 1 ms or 2 ms. These signal characteristics are true for the most enthusiast level servomotors, but it should be verified for each module in the manufacturer specification. Servomotor management chain meets the impulse every 20 ms, but the width of the pulse shows the position that the servomotor has to reach. For example, 1 ms corresponds to the 0° position but 2 ms – to 180° position against the starting point. When entering the defined position, the servomotor will keep it and resist any outer forces that are trying to change the current position. The graphical representation is in the following image.
Just like other motors, servomotors have different parameters, where the most important one is the time of performance – the time that is necessary to change the position to the defined position. The best enthusiast level servomotors do a 60° turn in 0.09 s. There are three types of servomotors:
Unfortunately, using Arduino, the servomotor is not as easily manageable as DC motor. For this purpose, a special servomotor management library Servo.h has been created.
The example code:
#include <Servo.h> //Include Servo library Servo servo; //Define a Servo object void setup () { servo.attach(6); //Connect servo object to pin D6 servoLeft.write(90); //Set position of servo to 90° Serial.begin(9600); } void loop () { servoLeft.write(110); //Set position of servo to 110° delay(200); //wait for 200 ms servoLeft.write(70);//Set position of servo to 70° delay(200); //Wait for 200 ms }
Many IoT projects developed using an Arduino board can be found in the official Arduino Project Hub [113]. Here are stored multiple projects that are developed by Arduino enthusiasts. In many of the following examples, the Arduino Yun board is used, because it is easy to use a controller that contains the WiFi connection that is necessary for IoT solutions. Additionally, the Amazon services are used for storing and handling the sensor data.
One of the IoT projects available at the Arduino Project Hub is the Arduino Home Controller Activated by Alexa [114] (Alexa is the Amazon Echo dot [115]) that uses an Arduino Yun controller. In this solution, it is possible to control internet-connected devices around the home using voice, using Alexa and Amazon Web Service (AWS). Natural language voice commands are interpreted using Amazon Skill and a Lambda Function in the AWS. The developed system gives an opportunity to control four lights installed in the room, garage, kitchen and living room, temperature and humidity, buzzer alarm and WebCam that takes a security photo and sends it by e-mail.
The Home Security Model [116] is another example to monitor the security status in the real-time using sensors and internet connection. The system data can be accessed and controlled remotely using a smart device or a PC. In this project, the Arduino Yun and Arduino Mega controllers are used. The AWS IoT services are used for connecting the devices around the home. The system includes temperature and humidity, gas, water, vibration, current and ultrasonic sensors, and it controls the light in multiple rooms and a buzzer.
The project Plant Monitoring System uses AWS IoT [117] and gives the opportunity to track the temperature and soil moisture of the plant using AWS IoT. Using the Arduino Yun controller, the system monitors such parameters as the temperature, soil moisture and light value. These log data are stored in the AWS IoT service. Additionally, the Amazon Simple Notification Service (SNS) is used for sending notification informative e-mails about the status of the sensor measurements.
Here is another IoT project about the home automation system, called Arduino based Amazon Echo using 1Sheeld [118]. 1Sheeld is a hardware shield that allows using sensors of the smartphone as the inputs for Arduino. In this project, a smartphone is used to play music (Music Player Shield), interact with the person by responding to questions (Text to Speech Shield), return the real-time (Clock Shield), return the information about the weather (Internet Shield) and it is controlled by voice commands (Voice Recognition Shield).
Few of the Arduino IoT related projects are also provided on the hackster.io website [119].
The first project that is viewed is the Harry Potter Weasleys' Clock using Bolt IoT [120]. The idea of this project is taken from the Harry Potter movie where the wall clock indicated the location of family members. In this modern IoT project, using Arduino Uno microcontroller, servomotor and Bolt WiFi Module, the clock that using arrow represents the location of the person that has a smartphone has been developed. The clock and the tracked smartphone are connected to the Bolt cloud server [121].
The breadboard is a mechanical base for electronics prototyping. Originally for this purpose was used the wooden board for cutting bread, that is why the name used now is the “breadboard”, also know as a solderless breadboard. It is because an electrical connection on this board can be made without soldering. Thus components can be reused, and changes to the circuit can be made easily.
On the front surface of the breadboard, many small holes are connected on the back of the board in a specific pattern using metal clips. These clips hold component leg or wire in place when they are put into the hole.
As the example, the circuit that contains the LED and resistance is taken.
Following the schematics, all the necessary components are connected in the right way using the breadboard.
Two side columns of the breadboard are made easily accessible from the rest of the breadboard and are commonly used for connecting the power to the circuit. Almost any DC (direct current) power source can be used to power the circuit, like batteries, Arduino board power pins, AC/DC regulators etc.
Two columns of 5 hole rows are used for connecting components. Extra connections can be made using wires. The gap in the middle allows using DIP (dual in-line package) circuits.
More information on breadboards can be found in the SparkFun webpage [122].
Soldering is one of the essential skills in the world of electronics. The basic of electronics can also be a learner without the knowledge of how to solder; however, the soldering allows to work on more exciting projects and to join a wide range of electronics enthusiasts. This skill is essential because nowadays the electronic and electrical equipment is being used more often. The essential element of the knowledge about electronics is not only the ability to understand the working principles of the electronics but also the skill to build, repair and supplement the electronic devices.
Materials
The main soldering material is a solder. As the name of the material indicates, it is a compound of various soft metals and consumable materials, which is usually similar to a wire, wrapped in a reel or other easy-to-use packaging.
Different types of solder vary in diameter of the wire and the chemical composition. The type of solder that is used depends on the task.
According to the chemical composition, the solder is divided into two types: solder containing lead and lead-free solder. Historically, lead (Pb) is used in combination with tin (Sn) to ensure lower melting temperatures and better flow, which is essential for good contact between the parts.
Since 2006, many countries have forbidden using lead-containing solder, caring for the protection of nature and human health. When lead accumulates in the human body in significant amounts, it can cause poisoning. For this reason, it is important to remember – after using lead-containing solder, you should carefully wash your hands.
To avoid the risk of getting lead in the human body, the alternative is to use lead-free solder. Nevertheless few things need to be taken into account – the melting temperature of lead-free solder is higher and grip with other materials is lower. For improving the grip, flux can be used. Some solders already have flux in the core of the wire, so it is not necessary to buy them separately.
The diameter of the solder wire depends on the size of details that need to be soldered. The bigger the detail, the bigger the diameter of the solder wire.
Tools
Tools used for soldering are a soldering iron, stand for soldering iron, as well as different tools for removing solder and keeping parts together.
A soldering iron is an electrical heater that is used for heating the solder to it's melting temperature. As with all of the electrical devices, the instructions provided by the manufacturer should be followed when using the soldering iron. For specific tasks, there are also hot air and gas soldering irons. In this section, only the electric ones will be described.
There are different types of soldering irons so that they can be effectively used for diverse tasks. Soldering irons can vary in a shape of the tip, electrical capacity, heating temperature and control options. However, the primary indicator is the convenience of use. If the chosen soldering iron is too big, it will be difficult to solder small details. If it is too small, details probably will not get enough of the heat, and the solder will not stick properly to them. For beginners, it is advisable to use a soldering iron with a conical tip.
In addition to the soldering iron, there are several useful tools for soldering.
Process of Soldering
It is not easy to describe the process of soldering in words, because it is connected with the kinesthetic skills of people and the paid attention. However, it is essential to follow several pieces of advice for good soldering.
In the Figure 211, the correct and incorrect soldering techniques are indicated.
The following sub-chapters cover programming fundamentals in Arduino(C) C/C++, which complies with the most C/C++ notations and have some specific Arduino notations. Those who feel comfortable in programming will find these chapter somewhat introductory, while for these having no or little experience, it highly recommended covering this introduction. This chapter and its sub-chapters also target ESP and Raspberry Pi devices on the general level partially, however, in any case, programming environment configuration is different for every platform even, if Arduino IDE constitutes the joint part. Refer to the chapters on ESP and Raspberry Pi hardware platforms:
Before starting programming the microcontroller, it is necessary to connect it to the computer.
Arduino Uno microcontroller is taken as a board for programming example tasks. It can be connected to a computer, using Universal Serial Bus (USB) port, using the appropriate USB cable. A microcontroller can be used together with a prototyping board or a robot. In the simplest programming tasks, it can be used as an independent device.
The microcontroller has to be powered via an external power supply or USB port. The microcontroller determines the power source automatically. If external power supplies other than USB are used, GND and VIN ports should be used to connect the power supply. The manufacturer recommends the use of a voltage of 7–12 V to ensure a normal operation of the microcontroller. If the voltage is exceeded, before reaching 20 V, then the power supply circuits of the microcontroller may get overheated. If the supply voltage is lower than 7 V, then the microcontroller may function unstable, and the result will be unpredictable.
In addition to the above mentioned, the microcontroller can provide a small power supply for external circuits by connecting them according to the microcontroller pins.
Each of the 14 digital inputs/outputs (I/O) of the microcontroller can be used to send or receive signals using the pinMode(), digitalWrite() and digitalRead() commands, which will be more detailed discussed in the chapter about the basics of programming. All I/O operate in the range of 0 V to 5 V. Each of the I/O is capable of receiving or sending no more than 40 mA of current. They all have internal load resistors in the range of 20–50 kΩ.
Descriptions of other microcontroller pin and their specific use are explained below. In addition to these I/O, the microcontroller also provides other specific functions that will be described below.
To start the development of software for a microcontroller, it is necessary to install and properly configure the development environment that consists of the program editor and the Arduino UNO driver. Below are described all the steps that are needed to prepare the programming environment for Windows 10 OS.
Step 1. Preparing Arduino UNO and the USB Cable
Before installing the programming environment, it is necessary to prepare the Arduino UNO board and the USB cable for connecting the board to the computer.
Step 2. Downloading the Arduino Software Development Environment
The open-source Arduino Software (Integrated development environment (IDE)) can be found in the official Arduino website [124]. The appropriate installation file depends on the OS of the computer and the access rights of the user.
For Windows OS, the Windows Installer should be clicked, and then the file should be saved on the computer. When the installation file has downloaded, the file should be run. If the ZIP file was downloaded, it is necessary to unarchive it and to run the installer. Follow the instructions of the installer. If the operating system asks for permission to install the driver of the board – allow it.
It is also possible to use Arduino Web Editor (can be found on the same website) to work online with the Arduino board, but this option will not be considered in this manual.
Step 3. Connecting to Arduino
Using USB cable, Arduino needs to be connected to a free USB port of a computer. The blue LED on the Arduino board starts to shine continuously. Aforementioned is the indicator that the Arduino board is working.
The green LED will blink, and that will indicate the performance of the manufacturer test software. In case if the green LED is not flashing, it is not an error.
Step 4. Starting Up the Programming Environment
The Arduino programming environment can be started with the double-click on the desktop shortcut of the Arduino software. The language of the environment will respond to the one that is set up in the OS of the computer, that means if it is English, then the menu of the programming environment will also be in the English language. To change the language preferences, it is necessary to follow the instructions in the following webpage [125].
Step 5. Open the Example Program
In the Arduino IDE open File → Examples → 01.Basics → Blink as shown in the image below.
This will open in the new window an example program for turning on and off green LED that is situated on the Arduino UNO board with the 1 second delay.
Step 6. Choosing the Microcontroller
In this step it is necessary to choose the type of board that is used. In this example the Arduino UNO board is used that is why in the menu of Arduino IDE choose Tools → Board → Arduino/Genuino Uno as shown in the image below.
Step 7. Setting Up COM Port
To ensure transmitting and receiving data to/from the microcontroller, it is necessary to set the serial communication port – COM port. All ports are numbered in order, and for Arduino microcontroller, it is usually higher than COM3, i.e. COM4, COM5, etc. In the image below, it is COM4.
Step 8. Uploading the Example Program to the Board
Now the program can be uploaded to the Arduino board using the Upload button in the top left corner of the software, then wait for a few seconds, during which you can see the data sending indicators – LEDs are blinking fast (indicates sending or receiving data) – and wait for the message to be “Upload is complete”.
After a few seconds, the green LED will blink with a one-second interval like it is written in the source code. If this can be observed successfully, then everything is done to start learning the basics of programming.
In case if the blinking green LED cannot be observed, instructions for troubleshooting can be read in the following link [126].
If you want to get acquainted yourself with microcontroller capabilities or programming basics independently, look at one of these sources of information:
Check Yourself
1. What power supply Arduino UNO microcontroller requires?
2. How to operate with inputs/outputs of the microcontroller?
3. Try different examples in the menu of Arduino IDE.
Arduino IDE is a software that allows writing Arduino code. Each file with Arduino code is called a sketch. The Arduino programming language is similar to the C++ language. For the Arduino IDE to compile the written code without errors, it is important to follow the pre-defined syntax.
Define
#define is a component that allows giving a name to a constant value at the very beginning of the program.
#define constant 4
In this example, the value four is assigned to the constant.
Note that at the end of this expression semicolon (;) is not necessary and between the name and the value, the sign of equality ( should not be added!
Include
#include is a component that allows to include libraries from the outside of the program. Just like the #define, #include is not terminated with the semicolon at the end of the line!
#include <Servo.h>
In this example, the library called Servo.h that manages servomotors has been added to the sketch.
Comments
There are two ways to write comments in the sketch so that the written text is not compiled as a part of the running code.
//Single line comment is written here
The double backslash is used when the only single line should be commented on.
/*Multi-line comments are written here
Second line of the comment
...
*/
In this way, the backslash followed by asterisk defines the beginning of the block comment, and the asterisk followed by backslash defines the end of the block comment.
Semicolon
The semicolon (;) is used at the end of each statement of the code.
int pin = 5;
In this example, a single statement is int pin = 5 and the semicolon at the end tells the compiler that this is the end of the statement and it can continue with the next one.
Curly Braces
Curly braces are used to enclose a further block of instructions. Curly braces usually follow different functions that will be viewed in the further sections.
Each opening curly brace should always be by a closing curly brace. Otherwise, the compiler will show an error.
void function(datatype argument){ statements(s) }
Use of curly braces in the own defined function.
Below is given an example, how a new empty sketch looks like.
Each Arduino sketch contains multiple parts.
void setup() //The result data type and the name of the function { //Beginning of the initialization function //The body of the function - contains all executable statements } //The end of the initialization function
As it was already mentioned, this function will execute only once. The function is described by the result data type (number, symbol array or something else). In this example, the keyword setup means that the setup function does not have the result that means it is executed only once, and that is all. The name necessarily should be setup, so that the built-in subsystem of the board program execution could differ the initialisation section from the rest of the code.
void loop() //The result data type and the name of the function { //Beginning of the loop function //Logic //The body of the function - contains all executable statements } //The end of the loop function
The result data type of this function is the same as previous – void – that shows that the function does not have the result, it will be executed in the loop continuously while the program is working.
The code of the Blink LED program code will be viewed now. The example can be opened by following the path in Arduino IDE: File → Examples → 01.Basics → Blink.
When the Blink LED example program is opened, the following sketch should open in the programming environment:
The code of the example program is the following:
//The setup function runs once when you press reset or power the board void setup() { //Initialize digital pin LED_BUILTIN as an output. //LED_BUILTIN stands for the built-in LED on the board. pinMode(LED_BUILTIN, OUTPUT); } //The loop function runs over and over again forever void loop() { digitalWrite(LED_BUILTIN, HIGH); //Turn the LED on (HIGH is the voltage level) delay(1000); //Wait for a second digitalWrite(LED_BUILTIN, LOW); //Turn the LED off by making the voltage LOW delay(1000); //Wait for a second }
In the source code of the program, the following things can be seen.
Hello World program is the simplest program because it simply outputs the text to the screen. Here is the Hello World program for Arduino that outputs the text on the Serial Monitor each second:
void setup() { Serial.begin(9600); //Establishes the connection with the serial port } void loop() { Serial.println("Hello World"); //Prints out the line with the text delay(1000); //Pause for 1 second }
Serial Monitor can be found following the path: Tools → Serial Monitor.
In the code can be seen that the setup() function contains the following command:
Serial.begin(9600);
This statement opens the serial port at the initialisation of the program so that the Serial Monitor can be used for outputting text or values on the screen.
For printing out text the following command is used:
Serial.println("Hello World");
Check Yourself
1. How to attach any library to a sketch?
2. What command are expressions not usually separated by the semicolon?
3. How to establish serial communication between devices?
4. How does delay() command works?
Each variable has its data type that determines its place in the memory of the microcontroller and also the way how it can be used. There are plenty of different data types. Further will be viewed the most used ones.
byte exampleVariable = 123;
int exampleVariable = 12300;
float exampleVariable = 12300.546;
int firstArray[] = {12,-3,8,15};
Square brackets of the array can be used to access some value in the array by index. In the following example, the element with index 1 (that is –3) is assigned to the secondVariable variable.
int secondVariable = firstArray[1];
An array can be easily processed in the cycle. The next example shows how to store the necessary values automatically from the analogue signal input to the previously defined array.
//The cycle that repeats 4 times for(int i = 0; i < 4; i = i + 1){ //Reads value from the pin 2 and stores it in the //exampleArray//. exampleArray[i] = analogRead(2); }
This cycle in the example starts with index 0 (i = 0), and it increases by 1 while it is smaller than 4 (not including). That means in the last cycle the index value will be 3, because when the i equals 4, the inequality i < 4 is not true, and the cycle stops working.
Data type conversion can be done using multiple techniques – casting or data type conversion using specific functions.
int i; float f=4.7; i = (int) f; //Now i is 4
int i = int(123.45); //The result will be 123
String string = "123fkm"; float f = string.toFLoat(); //The result will be 123.00
String string = "123fkm"; int i = string.toInt(); //The result will be 123
Arithmetic operations on Arduino are used to do mathematical calculations with the numbers or numerical variables. The arithmetic operators are the following.
int result = 1 + 2; //The result of the addition operation will be 3
int result = 3 - 2; //The result of the subtraction operation will be 1
int result = 2 * 3; //The result of the multiplication operation will be 6
//The result of the division operation will be 3 //(Only the whole part of the division result) int result = 7 / 2; //The result of the division operation will be 3.5 float result2 = 7.0 / 2.0;
//The result of the modulo operation will be 1, //Because if 7 is divided by 3, the remaining is 1 int result = 7 % 3;
Compound operators in Arduino are a short way of writing down the arithmetic operations with variables. All of these operations are done on integer variables. These operands are often used in the loops when it is necessary to manipulate with the same variable in each iteration of the cycle. The compound operators are the following.
int a = 5; a++; //The operation a = a + 1; the result will be 6
int a = 5; a--; //The operation a = a – 1; the result will be 4
int a = 5; a+=2; //The operation a = a + 2; the result will be 7
int a = 5; a-+3; //The operation a = a – 3; the result will be 2
int a = 5; a*=3; //The operation a = a × 3; the result will be 15
int a = 6; a/=3; //The operation a = a / 3; the result will be 2
int a = 5; //The result will be the remaining //Part of the operation a/2; it results in 1 a%=2;
int a = 5; a|=2; //The operation a=a|2; the result will be 7
int a = 6; a&=; //The operation a=a&2; the result will be 2
Check Yourself
1. What is the data type used for a variable range 0…255?
2. What should data type be used for more precise measurements?
3. Make data type conversion char to string.
4. What is the main advantage of using compound operators?
if is a statement that checks the condition and executes the following statements if the condition is TRUE. There are multiple ways how to write down the if statement:
//1st example if (condition) statement; //2nd example if (condition) statement; //3rd example if (condition) { statement; } //4th example if (condition) { statement; }
When both TRUE and FALSE cases of the condition should be viewed, the else part should is added to the if statement in the following ways:
if (condition) { statement1; //Executes when the condition is true } else { statement2; //Executes when the condition is false }
If more conditions should be viewed, the else if part is added to the if statement:
if (condition1) { statement1; //Executes when the condition1 is true } else if (condition2) { statement2; //Executes when the condition2 is true } else { statement3; //Executes in all of the rest cases }
The example when the x variable is compared and in the cases when it is higher than 10, the digitalWrite() method executes.
if (x>10) { //Statement is executed if the x > 10 expression is true digitalWrite(LEDpin, HIGH) }
Logical operators are widely used together with the condition operator if that is described below.
Comparison Operators
There are multiple comparison operators used for comparing variables and values. All of these operators compare the value of the variable on the left to the value of the variable on the right. Comparison operators are the following:
Examples:
if (x==y){ //Equal //Statement } if (x!=y){ //Not equal //Statement } if (x<y){ //Less than //Statement } if (x<=y){ //Less than or equal //statement } if (x>y){ //Greater than //Statement } if (x>=y){ //Greater than or equal //Statement }
Boolean Operators
Three Boolean logical operators in the Arduino environment are the following:
Examples:
//Logical NOT if (!a) { //The statement inside if will execute when the a is FALSE b = !a; //The reverse logical value of a is assigned to the variable b } //Logical AND //The statement inside if will execute when the //Values both of the a and b are TRUE if (a && b){ //Statement } //Logical OR //The statement inside if will execute when at least one of the //a and b values is TRUE if (a || b){ //Statement }
Switch statement similar like if statement controls the flow of program. The code inside switch is executed in various conditions. A switch statement compares the values of a variable to the specified values in the case statements. Allowed data types of the variable are int and char. The break keyword exits the switch statement.
Examples:
switch (x) { case 0: //Executes when the value of x is 0 // statements break; //Goes out of the switch statement case 1: //Executes when the value of x is 1 // statements break; //Goes out of the switch statement default: //Executes when none of the cases above is true // statements break; //Goes out of the switch statement }
Check Yourself
1. Which code part is the correct one?
2. What is the output of the next code part?
int x = 0; switch(x) { case 1: cout << "One"; case 0: cout << "Two"; case 2: cout << "Hello, world!"; }
3. In which cases switch structure should be used?
for is a cycle operator that allows specifying the number of times when the same statements will be executed. In this way, similar to the loop function it allows to control the program execution. Each time when all statements in the body of the cycle are executed is called the iteration. In this way, the cycle is one of the basic programming techniques that is used for all programs and automation in general.
The construction of a for cycle is the following:
for (initialization ; condition ; operation with the cycle variable) { //The body of the cycle }
Three parts of the for construction is the following:
The example of the for cycle:
for (int i = 0; i < 4; i = i + 1) { digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); }
On the initialization of the for cycle the variable i = 0 is defined. The condition states that the for cycle will be executed while the value of variable i will be less than 4 (i < 4). In the operation with the cycle variable it is increased by 1 each time when the cycle is repeated.
In this example above, the LED that is connected to the pin 13 of the Arduino board will turn on/off four times.
while cycle operator is similar to the for cycle operator that is described above, but it does not contain the cycle variable. Because of this, the while cycle allows to executed previously unknown number of iterations. The management of the cycle is realised using only condition that needs to be TRUE for the next operation to execute.
The construction of the while cycle is the following:
while (condition that is TRUE) { //The body of the cycle }
That way the while cycle can be used as a good instrument for the execution of a previously unpredictable program. For example, if it is necessary to wait until the signal from pin 2 reaches the defined voltage level – 100, the following code can be used:
int inputVariable = analogRead(2); while (inputVariable < 100) { digitalWrite(13, HIGH); delay(10); digitalWrite(13, LOW); delay(10); inputVariable = analogRead(2); }
In the cycle, the LED that is connected to the pin 13 of the Arduino board will be turned on/off while the signal will reach the specified level.
The do…while cycle works the same way like the while loop. The difference is that in the while cycle the condition is checked before entering the loop, but in the do…while cycle the condition is checked after execution of the statements in the loop and then if the condition is TRUE the loop repeats. As a result, the statements inside the cycle will execute at least once, even if the test condition is FALSE.
The construction of a do while cycle is the following:
do { //The body of the cycle } while (condition that is TRUE);
If the same code is taken from the while loop example and used in the do…while cycle, the difference is that the code will execute at least once, even if the inputVariable value is more than or equal to 100. The example code:
int inputVariable = analogRead(2); do { digitalWrite(13, HIGH); delay(10); digitalWrite(13, LOW); delay(10); inputVariable = analogRead(2); } while (inputVariable < 100);
Check Yourself
1. What is a kind of the loop, where the condition is checked after the loop body is executed?
2. How long will the operators in the body of the loop operate [while (x < 100)]?
3. What value will be for variable a after code executing?
int a; for(a = 0; a < 10; a++) {}
4. Which of the following operators are not loop(s) in Arduino IDE?
Functions are the set of statements that are executed always when the function is called. Two functions that were mentioned before are already known – setup() and loop(). The programmer is usually trying to make several functions that contain all the statements and then to call them in the setup() or loop() functions.
The structure of the function is following:
type functionName(arguments) //A return type, name and arguments of the function { //The body of a function – statements to execute }
For the example, a function that periodically turns on and off the LED is created:
void exampleFunction() { digitalWrite(13, HIGH); //the LED is ON delay(1000); digitalWrite(13, LOW); //the LED is OFF delay(1000); }
In the example code can be seen that the return type of aexampleFunction function is void that means the function does not have the return type. This function also does not have any arguments because the brackets are empty.
This function should be called inside the loop() function in the following way:
void loop() { exampleFunction(); //the call of the defined function inside loop() }
The whole code in the Arduino environment looks like this:
void loop() { exampleFunction(); //the call of the defined function inside loop() } void exampleFunction() { digitalWrite(13, HIGH); //the LED is ON delay(1000); digitalWrite(13, LOW); //the LED is OFF delay(1000); }
It can be seen that the function is defined outside the loop() or setup() functions.
When some specific result must be returned as a result of a function, then the function return type should be indicated, for example:
//the return type is "int"; arguments are still optional int sumOfTwoNumbers(int x, int y) { //the value next to the "return" should have the "int" type; //this is what will be returned as a result. return (x+y); }
In the loop() this function would be called in a following way:
void loop() { //the call of the defined function inside loop() int result = sumOfTwoNumbers(2, 3); }
Interrupt is a signal that stops the normal execution of a program in the processor, to be able to handle tasks with higher priority. These tasks usually are called Interrupt service routine (IRS) or interrupt handler. Interrupt signals can be generated from the external source, like a change of value on the pin and from the internal source, like a timer. When the interrupt signal is received, the processor stops executing the code and starts the IRS. After completing the IRS, the processor returns to the normal program execution state.
IRS should be as short as possible, and the return type of it is void. Some of normal Arduino functions do not work or behave differently in the IRS, for example, delay() function does not work in the IRS. Variables, used in the IRS must be volatile variables.
Interrupts are used to detect important real-time events, which occur during the normal code execution of the code, without continuously checking them, like pushing a button.
Different Arduino types have different external interrupt pin availability. In most Arduino boards pins, number 2 and 3 are used for interrupts.
To attach interrupt, the function attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) is called. This function has 3 parameters.
The example program that uses interrupt:
volatile bool button =0; //A variable to save button state void setup() { //Define LED pin pinMode(13,OUTPUT); //Define button pin pinMode(2,INPUT_PULLUP); //Attach interrupt to button pin attachInterrupt(digitalPinToInterrupt(2),ButtonIRS,FALLING); } void ButtonIRS() { //IRS function button =!button; } void loop() { digitalWrite (13,button); }
Check Yourself
1. What are the built-in functions used for?
2. Which of the following statements are true?
3. Is it possible to guarantee that the declared built-in function is really built-in?
The simplest solution to make functions work for a certain time is to use delay() [129] function. delay() function stops program execution for instructed time.
Blinking LED is a simple demonstration of delay functionality:
digitalWrite(LED_BUILTIN, HIGH); //Turn the LED on delay(1000); //Stop program for a second digitalWrite(LED_BUILTIN, LOW); //Turn the LED off delay(1000); //Stop program for a second
Limitations that come with using delay(): most tasks stop, there can be no sensor reading, calculation, pin manipulation. Some tasks continue to work, like receiving serial transmissions and outputting set PWM values. Alternative of using delay is to use millis().
millis() [130] returns number in milliseconds since Arduino began running current program. The number will reset after approximately 50 days. millis() can be used to replace delay().
Here is an example code of blinking LED using millis(). Millis is used as a timer. Every new cycle time is calculated since the last LED state change if passed time is equal to or greater than the threshold value LED is switched:
//Unsigned long should be used to store time values //Store value of current millis reading unsigned long currentTime = 0; //Store value of time when last time the LED state was switched unsigned long previousTime = 0; bool ledState = LOW; //Varible for setting LED state const int stateChangeTime = 1000; //Time at which switch LED states void setup() { pinMode (LED_BUILTIN, OUTPUT); //LED setup } void loop() { currentTime = millis(); //Read and store current time //Calculate passed time since last stateChange //If more time has passed than stateChangeTime, start state Change if (currentTime - previousTime >= stateChangeTime) { previousTime = currentTime; //Store LED state change time ledState = !ledState; //Change LED state to oposite digitalWrite(LED_BUILTIN, ledState); //Write LED state to LED } }
Protothread is a mechanism for concurrent programming in embedded systems with limited resources. In Arduino, it can be used to achieve a periodical function call, like sensor reading, output state change, calculations etc. Most Arduino used microcontrollers have only one core; it can’t execute multiple functions simultaneously. By using millis() and loop, we can schedule a function call if appropriate conditions are met, thus avoiding unnecessary taking processors time.
In the example, code second LED is added, to demonstrate how to blink two LED’s concurrent with different frequencies. In the same manner, servo control, button reading and other functionality can be added.
//Unsigned long should be used to store time values //Store value of current millis reading unsigned long currentTime = 0; //Store value of time when last time LED1 state was switched unsigned long previousTime1 = 0; //Store value of time when last time LED2 state was switched unsigned long previousTime2 = 0; bool led1State = LOW; //Varible for setting LED1 state bool led2State = LOW; //Varible for setting LED2 state const int stateChangeTimeLed1 = 1000; //Time at which switch LED1 states const int stateChangeTimeLed2 = 200; //Time at which switch LED2 states void setup() { pinMode (LED_BUILTIN, OUTPUT); //LED1 setup pinMode (2,OUTPUT); //LED2 setup } void loop() { currentTime = millis(); //Read and store current time //Calculate passed time since the last stateChange //If more time has passed than stateChangeTime, start state change if (currentTime - previousTime1 >= stateChangeTimeLed1) { previousTime1 = currentTime; //Store LED state change time led1State = !led1State; //Change LED1 state to oposite digitalWrite(LED_BUILTIN, led1State); //Write led1State to LED1 } if (currentTime - previousTime2 >= stateChangeTimeLed2) { previousTime2 = currentTime; //Store LED2 state change time led2State = !led2State; //Change led2state to oposite digitalWrite(2, led2State); //Write led2state to LED2 } }
There are dedicated libraries made by the Arduino community to implement protothreads [131].
Check Yourself
1. What are the drawbacks of using delay()?
2. What is the difference between delay() and millis()?
3. Can Arduino run truly parallel programs?
4. How is concurrency achieved on Arduino?
The digital inputs and output of Arduino allow connecting different type of sensors and actuators to the board. Digital signals can take two values – HIGH(1) or LOW(0). These types of inputs and outputs are used in applications when the signal can have only two states.
pinMode()
The function pinMode() is essential to indicate whether the specified pin will behave like an input or an output. This function does not return any value. Usually, the mode of a pin is set in the setup() function of a program – only once on initialisation.
The syntax of a function is the following:
pinMode(pin, mode)
The parameter pin is the number of the pin.
The parameter mode can have three different values – INPUT, OUTPUT, INPUT_PULLUP depending on whether the pin will be used as an input or an output. The INPUT_PULLUP mode means that the pin will work as an input whose state will be inverted (set to the opposite). More about Pullup Resistors can be found in the Arduino homepage [132].
digitalWrite()
The function digitalWrite() writes a HIGH or LOW value to the pin. This function is used for digital pins, for example, to turn on/off LED. This function as well does not return any value.
The syntax of a function is the following:
digitalWrite(pin, value)
The parameter pin is the number of the pin. The parameter value can take values HIGH or LOW. If the mode of the pin is set to the OUTPUT, the HIGH sets voltage to +5 V and LOW – to 0 V.
It is also possible to use this function for pins that are set to have the INPUT mode. In this case, HIGH or LOW values enable or disable the internal pull-up resistor.
digitalRead()
The function digitalRead() works in the opposite direction than the function digitalWrite(). It reads the value of the pin that can be either HIGH or LOW and returns it.
The syntax of a function is the following:
digitalRead(pin)
The parameter pin is the number of the pin.
On the opposite as the functions viewed before, this one has the return type, and it can take a value of HIGH or LOW.
The analogue inputs and outputs are used when the signal can take a range of values, unlike the digital signal that takes only two values (HIGH or LOW). For measuring the analogue signal, Arduino has built-in analogue-to-digital converter (ADC) that returns the digital value of the voltage level.
analogWrite()
The function analogWrite() is used to write an analog value (Pulse Width Modulation (PWM)) of the integer type as an output of the pin. The example of use is turning on/off the LED in various brightness levels or setting different speeds of the motors. The value that is written to the pin stays unchanged until the next value is written to the pin.
The syntax of a function is the following:
analogWrite(pin, value)
The parameter pin is the number of the pin.
The parameter value is the
This function does not have the return type.
analogRead()
The function analogRead() is used for analogue pins (A0, A1, A2, etc.) and it reads the value that is on the analogue pin.
The syntax of a function is the following:
analogRead(pin)
The parameter pin is the number of the pin whose value is read.
The return type of the function is the integer value between 0 and 1023. The reading of each analogue input takes around 100 ms that is 0.0001 s.
Check Yourself
1. To assign the Arduino pin operation mode, which function is using?
2. What command for analogue input read is used?
3. The digital output on Arduino works as a power source with voltage?
Arduino, along with a vast amount of peripheral boards, lacks integration of the networking capabilities in one SoC. Espressif ESP series was the natural answer for this disadvantage as their ESP 8266 with integrated WiFi, introduced in 2014, is widely recognised as a turning point for the IoT market, delivering de-facto fully functional IoT chip, providing high performance and low power to the end users and developers. ESP 32 launched in 2016 brought even more disrupting effect to the IoT ecosystems, introducing additional Bluetooth interface to the above. By the January 2018 company announced they delivered to the market 100 000 000 [133], thus constituting a de-facto standard for the IoT market devices.
Following chapters provide an overview of the networking programming with the use of ESP SoCs.
Espressif System-on-chip (SoC) devices are low-cost microcontrollers with full TCP/IP stack capability produced by Shanghai-based Chinese manufacturer, Espressif Systems [134]. The two most popular series of these microcontrollers are:
The ESP8266 is a low-cost system on chip (SoC) microcontroller with WiFi and full TCP/IP stack capability [135]. The chip first came to the market with the ESP-01 module, made by a third-party manufacturer, Ai-Thinker. This small module allows microcontrollers to connect to a WiFi network and make simple TCP/IP connections using Hayes-style AT commands. The low price and the fact that there were very few external components on the module, which suggested that it could eventually be very inexpensive in volume, attracted many users to explore it. The ESP8285 is an ESP8266 with 1 MiB of built-in flash, allowing for single-chip devices capable of connecting to WiFi. The successor to these microcontroller chips is the ESP32. For now, the ESP8366 family includes the following chip:
Main standard features of the ESP8266EX are:
Processor
Memory
Interfaces
Figure 225 shows function block of ESP8266 chip diagram [136].
There are many ESP8266 based modules on the market [137]. These modules combine ESP8266EX microcontroller and additional components mounted on PCB.
The most popular are these produced by AI-Thinker and remain the most widely available [138]:
Popular modules from other manufacturers:
The Espressif company also produces ready-made modules using the aforementioned chip. This is the series of ESP8266-based modules made by Espressif (Table 20).
The most widely used and chipest ESP-01 is presented on (Figure 226) and its pinout on (Figure 227).
Module ESP12F with pinout is presented on (Figure 228) and its pinout on (Figure 229).
Among the other modules, it is worth to be interested in WEMOS modules [144] (Figure 230, Figure 231). The WEMOS company offers dedicated sensor modules and inputs/outputs compatible with the processor modules. They are called WEMOS shields (Figure 232).
ESP32 NodeMCU pins (Figure 233).
ESP32 is a low-cost, low-power system on a chip (SoC) series microcontrollers with WiFi & dual-mode Bluetooth capabilities [145]. ESP32 SoC is highly integrated with built-in antenna switches, power amplifier, low-noise receive amplifier, filters, and power management modules. Inside all family there is a single-core or dual-core Tensilica Xtensa LX6 microprocessor with a clock rate of up to 240 MHz. ESP32 is designed for mobile, wearable electronics, and Internet-of-Things (IoT) applications. It features all the state-of-the-art characteristics of low-power chips, including fine-grained clock gating, multiple power modes, and dynamic power scaling. For now the ESP32 family includes the following chips:
(Figure ##REF:esp32_functions##) shows function block diagramm of ESP32 chip. Main common features of the ESP32 are: [146] [147].
Processors
Wireless connectivity
Memory: Internal memory
External Flash & SRAM
ESP32 chips with embedded flash do not support the address mapping between external flash and peripherals.
Peripheral Input/Output
Security
The company also produces ready-made modules using the aforementioned processors. These modules combines ESP32 microcontroller and additional components mounted on PCB with EM shield:
To accelerate the design of circuits, developers can use specially prepared sets with ESP32 which are ready to use. The original Espressif best known small development boards are:
Each ESP32 is equipped with standard 38/40-pis male connector containing universal GPIO ports, VCC 3.3/5 V, GND, CLK, I2C/SPI buses pins which developers can use to connect their external sensors, switches and other controlled devices to the ESP32 board and then program their behaviour within the code loaded to the board.
Using Espressif SoC devices in Arduino platform we can use all the previously described Arduino examples for sensors and actuators. But the most interesting are are those that use wireless networking functions. Both Espressif chip ESP32 and ESP8266 families can use similar network modes. As a WiFi device Espressif SoC can work in different networking modes (applies to medial layers 1–3 ISO-OSI model):
ESP32 can also use Bluetooth networking in the same configuration as WiFi.
The following sub-chapters cover programming fundamentals for ESP chips in C/C++, which complies with the most C/C++ notations and have some specific notations. Please note, this is a particular implementation of the programming, compatible with Arduino standards and Arduino(C) IDE. Following chapters present particular aspects of the network programming with ESP chips. Other structures (program flow control, GPIO, etc.) are shown in the Arduino section of this manual and apply straightforwardly to the ESP programming.
Further reading:
Following subchapters present guides for setting up the environment to enable you to use Arduino(C) IDE to develop solutions for ESP8266 and ESP32. Installation methodology varies much as ESP8266 is already integrated with so-called Board Manager in Ardiono(C) iDE, while ESP32 is somehow external to the Boards Manager and requires some steps to be done, to obtain fully integrated environment. Some of the steps vary in details among various operating systems you use (Windows/Mac/Linux), see details, but in any case, there is a common idea, what to do in following steps. Once it is done, programming of both ESP8266 and ESP32 does not differ much from programming, i.e. Arduino Uno, natively supported by Arduino(C) IDE.
We consider here software that is natively installed as a binary package on your operating system, not a WEB version of the IDE. To obtain the latest version of the binary package, visit the website [155], as on figure 253 or install it via integrated software management application (Windows App Store, iTunes, Linux Application Store, etc.).
Assuming you've purchased any of the ESP8266 development boards, it is essential to write the first program. In any case, you'll need a way to write, compile and upload your code to the ESP8266 SoC. Here Arduino(C) IDE comes handy, however before you start, you need to let the Arduino(C) IDE knows, how to compile and communicate with your ESP8266 chip. Below there is a short manual, presenting how to install development extension to the Arduino(C) IDE through the Boards Manager. Note, other solutions (i.e. manual installation via Github pull) is also possible, but we do not consider this option here. ESP8266 core for Arduino(C) IDE is maintained by the community, and the latest version is available here (along with up-to-date installation guide) [156].
Install ESP8266 Boards via Board Manager
Start Arduino(C) IDE (Figure 254):
Enter application menu File/Preferences:
and then go to the Additional Boards Manager URLs:, enter following URL and accept changes (press OK) (Figure 256):
http://arduino.esp8266.com/stable/package_esp8266com_index.json
Once finished, you need to tell the Board Manager, which definitions and tools to download. Open Tools/Board:/Boards Manager (Figure 257):
and filter all boards entering “ESP8266” in the Search area, as on figure 258, then click Install:
Note – installation downloads number of resources over Internet connection and may take some time.
Configure Project to Compile for ESP8266
Once you're done with Arduino(C) IDE configuration now it is time to start programming. The process above installs a number of board definitions. First, depending on the development kit you own, select the appropriate type of the development board in the Board Manager menu, i.e. WeMos D1 R2 & mini (Figure 259):
Communication Between Development Machine and ESP SoC
Most of the ESP development boards come with integrated programming interface via serial to USB converter (usually CH340, CP210x, FDTI, Prolific, etc.) that you connect to the development machine using the USB cable. Connector standards vary, but nowadays the most popular seems to be Micro USB connector. There is also possible to upload your compilation using wireless transmission (OTA – Over The Air), or dedicated programming device, but we do not consider it here as too complicated to implement (requires special firmware) and also insecure. You need to select correct device interface – it differs, depending on the operating system you use, see details below how to identify it in your computer.
Windows
Look into the Device Manager to identify COM port, your board is represented by, in the Windows OS (Figure 260, here COM4):
Linux
In case of the Linux distributions, run in the terminal:
lsusb
and
ls /dev/ttyUSB*
or
ls /dev/ttyACM*
to identify your board (Figure 261):
Mac OS
The similar way to Linux distros, look for the devices using:
ls /dev/tty.usbmodem*
or
ls /dev/tty.usbserial*
to get a list of connected devices providing serial port feature (Figure 262):
Configure Details for the SoC
Now select appropriate device in the Arduino IDE (Figure 263) and select communication speed (Figure 264). Eventually, change the other parameters. Details on the flash size, its organisation, communication speed and frequencies should be provided by your hardware vendor. If you experience errors during programming or programming hangs, try to reduce programming speed, as your computer may be not quick enough to deliver data stream over USB. Devices usually present their maximum programming (flashing) speed, and it is common they tolerate lower speeds.
Troubleshooting Access Denied Error
In case of the Linux and Mac, depending on the security context you run your Arduino(C) IDE, you may experience “Access Denied” error. There are several workarounds to this problem, starting from running Arduino IDE with sudo
credentials (not recommended) through creating udev rules
(advanced) to simplest – providing credentials on-demand and ad-hoc. The disadvantage of this method is you must usually run those command every time you reboot OS or reconnect your board, yet is simplest to handle (Figure 265 and 266):
sudo usermod -a -G dialout $USER
sudo chmod a+rw /dev/ttyUSB0
A need to run one or two of the commands above strongly depends on the operating system configuration.
In case of the Windows OS, marking application to “Run as Administrator” may help to remove permission related errors (Figure 267):
ESP32 SoC is a continuation and extension to the ESP8266 SOCs. At the moment of writing this manual, installation of the ESP32 development environment is not supported via integrated Board Manager of the Arduino(C) IDE, as presented above in the ESP8266 section. Uploading your binary to the ESP32 chip via USB to serial converter requires Python as the ESP32 flashing tool is written in the form of the Python script. In case you're aware of what Python programming language is and how to install it on your machine, please refer to the Python website [157] and install Python before continuing.
Installing ESP32 Core for Arduino IDE
Depending on the operating system you use, there is necessary to perform some steps to obtain a fully functional ESP32 development environment for Arduino(C) IDE. ESP32 core for Arduino is maintained by the community, and the latest version is available here (along with up-to-date installation guide) [158]. Steps tend to have the same meaning, but tools to obtain the result are different for different operating systems.
Linux
A guide for the most popular distros (Ubuntu/Debian) is presented below.
Modify user credentials
This step is optional, see related ESP8266 section. Run following command in the terminal to add a user to the dialout
group:
sudo usermod -a -G dialout $USER
Download and install git client
Run following command in the terminal:
sudo apt-get install git
Download and install python tools and packages
Run following commands in the terminal:
wget https://bootstrap.pypa.io/get-pip.py && \ sudo python get-pip.py && \ sudo pip install pyserial
Create destination folders and clone repository
Assuming your Arduino IDE is installed in your home directory, (~/Arduino
), run the folowing code in terminal:
mkdir -p ~/Arduino/hardware/espressif && \ cd ~/Arduino/hardware/espressif && \ git clone https://github.com/espressif/arduino-esp32.git esp32 && \ cd esp32
Pull depending modules and tools
Run following commands in the terminal:
git submodule update --init --recursive && \ cd tools && \ python2 get.py
Then start Arduino IDE.
Windows
Installing ESP32 core for Arduino requires Git client, both GUI and bash as well as command line operations.
Install Git and clone repository
You can download and install Git [159].
Once installed, choose “Clone Existing Repository” (Figure 268):
Use ESP32 core for Arduino repository address as source:
https://github.com/espressif/arduino-esp32.git
The destination folder depends on where you've installed Arduino IDE and your Windows user name. The common location is:
C:/Users/[YOUR_USER_NAME]/Documents/Arduino/hardware/espressif/esp32
note, you do not need to create this folder manually. If you install fresh copy of the Arduino IDE, perhaps there will be no hardware
subfolder, but Git GUI will create remaining path for you.
Once entered Source and Destination, click “Clone” (Figure 269). Cloning may take a while.
Pull depending modules
Use Git Bash command line (not a Windows command line!) to install dependencies (Figure 270).
Change directory to the esp32 git local copy then run submodules install:
cd Documents/Arduino/hardware/espressif/esp32/
git submodule update --init --recursive
Download ESP32 tools
Open Windows command line (not a Git Bash command line!), navigate to the tools folder (Figure 271):
cd C:/Users/[YOUR_USER_NAME]/Documents/Arduino/hardware/espressif/esp32/tools
then run:
get.exe
Mac OS
Instruction for the Mac is similar to this for Linux. They require terminal to issue a set of commands to install ESP32 development kit and tools.
Create destination folders and clone source
mkdir -p ~/Documents/Arduino/hardware/espressif && \ cd ~/Documents/Arduino/hardware/espressif && \ git clone https://github.com/espressif/arduino-esp32.git esp32 && \ cd esp32
Pull depending modules and tools
Run following commands in the terminal:
git submodule update --init --recursive && \ cd tools && \ python get.py
Then start Arduino IDE.
Troubleshooting
If you get the following error during installation:
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
then install command line developer tools using:
xcode-select --install
Configure Project to Compile for ESP32
Once ESP32 platform is installed, start Arduino IDE and you should see new board definitions, similar to those presented on the figure 272:
Refer to your board vendor for information about compatible configurations and setting up upload parameters. Detailed description and information on selecting communication port and upload speed is presented in the ESP8266 section, above.
To use the ESP8266 chip as a modem (Figure 273) we must first load the appropriate AT-command firmware.
If necessary, to restore the original firmware:
# BOOT MODE ## download ### Flash size 8Mbit: 512KB+512KB boot_v1.2+.bin 0x00000 user1.1024.new.2.bin 0x01000 esp_init_data_default.bin 0xfc000 (optional) blank.bin 0x7e000 & 0xfe000 ### Flash size 16Mbit: 512KB+512KB boot_v1.5.bin 0x00000 user1.1024.new.2.bin 0x01000 esp_init_data_default.bin 0x1fc000 (optional) blank.bin 0x7e000 & 0x1fe000 ### Flash size 16Mbit-C1: 1024KB+1024KB boot_v1.2+.bin 0x00000 user1.2048.new.5.bin 0x01000 esp_init_data_default.bin 0x1fc000 (optional) blank.bin 0xfe000 & 0x1fe000 ### Flash size 32Mbit: 512KB+512KB boot_v1.2+.bin 0x00000 user1.1024.new.2.bin 0x01000 esp_init_data_default.bin 0x3fc000 (optional) blank.bin 0x7e000 & 0x3fe000 ### Flash size 32Mbit-C1: 1024KB+1024KB boot_v1.2+.bin 0x00000 user1.2048.new.5.bin 0x01000 esp_init_data_default.bin 0x3fc000 (optional) blank.bin 0xfe000 & 0x3fe000
After uploading AT firmware and connecting module to PC, we can use ESP8266 as a modem with simple AT commands.
We can connect ESP8266 to PC with TTL-Serial-to-USB adapter, or we can use any microcontroller with a serial interface. The default baud rate settings are 115200,N,8,1. Next from any terminal type command:
AT
and press enter. If you get OK, the ESP8266 module is ready to use. Let’s try out some other commands. For example, let’s figure out exactly what firmware version we’re dealing with. To do that, we’ll use the following command:
AT+GMR
As a Wifi device ESP8266 can connect to the network in such modes:
By default, the ESP8266’s stock firmware is set to AP mode. If you’d like to confirm that, send the following command:
AT+CWMODE?
You should get this response: +CWMODE:2, where 2 corresponds to AP mode. To switch ESP8266 to client device mode, we use the following command:
AT+CWMODE=1
Now we can scan the airwaves for all WiFi access points in range. To do that, we send:
AT+CWLAP
Then the ESP8266 will return a list of all the access points in range. In with each line will be item consisting of the security level of the access point, the network name, the signal strength, MAC address, and wireless channel used. Possible security levels of the access point <0–4> mean:
Now we can connect to the available access point using proper “ssid_name” and “correct_password” with the command:
AT+CWJAP="ssid_name","corect_password"
If everything is OK, the ESP8266 will answer:
WIFI CONNECTED WIFI GOT IP OK
It means that ESP8266 is connected to the chosen AP and got a proper IP address. To check what the assigned address is we send the command:
AT+CIFSR
To set up ESp8266 to behave both as a WiFi client as well as a WiFi Access point.
AT+CWMODE=3
Programming networking services with ESP requires a connection on the networking layer between parties, mostly TCP.
ESP SoC can act as Access Point (AP): a device you connect to, like you connect a notebook to the Internet router, and as a client: ESP then behaves like any wifi enabled device, i.e. tablet or mobile phone, connecting to the Internet infrastructure. Interestingly, ESP 8366 SoC can act simultaneously in both modes at once, even, if it has only one WiFi interface!
Below there is sample code, how to implement both modes, using ESP libraries that came during installation of the development environment for Arduino IDE.
The third example shows how to send and receive a UDP packet while in client mode. It is the full solution to connect ESP to the NTP (Network Time Protocol) server to obtain current date and time from the Internet.
Last examples show, how to make a handy WiFi scanner showing available networks nearby.
This sketch based on standard example demonstrates how to program ESP8266 in AP mode:
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> /* Set these variables to your desired credentials. */ const char *ssid = "APmode"; const char *password = "password"; ESP8266WebServer server(80); void hRoot() { server.send(200, "text/html", "<h1>You are connected</h1>"); } /* Initialization */ void setup() { delay(1500); /* You can remove the password parameter if you want the AP to be open. */ WiFi.softAP(ssid, password); IPAddress myIP = WiFi.softAPIP(); server.on("/", hRoot); server.begin(); } void loop() { server.handleClient(); }
This sketch (standard example) demonstrates how to program ESP8266 in client mode:
#include <ESP8266WiFi.h> #include <ESP8266WiFiMulti.h> ESP8266WiFiMulti WiFiMulti; void setup() { delay(1000); // We start by connecting to a WiFi network WiFi.mode(WIFI_STA); WiFiMulti.addAP("SSID", "password"); while(WiFiMulti.run() != WL_CONNECTED) { delay(500); } delay(500); } void loop() { const uint16_t port = 80; const char * host = "192.168.1.1"; // ip or dns // Use WiFiClient class to create TCP connections WiFiClient client; if (!client.connect(host, port)) { delay(5000); return; } // This will send the request to the server client.println("Send this data to server"); //read back one line from server String line = client.readStringUntil('\r'); Serial.println(line); Serial.println("closing connection"); client.stop(); Serial.println("wait 5 sec..."); delay(5000); }
This sketch (based on standard example) demonstrates how to program ESP8266 as NTP client using UDP packets (send and receive):
#include <ESP8266WiFi.h> #include <WiFiUdp.h> char ssid[] = "**************"; // your network SSID (name) char pass[] = "**************"; // your network password unsigned int localPort = 2390; // local port to listen for UDP packets // NTP servers IPAddress ntpServerIP; // 0.pl.pool.ntp.org NTP server address const char* ntpServerName[] = {"0.pl.pool.ntp.org","1.pl.pool.ntp.org","2.pl.pool.ntp.org","3.pl.pool.ntp.org"}; const int timeZone = 1; //Central European Time int servernbr=0; // NTP time stamp is in the first 48 bytes of the message const int NTP_PACKET_SIZE = 48; //buffer to hold incoming and outgoing packets byte packetBuffer[ NTP_PACKET_SIZE]; // A UDP instance to let us send and receive packets over UDP WiFiUDP udp; void setup() { Serial.begin(115200); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); // WiFi.persistent(false); WiFi.mode(WIFI_OFF); delay(2000); // We start by connecting to a WiFi network WiFi.mode(WIFI_STA); delay(3000); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("DHCP assigned IP address: "); Serial.println(WiFi.localIP()); Serial.println("Starting UDP"); udp.begin(localPort); Serial.print("Local port: "); Serial.println(udp.localPort()); // first ntp server servernbr = 0; } void loop() { //get a random server from the pool WiFi.hostByName(ntpServerName[servernbr], ntpServerIP); Serial.print(ntpServerName[servernbr]); Serial.print(":"); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); // send an NTP packet to a time server // wait to see if a reply is available delay(1000); int cb = udp.parsePacket(); if (!cb) { Serial.println("no packet yet"); if ( servernbr = 5 ) { servernbr =0; } else { servernbr++; } } else { Serial.print("packet received, length="); Serial.println(cb); // We've received a packet, read the data from it udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer //the timestamp starts at byte 40 of the received packet and is four bytes, // or two words, long. First, esxtract the two words: unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // combine the four bytes (two words) into a long integer // this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; Serial.print("Seconds since Jan 1 1900 = " ); Serial.println(secsSince1900); // now convert NTP time into everyday time: Serial.print("Unix time = "); // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: const unsigned long seventyYears = 2208988800UL; // subtract seventy years: unsigned long epoch = secsSince1900 - seventyYears; // print Unix time: Serial.println(epoch); // print the hour, minute and second: // UTC is the time at Greenwich Meridian (GMT) Serial.print("The UTC time is "); // print the hour (86400 equals secs per day) Serial.print((epoch % 86400L) / 3600); Serial.print(':'); if ( ((epoch % 3600) / 60) < 10 ) { // In the first 10 minutes of each hour, we'll want a leading '0' Serial.print('0'); } // print the minute (3600 equals secs per minute) Serial.print((epoch % 3600) / 60); Serial.print(':'); if ( (epoch % 60) < 10 ) { // In the first 10 seconds of each minute, we'll want a leading '0' Serial.print('0'); } Serial.println(epoch % 60); // print the second } // wait ten seconds before asking for the time again delay(10000); } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress& address) { Serial.print("sending NTP packet to: "); Serial.println( address ); // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: udp.beginPacket(address, 123); //NTP requests are to port 123 udp.write(packetBuffer, NTP_PACKET_SIZE); udp.endPacket(); }
This sketch demonstrates how to scan WiFi networks. ESP8266 is programmed in access point mode. All found WiFi networks will be printed in TTY serial window.
#include "ESP8266WiFi.h" void setup() { Serial.begin(115200); // Set WiFi to station mode and disconnect // from an AP if it was previously connected WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); Serial.println("Setup done"); } void loop() { Serial.println("scan start"); // WiFi.scanNetworks will return the number of networks found int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) Serial.println("no networks found"); else { Serial.print(n); Serial.println(" networks found"); for (int i = 0; i < n; ++i) { // Print SSID and RSSI for each network found Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(")"); Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*"); delay(10); } } Serial.println(""); // Wait a bit before scanning again delay(5000); }
There are many different development software and tools which can be used for ESP32 programming [163]:
Of course, for programming ESP32 We can use all the previously described Arduino examples for sensors and actuators. But in our example, we will focus on programming in ESP-IDF, as this is the native Development Platform for ESP32. A detailed description of the installation of the development environment can be found here.
This example shows how to use the All Channel Scan or Fast Scan to connect to a Wi-Fi network. In the Fast Scan mode, the scan will stop as soon as the first network matching the SSID is found. In this mode, an application can set the threshold for the authentication mode and the Signal strength. Networks that do not meet the threshold requirements will be ignored. In the All Channel Scan mode, the scan will end after all the channels are scanned, and the connection will start with the best network. The networks can be sorted based on Authentication Mode or Signal Strength. The priority for the Authentication mode is: WPA2 > WPA > WEP > Open.
#include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_wifi.h" #include "esp_log.h" #include "esp_event_loop.h" #include "nvs_flash.h" /*Set the SSID and Password via "make menuconfig"*/ #define DEFAULT_SSID CONFIG_WIFI_SSID #define DEFAULT_PWD CONFIG_WIFI_PASSWORD #if CONFIG_WIFI_ALL_CHANNEL_SCAN #define DEFAULT_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN #elif CONFIG_WIFI_FAST_SCAN #define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN #else #define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN #endif /*CONFIG_SCAN_METHOD*/ #if CONFIG_WIFI_CONNECT_AP_BY_SIGNAL #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL #elif CONFIG_WIFI_CONNECT_AP_BY_SECURITY #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY #else #define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL #endif /*CONFIG_SORT_METHOD*/ #if CONFIG_FAST_SCAN_THRESHOLD #define DEFAULT_RSSI CONFIG_FAST_SCAN_MINIMUM_SIGNAL #if CONFIG_EXAMPLE_OPEN #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN #elif CONFIG_EXAMPLE_WEP #define DEFAULT_AUTHMODE WIFI_AUTH_WEP #elif CONFIG_EXAMPLE_WPA #define DEFAULT_AUTHMODE WIFI_AUTH_WPA_PSK #elif CONFIG_EXAMPLE_WPA2 #define DEFAULT_AUTHMODE WIFI_AUTH_WPA2_PSK #else #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN #endif #else #define DEFAULT_RSSI -127 #define DEFAULT_AUTHMODE WIFI_AUTH_OPEN #endif /*CONFIG_FAST_SCAN_THRESHOLD*/ static const char *TAG = "scan"; static esp_err_t event_handler(void *ctx, system_event_t *event) { switch (event->event_id) { case SYSTEM_EVENT_STA_START: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); ESP_ERROR_CHECK(esp_wifi_connect()); break; case SYSTEM_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); ESP_LOGI(TAG, "Got IP: %s\n", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); break; case SYSTEM_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); ESP_ERROR_CHECK(esp_wifi_connect()); break; default: break; } return ESP_OK; } /* Initialize Wi-Fi as sta and set scan method */ static void wifi_scan(void) { tcpip_adapter_init(); ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_LOGI(TAG, DEFAULT_SSID); ESP_LOGI(TAG, DEFAULT_PWD); wifi_config_t wifi_config = { .sta = { .ssid = DEFAULT_SSID, .password = DEFAULT_PWD, .scan_method = DEFAULT_SCAN_METHOD, .sort_method = DEFAULT_SORT_METHOD, .threshold.rssi = DEFAULT_RSSI, .threshold.authmode = DEFAULT_AUTHMODE, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); } void app_main() { // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK( ret ); wifi_scan(); }
To properly set up Station mode, it is necessary to enter SSID and password. To enter these values, before compiling the program, run the command:
make menuconfig
and then
make all
or
make flash
ESP application layer may offer simplified a vast number of services as known from the PC world and the Internet yet. The limitation is the RAM size, storage, number of concurrent connections and limited CPU capabilities. Response routines should be kept simple as ESP8266 is single-threaded and uses timers and interrupt system to handle WiFi tasks in the background.
Below we present a number of samples, introducing programming of the various scenarios with ESP8266.
ESP8266 Web Server Sample
This example can be compiled in Arduino IDE. It allows through the website to change the output state of PIN 4 and PIN 5 [164]. We can connect LED to these pins and change its state remotely using a web browser. Before compiling this example it is necessary to change these two lines, to enable the module to connect to the WIFI network:
const char* ssid = ".. put here your own SSID name ..."; const char* password = ".. put here your SSID password.. ";
Now please check in the serial console the ESp8266 IP number and connect with any browser to address: http://esp8266_ipnumber
// Load Wi-Fi library #include <ESP8266WiFi.h> // Replace with your network credentials const char* ssid = ".. put here your own SSID name ..."; const char* password = ".. put here your SSID password.. "; // Set web server port number to 80 WiFiServer server(80); // Variable to store the HTTP request String header; // Auxiliar variables to store the current output state String gpio5State = "off"; String gpio4State = "off"; // Assign output variables to GPIO pins const int gpiopin5 = 5; const int gpiopin4 = 4; void setup() { Serial.begin(115200); // Initialize the output variables as outputs pinMode(gpiopin5, OUTPUT); pinMode(gpiopin4, OUTPUT); // Set outputs to LOW digitalWrite(gpiopin5, LOW); digitalWrite(gpiopin4, LOW); // Connect to Wi-Fi network with SSID and password Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Print local IP address and start web server Serial.println(""); Serial.println("WiFi connected."); Serial.println("ESP8266 IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop(){ WiFiClient client = server.available(); // Listen for incoming clients if (client) { // If a new client connects, Serial.println("New Client."); // print a message out in the serial port String currentLine = ""; // make a String to hold incoming data while (client.connected()) { // loop while the client's connected if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then Serial.write(c); // print it out the serial monitor header += c; if (c == '\n') { // if the byte is a newline character // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code // (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, // then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); // turns the GPIOs on and off if (header.indexOf("GET /5/on") >= 0) { Serial.println("GPIO 5 on"); gpio5State = "on"; digitalWrite(gpiopin5, HIGH); } else if (header.indexOf("GET /5/off") >= 0) { Serial.println("GPIO 5 off"); gpio5State = "off"; digitalWrite(gpiopin5, LOW); } else if (header.indexOf("GET /4/on") >= 0) { Serial.println("GPIO 4 on"); gpio4State = "on"; digitalWrite(gpiopin4, HIGH); } else if (header.indexOf("GET /4/off") >= 0) { Serial.println("GPIO 4 off"); gpio4State = "off"; digitalWrite(gpiopin4, LOW); } // Display the HTML web page client.println("<!DOCTYPE html><html>"); client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); client.println("<link rel=\"icon\" href=\"data:,\">"); // CSS to style the on/off buttons // Feel free to change the background-color and // font-size attributes to fit your preferences client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"); client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;"); client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}"); client.println(".button2 {background-color: #77878A;}</style></head>"); // Web Page Heading client.println("<body><h1>ESP8266 Web Server</h1>"); // Display current state, and ON/OFF buttons for GPIO 5 client.println("<p>GPIO 5 - State " + gpio5State + "</p>"); // If the output5State is off, it displays the ON button if (gpio5State=="off") { client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>"); } else { client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>"); } // Display current state, and ON/OFF buttons for GPIO 4 client.println("<p>GPIO 4 - State " + gpio4State + "</p>"); // If the output4State is off, it displays the ON button if (gpio4State=="off") { client.println("<p><a href=\"/4/on\"><button class=\"button\">ON</button></a></p>"); } else { client.println("<p><a href=\"/4/off\"><button class=\"button button2\">OFF</button></a></p>"); } client.println("</body></html>"); // The HTTP response ends with another blank line client.println(); // Break out of the while loop break; } else { // if you got a newline, then clear currentLine currentLine = ""; } } else if (c != '\r') { // if you got anything // else but a carriage return character, currentLine += c; // add it to the end of the currentLine } } } // Clear the header variable header = ""; // Close the connection client.stop(); Serial.println("Client disconnected."); Serial.println(""); } }
After connecting with web browser to ESP8266 there will be such web page (figure 278), and we can change the input status of PIN 4 and 5 simply by pressing the appropriate button
ESP32 “Hello World”
This is simple program printing “Hello World” and it is written in Espressif IoT Development Framework
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_spi_flash.h" void app_main() { printf("Hello world!\n"); /* Print chip information */ esp_chip_info_t chip_info; esp_chip_info(&chip_info); printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ", chip_info.cores, (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); printf("silicon revision %d, ", chip_info.revision); printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024), (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); for (int i = 10; i >= 0; i--) { printf("Restarting in %d seconds...\n", i); vTaskDelay(1000 / portTICK_PERIOD_MS); } printf("Restarting now.\n"); fflush(stdout); esp_restart(); }
ESP32 Web Server
This example of ESP32 programming in Arduino and shows how to implement simple www server.
First we do a little initialisation
//################# LIBRARIES ################ #include <WiFi.h> #include <ESP32WebServer.h> #include <WiFiClient.h> //################ VARIABLES ################ String webpage = ""; // General purpose variable to hold HTML code const char* ssid = "ssdi"; // WiFi SSID const char* password = "password"; // WiFi Password int status = WL_IDLE_STATUS; int curr_index; String SensorStatusBME; // Site's Main Title String siteheading = "ESP32 Webserver"; // Sub-heading for all pages String subheading = "Sensor Readings"; // Appears on the tabe of a Web Browser String sitetitle = "ESP32 Webserver"; // A foot note e.g. "My Web Site" String yourfootnote = "ESP32 Webserver Demonstration"; // Version of your Website String siteversion = "v1.0";
Then we must implement the main www server activities. Mind, to access the server from outside of your network WiFi (LAN) e.g. on port 80 when in NAT mode, add a rule on your router that forwards a connection request to http://your_network_WAN_address:80 to http://your_network_LAN_address:80 and then you can access your ESP server from virtually anywhere on the Internet.
ESP32WebServer server(80); void setup() { Serial.begin(115200); // initialize serial communications curr_index = 1; time_to_measure = millis(); StartWiFi(ssid, password); StartTime(); //---------------------------------------------------------------------- Serial.println("To connect, uss: http://" + WiFi.localIP().toString() + "/"); // If the user types at their browser // http://192.168.0.100/ control is passed here and then // to user_input, you get values for your program... server.on("/", homepage); // If the user types at their browser // http://192.168.0.100/homepage or via menu control // is passed here and then to the homepage, etc server.on("/homepage", homepage); // If the user types something that is not supported, say so server.onNotFound(handleNotFound); // Start the webserver server.begin(); Serial.println(F("Webserver started...")); } void handleNotFound() { String message = "The request entered could not be found, please try again with a different option\n"; server.send(404, "text/plain", message); } void homepage() { append_HTML_header(); webpage += "<P class='style2'>This is the server home page</p><br>"; webpage += "<p class='style2'>"; webpage += "This is sample webpage"; webpage += "</p><br>"; webpage += "<p>This page was displayed on : " + GetTime() + " Hr</p>"; String Uptime = (String(millis() / 1000 / 60 / 60)) + ":"; Uptime += (((millis() / 1000 / 60 % 60) < 10) ? "0" + String(millis() / 1000 / 60 % 60) : String(millis() / 1000 / 60 % 60)) + ":"; Uptime += ((millis() / 1000 % 60) < 10) ? "0" + String(millis() / 1000 % 60) : String(millis() / 1000 % 60); webpage += "<p>Uptime: " + Uptime + "</p>"; append_HTML_footer(); server.send(200, "text/html", webpage); } void page1() { append_HTML_header(); webpage += "<H3>This is the server Page-1</H3>"; webpage += "<P class='style2'>This is the server home page</p>"; webpage += "<p class='style2'>"; webpage += "This is sample 1 page"; webpage += "</p>"; append_HTML_footer(); server.send(200, "text/html", webpage); }
next we must start Wifi :
void StartWiFi(const char* ssid, const char* password) { int connAttempts = 0; Serial.print(F("\r\nConnecting to: ")); Serial.println(String(ssid)); WiFi.begin(ssid, password); status = WiFi.status(); while (status != WL_CONNECTED ) { Serial.print("."); // wait 10 second for re-trying delay(10000); status = WiFi.status(); Serial.println(status); if (connAttempts > 5) { Serial.println("Failed to connect to WiFi"); // printWiFiStatus(); } connAttempts++; } Serial.print(F("WiFi connected at: ")); Serial.println(WiFi.localIP()); }
and last step is to implement main loop function:
void loop() { delay( 2000 ); server.handleClient(); }
As it is known, some of the microcontrollers, in order to increase performance provide more than one core. ESP32 is one of them providing two physical cores. In practice, it means that the program developed can run simultaneously on both cores. Thereby it is possible to optimize some of the tasks in a way that they are not waiting for each other but running in parallel instead. This is the main advantage of parallel programming comparing to a sequential one. However, it requires both dedicated program control structures and hardware support.
At the time while this chapter is being written, the simplest way of developing a parallel code on ESP32 is via using FreeRTOS™ [165], which is a widely used real-time library for different microcontrollers. The RTOS allows using most of the real-time and parallel programming features including semaphores, process assignments to cores and more. The following code chunks explain how to apply the most useful parallel programming features.
Let's start with an example of blinking LED and Text output (based on material found here [166]).
The first task is task1, that simply outputs a string “Hi there!” to default serial port with delay of 100 ms, i.e. 10 times per second:
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "driver/gpio.h" #define BLINK_GPIO 13 void task1_SayHi(void * parameters) { while(1) { printf("Hi there!\n"); vTaskDelay(100 / portTICK_RATE_MS); } }
The second task is to bilk a LED with a period of 2 seconds (1 second on, 1 second off):
void task2_BlinkLED(void * parameters) { gpio_pad_select_gpio(BLINK_GPIO); gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); while(1) { /*Sets the LED low for one second*/ gpio_set_level(BLINK_GPIO, 0); vTaskDelay(1000 / portTICK_RATE_MS); /*Sets the LED high for one second*/ gpio_set_level(BLINK_GPIO, 1); vTaskDelay(1000 / portTICK_RATE_MS); } }
Once both task functions are defined, they can be executed simultaneously:
void app_main() { nvs_flash_init(); xTaskCreate(&task1_SayHi, "task1_SayHi", 2000, NULL, 5, NULL); xTaskCreate(&task2_BlinkLED, "task2_BlinkLED", 2000,NULL,5,NULL ); }
To run the code physically in parallel it is necessary to assign task explicitly to the particular core, which requires a slight modification of the main() function:
void app_main() { nvs_flash_init(); xTaskCreatePinnedToCore(&task1_SayHi, "task1_SayHi", 2000, NULL, 5, NULL,0); xTaskCreatePinnedToCore(&task2_BlinkLED, "task2_BlinkLED", 2000,NULL,5,NULL,1); }
While ESP32 provide two computing nodes, other devices like particular serial port or other peripherals are only single devices. In some cases, it might be needed to access those devices by multiple processes in a way that does not disturb the others. In a terminology of parallel programming, those “single” devices are called resources that need to be shared or simply shared resources. To share a resource it is necessary to have a signal that is available to all processes and that determines if the resource is available or not. Those signals are dedicated data structures and are called - semaphores. Depending on the particular platform they might represent a different data structure to address particular use case. RTOS support three main semaphore types – Binary (True/False), Counting (represents a queue) and Mutex (binary semaphore with priority). More details on each type and use examples might be found here [169]. To explain the concept of resource sharing here a simple binary-semaphore example is provided. Example uses two SayHi tasks to share the same output device:
Since we need to define a semaphore at the beginning a setup function is also needed:
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "driver/gpio.h" SemaphoreHandle_t xSemaphore = NULL; void setup() { vSemaphoreCreateBinary( xSemaphore ); }
Now it is possible to define the task functions and modify them in a way they use the same resource
void task1_SayHi(void * parameters) { while(1) { /*check and waits for semaphore to be released for 100 ticks. If the semaphore is available it is taken / blocked */ if( xSemaphoreTake( xSemaphore, ( TickType_t ) 100 ) == pdTRUE ) { printf("TASK1: Hi there!\n"); vTaskDelay(100 / portTICK_RATE_MS); xSemaphoreGive( xSemaphore ); } else { //Does something else in case the semaphore is not available } } } void task2_SayHi(void * parameters) { while(1) { /*check and waits for semaphore to be released for 100 ticks. If the semaphore is available it is taken / blocked */ if( xSemaphoreTake( xSemaphore, ( TickType_t ) 100 ) == pdTRUE ) { printf("TASK2: Hi there!\n"); vTaskDelay(100 / portTICK_RATE_MS); xSemaphoreGive( xSemaphore ); } else { //Does something else in case the semaphore is not available } } }
Now both of the tasks are ready to be executed on the same or different cores as explained previously.
Raspberry Pi (referred as RPi or RPI) and its clones, i.e. Orange Pi, Banana Pi, Ordroid, Cubie, Olimex, are the class of devices located somewhere between low constraint IoT boards and regular PC/Mac machines.
Those devices technically are very close to smartphones and are far away from energy-efficient IoT solutions powered by a single battery that lasts for weeks or even years. They need DC, usually 5 or 12V and about 2-3W total, with external power adapter. It is still far less than even most efficient ultrabooks or PCs, requiring some 50-90W PSUs. They also use an operating system booted from storage like regular PCs - usually from flashed MicroSD card or embedded eMMC flash. The OS is mostly Linux based, but there do exist Microsoft Windows for certified Raspberry Pi devices. It is how this class of devices differ from, i.e. Arduino, where software is in the SoC model. The RPi and clones are holding a one-board solution that includes a processor, memory, storage slot, USB and networking. Many devices also offer hardware-based graphics acceleration, usually integrated with the processor core. Some devices like Orange Pi frequently provide an integrated flash for OS storage, so you do not necessarily need to boot and use an external flash like a USB dongle or TransFlash card. The most common processor in this class of the devices is an ARM architecture family, in case of the RPI it is Broadcom (i.e. BCM2936), other manufacturers use, i.e. Exynos, All-Winner and Samsung manufactured processors. What is pretty similar to the low-power, constrained IoT boards, RPi and clones offer GPIO, and you can connect various sensors and expansion boards (called here “hats”), and you have a wide choice of operating systems and modules. You can also extend the hardware by connecting hats that offer to sense and to actuate but sometimes advanced computing like dedicated coprocessors or FPGA-based AI. Interestingly, their GPIOs usually provide (among others) popular protocols like I2C, SPI, One-Wire, so you can directly connect with many sensors known as designed for Arduino-compatible development boards. This way, you can use those boards like conventional IoT devices with integrated networking capabilities, similar to, i.e. ESP chips.
What is much different from low constrained IoT devices is that they offer at least a command terminal you can connect to, and also most boards offer a capability to connect it to the external display via HDMI, analogue output or dedicated connector for LCD. They also provide the ability to interact with HID devices like regular keyboards, mouses, via USB but also wireless, i.e. using a Bluetooth connection. Of course, those features are dependent on operating systems. Manufacturers usually are trying to keep those development boards as small as possible, and it is a case that among high-end devices they also offer some constrained solution yet usually 50 % smaller in size and power consumption (i.e. RPi zero). Many boards also offer dedicated camera connector.
Being so far from the low-power, constrained IoT devices does not exclude them from IoT devices, however. They find their application everywhere, when there is a need for higher processing resources (i.e. voice recognition), high capacity and complex networking operations, i.e. gatewaying other devices to the Internet, convert networking protocols, implement software-based or hardware-assisted Artificial Intelligence, implementing rich user interface (GUI) where constrained devices are not powerful enough to fulfil the requirements yet there is still a limited power source, or there is not a need to set up a regular, PC-based solution, because of its cost. Most of the devices belonging to this class still can be switched to low power consumption modes, where low power means a dozen mA here.
On the other hand, most modern representatives of those devices are powered with multicore processors and large RAM and are powerful enough to replace the desktop computer in daily operations like web browsing, multimedia playback, software development and so on.
The Raspberry Pi is a series of small single-board computers developed in the UK by the Raspberry Pi Foundation to promote modern computer science in schools and developing electronic communities. Adding the 40-pin GPIO connector to the computer board allows developers not only improving their programming skills but also open them new horizons in controlling processes and devices not available for desktop computers. According to the Raspberry Pi Foundation, the entire boards' sales in July 2017 has reached nearly 15 million units. The first generation of this new board type was developed and then released in February 2012 – Raspberry Pi Model B. Each Raspberry Pi board contains hardware modules which together makes it fully usable PC like a computer which size fits the typical credit card (85/56 mm) size and small power consumption < 3.5 W. This makes this kind of single board computers one of the most popular in developers community. For today there exist thousands of hardware implementation projects available for users who want to learn the modern hardware and software controlling units within their projects. The general Raspberry Pi features are listed below.
Hardware boards (depending on the manufactured model) contains interfaces: Ethernet, Bluetooth, WiFi, USB, AUDIO, HDMI and GPIO ports [170]. The Raspberry Pi boards have evolved through several versions varying in memory capacity, System on Chips (SoC) and processor units. First generation models of Raspberry Pi used the Broadcom BCM2835 (ARMv6 architecture) based on 700 MHz ARM11176JZF-S processor and VideoCore IV graphics processing Unit (GPU). Models Pi 1 and B+ developed later uses the five-point USB/Ethernet hub chip while the Pi 1 Model B only contains two. On the Pi Zero, the USB port is connected directly to the SoC and uses the (OTG) micro USB port.
The first Raspberry Pi 2 models use the 900 MHz Broadcom BCM2836 SoC 32-bit quad-core ARM Cortex-A7 processor, with shared 256 KB L2 cache. After this earlier models, the Raspberry Pi 2 V1.2 has been upgraded to a Broadcom BCM2837 SoC equipped with a 1.2 GHz 64-bit quad-core ARM Cortex-A53 processor. Latest Raspberry Pi 3 series uses the same SoC. They use the Broadcom BCM2837 SoC with a 1.2 GHz 64-bit quad-core ARM Cortex-A53 processor, equipped with 512 KB shared L2 cache. The Raspberry Pi 3B+ uses the same processor (BCM2837B0) but running at 1.4 GHz. Next Raspberry Pi generations are going to be more and more powerful, but their power consumption is still rising to force developers to use CPU and GPU heatsinks.
Older B board models were designed with 128 MB RAM which was by default allocated between the GPU and CPU. The Model B (including Model A) release the RAM was extended to 256 MB split to there regions. The default split was 192 MB (RAM for CPU), which is sufficient for standalone 1080p video decoding, or for 3D modelling. Models B with 512 MB RAM initially, memory was split to files released (arm256_start.elf, arm384_start.elf, arm496_start.elf) for 256 MB, 384 MB and 496 MB CPU RAM (and 256 MB, 128 MB and 16 MB video RAM). The Raspberry Pi 2 and 3 are shipped with 1 GB of RAM. The Raspberry Pi Zero and Zero W contains 512 MB of RAM.
The Model A, A+ and Pi Zero have no dedicated Ethernet interface and can be connected to a network using an external USB Ethernet or WiFi adapter. In Models B and B+, the Ethernet port is built-in to the USB Ethernet adapter using the SMSC LAN9514 chip. The Raspberry Pi 3 and Pi Zero W (wireless) models are equipped with 2.4 GHz WiFi 802.11n (150 Mbit/s) and Bluetooth 4.1 (24 Mbit/s) based on Broadcom BCM43438 FullMAC chip. The Raspberry Pi 3 also has a 10/100 Ethernet port.
The Raspberry Pi may be controlled with any generic USB computer keyboard and mouse. It can also use USB storage, USB to MIDI converters, and virtually any other device/component which is USB compatible. Other peripherals can be attached through the various pins and connectors on the surface of the Raspberry Pi.
The video controller supports standard modern TV resolutions, such as HD and Full HD, and higher. It can emit 640 × 350 EGA; 640 × 480 VGA; 800 × 600 SVGA; 1024 × 768 XGA; 1280 × 720 720p HDTV; 1280 × 768 WXGA variant; 1280 × 800 WXGA variant; 1280 × 1024 SXGA; 1366 × 768 WXGA variant; 1400 × 1050 SXGA+; 1600 × 1200 UXGA; 1680 × 1050 WXGA+; 1920 × 1080 1080p HDTV; 1920 × 1200 WUXGA. Higher resolutions, such as, up to 2048 × 1152, may work or even 3840 × 2160 at 15 Hz. Although the Raspberry Pi 3 does not include H.265 hardware decoders, the CPU is more powerful than its predecessors, potentially fast enough for software decode H.265-encoded videos. The Raspberry Pi 3 GPU runs at a higher clock frequency – 300 MHz or 400 MHz, compared to 250 MHz previous versions. The Raspberry Pis is capable of generating 576i and 480i composite video signals, as used on old-style (CRT) TV screens and less-expensive monitors through standard connectors – either RCA or 3.5 mm phono connector depending on models. The television signal standards supported are PAL-BGHID, PAL-M, PAL-N, NTSC and NTSC-J.
None of the current Raspberry Pi models is equipped with a built-in real-time clock. Developers which needs the real clock time in their project can retrieve the time from a network time server (NTP) or use the external RTC module connected to the board via SPI or I²C interface. To save the file system consistency of time, the Raspberry Pi automatically saves the time on shutdown, and reload it time at boot. One of the best RTC solutions for keeping the proper boards time is to use the I²C DS1307 chip containing hardware clock with battery power supply.
As for today, on the market there are available few models of Raspberry Pi boards, from tiny ones to more powerful. User can choose the right board to fit the price and functionality to his project development needs. Below figures are listed form the tiny/cheap to most sophisticated Raspberry Pi models.
Each Raspberry Pi model is equipped with standard 34/40-pis male connector containing universal GPIO ports, VCC 3.3/5V, GND, CLK, I2C/SPI buses pins which developers can use to connect their external sensors, switches and other controlled devices to the Raspberry Pi board and then program their behaviour within the code loaded to the board.
Each Raspberry Pi model is equipped with the standard mini HDMI port allows user connect the monitor or TV set with the board.The electronic schematic is shown on the picture.
Raspberry Pi boars Zero, 1, A+, 2, 3 are equipped with Camera interface (CSI) port allowing user connect the CCD camera following the MIPI standard.
Raspberry Pi boars 2, 3 are equipped with LCD Display interface(DSI) port allowing the user to connect the LCD touch display to the board. The official Raspberry Pi LCD touch display shown in the figure below is 800 x 480 dpi 7“ size can be connected to the Raspberry board using the DSI interface. Such an assembly can be used in the projects to display controlling application view and with the ability to handle fingers touchscreen controls the project behaviour. The LCD can be mounted in portrait/landscape orientation fitting the best user needs.
Raspberry PI models boars Zero, 1, A+, 2, 3 contains USB ports (from 1 up to 4) and models boars 1, A+, 2, 3 the LAN port for TCP/IP network connections. This ports can be used for mouse/keyboard connection or if the software has appropriate driver installed to handle other USB devices.
Raspberry Pi boards offer an easy way to connect different sensors and control devices. With specially designed I/O pins available to program them by developers the amount of possible implementations growth year by year. Any I/O General Purpose Input-Output Ports (GPIO) can be set as Digital Input or Output. The board contains two PWM pins which can be used as output analogue signals. Some of the interface libraries, such as pigpio or wiringPi, support this feature. It is also the way the Raspberry Pi outputs analogue audio.
A pushbutton is an electromechanical sensor that connects or disconnects two points in a circuit when the force is applied. Button output discrete value is either HIGH or LOW.
A microswitch, also called a miniature snap-action switch, is an electromechanical sensor that requires a very little physical force and uses tipping-point mechanism. Microswitch has three pins, two of which are connected by default. When the force is applied, the first connection breaks and one of the pins is connected to the third pin.
The most common use of a push button is as an input device. Both force solutions can be used as simple object detectors, or as end switches in the industrial devices.
To proper work with the button, the GPIO4 must be configured as an digital input. Pressing the push button connects the GPIO4 pin to the boards GND. On Raspberry Pi GPIO input pins are normally pulled up to 3.3 V. When the button is pressed, and GPIO4 is read using GPIO.input, it will return the FALSE result. Each GPIO pin can be configured to use internal pull-up or pull-down resistors. Using a GPIO pin as an input, these resistors can be configured using the optional pull_up_down parameter in the GPIO.setup. If this parameter is omitted, resistors will not be activated. In this case, the input may floating giving unpredicted results during reading it. If the GPIO pin is set to GPIO.UD_UP, the pull-up resistor is enabled; if it is set to GPIO.PUD_DOWN, the pull-down resistor is enabled.
An example code:
#Python code for Raspberry Pi import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) s_pin = 7 #Select the GPIO4 pin #Set the GPIO4 port to input mode GPIO.setup(s_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) while True: input_state = GPIO.input(s_pin) if input_state == False: print('Button Pressed') time.sleep(0.2)
Running the code as superuser shows:
pi@raspberrypi ~ $ sudo python switch.py Button Pressed Button Pressed Button Pressed Button Pressed
A force sensor predictably changes resistance, depending on the applied force to its surface. Force-sensing resistors are manufactured in different shapes and sizes, and they can measure not only direct force but also the tension, compression, torsion and other types of mechanical forces. The voltage is measured by applying and measuring constant voltage to the sensor.
Force sensors are used as control buttons or to determine weight.
An example code:
#Python code for Raspberry Pi import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) a_pin = 7 #Select the GPIO4 pin b_pin = 29 #Select the GPIO5 pin def discharge(): GPIO.setup(a_pin, GPIO.IN) GPIO.setup(b_pin, GPIO.OUT) GPIO.output(b_pin, False) time.sleep(0.005) def charge_time(): GPIO.setup(b_pin, GPIO.IN) GPIO.setup(a_pin, GPIO.OUT) count = 0 GPIO.output(a_pin, True) while not GPIO.input(b_pin): count = count + 1 return count def analog_read(): discharge() return charge_time() while True: print(analog_read()) time.sleep(1)
Running the code as superuser shows:
$ sudo python pot_step.py 10 12 10 10 16 23 43 53 67 72 86 105 123 143 170
The idea of how to read the force sensor changing value is called step response. It works by checking how the circuit responds to the step change when an output is switched from low to high. Raspberry Pi isn't equipped with an ADC converter. So it is impossible to read voltage directly. However, it can be measured how long the capacitor will fill with the charge to the extent that it gets voltage above 1.65 V or so that constitutes a high digital input. The speed at which the capacitor fills with charge depends on the value of the variable resistor (Rt). The lower the resistance, the faster the capacitor fills with charge, and the voltage rises. To get the proper value, the circuit must empty the capacitor each time before the reading starts. In the schematic the GPIO4 is used to charge the capacitor and GPIO5 is used to discharge the capacitor through the 10 kΩ resistor. Both resistors are used to make sure that there is no way too much current can flow as the capacitor is charged and discharged. To discharge it, connection GPIO4 is set to be an input, effectively disconnecting Rc and Rt from the circuit. Connection GPIO5 is then set to be an output and low. It is held there for 5 milliseconds, to empty the capacitor.
Capacitive sensors are a range of sensors that use capacitance to measure changes in the surrounding environment. A capacitive sensor consists of a capacitor that is charged with a certain amount of current until the threshold voltage. A human finger, liquids or other conductive or dielectric materials that touch the sensor, can influence a charge time and a voltage level in the sensor. Measuring charge time and a voltage level gives information about changes in the environment.
Capacitive sensors are used as input devices and can measure proximity, humidity, fluid level and other physical parameters or serve as an input for electronic device control.
#Python code for Raspberry Pi import time import pigpio #http://abyz.co.uk/rpi/pigpio/python.html RXD=15 #Define the RxD serial input port pi = pigpio.pi() if not pi.connected: exit(0) pigpio.exceptions = False #Ignore error if already set as bit bang read. pi.bb_serial_read_open(RXD, 9600) #Set baud rate here. pigpio.exceptions = True pi.bb_serial_invert(RXD, 1) #Invert line logic. stop = time.time() + 60.0 while time.time() < stop: (count, data) = pi.bb_serial_read(RXD) if count: print(data) time.sleep(0.2) pi.bb_serial_read_close(RXD) pi.stop()
Ultrasound (ultrasonic) sensor measures the distance to objects by emitting ultrasound and measuring its returning time. The sensor consists of an ultrasonic emitter and receiver; sometimes, they are combined in a single device for emitting and receiving. Ultrasonic sensors can measure greater distances and cost less than infrared sensors, but are more imprecise and interfere which each other measurement if more than one is used. Simple sensors have trigger pin and echo pin, when the trigger pin is set high for the small amount of time ultrasound is emitted and on echo pin, response time is measured. Ultrasonic sensors are used in car parking sensors and robots for proximity detection.
Examples of IoT applications are robotic obstacle detection and room layout scanning.
An example code:
#Python code for Raspberry Pi import RPi.GPIO as GPIO import time TRIG = 7 #Define a trigger pin GPIO4 ECHO = 29 #Define an echo pin GPIO5 print ("Distance Measurement In Progress") GPIO.setup(TRIG, GPIO.OUT) #Set the GPIO4 as trigger output port GPIO.setup(ECHO,GPIO.IN) #Set the GPIO5 pin as echo input GPIO.output (TRIG,False) print ("Waiting for Sensor to Settle") time.sleep(2) GPIO.output (TRIG, True) time.sleep (0.00001) GPIO.output (TRIG, False) while GPIO.input(ECHO) == 0: pulse_start = time.time() while GPIO.input(ECHO) == 1: pulse_end = time.time() pulse_duration = pulse_end - pulse_start distance = pulse_duration*17150 distance = round(distance,2) #Calculating the distance print ("Distance:", distance, "cm")
Running the code as superuser shows:
pi@raspberrypi > $ sudo python range_sensor.py Distance Measurement To Settle Distance: 23.54 cm pi@raspberrypi > $
The motion detector is a sensor that detects moving objects, most people. Motion detectors use different technologies, like passive infrared sensors, microwaves and Doppler effect, video cameras and previously mentioned ultrasonic and IR sensors. Passive IR sensors are the simplest motion detectors that sense people trough detecting IR radiation that is emitted through the skin. When the motion is detected, the output of a motion sensor is a digital HIGH/LOW signal.
Motion sensors are used for security purposes, automated light and door systems. As an example in IoT, the PIR motion sensor can be used to detect motion in security systems a house or any building.
An example code:
#Python code for Raspberry Pi pirPin = 7; //Passive Infrared (PIR) sensor output is connected to the GPIO4 pin GPIO.setup(pirPin ,GPIO.IN) #Set the GPIO5 pin as echo input while 1: #Read the digital value of the PIR motion sensor GPIO4 pirReading = GPIO.input(pirPin) print (piReading) #Print out if pirReading == True: #Motion was detected print ('Motion Detected') time.sleep(10)
A gyroscope is a sensor that measures the angular velocity. The sensor is made of the microelectromechanical system (MEMS) technology and is integrated into the chip. The output of the sensor can be either analogue or digital value of information, using I2C or SPI interface. Gyroscope microchips can vary in the number of axes they can measure. The available number of the axis is 1, 2 or 3 axes in the gyroscope. For gyroscopes with 1 or 2 axes, it is essential to determine which axis the gyroscope measures and to choose a device according to the project needs. A gyroscope is commonly used together with an accelerometer, to determine the orientation, position and velocity of the device precisely. Gyroscope sensors are used in aviation, navigation and motion control.
Gyroscope sensors are used in aviation, navigation and motion control.
The example code for the FXAS21002C sensor used in the breakout board:
#Python code for Raspberry Pi #!/usr/bin/env python from __future__ import division, print_function from nxp_imu import IMU import time imu = IMU(gs=4, dps=2000, verbose=True) header = 67 print('-'*header) print("| {:17} | {:20} | {:20} |".format("Accels [g's]", " Magnet [uT]", "Gyros [dps]")) print('-'*header) for _ in range(10): a, m, g = imu.get() print('| {:>5.2f} {:>5.2f} {:>5.2f} | {:>6.1f} {:>6.1f} {:>6.1f} | {:>6.1f} {:>6.1f} {:>6.1f} |'.format( a[0], a[1], a[2], m[0], m[1], m[2], g[0], g[1], g[2]) ) time.sleep(0.50) print('-'*header) print(' uT: micro Tesla') print(' g: gravity') print('dps: degrees per second') print('')
A compass is a sensor, that can measure the orientation of the device to the magnetic field of the Earth. Solid state compass consists of the magnetometer and accelerometers in a single chip to precisely calculate the position of the device. Devices communicate through I2C or SPI interfaces and can return calculated heading, pitch and roll and raw accelerometer and magnetometer values. Compass is used in outdoor navigation for mobile devices, robots, quadcopters.
The example code:
1. Install i2c: sudo apt-get install i2c-tools 2. edit file /etc/modprobe.d/raspi-blacklist.conf and comment out the line blacklist i2c-bcm2708 3. edit /etc/modules, and add the lines: i2c-bcm2708 i2c-dev 4. Allow i2c access from users other than root, by creating the file /etc/udev/rules.d/99-i2c.rules with this line: SUBSYSTEM=="i2c-dev", MODE="0666" 5. Reboot the Pi. When it goes up again, type: ls /dev/i2c* On Pi (Model B, Revision 2 version, early 2013) it generates: /dev/i2c-0 /dev/i2c-1 Optional: For python, install the smbus python library with: 1. apt-get install python-smbus 2. Install Python 3, can’t hurt, and i2clibraries needs it. Just type sudo apt-get install python3 3. Test if the compass is detected, by typing: i2cdetect -y 1 (for Revision 1 Pis, replace 1 with 0). 4. Replace with 0 for Revision 1 Raspberry Pis and with 1 for Revision 2 boards. This is the output: 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- Tip: if you don’t see it, it’s because you haven’t welded the pins to the sensor. Just press with your finger. Or weld it. 1. Add the quick2wire code. Pull from git: git clone https://github.com/quick2wire/quick2wire-python-api.git. On /etc/profile, add: export QUICK2WIRE_API_HOME=/home/pi/quick2wire-python-api 2. export PYTHONPATH=$PYTHONPATH:$QUICK2WIRE_API_HOME Add i2clibraries. Pull from git:git clone https://bitbucket.org/thinkbowl/i2clibraries.git
A temperature sensor is a device that is used to determine the temperature of the surrounding environment. Most temperature sensors work on the principle that the resistance of the material is changed depending on its temperature. The most common temperature sensors are:
The main difference between sensors is the measured temperature range, precision and response time. Temperature sensor usually outputs the analogue value, but some existing sensors have a digital interface [189].
The temperature sensors most commonly are used in environmental monitoring devices and thermoelectric switches. In IoT applications, the sensor can be used for greenhouse temperature monitoring, warehouse temperature monitoring to avoid frozen fire suppression systems and tracking temperature of the soil, water and plants.
An example code is similar to the Raspberry Pi force sensor sample. The thermistor changes its resistance depends on the environment temperature, and it can be read using similar code:
#Python code for Raspberry Pi import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) a_pin = 7 #Select the GPIO4 pin b_pin = 29 #Select the GPIO5 pin def discharge(): GPIO.setup(a_pin, GPIO.IN) GPIO.setup(b_pin, GPIO.OUT) GPIO.output(b_pin, False) time.sleep(0.005) def charge_time(): GPIO.setup(b_pin, GPIO.IN) GPIO.setup(a_pin, GPIO.OUT) count = 0 GPIO.output(a_pin, True) while not GPIO.input(b_pin): count = count + 1 return count def analog_read(): discharge() return charge_time() while True: print(analog_read()) time.sleep(1)
A humidity sensor (hygrometer) is a sensor that detects the amount of water or water vapour in the environment. The most common principle of the air humidity sensors is the change of capacitance or resistance of materials that absorb the moisture from the environment. Soil humidity sensors measure the resistance between the two electrodes. The resistance between electrodes is influenced by soluble salts and water amount in the soil. The output of a humidity sensor is usually an analogue signal value [191].
Example IoT applications are monitoring of humidor, greenhouse temperature and humidity, agricultural environment and art gallery and museum environment.
An example code [193]:
1. Enter this at the command prompt to download the library: git clone https://github.com/adafruit/Adafruit_Python_DHT.git 2. Change directories with: cd Adafruit_Python_DHT 3. Now enter this: sudo apt-get install build-essential python-dev 4. Then install the library with: sudo python setup.py install
#Python code for Raspberry Pi #!/usr/bin/python import sys import Adafruit_DHT while True: humidity, temperature = Adafruit_DHT.read_retry(11, 7) #Read GPIO4 Pin 7 print ('Temp: {0:0.1f} C Humidity: {1:0.1f} %'.format(temperature, humidity))
A sound sensor is a sensor that detects vibrations in a gas, liquid or solid environments. At first, the sound wave pressure makes mechanical vibrations, who transfers to changes in capacitance, electromagnetic induction, light modulation or piezoelectric generation to create an electric signal. The electrical signal is then amplified to the required output levels. Sound sensors, can be used to record sound, detect noise and its level.
Sound sensors are used in drone detection, gunshot alert, seismic detection and vault safety alarm.
An example code:
#Python code for Raspberry Pi import time import RPi.GPIO as GPIO from qhue import Bridge GPIO.setmode(GPIO.BCM) #Use board pin numbers pin = 7 #Define GPIO4 as Input GPIO.setup(pin, GPIO.IN) def callback (pin) if GPIO.input (pin) print ("Sound detected!") else: print ("Sound detected!") #Activate when pin changed its state GPIO.add_event_detect(pin,GPIO_BOTH, bouncetime=300) #Assign function to GPIO PIN run it on changes GPIO.add_event_callback(pin,callback) #Infinite loop while True: time.sleep(1)
Gas sensors are a sensor group, that can detect and measure a concentration of certain gasses in the air. The working principle of electrochemical sensors is to absorb the gas and to create current from an electrochemical reaction. For process acceleration, a heating element can be used. For each type of gas, different kind of sensor needs to be used. Multiple different types of gas sensors can be combined in a single device as well. The single gas sensor output is an analogue signal, but devices with multiple sensors used to have a digital interface.
Gas sensors are used for safety devices, to control air quality and for manufacturing equipment. Examples of IoT applications are air quality control management in smart buildings and smart cities or toxic gas detection in sewers and underground mines.
An example code:
#Python code for Raspberry Pi 1. git clone https://github.com/tutRPi/Raspberry-Pi-Gas-Sensor-MQ 2. cd Raspberry-Pi-Gas-Sensor-MQ 3. sudo python example.py
#Python code for Raspberry Pi #!/usr/bin/env python import PCF8591 as ADC import RPi.GPIO as GPIO import time import math DO = 17 Buzz = 18 GPIO.setmode(GPIO.BCM) def setup(): ADC.setup(0x48) GPIO.setup (DO, GPIO.IN) GPIO.setup (Buzz, GPIO.OUT) GPIO.output (Buzz, 1) def Print(x): if x == 1: print ('') print (' *********') print (' * Safe~ *') print (' *********') print ('') if x == 0: print ('') print (' ***************') print (' * Danger Gas! *') print (' ***************') print ('') def loop(): status = 1 count = 0 while True: print (ADC.read(0)) tmp = GPIO.input(DO); if tmp != status: print(tmp) status = tmp if status == 0: count += 1 if count % 2 == 0: GPIO.output(Buzz, 1) else: GPIO.output(Buzz, 0) else: GPIO.output(Buzz, 1) count = 0 time.sleep(0.2) def destroy(): GPIO.output(Buzz, 1) GPIO.cleanup() if __name__ == '__main__': try: setup() loop() except KeyboardInterrupt: destroy()
A GPS receiver is a device, that can receive information from a global navigation satellite system and calculate its position on the Earth. GPS receiver uses a constellation of satellites and ground stations to compute position and time almost anywhere on the Earth. GPS receivers are used for navigation only in the outdoor area because it needs to receive signals from the satellites. The precision of the GPS location can vary.
A GPS receiver is used for device location tracking. Real world applications might be pet, kid or personal belonging location tracking.
The example code:
#Python code for Raspberry Pi #!/usr/bin/python import os import pygame, sys from pygame.locals import * import serial #Initialise serial port on /ttyUSB0 ser = serial.Serial('/dev/ttyUSB0',4800,timeout = None) #Set font size MAX 100 fontsize = 50 #Calculate window size width = fontsize * 17 height = fontsize + 10 #Initilaise pygame pygame.init() windowSurfaceObj = pygame.display.set_mode((width,height),1,16) fontObj = pygame.font.Font('freesansbold.ttf',fontsize) pygame.display.set_caption('GPS Location') redColor = pygame.Color(255,0,0) greenColor = pygame.Color(0,255,0) yellowColor = pygame.Color(255,255,0) blackColor = pygame.Color(0,0,0) fix = 1 color = redColor x = 0 while x == 0: gps = ser.readline() #Print (all NMEA strings) print (gps) #Check gps fix status if gps[1:6] == "GPGSA": fix = int(gps[9:10]) if fix == 2: color = yellowColor if fix == 3: color = greenColor #Print (time, lat and long from #GPGGA string) if gps[1 : 6] == "GPGGA": #Clear window pygame.draw.rect(windowSurfaceObj,blackColor,Rect(0,0,width,height)) pygame.display.update(pygame.Rect(0,0,width,height)) #Get time time = gps[7:9] + ":" + gps[9:11] + ":" + gps[11:13] #If 2 or 3D fix get lat and long if fix > 1: lat = " " + gps[18:20] + "." + gps[20:22] + "." + gps[23:27] + gps[28:29] lon = " " + gps[30:33] + "." + gps[33:35] + "." + gps[36:40] + gps[41:42] #If no fix else: lat = " No Valid Data " lon = " " #Print new values msgSurfaceObj = fontObj.render(str(time), False,color) msgRectobj = msgSurfaceObj.get_rect() msgRectobj.topleft =(2,0) windowSurfaceObj.blit(msgSurfaceObj, msgRectobj) msgSurfaceObj = fontObj.render(str(lat), False,color) msgRectobj = msgSurfaceObj.get_rect() msgRectobj.topleft =(210,0) windowSurfaceObj.blit(msgSurfaceObj, msgRectobj) msgSurfaceObj = fontObj.render(str(lon), False,color) msgRectobj = msgSurfaceObj.get_rect() msgRectobj.topleft =(495,0) windowSurfaceObj.blit(msgSurfaceObj, msgRectobj) pygame.display.update(pygame.Rect(0,0,width,height)) fix = 1 color = redColor #Check for ESC key pressed, or GPS Location window closed, to quit for event in pygame.event.get(): if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): pygame.quit() sys.exit() }
The light-emitting diode also called LED is a special type of diodes which emits light, unlike the other diodes. LED has a completely different body which is made of transparent plastic that protects the diode and lets it emit light. Like the other diodes LED conducts the current in only one way, so it is essential to connect it to the scheme correctly. There are two safe ways how to determine the direction of the diode:
The LED is one of the best light sources. Unlike incandescent light bulb LED transforms most of the power into light, not warmth; it is more durable, works for a more extended period and can be manufactured in a smaller size.
The LED colour is determined by the semiconductors material. Diodes are usually made from silicon then LEDs are made from elements like gallium phosphate, silicon carbide and others. Because the semiconductors used are different, the voltage needed for the LED to shine is also different. In the table, you can see with which semiconductor you can get a specific colour and the voltage required to turn on the LED.
When LED is connected to the voltage and turned on a huge current starts to flow through it, and it can damage the diode. That is why all LEDs have to be connected to current limiting resistor.
Current limiting resistors resistance is determined by three parameters:
To calculate the resistance needed for a diode, this is what you have to do.
The example of the blinking LED code:
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time LED = 18 #GPIO04 port GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(LED,GPIO.OUT) print ("LED on") GPIO.output(LED,GPIO.HIGH) time.sleep(1) print ("LED off") GPIO.output(LED,GPIO.LOW)
Using display is a quick way to get a feedback information from the device. There are many display technologies compatible with Arduino. For IoT solutions low power, easy to use and monochrome displays are used:
LCD uses modulating properties of liquid crystal light to block the incoming light. Thus when a voltage is applied to a pixel, it has a dark colour. A display consists of layers of electrodes, polarising filters, liquid crystals and reflector or back-light. Liquid crystals do not emit the light directly; they do it through reflection or backlight. Because of this reason, they are more energy efficient. Small, monochrome LCDs are widely used in devices to show a little numerical or textual information like temperature, time, device status etc. LCD modules commonly come with an onboard control circuit and are controlled through parallel or serial interface.
The example code:
#Raspberry Pi Python sample code #!/usr/bin/python #Example using a character LCD connected to a Raspberry Pi import time import Adafruit_CharLCD as LCD #Raspberry Pi pin setup lcd_rs = 25 lcd_en = 24 lcd_d4 = 23 lcd_d5 = 17 lcd_d6 = 18 lcd_d7 = 22 lcd_backlight = 2 #Define LCD column and row size for 16x2 LCD. lcd_columns = 16 lcd_rows = 2 lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns, lcd_rows, lcd_backlight) lcd.message('Hello\nworld!') #Wait 5 seconds time.sleep(5.0) lcd.clear() text = raw_input("Type Something to be displayed: ") lcd.message(text) #Wait 5 seconds time.sleep(5.0) lcd.clear() lcd.message('Goodbye\nWorld!') time.sleep(5.0) lcd.clear()
OLED display uses electroluminescent materials that emit light when the current passes through these materials. The display consists of two electrodes and a layer of an organic compound. OLED displays are thinner than LCDs, they have higher contrast, and they can be more energy efficient depending on usage. OLED displays are commonly used in mobile devices like smartwatches, cell phones and they are replacing LCDs in other devices. Small OLED display modules usually have an onboard control circuit that uses digital interfaces like I2C or SPI.
1. git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git 2. cd Adafruit_Python_SSD1306 For Python 2: 3. sudo python setup.py install For Python3: 4. sudo python3 setup.py install
cd examples Choose one of existing examples: - animate.py - buttons.py - image.py - shapes.py - stats.py
E-ink display uses charged particles to create a paper-like effect. The display consists of transparent microcapsules filled with oppositely charged white and black particles between electrodes. Charged particles change their location, depending on the orientation of the electric field, thus individual pixels can be either black or white. The image does not need the power to persist on the screen; power is used only when the image is changed. Thus e-ink display is very energy efficient. It has high contrast and viewing angle, but it has a low refresh rate. E-ink displays are commonly used in e-riders, smartwatches, outdoor signs, electronic shelf labels.
#Raspberry Pi Python sample code #From https://www.instructables.com/id/Waveshare-EPaper-and-a-RaspberryPi import time, datetime, sys, signal, urllib, requests from EPD_driver import EPD_driver def handler(signum, frame): print ('SIGTERM') sys.exit(0) signal.signal(signal.SIGTERM, handler) bus = 0 device = 0 disp = EPD_driver(spi = SPI.SpiDev(bus, device)) print ("disp size : %dx%d"%(disp.xDot, disp.yDot)) print ('------------init and Clear full screen------------') disp.Dis_Clear_full() disp.delay() #Display part disp.EPD_init_Part() disp.delay() imagenames = [] search = "http://api.duckduckgo.com/?q=Cat&format=json&pretty=1" if search: req = requests.get(search) if req.status_code == 200: for topic in req.json()["RelatedTopics"]: if "Topics" in topic: for topic2 in topic["Topics"]: try: url = topic2["Icon"]["URL"] text = topic2["Text"] if url: imagenames.append( (url,text) ) except: #Print topic pass try: url = topic["Icon"]["URL"] if url: imagenames.append( url ) except: #Print topic pass else: print (req.status_code) #Font for drawing within PIL myfont10 = ImageFont.truetype("amiga_forever/amiga4ever.ttf", 8) myfont28 = ImageFont.truetype("amiga_forever/amiga4ever.ttf", 28) #Mainimg is used as screen buffer, all image composing/drawing is done in PIL, #The mainimg is then copied to the display (drawing on the disp itself is no fun) mainimg = Image.new("1", (296,128)) name = ("images/downloaded.png", "bla") skip = 0 while 1: for name2 in imagenames: print ('---------------------') skip = (skip+1)%7 try: starttime = time.time() if skip==0 and name2[0].startswith("http"): name = name2 urllib.urlretrieve(name[0], "images/downloaded.png") name = ("images/downloaded.png", name2[1]) im = Image.open(name[0]) print (name, im.format, im.size, im.mode) im.thumbnail((296,128)) im = im.convert("1") #, dither=Image.NONE) #Print ('thumbnail', im.format, im.size, im.mode) loadtime = time.time() print ('t:load+resize:', (loadtime - starttime)) draw = ImageDraw.Draw(mainimg) #Clear draw.rectangle([0,0,296,128], fill=255) #Copy to mainimg ypos = (disp.xDot - im.size[1])/2 xpos = (disp.yDot - im.size[0])/2 print ('ypos:', ypos, 'xpos:', xpos) mainimg.paste(im, (xpos,ypos)) #Draw info text ts = draw.textsize(name[1], font=myfont10) tsy = ts[1]+1 oldy = -1 divs = ts[0]/250 for y in range(0, divs): newtext = name[1][(oldy+1)*len(name[1])/divs:(y+1)*len(name[1])/divs] #Print (divs, oldy, y, newtext) oldy = y draw.text((1, 1+y*tsy), newtext, fill=255, font=myfont10) draw.text((1, 3+y*tsy), newtext, fill=255, font=myfont10) draw.text((3, 3+y*tsy), newtext, fill=255, font=myfont10) draw.text((3, 1+y*tsy), newtext, fill=255, font=myfont10) draw.text((2, 2+y*tsy), newtext, fill=0, font=myfont10) #Draw time now = datetime.datetime.now() tstr = "%02d:%02d:%02d"%(now.hour,now.minute,now.second) #Draw a shadow, time tpx = 36 tpy = 96 for i in range(tpy-4, tpy+32, 2): draw.line([0, i, 295, i], fill=255) draw.text((tpx-1, tpy ), tstr, fill=0, font=myfont28) draw.text((tpx-1, tpy-1), tstr, fill=0, font=myfont28) draw.text((tpx , tpy-1), tstr, fill=0, font=myfont28) draw.text((tpx+2, tpy ), tstr, fill=0, font=myfont28) draw.text((tpx+2, tpy+2), tstr, fill=0, font=myfont28) draw.text((tpx , tpy+2), tstr, fill=0, font=myfont28) draw.text((tpx , tpy ), tstr, fill=255, font=myfont28) del draw im = mainimg.transpose(Image.ROTATE_90) drawtime = time.time() print ('t:draw:', (drawtime - loadtime)) listim = list(im.getdata()) #Print (im.format, im.size, im.mode, len(listim)) listim2 = [] for y in range(0, im.size[1]): for x in range(0, im.size[0]/8): val = 0 for x8 in range(0, 8): if listim[(im.size[1]-y-1)*im.size[0] + x*8 + (7-x8)] > 128: #Print (x,y,x8,'ON') val = val | 0x01 << x8 else: #Print (x,y,x8,'OFF') pass #Print val listim2.append(val) for x in range(0,1000): listim2.append(0) #Print len(listim2) convtime = time.time() print ('t:conv:', (convtime - loadtime)) ypos = 0 xpos = 0 disp.EPD_Dis_Part(xpos, xpos+im.size[0]-1, ypos, ypos+im.size[1]-1, listim2) #xStart, xEnd, yStart, yEnd, DisBuffer #disp.delay() uploadtime = time.time() print ('t:upload:', (uploadtime - loadtime)) except IOError as ex: print ('IOError', str(ex))
Relays are electromechanical devices that use electromagnets to connect or disconnect plates of a switch. Relays are used to control high power circuits with low power circuits. Circuits are mechanically isolated and thus protect logic control. Relays are used in household appliance automation, lighting and climate control.
The example code:
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time REL = 18 #GPIO04 port GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(REL,GPIO.OUT) print ("REL on") GPIO.output(REL,GPIO.HIGH) time.sleep(1) print ("REL off") GPIO.output(REL,GPIO.LOW)
Solenoids are devices that use electromagnets to pull or push iron or steel core. They are used as linear actuators for locking mechanisms indoors, pneumatic and hydraulic valves and in-car starter systems.
Solenoids and relays both use electromagnets and connecting them to Arduino is very similar. Coils need a lot of power, and they are usually attached to the power source of the circuit. Turning the power of the coil off makes the electromagnetic field to collapse and creates very high voltage. For the semiconductor devices protection, a shunt diode is used to channel the overvoltage. For extra safety, optoisolator can be used.
The example code:
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time SOL = 18 #GPIO04 port GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(SOL,GPIO.OUT) print ("SOL on") GPIO.output(SOL,GPIO.HIGH) time.sleep(1) print ("SOL off") GPIO.output(SOL,GPIO.LOW)
An electric motor is an electro-technical device which can turn electrical energy into mechanical energy; motor turns because of the electricity that flows in its winding. Electric motors have seen many technical solutions over the year from which the simplest is the permanent-magnet DC motor.
DC motor is a device which converts direct current into the mechanical rotation. DC motor consists of permanent magnets in stator and coils in the rotor. By applying the current to coils, the electromagnetic field is created, and the rotor tries to align itself to the magnetic field. Each coil is connected to a commutator, which in turns supplies coils with current, thus ensuring continuous rotation. DC motors are widely used in power tools, toys, electric cars, robots, etc.
#Raspberry Pi Python sample code import RPi.GPIO as GPIO import time DCM = 18 #GPIO04 port GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(DCM,GPIO.OUT) print ("DCM on") GPIO.output(DCM,GPIO.HIGH) time.sleep(1) print ("DCM off") GPIO.output(DCM,GPIO.LOW)
Stepper motors are motors, that can be moved by a certain angle or step. Full rotation of the motor is divided into small, equal steps. Stepper motor has many individually controlled electromagnets, by turning them on or off, the motor shaft rotates by one step. Changing switching speed or direction can precisely control turn angle, direction or full rotation speed. Because of very precise control ability they are used in CNC machines, 3D printers, scanners, hard drives etc. Example of use can be found in the source [204].
The example code:
#Raspberry Pi Python sample code #From https://www.rototron.info/raspberry-pi-stepper-motor-tutorial/ from time import sleep import RPi.GPIO as GPIO DIR = 20 #Direction GPIO Pin STEP = 21 #Step GPIO Pin CW = 1 #Clockwise Rotation CCW = 0 #Counterclockwise Rotation SPR = 48 #Steps per Revolution (360/7.5) GPIO.setmode(GPIO.BCM) GPIO.setup(DIR, GPIO.OUT) GPIO.setup(STEP, GPIO.OUT) GPIO.output(DIR, CW) step_count = SPR delay = .0208 for x in range(step_count): GPIO.output(STEP, GPIO.HIGH) sleep(delay) GPIO.output(STEP, GPIO.LOW) sleep(delay) sleep(.5) GPIO.output(DIR, CCW) for x in range(step_count): GPIO.output(STEP, GPIO.HIGH) sleep(delay) GPIO.output(STEP, GPIO.LOW) sleep(delay) GPIO.cleanup()
This code may result in motor vibration and jerky motion especially at low speeds. One way to counter these result is with microstepping. Adding the code above avoid it:
MODE = (14, 15, 18) #Microstep Resolution GPIO Pins GPIO.setup(MODE, GPIO.OUT) RESOLUTION = {'Full': (0, 0, 0), 'Half': (1, 0, 0), '1/4': (0, 1, 0), '1/8': (1, 1, 0), '1/16': (0, 0, 1), '1/32': (1, 0, 1)} GPIO.output(MODE, RESOLUTION['1/32']) step_count = SPR * 32 delay = .0208 / 32
Raspberry Pi all models are based on ARM processors which are typically quad-core Cortex-A7 CPUs. This means that most of popular multitasking OS systems can be uploaded and used to create and develop user software operations. The list of supported OS systems contains Linux, Windows and thirty part OS systems. The following list of figures of OS are specially designed for Raspberry Pi boards:
Before start installing the OS system on the Raspberry Pi board developer must prepare his hardware for it [214]. It means that the minimum hardware equipment is needed:
Connecting all establishes the minimum PC desktop kit which will allow to install and run the selected OS system on the SD card.
There are few ways to get the right OS system for Raspberry Pi board:
For other then Windows and NOOBS systems use the Etcher SD [220] card image utility which is designed to format and upload to the SD card different operating systems images. Then follow its instructions.
To install the OS system on SD card the best way is to use specially designed software which will provide SD card formatting tool.
Download and install the SD Formatter [221] tool. Run the SD Formatter tool.
Insert the SD card into the computer SD card reader.
Run the SD Formatter, select the drive letter for the SD card and format it.
Simply drag and drop the extracted NOOBS OS image files from unzipped NOOBS folder onto the SD card drive. The necessary files will be transferred to the SD card.
Gently remove SD card from the reader and push it into the Raspberry Pi SD card slot.
Power on the Raspberry Pi board and follow its instructions.
After the board reboot the Raspbian screen displays.
Python belongs to the high-level programming languages class which was first time developed by Guido van Rossum in 1991. The Python is similar to C++, C# or Java programming languages. It is very useable with a clean syntax and easy to learn even for programming beginners.
Raspberry Raspbian OS is shipped with pre installed two versions of Python language: Python2 and Python3 which are available from the Raspbian Menu.
Choosing the Python version from the menu, the command window with cursor opens.
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 20 2018, 15:12:02) [MSC v.1900 64 bit (AMD64)] on Win32 Type "copyright", "credits" or "license()" for more information. >>>
To test the simply program ”Hello World!“ User just can entry:
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 20 2018, 15:12:02) [MSC v.1900 64 bit (AMD64)] on Win32 Type "copyright", "credits" or "license()" for more information. >>>printf("Hello World") Hello World! >>>
Writing the same code in C language will look following:
#include <stdio.h> int main() { printf ("Hello World!"); return 0; }
Python can automate tasks using the batch commands renaming and move large amounts of files like shell scripts. It can be used as a command line with IDLE, Python’s REPL (read, eval, print, loop) functions. However, there are more useful tasks which can be done with Python. For example, Python can be used to program things like:
Because Python stays very popular, it has a large collection of libraries, which speeds up the development process. There exist libraries for – game programming, rendering graphics, GUI interfaces, web frameworks, and scientific computing.
Many of the programmings stuff in C language can also be programmed in Python. Python is generally slower at computations regarding the C compiler, but its ease for use, which makes Python a very popular tool for prototyping programs and designing applications which are not computationally intensive. One of the best Python tutorials can be found in the book: “Learning Python, 5th Edition by Mark Lutz”.
Python 2 and Python 3 come pre-installed on Raspbian OS systems, but if necessary to install Python on another Linux OS or to update it, the simple commands can be executed at the command prompt:
sudo apt-get install python3
Installs or updates Python 3.
sudo apt-get install python
Installs or updates Python 2.
To access the Python REPL (where the user can type Python commands just like the command line) the user can enter python or python3 commands depending on which version of Raspbian to use in the command prompt.
Pressing (CTRL-D) exits the REPL.
To run the program without making it executable, the user must navigate to the location where the file exists and enter the command:
python hello-world.py
Making a Python program executable allows to run the program without entering python before the file name. User can make a file executable executing the following commands in the command prompt:
chmod +x file-name.py
Now to run the program:
./file-name.py
[pczekalski]Add a simple app in Python. Check if Micropython has printing support over serial for tracing
Python aims to be consistent and straightforward in the design of its syntax. The best advantage of this language is that it can dynamically set the variable types depending on values types which are set for variables.
Python has a wide range of data types, like many simple programming languages:
Standard Python methods are used to create the numbers:
var = 1234 #Creates Integer number assignment var = 'George' #Creates String type var
Python can automatically convert types of the number from one type to another. Type can be also defined explicitly.
int a = 10 long a = 123L float a = 12.34 complex a = 3.23J <code> ==String== To define Strings use eclosing characters in quotes. Python uses single quotes ', double " and triple """ to denote strings. <code Python> Name = "George' lastName = "Smith" message = """this is the string message which is spanning across multiple lines."""
List contains a series of values. To declare list variables uses brackets [].
A = [] #Blank list variable B = [1, 2, 3] #List with 3 numbers C = [1, 'aa', 3] #List with different types
List are zero-based indexed. Data can be assigned to a specific element of the list using an index into the list.
mylist[0] = 'sasa' mylist[1] = 'wawa' print mylist[1]
List aren't limited to a single dimension.
myTable = [[],[]]
In two-dimensional array the first number is always the rows number, when the second is the columns number.
Python Tuples are defined as a group of values like a list and can be processed in similar ways. When assigned Tuples got the fixed size. In Python, the fixed size is immutable. The lists are dynamic and mutable. To define Tuples, parenthesis () must be used.
TestSet = ('Piotr', 'Jan', 'Adam')
To define the Dictionaries in the Python the lists of key–value pairs are used. This datatype is used to hold related information that can be associated through Keys. The Dictionary is used to extract a value based on the key name. Lists use the index numbers to access its members when dictionaries use a key. Dictionaries generally are used to sort, iterate and compare data.
To define the Dictionaries the braces ({}) are used with pairs separated by a comma (,) and the key values associated with a colon (:). Dictionaries Keys must be unique.
box_nbr = {'Alan': 111, 'John': 222} box_nbr['Alan'] = 222 #Set the associated 'Alan' key to value 222' print (box nbr['John']) #Print the 'John' key value box_nbr['Dave'] = 111 #Add a new key 'Dave' with value 111 print (box_nbr.keys()) #Print the keys list in the dictionary print ('John' in box_nbr) #Check if 'John' is in the dictionary #This returns true
All variables in Python hold references to objects, and are passed to functions. Function can't change the value of variable references in its body. The object's value may be changed in the called function with the “alias”.
>>> alist = ['a', 'b', 'c'] >>> def myfunc(al): al.append('x') print al >>> myfunc(alist) ['a', 'b', 'c', 'x'] >>> alist ['a', 'b', 'c', 'x']
If an expression returns TRUE statements are carried out. Otherwise they aren't.
if expression:
statements
Sample:
no = 11 if no >10: print ("Greater than 10") if no <=30 printf ("Between 10 and 30")
Output:
>>> Greater than 10 Between 10 and 30 >>>
An else statement follows an if statement and contains code that is called when the if statement is FALSE.
x = 2 if x == 6 printf ("Yes") else: printf ("No")
The elif (shortcut of else if) statement is used when changing if and else statements. A series of if…elif statements can have a final else block, which is called if none of the if or elif expression is TRUE.
num = 12 if num == 5: printf ("Number = 5") elif num == 4: printf ("Number = 4") elif num == 3: printf ("Number = 3") else: printf ("Number = 12")
Output:
>>> Number = 12 >>>
Python uses logic operators like AND, OR and NOT.
The AND operator uses two arguments, and evaluates to TRUE if, and only if, both of the arguments are TRUE. Otherwise, it evaluates to FALSE.
>>> 1 == 1 and 2 == 2 True >>> 1 == 1 and 2 == 3 False >>> 1 != 1 and 2 == 2 False >>> 4 < 2 and 2 > 6 False >>>
Boolean operator or uses two arguments, and evaluates as TRUE if either (or both) of its arguments are TRUE, and FALSE if both arguments are FALSE.
The result of NOT TRUE is FALSE, and NOT FALSE goes to TRUE.
>>> not 2 == 2 False >>> not 6 > 10 True >>>
Operator Precedence uses mathematical idea of operation order, e.g. multiplication begin performed before addition.
>>> False == False or True True >>> False == (False or True) False >>> (False == False) or True >>>True >>>
An if statement is run once if its condition evaluates to TRUE, and never if it evaluates to FALSE.
A while statement is similar, except that it can be run more than once. The statements inside it are repeatedly executed, as long as the condition holds. Once it evaluates to FALSE, the next section of code is executed.
i = 1 while i<=4: print (i) i+=1 print ('End')
Output:
>>> 1 2 3 4 End >>>
The infinite loop is a particular kind of the while loop, it never stops running. Its condition always remains TRUE.
while 1 == 1: print ('in the loop')
To end the while loop prematurely, the break statement can be used. When encountered inside a loop, the break statement causes the loop to finish immediately.
i = 0 while 1==1: print (i) i += 1 if i >=3: print('breaking') break; print ('finished')
Output:
>>> 0 1 2 3 breaking finished >>>
Another statement that can be used within loops is continue.
Unlike break, continue jumps back to the top of the loop, rather than stopping it.
i = 0 while True: i+=1 if i == 2: printf ('skipping 2') continue if i == 5: print ('breaking') break print (i) print ('finished')
Output:
>>> 1 skipping 2 3 4 breaking finished >>>
n = 9 for i in range (1,5): ml = n * i print ("{} * {} = {}".format (n, i, ml))
Output:
>>> 9 * 1 = 9 9 * 2 = 18 9 * 3 = 27 9 * 4 = 36 >>>
One of the most important in mathematics concept is to use functions. Functions in computer languages implement mathematical functions. The executing function produces one or more results, which are dependent by the parameters passed to it.
In general, a function is a structuring element in the programming language which groups a set of statements so they can be called more than once in a program. Programming without functions will need to reuse code by copying it and changing its different context. Using functions enhances the comprehensibility and quality of the program. It also lowers the memory usage, development cost and maintenance of the software.
Different naming is used for functions in programming languages, e.g. as subroutines, procedures or methods.
Python language defines function by a def statement. The function syntax looks:
def function-name(Parameter list): statements, i.e. the function body
Function bodie can contain one or more return statement. It can be situated anywhere in the function body. A return statement ends the function execution and returns the result, i.e. to the caller. If the return statement does not contain expression, the value None is returned.
def Fahrenheit(T_in_celsius): """ returns the temperature in degrees Fahrenheit """ return (T_in_celsius * 9 / 5) + 32 for t in (22.6, 25.8, 27.3, 29.8): print(t, ": ", fahrenheit(t))
Output:
>>> 22.6 : 72.68 25.8 : 78.44 27.3 : 81.14 29.8 : 85.64 >>>
Functions can be called with optional parameters, also named default parameters. If function is called without parameters the default values are used. The following code greets a person. If no person name is defined, it greets everybody:
def Hello(name="everybody"): """ Say hello to the person """ print("Hello " + name + "!") Hello("George") Hello()
Output:
>>> Hello George! Hello everybody! >>>
The string is usually the first statement in the function body, which can be accessed with function_name.doc. This is Docstring statement.
def Hello(name="everybody"): """ Say hello """ print("Hello " + name + "!") print("The docstring of the function Hello: " + Hello.__doc__)
Output:
>>> The function Hello docstring: Say hello >>>
The alternative way to make function calls is to use keyword parameters. The function definition stay unchanged.
def sumsub(a, b, c=0, d=0): return a - b + c - d print(sumsub(12,4)) print(sumsub(42,15,d=10))
Only keyword parameters are valid, which are not used as positional arguments. If keyword parameters don't exist, the next call to the function will need all four arguments, even if the c needs just the default value:
print(sumsub(42,15,0,10))
In above examples, the return statement exist in sumsub but not in Hello function. The return statement is not mandatory. If explicitly return statement doesn't exist in the sample code it will not show any result:
def no_return(x,y): c = x + y res = no_return(4,5) print(res)
Any result will not be displayed in:
>>>
Executing this script, the None will be printed. If a function doesn't contain expression the None will also be returned:
def empty_return(x,y): c = x + y return res = empty_return(4,5) print(res)
Otherwise the expression value following return will be returned. In this example 11 will be printed:
def return_sum(x,y): c = x + y return c res = return_sum(6,5) print(res)
Output:
>>> 9 >>>
Any function can return only one object. An object can be a numerical value – integer, float, list or a dictionary. To return i.e. three integer values, we can return a list or a tuple with these three integer values. It means that function can indirectly return multiple values. This following example calculates the Fibonacci boundary for a positive number, returns a 2-tuple. The Largest Fibonacci Number smaller than x is the first and the Smallest Fibonacci Number larger than x is next. The return value is stored via unpacking into the variables lub and sup:
def fib_intervall(x): """ returns the largest Fibonacci number, smaller than x and the lowest Fibonacci number, higher than x""" if x < 0: return -1 (old, new, lub) = (0,1,0) while True: if new < x: lub = new (old,new) = (new,old+new) else: return (lub, new) while True: x = int(input("Your number: ")) if x <= 0: break (lub, sup) = fib_intervall(x) print("Largest Fibonacci Number < than x: " + str(lub)) print("Smallest Fibonacci Number > than x: " + str(sup))
Microsoft Windows 10 IOT OS system is available for download from Windows Insider Preview Downloads page [222].
Register into the Microsoft Insider Program. To download images of the Microsoft IOT Core user must be logged into Microsoft Insider Program web page.
On the download page, User must choose which OS edition he wants to use in his project ieg. Windows 10 IOT Core Insider preview – build 17083 or 17035. The core numbers are changing depending on the latest developer editions available in the Microsoft repository. Microsoft IOT development policy is straightly tied with the latest Visual Studio environment. To fully use its power, users are asked to use the latest Visual Studio and Windows 10 IOT core builds in the development process synchronously. The Windows 10 IOT Core platform is still under development and improvement.
Install the right Windows10_InsiderPreview_IoTCore_RPi_ARM32_en-us_17035.iso image on your Windows 10 PC. This package installs the Windows IOT Core Image Helper application and stores the latest Raspberry Pi Windows 10 core image flash.ffu file into the C:\Program Files (x86)\Microsoft IoT\ directory.
Insert the SD card to your computer SD cards reader slot.
Run the Windows IOT Core Image Helper and follow its instructions – select the SD card drive letter, select the right FFU image in the C:\Program Files (x86)\Microsoft IoT\FFU\RaspberryPi2 folder.
Start formatting the SD card and install the FFU image on it.
Gently remove SD card from the reader and push it into the Raspberry Pi SD card slot.
Power on the Raspberry Pi board and follow the Windows 10 Core setup commands configuring the Windows 10 Core features.
After the board reboot the Main Windows 10 Core screen displays:
This chapter describes the typical programming technics used in Raspberry Pi boards developing projects.
To create and develop control applications on the Raspberry Pi boards needs the following development environment:
Programming skills needed:
For better development Raspberry applications, the Windows IoT Remote Client is welcome. This application is available for download from Microsoft Store. This application captures keyboard, mouse and screen from the Raspberry Pi board running the Windows 10 IoT Core system on the desktop PC. It allows developers to use standalone Raspberry board without a connected mouse, keyboard and monitor to it.
To write and develop applications under Windows 10 IoT Core developer must possess basic knowledge of how Windows operating system interacts with User applications. The major advantage of using Windows 10 IoT Core is that Microsoft concept based on use the same Kernel API available on different hardware platforms – desktop PCs, IoT boards suitable to run Windows Core, Tablets, phones etc. It reduces development costs due to the unifying system environment, and the only difference is in display view of the same application code written in C#/C++. Windows 10 Core is specially designed to handle applications working as standalone on the IoT platforms in 24/7 time model.
After installing the Windows 10 IoT Core, the developer must configure IoT platform using Windows Device Portal (WDP).
IoT board can be managed using any Internet browser – Chrome, Microsoft Edge, Firefox etc. To open the WDB portal on the IoT board user must enter the board IP address – IPaddress:8080/default.htm. The site is protected with username/password. Default account credentials are: administrator/p@ssw0rd. Following tabs in the WDP, it is possible to configure all necessary IoT platform settings, check the current board status, download development crash/debug information, configure network/Bluetooth settings, download drivers and configure security TPM modules. If all tasks are ready, the developer can start to write his own IoT application under Windows 10 IoT Core. Following steps are recommended before IoT board will be used for application:
In the Device Settings user is recommended to Change your device name. The default name is minwinpc. The aim to change it is that if a user uses many of the IoT devices connected to the same network segment, it is difficult to recognise which role each device is set for. Enumerating IoT devices will show boards with the same name but with different IP addresses. Proper naming will make it easy to know what role each device plays.
Because RPI boards don't have their own RTC clock modules, Windows 10 IoT Core sets the time using the NTP services during its work. So very important in the industrial implementations and in a case when the time is important in developed applications is to set the proper time zone for the board. In the Device settings user is recommended to select proper Time zone
Security reasons – the default password for the newly flashed device is p@ssw0rd. It is strongly recommended right after the first board boot to change it – to set it unique! It will prevent the IoT device from remote hacking. The password can be changed in Device Settings tab.
The Windows 10 IoT Core comes with Cortana service ready. If the board is equipped with microphone and speakers, it is always possible to turn the Cortana service on for voice commands communication with the board.
If the IoT board needs special hardware connected to it then in the Devices/Device Manager user can upload and install appropriate driver for it in case if it is not preinstalled in the IoT Core.
Raspberry Pi boards 1/2/3 are equipped with network connection modules. In case if the board under Windows 10 IoT Core is connected to LAN RJ45 connector the IP number can be set via DHCP server. In case if user wants to use WiFi connection or to activate Bluetooth then he can do it directly on the board main display or manage them via Windows Device Portal.
Security. In a case when IoT device must be protected for remote hacking one of solutions is to use Trusted Platform Modules (TPM) module following ISO/IEC 11889 standards for a secure cryptoprocessor, a dedicated microcontroller designed to secure hardware through integrated cryptographic keys. The chip contains physical security mechanisms to protect it from tampering, and malicious software is unable to hack the TPM security functions. Some of the TPM key advantages are:
The most common TPM functions are used for system integrity measurements, key creation and use. During the system boot process, the boot code is loaded (including firmware and OS components) and can be measured and recorded in the TPM module. The integrity measurements are used for evidence how OS started and to be sure when correct boot software was used with TPM-based key. Windows 10 IoT Core supports few TPM modules standards which can be connected to the 40-pin GPIO connector.
Under the TPM Configuration tab in the Windows Device, Portal user can select the right communication protocol for the TPM module installed in the Raspberry Pi board. Then appropriate driver for the TPM module can be installed in the Device Manager tab.
To create simple Hello Word application under Windows 10 IoT Core the Visual Studio 2015 or higher is needed. Visual Studio must be installed with the Universal Windows Platform development extension.
Create new UWP project choosing the Windows Universal/Blank App Project.
Select target version (according to Raspberry Pi Windows 10 IoT Core build version)
Create Hello solution.
Design the application screen modifying the MainPage.xaml file. To add different screen features use the Toolbox/All XAML controls.
Modify the MainPage.cs file content if you need controls events programming.
Compile and run Hello solution. Choosing the Solution Platform to the x86 user will be able to debug and run Hello application on the computers desktop emulator. This step is useful for program touchscreen design but is not capable of testing the sensors and controls programming due to software emulator restrictions.
Software emulators aren't capable of simulating their behaviour. To use sensors and controls instead, the Solution Platform must be changed to the ARM platform in the VC Solution Configuration property. To debug it application package must be then transferred to the real IoT device.
To deploy and debug the application package on the real IoT device user must configure the debug application settings. In the Debug property page user must enter the proper Remote IoT device IP number.
Start debugging application and after deploy application package to the board SD card it will be displayed on the monitor.
The C# [223] variables are categorized into the following types:
Value type variables can assign a value directly. The class System.ValueType defines them.
The value types directly contain data. Value types may be: int, char and float, storing numbers, strings or floating point values. When an int type is declared, the system allocates memory to store its value.
The available value types list in C# is presented following:
The reference types don't contain the actual data stored in a variable. They contain a reference to the variables.
Using multiple variables, the reference types can refer to a memory location. If the variable changes the data in the memory location, the other variable automatically reflects this change in value. Built-in reference example types are: object, dynamic, and string.
The Object Type is an alias for the System.Object class. It is the ultimate base class for all data types in C# Common Type System (CTS). The object types can be assigned with values of any other types, value types, reference types, predefined or user-defined types. Before assigning values, the type conversion is needed.
When a value type is converted to an object type, it is called boxing and when an object type is converted to a value type, it is called unboxing.
object obj; obj = 100; //This is boxing
The data type variable can store any value. But this type checking takes place at run-time.
Syntax for declaring a dynamic type is:
dynamic <variable_name> = value;
For example,
dynamic d = 20;
Dynamic types are similar to object types. That type checking for object type variables takes place at compile time. For the dynamic type variables checking takes place at run time.
The string type allows assigning any string values to a variable. The string type is an alias for the System.String class and is derived from object type. The string type value can be assigned using string literals in two forms: quoted and @quoted.
For example,
string str = "Tutorials Point";
A @quoted string literal looks as follows:
@"Tutorials Point";
The user-defined reference types are: class, interface, or delegate.
Pointer type variables store the memory address, which is another type. Pointers in C# are similar to pointers in C or C++.
Syntax for declaring a pointer type is:
type* identifier;
For example,
char* cptr; int* iptr;
Each variable in C# has a specific type, which determines the size and layout of the variable's memory.
The basic value types in C# can be categorised as follows:
Variable syntax definition in C# is:
<data_type> <variable_list>;
data_type must be a valid C# data type like char, int, float, double, or any user-defined data type. variable_list may consist of one or more identifier names separated by commas.
Examples of valid variable definitions are shown below:
int i, j, k; char c, ch; float f, salary; double d;
Variable can be initialized immediately during definition time:
int i = 100;
Variables are initialized with an equal sign followed by a constant expression. The general initialization form looks:
variable_name = value;
Variables can be initialized during their declaration. The initializer consists of an equal sign followed by a constant expression as:
<data_type> <variable_name> = value;
Some examples are:
int d = 3, f = 5; /* Initializing d and f */ byte z = 22; /* Initializes z */ double pi = 3.14159; /* Declares an approximation of PI */ char x = 'x'; /* The variable x has the value 'x' */
It is important to initialize variables properly, otherwise sometimes it may produce unexpected result.
The following example uses various types of variables:
using System; namespace VariableDefinition { class Program { static void Main(string[] args) { short a; int b ; double c; /* Actual initialization */ a = 10; b = 20; c = a + b; Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c); Console.ReadLine(); } } }
Output:
a = 10, b = 20, c = 30
A while loop statement in C# repeatedly executes a target statement as long as a given condition is true.
while(condition) { statement(s); }
Example:
using System; namespace Loops { class Program { static void Main(string[] args) { /* Local variable definition */ int a = 10; /* while loop execution */ while (a < 20) { Console.WriteLine("value of a: {0}", a); a++; } Console.ReadLine(); } } }
Output:
value of a: 10 value of a: 11 value of a: 12 value of a: 13 value of a: 14 value of a: 15 value of a: 16 value of a: 17 value of a: 18 value of a: 19
A for loop is a repetition control structure that allows you to efficiently write a loop that needs to execute a specific number of times. The syntax of a for loop in C# is:
for ( init; condition; increment ) { statement(s); }
Here is the flow of control in a for loop.
Example:
using System; namespace Loops { class Program { static void Main(string[] args) { /* for loop execution */ for (int a = 10; a < 20; a = a + 1) { Console.WriteLine("value of a: {0}", a); } Console.ReadLine(); } } }
Output:
alue of a: 10 value of a: 11 value of a: 12 value of a: 13 value of a: 14 value of a: 15 value of a: 16 value of a: 17 value of a: 18 value of a: 19
The syntax of a do…while loop in C# is:
do { statement(s); } while( condition );
Notice that the conditional expression appears at the end of the loop, so the statement(s) in the loop execute once before the condition is tested.
If the condition is true, the flow of control jumps back up to do, and the statement(s) in the loop execute again. This process repeats until the given condition becomes false. Example:
using System; namespace Loops { class Program { static void Main(string[] args) { /* Local variable definition */ int a = 10; /* do loop execution */ do { Console.WriteLine("value of a: {0}", a); a = a + 1; } while (a < 20); Console.ReadLine(); } } }
Output:
value of a: 10 value of a: 11 value of a: 12 value of a: 13 value of a: 14 value of a: 15 value of a: 16 value of a: 17 value of a: 18 value of a: 19
C# allows using one loop inside another loop (loop nesting). The following section shows a few examples to illustrate the concept. The syntax for a nested for loop statement in C# is as follows:
for ( init; condition; increment ) { for ( init; condition; increment ) { statement(s); } statement(s); }
The syntax for a nested while loop statement in C# is as follows:
while(condition) { while(condition) { statement(s); } statement(s); }
The syntax for a nested do…while loop statement in C# is as follows:
do { statement(s); do { statement(s); } while( condition ); } while( condition );
A final note on loop nesting is that you can put any type of loop inside of any other type of loop. For example, a for loop can be inside a while loop or vice versa. Example:
using System; namespace Loops { class Program { static void Main(string[] args) { /* local variable definition */ int i, j; for (i = 2; i < 100; i++) { for (j = 2; j <= (i / j); j++) if ((i % j) == 0) break; // if factor found, not prime if (j > (i / j)) Console.WriteLine("{0} is prime", i); } Console.ReadLine(); } } }
Output:
2 is prime 3 is prime 5 is prime 7 is prime 11 is prime 13 is prime 17 is prime 19 is prime 23 is prime 29 is prime 31 is prime 37 is prime 41 is prime 43 is prime 47 is prime 53 is prime 59 is prime 61 is prime 67 is prime 71 is prime 73 is prime 79 is prime 83 is prime 89 is prime 97 is prime
A loop becomes infinite loop if a condition never becomes false. The for loop is traditionally used for this purpose. Since none of the three expressions that form the for loop is required, you can make an endless loop by leaving the conditional expression empty.
Example:
using System; namespace Loops { class Program { static void Main(string[] args) { for (; ; ) { Console.WriteLine("Hey! I am Trapped"); } } } }
When the conditional expression is absent, it is assumed to be true.
C# [225] Specifying one or more conditions to be evaluated or tested by the program require decision making structures. If the condition is determined to be true or false, the proper statements must be performed.
C# provides following types of decision making statements:
An if statement consists of a boolean expression followed by one or more statements. The syntax of an if statement in C# is:
if(boolean_expression) { /* Statement(s) will execute if the boolean expression is true */ }
If the boolean expression evaluates to true, then the block of code inside the if statement is executed. If the boolean expression evaluates to false, then the first set of code after the end of the if statement (after the closing curly brace) is executed.
Example:
using System; namespace DecisionMaking { class Program { static void Main(string[] args) { /* Local variable definition */ int a = 10; /* Check the boolean condition using if statement */ if (a < 20) { /* If condition is true then print the following */ Console.WriteLine("a is less than 20"); } Console.WriteLine("value of a is : {0}", a); Console.ReadLine(); } } }
Output:
a is less than 20; value of a is : 10
An if statement can be followed by an optional else statement, which executes when the boolean expression is false. The syntax of an if…else statement in C# is:
if(boolean_expression) { /* Statement(s) will execute if the boolean expression is true */ } else { /* Statement(s) will execute if the boolean expression is false */ }
If the boolean expression evaluates to true, then the if block of code is executed, otherwise else block of code is executed.
Example:
using System; namespace DecisionMaking { class Program { static void Main(string[] args) { /* Local variable definition */ int a = 100; /* Check the boolean condition */ if (a < 20) { /* If condition is true then print the following */ Console.WriteLine("a is less than 20"); } else { /* If condition is false then print the following */ Console.WriteLine("a is not less than 20"); } Console.WriteLine("value of a is : {0}", a); Console.ReadLine(); } } }
Output:
a is not less than 20; value of a is : 100
It is always legal in C# to nest if…else statements, which means you can use one if or else if statement inside another if or else if statement(s). The syntax for a nested if statement is as follows:
if( boolean_expression 1) { /* Executes when the boolean expression 1 is true */ if(boolean_expression 2) { /* Executes when the boolean expression 2 is true */ } }
Example:
using System; namespace DecisionMaking { class Program { static void Main(string[] args) { //* Local variable definition */ int a = 100; int b = 200; /* Check the boolean condition */ if (a == 100) { /* If condition is true then check the following */ if (b == 200) { /* If condition is true then print the following */ Console.WriteLine("Value of a is 100 and b is 200"); } } Console.WriteLine("Exact value of a is : {0}", a); Console.WriteLine("Exact value of b is : {0}", b); Console.ReadLine(); } } }
Output:
Value of a is 100 and b is 200 Exact value of a is : 100 Exact value of b is : 200
A switch statement allows a variable to be tested for equality against a list of values. Each value is called a case, and the variable being switched on is checked for each switch case.
The syntax for a switch statement in C# is as follows:
switch(expression) { case constant-expression : statement(s); break; /* Optional */ case constant-expression : statement(s); break; /* Optional */ /* You can have any number of case statements */ default : /* Optional */ statement(s); }
The following rules apply to a switch statement.
Example:
using System; namespace DecisionMaking { class Program { static void Main(string[] args) { /* Local variable definition */ char grade = 'B'; switch (grade) { case 'A': Console.WriteLine("Excellent!"); break; case 'B': case 'C': Console.WriteLine("Well done"); break; case 'D': Console.WriteLine("You passed"); break; case 'F': Console.WriteLine("Better try again"); break; default: Console.WriteLine("Invalid grade"); break; } Console.WriteLine("Your grade is {0}", grade); Console.ReadLine(); } } }
Output:
Well done Your grade is B
It is possible to have a switch as part of the statement sequence of an outer switch. Even if the case constants of the inner and outer switch contain common values, no conflicts will arise.
The syntax for a nested switch statement is as follows:
switch(ch1) { case 'A': Console.WriteLine("This A is part of outer switch" ); switch(ch2) { case 'A': Console.WriteLine("This A is part of inner switch" ); break; case 'B': /* Inner B case code */ } break; case 'B': /* Outer B case code */ } Example: <code C> using System; namespace DecisionMaking { class Program { static void Main(string[] args) { int a = 100; int b = 200; switch (a) { case 100: Console.WriteLine("This is part of outer switch "); switch (b) { case 200: Console.WriteLine("This is part of inner switch "); break; } break; } Console.WriteLine("Exact value of a is : {0}", a); Console.WriteLine("Exact value of b is : {0}", b); Console.ReadLine(); } } }
Output:
This is part of outer switch This is part of inner switch Exact value of a is : 100 Exact value of b is : 200
A C# [226] class definition starts with the keyword class followed by the class name, and the class body enclosed by a pair of curly braces. Following is the general form of a class definition:
<access specifier> class class_name { //Member variables <access specifier> <data type> variable1; <access specifier> <data type> variable2; ... <access specifier> <data type> variableN; //Member methods <access specifier> <return type> method1(parameter_list) { //Method body } <access specifier> <return type> method2(parameter_list) { //Method body } ... <access specifier> <return type> methodN(parameter_list) { //Method body } }
Note:
Example:
using System; namespace BoxApplication { class Box { public double length; //Length of a box public double breadth; //Breadth of a box public double height; //Height of a box } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); //Declare Box1 of type Box Box Box2 = new Box(); //Declare Box2 of type Box double volume = 0.0; //Store the volume of a box here //Box1 specification Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; //Box2 specification Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; //Volume of Box1 volume = Box1.height * Box1.length * Box1.breadth; Console.WriteLine("Volume of Box1 : {0}", volume); //Volume of Box2 volume = Box2.height * Box2.length * Box2.breadth; Console.WriteLine("Volume of Box2 : {0}", volume); Console.ReadKey(); } } }
Output:
Volume of Box1 : 210 Volume of Box2 : 1560
A member function of a class is a function that has its definition or its prototype within the class definition similar to any other variable. It operates on an object of the class of which it is a member, and has access to all the members of a class for that object.
Member variables are the attributes of an object (from the design perspective), and they are kept private to implement encapsulation. These variables can only be accessed using the public member functions.
Sample:
using System; namespace BoxApplication { class Box { private double length; //Length of a box private double breadth; //Breadth of a box private double height; //Height of a box public void setLength( double len ) { length = len; } public void setBreadth( double bre ) { breadth = bre; } public void setHeight( double hei ) { height = hei; } public double getVolume() { return length * breadth * height; } } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); //Declare Box1 of type Box Box Box2 = new Box(); double volume; //Declare Box2 of type Box //Box1 specification Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); //Box2 specification Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); //Volume of Box1 volume = Box1.getVolume(); Console.WriteLine("Volume of Box1 : {0}" ,volume); //Volume of Box2 volume = Box2.getVolume(); Console.WriteLine("Volume of Box2 : {0}", volume); Console.ReadKey(); } } }
Output:
Volume of Box1 : 210 Volume of Box2 : 1560
A class constructor is a special member function of a class that is executed whenever we create new objects of that class.
A constructor has the same name as that of class, and it does not have any return type.
Example:
using System; namespace LineApplication { class Line { private double length; //Length of a line public Line() { Console.WriteLine("Object is being created"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); //Set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); Console.ReadKey(); } } }
Output:
Object is being created Length of line : 6
A default constructor does not have any parameter, but you can make one if you need to pass some setup values on the initialisation – such constructors are called parameterised constructors. This technique helps you to assign an initial value to an object at the time of its creation.
Example:
using System; namespace LineApplication { class Line { private double length; //Length of a line public Line(double len) { //Parameterized constructor Console.WriteLine("Object is being created, length = {0}", len); length = len; } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(10.0); Console.WriteLine("Length of line : {0}", line.getLength()); //Set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); Console.ReadKey(); } } }
Output:
Object is being created, length = 10 Length of line : 10 Length of line : 6
A destructor is a special member function of a class that is executed whenever an object of its class goes out of scope. A destructor has the same name as that of the class with a prefixed tilde (~), and it can neither return a value nor can it take any parameters. C# (.NET environment) has a built-in memory management system that tracks unused objects and release memory automatically, but in constrained memory systems like RPI, it is sometimes essential to manually notify this mechanism about the possibility to release memory once the object is no longer used. Here destructor helps much. Moreover, destructor brings a possibility to handle hardware-related issues, e.g. close connection, send a farewell message to the external device, etc. Destructors cannot be inherited or overloaded.
Example:
using System; namespace LineApplication { class Line { private double length; //Length of a line public Line() { //Constructor Console.WriteLine("Object is being created"); } ~Line() { //destructor Console.WriteLine("Object is being deleted"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); //Set line length line.setLength(6.0); Console.WriteLine("Length of line : {0}", line.getLength()); } } }
Output:
Object is being created Length of line : 6 Object is being deleted
We can define class members as static using the static keyword. When we declare a member of a class as static, it means no matter how many objects of the class are created, there is only one copy of the static member.
The keyword static implies that only one instance of the member exists for a class. Static variables are used for defining constants because their values can be retrieved by invoking the class without creating an instance of it. Static variables can be initialised outside the member function or class definition. You can also initialise static variables inside the class definition.
Example:
using System; namespace StaticVarApplication { class StaticVar { public static int num; public void count() { num++; } public int getNum() { return num; } } class StaticTester { static void Main(string[] args) { StaticVar s1 = new StaticVar(); StaticVar s2 = new StaticVar(); s1.count(); s1.count(); s1.count(); s2.count(); s2.count(); s2.count(); Console.WriteLine("Variable num for s1: {0}", s1.getNum()); Console.WriteLine("Variable num for s2: {0}", s2.getNum()); Console.ReadKey(); } } }
Output:
Variable num for s1: 6 Variable num for s2: 6
You can also declare a member function as static. Such functions can access only static variables. The static functions exist even before the object is created. Example:
using System; namespace StaticVarApplication { class StaticVar { public static int num; public void count() { num++; } public static int getNum() { return num; } } class StaticTester { static void Main(string[] args) { StaticVar s = new StaticVar(); s.count(); s.count(); s.count(); Console.WriteLine("Variable num: {0}", StaticVar.getNum()); Console.ReadKey(); } } }
Output:
Variable num: 3
Events occur when a user makes actions like a key press, clicks, mouse movements, etc., or some other occurrence such as system-generated notifications. Applications must respond to events if they occur, i.e. handle interrupts. Events are used during inter-process communication.
The events are declared and raised in a class. They are associated with the event handlers using delegates within the same class or some other class. To publish the event, the class containing it must be defined. It is called the publisher class. Some other class that accepts this event is called the subscriber class. Events use the publisher-subscriber model.
The object containing a definition of the event and the delegate is named publisher. The event-delegate association is also defined in this object. A publisher class object invokes the event, and it is notified to other objects.
A subscriber is an object that accepts the event and provides an event handler. The delegate in the publisher class invokes the method (event handler) of the subscriber class.
To declare an event inside a class, first, a delegate type for the event must be declared. For example,
public delegate string MyDel(string str);
Next, the event itself is declared, using the event keyword:
event MyDel MyEvent;
The preceding code defines a delegate named “BoilerLogHandler” and an event named “BoilerEventLog”, which invokes the delegate when it is raised.
Example:
using System; namespace SampleApp { public delegate string MyDel(string str); class EventProgram { event MyDel MyEvent; public EventProgram() { this.MyEvent += new MyDel(this.WelcomeUser); } public string WelcomeUser(string username) { return "Welcome " + username; } static void Main(string[] args) { EventProgram obj1 = new EventProgram(); string result = obj1.MyEvent("Tutorials Point"); Console.WriteLine(result); } } }
Output:
Welcome Tutorials Point
In no doubt, IoT is network oriented – even the name IoT naturally relates to the Internet network. Communication is an essential part of IoT idea. Every IoT device must communicate somehow, even most simple, passive RFID tag – it responds with some data to the excitation. Communication is always performed with some rules known for both communicating parties. Like people have their different languages to use, devices have protocols. Communication protocol describes how to address the information to the remote device, how to encode the data, how to check the correctness of the incoming message. The physical layer of protocol description also tells how to transmit every bit of data, what is the frequency of radio waves, how fast we can send the data, what is the maximum range of the transmission.
Communication in IoT devices can be wired or wireless.
IoT networking is much different than typical, multilayered, stack-oriented TCP/IP or similar communication model; we know well while using our PC, MAC, server or Smartphone on a daily basis.
Indeed constrained IoT devices are usually unable to operate regular – full time on, ISO/OSI layered stack, because of constrained resources. In details it primary means, IoT devices are limited by the processor power, RAM and storage sizes and mainly because of limited power resources. IoT device is expected to be energy efficient, thus low powered, that in most cases excludes typical wireless connection standards, e.g. WiFi. On the other hand, IoT devices are expected to communicate over long distances – some couple or a dozen of kilometres – where wired infrastructure like Ethernet cables and related infrastructure is non-existent and most of the wired technologies, copper-based is out of range.
Also, IoT devices daily life-cycle is much different than, e.g. or PC life-cycle. We as humans used to switch on the notebook, work extensively on the web, then put it to the low power or off, making the machine to sleep, hibernate or just shutting it down. And we wake it up when needed. It barely makes network operation during sleep. IoT devices are expected to be sleeping providing low power mode whenever possible, and on the other hand, they're supposed to be fully operable, when only needed. Most performed IoT tasks related to the sensing have cyclical nature, i.e. measuring gases as a sensor-network node whereas period can be something like between seconds and months or even longer. Anyway, they're usually expected to trigger themselves to be awake from sleep, perform some operation and connect to the network.
Because of the existence of different IoT devices including those very constrained from 8-bit processors with some kB of the RAM to 32-bit multicore machines well-replacing PCs, IoT networking is very competitive on protocols, approaches and solutions. There are indeed some networking standards introduced by standardisation organisation like IEEE, yet they are competed by large manufacturers forcing their complex solutions including dedicated hardware, software and protocols. The third force driving this market are open solutions and enthusiasts, usually working with cheap equipment, providing de-facto standards for many hobbyists and also industry.
Following chapters explain some most popular concepts about how to organise network fulfilling the above constraints on communication between IoT devices (Machine-2-Machine) and how to let them communicate with the Internet: including hardware, software and human-users. We focus on the de-facto standards existing in the web, usually as open-source libraries and somewhat low-cost devices.
An interesting survey made by RS components [227] shows 11 wireless protocols used in IoT. Some of them you can use free, without having any license to purchase, while some others are proprietary, some of them need the subscription plan.
IoT devices are not separated from the global networking environment that nowadays is highly integrated, connecting various wired and wireless transmission standards into one network called the Internet. Indeed some networks are separated because of security and safety reasons and regulations, yet they usually share the same standards that global, Internet network.
XXI century brought wide acceptance of wireless connections. They became very popular even in so-called pico-networks, like your PAN (Personal Area Network)[228], implemented using, e.g. Bluetooth Connection, where your smartphone in the left pocket of your jeans is hosting a server and there are many wireless devices connected to it: your wireless headset/audio device that you're listening to the music ad the moment, wireless HID controller (e.g. MYO device)[229], your Smartwatch, AR glasses, etc. All those devices constitute a personal, wireless network cell, usually also routed via NAT (Network Adress Translation) [230] to the Internet, through some other wireless connection like WiFi or mobile data (3G/4G/LTE). Naturally, many IoT devices share standard wireless protocols, models and ideas, but some of them are not powerful enough to integrate with the Internet. On the other hand, wireless connections are somehow natural to the IoT devices where they are expected to be operating in remote destinations, usually without any wired infrastructure. Because of it, some Internet standards were adapted to their constraints or such networks of constrained devices are separated and gatewayed through some more powerful devices, where protocol translation occurs. Those models are discussed below, in the following chapters.
The similar way to the regular Internet networking, IoT networking is implemented using (usually simplified) layered stack, similar to the regular ISO/OSI 7 layer networking stack well known to all IT students [231], where the lowest 3 levels constitute so-called Media layers of the stack (recommendation X.200).
Level 1 is a Physical Layer (PHY). On top of it, there is level 2 is Data Link Layer with Media Access Control and Logical Link Control (MAC/LLC). Level 3 is the Networking layer (NET) where packets are formed and routed as presented in Figure 366. ISO-OSI model was designed and implemented for dedicated network controller chips and powerful processors; thus not all of the IoT devices are capable to fully implement this model, particularly because of constrained RAM and storage memory sizes. Also, this model requires an instant connection to the remote node (PHY dependent), so it has a strong impact on the battery drain in case of the low power devices.
A quick overview of popular communication technologies for the Internet is presented in Figure 367. Note wide distance range between nodes of the Wireless devices regarding protocol used (mostly because of the PHY nature) where it varies from some meters in case of piconets up to some 180–2000 km when considering LEO (Low Earth Orbit) satellites, e.g. communicating through Iridium network and even up to about 35 786 km in case of the use of the geostationary satellites [232].
Another factor is the communication bandwidth. Fortunately, IoT devices usually do not require high bandwidth – some couple of kbps is enough; thus, almost all protocols apply here.
In many cases, IoT remote, distant nodes do not need constant communication, i.e. weather sensing would better communicate on datagram communication model (UDP rather than TCP) [233]. In such case, IoT devices utilize differently, simplified IoT stack as presented in Figure 368.
IoT Devices can be classified regarding their ability to implement full protocol stacks of the typical, Internet protocols like IPv4, IPv6, HTTP, etc.
Some IoT networks are also constrained by the number of IP addresses available regarding the number of IoT devices ones need to connect, so their topology is a priori prepared as NAT (Network Adress Translation) solution [234] thus it requires automatically use of routers.
IoT devices are usually expected to deliver their data to some cloud for storage and processing while the cloud can send back commands to the actuators/outputs.
Finally, there are security concerns, which make the IoT devices to be put in some separate sub-network and guarded by the firewall.
All of it brings the three, main communication models, explained below.
Device to device communication model, sometimes referenced as M2M (Machine to Machine communication model) used to be implemented between the homogeneous class of the IoT devices. Nowadays, there is a need to enable heterogeneous systems to collaborate and talk one to another. In a device to a device model, communication is usually held simple, sometimes with niche, proprietary protocols, i.e. ANT/ANT+ [235], sometimes do employs heavy protocols like XML, so there is a need to provide common communication ontologies and semantics. Devices participating in such networks usually act as multimode, constituting self-organising networks, capable of exchanging the data through routing and forwarding as it appears in 6LowPAN networks where nodes may not only act as data producer/consumer but is also expected to act as message forwarder/router.
Device to device model is highly utilized in the Industrial Automation Control systems and recently very popular in developing Industry 4.0 (I4.0) solutions, where manufacturing devices, i.e. robots and other Cyber-Physical systems (CPS) communicate to set operation sequence for optimal manufacturing process (so-called Industry 4.0) thus providing elastic working zones along with manufacturing flexibility and self-adaptation of the processes. It happens because of the presence of various IoT devices (here sometimes referenced as Industrial IoT) and advanced data processing including Big and Small data. Such a device to device networks very frequently mimics popular P2P (peer to peer) networks, where one device can virtually contact any other to ask for information or deliver one. Comparing to the classical, tree-like topology, a device to device communication constitutes a graph of relations rather than a hierarchized tree. The Figure 369 presents comparison between pre-I4.0 (Industry 3.0) and I4.0 data flow. Along with physical (real) devices participating in the manufacturing process, there usually goes their virtual representation (“virtual twin”) to enable cognitive manufacturing based on data science. The detailed description of the data analysis and its use in I4.0 is out of the scope of this book, however.
The device to device communication assumes, participating devices are smart enough to talk one another, without the need of the translation nor advanced data processing, even if their nature is different (e.g. your intelligent door can inform your smart, IoT kettle to start boiling water once they get informed about poor weather condition by the Internet weather monitoring service, when you're back home after long day of work). Devices constituting mesh or scatter network communicate virtually one another similar way people do. The Figure 370 briefly presents the data flow idea
Device to gateway communication occurs when there is a need to provide the translate information between different networks, i.e. some Zigbee [236] network devices need to send data to the Internet to let the smart home be remotely monitored and managed. This model also appears when there is a need to transfer messages between IoT network implemented with constrained devices, so using some simplified protocol (e.g. LoRA, 6LowPAN) and the Internet network, using the full implementation of the protocols (e.g. IP6). In this case, the gateway device (sometimes named here as Edge Router) needs to have knowledge about constrained devices constituting IoT network and it usually supplies some missing information instead of them, i.e. enriching message headers or addresses when passing packets from IoT constrained network to the Internet, but also translating Internet packets (e.g. by removing full address), when acting opposite, e.g. forwarding actuator requests to the IoT devices.
Gatewaying and protocol translation can also occur on the 6th and 7th level of the ISO/OSI model when the implementation of high-level protocols overwhelms even more advanced IoT devices, i.e. simple MQTT texting can be converted to the XML, heavy messages or exposed as XHTML. Those solutions are mostly software-based, i.e. Node-RED [237]. The Figure 371 briefly presents the data flow. Please note the protocol change: arrows of the different colours reflect it.
As IoT devices are usually unable to constitute an efficient computation structure (as single IoT node or even their federation), most data is forwarded to the server, often a cloud-based solution, where it is stored and processed. This data processing in the cloud varies, depending on the type of information, their goal, etc. In any case, we usually face the problem of the visualisation, data analytics (statistics, data mining, knowledge discovery, big data processing). Those tasks are resource consuming, require huge processing capabilities; thus, utilising cloud solution is usually a good choice. Note, claiming “cloud” we consider not only public clouds like Amazon, Google or Microsoft but also dedicated solutions hidden somewhere in the separated, manufacturing networks. Eventually, there is a need to send back some actuation requests to the devices, from the cloud. Cloud services are usually PC based solutions, thus they extensively use rich protocols, providing their APIs via i.e. REST [238], SoAP [239], HTTP GET/POST methods [240], etc. It requires the IoT devices interfacing cloud to implement full communication stacks for the protocols needed. Some of the IoT devices can interface cloud services directly, but some of them are unable to do so due to the constraints, so it is necessary to use gateways as mentioned in the previous chapter.
While the IoT ecosystem is usually considered to be composed of wireless devices, it is still possible to connect IoT solutions using a wired connection. In this chapter, we do not present communication protocols that are short distant one designed to connect sensors to IoT device, like I2C, SPI, Serial, etc. Those are described in chapter “4.2. Embedded Systems Communication Protocols”.
When wireless-enabled SoCs where about to be delivered to the market (i.e. ESP8266), there were already available sorts of extension devices for popular Embedded systems, like i.e. Ethernet Shield for Arduino boards.
Cooper based wired networks also bring an extra feature to the IoT designers – an ability to power the device via a wired connection, i.e. PoE (Power over Ethernet) – 802.3af, 802.3at, 802.3bt [241]. Long distance connections may be implemented using optic-based, fibre connections, but those require physical medium converters that are usually quite complex, pretty expensive and power consuming; thus, they apply only to the niche IoT solutions. Please note, mentioned optical connections do not cover so-called LiFi, as those are considered to be wireless [242].
A non exhaustive list of some present and former wired networking solutions are presented in the Table 29.
Nowadays, the most popular wired networks are 10/100/100BaseT – twisted pair with Cat 5, 5e and 6 cables. They require the IoT system to implement full TCP/IP stack to operate seamlessly with conventional Internet/Intranet/Extranet networks. Because it is usually out of the scope of standard Arduino Uno processor capabilities to implement full TCP stack, there are typically dedicated processors on the network interfaces that assist the central processor or even handle all networking tasks themselves.
Wireless connections define core communication for IoT devices. Wast and growing amount of protocols, their variations and dynamic IoT networking market, all present non-solid situation where old “adult” Internet protocols coexist along with new ideas and IoT hardware and software platforms are more and more capable with every new generation; thus new ideas appear almost daily. Currently, there are many IoT networking protocols defined for various layers of the protocol implementation stack, some of them compatible while others are concurring. The Figure 374 presents some selected protocols existing for IoT. Please note, this covers only the most popular ones and presents a non-exhaustive view. We discuss them more detailed below.
Below we present currently most popular, wireless protocols review for the lower ISO/OSI layers (1–2, some of them also implement layer 3 – Networking).
WiFI is the set of standards for wireless communication using the 2.4 GHz or 5 GHz band, slightly different spectrum in different countries. The core specification of the 2.4 GHz contains 14 channels with 20 MHz (currently 40 MHz) bandwidth. While there is no centralised physical layer controller, collisions frequently occur even more with a growing number of devices sharing the band. The collision is handled using CSMA-CA with a random binary exponential increase of repeating time.
With the high speed of transmission and range usually not exceeding 100 m, it is widely used as the direct replacement of wired Ethernet in local area networks. It is a very good choice while the amount of data to be transferred is larger, for example, video streams or assembled IoT stream delivered by gateways. It is also possible to use it in direct connectivity for smart sensors, and other IoT elements, but the protocol itself is not designed to transmit small data packets. For many IoT applications, it is too much energy consuming, especially when it comes to battery-powered devices. Moreover, WiFi itself offers only 1-to-1 or star-like models of connection, where the central point is a WiFi Access Point (1-to-many) and does not provide mechanisms for io.e. self-reorganizable, failure-tolerant mesh networks. WiFi becomes a more and more popular choice for not-so-constrained IoT devices because they need to implement full TCP/IP stack and those devices that are also not so constrained by power consumption.
Bluetooth is a prevalent method of connecting a variety of devices in short distance. Almost every computer and a smartphone have Bluetooth module built-in. Standard has been defined by Bluetooth SIG (Special Interest Group) founded in 1998. Bluetooth operates in the 2.4 GHz band with 79 channels with automatic channel switching when interference occurs (hopping frequency). The single channel offers up to about 1Mbps (where around 700kbps is available for the user) bandwidth, and it provides communication within the range from up to 1 m (class 3, 1 mW) till up to 100 m (class 1, 100 mW). The most popular version is class 2 with 10 m range (2.5 mW).
Every Bluetooth device has a unique, 48-bit MAC address.
Bluetooth offers various “profiles”, for multimedia, serial ports, packet transmission encapsulation (PAN), etc. The most useful for IoT devices is PAN (Personal Area Network) Profile and of course SPP (Serial Port) Profile.
Now Bluetooth covers two branches: BR/EDR (Basic Rate/Enhanced Data Rate) for high-speed audio and file transfer connections and LE (Low Energy) for short burst connections [243].
Classical (prior to BLE and 4.0) Bluetooth networks can create ad-hoc, so-called WPAN (Wireless Personal Area Networks) sometimes referenced as Piconets. Bluetooth Piconet can handle up to 7 + 1 devices, where 1 device acts as Master, and it can contact up to 7 Slave devices. Only the Master device can initiate a connection. Fortunately for the IoT approach, much Bluetooth hardware can act as Slave and Master simultaneously, constituting this way a kind of router; thus, devices can constitute a tree-like structure called Scatternet.
Bluetooth Low Energy (BLE) uses an simplified implementation of the state machine thus is more constrained-devices friendly. It offers a limited range, and it is designed to expose the state rather than transmit streamed data. It provides a speed reaching up to about 1.4 Mbps (2 Mbps aerial throughput) if needed, however. It uses 2.4 GHz band but is designed to avoid interference with WiFi AP and clients. Communication is organised into three advertising channels (located “between” WiFi) and 37 communication channels.
Latest Bluetooth implementations (protocol version 5.0 and newer, implemented in mid-2017) offer a Bluetooth mesh network extending ubiquitous connectivity via many-to-many communication model, dedicated to IoT devices, lighting, Industry 4.0, etc. The Bluetooth mesh is layer-organised, and since there is no longer Master-Slave model used, but messages are relayed through the mesh, it is considered to be no longer the Scatternet because of its flat structure [244]. Sample Bluetooth Mesh Network is presented in Figure 376 (source: Bluetooth SIG, Mesh Profile Specification v1.0 [245]).
Cellular (mobile/GSM) networks are one of the possible options because of its wide coverage and long range. Those network use orthogonality in frequency and time spaces. Cellular networks are presented by the subsequent generations (G) – currently up to 4.5G present on the market and 5G in the experimental phase (should be fully functional around the year 2020). Typical GSM network technology, sometimes referenced as an era, runs out within about 10–15 years. It is pretty close but still less than expected end-of-life for classes of IoT devices (15-25 years). GSM hardware used to be backwards compatible, enabling users to access older, even before 2G GSM networks with latest chips.
Figure 377 present GSM network evolution over time and generations. Cellular networks use different frequencies in different countries, yet available radio implementations nowadays are usually able to handle all of them.
Figure 378 presents sample GSM hardware (separate module and ready shield for the Arduino platform).
GSM protocols are proprietary, quite complex (including advanced ciphering) and require dedicated hardware. A sort of documentation and standards is not publicly available because of security considerations (i.e. voice transmission ciphering details).
On the one hand, the GSM network seems to be a good solution for extended distant IoT networks; on the other, they have many disadvantages, however. First of all, they require the use of operators' infrastructure – as GSM bands are not free to use.
Important! Professional operation requires licencing and connecting existing infrastructure involves a purchase of the unique identifier (phone ID and a number that is given by the SIM card, physical or virtual) and a service fee.
By the limited access constraints there do exist one more – GSM boards are using quite a significant amount of energy when establishing a connection because they need to broadcast their existence as far as possible, to gain a connection with a possibly distant-located base station. It requires tremendous power and drains the battery (even up to 10 W peak); thus, cellular solutions are not suitable for the IoT devices that use frequent data communication.
ZigBee protocol is so far very popular in Smart House but also in Industry appliances. Zigbee is a wireless technology developed as an open standard to address the needs of low-cost, low-power wireless machine to machine networks. It is more popular in the industry, however, but because of the relatively higher cost of equipment in comparison with WiFi, Bluetooth or other RF modules. The Zigbee standard operates on the radio bands 2.4 GHz for smart home applications, 915 MHz in US and Australia, 868 MHz in Europe and 784 MHz in China. The advantage of ZigBee is the possibility of forming the mesh networks where nodes are interconnected with others, so there are multiple paths connecting each pair of nodes. Connections are dynamically updated, so when one node turns off the path going through that node will be automatically rerouted via another path. Transmission speed is up to 250 kbps, theoretical range up to 100 m but usually to some 10–30 m. ZigBee does not provide direct, unique IP-addressing on the Networking layer like 6LowPAN or Thread do. Single ZigBee network can handle up to 65 000 devices.
Z-Wave is a protocol similar in principals to the ZigBee, but hardware is cheaper; thus, it is more towards inexpensive home automation systems. Like in ZigBee, Z-Wave operates on different frequencies depending on the world region, usually between 865 MHz and 926 MHz. Transmission speed is up to 200 kbps, and the range is up to 100m. A single Z-Wave network is pretty limited on a number of concurrent devices in one network, that is only 232 devices. Each Z-Wave network has a unique ID, and each node (device) in a network has a unique 8-bit identifier.
Another standard [246] that works using the same 802.15.4 radio. There are some differences in the protocol, like address allocation. In 6LowPAN it is done be nodes since in Thread addresses are obtained from DHCPv6 server.
NFC (Near Field Communication) is a technology that enables two-way interactions between electronic devices. What is important one of the devices does not have to be equipped with the power source – it is powered by the receiving radio signal. That’s why NFC is used in contactless card technology enabling devices to exchange the data at a distance of less than 4 cm. Transmission speed varies between 100–420 kbps, range between both active devices is up to 10 cm, operating frequency 13.56 MHz.
Sigfox [247] is the idea to connect objects with sub 1 GHz radio frequency. It uses the 900 MHz frequency range from the ISM band. The range is about 30–50 km (open space), 3–10 km (urban environments). This standard uses a technology called Ultra Narrow Band (UNB). It has been designed to transmit data with deficient speed – from 10 to 1000 bps. Thanks to small data packets it consumes only 50 mW of power. It is intended to create the public networks only so using Sigfox requires the subscription plan. Many (but not all) European countries are covered with Sigfox.
LoRa (Long Range) is the technology for data transmission with relatively low speed (20 bps do 41 kbps) and the range about 2 km (new transceivers can transmit data up to 15 km). It uses CSS (Chirp Spread Spectrum) modulation in the 433 MHz ISM radio band. The cell topology is the star with the gateway placed at the central point. End-devices use one hop communication with the gateway, that is connected to the standard IP network with a central network server. The LoRa technology is supported as LoRa WAN by LoRa Alliance [248] designed as Sigfox for public networks, but it can also be used in private networks that do not require a subscription.
Traditionally, we use IP addressing (usually masked by DNS to be more user-friendly) when accessing Internet resources. IoT devices may also benefit from this approach. However, constrained devices do require special “editions” of the conventional protocols, that are lightweight. Networking layer implements the basic communication mechanisms on the packet level like routing, delivery, proxying, etc. Many IoT, lightweight implementations of the protocols presented below benefit or at least inherit ideas from regular “adult” implementations. Please note that some protocols implement more than one layer, as presented on image 374. We also provide a short reference of the IPv4 and IPv6, to show advantages and drawbacks.
Internet Protocol v4 (1981) is perhaps the most widespread networking protocol. The predecessor of the IPv4 protocol originally called IP was introduced in 1974 and supported up to 2^8 hosts, organised in 2^4 subnetworks (RFC 675).
In IPv4 (RFC 760/RFC791) the logical addressing space was extended to 2^32 devices that seemed to be quote much in 1981, but now we struggle with lack of free addressing space. This number is less because some addresses are reserved, e.g. for broadcasting and due to the existence of different classes of addresses and their pools [249]. Sample IPv4 address is, for example, 192.168.1.1.
Some relief to suffocating Internet was brought as an ad-hoc solution with an introduction of the NAT (Network Address Translation). NAT-enabled subnetworks are those, where one public address represents a set of devices hidden behind the router, but that limits usability because of lack of direct access and unique identification in the global network of the devices sharing private address spaces. Even so, there are about 8.5 billion IoT devices expected to be connected to the Internet by the end of the 2017 year, according to the Gartner's report [250]. They all need to be uniquely addressed!
IPv6 is the next generation of the IPv4 protocol. It is supposed to replace IPv4, but this process is somehow not so quick as there are many solutions still present on the Internet and Intranets that implement IPv4 only and would become inoperable if IPv4 would not be available anymore. IPv6 brings addressing space large enough to cover all existing and future needs. The number of possible addresses is 2^128. Addresses are presented by 8 groups of 4 hexadecimal values, e.g. 2001:0db8:0000:0042:0000:8a2e:0370:7334.
This brings the capability to uniquely identify any device connected to the Internet using its IPv6 address. Regarding IoT, implementations have many drawbacks (IPv4 also has them). IPv6 network is star-like, whereas IoT networks can benefit from the mesh model. IPv6 network requires a controller providing free addresses (a DHCP server) – devices need to contact it to obtain the address. Every single IoT device needs to keep a list of devices it corresponds with (ARP) to resolve their physical address. Moreover, full IPv6 stack implementation requires large RAM, when used.
The name is the abbreviation of “IPv6 over Low-Power Wireless Personal Area Networks” [251] and as it says is the IP based network. This protocol was introduced as a lightweight version of full IPv6, IoT-oriented. This feature allows connecting 6LoWPAN networks with other networks using so-called Edge Router. Thus every node can be visible on the Internet as states in IoT idea. This standard has been developed to operate on the radio channel defined in 802.15.4 (as ZigBee, Z-Wave). It creates the adaptation layer that allows using IPv6 over 802.15.4 link. 6LoWPAN has been adopted in Bluetooth Smart 4.2 standard as well.
6LoWPAN supports two addressing models: 64 bit and 16 bit (that, of course, limits the number of devices connected to one network to 64 000 nodes). The primary frame size is just 127 bytes (comparing to full IPv6 where it is 1280 bytes at least). 6LoWPAN supports unicast and broadcast. It also supports IP routing and link-layer mesh (802.15.5) that enables the introduction of the fail-safe redundant, self-organising networks, because the link-layer mesh can have more than one Edge Router. 6LoWPAN uses autoconfiguration for neighbour devices discovery so does not require a DHCP server. It also supports ciphered transportation using AES 128 (and AES 64 for constrained devices).
6LoWPAN devices can be just nodes (Hosts) or nodes with routing capability (Routers) as presented in Figure 379.
A gateway between 6LoWPAN and regular IPv6 (IPv4) network is implemented by the Edge Router. Its purpose is to translate “compressed” IPv6 addresses to ensure bi-directional communication between the Internet and 6LoWPAN nodes. Note – the network structure of the 6LoWPAN is logically flat (star/mesh with single addressing space), and devices have unique MAC addresses to be recognisable by the Edge Router device.
When the 6LoWPAN network starts, there are three operations done, repeated consequently.
Typical IPv6 networking discovery won't work here because multicast/broadcast messages are not passable through 6LoWPAN routing nodes (routes as on in Figure 380.
An interesting procedure is performed when an IoT node (device) wants to connect to the existing 6LoWPAN network. As there is no central DHCP server broadcasting information, the device needs to discover the configuration and create 6LoWPAN address itself. It issues the network discovery process.
Network discovery (discovery of neighbour nodes) in 6LoWPAN uses four principals:
Network Automated Discovery is composed of two main sections.
This way, the new 6LoWPAN node can join the new network seamlessly. Moreover, this mechanism enables 6LoWPAN mesh network to self-organise itself if needed, e.g. in case of a failure of the router.
The host layers protocols include session (SES), presentation (PRES) and application (APP) level, particularly APP (application) layer in the regular Internet communication is dominated by the HTTP protocol and XML-related derivatives, e.g. SoAP. Also, FTP protocol for file transfer is ubiquitous; it exists since the beginnings of the Internet. Most of them are somehow related to the text. They're referenced as “WEB” protocols. Although these protocols are frequently used by advanced and more powerful IoT devices, this is problematic to be implemented in the constrained IoT devices world. Event simplest HTTP header occupies at least 24 + 8 + 8 + 31 bytes without any payload! There is also a problem to cross firewall boundaries when communication between subnetworks of the IoT devices is expected to occur. Some IoT designed protocols are reviewed below.
MQTT protocol [252] was invented especially for the constrained IoT devices and low bandwidth networks. It is located in APP layer 7 of the ISO/OSI stack, but in fact, it covers all layers 5–7. It is text-based protocol yet very compact end efficient. Protocol stack implementation requires about 10 kB of RAM/flash only.
MQTT uses TCP connection so requires open connection channel (this is in opposite to UDP connections, where communications work in a way: “send and forget”). It is considered a drawback of the original MQTT protocol, but there do exist MQTT variations for non-TCP networks, e.g. MQTT-SN. Protocol definition provides reliability and delivery ensure mechanisms.
The standard MQTT Message header is composed of just two bytes only (Table 32)! There are 16 MQTT messages types. Some messages types require variable length header.
MQTT requires for its operation a centralized MQTT broker that is located outside of firewalls and NATs, where all clients connect, send and receive messages via publish/subscribe model. The client can act as publisher and subscriber simultaneously. The image 381 presents publish-subscribe model idea.
MQTT is a text-based protocol and is data-agnostic. A message is composed of a Topic (text) and a Payload (data). The topic is a directory-like string with slash (”/“) delimiter. Thus all Topics constitute (or actually may represent) a kind of tree-like folders, similar to those on the file system. The subscriber can subscribe to specific, single Topic, or to a variety of Topics using wildcards, where:
Example Scenario
Publishers deliver some air quality information data in separate MQTT messages and for various rooms at the department Inf of the Universities (SUT, RTU, ITMO) to the Broker:
Topic (publishers): |
---|
SUT/Inf/Room1/Sensor/Temperature |
SUT/Inf/Corridor/Sensor/Temperature |
SUT/Inf/Auditorium1/Sensor/Temperature |
RTU/Inf/Room1/Sensor/Temperature |
ITMO/Inf/Room1/Sensor/Temperature |
RTU/Inf/Room1/Sensor/Humidity |
SUT/Inf/Room3/Sensor/Temperature |
RTU/Inf/Room1/Window/NorthSide/State |
The subscriber 1 wills to get all sensor data for SUT university, Inf (informatics) department only, for any space:
Topic (subscription): |
---|
SUT/Inf/+/Sensor/# |
The subscriber 2 wills to get only Temperature data virtually from any sensor and in any location in ITMO:
Topic (subscription): |
---|
ITMO/#/Temperature |
The subscriber 3 wills to get any information from the sensors, but only for the RTU
Topic (subscription): |
---|
RTU/# |
The payload (data) of the message is text as well, so in case one need to send binary data, it is necessary to encode it (e.g. Base64).
MQTT Broker is a server for both publishers and subscribers. The connection is initiated from the client to the Broker, so assuming it is located outside of a firewall, it breaks firewall its boundaries. The Broker provides QoS (Quality of Service), and it can retain message payload. There are three levels of MQTT Broker QoS (supplied in the message level).
For Acknowledged and Assured services it is vital to provide unique packet IDs in MQTT frame.
The DUP flag (byte 1, bit 3) represents information sent by the publisher if the message is a “first try” (0) or a retransmitted one (1). It is mostly for internal purposes, and this flag is never propagated to the subscribers.
MQTT offers some limited set of features (options):
Interestingly MQTT is a protocol used by Facebook Messenger [253]. It is also implemented natively in Microsoft Azure and Amazon Web Services (among many others).
MQTT security is rather weak. MQTT Broker can offer user and password verification yet it is sent plain text. However, all communication between client and Broker may be encapsulated in SSL, encrypted stream.
A short comparison of MQTT and HTTP protocols are presented in the Table 33.
CoAP protocol (RFC7252) originates from the REST (Representational State Transfer). CoAP does not use a centralised server as MQTT does, but every single device “hosts” a server on its own to provide available resources to the clients asking for service offering distributed resources. CoAP uses UDP (comparing to MQTT that uses TCP) and is stateless thus does not require memory for tracking the state. The CoAP implementation assumes every single IoT device has a unique ID, and things can have multiple various representations. It is intended to link “things” together using existing standard methods. It is rather a resource-oriented (not document-oriented like HTTP/HTML), designed for slow IoT networks with a high degree of packet loss, also support devices to be periodically offline. CoAP uses URIs :
It supports various content types, can work with proxy and can be cached.
The protocol is designed to be compact and simple to implement. The stack implementation takes only about 10 kB of RAM and 100 kB of storage. The header is only 4 bytes.
CoAP protocol has a binary header to decrease overhead but payload depends on the content type. Initial, non-exclusive list of the payload types include:
CoAP endpoint services are identified by unique IP and port number. However, they operate on the UDP instead of TCP (like, e.g. HTML does). The transfer in CoAP is made using non-reliable UDP network so that a message can appear duplicated, disappear or it can be delivered in other order than initially sent. Because of the nature of datagram communication, messages are exchanged asynchronously between two endpoints, transporting Requests and Responses. CoAP messages can be (non-exhaustive list):
Because of the UDP network characteristics, CoAP provides an efficient yet straightforward reliability mechanism to ensure successful delivery of messages:
Request-reponse pair is identified by a unique “Token”. Sample request-response scenarios are presented in images below. Sample CoAP message exchange scenarios between client and server are presented (two per image) in Figure 382 and Figure 383.
The scenario in the Figure 382 (left, with token 0 × 70) is executed in situation when a CoAP server device (a node) need some time to prepare data and cannot deliver information right away. The scenario in Figure 382 (right, with token 0 × 71) is used, when a CoAP server can provide information to the client immediately. The scenario in Figure 383 (left, with token 0 × 72) appears when a CoAP server cannot understand the request. The scenario in Figure 383 (right, with token 0 × 73) presents the situation where the request to the CoAP server was made with a non-confirmable request.