Commit 2190f69580c71bc715a9dd24c27ebcd81d53b29b
authorDoug Szumski <d.s.szumski@gmail.com>
Thu, 4 Sep 2014 20:54:25 +0000 (22:54 +0200)
committerDoug Szumski <d.s.szumski@gmail.com>
Thu, 4 Sep 2014 21:55:11 +0000 (23:55 +0200)
Adds a commonly used alternative to the Atmel TWI driver. This
particular driver is choosen because it covers a non-interrupt
driven approach to using the TWI module.

4 files changed:
examples/board_i2ctest/Makefile
examples/board_i2ctest/atmega1280_i2ctest.c
examples/shared/twimaster.c [new file with mode: 0755]
examples/shared/twimaster.h [new file with mode: 0755]

index ec0197bdc01d4cdc9d890e44bd0fcda8cbc8acc6..73d89832613db70d997d5968bd422fecfd360548 100644 (file)
@@ -36,7 +36,7 @@ all: obj ${firmware} ${target}
 
 include ${simavr}/Makefile.common
 
-atmega1280_${target}.axf: atmega1280_${target}.c
+atmega1280_${target}.axf: atmega1280_${target}.c twimaster.c
 atmega1280_${target}.axf: ${simavr}/examples/shared/avr_twi_master.c
 atmega1280_${target}.axf: ${simavr}/examples/shared/avr_twi_master.h
 
index e40e73fe53839b6e6ab1c25544a717f818a66520..8bab174505ac7e58cae3d09acae60bac52785fcd 100644 (file)
 AVR_MCU(F_CPU, "atmega1280");
 
 #include "../shared/avr_twi_master.h"
+#include "../shared/twimaster.h"
 
 #include <stdio.h>
 
