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バイト区切りに変換するためです。