2019年8月14日星期三

Google Chrome Dinosaur Game on 16x2 LCD Shield

Things used in this project

Hardware components

Arduino UNO & Genuino UNO ×1

DFRobot LCD keypad shield ×1


Software apps and online services

Microchip Atmel Studio 7


Story

Something that was bothering me for some time now is that I haven't made anything fun in a while, mainly because I lack good ideas for things like that. Luckily, an idea came to my mind to do this project out of nowhere, and viola, here we are.

Now, just to point out, this project was NOT written in Arduino, rathte pure AVR C code using Atmel Studio. I am using an Arduino Uno and an Uno-compatible shield, but mainly because that is the cheapest and most available thing I have with me (same goes for the keypad shield I have it for years now). So for anyone familiar with C but only worked in Arduino, about half of the code would look confusing here, but take it from someone who took to learn AVR C (by myself), it's not hard at all as I managed to adapt to it in a week (more less).

If you want to use the code or just flash the firmware on the UNO board, I recommend watching this video.

For the most part, I've explained everything in the code itself, here I'm going to more explain the working principle of the code.


Hitachi HD44780 (LCD Driver) and Its 4-Bit mode

This was a bit of a struggle for a datasheet newbie, but interfacing with this IC was pretty frustrating. I ended up copying configuration bytes from this guy (thanks dude). Everything else depends on setting the bits in the upper 4 pins of the PORTD register and using EN and RS pins properly. In my implementation the whole PORTD is wasted for communicating with the display (thus nullifying the benefits of the 4-bit mode) and effectively wasting the UART but I wasn't going to use those for anything else anyway so it's ok for this use case. Make sure when you interface with this IC to use proper delays otherwise it won't be able to process the data properly (it is a fairly slow chip). Another point is that the game speed is limited by how quick the display can refresh. At its highest peek speed, the display updates at 11.8Hz (85ms delay on loop) and it's readable enough to play this kind of game, everything further causes major gosting and brightness issues. Now, you have to keep in mind, this doesn't want the IC and display are made for, just simple text displaying.


Random Number Generation

As I wanted to make this game as random as possible, every time I loop I read the analog value from ADC5 that is left floating and use that number as a seed for C's RNG function. This is far from perfect as there can appear repeating sequences that take a few seconds to go away, but without adding any additional hardware, this is (probably) the best it can get.


Button Pooling

Every 500us Timer1 is set to trigger TIMER1_COMPA_vect ISR where it's checked if the up button is pressed and global variable(s) is set accordingly.


Upper, Downer Buffers and Screen Drawing

To make everything easier to code and effectively shrink the application logic I use a buffer for the line one and a buffer for the line two of the display. Everything is written to those 2 buffers and they are written as a whole to the display.


Custom Characters for Dinosaur and Cactus

Nothing special here, I made a 5x8 image in paint.net and drew the dinosaur and the cactus. Every colored square represents a 1 and empty (white) one represents to zero. After that I translated that to hex and added everything to the two uint8 arrays. Those 2 characters are stored in IC's CGRAM, where the dinosaur is 0x00 and cactus is 0x01 in its register.


Overview of Game Characteristics

The game speeds up the longer you play it

The cactuses are initialy seperated by minimaly 5 spaces, as the game progresses it goes down to 3

Current score is displayed in the game, current, and best score are displayed after you lose

There is 500ns interrupt pooling to check if the Up button is pressed and thus there are no issues with delay blocking the input

Cheating by holding the Up button constantly or spaming it are prevented

The dinosaur is in the air 3 loop cycles after it jumps

Custom characters made for the cactuses and dinosaur

After you loose, press the select button to restart the game.




Ghosting isn't as visible when you play the game

Code

main.c (C/C++)

main.c file for the project. Just create a new project and paste this in, no other dependecies (that aren't already a part of the Atmel Studio and AVR GCC)

#include <avr/io.h>
#define  F_CPU 16000000UL //Our CPU speed (16MHz)
#include <util/delay.h> //Libraries for delay and interrupt utilities
#include <avr/interrupt.h>
#define command 0 //explained in dispSend() function
#define write 1

uint8_t upperBuff[16] , downerBuff[16], overMsgUpper[] = "Score: ", overMsgDowner[] = "Best: ", scoremsg[] = "Score:" , din[] = {0x0E, 0x17, 0x1E, 0x1F, 0x18, 0x1F, 0x1A, 0x12}, cact[] = {0x04, 0x05, 0x15, 0x15, 0x16, 0x0C, 0x04, 0x04};
        //Buffers for line one and two. Message to display after lost game.                     //Score text during game. //Dinosaur and cactus bitmaps
uint8_t canup = 1, longhold = 0, distance = 6, speed = 200, isup = 0, dontprint = 0; //All of these are explained further
uint16_t aVal = 0, score = 1, bestscore = 0;
int i;

void dispInit();
void dispWrite(uint8_t bits);
void dispSend(uint8_t bits, uint8_t act);
void dispSetLine(uint8_t line);
void dispClear();
void dispHome();
void dispPrintChar(uint8_t chr[], uint8_t size);
uint16_t aRead();

int main(void)
{
 for(i = 0; i < 17; i++) downerBuff[i] = ' '; //Initialize upper and downward buffer
 for(i = 0; i < 17; i++) upperBuff[i] = ' ';
 
 dispInit(); //Initialize the display
 
 TCCR1B |= (1 << WGM12) | (1 << CS11); //Set Timer1 to compare to OCR1A and prescaler of 8
 OCR1AH = (500 >> 8); //This equals to 2000Hz or 500us timing, look for TIMER1_COMPA_vect down below
 OCR1AL = 500;
 TIMSK1 |= (1 << OCIE1A); //Enable Timer1 CompA ISR
 sei(); //Enable global interrupt

 
 ADMUX = (1 << REFS0); //Set AREF to VCC
 ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN); //set ADC prescaler to 128 and enable ADC (defaulted to free running mode)
 
 while (1) {  
  
  ADMUX |= (1 << MUX2) | (1 << MUX0); //Set pin from ADMUX to ADC5 (floating)
  srand(aRead()); //Use it as a random seed
  ADMUX &= ~(1 << MUX2) & ~(1 << MUX0); //Revert back to ADC0 to read the button value

  if(aRead() > 900) longhold = 0; //Reads if Up button has been released to prevent cheating. The value is so low because if you hold your fingers beneath one of the buttons the voltage would drop, this prevents the dinosaur from locking up

  for(i = 0; i < 16; i++) downerBuff[i] = downerBuff[i + 1]; //Shifts everything in downward buffer by one place to the left
   if((rand() % 100) > (rand() % 100) && !dontprint){ //This portion decides if it should put a cactus or a blank spot, dontprint is used to prevent cactus grouping
    downerBuff[15] = 0x01; //0x01 represents the cactus (we added cactus and dinosaur to CGRAM when we initialized the display)
    dontprint = 1; //This part acts both as a boolean and a counter to ensure cactus separation
   }
   else downerBuff[15] = ' ';
   char lastchar = downerBuff[3]; //We remember the whats initially added to the downward buffer before replacing it with the dinosaur
   if(!isup){ //If din should be placed down
   downerBuff[3] = 0x00; //Place it down
   dispSetLine(2);
   dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
   downerBuff[3] = lastchar; //Place back previous thing to the buffer
   canup = 1;  //This flag is used to disable dinosaur from getting up before it was drawn down, in this case he can go up
  } else { //If din should be placed up
   upperBuff[3] = 0x00; //Place it up in upper buff
   dispSetLine(1);
   dispPrintChar(upperBuff, sizeof(upperBuff));
   dispSetLine(2);
   dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
   canup = 0; //In this case he wont go up until rendered on line 2
  }

  if(dontprint) dontprint++;
  if(dontprint > distance) dontprint = 0; //This is the part that ensures cactus separation, it will keep the cactus 3-5 spaces apart minimally (depends on the game progress)
  
  if(isup) isup++; //This part makes sure din is on upper side for 3 loops after he was initially drawn there
  if(isup > 4){
   upperBuff[3] = ' ';
   dispSetLine(1);
   dispPrintChar(upperBuff, sizeof(upperBuff));
   isup = 0;
  }
  for(i = 0; i < sizeof(scoremsg); i++) upperBuff[i + 5] = scoremsg[i]; //This part prints the current score during the game
  uint8_t cnt = 11;
  for(i = 10000; i > 0; i /= 10){
   upperBuff[cnt] = ((score / i) % 10) + '0';
   cnt++;
   dispSetLine(1);
   dispPrintChar(upperBuff, sizeof(upperBuff));
  }

  score++; //Increment the score once on loop
  if(score > bestscore) bestscore = score; //Remember best score
  
  if(lastchar == 0x01 && !isup){ //Check if the dinosaur is downward and hit a cactus
   dispClear(); //Clear the display and buffers
   for(i = 0; i < 17; i++) downerBuff[i] = ' ';
   for(i = 0; i < 17; i++) upperBuff[i] = ' ';
   uint8_t cnt;
   
   dispSetLine(1);
   for(i = 0; i < sizeof(overMsgUpper); i++) upperBuff[i] = overMsgUpper[i]; //Display worst and best score
   cnt = sizeof(overMsgUpper) - 1;
   for(i = 10000; i > 0; i /= 10){
    upperBuff[cnt] = ((score / i) % 10) + '0';
    cnt++;
   }
   dispPrintChar(upperBuff, sizeof(upperBuff));
   
   dispSetLine(2);
   for(i = 0; i < sizeof(overMsgDowner); i++) downerBuff[i] = overMsgDowner[i];
   cnt = sizeof(overMsgDowner) - 1;
   for(i = 10000; i > 0; i /= 10){
    downerBuff[cnt] = ((bestscore / i) % 10) + '0';
    cnt++;
   }
   dispPrintChar(downerBuff, sizeof(downerBuff));
   
   while(1){ //Wait for select button to be pressed
    aVal = aRead();
    if(aVal > 635 && aVal < 645){ //After that clear all the variables
     for(i = 0; i < 17; i++) downerBuff[i] = ' ';
     dispSetLine(1);
     dispPrintChar(downerBuff, sizeof(downerBuff));
     for(i = 0; i < 17; i++) upperBuff[i] = ' ';
     dispSetLine(2);
     dispPrintChar(upperBuff, sizeof(upperBuff));
     dontprint = 0;
     isup = 0;
     score = 1;
     speed = 200;
     longhold = 0;
     distance = 6;
     canup = 1;
     break;
          }
   }
   
  }
        if(score % 5 == 0) speed -=2; //If score is divisible by 5 make game faster by -2ms
  if(speed < 85) speed = 85; //Minimal time in ms (+ ~2ms) that the loop will be halted for (limited by display refreshing, in my testing 11.8Hz was readable enough to be playable)
  if(score % 175 == 0) distance--; //Every time you score a number divisible by 175 minimal cactus distance gets smaller
  if(distance < 4) distance = 4;
  for(i = 0; i < speed; i++) _delay_ms(1); //This is the only way as the compiler expects a const number here
 }
}

