Crimson 3.1 C Programming

You put start but I know you meant stop for case 4. :)
That is what I meant, if both can't be right at the same time then it isn't a problem with how to code it, the problem is in the control definition. You either have to give Case [1] or Case [4] priority, or you have to redefine the problem.




Actually it can be programmed, it just needs to keep track of the two cases independently, because the two inputs are independent.


Call each pump run conditions an "independent start alarm:" Tstart (run because of temperature); Mstart (run because of mositure). If either independent start alarm is on (Tstart OR/|| Mstart), then ring the master alarm i.e. start the pump.



Call each pump stop conditionand "independent stop alarm: Tstop; Mstop. If both indep. stop alarms are off (Tstop AND/&& Mstop), turn off the master alarm i.e. stop the pump.


For this to work an important condition icans that Tstart and Tstop and never both be true at the same time, and the same is true for Mstart and Mstop; this is guarantted by the the High setpoints being above the Low setpoints, plus the mathematical axiom [if A>B and B>C, then A>C].


Also, because the high and low setpoints are separate, then </> does not matter vs. <=/>=.
 
Update

I have since worked on this problem, but the customer changed how he wants it to work, wants all field nodes in a group to control pumps in a group, AND also have individual overrides to turn on and off individual pumps at will.

I will provide the entire cd31 file and some code here.
ONTick
Code:
PumpsA_F.PumpAHOA();
PumpsA_F.PumpAHA();
PumpsA_F.PumpBHOA();
PumpsA_F.PumpBHA();
PumpsA_F.PumpCHOA();
PumpsA_F.PumpCHA();
PumpsA_F.PumpDHOA();
PumpsA_F.PumpDHA();
PumpsA_F.PumpEFHOA();
PumpsA_F.PumpEFHA();
PumpsK1_S4.PumpK1HOA();
PumpsK1_S4.PumpK1HA();
PumpsK1_S4.PumpK2HOA();
PumpsK1_S4.PumpK2HA();
PumpsK1_S4.PumpS1HOA();
PumpsK1_S4.PumpS1HA();
PumpsK1_S4.PumpS2HOA();
PumpsK1_S4.PumpS2HA();
PumpsK1_S4.PumpS3HOA();
PumpsK1_S4.PumpS3HA();
PumpsK1_S4.PumpS4HOA();
PumpsK1_S4.PumpS4HA();

PumpsA_F_FlowDelays.Pump_A_Flow_Delay();
PumpsA_F_FlowDelays.Pump_B_Flow_Delay();
PumpsA_F_FlowDelays.Pump_C_Flow_Delay();
PumpsA_F_FlowDelays.Pump_D_Flow_Delay();
PumpsA_F_FlowDelays.Pump_E_F_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_K1_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_K2_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_S1_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_S2_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_S3_Flow_Delay();
PumpsK1_S4_FlowDelays.Pump_S4_Flow_Delay();
Pump_AHA
Code:
if   	(Pump_A.OR == 1)
	Pump_A.DOutput_1 = 1;
		
else if (Pump_A.OR == 2)
	PumpsA_F.PumpABC();
	
else if (Pump_A.OR == 0)
	Pump_A.DOutput_1 = 0;

Pump_AHOA
Code:
if   	(Pump_A.WOR == 1)
	Pump_A.DOutput_2 = 1;
		
else if (Pump_A.WOR == 2)
	PumpsA_F.PumpABCWater();
	
else if (Pump_A.WOR == 0)
	Pump_A.DOutput_2 = 0;
PumpABC
Code:
if	((Field_A.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_A.Temp_Low_Mon >= Targets.High_Heat_SetPoint)||
	(Field_B.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_B.Temp_Low_Mon >= Targets.High_Heat_SetPoint)||
	(Field_C.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_C.Temp_Low_Mon >= Targets.High_Heat_SetPoint))
	{PumpsA_F.Temp_OnABC();
	 return;}

else if	((Field_A.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_A.Temp_Low_Mon <= Targets.Low_Heat_SetPoint)&&
	(Field_B.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_B.Temp_Low_Mon <= Targets.Low_Heat_SetPoint)&&
	(Field_C.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_C.Temp_Low_Mon <= Targets.Low_Heat_SetPoint))
	PumpsA_F.Temp_OffABC();

PumpABCWater
Code:
if	((Field_A.Irrometer >= Targets.Irrometer_SetPoint_High)||
	(Field_B.Irrometer >= Targets.Irrometer_SetPoint_High)||
	(Field_C.Irrometer >= Targets.Irrometer_SetPoint_High))
	{PumpsA_F.Water_OnABC();
	 return;}
		
	
else if ((Field_A.Irrometer <= Targets.Irrometer_SetPoint_Low)&&
	(Field_B.Irrometer <= Targets.Irrometer_SetPoint_Low)&&
	(Field_C.Irrometer <= Targets.Irrometer_SetPoint_Low))
	PumpsA_F.Water_OffABC();
PumpsA_F.Temp_OnABC
Code:
Pump_A.DOutput_1 = 1;
Pump_B.DOutput_1 = 1;
Pump_C.DOutput_1 = 1;

JKTemp_Alarms.ABC_Temp_Alarm = 1;

PumpsA_F.Temp_OffABC
Code:
Pump_A.DOutput_1 = 0;
Pump_B.DOutput_1 = 0;
Pump_C.DOutput_1 = 0;

JKTemp_Alarms.ABC_Temp_Alarm = 0;

PumpsA_F.Water_OnABC
Code:
Pump_A.DOutput_2 = 1;
Pump_B.DOutput_2 = 1;
Pump_C.DOutput_2 = 1;

JKWater_Alarms.ABC_Water_Alarm = 1;

PumpsA_F.Water_OffABC
Code:
Pump_A.DOutput_2 = 0;
Pump_B.DOutput_2 = 0;
Pump_C.DOutput_2 = 0;
JKWater_Alarms.ABC_Water_Alarm = 0;

I am looking to be able to Overide any pump to ON or OFF without affecting the Automation part. As you can see the HOA can turn the output on and off but the automation turns it back on for the other conditions...
 
I have messed around trying this, I dont think it will work. but maybe closer.
Code:
if   	(Pump_A.OR == 1)
	{Pump_A.DOutput_1 = 1;
	return;}
		
[COLOR="Red"]else if ((Pump_A.OR == 2)||(Pump_B.OR == 2)||(Pump_C.OR == 2))[/COLOR]
	{
if	((Field_A.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_A.Temp_Low_Mon >= Targets.High_Heat_SetPoint)||
	(Field_B.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_B.Temp_Low_Mon >= Targets.High_Heat_SetPoint)||
	(Field_C.Temp_Low_Mon <= Targets.Low_Frost_SetPoint)||
	(Field_C.Temp_Low_Mon >= Targets.High_Heat_SetPoint))
	{Pump_A.DOutput_1 = 1;
	Pump_B.DOutput_1 = 1;
	Pump_C.DOutput_1 = 1;
	JKTemp_Alarms.ABC_Temp_Alarm = 1;
	 return;}

else if	((Field_A.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_A.Temp_Low_Mon <= Targets.Low_Heat_SetPoint)&&
	(Field_B.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_B.Temp_Low_Mon <= Targets.Low_Heat_SetPoint)&&
	(Field_C.Temp_Low_Mon >= Targets.High_Frost_SetPoint)&&
	(Field_C.Temp_Low_Mon <= Targets.Low_Heat_SetPoint))
	{Pump_A.DOutput_1 = 0;
	Pump_B.DOutput_1 = 0;
	Pump_C.DOutput_1 = 0;
	JKTemp_Alarms.ABC_Temp_Alarm = 0;}
	}
		
else if (Pump_A.OR == 0)
	{Pump_A.DOutput_1 = 0;
	 return;}
 
(1) This is not hard.


(1.1) So why is this being done in C, instead of a PLC environment (e.g. ST or FB or Ladder)?


(2) If it needs to be done in C, then it CAN ONLY be done if some state variables (boolean bits) can be saved from one scan to the next.


(3) If the Crimson C environment is so constrained that all memory, apart from the physical I/O tags, is allocated on the fly for each scan, then this CANNOT be done in the Crimson C environment.


I find it very hard to believe that (3) above is true.


There needs to be a way to determine the specific reason, among all possibilities (e.g. temperature or moisture), any pump was on at the end of the last scan.


Simply put, several steps need to be executed for each pump:


  • Is the temperature above the HI-HI alarm? If yes, then latch the persistent bit pump_n_temp_on = 1
    • otherwise do nothing else to pump_n_temp_on in this step
    • so this step will never set pump_n_temp_on to 0
  • Is the temperature out of (below) the HI alarm? If yes, then unlatch the persistent bit pump_n_temp_on = 0
    • otherwise do nothing else to pump_n_temp_on in this step
    • so this step will never set pump_n_temp_on to 1
  • Is the moisture below the LO-LO alarm? If yes, then latch the persistent bit pump_n_moist_on = 1
    • otherwise ... this step will never set pump_n_moist_on to 0
  • Is the moisture out of (above) the LO alarm? If yes, then unlatch the persistent bit pump_n_moist_on = 0
    • otherwise ... 1
The four cases above can be coded with if{}elseif{} code, because the unused else case is to leave those persistent bits as-is. They do not have to be coded with if-elseif, but that is probably the most clear.

At this point the pump on/off state is determined by four bits:

  • pump_n_temp_on
    • Latched above, or unlatched above, or left as-is from the previous scan
  • pump_n_moist_on
    • Latched above, or unlatched above, or left as is from the previous scan
  • pump_n_override_on
    • 1 to force pump on
  • pump_n_override_off
    • 1 to force pump off
And the final step is to set the pump state based on those four bits, two of which may not have changeed from the previous scan.
pump_n_on = (pump_n_temp_on || pump_n_moist_on || pump_n_override_on) && (!pump_n_override_off);
That is it; in the code above, the [off override] overrides the [on override]; that is a choice to make as it has to be either that way or vice versa, coding the opposite case is not complicated.

Again, the pump_n_temp_on and pump_n_moist_on bits must persist to the next scan, no matter what the previous logic does to the pump_n_on bit.
 
I think I get it, I use this logic in my temp and moisture code already.
Code:
pump_n_on = (pump_n_temp_on || pump_n_moist_on || pump_n_override_on) && (!pump_n_override_off);

I will try this out. Thank you.

https://update.redlion.net/c31/update/c31-gold-3121-0.exe
Crimson Software is free and the only software I can use on a CR3000 HMI
Like I said I am not proficient in C code and have tried other logic using tags.
but again I appreciate everyones input and advice.
 
did you try the static declaration? e.g.


Code:
static int pump_1_moist_on;
That should (may?) ensure that pump_1_moist_on will persist in the C code from scan to scan.


Once you have that, rest is easy.


Btw, I think I was wrong to suggest if-elseif for the latch-unlatch. A start-stop circuit-equivalent should be just as clear:
pump_n_moist_on = ((moisture_n_measurement < moisture_n_LOW_LOW) || pump_n_moist_on) && (moisture_n_measurement < moisture_n_LOW);
You can decide for you own process whether those should be < (less than) or <= (less than or equal to).
 
Last edited:
I tried just copy and paste the Static declaration but it does not compile.

and yes,
tempA low =pump123ON OR moistureA high =pump123ON OR
tempB low =pump123ON OR moistureB high = pump123ON OR
tempC low =pump123ON OR moistureC high = pump123ON

my code explains it better.
 
this compiles but I dont know how to call it

int pump_1_moist_on;


It's scope will depend where you put it. for example, this is what a typical C program might look like:


int pump_1; /* Global variable, state persists as long as program is running */

A_function(arg1,arg2) {

int pump_2; /* Local variable, local to A_function, created fresh each time A_function runs */
pump_2 = C_function(2,arg1,arg2);
either_pump = pump_1 || pump_2;
...
}

B_function(3,arg3,arg4) {

int pump_3; /* Local variable, local to B_function, created fresh each time B_function runs */
pump_3 = C_function(3,arg3,arg4);
either_pump = pump_1 || pump_3;
...
}
B_function cannot see pump_2, and A_function cannot see pump_3, but both A_function and B_function can see pump_1.


Looking at the Crimson documents, it seems like that is not the way its C-like dialect works: each variable is local to its program unit (function).


Perhaps if the code can control (read and write to) bits somewhere else in the hardware (HMI? device?), e.g. the visibility or color of a button on a screen, then you could use those bits as proxies for global, persistent information. Another way would be to write the state to a database; if it only happened once every few seconds then it might not be too inefficent.
 
Last edited:
Here it is: https://www.redlion.net/sites/defau...Manual English (LP1044D) 12MB_0.pdf#G8.961164


That is the chapter on tags, and one type of tags is internal tags i.e. it represents "data elements" within the Crimson device. They can even be retentive and keep their value through a Crimson device power cycle. You will want to have your routines

  • called with those Crimson-internal tags as arguments, and
  • change Crimson-internal values as a result of the logic.
 
I haven't been following this thread for awhile but I can confirm that drbitboy is correct... variables defined within a Crimson program are retained only for one execution of the program. There are a few ways around this, the most straightforward is to just create data tags and reference them from your programs. Tags will retain their values indefinitely as long as power is applied, and can be set to Retentive to keep them across power cycles.

Another option would be to make your main program run continually. As long as the program never exits, the variables defined within it will keep their states. Here's one way to create an infinite loop:
Code:
int Var1
int Var2

while(true){

  [program code here]

}
Keep in mind that any variable declarations need to be outside the loop, or else they will be continually wiped out. Since it is continually looping, you would only need to trigger it once at startup (don't use On Tick).

Important: To keep this infinitely-executing program from locking up the processor, you need to make it a background task. Any programs called by a background program will also run in the background as long as their execution task is "same as caller".

background.png
 
Another option would be to make your main program run continually. As long as the program never exits, the variables defined within it will keep their states. Here's one way to create an infinite loop:...

Important: To keep this infinitely-executing program from locking up the processor, you need to make it a background task. Any programs called by a background program will also run in the background as long as their execution task is "same as caller".


Sweet hack!
 

Similar Topics

Hey everyone! Need a little help with some scripting in Crimson 3.0. With this first script I am just trying set a local variable string based...
Replies
8
Views
2,348
Hi, I have a Red Lion E3 io counting output from weld cells, connected to a PTV station for the visuals. Our number of weld cells is set to...
Replies
2
Views
1,551
Good day I'm looking for a way to let crimson 3 monitor 3 temperatures from modbus then i want crimson to "GotoPage" when these 3 temps have...
Replies
3
Views
1,625
Hello all, First time here, So I want to take an analog input and total it over time. For example, the input would be in ml/s and I would like...
Replies
1
Views
1,482
I am trying to program a G3 through Crimson 3 if (Part_1_Online) GotoPage(MAIN_369); else if (Part_2_Online) GotoPage(MAIN_369CR); else if...
Replies
11
Views
2,562
Back
Top Bottom