Compare commits

...

242 Commits

Author SHA1 Message Date
d68066014a
add technical documentation 2025-01-26 13:39:04 +01:00
6e99595c00
link to rgb document 2024-05-27 12:01:29 +02:00
2c87b2814d
some docs 2024-05-25 17:22:45 +02:00
95ad5a9e9f naming 2024-05-22 11:55:47 +02:00
9a0e3be4fe game counter reset 2024-05-21 14:21:35 +02:00
2827046b8b more configuration 2024-05-21 13:43:40 +02:00
faf75e158a sound volume configurable 2024-05-21 12:31:55 +02:00
f9b63c06fe another fix 2024-05-19 22:08:11 +02:00
1cbba5c8b3 fix 2024-05-19 22:06:21 +02:00
ebb0f5846d better way to clear config in case of invalid magic 2024-05-19 22:00:59 +02:00
1beb7ef04a code beautified 2024-05-19 21:57:46 +02:00
70467cf2a7 also random very first stone 2024-05-19 21:53:57 +02:00
d9769c6b28 other button to start config mode 2024-05-19 21:47:31 +02:00
d9fd18d799 color cleanup and brightness 2024-05-19 21:42:39 +02:00
7894359f30 flashcolor and highscore config done 2024-05-19 20:46:00 +02:00
4cb0a10617 colors 2024-05-18 23:11:00 +02:00
a92a3beb96 more colors 2024-05-18 20:38:54 +02:00
ccd395d6ab config 2024-05-18 20:09:21 +02:00
eb75e31577 different flash 2024-05-18 12:08:18 +02:00
ba4248ff24 flash 2024-05-17 16:08:10 +02:00
2cc5a6a4f3 changes 2024-05-05 21:48:20 +02:00
5c86d55458 dark mode 2024-05-05 17:24:29 +02:00
b9e5813223 flash 2024-05-04 12:58:02 +02:00
1b4a93d9e1 types bigger 2024-05-04 11:35:29 +02:00
30d50dcc5e seems to work but some unpleasant delays 2024-05-04 11:31:27 +02:00
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
5b9194caae fix 2024-04-10 13:43:28 +02:00
2f8e2937c1 tusch and sequencer stop fix 2024-04-10 12:58:30 +02:00
3769b3eb05 second chip works, stop mark introduces 2024-04-09 18:37:53 +02:00
8f777c9ac4 76498 not working as expected 2024-04-09 18:14:31 +02:00
aeb7aeb7f2 start with other sound chip 2024-04-09 10:32:19 +02:00
20c01f2efa
frequency table for 76489AN 2024-04-07 13:37:42 +02:00
40cb04bde5 fix wording 2024-03-29 18:08:37 +01:00
a1457e6a69 lengthCnt must be 16 Bit, one full tone for 160/4 has 375 2024-03-29 15:52:12 +01:00
0303bbdc3c corrects of tones 2024-03-29 15:11:38 +01:00
162bdaefee beat marks 2024-03-29 15:10:04 +01:00
b9fd0099a8 variable names 2024-03-29 14:22:02 +01:00
d09f8d240f
Merge branch 'main' of gitea.hottis.de:wn/tetris 2024-03-29 14:13:10 +01:00
ed6da383de
docs 2024-03-29 14:13:05 +01:00
9328e22425 pace calculation works 2024-03-29 14:11:36 +01:00
48b9fc7578
some docs 2024-03-29 13:33:39 +01:00
d4d494ae7b sync marks work 2024-03-29 13:29:53 +01:00
1ebf85cb9d sync marks 2024-03-29 13:04:05 +01:00
5a8323bddb up to three melodies will be played by one sequencer instance 2024-03-29 12:41:44 +01:00
97dc6ede70 dreistimmig 2024-03-29 11:15:38 +01:00
a4563cd393 third melody 2024-03-29 00:39:21 +01:00
b435396d67 other melody 2024-03-29 00:28:15 +01:00
38ea1a7fdb register name 2024-03-28 17:05:35 +01:00
6632630303 register names 2024-03-28 17:03:56 +01:00
fdb524c8d4 separate SPI 2024-03-28 16:53:19 +01:00
2c3bccd147 fix wrong initial state 2024-03-28 16:53:08 +01:00
ee98ba12a3 spi separated 2024-03-28 16:48:29 +01:00
33242a09c1
docs 2024-03-28 16:25:16 +01:00
2097280f2b
add docs 2024-03-28 16:07:50 +01:00
70b3b25a9d Merge branch 'main' of gitea.hottis.de:wn/tetris 2024-03-26 23:22:06 +01:00
5e1e9dfa92 sequencer init state 2024-03-26 23:21:24 +01:00
f397d0737b
docs 2024-03-26 23:15:56 +01:00
1c2414463b sounds good 2024-03-26 23:13:59 +01:00
2e629f12aa sequencer states 2024-03-26 22:12:46 +01:00
9989a52c38 legato 2024-03-26 21:05:48 +01:00
73d2bbc730 tetris melody 2024-03-26 16:56:46 +01:00
5e2f120432 Tonleiter is running 2024-03-26 16:47:42 +01:00
152f171c66 sequencer 2024-03-26 15:58:45 +01:00
9ddb747f16 refactored 2024-03-26 15:05:39 +01:00
bd11d12620 psg and notes 2024-03-26 14:43:02 +01:00
e5c6669284 notes refactored 2024-03-26 13:30:42 +01:00
202e91bfb6 playsommething 2024-03-26 12:23:44 +01:00
48c83f0b2d scheduler added, tuned for 1ms 2024-03-26 10:44:23 +01:00
d8e34ec209 note definitions 2024-03-25 23:13:33 +01:00
5a491140c7 register numbering is octal! 2024-03-25 22:11:15 +01:00
aaf709b0c9 begin sound driver 2024-03-25 13:28:33 +01:00
32bb08696f score reset 2024-03-22 23:03:31 +01:00
85d243551e score counter 2024-03-22 22:49:43 +01:00
a32ef8fa5b prepare display 2024-03-22 18:38:30 +01:00
10a09e3ad3 game over 2024-03-22 12:00:23 +01:00
611c56b329 game over 2024-03-22 11:59:28 +01:00
5b1dfde819
updated schematics 2024-03-21 21:14:05 +01:00
1cc4785ddb refactor SPI handling and introduce SPI CS, forgotten files 2024-03-21 20:55:41 +01:00
9acd56b79b refactor SPI handling and introduce SPI CS 2024-03-21 20:55:26 +01:00
474fce2278 mini canvas 2024-03-21 19:30:40 +01:00
8597a9f736
schematics 2024-03-21 16:02:24 +01:00
da21fac130
motion definition 2024-03-21 15:40:16 +01:00
5f9fab5f2c more to the center 2024-03-21 15:28:36 +01:00
f524a08687 introduce next shape 2024-03-21 12:24:12 +01:00
5c03232855 wipe full lines 2024-03-21 11:58:42 +01:00
f69c5bc59e random is working 2024-03-21 11:34:20 +01:00
50486f6ec0 adc for random number generation not yet working 2024-03-20 21:56:19 +01:00
8c995f66ff J done 2024-03-20 21:00:22 +01:00
ebe958ac49 start of J 2024-03-20 17:35:31 +01:00
fadb80c362 L done 2024-03-20 17:05:13 +01:00
d1e13dc16a S done 2024-03-20 15:19:26 +01:00
90a9294da6 Z done 2024-03-20 14:48:25 +01:00
cfc06ddb2d placeholders for all stones 2024-03-20 13:54:57 +01:00
5e5c616bde T done 2024-03-20 13:48:06 +01:00
2ca1d2e4ad T, move right fixed 2024-03-19 22:18:42 +01:00
a28cdcf6af T, move left fixed 2024-03-19 21:19:34 +01:00
7f6d027d1a fix T, rotate left 2024-03-19 18:14:03 +01:00
97b1d19da8 T 2024-03-19 17:42:25 +01:00
8b2f18415d buttons 2024-03-19 16:46:52 +01:00
5615c80e8f all leds 2024-03-19 14:57:41 +01:00
a68f74559f refactoring 2024-03-18 20:50:10 +01:00
326d0f66b0 remove unused code 2024-03-18 13:01:23 +01:00
feed11f977 change 2024-03-18 12:54:23 +01:00
fd3df973ec code beautifying 2024-03-18 12:51:57 +01:00
58ae9a641a
move displayTests to disabled folder 2024-03-18 12:18:18 +01:00
e0aae175b0
debug option in Makefile separated 2024-03-15 23:27:10 +01:00
7e4285f280 move down, left, right for T done 2024-03-15 14:23:19 +01:00
007548efbb O done 2024-03-15 12:54:32 +01:00
14f6018f5a motion table and next orientation table down 2024-03-15 10:22:11 +01:00
f4b614bf0f first motion table 2024-03-14 17:25:45 +01:00
a7ea698f9b move and rotate of i 2024-03-14 14:46:31 +01:00
1959a3f578 game-ctrl merged 2024-03-14 11:10:18 +01:00
349c93b68f Merge remote-tracking branch 'repo-game-ctrl/shapes' 2024-03-14 11:09:26 +01:00
9c1a8a6e21 moved 2024-03-14 11:07:10 +01:00
9003528897 colors.h added 2024-03-14 10:57:09 +01:00
2b34a3e51d start game 2024-03-13 17:35:46 +01:00
e5381a8c9d Merge branch 'main' into shapes 2024-03-13 14:55:04 +01:00
8bd6197c02 spi timing 2024-03-13 14:54:53 +01:00
f5b0e67056 shapes 2024-03-13 14:52:46 +01:00
19be1a6e48 more tests 2024-03-12 23:00:17 +01:00
05de9326d7 some more display tests 2024-03-12 18:18:26 +01:00
633fc814b2 display tests 2024-03-12 17:53:42 +01:00
88e0cff853 11 rows 2024-03-12 10:19:16 +01:00
6020118fc2 110 leds 2024-03-12 10:19:02 +01:00
d3846c3e08 up and down 2024-03-08 15:54:34 +01:00
30ab5287da rename isr 2024-03-08 15:09:05 +01:00
fb16cfaa6f clean up cleaned up 2024-03-08 15:06:35 +01:00
cf62e0c0a0
Merge branch 'main' of gitea.hottis.de:wn/game-ctrl-01 2024-03-08 15:01:33 +01:00
044779681b
more images 2024-03-08 15:01:01 +01:00
167617ad2c faster spi 2024-03-08 14:13:21 +01:00
26db6bf03d a funny light show 2024-03-08 14:07:09 +01:00
a4adf6ac27 synchronize scheduler, now it works 2024-03-08 13:12:55 +01:00
da3c2a5ffb
image 2024-03-08 08:23:51 +01:00
a5438cef14 accept multiple address/data octets 2024-03-08 08:16:56 +01:00
d295a36598 receivedata added 2024-03-07 21:50:09 +01:00
3eaf90dbd6 works again 2024-03-07 15:43:27 +01:00
25ad1ad7df try math 2024-03-06 13:17:08 +01:00
6536dc5534 try math 2024-03-06 13:16:18 +01:00
730ac0df30 try math 2024-03-06 13:15:56 +01:00
152c9f60d4 try math 2024-03-06 13:14:43 +01:00
2f07e37b5d try math 2024-03-06 13:12:59 +01:00
d61986630c try math 2024-03-06 13:12:13 +01:00
2e19371fac try math 2024-03-06 13:08:18 +01:00
53e33bf4ff try math 2024-03-06 13:08:04 +01:00
01c645d8df try math 2024-03-06 13:07:38 +01:00
75acdd3171 try math 2024-03-06 13:04:32 +01:00
e6db62c9a5 try math 2024-03-06 13:04:14 +01:00
2ccef81175 try math 2024-03-06 13:03:49 +01:00
ad37bb2280 fix for new image 2024-03-06 12:57:37 +01:00
fd6eb582eb
new image 2024-03-06 12:55:36 +01:00
8292d59cf0 doc on reset circuit 2024-03-06 10:44:31 +01:00
1e77cdaddf Merge branch 'main' of gitea.hottis.de:wn/rgb-driver-01 2024-03-06 10:39:23 +01:00
d0219a3c77 violet 2024-03-06 10:39:10 +01:00
2bef7bf2c2
Merge branch 'main' of gitea.hottis.de:wn/rgb-driver-01 2024-03-06 10:37:12 +01:00
9f50f6010b
reset circuit 2024-03-06 10:36:43 +01:00
c35ec0991c dimm factor introduced 2024-03-05 17:01:53 +01:00
584e834b0d comment 2024-03-05 16:21:56 +01:00
fbe779d3b8 code beautified 2024-03-05 16:18:19 +01:00
49d78662e2 rolling led 2024-03-05 16:14:13 +01:00
c502303a36 use of subroutines work 2024-03-05 15:27:34 +01:00
2d532a6403 this is working 2024-03-05 15:06:42 +01:00
86fb74bd49 adjust color offsets 2024-02-29 18:13:58 +01:00
42c06f0da4 drop unnecessary .extern statements 2024-02-28 12:10:22 +01:00
b4ebe4978b readme 2024-02-27 20:53:13 +01:00
a5536e63b7
six leds 2024-02-27 20:49:06 +01:00
4f70ea392b readme 2024-02-27 20:39:27 +01:00
9c493d515e
image 2024-02-27 20:35:48 +01:00
201a371a57 names refactor 2024-02-27 19:11:36 +01:00
40c49b6809 names refactor 2024-02-27 19:09:50 +01:00
c9b8fbba52 readme 2024-02-27 19:03:57 +01:00
d8755709cd readme 2024-02-27 19:02:02 +01:00
785810924e
timing images 2024-02-27 19:00:19 +01:00
a0794dd9c1 refactor names, 6 2024-02-27 18:59:57 +01:00
4104cbf3d6 refactor names, 5 2024-02-27 18:30:08 +01:00
c829a76273 refactor names, 4 2024-02-27 17:48:22 +01:00
e1b7c328f8 refactor names, 3 2024-02-27 17:45:40 +01:00
2435cee771 refactor names, 2 2024-02-27 17:34:11 +01:00
75598f973a refactor names, 1 2024-02-27 17:32:16 +01:00
155b7153e9 refactor 2024-02-27 16:58:43 +01:00
5c2a6c42d2 hold data in ram 2024-02-27 15:33:45 +01:00
b390b8ec8f hold data in ram 2024-02-27 15:32:35 +01:00
cbf8a3cf86
color wheel from https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide/3-custom-color-mixing 2024-02-27 15:11:27 +01:00
f0e8bf7616 color definitions 2024-02-27 15:10:38 +01:00
8f143104f6 works with leds attached 2024-02-27 14:23:32 +01:00
80801e185b readme 2024-02-27 12:09:39 +01:00
68490a4f83
schematics 2024-02-27 12:08:47 +01:00
5d7c75b358 fix 2024-02-27 11:58:02 +01:00
4e6fd76b08
Merge branch 'main' of gitea.hottis.de:wn/msp430-experiment-02 2024-02-27 11:56:31 +01:00
cea174197f
add images 2024-02-27 11:56:06 +01:00
df9faabc7f readme 2024-02-27 11:54:19 +01:00
5128d36a75 readme fixed 2024-02-27 11:42:54 +01:00
b3ee547b64 remove dead code 2024-02-27 11:38:44 +01:00
92fb90983f cycles work now 2024-02-27 11:25:57 +01:00
fd801003a8 seems to work so far 2024-02-23 21:20:16 +01:00
491a544919 changes 2024-02-23 10:39:57 +01:00
b411a05d0e change Makefile 2024-02-20 13:05:32 +01:00
29c2c88c71 initial 2024-02-20 12:50:23 +01:00
121 changed files with 4856 additions and 297 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
*.o
firmware.*
firmware.txt
firmware.elf
firmware.map

View File

@ -1,88 +0,0 @@
/*
* PontCoopScheduler.c
*
* Created on: 29.08.2016
* Author: wn
*/
#include <stdlib.h>
#include "PontCoopScheduler.h"
tTask tasks[MAX_NUM_OF_TASKS];
void schInit() {
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
tasks[i].delay = 0;
tasks[i].period = 0;
tasks[i].run = 0;
tasks[i].exec = NULL;
tasks[i].handle = NULL;
}
}
void schAdd(void (*exec)(void *), void *handle, uint32_t delay, uint32_t period) {
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec == NULL) {
tasks[i].delay = delay;
tasks[i].period = period;
if (delay == 0 && period == 0) {
tasks[i].run = 1;
} else {
tasks[i].run = 0;
}
tasks[i].exec = exec;
tasks[i].handle = handle;
break;
}
}
}
void schDel(void (*exec)(void *), void *handle) {
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if ((tasks[i].exec == exec) && (tasks[i].handle == handle)) {
tasks[i].exec = NULL;
break;
}
}
}
void schExec() {
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec != NULL && tasks[i].run > 0) {
tasks[i].run--;
tasks[i].exec(tasks[i].handle);
if (tasks[i].period == 0) {
tasks[i].exec = NULL;
}
}
}
}
void schUpdate() {
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec != NULL) {
if (tasks[i].delay == 0) {
tasks[i].delay = tasks[i].period;
tasks[i].run++;
} else {
tasks[i].delay--;
}
}
}
}
uint8_t schTaskCnt() {
uint8_t cnt = 0;
for (uint8_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec != NULL){
cnt++;
}
}
return cnt;
}

View File

@ -1,31 +0,0 @@
#include <string.h>
#include <stdint.h>
#include "canvas.h"
#include "displayDriver.h"
static canvas_t canvas;
static uint8_t canvasStorage[CANVAS_WIDTH * CANVAS_HEIGHT];
void canvasInit() {
canvas.height = CANVAS_HEIGHT;
canvas.width = CANVAS_WIDTH;
canvas.size = CANVAS_HEIGHT * CANVAS_WIDTH;
canvas.canvas = canvasStorage;
// Bit7 signals need to be transfered for octet
memset(canvasStorage, 0x80, canvas.size);
displayDriverTransferCanvas();
}
canvas_t *canvasGet() {
return &canvas;
}
void canvasShow() {
displayDriverTransferCanvas();
}

View File

@ -1,23 +0,0 @@
#ifndef _CANVAS_H_
#define _CANVAS_H_
#include <stdint.h>
#define CANVAS_WIDTH 10
#define CANVAS_HEIGHT 6
typedef struct {
uint8_t width;
uint8_t height;
uint8_t size;
uint8_t *canvas;
} canvas_t;
void canvasInit();
canvas_t *canvasGet();
void canvasShow();
#endif // _CANVAS_H_

View File

@ -4,14 +4,14 @@ OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
ARTIFACT=firmware
MCU=msp430g2553
CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O3 -g0
CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -O1 -g0
# for debugging
#CFLAGS=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -g3 -ggdb -gdwarf-2
#CFLAGS+= -g3 -ggdb -gdwarf-2
LDFLAGS=-mmcu=$(MCU) -L $(TOOLCHAIN_PREFIX)/include
$(ARTIFACT).elf: main.o led.o time.o PontCoopScheduler.o displayDriver.o canvas.o displayTest.o
$(ARTIFACT).elf: main.o
$(CC) -o $@ $(LDFLAGS) $^
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
@ -24,7 +24,7 @@ all: $(ARTIFACT).elf
.PHONY: clean
clean:
-rm -f *.o *.elf
-rm -f *.o $(ARTIFACT).elf $(ARTIFACT).txt
.PHONY: upload
upload: $(ARTIFACT).elf

Binary file not shown.

Binary file not shown.

131
display-driver/main.c Normal file
View File

@ -0,0 +1,131 @@
#include <msp430g2553.h>
#include <stdint.h>
#include <stdlib.h>
const uint8_t patterns[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00100111, // 7
0b01111111, // 8
0b01101111, // 9
};
volatile union {
uint16_t value;
uint8_t receiveBuffer[2];
} value;
static void delay() {
asm volatile (
"push r12\n"
"mov.w #1000, r12\n"
"loop:\n"
"dec.w r12\n"
"jnz loop\n"
"pop r12\n"
);
}
void __attribute__ ((interrupt (USCIAB0RX_VECTOR))) receive() {
static uint8_t octetNumber = 0;
if (UC0IFG & UCB0RXIFG) {
value.receiveBuffer[octetNumber] = UCB0RXBUF;
octetNumber++;
if (octetNumber > 1) {
octetNumber = 0;
}
}
}
// mask 0x0f: digit value
// mask 0x10: set dp
// mask 0x20: digit off
static void setDigit(uint16_t bit, uint8_t value) {
P1OUT |= (BIT0 | BIT1 | BIT2 | BIT3);
P2OUT = ((value & 0x20) ? 0 : patterns[value & 0x0f]) | ((value & 0x10) ? 0x80 : 0x00);
P1OUT &= ~bit;
}
int main() {
WDTCTL = WDTPW | WDTHOLD;
__disable_interrupt();
// highest possible system clock
DCOCTL = DCO0 | DCO1 | DCO2;
BCSCTL1 = XT2OFF | RSEL0 | RSEL1 | RSEL2 | RSEL3;
BCSCTL2 = 0;
BCSCTL3 = 0;
// SPI slave
// BIT4: UCB0STE
// BIT5: UCB0CLK
// BIT6: UCB0SOMI
// BIT7: UCB0SIMO
P1SEL |= BIT4 | BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT4 | BIT5 | BIT6 | BIT7;
// most significant bit first, enable STE
UCB0CTL0 = UCCKPH | UCSYNC | UCMSB | UCMODE_2;
UCB0CTL1 = 0x00;
// enable RX interrupt
UC0IE |= UCB0RXIE;
// digit driver
P1DIR |= BIT0 | BIT1 | BIT2 | BIT3;
P1SEL &= ~(BIT0 | BIT1 | BIT2 | BIT3);
P1SEL2 &= ~(BIT0 | BIT1 | BIT2 | BIT3);
// segment driver
P2DIR = 0xff;
P2SEL = 0x00;
P2SEL2 = 0x00;
// all digits off
P1OUT |= (BIT0 | BIT1 | BIT2 | BIT3);
// all segments off
P2OUT &= ~(BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6 | BIT7);
// reset value to 0
value.value = 0;
__enable_interrupt();
while (1) {
__disable_interrupt();
uint16_t shadowValue = value.value;
__enable_interrupt();
uint8_t digit0 = shadowValue % 10;
uint8_t digit1 = (shadowValue / 10) % 10;
uint8_t digit2 = (shadowValue / 100) % 10;
uint8_t digit3 = (shadowValue / 1000) % 10;
digit1 += (!((digit3 & 0x0f) | (digit2 & 0x0f) | (digit1 & 0x0f))) ? 0x20 : 0x00;
digit2 += (!((digit3 & 0x0f) | (digit2 & 0x0f))) ? 0x20 : 0x00;
digit3 += (!(digit3 & 0x0f)) ? 0x20 : 0x00;
setDigit(BIT0, digit0);
delay();
setDigit(BIT1, digit1);
delay();
setDigit(BIT2, digit2);
delay();
setDigit(BIT3, digit3);
delay();
}
}

View File

@ -1,54 +0,0 @@
#include "displayDriver.h"
#include "led.h"
#include "canvas.h"
#include <msp430g2553.h>
#include <stdlib.h>
#include <stdint.h>
static void spiSendOctet(uint8_t v) {
// wait for TX buffer empty
while (!(UC0IFG & UCB0TXIFG));
// load octet into TX buffer
UCB0TXBUF = v;
}
void displayDriverTransferCanvas() {
// wait for signal waiting for data
while ((P1IN & BIT3) == 0);
canvas_t *canvas = canvasGet();
for (uint8_t i = 0; i < canvas->size; i++) {
if ((*((canvas->canvas)+i) & 0x80) != 0) {
*((canvas->canvas)+i) &= ~0x80;
spiSendOctet(i);
spiSendOctet(*((canvas->canvas)+i));
}
}
spiSendOctet(0xfe);
}
void displayDriverInit() {
// SPI in master mode
UCB0CTL0 = UCMST;
// SPI timing config
UCB0CTL1 = UCSSEL_3;
UCB0BR0 = 8;
UCB0BR1 = 0;
// BIT5: UCB0CLK
// BIT6: UCB0SOMI
// BIT7: UCB0SIMO
P1SEL |= BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT5 | BIT6 | BIT7;
P1DIR |= BIT5 | BIT7;
// P1.3 is signal line
P1DIR &= ~BIT3;
// enable SPI module
UCB0CTL1 &= ~UCSWRST;
}

View File

@ -1,9 +0,0 @@
#ifndef _DISPLAY_DRIVER_H_
#define _DISPLAY_DRIVER_H_
void displayDriverInit();
void displayDriverTransferCanvas();
#endif // _DISPLAY_DRIVER_H_

View File

@ -1,29 +0,0 @@
#include <stdlib.h>
#include "canvas.h"
#include "PontCoopScheduler.h"
static canvas_t *canvas;
static uint8_t lastPixel = 0xff;
static uint8_t currentPixel = 0;
void displayTestExec(void *args) {
if (lastPixel != 0xff) {
*((canvas->canvas)+lastPixel) = 0x80;
}
lastPixel = currentPixel;
*((canvas->canvas)+currentPixel) = 0x81;
currentPixel++;
if (currentPixel >= canvas->size) {
currentPixel = 0;
}
canvasShow();
}
void displayTestInit() {
canvas = canvasGet();
schAdd(displayTestExec, NULL, 0, 50);
}

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

Binary file not shown.

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

BIN
docs/schematics.pdf Executable file

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

31
game-ctrl/Makefile Normal file
View File

@ -0,0 +1,31 @@
TOOLCHAIN_PREFIX=/opt/msp430-gcc
CC=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-gcc
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
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 config.o
$(CC) -o $@ $(LDFLAGS) $^
$(OBJDUMP) -D $(ARTIFACT).elf > $(ARTIFACT).txt
.c.o:
$(CC) $(CFLAGS) -c $<
.PHONY: all
all: $(ARTIFACT).elf
.PHONY: clean
clean:
-rm -f *.o $(ARTIFACT).elf $(ARTIFACT).txt
.PHONY: upload
upload: $(ARTIFACT).elf
mspdebug rf2500 "prog $(ARTIFACT).elf"

135
game-ctrl/buttons.c Normal file
View File

@ -0,0 +1,135 @@
#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"
#include "eeprom.h"
bool mutedFlag = true;
static uint8_t buttonsMoveLeftPressed() {
static uint8_t last = 0;
uint8_t current = (P2IN & BIT4);
uint8_t res = (current != 0) && (current != last);
last = current;
return res;
}
bool buttonsConfig1Pressed() {
return buttonsMoveLeftPressed();
}
static uint8_t buttonsMoveRightPressed() {
static uint8_t last = 0;
uint8_t current = (P2IN & BIT0);
uint8_t res = (current != 0) && (current != last);
last = current;
return res;
}
bool buttonsConfig4Pressed() {
return buttonsMoveRightPressed();
}
static uint8_t buttonsRotateLeftPressed() {
static uint8_t last = 0;
uint8_t current = (P2IN & BIT3);
uint8_t res = (current != 0) && (current != last);
last = current;
return res;
}
bool buttonsConfig2Pressed() {
return buttonsRotateLeftPressed();
}
static uint8_t buttonsRotateRightPressed() {
static uint8_t last = 0;
uint8_t current = (P2IN & BIT1);
uint8_t res = (current != 0) && (current != last);
last = current;
return res;
}
bool buttonsConfig3Pressed() {
return buttonsRotateRightPressed();
}
static uint8_t buttonsMoveDownPressed() {
return P2IN & BIT2;
}
bool isConfigMode() {
return (P2IN & BIT2);
}
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;
}
uint8_t buttonPressed = 0;
if (buttonsMoveLeftPressed()) {
stoneMoveLeft();
buttonPressed = 1;
}
if (buttonsMoveRightPressed()) {
stoneMoveRight();
buttonPressed = 1;
}
if (buttonsRotateLeftPressed()) {
stoneRotateLeft();
buttonPressed = 1;
}
if (buttonsRotateRightPressed()) {
stoneRotateRight();
buttonPressed = 1;
}
if (buttonsMoveDownPressed()) {
stoneMoveDown();
buttonPressed = 1;
}
if (buttonPressed == 1) {
canvasShow();
if (mutedFlag) {
eepromIncGameCounter();
soundCtrl(SOUND_UNMUTE);
mutedFlag = false;
}
unmuteTimestamp = currentTimestamp;
}
if ((! mutedFlag) && (unmuteTimestamp + MUTE_DELAY < currentTimestamp)) {
soundCtrl(SOUND_MUTE);
mutedFlag = true;
}
}
void buttonsInit() {
P2DIR &= ~(BIT0|BIT1|BIT2|BIT3|BIT4);
}
void buttonsStart() {
schAdd(buttonsExec, NULL, 0, 25);
}
bool isGameActive() {
return ! mutedFlag;
}

16
game-ctrl/buttons.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _BUTTONS_H_
#define _BUTTONS_H_
#include <stdbool.h>
void buttonsInit();
void buttonsStart();
bool isGameActive();
bool isConfigMode();
bool buttonsConfig1Pressed();
bool buttonsConfig2Pressed();
bool buttonsConfig3Pressed();
bool buttonsConfig4Pressed();
#endif // _BUTTONS_H_

113
game-ctrl/canvas.c Normal file
View File

@ -0,0 +1,113 @@
#include <string.h>
#include <stdint.h>
#include <msp430g2553.h>
#include "canvas.h"
#include "spi.h"
#include "eeprom.h"
#include "../rgb-driver/colors.h"
static uint8_t canvasStorage[CANVAS_WIDTH * CANVAS_HEIGHT];
const canvas_t canvas = {
.height = CANVAS_HEIGHT,
.width = CANVAS_WIDTH,
.canvas = canvasStorage
};
static uint8_t miniCanvasStorage[MINI_CANVAS_WIDTH * MINI_CANVAS_HEIGHT];
const canvas_t miniCanvas = {
.height = MINI_CANVAS_HEIGHT,
.width = MINI_CANVAS_WIDTH,
.canvas = miniCanvasStorage
};
void canvasShow() {
uint8_t brightness_offset = _brightness_offset * eepromReadBrightness();
// wait for signal waiting for data
while (!(P1IN & BIT3));
spiSendBegin(e_SPI_CANVAS);
for (uint8_t i = 0; i < (CANVAS_WIDTH*CANVAS_HEIGHT); i++) {
if ((*((canvas.canvas)+i) & 0x80) != 0) {
*((canvas.canvas)+i) &= ~0x80;
spiSendOctet(i);
spiSendOctet((*((canvas.canvas)+i) == 0) ? 0 : (*((canvas.canvas)+i) + brightness_offset));
}
}
for (uint8_t i = 0; i < (MINI_CANVAS_WIDTH*MINI_CANVAS_HEIGHT); i++) {
if ((*((miniCanvas.canvas)+i) & 0x80) != 0) {
*((miniCanvas.canvas)+i) &= ~0x80;
spiSendOctet(i + (CANVAS_HEIGHT*CANVAS_WIDTH));
spiSendOctet((*((miniCanvas.canvas)+i) == 0) ? 0 : (*((miniCanvas.canvas)+i) + brightness_offset));
}
}
spiSendOctet(0xfe);
spiSendEnd(e_SPI_CANVAS);
}
void canvasInit() {
// P1.3 is signal line
P1DIR &= ~BIT3;
canvasClear();
miniCanvasClear();
canvasShow();
}
void canvasClear() {
memset(canvas.canvas, 0x80, CANVAS_WIDTH*CANVAS_HEIGHT);
}
void miniCanvasClear() {
memset(miniCanvas.canvas, 0x80, MINI_CANVAS_WIDTH*MINI_CANVAS_HEIGHT);
}
//void canvasSetAll(uint8_t color) {
// memset(canvas.canvas, color + 0x80, CANVAS_WIDTH*CANVAS_HEIGHT);
//}
void canvasSetPixel(uint8_t column, uint8_t row, uint8_t color) {
*((canvas.canvas) + (row * canvas.width + column)) = (color + 0x80);
}
void miniCanvasSetPixel(uint8_t column, uint8_t row, uint8_t color) {
*((miniCanvas.canvas) + (row * miniCanvas.width + column)) = (color + 0x80);
}
void canvasWipeRow(uint8_t row) {
memmove(((canvas.canvas)+canvas.width), canvas.canvas, canvas.width*row);
for (uint8_t i = 10; i < canvas.width*(row+1); i++) {
*((canvas.canvas)+i) += 0x80;
}
memset(canvas.canvas, 0x80, canvas.width);
}
void canvasFillRow(uint8_t row, uint8_t color) {
for (uint8_t c = 0; c < canvas.width; c++) {
canvasSetPixel(c, row, color);
}
}
uint8_t canvasIsRowFilled(uint8_t row) {
uint8_t res = 1;
for (uint8_t column = 0; column < canvas.width; column++) {
if (*((canvas.canvas) + (row * canvas.width + column)) == 0) {
res = 0;
break;
}
}
return res;
}
uint8_t canvasIsPixelFree(uint8_t column, uint8_t row) {
return (*((canvas.canvas) + (row * canvas.width + column)) == 0) &&
(column < canvas.width) &&
(row < canvas.height);
}

30
game-ctrl/canvas.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef _CANVAS_H_
#define _CANVAS_H_
#include <stdint.h>
// Definition of CANVAS_WIDTH and CANVAS_HEIGHT
// imported from rgb-driver
#include "../rgb-driver/canvasSize.h"
typedef struct {
const uint8_t width;
const uint8_t height;
const uint8_t size;
uint8_t * const canvas;
} canvas_t;
void canvasInit();
void canvasClear();
void miniCanvasClear();
//void canvasSetAll(uint8_t color);
void canvasShow();
void canvasSetPixel(uint8_t column, uint8_t row, uint8_t color);
void miniCanvasSetPixel(uint8_t column, uint8_t row, uint8_t color);
uint8_t canvasIsPixelFree(uint8_t column, uint8_t row);
void canvasWipeRow(uint8_t row);
void canvasFillRow(uint8_t row, uint8_t color);
uint8_t canvasIsRowFilled(uint8_t row);
#endif // _CANVAS_H_

135
game-ctrl/config.c Normal file
View File

@ -0,0 +1,135 @@
#include <stddef.h>
#include "config.h"
#include "canvas.h"
#include "../rgb-driver/colors.h"
#include "scheduler.h"
#include "buttons.h"
#include "eeprom.h"
#include "display.h"
#include "shapes.h"
#include "sound.h"
static bool configChanged = false;
static bool muted = false;
static void configHandleFlash() {
uint8_t color = eepromReadFlashColor();
canvasFillRow(CANVAS_HEIGHT-1, color);
displaySetValue(color);
if (buttonsConfig2Pressed()) {
configChanged = true;
color += 1;
if (color > _color_end) {
color = 0;
}
eepromSetFlashColor(color);
}
}
static void configHandleResetHighScore() {
displaySetValue(eepromReadHighScore());
if (buttonsConfig2Pressed()) {
configChanged = true;
eepromSetHighScore(0);
}
}
static void configHandleResetGameCounter() {
displaySetValue(eepromReadGameCounter());
if (buttonsConfig2Pressed()) {
configChanged = true;
eepromClearGameCounter(0);
}
}
static void configHandleBrightness() {
displaySetValue(eepromReadBrightness());
stoneDrawConfigPattern();
if (buttonsConfig2Pressed()) {
configChanged = true;
uint8_t brightness = eepromReadBrightness() + 1;
if (brightness > _brightness_shifts) {
brightness = 0;
}
eepromSetBrightness(brightness);
}
}
static void configHandleAmplitude() {
displaySetValue(eepromReadAmplitude());
if (muted) {
muted = false;
soundCtrl(SOUND_START);
soundCtrl(SOUND_UNMUTE);
}
if (buttonsConfig2Pressed()) {
configChanged = true;
uint8_t amplitude = eepromReadAmplitude() + 1;
if (amplitude > 15) {
amplitude = 0;
}
eepromSetAmplitude(amplitude);
soundCtrl(SOUND_COMMAND + SOUND_SUBCMD_AMPLITUDE + amplitude);
}
}
void (*configHandler[])(void) = {
configHandleResetHighScore,
configHandleResetGameCounter,
configHandleFlash,
configHandleBrightness,
configHandleAmplitude
};
void configExec(void *handle) {
static uint8_t configState = 0;
static uint8_t lastConfigState = 255;
if (configState != lastConfigState) {
lastConfigState = configState;
miniCanvasClear();
canvasClear();
if (! muted) {
muted = true;
soundCtrl(SOUND_MUTE);
}
uint8_t row = configState / 3;
uint8_t column = configState % 3;
miniCanvasSetPixel(column, row, _red);
}
if (buttonsConfig1Pressed()) {
configState += 1;
if (configState >= sizeof(configHandler) / sizeof(configHandler[0])) {
configState = 0;
}
}
configHandler[configState]();
if (configChanged) {
miniCanvasSetPixel(0, 3, _red);
if (buttonsConfig4Pressed()) {
eepromCommit();
configChanged = false;
}
} else {
miniCanvasSetPixel(0, 3, _green);
}
canvasShow();
}
void configInit() {
schAdd(configExec, NULL, 0, 100);
}

8
game-ctrl/config.h Normal file
View File

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

View File

@ -0,0 +1,184 @@
#include <stdlib.h>
#include "canvas.h"
#include "PontCoopScheduler.h"
#define MAX_COLOR 0x0d
typedef enum {
e_WIPE_LAST_COLUMN_DOWN,
e_PIXELS_UP,
e_WIPE_LAST_PIXEL_UP,
e_PIXELS_DOWN,
e_WIPE_LAST_PIXEL_DOWN,
e_ROWS_UP,
e_WIPE_LAST_ROW_UP,
e_ROWS_DOWN,
e_WIPE_LAST_ROW_DOWN,
e_COLUMNS_UP,
e_WIPE_LAST_COLUMN_UP,
e_COLUMNS_DOWN
} t_State;
void displayTestExec(void *args) {
static int16_t last = 0xff;
static int16_t current = 0;
static uint8_t color = 0x01;
static t_State state = e_PIXELS_UP;
switch (state) {
// wipe last column
case e_WIPE_LAST_COLUMN_DOWN:
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(last, i, 0);
}
last = 0xff;
state = e_PIXELS_UP;
// pixels up
case e_PIXELS_UP:
if (last != 0xff) {
*((canvas.canvas)+last) = 0x80;
}
last = current;
*((canvas.canvas)+current) = (color + 0x80);
current++;
if (current >= canvas.size) {
current = 0;
state = e_WIPE_LAST_PIXEL_UP;
}
break;
// wipe last pixel
case e_WIPE_LAST_PIXEL_UP:
*((canvas.canvas)+last) = 0x80;
last = 0xff;
current = canvas.size - 1;
state = e_PIXELS_DOWN;
// pixels down
case e_PIXELS_DOWN:
if (last != 0xff) {
*((canvas.canvas)+last) = 0x80;
}
last = current;
*((canvas.canvas)+current) = (color + 0x80);
current--;
if (current < 0) {
current = 0;
state = e_WIPE_LAST_PIXEL_DOWN;
}
break;
// wipe last pixel
case e_WIPE_LAST_PIXEL_DOWN:
*((canvas.canvas)+last) = 0x80;
last = 0xff;
state = e_ROWS_UP;
// rows up
case e_ROWS_UP:
if (last != 0xff) {
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, last, 0);
}
}
last = current;
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, current, color);
}
current++;
if (current >= canvas.height) {
current = 0;
state = e_WIPE_LAST_ROW_UP;
}
break;
// wipe last row
case e_WIPE_LAST_ROW_UP:
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, last, 0);
}
last = 0xff;
current = canvas.height - 1;
state = e_ROWS_DOWN;
// rows down
case e_ROWS_DOWN:
if (last != 0xff) {
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, last, 0);
}
}
last = current;
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, current, color);
}
current--;
if (current < 0) {
current = 0;
state = e_WIPE_LAST_ROW_DOWN;
}
break;
// wipe last row
case e_WIPE_LAST_ROW_DOWN:
for (uint16_t i = 0; i < canvas.width; i++) {
canvasSetPixel(i, last, 0);
}
last = 0xff;
state = e_COLUMNS_UP;
// columns up
case e_COLUMNS_UP:
if (last != 0xff) {
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(last, i, 0);
}
}
last = current;
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(current, i, color);
}
current++;
if (current >= canvas.width) {
current = 0;
state = e_WIPE_LAST_COLUMN_UP;
}
break;
// wipe last column
case e_WIPE_LAST_COLUMN_UP:
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(last, i, 0);
}
last = 0xff;
current = canvas.width - 1;
state = e_COLUMNS_DOWN;
// columns down
case e_COLUMNS_DOWN:
if (last != 0xff) {
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(last, i, 0);
}
}
last = current;
for (uint16_t i = 0; i < canvas.height; i++) {
canvasSetPixel(current, i, color);
}
current--;
if (current < 0) {
current = 0;
state = e_WIPE_LAST_COLUMN_DOWN;
}
break;
}
color++;
if (color > MAX_COLOR) {
color = 1;
}
canvasShow();
}
void displayTestInit() {
schAdd(displayTestExec, NULL, 0, 50);
}

View File

@ -0,0 +1,75 @@
#include <stdlib.h>
#include <string.h>
#include "canvas.h"
#include "PontCoopScheduler.h"
#include "displayTest2.h"
typedef enum { e_idle, e_start, e_fillArea, e_placeObject, e_wipeLines_7, e_wipeLines_8, e_wipeLines_9, e_wipeLines_10 } t_state;
void displayTest2Exec(void *args) {
static t_state state = e_start;
switch (state) {
case e_idle:
break;
case e_start:
state = e_fillArea;
break;
case e_fillArea:
memset((canvas.canvas)+(canvas.width*7), 0x81, canvas.width);
memset((canvas.canvas)+(canvas.width*8), 0x8d, canvas.width);
memset((canvas.canvas)+(canvas.width*9), 0x82, canvas.width);
memset((canvas.canvas)+(canvas.width*10), 0x88, canvas.width);
for (uint8_t i = 0; i < canvas.width; i++) {
if (i != 4 && i != 5) {
canvasSetPixel(i, 6, 0x05);
}
}
state = e_placeObject;
break;
case e_placeObject:
canvasSetPixel(4, 0, 0x04);
canvasSetPixel(5, 0, 0x04);
canvasSetPixel(6, 0, 0x04);
canvasSetPixel(5, 1, 0x04);
state = e_wipeLines_7;
break;
case e_wipeLines_7:
canvasWipeRow(7);
state = e_wipeLines_8;
break;
case e_wipeLines_8:
canvasWipeRow(8);
state = e_wipeLines_9;
break;
case e_wipeLines_9:
canvasWipeRow(9);
state = e_wipeLines_10;
break;
case e_wipeLines_10:
canvasWipeRow(10);
state = e_idle;
break;
default:
state = e_idle;
break;
}
canvasShow();
}
void displayTest2Init() {
schAdd(displayTest2Exec, NULL, 0, 5000);
}

View File

@ -0,0 +1,11 @@
#ifndef _DISPLAY_TEST_2_H_
#define _DISPLAY_TEST_2_H_
void displayTest2Init();
void displayTest2Exec();
#endif // _DISPLAY_TEST_2_H_

View File

@ -0,0 +1,78 @@
#include <stdlib.h>
#include <string.h>
#include "canvas.h"
#include "PontCoopScheduler.h"
#include "displayTest3.h"
#include "led.h"
typedef enum { e_idle, e_delay, e_start, e_fillArea, e_placeObject, e_preWipeLines, e_wipeLines } t_state;
void displayTest3Exec(void *args) {
static t_state state = e_start;
static uint8_t wipeLineHelper = 0;
ledBlueToggle();
switch (state) {
case e_idle:
state = e_delay;
break;
case e_delay:
state = e_start;
break;
case e_start:
canvasClear();
state = e_fillArea;
break;
case e_fillArea:
memset((canvas.canvas)+(canvas.width*7), 0x82, canvas.width);
memset((canvas.canvas)+(canvas.width*8), 0x83, canvas.width);
memset((canvas.canvas)+(canvas.width*9), 0x84, canvas.width);
memset((canvas.canvas)+(canvas.width*10), 0x85, canvas.width);
for (uint8_t i = 0; i < canvas.width; i++) {
if (i != 4 && i != 5) {
canvasSetPixel(i, 6, 0x01);
}
}
state = e_placeObject;
break;
case e_placeObject:
canvasSetPixel(4, 0, 0x04);
canvasSetPixel(5, 0, 0x04);
canvasSetPixel(6, 0, 0x04);
canvasSetPixel(5, 1, 0x04);
state = e_preWipeLines;
break;
case e_preWipeLines:
wipeLineHelper = 0;
state = e_wipeLines;
// no break
case e_wipeLines:
if (canvasIsRowFilled(wipeLineHelper) == 1) {
canvasWipeRow(wipeLineHelper);
}
wipeLineHelper++;
if (wipeLineHelper >= canvas.height) {
state = e_idle;
}
break;
default:
state = e_idle;
break;
}
canvasShow();
}
void displayTest3Init() {
schAdd(displayTest3Exec, NULL, 0, 1000);
}

View File

@ -0,0 +1,11 @@
#ifndef _DISPLAY_TEST_3_H_
#define _DISPLAY_TEST_3_H_
void displayTest3Init();
void displayTest3Exec();
#endif // _DISPLAY_TEST_3_H_

View File

@ -1,6 +1,6 @@
#include "led.h"
#include <msp430g2553.h>
#include "PontCoopScheduler.h"
#include "scheduler.h"
#include <stdlib.h>
@ -12,6 +12,10 @@ void ledGreenOff() {
P1OUT &= ~BIT0;
}
void ledGreenToggle() {
P1OUT ^= BIT0;
}
void ledBlueOn() {
P1OUT |= BIT1;
}
@ -20,8 +24,12 @@ void ledBlueOff() {
P1OUT &= ~BIT1;
}
void ledBlueToggle() {
P1OUT ^= BIT1;
}
void ledExec(void *args) {
static int i = 0;
static uint16_t i = 0;
if (i == 0) {
ledGreenOff();
@ -41,7 +49,7 @@ void ledInit() {
ledBlueOff();
// schAdd(ledExec, NULL, 0, 50);
// schAdd(ledExec, NULL, 0, 500);
}

View File

@ -4,8 +4,10 @@
void ledBlueOff();
void ledBlueOn();
void ledBlueToggle();
void ledGreenOff();
void ledGreenOn();
void ledGreenToggle();
void ledInit();
void ledExec();

26
game-ctrl/display.c Normal file
View File

@ -0,0 +1,26 @@
#include <stdint.h>
#include "display.h"
#include "spi.h"
void displayInit() {
}
void displaySetValue(uint16_t v) {
union {
uint16_t value;
uint8_t sendBuffer[2];
} value;
value.value = v;
spiSendBegin(e_SPI_DISPLAY);
spiSendOctet(value.sendBuffer[0]);
spiSendOctet(value.sendBuffer[1]);
spiSendEnd(e_SPI_DISPLAY);
}

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

@ -0,0 +1,13 @@
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
#include <stdint.h>
void displayInit();
void displaySetValue(uint16_t v);
#endif // _DISPLAY_H_

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

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

@ -0,0 +1,153 @@
#include <stdint.h>
#include <string.h>
#include <sys/param.h>
#include "eeprom.h"
#include "spi.h"
#include "scheduler.h"
#include "display.h"
#include "canvas.h"
#include "../rgb-driver/colors.h"
#define MAGIC 0xb003
#define HIGHSCORE_ADDR 0x00
#define DUMMY 0x00
#define CMD_READ 0b00000011
#define CMD_WRITE 0b00000010
#define CMD_WRDI 0b00000100
#define CMD_WREN 0b00000110
typedef struct {
uint16_t magic;
uint16_t highScore;
uint16_t gameCounter;
uint8_t flashColor;
uint8_t brightness;
uint8_t amplitude;
} t_configBlock;
typedef union {
t_configBlock v;
uint8_t buffer[sizeof(t_configBlock)];
} 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);
for (uint8_t i = 0; i < sizeof(t_configBlock); i++) {
spiSendOctet(buf.buffer[i]);
}
spiSendEnd(e_SPI_EEPROM);
}
static void readBuf() {
spiSendBegin(e_SPI_EEPROM);
spiSendOctet(CMD_READ);
spiReceiveOctet();
spiSendOctet(HIGHSCORE_ADDR);
spiReceiveOctet();
for (uint8_t i = 0; i < sizeof(t_configBlock); i++) {
spiSendOctet(DUMMY);
buf.buffer[i] = spiReceiveOctet();
}
spiSendEnd(e_SPI_EEPROM);
}
void eepromInit() {
readBuf();
if (buf.v.magic != MAGIC) {
memset(buf.buffer, 0, sizeof(t_configBlock));
buf.v.magic = MAGIC;
writeBuf();
}
}
void eepromCommit() {
writeBuf();
}
void eepromShowValues() {
canvasClear();
canvasFillRow(0, _green);
canvasShow();
displaySetValue(buf.v.highScore);
wait(2);
canvasClear();
canvasFillRow(1, _green);
canvasShow();
displaySetValue(MIN(buf.v.gameCounter, 9999));
wait(2);
canvasClear();
canvasFillRow(2, _green);
canvasShow();
displaySetValue(buf.v.flashColor);
wait(2);
canvasClear();
canvasFillRow(3, _green);
canvasShow();
displaySetValue(buf.v.brightness);
wait(2);
canvasClear();
canvasFillRow(4, _green);
canvasShow();
displaySetValue(buf.v.amplitude);
wait(2);
}
uint16_t eepromReadHighScore() {
return buf.v.highScore;
}
void eepromSetHighScore(uint16_t v) {
buf.v.highScore = v;
writeBuf();
}
uint8_t eepromReadFlashColor() {
return buf.v.flashColor;
}
void eepromSetFlashColor(uint8_t v) {
buf.v.flashColor = v;
}
uint8_t eepromReadBrightness() {
return buf.v.brightness;
}
void eepromSetBrightness(uint8_t v) {
buf.v.brightness = v;
}
uint8_t eepromReadAmplitude() {
return buf.v.amplitude;
}
void eepromSetAmplitude(uint8_t v) {
buf.v.amplitude = v;
}
uint16_t eepromReadGameCounter() {
return buf.v.gameCounter;
}
void eepromIncGameCounter() {
buf.v.gameCounter += 1;
writeBuf();
}
void eepromClearGameCounter() {
buf.v.gameCounter = 0;
}

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

@ -0,0 +1,24 @@
#ifndef _EEPROM_H_
#define _EEPROM_H_
#include <stdint.h>
void eepromInit();
void eepromCommit();
void eepromShowValues();
uint16_t eepromReadHighScore();
void eepromSetHighScore(uint16_t v);
uint8_t eepromReadFlashColor();
void eepromSetFlashColor(uint8_t v);
uint8_t eepromReadBrightness();
void eepromSetBrightness(uint8_t v);
uint8_t eepromReadAmplitude();
void eepromSetAmplitude(uint8_t v);
void eepromIncGameCounter();
uint16_t eepromReadGameCounter();
void eepromClearGameCounter();
#endif // _EEPROM_H_

2
game-ctrl/firmware.gdb Normal file
View File

@ -0,0 +1,2 @@
target remote localhost:2000
file firmware.elf

196
game-ctrl/game.c Normal file
View File

@ -0,0 +1,196 @@
// #define STATE_DEBUGGING
#include "stddef.h"
#include "stdint.h"
#include "game.h"
#include "scheduler.h"
#include "shapes.h"
#include "canvas.h"
#include "../rgb-driver/colors.h"
#include "display.h"
#include "sound.h"
#include "eeprom.h"
#include "buttons.h"
#define GAME_CYCLE_TIME 10
#define GAMEOVER_DELAY 10
#define MAX_LEVEL 100
static uint16_t delayFactor(uint16_t level) {
return MAX_LEVEL + 1 - level;
}
typedef enum {
e_BootWait,
e_Start, e_NewStone, e_Down, e_DownDelay,
e_ClearRowInit, e_ClearRowNext, e_ClearRowCheck, e_ClearRowFlash, e_ClearRowFlashDelay, e_ClearRowWipe,
e_GameOver, e_GameOverFill, e_GameOverWipe, e_GameOverDelay
} state_t;
void gameExec(void *handle) {
static state_t state = e_BootWait;
static uint16_t bootWaitTime = 2500 / GAME_CYCLE_TIME;
static uint8_t gameOverDelay;
static uint8_t rowIndex;
static uint16_t proceedDelay;
static uint16_t level;
static uint16_t filledLines;
static uint16_t score;
static bool newHighScoreAchieved;
static uint8_t clearCheckCnt;
#ifdef STATE_DEBUGGING
displaySetValue(state);
#endif
// --- engine begin -------------------------------------------------------
switch (state) {
case e_BootWait:
bootWaitTime -= 1;
if (bootWaitTime == 0) {
state = e_Start;
}
break;
// --- phase: game --------------------------------------------------------
case e_Start:
canvasClear();
soundCtrl(SOUND_START);
level = 1;
filledLines = 0;
score = 0;
newHighScoreAchieved = false;
state = e_NewStone;
break;
case e_NewStone:
stoneCreate();
if (stoneDraw()) {
proceedDelay = delayFactor(level);
state = e_DownDelay;
} else {
state = e_GameOver;
}
break;
case e_DownDelay:
proceedDelay--;
if (proceedDelay == 0) {
state = e_Down;
}
break;
case e_Down:
if (! stoneMoveDown()) {
soundCtrl(SOUND_LOCK);
stoneLock();
state = e_ClearRowInit;
} else {
proceedDelay = delayFactor(level);
state = e_DownDelay;
}
break;
// --- phase: clear rows --------------------------------------------------
case e_ClearRowInit:
clearCheckCnt = 0;
state = e_ClearRowCheck;
break;
case e_ClearRowNext:
if (clearCheckCnt >= CANVAS_HEIGHT) {
state = e_NewStone;
} else {
clearCheckCnt += 1;
state = e_ClearRowCheck;
}
break;
case e_ClearRowCheck:
if (canvasIsRowFilled(clearCheckCnt)) {
score += level;
if (score > eepromReadHighScore()) {
newHighScoreAchieved = true;
eepromSetHighScore(score);
eepromCommit();
}
state = e_ClearRowFlash;
} else {
state = e_ClearRowNext;
}
break;
case e_ClearRowFlash:
canvasFillRow(clearCheckCnt, eepromReadFlashColor());
state = e_ClearRowFlashDelay;
break;
case e_ClearRowFlashDelay:
state = e_ClearRowWipe;
break;
case e_ClearRowWipe:
canvasWipeRow(clearCheckCnt);
filledLines += 1;
if ((filledLines > 0) && ((filledLines % 10) == 0)) {
if (level < MAX_LEVEL) {
level += 1;
}
soundCtrl(SOUND_FANFARE);
} else {
soundCtrl(SOUND_PLING);
}
state = e_ClearRowNext;
break;
// --- phase: game over ---------------------------------------------------
case e_GameOver:
soundCtrl(SOUND_GAMEOVER);
rowIndex = CANVAS_HEIGHT;
state = e_GameOverFill;
break;
case e_GameOverFill:
rowIndex--;
canvasFillRow(rowIndex, newHighScoreAchieved ? _green : _red);
if (rowIndex == 0) {
state = e_GameOverWipe;
}
break;
case e_GameOverWipe:
canvasWipeRow(rowIndex);
rowIndex++;
if (rowIndex == CANVAS_HEIGHT) {
gameOverDelay = GAMEOVER_DELAY;
state = e_GameOverDelay;
}
break;
case e_GameOverDelay:
gameOverDelay--;
if (gameOverDelay == 0) {
state = e_Start;
}
break;
}
// --- engine end ---------------------------------------------------------
canvasShow();
#ifndef STATE_DEBUGGING
if (isGameActive()) {
displaySetValue(score);
} else {
displaySetValue(eepromReadHighScore());
}
#endif
}
void gameInit() {
schAdd(gameExec, NULL, 0, GAME_CYCLE_TIME);
}

7
game-ctrl/game.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef _GAME_H_
#define _GAME_H_
void gameInit();
#endif // _GAME_H_

View File

@ -4,11 +4,17 @@
#include <stdbool.h>
#include "time.h"
#include "PontCoopScheduler.h"
#include "led.h"
#include "displayDriver.h"
#include "scheduler.h"
#include "canvas.h"
#include "displayTest.h"
#include "game.h"
#include "buttons.h"
#include "shapes.h"
#include "myrand.h"
#include "spi.h"
#include "display.h"
#include "eeprom.h"
#include "config.h"
#include "sound.h"
int main() {
@ -22,15 +28,25 @@ int main() {
BCSCTL2 = 0;
BCSCTL3 = 0;
timeInit();
schInit();
ledInit();
displayDriverInit();
spiInit();
eepromInit();
displayInit();
myRandInit();
canvasInit();
displayTestInit();
soundInit();
buttonsInit();
eepromShowValues();
if (isConfigMode()) {
configInit();
} else {
shapesInit();
gameInit();
buttonsStart();
}
__enable_interrupt();

26
game-ctrl/myrand.c Normal file
View File

@ -0,0 +1,26 @@
#include <stdint.h>
#include <msp430g2553.h>
#include "myrand.h"
void myRandInit() {
ADC10CTL1 = INCH_10;
ADC10CTL0 = SREF_1 | ADC10SHT_1 | REFON | ADC10ON;
}
uint16_t myRandGet() {
uint16_t res = 0;
for (uint8_t i = 0; i < 16; i++) {
ADC10CTL0 |= ENC | ADC10SC;
while ((ADC10CTL1 & ADC10BUSY));
res <<= 1;
res |= ADC10MEM & 0x0001;
}
return res;
}

8
game-ctrl/myrand.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _MYRAND_H_
#define _MYRAND_H_
void myRandInit();
uint16_t myRandGet();
#endif // _MYRAND_H_

117
game-ctrl/scheduler.c Normal file
View File

@ -0,0 +1,117 @@
/*
* PontCoopScheduler.c
*
* Originally created on: 29.08.2016
* Author: wn
*/
#include <stdlib.h>
#include <msp430g2553.h>
#include "scheduler.h"
tTask tasks[MAX_NUM_OF_TASKS];
uint32_t seconds;
void schInit() {
TACCR0 = 32;
TACCTL0 = CCIE;
TACTL = MC_1 | ID_0 | TASSEL_1 | TACLR;
for (uint16_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
tasks[i].delay = 0;
tasks[i].period = 0;
tasks[i].run = 0;
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) {
tasks[i].delay = tasks[i].period;
tasks[i].run++;
} else {
tasks[i].delay--;
}
}
}
}
void schAdd(void (*exec)(void *), void *handle, uint32_t delay, uint32_t period) {
for (uint16_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
if (tasks[i].exec == NULL) {
tasks[i].delay = delay;
tasks[i].period = period;
if (delay == 0 && period == 0) {
tasks[i].run = 1;
} else {
tasks[i].run = 0;
}
tasks[i].exec = exec;
tasks[i].handle = handle;
break;
}
}
}
/*
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 schExec() {
for (uint16_t i = 0; i < MAX_NUM_OF_TASKS; i++) {
// synchronize access to tasks[].run
__disable_interrupt();
if (tasks[i].exec != NULL && tasks[i].run > 0) {
tasks[i].run--;
// synchronize access to tasks[].run
// reenable interrupts before actually executing task
__enable_interrupt();
tasks[i].exec(tasks[i].handle);
if (tasks[i].period == 0) {
tasks[i].exec = NULL;
}
} else {
// synchronize access to tasks[].run
// reenable interrupts in case task is not yet executable
__enable_interrupt();
}
}
}
uint32_t getSeconds() {
uint32_t s;
__disable_interrupt();
s = seconds;
__enable_interrupt();
return s;
}
void wait(uint8_t t) {
uint8_t startTime = getSeconds();
while (getSeconds() < (startTime + t));
}

View File

@ -13,12 +13,12 @@
#define MAX_NUM_OF_TASKS 4
#define MAX_NUM_OF_TASKS 2
typedef struct {
uint32_t delay;
uint32_t period;
uint16_t delay;
uint16_t period;
uint8_t run;
void (*exec)(void *handle);
void *handle;
@ -31,6 +31,7 @@ void schDel(void (*exec)(void *), void *handle);
void schExec();
void schUpdate();
uint8_t schTaskCnt();
uint32_t getSeconds();
void wait(uint8_t t);
#endif /* PONTCOOPSCHEDULER_H_ */

529
game-ctrl/shapes.c Normal file
View File

