├── .gitignore ├── LICENSE.txt ├── README.md ├── examples ├── ArduBreakout │ ├── ArduBreakout.ino │ └── README.md ├── Buttons │ ├── Buttons.ino │ └── README.md ├── Double_Buffering │ └── Double_Buffering.ino ├── HelloWorld │ └── HelloWorld.ino └── MemoryCard │ └── MemoryCard │ └── MemoryCard.ino ├── keywords.txt ├── library.properties └── src ├── MicroGamer.cpp ├── MicroGamer.h ├── MicroGamerAudio.cpp ├── MicroGamerAudio.h ├── MicroGamerCore.cpp ├── MicroGamerCore.h ├── MicroGamerMemoryCard.cpp ├── MicroGamerMemoryCard.h ├── MicroGamerTones.cpp ├── MicroGamerTones.h ├── MicroGamerTonesPitches.h ├── Sprites.cpp ├── Sprites.h ├── ab_logo.c └── glcdfont.c /.gitignore: -------------------------------------------------------------------------------- 1 | doxygen/ 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | \page licenses Software License Agreements 3 | \verbatim 4 | 5 | Software License Agreements 6 | 7 | ------------------------------------------------------------------------------- 8 | Licensed under the BSD 3-clause license: 9 | 10 | MicroGamer library: 11 | Copyright (c) 2018, Fabien Chouteau 12 | All rights reserved. 13 | 14 | The MicroGamer library was forked from the Arduboy2 library: 15 | https://github.com/MLXXXp/Arduboy2 16 | 17 | Redistribution and use in source and binary forms, with or without 18 | modification, are permitted provided that the following conditions are met: 19 | 1. Redistributions of source code must retain the above copyright 20 | notice, this list of conditions and the following disclaimer. 21 | 2. Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 3. Neither the name of the copyright holders nor the 25 | names of its contributors may be used to endorse or promote products 26 | derived from this software without specific prior written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY 29 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 30 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 31 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 32 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 33 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 34 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 35 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 37 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | ------------------------------------------------------------------------------- 40 | Licensed under the BSD 3-clause license: 41 | 42 | Arduboy2 library: 43 | Copyright (c) 2016-2017, Scott Allen 44 | All rights reserved. 45 | 46 | The Arduboy2 library was forked from the Arduboy library: 47 | https://github.com/Arduboy/Arduboy 48 | Copyright (c) 2016, Kevin "Arduboy" Bates 49 | Copyright (c) 2016, Chris Martinez 50 | Copyright (c) 2016, Josh Goebel 51 | Copyright (c) 2016, Scott Allen 52 | All rights reserved. 53 | which is in turn partially based on the Adafruit_SSD1306 library 54 | https://github.com/adafruit/Adafruit_SSD1306 55 | Copyright (c) 2012, Adafruit Industries 56 | All rights reserved. 57 | 58 | SetNameAndID example sketch: 59 | Copyright (c) 2017, Scott Allen 60 | All rights reserved. 61 | 62 | Redistribution and use in source and binary forms, with or without 63 | modification, are permitted provided that the following conditions are met: 64 | 1. Redistributions of source code must retain the above copyright 65 | notice, this list of conditions and the following disclaimer. 66 | 2. Redistributions in binary form must reproduce the above copyright 67 | notice, this list of conditions and the following disclaimer in the 68 | documentation and/or other materials provided with the distribution. 69 | 3. Neither the name of the copyright holders nor the 70 | names of its contributors may be used to endorse or promote products 71 | derived from this software without specific prior written permission. 72 | 73 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY 74 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 75 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 76 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 77 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 78 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 79 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 80 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 81 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 82 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 83 | 84 | ------------------------------------------------------------------------------- 85 | Licensed under the BSD 2-clause license: 86 | 87 | Portions of the Arduboy library, and thus portions of the Arduboy2 library, 88 | based on the Adafruit-GFX library: 89 | https://github.com/adafruit/Adafruit-GFX-Library 90 | Copyright (c) 2012 Adafruit Industries 91 | All rights reserved. 92 | 93 | Redistribution and use in source and binary forms, with or without 94 | modification, are permitted provided that the following conditions are met: 95 | 96 | - Redistributions of source code must retain the above copyright notice, 97 | this list of conditions and the following disclaimer. 98 | - Redistributions in binary form must reproduce the above copyright notice, 99 | this list of conditions and the following disclaimer in the documentation 100 | and/or other materials provided with the distribution. 101 | 102 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 103 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 104 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 105 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 106 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 107 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 108 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 109 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 110 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 111 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 112 | POSSIBILITY OF SUCH DAMAGE. 113 | 114 | ------------------------------------------------------------------------------- 115 | Licensed under the MIT license: 116 | 117 | Code from ArduboyExtra: 118 | https://github.com/yyyc514/ArduboyExtra 119 | Copyright (c) 2015 Josh Goebel 120 | 121 | Code for drawing compressed bitmaps: 122 | https://github.com/TEAMarg/drawCompressed 123 | Copyright (c) 2016 TEAM a.r.g. 124 | 125 | Permission is hereby granted, free of charge, to any person obtaining a copy 126 | of this software and associated documentation files (the "Software"), to deal 127 | in the Software without restriction, including without limitation the rights 128 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 129 | copies of the Software, and to permit persons to whom the Software is 130 | furnished to do so, subject to the following conditions: 131 | 132 | The above copyright notice and this permission notice shall be included in all 133 | copies or substantial portions of the Software. 134 | 135 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 136 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 137 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 138 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 139 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 140 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 141 | SOFTWARE. 142 | 143 | ------------------------------------------------------------------------------- 144 | Licensed under the GNU LGPL license: 145 | https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 146 | 147 | ArduBreakout example sketch: 148 | Original work: 149 | Copyright (c) 2011 Sebastian Goscik 150 | All rights reserved. 151 | Modified work: 152 | Copyright (c) 2016 Scott Allen 153 | All rights reserved. 154 | 155 | Buttons and HelloWorld example sketches: 156 | Copyright (c) 2015 David Martinez 157 | All rights reserved. 158 | 159 | This work is free software; you can redistribute it and/or 160 | modify it under the terms of the GNU Lesser General Public 161 | License as published by the Free Software Foundation; either 162 | version 2.1 of the License, or (at your option) any later version. 163 | 164 | This library is distributed in the hope that it will be useful, 165 | but WITHOUT ANY WARRANTY; without even the implied warranty of 166 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 167 | Lesser General Public License for more details. 168 | 169 | =============================================================================== 170 | \endverbatim 171 | */ 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Micro:Gamer](https://hackaday.io/project/47760-microgamer) Arduino Library 2 | 3 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/MicroGamerConsole/Lobby) 4 | 5 | The Micro:Gamer Arduino library is a fork of the [Arduboy2 library](https://github.com/MLXXXp/Arduboy2), which provides a standard *application programming interface* (API) to the display, buttons and other hardware of the [Micro:Gamer](https://hackaday.io/project/47760-microgamer) miniature game system. 6 | 7 | ## Getting started 8 | 9 | - Follow the first page of AdaFruit's micro:bit guide for the Arduino IDE: 10 | 11 | https://learn.adafruit.com/use-micro-bit-with-arduino/install-board-and-blink 12 | 13 | - Install the Arduino Micro:Gamer library: 14 | - In the Arduino IDE select from the menus: `Sketch > Include Library > Manage Libraries...` 15 | - In the Library Manager *Filter your search...* field enter *microgamer*. 16 | - Click somewhere within the `MicroGamer` entry. 17 | - Click on the *Install* button. 18 | 19 | - Install your first game: 20 | - In the Arduino IDE, select from the menus: `File > Examples > MicroGamer -> ArduBreakout` 21 | - Connect your micro:bit board 22 | - In the Arduino IDE, click on the `Upload` button (arrow in a circle) 23 | 24 | - You can now try one of the games available: 25 | - TheBounceArduboy : https://github.com/MicroGamerConsole/TheBounceArduboy 26 | - Mine Sweeper : https://github.com/MicroGamerConsole/minesweeper 27 | - MicroCity : https://github.com/MicroGamerConsole/MicroCity 28 | - Galaxion : https://github.com/MicroGamerConsole/galaxion 29 | - Dark And Under : https://github.com/MicroGamerConsole/Dark-And-Under 30 | - Crates3D : https://github.com/MicroGamerConsole/Crates3D 31 | - Asteroids : https://github.com/MicroGamerConsole/arduino-asteroids 32 | - ArduBoyJetPack : https://github.com/MicroGamerConsole/ArduBoyJetPack 33 | - Arduboy3d : https://github.com/MicroGamerConsole/arduboy3d 34 | - ArduBreakout : https://github.com/uXeBoy/MicroGamer-ArduBreakout 35 | - A-Maze : https://github.com/uXeBoy/MicroGamer-A-Maze 36 | 37 | ## Library documentation 38 | 39 | Comments in the library header files are formatted for the [Doxygen](http://www.doxygen.org) document generation system. The HTML files generated using the configuration file _extras/Doxyfile_ can be found at: 40 | 41 | https://MLXXXp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/index.html 42 | 43 | A generated PDF file can be found at: 44 | 45 | https://MLXXXp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/pdf/Arduboy2.pdf 46 | 47 | ## Start up features 48 | 49 | The *begin()* function, used to initialize the library, includes features that are intended to be available to all sketches using the library (unless the sketch developer has chosen to disable one or more of them to free up some code space): 50 | 51 | ## Using the library in a sketch 52 | 53 | As with most libraries, to use MicroGamer in your sketch you must include its header file at the start: 54 | 55 | ```cpp 56 | #include 57 | ``` 58 | 59 | You must then create an MicroGamer class object: 60 | 61 | ```cpp 62 | MicroGamer mg; 63 | ``` 64 | 65 | To initialize the library, you must call its *begin()* function. This is usually done at the start of the sketch's *setup()* function: 66 | 67 | ```cpp 68 | void setup() 69 | { 70 | mg.begin(); 71 | // more setup code follows, if required 72 | } 73 | ``` 74 | 75 | The rest of the MicroGamer functions will now be available for use. 76 | 77 | If you wish to use the Sprites class functions you must create a Sprites object: 78 | 79 | ```cpp 80 | Sprites sprites; 81 | ``` 82 | 83 | Sample sketches have been included with the library as examples of how to use it. To load an example, for examination and uploading to the Arduboy, using the Arduino IDE menus select: 84 | 85 | `File > Examples > MicroGamer` 86 | 87 | ### Persistant data storage 88 | 89 | There is no EEPROM on the micro:bit board so the library uses the flash memory inside the micro-controller for persistant storage. 90 | 91 | The interface to use the flash as persistent storage is provided by the MicroGamerMemoryCard class. This class uses two different memory areas: 92 | 93 | - The first one is a 1k bytes page in the flash memory, this is where the data will be stored permanently. The reason for a fixed size of 1k bytes is because flash memory have to be erased/written by pages of 1k. 94 | 95 | - The second memory area is the temporary RAM buffer. This is where the program will read/write the data before saving it permanently in the flash page. Since there is not a lot of RAM available, the program can decide to have a temporary RAM buffer that is smaller than 1k. 96 | 97 | Here is an example of how to use it in your sketch: 98 | 99 | ```cpp 100 | #include 101 | 102 | // Create a memory card of one 32bit word 103 | MicroGamerMemoryCard mem(1); 104 | 105 | // Load the content of the flash page to the temporary RAM buffer 106 | mem.load(); 107 | 108 | // Read a value from the temporary RAM buffer 109 | if (mem.read(0) != 42) { 110 | 111 | // Write a value to the temporary RAM buffer 112 | mem.write(0, 42); 113 | 114 | // Permanently save the RAM buffer into flash memory 115 | mem.save(); 116 | } 117 | ``` 118 | 119 | ### Audio control functions 120 | 121 | The library includes an MicroGamerAudio class. This class provides functions to enable and disable (mute) sound. It doesn't contain anything to actually produce sound. 122 | 123 | The MicroGamerBase class, and thus the MircoGamer class, creates an MicroGamerAudio class object named *audio*, so a sketch doesn't need to create its own MicroGamerAudio object. 124 | 125 | Example: 126 | 127 | ```cpp 128 | #include 129 | 130 | MicroGamer mg; 131 | 132 | // MicroGamerAudio functions can be called as follows: 133 | mg.audio.on(); 134 | mg.audio.off(); 135 | ``` 136 | 137 | ### Ways to make more code space available to sketches 138 | 139 | #### Remove the text functions 140 | 141 | If your sketch doesn't use any of the functions for displaying text, such as *setCursor()* and *print()*, you can remove them. You could do this if your sketch generates whatever text it requires by some other means. Removing the text functions frees up code by not including the font table and some code that is always pulled in by inheriting the [Arduino *Print* class](http://playground.arduino.cc/Code/Printclass). 142 | 143 | To eliminate text capability in your sketch, when creating the library object simply use the *MicroGamerBase* class instead of *MicroGamer*: 144 | 145 | For example, if the object will be named *mg*: 146 | 147 | Replace 148 | 149 | ```cpp 150 | MicroGamer mg; 151 | ``` 152 | 153 | with 154 | 155 | ```cpp 156 | MicroGamerBase mg; 157 | ``` 158 | -------------------------------------------------------------------------------- /examples/ArduBreakout/ArduBreakout.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Breakout 3 | Copyright (C) 2011 Sebastian Goscik 4 | All rights reserved. 5 | 6 | Modifications by Scott Allen 2016 (after previous changes by ???) 7 | 8 | This library is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU Lesser General Public 10 | License as published by the Free Software Foundation; either 11 | version 2.1 of the License, or (at your option) any later version. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | // block in EEPROM to save high scores 19 | #define EE_FILE 2 20 | 21 | MicroGamer mg; 22 | MicroGamerMemoryCard mem(64/4); 23 | MicroGamerTones audio(mg.audio.enabled); 24 | 25 | const unsigned int COLUMNS = 13; //Columns of bricks 26 | const unsigned int ROWS = 4; //Rows of bricks 27 | int dx = -1; //Initial movement of ball 28 | int dy = -1; //Initial movement of ball 29 | int xb; //Balls starting possition 30 | int yb; //Balls starting possition 31 | boolean released; //If the ball has been released by the player 32 | boolean paused = false; //If the game has been paused 33 | byte xPaddle; //X position of paddle 34 | boolean isHit[ROWS][COLUMNS]; //Array of if bricks are hit or not 35 | boolean bounced=false; //Used to fix double bounce glitch 36 | byte lives = 3; //Amount of lives 37 | byte level = 1; //Current level 38 | unsigned int score=0; //Score for the game 39 | unsigned int brickCount; //Amount of bricks hit 40 | boolean pad, pad2, pad3; //Button press buffer used to stop pause repeating 41 | boolean oldpad, oldpad2, oldpad3; 42 | char text_buffer[16]; //General string buffer 43 | boolean start=false; //If in menu or in game 44 | boolean initialDraw=false;//If the inital draw has happened 45 | char initials[3]; //Initials used in high score 46 | 47 | //Ball Bounds used in collision detection 48 | byte leftBall; 49 | byte rightBall; 50 | byte topBall; 51 | byte bottomBall; 52 | 53 | //Brick Bounds used in collision detection 54 | byte leftBrick; 55 | byte rightBrick; 56 | byte topBrick; 57 | byte bottomBrick; 58 | 59 | byte tick; 60 | 61 | void setup() 62 | { 63 | mg.begin(); 64 | mg.setFrameRate(40); 65 | mg.initRandomSeed(); 66 | } 67 | 68 | void loop() 69 | { 70 | // pause render until it's time for the next frame 71 | if (!(mg.nextFrame())) 72 | return; 73 | 74 | //Title screen loop switches from title screen 75 | //and high scores until FIRE is pressed 76 | while (!start) 77 | { 78 | start = titleScreen(); 79 | if (!start) 80 | { 81 | start = displayHighScores(EE_FILE); 82 | } 83 | } 84 | 85 | //Initial level draw 86 | if (!initialDraw) 87 | { 88 | //Clears the screen 89 | mg.clear(); 90 | //Selects Font 91 | //Draws the new level 92 | level = 1; 93 | newLevel(); 94 | score = 0; 95 | initialDraw=true; 96 | } 97 | 98 | if (lives>0) 99 | { 100 | drawPaddle(); 101 | 102 | //Pause game if FIRE pressed 103 | pad = mg.pressed(Y_BUTTON) || mg.pressed(X_BUTTON); 104 | 105 | if(pad == true && oldpad == false && released) 106 | { 107 | oldpad2 = false; //Forces pad loop 2 to run once 108 | pause(); 109 | } 110 | 111 | oldpad = pad; 112 | drawBall(); 113 | 114 | if(brickCount == ROWS * COLUMNS) 115 | { 116 | level++; 117 | newLevel(); 118 | } 119 | } 120 | else 121 | { 122 | drawGameOver(); 123 | if (score > 0) 124 | { 125 | enterHighScore(EE_FILE); 126 | } 127 | 128 | mg.clear(); 129 | initialDraw=false; 130 | start=false; 131 | lives=3; 132 | newLevel(); 133 | } 134 | 135 | mg.display(); 136 | } 137 | 138 | void movePaddle() 139 | { 140 | //Move right 141 | if(xPaddle < WIDTH - 12) 142 | { 143 | if (mg.pressed(RIGHT_BUTTON)) 144 | { 145 | xPaddle+=2; 146 | } 147 | } 148 | 149 | //Move left 150 | if(xPaddle > 0) 151 | { 152 | if (mg.pressed(LEFT_BUTTON)) 153 | { 154 | xPaddle-=2; 155 | } 156 | } 157 | } 158 | 159 | void moveBall() 160 | { 161 | tick++; 162 | if(released) 163 | { 164 | //Move ball 165 | if (abs(dx)==2) { 166 | xb += dx/2; 167 | // 2x speed is really 1.5 speed 168 | if (tick%2==0) 169 | xb += dx/2; 170 | } else { 171 | xb += dx; 172 | } 173 | yb=yb + dy; 174 | 175 | //Set bounds 176 | leftBall = xb; 177 | rightBall = xb + 2; 178 | topBall = yb; 179 | bottomBall = yb + 2; 180 | 181 | //Bounce off top edge 182 | if (yb <= 0) 183 | { 184 | yb = 2; 185 | dy = -dy; 186 | playTone(523, 250); 187 | } 188 | 189 | //Lose a life if bottom edge hit 190 | if (yb >= 64) 191 | { 192 | mg.drawRect(xPaddle, 63, 11, 1, 0); 193 | xPaddle = 54; 194 | yb=60; 195 | released = false; 196 | lives--; 197 | playTone(175, 250); 198 | if (random(0, 2) == 0) 199 | { 200 | dx = 1; 201 | } 202 | else 203 | { 204 | dx = -1; 205 | } 206 | } 207 | 208 | //Bounce off left side 209 | if (xb <= 0) 210 | { 211 | xb = 2; 212 | dx = -dx; 213 | playTone(523, 250); 214 | } 215 | 216 | //Bounce off right side 217 | if (xb >= WIDTH - 2) 218 | { 219 | xb = WIDTH - 4; 220 | dx = -dx; 221 | playTone(523, 250); 222 | } 223 | 224 | //Bounce off paddle 225 | if (xb+1>=xPaddle && xb<=xPaddle+12 && yb+2>=63 && yb<=64) 226 | { 227 | dy = -dy; 228 | dx = ((xb-(xPaddle+6))/3); //Applies spin on the ball 229 | // prevent straight bounce 230 | if (dx == 0) { 231 | dx = (random(0,2) == 1) ? 1 : -1; 232 | } 233 | playTone(200, 250); 234 | } 235 | 236 | //Bounce off Bricks 237 | for (byte row = 0; row < ROWS; row++) 238 | { 239 | for (byte column = 0; column < COLUMNS; column++) 240 | { 241 | if (!isHit[row][column]) 242 | { 243 | //Sets Brick bounds 244 | leftBrick = 10 * column; 245 | rightBrick = 10 * column + 10; 246 | topBrick = 6 * row + 1; 247 | bottomBrick = 6 * row + 7; 248 | 249 | //If A collison has occured 250 | if (topBall <= bottomBrick && bottomBall >= topBrick && 251 | leftBall <= rightBrick && rightBall >= leftBrick) 252 | { 253 | Score(); 254 | brickCount++; 255 | isHit[row][column] = true; 256 | mg.drawRect(10*column, 2+6*row, 8, 4, 0); 257 | 258 | //Vertical collision 259 | if (bottomBall > bottomBrick || topBall < topBrick) 260 | { 261 | //Only bounce once each ball move 262 | if(!bounced) 263 | { 264 | dy =- dy; 265 | yb += dy; 266 | bounced = true; 267 | playTone(261, 250); 268 | } 269 | } 270 | 271 | //Hoizontal collision 272 | if (leftBall < leftBrick || rightBall > rightBrick) 273 | { 274 | //Only bounce once brick each ball move 275 | if(!bounced) 276 | { 277 | dx =- dx; 278 | xb += dx; 279 | bounced = true; 280 | playTone(261, 250); 281 | } 282 | } 283 | } 284 | } 285 | } 286 | } 287 | //Reset Bounce 288 | bounced = false; 289 | } 290 | else 291 | { 292 | //Ball follows paddle 293 | xb=xPaddle + 5; 294 | 295 | //Release ball if FIRE pressed 296 | pad3 = mg.pressed(Y_BUTTON) || mg.pressed(X_BUTTON); 297 | if (pad3 == true && oldpad3 == false) 298 | { 299 | released = true; 300 | 301 | //Apply random direction to ball on release 302 | if (random(0, 2) == 0) 303 | { 304 | dx = 1; 305 | } 306 | else 307 | { 308 | dx = -1; 309 | } 310 | //Makes sure the ball heads upwards 311 | dy = -1; 312 | } 313 | oldpad3 = pad3; 314 | } 315 | } 316 | 317 | void drawBall() 318 | { 319 | // mg.setCursor(0,0); 320 | // mg.print(mg.cpuLoad()); 321 | // mg.print(" "); 322 | mg.drawPixel(xb, yb, 0); 323 | mg.drawPixel(xb+1, yb, 0); 324 | mg.drawPixel(xb, yb+1, 0); 325 | mg.drawPixel(xb+1, yb+1, 0); 326 | 327 | moveBall(); 328 | 329 | mg.drawPixel(xb, yb, 1); 330 | mg.drawPixel(xb+1, yb, 1); 331 | mg.drawPixel(xb, yb+1, 1); 332 | mg.drawPixel(xb+1, yb+1, 1); 333 | } 334 | 335 | void drawPaddle() 336 | { 337 | mg.drawRect(xPaddle, 63, 11, 1, 0); 338 | movePaddle(); 339 | mg.drawRect(xPaddle, 63, 11, 1, 1); 340 | } 341 | 342 | void drawGameOver() 343 | { 344 | mg.drawPixel(xb, yb, 0); 345 | mg.drawPixel(xb+1, yb, 0); 346 | mg.drawPixel(xb, yb+1, 0); 347 | mg.drawPixel(xb+1, yb+1, 0); 348 | mg.setCursor(37, 42); 349 | mg.print("Game Over"); 350 | mg.setCursor(31, 56); 351 | mg.print("Score: "); 352 | mg.print(score); 353 | mg.display(); 354 | delay(4000); 355 | } 356 | 357 | void pause() 358 | { 359 | paused = true; 360 | //Draw pause to the screen 361 | mg.setCursor(52, 45); 362 | mg.print("PAUSE"); 363 | mg.display(); 364 | while (paused) 365 | { 366 | delay(150); 367 | //Unpause if FIRE is pressed 368 | pad2 = mg.pressed(Y_BUTTON) || mg.pressed(X_BUTTON); 369 | if (pad2 == true && oldpad2 == false && released) 370 | { 371 | mg.fillRect(52, 45, 30, 11, 0); 372 | 373 | paused=false; 374 | } 375 | oldpad2 = pad2; 376 | } 377 | } 378 | 379 | void Score() 380 | { 381 | score += (level*10); 382 | } 383 | 384 | void newLevel(){ 385 | //Undraw paddle 386 | mg.drawRect(xPaddle, 63, 11, 1, 0); 387 | 388 | //Undraw ball 389 | mg.drawPixel(xb, yb, 0); 390 | mg.drawPixel(xb+1, yb, 0); 391 | mg.drawPixel(xb, yb+1, 0); 392 | mg.drawPixel(xb+1, yb+1, 0); 393 | 394 | //Alter various variables to reset the game 395 | xPaddle = 54; 396 | yb = 60; 397 | brickCount = 0; 398 | released = false; 399 | 400 | //Draws new bricks and resets their values 401 | for (byte row = 0; row < 4; row++) { 402 | for (byte column = 0; column < 13; column++) 403 | { 404 | isHit[row][column] = false; 405 | mg.drawRect(10*column, 2+6*row, 8, 4, 1); 406 | } 407 | } 408 | 409 | mg.display(); 410 | } 411 | 412 | //Used to delay images while reading button input 413 | boolean pollFireButton(int n) 414 | { 415 | for(int i = 0; i < n; i++) 416 | { 417 | delay(15); 418 | pad = mg.pressed(Y_BUTTON) || mg.pressed(X_BUTTON); 419 | if(pad == true && oldpad == false) 420 | { 421 | oldpad3 = true; //Forces pad loop 3 to run once 422 | return true; 423 | } 424 | oldpad = pad; 425 | } 426 | return false; 427 | } 428 | 429 | //Function by nootropic design to display highscores 430 | boolean displayHighScores(byte file) 431 | { 432 | byte y = 8; 433 | byte x = 24; 434 | // Each block of EEPROM has 7 high scores, and each high score entry 435 | // is 5 bytes long: 3 bytes for initials and two bytes for score. 436 | int address = file * 7 * 5 + EEPROM_STORAGE_SPACE_START; 437 | byte hi, lo; 438 | mg.clear(); 439 | mg.setCursor(32, 0); 440 | mg.print("HIGH SCORES"); 441 | mg.display(); 442 | 443 | mem.load(); 444 | 445 | for(int i = 0; i < 7; i++) 446 | { 447 | sprintf(text_buffer, "%2d", i+1); 448 | mg.setCursor(x,y+(i*8)); 449 | mg.print(text_buffer); 450 | mg.display(); 451 | hi = mem.read(address + (5 * i)); 452 | lo = mem.read(address + (5 * i) + 1); 453 | 454 | if ((hi == 0xFF) && (lo == 0xFF)) 455 | { 456 | score = 0; 457 | } 458 | else 459 | { 460 | score = (hi << 8) | lo; 461 | } 462 | 463 | initials[0] = (char)mem.read(address + (5 * i) + 2); 464 | initials[1] = (char)mem.read(address + (5 * i) + 3); 465 | initials[2] = (char)mem.read(address + (5 * i) + 4); 466 | 467 | if (score > 0) 468 | { 469 | sprintf(text_buffer, "%c%c%c %u", initials[0], initials[1], initials[2], score); 470 | mg.setCursor(x + 24, y + (i*8)); 471 | mg.print(text_buffer); 472 | mg.display(); 473 | } 474 | } 475 | if (pollFireButton(300)) 476 | { 477 | return true; 478 | } 479 | return false; 480 | mg.display(); 481 | } 482 | 483 | boolean titleScreen() 484 | { 485 | //Clears the screen 486 | mg.clear(); 487 | mg.setCursor(16,22); 488 | mg.setTextSize(2); 489 | mg.print("BREAKOUT"); 490 | mg.setTextSize(1); 491 | mg.display(); 492 | if (pollFireButton(25)) 493 | { 494 | return true; 495 | } 496 | 497 | //Flash "Press FIRE" 5 times 498 | for(byte i = 0; i < 5; i++) 499 | { 500 | //Draws "Press FIRE" 501 | mg.setCursor(31, 53); 502 | mg.print("PRESS FIRE!"); 503 | mg.display(); 504 | 505 | if (pollFireButton(50)) 506 | { 507 | return true; 508 | } 509 | 510 | //Removes "Press FIRE" 511 | mg.setCursor(31, 53); 512 | mg.print(" "); 513 | mg.display(); 514 | 515 | if (pollFireButton(25)) 516 | { 517 | return true; 518 | } 519 | } 520 | 521 | return false; 522 | } 523 | 524 | //Function by nootropic design to add high scores 525 | void enterInitials() 526 | { 527 | byte index = 0; 528 | 529 | mg.clear(); 530 | 531 | initials[0] = ' '; 532 | initials[1] = ' '; 533 | initials[2] = ' '; 534 | 535 | while (true) 536 | { 537 | mg.display(); 538 | mg.clear(); 539 | 540 | mg.setCursor(16,0); 541 | mg.print("HIGH SCORE"); 542 | sprintf(text_buffer, "%u", score); 543 | mg.setCursor(88, 0); 544 | mg.print(text_buffer); 545 | mg.setCursor(56, 20); 546 | mg.print(initials[0]); 547 | mg.setCursor(64, 20); 548 | mg.print(initials[1]); 549 | mg.setCursor(72, 20); 550 | mg.print(initials[2]); 551 | for(byte i = 0; i < 3; i++) 552 | { 553 | mg.drawLine(56 + (i*8), 27, 56 + (i*8) + 6, 27, 1); 554 | } 555 | mg.drawLine(56, 28, 88, 28, 0); 556 | mg.drawLine(56 + (index*8), 28, 56 + (index*8) + 6, 28, 1); 557 | delay(150); 558 | 559 | if (mg.pressed(LEFT_BUTTON) || mg.pressed(X_BUTTON)) 560 | { 561 | if (index > 0) 562 | { 563 | index--; 564 | playTone(1046, 250); 565 | } 566 | } 567 | 568 | if (mg.pressed(RIGHT_BUTTON)) 569 | { 570 | if (index < 2) 571 | { 572 | index++; 573 | playTone(1046, 250); 574 | } 575 | } 576 | 577 | if (mg.pressed(DOWN_BUTTON)) 578 | { 579 | initials[index]++; 580 | playTone(523, 250); 581 | // A-Z 0-9 :-? !-/ ' ' 582 | if (initials[index] == '0') 583 | { 584 | initials[index] = ' '; 585 | } 586 | if (initials[index] == '!') 587 | { 588 | initials[index] = 'A'; 589 | } 590 | if (initials[index] == '[') 591 | { 592 | initials[index] = '0'; 593 | } 594 | if (initials[index] == '@') 595 | { 596 | initials[index] = '!'; 597 | } 598 | } 599 | 600 | if (mg.pressed(UP_BUTTON)) 601 | { 602 | initials[index]--; 603 | playTone(523, 250); 604 | if (initials[index] == ' ') { 605 | initials[index] = '?'; 606 | } 607 | if (initials[index] == '/') { 608 | initials[index] = 'Z'; 609 | } 610 | if (initials[index] == 31) { 611 | initials[index] = '/'; 612 | } 613 | if (initials[index] == '@') { 614 | initials[index] = ' '; 615 | } 616 | } 617 | 618 | if (mg.pressed(Y_BUTTON)) 619 | { 620 | if (index < 2) 621 | { 622 | index++; 623 | playTone(1046, 250); 624 | } else { 625 | playTone(1046, 250); 626 | return; 627 | } 628 | } 629 | } 630 | 631 | } 632 | 633 | void enterHighScore(byte file) 634 | { 635 | // Each block of EEPROM has 7 high scores, and each high score entry 636 | // is 5 bytes long: 3 bytes for initials and two bytes for score. 637 | int address = file * 7 * 5 + EEPROM_STORAGE_SPACE_START; 638 | byte hi, lo; 639 | char tmpInitials[3]; 640 | unsigned int tmpScore = 0; 641 | 642 | mem.load(); 643 | 644 | // High score processing 645 | for(byte i = 0; i < 7; i++) 646 | { 647 | hi = mem.read(address + (5 * i)); 648 | lo = mem.read(address + (5 * i) + 1); 649 | if ((hi == 0xFF) && (lo == 0xFF)) 650 | { 651 | // The values are uninitialized, so treat this entry 652 | // as a score of 0. 653 | tmpScore = 0; 654 | } else 655 | { 656 | tmpScore = (hi << 8) | lo; 657 | } 658 | if (score > tmpScore) 659 | { 660 | enterInitials(); 661 | for(byte j = i; j < 7; j++) 662 | { 663 | hi = mem.read(address + (5 * j)); 664 | lo = mem.read(address + (5 * j) + 1); 665 | 666 | if ((hi == 0xFF) && (lo == 0xFF)) 667 | { 668 | tmpScore = 0; 669 | } 670 | else 671 | { 672 | tmpScore = (hi << 8) | lo; 673 | } 674 | 675 | tmpInitials[0] = (char)mem.read(address + (5 * j) + 2); 676 | tmpInitials[1] = (char)mem.read(address + (5 * j) + 3); 677 | tmpInitials[2] = (char)mem.read(address + (5 * j) + 4); 678 | 679 | // write score and initials to current slot 680 | mem.write(address + (5 * j), ((score >> 8) & 0xFF)); 681 | mem.write(address + (5 * j) + 1, (score & 0xFF)); 682 | mem.write(address + (5 * j) + 2, initials[0]); 683 | mem.write(address + (5 * j) + 3, initials[1]); 684 | mem.write(address + (5 * j) + 4, initials[2]); 685 | 686 | // tmpScore and tmpInitials now hold what we want to 687 | //write in the next slot. 688 | score = tmpScore; 689 | initials[0] = tmpInitials[0]; 690 | initials[1] = tmpInitials[1]; 691 | initials[2] = tmpInitials[2]; 692 | } 693 | 694 | score = 0; 695 | initials[0] = ' '; 696 | initials[1] = ' '; 697 | initials[2] = ' '; 698 | mem.save(); 699 | 700 | return; 701 | } 702 | } 703 | } 704 | 705 | // Wrap the Arduino tone() function so that the pin doesn't have to be 706 | // specified each time. Also, don't play if audio is set to off. 707 | void playTone(unsigned int frequency, unsigned long duration) 708 | { 709 | if (mg.audio.enabled() == true) 710 | { 711 | audio.tone( frequency, duration); 712 | } 713 | } 714 | -------------------------------------------------------------------------------- /examples/ArduBreakout/README.md: -------------------------------------------------------------------------------- 1 | # ArduBreakout 2 | 3 | Brick breaking game in the vein of Atari's *Breakout*. 4 | 5 | Control the paddle with the directional keys to keep a ball bouncing against a brick wall until all of the bricks are broken. 6 | 7 | High scores are saved to EEPROM and can be saved through Arduboy restarts. 8 | -------------------------------------------------------------------------------- /examples/Buttons/Buttons.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Buttons example 3 | June 11, 2015 4 | Copyright (C) 2015 David Martinez 5 | All rights reserved. 6 | This code is the most basic barebones code for showing how to use buttons in 7 | arduboy. 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | */ 14 | 15 | #include 16 | 17 | // Make an instance of MicroGamer used for many functions 18 | MicroGamer mg; 19 | 20 | // Variables for your game go here. 21 | char title[] = "Press Buttons!"; 22 | byte x; 23 | byte y; 24 | 25 | // Width of each charcter including inter-character space 26 | #define CHAR_WIDTH 6 27 | 28 | // Height of each charater 29 | #define CHAR_HEIGHT 8 30 | 31 | // To get the number of characters, we subtract 1 from the length of 32 | // the array because there will be a NULL terminator at the end. 33 | #define NUM_CHARS (sizeof(title) - 1) 34 | 35 | // This is the highest value that x can be without the end of the text 36 | // going farther than the right side of the screen. We add one because 37 | // there will be a 1 pixel space at the end of the last character. 38 | // WIDTH and HEIGHT are defined in the mg library. 39 | #define X_MAX (WIDTH - (NUM_CHARS * CHAR_WIDTH) + 1) 40 | 41 | // This is the highest value that y can be without the text going below 42 | // the bottom of the screen. 43 | #define Y_MAX (HEIGHT - CHAR_HEIGHT) 44 | 45 | 46 | // This function runs once in your game. 47 | // use it for anything that needs to be set only once in your game. 48 | void setup() { 49 | //initiate mg instance 50 | mg.begin(); 51 | 52 | // here we set the framerate to 30, we do not need to run at default 60 and 53 | // it saves us battery life. 54 | mg.setFrameRate(30); 55 | 56 | // set x and y to the middle of the screen 57 | x = (WIDTH / 2) - (NUM_CHARS * CHAR_WIDTH / 2); 58 | y = (HEIGHT / 2) - (CHAR_HEIGHT / 2); 59 | } 60 | 61 | 62 | // our main game loop, this runs once every cycle/frame. 63 | // this is where our game logic goes. 64 | void loop() { 65 | // pause render until it's time for the next frame 66 | if (!(mg.nextFrame())) 67 | return; 68 | 69 | // the next couple of lines will deal with checking if the D-pad buttons 70 | // are pressed and move our text accordingly. 71 | // We check to make sure that x and y stay within a range that keeps the 72 | // text on the screen. 73 | 74 | // if the right button is pressed move 1 pixel to the right every frame 75 | if(mg.pressed(RIGHT_BUTTON) && (x < X_MAX)) { 76 | x++; 77 | } 78 | 79 | // if the left button is pressed move 1 pixel to the left every frame 80 | if(mg.pressed(LEFT_BUTTON) && (x > 0)) { 81 | x--; 82 | } 83 | 84 | // if the up button or B button or X button is pressed move 1 pixel up every frame 85 | if((mg.pressed(UP_BUTTON) || mg.pressed(B_BUTTON) || mg.pressed(X_BUTTON)) && (y > 0)) { 86 | y--; 87 | } 88 | 89 | // if the down button or A button or Y button is pressed move 1 pixel down every frame 90 | if((mg.pressed(DOWN_BUTTON) || mg.pressed(A_BUTTON) || mg.pressed(Y_BUTTON)) && (y < Y_MAX)) { 91 | y++; 92 | } 93 | 94 | 95 | // we clear our screen to black 96 | mg.clear(); 97 | 98 | // we set our cursor x pixels to the right and y down from the top 99 | mg.setCursor(x, y); 100 | 101 | // then we print to screen what is stored in our title variable we declared earlier 102 | mg.print(title); 103 | 104 | // then we finaly we tell the MicroGamer to display what we just wrote to the display. 105 | mg.display(); 106 | } 107 | -------------------------------------------------------------------------------- /examples/Buttons/README.md: -------------------------------------------------------------------------------- 1 | Buttons 2 | ======= 3 | 4 | A an example that demonstrates how to capture input from the buttons. 5 | -------------------------------------------------------------------------------- /examples/Double_Buffering/Double_Buffering.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | MicroGamer mg; 4 | 5 | void setup() { 6 | mg.boot(); 7 | mg.clear(); 8 | } 9 | 10 | void loop() { 11 | mg.setCursor(0, 0); 12 | if ( ! mg.doubleBuffer()) { 13 | mg.println("Some text that"); 14 | mg.println("glitches"); 15 | mg.println("because double buffer"); 16 | mg.println("is not enabled"); 17 | mg.println("==============="); 18 | 19 | } else { 20 | mg.println("Some text that is now"); 21 | mg.println("clean because"); 22 | mg.println("double buffer is"); 23 | mg.println("enabled"); 24 | mg.println("==============="); 25 | } 26 | 27 | mg.display(); 28 | 29 | delay(7 + rand() % 5); 30 | 31 | // If double buffering is not enabled, the clear operation will modify the 32 | // frame buffer while it is sent to the screen. This will create glitches. 33 | // With double buffering enabled, drawing oprations will be preformed on 34 | // buffer A while buffer B is sent to the screen, which means there will be 35 | // no glitch. 36 | mg.clear(); 37 | 38 | if (mg.pressed(Y_BUTTON)) { 39 | mg.enableDoubleBuffer(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/HelloWorld/HelloWorld.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Hello, World! example 3 | June 11, 2015 4 | Copyright (C) 2015 David Martinez 5 | All rights reserved. 6 | This code is the most basic barebones code for writing a program for Arduboy. 7 | 8 | This library is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU Lesser General Public 10 | License as published by the Free Software Foundation; either 11 | version 2.1 of the License, or (at your option) any later version. 12 | */ 13 | 14 | #include 15 | 16 | // make an instance of MicroGamer used for many functions 17 | MicroGamer mg; 18 | 19 | 20 | // This function runs once in your game. 21 | // use it for anything that needs to be set only once in your game. 22 | void setup() { 23 | // initiate MicroGamer instance 24 | mg.begin(); 25 | 26 | // here we set the framerate to 15, we do not need to run at 27 | // default 60 and it saves us battery life 28 | mg.setFrameRate(15); 29 | } 30 | 31 | 32 | // our main game loop, this runs once every cycle/frame. 33 | // this is where our game logic goes. 34 | void loop() { 35 | // pause render until it's time for the next frame 36 | if (!(mg.nextFrame())) 37 | return; 38 | 39 | // first we clear our screen to black 40 | mg.clear(); 41 | 42 | // we set our cursor 5 pixels to the right and 10 down from the top 43 | // (positions start at 0, 0) 44 | mg.setCursor(4, 9); 45 | 46 | // then we print to screen what is in the Quotation marks "" 47 | mg.print(F("Hello, world!")); 48 | 49 | // then we finaly we tell the arduboy to display what we just wrote to the display 50 | mg.display(); 51 | } 52 | -------------------------------------------------------------------------------- /examples/MemoryCard/MemoryCard/MemoryCard.ino: -------------------------------------------------------------------------------- 1 | #include "MicroGamer.h" 2 | #include 3 | 4 | MicroGamerMemoryCard mem(1); 5 | MicroGamer mg; 6 | 7 | void setup() { 8 | mg.boot(); 9 | mg.enableDoubleBuffer(); 10 | mg.setFrameRate(30); 11 | } 12 | 13 | void loop() { 14 | unsigned int *ptr = (unsigned int *)mem.data(); 15 | 16 | if (!mg.nextFrame()) { 17 | return; // go back to the start of the loop 18 | } 19 | 20 | mg.clear(); 21 | mg.setCursor(0, 0); 22 | mg.print("Data: 0x"); 23 | mg.print(*ptr, HEX); 24 | mg.print("\n"); 25 | mg.print("RAM Addr: 0x"); 26 | mg.print((unsigned int)ptr, HEX); 27 | mg.print("\n"); 28 | mg.println("Press:"); 29 | mg.println(" - UP to increment"); 30 | mg.println(" - DOWN to decrement"); 31 | mg.println(" - Y to load the data"); 32 | mg.println(" - X to save the data"); 33 | mg.display(); 34 | 35 | if (mg.pressed(UP_BUTTON)) { 36 | (*ptr)++; 37 | } else if (mg.pressed(DOWN_BUTTON)){ 38 | (*ptr)--; 39 | } 40 | 41 | if (mg.pressed(Y_BUTTON)) { 42 | mem.load(); 43 | } else if (mg.pressed(X_BUTTON)){ 44 | mem.save(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For MicroGamer 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | MicroGamer KEYWORD1 10 | MicroGamerBase KEYWORD1 11 | Sprites KEYWORD1 12 | 13 | ####################################### 14 | # Methods and Functions (KEYWORD2) 15 | ####################################### 16 | 17 | allPixelsOn KEYWORD2 18 | begin KEYWORD2 19 | blank KEYWORD2 20 | boot KEYWORD2 21 | bootLogo KEYWORD2 22 | bootLogoCompressed KEYWORD2 23 | bootLogoShell KEYWORD2 24 | bootLogoSpritesOverwrite KEYWORD2 25 | bootLogoSpritesSelfMasked KEYWORD2 26 | bootLogoText KEYWORD2 27 | buttonsState KEYWORD2 28 | clear KEYWORD2 29 | collide KEYWORD2 30 | cpuLoad KEYWORD2 31 | delayShort KEYWORD2 32 | digitalWriteRGB KEYWORD2 33 | display KEYWORD2 34 | displayOff KEYWORD2 35 | displayOn KEYWORD2 36 | drawBitmap KEYWORD2 37 | drawChar KEYWORD2 38 | drawCircle KEYWORD2 39 | drawCompressed KEYWORD2 40 | drawFastHLine KEYWORD2 41 | drawFastVLine KEYWORD2 42 | drawLine KEYWORD2 43 | drawPixel KEYWORD2 44 | drawRect KEYWORD2 45 | drawRoundRect KEYWORD2 46 | drawSlowXYBitmap KEYWORD2 47 | drawTriangle KEYWORD2 48 | enabled KEYWORD2 49 | everyXFrames KEYWORD2 50 | fillCircle KEYWORD2 51 | fillRect KEYWORD2 52 | fillRoundRect KEYWORD2 53 | fillScreen KEYWORD2 54 | fillTriangle KEYWORD2 55 | flashlight KEYWORD2 56 | flipVertical KEYWORD2 57 | flipHorizontal KEYWORD2 58 | getBuffer KEYWORD2 59 | getCursorX KEYWORD2 60 | getCursorY KEYWORD2 61 | getTextBackground KEYWORD2 62 | getTextColor KEYWORD2 63 | getTextSize KEYWORD2 64 | getTextWrap KEYWORD2 65 | height KEYWORD2 66 | idle KEYWORD2 67 | initRandomSeed KEYWORD2 68 | invert KEYWORD2 69 | justPressed KEYWORD2 70 | justReleased KEYWORD2 71 | nextFrame KEYWORD2 72 | nextFrameDEV KEYWORD2 73 | notPressed KEYWORD2 74 | off KEYWORD2 75 | on KEYWORD2 76 | paint8Pixels KEYWORD2 77 | paintScreen KEYWORD2 78 | pollButtons KEYWORD2 79 | pressed KEYWORD2 80 | readShowUnitNameFlag KEYWORD2 81 | readUnitID KEYWORD2 82 | readUnitName KEYWORD2 83 | safeMode KEYWORD2 84 | saveOnOff KEYWORD2 85 | setCursor KEYWORD2 86 | setFrameRate KEYWORD2 87 | setRGBled KEYWORD2 88 | setTextBackground KEYWORD2 89 | setTextColor KEYWORD2 90 | setTextSize KEYWORD2 91 | setTextWrap KEYWORD2 92 | SPItransfer KEYWORD2 93 | systemButtons KEYWORD2 94 | toggle KEYWORD2 95 | width KEYWORD2 96 | writeShowUnitNameFlag KEYWORD2 97 | writeUnitID KEYWORD2 98 | writeUnitName KEYWORD2 99 | 100 | # Sprites class 101 | drawErase KEYWORD2 102 | drawExternalMask KEYWORD2 103 | drawOverwrite KEYWORD2 104 | drawPlusMask KEYWORD2 105 | drawSelfMasked KEYWORD2 106 | 107 | ####################################### 108 | # Constants (LITERAL1) 109 | ####################################### 110 | 111 | HEIGHT LITERAL1 112 | WIDTH LITERAL1 113 | 114 | BLACK LITERAL1 115 | WHITE LITERAL1 116 | INVERT LITERAL1 117 | 118 | CLEAR_BUFFER LITERAL1 119 | 120 | A_BUTTON LITERAL1 121 | B_BUTTON LITERAL1 122 | X_BUTTON LITERAL1 123 | Y_BUTTON LITERAL1 124 | DOWN_BUTTON LITERAL1 125 | LEFT_BUTTON LITERAL1 126 | RIGHT_BUTTON LITERAL1 127 | UP_BUTTON LITERAL1 128 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MicroGamer 2 | version=0.1.0 3 | author=Chris J. Martinez, Kevin Bates, Josh Goebel, Scott Allen, Ross O.Shoger, Fabien Chouteau 4 | maintainer=Fabien Chouteau 5 | sentence=A library for content creation on the Micro:Gamer gaming platform 6 | paragraph=This is a fork of the Arduboy2 library, for the Micro:Gamer. 7 | category=Other 8 | url=https://github.com/MicroGamerConsole/MicroGamer-Arduino 9 | architectures=nRF5 10 | includes=MicroGamerCore.h,MicroGamerAudio.h,MicroGamer.h,MicroGamerMemoryCard.h,MicroGamerTones.h,MicroGamerTonesPitches.h,Sprites.h 11 | -------------------------------------------------------------------------------- /src/MicroGamer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamer.cpp 3 | * \brief 4 | * The MicroGamerBase and MicroGamer classes and support objects and definitions. 5 | */ 6 | 7 | #include "MicroGamer.h" 8 | #include "ab_logo.c" 9 | #include "glcdfont.c" 10 | 11 | //========================================== 12 | //========== class MicroGamerBase ========== 13 | //========================================== 14 | 15 | uint8_t MicroGamerBase::staticAllocatedBuffer[]; 16 | uint8_t *MicroGamerBase::displayBuffer; 17 | uint8_t *MicroGamerBase::sBuffer; 18 | 19 | MicroGamerBase::MicroGamerBase() 20 | { 21 | currentButtonState = 0; 22 | previousButtonState = 0; 23 | // frame management 24 | setFrameRate(60); 25 | frameCount = -1; 26 | nextFrameStart = 0; 27 | justRendered = false; 28 | // init not necessary, will be reset after first use 29 | // lastFrameStart 30 | // lastFrameDurationMs 31 | 32 | sBuffer = staticAllocatedBuffer; 33 | displayBuffer = NULL; 34 | } 35 | 36 | // functions called here should be public so users can create their 37 | // own init functions if they need different behavior than `begin` 38 | // provides by default 39 | void MicroGamerBase::begin() 40 | { 41 | boot(); // raw hardware 42 | 43 | clear(); 44 | display(); 45 | waitDisplayUpdate(); 46 | 47 | flashlight(); // light the RGB LED and screen if UP button is being held. 48 | 49 | // check for and handle buttons held during start up for system control 50 | systemButtons(); 51 | 52 | audio.begin(); 53 | 54 | // bootLogoText(); 55 | // alternative logo functions. Work the same a bootLogo() but may reduce 56 | // memory size if the sketch uses the same bitmap drawing function 57 | // bootLogoCompressed(); 58 | // bootLogoSpritesSelfMasked(); 59 | // bootLogoSpritesOverwrite(); 60 | 61 | // wait for all buttons to be released 62 | do { 63 | delayShort(50); 64 | } while (buttonsState()); 65 | } 66 | 67 | void MicroGamerBase::flashlight() 68 | { 69 | // if (!pressed(UP_BUTTON)) { 70 | // return; 71 | // } 72 | 73 | // sendLCDCommand(OLED_ALL_PIXELS_ON); // smaller than allPixelsOn() 74 | // digitalWriteRGB(RGB_ON, RGB_ON, RGB_ON); 75 | 76 | // // prevent the bootloader magic number from being overwritten by timer 0 77 | // // when a timer variable overlaps the magic number location, for when 78 | // // flashlight mode is used for upload problem recovery 79 | // power_timer0_disable(); 80 | 81 | // while (true) { 82 | // idle(); 83 | // } 84 | } 85 | 86 | void MicroGamerBase::systemButtons() 87 | { 88 | // while (pressed(B_BUTTON)) { 89 | // digitalWriteRGB(BLUE_LED, RGB_ON); // turn on blue LED 90 | // sysCtrlSound(UP_BUTTON + B_BUTTON, GREEN_LED, 0xff); 91 | // sysCtrlSound(DOWN_BUTTON + B_BUTTON, RED_LED, 0); 92 | // delayShort(200); 93 | // } 94 | 95 | // digitalWriteRGB(BLUE_LED, RGB_OFF); // turn off blue LED 96 | } 97 | 98 | void MicroGamerBase::sysCtrlSound(uint8_t buttons, uint8_t led, uint8_t eeVal) 99 | { 100 | // if (pressed(buttons)) { 101 | // digitalWriteRGB(BLUE_LED, RGB_OFF); // turn off blue LED 102 | // delayShort(200); 103 | // digitalWriteRGB(led, RGB_ON); // turn on "acknowledge" LED 104 | // EEPROM.update(EEPROM_AUDIO_ON_OFF, eeVal); 105 | // delayShort(500); 106 | // digitalWriteRGB(led, RGB_OFF); // turn off "acknowledge" LED 107 | 108 | // while (pressed(buttons)) { } // Wait for button release 109 | // } 110 | } 111 | 112 | void MicroGamerBase::bootLogo() 113 | { 114 | bootLogoShell(drawLogoBitmap); 115 | } 116 | 117 | void MicroGamerBase::drawLogoBitmap(int16_t y) 118 | { 119 | drawBitmap(20, y, arduboy_logo, 88, 16); 120 | } 121 | 122 | void MicroGamerBase::bootLogoCompressed() 123 | { 124 | bootLogoShell(drawLogoCompressed); 125 | } 126 | 127 | void MicroGamerBase::drawLogoCompressed(int16_t y) 128 | { 129 | drawCompressed(20, y, arduboy_logo_compressed); 130 | } 131 | 132 | void MicroGamerBase::bootLogoSpritesSelfMasked() 133 | { 134 | bootLogoShell(drawLogoSpritesSelfMasked); 135 | } 136 | 137 | void MicroGamerBase::drawLogoSpritesSelfMasked(int16_t y) 138 | { 139 | Sprites::drawSelfMasked(20, y, arduboy_logo_sprite, 0); 140 | } 141 | 142 | void MicroGamerBase::bootLogoSpritesOverwrite() 143 | { 144 | bootLogoShell(drawLogoSpritesOverwrite); 145 | } 146 | 147 | void MicroGamerBase::drawLogoSpritesOverwrite(int16_t y) 148 | { 149 | Sprites::drawOverwrite(20, y, arduboy_logo_sprite, 0); 150 | } 151 | 152 | // bootLogoText() should be kept in sync with bootLogoShell() 153 | // if changes are made to one, equivalent changes should be made to the other 154 | void MicroGamerBase::bootLogoShell(void (*drawLogo)(int16_t)) 155 | { 156 | 157 | // for (int16_t y = -18; y <= 24; y++) { 158 | // if (pressed(RIGHT_BUTTON)) { 159 | // return; 160 | // } 161 | 162 | // clear(); 163 | // (*drawLogo)(y); // call the function that actually draws the logo 164 | // display(); 165 | // delayShort(27); 166 | // // longer delay post boot, we put it inside the loop to 167 | // // save the flash calling clear/delay again outside the loop 168 | // if (y==-16) { 169 | // delayShort(250); 170 | // } 171 | // } 172 | 173 | // delayShort(700); 174 | 175 | // bootLogoExtra(); 176 | } 177 | 178 | // Virtual function overridden by derived class 179 | void MicroGamerBase::bootLogoExtra() { } 180 | 181 | /* Frame management */ 182 | 183 | void MicroGamerBase::setFrameRate(uint8_t rate) 184 | { 185 | eachFrameMillis = 1000 / rate; 186 | } 187 | 188 | bool MicroGamerBase::everyXFrames(uint8_t frames) 189 | { 190 | return frameCount % frames == 0; 191 | } 192 | 193 | bool MicroGamerBase::nextFrame() 194 | { 195 | unsigned long now = millis(); 196 | bool tooSoonForNextFrame = now < nextFrameStart; 197 | 198 | if (justRendered) { 199 | lastFrameDurationMs = now - lastFrameStart; 200 | justRendered = false; 201 | return false; 202 | } 203 | else if (tooSoonForNextFrame) { 204 | // if we have MORE than 1ms to spare (hence our comparison with 2), 205 | // lets sleep for power savings. We don't compare against 1 to avoid 206 | // potential rounding errors - say we're actually 0.5 ms away, but a 1 207 | // is returned if we go to sleep we might sleep a full 1ms and then 208 | // we'd be running the frame slighly late. So the last 1ms we stay 209 | // awake for perfect timing. 210 | 211 | // This is likely trading power savings for absolute timing precision 212 | // and the power savings might be the better goal. At 60 FPS trusting 213 | // chance here might actually achieve a "truer" 60 FPS than the 16ms 214 | // frame duration we get due to integer math. 215 | 216 | // We should be woken up by timer0 every 1ms, so it's ok to sleep. 217 | if ((uint8_t)(nextFrameStart - now) >= 2) 218 | idle(); 219 | 220 | return false; 221 | } 222 | 223 | // pre-render 224 | justRendered = true; 225 | lastFrameStart = now; 226 | nextFrameStart = now + eachFrameMillis; 227 | frameCount++; 228 | 229 | return true; 230 | } 231 | 232 | bool MicroGamerBase::nextFrameDEV() 233 | { 234 | bool ret = nextFrame(); 235 | 236 | // if (ret) { 237 | // if (lastFrameDurationMs > eachFrameMillis) 238 | // TXLED1; 239 | // else 240 | // TXLED0; 241 | // } 242 | return ret; 243 | } 244 | 245 | int MicroGamerBase::cpuLoad() 246 | { 247 | return lastFrameDurationMs*100 / eachFrameMillis; 248 | } 249 | 250 | void MicroGamerBase::initRandomSeed() 251 | { 252 | // power_adc_enable(); // ADC on 253 | 254 | // // do an ADC read from an unconnected input pin 255 | // ADCSRA |= _BV(ADSC); // start conversion (ADMUX has been pre-set in boot()) 256 | // while (bit_is_set(ADCSRA, ADSC)) { } // wait for conversion complete 257 | 258 | // randomSeed(((unsigned long)ADC << 16) + micros()); 259 | 260 | // power_adc_disable(); // ADC off 261 | 262 | randomSeed(analogRead(0)); 263 | 264 | } 265 | 266 | /* Graphics */ 267 | 268 | void MicroGamerBase::clear() 269 | { 270 | fillScreen(BLACK); 271 | } 272 | 273 | 274 | // // Used by drawPixel to help with left bitshifting since AVR has no 275 | // // multiple bit shift instruction. We can bit shift from a lookup table 276 | // // in flash faster than we can calculate the bit shifts on the CPU. 277 | // const uint8_t bitshift_left[] PROGMEM = { 278 | // _BV(0), _BV(1), _BV(2), _BV(3), _BV(4), _BV(5), _BV(6), _BV(7) 279 | // }; 280 | 281 | void MicroGamerBase::drawPixel(int16_t x, int16_t y, uint8_t color) 282 | { 283 | if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { 284 | return; 285 | } 286 | 287 | // x is which column 288 | switch (color) 289 | { 290 | case WHITE: sBuffer[x+ (y/8)*WIDTH] |= (1 << (y&7)); break; 291 | case BLACK: sBuffer[x+ (y/8)*WIDTH] &= ~(1 << (y&7)); break; 292 | case INVERSE: sBuffer[x+ (y/8)*WIDTH] ^= (1 << (y&7)); break; 293 | } 294 | } 295 | 296 | uint8_t MicroGamerBase::getPixel(uint8_t x, uint8_t y) 297 | { 298 | uint8_t row = y / 8; 299 | uint8_t bit_position = y % 8; 300 | return (sBuffer[(row*WIDTH) + x] & bit_position) >> bit_position; 301 | } 302 | 303 | void MicroGamerBase::drawCircle(int16_t x0, int16_t y0, uint8_t r, uint8_t color) 304 | { 305 | int16_t f = 1 - r; 306 | int16_t ddF_x = 1; 307 | int16_t ddF_y = -2 * r; 308 | int16_t x = 0; 309 | int16_t y = r; 310 | 311 | drawPixel(x0, y0+r, color); 312 | drawPixel(x0, y0-r, color); 313 | drawPixel(x0+r, y0, color); 314 | drawPixel(x0-r, y0, color); 315 | 316 | while (x= 0) 319 | { 320 | y--; 321 | ddF_y += 2; 322 | f += ddF_y; 323 | } 324 | 325 | x++; 326 | ddF_x += 2; 327 | f += ddF_x; 328 | 329 | drawPixel(x0 + x, y0 + y, color); 330 | drawPixel(x0 - x, y0 + y, color); 331 | drawPixel(x0 + x, y0 - y, color); 332 | drawPixel(x0 - x, y0 - y, color); 333 | drawPixel(x0 + y, y0 + x, color); 334 | drawPixel(x0 - y, y0 + x, color); 335 | drawPixel(x0 + y, y0 - x, color); 336 | drawPixel(x0 - y, y0 - x, color); 337 | } 338 | } 339 | 340 | void MicroGamerBase::drawCircleHelper 341 | (int16_t x0, int16_t y0, uint8_t r, uint8_t corners, uint8_t color) 342 | { 343 | int16_t f = 1 - r; 344 | int16_t ddF_x = 1; 345 | int16_t ddF_y = -2 * r; 346 | int16_t x = 0; 347 | int16_t y = r; 348 | 349 | while (x= 0) 352 | { 353 | y--; 354 | ddF_y += 2; 355 | f += ddF_y; 356 | } 357 | 358 | x++; 359 | ddF_x += 2; 360 | f += ddF_x; 361 | 362 | if (corners & 0x4) // lower right 363 | { 364 | drawPixel(x0 + x, y0 + y, color); 365 | drawPixel(x0 + y, y0 + x, color); 366 | } 367 | if (corners & 0x2) // upper right 368 | { 369 | drawPixel(x0 + x, y0 - y, color); 370 | drawPixel(x0 + y, y0 - x, color); 371 | } 372 | if (corners & 0x8) // lower left 373 | { 374 | drawPixel(x0 - y, y0 + x, color); 375 | drawPixel(x0 - x, y0 + y, color); 376 | } 377 | if (corners & 0x1) // upper left 378 | { 379 | drawPixel(x0 - y, y0 - x, color); 380 | drawPixel(x0 - x, y0 - y, color); 381 | } 382 | } 383 | } 384 | 385 | void MicroGamerBase::fillCircle(int16_t x0, int16_t y0, uint8_t r, uint8_t color) 386 | { 387 | drawFastVLine(x0, y0-r, 2*r+1, color); 388 | fillCircleHelper(x0, y0, r, 3, 0, color); 389 | } 390 | 391 | void MicroGamerBase::fillCircleHelper 392 | (int16_t x0, int16_t y0, uint8_t r, uint8_t sides, int16_t delta, 393 | uint8_t color) 394 | { 395 | int16_t f = 1 - r; 396 | int16_t ddF_x = 1; 397 | int16_t ddF_y = -2 * r; 398 | int16_t x = 0; 399 | int16_t y = r; 400 | 401 | while (x < y) 402 | { 403 | if (f >= 0) 404 | { 405 | y--; 406 | ddF_y += 2; 407 | f += ddF_y; 408 | } 409 | 410 | x++; 411 | ddF_x += 2; 412 | f += ddF_x; 413 | 414 | if (sides & 0x1) // right side 415 | { 416 | drawFastVLine(x0+x, y0-y, 2*y+1+delta, color); 417 | drawFastVLine(x0+y, y0-x, 2*x+1+delta, color); 418 | } 419 | 420 | if (sides & 0x2) // left side 421 | { 422 | drawFastVLine(x0-x, y0-y, 2*y+1+delta, color); 423 | drawFastVLine(x0-y, y0-x, 2*x+1+delta, color); 424 | } 425 | } 426 | } 427 | 428 | void MicroGamerBase::drawLine 429 | (int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color) 430 | { 431 | // bresenham's algorithm - thx wikpedia 432 | bool steep = abs(y1 - y0) > abs(x1 - x0); 433 | if (steep) { 434 | swap(x0, y0); 435 | swap(x1, y1); 436 | } 437 | 438 | if (x0 > x1) { 439 | swap(x0, x1); 440 | swap(y0, y1); 441 | } 442 | 443 | int16_t dx, dy; 444 | dx = x1 - x0; 445 | dy = abs(y1 - y0); 446 | 447 | int16_t err = dx / 2; 448 | int8_t ystep; 449 | 450 | if (y0 < y1) 451 | { 452 | ystep = 1; 453 | } 454 | else 455 | { 456 | ystep = -1; 457 | } 458 | 459 | for (; x0 <= x1; x0++) 460 | { 461 | if (steep) 462 | { 463 | drawPixel(y0, x0, color); 464 | } 465 | else 466 | { 467 | drawPixel(x0, y0, color); 468 | } 469 | 470 | err -= dy; 471 | if (err < 0) 472 | { 473 | y0 += ystep; 474 | err += dx; 475 | } 476 | } 477 | } 478 | 479 | void MicroGamerBase::drawRect 480 | (int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t color) 481 | { 482 | drawFastHLine(x, y, w, color); 483 | drawFastHLine(x, y+h-1, w, color); 484 | drawFastVLine(x, y, h, color); 485 | drawFastVLine(x+w-1, y, h, color); 486 | } 487 | 488 | void MicroGamerBase::drawFastVLine 489 | (int16_t x, int16_t y, uint8_t h, uint8_t color) 490 | { 491 | int end = y+h; 492 | for (int a = max(0,y); a < min(end,HEIGHT); a++) 493 | { 494 | drawPixel(x,a,color); 495 | } 496 | } 497 | 498 | void MicroGamerBase::drawFastHLine 499 | (int16_t x, int16_t y, uint8_t w, uint8_t color) 500 | { 501 | int16_t xEnd; // last x point + 1 502 | 503 | // Do y bounds checks 504 | if (y < 0 || y >= HEIGHT) 505 | return; 506 | 507 | xEnd = x + w; 508 | 509 | // Check if the entire line is not on the display 510 | if (xEnd <= 0 || x >= WIDTH) 511 | return; 512 | 513 | // Don't start before the left edge 514 | if (x < 0) 515 | x = 0; 516 | 517 | // Don't end past the right edge 518 | if (xEnd > WIDTH) 519 | xEnd = WIDTH; 520 | 521 | // calculate actual width (even if unchanged) 522 | w = xEnd - x; 523 | 524 | // buffer pointer plus row offset + x offset 525 | register uint8_t *pBuf = sBuffer + ((y / 8) * WIDTH) + x; 526 | 527 | // pixel mask 528 | register uint8_t mask = 1 << (y & 7); 529 | 530 | switch (color) 531 | { 532 | case WHITE: 533 | while (w--) 534 | { 535 | *pBuf++ |= mask; 536 | } 537 | break; 538 | 539 | case BLACK: 540 | mask = ~mask; 541 | while (w--) 542 | { 543 | *pBuf++ &= mask; 544 | } 545 | break; 546 | } 547 | } 548 | 549 | void MicroGamerBase::fillRect 550 | (int16_t x, int16_t y, uint8_t w, uint8_t h, uint8_t color) 551 | { 552 | // stupidest version - update in subclasses if desired! 553 | for (int16_t i=x; i= y1 >= y0) 604 | if (y0 > y1) 605 | { 606 | swap(y0, y1); swap(x0, x1); 607 | } 608 | if (y1 > y2) 609 | { 610 | swap(y2, y1); swap(x2, x1); 611 | } 612 | if (y0 > y1) 613 | { 614 | swap(y0, y1); swap(x0, x1); 615 | } 616 | 617 | if(y0 == y2) 618 | { // Handle awkward all-on-same-line case as its own thing 619 | a = b = x0; 620 | if(x1 < a) 621 | { 622 | a = x1; 623 | } 624 | else if(x1 > b) 625 | { 626 | b = x1; 627 | } 628 | if(x2 < a) 629 | { 630 | a = x2; 631 | } 632 | else if(x2 > b) 633 | { 634 | b = x2; 635 | } 636 | drawFastHLine(a, y0, b-a+1, color); 637 | return; 638 | } 639 | 640 | int16_t dx01 = x1 - x0, 641 | dy01 = y1 - y0, 642 | dx02 = x2 - x0, 643 | dy02 = y2 - y0, 644 | dx12 = x2 - x1, 645 | dy12 = y2 - y1, 646 | sa = 0, 647 | sb = 0; 648 | 649 | // For upper part of triangle, find scanline crossings for segments 650 | // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 651 | // is included here (and second loop will be skipped, avoiding a /0 652 | // error there), otherwise scanline y1 is skipped here and handled 653 | // in the second loop...which also avoids a /0 error here if y0=y1 654 | // (flat-topped triangle). 655 | if (y1 == y2) 656 | { 657 | last = y1; // Include y1 scanline 658 | } 659 | else 660 | { 661 | last = y1-1; // Skip it 662 | } 663 | 664 | 665 | for(y = y0; y <= last; y++) 666 | { 667 | a = x0 + sa / dy01; 668 | b = x0 + sb / dy02; 669 | sa += dx01; 670 | sb += dx02; 671 | 672 | if(a > b) 673 | { 674 | swap(a,b); 675 | } 676 | 677 | drawFastHLine(a, y, b-a+1, color); 678 | } 679 | 680 | // For lower part of triangle, find scanline crossings for segments 681 | // 0-2 and 1-2. This loop is skipped if y1=y2. 682 | sa = dx12 * (y - y1); 683 | sb = dx02 * (y - y0); 684 | 685 | for(; y <= y2; y++) 686 | { 687 | a = x1 + sa / dy12; 688 | b = x0 + sb / dy02; 689 | sa += dx12; 690 | sb += dx02; 691 | 692 | if(a > b) 693 | { 694 | swap(a,b); 695 | } 696 | 697 | drawFastHLine(a, y, b-a+1, color); 698 | } 699 | } 700 | 701 | void MicroGamerBase::drawBitmap 702 | (int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, 703 | uint8_t color) 704 | { 705 | // no need to draw at all if we're offscreen 706 | if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1) 707 | return; 708 | 709 | int yOffset = abs(y) % 8; 710 | int sRow = y / 8; 711 | if (y < 0) { 712 | sRow--; 713 | yOffset = 8 - yOffset; 714 | } 715 | int rows = h/8; 716 | if (h%8!=0) rows++; 717 | for (int a = 0; a < rows; a++) { 718 | int bRow = sRow + a; 719 | if (bRow > (HEIGHT/8)-1) break; 720 | if (bRow > -2) { 721 | for (int iCol = 0; iCol (WIDTH-1)) break; 723 | if (iCol + x >= 0) { 724 | if (bRow >= 0) { 725 | if (color == WHITE) 726 | sBuffer[(bRow*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; 727 | else if (color == BLACK) 728 | sBuffer[(bRow*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) << yOffset); 729 | else 730 | sBuffer[(bRow*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) << yOffset; 731 | } 732 | if (yOffset && bRow<(HEIGHT/8)-1 && bRow > -2) { 733 | if (color == WHITE) 734 | sBuffer[((bRow+1)*WIDTH) + x + iCol] |= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset); 735 | else if (color == BLACK) 736 | sBuffer[((bRow+1)*WIDTH) + x + iCol] &= ~(pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset)); 737 | else 738 | sBuffer[((bRow+1)*WIDTH) + x + iCol] ^= pgm_read_byte(bitmap+(a*w)+iCol) >> (8-yOffset); 739 | } 740 | } 741 | } 742 | } 743 | } 744 | } 745 | 746 | void MicroGamerBase::drawSlowXYBitmap 747 | (int16_t x, int16_t y, const uint8_t *bitmap, uint8_t w, uint8_t h, uint8_t color) 748 | { 749 | // no need to draw at all of we're offscreen 750 | if (x+w < 0 || x > WIDTH-1 || y+h < 0 || y > HEIGHT-1) 751 | return; 752 | 753 | int16_t xi, yi, byteWidth = (w + 7) / 8; 754 | for(yi = 0; yi < h; yi++) { 755 | for(xi = 0; xi < w; xi++ ) { 756 | if(pgm_read_byte(bitmap + yi * byteWidth + xi / 8) & (128 >> (xi & 7))) { 757 | drawPixel(x + xi, y + yi, color); 758 | } 759 | } 760 | } 761 | } 762 | 763 | typedef struct CSESSION { 764 | int byte; 765 | int bit; 766 | const uint8_t *src; 767 | int src_pos; 768 | } CSESSION; 769 | static CSESSION cs; 770 | 771 | static int getval(int bits) 772 | { 773 | int val = 0; 774 | int i; 775 | for (i = 0; i < bits; i++) 776 | { 777 | if (cs.bit == 0x100) 778 | { 779 | cs.bit = 0x1; 780 | cs.byte = pgm_read_byte(&cs.src[cs.src_pos]); 781 | cs.src_pos ++; 782 | } 783 | if (cs.byte & cs.bit) 784 | val += (1 << i); 785 | cs.bit <<= 1; 786 | } 787 | return val; 788 | } 789 | 790 | void MicroGamerBase::drawCompressed(int16_t sx, int16_t sy, const uint8_t *bitmap, uint8_t color) 791 | { 792 | int bl, len; 793 | int col; 794 | int i; 795 | int a, iCol; 796 | int byte = 0; 797 | int bit = 0; 798 | int w, h; 799 | 800 | // set up decompress state 801 | 802 | cs.src = bitmap; 803 | cs.bit = 0x100; 804 | cs.byte = 0; 805 | cs.src_pos = 0; 806 | 807 | // read header 808 | 809 | w = getval(8) + 1; 810 | h = getval(8) + 1; 811 | 812 | col = getval(1); // starting colour 813 | 814 | // no need to draw at all if we're offscreen 815 | if (sx + w < 0 || sx > WIDTH - 1 || sy + h < 0 || sy > HEIGHT - 1) 816 | return; 817 | 818 | // sy = sy - (frame*h); 819 | 820 | int yOffset = abs(sy) % 8; 821 | int sRow = sy / 8; 822 | if (sy < 0) { 823 | sRow--; 824 | yOffset = 8 - yOffset; 825 | } 826 | int rows = h / 8; 827 | if (h % 8 != 0) rows++; 828 | 829 | a = 0; // +(frame*rows); 830 | iCol = 0; 831 | 832 | byte = 0; bit = 1; 833 | while (a < rows) // + (frame*rows)) 834 | { 835 | bl = 1; 836 | while (!getval(1)) 837 | bl += 2; 838 | 839 | len = getval(bl) + 1; // span length 840 | 841 | // draw the span 842 | 843 | 844 | for (i = 0; i < len; i++) 845 | { 846 | if (col) 847 | byte |= bit; 848 | bit <<= 1; 849 | 850 | if (bit == 0x100) // reached end of byte 851 | { 852 | // draw 853 | 854 | int bRow = sRow + a; 855 | 856 | //if (byte) // possible optimisation 857 | if (bRow <= (HEIGHT / 8) - 1) 858 | if (bRow > -2) 859 | if (iCol + sx <= (WIDTH - 1)) 860 | if (iCol + sx >= 0) { 861 | 862 | if (bRow >= 0) 863 | { 864 | if (color) 865 | sBuffer[(bRow * WIDTH) + sx + iCol] |= byte << yOffset; 866 | else 867 | sBuffer[(bRow * WIDTH) + sx + iCol] &= ~(byte << yOffset); 868 | } 869 | if (yOffset && bRow < (HEIGHT / 8) - 1 && bRow > -2) 870 | { 871 | if (color) 872 | sBuffer[((bRow + 1)*WIDTH) + sx + iCol] |= byte >> (8 - yOffset); 873 | else 874 | sBuffer[((bRow + 1)*WIDTH) + sx + iCol] &= ~(byte >> (8 - yOffset)); 875 | } 876 | } 877 | 878 | // iterate 879 | iCol ++; 880 | if (iCol >= w) 881 | { 882 | iCol = 0; 883 | a ++; 884 | } 885 | 886 | // reset byte 887 | byte = 0; bit = 1; 888 | } 889 | } 890 | 891 | col = 1 - col; // toggle colour for next span 892 | } 893 | } 894 | 895 | void MicroGamerBase::display() 896 | { 897 | uint8_t *tmp; 898 | 899 | waitEndOfPaintScreen(); 900 | 901 | if(displayBuffer != NULL) { 902 | tmp = displayBuffer; 903 | displayBuffer = sBuffer; 904 | sBuffer = tmp; 905 | paintScreen(displayBuffer); 906 | } else { 907 | paintScreen(sBuffer); 908 | } 909 | } 910 | 911 | void MicroGamerBase::display(bool clear) 912 | { 913 | display(); 914 | if (clear) { 915 | this->clear(); 916 | } 917 | } 918 | 919 | void MicroGamerBase::waitDisplayUpdate() 920 | { 921 | waitEndOfPaintScreen(); 922 | } 923 | 924 | void MicroGamerBase::enableDoubleBuffer() 925 | { 926 | if(displayBuffer == NULL) { 927 | displayBuffer = (uint8_t *) malloc(((HEIGHT * WIDTH) / 8) * sizeof(uint8_t)); 928 | } 929 | } 930 | 931 | bool MicroGamerBase::doubleBuffer() 932 | { 933 | return displayBuffer != NULL; 934 | } 935 | 936 | uint8_t* MicroGamerBase::getBuffer() 937 | { 938 | return sBuffer; 939 | } 940 | 941 | bool MicroGamerBase::pressed(uint8_t buttons) 942 | { 943 | return (buttonsState() & buttons) == buttons; 944 | } 945 | 946 | bool MicroGamerBase::notPressed(uint8_t buttons) 947 | { 948 | return (buttonsState() & buttons) == 0; 949 | } 950 | 951 | void MicroGamerBase::pollButtons() 952 | { 953 | previousButtonState = currentButtonState; 954 | currentButtonState = buttonsState(); 955 | } 956 | 957 | bool MicroGamerBase::justPressed(uint8_t button) 958 | { 959 | return (!(previousButtonState & button) && (currentButtonState & button)); 960 | } 961 | 962 | bool MicroGamerBase::justReleased(uint8_t button) 963 | { 964 | return ((previousButtonState & button) && !(currentButtonState & button)); 965 | } 966 | 967 | bool MicroGamerBase::collide(Point point, Rect rect) 968 | { 969 | return ((point.x >= rect.x) && (point.x < rect.x + rect.width) && 970 | (point.y >= rect.y) && (point.y < rect.y + rect.height)); 971 | } 972 | 973 | bool MicroGamerBase::collide(Rect rect1, Rect rect2) 974 | { 975 | return !(rect2.x >= rect1.x + rect1.width || 976 | rect2.x + rect2.width <= rect1.x || 977 | rect2.y >= rect1.y + rect1.height || 978 | rect2.y + rect2.height <= rect1.y); 979 | } 980 | 981 | uint16_t MicroGamerBase::readUnitID() 982 | { 983 | //TODO 984 | return 0; 985 | } 986 | 987 | void MicroGamerBase::writeUnitID(uint16_t id) 988 | { 989 | // TODO 990 | // EEPROM.update(EEPROM_UNIT_ID, (uint8_t)(id & 0xff)); 991 | // EEPROM.update(EEPROM_UNIT_ID + 1, (uint8_t)(id >> 8)); 992 | } 993 | 994 | uint8_t MicroGamerBase::readUnitName(char* name) 995 | { 996 | // char val; 997 | // uint8_t dest; 998 | // uint8_t src = EEPROM_UNIT_NAME; 999 | 1000 | // for (dest = 0; dest < ARDUBOY_UNIT_NAME_LEN; dest++) 1001 | // { 1002 | // val = EEPROM.read(src); 1003 | // name[dest] = val; 1004 | // src++; 1005 | // if (val == 0x00 || (byte)val == 0xFF) { 1006 | // break; 1007 | // } 1008 | // } 1009 | 1010 | // name[dest] = 0x00; 1011 | // return dest; 1012 | return 0; 1013 | } 1014 | 1015 | void MicroGamerBase::writeUnitName(char* name) 1016 | { 1017 | // bool done = false; 1018 | // uint8_t dest = EEPROM_UNIT_NAME; 1019 | 1020 | // for (uint8_t src = 0; src < ARDUBOY_UNIT_NAME_LEN; src++) 1021 | // { 1022 | // if (name[src] == 0x00) { 1023 | // done = true; 1024 | // } 1025 | // // write character or 0 pad if finished 1026 | // EEPROM.update(dest, done ? 0x00 : name[src]); 1027 | // dest++; 1028 | // } 1029 | } 1030 | 1031 | bool MicroGamerBase::readShowUnitNameFlag() 1032 | { 1033 | // return (EEPROM.read(EEPROM_SYS_FLAGS) & SYS_FLAG_UNAME_MASK); 1034 | return 0; 1035 | } 1036 | 1037 | void MicroGamerBase::writeShowUnitNameFlag(bool val) 1038 | { 1039 | // uint8_t flags = EEPROM.read(EEPROM_SYS_FLAGS); 1040 | 1041 | // bitWrite(flags, SYS_FLAG_UNAME, val); 1042 | // EEPROM.update(EEPROM_SYS_FLAGS, flags); 1043 | } 1044 | 1045 | void MicroGamerBase::swap(int16_t& a, int16_t& b) 1046 | { 1047 | int16_t temp = a; 1048 | a = b; 1049 | b = temp; 1050 | } 1051 | 1052 | 1053 | //====================================== 1054 | //========== class MicroGamer ========== 1055 | //====================================== 1056 | 1057 | MicroGamer::MicroGamer() 1058 | { 1059 | cursor_x = 0; 1060 | cursor_y = 0; 1061 | textColor = 1; 1062 | textBackground = 0; 1063 | textSize = 1; 1064 | textWrap = 0; 1065 | } 1066 | 1067 | // bootLogoText() should be kept in sync with bootLogoShell() 1068 | // if changes are made to one, equivalent changes should be made to the other 1069 | void MicroGamer::bootLogoText() 1070 | { 1071 | textSize = 2; 1072 | 1073 | for (int8_t y = -18; y <= 24; y++) { 1074 | clear(); 1075 | cursor_x = 23; 1076 | cursor_y = y; 1077 | print("Micro:Gamer"); 1078 | display(); 1079 | delayShort(27); 1080 | // longer delay post boot, we put it inside the loop to 1081 | // save the flash calling clear/delay again outside the loop 1082 | if (y==-16) { 1083 | delayShort(250); 1084 | } 1085 | } 1086 | 1087 | delayShort(700); 1088 | textSize = 1; 1089 | 1090 | bootLogoExtra(); 1091 | } 1092 | 1093 | void MicroGamer::bootLogoExtra() 1094 | { 1095 | // uint8_t c; 1096 | 1097 | // if (!readShowUnitNameFlag()) 1098 | // { 1099 | // return; 1100 | // } 1101 | 1102 | // c = EEPROM.read(EEPROM_UNIT_NAME); 1103 | 1104 | // if (c != 0xFF && c != 0x00) 1105 | // { 1106 | // uint8_t i = EEPROM_UNIT_NAME; 1107 | // cursor_x = 50; 1108 | // cursor_y = 56; 1109 | 1110 | // do 1111 | // { 1112 | // write(c); 1113 | // c = EEPROM.read(++i); 1114 | // } 1115 | // while (i < EEPROM_UNIT_NAME + ARDUBOY_UNIT_NAME_LEN); 1116 | 1117 | // display(); 1118 | // delayShort(1000); 1119 | // } 1120 | } 1121 | 1122 | size_t MicroGamer::write(uint8_t c) 1123 | { 1124 | if (c == '\n') 1125 | { 1126 | cursor_y += textSize * 8; 1127 | cursor_x = 0; 1128 | } 1129 | else if (c == '\r') 1130 | { 1131 | // skip em 1132 | } 1133 | else 1134 | { 1135 | drawChar(cursor_x, cursor_y, c, textColor, textBackground, textSize); 1136 | cursor_x += textSize * 6; 1137 | if (textWrap && (cursor_x > (WIDTH - textSize * 6))) 1138 | { 1139 | // calling ourselves recursively for 'newline' is 1140 | // 12 bytes smaller than doing the same math here 1141 | write('\n'); 1142 | } 1143 | } 1144 | return 1; 1145 | } 1146 | 1147 | void MicroGamer::drawChar 1148 | (int16_t x, int16_t y, unsigned char c, uint8_t color, uint8_t bg, uint8_t size) 1149 | { 1150 | uint8_t line; 1151 | bool draw_background = bg != color; 1152 | const unsigned char* bitmap = font + c * 5; 1153 | 1154 | if ((x >= WIDTH) || // Clip right 1155 | (y >= HEIGHT) || // Clip bottom 1156 | ((x + 5 * size - 1) < 0) || // Clip left 1157 | ((y + 8 * size - 1) < 0) // Clip top 1158 | ) 1159 | { 1160 | return; 1161 | } 1162 | 1163 | for (uint8_t i = 0; i < 6; i++ ) 1164 | { 1165 | line = pgm_read_byte(bitmap++); 1166 | if (i == 5) { 1167 | line = 0x0; 1168 | } 1169 | 1170 | for (uint8_t j = 0; j < 8; j++) 1171 | { 1172 | uint8_t draw_color = (line & 0x1) ? color : bg; 1173 | 1174 | if (draw_color || draw_background) { 1175 | for (uint8_t a = 0; a < size; a++ ) { 1176 | for (uint8_t b = 0; b < size; b++ ) { 1177 | drawPixel(x + (i * size) + a, y + (j * size) + b, draw_color); 1178 | } 1179 | } 1180 | } 1181 | line >>= 1; 1182 | } 1183 | } 1184 | } 1185 | 1186 | void MicroGamer::setCursor(int16_t x, int16_t y) 1187 | { 1188 | cursor_x = x; 1189 | cursor_y = y; 1190 | } 1191 | 1192 | int16_t MicroGamer::getCursorX() 1193 | { 1194 | return cursor_x; 1195 | } 1196 | 1197 | int16_t MicroGamer::getCursorY() 1198 | { 1199 | return cursor_y; 1200 | } 1201 | 1202 | void MicroGamer::setTextColor(uint8_t color) 1203 | { 1204 | textColor = color; 1205 | } 1206 | 1207 | uint8_t MicroGamer::getTextColor() 1208 | { 1209 | return textColor; 1210 | } 1211 | 1212 | void MicroGamer::setTextBackground(uint8_t bg) 1213 | { 1214 | textBackground = bg; 1215 | } 1216 | 1217 | uint8_t MicroGamer::getTextBackground() 1218 | { 1219 | return textBackground; 1220 | } 1221 | 1222 | void MicroGamer::setTextSize(uint8_t s) 1223 | { 1224 | // size must always be 1 or higher 1225 | textSize = max(1, s); 1226 | } 1227 | 1228 | uint8_t MicroGamer::getTextSize() 1229 | { 1230 | return textSize; 1231 | } 1232 | 1233 | void MicroGamer::setTextWrap(bool w) 1234 | { 1235 | textWrap = w; 1236 | } 1237 | 1238 | bool MicroGamer::getTextWrap() 1239 | { 1240 | return textWrap; 1241 | } 1242 | 1243 | void MicroGamer::clear() 1244 | { 1245 | MicroGamerBase::clear(); 1246 | cursor_x = cursor_y = 0; 1247 | } 1248 | 1249 | -------------------------------------------------------------------------------- /src/MicroGamerAudio.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerAudio.cpp 3 | * \brief 4 | * The MicroGamerAudio class for speaker and sound control. 5 | */ 6 | 7 | #include "MicroGamer.h" 8 | #include "MicroGamerAudio.h" 9 | 10 | bool MicroGamerAudio::audio_enabled = true; 11 | 12 | void MicroGamerAudio::on() 13 | { 14 | audio_enabled = true; 15 | } 16 | 17 | void MicroGamerAudio::off() 18 | { 19 | audio_enabled = false; 20 | } 21 | 22 | void MicroGamerAudio::toggle() 23 | { 24 | if (audio_enabled) 25 | off(); 26 | else 27 | on(); 28 | } 29 | 30 | void MicroGamerAudio::saveOnOff() 31 | { 32 | } 33 | 34 | void MicroGamerAudio::begin() 35 | { 36 | on(); 37 | } 38 | 39 | bool MicroGamerAudio::enabled() 40 | { 41 | return audio_enabled; 42 | } 43 | -------------------------------------------------------------------------------- /src/MicroGamerAudio.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerAudio.h 3 | * \brief 4 | * The MicroGamerAudio class for speaker and sound control. 5 | */ 6 | 7 | #ifndef MICROGAMER_AUDIO_H 8 | #define MICROGAMER_AUDIO_H 9 | 10 | #include 11 | //#include 12 | 13 | /** \brief 14 | * Provide speaker and sound control. 15 | * 16 | * \details 17 | * This class provides functions to initialize the speaker and control the 18 | * enabling and disabling (muting) of sound. It doesn't provide any functions 19 | * to actually produce sound. 20 | * 21 | * The state of sound muting is stored in system EEPROM and so is retained 22 | * over power cycles. 23 | * 24 | * An MicroGamerAudio class object named `audio` will be created by the 25 | * MicroGamerBase class, so there is no need for a sketch itself to create an 26 | * MicroGamerAudio object. MicroGamerAudio functions can be called using the 27 | * MicroGamer or MicroGamerBase `audio` object. 28 | * 29 | * Example: 30 | * 31 | * \code 32 | * #include 33 | * 34 | * MicroGamer arduboy; 35 | * 36 | * // MicroGamerAudio functions can be called as follows: 37 | * arduboy.audio.on(); 38 | * arduboy.audio.off(); 39 | * \endcode 40 | * 41 | * \note 42 | * \parblock 43 | * In order for this class to be fully functional, the external library or 44 | * functions used by a sketch to actually to produce sounds should be compliant 45 | * with this class. This means they should only produce sound if it is enabled, 46 | * or mute the sound if it's disabled. The `enabled()` function can be used 47 | * to determine if sound is enabled or muted. Generally a compliant library 48 | * would accept the `enabled()` function as an initialization parameter and 49 | * then call it as necessary to determine the current state. 50 | * 51 | * For example, the ArduboyTones and ArduboyPlaytune libraries require an 52 | * `enabled()` type function to be passed as a parameter in the constructor, 53 | * like so: 54 | * 55 | * \code 56 | * #include 57 | * #include 58 | * 59 | * MicroGamer arduboy; 60 | * ArduboyTones sound(arduboy.audio.enabled); 61 | * \endcode 62 | * \endparblock 63 | * 64 | * \note 65 | * \parblock 66 | * A friend class named _MicroGamerEx_ is declared by this class. The intention 67 | * is to allow a sketch to create an _MicroGamerEx_ class which would have access 68 | * to the private and protected members of the MicroGamerAudio class. It is hoped 69 | * that this may eliminate the need to create an entire local copy of the 70 | * library, in order to extend the functionality, in most circumstances. 71 | * \endparblock 72 | */ 73 | class MicroGamerAudio 74 | { 75 | friend class MicroGamerEx; 76 | 77 | public: 78 | /** \brief 79 | * Initialize the speaker based on the current mute setting. 80 | * 81 | * \details 82 | * The speaker is initialized based on the current mute setting saved in 83 | * system EEPROM. This function is called by `MicroGamerBase::begin()` so it 84 | * isn't normally required to call it within a sketch. However, if 85 | * `MicroGamerCore::boot()` is used instead of `MicroGamerBase::begin()` and the 86 | * sketch includes sound, then this function should be called after `boot()`. 87 | */ 88 | void static begin(); 89 | 90 | /** \brief 91 | * Turn sound on. 92 | * 93 | * \details 94 | * The system is configured to generate sound. This function sets the sound 95 | * mode only until the unit is powered off. To save the current mode use 96 | * `saveOnOff()`. 97 | * 98 | * \see off() toggle() saveOnOff() 99 | */ 100 | void static on(); 101 | 102 | /** \brief 103 | * Turn sound off (mute). 104 | * 105 | * \details 106 | * The system is configured to not produce sound (mute). This function sets 107 | * the sound mode only until the unit is powered off. To save the current 108 | * mode use `saveOnOff()`. 109 | * 110 | * \see on() toggle() saveOnOff() 111 | */ 112 | void static off(); 113 | 114 | /** \brief 115 | * Toggle the sound on/off state. 116 | * 117 | * \details 118 | * If the system is configured for sound on, it will be changed to sound off 119 | * (mute). If sound is off, it will be changed to on. This function sets 120 | * the sound mode only until the unit is powered off. To save the current 121 | * mode use `saveOnOff()`. 122 | * 123 | * \see on() off() saveOnOff() 124 | */ 125 | void static toggle(); 126 | 127 | /** \brief 128 | * Save the current sound state in EEPROM. 129 | * 130 | * \details 131 | * The current sound state, set by `on()` or `off()`, is saved to the 132 | * reserved system area in EEPROM. This allows the state to carry over between 133 | * power cycles and after uploading a different sketch. 134 | * 135 | * \note 136 | * EEPROM is limited in the number of times it can be written to. Sketches 137 | * should not continuously change and then save the state rapidly. 138 | * 139 | * \see on() off() toggle() 140 | */ 141 | void static saveOnOff(); 142 | 143 | /** \brief 144 | * Get the current sound state. 145 | * 146 | * \return `true` if sound is currently enabled (not muted). 147 | * 148 | * \details 149 | * This function should be used by code that actually generates sound. 150 | * If `true` is returned, sound can be produced. If `false` is returned, 151 | * sound should be muted. 152 | * 153 | * \see on() off() toggle() 154 | */ 155 | bool static enabled(); 156 | 157 | protected: 158 | bool static audio_enabled; 159 | }; 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /src/MicroGamerCore.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerCore.cpp 3 | * \brief 4 | * The MicroGamerCore class for MicroGamer hardware initilization and control. 5 | */ 6 | 7 | /* 8 | * Part of this file is based on Adafruit_SSD1306: 9 | */ 10 | 11 | /********************************************************************* 12 | This is a library for our Monochrome OLEDs based on SSD1306 drivers 13 | 14 | Pick one up today in the adafruit shop! 15 | ------> http://www.adafruit.com/category/63_98 16 | 17 | These displays use SPI to communicate, 4 or 5 pins are required to 18 | interface 19 | 20 | Adafruit invests time and resources providing this open source code, 21 | please support Adafruit and open-source hardware by purchasing 22 | products from Adafruit! 23 | 24 | Written by Limor Fried/Ladyada for Adafruit Industries. 25 | BSD license, check license.txt for more information 26 | All text above, and the splash screen must be included in any redistribution 27 | *********************************************************************/ 28 | 29 | #include "MicroGamerCore.h" 30 | #include 31 | 32 | #define SSD1306_I2C_ADDRESS 0x3C // 011110+SA0+RW - 0x3C or 0x3D 33 | 34 | #define SSD1306_LCDWIDTH 128 35 | #define SSD1306_LCDHEIGHT 64 36 | 37 | #define SSD1306_SETCONTRAST 0x81 38 | #define SSD1306_DISPLAYALLON_RESUME 0xA4 39 | #define SSD1306_DISPLAYALLON 0xA5 40 | #define SSD1306_NORMALDISPLAY 0xA6 41 | #define SSD1306_INVERTDISPLAY 0xA7 42 | #define SSD1306_DISPLAYOFF 0xAE 43 | #define SSD1306_DISPLAYON 0xAF 44 | 45 | #define SSD1306_SETDISPLAYOFFSET 0xD3 46 | #define SSD1306_SETCOMPINS 0xDA 47 | 48 | #define SSD1306_SETVCOMDETECT 0xDB 49 | 50 | #define SSD1306_SETDISPLAYCLOCKDIV 0xD5 51 | #define SSD1306_SETPRECHARGE 0xD9 52 | 53 | #define SSD1306_SETMULTIPLEX 0xA8 54 | 55 | #define SSD1306_SETLOWCOLUMN 0x00 56 | #define SSD1306_SETHIGHCOLUMN 0x10 57 | 58 | #define SSD1306_SETSTARTLINE 0x40 59 | 60 | #define SSD1306_MEMORYMODE 0x20 61 | #define SSD1306_COLUMNADDR 0x21 62 | #define SSD1306_PAGEADDR 0x22 63 | 64 | #define SSD1306_COMSCANINC 0xC0 65 | #define SSD1306_COMSCANDEC 0xC8 66 | 67 | #define SSD1306_SEGREMAP 0xA0 68 | 69 | #define SSD1306_CHARGEPUMP 0x8D 70 | 71 | #define SSD1306_EXTERNALVCC 0x1 72 | #define SSD1306_SWITCHCAPVCC 0x2 73 | 74 | // Scrolling #defines 75 | #define SSD1306_ACTIVATE_SCROLL 0x2F 76 | #define SSD1306_DEACTIVATE_SCROLL 0x2E 77 | #define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 78 | #define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 79 | #define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 80 | #define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 81 | #define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A 82 | 83 | // Two Wire Interface 84 | 85 | #define TWI_DEVICE (NRF_TWI1) 86 | #define TWI_PIN_SDA (20) 87 | #define TWI_PIN_SCL (19) 88 | #define TWI_IRQn (SPI1_TWI1_IRQn) 89 | 90 | const uint8_t PROGMEM lcdBootProgram[] = { 91 | // boot defaults are commented out but left here in case they 92 | // might prove useful for reference 93 | // 94 | // Further reading: https://www.adafruit.com/datasheets/SSD1306.pdf 95 | // 96 | // Display Off 97 | // 0xAE, 98 | 99 | // Set Display Clock Divisor v = 0xF0 100 | // default is 0x80 101 | 0xD5, 0xF0, 102 | 103 | // Set Multiplex Ratio v = 0x3F 104 | 0xA8, 0x3F, 105 | 106 | // Set Display Offset v = 0 107 | 0xD3, 0x00, 108 | 109 | // Set Start Line (0) 110 | 0x40, 111 | 112 | // Charge Pump Setting v = enable (0x14) 113 | // default is disabled 114 | 0x8D, 0x14, 115 | 116 | // Set Segment Re-map (A0) | (b0001) 117 | // default is (b0000) 118 | 0xA1, 119 | 120 | // Set COM Output Scan Direction 121 | 0xC8, 122 | 123 | // Set COM Pins v 124 | 0xDA, 0x12, 125 | 126 | // Set Contrast v = 0xCF 127 | 0x81, 0xCF, 128 | 129 | // Set Precharge = 0xF1 130 | 0xD9, 0xF1, 131 | 132 | // Set VCom Detect 133 | 0xDB, 0x40, 134 | 135 | // Entire Display ON 136 | 0xA4, 137 | 138 | // Set normal/inverse display 139 | 0xA6, 140 | 141 | // Display On 142 | 0xAF, 143 | 144 | // set display mode = horizontal addressing mode (0x00) 145 | 0x20, 0x00, 146 | 147 | // set col address range 148 | // 0x21, 0x00, COLUMN_ADDRESS_END, 149 | 150 | // set page address range 151 | // 0x22, 0x00, PAGE_ADDRESS_END 152 | 153 | OLED_HORIZ_FLIPPED, OLED_VERTICAL_FLIPPED // Flip the screen 154 | }; 155 | 156 | volatile bool twiInProgress = false; 157 | const uint8_t *twiTxData = NULL; 158 | size_t twiByteToSend = 0; 159 | 160 | MicroGamerCore::MicroGamerCore() 161 | { 162 | twiInProgress = false; 163 | } 164 | 165 | void MicroGamerCore::boot() 166 | { 167 | bootPins(); 168 | bootTWI(); 169 | bootOLED(); 170 | bootPowerSaving(); 171 | } 172 | 173 | // Pins are set to the proper modes and levels for the specific hardware. 174 | // This routine must be modified if any pins are moved to a different port 175 | void MicroGamerCore::bootPins() 176 | { 177 | pinMode(BUTTON_A_PIN, INPUT); 178 | pinMode(BUTTON_B_PIN, INPUT); 179 | pinMode(BUTTON_X_PIN, INPUT_PULLUP); 180 | pinMode(BUTTON_Y_PIN, INPUT_PULLUP); 181 | pinMode(BUTTON_UP_PIN, INPUT_PULLUP); 182 | pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); 183 | pinMode(BUTTON_LEFT_PIN, INPUT_PULLUP); 184 | pinMode(BUTTON_RIGHT_PIN, INPUT_PULLUP); 185 | } 186 | 187 | void MicroGamerCore::bootOLED() 188 | { 189 | // Setup reset pin direction (used by both SPI and I2C) 190 | pinMode(SCREEN_RESET_PIN, OUTPUT); 191 | digitalWrite(SCREEN_RESET_PIN, HIGH); 192 | // VDD (3.3V) goes high at start, lets just chill for a ms 193 | delay(1); 194 | // bring reset low 195 | digitalWrite(SCREEN_RESET_PIN, LOW); 196 | // wait 10ms 197 | delay(10); 198 | // bring out of reset 199 | digitalWrite(SCREEN_RESET_PIN, HIGH); 200 | // turn on VCC (9V?) 201 | 202 | // run our customized boot-up command sequence against the 203 | // OLED to initialize it properly for MicroGamer 204 | for (uint8_t i = 0; i < sizeof(lcdBootProgram); i++) { 205 | sendLCDCommand(pgm_read_byte(lcdBootProgram + i)); 206 | } 207 | } 208 | 209 | void MicroGamerCore::bootTWI() 210 | { 211 | TWI_DEVICE->EVENTS_TXDSENT = 0; 212 | TWI_DEVICE->EVENTS_STOPPED = 0; 213 | TWI_DEVICE->EVENTS_RXDREADY = 0; 214 | TWI_DEVICE->EVENTS_ERROR = 0; 215 | 216 | TWI_DEVICE->INTENSET = 217 | TWI_INTENSET_TXDSENT_Set << TWI_INTENSET_TXDSENT_Pos | 218 | TWI_INTENSET_STOPPED_Set << TWI_INTENSET_STOPPED_Pos | 219 | TWI_INTENSET_ERROR_Set << TWI_INTENSET_ERROR_Pos | 220 | TWI_INTENSET_RXDREADY_Set << TWI_INTENSET_RXDREADY_Pos; 221 | 222 | Wire.begin(); 223 | Wire.setClock(400000); 224 | } 225 | 226 | /* TWI */ 227 | 228 | void MicroGamerCore::twiBeginTransmission(uint8_t address) 229 | { 230 | TWI_DEVICE->ADDRESS = address; 231 | TWI_DEVICE->SHORTS = 0x0UL; 232 | TWI_DEVICE->TASKS_RESUME = 0x1UL; 233 | TWI_DEVICE->TASKS_STARTTX = 0x1UL; 234 | } 235 | 236 | uint8_t MicroGamerCore::twiTransmit(const uint8_t data[], 237 | size_t quantity) 238 | { 239 | for(size_t i = 0; i < quantity; ++i) 240 | { 241 | TWI_DEVICE->TXD = data[i]; 242 | 243 | while(!TWI_DEVICE->EVENTS_TXDSENT && !TWI_DEVICE->EVENTS_ERROR); 244 | 245 | if (TWI_DEVICE->EVENTS_ERROR) 246 | { 247 | break; 248 | } 249 | 250 | TWI_DEVICE->EVENTS_TXDSENT = 0x0UL; 251 | } 252 | 253 | if (TWI_DEVICE->EVENTS_ERROR) 254 | { 255 | TWI_DEVICE->EVENTS_ERROR = 0x0UL; 256 | 257 | uint32_t error = TWI_DEVICE->ERRORSRC; 258 | 259 | TWI_DEVICE->ERRORSRC = error; 260 | 261 | if (error == TWI_ERRORSRC_ANACK_Msk) 262 | { 263 | return 2; 264 | } 265 | else if (error == TWI_ERRORSRC_DNACK_Msk) 266 | { 267 | return 3; 268 | } 269 | else 270 | { 271 | return 4; 272 | } 273 | } 274 | 275 | return 0; 276 | } 277 | 278 | void MicroGamerCore::twiTransmitAsync(const uint8_t data[], 279 | size_t quantity) 280 | { 281 | 282 | if (quantity == 0) { 283 | return; 284 | } 285 | 286 | if (quantity > 1 ) { 287 | twiInProgress = true; 288 | twiTxData = data + 1; 289 | twiByteToSend = quantity - 1; 290 | 291 | NVIC_ClearPendingIRQ(TWI_IRQn); 292 | NVIC_EnableIRQ(TWI_IRQn); 293 | } 294 | 295 | TWI_DEVICE->TXD = data[0]; 296 | } 297 | 298 | uint8_t MicroGamerCore::twiTransmit(uint8_t data) 299 | { 300 | twiTransmit(&data, 1); 301 | } 302 | 303 | uint8_t MicroGamerCore::twiEndTransmission() 304 | { 305 | TWI_DEVICE->TASKS_STOP = 0x1UL; 306 | while(!TWI_DEVICE->EVENTS_STOPPED); 307 | TWI_DEVICE->EVENTS_STOPPED = 0x0UL; 308 | 309 | if (TWI_DEVICE->EVENTS_ERROR) 310 | { 311 | TWI_DEVICE->EVENTS_ERROR = 0x0UL; 312 | 313 | uint32_t error = TWI_DEVICE->ERRORSRC; 314 | 315 | TWI_DEVICE->ERRORSRC = error; 316 | 317 | if (error == TWI_ERRORSRC_ANACK_Msk) 318 | { 319 | return 2; 320 | } 321 | else if (error == TWI_ERRORSRC_DNACK_Msk) 322 | { 323 | return 3; 324 | } 325 | else 326 | { 327 | return 4; 328 | } 329 | } 330 | 331 | return 0; 332 | } 333 | 334 | extern "C" { 335 | 336 | void SPI1_TWI1_IRQHandler(void) 337 | { 338 | 339 | if(TWI_DEVICE->EVENTS_TXDSENT) 340 | { 341 | TWI_DEVICE->EVENTS_TXDSENT = 0; 342 | if(twiByteToSend != 0) 343 | { 344 | TWI_DEVICE->TXD = *twiTxData++; 345 | twiByteToSend--; 346 | } 347 | else 348 | { 349 | TWI_DEVICE->TASKS_STOP = 1; 350 | } 351 | } 352 | 353 | if(TWI_DEVICE->EVENTS_STOPPED) 354 | { 355 | TWI_DEVICE->EVENTS_STOPPED = 0; 356 | twiInProgress = false; 357 | NVIC_DisableIRQ(TWI_IRQn); 358 | } 359 | 360 | if(TWI_DEVICE->EVENTS_RXDREADY) 361 | { 362 | TWI_DEVICE->EVENTS_RXDREADY = 0; 363 | } 364 | 365 | if(TWI_DEVICE->EVENTS_ERROR) 366 | { 367 | TWI_DEVICE->EVENTS_ERROR = 0; 368 | twiInProgress = false; 369 | NVIC_DisableIRQ(TWI_IRQn); 370 | } 371 | } 372 | 373 | } 374 | 375 | /* Power Management */ 376 | 377 | void MicroGamerCore::idle() 378 | { 379 | // set_sleep_mode(SLEEP_MODE_IDLE); 380 | // sleep_mode(); 381 | } 382 | 383 | void MicroGamerCore::bootPowerSaving() 384 | { 385 | // // disable Two Wire Interface (I2C) and the ADC 386 | // PRR0 = _BV(PRTWI) | _BV(PRADC); 387 | // // disable USART1 388 | // PRR1 = _BV(PRUSART1); 389 | // // All other bits will be written with 0 so will be enabled 390 | } 391 | 392 | // Shut down the display 393 | void MicroGamerCore::displayOff() 394 | { 395 | sendLCDCommand(0xAE, // display off 396 | 0x8D, // charge pump: 397 | 0x10); // disable 398 | delayShort(250); 399 | } 400 | 401 | // Restart the display after a displayOff() 402 | void MicroGamerCore::displayOn() 403 | { 404 | bootOLED(); 405 | } 406 | 407 | uint8_t MicroGamerCore::width() { return WIDTH; } 408 | 409 | uint8_t MicroGamerCore::height() { return HEIGHT; } 410 | 411 | 412 | /* Drawing */ 413 | 414 | void MicroGamerCore::paintScreen(const uint8_t *image) 415 | { 416 | waitEndOfPaintScreen(); 417 | 418 | sendLCDCommand(SSD1306_COLUMNADDR); 419 | sendLCDCommand(0); // Column start address (0 = reset) 420 | sendLCDCommand(WIDTH-1); // Column end address (127 = reset) 421 | 422 | sendLCDCommand(SSD1306_PAGEADDR); 423 | sendLCDCommand(0); // Page start address (0 = reset) 424 | sendLCDCommand(7); // Page end address 425 | 426 | twiBeginTransmission(SSD1306_I2C_ADDRESS); 427 | twiTransmit(0x40); 428 | twiTransmitAsync(image, SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); 429 | } 430 | 431 | bool MicroGamerCore::paintScreenInProgress() 432 | { 433 | return twiInProgress; 434 | } 435 | 436 | void MicroGamerCore::waitEndOfPaintScreen() 437 | { 438 | while (twiInProgress) { 439 | idle(); 440 | } 441 | } 442 | 443 | void MicroGamerCore::sendLCDCommand(uint8_t command) 444 | { 445 | uint8_t data[2] = {0x00, // Co = 0, D/C = 0 446 | command}; 447 | twiBeginTransmission(SSD1306_I2C_ADDRESS); 448 | twiTransmit(data, 2); 449 | twiEndTransmission(); 450 | } 451 | 452 | void MicroGamerCore::sendLCDCommand(uint8_t command, 453 | uint8_t command2) 454 | { 455 | uint8_t data[3] = {0x00, // Co = 0, D/C = 0 456 | command, 457 | command2}; 458 | twiBeginTransmission(SSD1306_I2C_ADDRESS); 459 | twiTransmit(data, 3); 460 | twiEndTransmission(); 461 | } 462 | 463 | void MicroGamerCore::sendLCDCommand(uint8_t command, 464 | uint8_t command2, 465 | uint8_t command3) 466 | { 467 | uint8_t data[4] = {0x00, // Co = 0, D/C = 0 468 | command, 469 | command2, 470 | command3}; 471 | twiBeginTransmission(SSD1306_I2C_ADDRESS); 472 | twiTransmit(data, 4); 473 | twiEndTransmission(); 474 | } 475 | 476 | // invert the display or set to normal 477 | // when inverted, a pixel set to 0 will be on 478 | void MicroGamerCore::invert(bool inverse) 479 | { 480 | sendLCDCommand(inverse ? OLED_PIXELS_INVERTED : OLED_PIXELS_NORMAL); 481 | } 482 | 483 | // turn all display pixels on, ignoring buffer contents 484 | // or set to normal buffer display 485 | void MicroGamerCore::allPixelsOn(bool on) 486 | { 487 | sendLCDCommand(on ? OLED_ALL_PIXELS_ON : OLED_PIXELS_FROM_RAM); 488 | } 489 | 490 | // flip the display vertically or set to normal 491 | void MicroGamerCore::flipVertical(bool flipped) 492 | { 493 | sendLCDCommand(flipped ? OLED_VERTICAL_NORMAL: OLED_VERTICAL_FLIPPED); 494 | } 495 | 496 | // flip the display horizontally or set to normal 497 | void MicroGamerCore::flipHorizontal(bool flipped) 498 | { 499 | sendLCDCommand(flipped ? OLED_HORIZ_NORMAL: OLED_HORIZ_FLIPPED); 500 | } 501 | 502 | /* Buttons */ 503 | 504 | uint8_t MicroGamerCore::buttonsState() 505 | { 506 | uint8_t buttons = 0; 507 | 508 | if (! digitalRead(BUTTON_LEFT_PIN)) { 509 | buttons |= LEFT_BUTTON; 510 | } 511 | if (! digitalRead(BUTTON_RIGHT_PIN)) { 512 | buttons |= RIGHT_BUTTON; 513 | } 514 | if (! digitalRead(BUTTON_UP_PIN)) { 515 | buttons |= UP_BUTTON; 516 | } 517 | if (! digitalRead(BUTTON_DOWN_PIN)) { 518 | buttons |= DOWN_BUTTON; 519 | } 520 | if (! digitalRead(BUTTON_A_PIN)) { 521 | buttons |= A_BUTTON; 522 | } 523 | if (! digitalRead(BUTTON_B_PIN)) { 524 | buttons |= B_BUTTON; 525 | } 526 | if (! digitalRead(BUTTON_Y_PIN)) { 527 | buttons |= Y_BUTTON; 528 | } 529 | if (! digitalRead(BUTTON_X_PIN)) { 530 | buttons |= X_BUTTON; 531 | } 532 | 533 | return buttons; 534 | } 535 | 536 | // delay in ms with 16 bit duration 537 | void MicroGamerCore::delayShort(uint16_t ms) 538 | { 539 | delay((unsigned long) ms); 540 | } 541 | -------------------------------------------------------------------------------- /src/MicroGamerCore.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerCore.h 3 | * \brief 4 | * The MicroGamerCore class for MicroGamer hardware initilization and control. 5 | */ 6 | 7 | #ifndef MICROGAMER_CORE_H 8 | #define MICROGAMER_CORE_H 9 | 10 | #include 11 | //#include 12 | //#include 13 | #include 14 | 15 | 16 | 17 | // bit values for button states 18 | // these are determined by the buttonsState() function 19 | #define LEFT_BUTTON (1<<0) /**< The Left button value for functions requiring a bitmask */ 20 | #define RIGHT_BUTTON (1<<1) /**< The Right button value for functions requiring a bitmask */ 21 | #define UP_BUTTON (1<<2) /**< The Up button value for functions requiring a bitmask */ 22 | #define DOWN_BUTTON (1<<3) /**< The Down button value for functions requiring a bitmask */ 23 | #define A_BUTTON (1<<4) /**< The A button value for functions requiring a bitmask */ 24 | #define B_BUTTON (1<<5) /**< The B button value for functions requiring a bitmask */ 25 | #define Y_BUTTON (1<<6) /**< The Y button value for functions requiring a bitmask */ 26 | #define X_BUTTON (1<<7) /**< The X button value for functions requiring a bitmask */ 27 | 28 | // -------------------- 29 | 30 | // OLED hardware (SSD1306) 31 | 32 | #define OLED_PIXELS_INVERTED 0xA7 // All pixels inverted 33 | #define OLED_PIXELS_NORMAL 0xA6 // All pixels normal 34 | 35 | #define OLED_ALL_PIXELS_ON 0xA5 // all pixels on 36 | #define OLED_PIXELS_FROM_RAM 0xA4 // pixels mapped to display RAM contents 37 | 38 | #define OLED_VERTICAL_FLIPPED 0xC0 // reversed COM scan direction 39 | #define OLED_VERTICAL_NORMAL 0xC8 // normal COM scan direction 40 | 41 | #define OLED_HORIZ_FLIPPED 0xA0 // reversed segment re-map 42 | #define OLED_HORIZ_NORMAL 0xA1 // normal segment re-map 43 | 44 | #define SCREEN_RESET_PIN (8) 45 | 46 | // ----- 47 | 48 | #define WIDTH (128) /**< The width of the display in pixels */ 49 | #define HEIGHT (64) /**< The height of the display in pixels */ 50 | 51 | #define COLUMN_ADDRESS_END (WIDTH - 1) & 127 // 128 pixels wide 52 | #define PAGE_ADDRESS_END ((HEIGHT/8)-1) & 7 // 8 pages high 53 | 54 | #define BUTTON_A_PIN (5) 55 | #define BUTTON_B_PIN (11) 56 | #define BUTTON_X_PIN (0) 57 | #define BUTTON_Y_PIN (1) 58 | #define BUTTON_UP_PIN (15) 59 | #define BUTTON_DOWN_PIN (14) 60 | #define BUTTON_LEFT_PIN (16) 61 | #define BUTTON_RIGHT_PIN (13) 62 | 63 | /** \brief 64 | * Lower level functions generally dealing directly with the hardware. 65 | * 66 | * \details 67 | * This class is inherited by MicroGamerBase and thus also MicroGamer, so wouldn't 68 | * normally be used directly by a sketch. 69 | * 70 | * \note 71 | * A friend class named _MicroGamerEx_ is declared by this class. The intention 72 | * is to allow a sketch to create an _MicroGamerEx_ class which would have access 73 | * to the private and protected members of the MicroGamerCore class. It is hoped 74 | * that this may eliminate the need to create an entire local copy of the 75 | * library, in order to extend the functionality, in most circumstances. 76 | */ 77 | class MicroGamerCore 78 | { 79 | friend class MicroGamerEx; 80 | 81 | public: 82 | MicroGamerCore(); 83 | 84 | /** \brief 85 | * Idle the CPU to save power. 86 | * 87 | * \details 88 | * This puts the CPU in _idle_ sleep mode. You should call this as often 89 | * as you can for the best power savings. The timer 0 overflow interrupt 90 | * will wake up the chip every 1ms, so even at 60 FPS a well written 91 | * app should be able to sleep maybe half the time in between rendering 92 | * it's own frames. 93 | */ 94 | void static idle(); 95 | 96 | /** \brief 97 | * Turn the display off. 98 | * 99 | * \details 100 | * The display will clear and be put into a low power mode. This can be 101 | * used to extend battery life when a game is paused or when a sketch 102 | * doesn't require anything to be displayed for a relatively long period 103 | * of time. 104 | * 105 | * \see displayOn() 106 | */ 107 | void static displayOff(); 108 | 109 | /** \brief 110 | * Turn the display on. 111 | * 112 | * \details 113 | * Used to power up and reinitialize the display after calling 114 | * `displayOff()`. 115 | * 116 | * \note 117 | * The previous call to `displayOff()` will have caused the display's 118 | * buffer contents to be lost. The display will have to be re-painted, 119 | * which is usually done by calling `display()`. 120 | * 121 | * \see displayOff() 122 | */ 123 | void static displayOn(); 124 | 125 | /** \brief 126 | * Get the width of the display in pixels. 127 | * 128 | * \return The width of the display in pixels. 129 | * 130 | * \note 131 | * In most cases, the defined value `WIDTH` would be better to use instead 132 | * of this function. 133 | */ 134 | uint8_t static width(); 135 | 136 | /** \brief 137 | * Get the height of the display in pixels. 138 | * 139 | * \return The height of the display in pixels. 140 | * 141 | * \note 142 | * In most cases, the defined value `HEIGHT` would be better to use instead 143 | * of this function. 144 | */ 145 | uint8_t static height(); 146 | 147 | /** \brief 148 | * Get the current state of all buttons as a bitmask. 149 | * 150 | * \return A bitmask of the state of all the buttons. 151 | * 152 | * \details 153 | * The returned mask contains a bit for each button. For any pressed button, 154 | * its bit will be 1. For released buttons their associated bits will be 0. 155 | * 156 | * The following defined mask values should be used for the buttons: 157 | * 158 | * LEFT_BUTTON, RIGHT_BUTTON, UP_BUTTON, DOWN_BUTTON, A_BUTTON, B_BUTTON, 159 | * Y_BUTTON, X_BUTTON 160 | */ 161 | uint8_t static buttonsState(); 162 | 163 | /** \brief 164 | * Asynchronously paints an entire image directly to the display from 165 | * program memory. 166 | * 167 | * \param image A byte array in program memory representing the entire 168 | * contents of the display. 169 | * 170 | * \details 171 | * The contents of the specified array in program memory is written to the 172 | * display. This is an asynchronous function, which means that the function 173 | * will return before the buffer is completely sent to the display. Each 174 | * byte in the array represents a vertical column of 8 pixels with the least 175 | * significant bit at the top. The bytes are written starting at the top 176 | * left, progressing horizontally and wrapping at the end of each row, to 177 | * the bottom right. The size of the array must exactly match the number of 178 | * pixels in the entire display. 179 | * 180 | * \see paintScreenInProgress() waitEndOfPaintScreen() 181 | */ 182 | void static paintScreen(const uint8_t *image); 183 | 184 | /** \brief 185 | * Paint screen in progress. 186 | * 187 | * \return True if a screen transfer is in progress. 188 | * 189 | * \see paintScreen() waitEndOfPaintScreen() 190 | */ 191 | bool static paintScreenInProgress(); 192 | 193 | /** \brief 194 | * Wait end of paint screen. 195 | * 196 | * \see paintScreen() 197 | */ 198 | void static waitEndOfPaintScreen(); 199 | 200 | /** \brief 201 | * Invert the entire display or set it back to normal. 202 | * 203 | * \param inverse `true` will invert the display. `false` will set the 204 | * display to no-inverted. 205 | * 206 | * \details 207 | * Calling this function with a value of `true` will set the display to 208 | * inverted mode. A pixel with a value of 0 will be on and a pixel set to 1 209 | * will be off. 210 | * 211 | * Once in inverted mode, the display will remain this way 212 | * until it is set back to non-inverted mode by calling this function with 213 | * `false`. 214 | */ 215 | void static invert(bool inverse); 216 | 217 | /** \brief 218 | * Turn all display pixels on or display the buffer contents. 219 | * 220 | * \param on `true` turns all pixels on. `false` displays the contents 221 | * of the hardware display buffer. 222 | * 223 | * \details 224 | * Calling this function with a value of `true` will override the contents 225 | * of the hardware display buffer and turn all pixels on. The contents of 226 | * the hardware buffer will remain unchanged. 227 | * 228 | * Calling this function with a value of `false` will set the normal state 229 | * of displaying the contents of the hardware display buffer. 230 | * 231 | * \note 232 | * All pixels will be lit even if the display is in inverted mode. 233 | * 234 | * \see invert() 235 | */ 236 | void static allPixelsOn(bool on); 237 | 238 | /** \brief 239 | * Flip the display vertically or set it back to normal. 240 | * 241 | * \param flipped `true` will set vertical flip mode. `false` will set 242 | * normal vertical orientation. 243 | * 244 | * \details 245 | * Calling this function with a value of `true` will cause the Y coordinate 246 | * to start at the bottom edge of the display instead of the top, 247 | * effectively flipping the display vertically. 248 | * 249 | * Once in vertical flip mode, it will remain this way until normal 250 | * vertical mode is set by calling this function with a value of `false`. 251 | * 252 | * \see flipHorizontal() 253 | */ 254 | void static flipVertical(bool flipped); 255 | 256 | /** \brief 257 | * Flip the display horizontally or set it back to normal. 258 | * 259 | * \param flipped `true` will set horizontal flip mode. `false` will set 260 | * normal horizontal orientation. 261 | * 262 | * \details 263 | * Calling this function with a value of `true` will cause the X coordinate 264 | * to start at the left edge of the display instead of the right, 265 | * effectively flipping the display horizontally. 266 | * 267 | * Once in horizontal flip mode, it will remain this way until normal 268 | * horizontal mode is set by calling this function with a value of `false`. 269 | * 270 | * \see flipVertical() 271 | */ 272 | void static flipHorizontal(bool flipped); 273 | 274 | /** \brief 275 | * Send a single command byte to the display. 276 | * 277 | * \param command The command byte to send to the display. 278 | * 279 | * \details 280 | * The display will be set to command mode then the specified command 281 | * byte will be sent. The display will then be set to data mode. 282 | * Multi-byte commands can be sent by calling this function multiple times. 283 | * 284 | * \note 285 | * Sending improper commands to the display can place it into invalid or 286 | * unexpected states, possibly even causing physical damage. 287 | */ 288 | void static sendLCDCommand(uint8_t command); 289 | 290 | void static sendLCDCommand(uint8_t command, 291 | uint8_t command2); 292 | 293 | void static sendLCDCommand(uint8_t command, 294 | uint8_t command2, 295 | uint8_t command3); 296 | 297 | /** \brief 298 | * Initialize the MicroGamer's hardware. 299 | * 300 | * \details 301 | * This function initializes the display, buttons, etc. 302 | * 303 | * This function is called by begin() so isn't normally called within a 304 | * sketch. However, in order to free up some code space, by eliminating 305 | * some of the start up features, it can be called in place of begin(). 306 | * The functions that begin() would call after boot() can then be called 307 | * to add back in some of the start up features, if desired. 308 | * See the README file or documentation on the main page for more details. 309 | * 310 | * \see MicroGamerBase::begin() 311 | */ 312 | void static boot(); 313 | 314 | /** \brief 315 | * Delay for the number of milliseconds, specified as a 16 bit value. 316 | * 317 | * \param ms The delay in milliseconds. 318 | * 319 | * \details 320 | * This function works the same as the Arduino `delay()` function except 321 | * the provided value is 16 bits long, so the maximum delay allowed is 322 | * 65535 milliseconds (about 65.5 seconds). Using this function instead 323 | * of Arduino `delay()` will save a few bytes of code. 324 | */ 325 | void static delayShort(uint16_t ms) __attribute__ ((noinline)); 326 | 327 | protected: 328 | // internals 329 | void static bootOLED(); 330 | void static bootPins(); 331 | void static bootPowerSaving(); 332 | void static bootTWI(); 333 | 334 | void static twiBeginTransmission(uint8_t address); 335 | uint8_t static twiTransmit(const uint8_t data[], 336 | size_t quantity); 337 | 338 | void static twiTransmitAsync(const uint8_t data[], 339 | size_t quantity); 340 | uint8_t static twiTransmit(uint8_t data); 341 | uint8_t static twiEndTransmission(); 342 | }; 343 | 344 | #endif 345 | -------------------------------------------------------------------------------- /src/MicroGamerMemoryCard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "MicroGamerMemoryCard.h" 3 | 4 | 5 | #define FLASH_PAGE_SIZE (1024) 6 | 7 | uint32_t flash_data[FLASH_PAGE_SIZE] 8 | __attribute__((aligned(FLASH_PAGE_SIZE), section (".rodata"))) 9 | = { 0 }; 10 | 11 | static void memcpy_by_word(uint32_t *dest, const uint32_t *src, size_t n) 12 | { 13 | int i = 0; 14 | for (i = 0; i < n; i++) { 15 | dest[i] = src[i]; 16 | } 17 | } 18 | 19 | MicroGamerMemoryCard::MicroGamerMemoryCard(size_t data_length_in_word) 20 | : _data_length(data_length_in_word) 21 | , _data(new uint32_t[data_length_in_word]) 22 | { 23 | } 24 | 25 | void MicroGamerMemoryCard::load() 26 | { 27 | // Load data from flash to the RAM buffer 28 | memcpy_by_word(_data, flash_data, _data_length); 29 | } 30 | 31 | void MicroGamerMemoryCard::save() 32 | { 33 | // Wait for the end of a current operation, if any 34 | while (NRF_NVMC->READY == 0) { 35 | continue; 36 | } 37 | 38 | // Enable erase 39 | NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos; 40 | 41 | // Erase the page in flash 42 | NRF_NVMC->ERASEPCR1 = (uint32_t)flash_data; 43 | 44 | // Wait for the end of the erase operation 45 | while (NRF_NVMC->READY == 0) { 46 | continue; 47 | } 48 | 49 | // Disable erase, Enable write 50 | NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos; 51 | 52 | memcpy_by_word(flash_data, _data, _data_length); 53 | 54 | // Wait for the end of write operation 55 | while (NRF_NVMC->READY == 0) { 56 | continue; 57 | } 58 | 59 | // Disable write 60 | NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos; 61 | 62 | } 63 | 64 | uint8_t *MicroGamerMemoryCard::data() 65 | { 66 | return (uint8_t *)_data; 67 | } 68 | 69 | void MicroGamerMemoryCard::update(int offset, uint8_t b) 70 | { 71 | write(offset, b); 72 | } 73 | 74 | void MicroGamerMemoryCard::write(int offset, uint8_t b) 75 | { 76 | data()[offset] = b; 77 | } 78 | 79 | uint8_t MicroGamerMemoryCard::read(int offset) 80 | { 81 | return data()[offset]; 82 | } 83 | -------------------------------------------------------------------------------- /src/MicroGamerMemoryCard.h: -------------------------------------------------------------------------------- 1 | #ifndef MICROGAMER_MEMORYCARD_H 2 | #define MICROGAMER_MEMORYCARD_H 3 | 4 | /** \brief 5 | * Provide non volatile memory for Micro:Gamer platform. 6 | * 7 | * \details 8 | * This class provides functions to save and load data to that is preserved when 9 | * the Micro:Gamer is powered off. 10 | * 11 | * Example: 12 | * 13 | * \code 14 | * #include 15 | * 16 | * MicroGamerMemoryCard mem(1); // A memory card of 1 word (4 bytes); 17 | * 18 | * ... 19 | * mem.load() 20 | * *mem.data() += 42; 21 | * mem.save(); 22 | * \endcode 23 | * 24 | */ 25 | class MicroGamerMemoryCard 26 | { 27 | 28 | public: 29 | 30 | /** \brief 31 | * The MicroGamerMemoryCard class constructor. 32 | * 33 | * \param data_length_in_word The size in words (32bit) of data that can be 34 | * saved. This value has to be lower or equal to 256. The size limit 35 | * requirement comes from the implementation that only supports writing one 36 | * page (1024 bytes) of memory. 37 | */ 38 | MicroGamerMemoryCard(size_t data_length_in_word); 39 | 40 | /** \brief 41 | * Load the non-volatile data in a writable temporary RAM buffer. 42 | * 43 | * \see load() 44 | */ 45 | void load(); 46 | 47 | /** \brief 48 | * Save the writable RAM buffer into the non-volatile memory. 49 | * 50 | * \see load() 51 | */ 52 | void save(); 53 | 54 | /** \brief 55 | * Return a pointer to the temporary RAM buffer. 56 | * 57 | * \details 58 | * This address can be used to read from and write to the temporary RAM buffer 59 | * before saving it to the non-volatile memory. 60 | * 61 | * \see load() save() 62 | */ 63 | uint8_t * data(); 64 | 65 | /** \brief 66 | * Write a byte in temporary RAM buffer. 67 | * 68 | * \param offset Offset in bytes from the data() address where the byte will 69 | * be written. 70 | * 71 | * \see load() save() 72 | */ 73 | void update(int offset, uint8_t b); 74 | 75 | /** \brief 76 | * Write a byte in temporary RAM buffer. 77 | * 78 | * \param offset Offset in bytes from the data() address where the byte will 79 | * be written. 80 | * 81 | * \see load() save() 82 | */ 83 | void write(int offset, uint8_t b); 84 | 85 | /** \brief 86 | * Write a byte in temporary RAM buffer. 87 | * 88 | * \param offset Offset in bytes from the data() address where the byte will 89 | * be written. 90 | * 91 | * \see load() save() 92 | */ 93 | uint8_t read(int offset); 94 | 95 | /** \brief 96 | * Read an object from the temporary RAM buffer. 97 | * 98 | * \param offset Offset in bytes from the data() address where the object will 99 | * be read. 100 | * 101 | * \see load() save() 102 | */ 103 | template 104 | T &get(int offset, T &t) 105 | { 106 | uint8_t *e = data() + offset; 107 | uint8_t *ptr = (uint8_t*) &t; 108 | for (int count = sizeof(T); count; --count, ++e) { 109 | *ptr++ = *e; 110 | } 111 | return t; 112 | } 113 | 114 | /** \brief 115 | * Write an object tothe temporary RAM buffer. 116 | * 117 | * \param offset Offset in bytes from the data() address where the object will 118 | * be written. 119 | * 120 | * \see load() save() 121 | */ 122 | template 123 | const T &put(int offset, const T &t) 124 | { 125 | uint8_t *e = data() + offset; 126 | const uint8_t *ptr = (const uint8_t*) &t; 127 | for (int count = sizeof(T); count; --count, ++e) { 128 | *e = *ptr++; 129 | } 130 | return t; 131 | } 132 | 133 | protected: 134 | size_t _data_length; 135 | uint32_t *_data; 136 | }; 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /src/MicroGamerTones.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerTones.cpp 3 | * \brief An Arduino library for playing tones and tone sequences, 4 | * intended for the MicroGamer game system. 5 | */ 6 | 7 | /***************************************************************************** 8 | MicroGamerTones 9 | 10 | An Arduino library to play tones and tone sequences. 11 | 12 | Specifically written for use by the MicroGamer miniature game system 13 | https://www.arduboy.com/ 14 | but could work with other Arduino AVR boards that have 16 bit timer 3 15 | available, by changing the port and bit definintions for the pin(s) 16 | if necessary. 17 | 18 | Copyright (c) 2017 Scott Allen 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | *****************************************************************************/ 38 | 39 | #include "MicroGamerTones.h" 40 | 41 | // pointer to a function that indicates if sound is enabled 42 | static bool (*outputEnabled)(); 43 | 44 | static volatile long durationCount = 0; 45 | static volatile bool tonesPlaying = false; 46 | static volatile bool toneSilent; 47 | #ifdef TONES_VOLUME_CONTROL 48 | static volatile bool toneHighVol; 49 | static volatile bool forceHighVol = false; 50 | static volatile bool forceNormVol = false; 51 | #endif 52 | 53 | static volatile uint16_t *tonesStart; 54 | static volatile uint16_t *tonesIndex; 55 | static volatile uint16_t toneSequence[MAX_TONES * 2 + 1]; 56 | static volatile bool inProgmem; 57 | 58 | #define AUDIO_TIMER_PRESCALER 5 59 | #define AUDIO_PIN 2 60 | 61 | MicroGamerTones::MicroGamerTones(boolean (*outEn)()) 62 | { 63 | outputEnabled = outEn; 64 | 65 | toneSequence[MAX_TONES * 2] = TONES_END; 66 | 67 | pinMode(AUDIO_PIN, OUTPUT); 68 | 69 | NRF_TIMER2->MODE = (NRF_TIMER2->MODE & ~TIMER_MODE_MODE_Msk) | ((TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos) & TIMER_MODE_MODE_Msk); 70 | 71 | NRF_TIMER2->BITMODE = (NRF_TIMER2->BITMODE & ~TIMER_BITMODE_BITMODE_Msk) | ((TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos) & TIMER_BITMODE_BITMODE_Msk); 72 | 73 | NRF_TIMER2->PRESCALER = (NRF_TIMER2->PRESCALER & ~TIMER_PRESCALER_PRESCALER_Msk) | ((AUDIO_TIMER_PRESCALER << TIMER_PRESCALER_PRESCALER_Pos) & TIMER_PRESCALER_PRESCALER_Msk); 74 | 75 | NRF_TIMER2->SHORTS = (NRF_TIMER2->SHORTS & ~TIMER_SHORTS_COMPARE0_CLEAR_Msk) | ((TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos) & TIMER_SHORTS_COMPARE0_CLEAR_Msk); 76 | 77 | // // Connect Timer1 compare 0 event to GPIOTE Out 0 task 78 | // NRF_PPI->CH[0].EEP = (uint32_t)&(NRF_TIMER2->EVENTS_COMPARE[0]); 79 | // NRF_PPI->CH[0].TEP = (uint32_t)&(NRF_GPIOTE->TASKS_OUT[0]); 80 | 81 | // // disable PPI channel 0 82 | // NRF_PPI->CHENSET = 1; 83 | 84 | // NRF_GPIOTE->CONFIG[0] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) 85 | // | (AUDIO_PIN << GPIOTE_CONFIG_PSEL_Pos) 86 | // | (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) 87 | // | (GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos); 88 | 89 | NVIC_ClearPendingIRQ(TIMER2_IRQn); 90 | NVIC_EnableIRQ(TIMER2_IRQn); 91 | 92 | NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Set << TIMER_INTENSET_COMPARE0_Pos; 93 | } 94 | 95 | void MicroGamerTones::tone(uint16_t freq, uint16_t dur) 96 | { 97 | stopTimer(); 98 | 99 | inProgmem = false; 100 | tonesStart = tonesIndex = toneSequence; // set to start of sequence array 101 | toneSequence[0] = freq; 102 | toneSequence[1] = dur; 103 | toneSequence[2] = TONES_END; // set end marker 104 | nextTone(); // start playing 105 | } 106 | 107 | void MicroGamerTones::tone(uint16_t freq1, uint16_t dur1, 108 | uint16_t freq2, uint16_t dur2) 109 | { 110 | stopTimer(); 111 | 112 | inProgmem = false; 113 | tonesStart = tonesIndex = toneSequence; // set to start of sequence array 114 | toneSequence[0] = freq1; 115 | toneSequence[1] = dur1; 116 | toneSequence[2] = freq2; 117 | toneSequence[3] = dur2; 118 | toneSequence[4] = TONES_END; // set end marker 119 | nextTone(); // start playing 120 | } 121 | 122 | void MicroGamerTones::tone(uint16_t freq1, uint16_t dur1, 123 | uint16_t freq2, uint16_t dur2, 124 | uint16_t freq3, uint16_t dur3) 125 | { 126 | stopTimer(); 127 | 128 | inProgmem = false; 129 | tonesStart = tonesIndex = toneSequence; // set to start of sequence array 130 | toneSequence[0] = freq1; 131 | toneSequence[1] = dur1; 132 | toneSequence[2] = freq2; 133 | toneSequence[3] = dur2; 134 | toneSequence[4] = freq3; 135 | toneSequence[5] = dur3; 136 | // end marker was set in the constructor and will never change 137 | nextTone(); // start playing 138 | } 139 | 140 | void MicroGamerTones::tones(const uint16_t *tones) 141 | { 142 | stopTimer(); 143 | 144 | inProgmem = true; 145 | tonesStart = tonesIndex = (uint16_t *)tones; // set to start of sequence array 146 | nextTone(); // start playing 147 | } 148 | 149 | void MicroGamerTones::tonesInRAM(uint16_t *tones) 150 | { 151 | stopTimer(); 152 | 153 | inProgmem = false; 154 | tonesStart = tonesIndex = tones; // set to start of sequence array 155 | nextTone(); // start playing 156 | } 157 | 158 | void MicroGamerTones::noTone() 159 | { 160 | stopTimer(); 161 | tonesPlaying = false; 162 | } 163 | 164 | void MicroGamerTones::volumeMode(uint8_t mode) 165 | { 166 | // There's no volume mode on the Micro:Gamer 167 | } 168 | 169 | bool MicroGamerTones::playing() 170 | { 171 | return tonesPlaying; 172 | } 173 | 174 | void MicroGamerTones::nextTone() 175 | { 176 | uint16_t freq; 177 | uint16_t dur; 178 | long Count; 179 | 180 | freq = getNext(); // get tone frequency 181 | 182 | if (freq == TONES_END) { // if freq is actually an "end of sequence" marker 183 | noTone(); // stop playing 184 | return; 185 | } 186 | 187 | tonesPlaying = true; 188 | 189 | if (freq == TONES_REPEAT) { // if frequency is actually a "repeat" marker 190 | tonesIndex = tonesStart; // reset to start of sequence 191 | freq = getNext(); 192 | } 193 | 194 | freq &= ~TONE_HIGH_VOLUME; // strip volume indicator from frequency 195 | 196 | if (freq == 0) { // if tone is silent 197 | toneSilent = true; 198 | } 199 | else { 200 | toneSilent = false; 201 | } 202 | 203 | if (!outputEnabled()) { // if sound has been muted 204 | toneSilent = true; 205 | } 206 | 207 | 208 | if (toneSilent) { 209 | // When there's no tone the timer still needs to run the timer to be able 210 | // to count duration. We choose an arbitrary value for the frequency of 211 | // the timer in that case. 212 | freq = 100; 213 | } 214 | 215 | dur = getNext(); // get tone duration 216 | 217 | if (dur != 0) { 218 | // A right shift is used to divide by 512 for efficiency. 219 | // For durations in milliseconds it should actually be a divide by 500, 220 | // so durations will by shorter by 2.34% of what is specified. 221 | Count = ((long)dur * freq) >> 9; 222 | } 223 | else { 224 | Count = -1; // indicate infinite duration 225 | } 226 | 227 | durationCount = Count; 228 | 229 | NRF_TIMER2->TASKS_CLEAR = 0x1UL; 230 | NRF_TIMER2->CC[0] = (16000000 / (1 << AUDIO_TIMER_PRESCALER)) / (freq * 2) ; 231 | startTimer(); 232 | } 233 | 234 | uint16_t MicroGamerTones::getNext() 235 | { 236 | if (inProgmem) { 237 | return pgm_read_word(tonesIndex++); 238 | } 239 | return *tonesIndex++; 240 | } 241 | 242 | void MicroGamerTones::stopTimer() 243 | { 244 | NRF_TIMER2->TASKS_STOP = 0x1UL; 245 | } 246 | 247 | void MicroGamerTones::startTimer() 248 | { 249 | NRF_TIMER2->TASKS_START = 0x1UL; 250 | } 251 | 252 | extern "C" { 253 | 254 | void TIMER2_IRQHandler(void) 255 | { 256 | NRF_TIMER2->EVENTS_COMPARE[0] = 0; 257 | 258 | NVIC_ClearPendingIRQ(TIMER2_IRQn); 259 | 260 | if (durationCount != 0) { 261 | if (!toneSilent) { 262 | digitalWrite(AUDIO_PIN, !digitalRead(AUDIO_PIN)); 263 | } else { 264 | digitalWrite(AUDIO_PIN, LOW); 265 | } 266 | } else { 267 | digitalWrite(AUDIO_PIN, LOW); 268 | } 269 | 270 | if (durationCount > 0) { 271 | durationCount--; 272 | } 273 | else { 274 | MicroGamerTones::nextTone(); 275 | } 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/MicroGamerTones.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerTones.h 3 | * \brief An Arduino library for playing tones and tone sequences, 4 | * intended for the MicroGamer game system. 5 | */ 6 | 7 | /***************************************************************************** 8 | MicroGamerTones 9 | 10 | An Arduino library to play tones and tone sequences. 11 | 12 | Specifically written for use by the MicroGamer miniature game system 13 | https://www.arduboy.com/ 14 | but could work with other Arduino AVR boards that have 16 bit timer 3 15 | available, by changing the port and bit definintions for the pin(s) 16 | if necessary. 17 | 18 | Copyright (c) 2017 Scott Allen 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | *****************************************************************************/ 38 | 39 | #ifndef MICROGAMER_TONES_H 40 | #define MICROGAMER_TONES_H 41 | 42 | #include 43 | 44 | // ************************************************************ 45 | // ***** Values to use as function parameters in sketches ***** 46 | // ************************************************************ 47 | 48 | /** \brief 49 | * Frequency value for sequence termination. (No duration follows) 50 | */ 51 | #define TONES_END 0x8000 52 | 53 | /** \brief 54 | * Frequency value for sequence repeat. (No duration follows) 55 | */ 56 | #define TONES_REPEAT 0x8001 57 | 58 | 59 | /** \brief 60 | * Add this to the frequency to play a tone at high volume 61 | */ 62 | #define TONE_HIGH_VOLUME 0x8000 63 | 64 | 65 | /** \brief 66 | * `volumeMode()` parameter. Use the volume encoded in each tone's frequency 67 | */ 68 | #define VOLUME_IN_TONE 0 69 | 70 | /** \brief 71 | * `volumeMode()` parameter. Play all tones at normal volume, ignoring 72 | * what's encoded in the frequencies 73 | */ 74 | #define VOLUME_ALWAYS_NORMAL 1 75 | 76 | /** \brief 77 | * `volumeMode()` parameter. Play all tones at high volume, ignoring 78 | * what's encoded in the frequencies 79 | */ 80 | #define VOLUME_ALWAYS_HIGH 2 81 | 82 | // ************************************************************ 83 | 84 | 85 | #ifndef AB_DEVKIT 86 | // ***** SPEAKER ON TWO PINS ***** 87 | // Indicates that each of the speaker leads is attached to a pin, the way 88 | // the MicroGamer is wired. Allows tones of a higher volume to be produced. 89 | // If commented out only one speaker pin will be used. The other speaker 90 | // lead should be attached to ground. 91 | #define TONES_2_SPEAKER_PINS 92 | // ******************************* 93 | 94 | // ***** VOLUME HIGH/NORMAL SUPPORT ***** 95 | // With the speaker placed across two pins, higher volume is produced by 96 | // toggling the second pin to the opposite state of the first pin. 97 | // Normal volume is produced by leaving the second pin low. 98 | // Comment this out for only normal volume support, which will slightly 99 | // reduce the code size. 100 | #define TONES_VOLUME_CONTROL 101 | // ************************************** 102 | 103 | #ifdef TONES_VOLUME_CONTROL 104 | // Must be defined for volume control, so force it if necessary. 105 | #define TONES_2_SPEAKER_PINS 106 | #endif 107 | #endif 108 | 109 | // ***** CONTROL THE TIMER CLOCK PRESCALER **** 110 | // Uncommenting this will switch the timer clock to use no prescaler, 111 | // instead of a divide by 8 prescaler, if the frequency is high enough to 112 | // allow it. This will result in higher frequencies being more accurate at 113 | // the expense of requiring more code. If left commented out, a divide by 8 114 | // prescaler will be used for all frequencies. 115 | //#define TONES_ADJUST_PRESCALER 116 | // ******************************************** 117 | 118 | // This must match the maximum number of tones that can be specified in 119 | // the tone() function. 120 | #define MAX_TONES 3 121 | 122 | #ifndef AB_DEVKIT 123 | // MicroGamer speaker pin 1 = Arduino pin 5 = ATmega32u4 PC6 124 | #define TONE_PIN_PORT PORTC 125 | #define TONE_PIN_DDR DDRC 126 | #define TONE_PIN PORTC6 127 | #define TONE_PIN_MASK _BV(TONE_PIN) 128 | // MicroGamer speaker pin 2 = Arduino pin 13 = ATmega32u4 PC7 129 | #define TONE_PIN2_PORT PORTC 130 | #define TONE_PIN2_DDR DDRC 131 | #define TONE_PIN2 PORTC7 132 | #define TONE_PIN2_MASK _BV(TONE_PIN2) 133 | #else 134 | // DevKit speaker pin 1 = Arduino pin A2 = ATmega32u4 PF5 135 | #define TONE_PIN_PORT PORTF 136 | #define TONE_PIN_DDR DDRF 137 | #define TONE_PIN PORTF5 138 | #define TONE_PIN_MASK _BV(TONE_PIN) 139 | #endif 140 | 141 | // The minimum frequency that can be produced without a clock prescaler. 142 | #define MIN_NO_PRESCALE_FREQ ((uint16_t)(((F_CPU / 2L) + (1L << 16) - 1L) / (1L << 16))) 143 | 144 | // Dummy frequency used to for silent tones (rests). 145 | #define SILENT_FREQ 250 146 | 147 | 148 | /** \brief 149 | * The MicroGamerTones class for generating tones by specifying 150 | * frequency/duration pairs. 151 | */ 152 | class MicroGamerTones 153 | { 154 | public: 155 | /** \brief 156 | * The MicroGamerTones class constructor. 157 | * 158 | * \param outEn A function which returns a boolean value of `true` if sound 159 | * should be played or `false` if sound should be muted. This function will 160 | * be called from the timer interrupt service routine, at the start of each 161 | * tone, so it should be as fast as possible. 162 | */ 163 | MicroGamerTones(bool (*outEn)()); 164 | 165 | /** \brief 166 | * Play a single tone. 167 | * 168 | * \param freq The frequency of the tone, in hertz. 169 | * \param dur The duration to play the tone for, in 1024ths of a 170 | * second (very close to milliseconds). A duration of 0, or if not provided, 171 | * means play forever, or until `noTone()` is called or a new tone or 172 | * sequence is started. 173 | */ 174 | static void tone(uint16_t freq, uint16_t dur = 0); 175 | 176 | /** \brief 177 | * Play two tones in sequence. 178 | * 179 | * \param freq1,freq2 The frequency of the tone in hertz. 180 | * \param dur1,dur2 The duration to play the tone for, in 1024ths of a 181 | * second (very close to milliseconds). 182 | */ 183 | static void tone(uint16_t freq1, uint16_t dur1, 184 | uint16_t freq2, uint16_t dur2); 185 | 186 | /** \brief 187 | * Play three tones in sequence. 188 | * 189 | * \param freq1,freq2,freq3 The frequency of the tone, in hertz. 190 | * \param dur1,dur2,dur3 The duration to play the tone for, in 1024ths of a 191 | * second (very close to milliseconds). 192 | */ 193 | static void tone(uint16_t freq1, uint16_t dur1, 194 | uint16_t freq2, uint16_t dur2, 195 | uint16_t freq3, uint16_t dur3); 196 | 197 | /** \brief 198 | * Play a tone sequence from frequency/duration pairs in a PROGMEM array. 199 | * 200 | * \param tones A pointer to an array of frequency/duration pairs. 201 | * The array must be placed in code space using `PROGMEM`. 202 | * 203 | * \details 204 | * \parblock 205 | * See the `tone()` function for details on the frequency and duration values. 206 | * A frequency of 0 for any tone means silence (a musical rest). 207 | * 208 | * The last element of the array must be `TONES_END` or `TONES_REPEAT`. 209 | * 210 | * Example: 211 | * 212 | * \code 213 | * const uint16_t sound1[] PROGMEM = { 214 | * 220,1000, 0,250, 440,500, 880,2000, 215 | * TONES_END 216 | * }; 217 | * \endcode 218 | * 219 | * \endparblock 220 | */ 221 | static void tones(const uint16_t *tones); 222 | 223 | /** \brief 224 | * Play a tone sequence from frequency/duration pairs in an array in RAM. 225 | * 226 | * \param tones A pointer to an array of frequency/duration pairs. 227 | * The array must be located in RAM. 228 | * 229 | * \see tones() 230 | * 231 | * \details 232 | * \parblock 233 | * See the `tone()` function for details on the frequency and duration values. 234 | * A frequency of 0 for any tone means silence (a musical rest). 235 | * 236 | * The last element of the array must be `TONES_END` or `TONES_REPEAT`. 237 | * 238 | * Example: 239 | * 240 | * \code 241 | * uint16_t sound2[] = { 242 | * 220,1000, 0,250, 440,500, 880,2000, 243 | * TONES_END 244 | * }; 245 | * \endcode 246 | * 247 | * \endparblock 248 | * 249 | * \note Using `tones()`, with the data in PROGMEM, is normally a better 250 | * choice. The only reason to use tonesInRAM() would be if dynamically 251 | * altering the contents of the array is required. 252 | */ 253 | static void tonesInRAM(uint16_t *tones); 254 | 255 | /** \brief 256 | * Stop playing the tone or sequence. 257 | * 258 | * \details 259 | * If a tone or sequence is playing, it will stop. If nothing 260 | * is playing, this function will do nothing. 261 | */ 262 | static void noTone(); 263 | 264 | /** \brief 265 | * Set the volume to always normal, always high, or tone controlled. 266 | * 267 | * \param mode 268 | * \parblock 269 | * One of the following values should be used: 270 | * 271 | * - `VOLUME_IN_TONE` The volume of each tone will be specified in the tone 272 | * itself. 273 | * - `VOLUME_ALWAYS_NORMAL` All tones will play at the normal volume level. 274 | * - `VOLUME_ALWAYS_HIGH` All tones will play at the high volume level. 275 | * 276 | * \endparblock 277 | */ 278 | static void volumeMode(uint8_t mode); 279 | 280 | /** \brief 281 | * Check if a tone or tone sequence is playing. 282 | * 283 | * \return boolean `true` if playing (even if sound is muted). 284 | */ 285 | static bool playing(); 286 | 287 | private: 288 | // Get the next value in the sequence 289 | static uint16_t getNext(); 290 | 291 | static void stopTimer(); 292 | static void startTimer(); 293 | 294 | public: 295 | // Called from ISR so must be public. Should not be called by a program. 296 | static void nextTone(); 297 | }; 298 | 299 | #include "MicroGamerTonesPitches.h" 300 | 301 | #endif 302 | -------------------------------------------------------------------------------- /src/MicroGamerTonesPitches.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MicroGamerTonesPitches.h 3 | * \brief Frequency definitions for standard note pitches. 4 | */ 5 | 6 | // Definitions ending with "H" indicate high volume 7 | 8 | #ifndef MICROGAMER_TONES_PITCHES_H 9 | #define MICROGAMER_TONES_PITCHES_H 10 | 11 | #define NOTE_REST 0 12 | #define NOTE_C0 16 13 | #define NOTE_CS0 17 14 | #define NOTE_D0 18 15 | #define NOTE_DS0 19 16 | #define NOTE_E0 21 17 | #define NOTE_F0 22 18 | #define NOTE_FS0 23 19 | #define NOTE_G0 25 20 | #define NOTE_GS0 26 21 | #define NOTE_A0 28 22 | #define NOTE_AS0 29 23 | #define NOTE_B0 31 24 | #define NOTE_C1 33 25 | #define NOTE_CS1 35 26 | #define NOTE_D1 37 27 | #define NOTE_DS1 39 28 | #define NOTE_E1 41 29 | #define NOTE_F1 44 30 | #define NOTE_FS1 46 31 | #define NOTE_G1 49 32 | #define NOTE_GS1 52 33 | #define NOTE_A1 55 34 | #define NOTE_AS1 58 35 | #define NOTE_B1 62 36 | #define NOTE_C2 65 37 | #define NOTE_CS2 69 38 | #define NOTE_D2 73 39 | #define NOTE_DS2 78 40 | #define NOTE_E2 82 41 | #define NOTE_F2 87 42 | #define NOTE_FS2 93 43 | #define NOTE_G2 98 44 | #define NOTE_GS2 104 45 | #define NOTE_A2 110 46 | #define NOTE_AS2 117 47 | #define NOTE_B2 123 48 | #define NOTE_C3 131 49 | #define NOTE_CS3 139 50 | #define NOTE_D3 147 51 | #define NOTE_DS3 156 52 | #define NOTE_E3 165 53 | #define NOTE_F3 175 54 | #define NOTE_FS3 185 55 | #define NOTE_G3 196 56 | #define NOTE_GS3 208 57 | #define NOTE_A3 220 58 | #define NOTE_AS3 233 59 | #define NOTE_B3 247 60 | #define NOTE_C4 262 61 | #define NOTE_CS4 277 62 | #define NOTE_D4 294 63 | #define NOTE_DS4 311 64 | #define NOTE_E4 330 65 | #define NOTE_F4 349 66 | #define NOTE_FS4 370 67 | #define NOTE_G4 392 68 | #define NOTE_GS4 415 69 | #define NOTE_A4 440 70 | #define NOTE_AS4 466 71 | #define NOTE_B4 494 72 | #define NOTE_C5 523 73 | #define NOTE_CS5 554 74 | #define NOTE_D5 587 75 | #define NOTE_DS5 622 76 | #define NOTE_E5 659 77 | #define NOTE_F5 698 78 | #define NOTE_FS5 740 79 | #define NOTE_G5 784 80 | #define NOTE_GS5 831 81 | #define NOTE_A5 880 82 | #define NOTE_AS5 932 83 | #define NOTE_B5 988 84 | #define NOTE_C6 1047 85 | #define NOTE_CS6 1109 86 | #define NOTE_D6 1175 87 | #define NOTE_DS6 1245 88 | #define NOTE_E6 1319 89 | #define NOTE_F6 1397 90 | #define NOTE_FS6 1480 91 | #define NOTE_G6 1568 92 | #define NOTE_GS6 1661 93 | #define NOTE_A6 1760 94 | #define NOTE_AS6 1865 95 | #define NOTE_B6 1976 96 | #define NOTE_C7 2093 97 | #define NOTE_CS7 2218 98 | #define NOTE_D7 2349 99 | #define NOTE_DS7 2489 100 | #define NOTE_E7 2637 101 | #define NOTE_F7 2794 102 | #define NOTE_FS7 2960 103 | #define NOTE_G7 3136 104 | #define NOTE_GS7 3322 105 | #define NOTE_A7 3520 106 | #define NOTE_AS7 3729 107 | #define NOTE_B7 3951 108 | #define NOTE_C8 4186 109 | #define NOTE_CS8 4435 110 | #define NOTE_D8 4699 111 | #define NOTE_DS8 4978 112 | #define NOTE_E8 5274 113 | #define NOTE_F8 5588 114 | #define NOTE_FS8 5920 115 | #define NOTE_G8 6272 116 | #define NOTE_GS8 6645 117 | #define NOTE_A8 7040 118 | #define NOTE_AS8 7459 119 | #define NOTE_B8 7902 120 | #define NOTE_C9 8372 121 | #define NOTE_CS9 8870 122 | #define NOTE_D9 9397 123 | #define NOTE_DS9 9956 124 | #define NOTE_E9 10548 125 | #define NOTE_F9 11175 126 | #define NOTE_FS9 11840 127 | #define NOTE_G9 12544 128 | #define NOTE_GS9 13290 129 | #define NOTE_A9 14080 130 | #define NOTE_AS9 14917 131 | #define NOTE_B9 15804 132 | 133 | #define NOTE_C0H (NOTE_C0 + TONE_HIGH_VOLUME) 134 | #define NOTE_CS0H (NOTE_CS0 + TONE_HIGH_VOLUME) 135 | #define NOTE_D0H (NOTE_D08 + TONE_HIGH_VOLUME) 136 | #define NOTE_DS0H (NOTE_DS0 + TONE_HIGH_VOLUME) 137 | #define NOTE_E0H (NOTE_E0 + TONE_HIGH_VOLUME) 138 | #define NOTE_F0H (NOTE_F0 + TONE_HIGH_VOLUME) 139 | #define NOTE_FS0H (NOTE_FS0 + TONE_HIGH_VOLUME) 140 | #define NOTE_G0H (NOTE_G0 + TONE_HIGH_VOLUME) 141 | #define NOTE_GS0H (NOTE_GS0 + TONE_HIGH_VOLUME) 142 | #define NOTE_A0H (NOTE_A0 + TONE_HIGH_VOLUME) 143 | #define NOTE_AS0H (NOTE_AS0 + TONE_HIGH_VOLUME) 144 | #define NOTE_B0H (NOTE_B0 + TONE_HIGH_VOLUME) 145 | #define NOTE_C1H (NOTE_C1 + TONE_HIGH_VOLUME) 146 | #define NOTE_CS1H (NOTE_CS1 + TONE_HIGH_VOLUME) 147 | #define NOTE_D1H (NOTE_D1 + TONE_HIGH_VOLUME) 148 | #define NOTE_DS1H (NOTE_DS1 + TONE_HIGH_VOLUME) 149 | #define NOTE_E1H (NOTE_E1 + TONE_HIGH_VOLUME) 150 | #define NOTE_F1H (NOTE_F1 + TONE_HIGH_VOLUME) 151 | #define NOTE_FS1H (NOTE_FS1 + TONE_HIGH_VOLUME) 152 | #define NOTE_G1H (NOTE_G1 + TONE_HIGH_VOLUME) 153 | #define NOTE_GS1H (NOTE_GS1 + TONE_HIGH_VOLUME) 154 | #define NOTE_A1H (NOTE_A1 + TONE_HIGH_VOLUME) 155 | #define NOTE_AS1H (NOTE_AS1 + TONE_HIGH_VOLUME) 156 | #define NOTE_B1H (NOTE_B1 + TONE_HIGH_VOLUME) 157 | #define NOTE_C2H (NOTE_C2 + TONE_HIGH_VOLUME) 158 | #define NOTE_CS2H (NOTE_CS2 + TONE_HIGH_VOLUME) 159 | #define NOTE_D2H (NOTE_D2 + TONE_HIGH_VOLUME) 160 | #define NOTE_DS2H (NOTE_DS2 + TONE_HIGH_VOLUME) 161 | #define NOTE_E2H (NOTE_E2 + TONE_HIGH_VOLUME) 162 | #define NOTE_F2H (NOTE_F2 + TONE_HIGH_VOLUME) 163 | #define NOTE_FS2H (NOTE_FS2 + TONE_HIGH_VOLUME) 164 | #define NOTE_G2H (NOTE_G2 + TONE_HIGH_VOLUME) 165 | #define NOTE_GS2H (NOTE_GS2 + TONE_HIGH_VOLUME) 166 | #define NOTE_A2H (NOTE_A2 + TONE_HIGH_VOLUME) 167 | #define NOTE_AS2H (NOTE_AS2 + TONE_HIGH_VOLUME) 168 | #define NOTE_B2H (NOTE_B2 + TONE_HIGH_VOLUME) 169 | #define NOTE_C3H (NOTE_C3 + TONE_HIGH_VOLUME) 170 | #define NOTE_CS3H (NOTE_CS3 + TONE_HIGH_VOLUME) 171 | #define NOTE_D3H (NOTE_D3 + TONE_HIGH_VOLUME) 172 | #define NOTE_DS3H (NOTE_DS3 + TONE_HIGH_VOLUME) 173 | #define NOTE_E3H (NOTE_E3 + TONE_HIGH_VOLUME) 174 | #define NOTE_F3H (NOTE_F3 + TONE_HIGH_VOLUME) 175 | #define NOTE_FS3H (NOTE_F3 + TONE_HIGH_VOLUME) 176 | #define NOTE_G3H (NOTE_G3 + TONE_HIGH_VOLUME) 177 | #define NOTE_GS3H (NOTE_GS3 + TONE_HIGH_VOLUME) 178 | #define NOTE_A3H (NOTE_A3 + TONE_HIGH_VOLUME) 179 | #define NOTE_AS3H (NOTE_AS3 + TONE_HIGH_VOLUME) 180 | #define NOTE_B3H (NOTE_B3 + TONE_HIGH_VOLUME) 181 | #define NOTE_C4H (NOTE_C4 + TONE_HIGH_VOLUME) 182 | #define NOTE_CS4H (NOTE_CS4 + TONE_HIGH_VOLUME) 183 | #define NOTE_D4H (NOTE_D4 + TONE_HIGH_VOLUME) 184 | #define NOTE_DS4H (NOTE_DS4 + TONE_HIGH_VOLUME) 185 | #define NOTE_E4H (NOTE_E4 + TONE_HIGH_VOLUME) 186 | #define NOTE_F4H (NOTE_F4 + TONE_HIGH_VOLUME) 187 | #define NOTE_FS4H (NOTE_FS4 + TONE_HIGH_VOLUME) 188 | #define NOTE_G4H (NOTE_G4 + TONE_HIGH_VOLUME) 189 | #define NOTE_GS4H (NOTE_GS4 + TONE_HIGH_VOLUME) 190 | #define NOTE_A4H (NOTE_A4 + TONE_HIGH_VOLUME) 191 | #define NOTE_AS4H (NOTE_AS4 + TONE_HIGH_VOLUME) 192 | #define NOTE_B4H (NOTE_B4 + TONE_HIGH_VOLUME) 193 | #define NOTE_C5H (NOTE_C5 + TONE_HIGH_VOLUME) 194 | #define NOTE_CS5H (NOTE_CS5 + TONE_HIGH_VOLUME) 195 | #define NOTE_D5H (NOTE_D5 + TONE_HIGH_VOLUME) 196 | #define NOTE_DS5H (NOTE_DS5 + TONE_HIGH_VOLUME) 197 | #define NOTE_E5H (NOTE_E5 + TONE_HIGH_VOLUME) 198 | #define NOTE_F5H (NOTE_F5 + TONE_HIGH_VOLUME) 199 | #define NOTE_FS5H (NOTE_FS5 + TONE_HIGH_VOLUME) 200 | #define NOTE_G5H (NOTE_G5 + TONE_HIGH_VOLUME) 201 | #define NOTE_GS5H (NOTE_GS5 + TONE_HIGH_VOLUME) 202 | #define NOTE_A5H (NOTE_A5 + TONE_HIGH_VOLUME) 203 | #define NOTE_AS5H (NOTE_AS5 + TONE_HIGH_VOLUME) 204 | #define NOTE_B5H (NOTE_B5 + TONE_HIGH_VOLUME) 205 | #define NOTE_C6H (NOTE_C6 + TONE_HIGH_VOLUME) 206 | #define NOTE_CS6H (NOTE_CS6 + TONE_HIGH_VOLUME) 207 | #define NOTE_D6H (NOTE_D6 + TONE_HIGH_VOLUME) 208 | #define NOTE_DS6H (NOTE_DS6 + TONE_HIGH_VOLUME) 209 | #define NOTE_E6H (NOTE_E6 + TONE_HIGH_VOLUME) 210 | #define NOTE_F6H (NOTE_F6 + TONE_HIGH_VOLUME) 211 | #define NOTE_FS6H (NOTE_FS6 + TONE_HIGH_VOLUME) 212 | #define NOTE_G6H (NOTE_G6 + TONE_HIGH_VOLUME) 213 | #define NOTE_GS6H (NOTE_GS6 + TONE_HIGH_VOLUME) 214 | #define NOTE_A6H (NOTE_A6 + TONE_HIGH_VOLUME) 215 | #define NOTE_AS6H (NOTE_AS6 + TONE_HIGH_VOLUME) 216 | #define NOTE_B6H (NOTE_B6 + TONE_HIGH_VOLUME) 217 | #define NOTE_C7H (NOTE_C7 + TONE_HIGH_VOLUME) 218 | #define NOTE_CS7H (NOTE_CS7 + TONE_HIGH_VOLUME) 219 | #define NOTE_D7H (NOTE_D7 + TONE_HIGH_VOLUME) 220 | #define NOTE_DS7H (NOTE_DS7 + TONE_HIGH_VOLUME) 221 | #define NOTE_E7H (NOTE_E7 + TONE_HIGH_VOLUME) 222 | #define NOTE_F7H (NOTE_F7 + TONE_HIGH_VOLUME) 223 | #define NOTE_FS7H (NOTE_FS7 + TONE_HIGH_VOLUME) 224 | #define NOTE_G7H (NOTE_G7 + TONE_HIGH_VOLUME) 225 | #define NOTE_GS7H (NOTE_GS7 + TONE_HIGH_VOLUME) 226 | #define NOTE_A7H (NOTE_A7 + TONE_HIGH_VOLUME) 227 | #define NOTE_AS7H (NOTE_AS7 + TONE_HIGH_VOLUME) 228 | #define NOTE_B7H (NOTE_B7 + TONE_HIGH_VOLUME) 229 | #define NOTE_C8H (NOTE_C8 + TONE_HIGH_VOLUME) 230 | #define NOTE_CS8H (NOTE_CS8 + TONE_HIGH_VOLUME) 231 | #define NOTE_D8H (NOTE_D8 + TONE_HIGH_VOLUME) 232 | #define NOTE_DS8H (NOTE_DS8 + TONE_HIGH_VOLUME) 233 | #define NOTE_E8H (NOTE_E8 + TONE_HIGH_VOLUME) 234 | #define NOTE_F8H (NOTE_F8 + TONE_HIGH_VOLUME) 235 | #define NOTE_FS8H (NOTE_FS8 + TONE_HIGH_VOLUME) 236 | #define NOTE_G8H (NOTE_G8 + TONE_HIGH_VOLUME) 237 | #define NOTE_GS8H (NOTE_GS8 + TONE_HIGH_VOLUME) 238 | #define NOTE_A8H (NOTE_A8 + TONE_HIGH_VOLUME) 239 | #define NOTE_AS8H (NOTE_AS8 + TONE_HIGH_VOLUME) 240 | #define NOTE_B8H (NOTE_B8 + TONE_HIGH_VOLUME) 241 | #define NOTE_C9H (NOTE_C9 + TONE_HIGH_VOLUME) 242 | #define NOTE_CS9H (NOTE_CS9 + TONE_HIGH_VOLUME) 243 | #define NOTE_D9H (NOTE_D9 + TONE_HIGH_VOLUME) 244 | #define NOTE_DS9H (NOTE_DS9 + TONE_HIGH_VOLUME) 245 | #define NOTE_E9H (NOTE_E9 + TONE_HIGH_VOLUME) 246 | #define NOTE_F9H (NOTE_F9 + TONE_HIGH_VOLUME) 247 | #define NOTE_FS9H (NOTE_FS9 + TONE_HIGH_VOLUME) 248 | #define NOTE_G9H (NOTE_G9 + TONE_HIGH_VOLUME) 249 | #define NOTE_GS9H (NOTE_GS9 + TONE_HIGH_VOLUME) 250 | #define NOTE_A9H (NOTE_A9 + TONE_HIGH_VOLUME) 251 | #define NOTE_AS9H (NOTE_AS9 + TONE_HIGH_VOLUME) 252 | #define NOTE_B9H (NOTE_B9 + TONE_HIGH_VOLUME) 253 | 254 | #endif 255 | -------------------------------------------------------------------------------- /src/Sprites.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Sprites.cpp 3 | * \brief 4 | * A class for drawing animated sprites from image and mask bitmaps. 5 | */ 6 | 7 | #include "Sprites.h" 8 | 9 | void Sprites::drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap, 10 | const uint8_t *mask, uint8_t frame, uint8_t mask_frame) 11 | { 12 | draw(x, y, bitmap, frame, mask, mask_frame, SPRITE_MASKED); 13 | } 14 | 15 | void Sprites::drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame) 16 | { 17 | draw(x, y, bitmap, frame, NULL, 0, SPRITE_OVERWRITE); 18 | } 19 | 20 | void Sprites::drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame) 21 | { 22 | draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK_ERASE); 23 | } 24 | 25 | void Sprites::drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame) 26 | { 27 | draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK); 28 | } 29 | 30 | void Sprites::drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame) 31 | { 32 | draw(x, y, bitmap, frame, NULL, 0, SPRITE_PLUS_MASK); 33 | } 34 | 35 | 36 | //common functions 37 | void Sprites::draw(int16_t x, int16_t y, 38 | const uint8_t *bitmap, uint8_t frame, 39 | const uint8_t *mask, uint8_t sprite_frame, 40 | uint8_t drawMode) 41 | { 42 | unsigned int frame_offset; 43 | 44 | if (bitmap == NULL) 45 | return; 46 | 47 | uint8_t width = pgm_read_byte(bitmap); 48 | uint8_t height = pgm_read_byte(++bitmap); 49 | bitmap++; 50 | if (frame > 0 || sprite_frame > 0) { 51 | frame_offset = (width * ( height / 8 + ( height % 8 == 0 ? 0 : 1))); 52 | // sprite plus mask uses twice as much space for each frame 53 | if (drawMode == SPRITE_PLUS_MASK) { 54 | frame_offset *= 2; 55 | } else if (mask != NULL) { 56 | mask += sprite_frame * frame_offset; 57 | } 58 | bitmap += frame * frame_offset; 59 | } 60 | 61 | // if we're detecting the draw mode then base it on whether a mask 62 | // was passed as a separate object 63 | if (drawMode == SPRITE_AUTO_MODE) { 64 | drawMode = mask == NULL ? SPRITE_UNMASKED : SPRITE_MASKED; 65 | } 66 | 67 | drawBitmap(x, y, bitmap, mask, width, height, drawMode); 68 | } 69 | 70 | void Sprites::drawBitmap(int16_t x, int16_t y, 71 | const uint8_t *bitmap, const uint8_t *mask, 72 | uint8_t w, uint8_t h, uint8_t draw_mode) 73 | { 74 | // no need to draw at all of we're offscreen 75 | if (x + w <= 0 || x > WIDTH - 1 || y + h <= 0 || y > HEIGHT - 1) 76 | return; 77 | 78 | if (bitmap == NULL) 79 | return; 80 | 81 | // xOffset technically doesn't need to be 16 bit but the math operations 82 | // are measurably faster if it is 83 | uint16_t xOffset, ofs; 84 | int8_t yOffset = abs(y) % 8; 85 | int8_t sRow = y / 8; 86 | uint8_t loop_h, start_h, rendered_width; 87 | 88 | if (y < 0 && yOffset > 0) { 89 | sRow--; 90 | yOffset = 8 - yOffset; 91 | } 92 | 93 | // if the left side of the render is offscreen skip those loops 94 | if (x < 0) { 95 | xOffset = abs(x); 96 | } else { 97 | xOffset = 0; 98 | } 99 | 100 | // if the right side of the render is offscreen skip those loops 101 | if (x + w > WIDTH - 1) { 102 | rendered_width = ((WIDTH - x) - xOffset); 103 | } else { 104 | rendered_width = (w - xOffset); 105 | } 106 | 107 | // if the top side of the render is offscreen skip those loops 108 | if (sRow < -1) { 109 | start_h = abs(sRow) - 1; 110 | } else { 111 | start_h = 0; 112 | } 113 | 114 | loop_h = h / 8 + (h % 8 > 0 ? 1 : 0); // divide, then round up 115 | 116 | // if (sRow + loop_h - 1 > (HEIGHT/8)-1) 117 | if (sRow + loop_h > (HEIGHT / 8)) { 118 | loop_h = (HEIGHT / 8) - sRow; 119 | } 120 | 121 | // prepare variables for loops later so we can compare with 0 122 | // instead of comparing two variables 123 | loop_h -= start_h; 124 | 125 | sRow += start_h; 126 | ofs = (sRow * WIDTH) + x + xOffset; 127 | uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset; 128 | uint8_t data; 129 | 130 | uint8_t mul_amt = 1 << yOffset; 131 | uint16_t mask_data; 132 | uint16_t bitmap_data; 133 | 134 | switch (draw_mode) { 135 | case SPRITE_UNMASKED: 136 | // we only want to mask the 8 bits of our own sprite, so we can 137 | // calculate the mask before the start of the loop 138 | mask_data = ~(0xFF * mul_amt); 139 | // really if yOffset = 0 you have a faster case here that could be 140 | // optimized 141 | for (uint8_t a = 0; a < loop_h; a++) { 142 | for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { 143 | bitmap_data = pgm_read_byte(bofs) * mul_amt; 144 | 145 | if (sRow >= 0) { 146 | data = MicroGamerBase::sBuffer[ofs]; 147 | data &= (uint8_t)(mask_data); 148 | data |= (uint8_t)(bitmap_data); 149 | MicroGamerBase::sBuffer[ofs] = data; 150 | } 151 | if (yOffset != 0 && sRow < 7) { 152 | data = MicroGamerBase::sBuffer[ofs + WIDTH]; 153 | data &= (*((unsigned char *) (&mask_data) + 1)); 154 | data |= (*((unsigned char *) (&bitmap_data) + 1)); 155 | MicroGamerBase::sBuffer[ofs + WIDTH] = data; 156 | } 157 | ofs++; 158 | bofs++; 159 | } 160 | sRow++; 161 | bofs += w - rendered_width; 162 | ofs += WIDTH - rendered_width; 163 | } 164 | break; 165 | 166 | case SPRITE_IS_MASK: 167 | for (uint8_t a = 0; a < loop_h; a++) { 168 | for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { 169 | bitmap_data = pgm_read_byte(bofs) * mul_amt; 170 | if (sRow >= 0) { 171 | MicroGamerBase::sBuffer[ofs] |= (uint8_t)(bitmap_data); 172 | } 173 | if (yOffset != 0 && sRow < 7) { 174 | MicroGamerBase::sBuffer[ofs + WIDTH] |= (*((unsigned char *) (&bitmap_data) + 1)); 175 | } 176 | ofs++; 177 | bofs++; 178 | } 179 | sRow++; 180 | bofs += w - rendered_width; 181 | ofs += WIDTH - rendered_width; 182 | } 183 | break; 184 | 185 | case SPRITE_IS_MASK_ERASE: 186 | for (uint8_t a = 0; a < loop_h; a++) { 187 | for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { 188 | bitmap_data = pgm_read_byte(bofs) * mul_amt; 189 | if (sRow >= 0) { 190 | MicroGamerBase::sBuffer[ofs] &= ~(uint8_t)(bitmap_data); 191 | } 192 | if (yOffset != 0 && sRow < 7) { 193 | MicroGamerBase::sBuffer[ofs + WIDTH] &= ~(*((unsigned char *) (&bitmap_data) + 1)); 194 | } 195 | ofs++; 196 | bofs++; 197 | } 198 | sRow++; 199 | bofs += w - rendered_width; 200 | ofs += WIDTH - rendered_width; 201 | } 202 | break; 203 | 204 | case SPRITE_MASKED: 205 | uint8_t *mask_ofs; 206 | mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset; 207 | for (uint8_t a = 0; a < loop_h; a++) { 208 | for (uint8_t iCol = 0; iCol < rendered_width; iCol++) { 209 | // NOTE: you might think in the yOffset==0 case that this results 210 | // in more effort, but in all my testing the compiler was forcing 211 | // 16-bit math to happen here anyways, so this isn't actually 212 | // compiling to more code than it otherwise would. If the offset 213 | // is 0 the high part of the word will just never be used. 214 | 215 | // load data and bit shift 216 | // mask needs to be bit flipped 217 | mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt); 218 | bitmap_data = pgm_read_byte(bofs) * mul_amt; 219 | 220 | if (sRow >= 0) { 221 | data = MicroGamerBase::sBuffer[ofs]; 222 | data &= (uint8_t)(mask_data); 223 | data |= (uint8_t)(bitmap_data); 224 | MicroGamerBase::sBuffer[ofs] = data; 225 | } 226 | if (yOffset != 0 && sRow < 7) { 227 | data = MicroGamerBase::sBuffer[ofs + WIDTH]; 228 | data &= (*((unsigned char *) (&mask_data) + 1)); 229 | data |= (*((unsigned char *) (&bitmap_data) + 1)); 230 | MicroGamerBase::sBuffer[ofs + WIDTH] = data; 231 | } 232 | ofs++; 233 | mask_ofs++; 234 | bofs++; 235 | } 236 | sRow++; 237 | bofs += w - rendered_width; 238 | mask_ofs += w - rendered_width; 239 | ofs += WIDTH - rendered_width; 240 | } 241 | break; 242 | 243 | 244 | case SPRITE_PLUS_MASK: 245 | // *2 because we use double the bits (mask + bitmap) 246 | bofs = (uint8_t *)(bitmap + ((start_h * w) + xOffset) * 2); 247 | 248 | uint8_t xi = rendered_width; // used for x loop below 249 | uint8_t yi = loop_h; // used for y loop below 250 | 251 | // asm volatile( 252 | // "push r28\n" // save Y 253 | // "push r29\n" 254 | // "movw r28, %[buffer_page2_ofs]\n" // Y = buffer page 2 offset 255 | // "loop_y:\n" 256 | // "loop_x:\n" 257 | // // load bitmap and mask data 258 | // "lpm %A[bitmap_data], Z+\n" 259 | // "lpm %A[mask_data], Z+\n" 260 | 261 | // // shift mask and buffer data 262 | // "tst %[yOffset]\n" 263 | // "breq skip_shifting\n" 264 | // "mul %A[bitmap_data], %[mul_amt]\n" 265 | // "movw %[bitmap_data], r0\n" 266 | // "mul %A[mask_data], %[mul_amt]\n" 267 | // "movw %[mask_data], r0\n" 268 | 269 | 270 | // // SECOND PAGE 271 | // // if yOffset != 0 && sRow < 7 272 | // "cpi %[sRow], 7\n" 273 | // "brge end_second_page\n" 274 | // // then 275 | // "ld %[data], Y\n" 276 | // "com %B[mask_data]\n" // invert high byte of mask 277 | // "and %[data], %B[mask_data]\n" 278 | // "or %[data], %B[bitmap_data]\n" 279 | // // update buffer, increment 280 | // "st Y+, %[data]\n" 281 | 282 | // "end_second_page:\n" 283 | // "skip_shifting:\n" 284 | 285 | 286 | // // FIRST PAGE 287 | // // if sRow >= 0 288 | // "tst %[sRow]\n" 289 | // "brmi skip_first_page\n" 290 | // "ld %[data], %a[buffer_ofs]\n" 291 | // // then 292 | // "com %A[mask_data]\n" 293 | // "and %[data], %A[mask_data]\n" 294 | // "or %[data], %A[bitmap_data]\n" 295 | // // update buffer, increment 296 | // "st %a[buffer_ofs]+, %[data]\n" 297 | // "jmp end_first_page\n" 298 | 299 | // "skip_first_page:\n" 300 | // // since no ST Z+ when skipped we need to do this manually 301 | // "adiw %[buffer_ofs], 1\n" 302 | 303 | // "end_first_page:\n" 304 | 305 | // // "x_loop_next:\n" 306 | // "dec %[xi]\n" 307 | // "brne loop_x\n" 308 | 309 | // // increment y 310 | // "next_loop_y:\n" 311 | // "dec %[yi]\n" 312 | // "breq finished\n" 313 | // "mov %[xi], %[x_count]\n" // reset x counter 314 | // // sRow++; 315 | // "inc %[sRow]\n" 316 | // "clr __zero_reg__\n" 317 | // // sprite_ofs += (w - rendered_width) * 2; 318 | // "add %A[sprite_ofs], %A[sprite_ofs_jump]\n" 319 | // "adc %B[sprite_ofs], __zero_reg__\n" 320 | // // buffer_ofs += WIDTH - rendered_width; 321 | // "add %A[buffer_ofs], %A[buffer_ofs_jump]\n" 322 | // "adc %B[buffer_ofs], __zero_reg__\n" 323 | // // buffer_ofs_page_2 += WIDTH - rendered_width; 324 | // "add r28, %A[buffer_ofs_jump]\n" 325 | // "adc r29, __zero_reg__\n" 326 | 327 | // "rjmp loop_y\n" 328 | // "finished:\n" 329 | // // put the Y register back in place 330 | // "pop r29\n" 331 | // "pop r28\n" 332 | // "clr __zero_reg__\n" // just in case 333 | // : [xi] "+&r" (xi), 334 | // [yi] "+&r" (yi), 335 | // [sRow] "+&d" (sRow), // CPI requires an upper register 336 | // [data] "=&r" (data), 337 | // [mask_data] "=&r" (mask_data), 338 | // [bitmap_data] "=&r" (bitmap_data) 339 | // : 340 | // [x_count] "r" (rendered_width), 341 | // [y_count] "r" (loop_h), 342 | // [sprite_ofs] "z" (bofs), 343 | // [buffer_ofs] "x" (MicroGamerBase::sBuffer+ofs), 344 | // [buffer_page2_ofs] "r" (MicroGamerBase::sBuffer+ofs+WIDTH), // Y pointer 345 | // [buffer_ofs_jump] "r" (WIDTH-rendered_width), 346 | // [sprite_ofs_jump] "r" ((w-rendered_width)*2), 347 | // [yOffset] "r" (yOffset), 348 | // [mul_amt] "r" (mul_amt) 349 | // // declaring an extra high register clobber here for some reason 350 | // // prevents a compile error for some sketches: 351 | // // can't find a register in class 'LD_REGS' while reloading 'asm' 352 | // : "r24" 353 | // ); 354 | break; 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/Sprites.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Sprites.h 3 | * \brief 4 | * A class for drawing animated sprites from image and mask bitmaps. 5 | */ 6 | 7 | #ifndef Sprites_h 8 | #define Sprites_h 9 | 10 | #include "MicroGamer.h" 11 | 12 | #define SPRITE_MASKED 1 13 | #define SPRITE_UNMASKED 2 14 | #define SPRITE_OVERWRITE 2 15 | #define SPRITE_PLUS_MASK 3 16 | #define SPRITE_IS_MASK 250 17 | #define SPRITE_IS_MASK_ERASE 251 18 | #define SPRITE_AUTO_MODE 255 19 | 20 | /** \brief 21 | * A class for drawing animated sprites from image and mask bitmaps. 22 | * 23 | * \details 24 | * The functions in this class will draw to the screen buffer an image 25 | * contained in an array located in program memory. A mask can also be 26 | * specified or implied, which dictates how existing pixels in the buffer, 27 | * within the image boundaries, will be affected. 28 | * 29 | * A sprite or mask array contains one or more "frames". Each frame is intended 30 | * to show whatever the sprite represents in a different position, such as the 31 | * various poses for a running or jumping character. By specifying a different 32 | * frame each time the sprite is drawn, it can be animated. 33 | * 34 | * Each image array begins with values for the width and height of the sprite, 35 | * in pixels. The width can be any value. The height must be a multiple of 36 | * 8 pixels, but with proper masking, a sprite of any height can be created. 37 | * 38 | * For a separate mask array, as is used with `drawExternalMask()`, the width 39 | * and height are not included but must contain data of the same dimensions 40 | * as the corresponding image array. 41 | * 42 | * Following the width and height values for an image array, or the from the 43 | * beginning of a separate mask array, the array contains the image and/or 44 | * mask data for each frame. Each byte represents a vertical column of 8 pixels 45 | * with the least significant bit (bit 0) at the top. The bytes are drawn as 46 | * 8 pixel high rows from left to right, top to bottom. When the end of a row 47 | * is reached, as specified by the width value, the next byte in the array will 48 | * be the start of the next row. 49 | * 50 | * Data for each frame after the first one immediately follows the previous 51 | * frame. Frame numbers start at 0. 52 | */ 53 | class Sprites 54 | { 55 | public: 56 | /** \brief 57 | * Draw a sprite using a separate image and mask array. 58 | * 59 | * \param x,y The coordinates of the top left pixel location. 60 | * \param bitmap A pointer to the array containing the image frames. 61 | * \param mask A pointer to the array containing the mask frames. 62 | * \param frame The frame number of the image to draw. 63 | * \param mask_frame The frame number for the mask to use (can be different 64 | * from the image frame number). 65 | * 66 | * \details 67 | * An array containing the image frames, and another array containing 68 | * corresponding mask frames, are used to draw a sprite. 69 | * 70 | * Bits set to 1 in the mask indicate that the pixel will be set to the 71 | * value of the corresponding image bit. Bits set to 0 in the mask will be 72 | * left unchanged. 73 | * 74 | * image mask before after 75 | * 76 | * ..... .OOO. ..... ..... 77 | * ..O.. OOOOO ..... ..O.. 78 | * OO.OO OO.OO ..... OO.OO 79 | * ..O.. OOOOO ..... ..O.. 80 | * ..... .OOO. ..... ..... 81 | * 82 | * image mask before after 83 | * 84 | * ..... .OOO. OOOOO O...O 85 | * ..O.. OOOOO OOOOO ..O.. 86 | * OO.OO OOOOO OOOOO OO.OO 87 | * ..O.. OOOOO OOOOO ..O.. 88 | * ..... .OOO. OOOOO O...O 89 | */ 90 | static void drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap, 91 | const uint8_t *mask, uint8_t frame, uint8_t mask_frame); 92 | 93 | /** \brief 94 | * Draw a sprite using an array containing both image and mask values. 95 | * 96 | * \param x,y The coordinates of the top left pixel location. 97 | * \param bitmap A pointer to the array containing the image/mask frames. 98 | * \param frame The frame number of the image to draw. 99 | * 100 | * \details 101 | * An array containing combined image and mask data is used to draw a 102 | * sprite. Bytes are given in pairs with the first byte representing the 103 | * image pixels and the second byte specifying the corresponding mask. 104 | * The width given in the array still specifies the image width, so each 105 | * row of image and mask bytes will be twice the width value. 106 | * 107 | * Bits set to 1 in the mask indicate that the pixel will be set to the 108 | * value of the corresponding image bit. Bits set to 0 in the mask will be 109 | * left unchanged. 110 | * 111 | * image mask before after 112 | * 113 | * ..... .OOO. ..... ..... 114 | * ..O.. OOOOO ..... ..O.. 115 | * OO.OO OO.OO ..... OO.OO 116 | * ..O.. OOOOO ..... ..O.. 117 | * ..... .OOO. ..... ..... 118 | * 119 | * image mask before after 120 | * 121 | * ..... .OOO. OOOOO O...O 122 | * ..O.. OOOOO OOOOO ..O.. 123 | * OO.OO OOOOO OOOOO OO.OO 124 | * ..O.. OOOOO OOOOO ..O.. 125 | * ..... .OOO. OOOOO O...O 126 | */ 127 | static void drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame); 128 | 129 | /** \brief 130 | * Draw a sprite by replacing the existing content completely. 131 | * 132 | * \param x,y The coordinates of the top left pixel location. 133 | * \param bitmap A pointer to the array containing the image frames. 134 | * \param frame The frame number of the image to draw. 135 | * 136 | * \details 137 | * A sprite is drawn by overwriting the pixels in the buffer with the data 138 | * from the specified frame in the array. No masking is done. A bit set 139 | * to 1 in the frame will set the pixel to 1 in the buffer, and a 0 in the 140 | * array will set a 0 in the buffer. 141 | * 142 | * image before after 143 | * 144 | * ..... ..... ..... 145 | * ..O.. ..... ..O.. 146 | * OO.OO ..... OO.OO 147 | * ..O.. ..... ..O.. 148 | * ..... ..... ..... 149 | * 150 | * image before after 151 | * 152 | * ..... OOOOO ..... 153 | * ..O.. OOOOO ..O.. 154 | * OO.OO OOOOO OO.OO 155 | * ..O.. OOOOO ..O.. 156 | * ..... OOOOO ..... 157 | */ 158 | static void drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame); 159 | 160 | /** \brief 161 | * "Erase" a sprite. 162 | * 163 | * \param x,y The coordinates of the top left pixel location. 164 | * \param bitmap A pointer to the array containing the image frames. 165 | * \param frame The frame number of the image to erase. 166 | * 167 | * \details 168 | * The data from the specified frame in the array is used to erase a 169 | * sprite. To "erase" a sprite, bits set to 1 in the frame will set the 170 | * corresponding pixel in the buffer to 0. Frame bits set to 0 will remain 171 | * unchanged in the buffer. 172 | * 173 | * image before after 174 | * 175 | * ..... ..... ..... 176 | * ..O.. ..... ..... 177 | * OO.OO ..... ..... 178 | * ..O.. ..... ..... 179 | * ..... ..... ..... 180 | * 181 | * image before after 182 | * 183 | * ..... OOOOO OOOOO 184 | * ..O.. OOOOO OO.OO 185 | * OO.OO OOOOO ..O.. 186 | * ..O.. OOOOO OO.OO 187 | * ..... OOOOO OOOOO 188 | */ 189 | static void drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame); 190 | 191 | /** \brief 192 | * Draw a sprite using only the bits set to 1. 193 | * 194 | * \param x,y The coordinates of the top left pixel location. 195 | * \param bitmap A pointer to the array containing the image frames. 196 | * \param frame The frame number of the image to draw. 197 | * 198 | * \details 199 | * Bits set to 1 in the frame will be used to draw the sprite by setting 200 | * the corresponding pixel in the buffer to 1. Bits set to 0 in the frame 201 | * will remain unchanged in the buffer. 202 | * 203 | * image before after 204 | * 205 | * ..... ..... ..... 206 | * ..O.. ..... ..O.. 207 | * OO.OO ..... OO.OO 208 | * ..O.. ..... ..O.. 209 | * ..... ..... ..... 210 | * 211 | * image before after 212 | * 213 | * ..... OOOOO OOOOO (no change because all pixels were 214 | * ..O.. OOOOO OOOOO already white) 215 | * OO.OO OOOOO OOOOO 216 | * ..O.. OOOOO OOOOO 217 | * ..... OOOOO OOOOO 218 | */ 219 | static void drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame); 220 | 221 | // Master function. Needs to be abstracted into separate function for 222 | // every render type. 223 | // (Not officially part of the API) 224 | static void draw(int16_t x, int16_t y, 225 | const uint8_t *bitmap, uint8_t frame, 226 | const uint8_t *mask, uint8_t sprite_frame, 227 | uint8_t drawMode); 228 | 229 | // (Not officially part of the API) 230 | static void drawBitmap(int16_t x, int16_t y, 231 | const uint8_t *bitmap, const uint8_t *mask, 232 | uint8_t w, uint8_t h, uint8_t draw_mode); 233 | }; 234 | 235 | #endif 236 | -------------------------------------------------------------------------------- /src/ab_logo.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ab_logo.c 3 | * \brief 4 | * The ARDUBOY logo bitmap. 5 | */ 6 | 7 | #include 8 | 9 | #ifndef ARDUBOY_LOGO_CREATED 10 | #define ARDUBOY_LOGO_CREATED 11 | 12 | // arduboy_logo.png 13 | // drawBitmap() format 14 | // 88x16 15 | const uint8_t arduboy_logo[] PROGMEM = { 16 | 0xF0, 0xF8, 0x9C, 0x8E, 0x87, 0x83, 0x87, 0x8E, 0x9C, 0xF8, 17 | 0xF0, 0x00, 0x00, 0xFE, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 18 | 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xFE, 0xFF, 0x03, 0x03, 19 | 0x03, 0x03, 0x03, 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xFF, 20 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 21 | 0x00, 0x00, 0xFE, 0xFF, 0x83, 0x83, 0x83, 0x83, 0x83, 0xC7, 22 | 0xEE, 0x7C, 0x38, 0x00, 0x00, 0xF8, 0xFC, 0x0E, 0x07, 0x03, 23 | 0x03, 0x03, 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0x3F, 0x7F, 24 | 0xE0, 0xC0, 0x80, 0x80, 0xC0, 0xE0, 0x7F, 0x3F, 0xFF, 0xFF, 25 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xFF, 0x00, 26 | 0x00, 0xFF, 0xFF, 0x0C, 0x0C, 0x0C, 0x0C, 0x1C, 0x3E, 0x77, 27 | 0xE3, 0xC1, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 28 | 0xC0, 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 0x1F, 0x3F, 0x70, 29 | 0xE0, 0xC0, 0xC0, 0xC0, 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 30 | 0x7F, 0xFF, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xE3, 0x77, 0x3E, 31 | 0x1C, 0x00, 0x00, 0x1F, 0x3F, 0x70, 0xE0, 0xC0, 0xC0, 0xC0, 32 | 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 33 | 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00 34 | }; 35 | 36 | // arduboy_logo.png 37 | // drawCompressed() format 38 | // 88x16 39 | const uint8_t arduboy_logo_compressed[] PROGMEM = { 40 | 0x57, 0x0F, 0x9C, 0x53, 0x72, 0x75, 0x29, 0xE5, 0x9C, 0x92, 41 | 0xCE, 0x95, 0x52, 0xAD, 0x4E, 0x49, 0xE7, 0x08, 0x09, 0xED, 42 | 0x76, 0xBB, 0xDD, 0x2A, 0xAB, 0xAC, 0x55, 0x92, 0x90, 0xD0, 43 | 0x6E, 0xB7, 0xDB, 0xAD, 0xB2, 0xCA, 0x5A, 0x25, 0xF9, 0xF8, 44 | 0xF0, 0xC6, 0x47, 0x48, 0x28, 0x95, 0x54, 0x52, 0x49, 0x25, 45 | 0x9D, 0x3A, 0x95, 0x5A, 0x3A, 0x45, 0x2A, 0xB7, 0x29, 0xA7, 46 | 0xE4, 0x76, 0xBB, 0x55, 0x56, 0x59, 0xAB, 0x24, 0x9F, 0x5D, 47 | 0x5B, 0x65, 0xD7, 0xE9, 0xEC, 0x92, 0x29, 0x3B, 0xA1, 0x4E, 48 | 0xA7, 0xD3, 0xE9, 0x74, 0x9A, 0x8F, 0x8F, 0xEF, 0xED, 0x76, 49 | 0xBB, 0x55, 0x4E, 0xAE, 0x52, 0xAD, 0x9C, 0x9C, 0x4F, 0xE7, 50 | 0xED, 0x76, 0xBB, 0xDD, 0x2E, 0x95, 0x53, 0xD9, 0x25, 0xA5, 51 | 0x54, 0xD6, 0x2A, 0xAB, 0xEC, 0x76, 0xBB, 0x54, 0x4E, 0x65, 52 | 0x97, 0x94, 0x3A, 0x22, 0xA9, 0xA4, 0x92, 0x4A, 0x2A, 0xE9, 53 | 0x94, 0x4D, 0x2D, 0x9D, 0xA2, 0x94, 0xCA, 0x5A, 0x65, 0x95, 54 | 0xDD, 0x6E, 0x97, 0xCA, 0xA9, 0xEC, 0x12, 0x55, 0x69, 0x42, 55 | 0x7A 56 | }; 57 | 58 | // arduboy_logo.png 59 | // Sprites::drawSelfMasked() format 60 | // 88x16 61 | const uint8_t arduboy_logo_sprite[] PROGMEM = { 62 | 88, 16, 63 | 0xF0, 0xF8, 0x9C, 0x8E, 0x87, 0x83, 0x87, 0x8E, 0x9C, 0xF8, 64 | 0xF0, 0x00, 0x00, 0xFE, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 65 | 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xFE, 0xFF, 0x03, 0x03, 66 | 0x03, 0x03, 0x03, 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xFF, 67 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 68 | 0x00, 0x00, 0xFE, 0xFF, 0x83, 0x83, 0x83, 0x83, 0x83, 0xC7, 69 | 0xEE, 0x7C, 0x38, 0x00, 0x00, 0xF8, 0xFC, 0x0E, 0x07, 0x03, 70 | 0x03, 0x03, 0x07, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0x3F, 0x7F, 71 | 0xE0, 0xC0, 0x80, 0x80, 0xC0, 0xE0, 0x7F, 0x3F, 0xFF, 0xFF, 72 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xFF, 0x00, 73 | 0x00, 0xFF, 0xFF, 0x0C, 0x0C, 0x0C, 0x0C, 0x1C, 0x3E, 0x77, 74 | 0xE3, 0xC1, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 75 | 0xC0, 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 0x1F, 0x3F, 0x70, 76 | 0xE0, 0xC0, 0xC0, 0xC0, 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 77 | 0x7F, 0xFF, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xE3, 0x77, 0x3E, 78 | 0x1C, 0x00, 0x00, 0x1F, 0x3F, 0x70, 0xE0, 0xC0, 0xC0, 0xC0, 79 | 0xE0, 0x70, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 80 | 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/glcdfont.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file glcdfont.c 3 | * \brief 4 | * The font definitions used to display text characters. 5 | */ 6 | 7 | //#include 8 | //#include 9 | 10 | #ifndef FONT5X7_H 11 | #define FONT5X7_H 12 | 13 | // standard ascii 5x7 font 14 | static const unsigned char font[] = 15 | { 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 18 | 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 19 | 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 20 | 0x18, 0x3C, 0x7E, 0x3C, 0x18, 21 | 0x1C, 0x57, 0x7D, 0x57, 0x1C, 22 | 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 23 | 0x00, 0x18, 0x3C, 0x18, 0x00, 24 | 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 25 | 0x00, 0x18, 0x24, 0x18, 0x00, 26 | 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 27 | 0x30, 0x48, 0x3A, 0x06, 0x0E, 28 | 0x26, 0x29, 0x79, 0x29, 0x26, 29 | 0x40, 0x7F, 0x05, 0x05, 0x07, 30 | 0x40, 0x7F, 0x05, 0x25, 0x3F, 31 | 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 32 | 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 33 | 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 34 | 0x14, 0x22, 0x7F, 0x22, 0x14, 35 | 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 36 | 0x06, 0x09, 0x7F, 0x01, 0x7F, 37 | 0x00, 0x66, 0x89, 0x95, 0x6A, 38 | 0x60, 0x60, 0x60, 0x60, 0x60, 39 | 0x94, 0xA2, 0xFF, 0xA2, 0x94, 40 | 0x08, 0x04, 0x7E, 0x04, 0x08, 41 | 0x10, 0x20, 0x7E, 0x20, 0x10, 42 | 0x08, 0x08, 0x2A, 0x1C, 0x08, 43 | 0x08, 0x1C, 0x2A, 0x08, 0x08, 44 | 0x1E, 0x10, 0x10, 0x10, 0x10, 45 | 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 46 | 0x30, 0x38, 0x3E, 0x38, 0x30, 47 | 0x06, 0x0E, 0x3E, 0x0E, 0x06, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x5F, 0x00, 0x00, 50 | 0x00, 0x07, 0x00, 0x07, 0x00, 51 | 0x14, 0x7F, 0x14, 0x7F, 0x14, 52 | 0x24, 0x2A, 0x7F, 0x2A, 0x12, 53 | 0x23, 0x13, 0x08, 0x64, 0x62, 54 | 0x36, 0x49, 0x56, 0x20, 0x50, 55 | 0x00, 0x08, 0x07, 0x03, 0x00, 56 | 0x00, 0x1C, 0x22, 0x41, 0x00, 57 | 0x00, 0x41, 0x22, 0x1C, 0x00, 58 | 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 59 | 0x08, 0x08, 0x3E, 0x08, 0x08, 60 | 0x00, 0x80, 0x70, 0x30, 0x00, 61 | 0x08, 0x08, 0x08, 0x08, 0x08, 62 | 0x00, 0x00, 0x60, 0x60, 0x00, 63 | 0x20, 0x10, 0x08, 0x04, 0x02, 64 | 0x3E, 0x51, 0x49, 0x45, 0x3E, 65 | 0x00, 0x42, 0x7F, 0x40, 0x00, 66 | 0x72, 0x49, 0x49, 0x49, 0x46, 67 | 0x21, 0x41, 0x49, 0x4D, 0x33, 68 | 0x18, 0x14, 0x12, 0x7F, 0x10, 69 | 0x27, 0x45, 0x45, 0x45, 0x39, 70 | 0x3C, 0x4A, 0x49, 0x49, 0x31, 71 | 0x41, 0x21, 0x11, 0x09, 0x07, 72 | 0x36, 0x49, 0x49, 0x49, 0x36, 73 | 0x46, 0x49, 0x49, 0x29, 0x1E, 74 | 0x00, 0x00, 0x14, 0x00, 0x00, 75 | 0x00, 0x40, 0x34, 0x00, 0x00, 76 | 0x00, 0x08, 0x14, 0x22, 0x41, 77 | 0x14, 0x14, 0x14, 0x14, 0x14, 78 | 0x00, 0x41, 0x22, 0x14, 0x08, 79 | 0x02, 0x01, 0x59, 0x09, 0x06, 80 | 0x3E, 0x41, 0x5D, 0x59, 0x4E, 81 | 0x7C, 0x12, 0x11, 0x12, 0x7C, 82 | 0x7F, 0x49, 0x49, 0x49, 0x36, 83 | 0x3E, 0x41, 0x41, 0x41, 0x22, 84 | 0x7F, 0x41, 0x41, 0x41, 0x3E, 85 | 0x7F, 0x49, 0x49, 0x49, 0x41, 86 | 0x7F, 0x09, 0x09, 0x09, 0x01, 87 | 0x3E, 0x41, 0x41, 0x51, 0x73, 88 | 0x7F, 0x08, 0x08, 0x08, 0x7F, 89 | 0x00, 0x41, 0x7F, 0x41, 0x00, 90 | 0x20, 0x40, 0x41, 0x3F, 0x01, 91 | 0x7F, 0x08, 0x14, 0x22, 0x41, 92 | 0x7F, 0x40, 0x40, 0x40, 0x40, 93 | 0x7F, 0x02, 0x1C, 0x02, 0x7F, 94 | 0x7F, 0x04, 0x08, 0x10, 0x7F, 95 | 0x3E, 0x41, 0x41, 0x41, 0x3E, 96 | 0x7F, 0x09, 0x09, 0x09, 0x06, 97 | 0x3E, 0x41, 0x51, 0x21, 0x5E, 98 | 0x7F, 0x09, 0x19, 0x29, 0x46, 99 | 0x26, 0x49, 0x49, 0x49, 0x32, 100 | 0x03, 0x01, 0x7F, 0x01, 0x03, 101 | 0x3F, 0x40, 0x40, 0x40, 0x3F, 102 | 0x1F, 0x20, 0x40, 0x20, 0x1F, 103 | 0x3F, 0x40, 0x38, 0x40, 0x3F, 104 | 0x63, 0x14, 0x08, 0x14, 0x63, 105 | 0x03, 0x04, 0x78, 0x04, 0x03, 106 | 0x61, 0x59, 0x49, 0x4D, 0x43, 107 | 0x00, 0x7F, 0x41, 0x41, 0x41, 108 | 0x02, 0x04, 0x08, 0x10, 0x20, 109 | 0x00, 0x41, 0x41, 0x41, 0x7F, 110 | 0x04, 0x02, 0x01, 0x02, 0x04, 111 | 0x40, 0x40, 0x40, 0x40, 0x40, 112 | 0x00, 0x03, 0x07, 0x08, 0x00, 113 | 0x20, 0x54, 0x54, 0x78, 0x40, 114 | 0x7F, 0x28, 0x44, 0x44, 0x38, 115 | 0x38, 0x44, 0x44, 0x44, 0x28, 116 | 0x38, 0x44, 0x44, 0x28, 0x7F, 117 | 0x38, 0x54, 0x54, 0x54, 0x18, 118 | 0x00, 0x08, 0x7E, 0x09, 0x02, 119 | 0x18, 0xA4, 0xA4, 0x9C, 0x78, 120 | 0x7F, 0x08, 0x04, 0x04, 0x78, 121 | 0x00, 0x44, 0x7D, 0x40, 0x00, 122 | 0x20, 0x40, 0x40, 0x3D, 0x00, 123 | 0x7F, 0x10, 0x28, 0x44, 0x00, 124 | 0x00, 0x41, 0x7F, 0x40, 0x00, 125 | 0x7C, 0x04, 0x78, 0x04, 0x78, 126 | 0x7C, 0x08, 0x04, 0x04, 0x78, 127 | 0x38, 0x44, 0x44, 0x44, 0x38, 128 | 0xFC, 0x18, 0x24, 0x24, 0x18, 129 | 0x18, 0x24, 0x24, 0x18, 0xFC, 130 | 0x7C, 0x08, 0x04, 0x04, 0x08, 131 | 0x48, 0x54, 0x54, 0x54, 0x24, 132 | 0x04, 0x04, 0x3F, 0x44, 0x24, 133 | 0x3C, 0x40, 0x40, 0x20, 0x7C, 134 | 0x1C, 0x20, 0x40, 0x20, 0x1C, 135 | 0x3C, 0x40, 0x30, 0x40, 0x3C, 136 | 0x44, 0x28, 0x10, 0x28, 0x44, 137 | 0x4C, 0x90, 0x90, 0x90, 0x7C, 138 | 0x44, 0x64, 0x54, 0x4C, 0x44, 139 | 0x00, 0x08, 0x36, 0x41, 0x00, 140 | 0x00, 0x00, 0x77, 0x00, 0x00, 141 | 0x00, 0x41, 0x36, 0x08, 0x00, 142 | 0x02, 0x01, 0x02, 0x04, 0x02, 143 | 0x3C, 0x26, 0x23, 0x26, 0x3C, 144 | 0x1E, 0xA1, 0xA1, 0x61, 0x12, 145 | 0x3A, 0x40, 0x40, 0x20, 0x7A, 146 | 0x38, 0x54, 0x54, 0x55, 0x59, 147 | 0x21, 0x55, 0x55, 0x79, 0x41, 148 | 0x21, 0x54, 0x54, 0x78, 0x41, 149 | 0x21, 0x55, 0x54, 0x78, 0x40, 150 | 0x20, 0x54, 0x55, 0x79, 0x40, 151 | 0x0C, 0x1E, 0x52, 0x72, 0x12, 152 | 0x39, 0x55, 0x55, 0x55, 0x59, 153 | 0x39, 0x54, 0x54, 0x54, 0x59, 154 | 0x39, 0x55, 0x54, 0x54, 0x58, 155 | 0x00, 0x00, 0x45, 0x7C, 0x41, 156 | 0x00, 0x02, 0x45, 0x7D, 0x42, 157 | 0x00, 0x01, 0x45, 0x7C, 0x40, 158 | 0xF0, 0x29, 0x24, 0x29, 0xF0, 159 | 0xF0, 0x28, 0x25, 0x28, 0xF0, 160 | 0x7C, 0x54, 0x55, 0x45, 0x00, 161 | 0x20, 0x54, 0x54, 0x7C, 0x54, 162 | 0x7C, 0x0A, 0x09, 0x7F, 0x49, 163 | 0x32, 0x49, 0x49, 0x49, 0x32, 164 | 0x32, 0x48, 0x48, 0x48, 0x32, 165 | 0x32, 0x4A, 0x48, 0x48, 0x30, 166 | 0x3A, 0x41, 0x41, 0x21, 0x7A, 167 | 0x3A, 0x42, 0x40, 0x20, 0x78, 168 | 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 169 | 0x39, 0x44, 0x44, 0x44, 0x39, 170 | 0x3D, 0x40, 0x40, 0x40, 0x3D, 171 | 0x3C, 0x24, 0xFF, 0x24, 0x24, 172 | 0x48, 0x7E, 0x49, 0x43, 0x66, 173 | 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 174 | 0xFF, 0x09, 0x29, 0xF6, 0x20, 175 | 0xC0, 0x88, 0x7E, 0x09, 0x03, 176 | 0x20, 0x54, 0x54, 0x79, 0x41, 177 | 0x00, 0x00, 0x44, 0x7D, 0x41, 178 | 0x30, 0x48, 0x48, 0x4A, 0x32, 179 | 0x38, 0x40, 0x40, 0x22, 0x7A, 180 | 0x00, 0x7A, 0x0A, 0x0A, 0x72, 181 | 0x7D, 0x0D, 0x19, 0x31, 0x7D, 182 | 0x26, 0x29, 0x29, 0x2F, 0x28, 183 | 0x26, 0x29, 0x29, 0x29, 0x26, 184 | 0x30, 0x48, 0x4D, 0x40, 0x20, 185 | 0x38, 0x08, 0x08, 0x08, 0x08, 186 | 0x08, 0x08, 0x08, 0x08, 0x38, 187 | 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 188 | 0x2F, 0x10, 0x28, 0x34, 0xFA, 189 | 0x00, 0x00, 0x7B, 0x00, 0x00, 190 | 0x08, 0x14, 0x2A, 0x14, 0x22, 191 | 0x22, 0x14, 0x2A, 0x14, 0x08, 192 | 0x95, 0x00, 0x22, 0x00, 0x95, 193 | 0xAA, 0x00, 0x55, 0x00, 0xAA, 194 | 0xAA, 0x55, 0xAA, 0x55, 0xAA, 195 | 0x00, 0x00, 0x00, 0xFF, 0x00, 196 | 0x10, 0x10, 0x10, 0xFF, 0x00, 197 | 0x14, 0x14, 0x14, 0xFF, 0x00, 198 | 0x10, 0x10, 0xFF, 0x00, 0xFF, 199 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 200 | 0x14, 0x14, 0x14, 0xFC, 0x00, 201 | 0x14, 0x14, 0xF7, 0x00, 0xFF, 202 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 203 | 0x14, 0x14, 0xF4, 0x04, 0xFC, 204 | 0x14, 0x14, 0x17, 0x10, 0x1F, 205 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 206 | 0x14, 0x14, 0x14, 0x1F, 0x00, 207 | 0x10, 0x10, 0x10, 0xF0, 0x00, 208 | 0x00, 0x00, 0x00, 0x1F, 0x10, 209 | 0x10, 0x10, 0x10, 0x1F, 0x10, 210 | 0x10, 0x10, 0x10, 0xF0, 0x10, 211 | 0x00, 0x00, 0x00, 0xFF, 0x10, 212 | 0x10, 0x10, 0x10, 0x10, 0x10, 213 | 0x10, 0x10, 0x10, 0xFF, 0x10, 214 | 0x00, 0x00, 0x00, 0xFF, 0x14, 215 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 216 | 0x00, 0x00, 0x1F, 0x10, 0x17, 217 | 0x00, 0x00, 0xFC, 0x04, 0xF4, 218 | 0x14, 0x14, 0x17, 0x10, 0x17, 219 | 0x14, 0x14, 0xF4, 0x04, 0xF4, 220 | 0x00, 0x00, 0xFF, 0x00, 0xF7, 221 | 0x14, 0x14, 0x14, 0x14, 0x14, 222 | 0x14, 0x14, 0xF7, 0x00, 0xF7, 223 | 0x14, 0x14, 0x14, 0x17, 0x14, 224 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 225 | 0x14, 0x14, 0x14, 0xF4, 0x14, 226 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 227 | 0x00, 0x00, 0x1F, 0x10, 0x1F, 228 | 0x00, 0x00, 0x00, 0x1F, 0x14, 229 | 0x00, 0x00, 0x00, 0xFC, 0x14, 230 | 0x00, 0x00, 0xF0, 0x10, 0xF0, 231 | 0x10, 0x10, 0xFF, 0x10, 0xFF, 232 | 0x14, 0x14, 0x14, 0xFF, 0x14, 233 | 0x10, 0x10, 0x10, 0x1F, 0x00, 234 | 0x00, 0x00, 0x00, 0xF0, 0x10, 235 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 236 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 237 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 238 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 239 | 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 240 | 0x38, 0x44, 0x44, 0x38, 0x44, 241 | 0x7C, 0x2A, 0x2A, 0x3E, 0x14, 242 | 0x7E, 0x02, 0x02, 0x06, 0x06, 243 | 0x02, 0x7E, 0x02, 0x7E, 0x02, 244 | 0x63, 0x55, 0x49, 0x41, 0x63, 245 | 0x38, 0x44, 0x44, 0x3C, 0x04, 246 | 0x40, 0x7E, 0x20, 0x1E, 0x20, 247 | 0x06, 0x02, 0x7E, 0x02, 0x02, 248 | 0x99, 0xA5, 0xE7, 0xA5, 0x99, 249 | 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 250 | 0x4C, 0x72, 0x01, 0x72, 0x4C, 251 | 0x30, 0x4A, 0x4D, 0x4D, 0x30, 252 | 0x30, 0x48, 0x78, 0x48, 0x30, 253 | 0xBC, 0x62, 0x5A, 0x46, 0x3D, 254 | 0x3E, 0x49, 0x49, 0x49, 0x00, 255 | 0x7E, 0x01, 0x01, 0x01, 0x7E, 256 | 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 257 | 0x44, 0x44, 0x5F, 0x44, 0x44, 258 | 0x40, 0x51, 0x4A, 0x44, 0x40, 259 | 0x40, 0x44, 0x4A, 0x51, 0x40, 260 | 0x00, 0x00, 0xFF, 0x01, 0x03, 261 | 0xE0, 0x80, 0xFF, 0x00, 0x00, 262 | 0x08, 0x08, 0x6B, 0x6B, 0x08, 263 | 0x36, 0x12, 0x36, 0x24, 0x36, 264 | 0x06, 0x0F, 0x09, 0x0F, 0x06, 265 | 0x00, 0x00, 0x18, 0x18, 0x00, 266 | 0x00, 0x00, 0x10, 0x10, 0x00, 267 | 0x30, 0x40, 0xFF, 0x01, 0x01, 268 | 0x00, 0x1F, 0x01, 0x01, 0x1E, 269 | 0x00, 0x19, 0x1D, 0x17, 0x12, 270 | 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 271 | 0x00, 0x00, 0x00, 0x00, 0x00, 272 | }; 273 | 274 | #endif 275 | --------------------------------------------------------------------------------