きっかけ
社会人6年目になった自分
「電子ペーパーをカレンダーとして活用しているけど、他にも色々接続しているラズパイに接続しているので取り回しが面倒だな...」
※色々接続というのは過去投稿したRaspberry Pi4でネットワークブートする等
「電子ペーパーのメーカーがArduinoで動かすためのライブラリを公開しているので、それを使ってラズパイと電子ペーパーを分離して遠隔で操作する仕組みを作ってみるか」
構想
電子ペーパーのメーカーがArduinoで動かすためのライブラリを公開しているが、ラズパイより高度なことをさせるのは難しそう。(アルファベットはともかく、日本語のフォントをArduinoで電子ペーパーに表示させるのは実質ほぼ無理なのでは? 等々...)
そこで、電子ペーパーに表示させるためのデータはラズパイ側で作成し、そのデータを離れた場所にあるArduinoに送りつけて電子ペーパーに書き込ませるというやり方なら実現できそうな気がしてきた。
用意したもの
上記構想を実現するために用意したものは以下の通り:
- Arduino R4 WiFi (興味本位で以前買ったもの)
- Arduino用 SDカードシールド メーカーサイト(受信した描画データを一時保存するためのもの)
※色々検証しながら実装していたが、ArduinoのRAMの領域が少なすぎる(受信した描画データを保持しておくことが厳しい)ので追加で購入 - ラズパイ (以前まで電子ペーパーを接続していた)
- 電子ペーパー
以下の電子ペーパーを使った
実装
ラズパイ側のコード実装
過去に実装したラズパイのコードを一部変更/追加する
後でもう少し綺麗にしたいが...
mport socket
import sys
import os
import cv2
import numpy as np
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
sys.path.append(libdir)
import logging
from waveshare_epd import epd7in5bc_V2
import time
from PIL import Image,ImageDraw,ImageFont,ImageFilter
import RPi.GPIO as GPIO
import datetime
import calendar
import jpholiday
logging.basicConfig(level=logging.DEBUG)
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.OUT)
GPIO.output(23, GPIO.HIGH)
dt_now = datetime.datetime.now()
day = dt_now.day
#描画データを送信する関数
def writeToArduino(ImageData:bytes, conn ,flag):
data_size = sys.getsizeof(ImageData)
kdata_size = round(data_size / 1024, 2)
print(kdata_size)
#引数のflagが0の場合は最初に0x00(黒の描画データのフラグ)を送信
#flagが1の場合は最初に0xff(赤の描画データのフラグ)を送信
flagData = bytes([0])
if flag == 0:
flagData = bytes([0])
else:
flagData = bytes([0xff])
#print(flagData)
conn.send(flagData)
#描画データを送信
conn.sendall(ImageData)
print("File sent successfully!")
def write_cal():
try:
epd = epd7in5bc_V2.EPD()
#--描画データ作成--#
blackImage = Image.new("RGB",(800,480),"white")
redImage = Image.new("RGB",(800,480),"white")
draw = ImageDraw.Draw(blackImage)
draw2 = ImageDraw.Draw(redImage)
draw.rectangle((0, 0, 799, 479), fill=(255, 255,255), outline=(0, 0, 0), width=3)
draw.rectangle((0, 0, 799, 60), fill=(0, 0, 0), outline=(0, 0, 0))
draw.line((0, 60, 799, 60), fill=(0, 0, 0), width=2)
draw.line((0, 40, 799, 40), fill=(255, 255, 255), width=2)
h = 70
l = 800 / 7
for i in [1,2,3,4,5,6]:
draw.line((0, 60 + i * h, 799, 60 + i * h), fill=(0, 0, 0), width=2)
draw.line((i * l, 60, i * l, 799), fill=(0, 0, 0), width=2)
draw.line((i * l, 40, i * l, 60), fill=(255, 255, 255), width=2)
cal_year_font_path = ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc',28)
cal_month_font_path = ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc',28)
cal_wamei_font_path = ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc',28)
cal_week_font_path = ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc',18)
cal_year = str(dt_now.year) + "年"
cal_month = str(dt_now.month) + "月"
wamei = {1:"睦月",2:"如月",3:"弥生",4:"卯月",5:"皐月",6:"水無月",7:"文月",8:"葉月",9:"長月",10:"神無月",11:"霜月",12:"師走"}
if dt_now.month in wamei:
cal_wamei = "ー " + wamei[dt_now.month] + " ー"
else:
cal_wamei = ""
week = {0:"Sun.",1:"Mon.",2:"Tue.",3:"Wed.",4:"Thu.",5:"Fri.",6:"Sat."}
n = 0
x = 20
draw.text((10, 0), cal_year , fill=(255, 255, 255), font=cal_year_font_path)
draw.text((395, 0), cal_month , fill=(255, 255, 255), font=cal_month_font_path)
draw.text((600, 0), cal_wamei , fill=(255, 255, 255), font=cal_wamei_font_path)
while n in range(len(week)):
cal_week = week[n]
draw.text((x, 38), cal_week , fill=(255, 255, 255), font=cal_week_font_path)
x = x + 800 / 7
n = n + 1
font_path = ImageFont.truetype('/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc',26)
week_set = {0:"1",1:"2",2:"3",3:"4",4:"5",5:"6",6:"0"}
first_week = int(week_set[first_day.weekday()])
frag = 0
for j in range(1, calendar.monthrange(dt_now.year, dt_now.month)[1]+1):
text5 = str(j)
day_x = int(week_set[datetime.datetime(dt_now.year, dt_now.month, j).weekday()])
if j > 1 and day_x == 0:
frag = frag + 1
if day_x == 0 or day_x == 6 or jpholiday.is_holiday(datetime.date(dt_now.year, dt_now.month, j)) == True:
draw2.rectangle(( 1 + day_x * 800 / 7, 61 + frag * 70, (day_x + 1)* 800 /7 , 60 + (frag + 1)* 70), fill=(180, 180, 180), outline=(0, 0, 0))
if j == dt_now.day:
draw.chord((day_x * 800 / 7, 60 + frag * 70, (day_x + 1)* 800 /7- 75, 60 + (frag + 1)* 70 - 32), 0, 360, fill=(0, 0,0))
draw.text((5 + day_x * 800 / 7, 60 + frag * 70), text5 , fill=(255, 255, 255), font=font_path)
else:
draw.text((5 + day_x * 800 / 7, 60 + frag * 70), text5 , fill=(0, 0, 0), font=font_path)
draw.rectangle((0, 0, 799, 479), fill=None, outline=(0, 0, 0), width=3)
blackImage.save("/home/pi/e-Paper/RaspberryPi&JetsonNano/python/pic/blackImage.png")
redImage.save("/home/pi/e-Paper/RaspberryPi&JetsonNano/python/pic/redImage.png")
##--ここまで描画データ作成--##
#print(bytes(epd.getbuffer(blackImage)))
#ホストのアドレスとポートを定義
HOST = '0.0.0.0' # Listen on all available interfaces
PORT = 8080
#黒の描画データを送信するためのソケットを生成
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen(1)
print(f"Listening for connections on {HOST}:{PORT}...")
conn, addr = server_socket.accept()
print(f"Connected by {addr}")
#黒の描画データを送信
writeToArduino(bytes(epd.getbuffer(blackImage)), conn, 0)
#ソケットを閉じる
conn.close()
server_socket.close()
time.sleep(10)
#赤の描画データはなぜか一度色相を反転する処理が必要
redImage = cv2.imread('/home/pi/e-Paper/RaspberryPi&JetsonNano/python/pic/redImage.png')
negativeRedImage = 255 - redImage
cv2.imwrite('/home/pi/e-Paper/RaspberryPi&JetsonNano/python/pic/redImage.png', negativeRedImage)
negativeRedImage = Image.open('/home/pi/e-Paper/RaspberryPi&JetsonNano/python/pic/redImage.png', 'r' )
#print(bytes(epd.getbuffer(negativeRedImage)))
#赤の描画データを送信するためのソケットを生成
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen(1)
conn, addr = server_socket.accept()
#赤の描画データを送信
writeToArduino(bytes(epd.getbuffer(negativeRedImage)), conn, 1)
#ソケットを閉じる
conn.close()
server_socket.close()
except IOError as e:
logging.info(e)
except KeyboardInterrupt:
logging.info("ctrl + c:")
epd7in5bc.epdconfig.module_exit()
exit()
dt_now = datetime.datetime.now()
first_day = datetime.datetime(dt_now.year, dt_now.month, 1)
write_cal()
Arduino周辺の接続
-
ArduinoとSDカードシールドを接続する
-
以下のようにArduinoと電子ペーパーを接続する
Arduino pin assignment | e-Paper pin assignment |
---|---|
3.3V | VCC (gray) |
GND | GND (brown) |
D11 (MOSI) | DIN (blue) |
D13 | CLK (yellow) |
D10 | CS (orange) |
D9 | DC (green) |
D8 | RST (white) |
D7 | BUSY (purple) |
※電子ペーパーのジャンパ線とArduino側のソケットはメス同士なので両端がオスのジャンパワイヤを間に接続する必要がある
Arduino側のコード実装
Arduino側では以下機能を実装する
・WiFiに接続し、ソケット通信でラズパイから描画データを受信する
→mainループに実装する
・描画データをSDカードに書き込む/読み出す
→controlSD.cpp/hに実装する
・電子ペーパーに描画データを書き込む
→writeEPaper.cppに実装する
C++のくせにクラスを定義していないがあしからず... (ヒマができたらきれいにリファクタリングします)
mainループの実装
#include <SPI.h>
#include <WiFi.h>
#include "controlSD.h"
#include "global.h"
#include "writeEPaper.h"
void setup() {
/*Serial Monitorの初期化*/
Serial.begin(115200);
/*WiFiの初期化*/
WiFi.begin(ssid, password);
/*SDの初期化*/
initSD();
/*残っている描画データを消去*/
eraseSD(BLACK_DATA_FLAG);
eraseSD(RED_DATA_FLAG);
/*電子ペーパーモジュールの初期化*/
initEPaper();
}
void loop() {
/*WiFiへの接続確認と接続待ち*/
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to Wi-Fi...");
}
Serial.println("Connected to Wi-Fi");
/*サーバー側からのソケット通信が確立されたかを確認*/
if (client.connect(serverIP, serverPort)) {
Serial.println("Connected to server!");
delay(1000);
/*ソケット通信が接続されている かつ 受信できるパケットが1byte以上になるまで待機*/
while (! client.connected() || client.available() == 0){}
/*最初に受信したパケットの値を読んで黒の描画データか赤の描画データか判断する*/
uint8_t readFlag = client.read();
Serial.print("readFlag:");
Serial.println(readFlag);
if (readFlag == 0x00){
Serial.println("Info : Black data will be received");
BlackRedFlag = BLACK_DATA_FLAG;
}else if(readFlag == 0xff){
BlackRedFlag = RED_DATA_FLAG;
Serial.println("Info : Red data will be received");
}else{
Serial.println("warning: may be not correct data");
BlackRedFlag = 0xFF;
}
/*既存の描画データの消去*/
eraseSD(BlackRedFlag);
/*Buffer indexの初期化*/
bufferIndex = 0;
receiveCount = 0;
/*描画データを受信する*/
while (client.connected()) {
Serial.println("Start receive data...");
Serial.println(client.available());
while (client.available() ) {
if (bufferIndex < BUFFER_SIZE) {
/*バッファにデータを溜め込む*/
imageBuffer[bufferIndex++] = (uint8_t)client.read(); // Store data in the buffer
} else {
/*バッファがいっぱいになったらSDカードに書き込む*/
Serial.println("Buffer is full. Write to SD...");
writeSDFromBuf(imageBuffer, BUFFER_SIZE, BlackRedFlag);
bufferIndex = 0;
break;
}
}
++receiveCount;
Serial.println("Continue to receive image");
Serial.print("Receive Count : ");
Serial.println(receiveCount);
}
/*受信完了後, バッファにまだ残っているデータをSDに書き込む*/
Serial.println("Write to SD...");
writeSDFromBuf(imageBuffer, bufferIndex, BlackRedFlag);
client.stop();
Serial.println("Image received successfully!");
if(BlackRedFlag == BLACK_DATA_FLAG){
receiveFlag[0] = 1;
}else if (BlackRedFlag == RED_DATA_FLAG){
receiveFlag[1] = 1;
}
} else {
Serial.println("Connection failed.");
}
/*黒の描画データ、赤の描画データの両方を受信したら電子ペーパーに書き込む*/
if(receiveFlag[0] == 1 && receiveFlag[1] == 1){
clearEPaper();
readSDAndWriteEPaper(BLACK_DATA_FLAG);
readSDAndWriteEPaper(RED_DATA_FLAG);
execDrawDisp();
receiveFlag[0] = 0;
receiveFlag[1] = 0;
}
}
描画データをSDカードに書き込む/読み出す実装
#include "controlSD.h"
/*SDモジュールの初期化*/
void initSD()
{
SD.begin(chipSelect);
if (!card.init(SPI_HALF_SPEED, chipSelect)) {
Serial.println("initialization failed. Things to check:");
Serial.println("* is a card inserted?");
Serial.println("* is your wiring correct?");
Serial.println("* did you change the chipSelect pin to match your shield or module?");
sdFailed = true;
}else {
Serial.print("Card type:");
switch (card.type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
}
if (!volume.init(card)) {
Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
sdFailed = true;
}else {
/*これ以降はSDカードの情報をデバッグ出力しているだけなので不要かも*/
Serial.print("Clusters: ");
Serial.println(volume.clusterCount());
Serial.print("Blocks x Cluster: ");
Serial.println(volume.blocksPerCluster());
Serial.print("Total Blocks: ");
Serial.println(volume.blocksPerCluster() * volume.clusterCount());
Serial.println();
uint32_t volumesize;
Serial.print("Volume type is: FAT");
Serial.println(volume.fatType(), DEC);
volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
volumesize *= volume.clusterCount(); // we'll have a lot of clusters
volumesize /= 2; // SD card blocks are always 512 bytes (2 blocks are 1KB)
Serial.print("Volume size (Kb): ");
Serial.println(volumesize);
Serial.print("Volume size (Mb): ");
volumesize /= 1024;
Serial.println(volumesize);
Serial.print("Volume size (Gb): ");
Serial.println( ( float )volumesize / 1024.0 ) ;
Serial.println("\nFiles found on the card (name, date and size in bytes): ");
root.openRoot(volume);
// list all files in the card with date and size
root.ls(LS_R | LS_DATE | LS_SIZE);
}
}
}
/*SDカードに書き込む関数*/
void writeSDFromBuf(unsigned char *imageBuffer,const long BufSize, int BlackRedFlag)
{
long long int writeData = 0;
long bufNum = 0;
if(!sdFailed)
{
/*フラグに応じて黒の描画データまたは赤の描画データを保存する*/
if(BlackRedFlag == 0)
{
//Serial.println("black data will received");
myFile = SD.open(BLACK_DATA, FILE_WRITE);
}else if(BlackRedFlag == 1){
//Serial.println("red data will received");
myFile = SD.open(RED_DATA, FILE_WRITE);
} else {
//Serial.println("warning: may be not correct data");
//myFile = SD.open("TEST.TXT", FILE_WRITE);
}
if (myFile) {
Serial.print("Writing to SD...");
SPI.beginTransaction(SPISettings(250000, MSBFIRST, SPI_MODE0));
/*受信データを書き込む*/
myFile.write(imageBuffer, BufSize);
SPI.endTransaction();
myFile.close();
Serial.println("done.");
} else {
/*Fileオブジェクトが正しく開けなかった場合のハンドリング*/
Serial.println("error opening file");
myFile.close();
}
}
}
/*開いたファイルから1byte分を読み出す*/
uint8_t readSDOneByte(File myFile){
uint8_t data;
SPI.beginTransaction(SPISettings(250000, MSBFIRST, SPI_MODE0));
data = myFile.read();
SPI.endTransaction();
return data;
}
/*指定されたファイルを削除する*/
void eraseSD(int fileFlag)
{
if(SD.exists(BLACK_DATA) && fileFlag == 0){
/*黒の描画データを削除する*/
SD.remove(BLACK_DATA);
Serial.println( "file erasure completed" );
}
if (SD.exists(RED_DATA) && fileFlag == 1){
/*赤の描画データを削除する*/
SD.remove(RED_DATA);
Serial.println( "file erasure completed" );
}
if (SD.exists("TEST.TXT")){
SD.remove("TEST.TXT");
Serial.println( "file erasure completed" );
}
if(!SD.exists(BLACK_DATA) && !SD.exists(BLACK_DATA) && !SD.exists("TEST.TXT"))
{
/*削除するファイルが無い場合のハンドリング*/
Serial.println( "target file does not exist" );
}
}
#ifndef CONTSD_H
#define CONTSD_H
#include "global.h"
#include "EPD_7in5b_V2.h"
#include "writeEPaper.h"
void initSD();
void writeSDFromBuf(unsigned char *imageBuffer,const long BufSize, int BlackRedFlag);
uint8_t readSDOneByte(File myFile);
void eraseSD(int fileFlag);
#endif
電子ペーパーに描画データを書き込む実装
#include "writeEPaper.h"
/*電子ペーパーの初期化*/
void initEPaper(){
Serial.println("e-Paper Init");
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
DEV_Module_Init();
EPD_7IN5B_V2_Init();
SPI.endTransaction();
}
/*電子ペーパーをクリア(消去)する*/
void clearEPaper(){
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
Serial.println("e-Paper Clear");
EPD_7IN5B_V2_Clear();
SPI.endTransaction();
}
/*電子ペーパーにコマンドを送信する*/
void sendCommandToEPaper(const uint8_t command){
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
EPD_7IN5B_V2_SendCommand(command);
SPI.endTransaction();
}
/*電子ペーパーにデータを送信する*/
void sendDataToEPaper(const uint8_t data){
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
EPD_7IN5B_V2_SendData(data);
SPI.endTransaction();
}
/*SDカードから描画データを読み出して電子ペーパーに送信する*/
void readSDAndWriteEPaper(int fileFlag)
{
uint8_t data;
if(fileFlag == BLACK_DATA_FLAG){
myFile = SD.open(BLACK_DATA);
}else if(fileFlag == RED_DATA_FLAG){
myFile = SD.open(RED_DATA);
}
if (myFile) {
if(fileFlag == BLACK_DATA_FLAG){
Serial.print(BLACK_DATA);
Serial.println(":");
sendCommandToEPaper(BLACK_WRITE_COMMAND);
}else if (fileFlag == RED_DATA_FLAG){
Serial.print(RED_DATA);
Serial.println(":");
sendCommandToEPaper(RED_WRITE_COMMAND);
}
while (myFile.available()) {
for (int32_t j = 0; j < Height; j++) {
for (int32_t i = 0; i < Width; i++) {
data = readSDOneByte(myFile);
sendDataToEPaper(data);
}
}
break;
}
// close the file:
myFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening test.txt");
myFile.close();
}
}
/*電子ペーパーに描画のトリガをかける*/
void execDrawDisp(){
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
EPD_7IN5B_V2_TurnOnDisplay();
SPI.endTransaction();
}
#ifndef WRITEEPAPER_H
#define WRITEEPAPER_H
#include "controlSD.h"
#include <SPI.h>
#include "EPD_7in5b_V2.h"
void initEPaper();
void clearEPaper();
void sendCommandToEPaper(const uint8_t command);
void sendDataToEPaper(const uint8_t data);
void readSDAndWriteEPaper(int fileFlag);
void execDrawDisp();
#endif
グローバル変数等の定義
#include "global.h"
/*WiFi接続に必要な定数,変数*/
const char* ssid = "xxxxxxxx"; //Your WiFi SSID
const char* password = "xxxxxxxx"; //WiFi password
const char* serverIP = "192.168.0.xxx"; // Raspberry Pi's IP address
const int serverPort = xxx;
WiFiClient client;
/*SDカード制御に必要な変数*/
const int chipSelect = 4;
Sd2Card card;
SdVolume volume;
SdFile root;
File myFile;
bool sdFailed = false;
/*データ受信時に必要な変数*/
unsigned char imageBuffer[BUFFER_SIZE]; // Buffer to hold the image data
unsigned char *ptrImageBuffer = imageBuffer;
long bufferIndex = 0;
int BlackRedFlag = 0;
int receiveCount = 0;
int receiveFlag[2] = {0,0};
/*電子ペーパーの画面サイズ*/
const uint32_t Width =(EPD_7IN5B_V2_WIDTH % 8 == 0)?(EPD_7IN5B_V2_WIDTH / 8 ):(EPD_7IN5B_V2_WIDTH / 8 + 1);
const uint32_t Height = EPD_7IN5B_V2_HEIGHT;
#ifndef GLOBAL_H
#define GLOBAL_H
#include <stdint.h>
#include <SD.h>
#include "Arduino_LED_Matrix.h"
#include <WiFi.h>
#include "EPD_7in5b_V2.h"
#define BLACK_DATA "BLACK.DAT"
#define RED_DATA "RED.DAT"
#define BLACK_DATA_FLAG 0
#define RED_DATA_FLAG 1
#define BUFFER_SIZE 1024 * 11 //11 KB buffer
#define BLACK_WRITE_COMMAND 0x10
#define RED_WRITE_COMMAND 0x13
extern const char* ssid ; //Your WiFi SSID
extern const char* password; //WiFi password
extern const char* serverIP; // Raspberry Pi's IP address
extern const int serverPort;
extern WiFiClient client;
extern const int chipSelect;
extern Sd2Card card;
extern SdVolume volume;
extern SdFile root;
extern File myFile;
extern bool sdFailed;
extern unsigned char imageBuffer[BUFFER_SIZE]; // Buffer to hold the image data
extern uint8_t *ptrImageBuffer;
extern long bufferIndex;
extern int BlackRedFlag;
extern int receiveCount;
extern int receiveFlag[2];
extern const uint32_t Width;
extern const uint32_t Height;
#endif
その他
以下GitHubから電子ペーパーを動かすための"ドライバー機能"を有するファイルをダウンロードし、同じスケッチにコピーする
・EPD_7in5b_V2.cpp
・EPD_7in5b_V2.h
https://github.com/waveshareteam/e-Paper/tree/master/Arduino_R4/src/e-Paper
・DEV_Config.cpp
・DEV_Config.h
・Debug.h
https://github.com/waveshareteam/e-Paper/tree/master/Arduino_R4/src/Config
結果
ラズパイで直接制御した場合と変わらず表示させることができた
追記
受信した描画データ(1byteずつ受信)を保持せずにそのまま電子ペーパーに送信してしまえばよかったのでは?ということに後から気づいた。SDカードいらないじゃん...(電子ペーパーのライブラリのコードを見る限り可能)
まあでもArduinoでSDカードに読み書きしたこと無かったので経験値が得られたということで良しとしましょう。
あと、大量のソースコードを公開するならやっぱりGitHubにアップした方が良いかもしれない。。。