Skip to content

Add support for bme68x sensor #751

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open

Conversation

gandarez
Copy link

@gandarez gandarez commented Apr 7, 2025

This PR adds support for BME68X (BME680/BME688) a low power gas, pressure, temperature & humidity sensor.

It includes the support for Sleep and Forced modes as well I2C and SPI interfaces. It introduces the Options Pattern to make the initialization of bme68x package more flexible.

Simple example:

tsensor := bme68x.NewI2C(machine.I2C0,
    bme68x.WithIIRFilter(bme68x.Coeff4),
    bme68x.WithTemperatureOversampling(bme68x.Sampling8X),
    bme68x.WithPressureOversampling(bme68x.Sampling4X),
    bme68x.WithHumidityOversampling(bme68x.Sampling2X),
    bme68x.WithHeatrDuration(150),
    bme68x.WithHeatrTemperature(320),
)
if err := tsensor.Configure(); err != nil {
     logger.Error(fmt.Sprintf("error configuring sensor: %s", err.Error()))
     return
}

connected, err := tsensor.Connected()
if err != nil {
    logger.Error(fmt.Sprintf("error checking sensor connection: %s", err.Error()))
    return
}

if !connected {
    logger.Error("sensor not connected")
    return
}

if err := tsensor.SetMode(bme68x.ModeForced); err != nil {
    logger.Error(fmt.Sprintf("error setting sensor mode: %s", err.Error()))
    return
}

for {
    if err := tsensor.Read(); err != nil {
        logger.Error(fmt.Sprintf("error reading sensor: %s", err.Error()))

	time.Sleep(2 * time.Second)
	continue
    }

     logger.Info(strings.Repeat("-", 40))

     logger.Info(fmt.Sprintf("    Temperature: %.2f°C", tsensor.Temperature))
     logger.Info(fmt.Sprintf("    Pressure: %.fhPa", tsensor.Pressure/100))
     logger.Info(fmt.Sprintf("    Gas: %.1fKOhms", tsensor.GasResistance/1000))
     logger.Info(fmt.Sprintf("    Approx. Altitude: %.1fm", bme68x.CalcAltitude(seaLevelPressurehPa, tsensor.Pressure)))
     logger.Info(fmt.Sprintf("    Humidity: %.1f%% (%s)", tsensor.Humidity, humidityDescription))
     logger.Info(strings.Repeat("-", 40))

     time.Sleep(2 * time.Second)
}

Copy link
Contributor

@soypat soypat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of good stuff and also some stuff to improve. hope this helps :)

Comment on lines +11 to +22
// REG_MEM_PAGE is the MEM_PAGE address
REG_MEM_PAGE uint8 = 0xF3
// MEM_PAGE_MSK is mask for SPI memory page
MEM_PAGE_MSK uint8 = 0x10
// SPI_RD_MSK is the mask for reading a register in SPI
SPI_RD_MSK uint8 = 0x80
// SPI_WR_MSK is the mask for writing a register in SPI
SPI_WR_MSK uint8 = 0x7F
// MEM_PAGE0 is the SPI memory page 0
MEM_PAGE0 uint8 = 0x10
// MEM_PAGE1 is the SPI memory page 1
MEM_PAGE1 uint8 = 0x00
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe unexport

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is specific to SPI but it defines the register address of SPI interface as well the other registers which are all exported. I don't think it's such a bit problem having them exported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user already has access to the Read/Write layer, they don't really need them. They might need the registers but not this...

func (s *spi) read(reg uint8, data []byte) error {
reg |= SPI_RD_MSK

return s.bus.Tx([]byte{reg}, data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beware heap alloc here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how to avoid heap in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key is always pass in the spi's in-struct array to Tx. All you gotta do is fill it with the correct data.

s.bus.wbuf[0] = reg | SPI_RD_MSK
n := copy(s.bus.wbuf[:], data)
return s.bus.Tx(s.bus.wbuf[:n], s.bus.rbuf[:n])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, your driver is technically incorrect, you should always pass in equal length buffers to Tx, or one of them should be nil

data[0] &^= MEM_PAGE_MSK
data[0] |= (memoryPage & MEM_PAGE_MSK)

return s.bus.Tx([]byte{REG_MEM_PAGE | SPI_WR_MSK}, []byte{data[0]})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heap

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how to avoid heap in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same rules as above. use in-struct arrays. please refer to https://deploy-preview-451--tinygo.netlify.app/docs/guides/driver-design/

If the path to avoiding heap allocations is not clear please leave a comment here with what is missing or could be improved: tinygo-org/tinygo-site#451

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants