/ src / display / display_screen.cpp
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