Planlos01
Sorry I'm new to OpenPLC so this is probably a dump question.
How can I access Data from I2C bus in my PLC programm?
Quote 0 0
thiagoralves

Currently you can't [frown]

However, you still can edit the I/O driver for the platform you're running on (Raspberry Pi, ESP8266, Arduino, etc) and add some I2C code to it. As long as you save the information on the OpenPLC buffers, they will be accessible by the located variables (like %IWx or %QWx).

Quote 0 0
nuktameron
Hi Thiago, would you give some advice on how to do that? I am working with a Raspberry Pi 3 v.1.2. Can you direct me to where the IO driver is and maybe where to look to figure out how it works? Thanks in advance, Alex
Quote 0 0
thiagoralves
The file core/hardware_layer.cpp contains information about the current driver running with OpenPLC. You can edit that file (if you know C++) to add I2C or any other hardware information you want. You can take a look at some examples at the /core/hardware_layers/ folder. I believe that the UniPi driver has some I2C code on it.

To make your changes permanent, copy them into the /core/hardware_layers/raspberry.cpp file. Therefore, if you ever need to recompile OpenPLC you will not loose them.
Quote 0 0
nuktameron
Hi Thiago,

Thanks for the tips, I have been working on getting the MCP23017 expansion for some time now but to no avail. It seems that there are two problems:

1) Nodejs - I2C conflict
   - After rebooting the Pi and running the i2cdetect command, I can see the expansion board's address. I can also write a test c application to control the GPIO using wiringPi (has has a very good section on how to do that with mcp23017)
   - As soon as I run nodejs- and an openPLC program, the connection with the MCP23017 is lost, I can't see it using i2cdetect and the test C program  does not work
   - Do you think there is a straightforward solution to this? Is it something you have encountered before? 
   - running lsmod I can see all modules running and they do not change between seeing and not seeing the board
  - After running and stopping openPLC I can only connect to MCP23017 after I reboot the Pi

