Master`s Thesis

Master`s Thesis
I-DROID02 PROJECT: UPDATING HUMANOID ROBOTICS
CAPABILITIES
A Master's Thesis
Submitted to the Faculty of the
Escola Tècnica d'Enginyeria de Telecomunicació de
Barcelona
Universitat Politècnica de Catalunya
by
Marc Darné Marí
In partial fulfilment
of the requirements for the degree of
MASTER IN TELECOMMUNICATIONS ENGINEERING
Advisor: Vicente Jiménez Serres
Barcelona, April 2017
Title of the thesis: I-Droid02 Project: Updating Humanoid Robotics Capabilities
Author:
Marc Darné Marí
Advisor:
Vicente Jiménez Serres
Abstract
In 2006 RoboTech S.L.R., a company specialized in industrial service robotics and custom
electronics, released a new product intended to be bought for the general public. I-Droid01
was a humanoid robot developed together with ARTS Lab, from Scuola Superiore
Sant’Anna University placed in Pisa (Italy). I-Droid01 was named as a last-generation
robot: aside from being able to move autonomously, thanks to its motor system and many
ambient sensors, it included a software inspired by artificial intelligence, adding moods to
the robot. However, despite good intentions of the developers, the robot presented some
design issues both in hardware and software. Also, the software used to manage I-Droid01
became obsolete beyond year 2009.
I-Droid02 Project aspires to upgrade the original I-Droid01 system to current technology.
The electronics design has been started from scratch although preserving as much as
possible the original features and the robot’s own structure.
I-Droid02 improves the fails of its predecessor and leaves the door opened for new
hardware and software features to be incorporated.
I-Droid02 Project is a wide project of which the initial phase is collected in this thesis. A
comprehensive analysis of original I-Droid01 robot is included, as a knowledge basis to
understand the I-Droid02 hardware and software design and also to compare obtained
results at the end of the thesis. In addition, the software basis that makes accessible all IDroid02 sensors and actuators to be used by any application and a couple of functional
applications are stated in this thesis.
1
Revision history and approval record
Revision
Date
Purpose
0
22/09/2016
Document creation
1
29/09/2016
Document revision by supervisor
2
25/02/2017
Document revision by supervisor
3
21/03/2017
Final document revision by author
4
05/04/2017
Final document revision by supervisor
Written by:
Reviewed and approved by:
Date
21/03/2017
Date
05/04/2017
Name
Marc Darné Marí
Name
Vicente Jiménez Serres
Position
Project Author
Position
Project Supervisor
2
Table of contents
Abstract ............................................................................................................................1
Revision history and approval record ................................................................................2
Table of contents ..............................................................................................................3
List of Figures ...................................................................................................................5
List of Tables ....................................................................................................................7
1.
2.
3.
4.
5.
Introduction................................................................................................................8
1.1.
Statement of purposes .......................................................................................8
1.2.
Requirements and specifications ........................................................................8
State of the art of the technology applied in this thesis ..............................................9
2.1.
Overview of the full original system ....................................................................9
2.2.
Original I-Droid01 hardware description............................................................ 13
2.3.
Original I-Droid01 software description ............................................................. 23
2.4.
The I-Droid01 evolution. New hardware and software requirements ................. 28
Methodology / project development ......................................................................... 32
3.1.
Introducing I-Droid02 hardware and software design ........................................ 32
3.2.
I-Droid02 full hardware design .......................................................................... 33
3.3.
433 MHz I-Droid02 native remote control ......................................................... 53
3.4.
First approach on I-Droid02 software ................................................................ 57
3.5.
433 MHz I-Droid02 native remote control firmware ........................................... 59
3.6.
Independent Bash/Python scripts ..................................................................... 62
3.7.
I-Droid02 firmware: Programming sensors and actuators microcontroller ......... 64
3.8.
I-Droid02 Driver: Main interface with I-Droid02 peripherals ............................... 75
3.9.
Setup program..................................................................................................79
3.10.
433 MHz native remote control server ........................................................... 80
3.11.
Future work: Other high-level applications .................................................... 83
Results ....................................................................................................................85
4.1.
Hardware design results ...................................................................................85
4.2.
Software design results ....................................................................................89
4.3.
Programming environment ...............................................................................91
Budget .....................................................................................................................93
5.1.
6.
I-Droid02 Project total budget ........................................................................... 93
Conclusions .............................................................................................................94
Bibliography ....................................................................................................................97
3
Appendix 1. I-Droid02 hardware schematics ................................................................... 98
Appendix 2. Arduino Nano, Mega and Raspberry Pi 2 Pinouts ..................................... 111
Appendix 3. I-Droid02 Software General Block Diagram ............................................... 115
Appendix 4. I-Droid02 native remote control firmware source code .............................. 116
Appendix 5. Raspbian Bash scripts source code .......................................................... 139
Appendix 6. Raspbian Python scripts source code ....................................................... 142
Appendix 7. Arduino Mega 2560 source code............................................................... 150
Appendix 8. Arduino Mega – Raspberry Pi serial commands........................................ 200
Appendix 9. I-Droid02 Driver source code .................................................................... 202
Appendix 10. The I-Droid02 Driver reference manual ................................................... 309
Appendix 11. Setup program source code .................................................................... 348
Appendix 12. 433 MHz native remote control server source code................................. 393
4
List of Figures
The figures of this document are found in the following table, with the number, a short
description and the page where they can be found. Figures placed at Appendices are
excluded from this table:
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Figure short description
Page
RoboTech S.L.R. Dustcart urban robot [2]
9
Veear Easy to implement Embedded Voice Recognition modules [3]
10
All fascicles of I-Droid01 collection (Spanish edition) and its multimedia
11
I-Droid01 product finished
12
I-Droid01 hardware complete block diagram [5]
13
H-Bridge circuit schematic and PWM operation
16
I-Droid01 removable tools and its location to be used with the robot
16
I-Droid01 head test board
17
I-Droid01 CMOS camera electronics board
20
I-Droid01 software architecture
23
I-Droid01 voice commands complete word set (Spanish edition)
25
I-Droid01 PC/Mobile Control software tool (Spanish edition)
26
I-Droid01 Visual C-Like Editor IDE tool
27
Duracell and RS AA Alkaline vs NiMH batteries discharge curves [7]
29
I-Droid01 external cables aesthetical failure
30
Arduino Mega 2560 as the I-Droid02 sensors and actuators microcontroller
39
GDM1602H-FE(B)-GBS/2P LCD display blocks diagram with involved pins
42
Microphones circuit schematic used in I-Droid02 design
43
I-Droid02 motors and encoders electronic schematic
45
Hand motor particular schematic
45
Touch sensor triggering circuit used in I-Droid02 hardware design
46
Transmitter design for I-Droid02 ultrasonic system
47
Receiver design for I-Droid02 ultrasonic system
48
Raspberry Pi 2 B as the I-Droid02 brain board
50
External audio amplifier designed to boost Raspberry Pi 2 B audio
51
FSK vs GFSK ideal spectrums
54
Transmitted pseudo-random signal spectrum received at I-Droid02 receiver
54
Arduino Nano as the I-Droid02 native remote control microcontroller
55
Schematic to connect pair of push buttons that interacts with the same motor
56
Example of high-level and low-level programming
57
Algorithm to detect objects used in the I-Droid02 ultrasonic system
69
Algorithm to monitor the absolute position of any motor with an encoder
71
Algorithm used to maintain I-Droid02 rectilinearity for straight trajectories
75
I-Droid02 peripheral access layer diagram
76
Example of I-Droid02 Driver operation with the Arduino Mega peripheral
77
I-Droid02 Driver handler thread software flow diagram
78
433 MHz native remote control server software flow diagram
82
5
38
39
40
41
42
43
44
45
46
Pictures of I-Droid02 and its remote control taken during assembly process
Inside Raspberry Pi 2 B box
I-Droid02 final aspect
433 MHz I-Droid02 native remote control final aspect
Original waveform (left) of ‘I-Droid02’ word vs captured sample (right)
Original spectrum (left) of ‘I-Droid02’ word vs captured sample (right)
Desktop aspect of the I-Droid02 on-board Raspberry Pi
Netbeans IDE running at I-Droid02 on-board Raspberry Pi
Arduino IDE running at I-Droid02 on-board Raspberry Pi
85
86
86
87
88
89
91
92
92
6
List of Tables
All the used tables of this document are found in the next table, with the number, a short
description and the page where they can be found. Tables placed at Appendices are
excluded from this table:
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Table short description
I-Droid01 Base Controller details
I-Droid01 Arm Controller details
I-Droid01 Head Controller details
I-Droid01 Motherboard details
I-Droid01 Brain & Vision details
I-Droid01 Bluetooth details
I-Droid01 Voice module details
I-Droid01 Universal Remote Control module details
I-Droid01 Hand Controller details
I-Droid02 battery for power supply system
I-Droid02 sensors and actuators interfaced with Arduino board
I-Droid02 native remote control analog actuator packet Byte control flag
I-Droid02 native remote control digital actuator packet Byte control flag
Movement parameters to wheels’ motors parameters translation
List of I-Droid02 Driver handlers
I-Droid02 and native remote control power consumption results
Total costs of I-Droid02 new parts
Page
14
15
17
18
19
20
21
22
22
35
38
61
62
74
78
88
93
7
1.
Introduction
1.1.
Statement of purposes
This thesis is part of the I-Droid02 Project which has the objective of design, assemble,
program and test a proposed evolution of a previous released robotics product. I-Droid01,
its commercial name, was assembled by Robotech S.L.R. as part of a university project
and distributed by Planeta DeAgostini editorial in Spain. I-Droid01 product was distributed
as a two-years collection of 90 fascicles. Its total cost came up to approximately 900 €.
RoboTech S.L.R. sold this product as an up-to-date last-generation robot, but hardware
used and, especially software tools provided by the manufacturer got obsolete very quickly
in time.
I-Droid02 Project, thus, is not a project started from scratch. It uses the obtained results of
I-Droid01, which was initially developed by the ARTS Lab, from Scuola Superiore
Sant’Anna University placed in Pisa (Italy). I-Droid02 Project also feeds on other projects
and individual people who have already investigated about original I-Droid01 product in
order to do a proper reverse engineering; being able to determine operation and the
strengths and weaknesses of the final product. At this point, the final objective of I-Droid02
Project is to create a final product able to:
1. Improve weaknesses of original I-Droid01 robot product.
2. Update all necessary hardware to overcome obsolescence problems of the original
product, trying to encourage the replacement of I-Droid02 key elements if they
become obsolete in the future.
3. Perform the same tasks and behaviors of original product but more efficiently,
taking advantage of the new installed hardware.
4. Avoid of creating a closed and unmodifiable product. Instead, create a platform
where new tasks and behaviors able to be carried by the hardware could be added
easily.
Part of the I-Droid02 Project has been developed in the UPC electronic labs.
1.2.
Requirements and specifications
In order to achieve the previous objectives, a final plan has been developed in the following
steps:
1. Perform a reverse engineering of original I-Droid01 product to identify hardware
parts and software operation. Determine obsolete hardware parts.
2. Design and assemble a new system (called I-Droid02) taking as a basis I-Droid01
mechanical skeleton and non-obsolete, updated and added hardware parts.
3. Program microcontrollers in order to have low level access to I-Droid02 sensors
and actuators.
4. Give I-Droid02 a basic functionality (high-level programming).
5. Program advanced tasks and behaviors to increase I-Droid02 functionalities.
Only 1 to 4 steps will take part of this thesis. Last step can be considered as future work.
8
2.
State of the art of the technology applied in this thesis
In this section, the background of I-Droid02 Project has been put on the table. Starting from
the beginning, I-Droid01 humanoid robot developed by RoboTech S.R.L. company is
shown in full detail, with the objective of having an accurate background of the initial design
of this commercial product. Then, in order to discuss later all the hardware and software
improvements done along I-Droid02 Project, a full review of the original hardware and
software capabilities is carried out.
Finally, after original I-Droid01 has been presented, last two subsections show, on one
hand, all hardware parts and full entities that will be part of the I-Droid01 evolution and, on
the other hand, which are the requirements of new software to be developed for I-Droid02
microcontrollers.
2.1.
Overview of the full original system
In 2004, Nicola Canelli (a research assistant at the ARTS Lab at Scuola Superiore
Sant’Anna in Pisa, Italy) and Cecilia Laschi (current professor of bio robotics at the same
university) founded the RoboTech S.L.R. company [1]. This company, that were initially
founded as an academic spin-off company of the public university of Pisa, had the initial
objective of combine academic research projects with actual market requirements.
RoboTech S.L.R. were initially founded to act as a bridge between academia and industry.
Nowadays, RoboTech S.L.R. company have focused their business model in three big
branches: Service robotics, voice recognition embedded modules and custom electronics.
The first commercial product for service robotics that RoboTech S.L.R. produced was
called Dustcart [2], which was the first completely autonomous system for urban door-todoor garbage collection with real users. Today Dustcart robot is still available today as a
tool for research. Voice recognition, in the pure electronics field, is an extensive expertise
area for RoboTech S.L.R. The own-produced embedded modules are available for the
most common languages for every type of product, from toy robots to service robots, and
including domestic appliances and vehicles. RoboTech S.L.R. develop and sell these voice
recognition modules under Tigal and Veear [3] commercial brands. Finally, RoboTech
S.L.R. offer totally customized products for their customers, contributing to the project with
the design, prototype and test of those customized products.
Figure 1. RoboTech S.L.R. Dustcart urban robot [2].
9
Figure 2. Veear Easy to implement Embedded Voice Recognition modules [3].
In 2006, RoboTech S.L.R. debuted in the worldwide commercial robotics market with the
launch of a humanoid robot, called I-Droid01. Beyond than a toy, I-Droid01 had the
objective of bringing to the general public a humanoid robot, equipped with several sensors
such as environment temperature, ultrasonic sensors to avoid obstacles, microphones to
detect direction of sounds, a CMOS camera, a touch sensor in the top head and, a first
version of RoboTech S.L.R. voice recognition module and a Bluetooth device also
developed by the same company.
I-Droid01 was designed to be a completely autonomous robot equipped also with certain
artificial intelligence [4], able to move by itself around a flat surface, avoiding obstacles in
front of it and, following a certain monochromatic object or a certain impulse sound source
(such as a hands clap) and also interacting via voice recognition with a human. Thanks to
the Bluetooth module, I-Droid01 was also able to be remote controlled via a computer or a
Nokia Symbian mobile phone.
A part of being an autonomous system, I-Droid01 included two programming environments:
The first one was a simple programming environment focused on create automatic
procedures to be done by I-Droid01. Based on Visual C, programming in this way was as
easy as set the high-level instructions (such as head up, say something, wait for
temperature being below a certain value, etc.) in a flow diagram. The second programming
environment, based on Java, was focused on develop Java applications running on a
computer and interacting in real time with I-Droid01, using a Bluetooth link.
The role of RoboTech S.L.R. was to develop I-Droid01 project, as part of a research project
of the ARTS Lab (Scuola Superiore Sant’Anna). However, RoboTech S.L.R. was not the
final vendor of I-Droid01 but some intermediates were in charge of distributing the product
worldwide. In Italy, the country of origin, the intermediate was a publishing house called
DeAgostini. The same publishing house distributed the product around Europe countries
through its subsidiaries (in the case of Spain, the publishing house was Planeta DeAgostini
and I-Droid01 robot was known as TUROBOT).
Those publishing houses distributed I-Droid01 product as a nine-phase collection [5], that
was composed by 90 fascicles and lasted two years. The main handicap of I-Droid01
product design was to be able to design a complex system (composed by several modules)
but each module being able to operate independently from the other. The reason was the
delivering format of this commercial product. In the case of I-Droid01 collection, after 6
10
deliveries (1 month and a half after being started the collection) the touch sensor of the
head, its eyes LEDs and the head tilt motor were assembled and the collector was able to
start playing with the just assembled module (after each touch, head just tilted at the same
time its eyes LEDs were blinking). The objective of that fact was to encourage the collector
to continue following the collection, in order to develop and improve the functionalities of
the robot he was assembling.
I-Droid01 project, thus, was also designed to be focused on the assembling simplicity.
Moreover, publishing houses contributed in the stimulation of continue the collection by
creating each fascicle containing the following sections:
•
•
•
A section of news and curiosities related with the robotics. In this section, news
about new discoveries in the field of robotics or also some chronicles of certain
robots of the time developing activities that could awake curiosity were put on the
table.
A section to talk about robotics in fiction, putting on the table some movies where
robotics and science-fiction were the protagonists.
The instruction manual step by step to assemble the delivered parts in the current
fascicle to the robot, taking into account that some parts may need to disassemble
other parts previously assembled and then reassemble all again.
Figure 3. All fascicles of I-Droid01 collection (Spanish edition) and its multimedia.
As it has been said, each fascicle of I-Droid01 collection was composed by its magazine
and the I-Droid01 parts to be assembled. However, the first delivery contained a DVD with
11
some commercials of this and other Planeta DeAgostini collections. In addition, other
special deliveries also contained a total number of 4 CDs. They contained videos to help
in the step-by-step assembling process and they also included the needed software (and
its corresponding updates) to flash I-Droid01 firmware when requested and the
programming tools (IDE). All this stuff and a technical support forum was available in a
website (http://www.planetadeagostini.es/turobot). This collection was edited once; for this
reason, at the end of the technical support this website stopped working.
The following figure shows which was the final appearance of the I-Droid01 after ending
this collection (Dimensions: 41x26x33 cm. Weight: 2 Kg. Autonomy: 2 hours):
Figure 4. I-Droid01 product finished.
When I-Droid01 was completely assembled, it was able to develop the following functions
[5]:
•
•
•
•
•
•
•
•
•
Speech recognition and synthesis capabilities (Voice control).
Image processing and visual recognition capabilities.
Detection of sound direction.
Obstacle avoidance (US sensors) capability.
Emotion and mood expression.
Behavior based with neural network based software control system.
Remote control by mobile phone and PC via Bluetooth.
Reprogrammable (able to execute up to 8 different procedures defined by the user).
Prototype for user custom circuits development, placed at I-Droid01 hip.
Next two subsections of this thesis will be focused on describe in an accurate way all the
hardware and software that I-Droid01 was made-up. The objective of that will be to have
the necessary background to discuss later in the final subsections what should be the new
requirements in order to update I-Droid01 to the latest technology. This project is named IDroid02 Project, which this thesis includes most part of it.
12
2.2.
Original I-Droid01 hardware description
Next paragraphs will be dedicated to describe which was the hardware structure of original
I-Droid01 robot. Taking into account that basic electronic parts such as LEDs, sensors and
actuators are maintained in the I-Droid01 evolution (I-Droid02), they will only be mentioned
in this section. To refer to the detailed schematics to use those parts, in the new I-Droid02
design environment, please refer to Appendix 1. I-Droid02 hardware schematics.
As explained in previous section, I-Droid01 was designed to be assembled progressively,
during two years. The main objective was to be able to play with I-Droid01 during its
assembling process, avoiding the need of waiting to the end of the collection to enjoy it. In
order to reach that, as explained in [5], all I-Droid01 sensors and actuators were controlled
by 9 independent entities with a microcontroller associated to each of them. All
microcontrollers were connected using an I2C bus.
Figure 5 shows a complete block diagram of the hardware architecture:
Figure 5. I-Droid01 hardware complete block diagram [5].
All previous entities were powered by eight 1.5 V AA alkaline batteries [6], which formed
two independent power supplies: A 4.5 V power supply called Logic Voltage (putting 3
batteries in series) and a 7.5 V power supply called Motors Voltage (putting the remaining
5 batteries in series). On one hand Motors Voltage, as its name states, was in charge of
powering the circuits able to drive current to each motor, when requested by the
corresponding entity. On the other hand, Logic Voltage was acting as the power supply of
the rest of the circuits; which were powered at 3.3 V. A DC-DC step-down voltage
conversion was applied at motherboard entity.
As said before, the interconnection between entities was done using an I2C bus, which
allows bidirectional and multi-master serial communications at rates of about 400 kbauds.
13
Taking into account that most part of low level tasks (like trigger and sense the movement
of a motor or process the value of a certain sensor) were performed inside the same entity
and processed by its own microcontroller, I2C bus was only used to share information. As
an example, print the value of the temperature sensor that was managed by the Arm
Controller on the display, managed by the Motherboard. This bus was also used to receive
external commands like motors movement from user programs or external remote control.
Next paragraphs will be used to describe in a detailed form each of the 9 entities, sorted
as they are mentioned in the previous figure.
1st entity. Base Controller
Board front and back pictures
Coupled sensors
•
•
•
•
•
2 ultrasonic
transmitters
(ChinaSound
CUT10G1A-40).
3 ultrasonic receivers
(ChinaSound
CUR10G1A-40).
Hip motor encoder.
Left wheel motor
encoder.
Right wheel motor
encoder.
Coupled actuators
•
•
•
Hip motor.
Left wheel motor.
Right wheel motor.
µC brand
µC model
Crystal frequency
RAM
Flash
Freescale
MC68HCS909GT16
40 MHz
1 KB
16 KB
Table 1. I-Droid01 Base Controller details.
Base Controller entity was in charge of managing hip and wheels motors, as part of the
base of I-Droid01, and also the ultrasonic system able to detect and avoid obstacles. The
ultrasonic transducers were placed in the battery box, coupled to the base structure.
The DC motors driver that provides the needed current to turn on each motor, was based
on H-Bridge CMOS transistors (BCV26 and BCV27) [6]. This circuit, whose schematic can
be seen in Figure 6, provides a smart solution to power any DC motor controlling at the
same time the direction of rotation, through A and B lines. The H-Bridge structure also
allows to brake a DC motor (when A and B are in logical high level voltage, connecting both
motor terminals to VCC.
Base Controller entity, as it can be seen in Figure 6, provides rotation speed control to the
wheels’ motors through Pulse-Width Modulation (PWM). PWM permits any digital output
pin to deliver any mean voltage value between low and high levels. The concept applied is
14
that a certain square voltage signal of fixed period has a different DC component according
to the duration of high level voltage pulse (also called duty cycle). PWM operation can be
seen also in Figure 6.
For the rest of the entities that managed motors, the same H-Bridge circuit was used.
Since hip motor and the rest of I-Droid01 motors, except wheels’ motors had a limited angle
of rotation, a mechanism is needed to know what is the exactly position of the motor at any
time. This issue was solved using an encoder, composed by a black disc with small holes
(moved together with the axis of rotation, marking the valid motor movement. A LED diode
and a phototransistor were used to illuminate the holes and generate a digital square signal,
making the microcontroller able to count holes and stop the motor when a certain value is
reached. In the case of wheels’ motors encoders, the same circuit was used to compute
wheels’ angular speed.
Regarding the ultrasonic circuit, transmitted ultrasonic burst were generated internally in
the microprocessor and delivered to the ultrasonic transmitters via a GPIO. The ultrasonic
receiver circuit was based on operational amplifiers (two LM324 integrated circuits) to
amplify received ultrasonic burst and differential comparators (LM339 integrated circuit) to
compare received bursts with the emitted one. Then, time of flight and the associated
distance with the obstacle was computed by the microprocessor.
2nd entity. Arm Controller
Board front and back pictures
Coupled sensors
•
•
•
•
•
•
•
Coupled actuators
4 prototype board
GPIO (set as digital
inputs).
2 prototype board
analog inputs.
Temperature sensor
(LM35).
Left arm motor
encoder.
Right arm motor
encoder.
Left arm coupled
tool interface.
Right arm coupled
tool interface (fixed
hand).
•
•
•
•
•
•
4 Base LEDs.
4 prototypes board
GPIO (set as digital
outputs).
Left arm motor.
Right arm motor.
Left arm coupled
tool interface.
Right arm coupled
tool interface (fixed
hand).
µC brand
µC model
Crystal frequency
Freescale
MC68HC908AP8
20 MHz
RAM Flash
1 KB
8 KB
Table 2. I-Droid01 Arm Controller details.
15
Figure 6. H-Bridge circuit schematic and PWM operation.
Arm Controller entity was in charge of the management of arms motors, following the same
procedure explained for the other motors. Moreover, since each I-Droid01 arm ends with a
certain removable tool, a I2C link was used to communicate with the entity in charge of the
corresponding tool. At the end of I-Droid01 collection, left arm was able to be
complemented with a universal remote control (see 8th entity. Universal Remote Control
module for details); a part from a LED torch and a tray that don’t need any management
from the microcontroller. Right arm was complemented with a fixed hand (see 9th entity.
Hand Controller for details).
It looks like Arm Controller entity was designed to manage those sensors and actuators
that were unable to fit in other entities boards. This is the case of the 4 LEDs of the base.
As its name specifies, these LEDs are placed on I-Droid01 base, near its wheels, so logic
says that they should be managed by Base Controller entity, not this one. The same
happens with the temperature sensor, which does not belong to any specific entity.
Finally, since Arm Controller microcontroller was able to deal with analog input signals
(using an integrated ADC) and it was also the microcontroller with less I/O usage, remaining
GPIO and analog inputs were mapped directly to the prototypes board placed on the
battery box, allowing user to build own circuits and interface with them through the robot,
via the programming tools.
Figure 7. I-Droid01 removable tools and its location to be used with the robot.
16
3rd entity. Head Controller
Board front and back pictures
Coupled sensors
•
•
•
•
Coupled actuators
3 head microphones
(eyes and back
head).
Touch capacitive
sensor.
Head tilt motor
encoder.
Head pan motor
encoder.
•
•
•
•
6 eyes LEDs (red,
yellow and green for
each eye).
2 ears LEDs.
Head tilt motor.
Head pan motor.
µC brand
µC model
Crystal frequency
Freescale
MC68HC908AP8
20 MHz
RAM Flash
1 KB
8 KB
Table 3. I-Droid01 Head Controller details.
Head Controller entity was, in fact, the first one delivered in the I-Droid01 collection. By
itself, this entity was able to listen to a sound like a clap and determine its direction of arrival.
This entity also managed both head motors, touch capacitive sensor and all head LEDs.
Sound Follower procedure was in charge of processing the signals from the 3 microphones
of the head, in order to determine the source of sounds [6]. The procedure for recognizing
the sound source was quite simple: When a sound reached I-Droid01 head, an electronic
circuit checked whether it came from rear microphone or frontal ones. The circuit was
based on Operational Amplifiers (LM324/LM358 ICs) to amplify the voltage coming from
the microphones and differential comparators (LM339) to check the origin of the sound. To
determine if sound direction was front left or right, another decider circuit was used.
In order to get pulsed data from touch sensor, a change of frequency of a certain known
signal was measured. Change in frequency was due to the change of the capacitance
detected by the sensor. This task was performed by a secondary board which was one
without any kind of microcontroller, just for testing head LEDs, tilt motor and touch
capacitive sensor. The circuit to make touch sensor work was reused for the final Head
Controller board, leaving the rest of the circuits unused.
Figure 8. I-Droid01 head test board.
17
4th entity. Motherboard
Board front and back pictures
Coupled sensors
•
3 buttons chest
keypad.
Coupled actuators
•
16x2 LCD Display
(Xiamen Ocular
GDM1602H-FE(B)GBS/2P).
µC brand
µC model
Crystal frequency
Freescale
MC68HC908AP8
20 MHz
RAM Flash
1 KB
8 KB
Table 4. I-Droid01 Motherboard details.
Motherboard entity, as it happens with all microcontroller-based systems, was the core
entity of I-Droid01 [6]. This entity was in charge of managing the functions of the display
and the keypad, sharing its functionality over all the rest of entities. Motherboard also had
the access to setup I-Droid01 basic functions and settings. Motherboard entity was also
acting as the master in the I2C communication; for that reason, the way to connect them
was to create a radial connection among devices with the Motherboard entity in the center.
Moreover, this entity managed the I-Droid01 stand-by mode automatically, allowing to
disconnect idle entities for energy saving purposes.
As it has been seen in previous subsections, Motherboard entity was the one that
distributes the power supply coming from the batteries to the rest of the entities. It also was
in charge of converting the Logic Voltage from 4.5 V to 3.3 V, to power the rest of entities
boards. In order to do that, a circuit based on the LM2734 step-down DC-DC regulator,
from Texas Instruments, was used. It allowed local DC-DC conversion with fast transient
response and accurate regulation for an output current of 1 A occupying the smallest PCB
area possible.
Motherboard entity board also contained a small analog circuit, based on the LMV354
integrated circuit from Texas Instruments (Low voltage rail-to-rail output operational
amplifier) to handle analog signals.
18
5th entity. Brain & Vision
Board front and back pictures
Coupled sensors
•
Coupled actuators
CMOS Camera
(Pixel Plus VGA
P02030NC).
-
µC brand
µC model
Crystal frequency
SDRAM
Flash
Kernel
Freescale
MC9328MXLVF15
150 MHz
16 MB
16 MB
Linux 2.4
Table 5. I-Droid01 Brain & Vision details.
If Motherboard entity was I-Droid01 core, Brain & Vision was its brain and the responsible
of the coordination among all the entities. This entity, as its name states, also was
responsible of managing the CMOS camera, placed in front of the head [6].
The microprocessor was a DragonBall i.MXL from Freescale manufacturer. It had an ARM
architecture of 32 bits. Samsung was the developer of the RAM memory chip
(K4M283233H-HN75). Strictly from the electronics point of view, the memory device was
constituted by four banks of 1048576 words of 32 bits each one.
Regarding Flash memory (S25FL216K0PMFI011 integrated circuit), also with a capacity of
16 MB, was manufactured by Spansion (company linked to AMD and Fujitsu). In this case,
memory allocation is divided in two banks of 8388608 words of 16 bits. This Flash memory
device had also a 16-bits access bus, which provided an access time of 100 ns.
The electronic card of the Brain & Vision entity was equipped with a USB transceiver
working at 12 Mbps, which allowed the programming of the card itself. Moreover, an UART
interface was also present to have a serial communication between Brain & Vision entity
and a PC. These ports were physically placed at the bottom of the I-Droid01 bag, just next
to the temperature sensor.
The I-Droid01 “eye” was a CMOS camera (see Figure 9), with a total number of 307200
square pixels of 5.2 µm side. The data from the pixels of the sensor were pre-processed
by the electronics of the camera itself. These electronics also applied some image
corrections such as edge enhancement, color correction, white balance and exposure
control. The output format of the image was according to the standard RGB 5:6:5.
19
Although CMOS camera resolution was VGA (640x480 pixels), only static pictures were
taken at its full resolution. Pictures taken were stored in JPEG format inside Flash memory,
with a total storage capacity of 50 pictures. In order to retrieve pictures through a computer
the USB connection was used. For continuous streaming video (PC/Mobile remote control
or Java applications developed by the user) the maximum allowed resolution was 160x120
pixels. Brain & Vision entity video processing engine was able to deliver only 2 frames per
second at this resolution.
Figure 9. I-Droid01 CMOS camera electronics board.
6th entity. Bluetooth
Board front and back pictures
Coupled sensors
Coupled actuators
-
-
µC brand
µC model
Crystal frequency
Flash
National
LMX9830A
13 MHz
16 KB
Table 6. I-Droid01 Bluetooth details.
The communications entity was a simply Bluetooth to RS232 Serial adapter, connected via
this interface with the Motherboard entity [6]. It was a Class-2 Bluetooth 1.2 product with a
coverage of at least 10 meters. This Bluetooth module is still nowadays a RoboTech S.L.R.
commercial product, under RBT-001 brand name.
20
7th entity. Voice module
Board front and back pictures
Coupled sensors
•
Chest microphone.
Coupled actuators
•
Chest speaker
(8 Ω, 250 mW).
µC brand
µC model
Crystal frequency
Sensory
RSC4128
40 MHz
RAM Flash
1 KB
1 MB
Table 7. I-Droid01 Voice module details.
Voice module entity was in charge to control I-Droid01 voice recognition and text-to-speech
(TTS) features [6]. Thanks to the Flash memory of 1 MB, this module had the capacity to
store up to 10 voice messages of 16 seconds of duration. As a safety issue, voice module
entity was able to identify certain voice parameters and create a biometric password. When
this function was activated, I-Droid01 could only be operated through voice commands for
the user who registered the biometric password. As a curiosity, ears LEDs behavior were
managed by this entity since these LEDs were used to indicate if I-Droid01 was ready or
not to listen to a command or it was expecting a complement of the previous command.
From the electronics point of view, this entity had an analog pre-processor circuit based on
the LM358 integrated circuit to amplify microphone captured voice. The rest of the circuits
presents on the board were just to support the microprocessor. This microprocessor was
designed to bring advanced speech I/O features to cost sensitive embedded and consumer
products. Based on an 8 bits’ microcontroller, the chip integrated speech-optimized digital
and analog processing blocks. The main features were to have accurate speech
recognition; high quality, low data-rate compressed speech and advanced music.
RSC4128 microcontroller had an on-chip 16-bit analog to digital converter (ADC) and a 10
bits digital to analog converter (DAC). An integrated analog comparator unit (ACU) of 4
inputs and 5 timers (3 general purpose, 1 watchdog and 1 multi-tasking) were also included.
The microcontroller operated at 3.3 V with a typical power consumption of 36 mW. In the
sleep mode, the power consumption was reduced by 10000 factor.
21
8th entity. Universal Remote Control module
Board front and back pictures
Coupled sensors
Coupled actuators
•
•
IR receiver
(TSOP322).
IR transmitter
(TSUS5400).
µC brand
µC model
Crystal frequency
Flash
Freescale
MC9328MXLVF15
16 MHz
16 KB
Table 8. I-Droid01 Universal Remote Control module details.
This entity was a simple infrared transceiver at 950 nm wavelength, enabling I-Droid01 to
receive and transmit infrared commands, like a TV remote control. Since this entity was a
transceiver, I-Droid01 was able to learn first all the commands from any remote control and
then transmit them as if it were the remote control itself. Those learnt commands were
accessible through programming environments. The microcontroller was only in charge of
that transceiver and the communication with the rest of entities via I2C.
9th entity. Hand Controller
Board front and back pictures
Coupled sensors
Coupled actuators
•
•
Hand motor
block control.
Hand motor.
µC brand
µC model
Crystal frequency
Flash
Freescale
MC9328MXLVF15
16 MHz
16 KB
Table 9. I-Droid01 Hand Controller details.
Hand tool is composed by a three-fingers hand of about 5 cm opening range. This hand
tool is placed at the end of right arm. Initially, I-Droid01 collection was designed to have
22
two identical arms, with the possibility of having removable tools at both arms end (see 2nd
entity. Arm Controller for the complete list). The main function of Hand Controller Entity is
to interface this hand with the Arms Controller entity, which actually expects a removable
tool, not a fixed motorized hand.
Using a very small microprocessor (the same than 8th entity), Hand Controller was in charge
of managing hand motor; allowing its movement and controlling when the motor had to be
turned off [6]. Hand motor has not any encoder, so a block control was used instead. This
block control just was a peak detector of a current demand from the hand motor, a fact that
happens when hand motor finds a certain impediment (an object inside the hand or hand
completely opened/closed).
2.3.
Original I-Droid01 software description
I-Droid01 software architecture was designed to be distributed among the entities
presented in the previous subsection, since each microcontroller was able to run a specific
part of the total running software. In general terms, I-Droid01 operating system was a Linux
distribution for ARM microprocessors over Linux 2.4 kernel. It was physically running on
Brain & Vision entity. In the high-level layer, there were basically two main processes
running in parallel [5]: System Controller (main process to control robot autonomous
behavior) and User Process (to run pre-loaded user programs).
Figure 10. I-Droid01 software architecture [5].
23
Starting with the System Controller process, it had a multithreaded architecture to manage
separately each one of the I-Droid01 behaviors (listed in subsection 2.1 Overview of the
full original system) and its basic functionalities. There were 9 threads running concurrently;
and they together implemented the I-Droid01 behavior. User Process environment was
managed in a separately process. The communication between processes was done using
software pipes.
In the low-level layer, the direct access on I-Droid01 sensors and actuators was
implemented directly on each hardware entity microprocessor. Thus, System Controller
process only had to worry about send commands and receive processed data from sensors.
The communication among entities, done physically through I2C bus, was managed by the
I2C manager, in order to schedule and multiplex bus requests done by each thread.
At the next paragraphs, some of the System Controller threads and User Process
environment will be presented, following the diagram shown in the previous figure. With
this explanation, I-Droid01 main aspects will be on the table, ready to be used as the basis
to present later in this section the new hardware and software requirements for the IDroid01 evolution. Moreover, this information can also be used to compare
I-Droid02 design with its predecessor.
From the previous diagram, a special thread can be distinguished over the rest: The main
thread called System Controller Thread. As expected, this thread was acting as the end
point of data flux among the rest of the environment and was actually the real brain of
I-Droid01.
As an important fact, System Controller Thread had an additional area that introduced
some randomness to its behavior. This zone was called Mood Manager, that was in charge
of the “emotions” of I-Droid01. In one direction, output information coming from the sensors
was treated as input information for Mood Manager, generating internally a particular mood.
This mood was at the same time input information of the next iteration of System Controller
Thread, causing a possible change in the I-Droid01 behavior for a certain set of commands.
As an example, I-Droid01 was able to deny a certain command from the user if its mood
was “tired”, according to the battery voltage level sensed. As some inconveniences, there
was the fact that current mood was not saved anywhere when I-Droid01 was switched off.
This causes that each time I-Droid01 was switched on the current mood was “happy”, and
it was very difficult to see the rest of moods since they needed many iterations of Mood
Manager to change. For example, to get I-Droid01 to the mood “angry”, it was needed to
insult it via voice command too much times, causing a big gap between I-Droid01 and
human emotions. The most frequented moods were “happy” and “tired”.
Another important thread from System Controller process was Voice Manager Thread,
allowing the I-Droid01 remote control via voice commands and also its communication with
the user using prerecorded phrased and separated words [6]. This thread was running
directly over Voice module entity, and the received voice command from the user were
then transmitted to System Control Thread via I2C link. Voice commands were structured
in a tree diagram structure (see next figure). This causes that each final command for
example, turn head left or record voice message needed a certain voice command path to
reach them. Similar voice commands were grouped in word sets and the way to reach
those word sets were to use certain voice commands, which acted as links between them.
When a word set was selected, only those phrases and separated words that belonged to
that word set were available for voice commands, reducing a lot the voice recognition
computing time.
24
The I-Droid01 possible answers was a single big word set of 140 phrases. The control of
this sentences was both in charge of System Controller Thread and User Process.
Figure 11. I-Droid01 voice commands complete word set (Spanish edition).
Ears LEDs were used to indicate if I-Droid01 was ready or not to listen to a new voice
command or was waiting for the user to complete a complex voice command.
25
I-Droid01, as it has been stated before, was able to be remotely controllable and
programmable, in order to override the autonomous behavior managed by System
Controller Thread during a certain period of time. When a command from a remote control
or a preprogrammed program from the user was running, User Process had full access to
System Controller Thread, including the received commands from remote control or
instructions coming from the program. Moreover, System Controller Thread was able to
provide direct sensors data to User Process.
A part from voice commands, there was a software tool able to control remotely I-Droid01
through a Bluetooth link. With this tool, called I-Droid01 PC Control, the user was able to
move any motor, turn on/off any LED, watch the state of certain sensors such as battery
voltages levels, manage voice messages or watch in real time CMOS camera video
streaming. This software tool, thus, was a real remote control able to control I-Droid01 robot
inside Bluetooth range of about 10 meters. Its GUI was simple and user-friendly. The Nokia
Symbian phones version of this software was called I-Droid01 Mobile Control.
Figure 12. I-Droid01 PC/Mobile Control software tool (Spanish edition).
Regarding programming environments, there were clearly two programming domains:
•
•
C-like embedded programming. Focused on rookie/downstream programming
users, in this programming environment user developed small sequential programs
that were compiled and run directly for I-Droid01 Brain & Vision entity ARM
microprocessor. These small routines were developed using C programming
language, since it is Linux native.
External Java tools. Focused on advanced programming users, this programming
environment had the objective of developing programs in Java, running on a
computer, that interacted directly with I-Droid01 through the Bluetooth link.
In order to invite users with zero knowledge about C to go inside C programming world, an
IDE software tool called I-Droid01 Visual C-Like Editor was provided. In this IDE, the way
to code programs was by using a graphical interface to generate the routine flow diagram,
instead of using the classical screen for text editing that was also supplied. To code
graphically, IDE tool provided a library with graphical icons that represented instructions
(pieces of C code). These graphical icons were sorted in categories, such as I-Droid01
body parts or basic language instructions. The way to create user programs was as simply
26
as put sequentially the required graphical icons in the corresponding execution order; C
code was automatically generated by IDE tool translating directly from the flow diagram.
Anecdotally, in the graphical icons for basic language instructions category, there were
those C structures which have a direct C code translation; such as While loops, Sleep,
function call, etc. but also a graphical icon called Wait for, which did not have a direct simple
translation in C code. As its name specifies, this graphical icon was used to stop the
program execution until a defined characteristic from a certain sensor changed its state (for
example, wait for temperature sensor going below 23 ºC). This behavior was translated by
the IDE as an active loop wait.
When user ended preparing the program, IDE tool included the necessary tools to
download the source code files into I-Droid01 Brain & Vision entity Flash memory, by using
the Bluetooth link (I-Droid01 PC Control tool was needed to be connected to the robot at
this step). Then, from the same IDE tool, the ARM C compiler was called. The compiler
was run directly by the I-Droid01 Brain & Vision microprocessor. Finally, also from the IDE
tool, by using a voice command or from remote control tool this preprogrammed software
was able to be launched.
I-Droid01 collection included some test programs to be used by rookie users as example
for developing its own custom programs.
In the external Java tools case, no IDE software tool was provided. Instead of that, all
needed Java classes to interact with I-Droid01 and to manage directly sensors data were
provided. As it has been explained before, all sensors data managed by System Controller
Thread were available to the user control plan via User Process. All provided software tools
by RoboTech S.L.R. (I-Droid01 PC Control and I-Droid01 Visual C-Like Editor) can be
considered part of this group, since both tools were developed in Java language and used
the Bluetooth link to interact with I-Droid01.
Figure 13. I-Droid01 Visual C-Like Editor IDE tool.
27
2.4.
The I-Droid01 evolution. New hardware and software requirements
I-Droid01 product could be considered one of the first attempts to reach low cost robotics
with artificial intelligence to the general public. Although I-Droid01 was more focused on
education rather than consumer entertainment, it can be compared with AIBO (Artificial
Intelligence Robot), a product by Sony that was released in 1999. AIBO was strictly focused
on the consumer entertainment, through its robotics pets of which most were dogs.
However, its elevated cost (around 2000 $, even though AIBO 1st release in 1999 came up
to 2500$) was certainly a handicap at the time of purchase. The robots price was not a
problem for many universities who adopted it for educational purposes. When talking about
I-Droid01 product, its collection format was an attempt to get further penetration in the
consumer market by spreading its total cost (a little less than 1000 €) along 2 years and
also by motivating final customers to enter the world of programming and robotics.
I-Droid01 was conceptually inspired in humanoid robots that appear in “I, Robot” movie as
it is told in the first fascicle of the I-Droid01 collection. Despite the good intentions of selling
I-Droid01 product as a last-generation robot, the reality is that the product has not met all
the expectations. Generally speaking, the main operation of the robot differed too much
from the prototype shown in advertising. Moreover, it became obsolete much earlier than
expected, in particular the included software, making impossible to take full benefit of the
money invested in the collection. Having a look on the Internet, the consequences are
evident: On one hand, there are people who have tried to sell I-Droid01 product completely
assembled or by parts. On the other hand, other people have developed modifications to
its original hardware design to improve its weaknesses or simply for tuning purposes.
I-Droid02 Project main motivation has emerged from this problematic. In the next
paragraphs of this subsection, the main disadvantages of I-Droid01 design in terms of
undesired operation and obsolescence will be presented, taking as a reference I-Droid01
hardware and software description previously stated. The improvement of those remarks,
together with the main specifications to be fulfilled by I-Droid01 initial design, will form the
complete package of requirements to be accomplished for I-Droid02 robot. This thesis
includes a significant part of the proposed I-Droid01 evolution.
Power supply system
The weakness point of the hardware design is definitely the power supply system, which
completely affects I-Droid01 normal operation. I-Droid01 was designed to be powered by
8 alkaline batteries of standard AA size. Obviously, the fact of not including any
rechargeable battery in the design reduced production costs but at the expenses of
increasing the user costs in buying alkaline batteries. In addition, since I-Droid01 was
designed to be autonomous, the design does not consider the possibility of plugging the
robot to any AC plug but to use NiMH rechargeable batteries. For this reason, an external
NiMH battery charger was supplied at the end of the collection.
Therefore, the only way to power I-Droid01 was to buy alkaline batteries or, in order to
reduce expenses, by using NiMH rechargeable batteries. In that terms, both common
alkaline and NiMH rechargeable batteries provide a capacity about 2000 mAh. When using
NiMH rechargeable batteries, a part from the hassle of having to open the battery
compartment any time that they need to be charged, the most important drawback is the
nominal voltage. Compared to a common AA alkaline batteries that provides 1.5 V, a AA
NiMH rechargeable battery provides only 1.2 V. That reduces Logic Voltage from 4.5 V to
28
3.6 V and Motors Voltage from 7.5 V to 6 V. This voltage reduction has its consequences
since I-Droid01 hardware design took into account a nominal voltage of 1.5 V per battery
unit.
As Motors Voltage power supply was only used to power motors drivers’ circuits, the only
consequence of reducing nominal voltage is that motors work slower than expected. In the
case of wheels’ motors, they have just the minimum necessary strength to be able to move
the robot, something that can be annoying and impractical. In the case of Logic Voltage
power supply, its voltage reduction could be more serious since integrated circuit LM2734
step-down DC-DC regulator needs a minimum voltage of 3 V to work, according to its
datasheet. Regarding voltage conversion efficiency, a nominal input voltage of only 3.6 V
implies a considerable reduction compared with the efficiency for 4.5 V input voltage.
As it can be seen, in both cases operating margin voltage is considerably reduced since
nominal voltages provided by NiMH rechargeable batteries are closer to the minimum
operating voltages. Comparing the discharge curves of both alkaline and NiMH batteries
[7], it can be seen that NiMH rechargeable batteries could reach 1V threshold long before
the end of the discharging cycle, reducing the operating range of the battery and, thus, the
autonomy of the system.
Figure 14. Duracell and RS AA Alkaline vs NiMH batteries discharge curves [7].
NiMH rechargeable batteries, as it happens with other technologies like NiCad, are affected
by memory effect. This phenomenon reduces the charge capacity of those affected
batteries when they are charged without being completely discharged. Adding to the
memory effect the fact that NiMH rechargeable batteries have a high self-discharge. After
a long period of time they have lost more than 50% of their capacity, reducing autonomy of
the system by the same percentage. As a conclusion, the use of NiMH rechargeable
batteries to power I-Droid01 was a good solution to save money at the beginning. However,
if the robot was not used for a long period of time, NiMH rechargeable batteries needed to
be fully discharged and charged again, in order to avoid memory effect. As it can be seen,
it is necessary to reconsider the power supply of the robot.
Different battery technologies can be found on the market [8]. Each one offers advantages
and disadvantages depending on the applications. Most common figures of merit that can
be used to compare different battery technologies are energy density per unit weight and
energy density per unit volume, a part from knowing what phenomena like memory effect
are affecting more or less each battery technology. An ideal battery would be the one with
both energy efficiencies as high as possible: a very light, small but powerful battery.
29
Starting from scratch, since I-Droid01 is a lightweight terrestrial autonomous system and
the space where batteries are placed is small, a battery with high energy efficiency per unit
volume should be necessary. Weight is also fundamental because the higher the weight
the higher the work that the robot will have to perform to move itself. This factor
automatically discards lead-acid rechargeable batteries, which are the cheapest but
provide an energy efficiency per unit volume of 100 Wh/m3 [8]. Given that its weight is
considerable, its energy efficiency per unit weight is only about 41 Wh/kg. Alkaline batteries
provide high energy efficiencies (110 Wh/kg and 320 Wh/m3) but they are not rechargeable,
thus, next battery technology with both energy efficiencies as high as possible is Li-Ion
(128 Wh/kg and 230 Wh/m3). At the start, a Li-Ion battery is more expensive than other
checked technologies, but a part from its energy efficiency a Li-Ion battery is almost not
affected by memory effect so there is no problem in having to recharge it if it has not been
used for a long period of time.
Communication with the external world
I-Droid01 used a Bluetooth link for remote control and high-level programming. Through a
serial connection, Linux operating system was able to be directly flashed on Brain & Vision
entity. Finally, the portion of Flash memory storing photos taken by the camera was
accessible through a USB connection.
A Bluetooth Class 2 link does not have a very high range (about 10 meters when line of
sight is available) even though it can provide a real transfer rate of about 2.1 Mbps
consuming only 2.5 mW of power. However, some remote-control applications such as
sending simple commands for certain concrete actions do not require a high transfer rate
but high range or behaviors more suited to indoor environments, such as buildings. Only
CMOS integrated camera video streaming could require a high throughput; however, a
transfer of 2 fps at 160x120 pixels resolution in jpeg format should not require enormous
bandwidth amounts.
Aesthetic aspects
Although aesthetic should not be the prevailing requirement of I-Droid01 hardware design,
sometimes there are some things that could be improved. In I-Droid01 case, CMOS camera
cables exiting from the head and power cable, which brings power from batteries receptacle
to Motherboard entity. Next figure shows both cases. There was enough space to put
CMOS camera cables internally through the neck and power cable connector could be
placed in a more discreet place.
Figure 15. I-Droid01 external cables aesthetical failure.
30
Voice recognition system
Voice recognition system did not present any technical fault apart from the fact that
sometimes it was difficult to recognize long voice commands. Nowadays, this kind of voice
recognition systems that groups commands in word sets and they are just able to recognize
commands placed on the current selected word set, are obsolete. Current systems are
focused on detect vowel phonemes and then construct words and sentences, not to simply
correlate known sounds with received ones. For this reason, nowadays voice commands
systems (such as those that are placed on smartphones) are able to recognize any word.
A voice command system software should detect first the acoustic model. A voice
command coming from a microphone, from a mobile call or whatever another media
sounds slightly different. Thus, if the distortion introduced by the channel is known it can
be mitigated digitally through signal processing. Next step is to identify language spoken
and its dialectal variety. Finally, the correct way to construct words and sentences taking
as a reference known correlated vowel phonemes must also take into account how
sentences are constructed in the recognized language and how this construction could vary
according to dialects or other situations. This kind of strategy is the one that should be
followed for the future I-Droid02 voice recognition system.
I-Droid01 entities
As explained before, I-Droid01 was divided in several entities with its own microprocessor,
each one in charge of a very specific part of the robot. Since I-Droid01 product was
distributed as a collection, the fact of dividing it in independent entities made possible that
it could be used even though it was not completely assembled. However, if reconsidering
hardware design was a feasible option, this model of distributed responsibilities could also
be reconsidered.
Nowadays there are available on the market faster boards that are able to handle many
analog and digital sensors at the same time. Concentrating most part of the I-Droid01
entities described before in a single board facilitates programming and maintenance work.
In I-Droid01 case, it was not possible to modify low-level programming code of any of the
entities. Particularly, it was observed that part of the code responsible of driving wheels’
motors behavior (placed in the Base Controller entity) did not take into account possible
differences in rotational speed between both motors. The final result was that I-Droid01
was not able to move in a straight line since slower rotational speed was not compensated.
Provided software tools for remote control and robot programming
I-Droid01 PC Control and I-Droid01 Visual C-Like editor, provided by the collection became
obsolete after a short time of ending the collection. The reason is that they were developed
in Java version 2, but using some classes interacting with Windows operating system that
nowadays are deprecated. I-Droid01 Mobile Control was only compatible with Nokia
Symbian operating system, nowadays also an obsolete OS.
The idea of using multi-platform programming languages like Java must be accompanied
at the same time by ensuring the compatibility with the highest possible number of
operating systems. In a hypothetical new software design, software tools for remote control
and robot programming should be completely unbound from the hosting operating system,
since they are connecting using a certain network link to a device controlled by another OS.
31
3.
Methodology / project development
As the main objective of I-Droid02 Project, I-Droid02 humanoid robot should be able to
perform at least the same tasks as its predecessors, but improving their performance
and/or their execution time. Moreover, according to I-Droid02 software layers’ architecture,
new high level tasks according to other new requirements should be able to be included
without altering the basic behaviors.
In this section I-Droid02 full hardware and software design will be presented. Since almost
all sensors and actuators from I-Droid02 are the same as its predecessor only new
hardware parts will be described in this section. The hardware design description can be
understood better looking at the complete set of electronic schematics, found in the
Appendix 1 of this document. For the software design implementation, the full source code
can be found also in the appendices.
3.1.
Introducing I-Droid02 hardware and software design
I-Droid02 requirements are quite similar as I-Droid01 were in the past, but in the I-Droid02
case there are two new conditions that make the difference: On one hand, I-Droid02 must
fix all design errors, dysfunctions and aesthetical issues but without modifying too much
the skeleton of the original robot. From outside, I-Droid02 should look as similar as its
predecessor since the important changes are done in the internal circuitry.
On the other hand; new design should be as flexible as possible, allowing straightforward
renewal of operative hardware parts that may become obsolete, such as microcontrollers
boards. Sometimes it could be difficult to enhance basic electronics circuitry for sensors
data analog processing (microphone amplifiers, motors driver circuitry, etc.), but this is not
the case of microcontrollers boards. Maybe 20 years in the future, there is a board on the
market able to perform ten times more tasks for the same size. This kind of boards should
be easy to renew if necessary. That is not the case on the original I-Droid01 design, where
microcontrollers boards and interface circuitry were in the same board, preventing to
change microcontrollers and keeping part of the circuitry at the same time.
Looking at the I-Droid01 hardware description done in the previous section of this thesis,
especially in the last subsection where the dysfunctionalities of the original design are
marked, it looks like the best choice is to redesign hardware structure from scratch,
maintaining only hardware parts actually acting as sensors or actuators. Designing from
scratch is the best to:
•
•
•
Have full control of sensors and actuators access, without intermediates.
Connect them to the new microcontrollers in the most efficient way, according to
their requirements.
Separate clearly both kinds of circuitry: The one to power, access and trigger
sensors and actuators and the one that belongs strictly to the microcontrollers.
Another very important factor is the microcontrollers structure. I-Droid01 was designed to
have a decentralized structure, by dividing the robot in 9 independent entities, each one in
charge of a part and every entity connected using a serial communications bus. This way
of designing has sense as I-Droid01 was distributed progressively in a collection, allowing
32
final users to take benefits of the product as the same time they were assembling it.
However, when having full product available, hardware design can be done without taking
into account this requirement.
Looking at the I-Droid01 basic skeleton, it can be seen a full set of hardware parts acting
as sensors and actuators that should be scanned or triggered respectively when necessary.
In this case, what is needed is some kind of programmable device with a big number of
input/output ports, to control all sensors and actuators. On the market, there can be found
many microcontrollers with a high number of input/output ports, called GPIO (General
Purpose Input Output). As it names states, a GPIO port can act as an input or output,
depending on how the microcontroller has been programmed.
Sensors like capacitive touch and actuators like a LED or a display are digital, thus, can be
managed directly by a GPIO. However, many other sensors (temperature, microphones,
etc.) provide analog data. For this reason, sometimes an Analog to Digital Converter (ADC)
is included inside the microcontroller. The cheapest way to provide analog data out, without
using any Digital to Analog Converter (DAC), is by using PWM (explained in the previous
section) with a low-pass filter in cascade if necessary.
Input/output access to the elements requires a microcontroller working in real time. In
general, a certain microcontroller can be designed to work in real time or to have a high
computation capacity. Unfortunately, both targets cannot be achieved by using the same
design. For that reason, I-Droid02 design is focused in dividing the computational capacity
in two different microcontrollers: The first one to have I/O access to most part of robot’s
sensors and actuators. To execute complex tasks, in a high-level environment, a second
microprocessor with bigger memory and faster clock frequency will be used. In this
scenario, initial microprocessor is only an automaton, in charge of obtain sensors data or
trigger actuators when necessary in a low-level environment; while faster microprocessor
handles all data and it is who really takes decisions. In terms of programming, the fact of
only having two microcontrollers managing different environments facilitates the sharing of
responsibilities and code maintenance.
Original I-Droid01 contained Brain & Vision entity acting as the brain microprocessor, while
the other entities were just automatons. For this reason, this entity is the only one that could
be maintained in the new I-Droid02 hardware design. However, on the market there can
be found faster microcontrollers, with more storage memory, that occupies the same space
as Brain & Vision entity board.
Following subsections will be devoted to describe in full detail I-Droid02 hardware and
software details. In 4th section, the results of this design can be found according to the
hardware and software design.
3.2.
I-Droid02 full hardware design
In order to structure I-Droid02 hardware design, it has been divided in several parts that
can be summarized as follows. Then, each one will be detailed:
•
Power supply system: An explanation of the new battery and other circuitry used to
power I-Droid02, according to its power needs and also enhancing I-Droid01 power
supply system following described requirements in previous section.
33
•
•
•
Basic sensors and actuators circuitry: Each sensor needs to be powered and, in
some cases, the voltage or current variation containing sensors data must be
electronically processed before connect this signal to a microprocessor’s GPIO.
This kind of circuitry and also motors driver circuits, which do not contain any kind
of “intelligence”, are described in this section.
Sensors and actuators interface microcontroller: This part contains a description of
the microcontroller used to handle I-Droid02 sensors and actuators.
I-Droid02 brain board: As its name states, I-Droid02 central microcontroller, with
similar structure as I-Droid01 Brain & Vision entity, is described here.
A part from the above, in the I-Droid02 hardware design it has been included a simple
remote control working at the ISM 433 MHz frequency. It will allow to convert I-Droid02 in
a remote-control toy. The idea is to have a remote control similar to I-Droid01 PC Control
software tool provided for I-Droid01, without needing any smartphone or PC. This remote
control will be described at next subsection.
Finally, before start with the I-Droid02 hardware description, it is important to remark that
I-Droid02 is not a commercial prototype but a proposed ad-hoc solution to update I-Droid01.
Power supply system
Power supply system is an essential part of the hardware design. As discussed in the
previous section, battery technology best choice is Li-Ion taking into account its dimensions,
weight and energy efficiencies. When battery technology has been chosen, next step is to
dimension the battery regarding some terms, like nominal voltage, maximum peak current
and capacity.
Starting from nominal voltage, a single Li-Ion battery rechargeable cell has a nominal
voltage of 3.7 V. However, in most of its charging cycle the supplied voltage is above this
value, with a maximum of 4.2 V when the battery is fully charged. Finally, a Li-Ion battery
cell must not be discharged beyond 2.5 V. These details should be taken into consideration
when designing the electronic circuits.
Powering I-Droid02 with only one battery cell it is clearly not enough, even leaving aside
the voltage needed to power the motors, that need a minimum voltage of 7.5 V according
to I-Droid01 power supply design. Most microcontrollers boards found on the market need
to be powered with at least 5 V. By putting two Li-Ion cells in series, nominal voltage rises
to 7.4 V (8.4 V peak value). Comparing these values with the ones provided in I-Droid01
initial design, it looks like using two cells is enough to power I-Droid02. However, it had
been observed that I-Droid01 motors performance was clearly bad when Motors Voltage
was a half of its nominal value. DC motors absolute maximum voltage is usually a high
value, which implies that it is not too much restrictive. The most important thing to take into
account is to ensure that dissipated power is not higher than a certain value, with the risk
of burning it. For this reason, in order to make I-Droid02 motors move faster, a third Li-Ion
battery cell in series is required. Finally, nominal voltage will be settled at 11.1 V (12.6 V
peak value).
Regarding battery maximum peak current, it is important to remark that motors need a
certain amount of current (higher than 1 A in the case of wheels’ motors) to start. This value
is significantly higher than total amount of current needed for I-Droid02 circuitry. In most
34
cases, battery packs of more than one cell bring a protection circuit coupled. This protection
circuit is there to prevent battery overcharge (charge the battery more than 12.6 V in this
case), battery over discharge (avoid battery voltage going below 7.5 V in this case) and
also over draining and short-circuits. The over drain current protection, thus, have to be
considerably higher than 1 A.
Finally, the last important battery parameter is its capacity. Capacity is an energy
measurement usually given in mAh or Wh. Usually Li-Ion batteries capacity is maintained
at a constant value until it is about to reach the end of their useful life (it can be charged
about 1200 times). The capacity of the battery will determine I-Droid02 autonomy time,
which at the same time will also depend on its peak consumption. Motors are the most
consuming parts, so autonomy depends on the robot movements.
In this stage of the I-Droid02 hardware design, it could be difficult to guess the needed
battery capacity in order to surpass original I-Droid01 autonomy. Nevertheless, it is
possible to find a hint by analyzing the capacity of original I-Droid01 power supply system
which was near 2000 mAh in case of using alkaline batteries. Therefore, a current capacity
equal or higher than 2000 mAh should increase I-Droid02 autonomy if at least total
consumption is the same as original I-Droid01 one.
Following previous requirements, the chosen battery to power I-Droid02 is described in the
next table:
Pack name
Li-Ion 18650 Battery
Pack manufacturer
BatterySpace
Cell name
ICR18650B4
Cell manufacturer
LG
Number of cells
3
Voltage (nominal)
11.1 V
Voltage (peak)
12.6 V
Voltage (cut-off)
7.5 V
Capacity
2600 mAh
Discharge rate (max)
4A
Dimensions
70x55x20 mm
Weight
150 g
Table 10. I-Droid02 battery for power supply system.
35
Equipping I-Droid02 with this battery means a slight reduction in the total weigh, since
original alkaline batteries weighted about 200 g. Thanks to its dimensions, it fits perfectly
in the old batteries space. In terms of capacity, its 2600 mAh at nominal voltage of 11.1 V
gives a power capacity of 28.86 Wh. Its maximum discharge rate of 4 A allows peak
currents of sufficient strength to turn motors on, especially wheels’ motors. Finally, its cutoff voltage coincides with nominal I-Droid01 Motors Voltage, so that in the worst case
motors performance will be the same as in the original design.
The vast majority of circuits involved should not be powered at 11.1 V. Starting from
microcontrollers boards, which usually work at 5 V, most part of the circuits that connect
them with sensors and actuators will be powered at the same voltage. For this reason, it is
logical to include a voltage regulator to obtain 5 V from the 11.1 V rail. In fact, it is the same
concept applied originally in I-Droid01 Logic Voltage power supply system, except that now
the power source is the same. With this design, optimal performance of both I-Droid02
logical and motors circuits is achieved, as voltage regulator will be able to provide 5 V
inside full battery voltage range (12.6 V – 7.5 V).
To choose a voltage regulator, a cheap integrated circuit such as L7805cv manufactured
by STMicroelectronics could be a solution. However, linear voltage regulator family circuits
have very low power efficiency when voltage difference between source and output is high.
If a load with a certain current consumption is connected, a linear voltage regulator will
drain at least the same current from the source plus the needed current to operate itself,
which can be usually neglected. For small currents (1 mA for example), load power is 5
mW and the drained one from the battery is 11.1 mW. Needed power from the source is
more than double, but this value is not much significant if the total power consumption is in
the order of 1 W. In the case of I-Droid02, if a linear voltage regulator is used to power most
part of the circuits, including microcontrollers boards, the total amount of required current
could be about 500 mA. In this case, drain power from the battery would be 5.55 W, when
electronics only demand 2.5 W.
In the case of needing significant voltage difference conversion in high power environments,
integrated solutions found on the market are called DC to DC converter. For I-Droid02
power supply system, the chosen DC to DC converter is S24SE from Delta manufacturer.
This integrated circuit transforms power up to 15 W, which is more than expected I-Droid02
total circuitry power. According to its datasheet, the transformation is done with an
efficiency of 90%.
In Appendix 1. I-Droid02 hardware schematics at Power supply system sheet the complete
schematic of I-Droid02 power supply system can be found. A part from details explained
before, it can be observed that main switch is connected so that either connects the battery
to the circuit or to its recharge jack connector. This solution avoids the need of opening the
battery compartment at any time that it needs to be charged. The same configuration
makes it possible to power I-Droid02 from an external power supply when battery is
disconnected from the circuit. This solves original I-Droid01 issue of not being able to plug
the robot on an electrical wall outlet. Finally, a green LED placed on the battery
compartment shows that the robot is powered on.
Sensors and actuators interface microcontroller
Original I-Droid01 hardware was designed to handle analog and digital data from sensors
and also to manage actuators by using digital signals (or analogically using pulse width
36
modulation). The new I-Droid02 keeps intact almost all sensors and actuators present in
its predecessor, so that the real-time microcontroller in charge of them must be able to
handle both analog and digital signals. As it has been specified at the beginning of this
section, there will be one microcontroller in charge of almost all sensors and actuators and
another one acting as the I-Droid02 brain MCU. This part of the subsection will be focused
on describe sensors and actuators microcontroller.
Having a look on the market in search of a microcontroller there is a complete, compact
and cheap solution able to handle with this task: Arduino. Since 2005, this company
develops open-source hardware, open-source software, and microcontroller-based kits for
building digital devices and interactive objects that can sense and control physical devices
[9]. Thus, their focus is precisely on develop microcontrollers boards able to control robots.
Arduino project is based on Wiring, a development platform created by Hernando Barragán
in his Master’s Thesis with the objective of creating low cost and simple tools for nonengineers to create digital projects.
The most important task that Arduino boards accomplish is to make simple what it is not.
It is possible to find on the market several microcontrollers brands, such as Atmega, that
provide microcontrollers able to handle with robots’ hardware parts. Arduino uses Atmega
brand microprocessors, among others, in their boards. The compromise of Arduino is
between complexity and power. The fact of using an Arduino hardware and software
solution, it is possible to simplify the development complexity at the expense of reducing
part of the microprocessors features. From this point of view, Arduino is not directly an
alternative of those microprocessors but a simplification layer of kind HAL (Hardware
Abstraction Layer), This layer allows a more straightforward access to its own connected
peripherals.
Arduino boards are simple to handle. They map in several connectors all GPIO provided
by the integrated microcontroller, which is usually from the Atmel company. These ports
are labeled by using numbers starting from 0 (analog input ports labels are preceded by an
A letter), overriding this way the labeling provided by the microprocessor’s manufacturer.
When it is time to program the behavior of a certain GPIO, instead of dealing with the
microcontroller registers, Arduino programming tool (called Arduino IDE) provides easy
routines to set pin parameters such as pin definition (input/output) or to read/write digital
values. Then, depending on the board, Arduino IDE translates these easy routines to the
corresponding code that handles the low-level registers programming.
Arduino, thus, acts as a simple encapsulation layer of a complex system, making it
accessible to the limited-knowledge users. However, at the same time this encapsulation
can be opened; because Arduino IDE allows to program the microprocessor directly by
using registers nomenclature provided in the microprocessor’s datasheet. In this way,
Arduino is still a good solution for experienced users which appreciate to have a more
accurate control, taking into account that Arduino routines are pretended to be used in any
environment, without knowing a priori which kind of hardware is connected at the pins.
Sometimes the same behavior can be obtained in a more efficient way by directly
accessing the microprocessor registers.
For I-Droid02 Project, the use of an Arduino board to handle the sensors and actuators
management is a key factor, because it provides a platform to interface with them easily,
in terms of programming. It also facilitates the design of the circuits in charge of interface
sensors and actuators with the Arduino board. In order to choose the appropriate Arduino
board, the most important thing is to know how many sensors and actuators will be
37
interfaced and how many ports are needed to interface with them. This information is
summarized in the next table, where robot subsystems are sorted in groups according to
their type. For each kind, table shows the number of instances, its group name, whether it
is a sensor or an actuator and the total number of needed GPIO pins to connect the parts;
distinguishing between input/output signals and also if they need analog processing (use
ADC for input signals and PWM for output signals). At the end of the table a summary of
total number of needed microcontroller pins and their type is provided.
#
Parts group name
S/A
# GPIO ports
#I
#O
Analog processing
1
Battery voltage sensor
S
1
1
-
1 ADC
3
Chest keypad buttons
S
3
3
-
-
1
Display
A
7
-
7
1 PWM
5
Head microphones
S
5
5
-
5 ADC
13
LEDs
A
10
-
10
1 PWM
8
Motors
Both
24
8
16
8 PWM & 1 ADC
1
Temperature sensor
S
1
1
-
1 ADC
1
Touch sensor
S
1
1
-
-
3
Ultrasonic sensors
S
3
3
-
-
Total GPIO ports needed: 55
Total ADC: 8
Total PWM: 10
Table 11. I-Droid02 sensors and actuators interfaced with Arduino board.
Additional details of the previous table:
•
•
•
Display needs a GPIO with PWM feature for contrast adjustment.
Although the total number of LEDs is 13, 4 base LEDs are controlled by 1 GPIO.
Each motor needs 3 GPIO: One for powering using PWM capability, one for
selecting its direction and the last one for encoder position sensor. The additional
ADC is for hand motor current consumption control (see explanation in 2nd section).
Comparing previous table elements with the ones specified in I-Droid01 entities tables
(which can also be found in the previous section), there are new added hardware parts and
missing ones. The additional new hardware parts will be justified in later, when basic
sensors and actuators circuitry to interface with Arduino board are presented. In the case
of missing hardware parts their functionality will be implemented directly in the I-Droid02
brain microcontroller board as will be explained later.
Another important detail that is shown in the previous table is the fact that all I-Droid02
motors will be powered using PWM capability. This fact implies to have a different speed
control over each motor. Initial I-Droid01 hardware design only contemplated PWM
capability for wheels’ motors.
38
As a summary, Arduino board in charge of managing I-Droid02 sensors and actuators
should be equipped with at least 55 GPIOs and provide at least 10 pins with PWM and 8
pins to be used with an ADC (usually a microcontroller integrates an ADC and is capable
of multiplex different pins, one by one when requested). Looking at Arduino products list, a
current board able to deal with this amount of GPIOs is Arduino Mega 2560 [10].
Figure 16. Arduino Mega 2560 as the I-Droid02 sensors and actuators microcontroller.
Arduino Mega 2560 is the Arduino board designed for projects over basic complexity [10].
It is based on the ATmega2560 microprocessor manufactured by Atmel, which has 70
GPIO pins. From these pins, 15 provides PWM capability and other 16 can be multiplexed
to internal ADC and be used as analog inputs. A part from that, other 8 pins can be used
to communicate 4 different serial devices via UART (Universal Asynchronous Receiver
Transmitter). UART0 is connected in the board to an ATmega 16u2 MCU that acts as a
serial to USB converter. This part is used because it is cheaper than them common FTDI
serial to USB solution. I2C bus and SPI are also supported on other pins.
Regarding electronic specifications, Arduino Mega 2560 works at a voltage of 5 V. It can
be provided directly through USB connection or it can be obtained by using an internal
linear voltage regulator that works with an input voltage range between 7 V and 12 V. Each
GPIO pin can drain a maximum DC current of 20 mA. MCU total DC drain current cannot
exceed 200 mA. Those absolute maximum values must be taken into account during next
design phase, where basic sensors and actuators circuitry is designed.
The ATmega2560 microprocessor works at a clock frequency of 16 MHz. This clock speed
is almost the same as the one of each microprocessor used in each I-Droid01 entity. In
terms of Flash memory, I-Droid01 entities had about 8-16 KB. Instead, Arduino Mega 2560
Flash memory is 256 KB, which it more than the total program memory in all I-Droid01
entities at the same time. As it has been stated before, this microcontroller cannot be used
to implement I-Droid02 brain since clock speed and total amount of memory are much
lower than required for that task.
39
In Appendix 2, the complete pin mapping of each sensor/actuator is shown. The pinout
design has been elaborated taking into account the following aspects: Allocating first those
hardware parts that require analog processing in the Arduino Mega 2560 provided analog
pins. Then, motor’s power pins must be allocated in those Arduino pins that support PWM
capability. At this stage, remaining sensors/actuators pins can be a priori assigned
anywhere, since each of them only need a simple GPIO to be managed. However, some
aspects must be taken into account before assign the remaining pins:
•
•
•
•
•
According to ATmega2560 microprocessor datasheet, there are some pins that can
be used by especial functions (for example as UART ports, but also to trigger
hardware interruptions and other).
Those pins that can implement especial functions (such as an UART interface, SPI,
etc.) cannot be used as a GPIO at the same time. Thus, it is important to establish
first which of these interfaces will be used or assign first those pins that act only as
a GPIO.
It is important to assign sensors/actuators whose state could be read/write
simultaneously in pins managed by the same register, as specified in the
ATmega2560 datasheet. Registers are of 8 bits each, so by simple reading/writing
them it can be get/set the value of 8 GPIO with only one instruction.
The pinout assignment will determine the physical location of hardware parts that
could be placed in a board next to the Arduino Mega 2560. The objective is to
facilitate as much as possible the Basic sensors and actuators circuitry assembly.
There are 16 analog pins available in the Arduino Mega 2560 board, but only 8 are
needed for this project. The rest of them can be used also as simple GPIO, since
the “analog” label only means that these pins can be multiplexed if necessary to the
ATmega2560 integrated ADC.
Basic sensors and actuators circuitry
After the microcontroller in charge of managing I-Droid02 sensors and actuators have been
chosen, next step is to connect them to the board. In most cases the sensors/actuators can
be connected directly to the Arduino Mega 2560 board. For the rest, some electronics are
needed. This is the case of a microphone; whose voltage variation is too weak and needs
amplification. Another example is a motor, which cannot be directly driven from a
microcontroller GPIO since its maximum output current (20 mA) is clearly insufficient.
Next paragraphs will be dedicated to explain all interface electronic circuits. It should be
noted that these circuits lack any intelligence (no programmable chips are used). In the
original I-Droid01 design, each respective sensor/actuator circuit was part of the
corresponding entity board. For this reason, it is not possible to reuse them in I-Droid02
design but they can be analyzed as a design reference.
Following Table 11 shown above, it can be distinguished the groups of sensors and
actuators which can be directly connected to the Arduino Mega 2560 board without any
complex circuitry structure. These elements are:
•
Battery voltage sensor: A sensor of this kind can be implemented by using a
resistive voltage divider directly from battery power supply node. The resistors
involved in the divider are designed according to these factors:
40
Output voltage from the divider cannot go over 5.5 V, which is the absolute
maximum input voltage value accepted by any ATmega2560 GPIO pin.
o Drained current from battery must be as low as possible, since only voltage
is the relevant value here. But at the same time, this current must be at least
10 times higher than ATmega2560 GPIO input leakage current (1 µA).
Chest keypad buttons: These three simple pushbuttons placed on I-Droid02 chest
will be connected each to a GPIO with a pull-up resistor. ATmega2560 has internal
pull-up resistors available in most of its GPIOs. The other terminal of each keypad
button goes connected to ground.
LEDs: Every LED is connected to a different GPIO through a resistor. The resistor
is dimensioned according to LED threshold voltage (Vγ) and the required current.
Eyes LEDs are integrated in a circuit that already contains the needed resistors.
Regarding I-Droid02 orange LED placed on top head, it will be connected to Arduino
Mega 2560 pin number 13, since it provides PWM (it allows to turn LED on with
different light intensities) but it also can be used as a diagnostics LED for the
Arduino Mega 2560 microcontroller board (it blinks when a compiled sketch is being
flashed, among other uses).
Temperature sensor: Temperature sensor is implemented by LM35 integrated
circuit. According to its datasheet, this device can work in several environments,
but the most common one is to provide a temperature range between 2 ºC and 100
ºC. To obtain this behavior it is enough to power the circuit at 5 V and connect the
output pin to an Arduino Mega 2560 analog input. LM35 device can also measure
negative temperatures, but a negative 5V power supply would be needed in this
case.
o
•
•
•
The sensors and actuators not listed above need some circuitry to operate. Following
previous table, the first hardware part of this kind is the display. In fact, this display
(GDM1602H-FE(B)-GBS/2P from Xiamen Ocular manufacturer) is not a simple component
but an integrated device that contains its own controller. This controller (a S6A0069
controller by Samsung) is in charge of printing characters on the crystal liquid LCD display
according to the instructions received from a communications bus. This device operates at
5 V.
Looking at the display device as a black box, it contains many pins used to send and
receive commands from the internal microcontroller:
•
•
•
V0: This pin is actually a power pin for crystal LCD contrast adjustment. Usually the
way to connect this pin is through an adjustable resistive voltage divider between
power supply and ground. The lower the voltage present in this pin the higher the
contrast intensity. In order to be able to adjust display contrast with Arduino
microcontroller, V0 pin can be connected to a GPIO with PWM. A low pass filter to
filter PWM signal, keeping only DC component, is mandatory. Finally, a BJT
transistor emitter follower is included, as GPIO output current is not enough to
power LCD segments.
RS. Register Select pin is used to differentiate between commands or data to be
read/write to display internal RAM. This pin can be connected directly to any
Arduino Mega 2560 GPIO pin.
R/W. Read/Write pin indicates if sent command is a read operation (there is a
command to read data from display internal RAM) or a write operation (the rest of
41
•
•
the available commands). In I-Droid02 Project data is never read from the display,
so this pin can be directly connected to ground to force always a write operation
command.
E. Enable pin is a high-active pin to signal data or command transmission. It can be
connected to any Arduino Mega 2560 GPIO pin.
DB7-DB0 bus. This 8-pin bus is used to send/receive data to/from LCD internal
microcontroller. Each pin has to be connected to a different Arduino Mega 2560
GPIO pin; however, the LCD controller has available a 4-pin mode where only DB7DB4 pins are needed to transmit 8-bit commands in two consecutive 4-bit
operations. In this design, this option has been used in order to save Arduino Mega
2560 GPIO pins for other purposes.
Figure 17. GDM1602H-FE(B)-GBS/2P LCD display blocks diagram with involved pins.
The next sensor group to be analyzed is the set of 5 microphones placed in I-Droid02 head.
In the original I-Droid01 hardware design there were only 3 microphones that roughly
predicted sound direction precedence. In order to improve the precision of this system, two
more microphones have been added (one in each ear). Now a four-elements array can be
used to predict sound direction in a 180º range from one ear to another. Last microphone
placed on the nape can be used both to determine that sound comes roughly from behind
and for voice recognition system, in order to remove original I-Droid01 microphone used
for that purpose, that was placed in the chest.
All microphones involved in I-Droid02 are electret microphones. This kind of microphones
are a type of electrostatic capacitor-based microphone, which produces a voltage variation
following the corresponding sound pressure wave. This kind of devices also include an
integrated small FET preamplifier that needs to be polarized using a 4.7 kΩ resistor
connected to 5 V power supply.
With this electronic configuration, the microphone presents an output DC voltage of about
half the power supply value. This voltage shows a small variation when a sound pressure
wave enters the microphone. The proposed solution is to amplify the signal outputting the
microphone, at expenses of adding thermal noise to the sample. In this design, the second
solution has been adopted, as it was done in original I-Droid01 design. ATmega2560
internal ADC is also used in other applications that could require the analog reference value
being at its maximum.
42
The complete circuit to process analog data from each microphone sensor is the following
one. It is important to remark that 5 V power supply comes from a 7805-linear voltage
regulator, in order to increase operational amplifier PSRR (Power Supply Rejection Ratio).
Figure 18. Microphones circuit schematic used in I-Droid02 design.
The amplifier circuit is based on the TLC272 dual operational amplifier integrated circuit. A
strong parameter of this device is to have a low thermal noise (25 nV/√Hz at 1 kHz
according to its datasheet). Moreover, it is designed to operate with unipolar signals
(signals that took values always higher than ground reference voltage).
The circuit is built around an inverting amplifier. Voltage outputted from the microphone is
coupled to the inverted voltage operational amplifier through a 2.2 µF capacitor. The value
of this capacitor avoids as much as possible the low-pass filtering of the audio signal
coming out of the microphone. Inverted voltage operational amplifier design allows easily
to add a desired DC component to the output signal simply by establishing it in the
operational amplifier positive node. This step is done by using a resistive voltage divider.
Using high resistors’ values, only 2.5 µA of current are drained from the power supply to
provide this reference voltage. At the same time this current is extremely high compared
with operational amplifier leakage current, which is about 2.5 pA (TLC272 input impedance
is about 1 TΩ). Finally, the voltage gain of this amplifier is 100, giving to the audio signal a
good level before being sampled by the ADC, without adding too much noise (about 2.6
µV of noise for an 11025 Hz input signal, according to TLC272 thermal noise figure).
Since TLC272 integrated circuit contains two independent operational amplifiers, a single
chip is used to amplify two microphones. Thus, only three chips are needed for the
complete circuit.
The motors are an essential hardware part of any robot. The I-Droid02 motors are directly
inherited from its predecessor. As explained before in this subsection, 3 GPIO pins are
needed from Arduino Mega 2560 board for each motor. First pin is needed to turn on the
motor. Another pin is used to, somehow, set the motor direction by selecting its polarity
(the motor turns in reverse when connected backwards to the source). Finally, the
microcontroller should know the relative position of the motor at all time, in order to stop it
to prevent the motor pass its route.
43
I-Droid01 design used an H-Bridge transistor circuit to drive the motor. This schematic
contemplates the use of at least 4 transistors per motor. I-Droid01 hardware was
assembled using SMD components which are very compact. However, I-Droid02 electronic
circuits will be assembled through hole elements, which are easier to handle and solder. It
can be done this way since I-Droid02 is not a commercial prototype (the industry opts for
SMD assembly because it is easier to find SMD components; they are more compact,
cheaper and easier to solder in an industrial chain).
In order to reduce the number of transistors needed in I-Droid02 hardware design, another
strategy is used to derive the current through the motor in one direction or the other using
a relay. A relay is a device used in electricity as a voltage controlled switch. On one side,
there is a coil that generates a magnetic field when some current crosses it. This magnetic
field attracts a metal contact causing it to switch from one circuit to another one. If the
current through the coil decreases, magnetic field disappears causing the metal contact to
recover the original position, thanks to a spring.
In order to implement motor’s rotation switching circuit, a relay with two magnetics
switchers from Finder manufacturer is used. Now, the circuit is as simple as connect the
motor between the outputs of both switches and leave each switch common node to be
connected to the power supply. Next figure shows this connection. Arduino Mega 2560
GPIO pin to control motor’s direction of rotation can be directly connected to the relay’s coil,
as drained current can be provided by GPIO pin.
Taken into account that GPIO’s maximum output current is clearly insufficient to drive any
DC motor, motor’s power has to be taken directly from the battery power supply, which
provides 11.1 V and a maximum drain current of 4 A. Then, in order to drive the motor a
BD135 transistor acting as a switch in common emitter configuration is used. This transistor
allows a maximum collector current of 1.5 A, so it is enough for the required purposes.
The last step to design in the motor’s circuitry is the encoder to sense the motor’s
movement. The encoder is implemented with a IR LED and a phototransistor separated
with a perforated disk linked to the motor axe. Two resistors are used to bias those devices.
The value of both resistors has to ensure having a voltage near to 5 V when phototransistor
is not illuminated and near zero when it is.
On the hand motor, since there is not encoder, a circuit to detect the blocking of the motor
movement at the end of the traveling is used. This circuit measures the current through the
motor using a shunt 2 Ω resistor. A motor’s end of stroke can be detected if the motor
requires more current (more strength) to continue the same movement. Zener’s diode is
used to prevent voltage increase more than 4.6 V, in order to avoid damaging the GPIO
pin.
44
Next both figures show the involved schematics to take control of each I-Droid02 motors.
Hand motor presents the specific design described above.
Figure 19. I-Droid02 motors and encoders electronic schematic.
Figure 20. Hand motor specific schematic.
According to Table 11, there are still two sensors to be connected to Arduino Mega 2560
board: touch sensor and ultrasonic sensors. Touch sensor is actually a metallic surface
that will change its capacitance when touched. The way to detect this capacitance change,
as I-Droid01 original design did, is by triggering a timer, that could be implemented by 555
integrated circuit configured in monostable mode. To keep the trigger shot for at least 1.5
s, the RC discharge circuit has been dimensioned with the values shown in the next figure.
45
Figure 21. Touch sensor triggering circuit used in I-Droid02 hardware design.
The ultrasonic system is the last to be described. This device is based on a set of
transducers able to convert an electrical voltage to a sound pressure wave and vice versa.
In fact, they act as a speaker or a microphone but in ultrasonic band. I-Droid02 ultrasonic
sensors are based on two transmitters and three receivers placed in front of battery location.
The concept behind the ultrasonic system is the same used in radar. A short burst is
transmitted periodically. The period of transmission is designed according to the distance
to cover and data updating time. A long period will cover higher distances but updating time
will be lower. The transducers (CUT10G1A-40 from ChinaSound manufacturer) are
designed to work to an ultrasonic frequency of 40 kHz so that it is not perceptible to the
human ear
To make the ultrasonic system work, we need to send each update time T a burst of at
least 10 frequency cycles of a 40 kHz signal and then no signal during the rest of period T.
This signal could be easily generated by using an Arduino Mega 2560 GPIO and an internal
timer, since signal carrier frequency of 40 kHz is much lower than microcontroller’s clock
frequency (16 MHz). In order to implement the receiver, it is needed to know the time
instants when the periodic signal emits the sine burst; then, when bounced signal is
received later, it is possible to compute time difference between received signal and
reference to extract finally the covered distance.
In the I-Droid01 original design the ultrasonic emitted signal was internally generated by
the Base Controller entity microcontroller. However, for I-Droid02 this idea has been
discarded due to the fact that this signal generation requires the use of an integrated timer.
Arduino Mega 2560 has 6 timers available, but almost all of them are used to trigger PWM
values or to count system events. Taking into account that a timer could be needed for
46
other purposes, the idea developed in I-Droid02 is to generate transmitted ultrasonic signal
externally and then take advantage of the structure where ultrasonic transductors are
placed. The fact of having transmitters and receivers’ transducers physically attached to
the same structure, a sound coupling effect is produced. Now, both transmitted and
received signals will be induced at the same GPIO where a certain transducer is connected,
so the emitter to receiver coupling is used to synchronize the burst generation. Another
advantage of use an external signal generator is that it can be powered directly from 11.1
V power supply, in order to increase transmitted signal power. This voltage is sufficiently
low to not damage both the transducers and human ears.
To generate the 40 kHz signal, a 555 timer integrated circuit is used. This timer must be
configured to work in astable mode to have a continuous generated signal. A 555 integrated
circuit generates an asymmetric unipolar square signal, but it is possible to generate an
almost symmetric square signal by selecting the discharge resistor value at least 100 times
higher than the other one.
The square 40 kHz carrier signal can be directly connected to the ultrasonic transducers,
since they will bandpass it as shown in the frequency response graph of transductor’s
datasheet. In order to generate the burst from the carrier, another 555 integrated circuit
timer is used. Signal requirements are to be in high voltage level about 250 µs (10
frequency cycles of a 40 kHz signal) and about 10 ms in low voltage level (it is theoretically
possible to have a 1.7 m coverage without taking into account power restriction).
Next schematic shows the transmitter design for ultrasonic system:
Figure 22. Transmitter design for I-Droid02 ultrasonic system.
From the previous circuit, it is important to remark the following details:
•
•
R18, R19 and R99 are acting as an aggregated resistor of 1.82 MΩ. In accordance
with 8.2 pF capacitor, its discharge time is about 12.5 µs (half of a 40 kHz signal
period). When the 555 timer cycle is reversed, the capacitor is charged through the
same resistors plus R17. As explained before, this resistor is at least 100 times
smaller than 1.82 MΩ aggregated resistor so that total charging aggregated
resistance is almost equal to discharge resistance, making the square signal
almost symmetric as required.
In the second 555 timer circuit, the diode placed in parallel with R7 discharge
resistor conducts it in the charging cycle. In this stage, the 220 nF capacitor is
directly charged through a 1.5 kΩ resistor, producing a fast charge lasting about
47
250 µs. Then, in 555 discharge cycle, the capacitor is discharged through an 82
kΩ resistor, which is almost 100 times higher than charging resistor, provoking a
discharge time also almost 100 times higher (10 ms).
Ultrasonic system receiver circuit for one transducer is as shown below:
Figure 23. Receiver design for I-Droid02 ultrasonic system.
Basically, the receiver circuit amplifies the received signal and performs other preprocessing tasks before send the signal to an Arduino Mega 2560 GPIO pin. What the
microcontroller needs to compute distance between and object and I-Droid02 ultrasonic
sensor are the reference pulse and its replica, to compute time difference between both.
Received signal is weak, needs a high amplification. Being that ultrasonic received signal
is very weak, a two-stage amplification (one TLC272 integrated circuit) is required. Its total
gain in amplitude is 484. Finally, when the signal is amplified an envelope detector is used
to eliminate 40 kHz oscillation.
From the previous circuit, it is also interesting to highlight the following aspects:
•
•
The 1 MΩ resistor (R3) placed in parallel with the ultrasonic receiver transducer is
used to reduce its DC impedance, allowing to have a discharge way for the
transducer internal capacitor (an ultrasonic receiver transducer is actually a
piezoelectric microphone).
To reduce noise in the operational amplifier non-inverting input, a 1 µF capacitor is
placed at each voltage resistive divider.
Previous lines were dedicated to explain in full detail all I-Droid02 sensors and actuators
basic circuitry. Full schematics of the hardware design can be found under Appendix 1. IDroid02 hardware schematics. To make a better use of the available space, conditioned
by the original distribution of I-Droid01 original circuitry, in the assembly process, all circuits
have been designed to be spread in different boards according to its proximity with the
involved sensors and actuators. To understand better which circuits can be found in each
board and the electrical connections among them, a block diagram is provided at the
beginning of the corresponding appendix.
I-Droid02 brain board:
At this point of the design, I-Droid02 has already a remarkable dose of intelligence. The
Arduino board is capable to provide some computing power to the robot but its capabilities
48
are lower than the ones available on the original I-Droid01. The more complex a system is
wanted to be, larger must be the casuistry forestalled by itself. At the same time, the higher
the complexity of a system, the higher the amount of data handled by the microcontroller.
In I-Droid02 design, the management of complex sensors such as the video camera or
actuators like the speaker and hand tools described in 2nd section of this thesis,
communications with the outside world or the ability of taking complex decisions, among
others, are tasks derived to another microcontroller. The main concept is to let Arduino
Mega 2560 microcontroller be an automaton that executes commands coming from the
other microcontroller. Then, at the end of the execution cycle, report the task results and
the state of the sensors back to the microcontroller and wait for new commands.
For I-Droid02 Project, the board chosen to act as the I-Droid02 brain board is the Raspberry
Pi, a very compact but also powerful board considered as the cheapest and smallest
portable PC nowadays. In a rectangular space of 85.60 mm × 56.5 mm, which is slightly
bigger than the one occupied by original I-Droid01 Brain & Vision entity, this board
(Raspberry Pi 2 B version) has the following characteristics [11]:
•
•
•
•
•
•
•
•
•
•
•
Processor: Quad-core ARM Cortex-A7 CPU at 900 MHz.
VideoCore IV 3D graphics core.
RAM memory: 1 GB.
Micro SD card slot (64 GB maximum Flash memory).
4 USB ports.
40 external pins (26 GPIO, 1 I2C bus, 1 UART interface and 1 SPI interface able to
interact with 2 slave devices).
Full HDMI port.
Ethernet port.
Combined 3.5mm audio jack and composite video.
Camera interface (CSI).
Display interface (DSI).
As it can be seen, this board has been designed to operate with many multimedia
applications as well as office applications, taking advantage of the USB ports that can be
used to plug typical PC peripherals. The native operating system that this board runs is
Raspbian, a Linux distribution based on Debian family with graphical user interface (GUI).
Although it can run other operating systems, Raspbian operating system is the one that
has been selected in this design because it is the most used one (many applications to
interact with Raspberry Pi peripherals have been developed for it).
49
Figure 24. Raspberry Pi 2 B as the I-Droid02 brain board.
Next lines describe the sensors and actuators that have not been connected yet. Raspberry
Pi 2 B board is able to handle most of these devices directly, without requiring any
electronic circuit in between. These devices and their connection with the board are:
•
•
•
•
•
Prototype board GPIO pins: Original I-Droid01 Arm Controller entity provided direct
access to some GPIO pins of the entity’s microprocessor. Owing that Raspberry Pi
2 B microcontroller also provides GPIO pins, 10 of them have been mapped to the
prototype board strip (including 3.3 V and 5V power supply and ground pin).
Camera: Raspberry Pi 2 B board provides an especial connector to be used by its
own compatible cameras. For this reason, original I-Droid01 CMOS Camera has
been replaced by RPi Camera (B), a camera with adjustable focus that is build
around a 5-megapixel sensor (OV5647). It is able to take pictures and video (30
fps) at Full HD 1080p resolution (1920 x 1080 pixels). These specifications are a
clear definite improvement over the original camera.
Communications: All the interfaces that I-Droid01 used to communicate with the
outside world (Bluetooth module, Serial port, etc.) are now supported by the
Raspberry Pi 2 B Ethernet port, providing I-Droid02 a link to a LAN network and
also Internet (not supported in its predecessor). In order to support wireless
communication (Wi-Fi), a USB dongle has been included in the design.
Speaker: Raspberry Pi 2 B includes an audio analog output interface where a
speaker can be connected. However, this audio output has been designed to be
used by headphones or externals speakers with amplification. For this reason, an
electronic circuit to amplify and mix stereo audio supplied has been included. This
circuit will be explained a few lines below.
Removable tools: Original I-Droid01 LED torch and universal remote control uses
an USB connection to connect with the robot. In order to be able to connect and
50
control these removable tools from Raspberry Pi 2 B microcontroller, one USB port
has been mapped directly to this mini-USB connector placed in the upper left
forearm. Due to the fact that original universal remote control uses I2C bus, which
cannot be directly connected to an USB connector, for the I-Droid02 design it has
been considered to substitute it by a generic USB IrDA adapter; which also provides
I-Droid02 an infrared connection.
In order to interface both the mono 8 Ω speaker with the Raspberry Pi 2 B audio analog
output interface an electronic circuit has been designed. The schematic is shown below:
Figure 25. External audio amplifier designed to boost Raspberry Pi 2 B audio.
The circuit is based on the LM386 low voltage audio power amplifier, from Texas
Instruments. It provides a fixed voltage gain of 20 to the inputted signal, although it is
possible to increase this gain up to 200 by adding the required electronic parts in the GAIN
pins, following the datasheet examples. In this case, it has been considered that
maintaining the original gain is enough for driving a small speaker. Audio stereo channels
are directly mixed using R29 and R30 resistors to the inverting input. The RC structure at
the output (R31 and C29) as well as C26 5 µF capacitor helps in reducing the distortion
produced by the speaker’s inductive reactance.
In order to be able to activate and deactivate this audio amplifier an electronic power source
switch controlled with a GPIO has been included. It is considered that output noise provided
by the microcontroller, in absence of sound, could be undesirable.
A part from the hardware parts stated before, in the I-Droid02 design there can be found
other elements that are directly connected to the Raspberry Pi 2 B microcontroller. These
hardware parts, listed below, are required for the I-Droid02 new functionalities:
•
Service buttons: 2 push buttons have been added to directly interact with Raspberry
Pi 2 B and Arduino Mega 2560 microcontrollers. These buttons are:
51
Shutdown Raspberry Pi: Used to have a safe and elegant way to tidily power
off the Raspberry Pi 2 B instead of directly removing power from the supply.
This button triggers a low voltage in a selected GPIO pin.
o Reset Arduino Mega: This push button connects the Arduino Mega 2560
RESET pin with ground. Thanks to this, it is possible to hard reset the
Arduino microcontroller, without need to restart the entire system. Arduino
board can also be reset through software.
Programmable buttons: Three push buttons with D, E and F labels are connected
to three different GPIOs. Their function is to start the execution of a preprogrammed
behavior, similar to the original I-Droid01 preprogrammed tasks. Identical buttons
(labeled A, B and C) can be found in the 433 MHz native remote control (see next
subsection).
LEDs: There are three new LEDs used to indicate certain situations:
o READY: It turns on when Raspbian operating system has ended its boot
process. Stays on indicating that I-Droid02 is ready to operate.
o 433 MHz remote control: When a command sent from 433 MHz native
remote control has been received, this LED blinks.
o External remote control: This LED turns and remains on when a client has
been connected to the external remote control server (see I-Droid02
external remote control server in next section of this thesis).
433 MHz native remote control receiver: The RFM31B integrated circuit from
HopeRF manufacturer is used to receive commands coming from the remote
control. This receiver communicates with the Raspberry Pi board by using the SPI
port. The strong points of this receiver are the high sensibility (-121 dBm in the bestcase link) and the low current consumption (only 800 µA in READY mode, waiting
for a packet, and 18.5 mA during the reception process). This device can be
powered at 3.3 V, directly from Raspberry Pi 2 B power supply.
o
•
•
•
At this point only remains one detail: the interconnection between Arduino Mega 2560 and
Raspberry Pi 2 B microcontrollers. The way to interconnect them is by using the USB
connection, because it turns out to be the easiest method to recognize and install the
Arduino microcontroller to the Raspberry Pi. Moreover, from the Arduino view, the UART0
interface which is precisely derived to the USB connection it is also used for programming
the board itself, allowing to program the Arduino board from the Raspberry Pi. In order to
have a second link in parallel, it has been decided to link Raspberry Pi UART GPIO and
UART1 from Arduino board. In this case, taking into account that Arduino board works at
5 V logic level whereas Raspberry Pi board works at 3.3 V.
Removable tools:
Original I-Droid01 LED torch and Universal Remote Control entity where designed to be
operated through I2C bus. In I-Droid02, LED torch has been substituted by a USB-to-UART
converter module, to be able to connect these tools directly in a Raspberry Pi USB port.
New remote control hardware is a USB IR transceiver from Iguanaworks company.
In Appendix 2. Arduino Nano, Mega and Raspberry Pi 2 Pinouts under I-Droid02 Raspberry
Pi pinhead pinout title the complete list of connected hardware elements can be found.
52
3.3.
433 MHz I-Droid02 native remote control
I-Droid02 hardware design also includes a new element which was strictly not part of its
predecessor’s design. What it concerns this subsection is about a basic remote control to
convert I-Droid02 in a radio-controlled device. Although the attributions of this new remote
control were perfectly covered in I-Droid02 predecessor via I-Droid01 PC/Mobile Control
software; the fact of needing a specific and expensive brand of mobile phones turned this
feature into something exclusive inside the potential buyers’ market of this collection.
For the I-Droid02 Project, the idea of include a device that only acts as the native I-Droid02
remote control solves the incompatibility issues and enhances the feature to use the robot
as if it were a radio-controlled toy. This remote control works at the 433 MHz ISM band, in
which the most part of the time is unoccupied in domestic urban environments, with the
exception of those low-power burst transmissions caused by radio-controlled toys of any
kind. This frequency propagates quite well in indoor environments, so initial Bluetooth
Class-2 coverage range of I-Droid01 design can be clearly improved. The available
bandwidth on a 433 MHz link is too small to support multimedia streams, but enough to
transmit few driving commands.
The link between the native remote control and I-Droid02 is unidirectional. In fact, the same
philosophy as the radio-controlled toys have has been applied. All this simplifies
significantly the general design, in comparison with the original provided bidirectional
system. In bidirectional systems, it is common to establish, maintain and end a session
between the remote control and the radio-controlled device before start the interchange of
information. That was the case of the original I-Droid01; however, most part of the
information was send in one direction, while the return channel was only used for
commands acknowledgment and/or data reporting (temperature sensor and CMOS
camera video stream was reported in the I-Droid01 case). In the unidirectional case, there
is no possibility of receive data from the radio-controlled device, including commands
acknowledgment. Thus, remote control device only has to worry about sending the
appropriate information in an unknown and uncontrolled channel. On the other hand, the
radio-controlled device will only execute those commands that were received correctly,
without errors. Of course, lost commands are not resent automatically.
The selected transmitter for this project to have best performance from all that has been
considered is RFM43B integrated circuit, from HopeRF. This device is the complement of
the receiver module, which has been presented in the previous subsection. As it happens
with the receiver, the transmitter has also been designed to be energetically efficient. Its
current consumption is about 800 µA in READY state, waiting for a packet transmission,
and 30 mA during its transmission, if the output power is set at its maximum value of 13
dBm.
The optimal receiver’s sensibility value of -121 dBm, as its datasheet states, can be
achieved using a GFSK (Gaussian Frequency Shift Keying) modulation and a bit rate
transmission of 2 kbps. Both modulation and bitrate values are compatible to the selected
transmitter. The main difference between GFSK and FSK modulation is the kind of signal
used to switch from both sub-frequencies used in the transmission. In a FSK modulation,
this baseband signal is composed by square pulses of different length, according to the
baseband signal which is conformed taking the stream of bits to be transmitted and using
Manchester encoding (baseband normalized voltage signal goes from -1 to 1). The way of
codify the baseband signal makes possible to recover the original clock of it. Instead, in a
GFSK modulation the baseband signal does not use square but Gaussian pulses to switch
53
between sub-frequencies. The effect can be seen in the transmitted passband signal
spectrum, which contains an important reduction of the secondary lobes. Next figure,
extracted directly from RFM43B datasheet, shows graphically this effect:
Figure 26. FSK vs GFSK ideal spectrums.
In order to proof that a GFSK signal produced by the RFM43B transmitter module is close
to the red colored spectrum graph, real transmitted signal has been measured using the
RFM31B receiver, with the transmitter placed a few centimeters of the receiver; taking
advantage of a command that provides the total received amount of power (signal plus
noise) in the tuned frequency and performing an accurate scan of the entire 433 MHz ISM
band. The obtained spectrum, which is almost the same as the theoretical one, can be
seen in the following figure:
Figure 27. Transmitted pseudo-random signal spectrum received at I-Droid02 receiver.
54
As it happens with the 433 MHz receiver placed onboard I-Droid02, this transmitter also
uses the SPI bus to interface with a microcontroller. Therefore, the associated
microcontroller must have this serial interface. As a design condition, the I-Droid02 native
remote control should be a simple device. Its main task is just watch the buttons pressed
by the user and send the appropriate command, according to a certain pre-established
protocol, via the RF transmitter described before. The microcontroller in charge of this task
only requires a relatively small amount of memory, several GPIO to be used by the remotecontrol button, a small size and a power consumption as low as possible.
Having a look on the market, it looks like the PIC, a microcontrollers family manufactured
by Microchip Technology company, fits the previous requirements. However, to reduce
hardware and software design complexity, it has been considered to use Arduino Nano
board instead; taking into account that the proposed solution also fits well with the previous
requirements.
Figure 28. Arduino Nano as the I-Droid02 native remote control microcontroller.
The Arduino Nano is a small, complete, and breadboard-friendly board based on the
ATmega328 microcontroller [12], similar but with less hardware capabilities than Arduino
Mega 2560 microcontroller (ATmega2560). Its microprocessor’s clock also works at 16
MHz, and the total equipped Flash memory is 32 KB. ATmega328 microprocessor comes
with 22 GPIO pins, of which 6 provide PWM capability and other 8 pins can be connected
to an internal ADC for analog input signal processing.
Arduino Nano also includes an UART port (pins 0 and 1 and also the same interface can
be managed via the USB port thanks to the FTDI USB-to-TTL Serial chip) and a SPI port,
which is used in this project to connect the RFM43B transmitter described before. In the
hardware design, it has been taken into account that RF transmitter operates at 3.3 V logic
level, while Arduino Nano operates at 5 V.
Regarding electronic specifications, Arduino Nano operates at 5 V logic level and can be
powered in a voltage margin between 6 V and 20 V, but it is recommended not to use a
power supply that provides a voltage higher than 12 V due to the dissipation associated to
the use of a linear voltage regulator to power the board. Each GPIO can only provide a
maximum amount of current of 40 mA, but the total amount of current drained from the
microprocessor cannot go beyond 200 mA. The maximum amount of driven current
specification becomes important in this design, since all the hardware parts will be powered
using the 5 V power supply provided by Arduino Nano board; instead of using an external
power supply as it happened with all hardware parts that interacts with Arduino Mega 2560.
55
I-Droid02 native remote control is mainly used to switch on any of the 8 robot motors in
both possible directions. The total number of interface elements of the transmitter that has
been included in this design are:
•
•
•
•
•
•
•
•
•
1 analog 2-axis joystick (by Parallax manufacturer, which is the same used in
PlayStation remote controls) to be able to control I-Droid02 wheels’ motors; allowing
the robot to go forward/backward and turn left/right. This joystick is made of two 10
kΩ potentiometers with common ground, providing a different voltage value
between VCC and GND when the joystick is moved. The joystick’s position can be
read by the microcontroller by sensing this voltage using the internal ADC.
4 small red LEDs placed near the joystick edges that increases its brightness when
the joystick is more away from the center point, taking advantage of the PWM
capability. They give feedback on the joystick control.
4 push buttons to turn I-Droid02 head up, down, left and right.
3 pairs of push buttons associated to the arms and hand movement.
1 push-pull switch of three positions used to raise and lower I-Droid02 hip.
3 programmable buttons (A, B and C) in order to be able to start any
preprogrammed procedure in a fast way, as explained in previous subsection.
ON LED. It indicates that power supply is on.
TX LED. It blinks when a packet has been sent.
LOW BATTERY LED. A circuit based on the µA741 general purpose operational
amplifier turns this LED on when battery power supply voltage drops below 7.9 V,
taking into account that the power supply of this system is a 9 V alkaline battery.
Excepting the analog 2-axis joystick, the rest of hardware parts can be directly connected
to any GPIO pin. In the case of the hip’s switch and the push buttons, the ATmega328
internal pull-up resistors drives voltage to high value when the buttons are not pressed.
However, after using 4 GPIO pins with the SPI port, only 10 GPIO pins are available. There
are not enough GPIO pins to connect all hardware parts described before. In order to take
advantage of having several input analog pins, in this design it has been decided to group
the pairs of push buttons which interact with the same motor and, thus, cannot be pressed
at the same time. When one of the push buttons is pressed, voltage goes from 2.5 V to
almost 5 V or almost 0 V. Although the current consumption rises, the fact of pushing both
buttons at the same time has no impact in the output voltage from the resistive divider.
Figure 29. Schematic to connect pair of push buttons that interacts with the same motor.
56
In order to power Arduino Nano board, it has been decided to use a 9 V alkaline battery,
which has a nominal capacity of 550 mAh. Current consumption during a packet
transmission will not be higher than 50 mA.
The complete electronic schematics of all hardware parts involved in this remote control
can be found in Appendix 1. I-Droid02 hardware schematics at Remote control based on
Arduino Nano sheet. In Appendix 2. Arduino Nano, Mega and Raspberry Pi 2 Pinouts under
433 MHz remote control Arduino Nano pinhead pinout title the complete list of connected
hardware elements can be found.
3.4.
First approach on I-Droid02 software
As it happened with original I-Droid01 software structure, two level environments were
distinguished in I-Droid02 software design: The low-level environment, where the
microprocessor handles and filters directly the I/O data coming from sensors/actuators,
and the high-level environment, where decisions are taken based on the information
received through low-level environment provided. This can be seen more clearly in the next
flow diagrams:
Example of situation: Blink red eyes’ LEDs when top head is touched:
High-level environment
Low-level environment
Figure 30. Example of high-level and low-level programming.
In the low-level programming environment, the actions of sensing the touch sensor and
turn red eyes’ LEDs on/off are physically controlled, according to the electronic circuitry of
the elements and the microcontroller GPIOs where they are connected to. The routines in
charge of that are seen as black boxes for the high-level programming point of view, which
57
is the one in charge of decide to blink the red eyes’ LEDs when the touch sensor has
detected a hit. Next lines will be dedicated to summarize the I-Droid02 software design,
following its block diagram which can be found at Appendix 3.
According to this block diagram it can be distinguished three independent systems: The
robot itself, its native remote control and an external environment to connect to the robot
and interact with it.
Taking into account that the microcontrollers that composes the entire system (Arduino
Mega 2560, Raspberry Pi 2 B and 433 MHz RF TX/RX) share information among them, it
is necessary to use certain routines in both sides of each channel to manage the link
between two microcontrollers. Raspberry Pi 2 B board uses two different UART interfaces
to communicate with Arduino Mega 2560 board. Its low-level software is already
implemented as part of the Linux Drivers set on the Raspberry Pi. On the other side of the
channel, Arduino also implements the UART communication in its native routines set. The
high-level programming environment that implements the protocol used in the information
sharing process will be described in 3.8 subsection of this thesis.
Regarding low-level programming software, the program or the set of programs that
configures the low-level operation of a certain device is commonly associated to the
firmware. A firmware access to the hardware which is responsible for. Firmware programs
are designed to develop a certain low-level task and usually its code does not require
constant updates, when they already perform the tasks they were entrusted for. A firmware
update should be required only to improve current hardware access performance, whether
hardware has been updated or modified or to cover possible hardware dysfunctions, for
example to avoid the overheating of certain devices or simply to override a certain
functionality, if the hardware in charge of is not working properly.
According to the I-Droid02 software blocks diagram, the blocks that contain a firmware to
operate are:
•
•
•
The 433 MHz RF TX/RX boards (RFM43B and RFM31B respectively). Its firmware
supports the configuration of these devices, the handle of sent/received packets
and the communication of its own microcontroller with another device connected
via a SPI port. This firmware is provided by the board’s manufacturer, HopeRF.
The Arduino Nano board, in charge of the native I-Droid02 remote control operation.
This software will be described at subsection 3.5.
The Arduino Mega 2560 board, which provides an access to most part of I-Droid02
sensors and actuators. The Arduino Mega 2560 software will be described at
subsection 3.7 of this thesis.
For the Raspberry Pi 2 B, it is more convenient to refer as drivers and daemons to those
pieces of software in charge of the board’s operation itself and the management of its
peripherals. The programs that compose the software directly related with I-Droid02
operation will be described in different subsections of this thesis, each one as a different
block following the I-Droid02 software general block diagram.
At the end of this section, a description of a server and its associated client (also known as
external remote control) that will allow I-Droid02 to be a completely controlled device will
be described. The last subsection of this chapter will be a collection of all the tasks to be
58
developed beyond this thesis to finish off the I-Droid01 evolution and, thus, to have an
enhanced autonomous robot comparing it with the original I-Droid02 predecessor.
3.5.
433 MHz I-Droid02 native remote control firmware
The I-Droid02 software description starts with the firmware description of its native remote
control. The entrusted task of this device is quite simple: Wait for the user to press any
interactive element and transmit the inputted command to I-Droid02 via the 433 MHz link.
In collaboration with the associated server of this device (see 3.10. subsection), the focus
is placed on ensuring that the inputted command is transmitted and executed by I-Droid02.
The source code can be found in the Appendix 4.
The first task to be performed by the firmware is to setup the GPIOs direction (act as input
or output) and also setup the RF transmitter. The GPIO configuration has been done
according to the Arduino Nano pinout that can be found in the Appendix 2.
Having a look of the RFM43B datasheet, there are several parameters of the transmitter to
setup (output power, modulation, data rate, etc.). Moreover, there are available certain
auxiliary functions (integrated ADC, own GPIOs, microprocessor temperature sensing,
etc.). By default, these auxiliary functions are deactivated but some of them can be
exploited in this project.
The next list shows the RFM43B applied settings, according to the required parameters:
•
•
•
•
•
Carrier frequency: A value inside 433 MHz ISM band range (433.05 MHz – 434.79
MHz). The value set can be automatically changed if the remote control is
connected to I-Droid02 using a USB cable (see RFM31B 433 MHz remote control
receiver subsection at Appendix 10).
Modulation type: GFSK (Gaussian Frequency Shift Keying).
Bitrate: 2 kbps. The amount of data to be transmitted per command is just some
few Bytes and this bitrate value ensures that the receiver is able to decode the
received command with the minimum received power.
Automatic packet handler: On. The RFM43B module allows an automatic
packetization of the inputted raw data in order to facilitate the protocol design.
o CRC: Enabled.
o Automatic packet length: Off. The length of the packet is always controlled
by Arduino Nano microcontroller.
o Use preamble, personalized sync word and header in packet: Yes. The
preamble is a sequence of ones and zeros used to wake up the receiver.
Then, the sync word is used to synchronize and alert the receiver that after
this word ends the packet will start. A personalized header helps the
receiver to accept only those packets that comes from this transmitter,
avoiding interferences.
Auxiliary functions: From those available functions, it has been decided to multiplex
the internal general purpose 8-bit ADC to read the analog values provided by the
internal temperature sensor, in order to monitor the heating of the device.
HopeRF manufacturer provides a spreadsheet to obtain easily the values of the registers
to be modified in order to have the selected settings. The same method will be used to
setup the RF receiver (see 3.10. 433 MHz I-Droid02 native remote control server). At this
59
point, once the RF transmitter has been configured, the firmware is ready to perform the
task which is responsible for. The algorithm is simple: Watch for the state of all the
interactive elements and send a packet only if one or many of them changes its default
state. This task is repeated infinitely.
In terms of software, there are two ways of sensing the state of a certain sensor (read the
value of certain variable or microcontroller register) and react according to the value: By
polling or by using interruptions. On one hand to poll means reading the value of interest
and then decide what to do according to certain conditions. In this method, the only way to
know whether a variable has changed is by reading it continuously; a task that is part of
the normal execution of the code so it consumes execution time. A part from that, the
reading will be performed when the microprocessor reaches that point of the code and not
necessary when the variable changes its state. On the other hand, an interruption means
to interrupt the execution of the code when certain event occurs, for example a variable
change and execute immediately certain task. The use of interruptions is useful when the
variable to be watched remains unchanged the most part of time, in order to avoid a loss
of time reading it continuously. Moreover, when certain behavior must be executed
synchronously each certain period of time, the use of an interruption triggered by a time
counter is mandatory.
In the I-Droid02 native remote control firmware design, it has been considered the
possibility of using interruptions to sense whether a button has been pushed. Although the
fact of pushing a button or move the joystick is occasional and completely asynchronous,
the number of hardware interruptions to be watched by the Arduino Nano microcontroller
is very limited. For this reason, and also due to the small load of the executed tasks when
any button is pushed, it has been considered to use the polling method to sense whether
an element has been pushed by the user. Besides, the microprocessor reaction time when
a button is pushed if it is watched by polling is in the order of microseconds in the worst
case (by using interruptions, the execution is executed almost immediately when the event
occurs), so it is fast enough to be perceived as immediate. Since the minimization of the
execution time is not a priority, in order to read the state of the buttons the Arduino native
functions digitalRead() and analogRead() has been used.
As a counterpart, the fact of using polling implies that the microprocessor is all the time
running, most part of it without any repercussion, if any element of the remote control is
actuated. This also implies an increase of the microcontroller’s power consumption.
When certain interactive element has been actuated, the firmware is able to detect which
is the desired action to be performed by the user, so it sends a packet to I-Droid02 following
a protocol that will be described below. During the packet transmission, it turns on the TX
LED. In the particular case of the joystick, when it is pushed in one of the 4 possible
directions, the firmware turns on the red LED that is in line with the direction of movement,
controlling its intensity by using PWM.
The protocol used to transmit the commands is described as follows: There are three
different kind of packets sent. Each one uses 1 Byte to identify itself. Packet IDs are the
following:
•
Control packet (ID 0xC3): 12 Bytes length. It contains battery voltage value of the
remote control, its discharge percentage and the temperature value of the on-board
sensor placed inside transmitter module. This packet is sent automatically by the
remote control every 10 seconds.
60
•
•
Analog actuator packet (ID 0x24): 2 Bytes length. It indicates the joystick axis value.
Digital actuator packet (ID 0x18): 2 Bytes length. Packet able to activate any of the
motors not associated with the joystick and also programmable functions A, B & C.
Control packet
Initial 4 Bytes contain battery voltage value in float. Next 4 Bytes are to store the
discharge percentage of the battery according to the 9 V alkaline battery discharge curve.
Last 4 Bytes are temperature value, after parsing it in decimal form with a precision of 0.5
ºC. To recover temperature value, the microprocessor applies the formula provided by
RFM43B datasheet.
Transmission mode is little endian, that is, the first transmitted bit is the less significant one.
This rule applies for both float values and for int values.
Analog and digital actuator packet
For the analog actuator packet, the initial Byte is a control flag called direction flag. This
flag tells the direction to be followed by I-Droid02, while next 2 Bytes tell the speed of
movement and the turning speed (only applicable for directions that implies a turning).
Speed values are integers from 1 to 10. As the joystick is moved by the axis, the value is
incremented up to 10, just when the joystick reaches the edge of the axis. When the joystick
is centered, the axis values are both 0 but they are not sent.
There are three kinds of nibble inside control flag. Expressed in binary form are: 0000 (no
movement), 0110 (forward/left) and 1001 (backward/right). Flag can only take 8 possible
different values, since 0x00 represents no movement and no packet is sent. A received
packet must be dropped if control flag sent does not coincide with one of the following ones:
Direction flag
0x66
0x06
0x60
0x00
0x96
0x90
Meaning
0x00
Stop (never sent)
0x06
Turn left (spin)
0x09
Turn right (spin)
0x60
Go forward and straight
0x66
Go forward and turn left
0x69
Go forward and turn right
0x90
Go backward and straight
0x96
Go backward and turn left
0x99
Go backward and turn right
0x69
0x09
0x99
Table 12. I-Droid02 native remote control analog actuator packet Byte control flag.
61
The digital actuator packet only contains 2 Bytes. Each one shows which is the pushed
button according to the bit set to ‘1’. In the case of first Byte, MSB is always ‘0’.
Binary value
Meaning
Binary value
Meaning
0b00000001
Head up
0b00000001
Left arm up
0b00000010
Head down
0b00000010
Left arm down
0b00000100
Turn head left
0b00000100
Right arm up
0b00001000
Turn head right
0b00001000
Right arm down
0b00010000
Programmable button A
0b00010000
Open hand
0b00100000
Programmable button B
0b00100000
Close hand
0b01000000
Programmable button C
0b01000000
Hip up
0b10000000
Hip down
Table 13. I-Droid02 native remote control analog actuator packet Byte control flag
Leaving apart the normal behavior of this remote control, the firmware allows 3 additional
modes that can be activated if A, B or C especial buttons are long-press pushed (more
than 2 seconds). In these cases, the normal behavior of the remote-control stops. These
especial modes are:
•
•
•
3.6.
When A button is long-pressed: A game inspired in Simon is launched. In this game,
the user has to follow the lighting sequence of red joystick LEDs by pushing in the
same order the head buttons that are placed following the same structure as them.
The TX LED and the same joystick LEDs are used to indicate that the pushed button
is correct or not. The LED sequence is generated randomly.
When B button is long-pressed: The RFM43B transmitter module is configured to
send continuously a random generated sequence, at the central frequency
established. This mode is useful to test that the transmitter is correctly configured
and also to measure the received power at the receiver placed in I-Droid02. During
the execution of this mode, red joystick LEDs blink.
When C button is long-pressed: The remote control enters in the test buttons mode.
In this mode, all buttons must be pushed following a certain order to test that they
are correctly connected to the Arduino Nano board. TX LED blinks twice when the
expected element is pushed. The test sequence is: joystick up – joystick down –
joystick left – joystick right – head up – head down – head left – head right – left
arm up – left arm down – right arm up – right arm down – open hand – close hand
– hip up switch – hip down switch – A button – B button – C button.
Independent Bash/Python scripts
All the applications involved in the I-Droid02 Project that run on the Raspberry Pi 2 B are
mainly developed using C language, which is the Linux native one. However, there are
some simple procedures to interact with the operating system which are easier to handle if
62
they are programmed using Bash scripts. Bash is a Unix shell and command language
where the user types commands that cause actions to the operating system [13]. In a Bash
script, a set of commands can be run consecutively to facilitate the execution of complex
behaviors; for example, the installation of a certain package (a type of archive containing
computer programs and additional metadata).
For the I-Droid02 project six Bash scripts have been developed:
•
•
•
•
•
•
Audioscript. In order to play any audio through the Raspberry Pi 2 B analog audio
output, this script turns on the external audio amplifier (see subsection 3.2 under IDroid02 brain board subtitle).
Autoupdate. This script is used to upgrade Raspbian operating system and all
installed packages to its latest version.
Backup. This script creates a copy of the entire contents of the SD card in a specific
network place (if available).
ResetAll. The panic script. If I-Droid02 crashes this script resets Arduino Mega 2560
and restarts the driver program (see subsection 3.8 for driver program details).
ResetShutdown. Script to restore motors’ position to its default position; a previous
step to shut down I-Droid02 properly.
Restore. This script restores the contents of the SD card from the backup.
Originally, the Raspberry Pi board includes an audio amplifier that is managed by the ALSA
driver, which is the driver in charge of handling audio playback and recording in Linux. The
external audio amplifier, described in 3.2 subsection, is activated and deactivated by using
a GPIO directly from the Raspberry Pi board. In order to prevent noise generation on the
speakers, it is turned on only when necessary. It is possible to manage GPIO directly from
Bash commands, thus, the audioscript script turns the amplifier on before run the command
that has been inputted as a parameter of this script. When the command ends, this script
turn off the amplifier again.
A part from that, the audioscript script also ensures that only one process is accessing the
ALSA driver at the same time, in order to avoid abrupt sound playback interruptions or
other errors. To check is ALSA driver is busy, this script looks at a flag file called status
placed in /proc/asound/card0/pcm0p/sub0 directory. To make I-Droid02 play any
sound, such as play any MP3 file using mpg321 command, it is as easy as place the
audioscript command before the audio command itself.
According to Raspbian documentation, the recommended way to update packages and
Raspbian version is by using the apt command, in administrative privileges. The
autoupdate script runs first the apt update command to update first the list of available
packages from the configured URL sources. If there are new versions of the installed
packages, the apt upgrade and the apt dist-upgrade commands download and
install this updates automatically. The apt dist-upgrade command is used if the entire
Raspbian distribution needs to be updated. Finally, when all the packages are updated, the
apt autoremove command uninstalls all the unnecessary packages. Usually these
packages were installed together with other packages that, in a newer version, does not
need those dependencies anymore. The command autoupdate is used to run this script.
Other simple scripts to be run occasionally by I-Droid02 are written in Python. Python is a
high-level, general-purpose, interpreted and dynamic programming language [14] which
63
allows to interact with Raspberry Pi 2 B peripherals in a high-level environment, but in a
less efficient execution time comparing with C. The purpose of this scripts are mainly for
testing hardware or developed functionalities in the Arduino Mega 2560 board, to debug
them before the definitive C program that implements them has been developed.
The available Python scripts for I-Droid02 are:
•
•
•
•
•
•
•
•
Basic.py. As an exception of the previous explanation, this script is used to turn
on the READY LED when I-Droid02 boots and also to activate the functionality of
shutdown Raspbian when the Shutdown Raspberry Pi button is pressed. To handle
this, a hardware interruption is used.
GPIO LEDs test.py. In order to test the GPIO that are mapped to the I-Droid02
prototype board, this script toggle them high and low sequentially. If a LED is
connected to every GPIO, it can be seen the effect.
Microphone audio test.py. This script is able to capture sent samples from
Arduino Mega 2560 that come from the sampling of one of the head’s microphone
(see next subsection for details). The script concatenates them in a WAV file.
RX measurement.py. For the RFM31B 433 MHz receiver, this script asks and
prints on the screen the current received power in dBm of the carrier frequency set.
Spectrum.py. Based on the previous script, this one senses the current received
power by sweeping all possible tunable frequencies, in a resolution of 1 kHz, and
writes the obtained results in a CSV file to obtain the obtained spectrum graph.
Figure 27 provided graph is done using this script.
SPI test.py. Also for the RF receiver, this script reads the firmware version
register and prints the result on the screen. If the hardware connections are correct
and the SPI speed is not too fast, the printed value must be 6.
UART GPIO test.py. This script is to test the 2nd UART link between Raspberry
Pi and Arduino boards, when the corresponding complementary routine in the
Arduino board is also running.
UART USB test.py This script tests the main UART link between both boards, in
the same way as the previous script does with 2nd UART link.
The source code of Bash scripts can be found under Appendix 5, while the Python scripts
source code is found at Appendix 6.
3.7.
I-Droid02 firmware: Programming sensors and actuators microcontroller
This is the part of the I-Droid02 software that is implemented on the Arduino Mega 2560
board. This board interfaces several sensors and actuators so it reduces the work load of
the Raspberry Pi main processing board. As it happened with I-Droid02 433 MHz native
remote control, this software can also be considered a firmware, being that its code does
not need to be upgraded frequently when it works as specified. The code will act as an
interface, like if all sensors and actuators were connected directly to the Raspberry Pi 2 B
board. Once this software is completely functional and it has been optimized, there is no
need to modify it unless if a hardware improvement or failure happens.
Taking into account that this software is an intermediate between a set of sensors and
actuators and all the processes that are running on the Raspberry Pi, it is advisable to
64
reduce as much as possible the response time of the command responses. Both the
Raspberry and Arduino boards are connected through two UART serial interfaces, but only
the main one is used for the commands interchange. The complete set of commands used
to communicate both boards can be seen at Appendix 8. The current subsection only refers
to the firmware needed to handle all the connected devices in a low-level programming
environment.
In order to describe the main behavior and algorithms used, the description will be divided
in several subsections regarding the different elements (or set of elements) that are
accessed for the firmware itself:
•
•
•
•
•
•
•
Setup of the board. All the necessary software to configure Arduino microcontroller
parameters.
Display management. The software design is directly based on the Arduino
LiquidCrystal library already developed to manage alphanumerical displays.
LEDs. The routine to turn on/off all the LEDs.
Simple sensors access. The access to those sensors such as temperature sensor,
touch sensor, chest push buttons, etc., which do not require much code to access
them, are described at this stage.
Head microphones. The head back microphone is used to record sound in real time.
The samples are transmitted through the second UART interface. The firmware in
charge of this functionality is described in this section.
Managing of ultrasonic system. The ultrasonic system is complex. Sensors
readings must be synchronized with ultrasonic burst emissions. This section
explains the design carried out to achieve the objectives.
Motors management. Motors and its position control system is the entity that
requires most software complexity, which is described in this subsection.
Sometimes a certain task needs to be executed periodically, needing a scheduler. This
scheduler is implemented in a routine called schedule(), which compares current time
with a previous stored value If the time difference is equal or higher than a threshold value,
this routine triggers the periodic actions and stores the current time, for next triggering. It
is possible to know the time since any Arduino board is turned on by using the native
routines millis() (time in milliseconds) and micros() (time in microseconds).
Setup of the board
The tasks associated with this section are executed once, during the startup of I-Droid02
or each time the Arduino board executes a hard reset. In this case, it is not necessary to
worry too much about execution times, just avoiding a setup process excessively long. In
the setup process, Arduino Mega 2560 GPIOs are set to be inputs or outputs, according to
the Arduino Mega 2560 pinout (Appendix 2); using Arduino native function pinMode().
When GPIOs are set, its initial value is also defined in order to control the initial state of all
the actuators. These instructions are executed using Arduino native functions
digitalWrite() and analogWrite().
During the startup time, the two serial communications between Arduino Mega 2560 and
the Raspberry Pi 2 B board have to be initialized. This instruction is executed using
begin() routine, an Arduino native function that is called for each serial link represented
65
by an object of type Serial. Serial interfaces are named Serial for the UART interface
over USB and Serial2 which is the one that is connected to the Raspberry Pi board UART
interface placed in GPIO pins line. The maximum baud rate that the Arduino Mega 2560
UART interface can handle is 115200 baud. This will be an important handicap when
managing the amount of information that should be interchanged between both boards, in
an execution time as small as possible. The size of variables must be controlled in order to
avoid sending more data than necessary.
The display is another element that requires initialization. Arduino includes a library to
manage displays compatible with the Hitachi HD44780 controller. The one used in the IDroid02 display is also compatible. Liquid Crystal library [15] just need to know which are
the pins where RS, E and DB7-DB4 pins are physically connected to initialize the display.
At this point, all the routines defined in this library can be directly used.
Finally, during the setup process the value of some ATmega2560 registers must be
modified from the initial state in order to enable some extra functions or modify the behavior
of others. The Arduino environment includes references for all ATmega2560 internal
registers. The bit names of each internal register are also referenced with symbolic names.
Using sbi(addr, bit) and cbi(addr, bit) to respectively set/clear a bit at the bit
position of the register with address addr, allows a fast and controlled access to any
register.
In the setup process, some register values have to be modified for different purposes:
•
•
•
The clock prescaler of ATmega2560 internal ADC is reduced from the original
divider of the master clock value, 128, to 16. It implies that the ADC will work faster,
reducing the needed time to capture samples. This eight times increase of the ADC
speed gives a good compromise of ADC precision and response time. The ADCSRA
register is modified accordingly to set the new clock prescaler.
The microphone sampling process, which implies the use of a timer compare match
interruption, implies the modification of some registers values. The ATmega2560
has 6 independent counters that are used for different tasks, for example to count
how long ago is the board started or to manage PWM. A timer can trigger an
interruption when it overflows or when its value reaches a certain threshold (output
compare match). Timer5 is used for microphone sampling process and requires a
fixed compare match threshold to count with precision, thus, its setup registers
(TCCR5A, TCCR5B and TCCR5C) are setup accordingly. In OCR5A register the
threshold value (1451) is set. Counting at 16 MHz, this value is reached at
microphone's sampling time of 90 us (11025 Hz sampling frequency). Finally, if
OCIE5A bit of register TIMSK5 is set, the timer starts counting.
The ultrasonic system will work using pin change interruptions, a kind of interruption
that is triggered when the value of a certain GPIO changes its state. In the setup
process, all the procedures to enable the interruptions for the GPIOs where the
ultrasonic receivers are connected are done.
Display management
For a display of this kind the main settings are:
•
To enable/disable the printing of the characters on the display, which is performed
by display() and noDisplay() routines of the Arduino library.
66
•
•
To enable/disable an underscore character at the position where the cursor is
pointing at. To get this the routines cursor() and noCursor() are used.
To blink the current position of the cursor by painting in black all the printable area.
To enable the blinking, blink() routine is used. noBlink() routine restores it.
Another important routine is clear(), which erases completely the display and points the
cursor to the home position. To write individual characters or strings, the routines write()
and print() are used respectively. But before start printing a character or string, the
cursor must be set in the initial position (row and column). This is done with the
setCursor() routine. In this software design, it has been considered to create one
function that first points the cursor at the initial position and then writes the content of a
char array in the display. If the purpose is to just move the cursor, the length of the input
char array must be established to 0.
Arduino Liquid Crystal library also includes a routine to create custom characters.
createChar() routine can store up to 8 different custom characters in the S6A0069
controller internal RAM. Each character has a printable area of 5x8 dots. To create a char,
a Byte array of 8 elements is used. Each value of the array contains a binary value that
establishes which dots of the matrix must be illuminated.
LEDs
To turn on/off a LED is as simple as set the value of the corresponding GPIO to 0 or 1,
depending whether the circuit that connects to the LED is low-active or high-active. The
Arduino native routine digitalWrite() can be used to set the value of a certain GPIO
to LOW or HIGH. But this routine takes 12 µs which is a value significantly high to be taken
into account (see Head microphones below for timing details). Moreover, this routine only
allows the modification of the state of one LED per routine call. Total execution time of the
LEDs() function, if using native digitalWrite()calls, would take about 132 µs, also
counting the activation of top head orange LED, which is controlled by Arduino native
analogWrite() routine. Execution time would be excessive.
To reduce this execution time, the use of registers manipulation is again the best solution.
Each bit of PORTx registers (x being A, B, C, D, E, F, G, H, J, K and L) contains the state
of 8 GPIOs and allows its modification. The Arduino Mega 2560 pinout allocation took into
account to join all GPIOs related with LEDs in the same hardware port. All LEDs that are
connected to those GPIOs that are managed by the same register can be modified in only
one instruction, which lasts less than 4 µs. After optimizing LEDs(), it only lasts 20 µs.
Simple sensors access
For most of the connected sensors, since they just provide single values that do not need
complex post processing, the low-level functions to access them are quite simple. The
sensors that belong to this group are the touch sensor, the three-chest push-buttons, the
temperature sensor and the battery voltage sensor.
For the touch sensor, the 555-timer circuit that is triggered when it is touched generating a
1.5 s high pulse. This time is enough to detect that the touch sensor has been touched by
67
polling it. However, in order to avoid more than one detection, a scheduler is used to stop
polling the sensor during 1.5 s when a single touch has been detected.
Regarding chest buttons, a Byte variable is used to inform which of the chest buttons is
the one pressed. Also thanks to the scheduling process explained before, it is possible to
avoid more than one detection if a certain button is kept pressed. If a button remains
pressed, after 300 ms it will be reported as a new press. The Byte variable that stores the
result has the following format: 0b00000LCR. L, C and R represents the left chest button,
the center one and the right one.
Finally, for temperature and battery voltage level sensors each function in charge to access
them only needs to read the analog value using analogRead() and provide it, just
applying before some math to convert the read value. For the temperature sensor, the
temperature value is obtained from the output voltage of the sensor by applying a formula
that is found on the datasheet. The battery voltage level sensor is just a resistive voltage
divider of the power supply. Math is used here to undo the dividing effect.
Head microphones
The attributions for all 5 microphones placed in the head have are two: To capture audio in
real time for many purposes (voice recognition, live audio playback, etc.) and to conform a
sound direction detector improved to the one implemented in I-Droid01. The
implementation of this sound direction detector is out of the scope of this thesis; thus, this
subsection will only refer to the other microphones attribution.
To capture audio in real time what is needed is some mechanism to interrupt the normal
execution of the Arduino program each time a new sample needs to be captured. A timer
is the exact solution to solve this issue. From all the timers that Arduino Mega 2560 has
available, Timer5, which is originally used to manage PWM of pins 44, 45 and 46, can be
used as a counter since PWM is not needed for these pins. The setup of this counter
depends on the parameters set by the sampling process.
For this design, it has been decided to sample one of the head microphones at the standard
frequency rate of 11025 Hz, that is, 11025 samples/second. In order to guarantee that the
sampling process is done correctly, these are the facts that has been taken into account:
•
•
The higher the sampling frequency, the higher the bandwidth of the captured signal
but also smaller the time between samples. A sampling frequency of 11025 Hz
implies an interruption of the normal execution code every 90 µs, which is really a
very small time window. It is convenient that other functions shown before in this
thesis fit, at least individually, inside the window between samples.
The other important factor is the execution time of the Interrupt Service Routine
(ISR) that belongs to the counter’s compare match interruption, that it, the piece of
code executed every time the interruption is occurred. Since all the processing
tasks of this signal will be performed at the Raspberry Pi 2 B board, this ISR only
needs to capture the sample (using analogRead()) and send it through one of
the UART interfaces that connects both boards, since the purpose of one of them
is to support this microphone sampling process. The ISR lasts about 50 µs, thus,
the window between samples that the normal execution code has to operate is only
40 µs.
68
In order to send via the UART interface the captured sample from the Arduino Mega 2560
internal ADC, it has been decided to fit the 10-bit value obtained from the ADC to a Byte
variable, which is 8-bit long, by applying linear compression. The compression done does
not have any significant effect of quality loss. 11025 samples per second of a size of 8 bit
implies a bit rate of 110.25 kbps (counting start and stop bits), which fits perfectly in the
UART interface that is configured to work at 115200 bauds. Instead, sending the 10-bits
original sample value, which needs at least a 16-bits variable, requires a bit rate of 176.4
kbps, which cannot be sent.
The sampling frequency of 11025 Hz, as it can be seen, is a compromise between sound
quality and available resources. Next standard audio frequency rate, which is 16000 Hz,
implies an interruption every 62.5 µs, leaving only 12.5 µs to execute the rest of tasks. This
value is clearly insufficient. A part from that, it is not possible to send 16000 samples of 8
bits every second through an UART interface working at 115200 bauds, because the
required bit rate would be 160 kbps.
Managing of ultrasonic system
Figure 31. Algorithm to detect objects used in the I-Droid02 ultrasonic system.
The complexity of the ultrasonic system falls over the receiver, as it has been seen in
previous subsections. The need of some mechanism of synchronization increases the
complexity, but not too much as it will be seen in the following paragraphs. It is also
important to state that time control is the key of the ultrasonic system. For this reason, the
use of interruptions guarantees that pulses detection is done at the correct time instant.
Previous figure shows the algorithm used to detect objects in front of an ultrasonic sensor,
when the interruption is triggered, and compute the distance between it and the sensor.
The algorithm starts every time the interruption is triggered.
69
Arduino supports two kind of hardware interrupts: The external interrupt and the pin change
interrupt. The external interrupt is always bound to a certain GPIO and it triggers when its
input value goes high. A pin change interruption is bound to a set of GPIOs and it triggers
when the value of some of the bound pins changes. This is the one used for the ultrasonic
system, since it is necessary to monitor more than one GPIO for the same interrupt
triggering. It is possible to establish a mask for this interrupt, in order to pay attention only
to the three ultrasonic receivers from the GPIOs set.
A pin change interrupt implies that it will be triggered every time the value of one of the
masked GPIOs changes its value; both rise and fall edges. In the previous algorithm, it is
important to remark that only rising edges are relevant, so the other triggers must be
ignored to preserve the functionality of the algorithm.
The way to detect whether the detected pulse is a reference pulse or the reflection on an
object is to obtain the value of the three sensors at the same time. Due to the separation
between sensors, it is impossible to receive a reflected burst at the same time in all the
sensors. It is possible to read all sensors in the same instruction since they can be read in
the same ATmega2560 register. If the three sensors at the same time receive a pulse, it is
for sure a reference one. At this point, the algorithm is able to compute the distance when
the replica is detected, or decide that there is no object in front of the sensor if the replica
is not received. The sensor to be watched is a parameter that Raspberry Pi sets and
changes when necessary. By default, the center sensor is the one watched.
The last thing to take into account is the frequency of readings that the ultrasonic system
can support. According to the hardware description of the system, the transmitter sends a
pulse approximately every 10 ms. It implies that at least every 10 ms the normal execution
of the Arduino code will be interrupted to attend the ultrasonic system. The number of
readings could be excessively high. To avoid that, a scheduler has been established in
order to activate the pin change interrupt every 200 ms. When the previous algorithm
obtains a distance, either a valid or infinite value, the interrupt is disabled again.
Motors management
I-Droid02, as its predecessor, has 8 independent motors. In terms of software, the control
of each motor consists on applying a certain speed and direction, by using the two GPIOs
that they are intended for this task. The remaining GPIO, which is the one used to control
the position of the motor, slightly modifies its intention depending of the motor. That is, for
the motors that has limited angle of movement, the GPIO that is connected to an encoder
system is used to stop the motor when it reaches its limit. For the wheels’ motors, which
do not have any limitation, the encoder is used to measure distance covered by the wheel
(and obtain its speed for example). Finally, the hand motor is a particular case. Although
its movement is limited, it does not have any encoder but it measures the current through
the motor. Then, it is possible to stop it when the current rises suddenly; something that
occurs when the finger of the hand finds an impediment to continue its movement.
The fact of having PWM availability in all the GPIOs in charge of providing power to the
motors allows two concepts: To control the speed of movement of the motor and to slow
down its movements when a change of speed is required. Having a soft start and stop of
the motor movements is something that was missing on original I-Droid01 operation. The
action of changing the speed of a running motor cannot be done in a single execution cycle.
If not, the rest of tasks that Arduino board performs will need to wait for some seconds
70
while the motor’s speed change is being executed. Thus, this action has to be spread inside
all needed execution cycles. To be able to control motors data at any moment, all data is
placed in the global variables environment.
At this point it is relevant to distinguish between two groups of motors: Limited motors (all
with limited movement) and wheels motors. For every limited motor, the current speed,
direction and absolute position inside its possible path is stored globally. The absolute
position value is also reported to Raspberry Pi every time it changes.
To provide high-level access to the motors, this firmware provides the following functions:
•
•
motorStart(). For a stopped motor, function to start any motor by setting its new
position and speed (an integer value between 1 and 10). Arduino translates this
speed value to the corresponding PWM value, which depends on each motor.
According to the current position and the new one, Arduino decides the direction of
movement. When the motor reaches the position, Arduino stops it.
motorChangeSpeed(). For a running motor, increase or decrease its speed. To
stop a motor is a particular case of this function.
It is possible to stop a limited motor in movement using motorStop() function, before it
reaches the position set in motorStart(). However, its usage is only recommended
when the requested motor in movement has been started at low speeds, to avoid
misalignments.
When a motor is started, depending of the established direction, the associated relay
should be activated. When it is stopped, the GPIO in charge of that relay always go to ‘0’
logical value, in order to avoid current draining over the relay when the motor is stopped.
To reverse the direction of a certain running motor, it must be stopped before start it again
in the opposite direction, just to avoid abrupt movements.
Figure 32. Algorithm to monitor the absolute position of any motor with an encoder.
71
A part from the variables that store the current state of a certain motor, there is another
variable that stores the target speed that this motor should reach smoothly. The call of one
of the previous stated functions modify the value of the target speed variable. Inside the
normal execution of the firmware loop, there is a routine called motorsCheckSpeed() in
charge of compare current and target speeds of every motor in movement. If the values do
not match, this routine physically increases or decreases by one the current motor’s speed.
In the end, the current speed will reach the target one.
With all the previous routines, it is possible to manage the movement of all the I-Droid02
limited motors, but a mechanism to control its position in real time is needed. To control
the position of any motor, the routine called motorsCheckPosition() checks for every
motor in movement its absolute position, by counting marks of its coupled encoder sensor.
The algorithm of the previous figure shows the behavior of this routine for each motor,
leaving apart the hand motor which does not have an encoder.
For a motor in movement, the program reads the current value of its encoder sensor and
determines whether a new hole/space between hole has been detected or not. A hole is
detected when the value of the sensor is ‘0’ (phototransistor illuminated) and the previous
read values were ‘1’. The opposite case detects a space between holes. 10 last values are
known because at the end of the algorithm they are stored in a global variable. Depending
on the motor’s direction the value of a counter is increased or decreased. Finally, when it
reaches the target position set before, the motor is stopped.
For the hand motor case, since the only parameter to check is the amount of current
crossing the motor, the previous algorithm is simplified. In this case, when the motor is in
movement the condition to stop it is to have a current value above a defined threshold,
which has been calibrated as a function of the strength that the hand applies over the object.
To compute wheels’ motors speed, the time difference between holes is used to compute
angular speed, since the total number of holes for each revolution is known.
The reading of the sensors is done by polling them. It is practically impossible to miss one
hole since the time while the phototransistor is watching a hole is much higher than the
execution time of the firmware, of a firmware loop.
Finally, the last issue to describe about the motors management is the concordance
between the physical position of a certain motor and the position known by the Arduino
firmware, which could not necessarily coincide. All the motors have a reset position, when
all the holes’ counters worth 1. When the robot is switched off normally all motors go to its
reset position. If a motor has been moved manually or the shutdown was not done properly,
a misalignment will occur.
To calibrate the motors position again, the Raspberry Pi – Arduino main driver (which will
be discussed in the next subsection) will include a function to move each motor manually
and slowly towards its reset position, overriding Arduino previous algorithm that prevents
motor operation when it has reached its end of movement. This will be the only allowed
situation; for the rest of cases, Raspberry Pi – Arduino main driver will not allow to
overcome motors end of movement position.
The task of motors calibration could be done automatically, but it implies to start moving
every motor, finding automatically its reset position, each time I-Droid02 boots. This
strategy was the one chosen for I-Droid01 software design. However, to avoid I-Droid02 to
start moving motors every time it boots and taking into account that a misalignment in this
72
application is a very uncommon case, it has been decided to manually calibrate motors
only when necessary.
Regarding I-Droid02 wheels, each one is controlled by an independent DC motor allowing
two degrees of freedom. It is possible to move I-Droid02 over a plain surface at any
direction, following straight or curved trajectories until a certain total distance has been
covered. A part from that, Arduino implemented software makes it possible to tell I-Droid02
to spin over itself at different speeds a certain number of degrees.
The keys to define I-Droid02 movement has been explained before in this thesis, under 3.5.
433 MHz I-Droid02 native remote control firmware subsection. As it can be seen, the analog
packet send by the remote control (see Table 12) follows the same format:
•
•
•
•
A direction flag to indicate the trajectory (straight/curved forward/backward, left/right
spin or stop).
The trajectory speed (ignored by spinning trajectories).
The turning speed, only applicable for curved trajectories.
The maximum distance to cover / angle to spin with previous defined movement. In
the case of setting a movement through remote control, this parameter is set as -1,
which means no distance/angle limit. In this case, the movement is maintained until
a new movement is set (including 0x00 direction flag that means stop).
At this point, the Arduino Mega 2560 task is simple: Translate these previous parameters
to those that are related to each wheel motor: direction and PWM value. The PWM value
of each wheel is translated from the combination of both trajectory and turning speeds. This
translation from direction flag and trajectory and turning speeds to those motor values is
not simple and immediate to understand. Table 14 shows this translation process for all
possible cases, taking as a reference the 0x00 direction (stop). In order to understand
better this table, the axis image included in Table 12 helps understanding both new and
previous direction flags, since only the corresponding flags of those directions are reported.
When a movement has been set, Arduino software must maintain it. In the case of straight
trajectories (direction flag set to 0x60 or 0x90), an additional algorithm is activated. This
algorithm ensures that rectilinearity is maintained all the time, by increasing the speed of
the slower wheels or, if it cannot be increased, by reducing the speed of the faster wheel.
To do it, it looks permanently to each wheel encoder and monitors the time difference
between two consecutive holes. Time difference can be measured using
millis()Arduino native function. In an iterative process, measured time difference at
both wheels tends to be equalized. Table 14 helps to understand this algorithm. This
algorithm is also used in both spinning movements (direction flag set to 0x06 or 0x09), in
order to avoid I-Droid02 moving in spiral.
Finally, the same procedure explained before for limited motors can be used to measure
distance/angle covered by both wheels. This tells to Arduino Mega 2560 when it is time to
stop a previous movement set (if distance/angle value is different from -1), when distance
or angle set is achieved.
73
New dir. flag
0x06
0x09
0x06
0x09
0x06 / 0x09
0x60
0x90
0x60
0x90
0x60
0x90
0x60 / 0x90
0x66
0x96
0x69
0x99
0x66
0x96
0x69
0x99
0x66
0x69
0x96
0x99
0x(6/9)(6/9)
Current dir. flag Left wheel direction Right wheel direction Left wheel PWM value
Right wheel PWM value
Backwards
Forwards
Both directly obtained from the turning speed
0x00
parameter. Trajectory speed value is ignored
Forwards
Backwards
0x06
Update smoothly both current PWM values according
Maintain current direction
to new turning speed parameter
0x09
Rest of dir. flags
Stop wheels and act as 0x00 current direction flag
Forwards
Forwards
Both directly obtained from the trajectory speed
0x00
parameter. Turning speed value is ignored
Backwards
Backwards
0x60
Update smoothly both current PWM values according
to new trajectory speed parameter
0x90
0x66
Increase current value
Update current value
Maintain current direction
0x69
Update current value
Increase current value
0x96
Increase current value
Update current value
0x99
Update current value
Increase current value
Rest of dir. flags
Stop wheels and act as 0x00 current direction flag
Forwards
Forwards
Low mean value as
Mean value of trajectory
turning speed increases
speed and turning speed
Backwards
Backwards
0x00
Forwards
Forwards
Mean value of trajectory
Low mean value as
speed and turning speed
turning speed increases
Backwards
Backwards
0x66
Update PWM according
Update PWM according
to turning speed weigh
to mean value
0x96
0x69
Update PWM according
Update PWM according
to
mean
value
to turning speed weigh
0x99
Maintain current direction
Low mean value
Update PWM value
0x60
Update PWM value
Low mean value
Low mean value
Update current value
0x90
Update current value
Low mean value
Rest of dir. flags
Stop wheels and act as 0x00 current direction flag
Table 14. Movement parameters to wheels’ motors parameters translation.
74
Figure 33. Algorithm used to maintain I-Droid02 rectilinearity for straight trajectories.
3.8.
I-Droid02 Driver: Main interface with I-Droid02 peripherals
The core of I-Droid02 software design is presented in this subsection. The main objective
of this software is to provide access to every I-Droid02 peripheral at the same access level,
without mattering the physical way they are accessed. As an example, the red LEDs of the
left eye (which belong to Arduino board peripheral) and the camera (which is directly
connected to the Raspberry Pi 2 B camera port) will be accessed using the same procedure.
I-Droid02 Driver classifies the peripherals in terms of connection and associates a handler
to it. A handler is a safe way to access the peripheral because it enables to request access
to the peripherals. In the case of Arduino board, since it is connected to several different
devices, I-Droid02 Driver has also several handlers associated.
This method ensures a controlled access to every peripheral, concentrating all the
peripherals access requests and allowing only one application to interact with a certain
peripheral at the same time. An interaction could mean both an input and output operation;
but in some cases, such as reading temperature sensor or battery voltage level, only
reading a value is required. In these cases, the software presented in this subsection will
be in charge of reading sensors data and making it accessible for all the high-level
applications, even at the same time so mutual exclusion will not be required.
This software, thus, coincides with the definition of a driver. However, to facilitate the
programming and startup of this application it will be run as a Linux daemon, instead of a
kernel driver. When this software is running, the way that the rest of applications have to
follow to communicate with a handler is by using UNIX sockets. If the handler allows
multiapplication access, I-Droid02 Driver prepares and maintains updated certain text files,
each one associated to a certain handler. All handlers’ files are placed under
/tmp/idroid02 directory of the Raspberry Pi 2 B filesystem. Only Arduino IDE
application, during Arduino Mega 2560 firmware update can bypass I-Droid02 Driver and
communicate directly with the board.
75
High-level application
Socket/File I/O
Socket/File I/O
I-Droid02 Driver application
Arduino IDE application
Linux drivers
I-Droid02 peripherals physical access layer
Figure 34. I-Droid02 peripheral access layer diagram.
The I-Droid02 driver uses several Linux drivers to communicate with the peripherals, both
directly or by sending commands to the Arduino board. As an example, for Arduino Mega
2560 board case, Linux drivers used are two:
•
•
ttyACM0: Represents the serial over USB connection which also is used to identify
the board from Raspbian. It provides the primary UART interface which supports
the asynchronous triggering of action commands (like turn a LED on) and also the
reporting of the state of the sensors that do not require commands. This kind of
data can be considered plesiochronous, that is, an information that is send every
certain period of time but without bothering of having delays in data delivery.
ttyAMA0: Secondary UART interface used to receive the microphone sampled
audio stream. This data is completely synchronous; any jitter shall be minimized.
For this reason, although this device also communicates with Arduino Mega 2560
board, it has a serial interface for itself and it is also managed as a different
peripheral from the driver’s point of view.
Primary UART interface is the only way to access to any sensor/actuator that Arduino board
is managing, thus, a command protocol is used to establish the connection. The
communications process consumes execution time from both boards; however, for Arduino
board this fact has an important impact of its final behavior. In order to simplify signaling
between the two boards, the protocol used is very simple: a letter to identify the
sensor/actuator requested and a minimum of extra needed Bytes. If a certain
sensor/actuator can request/execute more than one command, a second letter is used to
identify this action. At the same time, Arduino board also uses the same protocol to send
some sensors data periodically, like temperature, battery voltage or ultrasonic sensors
values.
I-Droid02 Driver is designed to act beyond a simple signaling bridge between high-level
applications and the hardware interfaces. In most cases the execution of high level
commands requires a coordination among more than one actuator. As an example, a highlevel command could be “move forward 1.5 meters at 60% of full speed”. Arduino Mega
2560 interface (explained in the previous subsection) allows to move left and right wheels
with separate commands, and also provides the wheels’ encoders count to know how many
degrees each wheel has been moved. The execution of the previous high-level command
76
requires the coordination of previous exposed sensors and actuators. The task of I-Drodi02
Driver is to translate previous high-level command to the ones that Arduino board needs.
Multithreading is the basis of I-Droid02 Driver software design, a capability supported by
both Raspberry Pi 2 B microcontroller and running operating system Raspbian. An
interaction with a Linux driver can only be managed by a thread at the same time, thus,
those previous running threads avoid any other processes outside I-Droid02 Driver to
interact directly with the hardware.
Every UNIX socket is controlled by a different thread. The use of sockets prevents the
access of more than one high-level application at the same time, since each socket is
programmed to allow only a connection simultaneously. However, there are other cases
where the access to a certain sensor or data stream could be done by many applications
at the same time. The access to the battery and temperature sensors data is an example
of this. In these cases, I-Droid02 Driver shares the information using text files. Following
the example of microphone audio stream, every time that a new sample is received through
ttyAMA0 serial interface, the handler in charge of this task writes the received value in a
file. The “client” high-level applications are waiting for a file change and the reading is
produced at the same time by all of them. In order to make wait lockingly and synchronize
all applications, the routines in the inotify library are used. Inotify allows a thread to
wait blocked waiting for an interruption. It is possible to setup an inotify watcher to a
certain file or directory and watch whether a creation, modification, writing and/or other
procedures has been produced.
In most cases, for the I-Droid02 Driver, the thread in charge of managing a certain
peripheral is the same in charge of managing the socket providing access to that peripheral
(the handler itself). This thread only awakes when a high-level application requests the use
of the involved peripheral, and goes to sleep when the action has been completed.
However, there are other cases, such as the access to sensors and actuators connected
to Arduino Mega that cannot be handled by only one socket. This is due to the fact that
output only peripherals don't request exclusive access. However, all sensors and actuators
are accessed through the same UART interface and, thus, from the same I-Droid02 Driver
thread. This is the reason to allocate more than one handler to Arduino Mega 2560 board.
The rest of the peripherals only share one handler. Next figure helps to understand the
handler concept, focusing the example on the Arduino Mega board:
HLA4
HLA3
HLA5
HLA2
LEDs handler
Thread in charge of handle
with battery and
temperature sensors data
Motors handler
Display handler
HLA1
Thread in charge of
serial communication
File
Socket
HLAx: High-Level Application
Figure 35. Example of I-Droid02 Driver operation with the Arduino Mega peripheral.
77
Figure 36. I-Droid02 Driver handler thread software flow diagram.
Handler name
Batteries data
Camera
Chest buttons
Display
GPIO for prototype board
LEDs
Microphone
Motors
Programmable buttons
RFM31B 433 MHz RX
Temperatures data
Torch tool
Touch sensor
Ultrasonic sensors
Ultrasonic sensors data
Commands
6
2
7
9
5
7
2
5
5
2
3
-
Handler full filesystem path
/tmp/idroid02/batteries.txt
/tmp/idroid02/camera
/tmp/idroid02/chestButtons
/tmp/idroid02/display
/tmp/idroid02/gpios
/tmp/idroid02/leds
/tmp/idroid02/microphone.txt
/tmp/idroid02/motors
/tmp/idroid02/programmableButtons
/tmp/idroid02/rfm31b
/tmp/idroid02/temperatures.txt
/tmp/idroid02/torch
/tmp/idroid02/touchSensor
/tmp/idroid02/ultrasonic
/tmp/idroid02/ultrasonic.txt
Table 15. List of I-Droid02 Driver handlers.
78
When a high-level application wants to access a certain handler, it opens a UNIX socket
and starts the interchange of information. In the Appendix 10. The I-Droid02 Driver
reference manual, the complete list of available handlers, the associated sockets path or
text file and the commands that each handler implements are provided. Finally, in the
Appendix 9 the complete source code of I-Droid02 Driver is provided.
Finally, it is important to remark that the only way to make this platform completely
functional is that all high-level applications respect the rules exposed by I-Droid02 Driver
To ensure that all high-level applications wanting to access a certain handler can do its job
without excessive delays, the socket connection must be freed immediately after the last
known task has been executed. Moreover, a handler connection shall only be requested if
there are new tasks to be executed or the current state of a certain device must be checked.
3.9.
Setup program
The first high-level application to describe is the one that is in charge of showing to the user
the state of the most relevant I-Droid02 sensors, such as battery voltage levels or the
temperature. At the same time, it must also provide a way to setup main parameters of the
robot, such as connect I-Droid02 to a Wi-Fi network or reposition any limited motor that
could not be placed in the default position on I-Droid02 boot, by only using the LCD display
and the chest buttons as a user data input.
The main objective of this application is to emulate the original behavior carried out by IDroid02 predecessor in idle mode. After I-Droid01 booting process was complete, the
message “I-Droid01 Ready” was printed on the display. Then, by pressing the chest buttons,
it was possible to move through different screens that showed up the value of the battery
and temperature sensors or to execute preprogrammed behaviors (as explained before at
subsection 2.3.). Except this last feature, which is now carried out by the programmable
buttons, the rest and some new ones are now available through Setup program.
This high-level application is designed to be run at the end of I-Droid02 booting process
and remain active when the current running tasks and applications do not require the user
intervention through the display and the chest buttons. Thus, chest buttons handler from IDroid02 Driver must remain captured all the time. Doing so conflicts with the exposed rules
for Driver handlers in the previous section. In order to be able to use chest buttons handler
in other applications, Server high-level application includes a UNIX socket server pointing
at /tmp/idroid02/setupEnd path. If any application connects to this socket, the Setup
application will quit.
As it happened in I-Droid01, when I-Droid02 end its booting process (and I-Droid02 Driver
application has been started), Setup program is also started, provoking the display to show
“I-Droid02 Ready” message. When right chest button is pressed, the “Status menu” screen
appears. This screen shows the following information in successive screens every time the
central chest button is pressed:
•
•
•
Battery voltage and discharge percentage of I-Droid02 main battery. If the 433 MHz
native remote control is turned on and in coverage, its battery status is also shown.
Temperature values from ambient sensor and Raspberry Pi and remote control
receiver microprocessor internal temperature. If the remote control is present, its
transmitter microprocessor internal temperature is also shown.
Distance value in centimeters provided by the current selected ultrasonic sensor.
79
•
IP addresses of the Ethernet and Wi-Fi interfaces.
If right chest button is pressed from any of the previous screens, the “Setup menu” screen
appears. This menu allows to setup one of the following parameters:
•
•
•
•
•
Calibrate motors: If one of the limited motors are not placed in the default position
after I-Droid02 booting process, it can be automatically calibrated using this
parameter. In the default state, head must be centered and tilt axis must point as
high as possible, both arms must be placed at the lowest position, hand must be
opened and hip must be completely down.
Display contrast: It can be changed to make it darker or lighter.
Setup Wi-Fi network: The Wi-Fi network which I-Droid02 is connected to can be
selected using this menu.
Speaker volume: Change the current speaker volume.
Update software: Update all packages and Raspberry Pi firmware following
autoupdate.sh script (See subsection 3.6 for details).
Both Wi-Fi and speaker settings are changed by Setup application directly by performing
calls to the Raspbian operating system. The rest of settings are changed through I-Droid02
Driver application. The espeak Linux application is the text-to-speech synthesizer
application used to allow I-Droid02 to speak. In some of the previous settings it is used to
provide more information to the user on how to setup some parameters.
3.10. 433 MHz native remote control server
The purpose of this high-level application is to allow I-Droid02 to be controlled using its
native remote control, which has been described in subsection 3.3. This software takes
advantage of I-Droid02 Driver application features to access to those needed devices. In
this case, the remote control sends all commands using its radio frequency transmitter at
433 MHz. As the remote control only acts on the motors, only RFM31B 433 MHz RX and
Motors handler are used by this application. The first handler delivers directly to this
application the received packets from the remote control. Thus, the purpose of this
application is simply process each received packet and move requested motors using
Motors handler.
Looking at remote control hardware characteristics in subsection 3.3 and software
implemented (see subsection 3.5) it can be seen that this remote control also sends a
control packet with information related to the battery and transmitter state. Moreover, when
one of the A, B and C buttons are pushed, a packet is also sent. However, this kind of
information is processed by others handlers of I-Droid02 Driver so this server application
do not care about it.
It is important to remark that the only way to make this server application functional is by
capturing all the time the RFM31B 433 MHz RX handler, in order to be able to receive and
process packets infinitely. Obviously, Motors handler is only captured when a packet has
been received and immediately freed when it is processed. The behavior case of RFM31B
433 MHz RX handler goes clearly against I-Droid02 Driver handler rules exposed in its
80
reference manual (see Appendix 10), although this handler has been designed ad-hoc for
this application.
RFM31B 433 MHz RX handler can be used for other purposes a part from receive packets
form remote control. In order to be able to use it in other applications this one also includes
a UNIX socket server at /tmp/idroid02/remoteServerEnd path. If any application
connects to this socket, the remote-control server application will quit after processing the
next received packet, freeing RFM31B 433 MHz RX handler. This server application is
considered to be one of the I-Droid02 core applications. If any application forces it to quit,
it is responsible to run it again when it finishes using RFM31B 433 MHz RX handler.
This application runs in an infinite loop following the same behavior all the time: It waits for
a packet to be delivered by RFM31B 433 MHz RX handler. While it is waiting, it does not
consume any CPU resources. When a packet is received, the server application connects
to the Motors handler to execute received commands, but first it retrieves all relevant
motors information in order to avoid, for example, to run any motor which is currently
running. Every received packet must be acknowledged with the ‘A’ Byte (or ‘R’ to say
RFM31B 433 MHz RX handler to stop sending packets to this application). All packets are
read and processed, but only new commands are delivered to Motors handler. If a certain
motor has been started and another start motor command is received, the server
application ignores the command.
There are two kind of packets, as explained before in subsection 3.5. The analog packet
information sent can be directly executed as a command to Motors handler without any
kind of translation process. However, digital packet received data needs some translation
before call corresponding Motors handler command to start any limited motor. For every
limited motor, digital packet only indicates the direction of movement (up, down, left, right,
etc.), but Motors handler command requests the motor number to be moved, the new
absolute position to reach and the speed of movement. Motor number can be set,
according to table shown in Appendix 10 under Motors handler section. Since digital packet
only says the direction to move the requested motor, it is assumed by this application that
the final position will be its maximum limits (upper or lower limit according to direction
information). When the remote-control stops requesting the movement of each
corresponding motor, this application will stop it automatically (or Arduino Mega 2560 board
will stop it if the final position is physically reached). Regarding motor speed, all limited
motors will be moved always at maximum speed.
Finally, RFM31B 433MHz RX handler also notifies to the application which is connected
when it stops receiving packets from the remote control. The native remote server
application takes advantage of this information and turns off all active motors, including
wheels’ movement, in order to stop any I-Droid02 movement if any packet is received from
the remote control.
The complete source code of this application can be found at Appendix 12.
81
Next figure shows the complete software flow diagram for this application:
Figure 37. 433 MHz native remote control server software flow diagram.
82
3.11. Future work: Other high-level applications
All the tasks involved in I-Droid02 Project that are part of this thesis have been described
so far. Taking the original I-Droid01 as a reference; at this point the new electronics design
and the main software that makes I-Droid02 functional and useful have been shown.
However, there are still many pending tasks to develop, since not all the objectives of this
project have been accomplished.
In order to incorporate to I-Droid02 all those features and behaviors that were part of the IDroid01 core, new high-level applications must be developed, using the already
programmed software base, shown in subsection 3.8. Every new behavior can be seen as
a new high-level application that takes advantage of the available resources. It is also
important to highlight the fact of using a Raspberry Pi board as the I-Droid02 brain. It is
known the wide potential that this board can handle and the multitude of applications
compatible with Raspbian operating system that have been developed up to now. In some
cases, an I-Droid02 behavior can be carried out using directly an application already
developed (sometimes with a light modification), but in other cases, ad-hoc applications
such as previous described ones shown will be required.
Before start with the description of those I-Droid02 tasks that are pending to develop, it is
important to remember that the original I-Droid01 robot was conceived to act in two clear
distinct scenarios: autonomous and controlled behavior. In this thesis, the development of
the most important part of the high-level applications that belong to the controlled behavior
scenario have been described. However, I-Droid02 does not include at this point any
application that makes it move autonomously over a plain surface, avoiding obstacles; or
any other one that takes advantage of any artificial intelligence or another one that provides
to I-Droid02 different moods, as a function of the state of several sensors or the user actions
when interacting with I-Droid02. Although these tasks were developed in a very initial or
primitive phase, they were the essence of I-Droid01 and the reason of why RoboTech S.L.R.
classified it as a last-generation robot.
Following the line of argument expressed in the previous subsection, the next high-level
application to develop already belongs to the controlled behavior scenario. The objective
of this application is to give to the user a way to get full access and control of all I-Droid02
devices through an external computer, smartphone or tablet. The application tends to
emulate but also improve I-Droid01 PC/Mobile Control application, explained in subsection
2.3. Thus, beyond that a simple I-Droid02 remote control (which is already implemented),
the objective of this application is to act as a control room. This high-level application will
be structured in a client-server pattern: A server application, running on Raspberry Pi, will
be in charge of establish a socket connection using TCP/IP protocols between each IDroid02 devices (through I-Droid02 Driver or direct OS calls) and the client application.
With the previous description of a future developed application, all the high-level
applications that belong to the controlled behavior scenario have been described.
Comparing the features that I-Droid02 offers at this point with the ones offered by its
predecessor, there are still some pending ones to offer:
•
A voice recognition system: The original I-Droid01 voice recognition system
(explained in subsection 2.3) had many limitations regarding understood word sets
and phrases. Following the requirements expressed in subsection 2.4, I-Droid02
must be powered with a voice recognition system taking as a reference Microsoft
83
•
•
•
•
Cortana, Apple Siri or Google Voice. There are some developed voice recognition
projects for Raspbian OS such as Jasper Project [16], but in any case, an adaption
to the I-Droid02 devices must be done. For example, to make possible I-Droid02
come towards the user when it says a command such as “Come here!”, an
intermediate application that catches the previous command and makes possible
the action (coordinating sensors and actuators such as the camera, the ultrasonic
transducers and the motors) should be necessary. Another aspect to remark is the
fact that any voice recognition system working under Linux operating systems uses
a microphone compatible with ALSA driver to receive commands. Nowadays, the
microphone data provided by I-Droid02 Driver is not ready to work with ALSA; thus,
it should be adapted or, instead, use another ALSA compatible microphone for
voice-recognition tasks.
Implement the sound direction detector system. As explained in subsection 3.2, IDroid02 has 5 microphones placed on the head and distributed around the
horizontal plane. The objective of this hardware is to improve the original I-Droid01
system that only used 3 microphones and was able to only the direction of a clap
sound (left, right or back) with very low precision. The new system should provide
an angle of arrival with respect to the center, being more precise. The intention is
to apply arrays of antennas theory over the 5 microphones, developing the
corresponding software at Arduino Mega 2560 board and export the results through
a new I-Droid02 Driver handler, using a procedure similar to that used for battery,
temperature or ultrasonic sensors (write periodically the angle of arrival in a text
file).
Implement an image recognition system and improve the original image-follower
system. Original I-Droid01 had a routine that was able to identify a solid color object
and follow it by moving the head. A similar high-level application should be
developed. Moreover, nowadays it is also possible to use image processing
techniques to detect faces, shapes and the direction of movement for objects in
movement, among many more things.
Give moods to I-Droid02. The main idea of this feature has been explained before
in this subsection.
Create a high-level application that makes I-Droid02 move autonomously, as
explained before in this subsection.
The previous list can be enlarged as much as wanted. Any idea that involve the use of any
I-Droid02 devices and features but also Raspberry Pi and Raspbian features can be
implemented as a high-level application. This fact is important in comparison with the
original I-Droid01 system, where the user was only able to develop small routines using
partially the available devices and features, in an isolated device. Now, since I-Droid02 can
be connected to the Internet, the potential new applications and features grows.
84
4.
Results
Several pictures taken during the assembling process and the results of each I-Droid02
Project developed task, included in this thesis, are shown in this section. Whenever
possible, the resulting values are given in comparison with the exposed values for the
original I-Droid01 (see 2. State of the art of the technology applied in this thesis section),
in order to see the improvement of those aspects.
The results presentation is separated in two subsections, hardware and software results,
following the same structure as previous sections. Finally, the last subsection includes a
detailed explanation of the programming environment used to code and build the software
applications shown in last subsections of the previous section.
The extracted conclusions of the following results are found at 6. Conclusions.
4.1.
Hardware design results
Next pictures were taken during the assembly process of I-Droid02 and its remote control:
Figure 38. Pictures of I-Droid02 and its remote control taken during assembly process.
85
Figure 39. Inside Raspberry Pi 2 B box.
The final aspect of I-Droid02 can be seen at the following pictures, which can be compared
with those shown in Figure 4:
Figure 40. I-Droid02 final aspect.
At first look, the I-Droid02 aspect is almost the same as its predecessor, with the exception
of the black box placed behind its back. The original box, which had the same gray color
that the rest of the structure, was not possible to adapt mechanically to allocate Raspberry
Pi 2 B board since it was used to allocate most of the I-Droid01 original entities boards
(Base and Arms Controller, Motherboard, Brain & Vision, Bluetooth and Voice Module).
For this reason, a black box with a very tailored size is replacing the original one.
Aesthetically speaking black box color does not highlight too much, in comparison with
original gray box, because other original I-Droid01 parts such as hip structure, tires or arm
screw covers were also black.
Next set of pictures show the final aspect of the 433MHz native remote control:
86
Figure 41. 433 MHz I-Droid02 native remote control final aspect.
This version of the remote control follows the hardware specifications explained in
subsection 3.3. The joystick position makes straightforward its movement using right thumb,
while at the same time the left arm can be used to press any other button or press the hip
up/down switch. The symmetrical distribution of the different elements gives to the user an
arrangement sensation, while the labels helps him to know the action of every button.
Finally, the USB square form connector placed at the right side of the remote control is
connected to the Arduino Nano USB port, making possible to upgrade the firmware
(program Arduino board) and also to change the central frequency of the 433 MHz
transmitter; when the remote control is connected to I-Droid02 (see Appendix 10 for details).
A small inconvenience of this remote control could be the aesthetical aspect, which can be
improved in future versions.
87
Regarding figures of merit, the most important parameter to evaluate for any autonomous
system, in hardware terms, is its power consumption and its approximately battery life,
which can be computed using the battery capacity parameter provided by the manufacturer.
Next table shows the measured power for both I-Droid02 and its native remote control in
two scenarios: idle and peak power.
In idle state, I-Droid02 is only running Driver, Setup and Remote Control Server
applications (see 3.8, 3.9 and 3.10 subsections respectively) and Raspbian background
processes. For remote control case, it is just waiting for a button press/joystick move. In
peak power state, I-Droid02 is moving away (wheels’ motors are the ones who consume
more energy) and remote control is sending data.
Both idle and peak power values are used to compute I-Droid02 and remote control
batteries autonomy, assuming that I-Droid02 is consuming its peak power at 60% of
running time, while remote control is transmitting data at 75%. Moreover, for the I-Droid02
case, the autonomy is compared with the original I-Droid01 one.
Idle power
4.5 W
360 mW
I-Droid02
Native RC
Peak power
11 W
800 mW
Original autonomy
2 hours
-
New autonomy
3 hours and a half
7 hours
Table 16. I-Droid02 and native remote control power consumption results.
As it can be seen, the autonomy of the robot has been increased a 75% in comparison with
the original I-Droid01 battery life. Using the Li-Ion battery specified in subsection 3.2 and
an electronic design also focused on save as much energy as possible helped to
accomplish this figure of merit.
To evaluate the quality of the electronic circuits that are part of the hardware design, other
parameters can be measured. Among all the electronic circuitry used to interconnect every
sensor and actuator to Arduino Mega 2560 board, the most interesting to evaluate are
those used to capture and amplify audio coming from each microphone placed around the
head.
Captured sample waveform
Original waveform
1
1
0.8
0.8
0.6
0.6
0.4
0.4
0.2
0.2
0
0
-0.2
-0.2
-0.4
-0.4
-0.6
-0.6
-0.8
-0.8
-1
-1
0
0.2
0.4
0.6
0.8
Time (seconds)
1
1.2
1.4
0
0.2
0.4
0.6
0.8
1
1.2
1.4
Time (seconds)
Figure 42. Original waveform (left) of ‘I-Droid02’ word vs captured sample (right).
88
Head microphone circuits are based on the low noise TLC272 operational amplifier.
Performed test consists on compare the waveform and the spectrum of an audio signal
containing the word “I-Droid02” generated using espeak Linux command. Figures 42 and
Figure 43 have been generated using MATLAB software application.
The comparison of both waveforms done in Figure 42 shows two aspects: The dynamic
compressions of the phonemes, due in part to the electret microphone frequency response,
and the noise addition which can be seen clearly in the silences between phenomes. The
obtained signal to noise ratio (SNR) of the captured sample is about 27 dB, which is enough
for a human to understand the speech but also to be used for voice-recognition techniques.
Original spectrum
0
-5
-10
-15
-20
-25
-30
-35
-40
-45
-50
0
2000
4000
6000
8000
10000
12000
Frequency (Hz)
Figure 43. Original spectrum (left) of ‘I-Droid02’ word vs captured sample (right).
Before start with the frequency analysis, it is important to remember that Arduino Mega
2560 board takes continues samples of head-back microphone (the one used for analysis)
at a frequency rate of 11025 Hz. Instead, the original ‘I-Droid02’ word generated with
espeak Linux command is sampled at 22050 Hz. Thus, according to Nyquist criterion it
has no sense to analyze the spectrum of the captured sample beyond 5012.5 Hz. The
spectrum truncation is the principal cause of the waveform smooth effect seen before.
The fact of subsampling the original sound as is causes the appearance of aliasing;
however, as explained before in subsection 3.2, the electret microphone frequency
response helps to avoid this effect. As it can be seen in Figure 43, frequencies around
3700 Hz are slightly boosted to the detriment of very low frequencies (under 100 Hz) which
are diminished. It is considered for this analysis that audio circuit, based on TLC272,
frequency response is flat between 0 Hz and 5000 Hz.
4.2.
Software design results
In this thesis, most of the software developed for I-Droid02 Project consist on the needed
software base to run any high-level application, such as those that are shown in
subsections 3.9 and 3.10. It is difficulty to provide visible results of the software running in
89
background. The most critical software element is the one running on Arduino Mega 2560
board.
As explained in subsection 3.7, a software that acts as a bridge between sensors/actuators
low-level programming and high-level commands must be fast; in comparison with the
minimum time variation perception by any human (about 150 ms). This is a very important
characteristic in a real-time system. The figure of merit to evaluate, thus, is the time needed
for Arduino Mega 2560 to run specific commands, such as turn on/off a LED or start any
motor.
To measure time differences at Arduino Mega 2560 code, the Arduino native routine
micros() is used. micros() can only detect time differences of at least 4 µs. While
showing the obtained results, it is important to remark how Arduino Mega 2560 software
timing is structured:
•
•
•
Arduino Mega 2560 runs the code placed in loop() routine indefinitely:
o If a command coming from Raspberry Pi board is received from primary
UART interface, 4 µs for each received Byte of the command is required.
Depending on the action to be taken after receiving the command, additional
time is required:
 To change the value of a variable or read/change an ATmega2560
register needs less than 4 µs.
 To clear the display needs 2.3 ms. A delay of this magnitude is
required by the display microcontroller after clearing the display;
before write it again.
 To write data on display requires about 288 µs for each character
written. At the beginning, to set the cursor to the appropriate row and
column it requires the same time.
 Every action that is related with a motor lasts between 4 µs and 8 µs
to be executed, depending on whether to change the associated
relay is required or not (see subsection 3.2) and the number of
variables that belong to the motor to be set (they can slightly vary
according to the motor). All motors have a certain inertia to start,
stop or change its speed. This time does not have any impact on the
code execution time.
o If no chest button is pressed, the touch sensor is not touched and all motors
are stopped, the loop() routine needs 116 µs to complete. This value
means that the loop() routine is interrupted at least 3 times for microphone
ISR before complete, as explained below.
o The functions that are in charge of checking the current position and speed
of every motor, among other variables, are those that require more time
inside loop() routine execution.
 If any motor is moved, this piece of code lasts 20 µs to execute.
 4 additional µs are required for each motor in movement; time
necessary to read the encoder or to check the current motor speed.
loop() routine is interrupted every 90.7 µs to run an Interrupt Service Routine
(ISR) in charge of taking a sample from the back-head microphone and send it
through secondary UART interface. The whole ISR lasts about 50 µs.
About 40 µs, thus, is the time between interruptions to execute loop() routine.
However, every 250 ms the loop() routine is also interrupted by another ISR in
90
charge of ultrasonic sensors. The ISR routine lasts between 8 and 12 additional
microseconds to detect a flank, either the reference burst of its echo.
Regarding software that is run on Raspberry Pi 2 B board, it is optimized to ensure that the
minimum CPU resources are consumed. This strategy ensures a fast awaking time of each
task to be executed by the microcontroller. The most relevant case is each Driver handler
(see subsection 3.8) which run in independent threads, but those threads are sleeping
when a handler is inactive. This means that the consumed resources of an inactive handler
are null.
4.3.
Programming environment
The I-Droid02 is a self-contained system down to the programming environment. The way
used to program Arduino Mega 2560 and develop main I-Droid02 applications that run at
Raspberry Pi 2 B board is to code the programs directly on the Raspberry Pi board.
Raspbian operating system is meant for use Raspberry Pi board as a PC. Other boards do
not embed a Linux distribution with graphical interface, thus, the only way to manage them
is through a serial communication.
Raspberry Pi desktop can be managed directly if the board is connected to a screen
through the HDMI connector and a mouse and a keyboard are connected to the USB ports.
For the I-Droid02 case, nevertheless, the most comfortable way to code is by using a
remote desktop session through a Virtual Network Computing (VNC) client:
Figure 44. Desktop aspect of the I-Droid02 on-board Raspberry Pi.
Raspbian operating system includes natively GCC application to compile C source code
files to be run by Raspberry Pi ARM microprocessor. To code in C, Raspbian offers different
solutions; such as write source files directly using a text editor or install an Integrated
Development Environment (IDE) application. To facilitate the programming task, it has
91
been decided to use an IDE application able to run on an ARM microprocessor. The
selected application is Netbeans, from Oracle company, which runs over a Java virtual
machine (included by default in Raspbian).
Figure 45. Netbeans IDE running at I-Droid02 on-board Raspberry Pi.
The fact of using an IDE application makes things much easier: It is not necessary to care
about code makefiles, this task is done automatically. Moreover, an IDE application such
as Netbeans assists during coding tasks by indicating those parts of the code that contains
syntax errors and other errors and warnings detected by the compiler. Moreover, it includes
a debugging tool.
Finally, to program Arduino Mega 2560 board the best way is to use its own application
called Arduino IDE. There is an official version compatible with ARM microprocessors.
Figure 46. Arduino IDE running at I-Droid02 on-board Raspberry Pi.
92
5.
Budget
As it has been seen in previous sections, this thesis is part of the I-Droid02 project that
englobes the full upgrading of initial I-Droid01 robot and the addition of new functionalities.
The hardware update, as part of this thesis and developed during the first part of the project,
is responsible of the most amount of the total invested money.
Before starting with the actual I-Droid02 project investment description, total invested
money should be compared with the initial investment on buying I-Droid01 collection from
Planeta DeAgostini, comprising 90 fascicles. This collection, as explained before, cost
around 900 € invested during two years (from September 2006 to July 2008). This amount
of money justifies the initial hardware cost, the cost of mechanical parts, the final software
coded and the full I-Droid01 design (besides the publisher’s part). Only mechanical parts
are now part of I-Droid02, so only a small quantity of the initial invested amount of money
has been useful in the new project.
The conclusion of that is simple: hardware technology has experienced a remarkable
progress during the last years, especially with the introduction in the market of those
smarter boards containing a fast microcontroller (such as Arduino or Raspberry Pi among
others).
5.1.
I-Droid02 Project total budget
I-Droid02 Li-Ion battery pack and smart charger price includes product cost, shipment cost
from USA, import and customs taxes.
Product description
I-Droid02 central microcontroller (Raspberry Pi 2 B)
I-Droid02 sensors and actuators microcontroller (Arduino Mega)
I-Droid02 electronic components (approximation)
I-Droid02 Li-Ion battery pack and smart charger
Raspberry Pi USB Wi-Fi Dongle
Raspberry Pi HD Camera Revision B
Iguanaworks USB IR Transceiver (Dual LED) for I-Droid02
433 MHz antennas
I-Droid02 and Remote Control 433 MHz SPI modules (TX and RX)
Remote control central microcontroller (Arduino Nano)
Remote control electronic parts (approximation)
Remote control box
Remote control 9V battery
Total
Total cost (in €)
41.26
36.18
85
168.38
16.75
32.21
52.05
12.09
6.73
21.30
30
4.50
2.19
508.64
Table 17. Total costs of I-Droid02 new parts.
The total estimated time that has been dedicated to this project are about 1000 hours, with
an approximated total cost of 12000 €.
93
6.
Conclusions
In this section, the obtained results that are shown at section 4 are compared with the
objectives of I-Droid02 Project. Basic objectives are stated in section 1 but the most
important ones are shown at subsection 2.4, as a guideline to improve original I-Droid01
features and technical aspects.
I-Droid02 Project main objectives were the following:
1. Improve weaknesses of original I-Droid01 robot product.
2. Update all necessary hardware to overcome obsolescence problems of the original
product, trying to encourage the replacement of I-Droid02 key elements if they
become obsolete in the future.
3. Perform the same tasks and behaviors of original product but more efficiently,
taking advantage of the new installed hardware.
4. Avoid of creating a closed and unmodifiable product. Instead, create a platform
where new tasks and behaviors able to be carried by the hardware could be added
easily.
The improvement of original I-Droid01 product weaknesses is explained in full details at
subsection 2.4. These objectives were to redesign the power supply system, to include
network interfaces to allow the robot to be connected in a network or even the Internet, to
improve aesthetical aspects, to get better the voice recognition system, to change original
I-Droid01 distributed entities structure in order to make it more efficient and to upgrade
provided software tools for remote controlling and robot programming.
Starting with the power supply system, looking at the obtained results regarding power
consumption and battery life, the improvement obtained in performance and autonomy is
evident. The original I-Droid01 robot had an autonomy of approximately 2 hours, using the
same statistical usage parameters that ones used in section 4 to compute I-Droid02
autonomy. However, I-Droid01 performance was not the maximum all the time, especially
what the motors movement is concerned. As nominal motors power supply voltage was
relatively low (7.5 V or 6 V if NiMH rechargeable batteries were used), a minimal reduction
of this voltage due to battery consumption had a clear impact on motors movement: They
moved too slow. I-Droid02 power supply, a part of providing more autonomy (3 hours and
a half approximately), makes the robot’s performance to be at 100% all the time. Motors
nominal voltage is directly the battery’s one, 11.1 V, which implies a faster movement.
When the Li-Ion battery is almost empty of charge, the voltage provided is about 7.9 V,
close to the original motors nominal voltage. Circuitry nominal voltage, 5 V, is guaranteed
throughout the entire charge cycle of the battery, something that was not guaranteed in the
original power supply system, as explained in subsection 2.4.
Another aspect to improve from original I-Droid01 was the isolation in terms of
communication interfaces and the maximum coverage related to using a control link
through the Bluetooth interface. This coverage distance of about 10 meters was considered
too low, when only this communication interface was available (beyond the USB or RS232
link used to upgrade firmware or pick up pictures taken by the camera). The fact of using
a Raspberry Pi 2 B board which is able to be connected to any network (in wired mode
using an Ethernet cable or in wireless mode using the Wi-Fi USB dongle) solves the
isolation problem de facto but also the coverage problem, since the new coverage depends
94
of the network one. A part from that, a direct 433 MHz link is provided for the native remote
control, regardless of whether I-Droid02 is connected to a network or not. The coverage of
the 433 MHz link could be hundreds of meters in open environments. In any case, in closed
environments, the coverage distance is more than 10 meters. All this implies a clear
improvement in comparison with original I-Droid01 features.
The improvement of aesthetical aspects can be considered as another accomplished
objective. Although the camera ribbon cable is placed outside, due to space constrains, it
is true that is much more aesthetic than the I-Droid01 original black camera cables exiting
from the top head. The rest of aesthetical aspects, such as the presence of cables exiting
from incorrect places have been corrected.
The efficiency of new centralized hardware structure has been seen clearly in section 4
with battery autonomy data. In terms of software, the code needed to make Arduino Mega
2560 board work occupies 18 KB approximately (compiled binary size), 7% of total memory
size of Arduino board which is 256 KB. This 18 KB value contrasts with the total amount of
available memory of original I-Droid01 entities (see subsection 2.2), which was 64 KB.
Motherboard, Brain & Vision, Bluetooth and Voice module original I-Droid01 entities have
been excluded from the previous computation due to the fact that the tasks developed by
them are carried out by Raspberry Pi board in I-Droid02 system, not by Arduino Mega 2560
board. Despite both size values cannot be compared directly, since the actual size of the
running code inside each I-Droid01 entity is unknown, it can be concluded that the objective
of simplify this original code and fit it in a unique board has been accomplished.
To improve the voice recognition system and to upgrade provided software tools for remote
controlling and robot programming are objectives outside of the scope of this thesis, thus,
these objectives are still pending. As a general conclusion, it has been seen that the
objective of improve weaknesses of original I-Droid01 robot product has been
accomplished with very good results.
2nd and 4th I-Droid02 main objectives are based on the same concept: To build a system
that is easy to upgrade, in terms of hardware. Clearly, the most upgradeable hardware part
is the Raspberry Pi 2 B board and its environment, that is, the main I-Droid02
microcontroller or brain. The rest of hardware elements are not so sensitive to
obsolescence. This group is composed by all the hardware and software distributed among
Arduino Mega 2560 board, which should be very stable in its performance and operability.
For this reason, physically this hardware is placed inside I-Droid02 body, a site which is
quite difficult to access without having to remove every hardware element. As a drawback,
this construction hinders the repairing process in case of hardware failures, even though
they are quite improbable (see Appendix 1 for hardware schematics for more details).
Raspberry Pi 2 B and the related hardware parts are placed inside the black box.
Conceptually, to upgrade I-Droid02 hardware capabilities only to substitute everything
inside this black box by the new system is needed. The only mandatory requirements for
the hypothetical new microcontroller is to have at least one USB port and another UART
interface in order to interface with Arduino Mega 2560 board and an analog audio output
to feed the speaker. If new microcontroller does not fit in the current black box, tit can be
exchanged by a bigger one. This step is very straightforward: Just remove Raspberry Pi 2
B board and unscrew the four screws that hold it next to the body enclosure. The final step
is to solder the remaining electric cables in the corresponding locations and install all the
software again. The previous steps can be done in approximately two days, if all the
material is available. This is very short in comparison with required time to upgrade original
95
I-Droid01 hardware, which was about a month. As it can be seen, this objective has been
accomplished clearly.
Finally, it has been demonstrated that current I-Droid02 hardware overcomes the original
I-Droid01 capabilities, thus, the statement of 2nd objective shown before has been
accomplished.
Last objective to comment is the 3rd one. In this thesis scope, this objective has been carried
out partially. Current I-Droid02 snapshot shows that this robot is not able to perform yet all
the tasks that original I-Droid01 was able to. As detailed in subsection 3.11, this is a matter
of software. New I-Droid02 behaviors and functionalities can be added by developing new
high-level applications. To continue this way is going to be the next steps in I-Droid02
Project, trying to get the most out of the hardware and software basis designed and
developed in this tesis.
96
Bibliography
[1] “Service Robotics - ROBOTECH srl - Pisa ITALY”
http://www.robotechsrl.com/. [Accessed: 19 September 2016].
[Online]
available:
[2] “Dustcart
–
urban
robot
–
ROBOTECH
srl”
[Online]
available:
http://www.robotechsrl.com/dustcart-en-urban-robot/. [Accessed: 19 September 2016].
[3] “VeeaR | Embedded Voice Recognition” [Online] available: http://www.veear.eu/.
[Accessed: 19 September 2016].
[4] “robot coleccionable deagostini i-droid 01,i-qbot,id-01 caracteristicas.” [Online]
21
available:
https://www.youtube.com/watch?v=NSEse7hqGWs. [Accessed:
September 2016].
[5] Giancarlo Teti. " Esercitazione con un robot umanoide programmabile per
edutainment". Corso di Percezione Robotica (PRO), RoboTech S.L.R.
[6] “I-Droid01
Hardware”
[Online]
http://www.adrirobot.it/id_01/le_pagine_di_chiccow/Hardware.html.
September 2016].
available:
[Accessed: 30
[7] “Discharge
tests
of
Alkaline
AA
batteries”
[Online]
available:
http://www.powerstream.com/AA-tests.htm. [Accessed: 04 October 2016].
[8] “Battery energy storage in various battery types” [Online] available:
http://www.allaboutbatteries.com/Battery-Energy.html. [Accessed: 04 October 2016].
[9] “Arduino
–
Wikipedia,
the
free
encyclopedia”
[Online]
https://en.wikipedia.org/wiki/Arduino. [Accessed: 09 October 2016].
available:
[10] “Arduino
–
ArduinoBoardMega2560”
[Online]
available:
https://www.arduino.cc/en/Main/ArduinoBoardMega2560. [Accessed: 09 October
2016].
[11]
“Raspberry
Pi
2
Model
B”
[Online]
available:
https://www.raspberrypi.org/products/raspberry-pi-2-model-b/. [Accessed: 14 October
2016].
[12] “Arduino
–
ArduinoBoardNano”
[Online]
available:
https://www.arduino.cc/en/Main/ArduinoBoardNano. [Accessed: 18 October 2016].
[13] “Bash (Unix shell) – Wikipedia, the free encyclopedia” [Online] available:
https://en.wikipedia.org/wiki/Bash_(Unix_shell). [Accessed: 24 October 2016].
[14] “Python (programming language) – Wikipedia, the free encyclopedia” [Online]
available: https://en.wikipedia.org/wiki/Python_(programming_language). [Accessed:
24 October 2016].
[15]
“Arduino
–
LiquidCrystal”
[Online]
available:
https://www.arduino.cc/en/Reference/LiquidCrystal. [Accessed 26 October 2016].
[16]
“Jasper | Control everythink with your voice”
https://jasperproject.github.io/. [Accessed 18 February 2017].
[Online]
available:
97
Appendix 1. I-Droid02 hardware schematics
This appendix shows all the I-Droid02 final hardware schematics, in order to be able to
follow hardware description details exposed in previous sections of this thesis.
98
99
100
101
102
103
104
105
106
107
108
109
110
Appendix 2. Arduino Nano, Mega and Raspberry Pi 2 Pinouts
433 MHz remote control Arduino Nano pinhead pinout
Analog pins
A0
A1
A2
A3
A4
A5
A6
A7
Digital pins
0
1
2
3
4
5
6
7
8
9
10
11
12
13
Mapped electronic part
Scaled battery power
Tilt direction wheels’ joystick control
Pan direction wheels’ joystick control
Head tilt motor control
Head pan motor control
Left arm motor control
Right arm motor control
Hand motor control
Mapped electronic part
A programmable button
Transmission green LED
B programmable button
Joystick up LED
C programmable button
Joystick right LED
Joystick left LED
Hip up switch
Hip down switch
Joystick down LED
Chip select SPI
Master Input Slave Output SPI
Master Output Slave Input SPI
Clock SPI
I/O
Details
Input
Input
Input
Input
Input
Input
Input
Input
I/O
Input pullup
Output
Input pullup
Output
Input pullup
Output
Output
Input pullup
Input pullup
Output
Output
Input
Output
Output
Details
Low active
High active
Low active
PWM
Low active
PWM
PWM
Low active
Low active
PWM
Low active
I-Droid02 Arduino Mega 2560 pinhead pinout
Analog pins
A0
A1
A2
A3
A4
A5
A6
A7
A8
A9
A10
A11
Mapped element
Scaled battery voltage
Temperature sensor
Microphones and ultrasonic system DC volts
Left eye microphone
.
.
.
.
Hand motor block control
Left ultrasonic sensor
Center ultrasonic sensor
Right ultrasonic sensor
I/O
Details
Input
Input
Input
Input
Input
Input
Input
Input
Input
Input
Input
Input
111
A12
A13
A14
A15
Digital pins
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Right eye microphone
Left ear microphone
Right ear microphone
Back head microphone
Mapped element
.
.
.
Register select (Display)
Contrast adjustment V0 (Display)
Head tilt motor
Head pan motor
Left arm motor
Right arm motor
Hand motor
Hip motor
Left wheel motor
Right wheel motor
Orange LED head
.
Enable (Display)
Raspberry Pi UART RX
Raspberry Pi UART TX
.
DB7 (Display)
DB6 (Display)
DB5 (Display)
DB4 (Display)
Head tilt motor encoder
Hip motor encoder
.
Head tilt motor direction control
Head pan motor encoder
Head pan motor direction control
Left arm motor encoder
Left arm motor direction control
Right arm motor encoder
Right arm motor direction control
Position LEDs
Hand motor direction control
.
Hip motor direction control
Left wheel motor encoder
Left wheel motor direction control
Input
Input
Input
Input
I/O
Output
Output
Output
Output
Output
Output
Output
Output
Output
Output
Output
Details
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM
PWM
Output
Output
Input
Output
Output
Output
Output
Input
Input
Output
Input
Output
Input
Output
Input
Output
Output
Output
Output
Input
Output
High active
High active
High active
High active
High active
High active
112
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Right wheel motor encoder
Right wheel motor direction control
.
Chest left button
Chest central button
Chest right button
Touch sensor control
Left ear LED
Right ear LED
Left eye red LED
Left eye yellow LED
Left eye green LED
Right eye red LED
Right eye yellow LED
Right eye green LED
Input
Output
High active
Input pullup
Input pullup
Input pullup
Input
Output
Output
Output
Output
Output
Output
Output
Output
Low active
Low active
Low active
High active
High active
Low active
Low active
Low active
Low active
Low active
Low active
I-Droid02 Raspberry Pi 2 B pinhead pinout
Digital pins
Name
Details
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
3V3
5V
GPIO2
5V
GPIO3
GND
GPIO4
GPIO14
GND
GPIO15
GPIO17
GPIO18
GPIO27
GND
GPIO22
GPIO23
3V3
GPIO24
GPIO10
GND
GPIO9
GPIO25
GPIO11
GPIO8
GND
3,3 V (Prototypes board 2nd pin)
5 V (Prototypes board 1st pin)
.
.
.
.
Shutdown Raspberry Pi button
.
.
.
.
D programmable button
.
.
External remote control connection LED
Own 433 MHz remote control LED
3,3 V (RFM31B)
E programmable button
SPI MOSI RFM31B
Ground (RFM31B)
SPI MISO RFM31B
RFM31B interruption line
SPI SCLK RFM31B
SPI CS RFM31B
.
113
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
GPIO7
ID_SD
ID_SC
GPIO5
GND
GPIO6
GPIO12
GPIO13
GND
GPIO19
GPIO16
GPIO26
GPIO20
GND
GPIO21
External amplifier ON/OFF switch
.
.
F programmable button
.
READY LED
Prototypes board 3rd pin
Prototypes board 4th pin
.
Prototypes board 5th pin
Prototypes board 6th pin
Prototypes board 7th pin
Prototypes board 8th pin
Ground (Prototypes board 10th pin)
Prototypes board 9th pin
114
Appendix 3. I-Droid02 Software General Block Diagram
115
Appendix 4. I-Droid02 native remote control firmware source code
/*
*
*
*/
This file is part of I-Droid02 Project.
Author: Marc Darné Marí.
//************** SETUP ************************************************//
#include <EEPROM.h> // Library used to access internal EEPROM memory.
#include <SPI.h> // SPI library to be used for RFM43B communication.
// Constants:
#define BUTTONS_LONG_PRESS_TIME 2000 // Minimum number of milliseconds
needed to press and hold A, B and C buttons to be a long press.
#define CONTROL_PACKET_INTERVAL 10000 // Minimum number of milliseconds
between control packets are transmitted.
// Global variables:
boolean botoA = false; // Boolean
unsigned long lastMillisBotoA; //
A button is pressed.
boolean botoB = false; // Boolean
unsigned long lastMillisBotoB; //
B button is pressed.
boolean botoC = false; // Boolean
unsigned long lastMillisBotoC; //
C button is pressed.
to detect if A button has been pressed.
Variable to store and know how much time
to detect if B button has been pressed.
Variable to store and know how much time
to detect if C button has been pressed.
Variable to store and know how much time
float central_frequency; // Central frequency of the transmitter. Must be
a value between 433.05 MHz y 434.79 MHz.
unsigned long lastMillisPaquetControl; // Variable to store and know when
it is time to send a control packet.
void setup() {
unsigned char frequencyValue[4];
unsigned char readValue[1];
// If device is powered using the USB cable, a new central frequency
value should be received:
if ((analogRead(A0) * (5.0 / 1023.0) * 1.88737067) < 5.0) {
// Remote control is powered using USB cable.
Serial.begin(300); // Start Serial communication at 9600 baud.
while(!Serial); // Wait for Serial to be ready.
Serial.setTimeout(2147483647); // Set a timeout of 10 seconds.
// Wait until flag, 0 or 255 values have been received:
do {
Serial.readBytes(readValue, 1); // Read control flag.
} while ((readValue[0] != 'F') && (readValue[0]
(readValue[0] != 255));
!=
0)
&&
// Read frequency value:
if (readValue[0] == 'F') {
116
Serial.readBytes(frequencyValue, 4);
writeEEPROM(frequencyValue); // Store frequency value to EEPROM
memory.
}
Serial.end(); // End Serial communication.
}
// Setup devices:
readEEPROM(); // Get central frequency of the transmitter.
SPI.begin(); // Start SPI communication.
configurarPins(); // Setup pins.
configurarRFM43B(); // Setup TX module.
checkTX_Fault(); // Check that TX module is working properly.
}
//************** RUN LOOP *********************************************//
void loop() {
int capVertical = analogRead(A3);
int capHoritzontal = analogRead(A4);
int brasEsquerre = analogRead(A5);
int brasDret = analogRead(A6);
int ma = analogRead(A7);
int malucUP = digitalRead(7);
int malucDOWN = digitalRead(8);
int A = digitalRead(0);
int B = digitalRead(2);
int C = digitalRead(4);
int ARelease = HIGH; // This will be LOW when A button is released.
int BRelease = HIGH; // This will be LOW when B button is released.
int CRelease = HIGH; // This will be LOW when C button is released.
// Game mode:
if (A == LOW) {
// A button pressed.
if (!botoA) {
botoA = true;
lastMillisBotoA = millis(); // Store instant when A button is pressed.
} // First time A button is pressed.
if (scheduler(&lastMillisBotoA, BUTTONS_LONG_PRESS_TIME)) {
joc();
} // Game starts after BUTTONS_LONG_PRESS_TIME of A button being
pressed.
} else {
// A button not pressed.
if (botoA) {
botoA = false;
ARelease = LOW;
} // A button release detection.
}
// Transmitter test mode:
if (B == LOW) {
// B button pressed.
if (!botoB) {
117
botoB = true;
lastMillisBotoB = millis(); // Store instant when B button is pressed.
} // First time B button is pressed.
if (scheduler(&lastMillisBotoB, BUTTONS_LONG_PRESS_TIME)) {
modeTestTX();
} // Transmitter test mode starts after BUTTONS_LONG_PRESS_TIME of B
button being pressed.
} else {
// B button not pressed.
if (botoB) {
botoB = false;
BRelease = LOW;
} // B button release detection.
}
// Buttons test mode:
if (C == LOW) {
// C button pressed.
if (!botoC) {
botoC = true;
lastMillisBotoC = millis(); // Store instant when C button is pressed.
} // First time C button is pressed.
if (scheduler(&lastMillisBotoC, BUTTONS_LONG_PRESS_TIME)) {
modeTestBotons();
} // Buttons mode starts after BUTTONS_LONG_PRESS_TIME of C button
being pressed.
} else {
// C buttpn not pressed.
if (botoC) {
botoC = false;
CRelease = LOW;
} // C button release detection.
}
// Control packet transmission:
if (scheduler(&lastMillisPaquetControl, CONTROL_PACKET_INTERVAL)) {
//
Now
it
is
time
to
send
a
control
packet
(every
CONTROL_PACKET_INTERVAL).
paquetControl();
}
gestioJoystick(); // Joystick values management.
gestioBotons(capVertical, capHoritzontal, brasEsquerre, brasDret, ma,
malucUP, malucDOWN, ARelease, BRelease, CRelease); // Buttons management.
}
//************** ADDITIONAL ROUTINES **********************************//
//-------------- ADDITIONAL SETUP ROUTINES ----------------------------//
// Digital pins INPUT/OUTPUT:
void configurarPins() {
pinMode(0, INPUT_PULLUP); // A programable button.
pinMode(1, OUTPUT); // Green TX LED.
pinMode(2, INPUT_PULLUP); // B programable button.
pinMode(3, OUTPUT); // Joystick up LED.
pinMode(4, INPUT_PULLUP); // C programable button.
118
pinMode(5, OUTPUT); // Joystick right LED.
pinMode(6, OUTPUT); // Joystick left LED.
pinMode(7, INPUT_PULLUP); // Switch hip up.
pinMode(8, INPUT_PULLUP); // Switch hip down.
pinMode(9, OUTPUT); // Joystick down LED.
pinMode(10, OUTPUT); // CS SPI.
}
// Function to check that RFM43B is working properly by reading 0x01
register:
void checkTX_Fault() {
unsigned char bufferByte[1];
// Reading Device version register:
transferenciaSPI(false, 0x01, bufferByte, 1);
// Stop Arduino execution forever if error occurs:
if (bufferByte[0] != 0x06) {
// Error with transmitter.
while (1) {
// Turn ON/OFF joystick LEDs forming a cross to show error:
analogWrite(3, 255); // Up LED ON.
analogWrite(9, 255); // Down LED ON.
analogWrite(6, 0); // Left LED OFF.
analogWrite(5, 0); // Right LED OFF.
delay(500);
analogWrite(3, 0); // Up LED OFF.
analogWrite(9, 0); // Down LED OFF.
analogWrite(6, 255); // Left LED ON.
analogWrite(5, 25); // Right LED ON.
delay(500);
}
}
}
// RFM43B transmitter registers settings:
void configurarRFM43B() {
unsigned char buff[1] = {0x80};
transferenciaSPI(true, 0x07, buff, 1); // RFM43B Reset.
delay(1);
// 1st block: Address 0x05-0x10 (12).
unsigned char buff1[12] = {0x00, 0x00, 0x01, 0x00, 0x7F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00};
transferenciaSPI(true, 0x05, buff1, 12);
// 2nd block: Address 0x12-0x16 (5) & address 0x1A.
unsigned char buff2[5] = {0x20, 0x00, 0x00, 0x00, 0x00};
transferenciaSPI(true, 0x12, buff2, 5);
unsigned char buff2a[1] = {0x00};
transferenciaSPI(true, 0x1A, buff2a, 1);
// 3rd block: Address 0x36-0x3E (9), address 0x30, address 0x33, address
0x34 & address 0x4F.
unsigned char buff3[9] = {0x42, 0x44, 0x4D, 0x44, 0x42, 0x44, 0x4D, 0x44,
0x0F};
119
transferenciaSPI(true, 0x36, buff3, 9);
unsigned char buff3a[1] = {0x88};
transferenciaSPI(true, 0x30, buff3a, 1);
unsigned char buff3b[1] = {0x06};
transferenciaSPI(true, 0x33, buff3b, 1);
unsigned char buff3c[1] = {0x08};
transferenciaSPI(true, 0x34, buff3c, 1);
unsigned char buff3d[1] = {0x10};
transferenciaSPI(true, 0x4F, buff3d, 1);
// 4th block: Address 0x6D-0x75 (9) & address 0x62.
unsigned char buff4[9] = {0x47, 0x10, 0x62, 0x20, 0x23, 0x50, 0x00, 0x00,
0x53};
transferenciaSPI(true, 0x6D, buff4, 9);
unsigned char buff4a[1] = {0x24};
transferenciaSPI(true, 0x62, buff4a, 1);
// 5th block: Address 0x76-0x77 (2).
int fc = ((central_frequency / 10.0) - 43) * 64000; // Frequency value
to be written in the register.
unsigned char fcAlt = ((fc & 0xFF00) >> 8);
unsigned char fcBaix = (fc & 0xFF);
unsigned char buff5[2] = {fcAlt, fcBaix};
transferenciaSPI(true, 0x76, buff5, 2);
// 6th block: Address 0x79-0x7A (2).
unsigned char buff6[2] = {0x00, 0x00};
transferenciaSPI(true, 0x79, buff6, 2);
// 7th block: Address 0x7C-0x7D (2).
unsigned char buff7[2] = {0x37, 0x02};
transferenciaSPI(true, 0x7C, buff7, 2);
}
void readEEPROM() {
unsigned char floatBuffer[4]; // Buffer used to parse floats.
int i;
// Read values from EEPROM:
for (i = 0; i < 4; i++) {
floatBuffer[i] = EEPROM.read(i);
}
// Parse frequency value:
memcpy(&central_frequency, floatBuffer, 4);
// Frequency check:
if (central_frequency
central_frequency =
}
if (central_frequency
central_frequency =
}
< 433.05) {
433.05;
> 434.79) {
434.79;
}
120
void writeEEPROM(unsigned char* bufferFrequency) {
// bufferFrequency: Buffer that contains the value of the central
frequency parsed as char array.
int i;
// Write value to EEPROM:
for (i = 0; i < 4; i++) {
EEPROM.write(i, bufferFrequency[i]);
}
}
//-------------- JOYSTICK AND BUTTONS MANAGEMENT ----------------------//
// Manages joystick LEDs and sends packet when necessary:
void gestioJoystick() {
int vertical = analogRead(A1); // Tilt joystick value.
int horitzontal = analogRead(A2); // Pan joystick value.
unsigned char flag = 0x00; // Control flag to indicate whether power
values are to go forwards/backwards and left/right.
unsigned char x = 0; // Abscissa modulus value.
unsigned char y = 0; // Ordered modulus value.
unsigned char led = 0; // Red LEDs PWM value.
//
that
//
//
Joystick LEDs range goes from 0 to 255 (as default) except right LED,
must be limited between 0-25.
Vertical joystick range: 0 - 510 & 530 - 950.
Horizontal joystick range: 15 - 530 & 550 - 1020.
// Joystick vertical position:
if (vertical > 529) {
// Forward direction (upper LED):
flag = flag | 0x60;
if (vertical > 950) {
vertical = 950;
} // This avoid values out of range.
// Set x value:
y = getYPositiveValue(vertical);
// Set LED value:
led = (unsigned char) ((127.0/210.0)*vertical - (6710.0/21.0));
analogWrite(3, led);
analogWrite(9, 0);
}
if (vertical < 511) {
// Backward direction (down LED):
flag = flag | 0x90;
if (vertical < 0) {
vertical = 0;
} // This avoid values out of range.
// Set x value:
y = getYNegativeValue(vertical);
121
// Set LED value:
led = (unsigned char) (-(127.0/255.0)*vertical + 255.0);
analogWrite(9, led);
analogWrite(3, 0);
}
// Joystick horizontal direction:
if (horitzontal < 531) {
// Left direction (left LED):
flag = flag | 0x06;
if (horitzontal < 15) {
horitzontal = 15;
} // This avoid values out of range.
// Set y value:
x = getXNegativeValue(horitzontal);
// Set LED value:
led = (unsigned char) (-(254.0/515.0)*horitzontal + (27027.0/103.0));
analogWrite(6, led);
analogWrite(5, 0);
}
if (horitzontal > 549) {
// Right direction (right LED):
flag = flag | 0x09;
if (horitzontal > 1020) {
horitzontal = 1020;
} // This avoid values out of range.
// Set y value:
x = getXPositiveValue(horitzontal);
// Set LED value:
led = (unsigned char) ((12.0/235.0)*horitzontal - (1273.0/47.0));
analogWrite(5, led);
analogWrite(6, 0);
}
// Central joystick position:
if ((vertical > 510) && (vertical < 530)) {
analogWrite(3, 0);
analogWrite(9, 0);
}
if ((horitzontal > 530) && (horitzontal < 550)) {
analogWrite(6, 0);
analogWrite(5, 0);
}
// If flag is different than 0, it means that a packet must be transmitted
(if packet not received, robot will stop):
if (flag != 0x00) {
122
// Wheels motors packet generation and send:
unsigned char paquet[3]; // 3 Bytes packet.
paquet[0] = 0x24; // ID.
paquet[1] = flag; // Direction flag control.
paquet[2] = (x << 4) | y; // Axis values.
enviarPaquet(paquet, 3);
}
}
// Function to manage
void gestioBotons(int
int brasDret, int ma,
unsigned char byte1
buttons).
unsigned char byte2
all buttons of the remote control:
capVertical, int capHoritzontal, int brasEsquerre,
int malucUP, int malucDOWN, int A, int B, int C) {
= 0x00; // 1st Bute (head motors and programable
= 0x00; // 2nd Byte (arms and hip motors).
// Head buttons:
if (capVertical > 900) {
// Head up button pressed.
byte1 = byte1 | 0x01;
}
if (capVertical < 100) {
// Head down button pressed.
byte1 = byte1 | 0x02;
}
if (capHoritzontal > 900) {
// Head left button pressed.
byte1 = byte1 | 0x04;
}
if (capHoritzontal < 100) {
// Head right button pressed.
byte1 = byte1 | 0x08;
}
// Programable buttons:
if (A == LOW) {
// A button pressed.
byte1 = byte1 | 0x10;
}
if (B == LOW) {
// B button pressed.
byte1 = byte1 | 0x20;
}
if (C == LOW) {
// C button pressed.
byte1 = byte1 | 0x40;
}
// Arms buttons:
if (brasEsquerre > 900) {
// Left arm up button pressed.
byte2 = byte2 | 0x01;
}
if (brasEsquerre < 100) {
// Left arm down button pressed.
123
byte2 = byte2 | 0x02;
}
if (brasDret > 900) {
// Right arm up button pressed.
byte2 = byte2 | 0x04;
}
if (brasDret < 100) {
// Right arm down button pressed.
byte2 = byte2 | 0x08;
}
// Hand buttons:
if (ma < 100) {
// Open hand button pressed.
byte2 = byte2 | 0x10;
}
if (ma > 900) {
// Close hand button pressed.
byte2 = byte2 | 0x20;
}
// Hip switch button:
if (malucUP == LOW) {
// Hip up.
byte2 = byte2 | 0x40;
}
if (malucDOWN == LOW) {
// Hip down.
byte2 = byte2 | 0x80;
}
// If at least one button has been pressed, a packet must be transmitted:
if ((byte1 != 0x00) || (byte2 != 0x00)) {
// Head motors, body motors and A, B and C buttons packet generation:
unsigned char paquet[3]; // 3 Bytes packet.
paquet[0] = 0x18; // ID.
paquet[1] = byte1;
paquet[2] = byte2;
enviarPaquet(paquet, 3);
}
}
// Routine to convert joystick positive axis adc value to the interval (1
- 10):
int getYPositiveValue(int adcValue) {
// adcValue: Value read from ADC.
// Returns: The scaled value (1 - 10).
int scaledValue = 0;
if ((adcValue > 529) && (adcValue <= 571)) {
scaledValue = 1;
} else if ((adcValue > 571) && (adcValue <= 613)) {
scaledValue = 2;
} else if ((adcValue > 613) && (adcValue <= 655)) {
scaledValue = 3;
124
} else if ((adcValue
scaledValue = 4;
} else if ((adcValue
scaledValue = 5;
} else if ((adcValue
scaledValue = 6;
} else if ((adcValue
scaledValue = 7;
} else if ((adcValue
scaledValue = 8;
} else if ((adcValue
scaledValue = 9;
} else if ((adcValue
scaledValue = 10;
}
> 655) && (adcValue <= 697)) {
> 697) && (adcValue <= 739)) {
> 739) && (adcValue <= 781)) {
> 781) && (adcValue <= 823)) {
> 823) && (adcValue <= 865)) {
> 865) && (adcValue <= 907)) {
> 907) && (adcValue <= 950)) {
return scaledValue;
}
// Routine to convert joystick negative axis adc value to the interval (1
- 10):
int getYNegativeValue(int adcValue) {
// adcValue: Value read from ADC.
// Returns: The scaled value (1 - 10).
int scaledValue = 0;
if ((adcValue > -1) &&
scaledValue = 10;
} else if ((adcValue >
scaledValue = 9;
} else if ((adcValue >
scaledValue = 8;
} else if ((adcValue >
scaledValue = 7;
} else if ((adcValue >
scaledValue = 6;
} else if ((adcValue >
scaledValue = 5;
} else if ((adcValue >
scaledValue = 4;
} else if ((adcValue >
scaledValue = 3;
} else if ((adcValue >
scaledValue = 2;
} else if ((adcValue >
scaledValue = 1;
}
(adcValue <= 51)) {
51) && (adcValue <= 102)) {
102) && (adcValue <= 153)) {
153) && (adcValue <= 204)) {
204) && (adcValue <= 255)) {
255) && (adcValue <= 306)) {
306) && (adcValue <= 357)) {
357) && (adcValue <= 408)) {
408) && (adcValue <= 459)) {
459) && (adcValue <= 510)) {
return scaledValue;
}
// Routine to convert joystick positive axis adc value to the interval (1
- 10):
int getXPositiveValue(int adcValue) {
// adcValue: Value read from ADC.
125
// Returns: The scaled value (1 - 10).
int scaledValue = 0;
if ((adcValue > 549)
scaledValue = 1;
} else if ((adcValue
scaledValue = 2;
} else if ((adcValue
scaledValue = 3;
} else if ((adcValue
scaledValue = 4;
} else if ((adcValue
scaledValue = 5;
} else if ((adcValue
scaledValue = 6;
} else if ((adcValue
scaledValue = 7;
} else if ((adcValue
scaledValue = 8;
} else if ((adcValue
scaledValue = 9;
} else if ((adcValue
scaledValue = 10;
}
&& (adcValue <= 597)) {
> 597) && (adcValue <= 644)) {
> 644) && (adcValue <= 691)) {
> 691) && (adcValue <= 738)) {
> 738) && (adcValue <= 785)) {
> 785) && (adcValue <= 832)) {
> 832) && (adcValue <= 879)) {
> 879) && (adcValue <= 926)) {
> 926) && (adcValue <= 973)) {
> 973) && (adcValue <= 1020)) {
return scaledValue;
}
// Routine to convert joystick negative axis adc value to the interval (1
- 10):
int getXNegativeValue(int adcValue) {
// adcValue: Value read from ADC.
// Returns: The scaled value (1 - 10).
int scaledValue = 0;
if ((adcValue > 14) &&
scaledValue = 10;
} else if ((adcValue >
scaledValue = 9;
} else if ((adcValue >
scaledValue = 8;
} else if ((adcValue >
scaledValue = 7;
} else if ((adcValue >
scaledValue = 6;
} else if ((adcValue >
scaledValue = 5;
} else if ((adcValue >
scaledValue = 4;
} else if ((adcValue >
scaledValue = 3;
} else if ((adcValue >
scaledValue = 2;
} else if ((adcValue >
scaledValue = 1;
(adcValue <= 66)) {
66) && (adcValue <= 117)) {
117) && (adcValue <= 168)) {
168) && (adcValue <= 219)) {
219) && (adcValue <= 270)) {
270) && (adcValue <= 321)) {
321) && (adcValue <= 372)) {
372) && (adcValue <= 423)) {
423) && (adcValue <= 474)) {
474) && (adcValue <= 530)) {
126
}
return scaledValue;
}
//-------------- GAME AND TEST MODES ----------------------------------//
// Game (based on SIMON):
void joc() {
const int MAX = 100;
int valors[MAX];
// Initialization:
randomSeed(millis()); // Random sequence initialization.
jocGuardarValor(valors, 0); // 1st value stored.
for (int i = 1; i < MAX; i++) {
valors[i] = 0;
} // Rest of values set to 0.
// LED indication to show that remote control is in game mode:
analogWrite(3, 255); // Up LED ON.
delay(500);
analogWrite(3, 0); // Up LED OFF.
analogWrite(5, 25); // Right LED ON.
delay(500);
analogWrite(5, 0); // Right LED OFF.
analogWrite(9, 255); // Down LED ON.
delay(500);
analogWrite(9, 0); // Down LED OFF.
analogWrite(6, 255); // Left LED ON.
delay(500);
analogWrite(6, 0); // Left LED OFF.
delay(1000); // Wait 1 second before start the game.
// The game starts!
int counterAbsolut = 0;
boolean continuar = true;
while (continuar) {
// Defines the speed of LEDs being ON and OFF as a function of the
sequence
int velocitatLectura;
if (counterAbsolut < 5) {
velocitatLectura = 600; // Below 5 successful values the delay is
600 ms.
} else if ((counterAbsolut >= 5) && (counterAbsolut < 10)) {
velocitatLectura = 450; // Above 5 and below 10 successful values
the delay is 450 ms.
} else {
velocitatLectura = 300; // Above 10 successful values the delay is
300 ms.
}
// Play sequence:
jocReproduirSequencia(valors, velocitatLectura);
127
// User's turn (must repeat the sequence by pressing the head buttons
accordingly to the sequence played. If user fails once, the game ends):
int counter = 0;
do {
// Head buttons variables:
int vertical = 500;
int horitzontal = 500;
int malucUP = HIGH; // If hip up switch is pressed, it means that
user has cancelled the game.
// Wait for user's interaction:
while (((vertical = analogRead(A3)) > 100) && ((vertical =
analogRead(A3)) < 900) && ((horitzontal = analogRead(A4)) > 100) &&
((horitzontal = analogRead(A4)) < 900) && ((malucUP = digitalRead(7) ==
HIGH)));
if (malucUP == LOW) {
// User has cancelled the game.
continuar = false;
break;
}
// Which button has the user pressed?
if (vertical > 900) {
// Head up button pressed.
if (valors[counter] == 3) {
// OK. It matches the sequence.
analogWrite(3, 255);
delay(350);
analogWrite(3, 0);
counter++;
} else {
// Wrong. It does not match the sequence.
continuar = false;
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(350);
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
break;
}
}
if (vertical < 100) {
// Head down button pressed.
if (valors[counter] == 9) {
// OK. It matches the sequence.
analogWrite(9, 255);
delay(350);
analogWrite(9, 0);
counter++;
} else {
128
// Wrong. It does not match the sequence.
continuar = false;
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(350);
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
break;
}
}
if (horitzontal > 900) {
// Head left button pressed.
if (valors[counter] == 6) {
// OK. It matches the sequence.
analogWrite(6, 255);
delay(350);
analogWrite(6, 0);
counter++;
} else {
// Wrong. It does not match the sequence.
continuar = false;
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(350);
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
break;
}
}
if (horitzontal < 100) {
// Head right button pressed.
if (valors[counter] == 5) {
// OK. It matches the sequence.
analogWrite(5, 25);
delay(350);
analogWrite(5, 0);
counter++;
} else {
// Wrong. It does not match the sequence.
continuar = false;
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(350);
analogWrite(3, 0);
analogWrite(5, 0);
129
analogWrite(6, 0);
analogWrite(9, 0);
break;
}
}
if (counter > counterAbsolut) {
// User has hit the entire sequence. A new random value can be
added at the end of the sequence.
counterAbsolut++;
if (counterAbsolut == MAX) {
// Congratulations! User has hit the maximum sequence! Game ends.
continuar = false;
analogWrite(6, 255); // Left LED ON.
delay(500);
analogWrite(6, 0); // Left LED OFF.
analogWrite(9, 255); // Down LED ON.
delay(500);
analogWrite(9, 0); // Down LED OFF.
analogWrite(5, 25); // Right LED ON.
delay(500);
analogWrite(5, 0); // Right LED OFF.
analogWrite(3, 255); // Up LED ON.
delay(500);
analogWrite(3, 0); // Up LED OFF.
break;
}
// New random value generated:
jocGuardarValor(valors, counterAbsolut);
delay(500);
break;
}
} while (true); // The end of this loop is controlled inside itself
by using break statement.
} // The game ends when continuar boolean is false.
}
// Auxiliary game function to generate and store a value in the sequence:
void jocGuardarValor(int* valors, int index) {
// valors: Pointer value that points to the int sequence.
// index: Position of the sequence to store the new value.
long aleatori = random(1, 5);
switch (aleatori) {
case 1:
valors[index] = 3;
break;
case 2:
valors[index] = 5;
break;
case 3:
valors[index] = 9;
130
break;
case 4:
valors[index] = 6;
break;
}
}
// Auxiliary game function to play the sequence (turn on the corresponding
joystick LED):
void jocReproduirSequencia(int* valors, int velocitatLectura) {
// valors: Pointer value that points to the int sequence.
// velocitatLectura: Delay between LED ON/OFF. If lower, LEDs will be
ON/OFF faste, adding some dificulty on the game.
int index = 0; // Sequence index.
while (valors[index] != 0) {
int led = valors[index];
if (led == 5) {
analogWrite(led, 25); // Right joystick LED.
}
else {
analogWrite(led, 255); // Rest of LEDs.
}
delay(velocitatLectura);
analogWrite(led, 0); // LED OFF.
delay(60); // Thanks to this delay, if the same LED must be turned ON
the user will appreciate that.
index++;
}
}
// Transmitter test mode:
void modeTestTX() {
//
Setup
RFM43B
transmitter
in
PN9
mode
(continuous
transmission):
Serial.begin(9600); // Turn TX LED ON.
unsigned char buff[1] = {0x33};
transferenciaSPI(true, 0x71, buff, 1); // Change mode to PN9.
buff[0] = {0x09};
transferenciaSPI(true, 0x07, buff, 1); // Start transmission.
random
// Infinite loop:
int brillo = 0; // Joystick LEDs brightness.
int fadeAmount = 1; // Brightness incrementation.
while (digitalRead(8) == HIGH) { // If hip down switch is pressed, this
mode ends.
// Joystick LEDs flicker to indicate that remote control is in
transmission test mode:
analogWrite(3, brillo); // Up LED.
analogWrite(5, brillo * (25.0 / 255.0)); // Right LED.
analogWrite(6, brillo); // Left LED.
analogWrite(9, brillo); // Down LED.
131
brillo = brillo + fadeAmount; // Brightness increment.
// Change fade direction at edges:
if (brillo == 0 || brillo == 255) {
fadeAmount = -fadeAmount;
}
delay(3);
}
// Shutdown LEDs:
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
//
//
//
//
Up LED.
Right LED dret.
Left LED.
Down LED.
// Setup RFM43B again:
buff[0] = {0x80};
transferenciaSPI(true, 0x07, buff, 1); // RFM43B Reset.
delay(1);
configurarRFM43B(); // General setup of RFM43B.
Serial.end();
}
// Test buttons mode:
void modeTestBotons() {
// Following a sequence, this program waits for user pressing a certain
button or moving the joystick (starting for that last).
// If element works fine, TX led blinks. To exit this mode, all elements
must be tested.
// Show that test buttons mode is running:
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(300);
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
delay(30);
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
analogWrite(9, 255);
delay(300);
analogWrite(3, 0);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(9, 0);
delay(30);
analogWrite(3, 255);
analogWrite(5, 25);
analogWrite(6, 255);
132
analogWrite(9,
delay(300);
analogWrite(3,
analogWrite(5,
analogWrite(6,
analogWrite(9,
delay(30);
255);
0);
0);
0);
0);
// Joystick test:
while (analogRead(A1) !=
pressed upwards.
testBotonsElementOK();
while (analogRead(A1) !=
pressed downwards.
testBotonsElementOK();
while (analogRead(A2) !=
pressed to the left.
testBotonsElementOK();
while (analogRead(A2) !=
pressed to the right.
testBotonsElementOK();
825); // To end this loop, joystick must be
210); // To end this loop, joystick must be
280); // To end this loop, joystick must be
850); // To end this loop, joystick must be
// Head buttons test:
while (analogRead(A3) < 900); // To end this loop, head up button must
be pressed.
testBotonsElementOK();
while (analogRead(A3) > 100); // To end this loop, head down button must
be pressed.
testBotonsElementOK();
while (analogRead(A4) < 900); // To end this loop, head left button must
be pressed.
testBotonsElementOK();
while (analogRead(A4) > 100); // To end this loop, head right button
must be pressed.
testBotonsElementOK();
// Arms buttons test:
while (analogRead(A5) < 900); // To end this loop, left arm up
must be pressed.
testBotonsElementOK();
while (analogRead(A5) > 100); // To end this loop, left arm down
must be pressed.
testBotonsElementOK();
while (analogRead(A6) < 900); // To end this loop, right arm up
must be pressed.
testBotonsElementOK();
while (analogRead(A6) > 100); // To end this loop, right arm down
must be pressed.
testBotonsElementOK();
button
button
button
button
// Hand buttons test:
while (analogRead(A7) > 100); // To end this loop, open hand button must
be pressed.
testBotonsElementOK();
133
while (analogRead(A7) < 900); // To end this loop, close hand button
must be pressed.
testBotonsElementOK();
// Hip switch test:
while (digitalRead(7) == HIGH); // To end this loop, hip up switch must
be pressed.
testBotonsElementOK();
while (digitalRead(8) == HIGH); // To end this loop, hip down switch
must be pressed.
testBotonsElementOK();
// Programmable buttons test:
while (digitalRead(0) == HIGH); // To end this loop, A button must be
pressed.
testBotonsElementOK();
while (digitalRead(2) == HIGH); // To end this loop, B button must be
pressed.
testBotonsElementOK();
while (digitalRead(4) == HIGH); // To end this loop, C button must be
pressed.
testBotonsElementOK();
// End of test:
analogWrite(6, 255); // Left LED ON.
delay(500);
analogWrite(6, 0); // Left LED OFF.
analogWrite(9, 255); // Down LED ON.
delay(500);
analogWrite(9, 0); // Down LED OFF.
analogWrite(5, 25); // Right LED ON.
delay(500);
analogWrite(5, 0); // Right LED OFF.
analogWrite(3, 255); // Up LED ON.
delay(500);
analogWrite(3, 0); // Up LED OFF.
}
// Auxiliary buttons test mode to turn ON TX LED:
void testBotonsElementOK() {
Serial.begin(9600);
delay(250);
Serial.end();
delay(250);
Serial.begin(9600);
delay(250);
Serial.end();
}
//-------------- PACKETS SENDING --------------------------------------//
// Packet control send (with battery and RFM43B internal temperature
value):
void paquetControl() {
unsigned char* bateria; // Buffer to store in Bytes current battery
voltage value.
134
unsigned char* bateriaPercentage; // Buffer to store in Bytes current
battery discharge percentage value.
unsigned char* temperatura; // Buffer to store in Bytes current RFM43B
internal temperature value.
// Low battery threshold (according to yellow LED analog circuit): 6,97V
or less.
double lecturaBateria = (analogRead(A0) * (5.0 / 1023.0) * 1.88737067);
// Battery voltage value after undo resistive divider prescaling.
bateria = (unsigned char*) &lecturaBateria;
// Discharge percentage computation:
double percentage = 0; // Temporal variable to store computed percentage.
if (lecturaBateria >= 8.55) {
// 1st straight function.
percentage = (536.0 / 45.0) * lecturaBateria - (36.0 / 5.0);
} else if ((lecturaBateria >= 7.2) && (lecturaBateria < 8.55)) {
// 2nd straight function.
percentage = 41.0 * lecturaBateria - (25591.0 / 100.0);
} else {
// 3rd straight function.
percentage = (3929.0 / 220.0) * lecturaBateria - (3929.0 / 44.0);
} // Discharge curve is approximated with 4 straight functions, according
to empirical graph.
if (lecturaBateria < 5.0) {
// Remote control is powered through the USB connector.
percentage = 100;
}
// Truncate obtained percentage according to absolute maximum values of
voltage:
if (percentage > 100) {
percentage = 100;
}
if (percentage < 0) {
percentage = 0;
}
bateriaPercentage = (unsigned char*) &percentage;
// RFM43B ADC value register reading:
unsigned char buff[1] = {0x80};
transferenciaSPI(true, 0x0F, buff, 1);
delay(1);
transferenciaSPI(false, 0x11, buff, 1);
// Temperature measurement according to datasheet formula:
double temp = buff[0] * 0.5 - 64.0;
temperatura = (unsigned char*) &temp;
// Control packet assembling and send:
unsigned char paquet[13]; // 13 Bytes packet.
paquet[0] = 0xC3; // ID.
paquet[1] = bateria[0];
paquet[2] = bateria[1];
135
paquet[3] = bateria[2];
paquet[4] = bateria[3]; // Battery voltage value.
paquet[5] = bateriaPercentage[0];
paquet[6] = bateriaPercentage[1];
paquet[7] = bateriaPercentage[2];
paquet[8] = bateriaPercentage[3]; // Battery percentage value.
paquet[9] = temperatura[0];
paquet[10] = temperatura[1];
paquet[11] = temperatura[2];
paquet[12] = temperatura[3]; // RFM43B internal temperature value.
enviarPaquet(paquet, 13);
}
// Routine to send a certain packet:
void enviarPaquet(unsigned char* paquet, int midaBytes) {
// paquet: Buffer to store the assembled packet, ready to be sent to
RFM43B FIFO buffer.
// midaBytes: Packet length in Bytes.
unsigned char packetSent[midaBytes + 1]; // Use another buffer to append
CRC.
int i;
checkTX_Fault();
Serial.begin(1); // Turn ON TX LED.
// Copy paquet buffer to packetSent:
for (i=0; i<midaBytes; i++) {
packetSent[i] = paquet[i];
}
// Compute CRC8 and append it:
packetSent[midaBytes] = CRC8(packetSent, midaBytes);
// Clear FIFO buffer:
unsigned char bufferByte[1] = {0x01}; // Set ffclrtx bit to '1'.
transferenciaSPI(true, 0x08, bufferByte, 1);
bufferByte[0] = {0x00}; // Set ffclrtx bit to '0'.
transferenciaSPI(true, 0x08, bufferByte, 1);
// Write Bytes to FIFO:
transferenciaSPI(true, 0x7F, packetSent, midaBytes + 1);
// Packet transmission:
bufferByte[0] = {0x09};
transferenciaSPI(true, 0x07, bufferByte, 1); // Set RFM43B state to TX.
do {
transferenciaSPI(false,
0x03,
bufferByte,
1);
//
Reading
Interrupt/Status 1 register.
} while ((bufferByte[0] & 0x04) != 0x04); // Wait for state register
being IDLE before end loop.
// Clear FIFO buffer:
bufferByte[0] = {0x01}; // Set ffclrtx bit to '1'.
transferenciaSPI(true, 0x08, bufferByte, 1);
136
bufferByte[0] = {0x00}; // Set ffclrtx bit to '0'.
transferenciaSPI(true, 0x08, bufferByte, 1);
// Reset RFM43B:
configurarRFM43B();
Serial.end(); // Turn OFF TX LED.
}
//-------------- OTHER ADDITIONAL ROUTINES ----------------------------//
// Routine to compute CRC of 8 bits following Dallas/Maxim algorithm:
unsigned char CRC8(unsigned char* data, int len) {
// data: The buffer of Bytes to compute its 8 bits CRC.
// len: The number of Bytes to compute its CRC.
// Returns: Computed 8-bit CRC.
unsigned char crc;
int i;
crc = 0x00;
while (len--) {
byte extract = *data++;
for (i=8; i; i--) {
byte sum = (crc ^ extract) & 0x01;
crc >>= 1;
if (sum) {
crc ^= 0x8C;
}
extract >>= 1;
}
}
return crc;
}
// RFM43B SPI reading and writting procedure. Allows w/r multiple
consecutive registers:
void transferenciaSPI(boolean escriptura, int adrInicial, unsigned char*
buff, int numRegistres) {
// escriptura: TRUE if a writing operation is requested. FALSE otherwise.
// adrInicial: 1st address to start w/r registers.
// buff: Array to store Bytes to be send or received by RFM43B.
// numRegistres: Consecutive registers number to be w/r. It must be
equal or less than buff size.
digitalWrite(10, LOW); // SPI transmission starts.
if (escriptura) {
SPI.transfer(adrInicial | 0x80); // According to datasheet, a '1' must
be added at the most significant bit if a write operation is requested.
} else {
SPI.transfer(adrInicial);
}
for (int i = 0; i < numRegistres; i++) {
buff[i] = SPI.transfer(buff[i]);
}
digitalWrite(10, HIGH); // End of SPI transmission.
137
}
// This routine is used to schedule different events. This routine does
not use interruptions, so it cannot be used to schedule events that must
occur exactly after cycle time.
boolean scheduler(unsigned long* lastMillis, unsigned int cycle) {
// lastMillis: millis() external pointer value to compare with current
millis() value.
// cycle: Minimum time in milliseconds to wait before execute the event.
unsigned long currentMillis = millis(); // Current microprocessor clock
value in miliseconds.
if (currentMillis - *lastMillis >= cycle) {
*lastMillis = currentMillis;
return true; // Time stored in cycle has run out.
}
else {
return false; // Time stored in cycle has not run out.
}
}
// EOF
138
Appendix 5. Raspbian Bash scripts source code
Audioscript.sh source code:
#!/bin/bash
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to activate/deactivate external audio amplifier before run
any audio command.
# Just call audioscript followed by the command to use it.
# Check that ALSA is not busy running another instance of this script:
while IFS='' read -r line || [[ -n "$line" ]]; do
if [ "$line" != "closed" ]; then
echo "ALSA is busy, exiting."
exit
else
break
fi
done < "/proc/asound/card0/pcm0p/sub0/status"
# Turn ON external audio amplifier:
echo "1" > /sys/class/gpio/gpio7/value
# Run audio command:
"$@"
# Turn OFF external audio amplifier:
echo "0" > /sys/class/gpio/gpio7/value
Autoupdate.sh source code:
#!/bin/bash
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to update Raspbian packages to the newest version using
apt-get.
sudo apt-get update # Update packages list.
sudo apt-get --yes –force-yes upgrade # Upgrade obsolete packages if
needed.
sudo apt-get --yes –force-yes dist-upgrade # Upgrade Raspbian
distribution if needed.
sudo apt-get --yes –force-yes autoremove # Remove unnecessary packages.
sudo apt-get clean # Remove garbage.
sudo SKIP_WARNING=1 rpi-update # Update Raspberry Pi firmware.
echo "I-Droid02 will reboot in 5 seconds."
sleep 5
139
sudo reboot # Restart Raspbian
# End of autoupdate.
Backup.sh source code:
#!/bin/bash
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to create a backup copy of all contents of SD card.
# Mount directory:
sudo mount -t cifs -o username=idroid02,password=idroid02
//192.168.10.10/MyComputers /mnt/Backup
# Delete current backup:
sudo rm -r /mnt/Backup/* /mnt/Backup/.* # Delete all files inside Backup
folder.
echo "Start copy"
sudo rsync -aAX -H --info=progress2 --exclude=/mnt/* / /mnt/Backup/
sudo umount /mnt/Backup
echo "Backup ended"
# End of backup.
ResetAll.sh source code:
#!/bin/bash
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to reset Arduino Mega 2560 and restart driver program.
# Kill Driver application to stop any active behavior:
pkill driver
# Send Reset Arduino Mega 2560 command:
echo "ra" > /dev/ttyACM0
# Start main driver application again:
driver
# End of resetAll.
ResetShutdown.sh source code:
#!/bin/bash
#
140
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to send command to Arduino to put motors in its default
position, before shut down the system.
# Kill Driver application to stop any active behavior:
pkill driver
# Send Reset Motors command:
echo "rms" > /dev/ttyACM0
# End of resetShutdown.
Restore.sh source code:
#!/bin/bash
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Bash script to restore a previous backup copy of all contents of SD
card.
# Mount directory:
sudo mount -t cifs -o username=idroid02,password=idroid02
//192.168.10.10/MyComputers /mnt/Backup
echo "Restore copy"
sudo rsync -aAX -H --info=progress2 --exclude=/mnt/* /mnt/Backup/ /
sudo umount /mnt/Backup
echo "Restore ended"
# End of restore.
141
Appendix 6. Raspbian Python scripts source code
Basic.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
# Basic program to manage READY LED and shutdown Raspberry Pi 2 button.
import
import
import
import
RPi.GPIO as GPIO
time
os
subprocess
# GPIO configuration:
GPIO.setmode(GPIO.BCM)
GPIO.setup(6, GPIO.OUT) # READY LED.
GPIO.setup(4, GPIO.IN, pull_up_down = GPIO.PUD_UP) # Shutdown RPi
button.
# Main script:
subprocess.Popen(“/home/pi/Main_Programs/driver”) # Start driver
application.
GPIO.output(6, GPIO.HIGH) # ON READY LED.
# Definition of Shutdown action:
def Shutdown(channel):
GPIO.output(6, GPIO.LOW) # OFF READY LED.
os.system("bash /home/pi/Main_Scripts/resetShutdown.sh")
os.system("sudo shutdown -h now")
# Events of waiting definition:
GPIO.add_event_detect(4, GPIO.FALLING, callback = Shutdown, bouncetime =
2000)
# Waiting for events:
while 1:
time.sleep(1)
GPIO LEDs test.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Program to test protoboard GPIO by turning on and off some LEDs set
according to the next GPIO settings:
import RPi.GPIO as GPIO
import time
# GPIO configuration:
GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.OUT) # Red LED.
GPIO.setup(13, GPIO.OUT) # Orange LED.
142
GPIO.setup(19, GPIO.OUT) # Yellow LED.
GPIO.setup(16, GPIO.OUT) # Blue LED.
GPIO.setup(26, GPIO.OUT) # Green LED.
GPIO.setup(20, GPIO.OUT) # Dark green LED.
GPIO.setup(21, GPIO.OUT) # White LED.
# Main script:
while 1:
# Turn Red LED on and off:
GPIO.output(12, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(12, GPIO.LOW)
time.sleep(0.125)
# Turn Orange LED on and off:
GPIO.output(13, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(13, GPIO.LOW)
time.sleep(0.125)
# Turn Yellow LED on and off:
GPIO.output(19, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(19, GPIO.LOW)
time.sleep(0.125)
# Turn Blue LED on and off:
GPIO.output(16, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(16, GPIO.LOW)
time.sleep(0.125)
# Turn Green LED on and off:
GPIO.output(26, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(26, GPIO.LOW)
time.sleep(0.125)
# Turn Dark green LED on and off:
GPIO.output(20, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(20, GPIO.LOW)
time.sleep(0.125)
# Turn White LED on and off:
GPIO.output(21, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(21, GPIO.LOW)
time.sleep(0.125)
# Turn all LEDs on and off:
GPIO.output(12, GPIO.HIGH)
GPIO.output(13, GPIO.HIGH)
GPIO.output(19, GPIO.HIGH)
GPIO.output(16, GPIO.HIGH)
GPIO.output(26, GPIO.HIGH)
GPIO.output(20, GPIO.HIGH)
GPIO.output(21, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(12, GPIO.LOW)
GPIO.output(13, GPIO.LOW)
GPIO.output(19, GPIO.LOW)
GPIO.output(16, GPIO.LOW)
GPIO.output(26, GPIO.LOW)
GPIO.output(20, GPIO.LOW)
GPIO.output(21, GPIO.LOW)
time.sleep(0.125)
143
Microphone audio test.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
# Basic program to record audio stream comming from GPIO UART serial in
a .wav file.
# To STOP this recording, break this script.
import wave
import serial
import struct
streamOut = wave.open('Microphone.wav', 'w') # Open new file
Microphone.wav to store samples comming from Arduino.
streamOut.setparams((1, 2, 11025, 0, 'NONE', 'not compressed')) # Set
audio file parameters (1 channel of 16 bit samples at a frequency of
11025 Hz).
arduino = serial.Serial('/dev/ttyAMA0', 115200) # Open serial port.
tempBuffer = [] # Temporal array to store samples before save them in
the wav file.
counter = 0 # When threshold value is reached, then the entire set of
samples is saved in the wav file.
seconds = 0 # Number of recorded seconds.
print "Recording Microphone.wav file."
while True:
sample
sample
to a 16 bits
sample
= ord(arduino.read(1)) # Read Byte of the sample.
= int(round(sample*65535/255)) # Amplify 10 bits ADC sample
sample in order to save it in a .wav file.
= sample - 32768 # Remove DC component.
# Save sample in the temporal array:
packedValue = struct.pack('h', sample)
tempBuffer.append(packedValue)
counter += 1
if (counter == 11025):
# Save entire sample buffer in the wav file:
tempBufferStr = "".join(tempBuffer) # Convert temporal
buffer to string
streamOut.writeframes(tempBufferStr) # Save sample to .wav
file.
tempBuffer = [] # Reset temporal buffer.
counter = 0 # Reset counter.
# Print seconds saved:
seconds += 1
print "Seconds saved:",seconds
arduino.close()
144
RX measurement.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darné MarÃ.
# Program to measure received power from RFM31B module.
import spidev
import time
# Compute frequency value to be written in the RFM31B carrier frequency
registers:
frequency = float(raw_input("Enter the central frequency in MHz (valid
range: 433.05 - 434.79): ")) # Current tunned frequency.
if (frequency < 433.05):
frequency = 433.05 # Frequency cannot go below ISM 433 MHz band.
if (frequency > 434.79):
frequency = 434.79 # Frequency cannot go beyond ISM 433 MHz band.
fc = int(((frequency/10.0) - 43)*64000)
fcHigh = ((fc & 0xFF00) >> 8);
fcLow = (fc & 0xFF);
# SPI settings:
spi = spidev.SpiDev() # SPI object to manage communication.
max_speed_hz = 15625000
spi.open(0, 0) # Opens SPI port '0' and the selected device (0, the
unique present).
# RFM31B minimum settings for measure received power (carrier frequency
and put RFM31B in RX mode):
tx = [0x87, 0x80]
rx = spi.xfer2(tx, max_speed_hz) # RFM31B registers reset.
time.sleep(0.001) # Stop needed to reset the RFM31B module.
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
tx
rx
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
[0x1C, 0x2B]
spi.xfer2(tx, max_speed_hz)
[0x1D, 0x40]
spi.xfer2(tx, max_speed_hz)
[0x20, 0xF4]
spi.xfer2(tx, max_speed_hz)
[0x21, 0x20]
spi.xfer2(tx, max_speed_hz)
[0x22, 0x41]
spi.xfer2(tx, max_speed_hz)
[0x23, 0x89]
spi.xfer2(tx, max_speed_hz)
[0x24, 0x00]
spi.xfer2(tx, max_speed_hz)
[0x25, 0x10]
spi.xfer2(tx, max_speed_hz)
[0xF5, 0x53]
spi.xfer2(tx, max_speed_hz)
[0xF6, fcHigh]
spi.xfer2(tx, max_speed_hz)
[0xF7, fcLow]
spi.xfer2(tx, max_speed_hz)
[0x87, 0x04]
spi.xfer2(tx, max_speed_hz)
# IF Filter Bandwidth.
# AFC Loop Gearshift Override.
# Clock Recovery Oversampling Rate.
# Clock Recovery Offset 2.
# Clock Recovery Offset 1.
# Clock Recovery Offset 0.
# Clock Recovery Timing Loop Gain 1.
# Clock Recovery Timing Loop Gain 0.
# Frequency band select.
# Central frequency (highest part).
# Central frequency (lowest part).
# Put RFM31B in RX state.
145
# Reading RSSI register state for power measurements:
while 1:
# Read RSSI value:
tx = [0x26, 0xFF]
rx = spi.xfer2(tx, max_speed_hz) # RSSI value.
# Express the received value in dBm following the plot in the
datasheet:
print (-122.5 + rx[1]*0.5),"dBm"
time.sleep(0.25) # Wait for 250 ms between measurements.
# Closing SPI connection:
spi.close()
Spectrum.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
# Program used to obtain spectrum for 433 MHz ISM band from RFM31B
module measured power (1 kHz of resolution).
import spidev
import time
# SPI settings:
spi = spidev.SpiDev() # SPI object to manage communication.
max_speed_hz = 15625000
spi.open(0, 0) # Opens SPI port '0' and the selected device (0, the
unique present).
# Frequency settings:
minFreq = 433.05
# Manage output file:
spectrum = open("433 MHz band current spectrum.csv", "w") # Opens the
file in write mode without buffering.
for i in range (0, 1741):
# Obtain required frequency:
freq = minFreq + i/1000.0
fc = int(((freq/10.0) - 43)*64000)
fcHigh = ((fc & 0xFF00) >> 8);
fcLow = (fc & 0xFF);
# RFM31B minimum settings for measure received power (initial
carrier frequency and put RFM31B in RX mode):
tx = [0x87, 0x80]
rx = spi.xfer2(tx, max_speed_hz) # RFM31B registers reset.
time.sleep(0.001) # Stop needed to reset the RFM31B module.
tx
rx
tx
rx
tx
=
=
=
=
=
[0x1C, 0x2B]
spi.xfer2(tx, max_speed_hz) # IF Filter Bandwidth.
[0x1D, 0x40]
spi.xfer2(tx, max_speed_hz) # AFC Loop Gearshift Override.
[0x20, 0xF4]
146
rx = spi.xfer2(tx, max_speed_hz) # Clock Recovery Oversampling
Rate.
tx
rx
tx
rx
tx
rx
tx
rx
=
=
=
=
=
=
=
=
[0x21, 0x20]
spi.xfer2(tx,
[0x22, 0x41]
spi.xfer2(tx,
[0x23, 0x89]
spi.xfer2(tx,
[0x24, 0x00]
spi.xfer2(tx,
max_speed_hz) # Clock Recovery Offset 2.
max_speed_hz) # Clock Recovery Offset 1.
max_speed_hz) # Clock Recovery Offset 0.
max_speed_hz) # Clock Recovery Timing Loop Gain
1.
tx = [0x25, 0x10]
rx = spi.xfer2(tx, max_speed_hz) # Clock Recovery Timing Loop Gain
0.
tx
rx
tx
rx
part).
tx
rx
part).
tx
rx
=
=
=
=
[0xF5, 0x53]
spi.xfer2(tx, max_speed_hz) # Frequency band select.
[0xF6, fcHigh]
spi.xfer2(tx, max_speed_hz) # Central frequency (highest
= [0xF7, fcLow]
= spi.xfer2(tx, max_speed_hz) # Central frequency (lowest
= [0x87, 0x04]
= spi.xfer2(tx, max_speed_hz) # Put RFM31B in RX state.
# Reading RSSI register state for power measurements (a mean of
10000 measurements):
mean = 0 # Variable to store mean obtained power for current
frequency.
for j in range (0, 10000):
# Read RSSI value:
tx = [0x26, 0xFF]
rx = spi.xfer2(tx, max_speed_hz) # RSSI value.
mean = mean + rx[1]/10000.0
# Express the received value in dBm following the plot in the
datasheet:
mean = (-122.5 + mean*0.5)
print "At frequency",freq,"MHz the mean measured power
is",mean,"dBm."
# Append obtained results in a .csv file:
spectrum.write(str(freq))
spectrum.write(str(";"))
spectrum.write(str(mean))
spectrum.write("\n")
# Closing SPI connection and output file:
spi.close()
spectrum.close()
SPI test.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
147
# Basic program to test SPI communication between RPi and 433 MHz
receiver RFM31B.
import spidev
spi = spidev.SpiDev() # SPI object to manage communication.
max_speed_hz = 15625000
# Reading the 0x01 register from RFM31B (module version, expected value:
6):
spi.open(0, 0) # Opens SPI port '0' and the selected device (0, the
unique present).
tx = [0x01, 0xFF] # Buffer sent. Contains register address and a dummy
byte to send while RFM31B transfers the response.
rx = spi.xfer2(tx, max_speed_hz) # Buffer received. The first byte is
dummy since it is the one sent by RFM31B while register address is
placed in tx buffer. The second one contains the value of 0x01 register.
# Printing the result and closing SPI connection:
print "RFM31B 433MHz receiver version:"
print rx[1]
spi.close()
UART GPIO test.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
# Basic program to test GPIO UART serial connection between RPi and
Arduino Mega.
# To STOP this transmission, break this script and reset Arduino Mega.
import serial
arduino = serial.Serial('/dev/ttyAMA0', 115200)
while True:
comando = raw_input('Press B + ENTER: ')
arduino.write(comando) # Send command to Arduino Mega
if comando == 'B':
while True:
line = arduino.readline() # Read line from Arduino
Mega
print line
else:
line = arduino.readline() # Read 'Invalid command' from
Arduino Mega
print line
arduino.close()
148
UART USB test.py source code:
#
#
#
#
This file is part of I-Droid02 Project.
Author: Marc Darne Mari.
# Basic program to test USB UART serial connection between RPi and
Arduino Mega.
# To STOP this transmission, close the Terminal window.
import serial
arduino = serial.Serial('/dev/ttyACM0', 115200)
print("'Starting!")
while True:
comando = raw_input('Type a command: ')
arduino.write(comando) # Send command to Arduino Mega
line = arduino.readline() # Read answer from Arduino Mega
print line
arduino.close()
149
Appendix 7. Arduino Mega 2560 source code
/*
This file is part of I-Droid02 Project.
Author: Marc Darné Marí.
*/
//************** SETUP ***********************************************//
#include <EEPROM.h> // Library used to access to EEPROM memory which
stores motors current position.
#include <LiquidCrystal.h> // Library to manage LCD.
LiquidCrystal lcd(3, 15, 22, 21, 20, 19);
// Constants:
#define COMMAND_BASIC 'b'
#define COMMAND_BASIC_WARNING_LOW_BATTERY 'w'
#define COMMAND_CHEST_BUTTONS 'c'
#define COMMAND_DISPLAY 'd'
#define COMMAND_DISPLAY_CLEAR 'c'
#define COMMAND_DISPLAY_SETUP 's'
#define COMMAND_DISPLAY_MOVE_OR_WRITE 'w'
#define COMMAND_END_OF_TRANSMISSION 0x04
#define COMMAND_LEDS 'l'
#define COMMAND_MOTORS 'm'
#define COMMAND_MOTORS_CALIBRATE 'S'
#define COMMAND_MOTORS_POSITION 'p'
#define COMMAND_MOTORS_REQUEST_DATA 'd'
#define COMMAND_MOTORS_RESET_ENCODERS 'R'
#define COMMAND_MOTORS_SET_MOVEMENT 'm'
#define COMMAND_MOTORS_SET_MOVEMENT_TARGET_REACHED 'e'
#define COMMAND_MOTORS_START 's'
#define COMMAND_MOTORS_STOP 't'
#define COMMAND_MOTORS_UPDATE_MOVING_FLAG 'u'
#define COMMAND_MOTORS_WHEELS_SPEED 'w'
#define COMMAND_TOUCH 't'
#define COMMAND_ULTRASONIC 'u'
#define COMMAND_RESET 'r'
#define COMMAND_RESET_ARDUINO_BOARD 'a'
#define COMMAND_RESET_MOTORS 'm'
#define LOW_BATTERY_THRESHOLD 8.6 // If battery voltage read is below
this threshold, a critical battery situation is detected.
#define ENCODER_PREVIOUS_READINGS 10 // Number of readings that are
stored for each encoder to check position.
#define DISPLAY_MAX_CHAR_SIZE 16 // Maximum characters that can be
written in each display row.
#define MICROPHONE_BUFFER_LENGTH 10 // Buffer length to store microphone
samples.
#define SERIAL_BAUD_RATE 115200 // Serial and Serial2 Baud Rate.
#define HEAD_TILT_MIN_PWM 110 // Minimum PWM value to move head tilt
motor.
#define HEAD_PAN_MIN_PWM 86 // Minimum PWM value to move head pan motor.
#define LEFT_ARM_MIN_PWM 117 // Minimum PWM value to move left arm
motor.
#define RIGHT_ARM_MIN_PWM 117 // Minimum PWM value to move right arm
motor.
#define HAND_MIN_PWM 85 // Minimum PWM value to move hand motor.
#define HIP_MIN_PWM 130 // Minimum PWM value to move hip motor.
150
#define LEFT_WHEEL_MIN_PWM 80 // Minimum PWM value to move left wheel
motor.
#define RIGHT_WHEEL_MIN_PWM 80 // Minimum PWM value to move right wheel
motor.
#define NO_ENCODER_MAX_VALUE 2 // For motors with no encoders, only two
positions are available (1 and 2).
#define HEAD_PAN_MAX_VALUE 21 // Number of positions that head pan motor
has.
#define ARMS_MAX_VALUE 31 // Number of positions that arms encoders
have.
#define WHEELS_ENCODER_MAX_VALUE 40 // Number of positions that wheels
encoders have.
#define WHEELS_PERIMETER_INC 24.2 / WHEELS_ENCODER_MAX_VALUE // The
perimeter of each wheel in centimeters divided by the number of holes
(minimum measurable distance).
#define WHEELS_ANGLE_INC 200 / WHEELS_ENCODER_MAX_VALUE // The angle
turned by I-Droid02 at each wheel turn divided by the number of holes
(minimum measurable angle).
// Macros:
#define cbi(sfr,bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // Clear bit in sfr
address.
#define sbi(sfr,bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Ser bit in sfr
address.
void(* resetFunc)(void) = 0; // Soft reset Arduino Mega via software at
address 0.
// Structs:
struct AXIS_MOVEMENT_POINT {
unsigned char directionFlag; // Flag to indicate direction of movement
(0x00 indicates STOP).
unsigned char x; // Abscissa modulus value of the point.
unsigned char y; // Ordered modulus value of the point.
long distanceAngle; // Total distance to cover/angle to rotate. -1
means infinite distance/angle.
}; // Variable type to store a point in the axis of movement and the
direction flag indicating the kind of trajectory.
struct WHEELS {
int leftWheelPWM; // PWM value for left wheel.
int rightWheelPWM; // PWM value for right wheel.
boolean leftWheelDirection; // Direction of left wheel (true =
forward, false = backward).
boolean rightWheelDirection; // Direction of right wheel (true =
forward, false = backward).
}; // Variable type to store wheels' motors data.
// Global variables:
unsigned long lastMillisPeriodicReport; // Variable used
battery and temperature sensors periodic report.
unsigned long lastMillisWheelsSpeed; // Variable used to
wheels' speed must be reported.
boolean batteryWarning = false; // If true and a new
boolean startUART_GPIOTest = false; // If char B is sent
then this boolean comes true and GPIO UART test starts.
boolean resetMode = false; // Variable used to stop head
automatically in reset command.
to schedule
schedule when
via GPIO UART,
pan motor
unsigned long lastMillisChestButtonPressed; // Variable to avoid more
than one detection per pressed button.
151
unsigned char limitedMotorsInMovement = 0; // Current motors in movement
according to the flag: 0b00HiHaRLPT. If the corresponding bit is set,
the motor is currently in movement. Hi (hip), Ha (hand), R (right arm),
L (left arm), P (head pan), T (head tilt).
boolean limitedMotorsDirection[6]; // Array used to store current
direction of all I-Droid02 motors (or last if someone is stopped).
int limitedMotorsCurrentPosition[6]; // Array to store the current
position of all limited I-Droid02 motors.
int limitedMotorsFinalPosition[6]; // Array to store the position to be
reached by all limited I-Droid02 motors.
int motorsCurrentSpeed[8]; // Array to store the current speed of all IDroid02 motors.
int motorsFinalSpeed[8]; // Array to store the new speed to be reached
of all I-Droid02 motors.
unsigned long lastMillisCorrectionTime[2]; // Array used to schedule
time between motor stop and motor position correction (only for head and
arms motors).
unsigned long lastMicrosLeftWheelChangeSpeed; // Variable used to
schedule all speed change intervals of left wheel motor.
unsigned long lastMicrosRightWheelChangeSpeed; // Variable used to
schedule all speed change intervals of right wheel motor.
boolean motorsNeedToReset[7]; // Array used to reset all motors (except
wheeels) one by one in order.
int headTiltTimeToStop; // Variable to store time to spend before stop
hip's motor.
unsigned long lastMillisHeadTiltMotorStarted; // Variable to store
instant time when head tilt's motor has started.
boolean headPanEncoderPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Array to store last readings from head pan phototransistor (glitch
avoidance).
unsigned long lastMillisHeadPanEncoderTo1; // Variable to store instant
time when this encoder value is 1 and the motor is in movement.
boolean leftArmCheckEncoder = false; // Restricts the reading of left
arm encoder only when it is in movement.
boolean leftArmRepositionStarted = false; // Variable to know when left
arm reposition has started.
boolean leftArmEncoderPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Variable to store last readings from left arm phototransistor (glitch
avoidance).
unsigned long lastMillisLeftArmEncoderTo1; // Variable to store instant
time when this encoder value is 1 and the motor is in movement.
boolean rightArmCheckEncoder = false; // Restricts the reading of right
arm encoder only when it is in movement.
boolean rightArmRepositionStarted = false; // Variable to know when
right arm reposition has started.
boolean rightArmEncoderPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Variable to store last readings from right arm phototransistor (glitch
avoidance).
unsigned long lastMillisRightArmEncoderTo1; // Variable to store instant
time when this encoder value is 1 and the motor is in movement.
int handDetectorThreshold; // Threshold ADC value as a function of PWM
to detect the instant to stop the hand motor (end of stroke detected).
int handEndDetectorPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Variable to store last readings from hand end of stroke detection
(glitch avoidance).
int hipTimeToStop; // Variable to store time to spend before stop hip's
motor.
unsigned long lastMillisHipMotorStarted; // Variable to store instant
time when hip's motor has started.
152
boolean leftWheelEncoderPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Variable to store last readings from left wheel phototransistor (glitch
avoidance).
boolean rightWheelEncoderPreviousReadings[ENCODER_PREVIOUS_READINGS]; //
Variable to store last readings from right wheel phototransistor (glitch
avoidance).
struct AXIS_MOVEMENT_POINT currentAxisPoint; // Current moving
trajectory.
struct AXIS_MOVEMENT_POINT nextAxisPoint; // Next moving trajectory.
boolean resettingMovement = false; // true if last movement set needs to
completely stop I-Droid02 before start next one.
float leftWheelSpeed; // Current speed of left wheel in km/h.
float rightWheelSpeed; // Current speed of right wheel in km/h.
long leftWheelPositionCounter = 0; // Counter incremented every time a
new hole is detected at left-wheel encoder.
long rightWheelPositionCounter = 0; // Counter incremented every time a
new hole is detected at right-wheel encoder.
unsigned int leftWheelTimeDifference; // Time difference between left
wheel encoder hole readings.
unsigned long lastMillisLeftWheelHole = millis(); // Variable used to
store last millis() call to compute left wheel speed.
unsigned int rightWheelTimeDifference; // Time difference between right
wheel encoder hole readings.
unsigned long lastMillisRightWheelHole = millis(); // Variable used to
store last millis() call to compute right wheel speed.
int originalLeftWheelFinalSpeed; // For rectilinear and spin movements,
this variable stores the original set speed of left wheel (the maximum
threshold for rectilinear algorithm).
int originalRightWheelFinalSpeed; // For rectilinear and spin movements,
this variable stores the original set speed of right wheel (the maximum
threshold for rectilinear algorithm).
unsigned long lastMillisTacte; // Variable to store when touch sensor
has been touched.
unsigned long lastMillisUltrasonic; // Variable used to schedule when it
is time to watch for a certain ultrasonic sensor.
long microsFirst; // Value of micros() when the reference pulse has been
detected.
volatile int ultrasonicSensor = 1; // Express current ultrasonic sensor
being watched (center ultrasonic sensor by default).
volatile long TOF; // Variable to store time between ultrasonic sensor
pulses in microseconds.
volatile boolean firstPulse = false; // Boolean variable to know if
detected pulse is the emitted one or the received one.
volatile boolean ended; // Boolean to mark a new TOF computed.
void setup() {
noInterrupts(); // Disable all interruptions.
// Serial setup:
Serial.begin(SERIAL_BAUD_RATE); // Raspberry Pi USB UART.
Serial2.begin(SERIAL_BAUD_RATE); // Raspberry Pi GPIO UART.
// Setup basic resources:
configurarPins();
valorsInicials();
initDisplay();
153
// Retrieve limited motors position from EEPROM:
retrieveLimitedMotorsPositionValues();
resetAllMotors(false); // If motors are not in default position, put
them.
// Reset movement distance/angle value:
currentAxisPoint.distanceAngle = -1;
nextAxisPoint.distanceAngle = -1;
// Ultrasonic system:
PCMSK2 = B00001110; // Set masks for pin change interruption used in
ultrasonic system.
// Setup ADC prescaler to 16 in order to reduce conversion time
without affecting the measurement accuracy:
// Clearing ADPS0 and ADPS1 bits allows directly to pass from 128
prescaler (default) to 16 prescaler.
cbi(ADCSRA, ADPS0);
cbi(ADCSRA, ADPS1);
// Setup Timer5 to sample microphone for audio tasks:
TCCR5A = 0; // Restore Timer5 A default settings.
TCCR5B = 0; // Restore Timer5 B default settings.
TCCR5C = 0; // Restore Timer5 C default settings.
TCNT5 = 0; // Reset counter.
sbi(TCCR5B, CS50); // No prescale timer's clock (it counts following
16 MHz microprocessor's clock).
sbi(TCCR5B, WGM52); // Waveform Generation Mode to CTC (no PWM is
generated; when timer reaches compare match value, it triggers an
interruption and resets counter).
OCR5A = 1451; // Timer5 counter threshold. Counting at 16 MHz, this
value is reached at microphone's sampling time of 90 us (11025 Hz
sampling frequency).
sbi(TIMSK5, OCIE5A); // Enable interruptions on Timer5 compare match A
event (enable microphone).
Serial.write(COMMAND_RESET); // Indicate that a reset has been
produced.
interrupts(); // Enable all interruptions.
// Show startup sequence only when I-Droid02 is booting:
if (EEPROM.read(6) == 1) {
// Head LEDs blink:
LEDs(0, false, true, true, true, false, false, true, false, false);
// Start ear LEDs and red LEDs of both eyes.
delay(500); // Wait 500 ms.
LEDs(0, false, true, true, false, true, false, false, true, false);
// Start yellow LEDs of both eyes.
delay(500); // Wait 500 ms.
LEDs(0, false, true, true, false, false, true, false, false, true);
// Start green LEDs of both eyes.
delay(500); // Wait 500 ms.
LEDs(0, false, false, false, false, false, false, false, false,
false); // Stop all LEDs.
// Display "I-Droid02 booting..." message:
writeDisplay(3, 0, "I-Droid02", 9);
writeDisplay(3, 1, "booting...", 10);
154
// Clear startup flag:
EEPROM.write(6, 0);
}
}
//************** RUN LOOP ********************************************//
void loop() {
// Reception of commands from Raspberry Pi:
unsigned char uartBuffer[64]; // Buffer to store received Bytes.
char intBuffer[2]; // Buffer used to parse integer values.
char displayText[16]; // Array to store text to be written to display.
int distanceAngle; // Established parameters for
COMMAND_MOTORS_SET_MOVEMENT command.
boolean resetMotorsFlag; // Boolean to distinguish whether
COMMAND_RESET_MOTORS is called from OS at shutdown or in normal
operation.
int i;
if (Serial.available()) {
// There are new Bytes to be read.
cbi(TIMSK5, OCIE5A); // Disable interruptions on Timer5 compare
match A event.
Serial.readBytesUntil(COMMAND_END_OF_TRANSMISSION, uartBuffer, 64);
// Read Bytes until receive COMMAND_END_OF_TRANSMISSION character.
sbi(TIMSK5, OCIE5A); // Enable interruptions on Timer5 compare match
A event.
// Determine received command.
switch (uartBuffer[0]) {
case COMMAND_DISPLAY:
// Display:
switch (uartBuffer[1]) {
case COMMAND_DISPLAY_CLEAR:
// Clear display:
clearDisplay();
break;
case COMMAND_DISPLAY_SETUP:
// Setup contrast, cursor, cursor blinking, display ON/OFF
and writting direction:
configDisplay(uartBuffer[2], ((uartBuffer[3] & 0x08) >> 3),
((uartBuffer[3] & 0x04) >> 2), ((uartBuffer[3] & 0x02) >> 1),
(uartBuffer[3] & 0x01));
break;
case COMMAND_DISPLAY_MOVE_OR_WRITE:
// Write characters to display or move cursor:
for (i = 0; i < uartBuffer[3]; i++) {
displayText[i] = uartBuffer[4 + i];
}
writeDisplay(((uartBuffer[2] & 0xF0) >> 4), (uartBuffer[2] &
0x0F), displayText, uartBuffer[3]);
break;
}
break;
case COMMAND_LEDS:
// LEDs:
LEDs(uartBuffer[2], uartBuffer[3], ((uartBuffer[1] & 0x80) >>
7), ((uartBuffer[1] & 0x40) >> 6), ((uartBuffer[1] & 0x20) >> 5),
((uartBuffer[1] & 0x10) >> 4), ((uartBuffer[1] & 0x08) >> 3),
155
((uartBuffer[1] & 0x04) >> 2), ((uartBuffer[1] & 0x02) >> 1),
(uartBuffer[1] & 0x01));
break;
case COMMAND_MOTORS:
// Motors:
switch (uartBuffer[1]) {
case COMMAND_MOTORS_CALIBRATE:
// Move a little bit the position of a motor (done in
calibration mode):
motorStartSetup(((uartBuffer[2] & 0xF0) >> 4),
(uartBuffer[2] & 0x01));
break;
case COMMAND_MOTORS_REQUEST_DATA:
// Raspberry Pi requests the position of all limited motors:
// Notify the position of all limited motors one by one:
for (i = 0; i < 6; i++) {
motorsNotifyPosition(5 + i,
limitedMotorsCurrentPosition[i]);
}
break;
case COMMAND_MOTORS_RESET_ENCODERS:
// Reset encoder count position (done in calibration mode):
motorsResetEncoderCount();
break;
case COMMAND_MOTORS_SET_MOVEMENT:
// Move I-Droid02:
// Parse distanceAngle value:
intBuffer[0] = uartBuffer[5];
intBuffer[1] = uartBuffer[6];
memcpy(&distanceAngle, intBuffer, sizeof(int));
motorSetNewMovement(uartBuffer[2], uartBuffer[3],
uartBuffer[4], distanceAngle);
break;
case COMMAND_MOTORS_START:
// Start any motor:
motorStart(uartBuffer[2], uartBuffer[3], uartBuffer[4]);
break;
case COMMAND_MOTORS_STOP:
// Stop any motor before reach its final position:
// Only motors with more than two positions can be stopped
using this command:
if ((uartBuffer[2] == 6) || (uartBuffer[2] == 7) ||
(uartBuffer[2] == 8)) {
// This motor must be stopped inmediatelly at next
position:
if (limitedMotorsDirection[uartBuffer[2] - 5]) {
// Motor moving up/left/forwards.
// Set new final position 2 positions higher than the
current one:
limitedMotorsFinalPosition[uartBuffer[2] - 5] =
limitedMotorsCurrentPosition[uartBuffer[2] - 5] + 2;
// Check upper bound:
if ((uartBuffer[2] == 6) &&
(limitedMotorsFinalPosition[uartBuffer[2] - 5] > HEAD_PAN_MAX_VALUE)) {
156
limitedMotorsFinalPosition[uartBuffer[2] - 5] =
HEAD_PAN_MAX_VALUE;
}
if (((uartBuffer[2] == 7) || (uartBuffer[2] == 8)) &&
(limitedMotorsFinalPosition[uartBuffer[2] - 5] > ARMS_MAX_VALUE)) {
limitedMotorsFinalPosition[uartBuffer[2] - 5] =
ARMS_MAX_VALUE;
}
} else {
// Motor moving down/right/backwards.
// Set new final position 2 positions lower than the
current one:
limitedMotorsFinalPosition[uartBuffer[2] - 5] =
limitedMotorsCurrentPosition[uartBuffer[2] - 5] - 2;
// Check lower bound:
if (limitedMotorsFinalPosition[uartBuffer[2] - 5] < 1) {
limitedMotorsFinalPosition[uartBuffer[2] - 5] = 1;
}
}
}
break;
}
break;
case COMMAND_RESET:
// Reset:
switch (uartBuffer[1]) {
case COMMAND_RESET_ARDUINO_BOARD:
// Soft reset Arduino Mega 2560:
clearDisplay();
writeDisplay(0, 0, "Warning message!", 16);
writeDisplay(0, 1, "Board SOFT RESET", 16);
delay(1500); // Wait 1.5 s before reset Arduino Mega 2560.
resetFunc();
break;
case COMMAND_RESET_MOTORS:
// Reset motors that are not in the reset position:
resetMotorsFlag = uartBuffer[2];
// Previous tasks to do if call is done from OS at shutdown:
if (resetMotorsFlag) {
EEPROM.write(6, 1); // Set startup flag.
clearDisplay();
writeDisplay(0, 0, "Shutdown process", 16);
writeDisplay(0, 1, "RESET MOTORS", 12);
delay(1500); // Wait 1.5 s before reset motors.
}
resetAllMotors(resetMotorsFlag);
break;
}
break;
case COMMAND_ULTRASONIC:
// Ultrasonic system:
switchUltrasonicSensor(uartBuffer[2]);
break;
}
}
157
// Battery voltage level and temperature sensor:
if (scheduler(&lastMillisPeriodicReport, 10000)) {
float batteryVoltageValue, batteryPercentageValue, temperatureValue;
unsigned char* batteryVoltageByteArray;
unsigned char* batteryPercentageByteArray;
unsigned char* temperatureByteArray;
cbi(TIMSK5, OCIE5A); // Disable microphone capture.
batteryVoltageValue = battery();
temperatureValue = temperature();
batteryPercentageValue = batteryPercentage(batteryVoltageValue);
batteryVoltageByteArray = (unsigned char*) &batteryVoltageValue;
batteryPercentageByteArray = (unsigned char*)
&batteryPercentageValue;
temperatureByteArray = (unsigned char*) &temperatureValue;
// Send command:
Serial.write(COMMAND_BASIC);
Serial.write(batteryVoltageByteArray[0]);
Serial.write(batteryVoltageByteArray[1]);
Serial.write(batteryVoltageByteArray[2]);
Serial.write(batteryVoltageByteArray[3]);
Serial.write(batteryPercentageByteArray[0]);
Serial.write(batteryPercentageByteArray[1]);
Serial.write(batteryPercentageByteArray[2]);
Serial.write(batteryPercentageByteArray[3]);
Serial.write(temperatureByteArray[0]);
Serial.write(temperatureByteArray[1]);
Serial.write(temperatureByteArray[2]);
Serial.write(temperatureByteArray[3]);
// Low battery warning:
if (batteryVoltageValue < LOW_BATTERY_THRESHOLD) {
// Battery voltage value read is too low!
if (batteryWarning) {
// Two consecutive battery voltage values read are below
threshold. Send COMMAND_BASIC_WARNING_LOW_BATTERY:
Serial.write(COMMAND_BASIC_WARNING_LOW_BATTERY);
} else {
batteryWarning = true; // Set alarm.
}
} else {
batteryWarning = false; // Clear a possible previous fake alarm.
}
sbi(TIMSK5, OCIE5A); // Enable microphone capture.
} // This data have to be reported every 10 seconds.
// Chest buttons:
unsigned char chestFlag = chestButtons();
if (chestFlag != 0) {
// Send command:
Serial.write(COMMAND_CHEST_BUTTONS);
Serial.write(chestFlag);
} // The command transmission is only required when a button is
pressed.
158
// Motors:
unsigned char* leftWheelSpeedByteArray;
unsigned char* rightWheelSpeedByteArray;
motorsReposition(); // After a limited motor movement, if it stops in
an unnapropiate position this routine moves it to the correct place
(only when motor reaches initial target position).
motorsCheckPosition(); // Monitors the current position of all motors
and stops them if necessary.
motorsCheckSpeed(); // Checks the current speed of all motors and
changes it if necessary.
checkMovement(); // Checks for wheels position and changes current
movement vector if a new movement has been set.
checkRectilinearity(); // For rectilinear and spinning movements, this
routine checks that actually both wheels are turning at the same speed.
if (scheduler(&lastMillisWheelsSpeed, 500) && ((leftWheelSpeed != 0)
|| (leftWheelSpeed != 0))) {
leftWheelSpeedByteArray = (unsigned char*) &leftWheelSpeed;
rightWheelSpeedByteArray = (unsigned char*) &rightWheelSpeed;
// Send command:
Serial.write(COMMAND_MOTORS);
Serial.write(COMMAND_MOTORS_WHEELS_SPEED);
Serial.write(leftWheelSpeedByteArray[0]);
Serial.write(leftWheelSpeedByteArray[1]);
Serial.write(leftWheelSpeedByteArray[2]);
Serial.write(leftWheelSpeedByteArray[3]);
Serial.write(rightWheelSpeedByteArray[0]);
Serial.write(rightWheelSpeedByteArray[1]);
Serial.write(rightWheelSpeedByteArray[2]);
Serial.write(rightWheelSpeedByteArray[3]);
} // Any of the wheels is moving, a command needs to be sent (every
500 ms).
// Check if any motor needs to be reset:
if (motorsNeedToReset[0] || motorsNeedToReset[1] ||
motorsNeedToReset[2] || motorsNeedToReset[3] || motorsNeedToReset[4] ||
motorsNeedToReset[5] || motorsNeedToReset[6] || motorsNeedToReset[7]) {
motorsReset();
}
// Touch sensor:
boolean touched = touchSensor();
if (touched) {
// Send command:
Serial.write(COMMAND_TOUCH);
} // The command transmission is only required when touch sensor is
touched.
// Ultrasonic system:
float distance;
unsigned char* distanceByteValue;
ultrasonicEnable(); // Enable ultrasonic interruption when schedule
requests it.
distance = computeUltrasonicDistance();
if (distance != -1) {
distanceByteValue = (unsigned char*) &distance;
159
// Send command:
Serial.write(COMMAND_ULTRASONIC);
Serial.write(distanceByteValue[0]);
Serial.write(distanceByteValue[1]);
Serial.write(distanceByteValue[2]);
Serial.write(distanceByteValue[3]);
} // Ultrasonic command is only sent when a valid distance is
computed.
}
//************** ADDITIONAL ROUTINES *********************************//
//-------------- SETUP ROUTINES --------------------------------------//
// Digital pins INPUT/OUTPUT:
void configurarPins() {
pinMode(A9, INPUT); // Left ultrasonic sensor (analog pin used as a
digital pin).
pinMode(A10, INPUT); // Center ultrasonic sensor (analog pin used as a
digital pin).
pinMode(A11, INPUT); // Right ultrasonic sensor (analog pin used as a
digital pin).
pinMode(3, OUTPUT); // Register Select (Display).
pinMode(4, OUTPUT); // Contrast adjustment (Display).
pinMode(5, OUTPUT); // Head tilt motor.
pinMode(6, OUTPUT); // Head pan motor.
pinMode(7, OUTPUT); // Left arm motor.
pinMode(8, OUTPUT); // Right arm motor.
pinMode(9, OUTPUT); // Hand motor.
pinMode(10, OUTPUT); // Hip motor.
pinMode(11, OUTPUT); // Left wheel motor.
pinMode(12, OUTPUT); // Right wheel motor.
pinMode(13, OUTPUT); // Head orange LED.
pinMode(15, OUTPUT); // Enable (Display).
pinMode(19, OUTPUT); // DB7 (Display).
pinMode(20, OUTPUT); // DB6 (Display).
pinMode(21, OUTPUT); // DB5 (Display).
pinMode(22, OUTPUT); // DB4 (Display).
pinMode(23, INPUT); // Head tilt motor encoder.
pinMode(24, INPUT); // Hip motor encoder.
pinMode(26, OUTPUT); // Head tilt motor direction control.
pinMode(27, INPUT); // Head pan motor encoder.
pinMode(28, OUTPUT); // Head pan motor direction control.
pinMode(29, INPUT); // Left arm motor encoder.
pinMode(30, OUTPUT); // Left arm motor direction control.
pinMode(31, INPUT); // Right arm motor encoder.
pinMode(32, OUTPUT); // Right arm motor direction control.
pinMode(33, OUTPUT); // Base position LEDs.
pinMode(34, OUTPUT); // Hand motor direction control.
pinMode(36, OUTPUT); // Hip motor direction control.
pinMode(37, INPUT); // Left wheel motor encoder.
pinMode(38, OUTPUT); // Left wheel motor direction control.
pinMode(39, INPUT); // Right wheel motor encoder.
pinMode(40, OUTPUT); // Right wheel motor direction control.
pinMode(42, INPUT_PULLUP); // Left chest button.
pinMode(43, INPUT_PULLUP); // Center chest button.
pinMode(44, INPUT_PULLUP); // Right chest button.
pinMode(45, INPUT); // Touch sensor.
pinMode(46, OUTPUT); // Left ear LED.
pinMode(47, OUTPUT); // Right ear LED.
pinMode(48, OUTPUT); // Left eye red LED.
pinMode(49, OUTPUT); // Left eye yellow LED.
160
pinMode(50,
pinMode(51,
pinMode(52,
pinMode(53,
OUTPUT);
OUTPUT);
OUTPUT);
OUTPUT);
//
//
//
//
Left eye green LED.
Right eye red LED.
Right eye yellow LED.
Right eye green LED.
}
// This routine is used to schedule different events. This routine does
not use interruptions, so it cannot be used to schedule events that must
occur exactly after cycle time:
boolean scheduler(unsigned long* lastMillis, unsigned long cycle) {
// lastMillis: millis() external pointer value to compare with current
millis() value.
// cycle: Minimum time in milliseconds to wait before execute the
event.
unsigned long currentMillis = millis(); // Current microprocessor
clock value in milliseconds.
if (currentMillis - *lastMillis >= cycle) {
*lastMillis = currentMillis;
return true; // Time stored in cycle has run out.
}
else {
return false; // Time stored in cycle has not run out.
}
}
// The same as scheduler() but using Arduino micros() function:
boolean schedulerMicros(unsigned long* lastMicros, unsigned long cycle)
{
// lastMicros: micros() external pointer value to compare with current
micros() value.
// cycle: Minimum time in microseconds to wait before execute the
event.
unsigned long currentMicros = micros(); // Current microprocessor
clock value in microseconds.
if (currentMicros - *lastMicros >= cycle) {
*lastMicros = currentMicros;
return true; // Time stored in cycle has run out.
}
else {
return false; // Time stored in cycle has not run out.
}
}
// Initial values of all digital output pins:
void valorsInicials() {
analogWrite(3, 0);
analogWrite(4, 130);
analogWrite(5, 0);
analogWrite(6, 0);
analogWrite(7, 0);
analogWrite(8, 0);
analogWrite(9, 0);
analogWrite(10, 0);
analogWrite(11, 0);
analogWrite(12, 0);
analogWrite(13, 0);
digitalWrite(15, LOW);
digitalWrite(19, LOW);
161
digitalWrite(20,
digitalWrite(21,
digitalWrite(22,
digitalWrite(26,
digitalWrite(28,
digitalWrite(30,
digitalWrite(32,
digitalWrite(33,
digitalWrite(34,
digitalWrite(36,
digitalWrite(38,
digitalWrite(40,
digitalWrite(46,
digitalWrite(47,
digitalWrite(48,
digitalWrite(49,
digitalWrite(50,
digitalWrite(51,
digitalWrite(52,
digitalWrite(53,
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
LOW);
HIGH);
HIGH);
HIGH);
HIGH);
HIGH);
HIGH);
}
//-------------- UARTS TEST ROUTINES ---------------------------------//
// Routine to test UART GPIO serial communication between Arduino Mega
and Raspberry Pi:
void testUART_GPIO() {
char temps[16]; // Pointer to store currentMillis for display show.
if (Serial2.available()) {
char c = Serial2.read();
if (c == 'B') {
clearDisplay();
startUART_GPIOTest = true;
} else {
clearDisplay();
writeDisplay(0, 0, "Invalid command", 15);
Serial2.println("Invalid command");
}
}
if (startUART_GPIOTest) {
long currentMillis = millis();
int currentMillisSeconds = currentMillis / 1000; // currentMillis
value divided by 1000.
ltoa(currentMillisSeconds, temps, 10);
writeDisplay(0, 0, "Init time:", 10);
writeDisplay(0, 1, temps, log10(currentMillisSeconds) + 1);
Serial2.print("Time since Arduino Mega is ON: ");
Serial2.println(currentMillis);
}
}
// Routine to test UART USB serial communication between Arduino Mega
and Raspberry Pi:
void testUART_USB() {
if (Serial.available()) {
char c = Serial.read();
switch (c) {
case 'A':
// Head up.
motorStartSetup(5, true);
162
clearDisplay();
writeDisplay(4, 0, "Head up", 7);
Serial.println("Head up");
break;
case 'B':
// Head down.
motorStartSetup(5, false);
clearDisplay();
writeDisplay(3, 0, "Head down", 9);
Serial.println("Head down");
break;
case 'C':
// Head left.
motorStartSetup(6, true);
clearDisplay();
writeDisplay(3, 0, "Head left", 9);
Serial.println("Head left");
break;
case 'D':
// Head right.
motorStartSetup(6, false);
clearDisplay();
writeDisplay(3, 0, "Head right", 10);
Serial.println("Head right");
break;
case 'E':
// Left arm up.
motorStartSetup(7, true);
clearDisplay();
writeDisplay(2, 0, "Left arm up", 11);
Serial.println("Left arm up");
break;
case 'F':
// Left arm down.
motorStartSetup(7, false);
clearDisplay();
writeDisplay(1, 0, "Left arm down", 13);
Serial.println("Left arm down");
break;
case 'G':
// Right arm up.
motorStartSetup(8, true);
clearDisplay();
writeDisplay(2, 0, "Right arm up", 12);
Serial.println("Right arm up");
break;
case 'H':
// Right arm down.
motorStartSetup(8, false);
clearDisplay();
writeDisplay(1, 0, "Right arm down", 14);
Serial.println("Right arm down");
break;
case 'I':
// Open hand.
motorStartSetup(9, true);
clearDisplay();
writeDisplay(3, 0, "Open hand", 9);
Serial.println("Open hand");
break;
163
case 'J':
// Close hand.
motorStartSetup(9, false);
clearDisplay();
writeDisplay(3, 0, "Close hand", 10);
Serial.println("Close hand");
break;
case 'K':
// Hip up.
motorStartSetup(10, true);
clearDisplay();
writeDisplay(5, 0, "Hip up", 6);
Serial.println("Hip up");
break;
case 'L':
// Hip down.
motorStartSetup(10, false);
clearDisplay();
writeDisplay(4, 0, "Hip down", 8);
Serial.println("Hip down");
break;
case 'M':
// Left wheel forwards at minimum speed.
motorStartSetup(11, true);
clearDisplay();
writeDisplay(0, 0, "L wheel forwards", 16);
Serial.println("Left wheel forwards");
break;
case 'N':
// Left wheel backwards at minimum speed.
motorStartSetup(11, false);
clearDisplay();
writeDisplay(0, 0, "L wheel bakwards", 16);
Serial.println("Left wheel backwards");
break;
case 'O':
// Right wheel forwards at minimum speed.
motorStartSetup(12, true);
clearDisplay();
writeDisplay(0, 0, "R wheel forwards", 16);
Serial.println("Right wheel forwards");
break;
case 'P':
// Right wheel backwards at minimum speed.
motorStartSetup(12, false);
clearDisplay();
writeDisplay(0, 0, "R wheel bakwards", 16);
Serial.println("Right wheel backwards");
break;
case 'Q':
// Turn ON all LEDs.
LEDs(200, true, true, true, true, true, true, true, true, true);
clearDisplay();
writeDisplay(4, 0, "LEDs ON", 7);
Serial.println("LEDs ON");
break;
case 'q':
// Turn OFF all LEDs.
LEDs(0, false, false, false, false, false, false, false, false,
false);
164
clearDisplay();
writeDisplay(4, 0, "LEDs OFF", 8);
Serial.println("LEDs OFF");
break;
case 'R':
// Reset motors that are not in the reset position:
resetAllMotors(false);
clearDisplay();
writeDisplay(0, 0, "Reset all motors", 16);
break;
case 'r':
// Reset Arduino Mega 2560 by software:
clearDisplay();
writeDisplay(2, 0, "Arduino Mega", 12);
writeDisplay(5, 1, "RESET", 5);
delay(1500); // Wait 1.5 s before reset Arduino Mega 2560.
resetFunc();
break;
default:
// Invalid command.
writeDisplay(0, 0, "Invalid command", 15);
Serial.println("Invalid command");
break;
}
}
}
//-------------- BATTERY VOLTAGE LEVEL -------------------------------//
// Function to obtain battery voltage level:
float battery() {
float sensorValue;
// Dummy readings for ADC stability:
sensorValue = analogRead(A0);
sensorValue = analogRead(A0);
sensorValue = analogRead(A0);
return sensorValue * (5.0 / 1023.0) * 2.415828142; // Convert the
analog reading (which goes from 0 - 1023) to a voltage (0 - 5V) and undo
hardware resistive divider.
}
// Function to compute battery percentage:
float batteryPercentage(float voltage) {
// voltage: Battery voltage value obtained with battery().
float percentage; // Variable to store computed percentage.
if (voltage >= 11.55) {
// 1st straight function.
percentage = (100.0 / 13.0) * voltage + (93.0 / 13.0);
} else if ((voltage >= 10.2) && (voltage < 11.55)) {
// 2nd straight function.
percentage = (752.0 / 27.0) * voltage - (10156.0 / 45.0);
} else if ((voltage >= 9.6) && (voltage < 10.2)) {
// 3rd straight function.
percentage = (260.0 / 3.0) * voltage - (4128.0 / 5.0);
} else {
// 4th straight function.
percentage = (64.0 / 17.0) * voltage - (2528.0 / 85.0);
165
} // Discharge curve is approximated with 4 straight functions,
according to empirical graph.
// Truncate obtained percentage according to absolute maximum values
of voltage:
if (percentage > 100) {
percentage = 100;
}
if (percentage < 0) {
percentage = 0;
}
return percentage;
}
//-------------- CHEST BUTTONS ---------------------------------------//
// Function to detect if any chest button is pressed:
byte chestButtons() {
byte flag = 0x00; // Byte structure: 00000LCR. L = Left button. C =
Center button. R = Right button.
int botons = PINL & 0xE0; // Reading chest buttons in the same
instruction.
if (scheduler(&lastMillisChestButtonPressed, 200)) {
// Space between pressings detection has passed.
if ((botons & 0x80) == 0x00) {
// Left chest button pressed.
flag = flag | 0x04;
}
if ((botons & 0x40) == 0x00) {
// Center chest button pressed.
flag = flag | 0x02;
}
if ((botons & 0x20) == 0x00) {
// Right chest button pressed.
flag = flag | 0x01;
}
}
return flag;
}
//-------------- DISPLAY ---------------------------------------------//
// Clear display function:
void clearDisplay() {
lcd.clear();
}
// Routine to setup display dynamically:
void configDisplay(int contrast, boolean cur, boolean blinking, boolean
onOff, boolean leftRight) {
// contrast: Adjust display contrast level (range: 0 (maximum
contrast) - 100 (minimum contrast). Optimal value: 50).
// cur: Boolean value to activate (_) or deactivate underscore in the
current position of display.
// blinking: Boolean value to activate or deactivate blinking in the
current position of display.
166
// onOff: Boolean value to activate or deactivate entire display.
// leftRight: Boolean value to setup display to be written from left
to write (TRUE) or vice versa (FALSE).
// Contrast:
if (contrast < 0) {
contrast = 0;
}
if (contrast > 100) {
contrast = 100;
}
analogWrite(4, contrast + 80);
// Cursor:
if (cur) {
lcd.cursor();
} else {
lcd.noCursor();
}
// Cursor blinking:
if (blinking) {
lcd.blink();
} else {
lcd.noBlink();
}
// Display ON/OFF:
if (onOff) {
lcd.display();
} else {
lcd.noDisplay();
}
// Writing direction:
if (leftRight) {
lcd.leftToRight();
} else {
lcd.rightToLeft();
}
}
// Routine to initialize display using Arduino library:
void initDisplay() {
// Special characters that can be used:
byte ceTrencadaCapital[8] = {
0b01110,
0b10001,
0b10000,
0b10010,
0b10101,
0b01110,
0b10000,
0b00000,
}; // Capital catalan broken C (Ç). It can be written to display using
char literal 0x00.
byte ceTrencada[8] = {
0b00000,
0b01110,
0b10000,
167
0b10010,
0b10101,
0b01110,
0b10000,
0b00000,
}; // Catalan broken C (ç). It can be written to display using char
literal 0x01.
byte euro[8] = {
0b00000,
0b00111,
0b01000,
0b11110,
0b11110,
0b01000,
0b00111,
0b00000,
}; // Euro (€). It can be written to display using char literal 0x02.
byte smiley[8] = {
0b00000,
0b10001,
0b00000,
0b00000,
0b10001,
0b01110,
0b00000,
0b00000,
}; // Smille. It can be written to display using char literal 0x03.
byte antiSmiley[8] = {
0b00000,
0b10001,
0b00000,
0b00000,
0b01110,
0b10001,
0b00000,
0b00000,
}; // No smille. It can be written to display using char literal 0x04.
byte cor[8] = {
0b00000,
0b00000,
0b01010,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000,
}; // Heart. It can be written to display using char literal 0x05.
byte gamma[8] = {
0b10001,
0b01010,
0b00100,
0b01010,
0b10001,
0b10001,
0b01110,
0b00000,
}; // Gamma greek letter. It can be written to display using char
literal 0x06.
byte lambda[8] = {
0b00000,
168
0b10000,
0b01000,
0b00100,
0b00110,
0b01001,
0b10000,
0b00000,
}; // Lambda greek letter. It can be written to display using char
literal 0x07.
// Special characters initialization:
lcd.createChar(0, ceTrencadaCapital);
lcd.createChar(1, ceTrencada);
lcd.createChar(2, euro);
lcd.createChar(3, smiley);
lcd.createChar(4, antiSmiley);
lcd.createChar(5, cor);
lcd.createChar(6, gamma);
lcd.createChar(7, lambda);
// Display init:
lcd.begin(16, 2); // 2 rows x 16 columns display initialization.
clearDisplay();
}
// Routine to write any character or text to display or simply move
cursor (this routine does not check whether text fits or not entire
display):
void writeDisplay(int columna, int fila, const char *text, int mida) {
// columna: Value from 0 to 15 to store row where 1st text character
must be displayed.
// fila: Value from 0 to 1 to store column where 1st text character
must be displayed.
// text: Text to be written.
// mida: Characters number to represent.
// Cursor position:
lcd.setCursor(columna, fila);
// To write any text, mida value must be greater than 0. If not, this
routine will only move cursor position:
if (mida > 0) {
// Display write procedure:
for (int i = 0; i < mida; i++) {
lcd.write(text[i]);
}
}
}
//-------------- LEDS ------------------------------------------------//
// Function to turn ON/OFF any of the I-Droid02 LEDs:
void LEDs(int ledTaronja, boolean base, boolean orellaEsq, boolean
orellaDreta, boolean vermellEsq, boolean grocEsq, boolean verdEsq,
boolean vermellDret, boolean grocDret, boolean verdDret) {
// ledTaronja: int value (between 0 and 255) to turn ON orange LED
using PWM feature.
// The rest of LEDs can be turned ON by simply setting TRUE the
corresponding boolean value that represents it, or FALSE to turn it OFF.
// Head orange LED:
169
analogWrite(13, ledTaronja);
// Base LEDs:
PORTC = (PORTC & 0xEF) | (base << 4);
// Ears and eyes LEDs (these last ones are low active):
PORTL = (PORTL & 0xF0) | (orellaEsq << 3) | (orellaDreta << 2) |
((~vermellEsq & 0x01) << 1) | (~grocEsq & 0x01); // Ears LEDs and left
eye red and yellow LEDs.
PORTB = (PORTB & 0xF0) | ((~verdEsq & 0x01) << 3) | ((~vermellDret &
0x01) << 2) | ((~grocDret & 0x01) << 1) | (~verdDret & 0x01); // Left
eye green LED and all right eye LEDs.
}
//-------------- MICROPHONES -----------------------------------------//
// Interrupt Service Routine for Timer5 compare match A interruption
(This routine lasts 50 us):
ISR(TIMER5_COMPA_vect) {
uint8_t currentSample = analogRead(A15) * 256.0 / 1024.0; // Dynamic
compression from 10-bit ADC to 8-bit variable.
Serial2.write(currentSample); // Send uint8_t value via GPIO UART.
}
// Function to detect the procedence of an impulsive noise (like a clap)
by using the on-board head microphones:
void soundDirectionDetector() {
// During sound direction detection, back head microphone sampling
must be disabled in order to free resources:
cbi(TIMSK5, OCIE5A); // Disable interruptions on Timer5 compare match
A event.
// TBD.
}
//-------------- MOTORS ---------------------------------------------//
// Routine to check or change current movement set:
void checkMovement() {
struct WHEELS wheelsData; // Wheels direction and speed to be set
according to the movement.
unsigned long temporalMillis; // Variable to store temporally a
millis() value.
boolean phototransistorValue; // Value to store readings for each
phototransistor.
int i;
// Check if reset movement process has ended (executed when an abrupt
change of direction is requested):
if ((resettingMovement) && (motorsCurrentSpeed[6] == 0) &&
(motorsCurrentSpeed[7] == 0)) {
resettingMovement = false;
// Current movement vector is STOP:
currentAxisPoint.directionFlag = 0x00;
currentAxisPoint.x = 0;
currentAxisPoint.y = 0;
currentAxisPoint.distanceAngle = 0;
}
// Check movement vector change:
170
if ((!resettingMovement) && ((currentAxisPoint.directionFlag !=
nextAxisPoint.directionFlag) || (currentAxisPoint.x != nextAxisPoint.x)
|| (currentAxisPoint.y != nextAxisPoint.y) ||
(currentAxisPoint.distanceAngle != nextAxisPoint.distanceAngle))) {
// A change has been produced.
wheelsData = movementTranslation(nextAxisPoint);
// Set initial wheels direction and speed (if it is stopped):
if (currentAxisPoint.directionFlag == 0x00) {
// Set wheels direction:
if (wheelsData.leftWheelDirection) {
cbi(PORTD, PORTD7); // Left wheel forward.
} else {
sbi(PORTD, PORTD7); // Left wheel backward.
}
if (wheelsData.rightWheelDirection) {
cbi(PORTG, PORTG1); // Right wheel forward.
} else {
sbi(PORTG, PORTG1); // Right wheel backward.
}
// Set wheels speed:
analogWrite(11, LEFT_WHEEL_MIN_PWM);
motorsCurrentSpeed[6] = LEFT_WHEEL_MIN_PWM;
analogWrite(12, RIGHT_WHEEL_MIN_PWM);
motorsCurrentSpeed[7] = RIGHT_WHEEL_MIN_PWM;
}
// Set wheels speed:
motorChangeSpeed(11, wheelsData.leftWheelPWM);
motorChangeSpeed(12, wheelsData.rightWheelPWM);
// Set current axis point:
currentAxisPoint.directionFlag = nextAxisPoint.directionFlag;
currentAxisPoint.x = nextAxisPoint.x;
currentAxisPoint.y = nextAxisPoint.y;
currentAxisPoint.distanceAngle = nextAxisPoint.distanceAngle;
}
// Check wheels current position:
if (motorsCurrentSpeed[6] != 0) {
phototransistorValue = PINC & 0x01;
if ((phototransistorValue == 0) &&
(leftWheelEncoderPreviousReadings[0] == 0) &&
(leftWheelEncoderPreviousReadings[1] == 0) &&
(leftWheelEncoderPreviousReadings[2] == 0) &&
(leftWheelEncoderPreviousReadings[3] == 0) &&
(leftWheelEncoderPreviousReadings[4] == 0) &&
(leftWheelEncoderPreviousReadings[5] == 0) &&
(leftWheelEncoderPreviousReadings[6] == 0) &&
(leftWheelEncoderPreviousReadings[7] == 0) &&
(leftWheelEncoderPreviousReadings[8] == 0) &&
(leftWheelEncoderPreviousReadings[9] == 1)) {
// Phototransistor just detected the start of a new hole (after 10
readings). The counter must be incremented:
leftWheelPositionCounter++;
// Compute left wheel speed in km/h:
temporalMillis = millis();
171
leftWheelTimeDifference = temporalMillis lastMillisLeftWheelHole;
leftWheelSpeed = (WHEELS_PERIMETER_INC * 36.0) /
leftWheelTimeDifference;
lastMillisLeftWheelHole = temporalMillis;
// STOP condition:
if ((currentAxisPoint.distanceAngle == leftWheelPositionCounter)
&& ((currentAxisPoint.directionFlag == 0x69) ||
(currentAxisPoint.directionFlag == 0x09) ||
(currentAxisPoint.directionFlag == 0x99) ||
(currentAxisPoint.directionFlag == 0x90))) {
motorSetNewMovement(0x00, 0, 0, -1);
}
}
// Update encoder's readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
leftWheelEncoderPreviousReadings[i] =
leftWheelEncoderPreviousReadings[i - 1];
}
leftWheelEncoderPreviousReadings[0] = phototransistorValue;
}
if (motorsCurrentSpeed[7] != 0) {
phototransistorValue = (PING & 0x04) >> 2;
if ((phototransistorValue == 0) &&
(rightWheelEncoderPreviousReadings[0] == 0) &&
(rightWheelEncoderPreviousReadings[1] == 0) &&
(rightWheelEncoderPreviousReadings[2] == 0) &&
(rightWheelEncoderPreviousReadings[3] == 0) &&
(rightWheelEncoderPreviousReadings[4] == 0) &&
(rightWheelEncoderPreviousReadings[5] == 0) &&
(rightWheelEncoderPreviousReadings[6] == 0) &&
(rightWheelEncoderPreviousReadings[7] == 0) &&
(rightWheelEncoderPreviousReadings[8] == 0) &&
(rightWheelEncoderPreviousReadings[9] == 1)) {
// Phototransistor just detected the start of a new hole (after 10
readings). The counter must be incremented:
rightWheelPositionCounter++;
// Compute right wheel speed in km/h:
temporalMillis = millis();
rightWheelTimeDifference = temporalMillis lastMillisRightWheelHole;
rightWheelSpeed = (WHEELS_PERIMETER_INC * 36.0) /
rightWheelTimeDifference;
lastMillisRightWheelHole = temporalMillis;
// STOP condition:
if ((currentAxisPoint.distanceAngle == rightWheelPositionCounter)
&& ((currentAxisPoint.directionFlag == 0x96) ||
(currentAxisPoint.directionFlag == 0x06) ||
(currentAxisPoint.directionFlag == 0x66) ||
(currentAxisPoint.directionFlag == 0x60))) {
motorSetNewMovement(0x00, 0, 0, -1);
}
}
172
// Update encoder's readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
rightWheelEncoderPreviousReadings[i] =
rightWheelEncoderPreviousReadings[i - 1];
}
rightWheelEncoderPreviousReadings[0] = phototransistorValue;
}
}
// Routine to check that both wheels are actually moving at the same
speed:
void checkRectilinearity() {
int difference; // The difference between left and right wheel mean
differences.
int newSpeed; // New speed that the corresponding wheel will have at
the end of the routine.
// Only applicable if the current movement set is a rectilinear or
spinning trajectory and both wheels have reached its target speed:
if (((currentAxisPoint.directionFlag == 0x06) ||
(currentAxisPoint.directionFlag == 0x09) ||
(currentAxisPoint.directionFlag == 0x60) ||
(currentAxisPoint.directionFlag == 0x90)) && (motorsCurrentSpeed[6] ==
motorsFinalSpeed[6]) && (motorsCurrentSpeed[7] == motorsFinalSpeed[7]))
{
// Compute the difference between means:
difference = leftWheelTimeDifference - rightWheelTimeDifference;
// Decide what to do according to difference value:
if (difference > 0) {
// Left wheel is moving lower than right wheel. If possible,
increase left wheel or reduce right wheel.
if (motorsCurrentSpeed[6] < originalLeftWheelFinalSpeed) {
// Can increase left wheel speed.
newSpeed = motorsFinalSpeed[6] + 1;
analogWrite(11, newSpeed); // Change it immediately.
motorsCurrentSpeed[6] = newSpeed;
motorsFinalSpeed[6] = newSpeed;
} else {
// Cannot increase left wheel speed. Reduce right wheel speed
(if possible):
if (motorsCurrentSpeed[7] > RIGHT_WHEEL_MIN_PWM) {
newSpeed = motorsFinalSpeed[7] - 1;
analogWrite(12, newSpeed); // Change it immediately.
motorsCurrentSpeed[7] = newSpeed;
motorsFinalSpeed[7] = newSpeed;
}
}
}
if (difference < 0) {
// Right wheel is moving lower than left wheel. If possible,
increase right wheel or reduce left wheel.
if (motorsCurrentSpeed[7] < originalRightWheelFinalSpeed) {
// Can increase right wheel speed.
newSpeed = motorsFinalSpeed[7] + 1;
analogWrite(12, newSpeed); // Change it immediately.
motorsCurrentSpeed[7] = newSpeed;
motorsFinalSpeed[7] = newSpeed;
} else {
173
// Cannot increase right wheel speed. Reduce left wheel speed
(if possible):
if (motorsCurrentSpeed[6] > LEFT_WHEEL_MIN_PWM) {
newSpeed = motorsFinalSpeed[6] - 1;
analogWrite(11, newSpeed); // Change it immediately.
motorsCurrentSpeed[6] = newSpeed;
motorsFinalSpeed[6] = newSpeed;
}
}
}
}
}
// Function to convert speed value (0 - 10) to PWM speed according to
each motor:
int getPWMSpeed(int motorNumber, float speedFloat) {
// motorNumber: The pin where the motor is connected (5 - 12).
// speedFloat: Tbe value to be converted.
// Returs: PWM speed.
float pwmSpeed;
// Switch according to each motor:
switch (motorNumber) {
case 5:
// Head tilt. PWM speed valid values: HEAD_TILT_MIN_PWM - 255. 0 HEAD_TILT_MIN_PWM-1 = STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = HEAD_TILT_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (2.05357142857142 * pow(speedFloat, 2) 6.93452380952374 * speedFloat + 125.863095238096);
}
break;
case 6:
// Head pan. PWM valid values: HEAD_PAN_MIN_PWM - 255. 0 HEAD_PAN_MIN_PWM-1 = STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = HEAD_PAN_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (2.35119047619048 * pow(speedFloat, 2) 9.61309523809518 * speedFloat + 110.029761904762);
}
break;
case 7:
// Left arm. PWM valid values: LEFT_ARM_MIN_PWM - 255. 0 LEFT_ARM_MIN_PWM-1 = STOP.
174
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = LEFT_ARM_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (1.60714285714286 * pow(speedFloat, 2) 0.773809523809518 * speedFloat + 120.952380952381);
}
break;
case 8:
// Right arm. PWM valid values: RIGHT_ARM_MIN_PWM - 255. 0 RIGHT_ARM_MIN_PWM-1 = STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = RIGHT_ARM_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (1.60714285714286 * pow(speedFloat, 2) 0.773809523809518 * speedFloat + 120.952380952381);
}
break;
case 9:
// Hand. PWM valid values: HAND_MIN_PWM - 255. 0 - HAND_MIN_PWM-1
= STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = HAND_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (0.922619047619037 * pow(speedFloat, 2) +
8.8392857142857 * speedFloat + 74.2559523809527);
}
break;
case 10:
// Hip. PWM valid values: HIP_MIN_PWM - 255. 0 - HIP_MIN_PWM-1 =
STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = HIP_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
175
} else {
pwmSpeed = (2.20238095238096 * pow(speedFloat, 2) 11.3690476190477 * speedFloat + 151.845238095239);
}
break;
case 11:
// Left wheel PWM valid values: LEFT_WHEEL_MIN_PWM - 255. 0 LEFT_WHEEL_MIN_PWM-1 = STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = LEFT_WHEEL_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (0.928030303030312 * pow(speedFloat, 2) +
9.67045454545439 * speedFloat + 67.0833333333324);
}
break;
case 12:
// Right wheel PWM valid values: RIGHT_WHEEL_MIN_PWM - 255. 0 RIGHT_WHEEL_MIN_PWM-1 = STOP.
if (speedFloat == 0) {
// 0 speed:
pwmSpeed = 0;
} else if (speedFloat == 1) {
// Minimum speed:
pwmSpeed = RIGHT_WHEEL_MIN_PWM;
} else if (speedFloat == 10) {
// Maximum speed:
pwmSpeed = 255;
} else {
pwmSpeed = (0.928030303030312 * pow(speedFloat, 2) +
9.67045454545439 * speedFloat + 67.0833333333324);
}
break;
default:
pwmSpeed = 0;
}
return (int) pwmSpeed;
}
// Routine to check if any limited motor is moving:
boolean isAnyLimitedMotorInMovement() {
return ((motorsCurrentSpeed[0] != 0) || (motorsCurrentSpeed[1] != 0)
|| (motorsCurrentSpeed[2] != 0) || (motorsCurrentSpeed[3] != 0) ||
(motorsCurrentSpeed[4] != 0) || (motorsCurrentSpeed[5] != 0) ||
(motorsCurrentSpeed[6] != 0));
}
// Function to set the new speed of any motor:
void motorChangeSpeed(int motorNumber, int newFinalSpeed) {
// motorNumber: The pin where the motor to change its speed is
connected. Valid values 5-12.
// newFinalSpeed: The new PWM value that selected motor much reach,
higher or lower than current one.
176
// Check that motor number is in movement and inside bounds (a wheel
motor can bypass stop condition since it can be stopped during a certain
movement):
if (((motorNumber >= 5) && (motorNumber <= 10) &&
(motorsCurrentSpeed[motorNumber - 5] != 0)) || (motorNumber == 11) ||
(motorNumber == 12)) {
// Set new speed of the motor:
motorsFinalSpeed[motorNumber - 5] = newFinalSpeed;
}
}
// Function to check the position of all limited motors and stop the
necessary ones:
void motorsCheckPosition() {
int currentADCValue; // Variable used to store hand motor measured
current.
boolean phototransistorValue; // Value to store readings for each
phototransistor.
int i;
if (motorsCurrentSpeed[0] != 0) {
if (scheduler(&lastMillisHeadTiltMotorStarted, headTiltTimeToStop))
{
// Time is over so head tilt motor has to stop.
motorChangeSpeed(5, HEAD_TILT_MIN_PWM - 1);
limitedMotorsCurrentPosition[0] = limitedMotorsFinalPosition[0];
motorsNotifyPosition(5, limitedMotorsCurrentPosition[0]);
}
} // To check stop condition only has sense if motor has reached its
maximum defined speed.
if (motorsCurrentSpeed[1] != 0) {
phototransistorValue = (PINA & 0x20) >> 5;
if (((phototransistorValue == 0) &&
(headPanEncoderPreviousReadings[0] == 0) &&
(headPanEncoderPreviousReadings[1] == 0) &&
(headPanEncoderPreviousReadings[2] == 0) &&
(headPanEncoderPreviousReadings[3] == 0) &&
(headPanEncoderPreviousReadings[4] == 0) &&
(headPanEncoderPreviousReadings[5] == 0) &&
(headPanEncoderPreviousReadings[6] == 0) &&
(headPanEncoderPreviousReadings[7] == 0) &&
(headPanEncoderPreviousReadings[8] == 0) &&
(headPanEncoderPreviousReadings[9] == 1)) || ((phototransistorValue ==
1) && (headPanEncoderPreviousReadings[0] == 1) &&
(headPanEncoderPreviousReadings[1] == 1) &&
(headPanEncoderPreviousReadings[2] == 1) &&
(headPanEncoderPreviousReadings[3] == 1) &&
(headPanEncoderPreviousReadings[4] == 1) &&
(headPanEncoderPreviousReadings[5] == 1) &&
(headPanEncoderPreviousReadings[6] == 1) &&
(headPanEncoderPreviousReadings[7] == 1) &&
(headPanEncoderPreviousReadings[8] == 1) &&
(headPanEncoderPreviousReadings[9] == 0))) {
// Phototransistor just detected the start of a new position
(after 10 readings). Depending of the motor's direction, the counter
must be incremented or decremented:
if (limitedMotorsDirection[1]) {
limitedMotorsCurrentPosition[1]++;
} else {
177
limitedMotorsCurrentPosition[1]--;
}
// Stop condition:
if (limitedMotorsCurrentPosition[1] ==
limitedMotorsFinalPosition[1]) {
motorChangeSpeed(6, HEAD_PAN_MIN_PWM - 1);
}
motorsNotifyPosition(6, limitedMotorsCurrentPosition[1]);
// Reset mode:
if ((limitedMotorsCurrentPosition[1] == 10) && resetMode) {
motorChangeSpeed(10, HEAD_PAN_MIN_PWM - 1);
resetMode = false;
}
}
// Update encoder's readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
headPanEncoderPreviousReadings[i] =
headPanEncoderPreviousReadings[i - 1];
}
headPanEncoderPreviousReadings[0] = phototransistorValue;
}
if (leftArmCheckEncoder) {
phototransistorValue = (PINA & 0x80) >> 7;
if (((phototransistorValue == 0) &&
(leftArmEncoderPreviousReadings[0] == 0) &&
(leftArmEncoderPreviousReadings[1] == 0) &&
(leftArmEncoderPreviousReadings[2] == 0) &&
(leftArmEncoderPreviousReadings[3] == 0) &&
(leftArmEncoderPreviousReadings[4] == 0) &&
(leftArmEncoderPreviousReadings[5] == 0) &&
(leftArmEncoderPreviousReadings[6] == 0) &&
(leftArmEncoderPreviousReadings[7] == 0) &&
(leftArmEncoderPreviousReadings[8] == 0) &&
(leftArmEncoderPreviousReadings[9] == 1)) || ((phototransistorValue ==
1) && (leftArmEncoderPreviousReadings[0] == 1) &&
(leftArmEncoderPreviousReadings[1] == 1) &&
(leftArmEncoderPreviousReadings[2] == 1) &&
(leftArmEncoderPreviousReadings[3] == 1) &&
(leftArmEncoderPreviousReadings[4] == 1) &&
(leftArmEncoderPreviousReadings[5] == 1) &&
(leftArmEncoderPreviousReadings[6] == 1) &&
(leftArmEncoderPreviousReadings[7] == 1) &&
(leftArmEncoderPreviousReadings[8] == 1) &&
(leftArmEncoderPreviousReadings[9] == 0))) {
// Phototransistor just detected the end of a new position.
Depending of the motor's direction, the counter must be incremented or
decremented:
if (limitedMotorsDirection[2]) {
limitedMotorsCurrentPosition[2]++;
} else {
limitedMotorsCurrentPosition[2]--;
}
// Stop condition:
if (limitedMotorsCurrentPosition[2] ==
limitedMotorsFinalPosition[2]) {
178
motorChangeSpeed(7, LEFT_ARM_MIN_PWM - 1);
lastMillisCorrectionTime[0] = millis();
}
motorsNotifyPosition(7, limitedMotorsCurrentPosition[2]);
}
// Update encoder's readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
leftArmEncoderPreviousReadings[i] =
leftArmEncoderPreviousReadings[i - 1];
}
leftArmEncoderPreviousReadings[0] = phototransistorValue;
}
if (rightArmCheckEncoder) {
phototransistorValue = (PINC & 0x40) >> 6;
if (((phototransistorValue == 0) &&
(rightArmEncoderPreviousReadings[0] == 0) &&
(rightArmEncoderPreviousReadings[1] == 0) &&
(rightArmEncoderPreviousReadings[2] == 0) &&
(rightArmEncoderPreviousReadings[3] == 0) &&
(rightArmEncoderPreviousReadings[4] == 0) &&
(rightArmEncoderPreviousReadings[5] == 0) &&
(rightArmEncoderPreviousReadings[6] == 0) &&
(rightArmEncoderPreviousReadings[7] == 0) &&
(rightArmEncoderPreviousReadings[8] == 0) &&
(rightArmEncoderPreviousReadings[9] == 1)) || ((phototransistorValue ==
1) && (rightArmEncoderPreviousReadings[0] == 1) &&
(rightArmEncoderPreviousReadings[1] == 1) &&
(rightArmEncoderPreviousReadings[2] == 1) &&
(rightArmEncoderPreviousReadings[3] == 1) &&
(rightArmEncoderPreviousReadings[4] == 1) &&
(rightArmEncoderPreviousReadings[5] == 1) &&
(rightArmEncoderPreviousReadings[6] == 1) &&
(rightArmEncoderPreviousReadings[7] == 1) &&
(rightArmEncoderPreviousReadings[8] == 1) &&
(rightArmEncoderPreviousReadings[9] == 0))) {
// Phototransistor just detected the end of a new position.
Depending of the motor's direction, the counter must be incremented or
decremented:
if (limitedMotorsDirection[3]) {
limitedMotorsCurrentPosition[3]++;
} else {
limitedMotorsCurrentPosition[3]--;
}
// Stop condition:
if (limitedMotorsCurrentPosition[3] ==
limitedMotorsFinalPosition[3]) {
motorChangeSpeed(8, RIGHT_ARM_MIN_PWM - 1);
lastMillisCorrectionTime[1] = millis();
}
motorsNotifyPosition(8, limitedMotorsCurrentPosition[3]);
}
// Update encoder's readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
rightArmEncoderPreviousReadings[i] =
rightArmEncoderPreviousReadings[i - 1];
}
179
rightArmEncoderPreviousReadings[0] = phototransistorValue;
}
if (motorsCurrentSpeed[4] != 0) {
currentADCValue = analogRead(A8);
if ((currentADCValue > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[0] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[1] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[2] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[3] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[4] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[5] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[6] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[7] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[8] > handDetectorThreshold) &&
(handEndDetectorPreviousReadings[9] > handDetectorThreshold)) {
// Hand motor has reached its end of movement.
motorChangeSpeed(9, HAND_MIN_PWM - 1);
limitedMotorsCurrentPosition[4] = limitedMotorsFinalPosition[4];
motorsNotifyPosition(9, limitedMotorsCurrentPosition[4]);
}
// Update sensor readings:
for (i = ENCODER_PREVIOUS_READINGS - 1; i >= 1 ; i--) {
handEndDetectorPreviousReadings[i] =
handEndDetectorPreviousReadings[i - 1];
}
handEndDetectorPreviousReadings[0] = currentADCValue;
} // To check the current through its motor only has sense when it is
turned ON.
if (motorsCurrentSpeed[5] != 0) {
if (scheduler(&lastMillisHipMotorStarted, hipTimeToStop)) {
// Time is over so hip's motor has to stop.
motorChangeSpeed(10, HIP_MIN_PWM - 1);
limitedMotorsCurrentPosition[5] = limitedMotorsFinalPosition[5];
motorsNotifyPosition(10, limitedMotorsCurrentPosition[5]);
}
} // To check motor's position only has sense when it is turned ON.
}
// This routine checks whether a motor needs to change its speed and
performs this change smoothly:
void motorsCheckSpeed() {
// Check one by one all 8 motors current speed and new possible value
established before, when scheduler authorizes it:
if (motorsFinalSpeed[0] != motorsCurrentSpeed[0]) {
// Head tilt motor needs to change its speed.
if (motorsFinalSpeed[0] > motorsCurrentSpeed[0]) {
motorsCurrentSpeed[0]++; // Increase speed.
if (motorsCurrentSpeed[0] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[0] = 255;
motorsFinalSpeed[0] = 255;
}
} else {
motorsCurrentSpeed[0]--; // Decrease speed.
if (motorsCurrentSpeed[0] < HEAD_TILT_MIN_PWM) {
180
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[0] = 0; // STOP.
motorsFinalSpeed[0] = 0;
cbi(PORTA, PORTA4); // Remove voltage from associated relay.
limitedMotorsInMovement = limitedMotorsInMovement & ~1;
updateLimitedMotorsInMovement();
}
}
analogWrite(5, motorsCurrentSpeed[0]);
}
if (motorsFinalSpeed[1] != motorsCurrentSpeed[1]) {
// Head pan motor needs to change its speed.
if (motorsFinalSpeed[1] > motorsCurrentSpeed[1]) {
motorsCurrentSpeed[1]++; // Increase speed.
if (motorsCurrentSpeed[1] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[1] = 255;
motorsFinalSpeed[1] = 255;
}
} else {
motorsCurrentSpeed[1]--; // Decrease speed.
if (motorsCurrentSpeed[1] < HEAD_PAN_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[1] = 0; // STOP.
motorsFinalSpeed[1] = 0;
cbi(PORTA, PORTA6); // Remove voltage from associated relay.
limitedMotorsInMovement = (limitedMotorsInMovement & ~(1 << 1));
updateLimitedMotorsInMovement();
}
}
analogWrite(6, motorsCurrentSpeed[1]);
}
if (motorsFinalSpeed[2] != motorsCurrentSpeed[2]) {
// Left arm motor needs to change its speed.
if (motorsFinalSpeed[2] > motorsCurrentSpeed[2]) {
motorsCurrentSpeed[2]++; // Increase speed.
if (motorsCurrentSpeed[2] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[2] = 255;
motorsFinalSpeed[2] = 255;
}
} else {
motorsCurrentSpeed[2]--; // Decrease speed.
if (motorsCurrentSpeed[2] < LEFT_ARM_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[2] = 0; // STOP.
motorsFinalSpeed[2] = 0;
cbi(PORTC, PORTC7); // Remove voltage from associated relay.
limitedMotorsInMovement = (limitedMotorsInMovement & ~(1 << 2));
updateLimitedMotorsInMovement();
}
}
analogWrite(7, motorsCurrentSpeed[2]);
181
}
if (motorsFinalSpeed[3] != motorsCurrentSpeed[3]) {
// Right arm motor needs to change its speed.
if (motorsFinalSpeed[3] > motorsCurrentSpeed[3]) {
motorsCurrentSpeed[3]++; // Increase speed.
if (motorsCurrentSpeed[3] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[3] = 255;
motorsFinalSpeed[3] = 255;
}
} else {
motorsCurrentSpeed[3]--; // Decrease speed.
if (motorsCurrentSpeed[3] < RIGHT_ARM_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[3] = 0; // STOP.
motorsFinalSpeed[3] = 0;
cbi(PORTC, PORTC5); // Remove voltage from associated relay.
limitedMotorsInMovement = (limitedMotorsInMovement & ~(1 << 3));
updateLimitedMotorsInMovement();
}
}
analogWrite(8, motorsCurrentSpeed[3]);
}
if (motorsFinalSpeed[4] != motorsCurrentSpeed[4]) {
// Hand motor needs to change its speed.
if (motorsFinalSpeed[4] > motorsCurrentSpeed[4]) {
motorsCurrentSpeed[4]++; // Increase speed.
if (motorsCurrentSpeed[4] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[4] = 255;
motorsFinalSpeed[4] = 255;
}
} else {
motorsCurrentSpeed[4]--; // Decrease speed.
if (motorsCurrentSpeed[4] < HAND_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[4] = 0; // STOP.
motorsFinalSpeed[4] = 0;
cbi(PORTC, PORTC3); // Remove voltage from associated relay.
limitedMotorsInMovement = (limitedMotorsInMovement & ~(1 << 4));
updateLimitedMotorsInMovement();
}
}
analogWrite(9, motorsCurrentSpeed[4]);
}
if (motorsFinalSpeed[5] != motorsCurrentSpeed[5]) {
// Hip motor needs to change its speed.
if (motorsFinalSpeed[5] > motorsCurrentSpeed[5]) {
motorsCurrentSpeed[5]++; // Increase speed.
if (motorsCurrentSpeed[5] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[5] = 255;
182
motorsFinalSpeed[5] = 255;
}
} else {
motorsCurrentSpeed[5]--; // Decrease speed.
if (motorsCurrentSpeed[5] < HIP_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[5] = 0; // STOP.
motorsFinalSpeed[5] = 0;
cbi(PORTC, PORTC1); // Remove voltage from associated relay.
limitedMotorsInMovement = (limitedMotorsInMovement & ~(1 << 5));
updateLimitedMotorsInMovement();
}
}
analogWrite(10, motorsCurrentSpeed[5]);
}
if ((motorsFinalSpeed[6] != motorsCurrentSpeed[6]) &&
schedulerMicros(&lastMicrosLeftWheelChangeSpeed, 800)) {
// Left wheel motor needs to change its speed.
if (motorsFinalSpeed[6] > motorsCurrentSpeed[6]) {
motorsCurrentSpeed[6]++; // Increase speed.
if (motorsCurrentSpeed[6] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[6] = 255;
motorsFinalSpeed[6] = 255;
}
} else {
motorsCurrentSpeed[6]--; // Decrease speed.
if (motorsCurrentSpeed[6] < LEFT_WHEEL_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[6] = 0; // STOP.
motorsFinalSpeed[6] = 0;
cbi(PORTD, PORTD7); // Remove voltage from associated relay.
leftWheelSpeed = 0.0; // Set wheel speed to 0 km/h.
}
}
analogWrite(11, motorsCurrentSpeed[6]);
}
if ((motorsFinalSpeed[7] != motorsCurrentSpeed[7]) &&
schedulerMicros(&lastMicrosRightWheelChangeSpeed, 800)) {
// Right wheel motor needs to change its speed.
if (motorsFinalSpeed[7] > motorsCurrentSpeed[7]) {
motorsCurrentSpeed[7]++; // Increase speed.
if (motorsCurrentSpeed[7] > 255) {
// This motor has reached its upper limit and it cannot increase
its speed.
motorsCurrentSpeed[7] = 255;
motorsFinalSpeed[7] = 255;
}
} else {
motorsCurrentSpeed[7]--; // Decrease speed.
if (motorsCurrentSpeed[7] < RIGHT_WHEEL_MIN_PWM) {
// This motor has reached its lower limit and it must be
switched off. Voltage over the associated relay also must be removed:
motorsCurrentSpeed[7] = 0; // STOP.
motorsFinalSpeed[7] = 0;
183
cbi(PORTG, PORTG1); // Remove voltage from associated relay.
rightWheelSpeed = 0.0; // Set wheel speed to 0 km/h.
}
}
analogWrite(12, motorsCurrentSpeed[7]);
}
}
// Function to notify the new position of a motor controlled with an
encoder:
void motorsNotifyPosition(byte motorNumber, byte encoder) {
// motorNumber: The motor that is wanted to notify its position.
// encoder: The new position value.
Serial.write(COMMAND_MOTORS);
Serial.write(COMMAND_MOTORS_POSITION);
Serial.write(motorNumber);
Serial.write(encoder);
// Update EEPROM value:
writeLimitedMotorCurrentPosition(motorNumber);
}
// Function to notify that last movement set has reached the target
distance/angle:
void motorsNotifySetMovementTargetReached() {
Serial.write(COMMAND_MOTORS);
Serial.write(COMMAND_MOTORS_SET_MOVEMENT_TARGET_REACHED);
}
// This routine checks whether a stopped motor is placed in a wrong
position from target and moves it to the correct one:
void motorsReposition() {
int targetPosition; // Temporal variable to store real target
position.
// Reposition code for arms motors:
if ((!leftArmRepositionStarted) && (motorsCurrentSpeed[2] == 0) &&
(limitedMotorsCurrentPosition[2] != limitedMotorsFinalPosition[2]) &&
scheduler(&lastMillisCorrectionTime[0], 650)) {
targetPosition = limitedMotorsFinalPosition[2]; // Save current
value.
limitedMotorsFinalPosition[2] = limitedMotorsCurrentPosition[2]; //
Change position artificially.
motorStart(7, targetPosition, 1);
leftArmRepositionStarted = true;
}
if (leftArmRepositionStarted && (limitedMotorsCurrentPosition[2] ==
0)) {
// Left arm motor reposition has ended.
leftArmRepositionStarted = false;
leftArmCheckEncoder = false;
}
if ((!rightArmRepositionStarted) && (motorsCurrentSpeed[3] == 0) &&
(limitedMotorsCurrentPosition[3] != limitedMotorsFinalPosition[3]) &&
scheduler(&lastMillisCorrectionTime[1], 650)) {
targetPosition = limitedMotorsFinalPosition[3]; // Save current
value.
limitedMotorsFinalPosition[3] = limitedMotorsCurrentPosition[3]; //
Change position artificially.
184
motorStart(8, targetPosition, 1);
rightArmRepositionStarted = true;
}
if (rightArmRepositionStarted && (limitedMotorsCurrentPosition[3] ==
0)) {
// Right arm motor reposition has ended.
rightArmRepositionStarted = false;
rightArmCheckEncoder = false;
}
// Check that current position for motors with encoder is not outside
bounds:
if (limitedMotorsCurrentPosition[1] < 1) {
// Head pan position below bounds.
limitedMotorsCurrentPosition[1] = 1;
}
if (limitedMotorsCurrentPosition[1] > HEAD_PAN_MAX_VALUE) {
// Head pan position above bounds.
limitedMotorsCurrentPosition[1] = HEAD_PAN_MAX_VALUE;
}
if (limitedMotorsCurrentPosition[2] < 1) {
// Left arm position below bounds.
limitedMotorsCurrentPosition[2] = 1;
}
if (limitedMotorsCurrentPosition[2] > ARMS_MAX_VALUE) {
// Left arm position above bounds.
limitedMotorsCurrentPosition[2] = ARMS_MAX_VALUE;
}
if (limitedMotorsCurrentPosition[3] < 1) {
// Right arm position below bounds.
limitedMotorsCurrentPosition[3] = 1;
}
if (limitedMotorsCurrentPosition[3] > ARMS_MAX_VALUE) {
// Right arm position above bounds.
limitedMotorsCurrentPosition[3] = ARMS_MAX_VALUE;
}
}
// Routine to reset all necessary motors one by one:
void motorsReset() {
// Check if head tilt motor needs to reset:
if (motorsNeedToReset[0] && !isAnyLimitedMotorInMovement()) {
motorStart(5, NO_ENCODER_MAX_VALUE, 10);
motorsNeedToReset[0] = false;
}
// Check if head pan motor needs to reset:
if (motorsNeedToReset[1] && !isAnyLimitedMotorInMovement()) {
motorStart(6, 10, 1);
motorsNeedToReset[1] = false;
}
// Check if left arm motor needs to reset:
if (motorsNeedToReset[2] && !isAnyLimitedMotorInMovement()) {
motorStart(7, 1, 10);
motorsNeedToReset[2] = false;
}
185
// Check if right arm motor needs to reset:
if (motorsNeedToReset[3] && !isAnyLimitedMotorInMovement()) {
motorStart(8, 1, 10);
motorsNeedToReset[3] = false;
}
// Check if hand motor needs to reset:
if (motorsNeedToReset[4] && !isAnyLimitedMotorInMovement()) {
motorStart(9, NO_ENCODER_MAX_VALUE, 10);
motorsNeedToReset[4] = false;
}
// Check if hip motor needs to reset:
if (motorsNeedToReset[5] && !isAnyLimitedMotorInMovement()) {
motorStart(10, 1, 10);
motorsNeedToReset[5] = false;
}
// Check if reset motors process has ended:
if (motorsNeedToReset[6] && !isAnyLimitedMotorInMovement()) {
// Show shutdown message:
clearDisplay();
writeDisplay(0, 0, "RESET MOTORS OK", 15);
writeDisplay(0, 1, "NOW can shutdown", 16);
motorsNeedToReset[6] = false;
}
}
// Function to reset encoders count (used in the calibration process):
void motorsResetEncoderCount() {
int i;
limitedMotorsCurrentPosition[0] = 2;
limitedMotorsFinalPosition[0] = 2;
writeLimitedMotorCurrentPosition(5);
limitedMotorsCurrentPosition[1] = 10;
limitedMotorsFinalPosition[1] = 10;
writeLimitedMotorCurrentPosition(6);
limitedMotorsCurrentPosition[2] = 1;
limitedMotorsFinalPosition[2] = 1;
writeLimitedMotorCurrentPosition(7);
limitedMotorsCurrentPosition[3] = 1;
limitedMotorsFinalPosition[3] = 1;
writeLimitedMotorCurrentPosition(8);
limitedMotorsCurrentPosition[4] = 2;
limitedMotorsFinalPosition[4] = 2;
writeLimitedMotorCurrentPosition(9);
limitedMotorsCurrentPosition[5] = 1;
limitedMotorsFinalPosition[5] = 1;
writeLimitedMotorCurrentPosition(10);
for (i = 0; i < 10; i++) {
headPanEncoderPreviousReadings[i] = 0;
leftArmEncoderPreviousReadings[i] = 1;
rightArmEncoderPreviousReadings[i] = 1;
handEndDetectorPreviousReadings[i] = 0;
leftWheelEncoderPreviousReadings[i] = PINC & 0x01;
rightWheelEncoderPreviousReadings[i] = (PING & 0x04) >> 2;
}
}
186
// Function to set a new I-Droid02 wheels movement:
void motorSetNewMovement(unsigned char newDirectionFlag, unsigned char
newX, unsigned char newY, int newDistanceAngle) {
// newDirectionFlag: Direction of the trajectory: (0x00: STOP. 0x06:
Turn left. 0x09: Turn right. 0x60: Straight forward. 0x66: Forward-Left.
0x69: Forward-Right. 0x90: Straight backward. 0x96: Backward-Left. 0x99:
Backward-Right).
// newX: New abscissa axis value.
// newY: New ordered axis value.
// newDistanceAngle: Distance to cover in the trajectory in cm. In
spinning directions, it indicates target angle in degrees. -1 means
infinite distance/angle.
// Check if new movement requires a change of direction:
if ((((newDirectionFlag == 0x06) || (newDirectionFlag == 0x09)) &&
((currentAxisPoint.directionFlag != 0x00) &&
(currentAxisPoint.directionFlag != 0x06) &&
(currentAxisPoint.directionFlag != 0x09))) || (((newDirectionFlag ==
0x60) || (newDirectionFlag == 0x90)) &&
((currentAxisPoint.directionFlag != 0x00) &&
(currentAxisPoint.directionFlag != 0x60) &&
(currentAxisPoint.directionFlag != 0x90) &&
(currentAxisPoint.directionFlag != 0x66) &&
(currentAxisPoint.directionFlag != 0x69) &&
(currentAxisPoint.directionFlag != 0x96) &&
(currentAxisPoint.directionFlag != 0x99))) || (((newDirectionFlag ==
0x66) || (newDirectionFlag == 0x69) || (newDirectionFlag == 0x96) ||
(newDirectionFlag == 0x99)) && ((currentAxisPoint.directionFlag != 0x00)
&& (currentAxisPoint.directionFlag != 0x60) &&
(currentAxisPoint.directionFlag != 0x90) &&
(currentAxisPoint.directionFlag != 0x66) &&
(currentAxisPoint.directionFlag != 0x69) &&
(currentAxisPoint.directionFlag != 0x96) &&
(currentAxisPoint.directionFlag != 0x99)))) {
resettingMovement = true;
motorChangeSpeed(11, 0);
motorChangeSpeed(12, 0);
}
// Set axis parameters:
nextAxisPoint.directionFlag = newDirectionFlag;
nextAxisPoint.x = newX;
nextAxisPoint.y = newY;
// Check if a new movement is set:
if (newDirectionFlag == 0x00) {
nextAxisPoint.distanceAngle = -1;
// Reset position counters of both wheels:
leftWheelPositionCounter = 0;
rightWheelPositionCounter = 0;
return;
}
// Set distance/angle value:
if (newDistanceAngle != -1) {
// Compute final distance/angle:
if ((newDirectionFlag == 0x06) || (newDirectionFlag == 0x09)) {
// Direction flag belongs to a spinning movement.
187
nextAxisPoint.distanceAngle = newDistanceAngle /
WHEELS_PERIMETER_INC;
} else {
// Direction flag belongs to a rectilinear movement.
nextAxisPoint.distanceAngle = newDistanceAngle / WHEELS_ANGLE_INC;
}
} else {
// Value is not set:
nextAxisPoint.distanceAngle = -1;
}
}
// Function to start any limited motor:
void motorStart(int motorNumber, int finalPosition, int finalSpeedInt) {
// motorNumber: The pin where the motor to be started is connected.
Valid values: 5-10.
// finalPosition: The final absolute position where the motor has to
be moved.
// finalSpeedInt: Speed value from 0 to 10 to be converted to PWM.
int initialSpeed = 0; // The initial PWM value of a motor.
int finalSpeed; // The PWM value that selected motor much reach.
// Convert speed:
if ((finalSpeedInt >= 0) && (finalSpeedInt <= 10)) {
// Speed value inside bounds.
finalSpeed = getPWMSpeed(motorNumber, finalSpeedInt);
} else {
// Speed value outside bounds.
finalSpeed = getPWMSpeed(motorNumber, 1); // Start motor at minimum.
}
// A motor can be started only if it is stopped.
if (motorsCurrentSpeed[motorNumber - 5] == 0) {
// Switch motor:
switch (motorNumber) {
case 5:
// Head tilt.
// Check that requested absolute position is inside motor
bounds:
if (finalPosition
finalPosition =
}
if (finalPosition
finalPosition =
}
< 1) {
1;
> NO_ENCODER_MAX_VALUE) {
NO_ENCODER_MAX_VALUE;
// Set motor direction by checking the current position and the
requested one:
if (limitedMotorsCurrentPosition[0] < finalPosition) {
cbi(PORTA, PORTA4); // Head up.
limitedMotorsDirection[0] = true;
} else if (limitedMotorsCurrentPosition[0] > finalPosition) {
sbi(PORTA, PORTA4); // Head down.
limitedMotorsDirection[0] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
}
188
// Compute head tilt time to stop (in ms):
if (finalSpeed <= HEAD_TILT_MIN_PWM) {
headTiltTimeToStop = 195;
} else if ((finalSpeed > HEAD_TILT_MIN_PWM) && (finalSpeed <=
120)) {
headTiltTimeToStop =
} else if ((finalSpeed
headTiltTimeToStop =
} else if ((finalSpeed
headTiltTimeToStop =
} else if ((finalSpeed
headTiltTimeToStop =
} else if ((finalSpeed
headTiltTimeToStop =
} else if ((finalSpeed
headTiltTimeToStop =
} else {
headTiltTimeToStop =
}
150;
> 120)
140;
> 140)
130;
> 150)
110;
> 170)
90;
> 190)
60;
&& (finalSpeed <= 140)) {
&& (finalSpeed <= 150)) {
&& (finalSpeed <= 170)) {
&& (finalSpeed <= 190)) {
&& (finalSpeed <= 235)) {
47;
initialSpeed = HEAD_TILT_MIN_PWM;
lastMillisHeadTiltMotorStarted = millis(); // Start time
reference.
break;
case 6:
// Head pan.
// Check that requested absolute position is inside motor
bounds:
if (finalPosition
finalPosition =
}
if (finalPosition
finalPosition =
}
< 1) {
1;
> HEAD_PAN_MAX_VALUE) {
HEAD_PAN_MAX_VALUE;
// Set motor direction by checking the current position and the
requested one:
if (limitedMotorsCurrentPosition[1] < finalPosition) {
cbi(PORTA, PORTA6); // Head left.
limitedMotorsDirection[1] = true;
} else if (limitedMotorsCurrentPosition[1] > finalPosition) {
sbi(PORTA, PORTA6); // Head right.
limitedMotorsDirection[1] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
}
initialSpeed = HEAD_PAN_MIN_PWM;
break;
case 7:
// Left arm.
// Check that requested absolute position is inside motor
bounds:
if (finalPosition < 1) {
finalPosition = 1;
}
if (finalPosition > ARMS_MAX_VALUE) {
189
finalPosition = ARMS_MAX_VALUE;
}
// Set motor direction by checking the current position and the
requested one:
if (limitedMotorsCurrentPosition[2] < finalPosition) {
cbi(PORTC, PORTC7); // Left arm up.
limitedMotorsDirection[2] = true;
} else if (limitedMotorsCurrentPosition[2] > finalPosition) {
sbi(PORTC, PORTC7); // Left arm down.
limitedMotorsDirection[2] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
}
leftArmCheckEncoder = true;
initialSpeed = LEFT_ARM_MIN_PWM;
break;
case 8:
// Right arm.
// Check that requested absolute position is inside motor
bounds:
if (finalPosition
finalPosition =
}
if (finalPosition
finalPosition =
}
< 1) {
1;
> ARMS_MAX_VALUE) {
ARMS_MAX_VALUE;
// Set motor direction by checking the current position and the
requested one:
if (limitedMotorsCurrentPosition[3] < finalPosition) {
sbi(PORTC, PORTC5); // Right arm up.
limitedMotorsDirection[3] = true;
} else if (limitedMotorsCurrentPosition[3] > finalPosition) {
cbi(PORTC, PORTC5); // Right arm down.
limitedMotorsDirection[3] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
}
rightArmCheckEncoder = true;
initialSpeed = RIGHT_ARM_MIN_PWM;
break;
case 9:
// Hand.
// Set motor direction:
if (limitedMotorsCurrentPosition[4] < finalPosition) {
sbi(PORTC, PORTC3); // Open hand.
limitedMotorsDirection[4] = true;
} else if (limitedMotorsCurrentPosition[4] > finalPosition) {
cbi(PORTC, PORTC3); // Close hand.
limitedMotorsDirection[4] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
190
}
// Set stop threshold as a function of PWM speed:
if ((finalSpeed <= 255) && (finalSpeed > 235)) {
handDetectorThreshold = 100;
} else if ((finalSpeed <= 235) && (finalSpeed > 225)) {
handDetectorThreshold = 82;
} else if ((finalSpeed <= 225) && (finalSpeed > 215)) {
handDetectorThreshold = 75;
} else if ((finalSpeed <= 215) && (finalSpeed > 205)) {
handDetectorThreshold = 70;
} else if ((finalSpeed <= 205) && (finalSpeed > 185)) {
handDetectorThreshold = 67;
} else if ((finalSpeed <= 185) && (finalSpeed > 155)) {
handDetectorThreshold = 55;
} else if ((finalSpeed <= 155) && (finalSpeed > 125)) {
handDetectorThreshold = 50;
} else if ((finalSpeed <= 125) && (finalSpeed > 105)) {
handDetectorThreshold = 45;
} else if ((finalSpeed <= 105) && (finalSpeed > HAND_MIN_PWM)) {
handDetectorThreshold = 40;
} else {
handDetectorThreshold = 0;
}
initialSpeed = HAND_MIN_PWM;
break;
case 10:
// Hip.
// Check that requested absolute position is inside motor
bounds:
if (finalPosition
finalPosition =
}
if (finalPosition
finalPosition =
}
< 1) {
1;
> NO_ENCODER_MAX_VALUE) {
NO_ENCODER_MAX_VALUE;
// Set motor direction by checking the current position and the
requested one:
if (limitedMotorsCurrentPosition[5] < finalPosition) {
cbi(PORTC, PORTC1); // Hip up.
if (finalSpeed <= HIP_MIN_PWM) {
hipTimeToStop = 6600;
} else if ((finalSpeed > HIP_MIN_PWM) && (finalSpeed <= 140))
{
hipTimeToStop = 5200;
} else if ((finalSpeed > 140) && (finalSpeed <= 149)) {
hipTimeToStop = 3400;
} else if ((finalSpeed > 149) && (finalSpeed <= 160)) {
hipTimeToStop = 3600;
} else if ((finalSpeed > 160) && (finalSpeed <= 170)) {
hipTimeToStop = 3000;
} else if ((finalSpeed > 170) && (finalSpeed <= 190)) {
hipTimeToStop = 2200;
} else if ((finalSpeed > 190) && (finalSpeed <= 210)) {
hipTimeToStop = 2000;
} else if ((finalSpeed > 210) && (finalSpeed <= 230)) {
hipTimeToStop = 1700;
191
} else {
hipTimeToStop = 1500;
}
limitedMotorsDirection[5] = true;
} else if (limitedMotorsCurrentPosition[5] > finalPosition) {
sbi(PORTC, PORTC1); // Hip down.
if (finalSpeed <= HIP_MIN_PWM) {
hipTimeToStop = 4400;
} else if ((finalSpeed > HIP_MIN_PWM) && (finalSpeed <= 140))
{
hipTimeToStop = 3100;
} else if ((finalSpeed > 140) && (finalSpeed <= 149)) {
hipTimeToStop = 2000;
} else if ((finalSpeed > 149) && (finalSpeed <= 160)) {
hipTimeToStop = 2400;
} else if ((finalSpeed > 160) && (finalSpeed <= 170)) {
hipTimeToStop = 2250;
} else if ((finalSpeed > 170) && (finalSpeed <= 190)) {
hipTimeToStop = 1700;
} else if ((finalSpeed > 190) && (finalSpeed <= 210)) {
hipTimeToStop = 1550;
} else if ((finalSpeed > 210) && (finalSpeed <= 230)) {
hipTimeToStop = 1250;
} else {
hipTimeToStop = 1350;
}
limitedMotorsDirection[5] = false;
} else {
return; // Current position and new one coincides. Motor must
not be moved.
}
initialSpeed = HIP_MIN_PWM;
lastMillisHipMotorStarted = millis(); // Start time.
break;
}
// Start motor, according to its minimum PWM voltage and set
required global variables:
analogWrite(motorNumber, initialSpeed); // Physical start.
limitedMotorsFinalPosition[motorNumber - 5] = finalPosition;
motorsCurrentSpeed[motorNumber - 5] = initialSpeed;
motorsFinalSpeed[motorNumber - 5] = finalSpeed; // Set final speed
of the motor.
// Update limitedMotorsInMovement flag:
limitedMotorsInMovement = (limitedMotorsInMovement | (1 <<
(motorNumber - 5)));
updateLimitedMotorsInMovement();
}
}
// Function to start any motor in calibration mode:
void motorStartSetup(int motorNumber, boolean fb) {
// motorNumber: The pin where the motor to be started is connected.
Valid values: 5-12.
// fb: Boolean value to set direction of movement (up/forwards or
down/backwards).
192
// Set direction of the motor by changing according relay voltage and
start its movement:
if (motorsCurrentSpeed[motorNumber - 5] == 0) {
switch (motorNumber) {
case 5:
// Head tilt.
// Set motor direction:
if (fb) {
cbi(PORTA, PORTA4); // Head up.
} else {
sbi(PORTA, PORTA4); // Head down.
}
break;
case 6:
// Head pan.
// Set motor direction:
if (fb) {
cbi(PORTA, PORTA6); // Head left.
} else {
sbi(PORTA, PORTA6); // Head right.
}
break;
case 7:
// Left arm.
// Set motor direction:
if (fb) {
cbi(PORTC, PORTC7); // Left arm up.
} else {
sbi(PORTC, PORTC7); // Left arm down.
}
break;
case 8:
// Right arm.
// Set motor direction:
if (fb) {
sbi(PORTC, PORTC5); // Right arm up.
} else {
cbi(PORTC, PORTC5); // Right arm down.
}
break;
case 9:
// Hand.
// Set motor direction:
if (fb) {
sbi(PORTC, PORTC3); // Open hand.
} else {
cbi(PORTC, PORTC3); // Close hand.
}
break;
case 10:
// Hip.
// Set motor direction and compute time to stop:
if (fb) {
cbi(PORTC, PORTC1); // Hip up.
} else {
sbi(PORTC, PORTC1); // Hip down.
}
break;
case 11:
// Left wheel.
193
// Set motor direction:
if (fb) {
cbi(PORTD, PORTD7); //
} else {
sbi(PORTD, PORTD7); //
}
break;
case 12:
// Right wheel.
// Set motor direction:
if (fb) {
cbi(PORTG, PORTG1); //
} else {
sbi(PORTG, PORTG1); //
}
break;
Left wheel forward.
Left wheel backward.
Right wheel forward.
Right wheel backward.
}
if (motorNumber == 5) {
analogWrite(5, HEAD_TILT_MIN_PWM); // Start head tilt motor,
according to its minimum PWM value.
delay(100); // Wait 100 ms for motor movement.
} else {
analogWrite(motorNumber, 255); // Start motor, according to its
maximum PWM value.
delay(100); // Wait 100 ms for motor movement.
}
// Clear relay bits:
cbi(PORTA, PORTA4);
cbi(PORTA, PORTA6);
cbi(PORTC, PORTC7);
cbi(PORTC, PORTC5);
cbi(PORTC, PORTC3);
cbi(PORTC, PORTC1);
cbi(PORTD, PORTD7);
cbi(PORTG, PORTG1);
analogWrite(motorNumber, 0); // Stop motor
} // A motor can be started only if it is stopped.
}
// Routine to translate a new trajectory from the axis of movement to
PWM values and direction of wheels' motors:
struct WHEELS movementTranslation(struct AXIS_MOVEMENT_POINT point) {
// point: A point in the axis of movement to be translated.
// Returns: The PWM value and the direction for each wheel motor; all
together in a struct variable.
float angle; // For 0x66, 0x69, 0x96 & 0x99 movements, contains the
angle formed by the square triangle of point.x and point.y.
float powerSpeed; // For 0x66, 0x69, 0x96 & 0x99 movements, the power
of the wheel that performs the turn.
float turningSpeed; // For 0x66, 0x69, 0x96 & 0x99 movements, the
power of the wheel that supports the turn.
struct WHEELS wheelsData;
// Determine if the desired trajectory is to spin I-Droid02 over
itself or change its position:
if ((point.directionFlag == 0x06) || (point.directionFlag == 0x09)) {
194
// Spin. The directionFlag value determines the direction of
rotation and the x point value the speed of rotation:
wheelsData.leftWheelPWM = getPWMSpeed(11, point.x);
wheelsData.rightWheelPWM = getPWMSpeed(12, point.x);
originalLeftWheelFinalSpeed = wheelsData.leftWheelPWM;
originalRightWheelFinalSpeed = wheelsData.rightWheelPWM;
if (point.directionFlag == 0x06) {
// Spin to the left:
wheelsData.leftWheelDirection = false;
wheelsData.rightWheelDirection = true;
} else {
// Spin to the right:
wheelsData.leftWheelDirection = true;
wheelsData.rightWheelDirection = false;
}
} else {
// Position change (from STOP or another trajectory).
// Set wheels direction according to point direction flag:
if ((point.directionFlag & 0xF0) == 0x60) {
// Forward direction:
wheelsData.leftWheelDirection = true;
wheelsData.rightWheelDirection = true;
} else {
// Backward direction:
wheelsData.leftWheelDirection = false;
wheelsData.rightWheelDirection = false;
}
// Determine whether the trajectory is rectilinear or curved:
if ((point.directionFlag == 0x60) || (point.directionFlag == 0x90))
{
// Rectilinear trajectory:
wheelsData.leftWheelPWM = getPWMSpeed(11, point.y);
wheelsData.rightWheelPWM = getPWMSpeed(12, point.y);
originalLeftWheelFinalSpeed = wheelsData.leftWheelPWM;
originalRightWheelFinalSpeed = wheelsData.rightWheelPWM;
} else {
// Curved trajectory. Compute parameters:
angle = atan(point.y/point.x); // The angle formed by two axis
values.
powerSpeed = 1 / sqrt(pow((cos(angle) / point.x), 2) +
pow((sin(angle) / point.y), 2)); // Formula of the radius of an ellipse.
turningSpeed = powerSpeed * sin(angle); // Ponderate the power
speed according to the angle value.
if ((point.directionFlag & 0x0F) == 0x06) {
// Curved left.
wheelsData.leftWheelPWM = getPWMSpeed(11, turningSpeed);
wheelsData.rightWheelPWM = getPWMSpeed(12, powerSpeed);
} else {
// Curved right.
wheelsData.leftWheelPWM = getPWMSpeed(11, powerSpeed);
wheelsData.rightWheelPWM = getPWMSpeed(12, turningSpeed);
}
}
}
return wheelsData;
}
195
// Function to start the reset of all motors:
void resetAllMotors(boolean sD) {
// sD: If true, the function is called from OS in the shutdown
process.
// Check if head tilt motor is in reset position:
if (limitedMotorsCurrentPosition[0] == 1) {
motorsNeedToReset[0] = true;
}
// Check if head pan motor is in reset position:
if ((limitedMotorsCurrentPosition[1] < 12) ||
limitedMotorsCurrentPosition[1] > 12) {
motorsNeedToReset[1] = true;
resetMode = true;
}
// Check if left arm motor is in reset position:
if (limitedMotorsCurrentPosition[2] > 1) {
motorsNeedToReset[2] = true;
}
// Check if right arm motor is in reset position:
if (limitedMotorsCurrentPosition[3] > 1) {
motorsNeedToReset[3] = true;
}
// Check if hand motor is in reset position:
if (limitedMotorsCurrentPosition[4] == 1) {
motorsNeedToReset[4] = true;
}
// Check if hip motor is in reset position:
if (limitedMotorsCurrentPosition[5] == 2) {
motorsNeedToReset[5] = true;
}
// This flag is dummy, just to represent the instant to show shutdown
message:
if (sD) {
motorsNeedToReset[6] = true;
}
}
// Function to retrieve the last stored position of all limited motors
from EEPROM memory:
void retrieveLimitedMotorsPositionValues() {
int i;
for (i = 0; i < 6; i++) {
limitedMotorsCurrentPosition[i] = EEPROM.read(i);
limitedMotorsFinalPosition[i] = limitedMotorsCurrentPosition[i];
}
// Initialize encoder previous readings to avoid false detections:
for (i = 0; i < 10; i++) {
headPanEncoderPreviousReadings[i] = 0;
leftArmEncoderPreviousReadings[i] = 1;
rightArmEncoderPreviousReadings[i] = 1;
handEndDetectorPreviousReadings[i] = 0;
196
leftWheelEncoderPreviousReadings[i] = PINC & 0x01;
rightWheelEncoderPreviousReadings[i] = (PING & 0x04) >> 2;
}
}
// Function to update current limited motors in movement:
void updateLimitedMotorsInMovement() {
Serial.write(COMMAND_MOTORS);
Serial.write(COMMAND_MOTORS_UPDATE_MOVING_FLAG);
Serial.write(limitedMotorsInMovement);
}
// Function to write limited motors current position to EEPROM memory:
void writeLimitedMotorCurrentPosition(int motorNumber) {
// motorNumber: The limited motor which is wanted to store its current
position.
EEPROM.write(motorNumber - 5, limitedMotorsCurrentPosition[motorNumber
- 5]);
}
//-------------- TEMPERATURE SENSOR ----------------------------------//
// Function to get temperature from temperature sensor:
float temperature() {
float sensorValue; // Variable to store temperature.
// Dummy readings for ADC stability:
sensorValue = analogRead(A1);
sensorValue = analogRead(A1);
sensorValue = analogRead(A1);
return ((sensorValue * 500.0 / 1023.0) - 1.4); // Convert ADC value to
mV and actual temperature according to datasheet (10 mV/ºC).
}
//-------------- TOUCH SENSOR ----------------------------------------//
// Routine to detect if touch sensor has been touched:
boolean touchSensor() {
boolean value = (PINL & 0x10) >> 4;
// In order to avoid more than one detection per trigger, only one
detection is reported during a margin of 1.5 seconds.
if (value && (scheduler(&lastMillisTacte, 1500))) {
return value;
} else {
return false;
}
}
//-------------- ULTRASONIC SYSTEM -----------------------------------//
// If distance between the obstacle and the sensor can be computed, it
will be expressed in cm:
float computeUltrasonicDistance() {
long actualTOF = TOF; // Save current value of obtained TOF.
float result = -1; // If obstacle is in a blind spot of the sensor or
too far this will be -1.
if (ended) {
if ((actualTOF > 300) && (actualTOF < 2000)) {
// Obstacle is inside sensor margin.
197
result = actualTOF * 343.0 / 20000.0; // Speed of sound: 343 m/s.
Divided by 20000 contains the half of the sound travel, the conversion
of time units from us to s and distance units from m to cm.
}
ended = false;
}
return result;
}
// Interrupt Service Routine for ultrasonic sensors:
ISR(PCINT2_vect) {
byte ultrasonicRegister = PINK;
byte mask = 0;
// Determine mask according to current selected sensor:
switch (ultrasonicSensor) {
case 0:
// Left ultrasonic sensor (A9) currently set.
mask = B00000010;
break;
case 1:
// Center ultrasonic sensor (A10) currently set.
mask = B00000100;
break;
case 2:
// Right ultrasonic sensor (A11) currently set.
mask = B00001000;
break;
}
if ((ultrasonicRegister & mask) == mask) {
if (firstPulse) {
// Second pulse detected.
TOF = micros() - microsFirst;
if (TOF > 250) {
ended = true; // A new valid TOF has been captured.
cbi(PCICR, PCIE2); // Deactivate ultrasonic interruptions.
firstPulse = false;
}
} else {
// First pulse detected.
microsFirst = micros(); // Store current time.
firstPulse = true;
}
} // Pin change interruption is triggered when any of the watched pins
change. Only LOW to HIGH changes are relevant.
}
// Function to change ultrasonic sensor for next sensing:
void switchUltrasonicSensor(byte sensor) {
// sensor: Ultrasonic sensor number to be changed (sensor position,
pin, interrupt mask): 0 (left, A9 pin, PCINT17), 1 (center, A10 pin,
PCINT18) or 2 (right, A11 pin, PCINT19).
ultrasonicSensor = sensor; // Update flag.
}
// Function to manage ultrasonic sensor interrupt.
void ultrasonicEnable() {
198
// In order to control how many readings are performed, selected
sensor will be watched and updated after 250 ms:
if (scheduler(&lastMillisUltrasonic, 250)) {
sbi(PCIFR, PCIF2); // Delete outstanding interruptions.
sbi(PCICR, PCIE2); // Activate ultrasonic interruptions.
}
}
// EOF
199
Appendix 8. Arduino Mega – Raspberry Pi serial commands
Battery voltage and temperature sensors
Command
b (12 Bytes)
w
Destination
Description
R
Notify battery voltage and temperature sensors values
R
Arduino sends the low battery alarm
Chest buttons
Command Destination
Description
c (1 Byte)
R
Notify which buttons have been pressed
Display
Command
dc
d s (2 Bytes)
d w (2 Bytes)
(max. 16 Bytes)
Destination
Description
A
Clear display
A
Setup display (contrast) (remaining settings)
A
Write characters or move cursor (position) (length)
(data)
LEDs
Command Destination
Description
l (3 Bytes)
A
Change LEDs (Heads) (Orange head) (Base)
Motors
Command
Destination
Description
md
A
Request all limited motors position (done at startup)
me
R
Notifies when m command has reached destination
m m (7 Bytes)
A
Move I-Droid02 (direction, axis point and distance)
m p (2 Bytes)
R
Notify motors’ position (# motor) (encoder value)
mR
A
Reset encoder count position in calibration
m S (1 Byte)
A
Start any motor in cal. (motor number | direction)
m s (3 Bytes)
A
Start any limited motor (# motor) (position) (speed)
m t (1 Byte)
A
Stop any motor at next encoder position (# motor)
m u (1 Byte)
R
Update limited motors in movement flag
m w (8 Bytes)
R
Both left and right wheels’ speeds in km/h.
Touch sensor
Command Destination
Description
t
R
Notify that touch sensor has been touched
200
Ultrasonic sensor
Command Destination
Description
u (1 Byte)
A
Change ultrasonic sensor to be watched with the interrupt
u (4 Bytes)
R
Distance value of the selected ultrasonic sensor
Resets
Command
r
ra
r m (1 Byte)
Destination
Description
R
Inform Raspberry Pi that Arduino Mega has been reset
A
Soft reset Arduino Mega 2560
A
Reset motors to default position (x = 1 only for OS call)
All commands must end with the EOT (End of Transmission) character.
201
Appendix 9. I-Droid02 Driver source code
main.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef MAIN_H
#define MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <wiringPi.h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
"BatteryData.h" // Battery values file thread.
"CameraHandler.h" // Camera thread.
"ChestButtonsHandler.h" // Chest buttons sensor thread.
"DisplayHandler.h" // Display thread.
"GPIOsHandler.h" // GPIOs thread.
"LEDsHandler.h" // LEDs thread.
"TorchHandler.h" // Torch thread.
"MicrophoneHandler.h" // Microphone UART capture thread.
"MotorsHandler.h" // Motors thread.
"ProgrammableButtonsHandler.h" // Programmable buttons thread.
"RFM31B_Handler.h" // RFM31B receiver thread.
"TemperatureData.h" // Temperatures file thread.
"TouchSensorHandler.h" // Touch sensor thread.
"UltrasonicHandler.h" // Ultrasonic distance file thread.
// Constants:
#define MAIN_DIRECTORY "/tmp/idroid02"
// Functions:
void createDriverDirectory();
#ifdef __cplusplus
}
#endif
#endif /* MAIN_H */
main.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
202
/* Main file of I-Droid02 Driver. This file is in charge of creating
* new threads for every task developed by the driver.
*/
#include "main.h"
void createDriverDirectory() {
char* deleteDirCommand;
int deleteDirCommandLength;
struct stat dir;
// Create folder to store UNIX sockets and common interchange files:
if (stat(MAIN_DIRECTORY, &dir) >= 0) {
// Directory exists and must be deleted first.
deleteDirCommandLength = 6 + strlen(MAIN_DIRECTORY) + 1;
deleteDirCommand = malloc(deleteDirCommandLength); // Memory
allocation of delete command string.
strncpy(deleteDirCommand, "rm -r ", deleteDirCommandLength);
strncat(deleteDirCommand, MAIN_DIRECTORY,
deleteDirCommandLength);
system(deleteDirCommand); // Directory delete.
}
mkdir(MAIN_DIRECTORY, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); //
Full permissions.
}
int main(int argc, char** argv) {
pthread_t arduinoPeripheral;
pthread_t batteryData;
pthread_t cameraHandler;
pthread_t chestButtonsHandler;
pthread_t displayHandler;
pthread_t gpiosHandler;
pthread_t ledsHandler;
pthread_t microphoneHandler;
pthread_t motorsHandler;
pthread_t programmableButtonsHandler;
pthread_t RFM31B_Handler;
pthread_t temperatureData;
pthread_t torchHandler;
pthread_t touchSensorHandler;
pthread_t ultrasonicHandler;
createDriverDirectory();
// GPIO setup:
if (wiringPiSetupSys() < 0) {
perror("Error with GPIO setup");
}
// Thread start process:
pthread_create(&arduinoPeripheral, NULL, arduinoPeripheral_Start,
NULL);
pthread_detach(arduinoPeripheral);
pthread_create(&batteryData, NULL, batteryData_Start, NULL);
pthread_detach(batteryData);
pthread_create(&cameraHandler, NULL, cameraHandler_Start, NULL);
pthread_detach(cameraHandler);
pthread_create(&chestButtonsHandler, NULL,
chestButtonsHandler_Start, NULL);
203
pthread_detach(chestButtonsHandler);
pthread_create(&displayHandler, NULL, displayHandler_Start, NULL);
pthread_detach(displayHandler);
pthread_create(&gpiosHandler, NULL, GPIOsHandler_Start, NULL);
pthread_detach(gpiosHandler);
pthread_create(&ledsHandler, NULL, LEDsHandler_Start, NULL);
pthread_detach(ledsHandler);
pthread_create(&microphoneHandler, NULL, microphoneHandler_Start,
NULL);
pthread_detach(microphoneHandler);
pthread_create(&motorsHandler, NULL, motorsHandler_Start, NULL);
pthread_detach(motorsHandler);
pthread_create(&programmableButtonsHandler, NULL,
programmableButtonsHandler_Start, NULL);
pthread_detach(programmableButtonsHandler);
pthread_create(&RFM31B_Handler, NULL, RFM31B_Handler_Start, NULL);
pthread_detach(RFM31B_Handler);
pthread_create(&temperatureData, NULL, temperatureData_Start, NULL);
pthread_detach(temperatureData);
pthread_create(&torchHandler, NULL, torchHandler_Start, NULL);
pthread_detach(torchHandler);
pthread_create(&touchSensorHandler, NULL, touchSensorHandler_Start,
NULL);
pthread_detach(touchSensorHandler);
pthread_create(&ultrasonicHandler, NULL, ultrasonicHandler_Start,
NULL);
pthread_detach(ultrasonicHandler);
// Infinite loop to check whether a thread has terminated:
while (1) {
sleep(1);
if (arduinoPeripheralAlive == 0) {
pthread_create(&arduinoPeripheral, NULL,
arduinoPeripheral_Start, NULL);
}
if (batteryDataAlive == 0) {
pthread_create(&batteryData, NULL, batteryData_Start, NULL);
}
if (cameraHandlerAlive == 0) {
pthread_create(&cameraHandler, NULL, cameraHandler_Start,
NULL);
}
if (chestButtonsHandlerAlive == 0) {
pthread_create(&chestButtonsHandler, NULL,
chestButtonsHandler_Start, NULL);
}
if (displayHandlerAlive == 0) {
pthread_create(&displayHandler, NULL, displayHandler_Start,
NULL);
}
if (GPIOsHandlerAlive == 0) {
pthread_create(&gpiosHandler, NULL, GPIOsHandler_Start,
NULL);
}
if (LEDsHandlerAlive == 0) {
pthread_create(&ledsHandler, NULL, LEDsHandler_Start, NULL);
}
if (microphoneHandlerAlive == 0) {
pthread_create(&microphoneHandler, NULL,
microphoneHandler_Start, NULL);
204
}
if (motorsHandlerAlive == 0) {
pthread_create(&microphoneHandler, NULL,
microphoneHandler_Start, NULL);
}
if (programmableButtonsHandlerAlive == 0) {
pthread_create(&programmableButtonsHandler, NULL,
programmableButtonsHandler_Start, NULL);
}
if (RFM31B_HandlerAlive == 0) {
pthread_create(&RFM31B_Handler, NULL, RFM31B_Handler_Start,
NULL);
}
if (temperatureDataAlive == 0) {
pthread_create(&temperatureData, NULL,
temperatureData_Start, NULL);
}
if (torchHandlerAlive == 0) {
pthread_create(&torchHandler, NULL, torchHandler_Start,
NULL);
}
if (touchSensorHandlerAlive == 0) {
pthread_create(&touchSensorHandler, NULL,
touchSensorHandler_Start, NULL);
}
if (ultrasonicHandlerAlive == 0) {
pthread_create(&ultrasonicHandler, NULL,
ultrasonicHandler_Start, NULL);
}
}
return EXIT_SUCCESS;
}
ArduinoPeripheral.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef ARDUINOPERIPHERAL_H
#define ARDUINOPERIPHERAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
#include
<pthread.h>
<stdint.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
#include "ChestButtonsHandler.h"
#include "TouchSensorHandler.h"
#include "UARTManager.h"
205
// Constants:
#define BUFFER_READ_LENGTH 20
#define COMMAND_BASIC 'b'
#define COMMAND_BASIC_WARNING_LOW_BATTERY 'w'
#define COMMAND_CHEST_BUTTONS 'c'
#define COMMAND_DISPLAY 'd'
#define COMMAND_DISPLAY_CLEAR 'c'
#define COMMAND_DISPLAY_SETUP 's'
#define COMMAND_DISPLAY_MOVE_OR_WRITE 'w'
#define COMMAND_END_OF_TRANSMISSION 0x04
#define COMMAND_LEDS 'l'
#define COMMAND_MOTORS 'm'
#define COMMAND_MOTORS_CALIBRATE 'S'
#define COMMAND_MOTORS_POSITION 'p'
#define COMMAND_MOTORS_REQUEST_DATA 'd'
#define COMMAND_MOTORS_RESET_ENCODERS 'R'
#define COMMAND_MOTORS_SET_MOVEMENT 'm'
#define COMMAND_MOTORS_SET_MOVEMENT_TARGET_REACHED 'e'
#define COMMAND_MOTORS_START 's'
#define COMMAND_MOTORS_STOP 't'
#define COMMAND_MOTORS_UPDATE_MOVING_FLAG 'u'
#define COMMAND_MOTORS_WHEELS_SPEED 'w'
#define COMMAND_TOUCH 't'
#define COMMAND_ULTRASONIC 'u'
#define COMMAND_RESET 'r'
#define COMMAND_RESET_ARDUINO_BOARD 'a'
#define COMMAND_RESET_MOTORS 'm'
#define DISPLAY_ROW_MAX_CHARACTERS 16
#define PRIMARY_UART "/dev/ttyACM0"
// Global variables:
char arduinoPeripheralAlive; // Flag to check if thread is alive.
pthread_mutex_t arduinoSendMutex; // Mutex to synchronize sendings
to Arduino.
int arduinoUART_FD; // File Descriptor for Arduino primary UART
interface.
struct axisMovementPoint {
char directionFlag;// Flag to indicate direction of movement
(0x00 indicates STOP).
char x; // Abscissa modulus value of the point.
char y; // Ordered modulus value of the point.
int distanceAngle; // Total distance to cover/angle to rotate. 1 means infinite distance/angle.
} currentMovementSet; // Current I-Droid02 movement set.
float batteryMain; // Voltage of I-Droid02 battery.
float batteryMainPercentage; // Discharging percentage of I-Droid02
battery.
char chestButtonsFlag; // Flag to state which buttons has been
pressed.
struct display {
char column; // Current column where cursor is placed (1-16).
char contrast; // Current display contrast (0-100, default: 50).
char row; // Current row where cursor is placed (1-2).
char settings; // Display settings. Byte format: 0000x1x2x3x4.
x1 = Cursor ON/OFF, x2 = blinking ON/OFF, x3 = Display ON/OFF, x4 =
Write Left-Right/Right-Left.
char textRow0[DISPLAY_ROW_MAX_CHARACTERS]; // Text at row 0.
char textRow0Length; // Number of Bytes of row 0 text.
char textRow1[DISPLAY_ROW_MAX_CHARACTERS]; // Text at row 1.
char textRow1Length; // Number of Bytes of row 1 text.
206
} displayData; // Current information of the display.
/* This integer variable stores the current status of all LEDs
following the
* next format: The first 8 bits are used to indicate the status of
LEDs
* placed in the head (0bBBRYGRYG). B = Blue ear LEDs, left and
right. RYG =
* Red, yellow and green eyes LEDs, left and right. Second 8 bits
group is
* to store current top-head orange LED PWM value. For 3rd 8 bits
group,
* only last bit is used to state whether base LEDs are ON or OFF.
*/
int LEDsData;
float leftWheelSpeed; // Left wheel speed in km/h.
struct limitedMotor {
char MAXIMUM_POSITION; // Maximum encoder position for this
motor.
char MOTOR_NUMBER; // Motor number ID.
char currentPosition; // Current motor position.
}; // Information about motors with limited movement.
struct limitedMotor limitedMotors[6]; // Array of previous struct.
/* Current motors in movement according to the flag: 0b00HiHaRLPT.
If the corresponding bit is set, the motor is currently in movement.
* Hi (hip), Ha (hand), R (right arm), L (left arm), P (head pan), T
(head tilt). */
char limitedMotorsInMovement;
// Both used to signal when a movement set previously has been
completed (distance or angle set has been reached):
pthread_mutex_t movementCompleteMutex;
pthread_cond_t movementCompleteCondition;
char movementCompleteCommand; // If 1, an application is waiting for
the last set movement to be completed.
float rightWheelSpeed; // Right wheel speed in km/h.
float temperatureAmbient; // Arduino ambient temperature sensor.
char touchSensorFlag; // Flag to store whether touch sensor has been
touched.
float ultrasonicDistance; // Distance obtained from the current
selected ultrasonic sensor.
char ultrasonicSensor; // Selected ultrasonic sensor (0: left
sensor, 1: centre sensor, 2: right sensor).
// Functions:
void* arduinoPeripheral_Start(void*); // Handler start function.
void resetDisplayData(); // Function to reset display to its default
state.
void resetLEDsData(); // Function to reset LEDs data.
void resetLimitedMotorsData(); // Function to reset limited motors
(all except wheels) data.
void resetMovementData(); // Function to reset movement data
void resetUltrasonicSensor(); // Function to reset selected
ultrasonic sensor.
void sendCommandToArduino(unsigned char*, int); // Function to send
any command to Arduino Mega 2560.
void requestLimitedMotorsData(); // Function to send
COMMAND_MOTORS_REQUEST_DATA to get limited motors data.
#ifdef __cplusplus
}
#endif
207
#endif /* ARDUINOPERIPHERAL_H */
ArduinoPeripheral.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* ArduinoPeripheral. This thread is in charge of the serial
communication with the
* Arduino Mega 2560 board through its principal interface (ttyACM0).
For the
* reception of any data coming from the board, an additional thread is
used in
* order to avoid a total block of the main thread waiting for a
transmission.
*/
#include "ArduinoPeripheral.h"
void* arduinoPeripheral_Start(void* arg) {
char bufferRead[BUFFER_READ_LENGTH];
char floatTemporalBuffer[sizeof (float)];
arduinoPeripheralAlive = 1;
pthread_mutex_init(&arduinoSendMutex, NULL);
// Setup UART:
arduinoUART_FD = openUART(PRIMARY_UART);
setupUART(arduinoUART_FD, B115200);
// Init movement complete mutex and condition:
pthread_mutex_init(&movementCompleteMutex, NULL);
pthread_cond_init(&movementCompleteCondition, NULL);
// Read commands from Arduino in an infinite loop:
while (1) {
// Read a new command ID letter coming from Arduino Mega 2560
board:
if (readUART(arduinoUART_FD, bufferRead, 1) < 0) {
// Error reading UART.
break;
}
// Determine received command ID:
switch (bufferRead[0]) {
case COMMAND_BASIC:
// Basic command received. Temperature and battery
sensors data:
if (readUART(arduinoUART_FD, bufferRead, 12) < 0) {
// Error reading UART.
break;
}
// Parse battery voltage value extracted from the
command:
floatTemporalBuffer[0] = bufferRead[0];
floatTemporalBuffer[1] = bufferRead[1];
208
floatTemporalBuffer[2] = bufferRead[2];
floatTemporalBuffer[3] = bufferRead[3];
memcpy(&batteryMain, floatTemporalBuffer, sizeof
(float));
// Parse battery percentage value extracted from the
command:
floatTemporalBuffer[0] = bufferRead[4];
floatTemporalBuffer[1] = bufferRead[5];
floatTemporalBuffer[2] = bufferRead[6];
floatTemporalBuffer[3] = bufferRead[7];
memcpy(&batteryMainPercentage, floatTemporalBuffer,
sizeof (float));
// Parse temperature value extracted from the command:
floatTemporalBuffer[0] = bufferRead[8];
floatTemporalBuffer[1] = bufferRead[9];
floatTemporalBuffer[2] = bufferRead[10];
floatTemporalBuffer[3] = bufferRead[11];
memcpy(&temperatureAmbient, floatTemporalBuffer, sizeof
(float));
break;
case COMMAND_BASIC_WARNING_LOW_BATTERY:
// A low battery warning has been received! System must
be shut down now!
system("sh /home/pi/Main_Scripts/resetShutdown.sh"); //
Tell Arduino Mega 2560 that I-Droid02 is going to be shut down.
system("sudo shutdown -h now"); // Shutdown Raspberry
Pi.
break;
case COMMAND_CHEST_BUTTONS:
// Chest buttons command received. A chest button has
been pressed:
// Store chest buttons flag:
if (readUART(arduinoUART_FD, bufferRead, 1) < 0) {
// Error reading UART.
break;
}
// Only update flag if a high application is connected:
if (chestButtonsActiveConnection != 0) {
pthread_mutex_lock(&chestButtonsMutex); // Lock
mutex.
chestButtonsFlag = bufferRead[0];
pthread_mutex_unlock(&chestButtonsMutex); // Unlock
mutex.
pthread_cond_signal(&chestButtonsCondition); //
Signal ChestButtonsHandler thread.
}
break;
case COMMAND_MOTORS:
// Motors command received. Information about motors
position or movement:
// Receive motors second command character:
if (readUART(arduinoUART_FD, bufferRead, 1) < 0) {
// Error reading UART.
break;
}
209
switch (bufferRead[0]) {
case COMMAND_MOTORS_POSITION:
// Arduino notifies the current position of a
motor in movement:
// Receive motor number and its encoder
position:
if (readUART(arduinoUART_FD, bufferRead, 2) < 0)
{
// Error reading UART.
break;
}
// Update current motor data:
if (bufferRead[0] < 11) {
// Limited motor position:
limitedMotors[bufferRead[0] 5].currentPosition = bufferRead[1];
}
break;
case COMMAND_MOTORS_SET_MOVEMENT_TARGET_REACHED:
// Arduino notifies that movement set has
reached its distance/angle.
if (movementCompleteCommand == 1) {
// An application is waiting for this
signal:
pthread_cond_signal(&movementCompleteCondition);
}
resetMovementData(); // Current movement is
STOP.
break;
case COMMAND_MOTORS_UPDATE_MOVING_FLAG:
// Arduino updates current limited motors in
movement:
// Receive motor number:
if (readUART(arduinoUART_FD, bufferRead, 1) < 0)
{
// Error reading UART.
break;
}
// Update current flag:
limitedMotorsInMovement = bufferRead[0];
break;
case COMMAND_MOTORS_WHEELS_SPEED:
// Arduino notifies the current speed of wheels
motors:
// Receive and parse left wheel speed:
if (readUART(arduinoUART_FD,
floatTemporalBuffer, sizeof(float)) < 0) {
// Error reading UART.
break;
}
memcpy(&leftWheelSpeed, floatTemporalBuffer,
sizeof(float));
210
// Receive and parse right wheel speed:
if (readUART(arduinoUART_FD,
floatTemporalBuffer, sizeof(float)) < 0) {
// Error reading UART.
break;
}
memcpy(&rightWheelSpeed, floatTemporalBuffer,
sizeof(float));
break;
}
break;
case COMMAND_RESET:
// Arduino Board has been reset manually or using
RESET_COMMAND:
// Reset all data from all Arduino sensors/actuators:
resetDisplayData();
resetLEDsData();
resetMovementData();
resetUltrasonicSensor();
break;
case COMMAND_TOUCH:
// Touch command received. Touch sensor has been
touched:
// Only update flag if a high application is connected:
if (touchSensorActiveConnection) {
pthread_mutex_lock(&touchSensorMutex); // Lock
mutex.
touchSensorFlag = 1;
pthread_mutex_unlock(&touchSensorMutex); // Unlock
mutex.
pthread_cond_signal(&touchSensorCondition); //
Signal TouchSensorHandler thread.
}
break;
case COMMAND_ULTRASONIC:
// Ultrasonic command received. Ultrasonic sensors data:
if (readUART(arduinoUART_FD, bufferRead, 4) < 0) {
// Error reading UART.
break;
}
// Parse ultrasonic distance value extracted from the
command:
floatTemporalBuffer[0] = bufferRead[0];
floatTemporalBuffer[1] = bufferRead[1];
floatTemporalBuffer[2] = bufferRead[2];
floatTemporalBuffer[3] = bufferRead[3];
memcpy(&ultrasonicDistance, floatTemporalBuffer, sizeof
(float));
break;
}
}
arduinoPeripheralAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
211
void requestLimitedMotorsData() {
unsigned char commandBuffer[3]; // Buffer to store Arduino command.
// Prepare restore motors position command:
commandBuffer[0] = COMMAND_MOTORS;
commandBuffer[1] = COMMAND_MOTORS_REQUEST_DATA;
commandBuffer[2] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(commandBuffer, 3);
}
void resetDisplayData() {
// Reset display data:
displayData.column = 0;
displayData.contrast = 50;
displayData.row = 0;
displayData.settings = 0b00000011;
strncpy(displayData.textRow0, "", 0);
displayData.textRow0Length = 0;
strncpy(displayData.textRow1, "", 0);
displayData.textRow1Length = 0;
}
void resetLEDsData() {
LEDsData = 0x00000; // Reset flag.
}
void resetLimitedMotorsData() {
// positionData: Received buffer from Arduino with current position
data.
// Head tilt:
limitedMotors[0].MAXIMUM_POSITION = 2;
limitedMotors[0].MOTOR_NUMBER = 5;
limitedMotors[0].currentPosition = 2;
// Head pan:
limitedMotors[1].MAXIMUM_POSITION = 21;
limitedMotors[1].MOTOR_NUMBER = 6;
limitedMotors[1].currentPosition = 10;
// Left arm:
limitedMotors[2].MAXIMUM_POSITION = 31;
limitedMotors[2].MOTOR_NUMBER = 7;
limitedMotors[2].currentPosition = 1;
// Right arm:
limitedMotors[3].MAXIMUM_POSITION = 31;
limitedMotors[3].MOTOR_NUMBER = 8;
limitedMotors[3].currentPosition = 1;
// Hand:
limitedMotors[4].MAXIMUM_POSITION = 2;
limitedMotors[4].MOTOR_NUMBER = 9;
limitedMotors[4].currentPosition = 2;
// Hip:
limitedMotors[5].MAXIMUM_POSITION = 2;
limitedMotors[5].MOTOR_NUMBER = 10;
limitedMotors[5].currentPosition = 1;
}
212
void resetMovementData() {
// Default movement is STOP:
currentMovementSet.directionFlag = 0x00;
currentMovementSet.x = 0;
currentMovementSet.y = 0;
currentMovementSet.distanceAngle = -1;
leftWheelSpeed = 0.0;
rightWheelSpeed = 0.0;
}
void resetUltrasonicSensor() {
ultrasonicSensor = 1; // Reset flag.
}
void sendCommandToArduino(unsigned char* buffer, int length) {
// buffer: Command to be sent, containing flag.
// length: Number of Bytes that command contains.
pthread_mutex_lock(&arduinoSendMutex); // Lock mutex.
writeUART(arduinoUART_FD, buffer, length);
pthread_mutex_unlock(&arduinoSendMutex); // Unlock mutex.
}
BatteryData.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef BATTERYDATA_H
#define BATTERYDATA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
#include <string.h>
#include "ArduinoPeripheral.h"
#include "FileManager.h"
// Constants:
#define BATTERIES_BUFFER_SIZE 50
#define BATTERIES_FILE "/tmp/idroid02/batteries.txt"
#define NULL_VALUE NAN
#define NULL_VALUE_TEXT "-"
// Global variables:
char batteryDataAlive; // Flag to check if thread is alive.
float batteryRemote; // Voltage of I-Droid02 remote control battery.
float batteryRemotePercentage; // Discharging percentage of IDroid02 remote control battery.
int currentPosition; // Current position of mainBatteryValues array
to write new value.
213
// Functions:
void* batteryData_Start(void*); // Handler start function.
void parseAndAppendBatteryValue(float, char*, int*); // Parse any
float battery value and store it to the string chain.
void resetBatteryValues(char*); // Reset battery values for next
reading.
#ifdef __cplusplus
}
#endif
#endif /* BATTERYDATA_H */
BatteryData.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* BatteryData. This thread is in charge of writing a file containing
the value
* of both available battery voltage sensors and its discharging
percentages.
*/
#include "BatteryData.h"
void* batteryData_Start(void* arg) {
char batteryBuffer[BATTERIES_BUFFER_SIZE]; // Buffer of Bytes to be
written.
int bytesCount; // Number of Bytes to be written.
batteryDataAlive = 1;
// Initial values of the batteries voltages and percentages before
recollect data:
resetBatteryValues(batteryBuffer);
// Loop to write battery voltages and percentages file:
while (1) {
bytesCount = 0;
// I-Droid02 battery voltage and discharging percentage:
parseAndAppendBatteryValue(batteryMain, batteryBuffer,
&bytesCount);
parseAndAppendBatteryValue(batteryMainPercentage, batteryBuffer,
&bytesCount);
// I-Droid02 remote control battery voltage and discharging
percentage:
parseAndAppendBatteryValue(batteryRemote, batteryBuffer,
&bytesCount);
parseAndAppendBatteryValue(batteryRemotePercentage,
batteryBuffer, &bytesCount);
// Write battery values to the corresponding file:
echoLineToFile(BATTERIES_FILE, batteryBuffer);
214
// Reset values and put the thread to sleep 10 seconds:
resetBatteryValues(batteryBuffer);
sleep(10);
}
batteryDataAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void parseAndAppendBatteryValue(float batteryValue, char* batteryBuffer,
int* bytesCount) {
// batteryValue: Battery voltage or percentage value to be parsed
and stored to the buffer.
// batteryBuffer: String buffer to store battery values.
// bytesCount: Pointer to number of Bytes that batteryBuffer
contains.
char temporal[10]; // Buffer to store temporal parsed values.
if (isnan(batteryValue)) {
// New battery value is not available.
*bytesCount = *bytesCount + 1;
if (*bytesCount == 1) {
// 1st Byte to append.
strncpy(batteryBuffer, NULL_VALUE_TEXT, *bytesCount);
} else {
strncat(batteryBuffer, NULL_VALUE_TEXT, *bytesCount);
}
} else {
// New battery value is available.
*bytesCount = *bytesCount + (int) (log10(batteryValue * 100) +
2);
sprintf(temporal, "%.2f", batteryValue);
if (*bytesCount == (int) (log10(batteryValue * 100) + 2)) {
// 1st Bytes to append.
strncpy(batteryBuffer, temporal, *bytesCount);
} else {
strncat(batteryBuffer, temporal, *bytesCount);
}
}
*bytesCount = *bytesCount + 1;
batteryBuffer[*bytesCount - 1] = '\n';
}
void resetBatteryValues(char* batteryBuffer) {
// batteryBuffer: Buffer of Bytes to be written.
int i;
batteryMain = NULL_VALUE;
batteryMainPercentage = NULL_VALUE;
batteryRemote = NULL_VALUE;
batteryRemotePercentage = NULL_VALUE;
// Battery values will be written following the previous order.
for (i=0; i<BATTERIES_BUFFER_SIZE; i++) {
batteryBuffer[i] = '\0';
}
}
215
CameraHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef CAMERAHANDLER_H
#define CAMERAHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include "FileManager.h"
#include "SocketManager.h"
// Constants:
#define CAMERA_BUFFER_SIZE 5
#define CAMERA_DIRECTORY "/tmp/idroid02/camera"
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_CAMERA_SETTINGS 'G'
#define COMMAND_SET_CAMERA_SETTINGS 'S'
#define COMMAND_START_STREAMING 'R'
#define COMMAND_TAKE_PICTURE 'P'
#define COMMAND_TAKE_VIDEO 'V'
#define H264_STRING "H264"
#define H264 4
#define JPEG_STRING "JPEG"
#define JPEG 3
#define MULTIMEDIA_FILE_PATH "/tmp/idroid02/CameraTempFile"
// Global variables:
char cameraHandlerAlive; // Flag to check if thread is alive.
int fps; // Frames per second.
int heightResolution; // Number of height pixels.
char pixelformat; // Camera output format (3 for JPEG (pictures) and
4 for H.264 (video)).
int widthResolution; // Number of width pixels.
// Functions:
void* cameraHandler_Start(void*); // Handler start function.
char* getStreamVideoCommand(); // Function to get appropriate
command to stream video through socket.
void setCameraSettings(); // Function to set camera settings through
V4L Driver.
void takePicture(); // Function to take a picture with current
camera settings.
void takeVideo(char); // Function to capture a video with current
camera settings.
void updateCameraSettings(); // Function to call V4L Driver and
retrieve current camera parameters.
#ifdef __cplusplus
}
#endif
216
#endif /* CAMERAHANDLER_H */
CameraHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* CameraHandler: This thread manages the communication between IDroid02 Driver
* and a high-level application which requests the use of the camera.
This
* thread remains slept if any application requests the camera sensor.
*/
#include "CameraHandler.h"
void* cameraHandler_Start(void* arg) {
unsigned char cameraSendBuffer[CAMERA_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
unsigned char cameraReceiveBuffer[CAMERA_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
int cameraFD, cameraFD2; // Socket File Descriptors.
struct sockaddr_un cameraSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
FILE* multimediaFile; // File descriptor of picture temporal file.
long multimediaSize; // Size of multimedia.
struct stat sb;
char* realtimeCommand; // Command used for real-time video
streaming.
char endCommand; // Flag to determine when application ends the
real-time streaming.
char videoDuration; // Length of a video captured in seconds.
long i;
cameraHandlerAlive = 1;
// Update global variables with current settings:
updateCameraSettings();
// Open and setup server socket:
cameraFD = openSocket();
cameraSocketStruct = setupSocket(cameraFD, CAMERA_DIRECTORY, 1);
while (1) {
// Wait for a high-level application to connect:
cameraFD2 = acceptConnection(cameraSocketStruct, cameraFD);
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(cameraFD2, cameraReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
217
// Switch received command and execute it:
switch (cameraReceiveBuffer[0]) {
case COMMAND_CLOSE_CONNECTION:
closeConnectionFlag = 1; // High-level application
wants to close connection.
break;
case COMMAND_GET_CAMERA_SETTINGS:
// The application wants to know current camera
settings:
// Update global variables with current settings:
updateCameraSettings();
// Send data through socket:
memcpy(cameraSendBuffer, &widthResolution, sizeof
(int));
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(int)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
memcpy(cameraSendBuffer, &heightResolution, sizeof
(int));
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(int)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
memcpy(cameraSendBuffer, &fps, sizeof (int));
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(int)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
cameraSendBuffer[0] = pixelformat;
if (sendData(cameraFD2, cameraSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_SET_CAMERA_SETTINGS:
// The application wants to change current camera
settings:
// Receive values from application:
if (receiveData(cameraFD2, cameraReceiveBuffer,
sizeof (int)) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
memcpy(&widthResolution, cameraReceiveBuffer, sizeof
(int));
if (receiveData(cameraFD2, cameraReceiveBuffer,
sizeof (int)) < 0) {
// Error receiving data.
218
closeConnectionFlag = 1;
break;
}
memcpy(&heightResolution, cameraReceiveBuffer,
sizeof (int));
if (receiveData(cameraFD2, cameraReceiveBuffer,
sizeof (int)) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
memcpy(&fps, cameraReceiveBuffer, sizeof (int));
if (receiveData(cameraFD2, cameraReceiveBuffer, 1) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
pixelformat = cameraReceiveBuffer[0];
setCameraSettings();
break;
case COMMAND_START_STREAMING:
// The application wants to receive a real-time H264
TS video stream:
realtimeCommand = getStreamVideoCommand(); // Get
custom command.
multimediaFile = popen(realtimeCommand, "r");
if (multimediaFile == NULL) {
printf("Failed to start streaming\n");
break;
}
// Start streaming:
endCommand = 0;
do {
fgets(cameraSendBuffer,
sizeof(cameraSendBuffer[0]), multimediaFile); // Read a Byte.
if (sendData(cameraFD2, cameraSendBuffer, 1) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
// React to application answer:
if (receiveData(cameraFD2, cameraReceiveBuffer,
1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
if (cameraReceiveBuffer[0] ==
COMMAND_START_STREAMING) {
// The application wants to end streaming:
endCommand = 1;
}
} while (endCommand == 0);
219
pclose(multimediaFile);
break;
case COMMAND_TAKE_PICTURE:
// The application wants to take a picture:
if (pixelformat == JPEG) {
// Camera is setup to take pictures.
// Take picture:
takePicture();
// Read the picture file and send it through
socket:
multimediaFile = openFile(MULTIMEDIA_FILE_PATH,
"r");
stat(MULTIMEDIA_FILE_PATH, &sb); // Get file
size.
multimediaSize = (long) sb.st_size;
memcpy(cameraSendBuffer, &multimediaSize, sizeof
(long));
// Send picture file length:
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(long)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
for (i=0; i<multimediaSize; i++) {
fread(cameraSendBuffer, sizeof(char), 1,
multimediaFile); // Read 1 Byte from file.
// Send picture file:
if (sendData(cameraFD2, cameraSendBuffer, 1)
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
}
// Close and remove temporal file:
closeFile(multimediaFile);
remove(MULTIMEDIA_FILE_PATH); // Remove temporal
file.
} else {
// Camera is not setup to take pictures.
multimediaSize = 0;
memcpy(cameraSendBuffer, &multimediaSize, sizeof
(long));
// Send 0 length to indicate that no picture
will be sent:
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(long)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
}
break;
case COMMAND_TAKE_VIDEO:
// The application wants to take a video:
220
// Obtain video duration from application:
if (receiveData(cameraFD2, cameraReceiveBuffer, 1) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
videoDuration = cameraReceiveBuffer[0];
if (videoDuration < 0) {
videoDuration = 5;
}
if (pixelformat == H264) {
// Camera is setup to take videos.
// Take video:
takeVideo(videoDuration);
// Read the picture file and send it through
socket:
multimediaFile = openFile(MULTIMEDIA_FILE_PATH,
"r");
stat(MULTIMEDIA_FILE_PATH, &sb); // Get file
size.
multimediaSize = (long) sb.st_size;
memcpy(cameraSendBuffer, &multimediaSize, sizeof
(long));
// Send video file length:
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(long)) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
for (i=0; i<multimediaSize; i++) {
fread(cameraSendBuffer, sizeof(char), 1,
multimediaFile); // Read 1 Byte from file.
// Send video file:
if (sendData(cameraFD2, cameraSendBuffer, 1)
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
}
// Close and remove temporal file:
closeFile(multimediaFile);
remove(MULTIMEDIA_FILE_PATH); // Remove temporal
file.
} else {
// Camera is not setup to take videos.
multimediaSize = 0;
memcpy(cameraSendBuffer, &multimediaSize, sizeof
(long));
// Send 0 length to indicate that no video will
be sent:
if (sendData(cameraFD2, cameraSendBuffer, sizeof
(long)) < 0) {
// Error sending data.
221
closeConnectionFlag = 1;
break;
}
}
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(cameraFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(cameraFD, CAMERA_DIRECTORY);
cameraHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
char* getStreamVideoCommand() {
// Returns: System command to start streaming.
char* widthResolutionString;
char* heightResolutionString;
char* fpsString;
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
// Parse video values to string:
widthResolutionString = malloc(2);
sprintf(widthResolutionString, "%d", (int) widthResolution);
heightResolutionString = malloc(2);
sprintf(heightResolutionString, "%d", (int) heightResolution);
fpsString = malloc(2);
sprintf(fpsString, "%d", (int) fps);
// Create raspivid command line:
totalSize = 12 + strlen(widthResolutionString) + 4 +
strlen(heightResolutionString) + 16 + strlen(fpsString) + 35 + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "raspivid -w ", totalSize);
strncat(command, widthResolutionString, totalSize);
strncat(command, " -h ", totalSize);
strncat(command, heightResolutionString, totalSize);
strncat(command, " -o - -t 0 -fps ", totalSize);
strncat(command, fpsString, totalSize);
strncat(command, " -pf high -fl -n -ex auto -awb auto", totalSize);
return command;
}
void setCameraSettings() {
char* widthResolutionString;
char* heightResolutionString;
char* fpsString;
char* pixelformatString;
222
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
// Parse input values to string:
widthResolutionString = malloc(2);
sprintf(widthResolutionString, "%d", (int) widthResolution);
heightResolutionString = malloc(2);
sprintf(heightResolutionString, "%d", (int) heightResolution);
fpsString = malloc(2);
sprintf(fpsString, "%d", (int) fps);
pixelformatString = malloc(2);
sprintf(pixelformatString, "%d", (int) pixelformat);
// Call set-fmt-video command:
totalSize = 31 + strlen(widthResolutionString) + 8 +
strlen(heightResolutionString) + 13 + strlen(pixelformatString) + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "v4l2-ctl --set-fmt-video=width=", totalSize);
strncat(command, widthResolutionString, totalSize);
strncat(command, ",height=", totalSize);
strncat(command, heightResolutionString, totalSize);
strncat(command, ",pixelformat=", totalSize);
strncat(command, pixelformatString, totalSize);
system(command); // OS call.
// Call --set-parm command for FPS setting:
totalSize = 20 + strlen(fpsString) + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "v4l2-ctl --set-parm=", totalSize);
strncat(command, fpsString, totalSize);
system(command); // OS call.
}
void takePicture() {
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
// Take a picture using v4l2-ctl command:
totalSize = 54 + strlen(MULTIMEDIA_FILE_PATH) + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "v4l2-ctl --stream-mmap=3 --stream-count=1 -stream-to=", totalSize);
strncat(command, MULTIMEDIA_FILE_PATH, totalSize);
system(command); // OS call.
}
void takeVideo(char duration) {
// duration: Number of seconds to capture.
char* framesString;
int frames; // Number of frames to capture.
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
// Obtain frames from duration and parse it to string:
frames = duration * fps;
framesString = malloc(2);
sprintf(framesString, "%d", (int) frames);
// Take a video using v4l2-ctl command:
223
totalSize = 40 + strlen(framesString) + 13 +
strlen(MULTIMEDIA_FILE_PATH) + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "v4l2-ctl --stream-mmap=3 --stream-count=",
totalSize);
strncat(command, framesString, totalSize);
strncat(command, " --stream-to=", totalSize);
strncat(command, MULTIMEDIA_FILE_PATH, totalSize);
system(command); // OS call.
}
void updateCameraSettings() {
FILE* command; // File Descriptor for commands.
char commandLine[100]; // Temporal string to read lines.
char command1[400]; // "v4l2-ctl --get-fmt-video" command execution.
char command2[250]; // "v4l2-ctl --get-parm" command execution.
char* finder; // Pointer used to find substrings.
// Read 1st command:
command = popen("v4l2-ctl --get-fmt-video", "r");
if (command == NULL) {
printf("Failed to run v4l2-ctl --get-fmt-video command\n");
return;
}
fgets(command1, sizeof(command1) - 1, command);
while (fgets(commandLine, sizeof(commandLine) - 1, command) != NULL)
{
strncat(command1, commandLine, sizeof(commandLine) - 1); //
Concatenate lines.
}
pclose(command);
// Read 2nd command:
command = popen("v4l2-ctl --get-parm", "r");
if (command == NULL) {
printf("Failed to run v4l2-ctl --get-parm command\n");
return;
}
fgets(command2, sizeof(command2) - 1, command);
while (fgets(commandLine, sizeof(commandLine) - 1, command) != NULL)
{
strncat(command2, commandLine, sizeof(commandLine) - 1); //
Concatenate lines.
}
pclose(command);
// Get video format:
finder = (strstr(command1, "Pixel Format : '") + strlen("Pixel
Format : '"));
finder = strtok(finder, "'"); // Extract value.
if (strcmp(finder, JPEG_STRING) == 0) {
pixelformat = JPEG;
} else if (strcmp(finder, H264_STRING) == 0) {
pixelformat = H264;
} else {
// Video as default state:
pixelformat = H264;
}
// Get FPS value:
224
finder = (strstr(command2, "Frames per second: ") + strlen("Frames
per second: "));
finder = strtok(finder, " "); // Extract value.
fps = atoi(finder);
// Get height resolution:
finder = (strstr(command1, "Width/Height : ") +
strlen("Width/Height : "));
finder = strtok(finder, "/"); // Extract resolutions line.
finder = strtok(NULL, "\n"); // Extract value.
heightResolution = atoi(finder);
// Get width resolution:
finder = (strstr(command1, "Width/Height : ") +
strlen("Width/Height : "));
finder = strtok(finder, "/"); // Extract value.
widthResolution = atoi(finder);
}
ChestButtonsHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef CHESTBUTTONSHANDLER_H
#define CHESTBUTTONSHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <stdlib.h>
#include "ArduinoPeripheral.h"
#include "SocketManager.h"
// Constants:
#define CHEST_BUTTONS_BUFFER_SIZE 1
#define CHEST_BUTTONS_DIRECTORY "/tmp/idroid02/chestButtons"
#define COMMAND_ATTACH_SENSOR 'A'
#define COMMAND_CLOSE_CONNECTION 'C'
// Global variables:
char chestButtonsActiveConnection; // Flag to inform if a client is
connected.
char chestButtonsHandlerAlive; // Flag to check if thread is alive.
pthread_mutex_t chestButtonsMutex;
pthread_cond_t chestButtonsCondition;
// Functions:
void ackChestButtons(); // Function called when high-level
application acknowledges pressed buttons.
void* chestButtonsHandler_Start(void*); // Handler start function.
#ifdef __cplusplus
}
225
#endif
#endif /* CHESTBUTTONSHANDLER_H */
ChestButtonsHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* ChestButtonsHandler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of the
chest
* buttons. This thread remains slept if any application requests the
sensor.
* When an application requests this thread, it also gets blocked until
* ArduinoHandler thread notifies the reception of a pressed button.
*/
#include "ChestButtonsHandler.h"
void ackChestButtons() {
// Reset value:
chestButtonsFlag = 0;
}
void* chestButtonsHandler_Start(void* arg) {
unsigned char buttonsSocketSendBuffer[CHEST_BUTTONS_BUFFER_SIZE]; //
Buffer of Bytes to send through socket.
unsigned char buttonsSocketReceiveBuffer[CHEST_BUTTONS_BUFFER_SIZE];
// Buffer of Bytes to send through socket.
int chestButtonsFD, chestButtonsFD2; // Socket File Descriptors.
struct sockaddr_un chestButtonsSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
chestButtonsHandlerAlive = 1;
pthread_mutex_init(&chestButtonsMutex, NULL);
pthread_cond_init(&chestButtonsCondition, NULL);
// Open and setup server socket:
chestButtonsFD = openSocket();
chestButtonsSocketStruct = setupSocket(chestButtonsFD,
CHEST_BUTTONS_DIRECTORY, 1);
while (1) {
// Wait for a high-level application to connect:
chestButtonsFD2 = acceptConnection(chestButtonsSocketStruct,
chestButtonsFD);
chestButtonsActiveConnection = 1;
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(chestButtonsFD2, buttonsSocketReceiveBuffer,
1) < 0) {
// Error receiving data.
226
closeConnectionFlag = 1;
break;
}
// Switch received command and execute it:
switch (buttonsSocketReceiveBuffer[0]) {
case COMMAND_ATTACH_SENSOR:
// The application wants to wait until any of the
buttons is pressed:
// Wait for a change of chestButtonsFlag value:
pthread_mutex_lock(&chestButtonsMutex);
while (chestButtonsFlag == 0) {
// Block thread until signal is received and
free mutex:
pthread_cond_wait(&chestButtonsCondition,
&chestButtonsMutex); // Blocking function.
}
pthread_mutex_unlock(&chestButtonsMutex);
// Send buttonsFlag to high-level application:
buttonsSocketSendBuffer[0] = chestButtonsFlag;
if (sendData(chestButtonsFD2,
buttonsSocketSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
ackChestButtons();
pthread_mutex_unlock(&chestButtonsMutex);
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(chestButtonsFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
chestButtonsActiveConnection = 0;
}
// Close socket:
closeSocket(chestButtonsFD, CHEST_BUTTONS_DIRECTORY);
chestButtonsHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
227
DisplayHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef DISPLAYHANDLER_H
#define DISPLAYHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include <string.h>
#include "ArduinoPeripheral.h"
#include "SocketManager.h"
// Constants:
#define COMMAND_CLEAR_DISPLAY 'D'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_CURRENT_DISPLAY_DATA 'G'
#define COMMAND_MOVE_CURSOR_WRITE_DISPLAY 'M'
#define COMMAND_RESET_DISPLAY 'R'
#define COMMAND_SET_TEXTS 'T'
#define COMMAND_SET_TEXT_ROW0 0x00
#define COMMAND_SET_TEXT_ROW1 0x01
#define COMMAND_SETUP_DISPLAY 'S'
#define DISPLAY_BUFFER_SIZE 40
#define DISPLAY_DIRECTORY "/tmp/idroid02/display"
// Global variables:
char displayHandlerAlive; // Flag to check if thread is alive.
// Functions:
void* displayHandler_Start(void*); // Handler start function.
void sendDisplayClearCommand(); // Function to tell Arduino to clear
display.
void sendDisplaySetupCommand(); // Function to setup display
settings.
void sendDisplayWriteCommand(char); // Write texts of row 1 and 2 to
display.
#ifdef __cplusplus
}
#endif
#endif /* DISPLAYHANDLER_H */
DisplayHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#include "DisplayHandler.h"
228
/* DisplayHandler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of the
display.
* This thread remains slept if any application requests its use.
*/
void* displayHandler_Start(void* arg) {
unsigned char displaySendBuffer[DISPLAY_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
unsigned char displayReceiveBuffer[DISPLAY_BUFFER_SIZE]; // Buffer
of Bytes to receive through socket.
int displayFD, displayFD2; // Socket File Descriptors.
struct sockaddr_un displaySocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
char getDisplayDataCommandLength; // Total number of Bytes sent in
GET_CURRENT_DISPLAY_DATA command.
char setTextEndCommand; // Flag to end the SET_TEXTS command.
int i;
displayHandlerAlive = 1;
// Open and setup server socket:
displayFD = openSocket();
displaySocketStruct = setupSocket(displayFD, DISPLAY_DIRECTORY, 1);
resetDisplayData(); // Reset all display settings to its default
state.
while (1) {
// Wait for a high-level application to connect:
displayFD2 = acceptConnection(displaySocketStruct, displayFD);
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(displayFD2, displayReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Switch received command and execute it:
switch (displayReceiveBuffer[0]) {
case COMMAND_CLEAR_DISPLAY:
// The application wants to clear display:
// Change display data:
displayData.column = 0;
displayData.row = 0;
// Send Arduino command:
sendDisplayClearCommand();
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
case COMMAND_GET_CURRENT_DISPLAY_DATA:
229
// The application wants to know current display
state:
// Prepare settings, cursor position and text
lengths information:
displaySendBuffer[0] = displayData.contrast;
displaySendBuffer[1] = displayData.settings;
displaySendBuffer[2] = (displayData.column << 4) +
displayData.row;
displaySendBuffer[3] = displayData.textRow0Length;
displaySendBuffer[4] = displayData.textRow1Length;
getDisplayDataCommandLength = 5;
// Insert row 1 and row 2 text in the command:
for (i = 0; i < displayData.textRow0Length; i++) {
displaySendBuffer[getDisplayDataCommandLength +
i] = displayData.textRow0[i];
}
getDisplayDataCommandLength =
getDisplayDataCommandLength + displayData.textRow0Length;
for (i = 0; i < displayData.textRow1Length; i++) {
displaySendBuffer[getDisplayDataCommandLength +
i] = displayData.textRow1[i];
}
getDisplayDataCommandLength =
getDisplayDataCommandLength + displayData.textRow1Length;
// Send command through socket:
if (sendData(displayFD2, displaySendBuffer,
getDisplayDataCommandLength) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_MOVE_CURSOR_WRITE_DISPLAY:
// The application wants to set the cursor's
position. If text has been previously defined, it will be written
automatically:
// Read cursor position:
if (receiveData(displayFD2, displayReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Store cursor position:
displayData.column = ((displayReceiveBuffer[0] &
0xF0) >> 4);
displayData.row = (displayReceiveBuffer[0] & 0x01);
// Send Arduino command (determine whether is a move
cursor command or a write text command):
if (((displayData.row == 0) &&
(displayData.textRow0Length != 0)) || ((displayData.row == 1) &&
(displayData.textRow1Length != 0))) {
// Text has been previously defined.
sendDisplayWriteCommand(1);
230
} else {
sendDisplayWriteCommand(0);
}
break;
case COMMAND_RESET_DISPLAY:
// The application wants to clear display and set
default settings:
resetDisplayData();
// Send Arduino commands:
sendDisplayClearCommand();
sendDisplaySetupCommand();
break;
case COMMAND_SET_TEXTS:
// The application wants to set texts for both
display rows:
// Know which row the text is wanted to be
added/modified:
setTextEndCommand = 0;
do {
if (receiveData(displayFD2,
displayReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
switch (displayReceiveBuffer[0]) {
case COMMAND_SET_TEXT_ROW0:
// Set row 0 text:
if (receiveData(displayFD2,
displayReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
displayData.textRow0Length =
displayReceiveBuffer[0];
// Only read text if text length is
different than 0. Otherwise, remove current text set:
if (displayData.textRow0Length != 0) {
// Read text Bytes according to its
length:
if (receiveData(displayFD2,
displayReceiveBuffer, displayData.textRow0Length) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
strncpy(displayData.textRow0,
displayReceiveBuffer, displayData.textRow0Length);
// Solve a bug that fails when a
text of less than 5 characters is printed:
if ((displayData.textRow0Length > 0)
&& (displayData.textRow0Length < 5)) {
for (i =
displayData.textRow0Length; i < 5; i++) {
231
// Full with spaces the text
until reach 5 characters:
displayData.textRow0[i] = '
';
}
displayData.textRow0Length = 5;
}
} else {
strncpy(displayData.textRow0, "",
0);
}
break;
case COMMAND_SET_TEXT_ROW1:
// Set row 1 text:
if (receiveData(displayFD2,
displayReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
displayData.textRow1Length =
displayReceiveBuffer[0];
// Only read text if text length is
different than 0. Otherwise, remove current text set:
if (displayData.textRow1Length != 0) {
// Read text Bytes according to its
length:
if (receiveData(displayFD2,
displayReceiveBuffer, displayData.textRow1Length) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
strncpy(displayData.textRow1,
displayReceiveBuffer, displayData.textRow1Length);
// Solve a bug that fails when a
text of less than 5 characters is printed:
if ((displayData.textRow1Length > 0)
&& (displayData.textRow1Length < 5)) {
for (i =
displayData.textRow1Length; i < 5; i++) {
// Full with spaces the text
until reach 5 characters:
displayData.textRow1[i] = '
';
}
displayData.textRow1Length = 5;
}
} else {
strncpy(displayData.textRow1, "",
0);
}
break;
case COMMAND_SET_TEXTS:
// Use the command letter at the end
marks the end of the command:
setTextEndCommand = 1;
break;
232
}
} while (setTextEndCommand == 0);
break;
case COMMAND_SETUP_DISPLAY:
// The application wants to setup main display
settings:
// Read settings:
if (receiveData(displayFD2, displayReceiveBuffer, 2)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Store settings:
if ((displayReceiveBuffer[0] >= 0) &&
(displayReceiveBuffer[0] <= 100)) {
// Contrast value sent is valid.
displayData.contrast = displayReceiveBuffer[0];
} else {
// Contrast value sent is invalid. Set value
will be the default one:
displayData.contrast = 50;
}
displayData.settings = (displayReceiveBuffer[1] &
0x0F);
// Send Arduino command:
sendDisplaySetupCommand();
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(displayFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(displayFD, DISPLAY_DIRECTORY);
displayHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void sendDisplayClearCommand() {
char clearDisplayCommand[3];
clearDisplayCommand[0] = COMMAND_DISPLAY;
clearDisplayCommand[1] = COMMAND_DISPLAY_CLEAR;
clearDisplayCommand[2] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(clearDisplayCommand, 3);
}
void sendDisplaySetupCommand() {
233
char setupDisplayCommand[5];
setupDisplayCommand[0] = COMMAND_DISPLAY;
setupDisplayCommand[1] = COMMAND_DISPLAY_SETUP;
setupDisplayCommand[2] = 100 - displayData.contrast; // Invert value
setupDisplayCommand[3] = displayData.settings;
setupDisplayCommand[4] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(setupDisplayCommand, 5);
}
void sendDisplayWriteCommand(char moveWrite) {
// moveWrite: If 1, the command is used to write a text. Otherwise,
the command is used only to move cursor.
char temporalBuffer[DISPLAY_ROW_MAX_CHARACTERS];
unsigned char writeRowCommand[21];
int i, textLength;
// Common data:
writeRowCommand[0] = COMMAND_DISPLAY;
writeRowCommand[1] = COMMAND_DISPLAY_MOVE_OR_WRITE;
writeRowCommand[2] = (displayData.column << 4) + displayData.row;
// Text to write:
textLength = 0;
if (moveWrite == 1) {
switch (displayData.row) {
case 0:
// Write row 0 to display:
textLength = displayData.textRow0Length;
memcpy(temporalBuffer, displayData.textRow0,
textLength);
break;
case 1:
// Write row 1 to display:
textLength = displayData.textRow1Length;
memcpy(temporalBuffer, displayData.textRow1,
textLength);
break;
}
}
// Add text to command (if any):
writeRowCommand[3] = textLength;
for (i = 0; i < textLength; i++) {
writeRowCommand[4 + i] = temporalBuffer[i];
}
// Add COMMAND_END_OF_TRANSMISSION:
writeRowCommand[4 + textLength] = COMMAND_END_OF_TRANSMISSION;
// Send command to Arduino:
sendCommandToArduino(writeRowCommand, 5 + textLength);
}
FileManager.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
234
*/
#ifndef FILEMANAGER_H
#define FILEMANAGER_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <unistd.h>
// Constants:
#define ECHO_NULL "/dev/null"
#define EVENT_BUFFER_LENGTH (1024*(sizeof(struct inotify_event) +16))
#define READ "r"
#define WRITE "w"
// Global variables:
char watchBuffer[EVENT_BUFFER_LENGTH];
// Functions:
void closeFile(FILE*); // Function to close a file.
void echoLineToFile(char*, char*); // Function to echo a line to a
file not opened.
int inotifyInstance(); // Creation of a inotify instance for the
selected file.
int inotifyInstanceAddWatch(int, char*); // Add an inotify watch for
an inotify instance.
void inotifyInstanceRemoveWatch(int, int); // Remove an inotify
watch for an inotify instance.
FILE* openFile(char*, char*); // Function to open a file.
int readLineFromFile(FILE*, char**, int); // Function to read a line
from a file.
void watchFileForChanges(int); // Function to watch whether a file
is modified externally.
int writeLineToFile(FILE*, char*); // Function to write a line to a
file.
#ifdef __cplusplus
}
#endif
#endif /* FILEMANAGER_H */
FileManager.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* FileManager. The functions defined in this file are used to manage
every
235
* file used to broadcast data.
*/
#include "FileManager.h"
void closeFile(FILE* fd) {
// fd: Pointer that represents the file.
fclose(fd);
}
void echoLineToFile(char* name, char* line) {
// name: Route and name of the file not opened.
// line: Line to be written to the file.
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
// Length of all strings to concatenate plus \0:
totalSize = 11 + strlen(line) + 4 + strlen(name) + 1;
// Command concatenation:
command = malloc(totalSize); // Memory allocation.
strncpy(command, "printf -- \"", totalSize);
strncat(command, line, totalSize);
strncat(command, "\" > ", totalSize);
strncat(command, name, totalSize);
system(command); // OS call.
}
int inotifyInstance() {
// Returns: The File Descriptor of the inotify object.
int fd; // inotify File Descriptor.
// inotify instance creation:
fd = inotify_init();
if (fd < 0) {
perror("inotify_init error");
}
return fd;
}
int inotifyInstanceAddWatch(int fd, char* name) {
// fd: Inotify file descriptor.
// name: Route and name of the file to be watched.
// Returns: The Watch Descriptor of the inotify object.
int wd;
// Watch the file for change events:
wd = inotify_add_watch(fd, name, IN_CLOSE_WRITE);
return wd;
}
void inotifyInstanceRemoveWatch(int fd, int wd) {
// fd: Inotify file descriptor.
// wd: Inotify watch descriptor.
inotify_rm_watch(fd, wd);
236
}
FILE* openFile(char* name, char* mode) {
// name: Route and name of the file.
// mode: Opening mode (read "r", write "w", append "a", etc).
// Returns: FILE pointer representing the opened file.
FILE* fd;
fd = fopen(name, mode);
if (fd == NULL) {
perror("Error opening the file");
}
return fd;
}
int readLineFromFile(FILE* fd, char** line, int lineSize) {
// fd: Pointer that represents the file.
// line: Pointer to a string chain to store line read from the file.
// lineSize: Number of characters to be read in a line.
// Returns: -1 if an error occurs. Otherwise returns 0.
char temporal[lineSize]; // Temporal buffer to store all read
characters.
char test;
int i;
// Try to read a character:
test = fgetc(fd);
if (test == EOF) {
// Error reading line.
return -1;
}
fseek(fd, -1, SEEK_CUR); // Rewind file pointer one position.
// Read lineSize characters or less if \n or EOF are detected:
for (i = 0; i < lineSize; i++) {
temporal[i] = fgetc(fd);
if ((temporal[i] == '\n') || (temporal[i] == EOF)) {
// The line has ended.
i--; // Do not consider this character as part of the string
chain.
break;
}
}
*line = malloc(i + 1); // Allocate memory only to store actually
read characters.
strncpy(*line, temporal, i + 1);
return 0;
}
void watchFileForChanges(int fd) {
// fd: inotify File Descriptor.
read(fd, watchBuffer, EVENT_BUFFER_LENGTH); // Blocking function.
}
int writeLineToFile(FILE* fd, char* line) {
// fd: Pointer that represents the file.
// line: Line to be written to the file.
237
// Returns: -1 if fputs() crashes. Otherwise returns 0.
if (fputs(line, fd) != EOF) {
// Writing line OK.
return 0;
} else {
// Error writing line.
return -1;
}
}
GPIOsHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef GPIOSHANDLER_H
#define GPIOSHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<string.h>
<sys/stat.h>
<wiringPi.h>
#include "SocketManager.h"
// Constants:
#define COMMAND_ATTACH_INTERRUPT 'A'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_GPIO_STATUS 'G'
#define COMMAND_READ_GPIO 'V'
#define COMMAND_RESET_GPIOS 'R'
#define COMMAND_SET_GPIO_DIRECTION 'D'
#define COMMAND_UNEXPORT_GPIO 'U'
#define COMMAND_WAIT_FOR_AN_INTERRUPT 'X'
#define COMMAND_WRITE_GPIO 'W'
#define GPIO12 12
#define GPIO13 13
#define GPIO16 16
#define GPIO19 19
#define GPIO20 20
#define GPIO21 21
#define GPIO26 26
#define GPIOS_BUFFER_SIZE 25
#define GPIOS_DIRECTORY "/tmp/idroid02/gpios"
// Global variables:
char GPIOsActiveConnection; // Flag to inform if a client is
connected.
char GPIOsHandlerAlive; // Flag to check if thread is alive.
// Both used to sync any GPIO interruption with socket link with
high-level application:
238
pthread_mutex_t gpioMutex;
pthread_cond_t gpioCondition;
/* Flag to indicate which GPIO has triggered the interruption and
the value:
* Flag format: 0xx12x13x16x19x20x21x26v.
* Each GPIO corresponding bit is set when is responsible to trigger
an interruption. v stores the value immediately read after an
interruption has occurred.
*/
char gpiosISR;
/* Matrix to store all 7 GPIO information in columns:
*
- 1st column: GPIO number according to I-Droid02 label.
*
- 2nd column (4 lowest bits): GPIO direction: 0 -> Input. 1 ->
Output. 2 -> Unset (default).
*
- 2nd column (4 highest bits): GPIO kind of attached interrupt:
Same value as wiringPi except 0 (default) that means no interrupt
attached.
*
- 3rd column: GPIO current value (0 also for unset GPIO).
*/
char GPIOs_Status[7][3];
// Functions:
int getGPIOIndex(unsigned char); // Routine to obtain GPIOs_Status
first array index directly from GPIO's number.
void* GPIOsHandler_Start(void*); // Handler start function.
void GPIO_AttachInterrupt(unsigned char, unsigned char); // Function
to attach an interrupt to any GPIO.
unsigned char GPIO_Read(unsigned char); // Function to read the
value of a GPIO.
void GPIO_SetDirection(unsigned char, unsigned char); // Function to
set the direction of any GPIO.
void GPIO_Unexport(unsigned char); // Function to unexport any GPIO.
void GPIO_Write(unsigned char, unsigned char); // Function to write
a value to any GPIO.
void ISR_GPIO12(); // ISR for GPIO12.
void ISR_GPIO13(); // ISR for GPIO13.
void ISR_GPIO16(); // ISR for GPIO16.
void ISR_GPIO19(); // ISR for GPIO19.
void ISR_GPIO20(); // ISR for GPIO20.
void ISR_GPIO21(); // ISR for GPIO21.
void ISR_GPIO26(); // ISR for GPIO26.
void resetGPIOs(); // Function to unexport all GPIOs.
#ifdef __cplusplus
}
#endif
#endif /* GPIOSHANDLER_H */
GPIOsHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* GPIOsHandler: This thread manages the communication between I-Droid02
* Driver and a high-level application which requests the use of
Raspberry Pi
239
* 2 B GPIOs that are accessible from battery box prototype board. This
thread
* remains slept if any application requests the use of any GPIO.
*/
#include "GPIOsHandler.h"
int getGPIOIndex(unsigned char gpio) {
// gpio: GPIO number.
// Returns: The first array index to be used in GPIOs_Status.
if (gpio == GPIO12) {
return 0;
} else if (gpio == GPIO13)
return 1;
} else if (gpio == GPIO16)
return 2;
} else if (gpio == GPIO19)
return 3;
} else if (gpio == GPIO20)
return 4;
} else if (gpio == GPIO21)
return 5;
} else if (gpio == GPIO26)
return 6;
} else {
return -1;
}
{
{
{
{
{
{
}
void* GPIOsHandler_Start(void* arg) {
unsigned char GPIOsSendBuffer[GPIOS_BUFFER_SIZE]; // Buffer of Bytes
to send through socket.
unsigned char GPIOsReceiveBuffer[GPIOS_BUFFER_SIZE]; // Buffer of
Bytes to receive through socket.
int GPIOsFD, GPIOsFD2; // Socket File Descriptors.
struct sockaddr_un GPIOsSocketStruct;
char GPIO_Value; // The current GPIO number that has been received.
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
char endCommand; // Flag used to know when
COMMAND_SET_GPIO_DIRECTION ends.
int i;
GPIOsHandlerAlive = 1;
gpiosISR = 0;
pthread_mutex_init(&gpioMutex, NULL);
pthread_cond_init(&gpioCondition, NULL);
// Open and setup server socket:
GPIOsFD = openSocket();
GPIOsSocketStruct = setupSocket(GPIOsFD, GPIOS_DIRECTORY, 1);
// Init GPIOs_Status
GPIOs_Status[0][0] =
GPIOs_Status[1][0] =
GPIOs_Status[2][0] =
GPIOs_Status[3][0] =
GPIOs_Status[4][0] =
GPIOs_Status[5][0] =
matrix:
GPIO12;
GPIO13;
GPIO16;
GPIO19;
GPIO20;
GPIO21;
240
GPIOs_Status[6][0] = GPIO26;
resetGPIOs();
while (1) {
// Wait for a high-level application to connect:
GPIOsFD2 = acceptConnection(GPIOsSocketStruct, GPIOsFD);
GPIOsActiveConnection = 1;
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (GPIOsReceiveBuffer[0]) {
case COMMAND_ATTACH_INTERRUPT:
// The application wants to be attached to some
GPIOs interrupts:
// Know which group of GPIOs an interruption is
wanted to be attached:
endCommand = 0;
do {
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
GPIO_Value = GPIOsReceiveBuffer[0];
receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1); //
Read kind of attached interrupts (same as wiringPi).
switch (GPIO_Value) {
case GPIO12:
case GPIO13:
case GPIO16:
case GPIO19:
case GPIO20:
case GPIO21:
case GPIO26:
// An interruption can only be set in
these GPIOs; any other will be ignored.
GPIO_AttachInterrupt(GPIO_Value,
(GPIOsReceiveBuffer[0] & 0x03));
break;
case COMMAND_ATTACH_INTERRUPT:
// Use the command letter at the end
marks the end of the command:
endCommand = 1;
break;
}
} while (endCommand == 0);
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
241
case COMMAND_GET_GPIO_STATUS:
// The application wants to know the current status
of all GPIOs:
// Send GPIOs_Status in series through socket
following columns order:
for (i = 0; i < 21; i = i + 3) {
GPIOsSendBuffer[i] = GPIOs_Status[i][0]; // GPIO
number.
GPIOsSendBuffer[i + 1] = GPIOs_Status[i][1]; //
GPIO status.
GPIOsSendBuffer[i + 2] = GPIOs_Status[i][2]; //
GPIO value.
}
if (sendData(GPIOsFD2, GPIOsSendBuffer, 21) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_READ_GPIO:
// The application wants to read the current status
of a GPIO set as input:
// Know which group of GPIOs is wanted to be read:
endCommand = 0;
do {
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
GPIO_Value = GPIOsReceiveBuffer[0];
switch (GPIO_Value) {
case GPIO12:
case GPIO13:
case GPIO16:
case GPIO19:
case GPIO20:
case GPIO21:
case GPIO26:
// Only these GPIOs can be read; any
other will be ignored.
GPIOsSendBuffer[0] =
GPIO_Read(GPIO_Value);
if (sendData(GPIOsFD2, GPIOsSendBuffer,
1) < 0) {
// Error sendng data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_READ_GPIO:
// Use the command letter at the end
marks the end of the command:
endCommand = 1;
break;
default:
242
// Invalid GPIO number:
GPIOsSendBuffer[0] = 0xFF;
if (sendData(GPIOsFD2, GPIOsSendBuffer,
1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
}
} while (endCommand == 0);
break;
case COMMAND_RESET_GPIOS:
// The application wants to reset all GPIO (unexport
all):
resetGPIOs();
break;
case COMMAND_SET_GPIO_DIRECTION:
// The application wants to set a GPIO as an input
or output:
// Know which group of GPIOs is wanted to be set:
endCommand = 0;
do {
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
GPIO_Value = GPIOsReceiveBuffer[0];
// Read direction:
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
switch (GPIO_Value) {
case GPIO12:
case GPIO13:
case GPIO16:
case GPIO19:
case GPIO20:
case GPIO21:
case GPIO26:
// Only these GPIOs can be set; any
other will be ignored.
GPIO_SetDirection(GPIO_Value,
(GPIOsReceiveBuffer[0] & 0x01));
break;
case COMMAND_SET_GPIO_DIRECTION:
// Use the command letter at the end
marks the end of the command:
endCommand = 1;
break;
}
} while (endCommand == 0);
break;
case COMMAND_UNEXPORT_GPIO:
243
// The application wants to stop using a GPIO:
// Know which group of GPIOs is wanted to be
unexported:
endCommand = 0;
do {
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
GPIO_Value = GPIOsReceiveBuffer[0];
switch (GPIO_Value) {
case GPIO12:
case GPIO13:
case GPIO16:
case GPIO19:
case GPIO20:
case GPIO21:
case GPIO26:
// Only these GPIOs can be unexported;
any other will be ignored.
GPIO_Unexport(GPIO_Value);
break;
case COMMAND_UNEXPORT_GPIO:
// Use the command letter at the end
marks the end of the command:
endCommand = 1;
break;
}
} while (endCommand == 0);
break;
case COMMAND_WAIT_FOR_AN_INTERRUPT:
// The application wants to wait until an
interruption is produced:
// Wait for a signal coming from ISR (only if at
least one interruption has been attached):
if (((GPIOs_Status[0][1] & 0xF0) != 0) ||
((GPIOs_Status[1][1] & 0xF0) != 0) || ((GPIOs_Status[2][1] & 0xF0) != 0)
|| ((GPIOs_Status[3][1] & 0xF0) != 0) || ((GPIOs_Status[4][1] & 0xF0) !=
0) || ((GPIOs_Status[5][1] & 0xF0) != 0) || ((GPIOs_Status[6][1] & 0xF0)
!= 0)) {
pthread_mutex_lock(&gpioMutex);
pthread_cond_wait(&gpioCondition, &gpioMutex);
// Blocking function.
pthread_mutex_unlock(&gpioMutex);
}
// Send gpiosISR flag through socket:
GPIOsSendBuffer[0] = gpiosISR;
if (sendData(GPIOsFD2, GPIOsSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
gpiosISR = 0; // Reset flag for another interruption
catch.
break;
244
case COMMAND_WRITE_GPIO:
// The application wants to set the value of a GPIO
set as output:
// Know which group of GPIOs is wanted to be
written:
endCommand = 0;
do {
if (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
GPIO_Value = GPIOsReceiveBuffer[0];
receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1); //
Read value to be written.
switch (GPIO_Value) {
case GPIO12:
case GPIO13:
case GPIO16:
case GPIO19:
case GPIO20:
case GPIO21:
case GPIO26:
// Only these GPIOs can be written; any
other will be ignored.
GPIO_Write(GPIO_Value,
(GPIOsReceiveBuffer[0] & 0x01));
break;
case COMMAND_WRITE_GPIO:
// Use the command letter at the end
marks the end of the command:
endCommand = 1;
break;
}
} while (endCommand == 0);
break;
default:
// Received command not identified.
while (receiveData(GPIOsFD2, GPIOsReceiveBuffer, 1)
> 0); // Flush socket.
break;
}
} while (closeConnectionFlag == 0);
closeSocket(GPIOsFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
GPIOsActiveConnection = 0;
}
// Close socket:
closeSocket(GPIOsFD, GPIOS_DIRECTORY);
GPIOsHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void GPIO_AttachInterrupt(unsigned char gpio, unsigned char interrupt) {
// gpio: GPIO number to be set.
245
// interrupt: The GPIO kind of interrupt (wiringPi values).
int index;
index = getGPIOIndex(gpio);
// Attach interrupt (only if current GPIO is set as input):
if ((GPIOs_Status[index][1] & 0x03) == 0x00) {
// This GPIO is set as input so an interruption can be attached.
if (gpio == GPIO12) {
if (wiringPiISR(GPIO12, interrupt, &ISR_GPIO12) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO13) {
if (wiringPiISR(GPIO13, interrupt, &ISR_GPIO13) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO16) {
if (wiringPiISR(GPIO16, interrupt, &ISR_GPIO16) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO19) {
if (wiringPiISR(GPIO19, interrupt, &ISR_GPIO19) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO20) {
if (wiringPiISR(GPIO20, interrupt, &ISR_GPIO20) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO21) {
if (wiringPiISR(GPIO21, interrupt, &ISR_GPIO21) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
} else if (gpio == GPIO26) {
if (wiringPiISR(GPIO26, interrupt, &ISR_GPIO26) < 0) {
perror("Error setting RFM31B interrupt process ISR");
}
}
GPIOs_Status[index][1] = GPIOs_Status[index][1] | (interrupt <<
4);
}
}
unsigned char GPIO_Read(unsigned char gpio) {
// gpio: GPIO number to be write.
// Returns: The current GPIO value.
int index;
unsigned char value; // The read value.
index = getGPIOIndex(gpio);
// Read GPIO value (only if current GPIO is set as input):
if ((GPIOs_Status[index][1] & 0x03) == 0x00) {
// This GPIO is set as input so it can be read.
value = digitalRead(gpio);
GPIOs_Status[index][2] = value;
return value;
} else {
// This GPIO is not set as input. It cannot be read.
return 0xFF;
}
246
}
void GPIO_SetDirection(unsigned char gpio, unsigned char direction) {
// gpio: GPIO number to be set.
// direction: The GPIO direction: input ('0') or output ('1').
char* echoInOut;
char* gpioString;
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
int index;
// Prepare gpioString with gpio number:
gpioString = malloc(2);
sprintf(gpioString, "%d", (int) gpio);
// Export selected GPIO only if it is not set at this time:
if (GPIOs_Status[index][1] == 0x02) {
totalSize = 11 + strlen(gpioString) + 26 + 1;
command = malloc(totalSize); // Memory allocation.
strncpy(command, "sudo echo \"", totalSize);
strncat(command, gpioString, totalSize);
strncat(command, "\" > /sys/class/gpio/export", totalSize);
system(command); // OS call.
// Set GPIO direction:
index = getGPIOIndex(gpio);
pinMode(gpio, direction);
GPIOs_Status[index][1] = GPIOs_Status[index][1] | direction;
}
}
void GPIO_Unexport(unsigned char gpio) {
// gpio: GPIO number to be unexported.
struct stat dir; // Used to check if GPIO directory exists before
unexport it.
char* gpioString;
char* check; // Path to GPIO directory.
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
int index;
// Prepare gpioString with gpio number:
gpioString = malloc(2);
sprintf(gpioString, "%d", (int) gpio);
index = getGPIOIndex(gpio);
GPIOs_Status[index][1] = 0x02;
GPIOs_Status[index][2] = 0x00;
// Prepare GPIO's directory path:
totalSize = 20 + strlen(gpioString) + 1;
check = malloc(totalSize); // Memory allocation.
strncpy(check, "/sys/class/gpio/gpio", totalSize);
strncat(check, gpioString, totalSize);
if (stat(check, &dir) >= 0) {
// GPIO's directory exists. It can be unexported.
totalSize = 11 + strlen(gpioString) + 28 + 1;
command = malloc(totalSize); // Memory allocation.
247
strncpy(command,
strncat(command,
strncat(command,
system(command);
"sudo echo \"", totalSize);
gpioString, totalSize);
"\" > /sys/class/gpio/unexport", totalSize);
// OS call.
}
}
void GPIO_Write(unsigned char gpio, unsigned char value) {
// gpio: GPIO number to be write.
char* gpioString;
char* gpioValue;
char* command; // Full command for OS call.
int totalSize; // Total size of the command.
int index;
index = getGPIOIndex(gpio);
// Write GPIO value (only if current GPIO is set as output):
if ((GPIOs_Status[index][1] & 0x03) == 0x01) {
// This GPIO is set as output so it can be written.
digitalWrite(gpio, value);
GPIOs_Status[index][2] = value;
}
}
void ISR_GPIO12() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO12);
GPIOs_Status[0][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x80 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO13() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO13);
GPIOs_Status[1][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x40 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO16() {
248
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO16);
GPIOs_Status[2][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x20 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO19() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO19);
GPIOs_Status[3][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x10 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO20() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO20);
GPIOs_Status[4][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x08 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO21() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO21);
GPIOs_Status[5][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
249
gpiosISR = 0x04 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void ISR_GPIO26() {
unsigned char value;
// Read value from GPIO:
value = GPIO_Read(GPIO26);
GPIOs_Status[6][2] = value;
// Signal
if (GPIOsActiveConnection == 1) {
pthread_mutex_lock(&gpioMutex); // Lock mutex.
gpiosISR = 0x02 | value;
pthread_mutex_unlock(&gpioMutex); // Unlock mutex.
pthread_cond_signal(&gpioCondition); // Signal
TouchSensorHandler thread.
} // Only update flag if a high application is connected.
}
void resetGPIOs() {
// Unexport all GPIOs:
GPIO_Unexport(GPIO12);
GPIO_Unexport(GPIO13);
GPIO_Unexport(GPIO16);
GPIO_Unexport(GPIO19);
GPIO_Unexport(GPIO20);
GPIO_Unexport(GPIO21);
GPIO_Unexport(GPIO26);
}
LEDsHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef LEDSHANDLER_H
#define LEDSHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include "ArduinoPeripheral.h"
#include "SocketManager.h"
// Constants:
#define COMMAND_ALL_LEDS_OFF 'R'
#define COMMAND_ALL_LEDS_ON 'A'
#define COMMAND_CHANGE_STATE_LEDS 'S'
#define COMMAND_CHANGE_STATE_LEDS_BASE 'b'
250
#define
#define
#define
#define
#define
#define
COMMAND_CHANGE_STATE_LEDS_HEAD 'h'
COMMAND_CHANGE_STATE_LEDS_ORANGE_HEAD 'o'
COMMAND_CLOSE_CONNECTION 'C'
COMMAND_GET_LEDs_STATE 'G'
LEDS_BUFFER_SIZE 5
LEDS_DIRECTORY "/tmp/idroid02/leds"
// Global variables:
char LEDsHandlerAlive; // Flag to check if thread is alive.
// Functions:
void* LEDsHandler_Start(void*); // Handler start function.
void sendLEDsCommand(); // Function to send current LEDsData flag
state to Arduino.
#ifdef __cplusplus
}
#endif
#endif /* LEDSHANDLER_H */
LEDsHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* LEDsHandler: This thread manages the communication between I-Droid02
* Driver and a high-level application which requests the use of any
LED. This
* thread remains slept if any application requests to turn ON/OFF a
LED.
*/
#include "LEDsHandler.h"
void* LEDsHandler_Start(void* arg) {
unsigned char LEDsSendBuffer[LEDS_BUFFER_SIZE]; // Buffer of Bytes
to send through socket.
unsigned char LEDsReceiveBuffer[LEDS_BUFFER_SIZE]; // Buffer of
Bytes to receive through socket.
int LEDsFD, LEDsFD2; // Socket File Descriptors.
struct sockaddr_un LEDsSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
char changeStateLEDsEndCommand; // Flag to end the CHANGE_STATE_LEDS
command.
LEDsHandlerAlive = 1;
// Open and setup server socket:
LEDsFD = openSocket();
LEDsSocketStruct = setupSocket(LEDsFD, LEDS_DIRECTORY, 1);
resetLEDsData(); // Turn all LEDs OFF (default state of LEDs).
while (1) {
// Wait for a high-level application to connect:
251
LEDsFD2 = acceptConnection(LEDsSocketStruct, LEDsFD);
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(LEDsFD2, LEDsReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (LEDsReceiveBuffer[0]) {
case COMMAND_ALL_LEDS_OFF:
// The application wants to turn all LEDs OFF:
resetLEDsData();
// Send Arduino command:
sendLEDsCommand();
break;
case COMMAND_ALL_LEDS_ON:
// The application wants to turn all LEDs ON:
LEDsData = 0x1FFFF; // Change the state of all LEDs
(orange top-head LED at maximum PWM value).
// Send Arduino command:
sendLEDsCommand();
break;
case COMMAND_CHANGE_STATE_LEDS:
// The application wants to change the state of one
or some LEDs:
// Know which group of LEDs is wanted to be changed:
changeStateLEDsEndCommand = 0;
do {
if (receiveData(LEDsFD2, LEDsReceiveBuffer, 2) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
switch (LEDsReceiveBuffer[0]) {
case COMMAND_CHANGE_STATE_LEDS_BASE:
// Base LEDs state is wanted to be
changed:
LEDsData = (LEDsData & 0xFFFF) |
((LEDsReceiveBuffer[1] & 0x01) << 16);
break;
case COMMAND_CHANGE_STATE_LEDS_HEAD:
// Some of the head LEDs state is wanted
to be changed:
LEDsData = (LEDsData & 0x1FF00) |
LEDsReceiveBuffer[1];
break;
case COMMAND_CHANGE_STATE_LEDS_ORANGE_HEAD:
// Orange top-head LED PWM value is
wanted to be changed:
LEDsData = (LEDsData & 0x100FF) |
(LEDsReceiveBuffer[1] << 8);
break;
case COMMAND_CHANGE_STATE_LEDS:
252
// Use the command letter at the end
marks the end of the command:
changeStateLEDsEndCommand = 1;
break;
}
} while (changeStateLEDsEndCommand == 0);
// Send Arduino command:
sendLEDsCommand();
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
case COMMAND_GET_LEDs_STATE:
// The application wants to know the current state
of LEDs:
// Send LEDsData value through socket:
LEDsSendBuffer[0] = (LEDsData & 0x000FF); // Head
LEDs state.
LEDsSendBuffer[1] = ((LEDsData & 0x0FF00) >> 8); //
Orange top-head LED.
LEDsSendBuffer[2] = ((LEDsData & 0x10000) >> 16); //
Base LEDs state.
if (sendData(LEDsFD2, LEDsSendBuffer, 3) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(LEDsFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(LEDsFD, LEDS_DIRECTORY);
LEDsHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void sendLEDsCommand() {
char LEDsArduinoBuffer[5]; // Buffer to send Arduino command.
// Prepare LEDs command:
LEDsArduinoBuffer[0] = COMMAND_LEDS;
LEDsArduinoBuffer[1] = (LEDsData & 0xFF);
LEDsArduinoBuffer[2] = ((LEDsData & 0xFF00) >> 8);
LEDsArduinoBuffer[3] = ((LEDsData & 0x10000) >> 16);
LEDsArduinoBuffer[4] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(LEDsArduinoBuffer, 5);
}
253
MicrophoneHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef MICROPHONEHANDLER_H
#define MICROPHONEHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <fcntl.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include "FileManager.h"
#include "UARTManager.h"
// Constants:
#define MICROPHONE_FILE "/tmp/idroid02/microphone.txt"
#define SECONDARY_UART "/dev/ttyAMA0"
// Global variables:
char microphoneHandlerAlive; // Flag to check if thread is alive.
// Functions:
void* microphoneHandler_Start(void*); // Handler start function.
#ifdef __cplusplus
}
#endif
#endif /* MICROPHONEHANDLER_H */
MicrophoneHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* MicrophoneHandler. This thread is in charge of receive sampled data
coming
* from the microphone sampled in Arduino Mega 2560 board. Samples are
sent
* through UART interface ttyAMA0.
*/
#include "MicrophoneHandler.h"
void* microphoneHandler_Start(void* arg) {
int uartFD; // ttyAMA0 UART port file descriptor.
char buffer[1]; // Buffer used to store received value from UART.
char stringValue[3]; // Buffer where captured value is parsed.
254
microphoneHandlerAlive = 1;
// Open ttyAMA0 UART File Descriptor:
uartFD = openUART(SECONDARY_UART);
setupUART(uartFD, B115200);
while (1) {
// Read microphone data from ttyAMA0:
if (readUART(uartFD, buffer, 1) < 0) {
// Error reading at UART.
break;
}
// Convert value to string:
sprintf(stringValue, "%d", (int) buffer[0]);
// Echo new value:
echoLineToFile(MICROPHONE_FILE, stringValue);
}
// Close connection:
closeUART(uartFD); // Close UART Port.
microphoneHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
MotorsHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef MOTORSHANDLER_H
#define MOTORSHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include "ArduinoPeripheral.h"
#include "SocketManager.h"
// Constants:
#define COMMAND_CHECK_MOVEMENT_COMPLETE 'D'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_MOTORS_INFORMATION 'G'
#define COMMAND_MOVE_LIMITED_MOTOR 'L'
#define COMMAND_MOVE_LIMITED_MOTOR_SETUP 'l' // Do not report!
#define COMMAND_RESET_LIMITED_MOTORS 'R'
#define COMMAND_RESET_LIMITED_MOTORS_DATA 'r' // Do not report!
#define COMMAND_SET_MOVEMENT 'M'
#define COMMAND_STOP_LIMITED_MOTOR 'S'
#define MOTORS_BUFFER_SIZE 35
#define MOTORS_DIRECTORY "/tmp/idroid02/motors"
255
// Global variables:
char motorsHandlerAlive; // Flag to check if thread is alive.
// Functions:
void* motorsHandler_Start(void*); // Handler start function.
void sendResetEncoderCountCommand(); // Function to send Arduino
command to reset encoder count (done in calibration).
void sendResetMotorsCommand(); // Function to send Arduino command
to reset motors to its default state.
void sendSetMovementCommand(struct axisMovementPoint); // Function
to send Arduino command to move I-Droid02.
void sendStartMotorCommand(char, char, char); // Function to send
Arduino command to start any stopped motor.
void sendStartMotorCalibrationCommand(char, char); // Function to
send Arduino command to move any motor for calibration.
void sendStopMotorCommand(char); // Function to send Arduino stop
motor command before it reaches target position.
#ifdef __cplusplus
}
#endif
#endif /* MOTORSHANDLER_H */
MotorsHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* MotorsHandler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of any
motor. This
* thread remains slept if any application requests to move any motor.
*/
#include "MotorsHandler.h"
void* motorsHandler_Start(void* arg) {
unsigned char motorsSendBuffer[MOTORS_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
unsigned char motorsReceiveBuffer[MOTORS_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
int motorsFD, motorsFD2; // Socket File Descriptors.
struct sockaddr_un motorsSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
char intBuffer[sizeof(int)]; // Buffer used to parse integer values.
char floatBuffer[sizeof(float)]; // Buffer used to parse float
values.
int distanceAngle; // In COMMAND_SET_MOVEMENT, target distance or
angle.
int i;
motorsHandlerAlive = 1;
// Open and setup server socket:
256
motorsFD = openSocket();
motorsSocketStruct = setupSocket(motorsFD, MOTORS_DIRECTORY, 1);
resetLimitedMotorsData();
requestLimitedMotorsData();
resetMovementData();
sendSetMovementCommand(currentMovementSet);
while (1) {
// Wait for a high-level application to connect:
motorsFD2 = acceptConnection(motorsSocketStruct, motorsFD);
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(motorsFD2, motorsReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (motorsReceiveBuffer[0]) {
case COMMAND_CHECK_MOVEMENT_COMPLETE:
// The application wants to block until last
movement set is complete:
movementCompleteCommand = 1;
// Block until movement is complete:
if (currentMovementSet.distanceAngle != -1) {
// Wait for signal flag coming from Arduino:
pthread_mutex_lock(&movementCompleteMutex);
pthread_cond_wait(&movementCompleteCondition,
&movementCompleteMutex); // Blocking function.
pthread_mutex_unlock(&movementCompleteMutex);
}
// Send a '1' through socket:
motorsSendBuffer[0] = 1;
if (sendData(motorsFD2, motorsSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
movementCompleteCommand = 0;
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
case COMMAND_GET_MOTORS_INFORMATION:
// The application wants to know all motors
information:
// Read limitedMotors information and send it
through socket
for (i=0; i<6; i++) {
motorsSendBuffer[0] =
limitedMotors[i].MOTOR_NUMBER;
257
motorsSendBuffer[1] =
limitedMotors[i].currentPosition;
motorsSendBuffer[2] =
limitedMotors[i].MAXIMUM_POSITION;
if (sendData(motorsFD2, motorsSendBuffer, 3) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
}
// Send limitedMotorsInMovement value:
motorsSendBuffer[0] = limitedMotorsInMovement;
if (sendData(motorsFD2, motorsSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
// Read currentMovementSet information and send it
through socket:
memcpy(intBuffer, &currentMovementSet.distanceAngle,
sizeof(int));
motorsSendBuffer[0] =
currentMovementSet.directionFlag;
motorsSendBuffer[1] = currentMovementSet.x;
motorsSendBuffer[2] = currentMovementSet.y;
motorsSendBuffer[3] = intBuffer[0];
motorsSendBuffer[4] = intBuffer[1];
if (sendData(motorsFD2, motorsSendBuffer, 5) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
// Read, parse and send wheels' speed:
memcpy(floatBuffer, &leftWheelSpeed, sizeof(float));
if (sendData(motorsFD2, floatBuffer, sizeof(float))
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
memcpy(floatBuffer, &rightWheelSpeed,
sizeof(float));
if (sendData(motorsFD2, floatBuffer, sizeof(float))
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_MOVE_LIMITED_MOTOR:
// The application wants to start moving a limited
motor:
// Know which motor is wanted to be moved, the new
position and the speed of movement:
258
if (receiveData(motorsFD2, motorsReceiveBuffer, 3) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Check received data:
if ((motorsReceiveBuffer[0] < 5) ||
(motorsReceiveBuffer[0] > 10)) {
// Invalid motor number.
break;
} else if ((motorsReceiveBuffer[1] < 1) ||
(motorsReceiveBuffer[1] > limitedMotors[motorsReceiveBuffer[0] 5].MAXIMUM_POSITION)) {
// Invalid motor parameter.
break;
}
// Start motor:
sendStartMotorCommand(motorsReceiveBuffer[0],
motorsReceiveBuffer[1], motorsReceiveBuffer[2]);
break;
case COMMAND_MOVE_LIMITED_MOTOR_SETUP:
// Setup program wants to move a limited motor
beyond limits (only setup can do this!):
// Know which motors are wanted to be moved:
if (receiveData(motorsFD2, motorsReceiveBuffer, 2) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Call Arduino command:
sendStartMotorCalibrationCommand(motorsReceiveBuffer[0],
motorsReceiveBuffer[1]);
break;
case COMMAND_RESET_LIMITED_MOTORS:
// The application wants to reset limited motors
(put all them in its default position):
sendResetMotorsCommand();
break;
case COMMAND_RESET_LIMITED_MOTORS_DATA:
// Setup program wants to reset limited motors
position data (only setup can do this!):
// Reset local data:
resetLimitedMotorsData();
// Call Arduino command:
sendResetEncoderCountCommand();
break;
case COMMAND_SET_MOVEMENT:
// The application wants to move I-Droid02 in a
certain trajectory:
259
// Receive movement data and parse distanceAngle
value:
if (receiveData(motorsFD2, motorsReceiveBuffer, 5) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
intBuffer[0] = motorsReceiveBuffer[3];
intBuffer[1] = motorsReceiveBuffer[4];
memcpy(&distanceAngle, intBuffer, sizeof(int));
// Check received data:
if (((motorsReceiveBuffer[0] == 0x00) ||
(motorsReceiveBuffer[0] == 0x06) || (motorsReceiveBuffer[0] == 0x09) ||
(motorsReceiveBuffer[0] == 0x60) || (motorsReceiveBuffer[0] == 0x66) ||
(motorsReceiveBuffer[0] == 0x69) || (motorsReceiveBuffer[0] == 0x90) ||
(motorsReceiveBuffer[0] == 0x96) || (motorsReceiveBuffer[0] == 0x99)) &&
((motorsReceiveBuffer[1] >= 0) || (motorsReceiveBuffer[1] <= 10)) &&
((motorsReceiveBuffer[2] >= 0) || (motorsReceiveBuffer[2] <= 10))) {
// Set movement parameters:
currentMovementSet.directionFlag =
motorsReceiveBuffer[0];
currentMovementSet.x = motorsReceiveBuffer[1];
currentMovementSet.y = motorsReceiveBuffer[2];
currentMovementSet.distanceAngle =
distanceAngle;
// Call Arduino command:
sendSetMovementCommand(currentMovementSet);
}
break;
case COMMAND_STOP_LIMITED_MOTOR:
// The application wants to stop a limited motor
which is currently moving:
// Know which motor is wanted to be stopped:
if (receiveData(motorsFD2, motorsReceiveBuffer, 1) <
0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Call Arduino command:
sendStopMotorCommand(motorsReceiveBuffer[0]);
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(motorsFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(motorsFD, MOTORS_DIRECTORY);
260
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void sendResetEncoderCountCommand() {
char motorsArduinoBuffer[3]; // Buffer to send Arduino command.
// Prepare reset encoder count command:
motorsArduinoBuffer[0] = COMMAND_MOTORS;
motorsArduinoBuffer[1] = COMMAND_MOTORS_RESET_ENCODERS;
motorsArduinoBuffer[2] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(motorsArduinoBuffer, 3);
}
void sendResetMotorsCommand() {
char motorsArduinoBuffer[4]; // Buffer to send Arduino command.
// Prepare reset motors command:
motorsArduinoBuffer[0] = COMMAND_RESET;
motorsArduinoBuffer[1] = COMMAND_RESET_MOTORS;
motorsArduinoBuffer[2] = 0;
motorsArduinoBuffer[3] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(motorsArduinoBuffer, 4);
}
void sendSetMovementCommand(struct axisMovementPoint point) {
// point: Movement data: Direction flag, axis movement point and
target distance or angle.
char motorsArduinoBuffer[8]; // Buffer to send Arduino command.
char distanceAngleBuffer[sizeof(int)];
// Parse integer value:
memcpy(distanceAngleBuffer, &point.distanceAngle, sizeof(int));
// Prepare set movement command:
motorsArduinoBuffer[0] = COMMAND_MOTORS;
motorsArduinoBuffer[1] = COMMAND_MOTORS_SET_MOVEMENT;
motorsArduinoBuffer[2] = point.directionFlag;
motorsArduinoBuffer[3] = point.x;
motorsArduinoBuffer[4] = point.y;
motorsArduinoBuffer[5] = distanceAngleBuffer[0];
motorsArduinoBuffer[6] = distanceAngleBuffer[1];
motorsArduinoBuffer[7] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(motorsArduinoBuffer, 8);
}
void sendStartMotorCommand(char motorNumber, char targetPosition, char
speed) {
// motorNumber: The motor to start.
// targetPosition: The new position where the motor is wanted to be
moved.
// speed: The speed of a motor (a value between 0 and 10).
char motorsArduinoBuffer[6]; // Buffer to send Arduino command.
// Prepare start motor
motorsArduinoBuffer[0]
motorsArduinoBuffer[1]
motorsArduinoBuffer[2]
motorsArduinoBuffer[3]
motorsArduinoBuffer[4]
motorsArduinoBuffer[5]
command:
= COMMAND_MOTORS;
= COMMAND_MOTORS_START;
= motorNumber;
= targetPosition;
= speed;
= COMMAND_END_OF_TRANSMISSION;
261
sendCommandToArduino(motorsArduinoBuffer, 6);
}
void sendStartMotorCalibrationCommand(char motorNumber, char direction)
{
// motorNumber: The motor to calibrate.
// direction: 0x01 for up/left/open, 0x00 for down/right/close.
char motorsArduinoBuffer[4]; // Buffer to send Arduino command.
// Prepare reset encoder count command:
motorsArduinoBuffer[0] = COMMAND_MOTORS;
motorsArduinoBuffer[1] = COMMAND_MOTORS_CALIBRATE;
motorsArduinoBuffer[2] = (motorNumber << 4) | (direction & 0x01);
motorsArduinoBuffer[3] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(motorsArduinoBuffer, 4);
}
void sendStopMotorCommand(char motorNumber) {
// motorNumber: The motor to change its speed.
char motorsArduinoBuffer[4]; // Buffer to send Arduino command.
// Prepare reset encoder count command:
motorsArduinoBuffer[0] = COMMAND_MOTORS;
motorsArduinoBuffer[1] = COMMAND_MOTORS_STOP;
motorsArduinoBuffer[2] = motorNumber;
motorsArduinoBuffer[3] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(motorsArduinoBuffer, 4);
}
ProgrammableButtonsHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef PROGRAMMABLEBUTTONSHANDLER_H
#define PROGRAMMABLEBUTTONSHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include <wiringPi.h>
#include "SocketManager.h"
#include "utilities.h"
// Constants:
#define BUTTON_D_GPIO 18
#define BUTTON_E_GPIO 24
#define BUTTON_F_GPIO 5
#define COMMAND_ATTACH_SENSOR 'A'
#define COMMAND_CLOSE_CONNECTION 'C'
#define PROGRAMMABLE_BUTTONS_BUFFER_SIZE 1
#define PROGRAMMABLE_BUTTONS_DIRECTORY
"/tmp/idroid02/programmableButtons"
262
// Global variables
char programmableButtonsActiveConnection; // Flag to inform if a
client is connected.
/* Flag to state which buttons has been pressed. This Byte has the
following
* format: 0b00XaXbXcXdXeXf. Xi bit is set to '1' when i button is
pressed.
* i represents A, B and C buttons (placed in the remote control)
and D, E
* and F buttons (placed in the I-Droid02 outer black box).
*/
char programmableButtonsFlag;
char programmableButtonsHandlerAlive; // Flag to check if thread is
alive.
pthread_mutex_t programmableButtonsMutex;
pthread_cond_t programmableButtonsCondition;
struct timespec storedTime; // Global variable to store current time
for time comparison.
// Functions:
void ackProgrammableButtons(); // Function called when high-level
application acknowledges pressed buttons.
void ISR_D_ProgrammableButton(); // GPIO Interrupt Service Routine
to handle D button.
void ISR_E_ProgrammableButton(); // GPIO Interrupt Service Routine
to handle E button.
void ISR_F_ProgrammableButton(); // GPIO Interrupt Service Routine
to handle F button.
void* programmableButtonsHandler_Start(void*); // Handler start
function.
#ifdef __cplusplus
}
#endif
#endif /* PROGRAMMABLEBUTTONSHANDLER_H */
ProgrammableButtonsHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* ChestButtonsHandler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of the
chest
* buttons. This thread remains slept if any application requests the
sensor.
* When an application requests this thread, it also gets blocked until
* ArduinoHandler thread notifies the reception of a pressed button.
*/
#include "ProgrammableButtonsHandler.h"
void ackProgrammableButtons() {
// Reset value:
programmableButtonsFlag = 0;
263
}
void ISR_D_ProgrammableButton() {
// Executed when user presses D programmable button:
if (checkTimeDifference(0, &storedTime, 1) == 1) {
// Allow only one detection every second.
if (programmableButtonsActiveConnection != 0) {
pthread_mutex_lock(&programmableButtonsMutex); // Lock
mutex.
programmableButtonsFlag = programmableButtonsFlag | 0x04;
pthread_mutex_unlock(&programmableButtonsMutex); // Unlock
mutex.
pthread_cond_signal(&programmableButtonsCondition); //
Signal application thread.
} // Only update flag if a high application is connected.
}
}
void ISR_E_ProgrammableButton() {
// Executed when user presses D programmable button:
if (checkTimeDifference(0, &storedTime, 1) == 1) {
// Allow only one detection every second.
if (programmableButtonsActiveConnection != 0) {
pthread_mutex_lock(&programmableButtonsMutex); // Lock
mutex.
programmableButtonsFlag = programmableButtonsFlag | 0x02;
pthread_mutex_unlock(&programmableButtonsMutex); // Unlock
mutex.
pthread_cond_signal(&programmableButtonsCondition); //
Signal application thread.
} // Only update flag if a high application is connected.
}
}
void ISR_F_ProgrammableButton() {
// Executed when user presses D programmable button:
if (checkTimeDifference(0, &storedTime, 1) == 1) {
// Allow only one detection every second.
if (programmableButtonsActiveConnection != 0) {
pthread_mutex_lock(&programmableButtonsMutex); // Lock
mutex.
programmableButtonsFlag = programmableButtonsFlag | 0x01;
pthread_mutex_unlock(&programmableButtonsMutex); // Unlock
mutex.
pthread_cond_signal(&programmableButtonsCondition); //
Signal application thread.
} // Only update flag if a high application is connected.
}
}
void* programmableButtonsHandler_Start(void* arg) {
unsigned char
buttonsSocketSendBuffer[PROGRAMMABLE_BUTTONS_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
unsigned char
buttonsSocketReceiveBuffer[PROGRAMMABLE_BUTTONS_BUFFER_SIZE]; // Buffer
of Bytes to send through socket.
264
int programmableButtonsFD, programmableButtonsFD2; // Socket File
Descriptors.
struct sockaddr_un programmableButtonsSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
programmableButtonsHandlerAlive = 1;
pthread_mutex_init(&programmableButtonsMutex, NULL);
pthread_cond_init(&programmableButtonsCondition, NULL);
// Setup programmable buttons associated ISR:
if (wiringPiISR(BUTTON_D_GPIO, INT_EDGE_FALLING,
&ISR_D_ProgrammableButton) < 0) {
perror("Error setting D programmable button ISR");
}
if (wiringPiISR(BUTTON_E_GPIO, INT_EDGE_FALLING,
&ISR_E_ProgrammableButton) < 0) {
perror("Error setting E programmable button ISR");
}
if (wiringPiISR(BUTTON_F_GPIO, INT_EDGE_FALLING,
&ISR_F_ProgrammableButton) < 0) {
perror("Error setting F programmable button ISR");
}
clock_gettime(CLOCK_REALTIME, &storedTime); // Get current time.
// Open and setup server socket:
programmableButtonsFD = openSocket();
programmableButtonsSocketStruct = setupSocket(programmableButtonsFD,
PROGRAMMABLE_BUTTONS_DIRECTORY, 1);
while (1) {
// Wait for a high-level application to connect:
programmableButtonsFD2 =
acceptConnection(programmableButtonsSocketStruct,
programmableButtonsFD);
programmableButtonsActiveConnection = 1;
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(programmableButtonsFD2,
buttonsSocketReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (buttonsSocketReceiveBuffer[0]) {
case COMMAND_ATTACH_SENSOR:
// The application wants to wait until any of the
buttons is pressed:
// Wait for a change of programmableButtonsFlag
value:
pthread_mutex_lock(&programmableButtonsMutex);
while (programmableButtonsFlag == 0) {
// Block thread until signal is received and
free mutex:
pthread_cond_wait(&programmableButtonsCondition,
&programmableButtonsMutex); // Blocking function.
265
}
// Send buttonsFlag to high-level application:
buttonsSocketSendBuffer[0] =
programmableButtonsFlag;
if (sendData(programmableButtonsFD2,
buttonsSocketSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
ackProgrammableButtons();
pthread_mutex_unlock(&programmableButtonsMutex);
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(programmableButtonsFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
programmableButtonsActiveConnection = 0;
}
// Close socket:
closeSocket(programmableButtonsFD, PROGRAMMABLE_BUTTONS_DIRECTORY);
programmableButtonsHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
RFM31B_Handler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef RFM31B_HANDLER_H
#define RFM31B_HANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
<pthread.h>
<stdlib.h>
<string.h>
<wiringPi.h>
#include "BatteryData.h"
#include "FileManager.h"
266
#include
#include
#include
#include
#include
"ProgrammableButtonsHandler.h"
"SocketManager.h"
"SPIManager.h"
"TemperatureData.h"
"UARTManager.h"
// Constants:
#define BAND_FREQUENCY_TOTAL_INC 1741
#define COMMAND_ACK 'A'
#define COMMAND_CHANGE_FREQUENCY 'F'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_FREQUENCY 'G'
#define COMMAND_MEASURE_POWER 'P'
#define COMMAND_OBTAIN_SPECTRUM 'S'
#define COMMAND_RECEIVE_DATA 'R'
#define FREQUENCY_FILE "/home/pi/Main_Programs/RFM31B_Frequency.txt"
#define PACKET_ID_CONTROL 0xC3
#define PACKET_ID_ANALOG 0x24
#define PACKET_ID_DIGITAL 0x18
#define RECEIVED_PACKET_LED_GPIO 23
#define REMOTE_CONTROL_UART "/dev/remoteControl"
#define RFM31B_BUFFER_SIZE 15
#define RFM31B_DIRECTORY "/tmp/idroid02/rfm31b"
#define RFM31B_FIFO_SIZE 64
#define RFM31B_INTERRUPT_GPIO 25
// Especial struct used to pass counters:
struct COUNTERS {
int temporalPacketCounter; // Variable to store temporal
packetCounter.
int temporalPacketCounterAnalog; // Variable to store temporal
packetCounterAnalog.
int temporalPacketCounterDigital; // Variable to store temporal
packetCounterDigital.
};
// Global variables:
float frequency; // RFM31B central frequency tuned in MHz.
volatile char noPacketFlag; // It is set with PACKET_ID_ANALOG or
PACKET_ID_DIGITAL according to the last received packet timeout.
unsigned char packetBuffer[RFM31B_FIFO_SIZE]; // Buffer where RFM31B
ISR stores received packet to be sent to connected high-application.
int packetCounter; // A counter for all received packet (only those
that are sent through socket are counted).
int packetCounterAnalog; // A counter for every analogue received
packet (only those that are sent through socket are counted).
int packetCounterDigital; // A counter for every digital received
packet (only those that are sent through socket are counted).
char packetLength; // Length of the packet stored in packetBuffer.
char RFM31B_ActiveConnection; // Flag to inform if a client is
connected.
char RFM43B_ActiveISR; // Flag used to enable/disable ISR.
char RFM31B_HandlerAlive; // Flag to check if thread is alive.
// Both used to sync RF packet reception with socket link with highlevel application:
pthread_mutex_t sendDataMutex;
pthread_cond_t sendDataCondition;
int spiFD; // The SPI File Descriptor.
pthread_mutex_t SPI_Mutex; // Mutex to sync transfers to RFM31B via
SPI port.
267
// Functions:
int accessRFM31B_Registers(char, unsigned char*, int); // Function
to access the RFM31B receiver registers through SPI.
int computeCentralFrequency(); // Obtain central frequency value to
be written in the corresponding register.
void* detectNoAnalogPacketReceived(void*); // Thread to detect if
any new analogue packet is received after last reception.
void* detectNoDigitalPacketReceived(void*); // Thread to detect if
any new digital packet is received after last reception.
void* getRFM31B_Temperature(void*); // Thread in charge of obtain
RFM31B internal temperature and place it in the TemperatureData handler.
void processReceivedPacket(); // This routine is called when a valid
packet has been received.
void* RFM31B_Handler_Start(void*); // Handler start function.
float RFM31B_MeasurePower(); // Obtain RSSI of current received
packet.
void RFM31B_RX_Mode(char); // Function to enter or exit RFM31B from
RX mode.
void ISR_RFM31B_ProcessInterrupt(); // ISR to process interrupt
received from RFM31B module.
void resetRFM31B_FIFO(); // Function to clear RFM31B FIFO buffer.
void setupRFM31B_Module(); // Function to setup all registers of
RFM31B receiver.
#ifdef __cplusplus
}
#endif
#endif /* RFM31B_HANDLER_H */
RFM31B_Handler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* RFM31B_Handler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of the 433
MHz
* RFM31B receiver. This thread setups the receiver to match RFM43B
transmitter
* settings, the one placed in the I-Droid02 native remote control.
Also, the
* thread monitors the IRQ line that is connected to the receiver in
order to
* know when a packet has been received or another event has been
occurred.
* According to the event occurred, this thread sends the corresponding
command
* to the high-level application to inform it. When a valid packet is
received,
* this thread delivers it to the connected high-level application,
without
* process it.
*/
#include "RFM31B_Handler.h"
268
int accessRFM31B_Registers(char writeFlag, unsigned char*
transferBuffer, int registersCount) {
// writeFlag: Flag to indicate a writing operation. Set to 0 for
reading.
// transferBuffer: Array to store Bytes to be sent and received by
RFM43B.
// registersCount: Consecutive registers number (plus initial
address) to be w/r. It must be equal than buffer size.
int ret;
if (writeFlag == 1) {
// A write operation is requested.
transferBuffer[0] = transferBuffer[0] | 0x80; // Add a '1' to
the most significant bit of the register's address Byte.
}
pthread_mutex_lock(&SPI_Mutex);
ret = transferSPI(spiFD, transferBuffer, registersCount); // SPI
transmission.
pthread_mutex_unlock(&SPI_Mutex);
return ret;
}
int computeCentralFrequency() {
int fc; // Parsed central frequency value to be written in the
appropriate register.
// Frequency entered check:
if (frequency < 433.05) {
frequency = 433.05;
}
if (frequency > 434.79) {
frequency = 434.79;
}
fc = ((frequency / 10.0) - 43) * 64000; // Frequency value to be
written in the register.
return fc;
}
void* detectNoAnalogPacketReceived(void* arg) {
struct COUNTERS currentCounter = *((struct COUNTERS*) arg);
// Sleep and check counters:
usleep(110000);
// If global counter has changed but not analogue counter, sleep
again:
if ((currentCounter.temporalPacketCounterAnalog ==
packetCounterAnalog) && (currentCounter.temporalPacketCounter !=
packetCounter)) {
usleep(110000);
}
if (currentCounter.temporalPacketCounterAnalog ==
packetCounterAnalog) {
// Any new packet has been received.
pthread_mutex_lock(&sendDataMutex); // Lock mutex.
269
packetLength = 0; // Set flag.
noPacketFlag = PACKET_ID_ANALOG; // Set NO_PACKET flag.
pthread_mutex_unlock(&sendDataMutex); // Unlock mutex.
pthread_cond_signal(&sendDataCondition);
}
pthread_exit(EXIT_SUCCESS);
}
void* detectNoDigitalPacketReceived(void* arg) {
struct COUNTERS currentCounter = *((struct COUNTERS*) arg);
// Sleep and check counters:
usleep(110000);
// If global counter has changed but not digital counter, sleep
again:
if ((currentCounter.temporalPacketCounterDigital ==
packetCounterDigital) && (currentCounter.temporalPacketCounter !=
packetCounter)) {
usleep(110000);
}
if (currentCounter.temporalPacketCounterDigital ==
packetCounterDigital) {
// Any new packet has been received.
pthread_mutex_lock(&sendDataMutex); // Lock mutex.
packetLength = 0; // Set flag.
noPacketFlag = PACKET_ID_DIGITAL; // Set NO_PACKET flag.
pthread_mutex_unlock(&sendDataMutex); // Unlock mutex.
pthread_cond_signal(&sendDataCondition);
}
pthread_exit(EXIT_SUCCESS);
}
void* getRFM31B_Temperature(void* arg) {
unsigned char tempSPI_Buffer[2]; // Buffer used for SPI
communication.
while (1) {
// RFM43B ADC value activation:
tempSPI_Buffer[0] = 0x0F; // Address of ADC activation register.
tempSPI_Buffer[1] = 0x80; // Change bit to trigger ADC.
if (accessRFM31B_Registers(1, tempSPI_Buffer, 2) < 0) {
break;
}
usleep(1000);
// RFM43B ADC value register reading:
tempSPI_Buffer[0] = 0x11; // Address of ADC reading.
if (accessRFM31B_Registers(0, tempSPI_Buffer, 2) < 0) {
break;
}
// Compute value and store it on temperatureRFM31B variable:
temperatureRFM31B = tempSPI_Buffer[1] * 0.5 - 64.0;
sleep(10); // Wait 10 seconds for the next reading.
}
pthread_exit(EXIT_SUCCESS); // Stop thread.
270
}
void* RFM31B_Handler_Start(void* arg) {
pthread_t getTemp;
pthread_t noAnalogPacketDetector;
pthread_t noDigitalPacketDetector;
FILE* frequencyFile; // File Descriptor of the text file containing
last RFM31B central frequency set.
int fc; // Frequency value integer value placed in RFM31B 0x76 &
0x77 registers.
float power, oldFrequency; // Variables used in
COMMAND_MEASURE_POWER & COMMAND_OBTAIN_SPECTRUM.
unsigned char RFM31B_SendBuffer[RFM31B_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
unsigned char RFM31B_ReceiveBuffer[RFM31B_BUFFER_SIZE]; // Buffer of
Bytes to send through socket.
int RFM31B_FD, RFM31B_FD2; // Socket File Descriptors.
struct sockaddr_un RFM31B_SocketStruct;
char* frequencyString; // String containing central frequency value
as string.
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
char endCommand; // Flag used to know when high-level application
ends a mode command.
char packetID; // ID of the packet to be sent through socket in
COMMAND_RECEIVE_DATA:
struct COUNTERS temporalCounters;
int remoteUART_FD; // File Descriptor used for UART communication
with remote control.
int i, j;
RFM31B_HandlerAlive = 1;
pthread_mutex_init(&sendDataMutex, NULL);
pthread_cond_init(&sendDataCondition, NULL);
pthread_mutex_init(&SPI_Mutex, NULL);
// Open and setup server socket:
RFM31B_FD = openSocket();
RFM31B_SocketStruct = setupSocket(RFM31B_FD, RFM31B_DIRECTORY, 1);
// Open and setup SPI port:
spiFD = openSPI();
setupSPI(spiFD);
// Setup RFM31B:
frequencyFile = openFile(FREQUENCY_FILE, "r"); // Open file where
frequency value is stored.
if (readLineFromFile(frequencyFile, &frequencyString, 6) != -1) {
frequency = atof(frequencyString); // Set frequency value.
}
closeFile(frequencyFile);
setupRFM31B_Module(); // Setup RFM31B registers.
// Put RFM31B in RX state:
RFM31B_RX_Mode(1);
// Interruption line GPIO init:
if (wiringPiISR(RFM31B_INTERRUPT_GPIO, INT_EDGE_FALLING,
&ISR_RFM31B_ProcessInterrupt) < 0) {
perror("Error setting RFM31B interrupt process ISR");
271
}
RFM43B_ActiveISR = 1;
// Start thread in charge of obtain the temperature value of the
RFM31B internal CPU:
pthread_create(&getTemp, NULL, getRFM31B_Temperature, NULL);
while (1) {
// Wait for a high-level application to connect:
RFM31B_FD2 = acceptConnection(RFM31B_SocketStruct, RFM31B_FD);
RFM31B_ActiveConnection = 1;
packetLength = 0; // Reset packetBuffer length value.
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(RFM31B_FD2, RFM31B_ReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (RFM31B_ReceiveBuffer[0]) {
case COMMAND_CHANGE_FREQUENCY:
// The application wants to change RFM31B tuned
frequency:
// Receive frequency value in MHz:
RFM43B_ActiveISR = 0; // Disable ISR temporally.
if (receiveData(RFM31B_FD2, RFM31B_ReceiveBuffer, 4)
< 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Check if native remote control is connected:
remoteUART_FD = openUART(REMOTE_CONTROL_UART); //
Try to open device, if fails, the returned FD is -1.
if (remoteUART_FD > 0) {
// Remote control device is present. Open it and
send frequency value:
setupUART(remoteUART_FD, B300);
memset(RFM31B_SendBuffer, 'f',
RFM31B_BUFFER_SIZE);
writeUART(remoteUART_FD, RFM31B_SendBuffer,
RFM31B_BUFFER_SIZE); // Send rubbish to flush.
RFM31B_SendBuffer[0] = 'F'; // Send flag
identifier.
writeUART(remoteUART_FD, RFM31B_SendBuffer, 1);
writeUART(remoteUART_FD, RFM31B_ReceiveBuffer,
4); // Send frequency value which is stored at socket RX buffer.
closeUART(remoteUART_FD);
// Parse frequency value and setup module:
memcpy(&frequency, RFM31B_ReceiveBuffer, 4);
setupRFM31B_Module(); // Setup RFM31B registers.
// Store frequency value on the external file:
frequencyString = malloc(6);
272
sprintf(frequencyString, "%.2f", frequency);
frequencyFile = openFile(FREQUENCY_FILE, "w");
// Open file where frequency value is stored.
writeLineToFile(frequencyFile, frequencyString);
// Write value to file.
closeFile(frequencyFile);
}
// Enable RX mode and ISR again:
RFM31B_RX_Mode(1);
RFM43B_ActiveISR = 1;
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
case COMMAND_GET_FREQUENCY:
// The application wants to know which is the
current central frequency set at the receiver:
// Parse current value and send it through socket:
memcpy(RFM31B_SendBuffer, &frequency, sizeof
(float));
if (sendData(RFM31B_FD2, RFM31B_SendBuffer, 4) < 0)
{
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_OBTAIN_SPECTRUM:
// The application wants to measure the current
spectrum (1 kHz precision) of full 433 MHz ISM band:
// This is an especial mode, for testing purposes.
ISR must be disabled:
RFM43B_ActiveISR = 0;
// Get the spectrum of 433 MHZ ISM band using RFM31B
RSSI measures:
digitalWrite(RECEIVED_PACKET_LED_GPIO, HIGH); //
Turn RECEIVED_PACKET_LED_GPIO ON.
oldFrequency = frequency; // Store current frequency
value.
for (j = 0; j < BAND_FREQUENCY_TOTAL_INC; j++) {
// Obtain current frequency of the swept:
frequency = 433.05 + j / 1000.0;
fc = computeCentralFrequency();
// Setup RFM31B module and start RX mode:
setupRFM31B_Module();
RFM31B_RX_Mode(1);
// Get current RSSI value in dBm format (mean of
10000 measurements).
power = 0.0;
for (i = 0; i < 10000; i++) {
power = power + RFM31B_MeasurePower() /
10000.0; // Get a mean value.
}
273
// Send current frequency and obtained power
value through socket:
memcpy(RFM31B_SendBuffer, &frequency, sizeof
(float));
if (sendData(RFM31B_FD2, RFM31B_SendBuffer, 4) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
memcpy(RFM31B_SendBuffer, &power, sizeof
(float));
if (sendData(RFM31B_FD2, RFM31B_SendBuffer, 4) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
}
frequency = oldFrequency; // Restore current
frequency value.
setupRFM31B_Module();
RFM31B_RX_Mode(1);
digitalWrite(RECEIVED_PACKET_LED_GPIO, LOW); // Turn
RECEIVED_PACKET_LED_GPIO OFF.
// Enable ISR:
RFM43B_ActiveISR = 1;
break;
case COMMAND_MEASURE_POWER:
// The application wants to measure received power
of RF input packets.
// This is an especial mode, for testing purposes.
ISR must be disabled:
RFM43B_ActiveISR = 0;
// Enable RFM31B RX Mode:
RFM31B_RX_Mode(1);
digitalWrite(RECEIVED_PACKET_LED_GPIO, HIGH); //
Turn RECEIVED_PACKET_LED_GPIO ON.
endCommand = 0;
do {
// Get RSSI data and send it through socket:
power = RFM31B_MeasurePower();
memcpy(RFM31B_SendBuffer, &power, sizeof
(float));
if (sendData(RFM31B_FD2, RFM31B_SendBuffer, 4) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
RFM31B_RX_Mode(1);
// Receive COMMAND_ACK or COMMAND_MEASURE_POWER
to end command:
if (receiveData(RFM31B_FD2,
RFM31B_ReceiveBuffer, 1) < 0) {
274
// Error receiving data.
closeConnectionFlag = 1;
break;
}
if (RFM31B_ReceiveBuffer[0] ==
COMMAND_MEASURE_POWER) {
// The application wants to end current
command.
endCommand = 1;
}
} while (endCommand == 0);
digitalWrite(RECEIVED_PACKET_LED_GPIO, LOW); // Turn
RECEIVED_PACKET_LED_GPIO OFF.
// Reset and setup again RFM31B RX module:
setupRFM31B_Module();
RFM31B_RX_Mode(1);
// Enable ISR:
RFM43B_ActiveISR = 1;
break;
case COMMAND_RECEIVE_DATA:
// The application wants to receive data from RF
input packets.
endCommand = 0;
do {
// Wait for a signal coming from ISR:
pthread_mutex_lock(&sendDataMutex);
pthread_cond_wait(&sendDataCondition,
&sendDataMutex); // Blocking function.
pthread_mutex_unlock(&sendDataMutex);
// Send length of the received packet:
RFM31B_SendBuffer[0] = packetLength;
if (sendData(RFM31B_FD2, RFM31B_SendBuffer, 1) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
// Send packet or noPacketFlag:
if (packetLength != 0) {
// Send received packet:
memcpy(RFM31B_SendBuffer, packetBuffer,
packetLength);
packetID = RFM31B_SendBuffer[0];
if (sendData(RFM31B_FD2, RFM31B_SendBuffer,
packetLength) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
} else {
RFM31B_SendBuffer[0] = noPacketFlag;
if (sendData(RFM31B_FD2, RFM31B_SendBuffer,
1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
275
break;
}
}
// Receive COMMAND_ACK or COMMAND_RECEIVE_DATA
to end command:
if (receiveData(RFM31B_FD2,
RFM31B_ReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
switch (RFM31B_ReceiveBuffer[0]) {
case COMMAND_ACK:
// The application still wants to
receive more packets:
temporalCounters.temporalPacketCounter =
packetCounter;
temporalCounters.temporalPacketCounterAnalog = packetCounterAnalog;
temporalCounters.temporalPacketCounterDigital = packetCounterDigital;
if (packetID == PACKET_ID_ANALOG) {
// Start no analogue packet detector
thread:
pthread_create(&noAnalogPacketDetector, NULL,
detectNoAnalogPacketReceived, &temporalCounters);
pthread_detach(noAnalogPacketDetector); // Detach created thread from
this one, to free resources when it dies.
}
if (packetID == PACKET_ID_DIGITAL) {
// Start no digital packet detector
thread:
pthread_create(&noDigitalPacketDetector, NULL,
detectNoDigitalPacketReceived, &temporalCounters);
pthread_detach(noDigitalPacketDetector); // Detach created thread from
this one, to free resources when it dies.
}
break;
case COMMAND_RECEIVE_DATA:
// The application wants to end current
command.
endCommand = 1;
break;
}
} while (endCommand == 0);
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(RFM31B_FD2, NULL);
276
closeConnectionFlag = 0; // Reset flag for a new client.
RFM31B_ActiveConnection = 0;
}
// Close SPI port and socket:
closeSPI(spiFD);
closeSocket(RFM31B_FD, RFM31B_DIRECTORY);
RFM31B_HandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
float RFM31B_MeasurePower() {
// Returns: The obtained value from RFM31B RSSI register in dBm.
unsigned char spiBuff[2];
// Read RSSI register:
spiBuff[0] = 0x26; // Address of Received Signal Strength Indicator
register.
if (accessRFM31B_Registers(0, spiBuff, 2) < 0) {
printf("Error obtaining RSSI value from register\n");
return;
}
return (-122.5 + 0.5 * spiBuff[1]);
}
void RFM31B_RX_Mode(char onOFF) {
// onOFF: '1' to enter RX mode or '0' to exit RX mode.
unsigned char buffer[2];
buffer[0] = 0x07; // Address.
if (onOFF == 1) {
buffer[1] = 0x04; // Enter RX mode.
} else {
buffer[1] = 0x00; // Exit RX mode.
}
if (accessRFM31B_Registers(1, buffer, 2) < 0) {
return;
}
}
void ISR_RFM31B_ProcessInterrupt() {
unsigned char spiRead[4]; // Buffer used to read status and
interrupts registers.
if (RFM43B_ActiveISR == 1) {
// Read status and interrupts registers:
spiRead[0] = 0x02; // Address of Status Register.
if (accessRFM31B_Registers(0, spiRead, 4) < 0) {
return;
}
// Determine which event has been produced:
if ((spiRead[2] & 0x02) == 0x02) {
// Valid Packet Received.
digitalWrite(RECEIVED_PACKET_LED_GPIO, HIGH); // Turn
RECEIVED_PACKET_LED_GPIO ON.
processReceivedPacket();
277
setupRFM31B_Module(); // Reset RFM31B.
RFM31B_RX_Mode(1); // Put RFM31B in RX state again.
digitalWrite(RECEIVED_PACKET_LED_GPIO, LOW); // Turn
RECEIVED_PACKET_LED_GPIO OFF.
}
if ((spiRead[2] & 0x10) == 0x12) {
// RX FIFO Almost Full.
// Only reset FIFO if any high-level application is
connected:
if (RFM31B_ActiveConnection == 0) {
RFM31B_RX_Mode(0); // Exit RX mode.
resetRFM31B_FIFO();
RFM31B_RX_Mode(1); // Enter RX mode.
}
}
if ((spiRead[2] & 0x80) == 0x80) {
// FIFO Underflow/Overflow Error.
// Reset FIFO to solve the error:
RFM31B_RX_Mode(0); // Exit RX mode.
resetRFM31B_FIFO();
RFM31B_RX_Mode(1); // Enter RX mode.
}
}
}
void processReceivedPacket() {
unsigned char spiBuff[RFM31B_FIFO_SIZE + 1];
unsigned char* tempBuffer; // Buffer to store extracted packet.
int receivedPacketLength; // Length of the received packet in Bytes
(fixed value).
unsigned char floatExtracted[4]; // Temporal buffer used for float
parsing.
int i;
// Read packet from FIFO buffer:
spiBuff[0] = 0x7F; // Address of FIFO buffer.
if (accessRFM31B_Registers(0, spiBuff, RFM31B_FIFO_SIZE + 1) < 0) {
return;
}
resetRFM31B_FIFO(); // Empty FIFO for the next reception.
// Identify received packet:
switch (spiBuff[1]) {
case PACKET_ID_CONTROL:
// This packet is only for remote control relevant data
signalling and must not be reported.
// Extract packet:
receivedPacketLength = 13;
tempBuffer = (unsigned char*) malloc(receivedPacketLength);
for (i = 0; i < receivedPacketLength; i++) {
tempBuffer[i] = spiBuff[1 + i];
}
// Check CRC:
if (computeCRC8(tempBuffer, receivedPacketLength) ==
spiBuff[receivedPacketLength + 1]) {
// CRC OK.
278
// Extract data from packet for publishing:
// Remote control battery voltage level:
for (i = 0; i < 4; i++) {
floatExtracted[i] = tempBuffer[1 + i];
}
memcpy(&batteryRemote, floatExtracted, sizeof (float));
// Remote control battery discharge percentage level:
for (i = 0; i < 4; i++) {
floatExtracted[i] = tempBuffer[5 + i];
}
memcpy(&batteryRemotePercentage, floatExtracted, sizeof
(float));
// RFM43B remote control transmitter temperature:
for (i = 0; i < 4; i++) {
floatExtracted[i] = tempBuffer[9 + i];
}
memcpy(&temperatureRFM43B, floatExtracted, sizeof
(float));
}
break;
case PACKET_ID_ANALOG:
// This packet reports the current position of the joystick.
// Extract packet:
receivedPacketLength = 3;
tempBuffer = (unsigned char*) malloc(receivedPacketLength);
for (i = 0; i < receivedPacketLength; i++) {
tempBuffer[i] = spiBuff[1 + i];
}
// Check CRC:
if (computeCRC8(tempBuffer, receivedPacketLength) ==
spiBuff[receivedPacketLength + 1]) {
// CRC OK.
// Send packet to high-level application:
if (RFM31B_ActiveConnection == 1) {
// A high-level application is connected in
MODE_RECEIVE_DATA:
pthread_mutex_lock(&sendDataMutex); // Lock mutex.
packetLength = receivedPacketLength;
memcpy(packetBuffer, tempBuffer,
receivedPacketLength);
packetCounter++;
packetCounterAnalog++;
pthread_mutex_unlock(&sendDataMutex); // Unlock
mutex.
pthread_cond_signal(&sendDataCondition); // Signal
TouchSensorHandler thread.
}
}
break;
case PACKET_ID_DIGITAL:
// This packet reports the pushed buttons. Programmable
buttons must be extracted from the packet and not report them.
// Extract packet:
receivedPacketLength = 3;
279
tempBuffer = (unsigned char*) malloc(receivedPacketLength);
for (i = 0; i < receivedPacketLength; i++) {
tempBuffer[i] = spiBuff[1 + i];
}
// Check CRC:
if (computeCRC8(tempBuffer, receivedPacketLength) ==
spiBuff[receivedPacketLength + 1]) {
// CRC OK.
// Extract programmable buttons data from packet for
publishing:
if (programmableButtonsActiveConnection != 0) {
pthread_mutex_lock(&programmableButtonsMutex); //
Lock mutex.
programmableButtonsFlag = programmableButtonsFlag |
((tempBuffer[1] & 0x10) << 1) | ((tempBuffer[1] & 0x20) >> 1) |
((tempBuffer[1] & 0x40) >> 3);
pthread_mutex_unlock(&programmableButtonsMutex); //
Unlock mutex.
pthread_cond_signal(&programmableButtonsCondition);
// Signal application thread.
} // Only update flag if a high application is
connected.
tempBuffer[1] = tempBuffer[1] & 0x0F; // Delete
programmable buttons information from packet.
// Send packet to high-level application:
if ((RFM31B_ActiveConnection == 1) && ((tempBuffer[1] !=
0x00) || (tempBuffer[2] != 0))) {
// A high-level application is connected in
MODE_RECEIVE_DATA:
pthread_mutex_lock(&sendDataMutex); // Lock mutex.
packetLength = receivedPacketLength;
memcpy(packetBuffer, tempBuffer,
receivedPacketLength);
packetCounter++;
packetCounterDigital++;
pthread_mutex_unlock(&sendDataMutex); // Unlock
mutex.
pthread_cond_signal(&sendDataCondition); // Signal
TouchSensorHandler thread.
}
}
break;
} // If packet ID is invalid or contains an error, the packet is
dropped.
}
void resetRFM31B_FIFO() {
unsigned char buffer[2]; // Buffer used to react against an
interruption by writing data to RFM31B.
buffer[0] = 0x08; // Address.
buffer[1] = 0x12; // Set ffclrrx bit to '1'.
if (accessRFM31B_Registers(1, buffer, 2) < 0) {
return;
}
buffer[0] = 0x08; // Address.
buffer[1] = 0x10; // Set ffclrrx bit to '0'.
280
if (accessRFM31B_Registers(1, buffer, 2) < 0) {
return;
}
}
void setupRFM31B_Module() {
unsigned char* setupBuffer; // Buffer used for setup transmissions.
int bufferLength; // Length of the buffer to be transmitted via SPI.
int fc; // Parsed central frequency value to be written in the
appropriate register.
// Reset all RFM31B registers for clean setup:
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x07; // Address.
setupBuffer[1] = 0x80; // Reset.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error resetting RFM31B registers\n");
return;
}
usleep(1000); // Wait 1 ms.
// Reading of version register at address 0x01. If the result is not
0x06, the RFM31B module is not working properly:
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x01; // Address.
if (accessRFM31B_Registers(0, setupBuffer, bufferLength) < 0) {
printf("Error reading RFM31B firmware version\n");
return;
}
fc = computeCentralFrequency();
// Setup RFM31B registers if firmware reading is successful (in
transmission blocks from Reserved to Reserved registers):
if (setupBuffer[1] == 0x06) {
// 1st block: Address 0x05-0x10 (12):
bufferLength = 13;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x05; // Address.
setupBuffer[1] = 0x92; // Interrupt Enable 1.
setupBuffer[2] = 0x00; // Interrupt Enable 2.
setupBuffer[3] = 0x01; // Operating & Function Control 1.
setupBuffer[4] = 0x00; // Operating & Function Control 2.
setupBuffer[5] = 0x7F; // Crystal Oscillator Load Capacitance.
setupBuffer[6] = 0x00; // Microcontroller output clock.
setupBuffer[7] = 0x00; // GPIO0 Configuration.
setupBuffer[8] = 0x00; // GPIO1 Configuration.
setupBuffer[9] = 0x00; // GPIO2 Configuration.
setupBuffer[10] = 0x00; // I/O Port Configuration.
setupBuffer[11] = 0x00; // ADC Configuration.
setupBuffer[12] = 0x00; // ADC Sensor Amplifier Offset.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 1st block registers\n");
return;
}
// 2nd block: Address 0x12-0x16 (5), address 0x19 & address
0x1A:
281
bufferLength = 6;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x12; // Address.
setupBuffer[1] = 0x20; // Temperature Sensor Control.
setupBuffer[2] = 0x00; // Temperature Value Offset.
setupBuffer[3] = 0x00; // Wake-Up Timer Period 1.
setupBuffer[4] = 0x00; // Wake-Up Timer Period 2.
setupBuffer[5] = 0x00; // Wake-Up Timer Period 3.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 2nd block registers\n");
return;
}
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x19; // Address.
setupBuffer[1] = 0x01; // Low-Duty Cycle Mode Duration.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x19 register\n");
return;
}
setupBuffer[0] = 0x1A; // Address.
setupBuffer[1] = 0x00; // Low Battery Detector Threshold.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x1A register\n");
return;
}
// 3rd block: Address 0x1C-0x25 (10), address 0x27 & address
0x2A:
bufferLength = 11;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x1C; // Address.
setupBuffer[1] = 0x1E; // IF Filter Bandwidth.
setupBuffer[2] = 0x40; // AFC Loop Gearshift Override.
setupBuffer[3] = 0x0A; // AFC Timing Control.
setupBuffer[4] = 0x03; // Clock Recovery Gearshift Override.
setupBuffer[5] = 0xE8; // Clock Recovery Oversampling Ratio.
setupBuffer[6] = 0x60; // Clock Recovery Offset 2.
setupBuffer[7] = 0x20; // Clock Recovery Offset 1.
setupBuffer[8] = 0xC5; // Clock Recovery Offset 0.
setupBuffer[9] = 0x00; // Clock Timing Loop Gain 1.
setupBuffer[10] = 0x05; // Clock Timing Loop Gain 0.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 3rd block registers\n");
return;
}
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x27; // Address.
setupBuffer[1] = 0x64; // RSSI Threshold for Clear Channel
Indicator.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x27 register\n");
return;
}
setupBuffer[0] = 0x2A; // Address.
setupBuffer[1] = 0x20; // AFC Limiter.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x2A register\n");
return;
282
}
// 4th block: Address 0x2C-0x2E (3) & address 0x30:
bufferLength = 4;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x2C; // Address.
setupBuffer[1] = 0x2A; // OOK Counter Value 1.
setupBuffer[2] = 0x71; // OOK Counter Value 2.
setupBuffer[3] = 0x2A; // Slicer Peak Hold.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 4th block registers\n");
return;
}
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x30; // Address.
setupBuffer[1] = 0x88; // Data Access Control.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x30 register\n");
return;
}
// 5th block: Address 0x32-0x39 (8):
bufferLength = 9;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x32; // Address.
setupBuffer[1] = 0x00; // Header Control 1.
setupBuffer[2] = 0x06; // Header Control 2.
setupBuffer[3] = 0x08; // Preamble Length.
setupBuffer[4] = 0x22; // Preamble Detection Control.
setupBuffer[5] = 0x42; // Sync Word 3.
setupBuffer[6] = 0x44; // Sync Word 2.
setupBuffer[7] = 0x4D; // Sync Word 1.
setupBuffer[8] = 0x44; // Sync Word 0.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 5th block registers\n");
return;
}
// 6th block: Address 0x3F-0x46 (8), address 0x4F, address 0x60,
address 0x62 & address 0x69:
bufferLength = 9;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x3F; // Address.
setupBuffer[1] = 0x42; // Check Header 3.
setupBuffer[2] = 0x44; // Check Header 2.
setupBuffer[3] = 0x4D; // Check Header 1.
setupBuffer[4] = 0x44; // Check Header 0.
setupBuffer[5] = 0xFF; // Header Enable 3.
setupBuffer[6] = 0xFF; // Header Enable 2.
setupBuffer[7] = 0xFF; // Header Enable 1.
setupBuffer[8] = 0xFF; // Header Enable 0.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 6th block registers\n");
return;
}
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x4F; // Address.
setupBuffer[1] = 0x10; // ADC8 Control.
283
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0)
printf("Error writing 0x4F register\n");
return;
}
setupBuffer[0] = 0x60; // Address.
setupBuffer[1] = 0xA0; // Channel Filter Coefficient Address.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0)
printf("Error writing 0x60 register\n");
return;
}
setupBuffer[0] = 0x62; // Address.
setupBuffer[1] = 0x24; // Crystal Oscillator/Control Test.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0)
printf("Error writing 0x62 register\n");
return;
}
setupBuffer[0] = 0x69; // Address.
setupBuffer[1] = 0x20; // AGC Override 1.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0)
printf("Error writing 0x69 register\n");
return;
}
{
{
{
{
// 7th block: Address 0x70-0x72 (3):
bufferLength = 4;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x70; // Address.
setupBuffer[1] = 0x20; // Modulation Mode Control 1.
setupBuffer[2] = 0x23; // Modulation Mode Control 2.
setupBuffer[3] = 0x50; // Frequency Deviation.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 7th block registers\n");
return;
}
// 8th block: Address 0x73-0x77 (5):
bufferLength = 6;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x73; // Address.
setupBuffer[1] = 0x00; // Frequency Offset 1.
setupBuffer[2] = 0x00; // Frequency Offset 2.
setupBuffer[3] = 0x53; // Frequency Band Select 2.
setupBuffer[4] = ((fc & 0xFF00) >> 8); // Nominal Carrier
Frequency 1.
setupBuffer[5] = (fc & 0xFF); // Nominal Carrier Frequency 0.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 8th block registers\n");
return;
}
// 9th block: Address 0x79-0x7A (2) & address 0x7E:
bufferLength = 3;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x79; // Address.
setupBuffer[1] = 0x00; // Frequency Hoping Channel Select.
setupBuffer[2] = 0x00; // Frequency Hopping Step Size.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 9th block registers\n");
return;
}
284
bufferLength = 2;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x7E; // Address.
setupBuffer[1] = 0x37; // RX FIFO Control.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error writing 0x7E register\n");
return;
}
// Clean interruptions by reading interrupt registers:
bufferLength = 3;
setupBuffer = (unsigned char*) malloc(bufferLength);
setupBuffer[0] = 0x03; // Address of Interrupt Status 1.
if (accessRFM31B_Registers(1, setupBuffer, bufferLength) < 0) {
printf("Error reading Interrupt Status registers\n");
return;
}
}
}
SocketManager.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef SOCKETMANAGER_H
#define SOCKETMANAGER_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
// Functions:
int acceptConnection(struct sockaddr_un, int); // Blocking function
for a server socket to wait for a connection.
void closeSocket(int, char*); // Close a socket.
void connectSocket(int, struct sockaddr_un); // Connect to a socket.
int openSocket(); // Function to open sockets.
int receiveData(int, unsigned char*, int); // Receive data from a
socket.
int sendData(int, unsigned char*, int); // Send data through a
socket.
struct sockaddr_un setupSocket(int, char*, char); // Setup opened
socket.
#ifdef __cplusplus
}
#endif
285
#endif /* SOCKETMANAGER_H */
SocketManager.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* SocketManager. The functions defined in this file are used to manage
* every socket that all threads need.
*/
#include "SocketManager.h"
int acceptConnection(struct sockaddr_un address, int fd) {
// address: The directory struct obtained from setupSocket().
// fd: Socket's file descriptor.
// Returns: Socket File Descriptor of the new socket.
socklen_t addressLength = sizeof (address);
int length = 1;
int fd2;
if (listen(fd, 100) != 0) {
perror("Error listening");
}
fd2 = accept(fd, (struct sockaddr*) &address, &addressLength); //
Blocking function.
if (fd2 < 0) {
perror("Accept connection failed");
}
return fd2;
}
void closeSocket(int fd, char* directory) {
// fd: Socket's File Descriptor.
// directory: The directory used for transmission.
close(fd); // Close socket.
unlink(directory);
}
void connectSocket(int fd, struct sockaddr_un directory) {
// fd: Socket's file descriptor.
// directory: The directory struct obtained from setupSocket().
unsigned char dummyBuffer[1];
/* WARNING: Due to a bug in socket management done by Linux OS when
more than
* one thread in the same program open different sockets, a dummy
connect/
* disconnect is required before real connect: */
connect(fd, (struct sockaddr*) &directory, sizeof (struct
sockaddr_un)); // Dummy connect.
dummyBuffer[0] = 'C'; // Close connection command.
sendData(fd, dummyBuffer, 1); // Send Close connection command,
common in all handlers.
286
close(fd); // Close socket.
fd = socket(AF_UNIX, SOCK_STREAM, 0); // Reopen the socket.
// Real connect:
if (connect(fd, (struct sockaddr*) &directory, sizeof (struct
sockaddr_un)) < 0) {
perror("Error connecting socket");
}
}
int openSocket() {
// Returns: File Descriptor of the opened socket.
int fd;
signal(SIGPIPE, SIG_IGN); // Ignore SIGPIPE signal to avoid crashing
the program if this signal is sent.
close(socket(AF_UNIX, SOCK_STREAM, 0));// Open and close for reset.
fd = socket(AF_UNIX, SOCK_STREAM, 0); // Datagram socket.
if (fd < 0) {
perror("Error opening socket");
}
return fd;
}
int receiveData(int fd, unsigned char* buffer, int bufferLength) {
// fd: Socket's File Descriptor.
// buffer: Data buffer to be read.
// bufferLength: Total amount of Bytes to be received.
// Returns: The return value of read() function.
int ret;
int i;
for (i = 0; i < bufferLength; i++) {
ret = recv(fd, &buffer[i], 1, MSG_WAITALL);
if (ret < 0) {
perror("Error reading at socket");
}
}
return ret;
}
int sendData(int fd, unsigned char* buffer, int bufferLength) {
// fd: Socket's File Descriptor.
// buffer: Data buffer to be written.
// bufferLength: Total amount of Bytes to be sent.
// Returns: The return value of write() function.
int ret;
int i;
for (i = 0; i < bufferLength; i++) {
ret = send(fd, &buffer[i], 1, MSG_NOSIGNAL);
if (ret < 0) {
perror("Error sending at socket");
}
}
return ret;
287
}
struct sockaddr_un setupSocket(int fd, char* path, char serverFlag) {
// fd: Socket's File Descriptor.
// path: The directory of the socket.
// serverFlag: This flag must be set to 1 is this routine is called
from the server.
struct sockaddr_un directory;
// Generate directory struct:
memset(&directory, 0, sizeof (struct sockaddr_un)); // Setup
structure.
directory.sun_family = AF_UNIX;
strncpy(directory.sun_path, path, sizeof (directory.sun_path));
// For the server's edge,
if (serverFlag == 1) {
// Bind the socket to the corresponding directory:
if (bind(fd, (struct sockaddr*) &directory, sizeof (struct
sockaddr_un)) < 0) {
perror("Error binding socket");
}
}
return directory;
}
SPIManager.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef SPIMANAGER_H
#define SPIMANAGER_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
#include
#include
<fcntl.h>
<linux/spi/spidev.h>
<stdio.h>
<string.h>
<sys/ioctl.h>
<termios.h>
<unistd.h>
// Constants:
#define SPI_PORT "/dev/spidev0.0"
// Global variables:
char bitsWord; // Number of bits per Byte.
char delayUS; // Transfer delay in microseconds.
int maxSpeedHz; // Transfer rate (20 MHz).
char mode; // SPI mode (clock polarity and clock phase).
// Functions:
288
void closeSPI(int); // Close SPI interface.
int openSPI(); // Open SPI port.
void setupSPI(int); // SPI port setup function.
int transferSPI(int, unsigned char*, int); // Transfer synchronous
data at SPI port.
#ifdef __cplusplus
}
#endif
#endif /* SPIMANAGER_H */
SPIManager.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* SPIManager. The functions defined in this file are used to open SPI
interface.
*/
#include "SPIManager.h"
void closeSPI(int fd) {
// fd: SPI File Descriptor.
close(fd);
}
int openSPI() {
// Returns: SPI File Descriptor.
int fd;
// Open SPI port:
fd = open(SPI_PORT, O_RDWR);
if (fd < 0) {
perror("Cannot open SPI port");
}
return fd;
}
void setupSPI(int fd) {
// fd: The File Descriptor that identifies the SPI port.
int ret; // Ioctl return value.
// Setup SPI settings variables:
bitsWord = 8;
maxSpeedHz = 15625000;
mode = SPI_MODE_0;
// Setup SPI mode:
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); // Setup writing mode.
if (ret < 0) {
perror("Cannot set writing mode");
}
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); // Setup reading mode.
289
if (ret < 0) {
perror("Cannot set reading mode");
}
// Setup bits per word:
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bitsWord); // Setup
writing bits per word.
if (ret < 0) {
perror("Cannot set writing bits per word");
}
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bitsWord); // Setup
reading bits per word.
if (ret < 0) {
perror("Cannot set reading bits per word");
}
// Setup maximum speed bus:
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &maxSpeedHz); // Setup
writing maximum speed.
if (ret < 0) {
perror("Cannot set writing maximum speed");
}
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &maxSpeedHz); // Setup
reading maximum speed.
if (ret < 0) {
perror("Cannot set reading maximum speed");
}
}
int transferSPI(int fd, unsigned char* bufferTX_RX, int length) {
// fd: SPI File Descriptor.
// bufferTX_RX: The buffer where Bytes to be transmitted are stored.
// length: Number of Bytes to be transfered.
// Returns: Result of ioctl() function.
struct spi_ioc_transfer transfer[length];
int i, ret;
// Setup transfer structure for all Bytes:
for (i = 0; i < length; i++) {
memset(&transfer[i], 0, sizeof (transfer[i])); // Allocate
memory.
transfer[i].tx_buf = (unsigned long) (bufferTX_RX + i);
transfer[i].rx_buf = (unsigned long) (bufferTX_RX + i);
transfer[i].len = sizeof(*(bufferTX_RX + i));
transfer[i].delay_usecs = delayUS;
transfer[i].bits_per_word = bitsWord;
transfer[i].speed_hz = maxSpeedHz;
transfer[i].cs_change = 0;
}
// Transfer process:
ret = ioctl(fd, SPI_IOC_MESSAGE(length), &transfer);
if (ret < 0) {
perror("Cannot transfer SPI message.");
}
return ret;
}
290
TemperatureData.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef TEMPERATUREDATA_H
#define TEMPERATUREDATA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
#include <string.h>
#include "ArduinoPeripheral.h"
#include "FileManager.h"
// Constants:
#define NULL_VALUE NAN
#define NULL_VALUE_TEXT "-"
#define RPI_TEMPERATURE_FILE "/sys/class/thermal/thermal_zone0/temp"
#define TEMPS_BUFFER_SIZE 50
#define TEMPERATURES_FILE "/tmp/idroid02/temperatures.txt"
// Global variables:
char temperatureDataAlive; // Flag to check if thread is alive.
float temperatureRPi; // Raspberry Pi CPU internal temperature.
float temperatureRFM31B; // 433 MHz receiver CPU internal
temperature.
float temperatureRFM43B; // 433 MHz transmitter CPU internal
temperature.
// Functions:
void parseAndAppendTempValue(float, char*, int*); // Parse any float
temperature value and store it to the string chain.
void resetTemperatureValues(char*); // Reset temperature values for
next reading.
void* temperatureData_Start(void*); // Handler start function.
#ifdef __cplusplus
}
#endif
#endif /* TEMPERATUREDATA_H */
TemperatureData.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* TemperatureData. This thread is in charge of writing a file
containing the
* value of all temperatures that I-Droid02 manages.
*/
291
#include "TemperatureData.h"
void parseAndAppendTempValue(float tempValue, char* tempsBuffer, int*
bytesCount) {
// tempValue: Temperature value to be parsed and stored to the
buffer.
// tempsBuffer: String buffer to store temperature values.
// bytesCount: Pointer to number of Bytes that tempsBuffer contains.
char temporal[10]; // Buffer to store temporal parsed values.
if (isnan(tempValue)) {
// New temperature value is not available.
*bytesCount = *bytesCount + 1;
if (*bytesCount == 1) {
// 1st Byte to append.
strncpy(tempsBuffer, NULL_VALUE_TEXT, *bytesCount);
} else {
strncat(tempsBuffer, NULL_VALUE_TEXT, *bytesCount);
}
} else {
// New temperature value is available.
*bytesCount = *bytesCount + (int) (log10(tempValue * 10) + 2);
sprintf(temporal, "%.1f", tempValue);
if (*bytesCount == (int) (log10(tempValue * 10) + 2)) {
// 1st Bytes to append.
strncpy(tempsBuffer, temporal, *bytesCount);
} else {
strncat(tempsBuffer, temporal, *bytesCount);
}
}
*bytesCount = *bytesCount + 1;
strncat(tempsBuffer, "\n", *bytesCount - 1);
}
void resetTemperatureValues(char* tempsBuffer) {
// tempsBuffer: Buffer of Bytes to be written.
int i;
temperatureAmbient = NULL_VALUE;
temperatureRPi = NULL_VALUE;
temperatureRFM31B = NULL_VALUE;
temperatureRFM43B = NULL_VALUE;
// Temperature values will be written following the previous order.
for (i=0; i<TEMPS_BUFFER_SIZE; i++) {
tempsBuffer[i] = '\0';
}
}
void* temperatureData_Start(void* arg) {
FILE* rpiTemp; // File Descriptor to read Raspberry Pi internal
temperature.
unsigned char tempsBuffer[TEMPS_BUFFER_SIZE]; // Buffer of Bytes to
be written.
char* rpiTempReadBuffer; // Buffer to store read value from RPI
internal temperature file.
int bytesCount; // Number of Bytes to be written.
temperatureDataAlive = 1;
292
// Initial values of the temperatures before recollect data:
resetTemperatureValues(tempsBuffer);
// Loop to write temperatures file:
while (1) {
bytesCount = 0;
// Ambient temperature:
parseAndAppendTempValue(temperatureAmbient, tempsBuffer,
&bytesCount);
// Raspberry Pi CPU internal temperature (always available):
rpiTemp = openFile(RPI_TEMPERATURE_FILE, READ); // Open file.
readLineFromFile(rpiTemp, &rpiTempReadBuffer, 6); // Read
temperature in mºC.
temperatureRPi = atoi(rpiTempReadBuffer) / 1000.0; // Parse
float.
closeFile(rpiTemp); // Close file.
parseAndAppendTempValue(temperatureRPi, tempsBuffer,
&bytesCount);
// RFM31B 433 MHz receiver internal temperature:
parseAndAppendTempValue(temperatureRFM31B, tempsBuffer,
&bytesCount);
// RFM43B 433 MHz transmitter internal temperature:
parseAndAppendTempValue(temperatureRFM43B, tempsBuffer,
&bytesCount);
// Write temperatures to the corresponding file:
echoLineToFile(TEMPERATURES_FILE, tempsBuffer);
// Reset values and put the thread to sleep 10 seconds:
resetTemperatureValues(tempsBuffer);
sleep(10);
}
temperatureDataAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
TorchHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef LANTERNHANDLER_H
#define LANTERNHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
293
#include
#include
#include
#include
"FileManager.h"
"SocketManager.h"
"UARTManager.h"
"utilities.h"
// Constants:
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_CURRENT_STATE 'G'
#define COMMAND_RECOGNIZE_LANTERN 'R'
#define COMMAND_TURN_OFF 'F'
#define COMMAND_TURN_ON 'N'
#define TORCH_BUFFER_SIZE 5
#define TORCH_DIRECTORY "/tmp/idroid02/torch"
#define TORCH_UART "/dev/torch"
// Global variables:
char torchHandlerAlive; // Flag to check if thread is alive.
/* Variable to store current written char through torch UART that
represents
* torch intensity. 9 possible torch intensities are available.
Written
* char value of intensity 1 is 0b11111111. Next intensity value (2)
turns
* first bit to '0' (0b01111111). Maximum intensity is 9
(0b00000000),
*/
unsigned char torchIntensity;
// Array used to parse intensity value to the corresponding written
UART Byte:
unsigned char TORCH_UART_BYTES[9];
char transmission; // Flag to indicate if UART thread must transmit
or not.
// Functions:
unsigned char getIntensity(); // Routine to parse current written
UART Byte to intensity value.
void* torchHandler_Start(void*); // Handler start function.
int torchSetupUART(); // Function to setup torch associated UART.
void parseIntensity(unsigned char); // Routine to parse an intensity
value to written UART Byte.
void* startUART_Transmission(void*); // Thread used to transmit over
torch UART start function.
#ifdef __cplusplus
}
#endif
#endif /* LANTERNHANDLER_H */
TorchHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* TorchHandler: This thread manages the communication between I-Droid02
* Driver and a high-level application which requests the use of the
torch,
294
* as one of the removable tools. This thread remains slept if any
application
* requests the use of the torch.
*/
#include "TorchHandler.h"
unsigned char getIntensity() {
// Returns: The actual intensity value corresponding to the current
written UART Byte.
unsigned char intensity;
int i;
for (i=0; i<9; i++) {
if (TORCH_UART_BYTES[i] == torchIntensity) {
intensity = i + 1;
}
}
return intensity;
}
void* torchHandler_Start(void* arg) {
unsigned char torchSendBuffer[TORCH_BUFFER_SIZE]; // Buffer of Bytes
to send through socket.
unsigned char torchReceiveBuffer[TORCH_BUFFER_SIZE]; // Buffer of
Bytes to receive through socket.
int torchFD, torchFD2; // Socket File Descriptors.
struct sockaddr_un torchSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
int uartFD; // File Descriptor used to check if torch is present by
opening its UART port temporally.
pthread_t uartThread; // Descriptor of thread in charge of write to
torch associated UART.
int i, j;
torchHandlerAlive = 1;
torchIntensity = 9;
transmission = 0;
// Init TORCH_UART_BYTES:
TORCH_UART_BYTES[0] = 0xFF;
TORCH_UART_BYTES[1] = 0x7F;
TORCH_UART_BYTES[2] = 0x3F;
TORCH_UART_BYTES[3] = 0x1F;
TORCH_UART_BYTES[4] = 0x0F;
TORCH_UART_BYTES[5] = 0x07;
TORCH_UART_BYTES[6] = 0x03;
TORCH_UART_BYTES[7] = 0x01;
TORCH_UART_BYTES[8] = 0x00;
// Open and setup server socket:
torchFD = openSocket();
torchSocketStruct = setupSocket(torchFD, TORCH_DIRECTORY, 1);
while (1) {
// Wait for a high-level application to connect:
torchFD2 = acceptConnection(torchSocketStruct, torchFD);
// Application connected.
295
do {
// Wait for a command coming from connected application:
if (receiveData(torchFD2, torchReceiveBuffer, 1) < 0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (torchReceiveBuffer[0]) {
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
case COMMAND_GET_CURRENT_STATE:
// The application wants to get the current state of
the torch:
// Parse current torch state and intensity:
torchSendBuffer[0] = transmission;
torchSendBuffer[1] = getIntensity();
if (sendData(torchFD2, torchSendBuffer, 2) < 0) {
// Error sending data.
closeConnectionFlag = 1;
}
break;
case COMMAND_RECOGNIZE_LANTERN:
// The application wants to know whether torch is
connected or not:
// Try to open port:
uartFD = openUART(TORCH_UART);
if (uartFD != -1) {
// Torch is present.
torchSendBuffer[0] = 1;
closeUART(uartFD);
} else {
// Torch is not present or it is in use.
torchSendBuffer[0] = 0;
}
// Send result:
if (sendData(torchFD2, torchSendBuffer, 1) < 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_TURN_OFF:
// The application wants to turn the torch OFF:
// Stop thread in charge of UART transmission:
transmission = 0;
break;
case COMMAND_TURN_ON:
// The application wants to turn the torch ON:
// Read the torch intensity:
if (receiveData(torchFD2, torchReceiveBuffer, 1) <
0) {
// Error receiving data.
296
closeConnectionFlag = 1;
break;
}
parseIntensity(torchReceiveBuffer[0]);
// Start UART transmission thread:
if (transmission == 0) {
pthread_create(&uartThread, NULL,
startUART_Transmission, NULL);
}
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(torchFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(torchFD, TORCH_DIRECTORY);
torchHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
int torchSetupUART() {
// Returns: The UART File Descriptor associated to the torch.
int fd;
fd = openUART(TORCH_UART);
setupUART(fd, B115200);
return fd;
}
void parseIntensity(unsigned char intensity) {
// intensity: The intensity value (from 0 to 8 both included).
if ((intensity < 1) || (intensity > 9)) {
intensity = 9;
}
torchIntensity = TORCH_UART_BYTES[intensity - 1];
}
void* startUART_Transmission(void* arg) {
int torchUSB_FD;
unsigned char UART_Buffer[1];
// Open and setup UART:
torchUSB_FD = openUART(TORCH_UART);
if (torchUSB_FD == -1) {
// Error opening UART.
transmission = 0;
} else {
setupUART(torchUSB_FD, B115200);
transmission = 1;
}
297
// Start UART transmission if torch is connected:
while (transmission == 1) {
UART_Buffer[0] = torchIntensity; // Intensity value may change
during transmission.
if (writeUART(torchUSB_FD, UART_Buffer, 1) < 0) {
// Error during UART transmission.
transmission = 0;
}
}
// Close UART:
closeUART(torchUSB_FD);
pthread_exit(EXIT_SUCCESS);
}
TouchSensorHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef TOUCHSENSORHANDLER_H
#define TOUCHSENSORHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pthread.h>
#include <stdlib.h>
#include "ArduinoPeripheral.h"
#include "SocketManager.h"
// Constants:
#define COMMAND_ATTACH_SENSOR 'A'
#define COMMAND_CLOSE_CONNECTION 'C'
#define TOUCH_SENSOR_BUFFER_SIZE 1
#define TOUCH_SENSOR_DIRECTORY "/tmp/idroid02/touchSensor"
// Global variables:
char touchSensorActiveConnection; // Flag to inform if a client is
connected.
char touchSensorHandlerAlive; // Flag to check if thread is alive.
pthread_mutex_t touchSensorMutex;
pthread_cond_t touchSensorCondition;
// Functions:
void ackTouch(); // Function called when high-level application
acknowledges touch sensor.
void* touchSensorHandler_Start(void*); // Handler start function.
#ifdef __cplusplus
}
#endif
#endif /* TOUCHSENSORHANDLER_H */
298
TouchSensorHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* TouchSensorHandler: This thread manages the communication between IDroid02
* Driver and a high-level application which requests the use of the
touch
* sensor. This thread remains slept if any application requests the
sensor.
* When an application requests this thread, it also gets blocked until
* ArduinoHandler thread notifies the reception of a touch sensor.
*/
#include "TouchSensorHandler.h"
void ackTouch() {
// Reset value:
touchSensorFlag = 0;
}
void* touchSensorHandler_Start(void* arg) {
unsigned char touchSocketSendBuffer[TOUCH_SENSOR_BUFFER_SIZE]; //
Buffer of Bytes to send through socket.
unsigned char touchSocketReceiveBuffer[TOUCH_SENSOR_BUFFER_SIZE]; //
Buffer of Bytes to receive through socket.
int touchFD, touchFD2; // Socket File Descriptors.
struct sockaddr_un touchSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
touchSensorHandlerAlive = 1;
pthread_mutex_init(&touchSensorMutex, NULL);
pthread_cond_init(&touchSensorCondition, NULL);
// Open and setup server socket:
touchFD = openSocket();
touchSocketStruct = setupSocket(touchFD, TOUCH_SENSOR_DIRECTORY, 1);
while (1) {
// Wait for a high-level application to connect:
touchFD2 = acceptConnection(touchSocketStruct, touchFD);
touchSensorActiveConnection = 1;
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(touchFD2, touchSocketReceiveBuffer, 1) < 0)
{
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (touchSocketReceiveBuffer[0]) {
case COMMAND_ATTACH_SENSOR:
299
// The application wants to wait until any of the
buttons is pressed:
// Wait for a change of touchSensorFlag value:
pthread_mutex_lock(&touchSensorMutex);
while (touchSensorFlag == 0) {
// Block thread until signal is received and
free mutex:
pthread_cond_wait(&touchSensorCondition,
&touchSensorMutex); // Blocking function.
}
// Send buttonsFlag to high-level application:
touchSocketSendBuffer[0] = touchSensorFlag;
if (sendData(touchFD2, touchSocketSendBuffer, 1) <
0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
ackTouch();
pthread_mutex_unlock(&touchSensorMutex);
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(touchFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
touchSensorActiveConnection = 0;
}
// Close socket:
closeSocket(touchFD, TOUCH_SENSOR_DIRECTORY);
touchSensorHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
UARTManager.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef UARTMANAGER_H
#define UARTMANAGER_H
#ifdef __cplusplus
extern "C" {
300
#endif
#include <fcntl.h>
#include <stdio.h>
#include <termios.h>
// Functions:
void closeUART(int); // Close an opened UART interface.
int openUART(char*); // Open an UART port.
int readUART(int, unsigned char*, int); // Read data from UART.
void setupUART(int, int); // UART port setup function.
int writeUART(int, unsigned char*, int); // Write data to UART.
#ifdef __cplusplus
}
#endif
#endif /* UARTMANAGER_H */
UARTManager.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* UARTManager. The functions defined in this file are used to open any
UART
* interface.
*/
#include "UARTManager.h"
void closeUART(int fd) {
// fd: UART File Descriptor.
close(fd);
}
int openUART(char* device) {
// device: Route to File System file that describes the UART device.
// Returns: UART File Descriptor or -1 if an error occurs.
int fd; // The File Descriptor that identifies the UART port.
close(open(device, O_RDWR | O_NOCTTY)); // Open and close for reset.
fd = open(device, O_RDWR | O_NOCTTY);
if (fd == -1) {
// An error has occurred.
printf("Unable to open ");
printf(device);
printf(" serial device.\n\r");
}
return fd;
}
int readUART(int fd, unsigned char* buffer, int length) {
// fd: UART File Descriptor.
// buffer: The buffer to store read data.
301
// length: Number of Bytes to be read.
// Returns: The result of read() function.
int ret;
ret = read(fd, (void*) buffer, length); // Blocking function.
if (ret < 0) {
printf("Error reading at UART\n");
}
return ret;
}
void setupUART(int fd, int speed) {
// fd: UART File Descriptor.
// speed: Flag to determine speed (use termios.h flag(.
struct termios options; // UART port options.
// Retrieve current options:
tcgetattr(fd, &options);
// Setup attributes:
options.c_cflag = (speed | CS8 | CLOCAL | CREAD);
options.c_iflag = IGNPAR; // Ignore characters with parity errors.
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(fd, TCIFLUSH); // Empty input buffer.
tcsetattr(fd, TCSANOW, &options); // Set new attributes.
}
int writeUART(int fd, unsigned char* buffer, int length) {
// fd: UART File Descriptor.
// buffer: The buffer to store read data.
// length: Number of Bytes to be read.
// Returns: The result of write() function.
int ret;
ret = write(fd, (void*) buffer, length);
if (ret < 0) {
printf("Error writing at UART\n");
}
return ret;
}
UltrasonicHandler.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef ULTRASONICDATA_H
#define ULTRASONICDATA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <math.h>
302
#include <pthread.h>
#include <string.h>
#include "ArduinoPeripheral.h"
#include "FileManager.h"
// Constants:
#define COMMAND_SET_SENSOR 'S'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_CURRENT_SELECTED_SENSOR 'G'
#define COMMAND_RESET_DEFAULT 'R'
#define NULL_VALUE NAN
#define NULL_VALUE_TEXT "-"
#define ULTRASONIC_BUFFER_SIZE 5
#define ULTRASONIC_FILE "/tmp/idroid02/ultrasonic.txt"
#define ULTRASONIC_DIRECTORY "/tmp/idroid02/ultrasonic"
// Global variables:
char ultrasonicHandlerAlive; // Flag to check if thread is alive.
// Functions:
void* socketChangeUltrasonicSensor(void*); // Socket thread start
function.
void parseAndAppendUltrasonicValue(float, char*, int*); // Parse any
float ultrasonic distance value and store it to the string chain.
void sendUltrasonicCommand(); // Function to send ultrasonic change
sensor command to Arduino.
void resetUltrasonicValue(); // Reset ultrasonic distance value for
next reading.
void* ultrasonicHandler_Start(void*); // Handler start function.
#ifdef __cplusplus
}
#endif
#endif /* ULTRASONICDATA_H */
UltrasonicHandler.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* UltrasonicData. This thread is in charge of writing a file containing
the
* value of the ultrasonic distance obtained from the selected
ultrasonic
* sensor. A part from that, another thread is in charge of manage the
* communication between I-Droid02 Driver and a high-level application
which
* wants to change the ultrasonic sensor. This thread remains slept if
any
* application requests to change it.
*/
#include "UltrasonicHandler.h"
void* socketChangeUltrasonicSensor(void* arg) {
303
unsigned char ultrasonicReceiveBuffer[ULTRASONIC_BUFFER_SIZE]; //
Buffer of Bytes to receive through socket.
unsigned char ultrasonicSendBuffer[ULTRASONIC_BUFFER_SIZE]; //
Buffer of Bytes to send through socket.
int ultrasonicFD, ultrasonicFD2; // Socket File Descriptors.
struct sockaddr_un ultrasonicSocketStruct;
char closeConnectionFlag; // Flag to determine when high-level
application closes the connection.
ultrasonicHandlerAlive = 1;
// Open and setup server socket:
ultrasonicFD = openSocket();
ultrasonicSocketStruct = setupSocket(ultrasonicFD,
ULTRASONIC_DIRECTORY, 1);
resetUltrasonicSensor(); // Set central sensor as default ultrasonic
sensor.
while (1) {
// Wait for a high-level application to connect:
ultrasonicFD2 = acceptConnection(ultrasonicSocketStruct,
ultrasonicFD);
// Application connected.
do {
// Wait for a command coming from connected application:
if (receiveData(ultrasonicFD2, ultrasonicReceiveBuffer, 1) <
0) {
// Error receiving data.
break;
}
// Switch received command and execute it:
switch (ultrasonicReceiveBuffer[0]) {
case COMMAND_SET_SENSOR:
// The application wants to change the ultrasonic
sensor:
// Read value of new selected sensor:
if (receiveData(ultrasonicFD2,
ultrasonicReceiveBuffer, 1) < 0) {
// Error receiving data.
closeConnectionFlag = 1;
break;
}
// Update value only if sent value is valid:
if ((ultrasonicReceiveBuffer[0] >= 0) &&
(ultrasonicReceiveBuffer[0] <= 2)) {
// Valid value:
ultrasonicSensor = ultrasonicReceiveBuffer[0];
// Send Arduino command:
sendUltrasonicCommand();
}
break;
case COMMAND_CLOSE_CONNECTION:
// The application wants to close the connection:
closeConnectionFlag = 1;
304
break;
case COMMAND_GET_CURRENT_SELECTED_SENSOR:
// The application wants to know the current
selected sensor:
ultrasonicSendBuffer[0] = ultrasonicSensor;
if (sendData(ultrasonicFD2, ultrasonicSendBuffer, 1)
< 0) {
// Error sending data.
closeConnectionFlag = 1;
break;
}
break;
case COMMAND_RESET_DEFAULT:
// The application wants to reset the selected
ultrasonic sensor as default (central sensor):
resetUltrasonicSensor();
// Send Arduino command:
sendUltrasonicCommand();
break;
default:
// Received command not identified.
closeConnectionFlag = 1;
break;
}
} while (closeConnectionFlag == 0);
closeSocket(ultrasonicFD2, NULL);
closeConnectionFlag = 0; // Reset flag for a new client.
}
// Close socket:
closeSocket(ultrasonicFD, ULTRASONIC_DIRECTORY);
ultrasonicHandlerAlive = 0;
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void parseAndAppendUltrasonicValue(float ultrasonicValue, char*
tempsBuffer, int* bytesCount) {
// ultrasonicValue: Ultrasonic distance value to be parsed and
stored to the buffer.
// tempsBuffer: String buffer to store the value of ultrasonic
distance.
// bytesCount: Pointer to number of Bytes that tempsBuffer contains.
char temporal[10]; // Buffer to store temporal parsed values.
if (isnan(ultrasonicValue)) {
// New ultrasonic distance value is not available.
*bytesCount = *bytesCount + 1;
if (*bytesCount == 1) {
// 1st Byte to append:
strncpy(tempsBuffer, NULL_VALUE_TEXT, *bytesCount);
} else {
strncat(tempsBuffer, NULL_VALUE_TEXT, *bytesCount);
}
} else {
// New ultrasonic distance value is available.
305
*bytesCount = *bytesCount + (int) (log10(ultrasonicValue * 100)
+ 2);
sprintf(temporal, "%.2f", ultrasonicValue);
if (*bytesCount == (int) (log10(ultrasonicValue * 100) + 2)) {
strncpy(tempsBuffer, temporal, *bytesCount);
} else {
strncat(tempsBuffer, temporal, *bytesCount);
}
}
*bytesCount = *bytesCount + 1;
strncat(tempsBuffer, "\n", *bytesCount);
}
void sendUltrasonicCommand() {
char ultrasonicBuffer[3]; // Buffer to send Arduino command.
// Prepare ultrasonic command:
ultrasonicBuffer[0] = COMMAND_ULTRASONIC;
ultrasonicBuffer[1] = ultrasonicSensor;
ultrasonicBuffer[2] = COMMAND_END_OF_TRANSMISSION;
sendCommandToArduino(ultrasonicBuffer, 3);
}
void resetUltrasonicValue(char* ultrasonicBuffer) {
// ultrasonicBuffer: Buffer of Bytes to be written.
int i;
ultrasonicDistance = NULL_VALUE;
for (i=0; i<ULTRASONIC_BUFFER_SIZE; i++) {
ultrasonicBuffer[i] = '\0';
}
}
void* ultrasonicHandler_Start(void* arg) {
pthread_t sensorChange; // Thread used for high-level applications
to change the sensor.
char ultrasonicBuffer[ULTRASONIC_BUFFER_SIZE]; // Buffer of Bytes to
be written.
int bytesCount; // Number of Bytes to be written.
// Start ultrasonic change sensor thread:
pthread_create(&sensorChange, NULL, socketChangeUltrasonicSensor,
NULL);
pthread_detach(sensorChange);
// Initial value of the ultrasonic distance before recollect data:
resetUltrasonicValue(ultrasonicBuffer);
// Loop to write ultrasonic file:
while (1) {
bytesCount = 0;
// Ultrasonic distance value parsing:
parseAndAppendUltrasonicValue(ultrasonicDistance,
ultrasonicBuffer, &bytesCount);
// Write value to the corresponding file:
echoLineToFile(ULTRASONIC_FILE, ultrasonicBuffer);
306
// Reset values and put the thread to sleep 250 microseconds:
resetUltrasonicValue(ultrasonicBuffer);
usleep(250000);
}
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
utilities.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef UTILITIES_H
#define UTILITIES_H
#ifdef __cplusplus
extern "C" {
#endif
#include <time.h>
// Functions:
char checkTimeDifference(char, struct timespec*, long int); // Check
if time difference has passed.
unsigned char computeCRC8(unsigned char*, int); // Routine to
compute CRC of 8 bits following Dallas/Maxim algorithm.
#ifdef __cplusplus
}
#endif
#endif /* UTILITIES_H */
utilities.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* utilities: This file contains some useful routines.
*/
#include "utilities.h"
char checkTimeDifference(char flagNanoseconds, struct timespec*
storedTime, long int timeDifference) {
// flagNanoseconds: Set this flag in order to compute time
difference in nanoseconds; otherwise the comparison is done in seconds.
// storedTime: A pointer to a struct containing a previous stored
time.
// timeDifference: Time difference wanted to be achieved.
// Returns: '1' if time has been passed. '0' otherwise.
struct timespec currentTime;
long long currentTimeDifference;
307
char timePassed = 0;
clock_gettime(CLOCK_REALTIME, &currentTime); // Get current time.
if (flagNanoseconds == 1) {
// Time difference is wanted to be computed in nanoseconds.
currentTimeDifference = currentTime.tv_nsec - storedTime>tv_nsec;
} else {
// Time difference is wanted to be computed in seconds.
currentTimeDifference = currentTime.tv_sec - storedTime->tv_sec;
}
if (currentTimeDifference > timeDifference) {
*storedTime = currentTime;
timePassed = 1;
}
return timePassed;
}
unsigned char computeCRC8(unsigned char* data, int len) {
// data: The buffer of Bytes to compute its 8 bits CRC.
// len: The number of Bytes to compute its CRC.
// Returns: Computed 8-bit CRC.
unsigned char crc, extract, sum;
int i;
crc = 0x00;
while (len--) {
extract = *data++;
for (i=8; i; i--) {
sum = (crc ^ extract) & 0x01;
crc >>= 1;
if (sum) {
crc ^= 0x8C;
}
extract >>= 1;
}
}
return crc;
}
308
Appendix 10. The I-Droid02 Driver reference manual
The aim of this document is to provide to the developer all the commands to access and
manage all the handlers that are involved in the I-Droid02 environment through I-Droid02
Driver application. Every I-Droid02 device has a default state, which is the one present
every time I-Droid02 boots. A handler consists on a channel to reach all I-Droid02 features
from any application. Some features can be accessed from more than one application at
the same time, whereas others not. Thus, there are two kind of handlers:
•
•
Socket handlers. Used for features that can only be accessed from one application
at the same time. They are accessed through an UNIX socket. After a socket
connection, several commands can be sent to interact with the device itself. This
document provides the socket path and the full set of commands for each socket
handler.
File handlers. Used for simultaneous access features. The application just needs
to read a text file to retrieve information. This document provides the text file path
and the contents and format of each line
Socket handler commands are described with full details, in order to facilitate the use of
them. To send a command, after connect to the required handler, a Byte character that
represents the command has to be sent. Sometimes additional Bytes are required for the
command, including int, float or long values. An int value results on 2 Bytes, while
float and long require 4 Bytes. All values are sent little-endian. float values are in
format IEEE 754.
It is important to remark that every time an application tries to access a socket handler, the
handler itself gets caught until the application sends the Close connection command,
avoiding other applications to access the same handler before it gets freed. It is mandatory
for an application to close every unused connection with Driver’s handlers, so other
applications can access them.
The correct way to access I-Droid02 hardware devices that require exclusive access is
trying to connect to the socket handlers that manages them. To prevent a direct access
bypassing I-Droid02 Driver application, it maintains captured the interfaces used to access
to the hardware through OS. After socket connection, sometimes it will be necessary to
wait for handler availability before execute the necessary commands. A connection to a
certain handler must be closed if there is no command to be executed immediately,
allowing other applications to access the same handler.
To access socket handlers, it is recommended to use SocketManager library because it
simplifies all the UNIX sockets programming. To access file handlers, its associated text
file must be read periodically. It is recommended to use Linux Inotify to read the file only
when an update actually occurs. The FileManager library, also available with I-Droid02
Driver documentation, includes the use of Inotify and also provides functions to open
and read text files line by line.
Next pages develop all the involved commands for every I-Droid02 handler controlled by IDroid02 Driver.
309
Batteries data
I-Droid02 Driver reads the battery voltage level and the associated discharge percentage
of the main battery. It performs the same actions for I-Droid02 433 MHz native remote
control if it is turned on. Recollected values are then written in a text file. Next table
summarizes handler text file structure:
Way to access the handler
Path to handler’s text file
File update period
Number of lines
Line 1 contents
Line 2 contents
Line 3 contents
Line 4 contents
Read a text file (multiapplication handler)
/tmp/idroid02/batteries.txt
10 seconds
4
A decimal number (with 2 digits of decimal precision) containing the
current I-Droid02 main battery voltage in Volts. If this value is
unavailable, a dash (-) is placed instead.
A decimal number (with 2 digits of decimal precision) containing the
current I-Droid02 main battery discharge percentage in %. If this
value is unavailable, a dash (-) is placed instead.
A decimal number (with 2 digits of decimal precision) containing the
current 433 MHz I-Droid02 native remote control battery voltage in
Volts. If this value is unavailable, a dash (-) is placed instead.
A decimal number (with 2 digits of decimal precision) containing the
current remote control battery discharge percentage in %. If this
value is unavailable, a dash (-) is placed instead.
310
Batteries data handler C code example:
The next piece of code reads the handler text file and extracts associated battery data,
using functions from FileManager.h library which includes implicitly the Inotify Linux library.
Before read the batteries data, the function blocks until an update of the file is produced:
#include <string.h>
#include “FileManager.h”
#define FILE_PATH “/tmp/idroid02/batteries.txt”
struct BATTERY_DATA_STRUCT {
float bat; // I-Droid02 main battery.
float batPercent; // I-Droid02 main battery % of discharge.
float remoteBat; // 433 MHz native remote control battery.
float remoteBatPercent; // RC battery % of discharge.
};
struct BATTERY_DATA_STRUCT readBatteryData() {
struct BATTERY_DATA_STRUCT batteries;
char* buffer; // Buffer used to read a line from text file.
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify fd and wd associated to text file.
textFD = openFile(FILE_PATH, “r”);
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, FILE_PATH);
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Read 1st line and parse value:
readLineFromFile(textFD, &buffer, 6);
batteries.bat = strtof(buffer, NULL);
// Read 2nd line and parse value:
readLineFromFile(textFD, &buffer, 6);
batteries.batPercent = strtof(buffer, NULL);
// Read 3rd line and parse value:
readLineFromFile(textFD, &buffer, 6);
batteries.remoteBat = strtof(buffer, NULL);
// Read 4th line and parse value:
readLineFromFile(textFD, &buffer, 6);
batteries.remoteBatPercent = strtof(buffer, NULL);
inotifyInstanceRemoveWatch(iFD, iWD); // Removes watcher
close(iFD); // Closes inotify file descriptor.
closeFile(textFD); // Closes the file.
return batteries;
}
311
Camera
The camera handler allows a high-level application to obtain a JPEG picture or a H264
video through the socket. It is possible to setup the camera resolution and also the frames
per second. The handler uses the V4L2 Driver libraries to handle the camera. When an
application connects to this handler, it can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Close connection
Get camera settings
Set camera settings
Start video streaming
Take picture
Take video
Connect through UNIX socket
/tmp/idroid02/camera
The handler is waiting for a connection
6
Character
Description
C
The handler closes the connection..
The handler asks V4L2 Driver for current camera
G
settings and they are sent through the socket.
Frame format sent is shown in table below.
Setups the camera. Additional Bytes (resolution,
S
fps and mode (H264 for video or JPEG for
pictures)) must be sent after command character
(see below).
The handler sets V4L2 Driver for continuous
transmission. Video data is sent as an H264
R
Transport Stream (TS) through socket. See format
in table below.
The handler takes a JPEG picture with current
settings. The picture is sent to high-level
P
application through socket. Frame format sent is
shown in table below.
The handler takes an H264 video capture with
current settings. Video is sent through socket. An
V
additional Byte indicating video duration is required
(see below).
Wrong command behavior The handler closes the connection with the application.
Camera commands additional information:
•
Get camera settings ‘G’:
When this command is send from the application, the camera handler sends a 7Bytes frame composed by 3 integer values (2 Bytes each one) and 1 additional
Byte through socket with the following contents:
1st Byte – 2nd Byte
Width resolution
o
3rd Byte – 4th Byte
Height resolution
5th Byte – 6th Byte
Frames per second
7th Byte
Mode
Mode: This Byte can take two possible values:
 0x03: The camera is setup to take pictures in JPEG format.
 0x04: The camera is setup to take videos in H264 format.
312
•
Set camera settings ‘S’:
The additional Bytes to be sent after command character ‘S’ follow the same format
as previous command ‘G’.
•
Start video streaming ‘R’:
When this command is send from the application, the camera handler should start
sending the video stream in H264 encapsulated as a Transport Stream (TS) Byte
by Byte. If a problem occurs, the command character ‘R’ will be sent instead and
the transmission will stop.
Every received Byte must be acknowledged by the high-level application by sending
any Byte character through socket, except command character ‘R’. This character
is used to tell the handler to stop the transmission.
•
Take picture ‘P’:
When this command is send from the application, the camera handler sends a
variable length frame containing the picture in JPEG format. Before send the picture
itself, the first 4 Bytes contain a long value with the length in Bytes of the picture.
•
Take video ‘V’:
After the command character ‘V’, an additional Byte must be sent containing the
length of the video in seconds. Sent Bytes from camera handler follow the same
format as ‘P’ command.
Camera handler C code example:
The next piece of code setups the camera resolution to 1920x1080 pixels and takes a
picture. A similar code is required to retrieve a video. The code uses functions from
SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/camera”
unsigned char* takePicture() {
unsigned char commandBuffer[10]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char intBuffer[2]; // Buffer used in int value parsing.
long pictureSize; // Size of picture taken in Bytes.
unsigned char* picture; // Picture taken.
int width, height, fps; // Camera settings.
char mode; // Camera mode.
// Camera settings:
width = 1920;
313
height = 1080;
fps = 30; // Not needed to take pictures.
mode = 0x03; // Picture mode.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Setup camera:
commandBuffer[0] = ‘S’;
memcpy(intBuffer, &width, sizeof(int));
commandBuffer[1] = intBuffer[0];
commandBuffer[2] = intBuffer[1];
memcpy(intBuffer, &height, sizeof(int));
commandBuffer[3] = intBuffer[0];
commandBuffer[4] = intBuffer[1];
memcpy(intBuffer, &fps, sizeof(int));
commandBuffer[5] = intBuffer[0];
commandBuffer[6] = intBuffer[1];
commandBuffer[7] = mode;
sendData(socketFD, commandBuffer, 8);
// Take picture:
commandBuffer[0] = ‘P’;
sendData(socketFD, commandBuffer, 1);
// Receive picture length:
receiveData(socketFD, commandBuffer, 4);
memcpy(&pictureSize, commandBuffer, sizeof(long));
// Receive picture from socket:
picture = malloc(pictureSize);
receiveData(socketFD, picture, pictureLength);
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL);
return picture;
}
314
Chest buttons
Using this handler, any application can monitor the state of I-Droid02 chest buttons. When
an application connects to this handler, it can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Character
Attach sensor
A
Close connection
C
Connect through UNIX socket
/tmp/idroid02/chestButtons
The handler is waiting for a connection
2
Description
The application blocks until any of the chest buttons is
pressed. When it occurs, the handler sends a Byte
indicating which button has been pressed (see below
for details). If the application wants to still monitor more
pressings, this command must be sent again.
The handler releases the socket and it waits until a new
application connects. The handler stops monitoring
buttons pressing until a new application connects.
Wrong command behavior The handler closes the connection with the application.
Chest buttons commands additional information:
•
Attach sensor ‘A’:
When one of the chest buttons is pressed, the handler sends a Byte flag. According
to the next table, the value of this Byte belongs to a button pressed if the
corresponding bit is set:
B7 B6 B5 B4 B3
0
0
0
0
0
B2
Left button
B1
Central button
B0
Right button
315
Chest buttons handler C code example:
The next piece of code waits until a chest button is pressed. The button pressed flag is
returned. Then the application closes the connection with the handler. The code uses
functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/chestButtons”
char receiveChestButtonsPressings() {
unsigned char commandBuffer[1]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char buttonPressedFlag; // Button pressed flag.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Attach application to the sensor:
commandBuffer[0] = ‘A’;
sendData(socketFD, commandBuffer, 1);
// Next line blocks until a button is pressed.
receiveData(socketFD, commandBuffer, 1);
// Set variable
buttonPressedFlag = commandBuffer[0];
// Send ‘C’ command:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
// Close socket:
closeSocket(socketFD, NULL);
return buttonPressedFlag;
}
316
Display
This handler manages the display, composed by 2 rows of 16 characters. It allows to setup
its basic settings and write on it. When an application connects to this handler, it can
execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Connect through UNIX socket
/tmp/idroid02/display
The handler is waiting for a connection
7
Command name
Character
Clear display
D
Close connection
C
Get current display data
G
Move cursor and/or write data
M
Reset display
R
Set texts
T
Setup display
S
Description
The data stored in the display is cleared.
Current display settings and texts set
using command ‘S’ are maintained.
The handler closes the connection with
the application, without erase any data
from the display.
The handler sends current display data
and settings through socket. This
command should be run every time an
application connects. The frame sent
format is shown below this table.
Tells the handler the new position of the
display cursor (row and column). If data
has been previously set for the current
row, it is written automatically on the
display. An additional Byte must be send
indicating the new cursor’s row and
column (see below this table for Byte
format).
Same as ‘D’. Display settings are also
reset to default (contrast set to 50, cursor
dash OFF, cursor blink OFF, display ON
and write direction Left to Right).
Set the texts to be written on each display
row. To unset the text of one of the rows,
the Byte indicating text length must be set
to 0. Texts are not physically written on
the display until ‘M’ command is
executed. Additional data (rows texts)
must be send after command character
(see below).
Tells the handler the new display settings.
2 additional Bytes must be send
indicating the new settings (see below ).
Wrong command behavior The handler closes the connection with the application.
317
Display commands additional information:
•
Get current display data ‘G’:
When this command is send from the application, the display handler sends a frame
of variable length (max 37 Bytes) through socket with the following contents:
o 1st Byte: Display contrast. Number between 0 (min) and 100 (max). 50:
Default.
o 2nd Byte: Rest of display settings. Byte format (default bit between
parenthesis):
o
o
o
o
B7 B6 B5 B4
B3
B2
B1
B0
0
0
0
0 Cursor (0) Blink (0) Display (1) Direction (1)
 Cursor: Cursor dash ‘_’ ON (1) or OFF (0).
 Blink: Cursor blinking ON (1) or OFF (0).
 Display: Show display printed characters (1) or not (0).
 Direction: Set text writing direction Left to Right (1) or vice-versa (0).
rd
3 Byte: Current cursor’s row and column. Highest nibble contains the
column (from 0 to 15) and lowest nibble contains the row (from 0 to 1).
4th Byte: Row 0 text set length.
5th Byte: Row 1 text set length.
Rest of Bytes: Texts of row 0 and 1 sent consecutively according to lengths.
•
Move cursor and/or write data ‘M’:
The application must send an additional Byte after the command character ‘M’
indicating the new row and column where the cursor must be placed, following the
same Byte structure of previous command 3rd Byte.
•
Set texts ‘T’:
Several additional Bytes must be sent after the command character ‘T’ to indicate
the display row to place the text (0x00 or 0x01), text length (16 characters max), the
text itself (its length must match previous length value) and finally command
character ‘T’ to indicate the end of the command. Example frame format:
1st Byte
‘T’
2nd Byte
0x00
3rd Byte
0x0C
4th Byte – 15th Byte
“Hello World!”
16th Byte
‘T’
Both rows texts can be set using the same command. To set the text of another row
the previous frame structure (from 2nd to 15th Bytes both included) must be repeated.
This handler also allows to write the following special characters that are not part of
the original ASCII printable characters (by sending these Bytes in the text field):
Byte
Char
•
0x00
Ç
0x01
ç
0x02
€
0x03

0x04

0x05
<3
0x06
γ
0x07
λ
Setup display ‘S’:
Two additional Bytes must be sent after the command character ‘S’. Those Bytes
share identical format of ‘G’ command 1st and 2nd Bytes.
318
Display handler C code example:
The next piece of code setups display reducing contrast and enabling the cursor dash ‘_’.
Finally, it prints the message “Hello World!” In the 2nd row, 2nd column. After this code
execution, the display 2nd row should show the following from 2nd column: “Hello World!_”.
The code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/display”
void testDisplay() {
unsigned char commandBuffer[20]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char* text = “Hello World!”;
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Setup display:
commandBuffer[0] = ‘S’;
commandBuffer[1] = 70; // Contrast.
commandBuffer[2] = 0b00001011; // Enable cursor dash ‘_’.
sendData(socketFD, commandBuffer, 3);
// Set text of 2nd row:
commandBuffer[0] = ‘T’;
commandBuffer[1] = 0x01;
commandBuffer[2] = 0x0C;
for (int i = 0; i < 12; i++)
commandBuffer[3 + i] = text[i];
commandBuffer[15] = ‘T’;
sendData(socketFD, commandBuffer, 16);
// Write data on display (moving cursor at target):
commandBuffer[0] = ‘M’;
commandBuffer[1] = (1 << 4) | 1;
sendData(socketFD, commandBuffer, 2);
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL); }
319
GPIO for prototype board
I-Droid02 Driver allows any application to access the Raspberry Pi GPIOs that are directly
accessible in the top of the I-Droid02 battery box. Each GPIO is identified with a number,
placed in the label next to the GPIOs connector. WARNING: GPIO INPUT VOLTAGE
CANNOT GO ABOVE 3.3V OR BELOW GND. The handler in charge of them allows, for
each GPIO, to set it as input or output, read and write digital values and also attach onchange interrupts to desired input GPIOs. When an application connects to this handler, it
can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Character
Attach interrupt
A
Close connection
C
Get GPIOs status
G
Read GPIO
V
Reset GPIOs
R
Set GPIO direction
D
Unexport GPIOs
U
Wait for an
interrupt
X
Write GPIOs
W
Connect through UNIX socket
/tmp/idroid02/gpios
The handler is waiting for a connection
9
Description
Tells the handler that requested GPIOs need to
attach a certain interrupt. 2 additional Bytes must be
send after command character (see below).
The handler closes the connection with the
application, without unexport or reset any GPIO (the
settings are maintained for next uses).
The handler sends current GPIO status through
socket. This command should be run every time an
application connects. See frame format below table.
The current value (‘0’ or ‘1’) of the requested GPIO is
sent through socket in a Byte, if it is set as an input.
If not, 0xFF is returned. An additional Byte indicating
the GPIO number must be sent after command
character (see Byte format below this table).
The handler unexport all GPIOs (settings are lost).
This command is used to set several GPIOs as input
or output. 2 additional data (GPIO and direction)
must be sent after command character (see below
this table).
Tells the handler to unset requested GPIOs.
Blocks the handler until one of the interrupts
previously set occurs. After that, a Byte flag
indicating which GPIO has caused the interrupt is
sent (see Byte format below).
Sets a binary value (‘0’ or ‘1’) of several requested
GPIOs, only if they are set as outputs. If not, the
command is ignored. 2 additional Bytes (GPIO
number and its value) must be sent after command
character (see Bytes format below this table).
Wrong command behavior The handler closes the connection with the application.
320
GPIO for prototype board commands additional information:
•
Attach interrupt ‘A’:
The application must send two additional Bytes after the command character ‘A’
indicating the GPIO’s number to attach the interrupt and the kind of interrupt. After
that, the command character ‘A’ must be sent to indicate the end of the command.
An interrupt can only be attached if GPIO’s direction has been configured first as
input. The Bytes frame format is the following:
o 1st Byte: GPIO’s number (12, 13, 16, 19, 20, 21, 26).
o 2nd Byte: Interrupt type:
 0x01: Interrupt on signal edge falling.
 0x02: Interrupt on signal edge rising.
 0x03: Interrupt on both signal edges (falling and rising).
o 3rd Byte: Command character ‘A’ Byte.
Interrupt values are directly extracted from WiringPi library. However, trying
to setup another kind of interrupt set on this library or send a not listed value
will no produce any effect. To change the current GPIO attached interrupt,
first it must be unexported and setup again its direction as input.
It is possible to attach several GPIOs interrupts using the same command.
To attach another interrupt, 1st and 2nd Byte must be repeated consecutively
for each new GPIO before send the command character ‘A’ Byte.
•
Get GPIOs status ‘G’:
When this command is send from the application, the GPIOs handler sends a frame
of 21 Bytes length through the socket, multiplexing every 3 Bytes the information
corresponding to one GPIO. 1st Byte of every period indicates the GPIO number.
The frame follows the next Byte structure:
1st Byte
12
o
o
•
2nd Byte
Status
3rd Byte
Value
4th Byte
13
…
…
19th Byte 20th Byte
26
Status
21st Byte
Value
Status: The current settings for this GPIO. Highest nibble corresponds to
the current attached interrupt (as set with ‘A’ command) or 0 if no interrupt
is attached. Lowest nibble shows the current GPIO’s direction (as set with
‘D’ command): input (0), output (1) or unset (2).
Value: Current GPIO value. For GPIOs set as input, this entry is not set in
real time but only after a call of ‘V’ command or when an attached interrupt
occurs (in this case the stored value is the one read immediately after the
interruption has occurred). For unset GPIOs, its value is always 0.
Read GPIO ‘V’:
The application must send an additional Byte after the command character ‘V’
indicating the GPIO’s number to read its value. After that, the handler will send a
Byte with the GPIO’s value or 0xFF if the GPIO is set as an input. The application
should repeat the previous procedure to read other GPIOs or send the command
character ‘V’ to end the command.
321
•
Set GPIO direction ‘D’:
The application must send two additional Bytes after the command character ‘D’
indicating the GPIO’s number to set its direction and the direction itself. This
command follows the same frame structure as ‘A’ command, but changing 2nd Byte:
o 2nd Byte: GPIO’s direction:
 0 to set a GPIO as an input.
 1 to set a GPIO as an output.
•
Unexport GPIOs ‘U’:
The application must send an additional Byte after the command character ‘U’
indicating the GPIO’s number to be unexported. The application should repeat the
previous procedure to unexport other GPIOs or send the command character ‘U’ to
end the command.
•
Wait for an interrupt ‘X’:
The application and the handler itself get blocked until an interrupt of one of the
GPIOs that has one attached occurs. When this happens, the handler sends a Byte
flag indicating which GPIO has caused the interrupt (by setting the corresponding
bit to 1) or 0 if any GPIO has an interrupt attached (in this case no block is produced).
Last bit indicates the GPIO value.
The Byte flag sent has the following structure:
B7
B6
B5
B4
B3
B2
B1
B0
GPIO12 GPIO13 GPIO16 GPIO19 GPIO20 GPIO21 GPIO26 Value
•
Write GPIOs ‘W’:
The application must send two additional Bytes after the command character ‘W’
indicating the GPIO’s number to set its value and the value itself. The handler only
changes the GPIO’s value if it is set as an output. This command follows the same
frame structure as ‘A’ command, but changing 2nd Byte:
o 2nd Byte: GPIO’s value:
 0 to set a GPIO in low voltage level (0 V with reference to GND).
 1 to set a GPIO in high voltage level (3.3 V with reference to GND).
322
GPIO for prototype board handler C code example:
The next piece of code setups GPIO12 as an input and GPIO13 as an output. An interrupt
on both signal edges (falling and rising) is attached to GPIO12. The program waits for a
signal change on GPIO12, reads the GPIO value and replies it on GPIO13. The action is
repeated 10 times. The code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/gpios”
void testGPIOs() {
unsigned char commandBuffer[6]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char gpioValue; // Value replied from GPIO12 to GPIO13.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Setup GPIOs:
commandBuffer[0] = ‘D’;
commandBuffer[1] = 12; // GPIO12.
commandBuffer[2] = 0; // Set as input.
commandBuffer[3] = 13; // GPIO13.
commandBuffer[4] = 1; // Set as output.
commandBuffer[5] = ‘D’;
sendData(socketFD, commandBuffer, 6);
// Attach interrupt on GPIO12:
commandBuffer[0] = ‘A’;
commandBuffer[1] = 12;
commandBuffer[2] = 3; // Set both signal edges as interrupt.
commandBuffer[3] = ‘A’;
sendData(socketFD, commandBuffer, 4);
// Read current value on GPIO 12:
commandBuffer[0] = ‘V’;
sendData(socketFD, commandBuffer, 1);
receiveData(socketFD, commandBuffer, 1); // Read GPIO value.
gpioValue = commandBuffer[0];
commandBuffer[0] = ‘V’;
sendData(socketFD, commandBuffer, 1); // End read command.
// Write read value to GPIO13:
commandBuffer[0] = ‘W’;
323
commandBuffer[1] = 13;
commandBuffer[2] = gpioValue;
sendData(socketFD, commandBuffer, 3);
// Repeat process 10 times at every signal change:
for (int i = 0; i < 10; i++) {
// Wait for an interrupt on GPIO12:
commandBuffer[0] = ‘X’;
sendData(socketFD, commandBuffer, 1);
receiveData(socketFD, commandBuffer, 1); // Blocking.
// An interruption has been produced. Check flag:
if ((commandBuffer[0] & 0xFE) == 0b10000000) {
// Interrupt produced by GPIO12. Value is on flag:
gpioValue = (commandBuffer[0] & 0x01);
// Write value to GPIO13:
commandBuffer[0] = ‘W’;
commandBuffer[1] = 13;
commandBuffer[2] = gpioValue;
sendData(socketFD, commandBuffer, 3);
}
}
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL);
}
324
LEDs
LEDs handler is in charge of all notification LEDs held on I-Droid02. Almost all the LEDs
are placed on the head, with the exception of base LEDs which are close to the wheels.
This handler allows to turn ON/OFF all LEDs simultaneously or select which of them must
be changed. When an application connects to this handler, it can execute the following
commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
All LEDs ON
All LEDs OFF
Change LEDs
state
Close connection
Get current LEDs
state
Connect through UNIX socket
/tmp/idroid02/leds
The handler is waiting for a connection
5
Character
Description
A
Tells the handler to turn ON all LEDs.
R
Tells the handler to turn OFF all LEDs.
Turn ON/OFF selective LEDs, leaving the other
S
unchanged. Additional Bytes are required for this
command (see frame format below this table).
C
The handler closes the connection keeping LEDs state.
The handler sends 3 Bytes to indicate the current state
G
of the LEDs. This command should be run every time
an application connects (see format below).
Wrong command behavior The handler closes the connection with the application.
LEDs commands additional information:
•
Change LEDs state ‘S’:
Several additional Bytes must be sent after the command character ‘S’ to indicate
which are the group LEDs that their state is wanted to be changed. Each LED group
has 1 Byte flag. A character identifies each group as follows:
Group name
Base LEDs
Head LEDs
Orange head LED
Character
b
h
o
LEDs belonging to this group
White LEDs near wheels (controlled simultaneously).
Ears LEDs and eyes LEDs (green, red and yellow ).
Top head orange LED with variable brightness.
The Byte flags associated to each group have the following format:
o Base LEDs flag. Two possible values:
 0x00: Base LEDs are OFF.
 0x01: Base LEDs are ON.
o Head LEDs flag. A bit set/cleared turns ON/OFF the corresponding LED. All
position references are from I-Droid02 point of view:
B7
Left
ear
B6
Right
ear
B5
Left eye
red
B4
Left eye
yellow
B3
Left eye
green
B2
Right
eye red
B1
Right eye
yellow
B0
Right eye
green
325
o
Orange head LED flag. Any value from 0x00 to 0xFF can be associated to
this flag, indicating the brightness of this LED from 0x01 to 0xFF. 0x00 turns
LED OFF.
After the command character ‘S’ the application has to send one of the previous
characters followed by a Byte flag indicating the new state of the indicated LEDs
group. Finally, the command character ‘S’ must be sent to indicate the end of the
command.
1st Byte
‘S’
2nd Byte
‘b’ / ‘h’ / ‘o’
3rd Byte
Associated Byte flag
4th Byte
‘S’
It is possible to set the state of more than one group of LEDs using the same
command. In order to proceed, both second and third Byte must be appended
before command character ‘S’ as many times as needed. A complete frame format
looks like this:
1st Byte
‘S’
•
2nd Byte
‘b’
3rd Byte
Flag
4th Byte
‘h’
5th Byte
Flag
6th Byte
‘o’
7th Byte
Flag
8th Byte
‘S’
Get current LEDs state ‘G’:
The handler returns a frame of 3 Bytes with the current state of all LEDs. These 3
Bytes follow the same structure of associated Byte flags of each group of LEDs,
defined in command ‘S’. Since the Byte flags are always transmitted in the same
order, they are not accompanied of the characters that identifies each group of
LEDs. The first transmitted Byte flag is the one corresponding to Head LEDs group,
followed by Orange Head LED Byte flag. Finally, the last transmitted one
corresponds to Base LEDs group.
Default associated Byte flags of all groups is 0x00:
1st Byte
‘h’ Associated Byte flag
2nd Byte
‘o’ Associated Byte flag
3rd Byte
‘b’ Associated Byte flag
326
LEDs handler C code example:
The next piece of code gets the current state of the LEDs and toggles all of them. In the
case of top head orange LED, current brightness value is ignored; the program sets its
brightness to 200. The code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/leds”
char LEDsState[3]; // Array to store LED state before change them.
void testGPIOs() {
unsigned char commandBuffer[8]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Retrieve LEDs data:
commandBuffer[0] = ‘G’;
sendData(socketFD, commandBuffer, 1);
receiveData(socketFD, commandBuffer, 3);
// Store and
LEDsState[0]
LEDsState[1]
LEDsState[2]
change received values:
= ~commandBuffer[0];
= 200; // Received value is ignored.
= ~commandBuffer[2];
// Set new LED values:
commandBuffer[0] = ‘S’;
commandBuffer[1] = ‘h’;
commandBuffer[2] = LEDsState[0];
commandBuffer[3] = ‘o’;
commandBuffer[4] = LEDsState[1];
commandBuffer[5] = ‘b’;
commandBuffer[6] = LEDsState[2];
commandBuffer[7] = ‘S’;
sendData(socketFD, commandBuffer, 8);
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL); }
327
Microphone
The microphone placed rear I-Droid02 head is used for real-time sound sampling, among
other usages. I-Droid02 Driver captures the current sampled value (a Byte) and stores it in
a file. To recover microphone’s signal stream any high-level application must read the text
file every sampling period in order to avoid jitter or duplicated samples. The use on
inotify ensures the avoidance of this effect. Next table summarizes handler text file
structure:
Way to access to the handler
Path to handler’s text file
File update period
Number of lines
Line 1 contents
Read a text file (multiapplication handler)
/tmp/idroid02/microphone.txt
90 microseconds
1
A Byte value containing the current sample (with DC component).
Microphone handler C code example:
The next piece of code reads the handler text file to get a microphone sample, without
processing it, using functions from FileManager.h library which includes implicitly the Inotify
Linux library. Before read the sample, the function blocks until a file update is produced:
#include “FileManager.h”
#define FILE_PATH “/tmp/idroid02/microphone.txt”
int getMicrophoneSample() {
char* buffer; // Buffer to read the sample.
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify fd and wd associated to text file.
int sample;
textFD = openFile(FILE_PATH, “r”);
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, FILE_PATH);
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Read and parse value:
readLineFromFile(textFD, &buffer, 3);
sample = atoll(buffer);
inotifyInstanceRemoveWatch(iFD, iWD); // Remove watcher.
close(iFD); // Close inotify instance file descriptor.
closeFile(textFD); // Closes the file.
return sample; }
328
Motors
The motors handler allows a high-level application to interact with all the 8 motors that IDroid02 has. With this handler, the high-level application is able to know and monitor the
current position of each motor. 6 of the 8 motors have a limited traveling across their axis;
for this reason, this handler provides a command to change the position of each motor
inside its limited bounds, at a desired speed. The 2 remaining motors are placed in both IDroid02 wheels. They allow the I-Droid02 movement along a plane at any direction and
speed. This handler provides a command to set a certain trajectory to be followed by the
robot, until a certain distance has been achieved. It also allows to stop the current
movement or set a new trajectory.
When an application connects to this handler, it can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Connect through UNIX socket
/tmp/idroid02/motors
The handler is waiting for a connection
7
Command name
Character
Check if movement is done
D
Close connection
C
Get motors information
G
Move limited motor
L
Reset limited motors position
R
Set movement trajectory
M
Stop limited motor movement
S
Wrong command behavior
Description
This command blocks the entity until the
last set movement is complete. A Byte with
value 0x01 is sent as acknowledge.
The handler closes the connection.
The handler sends through socket the
current state of each limited motor and the
current movement trajectory which IDroid02 is following. The frame format sent
can be seen below this table.
Tells the handler to change the position of
a limited motor. 3 additional Bytes are
required for this command (see below).
Tells the handler to reset the position of all
limited motors to default, if at least one
motor has been moved using ‘L’ command.
This command sets a new trajectory to be
traveled by I-Droid02 on a plane. The same
command allows to stop a current
movement. Additional Bytes (direction and
speed) are required to set the trajectory
(see below).
Tells the handler to stop immediately a
movement of a limited motor, set with ‘L’
command, before reach the previous
requested position. An additional Byte, the
motor number to stop, is required (see
below)
The handler closes the connection with the application.
329
Motors commands additional information:
•
Get motors information ‘G’:
The handler returns a frame of 32 Bytes with the current motors state. First 19 Bytes
contain the information of limited motors state, whereas the remaining 13 show the
current I-Droid02 movement set (including wheels’ speed in km/h). It is
recommended that every high-level application calls this command at the beginning
in order to know the current motors state before proceed to modify it.
The first 18 Bytes of the frame sent are the concatenation of 3 Bytes of information
of all 6 limited motors. The Bytes format is the following:
o 1st Byte: Motor number. This number is an ID to a specific limited motor,
according to the following table:
Motor number
Motor name
o
5
Head tilt
6
Head pan
7
Left arm
8
Right arm
9
Hand
10
Hip
2nd Byte: Motor maximum position. The maximum position that each limited
motor allows (minimum is 1). Next table summarizes this information:
Motor number
5 6
7
8 9 10
Maximum position 2 21 31 31 2 2
o
3rd Byte: Motor current position. The current position of each limited motor.
The default position of each limited motor is set by default every time IDroid02 shutdowns properly (it can be corrected if necessary using the
Setup program on boot). Next table shows the default position of each
limited motor:
Motor number
5 6 7 8
Default position 2 10 1 1
9
2
10
1
19th Byte is a flag indicating which limited motors are currently in movement. If the
corresponding bit is set, the motor in question is currently in movement.
B7 B6 B5
B4
0
0 Hip Hand
B3
Right arm
B2
Left arm
B1
Head pan
B0
Head tilt
330
The last 13 Bytes indicate the current I-Droid02 trajectory and the wheels’ speed.
The trajectory is expressed in the next format:
o
20th Byte: Movement trajectory flag. One of the following values stated in
the next graph and table:
0x66
0x06
0x96
o
o
o
o
•
0x60
0x00
0x90
0x69
0x09
0x99
Flag
0x00
0x06
0x09
0x60
0x66
0x69
0x90
0x96
0x99
Meaning
STOP
Turn left (spin over itself)
Turn right (spin over itself)
Go forward and straight
Go forward and turn left
Go forward and turn right
Go backward and straight
Go backward and turn left
Go backward and turn right
21st Byte: Speed of movement. An integer from 1 to 10 that indicates the
speed at which the selected trajectory is being executed.
 For 0x00, 0x06 and 0x09 trajectories this value is ignored.
nd
22 Byte: Turning speed. An integer from 1 to 10.
 For 0x00, 0x60 and 0x90 trajectories this value is ignored.
 For 0x06 and 0x09 trajectories this value becomes the spinning
speed; since these trajectories imply I-Droid02 to spin over itself.
 For 0x66, 0x69, 0x96 and 0x99 trajectories, the higher the value the
closer the turning radius. In the limit (10) I-Droid02 stops moving
forwards/backwards and only turns left/right over an imaginary axis
of rotation placed over the left/right wheel.
rd
23 and 24th Byte: A parsed int value which contains the distance in
centimeters to travel in the selected trajectory. For spinning trajectories
(0x06 and 0x09) this value is interpreted as the turning angle in degrees. If
this value is set to -1, it indicates that the distance/angle is infinite (the
movement must be stopped using ‘S’ command).
25th – 32nd Byte. Left and right wheels’ speed in km/h parsed as float.
Move limited motor ‘L’:
The application must send 3 additional Bytes after command character ‘L’ which
contain the limited motor number that is wanted to be moved, the new absolute
position value and the speed of movement, according to the next format:
o 1st Byte: Motor number. An integer value between 5 and 10 (the relationship
between numbers and motors is the same used in ‘G’ command (see above).
o 2nd Byte: Motor’s new position. An integer value between 1 and the
maximum position that this motor allows (values summarized in a table
above). To set a value higher than current one implies to move the
requested motor up (left in the head pan motor case or open the hand in the
hand motor case). To set a value lower than current one implies the opposite.
331
o
3rd Byte: Speed of movement. An integer value between 1 and 10 to indicate
the speed of traveling between current motor’s position and the new one.
•
Set movement trajectory ‘M’:
The application must send 5 additional Bytes after command character ‘M’ which
contain the parameters of the wheels’ trajectory that is wanted to be set. The frame
format and the corresponding interpretation of the movement is the same used by
last 5 Bytes (from 20th to 24th) of ‘G’ command (see above).
It is important to remark that any trajectory can be achieved from any other, with
the exception of 0x06 and 0x09 trajectories which can only be achieved from 0x00
(STOP).
•
Stop limited motor movement ‘S’:
The application must send an additional Byte after the command character ‘S’
indicating the motor ID to be stopped. To stop a limited motor before reach the
position set in ‘L’ command only has sense with motors with more than 2 positions
(head pan and arms).
332
Motors handler C code example:
The next piece of code rises the left arm up to the maximum position at minimum speed.
At the same time, it starts a straight movement at minimum speed along 5 meters. The
code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/motors”
void testMovement() {
unsigned char commandBuffer[25]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
int distance = 500; // Distance to cover in straight move.
char intBuffer[sizeof(int)]; // Buffer to parse int values.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Rise left arm up at maximum position at minimum speed:
commandBuffer[0] = ‘L’;
commandBuffer[1] = 7;
commandBuffer[2] = 31;
commandBuffer[3] = 1;
sendData(socketFD, commandBuffer, 4);
// Move I-Droid02 straight for 5 meters:
commandBuffer[0] = ‘M’;
commandBuffer[1] = 0x60;
commandBuffer[2] = 1;
commandBuffer[3] = 0; // Irrelevant value.
memcpy(intBuffer, &distance, sizeof(int));
commandBuffer[4] = intBuffer[0];
commandBuffer[5] = intBuffer[1];
sendData(socketFD, commandBuffer, 6);
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL);
}
333
Programmable buttons
I-Droid02 has 6 programmable buttons: A, B and C which are placed on the 433 MHz native
remote control and D, E and F which are placed directly on the Raspberry Pi box. I-Droid02
Driver, through this handler, monitors the current state of the buttons when a high-level
application is connected to this handler. When an application connects to this handler, it
can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Character
Attach sensor
A
Close connection
C
Connect through UNIX socket
/tmp/idroid02/programmableButtons
The handler is waiting for a connection
2
Description
The application blocks until any of the chest buttons is
pressed. When it occurs, the handler sends a Byte
indicating which button has been pressed (see below
for details). If the application wants to still monitor more
pressings, this command must be sent again.
The handler releases the socket and it waits until a new
application connects. The handler stops monitoring
buttons pressing until a new application connects.
Wrong command behavior The handler closes the connection with the application.
Chest buttons commands additional information:
•
Attach sensor ‘A’:
When a button is pressed, the handler sends a Byte flag indicating the
corresponding pressed button, by setting the associated bit:
B7 B6 B5 B4 B3 B2 B1 B0
0
0
A
B
C
D
E
F
334
Programmable buttons handler C code example:
The next piece of code waits until any programmable button is pressed. The button pressed
flag is returned. Then the application closes the connection with the handler. The code
uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/programmableButtons”
char receiveProgrammableButtonsPressings() {
unsigned char commandBuffer[1]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char buttonPressedFlag; // Button pressed flag.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Attach application to the sensor:
commandBuffer[0] = ‘A’;
sendData(socketFD, commandBuffer, 1);
// Next line blocks until a button is pressed.
receiveData(socketFD, commandBuffer, 1);
// Set variable
buttonPressedFlag = commandBuffer[0];
// Send ‘C’ command:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
// Close socket:
closeSocket(socketFD, NULL);
return buttonPressedFlag;
}
335
RFM31B 433 MHz remote control receiver
RFM31B is the 433 MHz receiver to manage I-Droid02 from its native remote control. The
main function of this remote control is control all I-Droid02 motors. The associated handler
of this device is in charge of receive those packets coming from the remote control and
deliver them to the connected high level application. A part from that, I-Droid02 Driver
through this handler also offers other functionalities for this device, such as measure the
power of received packets or obtain an approximate screenshot of the current spectrum
inside full 433 MHz ISM band. This last tool could be useful to know if part of the band is
still occupied by another transmission, in order to set another central frequency obtaining
better coverage results. When an application connects to this handler, it can execute the
following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Character
Change frequency
F
Close connection
C
Get frequency
G
Measure power
P
Obtain spectrum
S
Receive data
R
Wrong command behavior
Connect through UNIX socket
/tmp/idroid02/rfm31b
A part from wait for a connection, handler
delivers control packet sent by remote control
to the appropriate handler (this packet reports
remote control battery state and transmitter
internal temperature). Programmable buttons
(A, B and C) are also reported
5
Description
Sets the new central frequency inside band to tune
RFM31B. Initial value is the one set in a previous call.
Additional Bytes are required (see below).
The handler closes the connection with the
application, maintaining the current settings. The
handler continues reporting signaling data from the
remote control (if ON).
The handler sends the central frequency value in MHz
set in the last call of ‘F’ command. The handler sends
4 Bytes with the current value as a float.
Tells the handler to report to high-level application the
current input power at the tuned frequency. See
additional information below this table.
Stops temporally the reception of any packet (battery
and temperature data is not available) and puts
RFM31B to scan full 433 MHz ISM band, which a
precision of 1 kHz. Current measured frequency and
obtained power value is sent through socket in real
time (see below).
After the reception of this command, the handler
sends through socket the contents of a packet
received. See frame format below this table.
The handler closes the connection with the application.
336
RFM31B 433 MHz remote control receiver additional information:
•
Change frequency ‘F’:
After the command character ‘F’, the high-level application has to send the new
central frequency value in MHz, as a float variable (4 Bytes) of two decimal digits.
The RFM31B central frequency can only be changed if the native remote control is
also connected to I-Droid02 through a USB cable. The TX LED of the remote control
must be also on steadily. If previous requirements are not accomplished, RFM31B
central frequency will not be changed although this command is executed.
•
Measure power ‘P’:
In this mode, the handler disables control data processing. RFM31B just reports
received power at the current central frequency through socket. When the
application sends the command character ‘P’ the handler sends through socket a
float (4 Bytes) value containing the power value directly in dBm.
At this step, high-level application has to send character ‘A’, to acknowledge the
received power value. When handler receives the Byte that acknowledges previous
power value, a new power value is obtained from the receiver and sent through
socket.
The process is repeated infinitely until high-level application sends the command
character ‘P’ to acknowledge the last received power value. If character ‘P’ is sent
as acknowledgment of last power value, it tells the handler to stop this command.
•
Obtain spectrum ‘S’:
In this mode, the handler disables control data processing, as it happened with
previous command. RFM31B just reports received power but as a difference of
previous command, obtained power value is a mean of 10000 samples.
In order to obtain the spectrum of full 433 MHz ISM band, RFM31B is tuned in all
frequency values inside ISM band (with 1 kHz of frequency spacing). Two float
variables (4 Bytes each) are sent for each measure. The first float variable is the
current frequency tuned, whereas the second one is the measured power at this
point. A total number of 1741 measurements are performed (1741 is the total
number of frequency steps between 433 MHz bounds with a frequency step of 1
kHz).
This command ends automatically when last power value is transmitted through the
socket. This command does not require any Byte to acknowledge values.
The frame format sent by the handler looks like this:
1st Byte –
4th Byte
433.05
•
5th Byte –
8th Byte
Power
9th Byte –
12 Byte
433.051
13th Byte –
16th Byte
Power
… 13929th Byte –
13932nd Byte
…
434.79
13933rd Byte –
13936th Byte
Power
Receive data ‘R’:
There are two kinds of packets (called analog and digital) coming from the remote
control that can be processed by a high-level application. A third packet, called
control packet, only contains signaling data from the remote control and is directly
processed by the handler itself (as explained before).
337
Both packets have different size and format and they handle which elements of the
remote control has been pressed by the user. The handler blocks until a valid packet
is received, without any error. When it occurs, the handler sends through socket the
length of the packet and then the packet itself.
As it happened with previous command ‘P’, the high-level application has to
acknowledge each received packet by sending character ‘A’. If ‘R’ is sent, the
handler acknowledges last received packet and also ends the command. The
handler continues receiving packets but they are dropped, except control packet
which is always processed. After certain time, if any new packet is received, the
handler sends a 0 as the length of a supposed packet, followed by the packet ID
which has stopped receiving. This flag must also be acknowledged using any
character except ‘A’ and ‘R’.
When a packet is received, the frame format of these packets is the following:
o 1st packet: Analog packet. Length: 3 Bytes.
 1st Byte. Packet ID: 0x24.
 2nd Byte. Flag to interpret I-Droid02 direction of movement.
Binary value Hexadecimal value
0b00000110
0x06
0b00001001
0x09
0b01100000
0x60
0b01100110
0x66
0b01101001
0x69
0b10010000
0x90
0b10010110
0x96
0b10011001
0x99

o
Meaning
Turn left
Turn right
Go forward and straight
Go forward and turn left
Go forward and turn right
Go backward and straight
Go backward and turn left
Go backward and turn right
3rd Byte. Joystick value:
• Highest nibble: Speed of movement (integer from 1 to 10).
• Lowest nibble: Turning speed (integer from 1 to 10).
2nd packet: Digital packet. Length: 3 Bytes.
 1st Byte. Packet ID: 0x18.
 2nd Byte. Head movement Byte flag.
Binary value
0b00000001
0b00000010
0b00000100
0b00001000

Meaning
Head up
Head down
Turn head left
Turn head right
3rd Byte. Rest of motors movement Byte flag.
Binary value
0b00000001
0b00000010
0b00000100
0b00001000
Meaning
Binary value
Left arm up
0b00010000
Left arm down
0b00100000
Right arm up
0b01000000
Right arm down 0b10000000
Meaning
Open hand
Close hand
Hip up
Hip down
338
RFM31B 433 MHz remote control receiver handler C code example:
The next piece of code tells the RFM31B to measure the spectrum inside full 433 MHz ISM
band. The code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/rfm31b”
#define N_SAMPLES 1741
struct SPECTRUM {
float freq[1741];
float power[1741];
};
struct spectrum obtainSpectrum() {
unsigned char commandBuffer[4]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
struct SPECTRUM spec;
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Send Obtain spectrum ‘S’ command:
commandBuffer[0] = ‘S’;
sendData(socketFD, commandBuffer, 1);
for (int i = 0; i < N_SAMPLES; i++) {
// Get frequency value:
receiveData(socketFD, commandBuffer, 4);
memcpy(&spec.freq[i], commandBuffer, sizeof(float));
// Get power value:
receiveData(socketFD, commandBuffer, 4);
memcpy(&spec.power[i], commandBuffer, sizeof(float));
}
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL);
return spec;
}
339
Temperatures data
I-Droid02 Driver recollects the temperature of 4 different devices: Ambient temperature
sensor, Raspberry Pi 2 B internal microprocessor temperature, RFM31B (433 MHz
receiver) internal temperature and RFM43B (433 MHz transmitter) internal temperature
(when available). Recollected values are then written in a text file. Next table summarizes
handler text file structure:
Way to access to the handler
Path to handler’s text file
File update period
Number of lines
Line 1 contents
Line 2 contents
Line 3 contents
Line 4 contents
Read a text file (multiapplication handler)
/tmp/idroid02/temperatures.txt
10 seconds
4
A decimal number (with 1 digits of decimal precision) containing the
current ambient temperature in ºC. If this value is unavailable, a
dash (-) is placed instead.
A decimal number (with 1 digits of decimal precision) containing the
current Raspberry Pi 2 B internal microprocessor’s temperature in
ºC. If this value is unavailable, a dash (-) is placed instead.
A decimal number (with 1 digits of decimal precision) containing the
current RFM31B 433 MHz receiver internal microprocessor’s
temperature in ºC. If this value is unavailable, a dash (-) is placed
instead.
A decimal number (with 1 digits of decimal precision) containing the
current RFM43B 433 MHz transmitter internal microprocessor’s
temperature in ºC. If this value is unavailable, a dash (-) is placed
instead.
340
Temperatures data handler C code example:
The next piece of code reads the handler text file and extracts associated temperature data,
using functions from FileManager.h library which includes implicitly the Inotify Linux library.
Before read the temperatures data, the function blocks until a file update is produced:
#include <string.h>
#include “FileManager.h”
#define FILE_PATH “/tmp/idroid02/temperatures.txt”
struct TEMPERATURE_DATA_STRUCT {
float ambient; // Ambient temperature.
float rpiTemp; // Raspberry Pi internal temperature.
float rfm31bTemp; // 433 MHz native remote control RX temp.
Float rfm43bTemp; // 433 MHz native remote control TX temp.
};
struct TEMPERATURE_DATA_STRUCT readTemperatureData() {
struct TEMPERATURE_DATA_STRUCT temperatures;
char* buffer; // Buffer used to read a line from text file.
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify fd and wd associated to text file.
textFD = openFile(FILE_PATH, “r”);
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, FILE_PATH);
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Read 1st line and parse value:
readLineFromFile(textFD, &buffer, 6);
temperatures.ambient = strtof(buffer, NULL);
// Read 2nd line and parse value:
readLineFromFile(textFD, &buffer, 6);
temperatures.rpiTemp = strtof(buffer, NULL);
// Read 3rd line and parse value:
readLineFromFile(textFD, &buffer, 6);
temperatures.rfm31bTemp = strtof(buffer, NULL);
// Read 4th line and parse value:
readLineFromFile(textFD, &buffer, 6);
temperatures.rfm43bTemp = strtof(buffer, NULL);
inotifyInstanceRemoveWatch(iFD, iWD); // Removes watcher
close(iFD); // Closes inotify file descriptor.
closeFile(textFD); // Closes the file.
return batteries;
}
341
Torch tool
This handler manages the USB torch that can be attached I-Droid02 left arm. It is
composed by 1 LED which is directly connected to a USB to UART converter. This handler
allows to turn ON/OFF the torch with 9 different intensities in order to have more or less
brightness. The application can also check whether the torch is connected or not. When an
application connects to this handler, it can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Close connection
Character
C
Get current state
G
Recognize
connected torch
R
Turn OFF
F
Turn ON
N
Wrong
behavior
Connect through UNIX socket
/tmp/idroid02/torch
The handler is waiting for a connection
5
Description
The handler closes the connection.
The handler sends 2 Bytes through socket indicating
the current state of the torch. The first Byte indicates
whether the torch is ON (1) or OFF (0). The second
Byte indicates the current light brightness, a value from
1 to 9 (default). This command should be run every
time an application connects.
The handler checks if /dev/torch device is present
or not and sends the obtained result in a Byte: 1 if it is
present or 0 otherwise.
Tells the handler to turn the torch OFF.
Tells the handler to turn the torch ON. An additional
Byte value must be sent for the application to indicate
brightness (from 1 to 9).
command The handler closes the connection with the application.
342
Torch tool handler C code example:
The next piece of code turns the torch ON at maximum intensity, waits for 5 seconds and
turns it OFF. The program checks whether the torch is connected or not after turn it ON.
The code uses functions from SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/torch”
void testGPIOs() {
unsigned char commandBuffer[2]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Check if torch is connected:
commandBuffer[0] = ‘R’;
sendData(socketFD, commandBuffer, 1);
receiveData(socketFD, commandBuffer, 1);
if (commandBuffer[0] == 1) {
// Torch is connected. Turn it ON:
commandBuffer[0] = ‘N’;
commandBuffer[1] = 9;
sendData(socketFD, commandBuffer, 2);
sleep(5);
// Turn torch OFF:
commandBuffer[0] = ‘F’;
sendData(socketFD, commandBuffer, 1);
}
// Send Close connection command and close socket:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL);
}
343
Touch sensor
I-Droid02 Driver, through this handler, monitors the current state of the touch sensor placed
on the I-Droid02 head, only when a high-level application is connected to this handler.
When an application connects to this handler, it can execute the following commands:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Command name
Character
Attach sensor
A
Close connection
C
Connect through UNIX socket
/tmp/idroid02/touchSensor
The handler is waiting for a connection
2
Description
The application blocks until touch sensor is pressed.
When it occurs, the handler sends a Byte with the value
0x01. If the application wants to still monitor more
pressings, this command must be sent again.
The handler releases the socket and it waits until a new
application connects. The handler stops monitoring
buttons pressing until a new application connects.
Wrong command behavior The handler closes the connection with the application.
344
Touch sensor handler C code example:
The next piece of code waits until the touch sensor is pressed and returns. Before return,
proper command to end connection is sent. The code uses functions from
SocketManager.h library:
#include “SocketManager.h”
#define SOCKET_PATH “/tmp/idroid02/touchSensor”
void detectTouch() {
unsigned char commandBuffer[1]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Socket connected.
// Attach application to the sensor:
commandBuffer[0] = ‘A’;
sendData(socketFD, commandBuffer, 1);
// Next line blocks until touch sensor is pressed.
receiveData(socketFD, commandBuffer, 1);
// Send ‘C’ command:
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
// Close socket:
closeSocket(socketFD, NULL);
}
345
Ultrasonic sensors
I-Droid02 Driver monitors and stores the current distance between the current selected
ultrasonic sensor and an object. The default selected sensor is the central one. Next table
summarizes handler text file structure:
Way to access to the handler
Path to handler’s text file
File update period
Number of lines
Line 1 contents
Read a text file (multiapplication handler)
/tmp/idroid02/ultrasonic.txt
250 milliseconds
1
A decimal number (with 2 digits of decimal precision) containing the
current distance in centimeters between the ultrasonic sensor and
a near object. If this value is unavailable, a dash (-) is placed
instead. The reasons of an ultrasonic distance being unavailable
are two: Or the object is placed too close to the sensor or too far.
It is possible to setup the ultrasonic sensor to be watched. The ultrasonic sensors handler
has another path to perform this change. It is summarized in the following table:
Way to access to the handler
Path to handler’s socket directory
Default behavior
Number of commands
Connect through UNIX socket
/tmp/idroid02/ultrasonic
The handler is updating ultrasonic.txt file
4
Command name
Character
Set ultrasonic sensor
S
Close connection
C
Get current ultrasonic sensor
Reset default
G
R
Description
Tells the handler which is the new sensor to
retrieve data. An additional Byte specifying
the selected sensor must be sent (see
below).
The handler closes the connection. The
selected sensor is not changed and the
handler
continues
updating
ultrasonic.txt file.
The current sensor is sent in a Byte.
Selected sensor is reset to default (central).
Wrong command behavior The handler closes the connection with the application.
Ultrasonic sensors additional information:
•
Set ultrasonic sensor ‘S’:
The additional Byte containing the new sensor to be watched can be one of the 3
next values (they are the same sent in ‘G’ command:
o 0: Select the left ultrasonic sensor (from I-Droid02 point of view).
o 1: Select the central ultrasonic sensor (default).
o 2: Select the right ultrasonic sensor (from I-Droid02 point of view).
346
Ultrasonic sensors handler C code example:
The next piece of code changes the ultrasonic sensor to be watched and reads the sensor
value. The code uses functions from SocketManager.h and FileManager.h library:
#include <string.h>
#include “FileManager.h”
#include “SocketManager.h”
#define FILE_PATH “/tmp/idroid02/ultrasonic.txt”
#define SOCKET_PATH “/tmp/idroid02/ultrasonic”
char sensor = 2; // Central ultrasonic sensor.
float getUltrasonicValue() {
unsigned char commandBuffer[2]; // Socket command buffer.
struct sockaddr_un socketSettings; // UNIX socket settings.
int socketFD;
char* buffer; // Buffer to read a line from text file.
FILE* textFD; // File Descriptor of text file.
int iFD, iWD; // Inotify fd and wd associated to text file.
float sensorValue; // The ultrasonic sensor value retrieved.
// Setup UNIX socket:
socketFD = openSocket();
socketSettings = setupSocket(socketFD, SOCKET_PATH, 0);
connectSocket(socketFD, socketSettings); // Blocking.
// Set sensor to watch and close connection and socket:
commandBuffer[0] = ‘U’;
commandBuffer[1] = sensor;
sendData(socketFD, commandBuffer, 2);
commandBuffer[0] = ‘C’;
sendData(socketFD, commandBuffer, 1);
closeSocket(socketFD, NULL); // Close socket.
// Read ultrasonic sensor value:
textFD = openFile(FILE_PATH, “r”);
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, FILE_PATH);
watchFileForChanges(iFD); // Blocking function.
readLineFromFile(textFD, &buffer, 6);
sensorValue = strtof(buffer, NULL);
inotifyInstanceRemoveWatch(iFD, iWD); // Remove watcher.
close(iFD); // Close inotify instance.
closeFile(textFD); // Closes the file.
return sensorValue;
}
347
Appendix 11. Setup program source code
main.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef MAIN_H
#define MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include
#include
#include
#include
#include
"Devices.h"
"Networks.h"
"SocketManager.h"
"Speaker.h"
"StatusData.h"
// Constants:
#define ESSID_PASSWORD_LENGTHS 100
#define OWN_SOCKET_PATH "/tmp/idroid02/setupEnd"
#define MENU_BACK_CHANGE_NEXT "BACK CHANGE NEXT"
#define MENU_BACK_SETUP_TOP "BACK SETUP TOP"
#define MENU_BACK_GO_NEXT "BACK
GO
NEXT"
#define MENU_BOTH_ARROWS "<-ENTER -->"
#define MENU_LEFT_ARROW "<-ENTER"
#define MENU_LEFT_OK_RIGHT "LEFT
OK RIGHT"
#define MENU_OPEN_OK_CLOSE "OPEN
OK CLOSE"
#define MENU_UP_OK_DOWN "UP
OK
DOWN"
#define NO_SCREEN -1
#define PRINTABLE_ASCII (128 - 33)
#define SCREEN_MAIN_ID 0
#define SCREEN_STATUS_MENU_ID 1
#define SCREEN_SETUP_MENU_ID 2
#define SCREEN_MAIN_BATTERY_ID 3
#define SCREEN_RC_BATTERY_ID 4
#define SCREEN_AMBIENT_TEMP_ID 5
#define SCREEN_RPI_TEMP_ID 6
#define SCREEN_RFM43B_TEMP_ID 7
#define SCREEN_RFM31B_TEMP_ID 8
#define SCREEN_ULTRASONIC_VALUE_ID 9
#define SCREEN_ETHERNET_IP_ID 10
#define SCREEN_WIRELESS_IP_ID 11
#define SCREEN_CALIBRATE_MOTORS_ID 12
#define SCREEN_DISPLAY_CONTRAST_ID 13
#define SCREEN_SPEAKER_VOLUME_ID 14
#define SCREEN_UPDATE_SOFTWARE_ID 15
#define SCREEN_WIFI_NETWORK_ID 16
#define TOTAL_SCREENS 17
348
// Typedefs:
typedef void (*ACTION_FUNCTION)(); // Generic pointer to a function
with void arguments and void return.
// Global variables:
char printableASCII[PRINTABLE_ASCII]; // Array containing all ASCII
printable characters.
// Struct to define a screen (text to show in the display, voice
text, next screens according to the button pushed):
struct SCREEN {
int leftButtonScreenID; // Screen to show when left button is
pressed (-1 to not associate any button).
int centralButtonScreenID; // Screen to show when central button
is pressed (-1 to not associate any button).
int rightButtonScreenID; // Screen to show when right button is
pressed (-1 to not associate any button).
char needToUpdate; // If 1, the screen needs to be updated every
second.
char* textRow0; // Text to show in upper row.
char* textRow1; // text to show in lower row.
char row0StartColumn; // Column when text of row 0 starts.
char row1StartColumn; // Column when text of row 1 starts.
char hasActionFunction; // If 1, the screen performs some tasks
when it is showed up.
ACTION_FUNCTION actionFunction; // Pointer to the function that
executes an action when the screen is showing.
};
struct SCREEN allScreens[TOTAL_SCREENS];
int currentScreenID; // ID of the screen that is currently
displayed.
char endProgramFlag; // If 1, the program terminates.
pthread_mutex_t printMutex; // Mutex used to make printScreen
function atomic.
// Functions:
void actionSCREEN_CALIBRATE_MOTORS(); // Action function associated
to SCREEN_CALIBRATE_MOTORS.
void actionSCREEN_DISPLAY_CONTRAST(); // Action function associated
to SCREEN_DISPLAY_CONTRAST.
void actionSCREEN_SPEAKER_VOLUME(); // Action function associated to
SCREEN_SPEAKER_VOLUME.
void actionSCREEN_UPDATE_SOFTWARE(); // Action function associated
to SCREEN_UPDATE_SOFTWARE.
void actionSCREEN_WIFI_NETWORK(); // Action function associated to
SCREEN_WIFI_NETWORK.
void* actionSCREEN_WIFI_NETWORK_UpdateSSID(void*); // Thread used to
be able to print SSID with length higher than 16 characters.
void* checkForEndProgram(void*); // Thread in charge of handle a
server socket. When connects, the program ends.
void initPrintableASCII_Array(); // Routine used to init
printableASCII array.
void printScreen(struct SCREEN); // Function to print a screen on
display.
void updateScreens(); // Function to update all screens.
void* updateScreenThread(void*); // Thread in charge of update
current screen if it needs it.
char* updateSCREEN_MAIN_BATTERY(); // Function to get data to show
in SCREEN_MAIN_BATTERY.
349
char* updateSCREEN_RC_BATTERY(); // Function to get data to show in
SCREEN_RC_BATTERY.
char* updateSCREEN_AMBIENT_TEMP(); // Function to get data to show
in SCREEN_AMBIENT_TEMP.
char* updateSCREEN_RPI_TEMP(); // Function to get data to show in
SCREEN_RPI_TEMP.
char* updateSCREEN_RFM43B_TEMP(); // Function to get data to show in
SCREEN_RFM43B_TEMP.
char* updateSCREEN_RFM31B_TEMP(); // Function to get data to show in
SCREEN_RFM31B_TEMP.
char* updateSCREEN_ULTRASONIC_VALUE(); // Function to get data to
show in SCREEN_ULTRASONIC_VALUE.
char* updateSCREEN_ETHERNET_IP(); // Function to get data to show in
SCREEN_ETHERNET_IP.
char* updateSCREEN_WIRELESS_IP(); // Function to get data to show in
SCREEN_WIRELESS_IP.
#ifdef __cplusplus
}
#endif
#endif /* MAIN_H */
main.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* Main file of I-Droid02 Setup program. This program is in charge of
setup main
* parameters of I-Droid02 devices and also to display main parameters
such as
* battery voltages or temperatures. It allows to setup a Wi-Fi
connection using
* display and chest buttons. Moreover, it is possible to set up the
speaker
* volume, the display contrast, to update Linux Raspbian Operating
System
* software and to reposition to the default place those motors that
could have
* been moved accidentally while I-Droid02 was shutdown. This program
has been
* designed to be running all the time that any other application
requests the
* use of Display and Chest buttons handlers of I-Droid02 Driver. Thus,
these
* handlers remain captured all the time. For this reason, if an
external
* application requests the use of these handlers it has the way to end
Setup
* program by simply connecting to it using a UNIX socket pointing at
* OWN_SOCKET_PATH. The application who has decided to close Setup
program is
* responsible to run it again.
*/
#include "main.h"
350
int main(int argc, char** argv) {
pthread_t batteries;
pthread_t endProgram;
pthread_t temperatures;
pthread_t ultrasonic;
pthread_t updateScreen;
char chestButtonPressed; // Chest button pressed at any time.
// Init variables:
initPrintableASCII_Array();
initStatusData();
currentScreenID = SCREEN_MAIN_ID;
// Start data parser threads:
pthread_create(&batteries, NULL, readBatteryData, NULL);
pthread_detach(batteries);
pthread_create(&temperatures, NULL, readTemperatureData, NULL);
pthread_detach(batteries);
pthread_create(&ultrasonic, NULL, readUltrasonicValue, NULL);
pthread_detach(batteries);
// Start end program thread:
pthread_create(&endProgram, NULL, checkForEndProgram, NULL);
pthread_detach(endProgram);
// Connect to Display handler:
connectDisplaySocket();
// Init printMutex and start update screens thread:
pthread_mutex_init(&printMutex, NULL);
pthread_create(&updateScreen, NULL, updateScreenThread, NULL);
pthread_detach(updateScreen);
// Update all screens and print initial one:
updateScreens();
printScreen(allScreens[currentScreenID]);
// Start infinite loop:
endProgramFlag = 0;
while (endProgramFlag == 0) {
// Wait for a chest button pressed by the user:
chestButtonPressed = ackChestButtons();
// Determine next screen to show:
switch (chestButtonPressed) {
case CHEST_BUTTON_RIGHT:
if (allScreens[currentScreenID].rightButtonScreenID !=
NO_SCREEN) {
currentScreenID =
allScreens[currentScreenID].rightButtonScreenID;
}
break;
case CHEST_BUTTON_CENTER:
if (allScreens[currentScreenID].centralButtonScreenID !=
NO_SCREEN) {
currentScreenID =
allScreens[currentScreenID].centralButtonScreenID;
} else if (allScreens[currentScreenID].hasActionFunction
== 1) {
351
displayClear(); // Clear current screen.
// Run action function if it is defined:
allScreens[currentScreenID].actionFunction();
}
break;
case CHEST_BUTTON_LEFT:
if (allScreens[currentScreenID].leftButtonScreenID !=
NO_SCREEN) {
currentScreenID =
allScreens[currentScreenID].leftButtonScreenID;
}
break;
}
// Print screen:
printScreen(allScreens[currentScreenID]);
}
// Disconnect Display handler to free it:
disconnectDisplaySocket();
return EXIT_SUCCESS;
}
void actionSCREEN_CALIBRATE_MOTORS() {
char* motorsTextRow0[6]; // Array that contains all motors labels.
char* motorsTextRow1[6]; // Array that contains buttons labels for
each motor.
char* motorsHintVoice[6]; // Array to store help voice phrase for
each motor that indicates the default position.
char motorDirection; // If 1, current motor moves up/left/open
(hand). Otherwise, it goes down/right/close (hand).
char button; // Chest button pressed.
int motorsTitleStartColumn[6]; // Array that stores the start column
of each motor label.
int i;
// Init local variables:
motorsTextRow0[0] = "Head tilt";
motorsTextRow1[0] = MENU_UP_OK_DOWN;
motorsHintVoice[0] = "Place the head as high as possible.";
motorsTitleStartColumn[0] = 3;
motorsTextRow0[1] = "Head pan";
motorsTextRow1[1] = MENU_LEFT_OK_RIGHT;
motorsHintVoice[1] = "Now, center the head.";
motorsTitleStartColumn[1] = 4;
motorsTextRow0[2] = "Left arm";
motorsTextRow1[2] = MENU_UP_OK_DOWN;
motorsHintVoice[2] = "Left arm must be placed at the lowest
position.";
motorsTitleStartColumn[2] = 4;
motorsTextRow0[3] = "Right arm";
motorsTextRow1[3] = MENU_UP_OK_DOWN;
motorsHintVoice[3] = "Do the same with the right arm.";
motorsTitleStartColumn[3] = 3;
motorsTextRow0[4] = "Hand";
motorsTextRow1[4] = MENU_OPEN_OK_CLOSE;
motorsHintVoice[4] = "Hand must be opened.";
motorsTitleStartColumn[4] = 6;
352
motorsTextRow0[5] = "Hip";
motorsTextRow1[5] = MENU_UP_OK_DOWN;
motorsHintVoice[5] = "Place the hip down to the maximum.";
motorsTitleStartColumn[5] = 6;
// Connect to Motors handler:
connectMotorsSocket();
// Calibrate each motor in order:
for (i = 0; i < 6; i++) {
// Set screen texts:
displayData.textRow0 = motorsTextRow0[i];
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = motorsTextRow1[i];
displayData.textRow1Length = 16;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
// Print title (row 0) and say instruction:
displayData.row = 0;
displayData.column = motorsTitleStartColumn[i];
displayMoveCursorWriteTexts();
sayPhrase(1, motorsHintVoice[i]);
// Print buttons row:
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Wait for a button press and react to it:
do {
button = ackChestButtons();
switch (button) {
case CHEST_BUTTON_RIGHT:
motorDirection = 0;
break;
case CHEST_BUTTON_LEFT:
motorDirection = 1;
break;
}
if (button != 0x02) {
motorsCalibrate(5 + i, motorDirection); // Move motor in
calibration mode.
}
} while (button != 0x02);
// Clear display:
displayClear();
}
// Reset all motors data and disconnect from Motors handler:
motorsResetData();
disconnectMotorsSocket();
// Show "All motors have been calibrated" message:
displayClear();
displayData.textRow0 = "All motors have";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "been calibrated";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
353
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
sayPhrase(1, "All motors have been calibrated!");
}
void actionSCREEN_DISPLAY_CONTRAST() {
char barsString[10]; // Bars that represent the contrast bar.
char contrastString[3]; // String representation of the contrast
value.
int barsToPaint; // Number of bars to paint in the contrast
indicator.
char button; // Chest button pressed.
int i;
// Exit the action when the central button is pressed:
do {
// Retrieve current contrast value and compute number of bars:
displayGetSettings();
barsToPaint = displayData.contrast / 10;
// Prepare contrast bar:
memset(barsString, 255, barsToPaint);
for (i = barsToPaint; i < 10; i++) {
barsString[i] = ' '; // Fill the rest of characters with
spaces.
}
sprintf(contrastString, "%d", displayData.contrast);
// Prepare screen that shows current settings and change
options:
if (displayData.contrast == 0) {
// To avoid log(0). Reusing barsToPaint variable which is
not longer necessary.
barsToPaint = 1;
displayData.textRow0Length = 10 + 2 + (int)
log10(barsToPaint) + 1;
} else {
displayData.textRow0Length = 10 + 2 + (int)
log10(displayData.contrast) + 1;
}
displayData.textRow0 = malloc(displayData.textRow0Length + 1);
strncpy(displayData.textRow0, barsString,
displayData.textRow0Length);
displayData.textRow0[10] = ' ';
displayData.textRow0[11] = ' ';
if (displayData.contrast == 0) {
for (i = 0; i < (int) log10(barsToPaint) + 1; i++) {
displayData.textRow0[12 + i] = contrastString[i];
}
} else {
for (i = 0; i < (int) log10(displayData.contrast) + 1; i++)
{
displayData.textRow0[12 + i] = contrastString[i];
}
}
displayData.textRow1 = " OK
+ ";
354
displayData.textRow1Length = 16;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
// Print screen:
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayMoveCursorWriteTexts();
// Wait for a chest button pressed:
button = ackChestButtons();
switch (button) {
case CHEST_BUTTON_RIGHT:
// Right button pressed: Increase contrast:
displayData.contrast++;
if (displayData.contrast > 100) {
displayData.contrast = 100;
} else {
// Set new contrast:
displaySetSettings();
}
break;
case CHEST_BUTTON_LEFT:
// Left button pressed: Decrease contrast:
displayData.contrast--;
if (displayData.contrast < 0) {
displayData.contrast = 0;
} else {
// Set new contrast:
displaySetSettings();
}
break;
}
displayClear(); // Clear current screen.
} while (button != CHEST_BUTTON_CENTER);
}
void actionSCREEN_SPEAKER_VOLUME() {
char barsString[10]; // Bars that represent a volume bar.
char volumeString[3]; // String representation of the volume value.
int barsToPaint; // Number of bars to paint in the volume indicator.
long volumeValue; // Volume value (between 0 and 100).
char button; // Chest button pressed.
int i;
// Exit the action when the central button is pressed:
do {
// Retrieve current volume value and compute number of bars:
volumeValue = getALSA_Volume();
barsToPaint = volumeValue / 10;
// Prepare volume bar:
memset(barsString, 255, barsToPaint);
for (i = barsToPaint; i < 10; i++) {
barsString[i] = ' '; // Fill the rest of characters with
spaces.
}
sprintf(volumeString, "%d", volumeValue);
355
// Prepare screen that shows current settings and change
options:
if (volumeValue == 0) {
// To avoid log(0). Reusing barsToPaint variable which is
not longer necessary.
barsToPaint = 1;
displayData.textRow0Length = 10 + 2 + (int)
log10(barsToPaint) + 1 + 1;
} else {
displayData.textRow0Length = 10 + 2 + (int)
log10(volumeValue) + 1 + 1;
}
displayData.textRow0 = malloc(displayData.textRow0Length + 1);
strncpy(displayData.textRow0, barsString,
displayData.textRow0Length);
displayData.textRow0[10] = ' ';
displayData.textRow0[11] = ' ';
if (volumeValue == 0) {
for (i = 0; i < (int) log10(barsToPaint) + 1; i++) {
displayData.textRow0[12 + i] = volumeString[i];
}
} else {
for (i = 0; i < (int) log10(volumeValue) + 1; i++) {
displayData.textRow0[12 + i] = volumeString[i];
}
}
displayData.textRow0[12 + i] = '%';
displayData.textRow1 = " OK
+ ";
displayData.textRow1Length = 16;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
// Print screen:
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayMoveCursorWriteTexts();
// Wait for a chest button pressed:
button = ackChestButtons();
switch (button) {
case CHEST_BUTTON_RIGHT:
// Right button pressed: Increase volume:
volumeValue++;
if (volumeValue > 100) {
volumeValue = 100;
} else {
// Set new volume and play beep sound:
setALSA_Volume(volumeValue);
playBeep();
}
break;
case CHEST_BUTTON_LEFT:
// Left button pressed: Decrease volume:
volumeValue--;
if (volumeValue < 0) {
volumeValue = 0;
} else {
// Set new volume and play beep sound:
setALSA_Volume(volumeValue);
356
playBeep();
}
break;
}
displayClear(); // Clear current screen.
} while (button != CHEST_BUTTON_CENTER);
}
void actionSCREEN_UPDATE_SOFTWARE() {
// Show "Updating software... 0%":
displayData.textRow0 = "Updating";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "software... 0%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 4;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Call to apt-get update application:
system("sudo apt-get update");
// Update complete. Show "Updating software... 20%":
displayData.textRow1 = "software... 20%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Call to apt-get upgrade application:
system("sudo apt-get --yes --force-yes upgrade");
// Upgrade complete. Show "Updating software... 40%":
displayData.textRow1 = "software... 40%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Call to apt-get dist-upgrade application:
system("sudo apt-get --yes --force-yes dist-upgrade");
// Dist-Upgrade complete. Show "Updating software... 60%":
displayData.textRow1 = "software... 60%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Call to apt-get autoremove application and clean temporally
files:
system("sudo apt-get --yes --force-yes autoremove");
system("sudo apt-get clean");
357
// Autoremove and clean temporally files complete. Show "Updating
software... 80%":
displayData.textRow1 = "software... 80%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Call to rpi-update application:
system("sudo SKIP_WARNING=1 rpi-update");
// Rpi-update complete. Show "Updating software... 100%":
displayData.textRow1 = "software... 100%";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
sleep(2); // Wait 2 seconds before reboot.
// Show "I-Droid02 rebooting...":
displayClear();
displayData.textRow0 = "I-Droid02";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "rebooting...";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 3;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 2;
displayMoveCursorWriteTexts();
// Start rebooting process:
system("sudo reboot");
}
void actionSCREEN_WIFI_NETWORK() {
pthread_t ssidUpdater; // Thread used to print SSIDs on display row
1.
int counter; // Counter used to move between detected wireless
networks.
char* currentSSID; // SSID of the current selected Wi-Fi network.
char* password; // Wireless network password introduced by the user.
int passwordPosition; // Current character to set in password string
(cannot overcome 79).
int passwordType; // KEY_WPA or KEY_NONE according to the selected
Wi-Fi network.
char button; // Chest button pressed.
int i;
// Show "Scanning Wi-Fi networks" message:
displayData.textRow0 = "Scanning Wi-Fi";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "networks...";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
358
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 2;
displayMoveCursorWriteTexts();
// Scan for Wi-Fi networks:
scanWirelessNetworks();
// Print select wireless network title on display and say
instructions:
displayClear();
displayData.textRow0 = "Wi-Fi select:";
displayData.textRow0Length = strlen(displayData.textRow0);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_0);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
sayPhrase(1, "Use the left and right buttons to switch among
detected wireless networks. To select one network, press the central
button."); // Select network instructions.
counter = 0; // Start with first Wi-Fi network detected.
do {
// Avoid counter to overcome margins and make it circular:
if (counter < 0) {
counter = wirelessNetworksCount + 2; // 3 "especial"
networks are added (3 options to cancel network setup, rescan networks
or disconnect current one).
}
if (counter > (wirelessNetworksCount + 2)) {
counter = 0;
}
// Extract SSID of the current selected network:
switch (counter - wirelessNetworksCount) {
case 0:
// "Especial network" used to cancel this setting:
currentSSID = "- CANCEL -";
break;
case 1:
// "Especial network" used to rescan networks list:
currentSSID = "- RESCAN -";
break;
case 2:
// "Especial network" used to disconnect current
attached network:
currentSSID = "- DISCONNECT -";
break;
default:
// Any network:
currentSSID = malloc(ESSID_PASSWORD_LENGTHS);
strncpy(currentSSID,
wirelessNetworksData[counter].essid, ESSID_PASSWORD_LENGTHS);
break;
}
// Print current SSID of selected network:
359
pthread_create(&ssidUpdater, NULL,
actionSCREEN_WIFI_NETWORK_UpdateSSID, (void*) currentSSID);
pthread_detach(ssidUpdater);
// Wait for a button press and react to it:
button = ackChestButtons();
pthread_cancel(ssidUpdater); // Stop printing thread.
switch (button) {
case CHEST_BUTTON_RIGHT:
counter++; // Watch next wireless network.
break;
case CHEST_BUTTON_CENTER:
// A different action is required according to normal
and "especial" networks:
switch (counter - wirelessNetworksCount) {
case 0:
// "Especial network" used to cancel this
setting. Return action function:
return;
break;
case 1:
// "Especial network" used to rescan networks
list.
// Show "Scanning Wi-Fi networks" message:
displayClear();
displayData.textRow0 = "Scanning Wi-Fi";
displayData.textRow0Length =
strlen(displayData.textRow0);
displayData.textRow1 = "networks...";
displayData.textRow1Length =
strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 2;
displayMoveCursorWriteTexts();
// Call scanWirelessNetworks() again:
scanWirelessNetworks();
// Set title message:
displayData.textRow0 = "Wi-Fi select:";
displayData.textRow0Length =
strlen(displayData.textRow0);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_0);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
button = 0; // Change button flag to avoid loop
to end.
counter = 0; // Reset counter.
break;
case 2:
// "Especial network" used to disconnect current
attached network. Disconnect current network and return action function:
360
// Show "Wi-Fi interface disconnected" message:
displayClear();
displayData.textRow0 = "Wi-Fi interface";
displayData.textRow0Length =
strlen(displayData.textRow0);
displayData.textRow1 = "disconnected";
displayData.textRow1Length =
strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 2;
displayMoveCursorWriteTexts();
connectToWirelessNetwork(NULL, NULL, 0); // Any
network is defined.
sleep(2); // Give time to the user to read the
displayed message.
return;
break;
}
break;
case CHEST_BUTTON_LEFT:
counter--; // Watch previous wireless network.
break;
}
} while (button != CHEST_BUTTON_CENTER);
// If selected network has a password, request it to the user:
passwordType = wirelessNetworksData[counter].key_flags;
if (passwordType == KEY_WPA) {
// Print Enter password: title on display and say instructions:
displayClear();
displayData.textRow0 = "Enter password:";
displayData.textRow0Length = strlen(displayData.textRow0);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_0);
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
sayPhrase(1, "Use the left and right buttons to select each
password character. To enter a character, press the central button. To
confirm the entered password, press the left and right buttons
simultaneously."); // Enter password instructions.
// To exit the password loop, both left and right chest buttons
must be pressed:
password = malloc(ESSID_PASSWORD_LENGTHS); // Init password
string chain.
memset(password, 0, ESSID_PASSWORD_LENGTHS); // Full string of
NULL characters.
counter = 97 - 32; // Start with 'a' character.
passwordPosition = 0;
do {
// Avoid counter to overcome margins and make it circular:
if (counter < 0) {
counter = PRINTABLE_ASCII;
361
}
if (counter > PRINTABLE_ASCII) {
counter = 0;
}
// Clear display and print title:
displayClear();
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Print current entered password:
if (counter == PRINTABLE_ASCII) {
// Show the option to allow user to clear last entered
character:
displayData.textRow1 = "- CLEAR LAST -";
displayData.textRow1Length =
strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 1;
displayMoveCursorWriteTexts();
} else {
// Setup display to enable lower dash (_):
displayData.settings = displayData.settings | 0x08;
displaySetSettings();
// Print password normally:
displayData.textRow1 = malloc(16);
for (i = 0; i < 16; i++) {
displayData.textRow1[i] = password[passwordPosition
- (passwordPosition % 16) + i]; // passwordPosition cannot overcome 79
to avoid Segmentation fault.
}
displayData.textRow1Length =
strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
// Print current selected character in the next blank
position:
displayData.textRow1[0] = printableASCII[counter];
displayData.textRow1Length = 1;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = (passwordPosition % 16);
displayMoveCursorWriteTexts();
// Move cursor to write position:
displayData.textRow1Length = 0;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = (passwordPosition % 16);
displayMoveCursorWriteTexts();
}
// Wait for a button press and react to it:
button = ackChestButtons();
362
// Setup display to disable lower dash (_) and clear
display:
displayData.settings = displayData.settings & ~0x08;
displaySetSettings();
switch (button) {
case CHEST_BUTTON_RIGHT:
counter++;
break;
case CHEST_BUTTON_CENTER:
// Decide whether user has decided to add a
character or to clear it:
if (counter == PRINTABLE_ASCII) {
// Show character cleared message:
displayData.textRow1 = "- CHAR CLEARED -";
displayData.textRow1Length =
strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
usleep(500000);
// Clear character (only if at least one
character has been added before):
if (passwordPosition > 0) {
password[passwordPosition] = 0;
passwordPosition--;
}
} else {
// Add a character (allow a maximum value of 80
characters):
if (passwordPosition < 80) {
password[passwordPosition] =
printableASCII[counter];
passwordPosition++;
}
}
break;
case CHEST_BUTTON_LEFT:
counter--;
break;
}
} while (button != (CHEST_BUTTON_LEFT | CHEST_BUTTON_RIGHT));
}
// Print Connecting to the network message:
displayClear();
displayData.textRow0 = "Connecting to";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "the network...";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 1;
363
displayMoveCursorWriteTexts();
// Connect to the Wi-Fi network:
if (connectToWirelessNetwork(currentSSID, password, passwordType) >
0) {
// Error trying to connect to the Wi-Fi network. Print message
on display:
displayClear();
displayData.textRow0 = "Error connecting";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "to the network";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 0;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 1;
displayMoveCursorWriteTexts();
sayPhrase(3, "Error connecting to ", currentSSID, " network .");
}
// Wait some seconds whileOS is connecting to the network:
sayPhrase(3, "Connecting to ", currentSSID, " network .");
// Check if connection successful or not:
if (connectionSuccessful() == 1) {
// Connected. Print message on display:
displayClear();
displayData.textRow0 = "Connection";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "successful";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 3;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 3;
displayMoveCursorWriteTexts();
sayPhrase(1, "Connection successful!");
} else {
// Not connected. Print message on display:
displayClear();
displayData.textRow0 = "Connection";
displayData.textRow0Length = strlen(displayData.textRow0);
displayData.textRow1 = "failed";
displayData.textRow1Length = strlen(displayData.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
displayData.row = 0;
displayData.column = 3;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 5;
displayMoveCursorWriteTexts();
sayPhrase(1, "Connection failed!");
}
}
void* actionSCREEN_WIFI_NETWORK_UpdateSSID(void* arg) {
364
char* ssid = (char*) arg; // Obtain SSID.
int ssidLength = strlen(ssid);
int remainingChars; // Variable used to count remaining chars of the
ssid string that has not been printed yet.
int incrementCount; // Counter used to count how many positions of
ssid string must be skipped.
int oldCancelType;
int i;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldCancelType);
// Allow thread to be cancelled at any time.
// Know if SSID string has more than 16 characters and need to be
printed dynamically:
if ((16 - ssidLength) >= 0) {
// SSID string fits in a display row. Print it as is:
displayClear();
displayData.textRow1 = malloc(ssidLength);
strncpy(displayData.textRow1, ssid, ssidLength);
displayData.textRow1Length = ssidLength;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = (16 - ssidLength) / 2;
displayMoveCursorWriteTexts();
// Wait until main thread requests to stop this thread:
while (1) {
usleep(750000); // Wait 0.5 seconds.
}
} else {
// SSID string does not fit inside a display row.
// Start moving characters to print all of them:
remainingChars = ssidLength - 16; // Compute remaining
characters (at least 1).
incrementCount = 0;
while (1) {
// Print next partial ssid string:
displayClear();
displayData.textRow1 = malloc(16);
for (i = 0; i < 16; i++) {
displayData.textRow1[i] = ssid[incrementCount + i];
}
displayData.textRow1Length = 16;
displaySetTexts(DISPLAY_SET_TEXTS_ROW_1);
displayData.row = 0;
displayData.column = 1;
displayMoveCursorWriteTexts();
displayData.row = 1;
displayData.column = 0;
displayMoveCursorWriteTexts();
remainingChars--;
incrementCount++;
// Check if there are still characters to print:
if (remainingChars < 0) {
365
// Reset counters:
remainingChars = ssidLength - 16;
incrementCount = 0;
}
usleep(750000); // Wait 0.75 seconds.
}
}
pthread_exit(EXIT_SUCCESS);
}
void* checkForEndProgram(void* arg) {
int socketFD, socketFD2; // Socket File Descriptors.
struct sockaddr_un socketStruct; // Socket setup struct.
char* deleteCommand; // Command used to delete socket file if it
exists.
int deleteCommandLength;
// Remove socket file if exists:
if (access(OWN_SOCKET_PATH, F_OK) != -1) {
deleteCommandLength = 3 + strlen(OWN_SOCKET_PATH) + 1;
deleteCommand = malloc(deleteCommandLength);
strncpy(deleteCommand, "rm ", deleteCommandLength);
strncat(deleteCommand, OWN_SOCKET_PATH, deleteCommandLength);
system(deleteCommand);
}
// Open and setup server socket:
socketFD = openSocket();
socketStruct = setupSocket(socketFD, OWN_SOCKET_PATH, 1);
// Wait for a connection:
socketFD2 = acceptConnection(socketStruct, socketFD); // The thread
blocks here.
// An application has requested to endRemoteServer program.
// Close server socket:
closeSocket(socketFD2, NULL);
closeSocket(socketFD, OWN_SOCKET_PATH);
endProgramFlag = 1; // Change flag to 1.
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
void initPrintableASCII_Array() {
int i;
for (i = 0; i < PRINTABLE_ASCII; i++) {
printableASCII[i] = i + 32;
}
}
void printScreen(struct SCREEN print) {
// print: Screen to be printed.
pthread_mutex_lock(&printMutex);
displayClear(); // Clear current screen.
366
// Set texts:
displayData.textRow0 = print.textRow0;
displayData.textRow1 = print.textRow1;
displayData.textRow0Length = strlen(print.textRow0);
displayData.textRow1Length = strlen(print.textRow1);
displaySetTexts(DISPLAY_SET_TEXTS_ROW_BOTH);
// Set start column for each row and print it:
if (displayData.textRow0Length != 0) {
// Print row 0.
displayData.row = 0;
displayData.column = print.row0StartColumn;
displayMoveCursorWriteTexts();
}
if (displayData.textRow1Length != 0) {
// Print row 1.
displayData.row = 1;
displayData.column = print.row1StartColumn;
displayMoveCursorWriteTexts();
}
pthread_mutex_unlock(&printMutex);
}
void updateScreens() {
// Init SCREEN_MAIN:
allScreens[SCREEN_MAIN_ID].leftButtonScreenID = NO_SCREEN;
allScreens[SCREEN_MAIN_ID].centralButtonScreenID = NO_SCREEN;
allScreens[SCREEN_MAIN_ID].rightButtonScreenID =
SCREEN_STATUS_MENU_ID;
allScreens[SCREEN_MAIN_ID].needToUpdate = 0;
allScreens[SCREEN_MAIN_ID].textRow0 = "I-Droid02";
allScreens[SCREEN_MAIN_ID].textRow1 = "Ready";
allScreens[SCREEN_MAIN_ID].row0StartColumn = 3;
allScreens[SCREEN_MAIN_ID].row1StartColumn = 5;
allScreens[SCREEN_MAIN_ID].hasActionFunction = 1;
allScreens[SCREEN_MAIN_ID].actionFunction = playHello;
// Init SCREEN_STATUS_MENU:
allScreens[SCREEN_STATUS_MENU_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_STATUS_MENU_ID].centralButtonScreenID =
SCREEN_MAIN_BATTERY_ID;
allScreens[SCREEN_STATUS_MENU_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_STATUS_MENU_ID].needToUpdate = 0;
allScreens[SCREEN_STATUS_MENU_ID].textRow0 = "Status menu";
allScreens[SCREEN_STATUS_MENU_ID].textRow1 = MENU_BOTH_ARROWS;
allScreens[SCREEN_STATUS_MENU_ID].row0StartColumn = 2;
allScreens[SCREEN_STATUS_MENU_ID].row1StartColumn = 0;
allScreens[SCREEN_STATUS_MENU_ID].hasActionFunction = 0;
allScreens[SCREEN_STATUS_MENU_ID].actionFunction = NULL;
// Init SCREEN_SETUP_MENU:
allScreens[SCREEN_SETUP_MENU_ID].leftButtonScreenID =
SCREEN_STATUS_MENU_ID;
allScreens[SCREEN_SETUP_MENU_ID].centralButtonScreenID =
SCREEN_CALIBRATE_MOTORS_ID;
allScreens[SCREEN_SETUP_MENU_ID].rightButtonScreenID = NO_SCREEN;
allScreens[SCREEN_SETUP_MENU_ID].needToUpdate = 0;
allScreens[SCREEN_SETUP_MENU_ID].textRow0 = "Setup menu";
367
allScreens[SCREEN_SETUP_MENU_ID].textRow1 = MENU_LEFT_ARROW;
allScreens[SCREEN_SETUP_MENU_ID].row0StartColumn = 3;
allScreens[SCREEN_SETUP_MENU_ID].row1StartColumn = 0;
allScreens[SCREEN_SETUP_MENU_ID].hasActionFunction = 0;
allScreens[SCREEN_SETUP_MENU_ID].actionFunction = NULL;
// Init SCREEN_MAIN_BATTERY:
allScreens[SCREEN_MAIN_BATTERY_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_MAIN_BATTERY_ID].centralButtonScreenID =
SCREEN_RC_BATTERY_ID;
allScreens[SCREEN_MAIN_BATTERY_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_MAIN_BATTERY_ID].needToUpdate = 1;
allScreens[SCREEN_MAIN_BATTERY_ID].textRow0 = "Main battery";
allScreens[SCREEN_MAIN_BATTERY_ID].textRow1 =
updateSCREEN_MAIN_BATTERY();
allScreens[SCREEN_MAIN_BATTERY_ID].row0StartColumn = 2;
allScreens[SCREEN_MAIN_BATTERY_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_MAIN_BATTERY_ID].textRow1)) / 2;
allScreens[SCREEN_MAIN_BATTERY_ID].hasActionFunction = 0;
allScreens[SCREEN_MAIN_BATTERY_ID].actionFunction = NULL;
// Init SCREEN_RC_BATTERY:
allScreens[SCREEN_RC_BATTERY_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_RC_BATTERY_ID].centralButtonScreenID =
SCREEN_AMBIENT_TEMP_ID;
allScreens[SCREEN_RC_BATTERY_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_RC_BATTERY_ID].needToUpdate = 1;
allScreens[SCREEN_RC_BATTERY_ID].textRow0 = "RC battery";
allScreens[SCREEN_RC_BATTERY_ID].textRow1 =
updateSCREEN_RC_BATTERY();
allScreens[SCREEN_RC_BATTERY_ID].row0StartColumn = 3;
allScreens[SCREEN_RC_BATTERY_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_RC_BATTERY_ID].textRow1)) / 2;
allScreens[SCREEN_RC_BATTERY_ID].hasActionFunction = 0;
allScreens[SCREEN_RC_BATTERY_ID].actionFunction = NULL;
// Init SCREEN_AMBIENT_TEMP:
allScreens[SCREEN_AMBIENT_TEMP_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_AMBIENT_TEMP_ID].centralButtonScreenID =
SCREEN_RPI_TEMP_ID;
allScreens[SCREEN_AMBIENT_TEMP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_AMBIENT_TEMP_ID].needToUpdate = 1;
allScreens[SCREEN_AMBIENT_TEMP_ID].textRow0 = "Ambient temp";
allScreens[SCREEN_AMBIENT_TEMP_ID].textRow1 =
updateSCREEN_AMBIENT_TEMP();
allScreens[SCREEN_AMBIENT_TEMP_ID].row0StartColumn = 2;
allScreens[SCREEN_AMBIENT_TEMP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_AMBIENT_TEMP_ID].textRow1)) / 2;
allScreens[SCREEN_AMBIENT_TEMP_ID].hasActionFunction = 0;
allScreens[SCREEN_AMBIENT_TEMP_ID].actionFunction = NULL;
// Init SCREEN_RPI_TEMP:
allScreens[SCREEN_RPI_TEMP_ID].leftButtonScreenID = SCREEN_MAIN_ID;
368
allScreens[SCREEN_RPI_TEMP_ID].centralButtonScreenID =
SCREEN_RFM43B_TEMP_ID;
allScreens[SCREEN_RPI_TEMP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_RPI_TEMP_ID].needToUpdate = 1;
allScreens[SCREEN_RPI_TEMP_ID].textRow0 = "RaspberryPi temp";
allScreens[SCREEN_RPI_TEMP_ID].textRow1 = updateSCREEN_RPI_TEMP();
allScreens[SCREEN_RPI_TEMP_ID].row0StartColumn = 0;
allScreens[SCREEN_RPI_TEMP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_RPI_TEMP_ID].textRow1)) / 2;
allScreens[SCREEN_RPI_TEMP_ID].hasActionFunction = 0;
allScreens[SCREEN_RPI_TEMP_ID].actionFunction = NULL;
// Init SCREEN_RFM43B_TEMP:
allScreens[SCREEN_RFM43B_TEMP_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_RFM43B_TEMP_ID].centralButtonScreenID =
SCREEN_RFM31B_TEMP_ID;
allScreens[SCREEN_RFM43B_TEMP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_RFM43B_TEMP_ID].needToUpdate = 1;
allScreens[SCREEN_RFM43B_TEMP_ID].textRow0 = "433 MHz TX temp";
allScreens[SCREEN_RFM43B_TEMP_ID].textRow1 =
updateSCREEN_RFM43B_TEMP();
allScreens[SCREEN_RFM43B_TEMP_ID].row0StartColumn = 0;
allScreens[SCREEN_RFM43B_TEMP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_RFM43B_TEMP_ID].textRow1)) / 2;
allScreens[SCREEN_RFM43B_TEMP_ID].hasActionFunction = 0;
allScreens[SCREEN_RFM43B_TEMP_ID].actionFunction = NULL;
// Init SCREEN_RFM31B_TEMP:
allScreens[SCREEN_RFM31B_TEMP_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_RFM31B_TEMP_ID].centralButtonScreenID =
SCREEN_ULTRASONIC_VALUE_ID;
allScreens[SCREEN_RFM31B_TEMP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_RFM31B_TEMP_ID].needToUpdate = 1;
allScreens[SCREEN_RFM31B_TEMP_ID].textRow0 = "433 MHz RX temp";
allScreens[SCREEN_RFM31B_TEMP_ID].textRow1 =
updateSCREEN_RFM31B_TEMP();
allScreens[SCREEN_RFM31B_TEMP_ID].row0StartColumn = 0;
allScreens[SCREEN_RFM31B_TEMP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_RFM31B_TEMP_ID].textRow1)) / 2;
allScreens[SCREEN_RFM31B_TEMP_ID].hasActionFunction = 0;
allScreens[SCREEN_RFM31B_TEMP_ID].actionFunction = NULL;
// Init SCREEN_ULTRASONIC_VALUE:
allScreens[SCREEN_ULTRASONIC_VALUE_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].centralButtonScreenID =
SCREEN_ETHERNET_IP_ID;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].needToUpdate = 1;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].textRow0 = "Ultrasonic
dist.";
allScreens[SCREEN_ULTRASONIC_VALUE_ID].textRow1 =
updateSCREEN_ULTRASONIC_VALUE();
allScreens[SCREEN_ULTRASONIC_VALUE_ID].row0StartColumn = 0;
369
allScreens[SCREEN_ULTRASONIC_VALUE_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_ULTRASONIC_VALUE_ID].textRow1)) / 2;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].hasActionFunction = 0;
allScreens[SCREEN_ULTRASONIC_VALUE_ID].actionFunction = NULL;
// Init SCREEN_ETHERNET_IP:
allScreens[SCREEN_ETHERNET_IP_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_ETHERNET_IP_ID].centralButtonScreenID =
SCREEN_WIRELESS_IP_ID;
allScreens[SCREEN_ETHERNET_IP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_ETHERNET_IP_ID].needToUpdate = 1;
allScreens[SCREEN_ETHERNET_IP_ID].textRow0 = "Ethernet IP";
allScreens[SCREEN_ETHERNET_IP_ID].textRow1 =
updateSCREEN_ETHERNET_IP();
allScreens[SCREEN_ETHERNET_IP_ID].row0StartColumn = 2;
allScreens[SCREEN_ETHERNET_IP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_ETHERNET_IP_ID].textRow1)) / 2;
allScreens[SCREEN_ETHERNET_IP_ID].hasActionFunction = 0;
allScreens[SCREEN_ETHERNET_IP_ID].actionFunction = NULL;
// Init SCREEN_WIRELESS_IP:
allScreens[SCREEN_WIRELESS_IP_ID].leftButtonScreenID =
SCREEN_MAIN_ID;
allScreens[SCREEN_WIRELESS_IP_ID].centralButtonScreenID =
SCREEN_STATUS_MENU_ID;
allScreens[SCREEN_WIRELESS_IP_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_WIRELESS_IP_ID].needToUpdate = 1;
allScreens[SCREEN_WIRELESS_IP_ID].textRow0 = "Wi-Fi IP";
allScreens[SCREEN_WIRELESS_IP_ID].textRow1 =
updateSCREEN_WIRELESS_IP();
allScreens[SCREEN_WIRELESS_IP_ID].row0StartColumn = 4;
allScreens[SCREEN_WIRELESS_IP_ID].row1StartColumn = (16 strlen(allScreens[SCREEN_WIRELESS_IP_ID].textRow1)) / 2;
allScreens[SCREEN_WIRELESS_IP_ID].hasActionFunction = 0;
allScreens[SCREEN_WIRELESS_IP_ID].actionFunction = NULL;
// Init SCREEN_CALIBRATE_MOTORS:
allScreens[SCREEN_CALIBRATE_MOTORS_ID].leftButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].centralButtonScreenID =
NO_SCREEN;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].rightButtonScreenID =
SCREEN_DISPLAY_CONTRAST_ID;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].needToUpdate = 0;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].textRow0 = "Calibrate
motors";
allScreens[SCREEN_CALIBRATE_MOTORS_ID].textRow1 = MENU_BACK_GO_NEXT;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].row0StartColumn = 0;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].row1StartColumn = 0;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].hasActionFunction = 1;
allScreens[SCREEN_CALIBRATE_MOTORS_ID].actionFunction =
actionSCREEN_CALIBRATE_MOTORS;
// Init SCREEN_DISPLAY_CONTRAST:
allScreens[SCREEN_DISPLAY_CONTRAST_ID].leftButtonScreenID =
SCREEN_CALIBRATE_MOTORS_ID;
370
allScreens[SCREEN_DISPLAY_CONTRAST_ID].centralButtonScreenID =
NO_SCREEN;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].rightButtonScreenID =
SCREEN_SPEAKER_VOLUME_ID;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].needToUpdate = 0;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].textRow0 = "Display
contrast";
allScreens[SCREEN_DISPLAY_CONTRAST_ID].textRow1 =
MENU_BACK_CHANGE_NEXT;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].row0StartColumn = 0;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].row1StartColumn = 0;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].hasActionFunction = 1;
allScreens[SCREEN_DISPLAY_CONTRAST_ID].actionFunction =
actionSCREEN_DISPLAY_CONTRAST;
// Init SCREEN_SPEAKER_VOLUME:
allScreens[SCREEN_SPEAKER_VOLUME_ID].leftButtonScreenID =
SCREEN_DISPLAY_CONTRAST_ID;
allScreens[SCREEN_SPEAKER_VOLUME_ID].centralButtonScreenID =
NO_SCREEN;
allScreens[SCREEN_SPEAKER_VOLUME_ID].rightButtonScreenID =
SCREEN_UPDATE_SOFTWARE_ID;
allScreens[SCREEN_SPEAKER_VOLUME_ID].needToUpdate = 0;
allScreens[SCREEN_SPEAKER_VOLUME_ID].textRow0 = "Speaker volume";
allScreens[SCREEN_SPEAKER_VOLUME_ID].textRow1 =
MENU_BACK_CHANGE_NEXT;
allScreens[SCREEN_SPEAKER_VOLUME_ID].row0StartColumn = 1;
allScreens[SCREEN_SPEAKER_VOLUME_ID].row1StartColumn = 0;
allScreens[SCREEN_SPEAKER_VOLUME_ID].hasActionFunction = 1;
allScreens[SCREEN_SPEAKER_VOLUME_ID].actionFunction =
actionSCREEN_SPEAKER_VOLUME;
// init SCREEN_UPDATE_SOFTWARE:
allScreens[SCREEN_UPDATE_SOFTWARE_ID].leftButtonScreenID =
SCREEN_SPEAKER_VOLUME_ID;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].centralButtonScreenID =
NO_SCREEN;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].rightButtonScreenID =
SCREEN_WIFI_NETWORK_ID;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].needToUpdate = 0;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].textRow0 = "Update software";
allScreens[SCREEN_UPDATE_SOFTWARE_ID].textRow1 = MENU_BACK_GO_NEXT;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].row0StartColumn = 0;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].row1StartColumn = 0;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].hasActionFunction = 1;
allScreens[SCREEN_UPDATE_SOFTWARE_ID].actionFunction =
actionSCREEN_UPDATE_SOFTWARE;
// Init SCREEN_WIFI_NETWORK:
allScreens[SCREEN_WIFI_NETWORK_ID].leftButtonScreenID =
SCREEN_UPDATE_SOFTWARE_ID;
allScreens[SCREEN_WIFI_NETWORK_ID].centralButtonScreenID =
NO_SCREEN;
allScreens[SCREEN_WIFI_NETWORK_ID].rightButtonScreenID =
SCREEN_SETUP_MENU_ID;
allScreens[SCREEN_WIFI_NETWORK_ID].needToUpdate = 0;
allScreens[SCREEN_WIFI_NETWORK_ID].textRow0 = "Wi-Fi network AP";
allScreens[SCREEN_WIFI_NETWORK_ID].textRow1 = MENU_BACK_SETUP_TOP;
allScreens[SCREEN_WIFI_NETWORK_ID].row0StartColumn = 0;
allScreens[SCREEN_WIFI_NETWORK_ID].row1StartColumn = 0;
371
allScreens[SCREEN_WIFI_NETWORK_ID].hasActionFunction = 1;
allScreens[SCREEN_WIFI_NETWORK_ID].actionFunction =
actionSCREEN_WIFI_NETWORK;
}
void* updateScreenThread(void* arg) {
while (1) {
// Update all screens:
updateScreens();
if (allScreens[currentScreenID].needToUpdate == 1) {
// Current screen needs to be refreshed:
printScreen(allScreens[currentScreenID]);
}
sleep(1);
}
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
char* updateSCREEN_MAIN_BATTERY() {
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(batteryData.bat) + 3 +
strlen(batteryData.batPercent) + 2 + 1;
text = malloc(textLength);
strncpy(text, batteryData.bat, textLength);
strncat(text, " V ", textLength);
strncat(text, batteryData.batPercent, textLength);
strncat(text, " %", textLength);
return text;
}
char* updateSCREEN_RC_BATTERY() {
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(batteryData.remoteBat) + 3 +
strlen(batteryData.remoteBatPercent) + 2 + 1;
text = malloc(textLength);
strncpy(text, batteryData.remoteBat, textLength);
strncat(text, " V ", textLength);
strncat(text, batteryData.remoteBatPercent, textLength);
strncat(text, " %", textLength);
return text;
}
char* updateSCREEN_AMBIENT_TEMP() {
const char degree = 223; // Degree symbol (º).
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(temperatureData.ambient) + 1 + 1 + 1 + 1;
372
text = malloc(textLength);
strncpy(text, temperatureData.ambient, textLength);
strncat(text, " ", textLength);
strncat(text, &degree, 1);
strncat(text, "C", textLength);
return text;
}
char* updateSCREEN_RPI_TEMP() {
const char degree = 223; // Degree symbol (º).
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(temperatureData.rpiTemp) + 1 + 1 + 1 + 1;
text = malloc(textLength);
strncpy(text, temperatureData.rpiTemp, textLength);
strncat(text, " ", textLength);
strncat(text, &degree, 1);
strncat(text, "C", textLength);
return text;
}
char* updateSCREEN_RFM43B_TEMP() {
const char degree = 223; // Degree symbol (º).
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(temperatureData.rfm43bTemp) + 1 + 1 + 1 + 1;
text = malloc(textLength);
strncpy(text, temperatureData.rfm43bTemp, textLength);
strncat(text, " ", textLength);
strncat(text, &degree, 1);
strncat(text, "C", textLength);
return text;
}
char* updateSCREEN_RFM31B_TEMP() {
const char degree = 223; // Degree symbol (º).
char* text; // Text containing data.
int textLength; // Length of previous text.
// Make text by concatenating data strings:
textLength = strlen(temperatureData.rfm31bTemp) + 1 + 1 + 1 + 1;
text = malloc(textLength);
strncpy(text, temperatureData.rfm31bTemp, textLength);
strncat(text, " ", textLength);
strncat(text, &degree, 1);
strncat(text, "C", textLength);
return text;
}
char* updateSCREEN_ULTRASONIC_VALUE() {
char* text; // Text containing data.
int textLength; // Length of previous text.
373
// Make text by concatenating data strings:
textLength = strlen(ultrasonicSensorDistanceValue) + 3 + 1;
text = malloc(textLength);
strncpy(text, ultrasonicSensorDistanceValue, textLength);
strncat(text, " cm", textLength);
return text;
}
char* updateSCREEN_ETHERNET_IP() {
char* text; // Text containing data.
char* ipAddress; // String containing IP address.
int textLength; // Length of previous text.
// Obtain IP address:
if ((getIPAddress(ETHERNET_INTERFACE, &ipAddress) > 0) ||
(strcmp(ipAddress, "") == 0) || (strstr(ipAddress, ".") == NULL)) {
// Error in obtain Ethernet IP address or interface
disconnected.
ipAddress = "Not available";
}
// Make text by concatenating data strings:
textLength = strlen(ipAddress) + 1;
text = malloc(textLength);
strncpy(text, ipAddress, textLength);
return text;
}
char* updateSCREEN_WIRELESS_IP() {
char* text; // Text containing data.
char* ipAddress; // String containing IP address.
int textLength; // Length of previous text.
// Obtain IP address:
if ((getIPAddress(WIRELESS_INTERFACE, &ipAddress) > 0) ||
(strcmp(ipAddress, "") == 0) || (strstr(ipAddress, ".") == NULL)) {
// Error in obtain Wi-Fi IP address or interface disconnected.
ipAddress = "Not available";
}
// Make text by concatenating data strings:
textLength = strlen(ipAddress) + 1;
text = malloc(textLength);
strncpy(text, ipAddress, textLength);
return text;
}
Devices.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef DEVICES_H
374
#define DEVICES_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <stdio.h>
#include <stdlib.h>
#include "SocketManager.h"
// Constants:
#define ATTACH_SENSOR_COMMAND 'A'
#define CHEST_BUTTON_CENTER 0x02
#define CHEST_BUTTON_LEFT 0x04
#define CHEST_BUTTON_RIGHT 0x01
#define CHEST_BUTTONS_SOCKET "/tmp/idroid02/chestButtons"
#define CLOSE_CONNECTION_COMMAND 'C'
#define DISPLAY_CLEAR 'D'
#define DISPLAY_MOVE_CURSOR_WRITE_DATA 'M'
#define DISPLAY_SET_TEXTS 'T'
#define DISPLAY_SET_TEXTS_ROW_0 0
#define DISPLAY_SET_TEXTS_ROW_1 1
#define DISPLAY_SET_TEXTS_ROW_BOTH 2
#define DISPLAY_SOCKET "/tmp/idroid02/display"
#define GET_DATA_COMMAND 'G'
#define MOTORS_CALIBRATE_LIMITED_MOTOR 'l'
#define MOTORS_RESET_LIMITED_MOTORS_DATA 'r'
#define MOTORS_SOCKET "/tmp/idroid02/motors"
#define SET_DATA_COMMAND 'S'
// Global variables:
int chestButtonsFD, displayFD, motorsFD; // Sockets File
Descriptors.
struct sockaddr_un chestButtonsSettings, displaySettings,
motorsSettings; // Sockets setup structs.
// Struct to store current display settings:
struct DISPLAY_SETTINGS {
char column; // Current column where cursor is placed (1-16).
char contrast; // Current display contrast (0-100, default: 50).
char row; // Current row where cursor is placed (1-2).
char settings; // Display settings. Byte format: 0000x1x2x3x4.
x1 = Cursor ON/OFF, x2 = blinking ON/OFF, x3 = Display ON/OFF, x4 =
Write Left-Right/Right-Left.
char* textRow0; // Text at row 0.
char textRow0Length; // Number of Bytes of row 0 text.
char* textRow1; // Text at row 1.
char textRow1Length; // Number of Bytes of row 1 text.
} displayData;
// Functions:
char ackChestButtons(); // Function to attach the chest buttons
touch.
void connectDisplaySocket(); // Function to open, setup and connect
display socket.
void connectMotorsSocket(); // Function to open, setup and connect
motors socket.
375
void disconnectDisplaySocket(); // Function to disconnect display
socket.
void disconnectMotorsSocket(); // Function to disconnect and close
motors socket.
void displayClear(); // Function to send clear display command.
void displayGetSettings(); // Function to retrieve current display
settings.
void displayMoveCursorWriteTexts(); // Function to send move
cursor/write texts command.
void displaySetSettings(); // Function to change current display
settings according to the values set in global scope.
void displaySetTexts(char); // Function to send set texts command.
void motorsCalibrate(int, char); // Function to send calibrate
limited motor command.
void motorsResetData(); // Function to send motors reset position
command (to be done after all motors calibration).
#ifdef __cplusplus
}
#endif
#endif /* DEVICES_H */
Devices.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* This file contains all functions and routines to access any device
through
* I-Droid02 Driver.
*/
#include "Devices.h"
char ackChestButtons() {
// Returns: Received flag indicating which chest button has been
touched.
unsigned char commandTX[1];
unsigned char commandRX[1];
char touchFlag;
// Open, setup and connect to chest buttons socket:
chestButtonsFD = openSocket();
chestButtonsSettings = setupSocket(chestButtonsFD,
CHEST_BUTTONS_SOCKET, 0);
connectSocket(chestButtonsFD, chestButtonsSettings);
// Send ATTACH_SENSOR command:
commandTX[0] = ATTACH_SENSOR_COMMAND;
if (sendData(chestButtonsFD, commandTX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
// Wait until a touch is produced and acknowledge it to close
connection:
376
if (receiveData(chestButtonsFD, commandRX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
touchFlag = commandRX[0];
// Close chest buttons socket:
commandTX[0] = CLOSE_CONNECTION_COMMAND;
if (sendData(chestButtonsFD, commandTX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
closeSocket(chestButtonsFD, NULL); // Close touch sensor socket.
return touchFlag;
}
void connectDisplaySocket() {
// Open, setup and connect to display socket:
displayFD = openSocket();
displaySettings = setupSocket(displayFD, DISPLAY_SOCKET, 0);
connectSocket(displayFD, displaySettings);
// Set display default settings:
displayData.contrast = 50;
displayData.settings = 0b00000011;
displaySetSettings();
// Clear current display data:
displayClear();
}
void connectMotorsSocket() {
// Open, setup and connect to motors socket:
motorsFD = openSocket();
motorsSettings = setupSocket(motorsFD, MOTORS_SOCKET, 0);
connectSocket(motorsFD, motorsSettings);
}
void disconnectDisplaySocket() {
unsigned char closeBuffer[1]; // Buffer used to close connections.
closeBuffer[0] = CLOSE_CONNECTION_COMMAND;
// Close display socket:
if (sendData(displayFD, closeBuffer, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
closeSocket(displayFD, NULL); // Close display socket.
}
void disconnectMotorsSocket() {
unsigned char closeBuffer[1]; // Buffer used to close connections.
closeBuffer[0] = CLOSE_CONNECTION_COMMAND;
// Close motors socket:
if (sendData(motorsFD, closeBuffer, 1) < 0) {
377
// Error. Program must exit.
exit(EXIT_FAILURE);
}
closeSocket(motorsFD, NULL); // Close motors socket.
}
void displayClear() {
unsigned char commandTX[1];
// Make and send command:
commandTX[0] = DISPLAY_CLEAR;
if (sendData(displayFD, commandTX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
void displayGetSettings() {
unsigned char commandTX[1];
unsigned char commandRX[5];
// Make and send command:
commandTX[0] = GET_DATA_COMMAND;
if (sendData(displayFD, commandTX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
// Receive command and fill in data:
// Receive settings and text length:
if (receiveData(displayFD, commandRX, 5) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
displayData.contrast = commandRX[0];
displayData.settings = commandRX[1];
displayData.column = (commandRX[2] & 0xF0) >> 4;
displayData.row = commandRX[2] & 0x0F;
displayData.textRow0Length = commandRX[3];
displayData.textRow1Length = commandRX[4];
// Receive texts:
displayData.textRow0 = malloc(displayData.textRow0Length);
displayData.textRow1 = malloc(displayData.textRow1Length);
if (receiveData(displayFD, displayData.textRow0,
displayData.textRow0Length) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
if (receiveData(displayFD, displayData.textRow1,
displayData.textRow1Length) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
void displayMoveCursorWriteTexts() {
unsigned char commandTX[2];
// Make and send command:
378
commandTX[0] = DISPLAY_MOVE_CURSOR_WRITE_DATA;
commandTX[1] = (displayData.column << 4) | displayData.row;
if (sendData(displayFD, commandTX, 2) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
void displaySetSettings() {
unsigned char commandTX[3];
// Make and send command:
commandTX[0] = SET_DATA_COMMAND;
commandTX[1] = displayData.contrast;
commandTX[2] = displayData.settings;
if (sendData(displayFD, commandTX, 3) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
void displaySetTexts(char rowsToSet) {
// rowsToSet: Flag to determine which rows are wanted to be set (0,
1 or both).
unsigned char commandTX[38];
int commandLength;
int i;
// Make 1st part of the command:
commandTX[0] = DISPLAY_SET_TEXTS;
commandLength = 1;
// If row 0 must be printed, include it in the command:
if ((rowsToSet == DISPLAY_SET_TEXTS_ROW_0) || (rowsToSet ==
DISPLAY_SET_TEXTS_ROW_BOTH)) {
commandTX[commandLength] = 0x00; // Row 0.
commandTX[commandLength + 1] = displayData.textRow0Length;
for (i = 0; i < displayData.textRow0Length; i++) {
commandTX[commandLength + 2 + i] = displayData.textRow0[i];
}
commandLength = commandLength + 2 + displayData.textRow0Length;
}
// If row 1 must be printed, include it in the command:
if ((rowsToSet == DISPLAY_SET_TEXTS_ROW_1) || (rowsToSet ==
DISPLAY_SET_TEXTS_ROW_BOTH)) {
commandTX[commandLength] = 0x01; // Row 1.
commandTX[commandLength + 1] = displayData.textRow1Length;
for (i = 0; i < displayData.textRow1Length; i++) {
commandTX[commandLength + 2 + i] = displayData.textRow1[i];
}
commandLength = commandLength + 2 + displayData.textRow1Length;
}
// Make last part of the command:
commandTX[commandLength] = DISPLAY_SET_TEXTS;
commandLength = commandLength + 1;
// Send command:
if (sendData(displayFD, commandTX, commandLength) < 0) {
// Error. Program must exit.
379
exit(EXIT_FAILURE);
}
}
void motorsCalibrate(int motorNumber, char direction) {
// motorNumber: The motor to be moved: 5 (head tilt), 6 (head pan),
7 (left arm), 8 (right arm), 9 (hand), 10 (hip).
// direction: If 1, the motor will move up/forwards/left/open hand.
If 0, the motor will move down/backwards/right/close hand.
unsigned char commandTX[3];
// Make and send command:
commandTX[0] = MOTORS_CALIBRATE_LIMITED_MOTOR;
commandTX[1] = motorNumber;
commandTX[2] = direction;
if (sendData(motorsFD, commandTX, 3) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
void motorsResetData() {
unsigned char commandTX[1];
// Make and send command:
commandTX[0] = MOTORS_RESET_LIMITED_MOTORS_DATA;
if (sendData(motorsFD, commandTX, 1) < 0) {
// Error. Program must exit.
exit(EXIT_FAILURE);
}
}
FileManager.h source code: (See Appendix 9).
FileManager.c source code: (See Appendix 9).
Networks.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef NETWORKS_H
#define NETWORKS_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <iwlib.h>
#include <netdb.h>
#include <pthread.h>
#include <stdio.h>
380
#include
#include
#include
#include
<stdlib.h>
<string.h>
<sys/socket.h>
<unistd.h>
#include "FileManager.h"
// Constants:
#define ETHERNET_INTERFACE "eth0"
#define KEY_NONE 34816
#define KEY_WPA 2048
#define WIRELESS_INTERFACE "wlan0"
#define WPA_SUPPLICANT_CONFIG_FILE
"/etc/wpa_supplicant/wpa_supplicant.conf"
#define WPA_SUPPLICANT_CONFIG_FILE_TEMP "/tmp/wpa_supplicant.conf"
// Global variables:
char timeout; // If 1 during connectionTimeout call, a timeout
established to connect to wireless network has passed.
int wirelessNetworksCount; // Number of networks found.
wireless_config* wirelessNetworksData; // All information about all
wireless networks found during a scan.
// Functions:
int connectToWirelessNetwork(char*, char*, int); // Function to
connect to the Wi-Fi network specified (if SSID is NULL, the current
connected network is disconnected).
int connectionSuccessful(); // Routine to know whether previous
connectToWifiNetwork() call was successful or not.
void* connectionTimeout(void*); // Thread used to put a limit on
time used to connect to the network.
int getIPAddress(char*, char**); // Function to get the current IP
address of a network interface.
int scanWirelessNetworks(); // Function to scan wireless networks.
#ifdef __cplusplus
}
#endif
#endif /* NETWORKS_H */
Networks.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* This file contains all functions and routines to connect to a Wi-Fi
network,
* with the help of the OS. It takes into account if the Wi-Fi network
is known
* by the OS (a successful connection was done before) and uses the
touch sensor
* and the chest buttons to capture user inputs. This file can also be
used to
* retrieve current IP addresses from eth0 and wlan0 networks.
*/
381
#include "Networks.h"
int connectToWirelessNetwork(char* essid, char* password, int keyType) {
// essid: The ESSID (name) of the AP.
// password: The network encrypting key.
// keyType: The kind of security (WEP, WPA, NONE).
// Returns: 0 if any error occurs, or 1 otherwise.
FILE* configFile;
FILE* configFileTemp;
char* command;
int commandLength;
// Kill current wpa_supplicant task:
commandLength = 30 + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "sudo killall -q wpa_supplicant", commandLength);
system(command);
// Remove current configuration file:
commandLength = 8 + strlen(WPA_SUPPLICANT_CONFIG_FILE) + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "sudo rm ", commandLength);
strncat(command, WPA_SUPPLICANT_CONFIG_FILE, commandLength);
system(command);
// Create configuration file again:
configFile = openFile(WPA_SUPPLICANT_CONFIG_FILE_TEMP, "w");
if (configFile == NULL) {
return 1;
}
// Write initial common lines of the file:
writeLineToFile(configFile, "country=ES\n");
writeLineToFile(configFile,
"ctrl_interface=DIR=/var/run/wpa_supplicant GPOUP=netdev\n");
writeLineToFile(configFile, "update_config=1\n");
writeLineToFile(configFile, "\n");
// Only setup a Wi-Fi network if SSID is a valid string (otherwise
this routine has been called to disconnect current network):
if (essid != NULL) {
// Write network block:
writeLineToFile(configFile, "network={\n");
commandLength = 7 + strlen(essid) + 2 + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "\tssid=\"", commandLength);
strncat(command, essid, commandLength);
strncat(command, "\"\n", commandLength);
writeLineToFile(configFile, command);
// As a function of the key type, write different lines:
switch (keyType) {
case KEY_NONE:
// Wireless network without password:
writeLineToFile(configFile, "proto=RSN\n");
writeLineToFile(configFile, "key_mgmt=NONE\n");
break;
case KEY_WPA:
// Wireless network with WPA/WPA2 password:
commandLength = 6 + strlen(password) + 2 + 1;
382
command = malloc(commandLength); // Memory allocation.
strncpy(command, "\tpsk=\"", commandLength);
strncat(command, password, commandLength);
strncat(command, "\"\n", commandLength);
writeLineToFile(configFile, command);
break;
}
// Write last common line of the file:
writeLineToFile(configFile, "}");
}
// Close the file:
closeFile(configFile);
// Move file from temporal directory to definitive one:
commandLength = 8 + strlen(WPA_SUPPLICANT_CONFIG_FILE_TEMP) + 1 +
strlen(WPA_SUPPLICANT_CONFIG_FILE) + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "sudo mv ", commandLength);
strncat(command, WPA_SUPPLICANT_CONFIG_FILE_TEMP, commandLength);
strncat(command, " ", commandLength);
strncat(command, WPA_SUPPLICANT_CONFIG_FILE, commandLength);
system(command);
// Start a new instance of wpa_supplicant with the new configuration
file:
commandLength = 81 + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "sudo wpa_supplicant -B -Dwext -i wlan0 -c
/etc/wpa_supplicant/wpa_supplicant.conf", commandLength);
system(command);
// Wi-Fi network should be connected (must check for errors).
return 0;
}
int connectionSuccessful() {
// Returns 1 if success or 0 otherwise.
pthread_t timeoutThread;
char* ip; // IP address of Wi-Fi network.
timeout = 0; // Init timeout flag.
// Start timeout thread:
pthread_create(&timeoutThread, NULL, connectionTimeout, NULL);
pthread_detach(timeoutThread);
// Do an active-wait loop until IP address is set or timeout has
passed:
do {
getIPAddress(WIRELESS_INTERFACE, &ip);
usleep(200000); // Wait 200 ms between calls.
} while ((timeout == 0) && (strstr(ip, ".") == NULL));
// Check results:
if ((timeout == 0) && (strstr(ip, ".") != NULL)) {
// Success!
pthread_cancel(timeoutThread); // Stop timeout thread.
383
return 1;
} else {
// Fail!
return 0;
}
}
void* connectionTimeout(void* arg) {
int oldCancelType;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldCancelType);
// Allow thread to be cancelled at any time.
sleep(20); // Wait 20 seconds.
timeout = 1; // Timeout!
pthread_exit(EXIT_SUCCESS);
}
int getIPAddress(char* inface, char** address) {
// inface: The interface which is wanted to know its IP address.
// address: A char pointer to store address in a string.
// Returns: 0 if any error occurs, or 1 otherwise.
struct ifaddrs* ifaddr;
struct ifaddrs* ifa;
int family, s;
*address = malloc(NI_MAXHOST);
// Get full set of IP addresses:
if (getifaddrs(&ifaddr) == -1) {
perror("Error during getifaddrs");
return 1;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) {
// This interface does not have any IP address defined.
continue;
}
// If interface is found inside set, extract IP address:
if ((strcmp(ifa->ifa_name, inface) == 0) && (ifa->ifa_addr>sa_family == AF_INET)) {
s = getnameinfo(ifa->ifa_addr, sizeof (struct sockaddr_in),
*address, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (s != 0) {
printf("getnameinfo failed: %s\n", gai_strerror(s));
return 1;
}
}
}
freeifaddrs(ifaddr);
return 0;
}
int scanWirelessNetworks() {
// Returns: 0 if any error occurs, or 1 otherwise.
384
wireless_scan_head head;
wireless_scan* netCount;
wireless_scan* firstNetwork; // First network found used as a
reference to get the rest.
char* initialCommand; // Command used to call OS to force network
scan.
int commandLength;
iwrange range;
int counter; // Number of networks found during the scan.
int sock;
// Prepare OS command to force network scan:
commandLength = 12 + strlen(WIRELESS_INTERFACE) + 5 + 1;
initialCommand = malloc(commandLength);
strncpy(initialCommand, "sudo iwlist ", commandLength);
strncat(initialCommand, WIRELESS_INTERFACE, commandLength);
strncat(initialCommand, " scan", commandLength);
system(initialCommand);
// Open socket to kernel:
sock = iw_sockets_open();
// Get some metadata to use for scanning:
if (iw_get_range_info(sock, WIRELESS_INTERFACE, &range) < 0) {
perror("Error during iw_get_range_info");
return 1;
}
// Perform the scan:
if (iw_scan(sock, WIRELESS_INTERFACE, range.we_version_compiled,
&head) < 0) {
perror("Error during iw_scan");
return 1;
}
firstNetwork = head.result;
// Count the networks found:
counter = 0;
netCount = head.result;
while (netCount != NULL) {
counter++;
netCount = netCount->next;
}
wirelessNetworksCount = counter;
// Store the networks data:
free(wirelessNetworksData); // Free previous allocation.
wirelessNetworksData = malloc(wirelessNetworksCount * sizeof
(wireless_config)); // Memory allocation.
counter = 0;
while (firstNetwork != NULL) {
wirelessNetworksData[counter] = firstNetwork->b;
firstNetwork = firstNetwork->next;
counter++;
}
// Close socket:
iw_sockets_close(sock);
return 0;
385
}
SocketManager.h source code: (See Appendix 9).
SocketManager.c source code: (See Appendix 9).
Speaker.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef SPEAKER_H
#define SPEAKER_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <alsa/asoundlib.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Constants:
#define CARD "default"
#define MIXER "PCM"
#define PITCH "50"
#define SPEED "140"
#define VOICE_FILE "/tmp/idroid02/voice.wav"
#define VOLUME "200"
// Functions:
long getALSA_Volume(); // Function to get current volume from ALSA
driver.
void playBeep(); // Function to play a beep sound.
void playHello(); // Function to play hello message.
void sayPhrase(int, ...); // Function to say a phrase using espeak.
void setALSA_Volume(long); // Function to set a new volume to ALSA
driver.
#ifdef __cplusplus
}
#endif
#endif /* SPEAKER_H */
Speaker.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
386
/* This file contains all functions and routines to get current speaker
volume
* from Linux ALSA driver and set a new one and call espeak to make IDroid02
* say any phrase.
*/
#include "Speaker.h"
long getALSA_Volume() {
// Returns: The current ALSA volume value set.
long min, max, volumePercentage; // Obtained in dB 100 times from
ALSA.
float temp;
snd_mixer_t* handle;
snd_mixer_elem_t* elem;
snd_mixer_selem_id_t* sid;
// Load ALSA driver:
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, CARD);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
// Load mixer:
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, MIXER);
elem = snd_mixer_find_selem(handle, sid);
// Load current ALSA volume:
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_get_playback_volume(elem, 0, &volumePercentage);
temp = (log10(101) / (max - min)) * (volumePercentage - min);
volumePercentage = (long) round(pow(10.0, temp) - 1);
snd_mixer_close(handle);
return volumePercentage;
}
void playBeep() {
system("audioscript aplay /usr/share/sounds/Beep.wav");
}
void playHello() {
system("audioscript aplay /usr/share/sounds/Hello.wav");
}
void sayPhrase(int numPhrases, ...) {
// numPhrases: The number of phrases to concatenate in the same
speech.
// ...: Each one of the phrases, parsed as string chains.
va_list argumentsList; // List of arguments container.
char* phrases[numPhrases]; // All phrases parsed from argumentsList.
int totalPhrasesLength; // The total length of all string chain.
char* command; // The string chain containing the command to be
executed.
int commandLength; // The length of the command.
int i;
387
// Extract string chains from arguments list:
va_start(argumentsList, numPhrases);
totalPhrasesLength = 0;
for (i = 0; i < numPhrases; i++) {
phrases[i] = va_arg(argumentsList, char*);
totalPhrasesLength = totalPhrasesLength + strlen(phrases[i]) +
1; // Adding space for ' ' character between string chains.
}
va_end(argumentsList);
// Create espeak command line with speech:
commandLength = 10 + strlen(VOLUME) + 4 + strlen(PITCH) + 4 +
strlen(SPEED) + 4 + strlen(VOICE_FILE) + 5 + totalPhrasesLength + 1 + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "espeak -a ", commandLength);
strncat(command, VOLUME, commandLength);
strncat(command, " -p ", commandLength);
strncat(command, PITCH, commandLength);
strncat(command, " -s ", commandLength);
strncat(command, SPEED, commandLength);
strncat(command, " -w ", commandLength);
strncat(command, VOICE_FILE, commandLength);
strncat(command, " -z \"", commandLength);
for (i = 0; i < numPhrases; i++) {
strncat(command, phrases[i], commandLength);
strncat(command, " ", commandLength);
}
strncat(command, "\"", commandLength);
system(command);
// Play speech:
commandLength = 18 + strlen(VOICE_FILE) + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "audioscript aplay ", commandLength);
strncat(command, VOICE_FILE, commandLength);
system(command);
// Remove WAV file:
commandLength = 3 + strlen(VOICE_FILE) + 1;
command = malloc(commandLength); // Memory allocation.
strncpy(command, "rm ", commandLength);
strncat(command, VOICE_FILE, commandLength);
system(command);
}
void setALSA_Volume(long volumePercentage) {
// volumePercentage: The new volume value to be set (a number
between 0 and 100).
long min, max; // Obtained in dB 100 times from ALSA.
snd_mixer_t* handle;
snd_mixer_elem_t* elem;
snd_mixer_selem_id_t* sid;
// Check that volume
if (volumePercentage
volumePercentage
}
if (volumePercentage
volumePercentage
}
is inside bounds:
< 0) {
= 0;
> 100) {
= 100;
388
// Load ALSA driver:
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, CARD);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
// Load mixer:
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, MIXER);
elem = snd_mixer_find_selem(handle, sid);
// Set volume to ALSA:
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
volumePercentage = (long) (((max - min) / log10(101)) *
log10(volumePercentage + 1) + min);
snd_mixer_selem_set_playback_volume_all(elem, volumePercentage);
snd_mixer_close(handle);
}
StatusData.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
#ifndef STATUSDATA_H
#define STATUSDATA_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "FileManager.h"
// Constants:
#define BATTERIES_FILE_PATH "/tmp/idroid02/batteries.txt"
#define NO_DATA_STRING "-"
#define TEMPERATURES_FILE_PATH "/tmp/idroid02/temperatures.txt"
#define ULTRASONIC_FILE_PATH "/tmp/idroid02/ultrasonic.txt"
// Global variables:
// Struct containing data provided by Batteries data handler:
struct BATTERY_DATA_STRUCT {
char* bat; // I-Droid02 main battery.
char* batPercent; // I-Droid02 main battery % of discharge.
char* remoteBat; // 433 MHz native remote control battery.
char* remoteBatPercent; // 433 MHz native remote control % of
discharge.
} batteryData;
// Struct containing data provided by Temperatures data handler:
389
struct TEMPERATURE_DATA_STRUCT {
char* ambient; // I-Droid02 ambient temperature.
char* rpiTemp; // Raspberry Pi microprocessor internal
temperature.
char* rfm31bTemp; // 433 MHz native remote control RX temp.
char* rfm43bTemp; // 433 MHz native remote control TX temp.
} temperatureData;
char* ultrasonicSensorDistanceValue; // Ultrasonic sensor distance
value parsed from file.
// Functions:
void initStatusData(); // Function to init all data variables.
void* readBatteryData(void*); // Thread in charge of read
BATTERIES_FILE_PATH and parse their values.
void* readTemperatureData(void*); // Thread in charge of read
TEMPERATURES_FILE_PATH and parse their values.
void* readUltrasonicValue(void*); // Thread in charge of read
ULTRASONIC_FILE_PATH and parse its value.
#ifdef __cplusplus
}
#endif
#endif /* STATUSDATA_H */
StatusData.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darné MarÃ
*/
/* This file contains all functions and routines in charge of recollect
all
* status data provided by I-Droid02 Driver handlers.
*/
#include "StatusData.h"
void initStatusData() {
// Init battery data struct:
batteryData.bat = NO_DATA_STRING;
batteryData.batPercent = NO_DATA_STRING;
batteryData.remoteBat = NO_DATA_STRING;
batteryData.remoteBatPercent = NO_DATA_STRING;
// Init temperature data struct:
temperatureData.ambient = NO_DATA_STRING;
temperatureData.rpiTemp = NO_DATA_STRING;
temperatureData.rfm31bTemp = NO_DATA_STRING;
temperatureData.rfm43bTemp = NO_DATA_STRING;
// Init ultrasonic distance value:
ultrasonicSensorDistanceValue = NO_DATA_STRING;
}
void* readBatteryData(void* arg) {
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify instance and watcher associated to text
file.
390
while (1) {
// Open file and associate inotify instance:
textFD = openFile(BATTERIES_FILE_PATH, "r");
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, BATTERIES_FILE_PATH); //
Associates watcher.
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Data is up to date here.
// Read 1st line and parse value:
readLineFromFile(textFD, &batteryData.bat, 6);
// Read 2nd line and parse value:
readLineFromFile(textFD, &batteryData.batPercent, 6);
// Read 3rd line and parse value:
readLineFromFile(textFD, &batteryData.remoteBat, 6);
// Read 4th line and parse value:
readLineFromFile(textFD, &batteryData.remoteBatPercent, 6);
inotifyInstanceRemoveWatch(iFD, iWD); // Remove watcher.
close(iFD); // Close inotify instance.
closeFile(textFD); // Closes the file.
}
pthread_exit(EXIT_SUCCESS); // End thread.
}
void* readTemperatureData(void* arg) {
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify instance and watcher associated to text
file.
while (1) {
// Open file and associate inotify instance:
textFD = openFile(TEMPERATURES_FILE_PATH, "r");
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, TEMPERATURES_FILE_PATH); //
Associates watcher.
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Data is up to date here.
// Read 1st line and parse value:
readLineFromFile(textFD, &temperatureData.ambient, 5);
// Read 2nd line and parse value:
readLineFromFile(textFD, &temperatureData.rpiTemp, 5);
// Read 3rd line and parse value:
readLineFromFile(textFD, &temperatureData.rfm31bTemp, 5);
// Read 4th line and parse value:
readLineFromFile(textFD, &temperatureData.rfm43bTemp, 5);
inotifyInstanceRemoveWatch(iFD, iWD); // Remove watcher.
close(iFD); // Close inotify instance.
391
closeFile(textFD); // Closes the file.
}
pthread_exit(EXIT_SUCCESS); // End thread.
}
void* readUltrasonicValue(void* arg) {
FILE* textFD; // File Descriptor of batteries text file.
int iFD, iWD; // Inotify instance and watcher associated to text
file.
while (1) {
// Open file and associate inotify instance:
textFD = openFile(ULTRASONIC_FILE_PATH, "r");
iFD = inotifyInstance(); // Associates inotify.
iWD = inotifyInstanceAddWatch(iFD, ULTRASONIC_FILE_PATH); //
Associates watcher.
// Wait until a file modification is produced:
watchFileForChanges(iFD); // Blocking function.
// Data is up to date here.
// Read line and parse value:
readLineFromFile(textFD, &ultrasonicSensorDistanceValue, 6);
inotifyInstanceRemoveWatch(iFD, iWD); // Remove watcher.
close(iFD); // Close inotify instance.
closeFile(textFD); // Closes the file.
}
pthread_exit(EXIT_SUCCESS); // End thread.
}
392
Appendix 12. 433 MHz native remote control server source code
main.h source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darne Mari
*/
#ifndef MAIN_H
#define MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
// Includes:
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "FileManager.h"
#include "SocketManager.h"
// Constants:
#define ANALOG_PACKET 0x24
#define BUFFER_SIZE 35
#define COMMAND_ACK 'A'
#define COMMAND_CLOSE_CONNECTION 'C'
#define COMMAND_GET_MOTORS_DATA 'G'
#define COMMAND_MOVE_LIMITED_MOTOR 'L'
#define COMMAND_RECEIVE_DATA 'R'
#define COMMAND_SET_WHEELS_MOVEMENT 'M'
#define COMMAND_STOP_LIMITED_MOTOR 'S'
#define DIGITAL_PACKET 0x18
#define MOTORS_HANDLER_SOCKET_PATH "/tmp/idroid02/motors"
#define OWN_SOCKET_PATH "/tmp/idroid02/remoteServerEnd"
#define RFM31B_HANDLER_SOCKET_PATH "/tmp/idroid02/rfm31b"
// Global variables:
char endProgram; // If 1, the program terminates.
/* Flag extracted form Motors handler, to store current active
motors.
* Current motors in movement according to the flag: 0b00HiHaRLPT.
* If the corresponding bit is set, the motor is currently in
movement.
* Hi (hip), Ha (hand), R (right arm), L (left arm), P (head pan), T
(head tilt). */
char limitedMotorsON;
char LIMITED_MOTORS_MAX_POSITION[6]; // Array that contains maximum
position value for each limited motor (minimum value is always 1).
int motorsHandlerFD; // Motors handler socket File Descriptor.
struct MOVEMENT_STATUS {
char directionFlag;
char movementSpeed;
char turningSpeed;
} movementStatus; // Current I-Droid02 movement status extracted
from Motors handler.
393
// Functions:
void* checkForEndProgram(void*); // Thread in charge of handle a
server socket. When connects, the program ends.
int moveLimitedMotor(char, char); // Routine to start any limited
motor.
int processFlag(char); // Function to stop current running motors
and/or stop current movement.
int processPacket(char*); // Function to process the received packet
from RFM31B handler.
int setMovement(struct MOVEMENT_STATUS); // Routine to set a new
movement for I-Droid02.
int stopLimitedMotors(); // Routine to stop active limited motor.
#ifdef __cplusplus
}
#endif
#endif /* MAIN_H */
main.c source code:
/*
* This file is part of I-Droid02 Project
* Author: Marc Darne Mari
*/
/* Main file of Remote Server. All the program is developed here. This
program
* is in charge of decode received packets from remote control and
connect to
* the motors handler to execute received orders.
*/
#include "main.h"
int main(int argc, char** argv) {
pthread_t alive;
unsigned char sendBuffer[BUFFER_SIZE]; // Buffer used for sockets
sends.
unsigned char receiveBuffer[BUFFER_SIZE]; // Buffer used for sockets
receptions.
struct sockaddr_un motorsHandlerSetup;
unsigned char* packetBuffer; // Buffer to store received packet.
char packetLength; // Length of the received packet form RFM31B
handler.
int rfm31bHandlerFD; // RFM31B handler socket File Descriptor.
struct sockaddr_un rfm31bHandlerSetup;
// Init motors data:
limitedMotorsON = 0;
movementStatus.directionFlag = 0x00;
movementStatus.movementSpeed = 0;
movementStatus.turningSpeed = 0;
// Create a thread in charge of end this program:
pthread_create(&alive, NULL, checkForEndProgram, NULL);
// Init LIMITED_MOTORS_MAX_POSITION (maximum position for limited
motors):
394
LIMITED_MOTORS_MAX_POSITION[0]
LIMITED_MOTORS_MAX_POSITION[1]
LIMITED_MOTORS_MAX_POSITION[2]
LIMITED_MOTORS_MAX_POSITION[3]
LIMITED_MOTORS_MAX_POSITION[4]
LIMITED_MOTORS_MAX_POSITION[5]
=
=
=
=
=
=
2;
21;
31;
31;
2;
2;
// Open, setup and connect to RFM31B client socket:
rfm31bHandlerFD = openSocket();
rfm31bHandlerSetup = setupSocket(rfm31bHandlerFD,
RFM31B_HANDLER_SOCKET_PATH, 0);
connectSocket(rfm31bHandlerFD, rfm31bHandlerSetup);
// Send COMMAND_RECEIVE_DATA to receive a packet:
sendBuffer[0] = COMMAND_RECEIVE_DATA;
if (sendData(rfm31bHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through RFM31B socket\n");
return (EXIT_FAILURE);
}
// Start server:
endProgram = 0;
while (endProgram == 0) {
// Receive length of the packet:
if (receiveData(rfm31bHandlerFD, receiveBuffer, 1) < 0) {
printf("Error receiving data through RFM31B socket\n");
break;
} // Blocked here until a new packet (or no packet flag) is
received.
packetLength = receiveBuffer[0];
if (packetLength != 0) {
// Receive packet:
packetBuffer = malloc(packetLength);
if (receiveData(rfm31bHandlerFD, packetBuffer, packetLength)
< 0) {
printf("Error receiving data through RFM31B socket\n");
break;
}
// Open, setup and connect to Motors handler socket:
motorsHandlerFD = openSocket();
motorsHandlerSetup = setupSocket(motorsHandlerFD,
MOTORS_HANDLER_SOCKET_PATH, 0);
connectSocket(motorsHandlerFD, motorsHandlerSetup); //
Blocking function.
// Update motors data (only those relevant values):
sendBuffer[0] = COMMAND_GET_MOTORS_DATA;
if (sendData(motorsHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through Motors socket\n");
break;
}
if (receiveData(motorsHandlerFD, receiveBuffer, 32) < 0) {
printf("Error receiving data through Motors socket\n");
break;
}
limitedMotorsON = receiveBuffer[18];
movementStatus.directionFlag = receiveBuffer[19];
movementStatus.movementSpeed = receiveBuffer[20];
395
movementStatus.turningSpeed = receiveBuffer[21];
// Process received packet:
if (processPacket(packetBuffer) > 0) {
// An error has occurred while processing the received
packet.
break;
}
// Send close connection command and close socket:
sendBuffer[0] = COMMAND_CLOSE_CONNECTION;
if (sendData(motorsHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through Motors socket\n");
break;
}
closeSocket(motorsHandlerFD, NULL);
if (endProgram == 0) {
// Acknowledge received packet with COMMAND_ACK:
sendBuffer[0] = COMMAND_ACK;
if (sendData(rfm31bHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through RFM31B
socket\n");
break;
}
} else {
// Send COMMAND_RECEIVE_DATA to end command:
sendBuffer[0] = COMMAND_RECEIVE_DATA;
if (sendData(rfm31bHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through RFM31B
socket\n");
break;
}
}
} else {
// Flag that indicates that any new packet has been received
after receiving last one.
// Receive NO_PACKET_FLAG:
if (receiveData(rfm31bHandlerFD, receiveBuffer, 1) < 0) {
printf("Error receiving data through RFM31B socket\n");
break;
}
// Open, setup and connect to Motors handler socket:
motorsHandlerFD = openSocket();
motorsHandlerSetup = setupSocket(motorsHandlerFD,
MOTORS_HANDLER_SOCKET_PATH, 0);
connectSocket(motorsHandlerFD, motorsHandlerSetup); //
Blocking function.
// Stop current running motors:
if (processFlag(receiveBuffer[0]) > 0) {
// An error has occurred while processing the received
packet.
break;
}
// Send close connection command and close socket:
sendBuffer[0] = COMMAND_CLOSE_CONNECTION;
396
if (sendData(motorsHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through Motors socket\n");
}
closeSocket(motorsHandlerFD, NULL);
// If endProgram flag has been set before receive a packet,
acknowledge command is different:
if (endProgram == 0) {
// Acknowledge flag:
sendBuffer[0] = 0;
if (sendData(rfm31bHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through RFM31B
socket\n");
break;
}
} else {
// Send COMMAND_RECEIVE_DATA to end command:
sendBuffer[0] = COMMAND_RECEIVE_DATA;
if (sendData(rfm31bHandlerFD, sendBuffer, 1) < 0) {
printf("Error sending data through RFM31B
socket\n");
break;
}
}
}
}
// Disconnect from RFM31B handler (send Close connection command):
sendBuffer[0] = COMMAND_CLOSE_CONNECTION;
sendData(rfm31bHandlerFD, sendBuffer, 1);
// Close RFM31B socket:
closeSocket(rfm31bHandlerFD, NULL);
return (EXIT_FAILURE);
}
void* checkForEndProgram(void* arg) {
/* This thread consists on a UNIX socket handler. For the proper
normal
* operation of RemoteServer program, it maintains always captured
the RFM31B
* 433 MHz remote control receiver handler all the time, in order to
receive
* and decode received packet at any time. Although this handler has
been
* designed ad-hoc to be used for RemoteServer program, it has other
* functionalities that eventually could be used for any other
applications.
* For this reason, if an external application requests the use of
this
* handler it has the way to end RemoteServer program by simply
connecting
* to it using a UNIX socket pointing at OWN_SOCKET_PATH. The
application who
* has decided to close RemoteServer program is responsible to run
it again. */
int socketFD, socketFD2; // Socket File Descriptors.
struct sockaddr_un socketStruct; // Socket setup struct.
char* deleteCommand; // Command used to delete socket file if it
exists.
397
int deleteCommandLength;
// Remove socket file if exists:
if (access(OWN_SOCKET_PATH, F_OK) != -1) {
deleteCommandLength = 3 + strlen(OWN_SOCKET_PATH) + 1;
deleteCommand = malloc(deleteCommandLength);
strncpy(deleteCommand, "rm ", deleteCommandLength);
strncat(deleteCommand, OWN_SOCKET_PATH, deleteCommandLength);
system(deleteCommand);
}
// Open and setup server socket:
socketFD = openSocket();
socketStruct = setupSocket(socketFD, OWN_SOCKET_PATH, 1);
// Wait for a connection:
socketFD2 = acceptConnection(socketStruct, socketFD); // The thread
blocks here.
// An application has requested to endRemoteServer program.
// Close server socket:
closeSocket(socketFD2, NULL);
closeSocket(socketFD, OWN_SOCKET_PATH);
endProgram = 1; // Change flag to 1.
pthread_exit(EXIT_SUCCESS); // Stop thread.
}
int moveLimitedMotor(char motorNumber, char direction) {
// motorNumber: The limited motor ID (5-10).
// direction: The direction of movement (1: up/left/open, 0:
down/right/close).
// Returns: 0 if everything works or 1 otherwise.
unsigned char motorsBuffer[4];
// Send COMMAND_MOVE_LIMITED_MOTOR:
motorsBuffer[0] = COMMAND_MOVE_LIMITED_MOTOR;
motorsBuffer[1] = motorNumber;
if (direction == 1) {
motorsBuffer[2] = LIMITED_MOTORS_MAX_POSITION[motorNumber - 5];
} else {
motorsBuffer[2] = 1;
}
motorsBuffer[3] = 10;
if (sendData(motorsHandlerFD, motorsBuffer, 4) < 0) {
printf("Error sending data through Motors socket\n");
return 1;
}
return 0;
}
int processFlag(char packetID) {
// packetID: Indicates which is the kind of last received packet.
// Returns: 0 if any error occurs or 1 otherwise.
switch (packetID) {
case ANALOG_PACKET:
// Stop I-Droid02 movement:
398
movementStatus.directionFlag = 0x00;
movementStatus.movementSpeed = 0;
movementStatus.turningSpeed = 0;
if (setMovement(movementStatus) > 0) {
return 1;
}
break;
case DIGITAL_PACKET:
// Stop limited motors:
if (stopLimitedMotors() > 0) {
return 1;
}
break;
}
return 0;
}
int processPacket(char* packet) {
// packet: Received packet from RFM31B handler.
// Returns: 0 if any error occurs or 1 otherwise.
struct MOVEMENT_STATUS receivedMovementData;
int returnStatus;
// Determine which packet has been received:
returnStatus = 0;
switch (packet[0]) {
case ANALOG_PACKET:
receivedMovementData.directionFlag = packet[1];
receivedMovementData.movementSpeed = (packet[2] & 0xF0) >>
4;
receivedMovementData.turningSpeed = packet[2] & 0x0F;
// Send COMMAND_SET_WHEELS_MOVEMENT if any of the movement
values is different from current ones:
if ((receivedMovementData.directionFlag !=
movementStatus.directionFlag) || (receivedMovementData.movementSpeed !=
movementStatus.movementSpeed) || (receivedMovementData.turningSpeed !=
movementStatus.turningSpeed)) {
if (setMovement(receivedMovementData) > 0) {
returnStatus = 1;
}
}
break;
case DIGITAL_PACKET:
// Only activate those limited motors indicated in the
DIGITAL_PACKET and only if they are inactive.
// Head tilt up:
if (((packet[1] & 0x01) == 0x01) && ((limitedMotorsON &
0x01) == 0x00)) {
returnStatus = moveLimitedMotor(5, 1);
}
// Head tilt down:
if (((packet[1] & 0x02) == 0x02) && ((limitedMotorsON &
0x01) == 0x00)) {
returnStatus = moveLimitedMotor(5, 0);
}
// Head pan left:
399
if (((packet[1] & 0x04) == 0x04) && ((limitedMotorsON &
0x02) == 0x00)) {
returnStatus = moveLimitedMotor(6, 1);
}
// Head pan right:
if (((packet[1] & 0x08) == 0x08) && ((limitedMotorsON &
0x02) == 0x00)) {
returnStatus = moveLimitedMotor(6, 0);
}
// Left arm up;
if (((packet[2] & 0x01) == 0x01) && ((limitedMotorsON &
0x04) == 0x00)) {
returnStatus = moveLimitedMotor(7, 1);
}
// Left arm down:
if (((packet[2] & 0x02) == 0x02) && ((limitedMotorsON &
0x04) == 0x00)) {
returnStatus = moveLimitedMotor(7, 0);
}
// Right arm up:
if (((packet[2] & 0x04) == 0x04) && ((limitedMotorsON &
0x08) == 0x00)) {
returnStatus = moveLimitedMotor(8, 1);
}
// Right arm down:
if (((packet[2] & 0x08) == 0x08) && ((limitedMotorsON &
0x08) == 0x00)) {
returnStatus = moveLimitedMotor(8, 0);
}
// Open hand:
if (((packet[2] & 0x10) == 0x10) && ((limitedMotorsON &
0x10) == 0x00)) {
returnStatus = moveLimitedMotor(9, 1);
}
// Close hand:
if (((packet[2] & 0x20) == 0x20) && ((limitedMotorsON &
0x10) == 0x00)) {
returnStatus = moveLimitedMotor(9, 0);
}
// Hip up:
if (((packet[2] & 0x40) == 0x40) && ((limitedMotorsON &
0x20) == 0x00)) {
returnStatus = moveLimitedMotor(10, 1);
}
// Hip down:
if (((packet[2] & 0x80) == 0x80) && ((limitedMotorsON &
0x20) == 0x00)) {
returnStatus = moveLimitedMotor(10, 0);
}
break;
}
400
return returnStatus;
}
int setMovement(struct MOVEMENT_STATUS movement) {
// movement: I-Droid02 movement data to be set.
// Returns: 0 if any error occurs or a positive integer otherwise.
unsigned char motorsBuffer[6];
unsigned char intBuffer[sizeof (int)]; // Buffer used to parse
integer values.
int distanceAngle = -1; // Set distanceAngle to -1 means no
distance/angle limitation.
memcpy(intBuffer, &distanceAngle, sizeof (int)); // Parse integer
value.
// Send COMMAND_SET_WHEELS_MOVEMENT:
motorsBuffer[0] = COMMAND_SET_WHEELS_MOVEMENT;
motorsBuffer[1] = movement.directionFlag;
motorsBuffer[2] = movement.movementSpeed;
motorsBuffer[3] = movement.turningSpeed;
motorsBuffer[4] = intBuffer[0];
motorsBuffer[5] = intBuffer[1];
if (sendData(motorsHandlerFD, motorsBuffer, 6) < 0) {
printf("Error sending data through Motors socket\n");
return 1;
}
return 0;
}
int stopLimitedMotors() {
// Returns: 0 if everything works or 1 otherwise.
unsigned char motorsBuffer[2];
int i;
// Send COMMAND_STOP_LIMITED_MOTOR for each active motors:
for (i = 0; i < 6; i++) {
// This motor is active and must be stopped:
motorsBuffer[0] = COMMAND_STOP_LIMITED_MOTOR;
motorsBuffer[1] = 5 + i;
if (sendData(motorsHandlerFD, motorsBuffer, 2) < 0) {
printf("Error sending data through Motors socket\n");
return 1;
}
}
return 0;
}
FileManager.h source code: (See Appendix 9).
FileManager.c source code: (See Appendix 9).
SocketManager.h source code: (See Appendix 9).
SocketManager.c source code: (See Appendix 9).
401
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertising