Teensy 4.0 + Audio Shield / Bluetooth DM-10 module / MIT app inventor

Helmut

Member
I use a Teensy 4.0 with Audio Shield and try to control the Audio Shield via Bluetooth (DM10) and MIT app inventor.
All buttons / switches work well but with the slider / potentiometer I have problems.
I send via BLE values between 0-1023 to an analog input (analogWrite / analogRead) -> analogWrite(PinX, value) and after a short delay I try to read this PinX via analogRead(PinX) to adjust the volume.

Where is my error?

sketch -> see below

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>
#define HWSERIAL Serial1 // Teensy 4.0 RX1 PIN0 / TX1 PIN1



AudioInputI2S i2s1; //xy=359,315
AudioFilterStateVariable filter1; //xy=446,176
AudioFilterStateVariable filter2; //xy=446,310
AudioMixer4 mixer1; // mixers to combine wav file and audio shield inputs
AudioMixer4 mixer2;

// Use one of these 3 output types: Digital I2S, Digital S/PDIF, or Analog DAC
AudioOutputI2S audioOutput;
//AudioOutputSPDIF audioOutput;
//AudioOutputAnalog audioOutput;

// wire up the interfaces between audio components with patch cords
// mixer inputs
AudioConnection patchCord1(i2s1, 0, filter1, 0); // left channels into mixer 1
AudioConnection patchCord3(filter1, 0, mixer1, 0);
AudioConnection patchCord4(filter1, 1, mixer1, 1);
AudioConnection patchCord5(filter1, 2, mixer1, 2);

AudioConnection patchCord2(i2s1, 1, filter2, 0);
AudioConnection patchCord6(filter2, 0, mixer2, 0);
AudioConnection patchCord7(filter2, 1, mixer2, 1);
AudioConnection patchCord8(filter2, 2, mixer2, 2);

// mixer outputs
AudioConnection patchCord9(mixer1, 0, audioOutput, 0);
AudioConnection patchCord10(mixer2, 0, audioOutput, 1);

// object to allow control of the SGTL5000 audio shield settings
AudioControlSGTL5000 sgtl5000_1;


// Variables used for incoming data
const byte maxDataLength = 20;
char receivedChars[21] ;
boolean newData = false;


// Constants for hardware / buttons and potentiometers
const byte SWITCH_PIN[] = {2,3,4}; // 2 = bass, 3 = band, 4 = high
const int SV = 18; // Volume
const int SF = 19; // Frequence


// audio shield volume
int masterVolume = 0;


void setup() {
for (byte pin = 0; pin < 3; pin++)
{
// Set the SWITCH pins for output and make them LOW
pinMode(SWITCH_PIN[pin], INPUT_PULLUP);
digitalWrite(SWITCH_PIN[pin],LOW);
}
pinMode(SV, INPUT_PULLUP);
digitalWrite(SV,LOW);
pinMode(SF, INPUT_PULLUP);
digitalWrite(SF,LOW);

Serial.begin(115200);
Serial.println(" ");

// open software serial connection to the Bluetooth module.
HWSERIAL.begin(115200);
Serial.println("HWSERIAL HM-10 started at 115200");

newData = false;



// Audio connections require memory to work. For more
// detailed information, see the MemoryAndCpuUsage example
AudioMemory(20);


// comment these out if not using the audio adaptor board.
Serial.print("init audio shield...");
sgtl5000_1.enable();
// audioShield.inputSelect(inputChSelect); // select mic or line-in for audio shield input source
sgtl5000_1.volume(0.5);
Serial.println("done.");

mixer1.gain(0, 0.0);
mixer1.gain(1, 1.0); // default to hearing band-pass signal
mixer1.gain(2, 0.0);
mixer1.gain(3, 0.0);

mixer2.gain(0, 0.0);
mixer2.gain(1, 1.0);
mixer2.gain(2, 0.0);
mixer2.gain(3, 0.0);
delay(1000);
}


void loop()
{

recvWithStartEndMarkers(); // check to see if we have received any new commands
if (newData) { processCommand(); } // if we have a new command do something about it
}



