IO60P16 Module driver discussion thread (Contributors)

http://www.ghielectronics.com/catalog/product/363
The base of this module is CY8C9560A IO extender chip. A single socket from mainboard will add 60 inputs/outputs with interrupt capability. Also, 16 of these pins can be PWM signals. The signals with PWM are exposed on header-placement so it is easy to add standard RC servo motors.

Here is a link to the Alpha driver on codeplex:
http://gadgeteer.codeplex.com/SourceControl/changeset/view/16948#215953

We need the community’s help in completing the driver 8). The contributors will get an appreciation gift. (It’s a secret :slight_smile: )

Here is CY8C9560A datasheet:
http://www.cypress.com/?docID=31413

Here is the module schematic:
http://www.ghielectronics.com/downloads/Gadgeteer/Module/IO60P16%20sch.pdf

Let us know if we missed any piece of infromation that might help you out writing the driver.

I just ordered a couple of these. I’ll help out on this one.

Thanks Ian :wink:

This thing has an impressive number of features. One thing I don’t see, though, is a glitch filter. Is it possibly there under a different name that I’m not familiar with?

Community, I’d appreciate your input on my proposed design of the driver for this module. I expect the modules to arrive by Monday and I’d like to start coding by then. My anniversary is this weekend so there’s no chance it’s happening before then :wink: Please let me know what you think about the approach I’m taking and if there’s anything else you would like to see included that I haven’t yet considered.

The basic approach that I’m taking is that the IO60P16 module should appear as an extension of the IO that we’re normally used to using through standard NETMF. Unfortunately, NETMF still does not have common interfaces for IO ports. So, I will be creating new classes that mimic the below mentioned classes as they are in v4.2. Where possible, all methods, events, & properties of the NETMF class will also be implemented in it’s IO60P16 counterpart. This approach also makes it easy to build arrays of ports which is important in many applications (ex. LED cubes).

Let’s try and hash this out by Monday, if possible. Thanks for you input!

Port (base class for all other [italic]x[/italic]Port classes)

Based on: Port Members | Microsoft Learn

InputPort

Based on: InputPort Class | Microsoft Learn

[italic]Example Usage:[/italic]

IO60P16.InputPort pin1 = io60p16.CreateInputPort(Pins.IO.Port0_Pin1);
IO60P16.InputPort pin1 = io60p16.CreateInputPort(Pins.IO.Port0_Pin1, Port.ResistorMode.PullDown);
IO60P16.InputPort pin1 = io60p16.CreateInputPort(Pins.IO.Port0_Pin1, Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeLow);
IO60P16.InputPort pin1 = io60p16.CreateInputPort(0, 1, resistorMode=pullDown);

OutputPort

Based on: OutputPort Class | Microsoft Learn

[italic]Example Usage:[/italic]

IO60P16.OutputPort pin1 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin1, true /* state */);
IO60P16.OutputPort pin1 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin1, true /* state */, Port.ResistorMode.PullDown);
IO60P16.OutputPort pin1 = io60p16.CreateOutputPort(0, 1, Port.ResistorMode.PullDown);

InterruptPort

Based on: InterruptPort Class | Microsoft Learn

[italic]Example Usage:[/italic]

IO60P16.InterruptPort pin1 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin1, Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeLow);
IO60P16.InterruptPort pin1 = io60p16.CreateOutputPort(0, 1, Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeLow);

PWM

Based on: http://msdn.microsoft.com/en-us/library/hh401130.aspx

[italic]Example Usage:[/italic]

IO60P16.PWM pin1 = io60p16.CreatePWM(Pins.Pwm.Pwm15, 2800*1000, 2000*1000, false /* invert */ );
IO60P16.PWM pin2 = io60p16.CreatePWM(15, 2800, 2000, PWM.ScaleFactor.Milliseconds, false /* invert */);
pin1.Start();
pin1.Stop();
PWM.Start(new [] { pin1, pin2 });

TristatePort

Based on: TristatePort Class | Microsoft Learn

[italic]Example Usage:[/italic]

IO60P16.TristatePort pin1 = io60p16.CreateTristatePort(Pins.IO.Port0_Pin1, false /* initial state */, Port.ResistorMode.PullDown);
IO60P16.TristatePort pin1 = io60p16.CreateTristatePort(0, 1, Port.ResistorMode.PullDown, false /* initial state */, Port.ResistorMode.PullDown);

Module Chaining (Module collection)
Up to eight modules can be chained together using Extender modules. During bootup, chained modules will be auto-detected and an array of IO60P16 objects built.

[italic]Example Usage:[/italic]

