MicroPython开发esp32入门笔记–串口篇

文章目录

  • 一、串口简介
  • 二、串口通信
  • 1. UART协议层原理
  • 2. RS232,RS485,TTL电平的区别
  • 三、 ESP32开发 – – 基于串口和MQTT协议
  • 1. 氮磷钾土壤传感器
  • 2. MicroPython 实现数据读取和发布的代码
  • 总结

  • 一、串口简介

    串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口 (Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。


    二、串口通信

    通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)是串口通信的介质,通常称作UART,是一种异步收发传输器。

    首先介绍以下同步和异步通信,同步是指,发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式;异步是指,发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。换句话说,同步通信是阻塞方式,异步通信是非阻塞方式。在常见通信总线协议中,I2C,SPI属于同步通信而UART属于异步通信。同步通信的通信双方必须先建立同步,即双方的时钟要调整到同一个频率,收发双方不停地发送和接收连续的同步比特流,就像你打电话一样双方要同时在电话两头然后才能沟通交流。异步通信在发送字符时,发送端可以在任意时刻开始发送字符,就像你给别人微信发消息留言,在UART通信中数据起始位和停止位是必不可少的,不然你不知道该接受哪些信息。

    1. UART协议层原理

    协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通信双方的数据包格式要约定一致才能正常收发数据 。

    波特率:异步通信中由于没有时钟信号,所以2个通信设备需约定好波特率,常见的有4800、9600、115200等。波特率的单位是每秒传送的位数

    通信的起始和停止信号:串口通信的一个数据包从起始信号开始,知道停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。

    有效数据:在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为8位或9位长。

    数据校验:在有效数据之后,有一个可选的数据校验位。由于数据通信相对容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)以及无校验(noparity)
    奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:01101001,此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据加上 1 位的校验位总共 9 位。偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。

    2. RS232,RS485,TTL电平的区别

    RS232电气特性
    在RS-232-C中任何一条信号线的电压均为负逻辑关系,即逻辑“1”为-3到-15V;逻辑“0”为+3到+15V
    设备与PC机连接的RS-232接口,因为不使用对方的传送控制信号,只需要三条接口线,即“发送数据TXD”、“接收数据RXD”和“信号地GND”。RS-232传输线采用屏蔽双绞线。

    RS485电气特性
    采用差分信号负逻辑,逻辑"1”以两线间的电压差为-(2-6)V表示;逻辑"0"以两线间的电压差为+(2~6)V表示。RS485接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片, 且该电平与TTL电平兼容,可方便与TTL电路连接。RS-485的数据最高传输速率为10Mbps。

    TTL电气特性
    TTL电平信号被利用的最多是因为通常数据表示采用二进制规定,+5V等价于逻辑“1”,0V等价于逻辑“0”,这被称做TTL(晶体管-晶体管逻辑电平Transistor-Transistor Logic)信号系统,这是计算机处理器控制的设备内部各部分之间通信的标准技术。

    三、 ESP32开发 – – 基于串口和MQTT协议

    1. 氮磷钾土壤传感器

    氮磷钾五插针土壤传感器采用RS485电平标准,可以较为精准地测量土壤的温度,含水量,电导率,PH值,以及土壤中氮、磷、钾的含量

    土壤传感器可连接各种载有差分输入的数据采集器,数据采集卡,远程数据采集模块等设备,接线说明如下图:

    为了用ESP32的串口读取土壤传感器测出的数据,我们需要准备一个RS485转TTL模块,接线的方式是将模块上的RXD接ESP32上的TXD引脚,将模块上的TXD接ESP32上的RXD引脚,将RS485电平标准转换为TTL电平,然后根据串口从机主机一收一发的通信原理进行传感器的数据读取

    根据土壤传感器手册的数据读取原理,我们用ESP32的UART发送一串16进制数组,分别是地址、功能码、起始寄存器地址的高四位和低四位、寄存器长度高四位和低四位、CRC16的高四位和低四位,转成16进制数就是下面表格

    地址 0X01
    功能码 0X03
    起始寄存器地址高 0X00
    起始寄存器地址低 0X00
    寄存器长度高 0X00
    寄存器长度低 0X07
    CRC16低 0X44
    CRC16高 0X09

    若土壤传感器接收正确,将返回以下16进制数的数组,我们需要用UART读取16进制数的数组,然后我们还要将其转化为10进制数,并按照预定的计算式转化

    温度计算:
    当温度低于 0 ℃ 时温度数据以补码的形式上传。
    温度:FF9B H(十六进制)= -101 => 温度 = -10.1℃

    水分计算:
    水分:292 H (十六进制)= 658 => 湿度 = 65.8%,即土壤体积含水率为 65.8%。

    电导率计算:
    电导率:3E8 H (十六进制)= 1000电导率 = 1000 us/cm

    PH值计算:
    PH值:38H(十六进制)=56 => PH值=5.6

    氮磷钾含量值计算(分开读取):
    氮磷钾含量值:16H(十六进制)=22 => 含量值=22

    地址 0X01
    功能码 0X03
    返回有效字节数 0x08
    水分值 0x02 0x92
    温度值 0xFF 0x9B
    电导率值 0x03 0xE8
    PH值 0x00 0x38
    氮含量值
    磷含量值
    钾含量值
    校验码低字节 0x57
    校验码高字节 0xB6

    读取完7个土壤相关的数值后要通过MQTT协议将数据发布

    2. MicroPython 实现数据读取和发布的代码

    首先还是封装好的MQTT模块拿来备用

    import usocket as socket
    import ustruct as struct
    from ubinascii import hexlify
    
    
    class MQTTException(Exception):
        pass
    
    
    class MQTTClient:
        def __init__(
            self,
            client_id,
            server,
            port=0,
            user=None,
            password=None,
            keepalive=0,
            ssl=False,
            ssl_params={},
        ):
            if port == 0:
                port = 8883 if ssl else 1883
            self.client_id = client_id
            self.sock = None
            self.server = server
            self.port = port
            self.ssl = ssl
            self.ssl_params = ssl_params
            self.pid = 0
            self.cb = None
            self.user = user
            self.pswd = password
            self.keepalive = keepalive
            self.lw_topic = None
            self.lw_msg = None
            self.lw_qos = 0
            self.lw_retain = False
    
        def _send_str(self, s):
            self.sock.write(struct.pack("!H", len(s)))
            self.sock.write(s)
    
        def _recv_len(self):
            n = 0
            sh = 0
            while 1:
                b = self.sock.read(1)[0]
                n |= (b & 0x7F) << sh
                if not b & 0x80:
                    return n
                sh += 7
    
        def set_callback(self, f):
            self.cb = f
    
        def set_last_will(self, topic, msg, retain=False, qos=0):
            assert 0 <= qos <= 2
            assert topic
            self.lw_topic = topic
            self.lw_msg = msg
            self.lw_qos = qos
            self.lw_retain = retain
    
        def connect(self, clean_session=True):
            self.sock = socket.socket()
            addr = socket.getaddrinfo(self.server, self.port)[0][-1]
            self.sock.connect(addr)
            if self.ssl:
                import ussl
    
                self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
            premsg = bytearray(b"\x10\0\0\0\0\0")
            msg = bytearray(b"\x04MQTT\x04\x02\0\0")
    
            sz = 10 + 2 + len(self.client_id)
            msg[6] = clean_session << 1
            if self.user is not None:
                sz += 2 + len(self.user) + 2 + len(self.pswd)
                msg[6] |= 0xC0
            if self.keepalive:
                assert self.keepalive < 65536
                msg[7] |= self.keepalive >> 8
                msg[8] |= self.keepalive & 0x00FF
            if self.lw_topic:
                sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
                msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
                msg[6] |= self.lw_retain << 5
    
            i = 1
            while sz > 0x7F:
                premsg[i] = (sz & 0x7F) | 0x80
                sz >>= 7
                i += 1
            premsg[i] = sz
    
            self.sock.write(premsg, i + 2)
            self.sock.write(msg)
            # print(hex(len(msg)), hexlify(msg, ":"))
            self._send_str(self.client_id)
            if self.lw_topic:
                self._send_str(self.lw_topic)
                self._send_str(self.lw_msg)
            if self.user is not None:
                self._send_str(self.user)
                self._send_str(self.pswd)
            resp = self.sock.read(4)
            assert resp[0] == 0x20 and resp[1] == 0x02
            if resp[3] != 0:
                raise MQTTException(resp[3])
            return resp[2] & 1
    
        def disconnect(self):
            self.sock.write(b"\xe0\0")
            self.sock.close()
    
        def ping(self):
            self.sock.write(b"\xc0\0")
    
        def publish(self, topic, msg, retain=False, qos=0):
            pkt = bytearray(b"\x30\0\0\0")
            pkt[0] |= qos << 1 | retain
            sz = 2 + len(topic) + len(msg)
            if qos > 0:
                sz += 2
            assert sz < 2097152
            i = 1
            while sz > 0x7F:
                pkt[i] = (sz & 0x7F) | 0x80
                sz >>= 7
                i += 1
            pkt[i] = sz
            # print(hex(len(pkt)), hexlify(pkt, ":"))
            self.sock.write(pkt, i + 1)
            self._send_str(topic)
            if qos > 0:
                self.pid += 1
                pid = self.pid
                struct.pack_into("!H", pkt, 0, pid)
                self.sock.write(pkt, 2)
            self.sock.write(msg)
            if qos == 1:
                while 1:
                    op = self.wait_msg()
                    if op == 0x40:
                        sz = self.sock.read(1)
                        assert sz == b"\x02"
                        rcv_pid = self.sock.read(2)
                        rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
                        if pid == rcv_pid:
                            return
            elif qos == 2:
                assert 0
    
        def subscribe(self, topic, qos=0):
            assert self.cb is not None, "Subscribe callback is not set"
            pkt = bytearray(b"\x82\0\0\0")
            self.pid += 1
            struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
            # print(hex(len(pkt)), hexlify(pkt, ":"))
            self.sock.write(pkt)
            self._send_str(topic)
            self.sock.write(qos.to_bytes(1, "little"))
            while 1:
                op = self.wait_msg()
                if op == 0x90:
                    resp = self.sock.read(4)
                    # print(resp)
                    assert resp[1] == pkt[2] and resp[2] == pkt[3]
                    if resp[3] == 0x80:
                        raise MQTTException(resp[3])
                    return
    
        # Wait for a single incoming MQTT message and process it.
        # Subscribed messages are delivered to a callback previously
        # set by .set_callback() method. Other (internal) MQTT
        # messages processed internally.
        def wait_msg(self):
            res = self.sock.read(1)
            self.sock.setblocking(True)
            if res is None:
                return None
            if res == b"":
                raise OSError(-1)
            if res == b"\xd0":  # PINGRESP
                sz = self.sock.read(1)[0]
                assert sz == 0
                return None
            op = res[0]
            if op & 0xF0 != 0x30:
                return op
            sz = self._recv_len()
            topic_len = self.sock.read(2)
            topic_len = (topic_len[0] << 8) | topic_len[1]
            topic = self.sock.read(topic_len)
            sz -= topic_len + 2
            if op & 6:
                pid = self.sock.read(2)
                pid = pid[0] << 8 | pid[1]
                sz -= 2
            msg = self.sock.read(sz)
            self.cb(topic, msg)
            if op & 6 == 2:
                pkt = bytearray(b"\x40\x02\0\0")
                struct.pack_into("!H", pkt, 2, pid)
                self.sock.write(pkt)
            elif op & 6 == 4:
                assert 0
    
        # Checks whether a pending message from server is available.
        # If not, returns immediately with None. Otherwise, does
        # the same processing as wait_msg.
        def check_msg(self):
            self.sock.setblocking(False)
            return self.wait_msg()
    
    

    导入那几个库,实例化配置ESP32的UART,这里我们用UART2,波特率调成4800,设置好RXD和TXD的引脚编号。接着我们定义连接WIFI函数;回调函数,里面主要起到MQTT发布数据的作用;数据读取函数,ESP32的串口先发数据包,等待土壤传感器发来的数据包响应,收到数据包后就要进行16进制转10进制并按相应计算式转化

     #main.py
    from machine import UART
    import time
    import network
    from machine import Pin
    from umqttsimple import MQTTClient
    
    uart= UART(2, baudrate=4800, bits=8, parity=None, rx=16,tx=17, stop=1, timeout=100)
    
    array0 = bytearray([0x01, 0x03, 0x00, 0x00, 0x00, 0x07, 0x04, 0x08])
    
    
    
    def do_connect():
        wlan = network.WLAN(network.STA_IF)
        wlan.active(True)
        if not wlan.isconnected():
            print('connecting to network...')
            wlan.connect('SSID', 'passwords')
            i = 1
            while not wlan.isconnected():
                print("正在链接...{}".format(i))
                i += 1
                time.sleep(1)
        print('network config:', wlan.ifconfig())
    
    
    def sub_cb(topic, msg): # 回调函数,收到服务器消息后会调用这个函数
        print(topic, msg)
        if topic.decode("utf-8") == "data" and msg.decode("utf-8") == "earth":
           for i in range(7):
               mqtt.publish(b'data_earth',str(index[i]) + ':' +str(data[i]))
               time.sleep_ms(10)
               
               
    
    def red_data(array):
        uart.write(array)
        time.sleep(1)
        data = []
        aa = []
        if(uart.any()):
            aa=uart.read()        # 返回的是答应帧的内容
            print(aa)
            humi = int.from_bytes(aa[3:5],'big')
            humi /= 1000
            temp = int.from_bytes(aa[5:7],'big')
            temp /= 10
            cond = int.from_bytes(aa[7:9],'big')
            ph = int.from_bytes(aa[9:11],'big')
            ph /= 10
            N = int.from_bytes(aa[11:13],'big')
            P = int.from_bytes(aa[13:15],'big')
            K = int.from_bytes(aa[15:17],'big')
            
        return (humi, temp, cond, ph, N, P, K)
    
    
    do_connect()
    mqtt = MQTTClient("umqtt_client", "192.168.137.141")
    mqtt.set_callback(sub_cb)
    mqtt.connect()
    mqtt.subscribe(b'data')
    index = ['humi', 'temp', 'cond', 'ph', 'N', 'P', 'K']
    
    
    
    while True:
        data = red_data(array0)
        mqtt.check_msg()
        time.sleep_ms(100)
    

    总结

    以上就是MicroPython开发ESP32的学习笔记串口篇,本篇笔记简单介绍了串口的一些知识和MicroPython开发ESP32和五插针土壤传感器。串口在单片机开发中应用广泛,它为单片机和其它外设的信息传输提供了很大的便利性和很高的可操作性。

    物联沃分享整理
    物联沃-IOTWORD物联网 » MicroPython开发esp32入门笔记–串口篇

    发表评论