{"id":484,"date":"2024-04-08T07:05:37","date_gmt":"2024-04-08T01:35:37","guid":{"rendered":"https:\/\/blog.mshafeeq.com\/?p=484"},"modified":"2024-10-02T07:24:57","modified_gmt":"2024-10-02T01:54:57","slug":"stm32f103c6t6a-bare-metal-programming-i2c-part-1","status":"publish","type":"post","link":"https:\/\/blog.mshafeeq.com\/index.php\/2024\/04\/08\/stm32f103c6t6a-bare-metal-programming-i2c-part-1\/","title":{"rendered":"STM32F103C6T6A Bare Metal Programming &#8211; I2C Master Write"},"content":{"rendered":"\n<p>In my <a href=\"https:\/\/blog.mshafeeq.com\/index.php\/2024\/02\/15\/i2c-explained-bridging-devices-with-inter-integrated-circuit-technology\/\">previous post<\/a>, I explained how I2C functions and demonstrated a hands-on example using the ATTiny85 through the bit-banging method, as it lacks a built-in I2C peripheral. In this post, we will explore the implementation of I2C on a microcontroller that includes a built-in I2C peripheral, specifically the STM32F103C6T6A. We will focus on the I2C master mode in this discussion, with the slave mode covered in a future post. Detailed information about the STM32 I2C peripheral can be found in chapter 26 of the STM32 reference manual (RM0008). Our microcontroller features a single I2C peripheral, with pins PB6 designated for SCL and PB7 for SDA. These pins can also be remapped to PB8 and PB9 if needed.<\/p>\n\n\n\n<p>Let\u2019s split this post into two parts, the first part will explain how the master writes or sends the data to the slave (This blog post) and in the second part (Next blog post) how the master reads the data from the slave.<\/p>\n\n\n\n<p>Before write\/read operation, the I2C peripheral has to be initialized.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Initialize I2C peripheral<\/h4>\n\n\n\n<ol>\n<li><em>Enable the Clock for required peripherals:<\/em>&nbsp; As we usually do for STM32 microcontrollers, first we need to enable the clock for required peripherals. Since the I2C pins are multiplexed with PORTB pins, we have to enable the clock for GPIOB, AFIO and I2C. The GPIOB and AFIO are connected with APB2 and I2C is connected to the APB1 bus.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>RCC-&gt;APB2ENR |= RCC_APB2ENR_IOPBEN;\nRCC-&gt;APB2ENR |= RCC_APB2ENR_AFIOEN;\nRCC-&gt;APB1ENR |= RCC_APB1ENR_I2C1EN;<\/code><\/pre>\n\n\n\n<ol start=\"2\">\n<li><em>Configure the SCL and SDA pins:<\/em> With reference to the Table 27-I2C configure SCL(PB6) and SDA(PB7) as alternate function open-drain.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>GPIOB-&gt;CRL |= (GPIO_CRL_CNF6 | GPIO_CRL_MODE6_0);\nGPIOB-&gt;CRL |= (GPIO_CRL_CNF7 | GPIO_CRL_MODE7_0);<\/code><\/pre>\n\n\n\n<ol start=\"3\">\n<li><em>Reset the I2C peripheral:<\/em> This is recommended to avoid any previous error conditions<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CR1 |= I2C_CR1_SWRST;\nI2C1-&gt;CR1 &amp;= ~I2C_CR1_SWRST;\nwhile((I2C1-&gt;SR2 &amp; I2C_SR2_BUSY));<\/code><\/pre>\n\n\n\n<ol start=\"4\">\n<li><em>Configure the parameter to generate the I2C clock:<\/em> We need to tell I2C what clock we are using for the STM32 microcontroller. This is done through the last 6-bits of the I2C_CR2 register. This is done to reduce the hardware complexity and hence the cost. Since we are using the external 8MHz crystal oscillator (HSE), we are configuring this value in the I2C_CR2 register.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CR2 |= 0x08;<\/code><\/pre>\n\n\n\n<p>STM32-I2C peripheral supports two modes of operations, first one is standard mode(Sm) which supports up to 2MHz SCL frequency. The second one is Fast mode (Fm) which supports up to 4MHz SCL frequency. But as per the standard Sm uses 100KHz and Fm uses 400KHz clock frequency. Here we are using Sm with 100KHz SCL frequency. By default the STM32-I2C will be in Sm and we can change it to Fm using the I2C_CCR register.&nbsp;<\/p>\n\n\n\n<p>I2C specification says that the maximum raising time of SCL clock should not be more than 1000ns. We have to configure this value in the TRISE field of the I2C_TRISE enable register.<\/p>\n\n\n\n<p>The equation to find TRISE value is&nbsp;<\/p>\n\n\n\n<p>TRISE = Maximum Raise time1\/System Clock + 1 = 1000 ns125 ns + 1 = 9<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;TRISE |= 0x09;<\/code><\/pre>\n\n\n\n<p>Now we have to configure the Clock Control Register which is used to generate the SCL. The value of CCR register can be find out as follows<\/p>\n\n\n\n<p>CCR = System Clock Frequency\/2 x Target Frequency = 8000000\/2 x 100000 = 40 = 0x28<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CCR |= 0x28;<\/code><\/pre>\n\n\n\n<ol start=\"5\">\n<li><em>Enable the I2C:<\/em>&nbsp;<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CR1 |= I2C_CR1_PE;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Master Write Operation<\/h4>\n\n\n\n<p>Figure 273 in the RM0008 document explains how the master transmitter works. Here is the snapshot.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXc_a7km1nkspbX-WxT9UuMwDg6moGf_JfvVgLIIRjxQyRMfh5otUpqVcFrL35kZfrFjH8gGQwL5kaQFVvldaUqdvJYmwwRXKFRdbXXEf_Wg67-jQr5I_yJccE7GNxws3GSAJerPVIvNS7y52GKwbhdiBOWh?key=qNIjhNfGuH6o5NHvvLVpiw\" alt=\"\"\/><\/figure>\n\n\n\n<ol>\n<li>S &#8211; Start Condition<\/li>\n<\/ol>\n\n\n\n<p>The start condition can be generated by setting the START bit in the I2C_CR1 register.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CR1 |= I2C_CR1_START;&nbsp;<\/code><\/pre>\n\n\n\n<ol start=\"2\">\n<li>EV5 &#8211; Start Bit Set&nbsp;<\/li>\n<\/ol>\n\n\n\n<p>Once the start condition is generated the SB bit of I2C_SR1 will be set. This flag can be cleared by reading the I2C_SR1 register and followed by writing an address in the I2C_DR.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while((I2C1-&gt;SR1 &amp; I2C_SR1_SB) == 0);\n(void)I2C1-&gt;SR1;<\/code><\/pre>\n\n\n\n<ol start=\"3\">\n<li>Address &#8211; Write Address to I2C_DR along with Write bit (is_read = 0)<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;DR = (addr &lt;&lt; 1) | is_read;<\/code><\/pre>\n\n\n\n<ol start=\"4\">\n<li>A &#8211; Once the address is matched with any slave address the slave will pull the SDA line down as ACK.<\/li>\n\n\n\n<li>EV6 &#8211; Once the slave sends the ACK, the ADDR bit of I2C_SR1 will be set and this can be cleared by reading the I2C_SR1 and I2C_SR2.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>while(!(I2C1-&gt;SR1 &amp; I2C_SR1_ADDR));\n(void)I2C1-&gt;SR1;\n(void)I2C1-&gt;SR2;<\/code><\/pre>\n\n\n\n<ol start=\"6\">\n<li>EV8_1 &#8211; Shift register and Data register are empty<\/li>\n<\/ol>\n\n\n\n<p>Wait until the TXE bit of I2C_SR1 is set. Which means the shift register and data register of I2C are empty. So write the first byte of data to I2C_DR and wait until the byte transfer is finished. This can be notified by BTF of I2C_SR1.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while(!(I2C1-&gt;SR1 &amp; I2C_SR1_TXE));\nI2C1-&gt;DR = byte;\nwhile(!(I2C1-&gt;SR1 &amp; I2C_SR1_BTF));<\/code><\/pre>\n\n\n\n<ol start=\"7\">\n<li>P &#8211; Stop condition<\/li>\n<\/ol>\n\n\n\n<p>Generate the stop condition by setting the STOP bit of I2C_CR1 register.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>I2C1-&gt;CR1 |= I2C_CR1_STOP;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Hands-on<\/h4>\n\n\n\n<p>Now let\u2019s implement the code to write some data into AT24C04 EEPROM. Here is the code to write a paragraph to the EEPROM chip.<\/p>\n\n\n\n<p>AT24C04 write procedure, first we need to send the address to which the data to be written. On sending each byte the lower 4 bits of address will incremented automatically but won\u2019t increment the higher 4 bits. This will lead to overwriting the data if we write more than 16 bytes in a single shot. Also once the stop condition is sent to the EEPROM, there should be a minimum 5ms. These are taken care of in the following program.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"main.h\"\n\nstatic void delay(uint32_t tick) {\n    for (volatile uint32_t i = 0; i &lt; tick; ++i) {\n        __NOP();  \/\/ No operation (compiler barrier)\n    }\n}\n\nvoid i2c_init() {\n    RCC-&gt;APB2ENR |= RCC_APB2ENR_IOPBEN;\n    RCC-&gt;APB2ENR |= RCC_APB2ENR_AFIOEN;\n    RCC-&gt;APB1ENR |= RCC_APB1ENR_I2C1EN;\n\n    GPIOB-&gt;CRL |= (GPIO_CRL_CNF6 | GPIO_CRL_MODE6_0);\n    GPIOB-&gt;CRL |= (GPIO_CRL_CNF7 | GPIO_CRL_MODE7_0);\n\n    I2C1-&gt;CR1 |= I2C_CR1_SWRST;\n    I2C1-&gt;CR1 &amp;= ~I2C_CR1_SWRST;\n    while((I2C1-&gt;SR2 &amp; I2C_SR2_BUSY));\n\n    I2C1-&gt;CR2 |= 0x08;\n    I2C1-&gt;CCR |= 0x28;\n    I2C1-&gt;TRISE |= 0x09;\n\n    I2C1-&gt;CR1 |= I2C_CR1_PE;\n}\n\nvoid i2c_start() {\n    I2C1-&gt;CR1 |= I2C_CR1_START;\n    while((I2C1-&gt;SR1 &amp; I2C_SR1_SB) == 0);\n    (void)I2C1-&gt;SR1;\n}\n\nvoid i2c_addr(uint8_t addr, uint8_t is_read) {\n    I2C1-&gt;DR = (addr &lt;&lt; 1) | is_read;\n    while(!(I2C1-&gt;SR1 &amp; I2C_SR1_ADDR));\n    (void)I2C1-&gt;SR1;\n    (void)I2C1-&gt;SR2;\n}\n\nvoid i2c_send_byte(uint8_t byte) {\n    while(!(I2C1-&gt;SR1 &amp; I2C_SR1_TXE));\n    I2C1-&gt;DR = byte;\n    while(!(I2C1-&gt;SR1 &amp; I2C_SR1_BTF));\n}\n\nvoid i2c_stop() {\n    I2C1-&gt;CR1 |= I2C_CR1_STOP;\n}\n\nvoid write_eeprom_data(uint8_t chip, uint8_t addr, uint8_t* data, uint16_t len) {\n    uint16_t i = 0;\n    uint16_t end_addr = addr | 0x0F;\n    uint16_t start = 0;\n    uint16_t end = end_addr - addr + 1;\n\n    while(len) {\n        i2c_start();\n        i2c_addr(chip, 0);\n        i2c_send_byte(addr);\n\n        for(i = start; i &lt; end; i++) {\n            i2c_send_byte(data&#91;i]);\n            len--;\n        }\n\n        i2c_stop();\n        addr += (end - start);\n        start += (end - start);\n        if(len &gt; 16) {\n            end += 16;\n        } else {\n            end += len;\n        }\n\n        if(addr == 0 &amp;&amp; chip == 0x50) {\n\t    \/\/ If first 512 bytes is completed, go to the next page\n            \/\/ whose I2C address is current address + 1\n            chip++;\n        }\n\n        \/\/ 5ms delay\n        delay(10000);\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Here is the link to the complete source code<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/mshafeeqkn\/STM32-Bare-Metal\/tree\/master\/I2C\/I2C_MASTER\/I2C_MASTER_WRITE\">STM32-Bare-Metal\/I2C\/I2C_MASTER\/I2C_MASTER_WRITE at master \u00b7 mshafeeqkn\/STM32-Bare-Metal \u00b7 GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous post, I explained how I2C functions and demonstrated a hands-on example using the ATTiny85 through the bit-banging method, as it lacks a built-in I2C peripheral. In this post, we will explore the implementation of I2C on a microcontroller that includes a built-in I2C peripheral, specifically the STM32F103C6T6A. We will focus on the &#8230; <a title=\"STM32F103C6T6A Bare Metal Programming &#8211; I2C Master Write\" class=\"read-more\" href=\"https:\/\/blog.mshafeeq.com\/index.php\/2024\/04\/08\/stm32f103c6t6a-bare-metal-programming-i2c-part-1\/\" aria-label=\"Read more about STM32F103C6T6A Bare Metal Programming &#8211; I2C Master 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,45,44,15,24,27,12],"_links":{"self":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/484"}],"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=484"}],"version-history":[{"count":11,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/484\/revisions"}],"predecessor-version":[{"id":503,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/posts\/484\/revisions\/503"}],"wp:attachment":[{"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/media?parent=484"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/categories?post=484"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mshafeeq.com\/index.php\/wp-json\/wp\/v2\/tags?post=484"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}