ref: e06dd405bc9548ad6e1250b01d6132fbe032bc4d
src/drivers/St7789.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
#include <cstring> #include "drivers/St7789.h" #include <hal/nrf_gpio.h> #include <nrfx_log.h> #include "drivers/Spi.h" #include "task.h" using namespace Pinetime::Drivers; St7789::St7789(Spi& spi, uint8_t pinDataCommand, uint8_t pinReset) : spi {spi}, pinDataCommand {pinDataCommand}, pinReset {pinReset} { } void St7789::Init() { nrf_gpio_cfg_output(pinDataCommand); nrf_gpio_cfg_output(pinReset); nrf_gpio_pin_set(pinReset); HardwareReset(); SoftwareReset(); Command2Enable(); SleepOut(); PixelFormat(); MemoryDataAccessControl(); SetAddrWindow(0, 0, Width, Height); // P8B Mirrored version does not need display inversion. #ifndef DRIVER_DISPLAY_MIRROR DisplayInversionOn(); #endif PorchSet(); FrameRateNormalSet(); IdleFrameRateOff(); NormalModeOn(); SetVdv(); PowerControl(); GateControl(); DisplayOn(); } void St7789::WriteData(uint8_t data) { WriteData(&data, 1); } void St7789::WriteData(const uint8_t* data, size_t size) { WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { nrf_gpio_pin_set(pinDataCommand); }); } void St7789::WriteCommand(uint8_t data) { WriteCommand(&data, 1); } void St7789::WriteCommand(const uint8_t* data, size_t size) { WriteSpi(data, size, [pinDataCommand = pinDataCommand]() { nrf_gpio_pin_clear(pinDataCommand); }); } void St7789::WriteSpi(const uint8_t* data, size_t size, const std::function<void()>& preTransactionHook) { spi.Write(data, size, preTransactionHook); } void St7789::SoftwareReset() { EnsureSleepOutPostDelay(); WriteCommand(static_cast<uint8_t>(Commands::SoftwareReset)); // If sleep in: must wait 120ms before sleep out can sent (see driver datasheet) // Unconditionally wait as software reset doesn't need to be performant sleepIn = true; lastSleepExit = xTaskGetTickCount(); vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::Command2Enable() { WriteCommand(static_cast<uint8_t>(Commands::Command2Enable)); constexpr uint8_t args[] = { 0x5a, // Constant 0x69, // Constant 0x02, // Constant 0x01, // Enable }; WriteData(args, sizeof(args)); } void St7789::SleepOut() { if (!sleepIn) { return; } WriteCommand(static_cast<uint8_t>(Commands::SleepOut)); // Wait 5ms for clocks to stabilise // pdMS rounds down => 6 used here vTaskDelay(pdMS_TO_TICKS(6)); // Cannot send sleep in or software reset for 120ms lastSleepExit = xTaskGetTickCount(); sleepIn = false; } void St7789::EnsureSleepOutPostDelay() { TickType_t delta = xTaskGetTickCount() - lastSleepExit; // Due to timer wraparound, there is a chance of delaying when not necessary // It is very low (pdMS_TO_TICKS(125)/2^32) and waiting an extra 125ms isn't too bad if (delta < pdMS_TO_TICKS(125)) { vTaskDelay(pdMS_TO_TICKS(125) - delta); } } void St7789::SleepIn() { if (sleepIn) { return; } EnsureSleepOutPostDelay(); WriteCommand(static_cast<uint8_t>(Commands::SleepIn)); // Wait 5ms for clocks to stabilise // pdMS rounds down => 6 used here vTaskDelay(pdMS_TO_TICKS(6)); sleepIn = true; } void St7789::PixelFormat() { WriteCommand(static_cast<uint8_t>(Commands::PixelFormat)); // 65K colours, 16-bit per pixel WriteData(0x55); } void St7789::MemoryDataAccessControl() { WriteCommand(static_cast<uint8_t>(Commands::MemoryDataAccessControl)); #ifdef DRIVER_DISPLAY_MIRROR // [7] = MY = Page Address Order, 0 = Top to bottom, 1 = Bottom to top // [6] = MX = Column Address Order, 0 = Left to right, 1 = Right to left // [5] = MV = Page/Column Order, 0 = Normal mode, 1 = Reverse mode // [4] = ML = Line Address Order, 0 = LCD refresh from top to bottom, 1 = Bottom to top // [3] = RGB = RGB/BGR Order, 0 = RGB, 1 = BGR // [2] = MH = Display Data Latch Order, 0 = LCD refresh from left to right, 1 = Right to left // [0 .. 1] = Unused WriteData(0b01000000); #else WriteData(0x00); #endif } void St7789::DisplayInversionOn() { WriteCommand(static_cast<uint8_t>(Commands::DisplayInversionOn)); } void St7789::NormalModeOn() { WriteCommand(static_cast<uint8_t>(Commands::NormalModeOn)); } void St7789::IdleModeOn() { WriteCommand(static_cast<uint8_t>(Commands::IdleModeOn)); } void St7789::IdleModeOff() { WriteCommand(static_cast<uint8_t>(Commands::IdleModeOff)); } void St7789::PorchSet() { WriteCommand(static_cast<uint8_t>(Commands::Porch)); constexpr uint8_t args[] = { 0x02, // Normal mode front porch 0x03, // Normal mode back porch 0x01, // Porch control enable 0xed, // Idle mode front:back porch 0xed, // Partial mode front:back porch (partial mode unused but set anyway) }; WriteData(args, sizeof(args)); } void St7789::FrameRateNormalSet() { WriteCommand(static_cast<uint8_t>(Commands::FrameRateNormal)); // Note that the datasheet table is imprecise - see formula below table WriteData(0x0a); } void St7789::IdleFrameRateOn() { WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle)); // According to the datasheet, these controls should apply only to partial/idle mode // However they appear to apply to normal mode, so we have to enable/disable // every time we enter/exit always on constexpr uint8_t args[] = { 0x12, // Enable frame rate control for partial/idle mode, 4x frame divider 0x1e, // Idle mode frame rate 0x1e, // Partial mode frame rate (unused) }; WriteData(args, sizeof(args)); } void St7789::IdleFrameRateOff() { WriteCommand(static_cast<uint8_t>(Commands::FrameRateIdle)); constexpr uint8_t args[] = { 0x00, // Disable frame rate control and divider 0x0a, // Idle mode frame rate (normal) 0x0a, // Partial mode frame rate (normal, unused) }; WriteData(args, sizeof(args)); } void St7789::DisplayOn() { WriteCommand(static_cast<uint8_t>(Commands::DisplayOn)); } void St7789::PowerControl() { WriteCommand(static_cast<uint8_t>(Commands::PowerControl1)); constexpr uint8_t args[] = { 0xa4, // Constant 0x00, // Lowest possible voltages }; WriteData(args, sizeof(args)); WriteCommand(static_cast<uint8_t>(Commands::PowerControl2)); // Lowest possible boost circuit clocks WriteData(0xb3); } void St7789::GateControl() { WriteCommand(static_cast<uint8_t>(Commands::GateControl)); // Lowest possible VGL/VGH WriteData(0x00); } void St7789::SetAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { WriteCommand(static_cast<uint8_t>(Commands::ColumnAddressSet)); uint8_t colArgs[] = { static_cast<uint8_t>(x0 >> 8), // x start MSB static_cast<uint8_t>(x0), // x start LSB static_cast<uint8_t>(x1 >> 8), // x end MSB static_cast<uint8_t>(x1) // x end LSB }; WriteData(colArgs, sizeof(colArgs)); WriteCommand(static_cast<uint8_t>(Commands::RowAddressSet)); uint8_t rowArgs[] = { static_cast<uint8_t>(y0 >> 8), // y start MSB static_cast<uint8_t>(y0), // y start LSB static_cast<uint8_t>(y1 >> 8), // y end MSB static_cast<uint8_t>(y1) // y end LSB }; memcpy(addrWindowArgs, rowArgs, sizeof(rowArgs)); WriteData(addrWindowArgs, sizeof(addrWindowArgs)); } void St7789::WriteToRam(const uint8_t* data, size_t size) { WriteCommand(static_cast<uint8_t>(Commands::WriteToRam)); WriteData(data, size); } void St7789::SetVdv() { // By default there is a large step from pixel brightness zero to one. // After experimenting with VCOMS, VRH and VDV, this was found to produce good results. WriteCommand(static_cast<uint8_t>(Commands::VdvSet)); WriteData(0x10); } void St7789::DisplayOff() { WriteCommand(static_cast<uint8_t>(Commands::DisplayOff)); } void St7789::VerticalScrollStartAddress(uint16_t line) { verticalScrollingStartAddress = line; WriteCommand(static_cast<uint8_t>(Commands::VerticalScrollStartAddress)); uint8_t args[] = { static_cast<uint8_t>(line >> 8), // Frame memory line pointer MSB static_cast<uint8_t>(line) // Frame memory line pointer LSB }; memcpy(verticalScrollArgs, args, sizeof(args)); WriteData(verticalScrollArgs, sizeof(verticalScrollArgs)); } void St7789::Uninit() { } void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t* data, size_t size) { SetAddrWindow(x, y, x + width - 1, y + height - 1); WriteToRam(data, size); } void St7789::HardwareReset() { nrf_gpio_pin_clear(pinReset); vTaskDelay(pdMS_TO_TICKS(1)); nrf_gpio_pin_set(pinReset); // If hardware reset started while sleep out, reset time may be up to 120ms // Unconditionally wait as hardware reset doesn't need to be performant sleepIn = true; lastSleepExit = xTaskGetTickCount(); vTaskDelay(pdMS_TO_TICKS(125)); } void St7789::LowPowerOn() { IdleModeOn(); IdleFrameRateOn(); NRF_LOG_INFO("[LCD] Low power mode"); } void St7789::LowPowerOff() { IdleModeOff(); IdleFrameRateOff(); NRF_LOG_INFO("[LCD] Normal power mode"); } void St7789::Sleep() { SleepIn(); nrf_gpio_cfg_default(pinDataCommand); NRF_LOG_INFO("[LCD] Sleep"); } void St7789::Wakeup() { nrf_gpio_cfg_output(pinDataCommand); SleepOut(); VerticalScrollStartAddress(verticalScrollingStartAddress); DisplayOn(); NRF_LOG_INFO("[LCD] Wakeup") } |