Skip to content

Instantly share code, notes, and snippets.

@TG9541
Last active July 9, 2023 11:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TG9541/f7f84c1e3cf8f9e7d9179a380e09fad4 to your computer and use it in GitHub Desktop.
Save TG9541/f7f84c1e3cf8f9e7d9179a380e09fad4 to your computer and use it in GitHub Desktop.
Solar water heater control with transmission of collector and storage temperature via nRF24L01+
\ W1209 temperature measurement with filter and noise suppression
\ © 2017 TG9541, refer to https://github.com/TG9541/W1209/blob/master/LICENSE
\ Note: W1209 thermostats may require adjustment,
\ especially when used outside the range of -5C to +20C
\ Refer to https://github.com/TG9541/W1209/wiki/W1209-Sensor
RAM
900 CONSTANT USENSMAX \ max temperature
NVM
#require @inter
$8000 CONSTANT DEFAULT \ Default value indicator (-32768)
\ note: adjust to specific sensor here if accuracy is required!
\ note: @inter accepts 2..n value pairs
\ interpolation table ADC digits * 32 -> temperature * 10
CREATE dig2tem 14 , \ number of value pairs
1216 , 1100 , \ 1 1216 -> 11.0 C
1534 , 1000 , \ 2
1982 , 900 , \ 3
2560 , 800 , \ 4
3296 , 700 , \ 5
4320 , 600 , \ 6
5634 , 500 , \ 7
7370 , 400 , \ 8
9632 , 300 , \ 9
12382 , 200 , \ 10
15522 , 100 , \ 11
19012 , 0 , \ 12
20768 , -50 , \ 13
22466 , -100 , \ 14 22466 digits -> -10.0 C
: lpf32 ( n1 alpf -- n2 )
\ low pass filter, multiplies n1 by 32, uses a as LPF memory
( alpf ) DUP >R @ DUP 32 / - + DUP R> !
;
: unchatter ( n1 ahy -- n2 )
\ remove chatter (+/- 0.5 digit window), divides n1 by 32
\ ahy: hysteresis storage call address
SWAP 16 / OVER @ OVER SWAP - ABS 2-
0< IF DROP @ ELSE DUP ROT ! THEN
2/
;
: ntctheta ( adc astr -- theta )
\ filter noisy W1209 sensor input, result in astr @
SWAP DUP USENSMAX < IF
OVER 2+ lpf32 \ low pass filter, "noise to digits", 32*ADC
dig2tem @inter \ interpolation: lpf32 digits to temperature
OVER 2+ 2+ lpf32 \ low pass filter, "smoothen", 32*theta
OVER 2+ 2+ 2+ unchatter \ remove chatter, divides by 32
ELSE
DROP DEFAULT \ sensor error - default
THEN
SWAP ! \ store theta in astr
;
RAM
\ changed $HW to erase buffer with spaces so that the count and
\ retries saved into mybuff typed correctly at RX end
#require ntctheta
\res MCU: STM8S103
\res export BIT0 BIT1 BIT2 BIT3 BIT4 BIT5 BIT6 BIT7
\res export INT_EXTI2
\res export PA_ODR PA_DDR PA_CR1
\res export PC_DDR PC_CR1
\res MCU: nRF24L01
\res export R.SETUP_RETR R.STATUS R.OBSERVE_TX R.DYNPD R.FEATURE
1 CONSTANT _COL \ PA1 low side KTY10 solar collector
2 CONSTANT _STO \ PA2 low side KTY10 storage tank
3 CONSTANT _REL \ PA3 Relais
4 CONSTANT _AIN \ PC4/AIN2 ref=2000R to VCC
800 CONSTANT MAXTEMP \ safe storage temp
\ helper word: create BSET/BRES instructions
#require ]B!
#require BL
NVM
VARIABLE VALCOL 6 ALLOT \ collector temperature struct
\ theta \ temperature [0.1°C]
\ adc.lpf \ LPF memory for ADC value
\ tem.lpf \ LPF memory for temperature
\ hyst.lpf \ unchatter memory
VARIABLE VALSTO 6 ALLOT \ storage temperature struct
\ theta \ temperature [0.1°C]
\ adc.lpf \ LPF memory for ADC value
\ tem.lpf \ LPF memory for temperature
\ hyst.lpf \ unchatter memory
VARIABLE CSDIFF \ collector-storage diff (>0: col is warmer)
VARIABLE PUMPON \ current pump activation state
VARIABLE THRESH \ threshold value
VARIABLE HYSTER \ hysteresis value
VARIABLE LIMIT \ temperature Limit (with hysteresis)
: REL.on ( -- )
[ 1 PA_ODR _REL ]B!
;
: GetAin ( -- n )
2 ADC! ADC@
;
: REL.off ( -- )
[ 0 PA_ODR _REL ]B!
;
: SetCol ( -- )
[ 1 PA_DDR _COL ]B! [ 0 PA_DDR _STO ]B!
;
: SetSto ( -- )
[ 0 PA_DDR _COL ]B! [ 1 PA_DDR _STO ]B!
;
: measure ( -- ) \ background task
\ get collector an storage in odd and even cycles
GetAin ( ain ) TIM 1 AND 0= IF
SetSto \ switch to storage sensor
VALCOL ntctheta
ELSE
SetCol \ switch to collector sensor
VALSTO ntctheta
THEN
VALCOL @ VALSTO @ - CSDIFF !
;
: $HW ( -- )
mybuff P0_width $20 FILL \ erase buffer
$" Solar" COUNT ( --- a1 n1 )
mybuff SWAP CMOVE
;
: @retries ( --- n1 )
R.OBSERVE_TX nRF@1 \ get count of lost packets:retries
$0F AND \ mask off lost packets
;
: n>str ( n1 --- a1 n2 ) <# BL HOLD #S #> ;
: n>buff ( a1 n1 --- ) \ store n1 as a string at offset a1 in mybuff
n>str \ a1 a2 n2
ROT mybuff +
SWAP
CMOVE
;
VARIABLE STATUS \ nRF24 STATUS from IRQ
\ interrupt service routine
HERE ] \ headerless code (keep xt on stack)
SAVEC
\ R@STATUS ( s )
R.STATUS nRF@1 ( s )
DUP STATUS @ OR STATUS ! ( s )
DUP BIT4 AND IF
FlushTx \ FlushRx
THEN
\ DUP BIT5 AND IF
\ DEB.On \ Tx data sent
\ THEN
( s ) BIT6 AND IF
LED.On \ Rx data ready
\ RXBUFF 4 R_RX_PAYLOAD nrf>b DROP
RXBUFF 4 rx>b DROP
THEN
$70 R.STATUS nRF!1
IRET
[ OVERT ( xt ) INT_EXTI2 ! \ set Port C int vector
: PAYLOAD.TX ( -- ) \ send n bytes as set by P0_width
mybuff P0_WIDTH b>tx DROP
_CE.HD _CE.LOW \ 10us minimum, using 130uS
;
HERE $05A3 , $B4C5 , $D6E7 , ( myP0Addr )
: TxInit ( -- ) \ setup as primary transmitter
nRF24Init \ general setup
IrqInit \ enable nRF24 IRQ interrupt of PC.4
\ set pipe0 RX/TX to same address
( myP0Addr ) LITERAL COUNT b>p0addr
\ configure ACK payload mode
( DPL_P0 ) $01 R.DYNPD nRF!1
( EN_ACK_PAY ) $02 R.FEATURE nRF!1 \ see footnote ^d
4 SetPL_width \ make it work for SI24R1
\ set TX power to 0dBm output, modulation to 250kbps
[ $0 BIT5 >HIGH BIT2 >HIGH BIT1 >HIGH ] LITERAL RfSetup
\ ARD: retransmit delay, ARC: retries
[ ( ARD: 250us* ) $80 ( ARC: n* ) 3 + ] LITERAL R.SETUP_RETR nRF!1
$70 SetRF_CH
>Standby1
Set.TX \ enter TX mode
." Device reset" cr
;
: sol.init ( -- )
[ 0 PA_ODR _COL ]B! \ open drain output
[ 1 PA_DDR _COL ]B!
[ 0 PA_CR1 _COL ]B!
[ 0 PA_ODR _STO ]B! \ open drain output
[ 1 PA_DDR _STO ]B!
[ 0 PA_CR1 _STO ]B!
[ 1 PA_DDR _REL ]B! \ PA RELais output
[ 1 PA_CR1 _REL ]B! \ push-pull output
[ 0 PC_DDR _AIN ]B! \ PC4 AIN2 as input
[ 0 PC_CR1 _AIN ]B! \ no pull-up
0 PUMPON !
50 THRESH !
30 HYSTER !
MAXTEMP LIMIT !
[ ' measure ] LITERAL BG !
;
: sol.act
VALCOL ?
VALSTO ?
CSDIFF @ DUP . CR
( cs-diff ) THRESH @ - \ apply activation threshold
( diff ) PUMPON @ 0= IF
HYSTER @ - THEN \ apply hysteresis if pump is off
MAXTEMP VALSTO @ < IF
( diff ) DROP -1 THEN \ limit storage temperature
( limdiff ) 0< IF
REL.off 0 PUMPON !
ELSE
REL.on 1 PUMPON ! THEN
;
: Solar ( -- )
TxInit
0 STATUS !
sol.init
$HW BEGIN
8 VALCOL @ n>buff 16 VALSTO @ n>buff
PAYLOAD.TX
@retries DUP 27 SWAP n>buff DROP
100 ms
STATUS @ 0 STATUS !
DUP BIT4 AND IF ." Max retries cleared" CR THEN
\ DUP BIT5 AND IF ." Tx data sent" CR THEN
DUP BIT6 AND IF ." Rx data ready" CR THEN
( s ) . SPACE RXBUFF 4 TYPE SPACE
LED.Off \ DEB.Off
sol.act
900 ms
?RX \ input on serial line
IF 32 = ELSE 0 THEN \ only if it is a space char do we exit
UNTIL
DROP
;
' Solar 'Boot !
RAM
@TG9541
Copy link
Author

TG9541 commented Sep 24, 2018

This is in extension of solar.fs using stm8ef-nRF24L01 (using additional code from PingInt). PongInt works as a simple receiver. Note that in order to free up an analog input PC3 instead of PC4 was used for the nRF24L01+ interrupt.

There is a project on Hack-A-Day to track the progress.

@TG9541
Copy link
Author

TG9541 commented Jul 9, 2023

Here is a simplified version without NRF24 using Chinese W1209 style NTC sensors sensors and ntctheta:

\ 20230709 simplified version for NTC w/o NRF24

#require VALUE
#require ntctheta

\res MCU: STM8S103
\res export PA_ODR PA_DDR PA_CR1
\res export PC_DDR PC_CR1


1 CONSTANT _COL  \ PA1 low side W1209 NTC sensor solar collector
2 CONSTANT _STO  \ PA2 low side W1209 NTC sensor storage tank
3 CONSTANT _REL  \ PA3 Relais
4 CONSTANT _AIN  \ PC4/AIN2 pull-up to VCC with Rref=20K


\ helper word: create BSET/BRES instructions
#require ]B!

NVM

  \ ntctheta data structures for temperature measurement with LPF from W1209 
  VARIABLE VALCOL 6 ALLOT  \ collector temperature struct
  \ theta        \ temperature [0.1°C]
  \ adc.lpf      \ LPF memory for ADC value
  \ tem.lpf      \ LPF memory for temperature
  \ hyst.lpf     \ unchatter memory
  VARIABLE VALSTO  6 ALLOT   \ storage temperature struct
  \ theta        \ temperature [0.1°C]
  \ adc.lpf      \ LPF memory for ADC value
  \ tem.lpf      \ LPF memory for temperature
  \ hyst.lpf     \ unchatter memory
 
  \ Standard-Forth-like "changeable constants" in NVM
  \ change with, e.g., NVM 20 TO HYSTER RAM 
  10 VALUE THRESH  \ threshold value [0.1 deg C]
  30 VALUE HYSTER  \ hysteresis value 
 800 VALUE MAXTEMP \ temperature Limit (with hysteresis)

  VARIABLE CSDIFF  \ collector-storage diff (>0: collector is warmer)
  VARIABLE PUMPON  \ current pump activation state

  : REL.on  ( -- )
     [ 1 PA_ODR _REL ]B!
     ;

  : GetAin ( -- n )
     2 ADC! ADC@
     ;

  : REL.off ( -- )
     [ 0 PA_ODR _REL ]B!
     ;

  : SetCol ( -- )
     [ 1 PA_DDR _COL ]B!  [ 0 PA_DDR _STO ]B!
     ;

  : SetSto ( -- )
    [ 0 PA_DDR _COL ]B!  [ 1 PA_DDR _STO ]B!
    ;

  : measure ( -- )  \ background task
  \ get collector an storage in odd and even cycles
     GetAin ( ain ) TIM 1 AND 0= IF
        SetSto  \ switch to storage sensor
        VALCOL ntctheta
     ELSE
        SetCol  \ switch to collector sensor
        VALSTO ntctheta
     THEN
     VALCOL @ VALSTO @ - CSDIFF !
     ;

 : sol.init ( -- )
    [ 0 PA_ODR _COL ]B! \ open drain output
    [ 1 PA_DDR _COL ]B!
    [ 0 PA_CR1 _COL ]B!
    [ 0 PA_ODR _STO ]B! \ open drain output
    [ 1 PA_DDR _STO ]B!
    [ 0 PA_CR1 _STO ]B!
    [ 1 PA_DDR _REL ]B! \ PA RELais output
    [ 1 PA_CR1 _REL ]B! \ push-pull output
    [ 0 PC_DDR _AIN ]B! \ PC4 AIN2 as input
    [ 0 PC_CR1 _AIN ]B! \ no pull-up
    0       PUMPON !
    [ ' measure ] LITERAL BG !
    ;

  : sol.act
     PUMPON ?
     VALCOL ?
     VALSTO ?
     CSDIFF @ DUP . CR
     ( cs-diff ) THRESH -    \ apply activation threshold
     ( diff ) PUMPON @ 0= IF
        HYSTER - THEN        \ apply hysteresis if pump is off
     MAXTEMP VALSTO @ < IF
        ( diff ) DROP -1 THEN  \ limit storage temperature
     ( limdiff ) 0< IF
        REL.off  0 PUMPON !
     ELSE
        REL.on   1 PUMPON ! THEN
     ;


: Solar  ( -- )
   hi
   ." PunpOn Collector Storage Difference" CR
   sol.init

   BEGIN
      \ 640 ms
      TIM $7F AND 0= IF sol.act THEN
      ?RX  \ input on serial line
      IF 32 = ELSE 0 THEN  \ exit on console blank/space
   UNTIL
   DROP
   ;

' Solar 'Boot !
RAM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment