AP KS GM-pov code.c

From Mech
Revision as of 01:14, 19 March 2009 by Kwang Sim (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search
/**********************************************
 * 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;
    }
    
}