/*
****************************************
* Function processCommand
* parses data commands contained in receivedChars[]
* receivedChars[] has not been checked for errors
*
* passed:
*
* global:
* receivedChars[]
* newData
*
* Returns:
*
* Sets:
* receivedChars[]
* newData
*/
void processCommand(){

Serial.print("receivedChars = "); Serial.println(receivedChars);

if (receivedChars[0] == 'S') // do we have a SWITCH command?
{
// we know the SWITCH command has a fixed length "S00"
// and the value at pos 1 is the SWITCH and the value at pos 2 is 0 or 1 (on/off).
// 0 and 1 is the same as LOW and HIGH so we can use 0/1 instead of LOW/HIGH

byte SWITCHnum = receivedChars[1] - 48; // convert ascii to value by subtracting 48
boolean SWITCHstatus = receivedChars[2] - 48;

Serial.print(SWITCHnum);
Serial.println(SWITCHstatus);


if (SWITCHnum == 0 && SWITCHstatus == 1) {
digitalWrite(SWITCH_PIN[0],HIGH);
Serial.println("Low Pass Signal");
mixer1.gain(0, 1.0); // hear low-pass signal
mixer1.gain(1, 0.0);
mixer1.gain(2, 0.0);
mixer2.gain(0, 1.0);
mixer2.gain(1, 0.0);
mixer2.gain(2, 0.0);

}
if (SWITCHnum == 1 && SWITCHstatus == 1) {
digitalWrite(SWITCH_PIN[1],HIGH);
Serial.println("Band Pass Signal");
mixer1.gain(0, 0.0);
mixer1.gain(1, 1.0); // hear band-pass signal
mixer1.gain(2, 0.0);
mixer2.gain(0, 0.0);
mixer2.gain(1, 1.0);
mixer2.gain(2, 0.0);

}
if (SWITCHnum == 2 && SWITCHstatus == 1) {
digitalWrite(SWITCH_PIN[2],HIGH);
Serial.println("High Pass Signal");
mixer1.gain(0, 0.0);
mixer1.gain(1, 0.0);
mixer1.gain(2, 1.0); // hear high-pass signal
mixer2.gain(0, 0.0);
mixer2.gain(1, 0.0);
mixer2.gain(2, 1.0);

}

if (SWITCHnum == 0 && SWITCHstatus == 0) {
digitalWrite(SWITCH_PIN[1],LOW);
Serial.println("Low Pass Signal");

}

if (SWITCHnum == 1 && SWITCHstatus == 0) {
digitalWrite(SWITCH_PIN[0],LOW);
Serial.println("Band Pass Signal");
}

if (SWITCHnum == 2 && SWITCHstatus == 0) {
digitalWrite(SWITCH_PIN[2],LOW);
Serial.println("High Pass Signal");

}
}

if (receivedChars[0] == 'V') // do we have a slider command?
{


byte thousands = (receivedChars[1]-48) * 1000;
byte hundreds = (receivedChars[2]-48) * 100;
byte tens = (receivedChars[3]-48) * 10;
byte units = receivedChars[4]-48;
byte value = thousands + hundreds + tens + units;
Serial.print("Volume = ");
Serial.println(value);

// read volume control SLIDER SV and set audio shield volume if required


analogWrite(SV, value);
delay(500);

int vol = analogRead(SV);
if (vol != masterVolume) {
masterVolume = vol;

sgtl5000_1.volume((float)vol / 1023); // audio shield headphone out volume (optional)- original 1023
mixer1.gain(0, (float)vol / 1023); // software mixer input channel volume
mixer1.gain(1, (float)vol / 1023);
mixer2.gain(0, (float)vol / 1023);
mixer2.gain(1, (float)vol / 1023);
}
}


if (receivedChars[0] == 'F') // do we have a slider command?
{



byte thousands = (receivedChars[1]-48) * 1000;
byte hundreds = (receivedChars[2]-48) * 100;
byte tens = (receivedChars[3]-48) * 10;
byte units = receivedChars[4]-48;
byte value = thousands + hundreds + tens + units;

analogWrite(SF, value);
delay(500);

int knob = analogRead(SF);

float freq = expf((float)knob / 150.0) * 10.0 + 80.0; // original 150.0) * 10.0 + 80.0;
filter1.frequency(freq);
filter2.frequency(freq);
Serial.print("frequency = ");
Serial.println(freq);
delay(200);

}


receivedChars[0] = '\0';
newData = false;
}



