Week13: Networking and communication

  • |
  • 02 May 2022
Post image

Think

I like the idea of modular electronics design. Having small ICs, each doing their own little job, capable of working in parallel, makes designing time critical applications a bit simpler. For my final project I plan to make use of the I2C protocol because it allows me to have devices that only use two wires for communication and are daisy-chainable. I wanted to get some experience in building I2C student- and I2C master devices, so I started to design a small piezo speaker board that makes use of the Attiny412.

I was happy enough with the result to present it during the open review.

Make

The end result is a network of piezo speakers that can play tunes by making each speaks act as a music channel.

SpeakerNet playing the Mario tune
SpeakerNet playing the Monkey Island tune

SpeakerNet

The ultimate goal is to have a chain of little speakers that are little, single channel synthesizers in themselves. Each speaker would receive commands on the I2C bus and play a note. Together the devices would be able to play complex compositions.

A single SpeakerNet board would have:

  • a piezo speaker connected to the DAC pin of an Attiny412
  • a WS2812b RGB LED
  • an I2C connector that allows the device to be connected to a bus

The choice for the I2C master device wasn’t very important. It should have enough power to hold and send entire songs to all the speakers. Eventually I chose e pre-existing board I made for the ESP8266.

Designing the board

At first I designed a board with an “ingoing” and “outgoing” I2C connector.

The first design contained too much connectors

Later I realized I wouldn’t need those connectors on my board if I would use the 4-pin ribbon connectors that are in the Fablab inventory.

schematic_i2c_speaker_board
The schematic of the SpeakerNet board

I choose to make the UPDI pin a single pad on the board. This way I could program the board easily without a 3-pin connector taking up room on the board.

pcb_design_i2c_speaker_board
The PCB design of the SpeakerNet board

The board is very simple and came out pretty nice. Because I wanted to make a network of devices, I needed more, so I used the Gimp to duplicate the PNG images of the traces and the board outlines, so I could mill four boards at once.

Four times the traces Four times the interior
The finished SpeakerNet board Making a board makes it easy to duplicate

The connections between the boards are made using a 4-pin ribbon connector.

A single connector can be applied to any part of the ribbon All devices are ion the bus.

I soldered female dupont headers to the end of the ribbon cable to fit onto any master device.

connections_board connections_esp
The connections to the board: brown: SDA, red: SCL, orange: GND, yellow:VCC The connections to the ESP8266 board. Same colors apply.

Programming the boards

Programming the SpeakerNet receiver (Attiny412)

The Attiny412 has very limited output capabilities, and to keep my board simple, I didn’t bother to break out the UART pins. This made the board slightly harder to debug. Most of the testing was done on a version of the board with lots of pins broken out.

The test board… …in a test setup

I made a very simple command structure for the board. Assuming a messages wouldn’t be send as a continuous stream, but rather as intermittently, a single command byte would suffice. The bytes that follow the command-byte can be interpreted as one or multiple parameters.

I2C command Explanation
C Color, followed by four bytes, representing the r, g and b values, LSB first
P Play a frequency, followed by two bytes representing the frequency of the tone, MSB first
S Stop playing the note

The first test was to read RGB values from the I2C bus and have the WS2812b LED take on the received color. I used the Wire->slave_read example and the tinyNEOPixel->simple and merged them into my own wirepixel sketch. It didn’t work.

At first I activated the LED directly in the receiveEvent() interrupt handle. I remembered to not perform many operations in an interrupt handler, so I moved the LED code to the main loop.

In the receiveEvent() function, the command is read from the I2C input buffer.

switch (command) {
    case 'C':
      ledTriggered = true;
      r = Wire.read();
      g = Wire.read();
      b = Wire.read();
      break;
}

And in the main loop, the LED is switched to the color that was read.

void loop()
    if (ledTriggered) {
        leds.setPixelColor(0, leds.Color(r, g, b));
        leds.show();
        ledTriggered = false;
    }
}

It still wouldn’t work. I removed code until I got the LED working again. It turned out the TinyNEOPixel library is not compatible with the Wire library. Fortunately there was also a TinyNEOPixel_static library included in the MegaTiny core. I didn’t understand the purpose or the distinction between the two, but the static version of the library can work with the Wire library, so I was relieved I didn’t make an unusable board.

To activate the tone, the command 'P' is received. The two bytes after the command are interpreted as the frequency. I considered to have only one byte and make that the midi-note, but to calculate the frequency from the midi note would take up too much space in the microcontroller, because it used floating points.

    case 'P':
      //Turn two bytes into a single 16-bit integer
      frequency = Wire.read() + Wire.read() * 256;
      toneTriggered = true;
      break;
    case 'S':
      noTone(SPEAKER_PIN);
      break; 

Programming the SpeakerNet sender (ESP8266)

Using the Wire->master_writer I wrote several functions that would send commands to a particular student device.

void sendColor(uint8_t address, uint32_t color) {
  Wire.beginTransmission(address);  
  Wire.write('C');
  // Write a 32 bit int by pretending it is a poointer to 4 bytes
  Wire.write((uint8_t *)&color, 4);
  Wire.endTransmission();  // stop transmitting
}

void sendNote(uint8_t address, uint16_t frequency) {
  Wire.beginTransmission(address);
  Wire.write('P');
  // Write a 16 bit int by pretending it is a poointer to 2 bytes
  Wire.write((uint8_t *)&frequency, 2);
  Wire.endTransmission();  // stop transmitting
  sendColor(address, colors[iColor]);   
}

void stopNote(uint8_t address) {
  Wire.beginTransmission(address);
  Wire.write('S');
  Wire.endTransmission();  // stop transmitting
}

Sending data larger than bytes involves a trick where I cast an uint32_t address to a pointer to bytes, thus interpreting it as a byte array. the least significant bytes is always first.

Midi-to-code

I found a command-line utility that converts midi files to c-code called miditones. In its simplest application it just accepts a midi file as input and outputs a c-code file with a static, pre-initialized array.

> ./miditones overworld.mid
MIDITONES V2.4, (C) 2011-2021 Len Shustek
  Processing 5 tracks.
  Only 4 tone generators were used.
  5947 bytes of score data were generated, representing 84.380 seconds of music with 1 tempo changes
  Done.

The generated output is a static array filled with byte literals:

const unsigned char score [] = {
// Grosse Fuge
0x90,55, 0x91,67, 0x92,43, 0x93,55, 0,36, 0x80, 0,3, 0x90,67, 0,36, 0x80, 0,3, 0x90,79, 12,86, 0x80, 0x81, 
0x82, 0x83, 0,79, 0x90,67, 0x91,55, 0x92,43, 8,242, 0x80, 0x81, 0x82, 0,79, 0x90,68, 0x91,56, 0x92,44, 3,100, 0x80, 
0x81, 0x82, 0,79, 0x90,77, 0x91,65, 0x92,53, 3,100, 0x80, 0x81, 0x82, 0,79, 0x90,76, 0x91,64, 0x92,52, 3,101, 0x80, 
0x81, 0x82, 0,79, 0x90,68, 0x91,56, 0x92,44, 3,100, 0x80, 0x81, 0x82, 0,79, 0x90,69, 0x91,57, 0x92,45, 1,7, 0x80, 
0x81, 0x82, 0,211, 0x90,78, 0x91,66, 0x92,54, 1,7, 0x80, 0x81, 0x82, 0,211, 0x90,81, 0x91,69, 0x92,57, 0,52, 0x90,79, 
0x91,67, 0x92,55, 0,53, 0x90,81, 0x91,69, 0x92,57, 0,52, 0x90,79, 0x91,67, 0x92,55, 0,53, 0x90,78, 0x91,66
}

miditones supports up to 6 channels, so I need to build a bit more SpeakerNet boards.

MidiNotes stream interpreter
Stream command Explanation
0x9n The 0x9n command plays a note on a channel. The channelnumber is represented by the n. The byte that follows is the midi-note. For example: {0x91, 69} plays an A4 on midi channel 1
0x8n Stops any note playing on channel n
0x0n Pauses the stream for the amount of milliseconds in the following byte. n stands for the MSB part of the pause duration
0xF0 Signifies the end of the stream

The final code for the interpeter is not up to my own quality standards, but there was no time to do it properly.

Check

I was surprised that it actually worked. I still have about 1800 bytes left in the program memory opf the Attiny412. I hope I can get the Attiny DAC to act a bit faster than it currently does with analogWrite. It wouldn’t be far fetched to make each of these modules a more capable synthesizer with envelopes, filters and different waveforms to simulate instruments. I wish I had time to develop this project.

Group Assignment

As I didn’t have a board that was ready to be connected to other projects, during the group project time I decided to quickly design and mill a simple board that could do I2C. That turned out to be the project I just described above.

You May Also Like

Final Project

Final Project

Final project Initial conception (Febuary 2022) Learning to play any musical instrument takes a very long time and a lot of effort. To enjoy music …

Week18: Project development
  • 10 Jun, 2022

Week18: Project development

What I did. I created the enclosure and designed and milled all the PCBs. TODO Hardware Priority 1 20 x Button LED caps 8x Octave1 8x Octave2 4x chord …