Need help with the MCP2210 on Linux with libusb

nsaspook

Joined Aug 27, 2009
13,079
I've had a breakthrough. Basically, you use the libusb backend by replacing "hidapi-hidraw" by "hidapi-libusb" inside "Makefile-Debug.mk". Search for the following:

Code:
# Link Libraries and Options
LDLIBSOPTIONS=`pkg-config --libs hidapi-hidraw`

# Build Targets
.build-conf: ${BUILD_SUBPROJECTS}
    "${MAKE}"  -f nbproject/Makefile-${CND_CONF}.mk ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/hidapi_test

${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/hidapi_test: ${OBJECTFILES}
    ${MKDIR} -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}
    ${LINK.cc} -o ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/hidapi_test ${OBJECTFILES} ${LDLIBSOPTIONS}

${OBJECTDIR}/hidtest.o: hidtest.cpp
    ${MKDIR} -p ${OBJECTDIR}
    ${RM} "$@.d"
    $(COMPILE.cc) -g `pkg-config --cflags hidapi-hidraw`   -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/hidtest.o hidtest.cpp
If you can detach the kernel HID driver for libusb to communicate with the device that would be great.
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Using hidraw, the program produces the following output:
Code:
Device Found
  type: 3938 1031
  path: /dev/hidraw2
  serial_number:

  Manufacturer: MOSART Semi.
  Product:      2.4G Wireless Mouse
  Release:      108
  Interface:    0

Device Found
  type: 04d8 00de
  path: /dev/hidraw3
  serial_number: 0000992504

  Manufacturer: Microchip Technology Inc.
  Product:      MCP2210 USB to SPI Master
  Release:      2
  Interface:    0

Device Found
  type: 0461 0010
  path: /dev/hidraw0
  serial_number:

  Manufacturer: NOVATEK
  Product:      USB Keyboard
  Release:      104
  Interface:    0

Device Found
  type: 0461 0010
  path: /dev/hidraw1
  serial_number:

  Manufacturer: NOVATEK
  Product:      USB Keyboard
  Release:      104
  Interface:    1

Open Device: 04d8:00de
Manufacturer String: Microchip Technology Inc.
Product String: MCP2210 USB to SPI Master
Serial Number String: (48) 0000992504
Unable to read indexed string 1
Indexed String 1:

GPIO pin function:
   20 00 00 20 00 01 00 00 00 00 00 00 00 00
GPIO pin direction
   33 00 00 20 00 00
GPIO pin level
   30 00 00 20 fd 01
SPI MCP3204 transfer settings
   41 00 11 00 c0 c6 2d 00 ff 01 00 00 00 00 00 00 00 00 03 00 00
SPI transfer response
   42 00 00 20 00 00 00
MCP3204 chan 0 volts = 0.00

SPI MCP23S08 transfer settings
   41 00 11 00 c0 c6 2d 00 ff 01 00 00 00 00 00 00 00 00 03 00 00
Ctrl c to exit
Using libusb, the program produces the following output:
Code:
Debug/GNU-Linux/hidapi_test
Device Found
  type: 0461 0010
  path: 0003:0003:00
  serial_number: (null)

  Manufacturer: NOVATEK
  Product:      USB Keyboard
  Release:      104
  Interface:    0

Device Found
  type: 0461 0010
  path: 0003:0003:01
  serial_number: (null)

  Manufacturer: NOVATEK
  Product:      USB Keyboard
  Release:      104
  Interface:    1

Device Found
  type: 04d8 00de
  path: 0003:0023:00
  serial_number: 0000992504

  Manufacturer: Microchip Technology Inc.
  Product:      MCP2210 USB to SPI Master
  Release:      2
  Interface:    0

Device Found
  type: 3938 1031
  path: 0003:0007:00
  serial_number: (null)

  Manufacturer: MOSART Semi.
  Product:      2.4G Wireless Mouse
  Release:      108
  Interface:    0

Open Device: 04d8:00de
Manufacturer String: Microchip Technology Inc.
Product String: MCP2210 USB to SPI Master
Serial Number String: (48) 0000992504
Indexed String 1: Microchip Technology Inc.

GPIO pin function:
   20 00 01 d0 00 01 00 00 00 00 00 00 00 00
GPIO pin direction
   33 00 01 d0 00 00
GPIO pin level
   30 00 01 d0 fd 01
SPI MCP3204 transfer settings
   41 00 11 00 50 c3 00 00 ff 01 00 00 00 00 00 00 00 00 03 00 00
SPI transfer response
   42 00 03 10 06 0a a3
MCP3204 chan 0 volts = 3.32

SPI MCP23S08 transfer settings
   41 00 11 00 c0 c6 2d 00 ff 01 00 00 00 00 00 00 00 00 03 00 00
Ctrl c to exit
The libusb backend always detaches the kernel from the device, so it seems. The file dissapears once the program is called, and usbview lists the device as not having a driver attached once the program is finished.

Edit: Both backends are sending and receiving interrupt transfers only. No control transfers whatsoever captured on Wireshark.

Edit 2: Confirmed that, on both cases, the permissions take eons to get applied. Probably, the permissions given to the file are a symptom, not the cause. More so because the libusb backend doesn't need that device file at all. For sure, udev takes time to be restarted, which is not normal.
 
Last edited:

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
I've decided to make a small program to open the device using libusb. Note that this program reattaches the kernel, in good behavior, once it is finished. However, I'm having the same behavior again. Here is the code:
mcp2210-open.c:
// Includes
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <libusb-1.0/libusb.h>
#include "libusb-extra.h"

// Defines
#define EXIT_USERERR 2  // Exit status value to indicate a command usage error
#define TR_TIMEOUT 100  // Transfer timeout in milliseconds
#define VID 0x04D8      // USB vendor ID
#define PID 0x00DE      // USB product ID

int main(int argc, char **argv)
{
    int err_level = EXIT_SUCCESS;  // Note that this variable is declared externally!
    libusb_context *context;
    if (libusb_init(&context) != 0) {  // Initialize libusb. In case of failure
        fprintf(stderr, "Error: Could not initialize libusb.\n");
        err_level = EXIT_FAILURE;
    } else {  // If libusb is initialized
        libusb_device_handle *devhandle;
        if (argc < 2) {  // If the program was called without arguments
            devhandle = libusb_open_device_with_vid_pid(context, VID, PID);  // Open a device and get the device handle
        } else {  // Serial number was specified as argument
            devhandle = libusb_open_device_with_vid_pid_serial(context, VID, PID, (unsigned char *)argv[1]);  // Open the device having the specified serial number, and get the device handle
        }
        if (devhandle == NULL) {  // If the previous operation fails to get a device handle
            fprintf(stderr, "Error: Could not find device.\n");
            err_level = EXIT_FAILURE;
        } else {  // If the device is successfully opened and a handle obtained
            bool kernel_attached = false;
            if (libusb_kernel_driver_active(devhandle, 0) == 1) {  // If a kernel driver is active on the interface
                libusb_detach_kernel_driver(devhandle, 0);  // Detach the kernel driver
                kernel_attached = true;  // Flag that the kernel driver was attached
            }
            if (libusb_claim_interface(devhandle, 0) != 0) {  // Claim the interface. In case of failure
                fprintf(stderr, "Error: Device is currently unavailable.\n");
                err_level = EXIT_FAILURE;
            } else {  // If the interface is successfully claimed
                printf("Device successfully open.\n");
                libusb_release_interface(devhandle, 0);  // Release the interface
            }
            if (kernel_attached) {  // If a kernel driver was attached to the interface before
                libusb_attach_kernel_driver(devhandle, 0);  // Reattach the kernel driver
            }
            libusb_close(devhandle);  // Close the device
        }
        libusb_exit(context);  // Deinitialize libusb
    }
    return err_level;
}
libusb-extra.h:
/* Extra functions for libusb - Version 1.2
   Copyright (c) 2018-2020 Samuel Lourenço

   This library is free software: you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or (at your
   option) any later version.

   This library is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this library.  If not, see <https://www.gnu.org/licenses/>.


   Please feel free to contact me via e-mail: samuel.fmlourenco@gmail.com */


#ifndef LIBUSB_EXTRA_H_
#define LIBUSB_EXTRA_H_

// Includes
#include <libusb-1.0/libusb.h>

// Function prototypes
libusb_device_handle *libusb_open_device_with_vid_pid_serial(libusb_context *context, uint16_t vid, uint16_t pid, unsigned char *serial);

#endif
libusb-extra.c:
/* Extra functions for libusb - Version 1.2
   Copyright (c) 2018-2020 Samuel Lourenço

   This library is free software: you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or (at your
   option) any later version.

   This library is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this library.  If not, see <https://www.gnu.org/licenses/>.


   Please feel free to contact me via e-mail: samuel.fmlourenco@gmail.com */


// Includes
#include <string.h>
#include "libusb-extra.h"

libusb_device_handle *libusb_open_device_with_vid_pid_serial(libusb_context *context, uint16_t vid, uint16_t pid, unsigned char *serial)  // Opens the device with matching VID, PID and serial number
{
    libusb_device **devs;
    libusb_device_handle *devhandle = NULL;
    if (libusb_get_device_list(context, &devs) >= 0) {  // If the device list is retrieved
        libusb_device *dev;
        size_t devcounter = 0;
        while ((dev = devs[devcounter++]) != NULL) {  // Walk through all the devices
            struct libusb_device_descriptor desc;
            if (libusb_get_device_descriptor(dev, &desc) == 0 && desc.idVendor == vid && desc.idProduct == pid && libusb_open(dev, &devhandle) == 0) {  // If the device descriptor is retrieved, both PID and VID match, and if the device is successfully opened
                unsigned char str_desc[256];
                libusb_get_string_descriptor_ascii(devhandle, desc.iSerialNumber, str_desc, sizeof(str_desc));  // Get the serial number string in ASCII format
                if (strcmp((char *)str_desc, (char *)serial) == 0) {  // If the serial number match
                    break;
                } else {
                    libusb_close(devhandle);  // Close the device, since it is not the one with the corresponding serial number
                    devhandle = NULL;  // Set device handle value to null pointer
                }
            }
        }
        libusb_free_device_list(devs, 1);  // Free device list
    }
    return devhandle;  // Return device handle (or null pointer if no matching device was found)
}
Makefile:
CC = gcc
CFLAGS = -O2 -std=c99 -Wall -pedantic
LDFLAGS = -s
LDLIBS = -lusb-1.0
OBJECTS = libusb-extra.o
TARGETS = mcp2210-open

.PHONY: all clean install uninstall

all: $(TARGETS)

$(TARGETS): % : %.o $(OBJECTS)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $<

clean:
    $(RM) *.o $(TARGETS)

install:
    mv -f $(TARGETS) /usr/local/bin/.

uninstall:
    cd /usr/local/bin && $(RM) $(TARGETS)

I suspect that I have comflicting udev rules, namely this one that applies to every Microchip device (bad practice) but it is really targeted to the Microchip programmers.
Code:
# 2017.03,03 Added check for Atmel tools.
# 2012.01.23 Changed SYSFS reference(s) to ATTR.
# 2011.12.15 Note: Reboot works on all systems to have rules file recognized.
# 2010.01.26 Add reference to "usb" for Ubuntu.
# 2010.01.22 Attempt to further simplify rules files requirements.
# 2009.08.18 Rules file simplified.
# 2009.07.15 Rules file created.

ENV{hotplugscript}="/etc/.mplab_ide/mchplinusbdevice"

ACTION!="add", GOTO="check_remove"
SUBSYSTEM=="usb_device", GOTO="check_add"
SUBSYSTEM!="usb", GOTO="rules_end"

LABEL="check_add"

ATTR{idVendor}=="04d8", MODE="666", RUN+="%E{hotplugscript} add"
ATTR{idVendor}=="03eb", MODE="666", RUN+="%E{hotplugscript} add"
GOTO="rules_end"

LABEL="check_remove"

ACTION=="remove", RUN+="%E{hotplugscript} remove %E{PRODUCT}"

LABEL="rules_end"
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
My suspicion was right. I've changed the conflicting udev rules, and now it works flawlessly. PICkit3 still works as intended. Basically, the conflicting rules were changed to:
Code:
# 2017.03,03 Added check for Atmel tools.
# 2012.01.23 Changed SYSFS reference(s) to ATTR.
# 2011.12.15 Note: Reboot works on all systems to have rules file recognized.
# 2010.01.26 Add reference to "usb" for Ubuntu.
# 2010.01.22 Attempt to further simplify rules files requirements.
# 2009.08.18 Rules file simplified.
# 2009.07.15 Rules file created.

#ENV{hotplugscript}="/etc/.mplab_ide/mchplinusbdevice"

ACTION!="add", GOTO="check_remove"
SUBSYSTEM=="usb_device", GOTO="check_add"
SUBSYSTEM!="usb", GOTO="rules_end"

LABEL="check_add"

ATTR{idVendor}=="04d8", MODE="666"
ATTR{idVendor}=="03eb", MODE="666"
GOTO="rules_end"

LABEL="check_remove"

#ACTION=="remove", RUN+="%E{hotplugscript} remove %E{PRODUCT}"

LABEL="rules_end"

Furthermore, and changing the subject a bit, I found out that the libusb implementation is far more powerful than the hidraw one because:
- It can detect when the device is being used;
- It is able to take over a device being accessed by a hidraw based program, while a hidraw based program can't take over a libusb based one.

However, I rather prefer doing my own implementation with libusb, because I want to reattach the kernel once I'm done, and I want to have less overhead. Basically, I only need to reimplement hid_read() and hid_write(). I also want to be able to detect if and when a device is unplugged, which none of the current implementations do.
 

nsaspook

Joined Aug 27, 2009
13,079
My suspicion was right. I've changed the conflicting udev rules, and now it works flawlessly. PICkit3 still works as intended. Basically, the conflicting rules were changed to:
Code:
# 2017.03,03 Added check for Atmel tools.
# 2012.01.23 Changed SYSFS reference(s) to ATTR.
# 2011.12.15 Note: Reboot works on all systems to have rules file recognized.
# 2010.01.26 Add reference to "usb" for Ubuntu.
# 2010.01.22 Attempt to further simplify rules files requirements.
# 2009.08.18 Rules file simplified.
# 2009.07.15 Rules file created.

#ENV{hotplugscript}="/etc/.mplab_ide/mchplinusbdevice"

ACTION!="add", GOTO="check_remove"
SUBSYSTEM=="usb_device", GOTO="check_add"
SUBSYSTEM!="usb", GOTO="rules_end"

LABEL="check_add"

ATTR{idVendor}=="04d8", MODE="666"
ATTR{idVendor}=="03eb", MODE="666"
GOTO="rules_end"

LABEL="check_remove"

#ACTION=="remove", RUN+="%E{hotplugscript} remove %E{PRODUCT}"

LABEL="rules_end"

Furthermore, and changing the subject a bit, I found out that the libusb implementation is far more powerful than the hidraw one because:
- It can detect when the device is being used;
- It is able to take over a device being accessed by a hidraw based program, while a hidraw based program can't take over a libusb based one.

However, I rather prefer doing my own implementation with libusb, because I want to reattach the kernel once I'm done, and I want to have less overhead. Basically, I only need to reimplement hid_read() and hid_write(). I also want to be able to detect if and when a device is unplugged, which none of the current implementations do.
Great news!
 

geekoftheweek

Joined Oct 6, 2013
1,201
I picked up a couple MCP2221 boards to play with and after a few hours of research into writing USB programs to use them I stumbled across the kernel module hid_mcp2221. A quick 'modprobe i2c-dev' and 'modprobe hid_mcp2221' I ended up with /dev/i2c-8 and /dev/gpiochip1 showing up after I plug in the MCP2221.

I'm running this on a normal PC (not a PI) and Arch Linux.

The I2C part works as it should and I am able to use it with 'i2cdetect 8' to detect connected slaves.

I can use 'gpioinfo 1' to list the available pins on the MCP2221, but if I try 'gpioget 1' it returns
gpioget: error reading GPIO values: No such file or directory

If I look for /sys/class/gpio as I would on a PI it doesn't exist which is where I'm thinking libgpio is coming up with the no such file.

I'm just wondering if anyone else has been down this road. I see functions for setting gpio in the hid_mcp2221 module source so it would appear the functionality is there, but maybe udev isn't creating things right or I'm missing another module that needs loaded or something.

I tried to connect it to a Pi4 to see how it would react, but there doesn't seem to be a kernel module for the mcp2221 already installed and I'm not having much luck at the moment finding out why or how to change that.
 

geekoftheweek

Joined Oct 6, 2013
1,201
I scoured the internet some more and while I didn't find the answer I was looking for I did find libhidapi which I can use to send and receive reports and use it that way. I knew I found it before, but couldn't figure out what to search to find it and spent too long on other libraries that didn't work. So far so good...

I was hoping to be able to use the /dev/gpiochip and i2c so I could pretty much use the same programming for the PI and the MCP2221 by only changing a line or two.
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Yes, you are better off using hidapi (libhidapi), especially with the libusb backend (libhidapi-libusb). If you use that backend, though, you'll have to provide the VID and PID of your device, which you can easily find via usbview. The MCP2210 is not just an I2C device, and it is not a guarantee that the kernel module implements all the functionality (such as writing to the EEPROM or changing GPIO modes).

You have a whole topic about this in this very same forum, at https://forum.allaboutcircuits.com/threads/need-help-with-the-mcp2210-on-linux-with-libusb.179080/. Changing the backend from hidraw to libusb can be cryptic, and the libusb implementation of hidapi is not well behaved in the sense that it doesn't reattach the kernel to the device once it is finished. However, libusb itself provides that facility, and why hidapi doesn't implement it is beyond me. The information is terse, but I believe that, with the help of the MCP2210 app notes, you will be able to fully implement what you want.
 

geekoftheweek

Joined Oct 6, 2013
1,201
Thanks a million! I remembered seeing that a week or so ago, but could not seem to search the right combination to find it. Was searching mcp2221.

I was hoping the kernel module would work just to make life easier, but in the end it won't matter. I managed to get the read reports to work before I crashed for the day, but having issues with the write. I'll study up on the other post after work in the morning.
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,079
My next MCP2210 USB to SPI board. This one has the BMX160 9-axis IMU chip for motion control and detection with the 24vdc I/O sections from the previous board.
PXL_20211215_043142735.jpgPXL_20211215_013556116.jpgPXL_20211215_013504110.jpgPXL_20211214_220749146.jpg
The IMU is tiny and is a little tricky to solder. Solder blob the pads and use lots of flux with a hot air-gun.

Linux hidapi raw interface detection and BMX160 configuration output.
--- Driver Version V0.7 Dec 16 2021 09:05:49 ---
Open Device: 04d8:00de
Manufacturer String: Microchip Technology Inc.
Product String: MCP2210 USB to SPI Master
Serial Number String: (48) 0001312319
MCP2210 init complete
BMX160 IMU detected, Chip ID D8, Chip Power Status 00.
BMX160 IMU Sensors Not Ready.
BMX160 IMU Sensors Configuration Started. Chip Power Status: 00.
BMX160 IMU Sensors Configuration Complete. Chip Status: 15.
BMX160 Interface set to SPI in NVRAM.
BMX160 IMU: M -50.100 108.300 -60.300, G -0.038 0.008 -0.076, A -0.124 0.895 10.235
The last line is the processed mag, gryo and accel sensor data from the MCP2210 raw BMX160 SPI data.

C:
    /*
     * BMX160 IMU in SPI mode 3 @ 3MHz SCK testing
     */
    setup_bmx160_transfer(2); // 2 byte transfer, address and one data register
    bmx160_get(2, BMX160_REG_DUMMY); // toggle CS to set bmx160 SPI mode
    // check for bmx160 boot status and configure if needed
    if ((imu_id = bmx160_get(2, BMX160_ID_REG)) == BMX160_ID) {
        imu_status = bmx160_get(2, BMX160_PS_REG); // read power status
        printf("BMX160 IMU detected, Chip ID %02hhX, Chip Power Status %02hhX.\n", imu_id, imu_status);
        if (imu_status == BMX160_ALL_PM_NORMAL) {
            printf("BMX160 IMU Sensors All Operational.\n");
        } else {
            printf("BMX160 IMU Sensors Not Ready.\n");
            printf("BMX160 IMU Sensors Configuration Started. Chip Power Status: %02hhX.\n", imu_status);
            bmx160_set(BMX160_CMD_ACCEL_PM_NORMAL, BMX160_REG_CMD);
            sleep_us(us_5ms);
            bmx160_set(BMX160_CMD_GYRO_PM_NORMAL, BMX160_REG_CMD);
            sleep_us(us_100ms);
            bmx160_set(BMX160_CMD_MAG_PM_NORMAL, BMX160_REG_CMD);
            sleep_us(us_2ms);
            // BMX160 2.4.3.1.3 magnetometer Configuration Example
            bmx160_set(0x80, 0x4C); // mag_if0, setup mode
            bmx160_set(0x01, 0x4F); // mag_if3, indirect write
            bmx160_set(0x4B, 0x4E); // mag_if2, sleep mode
            bmx160_set(0x17, 0x4F); // mag_if3, indirect write
            bmx160_set(0x51, 0x4E); // mag_if2, high accuracy preset XY
            bmx160_set(0x52, 0x4F); // mag_if3, indirect write
            bmx160_set(0x52, 0x4E); // mag_if2, high accuracy preset Z
            bmx160_set(0x02, 0x4F); // mag_if3, data set
            bmx160_set(0x4C, 0x4E); // mag_if2, data set
            bmx160_set(0x42, 0x4D); // mag_if1, data set
            bmx160_set(0x05, 0x44); // 12.5Hz pool rate
            bmx160_set(0x00, 0x4C); // mag_if0, data mode
            bmx160_set(BMX160_CMD_MAG_PM_NORMAL, BMX160_REG_CMD);
            sleep_us(us_2ms);
            imu_status = bmx160_get(2, BMX160_PS_REG); // read power status
            printf("BMX160 IMU Sensors Configuration Complete. Chip Status: %02hhX.\n", imu_status);
            if (bmx160_get(2, BMX160_NV_CONF) != BMX160_SPI_SET) {
                bmx160_set(BMX160_SPI_SET, BMX160_NV_CONF);
                bmx160_set(BMX160_SPI_4WIRE, BMX160_IF_CONF);
                printf("BMX160 Interface set to SPI in NVRAM.\n");
            }
        }
    } else {
        printf("BMX160 IMU NOT detected, Bad Chip ID %02hhX.\n", imu_id);
    }

    if ((imu_id == BMX160_ID) && (imu_status == BMX160_ALL_PM_NORMAL)) {
        setup_bmx160_transfer(24); // 24 byte transfer, address and 23 data registers
        do {
            sleep_us(us_5ms);
            bmx160_get(24, BMX160_DATA_REG);
            //            show_bmx160_transfer();
            getAllData(&magn, &gyro, &accel);
            printf("\rBMX160 IMU: M %7.3f %7.3f %7.3f, G %7.3f %7.3f %7.3f, A %7.3f %7.3f %7.3f   \r", magn.x, magn.y, magn.z, gyro.x, gyro.y, gyro.z, accel.x, accel.y, accel.z);
        } while (true);
    }
Driver code fragment for testing the board and BMX160.
https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmx160-ds0001.pdf
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Nice code! You use the same style as me, for sure. I fail to see anything in the datasheet about bmx160_set() and bmx160_get(), or any code for that matter. Where are these implemented? I'm curious.

I didn't have the opportunity to experiment with this, since the support around the now defunct CP2130 (say, applications and libraries for Linux) is still taking most of my free time. My plan is to first "expand" the libusb library in order to add functions that are specific to HID devices. I don't wish to use hidapi (the libusb variant), but instead use libusb directly. Less one library that I have to install and fiddle with, and as a plus I can reattach the kernel driver when I'm done. It will cost me next to nothing, because it will be just a tiny C file containing two functions: spi_read() and spi_write().
 

nsaspook

Joined Aug 27, 2009
13,079
bmx160_set() and bmx160_get() are just BMX160 specific SPI I/O functions.
C:
/*
 * read SPI data from BMX160 register
 */
uint8_t bmx160_get(uint8_t nbytes, uint8_t addr)
{
    cbufs();
    // BMX160 config
    S->buf[0] = 0x42; // transfer SPI data command
    S->buf[1] = nbytes; // no. of SPI bytes to transfer
    S->buf[4] = addr | BMX160_R; //device address, read
    S->buf[5] = 0x00;
    S->buf[6] = 0x00;
    S->res = SendUSBCmd(S->handle, S->buf, S->rbuf);
    while (S->rbuf[3] == SPI_STATUS_STARTED_NO_DATA_TO_RECEIVE || S->rbuf[3] == SPI_STATUS_SUCCESSFUL) {
        S->res = SendUSBCmd(S->handle, S->buf, S->rbuf);
    }
    return S->rbuf[5];
}

/*
 * write SPI data to BMX160 register
 */
uint8_t bmx160_set(uint8_t set_data, uint8_t addr)
{
    cbufs();
    // BMX160 config
    S->buf[0] = 0x42; // transfer SPI data command
    S->buf[1] = 2; // no. of SPI bytes to transfer
    S->buf[4] = addr | BMX160_W; //device address, write
    S->buf[5] = set_data;
    S->buf[6] = 0x00;
    S->res = SendUSBCmd(S->handle, S->buf, S->rbuf);
    while (S->rbuf[3] == SPI_STATUS_STARTED_NO_DATA_TO_RECEIVE || S->rbuf[3] == SPI_STATUS_SUCCESSFUL) {
        S->res = SendUSBCmd(S->handle, S->buf, S->rbuf);
    }
    return S->rbuf[5];
}
C:
/*
 * send and receive USB data 
 * using the asynchronous hid device mode for this driver
 */
int32_t SendUSBCmd(hid_device *handle, uint8_t *cmdBuf, uint8_t *responseBuf)
{
    int32_t r;

    r = hid_write(handle, cmdBuf, COMMAND_BUFFER_LENGTH);
    if (r < 0) {
        return ERROR_UNABLE_TO_WRITE_TO_DEVICE;
    }

    //when the hid device is configured as synchronous, the first
    //hid_read returns the desired results. and the while() loop
    //is skipped.
    //
    //when the hid device is configured as asynchronous, the first
    //hid_read may or may not succeed, depending on the latency
    //of the attached device. When no data is returned, r = 0 and
    //the while loop keeps polling the returned data until it is
    //received.
    r = hid_read(handle, responseBuf, RESPONSE_BUFFER_LENGTH);
    if (r < 0) {
        return ERROR_UNABLE_TO_READ_FROM_DEVICE;
    }

    while (r == 0) {
        r = hid_read(handle, responseBuf, RESPONSE_BUFFER_LENGTH);
        if (r < 0) {
            return ERROR_UNABLE_TO_READ_FROM_DEVICE;
        }
        sleep_us(10);
    }

    return responseBuf[1];
}
I don't have a pre-existing body of code using libusb so hidapi works for me. My only real issue so far with the MCP2210 was making gpio 8 work correctly in CS mode. I just used gpio 0 instead for the IMU chip select.
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Ok, I see. SendUSBCmd() seems to be a write followed by a read. And bmx160_get() / bmx160_set() are specific uses of the previous function. At first, I thought there were too many layers, but I see a possible reason.

As for using libusb, you don't need to have much of a pre-existing code base. Much of it is implemented by the library. hid-write() and hid_read() basically use control transfers and interrupt transfers, IIRC. How the data is sent is the secret sauce.
 

nsaspook

Joined Aug 27, 2009
13,079
A quick demo of the BMX160 IMU connected to the USB port using the MCP2210. This is just raw unfiltered data but it shows how easy a motion detection system could be built.

The MCP2210/BMX160 C driver opens a named pipe to send IMU data.
C:
    int pipefd;
    char * myfifo = "/tmp/myfifo";
// ...
    /* remove the old FIFO */
    unlink(myfifo);
    /* create the FIFO (named pipe) */
    mkfifo(myfifo, 0777);

    /* Open the pipe. Just like you open a file */
    pipefd = open(myfifo, O_RDWR);

// ...
    if ((imu_id == BMX160_ID) && (imu_status == BMX160_ALL_PM_NORMAL)) {
        setup_bmx160_transfer(24); // 24 byte transfer, address and 23 data registers
        do {
            sleep_us(us_10ms);
            bmx160_get(24, BMX160_DATA_REG);
            //            show_bmx160_transfer();
            getAllData(&magn, &gyro, &accel);
            printf("\rBMX160 IMU: M %7.3f %7.3f %7.3f, G %7.3f %7.3f %7.3f, A %7.3f %7.3f %7.3f   \r", magn.x, magn.y, magn.z, gyro.x, gyro.y, gyro.z, accel.x, accel.y, accel.z);
            /* Write to the pipe */
            sprintf(fifo_buf, "%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f\n", magn.x, magn.y, magn.z, gyro.x, gyro.y, gyro.z, accel.x, accel.y, accel.z);
            write(pipefd, fifo_buf, sizeof(fifo_buf));
        } while (true);
    }
A separate Java program opens the named pipe as a fifo, reads and parses the sensor data, does a few quick display calculations and then displays that data as motions on a cube.
Java:
// ...
        // Connect to the named pipe
        File myfifo = new File("/tmp/myfifo");
        try {
            Scanner myReader = new Scanner(myfifo);
            while (myReader.hasNextLine()) {
                String line = myReader.nextLine();
                String[] token = line.split(",");
                double w = Double.parseDouble(token[6]);
                double x = -Double.parseDouble(token[0]);
                double y = -Double.parseDouble(token[1]);
                double z = -Double.parseDouble(token[2]);
                double ax = Double.parseDouble(token[3]);
                double ay = Double.parseDouble(token[4]);
                double az = Double.parseDouble(token[5]);
                System.out.println(String.format("w = %+2.3f     x = %+2.3f     y = %+2.3f     z = %+2.3f", w, x, y, z));
                Quat4d quaternion = new Quat4d(w, x, y, z);
                Vector3d vector = new Vector3d((az * 0.02), (ay * 0.02), (az * 0.02));
                transformGroup.setTransform(new Transform3D(quaternion, vector, 1.0));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
 

geekoftheweek

Joined Oct 6, 2013
1,201
In case you all venture down the MCP2221 path one day I did find this library does after all work https://github.com/zkemble/libmcp2221.

I tried using the binaries on the page and wound up with segmentation faults... then tried to recompile with no luck and gave up for a bit... then after going cross eyed from all the reading, searching, and getting my own partially working I noticed the instructions to recompile it. The I2C part is kind of unfinished in the library., but after staring at it long enough for ideas it started to make sense. I'll stick to my own creation just because I made it.
 

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Well, I will not use or rely on external libraries except for the proven and necessary libusb. This way, I reduce the number of needed dependencies. I can even do without hidapi.
 

nsaspook

Joined Aug 27, 2009
13,079
Updated the demo code for the new board. Getting about a 14ms complete I/O loop using the mcp2210 USB connection for a IMU update, 12 pin input and 24 pin output.

Simple IMU (magnetic sensor Z) control of the LEDs on the board.
C:
    if ((imu_id == BMX160_ID) && (imu_status == BMX160_ALL_PM_NORMAL)) {
        do {
            sleep_us(fspeed);
            setup_bmx160_transfer(24); // 24 byte transfer, address and 23 data registers
            bmx160_get(24, BMX160_DATA_REG);
            getAllData(&magn, &gyro, &accel);
            printf("\rBMX160 IMU: M %7.3f %7.3f %7.3f, G %7.3f %7.3f %7.3f, A %7.3f %7.3f %7.3f  %02hhX  \r", magn.x, magn.y, magn.z, gyro.x, gyro.y, gyro.z, accel.x, accel.y, accel.z, data_status);
            /* Write to the pipe */
            snprintf(fifo_buf, 255, "%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f,%7.3f\n", magn.x, magn.y, magn.z, gyro.x, gyro.y, gyro.z, accel.x, accel.y, accel.z);
            write(pipefd, fifo_buf, sizeof(fifo_buf));
            /*
             * handle the MC33966 chip MCP2210 SPI setting
             */
            setup_mc33996_transfer(3);
            /*
             * send data to the output ports
             */
            mc33996_set(mc33996_control, led_pattern[k & 0x0f], 0);
            /*
             * check for change in MCP2210 interrupt counter
             */
            if (get_MCP2210_ext_interrupt()) {
                /*
                 * handle the TIC12400 chip MCP2210 SPI setting
                 */
                setup_tic12400_transfer(); // CS 5 and mode 1
                /*
                 * read 24 switch inputs
                 */
                tic12400_read_sw(0, 0);
                /*
                 * look for switch 0 changes for led speeds
                 */
                do_switch_state();
                printf("tic12400_init value %X , status %X \n", tic12400_value, tic12400_status);
            } else {
                fspeed = abs((int32_t)(magn.z*1000.0));
            }
            k++;
        } while (true);
    }
Board: https://github.com/nsaspook/mcp2210/blob/main/mcp2210_sch.pdf https://github.com/nsaspook/mcp2210/blob/main/mcp2210_brd.pdf
Code: https://github.com/nsaspook/mcp2210/tree/main/linux_test/tictest
 
Last edited:

Thread Starter

bloguetronica

Joined Apr 27, 2007
1,541
Nice! An interval of 14ms for each transaction is acceptable, but I'm guessing that the MCP2210 is a tad slow. The CP2130 was better in that respect. That slowness might be problematic in my use case, if I am going to use it to replace the CP2130 in one of my USB test switches. I will need to take five measurements from an LTC2312CTS8-12 ADC, and the total of all those transactions, plus reading 5 GPIOs, must be well within an interval of 50ms!

By the way, what bitrate are you using?
 
Top