ESP32S3——基于MicroPython开发(2)

ESP32S3——基于MicroPython开发(2)

第8章_定时器中断实验

ESP32 内置 RTOS(实时操作系统) 定时器, 在 machine 的 Timer 模块中。通过 MicroPython 可以轻松编程使用。 我们也是只需要了

解其构造对象函数和使用方法即可。

image-20260912102403498

ESP32 拥有 4 个定时器。 使用 machine.Timer 类通过设置 timer ID 号为0-3。 使用方法如下:

image-20260912102455906

# 导入Pin模块
from machine import Pin
from machine import Timer

# 定义LED控制对象
led1 = Pin(7,Pin.OUT)

# 定义LED状态
led1_state = 0

# 定时器0 中断函数
def time0_irq(time0):
    global led1_state
    led1_state = not led1_state
    led1.value(led1_state)

# 程序入口
if __name__ == "__main__":
    led1.value(led1_state)                                                # 初始化LED,熄灭状态
    
    time0 = Timer(0)                                                      # 创建time0定时器对象
    time0.init(period = 500,mode = Timer.PERIODIC,callback = time0_irq)

    while True:
        pass

第9章_PWM呼吸灯实验

PWM 是脉冲宽度调制, 简称脉宽调制。 它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM 主要用于输

出不同频率、占空比(一个周期内高电平出现时间占总时间比例) 的方波。 以实现固定频率或平均电压输出。 频率固定, 改变占空比可

改变输出电压, 如下所示:

image-20260912104108952

PWM 可以通过 ESP32 所有 GPIO 引脚输出。 所有通道都有 1 个特定的频率,从 1 到 40M 之间(单位是 Hz) 。 占空比的值为 0 至

1023 之间。

PWM 在 machine 的 PWM 模块中, 我们也是只需要了解其构造对象函数和使用方法即可。

image-20260912105609210

PWM 使用方法如下:

image-20260912105652363

# 导入Pin模块
from machine import Pin
from machine import PWM
import time

# 定义LED1控制对象
led1 = PWM(Pin(7),freq = 1000,duty = 0)

# 程序入口
if __name__ == "__main__":
    duty_value = 0
    fx = 1
    while True:
        if fx == 1:
            duty_value += 10
            if duty_value > 1010:
                fx = 0
        else:
            duty_value -= 10
            if duty_value < 10:
                fx = 1
        led1.duty(duty_value)
        time.sleep_ms(10)

第10章_串口通信实验

ESP32 有三个硬件 UART: UART0、 UART1 和 UART2。 除了 UART0 有固定引脚,其它 2 个串口都可映射到任意 IO, 如下:

UART0 用于下载和 REPL 调试, 因此可以使用 UART2 与外部串口设备通信。

image-20260912110617730

UART 在 machine 的 UART 模块中, 我们也是只需要了解其构造对象函数和使用方法即可。

image-20260912110730015

image-20260912110812784

使用方法如下:

image-20260912110857901

'''
    实验名称:串口通信实验
    接线说明:USB转TTL模块 --> ESP32 IO
                (TXD) --> (16)
                (RXD) --> (17)
                (GND) --> (GND)
    实验现象:程序下载成功后,打开串口调试助手,选择好串口、波特率115200参数等,在串口助手上发送字符数据,
            ESP32 串口接收后原封不动返回到串口助手显示
    注意事项:USB转TTL模块上将电源切换到3.3V
'''

# 导入Pin模块
from machine import Pin
from machine import UART
import time

# 定义UART控制对象
uart = UART(2,115200,rx = 16,tx = 17)

# 程序入口
if __name__ == "__main__":
    uart.write("hello World!")
    while True:
        if uart.any():
            text = uart.read(128)
            uart.write(text)

第11章_ADC实验

image-20260912200040397

image-20260912200106116

image-20260912200125352

'''
实验名称:ADC实验
接线说明:ADC电位器 --> ESP32 IO
         ADC --> (10)

实验现象:程序下载成功后,会在软件shell控制台上输出ADC检测电压值,调节电位器可改变检测电压

ESP32-S3 ADC 引脚表:
    - ADC1(推荐,WiFi 开启也能用)
        - GPIO1、GPIO2、GPIO3、GPIO4、GPIO5、GPIO6、GPIO7、GPIO8、GPIO9、GPIO10
    - ADC2(不推荐,开启 WiFi 后 ADC2 失效)
        - GPIO11~GPIO20
'''

