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

Posted by Vytas Sinkevicius on

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

Share this post



← Older Post Newer Post →


Leave a comment

Please note, comments must be approved before they are published.