STM32F401xx I2C driver using registers

102 views Asked by At

I want to use two STM32F401xx MCU in proteus. One of them is to be a master and the other is to be a slave.

This is my code for the master:

Uusing (MicroC Pro For Arm) program.

 #include <stdint.h>
 #include <stdio.h>
 uint8_t temp=0;
 void begin(uint8_t addr);
 void end(void);
 void Write (uint8_t dat);
void main() {
       // Enable the I2C CLOCK and GPIO CLOCK
          RCC_APB1ENR |= (1<<21);  // enable I2C CLOCK
          RCC_AHB1ENR |= (1<<1);  // Enable GPIOB CLOCK

          // Configure the I2C PINs for ALternate Functions
          //PB8 and PB9 are connected to I2C1_SCL and I2C1_SDA
          GPIOB_MODER |= (2<<16) | (2<<18);

          GPIOB_OTYPER |= (1<<8) | (1<<9);
          GPIOB_OSPEEDR |= (3<<16) | (3<<18);
          GPIOB_PUPDR |= (1<<16) | (1<<18);
          GPIOB_AFRH |= (4<<0) | (4<<4);
          begin(0x08);
          Write(0x01);
          end();
}

void begin(uint8_t addr){
          //join I2C as a master mode
         //Generate a start condition
            I2C1_CR1 |= (1<<10);  // Enable the ACK
            I2C1_CR1 |= (1<<8);  // Generate START
            while (!(I2C1_SR1 & (1<<0)));  // Wait for SB bit to set to 1

           //Send the Slave Address to the DR Register
            I2C1_DR = addr;  //  send the address
            while (!(I2C1_SR1 & (1<<1)));  //
            temp = I2C1_SR1 | I2C1_SR2;  // read SR1 and SR2 to clear the ADDR bit

            // Reset the I2C
            I2C1_CR1 |= (1<<15); //I2C Peripheral under reset state -->page.493 manual
            I2C1_CR1 &= ~(1<<15); //I2C Peripheral not under reset  -->page.493 manual
            // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings
            I2C1_CR2 |= (42 <<0);  // PCLK1 FREQUENCY in MHz
            I2C1_CCR = 210<<0;  // Configure the clock control registers
            I2C1_TRISE = 43;  // Configure the rise time register
            I2C1_CR1 |= (1<<0);  // Enable I2C
}
 void end(void)  {

           I2C1_CR1 |= (1<<9);//stop generation
            // Wait until the STOP condition is complete
           while (I2C1_SR2 & (1<<0)); //Cleared by hardware after detecting a Stop condition on the bus
           // Clear the STOP bit
           I2C1_CR1 &= ~(1<<9);
  }

    void Write (uint8_t dat)
  {
          while (!(I2C1_SR1 & (1<<7)));  // wait for TXE bit to set
              I2C1_DR = dat   ;  // wait for BTF bit to set
          while (!(I2C1_SR1 & (1<<2))); //waiting while BTF=0 but when BTF=1; Data byte transfer succeeded

  }

code for the slave :

 #include <stdint.h>
 #include <stdio.h>
 uint8_t temp=0;
 uint8_t state =0x00;
 void begin(uint8_t addr);
 void end(void);
 uint8_t Read ();
void main() {
          RCC_APB1ENR |= (1<<21);  // enable I2C CLOCK -
          RCC_AHB1ENR |= (1<<1);  // Enable GPIOB CLOCK
          RCC_AHB1ENR |= (1<<0);  // Enable GPIOA CLOCK
          GPIOA_MODER |=(1<<0); //set pin 0 as output
          // Configure the I2C PINs for ALternate Functions
          //PB8 and PB9 are connected to I2C1_SCL and I2C1_SDA
          GPIOB_MODER |= (2<<16) | (2<<18);

          GPIOB_OTYPER |= (1<<8) | (1<<9);
          GPIOB_OSPEEDR |= (3<<16) | (3<<18);
          GPIOB_PUPDR |= (1<<16) | (1<<18);
          GPIOB_AFRH |= (4<<0) | (4<<4);
          begin(0x08);
          state =  Read();
          end();
        if (state == 0x01){
            GPIOA_ODR |=(1<<0);//set 1 for pin 0
         }
}
void begin(uint8_t addr){
            //join I2C bus as a slave mode
            I2C1_CR2 |= (42<<0);  // PCLK1 FREQUENCY in MHz
            I2C1_OAR1 = addr;//own address interface
            I2C1_CR1 |= (1<<0);  // Enable I2C
            I2C1_CR1 |= (1<<10);  // Enable the ACK ,indicate that a byte is received
}
 void end(void)  {
           I2C1_CR1 |= (1<<9);//stop generation
            // Wait until the STOP condition is complete
           while (I2C1_SR2 & (1<<0)); //Cleared by hardware after detecting a Stop condition on the bus

           // Clear the STOP bit
           I2C1_CR1 &= ~(1<<9);
  }
   uint8_t Read (){
            uint8_t receivedData = 0;
            I2C1_CR1 &= ~(1<<10);  // clear the ACK bit
            temp = I2C1_SR1 | I2C1_SR2;  // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
            I2C1_CR1 |= (1<<9);  // Stop I2C
            while (!(I2C1_SR1 & (1<<6)));  // wait for RxNE to set
            receivedData = I2C1_DR; // Read the data from the DATA REGISTER
        return receivedData;
     }

I try to make a simulation on Proteus, but there is nothing to change, the led still off. So, I want to know how I make the simulation on Proteus, and if there any problem in my provided code, I followed the datasheet and manual of STM32F401xx to write this simple driver. proteus connection of two STM32F401xx MCU :enter image description here

1

There are 1 answers

0
Ilya On

Your master's "begin" function does something strange. How is I2C1_TRISE = 43; after you set the start bit? You are trying to use I2C before you configure it. TRISE must be one of the first things you do to I2C before you use any acknowledge or start bits. The whole function does things in weird order.

"Start bit" in the register doesn't enable start bit functionality. It physically tries to send a start bit immediately when you write to it, and in your code, it happens before you you set TRISE or even before you enable I2C peripheral. Thus the behavior is unpredictable.

I checked out the peripheral of F401, it's exactly identical to the one of F103 with the exception that F401 has one extra register for analog & digital noise filter controls. Other than that, they are identical and map one to another perfectly. (and F401 doesn't have the same silicon bug as F103 with filter glitch, as per errata sheets).

So here is the sequence of actions I do to use this peripheral, skipping the obvious GPIO alternate function setup, so only I2C registers here. This is I2C master config, but the general setup is the same, the difference between slave and master device configuration comes after these necessary steps:

  1. Configure GPIO, enable I2C clock.
  2. Configure FREQ in CR2 with APBx clock frequency (for example, 32 for STM32 running at 64MHz, because the APBx of the I2C runs at 32000000Hz)
  3. Configure clock period multiplier in CCR
  4. Configure TRISE with rise/fall times

Now if you write start bit, you can load data into DR and have it sent as a master. You don't need to write anything into Start bit until now, and you don't need to write anything into ACK bit, those bits are used in the middle of ongoing communication, not for configuration.

For a slave, you do similar setup, but also need to configure its slave address, obviously.

I think you're rushing too much. Start with just a master MCU and make sure you can see something on the I2C lines first.