# 导入模块
from machine import Pin
from machine import ADC
from machine import Timer
import time


# 定义ADC控制对象
adc = ADC(Pin(10))
adc.atten(ADC.ATTN_11DB)                    # 开启衰减,量程增大到3.3V

# 全局变量,存放ADC读到的电压
adc_vol = 0.0

# 定时器0 中断函数:只做读取,不打印
def time0_irq(tim):
    global adc_vol
    raw = adc.read()
    adc_vol = 3.3 * raw / 4095

# 程序入口
if __name__ == "__main__":
    time0 = Timer(0)                        # 创建time0定时器对象
    time0.init(period = 500, mode = Timer.PERIODIC, callback = time0_irq)
    try:
        while True:
            # 打印放到主循环,安全
            print("ADC检测电压:%.2fV" % adc_vol)
            time.sleep_ms(500)
    except KeyboardInterrupt:
        time0.deinit()   # 关闭定时器
        print("程序退出,定时器关闭")

'''
实验名称:MQ135分级有害气体检测
硬件平台:ESP32-S3
接线说明:
MQ135 AO模拟输出 --> ESP32S3 GPIO10(ADC1)
MQ135 VCC --> 5V
MQ135 GND --> GND

功能:
1. 上电先预热,等待传感器稳定
2. 支持洁净空气一键标定R0(首次使用必须标定!)
3. 计算等效CO2 ppm,5级空气质量分级
4. 分级文字提示,超标告警提示
5. 定时器中断采集ADC,主循环处理计算、打印分级
ESP32-S3 ADC 引脚表:
    - ADC1(推荐,WiFi 开启也能用)
        - GPIO1、GPIO2、GPIO3、GPIO4、GPIO5、GPIO6、GPIO7、GPIO8、GPIO9、GPIO10
    - ADC2(不推荐,开启 WiFi 后 ADC2 失效)
        - GPIO11~GPIO20
'''

# 导入模块
from machine import Pin
from machine import ADC
from machine import Timer
import time

# 参数配置区
RL = 10000          # MQ135模块负载电阻,默认10kΩ
VCC = 5.0           # MQ135模块供电电压
ADC_MAX_V = 2.4     # ESP32 ADC最大采样电压
ADC_RES = 4095      # 12位ADC分辨率
CALIB_TIME = 60     # 标定采集时长,单位秒(干净空气标定R0)
WARMUP_TIME = 120   # 传感器预热时间,单位秒,2分钟预热


# MQ135接GPIO10 ADC1通道
mq135_adc = ADC(Pin(10))
mq135_adc.atten(ADC.ATTN_11DB)

# 全局变量
mq_raw_val = 0
mq_voltage = 0.0
R0 = None  # 干净空气基准电阻,标定后写入

# 定时器中断:仅读取ADC原始值,不做运算打印
def timer_irq(tim):
    global mq_raw_val, mq_voltage
    mq_raw_val = mq135_adc.read()
    mq_voltage = ADC_MAX_V * mq_raw_val / ADC_RES

# 计算传感器Rs
def get_Rs(vol):
    if vol <= 0:
        return 999999
    Rs = (VCC - vol) / vol * RL
    return Rs

# 空气质量分级函数,输入等效co2_ppm,返回等级和提示文本
def get_air_level(co2_ppm):
    if co2_ppm < 800:
        return 0, "✅ 优秀,空气质量良好"
    elif co2_ppm < 1200:
        return 1, "🟡 一般,轻微污染,可适时通风"
    elif co2_ppm < 2000:
        return 2, "🟠 较差,空气有异味,建议开窗通风"
    elif co2_ppm < 5000:
        return 3, "🔴 差,有害气体偏高,必须通风!"
    else:
        return 4, "🟥 危险!高浓度有害气体,请立即通风撤离!"

# 标定函数:在干净空气下运行,获取R0
def calibrate_R0():
    print(f"\n==== 开始标定R0,持续{CALIB_TIME}秒 ====")
    print("重要:请确保当前环境为干净空气,无烟雾、无异味!")
    sum_Rs = 0
    sample_cnt = 0
    start_t = time.ticks_ms()
    while time.ticks_diff(time.ticks_ms(), start_t) < CALIB_TIME *1000:
        rs = get_Rs(mq_voltage)
        sum_Rs += rs
        sample_cnt +=1
        time.sleep_ms(500)
        print(f"采集中,当前Rs={rs:.2f}",end="\r")
    R0_cal = sum_Rs / sample_cnt
    print(f"\n标定完成!R0 = {R0_cal:.2f} Ω")
    print("将下面R0数值保存到代码,以后无需重复标定:")
    print(f"R0 = {R0_cal:.2f}")
    return R0_cal

# 程序入口
if __name__ == "__main__":
    time0 = Timer(0)
    time0.init(period = 500, mode = Timer.PERIODIC, callback = timer_irq)

    print(f"MQ135预热,等待{WARMUP_TIME}秒...")
    time.sleep(WARMUP_TIME)
    print("预热完成!")

    # =========== 首次使用打开标定,标定完成后注释掉下面一行 ===========
    R0 = calibrate_R0()
    # =========== 标定完成后,手动填入标定得到的R0,取消下面注释,注释上面calibrate_R0()
    # R0 = 2957983.60  # 标定后的数据

    if R0 is None:
        print("R0未标定,程序退出!")
        time0.deinit()
    else:
        print("\n==== 开始环境气体检测 ====")
        try:
            while True:
                Rs = get_Rs(mq_voltage)
                ratio = Rs / R0
                # MQ135 CO2经验公式
                co2_ppm = 116 * (ratio ** (-2.76))
                level, msg = get_air_level(co2_ppm)

                print(f"ADC原始:{mq_raw_val:4d} | Vol:{mq_voltage:.2f}V | Rs:{Rs:.0f} | CO2当量:{co2_ppm:.0f} ppm | {msg}")
                time.sleep_ms(500)
        except KeyboardInterrupt:
            time0.deinit()
            print("\n程序退出,定时器关闭")


第12章_RGB彩灯实验

image-20260912201649968

image-20260912201715718

image-20260912201829865

image-20260912202026002

image-20260912202052780

image-20260912202205117

'''
实验名称:RGB彩灯实验
接线说明:RGB彩灯模块-->ESP32 IO
         WS-->(16)
         
实验现象:程序下载成功后,RGB彩灯循环点亮且循环变化颜色
'''
#导入Pin模块
from machine import Pin
from neopixel import NeoPixel
import time


#定义RGB控制对象
#控制引脚为16,RGB灯串联5个
pin=16
rgb_num=5
bright = 0.3           # 亮度调节,1为最高亮度
rgb_led=NeoPixel(Pin(pin,Pin.OUT),rgb_num)

#定义RGB颜色(加int,转为整数)
RED = (int(255*bright), int(0*bright), int(0*bright))
ORANGE = (int(255*bright), int(165*bright), int(0*bright))
YELLOW = (int(255*bright), int(150*bright), int(0*bright))
GREEN = (int(0*bright), int(255*bright), int(0*bright))
BLUE = (int(0*bright), int(0*bright), int(255*bright))
INDIGO = (int(75*bright), int(0*bright), int(130*bright))
VIOLET = (int(138*bright), int(43*bright), int(226*bright))
COLORS = (RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET)

#程序入口
if __name__=="__main__":
    while True:
        for color in COLORS:
            # 同一颜色,依次点亮5颗灯
            for i in range(rgb_num):
                rgb_led.fill((0,0,0)) #全部灯先熄灭
                rgb_led[i] = color    #只点亮第i个灯
                rgb_led.write()       #只在全部数据设置完成后,执行一次发送
                time.sleep_ms(100)
            time.sleep_ms(1000)


'''
实验名称:RGB彩灯实验
接线说明:WS2812 --> ESP32S3 IO 16

实验现象:5个彩灯同时点亮,每个灯颜色不一样,整套色彩循环变化
'''

# 导入模块
from machine import Pin
from neopixel import NeoPixel
import time


# 定义RGB控制对象
# 控制引脚为16,RGB灯串联5个
pin = 16
rgb_num = 5
rgb_led = NeoPixel(Pin(pin,Pin.OUT),rgb_num)

