I study some analogue electronics and digital circuit design as part of the Computer Science degree I am studying at the University of York and decided that converting an old Casio keyboard that was lying around into a MIDI one would be a good way to further my knowledge.
My implementation of the keyboard has a number of additional features:
I haven't yet bothered to add any audio output because I'm more than happy with plugging it into my computer.
I have documented most of the electronics side of things on my RaspberryPiTutorials YouTube channel:
Once I had everything working on an Arduino (see demo), I decided to move it onto a more permanent stripboard implementation. Instructions on moving the Arduino to a breadboard are here, and the pin mapping for the Arduino Uno to the actual ATMEGA chip is here.
You can find my code (including schematic files for Fritzing) here. The schematic as an SVG file is available here. The schematic I've made doesn't include the parts required for the standalone Arduino.
My stripboard implementation of the schematic:
The ATMEGA328 portion of the implementation:
This chip makes up the heart of the Arduino. It can be removed from the chip holder and put back on the Arduino to program them. The chips only cost about £3, which means you can buy another and then have a functioning Arduino again.
One crucial part of the standalone Arduino is to include decoupling (or bypass) capacitors, to filter noise from the power supply and other parts of the circuit. I didn't include these initially, and the Arduino behaved eratically. I used 0.1uF capacitors. There are two of these on the left hand side, and one on the right hand side. The remaining two capacitors on the right are 22pF, and used to stabilise the 16MHz crystal.
As I added more features to the keyboard, I noticed that it started to behave strangely. It turns out I was running out of memory on the Arduino. There is only 2kB of it, which means you have to be very strategic about how you use it. The Arduino webpage about memory is here. I had to make a number of changes such as:
const int foo = 5; to #define macros, such as
#define foo 5. This means that instead of storing the variable in ram, the compiler replaces each instance of the name with the value at compile time. This effectively hard codes them into the program.
uint8_t, so I used that where appropriate. You can read about the various data types available here.