一、目的
这一节我们来学习如何使用合宙的ESP32 C3连接DS1307时钟模块在ssd1306屏幕上显示时间。前面章节我们学习过DS1302时钟模块,请看这个文章:
ESP32 C3开发板(MicroPython v1.19.1 on 2022-06-18)+ RTC I2C模块 24C32存储器 DS1307时钟模块 + 0.96寸I2C SSD1306显示屏 + Win10商业版 + 几根杜邦线 + 面包板
ESP32 C3连接DS1307和ssd1306屏幕的方法:
实际接线图:
三、示例代码
from machine import Pin,I2C
from ssd1306 import SSD1306_I2C
from ds1307 import DS1307
from at24c32n import AT24C32N
from ufont import BMFont
import time
RTC_ADDR = 0X68
ATC_ADDR = 0X50
LED_ADDR = 0X3C
i2c = I2C(0,scl = Pin(5),sda = Pin(4),freq = 400_000)
oled = SSD1306_I2C(128,64,i2c,0x3c)
rtc = DS1307(i2c,RTC_ADDR)
rom = AT24C32N(i2c,ATC_ADDR)
font = BMFont("fonts/unifont-14-12888-16.v3.bmf")
def ds130x():
xingqi = ["星期一","星期二","星期三","星期四","星期五","星期六","星期日"]
# Year, month, day, week,hour, minute, second, subseconds
if rtc.datetime()[0] != 2023:
rtc.datetime((2023,5,9,1,21,50,05,1))
Time = rtc.datetime()
Year = Time[0]
month = Time[1]
day = Time[2]
week = Time[3]
hour = Time[4]
minute = Time[5]
second = Time[6]
hm = Time[7]
font.text(oled,"%.d年%.2d月%.2d日"%(Year,month,day),8,0, color=1,font_size=16,reverse=False,clear=False,show=True,half_char=True, auto_wrap=True)
font.text(oled,"%.2d时%.2d分%.2d秒"%(hour,minute,second),14,24, color=1,font_size=16,reverse=False,clear=False,show=True,half_char=True, auto_wrap=True)
font.text(oled,"%s"%(xingqi[week]),40,48, color=1,font_size=16,reverse=True,clear=False,show=True,half_char=True, auto_wrap=True)
def AT24CX():
#32K 二进制位(32*1024=32726/bit) 转换字节(32726/8=4096) 128页 * 32字节 = 4096
print(rom.capacity())
text = "https://www.baidu.com".encode("utf-8")
chinese = "魔都欢迎你".encode("utf-8")
rom.write(0,b'\x88' * 4096)
print(rom.read(0,4))
rom.write(1,text)
print("%s %d"%(rom.read(1,len(text)).decode("utf-8"),len(text)))
rom.write(2,chinese)
print("%s %d"%(rom.read(2,len(chinese)).decode("utf-8"),len(chinese)))
def main():
# 开启&关闭时钟
rtc.halt(False)
# 输出方波
rtc.square_wave(32,0)
# eeprom
AT24CX()
while True:
ds130x()
if __name__ == "__main__":
main()
演示效果:
四、ds1307时钟模块驱动
ds1307.py
"""
MicroPython TinyRTC I2C Module, DS1307 RTC + AT24C32N EEPROM
https://github.com/mcauser/micropython-tinyrtc-i2c
MIT License
Copyright (c) 2018 Mike Causer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
BCD Format:
https://en.wikipedia.org/wiki/Binary-coded_decimal
"""
from micropython import const
DATETIME_REG = const(0) # 0x00-0x06
CHIP_HALT = const(128)
CONTROL_REG = const(7) # 0x07
RAM_REG = const(8) # 0x08-0x3F
class DS1307(object):
"""Driver for the DS1307 RTC."""
def __init__(self, i2c, addr=0x68):
self.i2c = i2c
self.addr = addr
self.weekday_start = 1
self._halt = False
def _dec2bcd(self, value):
"""Convert decimal to binary coded decimal (BCD) format"""
return (value // 10) << 4 | (value % 10)
def _bcd2dec(self, value):
"""Convert binary coded decimal (BCD) format to decimal"""
return ((value >> 4) * 10) + (value & 0x0F)
def datetime(self, datetime=None):
"""Get or set datetime"""
if datetime is None:
buf = self.i2c.readfrom_mem(self.addr, DATETIME_REG, 7)
return (
self._bcd2dec(buf[6]) + 2000, # year
self._bcd2dec(buf[5]), # month
self._bcd2dec(buf[4]), # day
self._bcd2dec(buf[3] - self.weekday_start), # weekday
self._bcd2dec(buf[2]), # hour
self._bcd2dec(buf[1]), # minute
self._bcd2dec(buf[0] & 0x7F), # second
0 # subseconds
)
buf = bytearray(7)
buf[0] = self._dec2bcd(datetime[6]) & 0x7F # second, msb = CH, 1=halt, 0=go
buf[1] = self._dec2bcd(datetime[5]) # minute
buf[2] = self._dec2bcd(datetime[4]) # hour
buf[3] = self._dec2bcd(datetime[3] + self.weekday_start) # weekday
buf[4] = self._dec2bcd(datetime[2]) # day
buf[5] = self._dec2bcd(datetime[1]) # month
buf[6] = self._dec2bcd(datetime[0] - 2000) # year
if (self._halt):
buf[0] |= (1 << 7)
self.i2c.writeto_mem(self.addr, DATETIME_REG, buf)
def halt(self, val=None):
"""Power up, power down or check status"""
if val is None:
return self._halt
reg = self.i2c.readfrom_mem(self.addr, DATETIME_REG, 1)[0]
if val:
reg |= CHIP_HALT
else:
reg &= ~CHIP_HALT
self._halt = bool(val)
self.i2c.writeto_mem(self.addr, DATETIME_REG, bytearray([reg]))
def square_wave(self, sqw=0, out=0):
"""Output square wave on pin SQ at 1Hz, 4.096kHz, 8.192kHz or 32.768kHz,
or disable the oscillator and output logic level high/low."""
rs0 = 1 if sqw == 4 or sqw == 32 else 0
rs1 = 1 if sqw == 8 or sqw == 32 else 0
out = 1 if out > 0 else 0
sqw = 1 if sqw > 0 else 0
reg = rs0 | rs1 << 1 | sqw << 4 | out << 7
self.i2c.writeto_mem(self.addr, CONTROL_REG, bytearray([reg]))
五、at24c32n EEPROM驱动
at24c32n.py
"""
MicroPython TinyRTC I2C Module, DS1307 RTC + AT24C32N EEPROM
https://github.com/mcauser/micropython-tinyrtc-i2c
MIT License
Copyright (c) 2018 Mike Causer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
# AT24C32A, 32K (32768 kbit / 4 KB), 128 pages, 32 bytes per page, i2c addr 0x50
import time
class AT24C32N(object):
"""Driver for the AT24C32N 32K EEPROM."""
def __init__(self, i2c, i2c_addr=0x50, pages=128, bpp=32):
self.i2c = i2c
self.i2c_addr = i2c_addr
self.pages = pages
self.bpp = bpp # bytes per page
def capacity(self):
"""Storage capacity in bytes"""
return self.pages * self.bpp
def read(self, addr, nbytes):
"""Read one or more bytes from the EEPROM starting from a specific address"""
return self.i2c.readfrom_mem(self.i2c_addr, addr, nbytes, addrsize=16)
def write(self, addr, buf):
"""Write one or more bytes to the EEPROM starting from a specific address"""
offset = addr % self.bpp
partial = 0
# partial page write
if offset > 0:
partial = self.bpp - offset
self.i2c.writeto_mem(self.i2c_addr, addr, buf[0:partial], addrsize=16)
time.sleep_ms(5)
addr += partial
# full page write
for i in range(partial, len(buf), self.bpp):
self.i2c.writeto_mem(self.i2c_addr, addr+i-partial, buf[i:i+self.bpp], addrsize=16)
time.sleep_ms(5)
六、ssd1306屏幕驱动
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
# https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py
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_IREF_SELECT = const(0xAD)
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
SET_IREF_SELECT,
0x30, # enable internal IREF during display on
# 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 != 128:
# narrow displays use centred columns
col_offset = (128 - self.width) // 2
x0 += col_offset
x1 += col_offset
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)
def clear(self):
self.fill(0)
self.show()
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)
七、字体库程序
ufont.py
__version__ = 3
import time
import struct
import framebuf
DEBUG = False
def timeit(f, *args, **kwargs):
try:
myname = str(f).split(' ')[1]
except:
myname = "UNKONW"
def new_func(*args, **kwargs):
if DEBUG:
try:
t = time.ticks_us()
result = f(*args, **kwargs)
delta = time.ticks_diff(time.ticks_us(), t)
print('Function {} Time = {:6.3f}ms'.format(myname, delta / 1000))
except AttributeError:
t = time.perf_counter_ns()
result = f(*args, **kwargs)
delta = time.perf_counter_ns() - t
print('Function {} Time = {:6.3f}ms'.format(myname, delta / 1000000))
return result
else:
return f(*args, **kwargs)
return new_func
class BMFont:
@staticmethod
def _list_to_byte(arr):
b = 0
for a in arr:
b = (b << 1) + a
return bytes([b])
@timeit
def _bit_list_to_byte_data(self, bit_list):
"""将点阵转换为字节数据
Args:
bit_list:
Returns:
"""
byte_data = b''
for _col in bit_list:
for i in range(0, len(_col), 8):
byte_data += self._list_to_byte(_col[i:i + 8])
return byte_data
@timeit
def __init__(self, font_file):
self.font_file = font_file
self.font = open(font_file, "rb", buffering=0xff)
self.bmf_info = self.font.read(16)
if self.bmf_info[0:2] != b"BM":
raise TypeError("字体文件格式不正确: " + font_file)
self.version = self.bmf_info[2]
if self.version != 3:
raise TypeError("字体文件版本不正确: " + str(self.version))
self.map_mode = self.bmf_info[3] # 映射方式
self.start_bitmap = struct.unpack(">I", b'\x00' + self.bmf_info[4:7])[0] # 位图开始字节
self.font_size = self.bmf_info[7] # 字体大小
self.bitmap_size = self.bmf_info[8] # 点阵所占字节
@timeit
def _to_bit_list(self, byte_data, font_size, *, _height=None, _width=None):
"""将字节数据转换为点阵数据
Args:
byte_data: 字节数据
font_size: 字号大小
_height: 字体原高度
_width: 字体原宽度
Returns:
"""
_height = _height or self.font_size
_width = _width or self.bitmap_size // self.font_size * 8
new_bitarray = [[0 for j in range(font_size)] for i in range(font_size)]
for _col in range(len(new_bitarray)):
for _row in range(len(new_bitarray[_col])):
_index = int(_col / (font_size / _height)) * _width + int(_row / (font_size / _width))
new_bitarray[_col][_row] = byte_data[_index // 8] >> (7 - _index % 8) & 1
return new_bitarray
@timeit
def _color_render(self, bit_list, color):
"""将二值点阵图像转换为 RGB565 彩色字节图像
Args:
bit_list:
color:
Returns:
"""
color_array = b""
for _col in range(len(bit_list)):
for _row in range(len(bit_list)):
color_array += struct.pack("<H", color) if bit_list[_col][_row] else b'\x00\x00'
return color_array
@timeit
def _get_index(self, word):
"""获取索引
Args:
word: 字符
Returns:
"""
word_code = ord(word)
start = 0x10
end = self.start_bitmap
while start <= end:
mid = ((start + end) // 4) * 2
self.font.seek(mid, 0)
target_code = struct.unpack(">H", self.font.read(2))[0]
if word_code == target_code:
return (mid - 16) >> 1
elif word_code < target_code:
end = mid - 2
else:
start = mid + 2
return -1
@timeit
def get_bitmap(self, word):
"""获取点阵图
Args:
word: 字符
Returns:
bytes 字符点阵
"""
index = self._get_index(word)
if index == -1:
return b'\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x0f\xcf\xf3\xcf\xf3\xff\xf3\xff\xcf\xff?\xff?\xff\xff\xff' \
b'?\xff?\xff\xff\xff\xff'
self.font.seek(self.start_bitmap + index * self.bitmap_size, 0)
return self.font.read(self.bitmap_size)
@timeit
def text(self, display, string, x, y, color=1, *, font_size=None, reverse=False, clear=False, show=False,
half_char=True, auto_wrap=False, **kwargs):
"""通过显示屏显示文字
使用此函数显示文字,必须先确认显示对象是否继承与 framebuf.FrameBuffer。
如果显示对象没有 clear 方法,需要自行调用 fill 清屏
Args:
display: 显示实例
string: 字符串
x: 字体左上角 x 轴
y: 字体左上角 y 轴
color: 颜色
font_size: 字号
reverse: 是否反转背景
clear: 是否清除之前显示的内容
show: 是否立刻显示
half_char: 是否半字节显示 ASCII 字符
auto_wrap: 自动换行
**kwargs:
Returns:
None
"""
font_size = font_size or self.font_size
initial_x = x
# 清屏
try:
display.clear() if clear else 0
except AttributeError:
print("请自行调用 display.fill(*) 清屏")
for char in range(len(string)):
# 是否自动换行
if auto_wrap:
if auto_wrap and ((x + font_size // 2 >= display.width and ord(string[char]) < 128 and half_char) or
(x + font_size >= display.width and (not half_char or ord(string[char]) > 128))):
y += font_size
x = initial_x
# 回车
if string[char] == '\n':
y += font_size
x = initial_x
continue
# Tab
elif string[char] == '\t':
x = ((x // font_size) + 1) * font_size + initial_x % font_size
continue
# 其它的控制字符不显示
elif ord(string[char]) < 16:
continue
# 超过范围的字符不会显示*
if x > display.width or y > display.height:
continue
byte_data = list(self.get_bitmap(string[char]))
# 反转
if reverse:
for _pixel in range(len(byte_data)):
byte_data[_pixel] = ~byte_data[_pixel] & 0xff
# 缩放和色彩*
if color > 1 or font_size != self.font_size:
bit_data = self._to_bit_list(byte_data, font_size)
if color > 1:
display.blit(
framebuf.FrameBuffer(bytearray(self._color_render(bit_data, color)), font_size, font_size,
framebuf.RGB565), x, y)
else:
display.blit(
framebuf.FrameBuffer(bytearray(self._bit_list_to_byte_data(bit_data)), font_size, font_size,
framebuf.MONO_HLSB), x, y)
else:
display.blit(framebuf.FrameBuffer(bytearray(byte_data), font_size, font_size, framebuf.MONO_HLSB), x, y)
# 英文字符半格显示
if ord(string[char]) < 128 and half_char:
x += font_size // 2
else:
x += font_size
display.show() if show else 0
def char(self, char, color=1, font_size=None, reverse=False):
""" 获取字体字节数据
在没有继承 framebuf.FrameBuffer 的显示驱动,或者内存不足以将一整个屏幕载入缓存帧时
可以直接获取单字的字节数据,局部更新
Args:
char: 单个字符
color: 颜色
font_size: 字体大小
reverse: 反转
Returns:
bytearray
"""
font_size = font_size or self.font_size
byte_data = list(self.get_bitmap(char))
# 反转
if reverse:
for _pixel in range(len(byte_data)):
byte_data[_pixel] = ~byte_data[_pixel] & 0xff
if color > 1 or font_size != self.font_size:
bit_data = self._to_bit_list(byte_data, font_size)
if color > 1:
return self._color_render(bit_data, color)
else:
return self._bit_list_to_byte_data(bit_data)
else:
return bytearray(byte_data)
if __name__ == '__main__':
def show_bitmap(arr):
"""
显示点阵字 MONO_HLSB
:return:
"""
for row in arr:
for i in row:
if i:
print('* ', end=' ')
else:
print('. ', end=' ')
print()
font = BMFont("unifont-14-12888-16.v3.bmf")
print("16 ----")
bd = font.char("我", reverse=True, color=0xffff, font_size=16)
print("24 ----")
bd = font.char("我", reverse=True, color=0xffff, font_size=24)
print("16 ----")
# font._with_color(zoom(byte_to_bit(font.get_bitmap("我"), 16), 24), 0xff00)
font._color_render(font._to_bit_list(font.get_bitmap("我"), 24), 0xff00)
八、字体库和字体库制作工具
链接: https://pan.baidu.com/s/1NtndDmaTxEfC6_P3lWTS9g 提取码: r1j7 复制这段内容后打开百度网盘手机App,操作更方便哦
九、DS1307时钟模块购买
RTC I2C模块 24C32存储器 DS1307时钟模块
资料:https://pan.baidu.com/s/1mtXruLAktZj7UDhFD54BNw 提取码:d9xh
DS1307 I2C实时时钟芯片(RTC)
24C32 32K I2C EEPROM存储器
解决DS1307带备用电池不能读写的问题。
充满电后,能提供DS1307计时1年。
设计小巧,27mm*28mm*8.4mm
引出DS1307的时钟引脚,为单片机提供时钟信号。
可以级联其他I2C设备。