// function recvWithStartEndMarkers by Robin2 of the Arduino forums
// See http://forum.arduino.cc/index.php?topic=288234.0
/*
****************************************
* Function recvWithStartEndMarkers
* reads serial data and returns the content between a start marker and an end marker.
*
* passed:
*
* global:
* receivedChars[]
* newData
*
* Returns:
*
* Sets:
* newData
* receivedChars
*
*/
void recvWithStartEndMarkers()
{
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '[';
char endMarker = ']';
char rc;

if (HWSERIAL.available() > 0)
{
rc = HWSERIAL.read();
if (recvInProgress == true)
{
if (rc != endMarker)
{
receivedChars[ndx] = rc;
ndx++;
if (ndx > maxDataLength) { ndx = maxDataLength; }
}
else
{
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
}
}
else if (rc == startMarker) { recvInProgress = true; }
}
}
 
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>
#define HWSERIAL Serial1 // Teensy 4.0 RX1 PIN0 / TX1 PIN1



AudioInputI2S i2s1; //xy=359,315
AudioFilterStateVariable filter1; //xy=446,176
AudioFilterStateVariable filter2; //xy=446,310
AudioMixer4 mixer1; // mixers to combine wav file and audio shield inputs
AudioMixer4 mixer2;

// Use one of these 3 output types: Digital I2S, Digital S/PDIF, or Analog DAC
AudioOutputI2S audioOutput;
//AudioOutputSPDIF audioOutput;
//AudioOutputAnalog audioOutput;

// wire up the interfaces between audio components with patch cords
// mixer inputs
AudioConnection patchCord1(i2s1, 0, filter1, 0); // left channels into mixer 1
AudioConnection patchCord3(filter1, 0, mixer1, 0);
AudioConnection patchCord4(filter1, 1, mixer1, 1);
AudioConnection patchCord5(filter1, 2, mixer1, 2);

AudioConnection patchCord2(i2s1, 1, filter2, 0);
AudioConnection patchCord6(filter2, 0, mixer2, 0);
AudioConnection patchCord7(filter2, 1, mixer2, 1);
AudioConnection patchCord8(filter2, 2, mixer2, 2);

// mixer outputs
AudioConnection patchCord9(mixer1, 0, audioOutput, 0);
AudioConnection patchCord10(mixer2, 0, audioOutput, 1);

// object to allow control of the SGTL5000 audio shield settings
AudioControlSGTL5000 sgtl5000_1;


// Variables used for incoming data
const byte maxDataLength = 20;
char receivedChars[21];
boolean newData = false;


// Constants for hardware / buttons and potentiometers
const byte SWITCH_PIN[] = { 2,3,4 }; // 2 = bass, 3 = band, 4 = high
const int SV = 18; // Volume
const int SF = 19; // Frequence


// audio shield volume
int masterVolume = 0;


void setup() {
	for (byte pin = 0; pin < 3; pin++)
	{
		// Set the SWITCH pins for output and make them LOW
		pinMode(SWITCH_PIN[pin], INPUT_PULLUP);
		digitalWrite(SWITCH_PIN[pin], LOW);
	}
	pinMode(SV, INPUT_PULLUP);
	digitalWrite(SV, LOW);
	pinMode(SF, INPUT_PULLUP);
	digitalWrite(SF, LOW);

	Serial.begin(115200);
	Serial.println(" ");

	// open software serial connection to the Bluetooth module.
	HWSERIAL.begin(115200);
	Serial.println("HWSERIAL HM-10 started at 115200");

	newData = false;



	// Audio connections require memory to work. For more
	// detailed information, see the MemoryAndCpuUsage example
	AudioMemory(20);


	// comment these out if not using the audio adaptor board.
	Serial.print("init audio shield...");
	sgtl5000_1.enable();
	// audioShield.inputSelect(inputChSelect); // select mic or line-in for audio shield input source
	sgtl5000_1.volume(0.5);
	Serial.println("done.");

	mixer1.gain(0, 0.0);
	mixer1.gain(1, 1.0); // default to hearing band-pass signal
	mixer1.gain(2, 0.0);
	mixer1.gain(3, 0.0);

	mixer2.gain(0, 0.0);
	mixer2.gain(1, 1.0);
	mixer2.gain(2, 0.0);
	mixer2.gain(3, 0.0);
	delay(1000);
}


