Skip to content

Instantly share code, notes, and snippets.

@k-takata
Last active August 4, 2023 21:31
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 k-takata/bf6993e37975a502d4eecc966ced9495 to your computer and use it in GitHub Desktop.
Save k-takata/bf6993e37975a502d4eecc966ced9495 to your computer and use it in GitHub Desktop.
Temperature, humidity and pressure meter using GR-CITRUS + AE-BME680 + LCD module (AQM1602Y-FLW-FBW)
#!mruby
class LCD
ID = 0x3E
def initialize
@i2c = I2c.new(1)
end
def init
#delay(10)
send_cmd(0x38) # Function Set: 8 bits, 2 lines, normal height, Normal mode
end
def init2
delay(2)
send_seq([0x39, # Function Set: 8 bits, 2 lines, normal height, Extend mode
0x14]) # Internal OSC frequency: 1/5 bias, 183 Hz
# Contrast
contrast = 0x20
send_seq([0x70 + (contrast & 0x0F), # Contrast set: low byte
0x5C + ((contrast >> 4) & 0x03), # Icon on, Booster on, Contrast high byte
0x6C]) # Follower control: follower circuit on, amplified ratio = 2 (for 3.3 V)
@prev_ms = millis()
end
def init3
delay(200 - (millis() - @prev_ms))
send_seq([0x38, # Function Set: Normal mode
0x0C, # Display on, cursor off, cursor position off
0x01]) # Clear Display
#delay(2)
end
# Send a command sequence
def send_seq(cmds, dataarr=[])
@i2c.begin(ID)
if dataarr.empty? then
# Only command data
@i2c.lwrite(0x00) # Command byte: Co=0, RS=0
cmds.each {|cmd|
@i2c.lwrite(cmd) # Command data byte
}
else
# Send command words (if any)
cmds.each {|cmd|
@i2c.lwrite(0x80) # Command byte: Co=1, RS=0
@i2c.lwrite(cmd) # Command data byte
}
# Send RAM data bytes
@i2c.lwrite(0x40) # Command byte: Co=0, RS=1
dataarr.each {|dat|
@i2c.lwrite(dat) # RAM data byte
}
end
@i2c.end
#delay(0)
end
# Send a command
def send_cmd(cmd)
send_seq([cmd])
end
# Send 1-byte data
# def send_data(dat)
# send_seq([], [dat])
# end
# Set cursor position
def set_cursor(col, row)
send_cmd(0x80 + 0x40*row + col)
end
# Clear all display
# def clear()
# send_cmd(0x01) # Clear Display
# delay(2)
# end
# Show the string
def print(cs)
send_seq([], cs.bytes)
end
# Set CGRAM
# num: 0-7
# pat: Character pattern (8-bytes per character)
def set_cgram(num, pat)
send_seq([0x40 + num*8], pat)
end
# Shift the display to left or right
# dir: shift direction: 0=left, !0=right
# def shiftdisp(dir=0)
# send_cmd(0x18 + ((dir != 0) ? 0x04 : 0x00))
# end
end
class BME680
ID = 0x77
#REG_COEFF3 = 0x00
REG_FIELD0 = 0x1D
REG_IDAC_HEAT0 = 0x50
REG_RES_HEAT0 = 0x5A
REG_GAS_WAIT0 = 0x64
REG_SHD_HEATR_DUR = 0x6E
REG_CTRL_GAS_0 = 0x70
REG_CTRL_GAS_1 = 0x71
REG_CTRL_HUM = 0x72
REG_CTRL_MEAS = 0x74
REG_CONFIG = 0x75
REG_UNIQUE_ID = 0x83
REG_COEFF1 = 0x8A
REG_CHIP_ID = 0xD0
REG_RESET = 0xE0
REG_COEFF2 = 0xE1
REG_VARIANT_ID = 0xF0
LEN_COEFF1 = 23
LEN_COEFF2 = 14
#LEN_COEFF3 = 5
#LEN_COEFF_ALL = LEN_COEFF1 + LEN_COEFF2 + LEN_COEFF3
LEN_FIELD = 17
def initialize
@i2c = I2c.new(1)
end
def init
@i2c.write(ID, REG_RESET, 0xB6)
#delay(100)
if @i2c.read(ID, REG_CHIP_ID) != 0x61 then
puts "BME680 not found."
end
end
def u16(msb, lsb)
return (msb << 8) + lsb
end
def s16(msb, lsb)
return (msb << 8) + lsb + ((msb < 0x80) ? 0 : ~0xFFFF)
end
def u8(b)
return b
end
def s8(b)
return b + ((b < 0x80) ? 0 : ~0xFF)
end
# Get calibration data
def get_calib_data
coeff = []
#delay 10
@i2c.begin(ID)
@i2c.lwrite(REG_COEFF1)
@i2c.end(0)
@i2c.request(ID, LEN_COEFF1)
LEN_COEFF1.times do
coeff << @i2c.lread()
end
@i2c.begin(ID)
@i2c.lwrite(REG_COEFF2)
@i2c.end(0)
@i2c.request(ID, LEN_COEFF2)
LEN_COEFF2.times do
coeff << @i2c.lread()
end
#@i2c.begin(ID)
#@i2c.lwrite(REG_COEFF3)
#@i2c.end(0)
#@i2c.request(ID, LEN_COEFF3)
#LEN_COEFF3.times do
# coeff << @i2c.lread()
#end
#puts(["coeff:", coeff])
@par_t1 = u16(coeff[32], coeff[31])
@par_t2 = s16(coeff[1], coeff[0])
@par_t3 = s8(coeff[2])
puts("par_t1: #{@par_t1}")
puts("par_t2: #{@par_t2}")
puts("par_t3: #{@par_t3}")
@par_p1 = u16(coeff[5], coeff[4])
@par_p2 = s16(coeff[7], coeff[6])
@par_p3 = s8(coeff[8])
@par_p4 = s16(coeff[11], coeff[10])
@par_p5 = s16(coeff[13], coeff[12])
@par_p6 = s8(coeff[15])
@par_p7 = s8(coeff[14])
@par_p8 = s16(coeff[19], coeff[18])
@par_p9 = s16(coeff[21], coeff[20])
@par_p10 = u8(coeff[22])
puts("par_p1: #{@par_p1}")
puts("par_p2: #{@par_p2}")
puts("par_p3: #{@par_p3}")
puts("par_p4: #{@par_p4}")
puts("par_p5: #{@par_p5}")
puts("par_p6: #{@par_p6}")
puts("par_p7: #{@par_p7}")
puts("par_p8: #{@par_p8}")
puts("par_p9: #{@par_p9}")
puts("par_p10: #{@par_p10}")
@par_h1 = (coeff[25] << 4) + (coeff[24] & 0x0F)
@par_h2 = (coeff[23] << 4) + ((coeff[24] >> 4) & 0x0F)
@par_h3 = s8(coeff[26])
@par_h4 = s8(coeff[27])
@par_h5 = s8(coeff[28])
@par_h6 = u8(coeff[29])
@par_h7 = s8(coeff[30])
puts("par_h1: #{@par_h1}")
puts("par_h2: #{@par_h2}")
puts("par_h3: #{@par_h3}")
puts("par_h4: #{@par_h4}")
puts("par_h5: #{@par_h5}")
puts("par_h6: #{@par_h6}")
puts("par_h7: #{@par_h7}")
@par_g1 = s8(coeff[35])
@par_g2 = s16(coeff[34], coeff[33])
@par_g3 = s8(coeff[36])
puts("par_g1: #{@par_g1}")
puts("par_g2: #{@par_g2}")
puts("par_g3: #{@par_g3}")
puts
end
# Set operation mode
# mode: 0=Sleep mode, 1=Force mode
def set_op_mode(mode)
# Wait until it becomes to sleep mode.
cur_mode = 0
loop do
cur_mode = @i2c.read(ID, REG_CTRL_MEAS)
break if (cur_mode & 0x03) == 0
delay 10
end
if mode != 0 then
@i2c.write(ID, REG_CTRL_MEAS, (cur_mode & ~0x03) | (mode & 0x03))
end
end
# Set configuration for temperature, pressure, humidity
# osrs_x (0..5): Oversampling rate: 0: skip, 1..5: 2^(osrs_x - 1)
# filter (0..7): Filter coefficient: 2^(filter) - 1
def set_conf(osrs_t, osrs_p, osrs_h, filter)
set_op_mode(0)
tmp = @i2c.read(ID, REG_CTRL_MEAS)
@i2c.write(ID, REG_CTRL_MEAS, ((osrs_t & 0x07) << 5) | ((osrs_p & 0x07) << 2) | (tmp & 0x03))
tmp = @i2c.read(ID, REG_CTRL_HUM)
@i2c.write(ID, REG_CTRL_HUM, (tmp & ~0x07) | (osrs_h & 0x07))
tmp = @i2c.read(ID, REG_CONFIG)
@i2c.write(ID, REG_CONFIG, (tmp & ~(0x07 << 2)) | ((filter & 0x07) << 2))
end
def set_heater_conf()
# TODO
end
def read_field_data
data = []
@i2c.begin(ID)
@i2c.lwrite(REG_FIELD0)
@i2c.end(0)
@i2c.request(ID, LEN_FIELD)
LEN_FIELD.times do
data << @i2c.lread()
end
@adc_pres = (data[2] << 12) | (data[3] << 4) | (data[4] >> 4)
@adc_temp = (data[5] << 12) | (data[6] << 4) | (data[7] >> 4)
@adc_hum = (data[8] << 8) | data[9]
puts("adc_temp: #{@adc_temp}")
puts("adc_pres: #{@adc_pres}")
puts("adc_hum: #{@adc_hum}")
end
# Calculate temperature
# return: 100x degrees Celsius
def calc_temp
var1 = (@adc_temp >> 3) - (@par_t1 << 1)
var2 = (var1 * @par_t2) >> 11
var3 = ((((var1 >> 1) * (var1 >> 1)) >> 12) * (@par_t3 << 4)) >> 14
@t_fine = var2 + var3
@temp_comp = ((@t_fine * 5) + 128) >> 8
return @temp_comp
end
# Calculate pressure
# return: 100x hPa
def calc_pres
var1 = (@t_fine >> 1) - 64000
var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * @par_p6) >> 2
var2 = var2 + ((var1 * @par_p5) << 1)
var2 = (var2 >> 2) + (@par_p4 << 16)
var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * (@par_p3 << 5)) >> 3) + ((@par_p2 * var1) >> 1)
var1 = var1 >> 18
var1 = ((32768 + var1) * @par_p1) >> 15
press_comp = 1048576 - @adc_pres
press_comp = (press_comp - (var2 >> 12)) * 3125
if press_comp >= (1 << 30) then
press_comp = (press_comp.div(var1)) << 1
else
press_comp = (press_comp << 1).div(var1)
end
var1 = (@par_p9 * (((press_comp >> 3) * (press_comp >> 3)) >> 13)) >> 12
var2 = ((press_comp >> 2) * @par_p8) >> 13
var3 = ((press_comp >> 8) * (press_comp >> 8) * (press_comp >> 8) * @par_p10) >> 17
press_comp = press_comp + ((var1 + var2 + var3 + (@par_p7 << 7)) >> 4)
return press_comp
end
# Calculate humidity
# return: 1000x %rH
def calc_hum
temp_scaled = @temp_comp
var1 = @adc_hum - (@par_h1 << 4) - (((temp_scaled * @par_h3).div(100)) >> 1)
var2 = (@par_h2 * (((temp_scaled * @par_h4).div(100)) + (((temp_scaled * ((temp_scaled * @par_h5).div(100))) >> 6).div(100)) + (1 << 14))) >> 10
var3 = var1 * var2
var4 = ((@par_h6 << 7) + ((temp_scaled * @par_h7).div(100))) >> 4
var5 = ((var3 >> 14) * (var3 >> 14)) >> 10
var6 = (var4 * var5) >> 1
comp_hum = (((var3 + var6) >> 10) * 1000) >> 12
# Limit the result between 0 and 100.000.
comp_hum = [[comp_hum, 100000].min, 0].max
return comp_hum
end
end
def adj_digit(num, digit)
s = num.to_s
s[-digit,0] = '.'
return s
end
def align_right(s, digit)
return " " * (digit - s.length) + s
end
lcd = LCD.new
bme680 = BME680.new
bme680.init
lcd.init
bme680.get_calib_data
lcd.init2
bme680.set_conf(5, 5, 5, 3)
lcd.init3
delay(2)
# Custom symbols
# Degree symbol
lcd.set_cgram(0, [0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# DI (Discomfort Index)
lcd.set_cgram(1, [0x18, 0x14, 0x14, 0x18, 0x07, 0x02, 0x02, 0x07])
#t_end = micros()
t_end = millis()
t_delta = 0
loop do
t_start = t_end
led(1)
bme680.set_op_mode(1)
led(0)
bme680.set_op_mode(0)
bme680.read_field_data
temp = bme680.calc_temp
s = adj_digit(temp, 2)
puts("temp: #{s} C")
lcd.set_cursor(0, 0)
lcd.print("#{align_right(s[0..-2], 5)}\x00C")
hum = bme680.calc_hum
s = adj_digit(hum, 3)
puts("hum: #{s} %")
#lcd.set_cursor(7, 0)
lcd.print(" #{align_right(s[0..-3], 5)} %")
pres = bme680.calc_pres
s = adj_digit(pres, 2)
puts("pres: #{s} hPa")
lcd.set_cursor(0, 1)
lcd.print("#{align_right(s[0..-2], 6)} hPa")
# Discomfort Index (100x)
# DI = 0.81Td + 0.01H(0.99Td - 14.3) + 46.3
di = (81 * temp + (hum * ((99 * temp).div(100) - 1430)).div(1000)).div(100) + 4630
s = adj_digit(di, 2)
puts("DI: #{s}")
#lcd.set_cursor(10, 1)
lcd.print(" \x01#{align_right(s[0..-4], 3)}")
puts
#delayMicroseconds(1000000 - t_delta)
#t_end = micros()
#t_delta = (t_end - t_start) - (1000000 - t_delta)
delay(1000 - t_delta)
t_end = millis()
t_delta = (t_end - t_start) - (1000 - t_delta)
puts("interval: #{t_end - t_start}, t_delta: #{t_delta}")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment