Display Control Unit DCU for PFEIFFER vacuum Protocol devices using the PIC1847Q84

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
A replacement (inhouse) controller for the DCU that's expensive, rare and are a pain to repair when bricked by high voltage transients.
https://mmrc.caltech.edu/Vacuum/Pfeiffer Turbo/DCU Operatioins.pdf
1731982401112.png
1731982458216.png
Blown RS485 driver and logic regulator chips on the OEM board with a: https://www.nxp.com/products/68HC11E1 controller.
1731982556845.png
Adapting the FM80 interface board from another project for this one. It's only using the MODBUS port for pumping control.
1731982700015.png
DCU to TC 750 pump controller RS485 serial data.
https://www.ajvs.com/library/Pfeiffer_Vacuum_TPH_2101_P_PC_Manual.pdf

1731982965652.png
1731982986295.png
Q84 controller RS485 commands and responses. The Q84 RS485 driver (https://www.analog.com/media/en/technical-documentation/data-sheets/ADM3095E.pdf) is close to 4V signals vs the less that 3V signals from the OEM devices.
https://mmrc.caltech.edu/Vacuum/Pfeiffer Turbo/Pfeiffer Interface RS@32.pdf
1731983237184.png
Diagnostic mode on the Q84 display using the old Q84 controller board for testing and development.
1731983549579.png
1731983682921.png
Not using much of the boards total capability for this project.
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Basic raw data monitor functions are done. Pump controller ID and Sw version, motor drive frequency, DC link voltage, Motor current and acceleration readouts.
1732072490073.png
1732072511525.png
1732072552457.png
1732072579475.png
Controller name request message and reply.

A simple FSM command sequencer and parser
C:
/*
 * various PVP read-only messages
 */
P_data P_read = {
    .addr2 = '0',
    .addr1 = '0',
    .addr0 = '1',
    .action1 = '0',
    .action0 = '0',
    .para2 = '3',
    .para1 = '0',
    .para0 = '9',
    .dl1 = '0',
    .dl0 = '2',
    .data1 = '=',
    .data0 = '?',
    .chk2 = '0',
    .chk1 = '0',
    .chk0 = '0',
    .cr = 13, // EOF CR
};
// 309 Act rotspd Actual rotation speed TMP in Hz 0-2000

/*
 * PVP position set/read messages
 */
P_data_r P_action = {
    .addr2 = '0',
    .addr1 = '0',
    .addr0 = '1',
    .action1 = '1',
    .action0 = '0',
    .para2 = '0',
    .para1 = '2',
    .para0 = '3',
    .dl1 = '0',
    .dl0 = '6',
    .data[5] = '1',
    .data[4] = '1',
    .data[3] = '1',
    .data[2] = '1',
    .data[1] = '1',
    .data[0] = '1',
    .chk2 = '0',
    .chk1 = '0',
    .chk0 = '0',
    .cr = 13, // EOF CR
};
// 023 Motor TMP  Motor Turbopump ON/OFF OFF ON ON(*) R/W 0 

/*
 * Simple MODBUS master state machine for soft DCU
 * this needs to run in the main programming loop
 * to handle RS485 serial I/O exchanges
 */
int8_t master_controller_work_dcu(C_data * client)
{
    static uint32_t spacing = 0;

    if (spacing++ <SPACING && !M.rx) {
        return T_spacing;
    }
    spacing = 0;

    client->trace = T_begin;
    switch (client->cstate) {
    case CLEAR:
        client->trace = T_clear;
        clear_2hz();
        clear_500ahz();
        client->cstate = INIT;
        client->modbus_command = client->mcmd++; // sequence modbus commands to client
        if (client->mcmd > G_LAST) {
            client->mcmd = G_ID;
        }
        /*
         * command specific tx buffer setup
         */
        switch (client->modbus_command) {
        case G_CONFIG: // read code request
            client->trace = T_config;
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read_N, sizeof(P_read_N));
            break;
        case G_DATA1: // read code request
            client->trace = T_data;
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read_I, sizeof(P_read_I));
            break;
        case G_DATA2: // read code request
            client->trace = T_data;
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read_A, sizeof(P_read_A));
            break;
        case G_LINK: // read code request
            client->trace = T_link;
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read_L, sizeof(P_read_L));
            break;
        case G_VERSION: // read code request
            client->trace = T_version;
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read_V, sizeof(P_read_V));
            break;
        case G_LAST: // end of command sequences
            client->cstate = CLEAR;
            client->mcmd = G_ID; // what do we run next
            break;
        case G_ID: // operating mode request
            client->trace = T_id;
        default:
            client->req_length = modbus_dcu_send_msg((void*) cc_buffer_tx, (const void *) &P_read, sizeof(P_read));
            break;
        }
        break;
    case INIT:
        client->trace = T_init;
        /*
         * MODBUS master query speed
         */
        if (get_500ahz(false) >= CDELAY) {
            half_dup_tx(false); // no delays here
            M.recv_count = 0;
            client->cstate = SEND;
            clear_500hz();
            client->trace = T_init_d;
        }
        break;
    case SEND:
        client->trace = T_send;
        if (get_500hz(false) >= TEDELAY) {
            for (uint8_t i = 0; i < client->req_length; i++) {
                Swrite(cc_buffer_tx[i]);
            }
            client->cstate = RECV;
            clear_500hz(); // state machine execute background timer clear
            client->trace = T_send_d;
            M.sends++;
            M.rx = false;
            if (serial_trmt()) { // check for serial UART transmit shift register and buffer empty
                clear_500hz(); // clear timer until buffer empty
            }
            delay_ms(TDELAY + client->req_length);
            DERE_SetLow(); // enable modbus receiver
        }
        break;
    case RECV:
        client->trace = T_recv;
        if (get_500hz(false) >= TEDELAY) { // state machine execute timer test

            client->trace = T_recv_r;
            half_dup_rx(false); // no delays here

            /*
             * check received response data for size and format for each command sent
             */
            switch (client->modbus_command) {
            case G_VERSION: // 
                modbus_read_dcu_check(client, &client->version_ok, sizeof(P_action));
                break;
            case G_LINK: // 
                modbus_read_dcu_check(client, &client->version_ok, sizeof(P_action));
                break;
            case G_CONFIG: // 
                modbus_read_dcu_check(client, &client->config_ok, sizeof(P_action));
                break;
            case G_DATA1: //
                modbus_read_dcu_check(client, &client->data_ok, sizeof(P_action));
                break;
            case G_DATA2: // 
                modbus_read_dcu_check(client, &client->data_ok, sizeof(P_action));
                break;
            case G_ID: // check for client module type
            default:
                modbus_read_dcu_check(client, &client->id_ok, sizeof(P_action));
                break;
            }
        }
        break;
    default:
        break;
    }
    return client->trace;
}
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Have my V1.0 prototype boards from JLC.
1732237399221.png
1732237439065.png
1732237462456.png
1732237570946.png
A redesign that eliminated the FM80 interface and replaced that with RJ45 connections to power and RS485.

