C++ How to intialize MCP23017 Config values?

This may just be me sucking at C++, but I’m having a terrible time attempting to Init() a MCP23017 class with non-default pin assignments.

My understanding of transports and I2C handles is shaky at best, and most of my coding expertise is C# and Python (VFX and game dev) so I’m probably missing something obvious and could use a little help connecting the dots. This is what I’m trying to do:

  1. Instantiate a Mcp23017 object with SCL and SDA on Daisy Seed pins D11 and D12 respectively.
    2 Write a bool blinkState to MCP pin GPA7 that goes between true/false every 200ms.

This is the code:


/*
Simple blink test for mcp23017.
*/

#include "daisy_seed.h"
#include "daisysp.h"

#define SCL_PIN {DSY_GPIOB, 11} 
#define SDA_PIN {DSY_GPIOB, 12} 

using namespace daisy;
using namespace daisysp;
using namespace daisy::seed;

DaisySeed hw;
Mcp23017 mcp; // Do I init pin numbers and other I2CHandle params here?

const uint8_t LED7 = 7;
const uint8_t SW = 6; // encoder button, not yet implemented
bool blinkState = false;

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		out[0][i] = in[0][i];
		out[1][i] = in[1][i];
	}
}

int main(void)
{
	hw.Init();
	mcp.Init();
	mcp.PortMode(MCPPort::A, 0xff, false); //All inputs, no invert, GPA7 is output only
	mcp.PortMode(MCPPort::B, 0xff, false);
	mcp.PinMode(LED7, MCPMode::OUTPUT, true);
	// mcp.PinMode(SW, MCPMode::INPUT, false );
	bool cur_blinkState = false;

  //Init before or after all the stuff is set?

	hw.StartLog(true);
	// hw.SetAudioBlockSize(4); // number of samples handled per callback
	// hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
	// // hw.StartAudio(AudioCallback);
	// Logger<LOGGER_INTERNAL>::PrintLine("mcp: 0x%#02x", mcp.GetPin(LED7));

	while(1) {
		mcp.Read();
		System::Delay(200);
		blinkState = !blinkState;
		// blinkState = mcp.ReadPin(SW);
		// if (blinkState != cur_blinkState)
			// mcp.WritePin(LED7, blinkState);  // Also not  implemented.

		// With oscilloscope probes connected to LED7 and GND,
		// I would expect to see a square wave on the screen. 
               // If I hack the .h file's defaults to the pins I want, that happens.
		mcp.WritePin(LED7, blinkState); 


	}
}

The only thing that made even something remotely interesting happen was hacking the Mcp23017.h file and setting the defaults to the pins I needed:


class Mcp23017Transport
{
  public:
    struct Config
    {
        I2CHandle::Config i2c_config;
        uint8_t           i2c_address;
        void              Defaults()
        {
            i2c_config.periph         = I2CHandle::Config::Peripheral::I2C_1;
            i2c_config.speed          = I2CHandle::Config::Speed::I2C_1MHZ;
            i2c_config.mode           = I2CHandle::Config::Mode::I2C_MASTER;
            i2c_config.pin_config.scl = {DSY_GPIOB, 11};
            i2c_config.pin_config.sda = {DSY_GPIOB, 12};
            i2c_address               = 0x27;
        }
    };
...

And here is where I hit the wall of my C++ skills. I should be able to set the public struct members to whatever I want, right? The eventual goal of this exercise is a eurorack module with 4 pushbutton/encoders and a OLED display.
I’ve successfully implemented (software and hardware) 5-pin DIN MIDI and eurorack-level audio with TL074’s, but I’m stumped on this one and I need it to stand up the 4 encoders.

Thanks in advance

I don’t have mcp23017 to test this, but here’s what I’d try:

Create a config struct somewhere:

Mcp23017::Config config;

Initialize that struct the way you like:

