基于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,不是当前时间,需要解决这个问题。
问题分析
- 时间戳基准错误:NTP协议的时间戳以1900年1月1日为起点,而Unix时间戳以1970年1月1日为起点,两者相差2208988800秒,直接把NTP时间戳当Unix时间戳计算会导致时间偏差。
- 响应数据提取错误:ESP8266返回的响应包含AT指令状态信息(如
SEND OK),未过滤这些内容就截取字节,导致取到的不是NTP响应包的正确位置。 - 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
相关产品推荐
相关产品推荐

