IO60P16 Module driver discussion thread (Contributors)

In my opinion those are I2C start bytes but named restart in order to differ from the first start byte in the sequence. I have doubts if the order of steps they show is correct. Since they are sending all in one packet that might not make a difference but in your case it might. E.g. they chose the port number in 5th step and I would do it as you did - 1st.

Woo! Hoo! Writing 1ā€™s to the PWM port output fixed it. Iā€™m going to play with all the PWM options now and figure out how much really needs to be exposed through the driver. It should be smooth sailing from here out. Thanks, Gralin!

This is great. Do not forget to try some servos if you have any.

Nice. If you face any more problems just share them with us. Just out of curiosity, are you going to expose eeprom access in the driver?

This forum is for taking driver feature requests :wink: Iā€™ve only thought about that a little. Whatā€™s the use case you have in mind? Off the cuff, I canā€™t quite think of why it would be needed. Iā€™m certainly open to suggestions, though.

In my regular job Iā€™m responsible for integrating 3rd party systems (fire, intrustion, cctv, sound, autmation etc.) with our building management system. Before we create such driver I conduct and interview in order to know what are the requirements. When I ask ā€œwhich functionality do you want to have integratedā€ the usual anwser is ā€œeverythingā€ :stuck_out_tongue: I hate that but it seems that when Iā€™m on the other side of the fence I act the same way asking for functionality that I donā€™t know is needed :wink: I was working with USBizi chips recently and I have respect for memory so 37KB seemed interesting. I guess you wonā€™t find a good use case for it on a Gadgeteer board. The only thing that i can think of is the use case described in the manual. You could use that space for your internal driver operation and store the I/O configuration there. I donā€™t know if all the settings can be save so that a power reset doesnā€™t concern you.

I suppose it wouldnā€™t be a big deal to add a couple functions for reading & writing bytes to the eeprom. Maybe someone will end up using the module specifically for that purpose :wink:

I say add them please. Why not? :slight_smile:

The more the merrier! ;D

Consider it done!

Gus, hereā€™s you some twitchy goodness! (and my first test of the new video tags)

Awesomeā€¦now put 16 servos and make them all dance :slight_smile:

Thanks for helping in this driver. Let us know when you can share the code please.

My time has been a little more constrained than I would like but Iā€™m shooting to have the first version done by end of next week at the latest that will probably have everything except module chaining. Iā€™ll start exploring that after I can release a single module driver. How would you prefer I share the code? Email it to you or put it up on a temporary CodePlex site?

This has been a very educational experience for me. Understanding that there are actually multiple clocks involved in generating different PWM period lengths was something that Iā€™d never even considered. I also feel a new level of comfort in reading data sheets. I highly encourage anyone out there thatā€™s never written a driver for a device like this from the ground/datasheet up to volunteer to write the driver for the next module that comes up. Itā€™s a great learning experience.

1 Like

Email is fine please.

After much testing and tweaking (and learning that my scope can lieā€¦) Iā€™m able to come up with PWM signals I feel pretty good about except in one case when the 32 kHz clock is required (see attached picture). Iā€™d appreciate the input from you EEs out there. Is this variance normal? All of my other tests are now very close to expected values. So, Iā€™m assuming this one should also. If it is wrong then is it possible that the internal clock is not what it claims to be? Is there any way I could measure that? I could fix this with a multiplier but Iā€™m hesitant to do so in case my module has a specific issue which doesnā€™t accurately represent most others. I donā€™t think there could be a code problem specific to this clock but my low level functions are below for you to review. Please point out anything you would do differently. Thanks.


