środa, 29 marca 2023

[4] STM32F4 - OLED SSD1306 I2C

W tym poście chciałbym opisać działanie wyświetlacza z kontrolerem SSD1306. Do testów wykorzystałem układ STM32F411RE.


Wykorzystywany przeze mnie wyświetlacz OLED jest wyposażony w ekran o przekątnej 0,96 cala (128x64 px). Sterowanie odbywa się przez sterownik SSD1306. Dopuszczalne jest wyświetlanie obrazu w kolorze niebieskim, lub zastosowanie odwrócenia kolorów. Gdzie tło będzie niebieskie a obraz czarny. Komunikacja z układem odbywa się przez interfejs I2C. 

Pamięć obrazu:


Wyświetlacz posiada wbudowaną pamięć SRAM, która przechowuje obraz wyświetlany na wyświetlaczu. Każdy bit pamięci ma przypisany jeden piksel do sterowania. Została ona podzielona na 8 stron. Jedna strona przechowuje 128 bajtów danych. Zapis dokonujemy do każdej strony osobno. 

Biblioteka HAL:


Inicjalizacja I2C:

  1. static void MX_I2C1_Init(void)
  2. {
  3.  
  4.   /* USER CODE BEGIN I2C1_Init 0 */
  5.  
  6.   /* USER CODE END I2C1_Init 0 */
  7.  
  8.   /* USER CODE BEGIN I2C1_Init 1 */
  9.  
  10.   /* USER CODE END I2C1_Init 1 */
  11.   hi2c1.Instance = I2C1;
  12.   hi2c1.Init.ClockSpeed = 400000;
  13.   hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  14.   hi2c1.Init.OwnAddress1 = 0;
  15.   hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  16.   hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  17.   hi2c1.Init.OwnAddress2 = 0;
  18.   hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  19.   hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  20.   if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  21.   {
  22.     Error_Handler();
  23.   }
  24.   /* USER CODE BEGIN I2C1_Init 2 */
  25.  
  26.   /* USER CODE END I2C1_Init 2 */
  27.  
  28. }

Zestaw funkcji udostępnionych w bibliotece wygląda następująco:

  1. uint8_t SSD1306_Init(void);
  2. void SSD1306_UpdateScreen(void);
  3.  
  4. uint8_t SSD1306_DisplayChar(char ch, FontTypedef* Font, SSD1306_COLOR_t color);
  5. uint8_t SSD1306_DisplayString(char* str, FontTypedef* Font, SSD1306_COLOR_t color);
  6.  
  7. void SSD1306_ScrollRightDisplay(uint8_t start, uint8_t stop);
  8. void SSD1306_ScrollLeftDisplay(uint8_t start, uint8_t stop);
  9. void SSD1306_ScrollDiagRightDisplay(uint8_t start, uint8_t stop);
  10. void SSD1306_ScrollDiagLeftDisplay(uint8_t start, uint8_t stop);
  11. void SSD1306_StopScroll(void);
  12.  
  13. void SSD1306_InvertDisplay(uint8_t i);
  14. void SSD1306_Dim(uint8_t dim, uint8_t contrast);
  15. void SSD1306_DisplayOn(void);
  16. void SSD1306_DisplayOff(void);
  17.  
  18. void SSD1306_DrawBitmap(int16_t posX, int16_t posY, const unsigned char* bitmap, int16_t width, int16_t height, SSD1306_COLOR_t color);
  19. void SSD1306_DrawPixel(uint16_t posX, uint16_t posY, SSD1306_COLOR_t color);
  20. void SSD1306_DrawLine(uint16_t posXStart, uint16_t posYStart, uint16_t posXEnd, uint16_t posYEnd, SSD1306_COLOR_t color);
  21. void SSD1306_DrawRectangle(uint16_t posX, uint16_t posY, uint16_t width, uint16_t height, SSD1306_COLOR_t color);
  22. void SSD1306_DrawFilledRectangle(uint16_t posX, uint16_t posY, uint16_t width, uint16_t height, SSD1306_COLOR_t color);
  23. void SSD1306_DrawCircle(int16_t posX, int16_t posY, int16_t rad, SSD1306_COLOR_t color);
  24. void SSD1306_DrawFilledCircle(int16_t posX, int16_t posY, int16_t rad, SSD1306_COLOR_t color);
  25. void SSD1306_DrawTriangle(uint16_t posX1, uint16_t posY1, uint16_t posX2, uint16_t posY2, uint16_t posX3, uint16_t posY3, SSD1306_COLOR_t color);
  26. void SSD1306_DrawFilledTriangle(uint16_t posX1, uint16_t posY1, uint16_t posX2, uint16_t posY2, uint16_t posX3, uint16_t posY3, SSD1306_COLOR_t color);
  27.  
  28. void SSD1306_GoToPosition(uint16_t posX, uint16_t posY);
  29. void SSD1306_ClearScrean(void);
  30. void SSD1306_GotoXY(uint16_t x, uint16_t y);
  31. void SSD1306_FillBuffer(SSD1306_COLOR_t color);

Standardowe przesyłanie danych wygląda w następujący sposób:

  1. static void SSD1306_WriteCommand(uint8_t data)
  2. {
  3.     uint8_t dataToSend[2];
  4.     dataToSend[0] = 0x00;
  5.     dataToSend[1] = data;
  6.     HAL_I2C_Master_Transmit(&hi2c1, SSD1306_I2C_ADDR, dataToSend, 2, 10);
  7. }
  8.  
  9. static void SSD1306_WriteMultipleData(uint8_t address, uint8_t registerToWrite, uint8_t* dataToWrite, uint16_t dataCount) {
  10.     uint8_t data[256];
  11.     data[0] = registerToWrite;
  12.  
  13.     for(uint8_t i = 0; i < dataCount; i++) {
  14.         data[i+1] = dataToWrite[i];
  15.     }
  16.  
  17.     HAL_I2C_Master_Transmit(&hi2c1, address, data, dataCount+1, 10);
  18. }

