【MicroPython学习笔记】01 使用块设备

本文介绍了如何在MicroPython上使用块设备,包括基于RAM的块设备模型和W25QXX闪存芯片驱动的移植过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目录

前言

准备工作

一、MicroPython是什么?

二、使用块设备

1.测试官方示例

2.移植W25QXX芯片驱动

总结


前言

 

块设备是一类可以整块读取和写入的设备,对应的物理设备可以是SD卡和U盘之类,在块设备的基础之上可以使用文件系统来管理文件。

接下来介绍如何在MicroPython上使用块设备。

准备工作

  • 硬件:
    (1). 一块刷有MicroPython固件的开发板,可以是stm32开发板,也可以是ESP32,如果用ESP8266可能会遇到内存不够以及爆栈的问题,需要修改源码自行编译固件,这里不推荐使用;
    (2). 一片W25QXX芯片,带有转接板,连接好之后如下图:

  • 软件:
    可以使用任意串口终端软件,这里推荐Thonny,集编辑器、终端、烧录器于一身,同时支持文件上传 。在这里下载> thonny

 

一、MicroPython是什么?

MicroPython是一个开源项目,它在资源受限的微控制器上实现了精简的Python3语法。

简单来说就是可以在单片机上运行Python脚本。

这是官网> MicroPython官网

 

二、使用块设备

1.测试官方示例

官方文档给出了一个简单的RAM块设备模型,接下来的移植将以它为基础

