User Tools

Site Tools


en:pfw:i2c

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
en:pfw:i2c [2023-09-04 18:14] – gelöscht - Externe Bearbeitung (Unbekanntes Datum) 127.0.0.1en:pfw:i2c [2023-09-05 06:33] (current) – ↷ Links angepasst, weil Seiten im Wiki verschoben wurden 157.90.7.32
Line 1: Line 1:
 +{{pfw:banner.png}}
 +====== I2C, The I2C protocol ======
 +
 +**The PFW universal I2C drivers** The I2C protocol is a must-have for any microcontroller. It opens up access to IO-expanders, real-time clocks, more memory and much much more. But starting with I2C in Forth might seem a daunting task. To help you use, and understand, the I2C protocol, PFW has developed a universal set of drivers for 3 different hardware platforms (MSP430, Risc-V, Raspberry/ARM). At the top level the drivers are the same for each platform. For instance, a device-driver for a LCD-display developed for the MSP430 will also work on the other platforms. This makes developing new high-level drivers simple as there are always examples available to base your new driver on. The drivers are structured into 3 classical layers. First a small hardware specific layer for each of the platforms (both a bitbang and internal version for each). On top of this sits a universal high level abstraction layer. And on top of this sit the actual device drivers. Amazingly the hardware specific layer is very small, a few definitions is all that is needed for each platform. And this although the internal I2C-hardware for the 3 platforms is very different.
 +
 +With these drivers, the use of I2C should not be a problem for anyone. Have fun exploring and using I2C-devices!
 +
 +===== The Idea of I2C =====
 +
 +|{{https://user-images.githubusercontent.com/11397265/120920357-a63c7d00-c6be-11eb-8f43-5287f7f82a9c.jpg|i2c-logo}}|[[https://www.nxp.com/docs/en/user-guide/UM10204.pdf|I2C]] is a synchronous serial protocol with two lines (SDA & SCL). It is used for clocks, memory, I/O-expanders, sensors, etc. An in-depth protocol description of the I2C signals can be found on the [[https://www.i2c-bus.org/|I2C website]] or [[https://en.wikipedia.org/wiki/I%C2%B2C|on wikipedia]].|
 +|SDA                                                                                                               |Serial Data Line                                                                                                                                                                                                                                                                                                                                                |
 +|SCL                                                                                                               |Serial Clock Line                                                                                                                                                                                                                                                                                                                                               |
 +
 +**Note that the I2C-protocol uses 7-bits addresses and a read/write bit, but in some cases an 8-bit address is mentioned. Even the original designer of the protocol, Philips, sometimes falls into this trap.**
 +
 +
 +----
 +
 +==== The low level I2C states for a single master are in short: ====
 +
 +  * Start condition
 +  * Address a device (for read or write) A device may stretch the clock cycle to allow for a slow response\\
 +When a device exists and is ready, it responds with an ACK
 +  * Read or write one or more data bytes After each byte an ACK is received, a NAK is received when it is the last byte
 +  * Stop condition
 +
 +{{https://user-images.githubusercontent.com/11397265/120920036-121de600-c6bd-11eb-9e0c-0ab8664f9c47.jpg|I2C logic analyzer tracks}} **A read and a write to a PCF8574**
 +
 +
 +----
 +
 +==== Pseudo code for low level bitbang I2C ====
 +
 +This pseudo code is without the use of clock stretching. This is only necessary when you use a multi master system or I2C slaves implemented on slow microcontrollers. Note that the used example works on on a chip with a push/pull output ports
 +
 +<code>
 +Reserve RAM memory cells named: DEV  SUM  NACK?
 +Function: WAIT         ( -- )
 +  Delay for about 5 µsec.
 +
 +Function: I2START      ( -- )
 +  Clock line high, wait, generate low flank on data line, wait
 +
 +Function: I2ACK        ( -- )
 +  Clock line low, data line low, wait, 
 +  clock line high, wait
 +
 +Function: I2NACK       ( -- )
 +  Clock line low, data line high, wait, 
 +  clock line high, wait
 +
 +Function: I2ACK@       ( -- )
 +  Clock line low, data line high, wait, clock line high, wait
 +  Read status of data line, store true in NACK? if it is a nack, otherwise false
 +
 +Function: BUS!         ( x -- )
 +  8 loop
 +    clock line low
 +    write bit-7 level of x to data line, wait
 +    clock line high, wait
 +    shift x left
 +  Discard byte, perform i2ack@
 +
 +Function: {I2C-ADDR    ( +n -- )
 +    store +n + 1 in SUM,  perform i2start
 +    read dev, perform bus!
 +
 +
 +Higher level I2C access, hides internal details!
 +
 +Function: I2C-ON       ( -- )
 +  Setup I/O-bits for two bidirectional 
 +  open collector lines with pull-up
 +
 +Function: I2C}         ( -- )
 +  Clock line high, wait, generate high flank on data line, wait
 +
 +Function: BUS@         ( -- y )
 +  Initialise y at zero
 +  8 loop
 +    shift y left
 +    Clock line low, data line high, wait, clock line high, wait
 +    read data line to bit-0 position of y
 +  Decrease SUM
 +  Sum not zero IF  perform i2ack  ELSE  perform i2nack  THEN
 +
 +Function:  DEVICE!     ( dev -- )
 +  Multiply dev by 2,  AND result with 0xFE and store in DEV
 +
 +Function: {I2C-WRITE   ( +n -- )
 +  Discard +n, perform i2start, read DEV, (bus!
 +  Read nack? issue error message when true
 +
 +Function: {I2C-READ    ( +n -- )  
 +  Store +n in SUM, perform i2start, read DEV and set lowest bit, 
 +  Perform bus!, read nack? issue error message when true
 +
 +Function: {DEVICE-OK?} ( -- fl ) \ Flag 'fl' is true when an ACK is received
 +  Perform {i2c-addr, perform i2c} 
 +  Read nack?, leave true when result is zero
 +
 +
 +\ Waiting for an EEPROM write to succeed is named acknowledge polling.
 +Function: {POLL}      ( -- )          Start loop {device-ok?} leave when ACK received
 +Function: {I2C-OUT    ( dev +n -- )   Store dev in DEV  perform {i2c-write
 +Function: {I2C-IN     ( dev +n -- )   Store dev in DEV  perform {i2c-read
 +Function: BUS!}       ( b -- )        Perform bus!, perform i2c}
 +Function: BUS@}       ( -- b )        Perform bus@, perform i2c}
 +Function: BUS-MOVE    ( a u -- )      Sent string of 'u' bytes from 'a' over the I2C-bus
 +</code>
 +
 +
 +----
 +
 +==== When looked to I2C from a higher level it's access is: ====
 +
 +1) Simple write action - Open I2C-bus for write access to bus-address and output one byte to I2C-bus - Close I2C-bus access
 +
 +2) Multiple write action to a devices register or address: - Open I2C-bus for write access to bus-address and output one byte to I2C-bus - Output one ore more byte(s) to I2C-bus (with auto increment) - Close I2C-bus access
 +
 +3) A read action from a devices register or address: - Open I2C-bus for write access to bus-address and output the address byte to I2C-bus - Open I2C-bus for reading (Repeated start) - Read one byte from I2C-bus - Close I2C-bus access
 +
 +4) Multiple read action from a devices register or address: - Open I2C-bus for write access to bus-address and output the address byte to I2C-bus - Open I2C-bus for reading (Repeated start) - Read one or more byte(s) from I2C-bus (with auto increment) - Close I2C-bus access
 +
 +==== I2C pseudo code with high level factorisation ====
 +
 +<code>
 +Function: >PCF8574  ( byte dev-addr -- )
 +  perform device!  1  perform {i2c-write  perform bus!  perform i2c}
 +
 +Function: PCF8574>  ( dev-addr -- byte )
 +  perform device!  1  perform {i2c-read  perform bus@  perform i2c}
 +</code>
 +
 +
 +----
 +
 +==== Generic Forth low level part of bitbang example ====
 +
 +This example has the I2C interface pins connected like this.
 +
 +<code>
 +SDA (Serial DAta line)          = bit-7
 +SCL (Serial CLock line)         = bit-6
 +</code>
 +
 +**The used addresses are for port-1 of the MSP430G2553:**
 +
 +Note that the MSP430 controller series does not have the easiest I/O structure to implement a bitbang version of I2C! This is because it only has push/pull outputs and I2C needs an open collector (or open drain) output. So this example code mimics open collector ports.
 +
 +{{https://user-images.githubusercontent.com/11397265/123260134-83e79380-d4f5-11eb-86e8-8f3c6d46b4ba.jpg|Minimal forth example reading EEPROM}}\\
 +**Read byte from I2C EEPROM** %%**%%*
 +
 +===== Generic Forth example =====
 +
 +<code forth>
 +Extra words: ABORT"  TUCK  
 +
 +Words with hardware dependencies:
 +: *BIS  ( mask addr -- )        tuck c@ or  swap c! ; 
 +: *BIC  ( mask addr -- )        >r  invert  r@ c@ and  r> c! ;
 +: BIT*  ( mask addr -- b )      c@ and ;
 +
 +20 constant P1IN        \ Port-1 input register
 +21 constant P1OUT       \ Port-1 output register
 +22 constant P1DIR       \ Port-1 direction register
 +26 constant P1SEL       \ Port-1 function select
 +27 constant P1REN       \ Port-1 resistor enable (pullup/pulldown)
 +42 constant P1SEL2      \ Port-1 function select-2
 +
 +40 constant SCL         \ I2C clock line 
 +80 constant SDA         \ I2C data line
 +SCL SDA or constant IO  \ I2C bus lines
 +
 +: WAIT          ( -- )  \ Delay of 5 µsec. must be trimmed!
 +    ( true  drop ) ;
 +
 +: I2START       ( -- )
 +  scl p1out *bis  scl p1dir *bic  wait
 +  sda p1dir *bis  sda p1out *bic  wait ;
 +
 +: I2ACK         ( -- )
 +  scl p1out *bic  scl p1dir *bis
 +  sda p1out *bic  sda p1dir *bis  wait
 +  scl p1out *bis  scl p1dir *bic  wait ;
 +
 +: I2NACK        ( -- )
 +  scl p1out *bic  scl p1dir *bis
 +  sda p1out *bis  sda p1dir *bic  wait
 +  scl p1out *bis  scl p1dir *bic  wait ;
 +
 +: I2ACK@        ( -- flag )
 +  scl p1out *bic  scl p1dir *bis
 +  sda p1out *bis  sda p1dir *bic  wait
 +  scl p1out *bis  scl p1dir *bic  wait
 +  sda p1in bit* nack? ! ;
 +
 +: BUS!          ( byte -- )
 +  8 0 do
 +    scl p1out *bic  scl p1dir *bis
 +    dup 80 and if
 +      sda p1out *bis  sda p1dir *bic
 +    else
 +      sda p1out *bic  sda p1dir *bis
 +    then
 +    wait  2*
 +    scl p1out *bis  scl p1dir *bic  wait
 +  loop  drop  i2ack@ ;
 +
 +: {I2C-ADDR     ( +n -- )
 +  drop  i2start  dev @ bus! ; \ Start I2C write with address in DEV
 +
 +
 +\ Higher level I2C access, hides internal details!
 +
 +\ Note that this setup is valid for an MSP430 with external pull-up resistors attached!
 +\ On hardware which is able to use an open collector (or open source) with pull-up
 +\ resistor, you should initialise this mode!
 +: I2C-ON        ( -- )
 +  io p1ren *bic         \ Deactivate pull-up/pull-down resistors
 +  io p1dir *bic         \ SDA & SCL are inputs
 +  io p1out *bis         \ Which start high
 +  io p1sel *bic         \ Guarantee normal i/o on MSP430
 +  io p1sel2 *bic ;
 +
 +: BUS@          ( -- byte )
 +  0  8 0 do
 +    2*
 +    scl p1out *bic  scl p1dir *bis
 +    sda p1out *bis  sda p1dir *bic  wait
 +    sda p1in bit*  0= 0= 1 and  or
 +    scl p1out *bis  scl p1dir *bic  wait
 +  loop  -1 sum +!
 +  sum @ if  i2ack  else  i2nack  then ;
 +
 +: I2C}          ( -- )
 +  scl p1out *bic  scl p1dir *bis
 +  sda p1out *bic  sda p1dir *bis  wait
 +  scl p1out *bis  scl p1dir *bic  wait
 +  sda p1out *bis  sda p1dir *bic ;
 +
 +: DEVICE!       ( dev -- )  2* FE and dev ! ;
 +: {DEVICE-OK? ( -- f )    0 {i2c-addr  i2c}  nack? @ 0= ; \ 'f' is true when an ACK was received
 +: {I2C-WRITE    ( +n -- )   {i2c-addr  nack? @ abort" Ack error" ; \ Start I2C write with device in DEV
 +  
 +: {I2C-READ     ( +n -- )     \ Start read to device in DEV
 +    sum !  i2start  dev @ 1+ bus!   \ Used for repeated start
 +    nack? @ abort" Ack error" ; 
 +
 +
 +\ Waiting for an EEPROM write to succeed is named acknowledge polling.
 +: {POLL}    ( -- )          begin  {device-ok?} until ; \ Wait until ACK received
 +: {I2C-OUT  ( dev +n -- )   swap  device!  {i2c-write ;
 +: {I2C-IN   ( dev +n -- )   swap  device!  {i2c-read ;
 +: BUS!}     ( b -- )        bus!  i2c} ;
 +: BUS@}     ( -- b )        bus@  i2c} ;
 +: BUS-MOVE  ( a u -- )      bounds ?do i c@ bus! loop ; \ Send string of bytes from 'a' with length 'u
 +</code>
 +
 +==== I2C implementation examples ====
 +
 +This example is for an 8-bit PCF8574 like I/O-expander:
 +
 +<code forth>
 +: >PCF8574  ( byte dev-addr -- )
 +  device!  1 {i2c-write  bus!  i2c} ;
 +
 +: PCF8574>  ( dev-addr -- byte )
 +  device!  1 {i2c-read  bus@  i2c} ;
 +</code>
 +
 +More examples can be found in the file i2c-examples.f, for the EEPROM code you may adjust the address constant ''%%#EEPROM%%''. Note that the programming page size is different between EEPROM sizes. More info in the file.\\
 +See the list of example words below.
 +
 +^Word                ^Stack                        ^Description                                            ^
 +|''%%I2C?%%''        |( dev -- )                   |Show or device 'dev' is present on the bus             |
 +|''%%SCAN-I2C%%''    |( -- )                       |Show a grid with all device addresses found on the bus |
 +|''%%>PCF8574%%''    |( b dev -- )                 |Write data to 8-bit I/O-extender with 'dev' address    |
 +|''%%PCF8574>%%''    |( dev -- b )                 |Read data from 8-bit I/O-extender with 'dev' address   |
 +|''%%EC@%%''         |( addr -- b )                |Fetch byte from address in EEPROM                      |
 +|''%%EC!%%''         |( b addr -- )                |Store byte to address in EEPROM                        |
 +|''%%EDMP%%''        |( addr -- )                  |Dump EEPROM memory from EEPROM 'addr' onward           |
 +|''%%WRITE-PAGE%%''  |( sa1 ta1 dev +n -- sa2 ta2 )|Write '+n' bytes data from 'sa1' to 'ta1' in 'dev' etc.|
 +|''%%WRITE-MEMORY%%''|( sa ta u -- )               |Write 'u' bytes data from 'sa' to 'ta'                 |
 +|''%%EEFILL%%''      |( a u b -- )                 |Fill 'u' EEPROM bytes from address 'a' with byte 'b'   |
 +
 +
 +----
 +
 +==== Dedicated implementations ====
 +
 +Have a look at the sub directories for implementations for different systems.
 +
 +  * [[en:pfw:i2c_msp430|MSP430]], bitbang & hardware specific I2C implementations for MSP430
 +  * [[en:pfw:i2c_gd32vf|GD32VF103]], bitbang & hardware specific I2C implementations for the Risc-V
 +  * [[https://github.com/project-forth-works/project-forth-works/tree/main/Communication-Protocols/I2C/Raspberry3B%2B|Raspberry3B+]], bitbang & hardware specific I2C implementation for BCM2835
 +  * [[en:pfw:i2c_device-drivers|Generic device drivers]], for EEPROM, OLED, LCD, Clocks, etc.