void dispInit(){
 _delay_ms(50); //Just in case
 DDRD = 0b11110000; //Set these pins to output. PD4 - PD7 correspond to D4 - D7 on display, we need to configure it to run in 4 bit mode
 DDRB = 0b00000011; //PB0 is tied to RS and PB1 to EN
 dispWrite(0x30);//*This part here is explained in Hitachi HD44780 datasheet on how to initialize the display in 4bit mode
 _delay_us(4500);//*Essentially you send the reset signal 3 times, and then set it to 4 bit mode
 dispWrite(0x30);//*
 _delay_us(4500);//*
 dispWrite(0x30);//*
 _delay_us(4500);//*
 dispWrite(0x28);//*
 dispSend(0x28, command); //Send 4bit mode function set
 dispSend(0x08, command); //Turn the display off
 dispSend(0x01, command); //Clear its RAM (if MCU resets that doesn't mean the display was reset, so we clear everything)
    _delay_ms(50);
 dispSend(0x0C, command); //Turn the display on
 _delay_ms(5);
 dispSend(0x40, command); //Tell the display we want to enter a custom character to its CGRAM (on address 0x00)
 for(i=0; i<8; i++) dispSend(din[i], write);
 dispSend(0x80, command); //Transaction end
 dispSend(0x48, command); //Same thing, but for 0x01
 for(i=0; i<8; i++) dispSend(cact[i], write);
 dispSend(0x80, command);
}

void dispPrintChar(uint8_t chr[], uint8_t size){
 for(uint8_t i = 0; i < size; i++) dispSend(chr[i], write); //Self explanatory 
}

