Automation Project, code improvement and help.

kw0me

New Member
Long time no squeak!

So I decided to build an automation project for work. Mainly because I'm sick of seeing the water tank overflow and its a pain in the butt when it runs out.

The goal is to monitor a tanks water level using a sensor with a 4-20ma output. use this data to control a valve, send tank level in a percentage, valve state and trigger an alarm if the tank is not filling while the valve is open.

I am using a pair of HC-12's for communication
a simple LCD with a picaxe serial backpack
an "off the shelf " relay board to run the valve

The water sensor is a bit flash but its work supplied and runs 4-20ma
not sure if i go the 250ohm route and get 1-5v or go with a "ti RCV420jp" and get full 0-5v

I have written code that works and does what its meant to do. But I need some help with it.
So I readadc10 of a pin into w0 run it through a few if statements then bintoascii to send it to the hc-12 and out.
i need to set a 0% and 100% range in the coding and have it display as a percentage. at the moment I'm using a 10k pot to simulate the signal for testing so i can run the full range.

Here is the code for you to have a read through. I am still quite green to all this so if you can suggest or see any areas of improvement they would be well recived.

Code:
TANK UNIT
#picaxe 08m2

;symbol spare 	= C.0
symbol tx	      = C.1
symbol level      = C.2
;symbol spare	= C.3
symbol valve 	= C.4
;symbol spare     = C.5

Init:
Low valve

Main:
readadc10 Level,w0

bintoascii w0, b2,b3,b4,b5,b6

if w0 > 1000 then
	low valve
      b10 = 0
	end if
if w0 < 500 then
	high valve
	b10 = 1
      end if
if w0 > 400 then
	b11 = 0
	end if
If w0 < 400 then
	b11 = 1
end if

serout tx,t2400,("AAA",b3,b4,b5,b6,b10,b11)

goto main
Code:
LCD DISPLAY
#picaxe 08m2

;symbol spare 	= C.0
symbol tx	      = C.1
symbol lcd        = C.2
;symbol spare	= C.3
symbol alarm 	= C.4
;symbol spare     = C.5

Init:
Pause 500
serout lcd,n2400,(254,1)
pause 30
serout lcd,n2400,(254,128)
serout lcd,n2400,("    No  Link    ")
low alarm

Startup:
serin tx,t2400,("AAA")

serout lcd,n2400,(254,1)
pause 30

Main:
serin [500,Init], tx,t2400,("AAA"),b3,b4,b5,b6,b10,b11

if b11 = 0 then
	low alarm
	end if
If b11 = 1 then
	high alarm
      end if
if b10 = 0 then
	goto Closed
      end if
If b10 = 1 then
	goto Open
	end if

Open:
serout lcd,n2400,(254,128)
serout lcd,n2400,("Tank Level:%",b3,b4,b5,b6)

serout lcd,n2400,(254,192)
serout lcd,n2400,("Valve: Open     ")

goto main

Closed:
serout lcd,n2400,(254,128)
serout lcd,n2400,("Tank Level:%",b3,b4,b5,b6)

serout lcd,n2400,(254,192)
serout lcd,n2400,("Valve: Closed   ")

goto main
 

techElder

Well-known member
Looks like you are using SYMBOLs appropriately to assign variable names to inputs / outputs. That is always helpful in describing your program to others and will help a year from now when you revisit this project or change processors.

Certainly expand your SYMBOL assignments to those variables used to store and calculate data. "sensorOutput" is more descriptive than "w0".

The same thing applies to your constants. "highWaterLevel" means more in your program code than simply saying "1000". Define it like this: "symbol highWaterLevel = 1000". This will have the effect of placing all of your constants in one place in your program code (near the top) instead of being sprinkled all over the place. When you have lots of code and lots of constants, you will appreciate this method of coding.
 

darb1972

Senior Member
Hi

Welcome to the forum.

I am not sure I understand your "spare" symbol statements, even though they are "remmed" out and are not part of the program. You could probably leave them out all together. Redudes clutter. Not such a bad thing in a small program, but wait until you create something much bigger.

I agree with Tex on the constants. Furthermore, I'd suggest a couple of other refinements. As for the "Tank" program, I'd probably encourage you to give the variable W0 a name (for future reference) and maybe look at using the "Select Case" command to test/validate the various possible values of W0. A similar thing could be done on the "Display" side of things with regards to B11. Name the variable using the symbol statement and use the "Select Case" command to assess the possible outcomes.

Anyway, that's just my thoughts. Plenty of gurus on this forum with far greater knowledge and wisdom than I.
 

MartinM57

