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

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

", Switch);



for(i = 0; i < 8; i++) {



printf("Channel AN%d = %d ADC, %5.2f mA

", (i+1), AN_AD[i], AN_mA[i]);

}



printf("

");



// 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

n");

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

n");

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

", AN_mA[0], AN_mA[4] );

index += 20;

sprintf(&tx_buffer[index],"A2= %5.2f A6= %5.2f

", AN_mA[1], AN_mA[5] );

index += 20;

sprintf(&tx_buffer[index],"A3= %5.2f A7= %5.2f

", AN_mA[2], AN_mA[6] );

index += 20;

sprintf(&tx_buffer[index],"A4= %5.2f A8= %5.2f

", 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);



}

Save

Save

Save