int cnt = io60p16.Modules.Count;
IO60P16.OutputPort pin1 = io60p16.Modules[1].CreateOutputPort(Pins.IO.Port0_Pin1, true /* state */);

EDIT: Added per Mikes suggestion.

IO60P16.Write() - sets the state of multiple ports simultaneously.

[italic]Example Usage:[/italic]

IO60P16.OutputPort pin1 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin0, true /* state */);
IO60P16.OutputPort pin2 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin1, true /* state */);
IO60P16.OutputPort pin3 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin2, true /* state */);
IO60P16.OutputPort pin4 = io60p16.CreateOutputPort(Pins.IO.Port0_Pin3, true /* state */);
var halfByte = new [] {pin1, pin2, pin3, pin4};
IO60P16.Write(halfByte, 11 /* 1011 */);

Because all of this is controlled via I2C, there’s probably going to be a limit as to how many could be changed simultaneously. I’ll learn more about this limitation once I can play with the boards.

IO60P16.Read() - reads the state of multiple ports simultaneously.

[italic]Example Usage:[/italic]

IO60P16.InputPort pin1 = io60p16.CreateInputPort(Pins.IO.Port0_Pin0, true /* state */);
IO60P16.InputPort pin2 = io60p16.CreateInputPort(Pins.IO.Port0_Pin1, true /* state */);
IO60P16.InputPort pin3 = io60p16.CreateInputPort(Pins.IO.Port0_Pin2, true /* state */);
IO60P16.InputPort pin4 = io60p16.CreateInputPort(Pins.IO.Port0_Pin3, true /* state */);
var halfByte = new [] {pin1, pin2, pin3, pin4};
int val = IO60P16.Read(halfByte);

Ian

how about the ability to group two or more bits/pins into a virtual register?

Good idea, Mike! I added an example above of how this could work (IO60P16.Write()). I also added a Read() function for reading multiple pins as a single value.

Keep 'em coming!

Are there any known issues regarding SoftwareI2C threading? I’ve received my modules and am trying some basic tests using the alpha driver and have discovered very different behavior when controlling the module directly within ProgramStarted() vs. a timer started within ProgramStarted(). If I blink an LED on the module directly within ProgramStarted(), it works as one would expected. However, if I start up a timer that fires every second and blinks once within the timer (see below) then I see the expected one second pause between timer ticks but the the blink itself happens very quickly. Also, I get repeated occurances of the following error (which I assume are the root cause of the fast blink). Has anyone seen this with SoftwareI2C before? I do not get the error when looping within ProgramStarted(). Yes, I know you shouldn’t loop within ProgramStarted() but I think that’s irrelevant for this test (correct me if there’s some situation that I’m not thinking of).

[quote]Program Started
#### Exception System.ApplicationException - 0x00000000 (1) ####
#### Message: SoftwareI2C: Exception writing to device at address 32 on socket 6 - perhaps device is not responding or not plugged in.
#### Gadgeteer.Interfaces.SoftwareI2C::WriteRead [IP: 0123] ####
#### Gadgeteer.Interfaces.SoftwareI2C::Write [IP: 0017] ####
#### Gadgeteer.Modules.GHIElectronics.IO60P16::WriteRegister [IP: 001a] ####
#### Gadgeteer.Modules.GHIElectronics.IO60P16::WritePort [IP: 000a] ####
#### Gadgeteer.Modules.GHIElectronics.IO60P16::MakePinLow [IP: 0019] ####
#### Test_App.Program::timer_Tick [IP: 001b] ####
#### Gadgeteer.Timer::dt_Tick [IP: 0018] ####
#### Microsoft.SPOT.DispatcherTimer::FireTick [IP: 0010] ####
#### Microsoft.SPOT.Dispatcher::PushFrameImpl [IP: 004a] ####
#### Microsoft.SPOT.Dispatcher::PushFrame [IP: 001d] ####
#### Microsoft.SPOT.Dispatcher::Run [IP: 0006] ####
#### Gadgeteer.Program::Run [IP: 001c] ####
#### Test_App.Program::Main [IP: 001a] ####
A first chance exception of type ‘System.ApplicationException’ occurred in Gadgeteer.dll
Exception performing Timer operation[/quote]

    public partial class Program
    {
        private static GTM.GHIElectronics.IO60P16 io60p16;

        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            io60p16 = new GTM.GHIElectronics.IO60P16(6);

            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");

            io60p16.MakePinOutput(7, 0);
          
            const byte port = 7;
            const byte pin = 0;
            //while (true)                // This loop works as expected.
            {
                io60p16.MakePinHigh(port, pin);
                Thread.Sleep(1000);
                io60p16.MakePinLow(port, pin);
                Thread.Sleep(1000);
            }

            // This loop causes very tight blinks (<100ms)
            var timer = new GT.Timer(1000);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(GT.Timer timer)
        {
            const byte port = 7;
            const byte pin = 0;
            io60p16.MakePinHigh(port, pin);
            Thread.Sleep(1000);
            io60p16.MakePinLow(port, pin);
        }
    }

