Close

First PIC16 Fractals

A project log for PIC Graphics Demo

Generate 640x480 64-color VGA graphics with an 8-bit PIC and an SRAM framebuffer

ted-yapoTed Yapo 12/12/2016 at 01:291 Comment

My PIC16F1718's came in a recent DigiKey box; I was very happy to realize that they're pin-compatible with the PIC16F723A, for which I had a bunch of breakout boards made a while ago. All the purple ones are used up, so I had to mount it on a green one:

I ignored the crystal site on the board and instead decided to run this one on the internal 32 MHz oscillator. I don't have the proto-boards for the VGA interface yet, so I began by sending generated pixels over the UART to be collected into an image with a small python program on a PC.

The PIC16F1718 uses 14-bit instructions, so 585 of them fit into 1 kB. I was able to get a basic Mandelbrot set generator in 476 instructions using straight C-code. This was really convenient, because it allowed me to do the code development on a PC, where the program runs in about a hundred milliseconds (wall-clock time). On the PIC, the code took 1 hour and 45 minutes :-) A lot of this time is wasted on transferring pixels over the serial port, which I could only get going reliably at 19200 baud, possibly due to the tolerance of the internal oscillator. The transmission overhead could be improved significantly, but there's no point, because the UART won't be used in the final system.

The output is almost as expected, although some last-minute experiments in optimization flipped the coordinate system - this is easily fixed. Again, I'm saving color output for the final system with the VGA interface hardware.

When the hardware is ready, I'll need to add code to initialize the video SRAM and write the pixels in - I'm confident this will all fit, since I have 109 more instructions to play with, and this has all been done with the inferior free version of the XC8 compiler. A 60-day trial of the pro version would undoubtedly shrink the code, and I could always move to assembly if required.

So far, the center and zoom of the image are hard-coded. I'm considering adding a zoom knob and pan controls with simple potentiometers connected to a few analog input pins. Theoretically, it could be considered interactive, if you don't mind waiting two hours between renderings.

Ray Tracing Progress

I'm a few days into assembly experiments to create a vector math library I can use to implement a ray-tracer. I have a version running in C on the PC, and it probably isn't possible in C in 585 instructions on this PIC. Even the assembly version won't be easy to fit, but it's fun to think about, and I have the fractals as a backup plan. We shall see how it turns out.

PIC Mandelbrot Code

Here's the c-code for the fractal generator. It uses 16-bit fixed-point math, and a klunky serial handshake sequence to send pixels to the PC, but it works. I haven't tried to optimize it yet, because it fit as-is.

/* 
 * File:   pic1718_bringup.c
 * Author: tyapo
 *
 * Created on December 6, 2014, 7:37 PM
 */
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pic16f1718.h>

// CONFIG1
#pragma config FOSC = INTOSC
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config FCMEN = ON

// CONFIG2
#pragma config WRT = ALL
#pragma config PPS1WAY = OFF
#pragma config ZCDDIS = ON
#pragma config PLLEN = ON
#pragma config STVREN = OFF
#pragma config BORV = LO
#pragma config LPBOR = OFF
#pragma config LVP = ON

void SendByte(uint8_t b)
{
  while(!TRMT){
    continue;
  }
  TXREG = b;
}

void SendPacket(uint16_t row, uint16_t col, uint8_t iter)
{
 // SendByte( 255 );
  SendByte( (row & 0xff00) >> 8 );
  SendByte( (row & 0x00ff) >> 0 );
  SendByte( (col & 0xff00) >> 8 );
  SendByte( (col & 0x00ff) >> 0 );
  SendByte( iter );
}

typedef enum
{
  BAUD_RATE_300,
  BAUD_RATE_1200,
  BAUD_RATE_2400,
  BAUD_RATE_4800,
  BAUD_RATE_9600,
  BAUD_RATE_19200,
  BAUD_RATE_38400,
  BAUD_RATE_57600,
  BAUD_RATE_115200,
  BAUD_RATE_230400,
  BAUD_RATE_384000,
  BAUD_RATE_576000,
  BAUD_RATE_1152000
} BaudRate_t;

void SetBaudRate(BaudRate_t rate){
  // wait for any outgoing packet to finish
  while(!TRMT){
    continue;
  }

  // disable RX/TX during baud rate change
  CREN = 0;
  TXEN = 0;

  switch(rate){
  case BAUD_RATE_300:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 6666;
    break;
  case BAUD_RATE_1200:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 3332;
    break;
  case BAUD_RATE_2400:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 832;
    break;
  case BAUD_RATE_9600:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 207;
    break;
  case BAUD_RATE_19200:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 103;
    break;
  case BAUD_RATE_57600:
    BRG16 = 1;
    BRGH = 0;
    SPBRG = 34;
    break;
  }

  // configure AUSART RX/TX
  TXEN = 1;  // TX
  CREN = 1;  // RX
}

void SetupPeripherals() {
  // intosc 32 MHz
  OSCCON = 0b11110000;

  // configure PPS for EUSART
  RXPPS = 0b00010111;  // RC7 for RX
  RC6PPS = 0b00010100; // RC6 for TX

  // configure AUSART for aynch operation
  SYNC = 0;
  SPEN = 1;
  ANSELC = 0;
  //SCKP = 0;

  SetBaudRate(BAUD_RATE_19200);

  // configure EUSART for TX/RX
  TXEN = 1;
  CREN = 1;

  // set TRIS bits
  PORTA = 0x00;
  TRISA = 0x00;
  PORTB = 0x00;
  TRISB = 0x00;
  PORTC = 0x00;
  TRISC = 0x80;
}

#define S 12
#define FP(x) ((int16_t)((x) * (1<<S)))

#define ASPECT   (640./480.)
#define WIDTH     2.5
#define IMAG_MIN  FP(-1.25)
#define IMAG_STEP FP(WIDTH / 480.)
#define REAL_MIN  FP(-2.5)
#define REAL_STEP FP(ASPECT * WIDTH / 640.)
#define ESCAPE_RADIUS FP(4.)

int main() {
  SetupPeripherals();

#if 0
  int16_t row = 480;
  do {
    int16_t col = 640;
    do {
      // wait for rx'd byte to sync
      while (!RCIF) {
        continue;
      }
      SendByte(RCREG);
      SendPacket(row, col, 127);
    } while (--col);
  } while (--row);

#else
  int16_t dc = REAL_STEP;
  int16_t dd = IMAG_STEP;
  int16_t d = IMAG_MIN;  
  int16_t row = 480;
  do {
    int16_t c = REAL_MIN;
    int16_t col = 640;
    do {
      int16_t a = 0;
      int16_t b = 0;
      uint8_t iter = 0;
      while(iter++ < 254){
        int32_t aa32 = ((int32_t)a * (int32_t)a);
        if (aa32 & 0xf8000000){
          break;
        }
        int16_t aa = aa32 >> S;

        int32_t bb32 = ((int32_t)b * (int32_t)b);
        if (bb32 & 0xf8000000){
          break;
        }
        int16_t bb = bb32 >> S;

        if (aa > ESCAPE_RADIUS ||
            bb > ESCAPE_RADIUS ||
            aa + bb > ESCAPE_RADIUS){
          break;
        }
        b = (((int32_t)a * (int32_t)b) >> (S-1)) + d;
        a = aa - bb + c;
      }
      c += dc;

      // wait for rx'd byte to sync
      while (!RCIF) {
        continue;
      }
      SendByte(RCREG);
      SendPacket(row, col, iter);
    } while (--col);
    d += dd;
  } while (--row);
#endif
  return 0;
}

Discussions

Yann Guidon / YGDES wrote 12/12/2016 at 04:28 point

Yay !

  Are you sure? yes | no