Moderator
More SYMBOLs and more "comments" (start the line with the ; character or #REM or blocks of comments with #REM/#ENDREM)..
...the more the code "self-comments" itself by using SYMBOLs etc is good
...but standalone comments will be useful to you even now when putting them in (to confirm that the code does what you want), and useful to reviewers and yourself in the future when you forget what it's doing. Also add a version number as a comment, revision history (or a space for one even if there isn't one (yet) and date(s)
 

AllyCat

Senior Member
Hi,

... runs 4-20ma .. not sure if i go the 250ohm route and get 1-5v or go with a "ti RCV420jp" and get full 0-5v
Welcome also. On the "hardware" side, I'd use the "250 ohm route", or more likely 220 or 240 ohms to give some "headroom". It's not difficult to strip off the lower "1 volt" and any "overvoltage" values and scale the remaining ADC{10} value to 100%. There's an active thread at the moment on a similar topic. The RCV420jp seems to need a 10+ volt supply rail, so might create more problems than it solves.

But do you have an exact, stable 5 volt rail (or strictly 1.024 * 5 volts) on the "level-sensing" PICaxe? The default ADC reference is the "5 volt" supply rail, so the ADC is "ratiometric" (whether you use the RCV420jp or not). IMHO the simplest (hardware) solution is to use the PICaxe's internal "FVR" (2048 or 4096 mV Fixed Voltage Reference) for the ADC, and a fixed 100 or 200 ohm resistor in the "current loop". Then measure the "min" and "max" READADC10 values and store them in the PICaxe's EEPROM. A little maths, a SELECT .. CASE, and Bob's your uncle. ;)

Cheers, Alan.
 

kw0me

New Member
Some very good ideas to make the code easier to understand and make changes when needed. I have taken them on board and updated the code for both units. Here is the new "Tank unit" code.

Code:
;TANK UNIT
#picaxe 08m2

symbol TX                     = C.1
symbol Sensor               = C.2
symbol Valve                 = C.4
symbol SensorOutput       = w0
symbol ValveState           = b10
symbol AlarmState          = b11
symbol TankFull              = 1000
symbol TankLow             = 500
symbol TankAlarm          = 400

Init:
low Valve

Main:
readadc10 Sensor,SensorOutput

bintoascii SensorOutput, b2,b3,b4,b5,b6

if SensorOutput > TankFull then
	low Valve
      ValveState = 0
	end if
if SensorOutput < TankLow then
	high Valve
	ValveState = 1
      end if
if SensorOutput > TankAlarm then
	AlarmState = 0
	end if
If SensorOutput < TankAlarm then
	AlarmState = 1
end if

serout TX,t2400,("AAA",b3,b4,b5,b6,ValveState,AlarmState)

goto main
In regards to the hardware:

Thanks Alan for the 10v pickup on the IC!
I hadn't fully read through the data sheet yet. I was thinking of keeping it simple and just using a resistor this has just confirmed that plan.

With the resistor I'm running a 240ohm at the moment just for testing but Alan, you have just opened more doors I didn't know about yet!

I'm going to say I have a stable/constant 5v supply as there is 24v available in pump control board (where the tank unit will be mounted) and i have a regulator unit dropping that to 5v for the picaxe and hc-12 then the tank sensor will be run by 24v with a ground tied in to the picaxe so it can read the 1-5v from the resistor bridge.

In regards to the code:

I did have a look at the current post about scaling adc to come out as a percentage. I kind of understand whats going on there.

My problem is i have 0-1024 from the adc but the tanks %0 level will be around say 280 and %100 will be around 600 (these figures are very rough guesses)

I have never done any EEPROM work with picaxe most of my code has just been using variables and I have never done any SELECT .. CASE commands yet. I'm about to go do some more reading about the SELECT .. CASE commands and EEPROM use but a little bit of an example would be much appreciated as i wouldn't know where to begin! I'm guessing I would need to add some more symbols like:

symbol Tank100 = 424 ( based on sensor readings once installed)
symbol Tank0 = 280 ( based on sensor readings once installed)
symbol TankPercent = w1

and somehow work them into some math routine

Tank0 + Tank100 = w5

SensorOutput - w5 = w6

w6 / 100 = TankPercent

I'm Just throwing out how i think it would be... as i said a little example would really help!
 

techElder

Well-known member
SELECT CASE example

Good show on the change in variable names in your code. It quickly became recognizable!

One possibly important thing to keep track of is that you aren't checking for the case where the SensorOutput is " = " to your constant. Perhaps that adds some needed hysteresis to the level changes, but you'll have to decide if that's important to your application.

The following code sequence:

Code:
if SensorOutput > TankFull then
	low Valve
      ValveState = 0
	end if
if SensorOutput < TankLow then
	high Valve
	ValveState = 1
      end if
if SensorOutput > TankAlarm then
	AlarmState = 0
	end if
If SensorOutput < TankAlarm then
	AlarmState = 1
end if
... becomes a little clearer when using SELECT / CASE. Remember that once a CASE is found to be TRUE, the code below it executes and execution continues after the ENDSELECT statement. No further CASE tests are made. That is different than what happens in your IF/THEN block of code.

Code:
SELECT CASE SensorOutput

   CASE > TankFull 
      low Valve : ValveState = 0

   CASE < TankLow
      high Valve : ValveState = 1

   CASE > TankAlarm
      AlarmState = 0

   CASE < TankAlarm
      AlarmState = 1

ENDSELECT
... from the manual:

The 'select case' command may often be used in preference to nested 'if' commands or sequences of 'if' and 'elseif' commands to make code execution clearer or the source code more readable, particularly that with 'select case' the variable being tested only needs to be specified once. The choice of which to use is mainly a matter of personal preference and favoured programming style.
 

kw0me

New Member
-Tex

Thankyou for the input. From what you have explained with SELECT CASE I don&#8217;t think it would work properly with my setup.

The setpoints act as triggers. Below 50% the tank starts to fill and won&#8217;t stop until it gets to 100% so there is no need to worry if the sensoroutput is equal to either constant.

Using SELECT CASE looks like it would cancel out the alarm function that starts and stops on 40%. The CASE would get to the tanklow constant skip the alarm every time. That&#8217;s how I understand it would work but I could be wrong...
 

hippy

Technical Support
Staff member
Using SELECT CASE looks like it would cancel out the alarm function that starts and stops on 40%.
I haven't really studied it but that's possibly the case. One solution would be to have two separate SELECT-CASE, one for controlling filling, one to control alarms, or something like that ...

Code:
Select Case tankLevel
  Case >= TankFull : Gosub ValveClose
  Case <= TankLow  : Gosub ValveOpen
End Select

Select Case tankLevel
  Case > alarmLevel : Gosub AlarmOn
  Case < alarmLevel : Gosub AlarmOff
End Select
How it is actually implemented will depend on what one is doing. The point I think Texasclodhopper was trying to make is that SELECT-CASE can make things more easily understood.
 

AllyCat

Senior Member
Hi,

Also, it seems the RCV420jp needs a -5 volt rail, so certainly not worth the hassle. ;)

