我是靠谱客的博主 腼腆柠檬,最近开发中收集的这篇文章主要介绍树莓派62/100 - NB-IoT的PSM省电模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

我现在的一个项目里面对的需求是:设备最好能够连续待机1个月到3个月,不正常移动时要能够马上发出报警信息。NB-IoT里的PSM省电模式(Power Saving Mode)是必须要重点考虑的。

NB-IoT支持三种省电模式:

  • PSM (Power Saving Mode,省电模式)
  • DRX(Discontinuous Reception,不连续接收模式)
  • eDRX(Extended DRX,扩展不连续接收模式)

这篇文章主要讨论第一种,PSM。

查了不少文章,感觉这篇文章里的一张图比较清晰地说明PSM的原理。
在这里插入图片描述

这里又遇到一个关键的术语,TAU(Tracking Area Update),好像是LTE里的一个基本概念,这篇文章里也有详细介绍,我也没有看懂。我个人的理解是:

我手里有一张中国移动的NB-IoT卡,它会定期与中国移动运营商的基站进行通讯,确定它在蜂窝网络中的位置等信息,并且要不断更新这组信息。

图中的T3412和T3324是个两个定时器,T3412就是对应着TAU这段时间周期。

而T3324在图中称为IDLE空闲状态,在AT指令手册里称为Active Time,在这段时间里用户设备(UE)可以收到移动运营商的短信通知等,也就是说运营商可以找到设备。

随后进入PSM状态,也可以叫睡眠状态,这时移动运营商无法主动呼叫设备,而设备可以主动醒来,也可以等PSM定时器超时后唤醒设备,PSM的时间 = T3412 - T3324。

AT指令里的 AT+CPSMS 可以设置这2个时间值,它的语法:

AT+CPSMS=1,,,<Requested Periodic TAU>,<Requested Active Time>

第一个参数1表示启用PSM模式,随后的两个参数对于NB-IoT不适用,所以是三个逗号,后面两个参数分别对应于T3412和T3324。注意这里有Requested(请求)这个单词,意思是T3412和T3324不是设备自行确定的,它发给移动运营商,移动运营商为给出反馈,以运营商设定的数值为准。

T3412和T3324都用8个二进制的字符串来表示,高3位表示不同的时间单位,低5位为小于32的整数,如下图所示。
在这里插入图片描述
例如:

AT+CPSMS=1,,,"00100001","00000111"

表示T3412为1小时(1小时 * 1),T3324为14秒(2秒 * 7)
如果设置成功,系统会反馈+CEREG信息(需提前设置AT+CEREG=5)。

我得到的反馈是:

+CEREG: 1,"2A22","0202EE2A",9,"00",0,0,"00000111","00100001"

这里的9表示NB-IoT,最后的两个参数就是T3324和T3412,需要注意的是它们的顺序与CPSMS指令的顺序正好颠倒了。

我尝试了多组T3412和T3324参数组合,我手里的中国移动NB-IoT卡不支持1小时以内的T3412设置,但调高它还是允许的,最高可以设置为320小时*31=413天!而T3324则没有限制,只要小于T3412即可。

sim7020.py程序代码:

import machine
import utime
import ubinascii
from ulogging import *


# 从字符串中查找以指定的key开头的文本
# 比如:find_by_key(b'rn+CSQ: 12rn', b'+CSQ:')
# 将返回"12",移除了前后的空白符
# 如果查找不到,则返回""
def find_by_key(s, key_string):
    for line in s.split('rn'):
        if line.startswith(key_string):
            return line[len(key_string):].strip()

    return ""


def get_client_id(resp):
    str_id = find_by_key(resp, '+CHTTPCREATE:')
    if str_id != "":
        return int(str_id)
    return -1


