The purpose of this project was to create a sound player that can play high quality sound using nothing but a single chip (plus an SD card for data storage).
The chosen microcontroller was the PIC12F1840. It was chosen because of its fast clock rate (up to 32 MHz). Since microchip PICs have this atrocious property of executing one instruction every four clock cycles, this results in 8MIPS performance. The second reason for the choice was the hardware SPI module - bit-banging SPI is not hard, BUT doing it fast enough to sustain the sound playback would be - the hardware SPI module helps by allowing the SPI clock to be as high at 8MHz without using nearly as much CPU time as bit-banging would. The reset of the circuit design was easy: the PIC outputs a PWM waveform to drive the speaker, which is amplified by a MOSFET, PIC's SPI controller talks to the SD card, and another GPIO is used to provide power to te SD card allowing it to be powered off and thus letting the circuit sleep in very low power mode (nanowatts of power used). The pic is overclocked using the OSCTUNE register to 33MHz from the stock speed of 32MHz.
The SD card driver is my own work, and supports SD/MMC/SDHC/RS-MMC/HS-MMC/miniSD/microSD/transFlash and whatever others are in that family. Card is initialized at 375KHz (which is within spec), and then the bus is clocked at 8MHz for data reads/writes. The driver is peculiar for a few reasons, mainly because it does not try to be an abstraction between the SPI bus and the caller, thus the driver will init the card, and set it up to stream sectors to the caller. The caller then calls spiByte() directly to read sector data. Every 512 calls, a call to sdNextSector() is needed to notify the SD driver that we want the next sector & properly wait for it to start.
Since there is some time between the end of streaming of one sector and beginning of streaming the other, buffering is used for audio data. The buffer is a simple circular buffer that the audio thread reads from, and the SD thread writes to. When buffer is full, SD thread busy-waits for a slot to open to write data into. Since SD cards do not care how much time passes between clock ticks, this wait can be as long as needed.
Audio playback itself is relatively simple. PWM module is used to output the waveworm. Its frequency is 1MHz or so (adjustable) and the duty cycle is the needed audio amplitude. Currently 6 bits of data are used (bottom 2 are truncated). This produces pretty good quality sound with deep lows and nice crisp highs. Timer0 overflow interrupt is used to determine when to change the PWM duty cycle to the next audio sample's value. Duty cycles are kept at below 50% always, since anything above that causes distortion. Audio thread does NOT check for buffer underflow, and instead just keeps re-playing the buffer over and over. Needless to say we avoid this by filling the buffer in a timely fashion. The function audioOn() turns on the audio thread and audioOff() turns it off.
The random number geneartor used is a very simple 32-bit multiplication-addition-modulus type, where Xn = (Xn-1 * 0xDEECE66D + 0x0B) % 0x100000000. The seed is stored in the last 4 bytes of EEPROM, so that on each power-on the sequence of sonds is not repated, but continued from the last time. The top 16 bits of the modified seed are returned as the "random" number to the caller.
Given all of the above, the method of operation becomes simple:
Power on SD card
Init SD card
List files in root directory
Count number of non-hidden files with "WAV" extension
Loop:
Select a file randomly
Use uFAT to find the sector extents file occupies (fragmentation is properly handled)
Store those sectors in the sectorList storage
Init audio playback
Play data from sectors in each extent until there is no more
turn audio off
enter low power sleep for a determined time (30 sec currently, random length in the future)
power card on
[Re-] init card
Detailed PIC resource usage:
Clocks: 33MHz when playing, 500KHz when waking from sleep before deciding to sleep more
WDT: Used to wake from sleep
Reference Clock Module: Unused
Interrupts: Timer0 overflow interrupt used, others unused
Data EEPROM: 4 bytes at end used, others unused
Program Flash: Code uses 40%, sectorList uses 45%
GPIOs: 3 used for SPI, 1 used for output, 1 used for SD card power control. RA3 unused.
Fixed VoltageReference: Unused
Temperature indicator: Unused
ADC: Unused
DAC: Unused
SR Latch: Unused
Comparator: Unused
Timer0: Used for sound playback timing, overflows 22050 times a second, with proper reload value
Timer1: Unused
Timer2: Used for PWM module's timing
Data Signal Modulator: Unused
PWM: used to produce output sound
MSSP: used in SPI master mode to talk to the SD card
EUSART: Unused
Capacitative Sensing Module: Unused
As can be seen in the video here, it all works rather well. To address a few final questions: Audio files are uncompressed WAV files. The cde attempts to figure out the specific format and play nice with it, but 8 bit samples work best. SD card must be formatted FAT16 since uFAT does not support FAT12 or FAT32. This may (and likely will) change in the future. The maximum number of file fragments is set in the code, and is something like 128 currently, any more than that will not be played, or will cause data corruption, so defragment your card sometimes, if you plan to replace sound files often. Code cleanup is on the way, and the code will be released after cleanup is complete. For now, here's the HEX file for the PIC12F1840: [DOWNLOAD]. Source (current snapshot) can be downloaded [HERE]
The chosen microcontroller was the PIC12F1840. It was chosen because of its fast clock rate (up to 32 MHz). Since microchip PICs have this atrocious property of executing one instruction every four clock cycles, this results in 8MIPS performance. The second reason for the choice was the hardware SPI module - bit-banging SPI is not hard, BUT doing it fast enough to sustain the sound playback would be - the hardware SPI module helps by allowing the SPI clock to be as high at 8MHz without using nearly as much CPU time as bit-banging would. The reset of the circuit design was easy: the PIC outputs a PWM waveform to drive the speaker, which is amplified by a MOSFET, PIC's SPI controller talks to the SD card, and another GPIO is used to provide power to te SD card allowing it to be powered off and thus letting the circuit sleep in very low power mode (nanowatts of power used). The pic is overclocked using the OSCTUNE register to 33MHz from the stock speed of 32MHz.
The SD card driver is my own work, and supports SD/MMC/SDHC/RS-MMC/HS-MMC/miniSD/microSD/transFlash and whatever others are in that family. Card is initialized at 375KHz (which is within spec), and then the bus is clocked at 8MHz for data reads/writes. The driver is peculiar for a few reasons, mainly because it does not try to be an abstraction between the SPI bus and the caller, thus the driver will init the card, and set it up to stream sectors to the caller. The caller then calls spiByte() directly to read sector data. Every 512 calls, a call to sdNextSector() is needed to notify the SD driver that we want the next sector & properly wait for it to start.
Since there is some time between the end of streaming of one sector and beginning of streaming the other, buffering is used for audio data. The buffer is a simple circular buffer that the audio thread reads from, and the SD thread writes to. When buffer is full, SD thread busy-waits for a slot to open to write data into. Since SD cards do not care how much time passes between clock ticks, this wait can be as long as needed.
Audio playback itself is relatively simple. PWM module is used to output the waveworm. Its frequency is 1MHz or so (adjustable) and the duty cycle is the needed audio amplitude. Currently 6 bits of data are used (bottom 2 are truncated). This produces pretty good quality sound with deep lows and nice crisp highs. Timer0 overflow interrupt is used to determine when to change the PWM duty cycle to the next audio sample's value. Duty cycles are kept at below 50% always, since anything above that causes distortion. Audio thread does NOT check for buffer underflow, and instead just keeps re-playing the buffer over and over. Needless to say we avoid this by filling the buffer in a timely fashion. The function audioOn() turns on the audio thread and audioOff() turns it off.
The random number geneartor used is a very simple 32-bit multiplication-addition-modulus type, where Xn = (Xn-1 * 0xDEECE66D + 0x0B) % 0x100000000. The seed is stored in the last 4 bytes of EEPROM, so that on each power-on the sequence of sonds is not repated, but continued from the last time. The top 16 bits of the modified seed are returned as the "random" number to the caller.
Given all of the above, the method of operation becomes simple:
Power on SD card
Init SD card
List files in root directory
Count number of non-hidden files with "WAV" extension
Loop:
Select a file randomly
Use uFAT to find the sector extents file occupies (fragmentation is properly handled)
Store those sectors in the sectorList storage
Init audio playback
Play data from sectors in each extent until there is no more
turn audio off
enter low power sleep for a determined time (30 sec currently, random length in the future)
power card on
[Re-] init card
Detailed PIC resource usage:
Clocks: 33MHz when playing, 500KHz when waking from sleep before deciding to sleep more
WDT: Used to wake from sleep
Reference Clock Module: Unused
Interrupts: Timer0 overflow interrupt used, others unused
Data EEPROM: 4 bytes at end used, others unused
Program Flash: Code uses 40%, sectorList uses 45%
GPIOs: 3 used for SPI, 1 used for output, 1 used for SD card power control. RA3 unused.
Fixed VoltageReference: Unused
Temperature indicator: Unused
ADC: Unused
DAC: Unused
SR Latch: Unused
Comparator: Unused
Timer0: Used for sound playback timing, overflows 22050 times a second, with proper reload value
Timer1: Unused
Timer2: Used for PWM module's timing
Data Signal Modulator: Unused
PWM: used to produce output sound
MSSP: used in SPI master mode to talk to the SD card
EUSART: Unused
Capacitative Sensing Module: Unused
As can be seen in the video here, it all works rather well. To address a few final questions: Audio files are uncompressed WAV files. The cde attempts to figure out the specific format and play nice with it, but 8 bit samples work best. SD card must be formatted FAT16 since uFAT does not support FAT12 or FAT32. This may (and likely will) change in the future. The maximum number of file fragments is set in the code, and is something like 128 currently, any more than that will not be played, or will cause data corruption, so defragment your card sometimes, if you plan to replace sound files often. Code cleanup is on the way, and the code will be released after cleanup is complete. For now, here's the HEX file for the PIC12F1840: [DOWNLOAD]. Source (current snapshot) can be downloaded [HERE]
No comments:
Post a Comment