Analysis of Raspberry Pi PLC pinout time response

Technical analysis of the Raspberry Pi PLC industrial controller pinout performance
June 13, 2023 by
Analysis of Raspberry Pi PLC pinout time response
Boot & Work Corp. S.L., Ricard Franch Argullol

Introduction

In this post, we will explore the performance of the Raspberry Pi PLC pinout time response, examining its ability to detect and respond to changes in input,  as well as the ability to output signals.

Requirements

If you want to measure the industrial PLC characteristics, you will also need a precise oscilloscope capable of reading at the 100ns scale.

Digital output

 
The Raspberry Pi PLCs are equipped with pin multiplexer known as PCA9685A. This integrated circuit uses I2C communication to provide the necessary outputs to the PLC, allowing it to control various outputs using just an I2C bus. As a downside, these pins are slower than direct pins, as the I2C communication has some latency compared to controlling direct pins.

To carry out the pins test, you must program the RPI PLC using both Python and C, in order to check if there are any differences. Both programs perform writes to a digital pin, measuring the time it takes to execute the write function.

The C version uses our LIBRPIPLC library, which can be found in our GitHub repository, and the common.h file found in its tests. the Python version uses the corresponding library.

C:
#include <iostream>
#include <chrono>
#include <rpiplc.h>
#include "common.h"

int main() {

initPins();
pinMode(digitalOutputs[0], OUTPUT);
bool a = true;

for (;;) {

auto start = std::chrono::high_resolution_clock::now();
digitalWrite(digitalOutputs[0], a);
auto end = std::chrono::high_resolution_clock::now();

auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

std::cout << "Execution time: " << duration << " microseconds" << std::endl;

a = !a;
usleep(10000);
}
return 0;
}
Python:
from rpiplc_lib import rpiplc
import time

usleep = lambda x: time.sleep(x/1000000.0)

def main():
rpiplc.init("RPIPLC_21")
a=True
        rpiplc.pin_mode("Q0.0", rpiplc.OUTPUT)

while True:
a = not a
start = time.time()
rpiplc.digital_write("Q0.0", a)
end = time.time()
res = ( end - start )
print(res)
usleep(10000)

if __name__ == "__main__":
main()

Both languages perform virtually the same, and the digitalWrite() barely takes 1ms to execute. The rise and fall time of the output signal are 0.744µs and 55.6µs respectively. The fall time being slower is what determines the maximum output frequency of the pin, as performing successive writes on a pin fast enough will result in a signal which barely stays low.

Some measurements illustrating this behaviour are shown below:

C language:
Delay
Output frequency
Period
Pulse width
0ms
495Hz
2.020ms
1.888ms
5ms
82.24Hz
12.16ms
7.440ms
10ms
45.05Hz
22.20ms
11.20ms
Python language:
Delay
Output frequency
Period
Pulse width
0ms
478.9Hz
2.088ms
1.884ms
5ms
81.97Hz
12.2ms
7.44ms
10ms
45.05Hz
22.2ms
11.1ms

Therefore, a 50% duty cycle 45Hz signal can be achieved using the output pins. if the duty cycle is not crucial, though, much higher frequencies, up to 495Hz, can be achieved.

Digital Input

The digital inputs also go through a pin multiplexer, the MCP23008. The RPi PLC has different types of digital inputs: the opto-isolated inputs, the interrupt inputs (which are also isolated) and the analogue inputs, which can work as digital (5-24V) too.

To test the reading time of the input, measure the execution time of the digitalRead() function:

C:
#include <iostream>
#include <chrono>
#include <rpiplc.h>
#include "common.h"

#define PIN digitalInputs[0]
#define READ_TYPE digitalRead
#define N_AVERAGE 10000

int main() {
initPins();
pinMode(PIN, INPUT);

unsigned long t_max, t_average;
for (long i = 0; i < N_AVERAGE; i++) {
auto t1 = std::chrono::high_resolution_clock::now();
READ_TYPE(PIN);
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
t_average += duration;
if (t_max < duration) t_max = duration;
}
std::cout << "Average: " << t_average/N_AVERAGE << " microseconds" << std::endl;
std::cout << "Maximum: " << t_max << " microseconds" << std::endl;

return 0;
}
Python:
from rpiplc_lib import rpiplc
import time

PIN = "I0.7"
READ_TYPE = rpiplc.digital_read
N_AVERAGE = 10000

def main():
rpiplc.init("RPIPLC_21")
rpiplc.pin_mode(PIN, rpiplc.INPUT)

t_max = 0; t_average = 0;
for i in range(N_AVERAGE):
start = time.perf_counter()
READ_TYPE(PIN)
end = time.perf_counter()
res = (end - start) * 1e6
t_average += res
if t_max < res: t_max = res

print("Average: {}\nMaximum: {}".format(t_average/float(N_AVERAGE), t_max))

if __name__ == "__main__":
main()

The execution time of the digitalRead() using the opto-isolated input(I0.0) is 445.36µs average with 816µs maximum using both C and Python, which translates to a maximum sampling frequency of 2245.4Hz.

In the case of the interrupt pins, the execution time is 3µs on average and 74µs using C. And using Python, the average is 6.5µs and 81µs maximum. This means that the maximum sampling frequency is around 333.33kHz and 153.85kHz, respectively.

Finally, the analogue input used as a digital has an execution time of 1µs on average and 72µs using C. And using Python, the average is 4µs and 43µs maximum. This means that the maximum sampling frequency is around 1MHz and 250kHz, respectively.

Analog output

The RPI PLC also features analogue outputs. These outputs work with the PCA9685A outputs, using circuitry to convert the PWM to a continuous signal with the adequate voltage. As the previous cases, you can test the outputs using both C and Python:

C:
#include <iostream>
#include <chrono>
#include <rpiplc.h>
#include "common.h"

int main() {

initPins();
pinMode(analogOutputs[0], OUTPUT);
int a = 4095;

for (;;) {

auto start = std::chrono::high_resolution_clock::now();
analogWrite(analogOutputs[0], a);
auto end = std::chrono::high_resolution_clock::now();

auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
std::cout << "Execution time: " << duration << " microseconds" << std::endl;

usleep(1000000);
a = (a == 4095) ? 0 : 4095;
}
return 0;
}
Python:
from rpiplc_lib import rpiplc
import time

usleep = lambda x: time.sleep(x/1000000.0)

def main():
rpiplc.init("RPIPLC_21")
a=4095
        rpiplc.pin_mode("A0.5", rpiplc.OUTPUT)

while True:
if a == 4095: a = 0
else: a = 4095
start = time.time()
rpiplc.analog_write("A0.5", a)
end = time.time()
res = ( end - start )
print(res)
usleep(1000000)

if __name__ == "__main__":
main()

The execution time of the analogWrite() function is 1ms using both languages. In this case, what determines the maximum output frequency are the rise and fall time, 120ms and 173ms respectively, as they are much slower than the code execution. Considering the fall time as half the period, we can achieve a signal with a maximum stable signal frequency of 2.89Hz , and could be stretched up to 4.87Hz.

Analog input


The analogue inputs allow measuring voltages between 0V and 10V, returning a value with a 12 bit resolution (0-4095). In the RPI PLC, the analogue inputs use an I2C expansor. These programs can be used to test the analog input sampling frequency:

C:
#include <iostream>
#include <chrono>
#include <rpiplc.h>
#include "common.h"

int main() {

initPins();
pinMode(analogInputs[0], INPUT);

for (;;) {

auto start = std::chrono::high_resolution_clock::now();
int a = analogRead(analogInputs[0]);
auto end = std::chrono::high_resolution_clock::now();

auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

std::cout << "Execution time: " << duration << " microseconds" << std::endl;
std::cout << "Read result: " << a << std::endl;
}
return 0;
}
Python:
from rpiplc_lib import rpiplc
import time

usleep = lambda x: time.sleep(x/1000000.0)

def main():
rpiplc.init("RPIPLC_21")
rpiplc.pin_mode("I0.7", rpiplc.INPUT)

while True:
start = time.time()
a = rpiplc.analog_read("I0.7")
end = time.time()
res = ( end - start )
print(res)
print(a)
usleep(1000000)

if __name__ == "__main__":
main()

The analogue readout runtime averages 860µs with a maximum of 2657µs using both C and Python, which translates to a maximum sample rate of 1162.8Hz.

Direct Pins

The Raspberry Pi PLCs also have some pins connected directly to the Raspberry. These are the available direct pins, working at 3.3V:

  • TX
  • RX
  • MISO
  • MOSI
  • SCK
  • GPIO8

There are also the I2C pins, which operate at 5V and have an external pull-up:

  • SCL
  • SDA
Output
C language:

The direct pins can be used as outputs. In this case we conducted a test using the pigpio library:

#include <pigpio.h>
#include <iostream>
#include <thread>

#define PIN 8
#define DELAY_NS 8*1000