2) Using wiringPi to map extra IO
   - So I looked at the hardware_layer.cpp file and it seems that you assign an IO point (which is somehow connected to a modbus regsiter) to each Raspberry pin as per the wiringPi configuration
   - Will the following work ( I can't test it because of the first problem- when I run server.js I2C stops working)
   - We use mcp23017.h and its functions to configure the extra IO board (this works for sure- I can test it)
   - Set IO point 0 as output
   -  Change the total output number to 15
   - Change the token string to [MAX_OUTPUT] = { 15, 16, 4, 5, 6, 10, 11, 26, 27, 28, 29, 100 };
   - 100 is the first IO point of the mvp23017 board
   - expect that by using %QX1.3 in the PLC program the output will work?

   - do we have to change anything else in all the openPLC files to make it aware that this next IO point is coil register 11, corresponding to %QX1.3? 

   - At first  was planning on using the uniPi driver as you said before but it seemed to be that wiring pi with its mcp23017 functionality can also work- is that true

Alex
Quote 0 0
thiagoralves
The UniPi driver uses wiring pi to do its work. I don't know why you're facing these issues. Perhaps when wiring pi is initialized in your code, if you're not initializing I2C properly, it might be blocking communication with the I2C devices. It's hard to tell without seeing your code.

About your other questions, OpenPLC is modular. Since you're working on hardware, the only file you need to change is the hardware_layer.cpp. The PLC logic core takes the I/O buffer sequentially, so that output %QX1.3 will be bool_output[1][3]. Your hardware driver is responsible for writing/reading the values you want on the right positions of the buffer.

I highly recommend you to take the UniPi driver as an example for what you're trying to do. It should be straightforward.
Quote 0 0
ElevatorGuru
I'm looking into using the same expansion MCP23017, were you able to get this working?  I need a few more I/O than the standard GPIO provides.

Thanks
Quote 0 0
thiagoralves
If all you need is more I/O, have you considered using Arduino boards as slave devices? You can add as many as you want through the web interface
Quote 0 0
tim292stro
You could also go "old school" and use your existing GPIOs to fan out to an expansion bus.  I recently did this at work to get ~32 GPIO from 4 data IO, three address IO, and a bus strobe.  With a 3-to-8 address decoder that expands out to 8 select lines - the strobe enables the bus drive (inputs) or sets the latches (outputs) - and obviously the 4x data lines are data.  With the C-code driver for the Broadcom, the IO speed is in the multi-tens of megahertz - more than fast enough for IO with debounce...

I did 4x buttons, 4x indicator LEDs, a 4-state stepper, 4-bit HD44780 LCD (4in/4out), and 4-bits of LCD control - and had enough spares to do me for a while.  This left all of the SPI, I2C, and UART lines alone for other uses.  There are enough other GPIO available to be used as HW interrupts, or to further expand the data bus width later.
Quote 0 0
Tekeff
Hello Thiago,

I would like ask for advice about commissioning I2C on OpenPLC.

I'm trying to connect a simple digital light intensity sensor (BH1750) to RPI 3b+ and display the measured values ​​via Modbus. RPI has been set and enabled to use I2C.
According to the recommendations above, I have placed into the "Hardware Layer Code Box" some code from the Internet to read the values ​​from the sensor. However, after loading and running a simple program, it looks as if Modbus communication is always connected and disconnected during program execution, see appendix. I use the input register %IW2 to read the value.

Thank you.
Quote 0 0
thiagoralves
You curstom_layer code looks incomplete. You need to have the custom output function as well, even if it is empty. Modbus connection and disconnection probably is related to your modbus client software connecting and disconnecting from OpenPLC. Make sure your software has an option to keep tcp channel active and have that enabled. Finally, make sure you have disabled the pins associated with the i2c bus on the ignored_vectors. Otherwise, OpenPLC will try to control these pins as GPIO and you will end up in a conflict.
Quote 0 0
Tekeff
The code "custom_layer", which I previously sent in the attachment, contained only the changes made. Omitted sections are also included in the original code.
I used the "Radzio! Modbus Master Simulator" as a Modbus display software, in whose settings I did not find the option for the 'TCP with keep-alive' transport type. So I used ScadaBR for display. Based on your recommendation, I set disabled certain the pins in the OpenPLC webserver and set 'TCP with keep-alive' to Enabled in ScadaBR. Unfortunately, after this last step, Modbus communication has never been established. After resetting the transport type to 'TCP', the OpenPLC runtime repeatedly failed.

If I use the transport type option to 'TCP' only, sending information via Modbus works, but the sensor information looks instability, ScadaBR receives all values ​​with a time delay of 4 to 60 seconds and after some time the OpenPLC runtime is stop (timeout in ScadaBR is set to 2 seconds).
Quote 0 0
thiagoralves
I took a better look at your code, and it seems that
int handle = wiringPiI2CSetup(0x23) ;
wiringPiI2CWrite(handle,0x10);
sleep(1);

are initialization functions that take a long time (especially the sleep for one second function). If you do that every time OpenPLC tries to read inputs, then all it will do is initialize and sleep forever. That's why you're not being able to have communications or anything else working. In fact, sleep instructions must be used with great care not to affect the real-time behavior of the OpenPLC. You must move these initialization routines to initCustomLayer() so that they will be executed only once. You probably will have to declare int handle as a global variable for you to be able to access it from initCustomLayer() and updateCustomIn().
Quote 0 0
tim292stro
...In fact, sleep instructions must be used with great care not to affect the real-time behavior of the OpenPLC...


This is very true and probably worth expanding on.

For those reading this who have never written anything below user-space (user-speed programs), a general rule of lower level code is to only do what is absolutely necessary in a driver/kernel-level, then defer other processing to low-priority threads.  Drivers should not have sleeps or no-ops (unless necessary in the hardware to allow a function block to finish a previous command) as that is simply throwing away CPU cycles.  As an example a single cycle instruction processor with a two-cycle divide operation would require a no-op after the divide command is fired for the results to appear in the output register - this level of machine code optimization is probably WAY below where most OpenPLC users are writing code.  So for the rest of us, a better practice is to split up a function into more granular steps, and start using timers and yields.  This is like a sleep, but instead of stalling the CPU thread, the code is allowing a context switch for other threads to do work until the timer fires.  Effort should be spent on ensuring that a high-priority interrupt does only the work required to clear an interrupt as quickly as possible - for the simple reason that it is interrupting something else.

Think of it like this, if you have an interrupt from a chip that needs to send a message (like "my FIFO is almost full"), the interrupt function should just identify the chip involved (if it's a shared interrupt line), clear the interrupt, and queue a low-priority function to talk to that chip about what the interrupt is a flag for (i.e. read out the FIFO).  Then it should release the high-priority thread block.  When the CPU has a moment in the scheduler, it will pull the function out of the queue to do the follow-up handling of the interrupt actions.  This lower function is not as time critical so you can allow this thread to be interrupted by other threads with a higher priority (for example another hardware interrupt asserting, which just clears the interrupt and queues another lower-priority event handler).

The stuff that happens at the driver/kernel level are the most time critical things, there is a place between user-space priority and driver/kernel-level where other tasks which need to be completed in order for the user-space things to run - these can normally be handled with timers and yields.  Meaning run some code, and when you want to "wait", a timer is set for when the code should resume, then we release the paused thread CPU to do other work with a yield.  When the timer fires (or shortly after), the yielded code resumes - so the programmer gets the basic behavior of a sleep, but without stalling a CPU core for that whole time period.  A 1-second "sleep" on even a 5MHz core, is an eternity!!!

It takes a bit of consideration to get in the habit of assuming one's code is not the only thing running on a machine, and practice to know where to split one's code - especially when the only thing one is aware of, is the code being looked at in that moment - however on modern context switching threaded OSes, we have to be careful to not assume our code is the most important thing happening at any moment.
Quote 0 0