annotate core/RTAudio.cpp @ 39:638bc1ae2500 staging

Improved readibility of the DIGITAL code in the PRU, using register names instead of aliases and expanding some of the macros, removing unused macros. Binaries were not modified
author Giulio Moro <giuliomoro@yahoo.it>
date Wed, 13 May 2015 12:18:10 +0100
parents ad5cd8dd99b3
children 4255ecbb9bec 579c86316008
rev   line source
andrewm@0 1 /*
andrewm@0 2 * RTAudio.cpp
andrewm@0 3 *
andrewm@0 4 * Central control code for hard real-time audio on BeagleBone Black
andrewm@0 5 * using PRU and Xenomai Linux extensions. This code began as part
andrewm@0 6 * of the Hackable Instruments project (EPSRC) at Queen Mary University
andrewm@0 7 * of London, 2013-14.
andrewm@0 8 *
andrewm@0 9 * (c) 2014 Victor Zappi and Andrew McPherson
andrewm@0 10 * Queen Mary University of London
andrewm@0 11 */
andrewm@0 12
andrewm@0 13
andrewm@0 14 #include <stdio.h>
andrewm@0 15 #include <stdlib.h>
andrewm@0 16 #include <string.h>
andrewm@0 17 #include <strings.h>
andrewm@0 18 #include <math.h>
andrewm@0 19 #include <iostream>
andrewm@0 20 #include <assert.h>
andrewm@0 21 #include <vector>
andrewm@0 22
andrewm@0 23 // Xenomai-specific includes
andrewm@0 24 #include <sys/mman.h>
andrewm@0 25 #include <native/task.h>
andrewm@0 26 #include <native/timer.h>
andrewm@0 27 #include <rtdk.h>
andrewm@0 28
andrewm@0 29 #include "../include/RTAudio.h"
andrewm@0 30 #include "../include/PRU.h"
andrewm@0 31 #include "../include/I2c_Codec.h"
andrewm@0 32 #include "../include/render.h"
andrewm@0 33 #include "../include/GPIOcontrol.h"
giuliomoro@24 34 #include "../include/client.h"
andrewm@0 35
andrewm@0 36 using namespace std;
andrewm@0 37
andrewm@0 38 // Data structure to keep track of auxiliary tasks we
andrewm@0 39 // can schedule
andrewm@0 40 typedef struct {
andrewm@0 41 RT_TASK task;
andrewm@0 42 void (*function)(void);
andrewm@0 43 char *name;
andrewm@0 44 int priority;
andrewm@0 45 } InternalAuxiliaryTask;
andrewm@0 46
andrewm@0 47 const char gRTAudioThreadName[] = "beaglert-audio";
andrewm@0 48
andrewm@0 49 // Real-time tasks and objects
andrewm@0 50 RT_TASK gRTAudioThread;
andrewm@0 51 PRU *gPRU = 0;
andrewm@0 52 I2c_Codec *gAudioCodec = 0;
andrewm@0 53
andrewm@0 54 vector<InternalAuxiliaryTask*> gAuxTasks;
andrewm@0 55
andrewm@0 56 // Flag which tells the audio task to stop
andrewm@0 57 bool gShouldStop = false;
andrewm@0 58
andrewm@0 59 // general settings
giuliomoro@16 60 char *gPRUFilename;//[256] = "pru_rtaudio.bin"; // path to PRU binary file
andrewm@0 61 int gRTAudioVerbose = 0; // Verbosity level for debugging
andrewm@0 62 int gAmplifierMutePin = -1;
andrewm@5 63 int gAmplifierShouldBeginMuted = 0;
andrewm@0 64
giuliomoro@19 65 // Number of audio and analog channels, globally accessible
giuliomoro@19 66 // At least gNumAnalogChannels and gNumDigitalChannels need to be global to be used
giuliomoro@19 67 // by the AnalogRead() and AnalogWrite() and the digital macros without creating
andrewm@13 68 // extra confusion in their use cases by passing this argument
andrewm@13 69 int gNumAudioChannels = 0;
giuliomoro@19 70 int gNumAnalogChannels = 0;
giuliomoro@19 71 int gNumDigitalChannels = 0;
andrewm@0 72
andrewm@0 73 // initAudio() prepares the infrastructure for running PRU-based real-time
andrewm@0 74 // audio, but does not actually start the calculations.
andrewm@0 75 // periodSize indicates the number of _sensor_ frames per period: the audio period size
andrewm@0 76 // is twice this value. In total, the audio latency in frames will be 4*periodSize,
andrewm@0 77 // plus any latency inherent in the ADCs and DACs themselves.
giuliomoro@19 78 // useAnalog indicates whether to enable the ADC and DAC or just use the audio codec.
giuliomoro@19 79 // numAnalogChannels indicates how many ADC and DAC channels to use.
andrewm@0 80 // userData is an opaque pointer which will be passed through to the initialise_render()
andrewm@0 81 // function for application-specific use
andrewm@0 82 //
andrewm@0 83 // Returns 0 on success.
andrewm@0 84
giuliomoro@24 85
andrewm@5 86 int BeagleRT_initAudio(RTAudioSettings *settings, void *userData)
andrewm@0 87 {
andrewm@0 88 rt_print_auto_init(1);
andrewm@5 89 setVerboseLevel(settings->verbose);
giuliomoro@16 90 gPRUFilename=settings->pruFilename;
andrewm@0 91 if(gRTAudioVerbose == 1)
andrewm@0 92 rt_printf("Running with Xenomai\n");
andrewm@0 93
andrewm@5 94 if(gRTAudioVerbose) {
andrewm@5 95 cout << "Starting with period size " << settings->periodSize << "; ";
giuliomoro@19 96 if(settings->useAnalog)
giuliomoro@19 97 cout << "analog enabled\n";
andrewm@5 98 else
giuliomoro@19 99 cout << "analog disabled\n";
andrewm@5 100 cout << "DAC level " << settings->dacLevel << "dB; ADC level " << settings->adcLevel;
andrewm@5 101 cout << "dB; headphone level " << settings->headphoneLevel << "dB\n";
andrewm@5 102 if(settings->beginMuted)
andrewm@5 103 cout << "Beginning with speaker muted\n";
andrewm@5 104 }
andrewm@0 105
andrewm@0 106 // Prepare GPIO pins for amplifier mute and status LED
andrewm@5 107 if(settings->ampMutePin >= 0) {
andrewm@5 108 gAmplifierMutePin = settings->ampMutePin;
andrewm@5 109 gAmplifierShouldBeginMuted = settings->beginMuted;
andrewm@0 110
andrewm@5 111 if(gpio_export(settings->ampMutePin)) {
andrewm@0 112 if(gRTAudioVerbose)
giuliomoro@16 113 cout << "Warning: couldn't export amplifier mute pin " << settings-> ampMutePin << "\n";
andrewm@0 114 }
andrewm@5 115 if(gpio_set_dir(settings->ampMutePin, OUTPUT_PIN)) {
andrewm@0 116 if(gRTAudioVerbose)
andrewm@0 117 cout << "Couldn't set direction on amplifier mute pin\n";
andrewm@0 118 return -1;
andrewm@0 119 }
andrewm@5 120 if(gpio_set_value(settings->ampMutePin, LOW)) {
andrewm@0 121 if(gRTAudioVerbose)
andrewm@0 122 cout << "Couldn't set value on amplifier mute pin\n";
andrewm@0 123 return -1;
andrewm@0 124 }
andrewm@0 125 }
andrewm@0 126
giuliomoro@19 127 // Limit the analog channels to sane values
giuliomoro@19 128 if(settings->numAnalogChannels >= 8)
giuliomoro@19 129 settings->numAnalogChannels = 8;
giuliomoro@19 130 else if(settings->numAnalogChannels >= 4)
giuliomoro@19 131 settings->numAnalogChannels = 4;
andrewm@12 132 else
giuliomoro@19 133 settings->numAnalogChannels = 2;
andrewm@12 134
andrewm@12 135 // Sanity check the combination of channels and period size
giuliomoro@19 136 if(settings->numAnalogChannels <= 4 && settings->periodSize < 2) {
giuliomoro@19 137 cout << "Error: " << settings->numAnalogChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
andrewm@12 138 return 1;
andrewm@12 139 }
giuliomoro@19 140 if(settings->numAnalogChannels <= 2 && settings->periodSize < 4) {
giuliomoro@19 141 cout << "Error: " << settings->numAnalogChannels << " channels and period size of " << settings->periodSize << " not supported.\n";
andrewm@12 142 return 1;
andrewm@12 143 }
andrewm@12 144
andrewm@0 145 // Use PRU for audio
andrewm@0 146 gPRU = new PRU();
andrewm@0 147 gAudioCodec = new I2c_Codec();
andrewm@0 148
giuliomoro@19 149 gNumDigitalChannels = settings->useDigital ? settings->numDigitalChannels : 0; //this is called here to make sure prepareGPIO initializes the appropriate GPIO pins
giuliomoro@19 150 if(gPRU->prepareGPIO(settings->useAnalog, settings->useDigital, 1, 1)) {
andrewm@0 151 cout << "Error: unable to prepare GPIO for PRU audio\n";
andrewm@0 152 return 1;
andrewm@0 153 }
giuliomoro@19 154 if(gPRU->initialise(0, settings->periodSize, settings->numAnalogChannels, true)) {
andrewm@0 155 cout << "Error: unable to initialise PRU\n";
andrewm@0 156 return 1;
andrewm@0 157 }
andrewm@5 158 if(gAudioCodec->initI2C_RW(2, settings->codecI2CAddress, -1)) {
andrewm@0 159 cout << "Unable to open codec I2C\n";
andrewm@0 160 return 1;
andrewm@0 161 }
andrewm@0 162 if(gAudioCodec->initCodec()) {
andrewm@0 163 cout << "Error: unable to initialise audio codec\n";
andrewm@0 164 return 1;
andrewm@0 165 }
andrewm@0 166
andrewm@5 167 // Set default volume levels
andrewm@5 168 BeagleRT_setDACLevel(settings->dacLevel);
andrewm@5 169 BeagleRT_setADCLevel(settings->adcLevel);
andrewm@5 170 BeagleRT_setHeadphoneLevel(settings->headphoneLevel);
andrewm@5 171
giuliomoro@19 172 // Initialise the rendering environment: pass the number of audio and analog
giuliomoro@19 173 // channels, the period size for analog and audio, and the sample rates
andrewm@12 174
andrewm@12 175 int audioPeriodSize = settings->periodSize * 2;
andrewm@12 176 float audioSampleRate = 44100.0;
giuliomoro@19 177 float analogSampleRate = 22050.0;
giuliomoro@19 178 if(settings->useAnalog) {
giuliomoro@19 179 audioPeriodSize = settings->periodSize * settings->numAnalogChannels / 4;
giuliomoro@19 180 analogSampleRate = audioSampleRate * 4.0 / (float)settings->numAnalogChannels;
andrewm@12 181 }
andrewm@12 182
andrewm@13 183 gNumAudioChannels = 2;
giuliomoro@19 184 gNumAnalogChannels = settings->useAnalog ? settings->numAnalogChannels : 0;
giuliomoro@19 185 if(!initialise_render(gNumAnalogChannels, gNumDigitalChannels, gNumAudioChannels,
giuliomoro@19 186 settings->useAnalog ? settings->periodSize : 0, /* analog period size */
andrewm@12 187 audioPeriodSize,
giuliomoro@19 188 analogSampleRate, audioSampleRate,
giuliomoro@24 189 userData, settings)) {
andrewm@0 190 cout << "Couldn't initialise audio rendering\n";
andrewm@0 191 return 1;
andrewm@0 192 }
andrewm@0 193
andrewm@0 194 return 0;
andrewm@0 195 }
andrewm@0 196
andrewm@0 197 // audioLoop() is the main function which starts the PRU audio code
andrewm@0 198 // and then transfers control to the PRU object. The PRU object in
andrewm@0 199 // turn will call the audio render() callback function every time
andrewm@0 200 // there is new data to process.
andrewm@0 201
andrewm@0 202 void audioLoop(void *)
andrewm@0 203 {
andrewm@0 204 if(gRTAudioVerbose==1)
andrewm@0 205 rt_printf("_________________Audio Thread!\n");
andrewm@0 206
andrewm@0 207 // PRU audio
andrewm@0 208 assert(gAudioCodec != 0 && gPRU != 0);
andrewm@0 209
andrewm@0 210 if(gAudioCodec->startAudio(0)) {
andrewm@0 211 rt_printf("Error: unable to start I2C audio codec\n");
andrewm@0 212 gShouldStop = 1;
andrewm@0 213 }
andrewm@0 214 else {
giuliomoro@16 215 if(gPRU->start(gPRUFilename)) {
giuliomoro@16 216 rt_printf("Error: unable to start PRU from file %s\n", gPRUFilename);
andrewm@0 217 gShouldStop = 1;
andrewm@0 218 }
andrewm@0 219 else {
andrewm@0 220 // All systems go. Run the loop; it will end when gShouldStop is set to 1
andrewm@5 221
andrewm@5 222 if(!gAmplifierShouldBeginMuted) {
andrewm@5 223 // First unmute the amplifier
andrewm@5 224 if(BeagleRT_muteSpeakers(0)) {
andrewm@5 225 if(gRTAudioVerbose)
andrewm@5 226 rt_printf("Warning: couldn't set value (high) on amplifier mute pin\n");
andrewm@5 227 }
andrewm@0 228 }
andrewm@0 229
andrewm@0 230 gPRU->loop();
andrewm@0 231
andrewm@0 232 // Now clean up
andrewm@0 233 // gPRU->waitForFinish();
andrewm@0 234 gPRU->disable();
andrewm@0 235 gAudioCodec->stopAudio();
andrewm@0 236 gPRU->cleanupGPIO();
andrewm@0 237 }
andrewm@0 238 }
andrewm@0 239
andrewm@0 240 if(gRTAudioVerbose == 1)
andrewm@0 241 rt_printf("audio thread ended\n");
andrewm@0 242 }
andrewm@0 243
andrewm@0 244 // Create a calculation loop which can run independently of the audio, at a different
andrewm@0 245 // (equal or lower) priority. Audio priority is 99; priority should be generally be less than this.
andrewm@0 246 // Returns an (opaque) pointer to the created task on success; 0 on failure
andrewm@0 247 AuxiliaryTask createAuxiliaryTaskLoop(void (*functionToCall)(void), int priority, const char *name)
andrewm@0 248 {
andrewm@0 249 InternalAuxiliaryTask *newTask = (InternalAuxiliaryTask*)malloc(sizeof(InternalAuxiliaryTask));
andrewm@0 250
andrewm@0 251 // Attempt to create the task
andrewm@0 252 if(rt_task_create(&(newTask->task), name, 0, priority, T_JOINABLE | T_FPU)) {
andrewm@0 253 cout << "Error: unable to create auxiliary task " << name << endl;
andrewm@0 254 free(newTask);
andrewm@0 255 return 0;
andrewm@0 256 }
andrewm@0 257
andrewm@0 258 // Populate the rest of the data structure and store it in the vector
andrewm@0 259 newTask->function = functionToCall;
andrewm@0 260 newTask->name = strdup(name);
andrewm@0 261 newTask->priority = priority;
andrewm@0 262
andrewm@0 263 gAuxTasks.push_back(newTask);
andrewm@0 264
andrewm@0 265 return (AuxiliaryTask)newTask;
andrewm@0 266 }
andrewm@0 267
andrewm@0 268 // Schedule a previously created auxiliary task. It will run when the priority rules next
andrewm@0 269 // allow it to be scheduled.
andrewm@0 270 void scheduleAuxiliaryTask(AuxiliaryTask task)
andrewm@0 271 {
andrewm@0 272 InternalAuxiliaryTask *taskToSchedule = (InternalAuxiliaryTask *)task;
andrewm@0 273
andrewm@0 274 rt_task_resume(&taskToSchedule->task);
andrewm@0 275 }
andrewm@0 276
andrewm@0 277 // Calculation loop that can be used for other tasks running at a lower
andrewm@0 278 // priority than the audio thread. Simple wrapper for Xenomai calls.
andrewm@0 279 // Treat the argument as containing the task structure
andrewm@0 280 void auxiliaryTaskLoop(void *taskStruct)
andrewm@0 281 {
andrewm@0 282 // Get function to call from the argument
andrewm@0 283 void (*auxiliary_function)(void) = ((InternalAuxiliaryTask *)taskStruct)->function;
andrewm@0 284 const char *name = ((InternalAuxiliaryTask *)taskStruct)->name;
andrewm@0 285
andrewm@0 286 // Wait for a notification
andrewm@0 287 rt_task_suspend(NULL);
andrewm@0 288
andrewm@0 289 while(!gShouldStop) {
andrewm@0 290 // Then run the calculations
andrewm@0 291 auxiliary_function();
andrewm@0 292
andrewm@0 293 // Wait for a notification
andrewm@0 294 rt_task_suspend(NULL);
andrewm@0 295 }
andrewm@0 296
andrewm@0 297 if(gRTAudioVerbose == 1)
andrewm@0 298 rt_printf("auxiliary task %s ended\n", name);
andrewm@0 299 }
andrewm@0 300
andrewm@0 301 // startAudio() should be called only after initAudio() successfully completes.
andrewm@0 302 // It launches the real-time Xenomai task which runs the audio loop. Returns 0
andrewm@0 303 // on success.
andrewm@0 304
andrewm@5 305 int BeagleRT_startAudio()
andrewm@0 306 {
andrewm@0 307 // Create audio thread with the highest priority
andrewm@0 308 if(rt_task_create(&gRTAudioThread, gRTAudioThreadName, 0, 99, T_JOINABLE | T_FPU)) {
andrewm@0 309 cout << "Error: unable to create Xenomai audio thread" << endl;
andrewm@0 310 return -1;
andrewm@0 311 }
andrewm@0 312
andrewm@0 313 // Start all RT threads
andrewm@0 314 if(rt_task_start(&gRTAudioThread, &audioLoop, 0)) {
andrewm@0 315 cout << "Error: unable to start Xenomai audio thread" << endl;
andrewm@0 316 return -1;
andrewm@0 317 }
andrewm@0 318
andrewm@0 319 // The user may have created other tasks. Start those also.
andrewm@0 320 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 321 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 322 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 323
andrewm@0 324 if(rt_task_start(&(taskStruct->task), &auxiliaryTaskLoop, taskStruct)) {
andrewm@0 325 cerr << "Error: unable to start Xenomai task " << taskStruct->name << endl;
andrewm@0 326 return -1;
andrewm@0 327 }
andrewm@0 328 }
andrewm@0 329
andrewm@0 330 return 0;
andrewm@0 331 }
andrewm@0 332
andrewm@0 333 // Stop the PRU-based audio from running and wait
andrewm@0 334 // for the tasks to complete before returning.
andrewm@0 335
andrewm@5 336 void BeagleRT_stopAudio()
andrewm@0 337 {
andrewm@0 338 // Tell audio thread to stop (if this hasn't been done already)
andrewm@0 339 gShouldStop = true;
andrewm@0 340
andrewm@5 341 if(gRTAudioVerbose)
andrewm@5 342 cout << "Stopping audio...\n";
andrewm@5 343
andrewm@0 344 // Now wait for threads to respond and actually stop...
andrewm@0 345 rt_task_join(&gRTAudioThread);
andrewm@0 346
andrewm@0 347 // Stop all the auxiliary threads too
andrewm@0 348 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 349 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 350 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 351
andrewm@0 352 // Wake up each thread and join it
andrewm@0 353 rt_task_resume(&(taskStruct->task));
andrewm@0 354 rt_task_join(&(taskStruct->task));
andrewm@0 355 }
andrewm@0 356 }
andrewm@0 357
andrewm@0 358 // Free any resources associated with PRU real-time audio
andrewm@5 359 void BeagleRT_cleanupAudio()
andrewm@0 360 {
andrewm@0 361 cleanup_render();
andrewm@0 362
andrewm@0 363 // Clean up the auxiliary tasks
andrewm@0 364 vector<InternalAuxiliaryTask*>::iterator it;
andrewm@0 365 for(it = gAuxTasks.begin(); it != gAuxTasks.end(); it++) {
andrewm@0 366 InternalAuxiliaryTask *taskStruct = *it;
andrewm@0 367
andrewm@0 368 // Free the name string and the struct itself
andrewm@0 369 free(taskStruct->name);
andrewm@0 370 free(taskStruct);
andrewm@0 371 }
andrewm@0 372 gAuxTasks.clear();
andrewm@0 373
andrewm@0 374 if(gPRU != 0)
andrewm@0 375 delete gPRU;
andrewm@0 376 if(gAudioCodec != 0)
andrewm@0 377 delete gAudioCodec;
andrewm@0 378
andrewm@0 379 if(gAmplifierMutePin >= 0)
andrewm@0 380 gpio_unexport(gAmplifierMutePin);
andrewm@0 381 gAmplifierMutePin = -1;
andrewm@0 382 }
andrewm@0 383
andrewm@5 384 // Set the level of the DAC; affects all outputs (headphone, line, speaker)
andrewm@5 385 // 0dB is the maximum, -63.5dB is the minimum; 0.5dB steps
andrewm@5 386 int BeagleRT_setDACLevel(float decibels)
andrewm@5 387 {
andrewm@5 388 if(gAudioCodec == 0)
andrewm@5 389 return -1;
andrewm@5 390 return gAudioCodec->setDACVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 391 }
andrewm@5 392
andrewm@5 393 // Set the level of the ADC
andrewm@5 394 // 0dB is the maximum, -12dB is the minimum; 1.5dB steps
andrewm@5 395 int BeagleRT_setADCLevel(float decibels)
andrewm@5 396 {
andrewm@5 397 if(gAudioCodec == 0)
andrewm@5 398 return -1;
andrewm@5 399 return gAudioCodec->setADCVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 400 }
andrewm@5 401
andrewm@5 402 // Set the level of the onboard headphone amplifier; affects headphone
andrewm@5 403 // output only (not line out or speaker)
andrewm@5 404 // 0dB is the maximum, -63.5dB is the minimum; 0.5dB steps
andrewm@5 405 int BeagleRT_setHeadphoneLevel(float decibels)
andrewm@5 406 {
andrewm@5 407 if(gAudioCodec == 0)
andrewm@5 408 return -1;
andrewm@5 409 return gAudioCodec->setHPVolume((int)floorf(decibels * 2.0 + 0.5));
andrewm@5 410 }
andrewm@5 411
andrewm@5 412 // Mute or unmute the onboard speaker amplifiers
andrewm@5 413 // mute == 0 means unmute; otherwise mute
andrewm@5 414 // Returns 0 on success
andrewm@5 415 int BeagleRT_muteSpeakers(int mute)
andrewm@5 416 {
andrewm@5 417 int pinValue = mute ? LOW : HIGH;
andrewm@5 418
andrewm@5 419 // Check that we have an enabled pin for controlling the mute
andrewm@5 420 if(gAmplifierMutePin < 0)
andrewm@5 421 return -1;
andrewm@5 422
andrewm@5 423 return gpio_set_value(gAmplifierMutePin, pinValue);
andrewm@5 424 }
andrewm@5 425
andrewm@0 426 // Set the verbosity level
andrewm@0 427 void setVerboseLevel(int level)
andrewm@0 428 {
andrewm@0 429 gRTAudioVerbose = level;
andrewm@0 430 }