@ -0,0 +1,529 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include "shapes.h"
#include "myrand.h"
#include "canvas.h"
#include "../rgb-driver/colors.h"
typedef enum { e_I=0, e_O, e_T, e_Z, e_S, e_L, e_J, e_ShapeInvalid } shape_t;
typedef enum { e_MoveDown, e_MoveLeft, e_MoveRight, e_RotateLeft, e_RotateRight } direction_t;
typedef enum { e_0, e_90, e_180, e_270, e_Keep } orientation_t;
typedef struct {
shape_t shape;
orientation_t orientation;
uint8_t x; // column
uint8_t y; // row
bool locked;
} stone_t;
typedef struct {
uint8_t x;
uint8_t y;
} pixel_t;
typedef struct {
int8_t x;
int8_t y;
} offset_t;
typedef struct {
offset_t set[4];
offset_t reset[4];
offset_t offset;
} motion_t;
typedef struct {
uint8_t color;
uint8_t nullRotation;
pixel_t draw[4];
motion_t motion[5][4];
} motionTable_t;
const motionTable_t motions[7] = { // size = number of implemented stones
{ // I
.color = _cyan,
.nullRotation = 0,
.draw = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 3} },
.motion = {
{
// move down
{ .set = { { 0, 4}, { 0, 4}, { 0, 4}, { 0, 4} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 1} }, // 0
{ .set = { { 0, 1}, { 1, 1}, { 2, 1}, { 3, 1} }, .reset = { { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 4}, { 0, 4}, { 0, 4}, { 0, 4} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 1}, { 1, 1}, { 2, 1}, { 3, 1} }, .reset = { { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0} }, .offset = { 0, 1} } // 270
},
{
// move left
{ .set = { {-1, 0}, {-1, 1}, {-1, 2}, {-1, 3} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 3} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 0}, {-1, 0}, {-1, 0}, {-1, 0} }, .reset = { { 3, 0}, { 3, 0}, { 3, 0}, { 3, 0} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 0}, {-1, 1}, {-1, 2}, {-1, 3} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 3} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 0}, {-1, 0}, {-1, 0}, {-1, 0} }, .reset = { { 3, 0}, { 3, 0}, { 3, 0}, { 3, 0} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 1, 0}, { 1, 1}, { 1, 2}, { 1, 3} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 3} }, .offset = { 1, 0} }, // 0
{ .set = { { 4, 0}, { 4, 0}, { 4, 0}, { 4, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 1, 0} }, // 90
{ .set = { { 1, 0}, { 1, 1}, { 1, 2}, { 1, 3} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 3} }, .offset = { 1, 0} }, // 180
{ .set = { { 4, 0}, { 4, 0}, { 4, 0}, { 4, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { {-1, 1}, { 1, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 2}, { 0, 3}, { 0, 3} }, .offset = {-1, 1} }, // 0
{ .set = { { 1,-2}, { 1,-1}, { 1, 1}, { 1, 1} }, .reset = { { 0, 0}, { 2, 0}, { 3, 0}, { 3, 0} }, .offset = { 1,-2} }, // 90
{ .set = { {-1, 1}, { 1, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 2}, { 0, 3}, { 0, 3} }, .offset = {-1, 1} }, // 180
{ .set = { { 1,-2}, { 1,-1}, { 1, 1}, { 1, 1} }, .reset = { { 0, 0}, { 2, 0}, { 3, 0}, { 3, 0} }, .offset = { 1,-2} }, // 270
},
{
// rotate right
{ .set = { {-2, 1}, {-1, 1}, { 1, 1}, { 1, 1} }, .reset = { { 0, 0}, { 0, 2}, { 0, 3}, { 0, 3} }, .offset = {-2, 1} }, // 0
{ .set = { { 1,-1}, { 1, 1}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 2, 0}, { 3, 0}, { 3, 0} }, .offset = { 1,-1} }, // 90
{ .set = { {-2, 1}, {-1, 1}, { 1, 1}, { 1, 1} }, .reset = { { 0, 0}, { 0, 2}, { 0, 3}, { 0, 3} }, .offset = {-2, 1} }, // 180
{ .set = { { 1,-1}, { 1, 1}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 2, 0}, { 3, 0}, { 3, 0} }, .offset = { 1,-1} }, // 270
},
}
},
{ // O
.color = _yellow,
.nullRotation = 1,
.draw = { { 0, 0}, { 0, 1}, { 1, 0}, { 1, 1} },
.motion = {
{
// move down
{ .set = { { 0, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 0
{ .set = { { 0, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 1, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 1, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 1, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 1, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 0
{ .set = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 90
{ .set = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 180
{ .set = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 0
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 90
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 180
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 270
},
{
// rotate right
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 0
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 90
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 180
{ .set = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 0, 0} }, // 270
},
}
},
{ // T
.color = _violet,
.nullRotation = 0,
.draw = { { 0, 0}, { 1, 0}, { 2, 0}, { 1, 1} },
.motion = {
{
// move down
{ .set = { { 0, 1}, { 2, 1}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 0}, { 2, 0}, { 2, 0} }, .offset = { 0, 1} }, // 0
{ .set = { { 0, 2}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 2}, { 1, 2}, { 2, 2}, { 2, 2} }, .reset = { { 1, 0}, { 0, 1}, { 2, 1}, { 2, 1} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 3}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { {-1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 1, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = {-1, 0} }, // 0
{ .set = { { 0, 0}, {-1, 1}, { 0, 2}, { 0, 2} }, .reset = { { 1, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 1}, { 0, 0}, { 0, 0}, { 0, 0} }, .reset = { { 1, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 0}, {-1, 1}, {-1, 2}, {-1, 2} }, .reset = { { 0, 0}, { 0, 2}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 3, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 1, 0} }, // 0
{ .set = { { 2, 0}, { 2, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 1}, { 1, 0}, { 1, 2}, { 1, 2} }, .offset = { 1, 0} }, // 90
{ .set = { { 3, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 0, 1}, { 1, 0}, { 1, 0} }, .offset = { 1, 0} }, // 180
{ .set = { { 1, 0}, { 1, 2}, { 2, 1}, { 2, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 2} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { { 1,-1}, { 1,-1}, { 1,-1}, { 1,-1} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = { 1,-1} }, // 0
{ .set = { { 2, 1}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 1, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 90
{ .set = { { 1, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 2, 1}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = { 0, 0} }, // 180
{ .set = { {-1, 1}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 0, 2}, { 0, 2}, { 0, 2}, { 0, 2} }, .offset = {-1, 0} }, // 270
},
{
// rotate right
{ .set = { { 1,-1}, { 1,-1}, { 1,-1}, { 1,-1} }, .reset = { { 2, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-1} }, // 0
{ .set = { { 2, 1}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 1, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 0} }, // 90
{ .set = { { 1, 2}, { 1, 2}, { 1, 2}, { 1, 2} }, .reset = { { 0, 1}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 180
{ .set = { {-1, 1}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0} }, .offset = {-1, 1} }, // 270
},
}
},
{ // Z
.color = _red,
.nullRotation = 0,
.draw = { { 0, 0}, { 1, 0}, { 1, 1}, { 2, 1} },
.motion = {
{
// move down
{ .set = { { 0, 1}, { 1, 2}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 1, 0}, { 2, 1}, { 2, 1} }, .offset = { 0, 1} }, // 0
{ .set = { { 1, 2}, { 0, 3}, { 0, 3}, { 0, 3} }, .reset = { { 1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 1}, { 1, 2}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 1, 0}, { 2, 1}, { 2, 1} }, .offset = { 0, 1} }, // 180
{ .set = { { 1, 2}, { 0, 3}, { 0, 3}, { 0, 3} }, .reset = { { 1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { {-1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 1, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 1}, {-1, 2}, { 0, 0}, { 0, 0} }, .reset = { { 1, 0}, { 1, 1}, { 0, 2}, { 0, 2} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 1, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 1}, {-1, 2}, { 0, 0}, { 0, 0} }, .reset = { { 1, 0}, { 1, 1}, { 0, 2}, { 0, 2} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 3, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 1, 0} }, // 0
{ .set = { { 2, 0}, { 2, 1}, { 1, 2}, { 1, 2} }, .reset = { { 0, 1}, { 0, 2}, { 1, 0}, { 1, 0} }, .offset = { 1, 0} }, // 90
{ .set = { { 3, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 1, 0} }, // 180
{ .set = { { 2, 0}, { 2, 1}, { 1, 2}, { 1, 2} }, .reset = { { 0, 1}, { 0, 2}, { 1, 0}, { 1, 0} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { { 1,-1}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 2, 1}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0,-1} }, // 0
{ .set = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 2}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 0} }, // 90
{ .set = { { 1,-1}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 2, 1}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0,-1} }, // 180
{ .set = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 2}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 0} }, // 270
},
{
// rotate right
{ .set = { { 1,-1}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 2, 1}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0,-1} }, // 0
{ .set = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 2}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 0} }, // 90
{ .set = { { 1,-1}, { 0, 1}, { 0, 1}, { 0, 1} }, .reset = { { 2, 1}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0,-1} }, // 180
{ .set = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .reset = { { 0, 2}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 0} }, // 270
},
}
},
{ // S
.color = _green,
.nullRotation = 0,
.draw = { { 0, 1}, { 1, 1}, { 1, 0}, { 2, 0} },
.motion = {
{
// move down
{ .set = { { 0, 2}, { 1, 2}, { 2, 1}, { 2, 1} }, .reset = { { 0, 1}, { 1, 0}, { 2, 0}, { 2, 0} }, .offset = { 0, 1} }, // 0
{ .set = { { 0, 2}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 2}, { 1, 2}, { 2, 1}, { 2, 1} }, .reset = { { 0, 1}, { 1, 0}, { 2, 0}, { 2, 0} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 2}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 0, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { { 0, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 2, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 0}, {-1, 1}, { 0, 2}, { 0, 2} }, .reset = { { 0, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = {-1, 0} }, // 90
{ .set = { { 0, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 2, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 0}, {-1, 1}, { 0, 2}, { 0, 2} }, .reset = { { 0, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 2, 1}, { 3, 0}, { 3, 0}, { 3, 0} }, .reset = { { 1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 0
{ .set = { { 1, 0}, { 2, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 0, 1}, { 1, 2}, { 1, 2} }, .offset = { 1, 0} }, // 90
{ .set = { { 2, 1}, { 3, 0}, { 3, 0}, { 3, 0} }, .reset = { { 1, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 180
{ .set = { { 1, 0}, { 2, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 0, 1}, { 1, 2}, { 1, 2} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { { 0, 0}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-1} }, // 0
{ .set = { { 1, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 0} }, // 90
{ .set = { { 0, 0}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-1} }, // 180
{ .set = { { 1, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 0} }, // 270
},
{
// rotate right
{ .set = { { 0, 0}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-1} }, // 0
{ .set = { { 1, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 0} }, // 90
{ .set = { { 0, 0}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-1} }, // 180
{ .set = { { 1, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 0} }, // 270
},
}
},
{ // L
.color = _orange,
.nullRotation = 0,
.draw = { { 0, 0}, { 0, 1}, { 0, 2}, { 1, 2} },
.motion = {
{
// move down
{ .set = { { 0, 3}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 0, 1} }, // 0
{ .set = { { 1, 1}, { 2, 1}, { 0, 2}, { 0, 2} }, .reset = { { 0, 0}, { 1, 0}, { 2, 0}, { 2, 0} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 1}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 2}, { 1, 2}, { 2, 2}, { 2, 2} }, .reset = { { 2, 0}, { 0, 1}, { 1, 1}, { 1, 1} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { {-1, 0}, {-1, 1}, {-1, 2}, {-1, 2} }, .reset = { { 0, 0}, { 0, 1}, { 1, 2}, { 0, 0} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = {-1, 0} }, // 90
{ .set = { { 0, 1}, { 0, 2}, {-1, 0}, {-1, 0} }, .reset = { { 1, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 1}, { 1, 0}, { 1, 0}, { 1, 0} }, .reset = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 1, 0}, { 1, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 2} }, .offset = { 1, 0} }, // 0
{ .set = { { 1, 1}, { 3, 0}, { 3, 0}, { 3, 0} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 90
{ .set = { { 2, 0}, { 2, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = { 1, 0} }, // 180
{ .set = { { 3, 0}, { 3, 1}, { 3, 1}, { 3, 1} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { {-1, 2}, {-2, 2}, {-2, 2}, {-2, 2} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = {-2, 1} }, // 0
{ .set = { { 0,-1}, { 0,-2}, { 0,-2}, { 0,-2} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 0,-2} }, // 90
{ .set = { { 2, 0}, { 3, 0}, { 3, 0}, { 3, 0} }, .reset = { { 0, 0}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = { 1, 0} }, // 180
{ .set = { { 2, 2}, { 2, 3}, { 2, 3}, { 2, 3} }, .reset = { { 0, 1}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = { 1, 1} }, // 270
},
{
// rotate right
{ .set = { { 0, 3}, { 2, 2}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 0, 2} }, // 0
{ .set = { {-1, 0}, { 0, 2}, { 0, 2}, { 0, 2} }, .reset = { { 1, 0}, { 2, 0}, { 2, 0}, { 2, 0} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 0}, { 1,-1}, { 1,-1}, { 1,-1} }, .reset = { { 1, 1}, { 1, 2}, { 1, 2}, { 1, 2} }, .offset = {-1,-1} }, // 180
{ .set = { { 2,-1}, { 3, 1}, { 3, 1}, { 3, 1} }, .reset = { { 0, 1}, { 1, 1}, { 1, 1}, { 1, 1} }, .offset = { 2,-1} }, // 270
},
}
},
{ // J
.color = _blue,
.nullRotation = 0,
.draw = { { 0, 2}, { 1, 0}, { 1, 1}, { 1, 2} },
.motion = {
{
// move down
{ .set = { { 0, 3}, { 1, 3}, { 1, 3}, { 1, 3} }, .reset = { { 0, 2}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 0
{ .set = { { 0, 2}, { 1, 2}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 1, 1}, { 2, 1}, { 2, 1} }, .offset = { 0, 1} }, // 90
{ .set = { { 0, 3}, { 1, 1}, { 1, 1}, { 1, 1} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 0, 1} }, // 180
{ .set = { { 0, 1}, { 1, 1}, { 2, 2}, { 2, 2} }, .reset = { { 0, 0}, { 1, 0}, { 2, 0}, { 2, 0} }, .offset = { 0, 1} }, // 270
},
{
// move left
{ .set = { { 0, 0}, { 0, 1}, {-1, 2}, {-1, 2} }, .reset = { { 1, 0}, { 1, 1}, { 1, 2}, { 1, 2} }, .offset = {-1, 0} }, // 0
{ .set = { {-1, 0}, {-1, 1}, {-1, 1}, {-1, 1} }, .reset = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 90
{ .set = { {-1, 0}, {-1, 1}, {-1, 2}, {-1, 2} }, .reset = { { 1, 0}, { 0, 1}, { 0, 2}, { 0, 2} }, .offset = {-1, 0} }, // 180
{ .set = { {-1, 0}, { 1, 1}, { 1, 1}, { 1, 1} }, .reset = { { 2, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1, 0} }, // 270
},
{
// move right
{ .set = { { 2, 0}, { 2, 1}, { 2, 2}, { 2, 2} }, .reset = { { 1, 0}, { 1, 1}, { 0, 2}, { 0, 2} }, .offset = { 1, 0} }, // 0
{ .set = { { 1, 0}, { 3, 1}, { 3, 1}, { 3, 1} }, .reset = { { 0, 0}, { 0, 1}, { 0, 1}, { 0, 1} }, .offset = { 1, 0} }, // 90
{ .set = { { 1, 1}, { 1, 2}, { 2, 0}, { 2, 0} }, .reset = { { 0, 0}, { 0, 1}, { 0, 2}, { 0, 2} }, .offset = { 1, 0} }, // 180
{ .set = { { 3, 0}, { 3, 1}, { 3, 1}, { 3, 1} }, .reset = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = { 1, 0} }, // 270
},
{
// rotate left
{ .set = { { 1, 3}, {-1, 2}, {-1, 2}, {-1, 2} }, .reset = { { 1, 0}, { 1, 1}, { 0, 0}, { 0, 0} }, .offset = {-1, 2} }, // 0
{ .set = { {-1, 1}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 1, 1}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = {-1,-1} }, // 90
{ .set = { { 2, 0}, { 0,-1}, { 0,-1}, { 0,-1} }, .reset = { { 0, 1}, { 0, 2}, { 0, 2}, { 0, 2} }, .offset = { 0,-1} }, // 180
{ .set = { { 2, 2}, { 3, 0}, { 3, 0}, { 3, 0} }, .reset = { { 0, 0}, { 1, 0}, { 1, 0}, { 1, 0} }, .offset = { 2, 0} }, // 270
},
{
// rotate right
{ .set = { { 2, 2}, { 3, 2}, { 3, 2}, { 3, 2} }, .reset = { { 1, 0}, { 0, 2}, { 0, 2}, { 0, 2} }, .offset = { 1, 1} }, // 0
{ .set = { { 0, 2}, { 0, 3}, { 0, 3}, { 0, 3} }, .reset = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = { 0, 1} }, // 90
{ .set = { {-1, 0}, {-2, 0}, {-2, 0}, {-2, 0} }, .reset = { { 1, 0}, { 0, 2}, { 0, 2}, { 0, 2} }, .offset = {-2, 0} }, // 180
{ .set = { { 2,-1}, { 2,-2}, { 2,-2}, { 2,-2} }, .reset = { { 0, 0}, { 2, 1}, { 2, 1}, { 2, 1} }, .offset = { 1,-2} }, // 270
},
}
},
};
const orientation_t nextOrientation[5][4] = { // 5 = number of directions to move, 4 = number of orientation a stone can have
{ e_Keep, e_Keep, e_Keep, e_Keep }, // move down, current orientation: 0, 90, 180, 270
{ e_Keep, e_Keep, e_Keep, e_Keep }, // move left
{ e_Keep, e_Keep, e_Keep, e_Keep }, // move right
{ e_270, e_0, e_90, e_180 }, // rotate left
{ e_90, e_180, e_270, e_0 } // rotate right
};
stone_t stone;
shape_t nextShape;
static shape_t randomNextShape() {
return ((shape_t[]){ e_I, e_O, e_T, e_Z, e_S, e_L, e_J })[myRandGet() % e_ShapeInvalid];
}
void shapesInit() {
stone.shape = e_ShapeInvalid;
nextShape = randomNextShape();
}
void stoneCreate() {
stone.shape = nextShape;
nextShape = randomNextShape();
stone.orientation = e_0;
stone.x = 4;
stone.y = 0;
stone.locked = false;
}
void stoneLock() {
stone.locked = true;
}
uint8_t stoneIsValid() {
return stone.shape != e_ShapeInvalid;
}
// all of them return 1 in case of success and 0 in case of error
static uint8_t move(direction_t direction) {
// if this is a rotation and the shape is marked with nullRotation (just the O), do nothing
// and return success
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) &&
canvasIsPixelFree(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[1].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[1].y) &&
canvasIsPixelFree(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[2].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[2].y) &&
canvasIsPixelFree(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[3].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[3].y)) {
// if so, reset the pixels the shape moves away from
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].reset[0].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].reset[0].y,
_off);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].reset[1].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].reset[1].y,
_off);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].reset[2].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].reset[2].y,
_off);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].reset[3].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].reset[3].y,
_off);
// and set the pixels the shape moves to to the shape's color
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[0].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[0].y,
motions[stone.shape].color);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[1].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[1].y,
motions[stone.shape].color);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[2].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[2].y,
motions[stone.shape].color);
canvasSetPixel(stone.x + motions[stone.shape].motion[direction][stone.orientation].set[3].x,
stone.y + motions[stone.shape].motion[direction][stone.orientation].set[3].y,
motions[stone.shape].color);
// set the new origin of the shape
stone.x += motions[stone.shape].motion[direction][stone.orientation].offset.x;
stone.y += motions[stone.shape].motion[direction][stone.orientation].offset.y;
// set the new orientation of the shape, if required
stone.orientation = (nextOrientation[direction][stone.orientation] == e_Keep) ?
stone.orientation :
nextOrientation[direction][stone.orientation];
return 1;
}
return 0;
}
void nextStoneDraw() {
miniCanvasClear();
miniCanvasSetPixel(motions[nextShape].draw[0].x,
motions[nextShape].draw[0].y,
motions[nextShape].color);
miniCanvasSetPixel(motions[nextShape].draw[1].x,
motions[nextShape].draw[1].y,
motions[nextShape].color);
miniCanvasSetPixel(motions[nextShape].draw[2].x,
motions[nextShape].draw[2].y,
motions[nextShape].color);
miniCanvasSetPixel(motions[nextShape].draw[3].x,
motions[nextShape].draw[3].y,
motions[nextShape].color);
}
static void stoneJustDraw(uint8_t x, uint8_t y, shape_t shape) {
canvasSetPixel(x + motions[shape].draw[0].x,
y + motions[shape].draw[0].y,
motions[shape].color);
canvasSetPixel(x + motions[shape].draw[1].x,
y + motions[shape].draw[1].y,
motions[shape].color);
canvasSetPixel(x + motions[shape].draw[2].x,
y + motions[shape].draw[2].y,
motions[shape].color);
canvasSetPixel(x + motions[shape].draw[3].x,
y + motions[shape].draw[3].y,
motions[shape].color);
}
uint8_t stoneDraw() {
nextStoneDraw();
uint8_t res = 0;
// check if the pixels the shape should be drawn at are free
if (canvasIsPixelFree(stone.x + motions[stone.shape].draw[0].x,
stone.y + motions[stone.shape].draw[0].y) &&
canvasIsPixelFree(stone.x + motions[stone.shape].draw[1].x,
stone.y + motions[stone.shape].draw[1].y) &&
canvasIsPixelFree(stone.x + motions[stone.shape].draw[2].x,
stone.y + motions[stone.shape].draw[2].y) &&
canvasIsPixelFree(stone.x + motions[stone.shape].draw[3].x,
stone.y + motions[stone.shape].draw[3].y)) {
// if so, draw the shape
stoneJustDraw(stone.x, stone.y, stone.shape);
res = 1;
}
return res;
}
void stoneDrawConfigPattern() {
stoneJustDraw(1, 0, e_I);
stoneJustDraw(3, 4, e_O);
stoneJustDraw(4, 7, e_T);
stoneJustDraw(5, 10, e_Z);
stoneJustDraw(1, 12, e_S);
stoneJustDraw(5, 15, e_L);
stoneJustDraw(1, 17, e_J);
}
uint8_t stoneMoveDown() {
return move(e_MoveDown);
}
uint8_t stoneMoveLeft() {
return move(e_MoveLeft);
}
uint8_t stoneMoveRight() {
return move(e_MoveRight);
}
uint8_t stoneRotateLeft() {
return move(e_RotateLeft);
}
uint8_t stoneRotateRight() {
return move(e_RotateRight);
}

19
game-ctrl/shapes.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef _SHAPES_H_
#define _SHAPES_H_
#include <stdint.h>
void shapesInit();
void stoneCreate();
void stoneLock();
uint8_t stoneIsValid();
uint8_t stoneDraw();
uint8_t stoneMoveDown();
uint8_t stoneMoveLeft();
uint8_t stoneMoveRight();
uint8_t stoneRotateLeft();
uint8_t stoneRotateRight();
void stoneDrawConfigPattern();
#endif // _SHAPES_H_

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

@ -0,0 +1,20 @@
#include <stdint.h>
#include "sound.h"
#include "spi.h"
#include "eeprom.h"
void soundInit() {
soundCtrl(SOUND_COMMAND + SOUND_SUBCMD_AMPLITUDE + eepromReadAmplitude());
}
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_

56
game-ctrl/spi.c Normal file
View File

@ -0,0 +1,56 @@
#include <msp430g2553.h>
#include "spi.h"
void spiInit() {
// SPI in master mode, most significant bit first
UCB0CTL0 = UCCKPH | UCMST | UCMSB;
// SPI timing config
UCB0CTL1 = UCSSEL_3;
// Faster than 8 ends up in strange communication errors
// between the both MCUs.
// With 8 the transfer of a complete 110 pixel canvas takes
// about 720us.
// 8 was still too fast and caused problems.
UCB0BR0 = 16;
UCB0BR1 = 0;
// BIT5: UCB0CLK
// BIT6: UCB0SOMI
// BIT7: UCB0SIMO
P1SEL |= BIT5 | BIT6 | BIT7;
P1SEL2 |= BIT5 | BIT6 | BIT7;
// P1DIR |= BIT5 | BIT7;
// Device Select Lines: 0: Canvas, 1: Display, 2: Sound, 4: EEPROM
P1DIR |= BIT0 | BIT1 | BIT2 | BIT4;
// Disable all of them
P1OUT |= BIT0 | BIT1 | BIT2 | BIT4;
// enable SPI module
UCB0CTL1 &= ~UCSWRST;
}
void spiSendBegin(t_SpiDeviceSelector 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, BIT4 })[d];
P1OUT |= bit;
}
void spiSendOctet(uint8_t v) {
// wait for TX buffer empty
while (!(UC0IFG & UCB0TXIFG));
// load octet into TX buffer
UCB0TXBUF = v;
}
uint8_t spiReceiveOctet() {
while (!(UC0IFG & UCB0RXIFG));
uint8_t v = UCB0RXBUF;
return v;
}

17
game-ctrl/spi.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef _SPI_H_
#define _SPI_H_
#include <stdint.h>
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();
#endif // _SPI_H_

70
readme.md Normal file
View File

@ -0,0 +1,70 @@
# 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)
Details are here https://gitea.hottis.de/wn/tetris/src/branch/main/rgb-driver/readme.md
## 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)

36
rgb-driver/Makefile Normal file
View File

@ -0,0 +1,36 @@
TOOLCHAIN_PREFIX=/opt/msp430-gcc
CC=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-gcc
OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
MCU=msp430g2553
ARTIFACT=firmware
COMMON=-Wall -mmcu=$(MCU) -std=gnu99 -I $(TOOLCHAIN_PREFIX)/include -Os -g0 -fdata-sections -ffunction-sections
#COMMON+= -ggdb -gdwarf-2 # debug
CFLAGS=$(COMMON)
ASFLAGS=$(COMMON) -D__ASSEMBLER__
LDFLAGS=-L $(TOOLCHAIN_PREFIX)/include -Wl,-Map,firmware.map -nostdlib -nostartfiles -T $(MCU).ld
$(ARTIFACT).elf: colors.o main.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
.PHONY: clean
clean:
-rm -f *.o *.elf *.map *.txt
.PHONY: upload
upload: $(ARTIFACT).elf
mspdebug rf2500 "prog $(ARTIFACT).elf"

10
rgb-driver/canvasSize.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _CANVAS_SIZE_H_
#define _CANVAS_SIZE_H_
#define CANVAS_WIDTH 10
#define CANVAS_HEIGHT 20
#define MINI_CANVAS_WIDTH 3
#define MINI_CANVAS_HEIGHT 4
#endif // _CANVAS_SIZE_H_

55
rgb-driver/colors.S Normal file
View File

@ -0,0 +1,55 @@
#include "colors.h"
.section ".rodata","a"
;; color definitions according to
;; https://learn.sparkfun.com/tutorials/lilypad-protosnap-plus-activity-guide/3-custom-color-mixing
colors:
.global colors
;; red, green, blue, padding
.byte 0x00, 0x00, 0x00, 0 ;; off
.byte 0x00>>5, 0x00>>5, 0xff>>5, 0 ;; blue
.byte 0x00>>5, 0xff>>5, 0x00>>5, 0 ;; green
.byte 0xff>>5, 0x80>>5, 0x00>>5, 0 ;; orange
.byte 0x80>>5, 0x00>>5, 0xff>>5, 0 ;; violet
.byte 0x00>>5, 0xff>>5, 0xff>>5, 0 ;; cyan
.byte 0xff>>5, 0xff>>5, 0x00>>5, 0 ;; yellow
.byte 0xff>>5, 0x00>>5, 0x00>>5, 0 ;; red
.byte 0xff>>5, 0xff>>5, 0xff>>5, 0 ;; white
.byte 0x00>>4, 0x00>>4, 0xff>>4, 0 ;; blue
.byte 0x00>>4, 0xff>>4, 0x00>>4, 0 ;; green
.byte 0xff>>4, 0x80>>4, 0x00>>4, 0 ;; orange
.byte 0x80>>4, 0x00>>4, 0xff>>4, 0 ;; violet
.byte 0x00>>4, 0xff>>4, 0xff>>4, 0 ;; cyan
.byte 0xff>>4, 0xff>>4, 0x00>>4, 0 ;; yellow
.byte 0xff>>4, 0x00>>4, 0x00>>4, 0 ;; red
.byte 0xff>>4, 0xff>>4, 0xff>>4, 0 ;; white
.byte 0x00>>3, 0x00>>3, 0xff>>3, 0 ;; blue
.byte 0x00>>3, 0xff>>3, 0x00>>3, 0 ;; green
.byte 0xff>>3, 0x80>>3, 0x00>>3, 0 ;; orange
.byte 0x80>>3, 0x00>>3, 0xff>>3, 0 ;; violet
.byte 0x00>>3, 0xff>>3, 0xff>>3, 0 ;; cyan
.byte 0xff>>3, 0xff>>3, 0x00>>3, 0 ;; yellow
.byte 0xff>>3, 0x00>>3, 0x00>>3, 0 ;; red
.byte 0xff>>3, 0xff>>3, 0xff>>3, 0 ;; white
.byte 0x00>>2, 0x00>>2, 0xff>>2, 0 ;; blue
.byte 0x00>>2, 0xff>>2, 0x00>>2, 0 ;; green
.byte 0xff>>2, 0x80>>2, 0x00>>2, 0 ;; orange
.byte 0x80>>2, 0x00>>2, 0xff>>2, 0 ;; violet
.byte 0x00>>2, 0xff>>2, 0xff>>2, 0 ;; cyan
.byte 0xff>>2, 0xff>>2, 0x00>>2, 0 ;; yellow
.byte 0xff>>2, 0x00>>2, 0x00>>2, 0 ;; red
.byte 0xff>>2, 0xff>>2, 0xff>>2, 0 ;; white
.byte 0x00>>1, 0x00>>1, 0xff>>1, 0 ;; blue
.byte 0x00>>1, 0xff>>1, 0x00>>1, 0 ;; green
.byte 0xff>>1, 0x80>>1, 0x00>>1, 0 ;; orange
.byte 0x80>>1, 0x00>>1, 0xff>>1, 0 ;; violet
.byte 0x00>>1, 0xff>>1, 0xff>>1, 0 ;; cyan
.byte 0xff>>1, 0xff>>1, 0x00>>1, 0 ;; yellow
.byte 0xff>>1, 0x00>>1, 0x00>>1, 0 ;; red
.byte 0xff>>1, 0xff>>1, 0xff>>1, 0 ;; white

21
rgb-driver/colors.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef _COLORS_H_
#define _COLORS_H_
#define _off 0
#define _blue 1
#define _green 2
#define _orange 3
#define _violet 4
#define _cyan 5
#define _yellow 6
#define _red 7
#define _white 8
#define _brightness_offset 8
#define _brightness_shifts 5
#define _color_end (_brightness_offset * _brightness_shifts)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
rgb-driver/docs/timing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

334
rgb-driver/main.S Normal file
View File

@ -0,0 +1,334 @@
#include <msp430g2553.h>
#include "colors.h"
#include "canvasSize.h"
#define PC r0
#define SP r1
#define SR r2
;; ----------------------------------------------------
;; --- r4, r5 and r6 must not be used for any other ---
;; --- purpose ---
;; required for communication between drawscreen and isr
#define SIGNAL_REGISTER r4
#define SIGNAL_OCTET_DONE 0x01
#define SIGNAL_ISR_ENABLE 0x02
#define SIGNAL_ALL_DATA_DONE 0x04
#define SIGNAL_INIT_VALUE SIGNAL_OCTET_DONE
;; required for handover of data between drawscreen and isr
#define DATA_REGISTER r5
;; required for sequencing of isr
#define BIT_COUNTER_REGISTER r6
#define BIT_COUNTER_INIT_VALUE 0x01
;; ----------------------------------------------------
;; 2.48us
#define TIMER_COMPLETE 45
;; 1.18us
#define TIMER_LONG 22
;; 550ns
#define TIMER_SHORT 10
.macro set_data_bit
bis #BIT0, &P1OUT
.endm
.macro clear_data_bit
bic #BIT0, &P1OUT
.endm
.macro set_output_enable
bis #BIT1, &P1OUT
.endm
.macro clear_output_enable
bic #BIT1, &P1OUT
.endm
.macro set_debug
bis #BIT2, &P1OUT
.endm
.macro clear_debug
bic #BIT2, &P1OUT
.endm
.macro set_signal_waiting_for_data
bis #BIT3, &P1OUT
.endm
.macro clear_signal_waiting_for_data
bic #BIT3, &P1OUT
.endm
.section ".data"
screendata:
.rept (CANVAS_HEIGHT*CANVAS_WIDTH) + (MINI_CANVAS_HEIGHT*MINI_CANVAS_WIDTH) ;; number of leds in hardward
.byte 0
.endr
screendataend:
.byte 0xff
;; .text is the name of the section, it is a hint for the linker to
;; allocate the section
;; ax: a means allocatable by linker, x means executable
;; @progbits is a hint for the linker to allocate this section into
;; program memory (flash)
.section ".text","ax",@progbits
;; ----------------------------------------------------
_start:
;; disable watchdog
mov.w #WDTPW|WDTHOLD,&WDTCTL
;; configure clock system to the highest frequency
mov.b #DCO0|DCO1|DCO2,&DCOCTL
mov.b #XT2OFF|RSEL0|RSEL1|RSEL2|RSEL3,&BCSCTL1
mov.b #0,&BCSCTL2
mov.b #0,&BCSCTL3
;; initialize stack pointer with value from linker
mov.w #__stack, SP
init:
;; configuration of GPIO Ports
;; BIT0: data bit
;; BIT1: output enable
;; BIT2: debug
;; BIT3: Signal waiting for data
mov.b #BIT0|BIT1|BIT2|BIT3, &P1DIR
mov.b #0,&P1OUT
;; BIT4: spi, UCB0STE
;; BIT5: spi, UCB0CLK
;; BIT6: spi, UCB0SOMI
;; BIT7: spi, UCB0SIMO
mov.b #BIT4|BIT5|BIT6|BIT7, &P1SEL
mov.b #BIT4|BIT5|BIT6|BIT7, &P1SEL2
;; BIT4: long pulse
;; BIT1: short pulse
mov.b #BIT1|BIT4,&P2DIR
mov.b #BIT1|BIT4,&P2SEL
;; timer configuration
;; configure and stop timer
;; cycle time is 56.25ns
mov.w #ID_0|MC_0|TACLR|TASSEL_2,&TA1CTL
;; 2.0us
mov.w #TIMER_COMPLETE,&TA1CCR0
;; a bit less
mov.w #TIMER_SHORT,&TA1CCR1
mov.w #TIMER_LONG,&TA1CCR2
;; configure output mode for TA0.1
mov.w #CCIE,&TA1CCTL0
mov.w #OUTMOD_7,&TA1CCTL1
mov.w #OUTMOD_7,&TA1CCTL2
;; spi configuration
;; USCI B to slave mode, enable STE and most significant bit first
mov.b #UCCKPH|UCSYNC|UCMODE_2|UCMSB, &UCB0CTL0
mov.b #0x00, &UCB0CTL1
;; make sure the isr will not immediately start
mov.b #SIGNAL_INIT_VALUE, SIGNAL_REGISTER
;; start timer in up mode
bis.w #MC0,&TA1CTL
;; enable interrupts
eint
;; ----------------------------------------------------
mainloop:
call #resetscreen
mainloop_draw:
call #drawscreen
set_signal_waiting_for_data
call #receivedata
clear_signal_waiting_for_data
jmp mainloop_draw
;; ----------------------------------------------------
resetscreen:
push r7
push r8
;; reset screen data
mov.w #screendata, r7
mov.w #screendataend, r8
resetscreen_continue:
mov.b #_off, @r7
inc.w r7
cmp.w r7, r8
jnz resetscreen_continue
pop r8
pop r7
ret
;; ----------------------------------------------------
receivedata:
push r9
push r10
;; wait for first octet
receivedata_wait_for_control_octet:
bit #UCB0RXIFG, &UC0IFG
jz receivedata_wait_for_control_octet
;; get control or address octet from buffer register
mov.b UCB0RXBUF, r9
;; check whether value == 0xfe (no more data)
cmp.b #0xfe, r9
;; no more data
jz receivedata_end
;; it is an address octet
receivedata_wait_for_octet:
bit #UCB0RXIFG, &UC0IFG
jz receivedata_wait_for_octet
;; get data octet from buffer register
mov.b UCB0RXBUF, r10
;; move it to the destination
mov.b r10, screendata(r9)
;; next address/control octet
jmp receivedata_wait_for_control_octet
receivedata_end:
pop r10
pop r9
ret
;; ----------------------------------------------------
drawscreen:
push r7
push r8
push r9
#define DATA_NEXT_ADDRESS_REGISTER r7
#define DATA_END_ADDRESS_REGISTER r8
#define NEXT_DATA_REGISTER r9
;; initialize bit-counter for isr
mov.b #BIT_COUNTER_INIT_VALUE, BIT_COUNTER_REGISTER
;; initialize isr-sync register, signal BYTE_DONE for the first start
mov.b #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
;; screen data start/next into r7
mov.w #screendata, DATA_NEXT_ADDRESS_REGISTER
;; screen data end into r8
mov.w #screendataend, DATA_END_ADDRESS_REGISTER
drawscreen_continue:
;; prepare next byte to handle by isr
cmp.w DATA_NEXT_ADDRESS_REGISTER, DATA_END_ADDRESS_REGISTER
jz drawscreen_data_done
;; load next data byte
mov.b @DATA_NEXT_ADDRESS_REGISTER, NEXT_DATA_REGISTER
inc.w DATA_NEXT_ADDRESS_REGISTER
;; multiple color code by four to get color data
rla.b NEXT_DATA_REGISTER
rla.b NEXT_DATA_REGISTER
;; enable isr
bis #SIGNAL_ISR_ENABLE, SIGNAL_REGISTER
drawscreen_wait_for_isr_0:
;; check bit0 in sync register
bit #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
jz drawscreen_wait_for_isr_0
;; load data
mov.b colors(NEXT_DATA_REGISTER), DATA_REGISTER
;; clear BYTE_DONE
bic #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
drawscreen_wait_for_isr_1:
;; check bit0 in sync register
bit #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
jz drawscreen_wait_for_isr_1
;; load data
mov.b colors+1(NEXT_DATA_REGISTER), DATA_REGISTER
;; clear BYTE_DONE
bic #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
drawscreen_wait_for_isr_2:
;; check bit0 in sync register
bit #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
jz drawscreen_wait_for_isr_2
;; load data
mov.b colors+2(NEXT_DATA_REGISTER), DATA_REGISTER
;; clear BYTE_DONE
bic #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
;; continue
jmp drawscreen_continue
drawscreen_data_done:
;; signal all data processed, isr finish
bis #SIGNAL_ALL_DATA_DONE, SIGNAL_REGISTER
pop r9
pop r8
pop r7
ret
;; ----------------------------------------------------
; --- timer isr ---
;; r6: exclusively used by isr as bit-counter
shifter_isr:
;; check isr enable bit
bit #SIGNAL_ISR_ENABLE, SIGNAL_REGISTER
jz shifter_isr_exit
;; shift msb of data register r5 into carry flag and set or reset P1.0 accordingly
rla.b DATA_REGISTER
jnc shifter_isr_false_bit
set_data_bit
jmp shifter_isr_end
shifter_isr_false_bit:
clear_data_bit
shifter_isr_end:
;; enable output
set_output_enable
;; roll bit-counter
rla.b BIT_COUNTER_REGISTER
jnc shifter_isr_exit
;; reset bit-counter
mov.b #BIT_COUNTER_INIT_VALUE, BIT_COUNTER_REGISTER
;; signal byte done
bis #SIGNAL_OCTET_DONE, SIGNAL_REGISTER
;; check whether all data are processed
bit #SIGNAL_ALL_DATA_DONE, SIGNAL_REGISTER
jz shifter_isr_exit
;; disable isr
bic #SIGNAL_ISR_ENABLE, SIGNAL_REGISTER
;; disable output
clear_output_enable
shifter_isr_exit:
reti
;; ----------------------------------------------------
.section "__interrupt_vector_14","ax",@progbits
.word shifter_isr
;; .resetvec comes from linker
.section ".resetvec","ax",@progbits
.word _start
.end

128
rgb-driver/readme.md Normal file
View File

@ -0,0 +1,128 @@
## Debugging
```
mspdebug rf2500 gdb
msp430-gdb -x firmware.gdb
```
Attention: the gdb in the TI toolchain package is broken, use the one from Debian
## Signals Working Cycler
These signals are related to code under tag `cycler_works_include_output_stage`.
First octets:
![](./docs/cycler_working_first_octets.png)
Last octets:
![](./docs/cycler_working_last_octets.png)
Schematics and legend for signals:
![](./docs/schematics.jpeg)
### Some more explanations
Consider above schematics and the screen shot "Last octets" from the oscilloscope.
![](./docs/timing.png)
Timer TA1 is running in "up mode" to the value 45 set in compare register `TA1CCR0`. The compare registers `TA1CCR1` is set to 10, `TA1CCR2` is set to 22.
The output mode of the timer is set to "Reset/Set", which means the GPIO associated with `TA1CCR1` (P2.1) and `TA1CCR2` (P2.4) are set at the overflow and
restart of the counter and reset when the counter matches the associated compare value.
So, on P2.1 (D1 on the oscilloscope) we have a long pulse and at P2.4 (D0 on the oscilloscope) we have a short pulse, with synchronous raising edge.
![](./docs/74hc74-function-table.png)
The inverted signal P2.4 is connected to the Clock input of a 74HC74 D-flipflop, the data input of the flipflop is connected to GPIO P1.0 (D2 on the oscilloscope).
The interrupt service routine `shifter_isr` is triggered by the overflow and restart of the timer, this interrupt service routine provides the next bit to be
signaled on P1.0. This bit is stored at the falling edge of P2.4 (long pulse) in the flipflop.
The short pulse (P2.1, D1) is ANDed using a 74HC08 with the inverted output of the flipflop, the long pulse (P2.4, D0) is ANDed with the non-inverted output of
the flipflop, the ANDed results are ORed using a 74HC32.
So, at the output of the OR gate (yellow on the oscilloscope) we get a long pulse for a 1 at P1.0 provided by the ISR and a short pulse for a 0 at P1.0.
The routine `drawscreen` takes color values from the "frame buffer" beginning at `screendata` and translated them into the red, green and blue values and provides these values, first red, then green and finally blue to the ISR via the `DATA_REGISTER`.
The ISR cycles over the `DATA_REGISTER` and presents the bits at P1.0.
Additionally, when the first bit of a full draw screen cycle is presented at P1.0 by the ISR, it also sets the data enable signal at P1.1 and when the last bit has been provided it disabled the data enable signal. This signal is also synchronized using a flipflop and used to enable the short/long pulses using an AND gate.
## Timing
Complete cycle: 2.48us
![](./docs/pulse_complete.png)
Short pulse: 550ns
![](./docs/pulse_short.png)
Long pulse: 1.18us
![](./docs/pulse_long.png)
## Load Time
During of loading data into five LEDs: 297us
![](./docs/five_leds.png)
During of loading data into six LEDs: 297us
![](./docs/six_leds.png)
| # of LEDs | Load Time measured | calculated |
| --------- | ------------------ | ---------- |
| 5 | 297us | |
| 6 | 354us | 356.4us |
| 10 | | 594us |
| 100 | | 5.9ms |
| 200 | | 11.8ms |
## Reset Circuitry
It appears that the output voltage of the power supply raises that slow, that the MCU
will not handle the reset correctly.
The following circuitry should generate a valid reset signal far enough from the raise
of the supply voltage:
![](./docs/reset-circuit.jpeg)
The circuit generates the following signals:
![](./docs/reset-signal.png)
#### Reference voltage (green):
```math
U_ref = 3.3V \frac{22k\Omega}{22k\Omega + 10k\Omega} = 2.2V
```
#### Trigger voltage (purple):
```math
U_trigg = 3.3V \frac{330k\Omega}{330k\Omega + 82k\Omega} = 2.64V
```
#### RC constant:
```math
\tau = 82k\Omega \cdot 100nF = 8.2ms
```

35
sound-driver/Makefile Normal file
View File

@ -0,0 +1,35 @@
TOOLCHAIN_PREFIX=/opt/msp430-gcc
CC=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-gcc
OBJDUMP=$(TOOLCHAIN_PREFIX)/bin/msp430-elf-objdump
ARTIFACT=firmware
MCU=msp430g2553
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 spi_init.o sequencer.o melody_tetris.o melody_tusch1.o psg.o mute.o melody_pling.o config.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
.PHONY: clean
clean:
-rm -f *.o $(ARTIFACT).elf $(ARTIFACT).txt
.PHONY: upload
upload: $(ARTIFACT).elf
mspdebug rf2500 "prog $(ARTIFACT).elf"

26
sound-driver/config.c Normal file
View File

@ -0,0 +1,26 @@
#include <stdint.h>
#include <sys/param.h>
#include "config.h"
typedef struct {
uint8_t melodyAmplitude;
uint8_t effectsAmplitude;
} config_t;
config_t config;
void configSetAmplitude(uint8_t v) {
config.melodyAmplitude = MIN(v, 15);
config.effectsAmplitude = MIN(v+4, 15);
}
uint8_t *configGetMelodyAmplitudePtr() {
return &(config.melodyAmplitude);
}
uint8_t *configGetEffectsAmplitudePtr() {
return &(config.effectsAmplitude);
}

12
sound-driver/config.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include <stdint.h>
void configSetAmplitude(uint8_t v);
uint8_t *configGetMelodyAmplitudePtr();
uint8_t *configGetEffectsAmplitudePtr();
#endif // _CONFIG_H_

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

BIN
sound-driver/docs/Oktavskala.pdf Executable file

Binary file not shown.

BIN
sound-driver/docs/Registers.pdf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
target remote localhost:2000
file firmware.elf

38
sound-driver/main.c Normal file
View File

@ -0,0 +1,38 @@
#include <msp430g2553.h>
#include <stdint.h>
#include <stdlib.h>
#include "spi.h"
#include "psg.h"
#include "scheduler.h"
#include "sequencer.h"
#include "melody_tetris.h"
#include "melody_tusch1.h"
#include "mute.h"
int main() {
WDTCTL = WDTPW | WDTHOLD;
__disable_interrupt();
// highest possible system clock
DCOCTL = DCO0 | DCO1 | DCO2;
BCSCTL1 = XT2OFF | RSEL0 | RSEL1 | RSEL2 | RSEL3;
BCSCTL2 = 0;
BCSCTL3 = 0;
schInit();
spiInit();
psgInit();
muteInit();
sequencerInit();
__enable_interrupt();
while (1) {
schExec();
}
}

Some files were not shown because too many files have changed in this diff Show More