Posted on Leave a comment

Multiple MCP23017 Expanders With Interrupts Not Playing Nice

was manually polling my 3 MCP23017 expanders I need for all the buttons for my synth just to get the ball rolling. The serial monitor tells me it was taking almost 5ms….which is similar to the amount of time it’ll take for the dinosaurs to come back. (It’s a billion years in microcontroller land.) I thought making the jump to interrupts wouldn’t be that big of a deal. It was an absolute nightmare. I found a site that really got me going https://www.best-microcontroller-projects.com/mcp23017.html#L2415 , but they pulled a few tricks to account for some goofiness in the I2C’s interrupts. Within their tricks was a 1ms debounce delay. I’ve done my share of debouncing and I immediately did the ol’ nose scrunch to that one. A 1ms debounce is totally non-functional.

My problem was that after an interrupt was triggered on the MCP23017 (causing the 5V to drop to 0V), it was staying at 0V and not being reset. Using the Adafruit library, the method to do that was to simply read the getLastInterruptPinValue(); The problem was I was already doing this and the pin was not resetting back to 5V.

The solution came in realizing that the interrupts in the MCP23017 were continually being triggered by switch bounce. Nick Gammon, the king of everything, has an outstanding tutorial that illustrates this.  He uses no oddball ISR stuff…..just meat n’ taters interrupts. However, he uses a 500ms debounce. That’s a hair extreme, but it sure beats 1ms. I was lucky in that I could wait to call the getLastInterruptPinValue() until I was done doing my work. In fact, I had no need for the value.  All mine are zero.   I was using that function call as an ISR reset. So, I could call getLastInterruptPin(), do my work, put a delay(150) just before the clearing function and everyone is happy.

Even better, I’m using the switches connected to my 3 MCP23017s to update a 16×2 LCD (among other things). I can update the LCD instantaneously. I don’t have to wait at all. In this way, the end-user will never feel my LCD being slow or clunky even though I have delays built-in.  If I wanted to get really cute (and I may) and use an asynchronous delay like the infamous Arduino Blink Without Delay example. Actually, I’ll probably have to do that.

Anyway, here’s the code. I hope it helps.



#include "Arduino.h"


#include 
#include 


#define BUTTON1_INT_PIN PB8
#define BUTTON2_INT_PIN PB9
#define BUTTON3_INT_PIN PA15
#define MCP_INT_MIRROR true // Mirror inta to intb.
#define MCP_INT_ODR false   // Open drain.


/********** NOTES ********************/
Adafruit_MCP23017 button1; // Create an object for the first button board.
Adafruit_MCP23017 button2; // Create an object for the second button board.
Adafruit_MCP23017 button3; // Create an object for the third button board.

volatile byte button1Apushed, button1A_ID_value, button2Apushed, button2A_ID_value, button3Apushed, button3A_ID_value;
volatile byte button1A_ID = 16; // reset to be out of the range of the 0-15 MCP23017 buttons.
volatile byte button2A_ID = 16; // reset to be out of the range of the 0-15 MCP23017 buttons.
volatile byte button3A_ID = 16; // reset to be out of the range of the 0-15 MCP23017 buttons.

// ------------- FUNCTIONS ---------------------------

void button1A_ISR()
{
  button1Apushed = 1;
}

void button2A_ISR()
{
  button2Apushed = 1;
}

void button3A_ISR()
{
  button3Apushed = 1;
}



void setup()
{
  //---------------- BUTTON EXPANDER SETUP ------------------------
  // The address is specified using the A0, A1, and A2 pins
  button1.begin(0b000); // use default address 0, otherwise, the address needs to be defined
  button2.begin(0b001); // make sure to give A0 5V.
  button3.begin(0b010); // Set this up.

  pinMode(BUTTON1_INT_PIN, INPUT); // button1 interrupt pin on ucontroller
  pinMode(BUTTON2_INT_PIN, INPUT); // button1 interrupt pin on ucontroller
  pinMode(BUTTON3_INT_PIN, INPUT); // button1 interrupt pin on ucontroller

  for (int i = 0; i < 16; i++)
  {
    button1.pinMode(i, INPUT); // MCP23017 #1 pins are set for inputs
    button1.pullUp(i, HIGH);   // turn on a 100K pullup internally
    button2.pinMode(i, INPUT); // MCP23017 #2 pins are set for inputs
    button2.pullUp(i, HIGH);   // turn on a 100K pullup internally
    button3.pinMode(i, INPUT); // MCP23017 #2 pins are set for inputs
    button3.pullUp(i, HIGH);   // turn on a 100K pullup internally
  }
  
  button1.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW); // The mcp output interrupt pin.
  button2.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW); // The mcp output interrupt pin.
  button3.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW); // The mcp output interrupt pin.

  // Setup INTs on the MCP23017 button1 for all 16 buttons
  for (int i = 0; i < 16; i++)
  {
    button1.setupInterruptPin(i, FALLING);
    button2.setupInterruptPin(i, FALLING);
    button3.setupInterruptPin(i, FALLING);
  }

  button1.readGPIOAB();
  button2.readGPIOAB();
  button3.readGPIOAB();
  
  attachInterrupt(digitalPinToInterrupt(BUTTON1_INT_PIN), button1A_ISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(BUTTON2_INT_PIN), button2A_ISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(BUTTON3_INT_PIN), button3A_ISR, FALLING);


} // end of setup

void loop()
{

  if (button1Apushed)  // Buttons 0-15 on MCP23017 #1 
  {
    button1A_ID = button1.getLastInterruptPin();
	
	// update LDC here

    button1A_ID = 16;                                                                   // reset to a value that is outside the 0-15 button range of the MCP23017
    button1Apushed = 0;                                                                 // reset the pushed status
    delay(150);                                                                         // strong debounce
    button1A_ID_value = button1.getLastInterruptPinValue();                             // This one resets the interrupt state as it reads from reg INTCAPA(B).
    attachInterrupt(digitalPinToInterrupt(BUTTON1_INT_PIN), button1A_ISR, FALLING);
  }

  if (button2Apushed) // Buttons 16-31 on MCP23017 #2
  {
    button2A_ID = button2.getLastInterruptPin() + 16;


	// update LDC here
	
    button2A_ID = 16;                                                                   // reset to a value that is outside the 0-15 button range of the MCP23017
    button2Apushed = 0;                                                                 // reset the pushed status
    delay(150);                                                                         // strong debounce
    button2A_ID_value = button2.getLastInterruptPinValue();                             // This one resets the interrupt state as it reads from reg INTCAPA(B).
    attachInterrupt(digitalPinToInterrupt(BUTTON2_INT_PIN), button2A_ISR, FALLING);
  }
  if (button3Apushed) //Buttons 32-47 on MCP23017 #3
  {
    button3A_ID = button3.getLastInterruptPin() + 32;
	
	// update LDC here

    button3A_ID = 16;                                                                   // reset to a value that is outside the 0-15 button range of the MCP23017
    button3Apushed = 0;                                                                 // reset the pushed status
    delay(150);                                                                         // strong debounce
    button3A_ID_value = button3.getLastInterruptPinValue();                             // This one resets the interrupt state as it reads from reg INTCAPA(B).
    attachInterrupt(digitalPinToInterrupt(BUTTON3_INT_PIN), button3A_ISR, FALLING);
  }
}
Posted on Leave a comment

Serial USARTs On STM32 Blue Pill With Arduino Code in PlatformIO

Here’s the “Hello World” edition of getting access to USART2 and USART3 on an STM32 Blue Pill in PlatformIO using the Arduino framework.


/* lib_deps = NONE
  # Using a library name

-------- Platform.ini Settings----------
[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
#board = bluepill_f103c8
framework = arduino
upload_protocol = stlink
lib_compat_mode = soft

*/

#include "Arduino.h"
HardwareSerial Serial2(USART2);   // PA3  (RX)  PA2  (TX)
HardwareSerial Serial3(USART3); // PB11 (RX)  PB10   (TX)

void setup() {
Serial.begin(19200);   // PA10  (RX) PA9 (TX) 
Serial2.begin(19200);  // PA3  (RX)  PA2  (TX)
Serial3.begin(19200);  // PB11 (RX)  PB10   (TX)

Serial.println("Serial: 1");
Serial2.println("Serial2: 2");
Serial3.println("Serial3: 3");
}

void loop() {
Serial.println("Serial: 1");
Serial2.println("Serial2: 2");
Serial3.println("Serial3: 3");
delay(1000);
}
Posted on Leave a comment

STM32 Blue Pill ADC Values Too Low in PlatformIO

 

I kept getting values that were way too low when feeding an ADC on the STM32 Blue Pill 3.3V. I should have been getting values that were around 4000 and instead I was closer to 800.

Solution
1) The default ADC in the Arduino library is set to 10-bit resolution. This is what you normally get with analogRead(). To change it, put this in your setup(). Read more about it here: https://www.arduino.cc/reference/en/language/functions/zero-due-mkr-family/analogreadresolution/

analogReadResolution(12);

Notes
— This would have been a much easier problem to solve if i had been getting values of around 1000. It turns out that my ADC pin wasn’t getting exactly 3.3V. There was a voltage drop in my system – I’ve got a huge mess currently hooked to the Blue Pill – that knocked it down to around 3V. Normally, calculating bit resolution in decimal is 2^x – 1. So 2^10 – 1 = 1023.

— PlatformIO had nothing to do with the error. I’m new to PlatformIO and never know if my issues are due to the STM32’s specific needs, the Arduino library, or some kind of PlatformIO specific issue. It seems that as long as the hardware is setup correctly and
#include <Arduino.h>
is at the top of the program/sketch/whatever, PlatformIO has no ill effects. The problems I have had aren’t the fault of PlatformIO but from adapting libraries intended for AVR to STM32F1.

 

Posted on Leave a comment

Use Python To List All Files In A Directory

I needed a lengthy list of files for a massive 3D printing project I’m starting and I didn’t want to have to copy and paste each file one at a time.  Brandon’s Rule #232:  If you are faced with a 2 minute task that bores the hell out of you, invest 15 minutes into automating it.

Everyone should have Sublime Text installed on their machine and have just enough competence in Python to Google the living crap out of any problem that arises.

This Python code simply lists all the files in the directory containing this file.


from os import listdir
from os.path import isfile, join

mypath = '.'
onlyfiles = [f for f in listdir(mypath) if isfile(join(mypath, f))]

for x in range(len(onlyfiles)):
print(onlyfiles[x])
Posted on Leave a comment

Add 32 Buttons With Just 2 Pins With MCP23017 Expander

I need about 40 buttons for my synth project.  I’m trying to run the entire synth with a single STM32 Blue Pill.  I’ve already shown how I can run 256 LEDs with 3 pins LINK IT HERE.  Since the MCP23017 is I2C, I could possibly run 128 buttons on 2 pins.  For now, I’m just running two of my Tactile Switch Female Dog boards (each with 16 buttons and a MCP23017 chip).

Stuff I Didn’t Know Yesterday About The MCP23017 Expander

  • I needed to power cycle this chip quite a bit when it didn’t act as expected.  If you start to lose your mind, go ahead and lose it.  Then kill the power for a second.
  • When using the internal pullups, we are in active low land.  Pushing the button gives a zero.  That’s normal.  I got confused when using the onboard STM32 Blue Pill LED as the stupid thing is also active low.  Apparently, I can handle only one active low thing per week.

I debated whether I wanted to use a library or just bang it out with Wire commands.  It’s really all the same, but I guess the library saves 2 seconds of work (at what cost?).  I opted for the Adafruit MCP23017 library, but I’m keeping a link close by for the tutorial using Wire so I don’t have to think or open a datasheet today.

Code For A Single MCP23017 Expander

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h>
 
#define ONBOARD_LED PC13
 
// Connect pin #12 of the expander to PB6  I2C clock
// Connect pin #13 of the expander to PB7  I2C data
 
Adafruit_MCP23017 mcp;
 
void setup()
{
    // The address is specified using the A0, A1, and A2 pins
    mcp.begin(); // use default address 0, otherwise, the address needs to be defined
 
    for (int i = 0; i < 16; i++)
    {
        mcp.pinMode(i, INPUT); // MCP23017 pins are set for inputs
        mcp.pullUp(i, HIGH);   // turn on a 100K pullup internally
        delay(10);
    }
    pinMode(ONBOARD_LED, OUTPUT); // use the c13 LED as debugging
 
    // Sanity check to confirm functionality with onboard LED
    for (int i = 0; i < 8; i++)
    {
        digitalWrite(ONBOARD_LED, HIGH);
        delay(100);
        digitalWrite(ONBOARD_LED, LOW);
        delay(100);
    }
}
 
