How to Program the MAX14906 Quad-Channel Industrial Digital Output, Digital Input

Abstract

The MAX14906 an IEC 61131-2 compliant, high-speed, four-channel industrial digital output, digital input device that can be configured on a per-channel basis as a high-side (HS) switch, a push-pull (PP) driver, or a Type 1 and 3, or Type 2 digital input. A microcontroller-compatible serial peripheral interface (SPI) provides access to many diagnostic features. This application note provides example C-code implementation including setup, monitoring, and diagnostic functions.

Introduction

The MAX14906 is an IEC 61131-2 compliant, high-speed, four-channel industrial digital output, digital input device that can be configured on a per-channel basis as a high-side (HS) switch, a push-pull (PP) driver, or a Type 1 and 3, or Type 2 digital input. The SPI interface has a built-in chip addressing decoder, which allows communication with multiple MAX14906 devices utilizing a shared SPI with a common chip select (CS). The SPI interface provides flexibility for global and per-channel configuration and diagnostics, including supply overvoltage and undervoltage detection, wire-break or open-wire detection, thermal overload, and current limit reporting, and more.

This application note presents a series of functions to provide an easy and proven solution to program the MAX14906 (Figure 1). They are written in C# and should prove easy to port over to any common microcontroller. For detailed information about MAX14906 pins, operating modes, SPI commands and control registers, refer to the MAX14906 data sheet.

MAX14906 Typical Application Circuit

Figure 1. MAX14906 Typical Application Circuit.

MAX14906 SPI

The MAX14906 has a high-speed SPI serial interface with maximum clock rate of 10MHz. The SPI interface adheres with clock polarity CPOL = 0 (SCLK idle = 0) and clock phase CPHA = 0 (rising/first edge samples the data). The commands are clocked in most significant bit (MSb) first.

The MAX14906 SPI supports addressable SPI which, allows direct communication with up to four MAX14906s using a shared CS signal. The address pins A1 and A0 are used to configure the device address. The address bits A1 and A0 are sent as the first and second bits of the SPI read and write command. The device monitors the SPI command and responds with SDO appropriately when the address matches the status of the A1 and A0 pins.

The MAX14906 SPI supports single-cycle mode and burst mode. The single-cycle mode reads or writes one register at a time, while burst mode allows reading or writing multiple consecutive registers in one SPI cycle.

The single-cycle SPI commands are 16 bits long (8-bit instruction + 8-bit data), with CRC disabled, and if CRC is enabled, this adds 8 more bits including 5-bit CRC and 3 leading zeros. The 2 MSBs of the command byte are device address bits (A1 and A0), which allow 4 MAX14906s to share the same chip select (CS) pin. The BRST bit is set to 0 for single-cycle mode. Data sent by the device on SDO reports the device fault conditions, as well as per-channel status if it is a write command or register value if it is a read command. Single-cycle read and write diagrams are shown from Figure 2 to Figure 5.

SPI Single-Cycle Write Command, CRC Disabled

Figure 2. SPI Single-Cycle Write Command, CRC Disabled.

SPI Single-Cycle Write Command, CRC Enabled

Figure 3. SPI Single-Cycle Write Command, CRC Enabled.

SPI Single-Cycle Read Command, CRC Disabled

Figure 4. SPI Single-Cycle Read Command, CRC Disabled.

SPI Single-Cycle Read Command, CRC Enabled

Figure 5. SPI Single-Cycle Read Command, CRC Enabled.

The burst SPI command uses one SPI cycle and one register address to write to or read from multiple consecutive registers and is enabled by setting the BRST bit to 1.

The burst write command writes to both SetOUT and SetLED registers with BRST = 1, R/W = 1, and R[3:0] = 0. The command contains two data bytes, one to configure the SetOUT register and the other to configure the SetLED register. The number of SCLK cycles is 24 if CRC is not enabled and is 32 if CRC is enabled. SPI burst write diagrams are shown in Figure 6 and Figure 7.

The burst read command retrieves the data from six consecutive diagnostic registers from the address 0x02 to 0x07 (DoiLevel, Interrupt, OvrLdChF, OpnWirChF, ShtVDDChF, and GlobalErr registers) with BRST = 1, R/W = 0, R[3:0] = 2. The number of SCLK cycles is 56 if CRC is not enabled and is 64 if CRC is enabled. During a burst read, data bits 9 to 59 in the SDI data stream can be 0 or 1, but these bits are used to calculate the CRC bits if CRC is enabled. SPI burst read diagrams are shown in Figure 8 and Figure 9.

SPI Burst Write Command, CRC Disabled

Figure 6. SPI Burst Write Command, CRC Disabled.

SPI Burst Write Command, CRC Enabled

Figure 7. SPI Burst Write Command, CRC Enabled.

SPI Burst Read Command, CRC Disabled

Figure 8. SPI Burst Read Command, CRC Disabled.

SPI Burst Read Command, CRC Enabled

Figure 9. SPI Burst Read Command, CRC Enabled.

For more details of SPI commands with the register tables and instructions, refer to the MAX14906 data sheet.

MAX14906 - Code Application Examples

The MAX14906 is designed to support industrial applications in end equipment such as programmable logic controllers (PLCs) that require configurable digital input (Type 1/3 or Type 2) or digital output (high-side switch or push-pull driver). A typical application circuit that supports 4 channels group isolated, using a single MAX14483 digital isolator is shown in Figure 1.

Source Code

This application note provides C# source code examples, essentially providing driver functions to access the multiple registers within the MAX14906 for configuration, control, and diagnostic features. All software has been implemented and tested using MAX14906 EV kit. The functions in this application note are used in the MAX14906 EV kit software to handle basic MAX14906 SPI read and write commands. It is designed to work with FT2232 microcontroller on the MAX14906 EV kit hardware. The USB communication and low-level FT2232 functions are not included in this document. The graphic interface functions are also not included in this document.

Customers should use the functions in this document as reference only and design their own firmware/software based on their own microcontroller and hardware implementation in their application.

Single-Cycle Write

            public void WriteRegister(int index)
        {
            if (index < 0 || index >= registers.Count) { return; }

            byte register = 0x00;
            byte[] txBuffer = new byte[2];
            byte[] rxBuffer = new byte[2];

            // Register Byte
            register = 0x01;// Write
            register |= (byte)(index << 1);// Register Address
            register |= (byte)(Address << 6);// Chip Address
            txBuffer[0] = register;
            // Data Byte
            txBuffer[1] = registers[index].Value;
            SPIWriteRead(txBuffer, ref rxBuffer, 2);

            // Store Fault Data
            SDOFaults.SHTVDD = ((rxBuffer[0] & 0x20) >> 5);
            SDOFaults.AbvVDD = ((rxBuffer[0] & 0x10) >> 4);
            SDOFaults.OWOffF = ((rxBuffer[0] & 0x08) >> 3);
            SDOFaults.OvrCurr = ((rxBuffer[0] & 0x04) >> 2);
            SDOFaults.OvldF = ((rxBuffer[0] & 0x02) >> 1);
            SDOFaults.GLOBLF = (rxBuffer[0] & 0x01);
            SDOFaults.ChannelFaults = rxBuffer[1];

            // If successful, clean up
            registers[index].Modified = false;
        }

Single-Cycle Read

        public void ReadRegister(int index)
        {
            if (index < 0 || index >= registers.Count) { return; }

            FTDI.FT_STATUS ftStatus;
            byte register = 0x00;
            byte[] txBuffer = new byte[2];
            byte[] rxBuffer = new byte[2];

            // Register Byte
            register = 0x00;// Read
            register |= (byte)(index << 1);// Register Address
            register |= (byte)(Address << 6);// Chip Address
            txBuffer[0] = register;
            // Data Byte
            txBuffer[1] = 0x00;
            ftStatus = SPIWriteRead(txBuffer, ref rxBuffer, 2);
            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                registers[index].Value = rxBuffer[1];
            }

            // Store Fault Data
            SDOFaults.SHTVDD = ((rxBuffer[0] & 0x20) >> 5);
            SDOFaults.AbvVDD = ((rxBuffer[0] & 0x10) >> 4);
            SDOFaults.OWOffF = ((rxBuffer[0] & 0x08) >> 3);
            SDOFaults.OvrCurr = ((rxBuffer[0] & 0x04) >> 2);
            SDOFaults.OvldF = ((rxBuffer[0] & 0x02) >> 1);
            SDOFaults.GLOBLF = (rxBuffer[0] & 0x01);
            //SDOFaults.ChannelFaults = rxBuffer[1];// Not Valid, bc on read this is register data
            
            // If successful, clean up
            registers[index].Modified = false;
        }

Burst Write

        public void BurstWriteRegisters(int startIndex, int endIndex)
        {
            // Typically always 0 and 1 only
            if (endIndex < startIndex) { return; }
            if (startIndex < 0) { return; }
            if (endIndex >= registers.Count) { return; }

            int byteCount = (endIndex - startIndex) + 2;
            byte register = 0x00;
            byte[] txBuffer = new byte[byteCount];
            byte[] rxBuffer = new byte[byteCount];

            // Register Byte
            register = 0x01;// Write
            register |= (byte)(startIndex << 1);// Register Address
            register |= (byte)(Address << 6);// Chip Address
            register |= (byte)(0x01 << 5);// Burst
            txBuffer[0] = register;

            // Data Byte
            for (int x = 0; x < (byteCount - 1); x++)
            {
                txBuffer[1 + x] = registers[startIndex + x].Value;
                registers[startIndex + x].Modified = false;
            }
            SPIWriteRead(txBuffer, ref rxBuffer, byteCount);

            // Store Fault Data
            SDOFaults.SHTVDD = ((rxBuffer[0] & 0x20) >> 5);
            SDOFaults.AbvVDD = ((rxBuffer[0] & 0x10) >> 4);
            SDOFaults.OWOffF = ((rxBuffer[0] & 0x08) >> 3);
            SDOFaults.OvrCurr = ((rxBuffer[0] & 0x04) >> 2);
            SDOFaults.OvldF = ((rxBuffer[0] & 0x02) >> 1);
            SDOFaults.GLOBLF = (rxBuffer[0] & 0x01);
            SDOFaults.ChannelFaults = rxBuffer[1];            
        }

Burst Read

        public void BurstReadRegisters(int startIndex, int endIndex)
        {
            if (endIndex < startIndex) { return; }
            if (startIndex < 0) { return; }
            if (endIndex >= registers.Count) { return; }

            FTDI.FT_STATUS ftStatus;
            int byteCount = (endIndex - startIndex) + 2;
            byte register = 0x00;
            byte[] txBuffer = new byte[byteCount];
            byte[] rxBuffer = new byte[byteCount];

            // Register Byte
            register = 0x00;// Read
            register |= (byte)(startIndex << 1);// Register Address
            register |= (byte)(Address << 6);// Chip Address
            register |= (byte)(0x01 << 5);// Burst
            txBuffer[0] = register;

            // Data Byte
            for (int x = 0; x < (byteCount - 1); x++)
            {
                txBuffer[1 + x] = 0x00;
            }
            ftStatus = SPIWriteRead(txBuffer, ref rxBuffer, byteCount);

            if (ftStatus == FTDI.FT_STATUS.FT_OK)
            {
                for (int x = 0; x < (byteCount - 1); x++)
                {
                    registers[startIndex + x].Value = rxBuffer[1 + x];
                    registers[startIndex + x].Modified = false;
                }
            }

            // Store Fault Data
            SDOFaults.SHTVDD = ((rxBuffer[0] & 0x20) >> 5);
            SDOFaults.AbvVDD = ((rxBuffer[0] & 0x10) >> 4);
            SDOFaults.OWOffF = ((rxBuffer[0] & 0x08) >> 3);
            SDOFaults.OvrCurr = ((rxBuffer[0] & 0x04) >> 2);
            SDOFaults.OvldF = ((rxBuffer[0] & 0x02) >> 1);
            SDOFaults.GLOBLF = (rxBuffer[0] & 0x01);
        }

CRC Calculation

    private CRC8 crc8 = new CRC8(0x15, 0x1F);

    class CRC8
    {
        private readonly byte POLY = 0x00;
        private readonly byte START_VALUE = 0x00;

        public CRC8(byte poly, byte startValue = 0x00)
        {
            this.POLY = poly;
            this.START_VALUE = startValue;
        }

        public byte ComputeChecksum3MSB(byte[] bytes)
        {
            byte remainder = START_VALUE;
            byte min = 0;
            for (int bite = 0; bite < bytes.Length; ++bite)
            {
                // For MAX14906 it does 8/16bits plus the 3 "0" MSBs of check byte
                if (bite == (bytes.Length - 1)) { min = 5; }

                for (byte bit = 8; bit > min; --bit)
                {
                    remainder = (((bytes[bite] >> (bit - 1) & 0x01) ^ ((remainder >> 4) & 0x01)) > 0) ? (byte)((remainder << 1) ^ POLY) : (byte)(remainder << 1);
                }
            }
            return (byte)(remainder & 0x1F);
        }
    }

