' remote_humid_single_sensor.bas
' (C) Joseph Ellsworth Jan 2010
' Free use for all,  no warranty and no promises.
' For paid consulting and design services call 206-601-2985
'
' Provides remote transmitter for humidity and temperature with
' field calibratin capability built in.
'
' Provides interface to read capacitive humidity sensors using RC
' constant which changes with humidity.   We measure the charge time
' of the sensor through a resistor and detect a charge level over 2V
' using the on chip analog comparator.  Also reads temperature through
' a high precision thermister acting as ground leg of a voltage
' dividor.
'
' Calibration support built in with calibration readings stored in
' ERAM on the CPU chip.
'
' Tested with HC-1000-002 from Honeywell and HS-1101-LF from Humirel.
' Should work with most capactive sensors but sensitivity is limited
' for sensors that do not provide at least 200pf change across their
' operating range.
'
' Removed multi-sensor support because it is intended to support
' a single sensor usin the 4,000 meter transmitter range built into
' using RS485 on a shared bus.  Ideally the entire CPU + driver
' can be integrated as part of a thick cable which is part of the
' humidity sensor.  Could be adapted to support RS232, 1 wire or I2C.
'
' Initial transmitter support is via RS232 to allow easy read from the PC.
' This is intended to be fabricated
'
' Keep the sensor wires as short as possible to
' minimize noise interference from ambient.
'
' See: ... for schematic
' See: ... for humidity overview and calibration instructions
' See: ... for board layout.
'
' TODO: Decide if 1 wire or RS485 is the better strategy for use
'       in chip interaction.
'
' TODO: Add serial command processor similar to that used in the
'       resistive heater for the temperature calibrator. Only do
'       this if there is not a better way to do it using a 1 wire
'       strategy.
'
' TODO: Make it a 1 Wire compatible sensor. And provide a 1 wire to PC bridge
'
' TODO: Add dewpoint, temp and humdity alarms which can activate output pins.
'       Need to see how we can re-use the programmer pins when the programmer
'       is not attached.
'
' TODO: See if we have enough pins left over provide voltage dividor with a
'       40 to 1 voltage reducution so we can protect the IO pins from
'       inadvertant connection to 115VAC.
'


$regfile = "attiny84.dat"
$crystal = 8000000
'$crystal = 1000000
$hwstack = 64                                               ' default use 32 for the hardware stack
$swstack = 20                                               ' default use 10 for the SW stack
$framesize = 80                                             ' default use 40 for the frame space


'Open "comb.0:9600,8,N,1,inverted" For Output As #1           ' no RS232 TXRX just 1.5K to PC RX
Open "comb.0:9600,8,N,1" For Output As #1                   ' for when RS232 TXRX available
Print #1 , "remote_humid_single_sensor.bas"
' Adapted from the lib_hc_1000_humid_sensor libraries and
' simplified to fit in the lower RAM amount in the AVR Tiny
' CPU.  Switched from defined functions to
' to labeled gosub because the compiler was complaining
' of sram shortage way too soon when using the functions

' - - - - - -
' - -  Port Configuration
'' - - - - - -
'Sensor_bus_power Alias Portb.2
Cap_rc_charge Alias Porta.7
Cap_rc_read Alias Pina.2
Rs_485_tenable Alias Portb.3

Config Cap_rc_charge = Output
'Config Sensor_bus_power = Output
Config Cap_rc_read = Input

Config Adc = Single , Prescaler = Auto , Reference = Avcc
Const Adc_temp_channel = 3
Config Rs_485_tenable = Output


'- - - - - -
'- - Constants
'- - - - - -
Const Dhumid_wait_before_calibrate = 360
Const Micro_volt_per_adc_unit = 3226
Const Temp_error = -1000
Const Max_temp = 125
Const Low_temp = -40
Const Temp_inc = 5

Const Hum_num_slot = 3
Const Hum_num_to_read = 20
Const Hum_wait_discharge = 1
Const Hum_wait_before_calibrate = 360
Const Hum_max_allowed_cnt = 5900
Const Hum_no_sensor_min = 300
Const Hum_max_wait = 50



Bon Alias 1
Boff Alias 0



' - - - - - -
' - - EPROM
' - - - - - -
Dim Er_empty_byte As Eram Byte At 0                         '1b
Dim Er_dev_id As Eram Long
Dim Er_sens_calib(3) As Eram Word
   ' 1 sensors @ 3 slots
Dim Er_calib_delay As Eram Word
Dim Er_calib_complete As Eram Byte                          ' has calibration for all slots been completed sucessfully
Dim ER_Temp_offset As Eram Integer                       ' amount to adjust measured temp for fine tuned calibration


' - - - - - -
' - - RAM Variables
' - - - - - -
Dim Tlong As Long
Dim Tlong2 As Word
Dim Tint As Integer
Dim Tword As Word
dim tbyte as byte

Dim Adc_chan As Byte
Dim Adc_res As Word
Dim Temp_res As Integer                                     ' returned as C * 100 to retain decimal in int form
Dim Hum_res As Word
Dim Tempc As Word

Dim Slot_ndx As Byte
Dim Max_slot_ndx As Byte
Dim Tndx As Byte

Dim B0 As Word                                              ' boundry low
Dim B1 As Word                                              ' boundry high
Dim Mv_dif_per_c As Word
Dim Mv_above_b1 As Word

Dim Cap_res As Word
Dim Hum_err As Byte

Declare Function Conv_count_to_humid(sread As Word) As Word

' - - - - - -
' - Initialization
' - - - - - -
Gosub Check_and_init_eprom
Gosub Print_calibration


'Slot_ndx = 1                                                ' magnesium chloride
'Slot_ndx = 2                                                ' table salt
'Slot_ndx = 3                                                ' potassium nitrate
'Gosub Calibrate_humid_sensor                                ' Calibrates For Current Value Of Slot Num


' - - - - - -
' - - MAIN LOOP
' - - - - - -
Do
  Rs_485_tenable = Bon
  Adc_chan = 3
  Gosub Read_adc_mv
  Print #1 , "adc_mv_res=" ; Adc_res
  Gosub Read_temp
  Print #1 , " temp res=" ; Temp_res
  Gosub Read_cap_sensor_poll
  Print #1 , "hum_res=" ; Cap_res
  Gosub Read_cap_sensor
  Print #1 , "hum_res int=" ; Cap_res
  Gosub Read_cap_sensor_avg
  Print #1 , "hum_res avg=" ; Cap_res
  Gosub Read_cap_avg_avg
  Print #1 , "read_cap_avg_avg=" ; Cap_res
  Gosub Read_humidity
  Print #1 , "read_humidity=" ; Hum_res
  Rs_485_tenable = Boff


  Waitms 1500
Loop


' - - - - - -
Read_humidity:
' - - - - - -
  Gosub Read_cap_avg_avg
  Hum_res = Conv_count_to_humid(cap_res)
  Return




'-- Calibrate Humidity Sensor
'-- store the result in Er_sens_calib_reading(slot_ndx)
'-- where slot_ndx is set by caller
'Reuse B0 , B1 , Tword
Dim Last_read As Word
Dim Tdif As Integer
Dim Calib_cnt As Byte
Dim Cal_accum As Long
Dim Cal_avg As Long
Dim Ccnt As Byte
' - - - - - - - - - -
Calibrate_humid_sensor:
' - - - - - - - - - -
  Calib_cnt = 0
  Ccnt = 0
  Cal_accum = 0
  While calib_cnt < 75
    Gosub Read_cap_avg_avg
    ' Figure out delta to last read fast changing reads
    ' require decrementing
    Ccnt = Ccnt + 1
    Cal_accum = Cal_accum + Cap_res
    If Ccnt > 35 Then
      Ccnt = 1
      Cal_accum = Cap_res
      If Calib_cnt > 60 Then
        Calib_cnt = 60
      End If
    End If
    Cal_avg = Cal_accum / Ccnt
    Tword = Cal_avg
    Tdif = Tword - Cap_res
    Tdif = Abs(tdif)
    If calib_cnt > 2 Then
      If Tdif > 2 Then
        calib_cnt = calib_cnt - 3
      Elseif Tdif > 1 Then
        calib_cnt = calib_cnt - 2
      Elseif Tdif > 0 Then
        Calib_cnt = Calib_cnt - 1
      End If
    End If
    calib_cnt = calib_cnt + 1
    Print #1 , " calib slot=" ; Slot_ndx ; " cap_res=" ; Cap_res ; " calib_cnt=" ; Calib_cnt ; " tdif=" ; Tdif;
    Print #1 , " ccnt=" ; Ccnt ; " cal_avg=" ; Cal_avg;
    Last_read = Cal_avg
    Gosub Read_temp
    Print #1 , " tempC=" ; Temp_res

    If cap_res < 300 Or Hum_err > 0 Then
      Print #1 , "no sensor"
      Return
    End If
  Wend

  ' We fell through here once our
  ' counter reached 50 which indicates
  ' reasonable stability for the reads
  Tword = Er_sens_calib(slot_ndx)
  cap_res = cal_avg
  If Tword <> cap_res Then
    Print #1 , "saving calibration slot_ndx=" ; Slot_ndx ; " cap_res = " ; Cap_res
    Er_sens_calib(slot_ndx) = Cap_res
  Else
    Print #1 , "calibration not changed =" ; Cap_res
  End If
  Return




Dim Av2 As Byte
dim avg_avg_accum as long
' - - - - - - - - - -
Read_cap_avg_avg:
' - - - - - - - - - -
 Avg_avg_accum = 0
 For Av2 = 1 To Hum_num_to_read
   Gosub Read_cap_sensor_avg
   If Hum_err > 0 Then
     Print #1 , "hum_err=" ; Hum_err
     Return
   End If
   Avg_avg_accum = Avg_avg_accum + Cap_res
 Next Av2
 Avg_avg_accum = Avg_avg_accum / Hum_num_to_read
 Cap_res = Avg_avg_accum
 Return





Dim Avg_ndx As Byte
dim avg_accum as long
' - - - - - - - - - -
Read_cap_sensor_avg:
' - - - - - - - - - -
  Gosub Read_cap_sensor                                     ' first one to set up charge level
  Avg_accum = 0
  For Avg_ndx = 1 To Hum_num_to_read
    Gosub Read_cap_sensor
    If Hum_err > 0 Then
      cap_res = 0
      Return
    End If
    Avg_accum = Avg_accum + Cap_res
    'Print #1 , " avg tndx=" ; Avg_ndx ; " cap_res=" ; cap_res
    'waitms 50
  Next Avg_ndx
  Avg_accum = Avg_accum / Hum_num_to_read
  Cap_res = Avg_accum
  Return




'-- Read capacitive sensor using interupts and timer.  Seems
'-- to be more sensitive than timing loop.  Could probably have used
'-- a smaller resistor and a pre-scaler of 8.
' - - - - - - - - - -
Read_cap_sensor:
' - - - - - - - - - -
  Hum_err = 0
  Config Cap_rc_read = Input
  Reset Cap_rc_charge
  Bitwait Cap_rc_read , Reset
  Waitms Hum_wait_discharge                                 ' Give time to discharge through diode.
  Config Timer1 = Timer , Prescale = 1                      'R/C Timer
  Config Aci = On , Compare = On , Trigger = Falling
  On Aci Cap_sensor_charged                                 'on overflow timer0 jump to label
  Enable Interrupts
  Enable Aci
  Enable Timer1
  Timer1 = 0
  Start Timer0
  Start Timer1
  Cap_res = 0
  Set Cap_rc_charge
  Acsr.7 = 0                                                ' enable alnalog comparator ACSR bit 7 = 0

  ' Loop here until we get a result or
  ' timeout or we get something interesting
  ' in cap_res.
  Tndx = 0
  While Cap_res = 0
   Tndx = Tndx + 1
   Waitms 1
   ' Insert calls to other period logic
   ' like check command in serial port
   ' or
   If Tndx > Hum_max_wait Then
     Hum_err = 1                                            ' record timout
     Exit While
   End If
  Wend
  Reset Cap_rc_charge
  'Print #1 , " cap_res=" ; Cap_res ; " hum_err=" ; Hum_err

  ' cleanup
  Stop Timer0
  Stop Timer1
  Disable Aci
  Disable Timer0
  Disable Timer1
  Config Aci = Off , Compare = Off
  cap_res = Cap_res
  Return


' - - - - - - - - - -
Cap_sensor_charged:
' - - - - - - - - - -
  If Cap_res = 0 Then
    Cap_res = Timer1
    Timer1 = 0
  End If
  Return



' Loops on the IO line counting until it reads
' a high. This is the non interupt version
' of measuring the charge time of variable
' capacitors.
' - - - - - - - - - -
Read_cap_sensor_poll:
' - - - - - - - - - -
  cap_res = 0
  Reset Cap_rc_charge
  Bitwait Cap_rc_read , Reset                               ' wait until read low
  Waitms Hum_wait_discharge                                 ' Give time to discharge through diode.
  Tndx = 0
  Set Cap_rc_charge
  Tndx = 0
  While Cap_rc_read <> Bon
    cap_res = cap_res + 1                                   ' Charge the capacitor
    If cap_res > Hum_max_allowed_cnt Then
      cap_res = 0
      Exit While
    End If
  Wend
  Reset Cap_rc_charge
  Return







' Reads the temperature through a thermister ADC channel
' by reading mv from the adc and then looking that reading
' up in the data from Ertj0eg which was calculated using
' a 22.1K resistor as feed and thermister to ground.
' - - - - - - - - - -
Read_temp:                                                  ' read and calc result for ertj0eg
' - - - - - - - - - -
   Adc_chan = Adc_temp_channel

   Gosub Read_adc_mv                                        ' get reading from the ADC
   Temp_res = Temp_error
   'St_temp = Adc_res
   Temp_res = Low_temp

   ' Get first eading for error check.
   Restore Ertj0eg
   Read B1
   B0 = B1
   Temp_res = Temp_res + Temp_inc
   If Adc_res > B1 Then
    Temp_res = Temp_error
    Return
   End If

   ' look the reading up in the data chart
   While Temp_res <= Max_temp
     B0 = B1
     Read B1
     If Adc_res > B1 Then
       Exit While
     End If
     Temp_res = Temp_res + Temp_inc
   Wend

   If Temp_res > Max_temp Then
     Temp_res = Temp_error
     Return
   End If

   ' Now we look at the two settings and calculate the
   ' fractional degree
    Mv_dif_per_c = B0 - B1
    Mv_dif_per_c = Mv_dif_per_c / Temp_inc

    ' calculate MV below the upper level
    ' readings
    Mv_above_b1 = Adc_res - B1
    Mv_above_b1 = Mv_above_b1 * 100
    Mv_above_b1 = Mv_above_b1 / Mv_dif_per_c

    Temp_res = Temp_res * 100                               ' adjust scale of CC
    Temp_res = Temp_res - Mv_above_b1                       ' adjust for fractional delta in temp range

    ' Adjust our output with calibration offset.
    Tint = Er_temp_offset
    Temp_res = Temp_res + Tint


  Return



' - - - - - - - - - -
Read_adc_mv:
' - - - - - - - - - -
  Config Adc = Single , Prescaler = Auto , Reference = Avcc
  Start Adc
  Adc_res = Getadc(adc_chan)
  Tlong = Adc_res * Micro_volt_per_adc_unit
  Tlong = Tlong / 1000
  Adc_res = Tlong                                           ' force type conversion
  Stop Adc
  Return

' - - - - - - - - - -
Print_calibration:
' - - - - - - - - - -
 Tlong = Er_dev_id
 Print #1 , "id=" ; Tlong
 Tbyte = Er_empty_byte
 Print #1 , "empty_byte=" ; Tbyte
 Tword = Er_calib_delay
 Print #1 , "calib_delay=" ; Tword
 Tbyte = Er_calib_complete
 Print #1 , "calib_complete=" ; Tbyte

 For Slot_ndx = 1 To Hum_num_slot
    Tword = Er_sens_calib(slot_ndx)
    Print #1 , "S" ; Slot_ndx ; "=" ; Tword
 Next Slot_ndx
 Return



' - - - - - - - - - -
Check_and_init_eprom:
' - - - - - - - - - -
Tbyte = Er_empty_byte
If Tbyte = 255 Then
  ' initialization needed
  Er_dev_id = 1000
  Er_empty_byte = 1
  Er_calib_delay = 3600
  Er_calib_complete = 0
  ' rough calibration numbers to get started
  ' allows the sensor to show some calculations
  ' but beware that off the shelf sensors show
  ' upto 25% variance before calibration.
  Er_sens_calib(1) = 3278
  Er_sens_calib(2) = 3550
  Er_sens_calib(3) = 3650

  Er_temp_offset = 0

  'Assum that all users will use dev_id
  'other than 1000.  Devid 1,000 is reserved
  ' for new sensors which need to respond
  ' to calibration commands.
End If





