/* AVR ATmega32 KS0108 128x64 LCD controller and Breakout-style game Version: 1.0 Author: Pontus "Frigolit" Rodling Website: http://www.frigolit.net Licensed under the Creative Commons Attribution-Share Alike 2.5 License For more info, visit http://creativecommons.org/licenses/by-sa/2.5/ */ /* === Introduction === This is a bit of mixed code, the original code is a controller for a 128x64 graphical LCD (KS0108 chip). However when I got that part working I wanted to do something more, so I added code for a simple breakout game too. So you could pretty much call this a work in progress. === Pins === LCD ATmega32 ----------------------------------------- Data pins [0:7] Port A [0:7] CS2 Port C [7] CS1 Port C [6] RS Port C [5] E Port C [4] BK Port C [3] Buttons ATmega32 ----------------------------------------- Left Port C [2] Right Port C [1] Start/Fire Port C [0] NOTE: Buttons should have pull-down resistor connected and go high when pressed. === Fuse bits === Make sure the micro is running at 8MHz and JTAG support is disabled (since it interferes with port C) by setting the following bits: [X] = Programmed (bit = 0), [ ] = Unprogrammed (bit = 1) [ ] JTAGEN [X] CKSEL3 [ ] CKSEL 2 [X] CKSEL1 [X] CKSEL0 IMPORTANT: These values are correct for the ATmega32, if you're using another device please refer to it's datasheet! === Bitmap format === The bitmap format is pretty simple. The first two bytes determine width and height of the image. The graphics data is organized as 8 pixels on the Y axis per byte, just like the LCD. You can generate bitmaps using the datasrc/bitmapgen.pike Pike-script (http://pike.ida.liu.se). */ #include #include #include #include #include "data/bitmaps.h" #define sbi(var, mask) ((var) |= (uint8_t)(1 << mask)) #define cbi(var, mask) ((var) &= (uint8_t)~(1 << mask)) // Buttons #define B1 (PINC & 0b00000100) #define B2 (PINC & 0b00000010) #define B3 (PINC & 0b00000001) // LCD control pins #define LCD_CS2_HIGH sbi(PORTC, 7) #define LCD_CS2_LOW cbi(PORTC, 7) #define LCD_CS1_HIGH sbi(PORTC, 6) #define LCD_CS1_LOW cbi(PORTC, 6) #define LCD_RS_HIGH sbi(PORTC, 5) #define LCD_RS_LOW cbi(PORTC, 5) #define LCD_E_HIGH sbi(PORTC, 4) #define LCD_E_LOW cbi(PORTC, 4) #define LCD_BK_OFF sbi(PORTC, 3) #define LCD_BK_ON cbi(PORTC, 3) // UART defines /* #define FOSC 8000000 #define BAUD 9600 #define UBRR FOSC/16/BAUD-1 */ // LCD functions void lcd_init(void); void lcd_clear(void); void lcd_reset(void); void lcd_spixel(unsigned int x, unsigned int y); void lcd_cpixel(unsigned int x, unsigned int y); void lcd_update(void); void lcd_setx(unsigned char x); void lcd_sety(unsigned char y); void lcd_write(unsigned char a); void lcd_data(unsigned char a); void lcd_instruction(unsigned char a); void lcd_enable(unsigned char b); void lcd_e(void); // Delay functions void delay_ms(unsigned long i); void delay(unsigned long i); // Drawing functions void draw_bitmap(const unsigned char *bmp, unsigned int x, unsigned int y); // Graphics buffers unsigned char *pixels; // Pixel buffer unsigned char *pixelmod; // Pixel modification indicator unsigned char pixelbufmod; // Pixel buffer modification indicator // Game functions void clear_player(void); void draw_player(void); void draw_bricks(void); void ball_update(void); void bricks_update(void); void reset_game(void); // Player variables uint8_t plrx; // Ball variables uint8_t ballx; uint8_t bally; int ballxv; int ballyv; uint8_t ballrun; // Bricks char bricks[16]; // Entrypoint int main(void) { // Port initialization DDRA = 0xFF; // Set all pins on port A as output DDRC = 0xF8; // Set 5 pins on port A as output and 3 as input PORTA = 0x00; // Clear port A output PORTC = 0x00; // Clear port C output LCD_BK_OFF; // Backlight off // Allocate pixel buffers pixels = (unsigned char *)malloc(1024 * sizeof(unsigned char)); pixelmod = (unsigned char *)malloc(128 * sizeof(unsigned char)); LCD_E_LOW; // Set E low delay_ms(100); // Wait for LCD to power up completely lcd_reset(); // Reset LCD (enable and clear) LCD_BK_ON; // Backlight ON // Draw splash screen draw_bitmap(bmp_splash, 0, 0); lcd_update(); // Wait for button press while (!B3); delay_ms(10); while (B3); delay_ms(10); // Reset game reset_game(); // Game loop (TODO: use a timer interrupt instead) int n = 0; while (1) { if (B1 && plrx) { clear_player(); plrx--; draw_player(); } if (B2 && plrx < 128 - 16) { clear_player(); plrx++; draw_player(); } if (!ballrun && B3) ballrun = 1; n++; if (!(n % 3) || !ballrun) { ball_update(); } delay_ms(30); lcd_update(); } // Init UART /* UBRR0 = UBRR; // Set Baudrate UCSR0C = (3<= 126) { // Right border hit, bounce ballx = 126; ballxv = -ballxv; } if (bally <= 1) { // Top border hit, bounce bally = 1; ballyv = -ballyv; } else if (bally == 60 && ballx + 1 >= plrx && ballx - 1 <= plrx + 16) { // Paddle hit, bounce ballyv = -ballyv; } else if (bally == 63) { // Bottom border hit, die ballx = plrx + 8; bally = 60; ballxv = 1; ballyv = -1; ballrun = 0; draw_player(); delay_ms(1000); } // Brick collision checking uint8_t i; uint8_t n; uint8_t x; uint8_t y; uint8_t rx = 0; uint8_t ry = 0; for (i = 0; i < 16 * 8; i++) { if (!(bricks[i % 16] & (1 << i / 16))) continue; // Get X/Y coordinates x = (i % 16) * 8; y = (i / 16) * 4; // Check for collision if (ballx + 1 >= x && ballx - 1 <= x + 7 && bally + 1 >= y && bally - 1 <= y + 3) { if (ballx + 1 >= x && ballx -1 <= x + 7 && (bally + 1 == y || bally - 1 == y + 3)) { bricks[i % 16] &= ~(1 << i / 16); // Clear brick ry = 1; for (n = 0; n < 8; n++) { lcd_cpixel(x + n, y); lcd_cpixel(x + n, y + 1); lcd_cpixel(x + n, y + 2); lcd_cpixel(x + n, y + 3); } } else if (bally + 1 >= y && bally -1 <= y + 3 && (ballx + 1 == x || ballx - 1 == x + 7)) { bricks[i % 16] &= ~(1 << i / 16); // Clear brick rx = 1; for (n = 0; n < 8; n++) { lcd_cpixel(x + n, y); lcd_cpixel(x + n, y + 1); lcd_cpixel(x + n, y + 2); lcd_cpixel(x + n, y + 3); } } } } // Position and velocity fix on collision if (rx) { ballxv = -ballxv; ballx += ballxv * 2; } if (ry) { ballyv = -ballyv; bally += ballyv * 2; } // Check if all bricks have been destroyed if (rx || ry) { // Don't ask, I just thought this was probably faster than a for-loop if (!bricks[0] && !bricks[2] && !bricks[3] && !bricks[4] && !bricks[5] && !bricks[6] && !bricks[7] && !bricks[8] && !bricks[9] && !bricks[10] && !bricks[11] && !bricks[12] && !bricks[13] && !bricks[14] && !bricks[15]) { delay_ms(1000); reset_game(); } } } else { // Ball is being held by the player ballx = plrx + 8; bally = 60; } // Draw new ball lcd_spixel(ballx - 1, bally - 1); lcd_spixel(ballx, bally - 1); lcd_spixel(ballx + 1, bally - 1); lcd_spixel(ballx - 1, bally); lcd_spixel(ballx, bally); lcd_spixel(ballx + 1, bally); lcd_spixel(ballx - 1, bally + 1); lcd_spixel(ballx, bally + 1); lcd_spixel(ballx + 1, bally + 1); } // Clear player void clear_player() { uint8_t x; for (x = plrx; x < plrx + 16; x++) { lcd_cpixel(x, 62); lcd_cpixel(x, 63); } } // Draw player void draw_player() { uint8_t x; for (x = plrx; x < plrx + 16; x++) { lcd_spixel(x, 62); lcd_spixel(x, 63); } } // Draw bitmap void draw_bitmap(const uint8_t *bmp, unsigned int x, unsigned int y) { uint8_t ix; uint8_t iy; // Get width and height uint8_t w = pgm_read_byte(bmp++); uint8_t h = pgm_read_byte(bmp++); // Draw bitmap for (iy = 0; iy < h; iy++) { for (ix = 0; ix < w; ix++) { if (pgm_read_byte(bmp + ix + (iy / 8) * w) & (1 << (iy % 8))) lcd_spixel(x + ix, y + iy); else lcd_cpixel(x + ix, y + iy); } } } // Set pixel void lcd_spixel(unsigned int x, unsigned int y) { if (x > 127 || y > 63) return; unsigned char py = y / 8; // Get page (Y) address unsigned char pyb = y % 8; // Get Y bit in page if (pixels[x + py * 128] & (1 << pyb)) return; pixels[x + py * 128] |= (1 << pyb); // Set pixel bit pixelmod[x] |= (1 << py); // Set modification bit pixelbufmod = 1; } // Clear pixel void lcd_cpixel(unsigned int x, unsigned int y) { if (x > 127 || y > 63) return; unsigned char py = y / 8; // Get page (Y) address unsigned char pyb = y % 8; // Get Y bit in page if (~pixels[x + py * 128] & (1 << pyb)) return; pixels[x + py * 128] &= ~(1 << pyb); // Set pixel bit pixelmod[x] |= (1 << py); // Set modification bit pixelbufmod = 1; } // Draw modified pixels to LCD void lcd_update() { if (!pixelbufmod) return; pixelbufmod = 0; unsigned char x; unsigned char y; // Update left screen LCD_CS2_HIGH; LCD_CS1_LOW; for (x = 0; x < 64; x++) { if (pixelmod[x]) { for (y = 0; y < 8; y++) { if (pixelmod[x] & (1 << y)) { lcd_setx(x); lcd_sety(y); lcd_write(pixels[x + y * 128]); } } pixelmod[x] = 0; } } // Update right screen LCD_CS1_HIGH; LCD_CS2_LOW; for (x = 64; x < 128; x++) { if (pixelmod[x]) { for (y = 0; y < 8; y++) { if (pixelmod[x] & (1 << y)) { lcd_setx(x); lcd_sety(y); lcd_write(pixels[x + y * 128]); } } pixelmod[x] = 0; } } } // Send enable signal void lcd_e() { LCD_E_HIGH; delay(1); LCD_E_LOW; delay(5); } // Write pixels to the LCD void lcd_write(unsigned char a) { PORTA = a; // Set data bits delay(20); // Wait lcd_e(); } // Send an instruction to the LCD void lcd_instruction(unsigned char a) { LCD_RS_LOW; // Set RS low (instruction) PORTA = a; // Set data bits delay(20); // Wait lcd_e(); LCD_RS_HIGH; // Set RS high (data) } // LCD enable instruction (note, affects only the selected IC) void lcd_enable(unsigned char a) { // Send "enable" instruction to LCD lcd_instruction(0b00111110 | (a & 0x01)); } // Reset and clear LCD void lcd_reset() { LCD_CS1_HIGH; LCD_CS2_HIGH; lcd_enable(1); // Enable LCD lcd_instruction(0b11000000); // Set start page to 0 lcd_clear(); // Clear LCD } // LCD coordinate functions void lcd_setx(unsigned char x) { lcd_instruction(0b01000000 | (x & 0b00111111)); // Set X coordinate (Y address) } void lcd_sety(unsigned char y) { lcd_instruction(0b10111000 | (y & 0b00000111)); // Set Y coordinate (Page/X address) } // Clears the screen void lcd_clear() { unsigned int x = 0; unsigned int y = 0; // Select both chips LCD_CS1_HIGH; LCD_CS2_HIGH; // Clear all pixels lcd_setx(0); for (y = 0; y < 8; y++) { lcd_sety(y); for (x = 0; x < 64; x++) { lcd_write(0x00); } } // Reset coordinates lcd_setx(0); lcd_sety(0); // Clear pixels and pixelmod memset(pixels, 0, 1024); memset(pixelmod, 0, 128); } // Delay functions void delay_ms(unsigned long i) { i = 500 * i; // FIXME: Most likely a bit incorrect... while (--i != 0) asm volatile ("nop"); } void delay(unsigned long i) { while (--i != 0) asm volatile ("nop"); }