The Attack Of The Warriors, Part 2: IO-Warrior24 - 16 Bit Multipurpose I/O Ports

Body

The code discussed in this article can be found here.

The IO-Warrior24 device from Code Mercenaries is equipped with 16 general purpose I/O (input/output) pins. When enabling the so-called special mode functions, more or less pins are reassigned to serve a special purpose. You can select between:

  • I2C, IIC: a two-wire serial Inter-IC-Bus allowing connection to RAMs, EEPROMs, ADCs,DACs and a lot more.
  • LCD: parallel communication with alphanumerics Liquid-Christal-Display units
  • SPI: a four-wire serial Serial-Peripheral-Interface-Bus similar to I2C
  • LED-Matrix: a serial communication bus to control LEDs in a multiplexed matrix configuration
  • RC5IR: Infrared Remote control according to the RC5 code

We'll now examine this impressive manifold of functions step-by-step. To make life a little bit easier, Code Mercenaries is offering an "IO-Warrior24 Starter Kit" providing a LED controlled by an I/O-pin, an IR receiver, and the circuit and a 16-pin connector to directly connect a LCD module. A wrap-field can be found, too - you can add your own electronic components. Below you find the assembled starter kit and wiring diagram.

Figure 1: Starter KitFigure 1: Starter Kit

Figure 2: IO-Warrior24 Wiring DiagramFigure 2: IO-Warrior24 Wiring Diagram

If everything is prepared, then connect your warrior to the USB hub and launch USBCommander. Starting with release V1.0.1.0 all IO-Warrior chips do have unique serial numbers. If multiple IO-Warriors are connected to your hub you can easily distinguish them.

Figure 3: USB Device InfoFigure 3: USB Device Info

"USB Device Info" is delivering the following information. A single configuration is present, but -- in contrast to the simple JoyWarrior -- the IO-Warrior uses two interfaces: interface 0 and interface 1. Endpoint 0 from the first interface uses two bytes for interrupt-transfer. Data is always written via a two byte control-transfer to this endpoint. All special-mode functions are managed by endpoint 0 of interface 1, but now eight bytes of data are used for control- and interrupt-transfer. Control transfer is done via SetReport requests which can be distinguished by different ReportIDs.

Writing Data to Port 0 and Port 1

Configuration 0, interface 0, and endpoint 0 is the minimal configuration for all USB devices. BeOS doesn't need the ReportID to perform a control transfer concerning this configuration. The MaxPacketSize is two bytes representing the two ports. You are able to send a report every 10 ms (i.e., Interval = 10).

Control Transfer - Interface 0
ReportID = 0x00Write Data To Port 0,1
byte01
reportPort 0Port 1
Figure 4: Control Transfer Structure

Sending data to the both ports is very easy. You create a two-byte report-field and set the individual bits to high or low level for the appropriate pins.

USBDevice dev("/dev/bus/usb/0/1/3");	// USB device
uchar report[2];

report[0] = ~(0x08); // switch on the red LED1 -> Port 0, Bit 3, on-> force pin to ground
report[1] = 0xFF; // Port 1 all pins high

ssize_t size = 0;
size = dev.ControlTransfer( USB_REQTYPE_CLASS |USB_REQTYPE_INTERFACE_OUT, // request type
USB_REQUEST_SET_CONFIGURATION, // request value
0, // value
0, // index : Interface 0 (IF0)
sizeof(report), // length: 2 bytes->IF0
report); // report

printf("size written = %d
",(int)size);

The greatest problem is to find the correct parameters concerning the control transfer - USBCommander and the header file USB_spec.h are very helpful to perform this task. Don't forget to check the returned number of bytes: It must fit the transmitted report size, otherwise something went wrong.

Reading Data from Port 0 and Port 1

This is arranged by a two-byte interrupt transfer:

Interrupt Transfer - Interface 0
ReportID = 0x00Read Data From Port 0,1
byte01
replyPort 0Port 1
Figure 5: Interrupt Transfer Structure


uchar reply[2];
const USBEndpoint *ept = dev.ActiveConfiguration()->InterfaceAt(0)->EndpointAt(0);
size = ept -> InterruptTransfer(reply,sizeof(reply)); // last pin status
size = ept -> InterruptTransfer(reply,sizeof(reply)); // actual pin status
if (size == sizeof(reply))
printf("Port 0, Port 1: 0x%02X 0x%02X
", reply[0], reply[1]);
else
printf("Wrong size read = %d
",(int)size);

The terminal output from the above looks like:

Port 0, Port 1:  0xF7 0xFF  (1111 0111   1111 1111)

The small code fragment shows how to read the status of the red LED1 we switched on in the last example. First we must get access to the endpoint in order to perform an interrupt transfer. This transfer must be called twice--this seems to be a peculiarity concerning the interrupt handling of the Warrior device--an input pin status change is needed, and with Be's USB Kit, the last status is held. Normally you will not be affected by this issue when you have a thread periodically reading the status of the device.

It's possible to avoid this (mis)behavior when setting a "Special Mode Function" to get the current pin status - please have a look at chapter 5.10.4 of the data sheet.

Control Transfer - Interface 1
ReportID = 0xFFGet Current Pin Status
byte01234567
reportReportID0x000x000x000x000x000x000x00
Figure 6: Control Transfer Structure

After sending the special report to Interface 1, the data is delivered by an eight-byte interrupt transfer.

Interrupt Transfer - Interface 1
ReportID = 0xFFStatus Of Ports P0,...,P3
byte01234567
reportReportIDPort 0Port 1Port 2Port 30x000x000x00
Figure 7: Interrupt Transfer Structure

uchar report2[8] = {0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
size = dev.ControlTransfer(USB_REQTYPE_CLASS |USB_REQTYPE_INTERFACE_OUT, // request type
USB_REQUEST_SET_CONFIGURATION, // request value
0, // value
1, // index : Interface 1 (IF1)
sizeof(report2), // length: 8 bytes->IF1
report2); // report2
printf("size = %d
",(int)size);
uchar reply[8];
const USBEndpoint *ept = dev.ActiveConfiguration()->InterfaceAt(1)->EndpointAt(0);
if ( (ept ->InterruptTransfer(reply,8) ) == 8)
printf(" ReportID, Port0...Port3, ...: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X",
reply[0],reply[1],reply[2],reply[3],reply[4],reply[5],reply[6],reply[7]);

// avoid odd/even problem of USB bus manager
dev.ControlTransfer(USB_REQTYPE_CLASS |USB_REQTYPE_INTERFACE_OUT,
USB_REQUEST_SET_CONFIGURATION, 0, 1, sizeof(report2), report2);

ept->InterruptTransfer(reply,8);

The terminal output from that would be:

ReportID, Port0...Port3, ...:  0xFF  0xF7  0xFF  0x00  0x00  0x00  0x00 0x00

Again we are trying to get the status of the red LED1. The transfer is done by Interface 1 - keep in mind we are using a special mode. Byte number 1 and 2 of the reply-field are containing the expected result. Because the BeOS USB bus manager is suffering from the famous odd/even problem described by ITO, Takayuki we had to repeat the action twice to simulate an even number of accesses. Otherwise the program will freeze and you have to reboot. Zeta's USB stack is properly working, so you can remove the extra lines of code in your Zeta app.

After removing all pitfalls it's time to introduce IOW24_IO16, an application equipped with a nice GUI constructed with InterfaceElements.

Figure 8: IOW24_IO16 InputFigure 8: IOW24_IO16 Input Figure 9: IOW24_IO16 OutputFigure 9: IOW24_IO16 Output

Launch the application and type in the "VendorID", "ProductID" and the unique "Serial Number". Press the button "Search USB Device", and if the dedicated device is connected, the related "Product" string (see USBCommander's USB Device Info) will appear. Normally all port pins are high, but with the help of sixteen checkboxes the pins can be set individually to low level. If LEDs are connected, they will be switched on. Without additional hardware -- I added eight LEDs to Port 1 -- you can only watch our famous red LED1, Port 0, bit 3.

Now press "Start Reading" - both ports will periodically be scanned and the results will be displayed in the "Read Port" boxes. The most interesting part, however, is to stimulate the pins externally. Please press the "Output" button - the device will be switched to input mode, where all ports are set to high level and can then be pulled down to ground to indicate low level. The left image is showing the input mode and bit 7 and 6 of port 1 forced externally to ground.

The "Read Immediate" checkbox is reserved for the special mode "Get Current Pin Status". You will find out that reading is now instantaneous and the small delay of the normal reading mode has vanished. What about hot-plugging? No problem, as long as the "Read Immediate" mode isn't used. Next time we'll talk about the other special modes - stay tuned.