+#define EEPROM_ADDR    0xA0
+
 static int uart_putchar(char c, FILE *stream) {
   if (c == '\n')
     uart_putchar('\r', stream);
@@ -42,17 +45,14 @@ static int uart_putchar(char c, FILE *stream) {
 static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
                                          _FDEV_SETUP_WRITE);
 
-int main()
+static void
+test_twi_with_atmel_driver(void)
 {
-       stdout = &mystdout;
-
-       sei();
-
        TWI_Master_Initialise();
 
        {       // write 2 bytes at some random address
                uint8_t msg[8] = {
-                               0xa0, // TWI address,
+                               EEPROM_ADDR, // TWI address,
                                0xaa, 0x01, // eeprom address, in little endian
                                0xde, 0xad,     // data bytes
                };
@@ -63,7 +63,7 @@ int main()
        }
        {
                uint8_t msg[8] = {
-                               0xa0, // TWI address,
+                               EEPROM_ADDR, // TWI address,
                                0xa8, 0x01, // eeprom address, in little endian
                };
                TWI_Start_Transceiver_With_Data(msg, 3, 0); // dont send stop!
@@ -73,14 +73,68 @@ int main()
        }
        {
                uint8_t msg[9] = {
-                               0xa0 + 1, // TWI address,
+                               EEPROM_ADDR + 1, // TWI address,
                };
                TWI_Start_Transceiver_With_Data(msg, 9, 1); // write 1 byte, read 8, send stop
 
                while (TWI_Transceiver_Busy())
                        sleep_mode();
        }
+}
+
+/*
+ * Tests the TWI using a commonly used and non-interrupt driven
+ * alternative to the Atmel driver:
+ *
+ * "I2C master library using hardware TWI interface"
+ * Author:   Peter Fleury <pfleury@gmx.ch>  http://jump.to/fleury
+ */
+static void
+test_twi_with_pf_driver(void)
+{
+       /*
+        * This init followed by a start condition is enough to overwrite all TWI
+        * related bits set by TWI_Master_Initialise () in the Atmel driver.
+        */
+       i2c_init();
+
+       i2c_start(EEPROM_ADDR + I2C_WRITE);
+       // eeprom address, in little endian
+       i2c_write(0xaa);
+       i2c_write(0x01);
+       // data bytes
+       i2c_write(0xd0);
+       i2c_write(0x0d);
+       i2c_stop();
+
+       i2c_start(EEPROM_ADDR + I2C_WRITE);
+       // set address
+       i2c_write(0xa8);
+       i2c_write(0x01);
+       // Don't stop
+
+       // Read back data
+       i2c_start (EEPROM_ADDR + I2C_READ);
+       for (uint8_t i = 0; i < 8; ++i) {
+               i2c_readNak();
+       };
+       i2c_stop();
+}
+
+int main()
+{
+       stdout = &mystdout;
+
+       sei();
+
+       test_twi_with_atmel_driver();
+
+       /*
+        * This should produce *exactly* the same output as above, except
+        * the data written should be D00D as opposed to DEAD.
+        */
+       test_twi_with_pf_driver ();
+
        cli();
        sleep_mode();
 }
-
diff --git a/examples/shared/twimaster.c b/examples/shared/twimaster.c
new file mode 100755 (executable)
index 0000000..43925b3
--- /dev/null
@@ -0,0 +1,204 @@
+/*************************************************************************\r
+* Title:    I2C master library using hardware TWI interface\r
+* Author:   Peter Fleury <pfleury@gmx.ch>  http://jump.to/fleury\r
+* File:     $Id: twimaster.c,v 1.3 2005/07/02 11:14:21 Peter Exp $\r
+* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3\r
+* Target:   any AVR device with hardware TWI \r
+* Usage:    API compatible with I2C Software Library i2cmaster.h\r
+**************************************************************************/\r
+#include <inttypes.h>\r
+#include <compat/twi.h>\r
+#include <util/delay.h>\r
+\r
+#include "twimaster.h"\r
+\r
+\r
+/* define CPU frequency in Mhz here if not defined in Makefile */\r
+#ifndef F_CPU\r
+#define F_CPU 7372800UL\r
+#endif\r
+\r
+/* I2C clock in Hz */\r
+#define SCL_CLOCK  100000L\r
+\r
+\r
+/*************************************************************************\r
+ Initialization of the I2C bus interface. Need to be called only once\r
+*************************************************************************/\r
+void i2c_init(void)\r
+{\r
+  /* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */\r
+  \r
+  TWSR |= (1 << TWPS0);                         /* no prescaler */\r
+  TWBR = ((F_CPU/SCL_CLOCK)-16)/(2*4);  /* must be > 10 for stable operation */\r
+\r
+}/* i2c_init */\r
+\r
+\r
+/*************************************************************************     \r
+  Issues a start condition and sends address and transfer direction.\r
+  return 0 = device accessible, 1= failed to access device\r
+*************************************************************************/\r
+unsigned char i2c_start(unsigned char address)\r
+{\r
+    uint8_t   twst;\r
+\r
+       // send START condition\r
+       TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);\r
+\r
+       // wait until transmission completed\r
+       while(!(TWCR & (1<<TWINT)));\r
+\r
+       // check value of TWI Status Register. Mask prescaler bits.\r
+       twst = TW_STATUS & 0xF8;\r
+       if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;\r
+\r
+       // send device address\r
+       TWDR = address;\r
+       TWCR = (1<<TWINT) | (1<<TWEN);\r
+\r
+       // wail until transmission completed and ACK/NACK has been received\r
+       while(!(TWCR & (1<<TWINT)));\r
+\r
+       // check value of TWI Status Register. Mask prescaler bits.\r
+       twst = TW_STATUS & 0xF8;\r
+       if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;\r
+\r
+       return 0;\r
+\r
+}/* i2c_start */\r
+\r
+\r
+/*************************************************************************\r
+ Issues a start condition and sends address and transfer direction.\r
+ If device is busy, use ack polling to wait until device is ready\r
\r
+ FIXME: If device doesn't exist stays in an infinite loop\r
+\r
+ Input:   address and transfer direction of I2C device\r
+*************************************************************************/\r
+void i2c_start_wait(unsigned char address)\r
+{\r
+    uint8_t   twst;\r
+\r
+\r
+    while ( 1 )\r
+    {\r
+           // send START condition\r
+           TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);\r
+    \r
+       // wait until transmission completed\r
+       while(!(TWCR & (1<<TWINT)));\r
+    \r
+       // check value of TWI Status Register. Mask prescaler bits.\r
+       twst = TW_STATUS & 0xF8;\r
+       if ( (twst != TW_START) && (twst != TW_REP_START)) continue;\r
+    \r
+       // send device address\r
+       TWDR = address;\r
+       TWCR = (1<<TWINT) | (1<<TWEN);\r
+    \r
+       // wail until transmission completed\r
+       while(!(TWCR & (1<<TWINT)));\r
+\r
+       // check value of TWI Status Register. Mask prescaler bits.\r
+       twst = TW_STATUS & 0xF8;\r
+       if ( (twst == TW_MT_SLA_NACK )||(twst ==TW_MR_DATA_NACK) ) \r
+       {           \r
+           /* device busy, send stop condition to terminate write operation */\r
+               TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);\r
+\r
+               // wait until stop condition is executed and bus released\r
+               while(TWCR & (1<<TWSTO));\r
+           continue;\r
+       }\r
+       break;\r
+     }\r
+\r
+}/* i2c_start_wait */\r
+\r
+\r
+/*************************************************************************\r
+ Issues a repeated start condition and sends address and transfer direction \r
+\r
+ Input:   address and transfer direction of I2C device\r
\r
+ Return:  0 device accessible\r
+          1 failed to access device\r
+*************************************************************************/\r
+unsigned char i2c_rep_start(unsigned char address)\r
+{\r
+    return i2c_start( address );\r
+\r
+}/* i2c_rep_start */\r
+\r
+\r
+/*************************************************************************\r
+ Terminates the data transfer and releases the I2C bus\r
+*************************************************************************/\r
+void i2c_stop(void)\r
+{\r
+    /* send stop condition */\r
+       TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);\r
+       \r
+       // wait until stop condition is executed and bus released\r
+       while(TWCR & (1<<TWSTO));\r
+\r
+}/* i2c_stop */\r
+\r
+\r
+/*************************************************************************\r
+  Send one byte to I2C device\r
+  \r
+  Input:    byte to be transfered\r
+  Return:   0 write successful \r
+            1 write failed\r
+*************************************************************************/\r
+unsigned char i2c_write( unsigned char data )\r
+{      \r
+    uint8_t   twst;\r
+    \r
+       // send data to the previously addressed device\r
+       TWDR = data;\r
+       TWCR = (1<<TWINT) | (1<<TWEN);\r
+\r
+       // wait until transmission completed\r
+       while(!(TWCR & (1<<TWINT)));\r
+\r
+       // check value of TWI Status Register. Mask prescaler bits\r
+       twst = TW_STATUS & 0xF8;\r
+       if( twst != TW_MT_DATA_ACK) return 1;\r
+       return 0;\r
+\r
+}/* i2c_write */\r
+\r
+\r
+/*************************************************************************\r
+ Read one byte from the I2C device, request more data from device \r
\r
+ Return:  byte read from I2C device\r
+*************************************************************************/\r
+unsigned char i2c_readAck(void)\r
+{\r
+       TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);\r
+       while(!(TWCR & (1<<TWINT)));    \r
+\r
+    return TWDR;\r
+\r
+}/* i2c_readAck */\r
+\r
+\r
+/*************************************************************************\r
+ Read one byte from the I2C device, read is followed by a stop condition \r
\r
+ Return:  byte read from I2C device\r
+*************************************************************************/\r
+unsigned char i2c_readNak(void)\r
+{\r
+       TWCR = (1<<TWINT) | (1<<TWEN);\r
+       while(!(TWCR & (1<<TWINT)));\r
+    //while(!(TWCR ));\r
+       //_delay_ms(100);\r
+    return TWDR;\r
+\r
+}/* i2c_readNak */\r
diff --git a/examples/shared/twimaster.h b/examples/shared/twimaster.h
new file mode 100755 (executable)
index 0000000..70f51fd
--- /dev/null
@@ -0,0 +1,178 @@
+#ifndef _I2CMASTER_H\r
+#define _I2CMASTER_H   1\r
+/************************************************************************* \r
+* Title:    C include file for the I2C master interface \r
+*           (i2cmaster.S or twimaster.c)\r
+* Author:   Peter Fleury <pfleury@gmx.ch>  http://jump.to/fleury\r
+* File:     $Id: i2cmaster.h,v 1.10 2005/03/06 22:39:57 Peter Exp $\r
+* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3\r
+* Target:   any AVR device\r
+* Usage:    see Doxygen manual\r
+**************************************************************************/\r
+\r
+#ifdef DOXYGEN\r
+/**\r
+ @defgroup pfleury_ic2master I2C Master library\r
+ @code #include <i2cmaster.h> @endcode\r
+  \r
+ @brief I2C (TWI) Master Software Library\r
+\r
+ Basic routines for communicating with I2C slave devices. This single master \r
+ implementation is limited to one bus master on the I2C bus. \r
+\r
+ This I2c library is implemented as a compact assembler software implementation of the I2C protocol \r
+ which runs on any AVR (i2cmaster.S) and as a TWI hardware interface for all AVR with built-in TWI hardware (twimaster.c).\r
+ Since the API for these two implementations is exactly the same, an application can be linked either against the\r
+ software I2C implementation or the hardware I2C implementation.\r
+\r
+ Use 4.7k pull-up resistor on the SDA and SCL pin.\r
\r
+ Adapt the SCL and SDA port and pin definitions and eventually the delay routine in the module \r
+ i2cmaster.S to your target when using the software I2C implementation ! \r
\r
+ Adjust the  CPU clock frequence F_CPU in twimaster.c or in the Makfile when using the TWI hardware implementaion.\r
+\r
+ @note \r
+    The module i2cmaster.S is based on the Atmel Application Note AVR300, corrected and adapted \r
+    to GNU assembler and AVR-GCC C call interface.\r
+    Replaced the incorrect quarter period delays found in AVR300 with \r
+    half period delays. \r
+    \r
+ @author Peter Fleury pfleury@gmx.ch  http://jump.to/fleury\r
+\r
+ @par API Usage Example\r
+  The following code shows typical usage of this library, see example test_i2cmaster.c\r
+\r
+ @code\r
+\r
+ #include <i2cmaster.h>\r
+\r
+\r
+ #define Dev24C02  0xA2      // device address of EEPROM 24C02, see datasheet\r
+\r
+ int main(void)\r
+ {\r
+     unsigned char ret;\r
+\r
+     i2c_init();                             // initialize I2C library\r
+\r
+     // write 0x75 to EEPROM address 5 (Byte Write) \r
+     i2c_start_wait(Dev24C02+I2C_WRITE);     // set device address and write mode\r
+     i2c_write(0x05);                        // write address = 5\r
+     i2c_write(0x75);                        // write value 0x75 to EEPROM\r
+     i2c_stop();                             // set stop conditon = release bus\r
+\r
+\r
+     // read previously written value back from EEPROM address 5 \r
+     i2c_start_wait(Dev24C02+I2C_WRITE);     // set device address and write mode\r
+\r
+     i2c_write(0x05);                        // write address = 5\r
+     i2c_rep_start(Dev24C02+I2C_READ);       // set device address and read mode\r
+\r
+     ret = i2c_readNak();                    // read one byte from EEPROM\r
+     i2c_stop();\r
+\r
+     for(;;);\r
+ }\r
+ @endcode\r
+\r
+*/\r
+#endif /* DOXYGEN */\r
+\r
+/**@{*/\r
+\r
+#if (__GNUC__ * 100 + __GNUC_MINOR__) < 304\r
+#error "This library requires AVR-GCC 3.4 or later, update to newer AVR-GCC compiler !"\r
+#endif\r
+\r
+#include <avr/io.h>\r
+\r
+/** defines the data direction (reading from I2C device) in i2c_start(),i2c_rep_start() */\r
+#define I2C_READ    1\r
+\r
+/** defines the data direction (writing to I2C device) in i2c_start(),i2c_rep_start() */\r
+#define I2C_WRITE   0\r
+\r
+\r
+/**\r
+ @brief initialize the I2C master interace. Need to be called only once \r
+ @param  void\r
+ @return none\r
+ */\r
+extern void i2c_init(void);\r
+\r
+\r
+/** \r
+ @brief Terminates the data transfer and releases the I2C bus \r
+ @param void\r
+ @return none\r
+ */\r
+extern void i2c_stop(void);\r
+\r
+\r
+/** \r
+ @brief Issues a start condition and sends address and transfer direction \r
+  \r
+ @param    addr address and transfer direction of I2C device\r
+ @retval   0   device accessible \r
+ @retval   1   failed to access device \r
+ */\r
+extern unsigned char i2c_start(unsigned char addr);\r
+\r
+\r
+/**\r
+ @brief Issues a repeated start condition and sends address and transfer direction \r
+\r
+ @param   addr address and transfer direction of I2C device\r
+ @retval  0 device accessible\r
+ @retval  1 failed to access device\r
+ */\r
+extern unsigned char i2c_rep_start(unsigned char addr);\r
+\r
+\r
+/**\r
+ @brief Issues a start condition and sends address and transfer direction \r
+   \r
+ If device is busy, use ack polling to wait until device ready \r
+ @param    addr address and transfer direction of I2C device\r
+ @return   none\r
+ */\r
+extern void i2c_start_wait(unsigned char addr);\r
+\r
\r
+/**\r
+ @brief Send one byte to I2C device\r
+ @param    data  byte to be transfered\r
+ @retval   0 write successful\r
+ @retval   1 write failed\r
+ */\r
+extern unsigned char i2c_write(unsigned char data);\r
+\r
+\r
+/**\r
+ @brief    read one byte from the I2C device, request more data from device \r
+ @return   byte read from I2C device\r
+ */\r
+extern unsigned char i2c_readAck(void);\r
+\r
+/**\r
+ @brief    read one byte from the I2C device, read is followed by a stop condition \r
+ @return   byte read from I2C device\r
+ */\r
+extern unsigned char i2c_readNak(void);\r
+\r
+/** \r
+ @brief    read one byte from the I2C device\r
\r
+ Implemented as a macro, which calls either i2c_readAck or i2c_readNak\r
\r
+ @param    ack 1 send ack, request more data from device<br>\r
+               0 send nak, read is followed by a stop condition \r
+ @return   byte read from I2C device\r
+ */\r
+extern unsigned char i2c_read(unsigned char ack);\r
+#define i2c_read(ack)  (ack) ? i2c_readAck() : i2c_readNak(); \r
+\r
+\r
+/**@}*/\r
+#endif\r