ATtiny85 MIDI Tester

January 25, 2019 at 10:28 pm (computers, music) (, , )

Having spent some time messing about with building simple synthesizer circuits, I’m putting together a simple MIDI to CV converter.  I have one using an ATtiny85 but think I’m struggling from the fact it is only using SoftwareSerial, so I plan to have another go with an ATtiny231w pretty soon now.

One thing I was missing though was a simple “hands free” MIDI tester.  Now it would be fairly simple to hook up my laptop or a keyboard to a MIDI cable and use that, but I wanted something I could just plug in and leave sending MIDI data out to whatever I was building.  So the idea of using a simple USB-powered ATTiny85 to creating a continuous set of MIDI note on and not off messages was born.

I’m using one of those cheap Digispark USB clones you can buy. I had no luck ever getting the USB programming side of it to work, (its supposed to be able to have the nucleus boot loader installed to provide a software USB implementation), but its easy to programme if you have an 8-pin DIL test clip, in my cased hooked up to a sparkfun tiny programmer.

2019-01-25 21.22.26

Basic design notes for the board:

  • P1 (equivalent to Arduino D1 and the ATtiny85 pin 6) has the built-in LED.
  • I’m using P2 as MIDI TX and P3 as (unused) MIDI RX (D2 and D3, mapped to ATtiny85 pins 7 and 2).
  • P0 (ATtiny85 pin 5) as a digital input with internal pull-up resistors enabled.

I’m using a simple MIDI out circuit from the Internet that shows:

  • DIN pin 5 – MIDI OUT signal directly connected to P2.
  • DIN pin 2 – MIDI ground.
  • DIN pin 4 – MIDI +5v via a 220R resistor.

The resistor was soldered inside an in-line female MIDI DIN socket.

2019-01-25 21.36.522019-01-25 21.42.10

A switch was soldered across from P0 to GND on the Digispark board.  The code will flash the LED when the switch is registered so you know you’ve done something.

That is pretty much it.

2019-01-25 21.51.47

2019-01-25 21.51.56

In terms of code, I just tested it with an increasing scale of a few octaves, with the switch being used to increase the tempo (by reducing the delay between notes). Of course, you can use whatever test pattern works for you.

My initial (simple) code below.

Important: You must “set the fuses” to use the internal 16MHz clock in order to get the MIDI baud rates for the SoftwareSerial implementation.

Kevin

// MIDI Code Test Generator using ATtiny85
// NB: Use Sparkfun USB ATtiny85 Programmer
//     Set Arduino env to USBTinyISP
//     Set 16MHz Internal Clock (required for MIDI baud)
#include <SoftwareSerial.h>

// Pin Mapping for DigiSpark USB/ATtiny85
//  P0 = PB0/D0 = Pin 5 Attiny85
//  P1 = PB1/D1 = Pin 6 - built-in LED
//  P2 = PB2/D2 = Pin 7
//  P3 = PB3/D3 = Pin 2 - wired to USB+
//  P4 = PB4/D4 = Pin 3 - wired to USB-
//  P5 = PB5/D5 = Pin 1 - wired to RESET
//
// Use the Arduino D numbers below (which are the same as Digispark P numbers)
#define MIDITX   2
#define MIDIRX   3
#define BUTTON   0
#define BLTINLED 1

// MIDI Parameters for testing
#define MIDI_CHANNEL     1
#define MIDI_LOWNOTE     36
#define MIDI_HIGHNOTE    90
#define MIDI_VELOCITY    64
#define MIDI_DELAYMAX    550
#define MIDI_DELAYMIN    50
#define MIDI_DELAYSTEP   100

#define MIDI_NOTEON      0x90
#define MIDI_NOTEOFF     0x80

SoftwareSerial midiSerial(MIDIRX, MIDITX);

int delayRate;
int buttonState;
int lastButtonState;
byte midiNote;

void setup() {
  // Switch will trigger HIGH->LOW
  pinMode (BUTTON, INPUT_PULLUP);
  pinMode (BLTINLED, OUTPUT);
  digitalWrite (BLTINLED, LOW);
  buttonState = HIGH;
  lastButtonState = HIGH;
  
  midiSerial.begin (31250); // MIDI Baud rate

  delayRate = MIDI_DELAYMAX;
  midiNote  = MIDI_LOWNOTE;
}

void loop() {
  buttonState = digitalRead (BUTTON);
  if ((lastButtonState == HIGH) && (buttonState == LOW)) {
    ledOn();
    delayRate = delayRate - MIDI_DELAYSTEP;
    if (delayRate < MIDI_DELAYMIN) delayRate = MIDI_DELAYMAX;
  }
  lastButtonState = buttonState;

  midiNoteOn (MIDI_CHANNEL, midiNote, MIDI_VELOCITY);
  delay (400); // Need note on long enough to sound
  midiNoteOff (MIDI_CHANNEL, midiNote);
  delay (delayRate);

  midiNote++;
  if (midiNote > MIDI_HIGHNOTE) midiNote = MIDI_LOWNOTE;
  
  ledOff();
}

void midiNoteOn (byte midi_channel, byte midi_note, byte midi_vel) {
  midiSerial.write (midi_channel+MIDI_NOTEON);
  midiSerial.write (midi_note);
  midiSerial.write (midi_vel);
}

void midiNoteOff (byte midi_channel, byte midi_note) {
  midiSerial.write (midi_channel+MIDI_NOTEOFF);
  midiSerial.write (midi_note);
  midiSerial.write ((byte)0);
}

void ledOn () {
  digitalWrite (BLTINLED, HIGH);
}

void ledOff () {
  digitalWrite (BLTINLED, LOW);  
}

Advertisements

Permalink Leave a Comment

Arduino Nano “USB Device Not Recognised” Fix

January 19, 2019 at 5:06 pm (computers) (, , , )

I have quite a range of Arduino Nano boards of varying vintage, but I’ve found some always come up with “USB Device Not Recognised” in Windows and wanted to work out why.  Symptoms are:

  • all fine if powered externally or via the ISP header.
  • “USB Device Not Recognised” errors otherwise.

These are Arduino Nano v3.0 boards I believe, and it turns out there is a known issue where the TEST pin of the FTDI chip isn’t correctly grounded, which makes the boards unreliable at best, and constantly failing at worst.

The faulty boards look like this:

arduino nano ftdi - toparduino nano ftdi - bottom

Notice the FTDI chip on the bottom of the board – this is the cause of the issue.  Many of the other cheap clones of the Arduino Nano use the CH340 chip, these don’t seem to have a similar issue.  For reference, they look like something like this instead:

arduino nano ch340 - bottom

The fix for the issue is described here on the Arduino Forums: http://forum.arduino.cc/index.php/topic,23025.0.html

But you will need your soldering iron and a very steady hand (and probably a magnifying glass).  Basically the poster shorted pins 26 (TEST) and 25 (AGND) on the chip and that seems to do the trick.  These pins (3rd and 4th from the top right hand side of the chip) are highlighted below (see the FT232RL datasheet for details).

arduino-nano-ftdi-fix.jpg

ftdi-ft232rl-pinout

Attempt the fix at your own risk of course.  Double check the part number of the chip, the orientation with the spot and the datasheet for the part number you are reading on the chip before you go near the soldering iron.

It worked for me.  YMMV.

Kevin

 

Permalink Leave a Comment

Dual ScrollPhatHD 17×14 LED Array

January 13, 2019 at 12:31 pm (computers) (, , , , , )

Having now had a play with my ScrollPhatHDs with the Arduino I’ve now successfully linked two together via a TCA9548A breakout board (having solved my Weird Multi-I2C Bus Issues as previously described).

The ultimate aim was to make a self-contained unit containing two ScrollPhatHD boards, an Arduino Nano and the TCA9584A.  I’ve now managed that using a square piece of breadboard, various jumper wires and most importantly, the Pimoroni Pogo-a-go-go Solderless header pins – I just didn’t want to spoil the neat look of the ScrollPhatHD’s by soldering to them directly.  The Pogo-pins are just perfect for spring-loaded connections between the ScrollPhatHDs and breadboard.

The one quirk, is that if I wanted all boards nicely sandwiched between the ScrollPhatHDs and the breadboard, but wanted to use the pogo-pins, then the breadboard needs to be strip-side up.

Here is the plan, followed by some photos of the finished item.

scrollphat-breakout_bb

I’ve left the nano and tca breakout off, so I can see the tracks.  This was from a first experiment with the two Phats, so it already had three full height sets of cuts in tracks – hence the few places where there were a couple of bits of patching to do, which were just done with solder links.  There are a few pins added to support the nano, especially at the USB end where I’ll be plugging in and out, which aren’t connected to anything on the stripboard.  And the four pins highlighted for the two ScrollPhat’s themselves weren’t soldered pins – that is the location for the pogo-pins.

In the final board, I put the jumper wires on the underside, and used headers pushed right through from below.  I also added a reset switch (not shown in the plan) wired to the Nano RST and ground on the strip board.

The linking of the two Phats isn’t perfect – the Nano USB port is just a fraction too high to perfectly fit, meaning the two boards bow out slightly in the middle.  Note the use of the pogo-pins.  I could replace them all with a slightly longer stand-off, but this is fine for a prototype.

Also, I made sure to drop some insulating tape on the bottom of both the Nano and the TCA board, to make sure it wouldn’t short anything on the copper of the stripboard.  I also put a bit around the shield of the USB port just in case, but I don’t think there was anything conductive on the back of the Phat.

So I now have a USB-accessible, self-contained, programmable 17×14 LED array.

Software wise, this uses the modified Adafruit IS31FL3731 Library I mentioned before, with the added quirk that one of the boards needs the coordinates reversing.  Coupled with the need to switch boards using the TCA as well, this means the basic idea of using the board is as follows:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <ScrollPhat_IS31FL3731.h>

// ScrollPhats connected using a TCA9548A I2C Multiplexer
// These are the I2C bus numbers used
#define TCAONE       0
#define TCATWO       1

