46 Commits

Author SHA1 Message Date
1607dc62dd fix wrong wipe behaviour 2024-05-03 23:10:29 +02:00
78df7eee66 start new wipe approach 2024-05-03 16:21:20 +02:00
09fe302e63 comment on first amplifier udpate 2024-04-28 12:43:38 +02:00
d28e62fdd3 update power switch of amplifier 2024-04-28 12:41:20 +02:00
01d4fe3f85 update on docs 2024-04-26 14:33:09 +02:00
5de2761fde change pling and way to mute 2024-04-26 12:55:21 +02:00
1f807cdb7c disable debug 2024-04-25 22:34:11 +02:00
735599ee7f better pling 2024-04-25 19:14:46 +02:00
7a2c9f96d4 different pling, still boring 2024-04-25 12:21:50 +02:00
5d7a94c3b2 sound tuning 2024-04-25 12:12:52 +02:00
1d915baf77 docs 2024-04-23 15:38:11 +02:00
08b96a6617 photo 2024-04-23 15:34:15 +02:00
ff95034605 level and speed up stuff 2024-04-23 12:45:39 +02:00
ac4801c7cf docs update 2024-04-22 15:51:05 +02:00
a4cb8129c5 update 2024-04-22 15:48:49 +02:00
f5fa1e5e22 single schematics pages 2024-04-22 15:46:30 +02:00
1d96cee661 Merge branch 'main' of gitea.hottis.de:wn/tetris 2024-04-22 15:34:41 +02:00
7a560959c1 readme 2024-04-22 15:34:29 +02:00
8d8a818cf9 updates schematics 2024-04-19 22:51:39 +02:00
b400431607 add highscore display 2024-04-19 11:38:32 +02:00
7933aa46ae new schematics 2024-04-18 15:16:58 +02:00
076c0f3f1a start in muted state 2024-04-18 13:48:36 +02:00
b49665512f dead code dropped 2024-04-18 13:31:28 +02:00
36d3b2f735 sound works 2024-04-17 15:30:45 +02:00
d68dae167d pragmas 2024-04-16 10:19:08 +02:00
3412197cae pragmas 2024-04-16 10:16:56 +02:00
8dc0569407 pragmas 2024-04-16 10:16:13 +02:00
ac6ca860cb pragmas 2024-04-16 10:14:03 +02:00
2711f5fb4b some sounds 2024-04-15 17:10:33 +02:00
9674bc8ef5 fix 2024-04-15 17:04:27 +02:00
c0ee849cec fix 2024-04-15 17:03:16 +02:00
78c906ef26 fix 2024-04-15 17:02:09 +02:00
0c4533bfac fix 2024-04-15 17:00:59 +02:00
3eed2e30eb cmd in sound 2024-04-15 16:59:50 +02:00
3cebb5d351 fix 2024-04-15 16:47:05 +02:00
a29525ac9e fix 2024-04-15 16:46:24 +02:00
83e6227581 fix 2024-04-15 16:45:31 +02:00
010c493b90 more sound effects 2024-04-15 16:42:33 +02:00
2404910870 add getSeconds, sound controller, mute/unmute switches 2024-04-15 16:23:51 +02:00
53e538b112 docs 2024-04-14 19:39:51 +02:00
1232d0b884 changes 2024-04-14 19:39:10 +02:00
a1d6422897 some sound tests 2024-04-14 14:59:37 +02:00
761de5a94d add mute switch 2024-04-10 14:37:20 +02:00
9a58eedcc4 melodies 2024-04-10 14:10:49 +02:00
2fb12f8af0 melodies 2024-04-10 14:10:42 +02:00
fef3f69f63 fixed 2024-04-10 14:00:12 +02:00
53 changed files with 688 additions and 534 deletions

View File

@ -76,7 +76,7 @@ int main() {
P1SEL |= BIT4 | BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT4 | BIT5 | BIT6 | BIT7;
// most significant bit first, enable STE
UCB0CTL0 = UCSYNC | UCMSB | UCMODE_2;
UCB0CTL0 = UCCKPH | UCSYNC | UCMSB | UCMODE_2;
UCB0CTL1 = 0x00;
// enable RX interrupt
UC0IE |= UCB0RXIE;

BIN
docs/IMG_4936.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

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

BIN
docs/display-driver.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/game-ctrl.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/reset-ctrl.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
docs/rgb-driver.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

BIN
docs/sound-driver-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

BIN
docs/sound-driver-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/sound-driver-4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -11,7 +11,7 @@ CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O1 -g0
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
$(ARTIFACT).elf: main.o spi.o scheduler.o canvas.o shapes.o game.o buttons.o myrand.o display.o sound.o eeprom.o
$(CC) -o $@ $(LDFLAGS) $^
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt

View File

@ -1,13 +1,17 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <msp430g2553.h>
#include "buttons.h"
#include "scheduler.h"
#include "shapes.h"
#include "canvas.h"
#include "sound.h"
bool mutedFlag = true;
static uint8_t buttonsMoveLeftPressed() {
static uint8_t last = 0;
uint8_t current = (P2IN & BIT4);
@ -45,6 +49,10 @@ static uint8_t buttonsMoveDownPressed() {
}
void buttonsExec(void *handle) {
static uint32_t unmuteTimestamp;
uint32_t currentTimestamp = getSeconds();
if (! stoneIsValid()) {
// don't do anything, the stone has not been initialized
return;
@ -54,27 +62,43 @@ void buttonsExec(void *handle) {
if (buttonsMoveLeftPressed()) {
stoneMoveLeft();
soundCtrl(SOUND_MOTION);
buttonPressed = 1;
}
if (buttonsMoveRightPressed()) {
stoneMoveRight();
soundCtrl(SOUND_MOTION);
buttonPressed = 1;
}
if (buttonsRotateLeftPressed()) {
stoneRotateLeft();
soundCtrl(SOUND_MOTION);
buttonPressed = 1;
}
if (buttonsRotateRightPressed()) {
stoneRotateRight();
soundCtrl(SOUND_MOTION);
buttonPressed = 1;
}
if (buttonsMoveDownPressed()) {
stoneMoveDown();
soundCtrl(SOUND_MOTION);
buttonPressed = 1;
}
if (buttonPressed == 1) {
canvasShow();
if (mutedFlag) {
soundCtrl(SOUND_UNMUTE);
mutedFlag = false;
}
unmuteTimestamp = currentTimestamp;
}
if ((! mutedFlag) && (unmuteTimestamp + MUTE_DELAY < currentTimestamp)) {
soundCtrl(SOUND_MUTE);
mutedFlag = true;
}
}
@ -84,3 +108,7 @@ void buttonsInit() {
schAdd(buttonsExec, NULL, 0, 25);
}
bool isGameActive() {
return ! mutedFlag;
}

View File

@ -1,7 +1,10 @@
#ifndef _BUTTONS_H_
#define _BUTTONS_H_
#include <stdbool.h>
void buttonsInit();
bool isGameActive();
#endif // _BUTTONS_H_

75
game-ctrl/eeprom.c Normal file
View File

@ -0,0 +1,75 @@
#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();
}

13
game-ctrl/eeprom.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef _EEPROM_H_
#define _EEPROM_H_
#include <stdint.h>
void eepromInit();
uint16_t eepromReadHighScore();
void eepromWriteHighScore(uint16_t v);
#endif // _EEPROM_H_

View File

@ -7,43 +7,47 @@
#include "canvas.h"
#include "../rgb-driver/colors.h"
#include "display.h"
#include "sound.h"
#include "eeprom.h"
#include "buttons.h"
#define GAME_CYCLE_TIME 100
#define GAME_CYCLE_TIME 50
#define GAMEOVER_DELAY 10
#define MAX_LEVEL 20
static uint8_t delayFactor(uint8_t level) {
return 11 - 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_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 filledLines;
static uint16_t score;
static bool newHighScoreAchieved;
bool wipedLines = false;
// --- engine begin -------------------------------------------------------
switch (state) {
// --- phase: game --------------------------------------------------------
case e_Start:
canvasClear();
soundCtrl(SOUND_START);
level = 1;
filledLines = 0;
score = 0;
displaySetValue(score);
phase = e_Phase_Game;
newHighScoreAchieved = false;
state = e_NewStone;
break;
@ -61,33 +65,62 @@ void gameExec(void *handle) {
proceedDelay--;
if (proceedDelay == 0) {
rowIndex = 0;
state = e_ClearRows;
}
break;
case e_ClearRows:
state = e_Down;
}
break;
case e_Down:
if (! stoneMoveDown()) {
state = e_NewStone;
soundCtrl(SOUND_LOCK);
stoneLock();
state = e_ClearRows;
} else {
proceedDelay = delayFactor(level);
state = e_DownDelay;
}
break;
case e_ClearRows:
// clear filled lines
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);
}
state = e_NewStone;
break;
// --- phase: game over ---------------------------------------------------
case e_GameOver:
soundCtrl(SOUND_GAMEOVER);
rowIndex = CANVAS_HEIGHT;
phase = e_Phase_GameOver;
state = e_GameOverFill;
break;
case e_GameOverFill:
rowIndex--;
canvasFillRow(rowIndex, _red);
canvasFillRow(rowIndex, newHighScoreAchieved ? _green : _red);
if (rowIndex == 0) {
state = e_GameOverWipe;
}
@ -112,18 +145,15 @@ void gameExec(void *handle) {
// --- engine end ---------------------------------------------------------
canvasShow();
if (phase == e_Phase_Game) {
for (uint8_t r = 0; r < CANVAS_HEIGHT; r++) {
if (canvasIsRowFilled(r)) {
score += level;
if (isGameActive()) {
displaySetValue(score);
canvasWipeRow(r);
canvasShow();
}
}
} else {
displaySetValue(eepromReadHighScore());
}
}
void gameInit() {
schAdd(gameExec, NULL, 0, GAME_CYCLE_TIME);
}

View File

@ -12,6 +12,7 @@
#include "myrand.h"
#include "spi.h"
#include "display.h"
#include "eeprom.h"
int main() {
@ -28,6 +29,7 @@ int main() {
schInit();
spiInit();
eepromInit();
displayInit();
myRandInit();
canvasInit();

View File

@ -12,6 +12,7 @@
tTask tasks[MAX_NUM_OF_TASKS];
uint32_t seconds;
void schInit() {
TACCR0 = 32;
@ -25,9 +26,18 @@ void schInit() {
tasks[i].exec = NULL;
tasks[i].handle = NULL;
}
seconds = 0;
}
void __attribute__ ((interrupt (TIMER0_A0_VECTOR))) schUpdate() {
static uint16_t milliSeconds = 0;
if (milliSeconds >= 1000) {
seconds += 1;
milliSeconds = 0;
}
milliSeconds += 1;
for (uint16_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec != NULL) {
if (tasks[i].delay == 0) {
@ -89,4 +99,13 @@ void schExec() {
}
}
uint32_t getSeconds() {
uint32_t s;
__disable_interrupt();
s = seconds;
__enable_interrupt();
return s;
}

View File

@ -31,6 +31,7 @@ void schDel(void (*exec)(void *), void *handle);
void schExec();
void schUpdate();
uint8_t schTaskCnt();
uint32_t getSeconds();
#endif /* PONTCOOPSCHEDULER_H_ */

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();

18
game-ctrl/sound.c Normal file
View File

@ -0,0 +1,18 @@
#include <stdint.h>
#include "sound.h"
#include "spi.h"
void soundInit() {
}
void soundCtrl(uint8_t cmd) {
spiSendBegin(e_SPI_SOUND);
spiSendOctet(cmd);
spiSendEnd(e_SPI_SOUND);
}

15
game-ctrl/sound.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _SOUND_H_
#define _SOUND_H_
#include <stdint.h>
#define MUTE_DELAY 30 // seconds
#include "../sound-driver/soundCodes.h"
void soundInit();
void soundCtrl(uint8_t cmd);
#endif // _SOUND_H_

View File

@ -3,7 +3,7 @@
void spiInit() {
// SPI in master mode, most significant bit first
UCB0CTL0 = UCMST | UCMSB;
UCB0CTL0 = UCCKPH | UCMST | UCMSB;
// SPI timing config
UCB0CTL1 = UCSSEL_3;
// Faster than 8 ends up in strange communication errors
@ -19,25 +19,25 @@ void spiInit() {
// BIT7: UCB0SIMO
P1SEL |= BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT5 | BIT6 | BIT7;
P1DIR |= BIT5 | BIT7;
// P1DIR |= BIT5 | BIT7;
// Device Select Lines: 0: Canvas, 1: Display, 2: Sound
P1DIR |= BIT0 | BIT1 | BIT2;
// Device Select Lines: 0: Canvas, 1: Display, 2: Sound, 4: EEPROM
P1DIR |= BIT0 | BIT1 | BIT2 | BIT4;
// Disable all of them
P1OUT |= BIT0 | BIT1 | BIT2;
P1OUT |= BIT0 | BIT1 | BIT2 | BIT4;
// enable SPI module
UCB0CTL1 &= ~UCSWRST;
}
void spiSendBegin(t_SpiDeviceSelector d) {
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2 })[d];
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2, BIT4 })[d];
P1OUT &= ~bit;
}
void spiSendEnd(t_SpiDeviceSelector d) {
while (UCB0STAT & UCBUSY);
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2 })[d];
uint16_t bit = ((uint16_t[]){ BIT0, BIT1, BIT2, BIT4 })[d];
P1OUT |= bit;
}
@ -48,3 +48,9 @@ void spiSendOctet(uint8_t v) {
UCB0TXBUF = v;
}
uint8_t spiReceiveOctet() {
while (!(UC0IFG & UCB0RXIFG));
uint8_t v = UCB0RXBUF;
return v;
}

View File

@ -4,12 +4,13 @@
#include <stdint.h>
typedef enum { e_SPI_CANVAS, e_SPI_DISPLAY, e_SPI_SOUND } t_SpiDeviceSelector;
typedef enum { e_SPI_CANVAS, e_SPI_DISPLAY, e_SPI_SOUND, e_SPI_EEPROM } t_SpiDeviceSelector;
void spiInit();
void spiSendBegin(t_SpiDeviceSelector d);
void spiSendEnd(t_SpiDeviceSelector d);
void spiSendOctet(uint8_t v);
uint8_t spiReceiveOctet();

68
readme.md Normal file
View File

@ -0,0 +1,68 @@
# Tetris - Hardware and Software
![](./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.
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.
![](./docs/game-ctrl.jpg)
## 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.
![](./docs/rgb-driver.jpg)
## 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.
![](./docs/display-driver.jpg)
## 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.
![](./docs/sound-driver-1.jpg)
![](./docs/sound-driver-2.png)
![](./docs/sound-driver-3.jpg)
![](./docs/sound-driver-4.jpg)

View File

@ -123,7 +123,7 @@ init:
;; spi configuration
;; USCI B to slave mode, enable STE and most significant bit first
mov.b #UCSYNC|UCMODE_2|UCMSB, &UCB0CTL0
mov.b #UCCKPH|UCSYNC|UCMODE_2|UCMSB, &UCB0CTL0
mov.b #0x00, &UCB0CTL1
;; make sure the isr will not immediately start

View File

@ -4,20 +4,24 @@ OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
ARTIFACT=firmware
MCU=msp430g2553
CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O1 -g0
# for debugging
#CFLAGS+= -g3 -ggdb -gdwarf-2
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__
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
$(ARTIFACT).elf: main.o scheduler.o spi.o sequencer.o melody.o ay_3_8913.o
$(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) $^
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
.c.o:
$(CC) $(CFLAGS) -c $<
.S.o:
$(CC) $(ASFLAGS) -c $<
.PHONY: all
all: $(ARTIFACT).elf

Binary file not shown.

View File

@ -6,7 +6,9 @@
#include "psg.h"
#include "scheduler.h"
#include "sequencer.h"
#include "melody.h"
#include "melody_tetris.h"
#include "melody_tusch1.h"
#include "mute.h"
int main() {
WDTCTL = WDTPW | WDTHOLD;
@ -22,14 +24,13 @@ int main() {
schInit();
// spiInit();
spiInit();
psgInit();
muteInit();
sequencerInit();
__enable_interrupt();
melodyInit();
while (1) {
schExec();

View File

@ -1,8 +0,0 @@
#ifndef _MELODY_H_
#define _MELODY_H_
void melodyInit();
#endif // _MELODY_H_

View File

@ -0,0 +1,32 @@
#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);
}

View File

@ -0,0 +1,8 @@
#ifndef _MELODY_PLING_H_
#define _MELODY_PLING_H_
void playPling();
#endif // _MELODY_PLING_H_

View File

@ -4,120 +4,6 @@
#include "sequencer.h"
#include "scheduler.h"
/*
* most simple Tetris from https://de.wikipedia.org/wiki/Korobeiniki
const t_tone notes[] = {
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_4, .legato = false, .staccato = false },
// Triller
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_32, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_32, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_32, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_32, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_4, .legato = true , .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_8, .legato = true , .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_As, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_B, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_As, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = true , .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_8, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_3, .note = e_Es, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
*/
const t_tone tonleiter[] = {
{ .octave = e_O_6, .note = e_C, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_D, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_E, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_F, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_G, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_A, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_6, .note = e_H, .length = e_L_1_16, .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 },
};
const t_tone tonleiter2[] = {
{ .octave = e_O_4, .note = e_C, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_D, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_E, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_F, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_G, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_H, .length = e_L_1_16, .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_EndMark, .legato = false, .staccato = false },
};
const t_tone tonleiter3[] = {
{ .octave = e_O_5, .note = e_C, .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_E, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_F, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_G, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_A, .length = e_L_1_16, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_H, .length = e_L_1_16, .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_EndMark, .legato = false, .staccato = false },
};
/*
* three voices theme from https://www.gamemusicthemes.com/sheetmusic/gameboy/tetris/themea/Tetris_-_Theme_A_by_Gori_Fater.pdf
@ -1036,141 +922,27 @@ const t_tone voice3[] = {
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
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_F, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_F, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_F, .length = e_L_1_2, .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 },
};
const t_tone tusch1voice2[] = {
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .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_HoldMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
const t_tone tusch1voice3[] = {
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .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_HoldMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
const t_tone tusch2voice1[] = {
{ .octave = e_O_4, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_4, .note = e_E, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_4, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .legato = false, .staccato = true },
{ .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 },
};
const t_tone tusch2voice2[] = {
{ .octave = e_O_3, .note = e_G, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_H, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_4, .note = e_D, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_4, .note = e_Fis, .length = e_L_1_2, .legato = false, .staccato = true },
{ .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_HoldMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
const t_tone tusch2voice3[] = {
{ .octave = e_O_2, .note = e_B, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_D, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_F, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_3, .note = e_B, .length = e_L_1_2, .legato = false, .staccato = true },
{ .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_HoldMark,.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 = {
.melodies = { { .chip = 0, .amplitude = 3, .tones = voice1 }, { .chip = 0, .amplitude = 3, .tones = voice2 }, { .chip = 0, .amplitude = 3, .tones = voice3 } },
.melodies = { { .amplitude = 8, .tones = voice1 }, { .amplitude = 8, .tones = voice2 }, { .amplitude = 8, .tones = voice3 } },
.numOfMelodies = 3,
.pace = 160
.pace = INITIAL_PACE,
.chip = 0
};
t_melodies tonleiterTheme = {
.melodies = { { .chip = 1, .amplitude = 3, .tones = tonleiter }, { .chip = 1, .amplitude = 3, .tones = tonleiter2 }, { .chip = 1, .amplitude = 3, .tones = tonleiter3 } },
.numOfMelodies = 3,
.pace = 160
};
t_melodies tusch1 = {
.melodies = { { .chip = 1, .amplitude = 8, .tones = tusch1voice1 }, { .chip = 1, .amplitude = 8, .tones = tusch1voice2 }, { .chip = 1, .amplitude = 8, .tones = tusch1voice3 } },
.numOfMelodies = 3,
.pace = 200
};
t_melodies tusch2 = {
.melodies = { { .chip = 1, .amplitude = 1, .tones = tusch2voice1 }, { .chip = 1, .amplitude = 1, .tones = tusch2voice2 }, { .chip = 1, .amplitude = 1, .tones = tusch2voice3 } },
.numOfMelodies = 3,
.pace = 160
};
void playTusch1(void *handle) {
sequencerPlayMelodies(&tusch1);
schAdd(playTusch1, NULL, 30000, 0);
}
void melodyInit() {
void playMelodyTetris() {
tetrisTheme.pace = INITIAL_PACE; // reset to start value each time
sequencerPlayMelodies(&tetrisTheme);
schAdd(playTusch1, NULL, 2000, 0);
}
void playMelodyTetrisFaster() {
tetrisTheme.pace += 15;
sequencerChangePace(&tetrisTheme);
}
void stopMelodyTetris() {
sequencerStopMelodies(&tetrisTheme);
}

View File

@ -0,0 +1,9 @@
#ifndef _MELODY_TETRIS_H_
#define _MELODY_TETRIS_H_
void playMelodyTetris();
void stopMelodyTetris();
void playMelodyTetrisFaster();
#endif // _MELODY_TETRIS_H_

View File

@ -0,0 +1,87 @@
#include <stdbool.h>
#include <stddef.h>
#include "psg.h"
#include "sequencer.h"
#include "scheduler.h"
#include "melody_tetris.h"
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_F, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_F, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_5, .note = e_C, .length = e_L_1_4, .legato = false, .staccato = true },
{ .octave = e_O_5, .note = e_F, .length = e_L_1_2, .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 },
};
const t_tone tusch1voice2[] = {
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_5, .note = e_C, .length = e_L_1_2, .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_HoldMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
const t_tone tusch1voice3[] = {
{ .octave = e_O_Null, .note = e_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Pause, .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_Pause, .length = e_L_1_4, .legato = false, .staccato = false },
{ .octave = e_O_4, .note = e_A, .length = e_L_1_2, .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_HoldMark,.legato = false, .staccato = false },
{ .octave = e_O_Null, .note = e_Null, .length = e_L_EndMark, .legato = false, .staccato = false },
};
t_melodies tusch1 = {
.melodies = { { .amplitude = 12, .tones = tusch1voice1 }, { .amplitude = 12, .tones = tusch1voice2 }, { .amplitude = 12, .tones = tusch1voice3 } },
.numOfMelodies = 3,
.pace = 200,
.chip = 1
};
void playTusch1() {
sequencerPlayMelodies(&tusch1);
// playMelodyTetrisFaster();
}

View File

@ -0,0 +1,8 @@
#ifndef _MELODY_TUSCH1_H_
#define _MELODY_TUSCH1_H_
void playTusch1();
#endif // _MELODY_TUSCH1_H_

22
sound-driver/mute.c Normal file
View File

@ -0,0 +1,22 @@
#include <msp430g2553.h>
#include "mute.h"
void muteInit() {
// BIT6: MuteCtrl
P1DIR |= BIT6;
// initially, mute
P1OUT &= ~BIT6;
}
void mute() {
P1OUT &= ~BIT6;
}
void unMute() {
P1OUT |= BIT6;
}

8
sound-driver/mute.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _MUTE_H_
#define _MUTE_H_
void muteInit();
void mute();
void unMute();
#endif // _MUTE_H_

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];
}

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

@ -6,7 +6,7 @@
#define MAX_NUM_OF_TASKS 8
#define MAX_NUM_OF_TASKS 4
typedef struct {

View File

@ -6,29 +6,37 @@
#include "psg.h"
uint8_t slots;
void sequencerInit() {
slots = 0;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
#pragma GCC diagnostic ignored "-Wreturn-type"
static uint16_t calcLength(t_melodies *m, t_noteLength l) {
switch (l) {
case e_L_1:
return m->quarterLength << 2;
case e_L_1_2:
return m->quarterLength << 1;
case e_L_1_4:
return m->quarterLength;
case e_L_1_8:
return m->quarterLength >> 1;
case e_L_1_16:
return m->quarterLength >> 2;
case e_L_1_32:
return m->quarterLength >> 4;
}
}
#pragma GCC diagnostic pop
void sequencerExec(void *handle) {
static uint16_t lengths[e_L_LengthEnd];
t_melodies *melodies = (t_melodies*) handle;
if (melodies->firstRun) {
melodies->firstRun = false;
lengths[e_L_1_4] = 60000 / melodies->pace; // duration of a 1/4 tone in ms
lengths[e_L_1_2] = lengths[e_L_1_4] << 1;
lengths[e_L_1] = lengths[e_L_1_4] << 2;
lengths[e_L_1_8] = lengths[e_L_1_4] >> 1;
lengths[e_L_1_16] = lengths[e_L_1_4] >> 2;
lengths[e_L_1_32] = lengths[e_L_1_4] >> 4;
for (uint8_t i = 0; i < e_L_LengthEnd; i++) {
lengths[i] /= SEQUENCER_PERIOD;
}
}
for (uint8_t channel = 0; channel < melodies->numOfMelodies; channel++) {
t_melody *melody = &(melodies->melodies[channel]);
@ -51,8 +59,10 @@ void sequencerExec(void *handle) {
if (melody->tones[melody->idx].length == e_L_EndMark) {
melody->idx = 0;
}
psgPlayTone(melody->chip, channel, melody->amplitude, melody->tones[melody->idx].octave, melody->tones[melody->idx].note);
melody->lengthCnt = (melody->tones[melody->idx].staccato) ? (lengths[melody->tones[melody->idx].length] / 2) : lengths[melody->tones[melody->idx].length];
psgPlayTone(melodies->chip, channel, melody->amplitude, melody->tones[melody->idx].octave, melody->tones[melody->idx].note);
melody->lengthCnt = (melody->tones[melody->idx].staccato) ?
(calcLength(melodies, melody->tones[melody->idx].length) / 2) :
calcLength(melodies, melody->tones[melody->idx].length);
melody->state = e_HoldTone;
}
break;
@ -68,8 +78,8 @@ void sequencerExec(void *handle) {
}
break;
case e_StaccatoBreak:
psgPlayTone(melody->chip, channel, 0, e_O_Null, e_Pause);
melody->lengthCnt = lengths[melody->tones[melody->idx].length] / 2;
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
melody->lengthCnt = calcLength(melodies, melody->tones[melody->idx].length) / 2;
melody->state = e_HoldStaccatoBreak;
break;
case e_HoldStaccatoBreak:
@ -80,31 +90,51 @@ void sequencerExec(void *handle) {
break;
case e_SeparateTone:
if (! (melody->tones[melody->idx].legato)) {
psgPlayTone(melody->chip, channel, 0, e_O_Null, e_Pause);
psgPlayTone(melodies->chip, channel, 0, e_O_Null, e_Pause);
}
melody->idx += 1;
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;
}
}
}
uint16_t sequencerPlayMelodies(t_melodies *melodies) {
void sequencerPlayMelodies(t_melodies *melodies) {
melodies->slotMask = (1 << melodies->chip);
if ((slots & melodies->slotMask) != 0) {
return;
}
slots |= melodies->slotMask;
for (uint8_t i = 0; i < NUM_OF_CHANNELS; i++) {
melodies->melodies[i].idx = 0;
melodies->melodies[i].lengthCnt = 0;
melodies->melodies[i].state = e_Init;
}
melodies->sync = 0;
melodies->firstRun = true;
melodies->quarterLength = 60000 / melodies->pace / SEQUENCER_PERIOD; // duration of a 1/4 tone in ms
melodies->taskId = schAdd(sequencerExec, (void*) melodies, 0, SEQUENCER_PERIOD);
return melodies->taskId;
}
void sequencerStopMelodies(t_melodies *melodies) {
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) {
melodies->quarterLength = 60000 / melodies->pace / SEQUENCER_PERIOD; // duration of a 1/4 tone in ms
}

View File

@ -42,7 +42,6 @@ typedef enum {
typedef struct {
uint16_t idx;
uint8_t chip;
uint16_t lengthCnt;
t_sequencerState state;
uint8_t amplitude;
@ -52,16 +51,19 @@ typedef struct {
#define SEQUENCER_PERIOD 4 // ms
#define NUM_OF_CHANNELS 3
typedef struct {
uint8_t slotMask;
uint8_t chip;
uint8_t taskId;
bool firstRun;
uint16_t quarterLength;
uint8_t numOfMelodies;
uint8_t pace; // quarter notes per minute
uint16_t pace; // quarter notes per minute
uint8_t sync;
t_melody melodies[NUM_OF_CHANNELS];
} t_melodies;
void sequencerInit();
uint16_t sequencerPlayMelodies(t_melodies *melodies);
void sequencerPlayMelodies(t_melodies *melodies);
void sequencerStopMelodies(t_melodies *melodies);
void sequencerChangePace(t_melodies *melodies);
#endif // _SEQUENCER_H_

View File

@ -1,171 +0,0 @@
#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);
}

14
sound-driver/soundCodes.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef _SOUND_CODES_H_
#define _SOUND_CODES_H_
#define SOUND_IDLE 0x00
#define SOUND_MUTE 0x01
#define SOUND_UNMUTE 0x02
#define SOUND_START 0x04
#define SOUND_GAMEOVER 0x08
#define SOUND_FANFARE 0x10
#define SOUND_LOCK 0x20
#define SOUND_MOTION 0x40
#define SOUND_PLING 0x80
#endif // _SOUND_CODES_H_

64
sound-driver/spi.S Normal file
View File

@ -0,0 +1,64 @@
#include <msp430g2553.h>
#include "soundCodes.h"
.section ".text","ax",@progbits
receive_isr:
bit #UCB0RXIFG, &UC0IFG
jz receive_isr_no_data
bis UCB0RXBUF, &cmd
receive_isr_no_data:
reti
.global spiCmdHandler
spiCmdHandler:
spiCmdHandler_1:
bit #SOUND_MUTE, &cmd
jz spiCmdHandler_2
call #mute
bic #SOUND_MUTE, &cmd
spiCmdHandler_2:
bit #SOUND_UNMUTE, &cmd
jz spiCmdHandler_3
call #unMute
bic #SOUND_UNMUTE, &cmd
spiCmdHandler_3:
bit #SOUND_START, &cmd
jz spiCmdHandler_4
call #playMelodyTetris
bic #SOUND_START, &cmd
spiCmdHandler_4:
bit #SOUND_GAMEOVER, &cmd
jz spiCmdHandler_5
call #stopMelodyTetris
bic #SOUND_GAMEOVER, &cmd
spiCmdHandler_5:
bit #SOUND_FANFARE, &cmd
jz spiCmdHandler_6
call #playMelodyTetrisFaster
call #playTusch1
bic #SOUND_FANFARE, &cmd
spiCmdHandler_6:
bit #SOUND_LOCK, &cmd
jz spiCmdHandler_7
;; insert a call here
bic #SOUND_LOCK, &cmd
spiCmdHandler_7:
bit #SOUND_MOTION, &cmd
jz spiCmdHandler_8
;; insert a call here
bic #SOUND_MOTION, &cmd
spiCmdHandler_8:
bit #SOUND_PLING, &cmd
jz spiCmdHandler_end
call #playPling
bic #SOUND_PLING, &cmd
spiCmdHandler_end:
ret
.section "__interrupt_vector_8","ax",@progbits
.word receive_isr
.end

View File

@ -3,7 +3,7 @@
void spiInit();
void spiCmdHandler();
#endif // _SPI_H_

View File

@ -1,13 +1,12 @@
#include <stddef.h>
#include <msp430g2553.h>
#include <stdint.h>
#include "scheduler.h"
#include "spi.h"
#include "soundCodes.h"
void __attribute__ ((interrupt (USCIAB0RX_VECTOR))) receive() {
if (UC0IFG & UCB0RXIFG) {
// receive an octet
}
}
uint8_t cmd;
void spiInit() {
// SPI slave
@ -15,15 +14,18 @@ void spiInit() {
// BIT5: UCB0CLK
// BIT6: UCB0SOMI
// BIT7: UCB0SIMO
P1SEL |= BIT4 | BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT4 | BIT5 | BIT6 | BIT7;
P1SEL |= BIT4 | BIT5 | BIT7;
P1SEL2 |= BIT4 | BIT5 | BIT7;
// most significant bit first, enable STE
UCB0CTL0 = UCSYNC | UCMSB | UCMODE_2;
UCB0CTL0 = UCCKPH | UCSYNC | UCMSB | UCMODE_2;
UCB0CTL1 = 0x00;
// enable RX interrupt
UC0IE |= UCB0RXIE;
cmd = SOUND_IDLE;
schAdd(spiCmdHandler, NULL, 0, 100);
}