├── LICENSE
├── README.md
├── include
├── lcd.h
├── led.h
└── systick.h
├── platformio.ini
└── src
├── amigaball.inl
├── lcd.c
├── led.c
├── main.c
├── starfield.inl
└── systick.c
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019-2020, Samuli Laine
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gd32v-lcd
2 | LCD library with DMA support for Sipeed Longan Nano (GD32VF103). Also supports setting up automated framebuffer refresh in background.
3 |
4 | # Overview
5 | The official example repository for GD32V has a rudimentary [LCD library](https://github.com/sipeed/Longan_GD32VF_examples/blob/master/gd32v_lcd/src/lcd/lcd.c) but the support for DMA transfers appears to be unfinished and I could not get it working. Therefore, I decided to roll my own. This project contains a DMA-enabled LCD library with all bulk operations such as buffer reads and writes running asynchronously in background for maximum performance. The display controller interface is similar to ST7735 [(Datasheet PDF)](https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf) in case you're looking for low-level documentation.
6 |
7 | This library also supports setting up continuous automatic framebuffer upload in background so that you can effectively use the display as a memory mapped device. Unfortunately, there is no vertical sync pin connected on Sipeed Longan Nano, so avoiding tearing is not possible without hardware modifications (assuming that the LCD controller is similar to ST7735 in this regard).
8 |
9 | There are two simple graphics effects in the main file, one demonstrating on-demand framebuffer upload, and one demonstrating automatic background refresh.
10 |
11 |
12 |
13 |
14 |  |
15 |  |
16 |
17 |
18 | Amiga ball |
19 | Starfield |
20 |
21 |
22 |
23 |
24 | Thanks to [Kevin Sangeelee](https://github.com/Kevin-Sangeelee) for the comprehensive [blog post](https://www.susa.net/wordpress/2019/10/longan-nano-gd32vf103) on GD32VF103 that was very helpful for me in understanding how to set up the interrupts on this hardware.
25 |
--------------------------------------------------------------------------------
/include/lcd.h:
--------------------------------------------------------------------------------
1 | #ifndef __LCD_H__
2 | #define __LCD_H__
3 |
4 | // ------------------------------------------------------------------------
5 |
6 | #define LCD_WIDTH 160
7 | #define LCD_HEIGHT 80
8 | #define LCD_FRAMEBUFFER_PIXELS (LCD_WIDTH * LCD_HEIGHT)
9 | #define LCD_FRAMEBUFFER_BYTES (LCD_WIDTH * LCD_HEIGHT * 2)
10 |
11 | // ------------------------------------------------------------------------
12 | // Basic functions. All functions are asynchronous, i.e., they return while
13 | // bulk of the operation is running using DMA. They all synchronize between
14 | // themselves, but lcd_wait() should be called explicitly before accessing
15 | // the buffer used by read/write operations. Otherwise there will be race
16 | // conditions between DMA and CPU accessing the data.
17 | // ------------------------------------------------------------------------
18 |
19 | void lcd_init (void);
20 | void lcd_clear (unsigned short int color); // All colors are RGB565.
21 | void lcd_setpixel (int x, int y, unsigned short int color);
22 | void lcd_fill_rect (int x, int y, int w, int h, unsigned short int color);
23 | void lcd_rect (int x, int y, int w, int h, unsigned short int color);
24 | void lcd_write_u16 (int x, int y, int w, int h, const void* buffer); // Buffer size = w*h*2 bytes.
25 | void lcd_write_u24 (int x, int y, int w, int h, const void* buffer); // Buffer size = w*h*3 bytes.
26 | void lcd_read_u24 (int x, int y, int w, int h, void* buffer); // Buffer size = w*h*3 bytes.
27 | void lcd_wait (void); // Wait until previous operation is complete.
28 |
29 | // ------------------------------------------------------------------------
30 | // Framebuffer functions.
31 | // ------------------------------------------------------------------------
32 |
33 | void lcd_fb_setaddr (const void* buffer); // Buffer size = LCD_FRAMEBUFFER_BYTES. Does not enable auto-refresh by itself.
34 | void lcd_fb_enable (void); // Automatic framebuffer refresh. When enabled, other functions cannot be used.
35 | void lcd_fb_disable (void); // Disable auto-refresh. Waits until last refresh is complete before returning.
36 |
37 | // ------------------------------------------------------------------------
38 |
39 | #endif // __LCD_H__
40 |
--------------------------------------------------------------------------------
/include/led.h:
--------------------------------------------------------------------------------
1 | #ifndef __LED_H__
2 | #define __LED_H__
3 |
4 | // ------------------------------------------------------------------------
5 |
6 | void led_init();
7 | void led_set(int c); // c[0:2] = RGB
8 |
9 | // ------------------------------------------------------------------------
10 |
11 | #endif // __LED_H__
12 |
--------------------------------------------------------------------------------
/include/systick.h:
--------------------------------------------------------------------------------
1 | /*!
2 | \file systick.h
3 | \brief the header file of systick
4 |
5 | \version 2019-6-5, V1.0.0, firmware for GD32VF103
6 | */
7 |
8 | /*
9 | Copyright (c) 2019, GigaDevice Semiconductor Inc.
10 |
11 | Redistribution and use in source and binary forms, with or without modification,
12 | are permitted provided that the following conditions are met:
13 |
14 | 1. Redistributions of source code must retain the above copyright notice, this
15 | list of conditions and the following disclaimer.
16 | 2. Redistributions in binary form must reproduce the above copyright notice,
17 | this list of conditions and the following disclaimer in the documentation
18 | and/or other materials provided with the distribution.
19 | 3. Neither the name of the copyright holder nor the names of its contributors
20 | may be used to endorse or promote products derived from this software without
21 | specific prior written permission.
22 |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 | OF SUCH DAMAGE.
33 | */
34 |
35 | #ifndef SYS_TICK_H
36 | #define SYS_TICK_H
37 |
38 |
39 | #endif /* SYS_TICK_H */
40 |
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | ; PlatformIO Project Configuration File
2 | ;
3 | ; Build options: build flags, source filter, extra scripting
4 | ; Upload options: custom port, speed and extra flags
5 | ; Library options: dependencies, extra library storages
6 | ;
7 | ; Please visit documentation for the other options and examples
8 | ; http://docs.platformio.org/page/projectconf.html
9 |
10 | [env:sipeed-longan-nano]
11 | platform = gd32v
12 | framework = gd32vf103-sdk
13 | board = sipeed-longan-nano
14 | monitor_speed = 115200
15 | upload_protocol = dfu
16 |
--------------------------------------------------------------------------------
/src/amigaball.inl:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------
2 | // Code for rendering the "Amiga ball".
3 | // ------------------------------------------------------------------------
4 |
5 | int w_approx(int x)
6 | {
7 | // Generated in Python:
8 | // for i in range(32):
9 | // print(int(8192 / ((1 - (i/32.0)**2)**0.5)))
10 | // print(65535)
11 | static const int s_lut[33] = {
12 | 8192, 8196, 8208, 8228, 8256, 8293, 8339, 8395, 8460, 8536,
13 | 8623, 8723, 8836, 8965, 9110, 9273, 9459, 9669, 9908, 10180,
14 | 10494, 10856, 11280, 11782, 12385, 13123, 14052, 15262, 16921, 19378,
15 | 23541, 33027, 65535
16 | };
17 |
18 | if (x < 0)
19 | x = -x;
20 | if (x > (32<<8) - 1)
21 | x = (32<<8) - 1;
22 |
23 | int m = x >> 8;
24 | int f = x & 0xff;
25 | int v0 = s_lut[m];
26 | int v1 = s_lut[m+1];
27 | return ((v0 * (0xff - f)) + (v1 * f)) >> 8;
28 | }
29 |
30 | int approx_asin(int x)
31 | {
32 | x = (x * 11) >> 3;
33 | int x2 = (x*x)>>13;
34 | int x3 = (x2*x)>>13;
35 | int x5 = (x2*x3)>>13;
36 | x += (1365*x3 + 614*x5) >> 13;
37 | return x;
38 | }
39 |
40 | int c_remap(int x)
41 | {
42 | if (x < 256)
43 | return 256-x;
44 | if (x < 4096)
45 | return 0;
46 | if (x < 4096+256)
47 | return x-4096;
48 | return 256;
49 | }
50 |
51 | int amigaBall(int x_, int y_, int ph_)
52 | {
53 | x_ >>= 1;
54 | y_ >>= 1;
55 | x_ -= 32 << 8;
56 | y_ -= 32 << 8;
57 |
58 | int x = ( 8028 * x_ + 1627 * y_) >> 13;
59 | int y = (-1627 * x_ + 8028 * y_) >> 13;
60 |
61 | int r = x*x+y*y;
62 | if (r > (1<<26))
63 | return -1;
64 |
65 | x = (x * w_approx(y)) >> 13;
66 | y = approx_asin(y);
67 | x = approx_asin(x) + ph_;
68 |
69 | x &= 0x1fff;
70 | y &= 0x1fff;
71 |
72 | int cx = c_remap(x);
73 | int cy = c_remap(y);
74 | int cc = (((cx - 128) * (cy - 128)) >> 7) + 128;
75 |
76 | int R = 0x1f;
77 | int G = (0x3f * cc) >> 8;
78 | int B = (0x1f * cc) >> 8;
79 |
80 | cc = 256 - c_remap((1<<12) - (r >> 14));
81 | R = (R * cc) >> 8;
82 | G = (G * cc) >> 8;
83 | B = (B * cc) >> 8;
84 |
85 | return (R<<11) + (G<<5) + B;
86 | }
87 |
88 | // ------------------------------------------------------------------------
89 |
--------------------------------------------------------------------------------
/src/lcd.c:
--------------------------------------------------------------------------------
1 | #include "lcd.h"
2 | #include "gd32vf103.h"
3 | #include "systick.h"
4 |
5 | // ------------------------------------------------------------------------
6 |
7 | #define spi_wait_idle() do { while (SPI_STAT(SPI0) & SPI_STAT_TRANS); } while(0)
8 | #define spi_wait_tbe() do { while (!(SPI_STAT(SPI0) & SPI_STAT_TBE)); } while(0)
9 | #define spi_wait_rbne() do { while (!(SPI_STAT(SPI0) & SPI_STAT_RBNE)); } while(0)
10 | #define dma_wait_recv() do { while (DMA_CHCNT(DMA0, DMA_CH1); } while(0)
11 | #define lcd_mode_cmd() do { gpio_bit_reset(GPIOB, GPIO_PIN_0); } while(0)
12 | #define lcd_mode_data() do { gpio_bit_set (GPIOB, GPIO_PIN_0); } while(0)
13 | #define lcd_cs_enable() do { gpio_bit_reset(GPIOB, GPIO_PIN_2); } while(0)
14 | #define lcd_cs_disable() do { gpio_bit_set (GPIOB, GPIO_PIN_2); } while(0)
15 |
16 | // ------------------------------------------------------------------------
17 |
18 | typedef enum {
19 | WAIT_NONE = 0,
20 | WAIT_READ_U24 = 1,
21 | WAIT_WRITE_U24 = 2,
22 | } WaitStatus;
23 |
24 | static WaitStatus g_waitStatus = WAIT_NONE;
25 | static uint32_t g_fbAddress = 0;
26 | static int g_fbEnabled = 0;
27 |
28 | // ------------------------------------------------------------------------
29 | // Internal functions.
30 | // ------------------------------------------------------------------------
31 |
32 | void spi_set_8bit()
33 | {
34 | if (SPI_CTL0(SPI0) & (uint32_t)(SPI_CTL0_FF16))
35 | {
36 | SPI_CTL0(SPI0) &= ~(uint32_t)(SPI_CTL0_SPIEN);
37 | SPI_CTL0(SPI0) &= ~(uint32_t)(SPI_CTL0_FF16);
38 | SPI_CTL0(SPI0) |= (uint32_t)(SPI_CTL0_SPIEN);
39 | }
40 | }
41 |
42 | void spi_set_16bit()
43 | {
44 | if (!(SPI_CTL0(SPI0) & (uint32_t)(SPI_CTL0_FF16)))
45 | {
46 | SPI_CTL0(SPI0) &= ~(uint32_t)(SPI_CTL0_SPIEN);
47 | SPI_CTL0(SPI0) |= (uint32_t)(SPI_CTL0_FF16);
48 | SPI_CTL0(SPI0) |= (uint32_t)(SPI_CTL0_SPIEN);
49 | }
50 | }
51 |
52 | void dma_send_u8(const void* src, uint32_t count)
53 | {
54 | spi_wait_idle();
55 | lcd_mode_data();
56 | spi_set_8bit();
57 | dma_channel_disable(DMA0, DMA_CH2);
58 | dma_memory_width_config(DMA0, DMA_CH2, DMA_MEMORY_WIDTH_8BIT);
59 | dma_periph_width_config(DMA0, DMA_CH2, DMA_PERIPHERAL_WIDTH_8BIT);
60 | dma_memory_address_config(DMA0, DMA_CH2, (uint32_t)src);
61 | dma_memory_increase_enable(DMA0, DMA_CH2);
62 | dma_transfer_number_config(DMA0, DMA_CH2, count);
63 | dma_channel_enable(DMA0, DMA_CH2);
64 | }
65 |
66 | void dma_send_u16(const void* src, uint32_t count)
67 | {
68 | spi_wait_idle();
69 | lcd_mode_data();
70 | spi_set_16bit();
71 | dma_channel_disable(DMA0, DMA_CH2);
72 | dma_memory_width_config(DMA0, DMA_CH2, DMA_MEMORY_WIDTH_16BIT);
73 | dma_periph_width_config(DMA0, DMA_CH2, DMA_PERIPHERAL_WIDTH_16BIT);
74 | dma_memory_address_config(DMA0, DMA_CH2, (uint32_t)src);
75 | dma_memory_increase_enable(DMA0, DMA_CH2);
76 | dma_transfer_number_config(DMA0, DMA_CH2, count);
77 | dma_channel_enable(DMA0, DMA_CH2);
78 | }
79 |
80 | uint32_t g_dma_const_value = 0;
81 |
82 | void dma_send_const_u8(uint8_t data, uint32_t count)
83 | {
84 | spi_wait_idle();
85 | g_dma_const_value = data;
86 | lcd_mode_data();
87 | spi_set_8bit();
88 | dma_channel_disable(DMA0, DMA_CH2);
89 | dma_memory_width_config(DMA0, DMA_CH2, DMA_MEMORY_WIDTH_8BIT);
90 | dma_periph_width_config(DMA0, DMA_CH2, DMA_PERIPHERAL_WIDTH_8BIT);
91 | dma_memory_address_config(DMA0, DMA_CH2, (uint32_t)(&g_dma_const_value));
92 | dma_memory_increase_disable(DMA0, DMA_CH2);
93 | dma_transfer_number_config(DMA0, DMA_CH2, count);
94 | dma_channel_enable(DMA0, DMA_CH2);
95 | }
96 |
97 | void dma_send_const_u16(uint16_t data, uint32_t count)
98 | {
99 | spi_wait_idle();
100 | g_dma_const_value = data;
101 | lcd_mode_data();
102 | spi_set_16bit();
103 | dma_channel_disable(DMA0, DMA_CH2);
104 | dma_memory_width_config(DMA0, DMA_CH2, DMA_MEMORY_WIDTH_16BIT);
105 | dma_periph_width_config(DMA0, DMA_CH2, DMA_PERIPHERAL_WIDTH_16BIT);
106 | dma_memory_address_config(DMA0, DMA_CH2, (uint32_t)(&g_dma_const_value));
107 | dma_memory_increase_disable(DMA0, DMA_CH2);
108 | dma_transfer_number_config(DMA0, DMA_CH2, count);
109 | dma_channel_enable(DMA0, DMA_CH2);
110 | }
111 |
112 | void lcd_reg(uint8_t x)
113 | {
114 | spi_wait_idle();
115 | spi_set_8bit();
116 | lcd_mode_cmd();
117 | spi_i2s_data_transmit(SPI0, x);
118 | }
119 |
120 | void lcd_u8(uint8_t x)
121 | {
122 | spi_wait_idle();
123 | spi_set_8bit();
124 | lcd_mode_data();
125 | spi_i2s_data_transmit(SPI0, x);
126 | }
127 |
128 | void lcd_u8c(uint8_t x)
129 | {
130 | spi_wait_tbe();
131 | spi_i2s_data_transmit(SPI0, x);
132 | }
133 |
134 | void lcd_u16(uint16_t x)
135 | {
136 | spi_wait_idle();
137 | spi_set_16bit();
138 | lcd_mode_data();
139 | spi_i2s_data_transmit(SPI0, x);
140 | }
141 |
142 | void lcd_u16c(uint16_t x)
143 | {
144 | spi_wait_tbe();
145 | spi_i2s_data_transmit(SPI0, x);
146 | }
147 |
148 | void lcd_set_addr(int x, int y, int w, int h)
149 | {
150 | lcd_reg(0x2a);
151 | lcd_u16(x+1);
152 | lcd_u16c(x+w);
153 | lcd_reg(0x2b);
154 | lcd_u16(y+26);
155 | lcd_u16c(y+h+25);
156 | lcd_reg(0x2c);
157 | }
158 |
159 | // ------------------------------------------------------------------------
160 | // Public functions.
161 | // ------------------------------------------------------------------------
162 |
163 | void lcd_init(void)
164 | {
165 | rcu_periph_clock_enable(RCU_GPIOA);
166 | rcu_periph_clock_enable(RCU_GPIOB);
167 | rcu_periph_clock_enable(RCU_AF);
168 | rcu_periph_clock_enable(RCU_DMA0);
169 | rcu_periph_clock_enable(RCU_SPI0);
170 |
171 | gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7);
172 | gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2);
173 | gpio_bit_reset(GPIOB, GPIO_PIN_0 | GPIO_PIN_1); // DC=0, RST=0
174 | lcd_cs_disable();
175 |
176 | delay_1ms(1);
177 | gpio_bit_set(GPIOB, GPIO_PIN_1); // RST=1
178 | delay_1ms(5);
179 |
180 | // Deinit SPI and DMA.
181 | spi_i2s_deinit(SPI0);
182 | dma_deinit(DMA0, DMA_CH1);
183 | dma_deinit(DMA0, DMA_CH2);
184 |
185 | // Configure DMA, do not enable.
186 | DMA_CHCTL(DMA0, DMA_CH1) = (uint32_t)(DMA_PRIORITY_ULTRA_HIGH | DMA_CHXCTL_MNAGA); // Receive.
187 | DMA_CHCTL(DMA0, DMA_CH2) = (uint32_t)(DMA_PRIORITY_ULTRA_HIGH | DMA_CHXCTL_DIR); // Transmit.
188 | DMA_CHPADDR(DMA0, DMA_CH1) = (uint32_t)&SPI_DATA(SPI0);
189 | DMA_CHPADDR(DMA0, DMA_CH2) = (uint32_t)&SPI_DATA(SPI0);
190 |
191 | // Configure and enable SPI.
192 | SPI_CTL0(SPI0) = (uint32_t)(SPI_MASTER | SPI_TRANSMODE_FULLDUPLEX | SPI_FRAMESIZE_8BIT | SPI_NSS_SOFT | SPI_ENDIAN_MSB | SPI_CK_PL_LOW_PH_1EDGE | SPI_PSC_8);
193 | SPI_CTL1(SPI0) = (uint32_t)(SPI_CTL1_DMATEN);
194 | spi_enable(SPI0);
195 |
196 | // Enable lcd controller.
197 | lcd_cs_enable();
198 |
199 | // Initialization settings. Based on lcd.c in gd32v_lcd example.
200 | static const uint8_t init_sequence[] =
201 | {
202 | 0x21, 0xff,
203 | 0xb1, 0x05, 0x3a, 0x3a, 0xff,
204 | 0xb2, 0x05, 0x3a, 0x3a, 0xff,
205 | 0xb3, 0x05, 0x3a, 0x3a, 0x05, 0x3a, 0x3a, 0xff,
206 | 0xb4, 0x03, 0xff,
207 | 0xc0, 0x62, 0x02, 0x04, 0xff,
208 | 0xc1, 0xc0, 0xff,
209 | 0xc2, 0x0d, 0x00, 0xff,
210 | 0xc3, 0x8d, 0x6a, 0xff,
211 | 0xc4, 0x8d, 0xee, 0xff,
212 | 0xc5, 0x0e, 0xff,
213 | 0xe0, 0x10, 0x0e, 0x02, 0x03, 0x0e, 0x07, 0x02, 0x07, 0x0a, 0x12, 0x27, 0x37, 0x00, 0x0d, 0x0e, 0x10, 0xff,
214 | 0xe1, 0x10, 0x0e, 0x03, 0x03, 0x0f, 0x06, 0x02, 0x08, 0x0a, 0x13, 0x26, 0x36, 0x00, 0x0d, 0x0e, 0x10, 0xff,
215 | 0x3a, 0x55, 0xff,
216 | 0x36, 0x78, 0xff,
217 | 0x29, 0xff,
218 | 0x11, 0xff,
219 | 0xff
220 | };
221 |
222 | // Initialize the display.
223 | for (const uint8_t* p = init_sequence; *p != 0xff; p++)
224 | {
225 | lcd_reg(*p++);
226 | if (*p == 0xff)
227 | continue;
228 | spi_wait_idle();
229 | lcd_mode_data();
230 | while(*p != 0xff)
231 | lcd_u8c(*p++);
232 | }
233 |
234 | // Clear display.
235 | lcd_clear(0);
236 |
237 | // Init internal state.
238 | g_waitStatus = WAIT_NONE;
239 | g_fbAddress = 0;
240 | g_fbEnabled = 0;
241 | }
242 |
243 | void lcd_clear(uint16_t color)
244 | {
245 | if (g_fbEnabled)
246 | return;
247 |
248 | lcd_wait();
249 | lcd_set_addr(0, 0, LCD_WIDTH, LCD_HEIGHT);
250 | dma_send_const_u16(color, LCD_WIDTH * LCD_HEIGHT);
251 | }
252 |
253 | void lcd_setpixel(int x, int y, unsigned short int color)
254 | {
255 | if (g_fbEnabled)
256 | return;
257 |
258 | lcd_wait();
259 | lcd_set_addr(x, y, 1, 1);
260 | lcd_u8(color >> 8);
261 | lcd_u8c(color);
262 | }
263 |
264 | void lcd_fill_rect(int x, int y, int w, int h, uint16_t color)
265 | {
266 | if (g_fbEnabled)
267 | return;
268 |
269 | lcd_wait();
270 | lcd_set_addr(x, y, w, h);
271 | dma_send_const_u16(color, w*h);
272 | }
273 |
274 | void lcd_rect(int x, int y, int w, int h, uint16_t color)
275 | {
276 | if (g_fbEnabled)
277 | return;
278 |
279 | lcd_wait();
280 | lcd_fill_rect(x, y, x+w, y+1, color);
281 | lcd_fill_rect(x, y+w-1, x+w, y+w, color);
282 | lcd_fill_rect(x, y+1, x+1, y+w-1, color);
283 | lcd_fill_rect(x+w-1, y+1, x+w, y+w-1, color);
284 | }
285 |
286 | void lcd_write_u16(int x, int y, int w, int h, const void* buffer)
287 | {
288 | if (g_fbEnabled)
289 | return;
290 |
291 | lcd_wait();
292 | lcd_set_addr(x, y, w, h);
293 | dma_send_u16(buffer, w*h);
294 | }
295 |
296 | void lcd_write_u24(int x, int y, int w, int h, const void* buffer)
297 | {
298 | if (g_fbEnabled)
299 | return;
300 |
301 | lcd_wait();
302 | lcd_reg(0x3a); // COLMOD
303 | lcd_u8(0x66); // RGB666 (transferred as 3 x 8b)
304 | lcd_set_addr(x, y, w, h);
305 | dma_send_u8(buffer, w*h*3);
306 | g_waitStatus = WAIT_WRITE_U24;
307 | }
308 |
309 | void lcd_read_u24(int x, int y, int w, int h, void* buffer)
310 | {
311 | if (g_fbEnabled)
312 | return;
313 |
314 | lcd_wait();
315 |
316 | // Send receive commands.
317 | lcd_set_addr(x, y, w, h);
318 | lcd_reg(0x3a); // COLMOD
319 | lcd_u8(0x66); // RGB666 (transferred as 3 x 8b)
320 | lcd_reg(0x2e); // RAMRD
321 | lcd_u8(0x00); // Flush dummy first byte sent by display.
322 |
323 | // Configure SPI and DMA for receiving.
324 | spi_wait_idle();
325 | spi_disable(SPI0);
326 | lcd_mode_data();
327 | SPI_DATA(SPI0); // Clear RBNE.
328 | SPI_CTL0(SPI0) = (uint32_t)(SPI_MASTER | SPI_TRANSMODE_BDRECEIVE | SPI_FRAMESIZE_8BIT | SPI_NSS_SOFT | SPI_ENDIAN_MSB | SPI_CK_PL_HIGH_PH_2EDGE | SPI_PSC_8);
329 | SPI_CTL1(SPI0) = (uint32_t)(SPI_CTL1_DMAREN);
330 | dma_memory_address_config(DMA0, DMA_CH1, (uint32_t)buffer);
331 | dma_transfer_number_config(DMA0, DMA_CH1, w*h*3);
332 | dma_channel_enable(DMA0, DMA_CH1);
333 | spi_enable(SPI0); // Go.
334 | g_waitStatus = WAIT_READ_U24;
335 | }
336 |
337 | void lcd_wait(void)
338 | {
339 | if (g_fbEnabled)
340 | return;
341 |
342 | if (g_waitStatus == WAIT_NONE)
343 | return;
344 |
345 | if (g_waitStatus == WAIT_READ_U24)
346 | {
347 | // Poll until reception is complete.
348 | while(dma_transfer_number_get(DMA0, DMA_CH1));
349 |
350 | // Reception is complete, reconfigure SPI for sending and toggle LCD CS to stop transmission.
351 | dma_channel_disable(DMA0, DMA_CH1);
352 | spi_disable(SPI0);
353 | lcd_cs_disable();
354 | SPI_CTL0(SPI0) = (uint32_t)(SPI_MASTER | SPI_TRANSMODE_FULLDUPLEX | SPI_FRAMESIZE_8BIT | SPI_NSS_SOFT | SPI_ENDIAN_MSB | SPI_CK_PL_LOW_PH_1EDGE | SPI_PSC_8);
355 | SPI_CTL1(SPI0) = (uint32_t)(SPI_CTL1_DMATEN);
356 | lcd_cs_enable();
357 | spi_enable(SPI0);
358 |
359 | // Return to normal color mode.
360 | lcd_reg(0x3a); // COLMOD
361 | lcd_u8(0x55); // RGB565 (transferred as 16b)
362 |
363 | // Clear wait status and return.
364 | g_waitStatus = WAIT_NONE;
365 | return;
366 | }
367 |
368 | if (g_waitStatus == WAIT_WRITE_U24)
369 | {
370 | // Wait until send is complete, then restore normal color mode.
371 | spi_wait_idle();
372 | lcd_reg(0x3a); // COLMOD
373 | lcd_u8(0x55); // RGB565 (transferred as 16b)
374 |
375 | // Clear wait status and return.
376 | g_waitStatus = WAIT_NONE;
377 | return;
378 | }
379 | }
380 |
381 | // ------------------------------------------------------------------------
382 | // Framebuffer functions.
383 | // ------------------------------------------------------------------------
384 |
385 | void DMA0_Channel2_IRQHandler(void)
386 | {
387 | // Clear the interrupt flag to avoid retriggering.
388 | dma_interrupt_flag_clear(DMA0, DMA_CH2, DMA_INT_FLAG_G);
389 |
390 | if (g_fbEnabled)
391 | {
392 | // Restart transmission.
393 | lcd_set_addr(0, 0, LCD_WIDTH, LCD_HEIGHT); // Technically not needed?
394 | dma_send_u16((const void*)g_fbAddress, LCD_FRAMEBUFFER_PIXELS);
395 | }
396 | else
397 | {
398 | // Disable the interrupt. Ends the wait for complete.
399 | dma_interrupt_disable(DMA0, DMA_CH2, DMA_INT_FTF);
400 | }
401 | }
402 |
403 | void lcd_fb_setaddr(const void* buffer)
404 | {
405 | if (g_fbEnabled)
406 | return;
407 |
408 | g_fbAddress = (uint32_t)buffer;
409 | }
410 |
411 | void lcd_fb_enable(void)
412 | {
413 | if (g_fbEnabled || !g_fbAddress)
414 | return;
415 |
416 | // Wait and set enabled flag.
417 | lcd_wait();
418 | spi_wait_idle();
419 | g_fbEnabled = 1;
420 |
421 | // Enable interrupt controller.
422 | eclic_global_interrupt_enable();
423 | eclic_enable_interrupt(DMA0_Channel2_IRQn);
424 |
425 | // Set up transfer complete interrupt.
426 | dma_channel_disable(DMA0, DMA_CH2);
427 | dma_flag_clear(DMA0, DMA_CH2, DMA_FLAG_G);
428 | dma_interrupt_enable(DMA0, DMA_CH2, DMA_INT_FTF);
429 |
430 | // Start the first transfer.
431 | lcd_set_addr(0, 0, LCD_WIDTH, LCD_HEIGHT);
432 | dma_send_u16((const void*)(g_fbAddress), LCD_FRAMEBUFFER_PIXELS);
433 | }
434 |
435 | void lcd_fb_disable(void)
436 | {
437 | if (!g_fbEnabled)
438 | return;
439 |
440 | // Disable and wait until handler disables the interrupt.
441 | g_fbEnabled = 0;
442 | while(DMA_CHCTL(DMA0, DMA_CH2) & DMA_CHXCTL_FTFIE);
443 | }
444 |
445 | // ------------------------------------------------------------------------
446 |
--------------------------------------------------------------------------------
/src/led.c:
--------------------------------------------------------------------------------
1 | #include "led.h"
2 | #include "gd32vf103.h"
3 |
4 | // ------------------------------------------------------------------------
5 |
6 | void led_init()
7 | {
8 | rcu_periph_clock_enable(RCU_GPIOA);
9 | rcu_periph_clock_enable(RCU_GPIOC);
10 | gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1 | GPIO_PIN_2);
11 | gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
12 | GPIO_BOP(GPIOA) = GPIO_PIN_1 | GPIO_PIN_2;
13 | GPIO_BOP(GPIOC) = GPIO_PIN_13;
14 | }
15 |
16 | void led_set(int c) // c[0:2] = RGB
17 | {
18 | if (c & 1) GPIO_BC (GPIOC) = GPIO_PIN_13; // red
19 | else GPIO_BOP(GPIOC) = GPIO_PIN_13;
20 | if (c & 2) GPIO_BC (GPIOA) = GPIO_PIN_1; // green
21 | else GPIO_BOP(GPIOA) = GPIO_PIN_1;
22 | if (c & 4) GPIO_BC (GPIOA) = GPIO_PIN_2; // blue
23 | else GPIO_BOP(GPIOA) = GPIO_PIN_2;
24 | }
25 |
26 | // ------------------------------------------------------------------------
27 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include "gd32vf103.h"
2 | #include "lcd.h"
3 | #include "led.h"
4 |
5 | // ------------------------------------------------------------------------
6 | // Framebuffer and other globals.
7 | // ------------------------------------------------------------------------
8 |
9 | uint16_t g_fb[LCD_FRAMEBUFFER_PIXELS]; // LCD Color: RGB565 (MSB rrrrrggggggbbbbb LSB)
10 |
11 | uint16_t bgcolxy(int x, int y)
12 | {
13 | x -= LCD_WIDTH >> 1;
14 | y -= LCD_HEIGHT >> 1;
15 | y <<= 1;
16 | int r = x*x + y*y;
17 | return r >> 11;
18 | }
19 |
20 | uint16_t bgcol(int p)
21 | {
22 | int y = p / LCD_WIDTH;
23 | int x = p - y * LCD_WIDTH;
24 | return bgcolxy(x, y);
25 | }
26 |
27 | #include "amigaball.inl"
28 | #include "starfield.inl"
29 |
30 | int main(void)
31 | {
32 | led_init();
33 | lcd_init();
34 |
35 | // Clear the framebuffer.
36 | for (int i=0; i < LCD_FRAMEBUFFER_PIXELS; i++)
37 | g_fb[i] = bgcol(i);
38 |
39 | #if 0
40 | // Test pattern. Gray except for 1 pixel red border. Useful for detecting
41 | // timing/logic bugs that may cause the lcd internal pixel address register
42 | // getting out of sync with SPI upload.
43 | {
44 | for (int y=0,i=0; y < 80; y++)
45 | for (int x=0; x < 160; x++,i++)
46 | {
47 | if (x==0 || y==0 || x==159 || y==79)
48 | g_fb[i] = 0xc000;
49 | else
50 | g_fb[i] = 0x4208;
51 | }
52 | // Update in an infinite loop.
53 | for (;;)
54 | lcd_write_u16(0, 0, LCD_WIDTH, LCD_HEIGHT, g_fb);
55 | }
56 |
57 | #elif 1
58 | // Example with framebuffer upload after each frame completion (Amiga ball).
59 |
60 | int px = 0;
61 | int py = 0;
62 | int dx = 1 << 8;
63 | int dy = 1 << 8;
64 | int ph = 0;
65 | for(;;)
66 | {
67 | // Render into framebuffer.
68 | uint16_t* pfb = g_fb;
69 | for (int y=0; y < LCD_HEIGHT; y++)
70 | for (int x=0; x < LCD_WIDTH; x++)
71 | {
72 | int fx = (x << 8) - px;
73 | int fy = (y << 8) - py;
74 | int c = amigaBall(fx, fy, ph);
75 | if (c >= 0)
76 | *pfb++ = c;
77 | else
78 | *pfb++ = bgcolxy(x, y);
79 | }
80 |
81 | // Trigger framebuffer upload. We rely on the upload being faster than
82 | // our framebuffer writes in the loop above so that we can render the
83 | // next frame while this one is being uploaded asynchronously in the
84 | // background.
85 | lcd_write_u16(0, 0, LCD_WIDTH, LCD_HEIGHT, g_fb);
86 |
87 | // Update ball position.
88 | px += dx; py += dy;
89 | if (dx > 0) ph += (1 << 8); else ph -= (1 << 8); // Rotation.
90 | if (py > (16<<8)) { py = (16<<9) - py; dy = -dy; } // Floor.
91 | if (px > (96<<8)) { px = (96<<9) - px; dx = -dx; } // Right wall.
92 | if (px < (-64 << 8)) { px = (-64 << 9) - px; dx = -dx; } // Left wall.
93 | dy += 1 << 4; // Apply gravity.
94 | }
95 |
96 | #else
97 | // Continuous framebuffer upload example (starfield).
98 |
99 | // Enable continuous framebuffer update.
100 | lcd_fb_setaddr(g_fb);
101 | lcd_fb_enable();
102 |
103 | // Initial stars.
104 | for (int i=0; i < NUM_STARS; i++)
105 | {
106 | g_stars[i].x = rnd_u32();
107 | g_stars[i].y = rnd_u32();
108 | g_stars[i].z = rnd_u32();
109 | g_stars[i].p = -1;
110 | }
111 |
112 | // Render star field. Note that we only need to update the framebuffer
113 | // in memory, and the continuous upload in background makes the changes
114 | // visible on display.
115 | for(;;)
116 | {
117 | for (int i=0; i < NUM_STARS; i++)
118 | {
119 | Star* s = &g_stars[i];
120 | if (s->p >= 0)
121 | g_fb[s->p] = bgcol(s->p);
122 | update_star(s, 10);
123 | if (s->p >= 0)
124 | {
125 | int g = (0x7fff - s->z) >> 9;
126 | if (g > 31) g = 31;
127 | int r = g>>1;
128 | g_fb[s->p] += (r << 11) | (g << 5) | r;
129 | }
130 | }
131 | }
132 | #endif
133 | }
134 |
--------------------------------------------------------------------------------
/src/starfield.inl:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------
2 | // Code for constructing and updating a moving starfield.
3 | // ------------------------------------------------------------------------
4 |
5 | // Jenkins hash functions (https://en.wikipedia.org/wiki/Jenkins_hash_function).
6 |
7 | #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
8 | #define swap(a,b) do { a+=b; b=a-b; a-=b; } while(0)
9 | uint32_t jenkins_mix_3(uint32_t a, uint32_t b, uint32_t c)
10 | {
11 | a -= c; a ^= rot(c, 4); c += b;
12 | b -= a; b ^= rot(a, 6); a += c;
13 | c -= b; c ^= rot(b, 8); b += a;
14 | a -= c; a ^= rot(c,16); c += b;
15 | b -= a; b ^= rot(a,19); a += c;
16 | c -= b; c ^= rot(b, 4); b += a;
17 | return c;
18 | }
19 |
20 | uint32_t jenkins_mix_2(uint32_t a, uint32_t b)
21 | {
22 | return jenkins_mix_3(a, b, 0xdeadbeef);
23 | }
24 |
25 | uint32_t jenkins_mix(uint32_t a)
26 | {
27 | return jenkins_mix_3(a, 0x72837482, 0xdeadbeef);
28 | }
29 |
30 | // Random number generator based on Jenkins hash.
31 |
32 | static uint32_t g_rnd_state = 0;
33 | uint32_t rnd_u32(void)
34 | {
35 | return jenkins_mix(g_rnd_state++);
36 | }
37 |
38 | // ------------------------------------------------------------------------
39 |
40 | typedef struct
41 | {
42 | int16_t x, y, z, p;
43 | } Star;
44 |
45 | enum { NUM_STARS = 400 };
46 | Star g_stars[NUM_STARS];
47 |
48 | // ------------------------------------------------------------------------
49 |
50 | void update_star(Star* v, int delta)
51 | {
52 | v->z -= delta;
53 | if (v->z <= 0 || v->p < 0)
54 | {
55 | v->x = rnd_u32();
56 | v->y = rnd_u32();
57 | v->z = 0x7fff;
58 | }
59 |
60 | int x = ((int)(v->x) << 16) / v->z;
61 | int y = ((int)(v->y) << 16) / v->z;
62 |
63 | x >>= 10;
64 | y >>= 10;
65 |
66 | x += LCD_WIDTH >> 1;
67 | y += LCD_HEIGHT >> 1;
68 | if (x < 0 || x >= LCD_WIDTH || y < 0 || y >= LCD_HEIGHT)
69 | v->p = -1;
70 | else
71 | v->p = x + LCD_WIDTH * y;
72 | }
73 |
74 | // ------------------------------------------------------------------------
75 |
--------------------------------------------------------------------------------
/src/systick.c:
--------------------------------------------------------------------------------
1 | /*!
2 | \file systick.c
3 | \brief the systick configuration file
4 |
5 | \version 2019-6-5, V1.0.0, firmware for GD32VF103
6 | */
7 |
8 | /*
9 | Copyright (c) 2019, GigaDevice Semiconductor Inc.
10 |
11 | Redistribution and use in source and binary forms, with or without modification,
12 | are permitted provided that the following conditions are met:
13 |
14 | 1. Redistributions of source code must retain the above copyright notice, this
15 | list of conditions and the following disclaimer.
16 | 2. Redistributions in binary form must reproduce the above copyright notice,
17 | this list of conditions and the following disclaimer in the documentation
18 | and/or other materials provided with the distribution.
19 | 3. Neither the name of the copyright holder nor the names of its contributors
20 | may be used to endorse or promote products derived from this software without
21 | specific prior written permission.
22 |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 | OF SUCH DAMAGE.
33 | */
34 |
35 | #include "gd32vf103.h"
36 | #include "systick.h"
37 |
38 | /*!
39 | \brief delay a time in milliseconds
40 | \param[in] count: count in milliseconds
41 | \param[out] none
42 | \retval none
43 | */
44 | void delay_1ms(uint32_t count)
45 | {
46 | uint64_t start_mtime, delta_mtime;
47 |
48 | // Don't start measuruing until we see an mtime tick
49 | uint64_t tmp = get_timer_value();
50 | do {
51 | start_mtime = get_timer_value();
52 | } while (start_mtime == tmp);
53 |
54 | do {
55 | delta_mtime = get_timer_value() - start_mtime;
56 | }while(delta_mtime <(SystemCoreClock/4000.0 *count ));
57 | }
58 |
--------------------------------------------------------------------------------