PWM audio now works using either CPU or DMA in part9-sound demo

This commit is contained in:
Adam Greenwood-Byrne 2021-02-17 23:40:03 +00:00
parent 8b7079608f
commit 4386a4ca32
3 changed files with 99 additions and 26 deletions

View file

@ -10,5 +10,5 @@ This code was derived from [Peter Lemon's work](https://github.com/PeterLemon/Ra
Todo
----
* Write-up the CPU-driven `playaudio_cpu()` code, explaining clocks/PWM etc.
* Code the DMA version so we don't have to tie up the CPU
* 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)

View file

@ -1,4 +1,6 @@
#define PERIPHERAL_BASE 0xFE000000
#define LEGACY_BASE 0x7E000000
#define SAFE_ADDRESS 0x00400000 // Somewhere safe to store a lot of data
void uart_init();
void uart_writeText(char *buffer);

View file

@ -1,14 +1,19 @@
#include "fb.h"
#include "io.h"
#define PWM_BASE (PERIPHERAL_BASE + 0x20C000 + 0x800) /* PWM1 register base address on RPi4 */
#define CLOCK_BASE (PERIPHERAL_BASE + 0x101000)
#define PWM_BASE (PERIPHERAL_BASE + 0x20C000 + 0x800) /* PWM1 register base address on RPi4 */
#define PWM_LEGACY_BASE (LEGACY_BASE + 0x20C000 + 0x800) /* PWM1 register base legacy address on RPi4 */
#define CLOCK_BASE (PERIPHERAL_BASE + 0x101000)
#define DMA_BASE (PERIPHERAL_BASE + 0x007100) /* DMA register base address */
#define DMA_ENABLE (DMA_BASE + 0xFF0) /* DMA global enable bits */
#define BCM2711_PWMCLK_CNTL 40
#define BCM2711_PWMCLK_DIV 41
#define PM_PASSWORD 0x5A000000
#define BCM2711_PWM_CONTROL 0
#define BCM2711_PWM_STATUS 1
#define BCM2711_PWM_DMAC 2
#define BCM2711_PWM0_RANGE 4
#define BCM2711_PWM0_DATA 5
#define BCM2711_PWM_FIFO 6
@ -17,53 +22,109 @@
#define BCM2711_PWM1_USEFIFO 0x2000 /* Data from FIFO */
#define BCM2711_PWM1_ENABLE 0x0100 /* Channel enable */
#define BCM2711_PWM0_USEFIFO 0x0020 /* Data from FIFO */
#define BCM2711_PWM0_ENABLE 0x0001 /* Channel enable */
#define BCM2711_PWM_ENAB 0x80000000 /* PWM DMA Configuration: DMA Enable (bit 31 set) */
#define BCM2711_GAPO2 0x20
#define BCM2711_GAPO1 0x10
#define BCM2711_RERR1 0x8
#define BCM2711_WERR1 0x4
#define BCM2711_FULL1 0x1
#define PM_PASSWORD 0x5A000000
#define ERRORMASK (BCM2711_GAPO2 | BCM2711_GAPO1 | BCM2711_RERR1 | BCM2711_WERR1)
#define DMA_CS 0 /* Control/status register offset for DMA channel 0 */
#define DMA_CONBLK_AD 1
#define DMA_EN1 1 << 1 /* Enable DMA engine 1 */
#define DMA_ACTIVE 1 /* Active bit set */
#define DMA_DEST_DREQ 0x40 /* Use DREQ to pace peripheral writes */
#define DMA_PERMAP_1 0x10000 /* PWM1 peripheral for DREQ */
#define DMA_SRC_INC 0x100 /* Increment source address */
volatile unsigned* clk = (void*)CLOCK_BASE;
volatile unsigned* pwm = (void*)PWM_BASE;
volatile unsigned* dma = (void*)DMA_BASE;
volatile unsigned* dmae = (void*)DMA_ENABLE;
volatile unsigned* safe = (void*)SAFE_ADDRESS;
static void audio_init(void)
struct dma_cb {
unsigned int ti;
unsigned int source_ad;
unsigned int dest_ad;
unsigned int txfr_len;
unsigned int stride;
unsigned int nextconbk;
unsigned int null1;
unsigned int null2;
} __attribute__((aligned(32)));
struct dma_cb playback_cb;
static void playaudio_dma(void)
{
gpio_useAsAlt0(40); // Ensure PWM1 is mapped to GPIO 40/41
gpio_useAsAlt0(41);
extern unsigned char _binary_audio_bin_start[];
extern unsigned char _binary_audio_bin_size[];
unsigned int size = (long)&_binary_audio_bin_size;
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.txfr_len = size * 4; // They're unsigned ints now, not unsigned chars
playback_cb.stride = 0x00;
playback_cb.nextconbk = 0x00;
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;
*(dma+DMA_CONBLK_AD) = (long)&playback_cb; // checked and correct
wait_msec(2);
*(dma+DMA_CS) = DMA_ACTIVE;
}
static void audio_init(void)
{
gpio_useAsAlt0(40); // Ensure PWM1 is mapped to GPIO 40/41
gpio_useAsAlt0(41);
wait_msec(2);
// Setup clock
*(clk + BCM2711_PWMCLK_CNTL) = PM_PASSWORD | (1 << 5); // Stop clock
wait_msec(2);
int idiv = 2;
*(clk + BCM2711_PWMCLK_DIV) = PM_PASSWORD | (idiv<<12);
*(clk + BCM2711_PWMCLK_CNTL) = PM_PASSWORD | 16 | 1; // Enable + oscillator
*(clk + BCM2711_PWMCLK_CNTL) = PM_PASSWORD | 16 | 1; // osc + enable
wait_msec(2);
// Disable PWM
*(pwm + BCM2711_PWM_CONTROL) = 0;
// Setup PWM
*(pwm + BCM2711_PWM_CONTROL) = 0;
wait_msec(2);
*(pwm+BCM2711_PWM0_RANGE) = 0x264; // 44.1khz, Stereo, 8-bit (54Mhz / 44100 / 2)
*(pwm+BCM2711_PWM1_RANGE) = 0x264;
*(pwm+BCM2711_PWM_CONTROL) =
BCM2711_PWM1_USEFIFO |
BCM2711_PWM1_USEFIFO |
BCM2711_PWM1_ENABLE |
BCM2711_PWM0_USEFIFO |
BCM2711_PWM0_ENABLE | 1<<6;
wait_msec(2);
}
void playaudio_cpu()
@ -77,6 +138,8 @@ void playaudio_cpu()
unsigned int size = (long)&_binary_audio_bin_size;
unsigned char *data = &(_binary_audio_bin_start[0]);
// Write data out to FIFO
while (i < size) {
status = *(pwm + BCM2711_PWM_STATUS);
if (!(status & BCM2711_FULL1)) {
@ -86,9 +149,6 @@ void playaudio_cpu()
i++;
}
if ((status & ERRORMASK)) {
debugstr("error: ");
debughex(status);
debugcrlf();
*(pwm+BCM2711_PWM_STATUS) = ERRORMASK;
}
}
@ -98,13 +158,24 @@ void main()
{
fb_init();
debugstr("Initialising audio... ");
debugstr("Initialising audio unit... ");
audio_init();
debugstr("done"); debugcrlf();
debugstr("Starting to play sample using CPU"); debugcrlf();
debugstr("Playing sample using CPU... ");
playaudio_cpu();
debugstr("CPU finished playing sample"); debugcrlf();
debugstr("done"); debugcrlf();
while (1);
debugstr("Playing sample using DMA... ");
playaudio_dma();
debugstr("done"); debugcrlf();
debugstr("main() running still... ");
while (*(dma+DMA_CS) & 0x1) { // Wait for DMA transfer to finish - we could do anything here instead!
debugstr("x");
wait_msec(0x100000);
}
debugstr(" ----> finished");
while(1);
}