Full graphic 640x400 monochrome VGA driver library

Dependents:   vga640x400graphic

Revision:
0:ca7defdc9e44
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vga640x400g.c	Sun Jul 24 11:53:05 2011 +0000
@@ -0,0 +1,270 @@
+/*
+ * 640x400 70Hz full graphic VGA Driver
+ *
+ * Copyright (C) 2011 by Ivo van Poorten <ivop(at)euronet.nl>
+ * and Gert van der Knokke <gertk(at)xs4all.nl>
+ * This file is licensed under the terms of the GNU Lesser
+ * General Public License, version 3.
+ *
+ * Inspired by Simon's (not Ford) Cookbook entry and Cliff Biffle's
+ * assembly code.
+ */
+#include "fastlib/common.h"
+#include "fastlib/pinsel.h"
+#include "fastlib/gpio.h"
+#include "fastlib/clock.h"
+#include "fastlib/power.h"
+#include "fastlib/pwm.h"
+#include "fastlib/i2s.h"
+#include "fastlib/uart.h"
+#include "fastlib/dma.h"
+#include "fastlib/nvic.h"
+#include "vga640x400g_functions.h"
+
+
+// -----------------------------------------------------------------------------------
+
+// force the framebuffer in ABSHRAM0 and ABSHRAM1 (16k + 16k)
+// warning!! this disables the use of the USB, Ethernet and CAN bus functions!
+unsigned char *framebuffer = (unsigned char *)(0x2007C000);
+
+// the framepointer
+volatile unsigned char *pointer=framebuffer;
+
+// active line counter
+static unsigned line_counter;
+
+// -----------------------------------------------------------------------------------
+// setup CPU PLL
+#define FEED0_AND_WAIT(x,y)   fl_pll0_feed(); while(y fl_pll0_get_##x())
+
+static void init_pll0(unsigned int clock_source, int N, int M, int cpu_divider) {
+    fl_select_pll0_clock_source(clock_source);
+
+    fl_pll0_control(FL_ENABLE,  FL_DISCONNECT);
+    FEED0_AND_WAIT(connect,);
+    fl_pll0_control(FL_DISABLE, FL_DISCONNECT);
+    FEED0_AND_WAIT(enable,);
+
+    fl_pll0_config(N, M);
+    fl_pll0_feed();
+
+    fl_pll0_control(FL_ENABLE,  FL_DISCONNECT);
+    FEED0_AND_WAIT(enable,!);
+
+    fl_set_cpu_clock_divider(cpu_divider);
+    while (!fl_pll0_get_lock()) ;
+
+    fl_pll0_control(FL_ENABLE,  FL_CONNECT);
+    FEED0_AND_WAIT(connect,!);
+}
+
+// -----------------------------------------------------------------------------------
+// setup UART0
+static void init_uart0(unsigned divaddval, unsigned mulval, unsigned divisor) {
+    fl_power_uart0(FL_ON);
+    fl_select_clock_uart0(FL_CLOCK_DIV1);
+    fl_uart_set_fractional_divider(0, divaddval, mulval);
+    fl_uart_set_divisor_latch(0, divisor);
+}
+
+// -----------------------------------------------------------------------------------
+// setup VSYNC output on designated pin
+static void init_vsync(unsigned port, unsigned pin) {
+    fl_power_gpio(FL_ON);
+    fl_pinsel(port, pin, FL_FUNC_DEFAULT, FL_IGNORE, FL_IGNORE);
+    fl_gpio_set_direction(port, 1<<pin, FL_OUTPUT);
+    fl_gpio_clear_value    (port, 1<<pin);
+}
+
+// -----------------------------------------------------------------------------------
+// define structure for DMA linked lists
+struct dma_lli {
+    void *source;
+    void *dest;
+    struct dma_lli *next;
+    unsigned control_word;
+};
+
+// some arbitrary blank data for I2S used for blanking
+// even after DMA the I2S output will keep on emitting zeroes (= blank)
+static unsigned char blanking[32]={0,0,0,0,0,0,0,0,
+                                   0,0,0,0,0,0,0,0,
+                                   0,0,0,0,0,0,0,0,
+                                   0,0,0,0,0,0,0,0
+                                  };
+
+// preset our blanking DMA linked list
+extern const struct dma_lli blank_lli;
+
+// blank linked lists ends the DMA cycle (lli=0)
+static const struct dma_lli blank_lli = {
+    blanking, (void*)FL_I2STXFIFO, 0, 4
+    | (1 << 12)
+    | (1 << 15)
+    | (2 << 18)
+    | (2 << 21)
+    | (0 << 26)
+    | (0 << 27)
+    | (0 << 31)
+};
+
+// setup the DMA controller
+static void init_dma_controller(void) {
+    fl_power_gpdma(FL_ON);
+    fl_dma_enable(FL_ENABLE);
+    while (!fl_dma_is_enabled()) ;
+
+    // do some initial DMA setup but no need to start the DMA
+    fl_dma_set_srcaddr (0, framebuffer);         // initial source pointer
+    fl_dma_set_destaddr(0, (void*)FL_I2STXFIFO); // destination is I2S
+    fl_dma_set_next_lli(0, &blank_lli);          // link active list to blank list
+
+    fl_dma_channel_control(0,                    // control word
+                           20,                   // count (20*4 = 80)
+                           4, 4,                 // src and dest burst size
+                           32, 32,               // src and dest width
+                           FL_SRC_INCREMENT,
+                           FL_NO_DEST_INCREMENT,
+                           FL_OFF                // no interrupts
+                          );
+
+}
+
+// -----------------------------------------------------------------------------------
+// setup I2S for 25 MHz dot/pixel clock
+static void init_i2s(void) {
+    // I2S on P0.9 (DIP5)
+    fl_power_i2s(FL_ON);
+    fl_select_clock_i2s(FL_CLOCK_DIV1);                     // assume 100MHz
+    fl_pinsel(0, 7, FL_FUNC_ALT1, FL_IGNORE, FL_IGNORE);    // I2STX_CLK
+    fl_pinsel(0, 8, FL_FUNC_ALT1, FL_IGNORE, FL_IGNORE);    // I2STX_WS
+    fl_pinsel(0, 9, FL_FUNC_ALT1, FL_IGNORE, FL_IGNORE);    // I2STX_SDA
+    fl_i2s_set_tx_rate(1, 4);
+    fl_i2s_output_set_config(FL_8BITS, FL_STEREO, 8, 0, 0, 0, 0);
+
+}
+
+// -----------------------------------------------------------------------------------
+// create HSYNC output with PWM
+static void init_hsync(void) {
+    // PWM1.2 on P2.1 (DIP25)
+    fl_power_pwm1(FL_ON);
+    fl_select_clock_pwm1(FL_CLOCK_DIV1);
+    fl_pinsel(2, 1, FL_FUNC_ALT1, FL_FLOATING, FL_FLOATING);    // PWM1.2, no pullup/down
+    fl_pwm_set_prescale(4);         // 100/25 = 4
+
+#define HSHIFT 0
+
+    // main PWM
+    fl_pwm_set_match(0, 800);   // 800 color clocks
+
+    // generate line interrupts from PWM MR0
+    fl_pwm_config_match(0, FL_ON, FL_ON, FL_OFF);   // interrupt, reset, !stop
+
+    // this PWM generates the HSYNC pulse
+    fl_pwm_set_match(2, 16+HSHIFT);         // go low at 16
+    fl_pwm_set_match(1, 48+HSHIFT);         // go high at 48
+    fl_pwm_config_edges(2, FL_DOUBLE_EDGE); // need this for negative sync
+    fl_pwm_output_enable(2, FL_ENABLE);     // enable this output
+
+}
+
+// -----------------------------------------------------------------------------------
+// state machine list for the complete screen output
+static void state_before_vsync(void);
+
+static void (*state)(void) = state_before_vsync;
+
+static void state_blank_area(void) {
+    if (line_counter != 449) return;
+
+    line_counter = 0;
+    pointer=framebuffer;
+    state = state_before_vsync;
+}
+
+
+// emit a line from the visible area (framebuffer)
+static void state_visible_area(void) {
+    extern const struct dma_lli blank_lli;
+
+    if (line_counter != 438) {
+        // reset DMA parameters for active line
+        fl_dma_set_srcaddr (0,(unsigned char *)pointer);  // source is our current framebuffer pointer
+        fl_dma_set_destaddr(0, (void*)FL_I2STXFIFO);      // destination is I2S
+        fl_dma_set_next_lli(0, &blank_lli);               // connect to blanking list
+        fl_dma_channel_control(0,                  // control word
+                               20,                 // count (20*4 = 80 bytes active)
+                               4, 4,               // src and dest burst size
+                               32, 32,             // src and dest width
+                               FL_SRC_INCREMENT,
+                               FL_NO_DEST_INCREMENT,
+                               FL_OFF              // no interrupt on first 640 pixel
+                              );
+        // restart DMA sequence
+        fl_dma_channel_config(0, FL_ENABLE,
+                              FL_DMA_PERIPHERAL_IS_MEMORY, FL_DMA_BURST_REQUEST_I2S_CH0,
+                              FL_DMA_MEMORY_TO_PERIPHERAL,
+                              FL_ON, FL_ON
+                             );
+        // increment framebuffer pointer
+        pointer+=80;
+
+    } else   state = state_blank_area;
+}
+
+
+static void state_after_vsync(void) {
+    if (line_counter != 38) return;
+    state = state_visible_area;
+}
+
+static void state_during_vsync(void) {
+    if (line_counter != 4) return;
+
+    fl_gpio_clear_value(0, 1<<6);
+    state = state_after_vsync;
+}
+
+static void state_before_vsync(void) {
+    if (line_counter != 2) return;
+
+    fl_gpio_set_value(0, 1<<6);
+    state = state_during_vsync;
+}
+
+// inactive
+extern "C" void DMA_IRQHandler(void) __irq {
+    fl_dma_clear_terminal_count_interrupt_request(0);
+
+}
+
+// active
+extern "C" void PWM1_IRQHandler(void) __irq {
+    int regval=*FL_PWM1IR;
+    // clear interrupt flag
+    state();
+    line_counter++;
+    *FL_PWM1IR=regval;
+}
+
+
+// -----------------------------------------------------------------------------------
+
+void init_vga(void) {
+    fl_power_off_all_peripherals();
+
+    init_pll0(FL_PLL0_CLOCK_SOURCE_MAIN, 2, 25, 3); // 100MHz
+    init_uart0(0, 0, 651);                          // 100MHz/651/16=9600.6 (default 8N1)
+
+    init_vsync(0, 6);                               // VSYNC on P0.6 (DIP8)
+    init_i2s();
+    init_hsync();
+    init_dma_controller();
+
+    fl_pwm_timer_counter_enable(FL_ENABLE);
+    fl_pwm_enable(FL_ENABLE);
+    fl_i2s_config_dma1(FL_OFF, FL_ON, 0, 2);
+    fl_nvic_interrupt_set_enable(FL_NVIC_INT_PWM);  // start the PWM interrupts
+}