void loop()
{

	recvWithStartEndMarkers(); // check to see if we have received any new commands
	if (newData) { processCommand(); } // if we have a new command do something about it
}



/*
****************************************
* Function processCommand
* parses data commands contained in receivedChars[]
* receivedChars[] has not been checked for errors
*
* passed:
*
* global:
* receivedChars[]
* newData
*
* Returns:
*
* Sets:
* receivedChars[]
* newData
*/
void processCommand() {

	Serial.print("receivedChars = "); Serial.println(receivedChars);

	if (receivedChars[0] == 'S') // do we have a SWITCH command?
	{
		// we know the SWITCH command has a fixed length "S00"
		// and the value at pos 1 is the SWITCH and the value at pos 2 is 0 or 1 (on/off).
		// 0 and 1 is the same as LOW and HIGH so we can use 0/1 instead of LOW/HIGH

		byte SWITCHnum = receivedChars[1] - 48; // convert ascii to value by subtracting 48
		boolean SWITCHstatus = receivedChars[2] - 48;

		Serial.print(SWITCHnum);
		Serial.println(SWITCHstatus);


		if (SWITCHnum == 0 && SWITCHstatus == 1) {
			digitalWrite(SWITCH_PIN[0], HIGH);
			Serial.println("Low Pass Signal");
			mixer1.gain(0, 1.0); // hear low-pass signal
			mixer1.gain(1, 0.0);
			mixer1.gain(2, 0.0);
			mixer2.gain(0, 1.0);
			mixer2.gain(1, 0.0);
			mixer2.gain(2, 0.0);

		}
		if (SWITCHnum == 1 && SWITCHstatus == 1) {
			digitalWrite(SWITCH_PIN[1], HIGH);
			Serial.println("Band Pass Signal");
			mixer1.gain(0, 0.0);
			mixer1.gain(1, 1.0); // hear band-pass signal
			mixer1.gain(2, 0.0);
			mixer2.gain(0, 0.0);
			mixer2.gain(1, 1.0);
			mixer2.gain(2, 0.0);

		}
		if (SWITCHnum == 2 && SWITCHstatus == 1) {
			digitalWrite(SWITCH_PIN[2], HIGH);
			Serial.println("High Pass Signal");
			mixer1.gain(0, 0.0);
			mixer1.gain(1, 0.0);
			mixer1.gain(2, 1.0); // hear high-pass signal
			mixer2.gain(0, 0.0);
			mixer2.gain(1, 0.0);
			mixer2.gain(2, 1.0);

		}

		if (SWITCHnum == 0 && SWITCHstatus == 0) {
			digitalWrite(SWITCH_PIN[1], LOW);
			Serial.println("Low Pass Signal");

		}

		if (SWITCHnum == 1 && SWITCHstatus == 0) {
			digitalWrite(SWITCH_PIN[0], LOW);
			Serial.println("Band Pass Signal");
		}

		if (SWITCHnum == 2 && SWITCHstatus == 0) {
			digitalWrite(SWITCH_PIN[2], LOW);
			Serial.println("High Pass Signal");

		}
	}

	if (receivedChars[0] == 'V') // do we have a slider command?
	{


		byte thousands = (receivedChars[1] - 48) * 1000;
		byte hundreds = (receivedChars[2] - 48) * 100;
		byte tens = (receivedChars[3] - 48) * 10;
		byte units = receivedChars[4] - 48;
		byte value = thousands + hundreds + tens + units;
		Serial.print("Volume = ");
		Serial.println(value);

		// read volume control SLIDER SV and set audio shield volume if required


		analogWrite(SV, value);
		delay(500);

		int vol = analogRead(SV);
		if (vol != masterVolume) {
			masterVolume = vol;

			sgtl5000_1.volume((float)vol / 1023); // audio shield headphone out volume (optional)- original 1023
			mixer1.gain(0, (float)vol / 1023); // software mixer input channel volume
			mixer1.gain(1, (float)vol / 1023);
			mixer2.gain(0, (float)vol / 1023);
			mixer2.gain(1, (float)vol / 1023);
		}
	}


	if (receivedChars[0] == 'F') // do we have a slider command?
	{



		byte thousands = (receivedChars[1] - 48) * 1000;
		byte hundreds = (receivedChars[2] - 48) * 100;
		byte tens = (receivedChars[3] - 48) * 10;
		byte units = receivedChars[4] - 48;
		byte value = thousands + hundreds + tens + units;

		analogWrite(SF, value);
		delay(500);

		int knob = analogRead(SF);

		float freq = expf((float)knob / 150.0) * 10.0 + 80.0; // original 150.0) * 10.0 + 80.0;
		filter1.frequency(freq);
		filter2.frequency(freq);
		Serial.print("frequency = ");
		Serial.println(freq);
		delay(200);

	}


	receivedChars[0] = '\0';
	newData = false;
}



