Programming for Robotics 1, Chapter 2: Challenge 2 solution (a temperature sensor driver on a fake bus) Formulas: raw = (high x 256) + low (little endian: the LOW byte is at the lower register address, so it is read first) if raw >= 32768: raw = raw - 65536 (signed 16-bit, two's complement) temperature = raw / 256 degrees C (256 LSB per degree, so one LSB = 1/256 = 0.00391 C) Worked values: raw 0x1900 = 6400 6400 / 256 = 25.0 C raw 0xF600 = 62 976 -> 62 976 - 65 536 = -2560 -2560 / 256 = -10.0 C and the register bytes for 25.0 C are TEMP_L = 0x00, TEMP_H = 0x19 (the little-endian order is the opposite of the IMU of the chapter, which put the high byte first). The driver starts one conversion by writing CONFIG bit 0, then reads STATUS up to max_polls times and gives up with an OSError instead of waiting forever (the non-blocking rule of Programming for Robotics 1 section 5: every wait has a limit). The fake sensor needs a few status reads before it is ready, and one instance never becomes ready, to test the timeout. The code was run, and the output below is what it printed: CODE: class FakeBus: """Just enough of machine.I2C: devices are objects with read(reg) and write(reg, value).""" def __init__(self): self.devices = {} def readfrom_mem(self, addr, reg, n): if addr not in self.devices: raise OSError(19) return bytes(self.devices[addr].read(reg + i) for i in range(n)) def writeto_mem(self, addr, reg, data): if addr not in self.devices: raise OSError(19) for i, value in enumerate(data): self.devices[addr].write(reg + i, value) class FakeTempSensor: """TEMP-X: 0x00 TEMP_L, 0x01 TEMP_H (signed, 1/256 C per LSB), 0x02 STATUS bit 0 = ready, 0x03 CONFIG bit 0 = start.""" def __init__(self, temp_c, status_reads_until_ready): self.raw = int(round(temp_c * 256)) & 0xFFFF self.delay = status_reads_until_ready self.reads_left = 0 self.converting = False def read(self, reg): if reg == 0x00: return self.raw & 0xFF if reg == 0x01: return self.raw >> 8 if reg == 0x02: if self.converting: if self.reads_left > 0: self.reads_left -= 1 if self.reads_left == 0: self.converting = False return 0 if self.converting else 1 return 0 def write(self, reg, value): if reg == 0x03 and value & 1: self.converting = True self.reads_left = self.delay class TempSensor: ADDR = 0x48 REG_DATA = 0x00 REG_STATUS = 0x02 REG_CONFIG = 0x03 def __init__(self, bus, addr=ADDR): self.bus = bus self.addr = addr def read_c(self, max_polls=10): self.bus.writeto_mem(self.addr, self.REG_CONFIG, b"\x01") # start one conversion for _ in range(max_polls): # a bounded wait: never forever if self.bus.readfrom_mem(self.addr, self.REG_STATUS, 1)[0] & 1: low, high = self.bus.readfrom_mem(self.addr, self.REG_DATA, 2) raw = (high << 8) | low # little endian: low byte first if raw & 0x8000: raw -= 0x10000 return raw / 256 raise OSError("temperature sensor timed out") bus = FakeBus() for temp_c, delay in ((25.0, 3), (-10.0, 1), (0.5, 0)): bus.devices[0x48] = FakeTempSensor(temp_c, delay) print("true %6.2f C, ready after %d status reads -> %.4f C" % (temp_c, delay, TempSensor(bus).read_c())) bus.devices[0x48] = FakeTempSensor(25.0, 50) try: TempSensor(bus).read_c(max_polls=10) except OSError as err: print("never ready:", err) print("raw 0x1900 -> %.1f C raw 0xF600 -> %.1f C one LSB = %.5f C" % (0x1900 / 256, (0xF600 - 0x10000) / 256, 1 / 256)) END CODE OUTPUT: true 25.00 C, ready after 3 status reads -> 25.0000 C true -10.00 C, ready after 1 status reads -> -10.0000 C true 0.50 C, ready after 0 status reads -> 0.5000 C never ready: temperature sensor timed out raw 0x1900 -> 25.0 C raw 0xF600 -> -10.0 C one LSB = 0.00391 C END OUTPUT Reading the output: a delay of 0 behaves like a delay of 1 (the first status read already shows ready). All three true values are multiples of 1/256 C, so the fake returns them exactly; a value such as 25.3 C would come back as 25.2969 C, the nearest multiple of 0.00391 C. Check on method: the two bugs this exercise is built to catch are the byte order (the IMU in the chapter is big endian, this sensor is little endian: read the datasheet, never assume) and the missing sign step (-10.0 C would otherwise read as 62976 / 256 = 246.0 C). A third is the unbounded wait: a polling loop with no limit turns a loose wire into a frozen robot.