void delayNanoSeconds(unsigned int nanoseconds) {
auto start = std::chrono::high_resolution_clock::now();
auto end = start + std::chrono::nanoseconds(nanoseconds);
while (std::chrono::high_resolution_clock::now() < end);
}

int main() {
if (gpioInitialise() < 0) {
std::cout << "Failed to initialize pigpio library." << std::endl;
return 1;
}
gpioSetMode(PIN, PI_OUTPUT);
while (1) {
gpioWrite(PIN, 1);
delayNanoSeconds(DELAY_NS);
gpioWrite(PIN, 0);
delayNanoSeconds(DELAY_NS);
}
}

The maximum frequency of the signal achieved for all pins is 150kHz doing a kind of sine wave. If you want a square shape you can go up to 62.5kHz.

Python language:

The direct pins can be used as outputs. In this case, you can test using the gpiozero library:

from gpiozero import LED

led = LED(14)

while True:
led.on()
led.off()

The maximum signal frequency achieved for all the pins is 62.5kHz with a 50% duty cycle.

Their rise and fall time, measured with an oscilloscope, are both 162ns. The I2C pins, however, have a slightly higher rise time, 2.72µs.

Input

The sampling time of the direct pins when used as inputs can be measured using this code:

from gpiozero import Button
import time

button = Button(8)
N_AVERAGE = 10000

def main():
t_max = 0; t_average = 0;
for i in range(N_AVERAGE):
start = time.perf_counter()
button.is_pressed;
end = time.perf_counter()
res = (end - start) * 1e6
t_average += res
if t_max < res: t_max = res

print("Average: {}\nMaximum: {}".format(t_average/float(N_AVERAGE), t_max))

if __name__ == "__main__":
main()

The read method takes on average up to 8µs to execute using this library, which means you can achieve up to a 125kHz sampling frequency using this method. And it can take a maximum of 122µs of execution time.

Summary

Inputs
C language:
Input type
Sampling frequency - AverageSampling frequency - Highest read time
Digital Interrupt
13.514kHz (74µs)333.33kHz (3µs)
Digital Opto-isolated
1.223kHz (816µs)2.245kHz (445.36µs)
Digital Non Opto-isolated
13.889kHz (72µs)1MHz (1µs)
Analog
376.4Hz (2657µs)1.163 kHz(860µs)
Direct pins
8.197kHz (122µs)125kHz (8µs)
Python language:
Input type
Sampling frequency - AverageSampling frequency - Highest read time
Digital Interrupt
12.346kHz (81µs)153.85kHz (6.5µs)
Digital Opto-isolated
1.223kHz (816µs)2.245kHz (445.36µs)
Digital Non Opto-isolated
23.256kHz (43µs)250kHz (4µs)
Analog
376.4Hz (2657µs)1.163 kHz(860µs)
Direct pins
8.197kHz (122µs)125kHz (8µs)
Outputs
C language:
Output type
Maximum stable frequency
Signal Frequency - MaximumRise time
Fall time
Digital (QX.0-7)50Hz (59.4%)
495Hz (93.1%)
0.744µs55.6µs
Analog (AX.5-7)2.89Hz (49%)
4.87Hz (47%)
120ms
173ms
Direct pins
150kHz (50%)
62.5kHz (50%)
162ns
162ns
Python language:
Output type
Maximum stable frequency
Signal Frequency - MaximumRise time
Fall time
Digital (QX.0-7)50Hz (59.4%)
495Hz (93.1%)
0.744µs55.6µs
Analog (AX.5-7)2.89Hz (49%)
4.87Hz (47%)
120ms
173ms
Direct pins
62.5kHz (50%)
62.5kHz (50%)
162ns
162ns

Conclusion

In this post, we have analysed the response time of the pins of the Raspberry Pi PLC industrial controller, examining the ability to detect and respond to input changes, as well as the ability to generate output signals. 

  • The results show that, in both C and Python languages, the execution times for pin write and read operations are quite fast. 
  • The maximum output frequency achieved is 495Hz, while the maximum sampling frequency for the inputs is 1.163kHz.
  • As for the direct pins, a maximum signal frequency of 150kHz is achieved as a sine wave. 
Odoo • Text and Image

In summary, the Raspberry Pi PLC has demonstrated solid performance in terms of pin response time.

​Search in our Blog

Analysis of Raspberry Pi PLC pinout time response
Boot & Work Corp. S.L., Ricard Franch Argullol June 13, 2023
Share this post

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.


Industrial PLC comparison >>>