稼働中

Raspberry Pi Pico(d_25)SDカードのWrite、Read、Erase

SD Card Write/Read/Erase (SPI)

SDカードのWrite、Read、Eraseについて記載します。
Write/Read/Eraseのコマンドで送る32bitの引数はアドレスです。
High Capacity SD Cards(SDHC 2GB<)は512bytesのブロック単位、Standard Capacity Cards(SDC <=2GB)はバイト単位なので512バイトの倍数で指定します。

下方のスクリプトでは、
spi_cmd(cmd24,0x00,0x00,0x02,0x00,0x01) ※spi_cmd スクリプト内の関数
コマンドcmd24の後の32bitはアドレスです。例では0x00,0x00,0x02,0x00がアドレスです。
SDCの場合のアドレスは0x0200(512)、SDHCの場合は512bytesブロック(512*0x0200 ‘0x40000’)になります。

WRITE_BLOCK(CMD24)

CMD24(WRITE_BLOCK)でシングルブロックを書込みます。単一ブロックは512バイトです。SD(<=2GB)の場合512byteの倍数でアドレスを指定します。
SDC Write
※「Physical Layer Simplified Specification Version 2.00」より抜粋

CDM24を送信し、応答(R1)を待ってから、開始トークン(0xFE)を送信し、続いて512バイトのデータを送信します。
正常に書込みが終わるとData Responseが返ります。レスポンス値と’00011111(0x1f)’と積で評価します。’00000101(0x05)’で正常です。
data-response
※「Physical Layer Simplified Specification Version 2.00」より抜粋

READ_SINGLE_BLOCK(CMD17)

CMD17(READ_SINGLE_BLOCK)でシングルブロック読み取りをします。単一ブロックは512バイトです。
SD(&lt;=2GB)の場合512byteの倍数でアドレスを指定します。
CMD17を送った後のレスポンス’0x00’に続いてトークン’0xfe’を得た後に512バイトデータを読み込みます。512バイトデータの後に2バイトのCRCが続きます。
SDC Read
※「Physical Layer Simplified Specification Version 2.00」より抜粋

ERASE

Eraseのコマンドは以下になります。CDM32で開始アドレス、CMD33で終了アドレスを指定してCMD38でブロックを消去します。単一ブロックは512バイトです。SD(<=2GB)の場合512byteの倍数でアドレスを指定します。


cmd32=0x60    #ERASE_WR_BLK_START_ADDR  R1
cmd33=0x61    #ERASE_WR_BLK_END_ADDR    R1
cmd38=0x66    #ERASE  Erases            R1b

スクリプト

SDカードのWrite、Read、Eraseを行うスクリプトは以下のようにしました。
※開発環境はThonnyです。ThonnyでMicroPythonをRaspberry Pi Pico with RP2040にインストールして使っています。
※Raspberry Pi Pico単独で動作させるには’main.py’としてRaspberry Pi Picoにuploadして使います。


pico_sd_test_05b_02.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from machine import SPI, Pin
import sys
import time
# CMD ERASE
cmd32=0x60    #ERASE_WR_BLK_START_ADDR R1
cmd33=0x61    #ERASE_WR_BLK_END_ADDR   R1
cmd38=0x66    #ERASE  Erases           R1b
# CMD READ
cmd17=0x51    #READ_SINGLE_BLOCK
# CMD WRITE
cmd24=0x58    #WRITE_BLOCK

#SPI.init
spi = SPI(1, baudrate=100_000,sck=Pin(14), mosi=Pin(15), miso=Pin(12))
#Chip Select Pin
CS_pin=Pin(13, mode=Pin.OUT, value=1)  #CS GP1
# Chip Select CS(0)
def CS(dat):
    CS_pin.value(dat)
# card send data
def spi_cmd(cmd,da1,da2,da3,da4,crc):
    buf=bytearray(6)
    buf[0]=cmd
    buf[1]=da1
    buf[2]=da2
    buf[3]=da3
    buf[4]=da4
    buf[5]=crc
    spi.write(buf)
    return buf
# response
def spi_res(num=1):   
    response=spi.read(num)
    #print(response)
    time.sleep_us(100)
    return response
# dummy clock
def spi_ff():
    spi.write(b'\xff')
# spi end
def spi_end():
    spi_ff()
    CS(1)
    spi_ff()

# SET SPI MODE
def SPI_MODE():
    print('SPI_MODE')
    CS(1)                #CS=Hでダミークロックを80個送信
    for i in range(10):
        spi_ff()
    # CS select
    CS(0)                #カード選択
    spi_ff()

## SDC, SDHC---Erase(CMD38)、Write(CMD24)、Read(CMD17)
# CMD32  BLK_START_ADD
def ERASE_START(s=[0x00,0x00,0x00,0x00]):
    # CS select
    CS(0)
    spi_ff()
    spi_cmd(cmd32,s[0],s[1],s[2],s[3],0x01)
    spi_ff() 
    res=spi_res(1)
    if res != b'\x00':
       print('CMD32 ERROR',res)
       sys.exit()
    #end
    spi_end()

# CMD33  BLK_END_ADD
def ERASE_END(e=[0x00,0x00,0x02,0x00]):
    CS(0)
    spi_ff()
    spi_cmd(cmd33,e[0],e[1],e[2],e[3],0x01)    
    spi_ff() 
    res=spi_res(1)
    if res != b'\x00':
       print('CMD33 ERROR',res)
       sys.exit()
    #end
    spi_end()

# CMD38  ERASE  Erases
def ERASE_GO():
    CS(0)
    spi_ff()
    spi_cmd(cmd38,0x00,0x00,0x00,0x00,0x01) 
    spi_ff() 
    res=spi_res(1)
    if res != b'\x00':
       print('CMD38 ERROR',res)
       sys.exit()
    #end
    spi_end()

## ERASE (2)
def SD_ERASE(es,ee):
    ERASE_START(es)
    ERASE_END(ee)
    ERASE_GO()
    print('ERASE')
    print('START ADD',['{:#04x}'.format(i) for i in es])
    print(' END  ADD',['{:#04x}'.format(i) for i in ee])
    #end
    spi_end()

# CMD17  READ_SINGLE_BLOCK(3)
def SD_Read(r=[0x00,0x00,0x00,0x00]):
    # READ_SINGLE_ BLOCK
    # CS select
    CS(0)
    time.sleep_ms(100)
    spi_ff()
    radr=spi_cmd(cmd17,r[0],r[1],r[2],r[3],0x01)

    # 0x00>>0xfeが返るまで受信
    cnt=0
    while True:
        cnt=cnt+1
        spi_ff()    # CMD>>0xFF out >>read response
        res=spi_res()
        if res== b'\x00':
            print('CMD17 Read')
            break
        if cnt>10:
            print('CMD17-NG',cnt)
            sys.exit()
    cnt=0
    while True:
        cnt=cnt+1
        spi_ff()
        res=spi_res()
        if res== b'\xfe':
            print('token 0xfe')
            break
        if cnt>10:
            print('token-NG',cnt)
            sys.exit()

    #read data 512bytes
    block_buf=spi.read(512)   
    #CRC16 2byte
    crc_buf=spi.read(2)
    spi_end()
    return block_buf,crc_buf

# CMD24  WRITE_BLOCK(4)
def SD_Write(w=[0x00,0x00,0x00,0x00]):
    # CS select
    CS(0)
    spi_ff()

    # WRITE_BLOCK
    spi_cmd(cmd24,w[0],w[1],w[2],w[3],0x01)

    # SDHC R1確認(標準SDはCMD24の応答を確認せずに書込む)
    if Card=='HC':
        #Card='HC' 
        cnt=0
        while True:
            cnt=cnt+1
            res=spi_res()
            if res== b'\x00':
                print('CMD24 Write')
                break
            if cnt>10:
                print('CMD24 NG',cnt)
                sys.exit()

    #スタートバイト 0xFE をまず送ってからWriteデータを送ります
    spi_ff()                #One byte gap
    spi.write(b'\xfe')      #Start Block Token //Send Data token
    #512バイトデータを送ります
    spi.write(block_buf)
    
    # data response check  xxx00101
    cnt=0
    while True:
        cnt=cnt+1
        res=spi_res()
        re=res[0] & 0x1f    # datares  XXX1???1 
        if re==0x05:
            print('data response=',res,bin(res[0]))
            print('')
            break
        if cnt>10:
            print('WRITE-NG',cnt)
            sys.exit()
    #end
    spi_end()
    time.sleep_ms(200)  ###

