Full graphic 640x400 monochrome VGA driver library

Dependents:   vga640x400graphic

vga640x400g.c

Committer:
gertk
Date:
2011-07-24
Revision:
0:ca7defdc9e44

File content as of revision 0:ca7defdc9e44:

/*
 * 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
}