electronics

esp8266 opensound control teensy

today i ported my opensound control esp8266 example for arduino to run on a teensy 3.1.

the version below is a bit simpler but still works the same. it is just to show how to send and receive osc messages directly in sc or max.

teensy code:

//f0 150705 - modified for teensy3 160430
//sending and receiving udp osc with an esp8266
//for teensy + esp8266 with firmare 0.9.5.2
#define WLAN_SSID  "ssid"
#define WLAN_PASS  "pass"
#define WLAN_ADDR  "192.168.1.3" //laptop running sc EDIT
#define ADDR "/tap" //incoming osc addy
#define PORT  1112  //incoming osc port
uint8_t buf[16];
char indata[12];
char inbuffer[256];
char OKrn[] = "OK\r\n";
byte wait_for_esp_response(int timeout, char* term = OKrn) {
  unsigned long t = millis();
  bool found = false;
  int i = 0;
  int len = strlen(term);
  while (millis() < t + timeout) {
    if (Serial1.available()) {
      inbuffer[i++] = Serial1.read();
      if (i >= len) {
        if (strncmp(inbuffer + i - len, term, len) == 0) {
          found = true;
          break;
        }
      }
    }
  }
  inbuffer[i] = 0;
  return found;
}
void setup() {
  //--osc message
  buf[0] = 47;   // /
  buf[1] = 115;  // s
  buf[2] = 116;  // t
  buf[3] = 105;  // i
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;
  buf[8] = 44;   // ,
  buf[9] = 105;  // i
  buf[10] = 0;
  buf[11] = 0;
  buf[12] = 4;   // a
  buf[13] = 3;   // b
  buf[14] = 2;   // c
  buf[15] = 0;   // d
  pinMode(23, OUTPUT);
  Serial.begin(115200);   //usb serial for feedback
  delay(400);
  Serial1.begin(115200);  //teensy hardware pins 0 and 1
  Serial.println("starting");
  Serial.print("hard reset...");
  pinMode(4, OUTPUT);
  delay(10);
  pinMode(4, INPUT);
  Serial.print("ready...");
  boolean resp = wait_for_esp_response(1000, "ready\r\n");
  Serial.println(resp);
  Serial.print("mode1...");
  Serial1.println("AT+CWMODE=1");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.print("connecting...");
  do {
    Serial1.print("AT+CWJAP=\"");
    Serial1.print(WLAN_SSID);
    Serial1.print("\",\"");
    Serial1.print(WLAN_PASS);
    Serial1.println("\"");
    resp = wait_for_esp_response(3000);
    Serial.print(resp);
  } while (!resp);
  Serial.print("\nmux1...");
  Serial1.println("AT+CIPMUX=1");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.print("udp...");
  Serial1.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial1.print(WLAN_ADDR);
  Serial1.print("\",57120,");
  Serial1.print(PORT);
  Serial1.println(",0");
  resp = wait_for_esp_response(1000);
  Serial.println(resp);
  Serial.println("setup done");
}
void loop() {
  if (wait_for_esp_response(1000, "\r\n+IPD,4,16:")) {
    if (wait_for_esp_response(1000, ADDR)) {
      Serial1.readBytes(indata, 12);
      if (wait_for_esp_response(1000)) {
        buf[12] = indata[8] + 1; //add one to incomming values
        buf[13] = indata[9] + 1;
        buf[14] = indata[10] + 1;
        buf[15] = indata[11] + 1;
        Serial.println(int(indata[8]));
        Serial.println(int(indata[9]));
        Serial.println(int(indata[10]));
        Serial.println(int(indata[11]));
        Serial1.println("AT+CIPSEND=4,16");
        if (wait_for_esp_response(1000, "> ")) {
          Serial1.write(buf, sizeof(buf));
          if (wait_for_esp_response(1000)) {
            Serial.println("reply sent!");
          }
        }
      }
    }
  }
}

supercollider code:

(
//--call&response
var send= 0, last= Main.elapsedTime;
OSCdef(\sti, {|msg, time, addr|
        //should receive the values you sent +1
        ([msg[1]>>24, (msg[1]>>16)&255, (msg[1]>>8)&255, msg[1]&255]).post;
        (" % sec since last: %, % sec since sent").format(addr, time-last, time-send).postln;
        last= time;
}, \sti);
n= NetAddr("192.168.1.4", 1112); //esp8266 ip address EDIT
f= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
r= Routine.run({
        inf.do{|i|
                n.sendMsg(\tap, f.value(4, 3, i.asInteger%256, 1));
                send= Main.elapsedTime;
                0.5.wait;
        };
});
)
AttachmentSize
Package icon maxmsp example patch1.56 KB

joule thieves

to make something useful out of 'dead' batteries i've been building joule thief circuits. these circuits are very easy, cheap and fun to build plus it gives me a little bit less bad conscious when going to the recycling bin with the batteries. watch https://www.youtube.com/watch?v=K53beWYdIpc to learn more.

below are pictures of one variant. it has a small 3mm green led (salvaged from broken printer), a hand wound coil, a resistor and a transistor.

joulethief 0

joulethief 1

the batteries here came with my first ever (analogue) multimeter. they are 32 years old!. see the date code: 84-04. they still can drive the little led. amazing. i think running this little green led is a good way to use the last energy stored in these beautiful and truly long life batteries.

joulethief 2

esp8266 opensound control

here some example arduino code for sending and receiving osc via the cheap esp8266 serial wifi module.

note that the opensound control messages here are very basic - only 4 bytes packed into a single 32bit integer.

* upload the code below to an arduino.

* connect esp8266 TX pin to arduino pin0.

* connect esp8299 RX to arduino pin1. it is safest to use a 3v3 lever converter for this line (or at least a voltage divider).

* power the esp8266 (VCC and GND) from an external 3v source. do not use the arduino 3v3pin as it cannot provide the required current. i used a LF33CV voltage regulator to get 3.3v from the 5v supply that also powers the arduino.

* connect esp8288 RESET pin to arduino pin4.

* and last connect esp8266 CH_PD to 3v3

optional: connect a separate usb-serial (ftdi) chip to arduino pins 2 and 3 to use software serial debugging. start debugging in terminal with something like screen /dev/tty.usbserial-A4015TKA 115200

the arduino code sits and waits for an incoming osc message (/tap). it then replies by sending out a counter message (/sti).

//f0 150705
//sending and receiving udp osc with an esp8266
//for an arduino + esp8266 with firmare 0.9.5.2

#include <SoftwareSerial.h>

#define WLAN_SSID  "SSID"
#define WLAN_PASS  "PASS"
#define WLAN_ADDR  "192.168.1.51" //laptop running sc
#define PORT  1112 //incoming osc port
String tag = "/tap"; //incoming osc addy

SoftwareSerial mySerial(2, 3);

uint8_t buf[16];
byte cnt;
byte id, on, hi, lo;
boolean resp;

void setup() {

  //--osc message
  buf[0] = 47;   // /
  buf[1] = 115;  // s
  buf[2] = 116;  // t
  buf[3] = 105;  // i
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = 0;
  buf[7] = 0;
  buf[8] = 44;   // ,
  buf[9] = 105;  // i
  buf[10] = 0;
  buf[11] = 0;
  buf[12] = 4;   // a high   (id)
  buf[13] = 3;   // a low    (on)
  buf[14] = 2;   // b high   (hi)
  buf[15] = 0;   // b low    (lo)

  mySerial.begin(115200);
  Serial.begin(115200);
  Serial.setTimeout(10000);
  mySerial.println("");
  mySerial.println("starting");

  mySerial.print("hard reset...");
  digitalWrite(4, 0);
  pinMode(4, OUTPUT);
  delay(10);
  pinMode(4, INPUT);
  resp = Serial.find("ready\r\n");
  mySerial.println(resp);

  mySerial.print("mode1...");
  Serial.println("AT+CWMODE=1");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  mySerial.print("connecting...");
  do {
    Serial.print("AT+CWJAP=\"");
    Serial.print(WLAN_SSID);
    Serial.print("\",\"");
    Serial.print(WLAN_PASS);
    Serial.println("\"");
    resp = Serial.find("OK\r\n");
    mySerial.println(resp);
  } while (!resp);

  mySerial.print("mux1...");
  Serial.println("AT+CIPMUX=1");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  mySerial.print("udp...");
  Serial.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial.print(WLAN_ADDR);
  Serial.print("\",57120,");
  Serial.print(PORT);
  Serial.println(",0");
  resp = Serial.find("OK\r\n");
  mySerial.println(resp);

  Serial.setTimeout(1000);
}
void loop() {
  while (Serial.available()) {
    String abc = Serial.readStringUntil('\n');
    if (abc.startsWith("+IPD,4,16:" + tag)) {
      id = abc[22];
      on = abc[23];
      hi = abc[24];
      lo = abc[25];
      mySerial.print("id:");
      mySerial.println(id);
      mySerial.print("on:");
      mySerial.println(on);
      mySerial.print("hi:");
      mySerial.println(hi);
      mySerial.print("lo:");
      mySerial.println(lo);

      buf[15] = cnt++;
      Serial.println("AT+CIPSEND=4,16");
      Serial.find(">");
      Serial.write(buf, sizeof(buf));
      resp = Serial.find("OK\r\n");
      mySerial.print("send...");
      mySerial.println(resp);
    }
  }
}

supercollider test code:

(
//--call&response
var last= Main.elapsedTime;
OSCFunc({|msg, time, addr|
        [\id, msg[1]>>24, \on, (msg[1]>>16)&255, \hi, (msg[1]>>8)&255, \lo, msg[1]&255, time-last, addr].postln;
        last= time;
}, \sti);
n= NetAddr("192.168.1.50", 1112); //esp8266 ip address
f= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
r= Routine.run({
        inf.do{|i|
                n.sendMsg(\tap, f.value(4, 1, i.asInteger>>8&255, i.asInteger%256));
                0.5.wait;
        };
});
)

bass!

for a project in collaboration with stine janvin motland i built this 4-channel transducer bass shaker system.

the system has four transducers (visaton bs 130, 4Ω), two class d stereo amplifiers (2x50w, tda7492 chip) and a powerful atx switching power supply (codecom pm-350c).

i modified the power supply to only give out 12v (yellow&black cables) and also made it start up automatically by shorting the green cable (ps-on) to ground (black).

transducers 1

transducers 2

there's no volume control so better take care - the system is very hot.

audioSerial

clean-up: #56

compared to generating a serial bitstream in audio, analyzing and extract serial data from audio is much harder. the supercollider code below does it, but the program has limitations and is quite sensitive for noise.

the code takes a string, chops it up into groups of six 8bit bytes and generates a serial audio bitstream from that. another part listens to this sound and tries to decode it. if it finds six full bytes it sends the result back to sclang via osc where it is printed.
to test the example connect an audio cable directly from your computer's output to its input (preferably via a small mixer), or change the audioSerial synthdef to use an internal audio bus. i can also imagine it could function with a mic next to the speakers - but i didn't test this.
if it only prints gibberish try with a different threshold setting, different volume on you computer or use a lower baud rate.

(
s.waitForBoot{
        var baudrate= 9600;
        SynthDef(\serialAudio, {|out= 0, amp= -0.5|                     //for sending out serial via audio
                var data= Control.names([\data]).kr(Array.fill(60, 0)); //max 6 bytes
                var src= Duty.ar(1/baudrate, 0, Dseq(data), 2);
                OffsetOut.ar(out, src*amp);
        }).add;
        SynthDef(\audioSerial, {|in= 0, thresh= 0.05|           //for receiving serial via audio
                var raw= 0-SoundIn.ar(in); //here change to In.ar if trying internal audio bus
                var src= raw>thresh;
                var reading= DelayC.ar(Trig1.ar(src, 1/baudrate*9), 1/baudrate/2, 1/baudrate/2);
                var osc= Phasor.ar(reading, baudrate/SampleRate.ir);
                var clock= (osc-Delay1.ar(osc))<0+Impulse.ar(0);
                var index= PulseCount.ar(clock, reading);
                var stopTrig= index>7;
                var data= Latch.ar(src, index>=#[7, 6, 5, 4, 3, 2, 1]);
                var byte= (1-data).sum{|x, i| 2**(6-i)*x};
                SendReply.ar(stopTrig, '/data', byte);
                DC.ar(0);
        }).add;
        OSCFunc({|msg| msg[3].asInteger.asAscii.post}, '/data');
        s.sync;
        Synth(\audioSerial);
};
)

(
var str= "hello supercollider!";
var baudrate= 9600;
fork{
        1.wait;
        str.ascii.clump(6).do{|bytes|
                var data= bytes.collect{|x| [1]++(1-x.asBinaryDigits.reverse)++[0]}.flat;
                s.bind{
                        Synth(\serialAudio, [\data, data]);
                };
                (1/baudrate*60+0.005).wait;
        };
};
)

one can use this technique to communicate with another computer via audio. to communicate with a microcontroller (e.g. an arduino), one needs additional electronics (amplification, rectification). here's schematics for a bi-directional circuit for talking to a 5v arduino.

this audio-to-serial technique was used to get input from rfid, touch and bend sensors in our reflect installation. i.e. sc is running on an ipod touch and receives all sensor data via audio from an atmega168 microcontroller.

serialAudio

clean-up: #55

another way (compared to fsk in my previous blog entry) of sending data via audio is to directly generate the serial bit stream using supercollider.

to test and learn about these things i first wrote and uploaded a very simple program to an arduino board. the program just transmitted the bytes 128, 10, 20, 30, 40 and 50.

//arduino testcode
void setup() {
  Serial.begin(9600);
}
void loop() {
  delay(1000);
  Serial.write(128);
  Serial.write(10);
  Serial.write(20);
  Serial.write(30);
  Serial.write(40);
  Serial.write(50);
}

then i connected the arduino serial tx pin (pin1) to the audio line-in of my laptop (via a 1k + 10k voltage divider) and recorded the sound of the serial transmission.

i then analyzed the sound by hand and wrote a little program in sc that could generate similar waveforms.

s.boot;
o= {|chr| [1]++(1-chr.asBinaryDigits.reverse)++[0]};
(
SynthDef(\serialAudio, {|amp= -0.5|     //for sending out serial via audio
        var data= Control.names([\data]).kr(Array.fill(60, 0));//max 6 bytes
        var src= Duty.ar(1/9600, 0, Dseq(data), 2);     //baudrate
        OffsetOut.ar(1, src*amp);
}).add;
)
Synth(\serialAudio, [\data, [128, 10, 20, 30, 40, 50].collect{|c| o.value(c)}.flat, \amp, -0.5]);

this screenshot show the signal recorded from the arduino in the first channel, and the supercollider generated one in the second.

after all this i could reverse the process, generate any serial data and send it back to the arduino rx pin (pin0). a small amplifier circuit in between helped to get a more stable communication going.

this serial-to-audio technique was used to control the 24 leds (6 pwm channels) in our reflect installation. i.e. sc is running on an ipod touch and sends out serial audio to an atmega168 microcontroller.

here is another example that can fade a single led by sending serial commands over audio. includes schematics for an amplifier circuit plus sc and pd example code.

and for a more advanced (actually using a much better technique) example see here

redUniform2

for an upcoming performance i've revisited the electronics for my redUniform piece. my old setup used a nordic nRF24L01 wireless chip but now i changed to wifi and the adafruit cc3000 module.

the circuit is really minimal and simple. basically it's just the cc3000 wifi module, an atmega328, a 16Mhz chrystal, an on/off switch, one 1000mAh Li-ion and last two 6p connectors for the sensors.

the sensors are two modified minIMU-9.


with the battery fully charged i had it sending osc data at 50hz for more than a whole day.

attached are schematics and arduino code for reading sensors via spi. the code also show how to talk to the cc3000 and send sensor data via osc.
there are also two classes for supercollider called RedUniform2 and RedUniform2GUI.

redAlertLight

for a new piece i'm working on (redAlert) i wanted hundreds of red leds attached to special 'blobs' or lamps spread out on stage (designed by Jenny Michel). each led should be able to be freely placed and controlled individually and they had to be fairly bright. and because of the custom led placement i couldn't have used led strips - strips have a fixed distance between the leds.

so i built three circuits that could drive 32 pwm channels each and thereby got 96 pwm channels in total. each channel connects three leds in series (in a single package) and a resistor. that makes in total 288 leds.

the led i selected was the LED 5252 uh rt/1700mcd. it has 120 degrees spread angle and comes in a 5x5mm 6pin smd package that's possible to solder by hand. i bought it from segor where it costs 0.42 euro if you buy +100.

here a picture of it from the back side. the 270ohm resistor is chosen to match 12v and the three leds are connected in series using thin copper wire.

redAlertLight01

the three boards are controlled wirelessly and i send osc commands from supercollider to control all the leds. there's a class for supercollider attached below that helps with addressing and packaging of the network data. one can build and connect as many of these 32ch boards as one likes.

for actually generating the 12v pwm i used on each board two tlc5490 in combination with four uln2803a. and i also added a barebone arduino to get a stable spi communication with the tlc5490.

redAlertLight00

back side...
redAlertLight02

for receiving wireless osc data i added a raspberry pi (model a) with an usb wlan stick. on the rpi there's just a small python program that receives osc and sends out serial commands to the arduino.

last i have a tp-link TL-WR703N with open wrt installed acting as a router. when the boards start up they try to connect to this router and gets an ip assigned dynamically. this ip i use in supercollider to differentiate between the three boards.

installation instructions

//--
* put 2013-09-25-wheezy-raspbian.img on a sd-card with Pi Filler
* put the card in a raspberry pi MODEL B, connect ethernet and 5v
* find the IP with LanScan.app

(* ssh-keygen -R 192.168.1.51)
* ssh pi@192.168.1.51
* default password: raspberry
* sudo raspi-config
* select 'Expand Filesystem', change password, reboot and log in with ssh again

* sudo apt-get update
* sudo apt-get upgrade
* sudo pico /etc/inittab
* comment out the line 'T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100' near the bottom and save
* sudo pico /boot/cmdline.txt
* remove 'console=ttyAMA0,115200 kgdboc=ttyAMA0,115200' and save
* sudo reboot

* sudo apt-get install python-serial
* git clone git://gitorious.org/pyosc/devel.git
* cd devel
* sudo ./setup.py install
* cd ~

//--set up wlan on the rpi
* sudo nano /etc/network/interfaces
* edit to say...
        auto wlan0
        allow-hotplug wlan0
        iface wlan0 inet dhcp
                wpa-ssid SSID_NAME
                wpa-psk SSID_PASS
                wireless-power off
* sudo ifdown wlan0
* sudo ifup wlan0

//--copy file from laptop to rpi
* scp redAlertLight.py pi@192.168.1.51:/home/pi/

//--automatically start the python program at startup
* sudo pico /etc/rc.local
* add the following before the exit line: (sleep 1; python /home/pi/redAlertLight.py) & # autostart

//now move the sd card over to model a, connect the circuit and try

//--useful if you log in via ssh and want to stop the python program
* sudo pkill python

here is the python code...

#redFrik 2013

import serial
import socket
import OSC
import threading
import time
import os

addy= '0.0.0.0', 15000  #from sc
osc_server= OSC.OSCServer(addy)

port= serial.Serial('/dev/ttyAMA0', 115200)     #to atmega168
port.open()

def osc_led(addr, tags, data, source):
        #print tags
        #print "incoming osc data: %s" % data
        arr= bytearray()
        arr.append(254)
        for val in data:        #data has size 16 (24bit ints)
                hi_val= val>>12
                lo_val= val&4095
                #here can be optimized later to send fewer bytes (48 instead of 64)
                arr.append(hi_val>>8)
                arr.append(hi_val&255)
                arr.append(lo_val>>8)
                arr.append(lo_val&255)
        arr.append(255)
        port.write(arr)

osc_server.addMsgHandler("/led", osc_led)

def osc_stop(addr, tags, data, source):
        #print tags
        #print "shutting down"
        shutdown()

osc_server.addMsgHandler("/stop", osc_stop)

thread= threading.Thread(target= osc_server.serve_forever)
thread.start()

def all_led_off():
        osc_led(None, None, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], None)

def shutdown():
        close()
        os.system("sudo halt")
        exit()

def close():
        print "\nclosing"
        osc_server.close()
        all_led_off()
        port.close()
        #thread.join()

def main():
        try:
                while True:
                        line= port.readline()
                        if line.startswith("stop"):
                                shutdown()
        except KeyboardInterrupt:
                close()

if __name__ == "__main__":
        main()

attached is schematics, arduino firmware, partslist...

AttachmentSize
Image icon redAlertLight_schem.png1.5 MB
Package icon redAlert_mega168.zip1.1 KB
Plain text icon partlist.txt533 bytes
Package icon RedAlertLight.zip7.58 KB

Pages

Subscribe to RSS - electronics