changeset 12:a6beeba3a648

Initial support for higher matrix sample rates by reducing the number of channels. Input not tested yet, and not all examples updated to new format.
author andrewm
date Thu, 22 Jan 2015 19:00:22 +0000
parents 517715b23df0
children 6adb088196a7
files .cproject core/PRU.cpp core/RTAudio.cpp core/RTAudioCommandLine.cpp include/PRU.h include/RTAudio.h include/render.h projects/basic/render.cpp projects/basic_analog_output/render.cpp projects/basic_sensor/render.cpp pru_rtaudio.bin pru_rtaudio.p
diffstat 12 files changed, 178 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/.cproject	Thu Nov 13 16:02:59 2014 +0100
+++ b/.cproject	Thu Jan 22 19:00:22 2015 +0000
@@ -49,17 +49,8 @@
 							</tool>
 							<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.debug.214461086" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.debug"/>
 							<tool command="arm-linux-gnueabihf-g++" id="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug.1669966018" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug">
-								<option id="gnu.cpp.link.option.paths.462980690" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
-									<listOptionValue builtIn="false" value="/usr/arm-linux-gnueabihf/lib"/>
-									<listOptionValue builtIn="false" value="/usr/arm-linux-gnueabihf/lib/xenomai"/>
-									<listOptionValue builtIn="false" value="/usr/lib/x86_64-linux-gnu"/>
-								</option>
-								<option id="gnu.cpp.link.option.libs.139390951" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
-									<listOptionValue builtIn="false" value="rt"/>
-									<listOptionValue builtIn="false" value="native"/>
-									<listOptionValue builtIn="false" value="xenomai"/>
-									<listOptionValue builtIn="false" value="sndfile"/>
-								</option>
+								<option id="gnu.cpp.link.option.paths.462980690" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths"/>
+								<option id="gnu.cpp.link.option.libs.139390951" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs"/>
 								<option id="gnu.cpp.link.option.flags.2096887116" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-pthread -rdynamics" valueType="string"/>
 								<option id="gnu.cpp.link.option.userobjs.537608578" name="Other objects" superClass="gnu.cpp.link.option.userobjs" valueType="userObjs">
 									<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/libprussdrv.a}&quot;"/>
@@ -82,7 +73,7 @@
 					<sourceEntries>
 						<entry excluding="audio_routines_old.S" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="core"/>
 						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="include"/>
-						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects/tank_wars"/>
+						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects/basic_analog_output"/>
 					</sourceEntries>
 				</configuration>
 			</storageModule>
@@ -156,7 +147,7 @@
 					<sourceEntries>
 						<entry excluding="audio_routines_old.S" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="core"/>
 						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="include"/>
-						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects/tank_wars"/>
+						<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="projects/basic_analog_output"/>
 					</sourceEntries>
 				</configuration>
 			</storageModule>
--- a/core/PRU.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/core/PRU.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -50,8 +50,9 @@
 #define PRU_LED_PIN_MASK	7
 #define PRU_FRAME_COUNT		8
 #define PRU_USE_SPI			9
+#define PRU_SPI_NUM_CHANNELS 10
 
-#define PRU_SAMPLE_INTERVAL_NS 45351	// 22050Hz per SPI sample = 45.351us
+#define PRU_SAMPLE_INTERVAL_NS 11338	// 88200Hz per SPI sample = 11.338us
 
 #define GPIO0_ADDRESS 		0x44E07000
 #define GPIO1_ADDRESS 		0x4804C000
@@ -79,7 +80,7 @@
 // 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)
+  gpio_test_pin_enabled(false), spi_num_channels(0), xenomai_gpio_fd(-1), xenomai_gpio(0)
 {
 
 }
@@ -224,7 +225,7 @@
 }
 
 // Initialise and open the PRU