Wysyłam tutaj dane wykorzystując funkcję HAL_I2C_Master_Transmit. 

Biblioteka LL:


Poniżej opis funkcji komunikacyjnych z wyświetlaczem. Jest to w zasadzie cała zmiana jaką należy wykonać z bibliotekami LL. Reszta logiki ramki itp zostaje bez zmian.

Inicjalizacja I2C wygląda następująco:

  1. static void MX_I2C1_Init(void)
  2. {
  3.  
  4.   /* USER CODE BEGIN I2C1_Init 0 */
  5.  
  6.   /* USER CODE END I2C1_Init 0 */
  7.  
  8.   LL_I2C_InitTypeDef I2C_InitStruct = {0};
  9.  
  10.   LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
  11.  
  12.   LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOB);
  13.   /**I2C1 GPIO Configuration
  14.   PB6   ------> I2C1_SCL
  15.   PB7   ------> I2C1_SDA
  16.   */
  17.   GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7;
  18.   GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  19.   GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  20.   GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
  21.   GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
  22.   GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
  23.   LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  24.  
  25.   /* Peripheral clock enable */
  26.   LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
  27.  
  28.   /* USER CODE BEGIN I2C1_Init 1 */
  29.  
  30.   /* USER CODE END I2C1_Init 1 */
  31.   /** I2C Initialization
  32.   */
  33.   LL_I2C_DisableOwnAddress2(I2C1);
  34.   LL_I2C_DisableGeneralCall(I2C1);
  35.   LL_I2C_EnableClockStretching(I2C1);
  36.   I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
  37.   I2C_InitStruct.ClockSpeed = 399999;
  38.   I2C_InitStruct.DutyCycle = LL_I2C_DUTYCYCLE_2;
  39.   I2C_InitStruct.OwnAddress1 = 0;
  40.   I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
  41.   I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
  42.   LL_I2C_Init(I2C1, &I2C_InitStruct);
  43.   LL_I2C_SetOwnAddress2(I2C1, 0);
  44.   /* USER CODE BEGIN I2C1_Init 2 */
  45.   LL_I2C_Enable(I2C1);
  46.   /* USER CODE END I2C1_Init 2 */
  47.  
  48. }

W związku z tym, że wykorzystuje funkcję LL_mDelay() to należy pamiętać o wywołaniu funkcji LL_Init1msTick() w celu poprawnego skonfigurowania opóźnień.

Funkcja przesyłająca dane do urządzenia:

  1. static int8_t I2C_SendData(uint8_t devAddr, uint8_t* buffer, uint16_t len, uint16_t maxDelay)
  2. {
  3.   uint16_t countTimeout = 0;
  4.  
  5.   LL_I2C_GenerateStartCondition(I2C1);
  6.  
  7.   while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
  8.     LL_mDelay(1);
  9.     countTimeout++;
  10.     if(countTimeout > maxDelay) {
  11.         return -1;
  12.     }
  13.   }
  14.  
  15.   LL_I2C_TransmitData8(I2C1, (devAddr) | 0x00);
  16.  
  17.   while(!LL_I2C_IsActiveFlag_ADDR(I2C1))
  18.   {
  19.     LL_mDelay(1);
  20.     countTimeout++;
  21.     if(countTimeout > maxDelay) {
  22.         return -2;
  23.     }
  24.   }
  25.  
  26.   LL_I2C_ClearFlag_ADDR(I2C1);
  27.  
  28.   for(int i=0; i < len; i++)
  29.   {
  30.     while(!LL_I2C_IsActiveFlag_TXE(I2C1)) {
  31.       LL_mDelay(1);
  32.       countTimeout++;
  33.       if(countTimeout > maxDelay) {
  34.           return -3;
  35.       }
  36.     }
  37.  
  38.     LL_I2C_TransmitData8(I2C1, buffer[i]);
  39.     countTimeout = 0;
  40.   }
  41.  
  42.   while(!LL_I2C_IsActiveFlag_BTF(I2C1)) {
  43.     LL_mDelay(1);
  44.     countTimeout++;
  45.     if(countTimeout > maxDelay) {
  46.         return -4;
  47.     }
  48.   }
  49.  
  50.   LL_I2C_GenerateStopCondition(I2C1);
  51.  
  52.   return 0;
  53. }
  54.  
  55. static void SSD1306_WriteMultipleData(uint8_t address, uint8_t registerToWrite, uint8_t* dataToWrite, uint16_t dataCount) {
  56.     uint8_t data[256];
  57.     data[0] = registerToWrite;
  58.  
  59.     for(uint8_t i = 0; i < dataCount; i++) {
  60.         data[i+1] = dataToWrite[i];
  61.     }
  62.  
  63.     I2C_SendData(SSD1306_I2C_ADDR, &data[0], dataCount, 10);
  64. }
  65.  
  66. static void SSD1306_WriteCommand(uint8_t data)
  67. {
  68.     uint8_t dataToSend[2];
  69.     dataToSend[0] = 0x00;
  70.     dataToSend[1] = data;
  71.  
  72.     I2C_SendData(SSD1306_I2C_ADDR, &dataToSend[0], 2, 10);
  73. }
Pliki do projektu można pobrać z dysku Google pod tym linkiem.

Linki: