文章目录
前言
前文已经简单介绍了基于K210的色彩识别和数字识别,那么接下来就是一些简单的外设操作(包括定时器、按键、串口等),之后就可以和我们的主控进行连接了。
一、 常用硬件控制函数
1. register( pin, func, force=True)
- 功能:设置外设功能(引脚功能配置)
- 参数
- pin :功能映射引脚
- func:芯片功能
- force:强制分频,True:多次对同一个引脚注册;False:不容许多次注册
- 说明:无
2. GPIO( ID, MODE, PULL)
- 功能:注册IO
- 参数
- ID :使用的引脚号(利用GPIO里带的常量来指定)
- MODE:GPIO模式
- GPIO.IN :输入模式
- GPIO.OUT :输出模式
- PULL:GPIO上下拉模式
- GPIO.PULL_UP:上拉
- GPIO.PULL_DOWN:下拉
- GPIO.PULL_NONE:暂不设置
- 说明:无
3. sleep( )
- 功能:延时函数,单位为秒
4. Timer( id, channel, mode)
- 功能:定时器
- 参数
- id:定时器号(Timer.TIMER0~Timer.TIMER2)
- channel:定时器通道(Timer.CHANNEL0~Timer.CHANNEL3)
- mode:定时器模式(包括Timer.MODE_PWM,Timer.MODE_PERIODIC或者Timer.MODE_ONE_SHOT)
- period:定时器周期
- Timer.UNIT_S:秒
- Timer.UNIT_MS:毫秒
- Timer.UNIT_US:微妙
- Timer.UNIT_NS:纳秒
- unit:设置周期单位默认为毫秒
- callback:定时器回调函数
- arg:传给回调函数的参数
- start:是否立即开始启动定时器(True:立即;False:稍后使用start()开启)
- priority:硬件中断优先级,在 K210 中,取值范围是[1,7],值越小优先级越高
- div:硬件分频器。
- 说明:回调函数是在中断中调用的,不要长时间占用
5. PWM(tim, freq, duty, pin, enable)函数
- 功能:脉宽调制模块,硬件支持的PWM
- 参数
- tim:传递定时器号和通道
- freq:PWM波形频率
- duty:PWM占空比
- pin:输出引脚
- enable:是否立刻生成波形
- 说明:无
二、K210的定时器
K210具有3个定时器,每个定时器都具备4路通道,这些通道可以被配置为PWM(脉冲宽度调制)输出。这种设计使得K210的定时器在多种应用场景中都能发挥出色的性能。特性:K210的定时器具有一系列引人注目的特性。首先,它拥有32位计数器宽度,提供了广泛的时间范围。其次,定时器可以配置为向上或向下时基计数器,根据需求增加或减少计数。此外,每个定时器都具有独立的时钟源,可以实现精准的时间控制。中断极性、中断输出标志以及读/写一致性寄存器等特性也增强了定时器的灵活性和可靠性。
from Maix import GPIO #导入相关的包
from fpioa_manager import fm
from machine import Timer
#注册IO和构建LED对象
fm.register(12, fm.fpioa.GPIO0)
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT)
#计数变量
Count=0
#定时器回调函数
def fun(tim):
global Count
Count = Count + 1
print(Counter)
LED_B.value(Counter%2)#LED循环亮灭。
#定时器1通道1初始化,周期1秒
tim = Timer(Timer.TIMER1, Timer.CHANNEL1, mode=Timer.MODE_PERIODIC, period=500, callback=fun)
三、K210的串口
其实对于我们来说,K210并不具有很强的普适性,更多情况下是作为一个视觉模块的外设,这时候它就需要通过串口和我们的主控芯片进行连接.
1. 头文件
以下是串口配置中需要导入串口相关模块,以提供硬件支持。
import sensor, time, image # 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
from fpioa_manager import fm # 从 fpioa_manager 模块中导入 引脚注册模块 fm
from Maix import GPIO # 从 Maix 模块中导入 模块 GPIO
from machine import Timer, PWM, UART # 从 machine 模块中导入 定时器模块 Timer 脉宽调制模块 PWM 双向串行通信模块 UART
#以下库看个人需求
import math # 导入数学函数模块 math
import lcd, struct, ustruct
2. 串口配置
代码如下(示例):
#串口配置区
fm.register(6, fm.fpioa.UART1_TX, force=True)
fm.register(7, fm.fpioa.UART1_RX, force=True)
k210_uart = UART(UART.UART1, 921600, 8, 0, 0, timeout=1000, read_buf_len=4096)
这里我设置了uart1串口,将P6 映射为串口1的RX,P7 映射为串口1的TX。波特率是设置为 921600,其他设置例如:数据位、校验位、停止位,则根据个人需求即可。
3. 数据包(格式)的解析
如果想简单使用,则只需要按照以前的数据协议编写即可,这里考虑到视觉识别出来的信息较多且出于稳定性的考虑使用了开源的协议格式。
为了进一步提高数据的完整性、准确性、可靠性并简化数据处理过程,这里我们使用一个定义好的通讯协议。这里我们使用国产RT-Thread的一个软件包来解决这个问题,这个包是不依赖于RT-Thread的,在裸机中也可以直接使用,不需要额外更改,这个时候我们的数据包就是4字节(包头)+N字节数据。upacker所定义的数据格式如下所示:
4BYTE
------------------------------------------------------------------------------
D0[7:0] |D1[7:0] |D2[5:0] |D2[7:6] |D3[1:0] |D3[7:2]
------------------------------------------------------------------------------
包头 |包长(低8) |包长(高6) |Header校验[3:2] |Header校验[5:4] |check[7:2] |data
------------------------------------------------------------------------------
0x55 |0XFF |0X3F |0X0C |0X30 |0XFC |你的数据
这个数据格式的意思是D0后面的八个字节全部用来表示包头,D2[5:0]是用了位域,指的是用数据包的第2个字节中的0到5位,一个字节是8位。
D0[7:0]: 数据包的包头,该字段的值为固定的0x55,用于标识数据包的起始。
D1[7:0]: 数据包的长度的低8位,表示数据包的总长度。在示例中,该字段的值为0xFF,表示数据包的总长度的低八位为0xFF。
D2[5:0]: 数据包的长度的高6位,表示数据包的总长度的高位。在示例中,该字段的值为0x3F,表示数据包的总长度的高6位为0x3F。
D2[7:6]: Header校验的第2和第3位。在示例中,该字段的值为0x0C。
D3[1:0]: Header校验的第4和第5位。在示例中,该字段的值为0x30。
D3[7:2]: 校验位和数据字段。在示例中,该字段的值为0xFC。
示例中,包头为0x55,包长最大为16384个字节,校验位分布在Header校验和check字段中。这个数据结构的目的是在通信或数据传输中定义数据包的格式和内容,以便发送方和接收方能够正确解析和处理数据。
注:位域(Bit-field)是C语言中的一种数据结构,用于在一个特定的存储单元中存放一组位。位域把一个字节中的二进制位划分为几个不同的区域,每个区域有不同的位数,并且每个区域都有一个特定的域名,这样允许在程序中通过域名来操作这些区域。位域的主要目的是节省存储空间,并使处理更为简便。
在移植的时候,只需要实现发送数据的函数和解包成功后的处理回调函数就可以了。代码如下(示例):
4. MCU对K210发送数据
首先你需要将upacker包纳入工程路径,它里面包括了初始化等操作,如下所示下载连接在这:https://github.com/aeo123/upacker
//初始化
int upacker_init(upacker_inst_t packer, PACKER_CB h, PACKER_CB s)
{
#if USE_DYNAMIC_MEM
packer->data = (uint8_t *)UP_MALLOC(MAX_PACK_SIZE);
if (!packer->data)
{
return -1;
}
#endif
packer->cb = h;
packer->send = s;
return 0;
}
接着我们要进行串口的搭建。
//k210发送接口
static void k210_protocol_send(uint8_t *buff, uint16_t len)
{
for(uint16_t i = 0; i < len; i++)
{
usart_data_transmit(COM_UART2, (uint8_t)buff[i]);
while(RESET == usart_flag_get(COM_UART2, USART_FLAG_TBE));
}
}
// 利用upacker_init可以将发送程序挂载到k210_msg_packer上面
// 后续只需要调用upacker_pack函数就可以发送数据包到串口了
upacker_init(&k210_msg_packer, k210_protocol_handle_cb,
k210_protocol_send);
//控制K210进入模式1
int send_uart_k210_to_mode1(void)
{
static uint8_t temp = 0x01;
upacker_pack(&k210_msg_packer, &temp, 1);
return 0;
}
5. MCU解包数据
实际上,我们的数据流转大致是这样的:串口DMA接收→uart2的ringbuffer→系统空闲时upacker从ringbuffer中获取字符串。
//首先从串口中的ringbuffer获取字符串,有数据后就调用upacker的解包函数进行解包处理
if (ring_buffer_available_read(&rb_uart2) >= 1)
{
ring_buffer_read(&rb_uart2, &receive_char, 1);
upacker_unpack(&k210_msg_packer, &receive_char, 1);
}
//校验成功之后便可以进行赋值了
static void k210_protocol_handle_cb(uint8_t *buff, uint16_t len)
{
static uint32_t temp_tick;
// 接收到payload,到了这里下面的数据就是已经校验过的了
receive_count++;
if (get_system_tick() - temp_tick >= 1000)
{
receive_count = 0;
temp_tick = get_system_tick();
}
last_receive_k210_tick = get_system_tick();
...
}
6. K210串口函数编写
因为micro python在底层已经把串口的发送接收给封装好了,且前面已经介绍了串口初始化的方法,接下来将着重说一下读写函数。
def send_data_to_mcu(pack,global_uart_send_data):
hex_data = ustruct.pack("<bbhhhhbb", #小端字节序
global_uart_send_data.data)
pkg_data = pack.enpack(hex_data)
k210_uart.write(pkg_data)
read_data = k210_uart.read()
if read_data:
read_str = pack.unpack(read_data, print_hex)
send_data_to_mcu函数接受两个参数,一个是pack(Upacker的实例),另一个是global_uart_send_data(就是要发送给MCU的数据)。使用ustruct库的pack( )方法将global_uart_send_data中的数据打包成字节数组。在这里,使用了小端字节序格式(<),以及两种标识符:
- b:有符号字节(1字节整数)
- h:有符号短整数(2字节整数)
然后,使用pack.enpack( )方法将打包后的字节数组封装成一个数据包,最后通过串口(k210_uart.write( ))将数据包发送出去。
申明:本内容是基于立创梁山派开发板搭建的,资料参考自梁山派智能小车项目,感兴趣大家可以自己动手试一下,如有侵权或者内容错误请联系作者删除修改。