{"id":5666,"date":"2020-03-23T14:20:33","date_gmt":"2020-03-23T21:20:33","guid":{"rendered":"https:\/\/visualgdb.com\/w\/?p=5666"},"modified":"2020-03-23T14:28:53","modified_gmt":"2020-03-23T21:28:53","slug":"using-the-i2c-interface-on-the-stm32-devices","status":"publish","type":"post","link":"https:\/\/visualgdb.com\/tutorials\/arm\/stm32\/i2c\/","title":{"rendered":"Using the I2C Interface on the STM32 Devices"},"content":{"rendered":"<p>This tutorial shows how to use the I2C interface on the STM32 devices. We will connect 2 STM32 boards using their I2C interface, will go over the I2C packet format, and will show how to use the STM32 HAL API to send and receive message using I2C. We will use a third STM32 board together with <a href=\"https:\/\/sysprogs.com\/analyzer2go\/\">Analyzer2Go<\/a> to look into the I2C signals.<\/p>\n<p>Before you begin, install Visual Studio and VisualGDB.<\/p>\n<ol>\n<li>We will begin with creating a basic project sending a simple message over I2C in the <strong>master<\/strong> mode. Start Visual Studio and open the VisualGDB Embedded Project Wizard:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/01-newprj-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5667\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/01-newprj-1.png\" alt=\"\" width=\"1024\" height=\"680\" \/><\/a><\/li>\n<li>Enter the name and location for your project and click &#8220;Create&#8221; to launch the VisualGDB-specific part of the wizard:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/02-prjpath.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5668\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/02-prjpath.png\" alt=\"\" width=\"1024\" height=\"680\" \/><\/a><\/li>\n<li>Proceed with the default settings (Create a new Embedded Binary using MSBuild) on the first page of the VisualGDB&#8217;s Wizard:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/03-msbuild.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5669\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/03-msbuild.png\" alt=\"\" width=\"886\" height=\"693\" \/><\/a><\/li>\n<li>On the next page, select your device. In this example, we will use the STM32F4-Discovery board for the I2C master, so we pick the STM32F407VG device:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/04-device-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5670\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/04-device-1.png\" alt=\"\" width=\"886\" height=\"693\" \/><\/a><\/li>\n<li>Select the basic &#8220;LEDBlink&#8221; example on the next page. As we will be replacing the <strong>main()<\/strong> function with our own code, proceed with the default LED port settings even if they don&#8217;t match your board:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/05-blink.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5671\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/05-blink.png\" alt=\"\" width=\"886\" height=\"693\" \/><\/a><\/li>\n<li>Select debug settings that match your board and click &#8220;Finish&#8221; to create the project:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/06-debug.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5672\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/06-debug.png\" alt=\"\" width=\"886\" height=\"693\" \/><\/a><\/li>\n<li>The I2C protocol uses 2 physical signals: <strong>SCL<\/strong> (clock) and <strong>SDA<\/strong> (data). Open the datasheet (not reference manual) for your device and locate the pins that could be used for the I2C signals. In this example, we will use the pins PB6 and PB9 (both would need to be switched to the AF4 mode):<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/pinout.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5680\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/pinout.png\" alt=\"\" width=\"1409\" height=\"543\" \/><\/a><\/li>\n<li>Add the following function to your main file that will enable the GPIOB port and switch the PB6 and PB9 pins to the AF4 mode (I2C):\n<pre class=\"\">void ConfigureI2CPins()\r\n{\r\n    GPIO_InitTypeDef GPIO_InitStruct;\r\n\r\n    GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_9;\r\n    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;\r\n    GPIO_InitStruct.Pull = GPIO_PULLUP;\r\n    GPIO_InitStruct.Speed = GPIO_SPEED_FAST;\r\n    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;\r\n    __GPIOB_CLK_ENABLE();\r\n    HAL_GPIO_Init(GPIOB, &amp;GPIO_InitStruct);\r\n}<\/pre>\n<\/li>\n<li>Before we can start using the I2C peripheral from our code, we need to enable its clock by calling <strong>_I2C1_CLK_ENABLE()<\/strong> and configure it by calling <strong>HAL_I2C_Init()<\/strong>. To do this, replace the contents of your main() function with the following code:\n<pre class=\"\">    HAL_Init();\r\n    ConfigureI2CPins();\r\n\r\n    I2C_HandleTypeDef hI2C = I2C_HandleTypeDef();\r\n\r\n    hI2C.Instance = I2C1;\r\n    hI2C.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;\r\n    hI2C.Init.ClockSpeed = 10240;\r\n    hI2C.Init.DutyCycle = I2C_DUTYCYCLE_2;\r\n\r\n    __I2C1_CLK_ENABLE();\r\n\r\n    if (HAL_I2C_Init(&amp;hI2C) != HAL_OK)\r\n        asm(\"bkpt 255\");<\/pre>\n<p>The <strong>HAL_I2C_Init()<\/strong> function included in the STM32 SDK will automatically read the high-level parameters, such as <strong>AddressingMode<\/strong>, and will configure the I2C hardware accordingly. Below is an overview of the main I2C configuration parameters passed to <strong>HAL_I2C_Init(). <\/strong>Note that we have selected the 7-bit addressing mode, each I2C transmission will start with a byte consisting of a 7-bit address and 1 direction bit (specifying whether it&#8217;s a read or a write). We will explain this in detail later in the tutorial.<\/li>\n<li>Finally, add the following code to actually transmit some sample data over I2C and build the project via Build-&gt;Build Solution:\n<pre class=\"\">    if (HAL_I2C_Master_Transmit(&amp;hI2C, 0x5A, (uint8_t *)\"1234\", 4, 10000) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }<\/pre>\n<p><a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/07-built-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5682\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/07-built-1.png\" alt=\"\" width=\"1224\" height=\"819\" \/><\/a><\/li>\n<li>Set a breakpoint on the HAL_I2C_Master_Transmit() call and press F5 to start debugging. Once the brekapoint triggers, the device will be ready to send the data via the I2C interface:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/08-step-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5683\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/08-step-1.png\" alt=\"\" width=\"1224\" height=\"819\" \/><\/a><\/li>\n<li>Before we create an I2C <strong>slave<\/strong> firmware for the second board (that would accept transmissions from the I2C <strong>master<\/strong>), we will use a logic analyzer to have a detailed look into the I2C signals. In this tutorial, we will use <a href=\"https:\/\/sysprogs.com\/analyzer2go\/\">Analyzer2Go<\/a> to sample the I2C signals using a third STM32 board. Launch Analyzer2Go and select your the board you want to use as a logic analyzer from the list: <a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/09-analyzer.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5675\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/09-analyzer.png\" alt=\"\" width=\"881\" height=\"534\" \/><\/a><\/li>\n<li>Press OK to initialize the board. Analyzer2Go will show the ports that could be used a inputs. Connect the SCL and SDA signals (PB6 and PB9 in this tutorial) to the logic analyzer inputs and make sure that both USB ports of the logic analyzer boards are connected to your computer:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/i2c1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5685\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/i2c1.jpg\" alt=\"\" width=\"1200\" height=\"1215\" \/><\/a><br \/>\nIt is also recommended to connect the ground signals of both boards, even if they are connected to the same USB controller.<\/li>\n<li>Enable the logic analyzer inputs that are connected to the I2C signals and name them (e.g. SCL and SDA). Then press the <strong>Record<\/strong> button to begin continuous recording:<br \/>\n<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/10-signals.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5676\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/10-signals.png\" alt=\"\" width=\"1612\" height=\"1013\" \/><\/a><\/li>\n<li>Once Analyzer2Go is recording, step over the <strong>HAL_I2C_Master_Transmit()<\/strong> call. The call will return an error and Analyzer2Go will display some activity on the SCL and SDA pins:<br \/>\n<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/11-capture.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5677\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/11-capture.png\" alt=\"\" width=\"1612\" height=\"1013\" \/><\/a><\/li>\n<li>Stop the recording, click on the magnifying lens symbol on the SCL signal to automatically zoom to a meaningful display, and switch to the Protocol Analyzers page in Analyzer2Go:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/12-i2c.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5678\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/12-i2c.png\" alt=\"\" width=\"1612\" height=\"1013\" \/><\/a><\/li>\n<li>Drag the I2C analyzer to the Protocol Analyzers area and connect its inputs. Analyzer2Go will decode the I2C transmission, showing how it consists of a <strong>START<\/strong> bit, a <strong>WRITE<\/strong> request at address of 0x5A followed by a <strong>NAK<\/strong>, that caused the <strong>HAL_I2C_Master_Transmit()<\/strong> call to fail:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/13-write-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5755\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/13-write-1.png\" alt=\"\" width=\"1612\" height=\"1013\" \/><\/a><\/li>\n<li>Switch the SDA view to raw to examine what exactly is going on with the SCL and SDA signals:<br \/>\n<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/signals-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5688\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/signals-1.png\" alt=\"\" width=\"1138\" height=\"301\" \/><\/a><br \/>\nThe I2C master begins the transmission by setting the <strong>SDA<\/strong> signal to 0, then transmits 7 address bits (<strong>0101101<\/strong>) followed by <strong>0<\/strong> indicating a write. Then it waits for the slave to acknowledge the transmission by holding the <strong>SDA<\/strong> at 0 after the direction bit. As there is no slave connected to the master yet, the SDA line remains high, indicating a NAK.<br \/>\nNote that the address passed to <strong>HAL_I2C_Master_Transmit() <\/strong>is aligned to the left per ST&#8217;s documentation. This means that the <strong>least significant bit<\/strong> of the 8-bit value passed to the function is always replaced by the direction bit. I.e. writing to address <strong>0x5B\u00a0<\/strong>(0101101<span style=\"text-decoration: underline;\"><strong>1<\/strong><\/span>) would be equivalent to writing to <strong>0x5A<\/strong> (0101101<span style=\"text-decoration: underline;\"><strong>0<\/strong><\/span>).<\/li>\n<li>Now we will connect the I2C slave board that will receive the data from the master board. Pick a board that has I2C pins exposed and make a note of their numbers and locations. In this tutorial, we will use the STM32F410-Nucleo board, that has the I2C signals on the PB6 and PB8 pins:<br \/>\n<table style=\"border-collapse: collapse; width: 100%;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 33.3333%;\">Signal<\/td>\n<td style=\"width: 33.3333%;\">STM32F4-Discovery<\/td>\n<td style=\"width: 33.3333%;\">STM32F410RB-Nucleo<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">SCL<\/td>\n<td style=\"width: 33.3333%;\">PB6<\/td>\n<td style=\"width: 33.3333%;\">PB8 (CN10, Pin 3)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">SDA<\/td>\n<td style=\"width: 33.3333%;\">PB9<\/td>\n<td style=\"width: 33.3333%;\">PB9 (CN10, Pin 5)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li>Connect both boards used for I2C to the USB and make sure the SCL\/SDA signals and ground are connected:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/3boards.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5745\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/3boards.jpg\" alt=\"\" width=\"1280\" height=\"1066\" \/><\/a><\/li>\n<li>Now we will create a project for the I2C Slave board. Open another Visual Studio instance and launch the VisualGDB Embedded Project Wizard again:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/14-slave.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5746\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/14-slave.png\" alt=\"\" width=\"1024\" height=\"680\" \/><\/a><\/li>\n<li>Pick the device for your second board, and otherwise proceed with the same settings you used when creating the Master project:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/15-target2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5747\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/15-target2.png\" alt=\"\" width=\"886\" height=\"693\" \/><\/a><\/li>\n<li>The basic I2C slave project has 3 differences from the I2C master project:\n<ul>\n<li>Instead of using the PB6 pin for the SCL signal, it uses PB8 (per board layout)<\/li>\n<li>The initialization logic sets the <strong>hI2C.Init.OwnAddress1<\/strong> to the address used by the master board<\/li>\n<li>Instead of calling <strong>HAL_I2C_Master_Transmit()<\/strong>, it calls <strong>HAL_I2C_Slave_Receive()<\/strong><\/li>\n<\/ul>\n<p>The final code for the STM32F410-Nucleo board looks as follows:<\/p>\n<pre class=\"\">void ConfigureI2CPins()\r\n{\r\n    GPIO_InitTypeDef GPIO_InitStruct;\r\n    GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;\r\n    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;\r\n    GPIO_InitStruct.Pull = GPIO_PULLUP;\r\n    GPIO_InitStruct.Speed = GPIO_SPEED_FAST;\r\n    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;\r\n\r\n    __GPIOB_CLK_ENABLE();\r\n\r\n    HAL_GPIO_Init(GPIOB, &amp;GPIO_InitStruct);\r\n}\r\n\r\nint main(void)\r\n{\r\n    HAL_Init();\r\n    ConfigureI2CPins();\r\n\r\n    I2C_HandleTypeDef hI2C = I2C_HandleTypeDef();\r\n    hI2C.Instance = I2C1;\r\n    hI2C.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;\r\n    hI2C.Init.ClockSpeed = 10240;\r\n    hI2C.Init.OwnAddress1 = 0x5A;\r\n    hI2C.Init.DutyCycle = I2C_DUTYCYCLE_2;\r\n\r\n    __I2C1_CLK_ENABLE();\r\n\r\n    if (HAL_I2C_Init(&amp;hI2C) != HAL_OK)\r\n        asm(\"bkpt 255\");\r\n\r\n    uint8_t buffer[4];\r\n    if (HAL_I2C_Slave_Receive(&amp;hI2C, buffer, sizeof(buffer), HAL_MAX_DELAY) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }\r\n\r\n    asm(\"bkpt 255\");\r\n}<\/pre>\n<\/li>\n<li>Now we will try out the basic message transmission. First, launch the slave firmware and make sure it starts running the <strong>HAL_I2C_Slave_Receive()<\/strong> function, that will wait for the message. Then, start the master firmware and and get ready to step over <strong>HAL_I2C_Master_Transmit()<\/strong>:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/16-transmit.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5748\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/16-transmit.png\" alt=\"\" width=\"1655\" height=\"864\" \/><\/a><\/li>\n<li>As soon as you step over <strong>HAL_I2C_Master_Transmit()<\/strong>, the <strong>HAL_I2C_Slave_Receive()<\/strong> function on the slave board will return. The <strong>buffer<\/strong> variable will contain the message sent from the master:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/17-done.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5749\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/17-done.png\" alt=\"\" width=\"1655\" height=\"864\" \/><\/a><\/li>\n<li>If you were recording the transmission with Analyzer2Go, it will show a &#8220;Write at 5A&#8221; header followed by 4 bytes: 0x31, 0x32, 0x33 and ox34, corresponding to the &#8220;1234&#8221; message:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/18-packet.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5750\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/18-packet.png\" alt=\"\" width=\"1882\" height=\"273\" \/><\/a><\/li>\n<li>Switch to the raw view to see what is going on at the lowest level:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/19-raw.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5751\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/19-raw.png\" alt=\"\" width=\"1883\" height=\"275\" \/><\/a>Each I2C transmission consists of a 9 bits (8-bit data packet followed by the ACK\/NAK bit). The first packet always contains the 7-bit target address and the Read\/Write bit. The subsequent packets contain raw 8-bit bytes, as sent by the firmware.<br \/>\nNote how before we connected the slave board, the first packet was not acknowledged (the SDA pin was high during the ACK\/NAK phase) and the master immediately aborted the rest of the transmission. Now that the slave is present (and acknowledged the address packet), the master continued sending out the actual payload.<\/li>\n<li>Now we will play around with various I2C parameters and will show how it affects the physical data transmitted via the I2C pins. First of all, we will change the transfer direction, sending the data from the slave to the master:\n<pre class=\"\">    \/\/Master\r\n    uint8_t buffer[4];\r\n    if (HAL_I2C_Master_Receive(&amp;hI2C, 0x5A, buffer, sizeof(buffer), 10000) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }<\/pre>\n<pre class=\"\">    \/\/Slave\r\n    if (HAL_I2C_Slave_Transmit(&amp;hI2C, (uint8_t *)\"5678\", 4, HAL_MAX_DELAY) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }<\/pre>\n<\/li>\n<li>The first packet will change from &#8220;Write at 5A&#8221; (raw value of 0101101<span style=\"text-decoration: underline;\"><strong>0<\/strong><\/span>) to &#8220;Read at 5A&#8221; (raw value of 0101101<span style=\"text-decoration: underline;\"><strong>1<\/strong><\/span>) and the SDA pin will be driven by the slave board during the payload transfers:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/read.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5752\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/read.png\" alt=\"\" width=\"1882\" height=\"306\" \/><\/a><\/li>\n<li>The following table summarizes the physical data transferred via the I2C bus during the 2 sample transfers:<br \/>\n<table style=\"border-collapse: collapse; width: 100%;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 33.3333%;\"><\/td>\n<td style=\"width: 33.3333%;\">Write &#8220;1234&#8221; to 0x5A<\/td>\n<td style=\"width: 33.3333%;\">Read &#8220;5678&#8221; from 0x5A<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #0<\/td>\n<td style=\"width: 33.3333%;\">0x5A (left-aligned address, LSB = 0)<\/td>\n<td style=\"width: 33.3333%;\">0x5B (left-aligned adderss, LSB = 1)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #1<\/td>\n<td style=\"width: 33.3333%;\">0x31 (&#8216;1&#8217;)<\/td>\n<td style=\"width: 33.3333%;\">0x35 (&#8216;5&#8217;)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #2<\/td>\n<td style=\"width: 33.3333%;\">0x32 (&#8216;2&#8217;)<\/td>\n<td style=\"width: 33.3333%;\">0x36 (&#8216;6&#8217;)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #3<\/td>\n<td style=\"width: 33.3333%;\">0x33 (&#8216;3&#8217;)<\/td>\n<td style=\"width: 33.3333%;\">0x37 (&#8216;7&#8217;)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #4<\/td>\n<td style=\"width: 33.3333%;\">0x34 (&#8216;4&#8217;)<\/td>\n<td style=\"width: 33.3333%;\">0x38 (&#8216;8&#8217;)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li>Now we will show how to use 10-bit addresses instead of 7-bit ones. Replace the <strong>I2C_ADDRESSINGMODE_7BIT<\/strong> value with <strong>I2C_ADDRESSINGMODE_10BIT<\/strong> in both master and slave firmware. Then change address from <strong>0x5A<\/strong> to <strong>0x15A<\/strong>. Observe how the I2C communication changes:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/10bit-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5758\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/10bit-1.png\" alt=\"\" width=\"1543\" height=\"239\" \/><\/a>In order to maintain compatibility to the 7-bit devices, each 10-bit transfer always starts as if it was a write to a special address (1111 0<span style=\"text-decoration: underline;\"><strong>XX<\/strong><\/span>0). The two X bits store the 2 most-significant bits of the 10-bit address. The next byte contains the remaining 8 bits of the 10-bit address (7-bit devices will treat it as a regular data byte sent to some other device). For write transactions, the payload immediately follows the address byte. For read transactions, the master generates a START condition (without a STOP one) after sending the address byte, and then initiates a read from the special address (1111 0<span style=\"text-decoration: underline;\">XX<\/span>1). Below is a byte-level breakdown of the sample transfers using 10-bit addressing:<br \/>\n<table style=\"border-collapse: collapse; width: 100%; height: 168px;\" border=\"1\">\n<tbody>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\"><\/td>\n<td style=\"width: 33.3333%; height: 24px;\">Write &#8220;1234&#8221; to 0x15A<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">Read &#8220;5678&#8221; from 0x15A<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Byte #0<\/td>\n<td style=\"width: 33.3333%; height: 24px; text-align: center;\" colspan=\"2\">0xF2 (1111 0<span style=\"text-decoration: underline;\"><strong>01<\/strong><\/span>0).\u00a0 Transmits the 2 most significant bits of the address (<span style=\"text-decoration: underline;\"><strong>01<\/strong><\/span> = 0x1).<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Byte #1<\/td>\n<td style=\"width: 33.3333%; height: 24px; text-align: center;\" colspan=\"2\">0x5A<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Between bytes #1 and #2<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">Nothing<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">START condition<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Byte #2<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0x31 (&#8216;1&#8217;)<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0xF3 (1111 001<span style=\"text-decoration: underline;\"><strong>1<\/strong><\/span>). Starts a READ sequence.<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Byte #3<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0x32 (&#8216;2&#8217;)<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0x35 (&#8216;5&#8217;)<\/td>\n<\/tr>\n<tr style=\"height: 24px;\">\n<td style=\"width: 33.3333%; height: 24px;\">Byte #4<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0x33 (&#8216;3&#8217;)<\/td>\n<td style=\"width: 33.3333%; height: 24px;\">0x36 (&#8216;6&#8217;<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #5<\/td>\n<td style=\"width: 33.3333%;\">0x34 (&#8216;4&#8217;)<\/td>\n<td style=\"width: 33.3333%;\">0x37 (&#8216;7&#8217;)<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 33.3333%;\">Byte #6<\/td>\n<td style=\"width: 33.3333%;\"><\/td>\n<td style=\"width: 33.3333%;\">0x38 (&#8216;8&#8217;)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/li>\n<li>Now we will show how to move the I2C-related logic into the background by using the interrupt-driven and DMA-driven I2C API. Before we do that, modify the slave I2C firmware to continuously receive and acknowledge 4096-byte chunks of data:\n<pre class=\"\">    uint8_t buffer[4096];\r\n    for (;;)\r\n    {\r\n        HAL_I2C_Slave_Receive(&amp;hI2C, buffer, sizeof(buffer), HAL_MAX_DELAY);\r\n    }<\/pre>\n<p>We will keep the slave firmware unchanged for the rest of the tutorial and will demonstrate different transfer modes by changing the master firmware.<\/li>\n<li>First of all, we will demonstrate the interrupt-driven mode. Unlike the regular <strong>HAL_I2C_Master_Transmit()<\/strong> function, that does not return until the entire transfer is complete, the interrupt-driven mode (<strong>HAL_I2C_Master_Transmit_IT()<\/strong>) returns almost immediately and lets the main program continue, while the I2C transfer is in progress. After each transferred byte, the hardware will raise an I2C interrupt, requesting the HAL drivers to send the next byte or call the end-of-transfer callback. In order to convert the current master firmware to the interrupt mode, move the hI2C variable to the global scope and add the following functions:\n<pre class=\"\">I2C_HandleTypeDef hI2C;\r\nextern \"C\" void I2C1_EV_IRQHandler()\r\n{\r\n    HAL_I2C_EV_IRQHandler(&amp;hI2C);\r\n}\r\n\r\nextern \"C\" void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)\r\n{\r\n    asm(\"bkpt 255\");\r\n}<\/pre>\n<p>The <strong>I2C1_EV_IRQHandler()<\/strong> will immediately call the <strong>HAL_I2C_EV_IRQHandler()<\/strong> function included in the STM32 HAL, that will determine the cause of the interrupt, and handle it accordingly. When the entire transfer is complete, it will call the <strong>HAL_I2C_MasterTxCpltCallback()<\/strong> function.<\/li>\n<li>Update the main() function in the master firmware as shown below:\n<pre class=\"\">    if (HAL_I2C_Init(&amp;hI2C) != HAL_OK)\r\n        asm(\"bkpt 255\");\r\n\r\n    uint8_t tempBuffer[4096];\r\n    for (int i = 0; i &lt; sizeof(tempBuffer); i++)\r\n        tempBuffer[i] = i;\r\n\r\n    NVIC_EnableIRQ(I2C1_EV_IRQn);\r\n\r\n    if (HAL_I2C_Master_Transmit_IT(&amp;hI2C, 0x15B, tempBuffer, sizeof(tempBuffer)) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }\r\n\r\n    for (;;)\r\n    {\r\n        asm(\"nop\");\r\n    }<\/pre>\n<\/li>\n<li>Set a breakpoint at the call to <strong>HAL_I2C_Master_Transmit_IT()<\/strong> and wait for it to trigger. Then launch the slave firmware so that it begins listening on the I2C interface. Finally, resume the master. The breakpoint in <strong>HAL_I2C_MasterTxCpltCallback()<\/strong> will trigger:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/interrupt.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5761\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/interrupt.png\" alt=\"\" width=\"1196\" height=\"797\" \/><\/a>The call stack will show how the <strong>main()<\/strong> function was interrupted by the I2C interrupt, that invoked <strong>HAL_I2C_EV_IRQHandler()<\/strong> function, that, in turn, called <strong>HAL_I2C_MasterTxCpltCallback()<\/strong>.<\/li>\n<li>We can visualize the interrupt handler timing by setting an arbitrary GPIO pin from it and observing it via Analyzer2Go. Add the following code to <strong>Configure I2CPins()<\/strong>:\n<pre class=\"\">    __GPIOC_CLK_ENABLE();\r\n    GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;\r\n    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;\r\n    HAL_GPIO_Init(GPIOC, &amp;GPIO_InitStruct);<\/pre>\n<p>Then update the I2C interrupt handler to raise and lower the GPIOC12 pin before\/after calling <strong>HAL_I2C_EV_IRQHandler()<\/strong>:<\/p>\n<pre class=\"\">extern \"C\" void I2C1_EV_IRQHandler()\r\n{\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);\r\n    HAL_I2C_EV_IRQHandler(&amp;hI2C);\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);\r\n}<\/pre>\n<p>Observe how the pin controlled from <strong>I2C1_EV_IRQHandler()<\/strong> goes high for a short period of time after each payload byte is acknowledged, and then quickly goes low again, indicating that the control has returned to the <strong>main()<\/strong> function:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/irq.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5762\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/irq.png\" alt=\"\" width=\"1340\" height=\"371\" \/><\/a><\/li>\n<li>Finally, we will show how to transfer the data via the I2C interface using the DMA controller. Unlike the interrupt-driven mode where the hardware raises an interrupt after each byte, DMA mode can handle the entire memory buffer without interrupting the main firmware. Each peripheral (such as I2C) is associated with a specific stream\/channel of the DMA controller. You can find the specifics for your device by searching for &#8220;DMA1 Request Mapping&#8221; in the ST reference manual:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/dma.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5767\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/dma.png\" alt=\"\" width=\"1161\" height=\"471\" \/><\/a>For STM32F407, the outgoing I1C1 transfers are done using Stream 6 of Channel 1 on DMA1.<\/li>\n<li>In order to use transfer data via I2C using DMA, first declare an instance of the DMA handle:\n<pre class=\"\">static DMA_HandleTypeDef s_DMAHandle = DMA_HandleTypeDef();<\/pre>\n<p>Then, add the following logic to main():<\/p>\n<pre class=\"\">    __DMA1_CLK_ENABLE();\r\n    s_DMAHandle.Instance = DMA1_Stream6;\r\n    s_DMAHandle.Init.Channel = DMA_CHANNEL_1;\r\n\r\n    s_DMAHandle.Init.Direction = DMA_MEMORY_TO_PERIPH;\r\n    s_DMAHandle.Init.PeriphInc = DMA_PINC_DISABLE;\r\n    s_DMAHandle.Init.MemInc = DMA_MINC_ENABLE;\r\n    s_DMAHandle.Init.Mode = DMA_NORMAL;\r\n\r\n    s_DMAHandle.Init.Priority = DMA_PRIORITY_VERY_HIGH;\r\n\r\n    s_DMAHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;\r\n    s_DMAHandle.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;\r\n\r\n    if (HAL_DMA_Init(&amp;s_DMAHandle) != HAL_OK)\r\n        asm(\"bkpt 255\");\r\n\r\n    __HAL_LINKDMA(&amp;hI2C, hdmatx, s_DMAHandle);\r\n    NVIC_EnableIRQ(DMA1_Stream6_IRQn);\r\n    NVIC_EnableIRQ(I2C1_EV_IRQn);<\/pre>\n<p>Replace the call to <strong>HAL_I2C_Master_Transmit_IT()<\/strong> with <strong>HAL_I2C_Master_Transmit_DMA()<\/strong>:<\/p>\n<pre class=\"\">\u00a0   if (HAL_I2C_Master_Transmit_DMA(&amp;hI2C, 0x15B, tempBuffer, sizeof(tempBuffer)) != HAL_OK)\r\n    {\r\n        asm(\"bkpt 255\");\r\n    }<\/pre>\n<p>Finally, add an interrupt handler for the related DMA channel (keep the existing I2C interrupt handler as well):<\/p>\n<pre class=\"\">extern \"C\" void I2C1_EV_IRQHandler()\r\n{\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);\r\n    HAL_I2C_EV_IRQHandler(&amp;hI2C);\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_RESET);\r\n}\r\n\r\nextern \"C\" void DMA1_Stream6_IRQHandler()\r\n{\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);\r\n    HAL_DMA_IRQHandler(&amp;s_DMAHandle);\r\n    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);\r\n}<\/pre>\n<\/li>\n<li>Now the entire block will get transferred at once, with minimal interrupts from I2C and DMA:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/fast.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-5764\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/03\/fast.png\" alt=\"\" width=\"1351\" height=\"367\" \/><\/a>The hardware will still raise interrupts during the initial setup, and when one half of the DMA buffer is processed, but it will no longer interrupt the main program after each transferred byte.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>This tutorial shows how to use the I2C interface on the STM32 devices. We will connect 2 STM32 boards using<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[195,61],"_links":{"self":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/5666"}],"collection":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/comments?post=5666"}],"version-history":[{"count":10,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/5666\/revisions"}],"predecessor-version":[{"id":5768,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/5666\/revisions\/5768"}],"wp:attachment":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/media?parent=5666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/categories?post=5666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/tags?post=5666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}