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
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 - Average | Sampling frequency - Highest read time |
Digital Interrupt | 333.33kHz (3µs) | 13.514kHz (74µs) |
Digital Opto-isolated | 2.245kHz (445.36µs) | 1.223kHz (816µs) |
Digital Non Opto-isolated | 1MHz (1µs) | 13.889kHz (72µs) |
Analog | 1.163 kHz(860µs) | 376.4Hz (2657µs) |
Direct pins | 125kHz (8µs) | 8.197kHz (122µs) |
Python language:
Input type | Sampling frequency - Average | Sampling frequency - Highest read time |
Digital Interrupt | 153.85kHz (6.5µs) | 12.346kHz (81µs) |
Digital Opto-isolated | 2.245kHz (445.36µs) | 1.223kHz (816µs) |
Digital Non Opto-isolated | 250kHz (4µs) | 23.256kHz (43µs) |
Analog | 1.163 kHz(860µs) | 376.4Hz (2657µs) |
Direct pins | 125kHz (8µs) | 8.197kHz (122µs) |
Outputs
C language:
Output type | Maximum stable frequency | Signal Frequency - Maximum | Rise time | Fall time |
Digital (QX.0-7) | 50Hz (59.4%) | 495Hz (93.1%) | 0.744µs | 55.6µs |
Analog (AX.5-7) | 2.89Hz (49%) | 4.87Hz (47%) | 120ms | 173ms |
Direct pins | 62.5kHz (50%) | 62.5kHz (50%) | 162ns | 162ns |
Python language:
Output type | Maximum stable frequency | Signal Frequency - Maximum | Rise time | Fall time |
Digital (QX.0-7) | 50Hz (59.4%) | 495Hz (93.1%) | 0.744µs | 55.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.
In summary, the Raspberry Pi PLC has demonstrated solid performance in terms of pin response time.
Analysis of Raspberry Pi PLC pinout time response