Three-pin 640x400 VGA Console Mode

Dependents:   projet_AWA_testVGA2

vga640x400.c

Committer:
Ivop
Date:
2011-07-08
Revision:
1:30d7a3e8e7a3
Parent:
0:78fa88bb24cb
Child:
2:70daa29e01bd

File content as of revision 1:30d7a3e8e7a3:

/*
 * 640x400 70Hz VGA Driver
 *
 * Copyright (C) 2011 by Ivo van Poorten <ivop@euronet.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 "mbed.h"

#include <string.h>

#define DEBUG_VGA 0

#if DEBUG_VGA       // DEBUG messages on UART0
#include <stdio.h>
#define dbprintf(...) printf(__VA_ARGS__)
#else
#define dbprintf(...)
#endif
 
// -----------------------------------------------------------------------------------

unsigned char text_buffer[80*25];
unsigned char *font;

static unsigned line_counter;
static unsigned char scanline0[100], scanline1[100];    // 100*8=800 color clocks
static unsigned char *curline = scanline1+20;

#define NCHARS 256

struct dma_lli {
    const void *source;
    const void *dest;
    const struct dma_lli *next;
    unsigned control_word;
};

extern const struct dma_lli dma_lli1;

static const struct dma_lli dma_lli0 = {
    scanline0, (void*)FL_I2STXFIFO, &dma_lli1, 0x84489019 // control word is explained below
};
static const struct dma_lli dma_lli1 = {
    scanline1, (void*)FL_I2STXFIFO, &dma_lli0, 0x84489019
};

// -----------------------------------------------------------------------------------

#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,!);
}   

// -----------------------------------------------------------------------------------

#if DEBUG_VGA
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);
}
#endif

// -----------------------------------------------------------------------------------

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);
}

// -----------------------------------------------------------------------------------

static void init_dma_controller(void) {
    fl_power_gpdma(FL_ON);
    fl_dma_enable(FL_ENABLE);
    while (!fl_dma_is_enabled()) ;

    fl_dma_set_srcaddr (0, dma_lli0.source);
    fl_dma_set_destaddr(0, dma_lli0.dest);
    fl_dma_set_next_lli(0, dma_lli0.next);
    fl_dma_channel_control(0,       // control word
        25,
        4, 4,               // src and dest burst size
        32, 32,             // src and dest width
        FL_SRC_INCREMENT,
        FL_NO_DEST_INCREMENT,
        FL_ON               // terminal count interrupt
    );
//    if ((fl_dma_channel_get_control_mask(0) | 25) != dma_lli0.control_word) {
//        dbprintf("%08x and %08x\r\n", fl_dma_channel_get_control_mask(0) | 25, dma_lli0.control_word);
//        dbprintf("control_word mismatch\r\n");
//        while(1);
//    }

    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
    );
    
    fl_nvic_interrupt_set_enable(FL_NVIC_INT_DMA);
}

// -----------------------------------------------------------------------------------

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);
}

// -----------------------------------------------------------------------------------

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
    fl_pwm_config_match(0, FL_ON, FL_ON, FL_OFF);   // interrupt, reset, !stop
    fl_pwm_set_match(0, 800);   // 800 color clocks
  
#define HSHIFT 48
 
    fl_pwm_set_match(1, 97+HSHIFT);    // go high at 97
    fl_pwm_set_match(2, 1+HSHIFT);     // go low at 1
    fl_pwm_config_edges(2, FL_DOUBLE_EDGE);
    fl_pwm_output_enable(2, FL_ENABLE);
}

// -----------------------------------------------------------------------------------

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;
    state = state_before_vsync;
}

static void state_clearing_buffers(void) {
    int i;
    
    if (line_counter < 441) return;

    for (i=0; i<20; i++) {
        *(curline+i   ) = 0;
        *(curline+i+20) = 0;
        *(curline+i+40) = 0;
        *(curline+i+60) = 0;
    }
        
    if (line_counter == 442) {
        state = state_blank_area;
    }
}

static unsigned char *fp, *tb;

static void state_visible_area(void) {
    int x;
    unsigned char *sp = curline, *tbp = tb;

    for (x=0; x<20; x++) {
        *sp++ = *(fp + *tbp++);
        *sp++ = *(fp + *tbp++);
        *sp++ = *(fp + *tbp++);
        *sp++ = *(fp + *tbp++);
    }

    fp += NCHARS;
    if (fp == font+NCHARS*16) {
        fp = font;
        tb += 80;
    }

    if (line_counter == 438) {
        state = state_clearing_buffers;
    }
}

static void state_after_vsync(void) {
    if (line_counter != 38) return;
    
    fp = font;
    tb = text_buffer;
    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;
}

extern "C" void DMA_IRQHandler(void) __irq {
    fl_dma_clear_terminal_count_interrupt_request(0);
    line_counter++;
    curline = curline == scanline0+20 ? scanline1+20 : scanline0+20;
    state();
}

// -----------------------------------------------------------------------------------

void init_vga(void) {
    fl_power_off_all_peripherals();

    init_pll0(FL_PLL0_CLOCK_SOURCE_MAIN, 2, 25, 3); // 100MHz

#if DEBUG_VGA
    init_uart0(0, 0, 651);                          // 100MHz/651/16=9600.6 (default 8N1)
#endif

    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);
}