Skip to content

Commit

Permalink
updating content
Browse files Browse the repository at this point in the history
  • Loading branch information
dmccreary committed Jan 6, 2025
1 parent 4acf277 commit 40a40c6
Show file tree
Hide file tree
Showing 7 changed files with 567 additions and 0 deletions.
39 changes: 39 additions & 0 deletions docs/lessons/13-real-time-clocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,42 @@ Unless you need absolute minimal components, stick with the DS3231 for better ac

The internal RTC example on the [MicroPython RP2](https://docs.micropython.org/en/latest/rp2/quickref.html#real-time-clock-rtc) site would work functionally, but would need resetting after each power cycle.

## Reading the Temperature from the DS3231 Real Time Clock

The math of reading the temperature from the DS3231 is unfortunately rather complicated. Here are the steps

1. Strip off the sign bit by doing a Boolean AND with 0x7f
2. If the MSB is set we need to make the temperature negative
3. Then we add the fraction from the LSB
4. Finally, we convert the temperature in C to F

```python
def read_temperature():
"""Read temperature from DS3231 RTC."""
# Read temperature registers
i2c.writeto(DS3231_ADDR, b'\x11')
temp_data = i2c.readfrom(DS3231_ADDR, 2)
temp_msb = temp_data[0]
temp_lsb = temp_data[1]

# Get raw temp value (ignoring sign bit)
raw_temp = temp_msb & 0x7F # Strip off sign bit

# 0xD7 & 0x7F = 0x57 = 87 decimal (original value minus sign bit)
# If sign bit was set, make it negative
if temp_msb & 0x80:
raw_temp = raw_temp ^ 0x7F # Invert the bits
raw_temp = -(raw_temp + 1) # Two's complement

# Add fraction from LSB
frac = (temp_lsb >> 6) * 0.25
temp_c = raw_temp + frac

# Convert to Fahrenheit
temp_f = (temp_c * 9.0 / 5.0) + 32.0

print(f"Raw temp (after sign bit removal): {raw_temp}")
print(f"Temperature: {temp_c}°C = {temp_f}°F")

return temp_f
```
15 changes: 15 additions & 0 deletions docs/setup/06-pico-w-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Pico W Setup

When installing the Pico "W" (Wireless), you will need to download
a different version of the MicroPython runtime.

The list of runtime images is on the
[https://micropython.org/download/RPI_PICO_W](https://micropython.org/download/RPI_PICO_W/) page.

In this example, we are using [v1.24.1 (2024-11-29) .uf2](https://github.com/micropython/micropython/releases/tag/v1.24.1).

Here is the image from Thonny that shows
what version you are using. Note that you MUST select BOTSEL button
before Thonny will download the new MicroPython runtime.

![Pico W Thonny Setup for MicroPython 1.24.1](./06-thonny-pico-w-setup.png)
Binary file added docs/setup/06-thonny-pico-w-setup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions src/kits/large-oled-w/10-test-rtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from machine import I2C, Pin
import time

# I2C setup
i2c = I2C(0, sda=Pin(0), scl=Pin(1))

# Device addresses
RTC_ADDR = 0x68 # Both DS1307 and DS3231 use 0x68

def identify_rtc():
"""
Identify whether the RTC is a DS1307 or DS3231
Returns: String identifying the RTC type
"""
try:
# Try to read the status register (0x0F) - only exists on DS3231
i2c.writeto(RTC_ADDR, b'\x0F')
status = i2c.readfrom(RTC_ADDR, 1)[0]

# Try to read control register (0x0E) - only exists on DS3231
i2c.writeto(RTC_ADDR, b'\x0E')
control = i2c.readfrom(RTC_ADDR, 1)[0]

# If we got here, it's almost certainly a DS3231
# Try reading temperature registers as final confirmation
i2c.writeto(RTC_ADDR, b'\x11')
temp_data = i2c.readfrom(RTC_ADDR, 2)

return "DS3231 (Temperature-compensated RTC)"

except Exception as e:
# If we couldn't read those registers, it's probably a DS1307
# Let's verify by trying to read the control register (0x07) of DS1307
try:
i2c.writeto(RTC_ADDR, b'\x07')
control = i2c.readfrom(RTC_ADDR, 1)[0]
return "DS1307 (Basic RTC)"
except:
return "Unknown RTC device"

def main():
print("\nRTC Model Identifier")
print("-" * 40)

# First check if any device is present at RTC address
devices = i2c.scan()
if RTC_ADDR not in devices:
print(f"No RTC found at address 0x{RTC_ADDR:02X}")
return

# Identify the RTC
rtc_type = identify_rtc()
print(f"Found: {rtc_type}")

if "DS3231" in rtc_type:
# Read temperature for DS3231
i2c.writeto(RTC_ADDR, b'\x11')
temp_data = i2c.readfrom(RTC_ADDR, 2)
temp_msb = temp_data[0]
temp_lsb = (temp_data[1] >> 6) * 25 # 0.25°C precision
temp_c = temp_msb + (temp_lsb / 100.0)
temp_f = (temp_c * 9/5) + 32
print(f"Temperature: {temp_c:.2f}°C ({temp_f:.2f}°F)")

if __name__ == "__main__":
main()
159 changes: 159 additions & 0 deletions src/kits/large-oled-w/lib/ssd1306.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

from micropython import const
import framebuf


# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)

# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()

def init_display(self):
for cmd in (
SET_DISP, # display off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE, # start at line 0
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01, # display on
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()

def poweroff(self):
self.write_cmd(SET_DISP)

def poweron(self):
self.write_cmd(SET_DISP | 0x01)

def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)

def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))

def rotate(self, rotate):
self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
self.write_cmd(SET_SEG_REMAP | (rotate & 1))

def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)


class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)

def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)

def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)


class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time

self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)

def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)

def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
Loading

0 comments on commit 40a40c6

Please sign in to comment.