# 定义RGB颜色
RED = (255,0,0)
ORANGE = (255,165,0)
YELLOW = (255,150,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
INDIGO = (75,0,130)
VIOLET = (138,43,226)
COLORS = (RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET)

# 程序入口
if __name__ == "__main__":
    try:
        while True:
            # 遍历颜色偏移,实现色彩滚动效果
            for offset in range(len(COLORS)):
                # 给5个灯,分配不同颜色
                for i in range(rgb_num):
                    # 取不同下标,每个灯颜色不同
                    color_idx = (offset + i) % len(COLORS)
                    rgb_led[i] = COLORS[color_idx]
                # 全部灯设置完毕,只刷新一次
                rgb_led.write()
                time.sleep_ms(300)
    except KeyboardInterrupt:
        # 退出,关闭所有彩灯
        for i in range(rgb_num):
            rgb_led[i] = (0,0,0)
        rgb_led.write()
        print("程序退出,RGB全部熄灭")

第13章_数码管显示实验

image-20260912205621481

image-20260912205707975

image-20260912205739742

image-20260912205838046

K1(列 1) K2(列 2)
SG1 (行 1) 按键 S11 按键 S12
SG2 (行 2) 按键 S21 按键 S22
SG3 (行 3) 按键 S31 按键 S32
SG4 (行 4) 按键 S41 按键 S42
SG5 (行 5) 按键 S51 按键 S52
SG6 (行 6) 按键 S61 按键 S62
SG7 (行 7) 按键 S71 按键 S72
SG8 (行 8) 按键 S81 按键 S82

image-20260913231253470

image-20260912205943961

image-20260912210014609

image-20260912210040645

image-20260912210127042

tm1637.py

"""
MicroPython TM1637 quad 7-segment LED display driver
https://github.com/mcauser/micropython-tm1637

MIT License
Copyright (c) 2016 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.
"""

from micropython import const
from machine import Pin
from time import sleep_us, sleep_ms

TM1637_CMD1 = const(64)  # 0x40 data command
TM1637_CMD2 = const(192) # 0xC0 address command
TM1637_CMD3 = const(128) # 0x80 display control command
TM1637_DSP_ON = const(8) # 0x08 display on
TM1637_DELAY = const(10) # 10us delay between clk/dio pulses
TM1637_MSB = const(128)  # msb is the decimal point or the colon depending on your display

# 0-9, a-z, blank, dash, star
_SEGMENTS = bytearray(b'\x3F\x06\x5B\x4F\x66\x6D\x7D\x07\x7F\x6F\x77\x7C\x39\x5E\x79\x71\x3D\x76\x06\x1E\x76\x38\x55\x54\x3F\x73\x67\x50\x6D\x78\x3E\x1C\x2A\x76\x6E\x5B\x00\x40\x63')

class TM1637(object):
    """Library for quad 7-segment LED modules based on the TM1637 LED driver."""
    def __init__(self, clk, dio, brightness=7):
        self.clk = clk
        self.dio = dio

        if not 0 <= brightness <= 7:
            raise ValueError("Brightness out of range")
        self._brightness = brightness

        self.clk.init(Pin.OUT, value=0)
        self.dio.init(Pin.OUT, value=0)
        sleep_us(TM1637_DELAY)

        self._write_data_cmd()
        self._write_dsp_ctrl()

    def _start(self):
        self.dio(0)
        sleep_us(TM1637_DELAY)
        self.clk(0)
        sleep_us(TM1637_DELAY)

    def _stop(self):
        self.dio(0)
        sleep_us(TM1637_DELAY)
        self.clk(1)
        sleep_us(TM1637_DELAY)
        self.dio(1)

    def _write_data_cmd(self):
        # automatic address increment, normal mode
        self._start()
        self._write_byte(TM1637_CMD1)
        self._stop()

    def _write_dsp_ctrl(self):
        # display on, set brightness
        self._start()
        self._write_byte(TM1637_CMD3 | TM1637_DSP_ON | self._brightness)
        self._stop()

    def _write_byte(self, b):
        for i in range(8):
            self.dio((b >> i) & 1)
            sleep_us(TM1637_DELAY)
            self.clk(1)
            sleep_us(TM1637_DELAY)
            self.clk(0)
            sleep_us(TM1637_DELAY)
        self.clk(0)
        sleep_us(TM1637_DELAY)
        self.clk(1)
        sleep_us(TM1637_DELAY)
        self.clk(0)
        sleep_us(TM1637_DELAY)

    def brightness(self, val=None):
        """Set the display brightness 0-7."""
        # brightness 0 = 1/16th pulse width
        # brightness 7 = 14/16th pulse width
        if val is None:
            return self._brightness
        if not 0 <= val <= 7:
            raise ValueError("Brightness out of range")

        self._brightness = val
        self._write_data_cmd()
        self._write_dsp_ctrl()

    def write(self, segments, pos=0):
        """Display up to 6 segments moving right from a given position.
        The MSB in the 2nd segment controls the colon between the 2nd
        and 3rd segments."""
        if not 0 <= pos <= 5:
            raise ValueError("Position out of range")
        self._write_data_cmd()
        self._start()

        self._write_byte(TM1637_CMD2 | pos)
        for seg in segments:
            self._write_byte(seg)
        self._stop()
        self._write_dsp_ctrl()

    def encode_digit(self, digit):
        """Convert a character 0-9, a-f to a segment."""
        return _SEGMENTS[digit & 0x0f]

    def encode_string(self, string):
        """Convert an up to 4 character length string containing 0-9, a-z,
        space, dash, star to an array of segments, matching the length of the
        source string."""
        segments = bytearray(len(string))
        for i in range(len(string)):
            segments[i] = self.encode_char(string[i])
        return segments

    def encode_char(self, char):
        """Convert a character 0-9, a-z, space, dash or star to a segment."""
        o = ord(char)
        if o == 32:
            return _SEGMENTS[36] # space
        if o == 42:
            return _SEGMENTS[38] # star/degrees
        if o == 45:
            return _SEGMENTS[37] # dash
        if o >= 65 and o <= 90:
            return _SEGMENTS[o-55] # uppercase A-Z
        if o >= 97 and o <= 122:
            return _SEGMENTS[o-87] # lowercase a-z
        if o >= 48 and o <= 57:
            return _SEGMENTS[o-48] # 0-9
        raise ValueError("Character out of range: {:d} '{:s}'".format(o, chr(o)))

    def hex(self, val):
        """Display a hex value 0x0000 through 0xffff, right aligned."""
        string = '{:04x}'.format(val & 0xffff)
        self.write(self.encode_string(string))

    def number(self, num):
        """Display a numeric value -999 through 9999, right aligned."""
        # limit to range -999 to 9999
        num = max(-999, min(num, 9999))
        string = '{0: >4d}'.format(num)
        self.write(self.encode_string(string))

    def numbers(self, num1, num2, colon=True):
        """Display two numeric values -9 through 99, with leading zeros
        and separated by a colon."""
        num1 = max(-9, min(num1, 99))
        num2 = max(-9, min(num2, 99))
        segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2))
        if colon:
            segments[1] |= 0x80 # colon on
        self.write(segments)

    def temperature(self, num):
        if num < -9:
            self.show('lo') # low
        elif num > 99:
            self.show('hi') # high
        else:
            string = '{0: >2d}'.format(num)
            self.write(self.encode_string(string))
        self.write([_SEGMENTS[38], _SEGMENTS[12]], 2) # degrees C

    def show(self, string, colon=False):
        segments = self.encode_string(string)
        if len(segments) > 1 and colon:
            segments[1] |= 128
        self.write(segments[:4])

    def scroll(self, string, delay=250):
        segments = string if isinstance(string, list) else self.encode_string(string)
        data = [0] * 8
        data[4:0] = list(segments)
        for i in range(len(segments) + 5):
            self.write(data[0+i:4+i])
            sleep_ms(delay)


