LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

Rapsberry Pico中使用MicroPython

Raspberry Pico作为一种超小型化的开发板,相比普通版的树莓开发板,其更像是一个单片机——其没有操作系统,启动后自动加载默认的代码。但带来的好处也是更加简单可控,对于处理简单的硬件控制更加好用,同时体积和功耗也更低,一些精简化的Pico体积仅有邮票大小,非常适合部署在体积极为有限的设备内,并且价格仅有几十元,相比标准的树莓也有巨大的优势。

Pico同样可使用C或Python进行开发,但既然选择使用Pico那肯定用Python会更加简单高效,但其不能使用完整的Python系统,使用的为MicroPython,其实现了Python的基本功能,同时还提供了一些针对Pico的功能,例如UART、I2C端口的通信API等。代码直接可运行,不需要进行编译,调试也非常方便,不过为了保护代码也可以将代码编译成二进制。

1. 安装固件

首次使用时需要对Pico进行固件的安装,按住Pico的Boot按键将Pico通过USB连接至PC,此时会识别出一个移动磁盘设备,此时将下载的固件拖入其中即可完成固件的安装,完成后Pico会重启并开始加载固件。

开发可以使用Thonny,其为一个针对MicroPython和Pico的IDE,可以直接连接Pico,并上载代码运行和查看运行结果。

2. Pico的代码运行逻辑

Pico在加载固件后会自动运行main.py,为此可将代码的入口放在这里,通电启动后即开始执行这里的代码,执行结束后退出,一些版本的Pico中也支持通过一个按键进行复位从头再开始执行一次main.py。

对于一些需要一直保持运行状态的应用场景,可以将其主循环放在main.py中,其他的代码放在其余文件中,通过import引入进行调用。

3. 代码示例

通信接口

Pico有26个GPIO针脚,其中很多针脚可以实现多种用途,并且都可以进行PWM信号输出,详细可参考官方的文档

#引入Pico中的基础API
from machine import Pin, PWM, UART, I2C;
#定义对应的通信针脚代号
I2C_SCL_PIN = 3;
I2C_SDA_PIN = 2;
UART_TX_PIN = 0;
UART_RX_PIN = 1;

#初始化对应通信接口
i2c = I2C(1, scl=Pin(I2C_SCL_PIN), sda=Pin(I2C_SDA_PIN));
uart = UART(0, baudrate=9600, tx=Pin(UART_TX_PIN), rx=Pin(UART_RX_PIN));
协程实现异步操作

由于MicroPython中没有完成的多线程支持,为此只能通过协程实现IO部分的异步处理

#接收串口数据
async def uart_rx_task(uart, q):
    #循环接收数据
    while True:
        #如果收到数据则放入队列中
        if uart.any():
            data = uart.read();
            q.append(data);
        else:
            pass;
        #交出执行权给主线程
        await asyncio.sleep(0.1);
#主程序
async def main():
    #启动数据接收协程
    asyncio.create_task(uart_rx_task(uart, q));
    #主循环
    while True:
        #处理队列中的消息
        while len(q) > 0:
            data = q.popleft();
        #处理其他工作...
        #交出执行权给接收协程
        await asyncio.sleep(0.05);

在main.py中启动主程序的写入如下

if __name__ == '__main__':
    asyncio.run(main());

4. 将代码编译成二进制

有时我们不想暴露Pico中代码的实现细节,可以使用工具将代码编译成二进制,虽然编译成的二进制也可通过反编译等手段进行代码的还原,但还原后的代码的可读性要差很多,还是可以起到一定的保护作用。一般推荐的工具为mpy-cross,可以将python代码编程成为mpy文件。编译时需要指定CPU架构,Pico对应的CPU架构为armv6m。

mpy-cross -march=armv6m core.py

会得到一个core.mpy文件,这里不使用main.py的原因是因为Pico不会自己自动运行main.mpy,所以为了能够自动运行可以将代码封装到core.py中编译为core.mpy,另外写一个main.py调用core.mpy中的运行方法执行代码。

import core.main;
if __name__ == '__main__':
    core.main();

使用Python进行CAN总线通信

控制器局域网总线(CAN,Controller Area Network)是一种用于实时应用的串行通讯协议总线,它可以使用双绞线来传输信号,是世界上应用最广泛的现场总线之一。CAN协议用于汽车中各种不同元件之间的通信,以此取代昂贵而笨重的配电线束。

1. CAN总线的物理结构

CAN通讯是异步通讯,只有CAN_H和CAN_L两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。使用闭环总线网络,速度快,距离短,它的总线最大长度为40m,通讯速度最高1Mbps,总线两端各有一个120欧的电阻。

CAN总线上可以挂载多个通讯节点,节点之间的信号进过总线传输,实现节点间通讯。由于 CAN 通讯协议不对节点进行地址编码,而是对数据内容进行编码的,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。

2. CAN通信协议

CAN通信协议分为标准格式和扩展格式,在使用层上两者主要的区别在于ID的长度不同,标准帧为11位,扩展帧为29位,传输数据的部分都是0~8个字节的数据段(数据前用4位的标识数据的长度)。

3. CAN接口的配置

CAN接口在Linux系统中和以太网接口一样作为一个通信接口,可以通过ifconfig和ip命令进行配置和启动。以树莓派中常见的MCP2515为例,除了在内核中配置引导对应的模块外。可通过下列命令对CAN接口进行启用

ip link set can0 up type can bitrate 1000000
ifconfig can0 txqueuelen 65536
ifconfig can0 up

如果需要在开机时启动CAN接口,可以将相关配置放在rc.local或udev中。

KERNEL=="can*"
SUBSYSTEM=="net"
ACTION="add"
PROGRAM="/sbin/ifconfig $name txqueuelen 1024"
RUN+="/usr/bin/ip link set up $name type can bitrate 500000"

并执行

udevad control --reload-rules && udevadm trigger

即可实现CAN接口的自动启动。

4. CAN接口的相关工具

可使用命令行工具cansend和candump进行进行CAN通信的快速测试,其格式为

#接收并输出can0接口的数据
candump can0
#接受并输出任何can接口的数据
candump any
#从can0端口发送标识为123,0x11223344的数据
cansend can0 123#11223344

并且在调试时可以将数据同时发送给自己一份,以便进行调试,可在启动can的命令结尾加上loopback=on

5. 使用Python进行CAN通信

在python中使用CAN通信,可安装python的can扩展,可使用pip或系统软件包管理系统进行安装python-can,其使用的方式如所示

#使用can0接口,波特率500kbps,loopback模式为关闭
can0 = can.interface.Bus(channel = 'can0', \
    interface='socketcan', bitrate=500000, local_loopback=False);
#创建id为0x00000625的CAN帧,数据为data,格式为标准帧
msg = can.Message(arbitration_id=0x00000625, \
        data=bytearray([0x40, 0x05, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00]), is_extended_id=False);
#发送数据
can0.send(msg);
#接收数据,等待时间1秒
ret = can0.recv(1.0);