Compare commits

...

26 Commits
v0.5 ... master

Author SHA1 Message Date
SMFSW d31cca271e Updated to travis-ci.com status 2018-12-12 20:54:31 +01:00
SMFSW e94f51f762 Removed deprecated sudo from .travis.yml 2018-12-11 19:53:01 +01:00
SMFSW dbe9dc0a61 typo in ReleaseNotes.md 2018-06-17 18:23:58 +02:00
SMFSW e6d3d2a8ae v1.3: Delay between retries set to 1ms 2018-05-27 15:57:06 +02:00
SMFSW b9babcdde0 Added dedicated Doxyfile for Travis CI and updated .travis.yml to use graphviz 2018-05-04 17:43:37 +02:00
SMFSW b079e7997d Doxyfile updated for Travis CI 2018-05-04 12:30:50 +02:00
SMFSW 3277748c49 Updated Doxyfile & .travis.yml 2018-05-04 02:23:19 +02:00
SMFSW de152ccc5f Updated .travis.yml 2018-05-04 01:46:31 +02:00
SMFSW fa20634862 Updated .travis.yml 2018-05-04 01:29:09 +02:00
SMFSW 4ce72eeb34 Upadated Doxyfile and .travis.yml for documentation generation 2018-05-04 00:34:44 +02:00
SMFSW 669f2c787b Updated README.md 2018-05-04 00:16:21 +02:00
SMFSW 7122bfd2de Updated README.md 2018-05-03 21:24:01 +02:00
SMFSW 66ceef30b4 Updated README.md 2018-05-03 21:15:08 +02:00
SMFSW 7a2af3d743 README.md updated 2018-05-03 20:57:08 +02:00
SMFSW 668e2ebcf5 Release Notes.md renamed to ReleaseNotes.md 2018-05-03 20:06:58 +02:00
SMFSW 93e2ffcccc Release Notes.md renamed to ReleaseNotes.md 2018-05-03 20:05:10 +02:00
SMFSW dc8c19cb7a v1.3: Updated README.md 2018-05-03 19:33:34 +02:00
SMFSW ee3b56f52e v1.3: updated README.md for Travis 2018-05-03 19:22:12 +02:00
SMFSW a3ed48b1f1 v1.3: Release Notes is now markdown 2018-05-03 19:14:57 +02:00
SMFSW 0e091c3f95 updated .travis.yml 2018-05-03 19:02:27 +02:00
SMFSW 4fcef282b6 v1.3: Added Travis CI support and removed doxygen version anchors in sources 2018-05-03 18:42:08 +02:00
SMFSW 63be41402d Doxyfile 2018-03-15 01:38:58 +01:00
SMFSW acfcfec7aa v1.2: No internal address transmission when reading/writing to next internal address (auto-increment feature of most I2C devices) 2017-11-30 22:40:42 +01:00
SMFSW a44abc72ec v1.1: fixed bus speed calc & returning configuration value applied instead of bool 2017-11-30 01:32:44 +01:00
SMFSW ed7030cffc code review, changes following Wire lib updates & const for function parameters 2017-11-21 23:15:55 +01:00
SMFSW 035edd6d7f v0.6: compliance with Arduino v1.5+ IDE specs 2017-07-12 22:07:33 +02:00
12 changed files with 3328 additions and 825 deletions

4
.gitignore vendored Normal file → Executable file
View File

@ -27,3 +27,7 @@
*.exe
*.out
*.app
#Doxygen
doxygen_sqlite3.db
html

29
.travis.yml Executable file
View File

