AP KS GM-pov code.c

From Mech
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
/**********************************************
 * pov.c
 * Author:Greg McGlynn
 * Persistence-of-Vision Display project
 * Alexander Park, Kwang Xiong Sim, Grag McGlynn 
 * ME 333 Mechatronics - Winter 2008
 * Northwestern University 
 *
 *     This code runs on the rotating display PIC. The main loop displays the
 * pixels of the image one column at a time as the display rotations. It reads 
 * the LED states from the "pixels" array and turns on the appropriate LEDs, 
 * waits a few hundred microseconds, then moves on to the next column in the
 * image and repeats the process. The image is 193 pixels wide by 14 tall.
 *
 *     There are also interrupts happening that time the rotation of the POV,
 * adjusting the column number and the delay between columns so that the image
 * always appears in the same place no matter how fast the display is rotating.
 * There is an interrupt that occurs every 250 microseconds and increments a
 * counter. There is also an interrupt that occurs every time the hall sensor
 * detects the stationary magnet. This interrupt reads and resets the 250us 
 * counter, thereby determining how long the last full rotation of the display
 * took. It then adjusts the delay period between columns of pixels accordingly.
 *
 *    Finally, the display is also listening for incoming RS232 commands. An
 * incoming message triggers an interrupt to receive the command. All commands
 * are four bytes long. There are two classes of commands. One gives a new state
 * for a specified column of pixels in the image. This command is sent by the
 * PC applet. This command has the syntax
 *
 * <column # (two bytes)> <new LED states (two bytes)>
 *
 * The second class of command has the syntax
 *
 * <0xff> <0xff> <command type> <command argument>
 * 
 * Here command type can have four values:
 *    0x00 - clear          Clears the image (turns all pixels off). The 
 *                          argument is ignored.
 *    0x01 - image angle    Sets the image angle. The argument is a number from
 *                          0 to 192 that gives the new "home column."
 *    0x02 - scroll speed   Sets the scroll speed. The argument is a number from
 *                          0 to 255 that sets the scroll speed.
 *    0x03 - builtin image  Causes the display to show one of the builtin 
 *                          images. The argument is the image number (0-10)
 **********************************************/
 
#include <18f4520.h>
#fuses EC,NOLVP,NOWDT,NOPROTECT
#use delay(clock=40000000)
#use rs232(baud=9600, xmit=PIN_B3, rcv=PIN_B1)

//the 14 pixels in one column are stored in two 8-bit integers: pixels[col][0]
//and pixels[col][1]. The wiring and storing is done in such a way that 
//displaying the column can be accomplished by the two statements:
//output_c(pixels[column][1]);
//output_d(pixels[column][0]);
int pixels[193][2]; //the array storing the pixels of the image

int32 WIDTH = 193; //the total number of columns in the image

signed int16 home_column = 95; //the starting column. this column is the one
                               //shown as the display passes over the magnet
int16 col; //the offset of the current column from the home column

int32 delay = 800; //the wait between columns in microseconds

int32 us250 = 0; //the count of 250-us intervals in this rotation

//scrolling occurs by periodically adjusting the home column by one column
//either up or down:
int32 scroll_count = 0; //the count of 250-us intervals since we last scrolled
int32 us250_per_scroll = 100; //the number of 250-us intervals between scrolls
signed int16 scroll_dir = 1; //+1 is scrolling one way, -1 the other

//declarations of functions that show the builtin images by setting
//every element of the pixels array. These functions are hundreds of lines each.
void init0();  //"Hello World!!!"
void init1();  //"ME333 - Mechatronics"
void init2();  //"Gregory McGlynn"
void init3();  //"Alexander Park"
void init4();  //"Kwang Xiong Sim"
void init5();  //"Go Cats! N Go Cats! N" (with the Northwestern "N"!)
void init6();  //a Chinese message
void init7();  //some spirals
void init8();  //"R.I.P. XBEES #12&16"
void init9();  //A Mario level (our masterpiece)
void init10(); //"Goodbye World!!!" in inverted text (dark on bright)

//turn all the LEDs off:
void clear_image() {
    int16 x;
    for(x = 0; x < 193; x++) {
        pixels[x][0] = 0;
        pixels[x][1] = 0;
    }
}

//this interrupt is called every 250 microseconds
#int_timer2
void inc_us250() {
    us250++; //increment the count of 250-us intervals
    
    scroll_count++;
    if(scroll_count >= us250_per_scroll) {
        scroll_count = 0;
        
        //we can scroll by simply shifting the home column, since
        //everything is relative to it:
        home_column = (home_column + scroll_dir);
        if(home_column <   0) home_column += 193;
        if(home_column > 192) home_column -= 193;
    }
}

