RPi 3, Analog 4-20mA, Relay, and Character Display Interfaces

The most commonly used interfaces in commercial and industrial applications require some form of analog input (sensors) and relay outputs for control. Having a display that can be remote mounted is a bonus! Here is a test setup showing an RPi 3.

Starting from the top center and going clockwise:

  • RPI 3 with Jessie, SSH and VNC enabled
  • Fluke 787 Processmeter for generating 4-20 mA signals
  • Model VP-8AI 8 channel Analog Input Interface module connected to the RPi GPIO
  • Model VP-8KO 8 channel Relay module connected via RS485 to the VP-8AI
  • Model VP-RDU 4 Line x 20 Character Display Module connect to the VP-8KO module via RS485

And the last piece to show you, the desktop of the RPi via the VNC viewer:

This screen shot shows the STDIO window of the analog inputs in AD Counts and mA Readings.

The test parameters are as follows:

  • use the RPi 3 on board programming capabilities - wiringPi and the Geany "C" Compiler
  • GPIO digital read of the Dipswitch (can be used as a Modbus ID)
  • SPI interface to drive the 8 Green LEDs using the 74HC595 Driver - in this case each LED turns on if the input channel mA is > 2.5 mA
  • SPI interface to read 8 channels of mA information from the MCP3208 AD Converter (20 mA into a 150 Ohm Load Resistor = 3 VDC Full Scale, the MCP3208 uses the 3.3 VDC supply voltage as a reference, therefore 3/3.3 = .909 * 4096 max counts = 3723 AD Counts full scale at 20 mA (give or take)
  • RPi 3 as MODBUS Master to output relay information (if each channel is greater than 16.5 mA, turn on the relay for that channel) using Function 15
  • RPi 3 as MODBUS Master to output remote display information using Function 16

 

You can find the interfaces listed above (Tech info, schematics, prices, etc) at these links:

Raspberry Pi Analog Input Module

Relay Module 8 Point SPDT

LCD Display 4 Line x 20 Character

 

The following test source code works for both earlier RPi's and the current RPi 3 release. Remember to initialize the SPI and Serial on the RPi.

Finally, for updates on new projects and products, join our newsletter at the bottom of this page. Thanks and Enjoy!

/*
 * main.c
 *
 * Copyright 2016  <pi@raspberrypi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 *
 */
 
 /*
 *    Initialize the SPI and Serial ports on the RPi before compiling
 *    Compile, Build and Make as sudo in Geanie C compiler
 *    Code tested on RPI and Jessie OS
 */
 
 
 

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

// wiringPi Include Header Files
#include <wiringPi.h>     
#include <wiringPiSPI.h>
#include <wiringSerial.h>

// Define Pins for Dipswitch
#define PIN_SW1 2
#define PIN_SW2 3
#define PIN_SW3 4
#define PIN_SW4 17
#define PIN_SW5 27
#define PIN_SW6 22
#define PIN_SW7 24
#define PIN_SW8 23

// Define Pin for RS485 Direction
// Low = Receive, High = Transmit
#define PIN_DIR 25

// Define Pins for SPI Chip Enables
#define PIN_EN_LED 18
#define PIN_EN_AD  7

// Define mA settings
// 20 mA into 150Ohms = 3 Vdc / 3.3 Vdc full scale * 4096 Counts
#define mA_20        3723    
#define mA_Fault     2.5     // 2.5 mA
#define mA_Trip      12.5    // 12.5 mA

// Define MODBUS ID Settings
#define MODBUS_ID_RELAY       128
#define MODBUS_ID_DISPLAY     101
    
// Prototypes    
void Initialize_Pi_Hardware(void);
int Read_Switch(void);
void Update_Leds(void);
void Update_Trip_Points(void);
void Update_Analog(unsigned char channel);
void Update_Uart_Relay_Output(void);
void Update_Uart_Remote_Display(void);
unsigned int MB_crc16(char *p, unsigned char n);
     
// Variables
unsigned AN_AD[8];
float AN_mA[8];
int Switch;
int fd;
unsigned char Relay_Status;

// main

int main(void) {
    int i;
    
    // Initialize
    wiringPiSetupGpio();           
    Initialize_Pi_Hardware();      
    digitalWrite(PIN_EN_LED, HIGH);
    digitalWrite(PIN_EN_AD, HIGH);
    
    // Set RS485 Transceiver to Receive
     digitalWrite(PIN_DIR, LOW);
              
    while(1)
    {
        Switch = Read_Switch();
        
        // Update all 8 Analog Inputs
        Update_Analog(0);        
        Update_Analog(1);        
        Update_Analog(2);        
        Update_Analog(3);        
        Update_Analog(4);        
        Update_Analog(5);        
        Update_Analog(6);        
        Update_Analog(7);        
        
        // Print Analog Input Info to STDIO
        printf("ID = %d \n", Switch);
            
        for(i = 0; i < 8; i++) {
            
            printf("Channel AN%d = %d ADC, %5.2f mA \n", (i+1), AN_AD[i], AN_mA[i]);
        }
        
        printf("\n");
                
        // Update Leds on 8AI, ON if input > 2.5 mA
        Update_Leds();
        
        // Update Remote Relays, Each Relay On if mA > 12.5 mA
        Update_Trip_Points();
        
        // Send Relay info to Remote Module via Modbus
        Update_Uart_Relay_Output();
        
        // Delay to allow Remote Module to respond
        delay(500);
        
        // Send Analog info to Remote Display Unit
        Update_Uart_Remote_Display();
        
        // Delay to allow Remote Display to respond
        delay(500);

    }

    return 0;
}

// Read Analog Info by Channel from MCP3208
void Update_Analog(unsigned char channel) {

    unsigned int  adc, input;
    unsigned char buf[3];
    
    // Initialize SPI, - using slow speed
    wiringPiSPISetup(1, 100000);
    
    // Define inpu channel parameters fro MCP3208
    input = 0x0600 | (channel << 6);
    
    buf[0] = (input >> 8) & 0xff;
    buf[1] = input & 0xff;
    buf[2] = 0;
    
    // Enable the CS Line
    digitalWrite(PIN_EN_AD, LOW);
    
    // Write / Read data from MCP3208
    wiringPiSPIDataRW(1,buf,3);
    
    // Get AD Counts
    adc = ((buf[1] & 0x0f ) << 8) | buf[2];

    // Disable the CS Line
    digitalWrite(PIN_EN_AD, HIGH);
            
    // Save Analog info as ad Counts        
    AN_AD[channel] = adc;
    
    // Save Analog info as mA
    AN_mA[channel] = (float) adc * 20 / mA_20;
    
}

// Update the Status LEDs
void Update_Leds(void) {

    unsigned char buf[2];
    unsigned char value;
    
    value = 0;
    
    if(AN_mA[0] > mA_Fault) {
        value |=  0x01;
    }
    if(AN_mA[1] > mA_Fault) {
        value |=  0x02;
    }
    if(AN_mA[2] > mA_Fault) {
        value |=  0x04;
    }
    if(AN_mA[3] > mA_Fault) {
        value |=  0x08;
    }
    if(AN_mA[4] > mA_Fault) {
        value |=  0x10;
    }
    if(AN_mA[5] > mA_Fault) {
        value |=  0x20;
    }
    if(AN_mA[6] > mA_Fault) {
        value |=  0x40;
    }
    if(AN_mA[7] > mA_Fault) {
        value |=  0x80;
    }
    
    buf[0] = value;
    
    // Intialize the SPI - slow speed
    wiringPiSPISetup(0, 100000);
    // Write Data to the 74HC595
    wiringPiSPIDataRW(0,buf,1);
    
    // Enable, Delay, Disable the CS Line
    digitalWrite(PIN_EN_LED, LOW);
    delay(1);
    digitalWrite(PIN_EN_LED, HIGH);
    
}

// Update the Relay Status to be sent to the remote 8 channel Relay module
void Update_Trip_Points(void) {

    unsigned char value;
    
    value = 0;
    
    if(AN_mA[0] > mA_Trip) {
        value |=  0x01;
    }
    if(AN_mA[1] > mA_Trip) {
        value |=  0x02;
    }
    if(AN_mA[2] > mA_Trip) {
        value |=  0x04;
    }
    if(AN_mA[3] > mA_Trip) {
        value |=  0x08;
    }
    if(AN_mA[4] > mA_Trip) {
        value |=  0x10;
    }
    if(AN_mA[5] > mA_Trip) {
        value |=  0x20;
    }
    if(AN_mA[6] > mA_Trip) {
        value |=  0x40;
    }
    if(AN_mA[7] > mA_Trip) {
        value |=  0x80;
    }

    Relay_Status = value;
}

// Send via MODBUS Relay info to the Relay module
void Update_Uart_Relay_Output(void) {
    
    unsigned char i, index;
    char tx_buffer[11];
    unsigned int checksum;

     // Open Serial Port and Initialize
     //fd = serialOpen("/dev/ttyAMA0", 19200); // For RPi < 3    
     fd = serialOpen("/dev/ttyS0", 19200);     // For RPi = 3    
     
     if(fd < 0)
     {
        printf("Error opening Serial Port \nn");
        return;
    }
    
    index = 0;
    
    tx_buffer[index++] = MODBUS_ID_RELAY;    // MODBUS ID for slave relay module
    tx_buffer[index++] = 15;                 // Function 15 = write multiple coils
    
    tx_buffer[index++] = 0;                  // Start Address High Byte
    tx_buffer[index++] = 0;                  // Start Address Low Byte
    
    tx_buffer[index++] = 0;                  // Qty Coils High Byte
    tx_buffer[index++] = 8;                  // Qty Coils Low Byte
    
    tx_buffer[index++] = 1;                  // Byte Count
    tx_buffer[index++] = Relay_Status;       // Data Byte (8 Coils)
    
    checksum = MB_crc16(tx_buffer,index);
    
    // Checksum Low Byte, High Byte,    Sent Little Endian
    tx_buffer[index++] = (unsigned char)(checksum & 0x00ff);            
    tx_buffer[index++] = (unsigned char)((checksum >> 8) & 0x00ff);    
    
    digitalWrite(PIN_DIR, HIGH);             // Set RS485 to TX
    delay(5);                                // Allow lines to settle
    
    for(i=0; i<index; i++) {
        serialPutchar(fd, tx_buffer[i]);
    }
            
    delay(50);                               // Allow last byte stop bit
    digitalWrite(PIN_DIR, LOW);              // Set RS485 to RX
    
    serialClose(fd);
}        

// Read the Dipswitch GPIO lines
int Read_Switch(void) {
    
    int value;

    value = 0;
    
    if(!digitalRead(PIN_SW1)) {
        value |= 0x01;
    }
    if(!digitalRead(PIN_SW2)) {
        value |= 0x02;
    }
    if(!digitalRead(PIN_SW3)) {
        value |= 0x04;
    }
    if(!digitalRead(PIN_SW4)) {
        value |= 0x08;
    }
    if(!digitalRead(PIN_SW5)) {
        value |= 0x10;
    }
    if(!digitalRead(PIN_SW6)) {
        value |= 0x20;
    }
    if(!digitalRead(PIN_SW7)) {
        value |= 0x40;
    }
    if(!digitalRead(PIN_SW8)) {
        value |= 0x80;
    }
    
    return value;
    
}

// Initialize the RPi hardware GPIO lines
void Initialize_Pi_Hardware(void) {
    
    // SPI CS Enable Lines
    pinMode(PIN_EN_LED, OUTPUT);
    pinMode(PIN_EN_AD, OUTPUT);

    // Dipswitch Input Lines
    pinMode(PIN_SW1, INPUT);
    pinMode(PIN_SW2, INPUT);
    pinMode(PIN_SW3, INPUT);
    pinMode(PIN_SW4, INPUT);
    pinMode(PIN_SW5, INPUT);
    pinMode(PIN_SW6, INPUT);
    pinMode(PIN_SW7, INPUT);
    pinMode(PIN_SW8, INPUT);
    
    // RS485 Direction Line, 0 = Receive, 1 = Transmit
    pinMode(PIN_DIR, OUTPUT);
    
}

// MODBUS RTU Error Checkum calculation using Polynomial Method
unsigned int MB_crc16(char *p, unsigned char n) {

    unsigned char i;
    unsigned int crc, poly;
    
    crc = 0xffff;
    poly = 0xa001;
    
    while(n--) {
        crc ^= *p++;
        
        for(i = 8; i != 0; i--) {
            if(crc&1) {
                crc = (crc>>1) ^ poly;    
            }
            else {
                crc >>= 1;    
            }        
        }        
    }
    
    return crc;    
}

// Update the Remote Display via MODBUS
void Update_Uart_Remote_Display(void) {
    
    unsigned char i, index;
    char tx_buffer[97];
    unsigned int checksum;
    
     // Open Serial Port and Initialize
     //fd = serialOpen("/dev/ttyAMA0", 19200); // For RPi < 3    
     fd = serialOpen("/dev/ttyS0", 19200);     // For RPi = 3    
     
     if(fd < 0)
     {
        printf("Error opening Serial Port \nn");
        return;
    }
    
    index = 0;
    
    tx_buffer[index++] = 101;       // MODBUS ID for slave display module
    tx_buffer[index++] = 16;        // Function 16 = write multiple holding registers
    
    tx_buffer[index++] = 0;         // Start Address High Byte
    tx_buffer[index++] = 0;         // Start Address Low Byte
    
    tx_buffer[index++] = 0;         // Qty Regsiters High Byte
    tx_buffer[index++] = 43;        // Qty Registers Low Byte
    
    tx_buffer[index++] = 84;        // Byte Count

    // Update the 4 line x 20 character data buffers with analog info
    sprintf(&tx_buffer[index],"A1= %5.2f  A5= %5.2f  \n", AN_mA[0], AN_mA[4] );
    index += 20;
    sprintf(&tx_buffer[index],"A2= %5.2f  A6= %5.2f  \n", AN_mA[1], AN_mA[5] );
    index += 20;
    sprintf(&tx_buffer[index],"A3= %5.2f  A7= %5.2f  \n", AN_mA[2], AN_mA[6] );
    index += 20;
    sprintf(&tx_buffer[index],"A4= %5.2f  A8= %5.2f  \n", AN_mA[3], AN_mA[7] );
    index += 20;
    
    tx_buffer[index++] = 0;
    tx_buffer[index++] = 0;    // status LED 1 , not used
    tx_buffer[index++] = 0;
    tx_buffer[index++] = 0;    // status LED 2 not used
    tx_buffer[index++] = 0;
    tx_buffer[index++] = 0;    // audible driver not used
        
    checksum = MB_crc16(tx_buffer,index);
    
    // Checksum Low Byte, High Byte, Sent Little Endian
    tx_buffer[index++] = (unsigned char)(checksum & 0x00ff);            
    tx_buffer[index++] = (unsigned char)((checksum >> 8) & 0x00ff);    
    
    digitalWrite(PIN_DIR, HIGH);        // Set RS485 to TX
    delay(10);                          // Allow lines to settle
    
    for(i=0; i<index; i++) {
        serialPutchar(fd, tx_buffer[i]);
    }
            
    delay(100);                          // Allow last byte stop bit
    digitalWrite(PIN_DIR, LOW);          // Set RS485 to RX
    
    serialClose(fd);    
    
}

 

SaveSaveSave

Leave a comment

Please note, comments must be approved before they are published