Programming the controller

Microcontrollers provide a number of I2C controllers on board that can be bound to specific pins using alternate functions. On our reference board, to enable the I2C1 bus, we activate the clock gating, and start the initialization procedure by accessing the control, data, and status register mapped in the peripherals memory region:

#define APB1_CLOCK_ER (*(volatile uint32_t *)(0x40023840))
#define APB1_CLOCK_RST (*(volatile uint32_t *)(0x40023820))
#define I2C1_APB1_CLOCK_ER_VAL (1 << 21)

The I2C1 controller on STM32F407 is associated with pins PB6 and PB9 when they are configured with the AF4 alternate function:

#define I2C1_PIN_AF 4
#define I2C1_SCL 6
#define I2C1_SDA 9
#define GPIO_MODE_AF (2)


static void i2c1_pins_setup(void)
{
uint32_t reg;
AHB1_CLOCK_ER |= GPIOB_AHB1_CLOCK_ER;
/* Set mode = AF */
reg = GPIOB_MODE & ~(0x03 << (I2C1_SCL * 2));
reg &= ~(0x03 << (I2C1_SDA * 2));
GPIOB_MODE = reg | (2 << (I2C1_SCL * 2)) | (2 << (I2C_SDA * 2));

/* Alternate function: */
reg = GPIOB_AFL & ~(0xf << ((I2C1_SCL) * 4));
GPIOB_AFL = reg | (I2C1_PIN_AF << ((I2C1_SCL - 8) * 4));
reg = GPIOB_AFH & ~(0xf << ((I2C1_SDA - 8) * 4));
GPIOB_AFH = reg | (I2C1_PIN_AF << ((I2C1_SDA - 8) * 4));
}

The initialization function accesses the configuration registers of the I2C controller, mapped in the peripheral region. After the pin configuration and the RCC startup sequence, the transceiver speed is calibrated by using the frequency of the APB1 bus clock, in MHz. When the clocks are calibrated, the transceiver is enabled by setting a bit in the CR1 register. The parameters used here configure the master bus clock to run at 400 kHz. While the default setting for the protocol foresees a clock of 100 kHz, the 400 kHz option was added later on, and it is now supported by many devices:

#define I2C1 (0x40005400)
#define APB1_SPEED_IN_MHZ (42)
#define I2C1_CR1 (*(volatile uint32_t *)(I2C1))
#define I2C1_CR2 (*(volatile uint32_t *)(I2C1 + 0x04))
#define I2C1_OAR1 (*(volatile uint32_t *)(I2C1 + 0x08))
#define I2C1_OAR2 (*(volatile uint32_t *)(I2C1 + 0x0c))
#define I2C1_DR (*(volatile uint32_t *)(I2C1 + 0x10))
#define I2C1_SR1 (*(volatile uint32_t *)(I2C1 + 0x14))
#define I2C1_SR2 (*(volatile uint32_t *)(I2C1 + 0x18))
#define I2C1_CCR (*(volatile uint32_t *)(I2C1 + 0x1c))
#define I2C1_TRISE (*(volatile uint32_t *)(I2C1 + 0x20))
#define I2C_CR2_FREQ_MASK (0x3ff)
#define I2C_CCR_MASK (0xfff)
#define I2C_TRISE_MASK (0x3f)
#define I2C_CR1_ENABLE (1 << 0)

void i2c1_setup(void)
{
uint32_t reg;
i2c1_pins_setup();
APB1_CLOCK_ER |= I2C1_APB1_CLOCK_ER_VAL;
I2C1_CR1 &= ~I2C_CR1_ENABLE;
i2c1_reset();
reg = I2C1_CR2 & ~(I2C_CR2_FREQ_MASK);
I2C1_CR2 = reg | APB1_SPEED_IN_MHZ;

reg = I2C1_CCR & ~(I2C_CCR_MASK);
I2C1_CCR = reg | (APB1_SPEED_IN_MHZ * 5);

reg = I2C1_TRISE & ~(I2C_TRISE_MASK);
I2C1_TRISE = reg | APB1_SPEED_IN_MHZ + 1;
I2C1_CR1 |= I2C_CR1_ENABLE;
}

From this moment on, the controller is ready to be configured and used, either in master or slave mode. Data can be read and written using I2C1_DR, in the same way as SPI and UART. The main difference here is that, as master I2C device, the START condition and STOP condition must be manually triggered by setting the corresponding values in the I2C1_CR1 register. Functions such as the following are intended for this purpose:

static void i2c1_send_start(void)
{
volatile uint32_t sr1;
I2C1_CR1 |= I2C_CR1_START;
do {
sr1 = I2C1_SR1;
} while ((sr1 & I2C_SR1_START) == 0);
}

static void i2c1_send_stop(void)
{
I2C1_CR1 |= I2C_CR1_STOP;
}

At the end of each condition, the bus must be tested for possible errors or abnormal events. The combination of the flags in I2C1_CR1 and I2C1_CR2 must reflect the expected status for the transaction to continue, or it must be gracefully aborted in the case of timeouts or unrecoverable errors.

Due to the complexity given by the high number of events possible during the setup of the transaction, it is necessary to implement a complete state machine that keeps track of the phases of the transmission to use the transceiver in master mode.

As a demonstration of basic interactions with the transceiver, we can write a sequential interaction with the bus, but a real-life scenario would require us to keep track of the state of each transaction and react to the many scenarios possible within the combination of the flags contained in I2C1_SR1 and I2C1_SR2. This sequence initiates a transaction toward an I2C slave with an address of 0x42, and if the slave responds it sends two bytes, with values 0x00 and 0x01, respectively. The only purpose of this sequence is to show the interaction with the transceiver, and it does not recover from any of the possible errors. At the beginning of the transaction, we zero the flags related to ACK or the STOP condition, and we enable the transceiver using the lowest bit in CR1:

void i2c1_test_sequence(void)
{
volatile uint32_t sr1, sr2;
const uint8_t address = 0x42;
I2C1_CR1 &= ~(I2C_CR1_ENABLE | I2C_CR1_STOP | I2C_CR1_ACK);
I2C1_CR1 |= I2C_CR1_ENABLE;

To ensure that no other master is occupying the bus, the procedure hangs until the busy flag is cleared in the transceiver:

    do {
sr2 = I2C1_SR2;
} while ((sr2 & I2C_SR2_BUSY) != 0);;

A START condition is sent, using the function defined earlier, which will also wait until the same START condition appears on the bus:

    i2c1_send_start();

The destination address is set on the highest 7 bit of the byte we are about to transmit. The lowest bit is off as well, indicating a write operation. To proceed after a correct address selection that has been acknowledged by the receiving slave, two flags must be set in I2C1_SR2, indicating that the master mode has been selected and the bus is still taken:

    I2C1_DR = (address << 1);
do {
sr2 = I2C1_SR2;
} while ((sr2 & (I2C_SR2_BUSY | I2C_SR2_MASTER)) != (I2C_SR2_BUSY |
I2C_SR2_MASTER));;

The data communication with the slave has now been initiated, and the two data bytes can be transmitted. The TX FIFO EMPTY event indicates when each byte has been transferred within a frame in the transaction:

    I2C1_DR = (0x00);
do {
sr1 = I2C1_SR1;
} while ((sr1 & I2C_SR1_TX_EMPTY) != 0);;


I2C1_DR = (0x01);
do {
sr1 = I2C1_SR1;
} while ((sr1 & I2C_SR1_TX_EMPTY) != 0);;

Finally, the STOP condition is set, and the transaction is over:

    i2c1_send_stop();
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.240.178