{"id":499,"date":"2024-07-10T09:29:03","date_gmt":"2024-07-10T03:59:03","guid":{"rendered":"https:\/\/blog.mshafeeq.com\/?p=499"},"modified":"2024-10-02T09:35:59","modified_gmt":"2024-10-02T04:05:59","slug":"stm32f103c6t6a-bare-metal-programming-i2c-slave-read-write","status":"publish","type":"post","link":"https:\/\/blog.mshafeeq.com\/index.php\/2024\/07\/10\/stm32f103c6t6a-bare-metal-programming-i2c-slave-read-write\/","title":{"rendered":"STM32F103C6T6A Bare Metal Programming &#8211; I2C Slave Read &#038; Write"},"content":{"rendered":"\n<p>In my previous blog posts, we explored how to utilize the STM32 microcontroller as a master device, including performing read and write operations with a slave. Now, let\u2019s advance to the next level, as the microcontroller is also capable of functioning as a slave device. In this post, I will explain how to configure the STM32 as a slave and how to carry out read and write operations. Since this topic builds on the previous discussions, I highly recommend reviewing those posts if you are new to I2C.<\/p>\n\n\n\n<p>We will use most of the functions that were used in the master I2C with some necessary modifications.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">I2C Slave initialization<\/h4>\n\n\n\n<p>The I2C slave initialization is similar to what we have done for master. Since the master doesn\u2019t have any I2C address but the slave has, we just want to configure the address here. The I2C address is to be configured in the I2C Own Address Register 1 (I2C_OAR1) register. As per the reference manual software should set the 14th bit of this register as 1. So we need to add the following line in the init function<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;OAR1 = 0x4000 | (addr &lt;&lt; 1);<\/code><\/pre>\n\n\n\n<p>We can use the polling method to receive the data, but I am using the interrupt method here because that will be a more efficient way while writing the code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1->CR2 |= I2C_CR2_ITEVTEN;\n\nuint32_t prioritygroup = NVIC_GetPriorityGrouping();\nNVIC_SetPriority(I2C1_EV_IRQn, NVIC_EncodePriority(prioritygroup, 10, 0));\nNVIC_EnableIRQ(I2C1_EV_IRQn);<\/code><\/pre>\n\n\n\n<p>Once the slave address matches with the address sent by master, the slave has to send the ACK for that, so we need to enable the ACK bit in CR1 during initialization<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1->CR1 |= I2C_CR1_ACK;<\/code><\/pre>\n\n\n\n<p>So the complete initialization function will be looks like<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void i2c_slave_init(uint8_t addr) {\n    uint32_t prioritygroup;\n\n    \/\/ Enable clock for Port B, I2C, Alternate function IO\n    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;\n    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;\n    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;\n\n    \/\/ Configure the I2C pins as open-drain output\n    GPIOB->CRL |= (GPIO_CRL_CNF6 | GPIO_CRL_MODE6_0);\n    GPIOB->CRL |= (GPIO_CRL_CNF7 | GPIO_CRL_MODE7_0);\n\n    \/\/ Disable the peripheral enable bit\n    I2C1->CR1 &amp;= ~(I2C_CR1_PE);\n\n    \/\/ Reset I2C\n    I2C1->CR1 |= I2C_CR1_SWRST;\n    I2C1->CR1 &amp;= ~I2C_CR1_SWRST;\n\n    \/\/ Configure internal clock, raise time\n    I2C1->CR2 |= 0x08;\n    I2C1->TRISE = 0x9;\n\n    \/\/ Clock control register for 100KHz I2C frequency\n    \/\/ at system clock frequency of 8MHz\n    I2C1->CCR = 0x28;\n\n    \/\/ Configure the slave address; as per the datasheet\n    \/\/ bit 14 should be set.\n    I2C1->OAR1 = 0x4000 | (addr &lt;&lt; 1);\n\n    \/\/ Enable the peripheral enable bit and ACK\n    I2C1->CR1 |= I2C_CR1_PE;\n    I2C1->CR1 |= I2C_CR1_ACK;\n    I2C1->CR2 |= I2C_CR2_ITEVTEN;\n\n    \/\/ Enable NVIC\n    prioritygroup = NVIC_GetPriorityGrouping();\n    NVIC_SetPriority(I2C1_EV_IRQn, NVIC_EncodePriority(prioritygroup, 10, 0));\n    NVIC_EnableIRQ(I2C1_EV_IRQn);\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">I2C Slave Read<\/h4>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXeGNtU7_mo2fkcfsFmKMi_3keVgRtvpQETAhqi18SR-15DGz2mv4qWk6nzxMoL39Y4kxYZrAuHTjNn7W0GufePQJSOTIGg03wC9TE1N13pna99aflp1hi5Yw3DRTV9xaULvxwmulndkAqwudWB3zbwRfTPx?key=qNIjhNfGuH6o5NHvvLVpiw\" alt=\"\"\/><\/figure>\n\n\n\n<p>The sequence diagram for the I2C slave read is shown in the figure 272 of STM32 reference manual RM0008.<\/p>\n\n\n\n<ol>\n<li>S &#8211; Start condition<\/li>\n<\/ol>\n\n\n\n<p>We don\u2019t need to do anything here, because the master is responsible for starting the condition.<\/p>\n\n\n\n<ol start=\"2\">\n<li>Address &#8211; Slave address from master<\/li>\n<\/ol>\n\n\n\n<p>Unlike what we have seen in the master, the slave will be receiving the address from the master. So the device should be listening to any bus operation during the idle time. We already have enabled the interrupts for I2C events so if the address sent by master matches with the current slave the interrupt will be generated.<\/p>\n\n\n\n<ol start=\"3\">\n<li>A &#8211; ACK<\/li>\n<\/ol>\n\n\n\n<p>The ACK bit already enabled in the initialization, nothing to do here<\/p>\n\n\n\n<ol start=\"4\">\n<li>EV1<\/li>\n<\/ol>\n\n\n\n<p>Upon the detected address match with the slave address the ADDR bit in the I2C_SR1 will be set. This can be cleared by reading I2C_SR1 &amp; I2C_SR2. If the master is trying to read the data (slave write), the TRA bit in the I2C_SR2 register will be set and TRA will be clear if the slave is reading the data. In our case the TRA bit will be 0.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if((I2C1->SR1 &amp; I2C_SR1_ADDR) != 0) {\n\u00a0\u00a0\u00a0\u00a0\u00a0(void)I2C1->SR1;\n\u00a0\u00a0\u00a0\u00a0\u00a0(void)I2C1->SR2;\n\u00a0\u00a0\u00a0\u00a0 if(0 == (I2C1->SR2 &amp; I2C_SR2_TRA)) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Receive data\n\u00a0\u00a0\u00a0\u00a0\u00a0}\n}<\/code><\/pre>\n\n\n\n<p>\u00a0\u00a0\u00a0<\/p>\n\n\n\n<ol start=\"5\">\n<li>Data &#8211; The data sent from master<\/li>\n\n\n\n<li>A &#8211; ACK sent back to the master<\/li>\n\n\n\n<li>EV2<\/li>\n<\/ol>\n\n\n\n<p>Once the data received in the I2C_DR register, the RxNE flag in the I2C_SR1 will be set. This flag can be cleared by reading the I2C_DR register<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while(!(I2C1->SR1 &amp; I2C_SR1_RXNE));\ndata = I2C1->DR;<\/code><\/pre>\n\n\n\n<ol start=\"8\">\n<li>P &#8211; Stop condition generated by master<\/li>\n\n\n\n<li>EV4<\/li>\n<\/ol>\n\n\n\n<p>Once the stop condition is detected, the interrupt will be generated by setting the STOPF flag in the I2C_SR1 register. This flag can be cleared by reading the I2C_SR1 register and writing to I2C_CR1 register.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if (I2C1->SR1 &amp; I2C_SR1_STOPF) {\n\u00a0\u00a0\u00a0\u00a0(void)I2C1->SR1;\n\u00a0\u00a0\u00a0\u00a0I2C1->CR1 |= I2C_CR1_PE;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">I2C Slave Write<\/h4>\n\n\n\n<p>The sequence diagram 271 shown in STM32 reference manual RM0008 explains how the write works. This looks similar to what we have seen in the case of slave read except the points mentioned below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"880\" height=\"78\" src=\"https:\/\/blog.mshafeeq.com\/wp-content\/uploads\/2024\/10\/image-1.png\" alt=\"\" class=\"wp-image-506\" srcset=\"https:\/\/blog.mshafeeq.com\/wp-content\/uploads\/2024\/10\/image-1.png 880w, https:\/\/blog.mshafeeq.com\/wp-content\/uploads\/2024\/10\/image-1-300x27.png 300w, https:\/\/blog.mshafeeq.com\/wp-content\/uploads\/2024\/10\/image-1-768x68.png 768w\" sizes=\"(max-width: 880px) 100vw, 880px\" \/><\/figure>\n\n\n\n<p>EV1<\/p>\n\n\n\n<p>When the ADDR flag is set upon matching the address sent by the master, we have to check the TRA bit in the I2C_SR2 register. This flag will be set if the master sent the address for read operation (ie slave write).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if((I2C1->SR1 &amp; I2C_SR1_ADDR) != 0) {\n\u00a0\u00a0\u00a0\u00a0(void)I2C1->SR1;\n\u00a0\u00a0\u00a0\u00a0(void)I2C1->SR2;\n\u00a0\u00a0\u00a0\u00a0if(I2C1->SR2 &amp; I2C_SR2_TRA) {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ send data here\n\u00a0\u00a0\u00a0\u00a0}\n}<\/code><\/pre>\n\n\n\n<p>EV3-1<\/p>\n\n\n\n<p>Once the slave detects the address and ADDR flag has been cleared, the slave has to wait until the TxE flag becomes 1. This notifies that the transmit buffer and transmit shift register are empty and software can load the data into the I2C_DR.<\/p>\n\n\n\n<p>EV3<\/p>\n\n\n\n<p>Here also the TxE flag will be set. This notifies the I2C_DR is empty but the shift register is&nbsp; not empty. But we can write to I2C_DR in this stage, no need to wait until the shift register is free.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while(!(I2C1->SR1 &amp; I2C_SR1_TXE));\nI2C1->DR = byte;<\/code><\/pre>\n\n\n\n<p>EV3-2<\/p>\n\n\n\n<p>After the master has received all the data, it sends a NACK, which causes the AF bit in the SR register to be set. To clear this bit, we need to write a 0 to it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while(!(I2C1->SR1 &amp; I2C_SR1_AF));\nI2C1->SR1 &amp;= ~I2C_SR1_AF;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Combined Slave Read &amp; Write code<\/h4>\n\n\n\n<p>The complete read and write code will like<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void i2c_slave_init(uint8_t addr) {\n    uint32_t prioritygroup;\n\n    \/\/ Enable clock for Port B, I2C, Alternate function IO\n    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;\n    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;\n    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;\n\n    \/\/ Configure the I2C pins as open-drain output\n    GPIOB->CRL |= (GPIO_CRL_CNF6 | GPIO_CRL_MODE6_0);\n    GPIOB->CRL |= (GPIO_CRL_CNF7 | GPIO_CRL_MODE7_0);\n\n    \/\/ Disable the peripheral enable bit\n    I2C1->CR1 &amp;= ~(I2C_CR1_PE);\n\n    \/\/ Reset I2C\n    I2C1->CR1 |= I2C_CR1_SWRST;\n    I2C1->CR1 &amp;= ~I2C_CR1_SWRST;\n\n    \/\/ Configure internal clock, raise time\n    I2C1->CR2 |= 0x08;\n    I2C1->TRISE = 0x9;\n\n    \/\/ Clock control register for 100KHz I2C frequency\n    \/\/ at system clock frequency of 8MHz\n    I2C1->CCR = 0x28;\n\n    \/\/ Configure the slave address; as per the datasheet\n    \/\/ bit 14 should be set.\n    I2C1->OAR1 = 0x4000 | (addr &lt;&lt; 1);\n\n    \/\/ Enable the peripheral enable bit and ACK\n    I2C1->CR1 |= I2C_CR1_PE;\n    I2C1->CR1 |= I2C_CR1_ACK;\n    I2C1->CR2 |= I2C_CR2_ITEVTEN;\n\n    \/\/ Configure the NVIC\n    prioritygroup = NVIC_GetPriorityGrouping();\n    NVIC_SetPriority(I2C1_EV_IRQn, NVIC_EncodePriority(prioritygroup, 10, 0));\n    NVIC_EnableIRQ(I2C1_EV_IRQn);\n}\nvoid i2c_slave_listen() {\n    \/\/ Wait until any master sent our address\n    \/\/ through the I2C interface. Clear the flag\n    \/\/ if the address matched.\n    while(!(I2C1->SR1 &amp; I2C_SR1_ADDR));\n    (void)I2C1->SR1;\n    (void)I2C1->SR2;\n}\n\nuint8_t i2c_slave_recv_byte() {\n    \/\/ Receive 1 byte from the master.\n    while(!(I2C1->SR1 &amp; I2C_SR1_RXNE));\n    return I2C1->DR;\n}\n\nvoid i2c_slave_send_byte(uint8_t byte) {\n    \/\/ Send 1 byte to master\n    while(!(I2C1->SR1 &amp; I2C_SR1_TXE));\n    I2C1->DR = byte;\n}\n\nvoid i2c_slave_send_finish() {\n    \/\/ Wait until the NACK is received and clear the flag\n    while(!(I2C1->SR1 &amp; I2C_SR1_AF));\n    I2C1->SR1 &amp;= ~I2C_SR1_AF;\n}\n\nstatic volatile uint8_t off_time, on_time;\nvoid I2C1_EV_IRQHandler() {\n    if((I2C1->SR1 &amp; I2C_SR1_ADDR) != 0) {\n        (void)I2C1->SR1;\n        (void)I2C1->SR2;\n        if(I2C1->SR2 &amp; I2C_SR2_TRA) {\n            \/\/ Slave is in write mode\n            i2c_slave_send_byte('A');\n            i2c_slave_send_byte('B');\n            i2c_slave_send_finish();\n        } else {\n            \/\/ Slave is in read mode\n            on_time = i2c_slave_recv_byte();\n            off_time = i2c_slave_recv_byte();\n        }\n    }\n\n    if (I2C1->SR1 &amp; I2C_SR1_STOPF) {\n        (void)I2C1->SR1;\n        I2C1->CR1 |= I2C_CR1_PE;\n    }\n}<\/code><\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/mshafeeqkn\/STM32-Bare-Metal\/tree\/master\/I2C\/I2C_SLAVE\">STM32-Bare-Metal\/I2C\/I2C_SLAVE at master \u00b7 mshafeeqkn\/STM32-Bare-Metal \u00b7 GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous blog posts, we explored how to utilize the STM32 microcontroller as a master device, including performing read and write operations with a slave. Now, let\u2019s advance to the next level, as the microcontroller is also capable of functioning as a slave device. In this post, I will explain how to configure the &#8230; <a title=\"STM32F103C6T6A Bare Metal Programming &#8211; I2C Slave Read &#038; Write\" class=\"read-more\" href=\"https:\/\/blog.mshafeeq.com\/index.php\/2024\/07\/10\/stm32f103c6t6a-bare-metal-programming-i2c-slave-read-write\/\" aria-label=\"Read more about STM32F103C6T6A Bare Metal Programming &#8211; I2C Slave Read &#038; Write\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[10,43,46,45,47,15,24,27,12],"_links":{"self":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/499"}],"collection":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/comments?post=499"}],"version-history":[{"count":6,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/499\/revisions"}],"predecessor-version":[{"id":510,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/499\/revisions\/510"}],"wp:attachment":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/media?parent=499"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/categories?post=499"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/tags?post=499"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}