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
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);
}