Created
March 11, 2017 01:31
-
-
Save ChuckM/c4f83d30b10729179b1b87b3fb8d996a to your computer and use it in GitHub Desktop.
i2c utility functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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