/* * * Copyright (c) 2020-2023 Project CHIP Authors * Copyright (c) 2018 Nest Labs, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "driver/ledc.h" #include "esp_log.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/timers.h" #include "Display.h" #if CONFIG_HAVE_DISPLAY // Brightness picked such that it's easy for cameras to focus on #define DEFAULT_BRIGHTNESS_PERCENT 10 // 8MHz is the recommended SPI speed to init the driver with // It later gets set to the preconfigured defaults within the driver #define TFT_SPI_CLOCK_INIT_HZ 8000000 // The frequency used by the ledc timer // value chosen to eliminate flicker #define LEDC_PWM_HZ 1000 // with a duty resolution of LEDC_TIMER_8_BIT // the highest possible brightness value is 255 #define BRIGHTNESS_MAX 255 // The M5Stack's backlight is on Channel 7 #define BACKLIGHT_CHANNEL LEDC_CHANNEL_7 static const char TAG[] = "Display"; uint16_t DisplayHeight = 0; uint16_t DisplayWidth = 0; bool awake = false; #if CONFIG_DISPLAY_AUTO_OFF // FreeRTOS timer used to turn the display off after a short while TimerHandle_t displayTimer = NULL; #endif #if CONFIG_DISPLAY_AUTO_OFF static void TimerCallback(TimerHandle_t xTimer); #endif static void SetupBrightnessControl(); static void SetBrightness(uint16_t brightness_percent); esp_err_t InitDisplay() { esp_err_t err; spi_lobo_device_handle_t spi; // configured based on the display driver's examples spi_lobo_bus_config_t buscfg; memset((void *) &buscfg, 0, sizeof(buscfg)); buscfg.miso_io_num = PIN_NUM_MISO; // set SPI MISO pin buscfg.mosi_io_num = PIN_NUM_MOSI; // set SPI MOSI pin buscfg.sclk_io_num = PIN_NUM_CLK; // set SPI CLK pin buscfg.quadwp_io_num = -1; buscfg.quadhd_io_num = -1; spi_lobo_device_interface_config_t devcfg; memset((void *) &devcfg, 0, sizeof(devcfg)); devcfg.clock_speed_hz = TFT_SPI_CLOCK_INIT_HZ; devcfg.mode = 0; // SPI mode 0 devcfg.spics_io_num = -1; // we will use external CS pin devcfg.spics_ext_io_num = PIN_NUM_CS; // external CS pin devcfg.flags = LB_SPI_DEVICE_HALFDUPLEX; // ALWAYS SET to HALF DUPLEX MODE!! for display spi tft_max_rdclock = TFT_SPI_CLOCK_INIT_HZ; // Initialize all pins used by display driver. TFT_PinsInit(); // Initialize SPI bus and add a device for the display. err = spi_lobo_bus_add_device(TFT_HSPI_HOST, &buscfg, &devcfg, &spi); if (err != ESP_OK) return err; // Configure the display to use the new SPI device. tft_disp_spi = spi; err = spi_lobo_device_select(spi, 1); if (err != ESP_OK) return err; err = spi_lobo_device_deselect(spi); if (err != ESP_OK) return err; // Initialize the display driver. TFT_display_init(); // Detect maximum read speed and set it. tft_max_rdclock = find_rd_speed(); // Set the SPI clock speed overriding the initialized 8MHz speed spi_lobo_set_speed(spi, DEFAULT_SPI_CLOCK); TFT_setGammaCurve(0); TFT_setRotation(LANDSCAPE); TFT_resetclipwin(); DisplayWidth = (uint16_t) (1 + tft_dispWin.x2 - tft_dispWin.x1); DisplayHeight = (uint16_t) (1 + tft_dispWin.y2 - tft_dispWin.y1); ESP_LOGI(TAG, "Display initialized (height %u, width %u)", DisplayHeight, DisplayWidth); TFT_invertDisplay(INVERT_DISPLAY); // prepare the display for brightness control SetupBrightnessControl(); #if CONFIG_DISPLAY_AUTO_OFF displayTimer = xTimerCreate("DisplayTimer", pdMS_TO_TICKS(DISPLAY_TIMEOUT_MS), false, NULL, TimerCallback); #endif // lower the brightness of the screen WakeDisplay(); return err; } void SetBrightness(uint16_t brightness_percent) { uint16_t brightness = (brightness_percent * BRIGHTNESS_MAX) / 100; if (ledc_set_duty(LEDC_HIGH_SPEED_MODE, BACKLIGHT_CHANNEL, brightness) || ledc_update_duty(LEDC_HIGH_SPEED_MODE, BACKLIGHT_CHANNEL)) { ESP_LOGE(TAG, "Failed to set display brightness..."); } } bool WakeDisplay() { bool woken = !awake; awake = true; SetBrightness(DEFAULT_BRIGHTNESS_PERCENT); #if CONFIG_DISPLAY_AUTO_OFF xTimerStart(displayTimer, 0); ESP_LOGI(TAG, "Display awake but will switch off automatically in %d seconds", DISPLAY_TIMEOUT_MS / 1000); #endif return woken; } void ClearDisplay() { ClearRect(); } void ClearRect(uint16_t x_percent_start, uint16_t y_percent_start, uint16_t x_percent_end, uint16_t y_percent_end) { if (x_percent_end < x_percent_start) { x_percent_end = x_percent_start; } if (y_percent_end < y_percent_start) { y_percent_end = y_percent_start; } uint16_t start_x = (DisplayWidth * x_percent_start) / 100; uint16_t start_y = (DisplayHeight * y_percent_start) / 100; uint16_t end_x = (DisplayWidth * x_percent_end) / 100; uint16_t end_y = (DisplayHeight * y_percent_end) / 100; TFT_fillRect(start_x, start_y, end_x, end_y, TFT_BLACK); } void DisplayStatusMessage(char * msg, uint16_t vpos) { TFT_setFont(SMALL_FONT, NULL); uint16_t msgX = 0; uint16_t msgY = (DisplayHeight * vpos) / 100; TFT_print(msg, msgX, msgY); } void TimerCallback(TimerHandle_t xTimer) { ESP_LOGI(TAG, "Display going to sleep..."); SetBrightness(0); awake = false; } void SetupBrightnessControl() { ledc_timer_config_t ledc_timer; memset(&ledc_timer, 0, sizeof(ledc_timer)); ledc_timer.duty_resolution = LEDC_TIMER_8_BIT; // resolution of PWM duty ledc_timer.freq_hz = LEDC_PWM_HZ; // frequency of PWM signal ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE; // timer mode ledc_timer.timer_num = LEDC_TIMER_0; // timer index ledc_timer_config(&ledc_timer); ledc_timer_set(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0, LEDC_PWM_HZ, LEDC_TIMER_8_BIT, LEDC_REF_TICK); ledc_channel_config_t ledc_channel; memset(&ledc_channel, 0, sizeof(ledc_channel)); ledc_channel.channel = BACKLIGHT_CHANNEL; ledc_channel.duty = BRIGHTNESS_MAX; ledc_channel.gpio_num = PIN_NUM_BCKL; ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE; ledc_channel.timer_sel = LEDC_TIMER_0; ledc_channel_config(&ledc_channel); } #endif // CONFIG_HAVE_DISPLAY