@ -0,0 +1,29 @@
language: c
addons:
apt:
packages:
- graphviz
# Blacklist
branches:
except:
- gh-pages
env:
global:
- PRETTYNAME="cI2C: Arduino Hardware I2C for AVR (in plain c)"
- GH_REPO_NAME: cI2C
- GH_REPO_REF: github.com/SMFSW/cI2C.git
- DOXYFILE: $TRAVIS_BUILD_DIR/Doxyfile.auto
before_install:
- source <(curl -SLs https://raw.githubusercontent.com/SMFSW/travis-ci-arduino/master/install.sh)
script:
- build_avr_platforms
# Generate and deploy documentation
after_success:
- source <(curl -SLs https://raw.githubusercontent.com/SMFSW/travis-ci-arduino/master/library_check.sh)
- source <(curl -SLs https://raw.githubusercontent.com/SMFSW/travis-ci-arduino/master/doxy_gen_and_deploy.sh)

View File

@ -1,4 +1,4 @@
# Doxyfile 1.8.11
# Doxyfile 1.8.13
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -38,7 +38,7 @@ PROJECT_NAME = "Arduino Hardware I2C for AVR MCUs (plain c)"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 0.5
PROJECT_NUMBER = 1.3
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@ -244,7 +244,7 @@ TCL_SUBST =
# members will be omitted, etc.
# The default value is: NO.
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_FOR_C = YES
# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
# Python sources only. Doxygen will then generate output that is more tailored
@ -295,6 +295,15 @@ EXTENSION_MAPPING = ino=C++
MARKDOWN_SUPPORT = YES
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 0.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0
# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
@ -739,7 +748,7 @@ WARN_IF_DOC_ERROR = YES
# parameter documentation, but not about the absence of documentation.
# The default value is: NO.
WARN_NO_PARAMDOC = NO
WARN_NO_PARAMDOC = YES
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
@ -761,7 +770,7 @@ WARN_FORMAT = "$file:$line: $text"
# messages should be written. If left blank the output is written to standard
# error (stderr).
WARN_LOGFILE =
WARN_LOGFILE = workdir/doxy.log
#---------------------------------------------------------------------------
# Configuration options related to the input files
@ -773,7 +782,8 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = ./
INPUT = ./ \
./src
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -795,8 +805,8 @@ INPUT_ENCODING = UTF-8
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
FILE_PATTERNS = *.c \
*.cpp \
@ -925,7 +935,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE =
USE_MDFILE_AS_MAINPAGE = README.md
#---------------------------------------------------------------------------
# Configuration options related to source browsing
@ -1719,7 +1729,7 @@ LATEX_EXTRA_FILES =
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
PDF_HYPERLINKS = NO
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
@ -2365,6 +2375,11 @@ DIAFILE_DIRS =
PLANTUML_JAR_PATH =
# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
# configuration file for plantuml.
PLANTUML_CFG_FILE =
# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.

2446
Doxyfile.auto Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
# cI2C
# cI2C [![Build Status](https://travis-ci.com/SMFSW/cI2C.svg?branch=master)](https://travis-ci.com/SMFSW/cI2C)
Arduino Hardware I2C for AVR (plain c)
Hardware I2C library for AVR MCUs (lib intended for I2C protocols development in c, for easier ports to other MCUs)
## Library choice:
* cI2C library implements I2C bus for AVR tagets (Uno, Nano, Mega...)
## Library choice
* cI2C library implements I2C bus for AVR targets (Uno, Nano, Mega...)
* you may prefer this one when:
* working on AVR targets
* interrupts are not needed
@ -16,64 +18,69 @@ Hardware I2C library for AVR MCUs (lib intended for I2C protocols development in
No refactoring is required when switching between **cI2C** & **WireWrapper** libs;
Both libs share same Typedefs, Functions & Parameters.
## Notes:
## Notes
* cI2C is written in plain c (intentionally)
* cI2C does not use any interrupt (yet, but soon will have to)
* cI2C is designed to act as bus Master (Slave mode will be considered in future releases)
* cI2C is set to work on AVR targets only
* for other targets, you may use **WireWrapper** instead (will be using Wire)
## Usage:
## Usage
This library is intended to be able to work with multiple slaves connected on the same I2C bus.
Thus, the I2C bus and Slaves are defined separately.
* On one hand, I2C bus has to be initialised with appropriate speed:
* use I2C_init(speed): speed can be choosen from I2C_SPEED enum for convenience, or passing an integer as parameter
* On the other hand, Slave(s) have to be defined and initialised too:
* use I2C_SLAVE typedef to declare slaves structs
* use I2C_slave_init(pSlave, addr, regsize)
* **pSlave** is a pointer to the slave struct to initialise
* **addr** is the slave I2C address (don't shift addr, lib takes care of that)
* **regsize** is the width of internal slave registers (to be choosen from I2C_INT_SIZE)
* On one hand, I2C bus has to be initialized with appropriate speed:
* use `I2C_init(speed)`: speed can be chosen from `I2C_SPEED` enum for convenience, or passing an integer as parameter
* On the other hand, Slave(s) have to be defined and initialized too:
* use `I2C_SLAVE` typedef to declare slaves structs
* use `I2C_slave_init(pSlave, addr, regsize)`
* `pSlave`: pointer to the slave struct to initialize
* `addr`: slave I2C address (don't shift addr, lib takes care of that)
* `regsize`: width of internal slave registers (to be chosen from `I2C_INT_SIZE`)
* in case you need to use custom R/W procedures for a particular slave:
* use I2C_slave_set_rw_func(pSlave, pFunc, rw)
* **pSlave** is a pointer to the slave declaration to initialise
* **pFunc** is a pointer to the Read or Write bypass function
* **rw** can be choosen from I2C_RW enum (wr=0, rd=1)
* use `I2C_slave_set_rw_func(pSlave, pFunc, rw)`
* `pSlave`: pointer to the slave declaration to initialize
* `pFunc`: pointer to the Read or Write bypass function
* `rw`: can be chosen from `I2C_RW` enum (wr=0, rd=1)
After all inits are done, the lib can basically be used this way:
* I2C_read(pSlave, regaddr, pData, bytes)
* **pSlave** is a pointer to the slave struct to read from
* **regaddr** is the start address to read from
* **pData** is a pointer to the place where datas read will be stored
* **bytes** number of bytes to read from slave
* returns true if read is ok, false otherwise
* I2C_write(pSlave, regaddr, pData, bytes)
* **pSlave** is a pointer to the slave struct to write to
* **regaddr** is the start address to write to
* **pData** is a pointer to the block of datas to write to slave
* **bytes** number of bytes to write to slave
* returns true if write is ok, false otherwise
* `I2C_read(pSlave, regaddr, pData, bytes)`
* `pSlave`: pointer to the slave struct to read from
* `regaddr`: start address to read from
* `pData`: pointer to the place where datas read will be stored
* `bytes`: number of bytes to read from slave
* returns `true` if read is ok, `false` otherwise
* `I2C_write(pSlave, regaddr, pData, bytes)`
* `pSlave`: pointer to the slave struct to write to
* `regaddr`: start address to write to
* `pData`: pointer to the block of datas to write to slave
* `bytes`: number of bytes to write to slave
* returns `true` if write is ok, `false` otherwise
## Examples included
## Examples included:
following examples should work with any I2C EEPROM/FRAM with address 0x50
(yet function to get Chip ID are device dependant (and will probably only work on FUJITSU devices))
* ci2c_master_write.ino: Write some bytes to FRAM and compare them with what's read afterwards
* ci2c_master_read.ino: Read some bytes in FRAM
* ci2c_advanced.ino: Redirecting slave write & read functions (to custom functions following typedef)
(yet function to get Chip ID are device dependent (and will probably only work on FUJITSU devices))
* [ci2c_master_write.ino](examples/ci2c_master_write/ci2c_master_write.ino): Write some bytes to FRAM and compare them with what's read afterwards
* [ci2c_master_read.ino](examples/ci2c_master_read/ci2c_master_read.ino): Read some bytes in FRAM
* [ci2c_advanced.ino](examples/ci2c_advanced/ci2c_advanced.ino): Redirecting slave write & read functions (to custom functions following typedef)
Doxygen doc can be generated for the library using doxyfile
## Documentation
## Links:
Feel free to share your thoughts @ xgarmanboziax@gmail.com about:
* issues encountered
* optimisations
* improvements & new functionalities
Doxygen doc can be generated using "Doxyfile".
See [generated documentation](https://smfsw.github.io/cI2C/)
## Release Notes
See [release notes](ReleaseNotes.md)
## See also
**cI2C**
- https://github.com/SMFSW/cI2C
- https://bitbucket.org/SMFSW/ci2c
* [cI2C github](https://github.com/SMFSW/cI2C) - C implementation of this library
**WireWrapper**
- https://github.com/SMFSW/WireWrapper
- https://bitbucket.org/SMFSW/wirewrapper
* [WireWrapper github](https://github.com/SMFSW/WireWrapper) - Cpp implementation using Wire Wrapper

View File

@ -1,20 +1,40 @@
Arduino Hardware I2C for AVR (plain c)
2017-2017 SMFSW
2017-2018 SMFSW
- cI2C is set to work on AVR targets only
-> for other targets, you may use WireWrapper instead (will be using Wire)
-> cI2C & WireWrapper libs declare same structures & functions as seen from the outside
(switch between libs without changing anyhting but the include)
(switch between libs without changing anything but the include)
Feel free to share your thoughts @ xgarmanboziax@gmail.com about:
- issues encountered
- optimisations
- optimizations
- improvements & new functionalities
------------
** Actual:
v1.3 13 May 2018:
- Delay between retries is now 1ms
- Adding support for unit tests and doxygen documentation generation with Travis CI
- Updated README.md
v1.2 30 Nov 2017:
- No internal address transmission when reading/writing to next internal address (make sure not to r/w last 16 address right just after init, otherwise make a dummy of address 0 just before)
v1.1 29 Nov 2017:
- Frequency calculation fix (thanks to TonyWilk)
- Set Frequency higher than Fast Mode (400KHz) will set bus to Fast Mode (frequency is up to 400KHz on AVR)
- I2C_set_xxx now returns values applied, not bool
v1.0 21 Nov 2017:
- Added const qualifier for function parameters
- Return from comm functions if bytes to R/W set to 0
v0.6 12 Jul 2017:
- compliance with Arduino v1.5+ IDE source located in src subfolder
v0.5 31 Jan 2017:
- refactored I2C_SPEED enum names for coherence with I2C specifications
- High Speed mode added in I2C_SPEED enum

468
ci2c.c
View File

@ -1,468 +0,0 @@
/*!\file ci2c.c
** \author SMFSW
** \version 0.5
** \copyright MIT SMFSW (2017)
** \brief arduino master i2c in plain c code
**/
// TODO: add interrupt vector / callback for it operations (if not too messy)
// TODO: consider interrupts at least for RX when slave (and TX when master)
// TODO: change contigous r/w operations so it doesn't send internal address again
// TODO: split functions & headers
#include "ci2c.h"
#define START 0x08
#define REPEATED_START 0x10
#define MT_SLA_ACK 0x18
#define MT_SLA_NACK 0x20
#define MT_DATA_ACK 0x28
#define MT_DATA_NACK 0x30
#define MR_SLA_ACK 0x40
#define MR_SLA_NACK 0x48
#define MR_DATA_ACK 0x50
#define MR_DATA_NACK 0x58
#define LOST_ARBTRTN 0x38
#define TWI_STATUS (TWSR & 0xF8)
//#define isSetRegBit(r, b) ((r & (1 << b)) != 0)
//#define isClrRegBit(r, b) ((r & (1 << b)) == 0)
#define setRegBit(r, b) r |= (1 << b) //!< set bit \b b in register \b r
#define clrRegBit(r, b) r &= (uint8_t) (~(1 << b)) //!< clear bit \b b in register \b r
#define invRegBit(r, b) r ^= (1 << b) //!< invert bit \b b in register \b r
/*! \struct i2c
* \brief static ci2c bus config and control parameters
*/
static struct {
/*! \struct cfg
* \brief ci2c bus parameters
*/
struct {
I2C_SPEED speed; //!< i2c bus speed
uint8_t retries; //!< i2c message retries when fail
uint16_t timeout; //!< i2c timeout (ms)
} cfg;
uint16_t start_wait; //!< time start waiting for acknowledge
bool busy; //!< true if already busy (in case of interrupts implementation)
} i2c = { { (I2C_SPEED) 0, DEF_CI2C_NB_RETRIES, DEF_CI2C_TIMEOUT }, 0, false };
// Needed prototypes
static bool I2C_wr(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes);
static bool I2C_rd(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes);
/*! \brief Init an I2C slave structure for cMI2C communication
* \param [in] slave - pointer to the I2C slave structure to init
* \param [in] sl_addr - I2C slave address
* \param [in] reg_sz - internal register map size
* \return nothing
*/
void I2C_slave_init(I2C_SLAVE * slave, uint8_t sl_addr, I2C_INT_SIZE reg_sz)
{
(void) I2C_slave_set_addr(slave, sl_addr);
(void) I2C_slave_set_reg_size(slave, reg_sz);
I2C_slave_set_rw_func(slave, (ci2c_fct_ptr) I2C_wr, I2C_WRITE);
I2C_slave_set_rw_func(slave, (ci2c_fct_ptr) I2C_rd, I2C_READ);
slave->reg_addr = 0;
slave->status = I2C_OK;
}
/*! \brief Redirect slave I2C read/write function (if needed for advanced use)
* \param [in] slave - pointer to the I2C slave structure to init
* \param [in] func - pointer to read/write function to affect
* \param [in] rw - 0 = write function, 1 = read function
* \return nothing
*/
void I2C_slave_set_rw_func(I2C_SLAVE * slave, ci2c_fct_ptr func, I2C_RW rw)
{
ci2c_fct_ptr * pfc = (ci2c_fct_ptr*) (rw ? &slave->cfg.rd : &slave->cfg.wr);
*pfc = func;
}
/*! \brief Change I2C slave address
* \param [in, out] slave - pointer to the I2C slave structure to init
* \param [in] sl_addr - I2C slave address
* \return true if new address set (false if address is >7Fh)
*/
bool I2C_slave_set_addr(I2C_SLAVE * slave, uint8_t sl_addr)
{
if (sl_addr > 0x7F) { return false; }
slave->cfg.addr = sl_addr;
return true;
}
/*! \brief Change I2C registers map size (for access)
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_sz - internal register map size
* \return true if new size is correct (false otherwise and set to 16bit by default)
*/
bool I2C_slave_set_reg_size(I2C_SLAVE * slave, I2C_INT_SIZE reg_sz)
{
slave->cfg.reg_size = reg_sz > I2C_16B_REG ? I2C_16B_REG : reg_sz;
return !(reg_sz > I2C_16B_REG);
}
/*! \brief Set I2C current register address
* \attribute inline
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address
* \return nothing
*/
static inline void __attribute__((__always_inline__)) I2C_slave_set_reg_addr(I2C_SLAVE * slave, uint16_t reg_addr)
{
slave->reg_addr = reg_addr;
}
/*! \brief Enable I2c module on arduino board (including pull-ups,
* enabling of ACK, and setting clock frequency)
* \param [in] speed - I2C bus speed in KHz
* \return nothing
*/
void I2C_init(uint16_t speed)
{
// Set SDA and SCL to ports with pull-ups
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
setRegBit(PORTC, 4);
setRegBit(PORTC, 5);
#else
setRegBit(PORTD, 0);
setRegBit(PORTD, 1);
#endif
(void) I2C_set_speed(speed);
}
/*! \brief Disable I2c module on arduino board (releasing pull-ups, and TWI control)
* \return nothing
*/
void I2C_uninit()
{
// Release SDA and SCL ports pull-ups
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
clrRegBit(PORTC, 4);
clrRegBit(PORTC, 5);
#else
clrRegBit(PORTD, 0);
clrRegBit(PORTD, 1);
#endif
TWCR = 0;
}
/*! \brief I2C bus reset (Release SCL and SDA lines and re-enable module)
* \return nothing
*/
void I2C_reset(void)
{
TWCR = 0;
setRegBit(TWCR, TWEA);
setRegBit(TWCR, TWEN);
}
/*! \brief Change I2C frequency
* \param [in] speed - I2C speed in kHz (max 1MHz)
* \return true if change is successful (false otherwise)
*/
bool I2C_set_speed(uint16_t speed)
{
i2c.cfg.speed = (I2C_SPEED) ((speed == 0) ? (uint16_t) I2C_STD : ((speed > (uint16_t) I2C_HS) ? (uint16_t) I2C_STD : speed));
clrRegBit(TWCR, TWEN); // Ensure i2c module is disabled
// Set prescaler and clock frequency
clrRegBit(TWSR, TWPS0);
clrRegBit(TWSR, TWPS1);
TWBR = ((F_CPU / (i2c.cfg.speed * 1000)) - 16) / 2;
I2C_reset(); // re-enable module
return (i2c.cfg.speed == speed);
}
/*! \brief Change I2C ack timeout
* \param [in] timeout - I2C ack timeout (500 ms max)
* \return true if change is successful (false otherwise)
*/
bool I2C_set_timeout(uint16_t timeout)
{
static const uint16_t max_timeout = 500;
i2c.cfg.timeout = (timeout > max_timeout) ? max_timeout : timeout;
return (i2c.cfg.timeout == timeout);
}
/*! \brief Change I2C message retries (in case of failure)
* \param [in] retries - I2C number of retries (max of 8)
* \return true if change is successful (false otherwise)
*/
bool I2C_set_retries(uint8_t retries)
{
static const uint16_t max_retries = 8;
i2c.cfg.retries = (retries > max_retries) ? max_retries : retries;
return (i2c.cfg.retries == retries);
}
/*! \brief Get I2C busy status
* \return true if busy
*/
bool I2C_is_busy(void)
{
return i2c.busy;
}
/*! \brief This function reads or writes the provided data to/from the address specified.
* If anything in the write process is not successful, then it will be repeated
* up till 3 more times (default). If still not successful, returns NACK
* \param [in, out] slave - pointer to the I2C slave structure to init
* \param [in] reg_addr - register address in register map
* \param [in] data - pointer to the first byte of a block of data to write
* \param [in] bytes - indicates how many bytes of data to write
* \param [in] rw - 0 = write, 1 = read operation
* \return I2C_STATUS status of write attempt
*/
static I2C_STATUS I2C_comm(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes, I2C_RW rw)
{
uint8_t retry = i2c.cfg.retries;
bool ack = false;
ci2c_fct_ptr fc = (ci2c_fct_ptr) (rw ? slave->cfg.rd : slave->cfg.wr);
if (I2C_is_busy()) { return slave->status = I2C_BUSY; }
i2c.busy = true;
ack = fc(slave, reg_addr, data, bytes);
while ((!ack) && (retry != 0)) // If com not successful, retry some more times
{
delay(5);
ack = fc(slave, reg_addr, data, bytes);
retry--;
}
i2c.busy = false;
return slave->status = ack ? I2C_OK : I2C_NACK;
}
/*! \brief This function writes the provided data to the address specified.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in] data - pointer to the first byte of a block of data to write
* \param [in] bytes - indicates how many bytes of data to write
* \return I2C_STATUS status of write attempt
*/
I2C_STATUS I2C_write(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
{
return I2C_comm(slave, reg_addr, data, bytes, I2C_WRITE);
}
/*! \brief This function reads data from the address specified and stores this
* data in the area provided by the pointer.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in, out] data - pointer to the first byte of a block of data to read
* \param [in] bytes - indicates how many bytes of data to read
* \return I2C_STATUS status of read attempt
*/
I2C_STATUS I2C_read(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
{
return I2C_comm(slave, reg_addr, data, bytes, I2C_READ);
}
/*! \brief Start i2c_timeout timer
* \attribute inline
* \return nothing
*/
static inline void __attribute__((__always_inline__)) I2C_start_timeout(void)
{
i2c.start_wait = (uint16_t) millis();
}
/*! \brief Test i2c_timeout
* \attribute inline
* \return true if i2c_timeout occured (false otherwise)
*/
static inline uint8_t __attribute__((__always_inline__)) I2C_timeout(void)
{
return (((uint16_t) millis() - i2c.start_wait) >= i2c.cfg.timeout);
}
/*! \brief Send start condition
* \return true if start condition acknowledged (false otherwise)
*/
bool I2C_start(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == START) || (TWI_STATUS == REPEATED_START)) { return true; }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); }
return false;
}
/*! \brief Send stop condition
* \return true if stop condition acknowledged (false otherwise)
*/
bool I2C_stop(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
while ((TWCR & (1 << TWSTO)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
return true;
}
/*! \brief Send byte on bus
* \param [in] dat - data to be sent
* \return true if data sent acknowledged (false otherwise)
*/
bool I2C_wr8(uint8_t dat)
{
TWDR = dat;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == MT_DATA_ACK) { return true; }
if (TWI_STATUS == MT_DATA_NACK) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*! \brief Receive byte from bus
* \param [in] ack - true if wait for ack
* \return true if data reception acknowledged (false otherwise)
*/
uint8_t I2C_rd8(bool ack)
{
I2C_start_timeout();
if (ack) { TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA); }
else { TWCR = (1 << TWINT) | (1 << TWEN); }
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); return false; }
return ((((TWI_STATUS == MR_DATA_NACK) && (!ack)) || ((TWI_STATUS == MR_DATA_ACK) && (ack))) ? true : false);
}
/*! \brief Send I2C address
* \param [in] slave - pointer to the I2C slave structure
* \param [in] rw - read/write transaction
* \return true if I2C chip address sent acknowledged (false otherwise)
*/
bool I2C_sndAddr(I2C_SLAVE * slave, I2C_RW rw)
{
TWDR = (slave->cfg.addr << 1) | rw;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == MT_SLA_ACK) || (TWI_STATUS == MR_SLA_ACK)) { return true; }
if ((TWI_STATUS == MT_SLA_NACK) || (TWI_STATUS == MR_SLA_NACK)) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*! \brief This procedure calls appropriate functions to perform a proper send transaction on I2C bus.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in] data - pointer to the first byte of a block of data to write
* \param [in] bytes - indicates how many bytes of data to write
* \return Boolean indicating success/fail of write attempt
*/
static bool I2C_wr(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
{
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if (slave->cfg.reg_size)
{
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_wr8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_wr8((uint8_t) reg_addr) == false) { return false; }
}
for (uint16_t cnt = 0; cnt < bytes; cnt++)
{
if (I2C_wr8(*data++) == false) { return false; }
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}
/*! \brief This procedure calls appropriate functions to perform a proper receive transaction on I2C bus.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in, out] data - pointer to the first byte of a block of data to read
* \param [in] bytes - indicates how many bytes of data to read
* \return Boolean indicating success/fail of read attempt
*/
static bool I2C_rd(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
{
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (bytes == 0) { bytes = 1; }
if (slave->cfg.reg_size) // If start register has to be sent first
{
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_wr8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_wr8((uint8_t) reg_addr) == false) { return false; }
}
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_READ) == false) { return false; }
for (uint16_t cnt = 0; cnt < bytes; cnt++)
{
if (I2C_rd8((cnt == (bytes - 1)) ? false : true) == false) { return false; }
*data++ = TWDR;
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}

286
ci2c.h
View File

@ -1,286 +0,0 @@
/*!\file ci2c.h
** \author SMFSW
** \version 0.5
** \copyright MIT SMFSW (2017)
** \brief arduino i2c in plain c declarations
**/
/****************************************************************/
#ifndef __CI2C_H__
#define __CI2C_H__ "v0.5"
/****************************************************************/
#if defined(DOXY)
// Define gcc __attribute__ as void when Doxygen runs
#define __attribute__(a) //!< GCC attribute (ignored by Doxygen)
#endif
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include <inttypes.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C"{
#endif
#define DEF_CI2C_NB_RETRIES 3 //!< Default cI2C transaction retries
#define DEF_CI2C_TIMEOUT 100 //!< Default cI2C timeout
/*! \enum enI2C_RW
* \brief I2C RW bit enumeration
* \attribute packed enum
*/
typedef enum __attribute__((__packed__)) enI2C_RW {
I2C_WRITE = 0, //!< I2C rw bit (write)
I2C_READ //!< I2C rw bit (read)
} I2C_RW;
/*! \enum enI2C_SPEED
* \brief I2C bus speed
* \attribute packed enum
*/
typedef enum __attribute__((__packed__)) enI2C_SPEED {
I2C_STD = 100, //!< I2C Standard (100KHz)
I2C_FM = 400, //!< I2C Fast Mode (400KHz)
I2C_FMP = 1000, //!< I2C Fast mode + (1MHz)
I2C_HS = 3400 //!< I2C High Speed (3.4MHz)
} I2C_SPEED;
/*! \enum enI2C_STATUS
* \brief I2C slave status
* \attribute packed enum
*/
typedef enum __attribute__((__packed__)) enI2C_STATUS {
I2C_OK = 0x00, //!< I2C OK
I2C_BUSY, //!< I2C Bus busy
I2C_NACK //!< I2C Not Acknowledge
} I2C_STATUS;
/*! \enum enI2C_INT_SIZE
* \brief I2C slave internal address registers size
* \attribute packed enum
*/
typedef enum __attribute__((__packed__)) enI2C_INT_SIZE {
I2C_NO_REG = 0x00, //!< Internal address registers not applicable for slave
I2C_8B_REG, //!< Slave internal address registers space is 8bits wide
I2C_16B_REG //!< Slave internal address registers space is 16bits wide
} I2C_INT_SIZE;
typedef bool (*ci2c_fct_ptr) (const void*, uint16_t, uint8_t*, uint16_t); //!< i2c read/write function pointer typedef
/*! \struct StructI2CSlave
* \brief ci2c slave config and control parameters
* \attribute packed struct
*/
typedef struct __attribute__((__packed__)) StructI2CSlave {
/*! \struct cfg
* \brief ci2c slave parameters
*/
struct {
uint8_t addr; //!< Slave address
I2C_INT_SIZE reg_size; //!< Slave internal registers size
ci2c_fct_ptr wr; //!< Slave write function pointer
ci2c_fct_ptr rd; //!< Slave read function pointer
} cfg;
uint16_t reg_addr; //!< Internal current register address
I2C_STATUS status; //!< Status of the last communications
} I2C_SLAVE;
/***************************/
/*** I2C SLAVE FUNCTIONS ***/
/***************************/
/*! \brief Init an I2C slave structure for cMI2C communication
* \param [in] slave - pointer to the I2C slave structure to init
* \param [in] sl_addr - I2C slave address
* \param [in] reg_sz - internal register map size
* \return nothing
*/
extern void I2C_slave_init(I2C_SLAVE * slave, uint8_t sl_addr, I2C_INT_SIZE reg_sz);
/*! \brief Redirect slave I2C read/write function (if needed for advanced use)
* \param [in] slave - pointer to the I2C slave structure to init
* \param [in] func - pointer to read/write function to affect
* \param [in] rw - 0 = write function, 1 = read function
* \return nothing
*/
extern void I2C_slave_set_rw_func(I2C_SLAVE * slave, ci2c_fct_ptr func, I2C_RW rw);
/*! \brief Change I2C slave address
* \param [in, out] slave - pointer to the I2C slave structure to init
* \param [in] sl_addr - I2C slave address
* \return true if new address set (false if address is >7Fh)
*/
extern bool I2C_slave_set_addr(I2C_SLAVE * slave, uint8_t sl_addr);
/*! \brief Change I2C registers map size (for access)
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_sz - internal register map size
* \return true if new size is correct (false otherwise and set to 16bit by default)
*/
extern bool I2C_slave_set_reg_size(I2C_SLAVE * slave, I2C_INT_SIZE reg_sz);
/*! \brief Get I2C slave address
* \attribute inline
* \param [in] slave - pointer to the I2C slave structure
* \return I2C slave address
*/
inline uint8_t __attribute__((__always_inline__)) I2C_slave_get_addr(I2C_SLAVE * slave)
{
return slave->cfg.addr;
}
/*! \brief Get I2C register map size (for access)
* \attribute inline
* \param [in] slave - pointer to the I2C slave structure
* \return register map using 16bits if true (1Byte otherwise)
*/
inline bool __attribute__((__always_inline__)) I2C_slave_get_reg_size(I2C_SLAVE * slave)
{
return slave->cfg.reg_size;
}
/*! \brief Get I2C current register address (addr may passed this way in procedures if contigous accesses)
* \attribute inline
* \param [in] slave - pointer to the I2C slave structure
* \return current register map address
*/
inline uint16_t __attribute__((__always_inline__)) I2C_slave_get_reg_addr(I2C_SLAVE * slave)
{
return slave->reg_addr;
}
/*************************/
/*** I2C BUS FUNCTIONS ***/
/*************************/
/*! \brief Enable I2c module on arduino board (including pull-ups,
* enabling of ACK, and setting clock frequency)
* \param [in] speed - I2C bus speed in KHz
* \return nothing
*/
extern void I2C_init(uint16_t speed);
/*! \brief Disable I2c module on arduino board (releasing pull-ups, and TWI control)
* \return nothing
*/
extern void I2C_uninit();
/*! \brief Change I2C frequency
* \param [in] speed - I2C bus speed in KHz
* \return true if change is successful (false otherwise)
*/
extern bool I2C_set_speed(uint16_t speed);
/*! \brief Change I2C ack timeout
* \param [in] timeout - I2C ack timeout (500 ms max)
* \return true if change is successful (false otherwise)
*/
extern bool I2C_set_timeout(uint16_t timeout);
/*! \brief Change I2C message retries (in case of failure)
* \param [in] retries - I2C number of retries (max of 8)
* \return true if change is successful (false otherwise)
*/
extern bool I2C_set_retries(uint8_t retries);
/*! \brief Get I2C busy status
* \return true if busy
*/
extern bool I2C_is_busy(void);
/*! \brief This function writes the provided data to the address specified.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in] data - pointer to the first byte of a block of data to write
* \param [in] bytes - indicates how many bytes of data to write
* \return I2C_STATUS status of write attempt
*/
extern I2C_STATUS I2C_write(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes);
/*! \brief This inline is a wrapper to I2C_write in case of contigous operations
* \attribute inline
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] data - pointer to the first byte of a block of data to write
* \param [in] bytes - indicates how many bytes of data to write
* \return I2C_STATUS status of write attempt
*/
inline I2C_STATUS __attribute__((__always_inline__)) I2C_write_next(I2C_SLAVE * slave, uint8_t * data, uint16_t bytes)
{
// TODO: implement read next so that it doesn't have to send start register address again
return I2C_write(slave, slave->reg_addr, data, bytes);
}
/*! \brief This function reads data from the address specified and stores this
* data in the area provided by the pointer.
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] reg_addr - register address in register map
* \param [in, out] data - pointer to the first byte of a block of data to read
* \param [in] bytes - indicates how many bytes of data to read
* \return I2C_STATUS status of read attempt
*/
extern I2C_STATUS I2C_read(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes);
/*! \brief This inline is a wrapper to I2C_read in case of contigous operations
* \attribute inline
* \param [in, out] slave - pointer to the I2C slave structure
* \param [in] data - pointer to the first byte of a block of data to read
* \param [in] bytes - indicates how many bytes of data to read
* \return I2C_STATUS status of read attempt
*/
inline I2C_STATUS __attribute__((__always_inline__)) I2C_read_next(I2C_SLAVE * slave, uint8_t * data, uint16_t bytes)
{
// TODO: implement read next so that it doesn't have to send start register address again
return I2C_read(slave, slave->reg_addr, data, bytes);
}
/***********************************/
/*** cI2C LOW LEVEL FUNCTIONS ***/
/*** THAT MAY BE USEFUL FOR DVPT ***/
/***********************************/
/*! \brief I2C bus reset (Release SCL and SDA lines and re-enable module)
* \return nothing
*/
extern void I2C_reset(void);
/*! \brief Send start condition
* \return true if start condition acknowledged (false otherwise)
*/
extern bool I2C_start(void);
/*! \brief Send stop condition
* \return true if stop condition acknowledged (false otherwise)
*/
extern bool I2C_stop(void);
/*! \brief Send byte on bus
* \param [in] dat - data to be sent
* \return true if data sent acknowledged (false otherwise)
*/
extern bool I2C_wr8(uint8_t dat);
/*! \brief Receive byte from bus
* \param [in] ack - true if wait for ack
* \return true if data reception acknowledged (false otherwise)
*/
extern uint8_t I2C_rd8(bool ack);
/*! \brief Send I2C address
* \param [in] slave - pointer to the I2C slave structure
* \param [in] rw - read/write transaction
* \return true if I2C chip address sent acknowledged (false otherwise)
*/
extern bool I2C_sndAddr(I2C_SLAVE * slave, I2C_RW rw);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -7,7 +7,7 @@
This example code is in the public domain.
created Jan 12 2017
latest mod Jan 31 2017
latest mod Nov 30 2017
by SMFSW
*/
@ -24,8 +24,8 @@ void setup() {
Serial.begin(115200); // start serial for output
I2C_init(I2C_FM); // init with Fast Mode (400KHz)
I2C_slave_init(&FRAM, 0x50, I2C_16B_REG);
I2C_slave_set_rw_func(&FRAM, I2C_wr_advanced, I2C_WRITE);
I2C_slave_set_rw_func(&FRAM, I2C_rd_advanced, I2C_READ);
I2C_slave_set_rw_func(&FRAM, (ci2c_fct_ptr) I2C_wr_advanced, I2C_WRITE);
I2C_slave_set_rw_func(&FRAM, (ci2c_fct_ptr) I2C_rd_advanced, I2C_READ);
I2C_get_chip_id(&FRAM, &str[0]);
@ -62,14 +62,16 @@ void loop() {
* \param [in] bytes - indicates how many bytes of data to write
* \return Boolean indicating success/fail of write attempt
*/
bool I2C_wr_advanced(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
bool I2C_wr_advanced(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes)
{
slave->reg_addr = reg_addr;
if (bytes == 0) { return false; }
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if (slave->cfg.reg_size)
if ((slave->cfg.reg_size) && (reg_addr != slave->reg_addr)) // Don't send address if writing next
{
slave->reg_addr = reg_addr;
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_wr8((uint8_t) (reg_addr >> 8)) == false) { return false; }
@ -96,14 +98,14 @@ bool I2C_wr_advanced(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint1
* \param [in] bytes - indicates how many bytes of data to read
* \return Boolean indicating success/fail of read attempt
*/
bool I2C_rd_advanced(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t bytes)
bool I2C_rd_advanced(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes)
{
slave->reg_addr = reg_addr;
if (bytes == 0) { return false; }
if (bytes == 0) { bytes = 1; }
if (slave->cfg.reg_size)
if ((slave->cfg.reg_size) && (reg_addr != slave->reg_addr)) // Don't send address if reading next
{
slave->reg_addr = reg_addr;
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used

View File

@ -1,7 +1,7 @@
name=cI2C
version=0.5
author=SMFSW
maintainer=SMFSW
version=1.3
author=SMFSW <xgarmanboziax@gmail.com>
maintainer=SMFSW <xgarmanboziax@gmail.com>
sentence=Arduino Hardware I2C for AVR (in plain c)
paragraph=Hardware I2C library for AVR MCUs (lib intended for I2C protocols development in c, for easier ports to other MCUs)
category=Communication

453
src/ci2c.c Executable file
View File

@ -0,0 +1,453 @@
/*!\file ci2c.c
** \author SMFSW
** \copyright MIT SMFSW (2017-2018)
** \brief arduino master i2c in plain c code
** \warning Don't access (r/w) last 16b internal address byte alone right after init, this would lead to hazardous result (in such case, make a dummy read of addr 0 before)
**/
// TODO: add interrupt vector / callback for it operations (if not too messy)
// TODO: consider interrupts at least for RX when slave (and TX when master)
#include "ci2c.h"
#define START 0x08
#define REPEATED_START 0x10
#define MT_SLA_ACK 0x18
#define MT_SLA_NACK 0x20
#define MT_DATA_ACK 0x28
#define MT_DATA_NACK 0x30
#define MR_SLA_ACK 0x40
#define MR_SLA_NACK 0x48
#define MR_DATA_ACK 0x50
#define MR_DATA_NACK 0x58
#define LOST_ARBTRTN 0x38
#define TWI_STATUS (TWSR & 0xF8)
//#define isSetRegBit(r, b) ((r & (1 << b)) != 0)
//#define isClrRegBit(r, b) ((r & (1 << b)) == 0)
#define setRegBit(r, b) r |= (1 << b) //!< set bit \b b in register \b r
#define clrRegBit(r, b) r &= (uint8_t) (~(1 << b)) //!< clear bit \b b in register \b r
#define invRegBit(r, b) r ^= (1 << b) //!< invert bit \b b in register \b r
/*!\struct i2c
** \brief static ci2c bus config and control parameters
**/
static struct {
/*!\struct cfg
** \brief ci2c bus parameters
**/
struct {
I2C_SPEED speed; //!< i2c bus speed
uint8_t retries; //!< i2c message retries when fail
uint16_t timeout; //!< i2c timeout (ms)
} cfg;
uint16_t start_wait; //!< time start waiting for acknowledge
bool busy; //!< true if already busy (in case of interrupts implementation)
} i2c = { { (I2C_SPEED) 0, DEF_CI2C_NB_RETRIES, DEF_CI2C_TIMEOUT }, 0, false };
// Needed prototypes
static bool I2C_wr(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes);
static bool I2C_rd(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes);
/*!\brief Init an I2C slave structure for cMI2C communication
** \param [in] slave - pointer to the I2C slave structure to init
** \param [in] sl_addr - I2C slave address
** \param [in] reg_sz - internal register map size
** \return nothing
**/
void I2C_slave_init(I2C_SLAVE * slave, const uint8_t sl_addr, const I2C_INT_SIZE reg_sz)
{
(void) I2C_slave_set_addr(slave, sl_addr);
(void) I2C_slave_set_reg_size(slave, reg_sz);
I2C_slave_set_rw_func(slave, (ci2c_fct_ptr) I2C_wr, I2C_WRITE);
I2C_slave_set_rw_func(slave, (ci2c_fct_ptr) I2C_rd, I2C_READ);
slave->reg_addr = (uint16_t) -1; // To be sure to send address on first access (warning: unless last 16b byte address is accessed alone)
slave->status = I2C_OK;
}
/*!\brief Redirect slave I2C read/write function (if needed for advanced use)
** \param [in] slave - pointer to the I2C slave structure to init
** \param [in] func - pointer to read/write function to affect
** \param [in] rw - 0 = write function, 1 = read function
** \return nothing
**/
void I2C_slave_set_rw_func(I2C_SLAVE * slave, const ci2c_fct_ptr func, const I2C_RW rw)
{
ci2c_fct_ptr * pfc = (ci2c_fct_ptr*) (rw ? &slave->cfg.rd : &slave->cfg.wr);
*pfc = func;
}
/*!\brief Change I2C slave address
** \param [in, out] slave - pointer to the I2C slave structure to init
** \param [in] sl_addr - I2C slave address
** \return true if new address set (false if address is >7Fh)
**/
bool I2C_slave_set_addr(I2C_SLAVE * slave, const uint8_t sl_addr)
{
if (sl_addr > 0x7F) { return false; }
slave->cfg.addr = sl_addr;
return true;
}
/*!\brief Change I2C registers map size (for access)
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_sz - internal register map size
** \return true if new size is correct (false otherwise and set to 16bit by default)
**/
bool I2C_slave_set_reg_size(I2C_SLAVE * slave, const I2C_INT_SIZE reg_sz)
{
slave->cfg.reg_size = reg_sz > I2C_16B_REG ? I2C_16B_REG : reg_sz;
return !(reg_sz > I2C_16B_REG);
}
/*!\brief Set I2C current register address
** \attribute inline
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address
** \return nothing
**/
static inline void __attribute__((__always_inline__)) I2C_slave_set_reg_addr(I2C_SLAVE * slave, const uint16_t reg_addr) {
slave->reg_addr = reg_addr; }
/*!\brief Enable I2c module on arduino board (including pull-ups,
* enabling of ACK, and setting clock frequency)
** \param [in] speed - I2C bus speed in KHz
** \return nothing
**/
void I2C_init(const uint16_t speed)
{
// Set SDA and SCL to ports with pull-ups
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
setRegBit(PORTC, 4);
setRegBit(PORTC, 5);
#else
setRegBit(PORTD, 0);
setRegBit(PORTD, 1);
#endif
(void) I2C_set_speed(speed);
}
/*!\brief Disable I2c module on arduino board (releasing pull-ups, and TWI control)
** \return nothing
**/
void I2C_uninit()
{
// Release SDA and SCL ports pull-ups
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
clrRegBit(PORTC, 4);
clrRegBit(PORTC, 5);
#else
clrRegBit(PORTD, 0);
clrRegBit(PORTD, 1);
#endif
TWCR = 0;
}
/*!\brief I2C bus reset (Release SCL and SDA lines and re-enable module)
** \return nothing
**/
void I2C_reset(void)
{
TWCR = 0;
setRegBit(TWCR, TWEA);
setRegBit(TWCR, TWEN);
}
/*!\brief Change I2C frequency
** \param [in] speed - I2C speed in KHz (max 400KHz on avr)
** \return Configured bus speed
**/
uint16_t I2C_set_speed(const uint16_t speed)
{
i2c.cfg.speed = (I2C_SPEED) ((speed == 0) ? (uint16_t) I2C_STD : ((speed > (uint16_t) I2C_FM) ? (uint16_t) I2C_FM : speed));
clrRegBit(TWCR, TWEN); // Ensure i2c module is disabled
// Set prescaler and clock frequency
clrRegBit(TWSR, TWPS0);
clrRegBit(TWSR, TWPS1);
TWBR = (((F_CPU / 1000) / i2c.cfg.speed) - 16) / 2;
I2C_reset(); // re-enable module
return i2c.cfg.speed;
}
/*!\brief Change I2C ack timeout
** \param [in] timeout - I2C ack timeout (500 ms max)
** \return Configured timeout
**/
uint16_t I2C_set_timeout(const uint16_t timeout)
{
static const uint16_t max_timeout = 500;
i2c.cfg.timeout = (timeout > max_timeout) ? max_timeout : timeout;
return i2c.cfg.timeout;
}
/*!\brief Change I2C message retries (in case of failure)
** \param [in] retries - I2C number of retries (max of 8)
** \return Configured number of retries
**/
uint8_t I2C_set_retries(const uint8_t retries)
{
static const uint16_t max_retries = 8;
i2c.cfg.retries = (retries > max_retries) ? max_retries : retries;
return i2c.cfg.retries;
}
/*!\brief Get I2C busy status
** \return true if busy
**/
bool I2C_is_busy(void) {
return i2c.busy; }
/*!\brief This function reads or writes the provided data to/from the address specified.
* If anything in the write process is not successful, then it will be repeated
* up till 3 more times (default). If still not successful, returns NACK
** \param [in, out] slave - pointer to the I2C slave structure to init
** \param [in] reg_addr - register address in register map
** \param [in] data - pointer to the first byte of a block of data to write
** \param [in] bytes - indicates how many bytes of data to write
** \param [in] rw - 0 = write, 1 = read operation
** \return I2C_STATUS status of write attempt
**/
static I2C_STATUS I2C_comm(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes, const I2C_RW rw)
{
uint8_t retry = i2c.cfg.retries;
bool ack = false;
ci2c_fct_ptr fc = (ci2c_fct_ptr) (rw ? slave->cfg.rd : slave->cfg.wr);
if (I2C_is_busy()) { return slave->status = I2C_BUSY; }
i2c.busy = true;
ack = fc(slave, reg_addr, data, bytes);
while ((!ack) && (retry != 0)) // If com not successful, retry some more times
{
delay(1);
ack = fc(slave, reg_addr, data, bytes);
retry--;
}
i2c.busy = false;
return slave->status = ack ? I2C_OK : I2C_NACK;
}
/*!\brief This function writes the provided data to the address specified.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in] data - pointer to the first byte of a block of data to write
** \param [in] bytes - indicates how many bytes of data to write
** \return I2C_STATUS status of write attempt
**/
I2C_STATUS I2C_write(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes) {
return I2C_comm(slave, reg_addr, data, bytes, I2C_WRITE); }
/*!\brief This function reads data from the address specified and stores this
* data in the area provided by the pointer.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in, out] data - pointer to the first byte of a block of data to read
** \param [in] bytes - indicates how many bytes of data to read
** \return I2C_STATUS status of read attempt
**/
I2C_STATUS I2C_read(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes) {
return I2C_comm(slave, reg_addr, data, bytes, I2C_READ); }
/*!\brief Start i2c_timeout timer
** \attribute inline
** \return nothing
**/
static inline void __attribute__((__always_inline__)) I2C_start_timeout(void) {
i2c.start_wait = (uint16_t) millis(); }
/*!\brief Test i2c_timeout
** \attribute inline
** \return true if i2c_timeout occured (false otherwise)
**/
static inline uint8_t __attribute__((__always_inline__)) I2C_timeout(void) {
return (((uint16_t) millis() - i2c.start_wait) >= i2c.cfg.timeout); }
/*!\brief Send start condition
** \return true if start condition acknowledged (false otherwise)
**/
bool I2C_start(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == START) || (TWI_STATUS == REPEATED_START)) { return true; }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); }
return false;
}
/*!\brief Send stop condition
** \return true if stop condition acknowledged (false otherwise)
**/
bool I2C_stop(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
while ((TWCR & (1 << TWSTO)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
return true;
}
/*!\brief Send byte on bus
** \param [in] dat - data to be sent
** \return true if data sent acknowledged (false otherwise)
**/
bool I2C_wr8(const uint8_t dat)
{
TWDR = dat;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == MT_DATA_ACK) { return true; }
if (TWI_STATUS == MT_DATA_NACK) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*!\brief Receive byte from bus
** \param [in] ack - true if wait for ack
** \return true if data reception acknowledged (false otherwise)
**/
uint8_t I2C_rd8(const bool ack)
{
I2C_start_timeout();
if (ack) { TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA); }
else { TWCR = (1 << TWINT) | (1 << TWEN); }
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); return false; }
return ((((TWI_STATUS == MR_DATA_NACK) && (!ack)) || ((TWI_STATUS == MR_DATA_ACK) && (ack))) ? true : false);
}
/*!\brief Send I2C address
** \param [in] slave - pointer to the I2C slave structure
** \param [in] rw - read/write transaction
** \return true if I2C chip address sent acknowledged (false otherwise)
**/
bool I2C_sndAddr(I2C_SLAVE * slave, const I2C_RW rw)
{
TWDR = (slave->cfg.addr << 1) | rw;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == MT_SLA_ACK) || (TWI_STATUS == MR_SLA_ACK)) { return true; }
if ((TWI_STATUS == MT_SLA_NACK) || (TWI_STATUS == MR_SLA_NACK)) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*!\brief This procedure calls appropriate functions to perform a proper send transaction on I2C bus.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in] data - pointer to the first byte of a block of data to write
** \param [in] bytes - indicates how many bytes of data to write
** \return Boolean indicating success/fail of write attempt
**/
static bool I2C_wr(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes)
{
if (bytes == 0) { return false; }
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if ((slave->cfg.reg_size) && (reg_addr != slave->reg_addr)) // Don't send address if writing next
{
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_wr8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_wr8((uint8_t) reg_addr) == false) { return false; }
}
for (uint16_t cnt = 0; cnt < bytes; cnt++)
{
if (I2C_wr8(*data++) == false) { return false; }
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}
/*!\brief This procedure calls appropriate functions to perform a proper receive transaction on I2C bus.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in, out] data - pointer to the first byte of a block of data to read
** \param [in] bytes - indicates how many bytes of data to read
** \return Boolean indicating success/fail of read attempt
**/
static bool I2C_rd(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes)
{
if (bytes == 0) { return false; }
if ((slave->cfg.reg_size) && (reg_addr != slave->reg_addr)) // Don't send address if reading next
{
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_WRITE) == false) { return false; }
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_wr8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_wr8((uint8_t) reg_addr) == false) { return false; }
}
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave, I2C_READ) == false) { return false; }
for (uint16_t cnt = 0; cnt < bytes; cnt++)
{
if (I2C_rd8((cnt == (bytes - 1)) ? false : true) == false) { return false; }
*data++ = TWDR;
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}

