TECHNOLOGY IN ACTION™
Experimenting
with Raspberry Pi
LOW-COST PROJECTS TO HELP YOU
GENERATE IDEAS, FROM MASTERING
THE RASPBERRY PI
Warren Gay
For your convenience Apress has placed some of the front
matter material after the index. Please use the Bookmarks
and Contents at a Glance links to access them.
Contents at a Glance
About the Author���������������������������������������������������������������������������� xiii
About the Technical Reviewer��������������������������������������������������������� xv
Acknowledgments������������������������������������������������������������������������� xvii
Introduction������������������������������������������������������������������������������������ xix
■■Chapter 1: DHT11 Sensor���������������������������������������������������������������� 1
■■Chapter 2: MCP23017 GPIO Extender������������������������������������������� 15
■■Chapter 3: Nunchuk-Mouse���������������������������������������������������������� 47
■■Chapter 4: Real-Time Clock���������������������������������������������������������� 77
■■Chapter 5: VS1838B IR Receiver�������������������������������������������������� 99
■■Chapter 6: Stepper Motor����������������������������������������������������������� 119
■■Chapter 7: 76 The H-Bridge Driver��������������������������������������������� 139
■■Chapter 8: Remote-Control Panel����������������������������������������������� 159
■■Chapter 9: Pulse-Width Modulation�������������������������������������������� 183
■■Appendix A: Glossary����������������������������������������������������������������� 205
■■Appendix B: Power Standards���������������������������������������������������� 211
■■Appendix C: Electronics Reference��������������������������������������������� 213
■■Appendix D: ARM Compile Options��������������������������������������������� 215
■■Appendix E: Mac OS X Tips��������������������������������������������������������� 217
Index���������������������������������������������������������������������������������������������� 219
v
Introduction
These are exciting times for the computing enthusiast. AVR and PIC microcontrollers
make low-level digital computing readily accessible. At the high-level there exist System
on a Chip (SoC) platforms, such as the Raspberry Pi. These are capable of supporting
complex applications at affordable prices.
New challengers to the Raspberry Pi regularly appear now, yet the Pi remains
popular. This is because of the Raspberry Pi Foundation’s excellent support and the unit’s
continuing dominance in price. Both are critical to success. Foundation support provides
continued Raspbian Linux development, making it easier for people to get started and
use the platform. The foundation also continues to provide documentation and to
develop Pi specific peripherals such as the camera. Finally, low cost allows more people
to participate and at lower risk, should an experiment go bad.
Content of This Book
This book was formed from a category of chapters in the full volume Mastering the
Raspberry Pi. The focus in this particular book is experiments in Raspberry Pi interfacing
to the outside world. Every chapter involves some aspect of interfacing GPIO, PWM, I2C
bus, or SPI bus to some external electronics.
More than the electronic interface design is covered, however, since every interface
requires software to drive it. In some cases, applications will utilize Raspbian Linux
drivers to control the peripheral (such as the I2C bus). In other experiments, the
application software must control the GPIO pins directly. In every case, simplified
C programming is used as a place to start. The reader is encouraged therefore to apply
these programs as “idea generators.” Jump in and modify the programs to adapt to your
own ideas. Software is infinitely malleable.
Approach Used
The focus of this text is on learning. You would not be well served if you were presented
some kind of “end product” to be plugged in and simply used. Instead, you are
encouraged to learn to design interfaces to the Pi for yourself—to build from scratch or to
modify existing designs. This book will give you some practical examples to work through.
Experience is the best teacher.
xix
■ Introduction
While this is not an electronics engineering text, a light engineering approach is
applied. For example, the difference between the signal levels of the Pi versus the levels
required by an interfaced IC is scrutinized for some experiments. These parameters are
taken from the IC’s datasheet. This design work is to counter the glib “seems to work”
approach often given in web blogs. It is better to know that it will work and that it will
always work. Getting it right is not difficult when a little care and understanding goes into
the process.
Assumptions About the Reader
Since the experiments in this book involve attaching things to the Raspberry Pi’s GPIO
pins, some digital electronics knowledge is assumed. The reader should have a good grasp
of DC voltage, current, and resistance at a minimum. Students who know Ohm’s law
will fare best in these experiments. For students who have not yet committed Ohm’s law to
memory, Appendix C serves a quick reference.
The Raspberry Pi uses 3.3 V digital logic. This creates a special problem when
interfacing to older TTL logic, which operates at the 5 V level. The experiment in Chapter 4
Real Time Clock, for example, demonstrates how to interface safely to a 5 V device, after
making some modifications to a purchased pcb. These experiments require extra care to
avoid damaging the Pi.
Experiments involving the I2C bus require the reader to be familiar with the concept
of open collector drivers. Without this understanding, the student will not appreciate why
a 3.3 V Pi can interface to a 5 V real-time clock chip, using the I2C bus. This concept is also
critical to understanding why several peripherals can share that same bus.
Hardware for the experiments assumes a student budget. The parts and assembled
pcbs used in this book were purchased from eBay, usually as buy-it-now auctions
(with free shipping). For this reason, the student need not have deep pockets to acquire
the parts used in these experiments.
Since hardware needs software to direct it, C programs are used and provided.
Consequently, it is best that you have at least a vague idea about the C programming
language to get the most out of the experiments. The example programs are simplified as
much as they could be without sacrificing function. This keeps the software accessible to
the reader and eases the learning process.
Pi Hardware Assumed
All of the experiments in this book interface directly to the Raspberry Pi. No special
Gertboard or other special product is used. For this “bare-metal approach,” all you need
is a Raspberry Pi and the involved experiment’s hardware.
For my own experiments, I constructed a home-brewed setup where I placed the Pi
on a block of wood and ran wires out to some retro Fahnestock clips. While this worked
quite well, building this setup required considerable effort. I would recommend that
students get something easier like the Adafruit Pi Cobbler.
xx
■ Introduction
A simple homemade Raspberry Pi workstation
For the reader, the advantage of this “bare-metal” approach is threefold:
•
There is no dependence on product availability.
•
There is no built-in buffering between the Pi and your peripheral.
•
It costs less.
Products come and go, so why build on that foundation? Add-on products also often
provide buffering between the Pi and the outside world. But this feature would eliminate
the need to design this yourself.
Finally, in a large project like a robot, where several motor and sensor interfaces
exist, the need to economize becomes essential. This is where learning to design your
own interfaces pays off.
Test Equipment
The experiments in this book require access to a digital multimeter (DMM). This is critical
for testing voltages for the Raspberry Pi’s own safety. The Pi will not tolerate inputs above
+3.3 V without possible damage. Consequently, voltage readings are recommended
as part of several experiments to make sure that no damage to the Pi occurs whenever
voltages exceeding 3.3 V are involved.
Many experiments can be laid out on breadboards without the need for soldering.
A huge time saver is the use of ready-made breadboard jumper wires. These can be
purchased from eBay for about $1.50 for about 50 to 65 wires. They come in different
colors, fit the breadboard well, and don’t require you strip the ends. Students have better
things to do with their time.
Final Words
By now, you are probably itching to get started. There is no better time than the present!
xxi
Chapter 1
DHT11 Sensor
The DHT11 humidity and temperature sensor is an economical peripheral manufactured
by D-Robotics UK (www.droboticsonline.com). It is capable of measuring relative
humidity between 20 and 90% RH within the operating temperature range of 0 to 50°C,
with an accuracy of ±5% RH. Additionally, temperature is measured in the range of
0 to 50°C, with an accuracy of ±2°C. Both values are returned with 8-bit resolution.
Characteristics
The signaling used by the DHT sensor is similar to the 1-Wire protocol, but the response
times differ. Additionally, there is no device serial number support. These factors make
the device incompatible with the 1-Wire drivers within the Linux kernel. Figure 1-1 shows
a DHT11 sensor.
Figure 1-1. DHT11 sensor
1
Chapter 1 ■ DHT11 Sensor
The DHT11 sensor also requires a power supply. In contrast, the signal line itself
powers most 1-Wire peripherals. The datasheet states that the DHT11 can be powered
by a range of voltages, from 3 V to 5.5 V. Powering it from the Raspberry Pi’s 3.3 V source
keeps the sensor signal levels within a safe range for GPIO. The device draws between
0.5 mA and 2.5 mA. Its standby current is stated as 100 mA to 150 mA, for those concerned
about battery consumption.
Circuit
Figure 1-2 shows the general circuit connections between the Raspberry Pi and the
DHT11 sensor. Pin 4 connects to the common ground, while pin 1 goes to the 3.3 V
supply. Pin 2 is the signal pin, which communicates with a chosen GPIO pin.
The program listing for dht11.c is configured to use GPIO 22. This is easily modified
(look for gpio_dht11).
Figure 1-2. DHT11 circuit
When the Pi is listening on the GPIO pin and the DHT11 is not sending data, the line
will float. For this reason, R1 is required to pull the line up to a stable level of 3.3 V. The
datasheet recommends a 5 kW resistor for the purpose (a more common 4.7 kW resistor
can be substituted safely). This presents less than 1 mA of load on either the GPIO pin or
the sensor when they are active. The datasheet also states that the 5 kW resistor should be
suitable for cable runs of up to 20 meters.
Protocol
The sensor speaks only when spoken to by the master (Raspberry Pi). The master must
first make a request on the bus and then wait for the sensor to respond. The DHT sensor
responds with 40 bits of information, 8 of which are a checksum.
2
Chapter 1 ■ DHT11 Sensor
Overall Protocol
The overall signal protocol works like this:
1.
The line idles high because of the pull-up resistor.
2.
The master pulls the line low for at least 18 ms to signal a read
request and then releases the bus, allowing the line to return
to a high state.
3.
After a pause of about 20 to 40 ms, the sensor responds by
bringing the line low for 80 ms and then allows the line to
return high for a further 80 ms. This signals its intention to
return data.
4.
Forty bits of information are then written out to the bus: each
bit starting with a 50 ms low followed by:
a.
26 to 28 ms of high to indicate a 0 bit
b.
70 ms of high to indicate a 1 bit
5.
The transmission ends with the sensor bringing the line low
one more time for 50 ms.
6.
The sensor releases the bus, allowing the line to return to a
high idle state.
Figure 1-3 shows the overall protocol of the sensor. Master control is shown in thick
lines, while sensor control is shown in thin lines. Initially, the bus sits idle until the master
brings the line low and releases it (labeled Request). The sensor grabs the bus and signals
that it is responding (80 ms low, followed by 80 ms high). The sensor continues with 40 bits
of sensor data, ending with one more transition to low (labeled End) to mark the end of
the last bit.
Figure 1-3. General DHT11 protocol
3
Chapter 1 ■ DHT11 Sensor
Data Bits
Each sensor data bit begins with a transition to low, followed by the transition to high, as
shown in Figure 1-4. The end of the bit occurs when the line is brought low again as the
start of the next bit. The last bit is marked off by one final low-to-high transition.
Figure 1-4. DHT11 data bit
Each data bit starts with a transition to low, lasting for 50 ms. The final transition
to low after the last bit also lasts for 50 ms. After the bit’s low-to-high transition, the bit
becomes a 0 if the high lasts only 26 to 28 microseconds. A 1 bit stays high for 70 ms
instead. Every data bit is completed when the transition from high to low occurs for the
start of the next bit (or final transition).
Data Format
Figure 1-5 illustrates the 40-bit sensor response, transmitting the most significant bit first.
The datasheet states 16 bits of relative humidity, 16 bits of temperature in Celsius, and an
8-bit checksum. However, the DHT11 always sends 0s for the humidity and temperature
fractional bytes. Thus the device really has only 8 bits of precision for each measurement.
Presumably, other models (or future ones) provide fractional values for greater precision.
Figure 1-5. DHT11 data format
4
Chapter 1 ■ DHT11 Sensor
The checksum is a simple sum of the first 4 bytes. Any carry overflow is simply
discarded. This checksum gives your application greater confidence that it has received
correct values in the face of possible transmission errors.
Software
The user space software written to read the DHT11 sensor on the Raspberry Pi uses the
direct register access of the GPIO pin. The challenges presented by this approach include
the following:
•
Short timings: 26 to 70 ms
•
Preemptive scheduling delays within the Linux kernel
One approach is to count how many times the program could read the high-level
signal before the end of the bit is reached (when the line goes low). Then decide on 0 bits
for shorter times and 1s for longer times. After some experimentation, a dividing line
could be drawn, where shorter signals mean 0 while the others are 1s.
The difficulty with this approach is that it doesn’t adapt well if the Raspberry Pi is
accelerated. When the CPU clock is increased through overclocking, the program will
tend to fail. There is also the potential for future Raspberry Pis to include CPUs with
higher clock rates.
The signal emitted by the sensor is almost a Manchester encoding. In Manchester
encoding, one-half of the wave form is shorter than the other. This allows counting
up for the first half and counting down for the second. Based on whether the counter
underflows, a decision is made about the value of the bit seen.
The DHT11 signal uses a fixed first half of 50 ms The bit is decided based on how long
the signal remains at a high level after that. So a “bit bang” application could get a relative
idea by counting the number of times it could read the low-level signal. Based on that,
it can get a relative idea of where the dividing line between a short and long high-level
signal is.
This is the approach that was adopted by the program dht11.c. It counts the
number of times in a spin loop that it can read the signal as low. On a 700 MHz nonturbo
Raspberry Pi, I saw this count vary between 130 and 330 times, with an average of
292. This time period is supposed to be exactly 50 ms, which illustrates the real-time
scheduling problem within a user space program. (The program did not use any real-time
Linux priority scheduling.)
If the sensor waveform is true, a max count of 330 suggests that the Raspberry Pi can
read the GPIO pin a maximum of
330
= 6.6reads / m s
50
But the minimum of 130 GPIO reads shows a worst case performance of
130
= 2.6reads / m s
50
5
Chapter 1 ■ DHT11 Sensor
This variability in preemptive scheduling makes it difficult to do reliable timings.
I have seen the high-level bit counts vary between 26 and 378. (The interested reader
can modify the code to record the counts.) If the program is able to read 6.6 times per
microsecond, a 1-bit time of 70 ms should yield a count of 462. Yet the maximum seen was
378. Preemptive scheduling prevents the code from performing that many reads without
interruption.
The lower count of 26 represents the minimum count for 0 bits, where the line stays
high for a shorter period of time. This suggests that each GPIO read is about 1 ms or longer
during the 0-bit highs.
The preceding information is just a crude sampling of the problem to illustrate
the variability that must be grappled with in a user space program, on a multitasking
operating system.
Chosen Approach
The program shown in this chapter uses the following general approach:
1.
2.
Count the number of GPIO reads that report that the line is
low (call it Clow).
Compute an adjustment bias B based on B = C low , where D is
D
some fixed divisor.
3.
Compute a new count K = B + Chigh, where Chigh is the number
of times the line was read as high.
4.
If the count value K > Clow, the value is considered a 1-bit;
otherwise, it’s considered a 0-bit.
The method is intended to at least partially compensate for the level of preemption
being seen by the application program. By measuring the low read counts, we get an idea
of the number of times we can sample the line at the moment. The approach is intended
to adapt itself to a faster-running Raspberry Pi.
Table 1-1 shows some experimental results on an idle Raspberry Pi running at the
standard 700 MHz. Different divisors were tried and tested over 5-minute intervals.
When the program runs, it attempts to read and report as many sensor readings as it
can, tracking good reports, time-out, and error counts. The program was terminated by
pressing ^C when an egg timer went off.
6
Chapter 1 ■ DHT11 Sensor
Table 1-1. Bias Test Results
Divisor
Results
Time-outs
Errors
2
1
17
103
3
48
17
63
4
30
25
56
5
49
14
63
6
45
20
52
7
60
16
47
8
41
20
56
9
42
17
62
10
39
22
53
11
40
14
72
12
43
13
71
13
47
10
75
14
32
19
67
15
28
23
63
16
38
16
69
17
33
14
81
18
34
13
82
19
31
16
75
20
22
18
81
Using no bias at all, no successful reads result (which prompted the idea of applying
a bias). Using a divisor of 2 applies too much adjustment, as can be seen by the low
number of results (1). Increasing the divisor to the value 3 or more produced a much
higher success rate, near 48, which is almost 10 reports per minute. Setting the divisor to 3
seems to yield the most repeatable results overall.
It is uncertain from the datasheets how rapidly the sensor can be requeried. The
program takes the conservative approach of pausing 2 seconds between each sensor read
attempt or waiting 5 seconds when a time-out has occurred.
The program reports an error when the checksum does not match. Time-outs occur
if the code gets stuck waiting for the signal to go low, for too long. This can happen if the
program misses a critical event because of preemptive scheduling. It sometimes happens
that the high-to-low-to-high event can occur without the program ever seeing it. If the
going-low event takes too long, the program performs a longjmp into the main loop, to
allow a retry.
7
Chapter 1 ■ DHT11 Sensor
The errors are reported to stderr, allowing them to be suppressed by redirecting
unit 2 to /dev/null from the command line.
The way that the Raspberry Pi relinquishes the sensor bus is by changing the GPIO
pin from an output to an input. When configured as an input, the pull-up resistor brings
the bus line high when the bus is idle (the pull-up applies when neither master or slave
is driving the bus). When requested, the sensor grabs the bus and drives it high or low.
Finally, when the master speaks, we configure the pin as an output, causing the GPIO pin
to drive the bus.
Example Run
When the program dht11 is run, you should see output similar to the following:
$ sudo ./dht11
RH 37% Temp 18
(Error # 1)
(Timeout # 1)
RH 37% Temp 18
(Timeout # 2)
RH 37% Temp 18
RH 37% Temp 18
RH 37% Temp 18
(Error # 2)
(Timeout # 3)
(Error # 3)
(Error # 4)
(Error # 5)
RH 37% Temp 18
(Error # 6)
RH 37% Temp 18
(Error # 7)
(Error # 8)
(Error # 9)
RH 36% Temp 19
RH 37% Temp 18
(Timeout # 4)
RH 36% Temp 19
^C
Program exited
C Reading 1
C Reading 2
C Reading 3
C Reading 4
C Reading 5
C Reading 6
C Reading 7
C Reading 8
C Reading 9
C Reading 10
due to SIGINT:
Last Read: RH 36% Temp 19 C, 9 errors, 4 timeouts, 10 readings
8
Chapter 1 ■ DHT11 Sensor
Source Code
The next few pages list the source code for the program. This was assembled into one
compile unit by using the #include directive. This was done to save pages by eliminating
additional header files and extern declarations.
■■Note The source code for gpio_io.c is found in Chapter 10 of Raspberry Pi Hardware
Reference (Apress, 2014).
1 /∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
2
∗ dht11.c: Direct GPIO access reading DHT11 humidity and temp sensor.
3
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
4
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13
14 #include "gpio_io.c"
/∗ GPIO routines ∗/
15 #include "timed_wait.c"
/∗ timed_wait() ∗/
16
17 static const int gpio_dht11 = 22;
/∗ GPIO pin ∗/
18 static jmp_buf timeout_exit;
/∗ longjmp on timeout ∗/
19 static int is_signaled = 0;
/∗ Exit program if signaled ∗/
20
21/∗
22 ∗ Signal handler to quit the program:
23 ∗/
24 static void
25 sigint_handler(int signo) {
26
is_signaled = 1;
/∗ Signal to exit program ∗/
27 }
28
29 /∗
30 ∗ Read the GPIO line status:
31 ∗/
32 static inline unsigned
33 gread(void) {
34
return gpio_read(gpio_dht11);
35 }
36
9
Chapter 1 ■ DHT11 Sensor
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
10
/∗
∗ Wait until the GPIO line goes low:
∗/
static inline unsigned
wait_until_low(void) {
const unsigned maxcount = 12000;
unsigned count = 0;
while ( gread() )
if ( ++count >= maxcount || is_signaled )
longjmp(timeout_exit,1);
return count;
}
/∗
∗ Wait until the GPIO line goes high:
∗/
static inline unsigned
wait_until_high(void) {
unsigned count = 0;
while ( !gread() )
++count;
return count;
}
/∗
∗ Read 1 bit from the DHT11 sensor:
∗/
static unsigned
rbit(void) {
unsigned bias;
unsigned lo_count, hi_count;
wait_until_low();
lo_count = wait_until_high();
hi_count = wait_until_low();
bias = lo_count / 3;
return hi_count + bias > lo_count ? 1 : 0 ;
}
/∗
∗ Read 1 byte from the DHT11 sensor :
∗/
Chapter 1 ■ DHT11 Sensor
83 static unsigned
84 rbyte(void) {
85
unsigned x, u = 0;
86
87
for ( x=0; x<8; ++x )
88
u = (u << 1) | rbit();
89
return u;
90 }
91
92 /∗
93 ∗ Read 32 bits of data + 8 bit checksum from the
94 ∗ DHT sensor. Returns relative humidity and
95 ∗ temperature in Celsius when successful. The
96 ∗ function returns zero if there was a checksum
97 ∗ error.
98 ∗/
99 static int
100 rsensor(int ∗relhumidity, int ∗celsius) {
101
unsigned char u[5], cs = 0, x;
102
for ( x=0; x<5; ++x ) {
103
u[x] = rbyte();
104
if ( x < 4 )
/∗ Only checksum data..∗/
105
cs += u[x];
/∗ Checksum ∗/
106
}
107
108
if ( (cs & 0xFF) == u[4] ) {
109
∗relhumidity = (int)u [0];
110
∗celsius = (int)u [2];
111
return 1;
112
}
113
return 0;
114 }
115
116 /∗
117 ∗ Main program:
118 ∗/
119 int
120 main(int argc, char ∗∗argv) {
121
int relhumidity = 0, celsius = 0;
122
int errors = 0, timeouts = 0, readings = 0;
123
unsigned wait;
124
125
signal(SIGINT,sigint_handler); /∗ Trap on SIGINT ∗/
126
127
gpio_init();
/∗ Initialize GPIO access ∗/
128
gpio_config(gpio_dht11,Input); /∗ Set GPIO pin as Input ∗/
129
11
Chapter 1 ■ DHT11 Sensor
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
for (;;) {
if ( setjmp(timeout_exit) ) { /∗ Timeouts go here ∗/
if ( is_signaled ) /∗ SIGINT? ∗/
break;
/∗ Yes, then exit loop ∗/
fprintf(stderr," (Timeout # %d)\
n",++timeouts);
wait = 5;
} else wait = 2;
wait_until_high();
/∗ Wait GPIO line to go high ∗/
timed_wait(wait,0,0); /∗ Pause for sensor ready ∗/
gpio_config(gpio_dht11,Output); /∗ Output mode ∗/
gpio_write(gpio_dht11,0);
/∗ Bring line low ∗/
timed_wait(0,30000,0);
/∗ Hold low min of 18ms ∗/
gpio_write(gpio_dht11,1); /∗ Bring line high ∗/
gpio_config(gpio_dht11,Input); /∗ Input mode ∗/
wait_until_low()
/∗ Wait for low signal ∗/
wait_until_high();
/∗ Wait for return to high ∗/
if ( rsensor(&relhumidity,& celsius) )
printf("RH %d%% Temp %d C Reading %d\n",
relhumidity, celsius,++readings);
else fprintf(stderr," (Error # %d)\n",++errors);
}
gpio_config(gpio_dht11,Input); /∗ Set pin to input mode ∗/
puts("\ nProgram exited due to SIGINT: \n");
printf("Last Read: RH %d%% Temp %d C, %d errors, "
"%d timeouts, %d readings \n",
relhumidity, celsius, errors, timeouts, readings);
return 0;
159
160
161 }
162
163 /∗ End dht11.c ∗/
12
Chapter 1 ■ DHT11 Sensor
1
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
2
∗ Implement a precision "timed wait". The parameter early_usec
3
∗ allows an interrupted select(2) call to consider the wait as
4
∗ completed, when interrupted with only "early_usec" left remaining.
5
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
6
static void
7
timed_wait(long sec,long usec,long early_usec) {
8
fd_set mt;
9
struct timeval timeout;
10
int rc;
11
12
FD_ZERO(&mt);
13
timeout.tv_sec = sec;
14
timeout.tv_usec = usec;
15
do {
16
rc = select (0,&mt,&mt,&mt,&timeout);
17
if ( ! timeout.tv_sec && timeout.tv_usec < early_usec )
18
return;
/∗ Wait is good enough, exit ∗/
19
} while ( rc < 0 && timeout.tv_sec && timeout.tv_usec );
20 }
21
22 /∗ End timed_wait.c ∗/
13
Chapter 2
MCP23017 GPIO Extender
Microchip’s MCP23017 provides 16 additional GPIO pins that can be purchased for as
little as $1.99. The chip communicates using the I2C bus. (The companion MCP23S17 is
available for SPI bus.) The I2C bus allows the chip to be remote from the Raspberry Pi,
requiring only a four-wire ribbon cable (power, ground, and a pair of I2C bus lines). This
chapter explores the features and limits of this peripheral.
DC Characteristics
When shopping for chips or interface PCBs based on a particular chip, the first thing I
look at is the operating supply voltage. 5 V parts are inconvenient for the Pi because of its
3.3 V GPIO interface. Many newer devices operate over a range of voltages, which include
3.3 V. The MCP23017 supply VDD operates from an extended range of +1.8 V to +5.5 V. This
clearly makes it compatible, if we power the chip from a +3.3 V source. Figure 2-1 shows
the MCP23017 chip pinout diagram.
15
Chapter 2 ■ MCP23017 GPIO Extender
Figure 2-1. MCP23017 pinout
GPIO Output Current
Another factor in choosing a peripheral chip is its output drive capability. How well can
the GPIO pin source or sink current? As covered in Chapter 10 of Raspberry Pi Hardware
Reference (Apress, 2014), the Raspberry Pi’s own GPIO pins can source/sink up to
16 mA, depending on configuration. The MCP23017 chip specifications indicate that it
can source or sink up to 25 mA.
We still need to remember that if the MCP23017 is powered from the Raspberry
Pi’s 3.3 V regulator on header P1, the total current budget must not exceed 50 mA.
This budget includes the Pi’s own GPIO pin current usage. If, on the other hand, the
MCP23017 is powered from a separate 3.3 V power supply, this limitation is eliminated.
There are still reasons to budget current, however. The chip must not consume more
than 700 mW of power. This implies a total current limit as follows:
P
VDD
0.7
=
3.3
= 212mA
IVDD =
16
Chapter 2 ■ MCP23017 GPIO Extender
This power figure gives us an upper current limit. However, the datasheet of the
MCP23017 also lists a maximum of 125 mA for supply pin VDD. If every GPIO output is
sourcing power, this leaves us with the following average pin limit:
125mA
= 7.8mA
16
So while the output GPIO pins can source up to 25 mA, we cannot have all of them
doing so simultaneously.
Likewise, the datasheet lists VSS (ground) as limited to an absolute maximum of
150 mA. If every GPIO pin is an output and sinking current, the average for each output
pin cannot exceed the following:
150mA
= 9.4mA
16
Once again, while each output pin can sink up to 25 mA, we see that they cannot all
do so at the same time without exceeding chip limits. This should not be discouraging,
because in most applications, not all GPIO pins will be outputs, and not all will all be
driving heavy loads. The occasional pin that needs driving help can use a transistor driver
like the one discussed in Chapter 10 of Raspberry Pi Hardware Reference (Apress, 2014).
Before we leave the topic of GPIO output driving, we can apply one more simple
formula to help with interface design. With the foregoing information, we can calculate
the number of 25 mA outputs available:
125mA
= 5
25mA
From this, it is known that four to five GPIO pins can operate near their maximum
limits, as long as the remaining GPIO pins are inputs or remain unconnected.
GPIO Inputs
In normal operation, the GPIO inputs should never see a voltage below the ground
potential VSS. Nor should they ever see a voltage above the supply voltage VDD. Yet,
variations can sometimes happen when interfacing with the external world, particularly
with inductive components.
The datasheet indicates that clamping diodes provide some measure of protection
against this. Should the voltage on an input drop below 0, it is clamped by a diode so it
will not go further negative and cause harm. The voltage limit is listed at –0.6 V, which is
the voltage drop of the clamping diode. Likewise, if the voltage goes over VDD (+3.3 V in
our case), the clamping diode will limit the excursion to VDD + 0.6 V (+3.9 V).
This protection is limited by the current capability of the clamping diodes. The
datasheet lists the maximum clamping current as 20 mA. If pins are forced beyond their
limits and the clamping current is exceeded, damage will occur.
17
Chapter 2 ■ MCP23017 GPIO Extender
While we have focused on GPIO inputs in this section, the clamping diodes also
apply to outputs. Outputs can be forced beyond their limits by external circuits like
pull-up resistors. Pull-up resistors should not be attached to +5 V, for example, when the
MCP23017 is operating from a +3.3 V supply.
Standby Current
If the MCP23017 device is not sourcing or sinking output currents, the standby current
is stated as 3 μ A (for 4.5 to 5.5 V operation). This operating parameter is important to
designers of battery-operated equipment.
Input Logic Levels
Since the device operates over a range of supply voltages, the datasheet defines the logic
levels in terms of the supply voltage. For example, the GPIO input low level is listed as
0.2 × VDD. So if we operate with VDD= +3.3 V, the input low voltage is calculated as follows:
VILmax = 0.2 ´VDD
= 0.2 ´ 3.3
= 0.66V
Therefore, a voltage in the range of 0 to 0.66 V is guaranteed to read as a 0 bit.
Likewise, let’s calculate the input high voltage threshold, where the multiplier is
given as 0.8:
VIH min = 0.8 ´VDD
= 0.8 ´ 3.3
= 2.64V
Thus any voltage greater than or equal to 2.64 V is read as a 1 bit, when powered from
a +3.3 V supply. Any voltage between VILmax and VIHmin is undefined and reads as a 1 or a 0,
and perhaps randomly so.
Output Logic Levels
The output logic levels are stated differently. The datasheet simply states that the output
low voltage should not exceed a fixed limit. The high level is also stated as a minimum
value relative to VDD. This pair of parameters is listed here:
VOLma x = 0.6 V
VOH mi n = VDD - 0.7 V
= 3.3 - 0.7
= 2.7 V
18
Chapter 2 ■ MCP23017 GPIO Extender
Reset Timing
The only parameter of interest for timing apart from the I2C bus is the device reset time.
In order for the device to see a reset request, pin RESET must remain active (low) for a
minimum of 1 μs. The device resets and places outputs into the high-impedance mode
within a maximum of 1 μs.
Circuit
Figure 2-2 shows a circuit with two remote MCP23017 GPIO extenders connected
to one I2C bus. In the figure, the power, ground, I2C data, and optional RESET and
INT connections are shown connected through a six-conductor ribbon cable. This
allows the Raspberry Pi to communicate remotely to peripherals in a robot, for example.
Figure 2-2. MCP23017 circuit
The data communication occurs over the pair of signals SDA and SCL. These are
connected to the Raspberry Pi’s pins P1-03 and P1-05, respectively (GPIO 2 and 3 for Rev
2.0+). The other end of the I2C data bus is common to all slave peripherals.
Each MCP23017 slave device is addressed by its individually configured A2, A1,
and A0 pins. For device A, these pins are shown grounded to define it as device number
0x20 (low bits are zeroed). A1 is tied high for device B so that its peripheral address
becomes 0x21. In this configuration, the Raspberry Pi will use addresses 0x20 and 0x21 to
communicate with these slave devices.
Lines labeled RESET and INT are optional connections. The RESET line can be
eliminated if you never plan to force a hardware reset of the slaves (tie to VDD through a
10 K resistor). Usually the power-on reset is sufficient. The INT line is more desirable,
since the MCP23017 can be programmed to indicate interrupts when a GPIO input has
changed in value (or does not match a comparison value). The INT line is an open
collector pin so that many can be tied together on the same line. However, the Pi will have
19
Chapter 2 ■ MCP23017 GPIO Extender
to poll each peripheral to determine which device is causing the interrupt. Alternatively,
each slave could provide a separate INT signal, with a corresponding increase in
signal lines.
Each MCP23017 chip has two interrupt lines, named INT A and INT B . There is
the option of separate interrupt notifications for the A group or the B group of GPIO pins.
For remote operation, it is desirable to take advantage of MCP23017’s ability to configure
these to work in tandem, so that only one INT line is required.
On the Raspberry Pi end, the GPIO pin used for the RESET line would be configured
as an output and held high, until a reset is required. When activating a reset, the line must
be held low for at least 1 microsecond, plus 1 more microsecond to allow for the chip reset
operation itself (and possibly longer, if non-MCP23017 slaves are connected to the bus).
The INT line should be connected to a GPIO input on the Pi. This GPIO input
either needs to be polled by the application, or to have the GPIO configured to trigger
on changes. Then the select(2) or poll(2) system calls can be used to detect when an
interrupt is raised by one or more peripherals.
The interrupt line, when used, should have a pull-up resistor configured (see
Chapter 10 of Raspberry Pi Hardware Reference [Apress, 2014] for information about
internal pull-up resistors). It may be best to use an external pull-up resistor, especially for
longer cable runs. To keep the sink current at 2 mA or less, the pull-up resistance used
should be no lower than the following:
+3.3V
2mA
=1650W
Rpullup =
A 2.2 kΩ 10% resistor will do nicely.
The +3.3 V line should be powered separately from the Raspberry Pi, unless the
slaves expect to drive very low currents. The main concern here is to not overload the
remaining 50 mA capacity of the Pi’s +3.3 V regulated supply. See Chapter 10 of Raspberry
Pi Hardware Reference (Apress, 2014) about budgeting +3.3 V power.
I2C Bus
Throughout this chapter, we are assuming a Rev 2.0 or later Raspberry Pi. This matters for
the I2C bus because the early versions wired I2C bus 0 to P1-03 and P1-05 (GPIO 0 and 1).
Later this was changed to use bus 1. See Chapter 12 of Raspberry Pi Hardware Reference
(Apress, 2014) for more information about identifying your Pi and which I2C bus to use.
If you are using an early Raspberry Pi revision, you’ll need to substitute 0 for bus number
1, in commands and in the C source code that follows.
20
Chapter 2 ■ MCP23017 GPIO Extender
Wiring and Testing
The connections to the MCP23017 are simple enough that you can wire it up on a
breadboard. The first step after wiring is to determine that you can detect the peripheral
on the I2C bus.
But even before that, check your kernel module support. If lsmod doesn’t show these
modules loaded, you can manually load them now:
$ sudo modprobe i2c−dev
$ sudo modprobe i2c−bcm2708
If you haven’t already done so, install i2c-tools:
$ sudo apt−get install i2c−tools
If your I2C support is there, you should be able to list the available I2C buses:
$ i2cdetect −l
i2c −0 unknown
i2c −1 unknown
bcm2708_i2c.0
bcm2708_i2c.1
N/A
N/A
The I2C device nodes should also appear in /dev. These nodes give us access to the
I2C drivers:
$ ls −l /dev/i2c∗
crw−rw−−−T 1 root root
crw−rw−−−T 1 root root
89, 0
89, 1
Feb 18 23:53
Feb 18 23:53
/dev/i2c−0
/dev/i2c−1
The ultimate test is to see whether the MCP23017 chip is detected (change the 1 to 0
for older Pi revisions):
$ sudo
0
00:
10: −−
20: 20
30: −−
40: −−
50: −−
60: −−
70: −−
i2cdetect −y 1
1 2 3 4 5
−− −− −−
−− −− −− −− −−
−− −− −− −− −−
−− −− −− −− −−
−− −− −− −− −−
−− −− −− −− −−
−− −− −− −− −−
−− −− −− −− −−
6
−−
−−
−−
−−
−−
−−
−−
−−
7
−−
−−
−−
−−
−−
−−
−−
−−
8
−−
−−
−−
−−
−−
−−
−−
9
−−
−−
−−
−−
−−
−−
−−
a
−−
−−
−−
−−
−−
−−
−−
b
−−
−−
−−
−−
−−
−−
−−
c
−−
−−
−−
−−
−−
−−
−−
d
−−
−−
−−
−−
−−
−−
−−
e
−−
−−
−−
−−
−−
−−
−−
f
−−
−−
−−
−−
−−
−−
−−
In this example, the A2, A1, and A0 pins of the MCP23017 were grounded. This gives
the peripheral the I2C address of 0x20. In the session output, we see that address 0x20
was detected successfully.
21
Chapter 2 ■ MCP23017 GPIO Extender
The i2cdump utility can be used to check the MCP23017 register:
$
sudo i2cdump –y –r
0x00–0x15 1 0x20 b
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00
10: 00 00 00 00 00 00
0123456789abcdef
................
......
Here we have dumped out registers 0x00 to 0x15 on I2C bus 1, at peripheral address
0x20, in byte mode. This was performed after a power-on reset, so we can check whether
the register values match the datasheet values documented. As expected, IODIRA (register
0x00) and IODIRB (register 0x01) have the default of all 1s (0xFF). This also confirms that
the registers are in BANK=0 mode (this is discussed in the following sections). All other
MCP23017 registers default to 0 bits, which is also confirmed.
Software Configuration
The MCP23017 datasheet describes the full register complement and options available.
In this chapter, we’ll concern ourselves with a subset of its functionality, which is perhaps
considered “normal use.” The extended functionality is left as an exercise for you.
For this chapter’s project, we’re going to do the following:
•
Configure some GPIOs as inputs
•
Configure some GPIOs as outputs
•
Configure the group A and B inputs to signal an interrupt on any
change
General Configuration
The MCP23017 peripheral has 10 registers for the GPIO-A pins, 10 registers for the
GPIO-B pins, and one shared register. In other words, there are 22 registers, with one pair
of addresses referencing a common register. These registers may be accessed in banks or
interleaved. We’ll use interleaved mode in this chapter, to avoid having to reset the device.
Interleaved register addresses are shown in Table 2-1. These are valid addresses
when the IOCON register value for BANK=0 (discussed later in this section).
22
Chapter 2 ■ MCP23017 GPIO Extender
Table 2-1. MCP23017 Register Addresses
Register
A
B
Description
IODIRx
0x00
0x01
I/O direction
IPOLx
0x02
0x03
Input polarity
GPINTENx
0x04
0x05
Interrupt on change control
DEFVALx
0x06
0x07
Default comparison value
INTCONx
0x08
0x09
Interrupt control
IOCONx
0x0A
0x0B
Configuration
GPPUx
0x0C
0x0D
Pull-up configuration
INTFx
0x0E
0x0F
Interrupt flags
INTCAPx
0x10
0x11
Interrupt captured value
GPIOx
0x12
0x13
General-purpose I/O
OLATx
0x14
0x15
Output latch
IOCON Register
This is the first register that must be configured, since it affects how registers are
addressed. Additionally, other settings are established that affect the entire peripheral.
Table 2-2 illustrates the layout of the IOCON register. Setting the BANK bit determines
whether we use banked or interleaved register addressing. The MCP23017 is in
interleaved mode after a power-on reset. Once you set BANK=1, the register addresses
change. However, once this change is made, it is impossible to tell, after a program restart,
which register mode the peripheral is in. The only option is a hardware reset of the
MCP23017 chip, to put it in a known state. For this reason, we’ll keep the peripheral in its
power-on reset state of BANK=0.
23
Chapter 2 ■ MCP23017 GPIO Extender
Table 2-2. IOCON Register
Bit
Meaning
R
W
Reset
Description
7
BANK
Y
Y
0
Set to 0 for interleaved access
6
MIRROR
Y
Y
0
Set to 1 to join INTA & INTB
5
SEQOP
Y
Y
0
Set to 0 for auto-address increment
4
DISSLW
Y
Y
0
Set to 1 to disable slew rate control
3
HAEN
Y
Y
0
Ignored: I2C always uses address
2
ODR
Y
Y
0
Set to 1 for open-drain INT pins
1
INTPOL
Y
Y
0
Set to 0 for INT active low
0
N/A
0
X
0
Ignored: reads as zero
GPIO
Address
Note
A
0x0A
These access a shared register
B
0x0B
In the tables that follow, a Y under the R (read) or W (write) column/row indicates
that you can read or write the respective value. The Reset column indicates the state of
the bit after a device reset. An X indicates a “don’t care” or an undefined value when read.
An N indicates no access or no effect when written.
The bit MIRROR=1 is used to make INT A equivalent to INT B . In other words, GPIO
A and B interrupts are reported to both pins. This allows a single pin to be used for A and
B groups.
Setting bit SEQOP=0 allows the peripheral to automatically increment the register
address as each byte is read or written. This eliminates the need to transmit a register
address in many cases.
Bit DISSLW affects the physical handling of the SDA I2C bus line.
HAEN is applicable only to the MCP23S17 SPI device, since addresses are always
enabled for I2C devices.
This project uses ODR=1 to configure the INT A pin as an open-drain pin. This
allows multiple MCP23017 devices to share the same interrupt line. Use a pull-up resistor
on the INT line when this is in effect. Otherwise, you may experience several sporadic
interrupts.
Finally, INTPOL=0 is configured so that the interrupt is active low. This is required for
an open-drain connected line along with a pull-up resistor.
24
Chapter 2 ■ MCP23017 GPIO Extender
OLATx Register
GPIO pins are all configured as inputs after a power-on reset (or use of RESET ). Prior
to configuring pins as outputs, it is usually a good idea to set the required output values.
This is accomplished by writing the OLAT register, for group A or B. For this project, we’ll
just write 0x00 to both OLATA and OLATB.
OLATx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x14
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x15
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
OLATx Bit Value
0
Output set to 0
1
Output set to 1
GPPUx Register
A given project should also define a known value for its input pull-up resistors. Setting a
given bit to 1 enables a weak 100 KΩ internal pull-up resistor. This setting affects only the
inputs. The pull-up resistors are configured off after a reset. In our example code, we turn
them on.
GPPUx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x0C
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x0D
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
GPPUx Bit Value
0
Pull-up resistor disabled
1
100 KW pull-up resistor enabled
25
Chapter 2 ■ MCP23017 GPIO Extender
DEFVALx Register
This register is associated with interrupt processing. Interrupts are produced from
conditions arising from input GPIO pins only. An interrupt can be generated if the
input differs from the DEFVALx register or if the input value has changed. In the project
presented, we simply zero this value because it is not used when detecting changed
inputs.
DEFVALx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x06
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x07
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
DEFVALx Bit Value
0
Interrupt when input not 0
1
Interrupt when input not 1
INTCONx Register
This register specifies how input comparisons will be made. In our project, we set all
these bits to 0 so that inputs interrupt on change.
INTCONx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x08
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x09
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
INTCONx Bit Value
0
Input compared against its previous value
1
Input compared against DEFCONx bit value
26
Chapter 2 ■ MCP23017 GPIO Extender
IPOLx Register
Bits set in this register will invert the logic sense of the corresponding GPIO inputs. In our
project, we used no inversion and set the bits to 0.
IPOLx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x02
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x03
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
IPOLx Bit Value
0
Read same logic as input pin
1
Read inverted logic of input pin
IODIRx Register
This register determines whether a given GPIO pin is an input or an output. Our project
defines bits 4 through 7 as inputs, with the remaining bits 0 through 3 of each 8-bit port as
outputs.
IODIRx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x00
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x01
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
1
1
1
1
1
1
1
1
IODIRx Bit Value
0
Pin is configured as an output
1
Pin is configured as an input
27
Chapter 2 ■ MCP23017 GPIO Extender
GPINTENx Register
This register enables interrupts on input pin events. Only inputs generate interrupts, so
any enable bits for output pins are ignored. How the interrupt is generated by the input is
determined by registers DEFVALx and INTCONx.
GPINTENx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x04
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x05
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
0
0
0
0
0
0
0
0
GPINTENx Bit Value
0
Disable interrupts on this input
1
Enable interrupts for this input
For this project, we enabled interrupts on all inputs for ports A and B.
INTFx Register
This interrupt flags register contains the indicators for each input pin causing an
interrupt. This register is unwritable.
Interrupt service routines start with this register to identify which inputs are the
cause of the interrupt. The DEFVALx and INTCONx registers configure how those interrupts
are generated. The INTFx flags are cleared by reading the corresponding INTCAPx or
GPIOx register.
INTFx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x0E
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x0F
W
N
N
N
N
N
N
N
N
Reset
0
0
0
0
0
0
0
0
INTFx Bit Value
0
No interrupt for this input
1
Input has changed or does not compare
28
Chapter 2 ■ MCP23017 GPIO Extender
INTCAPx Register
The interrupt capture register reports the status of the inputs as the interrupt is being
raised. This register is read-only. Reading this register clears the INTFx register, to allow
new interrupts to be generated. When INT A is linked to INT B , both INTCAPA and
INTCAPB must be read to clear the interrupt (or read the GPIOx registers).
INTCAPx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x10
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x11
W
N
N
N
N
N
N
N
N
Reset
0
0
0
0
0
0
0
0
INTCAPx Bit Value
0
Input state was 0 at time of interrupt
1
Input state was 1 at time of interrupt
GPIOx Register
Reading this register provides the current input pin values, for pins configured as
inputs. Reading the GPIOx register also clears the interrupt flags in INTFx. When INT A
is linked to INT B , both GPIOA and GPIOB must be read to clear the interrupt (or read the
INTCAPx registers).
Presumably, the OLATx register is read, for pins configured for output (the datasheet
doesn’t say). Writing to the GPIOx register alters the OLATx settings in addition to
immediately affecting the outputs.
GPIOx Register
GPIO
Address
Bit
7
6
5
4
3
2
1
0
A
0x12
R
Y
Y
Y
Y
Y
Y
Y
Y
B
0x13
W
Y
Y
Y
Y
Y
Y
Y
Y
Reset
X
X
X
X
X
X
X
X
29
Chapter 2 ■ MCP23017 GPIO Extender
Value
R/W
GPIOx Bit Value
0
R
Current input pin state is low
W
Write 0 to OLATx and output pin
R
Current input pin state is high
W
Write 1 to OLATx and output pin
1
Main Program
Here are some change notes for the main program:
1.
If you have a pre-revision 2.0 Raspberry Pi, change line 36 to
use /dev/i2c-0.
2.
Change line 55 if your MCP23017 chip is using a different I2C
address than 0x20 (A2, A1, and A0 grounded).
3.
Change line 56 if you use a different Raspberry Pi GPIO for
your interrupt sense pin.
The main program is fairly straightforward. Here are the basic overall steps:
1.
A signal handler is registered in line 180, so ^C will cause the
program to exit cleanly.
2.
Routine i2c_init() is called to open the I2C driver and
initialize.
3.
Routine mcp23017_init() is called to initialize and configure
the MCP23017 device on the bus (only one is currently
supported).
4.
Routine gpio_open_edge() is called to open /sys/class/
gpio17/value, so changes on the interrupt line can be sensed.
This is discussed in more detail later.
5.
Finally, the main program enters a loop in lines 190 to 200,
looping until ^C is entered.
Once inside the main loop, the following events occur:
30
1.
Execution stalls when gpio_poll() is called. This blocks until
the interrupt on GPIO 17 transitions from a high to a low.
2.
The interrupt flags are read using routine mcp23017_
interrupts(). They’re only reported and otherwise not used.
3.
Routine mcp23017_captured() is used to read the INTCAPA
and INTCAPB registers in order to clear the interrupt.
4.
Finally, the routine post_outputs() reads the real-time input
values and sends the bits to the outputs.
Chapter 2 ■ MCP23017 GPIO Extender
Program mcp23017.c is shown here:
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
39
40
41
42
43
44
45
46
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ mcp23017.c : Interface with MCP23017 I/O Extender Chip
∗
∗ This code assumes the following :
∗
∗
1. MCP23017 is configured for address 0x20
∗
2. RPi's GPIO 17 (GEN0) will be used for sensing interrupts
∗
3. Assumed there is a pull up on GPIO 17.
∗
4. MCP23017 GPA4-7 and GPB4–7 will be inputs, with pull-ups.
∗
5. MCP23017 GPA0–3 and GPB0–3 will be ouputs.
∗
6. MCP23017 signals interrupt active low.
∗
7. MCP23017 operating in non–banked register mode.
∗
∗ Inputs sensed will be copied to outputs :
∗
1. GPA4–7 copied to GPA0–3
∗
2. GPB4–7 copied to GPB0–3
∗
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "i2c_funcs.c"
/∗ I2C
routines
/∗ Change to i2c 0 if using early Raspberry Pi ∗/
static const char ∗ node = "/dev/i2c–1";
#define GPIOA
#define GPIOB
0
1
#define
#define
#define
#define
#define
#define
0
1
2
3
4
5
IODIR
IPOL
GPINTEN
DEFVAL
INTCON
IOCON
∗/
31
Chapter 2 ■ MCP23017 GPIO Extender
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
32
#define
#define
#define
#define
#define
GPPU
INTF
INTCAP
GPIO
OLAT
6
7
8
9
10
#define MCP_REGISTER(r,g) (((r) <<1)|(g)) /∗ For I2C routines ∗/
static unsigned gpio_addr = 0x20;
static const int gpio_inta = 17;
static int is_signaled = 0;
#include "sysgpio.c"
/∗ MCP23017 I2C Address ∗/
/∗ GPIO pin for INTA connection ∗/
/∗ Exit program if signaled ∗/
/∗
∗ Signal handler to quit the program :
∗/
static void
sigint_handler(int signo) {
is_signaled = 1;
/∗ Signal to exit program ∗/
}
/∗
∗ Write to MCP23017 A or B register set:
∗/
static int
mcp23017_write(int reg,int AB,int value) {
unsigned reg_addr = MCP_REGISTER(reg,AB);
int rc;
rc = i2c_write8(gpio_addr,reg_addr,value);
return rc;
}
/∗
∗ Write value to both MCP23017 register sets :
∗/
static void
mcp23017_write_both(int reg,int value) {
mcp23017_write(reg,GPIOA,value); /∗ Set A ∗/
mcp23017_write(reg,GPIOB,value); /∗ Set B ∗/
}
/∗
∗ Read the MCP23017 input pins (excluding outputs,
∗ 16–bits) :
∗/
Chapter 2 ■ MCP23017 GPIO Extender
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
static unsigned
mcp23017_inputs(void) {
unsigned reg_addr = MCP_REGISTER(GPIO,GPIOA);
return i2c_read16(gpio_addr,reg_addr) & 0xF0F0;
}
/∗
∗ Write 16 bits to outputs :
∗/
static void
mcp23017_outputs(int value) {
unsigned reg_addr = MCP_REGISTER(GPIO,GPIOA);
i2c_write16 (gpio_addr,reg_addr,value & 0x0F0F);
}
/∗
∗ Read MCP23017 captured values (16–bits):
∗/
static unsigned
mcp23017_captured(void) {
unsigned reg_addr = MCP_REGISTER(INTCAP,GPIOA);
return i2c_read16(gpio_addr,reg_addr) & 0xF0F0;
}
/∗
∗ Read interrupting input flags (16–bits) :
∗/
static unsigned
mcp23017_interrupts(void) {
unsigned reg_addr = MCP_REGISTER(INTF,GPIOA);
return i2c_read16(gpio_addr,reg_addr) & 0xF0F0;
}
/∗
∗ Configure the MCP23017 GPIO Extender :
∗/
static void
mcp23017_init(void) {
int v, int_flags;
mcp23017_write_both(IOCON,0b01000100); /∗ MIRROR=1,ODR=1 ∗/
mcp23017_write_both(GPINTEN,0x00);
/∗ No interrupts enabled ∗/
33
Chapter 2 ■ MCP23017 GPIO Extender
mcp23017_write_both(DEFVAL,0x00);
/∗ Clear default value ∗/
mcp23017_write_both(OLAT,0x00);
/∗ OLATx=0 ∗/
mcp23017_write_both(GPPU,0b11110000); /∗ 4–7 are pull up ∗/
mcp23017_write_both(IPOL,0b00000000); /∗ No inverted polarity ∗/
/∗ 4–7 inputs, 0–3 outputs ∗/
mcp23017_write_both(IODIR,0b11110000);
mcp23017_write_both(INTCON,0b00000000); /∗ Cmp to previous ∗/
mcp23017_write_both(GPINTEN,0b11110000); /∗ Int on changes ∗/
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
34
/∗
∗ Loop until all interrupts are cleared:
∗/
do
{
int_flags = mcp23017_interrupts();
if ( int_flags != 0 ) {
v = mcp23017_captured();
printf(" Got change %04X values %04X\n",int_
flags,v);
}
} while ( int_flags != 0x0000 && !is_signaled );
}
/∗
∗ Copy input bit settings to outputs :
∗/
static void
post_outputs(void) {
int inbits = mcp23017_inputs(); /∗ Read inputs ∗/
int outbits = inbits >> 4;
/∗ Shift to output bits ∗/
mcp23017_outputs(outbits);
/∗ Copy inputs to outputs ∗/
printf ("
Outputs:
%04X\n",outbits);
}
/∗
∗Main program :
∗/
int
main(int argc,char ∗∗argv) {
int int_flags, v;
int fd;
signal(SIGINT,sigint_handler);
i2c_init(node);
mcp23017_init();
/∗ Trap on SIGINT ∗/
/∗ Initialize for I2C ∗/
/∗ Configure MCP23017 @ 20 ∗/
fd = gpio_open_edge(gpio_inta); /∗ Configure INTA pin ∗/
Chapter 2 ■ MCP23017 GPIO Extender
187
188
189
190
191
192
193
194
195
196
puts("Monitoring for MCP23017 input changes :\n");
post_outputs();
/∗ Copy inputs to outputs ∗/
do
{
gpio_poll(fd);
/∗ Pause until an interrupt ∗/
int_flags = mcp23017_interrupts();
if ( int_flags ) {
v = mcp23017_captured();
printf(" Input change: flags %04X values
%04X\n",
int_flags,v);
post_outputs();
}
} while ( !is_signaled ); /∗ Quit if ^C' d ∗/
197
198
199
200
201
202
fputc('\n', stdout);
203
204
i2c_close();
205
close(fd);
206
gpio_close(gpio_inta);
207
return 0;
208 }
209
210 /∗ End mcp23017.c ∗/
/∗ Close I2C driver ∗/
/∗ Close gpio17/value ∗/
/∗ Unexport gpio17 ∗/
Module i2c_funcs.c
To compile code when making use of I2C, you will need to install the libi2c
development library:
$ sudo apt-get install libi2c-dev
The i2c_funcs.c is a small module that wraps the ioctl(2) calls into neat little I/O
functions:
i2c_write8(): Writes 8-bit value to MCP23017 register
i2c_write16(): Writes 16-bit value to MCP23017 register
i2c_read8(): Reads 8-bit value from MCP23017 register
i2c_read16(): Reads 16-bit value from MCP23017 register
Additionally, the open and close routines are provided:
i2c_init(): Opens the bus driver for /dev/i2c-x
i2c_close(): Closes the opened I2C driver
35
Chapter 2 ■ MCP23017 GPIO Extender
The C API for these I2C functions are described in Chapter 12 of Raspberry Pi
Hardware Reference (Apress, 2014).
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
39
40
41
42
36
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ i2c_funcs.c : I2C Access Functions
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
static int i2c_fd = –1;
static unsigned long i2c_funcs = 0;
/∗ Device node : /dev/i2c–1 ∗/
/∗ Support flags ∗/
/∗
∗ Write 8 bits to I2C bus peripheral:
∗/
int
i2c_write8(int addr,int reg,int byte) {
struct i2c_rdwr_ioctl_data msgset;
struct i2c_msg iomsgs[1];
unsigned char buf[2];
int rc;
buf[0] = (unsigned char)reg;
buf[1] = (unsigned char)byte;
iomsgs[0].addr
iomsgs[0].flags
iomsgs[0].buf
iomsgs[0].len
=
=
=
=
/∗ MCP23017 register no. ∗/
/∗ Byte to write to register ∗/
(unsigned)addr;
0;
/∗ Write ∗/
buf;
2;
msgset.msgs = iomsgs;
msgset.nmsgs = 1;
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
return rc < 0 ? –1 : 0;
}
/∗
∗ Write 16 bits to Peripheral at address :
∗/
int
i2c_write16(int addr, int reg, int value) {
struct i2c_rdwr_ioctl_data msgset;
struct i2c_msg iomsgs[1];
unsigned char buf[3];
int rc;
Chapter 2 ■ MCP23017 GPIO Extender
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
buf[0] = (unsigned char)reg;
buf[1] = (unsigned char)(( value >> 8 ) & 0xFF);
buf[2] = (unsigned char)(value & 0xFF);
iomsgs[0].addr = (unsigned)addr;
iomsgs[0].flags = 0;
/∗ Write ∗/
iomsgs[0].buf = buf;
iomsgs[0].len = 3;
msgset.msgs = iomsgs;
msgset.nmsgs = 1;
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
return rc < 0 ? –1 : 0;
}
/∗
∗ Read 8–bit value from peripheral at addr :
∗/
int
i2c_read8(int addr,int reg) {
struct i2c_rdwr_ioctl_data msgset;
struct i2c_msg iomsgs[2];
unsigned char buf[1], rbuf[1];
int rc;
buf[0] = (unsigned char)reg;
iomsgs[0].addr = iomsgs[1].addr = (unsigned)addr;
iomsgs[0].flags = 0;
/∗ Write ∗/
iomsgs[0].buf = buf;
iomsgs[0].len = 1;
iomsgs[1].flags = I2C_M_RD;
iomsgs[1].buf = rbuf;
iomsgs[1].len = 1;
/∗ Read ∗/
msgset.msgs = iomsgs;
msgset.nmsgs = 2;
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
return rc < 0 ? –1 : ((int)(rbuf[0]) & 0x0FF);
}
37
Chapter 2 ■ MCP23017 GPIO Extender
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
38
/∗
∗ Read 16– bits of data from peripheral :
∗/
int
i2c_read16(int addr,int reg) {
struct i2c_rdwr_ioctl_data msgset;
struct i2c_msg iomsgs[2];
unsigned char buf[1], rbuf [2];
int rc;
buf[0] = (unsigned char)reg;
iomsgs[0].addr = iomsgs[1].addr = (unsigned)addr;
iomsgs[0].flags = 0;
/∗ Write ∗/
iomsgs[0].buf = buf;
iomsgs[0].len = 1;
iomsgs[1].flags = I2C_M_RD;
iomsgs[1].buf = rbuf;
iomsgs[1].len = 2;
/∗ Read ∗/
msgset.msgs = iomsgs;
msgset.nmsgs = 2;
if ( (rc = ioctl(i2c_fd,I2C_RDWR,&msgset)) < 0 )
return –1;
return (rbuf[0] << 8) | rbuf[1];
}
/∗
∗ Open I2C bus and check capabilities :
∗/
static void
i2c_init(const char ∗ node) {
int rc;
i2c_fd = open(node,O_RDWR);
/∗ Open driver /dev/i2s–1 ∗/
if ( i2c_fd < 0 ) {
perror("Opening /dev/i2s–1");
puts("Check that the i2c dev & i2c–bcm2708 kernel
modules "
"are loaded.");
abort();
}
Chapter 2 ■ MCP23017 GPIO Extender
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/∗
∗ Make sure the driver supports plain I2C I/O:
∗/
rc = ioctl(i2c_fd,I2C_FUNCS,&i2c_funcs);
assert(rc >= 0) ;
assert(i2c_funcs & I2C_FUNC_I2C);
}
/∗
∗ Close the I2C driver
∗/
static void
i2c_close(void) {
close(i2c_fd);
i2c_fd = –1;
}
/∗ End i2c_funcs.c
:
∗/
Module sysgpio.c
The sysgpio.c module performs some grunt work in making the /sys/class/gpio17/
value node available and configuring it. This node is opened for reading, so that poll(2)
can be called upon it.
The interesting code in this module is found in lines 89 to 106, where gpio_poll() is
defined. The file descriptor passed to it as fd is the /sys/class/gpio17/value file that is
•
Configured as input
•
Triggered on the falling edge (high-to-low transition)
The poll(2) system call in line 99 blocks the execution of the program until the
input (GPIO 17) changes from a high state to a low state. This is connected to the
MCP23017 INT A pin, so it can tell us when its GPIO extender input(s) have changed.
The poll(2) system call can return an error if the program has handled a signal. The
error returned will be EINTR when this happens (as discussed in Chapter 9 of Raspberry
Pi Hardware Reference [Apress, 2014], section “Error EINTR”). If the program detects that
^C has been pressed (is_signaled is true), then it exits, returning -1, to allow the main
program to exit.
A value of rc=1 is returned if /sys/class/gpio17/value has a changed value to be
read. Before returning from gpio_poll(), a read(2) of any unread data is performed.
This is necessary so that the next call to poll(2) will block until new data is available.
39
Chapter 2 ■ MCP23017 GPIO Extender
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
39
40
41
42
43
44
45
46
47
40
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ sysgpio.c : Open/configure /sys GPIO pin
∗
∗ Here we must open the /sys/class/gpio/gpio17/value and do a
∗ poll(2) on it, so that we can sense the MCP23017 interrupts.
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
typedef enum {
gp_export = 0,
gp_unexport,
gp_direction,
gp_edge,
gp_value
} gpio_path_t;
/∗ /sys/class/gpio/export ∗/
/∗ /sys/class/gpio/unexport ∗/
/∗ /sys/class/gpo%d/direction ∗/
/∗ /sys/class/gpio%d/edge ∗/
/∗ /sys/class/gpio%d/value ∗/
/∗
∗ Internal : Create a pathname for type in buf.
∗/
static const char ∗
gpio_setpath(int pin,gpio_path_t type,char ∗buf,unsigned bufsiz) {
static const char ∗paths[] = {
"export", "unexport", "gpio%d/direction",
"gpio%d/edge", "gpio%d/value" };
int slen;
strncpy(buf,"/sys/class/gpio/",bufsiz);
bufsiz –= (slen = strlen(buf));
snprintf(buf+slen,bufsiz,paths[type],pin);
return buf;
}
/∗
∗ Open /sys/class/gpio%d/value for edge detection
∗/
static int
gpio_open_edge(int pin) {
char buf[128];
FILE ∗f;
int fd;
:
/∗ Export pin: /sys/class/gpio/export ∗/
gpio_setpath(pin,gp_export,buf,sizeof buf);
f = fopen(buf, "w");
assert(f);
fprintf(f,"%d\n",pin);
fclose(f);
Chapter 2 ■ MCP23017 GPIO Extender
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/∗ Direction: /sys/class/gpio%d/direction ∗/
gpio_setpath(pin,gp_direction,buf,sizeof buf);
f = fopen(buf,"w");
assert(f);
fprintf(f,"in\n");
fclose(f);
/∗ Edge: /sys/class/gpio%d/edge ∗/
gpio_setpath(pin,gp_edge,buf,sizeof buf);
f = fopen(buf,"w");
assert(f);
fprintf(f,"falling\n");
fclose(f);
/∗ Value: /sys/class/gpio%d/value ∗/
gpio_setpath(pin,gp_value,buf,sizeof buf);
fd = open(buf,O_RDWR);
return fd;
}
/∗
∗ Close (unexport) GPIO pin :
∗/
static void
gpio_close(int pin) {
char buf[128];
FILE ∗f ;
/∗ Unexport: /sys/class/gpio/unexport ∗/
gpio_setpath(pin,gp_unexport,buf,size of buf);
f = fopen(buf,"w");
assert(f);
fprintf(f,"%d\n",pin);
fclose(f);
}
/∗
∗ This routine will block until the open GPIO pin has changed
∗ value. This pin should be connected to the MCP23017 /INTA
∗ pin.
∗/
static int
gpio_poll(int fd) {
unsigned char buf[32];
struct pollfd polls;
int rc;
41
Chapter 2 ■ MCP23017 GPIO Extender
95
polls.fd = fd;
/∗ /sys/class/gpio17/value ∗/
96
polls.events = POLLPRI;
/∗ Exceptions ∗/
97
98
do
{
99
rc = poll(&polls,1,–1); /∗ Block ∗/
100
if ( is_signaled )
101
return –1;
/∗ Exit if ^Creceived ∗/
102
} while ( rc < 0 && errno == EINTR );
103
104
(void)read(fd,buf,sizeof buf);
/∗ Clear interrupt ∗/
105
return 0;
106 }
107
108 /∗ End sysgpio.c ∗/
Example Run
The first time you run the program, you might encounter an error:
$ ./mcp23017
Opening /dev/i2s−1: No such file or directory
Check that the i2c−dev & i2c−bcm2708 kernel modules are loaded.
Aborted
$
As the program states in the error message, it is unable to open the I2C driver,
because the I2C kernel modules have not been loaded. See Chapter 12 of Raspberry Pi
Hardware Reference (Apress, 2014) for modprobe information.
The following is a successful session. After the program is started, the program
pauses after issuing the message “Monitoring for MCP23017 input changes.” At this point,
the program is in the poll(2) system call, waiting to be notified of an interrupt from the
MCP23017 peripheral. If you open another session, you can confirm that little or no CPU
resource is consumed by this.
$ sudo ./mcp23017
Monitoring for MCP23017 input changes :
Outputs :
Input change
Outputs :
Input change
Outputs :
Input change
Outputs :
Input change
Outputs :
42
:
:
:
:
0F0F
flags
070F
flags
070F
flags
070F
flags
070F
8000 values 70F0
8000 values F0F0
8000 values F0F0
8000 values F0F0
Chapter 2 ■ MCP23017 GPIO Extender
Input change :
Outputs :
Input change :
Outputs :
flags 8000 values F0F0
070F
flags 8000 values 70F0
0F0F
^C
$
While this was running, I carefully grounded pin 28 of the MCP28017 chip, which is
input GPA7. This is reflected immediately in the message:
Input change : flags 8000 values 70F0
The flags value reported as 8000 is decoded next, showing that GPA7 did indeed
change in value:
INTFA
INTFB
7
6
5
4
3
2
1
0
7
6
5
4
3
2
1
0
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
The value reported as 70F0 is from the INTCAPx register pair:
INTCAPA
INTCAPB
7
6
5
4
3
2
1
0
7
6
5
4
3
2
1
0
0
1
1
1
0
0
0
0
1
1
1
1
0
0
0
0
This shows us that the GPA7 pin is found in the zero state at the time of the interrupt.
All remaining inputs show high (0s indicate output pins).
While I grounded the GPA7 pin only once, you can see from the session output that
several events occur. This is due to the bounce of the wire as it contacts. You’ll also note
that some events are lost during this bounce period. Look at the input events:
1
2
3
4
5
6
Input
Input
Input
Input
Input
Input
change:
change:
change:
change:
change:
change:
flags
flags
flags
flags
flags
flags
8000
8000
8000
8000
8000
8000
values
values
values
values
values
values
70F0
F0F0
F0F0
F0F0
F0F0
70F0
43
Chapter 2 ■ MCP23017 GPIO Extender
Each interrupt correctly shows that pin GPA7 changed. But look closely at the
captured values for the inputs:
1.
The captured level of the input is 0 (line 1).
2.
The captured level change of the input is now 1 (line 2).
3.
The next input change shows a captured level of 1 (line 3).
How does the state of an input change from a 1 to a 1? Clearly what happens is that
the input GPA7 changes to low but returns to high by the time the interrupt is captured.
Push button, switch, and key bounces can occur often with a variety of pulse widths,
in the range of microseconds to milliseconds, until the contact stabilizes (off or on). The
very action of pushing a button can initiate a series of a thousand pulses. Some pulses
will be too short for the software to notice, so it must be prepared to deal with this.
Sometimes electronics circuits are applied to eliminate “key debouncing” so that the
software will see a clean and singular event. This is what is accomplished in Chapter 8,
where a flip-flop is used.
Response Times
You should be aware of the interrupt notification limitations provided by the poll(2)
system call. The input lines could easily change faster than the Raspberry Pi can respond.
Consider the process involved:
1.
An input GPIO extender pin changes in value.
2.
The MCP23017 device activates INT A by bringing it from a
high to a low (a few cycles later).
3.
The Raspberry Pi’s GPIO 17 pin sees a falling input level
change.
4.
The device driver responds to the GPIO pin change by
servicing an interrupt and then notifies the application
waiting in poll(2).
5.
The application sends some I2C traffic to query the INTFA and
INTFB flag registers in the MCP23017.
6.
Registers GPIOA and GPIOB must also be read to clear the
interrupt, involving more I/O on the I2C bus.
Consequently, there is considerable delay in sensing a GPIO change and the clearing
of the device interrupt.
An informal test using select(2) purely for delay purposes (no file descriptors)
required a minimum of approximately 150 μs on a 700 MHz Pi. The poll(2) call is likely
to be nearly identical. Attempting to set smaller timed delays bottomed out near
150 μs. This suggests that the quickest turnaround for reacting to an INT signal from the
MCP23017 will be 150 μs (excluding the time needed to actually service the registers in
44
Chapter 2 ■ MCP23017 GPIO Extender
the peripheral). This means that the absolute maximum number of interrupts per second
that can be processed will be 6,600.
To estimate the effects of the IC2 bus, let’s do some additional simplified
calculations. The I2C bus operates at 100 kHz on the Raspberry Pi (for more information,
see Chapter 12 of Raspberry Pi Hardware Reference [Apress, 2014]). A single byte requires
8 data bits and an acknowledgment bit. This requires about 90 μs per byte. To read one
MCP23017 register requires the following:
1.
The peripheral’s address byte to be sent (1 byte).
2.
The MCP23017 register address to be sent (1 byte).
3.
The MCP23017 responds back with 1 byte.
This requires 3 × 90 = 270 μs, just to read one register. Add to this the following:
1.
Both interrupt flag registers INTFA and INTFB must be read.
2.
Both GPIOA and GPIOB registers must be read, to clear the
interrupt.
So, ignoring the start and stop bits, this requires 4 × 270 = 1.08 ms to respond to one
input level change. (This also ignores the effect of multiple peripherals on the bus.) This,
added to the minimum of about 150 μs overhead for poll(2), leads to the minimum
response time of about 1.08 + 0.150 = 1.23 ms. This results in a practical limit of about 800
signal changes per second.
Because of the response-time limitations, it is recommended that the INTCAPx
register values be ignored. By the time the application can respond to a signal change, it
may have changed back again. This is why the program presented uses the values read in
GPIOA and GPIOB, rather than the captured values in the INTCAPx. But if your application
needs to know the state at the time of the event, the INTCAPx register values are available.
Some reduced I2C overhead can be attained by tuning the I/O operations. For
example, with use of the auto-increment address feature of the MCP23017, it is possible
to read both INTFx flags and GPIOx registers in one ioctl(2) call:
T = t addr + t register + t INTFA + t INTFB + t register + tGPIOA + tGPIOB
= 7 ´ 90
= 0.630 ms
That approach reduces the I2C time to approximately 7-byte times. The total
turnaround time can then be reduced to about 0.63 + 0.150 = 0.78 ms.
45
Chapter 3
Nunchuk-Mouse
This chapter’s project is about attaching the Nintendo Wii Nunchuk to the Raspberry Pi.
The Nunchuk has two buttons; an X-Y joystick; and an X, Y, and Z accelerometer. The
sensor data is communicated through the I2C bus. Let’s have some fun using a Nunchuk
as a pointing device for the X Window System desktop.
Project Overview
The challenge before us breaks down into two overall categories:
•
The I2C data communication details of the Nunchuk device
•
Inserting the sensed data into the X Window System desktop
event queue
Since you’ve mastered I2C communications in other parts of this book, we’ll focus
more on the Nunchuk technical details. The remainder of the chapter looks at the Linux
uinput API that will be used for the X-Windows interface. The final details cover a small
but critical X-Windows configuration change, to bring about the Nunchuk-Mouse.
Nunchuk Features
The basic physical and data characteristics of the Nunchuk are listed in Table 3-1.50
Table 3-1. Nunchuk Controls and Data Characteristics
User-Interface Features
Bits
Data
Hardware/Chip
C Button
1
Boolean
Membrane switch
Z button
1
Boolean
Membrane switch
X-Y joystick
8x2
Integers
30 kW potentiometers
X, Y, and Z accelerometer
10x3
Integers
ST LIS3L02 series
47
Chapter 3 ■ Nunchuk-Mouse
For application as a mouse, the C and Z buttons fill in for the left and right mouse
buttons. The joystick is used to position the mouse cursor.
While the Nunchuk normally operates at a clock rate of 400 kHz, it works just fine at
the Raspberry Pi’s 100 kHz I2C rate.
■■Note
I encourage you to experiment with the accelerometer.
Connector Pinout
There are four wires, two of which are power and ground (some units may have two
additional wires, one that connects to the shield and the other to the unused center pin).
The remaining two wires are used for I2C communication (SDA and SCL). The connections
looking into the cable-end connector are shown in Table 3-2.
Table 3-2. Nuncheck Cable Connections
SCL
GND
+3.3 V
N/C
SDA
The Nunchuk connector is annoyingly nonstandard. Some folks have rolled their
own adapters using a double-sided PCB to mate with the inner connections. Others have
purchased adapters for around $6. Cheap Nunchuk clones may be found on eBay for
about half that price. With the growing number of clone adapters becoming available at
more-competitive prices, there is less reason to cut off the connector.
■■Tip
Beware of Nunchuk forgeries and nonfunctional units.
If you do cut off the connector, you will quickly discover that there is no standard
wire color scheme. The only thing you can count on is that the pins are laid out as in
Table 3-2. If you have a genuine Wii Nunchuk, the listed wire colors in Table 3-3 might be
valid. The column labeled Clone Wire lists the wire colors of my own clone’s wires. Yours
will likely differ.
48
Chapter 3 ■ Nunchuk-Mouse
Table 3-3. Nunchuk Connector Wiring
Pin
Wii Wire
CloneWire†
Description
P1
Gnd
White
White
Ground
P1-25
SDA
Green
Blue
Data
P1-03
+3.3 V
Red
Red
Power
P1-01
SCL
Yellow
Green
Clock
P1-05
Clone wire colors vary!
†
Before you cut that connector off your clone, consider that you’ll need to trace the
connector to a wire color. Cut the cable, leaving about 3 inches of wire for the connector.
Then you can cut the insulation off and trace the pins to a wire by using an ohmmeter
(or by looking inside the cable-end connector).
Figure 3-1 shows the author’s clone Nunchuk with the connector cut off. In place
of the connector, solid wire ends were soldered on and a piece of heat shrink applied
over the solder joint. The solid wire ends are perfect for plugging into a prototyping
breadboard.
Figure 3-1. Nunchuk with wire ends
Testing the Connection
Once you’ve hooked up the Nunchuk to the Raspberry Pi, you’ll want to perform some
simple tests to make sure it is working. The first step is to make sure your I2C drivers are
loaded:
$ lsmod | grep i2c
i2c_bcm2708
i2c_dev
3759
5620
0
0
49
Chapter 3 ■ Nunchuk-Mouse
If you see these modules loaded, you’re good to go. Otherwise, manually load them now:
$ sudo modprobe i2c−bcm2708
$ sudo modprobe i2c−dev
Assuming the Raspberry Pi rev 2.0+, you’ll use I2C bus 1 (see Chapter 12 of Raspberry
Pi Hardware Reference [Apress, 2014] if you’re not sure). Scan to see whether your
Nunchuk is detected:
$ sudo i2cdetect −y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00:
−− −− −− −− −− −− −− −− −− −− −− −− −−
10: −− −− −− −− −− −− −− −− −− −− −− −− −− −− −− −−
20: −− −− −− −− −− −− −− −− −− −− −− −− −− −− −− −−
30: −− −− −− −− −− −− −− −− −− −− −− −− −− −− −− −−
40: −− −− −− −− −− −− −− −− −− −− −− −− −− −− −− −−
50: −− −− 52 −− −− −− −− −− −− −− −− −− −− −− −− −−
60: −− −− −− −− −− −− −− −− −− −− −− −− −− −− −− −−
70: −− −− −− −− −− −− −− −−
If the Nunchuk is working, it will show up in this display at address 0x52. With the
hardware verified, it is time to move on to the software.
Nunchuk I2C Protocol
The Nunchuk contains a quirky little controller that communicates through the I2C bus.
In order to know where to store bytes written to it, the first byte must be an 8-bit register
address. In other words, each write to the Nunchuk requires the following:
•
One register address byte, followed by
•
Zero or more data bytes to be written to sequential locations
Thus for write operations, the first byte sent to the Nunchuk tells it where to start.
Any following write bytes received are written with the register address incremented.
■■Tip
Don’t confuse the register address with the Nunchuk’s I2C address of 0x52.
It is possible to write the register address and then read bytes instead. This procedure
specifies the starting location of data bytes to be read.
A quirky aspect of the Nunchuk controller is that there must be a short delay between
writing the register address and reading the data. Performing the write followed by an
immediate read does not work. Writing data immediately after the register address does
succeed, however.
■■Note The Nunchuk uses I2C address 0x52.
50
Chapter 3 ■ Nunchuk-Mouse
Encryption
The Nunchuk is designed to provide an encrypted link. However, that can be disabled by
initializing it a certain way.60 The defeat procedure is as follows:
1.
Write 0x55 to Nunchuk location 0xF0.
2.
Pause.
3.
Write 0x00 to Nunchuk location 0xFB.
The following illustrates the message sequence involved. Notice that this is
performed as two separate I2C write operations:
Write
F0
55
Pause
Write
-
FB
00
Once this is successfully performed, all future data is returned unencrypted.
Read Sensor Data
The whole point of the Nunchuk is to read its sensor data. When requested, it returns 6
bytes of data formatted as shown in Table 3-4.
Table 3-4. Nunchuk Data
Byte
Bits
Description
1
Analog stick x axis value
2
Analog stick y axis value
3
X acceleration bits 9:2
4
Y acceleration bits 9:2
5
Z acceleration bits 9:2
6
0
Z button pressed (active low)
1
C button pressed (active low)
3:2
X acceleration bits 1:0
5:4
Y acceleration bits 1:0
7:6
Z acceleration bits 1:0
51
Chapter 3 ■ Nunchuk-Mouse
Some of the data is split over multiple bytes. For example, the X acceleration bits
9:2 are obtained from byte 3. The lowest 2 bits are found in byte 6, in bits 3 and 2. These
together form the 9-bit X acceleration value.
To retrieve this data, we are always required to tell the Nunchuk where to begin. So
the sequence always begins with a write of offset 0x00 followed by a pause:
Write
Pause
Read 6 bytes
00
-
01
02
03
04
05
06
The Nunchuk doesn’t allow us to do this in one ioctl(2) call, using two I/O
messages. A write of 0 must be followed by a pause. Then the 6 data bytes can be read as
a separate I2C read operation. If the pause is too long, however, the Nunchuk controller
seems to time out, resulting in incorrect data being returned. So we must do things the
Nunchuk way.
Linux uinput Interface
While reading the Nunchuk is fun, we need to apply it to our desktop as a mouse. We
need to insert mouse events based on what we read from it.
The Linux uinput driver allows programmers to develop nonstandard input drivers
so that events can be injected into the input stream. This approach allows new input
streams to be added without changing application code. Certainly the Nunchuk qualifies
as a nonstandard input device!
A problem with this uinput API is its general lack of documentation. The best
information available on the Internet seems to be from these three online sources:
•
“Getting started with uinput: the user level input subsystem”
http://thiemonge.org/getting-started-with-uinput
•
“Using uinput driver in Linux- 2.6.x to send user input”
http://www.einfochips.com/download/dash_jan_tip.pdf
•
“Types” http://www.kernel.org/doc/Documentation/input/
event-codes.txt
The only other source of information seems to be the device driver source code itself:
drivers/input/misc/uinput.c
The example program provided in this chapter can help pull all the necessary details
together for you.
52
Chapter 3 ■ Nunchuk-Mouse
Working with Header Files
The header files required for the uinput API include the following:
#include
#include
#include
To compile code, making use of I2C, you also need to install the libi2c development
library, if you have not done that already:
$ sudo apt-get install libi2c-dev
Opening the Device Node
The connection to the uinput device driver is made by opening the device node:
/dev/uinput
The following is an example of the required open(2) call:
int fd;
fd = open("/dev/uinput",O_WRONLY|O_NONBLOCK);
if ( fd < 0 ) {
perror("Opening /dev/uinput");
...
Configuring Events
In order to inject events, the driver must be configured to accept them. Each call to
ioctl(2) in the following code enables one class of events based on the argument event.
The following is a generalized example:
int rc;
unsigned long event = EV_KEY;
rc = ioctl(fd,UI_SET_EVBIT,event);
assert(!rc);
The list of UI_SET_EVBIT event types is provided in Table 3-5. The most commonly
needed event types are EV_SYN, EV_KEY, and EV_REL (or EV_ABS).
53
Chapter 3 ■ Nunchuk-Mouse
Table 3-5. List of uinput Event Types
Macro
From Header File input.h53]
Description
EV_SYN
Event synchronization/separation
EV_KEY
Key/button state changes
EV_REL
Relative axis mouse-like changes
EV_ABS
Absolute axis mouse-like changes
EV_MSC
Miscellaneous events
EV_SW
Binary (switch) state changes
EV_LED
LED on/off changes
EV_SND
Output to sound devices
EV_REP
For use with autorepeating devices
EV_FF
Force feedback commands to input device
EV_PWR
Power button/switch event
EV_FF_STATUS
Receive force feedback device status
■■Caution Do not or the event types together. The device driver expects each event type
to be registered separately.
Configure EV_KEY
Once you have registered your intention to provide EV_KEY events, you need to register all
key codes that might be used. While this seems a nuisance, it does guard against garbage
being injected by an errant program. The following code registers its intention to inject an
Escape key code:
int rc;
rc = ioctl(fd,UI_SET_KEYBIT,KEY_ESC);
assert(!rc);
To configure all possible keys, a loop can be used. But do not register key code 0
(KEY_RESERVED) nor 255; the include file indicates that code 255 is reserved for the special
needs of the AT keyboard driver.
54
Chapter 3 ■ Nunchuk-Mouse
int rc;
unsigned long key;
for ( key=1; key<255; ++key ) {
rc = ioctl(fd,UI_SET_KEYBIT,key);
assert(!rc);
}
Mouse Buttons
In addition to key codes, the same ioctl(2,UI_SET_KEYBIT) call is used to register
mouse, joystick, and other kinds of button events. This includes touch events from
trackpads, tablets, and touchscreens. The long list of button codes is defined in header file
linux/input.h. The usual suspects are shown in Table 3-6.
Table 3-6. Key Event Macros
Macro
Synonym
Description
BTN_LEFT
BTN_MOUSE
Left mouse button
BTN_RIGHT
Right mouse button
BTN_MIDDLE
Middle mouse button
BTN_SIDE
Side mouse button
The following example shows the application’s intention to inject left and right
mouse button events:
int rc;
rc = ioctl(fd,UI_SET_KEYBIT,BTN_LEFT);
assert(!rc);
rc = ioctl(fd,UI_SET_KEYBIT,BTN_RIGHT);
assert(!rc);
Configure EV_REL
In order to inject EV_REL events, the types of relative movements must be registered in
advance. The complete list of valid argument codes is shown in Table 3-7. The following
example indicates an intention to inject x and y relative axis movements:
rc = ioctl(fd,UI_SET_RELBIT,REL_X);
assert(!rc);
rc = ioctl(fd,UI_SET_RELBIT,REL_Y);
assert(!rc);
55
Chapter 3 ■ Nunchuk-Mouse
Table 3-7. UI_SET_RELBIT Options
Macro
Intention
REL_X
Send relative X changes
REL_Y
Send relative Y changes
REL_Z
Send relative Z changes
REL_RX
x-axis tilt
REL_RY
y- axis tilt
REL_RZ
z- axis tilt
REL_HWHEEL
Horizontal wheel change
REL_DIAL
Dial-turn change
REL_WHEEL
Wheel change
REL_MISC
Miscellaneous
Configure EV_ABS
While we don’t use the EV_ABS option in this project, it may be useful to introduce this
feature at this point. This event represents absolute cursor movements, and it too requires
registration of intentions. The complete list of EV_ABS codes is defined in linux/input.h.
The usual suspects are defined in Table 3-8.
Table 3-8. Absolute Cursor Movement Event Macros
Macro
Description
ABS_X
Move X to this absolute X coordinate
ABS_Y
Move Y to this absolute Y coordinate
The following is an example of registering intent for absolute x- and y-axis events:
int rc;
rc = ioctl(fd,UI_SET_ABSBIT,ABS_X);
assert(!rc);
rc = ioctl(fd,UI_SET_ABSBIT,ABS_X);
assert(!rc);
56
Chapter 3 ■ Nunchuk-Mouse
In addition to registering your intentions to inject these events, you need to define
some coordinate parameters. The following is an example:
struct uinput_user_dev uinp;
uinp.absmin[ABS_X] = 0;
uinp.absmax[ABS_X] = 1023;
uinp.absfuzz[ABS_X] = 0;
uinp.absflat[ABS_X] = 0;
uinp.absmin[ABS_Y] = 0;
uinp.absmax[ABS_Y] = 767;
uinp.absfuzz[ABS_Y] = 0;
uinp.absflat[ABS_Y] = 0;
These values must be established as part of your ioctl(2,UI_DEV_CREATE)
operation, which is described next.
Creating the Node
After all registrations with the uinput device driver have been completed, the final step is
to create the uinput node. This will be used by the receiving application, in order to read
injected events. This involves two programming steps:
1.
Write the struct uinput_user_dev information to the file
descriptor with write(2).
2.
Perform an ioctl(2,UI_DEV_CREATE) to cause the uinput
node to be created.
The first step involves populating the following structures:
struct input_id
__u16
__u16
__u16
__u16
};
{
bustype;
vendor;
product;
version;
struct uinput_user_dev {
char
name[UINPUT_MAX_NAME_SIZE];
struct input_id id;
int
ff_effects_max;
int
absmax[ABS_CNT];
int
absmin[ABS_CNT];
int
absfuzz[ABS_CNT];
int
absflat[ABS_CNT];
};
57
Chapter 3 ■ Nunchuk-Mouse
An example populating these structures is provided next. If you plan to inject
EV_ABS events, you must also populate the abs members, mentioned in the “Configure
EV_ABS” section.
struct uinput_user_dev uinp;
int rc;
memset(&uinp,0,sizeof uinp);
strncpy(uinp.name,"nunchuk",UINPUT_MAX_NAME_SIZE);
uinp.id.bustype = BUS_USB;
uinp.id.vendor = 0x1;
uinp.id.product = 0x1;
uinp.id.version = 1;
uinp.absmax[ABS_X] = 1023; /∗ EV_ABS only ∗/
...
//
//
rc = write(fd,&uinp,sizeof(uinp));
assert(rc == sizeof(uinp));
The call to write(2) passes all of this important information to the uinput driver.
Now all that remains is to request a device node to be created for application use:
int rc;
rc = ioctl(fd,UI_DEV_CREATE);
assert(!rc);
This step causes the uinput driver to make a device node appear in the pseudo
directory /dev/input. An example is shown here:
$ ls –l /dev/input
total 0
drwxr−xr−x 2 root root
drwxr−xr−x 2 root root
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
crw−rw−−−T 1 root input
120
120
13, 64
13, 65
13, 66
13, 67
13, 63
13, 32
13, 33
Dec
Dec
Dec
Dec
Dec
Feb
Dec
Dec
Feb
31 1969 by−id
31 1969 by−path
31 1969 event0
31 1969 event1
31 1969 event2
23 13:40 event3
31 1969 mice
31 1969 mouse0
23 13:40 mouse1
The device /dev/input/event3 was the Nunchuck’s created uinput node, when the
program was run.
58
Chapter 3 ■ Nunchuk-Mouse
Posting EV_KEY Events
The following code snippet shows how to post a key down event, followed by a key up
event:
1 static void
2 uinput_postkey(int fd,unsigned key) {
3
struct input_event ev;
4
int rc;
5
6
memset(&ev,0,sizeof(ev));
7
ev.type = EV_KEY;
8
ev.code = key;
9
ev.value = 1;
10
11
rc = write(fd,&ev,sizeof(ev));
12
assert(rc == sizeof(ev));
13
14
ev.value = 0;
15
rc = write(fd,&ev,sizeof(ev));
16
assert(rc == sizeof(ev));
17 }
From this example, you see that each event is posted by writing a suitably initialized
input_event structure. The example illustrates that the member named type was set to
EV_KEY, code was set to the key code, and a keypress was indicated by setting the member
value to 1 (line 9).
To inject a key up event, value is reset to 0 (line 14) and the structure is written again.
Mouse button events work the same way, except that you supply mouse button codes
for the code member. For example:
memset(&ev,0,sizeof(ev));
ev.type = EV_KEY;
ev.code = BTN_RIGHT;
ev.value = 1;
/∗ Right click ∗/
Posting EV_REL Events
To post a relative mouse movement, we populate the input_event as a type EV_REL. The
member code is set to the type of event (REL_X or REL_Y in this example), with the value
for the relative movement established in the member value:
static void
uinput_movement(int fd,int x,inty) {
struct input_event ev;
int rc;
59
Chapter 3 ■ Nunchuk-Mouse
memset(&ev,0,sizeof(ev));
ev.type = EV_REL;
ev.code = REL_X;
ev.value = x;
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
ev.code = REL_Y;
ev.value = y;
rc = write(fd,&ev,sizeof(ev));
assert (rc == sizeof(ev));
}
Notice that the REL_X and REL_Y events are created separately. What if you want the
receiving application to avoid acting on these separately? The EV_SYN event helps out in
this regard (next).
Posting EV_SYN Events
The uinput driver postpones delivery of events until the EV_SYN event has been injected.
The SYN_REPORT type of EV_SYN event causes the queued events to be flushed out and
reported to the interested application. The following is an example:
static void
uinput_syn(int fd) {
struct input_event ev;
int rc;
memset(&ev,0,sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
}
For a mouse relative movement event, for example, you can inject a REL_X and REL_Y,
followed by a SYN_REPORT event to have them seen by the application as a group.
Closing uinput
There are two steps involved:
60
1.
Destruction of the /dev/input/event%d node
2.
Closing of the file descriptor
Chapter 3 ■ Nunchuk-Mouse
The following example shows both:
int rc;
rc = ioctl(fd,UI_DEV_DESTROY);
assert(!rc);
close(fd);
Closing the file descriptor implies the ioctl(2,UI_DEV_DESTROY) operation.
The application has the option of destroying the device node while keeping the file
descriptor open.
X-Window
The creation of our new uinput device node is useful only if our desktop system is
listening to it. Raspbian Linux’s X-Windows system needs a little configuration help to
notice our Frankenstein creation. The following definition can be added to the
/usr/share/X11/xorg.config.d directory. Name the file 20-nunchuk.conf:
# Nunchuck event queue
Section "InputClass"
Identifier "Raspberry Pi Nunchuk"
Option "Mode" "Relative"
MatchDevicePath "/dev/input/event3"
Driver "evdev"
EndSection
# End 20−nunchuk.conf
This configuration change works only if your Nunchuk uinput device shows up
as /dev/input/event3. If you have other specialized input device creations on your
Raspberry Pi, it could well be event4 or some other number. See the upcoming section
“Testing the Nunchuk” for troubleshooting information.
Restart your X-Windows server to have the configuration file noticed.
■■Tip Normally, your Nunchuk program should be running already. But the X-Window
server will notice it when the Nunchuk does start.
61
Chapter 3 ■ Nunchuk-Mouse
Input Utilities
When writing uinput event-based code, you will find the package input-utils to be
extremely helpful. They can be installed from the command line as follows:
$ sudo apt−get install input−utils
The following commands are installed:
lsinput(8): List uinput devices
input-events(8): Dump selected uinput events
input-kbd(8): Keyboard map display
This chapter uses the first two utilities: lsinput(8) and input-events(8).
Testing the Nunchuk
Now that the hardware, drivers, and software are ready, it is time to exercise the Nunchuk.
Unfortunately, there is no direct way for applications to identify your created uinput
node. When the Nunchuk program runs, the node may show up as /dev/input/event3
or some other numbered node if event3 already exists. If you wanted to start a Nunchuk
driver as part of the Linux boot process, you need to create a script to edit the file with the
actual device name. The affected X-Windows config file is as follows:
/usr/share/X11/xord.conf.d/20-nunchuk.conf
The script (shown next) determines which node the Nunchuk program created. The
following is an example run, while the Nunchuk program was running:
$ ./findchuk
/dev/input/event3
When the node is not found, the findchuk script exits with a nonzero code and
prints a message to stderr:
$ ./findchuk
Nunchuk uinput device not found.
$ echo $?
1
62
Chapter 3 ■ Nunchuk-Mouse
The findchuk script is shown here:
#!/bin/bash
######################################################################
# Find the Nunchuck
######################################################################
#
# This script locates the Nunchuk uinput device by searching the
# /sys/devices/virtual/input pseudo directory for names of the form:
# input[0_9]∗. For all subdirectories found, check the ./name pseudo
# file, which will contain "nunchuk". Then we derive the /dev path
# from a sibling entry named event[0_9]∗. That will tell use the
# /dev/input/event%d pathname, for the Nunchuk.
DIR=/sys/devices/virtual/input
set_eu
# Top level directory
cd "$DIR"
find . −type d −name 'input[0−9]∗' | (
set −eu
while read dirname ; do
cd "$DIR/$dirname"
if [−f "name"] ; then
set +e
name=$(cat name)
set −e
if [ $(cat name) = nunchuk ] ; then
event="/dev/input/$ (ls−devent[0−9]∗)"
echo $event
exit 0
# Found it
fi
fi
done
echo "Nunchuk uinput device not found." >&2
exit 1
)
# End findchuk
63
Chapter 3 ■ Nunchuk-Mouse
Testing ./nunchuk
When you want to see what Nunchuk data is being received, you can add the
-d command-line option:
$ ./nunchuk −d
Raw nunchuk data: [83] [83] [5C] [89] [A2] [63]
.stick_x = 0083 (131)
.stick_y = 0083 (131)
.accel_x = 0170 (368)
.accel_y = 0226 (550)
.accel_z = 0289 (649)
.z_button= 0
.c_button= 0
The first line reports the raw bytes of data that were received. The remainder of the
lines report the data in its decoded form. While the raw data reports the button presses as
active low, the Z and C buttons are reported as 1 in the decoded data. The value in the left
column is in hexadecimal format, while the value in parenthesis is shown in decimal.
Utility lsinputs
When the Nunchuk program is running, you should be able to see the Nunchuk uinput
device in the list:
$ lsinput
...
/dev/input/event2
bustype
: BUS_USB
vendor
: 0x45e
product
: 0x40
version
: 272
name
: "Microsoft Micro soft 3−Button Mou"
phys
: "usb−bcm2708_usb−1.3.4/input0"
uniq
: ""
bitsev
: EV_SYN EV_KEY EV_REL EV_MSC
/dev/input/event3
bustype
:
vendor
:
product
:
version
:
name
:
bits ev
:
BUS_USB
0x1
0x1
1
"nunchuk"
EV_SYN EV_KEY EV_REL
In this example, the Nunchuk shows up as event3.
64
Chapter 3 ■ Nunchuk-Mouse
Utility input-events
When developing uinput-related code, the input-events utility is a great help. Here we run
it for event3 (the argument 3 on the command line), where the Nunchuk mouse device is:
$ input−events 3
/dev/input/event3
bustype
: BUS_USB
vendor
: 0x1
product
: 0x1
version
: 1
name
: "nunchuk"
bits ev
: EV_SYN EV_KEY EV_REL
waiting for events
23:35:15.345105: EV_KEY
23:35:15.345190: EV_SYN
23:35:15.517611: EV_KEY
23:35:15.517713: EV_SYN
23:35:15.833640: EV_KEY
23:35:15.833727: EV_SYN
23:35:16.019363: EV_KEY
23:35:16.019383: EV_SYN
23:35:16.564129: EV_REL
23:35:16.564213: EV_REL
23:35:16.564261: EV_SYN
...
BTN_LEFT (0x110) pressed
code=0 value=0
BTN_LEFT (0x110) released
code=0 value=0
BTN_RIGHT (0x111) pressed
code=0 value=0
BTN_RIGHT (0x111) released
code=0 value=0
REL_X −1
REL_Y 1
code=0 value=0
The Program
The code for nunchuk.c is presented on the following pages. The source code for timed_
wait.c is shown in Chapter 1. We’ve covered the I2C I/O in other chapters. The only thing
left to note is the difficulty of providing a smooth interface for events produced by the
Nunchuk. Here are a few hints for the person who wants to experiment:
1.
If the mouse moves too quickly, one major factor is the timed
delay used. The timed_wait() call in line 107 spaces out read
events for the Nunchuk (currently 15 ms). This also lightens
the load on the CPU. Reducing this time-out increases the
number of Nunchuk reads and causes more uinput events to
be injected. This speeds up the mouse pointer.
2.
The function curve() in line 349 attempts to provide a
somewhat exponential movement response. Small joystick
excursions should be slow and incremental. More-extreme
movements will result in faster mouse movements.
65
Chapter 3 ■ Nunchuk-Mouse
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
39
40
66
3.
The Z button is interpreted as the left-click button, while the
C button is the right-click button.
4.
No keystrokes are injected by this program, but it can be
modified to do so. The function uinput_postkey() on line 244
can be used for that purpose.
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ nunchuk.c: Read events from nunchuck and stuff as mouse events
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "timed_wait.c"
static int is_signaled = 0;
static int i2c_fd = −1;
static int f_debug = 0;
typedef struct {
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
} nunchuk_t;
char stick_x;
char stick_y;
accel_x;
accel_y;
accel_z;
z_button:1;
c_button:1;
char raw[6];
/∗ Exit program if signaled ∗/
/∗ Open/dev/i2c−1 device ∗/
/∗ True to print debug messages ∗/
/∗
/∗
/∗
/∗
/∗
/∗
/∗
/∗
Joystick X ∗/
Joystick Y ∗/
Accel X ∗/
Accel Y ∗/
Accel Z ∗/
Z button ∗/
C button ∗/
Raw received data ∗/
/∗
∗ Open I2C bus and check capabilities:
∗/
static void
i2c_init(const char ∗node) {
Chapter 3 ■ Nunchuk-Mouse
41
unsigned long i2c_funcs = 0;
/∗ Support flags ∗/
42
int rc;
43
44
i2c_fd = open(node,O_RDWR);
/∗ Open driver/dev/i2s−1 ∗/
45
if ( i2c_fd < 0 ) {
46
perror("Opening/dev/i2s−1");
47
puts("Check that the i2c−dev & i2c−bcm2708 kernel modules"
48
"are loaded.");
49
abort();
50
}
51
52
/∗
53
∗ Make sure the driver supports plain I2C I/O:
54
∗/
55
rc = ioctl(i2c_fd,I2C_FUNCS,&i2c_funcs);
56
assert(rc >= 0);
57
assert(i2c_funcs & I2C_FUNC_I2C);
58 }
59
60 /∗
61
∗ Configure the nunchuk for no encryption:
62
∗/
63 static void
64 nunchuk_init(void) {
65
static char init_msg1[] = {0xF0, 0x55};
66
static char init_msg2[] = {0xFB, 0x00};
67
struct i2c_rdwr_ioctl_data msgset;
68
struct i2c_msg iomsgs[1];
69
int rc;
70
71
iomsgs[0].addr = 0x52;
/∗ Address of Nunchuk ∗/
72
iomsgs[0].flags = 0;
/∗ Write ∗/
73
iomsgs[0].buf = init_msg1; /∗ Nunchuk 2 byte sequence ∗/
74
iomsgs[0].len = 2;
/∗ 2 bytes ∗/
75
76
msgset.msgs = iomsgs;
77
msgset.nmsgs = 1;
78
79
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
80
assert(rc == 1);
81
82
timed_wait(0,200,0);
/∗ Nunchuk needs time ∗/
83
84
iomsgs[0].addr = 0x52;
/∗ Address of Nunchuk ∗/
85
iomsgs[0].flags = 0;
/∗ Write ∗/
86
iomsgs[0].buf = init_msg2; /∗ Nunchuk 2 byte sequence ∗/
87
iomsgs[0].len = 2;
/∗ 2 bytes ∗/
88
67
Chapter 3 ■ Nunchuk-Mouse
89
msgset.msgs = iomsgs;
90
msgset.nmsgs = 1;
91
92
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
93
assert(rc == 1);
94 }
95
96 /∗
97
∗ Read nunchuk data :
98
∗/
99 static int
100 nunchuk_read(nunchuk_t ∗data) {
101
struct i2c_rdwr_ioctl_data msgset;
102
struct i2c_msg iomsgs[1];
103
char zero[1] = {0x00};
/∗ Written byte ∗/
104
unsigned t;
105
int rc;
106
107
timed_wait(0,15000,0);
108
109
/∗
110
∗ Write the nunchuk register address of 0x00:
111
∗/
112
iomsgs[0].addr = 0x52;
/∗ Nunchuk address ∗/
113
iomsgs[0].flags = 0;
/∗ Write ∗/
114
iomsgs[0].buf = zero;
/∗ Sending buf ∗/
115
iomsgs[0].len = 1;
/∗ 1 byte ∗/
116
117
msgset.msgs = iomsgs;
118
msgset.nmsgs = 1;
119
120
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
121
if ( rc < 0 )
122
return −1;
/∗ I /O error ∗/
123
124
timed_wait(0,200,0);
/∗ Zzzz, nunchuk needs time ∗/
125
126
/∗
127
∗ Read 6 bytes starting at 0x00:
128
∗/
129
iomsgs[0].addr = 0x52;
/∗ Nunchuk address ∗/
130
iomsgs[0].flags = I2C_M_RD;
/∗ Read ∗/
131
iomsgs[0].buf = (char ∗)data−>raw; /∗ Receive raw bytes here ∗/
132
iomsgs[0].len = 6;
/∗ 6 bytes ∗/
133
134
msgset.msgs = iomsgs;
135
msgset.nmsgs = 1;
136
68
Chapter 3 ■ Nunchuk-Mouse
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
rc = ioctl(i2c_fd,I2C_RDWR,&msgset);
if ( rc < 0 )
return −1;
/∗ Failed ∗/
data−>stick_x
data−>stick_y
data−>accel_x
data−>accel_y
data−>accel_z
=
=
=
=
=
data−>raw[0];
data−>raw[1];
data−>raw[2] << 2;
data−>raw[3] << 2;
data−>raw[4] << 2;
t = data−>raw[5];
data−>z_button = t
data−>c_button = t
t >>= 2;
data−>accel_x |= t
t >>= 2;
data−>accel_y |= t
t >>= 2;
data−>accel_z |= t
return 0;
& 1 ? 0 : 1;
& 2 ? 0 : 1;
& 3;
& 3;
& 3;
}
/∗
∗ Dump the nunchuk data:
∗/
static void
dump_data(nunchuk_t ∗data) {
int x;
printf("Raw nunchuk data : ");
for ( x=0; x<6; ++x )
printf("[%02X]",data−>raw[x]);
putchar('\n');
printf(".stick_x =
printf(".stick_y =
printf(".accel_x =
printf(".accel_y =
printf(".accel_z =
printf(".z_button=
printf(".c_button=
%04X (%4u)\n",data−>stick_x,data−>stick_x);
%04X (%4u)\n",data−>stick_y,data−>stick_y);
%04X (%4u)\n",data−>accel_x,data−>accel_x);
%04X (%4u)\n",data−>accel_y,data−>accel_y);
%04X (%4u)\n",data−>accel_z,data−>accel_z);
%d\n",data−>z_button);
%d\n\n",data−>c_button);
}
/∗
∗ Close the I2C driver :
∗/
69
Chapter 3 ■ Nunchuk-Mouse
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
70
static void
i2c_close(void) {
close(i2c_fd);
i2c_fd = −1;
}
/∗
∗ Open a uinput node :
∗/
static int
uinput_open(void) {
int fd;
struct uinput_user_dev uinp;
int rc;
fd = open("/dev/uinput",O_WRONLY|O_NONBLOCK);
if ( fd < 0 ) {
perror("Opening/dev/uinput");
exit(1);
}
rc = ioctl(fd,UI_SET_EVBIT,EV_KEY);
assert(!rc);
rc = ioctl(fd,UI_SET_EVBIT,EV_REL);
assert(!rc);
rc = ioctl(fd,UI_SET_RELBIT,REL_X);
assert(!rc);
rc = ioctl(fd,UI_SET_RELBIT,REL_Y);
assert(!rc);
rc = ioctl(fd,UI_SET_KEYBIT,KEY_ESC);
assert(!rc);
ioctl(fd,UI_SET_KEYBIT,BTN_MOUSE);
ioctl(fd,UI_SET_KEYBIT,BTN_TOUCH);
ioctl(fd,UI_SET_KEYBIT,BTN_MOUSE);
ioctl(fd,UI_SET_KEYBIT,BTN_LEFT);
ioctl(fd,UI_SET_KEYBIT,BTN_MIDDLE);
ioctl(fd,UI_SET_KEYBIT,BTN_RIGHT);
memset(&uinp,0,sizeof uinp);
strncpy(uinp.name,"nunchuk",UINPUT_MAX_NAME_SIZE);
uinp.id.bustype = BUS_USB;
uinp.id.vendor = 0x1;
uinp.id.product = 0x1;
uinp.id.version = 1;
Chapter 3 ■ Nunchuk-Mouse
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
rc = write(fd,&uinp,sizeof(uinp));
assert(rc == sizeof(uinp));
rc = ioctl(fd,UI_DEV_CREATE);
assert(!rc);
return fd;
}
/∗
∗ Post keystroke down and keystroke up events:
∗ (unused here but available for your own experiments)
∗/
static void
uinput_postkey(int fd,unsigned key) {
struct input_event ev;
int rc;
memset(&ev,0,sizeof(ev));
ev.type = EV_KEY;
ev.code = key;
ev.value = 1;
/∗ Key down ∗/
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
ev.value = 0;
/∗ Key up ∗/
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
}
/∗
∗ Post a synchronization point :
∗/
static void
uinput_syn(int fd) {
struct input_event ev;
int rc;
memset(&ev,0,sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
}
71
Chapter 3 ■ Nunchuk-Mouse
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
72
/∗
∗ Synthesize a button click :
∗
up_down
1=up,
0=down
∗
buttons
1=Left, 2=Middle, 4=Right
∗/
static void
uinput_click(int fd,int up_down,int buttons) {
static unsigned codes[] = {BTN_LEFT, BTN_MIDDLE, BTN_RIGHT};
struct input_event ev;
int x;
memset(&ev,0,sizeof(ev));
/∗
∗ Button down or up events:
∗/
for ( x=0; x < 3; ++x ) {
ev.type = EV_KEY;
ev.value = up_down; /∗ Button Up or down ∗/
if ( buttons & (1 << x) ) { /∗ Button 0, 1 or 2 ∗/
ev.code = codes[x];
write(fd,&ev,sizeof(ev));
}
}
}
/∗
∗ Synthesize relative mouse movement:
∗/
static void
uinput_movement(int fd,int x,int y) {
struct input_event ev;
int rc;
memset(&ev,0,sizeof(ev));
ev.type = EV_REL;
ev.code = REL_X;
ev.value = x;
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
ev.code = REL_Y;
ev.value = y;
rc = write(fd,&ev,sizeof(ev));
assert(rc == sizeof(ev));
}
Chapter 3 ■ Nunchuk-Mouse
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/∗
∗ Close uinput device :
∗/
static void
uinput_close(int fd) {
int rc;
rc = ioctl(fd,UI_DEV_DESTROY);
assert(!rc);
close(fd);
}
/∗
∗ Signal handler to quit the program:
∗/
static void
sigint_handler(int signo) {
is_signaled = 1;
/∗ Signal to exit program ∗/
}
/∗
∗ Curve the adjustment :
∗/
static int
curve(int relxy) {
int ax = abs(relxy); /∗ abs (relxy) ∗/
int sgn = relxy < 0 ? −1 : 1;
/∗ sign (relxy) ∗/
int mv = 1;
/∗ Smallest step ∗/
if ( ax >
mv
else if (
mv
else if (
mv
else if (
mv
return mv
}
100 )
= 10;
ax > 65 )
= 7;
ax > 35 )
= 5;
ax > 15 )
= 2;
∗ sgn;
/∗ Take large steps ∗/
/∗ 2nd smallest step ∗/
/∗
∗ Main program:
73
Chapter 3 ■ Nunchuk-Mouse
367 ∗/
368 int
369 main(int
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
74
argc,char ∗∗argv) {
int fd, need_sync, init = 3;
int rel_x=0, rel_y = 0;
nunchuk_t data0, data, last;
if ( argc > 1 && !strcmp(argv [1]," −d") )
f_debug = 1;
/∗ Enable debug messages ∗/
(void)uinput_postkey;
/∗ Suppress compiler warning about unused ∗/
i2c_init("/dev/i2c−1"); /∗ Open I2C controller ∗/
nunchuk_init();
/∗ Turn off encrypt ion ∗/
signal(SIGINT,sigint_handler); /∗ Trap on SIGINT ∗/
fd = uinput_open();
/∗ Open/dev/uinput ∗/
while ( !is_signaled ) {
if ( nunchuk_read(&data) < 0 )
continue;
if ( f_debug )
dump_data(&data);
/∗ Dump nunchuk data ∗/
if ( init > 0 && !data0.stick_x && !data0.stick_y ) {
data0 = data; /∗ Save initial values ∗/
last = data;
−−init;
continue;
}
need_sync = 0;
if ( abs(data.stick_x − data0.stick_x) > 2
|| abs(data.stick_y − data0.stick_y) > 2) {
rel_x = curve (data.stick_x − data0.stick_x);
rel_y = curve (data.stick_y − data0.stick_y);
if ( rel_x || rel_y ) {
uinput_movement(fd,rel_x,−rel_y);
need_sync = 1;
}
}
if ( last.z_button != data.z_button ) {
uinput_click(fd, data.z_button,1);
need_sync = 1;
}
Chapter 3 ■ Nunchuk-Mouse
415
if ( last.c_button != data.c_button ) {
416
uinput_click(fd,data.c_button,4);
417
need_sync = 1;
418
}
419
420
if ( need_sync )
421
uinput_syn(fd);
422
last = data;
423
}
424
425
putchar('\n');
426
uinput_close(fd);
427
i2c_close();
428
return 0;
429 }
430
431 /∗ End nunchuk.c ∗/
75
Chapter 4
Real-Time Clock
The Dallas Semiconductor DS1307 Real-Time Clock is the perfect project for the
Raspberry Pi, Model A. Lacking a network port, the Model A cannot determine the
current date and time when it boots up. A 3 V battery attached to the DS1307 will keep its
internal clock running for up to 10 years, even when the Pi is powered off. If you have a
Model B, don’t feel left out. There is no reason that you can’t try this project too; a Model
B not connected to a network could use the DS1307.
DS1307 Overview
The pinout of the DS1307 chip is provided in Figure 4-1. The chip is available in PDIP-8
form or in SO format (150 mils). Hobbyists who like to build their own will prefer the
PDIP-8 form.
Figure 4-1. DS1307 pinout
A crystal is wired between pins 1 and 2 (X1 and X2). The battery powers the chip
through pin 3 and flows to ground (pin 4). This keeps the clock alive while the main
power is absent. When there is power, it is supplied through pin 8 (VCC). The I2C
communication occurs via pins 5 and 6. Pin 7 provides an optional output clock signal or
can operate as an open collector output.
While you could build this circuit yourself, you can find fully assembled PCB
modules using the DS1307 on eBay for as little as $2.36 (with free shipping). These are
available as Buy It Now offers, so you don’t have to waste your time trying to win auctions.
Just keep in mind that some are not shipped with a 3 V battery. (Check the product.
77
Chapter 4 ■ Real-Time Clock
The Tiny RTC I used came with a 3.6 V battery.) It is claimed that there are mailing
restrictions on batteries to certain countries. So you may want to shop for suitable
batteries while you wait for the mail.
■■Tip Buying a fresh battery ahead of time is recommended, as batteries often arrive
exhausted.
Figure 4-2 shows a PCB unit that I purchased through eBay. This unit came paired
with the AT24C32 EEPROM chip. The auction was labeled “Tiny RTC I2C Modules.” You
don’t have to use this specific PCB, of course. The wiring for each chip is fairly basic and
can be prototyped on a breadboard if you can’t find one. But if you do choose this PCB, a
modification is required before you attach it to the Raspberry Pi.
Figure 4-2. An assembled DS1307 PCB purchased through eBay
■■Caution I2C pull-up resistors R2 and R3 must be removed before wiring the PCB to the
Raspberry Pi. Also, if you plan to attach the SQW/OUT output of this PCB to a GPIO input, be
sure to track down and remove its pull-up resistor as well.
78
Chapter 4 ■ Real-Time Clock
Pins X1 and X2
These pins are for connection to a 32.768 kHz crystal. The datasheet states that the
“internal oscillator circuitry is designed for operation with a crystal having a specified
load capacitance (CL) of 12.5 pF.”
Pin SQW/OUT
This is an open-drain output from the DS1307 chip that you can choose to ignore if you
like. It can be used as follows:
•
A GPIO-like output. If you plan on wiring this to a Pi GPIO pin, be
sure to remove the pull-up resistor (on PCB) first. The +5 V from
the pull-up will damage the Pi.
•
A square wave clock output, at one of the following programmable
frequencies:
··
1 Hz
··
4.096 kHz
··
8.192 kHz
··
32.768 kHz
The datasheet lists a current rating for output when low:
Parameter
Symbol
Logic 0 Output (IOL = 5 mA)
VOL
Min
Typ
Max
Units
0.4
Volts
Without more-specific information, we arrive at the conclusion that a given logic pin
is capable of sinking a maximum of 5 mA (SDA). While doing so, the maximum voltage
appearing at the pin is 0.4 V. This is well under the 0.8 V maximum, for the VIL voltage
level of the Raspberry Pi.
The datasheet indicates that the SQW/OUT pin is an open-drain output. As such,
you can use a pull-up to +3.3 V or +5 V as your interface requires. It could be used to drive
a small LED at 1 Hz, if the current is limited to less than 5 mA (although the datasheet
doesn’t indicate the current capability of this open-drain transistor). Alternatively, it can
be used to drive other external logic with a pull-up resistor.
The datasheet indicates that the SQW/OUT pin can pulled as high as 5.5 V, even
when VCC is lower in voltage (such as +3.3 V.) This is safe, provided that these higher
voltages never connect to the Pi’s GPIO pins.
79
Chapter 4 ■ Real-Time Clock
Power
If you’ve looked at the datasheet for the DS1307 before, you might be wondering about
the power supply voltage. The datasheet lists it as a +5 V part, and by now you are well
aware that the Pi’s GPIO pins operate at +3 V levels. The DC operating conditions are
summarized here:
Parameter
Symbol
Min
Typ
Max
Units
Supply voltage
VCC
4.5
5.0
5.5
Volts
Battery voltage
VBAT
2.0
3.5
Volts
It is tempting to consider that the PCB might operate at +3.3 V, given that the battery
in the unit is 3 V. However, that will not work because the DS1307 chip considers a VCC
lower than 1.25 × VBAT = 3.75 V to be a power-fail condition (for a typical operation). When
it senses a power fail, it relies on battery operation and will cease to communicate by I2C,
among other things. Power-fail conditions are summarized here:
Parameter
Symbol
Min
Typ
Max
Units
Power-fail voltage
VPF
1.26×VBAT
1.25×VBAT
1.284×VBAT
Volts
The note in the datasheet indicates that the preceding figures were measured when
VBAT = 3 V. So these figures will likely deviate when the battery nears expiry. Given the
power-fail conditions, we know that the device must be powered from a +5 V power
supply. But the only connections made to the Raspberry Pi are the SDA and SCL I2C lines.
So let’s take a little closer look at those and see if we can use them.
3-Volt Compatibility
The SCL line is always driven by the master, as far as the DS1307 is concerned. This
means that SCL is always seen as an input signal by the RTC clock chip. All we have to
check here is whether the Raspberry Pi will meet the input-level requirements. Likewise,
the AT24C32 EEPROM’s SCL pin is also an input only.
The SDA line is driven by both the master (Pi) and the DS1307 chip (slave). The SDA
is driven from the Pi as a 3 V signal, so again we need to make certain that the DS1307 will
accept those levels. But what about the DS1307 driving the SDA line? The Pi must not
see +5 V signals.
The DS1307 datasheet clearly states that “the SDA pin is open drain, which requires an
external pull-up resistor.” The Raspberry Pi already supplies the pull-up resistor to +3.3 V.
This means that the DS1307’s open drain will allow the line to be pulled up to +3.3 V for
the high logic level, when the output transistor is in the off state. When the transistor is
on, it simply pulls the SDA line down to ground potential. Thus with
open-drain operation, we can interoperate with the Raspberry Pi. A check of the AT24C32
EEPROM datasheets leads to the same conclusion.
80
Chapter 4 ■ Real-Time Clock
Logic Levels
How do the I2C logic levels compare between the Raspberry Pi and the DS1307?
Signal
Raspberry Pi
DS1307
VIL
£ 0.8 volts
£ 0.8 volts
VIH
³ 1.3 volts
³ 2.2 volts
The VIL figure matches perfectly for both sides. As long as the Raspberry Pi provides
a high level exceeding 2.2 V, the DS1307 chip should read high levels just fine. Given
that the Pi’s pull-up resistor is connected to +3.3 V, there is very little reason to doubt
problems meeting the DS1307 VIH requirement.
To summarize, we can safely power the DS1307 from +5 V, while communicating
between it and the Raspberry Pi at +3 V levels. The Pi already supplies pull-up resistors
for the SCL and SDA lines, and these are attached to +3.3 V. If, however, you choose to use
other GPIO pins to bit-bang I2C (say), you’ll need to provide these pull-up resistors (they
must go to only +3.3 V).
Tiny RTC Modifications
In the preceding section, you saw that even though the DS1307 is a +5 V part, the SDA
pin is driven by an open-drain transistor. With the Raspberry Pi tying the SDA line to +3.3
V, the highest voltage seen will be exactly that. The open-drain transistor can only pull
it down to ground (this also applies to the AT24C32 EEPROM). Both chips have the SCL
pins as inputs (only), which are not pulled high by the chips themselves.
If you purchased a PCB like the one I used, however, be suspicious of pull-up
resistors! I knew that the parts would support +3.3 V I2C bus operation before the PCB
arrived in the mail. However, I was suspicious of added pull-up resistors. So when the
PCB arrived, I quickly determined that the PCB did indeed include pull-up resistors
connected to the +5 V supply. The extra +5 V pull-up resistors must be tracked down and
removed for use with the Raspberry Pi.
Checking for Pull-up Resistors
There are two methods to test for pull-up resistors: a DMM resistance check and a voltage
reading. I recommend that you apply them both.
Since this modification is important to get correct, the following sections will walk
you through the two different procedures in detail.
81
Chapter 4 ■ Real-Time Clock
Performing a DMM Resistance Check
Use these steps for the DMM resistance check:
1.
Attach one probe of your DMM (reading kΩ) to the +5 V line
of the PCB.
2.
Attach the other probe to the SDA line and take the resistance
reading.
3.
Reverse the leads if you suspect diode action.
On my PCB, I read 3.3 kΩ. Reversing the DMM leads should read the same (proving
only resistance). Performing the same test with the SCL input, I also read 3.3 kΩ.
Performing a Voltage Reading
Do not skip this particular test. The result of this test will tell you whether your Raspberry
Pi will be at risk.
1.
Hook up your PCB to the +5 V supply it requires, but do not
attach the SDA/SCL lines to the Pi yet. Just leave them loose
for measuring with your DMM.
2.
With the DMM negative probe grounded, measure the
voltage seen at the PCB’s SDA and SCL inputs. If there is no
pull-up resistor involved, you should see a low reading of
approximately 0.07 V. The reading will be very near ground
potential.
On my unmodified PCB, these readings were +5 V because of the 3.3 kΩ pull-up
resistors. If this also applies to your PCB unit, a modification is required.
Performing a Tiny RTC Modification
If you have the exact same PCB that I used, you can simply remove resistors R2 and R3 (but
I would double-check with the preceding tests). These resistors are shown in Figure 4-3.
Carefully apply a soldering iron to sweep them off the PCB. Make sure no solder is left,
shorting the remaining contacts. I highly recommend that you repeat the tests to make
sure you have corrected the pull-up problem and test for short circuits.
82
Chapter 4 ■ Real-Time Clock
Figure 4-3. R2 and R3 of the Tiny RTC I2C PCB
Working with Other PCB Products
If you have a different PCB product, you may have optional resistors that you can
leave uninstalled. Even though they may be optional, someone might have done you the
favor of soldering them in. So make sure you check for that (use the earlier tests).
The Adafruit RTC module (http://www.adafruit.com/products/264) is reportedly
sometimes shipped with the 2.2 kΩ resistors installed. For the Raspberry Pi, they must be
removed.
Locating the Pull-up Resistors
Even if you don’t have a schematic for your PCB product, you will need to locate the pullup resistors. Since there aren’t many components on the PCB to begin with, they tend to
be easy to locate:
1.
Observe static electricity precautions.
2.
Attach one DMM (kΩ) probe to the +5 V input to the PCB.
3.
Look for potential resistors on the component side.
4.
Locate all resistors that have one lead wired directly to the
+5 V supply (resistance will read 0 Ω). These will be the prime
suspects.
5.
Now attach your DMM (range kΩ) to the SDA input. With the
other DMM probe, check the opposite ends of the resistors,
looking for readings of 0 Ω. You should find one resistor
end (the other end of the resistor will have been previously
identified as connected to the +5 V supply).
6.
Likewise, test the SCL line in the same manner as step 5.
7.
Double-check: take a resistance reading between the SDA
input and the +5 V supply. You should measure a resistance of
2 to 10 kΩ, depending on the PCB manufacturer. You should
get the same reading directly across the resistor identified.
8.
Repeat step 7 for the SCL line.
83
Chapter 4 ■ Real-Time Clock
If you’ve done this correctly, you will have identified the two resistors that need to be
removed. If you plan to interface the SQW/OUT pin to a Pi GPIO, you’ll want to remove
the pull-up used on that as well.
DS1307 Bus Speed
The DS1307 datasheet lists the maximum SCL clock speed at 100 kHz:
Parameter
Symbol
Min
SCL clock frequency
fSCL
0
Typ
Max
Units
100
kHz
The Raspberry Pi uses 100 kHz for its I2C clock frequency (see Chapter 12 of
Raspberry Pi Hardware Reference [Apress, 2014] for more information). The specification
also states that there is no minimum frequency. If you wanted to reserve the provided I2C
bus for use with other peripherals (perhaps at a higher frequency), you could bit-bang
interactions with the DS1307 by using another pair of GPIO pins. (Pull-up resistors to
+3.3 V will be required; the internal pull-up resistors are not adequate.) That is an exercise
left for you.
Now that we have met power, signaling, and clock-rate requirements, “Let’s light this
candle!”
RTC and RAM Address Map
The DS1307 has 56 bytes of RAM in addition to the real-time clock registers. I/O with
this chip includes an implied address register, which ranges in value from 0x00 to 0x3F.
The address register will wrap around to zero after reaching the end (don’t confuse the
register address with the I2C peripheral address).
■■Note The DS1307 RTC uses I2C address 0x68.
The address map of the device is illustrated in Table 4-1. The date and time
components are BCD encoded. In the table, 10s represents the tens digit, while
1s represents the ones digit.
84
Chapter 4 ■ Real-Time Clock
Table 4-1. DS1307 Register Map
Format
Address
Register
7
6
0x00
Seconds
CH
10s
1s
0x01
Minutes
0
10s
1s
0x02
Hours
0
24hr
10s
12hr
PM
10s
1s
0
0
5
4
0x03
Weekday
0
0
0
0x04
Day
0
0
10s
0x05
Month
0
0
0
0x06
Year
10s
0x07
Control
OUT
0x08
RAM 00
byte
…
0x3F
3
2
1
0
1s
1s
1s
10s
1s
1s
0
0
SQWE
0
0
RS1
RS2
…
RAM 55
byte
The components of the register map are further described in Table 4-2. Bit CH allows
the host to disable the oscillator and thus stop the clock. This also disables the SQW/OUT
waveform output (when SQWE=1). Bit 6 of the Hours register determines whether 12- or
24-hour format is used. When in 12-hour format, bit 5 becomes an AM/PM indicator.
Table 4-2. RTC Register Map Components
Bit
CH
Meaning
0
Clock running
1
Clock (osc) halt
24hr
0
24-hour format
12hr
1
12-hour format
RS1
RS0
Meaning
OUT
0
SQW/OUT = Low
0
0
1 Hz
1
SQW/OUT = High
0
1
4.096 kHz
0
SQW/OUT is OUT
1
0
8.192 kHz
1
SQW/OUT is SQW
1
1
32.768 kHz
SQWE
85
Chapter 4 ■ Real-Time Clock
The Control register at address 0x07 determines how the SQW/OUT pin behaves.
When SQWE=1, a square wave signal is produced at the SQW/OUT pin. The frequency is
selected by bits RS1 and RS0. In this mode, the OUT setting is ignored.
When SQWE=0, the SQW/OUT pin is set according to the bit placed in OUT (bit 7 of the
control register). In this mode, the pin behaves as an open-drain GPIO output pin.
Reading Date and Time
When the DS1307 device is being read, a snapshot of the current date and time is made
when the I2C start bit is seen. This copy operation allows the clock to continue to run
while returning a stable date/time value back to the master. If this were not done, time
components could change between reading bytes. The application should therefore
always read the full date/time set of registers as one I/O operation. The running clock
does not affect reading the control register or the RAM locations.
I2C Communication
The DS1307 registers and RAM can be written randomly, by specifying an initial starting
register address, followed by 1 or more bytes to be written. The register address is
automatically incremented with each byte written and wraps around to 0. The DS1307
slave device will ACK each byte as it is received, continuing until the master writes a stop
bit (P). The first byte sent is always the peripheral’s I2C address, which should not be
confused with the selected peripheral’s register address (that immediately follows). The
DS1307 I2C address is always 0x68. The general form of the write message is shown here:
The DS1307 supports multibyte reads. You can read multiple bytes from the DS1307
simply by starting with an I2C start bit (S), and peripheral address sent as a read request.
The slave will then serve up bytes one after another for the master. Receiving terminates
when the master sends a NAK.
86
Chapter 4 ■ Real-Time Clock
If you want to be certain that the register address is established with a known value,
you should always issue a write request first. In the preceding diagram, the write request
immediately follows the start bit (S). Only the peripheral’s register address byte is written
out prior to the repeating start bit (RS), which follows.
After the RS bit, the peripheral address is transmitted once more to re-engage
the DS1307, but this time as a read request. From that point on, the master reads
bytes sequentially from the DS13017 until a NAK is sent. The final stop bit (P) sent by
the master ends the exchange. This peripheral provides us with a good example of a
multimessage I/O.
This is demonstrated in lines 27 to 45 of the program ds1307get.c, in the upcoming
pages. The entire I/O is driven by the structures iomsgs[0] and iomsgs[1]. Structure
iomsgs[0] directs the driver to write to peripheral address 0x68 and writes 1 0x00 data
byte out to it. This establishes the RTC’s internal register with a value of 0x00. The read
request is described in iomsgs[1], which is a read from the same peripheral 0x68, for
8 bytes. (Only 7 bytes are strictly required for the date and time, but we read the
additional control byte anyway.)
The data structure is laid out in C terms in the file ds1307.h. An optional exercise for
you is to add a command-line option to ds1307set to stop the clock and turn it on again
using the ch bit (line 8 of ds1307.h).
Source module i2c_common.c has the usual I2C open/initialization and close
routines in it.
Wiring
Like any I2C project for the Pi, you’ll wire the SDA and SCL lines as follows:
Pre Rev 2.0
GPIO
Line
Rev 2.0+
GPIO
Line
P1
0
SDA0
2
SDA1
P1-03
1
SCL0
3
SCL1
P1-05
The DS1307 PCB (or chip) is powered from the +5 V supply. Prior to attaching
it to the Raspberry Pi, it is a good idea to power the DS1307 and measure the voltage
appearing on its SDA and SCL lines. Both should measure near ground potential. If you
see +5 V instead, stop and find out why.
Running the Examples
Since these programs use the I2C Linux drivers, make sure these kernel modules are
either already loaded, or load them manually now:
$ sudo modprobe i2c-bcm2708
$ sudo modprobe i2c-dev
87
Chapter 4 ■ Real-Time Clock
Program ds1307set.c (executable ds1307set) is used to reset the RTC to a new date/
time value of your choice. For example:
$ ./ds1307set 20130328215900
2013-03-28 21:59:00 (Thursday)
$
This sets the date according to the command-line value, which is in
YYYYMMDDHHMMSS format.
Once the RTC date has been established, you can use the executable ds1307get to
read back the date and time:
$ ./ds1307get
2013-03-28 22:00:37 (Thursday)
$
In this case, a little time had passed between setting the date and reading it. But we
can see that the clock is ticking away.
If you don’t like the date/time format used, you can either change the source code or
set the environment variable DS1307_FORMAT. For example:
$ export DS1307_FORMAT="%a %Y-%m-%d %H:%M:%S"
$ ./ds1307get
Thu 2013-03-28 22:03:38
$
For a description of the date/time format options available, use this:
$ man date
The setting of DS1307_FORMAT also affects the display format used by ds1307set.
The Ultimate Test
The ultimate test is to shut down the Raspberry Pi and turn off its power. Wait a minute
or so to make sure that all of the power has been drained out of every available capacitor.
Then bring up the Pi again and check the date/time with the program ds1307get. Did it
lose any time?
The Startup Script
To put the RTC to good practical use, you’ll want to apply ds1307get at a suitable point in
the Linux startup sequence. You’ll need to wait until the appropriate I2C driver support
is available (or can be arranged). You’ll need to develop a short shell script, using the
88
Chapter 4 ■ Real-Time Clock
DS1307_FORMAT environment variable in order to produce a format suitable for the
console date command. To set the system date (as root), you would use this command:
# date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
The startup script for doing all of this has not been provided here. I don’t want to
spoil your fun when you can develop this yourself. You learn best by doing. Refer to
Chapter 3 of Raspberry Pi System Software Reference (Apress, 2014) if you need some help.
As a further hint, you’ll want to develop a script for the /etc/rc2.d directory, with
a name starting with S and two digits. The digits determine where the script runs in the
startup sequence (you’ll want to make sure your script runs after the system has come up
far enough that I2C drivers are loaded).
Once your startup script is developed, your Raspberry Pi can happily reboot after
days, even years, of being powered off, and still be able to come up with the correct date
and time.
■■Note If you’re running the older Model B, where the I2C bus 0 is used instead of 1,
change line 21 in ds1307set.c and line 21 in ds1307get.c. See Chapter 12 of
Raspberry Pi Hardware Reference (Apress, 2014) for more information.
1 /∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
2
∗ ds1307.h: Common DS1307 types and macro definitions
3
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
4
5
typedef struct {
6
/∗ Register Address 0x00 : Seconds
∗/
7
unsigned char
secs_1s : 4;
/∗ Ones digit : seconds ∗/
8
unsigned char
secs_10s : 3; /∗ Tens digit : seconds ∗/
9
unsigned char
ch : 1;
/∗ CH bit ∗/
10
/∗ Register Address 0x01 : Minutes
∗/
11
unsigned char
mins_1s : 4;
/∗ Ones digit : minutes ∗/
12
unsigned char
mins_10s : 3;
/∗ Tens digit : minutes ∗/
13
unsigned char
mbz_1 : 1;
/∗ Zero bit ∗/
14
/∗ Register Address 0x02 : Hours
∗/
15
unsigned char
hour_1s : 4;
/∗ Ones digit : hours ∗/
16
unsigned char
hour_10s : 2;
/∗ Tens digit : hours
(24 hr mode) ∗/
17
unsigned char
mode_1224 : 1; /∗ Mode bit : 12/24 hour
format ∗/
18
/∗ Register Address 0x03 : Weekday
∗/
19
unsigned char
wkday : 3;
/∗ Day of week (1−7) ∗/
20
unsigned char
mbz_2 : 5;
/∗ Zero bits ∗/
21
/∗ Register Address 0x04 : Day of Month ∗/
22
unsigned char
day_1s : 4;
/∗ Ones digit : day of month
(1−31) ∗/
89
Chapter 4 ■ Real-Time Clock
23
unsigned char
day_10s : 2;
/∗ Tens digit : day of
month ∗/
unsigned char
mbz_3 : 2; /∗ Zero bits ∗/
/∗ Register Address 0x05 : Month ∗/
unsigned char
month_1s : 4; /∗ Ones digit : month (1−12)
24
25
26
∗/
27
unsigned char
month_10s : 1; /∗ Tens digit : month ∗/
28
unsigned char
mbz_4 : 3; /∗ Zero ∗/
29
/∗ Register Address 0x06 : Year ∗/
30
unsigned char
year_1s : 4
/∗ Ones digit : year (00−99)
∗/
31
unsigned char
year_10s : 4;
/∗ Tens digit : year ∗/
32
/∗ Register Address 0x07 : Control ∗/
33
unsigned char
rs0 : 1;
/∗ RS0 ∗/
34
unsigned char
rs1 : 1;
/∗ RS1 ∗/
35
unsigned char
mbz_5 : 2; /∗ Zeros ∗/
36
unsigned char
sqwe : 1; /∗ SQWE ∗/
37
unsigned char
mbz_6 : 2;
38
unsigned char
outbit : 1;
/∗ OUT ∗/
39 } ds1307_rtc_regs;
40
41 /∗ End ds1307 . h ∗/
1 /∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
2 ∗ i2c_common.c : Common I2C Access Functions
3 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
4
5 static int i2c_fd = −1;
/∗ Device node: /dev/i2c−1 ∗/
6 static unsigned long i2c_funcs = 0; /∗ Support flags ∗/
7
8 /∗
9
∗ Open I2C bus and check cap abilities:
10 ∗/
11 static void
12 i2c_init(const char ∗node) {
13
int rc;
14
15
i2c_fd = open(node,O_RDWR);
/∗ Open driver /dev/i2s−1 ∗/
16
if ( i2c_fd < 0 ) {
17
perror("Opening /dev/ i 2 s −1");
18
puts("Check that the i2c−dev & i2c−bcm2708 kernelmodules "
19
" are loaded . " ) ;
20
abort();
21
}
22
23
/∗
24
∗ Make sure the driver suppor tsplain I2C I /O:
90
Chapter 4 ■ Real-Time Clock
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
∗/
rc = ioctl(i2c_fd,I2C_FUNCS,&i2c_funcs);
assert(rc >= 0);
assert(i2c_funcs & I2C_FUNC_I2C);
}
/∗
∗ Close the I2C driver :
∗/
static void
i2c_close(void) {
close(i2c_fd);
i2c_fd = −1;
}
/∗ End i2c_common.c ∗/
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ ds1307set.c : Set real−time DS1307 clock on I2C bus
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "i2c_common.c"
#include "ds1307.h"
/∗ I2C routines ∗/
/∗ DS1307 types ∗/
/∗ Change to i2c−0 if using early Raspberry Pi ∗/
static const char ∗node = "/dev/i2c−1";
/∗
∗ Write [ S ] 0xB0 . . . [P]
∗/
static int
i2c_wr_rtc(ds1307_rtc_regs ∗rtc) {
struct i2c_rdwr_ioctl_data msgset;
struct i2c_msg iomsgs[1];
char buf[sizeof ∗rtc+1];
/∗ Work buffer ∗/
91
Chapter 4 ■ Real-Time Clock
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
92
buf[0] = 0x00;
memcpy(buf+1,rtc,sizeof ∗rtc);
iomsgs[0].addr = 0x68;
iomsgs[0].flags = 0;
iomsgs[0].buf = buf;
iomsgs[0].len = sizeof ∗rtc + 1;
/∗ Register 0x00 ∗/
/∗ Copy RTC info ∗/
/∗
/∗
/∗
/∗
msgset.msgs = &iomsgs[0];
msgset.nmsgs = 1;
DS1307 Address ∗/
Write ∗/
Register + data ∗/
Total msg len ∗/
return ioctl(i2c_fd,I2C_RDWR,&msgset);
}
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ Set the DS1307 real−time clock on the I2C bus :
∗
∗ ./ds1307set YYYYMMDDHHMM[ss]
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
int
main(int argc,char ∗∗argv) {
ds1307_rtc_regs rtc; /∗ 8 DS1307 Register Values ∗/
char buf[32];
/∗ Extraction buffe r ∗/
struct tm t0, t1;
/∗ Unix date / time values ∗/
int v, cx, slen;
char ∗date_format = getenv("DS1307_FORMAT");
char dtbuf[256];
/∗ Formatted date/time ∗/
int rc;
/∗ Return code ∗/
/∗
∗ If no environment variable named DS1307_FORMAT, then
∗ set a default date/time format.
∗/
if ( !date_format )
date_format = "%Y−%m−%d %H:%M:%S (%A) " ;
/∗
∗ Check command line usage :
∗/
if ( argc != 2 | | (slen = strlen(argv[1])) < 12 || slen > 14 ) {
usage: fprintf(stderr,
"Usage : %s YYYYMMDDhhmm[ss]\n",
argv[0]);
exit(1);
}
Chapter 4 ■ Real-Time Clock
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/∗
∗ Make sure every character is a digit in argument 1 .
∗/
for ( cx=0; cx 2099 )
goto usage;
t1.tm_year = v − 1900;
strncpy(buf,argv[1]+4,2)[2] = 0;
/∗ buf[] = "MM" ∗/
if ( sscanf(buf,"%d",&v) != 1 || v <= 0 || v > 12 )
goto usage;
t1.tm_mon = v−1;
/∗ 0 − 11 ∗/
strncpy(buf,argv[1]+6,2)[2] = 0;
/∗ buf[] = "DD" ∗/
if ( sscanf(buf,"%d",&v) != 1 || v <= 0 || v > 31 )
goto usage ;
t1.tm_mday = v;
/∗ 1 − 31 ∗/
strncpy(buf,argv[1]+8,2)[2] = 0;
/∗ buf[] = "hh" ∗/
if ( sscanf(buf,"%d",&v) != 1 || v < 0 || v > 23 )
goto usage;
t1.tm_hour = v;
strncpy(buf,argv[1]+10,2)[2] = 0;
/∗ buf[] = "mm" ∗/
if ( sscanf(buf,"%d",&v) != 1 || v < 0 || v > 59 )
goto usage;
t1.tm_min = v;
if ( slen > 12 ) {
/∗ Optional ss was provided : ∗/
strncpy(buf,argv[1]+12,2)[2] = 0;
/∗ buf[] = "ss" ∗/
if ( sscanf(buf,"%d",&v) != 1 || v < 0 || v > 59 )
goto usage;
93
Chapter 4 ■ Real-Time Clock
125
126
127
128
129
130
131
132
133
134
t1.tm_sec = v;
}
/∗
∗ Check the validity of the date :
∗/
t1.tm_isdst = −1;
/∗ Determine if daylight savings ∗/
t0 = t1;
/∗ Save initial values ∗/
if ( mktime(&t1) == 1L ) {
/∗ t1 is modified ∗/
bad_date : printf("Argument '%s ' is not avalid calendar date.\
n",argv[1]) ;
exit(2);
}
135
136
137
138
/∗
139
∗ If struct t1 was adjusted , then the original date/time
140
∗ values were invalid :
141
∗/
142
if ( t0.tm_year != t1.tm_year || t0.tm_mon != t1.tm_mon
143
|| t0.tm_mday != t1.tm_mday || t0.tm_hour != t1.tm_hour
144
|| t0.tm_min != t1.tm_min || t0.tm_sec != t1.tm_sec )
145
goto bad_date;
146
147
/∗
148
∗ Populate DS1307 registers :
149
∗/
150
rtc.secs_10s = t1.tm_sec / 10;
151
rtc.secs_1s = t1.tm_sec % 10;
152
rtc.mins_10s = t1.tm_min / 10;
153
rtc.mins_1s = t1.tm_min % 10;
154
rtc.hour_10s = t1.tm_hour / 10;
155
rtc.hour_1s = t1.tm_hour % 10;
156
rtc.month_10s = (t1.tm_mon + 1) / 10;
157
rtc.month_1s = (t1.tm_mon + 1) % 10;
158
rtc.day_10s = t1.tm_mday / 10;
159
rtc.day_1s = t1.tm_mday % 10;
160
rtc.year_10s = (t1.tm_year + 1900 − 2000) / 10;
161
rtc.year_1s = (t1.tm_year + 1900 − 2000) % 10;
162
163
rtc.wkday = t1.tm_wday + 1;
/∗ Weekday 1−7 ∗/
164
rtc.mode_1224 = 0;
/∗ Use 24 hour format ∗/
165
166 #if 0
/∗ Change to a 1 for debugging ∗/
167
printf("%d%d−%d%d−%d%d %d%d:%d%d:%d%d (wkday %d )\n",
168
rtc.year_10s,rtc.year_1s,
169
rtc.month_10s,rtc.month_1s,
170
rtc.day_10s,rtc.day_1s,
94
Chapter 4 ■ Real-Time Clock
171
rtc.hour_10s,rtc.hour_1s,
172
rtc.mins_10s,rtc.mins_1s,
173
rtc.secs_10s,rtc.secs_1s,
174
rtc.wkday);
175 #end if
176
rc = i2c_wr_rtc(&rtc );
177
178
/∗
179
∗ Display RTC values submitted :
180
∗/
181
strftime(dtbuf,sizeof dtbuf,date_format,&t1);
182
puts(dtbuf);
183
184
if ( rc < 0 )
185
perror("Writing to DS1307 RTC");
186
else if ( rc != 1 )
187
printf(" Incomplete write : %d msgs of 2written \n",rc);
188
189
i2c_close();
190
return rc ==1? 0 : 4;
191 }
192
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
∗ ds1307get.c : Read real−time DS1307 clock on I2C bus
∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Source Exif Data:
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.4
Linearized : No
Has XFA : No
Language : EN
XMP Toolkit : Adobe XMP Core 5.2-c001 63.139439, 2010/09/27-13:37:26
Create Date : 2014:11:11 07:09:47+05:30
Creator Tool : Adobe InDesign CS6 (Windows)
Modify Date : 2014:11:18 20:23:05+05:30
Metadata Date : 2014:11:18 20:23:05+05:30
Producer : Adobe PDF Library 10.0.1
Keywords :
Format : application/pdf
Description :
Title :
Creator :
Document ID : uuid:5139c87c-7b03-4d32-b0a6-7c52c6e6cc85
Instance ID : uuid:da3dad5e-1b77-43ca-9ee7-d49f24e1726b
Marked : True
Page Layout : SinglePage
Page Mode : UseOutlines
Page Count : 231
Author :
Subject :