środa, 10 lutego 2016

[13] STM32 M3 - Nucleo - F103RB - EEPROM, I2C

Niedawno przedstawiłem opis programowanej EEPROM poprzez układ Arduino. Tym razem przyszła kolej na STM32. Może to być w niektórych projektach wyjątkowo przydatny element do zastosowania, ponieważ STM32F103 jak i również STM32F4 nie posiada wbudowanej pamięci EEPROM. Do komunikacji z pamiecią zostanie wykorzystany interfejs I2C. 

I2C


Inter-Integrated Circuit został opracowany przez firmę Philips. Jest wykorzystywany do transmisji szeregowej, synchronicznej w trybie naprzemiennego odbierania i nadawania danych (half-duplex). 

Dostępne są trzy wersje tego interfejsu różniące się między sobą szybkością transmisji:
  • Standardowy (Standard) - 100kb/s
  • Szybki (Fast Mode) - 400kb/s
  • Wysoka prędkość (High Speed Mode) - 3,4 Mb/s

Do komunikacji wykorzystywane są dwa przewody SDA i SCL. Ta druga zapewnia przesłanie sygnału zegarowego, pierwsza natomiast pozwala na transmisję danych. Dane przesyłane są w obu kierunkach poprzez tą linię. 

Obie linie są podłączone do wejść bądź wyjść z otwartym kolektorem bądź z otwartym drenem. Układy mogą na magistrali ustawiać tylko stan niski. Wysoki natomiast otrzymuje się poprzez podciągniecie linii danych i zegarowej do zasilania poprzez rezystory (np. 4,7 kOhm). 

Można ustawić cztery tryby pracy tych układów:

  • master transmit - wysyła dane do podrzędnego poprzez nadrzędny układ
  • master receive - nadrzędny odbiera dane od podrzędnego
  • slave trasmit - podrzędny wysyła
  • slave receive - podrzędny odbiera

Pamięć EEPROM


Wykorzystywana kość pamięci 24AA01 została wyprodukowana przez firmę Microchip. Posiada ona 128B pojemności. Częstotliwość pracy wynosi 100kHz lub 400kHz. Ilość cykli zapisu i odczytu przekracza 1 mln. Sprzedawane jest ona w obudowach ośmionóżkowych PDIP, SOIC, TSSOP, DFN, TDFN, MSOP oraz pięcionóżkowych SOT-23, SC-70.

Rys. 1. Typy obudów

Opis wyprowadzeń oraz sposób podłączenia:

  • 1 - A0 Adresowanie - NC
  • 2 - A1 Adresowanie - NC
  • 3 - A2 Adresowanie - NC
  • 4 - VSS - GND - GND
  • 5 - SDA - linia danych - I2C1 lub I2C2
  • 6 - SCL - linia zegarowa - I2C1 lub I2C2
  • 7 - WP - Write Protect - ochrona przed zapisem, trzeba podłączyć do masy aby mieć możliwość zapisu danych. - GND
  • 8 - VCC - zasilanie od 1,7V do 5,5V - 3,3V

Linie SDA i SCL wymagają rezystora podciągającego dla 100 kHz jest to 10 kOhm, dla 400 kHz jest to wartość 2 kOhm. Ja wykorzystałem rezystory 4,7 kOhm podciągnięte pod zasilanie 3.3V.

Programowanie


Działanie oraz komunikacja wygląda w następujący sposób:
Wystawiony jest sygnał rozpoczęcia transmisji tzw. start. Inicjalizowany jest on poprzez ustawienie stanu niskiego na linii SDA w trakcie występowania stanu wysokiego na SCL. Gdy ten sygnał zostanie wykryty układy podrzędne przełączają się w stan odbierania danych. W pierwszej kolejności wysyłany jest adres układu docelowego, z którym ma nastąpić komunikacja. Następnie 8 bitów danych. Układ odbierający wystawia bit potwierdzenia odbioru danych (ACK). Po nim zostaje wysłany znak stop. Po całej operacji przesłania zostaje zwolniona linia SDA gdy na linii SCL panuje stan wysoki. Takie zmiany stanów pomiędzy liniami SDA i SCL pojawia się tylko w momęcie wysyłania sygnałów start i stop. 

Po zakończeniu jednej transmisji można odrazu rozpocząć kolejną. Przy takich operacja istnieje możliwość pominięcia sygnału zakończenia transmisji stop. 

Wykorzystywany mikrokontroler posiada dwa układy interfejsy I2C, które znajdują się na następujących wyprowadzeniach:

  • I2C 1 - SDA PB7 ; SCL PB6
  • I2C 2 - SDA ; SCL

Mogą one obsługiwać transmisję zarówno 7 jak i 10 bitową. Transmisję można rozróżnić na standardową bądź szybką. Dodatkowo możliwe jest wykrywanie błędów transmisji, obliczania sumy kontrolnej CRC, być źródłem przerwań bądź żądań danych dla DMA.

Biblioteka API udostępnia strukturę, w której istnieje możliwość wypełnienia takich pól jak:

  • I2C_Mode - określenie tryby pracy układu, czy I2C czy SMBus, to drugie jest standardem pochodzacym od I2C
  • I2C_DutyCycle - pozwala na określenie stosunku trwania sygnału wysokiego do niskiego na linii SCK. Jest to ważny element dla taktowania powyżej 100kHz.
  • I2C_OwnAddress1 - określa adres układu po stronie mikrokontrolera, jeśli jest on układem podrzędnym
  • I2C_AcknowledgedAddress - wybranie trybu adresowania
  • I2C_ClockSpeed - pozwala na określenie występującego sygnału zegarowego na linii SCK

I2C taktowane jest z szyny APB1. Piny od wyprowadzeń w konfiguracji otwarty dren. 

Poniżej przedstawiam prosty program, którego zadaniem jest zapis a następnie odczyt danych. Całość została opisana z odpowiednim komentarzem.

  1. #include <stdio.h>
  2. #include "stm32f10x.h"
  3.  
  4. //Wysłanie danych przez układ USART na komputer
  5. void SendCharc(char c);
  6.  
  7. //Potrzebne dla przesyłania danych z wykorzystaniem funckji printf
  8. int __io_putchar(int c);
  9.  
  10. //Zapis do pamięci
  11. void EEPROM_WRITE(uint32_t addr, const void* data, int size);
  12.  
  13. //Odczyt z pamięci
  14. void EEPROM_READ(uint32_t addr, void* data, int size);
  15.  
  16. //Inicjalizacja GPIO
  17. void GPIOInit(void);
  18.  
  19. //Inicjalizacja USARTU
  20. void USARTInit(void);
  21.  
  22. //Inicjalizacja I2C
  23. void I2CInit(void);
  24.  
  25. int main(void)
  26. {
  27.   uint32_t uruchomienia = 0;
  28.   uint32_t pomocnicza = 3;
  29.   char znak = 'a';
  30.  
  31.   GPIOInit();
  32.   USARTInit();
  33.   I2CInit();
  34.  
  35.   EEPROM_READ(0x20, &uruchomienia, sizeof(uruchomienia));
  36.   EEPROM_READ(0x40, &pomocnicza, sizeof(pomocnicza));
  37.  
  38.   uruchomienia++;
  39.   EEPROM_WRITE(0x20, &uruchomienia, sizeof(uruchomienia));
  40.   pomocnicza = pomocnicza + 3;
  41.  
  42.   printf("\n\rLiczba uruchomień zmiennej %ld \n\r", uruchomienia);
  43.   printf("Zmienna pomocnicza %ld \n\r", pomocnicza);
  44.  
  45.   EEPROM_WRITE(0x40, &pomocnicza, sizeof(pomocnicza));
  46.  
  47.   for(;;);
  48. }
  49.  
  50. //Inicjalizacja GPIO
  51. void GPIOInit(void)
  52. {
  53.  GPIO_InitTypeDef GPIOInit;
  54.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
  55.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  56.  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  57.  
  58.  
  59.  //Definicja pinów dla USARTU
  60.  GPIO_StructInit(&GPIOInit);
  61.  GPIOInit.GPIO_Pin = GPIO_Pin_2;
  62.  //GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  63.  GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
  64.  GPIO_Init(GPIOA, &GPIOInit);
  65.  
  66.  GPIOInit.GPIO_Pin = GPIO_Pin_3;
  67.  //GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  68.  GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  69.  GPIO_Init(GPIOA, &GPIOInit);
  70.  
  71.  //Definicja pinów dla EEPROM GPIOB6 - SCL, GPIOB7 - SDA
  72.  GPIOInit.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
  73.  GPIOInit.GPIO_Mode = GPIO_Mode_AF_OD;
  74.  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  75.  GPIO_Init(GPIOB, &GPIOInit);
  76. }
  77.  
  78. //Inicjalizacja USARTU
  79. void USARTInit(void)
  80. {
  81.   USART_InitTypeDef USARTInit;
  82.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
  83.  
  84.   //Ustawienie prędkości transmisji 9600bps
  85.   USARTInit.USART_BaudRate = 115200;
  86.  
  87.   //Ustawienie dlugosci slowa
  88.   USARTInit.USART_WordLength = 8;
  89.  
  90.   //Ustawienie bitu stopu
  91.   USARTInit.USART_StopBits = USART_StopBits_1;
  92.  
  93.   //Brak kontroli parzystosci
  94.   USARTInit.USART_Parity = USART_Parity_No;
  95.  
  96.   //Kontrola przepływu danych
  97.   USARTInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  98.  
  99.   //Tryb pracy TX i RX
  100.   USARTInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  101.  
  102.   USART_Init(USART2, &USARTInit);
  103.   USART_Cmd(USART2, ENABLE);
  104. }
  105.  
  106. //Inicjalizacja I2C
  107. void I2CInit(void)
  108. {
  109.  I2C_InitTypeDef I2CInit;
  110.  
  111.  I2C_StructInit(&I2CInit);
  112.  //Tryb pracy układu I2C
  113.  I2CInit.I2C_Mode = I2C_Mode_I2C;
  114.  //Stosunek trwania stanu wysokiego do niskiego linii SCK
  115.  I2CInit.I2C_DutyCycle = I2C_DutyCycle_2;
  116.  //Adres I2C po stronie mikrokontrolera jeśli pracuje jako slave
  117.  I2CInit.I2C_OwnAddress1 = 0x00;
  118.  //Wlaczenie potwierdzenia transmisji
  119.     I2CInit.I2C_Ack = I2C_Ack_Enable;
  120.  //Częstotliwośc SCK
  121.  I2CInit.I2C_ClockSpeed = 100000;
  122.  I2C_Init(I2C1, &I2CInit);
  123.  I2C_Cmd(I2C1, ENABLE);
  124. }
  125.  
  126. //Ustawienie adresu pamięci
  127. void EEPROM_ADDRES(uint32_t addr)
  128. {
  129.  //Rozpoczęcie komunikacji
  130.  I2C_GenerateSTART(I2C1, ENABLE);
  131.  //Czekanie aż polecenie zostanie wykonane
  132.  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
  133.  //W tym kroku wysłany jest identyfikator układu
  134.  I2C_Send7bitAddress(I2C1, 0xa0, I2C_Direction_Transmitter);
  135.  //Odczekanie na odpowiedź
  136.  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
  137.  
  138.  //Wysłanie adresu komórki pamięci do jakiej będą zapisywane dane.
  139.  I2C_SendData(I2C1, addr);
  140.  while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
  141. }
  142.  
  143. //Zapis do pamięci
  144. void EEPROM_WRITE(uint32_t addr, const void* data, int size)
  145. {
  146.   int i;
  147.   const uint8_t* buffer = (uint8_t*)data;
  148.  
  149.   //Wybranie adresu
  150.   EEPROM_ADDRES(addr);
  151.   //Przejście przez cały buffor z danymi
  152.   for (i = 0; i < size; i++)
  153.   {
  154.    I2C_SendData(I2C1, buffer[i]);
  155.    while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
  156.   }
  157.   //Wygenerowanie sygnału Stop
  158.   I2C_GenerateSTOP(I2C1, ENABLE);
  159. }
  160.  
  161. //Odczyt z pamięci
  162. void EEPROM_READ(uint32_t addr, void* data, int size)
  163. {
  164.   //Deklaracja zmiennych
  165.   int i;
  166.   uint8_t* buffer = (uint8_t*)data;
  167.  
  168.   //Wybór adresu urządzenia
  169.   EEPROM_ADDRES(addr);
  170.  
  171.   //Wysłanie sygnału start, następnie czekanie na odpowiedź
  172.   I2C_GenerateSTART(I2C1, ENABLE);
  173.   while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
  174.  
  175.   //Odbiór tylko jednego bajtu, wylacz potwierdzenie
  176.   I2C_AcknowledgeConfig(I2C1, ENABLE);
  177.   //Wysłanie adresu układu, ustawienie i2c jako master
  178.   I2C_Send7bitAddress(I2C1, 0xa0, I2C_Direction_Receiver);
  179.   //Odczekanie na EV6
  180.   while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
  181.  
  182.   for (i = 0; i < size - 1; i++)
  183.   {
  184.    while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
  185.    buffer[i] = I2C_ReceiveData(I2C1);
  186.   }
  187.   //Odbiór pojedyńczego bajtu
  188.   I2C_AcknowledgeConfig(I2C1, DISABLE);
  189.  
  190.   //Wygenerowanie sygnału stop
  191.   I2C_GenerateSTOP(I2C1, ENABLE);
  192.   //Odczekanie na sygnał
  193.   while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
  194.   //Odczytaj dane z rejestru
  195.   buffer[i] = I2C_ReceiveData(I2C1);
  196. }
  197.  
  198. //Wysłanie danych przez układ USART na komputer
  199. void SendCharc(char c)
  200. {
  201.  while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
  202.  USART_SendData(USART2, c);
  203. }
  204.  
  205. //Potrzebne dla przesyłania danych z wykorzystaniem funckji printf
  206. int __io_putchar(int c)
  207. {
  208.  SendCharc(c);
  209.  return c;
  210. }
  211.