Compare commits
2 Commits
another_wi
...
sound_inte
Author | SHA1 | Date | |
---|---|---|---|
f1afd20021
|
|||
2befb31ac9
|
@ -76,7 +76,7 @@ int main() {
|
|||||||
P1SEL |= BIT4 | BIT5 | BIT6 | BIT7;
|
P1SEL |= BIT4 | BIT5 | BIT6 | BIT7;
|
||||||
P1SEL2 |= BIT4 | BIT5 | BIT6 | BIT7;
|
P1SEL2 |= BIT4 | BIT5 | BIT6 | BIT7;
|
||||||
// most significant bit first, enable STE
|
// most significant bit first, enable STE
|
||||||
UCB0CTL0 = UCCKPH | UCSYNC | UCMSB | UCMODE_2;
|
UCB0CTL0 = UCSYNC | UCMSB | UCMODE_2;
|
||||||
UCB0CTL1 = 0x00;
|
UCB0CTL1 = 0x00;
|
||||||
// enable RX interrupt
|
// enable RX interrupt
|
||||||
UC0IE |= UCB0RXIE;
|
UC0IE |= UCB0RXIE;
|
||||||
|
Before Width: | Height: | Size: 2.0 MiB |
Before Width: | Height: | Size: 603 KiB |
Before Width: | Height: | Size: 262 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 94 KiB |
BIN
docs/logo.jpg
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 183 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 74 KiB |
@ -11,7 +11,7 @@ CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O1 -g0
|
|||||||
|
|
||||||
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
|
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
|
||||||
|
|
||||||
$(ARTIFACT).elf: main.o spi.o scheduler.o canvas.o shapes.o game.o buttons.o myrand.o display.o sound.o eeprom.o
|
$(ARTIFACT).elf: main.o spi.o scheduler.o canvas.o shapes.o game.o buttons.o myrand.o display.o sound.o
|
||||||
$(CC) -o $@ $(LDFLAGS) $^
|
$(CC) -o $@ $(LDFLAGS) $^
|
||||||
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
|
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
|
|
||||||
|
|
||||||
bool mutedFlag = true;
|
|
||||||
|
|
||||||
static uint8_t buttonsMoveLeftPressed() {
|
static uint8_t buttonsMoveLeftPressed() {
|
||||||
static uint8_t last = 0;
|
static uint8_t last = 0;
|
||||||
uint8_t current = (P2IN & BIT4);
|
uint8_t current = (P2IN & BIT4);
|
||||||
@ -51,6 +49,7 @@ static uint8_t buttonsMoveDownPressed() {
|
|||||||
void buttonsExec(void *handle) {
|
void buttonsExec(void *handle) {
|
||||||
static uint32_t unmuteTimestamp;
|
static uint32_t unmuteTimestamp;
|
||||||
uint32_t currentTimestamp = getSeconds();
|
uint32_t currentTimestamp = getSeconds();
|
||||||
|
static bool unmuteFlag = true;
|
||||||
|
|
||||||
|
|
||||||
if (! stoneIsValid()) {
|
if (! stoneIsValid()) {
|
||||||
@ -89,16 +88,16 @@ void buttonsExec(void *handle) {
|
|||||||
if (buttonPressed == 1) {
|
if (buttonPressed == 1) {
|
||||||
canvasShow();
|
canvasShow();
|
||||||
|
|
||||||
if (mutedFlag) {
|
if (! unmuteFlag) {
|
||||||
soundCtrl(SOUND_UNMUTE);
|
soundCtrl(SOUND_UNMUTE);
|
||||||
mutedFlag = false;
|
unmuteFlag = true;
|
||||||
}
|
}
|
||||||
unmuteTimestamp = currentTimestamp;
|
unmuteTimestamp = currentTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((! mutedFlag) && (unmuteTimestamp + MUTE_DELAY < currentTimestamp)) {
|
if (unmuteFlag && (unmuteTimestamp + MUTE_DELAY < currentTimestamp)) {
|
||||||
soundCtrl(SOUND_MUTE);
|
soundCtrl(SOUND_MUTE);
|
||||||
mutedFlag = true;
|
unmuteFlag = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +107,3 @@ void buttonsInit() {
|
|||||||
schAdd(buttonsExec, NULL, 0, 25);
|
schAdd(buttonsExec, NULL, 0, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isGameActive() {
|
|
||||||
return ! mutedFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
#ifndef _BUTTONS_H_
|
#ifndef _BUTTONS_H_
|
||||||
#define _BUTTONS_H_
|
#define _BUTTONS_H_
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
void buttonsInit();
|
void buttonsInit();
|
||||||
bool isGameActive();
|
|
||||||
|
|
||||||
|
|
||||||
#endif // _BUTTONS_H_
|
#endif // _BUTTONS_H_
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include "eeprom.h"
|
|
||||||
#include "spi.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define MAGIC 0xaffe
|
|
||||||
#define HIGHSCORE_ADDR 0x00
|
|
||||||
#define DUMMY 0x00
|
|
||||||
#define CMD_READ 0b00000011
|
|
||||||
#define CMD_WRITE 0b00000010
|
|
||||||
#define CMD_WRDI 0b00000100
|
|
||||||
#define CMD_WREN 0b00000110
|
|
||||||
|
|
||||||
|
|
||||||
typedef union {
|
|
||||||
uint8_t buffer[4];
|
|
||||||
struct {
|
|
||||||
uint16_t magic;
|
|
||||||
uint16_t highScore;
|
|
||||||
} v;
|
|
||||||
} eepromBuf_t;
|
|
||||||
|
|
||||||
eepromBuf_t buf;
|
|
||||||
|
|
||||||
static void writeBuf() {
|
|
||||||
spiSendBegin(e_SPI_EEPROM);
|
|
||||||
spiSendOctet(CMD_WREN);
|
|
||||||
spiSendEnd(e_SPI_EEPROM);
|
|
||||||
|
|
||||||
spiSendBegin(e_SPI_EEPROM);
|
|
||||||
spiSendOctet(CMD_WRITE);
|
|
||||||
spiSendOctet(HIGHSCORE_ADDR);
|
|
||||||
spiSendOctet(buf.buffer[0]);
|
|
||||||
spiSendOctet(buf.buffer[1]);
|
|
||||||
spiSendOctet(buf.buffer[2]);
|
|
||||||
spiSendOctet(buf.buffer[3]);
|
|
||||||
spiSendEnd(e_SPI_EEPROM);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void readBuf() {
|
|
||||||
spiSendBegin(e_SPI_EEPROM);
|
|
||||||
spiSendOctet(CMD_READ);
|
|
||||||
spiReceiveOctet();
|
|
||||||
spiSendOctet(HIGHSCORE_ADDR);
|
|
||||||
spiReceiveOctet();
|
|
||||||
spiSendOctet(DUMMY);
|
|
||||||
buf.buffer[0] = spiReceiveOctet();
|
|
||||||
spiSendOctet(DUMMY);
|
|
||||||
buf.buffer[1] = spiReceiveOctet();
|
|
||||||
spiSendOctet(DUMMY);
|
|
||||||
buf.buffer[2] = spiReceiveOctet();
|
|
||||||
spiSendOctet(DUMMY);
|
|
||||||
buf.buffer[3] = spiReceiveOctet();
|
|
||||||
spiSendEnd(e_SPI_EEPROM);
|
|
||||||
}
|
|
||||||
|
|
||||||
void eepromInit() {
|
|
||||||
readBuf();
|
|
||||||
|
|
||||||
if (buf.v.magic != MAGIC) {
|
|
||||||
buf.v.magic = MAGIC;
|
|
||||||
buf.v.highScore = 0;
|
|
||||||
writeBuf();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t eepromReadHighScore() {
|
|
||||||
return buf.v.highScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
void eepromWriteHighScore(uint16_t v) {
|
|
||||||
buf.v.highScore = v;
|
|
||||||
writeBuf();
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
#ifndef _EEPROM_H_
|
|
||||||
#define _EEPROM_H_
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
|
|
||||||
void eepromInit();
|
|
||||||
uint16_t eepromReadHighScore();
|
|
||||||
void eepromWriteHighScore(uint16_t v);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // _EEPROM_H_
|
|
118
game-ctrl/game.c
@ -1,5 +1,3 @@
|
|||||||
// #define STATE_DEBUGGING
|
|
||||||
|
|
||||||
#include "stddef.h"
|
#include "stddef.h"
|
||||||
#include "stdint.h"
|
#include "stdint.h"
|
||||||
|
|
||||||
@ -10,40 +8,34 @@
|
|||||||
#include "../rgb-driver/colors.h"
|
#include "../rgb-driver/colors.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "eeprom.h"
|
|
||||||
#include "buttons.h"
|
|
||||||
|
|
||||||
|
|
||||||
#define GAME_CYCLE_TIME 10
|
#define GAME_CYCLE_TIME 100
|
||||||
#define GAMEOVER_DELAY 10
|
#define GAMEOVER_DELAY 10
|
||||||
#define MAX_LEVEL 100
|
|
||||||
|
|
||||||
|
|
||||||
static uint16_t delayFactor(uint16_t level) {
|
static uint8_t delayFactor(uint8_t level) {
|
||||||
return MAX_LEVEL + 1 - level;
|
return 11 - level;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
e_Start, e_NewStone, e_Down, e_DownDelay,
|
e_Phase_Game, e_Phase_GameOver
|
||||||
e_ClearRowInit, e_ClearRowNext, e_ClearRowCheck, e_ClearRowFlash, e_ClearRowFlashDelay, e_ClearRowWipe,
|
} phase_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
e_Start, e_NewStone, e_Down, e_DownDelay, e_ClearRows,
|
||||||
e_GameOver, e_GameOverFill, e_GameOverWipe, e_GameOverDelay
|
e_GameOver, e_GameOverFill, e_GameOverWipe, e_GameOverDelay
|
||||||
} state_t;
|
} state_t;
|
||||||
|
|
||||||
void gameExec(void *handle) {
|
void gameExec(void *handle) {
|
||||||
|
static phase_t phase;
|
||||||
static state_t state = e_Start;
|
static state_t state = e_Start;
|
||||||
static uint8_t gameOverDelay;
|
static uint8_t gameOverDelay;
|
||||||
static uint8_t rowIndex;
|
static uint8_t rowIndex;
|
||||||
static uint16_t proceedDelay;
|
static uint8_t proceedDelay;
|
||||||
static uint16_t level;
|
static uint8_t level;
|
||||||
static uint16_t filledLines;
|
|
||||||
static uint16_t score;
|
static uint16_t score;
|
||||||
static bool newHighScoreAchieved;
|
|
||||||
|
|
||||||
static uint8_t clearCheckCnt;
|
|
||||||
|
|
||||||
#ifdef STATE_DEBUGGING
|
|
||||||
displaySetValue(state);
|
|
||||||
#endif
|
|
||||||
// --- engine begin -------------------------------------------------------
|
// --- engine begin -------------------------------------------------------
|
||||||
switch (state) {
|
switch (state) {
|
||||||
// --- phase: game --------------------------------------------------------
|
// --- phase: game --------------------------------------------------------
|
||||||
@ -51,9 +43,9 @@ void gameExec(void *handle) {
|
|||||||
canvasClear();
|
canvasClear();
|
||||||
soundCtrl(SOUND_START);
|
soundCtrl(SOUND_START);
|
||||||
level = 1;
|
level = 1;
|
||||||
filledLines = 0;
|
|
||||||
score = 0;
|
score = 0;
|
||||||
newHighScoreAchieved = false;
|
displaySetValue(score);
|
||||||
|
phase = e_Phase_Game;
|
||||||
state = e_NewStone;
|
state = e_NewStone;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -70,83 +62,36 @@ void gameExec(void *handle) {
|
|||||||
case e_DownDelay:
|
case e_DownDelay:
|
||||||
proceedDelay--;
|
proceedDelay--;
|
||||||
if (proceedDelay == 0) {
|
if (proceedDelay == 0) {
|
||||||
state = e_Down;
|
rowIndex = 0;
|
||||||
|
state = e_ClearRows;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case e_ClearRows:
|
||||||
|
state = e_Down;
|
||||||
|
break;
|
||||||
|
|
||||||
case e_Down:
|
case e_Down:
|
||||||
if (! stoneMoveDown()) {
|
if (! stoneMoveDown()) {
|
||||||
soundCtrl(SOUND_LOCK);
|
soundCtrl(SOUND_LOCK);
|
||||||
stoneLock();
|
state = e_NewStone;
|
||||||
state = e_ClearRowInit;
|
|
||||||
} else {
|
} else {
|
||||||
proceedDelay = delayFactor(level);
|
proceedDelay = delayFactor(level);
|
||||||
state = e_DownDelay;
|
state = e_DownDelay;
|
||||||
}
|
}
|
||||||
break;
|
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 ---------------------------------------------------
|
// --- phase: game over ---------------------------------------------------
|
||||||
case e_GameOver:
|
case e_GameOver:
|
||||||
soundCtrl(SOUND_GAMEOVER);
|
soundCtrl(SOUND_GAMEOVER);
|
||||||
rowIndex = CANVAS_HEIGHT;
|
rowIndex = CANVAS_HEIGHT;
|
||||||
|
phase = e_Phase_GameOver;
|
||||||
state = e_GameOverFill;
|
state = e_GameOverFill;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case e_GameOverFill:
|
case e_GameOverFill:
|
||||||
rowIndex--;
|
rowIndex--;
|
||||||
canvasFillRow(rowIndex, newHighScoreAchieved ? _green : _red);
|
canvasFillRow(rowIndex, _red);
|
||||||
if (rowIndex == 0) {
|
if (rowIndex == 0) {
|
||||||
state = e_GameOverWipe;
|
state = e_GameOverWipe;
|
||||||
}
|
}
|
||||||
@ -171,14 +116,21 @@ void gameExec(void *handle) {
|
|||||||
// --- engine end ---------------------------------------------------------
|
// --- engine end ---------------------------------------------------------
|
||||||
|
|
||||||
canvasShow();
|
canvasShow();
|
||||||
|
if (phase == e_Phase_Game) {
|
||||||
#ifndef STATE_DEBUGGING
|
uint8_t wipeCnt = 0;
|
||||||
if (isGameActive()) {
|
for (uint8_t r = 0; r < CANVAS_HEIGHT; r++) {
|
||||||
|
if (canvasIsRowFilled(r)) {
|
||||||
|
score += level;
|
||||||
displaySetValue(score);
|
displaySetValue(score);
|
||||||
} else {
|
canvasWipeRow(r);
|
||||||
displaySetValue(eepromReadHighScore());
|
canvasShow();
|
||||||
|
wipeCnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wipeCnt != 0) {
|
||||||
|
soundCtrl(SOUND_FANFARE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gameInit() {
|
void gameInit() {
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
#include "myrand.h"
|
#include "myrand.h"
|
||||||
#include "spi.h"
|
#include "spi.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "eeprom.h"
|
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@ -29,7 +28,6 @@ int main() {
|
|||||||
schInit();
|
schInit();
|
||||||
|
|
||||||
spiInit();
|
spiInit();
|
||||||
eepromInit();
|
|
||||||
displayInit();
|
displayInit();
|
||||||
myRandInit();
|
myRandInit();
|
||||||
canvasInit();
|
canvasInit();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "shapes.h"
|
#include "shapes.h"
|
||||||
#include "myrand.h"
|
#include "myrand.h"
|
||||||
@ -20,7 +19,6 @@ typedef struct {
|
|||||||
orientation_t orientation;
|
orientation_t orientation;
|
||||||
uint8_t x; // column
|
uint8_t x; // column
|
||||||
uint8_t y; // row
|
uint8_t y; // row
|
||||||
bool locked;
|
|
||||||
} stone_t;
|
} stone_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -365,11 +363,6 @@ void stoneCreate() {
|
|||||||
stone.orientation = e_0;
|
stone.orientation = e_0;
|
||||||
stone.x = 4;
|
stone.x = 4;
|
||||||
stone.y = 0;
|
stone.y = 0;
|
||||||
stone.locked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stoneLock() {
|
|
||||||
stone.locked = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t stoneIsValid() {
|
uint8_t stoneIsValid() {
|
||||||
@ -383,12 +376,6 @@ static uint8_t move(direction_t direction) {
|
|||||||
if (motions[stone.shape].nullRotation && (direction == e_RotateLeft || direction == e_RotateRight)) {
|
if (motions[stone.shape].nullRotation && (direction == e_RotateLeft || direction == e_RotateRight)) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the stone is already locked, do nothing
|
|
||||||
if (stone.locked) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether the pixels to move to are free
|
// check whether the pixels to move to are free
|
||||||
if (canvasIsPixelFree(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[0].x,
|
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) &&
|
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[0].y) &&
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
void shapesInit();
|
void shapesInit();
|
||||||
void stoneCreate();
|
void stoneCreate();
|
||||||
void stoneLock();
|
|
||||||
uint8_t stoneIsValid();
|
uint8_t stoneIsValid();
|
||||||
uint8_t stoneDraw();
|
uint8_t stoneDraw();
|
||||||
uint8_t stoneMoveDown();
|
uint8_t stoneMoveDown();
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
void spiInit() {
|
void spiInit() {
|
||||||
// SPI in master mode, most significant bit first
|
// SPI in master mode, most significant bit first
|
||||||
UCB0CTL0 = UCCKPH | UCMST | UCMSB;
|
UCB0CTL0 = UCMST | UCMSB;
|
||||||
// SPI timing config
|
// SPI timing config
|
||||||
UCB0CTL1 = UCSSEL_3;
|
UCB0CTL1 = UCSSEL_3;
|
||||||
// Faster than 8 ends up in strange communication errors
|
// Faster than 8 ends up in strange communication errors
|
||||||
@ -19,25 +19,25 @@ void spiInit() {
|
|||||||
// BIT7: UCB0SIMO
|
// BIT7: UCB0SIMO
|
||||||
P1SEL |= BIT5 | BIT6 | BIT7;
|
P1SEL |= BIT5 | BIT6 | BIT7;
|
||||||
P1SEL2 |= BIT5 | BIT6 | BIT7;
|
P1SEL2 |= BIT5 | BIT6 | BIT7;
|
||||||
// P1DIR |= BIT5 | BIT7;
|
P1DIR |= BIT5 | BIT7;
|
||||||
|
|
||||||
// Device Select Lines: 0: Canvas, 1: Display, 2: Sound, 4: EEPROM
|
// Device Select Lines: 0: Canvas, 1: Display, 2: Sound
|
||||||
P1DIR |= BIT0 | BIT1 | BIT2 | BIT4;
|
P1DIR |= BIT0 | BIT1 | BIT2;
|
||||||
// Disable all of them
|
// Disable all of them
|
||||||
P1OUT |= BIT0 | BIT1 | BIT2 | BIT4;
|
P1OUT |= BIT0 | BIT1 | BIT2;
|
||||||
|
|
||||||
// enable SPI module
|
// enable SPI module
|
||||||
UCB0CTL1 &= ~UCSWRST;
|
UCB0CTL1 &= ~UCSWRST;
|
||||||
}
|
}
|
||||||
|
|
||||||
void spiSendBegin(t_SpiDeviceSelector d) {
|
void spiSendBegin(t_SpiDeviceSelector d) {
|
||||||
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2, BIT4 })[d];
|
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2 })[d];
|
||||||
P1OUT &= ~bit;
|
P1OUT &= ~bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
void spiSendEnd(t_SpiDeviceSelector d) {
|
void spiSendEnd(t_SpiDeviceSelector d) {
|
||||||
while (UCB0STAT & UCBUSY);
|
while (UCB0STAT & UCBUSY);
|
||||||
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2, BIT4 })[d];
|
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2 })[d];
|
||||||
P1OUT |= bit;
|
P1OUT |= bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +48,3 @@ void spiSendOctet(uint8_t v) {
|
|||||||
UCB0TXBUF = v;
|
UCB0TXBUF = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t spiReceiveOctet() {
|
|
||||||
while (!(UC0IFG & UCB0RXIFG));
|
|
||||||
uint8_t v = UCB0RXBUF;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -4,13 +4,12 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
typedef enum { e_SPI_CANVAS, e_SPI_DISPLAY, e_SPI_SOUND, e_SPI_EEPROM } t_SpiDeviceSelector;
|
typedef enum { e_SPI_CANVAS, e_SPI_DISPLAY, e_SPI_SOUND } t_SpiDeviceSelector;
|
||||||
|
|
||||||
void spiInit();
|
void spiInit();
|
||||||
void spiSendBegin(t_SpiDeviceSelector d);
|
void spiSendBegin(t_SpiDeviceSelector d);
|
||||||
void spiSendEnd(t_SpiDeviceSelector d);
|
void spiSendEnd(t_SpiDeviceSelector d);
|
||||||
void spiSendOctet(uint8_t v);
|
void spiSendOctet(uint8_t v);
|
||||||
uint8_t spiReceiveOctet();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
68
readme.md
@ -1,68 +0,0 @@
|
|||||||
# Tetris - Hardware and Software
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Update of the power switch of the amplifier (at appears, that the small transistor couldn't deliver enough current):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Further documentation including calculations and drawing can be found in the `docs` subdirs of the four main subdirs.
|
|
||||||
|
|
||||||
## Game Play
|
|
||||||
|
|
||||||
Code is in subdir `game-ctrl` (https://gitea.hottis.de/wn/tetris/src/branch/main/game-ctrl).
|
|
||||||
|
|
||||||
In the firmware for this MSP430 microcontroller the whole game mechanics, reading the buttons, reading and writing the highscore EEPROM and the control of the peripherial microcontrollers are implemented.
|
|
||||||
|
|
||||||
The buttons are debounced using RC circuitry and Schmitt triggers and connected to GPIOs of the microcontroller.
|
|
||||||
|
|
||||||
The peripherial microcontrollers and the EEPROM are connected via SPI including individual chip select lines.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Play Ground Canvas
|
|
||||||
|
|
||||||
Code is in subdir `rgb-driver` (https://gitea.hottis.de/wn/tetris/src/branch/main/rgb-driver).
|
|
||||||
|
|
||||||
The play ground is implemented using a 10 * 20 matrix of PL9823 RGB LEDs which are controlled by another MSP430 microcontroller. The firmware for this microcontroller is implemented for performance and real time requirements in assembly code. Through some discret logic the signals for PL9823 LEDs are generated. Major challenge was to generated the signals according the datasheet of all 200 (including a mini canvas for the stone preview: 212) LEDs in real time without interrupts.
|
|
||||||
|
|
||||||
The communcation with the game play controller is implemented as a sequences of tuples of LED address (0 to 211) and color code. A single octet of 253 where the LED address is expected is taken as the end-of-telegram mark. Readiness to receive a telegram is signaled to the game play controller via a single line connected to a GPIO of the game play controller.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Score Display
|
|
||||||
|
|
||||||
Code is in subdir `display-driver` (https://gitea.hottis.de/wn/tetris/src/branch/main/display-driver).
|
|
||||||
|
|
||||||
In the first place, a MAX7221 was meant to be used for connecting a multiple digit seven-segment display. However, it appears, that the MAX7221 requires 3.5V as minimum voltage for the high-level, which caan't be provided by the MSP430 (which runs on 3.3V) and level-shifters haven't been around. Thus, the minimal required amount of functionality of the MAX7221 has been implemented in C on an MSP430. Just four digits are supported.
|
|
||||||
|
|
||||||
Communication with the game play controller is just a 16 bit number to be displayed.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
## Sound Effects
|
|
||||||
|
|
||||||
Code is in subdir `sound-driver` (https://gitea.hottis.de/wn/tetris/src/branch/main/sound-driver).
|
|
||||||
|
|
||||||
An MSP430 microcontroller and two mediaeval AY-3-8913 sound chips are deployed. The sound chips themselve run on 5V, their 8-bit-address/data bus is connected to the port 2 (bit 0 to 7) of the microcontroller. The bus control signal `_CS`, `BC1` and `BDIR` are generated in software and provided via GPIOs.
|
|
||||||
|
|
||||||
An amplifier following the proposal of the AY-3-8913 datasheet is implemented using a LM386 chip. A MOSFET BS108 controlled via a GPIO is use the shortcut the input of the amplifier to ground to mute sound effects.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
#include "colors.h"
|
#include "colors.h"
|
||||||
|
|
||||||
#define DIMM_FACTOR 5
|
#define DIMM_FACTOR 3
|
||||||
.section ".rodata","a"
|
.section ".rodata","a"
|
||||||
;; color definitions according to
|
;; color definitions according to
|
||||||
;; https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide/3-custom-color-mixing
|
;; https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide/3-custom-color-mixing
|
||||||
@ -35,8 +35,4 @@ white:
|
|||||||
.byte 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0
|
.byte 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0xff>>DIMM_FACTOR, 0
|
||||||
red:
|
red:
|
||||||
.byte 0xff>>DIMM_FACTOR, 0x00>>DIMM_FACTOR, 0x00>>DIMM_FACTOR, 0
|
.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
|
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
#define _yellow 0x0b
|
#define _yellow 0x0b
|
||||||
#define _white 0x0c
|
#define _white 0x0c
|
||||||
#define _red 0x0d
|
#define _red 0x0d
|
||||||
#define _flash 0x0e
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ init:
|
|||||||
|
|
||||||
;; spi configuration
|
;; spi configuration
|
||||||
;; USCI B to slave mode, enable STE and most significant bit first
|
;; USCI B to slave mode, enable STE and most significant bit first
|
||||||
mov.b #UCCKPH|UCSYNC|UCMODE_2|UCMSB, &UCB0CTL0
|
mov.b #UCSYNC|UCMODE_2|UCMSB, &UCB0CTL0
|
||||||
mov.b #0x00, &UCB0CTL1
|
mov.b #0x00, &UCB0CTL1
|
||||||
|
|
||||||
;; make sure the isr will not immediately start
|
;; make sure the isr will not immediately start
|
||||||
|
@ -4,15 +4,16 @@ OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
|
|||||||
|
|
||||||
ARTIFACT=firmware
|
ARTIFACT=firmware
|
||||||
MCU=msp430g2553
|
MCU=msp430g2553
|
||||||
DEBUGFLAGS=
|
COMMONFLAGS=-Wall -mmcu=$(MCU) -I $(TOOLCHAIN_PREFIX)/include -O0 -g0
|
||||||
# DEBUGFLAGS+= -g3 -ggdb -gdwarf-2
|
|
||||||
COMMONFLAGS=-Wall -mmcu=$(MCU) -I $(TOOLCHAIN_PREFIX)/include -O0 -g0 $(DEBUGFLAGS)
|
|
||||||
CFLAGS=$(COMMONFLAGS) -std=gnu99
|
CFLAGS=$(COMMONFLAGS) -std=gnu99
|
||||||
ASFLAGS=$(COMMONFLAGS) -D__ASSEMBLER__
|
ASFLAGS=$(COMMONFLAGS) -D__ASSEMBLER__
|
||||||
|
|
||||||
|
# for debugging
|
||||||
|
CFLAGS+= -g3 -ggdb -gdwarf-2
|
||||||
|
|
||||||
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
|
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
|
$(ARTIFACT).elf: main.o scheduler.o spi.o spi_init.o sequencer.o melody_tetris.o melody_tusch1.o ay_3_8913.o mute.o
|
||||||
$(CC) -o $@ $(LDFLAGS) $^
|
$(CC) -o $@ $(LDFLAGS) $^
|
||||||
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
|
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
|
||||||
|
|
||||||
|
@ -81,6 +81,19 @@ inline static void BUS_OP_CS1_DISABLE() {
|
|||||||
BUS_CTRL_REG |= _CS1;
|
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) {
|
static uint8_t psgReadShadow(uint8_t chip, uint8_t address) {
|
||||||
return psgShadowRegisters[chip][address];
|
return psgShadowRegisters[chip][address];
|
||||||
}
|
}
|
@ -31,6 +31,7 @@ int main() {
|
|||||||
|
|
||||||
__enable_interrupt();
|
__enable_interrupt();
|
||||||
|
|
||||||
|
// playMelodyTetris();
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
schExec();
|
schExec();
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "psg.h"
|
|
||||||
#include "sequencer.h"
|
|
||||||
#include "scheduler.h"
|
|
||||||
|
|
||||||
const t_tone plingVoice1[] = {
|
|
||||||
{ .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 },
|
|
||||||
|
|
||||||
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
|
|
||||||
};
|
|
||||||
|
|
||||||
t_melodies pling = {
|
|
||||||
.melodies = { { .amplitude = 12, .tones = plingVoice1 } },
|
|
||||||
.numOfMelodies = 1,
|
|
||||||
.pace = 200,
|
|
||||||
.chip = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
void playPling() {
|
|
||||||
sequencerPlayMelodies(&pling);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
#ifndef _MELODY_PLING_H_
|
|
||||||
#define _MELODY_PLING_H_
|
|
||||||
|
|
||||||
|
|
||||||
void playPling();
|
|
||||||
|
|
||||||
|
|
||||||
#endif // _MELODY_PLING_H_
|
|
@ -922,27 +922,19 @@ const t_tone voice3[] = {
|
|||||||
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
|
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
|
||||||
};
|
};
|
||||||
|
|
||||||
#define INITIAL_PACE 160
|
|
||||||
t_melodies tetrisTheme = {
|
t_melodies tetrisTheme = {
|
||||||
.melodies = { { .amplitude = 8, .tones = voice1 }, { .amplitude = 8, .tones = voice2 }, { .amplitude = 8, .tones = voice3 } },
|
.melodies = { { .chip = 0, .amplitude = 8, .tones = voice1 }, { .chip = 0, .amplitude = 8, .tones = voice2 }, { .chip = 0, .amplitude = 8, .tones = voice3 } },
|
||||||
.numOfMelodies = 3,
|
.numOfMelodies = 3,
|
||||||
.pace = INITIAL_PACE,
|
.pace = 160,
|
||||||
.chip = 0
|
.slotMask = 0x01
|
||||||
};
|
};
|
||||||
|
|
||||||
void playMelodyTetris() {
|
void playMelodyTetris() {
|
||||||
tetrisTheme.pace = INITIAL_PACE; // reset to start value each time
|
|
||||||
sequencerPlayMelodies(&tetrisTheme);
|
sequencerPlayMelodies(&tetrisTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
void playMelodyTetrisFaster() {
|
|
||||||
tetrisTheme.pace += 15;
|
|
||||||
sequencerChangePace(&tetrisTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopMelodyTetris() {
|
void stopMelodyTetris() {
|
||||||
sequencerStopMelodies(&tetrisTheme);
|
sequencerStopMelodies(&tetrisTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
void playMelodyTetris();
|
void playMelodyTetris();
|
||||||
void stopMelodyTetris();
|
void stopMelodyTetris();
|
||||||
void playMelodyTetrisFaster();
|
|
||||||
|
|
||||||
#endif // _MELODY_TETRIS_H_
|
#endif // _MELODY_TETRIS_H_
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include "psg.h"
|
#include "psg.h"
|
||||||
#include "sequencer.h"
|
#include "sequencer.h"
|
||||||
#include "scheduler.h"
|
#include "scheduler.h"
|
||||||
#include "melody_tetris.h"
|
|
||||||
|
|
||||||
const t_tone tusch1voice1[] = {
|
const t_tone tusch1voice1[] = {
|
||||||
{ .octave = e_O_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
|
{ .octave = e_O_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
|
||||||
@ -72,15 +71,14 @@ const t_tone tusch1voice3[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
t_melodies tusch1 = {
|
t_melodies tusch1 = {
|
||||||
.melodies = { { .amplitude = 12, .tones = tusch1voice1 }, { .amplitude = 12, .tones = tusch1voice2 }, { .amplitude = 12, .tones = tusch1voice3 } },
|
.melodies = { { .chip = 1, .amplitude = 12, .tones = tusch1voice1 }, { .chip = 1, .amplitude = 12, .tones = tusch1voice2 }, { .chip = 1, .amplitude = 12, .tones = tusch1voice3 } },
|
||||||
.numOfMelodies = 3,
|
.numOfMelodies = 3,
|
||||||
.pace = 200,
|
.pace = 200,
|
||||||
.chip = 1
|
.slotMask = 0x02
|
||||||
};
|
};
|
||||||
|
|
||||||
void playTusch1() {
|
void playTusch1() {
|
||||||
sequencerPlayMelodies(&tusch1);
|
sequencerPlayMelodies(&tusch1);
|
||||||
// playMelodyTetrisFaster();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,17 +6,15 @@
|
|||||||
void muteInit() {
|
void muteInit() {
|
||||||
// BIT6: MuteCtrl
|
// BIT6: MuteCtrl
|
||||||
P1DIR |= BIT6;
|
P1DIR |= BIT6;
|
||||||
|
|
||||||
// initially, mute
|
|
||||||
P1OUT &= ~BIT6;
|
P1OUT &= ~BIT6;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mute() {
|
void mute() {
|
||||||
P1OUT &= ~BIT6;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unMute() {
|
|
||||||
P1OUT |= BIT6;
|
P1OUT |= BIT6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void unMute() {
|
||||||
|
P1OUT &= ~BIT6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,16 @@ uint16_t schAdd(void (*exec)(void *), void *handle, uint32_t delay, uint32_t per
|
|||||||
return taskId;
|
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) {
|
void schDel(uint16_t taskId) {
|
||||||
tasks[taskId].exec = NULL;
|
tasks[taskId].exec = NULL;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ void sequencerExec(void *handle) {
|
|||||||
if (melody->tones[melody->idx].length == e_L_EndMark) {
|
if (melody->tones[melody->idx].length == e_L_EndMark) {
|
||||||
melody->idx = 0;
|
melody->idx = 0;
|
||||||
}
|
}
|
||||||
psgPlayTone(melodies->chip, channel, melody->amplitude, melody->tones[melody->idx].octave, melody->tones[melody->idx].note);
|
psgPlayTone(melody->chip, channel, melody->amplitude, melody->tones[melody->idx].octave, melody->tones[melody->idx].note);
|
||||||
melody->lengthCnt = (melody->tones[melody->idx].staccato) ?
|
melody->lengthCnt = (melody->tones[melody->idx].staccato) ?
|
||||||
(calcLength(melodies, melody->tones[melody->idx].length) / 2) :
|
(calcLength(melodies, melody->tones[melody->idx].length) / 2) :
|
||||||
calcLength(melodies, melody->tones[melody->idx].length);
|
calcLength(melodies, melody->tones[melody->idx].length);
|
||||||
@ -78,7 +78,7 @@ void sequencerExec(void *handle) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case e_StaccatoBreak:
|
case e_StaccatoBreak:
|
||||||
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
|
psgPlayTone(melody->chip, channel, 0, e_O_Null, e_Pause);
|
||||||
melody->lengthCnt = calcLength(melodies, melody->tones[melody->idx].length) / 2;
|
melody->lengthCnt = calcLength(melodies, melody->tones[melody->idx].length) / 2;
|
||||||
melody->state = e_HoldStaccatoBreak;
|
melody->state = e_HoldStaccatoBreak;
|
||||||
break;
|
break;
|
||||||
@ -90,17 +90,15 @@ void sequencerExec(void *handle) {
|
|||||||
break;
|
break;
|
||||||
case e_SeparateTone:
|
case e_SeparateTone:
|
||||||
if (! (melody->tones[melody->idx].legato)) {
|
if (! (melody->tones[melody->idx].legato)) {
|
||||||
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
|
psgPlayTone(melody->chip, channel, 0, e_O_Null, e_Pause);
|
||||||
}
|
}
|
||||||
melody->idx += 1;
|
melody->idx += 1;
|
||||||
melody->state = e_PlayTone;
|
melody->state = e_PlayTone;
|
||||||
break;
|
break;
|
||||||
case e_Hold:
|
case e_Hold:
|
||||||
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
|
|
||||||
break;
|
break;
|
||||||
case e_Terminate:
|
case e_Terminate:
|
||||||
schDel(melodies->taskId);
|
schDel(melodies->taskId);
|
||||||
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
|
|
||||||
slots &= ~(melodies->slotMask);
|
slots &= ~(melodies->slotMask);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -108,8 +106,6 @@ void sequencerExec(void *handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sequencerPlayMelodies(t_melodies *melodies) {
|
void sequencerPlayMelodies(t_melodies *melodies) {
|
||||||
melodies->slotMask = (1 << melodies->chip);
|
|
||||||
|
|
||||||
if ((slots & melodies->slotMask) != 0) {
|
if ((slots & melodies->slotMask) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -127,14 +123,7 @@ void sequencerPlayMelodies(t_melodies *melodies) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sequencerStopMelodies(t_melodies *melodies) {
|
void sequencerStopMelodies(t_melodies *melodies) {
|
||||||
schDel(melodies->taskId);
|
|
||||||
slots &= ~(melodies->slotMask);
|
slots &= ~(melodies->slotMask);
|
||||||
for (uint8_t channel = 0; channel < melodies->numOfMelodies; channel++) {
|
schDel(melodies->taskId);
|
||||||
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sequencerChangePace(t_melodies *melodies) {
|
|
||||||
melodies->quarterLength = 60000 / melodies->pace / SEQUENCER_PERIOD; // duration of a 1/4 tone in ms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ typedef enum {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t idx;
|
uint16_t idx;
|
||||||
|
uint8_t chip;
|
||||||
uint16_t lengthCnt;
|
uint16_t lengthCnt;
|
||||||
t_sequencerState state;
|
t_sequencerState state;
|
||||||
uint8_t amplitude;
|
uint8_t amplitude;
|
||||||
@ -52,11 +53,10 @@ typedef struct {
|
|||||||
#define NUM_OF_CHANNELS 3
|
#define NUM_OF_CHANNELS 3
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t slotMask;
|
uint8_t slotMask;
|
||||||
uint8_t chip;
|
|
||||||
uint8_t taskId;
|
uint8_t taskId;
|
||||||
uint16_t quarterLength;
|
uint16_t quarterLength;
|
||||||
uint8_t numOfMelodies;
|
uint8_t numOfMelodies;
|
||||||
uint16_t pace; // quarter notes per minute
|
uint8_t pace; // quarter notes per minute
|
||||||
uint8_t sync;
|
uint8_t sync;
|
||||||
t_melody melodies[NUM_OF_CHANNELS];
|
t_melody melodies[NUM_OF_CHANNELS];
|
||||||
} t_melodies;
|
} t_melodies;
|
||||||
@ -64,6 +64,5 @@ typedef struct {
|
|||||||
void sequencerInit();
|
void sequencerInit();
|
||||||
void sequencerPlayMelodies(t_melodies *melodies);
|
void sequencerPlayMelodies(t_melodies *melodies);
|
||||||
void sequencerStopMelodies(t_melodies *melodies);
|
void sequencerStopMelodies(t_melodies *melodies);
|
||||||
void sequencerChangePace(t_melodies *melodies);
|
|
||||||
|
|
||||||
#endif // _SEQUENCER_H_
|
#endif // _SEQUENCER_H_
|
||||||
|
171
sound-driver/sn76489an.c
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#include <msp430g2553.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "psg.h"
|
||||||
|
#include "scheduler.h"
|
||||||
|
|
||||||
|
|
||||||
|
// generated using utils/calc-76489an.py
|
||||||
|
const uint16_t frequencyCodes[8][12] = {
|
||||||
|
{ 3420, 3229, 3047, 2876, 2715, 2562, 2419, 2283, 2155, 2034, 1920, 1812 },
|
||||||
|
{ 1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017, 960, 906 },
|
||||||
|
{ 855, 807, 762, 719, 679, 641, 605, 571, 539, 508, 480, 453 },
|
||||||
|
{ 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226 },
|
||||||
|
{ 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 },
|
||||||
|
{ 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57 },
|
||||||
|
{ 53, 50, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28 },
|
||||||
|
{ 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14 }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define ADDR_DATA_REG P2OUT
|
||||||
|
#define BUS_CTRL_REG P1OUT
|
||||||
|
#define BUS_CTRL_IN_REG P1IN
|
||||||
|
#define _CS0 BIT0
|
||||||
|
#define _CS1 BIT1
|
||||||
|
#define _WE BIT2
|
||||||
|
#define READY BIT3
|
||||||
|
|
||||||
|
#define CHANNEL_A_PERIOD_ADDR 0
|
||||||
|
#define CHANNEL_A_ATTEN_ADDR 1
|
||||||
|
#define CHANNEL_B_PERIOD_ADDR 2
|
||||||
|
#define CHANNEL_B_ATTEN_ADDR 3
|
||||||
|
#define CHANNEL_C_PERIOD_ADDR 4
|
||||||
|
#define CHANNEL_C_ATTEN_ADDR 5
|
||||||
|
|
||||||
|
#define IGNORE_OCTET 0xff
|
||||||
|
|
||||||
|
uint8_t psgAmplitudeShadowValue[3];
|
||||||
|
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void WRITE_CYCLE(uint8_t chipNo) {
|
||||||
|
if (chipNo == 0) {
|
||||||
|
BUS_CTRL_REG &= ~_CS0;
|
||||||
|
} else {
|
||||||
|
BUS_CTRL_REG &= ~_CS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
BUS_CTRL_REG &= ~_WE;
|
||||||
|
|
||||||
|
delay();
|
||||||
|
|
||||||
|
while ((BUS_CTRL_IN_REG & READY) == 0);
|
||||||
|
|
||||||
|
BUS_CTRL_REG |= _WE;
|
||||||
|
|
||||||
|
if (chipNo == 0) {
|
||||||
|
BUS_CTRL_REG |= _CS0;
|
||||||
|
} else {
|
||||||
|
BUS_CTRL_REG |= _CS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void psgWrite(uint8_t chipNo, uint8_t value) {
|
||||||
|
ADDR_DATA_REG = value;
|
||||||
|
WRITE_CYCLE(chipNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void psgWriteFrequency(uint8_t channel, uint16_t frequencyCode) {
|
||||||
|
uint8_t chipNo = channel / 3;
|
||||||
|
uint8_t regAddr = (channel % 3) * 2;
|
||||||
|
|
||||||
|
// bit order in frequncyCode and order in octet on data bus are reversed
|
||||||
|
// see datacheat cp. 1 and cp. 6
|
||||||
|
uint8_t firstOctet = 0x01;
|
||||||
|
firstOctet |= ((regAddr & 0x04) > 1);
|
||||||
|
firstOctet |= ((regAddr & 0x02) < 1);
|
||||||
|
firstOctet |= ((regAddr & 0x01) < 3);
|
||||||
|
uint8_t lowerPart = frequencyCode & 0x0f;
|
||||||
|
firstOctet |= ((lowerPart & 0x08) << 1);
|
||||||
|
firstOctet |= ((lowerPart & 0x04) << 3);
|
||||||
|
firstOctet |= ((lowerPart & 0x02) << 5);
|
||||||
|
firstOctet |= ((lowerPart & 0x01) << 7);
|
||||||
|
|
||||||
|
uint8_t secondOctet = 0;
|
||||||
|
uint8_t upperPart = (frequencyCode & 0x03f0) >> 4;
|
||||||
|
secondOctet |= ((upperPart & 0x20) >> 3);
|
||||||
|
secondOctet |= ((upperPart & 0x10) >> 1);
|
||||||
|
secondOctet |= ((upperPart & 0x08) << 1);
|
||||||
|
secondOctet |= ((upperPart & 0x04) << 3);
|
||||||
|
secondOctet |= ((upperPart & 0x02) << 5);
|
||||||
|
secondOctet |= ((upperPart & 0x01) << 7);
|
||||||
|
|
||||||
|
ADDR_DATA_REG = firstOctet;
|
||||||
|
WRITE_CYCLE(chipNo);
|
||||||
|
|
||||||
|
ADDR_DATA_REG = secondOctet;
|
||||||
|
WRITE_CYCLE(chipNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void psgAmplitude(uint8_t channel, uint8_t volume) {
|
||||||
|
psgAmplitudeShadowValue[channel] = volume;
|
||||||
|
uint8_t chipNo = channel / 3;
|
||||||
|
uint8_t regAddr = ((channel % 3) * 2) + 1;
|
||||||
|
|
||||||
|
uint8_t attenuation = 15 - volume;
|
||||||
|
|
||||||
|
uint8_t firstOctet = 0x01;
|
||||||
|
firstOctet |= ((regAddr & 0x04) >> 1);
|
||||||
|
firstOctet |= ((regAddr & 0x02) << 1);
|
||||||
|
firstOctet |= ((regAddr & 0x01) << 3);
|
||||||
|
firstOctet |= ((attenuation & 0x01) << 7);
|
||||||
|
firstOctet |= ((attenuation & 0x02) << 5);
|
||||||
|
firstOctet |= ((attenuation & 0x04) << 3);
|
||||||
|
firstOctet |= ((attenuation & 0x08) << 1);
|
||||||
|
|
||||||
|
ADDR_DATA_REG = firstOctet;
|
||||||
|
WRITE_CYCLE(chipNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void psgPlayTone(uint8_t channel, uint8_t volume, t_octave octave, t_note note) {
|
||||||
|
if (note == e_Pause) {
|
||||||
|
psgAmplitude(channel, 0);
|
||||||
|
} else {
|
||||||
|
// if (psgAmplitudeShadowValue[channel] == 0) {
|
||||||
|
psgAmplitude(channel, volume);
|
||||||
|
// }
|
||||||
|
psgWriteFrequency(channel, frequencyCodes[octave][note]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void psgInit() {
|
||||||
|
// address/data bus
|
||||||
|
P2DIR = 0xff;
|
||||||
|
P2SEL = 0;
|
||||||
|
P2SEL2 = 0;
|
||||||
|
|
||||||
|
// bus control lines
|
||||||
|
// output:
|
||||||
|
// BIT0: /CS chip 0
|
||||||
|
// BIT1: /CS chip 1
|
||||||
|
// BIT2: /WE
|
||||||
|
// input:
|
||||||
|
// BIT3: READY
|
||||||
|
P1DIR |= BIT0 | BIT1 | BIT2;
|
||||||
|
P1DIR &= ~BIT3;
|
||||||
|
// immediately disable all outputs, all are active low
|
||||||
|
P1OUT |= BIT0 | BIT1 | BIT2;
|
||||||
|
|
||||||
|
// shutdown all channels including noise
|
||||||
|
psgWrite(0, 0b11111001);
|
||||||
|
psgWrite(0, 0b11111101);
|
||||||
|
psgWrite(0, 0b11111011);
|
||||||
|
psgWrite(0, 0b11111111);
|
||||||
|
|
||||||
|
// psgPlayTone(0, 5, e_O_3, e_A);
|
||||||
|
psgAmplitude(0, 3);
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,6 @@
|
|||||||
#define SOUND_FANFARE 0x10
|
#define SOUND_FANFARE 0x10
|
||||||
#define SOUND_LOCK 0x20
|
#define SOUND_LOCK 0x20
|
||||||
#define SOUND_MOTION 0x40
|
#define SOUND_MOTION 0x40
|
||||||
#define SOUND_PLING 0x80
|
#define SOUND_SPEED_UP 0x80
|
||||||
|
|
||||||
#endif // _SOUND_CODES_H_
|
#endif // _SOUND_CODES_H_
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
#include <msp430g2553.h>
|
#include <msp430g2553.h>
|
||||||
#include "soundCodes.h"
|
#include "soundCodes.h"
|
||||||
|
|
||||||
|
.section ".data"
|
||||||
|
.global cmd
|
||||||
|
cmd:
|
||||||
|
.byte
|
||||||
|
|
||||||
.section ".text","ax",@progbits
|
.section ".text","ax",@progbits
|
||||||
receive_isr:
|
receive_isr:
|
||||||
@ -30,12 +34,11 @@ spiCmdHandler_3:
|
|||||||
spiCmdHandler_4:
|
spiCmdHandler_4:
|
||||||
bit #SOUND_GAMEOVER, &cmd
|
bit #SOUND_GAMEOVER, &cmd
|
||||||
jz spiCmdHandler_5
|
jz spiCmdHandler_5
|
||||||
call #stopMelodyTetris
|
;; insert a call here
|
||||||
bic #SOUND_GAMEOVER, &cmd
|
bic #SOUND_GAMEOVER, &cmd
|
||||||
spiCmdHandler_5:
|
spiCmdHandler_5:
|
||||||
bit #SOUND_FANFARE, &cmd
|
bit #SOUND_FANFARE, &cmd
|
||||||
jz spiCmdHandler_6
|
jz spiCmdHandler_6
|
||||||
call #playMelodyTetrisFaster
|
|
||||||
call #playTusch1
|
call #playTusch1
|
||||||
bic #SOUND_FANFARE, &cmd
|
bic #SOUND_FANFARE, &cmd
|
||||||
spiCmdHandler_6:
|
spiCmdHandler_6:
|
||||||
@ -49,14 +52,17 @@ spiCmdHandler_7:
|
|||||||
;; insert a call here
|
;; insert a call here
|
||||||
bic #SOUND_MOTION, &cmd
|
bic #SOUND_MOTION, &cmd
|
||||||
spiCmdHandler_8:
|
spiCmdHandler_8:
|
||||||
bit #SOUND_PLING, &cmd
|
bit #SOUND_SPEED_UP, &cmd
|
||||||
jz spiCmdHandler_end
|
jz spiCmdHandler_end
|
||||||
call #playPling
|
;; insert a call here
|
||||||
bic #SOUND_PLING, &cmd
|
bic #SOUND_SPEED_UP, &cmd
|
||||||
spiCmdHandler_end:
|
spiCmdHandler_end:
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.section "__interrupt_vector_8","ax",@progbits
|
.section "__interrupt_vector_8","ax",@progbits
|
||||||
.word receive_isr
|
.word receive_isr
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include "soundCodes.h"
|
#include "soundCodes.h"
|
||||||
|
|
||||||
|
|
||||||
uint8_t cmd;
|
extern uint8_t cmd;
|
||||||
|
|
||||||
void spiInit() {
|
void spiInit() {
|
||||||
// SPI slave
|
// SPI slave
|
||||||
@ -18,7 +18,7 @@ void spiInit() {
|
|||||||
P1SEL2 |= BIT4 | BIT5 | BIT7;
|
P1SEL2 |= BIT4 | BIT5 | BIT7;
|
||||||
|
|
||||||
// most significant bit first, enable STE
|
// most significant bit first, enable STE
|
||||||
UCB0CTL0 = UCCKPH | UCSYNC | UCMSB | UCMODE_2;
|
UCB0CTL0 = UCSYNC | UCMSB | UCMODE_2;
|
||||||
UCB0CTL1 = 0x00;
|
UCB0CTL1 = 0x00;
|
||||||
|
|
||||||
// enable RX interrupt
|
// enable RX interrupt
|
||||||
|