1 /*
2  * Copyright (c) 2006-2023, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2023-05-23     Chushicheng  the first version
9  */
10 
11 #include "drv_spi.h"
12 #include "drv_st7796.h"
13 
14 #define LOG_TAG             "drv.st7796"
15 #include <drv_log.h>
16 
17 #ifndef ST7796_LCD_IPS_PANEL
18 #define ST7796_LCD_IPS_PANEL 1
19 #else
20 #define ST7796_LCD_IPS_PANEL 0
21 #endif
22 
23 #define ST7796_INIT_SEQ_NAME(x) st7796_init_seq_##x##_480_320
24 #if ST7796_LCD_IPS_PANEL
25 #define ST7796_LCD_INIT_SEQ ST7796_INIT_SEQ_NAME(ips)
26 #else
27 #define ST7796_LCD_INIT_SEQ ST7796_INIT_SEQ_NAME(tft)
28 #endif
29 
30 static st7796_t lcd_spi_obj;
31 
32 static rt_err_t _st7796_init_seq(st7796_lcd_t *lcd);
33 static rt_err_t _st7796_window(st7796_lcd_t *lcd, rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end);
34 static rt_err_t _st7796_reset(st7796_lcd_t *lcd);
35 static rt_err_t st7796_lcd_load(st7796_lcd_t *lcd, void *data, rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end);
36 static rt_err_t st7796_lcd_sleep(st7796_lcd_t *lcd, rt_uint8_t sleep_mode);
37 static rt_err_t st7796_lcd_display(st7796_lcd_t *lcd, rt_uint8_t display_on);
38 static rt_err_t st7796_lcd_config(st7796_lcd_t *lcd, st7796_config_t *config);
39 void lcd_load(rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end, void *data);
40 
41 static rt_uint8_t st7796_init_seq_tft_480_320[] =
42 {
43     0x01, 0xF0, 0xC3,                                            /* Enable command part 1 */
44     0x01, 0xF0, 0x96,                                            /* Enable command part 2*/
45     0x08, 0xE8, 0x40, 0x82, 0x07, 0x18, 0x27, 0x0A, 0xB6, 0x33,  /* DOCA */
46     0x01, 0xC5, 0x27,                                            /* VCOM control */
47     0x01, 0xC2, 0xA7,                                            /* Power control 3 */
48     0x0E, 0xE0, 0xF0, 0x01, 0x06, 0x0F, 0x12, 0x1D, 0x36, 0x54, 0x44, 0x0C, 0x18, 0x16, 0x13, 0x15,  /* PGC */
49     0x0E, 0xE1, 0xF0, 0x01, 0x05, 0x0A, 0x0B, 0x07, 0x32, 0x44, 0x44, 0x0C, 0x18, 0x17, 0x13, 0x16,  /* NGC */
50     0x01, 0xF0, 0x3C,  /* Disable command part 1 */
51     0x01, 0xF0, 0x69,  /* Disable command part 2 */
52 };
53 
54 static rt_uint8_t st7796_init_seq_ips_480_320[] =
55 {
56     0x01, 0xF0, 0xC3,                                            /* Enable command part 1 */
57     0x01, 0xF0, 0x96,                                            /* Enable command part 2 */
58     0x01, 0xB4, 0x01,                                            /* Display inversion */
59     0x02, 0xB1, 0x80, 0x10,                                      /* Frame rate control 1 */
60     0x04, 0xB5, 0x1F, 0x50, 0x00, 0x20,                          /* Blanking porch control */
61     0x03, 0xB6, 0x8A, 0x07, 0x3B,                                /* Display function control */
62     0x02, 0xC0, 0x80, 0x64,                                      /* Power control 1 */
63     0x01, 0xC1, 0x13,                                            /* Power control 2 */
64     0x01, 0xC2, 0xA7,                                            /* Power control 3 */
65     0x01, 0xC5, 0x09,                                            /* VCOM control */
66     0x08, 0xE8, 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33,  /* DOCA */
67     0x0E, 0xE0, 0xF0, 0x06, 0x0B, 0x07, 0x06, 0x05, 0x2E, 0x33, 0x47, 0x3A, 0x17, 0x16, 0x2E, 0x31,  /* PGC */
68     0x0E, 0xE1, 0xF0, 0x09, 0x0D, 0x09, 0x08, 0x23, 0x2E, 0x33, 0x46, 0x38, 0x13, 0x13, 0x2C, 0x32,  /* NGC */
69     0x01, 0xF0, 0x3C,  /* Disable command part 1 */
70     0x01, 0xF0, 0x69,  /* Disable command part 2 */
71 };
72 
_st7796_init_seq(st7796_lcd_t * lcd)73 static rt_err_t _st7796_init_seq(st7796_lcd_t *lcd)
74 {
75     rt_uint16_t i = 0;
76 
77     while (i < sizeof(ST7796_LCD_INIT_SEQ))
78     {
79         if (lcd->cb.write_cmd_cb(lcd->user_data, &ST7796_LCD_INIT_SEQ[i + 1], ST7796_LCD_INIT_SEQ[i] + 1) != RT_EOK)
80         {
81             return -RT_ERROR;
82         };
83         i += ST7796_LCD_INIT_SEQ[i] + 2;
84     }
85 
86     return RT_EOK;
87 }
88 
_st7796_window(st7796_lcd_t * lcd,rt_uint16_t x_start,rt_uint16_t x_end,rt_uint16_t y_start,rt_uint16_t y_end)89 static rt_err_t _st7796_window(st7796_lcd_t *lcd, rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end)
90 {
91     rt_uint16_t real_x_start, real_x_end, real_y_start, real_y_end;
92     rt_uint16_t x_offset, y_offset;
93     switch (lcd->config.direction)
94     {
95         case ST7796_DIR_0:
96             x_offset = 0;
97             y_offset = 0;
98             break;
99         case ST7796_DIR_90:
100             x_offset = 0;
101             y_offset = 0;
102             break;
103         case ST7796_DIR_180:
104             x_offset = 320;
105             y_offset = 480;
106             break;
107         case ST7796_DIR_270:
108             x_offset = 480;
109             y_offset = 320;
110             break;
111         default:
112             x_offset = 0;
113             y_offset = 0;
114     }
115     real_x_start = x_start + x_offset;
116     real_x_end   = x_end + x_offset;
117     real_y_start = y_start + y_offset;
118     real_y_end   = y_end + y_offset;
119 
120     rt_uint8_t tx_buf[5] = {0x2A, ((rt_uint8_t)(real_x_start >> 0x08U) & 0xFFU), (real_x_start & 0xFFU),
121                          ((rt_uint8_t)(real_x_end >> 0x08U) & 0xFFU), (real_x_end & 0xFFU)};
122 
123     if (lcd->cb.write_cmd_cb(lcd->user_data, tx_buf, 0x05) != RT_EOK)
124     {
125         return -RT_ERROR;
126     }
127     tx_buf[0] = 0x2B;
128     tx_buf[1] = ((rt_uint8_t)(real_y_start >> 0x08U) & 0xFFU);
129     tx_buf[2] = (real_y_start & 0xFFU);
130     tx_buf[3] = ((rt_uint8_t)(real_y_end >> 0x08U) & 0xFFU);
131     tx_buf[4] = (real_y_end & 0xFFU);
132     if (lcd->cb.write_cmd_cb(lcd->user_data, tx_buf, 0x05) != RT_EOK)
133     {
134         return -RT_ERROR;
135     }
136 
137     return RT_EOK;
138 }
139 
_st7796_reset(st7796_lcd_t * lcd)140 static rt_err_t _st7796_reset(st7796_lcd_t *lcd)
141 {
142     return lcd->cb.reset_cb(lcd->user_data);
143 }
144 
lcd_impl_reset(void * handle)145 static rt_err_t lcd_impl_reset(void *handle)
146 {
147     rt_pin_write(BSP_LCD_RST_PIN, PIN_LOW);
148     rt_thread_mdelay(50);
149     rt_pin_write(BSP_LCD_RST_PIN, PIN_HIGH);
150     rt_thread_mdelay(50);
151 
152     return RT_EOK;
153 }
154 
st7796_lcd_init(st7796_lcd_t * lcd)155 static rt_err_t st7796_lcd_init(st7796_lcd_t *lcd)
156 {
157     if (_st7796_reset(lcd) != RT_EOK) return -RT_ERROR;
158     if (_st7796_init_seq(lcd) != RT_EOK) return -RT_ERROR;
159     if (st7796_lcd_config(lcd, &lcd->config) != RT_EOK) return -RT_ERROR;
160     if (st7796_lcd_sleep(lcd, 0) != RT_EOK) return -RT_ERROR;
161     if (st7796_lcd_display(lcd, 1) != RT_EOK) return -RT_ERROR;
162 
163     return RT_EOK;
164 }
165 
st7796_lcd_load(st7796_lcd_t * lcd,void * data,rt_uint16_t x_start,rt_uint16_t x_end,rt_uint16_t y_start,rt_uint16_t y_end)166 static rt_err_t st7796_lcd_load(st7796_lcd_t *lcd, void *data, rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end)
167 {
168     rt_uint32_t pixel_count = (y_end - y_start + 1) * (x_end - x_start + 1);
169     rt_uint32_t data_len = 0;
170 
171     switch (lcd->config.pix_fmt)
172     {
173         case ST7796_RGB444:
174             data_len = pixel_count * 3 / 2;
175             break;
176         case ST7796_RGB565:
177             data_len = pixel_count * 2;
178             break;
179         case ST7796_RGB666:
180         case ST7796_RGB888:
181             data_len = pixel_count * 3;
182             break;
183         default:
184             data_len = pixel_count;
185             break;
186     }
187 
188     /* Set cursor */
189     if (_st7796_window(lcd, x_start, x_end, y_start, y_end) != RT_EOK)
190     {
191         return -RT_ERROR;
192     }
193 
194     rt_uint8_t command = 0x2C;  /* Memory Write */
195     if (lcd->cb.write_cmd_cb(lcd->user_data, &command, 0x01) != RT_EOK)
196     {
197         return -RT_ERROR;
198     }
199     /* Write pixel data */
200     if (lcd->cb.write_data_cb(lcd->user_data, data, data_len) != RT_EOK)
201     {
202         return -RT_ERROR;
203     }
204 
205     return RT_EOK;
206 }
207 
lcd_load(rt_uint16_t x_start,rt_uint16_t x_end,rt_uint16_t y_start,rt_uint16_t y_end,void * data)208 void lcd_load(rt_uint16_t x_start, rt_uint16_t x_end, rt_uint16_t y_start, rt_uint16_t y_end, void *data)
209 {
210     st7796_t *lcd_obj = (st7796_t *)rt_device_find("lcd");
211     st7796_lcd_load(&lcd_obj->st7796, data, x_start, x_end, y_start, y_end);
212 }
213 
st7796_lcd_sleep(st7796_lcd_t * lcd,rt_uint8_t sleep_mode)214 static rt_err_t st7796_lcd_sleep(st7796_lcd_t *lcd, rt_uint8_t sleep_mode)
215 {
216     /* Write SLPIN or SLPOUT command */
217     rt_uint8_t command = sleep_mode ? 0x10 : 0x11;
218     return lcd->cb.write_cmd_cb(lcd->user_data, &command, 0x01);
219 }
220 
st7796_lcd_display(st7796_lcd_t * lcd,rt_uint8_t display_on)221 static rt_err_t st7796_lcd_display(st7796_lcd_t *lcd, rt_uint8_t display_on)
222 {
223     /* write display_on command */
224     rt_uint8_t command = display_on ? 0x29 : 0x28;
225     if (lcd->cb.write_cmd_cb(lcd->user_data, &command, 0x01) != RT_EOK)
226     {
227         return -RT_ERROR;
228     }
229     if ((lcd->cb.backlight_cb != NULL) && (lcd->cb.backlight_cb(lcd->user_data, display_on) != RT_EOK))
230     {
231         return -RT_ERROR;
232     }
233 
234     return RT_EOK;
235 }
236 
st7796_lcd_config(st7796_lcd_t * lcd,st7796_config_t * config)237 static rt_err_t st7796_lcd_config(st7796_lcd_t *lcd, st7796_config_t *config)
238 {
239     lcd->config.direction = config->direction;
240 
241     /* Write inversion command */
242     rt_uint8_t command[2] = {config->inversion ? 0x20 : 0x21, 0x00};
243     if (lcd->cb.write_cmd_cb(lcd->user_data, command, 0x01) != RT_EOK)
244     {
245         return -RT_ERROR;
246     }
247     lcd->config.inversion = config->inversion;
248 
249     command[0] = 0x3A;
250     command[1] = config->pix_fmt;
251     if (lcd->cb.write_cmd_cb(lcd->user_data, command, 0x02) != RT_EOK)
252     {
253         return -RT_ERROR;
254     }
255     lcd->config.pix_fmt = config->pix_fmt;
256 
257     command[0] = 0x36;
258     command[1] = config->direction;
259     if (!config->bgr_mode)
260     {
261         command[1] &= ~0x08U;
262     }
263 
264     if (config->mirrored)
265     {
266         /* Invert X or Y bit */
267         if (config->direction == ST7796_DIR_90 || config->direction == ST7796_DIR_270)
268         {
269             command[1] ^= 0x80U;
270         }
271         else
272         {
273             command[1] ^= 0x40U;
274         }
275     }
276 
277     return lcd->cb.write_cmd_cb(lcd->user_data, command, 0x02);
278 }
279 
lcd_impl_write_cmd(void * handle,rt_uint8_t * cmd,rt_uint8_t len)280 static rt_err_t lcd_impl_write_cmd(void *handle, rt_uint8_t *cmd, rt_uint8_t len)
281 {
282     st7796_t *nxp_lcd = (st7796_t*)handle;
283 
284     rt_pin_write(BSP_LCD_DC_PIN, PIN_LOW);
285     rt_spi_send(nxp_lcd->spi_dev, cmd, 1);
286     if (len > 1)
287     {
288         rt_pin_write(BSP_LCD_DC_PIN, PIN_HIGH);
289         rt_spi_send(nxp_lcd->spi_dev, &cmd[1], len-1);
290     }
291 
292     return RT_EOK;
293 }
294 
lcd_impl_write_data(void * handle,void * data,rt_uint32_t len)295 static rt_err_t lcd_impl_write_data(void *handle, void *data, rt_uint32_t len)
296 {
297     st7796_t *nxp_lcd = (st7796_t*)handle;
298 
299     rt_pin_write(BSP_LCD_DC_PIN, PIN_HIGH);
300     rt_spi_send(nxp_lcd->spi_dev, data, len);
301 
302     return RT_EOK;
303 }
304 
drv_st7796_init(void)305 int drv_st7796_init(void)
306 {
307     rt_pin_mode(BSP_LCD_RST_PIN, PIN_MODE_OUTPUT);
308     rt_pin_mode(BSP_LCD_DC_PIN, PIN_MODE_OUTPUT);
309     rt_pin_write(BSP_LCD_RST_PIN, PIN_HIGH);
310 
311     lcd_spi_obj.st7796.config.direction = ST7796_DIR_90;
312     lcd_spi_obj.st7796.config.pix_fmt = ST7796_RGB565;
313     lcd_spi_obj.st7796.config.bgr_mode = 1;
314     lcd_spi_obj.st7796.config.inversion = 0;
315     lcd_spi_obj.st7796.config.mirrored = 1;
316     lcd_spi_obj.st7796.cb.reset_cb = lcd_impl_reset;
317     lcd_spi_obj.st7796.cb.write_cmd_cb = lcd_impl_write_cmd;
318     lcd_spi_obj.st7796.cb.write_data_cb = lcd_impl_write_data;
319     lcd_spi_obj.st7796.user_data = lcd_spi_obj.parent.user_data = &lcd_spi_obj;
320 
321     rt_hw_spi_device_attach(BSP_LCD_SPI_BUS, LCD_DEVICE_NAME, BSP_LCD_CS_PIN);
322     lcd_spi_obj.spi_dev = (struct rt_spi_device *)rt_device_find(LCD_DEVICE_NAME);
323     if (!lcd_spi_obj.spi_dev)
324     {
325         LOG_E("lcd init run failed! can't find %s device!", LCD_DEVICE_NAME);
326         return -RT_ERROR;
327     }
328     struct rt_spi_configuration cfg;
329     cfg.data_width = 8;
330     cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */
331     cfg.max_hz = 10 * 1000 * 1000; /* 10M */
332     rt_spi_configure(lcd_spi_obj.spi_dev, &cfg);
333 
334     st7796_lcd_init(&lcd_spi_obj.st7796);
335     rt_device_register(&lcd_spi_obj.parent, "lcd", RT_DEVICE_FLAG_RDWR);
336 
337     return RT_EOK;
338 }
339 INIT_ENV_EXPORT(drv_st7796_init);
340