You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Raspberry Pi Pico+ESP8266-01:通过AT指令与UART获取网络时间

通过ESP8266-01的AT指令和UART获取NTP时间(Raspberry Pi Pico + MicroPython)

问题描述

我正在做一个基于Raspberry Pi Pico的单片机项目,因为Pico本身不支持WiFi,所以外接了ESP8266-01模块。在MicroPython环境里无法使用sockets或ntptime库,想仅通过AT指令和UART获取当前时间。

已经建立了可用的网络连接,AT指令也能正常工作,但utime.localtime()返回的是Pico本地时间,rtc.datetime()断电后需要重新设置,所以希望通过网络获取真实时间。

尝试了以下代码:

import machine
import utime

uart0 = machine.UART(0, baudrate=115200)
uart0.init(115200, bits=8, parity=None, stop=1)

def send_at_cmd(cmd, uart=uart0):
    print("CMD: " + cmd)
    uart.write(cmd + "\r\n")

    utime.sleep(3)

    print(uart.read().decode())
    print()


def connect_to_ntp():
    send_at_cmd('AT+CIPCLOSE') #Close the previous open UDP connection
    send_at_cmd('AT+CIPSTART="UDP","pool.ntp.org",123') #Connect to pool.ntp.org"

def get_ntp_time(uart=uart0):
    send_at_cmd('AT+CIPSEND=48') 
    ntp_request = b'\x1b' + 47 * b'\0'  
    uart.write(ntp_request) #Request 48 Bytes from the server. Trying the get the time here
    
    utime.sleep(3)

    response = uart.read() #Read the 48 Byte response
    print("RAW response:", response) #The whole repsonse
    print()
    print("Decoded response:", int.from_bytes(response[40:44], 'big')) #The decoded part where the current time should be
        


connect_to_ntp()
get_ntp_time()

运行输出:

CMD: AT+CIPCLOSE

AT+CIPCLOSE  
CLOSED  
OK

CMD: AT+CIPSTART="UDP","pool.ntp.org",123

AT+CIPSTART="UDP","pool.ntp.org",123  
0,CONNECT  
OK

CMD: AT+CIPSEND=48

AT+CIPSEND=48  
OK  
 

RAW response:   
b'\\r\\nRecv 48 bytes\\r\\n\\r\\nSEND OK\\r\\n\\r\\n+IPD,48:\x1c\x02\x03\xe8\x00\x00\x00\x12\x00\x00\x07\xe2O\x85,\x8c\xea\xb3\xa1\xc5\xb0\xfa\xc82\x00\x00\x00\x00\x00\x00\x00\x00\xea\xb3\xa5\xc2v\x18\x1a\x86\xea\xb3\xa5\xc2v\x1a\xd3Z'

Decoded response: 65536000

但65536000对应的是1972年1月29日13:26:40,不是当前时间,需要解决这个问题。


问题分析

  1. 时间戳基准错误:NTP协议的时间戳以1900年1月1日为起点,而Unix时间戳以1970年1月1日为起点,两者相差2208988800秒,直接把NTP时间戳当Unix时间戳计算会导致时间偏差。
  2. 响应数据提取错误:ESP8266返回的响应包含AT指令状态信息(如SEND OK),未过滤这些内容就截取字节,导致取到的不是NTP响应包的正确位置。
  3. NTP请求包兼容性:当前使用的\x1b是NTP版本3的请求包,建议使用版本4的标准请求包,兼容性更好。

修正后的代码

import machine
import utime

uart0 = machine.UART(0, baudrate=115200)
uart0.init(115200, bits=8, parity=None, stop=1)

# NTP时间戳与Unix时间戳的差值(1900-1970的总秒数)
NTP_UNIX_DIFF = 2208988800

def send_at_cmd(cmd, uart=uart0):
    print("CMD: " + cmd)
    uart.write(cmd + "\r\n")
    utime.sleep(1)  # 缩短等待时间,可根据ESP8266响应速度调整
    resp = uart.read()
    if resp:
        print(resp.decode())
    print()

def connect_to_ntp():
    send_at_cmd('AT+CIPCLOSE')
    send_at_cmd('AT+CIPSTART="UDP","pool.ntp.org",123')

def get_ntp_time(uart=uart0):
    send_at_cmd('AT+CIPSEND=48')
    # 标准NTP版本4客户端请求包:LI=0, VN=4, Mode=3 → 十六进制0x23
    ntp_request = b'\x23' + 47 * b'\0'
    uart.write(ntp_request)
    
    utime.sleep(2)
    response = uart.read()
    if not response:
        print("未收到NTP响应")
        return None
    
    print("RAW response:", response)
    print()
    
    # 定位NTP响应数据的起始位置
    ipd_start = response.find(b'+IPD,48:')
    if ipd_start == -1:
        print("未找到有效NTP响应")
        return None
    
    # 截取纯NTP响应包(跳过+IPD,48:的7个字节,取后面48字节)
    ntp_response = response[ipd_start + 7 : ipd_start + 7 + 48]
    if len(ntp_response) != 48:
        print("NTP响应数据长度异常")
        return None
    
    # 提取NTP时间戳(第40-43字节,大端字节序)
    ntp_timestamp = int.from_bytes(ntp_response[40:44], 'big')
    # 转换为Unix时间戳
    unix_timestamp = ntp_timestamp - NTP_UNIX_DIFF
    print("Unix时间戳:", unix_timestamp)
    
    # 转换为UTC时间
    utc_time = utime.localtime(unix_timestamp)
    print("UTC时间:", utc_time)
    
    # 转换为东八区时间(根据自己的时区调整偏移量)
    local_timestamp = unix_timestamp + 8 * 3600
    local_time = utime.localtime(local_timestamp)
    print("东八区时间:", local_time)
    
    # 更新Pico的RTC
    rtc = machine.RTC()
    # RTC.datetime格式:(年, 月, 日, 星期, 时, 分, 秒, 微秒)
    rtc.datetime((utc_time[0], utc_time[1], utc_time[2], utc_time[6], utc_time[3], utc_time[4], utc_time[5], 0))
    print("RTC已更新")
    
    return local_time

connect_to_ntp()
get_ntp_time()

关键修正说明

  • 过滤AT响应头:通过response.find(b'+IPD,48:')精准定位NTP数据起始位置,确保只解析真实的NTP响应包。
  • 时间戳转换:减去NTP_UNIX_DIFF将NTP时间戳转为Unix时间戳,再用utime.localtime()转换为可读时间格式。
  • 优化等待逻辑:缩短utime.sleep()的时长,提升执行效率,可根据实际硬件响应速度微调。
  • RTC同步:获取到时间后直接更新Pico的RTC,若外接纽扣电池供电,断电后可保持时间。

内容的提问来源于stack exchange,提问作者Eric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:27:02