class TM1637Decimal(TM1637):
    """Library for quad 7-segment LED modules based on the TM1637 LED driver.

    This class is meant to be used with decimal display modules (modules
    that have a decimal point after each 7-segment LED).
    """

    def encode_string(self, string):
        """Convert a string to LED segments.

        Convert an up to 4 character length string containing 0-9, a-z,
        space, dash, star and '.' to an array of segments, matching the length of
        the source string."""
        segments = bytearray(len(string.replace('.','')))
        j = 0
        for i in range(len(string)):
            if string[i] == '.' and j > 0:
                segments[j-1] |= TM1637_MSB
                continue
            segments[j] = self.encode_char(string[i])
            j += 1
        return segments


main.py

'''
实验名称:数码管显示实验
接线说明:数码管模块-->ESP32 IO
         CLK-->(16)
         DIO-->(17)
         
实验现象:程序下载成功后,数码管间隔1s从0开始计数显示
         
注意事项:
'''

#导入Pin模块
from machine import Pin
import time
import tm1637

#定义数码管控制对象
smg=tm1637.TM1637(clk=Pin(16),dio=Pin(17)) 

#程序入口
if __name__=="__main__":
    #smg.numbers(1,24)           #显示小数01.24
    #smg.hex(123)                #将十进制数转换十六进制显示
    smg.brightness(0)            #亮度调节,不设置默认最高亮度(0~7)
    #smg.temperature(25)         #显示带温度符号°C,整数温度值
    #smg.show("1314")            #字符串显示,显示整数
    #smg.scroll("1314-520",500)  #字符串滚动显示,速度调节
    #time.sleep(5)
    n=0
    while True:
        smg.number(n)
        n+=1
        time.sleep(1)

