Introduction: Mcp23017 and PoKeys and Linuxcnc

Introduction:

The mcp23017 is really a nice piece of electronics especially when you need extra I/O's and you need a mix of outputs and inputs, now there is a lot of papers to discuss how to connect it to a bunch of microcontrollers ATMEL , PIC, Arduino or even raspberry pi.

but i will discuss here how to connect it to pokeys under linux environment and to be specific under Linuxcnc.

Copyright stuff:

This tutorial won’t be here if this thread wasn't there

https://forum.linuxcnc.org/24-hal-components/29816-pokeys?limitstart=0

I would like to thank "fixer" for his work on this topic.

Step 1: ​Communicating With Mcp23017:

Now the mcp23017 is easy to work with but you must know a couple of things beforehand. a look in the datasheet wouldn't hurt at all, but who will read a dozen of pages to know just three lines:

  1. Set port direction.
  2. Set pull up resistors
  3. Read or write to that port.

Easy huh.

Step 2: ​Some Basic Extra Information:

So as you may noticed connecting the mcp23017 to pokeys doesn't reserve any pins

with this configuration the address of mcp is 0x20 hexadecimal when all address pins a[0..2] are connected to ground, reset pin must be always connected to +5V unless you need reset it externally.

The MCP23017 has two 8 bits ports GPA and GPB these two ports as we said before needs to be initialized first to select IO direction, then if you select some of the IO's to be an input it's a good idea to use the internal programmable pull-up resistors, then you have to send a write command or send a read command to the mcp23017.

the table shows all registers that can be accessed in the mcp23017

whats really important is the
first two columns, the last column is just for knowing the default values as the mcp23017 is powered up or reseted.

Step 3: Setting Up the PoKeys Part:

Now pokeys is really designed to work on windows and the support for Linux is really limited but referring to this page i could have the cross platform library:

https://bitbucket.org/mbosnak/pokeyslib/overview

after downloading the library refer to installation section, in short some libraries must be installed first USBlib-1.0

sudo apt-get install libusb-1.0-0 
sudo apt-get install libusb-1.0-0-dev

then install pokeys library :

sudo make -f Makefile.noqmake install

Step 4: Communicate With Mcp23017 Through PoKeys:

this simple example shows you how

to call the mcp23017 when it's connected to PoKeys.

address of the mcp23017 is 0x20 so pay attention if have other addresses .

the build code will be something like this:

gcc -Wall -o "i2c" "i2c.c" -lPoKeys -lusb-1.0 -std=gnu99

i2c.c

#include<PoKeysLib.h>
#include<unistd.h>/* UNIX standard function definitions */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
intwriteI2C(sPoKeysDevice *PK,uint8_t Reg,uint8_t Value)
{
//read portB
uint8_t status;
usleep(1000);
unsignedchar cmd1[]= {Reg,Value};
PK_I2CWriteStart(PK, 0x20, cmd1, 2);
usleep(1000);
PK_I2CWriteStatusGet(PK, &status);
return status;
}
intreadI2C(sPoKeysDevice *PK,uint8_t Reg,unsignedchar* buffer,uint8_t length)
{
unsignedchar cmd[] = {Reg};
int i;
uint8_t status;
PK_I2CWriteStart(PK, 0x20, cmd, 1);
// Read bytes
PK_I2CReadStart(PK, 0x20, length);
unsignedchar readBytes;
unsignedchar readBuffer[length];
usleep(100000);
PK_I2CReadStatusGet(PK, &status, &readBytes, readBuffer, length);
if (status == PK_I2C_STAT_COMPLETE)
{
for (i = 0; i < length ; i++)
{
buffer[i] = readBuffer[i];
}
}
return status;
}
intsetupI2c(sPoKeysDevice *PK , unsignedchar* device)
{
PK_I2CBusScanStart(PK);
// Wait until the scan is complete...
intwait = 10;
uint8_t status;
unsignedchar devices[128] = {0};
while(wait--)
{
usleep(100000);
PK_I2CBusScanGetResults(PK, &status, devices, 128);
if (status == PK_I2C_STAT_COMPLETE) break;
}
if (status == PK_I2C_STAT_COMPLETE)
{
for (int i = 0; i < 128; i++)
{
if ((i % 16) == 0)
{
printf("\n%02X", i);
}
if (devices[i])
printf("\%02X", i);
else
printf("--");
}
} else
{
printf("\nScan was not completed in time.");
}
return status;
}
intmain()
{
int devNum = PK_EnumerateUSBDevices();
printf("Found %u USB PoKeys devices", devNum);
if (devNum == 0) return0;
printf("\nConnecting to first PoKeys device...");
sPoKeysDevice *dev = PK_ConnectToDevice(0);
if (dev == NULL)
{
printf(" Error!");
return0;
}
printf(" connected");
printf("\nScanning for present I2C devices...");
PK_I2CBusScanStart(dev);
// Wait until the scan is complete...
intwait = 10;
uint8_t status;
unsignedchar devices[128] = {0};
while(wait--)
{
usleep(100000);
PK_I2CBusScanGetResults(dev, &status, devices, 128);
if (status == PK_I2C_STAT_COMPLETE) break;
}
if (status == PK_I2C_STAT_COMPLETE)
{
for (int i = 0; i < 128; i++)
{
if ((i % 16) == 0)
{
printf("\n%02X", i);
}
if (devices[i])
printf("\%02X", i);
else
printf("--" );
}
} else
{
printf("\nScan was not completed in time.");
}
// Check if MCP23017 is present...
if (devices[0x20])
{
printf("\nMCP23017 Port is present \nReading PORT ...");
status = writeI2C(dev,0x00,255);
status |= writeI2C(dev,0x01,255);
status |= writeI2C(dev,0x0C,255);
status |= writeI2C(dev,0x0D,255);
if (status == PK_I2C_STAT_COMPLETE)
{
unsignedchar readBuffer[1];
status = readI2C(dev,0x12,readBuffer,sizeof readBuffer);
if (status == PK_I2C_STAT_COMPLETE)
{
printf(" A=%X", 255-readBuffer[0]);
} else
{
printf("Error reading");
}
unsignedchar readBufferB[1];
status = readI2C(dev,0x13,readBufferB,sizeof readBufferB);
if (status == PK_I2C_STAT_COMPLETE)
{
printf(" B=%X\n", 255-readBufferB[0]);
} else
{
printf("Error reading");
}
} else
{
printf("Error writing");
}
}
PK_DisconnectDevice(dev);
printf("\n");
return0;
}
view rawi2c.c hosted with ❤ by GitHub

Attachments

Step 5: Linuxcnc Part:

Compile this file use the following command:

sudo halcompile ./pokeys.comp

Then use this following big chunk of code to make the binary

gcc -Wall -Os -g -I. -I/usr/include/libusb-1.0 -I/usr/include -L/usr/lib/ -L/usr/lib/arm-linux-gnueabihf/ -lPoKeys -lusb-1.0 -I/usr/realtime-3.4-9-rtai-686-pae/include -I. -I/usr/realtime-3.4-9-rtai-686-pae/include -I/usr/include/i386-linux-gnu -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -fno-math-errno -funsafe-math-optimizations -fno-rounding-math -fno-signaling-nans -fcx-limited-range -mhard-float -DRTAI=3 -fno-fast-math -mieee-fp -fno-unsafe-math-optimizations -DRTAPI -D_GNU_SOURCE -Drealtime -D__MODULE__ -I/usr/include/linuxcnc -Wframe-larger-than=2560 -URTAPI -U__MODULE__ -DULAPI -Os -o pokeys ./pokeys.c -Wl,-rpath,/lib -L/lib -llinuxcnchal

Then move the binary file to place that linuxcnc can reach:

sudo mv ./pokeys /usr/bin/pokeys

pokeys.comp

