AnsweredAssumed Answered

STM32F4 I2C Interface with LSM6DS3 -- Interrupts

Question asked by Broydon Stufko on Apr 23, 2018

Hey everyone,

 

I have been trying to interface with an LSM6DS3 (datasheet attached) via I2C using an STM32F407 Discovery.  I have managed to get the I2C initialized and have successfully read the Who Am I register on the LSM6DS3, but I believe the code is just barely functional.  This working code is as follows:

 

#include "stm32f4xx.h"

#define ADDR 0x6B

int who;
int state;
int *sr;
int *dr;
int *odr;
int tmp1;
int *cr1;

void tim6_delay(void);
void delay(int ms);

int main(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //enable clock GPIOB for I2C pins

RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

GPIOD->MODER |= 1<< 30 | 1<<28; //set PD15 as output
GPIOB->MODER &= ~(0xF << 15); //clear them pins
GPIOB->MODER |= 0xA000; //set PB7 and PB6 to Alternate Function
GPIOB->OTYPER |= 0x3<<7; //set PB7 and PB6 as open-drain
GPIOB->OSPEEDR |= 0xA000; //set output to high speed
GPIOB->PUPDR &= ~(0xF<<15); //no pull-up/down


GPIOB->AFR[0] |= 0x44000000; //set PB7 and PB6 to I2C1 Alt Function

RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //enable clock for I2C1
delay(2000); //let her warm up

/***** 400kHz clock setup ***/
I2C1->CR2 |= 0x14; //set clock frequency to 8 MHz for 400kHz operation
I2C1->CCR |= 1<<15; //set I2C1 to Fm mode
I2C1->CCR |= 1<<14; //set to 16/9
I2C1->CCR |= 0x2; //doing the math, Ttotal = 400kHz^-1 = 2.5us
//tlow = 1.6us
//thigh = 0.9us
I2C1->TRISE |= 0x7; //clock rise time is 300ns/50ns + 1
//*******************************

/************ 100 kHz clock settings ******
I2C1->CR2 |= 0x8; //set clock frequency to 8 MHz for 100kHz operation
I2C1->CCR &= ~(1<<15); //set I2C1 to Sm mode
I2C1->CCR |= 0x28; //Ttotal = 2 * CCR * Tpclk
//Tpclk = (8MHz)^-1 = 125 ns, and we want 100 kHz operation
//so we Ttotal = (100kHz)^-1 = 10 us
//solving for CCR, we get 40d or 0x28
I2C1->TRISE |= 0x9; //max rise time in Sm is 1000 ns
//this register value is determined by (max rise time/Tpclk) + 1
// (1000/125) + 1 = 9
//*****************/

//debugging variables for Keil uVision
sr = &(I2C1->SR1);
dr = &(I2C1->DR);
odr = &(GPIOB->ODR);
cr1 = &(I2C1->CR1);

I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN; //enable I2C interrupts

__enable_irq();
NVIC_SetPriority(I2C1_EV_IRQn,0);
NVIC_ClearPendingIRQ(I2C1_EV_IRQn);
NVIC_EnableIRQ(I2C1_EV_IRQn);


I2C1->CR1 |= 0x1; //enable peripheral
I2C1->CR1 |= I2C_CR1_ACK; //enable acknowledge bit
I2C1->CR1 |= I2C_CR1_START; //generate a start bit
state = 0;

while(1)
{
}


}

void I2C1_EV_IRQHandler(void)
{
if(I2C1->SR1 & I2C_SR1_SB)
{
tmp1 = I2C1->SR1; //CLEAR SB FLAG
if(state == 0) //write
{
I2C1->DR = ADDR << 1; //left shift slave address and leave a 0 in LSB for write
}
if(state==1) //read
{
I2C1->DR = ADDR<<1 | 1; //left shift slave address and put a 1 in LSB for a read
}
}
if(I2C1->SR1 & I2C_SR1_ADDR)
{
//READ SR1 AND SR2 TO CLEAR ADDR FLAG
if(state==1)
{
I2C1->CR1 &= ~I2C_CR1_ACK; //CLEAR ACK before ADDR is cleared
}
tmp1 = I2C1->SR1;
tmp1 = I2C1->SR2;
if(state==0)
{
I2C1->DR = 0X0F; //INTERNAL REG ADDRESS of Who Am I
}
}
if(I2C1->SR1 & I2C_SR1_TXE)
{
state = 1; //reading Who Am I register now
I2C1->CR1 |= I2C_CR1_START; //NEXT START BIT
}
if(I2C1->SR1 & I2C_SR1_RXNE)
{
who = I2C1->DR; //ASSIGN WHO THE VALUE of Who Am I
I2C1->CR1 |= I2C_CR1_STOP; //SEND STOP BIT
state = 0;
}
}

 