1732237670529.png
Testing with a good and bad TC750 Turbo Pump Controller. On power-up the bad motor controller runs OK for a few minutes and then locks up.
1732237784703.png

Motor controller link power-up and DCU boot (the large HEX number duning the boot sequence is the PIC chip unique ID) with communications startup log messages.
https://microchip.my.site.com/s/article/Microchip-Unique-Identifer--MUI--on-PIC18F--PIC16F-devices
C:
/*
* Device Information Area (DIA) Table
*/

#define DIA_MUI                                             0x2C0000
#define DIA_MUI0                                            0x2C0000
#define DIA_MUI1                                            0x2C0002
#define DIA_MUI2                                            0x2C0004
#define DIA_MUI3                                            0x2C0006
#define DIA_MUI4                                            0x2C0008
#define DIA_MUI5                                            0x2C000A
#define DIA_MUI6                                            0x2C000C
#define DIA_MUI7                                            0x2C000E

/*
  * read and store the CPU_ID for PCB tracing
 */

    B.mui[i] = DeviceID_Read(DIA_MUI + (i * 2)); // Read CPU ID from memory and store in array


device_id_data_t DeviceID_Read(device_id_address_t address)
{
    device_id_data_t deviceID;

    //Save the table pointer
    uint32_t tablePointer = ((uint32_t) TBLPTRU << 16) | ((uint32_t) TBLPTRH << 8) | ((uint32_t) TBLPTRL);

    //Load table pointer with Device ID address
    TBLPTRU = (uint8_t) (address >> 16);
    TBLPTRH = (uint8_t) (address >> 8);
    TBLPTRL = (uint8_t) address;

    //Execute table read and increment table pointer
    asm("TBLRD*+");

    deviceID = (device_id_data_t) TABLAT;

    //Execute table read
    asm("TBLRD*");

    deviceID |= (device_id_data_t) (TABLAT << 8);

    //Restore the table pointer
    TBLPTRU = (uint8_t) (tablePointer >> 16);
    TBLPTRH = (uint8_t) (tablePointer >> 8);
    TBLPTRL = (uint8_t) tablePointer;

    return deviceID;
}
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
1732578666915.png
1732578690208.png
1732578717014.png

1732578746023.png
Vacuum turbo pump running at set speed of 525 Hz three-phase drive. RS485 decoded on the scope and on the Q84 DCU display.

1732578867810.png
The small pump is between the V63 and V60 vacuum manifold.
1732578922323.png
Connected to smaller turbo pump @1000Hz drive. Low load pumping at 1e-6 torr vacuum.
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Last control and monitor test before a production PCB spin. The Q84 will piggy-back the display and the RS485 termination bias was reworked a bit to increase the inactive bus voltage to 200 mV.
1732748306794.png
 

Attachments

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Enclosure. Download a generic 4X20 LCD panel STL file. Needs work. ;)
1733263208145.png
Modify that after measurements.
1733263243811.png
1733263269858.png
1733263297812.png

Hacked the back side to make box measurements for modifications when the new boards arrive.
1733263381183.png
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Have my production boards from OSHpark.
1733882459657.png
JLC with plating and upgraded FR4 (extra cost) is not too bad for the price.

Same manufacturing file on both.
1733882530150.png
JLC
1733882562130.png
OSH
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,251
Production spin of board and case. Redesigned the PCB to eliminate connectors and for a out the back RJ45 connector.

1735615165579.png
1735615443661.png
1735615190518.png
Mounted on headers pins from the LCD.
1735615213019.png
CF 3D printed case. With a glued plastic spacer to support the bottom of the board.
1735615246004.png
1735615275088.png
 
Top