CommonPIC32

Annotated examples/vga-pmp/main.c

50:ad11390f6d08
2018-10-24 Paul Boddie Extracted the display state machine, moving it into a separate library module.
paul@35 1
/*
paul@35 2
 * Generate a VGA signal using a PIC32 microcontroller.
paul@35 3
 *
paul@35 4
 * Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk>
paul@35 5
 *
paul@35 6
 * This program is free software: you can redistribute it and/or modify
paul@35 7
 * it under the terms of the GNU General Public License as published by
paul@35 8
 * the Free Software Foundation, either version 3 of the License, or
paul@35 9
 * (at your option) any later version.
paul@35 10
 *
paul@35 11
 * This program is distributed in the hope that it will be useful,
paul@35 12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
paul@35 13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
paul@35 14
 * GNU General Public License for more details.
paul@35 15
 *
paul@35 16
 * You should have received a copy of the GNU General Public License
paul@35 17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
paul@35 18
 */
paul@35 19
paul@35 20
paul@35 21
#include "pic32_c.h"
paul@35 22
#include "init.h"
paul@35 23
#include "debug.h"
paul@47 24
paul@47 25
/* Specific functionality. */
paul@47 26
paul@35 27
#include "main.h"
paul@47 28
#include "devconfig.h"
paul@35 29
#include "vga.h"
paul@48 30
#include "display.h"
paul@49 31
#include "display_config.h"
paul@50 32
#include "vga_display.h"
paul@35 33
paul@35 34
paul@48 35
paul@35 36
/* Pixel data. */
paul@35 37
paul@35 38
static const uint8_t zerodata[ZERO_LENGTH] = {0};
paul@35 39
paul@35 40
paul@35 41
paul@35 42
/* Blink an attached LED with delays implemented using a loop. */
paul@35 43
paul@35 44
static void blink(uint32_t delay, uint32_t port, uint32_t pins)
paul@35 45
{
paul@35 46
    uint32_t counter;
paul@35 47
paul@35 48
    /* Clear outputs (LED). */
paul@35 49
paul@35 50
    CLR_REG(port, pins);
paul@35 51
paul@35 52
    while (1)
paul@35 53
    {
paul@35 54
        counter = delay;
paul@35 55
paul@35 56
        while (counter--) __asm__("");	/* retain loop */
paul@35 57
paul@35 58
        /* Invert outputs (LED). */
paul@35 59
paul@35 60
        INV_REG(port, pins);
paul@35 61
    }
paul@35 62
}
paul@35 63
paul@35 64
paul@35 65
paul@35 66
/* Main program. */
paul@35 67
paul@35 68
void main(void)
paul@35 69
{
paul@50 70
    init_vga(&display_config, start_visible, update_visible, stop_visible,
paul@50 71
                              vsync_high, vsync_low);
paul@50 72
paul@49 73
    test_linedata(&display_config);
paul@35 74
paul@35 75
    init_memory();
paul@35 76
    init_pins();
paul@35 77
    init_outputs();
paul@35 78
paul@35 79
    unlock_config();
paul@35 80
    config_oc();
paul@35 81
    config_uart();
paul@35 82
    lock_config();
paul@35 83
paul@35 84
    init_dma();
paul@35 85
    init_pm();
paul@35 86
paul@35 87
    /* Configure parallel master mode. */
paul@35 88
paul@35 89
    pm_init(0, 0b10);
paul@35 90
    pm_set_output(0, 1, 0);
paul@35 91
    pm_on(0);
paul@35 92
paul@35 93
    /* Initiate DMA on the Timer2 interrupt transferring line data to the first
paul@35 94
       byte of PORTB. Do not enable the channel for initiation until the visible
paul@35 95
       region is about to start. */
paul@35 96
paul@35 97
    dma_init(0, 3);
paul@35 98
    dma_set_auto_enable(0, 1);
paul@35 99
    dma_set_interrupt(0, T2, 1);
paul@50 100
    dma_set_transfer(0, PHYSICAL((uint32_t) display_config.screen_start),
paul@50 101
                        display_config.line_length,
paul@35 102
                        HW_PHYSICAL(PM_REG(0, PMxDIN)), 1,
paul@45 103
                        TRANSFER_CELL_SIZE);
paul@35 104
paul@48 105
    /* Enable DMA on the preceding channel's completion, with the timer event
paul@48 106
       initiating the transfer. This "reset" or "zero" transfer is employed to
paul@48 107
       set the pixel level to black in a connected flip-flop. Without the
paul@48 108
       flip-flop it is superfluous. */
paul@35 109
paul@35 110
    dma_init(1, 3);
paul@35 111
    dma_set_chaining(1, dma_chain_previous);
paul@48 112
    dma_set_interrupt(1, T2, 1);
paul@35 113
    dma_set_transfer(1, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH,
paul@35 114
                        HW_PHYSICAL(PM_REG(0, PMxDIN)), 1,
paul@35 115
                        ZERO_LENGTH);
paul@35 116
    dma_set_receive_events(1, 1);
paul@35 117
paul@35 118
    /* Configure a timer for the horizontal sync. The timer has no prescaling
paul@35 119
       (0). */
paul@35 120
paul@35 121
    timer_init(2, 0, HFREQ_LIMIT);
paul@35 122
    timer_on(2);
paul@35 123
paul@35 124
    /* Horizontal sync. */
paul@35 125
paul@35 126
    /* Configure output compare in dual compare (continuous output) mode using
paul@35 127
       Timer2 as time base. The interrupt condition drives the first DMA channel
paul@35 128
       and is handled to drive the display state machine. */
paul@35 129
paul@35 130
    oc_init(1, 0b101, 2);
paul@35 131
    oc_set_pulse(1, HSYNC_END);
paul@35 132
    oc_set_pulse_end(1, HSYNC_START);
paul@35 133
    oc_init_interrupt(1, 7, 3);
paul@35 134
    oc_on(1);
paul@35 135
paul@35 136
    /* Vertical sync. */
paul@35 137
paul@35 138
    /* Configure output compare in single compare (output driven low) mode using
paul@35 139
       Timer2 as time base. The unit is enabled later. It is only really used to
paul@35 140
       achieve precisely-timed level transitions in hardware. */
paul@35 141
paul@35 142
    oc_init(2, 0b010, 2);
paul@35 143
    oc_set_pulse(2, 0);
paul@35 144
paul@47 145
    uart_init(1, FPB, 115200);
paul@35 146
    uart_on(1);
paul@35 147
paul@35 148
    interrupts_on();
paul@35 149
paul@35 150
    blink(3 << 24, PORTA, 1 << 2);
paul@35 151
}
paul@35 152
paul@35 153
paul@35 154
paul@35 155
/* Exception and interrupt handlers. */
paul@35 156
paul@35 157
void exception_handler(void)
paul@35 158
{
paul@35 159
    blink(3 << 12, PORTA, 1 << 2);
paul@35 160
}
paul@35 161
paul@35 162
void interrupt_handler(void)
paul@35 163
{
paul@35 164
    uint32_t ifs;
paul@35 165
paul@35 166
    /* Check for a OC1 interrupt condition. */
paul@35 167
paul@35 168
    ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF);
