こんにちは。Yukiです。

今回は、モータードライバを作る際に、どういう電流が流れているのか気になったので、書いていこうと思います。

 

今回は、電源OFF→ON時の電流と、PWM波形を与えているときの電流どちらも見てみたいと思います。

 

 

  今回使う物品

 

今回は実験っぽい内容なので、ちゃんと使ったもの書いておきます。

  • オシロスコープ Hantek 6254BD
  • オシロスコープ ISDS205A
  • 電源 ALIENTEK DP100
  • PWM生成 LA1010
  • モーター① TG-85B-KU-36-KA
  • モーター② FA-130RA-2270
今回は電気的時定数の算出もしたいのですが、モーター①だと軸をロックできなかったので、モーター②で算出をしています。
 
モータ①の時は10Vで、モータ②の時は3Vにしています。

 

(なんでこんなモータあったんだろう?買った記憶がない…)

 

 

 

 

  電源OFF→ONの波形

 

 

OFF→ONなので、今回はこんな感じでやります。

 

 

これがモーター①の波形です。TG-85B-KU-36-KAは最大電流が3800mA(12V)くらいなので、内部抵抗Raは3.15Ωくらいだと思います。出力部に1Ω抵抗をつけているので、今回は実質Ra=4.15Ω 電源電圧は10Vなので、最大電流は2.4Aくらいになります。

 

1マス500mAで、大体4マス程度、まあ2Aくらい。誤差の範囲でしょう。(実際にはモータードライバ内の抵抗値とかもあると思うので)

 

 

次に、軸ロックした時の波形を見ます。

波形ロック時の波形はISDS205Aのデータロガー機能で保存します。

 

 

これはあくまでも軸をロックしているという前提です。(軸ロックさせないと逆起電力が発生してしまうため)

 

FA-130RA-2270も最大電流が2.2A(1.5V)らしいので、内部抵抗Raは682mΩくらいになります。

今回も外部に1Ωつけているので、Ra = 1.682Ω 電源電圧は3Vだったので、1.78Aくらいになります。が、実際には800mAくらいしか流れてないですね。(おそらくですが、電源電圧が低いので、モータードライバICのON抵抗も上がっているのだと思います)

 

電気的時定数を求めるには、0→63.2%になる時間を測定します。今回だと172.9usですね。

モータードライバの内部抵抗などを考慮すると、Ra = 3V / 800mA = 3.75Ω L=Rt = 3.75Ω × 172.9us = 648uH と求まります。

 

 

  PWM波の電流

 

この実験はすべてモーター①を使っています。

 

 

今回はこのような感じで電流測定します。

さすがに差動プローブは持っていないので、受動プローブ2本を使って、MATHモードで差分を出します。

 

オシロスコープは紫(CH3)が入力PWM波で、赤がMATH値です。各2ポイントの差分を出して電流値を出しています。

 

 

見えにくくてすみません。急に上がって、そこからはじわじわと上がる感じっぽいですね。

 

ちなみにこれは1kHzで駆動させていて、非連続電流モードになっています。

 

 

 

次には100kHz駆動です。(本当はモータードライバの限界超えてますが、まあいけました)

 

こんな感じで結構なだらかな波形になるんですね。驚きました。

 

 

  なぜこんな実験したのか

 

今度作るモータードライバ基板で、電流カット方法をどうしようかなと考えていたためです。なんとなく予想はできていましたが、RCフィルタを組み合わせれば作れそうだなと思いました。

 

  アメブロの仕様変更

 

前からPVの仕様が変更されたらしく、明らかに少なくなってしまい悲しいです。

おそらくですが、AI(LLMのRAG等)から参照されたPV数もカウントされないのでしょうか?そこら辺はわかりませんが、まあしょうがないですね。

 

こんにちはYukiです。

今回はUbuntuに完全移行できなかったことについて書いていこうかと思います。

中身はなんにも内容がないポエムみたいな記事です。

 

  完全移行できなかった理由

 

まあ簡単です。アプリケーションが対応していないことです。

 

もともとOSはなんでもよいと思ってる私ですが、機械学習やらそういうことをやろうとするとなると、やはりLinux(特にDebian系)がやはり有利です。これはやはりAI開発者がLinuxを使うことが多いからでしょう。(無料だし、オープンソースなのでいろいろ楽なのだと思われる)

 

しかし逆に、CADソフトであったりはWindowsのみの対応であることが多いです。

私は使ってませんが、FusionなんかはLinux非対応です。また、組み込み系のIDEもLinuxに対応していなかったりします。

 

MounRiver Studio (CH32V/CH32F系のIDE)だとLinuxにも対応しているみたいですが、LinkUtilityなどが使えなかったりするみたいです。(もしかしたら今は変わっているかもしれません)

 

まあこんなことで使えないのかなしいんですが、しょうがないですね。

 

一応Wineとかのエミュレータを使うという方法もありますが、できればやりたくないですね。(落ちた時萎えるし)

 

 

  Linuxの便利だなと思うところ

 

やはりbashでしょう。もともと改造したルーターとかで遊ぶ時もbashに近い構文を使えます。(lsとかpwdとかmkdirとか)

確かopenWrtとかだとashだったかな?

 

実際ワンライナーでなんでもやる職人がいたり、使えこなせれば最強に近づくかもしれません。(まだ私はその領域には達していませんが)

 

まあ使えないものはしょうがない。

 

最近気づいたのですが、lsとかであればpowershellならできるので、powershellを使いこなせるようになりたいですね。powershell芸人 なんちゃって。

 

  やはり使いたいソフトで選ぶ

 

LTSpiceとかもLinux非対応なので、もうしばらくはWindows+WSLまたはdual bootでUbuntuを使おうと思います。

 

仮想PCでもいいっちゃいいんですけど、正直あまり好きではないので。

 

ここまでUbuntu使いたい!とか書いておいてあれですが、ぶっちゃけOSというよりかは使いたいソフトウェアで考えたほうが良い気はします。機械学習系はLinux 組み込み系はWindows ゲームはWindows みたいな感じですかね。

 

 

こんにちは!Yukiです。

今回はCH32V203を使って、SD+FAT32のファイルの読み書きをしてみたいと思います。

 

 

 

 

  使ったもの

 

 

 

今回は信頼と安心のKIOXIAのMicroSDカードを使いました。(株価も上がってr(ry)

 

 

 

今回私は秋月のものを買いましたが、これであれば抵抗なども実装されているので便利かもしれません。(保証はしません)

 

 

  FATとは

 

File Allocation Tableの略で、ディスクのファイルの位置情報や日付などに関する情報を保存しておくための領域です。

 

これらを使ったファイルシステムを FATファイルシステムと呼びます。

しかし、ほとんどの人はFAT = FATファイルシステムと呼びます。

 

FATには様々ながあり、

  • FAT12
  • FAT16
  • FAT32
  • exFAT
に分類されます。
 
FAT12は最大ファイルサイズ32MiB 最大ボリュームサイズ32MiBという今では小さすぎてなにもできない子です。フロッピーディスク(1.44MiB)に多く使われていました。
 
次にFAT16です。こちらは最大ファイルサイズ 2GiB 最大ボリュームサイズ 2GiBとなっておいて、こちらもまた今ではあまり見かけられないものになりました。
ちなみにWindows11とかのフォーマットで出てくるFATはこいつらしいです。
 
次にFAT32です。最大ファイルサイズ 4GiB 最大ボリュームサイズ2TiBとなっています。いきなりボリュームサイズが大きくなりました。ちなみにWindows標準のフォーマットツールだと32GiBまでしかフォーマットできません。
 
最後にexFATです。こちらはFAT32以前のシステムと大きくかわっており、exFATは少し異種です。最大ファイルサイズは16EiBで、最大ボリュームサイズは512TiBとなっています。ちなみに、ボリュームサイズはあくまでも推奨で、理論上は64ZiBまで扱えます。(まさかのPとYぬかしてのZですね…)
 
次に、ファイル名についてです。exFATには関係ない話です。
FATにはファイル名に8.3形式を採用しています。(ファイル名8文字以下 拡張子3文字以下) なおファイル名はすべて大文字という規則になっています。
HELLO.TXT とか NOTEPAD.EXE みたいな感じですね。
この形式をFATではSFN (Short File Name)といいます。
 
しかしこれだと不便だと思ったのか、MAX255文字までファイル名を拡張できるように仕様変更されました。これがLFN (Long File Name)です。
 
なのでFAT32でフォーマットされたUSBメモリなどでも長い名前で使えます。
ちなみにLFNを使わないと日本語も使えないので注意です。
 
 

 

  FatFsについて

 

FATは規格が複雑で、自分で実装するにはハードルが高いです。

そこでFatFsというChaN様が製作されたライブラリを組み込んで使うことが多いです。

 

 

 

 

簡単に言うと、SDにアクセスする方法さえ自力で書ければ、あとはFatFsがファイル書き込みや読み込みなどを行ってくれるようになります。

 

もちろんSDを取り外しPCにさせば、マイコン側で書き込んだファイルを見たり編集することだってできます。

 

 

このシステムを作っていただいたChaN様にお礼申し上げます。本当に素晴らしいシステムです。

 

 

  早速実装

 

 

前回の記事でSDの書き込み・読み込みは実装できているので、これを使います。

 

FatFsは本当は複数ブロックまとめて書き込み・読み込みをするマルチブロック書き込み・読み込みに対応しているので、非効率ではあるのですが、今回はシングルブロック書き込み・読み込みだけで実装していきます。

(また個人的に困ったことがあれば実装したいと思います)

 

 

実装するといっても、下位レイヤーを軽く書いてあげるだけです。

 

diskio.cを新規作成し、そこに書いていきます。

 

必要な関数は

  1. disk_initalize
  2. disk_status
  3. disk_read
  4. disk_write
  5. disk_ioctl
  6. disk_fattime
です。
 
まずインクルード・宣言・グローバル変数等です。
 

#include "diskio.h"

#include "../CH32V203_Mylib/SPI_Mylib.h"

 

#define DEV_SD_DEBUG 0

#define DEV_SD 1

 

static DSTATUS Stat_DEV[5] = {STA_NOINIT};

 
DEV_SD_DEBUGやDEV_SDはファイルアクセス時に使うことになるドライブ番号のようなものです。(WindowsでいうC:\みたいなもの)
 
DEBUGはUARTにログが出てきます。それだけの違いです。
 
Stat_DEV[5] はそのデバイスをマウントしたかどうか記録しておくための変数です。
 
 
次に、disk_initialize関数です。

DSTATUS disk_initialize (BYTE pdrv){

 

    DSTATUS stat;

    int result;

 

    if(pdrv == DEV_SD_DEBUG){

        result = SD_init_debug();

 

        if(result == 0){

            Stat_DEV[DEV_SD_DEBUG] = RES_OK;

            stat = RES_OK;

        }else{

            stat = RES_ERROR;

        }

 

        return stat;

 

    }else if(pdrv == DEV_SD){

        result = SD_init();

 

        if(result == 0){

            Stat_DEV[DEV_SD] = RES_OK;

            stat = RES_OK;

        }else{

            stat = RES_ERROR;

        }

 

        return stat;

 

    }

 

    return STA_NOINIT;

}

 
ここはSDの初期化について書くところです。
(なんかinitializeみてたら、急にマテリアライズを思い出しましたw)
 
次に、disk_statusです。

DSTATUS disk_status (BYTE pdrv){

    if(pdrv == DEV_SD){

        if(Stat_DEV[DEV_SD] == STA_NOINIT){

            return STA_NOINIT;

        }

        return RES_OK;

    }else if(pdrv == DEV_SD_DEBUG){

        if(Stat_DEV[DEV_SD_DEBUG] == STA_NOINIT){

            return STA_NOINIT;

        }

        return RES_OK;

    }

    return RES_ERROR;

}

 
ここはドライブが初期化されているかどうか・メディアがセットされているか・メディアにライトプロテクト(書き込み禁止)がされていないかを返すところです。
 
今回はメディアセットとライトプロテクトに関しては無視しています。
 
ドライブ初期化はdisk_initializeが正常に動作した際に代入される値で見ています。
(本当は適当なCMDから帰ってくるR1レスポンスを見るべきなのかもしれません)
 
 
次に、disk_read関数です。

DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count){

    if(pdrv == DEV_SD){

        int res = RES_OK;

        for(int i = 0; i < count; i++){

            res = SD_Read(sector + i, buff + (i * 512));

        }

 

        if(res != 0){

            return RES_ERROR;

        }

 

        return res;

 

    }else if(pdrv == DEV_SD_DEBUG){

        int res = RES_OK;

        for(int i = 0; i < count; i++){

            res = SD_Read_debug(sector + i, buff + (i * 512));

        }

 

        if(res != 0){

            return RES_ERROR;

        }

 

        return res;

    }

 

    return RES_ERROR;

}

 
ややこしいのですが、sectorは512byteずつで進んでいき、buffは1byteずつですすんでいくのでこのような記述になります。後はcount関数繰り返すだけです。
 
 
次に、disk_write関数です。

DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count){

    if(pdrv == DEV_SD){

        int res = RES_OK;

 

        for(int i = 0; i < count; i++){

            res = SD_Write(sector + i, buff + (i * 512));

        }

 

        if(res != 0){

            return RES_ERROR;

        }

 

        return RES_OK;

 

    }   else if (pdrv == DEV_SD_DEBUG){

        int res = RES_OK;

 

        for(int i = 0; i < count; i++){

            res = SD_Write_debug(sector + i, buff + (i * 512));

        }

 

        if(res != 0){

            return RES_ERROR;

        }

 

        return RES_OK;

    }

 

    return RES_ERROR;

}

 
こちらもRead関数と同様の書き方です。
 
次に、disk_ioctl関数です。

DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff){

    switch(pdrv)

    {

        case DEV_SD:

           

            if(cmd == CTRL_SYNC){

                return RES_OK;

            }else if(cmd == GET_SECTOR_SIZE){

                *(WORD*)buff = 512;

                return RES_OK;

            }else if(cmd == GET_SECTOR_COUNT){

                *(WORD*)buff = SD_AllBlockCnt_debug();

                return RES_OK;

            }else if(cmd == GET_BLOCK_SIZE){

                *(WORD*)buff = 1;

                return RES_OK;

            }else{

                return RES_ERROR;

            }

 

            break;

        case DEV_SD_DEBUG:

 

            if(cmd == CTRL_SYNC){

                return RES_OK;

            }else if(cmd == GET_SECTOR_SIZE){

                *(WORD*)buff = 512;

                return RES_OK;

            }else if(cmd == GET_SECTOR_COUNT){

                *(WORD*)buff = SD_AllBlockCnt();

                return RES_OK;

            }else if(cmd == GET_BLOCK_SIZE){

                *(WORD*)buff = 1;

                return RES_OK;

            }else{

                return RES_ERROR;

            }

 

            break;

        default:

           

            return RES_ERROR;

 

            break;

    }

 

    return RES_ERROR;

}

 
こちらが少し面倒です。
cmd引数で来たコマンドに応じて返却しなくてはいけません。
 
まず、CTRL_SYNCです。
これはドライブの書き込み処理が終わっているか確かめるコマンドです。
今回実装しているSD_write関数は、書き込みが終わらないと終わらない仕様になっているので、RES_OKを返しています。
 
次に、GET_SECTOR_SIZEです。
これはドライブのセクタサイズを返すコマンドです。
SDカードはブロック単位が512byteですから、512を返しています。
 
次に、GET_SECTOR_COUNTです。
これはドライブ上の総セクタを返す必要があります。ということでドライブの総セクタ数を返す関数を作りました。ということで、*(WORD*)buff = SD_AllBlockCnt();だけです。
 
次に、GET_BLOCK_SIZEです。
これは削除ブロックのサイズを返します。不明であれば1でよいとリファレンスマニュアルにありましたので、1を返しています。
 
最後にCTRL_ERASE_SECTORです。
これはフラッシュの一部領域を削除する際に使うようです。
これはよくわからなかったので実装していません。(正直マイコン内で削除処理を行う行為はやめた方がいいと思います。別のデータも飛びそうです)
 
 
最後に get_fattime関数です。

// とりあえず 2026年1月1日 00:00:00 を返す

DWORD get_fattime (void) {

    return ((DWORD)(2026 - 1980) << 25) /* Year 2026 */

         | ((DWORD)1 << 21)             /* Month 1 */

         | ((DWORD)1 << 16)             /* Mday 1 */

         | ((DWORD)0 << 11)             /* Hour 0 */

         | ((DWORD)0 << 5)              /* Min 0 */

         | ((DWORD)0 >> 1);             /* Sec 0 */

}

 
CH32V203マイコンにはRTCモジュールがついていますが、特に書き込み日時・読み込み日時についてどうでもいいということであれば、これでOKです。
 
ちゃんとやりたい場合は、RTCモジュールを使用し、UNIXタイムを返してあげましょう。
 
 

 

  CH32V203での制限

 

ROMの容量が足りなかった関係で、LFN(Long File Name)に関しては使えません。(対応表がとんでもなく重いみたいです)

 

ということで、このマイコンでは8.3表記しか扱えないことに注意が必要です。

(HELLO.TXTとかMINE.EXEとか)

 

  GitHub

 

今回のプログラムは長くなったので、GitHubに上げました。

少しですがサンプルのコードもあります。ぜひご活用ください。(つかう人がいればですが)

 

 

 

何かあったらここにコメントするか、issueに登録してください。(使う人いない気もしますが)

 

 

 

  次回までにやってみたいこと

 

FAT32ができたので、MP3プレイヤーもどきとか作ってみたいですね。

(ただ、ID3対応前提になると思うので、そこだけ面倒ですが)

 

どうせならアルバムアートなんかも表示して…
とか思ってると結構難しいかも…
 

チャレンジしたいですね。

こんにちは。Yukiです。

今回はLinuxで新たに見つかった脆弱性 Copy-failについて書いていこうと思います。

 

ここでは本当に素人でもできちゃった!というところを出しているだけです。
詳しい説明だったり、対策に関しては別の記事をご覧ください。

 

  今回の脆弱性

 

CVE-2026-31431

 

本来、管理者権限を持っていないアカウントでも、取得できてしまうというバグです。

インターネット経由で攻撃できるものではないので、そこだけは安心です。

 

 

 

 

 

 

  何がやばいのか

 

例えば共用のサーバー等ではUbuntuやDebianで動いていることも多いと思います。当たり前ですが、そのようなサーバーで自分たちがアクセスする場合は、管理者権限を持たないアカウントのはずです。(レンタルサーバーとかがよい例かもしれません)

 

共用のサーバーでは勝手にaptコマンドを使ってソフトウェアのインストールができなかったりということです。(aptコマンドの実行にはroot権限が必要なので)

 

それができてしまいます。

 

root権限とられて、PC内のデータ全消しという可能性も考えられますね。

 

 

また、Copy Failのやばいところ2点目として、再起動してしまうと痕跡が残らない点です。そのため誰が悪用したのかわからないというのもリスクになります。

 

 

  実際にやってみた

 

なんとたった数行のpythonコードで実行できてしまいます。

セキュリティの観点からここには書きませんが、githubとかに上がっていますね。

ちなみに共用サーバー等で実行すると不正アクセス禁止法等になる可能性があります。絶対にやらないでください。

 

まずUbuntu 22.04です。

今回仮想PCを用意しました。

 

 

まずはsudoersではないアカウント(管理者権限を持たないアカウント)を作ります。

今回は aaa にしました。

 

aaaのアカウントに入りなおしました。

 

当たり前ですが、管理者権限を持っていないためsudo apt updateは弾かれます。

 

 

それではプログラムを実行してみます。

 

なんと管理者権限を持たないaaaアカウント で sudo apt updateが実行できてしまいました。

 

 

 

ちなみにWSL(Ubutnu22.04)でも同様に管理者権限を得ることができました。

(当たり前ですが、パスワード打ち込んでないですよ)

 

 

 

  対策

 

Ubuntuの場合、

sudo apt update && sudo apt upgrade を実行することで修正されました。

 

 

上のようにエラーになり、root権限にはなりませんでした。

 

 

 

対策に関してはこの辺の記事が参考になりそうです。

 

 

 

  今まで見つからなかったのがすごい

 

詳しいことは知りませんがAIが見つけたとかなんとか言ってましたね。

 

こんな誰でも簡単にrootに昇格できる事例珍しいのではないでしょうか?(普通はタイミングがどうこうとかそんな感じだと思うので)

 

GW入った直後にこれなので、サーバー管理者は大変だと思います。本当にお疲れ様です。

こんにちは。Yukiです。

今回はCH32V203でSDを使ってみたいと思います。

 

 

  目標

 

今回の目標として、SDをWriteとReadを自由にできるようになることです。

 

FATとかそういう難しいことに関しては実装しません。

(でかい512byte単位でしか書き込み・読み込み出来ないEEPROMみたいなのを想像すればいいとおもいます。)

 

今回はSDHCを対象とします。SDSC(2GB以下のSD)やSDXCに関しては対象としません。(SDXCは動作するとは思いますが、保証はできません)

 

 

  回路図

 

みにくいかもしれませんが、こんな感じの配線にしました。

 

SDは結構電力消費が大きいので、今回はWCH-LinkEから給電はせず、USBから供給するようにしました。また、CH32V203とSDは3.3V駆動であることから、LM2940-3.3で5V→3.3Vに降圧しています。

 

 

 

  前段階 - SPIとSD

 

まずSDカードについて簡単に知っておく必要があります。

実は昔、PICでSDを使ったことがあるので軽く知っているのですが、まずSDにはデータのアクセス手段が2つあります。SDIOとSPIです。SDIOは内部規格が公開されておらず、一切わかりません。そのためESP32とかについているSDIOと一緒に使うことになります。CH32V203にはSDIOはありませんから、この時点でSPI一択になります。

 

次にSPI通信についてです。

 

 

上の画像はCH32V203のReference Manualから持ってきたものです。

ここでポイントとなるのが、Shift Registerであるという点です。

シフトレジスタはクロックが入ると1つずれるようなレジスタで、今回の例だと、1クロック与えられるとMOSIが1bit分送信 MISOが1bit分受信するようなかたちです。

 

ということで、例えば16byte分受信したいなと思ったら、ダミーのデータ(0xFF)を16byte分送信することにより取得するような形です。

 

CH32V203はバッファ等はついていないので、送信したら必ず受信する処理が必要です。シフトレジスタは8bitですから、16byte受信したいと思ったら、0xFF(8bit)送信。その後受信されたか確認するレジスタを確認して変数に入れる、また0xFFを送信...を16回繰り返します。

 

次に、SPIの設定ですが、SPOL=0 SPHA=0 (いわゆるMODE0)でOKです。

 

 

  SDの初期化手順

 

1. SS端子をHIGHにした状態で74クロック以上送信します。

これは0xFFを送信すればいいです。

 

2. SSをLOWにする

 

3. CMD0を送信する

0x40 00 00 00 00 95 を送信します。

 

送信後、レスポンス1byte 0x01を待ちます。0x01が返ってくるまで0xFFを送信し続けます。

 

4. SSをHIGHにする

5. 0xFFを送信する

 

6.SSをLOWにする

7. CMD8を送信する

0x48 00 00 01 AA 87 を送信します。

 

送信後、レスポンス5byteを受信します。

0xFFを送信して、レスポンスに0xFF以外が返ってきたタイミングから5byte取得する形になります。

 

0x01 00 00 01 AA なら成功です。

 

8. SSをHIGHにする

9. 0xFFを送信する

 

10. SSをLOWにする

11. CMD55を送信する

0x77 00 00 00 00 65 を送信します。

 

送信後、何らかのレスポンス1byteが返ってくるまで待ちます。(0x00または0x01が返ってきます)

 

12. ACMD41を送信する

0x69 40 00 00 00 77 を送信します。

 

送信後、1byteのレスポンスを待ちます。 返ってくるまで0xFFを送信し続けてください。

 

13. SSをHIGHにする

14. 0xFFを送信する

 

15. ACMD41のレスポンスで条件分岐

ACMD41のレスポンスが0x01であれば、10. に戻ります。

0x00であれば、16. に進みます。

 

16. SSをLOWにする

17. CMD59を送信する

0x7B 00 00 00 00 91 を送信します。

 

送信後、1byteのレスポンスを待ちます。0x00であれば成功です。

 

18. SSをHIGHにする

19. 0xFFを送信する

 

 

 

  初期化時のCMDの説明

 

この欄は、各コマンドの役割などを解説します。

 

CMDxは 0x40をorすることにより生成されます。

例えばCMD8であれば、 8 | 0x40 = 0x48となります。

 

レスポンス(応答)はR1とR3とR7があります。

R1は1byte 

R3は1byte(R1レスポンス) + 32byte

R7は1byte(R1レスポンス)+4byte

で構成されています。詳しく知りたい場合は、別サイトで調べてみてください。(ちゃんとビットごとに意味があります)

 

 

CMD0:

ソフトウェアリセット 応答はR1

 

CMD8:

動作電圧確認コマンド 実SDHC/SDXCしか使わないのであれば、実行する必要はありません。

このコマンドはSDSC(2GB)は持っていないコマンドなので、ここで弾かれます。

応答はR7です。

 

CMD55:

ACMDを動かす前に送信するコマンドです。

今回はACMD41の前に動かしています。

応答はR1です。

 

 

ACMD41:

SD初期化開始のコマンドです。

応答はR1です。

(ちなみにですが、初期化シーケンスだとここが一番長いです)

 

 

CMD59:

SPIモードでCRCモードを有効・無効にするコマンドです。

応答はR1です。(実は初期状態からCRCは無効のカードが多いのでやらなくても大丈夫かもしれません)

 

 

 

  SDの読み込み手順

 

次にSDの読み込み手順について説明します。

SDHCからは、512byteを1ブロックとして扱う方式になりました。

よって、

0x0000000を指定すると0byte~511byte

0x0000001を指定すると512byte~1023byte

という風になっています。

 

また、SDには1ブロックずつ読み込むモードと、まとめて複数ブロック読み込むモードがありますが、今回は1ブロックずつ読み込むモードのみ実装します。

 

 

1. SSをLOWにします。

2. CMD17を送信します

例えば読み込みたいブロック番地が 0xvv xx yy zzだとしたら

0x51 vv xx yy zz FF

を送信します。(最後のやつは0xFFです)

 

送信後、レスポンスが返ってくるまで待ちます。0x00であればOKです。

 

3. スタートトークンを待ちます

スタートトークン 0xFE が返ってくるまで待ちます。

 

4. データを受信します。

スタートトークン直後からデータが来るので、512byte分受信します。

 

5. CRCを破棄します。

CRCは無効化されていますが、一応CRCが来ます。なので0xFF FFを送信して2byte流します。

 

6. SSをHIGHにします。

7. 0xFFを送信します

 

 

 

 

  SDの書き込み手順

 

書き込みも同様に、1ブロックずつ書き込むモードと、まとめて複数ブロック書き込むモードがありますが、今回は1ブロックずつ書き込むモードのみ実装します。

(ちなみにですが、SDの書き込みは低速なので、本当はまとめて書き込んだ方が高速です)

 

 

1. SSをLOWにします。

2. CMD24を送信します。

例えば書き込みたいブロック番地が 0xvv xx yy zzだとしたら

0x58 vv xx yy zz FF を送信します。

 

送信後、レスポンスが戻ってくるまで待ちます。0x00だったらOKです。

 

3. 0xFFを送信します。

4. 0xFE(スタートトークン)を送信します。

5. 512byte分のデータを送信します。

6. 0xFF FF と2byte送信します。(ダミーCRC)

7. レスポンスが来るまで待ちます

書き込み中を示す0x05が返ってくるまで待機します。

なお0x05以外が返ってきたらエラーです。

8. レスポンスが変化するまで待ちます

返ってくるレスポンスが0x05が0xFFになるまで待機します。

9. SSをHIGHにします

10. 0xFFを送信します。

 

 

 

 

  ソースコード - ライブラリ

 

今回は容量が大きくなったのでGoogle Driveでの配布です。

(今後FAT32に対応させたときにGitHubに上げようと思っています)

 

 

 

SPI_Mylib.h / SPI_mylib.c : SPI通信やSDなどに関することが書いてあります。

XXXXX_debug()関数はUARTを使うので、このソースコードはUART_mylib.h/UART_mylib.cが必須になります。

 

timer_mylib.h / timer_mylib.c : タイマー関連のライブラリです。今のところはdelayしか実装していません。

 

UART_Mylib.h / UART_Mylib.c UART1でPCにデバッグ文字列を送信するためのライブラリです。今回はこれも使ってください。

 

 

  ソースコード - main.c

 

前提として、回路図通りの配線をしていると仮定しています。

また冒頭にも書きましたが、このプログラムではSDSC(2GB以下)の製品に関しては読み込めません。

 

ここはmain.cに書き込みます。

 

ここは共通のコードです。

 

 

#include "ch32v20x.h"

#include <stdio.h>

#include <stdlib.h>

#include "SPI_Mylib.h"

#include "UART_Mylib.h"

 

#define STR_BUF 512

 

void Binary_512byte_print(const uint8_t data[]);

 

//HSI(8MHz)を使用

//PLL x18により

//SYSCLK 144MHz

//HCLK 144MHz

//APB1 144MHz

//APB2 144MHz

//ADC 72MHz

//(状況により変更あり)

void osc_144MHz_init(void) {

    //PLL x18に設定

    RCC->CFGR0 |= (1 << 21);

    RCC->CFGR0 |= (1 << 20);

    RCC->CFGR0 |= (1 << 19);

    RCC->CFGR0 |= (1 << 18);

 

    //PLL HSI->そのまま入力 (/2しない)

    EXTEN->EXTEN_CTR |= (1 << 4);

 

    //PLL有効 (input clock x 18)

    RCC->CTLR |= (1 << 24);

 

    //PLLがReadyになるまで待機

    while((RCC->CTLR & (1 << 25)) == 0);

 

    //システムクロックをPLL入力にセット

    RCC->CFGR0 |= (1 << 1);

}



 

int main(void){

    osc_144MHz_init();

    SysTick_init();

 

    RCC->APB2PCENR |= RCC_IOPAEN;

    RCC->APB2PCENR |= RCC_AFIOEN;

 

    SD_SS_HIGH();

 

    //PA9 UART1 TX

    //Alternate Function Push-pull

    GPIOA->CFGHR |= GPIO_CFGHR_MODE9;

    GPIOA->CFGHR |= GPIO_CFGHR_CNF9_1;

    GPIOA->CFGHR &= ~GPIO_CFGHR_CNF9_0;

 

    //PA10 UART1 RX

    //Floating Input

    GPIOA->CFGHR &= ~GPIO_CFGHR_MODE10;

    GPIOA->CFGHR &= ~GPIO_CFGHR_CNF10_1;

    GPIOA->CFGHR |= GPIO_CFGHR_CNF10_0;

 

    //PA4 SPI1 SS (SD)

    //General Push-pull Output

    GPIOA->CFGLR |= GPIO_CFGLR_MODE4;

    GPIOA->CFGLR &= ~GPIO_CFGLR_CNF4;


 

    //PA5 SPI1 CLK

    //Alternate Function Push-pull

    GPIOA->CFGLR |= GPIO_CFGLR_MODE5;

    GPIOA->CFGLR |= GPIO_CFGLR_CNF5_1;

    GPIOA->CFGLR &= ~GPIO_CFGLR_CNF5_0;

 

    //PA6 SPI1 MISO

    //Floating Input

    GPIOA->CFGLR &= ~GPIO_CFGLR_MODE6;

    GPIOA->CFGLR &= ~GPIO_CFGLR_CNF6_1;

    GPIOA->CFGLR |= GPIO_CFGLR_CNF6_0;

 

    //PA7 SPI1 MOSI

    //Alternate Function Push-pull

    GPIOA->CFGLR |= GPIO_CFGLR_MODE7;

    GPIOA->CFGLR |= GPIO_CFGLR_CNF7_1;

    GPIOA->CFGLR &= ~GPIO_CFGLR_CNF7_0;

 

    us_delay(100*1000); //100ms待機

 

    UART1_Setting(9600);

    SPI1_init();

 

    //////この下に書いていく///////////


 

    //////////////////////////////////

 

    while(1){

       

       

       

    }

}

 

void Binary_512byte_print(const uint8_t data[]){

    printf("    00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");

 

    for(int i = 0; i < 32;i++){

 

        printf("%02x :", i);

 

        for(int j = 0; j < 16; j++){

            printf("%02x ", data[16 * i + j]);

        }

        printf("\r\n");

 

    }

}

 

 

これから下に書くコードは、

    //////この下に書いていく///////////


 

    //////////////////////////////////

 

の中に入れてください。

 

 

初期化 + 読み込み

初期化と読み込みのセットです。

 

    uint8_t data[512];

 

    SPI1_LowSpeed();

 

    SD_init_debug();

 

    SPI1_HighSpeed();

 

    SD_Read_debug(0,data);

 

    Binary_512byte_print(data);

 

 

SDを差し込んで実行すると、ログはこんな感じです。

 

 

今回は0x00000000番地を読み込んだので、MBR領域を読み込んだことになります。

 

ちなみに、関数に XXXX_debug();とついてますが、debugを消せばログは出なくなります。(要は高速化します)

 

 

初期化 + 書き込み + 読み込み

この事項は書き換えるアドレスによってはデータ破損することがあります。

 

    uint8_t write_data[512];

    uint8_t read_data[512];

 

    SPI1_LowSpeed();

 

    SD_init_debug();

 

    SPI1_HighSpeed();

 

    for(int i = 0; i < 512; i++){

        write_data[i] = i % 0x100;

    }

 

    SD_Write_debug(0x123123, write_data);

 

    SD_Read_debug(0x123123,read_data);

 

    Binary_512byte_print(read_data);

 

 

書き換え&読み出しができていることがわかると思います。

 

データ容量確認

これは一部のソフトウェアが必要とするデータ容量を確認する方法です。

 

SD_AllBlockCnt_debug();

 

 

 

CMD9のデータ容量は1024ブロックを1と返すため、関数内部で1024をかけています。

1ブロック512byteですから、61021184 * 512 = 31.24*10^9 ということになります。

 

この値を31.24*10^9 / 1024/1024/1024すると29.09GiBになります。

 

ということで、約32GBということですね。

 

 

 

  読み込めてよかった

 

ちょっと前にやったときは全然うまくいかなかった記憶があったのですが、ちゃんとうまくいってよかったです。

 

やはりSSをHIGHにした後の0xFF送信が非常に大切なようです。

 

手持ちに2GB以下のSDがなかったので対応できませんでしたが、もし持っていたら今後やってみようかなと思います。(SDとSDHC/SDXCはコマンドがかなり違うので対応化は面倒ではあるのですが)

 

次はFAT32に対応させてみようと思います。(と言っているのですが、実はこの記事を書いている時点で実装できています)