Skip to content

Instantly share code, notes, and snippets.

@ChuckM
Created March 11, 2017 01:31
Show Gist options
  • Save ChuckM/c4f83d30b10729179b1b87b3fb8d996a to your computer and use it in GitHub Desktop.
Save ChuckM/c4f83d30b10729179b1b87b3fb8d996a to your computer and use it in GitHub Desktop.
i2c utility functions
/*
* Look at using the i2c functions in the library
*
* This example talks to the XY screen touch controller
* (its a peripheral already on the board) and prints
* the co-ordinates pressed on the serial port.
*/
#include <stdint.h>
#include <stdio.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/cortex.h>
#include <utilloc3/stm32/console.h>
#include <utilloc3/stm32/term.h>
#include <utilloc3/stm32/pins.h>
#include <utilloc3/stm32/clock.h>
#include <utilloc3/stm32/i2c.h>
enum I2C_FEATURE {
I2C_CLOCK, I2C_AF, I2C_NAME
};
#define MY_I2C_TIMEOUT 100000
static uint32_t i2c_pin_map(PIN pin, enum I2C_FEATURE attr);
/*
* i2c Event interrupt
*/
void
i2c3_ev_isr(void) {
}
/*
* i2c Error interrupt
*/
void
i2c3_er_isr(void) {
}
#define MAX_I2C_CHANNELS 3
static uint32_t i2c_channels[MAX_I2C_CHANNELS];
static int nxt_channel = 0;
/*
* Initialize an I2C port.
*
* If input 'fast' is true, initialize it for
* 400Khz operation, if it is false, initialize
* for "regular" or 100Khz operation.
*/
int
i2c_master(PIN sclk, PIN sda, int speed)
{
uint32_t dev;
uint32_t f;
dev = i2c_pin_map(sclk, I2C_NAME);
if (dev == 0) {
return -1; /* not a legal i2c pin that we know about */
}
if (dev != i2c_pin_map(sda, I2C_NAME)) {
return -1; /* both pins aren't on same I2C port */
}
if (nxt_channel >= MAX_I2C_CHANNELS) {
return -1; /* we can't assign any more */
}
rcc_periph_clock_enable(i2c_pin_map(sclk, I2C_CLOCK));
pin_function(i2c_pin_map(sclk, I2C_AF), sclk, PXX);
pin_function(i2c_pin_map(sda, I2C_AF), sda, PXX);
f = rcc_apb1_frequency / 1000000;
/* disable the peripheral to set clocks */
I2C_CR1(dev) = I2C_CR1(dev) & ~(I2C_CR1_PE);
/* during init this check is fast enough */
if ((f < 2) || (f > 42)) {
return -1; /* can't run */
}
I2C_CR2(dev) = (f & 0x3f); /* XXX don't bother with DMA or interrupts yet */
/*
* Compute the clock delay,
* in normal mode this is (Freq(APB1) / 100Khz) / 2
* in fast mode its either
* - (Freq(APB1) / 400khz) / 3 (Duty 0)
* - (Freq(APB1) / 400khz) / 25 (Duty 1)
* Duty 1 is the 9:16 ratio instead of 2:1 "regular"
* duty cycle mode (Duty = 0). For now we only support
* regular duty cycle mode until I can figure out when
* you would need 9:16 mode.
*
*/
if (speed == I2C_400KHZ) {
/* f x (1000 / 400) / 3 == f x 5 / 6 */
I2C_CCR(dev) = 0x8000 | (((f * 5) / 6) & 0xfff);
} else {
/* f x (1000 / 100) / 2 == f x 5 */
I2C_CCR(dev) = (f * 5) & 0xfff;
}
I2C_TRISE(dev) = (f + 1) & 0x3f;
/* enable the peripheral */
I2C_CR1(dev) |= I2C_CR1_PE;
i2c_channels[nxt_channel] = dev;
nxt_channel ++;
return nxt_channel - 1;
}
/*
* Write data to i2c.
*
* Write bytes to the address 'addr' and return the number of
* bytes written (including the addr byte). Returns -1 on error.
*/
int
write_to_i2c(int chan, uint8_t addr, uint8_t *buf, size_t buf_size, int stop)
{
uint32_t dev = i2c_channels[chan];
unsigned int i;
int sent;
uint8_t *cur_val;
/* send start */
I2C_CR1(dev) |= (I2C_CR1_START | I2C_CR1_PE);
while ((I2C_SR1(dev) & I2C_SR1_SB) == 0) ;
/* send target address */
I2C_DR(dev) = (addr & 0xfe);
for (i = 0; i < MY_I2C_TIMEOUT; i++) {
if (I2C_SR1(dev) & I2C_SR1_ADDR) {
break;
}
}
if (i >= MY_I2C_TIMEOUT) {
/* if we exited due to time-out, return to caller */
return -1;
}
(void) I2C_SR2(dev); /* clears ADDR bit */
sent = 1;
for (i = 0, cur_val = buf; i < buf_size; i++, sent++) {
while ((I2C_SR1(dev) & (I2C_SR1_TxE | I2C_SR1_BTF)) == 0) ;
I2C_DR(dev) = *cur_val;
cur_val++;
}
/* wait for last byte to drain */
while ((I2C_SR1(dev) & (I2C_SR1_TxE | I2C_SR1_BTF)) == 0) ;
if (stop) {
I2C_CR1(dev) |= I2C_CR1_STOP;
}
return sent;
}
static uint8_t short_buf[2];
/* helper function for 2 byte writes */
uint16_t
write_two_bytes(int chan, uint8_t addr, uint8_t val1, uint8_t val2) {
short_buf[0] = val1;
short_buf[1] = val2;
return write_to_i2c(chan, addr, short_buf, 2, I2C_STOP);
}
/* helper function for 1 byte writes */
uint16_t
write_one_byte(int chan, uint8_t addr, uint8_t val) {
short_buf[0] = val;
return write_to_i2c(chan, addr, short_buf, 1, I2C_STOP);
}
/*
* The basic read bytes function.
*
* this function returns the number of bytes read
* if it is successful, 0 otherwise.
*
* Send the address with the low bit set (master receiver mode)
* and then ack bytes until we get buf_size bytes. There is some
* trickyness around 1 and 2 byte reads as the state machine has a
* hard time doing the correct NAK vs ACK vs STOP vs ADDR generation.
* If I understand the manual correctly, ACK must be set before you
* acknowledge address mode by reading CR2. Doing that means that the
* next byte you read will get an ACK and the slave will then send
* another.
*
* How ever, if you are ONLY going to receive one byte, you don't set
* ACK at all and the first byte you get back is NAK'd so the slave
* sends only 1 byte.
*
* In (all?) cases you want to set STOP *before* you get the last byte
* so when the slave releases SDA/SCL you'll stop. This is the opposite
* of write where you only set STOP after you've sent the last byte or
* you will stop prematurely.
*
* There is some noise that if you set POS then you're interface will
* ACK the first byte you get and NAK the second. But that seems a bit
* too tricky? When just keeping track of ACK does what you want?
*/
int
read_from_i2c(int chan, uint8_t addr, uint8_t *buf, size_t buf_size, int stop)
{
unsigned int i;
int read;
uint16_t status;
uint8_t *buf_ptr;
uint32_t dev = i2c_channels[chan];
read = 0;
I2C_CR1(dev) |= (I2C_CR1_START | I2C_CR1_PE);
while ((I2C_SR1(dev) & I2C_SR1_SB) == 0) ;
/* send address with bit 0 set (read) */
I2C_DR(dev) = addr | 0x1;
for (i = 0; i < MY_I2C_TIMEOUT; i++) {
status = I2C_SR1(dev);
if ((status & I2C_SR1_ADDR)) {
break;
}
}
if (i >= MY_I2C_TIMEOUT) {
return -1;
}
buf_ptr = buf;
read = 0;
if (buf_size > 1) {
I2C_CR1(dev) |= I2C_CR1_ACK;
status = I2C_SR2(dev); /* Clear ADDR flag */
/* read all bytes before last */
for (i = 0; i < (buf_size - 1); i++) {
while ((I2C_SR1(dev) & I2C_SR1_RxNE) == 0) ;
*buf_ptr++ = I2C_DR(dev);
read++;
}
/* turn off ACK for last byte */
I2C_CR1(dev) &= ~(I2C_CR1_ACK);
} else {
status = I2C_SR2(dev); /* Clear ADDR */
}
if (stop == I2C_STOP) {
I2C_CR1(dev) |= I2C_CR1_STOP;
}
while ((I2C_SR1(dev) & I2C_SR1_RxNE) == 0) ;
*buf_ptr++ = I2C_DR(dev);
read++;
return read;
}
/*
* Write register
*
* Convience function for writing a "register" in an i2c device which
* is a common modality for i2c. The parameters are a bit bit register
* "number" an 8 bit address, and a value which is 1 - 4 bytes in size.
*/
int
write_register_i2c(int chan, uint8_t addr, uint8_t reg, uint32_t val, size_t size)
{
static uint8_t buf[5]; /* potentially an address byte and a 4 byte value */
unsigned int i;
uint32_t tval;
if ((size > 4) || (i2c_channels[chan] == 0)) {
return -1;
}
buf[0] = reg;
for (i = size, tval = val; i > 0; i--, tval >>= 8) {
buf[i] = tval & 0xff;
}
return write_to_i2c(chan, addr, buf, size+1, I2C_STOP);
}
int
read_register_i2c(int chan, uint8_t addr, uint8_t reg, uint32_t *val, size_t size)
{
static uint8_t buf[5];
unsigned int i;
uint32_t tval;
if ((size > 4) || (i2c_channels[chan] == 0)) {
return -1;
}
buf[0] = reg;
if (write_to_i2c(chan, addr, buf, 1, I2C_NO_STOP) == -1) {
return -1;
}
if (read_from_i2c(chan, addr, buf, size, I2C_STOP) == -1) {
return -1;
}
for (i = 0, tval = 0; i < size; i++) {
tval <<= 8;
tval |= buf[i];
}
*val = tval;
return 0;
}
/*
* Needs lots of work to be filled in: XXX
* Helper function to return required attributes
* about the pins requested.
*/
static uint32_t
i2c_pin_map(PIN pin, enum I2C_FEATURE attr) {
switch (pin) {
case PB8:
switch (attr) {
case I2C_CLOCK:
return RCC_I2C1;
case I2C_AF:
return GPIO_AF4;
case I2C_NAME:
return I2C1;
default:
return 0;
}
case PB9:
switch (attr) {
case I2C_CLOCK:
return RCC_I2C1;
case I2C_AF:
return GPIO_AF4;
case I2C_NAME:
return I2C1;
default:
return 0;
}
case PA8:
switch (attr) {
case I2C_CLOCK:
return RCC_I2C3;
case I2C_AF:
return GPIO_AF4;
case I2C_NAME:
return I2C3;
default:
return 0;
}
case PC9:
switch (attr) {
case I2C_CLOCK:
return RCC_I2C3;
case I2C_AF:
return GPIO_AF4;
case I2C_NAME:
return I2C3;
default:
return 0;
}
default:
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment