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.
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.
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) ; } |
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 :)
ReplyDeletehey yes, by all means send it my way ill check it out and share it.... eddie@edwinfairchild.com
Delete