symbol Tank100 = 424 ( based on sensor readings once installed)
symbol Tank0 = 280 ( based on sensor readings once installed)
symbol TankPercent = w1
I have to confess that I normally use IF .. THEN .. ELSEs etc. in preference to SELECT .. CASE , because the exact behaviour of the IF .. is more self-evident.

Also, using the separate data EEPROM might be an unnecessary complication, since your symbol commands do much the same thing (without the need to specify values as WORDS each time). However, the READ and WRITE commands (into the EEPROM) do give the possibility to "self calibrate" the system, for example:
Code:
symbol TANKEMPTY = 0   ; The [U]address[/U] in EEPROM
symbol TANKFULL = 2    ; Address of the next word in EEPROM 
data TANKEMPTY,(0,2,0,2)          ; Initialise empty and full levels as 512 (Or closer values if known)

  read TANKEMPTY, WORD w5
  if TankLevel < w5 then
     write TANKEMPTY, WORD TankLevel 
  endif 
  read TANKFULL, WORD w5 
  if TankLevel > w5 then
     write TANKFULL, WORD TankLevel 
  endif
The above would automatically calibrate the scale after the tank had been fully emptied and filled (retained after any power-downs) and avoid future "errors" such as reporting "101%". But of course "101%" might be a correct reading if a control system failure had occurred (such as a stuck "float" valve), in which case a continuous self-calibration would be a bad idea.

To calculate percentage values you basically need to calculate the "scale range", by subtracting the minimum "raw" (or measured) level from the maximum raw level. Subtract the minimum level from the present measured value, then multiply by 100 (or a larger number if a decimal place is desired) and divide by the scale range.

However, be careful to avoid an overflow (of the numerical values) if the scale range exceeds 655. Personally, if the scale range is "known" (i.e. when writing/downloading the program), I prefer to use the ** operator mentioned by hippy in the other thread. This allows any numerical value to be scaled down (i.e. to a lower value) very accurately; or a trick I use to scale up (by a factor of up to *2) is: w1 = w1 ** CALIBRATIONFACTOR + w1.

Cheers, Alan.
 

kw0me

New Member
Nice demo on the auto calibrate Allan! I have another project that would work great on.

For this one it is better for me to code the actual values as a "safety" so if the valve does jam open and the display goes over 100% they know there is an issue.

Also the tank has a float to shut down the pump so it doesn't run dry. The 0% point will be set just above this. The 100% point will be around 90% actual volume so if the valve is stuck the tank wont over flow and the 10% will give them time to manually close it. This way it will gauge usable tank volume for production.

