• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • Stop wasting time looking for files and revisions. Connect your Gmail, DriveDropbox, and Slack accounts and in less than 2 minutes, Dokkio will automatically organize all your file attachments. Learn more and claim your free account.


Project Code

Page history last edited by Huangang Zheng 7 years, 2 months ago

Project Code


     Here is the code I used in my project. I have used external libraries from Adafruit.com for their 1.8 TFT graphics display, Sparkfun's MIDI example code for their VS1053 MIDI and MP3 decoder chip, and Adafruit's SD libraries. I also modified  µC eXperiment's code for using the 23LC1024 for my own functions. I also use many native Arduino libraries, such as Wire and SoftwareSerial and SPI.


Arduino Master Code




Code Walkthrough


Early on in the code, I have defined important constants as well as data I had not put into the resource files, such as a color bank, map memory addresses, special locations you can fly to, and a passability bitmap for tiles.


In the setup function, I begin SPI and establish a quick init of the TFT screen. Then I give the slave Arduino 6 seconds to prepare itself for inter-arduino communication. This seems ample time based on experimentation. I immediately display "Loading" on the screen to reassure the user that their device has not malfunctioned already, indicating the number of KB transferred between Arduino as well. The first thing to do is continuously poll the slave for an alive signal. The code will get stuck here if the slave device is unable to respond. I probably should have added a timeout limit so that the master could display some sort of error to notify the user. After a connection has been established, I notify the slave about a pending data transaction, then continuously request bytes in chunks of 32 (since 32 is the buffer size limit for I2C) until the transfer is complete. I broke the INIT step into 2 separate stages just to simplify reading the 2 files logically (so that I wouldn't need to send one half of the buffer to 1 sector in memory and another half to the other). Based on constants defined earlier, I send the bytes into the external SRAM, offset by those constants. Finally, I call fly to teleport the player to the first city (Celadon City due to alphabetical ordering).


Fly's job is to update the current map and position of the character so that it fits with the array information specified at the beginning of the code. I modeled fly's behavior off of the Pokemon game function Fly (HM02) that took the user to a defined location (usually a PokeCenter) they have already visited. Fly also updates the map tile buffer to reflect a total change in location as well as readies the sprite for drawing. Finally, fly queues a song change based on the target map and then prevents itself from being called again until the user has taken at least 1 step (to prevent flying to 2 cities without having time to visit the first!) Fly simply goes in order of the cities in my array (alphabetical) and cycles around when the bounds are hit.


Setsong simply tells the slave Arduino to change songs through I2C.


Pollbuttons simply reads the analog outputs of my controller board and sets the bits of keysDown based on which keys are down. It uses a bit of arithmetic to ensure read values are between a certain range. Constants are used (and precalculated) to determine which buttons are being pressed. The code does not support for multiple presses (as is not needed by this version of the project) but can be modified to do so since the resistor values were chosen such that readings would not overlap and would be distinct enough to determine which set of buttons were pressed at any reading. A threshold of 100/1024 volts is used to discount any strange readings (and is just assumed to be no buttons pressed).


UpdateSpriteInfo update information on how to draw the sprite based on the current state of the character. For example, it tells which of the 6 frames of the sprite (see Game Resources And SRAM) to draw and whether it should be reflected or not based on direction the character is facing and how he his moving.


The draw loop does a bit more than draw (sorry for the misnomer). It polls the buttons and then orients the player in the way they have indicated to move, prioritizing left up right down in that order (and then finally the fly command). It will not move you if passability for the tile you intend to move onto is not passable (array mentioned above is a bit array with 0 for passable and 1 for impassable). The sprite is then updated calling updateSpriteInfo and then moveState is incremented to 1. Movestate is a variable that represents which stage of the 4-tick move sequence the player is on. This is used for animation sequences as well as when to update the player's actual map coordinates. Movestate of 0 represents standstill and when the program accepts new commands. Any other movestate implies the character is mid move and will not do anything except continue the animation till the character has reached his destination. After checking for new commands, the loop then tries to resume any previous commands it was in the middle of by incrementing movestate and then checking for a complete move (movestate equaling 4). It checks for walking off map boundaries, and updates a map transfer based off of adjacent maps. This updates x and y positions with respect to the new map, changes songs, and updates the current map index to reflect the new map. Finally, after a successful move, the map tile buffer is updated and movestate is reset to open state (or 0) and then a special variable for changing which arm the sprite raises (reflect counter) is updated. Finally after doing all these calculations, the loop finally draws the background and then the sprite.


Drawing the sprite simply loads in the 64 bytes needed for color pixels and then the corresponding 16 bytes for transparency from the SRAM, drawing the opaque bytes over the background as needed. Information as to which frame to draw and whether to reflect or not has already been calculated in the draw loop.


DrawPixel is simply a wrapper around Adafruit's drawPixel function. My version ignores all draws outside the bound of the screen.


LoadTileColors is a helper function that loads the 16 bit color values for a given tile into an array buffer for later use. It loads memory from the SRAM and translates the color encoding into real 16bit color values.


GetMapBufferValue retrieves the ID of a tile within my map buffer array. The array is cyclical and loops around on itself; this function makes accounting for the shift and bounding the index an under-the-hood operation.


DrawScreen takes the map buffer array and actually draws the tiles onto the screen. It categorizes the tiles into likeness groups, guessing that at most 16 tiles will appear on each screenful of information. The function loops through the map buffer array, drawing all the tiles of the same type in one pass, going until all tiles have been swept.


UpdateMapBuffer updates the cyclical map tile buffer to reflect a move made by the character. Upon moving left or right, the offset is updated an a new column of tile info is read from the SRAM and placed into the buffer. Upon moving up or down, the same happens for a row of tile information. Upon flying, the entire buffer is rewritten (but this happens in a different function call).


Spi23LC1024Read8 and Spi23LC1024Write8 are taken from  µC eXperiment's code for using the 23LC1024 and modified slightly to accommodate my design.


GetMemMapInfo is a wrapper to get the i-th byte of a maps data from the SRAM.


GetTileValue takes a coordinate and a map index, calculating the actual tile to display. This takes into account both map boundaries and surrounding tiles (see Game Resources and SRAM) and gives you the actual ID of the tile to be displayed at that coordinate. It recursively calls itself if needed upon reaching a map boundary.



Arduino Slave Code




Code (Brief) Walkthrough


The slave code simply initializes the MIDI feature of the VS1053 and the SDMicro card and awaits orders from the Master Arduino across the I2C bus. Upon receiving a byte, it changes mode based on the instruction (INIT, ALIVE, and CHANGE SONG). It then changes behavior or queues bytes to send on getting a request from the master. Normally, when no commands are being handled, the slave processes the current song number by reading the information from the SD card and handling the MIDI instructions from the MIDI file chunk. Details on handling MIDI instructions can be found on the Sounds page. Notable functions worth mentioning include readWithWait (which reads info from the SD card and waits 1 ms before retrying upon failure), writeToMidi (which sends 1 byte of info to the VS1053 via serial communication), getVarLen (which looks into the MIDI commands and reads a special variable-length encoded value; again, see the Sounds page MIDI additional reading for more info), and setSong (which just advances the file cursor to the proper song's data and sends a "hush" message across all channels of the MIDI decoder).





Comments (0)

You don't have permission to comment on this page.