-int PRU::initialise(int pru_num, int frames_per_buffer, bool xenomai_test_pin)
+int PRU::initialise(int pru_num, int frames_per_buffer, int spi_channels, bool xenomai_test_pin)
 {
 	uint32_t *pruMem = 0;
 
@@ -235,6 +236,13 @@
 
 	pru_number = pru_num;
 
+	/* Set number of SPI ADC / DAC channels to use. This implicitly
+	 * also determines the sample rate relative to the audio clock
+	 * (half audio clock for 8 channels, full audio clock for 4,
+	 * double audio clock for 2)
+	 */
+	spi_num_channels = spi_channels;
+
     /* 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;
@@ -250,22 +258,22 @@
     prussdrv_pruintc_init(&pruss_intc_initdata);
 
     spi_buffer_frames = frames_per_buffer;
-    audio_buffer_frames = spi_buffer_frames * 2;
+    audio_buffer_frames = spi_buffer_frames * spi_num_channels / 4;
 
     /* 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];
+	/* ADC memory starts 2(ch)*2(buffers)*bufsize samples later */
+	pru_buffer_audio_adc = &pru_buffer_audio_dac[4 * audio_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];
+		/* ADC memory starts after N(ch)*2(buffers)*bufsize samples */
+		pru_buffer_spi_adc = &pru_buffer_spi_dac[2 * spi_num_channels * spi_buffer_frames];
 	}
 	else {
 		pru_buffer_spi_dac = pru_buffer_spi_adc = 0;
@@ -288,9 +296,11 @@
     }
     if(spi_enabled) {
     	pru_buffer_comm[PRU_USE_SPI] = 1;
+    	pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = spi_num_channels;
     }
     else {
     	pru_buffer_comm[PRU_USE_SPI] = 0;
+    	pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = 0;
     }
 
     /* Clear ADC and DAC memory */
@@ -340,7 +350,7 @@
 void PRU::loop()
 {
 	// Polling interval is 1/4 of the period
-	RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * spi_buffer_frames / 4;
+	RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * (spi_num_channels / 2) * spi_buffer_frames / 4;
 	float *audioInBuffer, *audioOutBuffer;
 
 	audioInBuffer = (float *)malloc(2 * audio_buffer_frames * sizeof(float));
@@ -410,7 +420,7 @@
 
 		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]);
+					&pru_buffer_spi_adc[spi_buffer_frames * spi_num_channels], &pru_buffer_spi_dac[spi_buffer_frames * spi_num_channels]);
 		else
 			render(0, audio_buffer_frames, audioInBuffer, audioOutBuffer, 0, 0);
 
--- a/core/RTAudio.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/core/RTAudio.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -67,7 +67,8 @@
 // periodSize indicates the number of _sensor_ frames per period: the audio period size
 // is twice this value. In total, the audio latency in frames will be 4*periodSize,
 // plus any latency inherent in the ADCs and DACs themselves.
-// useMatrix indicates whether to use the ADC and DAC or just the audio codec.
+// useMatrix indicates whether to enable the ADC and DAC or just use the audio codec.
+// numMatrixChannels indicates how many ADC and DAC channels to use.
 // userData is an opaque pointer which will be passed through to the initialise_render()
 // function for application-specific use
 //
@@ -114,6 +115,24 @@
 		}
 	}
 
+	// Limit the matrix channels to sane values
+	if(settings->numMatrixChannels >= 8)
+		settings->numMatrixChannels = 8;
+	else if(settings->numMatrixChannels >= 4)
+		settings->numMatrixChannels = 4;
+	else
+		settings->numMatrixChannels = 2;
+
+	// Sanity check the combination of channels and period size
+	if(settings->numMatrixChannels <= 4 && settings->periodSize < 2) {
+		cout << "Error: " << settings->numMatrixChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
+		return 1;
+	}
+	if(settings->numMatrixChannels <= 2 && settings->periodSize < 4) {
+		cout << "Error: " << settings->numMatrixChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
+		return 1;
+	}
+
 	// Use PRU for audio
 	gPRU = new PRU();
 	gAudioCodec = new I2c_Codec();
