PIC16F18326とSDカードで音楽再生（8bit, 24kHz）（FAT）

# 概要
PIC16F18326を使いました．クロックは32MHzです．

SDカードにあるPCMデータをMCCのFatFsで読み出して，PWM出力しました．
SDカードにファイルシステムを使ってアクセスしているので，音声データの書き込みがPCでちゃちゃっとできます（超便利）．

音質は8bit, 24kHz, モノラルです．PICの性能的には分解能はもっと上げられますが，10bitとか9bitとかキリが悪いので8bitにしました．

僕は今までレジスタこちょこちょ派で，MCCなんか使ったら面白くないじゃん！と思っていたのですが，短時間でこんなに複雑なことができたので大満足です．

# MCCの操作
MCCはClassicとMelodyがありますが，現時点（2024/08/21）ではMelodyにはSDカードのライブラリがない（？）のでClassicにします．

## System Module
まずはじめに，System Moduleのタブで，クロックを32MHzに，Clock Dividerを1にします．

## Timer 0
デューティ比を更新する割り込みを作るためにTimer 0を使います．
PWMのDuty値は，サンプリング周波数が24kHzなので41.66666666666[us]ごとに更新します．
説明がめんどくさいのでスクショはこんな感じです．
![スクリーンショット 2024-08-21 105829.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/a0472c17-149f-5e12-e9dd-97aef516e57f.png)

## Timer 2
PWM用のTimer 2です．
![スクリーンショット 2024-08-21 110825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/d4322ae1-3451-4175-929c-0fd36cd9606e.png)

## PWM
PWMです．キャリア周波数が125kHzなので，人間には聴こえないはずです．
![スクリーンショット 2024-08-21 110938.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/ec34f3a4-eced-64c2-340d-e4e775a5633a.png)

## SDCard
CDとWPはDisableしました．
![スクリーンショット 2024-08-21 111058.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/c38ad9b7-0366-5e7e-1b9d-e088cde72355.png)

## FatFS
いらない機能を削除すれば，プログラムメモリが節約できます（f_writeなどは今回必要ない）．
![スクリーンショット 2024-08-21 111307.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/69b842af-0f26-0c7d-ea1e-55afa6f9d162.png)
![スクリーンショット 2024-08-21 111348.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/12062afb-ed61-a902-d224-f54ccf8f72b0.png)
これ以外はDefaultのままです．

## PPS
SDO1，PWM5，CSは手動で設定しました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/8719352b-3c1f-9737-6ae0-e476beb435f4.png)

設定は終わったのでGenerateします．

## ユーザーコード
SDカード内の`music.raw`を読み込んで再生します．`music.raw`の作り方は後で説明します．
このプログラムは，常に`BUF_SIZE`分だけデータを先読みしながら，データ読み出しと音声再生を並列処理しています．
```C:main.c
#include "mcc_generated_files/mcc.h"
#define BUF_SIZE 256

/*
                         Main application
 */

uint8_t buf[2][BUF_SIZE];
uint8_t buf_id=0;
uint16_t ptr=0;
bool request=false;

void tmr0_isr() {
    if(ptr == 0) {
        request = true;    // メインルーチンにバッファへの読み込みをリクエスト
    }
    
    PWM5_LoadDutyValue(buf[buf_id][ptr++]);    // 読み込んだデータでデューティー比を更新
    
    if(ptr == BUF_SIZE) {    // バッファの末尾に達したら，バッファを切り替え
        ptr = 0;
        buf_id = buf_id+1 >= 2 ? 0 : buf_id+1;
    }
}

static FATFS drive;
static FIL file;

void main(void)
{
    // initialize the device
    SYSTEM_Initialize();

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();
    
    
    f_mount(&drive, "0:", 1);
    if(f_open(&file, "music.raw", FA_READ) != FR_OK) {
        f_mount(NULL, "0:", 0);
        while(1);
    }
    
    f_read(&file, buf[0], BUF_SIZE, NULL);
    
    TMR0_StartTimer();
    TMR0_SetInterruptHandler(tmr0_isr);
    TMR2_StartTimer();
    PWM5_LoadDutyValue(0);

    uint16_t actual_len, len;
    FRESULT res;
    
    while (1)
    {
        // Add your application code
        if(request) {    // 割り込みルーチンからのリクエストがあったら，つぎのバッファにデータを準備
            request = false;
            len = 0;
            
            do {
                res = f_read(&file, &buf[buf_id+1 >= 2 ? 0 : buf_id+1][len], BUF_SIZE-len, &actual_len);
                len += actual_len;
                
                if(actual_len == 0 || res != FR_OK) {
                    TMR0_StopTimer();
                    PWM5_LoadDutyValue(0);
                    f_close(&file);
                    f_mount(NULL, "0:", 0);
                    while(1);
                }
            } while(len != BUF_SIZE);
        }
    }
}
/**
 End of File
*/
```

メモリ使用量はこんな感じです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1570746/59e532e1-407c-a741-b8f5-5ad468b56ec2.png)


## 音声データの作り方
ffmpegで一発
```shell-session
ffmpeg -i "任意の音楽ファイル" -ac 1 -ar 24000 -acodec pcm_u8 -f u8 music.raw
```

## 再生
YouTube人生初投稿！
<iframe width="560" height="315" src="https://www.youtube.com/embed/AMAzHEtxy-w?si=IU6xKF9YfoVQMWKO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

:::note warn
SDカードの電源は**3.3V**です
:::
トランジスタ1個でアンプのようなものも作りました．

## おわりに
$ \mathrm{I^2C} $のEEPROM(24LC512)で音楽再生をやったこともありますが，容量的に再生できるのが数秒程度でした．今回は，16GBのSDカードを使ったので，約715828秒，11930分，199時間，8.29日の音楽を流せます！🎉🎉🎉