«  …3 4 5 6 7 8 9 »

Wireless MQTT Circuits

2016-11-25 14:29 electronics

I've started using MQTT for talking to microcontrollers over WiFi and here's some code and instructions on how to set up such a system.

There are two programs that have to run in the background. They handle all the communication between the wireless hardware and the client software (MaxMSPJitter, SuperCollider etc). One is Mosquitto. Mosquitto is a MQTT broker and the central pub/sub hub of the system. The second program is a Python MQTT-OSC bridge script using the Paho client. This Python script lets programs like MaxMSPJitter or SuperCollider talk to Mosquitto via OSC. See the readme.txt included below on how to install and configure these programs.

On the hardware side, I build send/receive circuit nodes consisting of an ESP8266-01 module and an Arduino Pro Mini. These circuits run on 3V, are small and configurable and the parts cost almost nothing. The ESP8266 module provides WiFi communication and runs a MQTT client (I'm using the Adafruit MQTT library), while the Pro Mini does the physical inputs and outputs (sensors, LEDs etc). The two modules talk to each other via serial.
Some circuits I've built do 12 digital + 8 analogue inputs, while others have 12 LEDs in combination with 8 analogue inputs. But any combination is possible and the number of ins/outs depends on how the Pro Mini is programmed. (See portable_promini_ana and portable_promini_led in the zip archive below.)

portable circuit photo 1 portable circuit photo 2

So far I'm really pleased with this new technique. It seems to scale well and work more reliable than what I used before (sending raw OSC via CC3000 or ESP8266).

Attachments:
portable.zip

Syntjuntan

2016-11-01 00:17 electronics

I got to design and build version 2 of Syntjuntan's sewable synthesizer circuit. For this version, they wanted to add an on-board amplifier that could drive a passive speaker element.

The circuit now has three Schmitt triggers and can run on 3-12V. The amplifier is the classic LM386 and the connector pads around the board are made to fit needle and conductive thread as well as being crocodile friendly.

There are some options as standard through-hole soldering pads (a fourth Schmitt trigger and x10 extra gain). The circuit can also be used as a standalone audio amplifier - just ignore the Schmitt triggers and connect your own signal to the pad marked in.

Anyway, lots of fun mass producing this and in the process I learned how to do hot-air SMD soldering with stencil and solder paste plus got to know KiCad a bit better.

I also built a test rig with an Arduino and some pogo pins. It both scans for short-cuts and tests the sound.


Dragspel

2016-10-29 10:58 electronics, supercollider

This board is using an old Raspberry Pi 1 to control the speed of computer fans. The electronics are pretty simple (see attached schematics below): it takes 7-36V input power, has twelve MOSFETs for PWM control and finally a DC-DC converter to power the RPi. It was built for controlling PC cooling fans but can also drive other types of DC motors, lightbulbs or solenoids. The off button is there to safely power down the Raspberry Pi.

The trick with this though is that the system can be livecoded over WiFi using SuperCollider, MaxMSP or any other OSC capable program. So when you start the board the RPi sets up a wireless access point and starts a Python script that accepts incoming Open Sound Control messages. At startup, the RPi1 will also start SuperCollider and load a file (dragspelFans.scd) that is meant to contain whatever code you'd like to run as default. This file you later overwrite with your own SuperCollider code that you've developed/livecoded using your laptop.

dragspel

Below are step-by-step instructions on how I set this up plus the relevant Python and SuperCollider code. It should work on all RPi models but here the RPi1 or RPi0 is assumed.

preparation

Python

This section will install OSC and GPIO libraries for Python and also set up the Python script to automatically start at system boot.

@reboot /usr/bin/pigpiod -s 5 && /usr/bin/python /home/pi/dragspelFans.py
#f.olofsson2016-2018
#pwm control for 12 fans/motors/LEDs

#NOTE: make sure to run this in terminal first...
# sudo pigpiod -s 5

import sys
from os import system
from time import sleep
import pigpio
from liblo import *

inport= 9999  #for OSC commands to this Python script
pinoff= 2  #bcm numbering
pins= [3, 4, 14, 15, 17, 18, 27, 22, 23, 24, 10, 9]  #bcm numbering - more can be added here
target= ('127.0.0.1', 57120)  #for OSC to sclang
hz= 800  #pwm frequency in hz - note may need to adapt -s option in sudo pigpio -s 5 above
range= 100  #duty cycle range 0 to 100

pi= pigpio.pi()
pi.set_mode(pinoff, pigpio.INPUT)  #no internal pullup needed
for pin in pins:
  pi.set_mode(pin, pigpio.OUTPUT)
  pi.set_PWM_frequency(pin, hz)
  pi.set_PWM_range(pin, range)
  pi.set_PWM_dutycycle(pin, 0)

class MyServer(ServerThread):
  def __init__(self):
    ServerThread.__init__(self, inport)
  @make_method('/pwms', 'i'*len(pins))
  def pwms_callback(self, path, args):
    #print args  #debug
    i= 0
    for pin in pins:
      pi.set_PWM_dutycycle(pin, min(max(0, args[i]), range))
      i= i+1
  @make_method('/shutdown', '')
  def shutdown_callback(self, path, args):
    stop('sudo halt -p')  #turn off rpi
  @make_method('/reboot', '')
  def reboot_callback(self, path, args):
    stop('sudo reboot')  #reboot rpi
  @make_method('/start', '')
  def start_callback(self, path, args):
    send(target, '/start', 1)  #start default program in SuperCollider
  @make_method('/stop', '')
  def stop_callback(self, path, args):
    send(target, '/stop', 0)  #stop default program in SuperCollider
    for pin in pins:  #and also set all pwm to 0
      pi.set_PWM_dutycycle(pin, 0)
  @make_method(None, None)
  def fallback(self, path, args):
    print 'received unknown message "%s"' % path

def stop(cmd):
  pi.stop()
  server.stop()
  system('killall pigpiod sclang')
  system(cmd)
  sleep(1)
  sys.exit()

try:
  server= MyServer()
except ServerError, err:
  print str(err)
  sys.exit()
server.start()

def main():
  while True:
    if pi.read(pinoff)==0:
      print 'shutting down...'
      stop('sudo halt -p')
    sleep(0.5)

if __name__ == '__main__':
  try:
    main()
  except KeyboardInterrupt:
    pi.stop()

Again use ctrl+o and ctrl+x to save and exit. Now sudo reboot and then try to send OSC commands to the RPi. Here's how to send some test OSC messages from your laptop to the RPi using SuperCollider...

n= NetAddr("fans.local", 9999);
n.sendMsg(\pwms, *[50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);  //the number of integers should match the number of pins and range in your Python code (here 12 pins, 0-100)
n.sendMsg(\pwms, *[25, 50, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0]);  //first pin 25%, second %50 third 75%, rest 0
n.sendMsg(\pwms, *[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);  //all off

You can also try to connect pin bcm2 to ground. That should now act like an off button and turn off the RPi in a safe way.

SuperCollider

This section is optional. Only install SuperCollider if you want to run your RPi as a standalone installation or something similar. So if you plan to always remote control the system you can skip over this step.

Note: this is for RPi0&RPi1, for RPi2&RPi3 change all references to supercolliderStandaloneRPI1 below to supercolliderStandaloneRPI2

See github.com/redFrik/supercolliderStandaloneRPI1#lite for more details (this page also show how to install jackd if you need audio from your RPi).

#!/bin/bash
./sclang -a -l sclang.yaml ../dragspelFans.scd
OSCFunc({"/home/pi/dragspelFans.scd".load}, \start).permanent= true;
OSCFunc({CmdPeriod.run}, \stop).permanent= true;
//f.olofsson2016-2018 - for controlling 12ch computer fan switch board
DragspelFans {
  var <rpi, num, vals, lastv, <>debug;
  *new {|debug= false, rpi, num= 12|
    ^super.new.initDragspelFans(debug, rpi, num);
  }
  initDragspelFans {|d, r, n|
    num= n;
    if(r.notNil, {
      rpi= r;
    }, {
      try{
        rpi= NetAddr("fans.local", 9999);
      } {|err|
        "could not connect to rpi.\n make sure you are connected to the wifi network 'dragspel'.".warn;
        rpi= NetAddr("127.0.0.1", 9999);  //temp just for testing
      };
    });
    debug= d;
    vals= 0!num;
  }

  setAll {|val= 100|  //val should be 0 to 100
    vals= val!num;
    this.prSend;
  }
  clearAll {
    vals= 0!num;
    this.prSend;
  }
  val {|index, val|  //index should be 0-11, val 0-100
    vals= vals.put(index, val);
    this.prSend;
  }
  arr {|arr|  //arr should be 12 numbers in an array
    vals= arr;
    this.prSend;
  }

  shutdown {
    rpi.sendMsg(\shutdown);
  }
  reboot {
    rpi.sendMsg(\reboot);
  }
  start {
    rpi.sendMsg(\start);
  }
  stop {
    rpi.sendMsg(\stop);
  }

  //--private
  prSend {|v|
    if(debug, {
      vals.postln;
    });
    v= vals.clip(0, 100).round.asInteger;
    if(v!=lastv, {  //filter out repeats
      lastv= v;
      rpi.sendMsg(\pwms, *v);  //send to dragspelFans.py
    });
  }
}
//demo autostart script - put your own standalone code in here
d= DragspelFans.new;
Event.addEventType(\fans, {d.val(~index, ~val)});
Pbind(\type, \fans, \dur, 0.5, \index, Pseq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], inf), \val, Pwhite(0, 100, inf)).play;
@reboot cd /home/pi/supercolliderStandaloneRPI1 && xvfb-run ./autostart.sh

Now sudo reboot and SuperCollider should automatically start the code in dragspelFans.scd. It'll take a while so give it a minute or two.

To test it more run the following SuperCollider code on your laptop...

n= NetAddr("fans.local", 9999);
n.sendMsg(\stop);  //first stop the dragspelFans.scd script
n.sendMsg(\pwms, *[25, 50, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0]);  //set pwm manually

//install the DragspelFans.sc class on your laptop SC and also try the following example code

a= DragspelFans(true);  //might take a moment or two
CmdPeriod.doOnce({a.clearAll});  //optional

//version0 - all on or off
a.setAll;
a.clearAll;
a.setAll(50);  //set all to some value 0-100
a.clearAll;

//version1 - using an array
a.arr([0, 0, 100, 0, 0, 100, 0, 0, 100, 0, 0, 100]);  //turn on some
a.arr([0, 100, 0, 0, 100, 0, 0, 100, 0, 0, 100, 0]);  //turn some other fans
a.arr([30, 0, 0, 40, 100, 0, 40, 0, 0, 80, 0, 0]);  //a few slower
a.clearAll;

//version2 - set index to value
a.val(9, 100);
a.val(9, 0);
a.val(11, 100);
a.val(11, 0);
a.val(11, 60);
a.val(11, 0);

//fade in each fan in order
(
r= Routine.run({
  12.do{|j|
    100.do{|i|
      a.val(j, i);
      0.05.wait;
    };
    100.do{|i|
      a.val(j, 99-i);
      0.05.wait;
    };
  };
});
)
r.stop;

//using patterns
a= DragspelFans.new;
Event.addEventType(\fans, {a.val(~index, ~val)});
Pdef(\test, Pbind(\type, \fans, \dur, 0.125, \index, Pseq([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].scramble, inf), \val, Pwhite(0, 100, inf))).play;
Pdef(\test).stop;

a.start;  //start the dragspelFans.scd script on the rpi again
a.stop;
a.reboot;
a.shutdown;

Or test it using the attached MaxMSP patch.

fansStandaloneController screenshot

WiFi SoftAP

This section is optional. It will set up a WiFi access point served from the RPi. Basically taken from www.raspberrypi.org/documentation/configuration/wireless/access-point-routed.md.

interface wlan0
  static ip_address=192.168.4.1/24
interface=wlan0
dhcp-range=192.168.4.2,192.168.4.50,255.255.255.0,4h
interface=wlan0
driver=nl80211
ssid=dragspel
hw_mode=g
channel=6
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=________
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
DAEMON_CONF="/etc/hostapd/hostapd.conf"

Now sudo reboot on the RPi, log on to dragspel WiFi network from your laptop and try to send some OSC commands.

Updates:

Attachments:
dragspel_schematics.pdf
fansStandaloneController.maxpat

Tamas 2

2016-10-06 19:56 electronics

Another version of the box described here: /f0blog/tamas/. Cheaper because I replaced the PWM breakout board with a TLC5940 chip.

Code stays exactly the same, slightly updated schematics attached.

tamas photo 2
Attachments:
tamas_teensy_schematics2.png

Paper Speakers

2016-09-11 20:44 electronics

Here's some technical information on a collaboration with visual artist Jenny Michel. It's a sounding art installation that has previously been shown at Galerie Feldbuschwiesner and Städtische Galerie Wolfsburg, and is now (Sep 2016) up and running at SMAC project space in Berlin. Also exhibited in Wiesbaden and at Kunstverein-Tiergarten in Berlin.

'Traps', as the piece is called, consists of speakers made out of paper and enamelled copper wire. The wire is embedded into the paper in the shape of a coil and two strong magnets are situated on the back. The paper is manually layered and filled with prints and later perforated with cutouts. Behind the paper is also a small circuit with a microcontroller, an amplifier and an antenna - all hand soldered dead-bug style. Power comes from a shared 5V wall adapter.

Traps consists of around 30 individual speakers and they all react on each other via their antennas. They also pick up different electromagnetic fields - but we can't say in exactly which frequency range they are sensitive as the antennas are made with arbitrary lengths and shapes (they're also copper wire).

The sound is synthesised with the microcontroller (ATtiny45). Loudness and quality vary depending on the shape of the speakers - they're all individual - but in general the resulting sounds are quite noisy and piercing. The code was written to create a lot of variation and not just sonify the raw antenna input. So it's more of an interpretation than a direct translation of trapped radio frequency signals to sound as we first had planned. We also wanted harsher electronic sounds as it both fits better with the visual impression and that that type of square wave audio work very well in these non-linear lo-fi speakers.

There are also two modes: if one quickly turn the power on-off-on, the installation will start in 'vernissage mode' and play more sound than if one just turns the power on as normal (look for the EEPROM part in the code below to see how it works more in detail).

In total, we built 50 circuits and many more paper speakers. Though ~30 is enough to fill a medium-sized gallery.

traps circuit 0 traps circuit 1 traps 0 traps 1 traps 2 traps 3

Here's the code...

//f.olofsson 2015-2016
//select ATtiny, 1MHz (internal)
//decreased durMax in two places from 80000 to 8000

#include <EEPROM.h>

int durPause;
unsigned long durMax;
byte pausePer;
void setup() {
  randomSeed(analogRead(3));
  pinMode(4, OUTPUT);
  byte magic = EEPROM.read(0);
  if (magic == 123) { //lots of sound - chaotic opening mode
    durPause = 15000;
    durMax = 8000;
    pausePer = 67;  //percentage
    for (byte i = 0; i < 3; i++) {  //beep three times at startup
      prgStatic(100);
      prgPause(100);
    }
  } else {  //default - soft gallery mode
    durPause = 30000;
    durMax = 8000;
    pausePer = 75;  //percentage
    EEPROM.write(0, 123);
    delay(3000);  //power on for <3sec & then next time mode 1
    prgStatic(1500);  //make a tone at startup to know all working
  }
  EEPROM.write(0, 255);
}
void loop() {
  prgNoise(analogRead(3));
  if (random(100) < pausePer) {
    prgPause(durPause);
  } else {
    unsigned long dur = random(durMax);
    switch (analogRead(3) % 7) {
      case 0:
        prgPause(dur);
        break;
      case 1:
        prgNoise(dur);
        break;
      case 2:
        prgNoise(dur);
        break;
      case 3:
        prgChunks(dur);
        break;
      case 4:
        prgChunks(dur);
        break;
      case 5:
        prgBitbang(dur);
        break;
      case 6:
        prgImpulses(dur);
        break;
    }
  }
}
void prgPause(unsigned long dur) {
  analogWrite(4, 0);
  pinMode(0, OUTPUT);
  digitalWrite(0, 0);
  delay(dur);
  pinMode(0, INPUT);
}
void prgStatic(int dur) {
  analogWrite(4, 127);
  delay(dur);
}
void prgNoise(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
  }
}
void prgNoise2(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    analogWrite(4, analogRead(3) >> 2);
    delay(1);
  }
}
void prgChunks(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val);
  }
}
void prgChunks2(unsigned long dur) {
  unsigned long stamp = millis();
  byte base = (analogRead(3) >> 10) * 255; //either 0 or 255
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    analogWrite(4, val >> 2);
    delay(val / 2);
    analogWrite(4, base);
    delay(val / 2);
  }
}
void prgBitbang(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3 >> 2);
    for (byte i = 0; i < 8; i++) {
      digitalWrite(4, val >> i & 1);
    }
  }
}
void prgImpulses(unsigned long dur) {
  unsigned long stamp = millis();
  while (millis() - stamp < dur) {
    int val = analogRead(3);
    digitalWrite(4, 1);
    digitalWrite(4, 0);
    delay(val);
  }
}

Note the use of the LM386 N-4. It is more powerful than the N-1, N-3 variants.

traps schematics

Stine

2016-09-09 15:41 electronics, supercollider

Next week in Bucharest we'll be setting up the subjective frequency transducer for the third time. I described the sound/vibration generating part of this system before but didn't write much about how the controllers work.

So for each sound channel (i.e. each bass transducer), there's a wireless controller that enables the audience to set their preferred frequency. Technically it's done with a rotary encoder, an ESP8266 WiFi module, an ATmega168 and a big 7-segment LCD. The circuit runs off two AAA batteries.

When someone touches the rotary encoder, the circuit wakes up and starts sending OSC messages to a laptop running SuperCollider. SuperCollider receives the values, starts playing an oscillator and sends the sound to the corresponding audio channel. When done, SuperCollider fades out the oscillator and sends an off message to the circuit and the controller goes back to sleep mode.

I spent quite some time optimising the microcontroller (ATmega168) code. It was hard to both reduce power consumption and still being able to quickly wake up and react on user input as well as on incoming OSC messages. It's a common problem with battery-powered radio devices.

Also getting the ESP8266 to handle OSC messages was a pain. /f0blog/esp8266-opensound-control/ and /f0blog/esp8266-opensound-control-teensy/ contain some more info and simplified versions of that.

In the end, the code for talking to these circuits in SuperCollider looked like this:

//SC example: sending. turn off circuit 3 and set it back to initial frequency
~encode= {|id, on, hi, lo| (id&255<<24)|(on&255<<16)|(hi&255<<8)|(lo&255)};
~encode.value(3, 0, 0, ~initFreq);
//SC example: receiving. decoding data from the four ESP8266
OSCdef(\sti, {|msg, time, addr|
    var id= msg[1]>>24;
    var onoff= (msg[1]>>16)&255;
    var freq= (msg[1]&65280)+(msg[1]&255);
    [\id, id, \onoff, onoff, \freq, freq].post;
}, \sti);

The microcontroller code could still be improved. I'd like it to wake up on both WDT and UART. At the moment the circuit is drawing 22mA average in off+idle state, and 33mA average with display set to '20' which is okey but not optimal. And when sending OSC you get current spikes of a few hundred milliamps but there's no way around that.

//f.olofsson 2015-2016

#define ID 3
#define FREQ 0 //start frequency
#define FREQ_MIN 0
#define FREQ_MAX 9999
#define WLAN_SSID "MYNETWORK"
#define WLAN_PASS "MYPASSWORD"
#define WLAN_ADDR "192.168.43.99" //laptop static ip
#define WLAN_PORT 1112
String tag = "/tap"; //incomming OSC addy

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

#include <Encoder.h>

Encoder myEnc(3, 2);
float freq = FREQ;  //starting frequency
int freqLast = -999;

byte state = 0;
int enc = 0;
byte dig = 0;
byte cnt = 0;
boolean resp;

uint8_t buf[16];  //OSC message

void setup() {
  pinMode(2, INPUT_PULLUP);  //encoder a
  pinMode(3, INPUT_PULLUP);  //encoder b
  pinMode(4, INPUT_PULLUP);  //encoder button
  DDRB = B11111111;  //segments
  DDRC = B00001111;  //digits selector

  //--set up wifi
  Serial.begin(115200);
  Serial.setTimeout(10000);
  resp = Serial.find("ready\r\n");
  progressDot(1);
  Serial.println("AT+CWMODE=1");
  resp = Serial.find("OK\r\n");
  progressDot(2);
  do {
    Serial.print("AT+CWJAP=\"");
    Serial.print(WLAN_SSID);
    Serial.print("\",\"");
    Serial.print(WLAN_PASS);
    Serial.println("\"");
    resp = Serial.find("OK\r\n");
  } while (!resp);
  progressDot(3);
  Serial.println("AT+CIPMUX=1");
  resp = Serial.find("OK\r\n");
  progressDot(4);
  Serial.print("AT+CIPSTART=4,\"UDP\",\"");
  Serial.print(WLAN_ADDR);
  Serial.print("\",57120,");  //SuperCollider default port
  Serial.print(WLAN_PORT);
  Serial.println(",0");
  resp = Serial.find("OK\r\n");
  Serial.setTimeout(1000);
  displayClear();

  //--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] = ID;  // a high   (id)
  buf[13] = state; // a low  (onoff)
  buf[14] = 0;   // b high   (freq hi)
  buf[15] = 0;   // b low    (freq lo)

  //--timer
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 32768;  //62.5Hz display updaterate
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS10);  //prescaler divide by 1
  TIMSK1 |= (1 << OCIE1A);
  interrupts();

  //--sleep
  MCUSR &= ~(1 << WDRF);
  WDTCSR |= (1 << WDCE) | (1 << WDE);
  WDTCSR = 1 << WDP0 | 1 << WDP1;
  WDTCSR |= _BV(WDIE);
}

volatile int f_wdt = 1; //watchdog wakeup
ISR(WDT_vect) {
  if (f_wdt == 0) {
    f_wdt = 1;
  }
}
void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_IDLE);
  sleep_enable();
  sleep_mode();
  sleep_disable();
  power_all_enable();
}

ISR(TIMER1_COMPA_vect) {  //update display periodically
  if (state == 2) {
    displayFreq();
  }
}

void sendOsc() {
  buf[13] = state;
  buf[14] = int(freq) >> 8;
  buf[15] = int(freq) & 255;
  Serial.println("AT+CIPSEND=4,16");
  Serial.find(">");
  Serial.write(buf, sizeof(buf));
  resp = Serial.find("OK\r\n");
}

void loop() {
  dig = 1 - ((PIND >> 4) & 1);  //encoder momentary button
  switch (state) {
    case 2:  //running (display on)
      enc = myEnc.read();
      if (enc != 0) {
        float incStep = enc / 2.0;
        myEnc.write(0);
        freq = max(FREQ_MIN, min(FREQ_MAX, freq + incStep));
        if (int(freq) != freqLast) {
          sendOsc();
          freqLast = int(freq);
        }
      }
      if (dig == 1) {  //TODO: or timeout here?
        state = 3;
      }
      break;
    case 0:  //sleeping (display off)
      f_wdt = 0;
      enterSleep();
      enc = myEnc.read();
      if ((dig == 1) || (enc != 0)) {
        state = 1;
        freq = FREQ; //reset
        sendOsc();
      }
      break;
    case 3:  //turning off when button released
      displayClear();
      if (dig == 0) {
        state = 0;
        sendOsc();
      }
      break;
    case 1:  //turning on when button released
      if ((dig == 0) || (enc != 0)) {
        state = 2;
        myEnc.write(0);
      }
  }

  //--receive OSC
  while (Serial.available()) {
    String abc = Serial.readStringUntil('\n');
    if (abc.startsWith("+IPD,4,16:" + tag)) {
      //if(abc[22]==ID) { //optional filter by device ID
      if (abc[23] == 0) {
        displayClear();
        state = 0;
      } else {
        state = 2;
        myEnc.write(0);
      }
      freq = (abc[24] << 8) + abc[25];
    }
  }
}
void displayClear() {
  PORTC = B00001111;
  PORTB = B00000000;
}
void progressDot(byte index) {
  setChr(255, true);
  selDig(index);
}
void displayFreq() {
  int val = freq; //cuts off fraction
  switch (cnt) {
    case 0:
      if (val > 999) {
        setChr((val % 10000) / 1000, false);
      } else {
        setChr(255, false);
      }
      selDig(1);
      cnt = 1;
      break;
    case 1:
      if (val > 99) {
        setChr((val % 1000) / 100, false);
      } else {
        setChr(255, false);
      }
      selDig(2);
      cnt = 2;
      break;
    case 2:
      if (val > 9) {
        setChr((val % 100) / 10, false);
      } else {
        setChr(255, false);
      }
      selDig(3);
      cnt = 3;
      break;
    case 3:
      setChr(val % 10, false);
      selDig(4);
      cnt = 0;
  }
}

void selDig(byte index) {
  switch (index) {
    case 1:
      PORTC = B00001110;
      break;
    case 2:
      PORTC = B00001101;
      break;
    case 3:
      PORTC = B00001011;
      break;
    case 4:
      PORTC = B00000111;
  }
}

void setChr(byte chr, bool dot) {
  switch (chr) {
    case 255:  //clear
      PORTB = B00000000;
      break;
    case 0:
      PORTB = B11111100;
      break;
    case 1:
      PORTB = B01100000;
      break;
    case 2:
      PORTB = B11011010;
      break;
    case 3:
      PORTB = B11110010;
      break;
    case 4:
      PORTB = B01100110;
      break;
    case 5:
      PORTB = B10110110;
      break;
    case 6:
      PORTB = B10111110;
      break;
    case 7:
      PORTB = B11100000;
      break;
    case 8:
      PORTB = B11111110;
      break;
    case 9:
      PORTB = B11100110;
      break;
      /*
        case 10:  //A
        case 11:  //B
        case 12:  //C
        case 13:  //D
        case 14:  //E
        case 15:  //F
        case 16:  //G
        case 17:  //H
      */
  }
  if (dot) {
    PORTB |= B00000001;
  }
}
subjectivefrequencytransducer photo 0 subjectivefrequencytransducer photo 1 subjectivefrequencytransducer schematics

Rara Avis

2016-09-08 11:53 other

Got this...

ALKU99 cassette

It's a project generating bird calls with code.

alkualkualkualkualkualkualkualkualkualku.org/pmwiki/pmwiki.php/Main/ALKU99

Thank you Roc, Anna and Joe for all the hard work releasing this.


Tamas

2016-09-01 14:32 electronics

Here another project. It's a box for replacing some old MIDI hardware. It can read 16 analogue sensors, 16 digital sensors and control 16 LEDs via PWM. Basically, it is just a Teensy 3.2 plus a PWM breakout board from SparkFun. To easily access all the pins on the Teensy, I also used the excellent Teensy 3.2 Breakout Board R3 by Daniel Gilbert.

Attached are schematics, Teensy code, and SuperCollider and MaxMSP code for dealing with the serial communication.

tamas circuit photo 1 tamas circuit photo 2

Updates:

Attachments:
TamasSerial.sc
tamas_teensy_schematics.png
tamas_teensy.ino
tamas_test.maxpat

«  …3 4 5 6 7 8 9 »