Finished part9 documentation and included new audio sample

This commit is contained in:
Adam Greenwood-Byrne 2021-02-18 22:54:49 +00:00
parent eb7bc24d80
commit d454cf2a80
5 changed files with 45 additions and 11 deletions

22
part9-sound/Makefile.gcc Normal file
View file

@ -0,0 +1,22 @@
CFILES = $(wildcard *.c)
OFILES = $(CFILES:.c=.o)
GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
GCCPATH = ../../gcc-arm-9.2-2019.12-x86_64-aarch64-none-elf/bin
all: clean kernel8.img
boot.o: boot.S
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o
audio.o : audio.bin
$(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@
%.o: %.c
$(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@
kernel8.img: boot.o $(OFILES) audio.o
$(GCCPATH)/aarch64-none-elf-ld -nostdlib -nostartfiles boot.o $(OFILES) audio.o -T link.ld -o kernel8.elf
$(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img
clean:
/bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true

View file

@ -7,8 +7,8 @@ One thing our game is missing is the excitement of sound! Some beeps and squeaks
I wrote this code as I referenced [Peter Lemon's work](https://github.com/PeterLemon/RaspberryPi/tree/master/Sound/PWM/8BIT/44100Hz/Stereo/CPU), for which I am very grateful. It did need some significant modification to work on the Raspberry Pi 4 hardware.
Design goals
------------
Design goal
-----------
Perhaps most importantly, we must be able to play sounds in the background. If our audio playback ties up the CPU, then gameplay will stop whilst the sound is playing. I think any player would be immediately put off by the rude intrusion into their adventure!
One solution for this is to implement multi-tasking, thereby making use of the four CPU cores (so far we've only used one). This is no small feat, and a big commitment for a fews beeps and squeaks.
@ -40,7 +40,7 @@ To convert back to our binary format, do this:
`ffmpeg -i audio.wav -f u8 -ar 44.1k -ac 2 audio.bin`
This should help you try the code with your own audio samples!
This should help you try the code with your own audio samples! This one is a short excerpt of [me](https://isometim.es) singing (argh!), with my wife playing woodwind. As an aside, I highly recommend checking out [the original track](https://www.youtube.com/watch?v=k1UoUNC3Wj0).
Testing playback using the CPU and PWM module
---------------------------------------------
@ -62,7 +62,16 @@ The code is fairly self-documenting. We essentially check the FIFO buffer isn't
And that's it... The PWM will pick up the digital data in the buffer and send it, PWM-style (faking an analogue signal), at the right speed (thanks to our clock divisor/PWM range mastery), to the audio jack.
Todo
----
* Write-up the DMA version `playaudio_dma()` version, which doesn't tie up the CPU but still plays sound!
* Add a Makefile.gcc (I'm using LLVM Clang these days, so not a priority)
Doing it with DMA
-----------------
Our first challenge is that DMA transfers and FIFO registers are 4 bytes wide, so our single byte samples need some zero-padding. Because `data` is an `unsigned char *` and `safe` is (technically) an `unsigned int *`, we can do this in C with a simple for loop. You'll notice that `safe` is a memory location in RAM which we know to be beyond our program code (`SAFE_ADDRESS` is defined in _io.h_).
We then set up the DMA Control Block. This is an in-memory structure that will tell the DMA engine everything it needs to know to perform the transfer. This is very well-documented already, but worth pointing out is `DMA_DEST_DREQ` and `DMA_PERMAP_1`. These settings ensure we use our previously-set hardware clock to 'pace' the transfer. If we didn't do this, the DMA engine would just get it done as fast as it could (and our audio wouldn't sound great!). `SRC_INC` simply tells the DMA engine to increment the source address throughout the transfer. We'll be keeping the destination address constant since we want it to always point to the PWM module's FIFO input. Note also the use of `PWM_LEGACY_BASE` rather than `PWM_BASE` to address this peripheral memory. This is another quirk of the Raspberry Pi 4 hardware!
Note finally how we set `.nextconbk` to 0x00. This tells the DMA engine that there is no more work to do after this job is complete. If we wanted to loop the audio sample infinitely, we could simply set this to address the same control block structure again!
As we enable the DMA engine, playback begins. Notably, however, we're returned to `main()` immediately and the CPU can get on with other things, thereby meeting our design goal.
Conclusion
----------
We're now ready to integrate sound into our Breakout game using background DMA transfers! Coming soon...

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -69,23 +69,26 @@ static void playaudio_dma(void)
unsigned char *data = &(_binary_audio_bin_start[0]);
// Convert data
for (int i=0;i<size;i++) *(safe+i) = *(data+i);
wait_msec(2);
// Set up control block
playback_cb.ti = DMA_DEST_DREQ + DMA_PERMAP_1 + DMA_SRC_INC;
playback_cb.source_ad = SAFE_ADDRESS;
playback_cb.dest_ad = PWM_LEGACY_BASE + 0x18; // Should be PWM_FIFO but it's an absolute offset so...
playback_cb.dest_ad = PWM_LEGACY_BASE + 0x18; // Points to PWM_FIFO
playback_cb.txfr_len = size * 4; // They're unsigned ints now, not unsigned chars
playback_cb.stride = 0x00;
playback_cb.nextconbk = 0x00;
playback_cb.nextconbk = 0x00; // Don't loop
playback_cb.null1 = 0x00;
playback_cb.null2 = 0x00;
wait_msec(2);
// Enable DMA
*(pwm+BCM2711_PWM_DMAC) =
BCM2711_PWM_ENAB + 0x0707; // Bits 0-7 Threshold For DREQ Signal = 1, Bits 8-15 Threshold For PANIC Signal = 0
*dmae = DMA_EN1;
@ -109,7 +112,7 @@ static void audio_init(void)
int idiv = 2;
*(clk + BCM2711_PWMCLK_DIV) = PM_PASSWORD | (idiv<<12);
*(clk + BCM2711_PWMCLK_CNTL) = PM_PASSWORD | 16 | 1; // osc + enable
*(clk + BCM2711_PWMCLK_CNTL) = PM_PASSWORD | 16 | 1; // Osc + Enable
wait_msec(2);
// Setup PWM