Servo API
The Theory versus Practice
Number | Description |
---|---|
2 | Servo Futaba S3003 or equivalent |
Schematic for Servo: One Servo
#include <Servo.h>
Servo myservo;
void setup()
{
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
void loop()
{
// in theory 0 degrees
myservo.writeMicroseconds(0);
delay(2000);
// in theory 180 degrees
myservo.writeMicroseconds(2000);
delay(2000);
}
myservo.attach(9)
. The loop sends a pulse of 0 microseconds that in theory means 0 with myservo.writeMicroseconds(0)
. After two seconds, it sends a pulse of 2,000 microseconds (2ms) with myservo.writeMicroseconds(2000)
, which would in theory move the servo to 180 degrees.write()
method, whereby you can exactly define the desired angles. See Listing 4-2.#include <Servo.h>
Servo myservo;
void setup()
{
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
void loop()
{
// move to 0 degrees
myservo.write(0);
delay(2000);
// move to 180 degrees
myservo.write(180);
delay(2000);
}
write()
method?Servo.h
in the directory ...\arduino-1.5.3\hardware\arduino\x86\libraries\Servo
. Take a look at the definition of this file:#define
MIN_PULSE_WIDTH 544
// the shortest pulse sent to a servo
#define
MAX_PULSE_WIDTH 2400
// the longest pulse sent to a servo
write()
method, the servo library uses 544 microseconds (0.544ms) and when 180 degrees is specified, it uses 2400 microseconds (2.4ms). The servo used in this experiment actually works with these values, not the theoretical values.myservo.attach(9, 800, 2200); // attaches the servo on pin 9 to the servo object
MIN_PULSE_WIDTH
and MAX_PULSE_
WIDTH
(544 and 2400 microseconds, respectively).write()
method. If an angle bigger than 180 degrees is passed to this function, it will be considered microseconds and write()
will behave the same way as the writeMicroseconds()
method. For example, both methods do the same thing:// move to 180 degrees
myservo.writeMicroseconds(2000);
// the value is > 180, so it means microseconds
myservo.write(2000);
The Mistake with Intel Galileo and Servos
code/datasheets
of this chapter in the file named CY8C95x0A.pdf
or you can access it at
http://www.cypress.com/?rID=3354
.Angle | Pulse Needed | Duty Cycle Byte @ 188Hz [0-255] | Duty Cycle Byte @ 48Hz [0-255] |
---|---|---|---|
1 | 0.554311111 | 26 | 6 |
2 | 0.564622222 | 27 | 6 |
3 | 0.574933333 | 27 | 6 |
4 | 0.585244444 | 28 | 7 |
5 | 0.595555556 | 28 | 7 |
6 | 0.605866667 | 29 | 7 |
7 | 0.616177778 | 29 | 7 |
8 | 0.626488889 | 30 | 7 |
9999 | 0.636800000 | 30 | 7 |
10 | 0.647111111 | 31 | 7 |
11 | 0.657422222 | 31 | 7 |
12 | 0.667733333 | 32 | 8 |
code
of this chapter, there is a spreadsheet named frequence_versus_resolution.xls
that contains all angles from 0 to 180 and the resolution offered by CY8C9540A.What Is New in Servo API?
attach()
method and included two new methods in Intel Galileo—set48hz()
and set188hz()
. Keep in mind that the frequency control is not independent per servo and once it’s set for one servo, all the other servos must use the same frequency. Otherwise, the servos will not work properly.void Servo::set48hz( )
void Servo::set188hz( )
attach()
method to set 48Hz in the servo initialization to avoid servo burns.uint8_t Servo::attach(int16_t pin, bool force48hz = false)
force48hz
. If this argument is set to true
, the initial frequency in the servo will be 48Hz; otherwise the default of 188Hz is assumed.pin
, only specifies the PWM pin to be used to emit the pulse signals to the servo.uint8_t Servo::attach(int pin, int min, int max, bool force48hz = false)
force48hz
. If this argument is set to true
, the initial frequency in the servo will be 48Hz; otherwise, the default of 188Hz is assumed.pin
argument specifies the PWM pin to be used to emit the pulse signals to the servo. The and min
and max
arguments specify the minimum and maximum frequency in microseconds, respectively.Schematic for Servo: Two Servos
Testing the New Servo APIs
#include <Servo.h>
Servo myservo1;
Servo myservo2;
void setup()
{
myservo1.attach(9); // attaches the servo to pin 9 at 188 hz
myservo2.attach(3, true); // this must work in 48 hz. All servos will work at 48hz now
Serial.begin(115200);
}
void loop()
{
myservo1.write(180);
myservo2.write(0);
delay(2000);
myservo1.write(0);
myservo2.write(180);
delay(2000);
myservo1.set188hz();; // all servos will work at 188hz
}
Reviewing servo_set_freq.ino
setup()
function, two servos are added to the system, one in pin 9 at 188Hz (the default frequency). The second servo object is created specifying a servo on pin 3 at 48Hz.myservo2.attach(3, true)
method is called, both servos begin operating at 48Hz.loop()
function, the first servo moves 180 degrees and the second moves to 0 degrees. After two seconds, they invert the order to 0 and 180, respectively.myservo1.write(180);
myservo2.write(0);
delay(2000);
myservo1.write(0);
myservo2.write(180);
delay(2000);
loop()
, both servos are initially working in 48Hz. After two seconds, all the servos will be working at 188Hz because one of the servos selected this frequency with myservo1.set188hz()
. The rest of the loop()
interactions the servos will remain in 188Hz.Challenges with Servos
Serial, Serial1, and Serial2 Objects
mux
are set to redirect the ports used on Linux console to provide the Serial2 functionality.Testing the Serial, Serial1, and Serial2 Objects
Materials List
Number | Description |
---|---|
2 | Simple wires for hookup |
Schematic for the Serial Example
String inputString = ""; // a string to hold incoming data
void setup() {
// put your setup code here, to run once:
Serial.begin(115200); // Serial console debugger
Serial1.begin(115200); // PIN 0->RX and 1->TX
Serial2.begin(115200); // PIN 2->RX and 3->TX
}
void loop() {
Serial.println("Transmitting from Serial 1 to Serial 2");
Serial1.println("Intel"); // this will transmitt using PIN 1
inputString="";
// Serial2 will wait for something
while (Serial2.available()) {
// get the new byte:
char inChar = (char)Serial2.read(); // receiving by Serial2 on pin 2
// add it to the inputString:
inputString += inChar;
}
// Serial 2 receive the word "Intel"
// let's send the word "Galileo" back to Serial 1
Serial.print("Message received from Serial 1:");
Serial.println(inputString);
inputString = "";
// transmitting another word to Serial2
Serial.println("Transmitting from Serial 2 to Serial 1");
Serial2.println("Galileo"); // transmitting by Serial2 using pin 3
// Serial1 will wait for something
while (Serial1.available()) {
// get the new byte:
char inChar = (char)Serial1.read(); // receiving by Serial1 using pin 0
// add it to the inputString:
inputString += inChar;
}
Serial.print("Message received from Serial 2:");
Serial.println(inputString);
inputString = "";
delay(2000);
}
Reviewing all_serials.ino
setup()
function, all three objects are initiated with a 115200 baud rate.Serial.begin(115200); // Serial console debugger
Serial1.begin(115200); // PIN 0->RX and 1->TX
Serial2.begin(115200); // PIN 2->RX and 3->TX
loop()
method, the Serial
object is used only for debugging purposes. Serial1 object starts transmitting the word "Intel"
to Serial2 using the println()
method.Serial1.println("Intel"); // this will transmitt using PIN 1
available()
method. The available()
method will keep returning the number of bytes available in the buffer and the loop will remain until available()
returns zero bytes (when there are no more bytes to be read). The read()
method reads the byte and casts it to a char byte using the inChar
variable. Once the byte is read the number of bytes reported by the available()
method is decreased and the byte is accumulated in the string buffer called inputString
.// Serial2 will wait for something
while (Serial2.available()) {
// get the new byte:
char inChar = (char)Serial2.read(); // receiving by Serial2 on pin 2
// add it to the inputString:
inputString += inChar;
}
"Intel"
message from Serial1, the word "Galileo"
is sent back to Serial1. Again, the println()
method is used for this purpose.// transmitting another word to Serial 2
...
...
...
Serial2.println("Galileo"); // transmitting by Serial2 using pin 3
"Galileo"
, Serial1 must receive it. The code has the same logic as was used for Serial2, using the available()
and read()
methods.// Serial1 will wait for something
while (Serial1.available()) {
// get the new byte:
char inChar = (char)Serial1.read(); // receiving by Serial1 using pin 0
// add it to the inputString:
inputString += inChar;
}
println()
method is used because it deals with strings more easily. However, if you’re transmitting raw bytes or integers, you could use the write()
method instead.Transmitting from Serial 1 to Serial 2
Message received from Serial 1:Intel
Transmitting from Serial 2 to Serial 1
Message received from Serial 2:Galileo
Transmitting from Serial 1 to Serial 2
Message received from Serial 1:Intel
delay(2000)
command that’s used at the end of the loop()
function.Improving the I/O Speed
digitalWrite()
, digitalRead()
, and pinMode()
to manage the I/O header./*
* This program tests the digital port speed
* using regular Arduino functions
*
* Intel Galileo: the max speed is 230Hz
* Intel Galileo Gen 2: the speed is 470KHz to all pins except
* pin 7, which achieves 1.8KHz
*
*/
int pin = 2; // this is pin header 2
void setup() {
// put your setup code here, to run once:
pinMode(pin, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
int state = LOW;
while(1){
digitalWrite(pin, state);
state =!state; // if as HIGH it will be LOW and vice-versa
}
}
setup()
function sets pin 2 as OUTPUT.
In the loop()
function, the variable "state"
is used in an infinite loop. It switches between LOW (0) and HIGH (1) and the pin state changes according to this variable through digitalWrite()
.
pin
to other pins and see if the same frequency is achieved.-
When the first Intel Galileo was designed with the GPIO expander CY8C9540A, manufactured by Cypress, some limitations were introduced. All header ports with the exception of pins 2 and 3 were connected to the GPIO expander, which made the management of these pins flow only through I2C commands at 100KHz. This limited the port’s speed to 230Hz. Also, the resolution of 8 bits to set the PWM duty cycle on this GPIO expander significantly impacted the performance of the servo motors, as explained in the section entitled “What’s New in the Servo API” in this chapter.
-
As explained in the beginning of this chapter, Intel Galileo runs Linux OS. The Arduino API is present in the userspace context, where the tasks run in a lower priority compared to the kernel. This makes it more difficult to have precision not only in terms of GPIOs, but also in terms of timer and interruptions controls. Also, Intel Galileo does not create bridges between the processor and the microcontroller. It allows you to run more than one application (or sketch) in Arduino API instead of only one instance. Quark can provide an I/O port speed superior to Arduino Uno if the new API that is discussed later is used.
The New APIs for I/O
digitalWrite()
and digitalRead()
offer 230Hz only and they use sysfs
to manage the pins. They manage the pins through a Linux character driver that accesses the file system and consequently is very slow.Memory-Mapped and Port-Mapped I/O
Memory-Mapped I/O
Port-Mapped I/O
https://communities.intel.com/docs/DOC-21828
or access a copy of this datasheet from the code/datasheet
folder.The I/O Distribution
Pins | Intel Galileo Gen 2 | Intel Galileo |
---|---|---|
0 | South-cluster | Expander |
1 | South-cluster | Expander |
2 | South-cluster | South-cluster |
3 | South-cluster | South-cluster |
4 | North-cluster | Expander |
5 | North-Cluster | Expander |
6 | North-Cluster | Expander |
7 | Expander | Expander |
8 | Expander | Expander |
9 | South-cluster | Expander |
10 | South-cluster | Expander |
11 | North-cluster | Expander |
12 | South-cluster | Expander |
13 | North-cluster | Expander |
OUTPUT_FAST and INPUT_FAST
pinMode()
function as the first attempt to improve the read and writing performance of the pins by abolishing the access using the regular sysfs
. The next sections discusses these two modes and some metrics.OUTPUT_FAST - 470KHz
digitalWrite()
and digitalRead()
functions with a small trick when the pins are configured with pinMode()
.
pinMode()
is used to configure the pin direction using the OUTPUT
and INPUT
defines.OUTPUT
with OUTPUT_FAST
in the pinMode()
function changes the frequency achieved to 470KHz.running_at_470hz.ino
using the IDE.int pin = 2; // this is pin header 2
void setup() {
// put your setup code here, to run once:
pinMode(pin, OUTPUT_FAST);
}
void loop() {
// put your main code here, to run repeatedly:
int state = LOW;
while(1){
digitalWrite(pin, state);
state =!state; // if as HIGH it will be LOW and vice-versa
}
}
Reviewing the Code
OUTPUT_FAST
, when used with digitalWrite()
, works only with pins 2 and 3 on Intel Galileo and all pins on Intel Galileo Gen 2.Recall that pins 7 and 8 reach a maximum of 1.7KHz instead of 470KHz.INPUT_FAST
OUTPUT_FAST
replaces the OUTPUT
when the pin is configured with pinMode()
, INPUT_FAST
replaces INPUT
.digitalRead()
using INPUT
and INPUT_FAST
./*
This program is only a demonstration of INPUT_FAST
*/
#define MAX_COUNTER 200000
void setup() {
Serial.begin(115200);
pinMode(2, INPUT_FAST); // using the pin 2
pinMode(3, INPUT); // using the pin 3
delay(3000); // only to give you time to open the serial debugger terminal
Serial.print("Number of interactions under test:");
Serial.println(MAX_COUNTER);
unsigned long t0,t;
unsigned int counter = 0;
t0 = micros(); // number of microseconds since booted
for (counter = 0; counter < MAX_COUNTER; counter++)
{
digitalRead(2); // this is the fast reading !!!!
}
t=micros()-t0; // delta time
Serial.print("digitalRead() configured with INPUT_FAST took: ");
Serial.print(t);
Serial.println(" microseconds");
t0 = micros(); // resetting to new initial time
for (counter = 0; counter < MAX_COUNTER; counter++)
{
digitalRead(3); // this is the lazy reading !!!!
}
t=micros()-t0; // delta time
Serial.print("digitalRead() configured with INPUT took:");
Serial.print(t);
Serial.println(" microseconds");
}
void loop() {
}
Reviewing the Code
#define MAX_COUNTER 200000
setup()
function, pinMode()
sets pin 2 as INPUT_FAST
and pin 3 as INPUT
. This works for Intel Galileo and Intel Galileo Gen 2. A small delay of three seconds was introduced to give you a chance to open the IDE serial console.t0
variable is created and receives the number of microseconds passed since the board was booted.t0 = micros(); // number of microseconds since booted
for
loop calls the digitalRead()
macro, which reads the state of pin 2 during MAX_COUNTER
for the number of interactions (200000).t0 = micros(); // number microseconds since booted
for (counter = 0; counter < MAX_COUNTER; counter++)
{
digitalRead(2); // this is the fast reading !!!!
}
for
loop is finished, the t
variable evaluates how many microseconds the digitalRead()
took for pin 2:t=micros()-t0; // delta time
digitalRead()
for pin 3, which uses INPUT
mode.Number of interactions under test:200000
digitalRead() configured with INPUT_FAST took: 233937 microseconds
digitalRead() configured with INPUT took:233716 microseconds
Number of interactions under test:200000
digitalRead() configured with INPUT_FAST took: 231954 microseconds
digitalRead() configured with INPUT took:437786889 microseconds
INPUT
and only 231954 microseconds (0.23 seconds) to read 200.000 times pin 3 with INPUT_FAST
. Thus, INPUT_FAST
is 1,888 times faster than INPUT
when you are using Intel Galileo.INPUT_FAST
and INPUT
have the same performance because they share the same implementation. Therefore, if digitalRead()
is being used to read a port in Intel Galileo Gen 2, it doesn’t matter if INPUT
or INPUT_FAST
are used. They are both equally fast.The Fast I/O Macros
variant.h
because it determines the I/O mappings and which pins are connected directly to the Quark SoC.hardware/arduino/x86/variants/galileo_fab_g/variant.h
file and contains the following macros:#define
GPIO_FAST_IO0
GPIO_FAST_ID_QUARK_SC(0x08)
#define
GPIO_FAST_IO1
GPIO_FAST_ID_QUARK_SC(0x10)
#define
GPIO_FAST_IO2
GPIO_FAST_ID_QUARK_SC(0x20)
#define
GPIO_FAST_IO3
GPIO_FAST_ID_QUARK_SC(0x40)
#define
GPIO_FAST_IO4
GPIO_FAST_ID_QUARK_NC_RW(0x10)
#define
GPIO_FAST_IO5
GPIO_FAST_ID_QUARK_NC_CW(0x01)
#define
GPIO_FAST_IO6
GPIO_FAST_ID_QUARK_NC_CW(0x02)
#define
GPIO_FAST_IO9
GPIO_FAST_ID_QUARK_NC_RW(0x04)
#define
GPIO_FAST_IO10
GPIO_FAST_ID_QUARK_SC(0x04)
#define
GPIO_FAST_IO11
GPIO_FAST_ID_QUARK_NC_RW(0x08)
#define
GPIO_FAST_IO12
GPIO_FAST_ID_QUARK_SC(0x80)
#define
GPIO_FAST_IO13
GPIO_FAST_ID_QUARK_NC_RW(0x20)
hardware/arduino/x86/variants/galileo_fab_d/variant.h
file and contains the following macros:#define
GPIO_FAST_IO2
GPIO_FAST_ID_QUARK_SC(0x40)
#define
GPIO_FAST_IO3
GPIO_FAST_ID_QUARK_SC(0x80)
GPIO_FAST_IO4
available. The same problem will occur with Intel Galileo Gen 2 if you try to use the GPIO_FAST_IO7
or GPIO_FAST_IO8
macros because they are not defined.GPIO_FAST_ID_QUARK_SC,
where _SC
means south-cluster or through port-mapped registers with the macro GPGIO_FAST_ID_QUARK_NC_RW macro,
where _NC
means north-cluster.GPIO_FAST_IOx
macros (x
refers to the pin number) are used to create the I/O descriptors using 32 bits for each pin. A description is a sequence of bits in memory that describes the ports directions, the state bitmask, and the reading and writing offset of each pin. This explains why each pin contains its own descriptor.HIGH
or LOW
(1 and 0) respectively, and this bitmask is called mask
in the IDE code.hardware/arduino/x86/cores/arduino/fast_gpio_common.h
file, as shown the code in bold:// Macros to (de-)construct GPIO_FAST_* register descriptors
#define GPIO_FAST_TYPE_NONE 0x00
#define GPIO_FAST_TYPE_QUARK_SC 0x01
#define GPIO_FAST_TYPE_QUARK_NC 0x02
#define GPIO_FAST_ID(type, rd_reg, wr_reg, mask) \
(0UL | ((type) << 24) | ((rd_reg) << 16) | ((wr_reg) << 8) | (mask))
#define GPIO_FAST_ID_TYPE(id) (((id) >> 24) & 0xFF)
#define GPIO_FAST_ID_RD_REG(id) (((id) >> 16) & 0xFF)
#define GPIO_FAST_ID_WR_REG(id) (((id) >> 8) & 0xFF)
#define GPIO_FAST_ID_MASK(id) ((id) & 0xFF)
fastGpioDigitalWrite(GPIO_FAST_IOx, unsigned int value) - 652KHz to 684KHz
GPGIO_FAST_IOx
, is a fast I/O macro used to identify the pin of interest.value
, is the value of the pin that might be LOW
or HIGH
.pinMode()
function must receive the OUTPUT_FAST
configuration and only pins 2 and 3 work.pinMode()
function must receive the OUTPUT
or OUTPUT_FAST
configuration because, unlike Intel Galileo, they provide the same effect. And all pins work except for pins 7 and 8, which achieve 684KHz. Pin 13 reaches 657KHz because it’s also connected to a built-in LED and there is a small loss when triggering this LED./*
This program makes the I/O speed to achieve 684KHz.
If you are using Intel Galileo: Only pins 2 and 3 work
If you are using Intel Galileo Gen 2: ALL pins work
Note: if you are using Intel Galileo Gen 2 and change
this software to support pin 13, the frequency will be
close to 657KHz and not 687KHz.
*/
void setup() {
// put your setup code here, to run once:
unsigned int pin = 2; // this is pin header 2
pinMode(pin, OUTPUT_FAST);
}
void loop() {
// put your main code here, to run repeatedly:
int state = LOW;
while(1){
fastGpioDigitalWrite(GPIO_FAST_IO2, state);
state =!state; // if as HIGH it will be LOW and vice versa
}
}
Reviewing 684khz.ino
setup()
function, the pin
variable is created to represent the pin 2 and pinMode()
sets this pin to OUTPUT_FAST.
This works for Intel Galileo and Intel Galileo Gen 2.loop()
function, the state
variable is initiated with state LOW
which creates an infinity loop. In this infinity loop, the fastGpioDigitalWrite()
macro identifies the GPGIO_FAST_IO2
macro and the state
variable. Finally, the state
variable has its stated inverted on each loop interaction, switching between LOW
(0) and HIGH
(1).fastGpioDigitalWrite()
to explore more other pins.Frequency Reduction with fastGpioDigitalWrite()
fastGpioDigitalWrite()
is the reduction of performance when fastGpioDigitalWrite()
is called multiple times in a single loop cycle.fastGpioDigitalWrite()
is handling different pins; the maximum frequency is divided by the number of fastGpioDigitalWrite()
methods in one loop interaction.fastGpioDigitalWrite()
is called twice, the end frequency will be 340KHz./*
This program makes the I/O speed achieve 340KHz.
If you are using Intel Galileo: Only pins 2 and 3 work
If you are using Intel Galileo Gen 2: ALL pins work
*/
void setup() {
// put your setup code here, to run once:
pinMode(2, OUTPUT_FAST);
pinMode(3, OUTPUT_FAST);
}
void loop() {
// put your main code here, to run repeatedly:
int state = LOW;
while(1){
fastGpioDigitalWrite(GPIO_FAST_IO2, state);
fastGpioDigitalWrite(GPIO_FAST_IO3, state);
state =!state; // if as HIGH it will be LOW and vice versa
}
}
fastGpioDigitalRegWriteUnsafe()
. It’s explained later in this chapter.int fastGpioDigitalRead(GPIO_FAST_IOx)
digitalRead()
.GPGIO_FAST_IOx
parameter is a fast I/O macro used to identify the pin of interest.0
for LOW
or any other value representing HIGH
, because the value returned is the current value in the register mask according to the register offset.pinMode()
function must receive the INPUT_FAST
configuration and only pins 2 and 3 work.pinMode()
function must receive the INPUT
or INPUT_FAST
confuguration because, unlike Intel Galileo, they provide the same effect and all pins work except pins 7 and 8.digitalRead()
and fastGpioDigitalRead()
and detects when a state changes in a pin. If you are interested only in checking the performance, it is not necessary to assemble any circuit. In this case, you can just run the sketch. Otherwise, if you want to detect when the pin state is changed, it is necessary to assemble the same circuit and materials mentioned in the section entitled “The Button Example” in Chapter 3./*
This program is only a demonstration of fastGpioDigitalRead()
*/
int pin = 2;
#define MAX_COUNTER 200000
void setup() {
Serial.begin(115200);
pinMode(pin, INPUT_FAST); // using pin 2
delay(3000); // only to give you time to open the serial debugger terminal
Serial.print("Number of interactions under test:");
Serial.println(MAX_COUNTER);
unsigned long t0,t;
unsigned int counter = 0;
t0 = micros(); // number of microseconds since booted
for (counter = 0; counter < MAX_COUNTER; counter++)
{
if (fastGpioDigitalRead(GPIO_FAST_IO2)) // using the fast I/O macro related
// to pin 2
{
// the pin is HIGH
Serial.println("HIGH detected by fastGpioDigitalRead()");
}
}
t=micros()-t0; // delta time
Serial.print("fastGpioDigitalRead() took: ");
Serial.print(t);
Serial.println(" microseconds");
t0 = micros(); // reseting to new initial time
for (counter = 0; counter < MAX_COUNTER; counter++)
{
if (digitalRead(pin)) // using the fast I/O macro related
// to pin 2
{
// the pin is HIGH
Serial.println("HIGH detected by digitalRead()");
}
}
t=micros()-t0; // delta time
Serial.print("digitalRead() took: ");
Serial.print(t);
Serial.println(" microseconds");
}
void loop() {
}
Number of interactions under test:200000
fastGpioDigitalRead() took: 123207 microseconds
digitalRead() took: 239766 microseconds
fastGpioDigitalRead()
executed 200000 readings in 123302 microseconds, while digitalRead()
took 239766 microseconds for the same amount of cycles. In other words:239766/123207 = 1.94
fastGpioDigitalRead()
method was almost twice as fast as the regular digitalRead()
method using the INPUT_FAST
mode.HIGH
state is being detected by fastGpioDigitalRead()
and digitalRead()
in INPUT_FAST
mode.Reviewing fastGpioDigitalRead_example.ino
int pin = 2;
#define MAX_COUNTER 200000
setup()
function, pinMode()
sets pin 2 to INPUT_FAST
, which works for Intel Galileo and Intel Galileo Gen 2. A delay of three seconds was introduced only to give you a chance to open the IDE serial console.t0
is created and receives the number of microseconds passed since the board was booted.t0 = micros(); // number microseconds since booted
for
loop is created by calling the macro fastGpioDigitalRead()
, which reads the state of pin 2 and passes the fast I/O macro GPIO_FAST_IO2
as the parameter during MAX_COUNTER
number of interactions (200000).t0 = micros(); // number of microseconds since booted
for (counter = 0; counter < MAX_COUNTER; counter++)
{
if (fastGpioDigitalRead(GPIO_FAST_IO2)) // using the fast I/O macro related
// to pin 2
{
// the pin is HIGH
Serial.println("HIGH detected by fastGpioDigitalRead()");
}
}
HIGH
state) because the Serial object prints a message reporting the HIGH
state.for
loop is finished, the t
variable evaluates how many microseconds the fastGpioDigitalRead()
took and again, using the Serial object, prints the information to the serial console.t=micros()-t0; // delta time
digitalRead()
instead of fastGpioDigitalRead()
, allowing you to compare the performance between both functions when INPUT_FAST
mode is used.fastGpioDigitalRegSnapshot(GPIO_FAST_IOx)
GPGIO_FAST_IOx
. It’s a fast I/O macro used to identify the pin of interest./*
This program is only a demonstration of fastGpioDigitalRegSnapshot()
and the importance of the bitmask fields.
*/
void setup() {
unsigned int latchValue;
Serial.begin(115200);
delay(3000); // only to give you time to open the serial debugger terminal
// latches the current value
latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO3);
// identifies the bit corresponding to pin 3 in the bitmask
unsigned int mask = 0x000000ff
&
GPIO_FAST_IO3;
if (latchValue
&
mask)
{
// the register indicated the pin is HIGH
Serial.println("HIGH");
}
else
{
// the register indicated the pin is LOW
Serial.println("LOW");
}
}
void loop() {
}
Reviewing latch_example.ino
setup()
method starts with a delay of three seconds to give you a chance to open the IDE serial console with Tools ➤ Serial Monitor or with CTRL+SHIFT+M.fastGpioDigitalRegSnapshot()
is called to retrieve the I/O latch regarding pin 3, using the descriptor constructed by the macro GPIO_FAST_IO3
.latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO3);
mask
is created by retrieving only the bitmask used an AND
operation with 8 fewer significant bits than the description created for pin 3.unsigned int mask = 0x000000ff
&
GPIO_FAST_IO3;
latchValue
variable is compared to the mask
variable to determine whether pin 3 is doing another simple AND
operation.if (latchValue
&
mask)
LOW
because it is the initial state of the pins and the logic in this case is merely illustrative. This same logic is used in the next section with the macro fastGpioDigitalRegWriteUnsafe()
.fastGpioDigitalRegWriteUnsafe (GPIO_FAST_IOx, unsigned int value) - 2.94MHz
GPGIO_FAST_IOx
, is a fast I/O macro used to identify the pin of interest.value
, corresponds to the descriptors and all current pin states.pinMode()
function must receive the OUTPUT_FAST
configuration and only pins 2 and 3 work.pinMode()
function must receive the OUTPUT
or OUTPUT_FAST
configurations because, unlike Intel Galileo, they provide the same effect. All the pins work. Pins 7 and 8 can achieve 2.93MHz whereas pin 13 reaches 1.16MHz, because it’s also connected to a built-in LED and there is a small loss when triggering this LED.Unsafe
on its name because you must preserve the I/O descriptor and not mess with other I/Os masks when a particular I/O is being used. If you mess with the description and pin states’ bitmask, unexpected results will be observed in the pin headers./*
This program makes the I/O speed achieve 2.93MHz.
If you are using Intel Galileo: Only pins 2 and 3 work
If you are using Intel Galileo Gen 2: ALL pins work
except pins 7 and 8
Note: if you are using Intel Galileo Gen 2 and change
this software to support pin 13, the frequency will be
close to 1.16MHz.
*/
unsigned int latchValue;
unsigned int bmask;
void setup() {
// put your setup code here, to run once:
pinMode(2, OUTPUT_FAST);
// latches the current value
latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO2);
// extract the mask that identifies pin 2 in the
// descriptor
bmask = 0x000000ff
&
GPIO_FAST_IO2;
}
void loop() {
while(1)
{
if (latchValue
&
bmask)
{
// state is HIGH
latchValue = GPIO_FAST_IO2
&
!bmask;
}
else
{
// state is LOW
latchValue = GPIO_FAST_IO2 | bmask;
}
fastGpioDigitalRegWriteUnsafe (GPIO_FAST_IO2, latchValue);
}
}
Reviewing running_at_2_93Mh.ino
setup()
function configures pin 2 as OUTPUT_FAST
for Intel Galileo and Intel Galileo Gen 2. Then the variable latchValue
reads the latest I/O states according to the GPIO_FAST_IO2
descriptor using fastGpioDigitalRegSnapshot()
.
pinMode(2, OUTPUT_FAST);
// latches the current value
latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO2);
setup()
, the variable bmask
extracts only the I/O bitmask for pin 2, disregarding the rest of I/O descriptor. Remember this mask occupies only the 8 LSB among the total of 32 bits that compose the I/O descriptor, as explained in the section entitled “The Fast I/O Macros” in this chapter.bmask = 0x000000ff
&
GPIO_FAST_IO2;
loop()
function, an infinity while
is introduced and latchValue
is tested to check if the state is HIGH
.HIGH
, because the bit mask will have the bit 1
in the respective pin state; otherwise, the latchValue
state is LOW
. This operation can be checked with a simple AND
:if (latchValue
&
bmask)
HIGH,
it is necessary to change it to LOW
. For this, you just need to invert the bitmask bits and create an AND
operation again:latchValue = GPIO_FAST_IO2
&
!bmask;
LOW
, it is necessary to change it to HIGH. For this, you must add an OR
using the bitmask:latchValue = GPIO_FAST_IO2 | bmask;
fastGpioDigitalRegWriteUnsafe()
is called, informing the fast I/O descriptor for pin 2 (GPIO_FAST_IO2) and the current value of latchValue
to be changed.bmask
" was retrieved and used with the AND
and OR
operation, which will preserve the other bits in the I/O descriptor. With this type of logic, you can handle more than one bit safely.North-Cluster (1.12MHz) versus South-Cluster (2.93MHz)
pinMode()
must be modified and all GPIO_FAST_IO2
calls must be replaced with GPIO_FAST_IO4
. You could also just open the code running_at_1_12MHz.ino
, which contains these changes.Keeping the Same Frequency on All Pins
fastGpioDigitalRegWriteUnsafe()
. This provides a good alternative to fastGpioDigitalWrite()
, which cannot do this./*
This program makes the I/O speed to achieve 2.93MHz.
If you are using Intel Galileo: Only pins 2 and 3 work
If you are using Intel Galileo Gen 2: ALL pins work
except pins 7 and 8
*/
unsigned int latchValue;
unsigned int bmask_pin2;
unsigned int bmask_pin3;
unsigned int bmask_pin12;
void setup() {
// put your setup code here, to run once:
pinMode(2, OUTPUT_FAST);
pinMode(3, OUTPUT_FAST);
pinMode(12, OUTPUT_FAST);
// latches the current value
latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO2);
// retrieving bitmasks from descriptors
bmask_pin2 = 0x000000ff
&
GPIO_FAST_IO2; //south-cluster
bmask_pin3 = 0x000000ff
&
GPIO_FAST_IO3; //south-cluster
bmask_pin12 = 0x000000ff
&
GPIO_FAST_IO12; //south-cluster
}
void loop() {
while(1)
{
if (latchValue
&
bmask_pin12)
{
// state is HIGH
latchValue = GPIO_FAST_IO2
&
!bmask_pin2;
latchValue |= GPIO_FAST_IO3
&
!bmask_pin3;
latchValue |= GPIO_FAST_IO12
&
!bmask_pin12;
}
else
{
// state is LOW
latchValue = GPIO_FAST_IO2 | bmask_pin2;
latchValue |= GPIO_FAST_IO3 | bmask_pin3;
latchValue |= GPIO_FAST_IO12 | bmask_pin12;
}
// considering all pins in this example belong to the south-cluster
// they share the same register in memory-mapped I/O. Only one
// fastGpioDigitalRegWriteUnsafe() must be called ensuring 2.93MHz
// to ALL pins
fastGpioDigitalRegWriteUnsafe (GPIO_FAST_IO2, latchValue);
}
}
Reviewing running_at_2_93Mhz_three_pins.ino
setup()
function, all pins are set to OUTPUT_FAST
as expected. The bitmask for each descriptor is captured and the latchValue
latches the current GPGIO_FAST_IO2
state. The latchValue
will contain the state of all pins in the south-cluster because all the pins share the same register in the memory (memory-mapped I/O) and it does not matter if the fast macro I/O is used.loop()
function, the logic is the same. One of the pin states is checked and the latchValue
changes the state of each pin, inverting the original state.fastGpioDigitalRegWriteUnsafe()
is called. The explanation is quite simple—all pins belong to the same cluster and share the same memory registers, so only one call ensures 2.93MHz to all the pins used in the code (pins 2, 3, and 12).When Pins from North-Cluster and South-Cluster Are Used in Same Sketch
fastGpioDigitalRegWriteUnsafe()
twice in order to configure the I/O pins for both clusters./*
This program is an example of how to mix pins from the
north-cluster and south-cluster
If you are using Intel Galileo: Only pins 2 and 3 work
If you are using Intel Galileo Gen 2: ALL pins work
except pins 7 and 8
*/
unsigned int latchValue;
unsigned int bmask_pin2;
unsigned int bmask_pin3;
unsigned int bmask_pin4;
void setup() {
// put your setup code here, to run once:
pinMode(2, OUTPUT_FAST);
pinMode(3, OUTPUT_FAST);
pinMode(4, OUTPUT_FAST);
// latches the current value
latchValue = fastGpioDigitalRegSnapshot(GPIO_FAST_IO2);
// retrieving bitmasks from descriptors
bmask_pin2 = 0x000000ff & GPIO_FAST_IO2; //south-cluster
bmask_pin3 = 0x000000ff & GPIO_FAST_IO3; //south-cluster
bmask_pin4 = 0x000000ff & GPIO_FAST_IO4; //north-cluster !!!!
}
void loop() {
while(1)
{
if (latchValue & bmask_pin4)
{
// state is HIGH
latchValue = GPIO_FAST_IO2 & !bmask_pin2;
latchValue |= GPIO_FAST_IO3 & !bmask_pin3;
latchValue |= GPIO_FAST_IO4 & !bmask_pin4;
}
else
{
// state is LOW
latchValue = GPIO_FAST_IO2 | bmask_pin2;
latchValue |= GPIO_FAST_IO3 | bmask_pin3;
latchValue |= GPIO_FAST_IO4 | bmask_pin4;
}
// pins from cluster different used, so it is necessary
// to make a couple call using pins from south-cluster and north-cluster
fastGpioDigitalRegWriteUnsafe (GPIO_FAST_IO2, latchValue);
fastGpioDigitalRegWriteUnsafe (GPIO_FAST_IO4, latchValue);
}
}
Reviewing mixing_north_and_south_clusters.ino
fastGpioDigitalRegWriteUnsafe()
calls, one for each cluster.When Port Speed Is Not Enough - pinMode( ) Limitations
pinMode()
is used to change the pin direction that introduces a three-millisecond delay due to the need to send the I2C command to set the mux in the boards.The Tone API
http://arduino.cc/en/reference/tone
.OUTPUT_FAST
and INPUT_FAST
because the frequencies required are not achieved with such modes in other pins. There is no PWM involved.What’s New in the Tone API?
void tone(unsigned int pin, unsigned int frequency, unsigned long duration = 0)
pin,
specifies the pin to be used. Note that if you are using this pin as PWM, the PWM will be disabled automatically.0.
In this case, the tone will be generated continuously until the noTone()
function is called. Note when a duration is not specified, the continuous generation of this tone makes the tone()
function call non-blocking.tone()
called as a non-blocking call, multiple tones can be generated in multiples pins. However, the accuracy goes down as the number of tones increases because each tone called as non-blocking is implemented with POSIX threads called mutually exclusively.duration
parameter is specified, the function will generate the tone until the duration is attended; the function call in this case is blocking. In these cases, multiples tones cannot be generated; the accuracy is better when compared to non-blocking calls.void noTone(uint8_t pin)
A Sample Running Non-Blocking Tone Calls
Number | Description |
---|---|
2 | Resistor 100 Ohms |
2 | Speaker 8 Ohms |
// some tones in hertz
#define NOTE_C4 262
#define NOTE_G3 196
#define NOTE_A3 220
#define NOTE_B3 247
// melody on pin 0:
int
melody_pin0[]
= {
NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4};
// melody on pin 1:
int
melody_pin1[]
= {
NOTE_G3, NOTE_C4,NOTE_G3, NOTE_G3, NOTE_A3,0, NOTE_C4, NOTE_B3};
void setup() {
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < sizeof(melody_pin0)/sizeof(int); thisNote++) {
//the duration is not specified to make a non-blocking call.
tone(0, melody_pin0[thisNote]);
tone(1, melody_pin1[thisNote]);
// small delay
delay(500);
// stop the tone playing:
noTone(0);
noTone(1);
}
}
void loop() {
// no need to repeat the melody.
}
Reviewing Tone.ino
melody_pin0
and melody_pin1
arrays, which contain the sequence of tones to be played, are created for pins 0 and 1, respectively.setup()
function, a for
loop scans the tone arrays and plays the arrays on pin 0 and 1 using the tone()
function. This is done without defining a duration, which means the call is non-blocking.tone(0, melody_pin0[thisNote]);
tone(1, melody_pin1[thisNote]);
delay()
function. It’s used only to allow the perception of tones in the speakers. Finally the tones are stopped by the noTone()
function call.// stop the tone playing:
noTone(0);
noTone(1);
tone()
function in a blocking scenario, check out
http://arduino.cc/en/Tutorial/tone
and run the simple sketch.The pulseIn API
pulseIn()
API is used to measure a pulse length in some specified pin.unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000)
pin
specifies the pin to be used. Note if you are using this pin as PWM, the PWM will be disabled automatically.state
. If HIGH
is specified, pulseIn()
will start timing when the pin changes from LOW
to HIGH
, and stop timing when it moves to the LOW
state again. If the state parameter is LOW
, the inverse order will be used to measure the pulse from HIGH
to LOW
, and LOW
to HIGH
again.timeout
is in microseconds and is optional. It imposes the maximum timeout that pulseIn()
needs to wait for the pin state transition. If it is not specified, the default value is 1.000.000 microseconds (1 second).pulseIn()
measures the pulse length, the length in microseconds is returned. Otherwise, if some timeout occurred, the value returned is 0.duration = pulseIn(3, HIGH);
pulseIn()
method will wait for pin 3 to reach the HIGH
state and then start timing until the state becomes LOW
. The pulseIn()
function returns the pulse length in microseconds (duration).http://arduino.cc/en/Reference/pulseIn
. However, it is crucial to understand its limitations if your sketch intends to use this API.What’s New with pulseIn()
A Sample Running pulseIn()
pulseIn()
function will then read how long the pulse was present.pulseIn()
will measure how long the pulse is generated by the button and will remain in the HIGH state during this time.int pin = 2;
unsigned long duration;
void setup()
{
Serial.begin(115200);
pinMode(pin, INPUT);
}
void loop()
{
// measuring the pulse lengh during 20 seconds
duration = pulseIn(pin, HIGH,20000000);
Serial.println(duration);
}
0
if the pulse could not be read; otherwise, the reading is displayed in microseconds.Reviewing pulseIn.ino
pin
, which is used to indicate that pin 2 is used to receive the pulse event. Another variable duration is used to receive the metric returned by the pulseIn()
function.setup()
function, pinMode()
is used only to set the pin to the INPUT
mode. The Serial object is initiated for debugging purposes.loop()
function, pulseIn()
is set to read pin 2 and measure the transition from LOW
to HIGH
using 20 seconds as the timeout.HIGH
state; otherwise, it receives 0. The Serial object prints the results in microseconds.Hacks
analogWrite()
, or need a servo motor that works with pules out of regular Arduino reference range, or want to create a single sketch that works in both Intel Galileo and Intel Galileo Gen 2.Hacking the Servo Library
MIN_PULSE_WIDTH
) and 2400 (MAX_PULSE_WIDTH
) microseconds....\arduino-1.5.3\hardware\arduino\x86\libraries\Servo.h
file:#define
MIN_PULSE_WIDTH 500
// the shortest pulse sent to a servo
#define
MAX_PULSE_WIDTH 2600
// the longest pulse sent to a servo
Hacking the GPIO Expander for New PWM Frequencies
code/datasheets
folder of this chapter in the file named CY8C95x0A.pdf
. You can also access it at
http://www.cypress.com/?rID=3354
.Changing the Frequency
Freq = Clock Source/Divider
Selecting the Clock Source
Selecting the Duty Cycle
Duty cyle = Pulse Width/Period
Setting the Period Register
0
for falling pulse edge and 255 (0xff) for rising pulse edge.Changing the PWM
122.533 hz = 8.16ms
Minimum granularity is: 8.16 / 255 = 32 microseconds
1
in the 0x2b register. Figure 4-7 shows this logic graphically.
2
; if the duty cycle desired is 94 microseconds, the value received is 3
, and so on.beginTransmission()
, write()
, and endTransmisstion()
.
Wire.beginTransmission(DEVICE_ADRESS_HERE);
Wire.write(BYTE1);
Wire.write(BYTE2);
Wire.write(BYTE3);
...
...
Wire.endTransmission();
#include "Wire.h"
int PIN = 9;
void setup()
{
// Set divider to get 122.533Hz freq.
Wire.beginTransmission(0x20);
Wire.write(0x2C);
Wire.write(0x03);
Wire.endTransmission();
// Select PWM source clock
Wire.beginTransmission(0x20);
Wire.write(0x29);
Wire.write(0x04);
Wire.endTransmission();
// Set period register
Wire.beginTransmission(0x20);
Wire.write(0x2a);
Wire.write(0xff);
Wire.endTransmission();
// Duty cycle of 32us @ 122.533hz
Wire.beginTransmission(0x20);
Wire.write(0x2b);
Wire.write(0x01); // 1 is the minimum granularity
Wire.endTransmission();
}
void loop()
{
}
Single Code for Intel Galileo and Intel Galileo Gen 2
PLATFORM_ID
representing the value 0x06.#if
PLATFORM_ID == 0x06
// this is Intel Galileo
#else
// this is Intel Galileo Gen 2
#endif
PLATFORM_NAME
directive, which is represented by the string "GalileoGen2"
for Intel Galileo Gen 2 and "Galileo"
for Intel Galileo. For example, the following code snippet shows how to check the board during run-time:if(PLATFORM_NAME == "GalileoGen2")
{
// this is Intel Galileo Gen 2
...
...
...
} else
if (PLATFORM_NAME == "Galileo")
{
// this is Intel Galileo
...
...
...
}
Project: DHT Sensor Library with Fast I/O APIs
pinMode()
when the pin direction changes.Materials List
Number | Description |
---|---|
1 | DHT11 sensor |
1 | 1/4W 5K Ohm resistor |
1 | Low enable tri-state buffer NTE74HC125 or equivalent |
The DHT Sensor
0
or 1
. The start bit is always identified by a pull down voltage of 50 microseconds. When the bit is a pull up voltage that remains between 26 to 28 microseconds, the bit value is 0
. If the voltage remains at 70 microseconds, the bit value is 1
. It is expected to receive 40 bits that will be used to fill five bytes (5 bytes = 8 bits * 5 = 40 bits).-
Byte 0 -> Contains the humidity
-
Byte 1 -> Always 0
-
Byte 2 -> Contains the temperature
-
Byte 3 -> Always 0
-
Byte 4 -> The checksum that must match the sum of other four bytes
pinMode()
with Intel Galileo because it takes around 3 milliseconds (see the section entitled “When Port Speed Is Not Enough - pinMode() Limitations” in this chapter).pinMode()
is still processing the change of pin direction. Pay attention to the numbered arrows. The number 1 arrow points to the oscilloscope channel that’s set to 5ms per time division. The number 2 arrow shows how pinMode()
takes almost 3ms (less than one division of 5ms) and applies a DC level in the port.
A Workaround Using Tri-State Buffers
pinMode()
does not have enough performance for this sensor; however, it is possible to create a workaround and separate commands from responses using two different pins even if the sensor uses a single wire to communicate.G
is used to represent the gate, Y
is the output, and A
is the input. The numbers in front of each letter represent the gate number. You need only one gate for this project.
INPUT | GATE | OUTPUT |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | Z (High impedance) |
1 | 1 | Z (High impedance) |
pinMode()
issue is very simple. As soon as the command is sent, the gate will be opened, which will isolate the input pin. The current will flow exclusively to the output pin, which will receives the response.Creating a New Library for DHT11 Sensor
The Problem with Public DHT Libraries
libraries
directory of the IDE and run a sketch example.DHT.cpp
file, it is possible to see the following:// ACKNOWLEDGE or TIMEOUT
unsigned int loopCnt = 10000;
while(digitalRead(pin) == LOW)
if (loopCnt-- == 0)
return DHTLIB_ERROR_TIMEOUT;
loopCnt = 10000;
while(digitalRead(pin) == HIGH)
if (loopCnt-- == 0)
return DHTLIB_ERROR_TIMEOUT;
pinMode()
to switch the pin direction, as explained.Designing a New DHT Library
code/DHT11_Galileo
, which must be installed in your IDE. The process is simple:
DHT11_Galileo
within the directory called arduino-1.5.3/libraries
of your IDE installation.DHT11_Galileo
is listed. If it is, open the sketch called DHT_4_Galileo.ino
in the examples
folder of the library. You can also select Files➤Examples➤ DHT11_Galileo➤ DHT11_4_Galileo using the menu. If the library is not present, check if the library is properly installed as recommended. You can also learn more about the libraries installed at
http://arduino.cc/en/Guide/Libraries
.
#ifndef DHT_4_GALILEO
#define DHT_4_GALILEO
#include <Arduino.h>
#include <stdint.h>
class DHT_4_Galileo
{
public:
// to report ACK status
static const int DHT_ACK_OK = 0;
static const int DHT_ACK_ERROR = -1;
// to report reading status
static const int DHT_READ_SUCCESS = 0;
static const int DHT_READ_TIMEOUT_ERROR = -1;
static const int DHT_READ_CHECKSUM_ERROR = -2;
int humidity;
int temperature;
/*
* The class constructor must receive the pins
* used as gate/command and the pin to read
* the data from the sensor
*/
DHT_4_Galileo(uint8_t p_gate, uint8_t p_read);
/*
* sendCommand():
* sends the commands and wait for the acknowledgement from the sensor
*
*/
int sendCommand(); // end of send_command()
/*
* read():
* Reads the 40 bits of data, parse the data in
* temperature and humidity
*/
int read ();
private:
int pin_gate;
int pin_read;
/*
* getFastIOMacro(int pin)
* only an utility function to return
* the right macro according to the pin
*/
static int getFastIOMacro(int pin);
}; // end of class
#endif
DHT_4_Galileo
does with the sendCommand()
and read()
methods.p_gate
is the pin that is connected to the gate of one of the tri-state gates (G) of 74HC125 and p_read
is for the pin that will receive the data (Y).// to report ACK status
static const int DHT_ACK_OK = 0;
static const int DHT_ACK_ERROR = -1;
// to report reading status
static const int DHT_READ_SUCCESS = 0;
static const int DHT_READ_TIMEOUT_ERROR = -1;
static const int DHT_READ_CHECKSUM_ERROR = -2;
DHT_4_Galileo
implementation.#include "DHT_4_Galileo.h"
#define DEBUG 1 // change to 0 if you do not want debug messages
/*
* The class constructor must receive the pins
* used as gate/command and the pin to read
* the data from the sensor
*/
DHT_4_Galileo::DHT_4_Galileo(uint8_t p_gate, uint8_t p_read)
{
pin_gate = p_gate;
pin_read = p_read;
pinMode(pin_gate, OUTPUT_FAST);
pinMode(pin_read, INPUT_FAST);
};
/*
* sendCommand():
* sends the commands and wait for the acknowledgement from the sensor
*
*/
int DHT_4_Galileo::sendCommand()
{
// pull down during 18 microseconds.. this is our command!
fastGpioDigitalWrite(getFastIOMacro(pin_gate), LOW);
delay(18);
fastGpioDigitalWrite(getFastIOMacro(pin_gate), HIGH); // High impedance
delayMicroseconds(40);
// now let's check if some ACK was received
unsigned long t0,ti;
int state = fastGpioDigitalRead(getFastIOMacro(pin_read));
int new_state = state;
boolean ack = false;
t0 = micros(); // number microseconds since booted
while (micros()-t0<80)
{
new_state = fastGpioDigitalRead(getFastIOMacro(pin_read));
if (new_state==0)
{
// cool!!! first ACK received during 80 microseconds
ack = true;
break;
}
}
if (!ack)
{
#if (DEBUG)
Serial.println("problem in FIRST PART OF ACK");
#endif
return DHT_ACK_ERROR;
}
ack=false;
t0 = micros(); // number microseconds since booted
while (micros()-t0 < 80)
{
// waiting for HIGH
new_state = fastGpioDigitalRead(getFastIOMacro(pin_read));
if (new_state!=0)
{
// the second ACK received!!! let's wait for the data!!!
ack = true;
break;
}
}
if (!ack)
{
#if (DEBUG)
Serial.println("problem in SECOND PART ACK");
#endif
return DHT_ACK_ERROR;
}
return DHT_ACK_OK;
} // end of send_command()
/*
* read():
* Reads the 40 bits of data, parse the data in
* temperature and humidity
*/
int DHT_4_Galileo::read () {
unsigned long t0;
// BUFFER TO RECEIVE
uint8_t bits[5];
uint8_t cnt = 7;
uint8_t idx = 0;
int start_bit[40];
int reading_bit[40];
// cleaning arrays
for (int i=0; i<40; i++)
{
start_bit[i] = 0;
reading_bit[i] = 0;
}
for (int i=0; i<5; i++)
{
bits[i] = 0;
}
// READ OUTPUT - 40 BITS => 5 BYTES or TIMEOUT
for (int i=0; i<40; i++)
{
//start bit
// will stay low for 50us
t0 = micros(); // number microseconds since booted
while(fastGpioDigitalRead(getFastIOMacro(pin_read)) == 0)
{
// using 70 instead 50 us due to the remaining
// state of last ack
if ((micros() - t0) > 70) {
return DHT_READ_TIMEOUT_ERROR;
}
}
start_bit[i] = micros() -t0;
t0 = micros(); // number microseconds since booted
//reading bit
// 26 to 28us -> 0
// up tp 70 us -> 1
//int c = 0;
while( fastGpioDigitalRead(getFastIOMacro(pin_read)) != 0) {
if ((micros() - t0) > 77) {
return DHT_READ_TIMEOUT_ERROR;
}
};
unsigned long delta_time = micros() - t0;
reading_bit[i] = delta_time;
if (delta_time > 50) bits[idx] |= (1 << cnt);
if (cnt == 0) // next byte?
{
cnt = 7; // restart at MSB
idx++; // next byte!
}
else cnt--;
}
// dump
#if (DEBUG)
Serial.println();
for (int i=0; i<40; i++)
{
Serial.print(i);
Serial.print(" ");
Serial.print(start_bit[i]);
Serial.print(" ");
Serial.print(reading_bit[i]);
Serial.print(" ");
if (reading_bit[i] > 40)
Serial.println("1");
else
Serial.println("0");
}
Serial.println();
Serial.println("BYTES PARSED:");
Serial.println("------------");
for (int i=0; i<5; i++)
{
Serial.print(i);
Serial.print(": ");
Serial.println(bits[i]);
}
Serial.println();
#endif
// parsing the bits
humidity = bits[0];
temperature = bits[2];
uint8_t sum = (bits[0] + bits[1] + bits[2] + bits[3])
&
0xff;
if (bits[4] != sum)
{
return DHT_READ_CHECKSUM_ERROR;
} else {
return DHT_READ_SUCCESS;
}
}
/*
* getFastIOMacro(int pin)
* only an utility function to return
* the right macro according to the pin
*/
int DHT_4_Galileo::getFastIOMacro(int pin)
{
int macro;
switch (pin)
{
#if PLATFORM_ID != 0x06
// this is Galileo Gen 2
case 0: macro = GPIO_FAST_IO0;
break;
case 1: macro = GPIO_FAST_IO1;
break;
#endif
case 2: macro = GPIO_FAST_IO2;
break;
case 3: macro = GPIO_FAST_IO3;
break;
#if PLATFORM_ID != 0x06
// this is Galileo Gen 2 - no fast I/O for pins 7 and 8
case 4: macro = GPIO_FAST_IO4;
break;
case 5: macro = GPIO_FAST_IO5;
break;
case 6: macro = GPIO_FAST_IO6;
break;
case 9: macro = GPIO_FAST_IO9;
break;
case 10: macro = GPIO_FAST_IO10;
break;
case 11: macro = GPIO_FAST_IO11;
break;
case 12: macro = GPIO_FAST_IO12;
break;
case 13: macro = GPIO_FAST_IO13;
break;
#endif
default:
macro = 0;
break;
}
return macro;
} // end of getFastIOMacro()
Reviewing the DHT_4_Galileo.cpp Library
INPUT_FAST
and OUTPUT_FAST
through the pinMode()
function. It defines the possibility for using the new fast I/O functions.sendCommand()
.
It pulls the pin connected to the tri-state gate up and down during the 18 milliseconds, which immediately keeps the acknowledgement waiting. The sensors pull the voltage levels down and up during the 80 microseconds on each state. If the acknowledgement is not received, sendCommand()
reports this by returning DHT_ACK_ERROR
; otherwise, DHT_ACK_OK
is returned. Note that this method uses fastGpioDigitalWrite()
and fastGpioDigitalRead()
fast I/Os. The static function getFastIOMacro()
is used only to convert the regular pin to the specific fast I/O macros.read()
method tries to read the 40 bits transmitted by the sensors containing the data to be parsed. The fast I/O fastGpioDigitalRead()
function reads the start and data bits. In the first code sequence, the code waits for the start bit:t0 = micros(); // number microseconds since booted
while(fastGpioDigitalRead(getFastIOMacro(pin_read)) == 0)
{
// using 70 instead 50 us due to the remaining
// state of last ack
if ((micros() - t0) > 70) {
return DHT_READ_TIMEOUT_ERROR;
}
}
start_bit[i] = micros() -t0;
t0 = micros(); // number microseconds since booted
//reading bit
// 26 to 28us -> 0
// up tp 70 us -> 1
while( fastGpioDigitalRead(getFastIOMacro(pin_read)) != 0) {
if ((micros() - t0) > 77) {
return DHT_READ_TIMEOUT_ERROR;
}
};
unsigned long delta_time = micros() - t0;
reading_bit[i] = delta_time;
reading_bit[]
array saves how long each data bit took in microseconds. Thus to decide if the bit received was a 0 or a 1, you use this simple code:if (delta_time > 50) bits[idx] |= (1 << cnt);
if (cnt == 0) // next byte?
{
cnt = 7; // restart at MSB
idx++; // next byte!
}
else cnt--;
bits[]
array is responsible for saving the five bytes expected from the sensor after parsing the 40 bits received. The code checks if the time that the bit was received is bigger than 50us. If it is, it’s considered bit 1; otherwise, it is 0 and each bit is shifted properly until eight bits are received. When the total of eight bits are received, the index in the array is incremented and the logic continues by saving the next eight bits in the next element of bits[]
array.bits[]
array properly filled, the humidity
and temperature
classes members are loaded and the checksum is checked to make sure the data received is correct:// parsing the bits
humidity = bits[0];
temperature = bits[2];
uint8_t sum = (bits[0] + bits[1] + bits[2] + bits[3])
&
0xff;
if (bits[4] != sum)
{
return DHT_READ_CHECKSUM_ERROR;
} else {
return DHT_READ_SUCCESS;
}
Creating the Sketch for DHT Sensor
DHT_4_Galileo
class.#include <DHT_4_Galileo.h>
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("start......");
delay(3000);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println("send command..");
Serial.println();
DHT_4_Galileo dht(3, 2);
dht.sendCommand();
int response = dht.read();
if (response == DHT_4_Galileo::DHT_READ_SUCCESS)
{
Serial.println("RESULTS:");
Serial.println("-----------------");
Serial.print("Humidity:");
Serial.print(dht.humidity,1);
Serial.print(",\t");
Serial.print("Temperature (C):");
Serial.println(dht.temperature,1);
}
else
{
Serial.print("there is an error:");
Serial.println(response);
}
delay(5000);
}
loop(),
a dht
object is created using pin 3 as the gate and pin 2 as the data. The methods sendCommand()
and read()
are used.Running the Code
0 2 19 0
1 53 23 0
2 54 70 1
3 53 25 0
4 53 22 0
5 54 69 1
6 54 23 0
7 53 69 1
8 54 22 0
9 54 23 0
10 53 24 0
11 53 23 0
12 54 23 0
13 54 23 0
14 53 24 0
15 53 25 0
16 53 23 0
17 54 23 0
18 54 23 0
19 53 71 1
20 53 70 1
21 54 23 0
22 54 23 0
23 53 24 0
24 53 23 0
25 54 23 0
26 54 23 0
27 53 24 0
28 53 23 0
29 54 22 0
30 54 22 0
31 53 26 0
32 53 24 0
33 53 23 0
34 54 70 1
35 53 71 1
36 53 70 1
37 54 70 1
38 54 23 0
39 53 67 1
BYTES PARSED:
------------
0: 37
1: 0
2: 24
3: 0
4: 61
RESULTS:
-----------------
Humidity:37, Temperature (C):24