B4R Tutorial Controllino Mega PLC Examples

Attached is a B4R project (using inline C) to manipulate the ports of the Controllino Mega. In this project we do the following:
1. Toggle the OVL (over voltage) LED of the PLC between on/off every second (Port E) via a timer
2. Toggle pins DO12 to DO19 (all on Port L) every second via the timer - split into 2 x groups of 4 DO's i.e DO 12, 13, 14, 15 switches ON/OFF immediately at the same time and DO 16, 17, 18, 19 switches OFF/ON immediately at the same time.

(2) above is useful if one wants to directly drive for eg a 24V DC motor in forward and reverse direction. It allows one to use for eg 2 (or more) x DO pins simultaneously to increase the available current to a DC motor. The pins need to be from the same PORT. See this posting about port manipulation (https://www.controllino.com/port-manipulation/)

20240330_131536.jpg


20240330_131547.jpg


Sample Code:

Port Manipulation:
Sub Process_Globals
    Public Serial1 As Serial
   
    Dim t As Timer
   
    Dim cnt As Int
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    cnt = 0
    t.Initialize("t_tick", 1000)
    RunNative("initDDRL", 0xff)
    RunNative("initDDRE", 0x80)
   
    t.Enabled = True
   
End Sub

Sub t_tick

    If cnt Mod 2 = 0 Then
        RunNative("ClearPortL", 0x00)
        RunNative("SetPortL", 0x0f)
        RunNative("SetPortE", 0x80)
    Else
        RunNative("ClearPortL", 0x00)
        RunNative("SetPortL", 0xf0)
        RunNative("ClearPortE", 0x7f)
    End If
   
    cnt = cnt + 1
End Sub

#if C
#define clear_port_bit(reg, bitmask) *reg &= ~bitmask
#define set_port_bit(reg, bitmask) *reg |= bitmask
#define pulse_high(reg, bitmask) clear_port_bit(reg, bitmask); set_port_bit(reg, bitmask);
#define pulse_low(reg, bitmask) set_port_bit(reg, bitmask); clear_port_bit(reg, bitmask);
#define clear_port(port, data) port &= data
#define set_port(port, data) port |= data

void initDDRA (B4R::Object* o) {
     DDRA |= o->toULong();
}

void initDDRB (B4R::Object* o) {
     DDRB |= o->toULong();
}

void initDDRC (B4R::Object* o) {
     DDRC |= o->toULong();
}

void initDDRD (B4R::Object* o) {
     DDRD |= o->toULong();
}

void initDDRE (B4R::Object* o) {
     DDRE |= o->toULong();
}

void initDDRF (B4R::Object* o) {
     DDRF |= o->toULong();
}

void initDDRG (B4R::Object* o) {
     DDRG |= o->toULong();
}

void initDDRH (B4R::Object* o) {
     DDRH |= o->toULong();
}

void initDDRJ (B4R::Object* o) {
     DDRJ |= o->toULong();
}

void initDDRK (B4R::Object* o) {
     DDRK |= o->toULong();
}

void initDDRL (B4R::Object* o) {
     DDRL |= o->toULong();
}

void ClearPortA (B4R::Object* o) {
  clear_port(PORTA, o->toULong());
}
void SetPortA (B4R::Object* o) {
  set_port(PORTA, o->toULong());
}

void ClearPortB (B4R::Object* o) {
  clear_port(PORTB, o->toULong());
}
void SetPortB (B4R::Object* o) {
  set_port(PORTB, o->toULong());
}

void ClearPortC (B4R::Object* o) {
  clear_port(PORTC, o->toULong());
}
void SetPortC (B4R::Object* o) {
  set_port(PORTC, o->toULong());
}

void ClearPortD (B4R::Object* o) {
  clear_port(PORTD, o->toULong());
}
void SetPortD (B4R::Object* o) {
  set_port(PORTD, o->toULong());
}

void ClearPortE (B4R::Object* o) {
  clear_port(PORTE, o->toULong());
}
void SetPortE (B4R::Object* o) {
  set_port(PORTE, o->toULong());
}

void ClearPortF (B4R::Object* o) {
  clear_port(PORTF, o->toULong());
}
void SetPortF (B4R::Object* o) {
  set_port(PORTF, o->toULong());
}

void ClearPortG (B4R::Object* o) {
  clear_port(PORTG, o->toULong());
}
void SetPortG (B4R::Object* o) {
  set_port(PORTG, o->toULong());
}

void ClearPortH (B4R::Object* o) {
  clear_port(PORTH, o->toULong());
}
void SetPortH (B4R::Object* o) {
  set_port(PORTH, o->toULong());
}

void ClearPortJ (B4R::Object* o) {
  clear_port(PORTJ, o->toULong());
}
void SetPortJ (B4R::Object* o) {
  set_port(PORTJ, o->toULong());
}

void ClearPortK (B4R::Object* o) {
  clear_port(PORTK, o->toULong());
}
void SetPortK (B4R::Object* o) {
  set_port(PORTK, o->toULong());
}

void ClearPortL (B4R::Object* o) {
  clear_port(PORTL, o->toULong());
}
void SetPortL (B4R::Object* o) {
  set_port(PORTL, o->toULong());
}

#end if

Port Groups:
MEGA:
1st group: D0, D1, D3
2nd group: D2
3rd group: D4, D5, D6, D7
4th group: D8, D9, D10, D11
5th group: D12, D13, D14, D15, D16, D17, D18, D19
6th group: D20, D21, D22
7th group: D23
 

Attachments

  • ControllinoPorts.zip
    1.2 KB · Views: 34

Johan Schoeman

Expert
Licensed User
Longtime User
A B4R project using the onboard RTC of the Controllino Mega. Library and sample project attached.

Sample code to set and read the date/time and to also set an alarm.

Sample Code:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 300
#End Region
'Ctrl+Click to open the C code folder: ide://run?File=%WINDIR%\System32\explorer.exe&Args=%PROJECT%\Objects\Src

Sub Process_Globals
    
    Public Serial1 As Serial
    Dim aDay , aWeekDay, aMonth, aYear, aHour, aMinute, aSecond As Byte

    Dim t As Timer
    
    Dim p As Pin                  'will be using it for cont_rtc.CONTROLLINO_D0
    
    Dim p2 As Pin                 'RTC Alarm Interrupt (cont_rtc.CONTROLLINO_RTC_INTERRUPT)
    
    Dim p3 As Pin
    
    Dim cont_rtc As CONTROLLINO   'Get an instance of the CONTROLLINO library
    

End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    
    
    
    cont_rtc.Init                  'initialize the RTC
    
    p2.AddListener("RTCInterrupt_StateChanged")
    
    cont_rtc.ClearRTCAlarm         'clear all RTC alarms
    
    t.Initialize("t_tick", 1000)   'timer runs every 1 second
    
    p.Initialize(cont_rtc.CONTROLLINO_D0, p.MODE_OUTPUT)    'it will just blink the D0 LED in the timer
    
    p3.Initialize(cont_rtc.CONTROLLINO_D16, p3.MODE_OUTPUT)
    
    aDay = 25
    aWeekDay = 1
    aMonth = 2
    aYear = 24
    aHour = 18
    aMinute = 26
    aSecond = 0
    
    'Method 1 to set date and time
    'cont_rtc.SetTimeDate(aDay, aWeekDay, aMonth, aYear, aHour, aMinute, aSecond)

    'Method 2 to set date and time
    Dim datum() As Byte = "Mar 29 2024"
    Dim tyd() As Byte = "14:58:45"
    Dim err As Byte = cont_rtc.SetTimeDateStrings(datum, tyd)
    Log("err = ", err)
    
    p2.Initialize(cont_rtc.CONTROLLINO_RTC_INTERRUPT, p2.MODE_INPUT_PULLUP)    'pull the CONTROLLINO_RTC_INTERRUPT pin HIGH (see the hover help - it is pin 73)
    Dim alarmset As Byte = cont_rtc.SetRTCAlarm(14, 59)    'set the time that the alarm should be triggered (RTC time - it is not relative to current RTC time but actual time)
    If alarmset = 0 Then
        p3.DigitalWrite(True)                             'if the alarm has been set then light up the LED of pin CONTROLLINO_D16
    End If
    t.Enabled = True                     'enable the timer
    
End Sub

Sub t_tick
    
    p.DigitalWrite(Not(p.DigitalRead))    'blink the LED of D0
    
    cont_rtc.PrintTimeAndDate             'prints the date and time to the log (eg 29/3/24   11:14:28)
    
    aDay = cont_rtc.GetDay                'get the Day of the month
    aWeekDay = cont_rtc.GetWeekDay        'get the weekday of the date (0 = Sun, 6 = Sat)
    aMonth = cont_rtc.GetMonth            'get the month (1 to 12)
    aYear = cont_rtc.GetYear              'get the year (it returns the last 2 digits eg 24 for 2024)
    aHour = cont_rtc.GetHour              'get the hour (1 to 12 or 0 to 23)
    aMinute = cont_rtc.GetMinute          'get the minutes (0 to 59)
    aSecond = cont_rtc.GetSecond          'get the seconds (0 to 59)
    
    Log("aDay = " , aDay)                 'log what was read from the RTC
    Log("aWeekday = " , aWeekDay)
    Log("aMonth = " , aMonth)
    Log("aYear = " , aYear)
    Log("aHour = " , aHour)
    Log("aMinute = " , aMinute)
    Log("aSecond = " , aSecond)
    Log(" ")
    
    If p2.DigitalRead = False Then       'P2 was pulled HIGH when initialized - Once the alarm trigger is will be pulled LOW
        cont_rtc.ClearRTCAlarm           'Clear the alarm after it was triggered
        Log("Alarm triggered!")          'Log Alaram Triggered
        p3.DigitalWrite(False)           'switch off the LED of pin CONTROLLINO_D16 when the alarm was triggered
        
        If aMinute < 59 Then
            t.Enabled = False
            Delay(200)
            cont_rtc.SetRTCAlarm(aHour, aMinute + 1)
            t.Enabled = True
            p3.DigitalWrite(True)               'switch on the LED of CONTROLLINO_D16
        Else if aMinute = 59 Then
            t.Enabled = False
            Delay(200)
            cont_rtc.SetRTCAlarm(aHour + 1 , 0)    'switch on the LED of CONTROLLINO_D16
            t.Enabled = True
            p3.DigitalWrite(True)
        End If
        
    End If

    
End Sub

Sub RTCInterrupt_StateChanged (State As Boolean)
    Log("!!!!!!!!!!!!!")
    Log("GOTCHA")
    Log("!!!!!!!!!!!!!")
    Log(" ")
    
End Sub
 

Attachments

  • rControllino.zip
    105.3 KB · Views: 26
  • ControllinoNew.zip
    2.2 KB · Views: 22

Johan Schoeman

Expert
Licensed User
Longtime User
Connected a HC-05 Bluetooth module to the header pins of the Controllino Mega and can control the Mega from an old B4A app that I have done to control my DSTV decoder via Bluetooth and IR.

B4R code:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 300
#End Region

Sub Process_Globals
    Public Serial1 As Serial
    Private SoftwareSerial1 As SoftwareSerial
    Private astream As AsyncStreams
    Dim cont As CONTROLLINO
    
    Dim r0 As Pin

End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    r0.Initialize(cont.CONTROLLINO_R0, r0.MODE_OUTPUT)
    
    SoftwareSerial1.Initialize(9600, cont.CONTROLLINO_D9, cont.CONTROLLINO_D10) 'software serial port on pins 10 and 11
    astream.Initialize(SoftwareSerial1.Stream, "astream_NewData", Null)
End Sub

Sub AStream_NewData (Buffer() As Byte)
    
    For i = 0 To Buffer.Length - 2 Step 2
        If Buffer(i + 1) = 1 Then

            r0.DigitalWrite(True)
            
            Dim b() As Byte = "R0 is now ON"
            Dim c() As Byte = CRLF
            astream.Write(b)
            astream.Write(c)
            
            
        else If Buffer(i + 1) = 2 Then

            r0.DigitalWrite(False)
            
                        
            Dim b() As Byte = "R0 is now OFF"
            Dim c() As Byte = CRLF
            astream.Write(b)
            astream.Write(c)

        End If       
    Next
    
End Sub

20240407_154217.jpg
 

Johan Schoeman

Expert
Licensed User
Longtime User
Using this Github library with inline C code for PID control. It works very well
I am using a Controllino Mega (I have no more UNO's or Nano's) to PID control a 24VDC fan. On standard 5V Arduino boards you can replace the 24VDC fan with a 5VDC light bulb or with a LED/Resistor pair.

The Pot is used to set the Setpoint
The LDR is the feedback from the "plant"
The brightness of the LED is what is being controlled. I have added the fan to get a better understanding of the PID output.
So, the LED and the Fan follows the setpoint via the PID output and the PID controller drives the brightness of the LED until the LDR output matches the setpoint of the Pot.

Very educational to play around with the kp, ki, kd parameters and see what happens.
the 24VDC fan will only start spinning once there is sufficient DC voltage applied (PWM). Until such time it sings like a honey bee (from setpoint being 0 to whenever the fan starts running).
The LED must face directly to the LDR!
Placed some translucent film between the LDR and the LED and see how the LED brightness (and the fan) reacts.
Check the B4R log values - will give you a good idea of what is happening with the PID controller.
Change kp = 0.1, ki = 0.1, kd = 0.0001 and see how slow it reacts to reach the setpoint
Change kp to 2.0 and see how it oscillates.

Enjoy!


Sure you will figure it out - have attached a sample project and the PIDController.hpp file. Copy the latter to your Arduino library folder.

B4R code:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 300
#End Region
'Ctrl+Click to open the C code folder: ide://run?File=%WINDIR%\System32\explorer.exe&Args=%PROJECT%\Objects\Src

Sub Process_Globals
    Public Serial1 As Serial
    
    Dim t As Timer
    
    Dim kp, ki, kd As Double    'Used to set the kp, ki, kd parameters in the inline C code
    
    Dim setpnt As Int    'inline C code will set this value               
    Dim illum As Int     'inline C code will set this value
    Dim outpt As Int     'inline C code will set this value
    
    Dim cont As CONTROLLINO

End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    t.Initialize("t_tick", 10)
    
    kp = 0.4                         'the kp constant of the PID controller
    ki = 6.0                         'the ki constant of the PID controller
    kd = 0.0001                      'the kd constant of the PID controller
    
    RunNative("declareSetpointPin", cont.CONTROLLINO_A1)     'POT WIPER connected to pin 55
    
    RunNative("declareLDRPin", cont.CONTROLLINO_A0)          'LDR connected to pin 54
    
    RunNative("declareLEDPin", cont.CONTROLLINO_D0)          'LED connected to pin 2
    
    RunNative("declareFANPin", cont.CONTROLLINO_D1)          'FAN connected to pin 3 (24V PWM)
    
    RunNative("setup", Null)                                 'Call inline C Setup
    
    RunNative("setParameters", Null)                         'Call inline C setParameters
    
    t.Enabled = True

End Sub

Sub t_tick
    
    RunNative("loop", Null)                                   'Call inline C Loop
    Log("Setpoint = ", setpnt)                                'Log the Setpoint - it is set by the inline C code
    Log("Ilum = ", illum)                                     'Log the Illumination value from the LDR - it is set by the inline C code
    Log("Output = ", outpt)                                   'Log the Output - it is set by the inline C code and will drive the brighness of the LED that is aimed at the LDR
    
    Log(" ")
    
End Sub

#If C

#include <PIDController.hpp>

const long A = 1000; //Resistance in darkness in KΩ
const int B = 15; //Light resistance (10 Lux) in KΩ
const int Rc = 10; //Calibration resistance in KΩ

int PIN_LDR;
int PIN_LED;
int PIN_POT;
int PIN_FAN;

PID::PIDParameters < double > parameters(0.0, 0.0, 0.0);
PID::PIDController < double > pidController(parameters);

void setup(B4R::Object* o)
{
    Serial.begin(115200);
    pinMode(PIN_LED, OUTPUT);
    pinMode(PIN_FAN, OUTPUT);

    pidController.Input = analogRead(PIN_LDR);
    pidController.Setpoint = analogRead(PIN_POT);

    pidController.TurnOn();
}

float readLDR()
{
    float mean = 0;
    for (auto i = 0; i < 100; i++) {
        auto V = (float) analogRead(PIN_LDR);
        mean += (V * A * 10) / (B * Rc * (1024 - V));
    }
    mean /= 100;
    return mean;
}

void loop(B4R::Object* o)
{
    auto ilum = readLDR();
  
    auto setpoint = analogRead(PIN_POT);
    pidController.Setpoint = setpoint / 4;
     b4r_main::_setpnt = setpoint / 4;
    
    pidController.Input = ilum;
     b4r_main::_illum = ilum;

    pidController.Update();

    auto output = pidController.Output;
    b4r_main::_outpt = (int) output;

    analogWrite(PIN_LED, (int) output);
    analogWrite(PIN_FAN, (int) output);   
    
}

void declareSetpointPin(B4R::Object* o) {
     PIN_POT = (Byte)o->toULong();
}

void declareLDRPin(B4R::Object* o) {
     PIN_LDR = (Byte)o->toULong();
}

void declareLEDPin(B4R::Object* o) {
     PIN_LED = (Byte)o->toULong();
}

void declareFANPin(B4R::Object* o) {
     PIN_FAN = (Byte)o->toULong();
}

void setParameters(B4R::Object* o) {
    PID::PIDParameters < double > parameters1(b4r_main::_kp, b4r_main::_ki, b4r_main::_kd);
    pidController.SetTunings(parameters1);
}


#End If
B4X:



20240501_124746.jpg
 

Attachments

  • PIDController.zip
    2.6 KB · Views: 4
  • ControllinoPID.zip
    1.9 KB · Views: 6
  • Arduino-PIDController-Library-main.zip
    21.9 KB · Views: 6
Top