0

STM32 Adventures – Communication using I2C


When working with STM32 using registries only it is important that you have REFERENCE MANUAL and DATASHEET available. We will use them a lot 🙂


 

Today we start with I2C communication protocol. Configuration of it might be a bit of tricky at the beginning gut I hope to explain to you what I have came across of when trying to get the protocol to work. For more details about I2C itself check out wiki .

Device address

Since I have worked with it I usually like to start off with defining address of the device that I’m working with ( or devices if you work with more than one ).

#define DEVICE_ADDRESS 0x01
#define DEVICE_ADDRESS_W (DEVICE_ADDRESS << 1)
#define DEVICE_ADDRESS_R (DEVICE_ADDRESS_W | 1)

This has saved me many times from problems with wrong addressing and spending time troubleshooting something which is easy as above 🙂

Transmission flow

So now its time to dive back into RM. We need to find part about I2C. Since we will be building a master device we focus there (at least for now ). As you can see on image below its shows really nice what we must program. Specific events at specific point of transmissions ( for transmitter and receiver ). This makes it really easy to code against.

RMf407vgen_DM00031020_i2c_01

 

Configuration of peripherial

Let’s move on to configuration. What is important for you is to know that the code presented here is not using interrupts or DMA. Reason for this is slow build up of skills. I will document those later on and update the links in this post accordingly.

Let’s start by finding which PINs we need to configure. For that one I jump to DS and search for I2C1 (that one I want to use ) and checkout Alternate Function Mappings

DSF407VGen_DM00037051_af_i2c

 

So now I can choose even what suits me more 🙂 I will stick with PB6 and PB7. Below function will set those pins as alternate function, high speed , open drain and finally configure that we use AF4 for those pins.

/*
 * Function responsbile for configuration
 * of GPIO pins
 */
void i2c_setup_gpio(void)
{
	GPIOB->MODER |= GPIO_MODER_MODER6_1 |   // AF: PB6 => I2C1_SCL
			GPIO_MODER_MODER7_1; 	// AF: PB7 => I2C1_SDA


	GPIOB->OTYPER |= GPIO_OTYPER_OT_6|
			 GPIO_OTYPER_OT_7;

	GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6|
			  GPIO_OSPEEDER_OSPEEDR7;

	GPIOB->PUPDR |= GPIO_PUPDR_PUPDR6_0|
			GPIO_PUPDR_PUPDR7_0;

	/*
	 * Alternate functions are configured in ARFL for PINS 0..7
	 * and in ARFH for PINS 8..15
	 * Based on DS we will select appropiate AF0..AF7
	 */
	GPIOB->AFR[0] |= ( 1 << 30 ) | ( 1 << 26); // P6/P7 => AF4
}

If you have by any chance forgotten about RCC here is my routine which I used to enable all peripherals

void rcc_init(void)
{

	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN|	// Enable clock: GPIOB
			RCC_AHB1ENR_GPIOCEN;	// Enable clock: GPIOC
	RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;	// Enable clock: I2C1

	__DSB();	// Data Synchronization Barrier

}

Once hardware is ready we can configure the I2C registries.

void i2c_init(void)
{

	/*
	 *  Reset I2C from lock state
	 */
	//I2C1->CR1 |= I2C_CR1_SWRST;

	/*
	 * FREQ: Set frequencey based on APB1 clock
	 * 8MHz
	 */
	I2C1->CR2 &= ~(I2C_CR2_FREQ);
	I2C1->CR2 |= 0x08;

	/*
	 * Depending on the frequency of said prescaler must be installed in
	 * accordance with the selected data rate.
	 * We choose the maximum, for standard mode - 100 kHz:
	 *
	 *  8MHz / 100 = 80 kHz;
	 *
	 */
	I2C1->CCR &= ~I2C_CCR_CCR;
	I2C1->CCR |= 80;

	/*
	 * clock period is equal to (1 / 8 MHz = 125 ns), therefore the maximum rise time:
	 * 1000 ns / 125 ns = 8 + 1 (plus one - a small margin) = 3
	 *
	 */
	I2C1->TRISE = 9;


	/*
	 * Enable perifpherial at the END
	 */
	I2C1->CR1 = I2C_CR1_ACK|
	I2C_CR1_PE;     // PE : Peripherial enable
}

Its quite easy here – we set freq of peripherial ( in my case it was 8Mhz ) then set clock rate ( which has simple formula ) and then we setup time rise. All of this is documented in RM and clearly explained.

The only thing that is left now is to read and write from the device.

Read using I2C

uint8_t i2c_read(uint8_t address, uint8_t registry)
{
	uint32_t timeout = TIMEOUT_MAX;

	while(I2C1->SR2 & I2C_SR2_BUSY);		// Wait for BUSY line
	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = address<<1;					// Write device address (W)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    (void)I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_TXE));		// Wait for EV8_1
	I2C1->DR = registry;

	I2C1->CR1 |= I2C_CR1_STOP;				// Generate STOP condition


	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = (address << 1 ) | 1;			// Write device address (R)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    I2C1->CR1 &= ~I2C_CR1_ACK;              // No ACK
    (void)I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_RXNE));	// Wait for EV7_1
    uint8_t value = (uint8_t)I2C1->DR;      // Read value

    I2C1->CR1 |= I2C_CR1_STOP;			    // Generate STOP condition

	return value;
}

Writing using I2C

void i2c_write(uint8_t address, uint8_t registry, uint8_t data) 
{


	I2C1->CR1 |= I2C_CR1_START;				// Generate START condition

	while (!(I2C1->SR1 & I2C_SR1_SB)); 		// Wait for EV5
	I2C1->DR = address<<1;					// Write device address (W)

	while (!(I2C1->SR1 & I2C_SR1_ADDR));	// Wait for EV6
    reg2 = I2C1->SR2;						// Read SR2

	while (!(I2C1->SR1 & I2C_SR1_TXE));		// Wait for EV8_1
	I2C1->DR = registry;					// Write registry address

	while (!(I2C1->SR1 & I2C_SR1_BTF));	    // Wait for BTF
	I2C1->DR = data;

	I2C1->CR1 |= I2C_CR1_STOP;			    // Generate STOP condition
}

Summary

Code presented above has still work in progress as I need to add error control and timeouts – otherwise if something would go wrong now it would block whole uC

 


logo_github

 

Code for this exercise can be of course found at github https://github.com/RafPe/STM32F4DISCOVERY-I2C

 

 

 


 

 

 

 

 

 

 

 

rafpe

Leave a Reply

Your email address will not be published. Required fields are marked *