//  HOR = number in horizontal (x) plane
//  VER = number in vertical (y) plane
#define HOR 17
#define VER 14

// Scrollphats have a hardcoded I2C address.
// Assumes connected as follows:
//      ledmatrix1 - using SC0 via the TCA
//      ledmatrix2 - using SC1 via the TCA
ScrollPhat_IS31FL3731 ledmatrix1 = ScrollPhat_IS31FL3731();
ScrollPhat_IS31FL3731 ledmatrix2 = ScrollPhat_IS31FL3731();

#define TCAADDR 0x70
void tcaselect(uint8_t i) {
  if (i > 7) return;
 
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();  
}

void setup () {
  // Initialse the I2C handling
  Wire.begin();
  
  tcaselect(TCAONE);
  ledmatrix1.begin();
  tcaselect(TCATWO);
  ledmatrix2.begin();
    
  // Do something to initialse your pixel array
}

void loop () {
  // Do stuff on your pixel array
  // Don't forget to write the displayRead(x,y) function
  // used in the scan routine

  ScanDisplay();
}

// Note: you need to implement the displayRead (x,y) function
//      to determine if a pixel is on or off
//
void ScanDisplay () {
  // Scan the first matrix
  tcaselect(TCAONE);
  for (uint8_t x=0; x<HOR; x++) {
    for (uint8_t y=0; y<VER/2; y++) {
      if (displayRead (x, y)) {
        ledmatrix1.drawPixel(x, y, 64);
      } else {
        ledmatrix1.drawPixel(x, y, 0);
      }
    }
  }

  // Scan the second matrix
  // Note this is oriented 180 degrees, so reverse both
  // x and y prior to setting, also of course, only using
  // the second half of the pixel array to display here.
  tcaselect(TCATWO);
  for (uint8_t x=0; x<HOR; x++) {
    for (uint8_t y=0; y<VER/2; y++) {
      if (displayRead (HOR-x-1, VER/2-1-y+VER/2)) {
        ledmatrix2.drawPixel(x, y, 64);
      } else {
        ledmatrix2.drawPixel(x, y, 0);
      }
    }
  }
}

 

Kevin

Permalink Leave a Comment

Weird Multi-I2C Bus Issues

January 11, 2019 at 8:17 pm (computers) (, , , )

This is just a quick post as a future reminder really.  I have two ScrollPhat HD Pimoroni boards that I’m using with an Arduino Nano, but as the I2C address is fixed for the boards, I’m using a TCA9548A I2C multiplexer breakout board to change I2C buses between accessing the two ScrollPhatHD boards.

But I was getting weird bus issues – sometimes only one of the boards came up, sometimes none, very occasionally, they were both fine.  I tried different channels off the TCA, swapping the boards over, I tried linking the RESET line of the TCA to the RESET line of the Arduino.  I even tried connecting the RESET line of the TCA to a digital pin and resetting the TCA manually on power up, but nothing seemed to work.

Then I saw a post somewhere about some weird I2C bus issues and one of the responses was “make sure you are calling Wire.begin();”.  I’d forgotten to put that in.

Once the code was calling Wire.begin() (only once mind), all was well.  It now powers up reliably and no resetting of the TCA is required.  All is good.

Kevin

 

Permalink 1 Comment

Pogo-pin ISP Programming Header

January 9, 2019 at 8:20 pm (computers) (, , , )

I have a batch of Arduino Nanos that have been used on projects but now don’t seem to accept a download unless I burn a new bootloader to them.  I have an excellent USBasp  ISP programmer to do that, but connecting to unpopulated ISP header pads on a nano without taking it out of a project is a bit fiddly, so I wanted a pogo-pin ISP header programmer.

But this is one of those times where I just haven’t seemed to hit on the right search terms to bring one up to buy.  So after a bit of experimenting, I’ve made my own.

Shopping list:

  • Cheap pogo-pins (I grabbed a pack of 50 from China).
  • “Dupont” crimp headers for a 3×2 header block (I got a multi-pack with a range of headers and male and female crimp inserts).
  • Your favourite ISP programmer of course.

2019-01-09 19.39.47

The idea is the solder the pogo-pins into the crimp part of the pins instead of a cable, and end up with a two-sided 3×2 header blog – pins on one side, pogo-pins on the other, as follows:

2019-01-09 19.41.142019-01-09 19.40.002019-01-09 19.55.20

The only thing to watch out for is getting the pins straight enough to make a neat 3×2 grid that will line up with header pads.  This can then be inserted in your ISP adapter header in the usual way.

2019-01-09 19.56.30

The only downside for me is that the pins are a little small still to make a perfect connection to an unpopulated set of header pads with solder holes.  But its more reliable than just poking an unsoldered 3×2 header block in the holes, which is what I was doing before.

Its a pretty simple idea, but I’ve just not seen anything like it around to buy off the shelf, or described in any kind of diy manner.  Let me know if you know of a better (cheap) solution.

Kevin

Permalink Leave a Comment

Pimoroni ScrollPhat using Adafruit IS31FL3731 Library

December 29, 2018 at 11:08 pm (computers) (, , , , , , )

I finally got myself a genuine Adafruit IS31FL3731 driver board and LED matrix (that I mentioned in a previous post) and glued an Arduino Nano on the back, thus creating a very neat little self-contained USB-driven 16×9 matrix of surface mount LEDs, which is great.

This uses the same driver chip as the Pimoroni ScrollPhat HD board for the Raspberry Pi, which is another great board I have, but I wanted to drive it from an Arduino too, so set about looking to see if the Adafruit libraries would work for the Pimoroni board too.  Four connections are required, as usual for I2C – VCC, GND, SDA, SCL as defined in the pinout here.

In summary, yes, but the Pimoroni has an odd LED configuration, as can be seen in the provided Python library:

class ScrollPhatHD(Matrix):
    width = 17
    height = 7

    def _pixel_addr(self, x, y):
        if x > 8:
            x = x - 8
            y = 6 - (y + 8)
        else:
            x = 8 - x
        return x * 16 + y

So the key feature is getting this translation over into the Adafruit IS31FL3731 library in the correct place.  So, taking a copy of the Adafruit_IS31FL3731.cpp and Adafruit_IS31FL3731.h files there were a number of changes to make.

First of all, the Adafruit boards provide a matrix of 16×9 (total 144) leds, compared to the ScrollPhat’s 17×7 (119) leds, so everywhere there is a reference to a width of 16 and height of 9, that needs changing to 17 and 7 respectively.  Unfortunately, there were a number of hard-coded “16”s and “9”s that needed updating.

Then, the translation function is required as follows (added to the .cpp and defined in the .h files):

// ScrollPhat LED Mapping taken from:
// https://github.com/pimoroni/scroll-phat-hd/blob/master/library/scrollphathd/is31fl3731.py
uint8_t ScrollPhat_IS31FL3731::xy2led (uint8_t x, uint8_t y) {
  if (x>8) {
    x = x-8;
    y = 6-(y+8);
  } else {
    x = 8-x;
  }
  return x*16 + y;
}

This function is responsible for translating the x,y values into a serialised led offset number (0 to 144 in the case of a fully populated matrix like the Adafruit).  There is one place where this has to be called – in the function ScrollPhat_IS31FL3731::drawPixel the following line needs to be changed as indicated:

From:
  setLEDPWM(x + y*16, color, _frame);

To:
  setLEDPWM(xy2led(x,y), color, _frame);

Assuming all the 16×9 references have been updated correctly to 17×7, and no special rotations or anything were required (I’ve not tested all options in the Adafruit GFX libraries), then this largely seems to work, at least it works with the Adafruit Swirl and gfx demos (although there might be some sorting out still to do in terms of handling rotations).

Kevin

 

Permalink 2 Comments

Charlieplex LED Failure Modes

December 23, 2018 at 2:28 pm (computers) (, , , )

I’ve been playing with Charlieplex LED arrays for a while now, and have been linking up my range of 8×7 LED Array and MCP23017 modules to create a larger display.  However, I’ve had that fault you sometimes get with Charlieplex LEDS where instead of a single LED lighting up, you get a good chunk of a row and a column lighting up at the same time.  I wanted to understand why.

From Wikipedia:

If the failed LED becomes an open circuit, the voltage between the LED’s 2 electrodes may build up until it finds a path through two other LEDs. There are as many such paths as there are pins used to control the array minus 2; if the LED with anode at node m and cathode at node n fails in this way, it may be that every single pair of LEDs in which one’s anode is node m, cathode is p for any value of p (with the exceptions that p cannot be m or n, so there are as many possible choices for p as the number of pins controlling the array minus 2), along with the LED whose anode is p and cathode is n, will all light up.

To understand what this means, I took the excellent Sparkfun tutorial on their Charlieplex array, and annotated the diagram to show what happens if the LED for P1->P2 fails with an open circuit, following the Wikipedia description:

Charlie-failure

So instead of the flow from P1 to P2 directly, illuminating D1, the current finds a path via the common connection to P3 between P1 and P2, thus illuminating both D2 and D6.

When this is expanded out to a whole array, you can see how the current finds a path through all pairs of LEDs sharing the anode and cathode, via another pin, with the failed LED.

If you are scanning the LED with a persistence of vision type arrangement, this kind of thing might be hard to spot other than some general weirdness in the display.  The key to finding out which LED is at fault is to slow down the scanning and illuminate each LED in turn.  Of course, whether it is possible to fix or not will depend on your LEDs and soldering ability…

Wikipedia describes other modes: closed circuit – in which case, presumably you’ll start to get pairs of LEDS lighting up (as if both P1 and P2 are joined); and “leaky” where there is current flow in both directions, in which case you presumably see a combination of both of these failure modes.

Charlieplex displays are great … but when they go wrong, they go wrong fairly completely.

Kevin

 

Permalink Leave a Comment

Raspberry Pi Family Tree

December 8, 2018 at 10:44 pm (computers) ()

There are a number of pages around listing the history of the various models of Raspberry Pi, and a few photo galleries of all models, but so far I haven’t been able to find an actual evolutionary family tree of the various boards produced to date.  So I’ve created one which will do until I find a better one.

Sources:

And the images themselves were picked out from Alex of Raspi.tv’s Raspberry Pi Family photo from March 2018:

(with the manual addition of the 3A+)

I think I’ve got the right linkages, but if not let me know.  Anything with a line across to the right hand side is still listed (at the time of writing) as a live product on raspberrypi.org as far as I can see.

Kevin

Raspberry Pi Family Tree.png

Permalink Leave a Comment

8×7 LED Array and MCP23017

December 7, 2018 at 8:21 pm (computers) (, , , , , , )

I can’t resist playing with an LED array, especially a surface mount one, so when I stumbled across some cheap (<$2) surface mount 8×7 LED arrays, charlieplexed onto 8 IO pins on ebay, I had to grab some.

Now I should say, I can’t really imagine why anyone would bother doing what I’m about to describe, as you already have many other options, including:

But as I say, I can’t resist a surface mount LED board, so I started to play.  When these arrived and I started poking around, I soon found out they were actually a cheap knock-off of the Sparkfun 8×7 LED Array, something I didn’t even know existed!  Apologies to Sparkfun for not getting an official board, but many thanks to them for open sourcing the design.

Initially they are fairly easy to hook-up and drive.  I used an Arduino Uno for my first experiments, as they could be paired straight away with pins 2-9 and then driven directly using the Arduino Charlieplex library (the Sparkfun Tutorial uses the Chaplex library and a special Sparkfun library for the board, but I was just doing fairly simple things for now).

With the neat serial interface to the Pi-Lite in the back of my mind, I wanted a better way to connect to the board.  Ideally, something that could be soldered onto the back to make it a single unit.  Some options I explored were:

  • Using an IO expander – such as the MCP23017 (the subject of this post).
  • Using an Arduino Mini Pro – to provide a serial interface (bit more on that later).
  • Using an LED driver module.

For this first experiment, I thought having an I2C addressable LED module would be quite neat (I didn’t know about the IS31FL3731 used on the Adafruit and Pimoroni boards at this time – that is an experiment for another day), so I grabbed some cheap MCP23017 breakout boards and after breadboarding it, ended up simply soldering one on the back.  It overlaps ever so slightly, but I think I could still place a few of these side by side to chain them.

2018-12-01-16-06-19.jpg2018-12-01-16-04-34.jpg2018-12-01-16-04-20.jpg

Link-up wise, this ties the 8 IO pins of the matrix to port A pins 0 to 7 on the MCP23017.  The 7 pins with header connections are (in order top to bottom):

  • VCC
  • GND
  • SCL/SCK
  • SDA/SI
  • NC/CS
  • NC/SO
  • RESET

And the three connections with no header pins are the three address encoding pins.  As I’m using I2C I only need four connections: VCC, GND, SCL and SDA (I could have left off the other three header pins really).

In terms of the code, there are libraries for the MCP23017, Charlieplexing and even the aforementioned Sparkfun library for this module, but to link them all together is quite inefficient.  The simple approach would be to replace the low-level pin setting/clearing code in the charlieplex library with commands for the MCP23017.  But as every setting of a single LED, even when triggered as part of a ‘scan’ managing a display buffer, requires I2C commands to be sent over the bus, and each command requires bit manipulation to process the IO pins individually, this is massively inefficient.

However, the MCP23017 is really only be driven by two registers: GPIOA to set the IO pins HIGH or LOW, and IODIRA to set the pins to INPUT or OUTPUT.  Recall that for Charlieplexing, all three states of pins are significant: INPUT, OUTPUT+HIGH, OUTPUT+LOW.

To work out which pins do what, we need to refer to the wiring layout of the LEDs on the board, for which the Sparkfun schematic is key (my thanks again for the open source design).  Here is an annotated version showing which IO pins (numbered 1 to 8 on the schematic) are connected to which LED in the matrix.

2018-12-07-19-29-34.jpg

The key thing to notice is how for every column listed, there is, naturally, a gap in the numbering of the rows (you can’t have an LED connected with both leads to the same IO pin).

So a more efficient scanning algorithm would thus run something like this:

// IODIRA = INPUT (1) or OUTPUT (0)
// GPIOA  = HIGH (1) or LOW (0)

FOREACH col (0 to 7)  // scan one column at a time
  gpiomask = (1<<col) // only pin that is HIGH in the col pin
  dirmask = 0         // default to all OUTPUTS
  FOREACH iopin (0 to 7)
    led = iopin
    IF (iopin == col) SKIP
    IF (iopin > col) THEN
       // led we want is one less than iopin due to charlieplex mapping
    IF (led for col is OFF) THEN
       dirmask |= (1<<iopin)  // set this one to an INPUT i.e. OFF
  NEXT iopin
  IODIRA = dirmask
  GPIOA  = gpiomask
NEXT col

The basic principle being that for a single scan, set one pin HIGH/OUTPUT (for one column in the matrix) and then work out which other LEDs need to be active by setting the direction of the pins accordingly – LOW/OUTPUT will light the LED for that pin combination; INPUT will mean the led for that combination is off.  Then on the next scan, do the same for the next column.

The following is some basic code, based on a whole range of sources (and to be honest, not particularly well structured in terms of functional separation) to provide an I2C optimised scanning function for the MCP23017 and LED array combination.

Usual caveats apply – I’m not really a cpp person, use at your own risk, etc, etc.

Key gotchas from doing this:

  • You can set the I2C bus to run at a higher speed, which is well worth doing in this case.  Note that I don’t know what happens if you have lots of things on the bus – this was working with just this LED array and nothing else.  See Wire.SetClock() in the Arduino Wire library.
  • You can’t do I2C from an interrupt routine.  I wanted the scanning routine to run off a timer interrupt, but nothing works if you try to do that – in fact, everything actually completely locks up.

So that’s it for now.  It scans quite well and with not a bad refresh rate, but as I said right at the start, with so many options of LED array available, not quite sure why anyone would want to do this.

Next, I’ll see if I can get the Pi-Lite firmware onto an Arduino Pro Mini and solder that on the back of one of these too.  That will create a standalone, drivable over serial port, module similar to the original Pi-Lite (ish).  Not really sure why anyone would want to do that either.

Kevin

I2CCharlieExample.ino

/*
 * Charlieplex Example for the Sparkfun LED Array board connected
 * to an MCP23017 IO expander chip and accessed via I2C.
 */
#include "I2CCharlieplex.h"
#include "Wire.h" // required for I2CCharlieplex.h

#define MCPADDR  0   // 0 to 7 for 0x20 to 0x27 for MCP23017 expander

// Define the pins in the order you want to address them.
//
// Note: for the MCP23017, pin numbers are as follows:
//         Port A pins 0 to 7 = pins 0 to 7
//         Port B pins 0 to 7 = pins 8 to 15
//
#define NUMBER_OF_PINS 8
byte pins[] = {0,1,2,3,4,5,6,7};

I2CCharlieplex charlieplex = I2CCharlieplex(MCPADDR, pins, NUMBER_OF_PINS);

void setup(){
  charlieplex.init();
  charlieplex.clr();
  charlieplex.scan();
}

int x=0;
int y=0;
int scan=0;
int clr=0;

void loop(){
  delay(1);
  charlieplex.scan();

  // Update the LED pattern from time to time
  if (scan > 200) {
    scan = 0;

    if (clr) {
      charlieplex.clr();
      clr=0;
    } else {
      charlieplex.led (x, y, HIGH);
      x++;
      if (x>=8) {
        x=0;
        y++;
        if (y>=7) {
          y=0;
          clr=1;
        }
      }
    }
  }
  scan++;
}

I2CCharliePlex.cpp

/*
 * I2C MCP23017 I2C IO Expander Charlieplex
 * Based on elements of MCP23017 Library from Adafruit and
 * the Charlieplex library by Alexander Brevig
 *
 * Pins are numbered as follows:
 *   0-7  Port A 0-7
 *   8-15 Port B 0-7
 */

#include "I2CCharlieplex.h"
#include "Wire.h"

// Default address
#define MCP23017_ADDRESS 0x20

// registers (using same definitions as Adafruit_MCP23017.h)
// Note: MCP23017 can be configured for interleaving registers or
//       sequential, depending on the setting of IOCON[AB].BANK.
//       By default on power up IOCON.BANK = 0; i.e. interleaved
//       as defined below..
#define MCP23017_IODIRA 0x00
#define MCP23017_IPOLA 0x02
#define MCP23017_GPINTENA 0x04
#define MCP23017_DEFVALA 0x06
#define MCP23017_INTCONA 0x08
#define MCP23017_IOCONA 0x0A
#define MCP23017_GPPUA 0x0C
#define MCP23017_INTFA 0x0E
#define MCP23017_INTCAPA 0x10
#define MCP23017_GPIOA 0x12
#define MCP23017_OLATA 0x14

#define MCP23017_IODIRB 0x01
#define MCP23017_IPOLB 0x03
#define MCP23017_GPINTENB 0x05
#define MCP23017_DEFVALB 0x07
#define MCP23017_INTCONB 0x09
#define MCP23017_IOCONB 0x0B
#define MCP23017_GPPUB 0x0D
#define MCP23017_INTFB 0x0F
#define MCP23017_INTCAPB 0x11
#define MCP23017_GPIOB 0x13
#define MCP23017_OLATB 0x15

#define MCP23017_INT_ERR 255

// Address in range 0 to 7.  MCP23017 address set in the range
//  0x20 to 0x27 using the A0, A1, A2 pins
I2CCharlieplex::I2CCharlieplex(uint8_t address, byte* userPins, byte numberOfUserPins){
  pins = userPins;
  numberOfPins = numberOfUserPins;

  // Initialise the MCP23017 IO Expander using I2C
  if (address > 7) {
    addr = MCP23017_ADDRESS;  // default to first address if not sure
  } else {
    addr = MCP23017_ADDRESS + address;
  }
}

void I2CCharlieplex::init () {
  Wire.begin();  // We are the bus master

  // Note on I2C clock speed - as a synchronous comms, the clock is set
  // by the master and the slave will be triggered by it.
  // The default is 100kbit/s (standard); many devices will also support 400kbit/s (full speed).
  // Some might support 1Mbit/s (fast mode) or 3.2Mbit/s (high speed).
  // See https://www.i2c-bus.org/speed/
  //
  // 400000 bps is 400000/8 = 50 KB/s
  Wire.setClock(400000); // Use I2C in full speed mode

  // Clear all leds (set all pins to INPUT)
  mcpAllInputs();
}

// LED Buffer access routines
void I2CCharlieplex::led(uint8_t x, uint8_t y, bool state) {
  if ((x<NUMXLEDS) && (y<NUMYLEDS)) {
    if (state == HIGH) {
      leds[y] |= (1<<x);
    } else {
      leds[y] &= ~(1<<x);
    }
  }
}

void I2CCharlieplex::clr() {
  for (uint8_t i=0; i<NUMYLEDS; i++) {
    leds[i] = 0;
  }
}

// WARNING: This cannot be called from an ISR if it is using I2C
//          (everything locks up if you do)
void I2CCharlieplex::scan() {
  // Only scan one column at a time

  // Scan according to the following algorithm:
  //  IODIRA = INPUT (1) or OUTPUT (0)
  //  GPIOA  = HIGH (1) or LOW (0)
  //  
  //  FOREACH col (0 to 7)  // scan one column at a time
  //    gpiomask = (1<<col) // only pin that is HIGH in the col led
  //    dirmask = 0         // default to all OUTPUTS
  //    FOREACH iopin (0 to 7)
  //      IF (iopin == col) SKIP
  //      IF (iopin > col) THEN
  //         led we want is one less than iopin due to charlieplex mapping
  //      IF (led for col is OFF) THEN
  //         dirmask |= (1<<iopin)  // set this one to an INPUT i.e. OFF
  //    NEXT iopin
  //    IODIRA = dirmask
  //    GPIOA  = gpiomask
  //  NEXT col

  // First kill all LEDS
  mcpWrite(MCP23017_IODIRA,0xff);

  uint8_t ledmask = (1<<col);
  uint8_t dirmask = 0;
  for (uint8_t p=0; p<NUMPINS; p++) {
    uint8_t y = p;
    if (p == col) {
      // skip this one
      continue;
    } else if (p > col) {
      // LED index will be one less than pin value due to charlieplex pattern
      y--;
    }
    // check the 'col'th LED in each 'y' row to see if pin 'p' is IN or OUT
    if ((leds[y] & (1<<col)) == 0) {
      // Want IO pin to be low to illuminate the LED
      // So only set IO pin if LED is OFF
      dirmask |= (1<<p);
    }
  }
  // Always need "col" pin as an OUTPUT (low) though
  dirmask &= ~(1<<col);
  
  // Only High pin will be the col being scanned
  mcpWrite (MCP23017_GPIOA, ledmask);
  // All other pins are low, so direction will determine if they are
  // a low OUTPUT or INPUT (which will disable that LED)
  mcpWrite (MCP23017_IODIRA, dirmask);
  // next column
  col++;
  if (col >= NUMXLEDS) {
    col = 0;
  }
}

// I2C MCP23017 IO Handling
// Not: No DigitalRead as yet

void I2CCharlieplex::mcpPinMode (uint8_t p, uint8_t mode) {
  if (mode == INPUT) {
    // Set appropriate bit in register
    mcpBitUpdate(p, 1, pin2reg(p, MCP23017_IODIRA, MCP23017_IODIRB));
  } else {
    // Clear appropriate bit in register
    mcpBitUpdate(p, 0, pin2reg(p, MCP23017_IODIRA, MCP23017_IODIRB));
  }
}

void I2CCharlieplex::mcpAllInputs () {
    // Set all pins to INPUT at once
    // (makes a full clear much quicker than setting individual bits)
    mcpWrite(MCP23017_IODIRA,0xff);
    mcpWrite(MCP23017_IODIRB,0xff);
}

void I2CCharlieplex::mcpDigitalWrite (uint8_t p, uint8_t val) {
  // Need to read current I/O states from the latch,
  // set/clear the appropriate bit and
  // write it back to the GPIO register
  uint8_t io;
  io = mcpRead(pin2reg(p, MCP23017_OLATA, MCP23017_OLATB));
  bitWrite(io, pin2bit(p), val);
  mcpWrite(pin2reg(p, MCP23017_GPIOA, MCP23017_GPIOB), io);
}

//
// I2C MCP23017 Register Handling
//

void I2CCharlieplex::mcpWrite(uint8_t reg, uint8_t val) {
  // Write sequence:
  //    Send register address to write
  //    Send the byte of data
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.write(val);
  Wire.endTransmission();
}

uint8_t I2CCharlieplex::mcpRead(uint8_t reg) {
  // Read sequence:
  //    Send register address to read
  //    Request 1 byte of data from the device
  //    Read the byte of data
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom(addr, (uint8_t)1);
  return Wire.read();
}

