display_screen.cpp
1 #include "display_screen.h" 2 3 #include "app_timer.h" 4 #include "boards.h" 5 #include "ili9341.h" 6 #include "lvgl.h" 7 #include "nrf_gfx.h" 8 9 #include "screen_ui/display_screen_ui.h" 10 #include "led/display_led_pwm.h" 11 12 #include "display_log_module.ii" 13 14 APP_TIMER_DEF(display_screen_tick_timer); 15 16 namespace display 17 { 18 namespace 19 { 20 ////////////////////////////////////////////////////////////////////////// 21 // Display Buffer 22 constexpr uint32_t displayBufferPixelCount = LV_HOR_RES_MAX * 10; // 10 scanlines is suggested. 23 static lv_disp_buf_t displayBuffer; 24 static lv_color_t displayBuffer0[displayBufferPixelCount]; 25 static lv_color_t displayBuffer1[displayBufferPixelCount]; 26 27 ////////////////////////////////////////////////////////////////////////// 28 // Display Driver 29 static const nrf_lcd_t *lcd = &lcd_ili9341; // the graphics hardware 30 static const nrf_lcd_ex_t *lcdEx = &lcd_ili9341_ex; // the graphics hardware, bonus fns 31 lv_disp_t* display = nullptr; // the graphics middleware 32 void lvFlushCallback(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); 33 34 extern const nrf_lcd_t nrf_lcd_ili9341; 35 36 ////////////////////////////////////////////////////////////////////////// 37 // Backlight driver 38 39 static led::RgbLedColorBufferDescriptor* GetBacklightBufferDescriptor() 40 { 41 static led::RgbLedColorBuffer<1> data; 42 return &data; 43 } 44 45 static const display_led_rgb_driver_t* BacklightDriver = &display_led_pwm; 46 47 static const display_led_rgb_driver_config_t* GetBacklightPwmDriverConfig() 48 { 49 DISPLAY_LED_RGB_DRIVER_CONFIG_DEFINE(pwm_cfg, 1); 50 pwm_cfg.pins[0].pin = ILI9341_BACKLIGHT_CONTROL_PIN; 51 pwm_cfg.invert_polarity = false; 52 return &pwm_cfg; 53 } 54 } 55 56 Screen::Screen(input::Keypad* _keypad, float initialBacklight /*= 1.0f*/) : 57 backlight(GetBacklightBufferDescriptor(), BacklightDriver, GetBacklightPwmDriverConfig()) 58 { 59 if (initialized) 60 { 61 NRF_LOG_INFO("A Display::Screen was created after one was already initialized. This use case is not supported."); 62 return; 63 } 64 65 keypad = _keypad; 66 67 NRF_LOG_INFO("Initializing gfx hardware..."); 68 ret_code_t initResult = nrf_gfx_init(lcd); 69 70 if (initResult != NRF_SUCCESS) 71 { 72 NRF_LOG_ERROR("Failed to initialize gfx hardware!"); 73 APP_ERROR_CHECK(initResult); 74 return; 75 } 76 77 // and init the backlight, too. 78 InitializeBacklight(initialBacklight); 79 80 NRF_LOG_VERBOSE("Initializing gfx lvgl..."); 81 lv_init(); 82 83 NRF_LOG_VERBOSE("Creating timer event for gfx..."); 84 ret_code_t timerResult = app_timer_create(&display_screen_tick_timer, 85 APP_TIMER_MODE_REPEATED, 86 Screen::Tick); 87 APP_ERROR_CHECK(timerResult); 88 89 NRF_LOG_VERBOSE("Initializing gfx display buffers..."); 90 lv_disp_buf_init(&displayBuffer, displayBuffer0, displayBuffer1, displayBufferPixelCount); 91 92 NRF_LOG_VERBOSE("Initializing gfx display driver..."); 93 lv_disp_drv_t displayDriver; // docs say this can be local. 94 lv_disp_drv_init(&displayDriver); 95 96 NRF_LOG_VERBOSE("Registering gfx display driver..."); 97 displayDriver.buffer = &displayBuffer; 98 displayDriver.flush_cb = lvFlushCallback; 99 // displayDriver.monitor_cb = nullptr; // #perf monitoring later 100 displayDriver.rotated = 1; 101 displayDriver.color_chroma_key = LV_COLOR_MAGENTA; 102 displayDriver.user_data = this; 103 display = lv_disp_drv_register(&displayDriver); 104 105 if (displayDriver.rotated) 106 { 107 lcd->lcd_rotation_set(NRF_LCD_ROTATE_90); 108 } 109 110 NRF_LOG_VERBOSE("Starting gfx timer..."); 111 uint32_t ticks = APP_TIMER_TICKS(GraphicsTickMs); 112 timerResult = app_timer_start(display_screen_tick_timer, ticks, this); 113 APP_ERROR_CHECK(timerResult); 114 115 NRF_LOG_INFO("Gfx ready!"); 116 117 initialized = true; 118 } 119 120 Screen::~Screen() 121 { 122 if (initialized) 123 { 124 BacklightOffImmediate(); 125 lv_deinit(); 126 nrf_gfx_uninit(lcd); 127 initialized = false; 128 NRF_LOG_INFO("Gfx uninitialized."); 129 } 130 } 131 132 void Screen::InitializeBacklight(float initialBrightness) 133 { 134 NRF_LOG_VERBOSE("Starting backlight PWM..."); 135 lastBacklightBrightness = initialBrightness; 136 BacklightOn(); 137 } 138 139 void Screen::Update(float timeDelta) 140 { 141 if (initialized) 142 { 143 lv_task_handler(); 144 backlight.Update(timeDelta); 145 } 146 } 147 148 void Screen::Tick(void* context) 149 { 150 Screen* that = (Screen*)context; 151 if (that->initialized) 152 { 153 lv_tick_inc(GraphicsTickMs); 154 } 155 } 156 157 void Screen::BacklightOn() 158 { 159 static led::effect::Interpolate animateOn( 160 led::effect::Color(0), 161 led::effect::ColorFromFloat(lastBacklightBrightness), 162 BacklightAnimationDurationSlow 163 ); 164 animateOn.Reset(); 165 backlight.SetEffect(&animateOn); 166 } 167 168 void Screen::BacklightOff() 169 { 170 static led::effect::Interpolate animateOff( 171 led::effect::ColorFromFloat(lastBacklightBrightness), 172 led::effect::Color(0), 173 BacklightAnimationDurationSlow 174 ); 175 animateOff.Reset(); 176 backlight.SetEffect(&animateOff); 177 } 178 179 void Screen::BacklightOffImmediate() 180 { 181 // no animation, just turn it off. 182 static led::effect::Static off( 183 led::effect::Color(0) 184 ); 185 off.Reset(); 186 backlight.SetEffect(&off); 187 } 188 189 void Screen::SetBacklightBrightness(float brightnessPercent) 190 { 191 static led::effect::Interpolate animate( 192 led::effect::ColorFromFloat(lastBacklightBrightness), 193 led::effect::ColorFromFloat(brightnessPercent), 194 BacklightAnimationDurationFast 195 ); 196 197 // our colors may change, so since the static constructor only rusn once, set them. 198 animate.SetStartColor(led::effect::ColorFromFloat(lastBacklightBrightness)); 199 animate.SetEndColor(led::effect::ColorFromFloat(brightnessPercent)); 200 201 animate.Reset(); 202 203 backlight.SetEffect(&animate); 204 205 // store new 'last' brightness. 206 lastBacklightBrightness = brightnessPercent; 207 } 208 209 void Screen::SetBatteryStatus(uint8_t stateOfCharge, bool isCharging) 210 { 211 ui_status_widget_set_percentage(stateOfCharge, isCharging); 212 } 213 214 void Screen::DisplaySleep() 215 { 216 if (initialized) 217 { 218 lcdEx->sleep(); 219 } 220 } 221 222 void Screen::DisplayWake() 223 { 224 if (initialized) 225 { 226 lcdEx->wake(); 227 } 228 } 229 230 namespace 231 { 232 void lvFlushCallback(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 233 { 234 lv_coord_t hres = disp_drv->rotated == 0 ? disp_drv->hor_res : disp_drv->ver_res; 235 lv_coord_t vres = disp_drv->rotated == 0 ? disp_drv->ver_res : disp_drv->hor_res; 236 237 // Return if the area is out the screen 238 if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) 239 { 240 241 lv_disp_flush_ready(disp_drv); 242 return; 243 } 244 245 // grab our width and hight to give to the draw 246 uint16_t x = area->x1; 247 uint16_t y = area->y1; 248 uint16_t width = lv_area_get_width(area); 249 uint16_t height = lv_area_get_height(area); 250 251 // make sure we actually fit our data before we go passing it all willy nilly. 252 static_assert(sizeof(uint16_t) == sizeof(lv_color_t)); 253 254 // we are all set up to draw. we just need to wait and make sure 255 // that the driver is ready for us to actually hand it the next bit. 256 while (lcdEx->ready_for_command() == false) 257 { 258 __WFE(); 259 } 260 261 // ship it! 262 lcdEx->raw_draw(x, y, width, height, (const uint16_t*)color_p); 263 264 lv_disp_flush_ready(disp_drv); 265 } 266 } 267 268 } // namespace display