Skip to content

Commit 8ce7bdc

Browse files
Start in I2C doc page
1 parent c35001c commit 8ce7bdc

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

docs/.nav.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ nav:
66
- index.md
77
- Getting Started: getting-started
88
- upload-methods.md
9+
- Feature Overviews: feature-overviews
910
- contributing.md
1011
- maintainers.md
1112
- mbed-history-and-mbed-ce.md

docs/feature-overviews/i2c.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# I²C
2+
3+
I²C, short for Inter-Integrated-Circuit, is a two-wire bus commonly used to move data between chips on a circuit board. Mbed supports I2C communication via the `I2C` and `I2CSlave` classes. This doc page will provide an overview of how to use I2C in Mbed OS.
4+
5+
## A brief overview of I²C
6+
7+
![I2C topology diagram](https://www.analog.com/en/_/media/analog/en/landing-pages/technical-articles/i2c-primer-what-is-i2c-part-1-/36684.png?la=en&w=900)
8+
9+
First let's begin with a brief overview of how the I²C bus works in hardware. I²C uses two data lines, SDA (**S**erial **DA**ta) and SCL (**S**erial **CL**ock). Both data lines are *open-drain*, meaning that each I²C device can either connect the line to ground or leave it floating. If no device is connecting the line to ground, a pullup resistor (usually between 1kΩ and 10kΩ) pulls the line up to a logic high. This system means that multiple chips can safely use the bus lines without the risk of one device trying to output a high while another device outputs a low.
10+
11+
In an I²C bus, there must be one or more master devices, which can initiate reads and writes, and one or more slave devices, which can accept reads and writes.
12+
13+
!!! note
14+
Referring to I²C devices as masters and slaves is problematic terminology, which we would like to move away from over time. The accepted newer terms are 'controllers' and 'targets'. However, Mbed OS has not been updated to use the newer terms yet, as this would be a badly breaking change.
15+
16+
### Addressing
17+
18+
Each slave device is identified by a seven-bit address. This address must be passed to Mbed APIs in order to direct reads and writes to the correct location.
19+
20+
!!! info
21+
Each slave device on a bus must have a unique address, which can sometimes be a problem if you want multiple instances of the same chip on an I²C bus. Various methods exist to work around this issue, including chips with pin-configurable addresses and I²C multiplexers.
22+
23+
### Speed
24+
25+
The base I²C standard defines three different speeds:
26+
- Standard mode, which runs at 100kHz
27+
- Fast mode, which runs at 400kHz
28+
- Fast mode plus, which runs at 1MHz
29+
30+
All Mbed devices which support I²C should support at least standard and fast mode. Fast mode plus support depends on the target implementation, and also requires somewhat different hardware (e.g. lower pullup resistors).
31+
32+
It is possible to run I²C at speeds other than these three frequencies (useful, for instance, for debugging signal integrity issues by seeing if behavior is different at a lower frequency). However, some Mbed targets use precalculated timings and may not support arbitrary I²C frequencies.
33+
34+
!!! warning
35+
Due to how I²C works, if multiple devices are sharing a bus, you cannot go faster than the maximum bus speed of any of the devices. Otherwise, slower devices may misinterpret messages that are too fast for them and cause interference on the bus.
36+
37+
For example, if you have two 400kHz devices and one 100kHz device on a bus, you must run the entire bus at 100kHz!
38+
39+
### Transactions
40+
41+
All activity on the I²C bus takes the form of a transaction. Each transaction begins with a start condition, a direction indicator (read/write), and a slave address. If the slave is present on the bus and sees the start operation, it will respond with an acknowledge (ACK) indicating it's there and ready to listen. If no slave responds, this is a **N**ot **ACK**nowledge (NACK), and the master stops the transaction and returns an error to the application.
42+
43+
Once the slave acknowledges a write operation, the master can then send as many bytes as it wishes. The slave has the option to NACK each byte, meaning that a slave device can potentially indicate an error or out-of-data condition by NACKing at some point during the write.
44+
45+
For a read operation, on the other hand, the master supplies the clock and the slave device must output bytes over the bus. The master controls the number of bytes read, and the slave cannot NACK after the read starts.
46+
47+
Once the read or write operation completes, whether successful or with an error, the master must end the transaction. This is done either by sending a Stop condition (which relinquishes the bus and potentially allows another master to initiate a transaction) or a Repeated Start condition, which initiates another transaction without reliquishing the bus.
48+
49+
!!! note
50+
Most I2C target devices don't make a distinction between a stop/start and a repeated start, but a few do! (looking at you U-Blox)
51+
52+
### More Info
53+
The above sections should tell you everything you need to know about I²C in order to use the Mbed `I2C` class and communicate with I2C target devices.
54+
55+
For a deeper dive into I²C, including physical layer signaling, [this page from Analog Devices](https://www.analog.com/en/resources/technical-articles/i2c-primer-what-is-i2c-part-1.html) looks like a good reference! (it's also where I got the above image)
56+
57+
## I2C in Mbed
58+
59+
In Mbed, the `I2C` class allows using the I²C bus as a master device.
60+
61+
There are three different forms of the I²C API usable via this class:
62+
<ul>
63+
<li>Transaction-based I²C</li>
64+
<li>Single-byte I²C</li>
65+
<li>Asynchronous I²C</li>
66+
</ul>
67+
68+
All three of these APIs let you execute I²C operations, but they work differently.
69+
70+
<h1>Transaction-Based API</h1>
71+
72+
The simplest API, which should be appropriate for most use cases, is the transaction-based API, which is
73+
accessed through the \link I2C::read(int address, char *data, int length, bool repeated) read() \endlink and the
74+
\link write(int address, const char *data, int length, bool repeated) write() \endlink functions. These functions
75+
execute an entire I²C transaction (the start condition, address, data bytes, and stop condition) in a single
76+
function call.
77+
78+
The bytes to be read/written are passed in through an array, which requires that you can predict the
79+
size of the data ahead of time. If this information is not known, you may want to use the single-byte API instead
80+
(see below).
81+
82+
Example of using the transaction-based API to read the temperature from an LM75BD:
83+
```cpp
84+
#include "mbed.h"
85+
I2C i2c(I2C_SDA , I2C_SCL);
86+
const int addr7bit = 0x48; // 7-bit I2C address
87+
const int addr8bit = 0x48 << 1; // 8-bit I2C address, 0x90
88+
89+
int main() {
90+
char cmd[2];
91+
while (1) {
92+
cmd[0] = 0x01;
93+
cmd[1] = 0x00;
94+
95+
// read and write takes the 8-bit version of the address.
96+
// set up configuration register (at 0x01)
97+
I2C::Result result = i2c.write(addr8bit, cmd, 2);
98+
99+
if(result != I2C::ACK)
100+
{
101+
// Chip not accessible, handle error....
102+
}
103+
104+
ThisThread::sleep_for(500);
105+
106+
// read temperature register
107+
cmd[0] = 0x00;
108+
i2c.write(addr8bit, cmd, 1, true); // Set repeated to true so that we don't give up the bus after this transactio
109+
i2c.read(addr8bit | 1, cmd, 2);
110+
111+
float tmp = (float((cmd[0]<<8)|cmd[1]) / 256.0);
112+
printf("Temp = %.2f\n", tmp);
113+
}
114+
}
115+
```
116+
117+
118+
<h1>Single-Byte API</h1>
119+
120+
The single-byte API consists of the \link I2C::start() start() \endlink, \link I2C::write_byte() write_byte()
121+
\endlink, \link I2C::read_byte() read_byte() \endlink, and \link I2C::stop() stop() \endlink functions.
122+
With the single-byte API, you have manual control over each condition and data byte put onto the I2C bus.
123+
This is useful for dealing with devices which can return variable amounts of data in one I2C operation,
124+
or when you don't want to create buffers to store the data. However, this API is more verbose than the
125+
transaction-based API and will have a bit more overhead since there's more code executing per byte.
126+
127+
The following is an example that accomplishes the same thing as the above code, but using the single-byte API.
128+
@code
129+
#include "mbed.h"
130+
I2C i2c(I2C_SDA , I2C_SCL);
131+
const int addr7bit = 0x48; // 7-bit I2C address
132+
const int addr8bit = 0x48 << 1; // 8-bit I2C address, 0x90
133+
134+
int main() {
135+
while (1) {
136+
// read and write takes the 8-bit version of the address.
137+
// set up configuration register (at 0x01)
138+
i2c.lock();
139+
i2c.start();
140+
I2C::Result result = i2c.write_byte(addr8bit); // Write address, LSBit low to indicate write
141+
i2c.write_byte(0x01);
142+
i2c.write_byte(0x00);
143+
i2c.stop();
144+
i2c.unlock();
145+
146+
if(result != I2C::ACK)
147+
{
148+
// Chip not accessible, handle error....
149+
}
150+
151+
ThisThread::sleep_for(500);
152+
153+
// Set register to read
154+
i2c.lock();
155+
i2c.start();
156+
i2c.write_byte(addr8bit); // Write address
157+
i2c.write_byte(0x00);
158+
// To create a repeated start condition, we do not call stop() here
159+
160+
i2c.start();
161+
i2c.write_byte(addr8bit | 1); // Write address, LSBit high to indicate read
162+
163+
// Read the two byte temperature word
164+
uint16_t temperatureBinary = 0;
165+
temperatureBinary |= static_cast<uint16_t>(i2c.read_byte(true)) << 8;
166+
temperatureBinary |= static_cast<uint16_t>(i2c.read_byte(false)); // send NACK to indicate last byte
167+
i2c.stop();
168+
i2c.unlock();
169+
170+
float tmp = (float(temperatureBinary) / 256.0);
171+
printf("Temp = %.2f\n", tmp);
172+
}
173+
}
174+
@endcode
175+
176+
\attention If a single I2C object is being shared among multiple threads, you should surround usage of the
177+
single-byte API with \link I2C::lock() lock() \endlink and \link I2C::unlock() unlock() \endlink. This
178+
ensures that a transaction by one thread is not interrupted by another. It may also improve performance
179+
because the backing mutex will not need to be locked for each byte.
180+
181+
<h1>Asynchronous API</h1>
182+
183+
The asynchronous API allows you to run I²C operations in the background. This API is only
184+
available if your device has the I2C_ASYNCH feature. To use this API, use \link I2C::transfer() transfer() \endlink
185+
to start an operation and \link I2C::abort_transfer() abort_transfer() \endlink to stop it. Alternately, use the
186+
\link I2C::transfer_and_wait() transfer_and_wait() \endlink function to block the current thread until
187+
the transfer finishes.
188+
189+
Some devices implement these features using DMA, others use interrupts, so be mindful that there may still be
190+
significant CPU usage if you have multiple and/or high-rate transfers going on.
191+
192+
<h1>A Note about Addressing</h1>
193+
Most I²C devices make use of 7-bit addresses (see <a href="https://www.i2c-bus.org/addressing/">here</a> for details).
194+
Mbed OS, however, works with addresses in 8-bit format, where the least significant bit specifies if the transaction
195+
is a read (1) or a write (0). Due to this, you will generally need to use bitshifts and bitwise ORs when passing
196+
addresses to I2C functions. See the documentation on each function for details.
197+
198+
I²C also has a <a href="https://www.i2c-bus.org/addressing/10-bit-addressing/">10-bit addressing mode</a>, where
199+
the address is sent in two physical bytes on the bus. Some, but not all, Mbed targets support this mode -- refer
200+
to your MCU datasheet and your target's HAL code for details. For 10-bit addresses, use the same format to
201+
pass them to I2C functions -- shift them left by one and set the LSBit to indicate the read/write direction.
202+
On MCUs that do not natively support 10-bit addressing, you can emulate support by using the single-byte API
203+
to send two address bytes; see the linked page above for details.
204+
205+
<h1>Other Info</h1>
206+
207+
The I2C class is thread-safe, and uses a mutex to prevent multiple threads from using it at the same time.
208+
209+
\warning Mbed OS requires that you only create one instance of the I2C class per physical I²C bus on your chip.
210+
This means that if you have multiple sensors connected together on a bus, you must create one I2C object at the
211+
top level and pass it in to the drivers for each sensor. Violating this directive will cause undefined
212+
behavior in your code.

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ markdown_extensions:
2121
- pymdownx.inlinehilite
2222
- pymdownx.snippets
2323
- pymdownx.superfences
24+
- pymdownx.details
25+
- admonition
2426
- attr_list

0 commit comments

Comments
 (0)