diff --git a/samples/mcp9808.go b/samples/mcp9808.go new file mode 100644 index 0000000..03a7cc9 --- /dev/null +++ b/samples/mcp9808.go @@ -0,0 +1,119 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "github.com/kidoman/embd" + _ "github.com/kidoman/embd/host/rpi" + "github.com/kidoman/embd/sensor/mcp9808" +) + +func main() { + bus := embd.NewI2CBus(1) + defer embd.CloseI2C() + + therm, _ := mcp9808.New(bus) + // set sensor to low power mode when we're done + defer therm.SetShutdownMode(true) + + if id, err := therm.ManufacturerID(); err == nil { + fmt.Printf("Manufacturer ID: 0x%x\n", id) + } + + if devID, rev, err := therm.DeviceID(); err == nil { + fmt.Printf("Device ID: 0x%x rev. 0x%x\n", devID, rev) + } + + therm.SetShutdownMode(false) + therm.SetCriticalTempLock(false) + therm.SetWindowTempLock(false) + therm.SetAlertMode(true) + therm.SetInterruptClear(true) + therm.SetAlertStatus(true) + therm.SetAlertControl(true) + therm.SetAlertSelect(false) + therm.SetAlertPolarity(true) + + // get faster results (130ms vs 250ms default) + therm.SetTempResolution(mcp9808.EighthC) + + config, _ := therm.Config() + fmt.Printf("New Config: %b\n", config) + + if err := therm.SetCriticalTemp(TempFToC(95)); err != nil { + panic(err) + } + critTemp, err := therm.CriticalTemp() + if err != nil { + fmt.Printf("Error reading critical temp limit: %s\n", err.Error()) + } + fmt.Printf("Critical Temp set to: %fC\n", critTemp) + + if err := therm.SetWindowTempLower(TempFToC(60)); err != nil { + panic(err) + } + lowerTemp, err := therm.WindowTempLower() + if err != nil { + fmt.Printf("Error reading lower temp limit: %s\n", err.Error()) + } + fmt.Printf("Lower Temp Limit set to: %fC\n", lowerTemp) + + if err := therm.SetWindowTempUpper(TempFToC(80)); err != nil { + panic(err) + } + upperTemp, _ := therm.WindowTempUpper() + fmt.Printf("Upper Temp Limit set to: %fC\n", upperTemp) + + therm.SetCriticalTempLock(true) + therm.SetWindowTempLock(true) + + alert, err := embd.NewDigitalPin(23) + if err != nil { + panic(err) + } + defer alert.Close() + + alert.SetDirection(embd.In) + alert.ActiveLow(false) + + err = alert.Watch(embd.EdgeRising, func(alert embd.DigitalPin) { + fmt.Printf("Temperature is outside the specified window!\n") + therm.SetInterruptClear(true) + therm.Config() + }) + if err != nil { + panic(err) + } + + cancel := make(chan bool) + go func() { + reader := bufio.NewReader(os.Stdin) + reader.ReadString('\n') + cancel <- true + }() + + for { + select { + case <-cancel: + return + default: + temp, err := therm.AmbientTemp() + if err != nil { + fmt.Printf("Error reading temp: %s\n", err.Error()) + } else { + fmt.Printf("Current temp is: %fF (%fC), Window Alert: %v, Critical Alert: %v\n", + TempCToF(temp.CelsiusDeg), temp.CelsiusDeg, temp.AboveUpper || temp.BelowLower, temp.AboveCritical) + } + } + } +} + +func TempCToF(tempC float64) float64 { + return tempC*9/5 + 32 +} + +func TempFToC(tempF float64) float64 { + return (tempF - 32) * 5 / 9 +} diff --git a/sensor/mcp9808/mcp9808.go b/sensor/mcp9808/mcp9808.go new file mode 100644 index 0000000..8b406ff --- /dev/null +++ b/sensor/mcp9808/mcp9808.go @@ -0,0 +1,382 @@ +// Package mcp9808 is a driver for the MCP9808 temperature sensor +// and all code is based off of the documentation found here: +// http://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf +package mcp9808 + +import "github.com/kidoman/embd" + +const ( + // default I2C address for device + address = 0x18 + + // Register addresses. + regConfig = iota // starts at 1, this is what we want + regUpperTemp + regLowerTemp + regCriticalTemp + regAmbientTemp + regManufID + regDeviceID + regResolution +) + +const ( + configAlertMode uint16 = 1 << iota + configAlertPolarity + configAlertSelect + configAlertControl + configAlertStatus + configInterruptClear + configWindowTempLock + configCriticalTempLock + configShutDown +) + +// MCP9808 represents a MCP9808 temperature sensor. +type MCP9808 struct { + Bus embd.I2CBus + config uint16 +} + +// New returns a handle to a MCP9808 sensor. +func New(bus embd.I2CBus) (*MCP9808, error) { + d := &MCP9808{Bus: bus} + + // initialize the configuration + _, err := d.Config() + return d, err +} + +// ManufacturerID reads the device manufacturer ID +func (d *MCP9808) ManufacturerID() (uint16, error) { + return d.Bus.ReadWordFromReg(address, regManufID) +} + +// DeviceID reads the device ID and revision +func (d *MCP9808) DeviceID() (uint8, uint8, error) { + devIDRev, err := d.Bus.ReadWordFromReg(address, regDeviceID) + if err != nil { + return 0, 0, err + } + + return uint8(devIDRev >> 8), uint8(devIDRev & 0xFF), nil +} + +// Config gets the config word from the device. +func (d *MCP9808) Config() (uint16, error) { + config, err := d.Bus.ReadWordFromReg(address, regConfig) + if err != nil { + return 0, err + } + d.config = config + return d.config, nil +} + +// setConfig writes the sensor's config word to the device and returns the resulting config +func (d *MCP9808) setConfig() error { + return d.Bus.WriteWordToReg(address, regConfig, d.config) +} + +// flipConfig bit sets (1, set = true) or clears (0, set = false) a bit within the config word +func (d *MCP9808) flipConfigBit(val uint16, set bool) error { + if set { + d.config |= val + } else { + d.config &= ^val + } + return d.setConfig() +} + +func (d *MCP9808) readConfigValue(val uint16) (bool, error) { + config, err := d.Config() + return !(config&(1<> 9), err +} + +// SetTempHysteresis - TUPPER and TLOWER Limit Hysteresis bits +// 00 = 0°C (power-up default) +// 01 = +1.5°C +// 10 = +3.0°C +// 11 = +6.0°C +// The hysteresis applies for decreasing temperature only (hot to cold) or as temperature +// drifts below the specified limit. +// This bit can not be altered when either of the Lock bits are set (bit 6 and bit 7). +// Thi s bit can be programmed in Shutdown mode. +func (d *MCP9808) SetTempHysteresis(val Hysteresis) error { + d.config = d.config - d.config&^(d.config>>9) + uint16(val)<<9 + return d.setConfig() +} + +// ShutdownMode bit +// 0 (false) = Continuous conversion (power-up default) +// 1 (true) = Shutdown (Low-Power mode) +// In shutdown, all power-consuming activities are disabled, though all registers can be written to or read. +func (d *MCP9808) ShutdownMode() (bool, error) { + return d.readConfigValue(configShutDown) +} + +// SetShutdownMode bit +// 0 (false) = Continuous conversion (power-up default) +// 1 (true) = Shutdown (Low-Power mode) +// In shutdown, all power-consuming activities are disabled, though all registers can be written to or read. +// This bit cannot be set to ‘1’ when either of the Lock bits is set (bit 6 and bit 7). However, it can be +// cleared to ‘0’ for continuous conversion while locked +func (d *MCP9808) SetShutdownMode(set bool) error { + return d.flipConfigBit(configShutDown, set) +} + +// CriticalTempLock - TCRIT Lock bit +// 0 (false) = Unlocked. TCRIT register can be written (power-up default) +// 1 (true) = Locked. TCRIT register can not be written +// When enabled, this bit remains set to ‘1’ or locked until cleared by an internal Reset +func (d *MCP9808) CriticalTempLock() (bool, error) { + return d.readConfigValue(configCriticalTempLock) +} + +// SetCriticalTempLock - TCRIT Lock bit +// 0 (false) = Unlocked. TCRIT register can be written (power-up default) +// 1 (true) = Locked. TCRIT register can not be written +// When enabled, this bit remains set to ‘1’ or locked until cleared by an internal Reset +// This bit can be programmed in Shutdown mode. +func (d *MCP9808) SetCriticalTempLock(locked bool) error { + return d.flipConfigBit(configCriticalTempLock, locked) +} + +// WindowTempLock - TUPPER and TLOWER Window Lock bit +// 0 (false) = Unlocked; TUPPER and TLOWER registers can be written (power-up default) +// 1 (true) = Locked; TUPPER and TLOWER registers can not be written +// When enabled, this bit remains set to ‘1’ or locked until cleared by a Power-on Reset +func (d *MCP9808) WindowTempLock() (bool, error) { + return d.readConfigValue(configWindowTempLock) +} + +// SetWindowTempLock - TUPPER and TLOWER Window Lock bit +// 0 (false) = Unlocked; TUPPER and TLOWER registers can be written (power-up default) +// 1 (true) = Locked; TUPPER and TLOWER registers can not be written +// When enabled, this bit remains set to ‘1’ or locked until cleared by a Power-on Reset +// This bit can be programmed in Shutdown mode. +func (d *MCP9808) SetWindowTempLock(locked bool) error { + return d.flipConfigBit(configWindowTempLock, locked) +} + +// InterruptClear - Interrupt Clear bit +// 0 (false) = No effect (power-up default) +// 1 (true) = Clear interrupt output; when read, this bit returns to ‘0’ +func (d *MCP9808) InterruptClear() (bool, error) { + return d.readConfigValue(configInterruptClear) +} + +// SetInterruptClear - Interrupt Clear bit +// 0 (false) = No effect (power-up default) +// 1 (true) = Clear interrupt output; when read, this bit returns to ‘0’ +// This bit can not be set to ‘1’ in Shutdown mode, but it can be cleared after the device enters Shutdown +// mode. +func (d *MCP9808) SetInterruptClear(set bool) error { + return d.flipConfigBit(configInterruptClear, set) +} + +// AlertStatus Alert Output Status bit +// 0 (false) = Alert output is not asserted by the device (power-up default) +// 1 (true) = Alert output is asserted as a comparator/Interrupt or critical temperature output +func (d *MCP9808) AlertStatus() (bool, error) { + return d.readConfigValue(configAlertStatus) +} + +// SetAlertStatus Alert Output Status bit +// 0 (false) = Alert output is not asserted by the device (power-up default) +// 1 (true) = Alert output is asserted as a comparator/Interrupt or critical temperature output +// This bit can not be set to ‘1’ or cleared to ‘0’ in Shutdown mode. However, if the Alert output is configured +// as Interrupt mode, and if the host controller clears to ‘0’, the interrupt, using bit 5 while the device +// is in Shutdown mode, then this bit will also be cleared ‘0’. +func (d *MCP9808) SetAlertStatus(set bool) error { + return d.flipConfigBit(configAlertStatus, set) +} + +// AlertControl - Alert Output Control bit +// 0 (false) = Disabled (power-up default) +// 1 (true) = Enabled +func (d *MCP9808) AlertControl() (bool, error) { + return d.readConfigValue(configAlertControl) +} + +// SetAlertControl - Alert Output Control bit +// 0 (false) = Disabled (power-up default) +// 1 (true) = Enabled +// This bit can not be altered when either of the Lock bits are set (bit 6 and bit 7). +// This bit can be programmed in Shutdown mode, but the Alert output will not assert or deassert. +func (d *MCP9808) SetAlertControl(set bool) error { + return d.flipConfigBit(configAlertControl, set) +} + +// AlertSelect - Alert Output Select bit +// 0 (false) = Alert output for TUPPER, TLOWER and TCRIT (power-up default) +// 1 (true) = TA > TCRIT only (TUPPER and TLOWER temperature boundaries are disabled) +func (d *MCP9808) AlertSelect() (bool, error) { + return d.readConfigValue(configAlertSelect) +} + +// SetAlertSelect - Alert Output Select bit +// 0 (false) = Alert output for TUPPER, TLOWER and TCRIT (power-up default) +// 1 (true) = TA > TCRIT only (TUPPER and TLOWER temperature boundaries are disabled) +// When the Alarm Window Lock bit is set, this bit cannot be altered until unlocked (bit 6). +// This bit can be programmed in Shutdown mode, but the Alert output will not assert or deassert. +func (d *MCP9808) SetAlertSelect(set bool) error { + return d.flipConfigBit(configAlertSelect, set) +} + +// AlertPolarity - Alert Output Polarity bit +// 0 (false) = Active-low (power-up default; pull-up resistor required) +// 1 (true) = Active-high +func (d *MCP9808) AlertPolarity() (bool, error) { + return d.readConfigValue(configAlertPolarity) +} + +// SetAlertPolarity - Alert Output Polarity bit +// 0 (false) = Active-low (power-up default; pull-up resistor required) +// 1 (true) = Active-high +// This bit cannot be altered when either of the Lock bits are set (bit 6 and bit 7). +// This bit can be programmed in Shutdown mode, but the Alert output will not assert or deassert. +func (d *MCP9808) SetAlertPolarity(set bool) error { + return d.flipConfigBit(configAlertPolarity, set) +} + +// AlertMode - Alert Output Mode bit +// 0 (false) = Comparator output (power-up default) +// 1 (true) = Interrupt output +func (d *MCP9808) AlertMode() (bool, error) { + return d.readConfigValue(configAlertMode) +} + +// SetAlertMode - Alert Output Mode bit +// 0 (false) = Comparator output (power-up default) +// 1 (true) = Interrupt output +// This bit cannot be altered when either of the Lock bits are set (bit 6 and bit 7). +// This bit can be programmed in Shutdown mode, but the Alert output will not assert or deassert. +func (d *MCP9808) SetAlertMode(set bool) error { + return d.flipConfigBit(configAlertMode, set) +} + +// Temperature contains the ambient temperature along with alert values. +type Temperature struct { + CelsiusDeg float64 + AboveCritical, AboveUpper, BelowLower bool +} + +// readTempC reads from the reg temperature register and returns the current temperature value in celsius +func (d *MCP9808) readTempC(reg byte) (float64, error) { + temp, err := d.Bus.ReadWordFromReg(address, reg) + if err != nil { + return 0, err + } + + return convertWordToTempC(temp), nil +} + +func convertWordToTempC(temp uint16) float64 { + return float64(int16(temp<<3)>>3) / 16 +} + +func (d *MCP9808) setTemp(reg byte, newTemp float64) error { + return d.Bus.WriteWordToReg(address, reg, uint16(newTemp*16+2)&0x1ffc) +} + +// AmbientTemp reads the current sensor value along with the flags denoting what boundaries the +// current temperature exceeds. +func (d *MCP9808) AmbientTemp() (*Temperature, error) { + temp, err := d.Bus.ReadWordFromReg(address, regAmbientTemp) + if err != nil { + return nil, err + } + + tempResult := &Temperature{ + AboveCritical: temp&0x8000 == 0x8000, + AboveUpper: temp&0x4000 == 0x4000, + BelowLower: temp&0x2000 == 0x2000} + tempResult.CelsiusDeg = convertWordToTempC(temp) + + return tempResult, nil +} + +// CriticalTemp reads the current temperature set in the critical temperature register. +func (d *MCP9808) CriticalTemp() (float64, error) { + return d.readTempC(regCriticalTemp) +} + +// SetCriticalTemp when the temperature goes above the set value the alert will be +// triggered if enabled. This has no effect if CriticalTempLock is set. +func (d *MCP9808) SetCriticalTemp(newTemp float64) error { + return d.setTemp(regCriticalTemp, newTemp) +} + +// WindowTempUpper reads the current temperature set in the upper window temperature register. +func (d *MCP9808) WindowTempUpper() (float64, error) { + return d.readTempC(regUpperTemp) +} + +// SetWindowTempUpper when the temperature goes above the set value the alert will be +// triggered if enabled. This has no effect if WindowTempLock is set. +func (d *MCP9808) SetWindowTempUpper(newTemp float64) error { + return d.setTemp(regUpperTemp, newTemp) +} + +// WindowTempLower reads the current temperature set in the lower window temperature register. +func (d *MCP9808) WindowTempLower() (float64, error) { + return d.readTempC(regLowerTemp) +} + +// SetWindowTempLower when the temperature goes below the set value the alert will be +// triggered if enabled. This has no effect if WindowTempLock is set. +func (d *MCP9808) SetWindowTempLower(newTemp float64) error { + return d.setTemp(regLowerTemp, newTemp) +} + +// TempResolution reads the current temperature accuracy from the sensor (affects temperature read speed) +// 0 - +/- .5 degrees C (~30ms) +// 1 - +/- .25 degrees C (~65ms) +// 2 - +/- .125 degrees C (~130ms) +// 3 (default) - +/- .0625 degrees C (~250ms) +type TempResolution uint8 + +const ( + HalfC TempResolution = iota + QuarterC + EighthC + SixteenthC +) + +// TempResolution reads the temperature resolution from the sensor. +func (d *MCP9808) TempResolution() (TempResolution, error) { + res, err := d.Bus.ReadByteFromReg(address, regResolution) + return TempResolution(res), err +} + +// SetTempResolution writes a new temperature resolution to the sensor +func (d *MCP9808) SetTempResolution(res TempResolution) error { + return d.Bus.WriteByteToReg(address, regResolution, byte(res)) +}