STM32L0/F0/F3 I2C : Part 2 Basic RX TX


Ok now for the fun part, CODING!! Below you can see every line of code for my main.c file. If you are using an STM32L0 and Keil without HAL or SPL this should compile just fine, otherwise sue me.



lines 1-3 are self explanatory.
#include "Stm32l0xx.h"
#include "stdint.h"
#include "string.h"



6-9 are function prototypes for some functions I used, I would include them just for now because I did the timing calculation based on 32MHz and I also threw in  a delay in between read and write operations because the EEPROM needs some time to do that actual writing.
void setClockTo32Mhz(void);
uint32_t msTICKS;
void delayMs(uint32_t ms);
void SysTick_Handler(void);


22-25 I make some variables to store data, and the data which i will be transmitting as well as a counter variable to keep track of bytes sent. Note that on line 23 I add 1 to the length of string and hence the num_bytes, this is because on top of sending this string over I also have to send one byte consisting of the address where ai want to start storing the string , so the total number of bytes I have to send is all the string characters pluss the intial adress byte, this is why I added 1.
 //some sample data buffers and counter variable
 char TX_data[]  ="Hello ST";
 uint8_t num_bytes = strlen(TX_data) + 1; //get the length of the above data
 char RX_data[10]  ={ 0,0,0,0,0,0,0,0,0,0}; // where we will store the received bytes
 uint8_t count = 0; // to keep track of bytes sent


28-30 just like any other thing we have to enable the clocks for the i2c1 peripheral and the clock for the pins it will use.
 // i2c  & GPIO clock enable 
 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // enable i2c1
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN; //enable GPIOA


32-36 if you remember from the Part one of this post, we must set the pins to Alternate Function with open drain
//gpio configure to alternate function open drian
 GPIOA->MODER |= GPIO_MODER_MODE9_1 | GPIO_MODER_MODE10_1; //alternate function mode
 GPIOA->MODER &= ~(GPIO_MODER_MODE9_0 | GPIO_MODER_MODE10_0);//alternate function mode
 GPIOA->OTYPER |= (1<<10) | (1<<9); // output open drain
 GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEED10 | GPIO_OSPEEDER_OSPEED9; // high speed


38-40 can be used to enable the internal pull ups, since i2c requires pull up on the clock and data lines, I dont need to do that because the EEPROM chip has external pull ups on the circuit board.
 //enabling upll-ups-- but i dont need them so i have commented these lines
 //GPIOA->PUPDR  |= GPIO_PUPDR_PUPD10_0 | GPIO_PUPDR_PUPD9_0;
 //GPIOA->PUPDR  &= ~(GPIO_PUPDR_PUPD10_1 | GPIO_PUPDR_PUPD9_1);


43 We go into the Alternate Function register for GPIOA , this register is an array of 2, so it has AFR[0] and AFR[1]  It allows you to choose what alternate functions the pins will have. AFR[0] corresponds to the lower pins 0 - 7 and AFR[1] pins 8 - 15. So i set AFR[1] 1 and 2 which correspond to pins 9 and 10. I set those to 1 which means alternate function 1 which from in last post I told you it was i2c mode.
 //chosing the alternate function 1
 GPIOA->AFR[1] |= (1<<GPIO_AFRH_AFRH1_Pos) | (1<<GPIO_AFRH_AFRH2_Pos); // Alternate function 1


46 I set the timing register, like I mentioned  this is not so straight forward and if you find a good way to calculate it please enlighten me.
 //timing register about 400Khz 
 I2C1->TIMINGR =  0x0050101B;  


48-54 Here I make the necessary settings to the I2C1 registers that correspond to the current transmission I want to send which is as follow: I want to send a write operation to slave at address 0xA0, this transmission will have a certain number of bytes (num_bytes), I will be  using 7 bit addressing mode because my chip does not support 10 bit addressing. I do not want to enable the auto end feature, so instead of sending a STOP condition when the number of bytes is sent, the peripheral will let me know its done by setting the TC flag. That way I can handle what to do next. Ultimately I enable the peripheral.
 I2C1->CR2 |= 0xA0; //set slave address 
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= (num_bytes)<<I2C_CR2_NBYTES_Pos; // set num of bytes to send
 I2C1->CR2 &= ~(I2C_CR2_AUTOEND); //disable auto end, instead TC flag is set when NBYTES data are transferred
 I2C1->CR2 &= ~(I2C_CR2_ADD10); //7 bit addressing mode.. this is default just here for referance
 I2C1->CR2 &= ~(I2C_CR2_RD_WRN); //write operation.. this is default just here for referance
 I2C1->CR1 |= I2C_CR1_PE; //enable peripheral


64-66 I send the START condition to begin communication, I wait for the TXE to be ready ,then I write the first byte in the TXDR register, this first byte I am writing is the memory address where I want the EEPROM to begin storing the data. After every byte I send it will increment its "internal pointer" to the next available address, so I only have to give it the first initial address and it does the rest. (do not confuse this memory address within the EEPROM with the actual EEPROM slave address)
 //first send the memory address where we want to start writing - dont confuse this with slave address
 I2C1->CR2 |= I2C_CR2_START; // send start condition
 while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
 I2C1->TXDR = 0x00; // memory adress to start storing
 


70 This while loop will go through all the bytes we have to send, note the minus 1 because we have to exclude the first byte for the address because we already sent that in the last step.
while(count < num_bytes -1) // N bytes transfered
 {
  //send bytes in TX_data
  while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
  
  I2C1->TXDR =TX_data[count++]; // send next byte   
 
 }


79-83 Since we disabled the auto end feature the I2C will generate the TC flag telling us the number of bytes we specified to send is complete (in the next operation we will take advantage of this feature) So we wait for this flag to be set, then we send a STOP condition and I added a delay because the chip needs time to write this data and the microcontroller is so fast it will seem almost instant once we start reading with no delay in between.
while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // N bytes transfered
 
 I2C1->CR2 |= I2C_CR2_STOP;
 
 delayMs(2);


87 Dont forget to reset count
 //-------- READING ----------------------------
 
 count = 0;


89-99 Is identicle to the write operation, this is because to read from the EEPROM you must first send a write command with the address you want to start reading from, once you do that you have to send it another START condition and then begin reading. The first write command is so that it can move its "internal pointer" to the write location. Afterwards you can just keep reading data from it, preferably in 8 byte chunks according to the datasheet.
//1 siugle write operation to the address we want to start reading from
 I2C1->CR1 &= ~I2C_CR1_PE; //disable peripheral 
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= 1<<I2C_CR2_NBYTES_Pos; //set nbytes to 1 because only need to send initial address to read from 
 I2C1->CR2 &= ~(I2C_CR2_RD_WRN); //write operation 
 I2C1->CR1 |= I2C_CR1_PE; //enable peripheral
 
 I2C1->CR2 |= I2C_CR2_START; // send start condition 
 while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
 I2C1->TXDR = 0x00; // send the address we will start reading from
 while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // wait for transfer complete


103-108 I reconfigure the i2c this time I enable auto end,cwhich will send a STOP condition once the number of bytes programmed in the NBYTES register is complete.
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= (num_bytes -1)<<I2C_CR2_NBYTES_Pos; //set nbytes
 I2C1->CR2 |= (I2C_CR2_RD_WRN); //read operation
 
 I2C1->CR2 |= I2C_CR2_START; // send start condition again
 I2C1->CR2 |= I2C_CR2_AUTOEND; //enable auto end

110 This will cycles the number of times needed to read the bytes from the RXDR register when ever new data is ready
 while(count < num_bytes) // keep track of how many bytes we have sent
 {
  while(!((I2C1->ISR & I2C_ISR_RXNE) == (I2C_ISR_RXNE)))
  {
   
  } // wait for tx buffer to be empty/ready  
  
  RX_data[count++]= I2C1->RXDR;
 }


FINITO
 //these 3 lines are not needed because we have enabled auto end feature
 //while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // wait for tx buffer to be empty/ready 
   
 //I2C1->CR2 |= I2C_CR2_STOP; 

 //I2C1->CR2 |= I2C_CR2_STOP; 

I hope I explained everything well enough, if not shoot me a message. Follow me on Instagram for a quicker reply, dont just message me a random question, tell me it is about something posted here on my blog. https://www.instagram.com/edwinfairchild/  

In the next part we will take the structure of this code and make C and H files to make a simple I2C driver along with necessary functions like I2C_send and I2C_read and anything else we might need. 

Here is all the code in its full glory, or full gore...who knows ..
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include "Stm32l0xx.h"
#include "stdint.h"
#include "string.h"


void setClockTo32Mhz(void);
uint32_t msTICKS;
void delayMs(uint32_t ms);
void SysTick_Handler(void);


int main(void)
{

 setClockTo32Mhz();
 SystemCoreClockUpdate();
 SysTick_Config(SystemCoreClock / 1000);
 

 
 //some sample data buffers and counter variable
 char TX_data[]  ="Hello ST";
 uint8_t num_bytes = strlen(TX_data) + 1; //get the length of the above data
 char RX_data[10]  ={ 0,0,0,0,0,0,0,0,0,0}; // where we will store the received bytes
 uint8_t count = 0; // to keep track of bytes sent
 

 // i2c  & GPIO clock enable 
 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // enable i2c1
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN; //enable GPIOA
 
 //gpio configure to alternate function open drian
 GPIOA->MODER |= GPIO_MODER_MODE9_1 | GPIO_MODER_MODE10_1; //alternate function mode
 GPIOA->MODER &= ~(GPIO_MODER_MODE9_0 | GPIO_MODER_MODE10_0);//alternate function mode
 GPIOA->OTYPER |= (1<<10) | (1<<9); // output open drain
 GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEED10 | GPIO_OSPEEDER_OSPEED9; // high speed
 
 //enabling upll-ups-- but i dont need them so i have commented these lines
 //GPIOA->PUPDR  |= GPIO_PUPDR_PUPD10_0 | GPIO_PUPDR_PUPD9_0;
 //GPIOA->PUPDR  &= ~(GPIO_PUPDR_PUPD10_1 | GPIO_PUPDR_PUPD9_1);
 
 //chosing the alternate function 1
 GPIOA->AFR[1] |= (1<<GPIO_AFRH_AFRH1_Pos) | (1<<GPIO_AFRH_AFRH2_Pos); // Alternate function 1

 //timing register about 400Khz 
 I2C1->TIMINGR =  0x0050101B;  
 
 I2C1->CR2 |= 0xA0; //set slave address 
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= (num_bytes)<<I2C_CR2_NBYTES_Pos; // set num of bytes to send
 I2C1->CR2 &= ~(I2C_CR2_AUTOEND); //disable auto end, instead TC flag is set when NBYTES data are transferred
 I2C1->CR2 &= ~(I2C_CR2_ADD10); //7 bit addressing mode.. this is default just here for referance
 I2C1->CR2 &= ~(I2C_CR2_RD_WRN); //write operation.. this is default just here for referance
 I2C1->CR1 |= I2C_CR1_PE; //enable peripheral
 
 
 //first byte to sent to eeprom will be the memory address
 //where we want to store our data, in this case ill send 
 //the data to address 0x00, with every byte I send the eeprom
 //increment its pointer and store the data I send in the next
 //sequential memory location.
 
 //first send the memory address where we want to start writing - dont confuse this with slave address
 I2C1->CR2 |= I2C_CR2_START; // send start condition
 while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
 I2C1->TXDR = 0x00; // memory adress to start storing
 
 
 
 while(count < num_bytes -1) // N bytes transfered
 {
  //send bytes in TX_data
  while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
  
  I2C1->TXDR =TX_data[count++]; // send next byte   
 
 }

 while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // N bytes transfered
 
 I2C1->CR2 |= I2C_CR2_STOP;
 
 delayMs(2);
 
 //-------- READING ----------------------------
 
 count = 0;
 
 //1 siugle write operation to the address we want to start reading from
 I2C1->CR1 &= ~I2C_CR1_PE; //disable peripheral 
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= 1<<I2C_CR2_NBYTES_Pos; //set nbytes to 1 because only need to send initial address to read from 
 I2C1->CR2 &= ~(I2C_CR2_RD_WRN); //write operation 
 I2C1->CR1 |= I2C_CR1_PE; //enable peripheral
 
 I2C1->CR2 |= I2C_CR2_START; // send start condition 
 while(!((I2C1->ISR & I2C_ISR_TXE) == (I2C_ISR_TXE))); // wait for tx buffer to be empty/ready 
 I2C1->TXDR = 0x00; // send the address we will start reading from
 while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // wait for transfer complete
 
 
 
 I2C1->CR2 &= ~(I2C_CR2_NBYTES); //clear nbytes
 I2C1->CR2 |= (num_bytes -1)<<I2C_CR2_NBYTES_Pos; //set nbytes
 I2C1->CR2 |= (I2C_CR2_RD_WRN); //read operation
 
 I2C1->CR2 |= I2C_CR2_START; // send start condition again
 I2C1->CR2 |= I2C_CR2_AUTOEND; //enable auto end
 
 while(count < num_bytes) // keep track of how many bytes we have sent
 {
  while(!((I2C1->ISR & I2C_ISR_RXNE) == (I2C_ISR_RXNE)))
  {
   
  } // wait for tx buffer to be empty/ready  
  
  RX_data[count++]= I2C1->RXDR;
 }
 
 
 //these 3 lines are not needed because we have enabled auto end feature
 //while(!((I2C1->ISR & I2C_ISR_TC) == (I2C_ISR_TC))); // wait for tx buffer to be empty/ready 
   
 //I2C1->CR2 |= I2C_CR2_STOP; /* Go */

 //I2C1->CR2 |= I2C_CR2_STOP; /* Go */
 

 while(1)
 {
 
 
 }


}



void delayMs(uint32_t ms)
{
 msTICKS = 0;
 while (msTICKS < ms)
  ;
}
void SysTick_Handler(void)
{
 msTICKS++;
}
void setClockTo32Mhz(void)
{

 //adjust flash latency
 FLASH->ACR |= FLASH_ACR_LATENCY;
 while ((FLASH->ACR & FLASH_ACR_LATENCY) == 0)
  ; //wait for latency set flag

 //set voltage scaling to range 1
 PWR->CR |= PWR_CR_VOS_0;
 PWR->CR &= ~(PWR_CR_VOS_1);
 while (((PWR->CSR) & (PWR_CSR_VOSF)) == 1)
  ; //wait for voltage to settle

 //turn on HSE external, HSE bypass and security
 RCC->CR |= RCC_CR_CSSHSEON | RCC_CR_HSEBYP | RCC_CR_HSEON;
 while (((RCC->CR) & RCC_CR_HSERDY) == 0)
  ; //wait for the HSE to be ready

 //reset and configure pll mull and div settings, and PLL source
 RCC->CFGR = ((RCC->CFGR & ~(RCC_CFGR_PLLDIV | RCC_CFGR_PLLMUL))
   | RCC_CFGR_PLLDIV2 | RCC_CFGR_PLLMUL8 | RCC_CFGR_PLLSRC_HSE);
 while ((RCC->CR & RCC_CR_PLLRDY) == 1)
  ;

 //turn on PLL , wait for ready
 RCC->CR |= RCC_CR_PLLON;
 while (((RCC->CR) & RCC_CR_PLLRDY) == 0)
  ; // wait for pll to ready

 //set PLL as system clock
 RCC->CFGR |= RCC_CFGR_SW_PLL;
 while (((RCC->CFGR) & (RCC_CFGR_SWS_PLL)) != RCC_CFGR_SWS_PLL)
  ;
}


<< PREVIOUS | NEXT >>

Comments

  1. Hey Eddie, I see you still haven't updated on the TIMINGR register. I read the reference manual and it's mentioned how to calculate it. I've made an doc with example and created a very rudimentary C subroutine that calculates the value within +-1 digit to what the cubemx software provides. Is there any way for me to send you those files? Don't worry it'll just be pdf and .c file :)

    ReplyDelete
    Replies
    1. hey yes, by all means send it my way ill check it out and share it.... eddie@edwinfairchild.com

      Delete
Share your comments with me

Archive

Contact Form

Send