(官方文档,文件系统部分> Working with filesystems

代码如下:

class RAMBlockDev:
    def __init__(self, block_size, num_blocks):
        self.block_size = block_size
        self.data = bytearray(block_size * num_blocks)

    def readblocks(self, block_num, buf):
        for i in range(len(buf)):
            buf[i] = self.data[block_num * self.block_size + i]

    def writeblocks(self, block_num, buf):
        for i in range(len(buf)):
            self.data[block_num * self.block_size + i] = buf[i]

    def ioctl(self, op, arg):
        if op == 4: # get number of blocks
            return len(self.data) // self.block_size
        if op == 5: # get block size
            return self.block_size

定义了一个 RAMBlockDev 类,提供三个基本方法,分别是readblocks,writeblocks,ioctl

只要实现了这三个方法就可以通过uos.VsFat格式化以及挂载块设备。下面测试一下官方的例子:

import os

bdev = RAMBlockDev(512, 50)
os.VfsFat.mkfs(bdev)
os.mount(bdev, '/ramdisk')

这样在Thonny中查看文件:

执行完上面的代码之后在Thonny中刷新一下可以看到多出了一个ramdisk目录,说明挂载成功了。

挂载成功后,接下来试着读写文件

f = open("/ramdisk/test.txt","w")
f.write("hello")
f.close()

可以看见确实写入了。

官方给的示例是基于内部RAM的块设备,掉电之后文件就会消失,为了解决这个问题我们需要使用非易失性储存设备,比如flash芯片。

 

2.移植W25QXX芯片驱动

 

W25QXX芯片是经典的SPI接口flash储存器,本次使用的芯片是W25Q32,有4M字节的空间可用。在网上可以找到很多的C语言驱动程序,现在将其移植到MicroPython平台

完整代码如下

文件名:w25qxx.py

# 移植自: 正点原子W25QXX驱动代码	
from machine import SPI, Pin
from micropython import const
import gc
import time


__TYPE = dict(
 W25Q80 	= 0XEF13,	
 W25Q16 	= 0XEF14,
 W25Q32 	= 0XEF15,
 W25Q64 	= 0XEF16,
 W25Q128    = 0XEF17,
)

MEM_SIZE = {
    0XEF13 : 1000000,
    0XEF14 : 2000000,
    0XEF15 : 4000000,
    0XEF16 : 8000000,
    0XEF17 : 16000000,
}

# 指令表
W25X_WriteEnable		= const(0x06)
W25X_WriteDisable		= const(0x04)
W25X_ReadStatusReg		= const(0x05)
W25X_WriteStatusReg		= const(0x01) 
W25X_ReadData			= const(0x03) 
W25X_FastReadData		= const(0x0B) 
W25X_FastReadDual		= const(0x3B) 
W25X_PageProgram		= const(0x02) 
W25X_BlockErase			= const(0xD8) 
W25X_SectorErase		= const(0x20) 
W25X_ChipErase			= const(0xC7) 
W25X_PowerDown			= const(0xB9) 
W25X_ReleasePowerDown	= const(0xAB) 
W25X_DeviceID			= const(0xAB) 
W25X_ManufactDeviceID	= const(0x90) 
W25X_JedecDeviceID		= const(0x9F) 

class __W25QXX:
    def __init__(self, SPI, CS_PIN):
        self.__SPI = SPI
        self.__CS_PIN = CS_PIN
        self.read(0,1)
        self.TYPE = self.__readID()
        # self.TYPE = 0XEF15
        self.__MEM = MEM_SIZE[self.TYPE]
        if self.TYPE in list(__TYPE.values()):
            print("W25QXX type is " + list(__TYPE.keys())[list(__TYPE.values()).index(self.TYPE)])
        else: print("unknown device")
    # 写一个字节
    def __writeByte(self, b):
        self.__SPI.write(bytearray([b]))
    # 读取状态寄存器
    def __readSR(self):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_ReadStatusReg]))
        ret = list(self.__SPI.read(1))
        self.__CS_PIN.on()
        return ret[0]
    # 写状态寄存器
    def __writeSR(self, sr):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_WriteStatusReg]))
        self.__SPI.write(bytearray([(sr)]))
        self.__CS_PIN.on()
    # W25QXX写使能	
    # 将WEL置位   
    def __writeEnable(self):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_WriteEnable]))
        self.__CS_PIN.on()
    # W25QXX写禁止	
    # 将WEL清零  
    def __writeDisable(self):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_WriteDisable]))
        self.__CS_PIN.on()
    # 读取芯片ID
    def __readID(self):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([0x90,0,0,0]))
        tmp = list(self.__SPI.read(2))
        return (tmp[0]<<8)+ tmp[1]
    # 读取SPI FLASH  
    # 在指定地址开始读取指定长度的数据
    # pBuffer:数据存储区
    # ReadAddr:开始读取的地址(24bit)
    # NumByteToRead:要读取的字节数(最大65535)    
    def read(self, addr, num):
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_ReadData, (addr>>16)&0xff, (addr>>8)&0xff,addr&0xff]))
        ret = self.__SPI.read(num)
        self.__CS_PIN.on()
        return ret
    # SPI在一页(0~65535)内写入少于256个字节的数据
    # 在指定地址开始写入最大256字节的数据
    # pBuffer:数据存储区
    # WriteAddr:开始写入的地址(24bit)
    # NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
    def __writePage(self, buff, addr, num):
        self.__writeEnable()
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_PageProgram, (addr>>16)&0xff, (addr>>8)&0xff, addr&0xff]))
        self.__SPI.write(buff[:num])
        self.__CS_PIN.on()
        self.__waitBusy()
    # 无检验写SPI FLASH 
    # 必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
    # 具有自动换页功能 
    # 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
    # pBuffer:数据存储区
    # WriteAddr:开始写入的地址(24bit)
    # NumByteToWrite:要写入的字节数(最大65535)
    # CHECK OK
    # 等待空闲
    def __writeNoCheck(self, buff, addr, num):
        pageremain = 256- addr%256
        if(num <= pageremain): pageremain = num
        while True:
            self.__writePage(buff, addr, pageremain)
            if(num == pageremain): break
            else:
                buff = buff[pageremain:]
                gc.collect()
                addr += pageremain
                num -= pageremain
                if num > 256: pageremain = 256
                else: pageremain = num 
    # 写SPI FLASH  
    # 在指定地址开始写入指定长度的数据
    # 该函数带擦除操作!
    # pBuffer:数据存储区
    # WriteAddr:开始写入的地址(24bit)						
    # NumByteToWrite:要写入的字节数(最大65535)  
    def write(self, buff, addr, num):
        buff_tmp = None
        secpos = addr // 4096
        secoff = addr % 4096
        secremain = 4096 - secoff
        if num <= secremain: secremain = num
        while True:
#             print("...")
            buff_tmp = bytearray(self.read(secpos * 4096, 4096))
            if sum(map(lambda x : x^0xff, buff_tmp[secoff:])): # 需要擦除
            # if True: # 需要擦除
                self.__eraseSector(secpos)
                for i in range(0, secremain):
                    buff_tmp[i + secoff] = buff[i]
                self.__writeNoCheck(buff_tmp, secpos * 4096, 4096)
            else: self.__writeNoCheck(buff, secpos * 4096, 4096)
            if num == secremain: break # 写入结束
            else:
                secpos += 1
                secoff = 0
                buff = buff[secremain:]
                addr += secremain
                if num > 4096: secremain = 4096
                else: secremain = num
            gc.collect()
    
    # 擦除整个芯片		  
    # 等待时间超长...
    def eraseChip(self):
        self.__writeEnable()
        self.__waitBusy()
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_ChipErase]))
        self.__CS_PIN.on()
        self.__waitBusy()

    # 擦除一个扇区
    # Dst_Addr:扇区地址 根据实际容量设置
    # 擦除一个扇区的最少时间:150ms
    def __eraseSector(self, addr):
#         print("erase sector : {0:}".format(addr))
        addr *= 4096
        self.__writeEnable()
        self.__waitBusy()
        self.__CS_PIN.off()
        self.__SPI.write(bytearray([W25X_SectorErase, (addr>>16)&0xff, (addr>>8)&0xff, addr&0xff]))
        self.__CS_PIN.on()
        self.__waitBusy()
    # 等待空闲
    def __waitBusy(self):
        while True:
            if self.__readSR()&0x01 == 0x01:
                time.sleep_ms(5)
            else: break

# block device 接口
class W25QXX_BlockDev(__W25QXX):
    def __init__(self, SPI, CS_PIN):
        self.block_size = 4096
        super().__init__(SPI, CS_PIN)
        super().read(0,1)
        # self.data = bytearray(block_size * num_blocks)

    def readblocks(self, block_num, buf):
        buf_tmp = self.read(block_num * self.block_size, len(buf))
        for i in range(len(buf)):
            buf[i] = buf_tmp[i]

    def writeblocks(self, block_num, buff):
        self.write(buff, block_num * self.block_size, len(buff))

    def ioctl(self, op, arg):
        if op == 4: # get number of blocks
            return self.__MEM // self.block_size
        if op == 5: # get block size
            return self.block_size

将该文件上传到开发板中,然后使用如下代码挂载:

from machine import  Pin, SPI
import w25qxx
import uos

spi = SPI(baudrate=10000000, polarity=1, phase=1, sck=Pin(21), mosi=Pin(4), miso=Pin(22))
cs = Pin(23, Pin.OUT)
flash = w25qxx.W25QXX_BlockDev(SPI = spi, CS_PIN = cs)
uos.VfsFat.mkfs(flash)
uos.mount(flash, '/flash')

!!!(注意这里的SCK连接21引脚,MOSI连接4引脚,MISO连接22引脚,CS片选连接23引脚,实际使用时请根据自己的硬件连接情况修改上述代码)

然后就可以愉快的使用外部flash芯片放置文件了^o^y

 

总结

 

将flash芯片的驱动移植之后就可以很方便的在其他支持MicroPython的开发板上使用了,在没有SD卡插槽的情况下还是很管用的

 2021-01-26

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值