summaryrefslogtreecommitdiff
path: root/docs/i2c_driver.md
blob: 9a3c08b90b66822e956e5b82f2a0d81455423e1d (plain)
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
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
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
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
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
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
277
278
279
280
281
282
283
284
285
286
287
288
# I2C Master Driver :id=i2c-master-driver

The I2C Master drivers used in QMK have a set of common functions to allow portability between MCUs.

## Usage :id=usage

In most cases, the I2C Master driver code is automatically included if you are using a feature or driver which requires it, such as [OLED](feature_oled_driver.md).

However, if you need to use the driver standalone, add the following to your `rules.mk`:

```make
I2C_DRIVER_REQUIRED = yes
```

You can then call the I2C API by including `i2c_master.h` in your code.

## I2C Addressing :id=note-on-i2c-addresses

All of the addresses expected by this driver should be pushed to the upper 7 bits of the address byte. Setting
the lower bit (indicating read/write) will be done by the respective functions. Almost all I2C addresses listed 
on datasheets and the internet will be represented as 7 bits occupying the lower 7 bits and will need to be
shifted to the left (more significant) by one bit. This is easy to do via the bitwise shift operator `<< 1`.

You can either do this on each call to the functions below, or once in your definition of the address. For example, if your device has an address of `0x18`:

```c
#define MY_I2C_ADDRESS (0x18 << 1)
```

See https://www.robot-electronics.co.uk/i2c-tutorial for more information about I2C addressing and other technical details.

## AVR Configuration :id=avr-configuration

The following defines can be used to configure the I2C master driver:

|`config.h` Override|Description          |Default |
|-------------------|---------------------|--------|
|`F_SCL`            |Clock frequency in Hz|`400000`|

No further setup is required - just connect the `SDA` and `SCL` pins of your I2C devices to the matching pins on the MCU:

|MCU               |`SCL`|`SDA`|
|------------------|-----|-----|
|ATmega16/32U4     |`D0` |`D1` |
|AT90USB64/128     |`D0` |`D1` |
|ATmega32A         |`C0` |`C1` |
|ATmega328/P       |`C5` |`C4` |

?> The ATmega16/32U2 does not possess I2C functionality, and so cannot use this driver.

## ChibiOS/ARM Configuration :id=arm-configuration

You'll need to determine which pins can be used for I2C -- a an example, STM32 parts generally have multiple I2C peripherals, labeled I2C1, I2C2, I2C3 etc.

To enable I2C, modify your board's `halconf.h` to enable I2C:

```c
#define HAL_USE_I2C TRUE
```

Then, modify your board's `mcuconf.h` to enable the peripheral you've chosen, for example:

```c
#undef STM32_I2C_USE_I2C2
#define STM32_I2C_USE_I2C2 TRUE
```

|`mcuconf.h` Setting         |Description                                                                       |Default|
|----------------------------|----------------------------------------------------------------------------------|-------|
|`STM32_I2C_BUSY_TIMEOUT`    |Time in milliseconds until the I2C command is aborted if no response is received  |`50`   |
|`STM32_I2C_XXX_IRQ_PRIORITY`|Interrupt priority for hardware driver XXX (THIS IS AN EXPERT SETTING)            |`10`   |
|`STM32_I2C_USE_DMA`         |Enable/Disable the ability of the MCU to offload the data transfer to the DMA unit|`TRUE` |
|`STM32_I2C_XXX_DMA_PRIORITY`|Priority of DMA unit for hardware driver XXX (THIS IS AN EXPERT SETTING)          |`1`    |

Configuration-wise, you'll need to set up the peripheral as per your MCU's datasheet -- the defaults match the pins for a Proton-C, i.e. STM32F303.

|`config.h` Overrride    |Description                                                   |Default|
|------------------------|--------------------------------------------------------------|-------|
|`I2C_DRIVER`            |I2C peripheral to use - I2C1 -> `I2CD1`, I2C2 -> `I2CD2` etc. |`I2CD1`|
|`I2C1_SCL_PIN`          |The pin definition for SCL                                    |`B6`   |
|`I2C1_SCL_PAL_MODE`     |The alternate function mode for SCL                           |`4`    |
|`I2C1_SDA_PIN`          |The pin definition for SDA                                    |`B7`   |
|`I2C1_SDA_PAL_MODE`     |The alternate function mode for SDA                           |`4`    |

The following configuration values depend on the specific MCU in use.

### I2Cv1 :id=arm-configuration-i2cv1

* STM32F1xx
* STM32F2xx
* STM32F4xx
* STM32L0xx
* STM32L1xx

