diff --git a/README.md b/README.md index 90ae2f2..1694517 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,12 @@ The MiSTeryNano is a work in progress. Current features are: * Mapped to USB via BL616 MCU * Mouse and keyboard via USB * Joystick via IO pins of Tang Nano - * Floppy disk image stored on regular FAT/exFAT formatted SD card + * Floppy disk images + * Read support for drive A: and drive B: + * Images stored on regular FAT/exFAT formatted SD card ## Missing features - * Support for floppy drive B * Support for hard disk images * Floppy disk write support diff --git a/bl616/misterynano_fw/SPI.md b/bl616/misterynano_fw/SPI.md index d8efe6a..bc8139b 100644 --- a/bl616/misterynano_fw/SPI.md +++ b/bl616/misterynano_fw/SPI.md @@ -152,12 +152,13 @@ The SDC target supports the following commands: The ```SPI_SDC_STATUS``` is used to poll the SD card status. The first byte returned is a generic status byte indicating whether the card -could be initialized and the card type (SDv1, SDv2, SDHC). The lower -two bits are request bytes which are set by the core if it needs to -read a sector. In the Atari ST core bit 0 indicates a request for -floppy A: and bit 1 for floppy B: (currently only floppy A is -implemented). The following four bytes contain the sector number the -core wants to read. +could be initialized and the card type (SDv1, SDv2, SDHC). Bit 1 of +the status byte indicates whether the SD card is currenly busy reading +or writing data. The second byte contains core requests. Currently the +lower two bits are used by the core to request sector data. In the +Atari ST core bit 0 indicates a request for floppy A: and bit 1 for +floppy B:. The following four bytes contain the sector number the core +wants to read. The ```SPI_SDC_READ``` requests the core to read a sector from SD card and use it for it's own purposes. The command is followed by four @@ -179,10 +180,10 @@ data is not visible to the core. Instead it used by the MCU to display the contents of the SD card and to determine where data is stored inside the images files stored on the SD card. -The ```SPI_SDC_INSERTED``` command and the following four data bytes -are used to inform the core about the size of the selected disk -image. This is needed to translate between the track/sector/side +The ```SPI_SDC_INSERTED``` command and the following five data bytes +are used to inform the core about drive and the size of the selected +disk image. This is needed to translate between the track/sector/side values typically used when reading from floppy disk into sector -offsets into the image file. A size of 0 indicates that no image -is currently selected. The core should then behave as if e.g. no -floppy disk is inserted. \ No newline at end of file +offsets into the image file. A size of 0 indicates that no image is +currently selected. The core should then behave as if e.g. no floppy +disk is inserted. \ No newline at end of file diff --git a/bl616/misterynano_fw/fatfs_conf_user.h b/bl616/misterynano_fw/fatfs_conf_user.h index a1addd7..8cf6311 100644 --- a/bl616/misterynano_fw/fatfs_conf_user.h +++ b/bl616/misterynano_fw/fatfs_conf_user.h @@ -51,7 +51,7 @@ All configuration items must be included in the file */ #define FF_USE_FORWARD 0 /* This option switches f_forward() function. (0:Disable or 1:Enable) */ -#define FF_USE_STRFUNC 0 +#define FF_USE_STRFUNC 1 #define FF_PRINT_LLI 1 #define FF_PRINT_FLOAT 1 #define FF_STRF_ENCODE 3 diff --git a/bl616/misterynano_fw/font_helvR08_te.c b/bl616/misterynano_fw/font_helvR08_te.c new file mode 100644 index 0000000..b1ff415 --- /dev/null +++ b/bl616/misterynano_fw/font_helvR08_te.c @@ -0,0 +1,185 @@ +/* + Fontname: -Adobe-Helvetica-Medium-R-Normal--11-80-100-100-P-56-ISO10646-1 + Copyright: Copyright (c) 1984, 1987 Adobe Systems Incorporated. All Rights Reserved. Copyright (c) 1988, 1991 Digital Equipment Corporation. All Rights Reserved. + Glyphs: 450/756 + BBX Build Mode: 0 +*/ +const uint8_t font_helvR08_te[5694] U8G2_FONT_SECTION("u8g2_font_helvR08_te") = + "\302\0\3\2\4\4\2\4\5\14\22\376\374\10\376\10\376\1S\2\275\10g \5\0\346\4!\6\201\242" + "\304%\42\7#\372D\242\4#\20v\242U\222%\321 %\311 %Y\22\1$\20\245\236U\266T" + "\224lK\242D\251l\21\0%\20\210b\216\324\222)y\234'Z\22\225\24\0&\15\206\342\215\26\265" + "iQ\242%\221)'\6!\272\204\0(\13\243\32U\22%Q[\224\5)\14\243\332D\26eQK" + "\224D\0*\7\63\66E\322\1+\12U\246U\30\15R\30\1,\7\62\332L\242\0-\6\23.\305" + "\0.\6\21\242D\0/\12\203\342T%j\211\42\0\60\12\205\242\315\222\371\226,\0\61\7\202b\315" + "\322\7\62\14\205\242\315\222\205%)\13\7\1\63\14\205\242\315\222\205\221\32j\311\2\64\16\205\242]&" + "%QRJ\6-L\0\65\14\205\242\315\220\204\341\32j\311\2\66\14\205\242\315\222\211C\222\331\222\5" + "\67\13\205\242\305 f\305,\254\1\70\14\205\242\315\222i\311\222\331\222\5\71\14\205\242\315\222\331\222!" + "\324\222\5:\6a\242D\30;\11\202\332L\16%\12\0<\7SfUR+=\10\64j\305\20\16" + "\1>\10SfEV)\1\77\13\204b\215\22eQ-\217\0@\26\232\336\336 \246Q\244$RI" + "\311\42%\252D\212\226c\203\6A\16\207\342]\234&a\222\225\6%U\3B\15\205\242\305\220d\332" + "\240d\266A\1C\14\206\342\315\220\204j\307dH\0D\15\206\342\305\20eI\350-\31\42\0E\12" + "\205\242\305\61\34\306\342 F\12\205b\305\61\34\222\260\21G\14\206\342\315\220\204jm\24\223AH\12" + "\206\342Eh\34\6\321\61I\6\201\242\304AJ\11\204b]oR\242\0K\15\205\242E&%\245\251" + "\22%\231\26L\10\204bE\326\267!M\20\207\42F\272\15\331RQ*R$ER\1N\17\206\342" + "\205\266)\221\22I\211\224h\233\0O\13\206\342\315\220\204>&C\2P\14\205\242\305\220d\332\240\204" + "\215\0Q\22\227\336\315\20\205I\230\204I\230DIM\32\364\0R\14\205\242\305\220d\332\240dn\1" + "S\14\205\242\315\222\251\253\246%\13\0T\11\205b\305 \205}\2U\12\206\342E\350\307dH\0V" + "\17\207\342E\252&Y\224EY%L\322\14W\20\211bF\226i\225\250/m\305,\314\42\0X\15" + "\205\242E\246%\265J\224\324\264\0Y\16\207\342E\232dQV\11\223\64\256\1Z\13\205\242\305 f" + "\305\254\70\10[\10\242\232\304\322/\2\134\12\203\342D\224EmQ\0]\10\242\332\204\322/\3^\12" + "U\256U\230%QR\13_\6\26\232\305\1`\6\42\376D\24a\14eb\305\32-Q\22EJ\0" + "b\14\205\242EX\61i\332\244(\0c\12db\215\22i\245D\1d\12\205\242e\305\264\231\24%" + "e\13db\215\22\15[\224(\0f\12\204\42\225\22MY'\0g\14\205\232\215i\63)J\230," + "\0h\12\205\242EX\61in\1i\7\201\242D\62\14j\11\242\231L\226\364\242\0k\14\204bE" + "VR\22))I\1l\6\201\242\304Am\16g\42\306\242D\221\24I\221\24I\5n\11e\242E" + "b\322\334\2o\12e\242\315\222\271%\13\0p\14\205\232Eb\322\264IQ\302\20q\12\205\232\215i" + "\63)JXr\11c\42E\262DM\0s\13db\215\22%\242\224(\0t\12\203\42M\224,Q" + "\223\0u\10dbE\344)\31v\14e\242E\246%\245$\13#\0w\16g\42F\24I\225\246\244" + "\226\204I\4x\12e\242E\226\324*\65-y\16\205YM\224DI)\311\264\60\322\0z\12db" + "\305\220EmC\0{\12\243\332T\22U\262\250-|\7\241\232\304C\0}\13\243\332D\26\325\222\250" + "%\2~\7&\356\215d\1\240\5\0\346\4\241\7\201\232D\62\14\242\16\205^]\264T\224(\211\222" + "\312\224\1\243\14\205\242\225T\311\266\260\244(\0\244\14dfE\224(\221\224(Q\0\245\15\205\242E" + "fK*\203\24\15R\4\246\10\241\232\304\20\15\1\247\17\245\232\315\222\215K\64D\311\270%\13\0\250" + "\6\23\376D\22\251\16w\42\326V\211\24%\223\224Z\66\1\252\10S.\305\244d\3\253\12U\242U" + "\322[\222%\1\254\7\65\252\305 \26\255\6\23.\305\0\256\15w\42\326VI\26\223\322-\233\0\257" + "\6\23\376\304\0\260\11D\62\215\22I\211\2\261\13u\242U\30\15R\230\17\2\262\7C\362\314R\31" + "\263\10C\362\304\222-\0\264\6\42\376\214\2\265\12\204ZE\344i\330\62\0\266\25\246\232\315\60(\311" + "\222,\221\222%Y\222%Y\222%\11\0\267\6\22\356\204\0\270\6\42\332\314\0\271\6B\362\314R\272" + "\11S.\305\220\14\331\0\273\13U\242E\222%Y\322\23\0\274\22\211bN\226ia\224Fy\26F" + "Z\64Da\2\275\20\210bN\26i\245\60\212#-)e\245\1\276\22\211b\306\236\245Q&\345H" + "\24Fb\62Da\2\277\13\204ZU\236E\265(Q\0\300\20\267\342U\236\303\325$L\262\322\240\244" + "j\0\301\20\267\342e\232\303\325$L\262\322\240\244j\0\302\21\267\342]\232\344h\234&a\222\225\6" + "%U\3\303\22\267\342]\222%\71\32\247I\230d\245AI\325\0\304\20\247\342U\222\243q\232\204I" + "V\32\224T\15\305\20\267\342]\232\244q\65\11\223\254\64(\251\32\306\22\211\242\336\260\311I\234\14J" + "\224\16a\26f\203\0\307\15\246\332\315\220\204j\307d\10C\11\310\14\265\242M\232\17c\70\214\305A" + "\311\14\265\242]}\30\303a,\16\2\312\15\265\242U\226\304\303\30\16cq\20\313\14\245\242My\30" + "\303a,\16\2\314\11\262\342D\224%\375\0\315\11\262\242\214\226\364\13\0\316\11\263\342Lc\324/\0" + "\317\11\243\342D\22F\375\2\320\21\207\42\316\220\225\302AJ\302$L\262h\210\0\321\22\266\342U\22" + "%\71\242mJ\244DR\42%\332&\322\16\266\342U\234CC\22\372\230\14\11\0\323\15\266\342e\35" + "\32\222\320\307dH\0\324\16\266\342U\230\344\320\220\204>&C\2\325\17\266\342U\22%\71\64$\241" + "\217\311\220\0\326\16\246\342M\224#C\22\372\230\14\11\0\327\12U\246E\226\324*\265\0\330\22\246\336" + "m\62$\331\224H\211\22)\321\226\14I\12\331\14\266\342U\234#\241\37\223!\1\332\14\266\342e\35" + "\11\375\230\14\11\0\333\15\266\342U\230\344H\350\307dH\0\334\13\246\342M\224\207~L\206\4\335\20" + "\267\342e\232ci\222EY%L\322\270\6\336\15\205\242E\30\16I\246\15J\30\2\337\13\204b\215" + "\22YJ.\11\0\340\15\225bM\232\257\321\22%Q\244\4\341\16\225bU\226#k\264DI\24)" + "\1\342\16\225bU\226\304k\264DI\24)\1\343\15\225bM\367\65Z\242$\212\224\0\344\14\205b" + "My\215\226(\211\42%\345\17\225bU\226d\321\32-Q\22EJ\0\346\16g\42\306\42F\311\60" + "DYT\261\0\347\14\204Z\215\22i\245D\213\24\0\350\14\224bM\30+\321\260E\211\2\351\14\224" + "bU]\211\206-J\24\0\352\15\224bM\224\304J\64lQ\242\0\353\14\204bE\22+\321\260E" + "\211\2\354\10\222\241D\224%=\355\10\222\242\214\226\364\2\356\11\223\241Lc\324\13\0\357\11\203\241D" + "\22F\275\0\360\15\225\242M\70H\321\240\271%\13\0\361\12\224bM\242\244K\344)\362\14\225\242M" + "\232#K\346\226,\0\363\13\225\242]\35Y\62\267d\1\364\14\225\242U\226\344K\346\226,\0\365\13" + "\225\242U\322\276dn\311\2\366\12\205\242M}\311\334\222\5\367\11U\246U>\350\21\0\370\15g\241" + "\325R\222\222\222TK\222\11\371\12\224bM\230F\236\222\1\372\12\224b]\224F\236\222\1\373\12\224" + "bM\224\244\221\247d\374\12\204bE\222F\236\222\1\375\17\265Ye\226GI\224\224\222L\13#\15" + "\376\15\245\232EX\61i\332\244(a\10\377\17\245YM=J\242\244\224dZ\30i\0\0\0\0\10" + "\1d\5B\377\377\1\0\20\247\342\325\216\306i\22&YiPR\65\1\1\15\205b\315\274FK\224" + "D\221\22\1\2\21\267\342UQG\343\64\11\223\254\64(\251\32\1\3\17\225bE\24\351k\264DI" + "\24)\1\1\4\21\247\332]\234&a\222\225\6%U\263X\2\1\5\17\205Z\305\32-Q\22EJ" + "\24*\0\1\6\17\266\342]\230cC\22\252\35\223!\1\1\7\14\224bU]\211\264R\242\0\1\10" + "\20\266\342U\230\344\320\220\204j\307dH\0\1\11\15\224bU%U\42\255\224(\0\1\12\16\246\342" + "U\216\15I\250vL\206\4\1\13\14\204bU\254DZ)Q\0\1\14\17\266\342M\61\307\206$T" + ";&C\2\1\15\15\224bM)V\42\255\224(\0\1\16\20\266\342M\61\207\206(KBo\311\20" + "\1\1\17\23\211bf\26f\211\22%\221\230\205Y\30\251J\10\1\20\22\207\42\316\220\225\302AJ\302" + "$L\262h\210\0\1\21\14\205\242e\264\230\66\223\242\4\1\22\15\245\242\315<\214\341\60\26\7\1\1" + "\23\15\204b\315\252D\303\26%\12\0\1\24\16\265\242E\24\351\303\30\16cq\20\1\25\16\224bE" + "\224\310J\64lQ\242\0\1\26\15\245\242U>\214\341\60\26\7\1\1\27\15\204bU\254D\303\26%" + "\12\0\1\30\16\245\232\305\61\34\306\342\240\204\22\0\1\31\15\204Z\215\22\15[\224H\231\2\1\32\16" + "\265\242M-\37\306p\30\213\203\0\1\33\15\224bM)V\242a\213\22\5\1\34\20\266\342U\230\344" + "\320\220\204jm\24\223A\1\35\20\265\232U\226\344\212\264\231\24%L\26\0\1\36\20\266\342M\224\351" + "\320\220\204jm\24\223A\1\37\20\265\232M\24\351\212\264\231\24%L\26\0\1 \17\246\342U\216\15" + "I\250\326F\61\31\4\1!\16\245\232U\216\230\66\223\242\204\311\2\1\42\21\306\322\315\220\204jm\24" + "\223A\207\322\60\3\1#\20\305\232M\230\346\210i\63)J\230,\0\1$\16\266\342]\230\344\241q" + "\30D\307\0\1%\15\265\242M\226\344a\305\244\271\5\1&\15\206\342E\70\14\342\60\210\216\1\1'" + "\13\205\242E\70%&\315-\1(\13\264\342L\242\304Y\77\1\1)\13\224\241L\242\304YO\0\1" + "*\12\243\342\304\30\365\13\0\1+\11\203\241\304\30\365\2\1,\13\264\342D\224\310Y\77\1\1-\13" + "\224\241D\224\310YO\0\1.\11\242\232D\322_\4\1/\11\242\232D\226\364E\1\60\11\241\242D" + "\62\14\2\1\61\7a\242\304\1\1\62\12\206\342E\350\227\310\2\1\63\14\243\32E\222%\276DI\2" + "\1\64\15\265b]%G\302^\242H\2\1\65\12\263\231Lc\324\227\10\1\66\21\305\222E&%\245" + "\251\22%\231\226\207Y\6\1\67\20\304REVR\22))Iq\26E\0\1\70\14dbE\244$" + "RR\222\2\1\71\13\264bM\224g}\33\2\1:\12\262\242\214\226\364\13\0\1;\14\304RE\326" + "\267!\315\242\14\1<\12\302\221L\177K\24\0\1=\14\210\42FlM\362\366!\4\1>\13\205b" + "EfJ\302\216\0\1\77\13\207\42F\334q\216\207\14\1@\12\204bE\326e\253\1\1A\13\205\242" + "MX\224\304\342\20\1B\13\203\241LT\32\242\26\0\1C\22\266\342]\230C\332\246DJ$%R" + "\242m\2\1D\13\225\242]=\61in\1\1E\23\306\322\205\266)\221\22I\211\224h\233\16\245a" + "\6\1F\15\245\222Eb\322\334\362\60\313\0\1G\22\266\342M\61\207\264M\211\224HJ\244D\333\4" + "\1H\14\225\242M-OL\232[\0\1I\20\207\42N\234&\211&\325\242,\312\242,\1J\22\246" + "\332\205\266)\221\22I\211\224h\233\32&\0\1K\14\205\232Eb\322\334\302,\1\1L\16\246\342\325" + "\216\14I\350c\62$\0\1M\13\205\242\315\276dn\311\2\1N\17\266\342M\224\351\320\220\204>&" + "C\2\1O\15\225\242M\24\351K\346\226,\0\1P\17\266\342U%\312\221!\11}L\206\4\1Q" + "\15\225\242M$\345K\346\226,\0\1R\16\210\242\316\60h\335\6\255\307a\10\1S\17hb\316\242" + "d\221\66hM\311\242\0\1T\16\265\242]}H\62mP\62\267\0\1U\13\223\42U\22&K\324" + "\4\1V\20\305\222\305\220d\332\240dny\230e\0\1W\14\243\22E\262D\315Q\22\1\1X\16" + "\265\242M-\37\222L\33\224\314-\1Y\13\223\42ERL\226\250\11\1Z\16\265\242]\35Y\62u" + "\325\264d\1\1[\15\224bU]\211\22QJ\24\0\1\134\17\265\242U\226\344K\246\256\232\226,\0" + "\1]\15\224bU%U\242D\224\22\5\1^\16\245\232\315\222\251\253\246%[&\1\1_\15\204Z" + "\215\22%\242\224h\221\2\1`\17\265\242M-G\226L]\65-Y\0\1a\15\224bM)V\242" + "D\224\22\5\1b\13\245Z\305 \205\375&\1\1c\15\243\32M\224,Q\223\222(\0\1d\14\265" + "bM-\37\244\260O\0\1e\15\207\342M\230\204CTn\26\1\1f\14\205b\305 \205\331\26\66" + "\1\1g\14\203\42M\224,\311\22E\2\1h\17\266\342U\22%\71\22\372\61\31\22\0\1i\13\224" + "bM\242\244\221\247d\1j\14\246\342\325\36\372\61\31\22\0\1k\12\204b\315\30yJ\6\1l\16" + "\266\342M\224\351H\350\307dH\0\1m\13\224bE\224\250\221\247d\1n\17\306\342]\230\204\71\22" + "\372\61\31\22\0\1o\14\244bU%J#O\311\0\1p\15\266\342U%\312C\77&C\2\1q" + "\21\225bM$\305Q\22%Q\22%Q\264\0\1r\14\246\332E\350\307d\310R\11\1s\13\204Z" + "E\344)\231\62\1\1t\24\271bf\236\344h\226i\225\250/m\305,\314\42\0\1u\22\227\42^" + "\232\344PI\252\64%\265$L\42\0\1v\22\267\342]\232\344P\232dQV\11\223\64\256\1\1w" + "\21\265YU\226\344Q\22%\245$\323\302H\3\1x\21\247\342U\222Ci\222EY%L\322\270\6" + "\1y\15\265\242]}\20\263bV\34\4\1z\14\224b]\224\16Y\324\66\4\1{\15\245\242U>" + "\210Y\61+\16\2\1|\13\204bU:dQ\333\20\1}\16\265\242M-\37\304\254\230\25\7\1\1" + "~\14\224bM)\35\262\250m\10\1\206\16\206\342\315\220\204i\253\230\14\11\0\1\211\22\207\42\316\220" + "\225\302AJ\302$L\262h\210\0\1\216\14\205\242\305 \26\207\61\34\6\1\222\13\224\32\225\22MY" + "\247\14\1\227\13\203\342LTY\242\26\0\1\232\13\203\241LTY\242\26\0\1\235\25\247\32\216\226h" + "I))%QRJjJ\246\244\61\0\1\237\17\206\342\315\220\204\342\60\210\306dH\0\1\240\23\210" + "\342\315\20\211\211\250\204Q\30\205Q\230\15\31\0\1\241\16g\242\315\244%\232\222EY\266\1\1\247\16" + "\205\242\315\222\205\311\22fZ\262\0\1\250\15db\215\22%J\26%\12\0\1\256\13\245Z\305 \205" + "\375\232\0\1\257\23\210\342E\230\210\211\250\204Q\30\205Q\230\15\31\0\1\260\16fbE\224H\211\244" + "Dm\23\0\1\265\14\205\242\305 f\321\326\70\10\1\266\14db\305\220%K\224\15\1\1\273\15\205" + "\242\315\222\205\303$e\341 \1\274\15\205\242\315\220\204\341\32j\311\2\1\300\10\241\232\304C\0\1\302" + "\16\244^U\326\62D\311\20e\25\0\1\303\7\201\242\304%\1\315\21\267\342U\222\346p\65\11\223\254" + "\64(\251\32\1\316\16\225bM-_\243%J\242H\11\1\317\12\263\342DR\215\372\5\1\320\12\223" + "\241DR\215z\1\1\321\17\266\342U\22\346\320\220\204>&C\2\1\322\15\225\242M-G\226\314-" + "Y\0\1\323\16\266\342U\22\346H\350\307dH\0\1\324\13\224bM)\215<%\3\1\325\16\306\342" + "\325\216Dy\350\307dH\0\1\326\13\244b\305\232\244\221\247d\1\327\17\326\342]\230cQ\36\372\61" + "\31\22\0\1\330\14\264bU\71I#O\311\0\1\331\17\326\342U\22\346P=\364c\62$\0\1\332" + "\15\264bE\22\305I\32yJ\6\1\333\16\326\342U\234C\365\320\217\311\220\0\1\334\14\264bM\230" + "&i\344)\31\1\335\14db\215\22e\303\224(\0\1\336\23\307\342\325\16&\71\32\247I\230d\245" + "AI\325\0\1\337\17\245b\315\236\304k\264DI\24)\1\1\340\21\307\342\325\216\346p\65\11\223\254" + "\64(\251\32\1\341\17\245b\315\216\344k\264DI\24)\1\1\342\24\251\242\336N\31\66\71\211\223A" + "\211\322!\314\302l\20\1\343\21\207\42\326\16-b\224\14C\224E\25\13\0\1\344\16\206\342\315\220\204" + "jm\32\302d\20\1\345\15\205\232\215i\63\15C\230,\0\1\346\20\266\342M\61\307\206$Tk\243" + "\230\14\2\1\347\17\265\232M-GL\233IQ\302d\1\1\350\20\265\242M-\317\244\244\64U\242$" + "\323\2\1\351\17\264bE\22\305YII\244\244$\5\1\352\16\246\332\315\220\204>&C\226J\0\1" + "\353\14\205\232\315\222\271%[\250\0\1\354\17\306\332\325\216\14I\350c\62d\251\4\1\355\15\245\232\315" + "\276dn\311\26*\0\1\360\13\263\231DR\215\372\22\1\1\364\20\266\342]\230cC\22\252\265QL" + "\6\1\1\365\16\265\232]\35\61m&E\11\223\5\1\370\22\266\342M\234C\332\246DJ$%R\242" + "m\2\1\371\13\225\242M\232'&\315-\1\372\24\347\342e\232\303i\222\306\325$L\262\322\240\244j" + "\0\1\373\21\305b]\35\312\222,Z\243%J\242H\11\1\374\25\271\242n\236\223\206MN\342dP" + "\242t\10\263\60\33\4\1\375\21\227\42f\232c\213\30%\303\20eQ\305\2\1\376\25\326\336e\35N" + "\206$\233\22)Q\42%\332\222!I\1\1\377\21\227\241e\232\243KIJJR-I&\0\2\0" + "\23\267\342M\24F\71\30\247I\230d\245AI\325\0\2\1\16\226aE\224E\71\62gS\67%\2" + "\2\22\267\342\235\30\345`\234&a\222\225\6%U\3\2\3\16\225b\215T^\243%J\242H\11\2" + "\4\21\266\342E\224E\71\62(i:(iu\20\2\5\16\225aE\324.U\206$\214\42\5\2\6" + "\16\265\242\215T\36\306p\30\213\203\0\2\7\16\224b\215\22\245J\64lQ\242\0\2\10\13\265\341D" + "\324\36\366\23\0\2\11\12\225\240D\324\36\366\4\2\12\13\264\342\214\22\245Y\77\1\2\13\13\224\241\214" + "\22\245YO\0\2\14\20\266\342E\224E\71\62$\241\217\311\220\0\2\15\14\225\242E\324\274dn\311" + "\2\2\16\17\266\342\225\26\345\310\220\204>&C\2\2\17\14\225\242\225T^\62\267d\1\2\20\17\265" + "\242E\324:$\231\66(\231[\0\2\21\14\225!E\324\234DZ\330\6\2\22\16\265\242\215T\36\222" + "L\33\224\314-\2\23\15\224!\215\22\245I\42eM\0\2\24\16\266\342E\224Ey\350\307dH\0" + "\2\25\20\225aE\324\34%Q\22%Q\22E\3\2\26\15\266\342\225\26\345\241\37\223!\1\2\27\13" + "\224b\215\22\205\221\247d\2\30\20\305\222\315\222\251\253\246%;\22f\31\0\2\31\16\244R\215\22%" + "\242\224\350Y\24\1\2\32\15\305R\305 \205}\207\302,\3\2\33\16\303\22M\224,Q\223\30%\21" + "\0\2\36\16\266\342U\22\346Hh\34\6\321\61\2\37\16\265\242E\222\345HX\61in\1\2&\20" + "\247\342]\16W\223\60\311J\203\222\252\1\2'\15\205bU\276FK\224D\221\22\2(\16\245\232\305" + "\61\34\306\342 e\22\0\2)\15\204Z\215\22\15[\224h\221\2\2*\20\306\342\325\216D\71\62$" + "\241\217\311\220\0\2+\15\245\242\315\236\344K\346\226,\0\2,\22\326\342\325\16%Q\222CC\22\372" + "\230\14\11\0\2-\15\265\242\315\216t_\62\267d\1\2.\16\246\342]\16\15I\350c\62$\0\2" + "/\14\205\242U\216,\231[\262\0\2\60\17\306\342\325\216\345\320\220\204>&C\2\2\61\16\245\242\315" + "\216\344\310\222\271%\13\0\2\62\21\247\342\325\16\245I\26e\225\60I\343\32\0\2\63\20\245Y\315\36" + "%QRJ\62-\214\64\0\2P\14ebE\42U\242dJ\7\2T\14db\215\22e\231\224(" + "\0\2X\14db\215\22\15\232\224(\0\2Y\14db\215\22e\303\224(\0\2_\13\204\32U\326" + "iJ$\0\2e\13\205\232E\346IQ\302\2\2u\15e\242\315\222\15C\246%\13\0\2y\10c" + "\42U\227%\2\207\13\203\42\205\324e\211\22\0\2\210\13\243\32M\224,Qo\1\2\211\13dbE" + "$\15\223\224\14\2\214\14e\242U\230%QR\323\2\2\215\17g\42V\22&YRJ\232\42\251\0" + "\2\216\17\205a\235\24fZ\22%\245$J\0\2\236\15\204ZE$%%%\221\262\2\2\273\10\62" + "\266D\22\5\2\274\10\62\266L\242\0\2\275\10\62\266D\22\5 \201\7B\332\314R \202\10C\332" + "\314R\31 \203\11C\332\304\222-\0 \254\20\207\42\326\20\205\203\230\16a\34FC\2\0"; diff --git a/bl616/misterynano_fw/main.c b/bl616/misterynano_fw/main.c index 727643b..7fc4875 100644 --- a/bl616/misterynano_fw/main.c +++ b/bl616/misterynano_fw/main.c @@ -8,11 +8,6 @@ #include "usb_config.h" -// #include "bflb_ef_ctrl.h" -// efuse_dev = bflb_device_get_by_name("ef_ctrl"); -//bflb_ef_ctrl_write_direct(efuse_dev, EFUSE_DATA_START_ADDR, (uint32_t *)efuse_data_write, EFUSE_DATA_LEN, 1); -//bflb_ef_ctrl_read_direct(efuse_dev, EFUSE_DATA_START_ADDR, (uint32_t *)efuse_data_read, EFUSE_DATA_LEN, 1); - struct bflb_device_s *gpio; #include @@ -25,16 +20,14 @@ struct bflb_device_s *gpio; QueueHandle_t xQueue = NULL; static void sdc_task(void *parms) { - spi_t *spi = (spi_t*)parms; - while(1) { sdc_poll(); - vTaskDelay(10); + // this delay needs to be rather short to ensure that the + // MCU responds fast enough to sector requests by the core + vTaskDelay(pdMS_TO_TICKS(1)); } } -// OSD task gets a callback which it calls whenever the OSD -// opems or closes to inform the USB layer about the OSD state static void osd_task(void *parms) { menu_t *menu; @@ -52,7 +45,8 @@ static void osd_task(void *parms) { xQueueReceive( xQueue, &cmd, 0xffffffffUL); menu_do(menu, cmd); - printf("BTN %d\r\n", bflb_gpio_read(gpio, GPIO_PIN_2)); + // test poll the UPDATE button + // printf("BTN %d\r\n", bflb_gpio_read(gpio, GPIO_PIN_2)); } } @@ -114,11 +108,11 @@ int main(void) { // printf("SET: %02x\r\n", *(unsigned char*)0x20010800); // start a thread for the on screen display - xTaskCreate(osd_task, (char *)"osd_task", 512, spi, configMAX_PRIORITIES-3, &osd_handle); + xTaskCreate(osd_task, (char *)"osd_task", 4096, spi, configMAX_PRIORITIES-3, &osd_handle); // create another task to frequently poll the FPGA via SPI for e.g. // floppy disk requests - xTaskCreate(sdc_task, (char *)"sdc_task", 512, spi, configMAX_PRIORITIES-3, &sdc_handle); + xTaskCreate(sdc_task, (char *)"sdc_task", 4096, spi, configMAX_PRIORITIES-3, &sdc_handle); vTaskStartScheduler(); diff --git a/bl616/misterynano_fw/menu.c b/bl616/misterynano_fw/menu.c index 94fe61b..1240bce 100644 --- a/bl616/misterynano_fw/menu.c +++ b/bl616/misterynano_fw/menu.c @@ -1,386 +1,513 @@ /* - menu.c - MiSTeryNano menu based in u8g2/MUI + menu.c - MiSTeryNano menu based in u8g2 */ #include +#include +#include +#include +#include "sdc.h" #include "menu.h" -// a global OSD pointer as mui has no userdata or the like -osd_t *osd = NULL; +// this is the u8g2_font_helvR08_te with any trailing +// spaces removed +#include "font_helvR08_te.c" -void menu_do(menu_t *menu, int event) { - if(event) { - if(event == MENU_EVENT_DOWN) mui_NextField(&menu->ui); - if(event == MENU_EVENT_UP) mui_PrevField(&menu->ui); - if(event == MENU_EVENT_SELECT) mui_SendSelect(&menu->ui); - if(event == MENU_EVENT_SHOW) osd_enable(menu->osd, OSD_VISIBLE); - if(event == MENU_EVENT_HIDE) osd_enable(menu->osd, OSD_INVISIBLE); - } - - u8g2_SetFontRefHeightExtendedText(&menu->osd->u8g2); - u8g2_FirstPage(&menu->osd->u8g2); - do - mui_Draw(&menu->ui); - while( u8g2_NextPage(&menu->osd->u8g2) ); +#define MENU2U8G2(a) (&(a->osd->u8g2)) + +char *disk_a = NULL; + +// variable ids must match the ones in the menu string +menu_variable_t variables[] = { + { 'C', { 2 }}, // default chipset = STE + { 'M', { 0 }}, // default memory = 4MB + { 'V', { 0 }}, // default video = color + { 'S', { 0 }}, // default scanlines = none + { 'A', { 1 }}, // default volume = 33% + // { 'D', { .ptr=&disk_a }}, // default disk is initialized separately + { '\0',{ 0 }} +}; + +#define MENU_FORM_FSEL -1 + +#define MENU_ENTRY_INDEX_ID 0 +#define MENU_ENTRY_INDEX_LABEL 1 +#define MENU_ENTRY_INDEX_FORM 2 +#define MENU_ENTRY_INDEX_OPTIONS 2 +#define MENU_ENTRY_INDEX_VARIABLE 3 + +static const char main_form[] = + "MiSTeryNano,;" // main form has no parent + // -------- + "S,System,1;" // System submenu is form 1 + "F,Disk A:,0;" // fileselector for Disk A: + "F,Disk B:,1;" // fileselector for Disk B: + "B,Reset,R;"; // system reset + +static const char system_form[] = + "System,0;" // parent form is 0 + // -------- + "L,Chipset:,ST|Mega ST|STE,C;" // selection stored in variable "SC" + "L,Memory:,4MB|8MB,M;" // ... + "L,Video:,Color|Mono,V;" + "L,Scanlines:,None|25%|50%|75%,S;" + "L,Volume:,Mute|33%|66%|100%,A;" + "B,Cold Boot,B;"; // system reset with memory reset +// "B,Save settings,S;"; + +static const char *forms[] = { + main_form, + system_form +}; + +static void menu_goto_form(menu_t *menu, int form) { + menu->form = form; + menu->entry = 1; + menu->entries = -1; + menu->offset = 0; } -/* ------------------ MUI ----------------------- */ +static void menu_settings_load(menu_t *menu) { + FIL fil; -uint8_t mui_style_helv_r_08(mui_t *ui, uint8_t msg) { - if(msg == MUIF_MSG_DRAW) - u8g2_SetFont(mui_get_U8g2(ui), u8g2_font_helvR08_tr); // _te is with some unicode - - return 0; + if(f_open(&fil, "/sd/atarist.ini", FA_OPEN_EXISTING | FA_READ) == FR_OK) { + char buffer[8]; + + printf("Settings file open\r\n"); + while(f_gets(buffer, sizeof(buffer), &fil) != NULL) { + int value = atoi(buffer+1); + printf("Setting %c:%d\r\n", buffer[0], value); + + for(int i=0;menu->vars[i].id;i++) + if(menu->vars[i].id == buffer[0]) + menu->vars[i].value = value; + } + f_close(&fil); + } } -uint8_t mui_style_helv_b_08(mui_t *ui, uint8_t msg) { - if(msg == MUIF_MSG_DRAW) - u8g2_SetFont(mui_get_U8g2(ui), u8g2_font_helvB08_tr); - - return 0; +#if 0 +static void menu_settings_save(menu_t *menu) { + unsigned char buffer[512]; + unsigned char *p = buffer; + for(int i=0;menu->vars[i].id;i++) { + *p++ = menu->vars[i].id; + *p++ = menu->vars[i].value; + } + + // append NUL id as end marker + *p++ = 0; + + // saving does not work, yet, as there is no SD card write support by now } +#endif -uint8_t mui_hrule(mui_t *ui, uint8_t msg) +#ifndef SDL +menu_t *menu_init(spi_t *spi) +#else +menu_t *menu_init(u8g2_t *u8g2) +#endif { - u8g2_t *u8g2 = mui_get_U8g2(ui); - if(msg == MUIF_MSG_DRAW) - u8g2_DrawHLine(u8g2, 0, mui_get_y(ui), u8g2_GetDisplayWidth(u8g2)); + static menu_t menu; + + menu.forms = forms; + menu.vars = variables; + menu_goto_form(&menu, 0); // first form selected at start + +#ifndef SDL + menu.osd = osd_init(spi); +#else + static osd_t losd; + menu.osd = &losd; + menu.osd->u8g2 = *u8g2; +#endif + + // try to restore variables from eeprom + menu_settings_load(&menu); - return 0; + // send initial values for all variables + for(int i=0;menu.vars[i].id;i++) + osd_emit(menu.osd, menu.vars[i].id, menu.vars[i].value); + + // and cold reset the core, just in case ... + // TODO: keep core in reset until MCU releases it! + osd_emit(menu.osd, 'R', 3); + osd_emit(menu.osd, 'R', 0); + + return &menu; } -/* - global variables which form the communication gateway between the user interface and the rest of the code -*/ +// find first occurence of any char in chrs within str +const char *strchrs(const char *str, char *chrs) { + while(*str) { + for(int i=0;icurrent_form_fds[1]) + const char *sub = strchrs(s, ";,"); + if(!sub) + strcpy(menu->buffer, s); + else { + strncpy(menu->buffer, s, sub-s); // copy characters + menu->buffer[sub-s] = '\0'; // terminate string + } + + return menu->buffer; +} -#include "sdc.h" +// get the n'th char in colon separated string +static char menu_get_chr(menu_t *menu, const char *s, int n) { + while(n--) s = strchr(s, ',')+1; // skip n substrings + return s[0]; +} -// taken from XBM bitmaps -static const unsigned char folder_icon[] = { 0x70,0x8e,0xff,0x81,0x81,0x81,0x81,0x7e }; -static const unsigned char up_icon[] = { 0x04,0x0e,0x1f,0x0e,0xfe,0xfe,0xfe,0x00 }; +// get the n'th substring in | separated string in a colon string +static char *menu_get_substr(menu_t *menu, const char *s, int n, int m) { + while(n--) s = strchr(s, ',')+1; // skip n substrings + while(m--) s = strchr(s, '|')+1; // skip m subsubstrings -uint8_t file_selection(mui_t *ui, uint8_t msg) { - static sdc_dir_t *dir = NULL; + const char *sub = strchrs(s, ";,|"); + strncpy(menu->buffer, s, sub-s); // copy characters + menu->buffer[sub-s] = '\0'; // terminate string - // load directory on form start for first file entry - if ( msg == MUIF_MSG_FORM_START ) { - if( !ui->arg ) { - dir = sdc_readdir(NULL); - - ui->form_scroll_total = dir->len; - ui->form_scroll_top = 0; + return menu->buffer; +} + +static int menu_get_int(menu_t *menu, const char *s, int n) { + char *str = menu_get_str(menu, s, n); + return atoi(str); +} - ui->form_scroll_visible = 1; - } else - ui->form_scroll_visible++; - } +static int menu_variable_get(menu_t *menu, const char *s) { + char id = menu_get_chr(menu, s, MENU_ENTRY_INDEX_VARIABLE); - // at this point dir must be valid - sdc_dir_entry_t *entry = &(dir->files[ui->form_scroll_top+ui->arg]); - - if ( msg == MUIF_MSG_CURSOR_SELECT ) { - if(entry->is_dir) { - dir = sdc_readdir(entry->name); - - ui->form_scroll_total = dir->len; - ui->form_scroll_top = 0; + for(int i=0;menu->vars[i].id;i++) + if(menu->vars[i].id == id) + return menu->vars[i].value; - // go to first entry of newly opened subdir - int scroll_up = ui->form_scroll_top+ui->arg; - for(int i=0;iname); - - // return to parent form, second entry - // printf("PRE = %d\n", ui->last_form_id); - - mui_GotoForm(ui, 0, 2); - return 0; + return -1; +} + +static void menu_variable_set(menu_t *menu, const char *s, int val) { + char id = menu_get_chr(menu, s, MENU_ENTRY_INDEX_VARIABLE); + + for(int i=0;menu->vars[i].id;i++) { + if(menu->vars[i].id == id) { + menu->vars[i].value = val; + + // also set this in the core + osd_emit(menu->osd, id, val); + + // trigger cold reset if memory or chipset have been changed a + // video change will also trigger a reset, but that's handled by + // the ST itself + if((id == 'C') || (id == 'M') ) { + osd_emit(menu->osd, 'R', 3); + osd_emit(menu->osd, 'R', 0); + } } } +} + +static int menu_get_options(menu_t *menu, const char *s, int n) { + // get possible number of values + int num = 1; + char *v = menu_get_str(menu, s, n); + while((v = strchr(v, '|'))) { v++; num++; } + return num; +} - if ( msg == MUIF_MSG_DRAW ) { - char str[32]; - static const int icon_skip = 10; +// various 8x8 icons +const static unsigned char icn_right_bits[] = { 0x00,0x04,0x0c,0x1c,0x3c,0x1c,0x0c,0x04 }; +const static unsigned char icn_left_bits[] = { 0x00,0x20,0x30,0x38,0x3c,0x38,0x30,0x20 }; +const static unsigned char icn_floppy_bits[] = { 0xff,0x81,0x83,0x81,0xbd,0xad,0x6d,0x3f }; - // check if we are supposed to draw an entry which arg is higher - // then the number file entries. This means that there are less - // files then entries on screen - if(ui->form_scroll_top+ui->arg >= dir->len) - return 1; +// Draw menu title. Submenu titles are selectable and can be used to return to the +// parent menu. +static void menu_draw_title(menu_t *menu, const char *s) { + int x = 1; - strncpy(str, entry->name, sizeof(str)); - str[sizeof(str)-1] = 0; // terminate possibly truncated string + // draw left arrow for submenus + if(menu->form) { + u8g2_DrawXBM(MENU2U8G2(menu), 0, 1, 8, 8, icn_left_bits); + x = 8; + } - int width = u8g2_GetDisplayWidth(mui_get_U8g2(ui)) - 2*mui_get_x(ui); - - // properly ellipsize string - int dotlen = u8g2_GetUTF8Width(mui_get_U8g2(ui), "..."); - if(u8g2_GetUTF8Width(mui_get_U8g2(ui), str) > width-icon_skip) { - while(u8g2_GetUTF8Width(mui_get_U8g2(ui), str) > width-icon_skip-dotlen) str[strlen(str)-1] = 0; - if(strlen(str) < sizeof(str)-4) strcat(str, "..."); - } + // draw title in bold and seperator line + u8g2_SetFont(MENU2U8G2(menu), u8g2_font_helvB08_tr); + u8g2_DrawStr(MENU2U8G2(menu), x, 9, menu_get_str(menu, s, 0)); + u8g2_DrawHLine(MENU2U8G2(menu), 0, 13, u8g2_GetDisplayWidth(MENU2U8G2(menu))); + + if(x > 0 && menu->entry == 0) + u8g2_DrawButtonFrame(MENU2U8G2(menu), 0, 9, U8G2_BTN_INV, u8g2_GetDisplayWidth(MENU2U8G2(menu)), 1, 1); + + // draw the rest with normal font + u8g2_SetFont(MENU2U8G2(menu), font_helvR08_te); +} + +static void menu_draw_entry(menu_t *menu, int y, const char *s) { + char *buf = menu_get_str(menu, s, MENU_ENTRY_INDEX_LABEL); - // draw folder icon in front of directories - if(entry->is_dir) - u8g2_DrawXBM(mui_get_U8g2(ui), mui_get_x(ui), mui_get_y(ui)-8, - 8, 8, strcmp(entry->name, "..")?folder_icon:up_icon); + int ypos = 13 + 12 * y; + int width = u8g2_GetDisplayWidth(MENU2U8G2(menu)); + + // all menu entries are a plain text + u8g2_DrawStr(MENU2U8G2(menu), 1, ypos, buf); + + // prepare highlight + int hl_x = 0; + int hl_w = width; + + // handle second string for 'L'ist entries + if(s[0] == 'L') { + // get variable + int value = menu_variable_get(menu, s); + + buf = menu_get_substr(menu, s, MENU_ENTRY_INDEX_OPTIONS, value); + u8g2_DrawStr(MENU2U8G2(menu), width/2, ypos, buf); - u8g2_DrawUTF8(mui_get_U8g2(ui), mui_get_x(ui) + icon_skip, mui_get_y(ui), str); - if(ui->dflags & 1) - u8g2_DrawButtonFrame(mui_get_U8g2(ui), mui_get_x(ui), mui_get_y(ui), U8G2_BTN_INV, width, 1, 1); + hl_x = width/2; + hl_w = width/2; } + + // some entries have a small arrow to the right + if(s[0] == 'S') u8g2_DrawXBM(MENU2U8G2(menu), hl_w-8, ypos-8, 8, 8, icn_right_bits); + if(s[0] == 'F') u8g2_DrawXBM(MENU2U8G2(menu), hl_w-9, ypos-8, 8, 8, icn_floppy_bits); + + if(y+menu->offset == menu->entry) + u8g2_DrawButtonFrame(MENU2U8G2(menu), hl_x, ypos, U8G2_BTN_INV, hl_w, 1, 1); +} - // prev and next events are eaten (retval 1), so we can scroll through - // the file list while keeping focus on the same line - if ( msg == MUIF_MSG_EVENT_PREV || msg == MUIF_MSG_EVENT_NEXT) { - // check if we need scrolling at all or if all files fit on screen - if(ui->form_scroll_visible >= dir->len) { - - // if we are on the last used entry and there are unused ones ahead, then - // skip these - if( (msg == MUIF_MSG_EVENT_NEXT )&&( ui->form_scroll_visible > dir->len)) { - if( ui->arg == dir->len - 1) { - extern void mui_next_field(mui_t *ui); - for(int i=0;iform_scroll_visible - dir->len;i++) mui_next_field(ui); - } - } - - // if we are on the first entry and there are unused ones at the end, then - // skip these - if( (msg == MUIF_MSG_EVENT_PREV )&&( ui->form_scroll_visible > dir->len)) { - if( ui->arg == 0) { - // there is no mui_prev_field(ui), so we use mui_next_field(ui) - extern void mui_next_field(mui_t *ui); - for(int i=0;ilen;i++) mui_next_field(ui); - } - } - return 0; - } +static void menu_fileselector(menu_t *menu, int state) { + static const unsigned char folder_icon[] = { 0x70,0x8e,0xff,0x81,0x81,0x81,0x81,0x7e }; + static const unsigned char up_icon[] = { 0x04,0x0e,0x1f,0x0e,0xfe,0xfe,0xfe,0x00 }; + + static sdc_dir_t *dir = NULL; + const static char *s; + static int parent; + static int drive; + + if(state == 0) { + // init + s = menu->forms[menu->form]; + for(int i=0;ientry;i++) s = strchr(s, ';')+1; - // check if we'd leave the menu at top - if(ui->arg == 0 && msg == MUIF_MSG_EVENT_PREV && ui->form_scroll_top == 0) - ui->form_scroll_top = ui->form_scroll_total - ui->form_scroll_visible; + // scan files + dir = sdc_readdir(NULL); + menu->entry = 1; // start by highlighting first file entry + menu->entries = dir->len + 1; // incl. title + menu->offset = 0; + parent = menu->form; + drive = menu_get_int(menu, s, 2); - // check if we'd leave the menu at bottom - if(ui->arg == ui->form_scroll_visible-1 && msg == MUIF_MSG_EVENT_NEXT && - ui->form_scroll_top == ui->form_scroll_total - ui->form_scroll_visible) - ui->form_scroll_top = 0; - - // scrolling only happens on the middle entry - if(ui->arg == (ui->form_scroll_visible-1)/2) { - if( msg == MUIF_MSG_EVENT_PREV && ui->form_scroll_top > 0) { - ui->form_scroll_top--; - return 1; + } else if(state == 1) { + // draw + menu_draw_title(menu, menu_get_str(menu, s, MENU_ENTRY_INDEX_LABEL)); + + // draw up to four files + for(int i=0;i<((dir->len<4)?dir->len:4);i++) { + char str[32]; + static const int icon_skip = 10; + int y = 13 + 12 * (i+1); + sdc_dir_entry_t *entry = &(dir->files[i+menu->offset]); + + strncpy(str, entry->name, sizeof(str)); + str[sizeof(str)-1] = 0; // terminate possibly truncated string + + int width = u8g2_GetDisplayWidth(MENU2U8G2(menu)); + + // properly ellipsize string + int dotlen = u8g2_GetStrWidth(MENU2U8G2(menu), "..."); + if(u8g2_GetStrWidth(MENU2U8G2(menu), str) > width-icon_skip) { + while(u8g2_GetStrWidth(MENU2U8G2(menu), str) > width-icon_skip-dotlen) str[strlen(str)-1] = 0; + if(strlen(str) < sizeof(str)-4) strcat(str, "..."); } + u8g2_DrawStr(MENU2U8G2(menu), icon_skip, y, str); + + // draw folder icon in front of directories + if(entry->is_dir) + u8g2_DrawXBM(MENU2U8G2(menu), 1, y-8, 8, 8, strcmp(entry->name, "..")?folder_icon:up_icon); + + if(menu->entry == i+menu->offset+1) + u8g2_DrawButtonFrame(MENU2U8G2(menu), 0, y, U8G2_BTN_INV, width, 1, 1); } - - if(ui->arg == ui->form_scroll_visible/2) { - if( msg == MUIF_MSG_EVENT_NEXT && ui->form_scroll_top < - ui->form_scroll_total - ui->form_scroll_visible) { - ui->form_scroll_top++; - return 1; + } else if(state == 4) { + + if(!menu->entry) + menu_goto_form(menu, parent); + else { + sdc_dir_entry_t *entry = &(dir->files[menu->entry - 1]); + + if(entry->is_dir) { + dir = sdc_readdir(entry->name); + menu->entry = 1; // start by highlighting first file entry + menu->entries = dir->len + 1; // incl. title + menu->offset = 0; + } else { + // request insertion of this image + sdc_image_open(drive, entry->name); + menu_goto_form(menu, parent); } } - } - - return 0; + } } -/* - System: SC,SM,SV - */ +static void menu_draw_form(menu_t *menu, const char *s) { + u8g2_ClearBuffer(MENU2U8G2(menu)); -uint8_t btn_goto(mui_t *ui, uint8_t msg) { - // the form to go to is stored in arg. There are special forms with arg >= 128 - if((msg == MUIF_MSG_CURSOR_SELECT) && (ui->arg >= 128)) { + // regular entry? + if(menu->form >= 0) { + // count menu entries if not done yet + if(menu->entries < 0) { + menu->entries = 0; - if(ui->arg == 255) { // send reset - osd_emit(osd, "SR", 1); // activate reset signal - osd_emit(osd, "SR", 0); // de-activate reset signal + for(const char *p = s;*p && strchr(p, ';');p=strchr(p, ';')+1) + menu->entries++; } - // hiding the OSD here won't work as the USB side needs to know about this - // as well .... + // -------- draw title ----------- + menu_draw_title(menu, s); + s = strchr(s, ';')+1; + + // ------- draw menu entries ------ + + // skip 'offset' entries + for(int i=0;ioffset;i++) + s = strchr(s, ';')+1; // skip to next entry - // osd_enable(osd, OSD_INVISIBLE); // hide OSD - // mui_GotoForm(ui, 0, 0); // return to main form - return 0; - } + // walk over menu string + int y = 1; + while(*s) { + menu_draw_entry(menu, y++, s); + s = strchr(s, ';')+1; // skip to next entry + } + } else if(menu->form == MENU_FORM_FSEL) + menu_fileselector(menu, 1); - return mui_u8g2_btn_goto_w1_pi(ui, msg); + u8g2_SendBuffer(MENU2U8G2(menu)); } -// used for "ok" and "cancel" buttons -uint8_t btn_dialog(mui_t *ui, uint8_t msg) { - static uint8_t form = 0xff; - static uint8_t backup[3]; - - // if a cancel button is to be drawn, then the forms values have to be backed up, - // to allow to restore them on cancel - if(ui->id1 == 'C' && msg == MUIF_MSG_DRAW && form != CURRENT_FORM_ID(ui)) { - form = CURRENT_FORM_ID(ui); - printf("prepare cancel of form %d\n", form); - - if(form == 10) { - backup[0] = system_chipset; - backup[1] = system_memory; - backup[2] = system_video; - } +static void menu_select(menu_t *menu) { + if(menu->form == MENU_FORM_FSEL) { + menu_fileselector(menu, 4); + return; } + + const char *s = menu->forms[menu->form]; + // skip to current entry (incl. title) + for(int i=0;ientry;i++) s = strchr(s, ';')+1; + + printf("Selected: %s\r\n", s); - // ok/cancel has been selcted - if(msg == MUIF_MSG_CURSOR_SELECT) { - if(CURRENT_FORM_ID(ui) == 10 && ui->id1 == 'C') printf("system form canceled\n"); - if(CURRENT_FORM_ID(ui) == 10 && ui->id1 == 'O') printf("system form ok\n"); + // if the title was selected, then goto parent form + if(!menu->entry) { + menu_goto_form(menu, menu_get_int(menu, s, 1)); + return; + } + + switch(*s) { + case 'F': + menu_fileselector(menu, 0); // init + menu->form = MENU_FORM_FSEL; + menu->entry = 1; + break; + + case 'S': + menu_goto_form(menu, menu_get_int(menu, s, MENU_ENTRY_INDEX_FORM)); + break; + + case 'L': { + int value = menu_variable_get(menu, s) + 1; + int max_value = menu_get_options(menu, s, MENU_ENTRY_INDEX_OPTIONS)-1; + if(value > max_value) value = 0; + menu_variable_set(menu, s, value); + } break; + + case 'B': { + char id = menu_get_chr(menu, s, 2); - if(ui->id1 == 'C') { - // restore old values + // if(id == 'S') + // menu_settings_save(menu); - if(CURRENT_FORM_ID(ui) == 10) { - system_chipset = backup[0]; - system_memory = backup[1]; - system_video = backup[2]; - } + // normal reset + if(id == 'R') { + osd_emit(menu->osd, 'R', 1); + osd_emit(menu->osd, 'R', 0); } - - if(ui->id1 == 'O') { - // transmit new values to fpga - // ... - if(CURRENT_FORM_ID(ui) == 11) { - printf("disk a: ok\r\n"); - } - - if(CURRENT_FORM_ID(ui) == 10) { - osd_emit(osd, "SC", system_chipset); - osd_emit(osd, "SM", system_memory); - osd_emit(osd, "SV", system_video); - - // send reset if chipset or memory has changed - if(system_chipset != backup[0] || system_memory != backup[1]) { - osd_emit(osd, "SR", 1); - osd_emit(osd, "SR", 0); - } - } + // cold boot + if(id == 'B') { + osd_emit(menu->osd, 'R', 3); + osd_emit(menu->osd, 'R', 0); } - - form = 0xff; + } break; + + default: + printf("unknown %c\r\n", *s); } - - return mui_u8g2_btn_goto_wm_fi(ui, msg); } -muif_t muif_list[] MUI_PROGMEM = { - /* normal text style */ - MUIF_STYLE(0, mui_style_helv_r_08), - - /* bold text style */ - MUIF_STYLE(1, mui_style_helv_b_08), - - /* horizontal line (hrule) */ - MUIF_RO("HR", mui_hrule), +static int menu_entry_is_usable(menu_t *menu) { + // check the current entry in the menu is actually selectable + // (currently only the title of the start form is not) - // System variables - MUIF_VARIABLE("SC", &system_chipset, mui_u8g2_u8_opt_line_wa_mse_pi), - MUIF_VARIABLE("SM", &system_memory, mui_u8g2_u8_opt_line_wa_mse_pi), - MUIF_VARIABLE("SV", &system_video, mui_u8g2_u8_opt_line_wa_mse_pi), + // not start form? -> ok + if(menu->form) return 1; - // file selection - MUIF_VARIABLE("FS", &fs, file_selection), + return (menu->entry == 0)?0:1; +} - /* Goto Form Button where the width is equal to the size of the text, spaces can be used to extend the size */ - //MUIF("G1",MUIF_CFLAG_IS_CURSOR_SELECTABLE,0,mui_u8g2_btn_goto_wm_fi), - MUIF_BUTTON("GO", btn_dialog), - MUIF_BUTTON("GC", btn_dialog), - MUIF_BUTTON("G1", mui_u8g2_btn_goto_wm_fi), - - /* MUI_GOTO uses the fixed ".G" id and is intended for goto buttons. This is a full display width style button */ - MUIF_GOTO(btn_goto), - - /* MUI_LABEL uses the fixed ".L" id and is used to place read only text on a form */ - //MUIF(".L",0,0,mui_u8g2_draw_text), - MUIF_LABEL(mui_u8g2_draw_text), -}; +static void menu_entry_go(menu_t *menu, int step) { + do { + menu->entry += step; + + if(menu->entry < 0) menu->entry = menu->entries-1; + if(menu->entry >= menu->entries) menu->entry = 0; + + // scrolling needed? + if(step == 1) { + if(menu->entries <= 5) menu->offset = 0; + else { + if(menu->entry <= 3) menu->offset = 0; + else if(menu->entry < menu->entries-2) menu->offset = menu->entry - 3; + else menu->offset = menu->entries-5; + } + } -fds_t fds[] = - -// --- form 0: main menu -MUI_FORM(0) -MUI_STYLE(1) -MUI_LABEL(5,10, "MiSTeryNano") -MUI_XY("HR", 0,13) -MUI_STYLE(0) -MUI_GOTO(5,25,10, "System") - // MUI_GOTO(5,37,1, "Storage") -MUI_GOTO(5,37,11, "Disk A:") -MUI_GOTO(5,49,255, "Reset") - -// --- form 1: Storage handling -MUI_FORM(1) -MUI_STYLE(1) -MUI_LABEL(5,10, "Storage") -MUI_XY("HR", 0,13) -MUI_STYLE(0) -MUI_GOTO(5,25,12, "SD card") -MUI_GOTO(5,37,11, "Disk A:") -MUI_GOTO(5,49,11, "Disk B:") -MUI_GOTO(5,61,0, "Back...") - -/* form 10: system setup dialog */ -MUI_FORM(10) -MUI_STYLE(1) -MUI_LABEL(5,8, "System") MUI_XY("HR", 0,11) MUI_STYLE(0) - -MUI_LABEL(5,22, "Chipset:") MUI_XYAT("SC", 60, 22, 60, "ST|MegaST|STE") -MUI_LABEL(5,34, "Memory:") MUI_XYAT("SM", 60, 34, 60, "4MB|8MB") -MUI_LABEL(5,46, "Video:") MUI_XYAT("SV", 60, 46, 60, "Color|Mono") - -MUI_XYAT("GO", 32, 60, 0, " Ok ") -MUI_XYAT("GC", 90, 60, 0, " Cancel ") - -/* form 11: disk file selection */ -MUI_FORM(11) -MUI_STYLE(1) -MUI_LABEL(0,8, "Disk A:") MUI_XY("HR", 0,11) MUI_STYLE(0) + if(step == -1) { + if(menu->entries <= 5) menu->offset = 0; + else { + if(menu->entry <= 2) menu->offset = 0; + else if(menu->entry < menu->entries-3) menu->offset = menu->entry - 2; + else menu->offset = menu->entries-5; + } + } + + // give file selector a chance to adjust scroll + if(menu->form == MENU_FORM_FSEL) + menu_fileselector(menu, (step>0)?2:3); + + } while(!menu_entry_is_usable(menu)); +} -MUI_XYA("FS", 0, 22, 0) -MUI_XYA("FS", 0, 34, 1) -MUI_XYA("FS", 0, 46, 2) -MUI_XYA("FS", 0, 58, 3) -; +void menu_do(menu_t *menu, int event) { + if(event) { + if(event == MENU_EVENT_SHOW) osd_enable(menu->osd, OSD_VISIBLE); + if(event == MENU_EVENT_HIDE) osd_enable(menu->osd, OSD_INVISIBLE); + + if(event == MENU_EVENT_UP) menu_entry_go(menu, -1); + if(event == MENU_EVENT_DOWN) menu_entry_go(menu, 1); -#ifndef SDL -menu_t *menu_init(spi_t *spi) -#else - menu_t *menu_init(u8g2_t *u8g2, mui_t *ui) -#endif -{ - static menu_t menu; - -#ifndef SDL - menu.osd = osd_init(spi); -#else - static osd_t losd; - menu.ui = *ui; - menu.osd = &losd; - menu.osd->u8g2 = *u8g2; -#endif - - osd = menu.osd; - mui_Init(&menu.ui, &menu.osd->u8g2, fds, muif_list, sizeof(muif_list)/sizeof(muif_t)); - mui_GotoForm(&menu.ui, 0, 0); - - return &menu; + if(event == MENU_EVENT_SELECT) menu_select(menu); + } + menu_draw_form(menu, menu->forms[menu->form]); } + diff --git a/bl616/misterynano_fw/menu.h b/bl616/misterynano_fw/menu.h index 828bcc7..442a2eb 100644 --- a/bl616/misterynano_fw/menu.h +++ b/bl616/misterynano_fw/menu.h @@ -2,25 +2,40 @@ #define MENU_H #include "osd.h" -#include "mui.h" -#include "mui_u8g2.h" #define MENU_EVENT_NONE 0 #define MENU_EVENT_UP 1 #define MENU_EVENT_DOWN 2 -#define MENU_EVENT_SELECT 3 -#define MENU_EVENT_SHOW 4 -#define MENU_EVENT_HIDE 5 +#define MENU_EVENT_LEFT 3 +#define MENU_EVENT_RIGHT 4 +#define MENU_EVENT_SELECT 5 +#define MENU_EVENT_SHOW 6 +#define MENU_EVENT_HIDE 7 + +// variables +typedef struct { + const char id; + union { + int value; + void *ptr; + }; +} menu_variable_t; typedef struct { osd_t *osd; - mui_t ui; + char buffer[32]; + const char **forms; + menu_variable_t *vars; + int form; + int entry; + int entries; + int offset; // scroll offset } menu_t; #ifndef SDL menu_t *menu_init(spi_t *spi); #else -menu_t *menu_init(u8g2_t *u8g2, mui_t *ui); +menu_t *menu_init(u8g2_t *u8g2); #endif void menu_do(menu_t *, int); diff --git a/bl616/misterynano_fw/osd.h b/bl616/misterynano_fw/osd.h index 5b7b957..80166f3 100644 --- a/bl616/misterynano_fw/osd.h +++ b/bl616/misterynano_fw/osd.h @@ -16,6 +16,6 @@ typedef struct { osd_t *osd_init(spi_t *); void osd_enable(osd_t *, char); -void osd_emit(osd_t *, char [2], uint8_t); +void osd_emit(osd_t *, char, uint8_t); #endif // OSD_H diff --git a/bl616/misterynano_fw/osd_u8g2.c b/bl616/misterynano_fw/osd_u8g2.c index 93908e3..6c85a2e 100644 --- a/bl616/misterynano_fw/osd_u8g2.c +++ b/bl616/misterynano_fw/osd_u8g2.c @@ -100,18 +100,26 @@ void osd_enable(osd_t *osd, char en) { spi_end(osd->spi); } -void osd_emit(osd_t *osd, char id[2], uint8_t value) { - printf("OSD emit %c%c = %d\r\n", id[0], id[1], value); +void osd_emit(osd_t *osd, char id, uint8_t value) { + printf("OSD emit %c = %d\r\n", id, value); spi_begin(osd->spi); spi_tx_u08(osd->spi, SPI_TARGET_OSD); spi_tx_u08(osd->spi, SPI_OSD_SET); // send value command - spi_tx_u08(osd->spi, id[0]); // value id hi - spi_tx_u08(osd->spi, id[1]); // value id low + spi_tx_u08(osd->spi, id); // value id spi_tx_u08(osd->spi, value); // value itself spi_end(osd->spi); } +// TODO: reorg to make space for floppy b and acsi +void osd_message(osd_t *osd, char *message) { + u8g2_SetFont(&(osd->u8g2), u8g2_font_6x10_tf); + u8g2_SetFontRefHeightAll(&(osd->u8g2)); /* this will add some extra space for the text inside the buttons */ + u8g2_UserInterfaceMessage(&(osd->u8g2), message, "Title2", "Title3", " Ok "); + + osd_enable(osd, 1); +} + osd_t *osd_init(spi_t *spi) { // prepare u8g2 static osd_t osd; diff --git a/bl616/misterynano_fw/sdc.c b/bl616/misterynano_fw/sdc.c index 51f808e..54329ea 100644 --- a/bl616/misterynano_fw/sdc.c +++ b/bl616/misterynano_fw/sdc.c @@ -12,7 +12,7 @@ static spi_t *spi; static FATFS fs; -static FIL fil; +static FIL fil[2]; static DWORD *lktbl = NULL; @@ -52,6 +52,19 @@ static void hexdump(void *data, int size) { } int sdc_read_sector(unsigned long sector, unsigned char *buffer) { + // check if sd card is still busy as it may + // be reading a sector for the core. Forcing a MCU read + // may change the data direction from core to mcu while + // the core is still reading + unsigned char status; + do { + sdc_spi_begin(spi); + spi_tx_u08(spi, SPI_SDC_STATUS); + status = spi_tx_u08(spi, 0); + spi_end(spi); + printf("SD status = %x\r\n", status); + } while(status & 0x02); // card busy? + sdc_spi_begin(spi); spi_tx_u08(spi, SPI_SDC_MCU_READ); spi_tx_u08(spi, (sector >> 24) & 0xff); @@ -114,7 +127,22 @@ static int fs_init() { MSC_DiskioDriver.error_code_parsing = Translate_Result_Code; disk_driver_callback_init(DEV_SD, &MSC_DiskioDriver); - + + // wait for SD card to become available + // TODO: Add timeout and display error in OSD + unsigned char status; + do { + sdc_spi_begin(spi); + spi_tx_u08(spi, SPI_SDC_STATUS); + status = spi_tx_u08(spi, 0); + spi_end(spi); + } while((status & 0xf0) != 0x80); + + char *type[] = { "UNKNOWN", "SDv1", "SDv2", "SDHCv2" }; + printf("SDC status: %02x\r\n", status); + printf(" card status: %d\r\n", (status >> 4)&15); + printf(" card type: %s\r\n", type[(status >> 2)&3]); + res_msc = f_mount(&fs, CARD_MOUNTPOINT, 1); if (res_msc != FR_OK) { printf("mount fail,res:%d\r\n", res_msc); @@ -133,33 +161,32 @@ int sdc_poll(void) { // read sd status sdc_spi_begin(spi); spi_tx_u08(spi, SPI_SDC_STATUS); - unsigned char status = spi_tx_u08(spi, 0); + spi_tx_u08(spi, 0); + unsigned char request = spi_tx_u08(spi, 0); unsigned long rsector = 0; for(int i=0;i<4;i++) rsector = (rsector << 8) | spi_tx_u08(spi, 0); spi_end(spi); -#if 0 - char *type[] = { "UNKNOWN", "SDv1", "SDv2", "SDHCv2" }; - printf("SDC status: %02x\r\n", status); - printf(" card status: %d\r\n", (status >> 4)&15); - printf(" card type: %s\r\n", type[(status >> 2)&3]); -#endif + int drive = 0; // 0 = Drive A: + if(request == 2) drive = 1; // 1 = Drive B: - if(status & 1) { - if(!fil.flag) { - // no file selected, nak to make core clear the interrupt + if(request & 3) { + if(!fil[drive].flag) { + // no file selected + // this should actually never happen as the core won't request + // data if it hasn't been told that an image is inserted return -1; } // ---- figure out which physical sector to use ---- // translate sector into a cluster number inside image - f_lseek(&fil, (rsector+1)*512); + f_lseek(&fil[drive], (rsector+1)*512); // and add sector offset within cluster - unsigned long dsector = clst2sect(fil.clust) + rsector%fs.csize; + unsigned long dsector = clst2sect(fil[drive].clust) + rsector%fs.csize; - printf("request sector %lu -> %lu\r\n", rsector, dsector); + printf("%c: sector %lu -> %lu\r\n", drive?'B':'A', rsector, dsector); // send sector number to core, so it can fetch the right // sector from it's local sd card @@ -175,15 +202,16 @@ int sdc_poll(void) { return 0; } -int sdc_image_inserted(unsigned long size) { +static int sdc_image_inserted(char drive, unsigned long size) { // report the size of the inserted image to the core. This is needed // to guess sector/track/side information for floppy disk images, so the // core can translate from floppy disk to LBA - printf("Image inserted. Size = %d\r\n", size); + printf("%c: inserted. Size = %d\r\n", drive?'B':'A', size); sdc_spi_begin(spi); spi_tx_u08(spi, SPI_SDC_INSERTED); + spi_tx_u08(spi, drive); // 0 = Disk A:, 1 = Disk B: spi_tx_u08(spi, (size >> 24) & 0xff); spi_tx_u08(spi, (size >> 16) & 0xff); spi_tx_u08(spi, (size >> 8) & 0xff); @@ -193,39 +221,39 @@ int sdc_image_inserted(unsigned long size) { return 0; } -int sdc_image_open(char *name) { +int sdc_image_open(int drive, char *name) { char fname[strlen(cwd) + strlen(name) + 2]; strcpy(fname, cwd); strcat(fname, "/"); strcat(fname, name); // tell core that the "disk" has been removed - sdc_image_inserted(0); + sdc_image_inserted(drive, 0); // close any previous image, especially free the link table - if(fil.cltbl) { + if(fil[drive].cltbl) { printf("freeing link table\r\n"); free(lktbl); lktbl = NULL; - fil.cltbl = NULL; + fil[drive].cltbl = NULL; } printf("Mounting %s\r\n", fname); - if(f_open(&fil, fname, FA_OPEN_EXISTING | FA_READ) != 0) { + if(f_open(&fil[drive], fname, FA_OPEN_EXISTING | FA_READ) != 0) { printf("file open failed\r\n"); return -1; } else { - printf("file opened, cl=%d(%d)\r\n", fil.obj.sclust, clst2sect(fil.obj.sclust)); + printf("file opened, cl=%d(%d)\r\n", fil[drive].obj.sclust, clst2sect(fil[drive].obj.sclust)); printf("File len = %d, spc = %d, clusters = %d\r\n", - fil.obj.objsize, fs.csize, fil.obj.objsize / 512 / fs.csize); + fil[drive].obj.objsize, fs.csize, fil[drive].obj.objsize / 512 / fs.csize); // try with a 16 entry link table lktbl = malloc(16 * sizeof(DWORD)); - fil.cltbl = lktbl; + fil[drive].cltbl = lktbl; lktbl[0] = 16; - if(f_lseek(&fil, CREATE_LINKMAP)) { + if(f_lseek(&fil[drive], CREATE_LINKMAP)) { // this isn't really a problem. But sector access will // be slower printf("Link table creation failed, required size: %d\r\n", lktbl[0]); @@ -234,11 +262,11 @@ int sdc_image_open(char *name) { lktbl = realloc(lktbl, sizeof(DWORD) * lktbl[0]); // and retry link table creation - if(f_lseek(&fil, CREATE_LINKMAP)) { + if(f_lseek(&fil[drive], CREATE_LINKMAP)) { printf("Link table creation finally failed, required size: %d\r\n", lktbl[0]); free(lktbl); lktbl = NULL; - fil.cltbl = NULL; + fil[drive].cltbl = NULL; return -1; } else @@ -247,7 +275,7 @@ int sdc_image_open(char *name) { } // image has successfully been opened, so report image size to core - sdc_image_inserted(fil.obj.objsize); + sdc_image_inserted(drive, fil[drive].obj.objsize); return 0; } @@ -255,9 +283,6 @@ int sdc_image_open(char *name) { sdc_dir_t *sdc_readdir(char *name) { static sdc_dir_t sdc_dir = { 0, NULL }; - // set default path - if(!cwd) cwd = strdup(CARD_MOUNTPOINT); - int dir_compare(const void *p1, const void *p2) { sdc_dir_entry_t *d1 = (sdc_dir_entry_t *)p1; sdc_dir_entry_t *d2 = (sdc_dir_entry_t *)p2; @@ -344,6 +369,13 @@ int sdc_init(spi_t *p_spi) { fs_init(); + // set default path + if(!cwd) cwd = strdup(CARD_MOUNTPOINT); + + // try to open default images + sdc_image_open(0, "disk_a.st"); + sdc_image_open(1, "disk_b.st"); + sdc_poll(); return 0; diff --git a/bl616/misterynano_fw/sdc.h b/bl616/misterynano_fw/sdc.h index b266f82..92cb8e4 100644 --- a/bl616/misterynano_fw/sdc.h +++ b/bl616/misterynano_fw/sdc.h @@ -15,9 +15,8 @@ typedef struct { } sdc_dir_t; int sdc_init(spi_t *spi); -int sdc_image_open(char *name); +int sdc_image_open(int drive, char *name); sdc_dir_t *sdc_readdir(char *name); int sdc_poll(void); -int sdc_image_inserted(unsigned long size); #endif // SDC_H diff --git a/bl616/misterynano_fw/sdl_menu_test.c b/bl616/misterynano_fw/sdl_menu_test.c index e27d06e..053d263 100644 --- a/bl616/misterynano_fw/sdl_menu_test.c +++ b/bl616/misterynano_fw/sdl_menu_test.c @@ -4,14 +4,21 @@ #include #include #include -#include "mui.h" -#include "mui_u8g2.h" #include "ff.h" #include "diskio.h" + #include "menu.h" +/* TODO + - font fix + - usb message from cb + - long filename scroll + - config + - scanlines + - volume + */ + u8g2_t u8g2; -mui_t ui; static FATFS fs; static FIL fil; @@ -172,7 +179,7 @@ sdc_dir_t *sdc_readdir(char *name) { void osd_enable(osd_t *, char) { } -void osd_emit(osd_t *, char id[2], uint8_t v) { +void osd_emit(osd_t *, const char id[2], uint8_t v) { printf("EMIT %c%c=%d\n", id[0], id[1], v); } @@ -183,17 +190,12 @@ static LBA_t clst2sect(DWORD clst) { } int main(void) { - u8g2_SetupBuffer_SDL_128x64_4(&u8g2, &u8g2_cb_r0); + u8g2_SetupBuffer_SDL_128x64(&u8g2, &u8g2_cb_r0); u8x8_InitDisplay(u8g2_GetU8x8(&u8g2)); - u8x8_SetPowerSave(u8g2_GetU8x8(&u8g2), 0); - - u8x8_ConnectBitmapToU8x8(u8g2_GetU8x8(&u8g2)); /* connect to bitmap */ - - u8g2_SetFontMode(&u8g2, 1); fs_init(); - menu_t *menu = menu_init(&u8g2, &ui); + menu_t *menu = menu_init(&u8g2); menu_do(menu, 0); int k = -1; @@ -203,6 +205,8 @@ int main(void) { if(k >= 0) { // printf("K = %d\n", k); int event = 0; + if ( k == 276 ) event = MENU_EVENT_LEFT; + if ( k == 275 ) event = MENU_EVENT_RIGHT; if ( k == 274 ) event = MENU_EVENT_DOWN; if ( k == 273 ) event = MENU_EVENT_UP; if ( k == ' ' ) event = MENU_EVENT_SELECT; diff --git a/bl616/misterynano_fw/spi.h b/bl616/misterynano_fw/spi.h index 63bc1fa..225afb2 100644 --- a/bl616/misterynano_fw/spi.h +++ b/bl616/misterynano_fw/spi.h @@ -10,6 +10,7 @@ #define SPI_HID_STATUS 0 #define SPI_HID_KEYBOARD 1 #define SPI_HID_MOUSE 2 +#define SPI_HID_JOYSTICK 3 #define SPI_TARGET_OSD 2 // on-screen-display #define SPI_OSD_ENABLE 1 @@ -20,7 +21,7 @@ #define SPI_SDC_STATUS 1 // get sd card status #define SPI_SDC_READ 2 // trigger core read #define SPI_SDC_MCU_READ 3 // read sector into MCU (e.g. for dir listing) -#define SPI_SDC_INSERTED 4 // inform core that some disk image has been inserted +#define SPI_SDC_INSERTED 4 // inform core that some disk image has been insered typedef struct { #ifndef SDL diff --git a/bl616/misterynano_fw/usb_host.c b/bl616/misterynano_fw/usb_host.c index 41b400f..cffc33f 100644 --- a/bl616/misterynano_fw/usb_host.c +++ b/bl616/misterynano_fw/usb_host.c @@ -104,7 +104,8 @@ void kbd_parse(struct usb_config *usb, unsigned char *buffer, int nbytes) { // check if cursor up/down or space has been pressed if(buffer[2+i] == 0x51) msg = MENU_EVENT_DOWN; if(buffer[2+i] == 0x52) msg = MENU_EVENT_UP; - if(buffer[2+i] == 0x2c) msg = MENU_EVENT_SELECT; + if((buffer[2+i] == 0x2c) || (buffer[2+i] == 0x28)) + msg = MENU_EVENT_SELECT; } } @@ -129,6 +130,20 @@ void mouse_parse(struct usb_config *usb, signed char *buffer, int nbytes) { xQueueSendToBackFromISR(iQueue, mouse_cmd, ( TickType_t ) 0); } +void rii_joy_parse(struct usb_config *usb, unsigned char *buffer) { + unsigned char b = 0; + if(buffer[0] == 0xcd && buffer[1] == 0x00) b = 0x10; // cd == play/pause -> center + if(buffer[0] == 0xe9 && buffer[1] == 0x00) b = 0x08; // e9 == V+ -> up + if(buffer[0] == 0xea && buffer[1] == 0x00) b = 0x04; // ea == V- -> down + if(buffer[0] == 0xb6 && buffer[1] == 0x00) b = 0x02; // b6 == skip prev -> left + if(buffer[0] == 0xb5 && buffer[1] == 0x00) b = 0x01; // b5 == skip next -> right + + printf("RII Joy: %02x\r\n", b); + + unsigned char rii_joy_cmd[4] = { SPI_HID_JOYSTICK, b }; + xQueueSendToBackFromISR(iQueue, rii_joy_cmd, ( TickType_t ) 0); +} + void usbh_hid_callback(void *arg, int nbytes) { struct hid_info_S *hid_info = (struct hid_info_S *)arg; @@ -150,7 +165,18 @@ void usbh_hid_callback(void *arg, int nbytes) { USB_LOG_RAW("\r\n"); // parse reply - + + // the following is a hack for the Rii keyboard/touch combos to use the + // left top multimedia pad as a joystick. These special keys are sent + // via the mouse/touchpad part + if(hid_info->report.report_id_present && + hid_info->report.type == REPORT_TYPE_MOUSE && + nbytes == 3 && + hid_info->buffer[0] == 0x01) { + rii_joy_parse(hid_info->usb, hid_info->buffer+1); + return; + } + // check and skip report id if present unsigned char *buffer = hid_info->buffer; if(hid_info->report.report_id_present) { diff --git a/sim/floppy_tb/floppy_tb.cpp b/sim/floppy_tb/floppy_tb.cpp index 7845e97..5b22a8f 100644 --- a/sim/floppy_tb/floppy_tb.cpp +++ b/sim/floppy_tb/floppy_tb.cpp @@ -252,7 +252,8 @@ void mcu_read_sector(unsigned long sector, unsigned char *buffer) { void mcu_poll(void) { // MCU requests sd card status - unsigned char status = mcu_write_byte(0x01, 1); + unsigned char status = mcu_write_byte(0x01, 1); + unsigned char request = mcu_write_byte(0, 0); // push in one more byte // unsigned char status = mcu_read_byte(); printf("STATUS = %02x\n", status); @@ -265,7 +266,7 @@ void mcu_poll(void) { for(int i=0;i<4;i++) sector = (sector << 8) | mcu_read_byte(); - if(status & 1) { + if(request & 1) { run(100); if(!fil.flag) return; @@ -282,9 +283,9 @@ void mcu_poll(void) { // in return request core to load sector + 100 unsigned char status = mcu_write_byte(0x02, 1); - - for(int i=0;i<4;i++) - mcu_write_byte((dsector >> 8*(3-i))&0xff, 0); + + // write sector number to load + for(int i=0;i<4;i++) mcu_write_byte((dsector >> 8*(3-i))&0xff, 0); } } @@ -439,9 +440,10 @@ int main(int argc, char **argv) { wait_ms(40); - read_sector(0, 3); + read_sector(0, 3); + read_sector(0, 4); - wait_ms(10); + wait_ms(20); #else run(10000); diff --git a/src/misc/hid.v b/src/misc/hid.v index 85be044..4bb9c34 100644 --- a/src/misc/hid.v +++ b/src/misc/hid.v @@ -14,7 +14,8 @@ module hid ( output reg [7:0] data_out, output [5:0] mouse, - output reg [7:0] keyboard[14:0] + output reg [7:0] keyboard[14:0], + output reg [4:0] joystick ); reg [1:0] mouse_btns; @@ -72,6 +73,11 @@ always @(posedge clk) begin if(state == 4'd2) mouse_x_cnt <= mouse_x_cnt + data_in; if(state == 4'd3) mouse_y_cnt <= mouse_y_cnt + data_in; end + + // digital joystick data? + if(device == 8'd3) begin + if(state == 4'd1) joystick <= data_in[4:0]; + end end end else begin // if (data_in_strobe) mouse_div <= mouse_div + 14'd1; diff --git a/src/misc/osd_u8g2.v b/src/misc/osd_u8g2.v index deb069e..ad8a99d 100644 --- a/src/misc/osd_u8g2.v +++ b/src/misc/osd_u8g2.v @@ -23,7 +23,9 @@ module osd_u8g2 ( output reg [1:0] system_chipset, output reg system_memory, output reg system_video, - output reg system_reset, + output reg [1:0] system_reset, + output reg [1:0] system_scanlines, + output reg [1:0] system_volume, output [5:0] r_out, output [5:0] g_out, @@ -125,23 +127,21 @@ always @(posedge clk) begin // OSD command 3: config values set by user via OSD if(command == 8'd3) begin - if(data_cnt == 10'd0) begin - // first byte must be "S" - if(data_in == "S") data_cnt <= 10'd1; - else command <= 8'h00; - end - - // second byte can be any character - if(data_cnt == 10'd1) data_cnt <= { 2'b00, data_in }; + // second byte can be any character which identifies the variable to set + if(data_cnt == 10'd0) data_cnt <= { 2'b00, data_in }; - // Value "SC": chipset ST(0), MegaST(1) or STE(2) + // Value "C": chipset ST(0), MegaST(1) or STE(2) if(data_cnt == { 2'b00, "C" }) system_chipset <= data_in[1:0]; - // Value "SM": 4MB(0) or 8MB(1) + // Value "M": 4MB(0) or 8MB(1) if(data_cnt == { 2'b00, "M" }) system_memory <= data_in[0]; - // Value "SV": color(0) or monochrome(1) + // Value "V": color(0) or monochrome(1) if(data_cnt == { 2'b00, "V" }) system_video <= data_in[0]; - // Value "SR": reset(1) or run(0) - if(data_cnt == { 2'b00, "R" }) system_reset <= data_in[0]; + // Value "R": coldboot(3), reset(1) or run(0) + if(data_cnt == { 2'b00, "R" }) system_reset <= data_in[1:0]; + // Value "S": scanlines none(0), 25%(1), 50%(2) or 75%(3) + if(data_cnt == { 2'b00, "S" }) system_scanlines <= data_in[1:0]; + // Value "A": volume mute(0), 33%(1), 66%(2) or 100%(3) + if(data_cnt == { 2'b00, "A" }) system_volume <= data_in[1:0]; end end end diff --git a/src/misc/sd_card.v b/src/misc/sd_card.v index 7759cc5..f30508c 100644 --- a/src/misc/sd_card.v +++ b/src/misc/sd_card.v @@ -32,8 +32,8 @@ module sd_card # ( // sd card. Now this goes to the MCU via the MCU interface as the MCU translates sector // numbers from those the core tries to use to physical ones inside the file system // of the sd card - input rstart, - input [31:0] rsector, + input [1:0] rstart, + input [31:0] rsector, output rbusy, output rdone, @@ -49,6 +49,8 @@ wire [1:0] card_type; // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2 reg [7:0] command; reg [3:0] byte_cnt; +reg [7:0] image_target; + reg lstart; reg [31:0] lsector; @@ -57,11 +59,18 @@ reg mcu_tx_data; reg [8:0] mcu_tx_cnt; reg [7:0] buffer [512]; +// Keep track of current sector destination. We cannot use the command +// directly as the MCU may alter this during sector transfer +reg read_to_mcu; + always @(posedge clk) begin - if(command == 8'd3 && louten) + if(read_to_mcu && louten) buffer[outaddr] <= outbyte; end - + +// register the rising edge of rstart and clear it once +// it has been reported to the MCU + always @(posedge clk) begin if(!rstn) begin byte_cnt <= 4'd15; @@ -72,18 +81,25 @@ always @(posedge clk) begin end else begin // done from sd reader acknowledges/clears start if(rdone) lstart <= 1'b0; - + if(data_strobe) begin if(data_start) begin command <= data_in; + + // differentiate between the two reads + if(data_in == 8'd2 || data_in == 8'd3) + read_to_mcu <= (data_in == 8'd3); + byte_cnt <= 4'd0; - data_out <= { card_stat, card_type, 1'b0, rstart }; + data_out <= { card_stat, card_type, rbusy, 1'b0 }; end else begin if(command == 8'd1) begin - if(byte_cnt == 4'd0) data_out <= rsector[31:24]; - if(byte_cnt == 4'd1) data_out <= rsector[23:16]; - if(byte_cnt == 4'd2) data_out <= rsector[15: 8]; - if(byte_cnt == 4'd3) data_out <= rsector[ 7: 0]; + // request byte + if(byte_cnt == 4'd0) data_out <= { 6'b00000, rstart }; + if(byte_cnt == 4'd1) data_out <= rsector[31:24]; + if(byte_cnt == 4'd2) data_out <= rsector[23:16]; + if(byte_cnt == 4'd3) data_out <= rsector[15: 8]; + if(byte_cnt == 4'd4) data_out <= rsector[ 7: 0]; end if(command == 8'd2 || command == 8'd3) begin @@ -123,12 +139,14 @@ always @(posedge clk) begin if(command == 8'd4) begin // MCU reports that some image has been inserted. If the image size // is 0, then no image is inserted - if(byte_cnt == 4'd0) image_size[31:24] <= data_in; - if(byte_cnt == 4'd1) image_size[23:16] <= data_in; - if(byte_cnt == 4'd2) image_size[15:8] <= data_in; - if(byte_cnt == 4'd3) begin + if(byte_cnt == 4'd0) image_target <= data_in; + if(byte_cnt == 4'd1) image_size[31:24] <= data_in; + if(byte_cnt == 4'd2) image_size[23:16] <= data_in; + if(byte_cnt == 4'd3) image_size[15:8] <= data_in; + if(byte_cnt == 4'd4) begin image_size[7:0] <= data_in; - image_mounted[0] <= (image_size[31:8] != 24'd0 || data_in != 8'd0)?1'b1:1'b0; + if(image_target <= 8'd1) + image_mounted[image_target] <= (image_size[31:8] != 24'd0 || data_in != 8'd0)?1'b1:1'b0; end end @@ -140,7 +158,7 @@ end // only export outen if the resulting data is for the core wire louten; -assign outen = (command == 2'd2)?louten:1'b0; +assign outen = read_to_mcu?1'b0:louten; sd_reader #(.CLK_DIV(CLK_DIV), .SIMULATE(SIMULATE)) sd_reader ( // rstn active-low, 1:working, 0:reset diff --git a/src/tangnano20k/top.sv b/src/tangnano20k/top.sv index 7eaf8dc..142c4eb 100644 --- a/src/tangnano20k/top.sv +++ b/src/tangnano20k/top.sv @@ -109,12 +109,26 @@ wire [15:0] mdin; // in from ram wire ram_ready; wire refresh; -// address bit 4 is inverted in 8MB setup. This is done to make -// sure the memory layout is different in 4MB and 8MB setup and -// when being switched the ram contents appear invalid and ram is -// retested by tos after a change of ram size -wire [22:1] ram_a_s = { ram_a[22:5], ram_a[4]^osd_system_memory, ram_a[3:1] }; - +// osd_system_reset[1] indicates whether a coldboot is requested. This +// can either be triggered imlicitely by the user changing hardweare +// specs (ST vs. STE or RAM size) or explicitely via an OSD menu entry. +// A cold boot means that the ram contents becomoe invalid. We achieve this +// by scrambling the RAM address space a little bit on every rising edge +// of osd_system_reset[1] +reg [1:0] ram_scramble; +always @(posedge clk_32) begin + reg cb_D; + cb_D <= osd_system_reset[1]; + + if(osd_system_reset[1] && !cb_D) + ram_scramble <= ram_scramble + 2'd1; +end + +// RAM is scrambled by xor'ing adress lines 3 and 4 with the scramble bits +wire [22:1] ram_a_s = { ram_a[22:5], + ram_a[4:3] ^ ram_scramble, + ram_a[2:1] }; + sdram sdram ( .clk(clk_32), .reset_n(pll_lock), @@ -192,11 +206,11 @@ mcu_spi mcu ( .mcu_sdc_din(sdc_data_out) ); -// joy0 is usually used for the mouse, joy1 for the joystick -// in HID mode the joystick uses the inputs that were formerly -// used for the mouse. This may also be replaced by USB joysticks +// joy0 is usually used for the mouse, joy1 for the joystick. The +// joystick can either be driven from the external MCU or via FPGA IO pins wire [5:0] joy0; -wire [4:0] joy1 = { !io[0], !io[2], !io[1], !io[4], !io[3] }; +wire [4:0] hid_joy1; +wire [4:0] joy1 = hid_joy1 | { !io[0], !io[2], !io[1], !io[4], !io[3] }; // The keyboard matrix is maintained inside HID wire [7:0] keyboard[14:0]; @@ -232,7 +246,8 @@ hid hid ( .data_out(hid_data_out), .mouse(joy0), - .keyboard(keyboard) + .keyboard(keyboard), + .joystick(hid_joy1) ); @@ -250,11 +265,11 @@ wire [1:0] sd_img_mounted; wire [1:0] osd_system_chipset; wire osd_system_memory; wire osd_system_video; -wire osd_system_reset; +wire [1:0] osd_system_reset; atarist atarist ( .clk_32(clk_32), - .resb(!osd_system_reset && !reset && pll_lock && ram_ready && flash_ready && sd_ready), // user reset button + .resb(!osd_system_reset[0] && !reset && pll_lock && ram_ready && flash_ready && sd_ready), // user reset button .porb(pll_lock), // video output @@ -399,7 +414,7 @@ sd_card #( .image_mounted(sd_img_mounted), // user read sector command interface (sync with clk) - .rstart(sd_rd[0]), + .rstart(sd_rd), .rsector(sd_lba), .rbusy(sd_busy), .rdone(sd_done), diff --git a/src/tangnano20k/video.v b/src/tangnano20k/video.v index e649633..2fa9c78 100644 --- a/src/tangnano20k/video.v +++ b/src/tangnano20k/video.v @@ -21,11 +21,11 @@ module video ( input mcu_osd_strobe, input [7:0] mcu_data, - // values that can be configure by the user via osd + // values that can be configure by the user via osd output [1:0] osd_system_chipset, output osd_system_memory, output osd_system_video, - output osd_system_reset, + output [1:0] osd_system_reset, // reset and coldboot flag // hdmi/tdms output tmds_clk_n, @@ -81,6 +81,9 @@ video_analyzer video_analyzer ( .vreset(vreset) // reset signal ); +wire [1:0] osd_system_scanlines; +wire [1:0] osd_system_volume; + wire sd_hs_n, sd_vs_n; wire [5:0] sd_r; wire [5:0] sd_g; @@ -94,7 +97,7 @@ scandoubler #(10) scandoubler ( .pixel_ena(), // scanlines (00-none 01-25% 10-50% 11-75%) - .scanlines(2'b00), + .scanlines(osd_system_scanlines), // shifter video interface .hs_in(hs_in_n), @@ -114,7 +117,7 @@ scandoubler #(10) scandoubler ( wire [5:0] osd_r; wire [5:0] osd_g; wire [5:0] osd_b; - + osd_u8g2 osd_u8g2 ( .clk(clk_pixel), .reset(!pll_lock), @@ -134,6 +137,8 @@ osd_u8g2 osd_u8g2 ( .system_memory(osd_system_memory), .system_video(osd_system_video), .system_reset(osd_system_reset), + .system_scanlines(osd_system_scanlines), + .system_volume(osd_system_volume), .r_out(osd_r), .g_out(osd_g), @@ -143,6 +148,19 @@ osd_u8g2 osd_u8g2 ( wire [2:0] tmds; wire tmds_clock; +// scale audio for valume by signed division +wire [15:0] audio_vol_l = + (osd_system_volume == 2'd0)?16'd0: + (osd_system_volume == 2'd1)?{ {2{audio_l[15]}}, audio_l[15:2] }: + (osd_system_volume == 2'd2)?{ audio_l[15], audio_l[15:1] }: + audio_l; + +wire [15:0] audio_vol_r = + (osd_system_volume == 2'd0)?16'd0: + (osd_system_volume == 2'd1)?{ {2{audio_r[15]}}, audio_r[15:2] }: + (osd_system_volume == 2'd2)?{ audio_r[15], audio_r[15:1] }: + audio_r; + hdmi #( .AUDIO_RATE(48000), .AUDIO_BIT_WIDTH(16), .VENDOR_NAME( { "MiSTle", 16'd0} ), @@ -151,7 +169,7 @@ hdmi #( .clk_pixel_x5(clk_pixel_x5), .clk_pixel(clk_pixel), .clk_audio(clk_audio), - .audio_sample_word( { audio_l, audio_r } ), + .audio_sample_word( { audio_vol_l, audio_vol_r } ), .tmds(tmds), .tmds_clock(tmds_clock),