├── LICENSE ├── README.md ├── i2c.h └── i2c.c /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Pieter Noordhuis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # avr-i2c 2 | 3 | A simple, interrupt driven I2C API for Atmel AVR processors. 4 | 5 | * Only assumes the I2C master role. 6 | * **Not** multi-master aware. 7 | * Made to run on an ATmega328P without portability in mind. 8 | * Made with avr-libc 1.8.0 (may not work with earlier versions). 9 | 10 | ## Why? 11 | 12 | * Other I2C APIs I found were blocking (e.g. the on in the Arduino SDK). 13 | * I like to learn about the internals of the processor. 14 | 15 | ## How? 16 | 17 | First, call `i2c_init()` to enable and initialize the processor's I2C system. 18 | 19 | (the AVR docs call it TWI (Two Wire Interface), but it's really just I2C) 20 | 21 | Then, for every set of I2C operations you want to execute while being bus master, 22 | create a struct of type `i2c_tnx_t` and pass it as argument to `i2c_post`: 23 | 24 | ```c 25 | char hello[6] = "hello"; 26 | char world[6]; 27 | 28 | i2c_txn_t *t; 29 | 30 | t = alloca(sizeof(*t) + 2 * sizeof(t->ops[0])); 31 | i2c_txn_init(t, 2); 32 | i2c_op_init_wr(&t->ops[0], I2C_ADDRESS, hello, sizeof(hello)); 33 | i2c_op_init_rd(&t->ops[1], I2C_ADDRESS, world, sizeof(world)); 34 | 35 | /* Post transaction and wait for it to complete. */ 36 | i2c_post(t); 37 | while (!(t->flags & I2C_TXN_DONE)) { 38 | /* You can do whatever you want here. */ 39 | sleep_mode(); 40 | } 41 | 42 | if (t->flags & I2C_TXN_ERR) { 43 | /* 44 | * Some error occured... 45 | */ 46 | } else { 47 | /* 48 | * The char[] `world` now contains the response to the read request. 49 | */ 50 | } 51 | ``` 52 | 53 | ## Critique 54 | 55 | * The transaction struct can take up quite a bit of memory. 56 | 57 | ## License 58 | 59 | MIT (see ``LICENSE``). 60 | -------------------------------------------------------------------------------- /i2c.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pieter Noordhuis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef _I2C_H 26 | #define _I2C_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | /* 100kHz */ 33 | #define I2C_FREQ 100000 34 | 35 | #define I2C_TXN_DONE _BV(0) 36 | #define I2C_TXN_ERR _BV(1) 37 | 38 | typedef struct i2c_op i2c_op_t; 39 | typedef struct i2c_txn i2c_txn_t; 40 | 41 | struct i2c_op { 42 | uint8_t address; 43 | uint8_t buflen; 44 | uint8_t bufpos; 45 | uint8_t *buf; 46 | }; 47 | 48 | struct i2c_txn { 49 | struct i2c_txn *next; 50 | volatile uint8_t flags; 51 | uint8_t opslen; 52 | uint8_t opspos; 53 | struct i2c_op ops[]; 54 | }; 55 | 56 | static inline void i2c_op_init(i2c_op_t *o, uint8_t address, uint8_t *buf, uint8_t buflen) { 57 | o->address = address; 58 | o->buflen = buflen; 59 | o->bufpos = 0; 60 | o->buf = buf; 61 | } 62 | 63 | static inline void i2c_op_init_rd(i2c_op_t *o, uint8_t address, uint8_t *buf, uint8_t buflen) { 64 | i2c_op_init(o, (address << 1) | TW_READ, buf, buflen); 65 | } 66 | 67 | static inline void i2c_op_init_wr(i2c_op_t *o, uint8_t address, uint8_t *buf, uint8_t buflen) { 68 | i2c_op_init(o, (address << 1) | TW_WRITE, buf, buflen); 69 | } 70 | 71 | static inline void i2c_txn_init(i2c_txn_t *t, uint8_t opslen) { 72 | t->flags = 0; 73 | t->opslen = opslen; 74 | t->opspos = 0; 75 | t->next = NULL; 76 | } 77 | 78 | void i2c_init(void); 79 | void i2c_post(i2c_txn_t *t); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pieter Noordhuis 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "i2c.h" 32 | 33 | /* Pointers to current txn and op. */ 34 | static volatile i2c_txn_t *txn; 35 | static volatile i2c_op_t *op; 36 | 37 | /* 38 | * By default, the control register is set to: 39 | * - TWEA: Automatically send acknowledge bit in receive mode. 40 | * - TWEN: Enable the I2C system. 41 | * - TWIE: Enable interrupt requests when TWINT is set. 42 | */ 43 | #define TWCR_DEFAULT (_BV(TWEA) | _BV(TWEN) | _BV(TWIE)) 44 | 45 | #define TWCR_NOT_ACK (_BV(TWINT) | _BV(TWEN) | _BV(TWIE)) 46 | #define TWCR_ACK (TWCR_NOT_ACK | _BV(TWEA)) 47 | 48 | void i2c_init(void) { 49 | uint8_t sreg; 50 | 51 | /* Store the status register and disable interrupts. */ 52 | sreg = SREG; 53 | cli(); 54 | 55 | /* 56 | * From ATmega328P datasheet: 57 | * SCL freq = (CPU Clock freq) / (16 + 2(TWBR) * (PrescalerValue)) 58 | * 59 | * Which means: 60 | * TWBR = ((CPU Clock freq) / (SCL freq) - 16) / (2 * (PrescalerValue)) 61 | * 62 | * Disable the prescaler and set TWBR according to CPU freq and SCL freq. 63 | */ 64 | TWSR &= ~(_BV(TWPS1) | _BV(TWPS0)); 65 | TWBR = ((F_CPU / I2C_FREQ) - 16) / (2 * 1); 66 | 67 | /* 68 | * Active internal pull-up resistors for SCL and SDA. 69 | * Their ports are PC5 for SCL and PC4 for SDA on the ATmega328P. 70 | */ 71 | PORTC |= _BV(PC5) | _BV(PC4); 72 | 73 | /* Enable interrupts via the control register. */ 74 | TWCR = TWCR_DEFAULT; 75 | 76 | /* Disable slave mode. */ 77 | TWAR = 0; 78 | 79 | /* Restore the status register. */ 80 | SREG = sreg; 81 | } 82 | 83 | void i2c_post(i2c_txn_t *t) { 84 | uint8_t sreg; 85 | 86 | /* Reset transaction attributes. */ 87 | t->flags = 0; 88 | t->opspos = 0; 89 | t->next = NULL; 90 | 91 | sreg = SREG; 92 | cli(); 93 | 94 | /* Append transaction to linked list. */ 95 | if (txn == NULL) { 96 | txn = t; 97 | op = &txn->ops[0]; 98 | 99 | /* Transmit START to kickstart things. */ 100 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA); 101 | } else { 102 | volatile i2c_txn_t *txn_ = txn; 103 | while (txn_ != NULL) { 104 | if (txn_->next != NULL) { 105 | txn_ = txn_->next; 106 | } else { 107 | txn_->next = t; 108 | break; 109 | } 110 | } 111 | } 112 | 113 | SREG = sreg; 114 | } 115 | 116 | ISR(TWI_vect, ISR_BLOCK) { 117 | uint8_t status = TW_STATUS; 118 | 119 | /* This interrupt should only fire if there is something to do. */ 120 | assert(op != NULL); 121 | 122 | if ((op->address & _BV(0)) == TW_READ) { 123 | /* Master Receiver mode. */ 124 | switch (status) { 125 | 126 | /* A START condition has been transmitted. */ 127 | case TW_START: 128 | /* A repeated START condition has been transmitted. */ 129 | case TW_REP_START: 130 | assert(op->buflen > 0); 131 | op->bufpos = 0; 132 | TWDR = op->address; 133 | TWCR = TWCR_DEFAULT | _BV(TWINT); 134 | break; 135 | 136 | /* Arbitration lost in SLA+R or NOT ACK bit. */ 137 | case TW_MR_ARB_LOST: 138 | /* A START condition will be transmitted when the bus becomes free. */ 139 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA); 140 | break; 141 | 142 | /* SLA+R has been transmitted; ACK has been received. */ 143 | case TW_MR_SLA_ACK: 144 | if (op->buflen == 1) { 145 | TWCR = TWCR_NOT_ACK; 146 | } else { 147 | TWCR = TWCR_ACK; 148 | } 149 | break; 150 | 151 | /* SLA+R has been transmitted; NOT ACK has been received. */ 152 | case TW_MR_SLA_NACK: 153 | txn->flags = I2C_TXN_DONE | I2C_TXN_ERR; 154 | goto next_txn; 155 | 156 | /* Data byte has been received; ACK has been returned. */ 157 | case TW_MR_DATA_ACK: 158 | op->buf[op->bufpos++] = TWDR; 159 | if (op->bufpos+1 == op->buflen) { 160 | TWCR = TWCR_NOT_ACK; 161 | } else { 162 | TWCR = TWCR_ACK; 163 | } 164 | break; 165 | 166 | /* Data byte has been received; NOT ACK has been returned. */ 167 | case TW_MR_DATA_NACK: 168 | op->buf[op->bufpos++] = TWDR; 169 | goto next_op; 170 | 171 | default: 172 | assert(0 && "unknown status in master receiver mode"); 173 | } 174 | } else { 175 | /* Master Transmitter mode. */ 176 | switch (status) { 177 | 178 | /* A START condition has been transmitted. */ 179 | case TW_START: 180 | /* A repeated START condition has been transmitted. */ 181 | case TW_REP_START: 182 | assert(op->buflen > 0); 183 | op->bufpos = 0; 184 | TWDR = op->address; 185 | TWCR = TWCR_DEFAULT | _BV(TWINT); 186 | break; 187 | 188 | /* Arbitration lost in SLA+W or data bytes. */ 189 | case TW_MT_ARB_LOST: 190 | /* A START condition will be transmitted when the bus becomes free. */ 191 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA); 192 | break; 193 | 194 | /* SLA+W has been transmitted; ACK has been received. */ 195 | case TW_MT_SLA_ACK: 196 | TWDR = op->buf[op->bufpos++]; 197 | TWCR = TWCR_DEFAULT | _BV(TWINT); 198 | break; 199 | 200 | /* SLA+W has been transmitted; NOT ACK has been received. */ 201 | case TW_MT_SLA_NACK: 202 | txn->flags = I2C_TXN_DONE | I2C_TXN_ERR; 203 | goto next_txn; 204 | 205 | /* Data byte has been transmitted; ACK has been received. */ 206 | case TW_MT_DATA_ACK: 207 | if (op->bufpos < op->buflen) { 208 | TWDR = op->buf[op->bufpos++]; 209 | TWCR = TWCR_DEFAULT | _BV(TWINT); 210 | break; 211 | } 212 | 213 | /* No more bytes left to transmit... */ 214 | goto next_op; 215 | 216 | /* Data byte has been transmitted; NOT ACK has been received. */ 217 | case TW_MT_DATA_NACK: 218 | if (op->bufpos < op->buflen) { 219 | /* There were more bytes left to transmit! */ 220 | txn->flags = I2C_TXN_DONE | I2C_TXN_ERR; 221 | goto next_txn; 222 | } 223 | 224 | goto next_op; 225 | 226 | default: 227 | assert(0 && "unknown status in master transmitter mode"); 228 | } 229 | } 230 | 231 | return; 232 | 233 | next_op: 234 | /* 235 | * Advance to next operation in transaction, if possible. 236 | */ 237 | if (++(txn->opspos) < txn->opslen) { 238 | op = &txn->ops[txn->opspos]; 239 | 240 | /* Repeated start. */ 241 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA); 242 | return; 243 | } 244 | 245 | /* No more operations, mark transaction as done. */ 246 | txn->flags = I2C_TXN_DONE; 247 | 248 | next_txn: 249 | /* 250 | * Advance to next transaction, if possible. 251 | */ 252 | if (txn->next != NULL) { 253 | txn = txn->next; 254 | op = &txn->ops[0]; 255 | 256 | /* Repeated start. */ 257 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTA); 258 | return; 259 | } 260 | 261 | txn = NULL; 262 | op = NULL; 263 | 264 | /* No more transaction, transmit STOP. */ 265 | TWCR = TWCR_DEFAULT | _BV(TWINT) | _BV(TWSTO); 266 | } 267 | --------------------------------------------------------------------------------