【K210】K210学习笔记五——串口通信
前言
本人大四学生,电赛生涯已经走到尽头,一路上踩过不少坑,但运气也不错拿了两年省一,思来想去,决定开始写博客,将电赛经验分享一二,能力有限,高手轻喷。
往期的博客讲述了 K210 的感光元件模块 sensor 的配置,机器视觉模块 image 中部分函数的使用(目前是用 find_blobs 函数实现一些寻找不同颜色的目标点,寻找不同颜色的线,后面会更新更多 image 模块中的函数使用方法),按键、LCD、LED的使用,以及定时器的使用。
sensor 的学习笔记传送门
【K210】K210学习笔记一——sensor
image 的学习笔记传送门
【K210】K210学习笔记二——image
按键、LCD、LED的使用 的学习笔记传送门
【K210】K210学习笔记三——按键、LCD、LED的使用
定时器的使用
【K210】K210学习笔记四——定时器的使用
本文着重于 K210 如何进行串口通信,如果 MCU 之间无法通信,就算在 K210 上识别做的再好也没有用。我之前发过一篇K210和STM32通信、K210和OpenMV通信的博客,但我觉得那一份的代码还不够“通用”,因此我今天将串口通信的代码进行重置,希望能让大家觉得比上次那一期更好用,要是想回顾上一期代码,了解串口发送与接收,传送门如下。
串口通信 传送门
【串口通信】K210与STM32串口通信、K210与OpenMV串口通信
K210如何进行串口通信
K210串口配置
串口配置需要导入串口相关模块,以提供硬件支持。UART、fm、GPIO、math这四个是必不可少的。其他模块是往期代码中的,我没有删除(主要是想让这份代码大家拿去就可以使用,不用一篇篇翻我的博客去添加那些模块的配置,按需删除不需要的部分)。
# UART_V2.0 - By: FITQY - 周三 8 月 24 日 2022
#__________________________________________________________________
# 导入模块
import sensor, time, image # 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
import utime # 导入延时模块 utime
from fpioa_manager import fm # 从 fpioa_manager 模块中导入 引脚注册模块 fm
from Maix import GPIO # 从 Maix 模块中导入 模块 GPIO
import lcd # 导入 LCD 模块
from machine import Timer, PWM, UART # 从 machine 模块中导入 定时器模块 Timer 脉宽调制模块 PWM 双向串行通信模块 UART
import math # 导入数学函数模块 math
然后就是对 K210 的串口进行设置,这里我设置了两个串口。K210的串口引脚是可以自行映射的,我用的 K210 是 01Studio 的,这一款 K210 的 P9 和 P6 引脚是相邻的,P7 和 P8 引脚是相邻的,因此我就把 P9 映射为 串口1的RX,P6 映射为 串口1的TX,P7 映射为 串口2的RX,P8 映射为 串口2的TX。波特率是设置为 921600,这个是可以更改的,你要使用 115200也是可以的,只要和其他硬件的串口波特率对的上都可以。至于其他设置,数据位8,校验位0,停止位1,一般都是这样设置,这个不建议更改。
需要注意的是,不建议使用K210引脚号数偏大的引脚,比如26以后的,因为这些引脚似乎在接入SD卡、LCD、麦克风阵列等外设的时候会被占用。
#__________________________________________________________________
# 串口的使用
# 串口1 设置 P9 RX P6 TX
fm.register(9, fm.fpioa.UART1_RX, force = True) # 配置 9 脚为 UART1_RX 强制注册
fm.register(6, fm.fpioa.UART1_TX, force = True) # 配置 6 脚为 UART1_TX 强制注册
uart1 = UART(UART.UART1, 921600, 8, 0, 1) # 设置 uart1 为 串口1 波特率 921600 数据位 8位 校验位 0位 停止位 1位
# 串口2 设置 P7 RX P8 TX
fm.register(7, fm.fpioa.UART2_RX, force = True) # 配置 7 脚为 UART2_RX 强制注册
fm.register(8, fm.fpioa.UART2_TX, force = True) # 配置 8 脚为 UART2_TX 强制注册
uart2 = UART(UART.UART2, 921600, 8, 0, 1) # 设置 uart2 为 串口2 波特率 921600 数据位 8位 校验位 0位 停止位 1位

K210串口发送相关定义
这里我定义了一个类,用来保存串口发送的相关信息,方便传入参数。
串口发送数据的核心思想就是将要发送的数据打包成一个数组,然后将这个数组发送出去。如果不打包,接收端很难将数据正常解析出来,帧头的目的就是方便接收端找到数组的“头”,然后从这个“头”开始,逐个接收数据,有效数据长度位控制的是接收多少个有效数据(就是你要发送的数据),加有效数据长度位是方便后续添加新的需要发送的数据,另外接收端可以灵活接收不同长度的数据,最后接收端对接收数据进行校验,如果校验通过,则保存这些数据,这里用的校验方法是校验和。
帧头我都是用 0xAA,因为 0xAA 转换成8位是 1010 1010 电平是有持续跳变的,可以提高点稳定性。
需要注意的是一次最好不要发送太多的数据,我在接收端代码的定义中是定义有效数据长度不超过40个8位,如果要发送x,y,color,shape,flag,虽然好像这里只有五个数据,但x和y是需要使用两个8位来发送的,因此实际有效数据长度是7个8位。关于为什么要用两个8位来发,原因就是我感光元件设置的图像大小是QVGA,x轴坐标变化范围为0到319,y轴坐标变化范围为0到239,x超过了255因此需要用两个8位来发送,一个8位能保存的数据是从0到255,两个8位是可以保存0到65535的。至于像颜色标志位、形状标志位、目标标志位这些一个8位就足够了,比如用1、2、3分别表示红、绿、蓝,也就是在识别到红色的时候,加一句TSTM32.color = 1,以此类推。当然,这些参数都只是一个名字而已,想来保存什么标志位或者什么数据发送到主控,都可以根据个人需求定义,理解了就不难。
还有一点就是,如果接收端经常出现乱序,不妨可以试一下更改帧头。当然,发送端发送的帧头和接收端识别接收的帧头要一并修改,这一点需要牢记。
#__________________________________________________________________
# 串口发送
# 定义 UART 发送类
class UART_Transmit(): # 定义 UART 发送类
pack_flag = 0 # 打包方法标志位
head1 = 0x00 # uint8_t 帧头1
head2 = 0x00 # uint8_t 帧头2
x = 0 # uint16_t 目标x轴坐标
y = 0 # uint16_t 目标y轴坐标
color = 0 # uint8_t 目标颜色标志位
shape = 0 # uint8_t 目标形状标志位
flag = 0 # uint8_t 目标标志位
mode = 0 # uint8_t 模式标志位
# 实例化类
TSTM32 = UART_Transmit() # 实例化 UART_Transmit() 为 TSTM32
TSTM32.pack_flag = 1 # 打包方法为 方法1
TSTM32.head1 = 0xAA # TSTM32 的帧头1为 0xAA
TSTM32.head2 = 0xAA # TSTM32 的帧头2为 0xAA
TOpenMV = UART_Transmit() # 实例化 UART_Transmit() 为 TOpenMV
TOpenMV.pack_flag = 2 # 打包方法为 方法2
TOpenMV.head1 = 0xAA # TOpenMV 的帧头1为 0xAA
TOpenMV.head2 = 0xAA # TOpenMV 的帧头2为 0xAA
# 定义打包函数
def Pack_Data(TData):
data = UART_Pack_Method(TData) # 根据不同的方法打包发送数据
# 数据包的长度
data_len = len(data) # 获得数据包总长度
data[2] = data_len - 4 # 有效数据的长度 扣去 帧头1 帧头2 有效数据长度位 校验位
# 校验和
sum = 0 # 和置零
for i in range(0,data_len-1):
sum = sum + data[i] # 和累加
data[data_len-1] = sum # 和赋值 给数组最后一位发送 只保存低8位 溢出部分无效
# 返回打包好的数据
return data
与之前不同的是,我将打包函数做成了一个可以带入参数的函数,这样就可以方便实现不同的打包需求,不需要每一次有新的需求的时候,将整段代码复制下来,然后改个名字。 如果你有新的打包需求,就只需要实例化一个新的串口发送类,然后将打包方法设置为3,在这里加个
elif TData.pack_flag == 3:
然后在这个 elif 里面写上你新的打包需求即可。
# 串口数据打包方法函数
def UART_Pack_Method(TData):
if TData.pack_flag == 1:
data = bytearray([TData.head1, # 帧头1
TData.head2, # 帧头2
0x00, # 有效数据长度 0x00 + data_len - 4
TData.mode, # 保存目标x轴坐标 高八位
0x00]) # 数据和校验位
elif TData.pack_flag == 2:
data = bytearray([TData.head1, # 帧头1
TData.head2, # 帧头2
0x00, # 有效数据长度 0x00 + data_len - 4
TData.x>>8, # 保存目标x轴坐标 高八位
TData.x, # 保存目标x轴坐标 低八位
TData.y>>8, # 保存目标y轴坐标 高八位
TData.y, # 保存目标y轴坐标 低八位
TData.color, # 保存目标颜色标志位
TData.shape, # 保存目标形状标志位
TData.flag, # 保存目标标志位
0x00]) # 数据和校验位
return data
K210串口接收相关定义
串口接收数据需要用数组来缓冲,因此还需要定义一个类来缓冲串口数据。与之前不同的是,我新增了一点定义,目的是为了让串口接收的函数可以带入参数,就不用在每次有新的接收方法的时候将整段的代码复制下来改名字了。
#__________________________________________________________________
# 串口接收
# 定义 UART 接收类
class UART_Receive(object): # 定义 UART 接收类
uart_buf = [] # 串口缓冲区数组
data_len = 0 # 有效数据长度
data_cnt = 0 # 总数据长度
state = 0 # 接收状态
buf_len = 0 # 保存串口等待字节的数量
head1 = 0x00 # 接收帧头1
head2 = 0x00 # 接收帧头2
lenmax = 0 # 有效数据最大长度
save_flag = 0 # 保存方法标志位
# 实例化类
RSTM32 = UART_Receive() # 实例化 UART_Receive() 为 RSTM32
RSTM32.head1 = 0xAA # RSTM32 的帧头1为 0xAA
RSTM32.head2 = 0xAA # RSTM32 的帧头2为 0xAA
RSTM32.lenmax = 40 # RSTM32 的有效数据最大长度为 40
RSTM32.save_flag = 1 # RSTM32 的保存方法标志位为 1
ROpenMV = UART_Receive() # 实例化 UART_Receive() 为 ROpenMV
ROpenMV.head1 = 0xAA # ROpenMV 的帧头1为 0xAA
ROpenMV.head2 = 0xAA # ROpenMV 的帧头2为 0xAA
ROpenMV.lenmax = 40 # ROpenMV 的有效数据最大长度为 40
ROpenMV.save_flag = 2 # ROpenMV 的保存方法标志位为 2
首先是串口数据的读取,这个被我制成了可以带入参数的读取函数,每次有新的需求也不需要整段复制改名字。
# 定义串口数据读取函数
def UART_Read(RData, uart):
RData.buf_len = uart.any() # 检查 串口 是否有内容需要读取 返回等待的字节数量(可能为0)
for i in range(0, RData.buf_len): # 读取 RData.buf_len 个数据
Receive_Data(RData, uart.readchar()) # 接收单个数据 uart.readchar() 然后将这个数据传递到函数 Receive_Data() 进行 数据接收
串口数据接收函数的执行流程如下。
默认状态为状态0,若传入的数据等于帧头1,则保存该数据,然后进入状态1。
进入状态1后,若传入的数据等于帧头2,则保存该数据,然后进入状态2。
进入状态2后,判断需要接收的有效数据长度是否小于设定的有效数据最大长度,若小于,则保存该数据以及总数据长度,然后进入状态3。
进入状态3后,开始接收有效数据长度个数据,接收完毕会进入状态4。
进入状态4后,保存最后一位校验位,然后将数组以及数据总长传入解析函数进行解析。
解析完会将状态置0,数组清空,准备下一次接收。
这个同样也被我制成了可以带入参数的接收函数,每次有新的需求也不需要整段复制改名字。
# 定义串口数据接收函数
def Receive_Data(RData, buf):
if RData.state == 0 and buf == RData.head1: # 判断帧头1是否符合要求 符合则进入下一个状态
RData.state = 1 # 更改状态为 1
RData.uart_buf.append(buf) # 将这个数据添加到数组末尾
elif RData.state == 1 and buf == RData.head2: # 判断帧头2是否符合要求 符合则进入下一个状态
RData.state = 2 # 更改状态为 2
RData.uart_buf.append(buf) # 将这个数据添加到数组末尾
elif RData.state == 2 and buf < RData.lenmax: # 有效数据长度位 规定有效数据长度小于40 符合则进入下一个状态
RData.state = 3 # 更改状态为 3
RData.data_len = buf # 获得有效数据长度
RData.data_cnt = buf + 4 # 获得总数据长度 总数据长度 = 帧头1 + 帧头2 + 有效数据长度位 + 有效数据 + 校验位
RData.uart_buf.append(buf) # 将这个数据添加到数组末尾
elif RData.state == 3 and RData.data_len > 0: # 存储有效数据长度个数据
RData.data_len = RData.data_len - 1 # 每存储一次 还需要存储的数据个数减1
RData.uart_buf.append(buf) # 将这个数据添加到数组末尾
if RData.data_len == 0: # 直到存储完毕
RData.state = 4 # 进入下一个状态
elif RData.state == 4: # 当接收到存储完毕的信息
RData.uart_buf.append(buf) # 保存最后一位校验位 将这个数据添加到数组末尾
RData.state = 0 # 状态重置为0 调用串口数据解析函数进行数据解析
Parse_Data(RData) # 解析数据
#print(RData.uart_buf) # 打印接收数组 若接收结果不对 可取消注释查看数组
RData.uart_buf = [] # 清空缓冲区 准备下次接收数据
else: # 不满足以上条件 视为接收出错 重置状态为0 丢弃所有数据

本文详细介绍了K210微控制器的串口通信实现,包括配置串口、定义发送与接收数据的类、打包与解析数据的方法,以及串口发送接收测试。文中提供了完整的源码,适用于K210与STM32、OpenMV等硬件的串口通信,并展示了如何自定义打包和保存数据的策略。

2万+

被折叠的 条评论
为什么被折叠?