void tim6_delay(void){
// enable APB1 bus clock
RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
//TIM6 prescaler set at default to 0 for now
TIM6->PSC=0; // prescalar
TIM6->ARR = 21000; //auto reload register
TIM6->CNT=0; //clear counter register
TIM6->CR1|=TIM_CR1_CEN;
//WHEN COUNTER IS DONE THE TIM2_SR REG UIF FLAG IS SET
while(TIM6->SR==0);
TIM6->SR=0; //CLEAR uIF FLAG
}

/*******************************
* delay(int ms)
* Inputs: delay in milliseconds
* Outputs: NONE
* An approximate delay because
* call of tim2_delay() creates about 1.33ms
*******************************
*/
void delay(int ms){
int i;
for (i=ms; i>0; i--)
{
tim6_delay();
}
}

While this works for reading the Who Am I register, I don't believe it will be very portable at all to trying to read or write the other registers on the LSM6DS3.  I have been trying to retool my code to work in a more general fashion but I am running into a few problems.  Below is the new code where I have been trying to write a general function for writing the sub-register offset then reading the contents.

 

 

#include "stm32f4xx.h"

#define ADDR 0x6B

int who;
int state;
uint32_t *sr;
uint32_t *dr;
uint32_t *odr;
int tmp1;
uint32_t *cr1;
int read;

void tim6_delay(void);
void delay(int ms);
void GPIO_I2C_Init(void);
void I2C_Init(void);
void I2C_Write_Read(uint8_t internal_reg);

int main(void)
{
//initialize the gpio to alt function -- i2c
GPIO_I2C_Init();

//initialize the i2c parameters desired
I2C_Init();

I2C_Write_Read(0x0F);
while(1)
{
}


}

/*void I2C1_EV_IRQHandler(void)
{
if(I2C1->SR1 & I2C_SR1_SB)
{
tmp1 = I2C1->SR1; //CLEAR SB FLAG
}
if(I2C1->SR1 & I2C_SR1_ADDR)
{
//READ SR1 AND SR2 TO CLEAR ADDR FLAG
tmp1 = I2C1->SR1;
tmp1 = I2C1->SR2;
}
if(I2C1->SR1 & I2C_SR1_TXE)
{
read = 1; //NEXT START BIT
}
if(I2C1->SR1 & I2C_SR1_RXNE)
{
who = I2C1->DR; //ASSIGN WHO THE VALUE
I2C1->CR1 |= I2C_CR1_STOP; //SEND STOP BIT
state = 0;
}
}*/

 


void tim6_delay(void){
// enable APB1 bus clock
RCC->APB1ENR|=RCC_APB1ENR_TIM6EN;
//TIM6 prescaler set at default to 0 for now
TIM6->PSC=0; // prescalar
TIM6->ARR = 21000; //auto reload register
TIM6->CNT=0; //clear counter register
TIM6->CR1|=TIM_CR1_CEN;
//WHEN COUNTER IS DONE THE TIM2_SR REG UIF FLAG IS SET
while(TIM6->SR==0);
TIM6->SR=0; //CLEAR uIF FLAG
}

/*******************************
* delay(int ms)
* Inputs: delay in milliseconds
* Outputs: NONE
* An approximate delay because
* call of tim2_delay() creates about 1.33ms
*******************************
*/
void delay(int ms){
int i;
for (i=ms; i>0; i--)
{
tim6_delay();
}
}

