16 Commits

23 changed files with 146 additions and 129 deletions

BIN
docs/IMG_4941.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

BIN
docs/IMG_4958.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/sound-driver-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

View File

@ -7,7 +7,7 @@ MCU=msp430g2553
CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O1 -g0
# for debugging
CFLAGS+= -g3 -ggdb -gdwarf-2
#CFLAGS+= -g3 -ggdb -gdwarf-2
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include

View File

@ -1,3 +1,5 @@
// #define STATE_DEBUGGING
#include "stddef.h"
#include "stdint.h"
@ -12,35 +14,36 @@
#include "buttons.h"
#define GAME_CYCLE_TIME 50
#define GAME_CYCLE_TIME 10
#define GAMEOVER_DELAY 10
#define MAX_LEVEL 20
#define MAX_LEVEL 100
static uint8_t delayFactor(uint8_t level) {
static uint16_t delayFactor(uint16_t level) {
return MAX_LEVEL + 1 - level;
}
typedef enum {
e_Phase_Game, e_Phase_GameOver
} phase_t;
typedef enum {
e_Start, e_NewStone, e_Down, e_DownDelay, e_ClearRows,
e_Start, e_NewStone, e_Down, e_DownDelay,
e_ClearRowInit, e_ClearRowNext, e_ClearRowCheck, e_ClearRowFlash, e_ClearRowFlashDelay, e_ClearRowWipe,
e_GameOver, e_GameOverFill, e_GameOverWipe, e_GameOverDelay
} state_t;
void gameExec(void *handle) {
static phase_t phase;
static state_t state = e_Start;
static uint8_t gameOverDelay;
static uint8_t rowIndex;
static uint8_t proceedDelay;
static uint8_t level;
static uint16_t proceedDelay;
static uint16_t level;
static uint16_t filledLines;
static uint16_t score;
static bool newHighScoreAchieved;
static uint8_t clearCheckCnt;
#ifdef STATE_DEBUGGING
displaySetValue(state);
#endif
// --- engine begin -------------------------------------------------------
switch (state) {
// --- phase: game --------------------------------------------------------
@ -51,7 +54,6 @@ void gameExec(void *handle) {
filledLines = 0;
score = 0;
newHighScoreAchieved = false;
phase = e_Phase_Game;
state = e_NewStone;
break;
@ -68,30 +70,77 @@ void gameExec(void *handle) {
case e_DownDelay:
proceedDelay--;
if (proceedDelay == 0) {
rowIndex = 0;
state = e_ClearRows;
}
break;
case e_ClearRows:
state = e_Down;
}
break;
case e_Down:
if (! stoneMoveDown()) {
soundCtrl(SOUND_LOCK);
state = e_NewStone;
stoneLock();
state = e_ClearRowInit;
} else {
proceedDelay = delayFactor(level);
state = e_DownDelay;
}
break;
// --- phase: clear rows --------------------------------------------------
case e_ClearRowInit:
clearCheckCnt = 0;
state = e_ClearRowCheck;
break;
case e_ClearRowNext:
if (clearCheckCnt >= CANVAS_HEIGHT) {
state = e_NewStone;
} else {
clearCheckCnt += 1;
state = e_ClearRowCheck;
}
break;
case e_ClearRowCheck:
if (canvasIsRowFilled(clearCheckCnt)) {
score += level;
if (score > eepromReadHighScore()) {
newHighScoreAchieved = true;
eepromWriteHighScore(score);
}
state = e_ClearRowFlash;
} else {
state = e_ClearRowNext;
}
break;
case e_ClearRowFlash:
canvasFillRow(clearCheckCnt, _flash);
state = e_ClearRowFlashDelay;
break;
case e_ClearRowFlashDelay:
state = e_ClearRowWipe;
break;
case e_ClearRowWipe:
canvasWipeRow(clearCheckCnt);
filledLines += 1;
if ((filledLines > 0) && ((filledLines % 10) == 0)) {
if (level < MAX_LEVEL) {
level += 1;
}
soundCtrl(SOUND_FANFARE);
} else {
soundCtrl(SOUND_PLING);
}
state = e_ClearRowNext;
break;
// --- phase: game over ---------------------------------------------------
case e_GameOver:
soundCtrl(SOUND_GAMEOVER);
rowIndex = CANVAS_HEIGHT;
phase = e_Phase_GameOver;
state = e_GameOverFill;
break;
@ -121,42 +170,15 @@ void gameExec(void *handle) {
}
// --- engine end ---------------------------------------------------------
bool wipedLines = false;
canvasShow();
if (phase == e_Phase_Game) {
for (uint8_t r = 0; r < CANVAS_HEIGHT; r++) {
if (canvasIsRowFilled(r)) {
score += level;
if (score > eepromReadHighScore()) {
newHighScoreAchieved = true;
eepromWriteHighScore(score);
}
displaySetValue(score);
canvasWipeRow(r);
canvasShow();
wipedLines = true;
filledLines += 1;
}
}
}
if (wipedLines) {
soundCtrl(SOUND_PLING);
}
if (wipedLines && (filledLines > 0) && ((filledLines % 10) == 0)) {
if (level < MAX_LEVEL) {
level += 1;
}
soundCtrl(SOUND_FANFARE);
}
#ifndef STATE_DEBUGGING
if (isGameActive()) {
displaySetValue(score);
} else {
displaySetValue(eepromReadHighScore());
}
#endif
}
void gameInit() {

View File

@ -1,6 +1,7 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include "shapes.h"
#include "myrand.h"
@ -19,6 +20,7 @@ typedef struct {
orientation_t orientation;
uint8_t x; // column
uint8_t y; // row
bool locked;
} stone_t;
typedef struct {
@ -363,6 +365,11 @@ void stoneCreate() {
stone.orientation = e_0;
stone.x = 4;
stone.y = 0;
stone.locked = false;
}
void stoneLock() {
stone.locked = true;
}
uint8_t stoneIsValid() {
@ -376,6 +383,12 @@ static uint8_t move(direction_t direction) {
if (motions[stone.shape].nullRotation && (direction == e_RotateLeft || direction == e_RotateRight)) {
return 1;
}
// if the stone is already locked, do nothing
if (stone.locked) {
return 0;
}
// check whether the pixels to move to are free
if (canvasIsPixelFree(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[0].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[0].y) &&

View File

@ -5,6 +5,7 @@
void shapesInit();
void stoneCreate();
void stoneLock();
uint8_t stoneIsValid();
uint8_t stoneDraw();
uint8_t stoneMoveDown();

View File

@ -2,6 +2,14 @@
![](./docs/IMG_4936.jpg)
Update Amplifier (separate input circuitry per PSG, it appears, that a silent PSG has a DC level on its output which is summarized to the AC output of the working PSG, so two input circuits with individual couping capacitor):
![](./docs/IMG_4941.jpg)
Update of the power switch of the amplifier (at appears, that the small transistor couldn't deliver enough current):
![](./docs/IMG_4958.jpeg)
This Tetris implementation consists of a hardware and a software (running on that hardware).
The hardware utilizes four MSP430 microcontrollers for 1.) the game play, 2.) the play ground canvas, 3.) the score display and 4.) the sound effects.
@ -54,7 +62,7 @@ An amplifier following the proposal of the AY-3-8913 datasheet is implemented us
The clock generator proposed by the AY-3-8913 does not work reliably, so an alternative design from "The Art of Electronics" has been used.
![](./docs/sound-driver-1.jpg)
![](./docs/sound-driver-2.jpg)
![](./docs/sound-driver-2.png)
![](./docs/sound-driver-3.jpg)
![](./docs/sound-driver-4.jpg)

View File

@ -1,6 +1,6 @@
#include "colors.h"
#define DIMM_FACTOR 3
#define DIMM_FACTOR 5
.section ".rodata","a"
;; color definitions according to
;; https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide/3-custom-color-mixing
@ -35,4 +35,8 @@ white:
.byte 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0
red:
.byte 0xff>>DIMM_FACTOR, 0x00>>DIMM_FACTOR, 0x00>>DIMM_FACTOR, 0
flash:
.byte 0xff, 0xff, 0xff, 0
;; .byte 0x00, 0x00, 0x00, 0
;; .byte 0x80>>3, 0x00>>3, 0xff>>3, 0

View File

@ -16,6 +16,7 @@
#define _yellow 0x0b
#define _white 0x0c
#define _red 0x0d
#define _flash 0x0e

View File

@ -4,15 +4,13 @@ OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
ARTIFACT=firmware
MCU=msp430g2553
COMMONFLAGS=-Wall -mmcu=$(MCU) -I $(TOOLCHAIN_PREFIX)/include -g0 -fdata-sections -ffunction-sections
DEBUGFLAGS=
# DEBUGFLAGS+= -g3 -ggdb -gdwarf-2
COMMONFLAGS=-Wall -mmcu=$(MCU) -I $(TOOLCHAIN_PREFIX)/include -O0 -g0 $(DEBUGFLAGS)
CFLAGS=$(COMMONFLAGS) -std=gnu99
ASFLAGS=$(COMMONFLAGS) -D__ASSEMBLER__
CFLAGS=$(COMMONFLAGS) -std=gnu99 -O0 $(DEBUGFLAGS)
ASFLAGS=$(COMMONFLAGS) -D__ASSEMBLER__ -Os $(DEBUGFLAGS)
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include -Wl,-Map,firmware.map
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
$(ARTIFACT).elf: main.o scheduler.o spi.o spi_init.o sequencer.o melody_tetris.o melody_tusch1.o psg.o mute.o melody_pling.o
$(CC) -o $@ $(LDFLAGS) $^

View File

@ -6,6 +6,8 @@
#include "psg.h"
#include "scheduler.h"
#include "sequencer.h"
#include "melody_tetris.h"
#include "melody_tusch1.h"
#include "mute.h"
int main() {
@ -29,6 +31,7 @@ int main() {
__enable_interrupt();
while (1) {
schExec();
}

View File

@ -5,8 +5,11 @@
#include "scheduler.h"
const t_tone plingVoice1[] = {
{ .octave = e_O_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_Cis, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_D, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_Dis, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_E, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_SyncMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_StopMark,.legato = false, .staccato = false },
@ -16,10 +19,9 @@ const t_tone plingVoice1[] = {
t_melodies pling = {
.melodies = { { .amplitude = 12, .tones = plingVoice1 } },
.chip = 1,
.numOfMelodies = 1,
.pace = 200,
.slotMask = 0x02
.chip = 1
};
void playPling() {

View File

@ -922,21 +922,21 @@ const t_tone voice3[] = {
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
#define INITIAL_PACE 160
t_melodies tetrisTheme = {
.melodies = { { .amplitude = 8, .tones = voice1 }, { .amplitude = 8, .tones = voice2 }, { .amplitude = 8, .tones = voice3 } },
.chip = 0,
.numOfMelodies = 3,
.pace = 160,
.slotMask = 0x01
.pace = INITIAL_PACE,
.chip = 0
};
void playMelodyTetris() {
tetrisTheme.pace = 160; // reset to start value each time
tetrisTheme.pace = INITIAL_PACE; // reset to start value each time
sequencerPlayMelodies(&tetrisTheme);
}
void playMelodyTetrisFaster() {
tetrisTheme.pace += 10;
tetrisTheme.pace += 15;
sequencerChangePace(&tetrisTheme);
}

View File

@ -73,14 +73,14 @@ const t_tone tusch1voice3[] = {
t_melodies tusch1 = {
.melodies = { { .amplitude = 12, .tones = tusch1voice1 }, { .amplitude = 12, .tones = tusch1voice2 }, { .amplitude = 12, .tones = tusch1voice3 } },
.chip = 1,
.numOfMelodies = 3,
.pace = 200,
.slotMask = 0x02
.chip = 1
};
void playTusch1() {
sequencerPlayMelodies(&tusch1);
// playMelodyTetrisFaster();
}

View File

@ -8,15 +8,15 @@ void muteInit() {
P1DIR |= BIT6;
// initially, mute
P1OUT |= BIT6;
}
void mute() {
P1OUT |= BIT6;
}
void unMute() {
P1OUT &= ~BIT6;
}
void mute() {
P1OUT &= ~BIT6;
}
void unMute() {
P1OUT |= BIT6;
}

View File

@ -81,19 +81,6 @@ inline static void BUS_OP_CS1_DISABLE() {
BUS_CTRL_REG |= _CS1;
}
#if 0
static void delay() {
asm volatile (
"push r12\n"
"mov.w #5, r12\n"
"loop:\n"
"dec.w r12\n"
"jnz loop\n"
"pop r12\n"
);
}
#endif
static uint8_t psgReadShadow(uint8_t chip, uint8_t address) {
return psgShadowRegisters[chip][address];
}
@ -175,21 +162,5 @@ void psgInit() {
// disable everything
psgWrite(0, _ENABLE_REG, 0xff);
psgWrite(1, _ENABLE_REG, 0xff);
// volume to 0 on all channels
psgWrite(0, CHANNEL_A_AMPLITUDE_REG, 0);
psgWrite(0, CHANNEL_B_AMPLITUDE_REG, 0);
psgWrite(0, CHANNEL_C_AMPLITUDE_REG, 0);
psgWrite(1, CHANNEL_A_AMPLITUDE_REG, 0);
psgWrite(1, CHANNEL_B_AMPLITUDE_REG, 0);
psgWrite(1, CHANNEL_C_AMPLITUDE_REG, 0);
// frequency preset
psgWriteFrequency(0, 0, 0);
psgWriteFrequency(0, 1, 0);
psgWriteFrequency(0, 2, 0);
psgWriteFrequency(1, 0, 0);
psgWriteFrequency(1, 1, 0);
psgWriteFrequency(1, 2, 0);
}

View File

@ -52,16 +52,6 @@ uint16_t schAdd(void (*exec)(void *), void *handle, uint32_t delay, uint32_t per
return taskId;
}
/*
void schDel(void (*exec)(void *), void *handle) {
for (uint16_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if ((tasks[i].exec == exec) && (tasks[i].handle == handle)) {
tasks[i].exec = NULL;
break;
}
}
}
*/
void schDel(uint16_t taskId) {
tasks[taskId].exec = NULL;
}

View File

@ -96,9 +96,11 @@ void sequencerExec(void *handle) {
melody->state = e_PlayTone;
break;
case e_Hold:
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
break;
case e_Terminate:
schDel(melodies->taskId);
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
slots &= ~(melodies->slotMask);
break;
}
@ -106,6 +108,8 @@ void sequencerExec(void *handle) {
}
void sequencerPlayMelodies(t_melodies *melodies) {
melodies->slotMask = (1 << melodies->chip);
if ((slots & melodies->slotMask) != 0) {
return;
}
@ -123,8 +127,11 @@ void sequencerPlayMelodies(t_melodies *melodies) {
}
void sequencerStopMelodies(t_melodies *melodies) {
slots &= ~(melodies->slotMask);
schDel(melodies->taskId);
slots &= ~(melodies->slotMask);
for (uint8_t channel = 0; channel < melodies->numOfMelodies; channel++) {
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
}
}
void sequencerChangePace(t_melodies *melodies) {

View File

@ -51,8 +51,8 @@ typedef struct {
#define SEQUENCER_PERIOD 4 // ms
#define NUM_OF_CHANNELS 3
typedef struct {
uint8_t chip;
uint8_t slotMask;
uint8_t chip;
uint8_t taskId;
uint16_t quarterLength;
uint8_t numOfMelodies;

View File

@ -1,10 +1,6 @@
#include <msp430g2553.h>
#include "soundCodes.h"
;; .global cmd
;; .section .data.spi,"aw"
;;cmd:
;; .byte
.section ".text","ax",@progbits
receive_isr:
@ -29,19 +25,18 @@ spiCmdHandler_2:
spiCmdHandler_3:
bit #SOUND_START, &cmd
jz spiCmdHandler_4
;;call #playMelodyTetris
call #playMelodyTetris
bic #SOUND_START, &cmd
spiCmdHandler_4:
bit #SOUND_GAMEOVER, &cmd
jz spiCmdHandler_5
;;call #stopMelodyTetris
;; insert a call here
call #stopMelodyTetris
bic #SOUND_GAMEOVER, &cmd
spiCmdHandler_5:
bit #SOUND_FANFARE, &cmd
jz spiCmdHandler_6
;;call #playMelodyTetrisFaster
;;call #playTusch1
call #playMelodyTetrisFaster
call #playTusch1
bic #SOUND_FANFARE, &cmd
spiCmdHandler_6:
bit #SOUND_LOCK, &cmd
@ -56,12 +51,14 @@ spiCmdHandler_7:
spiCmdHandler_8:
bit #SOUND_PLING, &cmd
jz spiCmdHandler_end
;;call #playPling
call #playPling
bic #SOUND_PLING, &cmd
spiCmdHandler_end:
ret
.section "__interrupt_vector_8","ax",@progbits
.word receive_isr
.end