第14章_RTC实时时钟

image-20260914094205768

image-20260914094231967

由于RTC模块为MicroPython固件所含有的功能,且在shell控制台输出,因此只需ESP32开发板即可实现。

'''
实验名称:RTC实时时钟实验
实验平台:ESP32-S3
实验现象:程序下载成功后,软件shell控制台间隔1s输出RTC实时时钟年月日时分秒星期
'''
#导入模块
from machine import RTC
import time

#定义RTC控制对象
rtc = RTC()

#定义星期,索引0对应星期一,和RTC匹配
week = ("星期一","星期二","星期三","星期四","星期五","星期六","星期日")

#程序入口
if __name__=="__main__":
    # 判断年份,如果不是设定年份,则设置时间(只上电第一次设置)
    if rtc.datetime()[0] != 2022:
        # rtc.datetime( (年,月,日,星期,时,分,秒,子秒) )
        rtc.datetime((2022,8,10,2,10,20,58,0))
    
    while True:
        date_time = rtc.datetime()
        year,month,day,wk,hour,minute,second,_ = date_time
        print(f"{year}-{month:02d}-{day:02d} \t {hour:02d}:{minute:02d}:{second:02d} \t {week[wk]}")
        time.sleep(1)


year,month,day,wk,hour,minute,second,_ = date_time

date_time = rtc.datetime() 返回的是一个8 元素元组:(年,月,日,星期,时,分,秒,亚秒)

索引 含义
0 year 年
1 month 月
2 day 日
3 wk 星期(0 = 周一,6 = 周日)
4 hour 小时
5 minute 分钟
6 second 秒
7 亚秒(微秒级,我们不需要)

_ 是 Python 约定:这个变量不用,丢弃第 8 个亚秒数据。

f-string 格式化打印:

print(f"{year}-{month:02d}-{day:02d} \t {hour:02d}:{minute:02d}:{second:02d} \t {week[wk]}")
  • :02d:固定占 2 位,不足 2 位前面补 0
    • 例:月份8 → 输出08;小时5 → 05,保证 2026-09-14 这种整齐格式
  • \t:制表符(Tab 空格),用来隔开日期、时间、星期,控制台排版对齐
  • week[wk]:wk 是 0~6 的数字,作为索引取星期元组里的中文,wk=0 → week[0] → "星期一"
import network
import ntptime
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect("wifi名称","wifi密码")
# 联网后自动同步RTC时间
ntptime.settime()

第15章_DS1302实时时钟实验

image-20260914101220038

image-20260914101248494

引脚号 符号 名称 功能说明
1 VCC2 主电源 系统上电时的供电,3.3V~5.5V,优先供电。VCC2 有电,芯片由 VCC2 工作
2 X1 晶振脚 1 接 32.768kHz 石英晶振一端,计时基准源
3 X2 晶振脚 2 接 32.768kHz 晶振另一端,晶振必须接这两个脚,不能省。32.768K 分频后得到 1Hz(1 秒)
4 GND 地 公共地,必须和单片机共地
5 CE(RST) 片选 / 使能 高电平开启通信;低电平结束通信,I/O 口高阻。读写 DS1302 前,必须把 CE 拉高
6 I/O 双向数据线 半双工,单片机和芯片互相传输数据,读写共用这一根线
7 SCLK 串行时钟 同步通信时钟,SCLK 脉冲驱动数据移位输出 / 输入
8 VCC1 备用电源 接 CR2032 纽扣电池(3V)。主电源 VCC2 断电之后,自动切换 VCC1 电池供电,维持时钟继续计时

供电优先级规则(重点)

DS1302 会自动选择电压更高的电源:

  • VCC2(主电源) > VCC1+0.2V → 使用 VCC2 供电
  • VCC2 掉电,VCC1 电池电压更高 → 电池单独维持时钟,功耗极低(<1μW)

芯片内置涓流充电电路:可以对可充电备用电池充电,普通一次性 CR2032禁止开启涓流充电,会充坏电池!

image-20260914102151808

image-20260914103216652

DS1302 完整读写时序流程

DS1302 通信第一步永远:拉高CE(RST),启动一次通信帧,然后先发1 字节命令字节。
命令字节最高位 bit7=1 表示读,bit7=0 表示写。

情况 1:ESP32 只想【读取 DS1302 时间】(读操作)

  1. CE = 高(开启通信)
  2. ESP32(主机)输出 8 个 SCLK 脉冲,同时主机把【读命令字节】放到 I/O 线上发送给 DS1302
    • 这一段:主机发送,DS1302 接收
    • 上升沿锁存 I/O 数据,DS1302 收下这 8bit 命令,知道主机要读哪个寄存器
  3. 发完 8bit 命令之后,I/O 引脚切换方向!变成 DS1302 驱动 I/O 线,ESP32 的 I/O 改为输入模式
  4. 接下来继续产生 8 个 SCLK 脉冲:
    • SCLK 下降沿,DS1302 把 bit 放到 I/O
    • ESP32 在下降沿读取 I/O 电平(接收 DS1302 的数据)
    • 这一段:DS1302 发送,ESP32 接收
  5. 读完 8bit 数据,拉低 CE,通信结束

关键点:发送命令 和 读取数据 是分两段、分开的 8 个时钟周期,不是同一个时钟周期同时收发。

情况 2:写操作(ESP32 往 DS1302 写时间)

CE 拉高 → 主机发送写命令 (8bit) → 继续主机输出 8bit 数据给 DS1302,全程主机驱动 I/O 输出,DS1302 接收。I/O 全程由 ESP32 驱动输出,不需要切换方向。

image-20260914103105403

image-20260914103246920

image-20260914103306190

DS1302.py

# DS1302.py
from machine import Pin

DS1302_REG_SECOND = (0x80)
DS1302_REG_MINUTE = (0x82)
DS1302_REG_HOUR   = (0x84)
DS1302_REG_DAY    = (0x86)
DS1302_REG_MONTH  = (0x88)
DS1302_REG_WEEKDAY= (0x8A)
DS1302_REG_YEAR   = (0x8C)
DS1302_REG_WP     = (0x8E)
DS1302_REG_CTRL   = (0x90)
DS1302_REG_RAM    = (0xC0)

class DS1302:
    def __init__(self, clk, dio, cs):
        self.clk = clk
        self.dio = dio
        self.cs  = cs
        self.clk.init(Pin.OUT)
        self.cs.init(Pin.OUT)
        
    def DecToHex(self, dat):
        return (dat//10) * 16 + (dat%10)

    def HexToDec(self, dat):
        return (dat//16) * 10 + (dat%16)

    def write_byte(self, dat):
        self.dio.init(Pin.OUT)
        for i in range(8):
            self.dio.value((dat >> i) & 1)
            self.clk.value(1)
            self.clk.value(0)

    def read_byte(self):
        d = 0
        self.dio.init(Pin.IN)
        for i in range(8):
            d = d | (self.dio.value()<<i)
            self.clk.value(1)
            self.clk.value(0)
        return d

    def getReg(self, reg):
        self.cs.value(1)
        self.write_byte(reg)
        t = self.read_byte()
        self.cs.value(0)
        return t

    def setReg(self, reg, dat):
        self.cs.value(1)
        self.write_byte(reg)
        self.write_byte(dat)
        self.cs.value(0)

    def wr(self, reg, dat):
        self.setReg(DS1302_REG_WP, 0)
        self.setReg(reg, dat)
        self.setReg(DS1302_REG_WP, 0x80)
                
    def start(self):
        t = self.getReg(DS1302_REG_SECOND + 1)
        self.wr(DS1302_REG_SECOND, t & 0x7f)

    def stop(self):
        t = self.getReg(DS1302_REG_SECOND + 1)
        self.wr(DS1302_REG_SECOND, t | 0x80)
        
    def Second(self, second = None):
        if second == None:
            return self.HexToDec(self.getReg(DS1302_REG_SECOND+1))%60
        else:
            self.wr(DS1302_REG_SECOND, self.DecToHex(second%60))

    def Minute(self, minute = None):
        if minute == None:
            return self.HexToDec(self.getReg(DS1302_REG_MINUTE+1))
        else:
            self.wr(DS1302_REG_MINUTE, self.DecToHex(minute%60))

    def Hour(self, hour = None):
        if hour == None:
            return self.HexToDec(self.getReg(DS1302_REG_HOUR+1))
        else:
            self.wr(DS1302_REG_HOUR, self.DecToHex(hour%24))

    def Weekday(self, weekday = None):
        if weekday == None:
            return self.HexToDec(self.getReg(DS1302_REG_WEEKDAY+1))
        else:
            self.wr(DS1302_REG_WEEKDAY, self.DecToHex(weekday%8))

    def Day(self, day = None):
        if day == None:
            return self.HexToDec(self.getReg(DS1302_REG_DAY+1))
        else:
            self.wr(DS1302_REG_DAY, self.DecToHex(day%32))

    def Month(self, month = None):
        if month == None:
            return self.HexToDec(self.getReg(DS1302_REG_MONTH+1))
        else:
            self.wr(DS1302_REG_MONTH, self.DecToHex(month%13))

    def Year(self, year = None):
        if year == None:
            return self.HexToDec(self.getReg(DS1302_REG_YEAR+1)) + 2000
        else:
            self.wr(DS1302_REG_YEAR, self.DecToHex(year%100))

    def DateTime(self, dat = None):
        if dat == None:
            return [self.Year(), self.Month(), self.Day(), self.Weekday(), self.Hour(), self.Minute(), self.Second()]
        else:
            self.Year(dat[0])
            self.Month(dat[1])
            self.Day(dat[2])
            self.Weekday(dat[3])
            self.Hour(dat[4])
            self.Minute(dat[5])
            self.Second(dat[6])

    def ram(self, reg, dat = None):
        if dat == None:
            return self.getReg(DS1302_REG_RAM + 1 + (reg%31)*2)
        else:
            self.wr(DS1302_REG_RAM + (reg%31)*2, dat)


main.py

'''
实验名称:DS1302实时时钟实验
接线说明:DS1302时钟模块-->ESP32 IO
         (CLK)-->(16)
         (IO) -->(12)
         (CE) -->(11)
         
实验现象:程序下载成功后,软件shell控制台间隔1S输出DS1302实时时钟年月日时分秒星期
'''
from machine import Pin
import time
from DS1302 import DS1302

#定义DS1302控制对象
ds1302=DS1302(clk=Pin(16),dio=Pin(12),cs=Pin(11))

# DS1302: 1=周一,7=周日;数组索引0空置不用,直接匹配
week=("","星期一","星期二","星期三","星期四","星期五","星期六","星期天")

#程序入口
if __name__=="__main__":
    # 判断年份,如果不是2022,则设置时间
    if ds1302.DateTime()[0]!=2026:
        # [年,月,日,星期(1=周一),时,分,秒]
        ds1302.DateTime([2026,9,16,1,12,19,12])
        ds1302.start() #启动时钟,防止暂停
        
    while True:
        date_time=ds1302.DateTime()
        year,mon,day,wk,h,m,s = date_time
        print(f"{year}-{mon:02d}-{day:02d} \t {h:02d}:{m:02d}:{s:02d} \t {week[wk]}")
        time.sleep(1)

posted @ 2026-09-14 12:21  Q&25  阅读(13)  评论(0)    收藏  举报