@@ -122,7 +141,7 @@
 		cout << "Error: unable to prepare GPIO for PRU audio\n";
 		return 1;
 	}
-	if(gPRU->initialise(0, settings->periodSize, true)) {
+	if(gPRU->initialise(0, settings->periodSize, settings->numMatrixChannels, true)) {
 		cout << "Error: unable to initialise PRU\n";
 		return 1;
 	}
@@ -140,7 +159,23 @@
 	BeagleRT_setADCLevel(settings->adcLevel);
 	BeagleRT_setHeadphoneLevel(settings->headphoneLevel);
 
-	if(!initialise_render(2, settings->useMatrix ? settings->periodSize : 0, settings->periodSize * 2, 22050.0, 44100.0, userData)) {
+	// Initialise the rendering environment: pass the number of audio and matrix
+	// channels, the period size for matrix and audio, and the sample rates
+
+	int audioPeriodSize = settings->periodSize * 2;
+	float audioSampleRate = 44100.0;
+	float matrixSampleRate = 22050.0;
+	if(settings->useMatrix) {
+		audioPeriodSize = settings->periodSize * settings->numMatrixChannels / 4;
+		matrixSampleRate = audioSampleRate * 4.0 / (float)settings->numMatrixChannels;
+	}
+
+	if(!initialise_render(settings->useMatrix ? settings->numMatrixChannels : 0, /* matrix channels */
+						  2, /* audio channels */
+				          settings->useMatrix ? settings->periodSize : 0, /* matrix period size */
+				          audioPeriodSize,
+				          matrixSampleRate, audioSampleRate,
+				          userData)) {
 		cout << "Couldn't initialise audio rendering\n";
 		return 1;
 	}
--- a/core/RTAudioCommandLine.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/core/RTAudioCommandLine.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -18,6 +18,7 @@
 	{"period", 1, NULL, 'p'},
 	{"verbose", 0, NULL, 'v'},
 	{"use-matrix", 1, NULL, 'm'},
+	{"matrix-channels", 1, NULL, 'C'},
 	{"mute-speaker", 1, NULL, 'M'},
 	{"dac-level", 1, NULL, 'D'},
 	{"adc-level", 1, NULL, 'A'},
@@ -25,7 +26,7 @@
 	{NULL, 0, NULL, 0}
 };
 
-const char gDefaultShortOptions[] = "p:vm:M:D:A:H:";
+const char gDefaultShortOptions[] = "p:vm:M:C:D:A:H:";
 
 // This function sets the default settings for the RTAudioSettings structure
 void BeagleRT_defaultSettings(RTAudioSettings *settings)
@@ -37,6 +38,7 @@
 	settings->adcLevel = DEFAULT_ADC_LEVEL;
 	settings->headphoneLevel = DEFAULT_HP_LEVEL;
 	settings->useMatrix = 1;
+	settings->numMatrixChannels = 8;
 	settings->verbose = 0;
 	settings->codecI2CAddress = CODEC_I2C_ADDRESS;
 	settings->ampMutePin = kAmplifierMutePin;
@@ -123,6 +125,15 @@
 		case 'm':
 			settings->useMatrix = atoi(optarg);
 			break;
+		case 'C':
+			settings->numMatrixChannels = atoi(optarg);
+			if(settings->numMatrixChannels >= 8)
+				settings->numMatrixChannels = 8;
+			else if(settings->numMatrixChannels >= 4)
+				settings->numMatrixChannels = 4;
+			else
+				settings->numMatrixChannels = 2;
+			break;
 		case 'M':
 			settings->beginMuted = atoi(optarg);
 			break;
@@ -146,11 +157,12 @@
 // Call from within your own usage function
 void BeagleRT_usage()
 {
-	std::cerr << "   --period [-p] period:    Set the hardware period (buffer) size in matrix samples\n";
-	std::cerr << "   --dac-level [-D] dBs:    Set the DAC output level (0dB max; -63.5dB min)\n";
-	std::cerr << "   --adc-level [-A] dBs:    Set the ADC input level (0dB max; -12dB min)\n";
-	std::cerr << "   --hp-level [-H] dBs:     Set the headphone output level (0dB max; -63.5dB min)\n";
-	std::cerr << "   --mute-speaker [-M] val: Set whether to mute the speaker initially (default: no)\n";
-	std::cerr << "   --use-matrix [-m] val:   Set whether to use ADC/DAC matrix\n";
-	std::cerr << "   --verbose [-v]:          Enable verbose logging information\n";
+	std::cerr << "   --period [-p] period:       Set the hardware period (buffer) size in matrix samples\n";
+	std::cerr << "   --dac-level [-D] dBs:       Set the DAC output level (0dB max; -63.5dB min)\n";
+	std::cerr << "   --adc-level [-A] dBs:       Set the ADC input level (0dB max; -12dB min)\n";
+	std::cerr << "   --hp-level [-H] dBs:        Set the headphone output level (0dB max; -63.5dB min)\n";
+	std::cerr << "   --mute-speaker [-M] val:    Set whether to mute the speaker initially (default: no)\n";
+	std::cerr << "   --use-matrix [-m] val:      Set whether to use ADC/DAC matrix\n";
+	std::cerr << "   --matrix-channels [-C] val: Set the number of ADC/DAC channels (default: 8)\n";
+	std::cerr << "   --verbose [-v]:             Enable verbose logging information\n";
 }
--- a/include/PRU.h	Thu Nov 13 16:02:59 2014 +0100
+++ b/include/PRU.h	Thu Jan 22 19:00:22 2015 +0000
@@ -33,7 +33,8 @@
 	void cleanupGPIO();
 
 	// Initialise and open the PRU
-	int initialise(int pru_num, int frames_per_buffer, bool xenomai_test_pin = false);
+	int initialise(int pru_num, int frames_per_buffer, int spi_channels,
+				   bool xenomai_test_pin = false);
 
 	// Run the code image in the specified file
 	int start(char * const filename);
@@ -58,6 +59,7 @@
 	bool gpio_enabled;	// Whether GPIO has been prepared
 	bool led_enabled;	// Whether a user LED is enabled
 	bool gpio_test_pin_enabled; // Whether the test pin was also enabled
+	int spi_num_channels; // How many channels to use for SPI ADC/DAC
 
 	volatile uint32_t *pru_buffer_comm;
 	uint16_t *pru_buffer_spi_dac;
--- a/include/RTAudio.h	Thu Nov 13 16:02:59 2014 +0100
+++ b/include/RTAudio.h	Thu Jan 22 19:00:22 2015 +0000
@@ -43,7 +43,8 @@
 	float dacLevel;			// Level for the audio DAC output
 	float adcLevel;			// Level for the audio ADC input
 	float headphoneLevel;	// Level for the headphone output
-	int useMatrix;			// Whether to enable the ADC and DAC
+	int useMatrix;			// Whether to use the matrix
+	int numMatrixChannels;	// How many channels for the ADC and DAC
 	int verbose;			// Whether to use verbose logging
 
 	// These items are hardware-dependent and should only be changed
--- a/include/render.h	Thu Nov 13 16:02:59 2014 +0100
+++ b/include/render.h	Thu Jan 22 19:00:22 2015 +0000
@@ -33,7 +33,8 @@
 
 #define MATRIX_MAX  65535.0
 
-bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
+bool initialise_render(int numMatrixChannels, int numAudioChannels,
+		  	   	   	   int numMatrixFramesPerPeriod,
 					   int numAudioFramesPerPeriod,
 					   float matrixSampleRate, float audioSampleRate,
 					   void *userData);
--- a/projects/basic/render.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/projects/basic/render.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -23,14 +23,16 @@
 //
 // Return true on success; returning false halts the program.
 
-bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
-					   int numAudioFramesPerPeriod, float matrixSampleRate,
-					   float audioSampleRate, void *userData)
+bool initialise_render(int numMatrixChannels, int numAudioChannels,
+					   int numMatrixFramesPerPeriod,
+					   int numAudioFramesPerPeriod,
+					   float matrixSampleRate, float audioSampleRate,
+					   void *userData)
 {
 	// Retrieve a parameter passed in from the initAudio() call
 	gFrequency = *(float *)userData;
 
-	gNumChannels = numChannels;
+	gNumChannels = numAudioChannels;
 	gInverseSampleRate = 1.0 / audioSampleRate;
 	gPhase = 0.0;
 
--- a/projects/basic_analog_output/render.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/projects/basic_analog_output/render.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -19,6 +19,8 @@
 float gPhase;
 float gInverseSampleRate;
 
+int gMatrixChannels;
+
 // initialise_render() is called once before the audio rendering starts.
 // Use it to perform any initialisation and allocation which is dependent
 // on the period size or sample rate.
@@ -28,19 +30,22 @@
 //
 // Return true on success; returning false halts the program.
 
-bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
-					   int numAudioFramesPerPeriod, float matrixSampleRate,
-					   float audioSampleRate, void *userData)
+bool initialise_render(int numMatrixChannels, int numAudioChannels,
+					   int numMatrixFramesPerPeriod,
+					   int numAudioFramesPerPeriod,
+					   float matrixSampleRate, float audioSampleRate,
+					   void *userData)
 {
 	// Retrieve a parameter passed in from the initAudio() call
 	gFrequency = *(float *)userData;
 
-	if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) {
-		rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n");
+	if(numMatrixFramesPerPeriod == 0) {
+		rt_printf("Error: this example needs the matrix enabled\n");
 		return false;
 	}
 
-	gInverseSampleRate = 1.0 / audioSampleRate;
+	gMatrixChannels = numMatrixChannels;
+	gInverseSampleRate = 1.0 / matrixSampleRate;
 	gPhase = 0.0;
 
 	return true;
@@ -57,12 +62,13 @@
 	for(int n = 0; n < numMatrixFrames; n++) {
 		// Set LED to different phase for each matrix channel
 		float relativePhase = 0.0;
-		for(int channel = 0; channel < 8; channel++) {
+		for(int channel = 0; channel < gMatrixChannels; channel++) {
 			float out = kMinimumAmplitude + kAmplitudeRange * 0.5f * (1.0f + sinf(gPhase + relativePhase));
 			if(out > MATRIX_MAX)
 				out = MATRIX_MAX;
 
-			analogWrite(channel, n, out);
+			matrixOut[n * gMatrixChannels + channel] = (uint16_t)out;
+			//analogWrite(channel, n, out);
 
 			// Advance by pi/4 (1/8 of a full rotation) for each channel
 			relativePhase += M_PI * 0.25;
--- a/projects/basic_sensor/render.cpp	Thu Nov 13 16:02:59 2014 +0100
+++ b/projects/basic_sensor/render.cpp	Thu Jan 22 19:00:22 2015 +0000
@@ -31,16 +31,18 @@
 //
 // Return true on success; returning false halts the program.
 
-bool initialise_render(int numChannels, int numMatrixFramesPerPeriod,
-					   int numAudioFramesPerPeriod, float matrixSampleRate,
-					   float audioSampleRate, void *userData)
+bool initialise_render(int numMatrixChannels, int numAudioChannels,
+					   int numMatrixFramesPerPeriod,
+					   int numAudioFramesPerPeriod,
+					   float matrixSampleRate, float audioSampleRate,
+					   void *userData)
 {
 	if(numMatrixFramesPerPeriod*2 != numAudioFramesPerPeriod) {
 		rt_printf("Error: this example needs the matrix enabled, running at half audio rate\n");
 		return false;
 	}
 
-	gNumChannels = numChannels;
+	gNumChannels = numAudioChannels;
 	gInverseSampleRate = 1.0 / audioSampleRate;
 	gPhase = 0.0;
 
Binary file pru_rtaudio.bin has changed
--- a/pru_rtaudio.p	Thu Nov 13 16:02:59 2014 +0100
+++ b/pru_rtaudio.p	Thu Jan 22 19:00:22 2015 +0000
@@ -80,6 +80,7 @@
 #define COMM_LED_PIN_MASK     28          // Which pin to write to change LED
 #define COMM_FRAME_COUNT      32	  // How many frames have elapse since beginning
 #define COMM_USE_SPI          36          // Whether or not to use SPI ADC and DAC
+#define COMM_NUM_CHANNELS     40	  // Low 2 bits indicate 8 [0x3], 4 [0x1] or 2 [0x0] channels
 	
 #define MCASP0_BASE 0x48038000
 #define MCASP1_BASE 0x4803C000
@@ -173,15 +174,17 @@
 #define MCASP_DATA_MASK 	0xFFFF		// 16 bit data
 #define MCASP_DATA_FORMAT	0x807C		// MSB first, 0 bit delay, 16 bits, CFG bus, ROR 16bits
 
-#define C_MCASP_MEM             C28     // Shared PRU mem
+#define C_MCASP_MEM             C28     	// Shared PRU mem
 
 // Flags for the flags register
 #define FLAG_BIT_BUFFER1	0
 #define FLAG_BIT_USE_SPI	1
+#define FLAG_BIT_MCASP_HWORD	2		// Whether we are on the high word for McASP transmission
 	
 // Registers used throughout
 
 // r1, r2, r3 are used for temporary storage
+#define reg_num_channels	r9		// Number of SPI ADC/DAC channels to use
 #define reg_frame_current	r10		// Current frame count in SPI ADC/DAC transfer
 #define reg_frame_total		r11		// Total frame count for SPI ADC/DAC
 #define reg_dac_data		r12		// Current dword for SPI DAC
@@ -356,6 +359,9 @@
       // Clear flags
       MOV reg_flags, 0
 
+      // Default number of channels in case SPI disabled
+      LDI reg_num_channels, 8
+	
       // Find out whether we should use SPI ADC and DAC
       LBBO r2, reg_comm_addr, COMM_USE_SPI, 4
       QBEQ SPI_FLAG_CHECK_DONE, r2, 0
@@ -364,6 +370,19 @@
 SPI_FLAG_CHECK_DONE:
       // If we don't use SPI, then skip all this init
       QBBC SPI_INIT_DONE, reg_flags, FLAG_BIT_USE_SPI
+
+      // Load the number of channels: valid values are 8, 4 or 2
+      LBBO reg_num_channels, reg_comm_addr, COMM_NUM_CHANNELS, 4
+      QBGT SPI_NUM_CHANNELS_LT8, reg_num_channels, 8 // 8 > num_channels ?
+      LDI reg_num_channels, 8		// If N >= 8, N = 8
+      QBA SPI_NUM_CHANNELS_DONE
+SPI_NUM_CHANNELS_LT8:	
+      QBGT SPI_NUM_CHANNELS_LT4, reg_num_channels, 4 // 4 > num_channels ?
+      LDI reg_num_channels, 4		// If N >= 4, N = 4
+      QBA SPI_NUM_CHANNELS_DONE
+SPI_NUM_CHANNELS_LT4:
+      LDI reg_num_channels, 2		// else N = 2
+SPI_NUM_CHANNELS_DONE:	
 	
       // Init SPI clock
       MOV r2, 0x02
@@ -491,11 +510,13 @@
 MCASP_REG_SET_BIT_AND_POLL MCASP_XGBLCTL, (1 << 12)	// Set XFRST
 
 // Initialisation
-LBBO reg_frame_total, reg_comm_addr, COMM_BUFFER_FRAMES, 4  // Total frame count (SPI; 2x for McASP)
+LBBO reg_frame_total, reg_comm_addr, COMM_BUFFER_FRAMES, 4  // Total frame count (SPI; 0.5x-2x for McASP)
 MOV reg_dac_buf0, 0                      // DAC buffer 0 start pointer
-LSL reg_dac_buf1, reg_frame_total, 4     // DAC buffer 1 start pointer = 8[ch]*2[bytes]*bufsize
+LSL reg_dac_buf1, reg_frame_total, 1     // DAC buffer 1 start pointer = N[ch]*2[bytes]*bufsize
+LMBD r2, reg_num_channels, 1		 // Returns 1, 2 or 3 depending on the number of channels
+LSL reg_dac_buf1, reg_dac_buf1, r2	 // Multiply by 2, 4 or 8 to get the N[ch] scaling above
 MOV reg_mcasp_buf0, 0			 // McASP DAC buffer 0 start pointer
-LSL reg_mcasp_buf1, reg_frame_total, 3   // McASP DAC buffer 1 start pointer = 2[ch]*2[bytes]*2[samples/spi]*bufsize
+LSL reg_mcasp_buf1, reg_frame_total, r2  // McASP DAC buffer 1 start pointer = 2[ch]*2[bytes]*(N/4)[samples/spi]*bufsize
 CLR reg_flags, reg_flags, FLAG_BIT_BUFFER1  // Bit 0 holds which buffer we are on
 MOV r2, 0
 SBBO r2, reg_comm_addr, COMM_FRAME_COUNT, 4  // Start with frame count of 0
@@ -521,16 +542,19 @@
       // Write a single buffer of DAC samples and read a buffer of ADC samples
       // Load starting positions
       MOV reg_dac_current, reg_dac_buf0         // DAC: reg_dac_current is current pointer
-      LSL reg_adc_current, reg_frame_total, 5   // 16 * 2 * bufsize
-      ADD reg_adc_current, reg_adc_current, reg_dac_current // ADC: starts 16 * 2 * bufsize beyond DAC
+      LMBD r2, reg_num_channels, 1		// 1, 2 or 3 for 2, 4 or 8 channels
+      LSL reg_adc_current, reg_frame_total, r2
+      LSL reg_adc_current, reg_adc_current, 2   // N * 2 * 2 * bufsize
+      ADD reg_adc_current, reg_adc_current, reg_dac_current // ADC: starts N * 2 * 2 * bufsize beyond DAC
       MOV reg_mcasp_dac_current, reg_mcasp_buf0 // McASP: set current DAC pointer
-      LSL reg_mcasp_adc_current, reg_frame_total, 4 // McASP ADC: starts 4*2*2*bufsize beyond DAC
+      LSL reg_mcasp_adc_current, reg_frame_total, r2 // McASP ADC: starts (N/2)*2*2*bufsize beyond DAC
+      LSL reg_mcasp_adc_current, reg_mcasp_adc_current, 1
       ADC reg_mcasp_adc_current, reg_mcasp_adc_current, reg_mcasp_dac_current
       MOV reg_frame_current, 0
 	
 WRITE_LOOP:
-      // Write 8 channels to DAC from successive values in memory
-      // At the same time, read 8 channels from ADC
+      // Write N channels to DAC from successive values in memory
+      // At the same time, read N channels from ADC
       // Unrolled by a factor of 2 to get high and low words
       MOV r1, 0
 ADC_DAC_LOOP:
@@ -544,7 +568,8 @@
 
       // On even iterations, load two more samples and choose the first one
       // On odd iterations, transmit the second of the samples already loaded
-      QBBS MCASP_DAC_HIGH_WORD, r1, 1
+      // QBBS MCASP_DAC_HIGH_WORD, r1, 1
+      QBBS MCASP_DAC_HIGH_WORD, reg_flags, FLAG_BIT_MCASP_HWORD
 MCASP_DAC_LOW_WORD:	
       // Load next 2 Audio DAC samples and store zero in their place
       LBCO reg_mcasp_dac_data, C_MCASP_MEM, reg_mcasp_dac_current, 4
@@ -561,8 +586,7 @@
       // Take the high word of the previously loaded data
       LSR r7, reg_mcasp_dac_data, 16
 	
-      // Two audio frames per SPI frame = 4 audio samples per SPI frame
-      // Therefore every 2 channels we send one audio sample; this loop already
+      // Every 2 channels we send one audio sample; this loop already
       // sends exactly two SPI channels.
       // Wait for McASP XSTAT[XDATA] to set indicating we can write more data
 MCASP_WAIT_XSTAT:
@@ -573,7 +597,8 @@
 	
       // Same idea with ADC: even iterations, load the sample into the low word, odd
       // iterations, load the sample into the high word and store
-      QBBS MCASP_ADC_HIGH_WORD, r1, 1
+      // QBBS MCASP_ADC_HIGH_WORD, r1, 1
+      QBBS MCASP_ADC_HIGH_WORD, reg_flags, FLAG_BIT_MCASP_HWORD
 MCASP_ADC_LOW_WORD:	
       // Start ADC data at 0
       LDI reg_mcasp_adc_data, 0
@@ -618,12 +643,13 @@
 
       // Read ADC channels: result is always 2 commands behind
       // Start by reading channel 2 (result is channel 0) and go
-      // to 10, but masking the channel number to be between 0 and 7
+      // to N+2, but masking the channel number to be between 0 and N-1
       LDI reg_adc_data, 0
+      ADD r8, r1, 2
+      SUB r7, reg_num_channels, 1
+      AND r8, r8, r7
+      LSL r8, r8, AD7699_CHANNEL_OFFSET
       MOV r7, AD7699_CFG_MASK
-      ADD r8, r1, 2
-      AND r8, r8, 7
-      LSL r8, r8, AD7699_CHANNEL_OFFSET
       OR r7, r7, r8
       ADC_WRITE r7, r7
 
@@ -645,11 +671,13 @@
 
       // Read ADC channels: result is always 2 commands behind
       // Start by reading channel 2 (result is channel 0) and go
-      // to 10, but masking the channel number to be between 0 and 7
+      // to N+2, but masking the channel number to be between 0 and N-1
+      LDI reg_adc_data, 0
+      ADD r8, r1, 2
+      SUB r7, reg_num_channels, 1
+      AND r8, r8, r7
+      LSL r8, r8, AD7699_CHANNEL_OFFSET
       MOV r7, AD7699_CFG_MASK
-      ADD r8, r1, 2
-      AND r8, r8, 7
-      LSL r8, r8, AD7699_CHANNEL_OFFSET
       OR r7, r7, r8
       ADC_WRITE r7, r7
 
@@ -661,16 +689,26 @@
       SBCO reg_adc_data, C_ADC_DAC_MEM, reg_adc_current, 4
       ADD reg_adc_current, reg_adc_current, 4
 
-      // Repeat 4 times (2 samples per loop, r1 += 1 already happened)
+      // Toggle the high/low word for McASP control (since we send one word out of
+      // 32 bits for each pair of SPI channels)
+      XOR reg_flags, reg_flags, (1 << FLAG_BIT_MCASP_HWORD)
+	
+      // Repeat 4 times for 8 channels (2 samples per loop, r1 += 1 already happened)
+      // For 4 or 2 channels, repeat 2 or 1 times, according to flags
       ADD r1, r1, 1
-      QBNE ADC_DAC_LOOP, r1, 8
+      QBNE ADC_DAC_LOOP, r1, reg_num_channels
       QBA ADC_DAC_LOOP_DONE
-	
+
 SPI_SKIP_WRITE:
       // We get here only if the SPI ADC and DAC are disabled
       // Just keep the loop going for McASP
+
+      // Toggle the high/low word for McASP control (since we send one word out of
+      // 32 bits for each pair of SPI channels)
+      XOR reg_flags, reg_flags, (1 << FLAG_BIT_MCASP_HWORD)
+
       ADD r1, r1, 2
-      QBNE ADC_DAC_LOOP, r1, 8
+      QBNE ADC_DAC_LOOP, r1, reg_num_channels
 	
 ADC_DAC_LOOP_DONE:	
       // Increment number of frames, see if we have more to write