How to run copier.copyMs() in a separate Task #1042
-
I wish to have tones of a set frequency volume and duration in my project. These are for keypad feedback and alarm beeps. I have this function based on an example: If I call playV() then I get my tones to give audio output. I see in this example Can you suggest a way to set up the tone parameters as and when needed which will allow the copier.copy() to run in a separate task and so play tomes in the back ground? |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
I would suggest to use a queue with objects that contain the input parameters of your function to drive your sound task. |
Beta Was this translation helpful? Give feedback.
-
Phil
Thanks for the suggestion.
I attach my attempt at Streaming using a freeRTOS task. I would welcome
any observations. I built this in PlatformIO rather than the Arduino IDE
As the copy function can run for a long time I set up a low priority
task with a yield in the loop.
As I understand it this long task has to have a low priority else the
Yield would not give other tasks the opportunity to run.
This also ensure the Idle task can reset the watchdog.
This application works with no other tasks running. Time will tell
whether this approach will work with a multi tasked application.
The test code plays a little tune!
Cheers
Ted
/**
* @file audio stream of a tone in a low priority freeRTOS task
* @author Ted Finch
* @brief Uses Audio Tools by pschatzman
* @copyright GPLv3
*/
/**
* @file streams-generator-i2s.ino
* @author Phil Schatzmann
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-stream/streams-generator-i2s/README.md
* @copyright GPLv3
*/
//#define XIAOESP32S3 //which board are we using
#define QTPYESP32S3
#include <AudioTools.h>
AudioInfo audioinfo(44100, 2, 16); // sample_rate, channels, bits_per_sample
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
I2SStream output;
VolumeStream vol(output);
StreamCopy copier(vol, sound); // copies sound to out
// I2S write task
TaskHandle_t m_i2sWriterTaskHandle;
// i2s command queue
#define AudioLog Info //values can be Debug, Info, Warning, Error
const TickType_t xCmdQTicksToWait = pdMS_TO_TICKS( 10 ); //max wait on queue
QueueHandle_t m_i2sCommandQ;
/**
* @brief Tone data structure used in I2S command
*/
typedef struct {
float freq; /* frequency of new tone */
float amplitute; /* amplitute of tone range 0 - 1.0 */
uint16_t duration_ms; /* duration of tone in ms */
}
i2stone_t;
void debugcommand(i2stone_t i2stone){
#ifndef COPY_LOG_OFF
LOGI("Tone with freq %.2f , amplitute %.2f , duration %u MS", (float) i2stone.freq, (float) i2stone.amplitute , (unsigned int)i2stone.duration_ms);
#endif
}
void i2sWriterTask(void *param)
{
//wait on the command queue then copy data to audio buffer
//this can be a long process so yield in the loop
//This task has 0 priority so a Yield will give CPU to higher priority tasks
i2stone_t i2stone;
while ( true) {
if (xQueueReceive(m_i2sCommandQ, &i2stone, portMAX_DELAY) == pdPASS) {
debugcommand(i2stone);
#ifndef COPY_LOG_OFF
LOGI("A Tone with freq %.2f , amplitute %.2f , duration %u MS", (unsigned int) &i2stone.freq, &i2stone.amplitute , (unsigned int)&i2stone.duration_ms);
#endif
sineWave.setFrequency(i2stone.freq);
vol.setVolume(i2stone.amplitute);
//from copyMS
size_t pages = AudioTime::toBytes(i2stone.duration_ms, audioinfo) / copier.bufferSize();
#ifndef COPY_LOG_OFF
LOGI("CopyMs %u MS needs %u pages", (unsigned int) i2stone.duration_ms, (unsigned int)pages);
#endif
//from copyN
for (size_t j=0;j<pages;j++){
copier.copy();
taskYIELD(); //Yield else the WatchDog will bark
}
}
}
}
void buzz(float frequency, float volume, uint16_t duration_ms)
{ // put tone parameters into the structure and send to the i2stone queue
// the queue is processed in the I2SWriter task
i2stone_t i2stone;
i2stone.freq = frequency;
i2stone.amplitute = volume;
i2stone.duration_ms = duration_ms;
xQueueSend(m_i2sCommandQ, &i2stone, xCmdQTicksToWait) ;
}
void setupaudio(){
Serial.println("starting I2S...");
auto config = output.defaultConfig(TX_MODE);
#ifdef XIAOESP32S3 //which board are we using
config.pin_bck = 9;
config.pin_ws = 7;
config.pin_data = 44;
#else
#ifdef QTPYESP32S3
config.pin_bck = 35;
config.pin_ws = 36;
config.pin_data = 16;
#endif
#endif
output.begin(config);
#ifdef AudioLog
AudioLogger::instance().begin(Serial, AudioLogger::Info);
#endif
//Setup sine wave generator with some initial settings
sineWave.begin(audioinfo, N_B4);
// set initial volume level for the volume control
vol.begin(config);
vol.setVolume(0.3);
buzz(N_C4, 0.5,100);
vTaskDelay(pdMS_TO_TICKS( 120));
buzz(N_A4, 0.5,100);
vTaskDelay(pdMS_TO_TICKS( 120));
}
void setupTasksQueues(){
//set up a low priority task to run audiooutput
TaskHandle_t writerTaskHandle;
xTaskCreate(i2sWriterTask, "i2s Writer Task", 4096, NULL , tskIDLE_PRIORITY, &writerTaskHandle);
//creat a queue for commands, can hold 4 commands
m_i2sCommandQ = xQueueCreate( 4, sizeof( i2stone_t) );
}
// Arduino Setup
void setup(void) {
// Open Serial
Serial.begin(11200);
//while(!Serial); //wait for serial when testing
setupTasksQueues();
setupaudio();// start I2S
Serial.println("started...");
Serial.println("Start a Tune!");
vTaskDelay(pdMS_TO_TICKS( 5000));//delay here just in case
}
// Arduino loop - copy sound to out
void loop() {
// normally nothing to do here? - everything should be taken care of by tasks
//send command to audio queue, use a delay after each note
buzz(N_C4, 0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_C4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_A4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_A4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_G4,0.7,1000);
vTaskDelay(pdMS_TO_TICKS( 1020));
//
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_D4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_D4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_C4,0.7,1000);
vTaskDelay(pdMS_TO_TICKS( 1020));
//
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_D4,0.7,1000);
vTaskDelay(pdMS_TO_TICKS( 1020));
//
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_G4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_F4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_E4,0.5,500);
vTaskDelay(pdMS_TO_TICKS( 520));
buzz(N_D4,0.7,1000);
vTaskDelay(pdMS_TO_TICKS( 1020));
Serial.println("Tune done!");
}
|
Beta Was this translation helpful? Give feedback.
-
I recommend free-rtos-addon which usually leads to much shorter programs. |
Beta Was this translation helpful? Give feedback.
I would suggest to use a queue with objects that contain the input parameters of your function to drive your sound task.
You can e.g use my QueueFreeRTOS class for this.
So play(float frequency, float volume, int durationMs) would fill this queue and the task would just call your playV method.