This project is creating a soundchip out of an ATtiny85. Written entirely in assembly, it is small enough to fit even onto an ATtiny25/45.
The registers themselves:
___________ _______ _______________________________________________________________
| | Bit → | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| Index | Name |=======|=======|=======|=======|=======|=======|=======|=======|
| 0x0n | PILOX | Pitch increment value for tone on channel X |
|(n = 0..4,X = A..E)|_______________________________________________________________|
| 0x05 | PILON | Pitch increment value for noise generator |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x06 | PHIAB |Ch.B PR|Channel B octave number|Ch.A PR|Channel A octave number|
| 0x07 | PHICD |Ch.D PR|Channel D octave number|Ch.C PR|Channel C octave number|
| 0x08 | PHIEN |NoisePR|Noise gen octave number|Ch.E PR|Channel E octave number|
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x0n | DUTYX | Channel X tone duty cycle |
|(n = 9..D,X = A..E)| |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x0E | NTPLO | Noise LFSR inversion value (low byte) |
| 0x0F | NTPHI | Noise LFSR inversion value (high byte) |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1n | VOL_X | Channel X static volume |
|(n = 0..4,X = A..E)| |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1n | CFG_X | Noise | Envel.| ===== | Slot | Right volume | Left volume |
|(n = 5..9,X = A..E)| Enable| Enable| ===== | number| | |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1A | ELDLO | Low byte of envelope phase load value |
| 0x1B | ELDHI | High byte of envelope phase load value |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1C | E_SHP |EnvB PR| Envelope B shape |EnvA PR| Envelope A shape |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1D | EPLOA | Pitch increment value for envelope A |
| 0x1E | EPLOB | Pitch increment value for envelope B |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
| 0x1F | EPIHI | Envelope B octave num | Envelope A octave num |
|===========|=======|=======|=======|=======|=======|=======|=======|=======|=======|
|___________|_______|_______|_______|_______|_______|_______|_______|_______|_______|
Notes:
NTPHI
is 0x24, sorta corresponding to the AY-3-8910CFG_X
is 0x0F, corresponding to maximum panning volume on both sidesCFG_X
are used. It is recommended to set the right panning bits to the same contents as the left panning bits for compatibility with future versions.CFG_X
is reserved and must be left at 0 for compatibility with future versions.The firmware is located in the avr folder, and is entirely written in AVR assembly. Its size is currently under 2 Kibibytes, therefore it can fit onto an ATtiny25 and '45 in addition to the '85. It is assembled with the avra assembler by running the command make firmware
or make avr
, which puts the resulting main.hex
file into the bin/avr
folder.
The pin connection diagram is:
__ __
/Reset -1|° U |8- VCC
CS -2| |7- SPI CLK
OUT -3| |6- SPI DO (MOSI)
GND -4|_____|5- SPI DI (MISO)
Pins 2, 5, 6 and 7 are used for communicating with the register write buffer via SPI. The ATtiny is the master of SPI, checking for new register writes once every sample. The connection is: | CS |
Function |
---|---|---|
0 | Fetch from register write storage | |
1 | No SPI |
Currently, the only available output option is PWM output through the OUT
pin. The PWM rate is 2× the sample rate, aka the master clock divided by 256.
1 register write is 16 bits long and must work like this:
The ATtiny85APU automatically flushes 1 register write per sample, the SPI CLK speed is ½ of the master clock speed (e.g. 4 MHz SPI CLK speed at 8 MHz clock speed). Order of transfer is MSB first.
Due to all of the pins being busy, the ATtiny85APU cannot receive an external clock signal. Therefore, it only has 2 clock source options:
CKDIV8
fuse (set by default, it is absolutely recommended to disable this for the sake of your ears)The emulator is located in the emu folder. It is written in C99. Features:
T85APU_REGWRITE_BUFFER_SIZE
defineFor more info check out the t85apu.h and t85apu.hpp files. The emulator also provides useful register defines in the t85apu_regdefines.h file.
To use it manually, copy the emu folder into your project, and include the t85apu.h
file in C, or t85apu.hpp
if you want to use the C++ wrapper. Then, compile the t85apu.c file as a library and link it to the final executable.
The emulator also has a CMake API, which makes including it in your project much easier.
You can just copy the emu folder into your project, and use add_subdirectory()
to add it to your CMakeLists.txt
:
add_subdirectory(<your name of the emu folder>)
target_link_libraries(<your_executable_name> PRIVATE t85apu_emu)
Then you can include the aforementioned header files in your code.
FetchContent
You can also just use FetchContent
to include the emulator in your project. Here's an example of what to add to your CMakeLists.txt
:
include(FetchContent)
FetchContent_Declare(t85apu
GIT_REPOSITORY https://github.com/ADM228/ATtiny85APU.git
GIT_TAG main)
set(T85APU_REGWRITE_BUFFER_SIZE 1)
FetchContent_MakeAvailable(t85apu)
target_link_libraries(<your_executable_name> PRIVATE t85apu_emu)
There is an example program provided together with the emulator in C and C++. It is located in the examples folder and illustrates how to use the soundchip and the emulator API:
The examples are not built by default as they have the EXCLUDE_FROM_ALL
flag enabled, so you don't have to worry about them bloating your software. To build them, you have to explicitly select the example_c
and/or example_cpp
targets in CMake. The executables will appear in the examples
subfolder of where the CMake Cache is.