こんにちは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に対応させてみようと思います。(と言っているのですが、実はこの記事を書いている時点で実装できています)

こんにちは。Yukiです。

今回は、PSpice用のモデルをLTspiceに取り込んだので、備忘録として記しておこうかと思います。

 

 

  発端

 

まず新しく使うICなので、シミュレーションしたいなと思ったのが発端です。(いきなり基板発注はお金の無駄になりそうで…)

 

今回は使いたいICはINA181という電流センス用アンプです。

 

最初は、PSpice for TI使えば簡単にできるやろと思っていたのですが、操作が意味不明&なぜか取り込み方がわからない。(そもそも情報が少なすぎます)

 

そもそもなぜPSpice for TIなのにTI社のICが初期状態で入っていないのか…

 

制限も多く、結局LTspice使おうとなったわけです。

 

しかしTI社などの多くの企業はPSpice用モデルしか公開していないので、それをLTspiceで使えるようにしようということですね。

 

 

 

  お断り

 

私は成功しましたが、この方法であっているのかは知りません。

あと、PSpiceでしか使えないように暗号化されている例もあるらしく、その場合は対処ができません。(諦めてPSpice使ってください)

 

今回使うLTspiceのバージョンは、24.0.12です。

OSはWindows 11 25H2です。

 

 

 

 

  前提知識

 

LTspiceは、見た目とモデルで分かれています。

 

まず見た目ですが、これは皆さんがよく目にみる回路図のあれです。

こんなやつですね。これはシンボルデータと呼ばれます。

LTspiceの拡張子は.symです。

 

次に中身です。これは内部構造がどんなふうになっているかが記載されているファイルで、テキストファイルになっています。これはライブラリと呼ばれることが多いみたいです。

拡張子は.libとかです。(他にもいろんな拡張子があるみたいです)

 

基本的に、サイトからDLすると、libファイルはついてくるので、こちらは作成不要です。symは既存のモデルから持ってくるか、自作するか選べます。

が、結局自作したほうが早いと思いますね。(オペアンプ程度であればそんなに大変ではないです)

 

  LTspiceの設定を変える

 

まず、LTspiceに、ここにモデルがあるよということを教えてあげるために、ディレクトリを作り、その後設定を変更します。

 

今回は私は、Cドライブ直下にLTspice_Mylibというディレクトリを作成しました。

 

今後はC:\LTspice_Mylibというフォルダにモデルを入れていくことになります。

 

上のToolsまたはSimulateからSettingを開きます。

(なかったら、別のところも探してみてください)

 

Search Pathsタブを開き、Symbol Search PathとLibrary Search Pathにそれぞれパスを入れます。

 

パスとかの概念が面倒なので同じディレクトリ指定で大丈夫です。

 

終わったら、LTspiceを一度再起動してください。

 

 

 

  PSpiceモデルをDL

 

今回はLM358のモデルをダウンロードしたいと思います。

 

 

いろいろありますが、一番上の PSpice Model というのを選びます。

(ちなみに、TINA-TIというのは、TI社が昔開発していた回路シミュレータです。今は使われていないらしいです)

 

 

ダウンロードして、ZIPファイルを解凍すると色々出てきますが、使うのはlm393.libのみです。

 

 

次に、lm393.libをメモ帳などで開きます。

.SUBCKTという項目を探します。

この.SUBCKTに書いてある内容で、ライブラリとシンボルを連動させるため、後で使います。

 

ちなみに、.SUBCKTは複数あることがあります。(というか大体複数あります)

今回だと、LM2903BはLM393の温度動作範囲を広げたものなので、能力自体は同じです。なので、LM2903Bで大丈夫ということですね。(ほかの.SUBCKTは、CMRRとかおかしいことが書いてあるので、なんとなくですが違うとわかると思います)

 

というか、おそらくですがファイル名が違うだけで、実際にはLM2903Bのもでるなんでしょうこれ。(能力同じだからOKってことなんでしょうけど)

 

 

このlibファイルは、先ほどパス設定したディレクトリにコピーしておきます。

 

 

 

  シンボルを作る

 

次に、シンボルを作っていきます。

 

 

File→New Symbol をクリックします。

 

New Symbolが出てこない場合ですが、スケッチ状態(回路図を書くモード)だと出てこないので、スケッチをすべて閉じるか、LTSpiceを再起動して下さい。

 

 

ここからはお絵描きです。

 

Drawにあるやつで絵を描けます。

 

 

右クリックすると、端子(ピン)を追加できます。

 

ここからが注意なのですが、ここで先ほど調べた.SUBSKTが出てきます。

 

今回だと、

.SUBSKT LM2903B IN+ IN- Vcc GND OUT となっています。

このLM2903Bは、まだ使いません。(後ほど紐づけで使います)

 

IN+ とか IN- とか後に続くやつはピンの名前を表しています。

 

ここからがややこしいのですが、このピンの通りに名前を付けてください。

あと、左から番号が1, 2, 3とついています。

今回だと、IN+が1 IN-が2 Vccが3ですね。

 

IN+とかIN-とかをLabelに。Netlist Orderは先ほどの順番通りにつけます。

 

Pin Labelはピンに文字をつけるかのやつです。今回はLeftを選択。

 

 

完成するとこんな感じです。(中心じゃないですけど、まあ自分しか使わないのでよし!)

今回は忘れていましたが、右上とかに部品名をテキストで入れといた方がよいと思います。(ないと後で見返したときぱっと見わからないので)

 

次に、このシンボルとライブラリを紐づかせます。

Edit → Attributes → Edit Attributesを開きます。 (またはCtrl + A)

 

こういうのが出てくるので、

PrefixにはX(エックス)をいれます。

Valueは、.SUBSKTで出てきた文字列を入れます。 今回だとLM2903Bです。

ModeFileは、ライブラリのファイル名を入れます。今回は、同じディレクトリに配置するので、ファイル名だけで大丈夫です。

 

 

完成したら、Ctrl+Sで保存します。

 

 

最初に通したパスの場所に保存します。

 

 

今回だとこんな感じですね

 

 

  動作確認

 

次に動作確認をします。

 

 

Top Directoryが増えていると思います。ここに先ほど作ったシンボルがあると思うので、それを選択します。

 

 

(でかいな…)

 

 

 

LM393はコンパレータICなので、今回は正弦波を入れてテストしてみました。

ちゃんと動いてそうですね。

 

 

 

同様にINA181もやってみましたが、ちゃんと動作しました。よかったよかった。

 

 

 

  やっぱりLTSpice

 

ここまで使いやすくて制限がないシミュレータソフトは珍しいと思います。

 

まあ元となっているSPICEは無料で使える(というかみんなそれを使っている)ので、頑張ってテキストで書いてそれ使えば無料ですけど。そんなことするならフリーで使えるLTspice使いますよね。

 

本当にアナログデバイセズ様 様様です。