# main ---------------------動作確認
block_buf=bytearray(512)    # Write Data 512byte 同じ値
for i in range(512):
    block_buf[i]=0xff

Card='HC'          #Card='SD' Card='HC'

# write ---------           CMD24
SPI_MODE()         # SPI MODE ---##### 必要
w_adr=[0x00,0x00,0x02,0x00] #BLK_WRITE_ADD 
SD_Write(w_adr)
print('')

# read ---------------------CMD17
#SPI_MODE() NG
#32bit address SDcard bytes,SDHCcard_Block unit
r_adr=[0x00,0x00,0x02,0x00] #BLK_READ_ADD 
r_dat,r_crc=SD_Read(r_adr)
print('Data=',r_dat)
print('CRC16=',r_crc, hex(r_crc[0]),hex(r_crc[1]))
print('')

# erase --------------      CMD32-33-38
SPI_MODE()
# 32bit address SDcard bytes,SDHCcard_Block unit
s_adr=[0x00,0x00,0x00,0x00]   #BLK_END_ADD
e_adr=[0x00,0x00,0x04,0x00]   #BLK_START_ADD
SD_ERASE(s_adr,e_adr)

関数・部分説明

■SPI_MODE()
SDカードをSPIモードに設定します。
■SD_Read(r=[0x00,0x00,0x00,0x00])
シングルブロックデータとCRC16をリストで返します。ブロックは512byte単位です。
Read開始アドレスを[0x00,0x00,0x00,0x00]で与えます。
SDHCは512byteのブロックアドレスになります。SDCは512byteの倍数で指定します。
■SD_Write(w=[0x00,0x00,0x00,0x00])
シングルブロックでデータを書き込みます。ブロックは512byte単位です。
Write開始アドレスを[0x00,0x00,0x00,0x00]で与えます。
SDHCは512byteのブロックアドレスになります。SDCは512byteの倍数で指定します。
■SD_ERASE(es,ee)
es:消去開始アドレスを[0x00,0x00,0x00,0x00]で与えます。
es:消去終了アドレスを[0x00,0x00,0x00,0x00]で与えます。
SDHCは512byteのブロックアドレスになります。SDCは512byteの倍数で指定します。

実行結果

初期化後に16GBのSDHCで実行してみました。初期化は前回の記事を参照ください。
結果は以下のようになりました。※Thonnyのshellに表示されます。


>>> %Run -c $EDITOR_CONTENT
SPI_MODE
CMD24 Write                 #[0x00,0x00,0x02,0x00] 0xffを512bytes書き込み
data response= b'\xe5' 0b11100101

CMD17 Read                  #[0x00,0x00,0x02,0x00]読み出し
token 0xfe                  #読み出し
Data= b'\xff\xff\xff\xff\xff\xff\xff\ 
..省略..
\xff\xff\xff\xff'#
CRC16= b'\x7f\xa1' 0x7f 0xa1        #正常です。下CRC16例を参照。

SPI_MODE
ERASE
START ADD ['0x00', '0x00', '0x00', '0x00']
 END  ADD ['0x00', '0x00', '0x04', '0x00']
>>> 

CRC16の例がデータシートにあります。
512bytes すべて’0xff’のデータを読みだした際のCRC16の例です。値は0x7FA1です。
SDC CRC16例
※「Physical Layer Simplified Specification Version 2.00」より抜粋

ちなみにCard=’SD’にすれば、SDカード(初期化後)のテストが出来ます。
その際のRead開始などのアドレスは
[0x00,0x00,0x02,0x00]、[0x00,0x00,0x04,0x00]・・などの512の倍数で指定しないとエラーになります。

まとめ

Raspberry Pi PicoでSDHCカードのWrite、Read、Erase動作を確認しました。