void dispSetLine(uint8_t line){
 if(line == 2) dispSend(0xC0, command); //Sets the line where 0xC0 is line 2 and 0x80 is line 1
 else dispSend(0x80, command);
}

void dispClear(){
 dispSend(0x01, command); //Self explanatory 
 _delay_ms(2); //This command takes longer for the IC to process, this delay is necessary
}

void dispHome(){ //This function isn't used in this application but its there for expandability, it places the cursor on the line 1 column 1
 dispSend(0x02, command); //Self explanatory 
 _delay_ms(2);
}

void dispSend(uint8_t bits, uint8_t act){
 if(act) PORTB |= (1 << DDB0); //Set PB0 if we are writing a character, else pull it low
 else PORTB &= ~(1<<DDB0);
 dispWrite(bits); //Send the bit then shift them 4 bit to the left to work in displays 4bit mode
 dispWrite(bits << 4);
 _delay_us(80);
}

void dispWrite(uint8_t bits){
 PORTD = bits; //This is a dirty way to write it but it's perfect for this application as it's not bulky and PORTD isn't used for anything else anyway
 PORTB |= (1<<DDB1); //Pulse the PB1 to signal the IC to read the data
 _delay_us(1);
 PORTB &= ~(1<<DDB1);
 _delay_us(1);
}

uint16_t aRead(){
 ADCSRA |= (1 << ADSC); //This signal the avr to read the ADC value
 while  (ADCSRA & (1 << ADSC)); //Wait until it's finished
 return ADCL | (ADCH << 8); //Send it back stitched together
}

ISR (TIMER1_COMPA_vect){ //Timer ISR we set up earlier
 if(!longhold){ //Return if the Up button was still held
  aVal = aRead(); //Read from ADC0
  if(aVal > 95 && aVal < 104 && canup){ //Check if Up is pressed and that din was rendered down
   isup = 1;
   longhold++;
   }
  }
}


C++ adaptation

bane9 AVR-CPP-LCD-Game

C++ port of of my chrome's dinosaur game — Read More

Latest commit to the master branch on 8-21-2018      Download as Zip


This article copied from hackster.io, Author: brzi

Arduino Gyroscope Game with MPU-6050

An addictive Arduino game on a 128x64 OLED display.

Things used in this project

Hardware components

Arduino Nano R3 ×1 DFRobot 6 DOF Sensor - MPU6050 ×1 Graphic OLED, 128 x 64 ×1 LED (generic) ×1 Resistor 221 ohm ×1 Slide Switch ×2


Software apps and online services

Arduino IDE


Hand tools and fabrication machines
Soldering iron (generic)


Story

The following elements are needed to make this project:

Arduino Nano

GY-521 module with the MPU-6050 sensor

0.96" OLED SPI display module 128x64 with SSD1306 chip

Buzzer

3mm LED diode

220 Ohm resistor

The rules of the game are simple: Tilt the sensor to move the ball around and try to catch as many squares as possible within 1 minute, without touch the borders.

Sketch below includes 2 functions: the game, if D7 is connected to GND; and a basic gyro test program that shows the pitch and roll values, shows them in an XY grid, and changes the buzzer pitch according to distance from origin.

For the OLED is used the excellent U8G library which allows many fonts and fast graphics. You will need to include 2 files in your sketch folder I2C and Kalman.h, they come with the MPU-6050 example.

The scheme is presented in the picture below. Detailed instructions can be found on the author's website cassiopeia.hk.

Finally, the whole assembly is placed in a box made of 3mm PVC material.


Schematics

Click Here to Download                                      


Code

Code(Arduino)             Click Here to Download


This article copied from hackster.io, Author: Mirko Pavleski

The Memory Game

Things used in this project

Hardware components

Arduino UNO & Genuino UNO ×1

9V battery (generic) ×1

Switch ×1

Jumper wires (generic) ×1

Male Dc Barrel Jack Adapter For Arduino ×1

Breadboard (generic) ×1

DFRobot I2C 16x2 Arduino LCD Display Module ×1

Adafruit Standard LCD - 16x2 White on Blue ×1

Push Button ×1

Resistor 100 ohm ×1

4 different colored LEDs ×1

Speaker: 0.25W, 8 ohms ×1

Story

If you have a bad memory to remember, Memory game is a great idea for improving memory and concentration.