void I2CCharlieplex::mcpBitUpdate (uint8_t bit, uint8_t val, uint8_t reg) {
  uint8_t regval;
  regval = mcpRead(reg);
  bitWrite(regval,bit, val);
  mcpWrite(reg, regval);
}

// Register and pin helper functions
uint8_t I2CCharlieplex::pin2bit(uint8_t pin){
  // Returns bit 0 to 7 for range of pins 0 to 7 and pins 8 to 15
  return pin%8;
}
uint8_t I2CCharlieplex::pin2reg(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr){
  if (pin < 8) {
    return portAaddr;
  } else {
    return portBaddr;
  }
}

I2CCharlieplex.h

#ifndef I2CCHARLIEPLEX_H
#define I2CCHARLIEPLEX_H

#include <Arduino.h>

class I2CCharlieplex {

// LED matrix is 8 across by 7 deep
// Wired up in a charlieplex mode to map to 8 IO pins
#define NUMXLEDS 8
#define NUMYLEDS 7
#define NUMPINS  8

public:
  I2CCharlieplex(uint8_t address, byte* userPins,byte numberOfUserPins);
  void init ();

  // LED buffer handler functions
  void led(uint8_t x, uint8_t y, bool state);
  void clr();
  void scan();

  // MCP23017 handler functions
  void    mcpPinMode(uint8_t p, uint8_t mode);
  void    mcpAllInputs();
  void    mcpDigitalWrite(uint8_t p, uint8_t val);
  void    mcpWrite(uint8_t reg, uint8_t val);
  uint8_t mcpRead(uint8_t reg);
  void    mcpBitUpdate (uint8_t bit, uint8_t val, uint8_t reg);
  uint8_t pin2bit(uint8_t pin);
  uint8_t pin2reg(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr);

private:
  byte    numberOfPins;
  byte*   pins;
  uint8_t addr;
  uint8_t leds[NUMYLEDS];
  uint8_t col;
};

#endif

Permalink 2 Comments

Extractivism and the Anatomy of AI

November 6, 2018 at 10:20 pm (computers, interesting, science) (, , , )

I found this fascinating website – the Anatomy of an AI System – which takes an Amazon Echo and attempts to map out the behind the scenes costs in terms of manual labour, material resources, and data required to power the ecosystem.

It is particularly telling how much it focuses on the raw material impact of our modern lifestyles, which when all said and done is not unique to the Echo, but a symptom of our continued fascination with electronic gadgetry in its totality.  It has a word, that was new to me, for the way much of the impact is continually hidden from end consumers by large companies – extractivism – and attempts to bring to the fore the continued extractivism going on in support of the huge technology base being created (and in some cases just as quickly obsoleted) by large technology companies.

It uses as one example, The Salar, which is a high plateau in Bolivia that apparently contains the majority of the world’s source for Lithium, becoming increasingly important of course in our desire for mobile power (both smartphones and electric vehicles).

Another interesting example is the metaphor of “the cloud”:

Vincent Mosco has shown how the ethereal metaphor of ‘the cloud’ for offsite data management and processing is in complete contradiction with the physical realities of the extraction of minerals from the Earth’s crust and dispossession of human populations that sustain its existence.

We think of putting our data in “the cloud”, of using services in “the cloud” and all the mechanics are abstracted away, out of sight, out of mind. It is rarely that the physical realities of “the cloud” surface, expect in exceptional circumstances, usually where something goes wrong.

I would be interested in reading an update to Andrew Blum’s Tubes, updating it to make “the cloud” real in the same way he did for the also ethereal Internet itself.

Another interesting observation to come out of the study is the importance of the multiple roles of the end user:

When a human engages with an Echo, or another voice-enabled AI device, they are acting as much more than just an end-product consumer. It is difficult to place the human user of an AI system into a single category: rather, they deserve to be considered as a hybrid case. Just as the Greek chimera was a mythological animal that was part lion, goat, snake and monster, the Echo user is simultaneously a consumer, a resource, a worker, and a product.

(emphasis in the original paper).

This also comes out in the scale of income distributions – with Jeff Bezos at the top (apparently earning $275 million a day) – through US developers and workers, through overseas developers and workers – right down to “unpaid user labour” at the bottom generating the data that feeds the system and continually improves it.

The study rightly points out the fractal nature of attempting to display any of this in a linear manner on a single diagram.  Of course, each supply chain is supported by components each with their own supply chain.  Its supply chains all the way down until you reach the raw elements.

In summary I am minded simultaneously of Carl Sagan:

“We live in a society exquisitely dependent on science and technology, in which hardly anyone knows anything about science and technology.”

But seeing this complication laid bare, somewhat in defence of humanity, also of Douglas Adams (from “Mostly Harmless”):

“The available worlds looked pretty grim. They had little to offer him because he had little to offer them. He had been extremely chastened to realize that although he originally came from a world which had cars and computers and ballet and Armagnac, he didn’t, by himself, know how any of it worked. He couldn’t do it. Left to his own devices he couldn’t build a toaster. He could just about make a sandwich and that was it.”

Kevin

Permalink 2 Comments

Next page »