// function recvWithStartEndMarkers by Robin2 of the Arduino forums
// See http://forum.arduino.cc/index.php?topic=288234.0
/*
****************************************
* Function recvWithStartEndMarkers
* reads serial data and returns the content between a start marker and an end marker.
*
* passed:
*
* global:
* receivedChars[]
* newData
*
* Returns:
*
* Sets:
* newData
* receivedChars
*
*/
void recvWithStartEndMarkers()
{
	static boolean recvInProgress = false;
	static byte ndx = 0;
	char startMarker = '[';
	char endMarker = ']';
	char rc;

	if (HWSERIAL.available() > 0)
	{
		rc = HWSERIAL.read();
		if (recvInProgress == true)
		{
			if (rc != endMarker)
			{
				receivedChars[ndx] = rc;
				ndx++;
				if (ndx > maxDataLength) { ndx = maxDataLength; }
			}
			else
			{
				receivedChars[ndx] = '\0'; // terminate the string
				recvInProgress = false;
				ndx = 0;
				newData = true;
			}
		}
		else if (rc == startMarker) { recvInProgress = true; }
	}
}
If you want help with your problem, it helps if you display your code between code tags using the # button.
 
I use a Teensy 4.0 with Audio Shield and try to control the Audio Shield via Bluetooth (DM10) and MIT app inventor.
All buttons / switches work well but with the slider / potentiometer I have problems.
I send via BLE values between 0-1023 to an analog input (analogWrite / analogRead) -> analogWrite(PinX, value) and after a short delay I try to read this PinX via analogRead(PinX) to adjust the volume.