This game is a Simon based game for the Arduino. This project consists of 4 buttons, 4 LEDs, an Arduino, an LCD, and a speaker. In this game, you have to follow the pattern of LEDs/notes as long as you can.


Step 1: Materials Needed

Arduino: https://amzn.to/2qU18sO

I2C LCD Module: https://amzn.to/2NkVQje

16x2 LCD: https://amzn.to/2LlYrJ7

9v battery: https://amzn.to/2zIcBmL

Switch: https://amzn.to/2upTngE

Jumper wires: https://amzn.to/2zKePBX

Cardboard: https://amzn.to/2JtgVFZ

Speaker: https://amzn.to/2qRuE2B

Male DC Barrel Jack Adapter for Arduino: https://amzn.to/2DLx6jI

4 Push Button: https://amzn.to/2qRwssp

100 Ohm Resistor: https://amzn.to/2Q8zxCs

4 different colored LEDs: https://amzn.to/2Kb6EQA

9v Battery Connector: https://amzn.to/2r3TeNP


Step 2: Making the Circuit on Breadboard

Place 4 different colored LEDs on your breadboard, make sure to note which way the long leg is facing.

Now connect one side of the 100 Ohm resistor to the long leg of each LED.

Connect a jumper wire from one side of the 100-ohm resistor to digital pin 8. Repeat this with the other resistors and connect to digital pin 9-11.

Place your 4 buttons on the breadboard.

Connect a wire from one side of the button to the short leg of the LED and then connect a jumper wire with this side of the button to ground on the Arduino. Repeat this with the other LEDs and push buttons

Connect a jumper wire from the other side of the button to digital pin 2 on Arduino. Same with the other buttons and connect it to pin 3-5.

Connect a jumper wire from a speaker to digital pin 12 and the other wire to the ground.

Connect an I2C LCD to SDA, SCL, VCC, GND on Arduino. SCL is the clock signal, and SDA is the data signal. The clock signal is always generated by the current bus master; some slave devices may force the clock low at times to delay the master sending more data

To power your Arduino you will need a 9v Battery, a 9v battery connector, a switch, a male DC Barrel jack adapter. Solder the switch to the red lead from the 9V battery connector. Connect the black lead from the battery connector to the negative terminal of the jack adapter and connect the lead from the switch to the positive terminal.


Step 3: The Sketch

All you need to do is install 1602 I2C LCD library and Tone library. You will get an error if you don't install.

You can download source code and libraries from here.

When you want to add a new library into your Arduino IDE. Go to the directory where you have downloaded the ZIP file of the library. Extract the ZIP file with all its folder structure in a temporary folder, then select the main folder, that should have the library name. Copy it in the “libraries” folder inside your sketchbook


Step 4: Building a Cardboard Enclosure.

Find a cardboard box that fits the circuit. Don’t forget to make holes for LEDs, buttons and also make the holes to mount the switch and the LCD display. Put the circuit into the enclosure.


Step 5: Finish


Schematics

Memory Game


Code

Github

https://github.com/IsmailSan/Memory-Game

IsmailSan / Memory-Game

No description — Read More

Latest commit to the master branch on 11-19-2018    Download as Zip


This article copied from hackster.io, Author: Ikhsan Ismail

DFRobot's 2.8&quot; TFT Touch Shield for Arduino

Wanna play with some graphic or make a touch interface? Use this DFRobot's Touch Shield for Arduino UNO!

Things used in this project

Hardware components

Arduino UNO & Genuino UNO ×1

DFRobot's 2.8" TFT Touch Shield ×1


Software apps and online services

Arduino IDE

DFRobot Image Converter


Hand tools and fabrication machines

Stylus (generic)


Story

Introduction

