AVR Embedded Tutorial - Software I2C, TWI: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: AVR Embedded Tutorial - Software I2C, TWI
| Old | New | Line | |
|---|---|---|---|
| 1 | + | # Software I2C / TWI for ATmega328 / Arduino | |
| 2 | + | ||
| 3 | + | Many AVR controllers offer a hardware I2C interface. Sometimes you need an I2C interface on other pins, or several I2C interfaces. Software I2C helps in these situations. | |
| 4 | + | ||
| 5 | + | For the basics of I2C see this WikiPedia [article](https://de.wikipedia.org/wiki/I2c). | |
| 6 | + | ||
| 7 | + | # TWI Master software | |
| 8 | + | ||
| 9 | + | ## TWI header | |
| 10 | + | ||
| 11 | + | The pins for SCL and SDA can be set as desired, but must all be on one port. For pins on different ports, the pin accesses have to be adjusted in the following routines. | |
| 12 | + | ||
| 13 | + | ``` pascal | |
| 14 | + | unit test_twi; | |
| 15 | + | ||
| 16 | + | {$mode objfpc} | |
| 17 | + | {$goto on} | |
| 18 | + | ||
| 19 | + | interface | |
| 20 | + | ||
| 21 | + | uses | |
| 22 | + | ; | |
| 23 | + | ||
| 24 | + | procedure twi_start(); | |
| 25 | + | procedure twi_stop(); | |
| 26 | + | ||
| 27 | + | function twi_write(data : uint8) : boolean; | |
| 28 | + | function twi_readbyte(ackn : boolean) : uint8; | |
| 29 | + | function twi_readword(ackn : boolean) : uint16; | |
| 30 | + | ||
| 31 | + | const | |
| 32 | + | Psclin = 1 shl 5; // PC5 any pins, but on the same port | |
| 33 | + | Psdain = 1 shl 4; // PC4 | |
| 34 | + | ||
| 35 | + | var | |
| 36 | + | DDRtwi : byte absolute DDRC; // TWI pins on port C | |
| 37 | + | PRTtwi : byte absolute PORTC; | |
| 38 | + | PINtwi : byte absolute PINC; | |
| 39 | + | ||
| 40 | + | implementation | |
| 41 | + | ``` | |
| 42 | + | ||
| 43 | + | ## Delays | |
| 44 | + | ||
| 45 | + | The clock rate is adjusted over the duration of the delays, the numbers after **ldi r16**. Inline assembler does not allow comments here and for timing reasons the values should be entered directly and not passed as variables. | |
| 46 | + | ||
| 47 | + | With the given values 9 and 10, a clock rate of 100kHz results at 8MHz clock frequency, with 59 and 60 a clock rate of 10kHz can be used for long data lines. | |
| 48 | + | ||
| 49 | + | ``` pascal | |
| 50 | + | // pause TWI delays | |
| 51 | + | ||
| 52 | + | procedure delay2(); | |
| 53 | + | label | |
| 54 | + | loop; | |
| 55 | + | begin | |
| 56 | + | asm | |
| 57 | + | ldi r16, 9 | |
| 58 | + | loop: | |
| 59 | + | dec r16 | |
| 60 | + | brne loop | |
| 61 | + | end['r16']; | |
| 62 | + | end; | |
| 63 | + | ||
| 64 | + | procedure delay3(); | |
| 65 | + | label | |
| 66 | + | loop; | |
| 67 | + | begin | |
| 68 | + | asm | |
| 69 | + | ldi r16, 10 | |
| 70 | + | loop: | |
| 71 | + | dec r16 | |
| 72 | + | brne loop | |
| 73 | + | end['r16']; | |
| 74 | + | end; | |
| 75 | + | ``` | |
| 76 | + | ||
| 77 | + | ## TWI start | |
| 78 | + | ||
| 79 | + | The start sequence for a transmission. | |
| 80 | + | ||
| 81 | + | ``` pascal | |
| 82 | + | // Start TWI | |
| 83 | + | ||
| 84 | + | procedure twi_start(); | |
| 85 | + | begin | |
| 86 | + | DDRtwi := DDRtwi or Psdain; // SDA low | |
| 87 | + | delay3(); | |
| 88 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 89 | + | delay3(); | |
| 90 | + | end; | |
| 91 | + | ``` | |
| 92 | + | ||
| 93 | + | ## TWI stop | |
| 94 | + | ||
| 95 | + | The stop sequence. For a restart, as required by some data sheets, you simply use a stop sequence followed by a start sequence. | |
| 96 | + | ||
| 97 | + | ``` pascal | |
| 98 | + | // TWI stop sequence | |
| 99 | + | ||
| 100 | + | procedure twi_stop(); | |
| 101 | + | begin | |
| 102 | + | DDRtwi := DDRtwi or Psdain; // SDA low | |
| 103 | + | delay3(); | |
| 104 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 105 | + | delay3(); | |
| 106 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high | |
| 107 | + | delay3(); | |
| 108 | + | end; | |
| 109 | + | ``` | |
| 110 | + | ||
| 111 | + | ## Write TWI byte | |
| 112 | + | ||
| 113 | + | Hand over byte and go. Duration is about 10x cycle time, that is 100µsec at 100kHz. The routine may be interrupted by interrupts, the slave then waits. | |
| 114 | + | ||
| 115 | + | ``` pascal | |
| 116 | + | // Write TWI byte | |
| 117 | + | ||
| 118 | + | function twi_write(data : uint8) : boolean; | |
| 119 | + | var | |
| 120 | + | i : uint8; | |
| 121 | + | begin | |
| 122 | + | for i := 0 to 7 do // output 8 bits | |
| 123 | + | begin | |
| 124 | + | if(data and (1 shl 7)) <> 0 then | |
| 125 | + | DDRtwi := DDRtwi and (not Psdain) // SDA high if bit is set | |
| 126 | + | else | |
| 127 | + | DDRtwi := DDRtwi or Psdain; // SDA low if bit is not set | |
| 128 | + | ||
| 129 | + | data := data shl 1; // shift bit, 7 to 0 | |
| 130 | + | delay2(); | |
| 131 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 132 | + | delay3(); | |
| 133 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 134 | + | end; | |
| 135 | + | ||
| 136 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high | |
| 137 | + | delay2(); | |
| 138 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 139 | + | delay2(); | |
| 140 | + | ||
| 141 | + | if(PINtwi and Psdain) = 0 then // test SDA on Acknowledge low | |
| 142 | + | twi_write := true // set ack flag | |
| 143 | + | else | |
| 144 | + | twi_write := false; // otherwise unset ack flag | |
| 145 | + | ||
| 146 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 147 | + | end; | |
| 148 | + | ``` | |
| 149 | + | ||
| 150 | + | ## Read TWI data byte | |
| 151 | + | ||
| 152 | + | ``` pascal | |
| 153 | + | // read TWI byte | |
| 154 | + | ||
| 155 | + | function twi_readbyte(ackn : boolean) : uint8; | |
| 156 | + | var | |
| 157 | + | data : uint8 = 0; | |
| 158 | + | i : uint8; | |
| 159 | + | begin | |
| 160 | + | for i := 0 to 7 do // output 8 bits | |
| 161 | + | begin | |
| 162 | + | data := data shl 1; // shift bit, 7 to 0 | |
| 163 | + | delay2(); | |
| 164 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 165 | + | delay2(); | |
| 166 | + | ||
| 167 | + | if(PINtwi and (Psdain)) <> 0 then // test SDA on high | |
| 168 | + | data := data or (1 shl 0); // if high, set bit 0 | |
| 169 | + | ||
| 170 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 171 | + | end; | |
| 172 | + | ||
| 173 | + | if ackn then | |
| 174 | + | DDRtwi := DDRtwi or Psdain // SDA low, acknowledge | |
| 175 | + | else | |
| 176 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high, no acknowledge | |
| 177 | + | ||
| 178 | + | delay2(); | |
| 179 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 180 | + | delay3(); | |
| 181 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 182 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high | |
| 183 | + | ||
| 184 | + | twi_readbyte := data; // return data | |
| 185 | + | end; | |
| 186 | + | ``` | |
| 187 | + | ||
| 188 | + | ## Read TWI data word | |
| 189 | + | ||
| 190 | + | ``` pascal | |
| 191 | + | // Read TWI Word, MSB + LSB | |
| 192 | + | ||
| 193 | + | function twi_readword(ackn : boolean ) : uint16; | |
| 194 | + | var | |
| 195 | + | data : uint16 = 0; | |
| 196 | + | i : uint8; | |
| 197 | + | begin | |
| 198 | + | for i := 0 to 7 do // output 8 bits | |
| 199 | + | begin | |
| 200 | + | data := data shl 1; // shift bit, 7 to 0 | |
| 201 | + | delay2(); | |
| 202 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 203 | + | delay2(); | |
| 204 | + | ||
| 205 | + | if(PINtwi and (Psdain)) <> 0 then // test SDA on high | |
| 206 | + | data := data or (1 shl 0); // if high, set bit 0 | |
| 207 | + | ||
| 208 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 209 | + | end; | |
| 210 | + | ||
| 211 | + | DDRtwi := DDRtwi or Psdain; // SDA low, acknowledge | |
| 212 | + | delay2(); | |
| 213 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 214 | + | delay3(); | |
| 215 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 216 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high | |
| 217 | + | ||
| 218 | + | for i := 0 to 7 do // output 8 bits | |
| 219 | + | begin | |
| 220 | + | data := data shl 1; // shift bit, 7 to 0 | |
| 221 | + | delay2(); | |
| 222 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 223 | + | delay2(); | |
| 224 | + | ||
| 225 | + | if(PINtwi and (Psdain)) <> 0 then // test SDA on high | |
| 226 | + | data := data or (1 shl 0); // if high, set bit 0 | |
| 227 | + | ||
| 228 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 229 | + | end; | |
| 230 | + | ||
| 231 | + | if ackn then | |
| 232 | + | DDRtwi := DDRtwi or Psdain // SDA low, acknowledge | |
| 233 | + | else | |
| 234 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high, no acknowledge | |
| 235 | + | ||
| 236 | + | delay2(); | |
| 237 | + | DDRtwi := DDRtwi and (not Psclin); // SCL high | |
| 238 | + | delay3(); | |
| 239 | + | DDRtwi := DDRtwi or Psclin; // SCL low | |
| 240 | + | DDRtwi := DDRtwi and (not Psdain); // SDA high | |
| 241 | + | ||
| 242 | + | twi_readword := data; // return data | |
| 243 | + | end; | |
| 244 | + | ``` | |
| 245 | + | ||
| 246 | + | ## End | |
| 247 | + | ||
| 248 | + | The unit is completed with the following line. | |
| 249 | + | ||
| 250 | + | ``` pascal | |
| 251 | + | end. | |
| 252 | + | ``` | |
| 253 | + | ||
| 254 | + | # See also | |
| 255 | + | ||
| 256 | + | - Overview page - [[AVR Embedded Tutorial]] | |
| 257 | + | ||
| 258 | + | --- | |
| 259 | + | *From the Free Pascal wiki, page "AVR Embedded Tutorial - Software I2C, TWI", as it stood on 2020-03-07 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