Where is my error?
I can see two (one of them repeated):
  • you say "I have problems", but you don't tell us what they are
  • this code (which is in there twice. Pro tip - don't do this: write a function and call that as needed...)

Code:
    if (receivedChars[0] == 'V')      // do we have a slider command?
    {

              
        byte thousands = (receivedChars[1]-48) * 1000;
        byte hundreds = (receivedChars[2]-48) * 100;
        byte tens = (receivedChars[3]-48) * 10;
        byte units = receivedChars[4]-48;
        byte value = thousands + hundreds + tens + units;
        Serial.print("Volume = ");
        Serial.println(value);
Both hundreds and thousands are likely to overflow a byte value. In this instance you've even printed out the result (though not for the 'F' command), so I'm surprised you didn't notice the discrepancy yourself.
 
Here is how I should have done it

Code:
int getFourDigitInt(const *char ac) // ac=asciiDec
{
    return (ac[0]-0x30)*1000+(ac[1]-0x30)*100+(ac[2]-0x30)*10+(ac[3]-0x30);
}

usage:
Code:
if (receivedChars[0] == 'V')      // do we have a slider command?
{
    int value = getFourDigitInt(&receivedChars[1]);
    Serial.print("Volume = ");
    Serial.println(value);
}

note. the getFourDigitInt don't take into account that any of the characters
may not be a valid ascii number

there is also atoi but that would require a null char after receivedChars[4]
to work as intended.
 
Code:
// works given n, or NULL terminated with large n,
// for any length of number (that fits an int!)
int getNdigitInt(char* buf, int n)
{
    int value = 0;
    while (n-- && *buf)
        value = value*10 + (*buf++) - '0';
    return value;
}
...
int value = getNdigitInt(receivedChars+1,4);
...or just use atoi() as you suggest, since recvWithStartEndMarkers() does NULL terminate the string it returns.

This also looks highly suspect:
Code:
pinMode(SV, INPUT_PULLUP);
digitalWrite(SV, LOW);
...
analogWrite(SV, value);
delay(500);
int vol = analogRead(SV);
SV (aka pin 18) is a PWM-capable digital I/O pin: the Teensy 4.x boards don't have DAC outputs. It's initially configured as an input, immediately set to a low digital output value, then later an analogue (PWM) level between 0 and 1023 (legal range is 0 to 256) is output, and after burning 300,000,000 CPU cycles an analogRead() is done. At a guess this will only ever return values (close to) 0 or 1023, as the read will catch the PWM output either high or low, depending.

This looks like a massively long-winded way of failing to copy "value" to "vol", but it's hard to guess the original intent.
 
I have two slider -> one to change the volume and the other to change the frequency.
Both silders send via bluetooth values (slider function with MIT app inventor - V for volume / F for frequency puls values 0-1023).
I have tried that function to dim LEDs and it works perfect with "analogWrite(LEDPin, value)" but it does not work to change the volume or frequency on the audio shield.

sketch part1 for volume:

Code:
 if (receivedChars[0] == 'V')      // do we have a slider command?
    {

              
        byte thousands = (receivedChars[1]-48) * 1000;
        byte hundreds = (receivedChars[2]-48) * 100;
        byte tens = (receivedChars[3]-48) * 10;
        byte units = receivedChars[4]-48;
        byte value = thousands + hundreds + tens + units;
        Serial.print("Volume = ");
        Serial.println(value);
        analogWrite(PINVol, value); // Pin14, A0 Teensy 4.0
        delay(500);

The problem is that the next part of the sketch does not work :-(

sketch part2 for volume:

Code:
int vol = analogRead(PINVol);
		if (vol != masterVolume) {
			masterVolume = vol;

			sgtl5000_1.volume((float)vol / 1023); // audio shield headphone out volume (optional)- original 1023
			mixer1.gain(0, (float)vol / 1023); // software mixer input channel volume
			mixer1.gain(1, (float)vol / 1023);
			mixer2.gain(0, (float)vol / 1023);
			mixer2.gain(1, (float)vol / 1023);
		}
	}

I get only one value with the analogRead function...as soon as I move the slider away from "0" I get only the value ascii 1023 / converted 255.
I need the function "analogRead" so that I can read the values of A0 / A1 to simulate physical potentimeter.
Do I have a thought error or an error in my sketch?
Hopefully not both :)
 
See https://www.pjrc.com/teensy/td_pulse.html. Analogue outputs aren’t really analogue… your LEDs don’t really dim, they’re just bright for less of the time, which looks dim.

Please read the above posts properly, consider the maximum value that the byte type can hold, look at what happens when your app sends a value of 300, and fix that.

Then remove all the unnecessary code relating to analogue pins, and just scale the received value directly to control the audio objects. You’re already simulating a potentiometer in the app, no need to do it again in your code - in a microcontroller nothing is analogue, it’s all just numbers.
 
a byte can only hold values between 0-255
so if you put 256 into that byte you will get 0
257 -> 1
258 -> 2
and so on...
 
I wanted them to look that up for themselves ;) ... you learn so much more that way (generally by finding stuff you weren't looking for).

MIT App Inventor sounds interesting, I'll take a look at that sometime.
 
Dear all,

many many THANKS for your support :)

Also a big THANKS to Martyn Currey for his excellent Bluetooth + MIT App inventor description.

My project to control Teensy 4.0 + Audio Board via Bluetooth (DM-10) works!
 
Back
Top