' 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%