My first bonus design is a remote light organ. I've built it with an Arduino Yún, the Infineon RGB Led Shield and a bunch of LEDs.
Part 1 was my log on the build, and trying out if the Arduino-to-Infineon-toLEDs works.
In part 2, I'm showing off the completed design.
It Didn't Work Just Like That
I had issues getting it to work with a 64 char payload. I have some candidate root causes for that, but it's hard to get to the bottom without debugger (I think it is my mechanism to use a char array as payload).
I spent some time riddling my code with println()s and stared at it for a long time, to no avail. So I took the easy way out.
I've reduced my frequency spectrum bucket array from 64 to 32 buckets. That's enough for my purpose - I want to drive a 16*2 LCD in Bonus 2, and 32 data points will be enough..
This video shows the action. Appreciate the huge delay that I have
This is nowhere near to real time light effects.
The next design challenge I promise to spend more time in sound programming and performance. This time I only spent time in getting things working somehow.
Why don't you improve my code? It's all here.
I bet I'm off where I'm reverting the 64 bucket list to smaller on the server, and again when I'm reading that same bucket list on the client.
Go ahead and slam those bugs in my face. That's how I learn.
The light organ server code, firmware for the Yún that's built into the turntable
// includes for servo
#include <Servo.h>
// includes for light organ
#include <fix_fft.h>
// MQTT includes start
#include <Bridge.h>
#include <SPI.h>
#include <YunClient.h>
#include <IPStack.h>
#include <Countdown.h>
#include <MQTTClient.h>
#include <string.h>
// MQTT includes end
// servo declaration
Servo servo;
#define SERVO_PIN 9
#define SERVO_DELAY 25
#define SERVO_BASE 90
#define SERVO_TOP (SERVO_BASE + 61)
int servoPos = SERVO_BASE;
bool servoUp = false;
// light organ declaration
#define MUESTRAS 128 // Numero de muestras para el cálculo de la FFT
#define LOGM 7 // Logaritmo en base 2 del número de muestras
#define BAJOS_MEDIOS 7 // Nº de banda para el corte entre Bajos y Medios
#define MEDIOS_AGUDOS 35 // Nº de banda para el corte entre Medios y Agudos
#define BPIN 13 // Pin de salida Bajos
#define MPIN 12 // Pin de salida Medios
#define APIN 11 // Pin de salida Agudos
#define TIMERPIN 8
#define MAX_PASADAS 10 // Nº de pasadas para el cálculo de los lÃmites
char data[MUESTRAS]; // Array con los valores muestreados (parte real)
char im[MUESTRAS]; // Array con los valores muestreados (parte imaginaria)
unsigned char salida[MUESTRAS/2]; // Valores obtenidos de la FFT (espectro de 64 bandas)
unsigned char bajos,medios,agudos; // Valores calculados para cada canal
byte pasada, // nº de pasada para el cáculo de los lÃmites
acumBajos,acumMedios,acumAgudos, // acumuladores de veces que se supera el lÃmite
limBajos,limMedios,limAgudos; // lÃmites calculados para cada canal
// start MQTT declarations
YunClient c; // replace by a YunClient if running on a Yun
IPStack ipstack(c);
MQTT::Client<IPStack, Countdown> client = MQTT::Client<IPStack, Countdown>(ipstack);
char payLoadBuf[MUESTRAS/2] = {}; // I'm going to give one byte per range, so that I can choose to implement a graph if I want.
const char* send_topic = "PerpetuumEbnerSpectrum";
const char* _id = "1cfee8dd0afc_yun_syberia";
// end MQTT declarations
// both:
// decay is a decreasing counter that tells how long the servo will stay up after the last led flashed.
// reset to DECAY each time a led lights up
// looses one live each time no leds are active
#define DECAY 150U
unsigned int uDecay = 0U;
// servo functionality
void servoInit() {
servoUp = false;
servo.attach(SERVO_PIN);
servoPos = SERVO_BASE;
servo.write(SERVO_BASE);
delay(1000);
}
void servoGoUp() {
if (servoPos < SERVO_BASE) {
servoInit();
}
if (servoPos < SERVO_TOP) {
servoPos++;
servo.write(servoPos);
delay(SERVO_DELAY);
}
}
void servoGoDown() {
if (servoPos < SERVO_BASE) {
servoInit();
}
if (servoPos > SERVO_BASE) {
servoPos--;
servo.write(servoPos);
delay(SERVO_DELAY);
}
}
void servoGo() {
if (servoUp) {
servoGoUp();
} else {
servoGoDown();
}
}
// light organ fuctions
/*
* Funcion que aplica una ventana de Hann a los datos muestreados para reducir el
* efecto de las discontinuidades en los extremos
*/
void aplicaVentana (char *vData) {
double muestrasMenosUno = (double(MUESTRAS) - 1.0);
// Como la ventana es simétrica , se calcula para la mitad y se aplica el factor por los dos extremos
for (uint8_t i = 0; i < MUESTRAS/2 ; i++) {
double indiceMenosUno = double(i);
double ratio = (indiceMenosUno / muestrasMenosUno);
double factorPeso = 0.5 * (1.0 - cos(6.28 * ratio));
vData[i] *= factorPeso;
vData[MUESTRAS - (i + 1)] *= factorPeso;
}
}
void lightOrganSetup() {
// Configuramos el prescaler a 32 -> 16Mhz/32 = 500 KHz
// como cada conversion son 13 ciclos 500/13 ~ 38.4KHz
// Es decir podemos medir en teoria hasta unos 19KHz,
// que para este proyecto sobra.
bitWrite(ADCSRA,ADPS2,1);
bitWrite(ADCSRA,ADPS1,0);
bitWrite(ADCSRA,ADPS0,1);
// Como la señal es muy baja,utilizamos la referencia interna
// de 1.1 V en vez de la de defecto de 5 V.
analogReference(INTERNAL);
// Salidas para los canales de Bajos,Medios y Agudos
pinMode(BPIN,OUTPUT);
pinMode(MPIN,OUTPUT);
pinMode(APIN,OUTPUT);
// debug
pinMode(TIMERPIN,OUTPUT);
Serial.begin(9600);
// Variables para el cálculo de los lÃmites
pasada = 0;
acumBajos = acumMedios = acumAgudos = 0;
limBajos = limMedios = limAgudos = 50;
}
void lightOrganGo() {
// Realizamos el muestreo
// Serial.println("Start sampling:");
digitalWrite(TIMERPIN, HIGH);
for( int i=0; i < MUESTRAS; i++) {
data[i] = analogRead(0)/4 -128; //Convertimos de 0..1024 a -128..127
im[i] = 0; // parte imaginaria = 0
}
digitalWrite(TIMERPIN, LOW);
/*
{
Serial.println(" samples\n");
for( int i=0; i < MUESTRAS; i++) {
Serial.print(data[i], DEC);
Serial.print(",");
}
Serial.println(" ---");
}
*/
// Aplicamos la ventana de Hann
aplicaVentana (data);
/*
Serial.println(" window\n");
for( int i=0; i < MUESTRAS; i++) {
Serial.print(data[i], DEC);
Serial.print(",");
}
Serial.println(" ---");
*/
// Calculamos la FFT
fix_fft(data,im,LOGM,0);
// Sólo nos interesan los valores absolutos, no las fases, asi que
// calculamos el módulos de los vectores re*re + im*im.
// Dado que los valores son pequeños utilizamos el cuadrado
for (int i=0; i < MUESTRAS/2; i++){
salida[i] = data[i] * data[i] + im[i] * im[i];
// the cloud message contains half of the buckets, each pair of buckets is mediated
if (i % 2) { // uneven
payLoadBuf[i/2] = (salida[i] + salida [i-1])/2;
}
}
/*
Serial.println(" result\n");
for( int i=0; i < MUESTRAS/2; i++) {
Serial.print(salida[i], DEC);
Serial.print(",");
}
Serial.println(" ---");
*/
// Ahora repartimos las bandas entre las 3 salidas
// En vez de sacar la media, utilizo sólo el valor máximo de
// una banda
bajos = 0;
for (int i=2; i < BAJOS_MEDIOS; i++){
bajos += salida[i];
}
bajos = bajos/2;
medios = 0;
for (int i=BAJOS_MEDIOS ; i < MEDIOS_AGUDOS; i++){
medios += salida[i];
}
medios = medios/2;
agudos = 0;
for (int i=MEDIOS_AGUDOS; i < MUESTRAS/2; i++){
agudos += /*2**/(salida[i]); // jc 20150604 undid because Yun ADC more sensitive or so // 20150601 doubled the highs sesitivity
}
agudos = agudos/2;
// Calculamos si el canal correspondiente
// supera le lÃmite para encenderlo
int siBajos = bajos > limBajos;
int siMedios = medios > limMedios;
int siAgudos = agudos > limAgudos;
digitalWrite(BPIN,siBajos ? HIGH : LOW);
digitalWrite(MPIN,siMedios? HIGH : LOW);
digitalWrite(APIN,siAgudos? HIGH : LOW);
// Utilizamos las veces que se supera para
// recalcular el lÃmite y evitar que con los
// cambios de volumen los canales se saturen
// o no funcionen.
acumBajos += siBajos;
acumMedios += siMedios;
acumAgudos += siAgudos;
if ( ++pasada > MAX_PASADAS ) {
pasada = 0;
limBajos = 20 + acumBajos*5;
limMedios = 20 + acumMedios*5;
limAgudos = 20 + acumAgudos*5;
acumBajos = 0;
acumMedios = 0;
acumAgudos = 0;
}
if (siBajos | siMedios /*| siAgudos*/) { // jc 20150601 renew lift up decay if a led is on,
// ignore highs because I've made them more sensitive above
uDecay = DECAY;
} else if (uDecay > 0) {
uDecay--;
}
}
// MQTT start
char printbuf[100];
void mqttInit() {
// Ethernet.begin(mac); // replace by Bridge.begin() if running on a Yun
Bridge.begin();
connect();
}
void connect() // connect to the MQTT broker
{
char hostname[] = "iot.eclipse.org";
int port = 1883;
sprintf(printbuf, "Connecting to %s:%d\n", hostname, port);
Serial.print(printbuf);
int rc = ipstack.connect(hostname, port);
if (rc != 1)
{
sprintf(printbuf, "rc from TCP connect is %d\n", rc);
Serial.print(printbuf);
}
Serial.println("MQTT connecting");
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.MQTTVersion = 3;
data.clientID.cstring = (char*)_id;
rc = client.connect(data);
if (rc != 0)
{
sprintf(printbuf, "rc from MQTT connect is %d\n", rc);
Serial.print(printbuf);
}
Serial.println("MQTT connected");
}
void sendMessage() {
if (!client.isConnected())
connect();
MQTT::Message message;
int rc;
// Send QoS 1 message
// // Serial.println(payLoadBuf);
message.qos = MQTT::QOS1;
message.retained = false;
message.dup = false;
message.payload = (void*)payLoadBuf;
// message.payloadlen = MUESTRAS/2;
message.payloadlen = MUESTRAS/8;
rc = client.publish(send_topic, message);
}
// end MQTT functionality ==============================
// arduino general
void setup()
{
Serial.begin(9600);
delay(10000); // give me time to start the yun monitor
servoInit();
lightOrganSetup();
// MQTT related tasks
mqttInit();
}
void loop()
{
static unsigned int counter = 0;
client.yield(10); // this takes 30 ms. May reduce the parameter // if you get duplicates, increase
/*
// lift debug
while (Serial.available() > 0) {
uDecay = (Serial.parseInt());
}
*/
servoUp = (uDecay > 0);
servoGo();
lightOrganGo();
if (uDecay > 0) {
counter++;
if (counter == 30) {
counter = 0;
sendMessage();
}
}
}The light organ client code, firmware for the Yún that's built into the remote light organ
// needed for MQTT and Process lib
#include <Bridge.h>
// MQTT includes start
#include <SPI.h>
#include <YunClient.h>
#include <IPStack.h>
#include <Countdown.h>
#include <MQTTClient.h>
#include <string.h>
// MQTT includes end
// INFINEON includes start
#include <Wire.h>
#include "Infineon.h"
// INFINEON declaration
Infineon RGBLEDS = Infineon();
// light organ declaration
#define MUESTRAS 128 // Numero de muestras para el cálculo de la FFT
#define BAJOS_MEDIOS 4 // Nº de banda para el corte entre Bajos y Medios
#define MEDIOS_AGUDOS 10 // Nº de banda para el corte entre Medios y Agudos
#define MAX_PASADAS 10 // Nº de pasadas para el cálculo de los lÃmites
unsigned char bajos,medios,agudos; // Valores calculados para cada canal
byte pasada, // nº de pasada para el cáculo de los lÃmites
acumBajos,acumMedios,acumAgudos, // acumuladores de veces que se supera el lÃmite
limBajos,limMedios,limAgudos; // lÃmites calculados para cada canal
// start MQTT declarations
char printbuf[100];
YunClient yc; // replace by a YunClient if running on a Yun
IPStack ipstack(yc);
MQTT::Client<IPStack, Countdown> client = MQTT::Client<IPStack, Countdown>(ipstack);
char payLoadBuf[MUESTRAS/2] = {}; // I'm going to give one byte per range, so that I can choose to implement a graph if I want.
const char* subscribe_topic = "PerpetuumEbnerSpectrum";
const char* _id = "1cfee8dd0afc_yun_paradise";
boolean bDataReceived = false;
// end MQTT declarations
// start timer functionality ============================================================
boolean bRunSeconds = false;
boolean bIsRunningSeconds = false;
boolean bRunMinutes = false;
boolean bIsRunningMinutes = false;
boolean bRunHours = false;
boolean bIsRunningHours = false;
void runSeconds() {
bIsRunningSeconds = true;
Serial.print("s"); // remove when confident
if (bDataReceived) {
bDataReceived = false;
// Ahora repartimos las bandas entre las 3 salidas
// En vez de sacar la media, utilizo sólo el valor máximo de
// una banda
bajos = 0;
for (int i=2; i < BAJOS_MEDIOS; i++){
bajos += payLoadBuf[i];
}
bajos = bajos/2;
medios = 0;
for (int i=BAJOS_MEDIOS ; i < MEDIOS_AGUDOS; i++){
medios += payLoadBuf[i];
}
medios = medios/2;
agudos = 0;
for (int i=MEDIOS_AGUDOS; i < MUESTRAS/8; i++){
agudos += /*2**/(payLoadBuf[i]); // jc 20150604 undid because Yun ADC more sensitive or so // 20150601 doubled the highs sesitivity
}
agudos = agudos/2;
// Calculamos si el canal correspondiente
// supera le lÃmite para encenderlo
int siBajos = bajos > limBajos;
int siMedios = medios > limMedios;
int siAgudos = agudos > limAgudos;
/*
digitalWrite(BPIN,siBajos ? HIGH : LOW);
digitalWrite(MPIN,siMedios? HIGH : LOW);
digitalWrite(APIN,siAgudos? HIGH : LOW);
*/
sprintf(printbuf, "Writing rgb: %d, %d, %d\n",
siBajos, siMedios, siAgudos);
Serial.print(printbuf);
RGBLEDS.I2CWRITE6BYTES(ADDRESS, INTENSITY_RGB, siBajos*0xFFF, siMedios*0xFFF, siAgudos*0xFFF);
// Utilizamos las veces que se supera para
// recalcular el lÃmite y evitar que con los
// cambios de volumen los canales se saturen
// o no funcionen.
acumBajos += siBajos;
acumMedios += siMedios;
acumAgudos += siAgudos;
if ( ++pasada > MAX_PASADAS ) {
pasada = 0;
limBajos = 20 + acumBajos*5;
limMedios = 20 + acumMedios*5;
limAgudos = 20 + acumAgudos*5;
acumBajos = 0;
acumMedios = 0;
acumAgudos = 0;
}
}
bRunSeconds = false;
bIsRunningSeconds = false;
}
void runMinutes() {
bIsRunningMinutes = true;
bRunMinutes = false;
bIsRunningMinutes = false;
}
void runHours() {
bIsRunningHours = true;
bRunHours = false;
bIsRunningHours = false;
}
void timerInit() {
// initialize timer1 for 1 second ticks; ISR(TIMER1_COMPA_vect) will be called as interrupt handler
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 62500; // compare match register 16MHz/256/1Hz
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS12); // 256 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
static unsigned int uSeconds = 0;
uSeconds++; // every call is a second
bRunSeconds = true; // so yes, flag that the seconds handler should be called
bRunMinutes = ! (uSeconds % 60); // each 60th second, flag that the minutes handler should be called
if (uSeconds > 3599) { // every hour
bRunHours = true; // flag that the hours handler should be called
uSeconds = 0; // and start over
}
}
void timerTasks() {
if (bRunSeconds && ! bIsRunningSeconds) { // timer interrupt flagged that seconds handler should be called
runSeconds(); // but we only run it if it's not active
}
if (bRunMinutes && ! bIsRunningMinutes) { // timer interrupt flagged that minutes handler should be called
runMinutes(); // but we only run it if it's not active
}
if (bRunHours && ! bIsRunningHours) { // timer interrupt flagged that hours handler should be called
runHours(); // but we only run it if it's not active
}
}
// end timer functionality =====================================
// start MQTT functionality ====================================
void mqttInit() {
// Ethernet.begin(mac); // replace by Bridge.begin() if running on a Yun
Bridge.begin();
Serial.println("Enchanted Objects Remote Light Organ");
connect();
}
void messageArrived(MQTT::MessageData& md) // this handler is called when a subscribed MQTT message arrives
{
MQTT::Message &message = md.message;
bDataReceived = true;
// big chance that I'm off here by translating bytes, words, chars. But I Don't Have A Debugger in the Arduino IDE to Check.
for (int ii = 0; ii < message.payloadlen; ii++ ) {
payLoadBuf[ii] = ((char*)message.payload)[ii];
}
// debug code
sprintf(printbuf, "Message arrived: len %d, retained %d, dup %d, packetid %d\n",
message.payloadlen, message.retained, message.dup, message.id);
Serial.print(printbuf);
/*
sprintf(printbuf, "Payload %s\n", (char*)message.payload);
Serial.print(printbuf);
// sprintf(printbuf, "Topic len %d\n", md.topicName.lenstring);
int i;
for (i = 0; i < (md.topicName.lenstring.len); i++) {
printbuf[i] = md.topicName.lenstring.data[i];
}
printbuf[(md.topicName.lenstring.len)]= '\n';
printbuf[md.topicName.lenstring.len + 1]= 0;
*/
}
void connect() // connect to the MQTT broker
{
char hostname[] = "iot.eclipse.org";
int port = 1883;
sprintf(printbuf, "Connecting to %s:%d\n", hostname, port);
Serial.print(printbuf);
int rc = ipstack.connect(hostname, port);
if (rc != 1)
{
sprintf(printbuf, "rc from TCP connect is %d\n", rc);
Serial.print(printbuf);
}
// // Serial.println("MQTT connecting");
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.MQTTVersion = 3;
data.clientID.cstring = (char*)_id;
rc = client.connect(data);
if (rc != 0)
{
sprintf(printbuf, "rc from MQTT connect is %d\n", rc);
Serial.print(printbuf);
}
Serial.println("MQTT connected");
rc = client.subscribe(subscribe_topic, MQTT::QOS1, messageArrived);
if (rc != 0)
{
sprintf(printbuf, "rc from MQTT subscribe is %d\n", rc);
Serial.print(printbuf);
}
Serial.println("MQTT subscribed");
}
// end MQTT functionality ==============================
// start INFINEON functionality =====
void rgbSetup() {
Wire.begin();
Serial.println("polling led shield...");
while (RGBLEDS.on != 1) // Wait for shield to respond, keep setting the values till it does
{
Serial.println("led shield setup");
RGBLEDS.I2CWRITE2BYTES (ADDRESS, FADERATE, 0x0000); // Immediate fade
Serial.println("faderate set up");
RGBLEDS.I2CWRITE2BYTES (ADDRESS, DIMMINGLEVEL, 0x0000); // 0% brightness level
RGBLEDS.on = RGBLEDS.I2CREAD(ADDRESS, READ_DIMMINGLEVEL); // Request for brightness level
if (RGBLEDS.message == 1 && RGBLEDS.on == 0) // If message received and dimming level = 0%, "message" is set in the I2CREAD function
{
Serial.println("message check for 0");
RGBLEDS.message = 0;
RGBLEDS.on = 1; // break out of loop
}
}
RGBLEDS.I2CWRITE2BYTES (ADDRESS, OFFTIME_RED, 0x38); // Set off-time of red channel to 0x38
RGBLEDS.I2CWRITE2BYTES (ADDRESS, OFFTIME_GREEN, 0x38); // Set off-time of green channel to 0x39
RGBLEDS.I2CWRITE2BYTES (ADDRESS, OFFTIME_BLUE, 0x38); // Set off-time of blue channel to 0x38
RGBLEDS.I2CWRITE6BYTES (ADDRESS, CURRENT_RGB, 0x05, 0x05, 0x05); // max: 0x80 = 780mA, I need 15 for standard leds, (0x03), but dimmed down to 0x02 because that's enough as max
RGBLEDS.I2CWRITE2BYTES (ADDRESS, FADERATE, 0x0000); // Fade Rate between intensities --> 0.0s
RGBLEDS.I2CWRITE2BYTES (ADDRESS, WALKTIME, 0x0000); // walk time between colors = 0s
RGBLEDS.I2CWRITE6BYTES (ADDRESS, INTENSITY_RGB, 0x0555, 0x0555, 0x0555); // low level White Light
RGBLEDS.I2CWRITE2BYTES (ADDRESS, DIMMINGLEVEL, 0x0FFF); // Maximum dimming level means inensity settings are directly used
Serial.println("led shield active \n");
}
// end infineon functionality ======
void setup()
{
Serial.begin(9600);
delay(10000); // give me time to start the yun monitor
// MQTT related tasks
mqttInit();
// INFINEON related tasks
rgbSetup();
// timer related tasks
timerInit();
}
void loop()
{
timerTasks();
// task: keep MQTT alive
client.yield(10); // this takes 30 ms. May reduce the parameter // if you get duplicates, increase
}
.

