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:
- static void MX_I2C1_Init(void)
- {
- /* USER CODE BEGIN I2C1_Init 0 */
- /* USER CODE END I2C1_Init 0 */
- /* USER CODE BEGIN I2C1_Init 1 */
- /* USER CODE END I2C1_Init 1 */
- hi2c1.Instance = I2C1;
- hi2c1.Init.ClockSpeed = 400000;
- hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
- hi2c1.Init.OwnAddress1 = 0;
- hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
- hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
- hi2c1.Init.OwnAddress2 = 0;
- hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
- hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
- if (HAL_I2C_Init(&hi2c1) != HAL_OK)
- {
- Error_Handler();
- }
- /* USER CODE BEGIN I2C1_Init 2 */
- /* USER CODE END I2C1_Init 2 */
- }
Zestaw funkcji udostępnionych w bibliotece wygląda następująco:
- uint8_t SSD1306_Init(void);
- void SSD1306_UpdateScreen(void);
- uint8_t SSD1306_DisplayChar(char ch, FontTypedef* Font, SSD1306_COLOR_t color);
- uint8_t SSD1306_DisplayString(char* str, FontTypedef* Font, SSD1306_COLOR_t color);
- void SSD1306_ScrollRightDisplay(uint8_t start, uint8_t stop);
- void SSD1306_ScrollLeftDisplay(uint8_t start, uint8_t stop);
- void SSD1306_ScrollDiagRightDisplay(uint8_t start, uint8_t stop);
- void SSD1306_ScrollDiagLeftDisplay(uint8_t start, uint8_t stop);
- void SSD1306_StopScroll(void);
- void SSD1306_InvertDisplay(uint8_t i);
- void SSD1306_Dim(uint8_t dim, uint8_t contrast);
- void SSD1306_DisplayOn(void);
- void SSD1306_DisplayOff(void);
- void SSD1306_DrawBitmap(int16_t posX, int16_t posY, const unsigned char* bitmap, int16_t width, int16_t height, SSD1306_COLOR_t color);
- void SSD1306_DrawPixel(uint16_t posX, uint16_t posY, SSD1306_COLOR_t color);
- void SSD1306_DrawLine(uint16_t posXStart, uint16_t posYStart, uint16_t posXEnd, uint16_t posYEnd, SSD1306_COLOR_t color);
- void SSD1306_DrawRectangle(uint16_t posX, uint16_t posY, uint16_t width, uint16_t height, SSD1306_COLOR_t color);
- void SSD1306_DrawFilledRectangle(uint16_t posX, uint16_t posY, uint16_t width, uint16_t height, SSD1306_COLOR_t color);
- void SSD1306_DrawCircle(int16_t posX, int16_t posY, int16_t rad, SSD1306_COLOR_t color);
- void SSD1306_DrawFilledCircle(int16_t posX, int16_t posY, int16_t rad, SSD1306_COLOR_t color);
- 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);
- 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);
- void SSD1306_GoToPosition(uint16_t posX, uint16_t posY);
- void SSD1306_ClearScrean(void);
- void SSD1306_GotoXY(uint16_t x, uint16_t y);
- void SSD1306_FillBuffer(SSD1306_COLOR_t color);
Standardowe przesyłanie danych wygląda w następujący sposób:
- static void SSD1306_WriteCommand(uint8_t data)
- {
- uint8_t dataToSend[2];
- dataToSend[0] = 0x00;
- dataToSend[1] = data;
- HAL_I2C_Master_Transmit(&hi2c1, SSD1306_I2C_ADDR, dataToSend, 2, 10);
- }
- static void SSD1306_WriteMultipleData(uint8_t address, uint8_t registerToWrite, uint8_t* dataToWrite, uint16_t dataCount) {
- uint8_t data[256];
- data[0] = registerToWrite;
- for(uint8_t i = 0; i < dataCount; i++) {
- data[i+1] = dataToWrite[i];
- }
- HAL_I2C_Master_Transmit(&hi2c1, address, data, dataCount+1, 10);
- }
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:
- static void MX_I2C1_Init(void)
- {
- /* USER CODE BEGIN I2C1_Init 0 */
- /* USER CODE END I2C1_Init 0 */
- LL_I2C_InitTypeDef I2C_InitStruct = {0};
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
- LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOB);
- /**I2C1 GPIO Configuration
- PB6 ------> I2C1_SCL
- PB7 ------> I2C1_SDA
- */
- GPIO_InitStruct.Pin = LL_GPIO_PIN_6|LL_GPIO_PIN_7;
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
- GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
- GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
- GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
- GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
- LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- /* Peripheral clock enable */
- LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
- /* USER CODE BEGIN I2C1_Init 1 */
- /* USER CODE END I2C1_Init 1 */
- /** I2C Initialization
- */
- LL_I2C_DisableOwnAddress2(I2C1);
- LL_I2C_DisableGeneralCall(I2C1);
- LL_I2C_EnableClockStretching(I2C1);
- I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
- I2C_InitStruct.ClockSpeed = 399999;
- I2C_InitStruct.DutyCycle = LL_I2C_DUTYCYCLE_2;
- I2C_InitStruct.OwnAddress1 = 0;
- I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
- I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
- LL_I2C_Init(I2C1, &I2C_InitStruct);
- LL_I2C_SetOwnAddress2(I2C1, 0);
- /* USER CODE BEGIN I2C1_Init 2 */
- LL_I2C_Enable(I2C1);
- /* USER CODE END I2C1_Init 2 */
- }
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:
- static int8_t I2C_SendData(uint8_t devAddr, uint8_t* buffer, uint16_t len, uint16_t maxDelay)
- {
- uint16_t countTimeout = 0;
- LL_I2C_GenerateStartCondition(I2C1);
- while(!LL_I2C_IsActiveFlag_SB(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -1;
- }
- }
- LL_I2C_TransmitData8(I2C1, (devAddr) | 0x00);
- while(!LL_I2C_IsActiveFlag_ADDR(I2C1))
- {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -2;
- }
- }
- LL_I2C_ClearFlag_ADDR(I2C1);
- for(int i=0; i < len; i++)
- {
- while(!LL_I2C_IsActiveFlag_TXE(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -3;
- }
- }
- LL_I2C_TransmitData8(I2C1, buffer[i]);
- countTimeout = 0;
- }
- while(!LL_I2C_IsActiveFlag_BTF(I2C1)) {
- LL_mDelay(1);
- countTimeout++;
- if(countTimeout > maxDelay) {
- return -4;
- }
- }
- LL_I2C_GenerateStopCondition(I2C1);
- return 0;
- }
- static void SSD1306_WriteMultipleData(uint8_t address, uint8_t registerToWrite, uint8_t* dataToWrite, uint16_t dataCount) {
- uint8_t data[256];
- data[0] = registerToWrite;
- for(uint8_t i = 0; i < dataCount; i++) {
- data[i+1] = dataToWrite[i];
- }
- I2C_SendData(SSD1306_I2C_ADDR, &data[0], dataCount, 10);
- }
- static void SSD1306_WriteCommand(uint8_t data)
- {
- uint8_t dataToSend[2];
- dataToSend[0] = 0x00;
- dataToSend[1] = data;
- I2C_SendData(SSD1306_I2C_ADDR, &dataToSend[0], 2, 10);
- }