稼働中

Raspberry Pi Pico(d_24)SDカード モジュール 初期化

SD Card INIT (SPI)

Raspberry Pi PicoでSDカードモジュールを使ってみました。SPI通信で制御できます。
SDカード(Secure Digital Card)の初期化について記載します。
SPIのクロック周波数は100kHz~400kHzのようです。100kHzで動作を確認しました。
※micro:bitの記事e_22などからRaspberry Pi Picoで使えるように追記、修正等をしました。

外観

SDカード モジュールの外観写真です。(以後はSDカード、SDC、SDHCと記載します)
SDカードモジュール
テストに使用したSDカードです。SD 1GBとSDHC 16GBを使いました。
使用したSDカード2種類

接続例

SDカードの電源電圧は4.5~5.5Vなので、5Vの別電源を使用しました。
レベル変換回路を搭載しているので、SPI通信はRaspberry Pi Picoと直接接続できます。
SPI通信にはSPI1のRX(GP12)、CSn(GP13)、SCK(GP14)、RX(GP15)を使いました。それぞれMISO、CS、SCK、MISOと接続しました。
SDカードモジュール接続

SDカードの説明

データシートから抜粋してSDカード(Secure Digital Card)の一部を説明します。
※素人レベルなので詳しくは分かりません。間違っているかも知れません。詳細はデータシートなどを参照ください。
使用できるSDカードは、SDC(Standard Capacity SD Memory Card ~2GB)とSDHC(High Capacity SD Memory Card 2~32GB?)があります。カードの種類で初期化やアドレス指定などが変わります。
■SPIモード
SDカードの制御をSPIモードにするには、CS(チップセレクトピン)を’1’にした状態で74クロック以上を送り、CSを’0’にしてデバイスを指定します。
■SDカードの初期化
SDHCの場合はCMD0>CMD8>CMD55>ADCMD41で行います。SDCの場合にはCMD8でエラーになります。
この記事ではSDCの初期化はCMD0>CMD1で行います。


初期化で使うコマンド
コマンド        Resp   概略
CMD0    0x40   R1    #GO_IDLE_STATE  CRC必要   リセット
CMD8    0x48   R7    #SEND_IF_COND   CRC必要   カードの世代、動作電圧確認
CMD55   0x77   R1    #APP_CMD        次コマンドはAPP_CMD
ACMD41  0x69   R1    #SD_APP_OP_COND 初期化 SDHCのサポート
CMD1    0x41   R1    #SEND_OP_COND   初期化

■コマンド
Command Formatは下表のようになっています。
Comand(1byte) + data(4bytes) + |CRC|0x01|(1byte)の6バイトを送ります。
SDカード Command Format
※「Physical Layer Simplified Specification Version 2.00」より抜粋

Comand(1byte)は‘0’+’1’+’comand index xxxxxx'(0x40|comand index)です。
例えばCMD55なら0x40|55


>>> hex(0x40|55)
'0x77'

になります。
コマンドがcmd55、データが0x00 0x00 0x01 0xAA,「CRC|0x01」が0x87の場合、
0x77,0x00,0x00,0x01,0xAA,0x87を送ることになります。
下方のスクリプトでは、
spi_cmd(0x77,0x00,0x00,0x01,0xAA,0x87)
で送信できるように関数化しています。


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

■レスポンス
コマンドを送信すると状態が返ります。
大半はR1形式1バイトが戻ります。正常な場合は0x01か0x00です。
R1フォーマット
※「Physical Layer Simplified Specification Version 2.00」より抜粋
CMD8のレスポンスは下のR7形式の5バイトで返ります。エラーの確認は上位部のR1で判別します。
R7フォーマット
※「Physical Layer Simplified Specification Version 2.00」より抜粋
■CRC
SPIモードではCRC値は適当でよい場合が大半らしいです。詳しくは分かりません。
CRC値が必要なコマンドはCMD0、CMD8らしいです。値は以下になります。
CMD0 CRC|0x01 = 0x95
CMD8 CRC|0x01 = 0x87
ちなみに記事(d_21)HTU21D 温湿度センサーで作成したCRC計算のスクリプトを修正した「SD_CRC_07b.py」で計算してみました。
コマンド送信時のCRC7の多項式は以下です。
SDカード CRC7


SD_CRC_07b.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# polynomial  X7+X3++1 ---
poly=[7,3,0]
div=2**poly[0]+2**poly[1]+2**poly[2]
zisu=poly[0] #次数
#message data
msg_dat =0x4000000000    #0x4000000000(CMD0) ,0x48000001aa(CMD8)
msg_len=len(bin(msg_dat))-2
msg =msg_dat << zisu
div = div<<(msg_len-1)
for i in range((msg_len+zisu-1), (zisu-1), -1):
    if msg & (1<>1
print('result=',msg, hex(msg))

CMD0は (0x40|comand index 0),00,00,00,00 なので
msg_dat(message) =0x4000000000で計算します。
結果は
result= 74(0x4a)になります。
CRC|0x01を計算すると


>>> hex(74<<1 |1)
'0x95'

になります。
したがって、CMD0は
0x40,0x00 0x00 0x00 0x00,0x95
を送ることになります。

CMD8の場合は下表でチェックパターンを推奨の1010_1010にすると
0b_0100_1000_0000_0000_0000_0000_0000_0001_1010_1010(‘0x48000001aa’)でCRC7を計算します。結果は
result= 67 (0x43)になります。
CRC|0x01を計算すると


>>> hex(67<<1 |1)
'0x87'

になります。
したがって、CMD8は
0x48,0x00 0x00 0x00 0x00,0x87
を送ることになります。
CMD8フォーマット
SDC checkpattern
※「Physical Layer Simplified Specification Version 2.00」より抜粋

CMD58
CDM58 READ_OCR(Operation conditions register)
CMD58はOCR(動作条件レジスタ)を読み取ります。レスポンスは5バイトです。下のR3フォーマットになります。
R3フォーマット
※Physical Layer Simplified Specification Version 2.00から引用

OCRレジスタのビット15~31の内容は以下になります。
OCR
※Physical Layer Simplified Specification Version 2.00から引用
ビット15~23はサポートされている電圧です。2.7~3.6Vならxff\x80\にあたります。
ビット31はカードが電源投入ルーチン(初期化)を終了なら’1’になります。
ビット30はカード容量を示し、初期化後のみ有効でありSDHCやSSCXCなら’1’になります。
電源投入後の値はSDならb’\x01\x80\xff\x80\x00’、SDHCはb’\x00\xc0\xff\x80\x00′

スクリプト

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


pico_sd_test_05b_01.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from machine import SPI, Pin
import sys
import time

# CMD INIT
cmd0=0x40     #GO_IDLE_STATE 
cmd1=0x41     #SEND_OP_CND 
cmd8=0x48     #SEND_IF_COND
cmd16=0x50    #SET_BLOCKLEN
cmd55=0x77    #APP_CMD 
cmd58=0x7A    #READ_OCR
acmd41=0x69   #SD_APP_OP_COND 

#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_pin 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)
    return response
# Dummy Clock
def spi_ff():
    spi.write(b'\xff')
# spi end 
def spi_end():
    spi_ff()
    CS(1)
    spi_ff()
# 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()

## SD-SDHC INIT (1)
# CMD0      GO_IDLE_STATE
def CMD_00(): 
    CS(0)
    spi_ff()
    spi_cmd(cmd0,0x00,0x00,0x00,0x00,0x95) 
    while True:
        spi_ff() 
        res=spi_res()
        if res== b'\x01':
            print('CMD0 pass')
            break

# CMD08     SEND_IF_COND
def CMD_08():
    #print('CMD8')
    spi_ff()
    spi_cmd(cmd8,0x00,0x00,0x01,0xAA,0x87)
    spi_ff() 
    # R7(5bytes)=R1+4bytes
    res=spi_res(5)
    #print('R7-5bytes >>',res)
    if (res[0]== 0x01) or (res[0]== 0x00):
        print('CMD8 pass')
        return 1        #Card='HC'
    else:
        print('CMD8 ERROR >>> SD ')
        return 0        #Card='SD'

# CMD55     APP_CMD
def CMD_55():
    #print('CMD55')
    spi_ff()
    spi_cmd(cmd55,0x00,0x00,0x00,0x00,0x01)
    spi_ff()
    res=spi_res()

    if res == b'\x01':
        print('CMD55 Pass')
    else:
        print('CMD55 ERROR EXIT')
        sys.exit()
        
# ACMD4     SD_SEND_OP_COND
def CMD_ACMD41():
    #print('ACMD41')
    spi_ff()
    spi_cmd(acmd41,0x40,0x00,0x00,0x00,0x01)
    spi_ff()
    res=spi_res(1)
    if res == b'\x01':
        print('ACMD41 Retry')
    if res == b'\x00':
        print('ACMD41 Pass')
        return 0

# CMD01     SEND_OP_CND
def CMD_01():
    while True:
        spi_ff()
        spi_cmd(cmd1,0x00,0x00,0x00,0x00,0x01) # CMD1
        spi_ff()
        res=spi_res()
        if res== b'\x00':
            print('CMD1 Pass')
            break

# CMD58     READ_OCR Operation conditions register
def CMD_58():
    #print('CMD58')
    spi_ff()
    spi_cmd(cmd58,0x00,0x00,0x00,0x00,0x01)
    spi_ff() 
    # R3 5bytes (R1+OCR_4bytes)
    res=spi_res(5)
    if (res[0]== 0x01) or (res[0]== 0x00):
        print('CMD58')
        print('R1(1byte)+OCR(4bytes)=',res)
    else:
        print('CMD58 ERROR EXIT')
        sys.exit()

## SDC SDHC Init CMD0>CMD8>CMD55>ACMD41, CMD0>CMD1
def SD_Init():
    global Card
    SPI_MODE()
    CMD_00()
    SEND_IF_COND=CMD_08()

    # CMD8 NG   SD<=2GB Initialize
    if SEND_IF_COND == 0:
            Card='SD'
            CMD_00()
            CMD_01()
            print('SD-INIT')

    # CMD8 Pass SDHC>2GB Initialize
    elif SEND_IF_COND == 1:
        Card='HC'
        while True:
            CMD_55()
            if CMD_ACMD41()==0:
                #CMD_58()
                print('SDHC-INIT')
                break
    else:
        print('CARD ERROR')
        sys.exit()

    spi_end()

## SD SDHC card init SPI-MODE
SD_Init()           # SDカード初期化

CS(0)
CMD_58()            # OCR読み出し

関数 部分説明

■SD_Init()
SDC、SDHCを初期化します。
■CMD_58()
OCR(Operation conditions register)の値を返します。

実行結果

16GBのSDHCカードの初期化を実行しました。
結果は以下のようになりました。初期化出来たようです。※Thonnyのshellに表示されます。


>>> %Run -c $EDITOR_CONTENT
SPI_MODE
CMD0 pass
CMD8 pass
CMD55 Pass
ACMD41 Retry        # 再確認
CMD55 Pass
ACMD41 Pass
SDHC-INIT
CMD58
R1(1byte)+OCR(4bytes)= b'\x00\xc0\xff\x80\x00'# SDHC,2.7~3.6V

次に、1GBのSDカードを実行してみました。初期化出来たようです。


>>> %Run -c $EDITOR_CONTENT
SPI_MODE
CMD0 pass
CMD8 ERROR >>> SD       #CMD8エラーのためCMD1初期化へ
CMD0 pass
CMD1 Pass
SD-INIT
CMD58
R1(1byte)+OCR(4bytes)= b'\x00\x80\xff\x80\x00' # SD,2.7~3.6V
>>> 

まとめ

Raspberry Pi PicoでSDHC、SDカードの初期化ができました。