1 /*
2  * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
8 
9 #include <stdlib.h>
10 #include <sys/cdefs.h>
11 #include "freertos/FreeRTOS.h"
12 #include "freertos/task.h"
13 #include "esp_lcd_panel_interface.h"
14 #include "esp_lcd_panel_io.h"
15 #include "esp_lcd_panel_vendor.h"
16 #include "esp_lcd_panel_ops.h"
17 #include "esp_lcd_panel_commands.h"
18 #include "driver/gpio.h"
19 #include "esp_log.h"
20 #include "esp_check.h"
21 
22 static const char *TAG = "lcd_panel.st7789";
23 
24 static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel);
25 static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel);
26 static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel);
27 static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
28 static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
29 static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
30 static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
31 static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
32 static esp_err_t panel_st7789_disp_off(esp_lcd_panel_t *panel, bool off);
33 
34 typedef struct {
35     esp_lcd_panel_t base;
36     esp_lcd_panel_io_handle_t io;
37     int reset_gpio_num;
38     bool reset_level;
39     int x_gap;
40     int y_gap;
41     unsigned int bits_per_pixel;
42     uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
43     uint8_t colmod_cal; // save surrent value of LCD_CMD_COLMOD register
44 } st7789_panel_t;
45 
esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io,const esp_lcd_panel_dev_config_t * panel_dev_config,esp_lcd_panel_handle_t * ret_panel)46 esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
47 {
48     esp_err_t ret = ESP_OK;
49     st7789_panel_t *st7789 = NULL;
50     ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
51     st7789 = calloc(1, sizeof(st7789_panel_t));
52     ESP_GOTO_ON_FALSE(st7789, ESP_ERR_NO_MEM, err, TAG, "no mem for st7789 panel");
53 
54     if (panel_dev_config->reset_gpio_num >= 0) {
55         gpio_config_t io_conf = {
56             .mode = GPIO_MODE_OUTPUT,
57             .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
58         };
59         ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
60     }
61 
62     switch (panel_dev_config->color_space) {
63     case ESP_LCD_COLOR_SPACE_RGB:
64         st7789->madctl_val = 0;
65         break;
66     case ESP_LCD_COLOR_SPACE_BGR:
67         st7789->madctl_val |= LCD_CMD_BGR_BIT;
68         break;
69     default:
70         ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
71         break;
72     }
73 
74     switch (panel_dev_config->bits_per_pixel) {
75     case 16:
76         st7789->colmod_cal = 0x55;
77         break;
78     case 18:
79         st7789->colmod_cal = 0x66;
80         break;
81     default:
82         ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
83         break;
84     }
85 
86     st7789->io = io;
87     st7789->bits_per_pixel = panel_dev_config->bits_per_pixel;
88     st7789->reset_gpio_num = panel_dev_config->reset_gpio_num;
89     st7789->reset_level = panel_dev_config->flags.reset_active_high;
90     st7789->base.del = panel_st7789_del;
91     st7789->base.reset = panel_st7789_reset;
92     st7789->base.init = panel_st7789_init;
93     st7789->base.draw_bitmap = panel_st7789_draw_bitmap;
94     st7789->base.invert_color = panel_st7789_invert_color;
95     st7789->base.set_gap = panel_st7789_set_gap;
96     st7789->base.mirror = panel_st7789_mirror;
97     st7789->base.swap_xy = panel_st7789_swap_xy;
98     st7789->base.disp_off = panel_st7789_disp_off;
99     *ret_panel = &(st7789->base);
100     ESP_LOGD(TAG, "new st7789 panel @%p", st7789);
101 
102     return ESP_OK;
103 
104 err:
105     if (st7789) {
106         if (panel_dev_config->reset_gpio_num >= 0) {
107             gpio_reset_pin(panel_dev_config->reset_gpio_num);
108         }
109         free(st7789);
110     }
111     return ret;
112 }
113 
panel_st7789_del(esp_lcd_panel_t * panel)114 static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel)
115 {
116     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
117 
118     if (st7789->reset_gpio_num >= 0) {
119         gpio_reset_pin(st7789->reset_gpio_num);
120     }
121     ESP_LOGD(TAG, "del st7789 panel @%p", st7789);
122     free(st7789);
123     return ESP_OK;
124 }
125 
panel_st7789_reset(esp_lcd_panel_t * panel)126 static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel)
127 {
128     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
129     esp_lcd_panel_io_handle_t io = st7789->io;
130 
131     // perform hardware reset
132     if (st7789->reset_gpio_num >= 0) {
133         gpio_set_level(st7789->reset_gpio_num, st7789->reset_level);
134         vTaskDelay(pdMS_TO_TICKS(10));
135         gpio_set_level(st7789->reset_gpio_num, !st7789->reset_level);
136         vTaskDelay(pdMS_TO_TICKS(10));
137     } else { // perform software reset
138         esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0);
139         vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5m before sending new command
140     }
141 
142     return ESP_OK;
143 }
144 
panel_st7789_init(esp_lcd_panel_t * panel)145 static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel)
146 {
147     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
148     esp_lcd_panel_io_handle_t io = st7789->io;
149     // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
150     esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0);
151     vTaskDelay(pdMS_TO_TICKS(100));
152     esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
153         st7789->madctl_val,
154     }, 1);
155     esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
156         st7789->colmod_cal,
157     }, 1);
158     // turn on display
159     esp_lcd_panel_io_tx_param(io, LCD_CMD_DISPON, NULL, 0);
160 
161     return ESP_OK;
162 }
163 
panel_st7789_draw_bitmap(esp_lcd_panel_t * panel,int x_start,int y_start,int x_end,int y_end,const void * color_data)164 static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
165 {
166     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
167     assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
168     esp_lcd_panel_io_handle_t io = st7789->io;
169 
170     x_start += st7789->x_gap;
171     x_end += st7789->x_gap;
172     y_start += st7789->y_gap;
173     y_end += st7789->y_gap;
174 
175     // define an area of frame memory where MCU can access
176     esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) {
177         (x_start >> 8) & 0xFF,
178         x_start & 0xFF,
179         ((x_end - 1) >> 8) & 0xFF,
180         (x_end - 1) & 0xFF,
181     }, 4);
182     esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) {
183         (y_start >> 8) & 0xFF,
184         y_start & 0xFF,
185         ((y_end - 1) >> 8) & 0xFF,
186         (y_end - 1) & 0xFF,
187     }, 4);
188     // transfer frame buffer
189     size_t len = (x_end - x_start) * (y_end - y_start) * st7789->bits_per_pixel / 8;
190     esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len);
191 
192     return ESP_OK;
193 }
194 
panel_st7789_invert_color(esp_lcd_panel_t * panel,bool invert_color_data)195 static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
196 {
197     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
198     esp_lcd_panel_io_handle_t io = st7789->io;
199     int command = 0;
200     if (invert_color_data) {
201         command = LCD_CMD_INVON;
202     } else {
203         command = LCD_CMD_INVOFF;
204     }
205     esp_lcd_panel_io_tx_param(io, command, NULL, 0);
206     return ESP_OK;
207 }
208 
panel_st7789_mirror(esp_lcd_panel_t * panel,bool mirror_x,bool mirror_y)209 static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
210 {
211     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
212     esp_lcd_panel_io_handle_t io = st7789->io;
213     if (mirror_x) {
214         st7789->madctl_val |= LCD_CMD_MX_BIT;
215     } else {
216         st7789->madctl_val &= ~LCD_CMD_MX_BIT;
217     }
218     if (mirror_y) {
219         st7789->madctl_val |= LCD_CMD_MY_BIT;
220     } else {
221         st7789->madctl_val &= ~LCD_CMD_MY_BIT;
222     }
223     esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
224         st7789->madctl_val
225     }, 1);
226     return ESP_OK;
227 }
228 
panel_st7789_swap_xy(esp_lcd_panel_t * panel,bool swap_axes)229 static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
230 {
231     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
232     esp_lcd_panel_io_handle_t io = st7789->io;
233     if (swap_axes) {
234         st7789->madctl_val |= LCD_CMD_MV_BIT;
235     } else {
236         st7789->madctl_val &= ~LCD_CMD_MV_BIT;
237     }
238     esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
239         st7789->madctl_val
240     }, 1);
241     return ESP_OK;
242 }
243 
panel_st7789_set_gap(esp_lcd_panel_t * panel,int x_gap,int y_gap)244 static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
245 {
246     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
247     st7789->x_gap = x_gap;
248     st7789->y_gap = y_gap;
249     return ESP_OK;
250 }
251 
panel_st7789_disp_off(esp_lcd_panel_t * panel,bool off)252 static esp_err_t panel_st7789_disp_off(esp_lcd_panel_t *panel, bool off)
253 {
254     st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
255     esp_lcd_panel_io_handle_t io = st7789->io;
256     int command = 0;
257     if (off) {
258         command = LCD_CMD_DISPOFF;
259     } else {
260         command = LCD_CMD_DISPON;
261     }
262     esp_lcd_panel_io_tx_param(io, command, NULL, 0);
263     return ESP_OK;
264 }
265