blob: 05e9ef16126ce8fde19a194a7e6252ddecf55287 [file] [log] [blame]
Chris Mumford34270472023-01-10 21:29:08 +00001// Copyright 2023 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15#include "pw_display_driver_st7735/display_driver.h"
16
17#include <array>
18#include <cstddef>
19
Anthony DiGirolamof0badda2024-03-29 22:34:21 +000020#include "pw_chrono/system_clock.h"
Chris Mumford34270472023-01-10 21:29:08 +000021#include "pw_digital_io/digital_io.h"
Chris Mumford8db6cc12023-05-02 11:57:18 +000022#include "pw_framebuffer/framebuffer.h"
Anthony DiGirolamof0badda2024-03-29 22:34:21 +000023#include "pw_thread/sleep.h"
Chris Mumford34270472023-01-10 21:29:08 +000024
Chris Mumford34270472023-01-10 21:29:08 +000025using pw::digital_io::State;
Chris Mumford8db6cc12023-05-02 11:57:18 +000026using pw::framebuffer::Framebuffer;
27using pw::framebuffer::PixelFormat;
Chris Mumford34270472023-01-10 21:29:08 +000028using pw::spi::ChipSelectBehavior;
29using pw::spi::Device;
30using std::array;
31using std::byte;
32
33namespace pw::display_driver {
34
35namespace {
36
37constexpr array<byte, 0> kEmptyArray;
38
39// ST7735 Display Registers
40// clang-format off
41#define ST7735_SWRESET 0x01
42#define ST7735_RDDID 0x04
43#define ST7735_RDDST 0x09
44#define ST7735_SLPIN 0x10
45#define ST7735_SLPOUT 0x11
46#define ST7735_PTLON 0x12
47#define ST7735_NORON 0x13
48#define ST7735_INVOFF 0x20
49#define ST7735_INVON 0x21
50#define ST7735_GAMSET 0x26
51#define ST7735_DISPOFF 0x28
52#define ST7735_DISPON 0x29
53#define ST7735_RAMWR 0x2C
54#define ST7735_CASET 0x2A
55#define ST7735_RASET 0x2B
56#define ST7735_RAMWR 0x2C
57#define ST7735_RAMRD 0x2E
58#define ST7735_PTLAR 0x30
59#define ST7735_TEOFF 0x34
60#define ST7735_TEON 0x35
61#define ST7735_MADCTL 0x36
62#define ST7735_COLMOD 0x3A
63#define ST7735_FRMCTR1 0xB1 // Frame Rate Control (normal mode/ Full colors)
64#define ST7735_FRMCTR2 0xB2 // Frame Rate Control (Idle mode/ 8-colors)
65#define ST7735_FRMCTR3 0xB3 // Frame Rate Control (Partial mode/ full colors)
66#define ST7735_INVCTR 0xB4
67#define ST7735_DISSET5 0xB6
68#define ST7735_GCTRL 0xB7
69#define ST7735_VCOMS 0xBB
70#define ST7735_PWCTR1 0xC0
71#define ST7735_PWCTR2 0xC1
72#define ST7735_PWCTR3 0xC2
73#define ST7735_VRHS 0xC3
74#define ST7735_VDVS 0xC4
75#define ST7735_VMCTR1 0xC5
76#define ST7735_FRCTRL2 0xC6
77#define ST7735_PWCTRL1 0xD0
78#define ST7735_RDID1 0xDA
79#define ST7735_RDID2 0xDB
80#define ST7735_RDID3 0xDC
81#define ST7735_RDID4 0xDD
82#define ST7735_PORCTRL 0xB2
83#define ST7735_GMCTRP1 0xE0
84#define ST7735_GMCTRN1 0xE1
85#define ST7735_PWCTR6 0xFC
86
87// MADCTL Bits (See page 215: MADCTL (36h): Memory Data Access Control)
88#define ST7735_MADCTL_ROW_ORDER 0b10000000
89#define ST7735_MADCTL_COL_ORDER 0b01000000
90#define ST7735_MADCTL_SWAP_XY 0b00100000
91#define ST7735_MADCTL_SCAN_ORDER 0b00010000
92#define ST7735_MADCTL_RGB_BGR 0b00001000
93#define ST7735_MADCTL_HORIZ_ORDER 0b00000100
94
95#define ST7735_INVCTR_NLA 0b00000100 // Inversion setting in full Colors normal mode
96#define ST7735_INVCTR_NLB 0b00000010 // Inversion setting in Idle mode
97#define ST7735_INVCTR_NLC 0b00000001 // Inversion setting in full Colors partial mode
98
99// clang-format on
100
101constexpr uint8_t HighByte(uint16_t val) { return val >> 8; }
102
103constexpr uint8_t LowByte(uint16_t val) { return val & 0xff; }
104
105} // namespace
106
107// The ST7735 supports a max display size of 162x132. This was developed with
108// a ST7735 development board with a 160x128 pixel screen - hence the row/col
109// start values. These should be parameterized.
110DisplayDriverST7735::DisplayDriverST7735(const Config& config)
111 : config_(config), row_start_(2), col_start_(1) {}
112
113void DisplayDriverST7735::SetMode(Mode mode) {
114 // Set the D/CX pin to indicate data or command values.
115 if (mode == Mode::kData) {
116 config_.data_cmd_gpio.SetState(State::kActive);
117 } else {
118 config_.data_cmd_gpio.SetState(State::kInactive);
119 }
120}
121
122Status DisplayDriverST7735::WriteCommand(Device::Transaction& transaction,
123 const Command& command) {
124 SetMode(Mode::kCommand);
125 byte buff[1]{static_cast<byte>(command.command)};
126 auto s = transaction.Write(buff);
127 if (!s.ok())
128 return s;
129
130 SetMode(Mode::kData);
131 if (command.command_data.empty()) {
132 return OkStatus();
133 }
134 return transaction.Write(command.command_data);
135}
136
137Status DisplayDriverST7735::Init() {
138 auto transaction = config_.spi_device_8_bit.StartTransaction(
139 ChipSelectBehavior::kPerWriteRead);
140
141 WriteCommand(transaction, {ST7735_SWRESET, kEmptyArray}); // Software reset
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000142 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(150ms));
Chris Mumford34270472023-01-10 21:29:08 +0000143 WriteCommand(transaction, {ST7735_SLPOUT, kEmptyArray});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000144 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(500ms));
Chris Mumford34270472023-01-10 21:29:08 +0000145
146 WriteCommand(transaction,
147 {ST7735_FRMCTR1,
148 array<byte, 3>{
149 byte{0x00},
150 byte{0x06},
151 byte{0x03},
152 }});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000153 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000154
155 WriteCommand(transaction,
156 {ST7735_DISSET5,
157 array<byte, 2>{
158 byte{0x15},
159 byte{0x02},
160 }});
161
162 constexpr uint8_t inversion_val =
163 ST7735_INVCTR_NLA | ST7735_INVCTR_NLB | ST7735_INVCTR_NLC;
164
165 WriteCommand(transaction,
166 {ST7735_INVCTR,
167 array<byte, 1>{
168 byte{inversion_val},
169 }});
170
171 WriteCommand(transaction, {ST7735_TEON, kEmptyArray});
172 WriteCommand(transaction, {ST7735_COLMOD, array<byte, 1>{byte{0x05}}});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000173 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000174
175 WriteCommand(transaction,
176 {ST7735_PORCTRL,
177 array<byte, 5>{
178 byte{0x0c},
179 byte{0x0c},
180 byte{0x00},
181 byte{0x33},
182 byte{0x33},
183 }});
184 WriteCommand(transaction,
185 {ST7735_PWCTR1,
186 array<byte, 2>{
187 byte{0x02}, // GVDD = 4.7V.
188 byte{0x70}, // 1.0uA.
189 }});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000190 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000191 WriteCommand(transaction,
192 {ST7735_PWCTR2,
193 array<byte, 1>{
194 byte{0x05},
195 }});
196 WriteCommand(transaction,
197 {ST7735_PWCTR3,
198 array<byte, 2>{
199 byte{0x01},
200 byte{0x02},
201 }});
202 WriteCommand(transaction,
203 {ST7735_VMCTR1,
204 array<byte, 2>{
205 byte{0x3c},
206 byte{0x38},
207 }});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000208 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000209 WriteCommand(transaction,
210 {ST7735_PWCTR6,
211 array<byte, 2>{
212 byte{0x11},
213 byte{0x15},
214 }});
215
216 WriteCommand(transaction, {ST7735_VRHS, array<byte, 1>{byte{0x12}}});
217 WriteCommand(transaction, {ST7735_VDVS, array<byte, 1>{byte{0x20}}});
218 WriteCommand(transaction,
219 {ST7735_PWCTRL1,
220 array<byte, 2>{
221 byte{0xa4},
222 byte{0xa1},
223 }});
224 WriteCommand(transaction, {ST7735_FRCTRL2, array<byte, 1>{byte{0x0f}}});
225
226 WriteCommand(transaction, {ST7735_INVOFF, kEmptyArray});
227
228 WriteCommand(transaction,
229 {ST7735_GMCTRP1,
230 array<byte, 16>{
231 byte{0x09},
232 byte{0x16},
233 byte{0x09},
234 byte{0x20},
235 byte{0x21},
236 byte{0x1B},
237 byte{0x13},
238 byte{0x19},
239 byte{0x17},
240 byte{0x15},
241 byte{0x1E},
242 byte{0x2B},
243 byte{0x04},
244 byte{0x05},
245 byte{0x02},
246 byte{0x0E},
247 }});
248 WriteCommand(transaction,
249 {ST7735_GMCTRN1,
250 array<byte, 16>{
251 byte{0x0B},
252 byte{0x14},
253 byte{0x08},
254 byte{0x1E},
255 byte{0x22},
256 byte{0x1D},
257 byte{0x18},
258 byte{0x1E},
259 byte{0x1B},
260 byte{0x1A},
261 byte{0x24},
262 byte{0x2B},
263 byte{0x06},
264 byte{0x06},
265 byte{0x02},
266 byte{0x0F},
267 }});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000268 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000269
270 // Landscape drawing Column Address Set
271 const uint16_t max_column = config_.screen_width + col_start_ - 1;
272 WriteCommand(transaction,
273 {ST7735_CASET,
274 array<byte, 4>{
275 byte{HighByte(col_start_)},
276 byte{LowByte(col_start_)},
277 byte{HighByte(max_column)},
278 byte{LowByte(max_column)},
279 }});
280
281 // Page Address Set
282 const uint16_t max_row = config_.screen_height + row_start_ - 1;
283 WriteCommand(transaction,
284 {ST7735_RASET,
285 array<byte, 4>{
286 byte{HighByte(row_start_)},
287 byte{LowByte(row_start_)},
288 byte{HighByte(max_row)},
289 byte{LowByte(max_row)},
290 }});
291
292 constexpr bool rotate_180 = false;
293 uint8_t madctl = ST7735_MADCTL_COL_ORDER;
294 if (rotate_180)
295 madctl = ST7735_MADCTL_ROW_ORDER;
296 madctl |= ST7735_MADCTL_SWAP_XY | ST7735_MADCTL_SCAN_ORDER;
297 WriteCommand(transaction, {ST7735_MADCTL, array<byte, 1>{byte{madctl}}});
298
299 WriteCommand(transaction, {ST7735_NORON, kEmptyArray});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000300 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
Chris Mumford34270472023-01-10 21:29:08 +0000301
302 WriteCommand(transaction, {ST7735_DISPON, kEmptyArray});
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000303 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(500ms));
Chris Mumford34270472023-01-10 21:29:08 +0000304
305 return OkStatus();
306}
307
Chris Mumford8db6cc12023-05-02 11:57:18 +0000308void DisplayDriverST7735::WriteFramebuffer(Framebuffer framebuffer,
309 WriteCallback write_callback) {
310 PW_ASSERT(framebuffer.is_valid());
311 PW_ASSERT(framebuffer.pixel_format() == PixelFormat::RGB565);
312 Status s;
Chris Mumford34270472023-01-10 21:29:08 +0000313 // Let controller know a write is coming.
314 {
315 auto transaction = config_.spi_device_8_bit.StartTransaction(
316 ChipSelectBehavior::kPerWriteRead);
Chris Mumford8db6cc12023-05-02 11:57:18 +0000317 s = WriteCommand(transaction, {ST7735_RAMWR, kEmptyArray});
318 if (!s.ok()) {
319 write_callback(std::move(framebuffer), s);
320 return;
321 }
Chris Mumford34270472023-01-10 21:29:08 +0000322 }
323
324 // Write the pixel data.
325 auto transaction = config_.spi_device_16_bit.StartTransaction(
326 ChipSelectBehavior::kPerWriteRead);
Chris Mumford8db6cc12023-05-02 11:57:18 +0000327 const uint16_t* fb_data = static_cast<const uint16_t*>(framebuffer.data());
Chris Mumford79baf952023-03-08 16:32:16 +0000328 const size_t num_pixels = config_.screen_width * config_.screen_height;
Chris Mumford8db6cc12023-05-02 11:57:18 +0000329 s = transaction.Write(
Chris Mumford34270472023-01-10 21:29:08 +0000330 ConstByteSpan(reinterpret_cast<const byte*>(fb_data), num_pixels));
Chris Mumford8db6cc12023-05-02 11:57:18 +0000331 write_callback(std::move(framebuffer), s);
332}
333
334Status DisplayDriverST7735::WriteRow(span<uint16_t> row_pixels,
335 uint16_t row_idx,
336 uint16_t col_idx) {
337 PW_ASSERT(false);
338 return Status::Unimplemented();
Chris Mumford34270472023-01-10 21:29:08 +0000339}
340
341Status DisplayDriverST7735::Reset() {
342 if (!config_.reset_gpio)
343 return Status::Unavailable();
Chris Mumford34270472023-01-10 21:29:08 +0000344 PW_TRY(config_.reset_gpio->SetStateInactive());
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000345 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
Chris Mumford34270472023-01-10 21:29:08 +0000346 PW_TRY(config_.reset_gpio->SetStateActive());
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000347 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
Anthony DiGirolamo0c1e0ce2024-02-13 21:03:05 +0000348 PW_TRY(config_.reset_gpio->SetStateInactive());
Anthony DiGirolamof0badda2024-03-29 22:34:21 +0000349 pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
Chris Mumford34270472023-01-10 21:29:08 +0000350 return OkStatus();
351}
352
353} // namespace pw::display_driver