diff core/PRU.cpp @ 0:8a575ba3ab52

Initial commit.
author andrewm
date Fri, 31 Oct 2014 19:10:17 +0100
parents
children a6beeba3a648
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/PRU.cpp	Fri Oct 31 19:10:17 2014 +0100
@@ -0,0 +1,469 @@
+/*
+ * PRU.cpp
+ *
+ * Code for communicating with the Programmable Realtime Unit (PRU)
+ * on the BeagleBone AM335x series processors. The PRU loads and runs
+ * a separate code image compiled from an assembly file. Here it is
+ * used to handle audio and SPI ADC/DAC data.
+ *
+ * This code is specific to the PRU code in the assembly file; for example,
+ * it uses certain GPIO resources that correspond to that image.
+ *
+ *  Created on: May 27, 2014
+ *      Author: andrewm
+ */
+
+#include "../include/PRU.h"
+#include "../include/prussdrv.h"
+#include "../include/pruss_intc_mapping.h"
+#include "../include/GPIOcontrol.h"
+#include "../include/render.h"
+
+#include <iostream>
+#include <stdlib.h>
+#include <cstdio>
+#include <cerrno>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+// Xenomai-specific includes
+#include <sys/mman.h>
+#include <native/task.h>
+#include <native/timer.h>
+#include <rtdk.h>
+
+using namespace std;
+
+#define PRU_MEM_MCASP_OFFSET 0x2000  // Offset within PRU-SHARED RAM
+#define PRU_MEM_MCASP_LENGTH 0x2000  // Length of McASP memory, in bytes
+#define PRU_MEM_DAC_OFFSET 0x0     // Offset within PRU0 RAM
+#define PRU_MEM_DAC_LENGTH 0x2000  // Length of ADC+DAC memory, in bytes
+#define PRU_MEM_COMM_OFFSET 0x0    // Offset within PRU-SHARED RAM
+
+#define PRU_SHOULD_STOP 	0
+#define PRU_CURRENT_BUFFER  1
+#define PRU_BUFFER_FRAMES   2
+#define PRU_SHOULD_SYNC     3
+#define PRU_SYNC_ADDRESS    4
+#define PRU_SYNC_PIN_MASK   5
+#define PRU_LED_ADDRESS		6
+#define PRU_LED_PIN_MASK	7
+#define PRU_FRAME_COUNT		8
+#define PRU_USE_SPI			9
+
+#define PRU_SAMPLE_INTERVAL_NS 45351	// 22050Hz per SPI sample = 45.351us
+
+#define GPIO0_ADDRESS 		0x44E07000
+#define GPIO1_ADDRESS 		0x4804C000
+#define GPIO_SIZE			0x198
+#define GPIO_CLEARDATAOUT 	(0x190 / 4)
+#define GPIO_SETDATAOUT 	(0x194 / 4)
+
+#define TEST_PIN_GPIO_BASE	GPIO0_ADDRESS	// Use GPIO0(31) for debugging
+#define TEST_PIN_MASK		(1 << 31)
+#define TEST_PIN2_MASK		(1 << 26)
+
+#define USERLED3_GPIO_BASE  GPIO1_ADDRESS // GPIO1(24) is user LED 3
+#define USERLED3_PIN_MASK   (1 << 24)
+
+const unsigned int PRU::kPruGPIODACSyncPin = 5;	// GPIO0(5); P9-17
+const unsigned int PRU::kPruGPIOADCSyncPin = 48; // GPIO1(16); P9-15
+
+const unsigned int PRU::kPruGPIOTestPin = 60;	// GPIO1(28); P9-12
+const unsigned int PRU::kPruGPIOTestPin2 = 31;	// GPIO0(31); P9-13
+const unsigned int PRU::kPruGPIOTestPin3 = 26;	// GPIO0(26); P8-14
+
+extern int gShouldStop;
+extern int gRTAudioVerbose;
+
+// Constructor: specify a PRU number (0 or 1)
+PRU::PRU()
+: pru_number(0), running(false), spi_enabled(false), gpio_enabled(false), led_enabled(false),
+  gpio_test_pin_enabled(false), xenomai_gpio_fd(-1), xenomai_gpio(0)
+{
+
+}
+
+// Destructor
+PRU::~PRU()
+{
+	if(running)
+		disable();
+	if(gpio_enabled)
+		cleanupGPIO();
+	if(xenomai_gpio_fd >= 0)
+		close(xenomai_gpio_fd);
+}
+
+// Prepare the GPIO pins needed for the PRU
+// If include_test_pin is set, the GPIO output
+// is also prepared for an output which can be
+// viewed on a scope. If include_led is set,
+// user LED 3 on the BBB is taken over by the PRU
+// to indicate activity
+int PRU::prepareGPIO(int use_spi, int include_test_pin, int include_led)
+{
+	if(use_spi) {
+		// Prepare DAC CS/ pin: output, high to begin
+		if(gpio_export(kPruGPIODACSyncPin)) {
+			if(gRTAudioVerbose)
+				cout << "Warning: couldn't export DAC sync pin\n";
+		}
+		if(gpio_set_dir(kPruGPIODACSyncPin, OUTPUT_PIN)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set direction on DAC sync pin\n";
+			return -1;
+		}
+		if(gpio_set_value(kPruGPIODACSyncPin, HIGH)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set value on DAC sync pin\n";
+			return -1;
+		}
+
+		// Prepare ADC CS/ pin: output, high to begin
+		if(gpio_export(kPruGPIOADCSyncPin)) {
+			if(gRTAudioVerbose)
+				cout << "Warning: couldn't export ADC sync pin\n";
+		}
+		if(gpio_set_dir(kPruGPIOADCSyncPin, OUTPUT_PIN)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set direction on ADC sync pin\n";
+			return -1;
+		}
+		if(gpio_set_value(kPruGPIOADCSyncPin, HIGH)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set value on ADC sync pin\n";
+			return -1;
+		}
+
+		spi_enabled = true;
+	}
+
+	if(include_test_pin) {
+		// Prepare GPIO test output (for debugging), low to begin
+		if(gpio_export(kPruGPIOTestPin)) {
+			if(gRTAudioVerbose)
+				cout << "Warning: couldn't export GPIO test pin\n";
+		}
+		if(gpio_set_dir(kPruGPIOTestPin, OUTPUT_PIN)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set direction on GPIO test pin\n";
+			return -1;
+		}
+		if(gpio_set_value(kPruGPIOTestPin, LOW)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set value on GPIO test pin\n";
+			return -1;
+		}
+
+		if(gpio_export(kPruGPIOTestPin2)) {
+			if(gRTAudioVerbose)
+				cout << "Warning: couldn't export GPIO test pin 2\n";
+		}
+		if(gpio_set_dir(kPruGPIOTestPin2, OUTPUT_PIN)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set direction on GPIO test pin 2\n";
+			return -1;
+		}
+		if(gpio_set_value(kPruGPIOTestPin2, LOW)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set value on GPIO test pin 2\n";
+			return -1;
+		}
+
+		if(gpio_export(kPruGPIOTestPin3)) {
+			if(gRTAudioVerbose)
+				cout << "Warning: couldn't export GPIO test pin 3\n";
+		}
+		if(gpio_set_dir(kPruGPIOTestPin3, OUTPUT_PIN)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set direction on GPIO test pin 3\n";
+			return -1;
+		}
+		if(gpio_set_value(kPruGPIOTestPin3, LOW)) {
+			if(gRTAudioVerbose)
+				cout << "Couldn't set value on GPIO test pin 3\n";
+			return -1;
+		}
+		gpio_test_pin_enabled = true;
+	}
+
+	if(include_led) {
+		// Turn off system function for LED3 so it can be reused by PRU
+		led_set_trigger(3, "none");
+		led_enabled = true;
+	}
+
+	gpio_enabled = true;
+
+	return 0;
+}
+
+// Clean up the GPIO at the end
+void PRU::cleanupGPIO()
+{
+	if(!gpio_enabled)
+		return;
+	if(spi_enabled) {
+		gpio_unexport(kPruGPIODACSyncPin);
+		gpio_unexport(kPruGPIOADCSyncPin);
+	}
+	if(gpio_test_pin_enabled) {
+		gpio_unexport(kPruGPIOTestPin);
+		gpio_unexport(kPruGPIOTestPin2);
+		gpio_unexport(kPruGPIOTestPin3);
+	}
+	if(led_enabled) {
+		// Set LED back to default eMMC status
+		// TODO: make it go back to its actual value before this program,
+		// rather than the system default
+		led_set_trigger(3, "mmc1");
+	}
+
+	gpio_enabled = gpio_test_pin_enabled = false;
+}
+
+// Initialise and open the PRU
+int PRU::initialise(int pru_num, int frames_per_buffer, bool xenomai_test_pin)
+{
+	uint32_t *pruMem = 0;
+
+	if(!gpio_enabled) {
+		rt_printf("initialise() called before GPIO enabled\n");
+		return 1;
+	}
+
+	pru_number = pru_num;
+
+    /* Initialize structure used by prussdrv_pruintc_intc   */
+    /* PRUSS_INTC_INITDATA is found in pruss_intc_mapping.h */
+    tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
+
+    /* Allocate and initialize memory */
+    prussdrv_init();
+    if(prussdrv_open(PRU_EVTOUT_0)) {
+    	rt_printf("Failed to open PRU driver\n");
+    	return 1;
+    }
+
+    /* Map PRU's INTC */
+    prussdrv_pruintc_init(&pruss_intc_initdata);
+
+    spi_buffer_frames = frames_per_buffer;
+    audio_buffer_frames = spi_buffer_frames * 2;
+
+    /* Map PRU memory to pointers */
+	prussdrv_map_prumem (PRUSS0_SHARED_DATARAM, (void **)&pruMem);
+    pru_buffer_comm = (uint32_t *)&pruMem[PRU_MEM_COMM_OFFSET/sizeof(uint32_t)];
+	pru_buffer_audio_dac = (int16_t *)&pruMem[PRU_MEM_MCASP_OFFSET/sizeof(uint32_t)];
+
+	/* ADC memory starts 2(ch)*2(buffers)*2(samples/spi)*bufsize samples later */
+	pru_buffer_audio_adc = &pru_buffer_audio_dac[8 * spi_buffer_frames];
+
+	if(spi_enabled) {
+		prussdrv_map_prumem (pru_number == 0 ? PRUSS0_PRU0_DATARAM : PRUSS0_PRU1_DATARAM, (void **)&pruMem);
+		pru_buffer_spi_dac = (uint16_t *)&pruMem[PRU_MEM_DAC_OFFSET/sizeof(uint32_t)];
+
+		/* ADC memory starts after 8(ch)*2(buffers)*bufsize samples */
+		pru_buffer_spi_adc = &pru_buffer_spi_dac[16 * spi_buffer_frames];
+	}
+	else {
+		pru_buffer_spi_dac = pru_buffer_spi_adc = 0;
+	}
+
+    /* Set up flags */
+    pru_buffer_comm[PRU_SHOULD_STOP] = 0;
+    pru_buffer_comm[PRU_CURRENT_BUFFER] = 0;
+    pru_buffer_comm[PRU_BUFFER_FRAMES] = spi_buffer_frames;
+    pru_buffer_comm[PRU_SHOULD_SYNC] = 0;
+    pru_buffer_comm[PRU_SYNC_ADDRESS] = 0;
+    pru_buffer_comm[PRU_SYNC_PIN_MASK] = 0;
+    if(led_enabled) {
+    	pru_buffer_comm[PRU_LED_ADDRESS] = USERLED3_GPIO_BASE;
+    	pru_buffer_comm[PRU_LED_PIN_MASK] = USERLED3_PIN_MASK;
+    }
+    else {
+    	pru_buffer_comm[PRU_LED_ADDRESS] = 0;
+    	pru_buffer_comm[PRU_LED_PIN_MASK] = 0;
+    }
+    if(spi_enabled) {
+    	pru_buffer_comm[PRU_USE_SPI] = 1;
+    }
+    else {
+    	pru_buffer_comm[PRU_USE_SPI] = 0;
+    }
+
+    /* Clear ADC and DAC memory */
+    if(spi_enabled) {
+		for(int i = 0; i < PRU_MEM_DAC_LENGTH / 2; i++)
+			pru_buffer_spi_dac[i] = 0;
+    }
+	for(int i = 0; i < PRU_MEM_MCASP_LENGTH / 2; i++)
+		pru_buffer_audio_dac[i] = 0;
+
+	/* If using GPIO test pin for Xenomai (for debugging), initialise the pointer now */
+	if(xenomai_test_pin && xenomai_gpio_fd < 0) {
+		xenomai_gpio_fd = open("/dev/mem", O_RDWR);
+		if(xenomai_gpio_fd < 0)
+			rt_printf("Unable to open /dev/mem for GPIO test pin\n");
+		else {
+			xenomai_gpio = (uint32_t *)mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, xenomai_gpio_fd, TEST_PIN_GPIO_BASE);
+			if(xenomai_gpio == MAP_FAILED) {
+				rt_printf("Unable to map GPIO address for test pin\n");
+				xenomai_gpio = 0;
+				close(xenomai_gpio_fd);
+				xenomai_gpio_fd = -1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+// Run the code image in the specified file
+int PRU::start(char * const filename)
+{
+	/* Clear any old interrupt */
+	prussdrv_pru_clear_event(pru_number == 0 ? PRU0_ARM_INTERRUPT : PRU1_ARM_INTERRUPT);
+
+    /* Load and execute binary on PRU */
+    if(prussdrv_exec_program(pru_number, filename)) {
+    	rt_printf("Failed to execute PRU code from %s\n", filename);
+    	return 1;
+    }
+
+    running = true;
+    return 0;
+}
+
+// Main loop to read and write data from/to PRU
+void PRU::loop()
+{
+	// Polling interval is 1/4 of the period
+	RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * spi_buffer_frames / 4;
+	float *audioInBuffer, *audioOutBuffer;
+
+	audioInBuffer = (float *)malloc(2 * audio_buffer_frames * sizeof(float));
+	audioOutBuffer = (float *)malloc(2 * audio_buffer_frames * sizeof(float));
+
+	if(audioInBuffer == 0 || audioOutBuffer == 0) {
+		rt_printf("Error: couldn't allocated audio buffers\n");
+		return;
+	}
+
+	while(!gShouldStop) {
+		// Wait for PRU to move to buffer 1
+		while(pru_buffer_comm[PRU_CURRENT_BUFFER] == 0 && !gShouldStop) {
+			rt_task_sleep(sleepTime);
+		}
+		if(gShouldStop)
+			break;
+
+		if(xenomai_gpio != 0) {
+			// Set the test pin high
+			xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN_MASK;
+		}
+
+		// Render from/to buffer 0
+
+		// Convert short (16-bit) samples to float
+		for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++)
+			audioInBuffer[n] = (float)pru_buffer_audio_adc[n] / 32768.0;
+
+		if(spi_enabled)
+			render(spi_buffer_frames, audio_buffer_frames, audioInBuffer, audioOutBuffer,
+					pru_buffer_spi_adc, pru_buffer_spi_dac);
+		else
+			render(0, audio_buffer_frames, audioInBuffer, audioOutBuffer, 0, 0);
+
+		// Convert float back to short
+		for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) {
+			int out = audioOutBuffer[n] * 32768.0;
+			if(out < -32768) out = -32768;
+			else if(out > 32767) out = 32767;
+			pru_buffer_audio_dac[n] = (int16_t)out;
+		}
+
+		if(xenomai_gpio != 0) {
+			// Set the test pin high
+			xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN_MASK;
+		}
+
+		// Wait for PRU to move to buffer 0
+		while(pru_buffer_comm[PRU_CURRENT_BUFFER] != 0 && !gShouldStop) {
+			rt_task_sleep(sleepTime);
+		}
+
+		if(gShouldStop)
+			break;
+
+		if(xenomai_gpio != 0) {
+			// Set the test pin high
+			xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN_MASK;
+		}
+
+		// Render from/to buffer 1
+
+		// Convert short (16-bit) samples to float
+		for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++)
+			audioInBuffer[n] = (float)pru_buffer_audio_adc[n + audio_buffer_frames * 2] / 32768.0;
+
+		if(spi_enabled)
+			render(spi_buffer_frames, audio_buffer_frames, audioInBuffer, audioOutBuffer,
+					&pru_buffer_spi_adc[spi_buffer_frames * 8], &pru_buffer_spi_dac[spi_buffer_frames * 8]);
+		else
+			render(0, audio_buffer_frames, audioInBuffer, audioOutBuffer, 0, 0);
+
+		// Convert float back to short
+		for(unsigned int n = 0; n < 2 * audio_buffer_frames; n++) {
+			int out = audioOutBuffer[n] * 32768.0;
+			if(out < -32768) out = -32768;
+			else if(out > 32767) out = 32767;
+			pru_buffer_audio_dac[n + audio_buffer_frames * 2] = (int16_t)out;
+		}
+
+		if(xenomai_gpio != 0) {
+			// Set the test pin high
+			xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN_MASK;
+		}
+	}
+
+	// Tell PRU to stop
+	pru_buffer_comm[PRU_SHOULD_STOP] = 1;
+
+	free(audioInBuffer);
+	free(audioOutBuffer);
+}
+
+// Wait for an interrupt from the PRU indicate it is finished
+void PRU::waitForFinish()
+{
+	if(!running)
+		return;
+    prussdrv_pru_wait_event (PRU_EVTOUT_0);
+    prussdrv_pru_clear_event(pru_number == 0 ? PRU0_ARM_INTERRUPT : PRU1_ARM_INTERRUPT);
+}
+
+// Turn off the PRU when done
+void PRU::disable()
+{
+    /* Disable PRU and close memory mapping*/
+    prussdrv_pru_disable(pru_number);
+    prussdrv_exit();
+	running = false;
+}
+
+// Debugging
+void PRU::setGPIOTestPin()
+{
+	if(!xenomai_gpio)
+		return;
+	xenomai_gpio[GPIO_SETDATAOUT] = TEST_PIN2_MASK;
+}
+
+void PRU::clearGPIOTestPin()
+{
+	if(!xenomai_gpio)
+		return;
+	xenomai_gpio[GPIO_CLEARDATAOUT] = TEST_PIN2_MASK;
+}