| Weitere Artikel aus dem Elo-Magazin |
Tetris
von Hans Eckardt
Da ich inzwischen etwas Anspruchsvolleres auf der Pong-Hardware entwickeln wollte, habe ich beschlossen ein Tetris zu programmieren. Dieses Spiel ist ja genau wie Pong ein Spieleklassiker, und es reizte mich ob es auf der Hardware sinnvoll zum Laufen zu bringen ist. Kurz gesagt es läuft und macht einfach Spaß! In Aktion sehen Sie es auf Youtube .
Um nicht bei Null anfangen zu müssen, habe ich auf den Code unter der Adresse http://www.1541.org/ - eine Spielesammlung mit dem Namen „This is the "OSD Games Collection plugin" for the Video Disk Recorder (VDR)" aufgesetzt. Dieser Code hat die Lizenz GPL. Mein Code steht damit auch unter der GPL, was anderen die Weiterentwicklung ermöglicht.
Ganzen Artikel lesen...

Nun zum Spiel selbst: Es läuft auf dem Original Pong ohne Veränderungen der Hardware, allein das Spielfeld muss hochkant stehen (um 90 Grad nach rechts gedreht). Drehen Sie beide Potis in die Mitte.Nach Einlegen der Batterien startet das Spiel mit einer kurzen Begrüßung, Danach wird der Score-Screen angezeigt. Oben steht Ihr aktueller Punktestand und unten der Spiel-Level.
Danach erscheint oben der erste Spielstein und beginnt langsam nach unten zu fallen. Mit dem unteren Poti bewegen Sie ihn in der Waagerechten. Mit dem oberen Poti können Sie durch eine Linksdrehung den Spielstein um 90 Grad drehen. Bei einer Rechtsdrehung fällt er ungebremst nach unten. Es empfiehlt sich, die Mittelstellung später mit einem Strich auf der Frontplatte zu markieren.
Um den Spielstein um mehr als 90 Grad zu drehen, müssen Sie ihn mehrere Drehungen machen lassen. Dazu jeweils das obere Poti in die Mittelstellung bringen und erneut nach links drehen.
Der nächste Spielstein erscheint. Jeweils ein heller und ein etwas dunklerer Spielstein erscheinen abwechselnd am oberen Rand. Ziel ist es, komplette Reihen mit Spielsteinen zu füllen. Es ist normal und durch die Hardware vorgegeben, dass schon unten liegende Spielsteine mit der Bewegung des fallenden Spielsteines leicht heller und dunkler werden. Am besten begegnen Sie dem, indem Sie möglichst komplette Reihen bauen die dann verschwinden ;-) Eine komplette Reihe blinkt vier mal und verschwindet dann. Wenn ein Spielstein nicht mehr nach unten fallen kann, wird kurz der Score-Screen angezeigt, dann kommt der nächste Spielstein. Das Spiel ist zu Ende wenn oben keine neuen Spielsteine eingefügt werden können. Es gibt neun Level. Wenn Sie Score-Werte über 199 erreichen ;-) nicht erschrecken, die letzte Ziffer ist nicht komplett da nur 10 Spalten da sind. Die Score-Anzeige geht bis 999! Nach Spielende wird blinkend der Score angezeigt, danach kurz das letzte Spielfeld. Der Atmega8 geht in den Power-Off Modus und kann erst durch Münzeinwurf zum nächsten Spiel überredet werden.
Im Programm ist eine Pausenfunktion implementiert, dazu müssen Sie jedoch einen zusätzlichen Taster zwischen D3 und Masse (K1) anschließen. Ein Druck auf den Taster zeigt ein II - Symbol an (Pause) und nach erneutem Betätigen geht es weiter. Das ist aber nur ein zusätzliches Gimmick.
Es ist schon erstaunlich was so ein kleiner Atmega8 leistet. Das Spiel nimmt gemessen gerade mal 10...16 mA Strom auf, und im Power-Off Modus beträgt der Stromverbrauch nur 0,6 µA. Die Code-Portierung war, auch dank PongSimu (siehe anderer Artikel von mir) recht einfach zu bewältigen, die Adaption an die Hardware hat dann doch etwas mehr Mühe gemacht.
Das Schwierigste war von einer Tastensteuerung auf die Steuerung mit den beiden Potis überzugehen. Ursprünglich war die Richtungssteuerung des Spielsteins mit dem unteren Poti nur eine „Lenksteuerung" stur rechts/links wie bei einfachen ferngesteuerten Autos. Mit einer Positionssteuerung ist das Spiel jedoch viel besser spielbar weshalb diese jetzt implementiert wurde.
Das obere Poti bewirkte zuerst eine dauernde Drehung des Spielsteins, wenn nach links gedreht. Mit dem Zwang nach jeder Drehung wieder in die Mittelstellung zu gehen, ist die Drehung des Spielsteins viel besser zu kontrollieren. Im Quelltext finden Sie weitere Hinweise, wenn Sie das Programm weiterentwickeln wollen. Und jetzt viel Spaß beim Spielen!
Download: Quelltext und Hex-Datei
// app to play the famous tetris game
// using ATMEGA8 on the Conrad Pong PCB
// This code is released to the Public Domain. No warranty for any purpose is given.
// Licensed under GPL (General Puplic License)
// revision (newest first)
// 2010-03-24: drop piece now has priority before move
// 2010-03-23: fixes: StandBy return gfx not cleared, Pause mode not stable
// 2010-03-22: finished the code, also implemented StandBy like the original pong
// hardware measurement at 3xAA:
// voltage 4.7 V, current game 10-16 mA, current Standby 0.6 uA
// 2010-03-21: initial taken from the code simulated in PongSimu, which is an
// adaption from the original "OSD Games Collection plugin" for
// the Video Disk Recorder (VDR) C++ code, for copyright see below
// room for improvement:
// - rotate the pieces by a per-piece defined center point would be nice
/*****************************************************************************\
* *
* Programm: Tetris *
* License: GPL (General Puplic License) *
* Last Change: 2003-05-04 *
* Authors: Franko Kulaga <franko@kulaga.net> *
* Clemens Kirchgatterer <clemens@thf.ath.cx> *
* *
\*****************************************************************************/
// This define is for Visual Studio intellisense, type before any stystem header includes
// It must match the MCU name (MCU = atmega8) in the makefile else intellisense can take you to the wrong definition
#ifndef __GNUC__ // only for the Visual Studio, GCC generates it from MCU name
#define __AVR_ATmega8__
#endif
// Intellisense can be triggered update by ctr+space, or Save all, or at last deleting prj.ncb file and restarting Visual Studio
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "my_avr_defs.h"
#include <stdlib.h>
#include "tetris.h"
#include "smallfont.h"
// which of the poti value must differ +/- to detect a change for left poti
#define POTI_SENSITIVITY 50
// factor which of the poti value must differ +/- to get move speed for right poti
#define POTI_SPEED 60
// display orientation landscape if defined, else portrait (tetris needs portrait)
// #define LANDSCAPE
#ifdef LANDSCAPE
// width of display (LED count)
#define WIDTH 12
// height of display (LED count)
#define HEIGHT 10
#else
// width of display (LED count)
#define WIDTH 10
// height of display (LED count)
#define HEIGHT 12
#endif
#define CYCLE1_TIME 1 // // cycle 1 is very little of the time of cycle 2, so the brightness is way lower
#define CYCLE2_TIME 32
// cycle 1 is 0.008 millisec
// Time = prescale * counter range / clock frequency
// 0.064 ms = 64 * 1 (CYCLE1_TIME) / 8 000 000 Hz
// cycle 2 is 0.25 millisec
// Time = prescale * counter range / clock frequency
// 2.048 ms = 64 * 32 (CYCLE2_TIME) / 8 000 000 Hz
// global Pixel buffer (always anode, cathode order as this is optimal for the multiplexer)
static byte pixbuf[10][12];
// v is the brightness code, use only 0, 1, and 2, while 2 be the brightest
static inline void SetPixel(byte x, byte y, byte v)
{
#ifdef LANDSCAPE
pixbuf[y][x]= v;
#else
pixbuf[WIDTH-1-x][y]= v;
#endif
}
static inline byte GetPixel(byte x, byte y)
{
#ifdef LANDSCAPE
return pixbuf[y][x];
#else
return pixbuf[WIDTH-1-x][y];
#endif
}
// RAND(x) returns a random number of 0...1...n...x (x be the greatest returned one)
// see http://linux.die.net/man/3/rand remark for creating random numbers in certain range
//#define RAND(x) ((rand()*(x))/(RAND_MAX+1))
#define RAND(x) (rand() % ((x)+1))
#define COLORS 2
#define WIDTH 10
#define HEIGHT 12
#define COL_BGR 0
#define COL_WHT 2
#define KEY_NONE 0
#define KEY_LEFT 1
#define KEY_RIGHT 2
#define KEY_UP 3
#define KEY_DOWN 4
#define KEY_BACK 5
#define KEY_OK 6
static unsigned char field[HEIGHT][WIDTH];
static unsigned char piece[4][2];
static signed char rotated = 0;
static int score = 0;
static int level = 0;
static int lines = 0;
static int pieces = 0;
static int halt = 0;
static int fallen = 0;
static int over = 0;
static int color = 0;
static int piecepos = 0;
static void
draw_char(byte x0, byte y0, byte c)
{
const byte* fnt= &font4x6[3*(c - 32)];
for (byte y= 0; y < 6; y+=2) {
byte mask= 0x80;
byte fntval= pgm_read_byte(fnt);
++fnt;
for (byte y2= 0; y2 < 2; ++y2) {
byte ys= HEIGHT-1-y0-(y+y2);
if (ys > HEIGHT) break; // allow column special tweaking
for (byte x= 0; x < 4; ++x) {
if (x+x0 < WIDTH) // allow last char in a row special tweaking
SetPixel(x+x0,ys, (fntval & mask)? 2 : 0);
mask/=2;
}
}
}
}
// draw a string
static void
draw_text(byte x0, byte y0, const char* txt)
{
for(;;) {
byte c= *txt;
if (c==0) break;
++txt;
draw_char(x0, y0, c);
x0+= 4;
}
}
// draw a string, for progmem string
static void
draw_text_P(byte x0, byte y0, const char* txt)
{
for(;;) {
byte c= pgm_read_byte(txt);
if (c==0) break;
++txt;
draw_char(x0, y0, c);
x0+= 4;
}
}
/* test of draw_text
while (!avrEndRun()) {
int move= avrGetRightADC() / 11;
char buf[10];
//buf[0]= ' ';
//itoa(move, move > 9 ? buf : buf+1, 10);
buf[1]=0;
buf[0]= 32+move;
draw_text(0,0, buf);
avrTriggerRepaint();
}
return;
*/
static void
draw_block(int x, int y, int c) {
SetPixel(x, HEIGHT-1-y, c);
}
static void
erase_gfx(void) {
for (byte y=0; y<HEIGHT; y++)
for (byte x=0; x<WIDTH; x++)
SetPixel(x, y, 0);
}
static void
draw_score0(void) {
char buf[6];
// draw the score and level as decimal
if (score > 199) {
itoa(score, buf, 10);
buf[3]= '\0'; // cut after 3th decimal
draw_text(0,0, buf); // 3th decimal will be clipped, but we can't do anything against...
}
else if (score > 99) { // score up to 199
int sc= score-100;
draw_text_P(0,0, PSTR("1"));
SetPixel(0,HEIGHT-1-4,0); // special "hinting"
SetPixel(2,HEIGHT-1-4,0); // special "hinting"
buf[0]= '0';
itoa(sc, sc > 9 ? buf : buf+1, 10);
draw_text(3,0, buf);
}
else { // score up to 99
buf[0]= ' ';
itoa(score, score > 9 ? buf : buf+1, 10);
draw_text(3,0, buf);
}
buf[0]= ' ';
itoa(level, level > 9 ? buf : buf+1, 10);
draw_text(2,7, buf);
}
static void
game_over(void) {
over = 1;
//Dpy::message_show("Game", "Over", COL_WHT);
//Dpy::update();
erase_gfx();
for (byte i= 0; i < 4; ++i) {
draw_score0();
_delay_ms(800);
erase_gfx();
_delay_ms(300);
}
}
static void
init_field(void) {
for (int y=0; y<HEIGHT; y++) {
for (int x=0; x<WIDTH; x++) {
field[y][x] = 0;
}
}
}
static void
draw_field(void) {
for (int y=0; y<HEIGHT; y++) {
for (int x=0; x<WIDTH; x++) {
draw_block(x, y, field[y][x]);
}
}
}
static void
draw_piece(void) {
for (int i = 0; i < 4; i++) {
draw_block(piece[i][0], piece[i][1], color);
}
}
static void
draw_score(void) {
//Dpy::status_left(score, COL_WHT);
//Dpy::status_right(level, COL_WHT);
erase_gfx();
draw_score0();
_delay_ms(500);
draw_field();
draw_piece();
}
static void
init_piece(void) {
int j, i = 0;
j = RAND(6);
//color = 1+RAND(COLORS-1);
color = color > 1 ? 1 : 2; // alternate full/half brightness
//int xfirst= 2 + RAND(2);
int xpos0= RAND(5); // random pos of first brick
piecepos= 5; // for the poti the random pos is not relevant
for (int x=0; x<4; x++) {
for (int y=0; y<4; y++) {
if (pgm_read_byte(&shapes[j][x][y])) {
piece[i][0] = x + xpos0; // x pos in field is random too
piece[i][1] = y - 1; // -1 so all peaces start at first line...
i++;
}
}
}
pieces++;
fallen = 0;
}
static void
delete_piece(void) {
for (int i = 0; i < 4; i++) {
draw_block(piece[i][0], piece[i][1], COL_BGR);
}
}
static void
reset_game(void) {
level = 1; lines = 0;
pieces = 0; score = 0;
fallen = 0; over = 0;
halt = 0;
rotated = 0;
init_piece();
init_field();
//draw_field();
//draw_piece();
draw_score();
//Dpy::update();
}
static void
halt_game(void) {
if (over) {
reset_game();
return;
}
if (halt) {
_delay_ms(500);
draw_field();
draw_piece();
halt = 0;
} else {
//Dpy::message_show("Pause", NULL, COL_WHT);
erase_gfx();
draw_text_P(2,4, PSTR("II"));
halt = 1;
}
//Dpy::update();
}
static int
drop_piece(void) {
for (int i=0; i<4; i++) {
if ((field[piece[i][1]+1][piece[i][0]]) || ((piece[i][1]+1)>=HEIGHT)) {
if (!fallen) game_over();
return (1);
}
}
delete_piece();
for (int i=0; i<4; i++) {
piece[i][1]++;
}
draw_piece();
//draw_score();
//Dpy::update();
fallen++;
return (0);
}
static void
move_piece(int dir) {
for (int i=0; i<4; i++) {
if (field[piece[i][1]][piece[i][0]+dir]) return;
if ((piece[i][0]+dir)<0) return;
if ((piece[i][0]+dir)>WIDTH-1) return;
}
delete_piece();
for (int i=0; i<4; i++) {
piece[i][0] += dir;
}
piecepos+= dir;
draw_piece();
//draw_score();
//Dpy::update();
}
static void
rotate_piece(void) {
byte tmp[4][2];
byte x = WIDTH, y = HEIGHT;
for (int i=0; i<4; i++) {
if (piece[i][0] < x) x = piece[i][0];
if (piece[i][1] < y) y = piece[i][1];
}
for (int i=0; i<4; i++) {
tmp[i][0] = x+(piece[i][1]-y);
tmp[i][1] = 2+y-(piece[i][0]-x);
}
for (int i=0; i<4; i++) {
if (field[(int)tmp[i][1]][(int)tmp[i][0]]) return;
//if ((tmp[i][0]) < 0) return;
if ((tmp[i][0]) > WIDTH-1) return;
//if ((tmp[i][1]) < 0) return;
if ((tmp[i][1]) > HEIGHT-1) return;
}
delete_piece();
for (int i=0; i<4; i++) {
piece[i][0] = tmp[i][0];
piece[i][1] = tmp[i][1];
}
draw_piece();
_delay_ms(500);
//draw_score();
//Dpy::update();
}
static void
flash_line(int line) {
for (int i=0; i<4; i++) {
for (int x=0; x<WIDTH; x++) {
draw_block(x, line, COL_BGR);
}
//Dpy::update();
_delay_ms(100);
for (int x=0; x<WIDTH; x++) {
draw_block(x, line, COL_WHT);
}
//Dpy::update();
_delay_ms(100);
}
}
static void
remove_lines(void) {
bool erase;
for (int line=0; line<HEIGHT; line++) {
erase = true;
for (int x=0; x<WIDTH; x++) {
if (!field[line][x]) erase = false;
}
if (erase) {
for (int i=line; i>0; i--) {
for (int x=0; x<WIDTH; x++) {
field[i][x] = field[i-1][x];
}
}
flash_line(line);
lines++;
if (!(lines%15)) level++;
if (level>9) level = 9;
score += 3*level;
draw_field();
//draw_score();
//Dpy::update();
}
}
}
//static void Brick(byte x0, byte y0)
//{
//#define BX 5
//#define BY 3
// for (byte x= 0; x < BX; ++x) {
// avrSetPixel(x+x0, HEIGHT-1-(y0), 2);
// avrSetPixel(x+x0, HEIGHT-1-(y0+BY-1), 2);
// }
// for (byte y= 1; y < BY-1; ++y) {
// avrSetPixel(x0, HEIGHT-1-(y0+y), 2);
// avrSetPixel(x0+BX-1, HEIGHT-1-(y0+y), 2);
// }
//}
static void Splash(void)
{
draw_text_P(0,0,PSTR("T"));
draw_text_P(3,1,PSTR("e"));
draw_text_P(7,0,PSTR("t"));
draw_text_P(0,6,PSTR("r"));
draw_text_P(4,7,PSTR("i"));
draw_text_P(7,6,PSTR("s"));
_delay_ms(1600);
//erase_gfx();
//Brick(0,9);
//Brick(5,9);
//Brick(1,7);
//Brick(5,7);
//Brick(3,5);
}
// init all stuff of the AVR for safe operation
static void AVR_Init(void)
{
//cli(); // interrupts are disabled at startup
// watchdog should reset after 15 millisec (protect the LEDs, although the max current is OK for always on)
wdt_enable(WDTO_15MS);
// activate internal pullups to avoid floating input pins
PORTB= (1u<<6)|(1u<<7);
PORTC= (1u<<4)|(1u<<5)/*|(1u<<6)|(1u<<7)*/; // Port C Pin 6,7 are the ADC inputs, no need for pullups here
PORTD= (1u<<0)|(1u<<1)|/*(1u<<2)|*/(1u<<3); // Port D Pin 2 set low for Poti GND
// set output ports
DDRB= (1u<<0)|(1u<<1)|(1u<<2)|(1u<<3)|(1u<<4);
DDRC= (1u<<0)|(1u<<1)|(1u<<2)|(1u<<3);
DDRD= (1u<<2)|(1u<<4)|(1u<<5)|(1u<<6)|(1u<<7); // Port D Pin 2 is the Poti GND so must be output and Low
// 8bit timer0 init, internal clock / 64
// LED multiplexing occurs on overflow0 Intr
TCCR0= (1u<<CS01)|(1u<<CS00);
TIMSK= (1u<<TOIE0); // interrupt on overflow
// init the analog-digital converter
ADMUX=
// no REFS0, REFS1: voltage ref is external connected to AVcc, and we can't change this
(1u << MUX2) | (1u << MUX1); // input is ADC6 (left poti) initial
ADCSRA= (1u << ADEN) | // enable the ADC
(1u << ADSC) | // start conversion
// no ADFR: free running mode is not needed, we explicitly start a new ADU cycle
(1u << ADPS2) | (1u << ADPS1) | (1u << ADPS0); // prescaler 128 (8MHz / 128 = 62.5 kHz, could max use 200 kHz for 10bit)
}
static void WaitForADC(void)
{
while ((ADCSRA & (1u << ADIF)) == 0); // spin until adc result is available
//while ((ADCSRA & (1u << ADSC)) != 0); // spin until adc result is available
}
static void Standby(void)
{
wdt_disable();
TIMSK= 0; // stop timer interrupt
_delay_ms(1); // wait any pending Interrupt
ADCSRA= 0; // disable adc
// bitbang ones for cathodes through the shift register
PORTB |= (1u<<4);
for (byte b= 0; b < 12; ++b) {
// impulses for the shift registers (CLK)
PORTB |= (1u<<3);
PORTB &= ~(1u<<3);
}
// impulses for the shift registers (strobe)
PORTB |= (1u<<2);
PORTB &= ~(1u<<2);
// all ports input , internal pullup at all ports (also LED's anodes to avoid any rest current)
DDRB= 0;
DDRC= 0;
DDRD= 0;
PORTB= 0xff;
PORTC= 0xff;
PORTD= 0xff;
MCUCR &= ((1u << ISC01) | (1u << ISC00)); // rising edge of INT0 generates interrupt
GICR |= (1u<<INT0); // Enable interrupt for INT0
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // setup power save mode as sleep mode
sleep_mode(); // go into sleep mode
// back from sleep mode
MCUCR |= ~((1u << ISC01) | (1u << ISC00)); // no INT0 generates interrupt
erase_gfx();
AVR_Init();
}
static void ReInit(void)
{
Splash();
WaitForADC();
srand(ADC+2);
ADCSRA |= (1u << ADSC); // start new conversion
reset_game();
}
int __attribute__ ((OS_main)) main(void)
{
AVR_Init();
sei(); // we want interrupts globally now
//Dpy::open(X, Y, W, H);
//Dpy::status_center("Tetris", COL_WHT);
ReInit();
for(;;) {
for (int j=0; j<5; j++) {
byte key = KEY_NONE;
byte nmoves= 0; // hack to get proportional moves of piece
// rotate/fall piece left poti have priority above position
WaitForADC();
int val= ADC; // read the value
int rotate= val - 1024/2;
if (rotate < -POTI_SENSITIVITY) {
if (rotated <= 0) // no repeated rotate if poti is up...
key= KEY_UP;
rotated= 1;
}
else if (rotate > POTI_SENSITIVITY) {
if (rotated >= 0)
key= KEY_DOWN;
rotated= -1;
}
else
rotated= 0;
if (!(PIND & (1u << 3))) { // right key, extension to original pong
_delay_ms(30);
while (!(PIND & (1u << 3))); // wait until key is up
key= KEY_OK;
}
// rotation/drop do not block piece move
if (key == KEY_UP)
rotate_piece();
else if (key == KEY_DOWN)
while (!drop_piece());
// right poti is piece position
ADMUX= (1u << MUX2) | (1u << MUX1) | (1u << MUX0); // input is ADC7 (right poti)
ADCSRA |= (1u << ADSC); // start new conversion
WaitForADC();
val= ADC; // read the value
ADMUX= (1u << MUX2) | (1u << MUX1); // input is ADC6 (left poti) again
ADCSRA |= (1u << ADSC); // start new conversion
int move= 1024/2 - val;
int desiredpos= WIDTH/2 + move / POTI_SPEED;
if (desiredpos > piecepos) {
key= KEY_RIGHT;
nmoves= desiredpos - piecepos;
}
else if (desiredpos < piecepos) {
key= KEY_LEFT;
nmoves= piecepos - desiredpos;
}
switch (key) {
case KEY_LEFT: for (byte b= 0; b < nmoves; ++b) move_piece(-1); break;
case KEY_RIGHT: for (byte b= 0; b < nmoves; ++b) move_piece(1); break;
//case KEY_UP: rotate_piece(); break;
//case KEY_DOWN: while (!drop_piece()); break;
//case KEY_BACK: running = false; break;
case KEY_OK: halt_game(); break;
case KEY_NONE: break;
}
key = KEY_NONE;
_delay_ms(17+10*(10-level));
//_delay_ms(17+8*(10-level));
//if (!running) goto end;
}
if (over) {
_delay_ms(2000);
Standby();
over= false;
ReInit();
continue;
}
if (halt || over) continue;
if (drop_piece()) {
for (int i=0; i<4; i++) {
field[piece[i][1]][piece[i][0]] = color;
}
remove_lines();
if (!over)
score += level;
draw_score();
if (!over) {
init_piece();
draw_piece();
}
}
}
//end:
//Dpy::close();
}
// interrupt routine
// will put the pixel data to the LED matrix
// select actual cathode line by the shift register, then put the anodes with the ports to high for the on pixel
//
// Note:
// Because CLK connected to MOSI, not SCK, we cannot bitbang with SPI
ISR (TIMER0_OVF_vect)
{
static bool firstcy;
static byte anode;
static byte shift= 1;
firstcy = !firstcy;
if (firstcy) {
TCNT0= 256-CYCLE1_TIME;
anode++;
shift*=2;
if (anode == 8)
shift= 1;
else if (anode == 10) {
anode= 0;
shift= 1;
}
}
else
TCNT0= 256-CYCLE2_TIME;
byte cmp= firstcy ? 1 : 2;
// bitbang the cathode pattern through the shift register
for (byte b= 0; b < 12; ++b) {
if (pixbuf[anode][b] == cmp)
PORTB &= ~(1u<<4); // first cathode line put to low, this low is bitbanged through the registers
else
PORTB |= (1u<<4); /// next ones put to high
// impulses for the shift registers (CLK)
PORTB |= (1u<<3);
PORTB &= ~(1u<<3);
}
wdt_reset(); // keep watchdog watching...
// reset all anode lines
PORTB &= ~((1u<<0)|(1u<<1));
PORTC &= ~((1u<<0)|(1u<<1)|(1u<<2)|(1u<<3));
PORTD &= ~((1u<<4)|(1u<<5)|(1u<<6)|(1u<<7));
// impulses for the shift registers (strobe)
PORTB |= (1u<<2);
PORTB &= ~(1u<<2);
// set according anode line
if (anode < 4)
PORTC |= shift;
else if (anode < 8)
PORTD |= shift;
else
PORTB |= shift;
}
ISR(INT0_vect)
{
// Interupt handler for T0 button in standby mode
GICR &= ~(1u<< INT0); // Disable interrupt for Int0 for after standby mode
}
