void loop()
{
 
    // Check for a button press of any button.  A LOW indicates a button press
    if (mcp.digitalRead(0) == 0 ||
        mcp.digitalRead(1) == 0 ||
        mcp.digitalRead(2) == 0 ||
        mcp.digitalRead(3) == 0 ||
        mcp.digitalRead(4) == 0 ||
        mcp.digitalRead(5) == 0 ||
        mcp.digitalRead(6) == 0 ||
        mcp.digitalRead(7) == 0 ||
        mcp.digitalRead(8) == 0 ||
        mcp.digitalRead(9) == 0 ||
        mcp.digitalRead(10) == 0 ||
        mcp.digitalRead(11) == 0 ||
        mcp.digitalRead(12) == 0 ||
        mcp.digitalRead(13) == 0 ||
        mcp.digitalRead(14) == 0 ||
        mcp.digitalRead(15) == 0)
    {
        digitalWrite(ONBOARD_LED, LOW); // Turn on the dumb, active low PC13 onboard LED
        delay(1000);                    // Keep the LED on a second.
    }
    else
    {
        digitalWrite(ONBOARD_LED, HIGH); // Turn off the dumb, active low PC13 onboard LED
    }
}

Code For 2 Simultaneous MCP23017 Expanders (32 Buttons)

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h> 

#define ONBOARD_LED PC13

/********** HARDWARE *****************/
// Connect pin #12 of the expander to PB6  I2C clock
// Connect pin #13 of the expander to PB7  I2C data
// Remember that I2C requires pullups on clock and data pins.
// I used 2.2k resistors because I mindlessly read it on the internet
// From a guy who typed wth confidence

/********** NOTES ********************/
// If the board acts stupid and you don't want to blame yourself,
// power cycle your microcontroller and the MCP23017

Adafruit_MCP23017 mcp1; // Create an object for the first board.
Adafruit_MCP23017 mcp2; // Create an object for the second board.

void setup()
{
    // The address is specified using the A0, A1, and A2 pins
    mcp1.begin();    // use default address 0, otherwise, the address needs to be defined
    mcp2.begin(001); // make sure to give A0 5V.

    for (int i = 0; i < 16; i++)
    {
        mcp1.pinMode(i, INPUT); // MCP23017 #1 pins are set for inputs
        mcp1.pullUp(i, HIGH);   // turn on a 100K pullup internally
        mcp2.pinMode(i, INPUT); // MCP23017 #2 pins are set for inputs
        mcp2.pullUp(i, HIGH);   // turn on a 100K pullup internally
    }
    pinMode(ONBOARD_LED, OUTPUT); // use the c13 LED as debugging

    // Sanity check to confirm functionality with onboard LED
    for (int i = 0; i < 8; i++)
    {
        digitalWrite(ONBOARD_LED, HIGH);
        delay(75);
        digitalWrite(ONBOARD_LED, LOW);
        delay(75);
    }
}

void loop()
{

    // Check for a button press of any button.  A LOW indicates a button press
    // There is surely a smarter way to do this, but that never stopped me.
    if (mcp1.digitalRead(0) == 0 ||
        mcp1.digitalRead(1) == 0 ||
        mcp1.digitalRead(2) == 0 ||
        mcp1.digitalRead(3) == 0 ||
        mcp1.digitalRead(4) == 0 ||
        mcp1.digitalRead(5) == 0 ||
        mcp1.digitalRead(6) == 0 ||
        mcp1.digitalRead(7) == 0 ||
        mcp1.digitalRead(8) == 0 ||
        mcp1.digitalRead(9) == 0 ||
        mcp1.digitalRead(10) == 0 ||
        mcp1.digitalRead(11) == 0 ||
        mcp1.digitalRead(12) == 0 ||
        mcp1.digitalRead(13) == 0 ||
        mcp1.digitalRead(14) == 0 ||
        mcp1.digitalRead(15) == 0 ||
        mcp2.digitalRead(0) == 0 ||
        mcp2.digitalRead(1) == 0 ||
        mcp2.digitalRead(2) == 0 ||
        mcp2.digitalRead(3) == 0 ||
        mcp2.digitalRead(4) == 0 ||
        mcp2.digitalRead(5) == 0 ||
        mcp2.digitalRead(6) == 0 ||
        mcp2.digitalRead(7) == 0 ||
        mcp2.digitalRead(8) == 0 ||
        mcp2.digitalRead(9) == 0 ||
        mcp2.digitalRead(10) == 0 ||
        mcp2.digitalRead(11) == 0 ||
        mcp2.digitalRead(12) == 0 ||
        mcp2.digitalRead(13) == 0 ||
        mcp2.digitalRead(14) == 0 ||
        mcp2.digitalRead(15) == 0

    )
    {
        digitalWrite(ONBOARD_LED, LOW); // Turn on the dumb, active low PC13 onboard LED
        delay(1000);                    // Keep the LED on a second.
    }
    else
    {
        digitalWrite(ONBOARD_LED, HIGH); // Turn off the dumb, active low PC13 onboard LED
    }
}