Add some flavor to your Arduino project with this large colorful touch screen by DFRobot. This TFT Touch screen is a fantastic shield with big (2.8" diagonal) and 240x320 pixels with individual pixel control which could apply to Arduino and mbed. It also comes with micro SD slot and 4 MB flash so you could add it easily to your projects with this 2.8" TFT Touch screen.

The Specifications

Model: DM-TFT28-105

Display size: 2.8"

Operating voltage: 3.3V or 5V

Resolution ratio: 240x320

Communication Interface: SPI

Flash memory: 4MB

Operating temperature: -10~70℃

Support micro-SD card

Support Arduino

Size: 55*70 (W*H)mm

Viewing area:43.2*57.6 (W*H)mm

Weight: 40g


Pinout

TFT, Touch, SD-card and external flash memory pin mapping

Pin Definitions


Make your list:

Hardware components:

Arduino UNO & Genuino UNO × 1

DFRobot's 2.8" TFT Touch Shield x 1


Software apps and online services:

Arduino IDE

DFRobot Image Converter


Hand tools and fabrication machines:

Stylus (generic)


Bubble Display

First download the DmTftLibrary from dmtftlibrary.

Extract the content to your Arduino library folder. In Windows this is usually located in Arduino IDE folder\libraries. Check Arduino's official guide if you want more information on how to install the Arduino Library. The official guide of Arduino.

Start Arduino IDE, open the sample code, click "File--> Examples-> DmTftLibraries", select the right board and COM port: DM-TFT28-105

Open the Example and upload to your Arduino board.

Graphic Display Example

In the sketch -

tft.drawString(5, 10, "Romantic cabin"); 

It sends command to print "Romantic Cabin" 5 px away on x-axis and 10 px from y-axis. You can also print a only text on screen ( if you give proper distance of 20 px between each line.

tft.drawLine(x, y, x-80, y+30, YELLOW );

It gives command to draw line from x and y co-ordinate to x-80 and y+30 co-ordinate. It also specifies that the color of line is yellow. (Remember to enter color in CAPS or HEX code only). Understanding it is easy but easier if you know basic 2D geometry and about Cartesian Plane.

tft.drawRectangle(x-40, y+50,x-20, y+70, 0x8418); 

It gives command to draw a rectangle where corner points and color of it's outline are given as the arguments.

tft.fillCircle(x+100, y-30, 20, RED );

This one fills color in the circle whose center and color to be filled are given as arguments.


The Drawing Board

Use stylus and don't swipy movements to prevent unwanted dots on screen

We can make a home made digital drawing board in just a few minutes. All we need are Arduino and this TFT shield.


How:

Stack the shield on the Arduino.

Connect the Arduino with the laptop and upload the sketch for drawing board.

Wait for the screen to turn black.

Now, swipe your finger on the screen and a line will be drawn. I didn't have but I would prefer you to use a stylus.

In the sketch

dmTouch.readTouchData(x, y, touched);

It reads the x and y co-ordinates of the point touched.

tft.fillCircle(x, y, 2, BLUE);

It draws a point on the area where x and y co-ordinates belong to.

The syntax is:

<object_name>.fillCircle(x ,y, <point thickness>, <point color>);

Moreover, you can draw on it and to clear it, use the reset button on the screen to clear the graphics.


Display a pictures from a SD card

It requires a special format for the displaying picture: 16bit RGBRGB bmp. You could download the convert tool here: ImageConverter

Why 16 bit, not 24 bit?

There are 2 reasons for converting your images to 16-bit bmp with flipped row order:

Size: The image is only 2/3 of the original size and because most embedded displays/drivers uses 16-bit color, the picture quality will be the same.

Speed: Most embedded TFT uses the 16-bit 565 format and support the top-bottom write order.


a) The MCU does not need to convert the 24-bit to 16-bit 565, this is already done.

b) If the source format is already in correct order, a faster algorithm for reading the data can be used.

c) There is less data to read (because the image is only 2/3 of an original size)

Compared to raw picture data, there are several advantages:

a) It has meta data about the format, size, how it is stored etc. With raw data, the program must already know this.

b) It is a standard so it can be viewed directly in most viewer (although not all viewer)

c) Can create and edit the images in advanced photo editing programs like photoshop.

Anyway, there is converted picture in the library folder (DmTftLibrary\examples\DM-TFT28-105). You could have a try with it first.


Copy the converted picture to the SD.

Plug SD card in the touch screen.

Upload the sketch given in the attachments.

Note: Install the SPIFlash library before you compile the sketch. Click here to download it form the GitHub.


Code

Github

chauhannaman98 / ArduinoCodes

Arduino sketches made so far and are used in the projects. — Read More

https://www.hackster.io/techmirtz

Latest commit to the master branch on 7-28-2019           Download as Zip


This article copied from hackster.io, Author: Techmirtz