class SIM7020:
    HTTP_GET = 0
    HTTP_POST = 1
    
    def __init__(self, uart, power_pin=14):
        self.uart = uart
        self.power_enable = machine.Pin(power_pin, machine.Pin.OUT)


    def read_uart(self, timeout = 2000, fast_return = False):
        ticks_start = utime.ticks_ms()
        resp = b''
        while utime.ticks_diff(utime.ticks_ms(), ticks_start) < timeout:
            if self.uart.any():
                line = self.uart.readline()
                resp += line
                debug("■■■■", utime.ticks_diff(utime.ticks_ms(), ticks_start), line)
                if fast_return:
                    if resp.endswith(b'rnERRORrn') or resp.endswith(b'rnOKrn'): 
                        break               
                   
        return resp
    

    def exec_cmd(self, cmd, timeout=2000):
        if cmd != "":
            debug("AT命令>>>", cmd)
            self.uart.write((cmd + 'rn').encode())
        resp = self.read_uart(timeout).decode()
        debug("响应<<<", resp)
        return resp


    # 读串口时,遇到OK或ERROR时就直接返回结果,不再等待超时
    def exec_cmd_fast(self, cmd):
        self.uart.write((cmd + 'rn').encode())
        raw = self.read_uart(1000, fast_return = True)
        resp = ""
        try:
            resp = raw.decode()
            
        except:
            resp = "ERROR: UnicodeError: " + ubinascii.hexlify(raw).decode()
            print("raw data:", raw)
            
        return resp


    def exec_cmd_find_key(self, cmd, key):
        resp = self.exec_cmd_fast(cmd)
        return find_by_key(resp, key)


    def soft_boot(self):
        self.exec_cmd_fast('AT+CFUN=0')
        utime.sleep(5)
        self.exec_cmd_fast('AT+CFUN=1')
        

    def reboot(self, force = False):
        if force:
            self.power_enable.off()
            utime.sleep(0.1)
        self.power_enable.on()
        self.read_uart() #清除串口里可能残留的数据


    def get_signal_strength(self):
        s = self.exec_cmd_find_key("AT+CSQ", '+CSQ:')
        if s != "":
            return int(s.split(',')[0])

        return -1 

    ### 自检,等待设备联网
    def wait_ready(self):
        #self.exec_cmd_fast("ATE1")
        print("===== 自检开始 =====")
        print(self.exec_cmd_fast("AT+CGMM"))
        print(self.exec_cmd_fast("AT+CPIN?"))
        
        for i in range(1, 10):
            strength = self.get_signal_strength()
            print(i, "信号强度:", strength)
            if strength > 5 and strength != 99:
                break
            utime.sleep(1)
            
        for i in range(1, 10):
            ip = self.get_IP()
            print(i, "IP地址:", ip)
            if ip != "":
                break
            utime.sleep(1)
        print("===== 自检结束 =====")


    def get_IP(self):
        # +CGCONTRDP: 1,5,"cmnbiot","100.1.2.3.255.255.255.0"
        s = self.exec_cmd_find_key("AT+CGCONTRDP", "+CGCONTRDP:")
        paras = s.split(',')
        if len(paras) >= 4: 
            ip_and_mask = paras[3].replace('"', '')
            ip = ip_and_mask.split('.')
            if len(ip) >= 4:
                return '.'.join(ip[0:4])
        return ""


    def is_cpin_ready(self):
        s = self.exec_cmd_find_key("AT+CPIN?", "+CPIN:")
        return s == "READY"


    def get_clock(self):
        clock = self.exec_cmd_find_key('AT+CCLK?', '+CCLK:')
        return clock
        
        
    def http_get(self, url):
        index_slash = url.find('/', 8)  # 跳过http://或https:// 找下一个斜杠的位置
        host = url[:index_slash]
        path = url[index_slash:]

        # CHTTPCREATE会返回一个ID,拿着这个ID,进行访问网页的手续操作
        resp = self.exec_cmd_fast('AT+CHTTPCREATE="' + host + '"')
        id = get_client_id(resp)
        if id < 0:
            print(resp)
            return ""
        print("■■■■client id: ", id)
        
        self.exec_cmd_fast('AT+CHTTPCON=' + str(id))
        
        resp = self.exec_cmd('AT+CHTTPSEND=' + str(id) + ',' + str(self.HTTP_GET) + ',"' + path + '"')
        print("HTTP SEND的反馈:", resp)
        
        
        self.exec_cmd_fast('AT+CHTTPDISCON=' + str(id))    
        self.exec_cmd_fast('AT+CHTTPDESTROY=' + str(id))
        return resp

测试PSM的主程序:

import sim7020
import utime
import ubinascii
from ulogging import *


def curTime():
    now = machine.RTC().datetime()
    return "{:02d}:{:02d}:{:02d}".format(now[4], now[5], now[6])


uart = machine.UART(0, 115200, txbuf=1024, rxbuf=1024)
print(uart)

sim = sim7020.SIM7020(uart)
sim.reboot(force=True)
#sim.reboot()

sim.wait_ready()

print(sim.exec_cmd('AT+CPSMS=0'))   #禁止PSM模式
print(sim.exec_cmd('AT+CEREG=5'))
print(sim.exec_cmd('AT+CPSMS=1,,,"11000001","00000111"'))

print("========开始==============", curTime())
for i in range(1, 100000):
    resp = sim.read_uart(1000).decode()
    if resp != "":
        print(curTime(), i, resp)
    if i % 100 == 0:
        print(curTime(), sim.exec_cmd('AT+CEREG?', 5000))
        resp = sim.http_get('http://api.seniverse.com/v3/weather/now.json?key=SwwwfskBjB6fHVRon&location=nanjing&language=en&unit=c')
        print(resp)

在进入和退出PSM状态的时候,串口里还可以收到状态报告:
+CPSMSTATUS: “ENTER PSM”
+CPSMSTATUS: “EXIT PSM”

推荐阅读:
树莓派Pico开发系列文章

最后

以上就是腼腆柠檬为你收集整理的树莓派62/100 - NB-IoT的PSM省电模式的全部内容,希望文章能够帮你解决树莓派62/100 - NB-IoT的PSM省电模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部