I guess GT timer must be different to a normal timer thread and doesn’t like the thread.sleep. Perhaps simply change your timer to a half-period timer and turn it on or turn it off based on current state?

A normal timer would cause concurrency issues because the tick handler is invoke each second regardless if the last invoke ended. So you should be getting two threads at a time in your case.

If I replace the GT.Timer with a normal Timer then the code works as expected. So, the problem is definitely the fact that Thread.Sleep() and GT.Timer do not get along. I’ve never had problems using this syntax in the past. Is there something unique about SoftwareI2C that causes this behavior?

@ Gralin, I’m not sure I follow you on how the original example would result in two threads? Are you talking about the dispatcher thread + the timer thread or dispatcher + two timer threads?

Actually, perhaps it’s behaving as expected? Maybe what you’re actually seeing is the two timer ticks tripping up on each other?

Your timer will fire every second; in there, you sleep for a second.

Try halving your thread.sleep interval and see what happens.

@ ianlee, What i mean is the same Brett just described. Regular .NET timer invokes it’s tick handlers on a new thread. This way no matter if you sleep in you handler, the interval will be maintained. Try setting the gadgeteer timer behaviour to RunOnce and restart it at the end of your tick handler.

Of course… Silly me. Halving the time worked fine. Copy & paste w/o using my brain again… :-[
Thanks!

I need a little help understand this data sheet. I’ve been working all night trying to get it to generate a PWM signal but am not having any luck. I’m new at working at this level but this is what I’ve deciphered as should work but I’m not getting a wave - only a solid 1.63V out of PWM8.

Is there something I have to do to activate the PWM settings or does each change get applied as it is written? I’m running out of ideas.

io60p16.SetPwm(7, 0, 0x5e, 0x2f);  // Port 7, pin 0 = PWM8
        public void SetPwm(byte port, byte pin, byte period, byte pulseWidth)
        {
            WriteRegister(0x18, port);          // Select port

            var b = ReadRegister(0x1a);         
            b &= (byte)(~(1 << pin));           
            WriteRegister(0x1a, b);             // select PWM for port output

            b = ReadRegister(0x1C);             
            b &= (byte)(~(1 << pin));           
            WriteRegister(0x1C, b);             // Set pin for output.

            WriteRegister(0x28, (byte)(0x08 + pin));          // Select the PWM pin to configure.
            WriteRegister(0x29, 0x00);          // Config PWM (select 32kHz clock source)
            WriteRegister(0x2a, period);        // set the period (0-256)
            WriteRegister(0x2b, pulseWidth);    // set the pulse width (0-(period-1))
        }

ReadRegister() & WriteRegister() work fine with my I/O tests. So, I don’t think there’s any problems there.

Thanks!

I agree the datasheet is not clear

Any ideas?

Try Cypress forum.You might get an answer faster there:

http://www1.cypress.com/?app=forum&rID=54008

Indeed the datasheet is not very clear but i see a few things you can try:

[line]

var b = ReadRegister(0x1a);         
b &= (byte)(~(1 << pin));           
WriteRegister(0x1a, b);   // select PWM for port output

You are reseting the bit and you should do the oposite. You do the same to set the pin as output but that’s ok because ‘0’ means output mode.

value |= (byte)(1 << pin);

[line]

WriteRegister(0x28, (byte)(0x08 + pin));   // Select the PWM pin to configure.

Why are you adding 0x08?

[line]

[quote]Note that a pin used as PWM output must be configured to the
appropriate drive mode. See Table 10 on page 12 for more information.[/quote]

I didn’t find any info about which mode is the approporiate one. We can chose from: Resistive Pull Up, Resistive Pull Down, Open Drain High, Open Drain Low, Strong Drive, Slow Strong Drive, High Impedance.

Good catch. I’ll give this a try tonight. This very well could be what is holding it up.

That was one of my late night stabs at trying to make sure I was specifying an output port (0x08-0x0f). Looking at it again this morning it indeed is wrong. Prior attempts did not add the 0x08.

I didn’t either.

I also found this application note that shows some crude examples of configuration. It does a lot of restarts. It’s not clear to me if those are meant to be physical resets or if that’s saying that those are just meant to be different I2C packets. Perhaps it means more to you.

http://www.cypress.com/?docID=31264

I’ve started a thread there also at:
http://www.cypress.com/?app=forum&id=1573&rID=62620