See [this page](https://www.playembedded.org/blog/stm32-i2c-chibios/#7_I2Cv1_configuration_structure) for the I2Cv1 configuration structure.

|`config.h` Override|Default         |
|-------------------|----------------|
|`I2C1_OPMODE`      |`OPMODE_I2C`    |
|`I2C1_CLOCK_SPEED` |`100000`        |
|`I2C1_DUTY_CYCLE`  |`STD_DUTY_CYCLE`|

### I2Cv2 :id=arm-configuration-i2cv2

* STM32F0xx
* STM32F3xx
* STM32F7xx
* STM32L4xx

See [this page](https://www.playembedded.org/blog/stm32-i2c-chibios/#8_I2Cv2_I2Cv3_configuration_structure) for the I2Cv2 configuration structure.

|`config.h` Override  |Default|
|---------------------|-------|
|`I2C1_TIMINGR_PRESC` |`0U`   |
|`I2C1_TIMINGR_SCLDEL`|`7U`   |
|`I2C1_TIMINGR_SDADEL`|`0U`   |
|`I2C1_TIMINGR_SCLH`  |`38U`  |
|`I2C1_TIMINGR_SCLL`  |`129U` |

## API :id=api

### `void i2c_init(void)` :id=api-i2c-init

Initialize the I2C driver. This function must be called only once, before any of the below functions can be called.

This function is weakly defined, meaning it can be overridden if necessary for your particular use case:

```c
void i2c_init(void) {
    gpio_set_pin_input(B6); // Try releasing special pins for a short time
    gpio_set_pin_input(B7);
    wait_ms(10); // Wait for the release to happen

    palSetPadMode(GPIOB, 6, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP); // Set B6 to I2C function
    palSetPadMode(GPIOB, 7, PAL_MODE_ALTERNATE(4) | PAL_STM32_OTYPE_OPENDRAIN | PAL_STM32_PUPDR_PULLUP); // Set B7 to I2C function
}
```

---

### `i2c_status_t i2c_transmit(uint8_t address, uint8_t *data, uint16_t length, uint16_t timeout)` :id=api-i2c-transmit

Send multiple bytes to the selected I2C device.

#### Arguments :id=api-i2c-transmit-arguments

 - `uint8_t address`  
   The 7-bit I2C address of the device.
 - `uint8_t *data`  
   A pointer to the data to transmit.
 - `uint16_t length`  
 The number of bytes to write. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-transmit-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_receive(uint8_t address, uint8_t* data, uint16_t length, uint16_t timeout)` :id=api-i2c-receive

Receive multiple bytes from the selected I2C device.

#### Arguments :id=api-i2c-receive-arguments

 - `uint8_t address`  
   The 7-bit I2C address of the device.
 - `uint8_t *data`  
   A pointer to the buffer to read into.
 - `uint16_t length`  
 The number of bytes to read. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-receive-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_write_register(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` :id=api-i2c-write-register

Writes to a register with an 8-bit address on the I2C device.

#### Arguments :id=api-i2c-write-register-arguments

 - `uint8_t devaddr`  
   The 7-bit I2C address of the device.
 - `uint8_t regaddr`  
   The register address to write to.
 - `uint8_t *data`  
   A pointer to the data to transmit.
 - `uint16_t length`  
 The number of bytes to write. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-write-register-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_write_register16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` :id=api-i2c-write-register16

Writes to a register with a 16-bit address (big endian) on the I2C device.

#### Arguments :id=api-i2c-write-register16-arguments

 - `uint8_t devaddr`  
   The 7-bit I2C address of the device.
 - `uint16_t regaddr`  
   The register address to write to.
 - `uint8_t *data`  
   A pointer to the data to transmit.
 - `uint16_t length`  
 The number of bytes to write. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-write-register16-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_read_register(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` :id=api-i2c-read-register

Reads from a register with an 8-bit address on the I2C device.

#### Arguments :id=api-i2c-read-register-arguments

 - `uint8_t devaddr`  
   The 7-bit I2C address of the device.
 - `uint8_t regaddr`  
   The register address to read from.
 - `uint16_t length`  
 The number of bytes to read. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-read-register-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_read_register16(uint8_t devaddr, uint16_t regaddr, uint8_t* data, uint16_t length, uint16_t timeout)` :id=api-i2c-read-register16

Reads from a register with a 16-bit address (big endian) on the I2C device.

#### Arguments :id=api-i2c-read-register16-arguments

 - `uint8_t devaddr`  
   The 7-bit I2C address of the device.
 - `uint16_t regaddr`  
   The register address to read from.
 - `uint16_t length`  
 The number of bytes to read. Take care not to overrun the length of `data`.
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value :id=api-i2c-read-register16-return

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.

---

### `i2c_status_t i2c_ping_address(uint8_t address, uint16_t timeout)` :id=api-i2c-ping-address

Pings the I2C bus for a specific address. 

On ChibiOS a "best effort" attempt is made by reading a single byte from register 0 at the requested address. This should generally work except for I2C devices that do not not respond to a register 0 read request, which will result in a false negative result (unsucessful response to ping attempt).

This function is weakly defined, meaning it can be overridden if necessary for your particular use case:

#### Arguments

 - `uint8_t address`  
   The 7-bit I2C address of the device (ie. without the read/write bit - this will be set automatically).
 - `uint16_t timeout`  
   The time in milliseconds to wait for a response from the target device.

#### Return Value

`I2C_STATUS_TIMEOUT` if the timeout period elapses, `I2C_STATUS_ERROR` if some other error occurs, otherwise `I2C_STATUS_SUCCESS`.