281
src/ci2c.h Executable file
View File

@ -0,0 +1,281 @@
/*!\file ci2c.h
** \author SMFSW
** \copyright MIT SMFSW (2017-2018)
** \brief arduino i2c in plain c declarations
** \warning Don't access (r/w) last 16b internal address byte alone right after init, this would lead to hazardous result (in such case, make a dummy read of addr 0 before)
**/
/****************************************************************/
#ifndef __CI2C_H__
#define __CI2C_H__
/****************************************************************/
#if defined(DOXY)
// Define gcc __attribute__ as void when Doxygen runs
#define __attribute__(a) //!< GCC attribute (ignored by Doxygen)
#endif
#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include <inttypes.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define DEF_CI2C_NB_RETRIES 3 //!< Default cI2C transaction retries
#define DEF_CI2C_TIMEOUT 100 //!< Default cI2C timeout
/*!\enum enI2C_RW
** \brief I2C RW bit enumeration
** \attribute packed enum
**/
typedef enum __attribute__((__packed__)) enI2C_RW {
I2C_WRITE = 0, //!< I2C rw bit (write)
I2C_READ //!< I2C rw bit (read)
} I2C_RW;
/*!\enum enI2C_SPEED
** \brief I2C bus speed
** \attribute packed enum
**/
typedef enum __attribute__((__packed__)) enI2C_SPEED {
I2C_STD = 100, //!< I2C Standard (100KHz)
I2C_FM = 400, //!< I2C Fast Mode (400KHz)
I2C_FMP = 1000, //!< I2C Fast mode + (1MHz): will set speed to Fast Mode (up to 400KHz on avr)
I2C_HS = 3400 //!< I2C High Speed (3.4MHz): will set speed to Fast Mode (up to 400KHz on avr)
} I2C_SPEED;
/*!\enum enI2C_STATUS
** \brief I2C slave status
** \attribute packed enum
**/
typedef enum __attribute__((__packed__)) enI2C_STATUS {
I2C_OK = 0x00, //!< I2C OK
I2C_BUSY, //!< I2C Bus busy
I2C_NACK //!< I2C Not Acknowledge
} I2C_STATUS;
/*!\enum enI2C_INT_SIZE
** \brief I2C slave internal address registers size
** \attribute packed enum
**/
typedef enum __attribute__((__packed__)) enI2C_INT_SIZE {
I2C_NO_REG = 0x00, //!< Internal address registers not applicable for slave
I2C_8B_REG, //!< Slave internal address registers space is 8bits wide
I2C_16B_REG //!< Slave internal address registers space is 16bits wide
} I2C_INT_SIZE;
typedef bool (*ci2c_fct_ptr) (void*, const uint16_t, uint8_t*, const uint16_t); //!< i2c read/write function pointer typedef
/*!\struct StructI2CSlave
** \brief ci2c slave config and control parameters
** \attribute packed struct
**/
typedef struct __attribute__((__packed__)) StructI2CSlave {
/*!\struct cfg
** \brief ci2c slave parameters
**/
struct {
uint8_t addr; //!< Slave address
I2C_INT_SIZE reg_size; //!< Slave internal registers size
ci2c_fct_ptr wr; //!< Slave write function pointer
ci2c_fct_ptr rd; //!< Slave read function pointer
} cfg;
uint16_t reg_addr; //!< Internal current register address
I2C_STATUS status; //!< Status of the last communications
} I2C_SLAVE;
/***************************/
/*** I2C SLAVE FUNCTIONS ***/
/***************************/
/*!\brief Init an I2C slave structure for cMI2C communication
** \param [in] slave - pointer to the I2C slave structure to init
** \param [in] sl_addr - I2C slave address
** \param [in] reg_sz - internal register map size
** \return nothing
**/
void I2C_slave_init(I2C_SLAVE * slave, const uint8_t sl_addr, const I2C_INT_SIZE reg_sz);
/*!\brief Redirect slave I2C read/write function (if needed for advanced use)
** \param [in] slave - pointer to the I2C slave structure to init
** \param [in] func - pointer to read/write function to affect
** \param [in] rw - 0 = write function, 1 = read function
** \return nothing
**/
void I2C_slave_set_rw_func(I2C_SLAVE * slave, const ci2c_fct_ptr func, const I2C_RW rw);
/*!\brief Change I2C slave address
** \param [in, out] slave - pointer to the I2C slave structure to init
** \param [in] sl_addr - I2C slave address
** \return true if new address set (false if address is >7Fh)
**/
bool I2C_slave_set_addr(I2C_SLAVE * slave, const uint8_t sl_addr);
/*!\brief Change I2C registers map size (for access)
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_sz - internal register map size
** \return true if new size is correct (false otherwise and set to 16bit by default)
**/
bool I2C_slave_set_reg_size(I2C_SLAVE * slave, const I2C_INT_SIZE reg_sz);
/*!\brief Get I2C slave address
** \attribute inline
** \param [in] slave - pointer to the I2C slave structure
** \return I2C slave address
**/
inline uint8_t __attribute__((__always_inline__)) I2C_slave_get_addr(const I2C_SLAVE * slave) {
return slave->cfg.addr; }
/*!\brief Get I2C register map size (for access)
** \attribute inline
** \param [in] slave - pointer to the I2C slave structure
** \return register map using 16bits if true (1Byte otherwise)
**/
inline bool __attribute__((__always_inline__)) I2C_slave_get_reg_size(const I2C_SLAVE * slave) {
return slave->cfg.reg_size; }
/*!\brief Get I2C current register address (addr may passed this way in procedures if contigous accesses)
** \attribute inline
** \param [in] slave - pointer to the I2C slave structure
** \return current register map address
**/
inline uint16_t __attribute__((__always_inline__)) I2C_slave_get_reg_addr(const I2C_SLAVE * slave) {
return slave->reg_addr; }
/*************************/
/*** I2C BUS FUNCTIONS ***/
/*************************/
/*!\brief Enable I2c module on arduino board (including pull-ups,
* enabling of ACK, and setting clock frequency)
** \param [in] speed - I2C bus speed in KHz
** \return nothing
**/
void I2C_init(const uint16_t speed);
/*!\brief Disable I2c module on arduino board (releasing pull-ups, and TWI control)
** \return nothing
**/
void I2C_uninit();
/*!\brief Change I2C frequency
** \param [in] speed - I2C bus speed in KHz (max 400KHz on AVR)
** \return Configured bus speed
**/
uint16_t I2C_set_speed(const uint16_t speed);
/*!\brief Change I2C ack timeout
** \param [in] timeout - I2C ack timeout (500 ms max)
** \return Configured timeout
**/
uint16_t I2C_set_timeout(const uint16_t timeout);
/*!\brief Change I2C message retries (in case of failure)
** \param [in] retries - I2C number of retries (max of 8)
** \return Configured number of retries
**/
uint8_t I2C_set_retries(const uint8_t retries);
/*!\brief Get I2C busy status
** \return true if busy
**/
bool I2C_is_busy(void);
/*!\brief This function writes the provided data to the address specified.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in] data - pointer to the first byte of a block of data to write
** \param [in] bytes - indicates how many bytes of data to write
** \return I2C_STATUS status of write attempt
**/
I2C_STATUS I2C_write(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes);
/*!\brief This inline is a wrapper to I2C_write in case of contigous operations
** \attribute inline
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] data - pointer to the first byte of a block of data to write
** \param [in] bytes - indicates how many bytes of data to write
** \return I2C_STATUS status of write attempt
**/
inline I2C_STATUS __attribute__((__always_inline__)) I2C_write_next(I2C_SLAVE * slave, uint8_t * data, const uint16_t bytes) {
return I2C_write(slave, slave->reg_addr, data, bytes); }
/*!\brief This function reads data from the address specified and stores this
* data in the area provided by the pointer.
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] reg_addr - register address in register map
** \param [in, out] data - pointer to the first byte of a block of data to read
** \param [in] bytes - indicates how many bytes of data to read
** \return I2C_STATUS status of read attempt
**/
I2C_STATUS I2C_read(I2C_SLAVE * slave, const uint16_t reg_addr, uint8_t * data, const uint16_t bytes);
/*!\brief This inline is a wrapper to I2C_read in case of contigous operations
** \attribute inline
** \param [in, out] slave - pointer to the I2C slave structure
** \param [in] data - pointer to the first byte of a block of data to read
** \param [in] bytes - indicates how many bytes of data to read
** \return I2C_STATUS status of read attempt
**/
inline I2C_STATUS __attribute__((__always_inline__)) I2C_read_next(I2C_SLAVE * slave, uint8_t * data, const uint16_t bytes) {
return I2C_read(slave, slave->reg_addr, data, bytes); }
/***********************************/
/*** cI2C LOW LEVEL FUNCTIONS ***/
/*** THAT MAY BE USEFUL FOR DVPT ***/
/***********************************/
/*!\brief I2C bus reset (Release SCL and SDA lines and re-enable module)
** \return nothing
**/
void I2C_reset(void);
/*!\brief Send start condition
** \return true if start condition acknowledged (false otherwise)
**/
bool I2C_start(void);
/*!\brief Send stop condition
** \return true if stop condition acknowledged (false otherwise)
**/
bool I2C_stop(void);
/*!\brief Send byte on bus
** \param [in] dat - data to be sent
** \return true if data sent acknowledged (false otherwise)
**/
bool I2C_wr8(const uint8_t dat);
/*!\brief Receive byte from bus
** \param [in] ack - true if wait for ack
** \return true if data reception acknowledged (false otherwise)
**/
uint8_t I2C_rd8(const bool ack);
/*!\brief Send I2C address
** \param [in] slave - pointer to the I2C slave structure
** \param [in] rw - read/write transaction
** \return true if I2C chip address sent acknowledged (false otherwise)
**/
bool I2C_sndAddr(I2C_SLAVE * slave, const I2C_RW rw);
#ifdef __cplusplus
}
#endif
#endif