1 /* 2 * Generate a VGA signal using a PIC32 microcontroller. 3 * 4 * Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "mips.h" 21 #include "pic32.h" 22 23 #define HFREQ_LIMIT 627 /* 20MHz cycles */ 24 #define VSYNC_START 1 /* horizontal lines, front porch end */ 25 #define VISIBLE_START 4 /* horizontal lines */ 26 #define VBP_START 516 /* horizontal lines, back porch start */ 27 #define VBP_END 531 /* horizontal lines, back porch end */ 28 29 /* Disable JTAG functionality on pins. */ 30 31 .section .devcfg0, "a" 32 .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ 33 34 /* 35 Set the oscillator to be the FRC oscillator with PLL, with peripheral clock 36 divided by 1, HS oscillator mode selected (for PLL), and FRCDIV+PLL selected. 37 With a system clock of 20MHz, the peripheral clock is therefore 20MHz. 38 39 The watchdog timer (FWDTEN) is also disabled. 40 */ 41 42 .section .devcfg1, "a" 43 .word 0xff7fcff9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<2:0> = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ 44 45 /* 46 Set the FRC oscillator PLL function with an input division of 4, an output 47 division of 2, a multiplication of 20, yielding a multiplication of 2.5. 48 This should take the FRC at 8MHz and produce a system clock of 20MHz. 49 */ 50 51 .section .devcfg2, "a" 52 .word 0xfff9ffdb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 1; DEVCFG2<6:4> = FPLLMUL<2:0> = 101; DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ 53 54 .text 55 .globl _start 56 57 _start: 58 /* 59 Configure RAM. 60 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 61 */ 62 63 la $v0, BMXCON 64 li $v1, (1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 65 sw $v1, CLR($v0) 66 67 /* Enable caching. */ 68 69 li $v0, CONFIG_CM_CACHABLE_NONCOHERENT 70 mtc0 $v0, CP0_CONFIG 71 nop 72 73 /* Get the RAM size. */ 74 75 la $v0, BMXDRMSZ 76 lw $v0, 0($v0) 77 78 /* Initialise the stack pointer. */ 79 80 lui $v1, 0x8000 /* 0x80000000 */ 81 addu $sp, $v0, $v1 /* sp = 0x80000000 + RAM size */ 82 83 /* Initialise the globals pointer. */ 84 85 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 86 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 87 88 /* Set pins for output and set outputs low. */ 89 90 jal init_pins 91 nop 92 93 /* Initialise the status register. */ 94 95 jal init_interrupts 96 nop 97 98 /* Initialise timer. */ 99 100 jal init_timer1 101 nop 102 103 /* Enable interrupts and loop. */ 104 105 jal enable_interrupts 106 nop 107 108 jal handle_error_level 109 nop 110 111 li $a1, 20000000 /* counter = 20000000 */ 112 li $a2, 6374 /* test timer scaling */ 113 li $s0, 0 /* line counter */ 114 la $s1, vfp_active /* current event */ 115 loop: 116 addiu $a1, $a1, -1 /* counter -= 1 */ 117 bnez $a1, loop /* until counter == 0 */ 118 nop 119 120 li $a1, 20000000 /* counter = 20000000 */ 121 122 la $v0, PORTA 123 la $v1, (1 << 2) /* PORTA<2> = RA2 */ 124 sw $v1, INV($v0) 125 126 _next: 127 j loop 128 nop 129 130 131 132 init_pins: 133 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 134 135 la $v0, CFGCON 136 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 137 sw $v1, CLR($v0) 138 139 la $v0, TRISB 140 li $v1, (7 << 7) | 15 /* TRISB<9:7> = RB9, RB8, RB7 = 0 */ 141 /* sw $v1, CLR($v0) */ 142 143 la $v0, PORTB 144 li $v1, (7 << 7) | 15 /* PORTB<9:7> = RB9, RB8, RB7 = 0 */ 145 /* sw $v1, CLR($v0) */ 146 147 init_leds: 148 la $v0, TRISA 149 li $v1, (1 << 2) /* TRISA<2> = RA2 = 0 */ 150 sw $v1, CLR($v0) 151 152 la $v0, TRISB 153 li $v1, (3 << 10) /* TRISB<11:10> = RB11, RB10 = 0 */ 154 sw $v1, CLR($v0) 155 156 clear_leds: 157 la $v0, PORTA 158 li $v1, (1 << 2) /* PORTA<2> = RA2 = 0 */ 159 sw $v1, CLR($v0) 160 161 la $v0, PORTB 162 li $v1, (3 << 10) /* PORTB<11:10> = RB11, RB10 = 0 */ 163 sw $v1, CLR($v0) 164 165 jr $ra 166 nop 167 168 169 170 /* Utilities. */ 171 172 handle_error_level: 173 mfc0 $t3, CP0_STATUS 174 li $t4, ~(STATUS_ERL | STATUS_EXL) 175 and $t3, $t3, $t4 176 mtc0 $t3, CP0_STATUS 177 jr $ra 178 nop 179 180 enable_interrupts: 181 mfc0 $t3, CP0_STATUS 182 nop 183 ori $t3, $t3, STATUS_IRQ | STATUS_IE 184 mtc0 $t3, CP0_STATUS 185 jr $ra 186 nop 187 188 init_interrupts: 189 mfc0 $t3, CP0_STATUS 190 li $t4, ~STATUS_BEV 191 and $t3, $t3, $t4 192 mtc0 $t3, CP0_STATUS /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 193 194 la $t3, _start 195 mtc0 $t3, CP0_EBASE /* EBASE = _start */ 196 197 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 198 mtc0 $t3, CP0_CAUSE 199 200 jr $ra 201 nop 202 203 204 205 /* Interrupt servicing. */ 206 207 .org 0x200 208 209 interrupt_handler: 210 211 /* Check for a timer interrupt condition. */ 212 213 la $v0, IFS0 214 lw $v1, 0($v0) 215 and $v1, $v1, (1 << 4) /* T1IF */ 216 beqz $v1, irq_exit 217 nop 218 219 /* Test timer scaling. */ 220 221 addiu $a2, $a2, -1 222 bnez $a2, irq_clear 223 nop 224 li $a2, 6374 225 226 /* Increment the line counter. */ 227 228 addiu $s0, $s0, 1 229 230 /* Jump to the event handler. */ 231 232 jalr $s1 233 nop 234 235 irq_clear: 236 237 /* Clear the timer interrupt condition. */ 238 239 la $v0, IFS0 240 li $v1, (1 << 4) /* IFS0<4> = T1IF = 0 */ 241 sw $v1, CLR($v0) 242 243 irq_exit: 244 eret 245 nop 246 247 248 249 /* Initialisation routines. */ 250 251 init_timer1: 252 253 /* Initialise Timer1 interrupt. */ 254 255 la $v0, T1CON 256 sw $zero, 0($v0) /* T1CON = 0 */ 257 nop 258 259 la $v0, TMR1 260 sw $zero, 0($v0) /* TMR1 = 0 */ 261 la $v0, PR1 262 li $v1, HFREQ_LIMIT 263 sw $v1, 0($v0) /* PR1 = HFREQ_LIMIT */ 264 265 /* Initialise Timer1 interrupt. */ 266 267 la $v0, IFS0 268 li $v1, (1 << 4) 269 sw $v1, CLR($v0) /* IFS0CLR: T1IF = 0 */ 270 la $v0, IPC1 271 li $v1, (7 << 2) 272 sw $v1, SET($v0) /* IPC1SET: T1IP = 7 */ 273 la $v0, IPC1 274 li $v1, 3 275 sw $v1, SET($v0) /* IPC1SET: T1IS = 3 */ 276 la $v0, IEC0 277 li $v1, (1 << 4) 278 sw $v1, SET($v0) /* IEC0SET: T1IE = 1 */ 279 280 /* Start timer. */ 281 282 la $v0, T1CON 283 li $v1, (1 << 15) /* ON = 1 */ 284 sw $v1, SET($v0) /* T1CONSET: ON = 1 */ 285 286 jr $ra 287 nop 288 289 290 291 /* Event routines. */ 292 293 /* Start of, and within, the vertical front porch. */ 294 295 vfp_start: 296 /* Clear vsync. */ 297 298 la $v0, PORTB 299 la $v1, (1 << 10) /* PORTB<10> = RB10 */ 300 sw $v1, CLR($v0) 301 302 /* Enter the active region. */ 303 304 la $s1, vfp_active 305 306 vfp_active: 307 /* Test for vsync. */ 308 309 subu $v0, $s0, VSYNC_START 310 bnez $v0, _vfp_active_ret 311 nop 312 313 /* Start the vsync region. */ 314 315 la $s1, vsync_start 316 317 _vfp_active_ret: 318 jr $ra 319 nop 320 321 322 323 /* Start of, and within, vsync. */ 324 325 vsync_start: 326 /* Set vsync. */ 327 328 la $v0, PORTB 329 la $v1, (1 << 10) /* PORTB<10> = RB10 */ 330 sw $v1, SET($v0) 331 332 /* Enter the active region. */ 333 334 la $s1, vsync_active 335 336 vsync_active: 337 /* Test for visible region. */ 338 339 subu $v0, $s0, VISIBLE_START 340 bnez $v0, _vsync_active_ret 341 nop 342 343 /* Start the visible region. */ 344 345 la $s1, visible_start 346 347 _vsync_active_ret: 348 jr $ra 349 nop 350 351 352 353 /* Start of, and within, the visible period. */ 354 355 visible_start: 356 /* Clear vsync. */ 357 358 la $v0, PORTB 359 la $v1, (1 << 10) /* PORTB<10> = RB10 */ 360 sw $v1, CLR($v0) 361 362 /* Enter the active region. */ 363 364 la $s1, visible_active 365 366 visible_active: 367 /* NOTE: Perform line activities. */ 368 369 la $v0, PORTB 370 la $v1, (1 << 11) /* PORTB<11> = RB11 */ 371 sw $v1, INV($v0) 372 373 /* Test for back porch. */ 374 375 subu $v0, $s0, VBP_START 376 bnez $v0, _visible_active_ret 377 nop 378 379 /* Start the back porch. */ 380 381 la $s1, vbp_active 382 383 _visible_active_ret: 384 jr $ra 385 nop 386 387 388 389 /* Within the vertical back porch. */ 390 391 vbp_active: 392 /* Test for front porch. */ 393 394 subu $v0, $s0, VBP_END 395 bnez $v0, _vbp_active_ret 396 nop 397 398 /* Start the front porch. */ 399 400 li $s0, 0 401 la $s1, vfp_start 402 403 _vbp_active_ret: 404 jr $ra 405 nop