As for the variable scaling...not having much of a win. I can get it to do 0-99 of the full range. I don't know why it doesn't go to 100. I'm thinking maybe the pot I'm using is a bit out as the raw data goes 3-1023.

I also can't quite work out how to set the scale.

raw = 250 for 0%
raw = 670 for 100%

This is not the code, its just the constants I'm using to test run off the pot until i get the sensor installed and read the actual values. Which ill set on the final download when install the controller.

Code:
;TANK UNIT
#picaxe 08m2

symbol TX	        = C.1
symbol Sensor       = C.2
symbol Valve 	  = C.4
symbol SensorOutput = w0
symbol ValveState   = b10
symbol AlarmState   = b11
symbol TankPerc     = w10
symbol TankConv     = w8
symbol TankFull     = 670
symbol TankEmpty    = 250
symbol TankLow      = 350
symbol TankAlarm    = 300

Init:
low Valve

Main:
readadc10 Sensor,SensorOutput

;TankConv = SensorOutput - TankEmpty    (these lines are only rem until i work out what I'm doing wrong)
;TankPerc = TankConv * 25 / 256
TankPerc = SensorOutput *25 / 256

bintoascii TankPerc, b2,b3,b4,b5,b6
Using this will get me 0-99 but at full range. I have read your explanation of this Allan and also looked at hippy's post on the other thread but can't quite grasp the concept.

While I await your words of wisdom I will keep trying to figure it out!
 

hippy

Technical Support
Staff member
When a sensor input runs from 0 to 255 or 0 to 1023 its 0 to 1 range is determined by -

Code:
sensor        sensor
------   or   ------
 255           1023
This is then multiplied by 100 to give a 0 to 100 percentage. This is the optimised case of the generic equation which will be -

Code:
 sensor - minValue
-------------------
maxValue - minValue
To calculate a percentage using real world maths that would be -

( ( sensor - minValue ) / ( maxValue - minValue ) ) * 100

Using PICAXE left to right integer only maths, to get a percentage that would become -

sensor - minValue * 100 / (maxValue - minValue)

The multiply is done before the division to avoid loses in the integer division.

The PICAXE doesn't support parenthesis so (maxValue-minVale) has to be pre-calculated -

maxMinusMinValue = maxValue - minValue
sensor - minValue * 100 / maxMinusMinValue

And to prevent odd things happening if the sensor should read less than the minimum or more than the maximum value -

maxMinusMinValue = maxValue - minValue
sensor Min minValue - minValue Max maxMinusMinValue * 100 / maxMinusMinValue
 

AllyCat

Senior Member
Hi,

Using this will get me 0-99 but at full range. I have read your explanation of this Allan and also looked at hippy's post on the other thread but can't quite grasp the concept.
It's debatable whether the ADC has 1023 or 1024 "steps". It generates 1024 "values" but the first is zero and I believe the last actually represents 1023/1024 (or possibly even over 1022.5 / 1024 according to the Microchip data sheet). [The reason is that 1024 carries a '1' bit into the 11th bit position (with the lower 10 bits all zero) so the 10 bit data field just reads as zero.] Therefore, in practice it's normally better to use a value of 1023 (or 1022) as the full-scale divisor in calculations. PICaxe's integer maths is not going to generate a value above 100% unless the "input" value (to be scaled) is significantly above the intended 100% threshold.

If you only require an integer percentage value (i.e. no decimal point) and your raw max-min range value is less than 655 then you should have no problems; just multiply the "present level - min" raw value by 100 and divide by the number of ADC steps in the 100% scale range. If the resulting value exceeds (say) 200%, then that indicates a numerical "underflow" (i.e. a negative percentage). If that occurs you can either just warn as "Empty", or do some more maths to indicate a real negative percentage (e.g. subtract the raw level from the min level and scale as before, prefixing with a "-" character).

Cheers, Alan.
 

marks

Senior Member
Hi kw0me ,
When ever I get a new toy (sensor)
it always makes it easy if you make yourself a test program to run and also simulate
it looks like your almost there...
Code:
#picaxe 08m2
#terminal 4800
symbol TX	        = C.1
symbol Sensor       = C.2
symbol Valve 	  = C.4
symbol SensorOutput = w0
symbol ValveState   = b10
symbol AlarmState   = b11
symbol TankPerc     = w10
symbol SensorFullVal     = 670
symbol SensorEmptyVal    = 250
symbol TankLow      = 350
symbol TankAlarm    = 300
Symbol TankVolume  = SensorFullVal - SensorEmptyVal

Init:
low Valve

Main:
readadc10 Sensor,SensorOutput 
TankPerc = SensorOutput -SensorEmptyVal *100 / TankVolume

bintoascii TankPerc, b4,b3,b2
Sertxd (CR,LF," Tank at ",b4,b3,b2,"% Sensor ",#W0,CR,LF)
pause 1000 : goto main
 
Top