To come in
TechMaster. Installation, configuration. Operating rules
  • The Grandfather Who Feared Hitler
  • Mekhlis Lev Zakharovich Commissioner Mekhlis biography
  • Major Arcana of the Tarot: meanings and calculation by date of birth
  • Lenormand Fox Meanings Description Combinations Lenormand Fox
  • Runes for love and relationships
  • Why do you dream about drizzling rain?
  • I2C bus and its use in STM32 MCUs. I2C bus and its use in the STM32 MCU Stm32 i2c registers

    I2C bus and its use in STM32 MCUs.  I2C bus and its use in the STM32 MCU Stm32 i2c registers

    kselltrum February 20, 2017 at 01:17

    First steps with STM32 and mikroC compiler for ARM architecture - Part 4 - I2C, pcf8574 and HD4478 based LCD connection

    • Microcontroller programming

    I would like to devote the next article to working with the common i2c interface, which is often used in various microcircuits connected to a microcontroller.

    I2C is a bus that operates over two physical connections (in addition to the common wire). Quite a lot is written about it on the Internet; there are good articles on Wikipedia. In addition, the bus operation algorithm is very clearly described. In short, the bus is a two-wire synchronous bus. Up to 127 devices can be on the bus at the same time (the device address is 7-bit, we’ll come back to this later). Below is a typical diagram for connecting devices to the i2c bus, with the MK as the master device.


    For i2c, all devices (both master and slaves) use open-drain outputs. Simply put, they can ONLY attract the tire to the GROUND. The high bus level is ensured by pull-up resistors. The value of these resistors is usually selected in the range from 4.7 to 10 kOhm. i2c is quite sensitive to the physical lines connecting devices, so if a connection with a large capacitance is used (for example, a long thin or shielded cable), the influence of this capacitance can “blur” the signal edges and interfere with normal operation tires. The smaller the pull-up resistor, the less influence this capacitance has on the characteristics of the signal edges, but the GREATER the LOAD on the output transistors on the i2c interfaces. The value of these resistors is selected for each specific implementation, but they should not be less than 2.2 kOhms, otherwise you can simply burn the output transistors in devices that work with the bus.

    The bus consists of two lines: SDA (data line) and SCL (clock signal). Clocks the bus Master device, usually our MK. When SCL is high, information is read from the data bus. The SDA state can only be changed when the clock signal is low.. When SCL is high, the signal on SDA changes when generating signals START (when SCL is high the signal on SDA changes from high to low) and STOP - when the SCL level is high, the signal on SDA changes from low to high).

    Separately, it should be said that in i2c the address is specified as a 7-bit number. 8 - least significant bit indicates the direction of data transfer 0 - means that the slave will transmit data, 1 - receive.. Briefly, the algorithm for working with i2c is as follows:

    • High level on SDA and SCL- the bus is free, you can start working
    • Master lifts SCL to 1, and changes state S.D.A. from 1 to 0 - attracts it to the ground - a signal is formed START
    • The master transmits a 7-bit slave address with a direction bit (data on S.D.A. are exhibited when SCL pulled to the ground, and read by the slave when it is released). If the slave does not have time to “grab” the previous bit, it attracts SCL to the ground, making it clear to the master that the state of the data bus does not need to be changed: “I’m still reading the previous one.” After the master has released the tire, he checks did the slave let her go?.
    • After transmitting 8 bits of the address, the master generates the 9th clock cycle and releases the data bus. If the slave heard his address and accepted it, then he will press S.D.A. to the ground. This is how the signal is formed ASK- accepted, everything is OK. If the slave doesn’t understand anything, or he’s simply not there, then there will be no one to press the tire. the master will wait for a timeout and understand that he was not understood.
    • After transmitting the address, if we have set the direction from master to slave(8 bits of the address are equal to 1), then the master transmits the data to the slave, not forgetting to check the presence of ASK from the slave, waiting for the slave device to process the incoming information.
    • When the master receives data from the slave, the master itself generates a signal ASK after receiving each byte, and the slave controls its presence. The master may not specifically send ASK before sending the command STOP, usually making it clear to the slave that there is no need to provide any more data.
    • If, after sending data by the master (write mode), it is necessary to read data from the slave, then the master generates the signal again START , sending the slave address with a read flag. (if before the command START was not transferred STOP then a team is formed RESTART). This is used to change the direction of master-slave communication. For example, we pass the register address to the slave, and then read data from it.)
    • Upon completion of work with the slave, the master generates a signal STOP- at a high level of the clock signal, it forms a data bus transition from 0 to 1.
    The STM 32 has hardware-implemented i2c bus transceivers. There can be 2 or 3 such modules in an MK. To configure them, special registers are used, described in the reference for the MK used.

    In MicroC, before using i2c (as well as any peripheral), it must be properly initialized. To do this, we use the following function (Initialization as a master):

    I2Cn_Init_Advanced(unsigned long: I2C_ClockSpeed, const Module_Struct *module);

    • n- number of the module used, for example I2C1 or I2C2.
    • I2C_ClockSpeed- bus speed, 100000 (100 kbs, standard mode) or 400000 (400 kbs, fast mode). The second one is 4 times faster, but not all devices support it
    • *module- pointer to a peripheral module, for example &_GPIO_MODULE_I2C1_PB67, let's not forget here that Code Assistant (ctrl-space ) helps a lot.
    First, let's check if the bus is free; there is a function for this I2Cn_Is_Idle(); returning 1 if the bus is free, and 0 if there is an exchange on it.

    I2Cn_Start();
    Where n- number of the used i2c module of our microcontroller. The function will return 0 if there is an error on the bus and 1 if everything is OK.

    In order to transfer data to the slave we use the function:

    I2Cn_Write(unsigned char slave_address, unsigned char *buf, unsigned long count, unsigned long END_mode);

    • n- number of the module used
    • slave_address- 7-bit slave address.
    • *buf- a pointer to our data - a byte or byte array.
    • count- the number of data bytes transferred.
    • END_mode- what to do after transferring data to the slave, END_MODE_STOP - transmit a signal STOP, or END_MODE_RESTART send again START, generating a signal RESTART and making it clear to the department that the session with him is not over and data will now be read from him.
    To read data from the slave, use the function:

    I2Cn_Read(char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);

    • n- number of the module used
    • slave_address- 7-bit slave address.
    • *buf- a pointer to a variable or array into which we receive data, type char or short int
    • count- number of data bytes received.
    • END_mode- what to do after receiving data from the slave - END_MODE_STOP - transmit a signal STOP, or END_MODE_RESTART send a signal RESTART.
    Let's try to connect something to our MK. To begin with: the widespread PCF8574(A) microcircuit, which is an expander of input/output ports controlled via the i2c bus. This chip contains only one internal register, which is its physical I/O port. That is, if you pass a byte to her, it will immediately be exposed to her conclusions. If you count a byte from it (Transmit START address with read flag, signal RESTERT, read the data and finally generate a signal STOP) then it will reflect the logical states on its outputs. Let's connect our microcircuit in accordance with the datasheet:


    The microcircuit address is formed from the state of the pins A0, A1, A2. For microcircuit PCF8574 the address will be: 0100A0A1A2. (For example, we have A0, A1, A2 at a high level, so the address of our microcircuit will be 0b0100 111 = 0x27). For PCF8574A - 0111A0A1A2, which with our connection diagram will give the address 0b0111 111 = 0x3F. If, say, A2 is connected to ground, then the address for PCF8574A will 0x3B. In total, 16 microcircuits can be simultaneously mounted on one i2c bus, 8 PCF8574A and PCF8574 each.

    Let's try to transfer something, initialize the i2c bus and transfer something to our PCF8574.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Generate the START signal I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Transfer 1 byte of data and generate the STOP signal ) char PCF8574A_reg ; // the variable that we write in PCF8574 void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Start I2C delay_ms(25); // Wait a little PCF8574A_reg.b0 = 0; // light the first LED PCF8574A_reg.b1 = 1; // turn off the second LED while (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // invert the state of the LEDs I2C_PCF8574_WriteReg (PCF8574A_reg); // transfer data to our PCF8574 ) )
    We compile and run our program and see that our LEDs blink alternately.
    I connected the LEDs cathode to our PCF8574 for a reason. The thing is that when a logical 0 is supplied to the output, the microcircuit honestly pulls its output to the ground, but when a logical 1 is applied, it connects it to + power through a current source of 100 μA. That is, you cannot get an “honest” logical 1 at the output. And you can’t light an LED with 100 µA. This was done in order to configure the PCF8574 output to the input without additional registers. We simply write to output register 1 (essentially setting the pin states to Vdd) and can simply short it to ground. The current source will not allow the output stage of our I/O expander to “burn out”. If the leg is pulled to the ground, then the ground potential is on it, and logical 0 is read. If the leg is pulled to +, then logical 1 is read. On the one hand, it’s simple, but on the other, you should always remember this when working with these microcircuits.


    Let's try to read the state of the pins of our expander chip.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Generate the START signal I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Transfer 1 byte of data and generate the STOP signal ) void I2C_PCF8574_ReadReg (unsigned char rData) ( I2C1_Start(); // Generate the START signal I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Read 1 byte of data and generate the STOP signal) char PCF8574A_reg; //variable that we write to PCF8574 char PCF8574A_out; // the variable we read into and PCF8574 char lad_state; //our LED is on or off void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Start I2C delay_ms(25); // Wait a little PCF8574A_reg.b0 = 0; // light the first LED PCF8574A_reg.b1 = 1; / / turn off the second LED PCF8574A_reg.b6 = 1; // Pull pins 6 and 7 to power. PCF8574A_reg.b7 = 1; while (1) ( delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg); // write data to PCF8574 I2C_PCF8574_ReadReg (PCF85 74A_out ); // read from PCF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // If 1 button is pressed (the 6th bit of the read byte from PCF8574 is 0, then turn our LED on/off) if (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // similar for 2 buttons and 2 LEDs ) )
    Now by pressing the buttons we turn our LED on or off. The microcircuit has another output INT. A pulse is generated on it every time the state of the pins of our I/O expander changes. By connecting it to the external interrupt input of our MK (I will tell you how to configure external interrupts and how to work with them in one of the following articles).

    Let's use our port expander to connect a character display through it. There are a great many of these, but almost all of them are built on the basis of a controller chip HD44780 and his clones. For example, I used an LCD2004 display.


    The datasheet for it and the HD44780 controller can be easily found on the Internet. Let's connect our display to the PCF8574, and hers, respectively, to our STM32.

    HD44780 uses a parallel gated interface. Data is transmitted by 8 (in one clock cycle) or 4 (in 2 clock cycles) gate pulses at the output E. (read by the display controller on a descending edge, transition from 1 to 0). Conclusion R.S. indicates whether we are sending data to our display ( RS = 1) (the characters it should display are actually ASCII codes) or the command ( RS = 0). RW indicates the direction of data transfer, writing or reading. Usually we write data to the display, so ( RW=0). Resistor R6 controls the display contrast. You can’t simply connect the contrast adjustment input to ground or power, otherwise you won’t see anything.. VT1 is used to turn the display backlight on and off according to MK commands. MicroC has a library for working with such displays via a parallel interface, but usually it’s expensive to spend 8 legs on a display, so I almost always use the PCF8574 to work with such screens. (If anyone is interested, I will write an article about working with HD44780-based displays built into MicroC via a parallel interface.) The exchange protocol is not particularly complicated (we will use 4 data lines and transfer information in 2 clock cycles), it is clearly shown by the following timing diagram:


    Before transferring data to our display, it must be initialized by passing service commands. (described in the datasheet, here we present only the most used ones)

    • 0x28- communication with the indicator via 4 lines
    • 0x0C- enable image output, disable cursor display
    • 0x0E- enable image output, enable cursor display
    • 0x01- clear the indicator
    • 0x08- disable image output
    • 0x06- after the symbol is displayed, the cursor moves by 1 familiar place
    Since we will need to work with this indicator quite often, we will create a plug-in library "i2c_lcd.h" . For this purpose in Project Manager Header Files and choose Add New File . Let's create our header file.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 #define DB4 b4 // Correspondence between the PCF8574 pins and the indicator #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 // backlight control #define displenth 20 // number of characters in our display line static unsigned char BL_status; // variable storing the state of the backlight (on/off) void lcd_I2C_Init(void); // Display and PCF8574 initialization function void lcd_I2C_txt(char *pnt); // Displays a line of text, the parameter is a pointer to this line void lcd_I2C_int(int pnt); // Displays the value of an integer variable, the parameter is the output value void lcd_I2C_Goto(unsigned short row, unsigned short col); // moves the cursor to the specified position, parameters row - line (from 1 to 2 or 4 depending on the display) and col - (from 1 to displenth)) void lcd_I2C_cls(); // Clears the screen void lcd_I2C_backlight (unsigned short int state); // Enables (when transmitting 1 and disables - when transmitting 0 the display backlight)
    Now let's describe our functions, again we go to Project Manager right click on the folder Sources and choose Add New File . Create a file "i2c_lcd.с" .

    #include "i2c_lcd.h" //include our header file char lcd_reg; //register for temporary storage of data sent to PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //function for sending data via i2c to the PCF8574 chip ( I2C1_Start(); I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); ) void LCD_COMMAND (char com) / /function of sending a command to our display ( lcd_reg = 0; //write 0 to the temporary register lcd_reg.BL = BL_status.b0; //set the backlight pin in accordance with the value of the variable storing the backlight state lcd_reg.DB4 = com.b4; // set the 4 most significant bits of our command to the indicator data bus lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.EN = 1; //set the strobe output to 1 I2C_PCF8574_WriteReg ( lcd_reg); //write to the PCF8574 register, actually sending data to the indicator delay_us (300); //wait for timeout lcd_reg.EN = 0; //reset the strobe pulse to 0, the indicator reads the data I2C_PCF8574_WriteReg (lcd_reg); delay_us (300) ; lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = com.b0; //same for the 4 least significant bits lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); ) void LCD_CHAR (unsigned char com) //sending data (ASCII character code) to the indicator ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //sending a character is different from sending commands by setting the RS bit to 1 lcd_reg.DB4 = com.b4; //set the 4 most significant bits at the inputs lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg) ; delay_us (300); lcd_reg.EN = 0; // reset the strobe pulse to 0, the indicator reads the data I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //set the 4 least significant bits at the inputs lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg ( lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); ) void lcd_I2C_Init(void) ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //initialize our I2c module for MK delay_ms(200) ;lcd_Command(0x28); // Display in 4 bits per clock mode delay_ms (5); lcd_Command(0x08); //Disable data output to the display delay_ms (5); lcd_Command(0x01); //Clear the display delay_ms (5); lcd_Command(0x06); //Enable automatic cursor shift after displaying the symbol delay_ms (5); lcd_Command(0x0C); //Turn on displaying information without displaying the cursor delay_ms (25); ) void lcd_I2C_txt(char *pnt) //Output a string of characters to the display ( unsigned short int i; //temporary character array index variable char tmp_str; //temporary array of characters, length 1 greater than the length of the display line, since the line needs to be terminated сiv with a NULL ASCII character 0x00 strncpy(tmp_str, pnt, displenth); //copy no more than displenth characters of the original string to our temporary string for (i=0; i Now let's connect the newly created library to the file with our main function:

    #include "i2c_lcd.h" //include our header file unsigned int i; //temporary variable counter void main() ( lcd_I2C_Init(); //initialize the display lcd_I2C_backlight (1); //turn on the backlight lcd_I2C_txt ("Hello habrahabr"); //display the line while (1) ( delay_ms(1000) ; lcd_I2C_Goto (2,1); //go to character 1 of line 2 lcd_i2c_int (i); //display the value i++; //increment the counter ) )

    If everything is assembled correctly, then we should see text on the indicator and a counter incrementing every second. In general, nothing complicated :)

    In the next article we will continue to understand the i2c protocol and devices that work with it. Let's consider working with EEPROM 24XX memory and the MPU6050 accelerometer/gyroscope.

    First steps with STM32 and mikroC compiler for ARM architecture - Part 4 - I2C, pcf8574 and HD4478 based LCD connection

    I would like to devote the next article to working with the common i2c interface, which is often used in various microcircuits connected to a microcontroller.

    I2C is a bus that operates over two physical connections (in addition to the common wire). Quite a lot is written about it on the Internet; there are good articles on Wikipedia. In addition, the bus operation algorithm is very clearly described. In short, the bus is a two-wire synchronous bus. Up to 127 devices can be on the bus at the same time (the device address is 7-bit, we’ll come back to this later). Below is a typical diagram for connecting devices to the i2c bus, with the MK as the master device.


    For i2c, all devices (both master and slaves) use open-drain outputs. Simply put, they can ONLY attract the tire to the GROUND. The high bus level is ensured by pull-up resistors. The value of these resistors is usually selected in the range from 4.7 to 10 kOhm. i2c is quite sensitive to the physical lines connecting devices, so if a connection with a large capacitance is used (for example, a long thin or shielded cable), the influence of this capacitance can “blur” the edges of the signals and interfere with normal operation of the bus. The smaller the pull-up resistor, the less influence this capacitance has on the characteristics of the signal edges, but the GREATER the LOAD on the output transistors on the i2c interfaces. The value of these resistors is selected for each specific implementation, but they should not be less than 2.2 kOhms, otherwise you can simply burn the output transistors in devices that work with the bus.

    The bus consists of two lines: SDA (data line) and SCL (clock signal). Clocks the bus Master device, usually our MK. When SCL is high, information is read from the data bus. The SDA state can only be changed when the clock signal is low.. When SCL is high, the signal on SDA changes when generating signals START (when SCL is high the signal on SDA changes from high to low) and STOP - when the SCL level is high, the signal on SDA changes from low to high).

    Separately, it should be said that in i2c the address is specified as a 7-bit number. 8 - least significant bit indicates the direction of data transfer 0 - means that the slave will transmit data, 1 - receive.. Briefly, the algorithm for working with i2c is as follows:

    • High level on SDA and SCL- the bus is free, you can start working
    • Master lifts SCL to 1, and changes state S.D.A. from 1 to 0 - attracts it to the ground - a signal is formed START
    • The master transmits a 7-bit slave address with a direction bit (data on S.D.A. are exhibited when SCL pulled to the ground, and read by the slave when it is released). If the slave does not have time to “grab” the previous bit, it attracts SCL to the ground, making it clear to the master that the state of the data bus does not need to be changed: “I’m still reading the previous one.” After the master has released the tire, he checks did the slave let her go?.
    • After transmitting 8 bits of the address, the master generates the 9th clock cycle and releases the data bus. If the slave heard his address and accepted it, then he will press S.D.A. to the ground. This is how the signal is formed ASK- accepted, everything is OK. If the slave doesn’t understand anything, or he’s simply not there, then there will be no one to press the tire. the master will wait for a timeout and understand that he was not understood.
    • After transmitting the address, if we have set the direction from master to slave(8 bits of the address are equal to 1), then the master transmits the data to the slave, not forgetting to check the presence of ASK from the slave, waiting for the slave device to process the incoming information.
    • When the master receives data from the slave, the master itself generates a signal ASK after receiving each byte, and the slave controls its presence. The master may not specifically send ASK before sending the command STOP, usually making it clear to the slave that there is no need to provide any more data.
    • If, after sending data by the master (write mode), it is necessary to read data from the slave, then the master generates the signal again START , sending the slave address with a read flag. (if before the command START was not transferred STOP then a team is formed RESTART). This is used to change the direction of master-slave communication. For example, we pass the register address to the slave, and then read data from it.)
    • Upon completion of work with the slave, the master generates a signal STOP- at a high level of the clock signal, it forms a data bus transition from 0 to 1.
    The STM 32 has hardware-implemented i2c bus transceivers. There can be 2 or 3 such modules in an MK. To configure them, special registers are used, described in the reference for the MK used.

    In MicroC, before using i2c (as well as any peripheral), it must be properly initialized. To do this, we use the following function (Initialization as a master):

    I2Cn_Init_Advanced(unsigned long: I2C_ClockSpeed, const Module_Struct *module);

    • n- number of the module used, for example I2C1 or I2C2.
    • I2C_ClockSpeed- bus speed, 100000 (100 kbs, standard mode) or 400000 (400 kbs, fast mode). The second one is 4 times faster, but not all devices support it
    • *module- pointer to a peripheral module, for example &_GPIO_MODULE_I2C1_PB67, let's not forget here that Code Assistant (ctrl-space ) helps a lot.
    First, let's check if the bus is free; there is a function for this I2Cn_Is_Idle(); returning 1 if the bus is free, and 0 if there is an exchange on it.

    I2Cn_Start();
    Where n- number of the used i2c module of our microcontroller. The function will return 0 if there is an error on the bus and 1 if everything is OK.

    In order to transfer data to the slave we use the function:

    I2Cn_Write(unsigned char slave_address, unsigned char *buf, unsigned long count, unsigned long END_mode);

    • n- number of the module used
    • slave_address- 7-bit slave address.
    • *buf- a pointer to our data - a byte or byte array.
    • count- the number of data bytes transferred.
    • END_mode- what to do after transferring data to the slave, END_MODE_STOP - transmit a signal STOP, or END_MODE_RESTART send again START, generating a signal RESTART and making it clear to the department that the session with him is not over and data will now be read from him.
    To read data from the slave, use the function:

    I2Cn_Read(char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);

    • n- number of the module used
    • slave_address- 7-bit slave address.
    • *buf- a pointer to a variable or array into which we receive data, type char or short int
    • count- number of data bytes received.
    • END_mode- what to do after receiving data from the slave - END_MODE_STOP - transmit a signal STOP, or END_MODE_RESTART send a signal RESTART.
    Let's try to connect something to our MK. To begin with: the widespread PCF8574(A) microcircuit, which is an expander of input/output ports controlled via the i2c bus. This chip contains only one internal register, which is its physical I/O port. That is, if you pass a byte to her, it will immediately be exposed to her conclusions. If you count a byte from it (Transmit START address with read flag, signal RESTERT, read the data and finally generate a signal STOP) then it will reflect the logical states on its outputs. Let's connect our microcircuit in accordance with the datasheet:


    The microcircuit address is formed from the state of the pins A0, A1, A2. For microcircuit PCF8574 the address will be: 0100A0A1A2. (For example, we have A0, A1, A2 at a high level, so the address of our microcircuit will be 0b0100 111 = 0x27). For PCF8574A - 0111A0A1A2, which with our connection diagram will give the address 0b0111 111 = 0x3F. If, say, A2 is connected to ground, then the address for PCF8574A will 0x3B. In total, 16 microcircuits can be simultaneously mounted on one i2c bus, 8 PCF8574A and PCF8574 each.

    Let's try to transfer something, initialize the i2c bus and transfer something to our PCF8574.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Generate the START signal I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Transfer 1 byte of data and generate the STOP signal ) char PCF8574A_reg ; // the variable that we write in PCF8574 void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Start I2C delay_ms(25); // Wait a little PCF8574A_reg.b0 = 0; // light the first LED PCF8574A_reg.b1 = 1; // turn off the second LED while (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // invert the state of the LEDs I2C_PCF8574_WriteReg (PCF8574A_reg); // transfer data to our PCF8574 ) )
    We compile and run our program and see that our LEDs blink alternately.
    I connected the LEDs cathode to our PCF8574 for a reason. The thing is that when a logical 0 is supplied to the output, the microcircuit honestly pulls its output to the ground, but when a logical 1 is applied, it connects it to + power through a current source of 100 μA. That is, you cannot get an “honest” logical 1 at the output. And you can’t light an LED with 100 µA. This was done in order to configure the PCF8574 output to the input without additional registers. We simply write to output register 1 (essentially setting the pin states to Vdd) and can simply short it to ground. The current source will not allow the output stage of our I/O expander to “burn out”. If the leg is pulled to the ground, then the ground potential is on it, and logical 0 is read. If the leg is pulled to +, then logical 1 is read. On the one hand, it’s simple, but on the other, you should always remember this when working with these microcircuits.


    Let's try to read the state of the pins of our expander chip.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Generate the START signal I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Transfer 1 byte of data and generate the STOP signal ) void I2C_PCF8574_ReadReg (unsigned char rData) ( I2C1_Start(); // Generate the START signal I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Read 1 byte of data and generate the STOP signal) char PCF8574A_reg; //variable that we write to PCF8574 char PCF8574A_out; // the variable we read into and PCF8574 char lad_state; //our LED is on or off void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Start I2C delay_ms(25); // Wait a little PCF8574A_reg.b0 = 0; // light the first LED PCF8574A_reg.b1 = 1; / / turn off the second LED PCF8574A_reg.b6 = 1; // Pull pins 6 and 7 to power. PCF8574A_reg.b7 = 1; while (1) ( delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg); // write data to PCF8574 I2C_PCF8574_ReadReg (PCF85 74A_out ); // read from PCF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // If 1 button is pressed (the 6th bit of the read byte from PCF8574 is 0, then turn our LED on/off) if (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // similar for 2 buttons and 2 LEDs ) )
    Now by pressing the buttons we turn our LED on or off. The microcircuit has another output INT. A pulse is generated on it every time the state of the pins of our I/O expander changes. By connecting it to the external interrupt input of our MK (I will tell you how to configure external interrupts and how to work with them in one of the following articles).

    Let's use our port expander to connect a character display through it. There are a great many of these, but almost all of them are built on the basis of a controller chip HD44780 and his clones. For example, I used an LCD2004 display.


    The datasheet for it and the HD44780 controller can be easily found on the Internet. Let's connect our display to the PCF8574, and hers, respectively, to our STM32.

    HD44780 uses a parallel gated interface. Data is transmitted by 8 (in one clock cycle) or 4 (in 2 clock cycles) gate pulses at the output E. (read by the display controller on a descending edge, transition from 1 to 0). Conclusion R.S. indicates whether we are sending data to our display ( RS = 1) (the characters it should display are actually ASCII codes) or the command ( RS = 0). RW indicates the direction of data transfer, writing or reading. Usually we write data to the display, so ( RW=0). Resistor R6 controls the display contrast. You can’t simply connect the contrast adjustment input to ground or power, otherwise you won’t see anything.. VT1 is used to turn the display backlight on and off according to MK commands. MicroC has a library for working with such displays via a parallel interface, but usually it’s expensive to spend 8 legs on a display, so I almost always use the PCF8574 to work with such screens. (If anyone is interested, I will write an article about working with HD44780-based displays built into MicroC via a parallel interface.) The exchange protocol is not particularly complicated (we will use 4 data lines and transfer information in 2 clock cycles), it is clearly shown by the following timing diagram:


    Before transferring data to our display, it must be initialized by passing service commands. (described in the datasheet, here we present only the most used ones)

    • 0x28- communication with the indicator via 4 lines
    • 0x0C- enable image output, disable cursor display
    • 0x0E- enable image output, enable cursor display
    • 0x01- clear the indicator
    • 0x08- disable image output
    • 0x06- after the symbol is displayed, the cursor moves by 1 familiar place
    Since we will need to work with this indicator quite often, we will create a plug-in library "i2c_lcd.h" . For this purpose in Project Manager Header Files and choose Add New File . Let's create our header file.

    #define PCF8574A_ADDR 0x3F //Address of our PCF8574 #define DB4 b4 // Correspondence between the PCF8574 pins and the indicator #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 // backlight control #define displenth 20 // number of characters in our display line static unsigned char BL_status; // variable storing the state of the backlight (on/off) void lcd_I2C_Init(void); // Display and PCF8574 initialization function void lcd_I2C_txt(char *pnt); // Displays a line of text, the parameter is a pointer to this line void lcd_I2C_int(int pnt); // Displays the value of an integer variable, the parameter is the output value void lcd_I2C_Goto(unsigned short row, unsigned short col); // moves the cursor to the specified position, parameters row - line (from 1 to 2 or 4 depending on the display) and col - (from 1 to displenth)) void lcd_I2C_cls(); // Clears the screen void lcd_I2C_backlight (unsigned short int state); // Enables (when transmitting 1 and disables - when transmitting 0 the display backlight)
    Now let's describe our functions, again we go to Project Manager right click on the folder Sources and choose Add New File . Create a file "i2c_lcd.с" .

    #include "i2c_lcd.h" //include our header file char lcd_reg; //register for temporary storage of data sent to PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //function for sending data via i2c to the PCF8574 chip ( I2C1_Start(); I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); ) void LCD_COMMAND (char com) / /function of sending a command to our display ( lcd_reg = 0; //write 0 to the temporary register lcd_reg.BL = BL_status.b0; //set the backlight pin in accordance with the value of the variable storing the backlight state lcd_reg.DB4 = com.b4; // set the 4 most significant bits of our command to the indicator data bus lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.EN = 1; //set the strobe output to 1 I2C_PCF8574_WriteReg ( lcd_reg); //write to the PCF8574 register, actually sending data to the indicator delay_us (300); //wait for timeout lcd_reg.EN = 0; //reset the strobe pulse to 0, the indicator reads the data I2C_PCF8574_WriteReg (lcd_reg); delay_us (300) ; lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = com.b0; //same for the 4 least significant bits lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg(lcd_reg); delay_us(300); ) void LCD_CHAR (unsigned char com) //sending data (ASCII character code) to the indicator ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //sending a character is different from sending commands by setting the RS bit to 1 lcd_reg.DB4 = com.b4; //set the 4 most significant bits at the inputs lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg) ; delay_us (300); lcd_reg.EN = 0; // reset the strobe pulse to 0, the indicator reads the data I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //set the 4 least significant bits at the inputs lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg ( lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); ) void lcd_I2C_Init(void) ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //initialize our I2c module for MK delay_ms(200) ;lcd_Command(0x28); // Display in 4 bits per clock mode delay_ms (5); lcd_Command(0x08); //Disable data output to the display delay_ms (5); lcd_Command(0x01); //Clear the display delay_ms (5); lcd_Command(0x06); //Enable automatic cursor shift after displaying the symbol delay_ms (5); lcd_Command(0x0C); //Turn on displaying information without displaying the cursor delay_ms (25); ) void lcd_I2C_txt(char *pnt) //Output a string of characters to the display ( unsigned short int i; //temporary character array index variable char tmp_str; //temporary array of characters, length 1 greater than the length of the display line, since the line needs to be terminated сiv with a NULL ASCII character 0x00 strncpy(tmp_str, pnt, displenth); //copy no more than displenth characters of the original string to our temporary string for (i=0; i Now let's connect the newly created library to the file with our main function:

    #include "i2c_lcd.h" //include our header file unsigned int i; //temporary variable counter void main() ( lcd_I2C_Init(); //initialize the display lcd_I2C_backlight (1); //turn on the backlight lcd_I2C_txt ("Hello habrahabr"); //display the line while (1) ( delay_ms(1000) ; lcd_I2C_Goto (2,1); //go to character 1 of line 2 lcd_i2c_int (i); //display the value i++; //increment the counter ) )

    If everything is assembled correctly, then we should see text on the indicator and a counter incrementing every second. In general, nothing complicated :)

    In the next article we will continue to understand the i2c protocol and devices that work with it. Let's consider working with EEPROM 24XX memory and the MPU6050 accelerometer/gyroscope.

    Published 10/26/2016

    In the previous article, we looked at the operation of the STM32 with the I 2 C bus as a Master. That is, he was the leader and interrogated the sensor. Now let’s make the STM32 a Slave and respond to requests, that is, it itself works as a sensor. We will allocate 255 bytes of memory for registers with addresses from 0 to 0xFF, and allow the Master to write/read them. And to make the example not so simple, let’s make our STM32 an analog-to-digital converter with an I 2 C interface. The ADC will process 8 channels. The controller will give the results of the transformations to the Master when reading from registers. Since the result of the ADC conversion is 12 bits, we need 2 registers (2 bytes) for each ADC channel.

    i2c_slave.h contains settings:

    I2CSLAVE_ADDR– address of our device;

    ADC_ADDR_START– the starting address of the registers that are responsible for the results of ADC conversions.

    In file i2c_slave.c we are most interested in functions get_i2c1_ram And set_i2c1_ram. Function get_i2c1_ram is responsible for reading data from registers. It returns data from the specified address, which is given to the Master. In our case, the data is read from the array i2c1_ram, but if the Master asks for register addresses from the range allocated for ADC results, then the ADC conversion data is sent.

    get_i2c1_ram:

    Uint8_t get_i2c1_ram(uint8_t adr) ( //ADC data if ((ADC_ADDR_START<= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }

    Function set_i2c1_ram– writes data received from the Master into registers with the specified address. In our case, the data is simply written to an array i2c1_ram. But this is optional. You can, for example, add a check and, when a certain number arrives at a certain address, perform some actions. This way you can send different commands to the microcontroller.

    set_i2c1_ram:

    Void set_i2c1_ram(uint8_t adr, uint8_t val) ( i2c1_ram = val; return; )

    Initialization is quite simple:

    Int main(void) ( SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) ( ) )

    First we set the maximum operating frequency of the controller. Maximum speed is required when any delays on the I 2 C bus need to be avoided. Then we start the ADC operation using DMA. ABOUT . ABOUT . And finally, we initialize the I 2 C bus as Slave. As you can see, nothing complicated.

    Now let's connect our STM32 module to the Raspberry Pi. Let's connect potentiometers to the ADC channels. And we will read ADC indicators from our controller. Do not forget that for the I 2 C bus to work, you need to install pull-up resistors on each line of the bus.

    In the Raspberry console, let's check whether our device is visible on the I 2 C bus (about that):

    I2cdetect -y 1

    As you can see, the device address 0x27, although we specified 0x4E. When you have time, think about why this happened.

    To read from the registers of the I 2 C-Slave device, execute the command:

    I2cget -y 1 0x27 0x00

    Where:
    0x27– device address,
    0x00– register address (0x00…0xFF).

    To write to the registers of the I 2 C-Slave device, execute the command:

    I2cset -y 1 0x27 0xA0 0xDD

    De:
    0x27– device address,
    0xA0– register address
    0xDD-8-bit data (0x00…0xFF)

    The previous command wrote the number 0xDD to the register 0xA0(you can write to the first 16 registers, but there is no point, but they are reserved for ADC). Now let's read:

    I2cget -y 1 0x27 0xA0

    To simplify the process of reading ADC channel data, I wrote a script:

    #!/usr/bin/env python import smbus import time bus = smbus.SMBus(1) address = 0x27 while (1): ADC = (); for i in range(0, 8): LBS = bus.read_byte_data(address, 0x00+i*2) MBS = bus.read_byte_data(address, 0x00+i*2+1) ADC[i] = MBS*256 + LBS print ADC time.sleep(0.2)

    It polls and displays the results of all 8 ADC channels to the console.

    In a similar way, you can combine several microcontrollers. One of them should be Master(), the other Slave.

    I wish you success!

    In one of my projects I use STM32F030 microcontrollers. Recently, the need arose to connect external EEPROM memory via the I 2 C bus. At first I wanted to take a ready-made example from the Internet, but in the end I had to reinvent my wheel and write my own code. In the article I talk about typical rakes when working with the I 2 C STM32F030 bus, and I offer my bike an option for working with the tire.

    So, for criticism, I’ll take one of the code examples taken from the Internet:

    /** Description Writes a data byte to the I2C EEPROM. * Parameter data: variable to write to EEPROM. * WriteAddr parameter: Internal EEPROM write address. * Return value none */ uint32_t EEPROM_I2C_Write(uint8_t data, uint16_t WriteAddr) ( //uint32_t DataNum = 0; Address = Address + (WriteAddr / 256); /* Slave address configuration; number of bytes to be programmed (transferred); reboots and generate start */ I2C_TransferHandling(I2C1, Address, 1, I2C_Reload_Mode, I2C_Generate_Start_Write); /* Wait until the TXIS flag is set */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET); /* Send memory address */ I2C_SendData(I2C1, (uint8_t)WriteAddr); /* Wait until the TCR flag is set */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TCR) == RESET); /* Update CR2: set Slave Address, set write request, generate Start and specified end mode */ I2C_TransferHandling(I2C1, Address, 1, I2C_AutoEnd_Mode, I2C_No_StartStop); /* Wait until the TXIS flag is set */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET); /* Write data to TXDR */ I2C_SendData(I2C1, data); /* Wait until the STOPF flag is set */ while(I2C_GetFlagStatus(I2C1, I2C_ISR_STOPF) == RESET); /* Clear the STOPF flag */ I2C_ClearFlag(I2C1, I2C_ICR_STOPCF); )

    I paste this code into my project and check that the bus is working. Then I start checking the code for errors. First, I disable the memory chip so that there is no ACK on the bus when accessing. I check the code and immediately come across a rake. Let's figure out where the catch is.

    A start was issued to the bus, the device address was sent. Since we disabled the memory chip, the NACKF (Not Acknowledge Flag) flag was set. Let's look at the code further.

    But here the microcontroller freezes, as it is waiting for a request to transfer a byte (setting the TXIS flag, Transmit Interrupt Status ). The request will never arrive because the slave on the bus is not responding. This is the first rake. Accordingly, as long as there are no failures on the bus, our device is working normally. As soon as the slightest failure occurs, the microcontroller freezes completely. I look at the code further.

    There is an error here too. If the chip does not respond, the NACKF flag is set, and the TCR (Transfer Complete Reload) flag will never be raised. The microcontroller will freeze.

    The last line is waiting for the STOPF (Stop detection Flag) flag to be raised, but we have shorted the legs and blocked data exchange. Sheena notices the trick and the ARLO (Arbitration Lost) flag flies up. The STOPF flag is not set, the microcontroller freezes. Moreover, another rake appears.

    Since the ARLO flag is raised, data exchange on the bus is impossible, the microcontroller will not even issue a start to the bus.

    Thus, using the code described above, we add a pack of randomly appearing rakes to the project. In general, I decided to reinvent the wheel and write my own version of the code. I won’t describe the entire code (you can download it at the end of the article), I’ll just explain the key points.

    Sending data.

    /* Performs a transaction to write Size bytes into the Register at Address. Parameters: Adress - address of the slave device Register - register to which we want to transfer data Data - indicates where to get the data for transfer Size - how many bytes we want to transfer (from 1 to 254) Returns: 1 - if the data was successfully transferred 0 - if an error occurred * / u8 I2C_Write_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size) ( u8 Count=0; // Counter of successfully transferred bytes // Start I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1+Size); // Now either I2C will request the first byte to send, // Or the NACK flag will fly, indicating that the chip is not responding. // If the NACK flag will fly, we stop sending. while ((((I2C_BUS->ISR & I2C_ISR_TXIS)==0) && ( (I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) (); if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=Register; // Sending register address // Sending bytes until the TC flag goes up. // If the NACK flag goes up, we stop sending. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)= =0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) ( if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=*(Data+Count++); // Sending data ) I2C_Stop(); if (Count == Size) return 1; return 0; )

    After the bus starts and the address of the microcircuit is sent, there are 3 options for the outcome of events:

    • the byte was sent successfully, if there is another byte in the queue - TXIS is built, if all bytes are transferred - TC (Transfer Complete) is built
    • the microcircuit does not respond - NACKF is being built
    • other errors on the bus - ARLO or BERR (Bus Error) is raised, BUSY (Bus Busy) is lowered

    You should pay attention to the while loops - they are implemented taking into account all the options described above. Go ahead.

    Receiving data.

    /* Performs a transaction to read Size bytes from Register at address Address. Parameters: Adress - the address of the slave device Register - the register from which we want to receive data Data - indicates where to add the received data Size - how many bytes we want to receive (from 1 to 255) Returns: 1 - if the data was successfully received 0 - if an error occurred */ u8 I2C_Read_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size) ( u8 Count=0; // Counter of successfully received bytes // Start I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1); // Now either I2C will request the first byte to send , // Or the NACK flag flies, indicating that the chip is not responding. // If the NACK flag flies, we stop sending. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS- >ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) ( if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR = Register; // Sending register address ) // Restart I2C_Start_Direction_Adress_Size (I2C_Receiver , Adress, Size); // We receive bytes until the TC flag flies. // If the NACK flag flies, we stop receiving. while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) ( if (I2C_BUS->ISR & I2C_ISR_RXNE ) *(Data+Count++) = I2C_BUS->RXDR; // Receiving data ) I2C_Stop(); if (Count == Size) return 1; return 0; )

    Here everything is almost the same as during the transfer. I won’t describe it in detail, let’s move on.

    The article describes the I2C serial interface of 32-bit ARM microcontrollers of the STM32 series from STMicroelectronics. The architecture, composition and purpose of the interface configuration registers are considered, as well as examples of programs for its use are given.

    Introduction

    The I2C interface, or IIC, gets its acronym from the English words Inter-Integrated Circuit and is a serial bus consisting of two bidirectional communication lines called SDA and SCL, an abbreviation for the words Serial Data Address and Serial Clock. It provides data exchange between the microcontroller and various peripheral devices such as ADC, DAC, memory chips, other microcontrollers and chips. The diagram for connecting devices via the I2C interface is shown in Figure 1.

    Rice. 1. Connection diagram of devices via I 2 C interface

    The I2C interface standard was developed by Philips in the early 1980s. According to this standard, the interface had a 7-bit address. It allowed access to 127 devices at speeds of up to 100 kbps. Subsequently, the interface was developed and became 10-bit, allowing access to 1023 devices at speeds of up to 400 kbit/s. Maximum
    the permissible number of chips connected to one bus is limited by the maximum bus capacitance of 400 pF. Version 2.0 of the standard, released in 1998, introduced
    high-speed operating mode with speeds up to 3.4 Mbit/s with reduced power consumption. Version 2.1 from 2001 includes only minor improvements.

    Description of the I 2 C interface

    The STM32 microcontroller includes an I2C interface, which is distinguished by its development. It allows multiple bus masters and supports high-speed mode. In addition, the STM32 microcontroller can use the I2C interface for a wide range of applications, including checksum generation and verification. It can also be operated via SMBus (System Management Bus) and PMBus (Power Management Bus) protocols. Most STM32 models include two I2C interfaces named I2C1 and I2C2. The interface can operate in one of the following four modes:

    • Slave transmitter;
    • Slave receiver;
    • Master transmitter (leading transmitter);
    • Master receiver.

    By default, the interface operates in “Slave” mode and automatically switches to “Master” after generating a start condition. Switching from “Master” to “Slave” occurs when arbitration is lost or after a stop condition is generated, which allows several “Master” microcontrollers to operate in one system alternately. In Master mode, I2C initiates data exchange and generates a clock signal. The transfer of serial data is always preceded by a start condition, and the exchange always ends with a stop condition. Both of these conditions are generated in Master mode programmatically. In Slave mode, I2C is able to recognize its own address (7 or 10 bits) and the general call address. All-call address detection can be enabled or disabled programmatically. The address and data are transmitted in 8-bit parcels, most significant bit first. The first byte following the start condition contains the address (one byte in 7-bit mode and two bytes in 10-bit mode). The address is always transmitted in Master mode.

    The 8 clock cycles of transmitting a data byte are followed by the 9th clock cycle, during which the receiver must send the ACK notification bit, which gets its name from the word ACKnowledge. Figure 2 shows the timing diagram of one message of the I2C interface. The presence of a notification in the response can be programmatically enabled or disabled. I2C interface address size (7 bits or 10 bits and address
    All Call) can be selected programmatically.


    Rice. 2. Timing diagram of one packet of interface I

    Architecture of the I 2 C interface block

    Functional diagram I2C interface block for the STM32 microcontroller is shown in Figure 3.


    Rice. 3. Functional diagram of the I 2 C interface block

    The shift register in this diagram is the main register through which data is sent and received. The transmitted data is pre-recorded in the data register, after which it is sequentially transmitted through the shift register to the SDA communication line. Data received over the same communication line is accumulated in a shift register and then moved to the data register. Thus, the interface can only send and receive data one at a time. In addition, the shift register is connected in hardware to a comparator, which allows you to compare the received address
    with address registers and thus determine for whom the next data block is intended. The frequency control node allows you to generate the SCL synchronization signal as a master and synchronize from this signal as a slave device. The CCR register provides software configuration of this node. The interface block is connected to the PCLK1 output of the APB1 bus via
    two prescalers. The microcontroller supports two communication modes: Standard Speed ​​– up to 100 kHz, and Fast Speed ​​– up to 400 kHz. Depending on the exchange mode, the module clock frequency must be at least 2 MHz in standard mode and at least 4 MHz in fast mode. The calculation block allows hardware to calculate the checksum of a data block and store it in the PEC register. The control of the I2C interface block, as well as the generation of event and interrupt flags, is performed by the control logic unit. It also allows you to service DMA requests and generate an ACK signal. Communication of this block with the microcontroller is carried out programmatically using control registers CR1, CR2 and status registers SR1, SR2.

    Interrupts from I 2 C

    The I2C interface has a hardware organization capable of generating interrupt requests depending on the operating mode and current events. Table 1 shows interrupt requests from the I2C interface.

    Table 1. Interrupt requests from the I 2 C interface

    Description of registers

    To work with the I2C interface, the STM32 microcontroller has special registers. A map of these registers with the names of the bits included in them is presented in Table 2. Let's consider the registers necessary for the operation of the I2C interface. These include:

    • I 2 C_CR1 – control register 1;
    • I 2 C_CR2 – control register 2;
    • I 2 C_OAR1 – own address register 1;
    • I 2 C_OAR2 – own address register 2;
    • I 2 C_DR – data register;
    • I 2 C_SR1 – status register 1;
    • I 2 C_SR2 – status register 2;
    • I 2 C_CCR – clock signal control register;
    • I 2 C_TRISE – TRISE parameter register.

    Some bits of these registers are used to operate in SMBus mode. The I2C_CR1 register is the first control register of the I2C interface. It has the following control bits:

    • bit 15 SWRST – provides software reset of the I 2 C bus;
    • digit 14 – reserved;
    • bit 13 SMBus – generates an alarm signal in SMBus mode;
    • bit 12 PEC – serves for the Packet Error Checking function;
    • bit 11 POS – serves to analyze ACK or PEC signals upon reception;
    • bit 10 ACK – returns the ACK notification bit after receiving a valid address or data byte;
    • bit 9 STOP – serves to generate and analyze a stop condition;
    • bit 8 START – serves to generate and analyze the start condition;
    • bit 7 NOSCTETCH – disables clock stretching in slave mode;
    • bit 6 ENGC – allows general call;
    • bit 5 ENPEC – enables the PEC signal;
    • bit 4 ENARP – enables the ARP signal;
    • bit 3 SMBTYPE – assigns the interface type as a master or slave for SMBus mode;
    • digit 2 – reserved;
    • bit 1 SMBUS – switches I 2 C and SMBus modes;
    • bit 0 PE – allows the interface to operate.

    The I2C_CR2 register is the second control register of the I2C interface and has the following control bits:

    • bits 15…13 – reserved;
    • bit 12 LAST – used in master receiver mode to allow generation of a NACK signal based on the last byte received;
    • bit 11 DMAEN – allows DMA request;
    • bit 10 ITBUFEN – allows interrupts from the buffer;
    • bit 9 ITEVTEN – enables event interrupts;
    • bit 8 ITERREN – enables error interrupts;
    • digits 7 and 6 – reserved;
    • bits 5…0 FREQ – set the bus operating frequency.

    The I2C_OAR1 register is the first register of its own address, includes the following bits:

    • bit 15 ADDMODE – sets 7- or 10-bit addressing mode as a slave;
    • bits 14…10 – reserved;
    • bits 9 and 8 ADD – assign 9 and 8 address bits for 10-bit interface addressing;
    • bits 7…1 ADD – assign 7…1 address bits;
    • bit 0 ADD0 – assigns address bit 0 for 10-bit interface addressing.

    The I2C_OAR2 register is the second register of its own address, includes the following bits:

    • bits 15...8 – reserved;
    • bits 7…1 ADD – assign 7…1 address bits in double addressing mode;
    • bit 0 ENDUAL – allows double addressing mode.

    The I2C_DR data register has 8 DR bits for receiving and transmitting data on the I2C bus. Data is written to this register for transmission and read from it upon reception. Bits 15...9 are reserved. The I2C_SR1 register is the first status register and includes the following bits:

    • bit 15 SMBALERT – signals an SMBus bus alarm;
    • digit 13 – reserved;
    • bit 14 TIMEOUT – notifies about a timeout error for the SCL signal;
    • bit 12 PECERR – indicates a PEC error during reception;
    • bit 11 OVR – generated when there is a data overflow error;
    • bit 10 AF – occurs in case of notification error;
    • bit 9 ARLO – indicates a loss of bus rights error;
    • bit 8 BERR – set when there is a bus error;
    • bit 7 TxE – notifies that the data register is empty;
    • digit 5 ​​– reserved;
    • bit 6 RxNE – informs that the data register is not empty;
    • bit 4 STOPF – detects a stop condition in slave mode;
    • bit 3 ADD10 – set when the master sent the first byte of the address with 10-bit addressing;
    • bit 2 BTF – notifies about the completion of a byte transfer;
    • bit 1 ADDR – set if an address is sent in master mode or an address is received in slave mode;
    • bit 0 SB – set when generating a start condition in master mode.

    The I2C_SR2 register is the second status register, includes the following bits:

    • bits 15…8 PEC – contain the frame checksum;
    • bit 7 DUALF – is a double addressing flag in slave mode;
    • bit 6 SMBHOST – set when the SMBus Host header is received in slave mode;
    • bit 5 SMBDEFAULT – occurs if the default address for the SMBus device in slave mode is accepted;
    • bit 4 GENCALL – indicates that the general call address in slave mode has been received;
    • digit 3 – reserved;
    • bit 2 TRA – notifies about the transmission/reception mode;
    • bit 1 BUSY – informs that the bus is busy;
    • bit 0 MSL – detects the “Master”/“Slave” mode.

    The I2C_CCR register is a clock signal control register, which includes the following bits:

    • bit 15 F/S – sets standard or fast speed for master mode;
    • bit 14 DUTY – assigns a duty cycle of 2 or 16/9 in fast mode;
    • digits 13 and 12 are reserved;
    • bits 11…0 CCR – control the clock signal for fast and standard speed in master mode.

    The I2C_TRISE register is a TRISE parameter register, which includes:

    • bits 15...6 – reserved;
    • bits 5…0 TRISE – define the maximum rise time for fast and standard speed in master mode. This parameter specifies the point in time at which the line states are sampled.

    More detailed description The assignments of all I2C registers and their bits can be found on the website www.st.com.

    Pprogramming interfaceI 2 WITH

    Let's consider a practical implementation of using the I2C interface. To do this, you can use the standard peripheral library of the STM32 microcontroller. For the I2C interface, the settings for mode, speed and everything else are in the header file and declared as a structure:

    I2C_InitTypeDef:typedef struct( uint32_t I2C_ClockSpeed; uint16_t I2C_Mode; uint16_t I2C_DutyCycle; uint16_t I2C_OwnAddress1; uint16_t I2C_Ack; uint16_t I2C_ AcknowledgedAddress; )I2C_InitTypeDef;

    In this structure, its elements have the following purpose:

    • uint32_t I 2 C_ClockSpeed ​​– clock signal frequency, maximum – 400 KHz;
    • uint16_t I 2 C_Mode – operating mode;
    • uint16_t I 2 C_DutyCycle – settings for working in fast mode;
    • uint16_t I 2 C_OwnAddress – device’s own address;
    • uint16_t I 2 C_Ack – whether the use of the ACK acknowledgment bit is enabled or disabled;
    • uint16_t I 2 C_AcknowledgedAddress – select the address format: 7 bits or 10 bits.

    Let's look at the procedures for initializing and working with the I2C interface. To configure the I2C interface as a master device and transmit data through it, you must perform the following steps:

    1. allow port clocking;
    2. initialize I 2 C by specifying its speed, address and address format;
    3. assign microcontroller pins;
    4. enable the interface;
    5. create a starting condition;
    6. send the address of the addressed device and data;
    7. create a stop condition.

    To facilitate the programming process, it is advisable to create a set of basic functions for working with I2C. Listing 1 shows the function to initialize the I2C interface in accordance with the algorithm described above.

    Listing 1 GPIO_InitTypeDef gpio; // Create a structure for I/O ports I2C_InitTypeDef i2c; // Create a structure for the I2C interface void init_I2C1(void) ( // Enable clocking RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // Initialize I2C i2c.I2C_ClockSpeed ​​= 1000 00;i2c.I2C_Mode = I2C_Mode_I2C;i2c.I2C_DutyCycle = I2C_DutyCycle_2; // Set address=0x12 i2c.I2C_OwnAddress1 = 0x12; i2c.I2C_Ack = I2C_Ack_Disable; i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &i2c); // Assign gpio interface pins .GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;

    Now let's look at the function for communicating via I2C. To expand its capabilities, this function has three parameters: the number of the I2C block used, the direction of data transfer and the address of the slave device. The code for this function is shown in Listing 2.

    Listing 2 void I2C_StartTransmission(I2C_TypeDef* I2Cx, uint8_t transmissionDirection, uint8_t slaveAddress) ( // Wait for the bus to become free while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)); // Generate the start condition I2C_GenerateSTART(I2Cx, ENABLE); // Wait for the bit to be set while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); // Send address to slave I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); // If data transmission if(transmissionDirection== I2C_Direction_Transmitter) (while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MA STER_TRANSMITTER_MODE_SELECTED));) // If data is received if(transmissionDirection== I2C_Direction_Receiver) (while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));) )

    The above function uses the simple functions for sending and receiving data shown in Listing 3.

    Listing 3 // Data transfer function void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data) ( // Call the library data transfer function I2C_SendData(I2Cx, data); // Wait for the end of data transfer while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); ) // Data receiving function uint8_t I2C_ReadData(I2C_TypeDef* I2Cx) ( // Wait for data to arrive while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2Cx); // Read data from the register return data; // Return data to calling function

    Having finished exchanging data via I2C, you need to call the function for generating a stop condition
    I2C_Generate STOP(I2Cx, ENABLE).
    Based on the above functions, you can create programs to work with a wide variety of peripheral devices.

    Conclusion

    The undeniable advantage of the I2C interface is the ease of connecting devices using just two communication lines and a common wire, thanks to which this interface is firmly established in technology and is still widely used in modern equipment.