RP2040に書き込めるUF2ファイルをC#で作る

RP2040マイコンは、RP2040に接続されたNORフラッシュメモリ上にファームウェア等を書込み、動作する仕組みになってます。これらのファームウェアや、LittleFSを使ったフラッシュメモリ内のファイルシステムは、USBフラッシュドライブ経由で簡単に書き込める仕組みであるUF2が採用されています。

しかし、RP2040データを置くとき、通常はArduino IDEやArduino IDEにPico LittleFS Data Uploadのプラグインを追加してデータを書き込みますが、データが変わらないのであればもっと簡単にしたいと思いますよね。

UF2ファイルを作るコードは、github上にSeeedStudio(JP)が公開しております。しかし、これをそのまんま使って、UF2ファイルを作っても、実は書き込めるUF2にはなりません。ちょっと細工が必要です。最初に答えを言いますと、以下のコードを使えば、RP2040のUF2を作ることができます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

//https://github.com/SeeedJP/uf2/tree/main/src/uf2

namespace uf2
{
    public class BinaryBlock
    {
        public uint Address { get; set; }
        public byte[] Data { get; set; }
    }

    public static class Uf2Converter
    {
        public static byte[] BytesToUf2(uint address, byte[] data)
        {
            var numBlocks = (uint)(data.Length / CHUNK_SIZE);
            if (data.Length % CHUNK_SIZE >= 1) numBlocks++;

            var uf2 = new List<byte>();
            var payload = new byte[476];
            for (uint blockNo = 0; blockNo < numBlocks; blockNo++)
            {
                var offset = CHUNK_SIZE * blockNo;
                var targetAddr = address + offset;
                var payloadSize = (uint)Math.Min(data.Length - offset, CHUNK_SIZE);
                Array.Copy(data, offset, payload, 0, payloadSize);
                for (uint i = payloadSize; i < payload.Length; i++) payload[i] = 0;

                uf2.AddRange(BitConverter.GetBytes(FIRST_MAGIC_NUMBER));
                uf2.AddRange(BitConverter.GetBytes(SECOND_MAGIC_NUMBER));
                uf2.AddRange(BitConverter.GetBytes(UF2_FLAG_FAMILY_ID_PRESENT));
                uf2.AddRange(BitConverter.GetBytes(targetAddr));
                uf2.AddRange(BitConverter.GetBytes(CHUNK_SIZE));    // payloadSize?
                uf2.AddRange(BitConverter.GetBytes(blockNo));
                uf2.AddRange(BitConverter.GetBytes(numBlocks));
                uf2.AddRange(BitConverter.GetBytes(FAMILY_ID_RP2040));               
                uf2.AddRange(payload);
                uf2.AddRange(BitConverter.GetBytes(FINAL_MAGIC_NUMBER));
            }

            return uf2.ToArray();
        }

        public static byte[] BytesToUf2(IEnumerable<BinaryBlock> blocks)
        {
            var uf2 = new List<byte>();

            foreach (var block in blocks)
            {
                uf2.AddRange(BytesToUf2(block.Address, block.Data));
            }

            return uf2.ToArray();
        }

        private const int CHUNK_SIZE = 256;

        private const uint FIRST_MAGIC_NUMBER = 0x0a324655;
        private const uint SECOND_MAGIC_NUMBER = 0x9e5d5157;
        private const uint FINAL_MAGIC_NUMBER = 0x0ab16f30;
        private const uint UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000;
        private const uint FAMILY_ID_RP2040 = 0xe48bff56;

        //UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
        //UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
        //UF2_MAGIC_END    = 0x0AB16F30 # Ditto

        //output size: 31457280, start address: 0x100ff000

    }
}

RP2040のユーザーマニュアルにも、UF2の仕様が書かれており、これに準拠した形で直してあげれば完成します。

ポイントなのは、チャンクサイズを256バイトにして、ファイルサイズの項に0xe48bff56を書くこと(通常は0埋めされている模様)と、flagには0x2000を入れるという点でしょうか。あとは、LittleFSを使っている場合には、どこから開始なのかは皆様バラバラだと思うので、そこの開始位置だけ気を付ける(Arduino IDEでファームウェアでサイズをよく確認する)ところだと思います。ウチは1MBがファームウェア、15MBにLittleFSでファイルシステムを構築してるので、ファイルシステムにファイルを置くUF2を作りたい場合は、開始アドレスは0x100FF000となります。

UF2ファイルを生成する前に、LittleFSにファイルを置く場合にはmklittlefsを使って、ファイルやフォルダを先にパックしておいて1ファイルにしてからUF2に変換することも忘れずに。おおよそUF2にすると2倍くらいになる感じです。UF2ではデータを476バイト区切りに変換するためです。

タイトルとURLをコピーしました