' Read humidity value by reading capacitor
' and using calibration data points to convert to
' humidity value.   Returns humidity as word value
' which is the % value * 100 which truncates minor
' % humdiity difference but our sensor is not
' that accurate anyway.
' - - - - - -
Function Conv_count_to_humid(sread As Word) As Word
' - - - - - -
  Local Slot_lo As Byte                                     ' lower active slot
  Local Slot_hi As Byte                                     ' higher actie slot
  Local Low_p As Word                                       ' lower precent
  Local Hi_p As Word                                        ' higher percent
  Local Low_r As Word                                       ' lower calibration reading
  Local Hi_r As Word                                        ' higher calibration reading
  Local Read_dif As Word                                    ' calibration reading difference
  Local Pc_dif As Word                                      ' calibartion percentage difference
  Local Cr_dif As Word                                      ' difference between sensor reading and low calibration
  Local P_per_c As Word                                     ' percent reading per count unit
  Local Tanswer As Word                                     ' used for final calculations


  ' Apply the reading to the calibration points
  Slot_lo = 1
  Slot_hi = 2
  restore Humidity_calib_points
  read low_p
  Do
    Low_r = Er_sens_calib(slot_lo)
    Hi_r = Er_sens_calib(slot_hi)
    Read hi_p  ' synchronize read through calip percent with stored calib points
    if sread < low_r then
      exit do  ' reading is lower than our lowest so use the bottom two
    end if

    if sread < hi_r then
       exit do ' reading is between our two current points so use them
    end if

    If Slot_hi = Hum_num_slot Then
      exit do ' our reading is above our highest calibratino readings so use them
    end if
    slot_lo = slot_lo+1
    slot_hi = slot_hi+1
    low_p = hi_p
  loop


  ' Now convert the reading to a percentage
  ' Basically calculate the reading delta between
  ' the two calibration points and then the number
  ' of percentage points per calibration unit
  ' use those to add the point on the slop between
  ' the two percentage points back to the lower
  ' percentage point.
  if sread < low_r then
    read_dif = low_r - sread ' special case when lower than our lowest calibarion reading
  else
    read_dif = sread - low_r
  end if

  cr_dif = hi_r - low_r
  Pc_dif = Hi_p - Low_p                                     '
  pc_dif = pc_dif * 10  ' increase units to maintain fidelity
  p_per_c = pc_dif \ cr_dif
  tanswer = p_per_c * read_dif
  tanswer = tanswer \ 10
  print #1, " sread="; sread; " low_r=";low_r; " low_p="; low_p; " hi_r=";hi_r; " hi_p="; hi_p
  print #1, " cr_dif="; cr_dif; " pc_dif="; pc_dif;  " read_dif="; read_dif; " p_per_c="; p_per_c; " taswer=";tanswer; " slot_lo="; slot_lo;  " slot_hi="; slot_hi


  if sread < low_r then       ' answer is below lowest calibration point so subtract instead.
    if tanswer > low_p then   '  Eg:  lower calibration at 9% but answer at is 7%.
      tanswer = 0  ' Can not have answer below 0 percent
    else
      tanswer = low_p - tanswer
    end if
  else
    tanswer = low_p + tanswer
  end if
  conv_count_to_humid = tanswer
End Function





' - - - - - -
Ertj0eg:
' - - - - - -
' This sensor is specified from -40C to 125C in 5C increments
' as a multiplier ratio.  The following is calculated from
' excel ert-j0EG103FA-resistance-and-mv-calcs-v2.1.xls
' as a actuall mv output across the voltage drop where
' a 1% 10K  feeds feeds the circuit and the thermistor
' drains to ground which provides a measurable voltage divider
' at the junction.   To calculate the temperature we find the two
' closest readings and then calculate a ratio the current reading
' is between the two.
  Data 2979% , 2888% , 2779% , 2653% , 2511% , 2296% , 2187%
  Data 2012% , 1834% , 1656% , 1484% , 1320% , 1168% , 1028%
  Data 902% , 789% , 689% , 600% , 523% , 456% , 398% , 347%
  Data 303% , 265% , 232% , 203% , 178% , 156% , 137% , 120%
  Data 106% , 93% , 82% , 73


' - - - - - -
Humidity_calib_points:
' - - - - - -
'  These data points basically represent humidity over saturated
'  salt at 20C. for Magnesium Chloride, Sodium Chloride and
'  Potassium Nitrate.
'  See: lib_hc_1000_humid_sensor.bas for full explanation


   'Data 0020% , 3330% , 7547% , 9462% , 9999%         ' 5 point calibration
   '                                                    Adjusted to percent * 100 to store as word
   Data 3330% , 7547% , 9462%