Teensy LC and Prop Shield with APA102

Status
Not open for further replies.
Hello everyone,

I just signed in to this forum so I might as well quickly tell you that I'm a 17 year old German mechanical engineering student before asking:

Can someone here help me? :D

I am currently busy building a lightsaber with 2 60 LED Apa102 Led Strips, the Teensy Prop Shield and the Teensy LC. As a tutorial I picked this video:

https://www.youtube.com/watch?v=a0d-wT6YS4w

My initial idea, as I am completely new to microcontrollers except for BlueJ on the Pi a few years ago, was to just take the code he supplies on github and put it on my Teensy to test my (basically finished) hardware and maybe change a little bit of stuff later on - which I thought was the most sensible approach because of my lack of knowledge about this kind of programming.

But I quickly discovered that won't work because obviously the Teensy LC compilation doesn't take the code written for the 3.2.
Another difference is of course that the LEDs aren't the same (DotStar vs APA102), but I figured I'd just try it -a point to which I didn't get because three error messages popped up while compiling:

Error: selected processor does not support `smull r0,ip,r3,r5' in Thumb mode
Error: shifts in CMP/MOV instructions are only supported in unified syntax -- `mov ip,ip,asl r6'
Error: unshifted register required -- `orr r0,ip,r0,lsr r7'

So, my questions:

- Are these errors popping up due to this code not being realizable on the LC?
- If yes, do I have to get a Teensy 3.2 to get the project running or is it purely my fault for being a programming noob? ^^
- Are there any kinds of advice on how to start learning to program this stuff (note - I have more or less ok knowledge of BlueJ programming and Java doesnt seem to be very different from the code structure Arduino software uses)?

As I am fairly busy with university and train rides during the week, I can mainly work on this project and answer to forum posts on weekends, so please don't take it as disrespect/lost interest if I don't answer the same day. :)

Any help is very appreciated!

Thanks in advance,
Mirco
 
I suspect the issue is parts of the audio library does not work on the LC. The LC is an ARM M0 chip and it does not have the SIMD (single issue, multiple data) instructions used by the audio library to do the various sound processing, while the 3.2 is a M4 chip. The LC is also slower than the 3.2 and has less memory. It may be possible to remove the sound effects from the saber to get it to work on the LC, but then it may not be worth it, after all, what's a saber without the sound effects. You can do basic sound from the LC, but without the audio library, it may be much harder.

Dotstar is just Adafruit's brand name for APA102 lights.
 
I suspect the issue is parts of the audio library does not work on the LC. The LC is an ARM M0 chip and it does not have the SIMD (single issue, multiple data) instructions used by the audio library to do the various sound processing, while the 3.2 is a M4 chip. The LC is also slower than the 3.2 and has less memory. It may be possible to remove the sound effects from the saber to get it to work on the LC, but then it may not be worth it, after all, what's a saber without the sound effects. You can do basic sound from the LC, but without the audio library, it may be much harder.

Dotstar is just Adafruit's brand name for APA102 lights.

So, the first step - getting the code to run without changing something to see if everything works - would be cutting all methods using sound files/the amp?
 
So, the first step - getting the code to run without changing something to see if everything works - would be cutting all methods using sound files/the amp?

Yes.

Obviously a simpler approach would be to get a 3.2, so that you don't have to worry about the incompatibilities and concentrate on just doing the build. I remember when I was in college (some 40 years ago) and being rather budget challenged. But part of the calculations will be how much work is it to work with what you have vs. the extra cost of the Teensy 3.2. Hopefully you will stick with it if you go down the LC path, and not get too frustrated. You will learn more having to modify things to get them to fit.
 
Yes.

Obviously a simpler approach would be to get a 3.2, so that you don't have to worry about the incompatibilities and concentrate on just doing the build. I remember when I was in college (some 40 years ago) and being rather budget challenged. But part of the calculations will be how much work is it to work with what you have vs. the extra cost of the Teensy 3.2. Hopefully you will stick with it if you go down the LC path, and not get too frustrated. You will learn more having to modify things to get them to fit.

Okay, so I now removed all of the code parts that have anything to do with the sound output, changed the default state of the leds to on because I haven't wired the buttons yet, uploaded the code and out the Teensy and the Shield together - and nothing happens. The LEDs should be wired correctly, but I don't exactly know how they are controlled so it could be I overlooked something.
 
Okay, so I now removed all of the code parts that have anything to do with the sound output, changed the default state of the leds to on because I haven't wired the buttons yet, uploaded the code and out the Teensy and the Shield together - and nothing happens. The LEDs should be wired correctly, but I don't exactly know how they are controlled so it could be I overlooked something.

There you need to follow the main rule for asking about support. You would need to post with the code and pictures of how things are set up. And remember, people answer questions on a volunteer basis.
 
Maybe, just maybe I failed adding the code :*
I'm not at home atm, I'l look further into it this evening.

Edit: Source code here:


/* Presented without warranty, guarantee and support.
* By Bob Clagett - I Like To Make Stuff
*
* This is the code to accompany the lightsaber project at http://www.iliketomakestuff.com/make-lightsaber/
* Expected hardware is listed in post (Teensy 3.2, Prop Shield, DotStar LEDs)
*
* This is functional v1 of this code. Lots more I want to add to it including crash detection/sounds.
* This code is dirty, bulky, poorly documented and ugly but I finally got it all functional.
* Cleanup to come later in v2.
*/


//#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// GUItool: begin automatically generated code
//AudioPlaySerialflashRaw playFlashRaw1; //xy=149,388
//AudioPlaySerialflashRaw playHUMraw; //xy=149,388
//AudioPlaySerialflashRaw playSwingRaw; //xy=149,388
/*AudioMixer4 mixer1; //xy=445,386
AudioOutputAnalog dac1; //xy=591,379
AudioConnection patchCord4(playSwingRaw, 0, mixer1, 2);
AudioConnection patchCord1(playFlashRaw1, 0, mixer1, 1);
AudioConnection patchCord3(playHUMraw, 0, mixer1, 0);
AudioConnection patchCord2(mixer1, dac1);
// GUItool: end automatically generated code
*/
#include <Adafruit_DotStar.h>
#include <NXPMotionSense.h>

NXPMotionSense imu;
NXPSensorFusion filter;

int motionThreshold = 2;

float lastPitch = 0, lastRoll = 0, lastHeading = 0;

int buttonState = HIGH;
int bladeState = 1; //0 off, 1, fading up, 2 on, 3 fading down
int fadeStep = 0;
int fadeStepSize = 6;
int selectedColor = 0;
int lastSelectedColor = 0;
bool isAnimating = 0;
bool bladeOn = 0;
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 50;
int lastButtonState = HIGH;
int pendingPress = 0;

//BC = Blade Color
int BCbuttonState = HIGH;
long BClastDebounceTime = 0; // the last time the output pin was toggled
long BCdebounceDelay = 50;
int BClastButtonState = HIGH;
int BCpendingPress = 0;
int pendingColorPress = 0;

#define COLOR_BUTTON_PIN 1
#define POWER_BUTTON_PIN 0
#define COLOR_ORDER BGR
#define CHIPSET APA102
#define NUMPIXELS 252

#define BRIGHTNESS 100
#define FRAMES_PER_SECOND 70

//#define PROP_AMP_ENABLE 5
#define FLASH_CHIP_SELECT 6
#define LED_BUFFER_SELECT 7

#define VOLUME_POT 15

#define LED_PIN 7
Adafruit_DotStar strip = Adafruit_DotStar(
NUMPIXELS, DOTSTAR_BRG);

int outputValue = 0;
int presetColors[] = {0x0000FF,0x00FF00,0xFF0000,0xFFFFFF};
int totalPresetColors = (sizeof(presetColors)/sizeof(int));
int selectedColorIndex = 0;

int swingSounds = 4;
int lastSwingSound = swingSounds;

int clashSounds = 0;
void setup() {
delay(500); // sanity delay
imu.begin();
filter.begin(100);
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
clock_prescale_set(clock_div_1); // Enable 16 MHz on Trinket
#endif

strip.begin(); // Initialize pins for output
strip.show(); // Turn all LEDs off ASAP

pinMode(POWER_BUTTON_PIN, INPUT);
pinMode(COLOR_BUTTON_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(POWER_BUTTON_PIN, HIGH);
digitalWrite(COLOR_BUTTON_PIN, HIGH);
digitalWrite(LED_PIN, LOW);

Serial.begin(9600);
// wait up to 3 seconds for the Serial device to become available
long unsigned debug_start = millis ();
while (!Serial && ((millis () - debug_start) <= 3000))
;

Serial.println ("Start prop shield RAW player");
SPI.begin();
// Enable the amplifier on the prop shield
/*pinMode(PROP_AMP_ENABLE, OUTPUT);
digitalWrite(PROP_AMP_ENABLE, HIGH);
pinMode(LED_BUFFER_SELECT, OUTPUT);
digitalWrite(LED_BUFFER_SELECT, HIGH);

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

// Set initial volume
mixer1.gain(1, 1.5f); //other sounds
mixer1.gain(0, 0.8f); //hum
*/
// Start SerialFlash
if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
while (1)
{
Serial.println ("Cannot access SPI Flash chip");
delay (3000);
}

}
//triggerSound("saberswing1.raw");
}

void powerUpBlade(){

isAnimating = 1;
bladeState = 1;
Serial.println("Turn on blade");
bladeOn = 1;
//triggerSound("saberon.raw");
}

void powerDownBlade(){
//animate DOWN
//triggerSound("saberoff.raw");
bladeState = 3;
fadeStep = NUMPIXELS;
isAnimating = 1;
Serial.println("Turn off blade");
//stopHum();
bladeOn = 0;
}

void bladeIsOn(){

/*if(!playHUMraw.isPlaying()){
startHum();
}*/
//nothing needed here unless adding animation
}

void bladeIsAnimatingUp(){
//Serial.println("bladeIsAnimatingUp");
int midpoint = NUMPIXELS/2;
int newSection = fadeStep+fadeStepSize;
for( int j = fadeStep; j < newSection; j++) {
strip.setPixelColor(j, selectedColor);
strip.setPixelColor(NUMPIXELS-j, selectedColor);
}
Serial.println(newSection);
fadeStep = newSection;

if (fadeStep >= midpoint+fadeStepSize){

fadeStep = NUMPIXELS;
isAnimating=0;
bladeState = 2;
// delay(200);
Serial.println("blade up complete");
//startHum();
}
}

void bladeIsAnimatingDown(){
//Serial.println("bladeIsAnimatingDown");
int midpoint = NUMPIXELS/2;
int newSection = fadeStep-fadeStepSize;
for( int j = fadeStep; j > newSection; j--) {

strip.setPixelColor(j-midpoint, 0x000000);

strip.setPixelColor(midpoint+NUMPIXELS-j, 0x000000);
}
//Serial.println(fadeStep);
fadeStep = newSection;

if (fadeStep <=midpoint-fadeStepSize){
fadeStep = 0;
isAnimating=0;
bladeState = 0;
}
}

void detectMotion() {
float ax, ay, az;
float gx, gy, gz;
float mx, my, mz;
float roll, pitch, heading;

if (imu.available()) {
// Read the motion sensors
imu.readMotionSensor(ax, ay, az, gx, gy, gz, mx, my, mz);

// Update the SensorFusion filter
filter.update(gx, gy, gz, ax, ay, az, mx, my, mz);

// print the heading, pitch and roll
roll = filter.getRoll();
pitch = filter.getPitch();
heading = filter.getYaw();

// does it need a time threshold too?

float headingDiff = abs(lastHeading - heading);
float pitchDiff = abs(lastPitch - pitch);
if(lastHeading != 0){
if(pitchDiff > motionThreshold || headingDiff > motionThreshold){
//cyle through swing sounds
lastSwingSound++;
if(lastSwingSound>swingSounds){
lastSwingSound=1;
}
String swingFile = "saberswingX.raw";
swingFile.replace("X",lastSwingSound);
char charBuf[50];
swingFile.toCharArray(charBuf, 50);
//triggerSwing(charBuf); // needs sequence to iterate through
}
}
lastHeading = heading;
lastPitch = pitch;
}

}
void loop(){
// Add entropy to random number generator; we use a lot of it.
// random16_add_entropy( random() );

//handle color selector button
int BCreading = digitalRead(COLOR_BUTTON_PIN);

if (BCreading != BClastButtonState) {
// reset the debouncing timer
BClastDebounceTime = millis();
}

if ((millis() - BClastDebounceTime) > BCdebounceDelay) {
if (BCreading != BCbuttonState) {
BCbuttonState = BCreading;

if(BCbuttonState == HIGH){
nextColor();
}
}
}
BClastButtonState = BCreading;

selectedColor = presetColors[selectedColorIndex];
if(selectedColor != lastSelectedColor && bladeState != 0){
Serial.print("COLOR CHANGE");
Serial.println(selectedColorIndex);
for( int j = 0; j < NUMPIXELS; j++) {
strip.setPixelColor(j,selectedColor);
}
}
lastSelectedColor = selectedColor;

//handle blade on/off

int reading = digitalRead(POWER_BUTTON_PIN);
//debounce
if (reading != lastButtonState) {
// reset the debouncing timer
lastDebounceTime = millis();
}

if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
}
}
lastButtonState = reading;

if( pendingPress == 1 && buttonState != LOW){
buttonState = HIGH;
pendingPress = 0;
Serial.print("execute pending blade press");

if(bladeOn){
powerDownBlade();
} else {
powerUpBlade();
}
}

if (buttonState == LOW && !isAnimating) {
Serial.println("BLADE BUTTON IS PRESSED");
pendingPress = 1;
}
switch(bladeState){
case 0:
//blade is off
break;
case 1:
//blade is animating up
bladeIsAnimatingUp();
break;
case 2:
//blade is on
bladeIsOn();
break;
case 3:
//blade is animating down
bladeIsAnimatingDown();
break;
}
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
digitalWrite(LED_PIN, HIGH); // enable access to LEDs
strip.show(); // Refresh strip
digitalWrite(LED_PIN, LOW);
SPI.endTransaction(); // allow other libs to use SPI again

delay(10);

if(bladeState == 2){
detectMotion();
}

}
void nextColor(){
selectedColorIndex++;
if(selectedColorIndex >= totalPresetColors){
selectedColorIndex = 0;
}
}
/*void triggerSound(const char *filename){
playFlashRaw1.play(filename);
}
void triggerSwing(const char *filename){
if(playSwingRaw.isPlaying()==0){
playSwingRaw.play(filename);
} else {
Serial.println("already swinging");
}
}
void startHum(){
//Serial.println("startHum");
playHUMraw.play("HUM2.RAW");
Serial.println(playHUMraw.isPlaying());
}
void stopHum(){
playHUMraw.stop();
}*/
 
Last edited:
Teensy LC is also too slow to run the NXP sensor fusion code. It can run the Madgwick and Mahony libs, which are similar but not as advanced as NXP’s algorithms.
 
Ok, so I've decided to swap the Teensy LC for a 3.2, set up all the hardware and got it running, started modifying the code for my use case and so far, everything works out fine. Except for the sound.

I just have no clue at all how to upload audio files to the Prop Shield. All the programs I found out about like TeensyTransfer didn't work for me - when I tried to run the .exe the window opens and immediately closes again. I don't know anything about this whole thing and for now it seems way too complicated for me. As there seems to be no easy way to get sound working with this, I'd like to ask if anyone here can help me with this.
 
On Linux, I do the following steps. I would imagine it is similar on Mac/Windows:
  • Download the T32_teensytransfertool_with_SerFlash_CSPIN6.ino.hex from https://github.com/FrankBoesing/TeensyTransfer/tree/master/extras;
  • Program your teensy with a random program such as blink so that the Teensy Loader is running;
  • Exit the Teensy/Arduino GUI, but leave the Teensy Loader running;
  • On the Teensy Loader window, there is a file menu, click that and click on Open Hex File;
  • At the prompt, enter the location of T32_teensytransfertool_with_SerFlash_CSPIN6.ino.hex;
  • Hit the program button on the Teensy, and it should download the 3.2 file that has the transfer program loaded with pin 6 being the SD CS pin (which is appropriate for the prop shield);
  • Then download the program for your operating system and unzip/un-gzip the image;
  • Execute the program and add the -e option to erase the flash memory;
  • Execute the program and add the -l option, and it should show there are no files;
  • Execute the program and add the -w option and a file to copy to the flash memory. The filename should be in MSDOS 8+3 format (i.e. upper case, 8 letters/numbers, period, 3 letters/numbers);
  • Repeat this multiple times to load different files;
  • Execute the program and add the -l option. It should show the file(s) you uploaded;
  • Restart the Teensy/Arduino GUI, and issue the program you want to use the sound files.

I use this variant of the wavPlayer to test files in RAW format (the files must be named SDTEST1.RAW, SDTEST2.RAW, SDTEST3.RAW, and SDTEST4.RAW):

Code:
// Converted from the WavFilePlay from the Teensy release:
// hardware/teensy/avr/libraries/Audio/examples/WavFilePlayer/WavFilePlayer.ino
//
// Simple RAW file player example for the prop shield to use the Analog DAC
// and prop shield amplifier to play mono sounds.
//         http://www.pjrc.com/teensy/gui/?info=AudioOutputAnalog
//
// On the prop shield, pin 6 selects the serial flash memory controller,
// and pin 5 enables the amplifier.
//
// This example code is in the public domain.

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySerialflashRaw  playFlashRaw1;  //xy=149,388
AudioMixer4              mixer1;         //xy=445,386
AudioOutputAnalog        dac1;           //xy=591,379
AudioConnection          patchCord1(playFlashRaw1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, dac1);
// GUItool: end automatically generated code

#define PROP_AMP_ENABLE		5
#define FLASH_CHIP_SELECT	6
#define VOLUME_POT		A1

void setup() {
  Serial.begin(9600);

  // wait up to 3 seconds for the Serial device to become available
  long unsigned debug_start = millis ();
  while (!Serial && ((millis () - debug_start) <= 3000))
    ;

  Serial.println ("Start prop shield RAW player");

  // Enable the amplifier on the prop shield
  pinMode(PROP_AMP_ENABLE, OUTPUT);
  digitalWrite(PROP_AMP_ENABLE, HIGH);

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

  // Set initial volume
  // mixer1.gain(0, 0.5f);

  // uncomment these lines if you have a potentiometer or trimpot
  // on the pin A1 to control the volume, and comment out the
  // above line
  float vol = analogRead(VOLUME_POT);
  mixer1.gain(0, vol / 1024.0f);

  // Start SerialFlash
  if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
    while (1)
      {
	Serial.println ("Cannot access SPI Flash chip");
	delay (1000);
      }
  }
}

void playFile(const char *filename)
{
  Serial.print("Playing file: ");
  Serial.println(filename);

  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playFlashRaw1.play(filename);

  // A brief delay for the library read RAW info
  delay(5);

  // Simply wait for the file to finish playing.
  while (playFlashRaw1.isPlaying()) {
    // uncomment these lines if you have a potentiometer or trimpot
    // on the pin A1 to control the volume
    float vol = analogRead(VOLUME_POT);
    mixer1.gain(0, vol / 1024.0f);
  }
}


void loop() {
  playFile("SDTEST1.RAW");  // filenames are always uppercase 8.3 format
  delay(500);
  playFile("SDTEST2.RAW");
  delay(500);
  playFile("SDTEST3.RAW");
  delay(500);
  playFile("SDTEST4.RAW");
  delay(1500);
}

This is a slightly more general example for playing MP3 files. It plays the MP3 files that it finds in flash memory:

Code:
// Simple MP3 file player example
// from https://forum.pjrc.com/threads/27059-MP3-Player-Lib-with-example?p=101537&viewfull=1#post101537
//
// Requires the prop-shield
// This example code is in the public domain.

#include <Audio.h>
#include <SerialFlash.h>
#include <play_sd_mp3.h>
//#include <play_sd_aac.h>


// GUItool: begin automatically generated code
//AudioPlaySdWav	playWav1;		//xy=154,422
AudioPlaySdMp3		playMp31;		//xy=154,422
AudioMixer4		mixer1;			//xy=327,432
AudioOutputAnalog	dac1;			//xy=502,412
AudioConnection		patchCord1 (playMp31, 0, mixer1, 0);
AudioConnection		patchCord2 (playMp31, 1, mixer1, 1);
AudioConnection		patchCord3 (mixer1, dac1);
// GUItool: end automatically generated code


#define PROP_AMP_ENABLE    5
#define FLASH_CHIP_SELECT  6

void setup() {
  AudioMemory(8); //4
  delay(2000);

  // Start SerialFlash
  if (!SerialFlash.begin(FLASH_CHIP_SELECT)) {
    while (1)
      {
        Serial.println ("Cannot access SPI Flash chip");
        delay (1000);
      }
  }

  // Set Volume
  for (int i = 0; i < 2; i++)
    mixer1.gain (i, 0.50);

  // Start Amplifier
  pinMode (PROP_AMP_ENABLE , OUTPUT);
  digitalWrite (PROP_AMP_ENABLE , 1); 
}

void playFileMp3 (const char *filename, uint32_t sz)
{

  SerialFlashFile ff  = SerialFlash.open (filename);
  uint32_t	  pos = ff.getFlashAddress ();

  // Start playing the file.  This sketch continues to
  // run while the file plays.
  playMp31.play (pos, sz);

  // Simply wait for the file to finish playing.
  while (playMp31.isPlaying ())
    {
      yield ();
    }
}

void loop ()
{
  char filename[12];
  uint32_t filesize;

  SerialFlash.opendir ();
  while (SerialFlash.readdir (filename, sizeof (filename), filesize))
    {
      const char *dot	= strchr (filename, '.');
      bool mp3_p	=  (dot
			    && (dot[1] == 'M' || dot[1] == 'm')
			    && (dot[2] == 'P' || dot[2] == 'p')
			    &&  dot[3] == '3');

      Serial.printf ("%s %-12s%7lu bytes\n",
		     (mp3_p ? "Playing " : "Skipping"),
		     filename,
		     (unsigned long) filesize);

      if (mp3_p)
	playFileMp3 (filename, filesize);
    }

  Serial.println ("Pausing for 30 seconds.");
  Serial.println ("");
  delay (30000);
}
 
Last edited:
  • Then download the program for your operating system and unzip/un-gzip the image;
  • Execute the program and add the -e option to erase the flash memory;

So, this is the last step I come to. I uploaded the hex file to the board, but when I try to run the teensytransfer.exe file, it instantly closes again, I don't get a chance to do the rest.
 
So, this is the last step I come to. I uploaded the hex file to the board, but when I try to run the teensytransfer.exe file, it instantly closes again, I don't get a chance to do the rest.

I suspect something has changed in the Mac world that it can't find the Teensy connection. Since I only use Linux, you will need to get help on this from somebody else (such as FrankB).
 
btw, I'm using Windows if it matters. I wonder if there is any other way I could execute the file apart from just double clicking it that would make it work?
Oooook, I found a way to execute it. I got the command window opened and erased the memory. My file is named SABERHUM.RAW, and when I execute C:\teensytransfer -w SABERHUM.RAW, I get the error message Unable to read SABERHUM.RAW.



Edit no.2: Nevermind, I got it. I failed with the file path. Thanks for your help!

Edit again: Now I am able to load sound files onto the board, but no format I tried works without getting distorted terribly. I tested it with my program and yours, both sound the same way. Is there any other format I should use to make the quality better, or another program that makes it better? I also tested it with the sample program with the percussion kit, they sounded just fine.
 
Last edited:
Status
Not open for further replies.
Back
Top