void GPIO_I2C_Init(void)
{
//enable clock GPIOB for I2C pins
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;

GPIOD->MODER |= 1<< 30 | 1<<28; //set PD15 as output
GPIOB->MODER &= ~(0xF << 15); //clear them pins
GPIOB->MODER |= 0xA000; //set PB7 and PB6 to Alternate Function
GPIOB->OTYPER |= 0x3<<7; //set PB7 and PB6 as open-drain
GPIOB->OSPEEDR |= 0xA000; //set output to high speed
GPIOB->PUPDR &= ~(0xF<<15); //no pull-up/down

GPIOB->AFR[0] |= 0x44000000; //set PB7 and PB6 to I2C1 Alt Function
}

void I2C_Init(void)
{
//enable clock for I2C1
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
//let her warm up
delay(2000);

/***** 400kHz clock setup ***/
I2C1->CR2 |= 0x14; //set clock frequency to 8 MHz for 400kHz operation
I2C1->CCR |= 1<<15; //set I2C1 to Fm mode
I2C1->CCR |= 1<<14; //set to 16/9
I2C1->CCR |= 0x2; //doing the math, Ttotal = 400kHz^-1 = 2.5us
//tlow = 1.6us
//thigh = 0.9us
I2C1->TRISE |= 0x7; //clock rise time is 300ns/50ns + 1
//*******************************

/************ 100 kHz clock settings ******
I2C1->CR2 |= 0x8; //set clock frequency to 8 MHz for 100kHz operation
I2C1->CCR &= ~(1<<15); //set I2C1 to Sm mode
I2C1->CCR |= 0x28; //Ttotal = 2 * CCR * Tpclk
//Tpclk = (8MHz)^-1 = 125 ns, and we want 100 kHz operation
//so we Ttotal = (100kHz)^-1 = 10 us
//solving for CCR, we get 40d or 0x28
I2C1->TRISE |= 0x9; //max rise time in Sm is 1000 ns
//this register value is determined by (max rise time/Tpclk) + 1
// (1000/125) + 1 = 9
//*****************/

//DEBUGGING VARIABLES
sr = &(I2C1->SR1);
dr = &(I2C1->DR);
odr = &(GPIOB->ODR);
cr1 = &(I2C1->CR1);

/***** INTERRUPT STUFF
__enable_irq();
NVIC_SetPriority(I2C1_EV_IRQn,0);
NVIC_ClearPendingIRQ(I2C1_EV_IRQn);
NVIC_EnableIRQ(I2C1_EV_IRQn);

//enable interrupts for a bunch of stuff
I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN;
*/
//enable peripheral
I2C1->CR1 |= 0x1;
I2C1->CR1 |= I2C_CR1_ACK;
}

void I2C_Write_Read(uint8_t internal_reg)
{
//generate a start bit
I2C1->CR1 |= I2C_CR1_START; //should go to interrupt handler to clear SB

//write slave address + write bit in LSB (0)
I2C1->DR = ADDR << 1;

//after ADDR is cleared, write the internal reg address
if(read)
I2C1->DR = internal_reg;

//repeated start bit
I2C1->CR1 |= I2C_CR1_START;

//send the slave address again with read bit
I2C1->DR = ADDR<<1 | 1;
I2C1->CR1 &= ~I2C_CR1_ACK;

if(I2C1->SR1 & I2C_SR1_RXNE)
{
who = I2C1->DR; //ASSIGN WHO THE VALUE
}
//SEND STOP BIT
I2C1->CR1 |= I2C_CR1_STOP;
}

When I am debugging the code, the SB bit in SR1 is not being set and therefore not entering the ISR, and the final value that ends up in the I2C1->DR is 0xD7, the value of the slave address shifted left one with a read bit as the LSB.  It seems to me that the ISR is never being entered at all.  My only guess as to why this is the case is that the corresponding interrupt bits are not being set before the next line in the I2C_Write_Read function.  Is it possible that the code is running too fast for them to be set? That doesn't seem right to me.  If there is anything that is sticking out to anyone here as an obvious problem that would be very helpful too, I am by no means a great programmer so ant help is greatly appreciated.  

 

Thank you all very much

Broydon

Attachments

Outcomes