    config.transport_config.i2c_config.periph         = I2CHandle::Config::Peripheral::I2C_1;
    config.transport_config.i2c_config.speed          = I2CHandle::Config::Speed::I2C_1MHZ;
    config.transport_config.i2c_config.mode          = I2CHandle::Config::Mode::I2C_MASTER;
    config.transport_config.i2c_config.pin_config.scl = {DSY_GPIOB, 11};
    config.transport_config.i2c_config.pin_config.sda = {DSY_GPIOB, 12};
    config.transport_config.i2c_address               = 0x27;

Call mcp.Init with your struct:

mcp.Init(config);

Edit: on further thought, I think this can be done without creating a local config struct - but C++ confuses me…

1 Like

Thanks! That init framework compiled with no issues. I have to wait until I get back to my office before I can upload it, but it should be all good. In my other failed attempts, I don’t think I had the transport_config part of the statements I was using to set the I2C values and pins.

Once I got your snippet working, I went back and tried to see if I could do it without a local config struct, but I couldn’t get that to work.

1 Like

I’m going to update this thread as I stagger through my implementation in the hope that someone else looking for this finds it and avoids wandering in the wilderness like I’ve been doing, or at least as a cautionary tale of what happens when you punch far above your weight in C++. :slight_smile: What I write here is likely to be a compendium of noob mistakes I made, but the template syntax is starting to gel now.

Thanks to @tele_player I got back on track, and that allowed me to make sense of this thread about initializing different I2C peripherals. This is the code:


/*
Simple MCP23017 o-scope test.
*/

#include "daisy_seed.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;
using namespace daisy::seed;
DaisySeed hw;
Mcp23017 mcp;

TimerHandle timer;

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		out[0][i] = in[0][i];
		out[1][i] = in[1][i];
	}
}

//Timer callback - Can I do control reads here?
void TimerCallback(void* data)
{
    /** Use system time to blink LED once per second (1023ms) */
    bool led_state = (System::GetNow() & 1023) > 511;
    /** Set LED */
    hw.SetLed(led_state);
}

int main(void)
{
	hw.Init();
	
	// Cribbed from another forum post.  This works, at least to the point where
	// I see what looks like I2C waveforms for SCL and SDA on my cheap oscilloscope.
	static constexpr I2CHandle::Config i2c_config
		= {I2CHandle::Config::Peripheral::I2C_1,
		{{DSY_GPIOB, 8}, {DSY_GPIOB, 9}},
		I2CHandle::Config::Speed::I2C_100KHZ,  //3.3v does mushy square waves above this.
		I2CHandle::Config::Mode::I2C_MASTER};
	
	// The address param is set to 0x20, with A0, A1, and A2 pins
	// on the MCP pulled low on the breadboard.  The MCP23017 class
	// defaults to 0x27 on a bare Init().
	static constexpr Mcp23017::Config config {i2c_config, 0b100111};

	/**
	 * Questions:  
	 * 1. Should the I2C_Handle and Mcp23017 objects be static constexpr?
	 * 2. 
	*/

	mcp.Init(config);
	
	mcp.PortMode(MCPPort::A, 0x00, false); //0x00 is in, 0xff is out. All inputs, no invert, GPA7 is output only
	mcp.PortMode(MCPPort::B, 0x00, false);
	
	// Timer stuff.  Placeholder for now.
    TimerHandle         tim5;
    TimerHandle::Config tim_cfg;
    tim_cfg.periph     = TimerHandle::Config::Peripheral::TIM_5;
    tim_cfg.enable_irq = true;
    auto tim_target_freq = 25;
    auto tim_base_freq   = System::GetPClk2Freq();
    tim_cfg.period       = tim_base_freq / tim_target_freq;

    /** Initialize timer */
    tim5.Init(tim_cfg);
    tim5.SetCallback(TimerCallback);

    /** Start the timer, and generate callbacks at the end of each period */
    tim5.Start();;

	// What I expect to see on my scope from this is the SDA waveform changing
	// as it rolls through the pins and changes modes.  At the moment, 
	// the SDA waveform does not change.
	bool led_state = false;
	for(;;)
		{
			for(uint8_t i = 0; i < 7; i++) {
				if (led_state == true)
					mcp.PinMode(uint8_t(i), MCPMode::INPUT_PULLUP, false);
				else
					mcp.PinMode(uint8_t(i), MCPMode::OUTPUT, false);
			
			led_state = !led_state;
		}

		
	};

}

I currently have limited means to test the output pins. I have no 3.3v LEDs available, but I guess I could use the scope. Since most of this is really new to me, I’m not clear on what to expect on the scope, voltage at the pins, or what can be written to the serial port.