├── 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 | 19 | 20 | 21 | 22 |
Amiga ballStarfield
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 | --------------------------------------------------------------------------------