一、串口简介
串行接口简称串口,也称串行通信接口或串行通讯接口(通常指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进制并按相应计算式转化文章来源:https://www.toymoban.com/news/detail-449208.html
#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和五插针土壤传感器。串口在单片机开发中应用广泛,它为单片机和其它外设的信息传输提供了很大的便利性和很高的可操作性。文章来源地址https://www.toymoban.com/news/detail-449208.html
到了这里,关于MicroPython开发esp32入门笔记--串口篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!