void ProgramStarted()
 {
            io60p16 = new GTM.GHIElectronics.IO60P16(7);
            const byte port = 7;
            const byte pin = 0;

            io60p16.SetPwm(port, pin, 692 * 1000, 20000);
            io60p16.SetPwm(port, pin, 8 * 1000, 4000);
            io60p16.SetPwm(port, pin, 6 * 1000, 3000);
            io60p16.SetPwm(port, pin, 2 * 1000, 1000);
            io60p16.SetPwm(port, pin, 1 * 1000, 500);
            io60p16.SetPwm(port, pin, 150, 75);
            io60p16.SetPwm(port, pin, 10, 5);
}


    public class IO60P16 : GTM.Module
    {
       ...

        protected static PwmClockSource SelectClock(int periodns, out byte period, out byte divider)
        {
            // Tout = periodns = 1/clock * period * 1000
            // period = periodns / (1/clock) * 1000
            //        = clock / periodns * 1000

            PwmClockSource clock;
            divider = 0;

            if(periodns < 11)
            {
                clock = PwmClockSource.Clock_24MHz;
                period = (byte)(24*periodns);
            }
            else if(periodns < 170)
            {
                clock = PwmClockSource.Clock_1Mhz5;
                period = (byte)(1.5*periodns);
            }
            else if(periodns < 2720)
            {
                clock = PwmClockSource.Clock_93kHz75;
                period = (byte) (.09375*periodns);
            }
            else if(periodns < 7970)
            {
                clock = PwmClockSource.Clock_32kHz;
                period = (byte) (.032*periodns);
            }
            else if(periodns <= 693000)
            {
                clock = PwmClockSource.Clock_367Hz6;
                // Calc the best divider.
                divider = (byte)Math.Round(periodns/2720.085);        // Round() is necessary.  Otherwise, it always rounds down.
                period = 255;

                /* Why 2720.085?  Here's why...
                 * Tout_ms = divider / 93.75kHz * period
                 * Tout_ns = divider / 93.75kHz * period * 1000
                 * Tout_ns = divider * period * 10.667
                 * divider = Tout_ns / period / 10.667
                 * divider = Tout_ns / 255 / 10.667               <= default period to max (255)
                 * divider = Tout_ns / 2720.085
                 */
            }
            else
            {
                throw new ArgumentOutOfRangeException("periodns", "Period cannot be longer than 693,000 nS.");
            }

            return clock;
        }

        public void SetPwm(byte port, byte pin, int period_ns, int pulseWidth_ns)
        {
            byte period;
            byte clockDivider;
            var clock = SelectClock(period_ns, out period, out clockDivider);
            var dutyCycle = (float)pulseWidth_ns/(float)period_ns; 
            var pulseWidth = dutyCycle*period;
            SetPwm(port, pin, period, (byte)pulseWidth, clock, clockDivider);
        }

        public void SetPwm(byte port, byte pin, byte period, byte pulseWidth, PwmClockSource clock, byte clockDivider = 0)
        {
            MakePinLow(port, pin);

            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, (byte)clock);               // Config PWM (select 32kHz clock source)
            if(clockDivider > 0) WriteRegister(0x2c, clockDivider);     // Set the clock divider (if using 367.6 Hz clock)
            WriteRegister(0x2a, period);                    // set the period (0-256)
            WriteRegister(0x2b, pulseWidth);                // set the pulse width (0-(period-1))

            MakePinHigh(port, pin);
        }
    }
}

Question:
ā€œPeriod cannot be longer than 693,000 nS.ā€?

That canā€™t be right as that is 693us. But you need 1.5ms to run a servoā€¦ :slight_smile:

Good catch GMod. I had realized at one point that my ā€œnsā€'s should all be ā€œusā€'s but never went back and updated my documentation/variables. Alphabetically, ā€œnsā€ comes after ā€œmsā€ā€¦ Silly metric system. :wink: Iā€™ll get that cleaned up before release. Thanks.

Any ideas why ReadPort() always returns 255? I know Iā€™m writing to the port correctly (LED blinks) but I always get 255 when I try to read the current values.


        internal byte ReadPort(byte port)
        {
            // Read data start from register 0x00
            return ReadRegister((byte)(0x00 + port));
        }

        internal byte ReadRegister(byte reg)
        {
            var data = new byte[1];
            // Bring the pointer to the needed address
            i2c.Write(DEV_ADDR, new byte[] { reg });
            // Read the address
            i2c.Read(DEV_ADDR, data);
            return data[0];
        }

I have not looked at the datasheet, but maybe it needs the write and read to be in one transaction. No stop/start between the write and read, only a restartā€¦

@ gmod - Iā€™ve triedā€¦

internal byte ReadRegister(byte reg)
        {
            var data = new byte[1];
            i2c.WriteRead(DEV_ADDR, new byte[] {reg}, data);
            return data[0];
        }

andā€¦

internal byte ReadRegister(byte reg)
        {
            var data = new byte[1];
            i2c.ReadRegister(DEV_ADDR, reg);
            return data[0];
        }

Neither worked. Is there another syntax you had in mind? Keep in mind this is using the SoftwareI2C class.