Example to Configure Operation Mode for Each Channel

        public void SendModeAndSetting(int channel, int mode, int setting)
        {
            if (channel < 1 || channel > 4) { return; }
            if (mode < 0 || mode > 2) { return; }
            if (((mode == 1) && (setting < 0 || setting > 4)) || ((mode == 0) && (setting < 0 || setting > 2)) || (mode == 2 && setting != 0)) { return; }
            
            byte register = 0x00;
            byte data = 0x00;
            byte[] txBuffer = new byte[2];
            byte[] rxBuffer = new byte[2];

            if (mode == 0 || mode == 2)// Input or Low-leakage
            {
                ////////////////////////////
                // Reg 0x00:
                ///////////////////////////
                // Register Byte
                register = 0x01;// Write
                register |= (0x00 << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Data Byte
                // Set current bit mode from FTDI
                byte temp = 0x00;
                // Remember bit logic for FTDI input/output inverted from MAX14906 Reg 0x00 
                temp = (byte)((~FTDI_ADBUS_MODE & 0x80) >> 7);// DIO1
                data |= (byte)(temp << 4);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x01));// DIO2
                data |= (byte)(temp << 5);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x02) >> 1);// DIO3
                data |= (byte)(temp << 6);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x04) >> 2);// DIO4
                data |= (byte)(temp << 7);
                // New Mode to Set
                temp = (byte)(0x01 << (channel + 3));
                data = (mode == 0 || mode == 2) ? (byte)(temp | data) : (byte)(~temp & data);
                txBuffer[1] = data;
                SPIWriteRead(txBuffer, ref rxBuffer, 2);

                ////////////////////////////
                // Reg 0x0C:
                ///////////////////////////
                // Register Byte
                register = 0x00;// Read
                register |= (0x0C << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Get Current Data from Reg 0x0C
                SPIWriteRead(txBuffer, ref rxBuffer, 2);
                temp = rxBuffer[1];

                // Register Byte
                register = 0x01;// Write
                register |= (0x0C << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Data Byte
                data = (setting == 1) ? (byte)(temp | 0x80) : (byte)(temp & ~0x80);// Type 2 IEC Mode, Note this forces all DI to be in this mode
                txBuffer[1] = data;
                SPIWriteRead(txBuffer, ref rxBuffer, 2);

                ////////////////////////////
                // Reg 0x0D:
                ///////////////////////////
                // Register Byte
                register = 0x01;// Write
                register |= (0x0D << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Data Byte
                data = (byte)(DO_Settings & ~(0x03 << (channel - 1) * 2));// Whether any setting (Normal, IEC 2, or Low-leakage) zero out both bits
                if (mode == 2)// Low-leakage
                {
                    data |= (byte)(0x02 << (channel - 1) * 2);// For Low-leakage DI must set PP
                }
                DO_Settings = data;

                txBuffer[1] = data;
                SPIWriteRead(txBuffer, ref rxBuffer, 2);
            }
            else// Output
            {
                ////////////////////////////
                // Reg 0x00:
                ///////////////////////////
                // Register Byte
                register = 0x01;// Write
                register |= (0x00 << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Data Byte
                // Set current bit mode from FTDI
                byte temp = 0x00;
                // Remember bit logic for FTDI input/output inverted from MAX14906 Reg 0x00 
                temp = (byte)((~FTDI_ADBUS_MODE & 0x80) >> 7);// DIO1
                data |= (byte)(temp << 4);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x01));// DIO2
                data |= (byte)(temp << 5);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x02) >> 1);// DIO3
                data |= (byte)(temp << 6);
                temp = (byte)((~FTDI_ACBUS_MODE & 0x04) >> 2);// DIO4
                data |= (byte)(temp << 7);
                // New Mode to Set
                temp = (byte)(0x01 << (channel + 3));
                data = (mode == 0) ? (byte)(temp | data) : (byte)(~temp & data);
                txBuffer[1] = data;
                SPIWriteRead(txBuffer, ref rxBuffer, 2);

                ////////////////////////////
                // Reg 0x0D:
                ///////////////////////////
                // Register Byte
                register = 0x01;// Write
                register |= (0x0D << 1);// Register Address
                register |= (byte)(Address << 6);// Chip Address
                txBuffer[0] = register;
                // Data Byte
                data = (byte)(DO_Settings & ~(0x03 << (channel - 1) * 2));// Whether any setting (HS, HS 2x, PP clamp, PP low) zero out both bits
                data |= (byte)(setting << (channel - 1) * 2);

                DO_Settings = data;// Update settings

                txBuffer[1] = data;
                SPIWriteRead(txBuffer, ref rxBuffer, 2);
            }

            // Store Fault Data
            SDOFaults.SHTVDD = ((rxBuffer[0] & 0x20) >> 5);
            SDOFaults.AbvVDD = ((rxBuffer[0] & 0x10) >> 4);
            SDOFaults.OWOffF = ((rxBuffer[0] & 0x08) >> 3);
            SDOFaults.OvrCurr = ((rxBuffer[0] & 0x04) >> 2);
            SDOFaults.OvldF = ((rxBuffer[0] & 0x02) >> 1);
            SDOFaults.GLOBLF = (rxBuffer[0] & 0x01);
            SDOFaults.ChannelFaults = rxBuffer[1];
        }

Conclusion

This application note shows how to program the MAX14906 to monitor inputs, drive outputs, and diagnose fault-conditions. This code is tested by using MAX14906EVKIT#. An engineer can implement an interface quickly and easily between popular microcontrollers and the MAX14906 by using C-code examples mentioned in this application note.