paul@35 169
paul@35 170
    if (ifs)
paul@35 171
    {
paul@50 172
        vga_interrupt_handler();
paul@35 173
        CLR_REG(OCIFS, ifs);
paul@35 174
    }
paul@35 175
}
paul@35 176
paul@35 177
paul@35 178
paul@50 179
/* Enable the channel for the next line. */
paul@35 180
paul@50 181
void start_visible(vga_display_t *vga_display)
paul@50 182
{
paul@50 183
    dma_set_source(0, PHYSICAL((uint32_t) vga_display->linedata),
paul@50 184
                               display_config.line_length);
paul@35 185
    dma_on(0);
paul@35 186
}
paul@35 187
paul@50 188
/* Update the channel for the next line. */
paul@35 189
paul@50 190
void update_visible(vga_display_t *vga_display)
paul@50 191
{
paul@50 192
    dma_set_source(0, PHYSICAL((uint32_t) vga_display->linedata),
paul@50 193
                               display_config.line_length);
paul@50 194
}
paul@48 195
paul@50 196
/* Disable the channel for the next line. */
paul@35 197
paul@50 198
void stop_visible(vga_display_t *vga_display)
paul@50 199
{
paul@35 200
    dma_off(0);
paul@35 201
}
paul@35 202
paul@50 203
/* Bring vsync low (single compare, output driven low) when the next line
paul@50 204
   starts. */
paul@35 205
paul@50 206
void vsync_low(void)
paul@50 207
{
paul@35 208
    oc_init(2, 0b010, 2);
paul@35 209
    oc_on(2);
paul@35 210
}
paul@35 211
paul@50 212
/* Bring vsync high (single compare, output driven high) when the next line
paul@50 213
   starts. */
paul@35 214
paul@50 215
void vsync_high(void)
paul@50 216
{
paul@35 217
    oc_init(2, 0b001, 2);
paul@35 218
    oc_on(2);
paul@35 219
}
paul@35 220
paul@35 221
paul@35 222
paul@35 223
/* Peripheral pin configuration. */
paul@35 224
paul@35 225
void config_oc(void)
paul@35 226
{
paul@35 227
    /* Map OC1 to RPB4. */
paul@35 228
paul@35 229
    REG(RPB4R) = 0b0101;        /* RPB4R<3:0> = 0101 (OC1) */
paul@35 230
paul@35 231
    /* Map OC2 to RPB5. */
paul@35 232
paul@35 233
    REG(RPB5R) = 0b0101;        /* RPB5R<3:0> = 0101 (OC2) */
paul@35 234
}
paul@35 235
paul@35 236
void config_uart(void)
paul@35 237
{
paul@35 238
    /* Map U1RX to RPB13. */
paul@35 239
paul@35 240
    REG(U1RXR) = 0b0011;        /* U1RXR<3:0> = 0011 (RPB13) */
paul@35 241
paul@35 242
    /* Map U1TX to RPB15. */
paul@35 243
paul@35 244
    REG(RPB15R) = 0b0001;       /* RPB15R<3:0> = 0001 (U1TX) */
paul@35 245
paul@35 246
    /* Set RPB13 to input. */
paul@35 247
paul@35 248
    SET_REG(TRISB, 1 << 13);
paul@35 249
}