#include #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" // Our assembled programs: // Each gets the name #include "hsync.pio.h" #include "vsync.pio.h" #include "rgb.pio.h" // Header file #include "vga_graphics.h" // Font file #include "glcdfont.c" // VGA timing constants #define H_ACTIVE 655 // (active + frontporch - 1) - one cycle delay for mov #define V_ACTIVE 479 // (active - 1) #define RGB_ACTIVE 319 // (horizontal active)/2 - 1 // #define RGB_ACTIVE 639 // change to this if 1 pixel/byte // Length of the pixel array, and number of DMA transfers #define TXCOUNT 153600 // Total pixels/2 (since we have 2 pixels per byte) // Pixel color array that is DMA's to the PIO machines and // a pointer to the ADDRESS of this color array. // Note that this array is automatically initialized to all 0's (black) unsigned char vga_data_array[TXCOUNT]; char * address_pointer = &vga_data_array[0] ; // Bit masks for drawPixel routine #define TOPMASK 0b11000111 #define BOTTOMMASK 0b11111000 // For drawLine #define swap(a, b) { short t = a; a = b; b = t; } // For writing text #define tabspace 4 // number of spaces for a tab // For accessing the font library #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) // For drawing characters unsigned short cursor_y, cursor_x, textsize ; char textcolor, textbgcolor, wrap; // Screen width/height #define _width 640 #define _height 480 void initVGA() { // Choose which PIO instance to use (there are two instances, each with 4 state machines) PIO pio = pio0; // Our assembled program needs to be loaded into this PIO's instruction // memory. This SDK function will find a location (offset) in the // instruction memory where there is enough space for our program. We need // to remember these locations! // // We only have 32 instructions to spend! If the PIO programs contain more than // 32 instructions, then an error message will get thrown at these lines of code. // // The program name comes from the .program part of the pio file // and is of the form uint hsync_offset = pio_add_program(pio, &hsync_program); uint vsync_offset = pio_add_program(pio, &vsync_program); uint rgb_offset = pio_add_program(pio, &rgb_program); // Manually select a few state machines from pio instance pio0. uint hsync_sm = 0; uint vsync_sm = 1; uint rgb_sm = 2; // Call the initialization functions that are defined within each PIO file. // Why not create these programs here? By putting the initialization function in // the pio file, then all information about how to use/setup that state machine // is consolidated in one place. Here in the C, we then just import and use it. hsync_program_init(pio, hsync_sm, hsync_offset, HSYNC); vsync_program_init(pio, vsync_sm, vsync_offset, VSYNC); rgb_program_init(pio, rgb_sm, rgb_offset, RED_PIN); ///////////////////////////////////////////////////////////////////////////////////////////////////// // ============================== PIO DMA Channels ================================================= ///////////////////////////////////////////////////////////////////////////////////////////////////// // DMA channels - 0 sends color data, 1 reconfigures and restarts 0 int rgb_chan_0 = 0; int rgb_chan_1 = 1; // Channel Zero (sends color data to PIO VGA machine) dma_channel_config c0 = dma_channel_get_default_config(rgb_chan_0); // default configs channel_config_set_transfer_data_size(&c0, DMA_SIZE_8); // 8-bit txfers channel_config_set_read_increment(&c0, true); // yes read incrementing channel_config_set_write_increment(&c0, false); // no write incrementing channel_config_set_dreq(&c0, DREQ_PIO0_TX2) ; // DREQ_PIO0_TX2 pacing (FIFO) channel_config_set_chain_to(&c0, rgb_chan_1); // chain to other channel dma_channel_configure( rgb_chan_0, // Channel to be configured &c0, // The configuration we just created &pio->txf[rgb_sm], // write address (RGB PIO TX FIFO) &vga_data_array, // The initial read address (pixel color array) TXCOUNT, // Number of transfers; in this case each is 1 byte. false // Don't start immediately. ); // Channel One (reconfigures the first channel) dma_channel_config c1 = dma_channel_get_default_config(rgb_chan_1); // default configs channel_config_set_transfer_data_size(&c1, DMA_SIZE_32); // 32-bit txfers channel_config_set_read_increment(&c1, false); // no read incrementing channel_config_set_write_increment(&c1, false); // no write incrementing channel_config_set_chain_to(&c1, rgb_chan_0); // chain to other channel dma_channel_configure( rgb_chan_1, // Channel to be configured &c1, // The configuration we just created &dma_hw->ch[rgb_chan_0].read_addr, // Write address (channel 0 read address) &address_pointer, // Read address (POINTER TO AN ADDRESS) 1, // Number of transfers, in this case each is 4 byte false // Don't start immediately. ); ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// // Initialize PIO state machine counters. This passes the information to the state machines // that they retrieve in the first 'pull' instructions, before the .wrap_target directive // in the assembly. Each uses these values to initialize some counting registers. pio_sm_put_blocking(pio, hsync_sm, H_ACTIVE); pio_sm_put_blocking(pio, vsync_sm, V_ACTIVE); pio_sm_put_blocking(pio, rgb_sm, RGB_ACTIVE); // Start the two pio machine IN SYNC // Note that the RGB state machine is running at full speed, // so synchronization doesn't matter for that one. But, we'll // start them all simultaneously anyway. pio_enable_sm_mask_in_sync(pio, ((1u << hsync_sm) | (1u << vsync_sm) | (1u << rgb_sm))); // Start DMA channel 0. Once started, the contents of the pixel color array // will be continously DMA's to the PIO machines that are driving the screen. // To change the contents of the screen, we need only change the contents // of that array. dma_start_channel_mask((1u << rgb_chan_0)) ; } // A function for drawing a pixel with a specified color. // Note that because information is passed to the PIO state machines through // a DMA channel, we only need to modify the contents of the array and the // pixels will be automatically updated on the screen. void drawPixel(short x, short y, char color) { // Range checks (640x480 display) if (x > 639) x = 639 ; if (x < 0) x = 0 ; if (y < 0) y = 0 ; if (y > 479) y = 479 ; // Which pixel is it? int pixel = ((640 * y) + x) ; // Is this pixel stored in the first 3 bits // of the vga data array index, or the second // 3 bits? Check, then mask. if (pixel & 1) { vga_data_array[pixel>>1] = (vga_data_array[pixel>>1] & TOPMASK) | (color << 3) ; } else { vga_data_array[pixel>>1] = (vga_data_array[pixel>>1] & BOTTOMMASK) | (color) ; } } void drawVLine(short x, short y, short h, char color) { for (short i=y; i<(y+h); i++) { drawPixel(x, i, color) ; } } void drawHLine(short x, short y, short w, char color) { for (short i=x; i<(x+w); i++) { drawPixel(i, y, color) ; } } // Bresenham's algorithm - thx wikipedia and thx Bruce! void drawLine(short x0, short y0, short x1, short y1, char color) { /* Draw a straight line from (x0,y0) to (x1,y1) with given color * Parameters: * x0: x-coordinate of starting point of line. The x-coordinate of * the top-left of the screen is 0. It increases to the right. * y0: y-coordinate of starting point of line. The y-coordinate of * the top-left of the screen is 0. It increases to the bottom. * x1: x-coordinate of ending point of line. The x-coordinate of * the top-left of the screen is 0. It increases to the right. * y1: y-coordinate of ending point of line. The y-coordinate of * the top-left of the screen is 0. It increases to the bottom. * color: 3-bit color value for line */ short steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { swap(x0, y0); swap(x1, y1); } if (x0 > x1) { swap(x0, x1); swap(y0, y1); } short dx, dy; dx = x1 - x0; dy = abs(y1 - y0); short err = dx / 2; short ystep; if (y0 < y1) { ystep = 1; } else { ystep = -1; } for (; x0<=x1; x0++) { if (steep) { drawPixel(y0, x0, color); } else { drawPixel(x0, y0, color); } err -= dy; if (err < 0) { y0 += ystep; err += dx; } } } // Draw a rectangle void drawRect(short x, short y, short w, short h, char color) { /* Draw a rectangle outline with top left vertex (x,y), width w * and height h at given color * Parameters: * x: x-coordinate of top-left vertex. The x-coordinate of * the top-left of the screen is 0. It increases to the right. * y: y-coordinate of top-left vertex. The y-coordinate of * the top-left of the screen is 0. It increases to the bottom. * w: width of the rectangle * h: height of the rectangle * color: 16-bit color of the rectangle outline * Returns: Nothing */ drawHLine(x, y, w, color); drawHLine(x, y+h-1, w, color); drawVLine(x, y, h, color); drawVLine(x+w-1, y, h, color); } void drawCircle(short x0, short y0, short r, char color) { /* Draw a circle outline with center (x0,y0) and radius r, with given color * Parameters: * x0: x-coordinate of center of circle. The top-left of the screen * has x-coordinate 0 and increases to the right * y0: y-coordinate of center of circle. The top-left of the screen * has y-coordinate 0 and increases to the bottom * r: radius of circle * color: 16-bit color value for the circle. Note that the circle * isn't filled. So, this is the color of the outline of the circle * Returns: Nothing */ short f = 1 - r; short ddF_x = 1; short ddF_y = -2 * r; short x = 0; short y = r; drawPixel(x0 , y0+r, color); drawPixel(x0 , y0-r, color); drawPixel(x0+r, y0 , color); drawPixel(x0-r, y0 , color); while (x= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; drawPixel(x0 + x, y0 + y, color); drawPixel(x0 - x, y0 + y, color); drawPixel(x0 + x, y0 - y, color); drawPixel(x0 - x, y0 - y, color); drawPixel(x0 + y, y0 + x, color); drawPixel(x0 - y, y0 + x, color); drawPixel(x0 + y, y0 - x, color); drawPixel(x0 - y, y0 - x, color); } } void drawCircleHelper( short x0, short y0, short r, unsigned char cornername, char color) { // Helper function for drawing circles and circular objects short f = 1 - r; short ddF_x = 1; short ddF_y = -2 * r; short x = 0; short y = r; while (x= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x4) { drawPixel(x0 + x, y0 + y, color); drawPixel(x0 + y, y0 + x, color); } if (cornername & 0x2) { drawPixel(x0 + x, y0 - y, color); drawPixel(x0 + y, y0 - x, color); } if (cornername & 0x8) { drawPixel(x0 - y, y0 + x, color); drawPixel(x0 - x, y0 + y, color); } if (cornername & 0x1) { drawPixel(x0 - y, y0 - x, color); drawPixel(x0 - x, y0 - y, color); } } } void fillCircle(short x0, short y0, short r, char color) { /* Draw a filled circle with center (x0,y0) and radius r, with given color * Parameters: * x0: x-coordinate of center of circle. The top-left of the screen * has x-coordinate 0 and increases to the right * y0: y-coordinate of center of circle. The top-left of the screen * has y-coordinate 0 and increases to the bottom * r: radius of circle * color: 16-bit color value for the circle * Returns: Nothing */ drawVLine(x0, y0-r, 2*r+1, color); fillCircleHelper(x0, y0, r, 3, 0, color); } void fillCircleHelper(short x0, short y0, short r, unsigned char cornername, short delta, char color) { // Helper function for drawing filled circles short f = 1 - r; short ddF_x = 1; short ddF_y = -2 * r; short x = 0; short y = r; while (x= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x1) { drawVLine(x0+x, y0-y, 2*y+1+delta, color); drawVLine(x0+y, y0-x, 2*x+1+delta, color); } if (cornername & 0x2) { drawVLine(x0-x, y0-y, 2*y+1+delta, color); drawVLine(x0-y, y0-x, 2*x+1+delta, color); } } } // Draw a rounded rectangle void drawRoundRect(short x, short y, short w, short h, short r, char color) { /* Draw a rounded rectangle outline with top left vertex (x,y), width w, * height h and radius of curvature r at given color * Parameters: * x: x-coordinate of top-left vertex. The x-coordinate of * the top-left of the screen is 0. It increases to the right. * y: y-coordinate of top-left vertex. The y-coordinate of * the top-left of the screen is 0. It increases to the bottom. * w: width of the rectangle * h: height of the rectangle * color: 16-bit color of the rectangle outline * Returns: Nothing */ // smarter version drawHLine(x+r , y , w-2*r, color); // Top drawHLine(x+r , y+h-1, w-2*r, color); // Bottom drawVLine(x , y+r , h-2*r, color); // Left drawVLine(x+w-1, y+r , h-2*r, color); // Right // draw four corners drawCircleHelper(x+r , y+r , r, 1, color); drawCircleHelper(x+w-r-1, y+r , r, 2, color); drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color); drawCircleHelper(x+r , y+h-r-1, r, 8, color); } // Fill a rounded rectangle void fillRoundRect(short x, short y, short w, short h, short r, char color) { // smarter version fillRect(x+r, y, w-2*r, h, color); // draw four corners fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color); fillCircleHelper(x+r , y+r, r, 2, h-2*r-1, color); } // fill a rectangle void fillRect(short x, short y, short w, short h, char color) { /* Draw a filled rectangle with starting top-left vertex (x,y), * width w and height h with given color * Parameters: * x: x-coordinate of top-left vertex; top left of screen is x=0 * and x increases to the right * y: y-coordinate of top-left vertex; top left of screen is y=0 * and y increases to the bottom * w: width of rectangle * h: height of rectangle * color: 3-bit color value * Returns: Nothing */ // rudimentary clipping (drawChar w/big text requires this) // if((x >= _width) || (y >= _height)) return; // if((x + w - 1) >= _width) w = _width - x; // if((y + h - 1) >= _height) h = _height - y; // tft_setAddrWindow(x, y, x+w-1, y+h-1); for(int i=x; i<(x+w); i++) { for(int j=y; j<(y+h); j++) { drawPixel(i, j, color); } } } // Draw a character void drawChar(short x, short y, unsigned char c, char color, char bg, unsigned char size) { char i, j; if((x >= _width) || // Clip right (y >= _height) || // Clip bottom ((x + 6 * size - 1) < 0) || // Clip left ((y + 8 * size - 1) < 0)) // Clip top return; for (i=0; i<6; i++ ) { unsigned char line; if (i == 5) line = 0x0; else line = pgm_read_byte(font+(c*5)+i); for ( j = 0; j<8; j++) { if (line & 0x1) { if (size == 1) // default size drawPixel(x+i, y+j, color); else { // big size fillRect(x+(i*size), y+(j*size), size, size, color); } } else if (bg != color) { if (size == 1) // default size drawPixel(x+i, y+j, bg); else { // big size fillRect(x+i*size, y+j*size, size, size, bg); } } line >>= 1; } } } inline void setCursor(short x, short y) { /* Set cursor for text to be printed * Parameters: * x = x-coordinate of top-left of text starting * y = y-coordinate of top-left of text starting * Returns: Nothing */ cursor_x = x; cursor_y = y; } inline void setTextSize(unsigned char s) { /*Set size of text to be displayed * Parameters: * s = text size (1 being smallest) * Returns: nothing */ textsize = (s > 0) ? s : 1; } inline void setTextColor(char c) { // For 'transparent' background, we'll set the bg // to the same as fg instead of using a flag textcolor = textbgcolor = c; } inline void setTextColor2(char c, char b) { /* Set color of text to be displayed * Parameters: * c = 16-bit color of text * b = 16-bit color of text background */ textcolor = c; textbgcolor = b; } inline void setTextWrap(char w) { wrap = w; } void tft_write(unsigned char c){ if (c == '\n') { cursor_y += textsize*8; cursor_x = 0; } else if (c == '\r') { // skip em } else if (c == '\t'){ int new_x = cursor_x + tabspace; if (new_x < _width){ cursor_x = new_x; } } else { drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); cursor_x += textsize*6; if (wrap && (cursor_x > (_width - textsize*6))) { cursor_y += textsize*8; cursor_x = 0; } } } inline void writeString(char* str){ /* Print text onto screen * Call tft_setCursor(), tft_setTextColor(), tft_setTextSize() * as necessary before printing */ while (*str){ tft_write(*str++); } }