component pokeys "PoKeys IO driver, by Mit Zot";
option userspace yes;
pin out bit in-# [55];
pin out bit inext-# [16];
pin out unsigned ain-# [3];
pin out bit err;
pin in unsigned devSerial;
pin out bit alive;
license "GPL";
;;
#include "PoKeysLib.h"
#include /* UNIX standard function definitions */
sPoKeysDevice * dev=0;
int i=0;
bool notsetup=true;
unsigned char readBuffer[1];
int status=0;
int writeI2C(sPoKeysDevice *PK,uint8_t Reg,uint8_t Value)
{
uint8_t status;
usleep(1000);
unsigned char cmd1[]= {Reg,Value};
PK_I2CWriteStart(PK, 0x20, cmd1, 2);
usleep(1000);
PK_I2CWriteStatusGet(PK, &status);
return status;
}
int readI2C(sPoKeysDevice *PK,uint8_t Reg,unsigned char* buffer,uint8_t length)
{
unsigned char cmd[] = {Reg};
int i;
uint8_t status;
PK_I2CWriteStart(PK, 0x20, cmd, 1);
// Read bytes
PK_I2CReadStart(PK, 0x20, length);
unsigned char readBytes;
unsigned char readBuffer[length];
usleep(1000);
PK_I2CReadStatusGet(PK, &status, &readBytes, readBuffer, length);
if (status == PK_I2C_STAT_COMPLETE)
{
for (i = 0; i < length ; i++)
{
buffer[i] = readBuffer[i];
}
}
return status;
}
void user_mainloop(void)
{
while(0xb){
FOR_ALL_INSTS() {
while(dev == NULL)dev = PK_ConnectToDeviceWSerial(devSerial, 2000); //waits for usb device
alive=1;
if ((PK_DigitalIOGet(dev) == PK_OK) && (PK_AnalogIOGet(dev) == PK_OK)){ //gets IO data and checks return value
err=0;
for(i=0;i<54;i++)in(i)=!dev->Pins[i].DigitalValueGet; //just transfers values
for(i=0;i<3;i++)ain(i)=dev->Pins[i+44].AnalogValue;
//if (notsetup)
//{
status = writeI2C(dev,0x00,255);
status |= writeI2C(dev,0x01,255);
status |= writeI2C(dev,0x0C,255);
status |= writeI2C(dev,0x0D,255);
//if (status == PK_I2C_STAT_COMPLETE)
//{notsetup = false;}}
//if (!notsetup)
//{
status = readI2C(dev,0x12,readBuffer,sizeof readBuffer);
for (i=0;i<8;i++)inext(i) = (readBuffer[0]>>i)&0x01;
status = readI2C(dev,0x13,readBuffer,sizeof readBuffer);
for (i=0;i<8;i++)inext(i+8) = (readBuffer[0]>>i)&0x01;
//}
//}
}
else{ //on connection error
PK_DisconnectDevice(dev);
dev=NULL; //tries to reconnect
err=1;
for(i=0;i<54;i++)in(i)=0;
for(i=0;i<3;i++)ain(i)=0;
}
alive=0;
usleep(40000);
}
}
exit(0);
}
view rawpokeys.comp hosted with ❤ by GitHub

Step 6: Finding Pokeys Serial

you can get it through several ways:

-Windows : just open PoKeys program and when you connect to it will show the ID of your PoKeys

-Linux : use the following command to find it

less /proc/bus/input/devices

scroll down till you find somthing related to PoKeys

Note the ID is mention in "U" line use the last five digits.

Step 7: Test the Comp File

To test our setup we will use halrun command, open a terminal window:

linuxcnc@debian:/usr/share/linuxcnc$ halrun
halcmd: loadusr pokeys

//to see all pins note there will be all pokeys pins and external ios from mcp23017

halcmd: show pin

To start your comp

setp pokeys.0.devSerial 12345

replace 12345 with device serial found from previous step.

the next step to make sure it's running and check signals

Step 8: Testing the Comp File

but how can be sure that it's really started, ok we need some kind of scope to see it, don't worry no need to buy fancy stuff it's all there you know it we can use halscope

then back to the halrun command:

linuxcnc@debian:/usr/share/linuxcnc$ halrun

.

halcmd: loadusr pokeys

halcms: setp pokeys.o.devserial 32832

halcmd: loadrt threads name1=fast period1=50000

halcmd: start

halcmd: loadusr halscope

Step 9: Choose How Many Channels

Step 10: Select First Channel to Be Pokeys.0.alaive

Step 11: Then in Run Mode Select Roll and You Will See the Alive Pin Toggling

Step 12: Add Some Extra Channels

add some channels an IO like "pokeys.0.inext-0", and try to toggle it by wiring it to ground ( remember we used the the internal pull-up resistor ) .

Step 13: Final Word

please note the scan rate of this setup not more than10 Hz which is good for buttons lamps or none vital inputs or outputs ( don't use it for emergency or limit switches)