Homework for this week:
Cabling
The prominent task for week 8 was to program my "Hello World" board that I had built in week 6.
Contrary to my colleagues, I had attached the switch to processor pin 6 = PA7 = output 7,
and the LED to processor pin 5 = PB2 = output 8 (see figs. 1a and 1b, taken from here).
The first chart is taken from this tutorial.
Both charts contain basically the same information. The 'D0 .. D10' in the upper chart correspond to 'Pin0 .. Pin10' in the lower one.
I used wires in different colors to link the "Hello World" board with the Arduino Uno. Both need to be attached to a USB hub, so to not destroy the computer in case of a stress peak or similar. The VCC pin of the 2x3 pin header need not be attached to power.
So far, I have managed to program my "Hello World" board only using an Arduino Uno and The Arduino IDE. Some nice guy has explained
here
how this can be accomplished. Why exactly this works and why I cannot program it via my in-circuit programmer that I have built in week 4
is not clear yet. AVRDude does not recognize the ATtiny44 (see my documentation for week 6).
Plain Arduino Sketch
Programming the board from within the Arduino IDE is fairly straightforward.
The Arduino sketch from fig. 5 turns the attached LED on or off every 500 milliseconds.
C-like program within Arduino IDE
I replaced the Arduino Sketch functions digitalWrite(), digitalRead() and pinMode() for the input/output settings
with some C bit manipulation code. '_BV(...)' is a Macro and stands for 'Bit Vector()'. '<<' is a 'bit shift to the left'-operation.
Both do the same. Please see the code in fig. 6.
Python
I also tried to program my "Hello World" board in Python. I copied the files rx.py and term.py into directory
week6_files/HelloWorldBoard/Hello.FTDI, found out in the Arduino IDE to which port the ATtiny44 is attached and executed
python term.py /dev/tty.usbmodemFD1331 115200
from the shell. The GUI for the terminal came up. When I typed something at the shell, nothing was mirrored to the terminal's GUI.
I have not yet found out why this does not work.
Wednesday, April 8th.: The terminal application in Python works now with my "Hello World" board, as you can see in fig. 7. Please excuse the silly text.
I have found out that it does not work when I use the Arduino Uno as programmer. Instead, I need to use my home-brew programmer.
I have also verified the correct cabling (see figs. 8 and 9).
thomas ~/Desktop/FabAcademy/repository/fablabkamplintfort/students/125/week6_files/HelloWorldBoard/Hello.FTDI $ sudo make -f hello.ftdi.44.echo.c.make program-usbtiny-fuses
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
----------------
Device: attiny44
Program: 758 bytes (18.5% Full)
(.text + .data + .bootloader)
Data: 64 bytes (25.0% Full)
(.data + .bss + .noinit)
avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e9207
avrdude: reading input file "0x5E"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.01s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0x5E:
avrdude: load data lfuse data from input file 0x5E:
avrdude: input file 0x5E contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK (H:FF, E:DF, L:5E)
avrdude done. Thank you.
and
thomas ~/Desktop/FabAcademy/repository/fablabkamplintfort/students/125/week6_files/HelloWorldBoard/Hello.FTDI $ sudo make -f hello.ftdi.44.echo.c.make program-usbtiny
Password:
avr-objcopy -O ihex hello.ftdi.44.echo.out hello.ftdi.44.echo.c.hex;\
avr-size --mcu=attiny44 --format=avr hello.ftdi.44.echo.out
AVR Memory Usage
----------------
Device: attiny44
Program: 758 bytes (18.5% Full)
(.text + .data + .bootloader)
Data: 64 bytes (25.0% Full)
(.data + .bss + .noinit)
avrdude -p t44 -P usb -c usbtiny -U flash:w:hello.ftdi.44.echo.c.hex
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e9207
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hello.ftdi.44.echo.c.hex"
avrdude: input file hello.ftdi.44.echo.c.hex auto detected as Intel Hex
avrdude: writing flash (758 bytes):
Writing | ################################################## | 100% 2.34s
avrdude: 758 bytes of flash written
My final project will be based on something I have already done, the LEDmePlay video game console that uses as display a 32x32 RGB LED matrix panel. While the LEDmePlay uses an Arduino Mega 2560, my final project will use an Atmel ATmega1284p. The reason is that I need much more memory, variable memory and program memory, than what the Arduino Uno offers. But I cannot solder the processor of the Arduino Mega 2560.
Some weeks ago, when I had decided to change my final project, I began to program a Breakout-style game by the name Wallbreaker. The game is controlled with a digital joystick. The Arduino sketch for Wallbreaker is to a large extent compatible with the LEDmePlay. I wanted it to feature five balls, four bats and different screens, like the classic game Arkanoid for instance.
Until now, I have programmed a single screen and the basic game mechanics. I can already move five balls simultaneously and can control four bats at once, one pair with vertical joystick movements, the other with horizontal movements. So far, I had no time to implement a functioning collision detection for them. It is hopefully visible in the picture that you can distinguish the various bricks. To achieve this, I had to slightly vary the color patterns they are made of.
I will briefly explain the key elements of the sketch:
Excerpts from the Wallbreaker program (Note: this is not university-quality code :-) )
void initializePlayfield()
{
// Initialize play field structure
for (byte i = 0; i < 32; i++)
{
for (byte j = 0; j < 32; j++)
{
playfield[i][j] = EMPTY;
}
}
// Draw border on play field
for(i = 0; i < 32; i++)
{
playfield[i][0] = WALL;
playfield[i][31] = WALL;
playfield[0][i] = WALL;
playfield[31][i] = WALL;
}
}
// Reads the play field structure from Flash ROM (PROGMEM) and transfers it to data structure in RAM
// Draws the play field
void drawPlayfield()
{
// Draw play field border
matrix.drawLine(0, 0, 31, 0, matrix.Color888(17, 17, 68));
matrix.drawLine(0, 0, 0, 31, matrix.Color888(17, 17, 68));
matrix.drawLine(31, 0, 31, 31, matrix.Color888(17, 17, 68));
matrix.drawLine(0, 31, 31, 31, matrix.Color888(17, 17, 68));
matrix.fillRect(1, 1, 31, 31, matrix.Color333(0,0,0)); // Clear LED matrix
initializePlayfield();
// Initialize the levelData and play field data structures
for (byte i = 0; i < 140; i++) { levelData[i] = EMPTY; }
// Copy the maze from program memory into data structure levelData
for (byte k = 0; k < 140; k++)
{
levelData[k] = pgm_read_byte_near(maze + (stage*140) + k);
}
// Draw bricks and walls, and store them in the play field
for (i = 0; i < 10; i++)
{
for (j = 0; j < 14; j++)
{
if (levelData[(j * 10) + i] == EMPTY) // Passage
{
setPlayfieldElement(i, j, EMPTY);
}
else if(levelData[(j * 10) + i] == WALL) // Wall
{
//setPlayfieldElement(i, j, WALL);
//matrix.drawLine(i*3+1, j*2+1, i*3+3, j*2+1, wall_color);
}
else if(levelData[(j * 10) + i] == RED_BRICK)
{
setPlayfieldElement(i, j, RED_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 0, 0));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 0, 0));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 0, 0));
}
else if(levelData[(j * 10) + i] == GREEN_BRICK)
{
setPlayfieldElement(i, j, GREEN_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(0, 1, 0));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(0, 3, 0));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(0, 5, 0));
}
else if(levelData[(j * 10) + i] == BLUE_BRICK)
{
setPlayfieldElement(i, j, BLUE_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(0, 0, 1));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(0, 0, 3));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(0, 0, 5));
}
else if(levelData[(j * 10) + i] == VIOLET_BRICK)
{
setPlayfieldElement(i, j, VIOLET_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 0, 1));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 0, 3));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 0, 5));
}
else if(levelData[(j * 10) + i] == YELLOW_BRICK)
{
setPlayfieldElement(i, j, YELLOW_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 1, 0));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 3, 0));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 5, 0));
}
else if(levelData[(j * 10) + i] == SOLID_BRICK)
{
setPlayfieldElement(i, j, SOLID_BRICK);
matrix.drawLine(i*3+1, j*2+1, i*3+1, j*2+2, matrix.Color333(1, 1, 1));
matrix.drawLine(i*3+2, j*2+1, i*3+2, j*2+2, matrix.Color333(3, 3, 3));
matrix.drawLine(i*3+3, j*2+1, i*3+3, j*2+2, matrix.Color333(5, 5, 5));
}
} // FOR j
} // FOR i
} // drawMaze
void setPlayfieldElement(byte i, byte j, byte element)
{
for (byte ox = 0; ox < 4; ox++)
{
for (byte oy = 0; oy < 2; oy++)
{
playfield[1 + (i * 3) + ox][(1 + j * 2) + oy] = element;
}
}
}
...
double calcMoveAngle_x(double angle)
{
return (double) cos(angle * PI_FRACTION);
}
double calcMoveAngle_y(double angle)
{
return (double) (sin(angle * PI_FRACTION));
}
double getInkrement_x(double angle)
{
if ((angle > 0) && (angle < 180)) { return 1; }
else if ((angle > 180) && (angle < 360)) { return -1; }
else { return 0; }
}
double getInkrement_y(double angle)
{
if ( ((angle > 270) && (angle < 360)) || ((angle > 0) && (angle < 90)) ) { return -1; }
else if ( (angle > 90) && (angle < 270)) { return 1; }
else { return 0; }
}
...
void moveBat()
{
boolean ignoreJoystick = false;
if (!ignoreJoystick)
{
// Check joystick 1 for player 1
// Player indicates a new direction
if ((digitalRead(buttonL1) == LOW) && (digitalRead(buttonU1) != LOW) && (digitalRead(buttonD1) != LOW)) // LEFT
{
if (bat_active[LOWER_BAT]) { if (bat_pos[LOWER_BAT] > 2) { bat_pos_old[LOWER_BAT] = bat_pos[LOWER_BAT]; bat_pos[LOWER_BAT]--; } }
if (bat_active[UPPER_BAT]) { if (bat_pos[UPPER_BAT] > 2) { bat_pos_old[UPPER_BAT] = bat_pos[UPPER_BAT]; bat_pos[UPPER_BAT]--; } }
repaintBat();
}
else if ((digitalRead(buttonR1) == LOW) && (digitalRead(buttonU1) != LOW) && (digitalRead(buttonD1) != LOW)) // RIGHT
{
if (bat_active[LOWER_BAT]) { if (bat_pos[LOWER_BAT] < 26) { bat_pos_old[LOWER_BAT] = bat_pos[LOWER_BAT]; bat_pos[LOWER_BAT]++; } }
if (bat_active[UPPER_BAT]) { if (bat_pos[UPPER_BAT] < 26) { bat_pos_old[UPPER_BAT] = bat_pos[UPPER_BAT]; bat_pos[UPPER_BAT]++; } }
repaintBat();
}
else if ((digitalRead(buttonU1) == LOW) && (digitalRead(buttonL1) != LOW) && (digitalRead(buttonR1) != LOW)) // UP
{
if (bat_active[LEFT_BAT]) { if (bat_pos[LEFT_BAT] > 2) { bat_pos_old[LEFT_BAT] = bat_pos[LEFT_BAT]; bat_pos[LEFT_BAT]--; } }
if (bat_active[RIGHT_BAT]) { if (bat_pos[RIGHT_BAT] > 2) { bat_pos_old[RIGHT_BAT] = bat_pos[RIGHT_BAT]; bat_pos[RIGHT_BAT]--; } }
repaintBat();
}
else if ((digitalRead(buttonD1) == LOW) && (digitalRead(buttonL1) != LOW) && (digitalRead(buttonR1) != LOW)) // DOWN
{
if (bat_active[LEFT_BAT]) { if (bat_pos[LEFT_BAT] < 26) { bat_pos_old[LEFT_BAT] = bat_pos[LEFT_BAT]; bat_pos[LEFT_BAT]++; } }
if (bat_active[RIGHT_BAT]) { if (bat_pos[RIGHT_BAT] < 26) { bat_pos_old[RIGHT_BAT] = bat_pos[RIGHT_BAT]; bat_pos[RIGHT_BAT]++; } }
repaintBat();
}
if (digitalRead(buttonFire1) == LOW)
{
//if (fireSkipsStage) { skipStage = true; }
}
} // !ignoreJoystick
} // moveBat
...
boolean reflectAtUpperSurface(byte b, int randomAngleOffset)
{
if ((ball_moveAngle[b] > 270) && (ball_moveAngle[b] < 360))
{
ball_moveAngle[b] = 180 + (360 - ball_moveAngle[b]);
ball_vector_y[b] = getInkrement_y(ball_moveAngle[b]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[b])));
return true;
}
else if ((ball_moveAngle[b] >= 0) && (ball_moveAngle[b] < 90))
{
ball_moveAngle[b] = 180 - ball_moveAngle[b];
ball_vector_y[b] = getInkrement_y(ball_moveAngle[b]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[b])));
return true;
}
return false;
}
...
void checkBallReflectedAtWall(byte i, double ball_x, double ball_y)
{
// IS BALL I BEING REFLECTED AT ONE OF THE WALLS?
// CHECK WHETHER THE NEXT POSITION WOULD BE ON A WALL
// Calculate the next position of the ball
if (floor(ball_y) <= 1) // Ball is reflected at the upper wall
{
ballReflected[i] = reflectAtUpperSurface(i, 0);
}
else if (floor(ball_y) >= 30) // Ball is reflected at the lower wall
{
ballReflected[i] = reflectAtLowerSurface(i, 0);
}
if (floor(ball_x) <= 1) // Ball is reflected at the left wall
{
ballReflected[i] = reflectAtLeftSurface(i, 0);
}
else if (floor(ball_x) >= 30) // Ball is reflected at the right wall
{
ballReflected[i] = reflectAtRightSurface(i, 0);
}
} // checkBallReflectedAtWall
void checkBallReflectedAtBat(byte i, double ball_x, double ball_y) // DID THE BALL HIT ONE OF THE ACTIVE BATS?
{
// CHECK LOWER BAT
if (bat_active[LOWER_BAT])
{
if (29-ball_y <= 1.0) // Ball is right over the bat (can potentially be reflected)
{
if ((abs(ball_x-1 - (double) bat_pos[LOWER_BAT]) <= 1.0) || (abs(ball_x-2 - (double) bat_pos[LOWER_BAT]) <= 1.0)) // Ball hit pad central
{
ballReflected[i] = reflectAtLowerSurface(i, 10);
}
else if (abs(ball_x-0 - (double) bat_pos[LOWER_BAT]) <= 1.0) // Ball hit pad leftmost
{
ballReflected[i] = reflectAtRightSurface(i, -45);
}
else if (abs(ball_x-3 - (double) bat_pos[LOWER_BAT]) <= 1.0) // Ball hit pad rightmost
{
ballReflected[i] = reflectAtLeftSurface(i, 45);
}
} // ball right over the bat
else if (abs(29-ball_y) <= 0.2) // Ball is right beside the bat
{
if (bat_pos[LOWER_BAT]-ball_x <= 1.0) // Ball hits the left side of the pad
{
ballReflected[i] = reflectAtRightSurface(i, 10);
}
else if (bat_pos[LOWER_BAT]+3-ball_x <= 1.0) // Ball hits the right side of the pad
{
ballReflected[i] = reflectAtLeftSurface(i, 10);
}
}
if (collisionOccurred)
{
// Compute a new y-inkrement, as the angle got reflected at the bat
//avertHorizontalOrVerticalPath(i);
ball_vector_y[i] = getInkrement_y(ball_moveAngle[i]) * abs(calcMoveAngle_y(min(360, ball_moveAngle[i] + random(5))));
}
} // checkBallReflectedAtBat
// (ball_x, ball_y) is the next position of the ball. Check whether the ball gets reflected at a brick
void checkBallReflectedAtBrick(byte i, double ball_x, double ball_y)
{
byte elementHit;
byte nx = ball_x;
byte ny = ball_y;
byte ex;
byte ey;
// IS BALL i HITTING A BRICK OR A WALL?
elementHit = playfield[nx][ny];
if (elementHit > WALL)// PROBLEM: Wenn der Ball diagonal von außen in den unteren Bereich eines Steins kommt, wird das nicht erkannt
{
elementHit = playfield[nx][ny];
ex = 1 + 3*((nx - 1) / 3);
ey = 1 + 2*((ny - 1) / 2);
if ((elementHit == RED_BRICK) || (elementHit == GREEN_BRICK) || (elementHit == YELLOW_BRICK) ||
(elementHit == BLUE_BRICK) || (elementHit == VIOLET_BRICK) || (elementHit == SOLID_BRICK))
{
matrix.fillRect(ex, ey, 3, 2, matrix.Color333(0, 0, 0));
// Delete element from play field
playfield[ex][ey] = EMPTY; playfield[ex+1][ey] = EMPTY; playfield[ex+2][ey] = EMPTY;
playfield[ex][ey+1] = EMPTY; playfield[ex+1][ey+1] = EMPTY; playfield[ex+2][ey+1] = EMPTY;
}
// LET THE OBJECT REFLECT THE BALL
// CASE: BALL IS LEFT OR RIGHT FROM THE OBJECT
//DAS STIMMT NOCH NICHT: DER BALL WIRD MANCHMAL NACH OBEN REFLEKTIERT, OBWOHL ER BEI KOLLISION VON UNTEN HER GEKOMMEN IST UND UMGEKEHRT
if ((floor(ey) == floor(ball_y)) || (floor(ey+1) == floor(ball_y))) // Ball is at the same y-position as the object
{
if ( ((ball_moveAngle[i] > 0) && (ball_moveAngle[i] <= 90)) // Ball comes from the left
|| ((ball_moveAngle[i] > 90) && (ball_moveAngle[i] < 180)) )
{
ballReflected[i] = reflectAtRightSurface(i, 0);
}
else if (((ball_moveAngle[i] > 270) && (ball_moveAngle[i] < 360)) // Ball comes from the right
|| ((ball_moveAngle[i] > 180) && (ball_moveAngle[i] <= 270)) )
{
ballReflected[i] = reflectAtLeftSurface(i, 0);
}
} // Ball left or right from the object
else if ((((ball_moveAngle[i] > 90) && (ball_moveAngle[i] < 180)) // CASE: BALL IS ABOVE THE OBJECT, 90° > a > 180° or or 270° > a > 180°
|| ((ball_moveAngle[i] >= 180) && (ball_moveAngle[i] < 270))))
{
ballReflected[i] = reflectAtLowerSurface(i, 0);
}
else if ( (((ball_moveAngle[i] > 270) && (ball_moveAngle[i] < 360)) // CASE: Ball is beneath the object, 270° < a <= 0° or 0° < a < 90°
|| ((ball_moveAngle[i] > 0) && (ball_moveAngle[i] < 90))))
{
ballReflected[i] = reflectAtUpperSurface(i, 0);
}
} // hit something that is not a wall
} // checkBallReflectedAtBrick
void moveBall()
{
for (byte i = 0; i < 5; i++)
{
if (ball_active[i])
{
// Update ball position
// Clear ball at old position
matrix.drawPixel(byte(ball_x[i]), byte(ball_y[i]), matrix.Color333(0, 0, 0));
//Serial.print("\nBall inkrements: "); Serial.print(ball_vector_x[i]); Serial.print(" ,"); Serial.print(ball_vector_y[i]);
//Serial.print("\nBall: "); Serial.print(ball_x[i]); Serial.print(" ,"); Serial.print(ball_y[i]);
//Serial.println();
//Serial.print("\nBall velocity: "); Serial.print(ball_velocity_x[i]); Serial.print(" ,"); Serial.print(ball_velocity_y[i]);
// For certain angles, the ball moves in a stairway fashion.
// We prevent this by looking at the next two positions of the ball
// If both are within the 8-neighborhood of the current position, we skip the next position
// and directly go to the next after the skipped one.
if (bothInMooreNeighborhood(ball_x[i], ball_y[i], ball_vector_x[i], ball_vector_y[i]))
{
ball_x[i] = ball_x[i] + 2*ball_vector_x[i];
ball_y[i] = ball_y[i] + 2*ball_vector_y[i];
}
else
{
ball_x[i] = ball_x[i] + ball_vector_x[i];
ball_y[i] = ball_y[i] + ball_vector_y[i];
}
// Only at this point the coordinates of the ball are known
// The collision detection is, therefore, at the right place here
// (ball_x, ball_y) is the next position of the ball
ballReflected[i] = false;
checkBallReflectedAtWall(i, ball_x[i], ball_y[i]);
checkBallReflectedAtBat(i, ball_x[i], ball_y[i]);
checkBallReflectedAtBrick(i, ball_x[i], ball_y[i]);
// Paint ball at new position
matrix.drawPixel(ball_x[i], ball_y[i], ball_color);
} // ball_active[i]
} // for
// Draw bats
repaintBat(); // TODO: Do only, if necessary. This means after a collision between bat and ball/baddie
}
boolean bothInMooreNeighborhood(double x, double y, double vx, double vy)
{
double x2 = x + vx;
double y2 = y + vy;
double x3 = x + 2*vx;
double y3 = y + 2*vy;
if ((abs(byte(x3)-byte(x)) == 1) && (abs(byte(y3)-byte(y)) == 1)) { return true; }
return false;
}
...
When the preliminary work for the final project is complete, I will make a video of the Video Game System and Wallbreaker in action. Over the years, my friend and I have programmed some games for our LEDmePlay, the spiritual father of the device I am building in my final project. Some videos of them are here.