//this is called whenever the hall switch is triggered   
#int_ext
void hall_switch_triggered() {
    col = 0; //go to the home column
    
    //delay per column = (total rotation time) / (total # of columns)
    delay = (int32)250*us250/(WIDTH+9); 
    
    us250 = 0; //restart our timer for the rotation
}

int ignore = true; //ignore the first interrupt on B1; it's a false alarm

//this is called when we start to receive a 4-byte message from the controller
#int_ext1
void incoming_rs232() {
    unsigned int byte1, byte2, byte3, byte4;
    int16 updateCol;
    int16 timer_ticks_elapsed;
    
    //this interupt fires on startup even though there's no message. so 
    //ignore this first message
    if(ignore) {
        ignore = false;
        return;
    }
    
    //Servicing this interrupt takes several microseconds during which several
    //columns should go by. In an effort to reduce display weirdness while
    //we receive commands, we turn off all the LEDs while we read the RS232 
    //commands and time how long we're out for, then try to set col properly
    //when we're done. This doesn't actually work very well. A better way to
    //do it would be to have the LED updates done in a high-priority interrupt
    //service routing that could interrupt this one, so that the LEDs would
    //still get updated while we received commands.
    set_timer3(0);
    output_d(0);
    output_c(0);
    
    //get the four bytes
    byte1 = getc();
    byte2 = getc();
    byte3 = getc();
    byte4 = getc();
        
    //special commands start with the bytes 0xff 0xff
    if(byte1 == 0xff && byte2 == 0xff) {
        switch(byte3) {
            case 0x00: //clear
                clear_image();
                break;
            
            case 0x01: //set angle
                home_column = byte4;
                break;
                
            case 0x02: //set scroll speed
                if(byte4 >= 98 && byte4 <= 158) { //deadband, don't scroll
                    us250_per_scroll = 32000;
                    scroll_dir = 0;
                } else if (byte4 > 158) { //scroll forward
                    us250_per_scroll = 3000 / ((int16)byte4 - (int16)158);
                    scroll_dir = -1;
                } else { //scroll backward
                    us250_per_scroll = 3000 / ((int16)98 - (int16)byte4);
                    scroll_dir = 1;
                }
                break;
                
            case 0x03: //display builtin image
                switch(byte4) {
                    case  0:  init0(); break;
                    case  1:  init1(); break;
                    case  2:  init2(); break;
                    case  3:  init3(); break;
                    case  4:  init4(); break;
                    case  5:  init5(); break;
                    case  6:  init6(); break;
                    case  7:  init7(); break;
                    case  8:  init8(); break;
                    case  9:  init9(); break;
                    case 10: init10(); break;
                    default: break;
                }
                break;
        
            default:
                break;
        }
    } else {
        //if it's not a special command, it's a column update command
        //extract the # of the column being updated:
        updateCol = (((int16)byte1) << 8) | ((int16)byte2);
                
        //store the new column state:
        pixels[updateCol][0] = byte3;
        pixels[updateCol][1] = byte4;
       
    }
    
    //try to compensate for the time this isr took:
    timer_ticks_elapsed = get_timer3();
    col = col + timer_ticks_elapsed / (delay * 5 / 4);
    if(col > 192) col = col - 193;
    
}

void main() {
    int16 disp_column;

    //set up timers and interrupts interrupts:
    setup_timer_3(T3_INTERNAL | T3_DIV_BY_8); //ticks every 0.8us
    setup_timer_2(T2_DIV_BY_16, 51, 3); //interrupts every 250us
    enable_interrupts(INT_TIMER2); //250us interrupt
    enable_interrupts(INT_EXT);    //hall sensor interrupt
    ext_int_edge(0, H_TO_L);
    enable_interrupts(INT_EXT1);   //rs232 interrupt
    ext_int_edge(1, H_TO_L);
    enable_interrupts(GLOBAL);
    
    init1(); //"ME333 - Mechatronics"
    
    col = 0;
    while(true) { //infinite loop
        //everything is relative to the home column:
        disp_column = (home_column + col);
        if(disp_column > 192) disp_column = disp_column - 192;
    
        //set the LED states:
        output_c(pixels[disp_column][1]);
        output_d(pixels[disp_column][0]);
                        
        //wait while we rotate a bit:
        delay_us(delay);
        
        
        //increment the column number:
        col = col + 1;
        if(col > 192) col = 0;
    }
    
}