diff --git a/samples/mcp9808.go b/samples/mcp9808.go index 17fad1c..1db1189 100644 --- a/samples/mcp9808.go +++ b/samples/mcp9808.go @@ -10,18 +10,15 @@ import ( "github.com/kidoman/embd" _ "github.com/kidoman/embd/host/rpi" "github.com/kidoman/embd/sensor/mcp9808" - "github.com/stianeikeland/go-rpio" ) func main() { - if err := embd.InitI2C(); err != nil { - panic(err) - } + bus := embd.NewI2CBus(1) defer embd.CloseI2C() - bus := embd.NewI2CBus(1) - 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) @@ -32,51 +29,36 @@ func main() { } therm.SetShutdownMode(false) - - //therm.SetWindowTempLock(false) - // therm.SetCriticalTempLock(false) - - config, err := therm.WriteConfig() - if err != nil { - panic(err) - } - fmt.Printf("New Config: %b\n", config) + therm.SetAlertMode(true) + therm.SetInterruptClear(true) + therm.SetAlertStatus(true) therm.SetAlertControl(true) - //therm.SetInterruptClear(true) - //therm.SetAlertStatus(true) - //therm.SetAlertSelect(false) - //therm.SetAlertPolarity(true) - //therm.SetAlertMode(true) - config, err = therm.WriteConfig() - if err != nil { + therm.SetAlertSelect(false) + therm.SetAlertPolarity(false) + + config, _ := therm.Config() + fmt.Printf("New Config: %b\n", config) + + if err := therm.SetCriticalTemp(TempFToC(90)); err != nil { panic(err) } - fmt.Printf("New Config: %b\n", config) if err := therm.SetWindowTempUpper(TempFToC(80)); err != nil { panic(err) } + fmt.Printf("Set upper temp to %fC\n", TempFToC(80)) - temp, err := therm.AmbientTemp() + upperTemp, _ := therm.WindowTempUpper() + fmt.Printf("Upper Temp Limit set to: %fC\n", upperTemp) + + alert, err := embd.NewDigitalPin(23) if err != nil { panic(err) } - fmt.Printf("Temp is %f\n", TempCToF(temp.CelsiusDeg)) - - if err := embd.InitGPIO(); err != nil { - panic(err) - } - if err := rpio.Open(); err != nil { - log.Fatalf("Error: %v\n", err) - } - defer rpio.Close() defer embd.CloseGPIO() - alert := rpio.Pin(4) - alert.Input() - alert.PullDown() - - timer := time.Tick(time.Duration(5) * time.Second) + alert.SetDirection(embd.In) + alert.PullUp() cancel := make(chan bool) go func() { @@ -84,17 +66,26 @@ func main() { reader.ReadString('\n') cancel <- true }() + + timer := time.Tick(time.Duration(5) * time.Second) for { select { case <-timer: temp, err := therm.AmbientTemp() - if err == nil { - fmt.Printf("Ambient temp is: %f\n", TempCToF(temp.CelsiusDeg)) + 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) + } + status, err := alert.Read() + if err != nil { + log.Printf("Error reading pin: %s\n", err.Error()) + continue } - status := alert.Read() fmt.Printf("Status: %d\n\n", status) - if status == rpio.High { - fmt.Println("Alert temp has been reached.") + if status == embd.High { + fmt.Println("Alert temp has been reached!") return } case <-cancel: diff --git a/sensor/mcp9808/mcp9808.go b/sensor/mcp9808/mcp9808.go index 96f7de6..b456235 100644 --- a/sensor/mcp9808/mcp9808.go +++ b/sensor/mcp9808/mcp9808.go @@ -4,9 +4,8 @@ package mcp9808 import ( - "log" + "math" "sync" - "time" "github.com/kidoman/embd" ) @@ -16,14 +15,14 @@ const ( address = 0x18 // Register addresses. - regConfig = 0x01 - regUpperTemp = 0x02 - regLowerTemp = 0x03 - regCriticalTemp = 0x04 - regAmbientTemp = 0x05 - regManufID = 0x06 - regDeviceID = 0x07 - regResolution = 0x08 + regConfig = iota // starts at 1, this is what we want + regUpperTemp + regLowerTemp + regCriticalTemp + regAmbientTemp + regManufID + regDeviceID + regResolution ) const ( @@ -40,20 +39,14 @@ const ( // MCP9808 represents a MCP9808 temperature sensor. type MCP9808 struct { - Bus embd.I2CBus - Poll time.Duration - - cmu sync.RWMutex - + Bus embd.I2CBus + cmu sync.Mutex config uint16 - synced bool - - temps chan uint16 } // New returns a handle to a MCP9808 sensor. func New(bus embd.I2CBus) *MCP9808 { - return &MCP9808{Bus: bus, Poll: time.Second, synced: false} + return &MCP9808{Bus: bus} } // ManufacturerID reads the device manufacturer ID @@ -79,48 +72,41 @@ func (d *MCP9808) DeviceID() (uint8, uint8, error) { // Config returns the current word value of the sensor config struct and whether // that value reflects what is set on the sensor -func (d *MCP9808) Config() (uint16, bool) { - return d.config, d.synced +func (d *MCP9808) Config() (uint16, error) { + return d.readConfig() } // ReadConfig reads the config word from the device and writes it to the config attribute // this overwrites any changes that may have been made to the config attribute -func (d *MCP9808) ReadConfig() (uint16, error) { - d.cmu.RLock() - defer d.cmu.RUnlock() +func (d *MCP9808) readConfig() (uint16, error) { config, err := d.Bus.ReadWordFromReg(address, regConfig) if err != nil { return 0, err } d.config = config - d.synced = true return d.config, nil } // WriteConfig writes the sensor's config word to the device and returns the resulting config -func (d *MCP9808) WriteConfig() (uint16, error) { - d.cmu.Lock() - if err := d.Bus.WriteWordToReg(address, regConfig, d.config); err != nil { - return 0, err - } - d.cmu.Unlock() - - // read the config after write in case some changes were invalid - return d.ReadConfig() +func (d *MCP9808) writeConfig() 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) { +func (d *MCP9808) flipConfigBit(val uint16, set bool) error { + d.cmu.Lock() + defer d.cmu.Unlock() if set { d.config |= val } else { d.config &= ^val } - d.synced = false + return d.writeConfig() } -func (d *MCP9808) readConfigValue(val uint16) bool { - return !(d.config&(1<> 9) +func (d *MCP9808) TempHysteresis() (Hysteresis, error) { + _, err := d.readConfig() + return Hysteresis(d.config >> 9), err } // SetTempHysteresis - TUPPER and TLOWER Limit Hysteresis bits @@ -158,16 +145,19 @@ func (d *MCP9808) TempHysteresis() Hysteresis { // 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) { +func (d *MCP9808) SetTempHysteresis(val Hysteresis) error { + d.cmu.Lock() + defer d.cmu.Unlock() + d.config = d.config - d.config&^(d.config>>9) + uint16(val)<<9 - d.synced = false + return d.writeConfig() } // 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 { +func (d *MCP9808) ShutdownMode() (bool, error) { return d.readConfigValue(configShutDown) } @@ -177,15 +167,15 @@ func (d *MCP9808) ShutdownMode() bool { // 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) { - d.flipConfigBit(configShutDown, set) +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 { +func (d *MCP9808) CriticalTempLock() (bool, error) { return d.readConfigValue(configCriticalTempLock) } @@ -194,31 +184,31 @@ func (d *MCP9808) CriticalTempLock() bool { // 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) { - d.flipConfigBit(configCriticalTempLock, locked) +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 { +func (d *MCP9808) WindowTempLock() (bool, error) { return d.readConfigValue(configWindowTempLock) } -// SetWindowTempLock - TUPPER and TLOWER Window Lock bit +// 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) { - d.flipConfigBit(configWindowTempLock, locked) +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 { +func (d *MCP9808) InterruptClear() (bool, error) { return d.readConfigValue(configInterruptClear) } @@ -227,14 +217,14 @@ func (d *MCP9808) InterruptClear() bool { // 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) { - d.flipConfigBit(configInterruptClear, set) +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 { +func (d *MCP9808) AlertStatus() (bool, error) { return d.readConfigValue(configAlertStatus) } @@ -244,14 +234,14 @@ func (d *MCP9808) AlertStatus() bool { // 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) { - d.flipConfigBit(configAlertStatus, set) +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 { +func (d *MCP9808) AlertControl() (bool, error) { return d.readConfigValue(configAlertControl) } @@ -260,14 +250,14 @@ func (d *MCP9808) AlertControl() bool { // 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) { - d.flipConfigBit(configAlertControl, set) +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 { +func (d *MCP9808) AlertSelect() (bool, error) { return d.readConfigValue(configAlertSelect) } @@ -276,14 +266,14 @@ func (d *MCP9808) AlertSelect() bool { // 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) { - d.flipConfigBit(configAlertSelect, set) +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 { +func (d *MCP9808) AlertPolarity() (bool, error) { return d.readConfigValue(configAlertPolarity) } @@ -292,14 +282,14 @@ func (d *MCP9808) AlertPolarity() bool { // 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) { - d.flipConfigBit(configAlertPolarity, set) +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 { +func (d *MCP9808) AlertMode() (bool, error) { return d.readConfigValue(configAlertMode) } @@ -308,8 +298,8 @@ func (d *MCP9808) AlertMode() bool { // 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) { - d.flipConfigBit(configAlertMode, set) +func (d *MCP9808) SetAlertMode(set bool) error { + return d.flipConfigBit(configAlertMode, set) } // Temperature contains the ambient temperature along with alert values. @@ -318,6 +308,60 @@ type Temperature struct { 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 { + wholeNum := float64(temp&0xFF0) / 16.0 + fraction := float64(temp&0xF) * .0625 + + tempRead := wholeNum + fraction + + if !(temp&(1<<12) == 0) { // read sign bit + tempRead *= -1 + } + return tempRead +} + +func (d *MCP9808) setTemp(reg byte, newTemp float64) error { + d.cmu.Lock() + defer d.cmu.Unlock() + + var signBit uint16 + if newTemp < 0 { + newTemp *= -1 + signBit = 0x1000 + } + + wholeNum, fraction := math.Modf(newTemp) + var roundedFrac uint16 + switch { + case fraction < .125: + roundedFrac = 0 + case fraction < .375: + roundedFrac = 1 + case fraction < .625: + roundedFrac = 2 + case fraction < .875: + roundedFrac = 3 + default: + roundedFrac = 4 + } + newTempWord := signBit + uint16(wholeNum)*16 + roundedFrac*4 + + if err := d.Bus.WriteWordToReg(address, reg, newTempWord); err != nil { + return err + } + return nil +} + // AmbientTemp reads the current sensor value along with the flags denoting what boundaries the // current temperature exceeds. func (d *MCP9808) AmbientTemp() (*Temperature, error) { @@ -326,57 +370,15 @@ func (d *MCP9808) AmbientTemp() (*Temperature, error) { return nil, err } - tempC := temp &^ 0xF000 - - wholeNum := float64(tempC&^0xF) / 16.0 - fraction := tempC &^ 0xFF0 - tempResult := &Temperature{ AboveCritical: !(temp&(1<<15) == 0), AboveUpper: !(temp&(1<<14) == 0), BelowLower: !(temp&(1<<13) == 0)} - tempResult.CelsiusDeg = wholeNum + (float64(fraction) / 10000.0) - if !(temp&(1<<12) == 0) { // read sign bit - tempResult.CelsiusDeg *= -1 - } + tempResult.CelsiusDeg = convertWordToTempC(temp) + return tempResult, nil } -// readTempC reads from the ambient 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 - } - - log.Printf("\nTA vs TCrit: %v\nTA vs TUpper: %v\nTA vs TLower: %v\n", !(temp&(1<<15) == 0), !(temp&(1<<14) == 0), !(temp&(1<<13) == 0)) - - wholeTempC := temp &^ 0xF000 - - wholeNum := float64(wholeTempC&^0xF) / 16.0 - fraction := wholeTempC &^ 0xFF0 - - tempRead := wholeNum + (float64(fraction) / 10000.0) - if !(temp&(1<<12) == 0) { // read sign bit - tempRead *= -1 - } - return tempRead, nil -} - -func (d *MCP9808) setTemp(reg byte, newTemp float64) error { - var signBit uint16 - if newTemp < 0 { - newTemp *= -1 - signBit = 0x1000 - } - newTempWord := uint16(newTemp)*15 + uint16(newTemp*16) + signBit - - if err := d.Bus.WriteWordToReg(address, reg, newTempWord); err != nil { - return err - } - return nil -} - // CriticalTempUpper reads the current temperature set in the critical temperature register. func (d *MCP9808) CriticalTempUpper() (float64, error) { return d.readTempC(regCriticalTemp) @@ -385,11 +387,7 @@ func (d *MCP9808) CriticalTempUpper() (float64, error) { // SetCriticalTemp when the temperature goes above the set value the alert will be // triggered if enabled. func (d *MCP9808) SetCriticalTemp(newTemp float64) error { - d.cmu.Lock() - defer d.cmu.Unlock() - - d.SetCriticalTempLock(false) - if _, err := d.WriteConfig(); err != nil { + if err := d.setCriticalTempLock(false); err != nil { return err } @@ -397,9 +395,7 @@ func (d *MCP9808) SetCriticalTemp(newTemp float64) error { return err } - d.SetCriticalTempLock(true) - _, err := d.WriteConfig() - return err + return d.setCriticalTempLock(true) } // WindowTempUpper reads the current temperature set in the upper window temperature register. @@ -410,11 +406,7 @@ func (d *MCP9808) WindowTempUpper() (float64, error) { // SetWindowTempUpper when the temperature goes above the set value the alert will be // triggered if enabled. func (d *MCP9808) SetWindowTempUpper(newTemp float64) error { - d.cmu.Lock() - defer d.cmu.Unlock() - - d.SetWindowTempLock(false) - if _, err := d.WriteConfig(); err != nil { + if err := d.setWindowTempLock(false); err != nil { return err } @@ -422,9 +414,7 @@ func (d *MCP9808) SetWindowTempUpper(newTemp float64) error { return err } - d.SetWindowTempLock(true) - _, err := d.WriteConfig() - return err + return d.setWindowTempLock(true) } // WindowTempLower reads the current temperature set in the lower window temperature register. @@ -435,11 +425,7 @@ func (d *MCP9808) WindowTempLower() (float64, error) { // SetWindowTempLower when the temperature goes below the set value the alert will be // triggered if enabled. func (d *MCP9808) SetWindowTempLower(newTemp float64) error { - d.cmu.Lock() - defer d.cmu.Unlock() - - d.SetWindowTempLock(false) - if _, err := d.WriteConfig(); err != nil { + if err := d.setWindowTempLock(false); err != nil { return err } @@ -447,9 +433,7 @@ func (d *MCP9808) SetWindowTempLower(newTemp float64) error { return err } - d.SetWindowTempLock(true) - _, err := d.WriteConfig() - return err + return d.setWindowTempLock(true) } // TempResolution reads the current temperature accuracy from the sensor (affects temperature read speed)