東芝デジタルカメラユニット DMR-C1
2025.09.09

偶然、昔の携帯電話用の東芝デジタルカメラユニット「DMR-C1 SHOT ON BAR」が目に留まり、購入してしまいました。
以前、
京セラ feelH” Treva カメラ
を紹介しましたが、2000年9月に発売されたこのカメラユニットは画素数は96x72と今では考えられないような少なさ、
2002年3月に発売されたDMR-C1は352×288と画素数が増加していました。
●DMR-C1カメラの作成
このカメラユニットを利用して、デジカメを作成しましょう。まずは分解です。

2つの小さなネジを外すと簡単に蓋を開けることができます。
このカメラはTrevaとコネクタの形状も同じで、クロック信号がピンク色の線、データ信号が黄色の線 GNDが黒線、3.3Vが赤線のようです。

カメラベースは底面側ケースとネジで固定してあるので、ケースの裏側に両面テープは貼付けて基板に取り付けます。
ミニジャックにハンダ付けされていた4本の線を外してユニバーサル基板に取り付けています。

左手前にESP32マイコンボード、その後方にシャッタースイッチ、中央にDMR-C1ユニット、右側にmicroSDカードモジュールを配置しています。
画像処理のために、マイコンボードには、PSRAM 2MBを実装している
Adafruit QT Py ESP32-S2 を使用しています。Arduino UNOなどは使用できません。

ST7789 TFT DISPLAYではDMR-C1から垂れ流されてくる画像データの中央部240x240の範囲を表示しています。
写真中央下のLEDとトランジスタはmicroSDカードへ画像保存中点灯する回路です。
配線はこんな感じです
| ST7789 | | QT Py | | micro SD |
| (※1)RESET | - | A0 | | |
| DC | - | A1 | | |
| TFTCS | - | A2 | | |
| | | A3 | - | CS (※2) |
| SCK | - | SCK | - | CLK |
| | | MI | - | MISO |
| MOSI | - | MO | - | MOSI |
| 3V | - | 3V | - | 3v3 |
| GND | - | GND | - | GND |
| | | | | DMR-C1 |
| | | SDA | - | DOUT(黄色) |
| | | SCL | - | CLK_IN(ピンク) |
| | | 3V | - | 3.3V(赤) |
| | | GND | - | GND(黒) |
| SWITCH | | | | |
| SWITCH | - | A7(RX) 10KΩPULL-UP | | |
| - | GND | | |
※I2C用ピン(SDA,SCL)を使っていますが、I2C通信ではありません
※1 ST7789 RESET端子
非接続にする場合が多いのですが、今回はハードウェア・リセットを掛けるために使用しています
※2 microSD Card Module 画像保存中、LEDを点灯させる
microSDカード書込み中はモジュールのSPIチップセレクトはLOWに設定されるので、LOWでLEDが光るようにPNP型トランジスタを使用しています。

電子工作初心者のためのトランジスタ講座
●DMR-C1 シーケンス
画像の取込みは、垂れ流されるデータ列に0xAA,0x55を探し 画像ヘッダー情報28byte読込み後、次のデータ列が0x55,0xAA(データスタートマーカー)であれば、
それに続く画像データ(202,752byte)を取得します。
| AA 55 | ダミーコード |
| FF D8 | フレームスタートマーカ | ヘッダ |
| 1C | ヘッダ長(28) |
| F0 | モデルタイプ |
| F1 | バージョン |
| 01 60 | 水平画像サイズ(352) |
| 01 20 | 垂直画像サイズ(288) |
| 81 31 20 |
フォーマット情報
8 : 8bitデータ
1 : YUV
3 : UYVY
1 : MSB
2 : 4:2:2
0 : 非圧縮
|
| 4B 43 23 38 43 46 2E 2E | メーカ情報 |
| 00 00 00 00 00 00 00 00 | ユーザ情報 |
| 55 AA | データスタートマーカ |
|
U0,Y0,V0,Y1,
U2,Y2,V2,Y3...
|
画像データ(352×288×2) |
| FF D9 | エンドマーカ |
Ref:
Serial Camera
〇補足:シリアル転送をする際の注意点
ななしのさんの解析によると、DMR-C1から1バイトを取得するとき44usecかかるとダメなようです。
画像を取込みながら115200bpsで転送すると、DMR-C1から取得されるデータはオールゼロになってしまうようです。
リアルタイム転送の場合、460,800bps以上が必要になるようです。
●プログラミング
ソースコードのビルドには、PlatformIOを使用しています。
Arduino開発環境構築 PlatformIO
DMR-C1は現時点で入手困難ですのでソースコードの詳細は説明しません。
コーディングの要点は下記の通りです。
(1)カメラからの画像はUYVY形式です。このUYVY 4バイトが連続する2ピクセル分の画像になります
(2)2ピクセルごと表示していると描画に時間が掛かるので、1画像分の処理が終了した段階でTFT DISPLAYにバースト転送しています。
煩雑なメモリー処理をするとリセットを繰り返すので注意が必要です。
(3)保存画像はBMP形式で連番のファイル名が付けられます。
(4)SPI 1系統のみのマイコンボードの場合、きちんと制御しないと microSDカードモジュールへの書込みに失敗します。
ソースコード
一応ソースコードを掲載しておきます。適当に改造して使ってください。
#include <SPI.h>
#include <SD.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#define SPI_TFT_CS A2
#define SPI_DC A1
#define SPI_MISO MISO
#define SPI_MOSI MOSI
#define SPI_SCK SCK
#define SPI_TFT_RST A0
#define SPI_TFT_BL -1
#define SPI_SD_CS A3
#define SWITCH_PIN A7
Adafruit_ST7789 tft = Adafruit_ST7789(SPI_TFT_CS, SPI_DC, SPI_MOSI, SPI_SCK, SPI_TFT_RST);
#define CLK_PIN SCL
#define DAT_PIN SDA
long DMRC1_WIDTH = 352;
long DMRC1_HEIGHT = 288;
int TFT_WIDTH = 240;
int TFT_HEIGHT = 240;
typedef struct {
uint8_t Signature[2]; // 'BM'
uint8_t FileSize[4];
uint8_t reserved1[4]; // unused (=0)
uint8_t DataOffset[4];
uint8_t Size[4]; // Size of InfoHeader =40
uint8_t Width[4];
uint8_t Height[4];
uint8_t Planes[2]; // always 1
uint8_t BitPerPixel[2];
uint8_t Compression[4];
uint8_t ImageSize[4];
uint8_t XRGBsPerM[4];
uint8_t YRGBsPerM[4];
uint8_t ColorsUsed[4];
uint8_t ImportantColors[4];
} BMP_FORMAT;
typedef struct {
uint8_t R;
uint8_t G;
uint8_t B;
} PIXEL_INFO;
BMP_FORMAT *Bmp = NULL;
PIXEL_INFO *RGBs = NULL;
SPISettings settingsST7789(24000000, MSBFIRST, SPI_MODE3);
#define HBYTE(u) ((u >> 8) & 0xFF)
#define LBYTE(u) (u & 0xFF)
void tftSendCommand(uint8_t command) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
}
void tftSendCommand1(uint8_t command, uint8_t data1) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
digitalWrite(SPI_TFT_CS, HIGH);
}
void tftSendCommand2(uint8_t command, uint8_t data1, uint8_t data2) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
SPI.transfer(data2);
digitalWrite(SPI_TFT_CS, HIGH);
}
void tftSendCommand4(uint8_t command, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(data1);
SPI.transfer(data2);
SPI.transfer(data3);
SPI.transfer(data4);
digitalWrite(SPI_TFT_CS, HIGH);
}
void drawColor565(uint16_t *t565s, uint16_t width, uint16_t height)
{
uint8_t h, l;
uint16_t x, y;
SPI.beginTransaction(settingsST7789);
x = 0;
y = 0;
width--;
h = (uint8_t)(width>>8);
l = (uint8_t)(width&0x00ff);
tftSendCommand4(0x2A,x,y,h,l); // Colmun Address
height--;
h = (uint8_t)(height>>8);
l = (uint8_t)(height&0x00ff);
tftSendCommand4(0x2B,x,y,h,l); // Row Address
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(0x2C);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(t565s, width*height*2);
digitalWrite(SPI_TFT_CS, HIGH);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.endTransaction();
}
void dispStartLine(uint16_t y) {
uint8_t yH = (y >> 8) & 0xFF ;
uint8_t yL = y & 0xFF ;
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(0x37);
digitalWrite(SPI_DC, HIGH); // Data mode
SPI.transfer(yH);
SPI.transfer(yL);
digitalWrite(SPI_TFT_CS, HIGH);
}
void init_tft() {
SPI.beginTransaction(settingsST7789);
// --- HARD Ware Reset
if (SPI_TFT_RST >= 0) {
digitalWrite(SPI_TFT_RST, HIGH);
debug_delay(50); // VDD goes high at start, pause for 500 ms
digitalWrite(SPI_TFT_RST, LOW); // Bring reset low
debug_delay(10); // Wait 100 ms
digitalWrite(SPI_TFT_RST, HIGH); // Bring out of reset
debug_delay(50); // Wait 500 ms, more then 120 ms
}
// --- SOFT Ware Reset
tftSendCommand(0x01) ; // SOFTWARE RESET
debug_delay(50);
// --- Initial Comands
tftSendCommand(0x28) ; // Display OFF
debug_delay(50);
tftSendCommand(0x11) ; // Sleep Out
debug_delay(50);
tftSendCommand1(0x3A,0x05) ; // 16Bit Pixel Mode
debug_delay(10);
tftSendCommand1(0x36,B00000000) ; // MX MY MV ML RGB MH x x:縦向き1
tftSendCommand2(0xB6,0x15,0x02) ; // Frame Rate
tftSendCommand(0x13) ; // NomalDisplayMode
tftSendCommand(0x21) ; // Display Inversion Off
tftSendCommand(0x29) ; // Display ON
// tftSendCommand1(0x36,B10100000) ;// MX MY MV ML RGB MH x x: Horizontal 1
// tftSendCommand1(0x36,B01100000) ; // MX MY MV ML RGB MH x x:Horizontal 2
// tftSendCommand1(0x36,B00000000) ; // MX MY MV ML RGB MH x x:Vertical 1
tftSendCommand1(0x36,B11000000) ; // MX MY MV ML RGB MH x x:Vertical 2
dispStartLine(82);
SPI.endTransaction();
}
void debug_delay(uint32_t msec) {
portTickType delay_ms = msec / portTICK_RATE_MS;
vTaskDelay(delay_ms);
}
void short2byte(uint8_t *pt, uint16_t val) {
*pt = (uint8_t)val &0x00ff;
*(pt+1) = (uint8_t)((val>>8)&0x00ff);
}
void long2byte(uint8_t *pt, uint32_t val) {
*pt = (uint8_t)val &0x000000ff;
*(pt+1) = (uint8_t)((val>> 8)&0x000000ff);
*(pt+2) = (uint8_t)((val>>16)&0x000000ff);
*(pt+3) = (uint8_t)((val>>24)&0x000000ff);
}
int readbit() {
int a;
digitalWrite(CLK_PIN, HIGH);
a = digitalRead(DAT_PIN);
digitalWrite(CLK_PIN, LOW);
if(a){
return 1;
} else {
return 0;
}
}
boolean saveBmpFile(const char *path)
{
long pos;
char fname[32];
static int fno = 0;
digitalWrite(SPI_SD_CS, LOW);
SPI.setFrequency(16000000); // 16MHz
if (!SD.begin(SPI_SD_CS, SPI)) return false;
while (1) {
sprintf(fname,"%s/%d.bmp",path,++fno);
if (!SD.exists(fname)) break;
}
File fileSD = SD.open(fname, "wb");
if (!fileSD) return false;
if (!Bmp) Bmp = (BMP_FORMAT *)malloc(sizeof(BMP_FORMAT));
memset(Bmp, 0x00, sizeof(BMP_FORMAT));
memcpy(&Bmp->Signature,(uint8_t*)"BM",2);
long2byte(Bmp->FileSize, sizeof(BMP_FORMAT) + DMRC1_WIDTH * DMRC1_HEIGHT * 3);
long2byte(Bmp->DataOffset, sizeof(BMP_FORMAT));
long2byte(Bmp->Size, 40);
long2byte(Bmp->Width, DMRC1_WIDTH);
long2byte(Bmp->Height, DMRC1_HEIGHT);
short2byte(Bmp->Planes, 1);
short2byte(Bmp->BitPerPixel, 24);
long2byte(Bmp->ImageSize, DMRC1_WIDTH * DMRC1_HEIGHT * 3);
long2byte(Bmp->ColorsUsed, 0);
fileSD.write((uint8_t*)Bmp, sizeof(BMP_FORMAT));
// Correction for RGB information, as one horizontal row
// of the image must be a multiple of 4 bytes
uint8_t mod = (DMRC1_WIDTH % 4) * 3;
for(int y = DMRC1_HEIGHT - 1; y >= 0; y--){
pos = DMRC1_WIDTH * y;
for(int x=0; x < DMRC1_WIDTH; x++,pos++){
fileSD.write(&RGBs[pos].B, 1);
fileSD.write(&RGBs[pos].G, 1);
fileSD.write(&RGBs[pos].R, 1);
}
if (mod>0) fileSD.write(0x00,mod);
}
fileSD.close();
SD.end();
digitalWrite(SPI_SD_CS, HIGH);
return true;
}
boolean capture()
{
unsigned short marker = 0x0;
long i,k;
int bit;
float u, y0, v, y1;
unsigned char r = 0, g = 0, b = 0;
unsigned char d;
long pos = 0;
long pos_max = DMRC1_WIDTH * DMRC1_HEIGHT;
// find start marker
while((marker & 0xffff) != 0xaa55) {
marker <<= 1;
if(readbit()) marker |= 0x01;
}
// skip 28 bytes
for(i=0; i<28*8; i++) readbit();
// check data start marker
marker = 0; bit = 0;
while (1) {
if(readbit()) marker |= 0x01;
if ((++bit)<16) {
marker <<= 1;
} else {
break;
}
}
if (marker != 0x55aa) {
return false;
}
for( k=0; k < DMRC1_WIDTH * DMRC1_HEIGHT * 4; k++) {
d = 0;
for (i=0; i<8; i++) {
d <<= 1;
if(readbit()) d |= 0x1;
}
switch (k%4) {
case 0:
u = d;
break;
case 1:
y0 = d;
break;
case 2:
v = d;
break;
case 3:
y1 = d;
r = y0 + 1.140*(v - 128);
g = y0 - 0.394*(u - 128) - 0.581*(v - 128);
b = y0 + 2.032*(u - 128);
if(255 < r) r = 255;
if(r < 0) r = 0;
if(255 < g) g = 255;
if(g < 0) g = 0;
if(255 < b) b = 255;
if(b < 0) b = 0;
if (pos < pos_max) {
RGBs[pos].R = r;
RGBs[pos].G = g;
RGBs[pos].B = b;
}
r = y1 + 1.140*(v - 128);
g = y1 - 0.394*(u - 128) - 0.581*(v - 128);
b = y1 + 2.032*(u - 128);
if(255 < r) r = 255;
if(r < 0) r = 0;
if(255 < g) g = 255;
if(g < 0) g = 0;
if(255 < b) b = 255;
if(b < 0) b = 0;
if (pos < pos_max) {
RGBs[pos+1].R = r;
RGBs[pos+1].G = g;
RGBs[pos+1].B = b;
pos+=2;
}
break;
}
}
return true;
}
void show()
{
uint16_t from_w, to_w, from_h, to_h;
uint16_t color565;
long pos1 = 0;
long pos2 = 0;
from_h = (DMRC1_HEIGHT - TFT_HEIGHT)/2;
to_h = from_h + TFT_HEIGHT;
from_w = (DMRC1_WIDTH - TFT_WIDTH)/2;
to_w = from_w + TFT_WIDTH;
uint16_t *color565s = (uint16_t*)malloc(TFT_WIDTH * TFT_HEIGHT * 2);
if (color565s) {
for (int y = 0; y < DMRC1_HEIGHT; y++) {
for (int x = 0; x < DMRC1_WIDTH; x++) {
if ((y >= from_h)&&(y < to_h)&&(x >= from_w)&&(x < to_w)) {
color565 = ((RGBs[pos1].R >> 3)<<11) | ((RGBs[pos1].G >> 2)<<5) | (RGBs[pos1].B >> 3);
color565s[pos2] = ((color565>>8)&0x00FF)|((color565<<8)&0xFF00);
pos2++;
}
pos1++;
}
}
drawColor565(color565s,TFT_WIDTH,TFT_HEIGHT);
free(color565s);
}
}
void setup()
{
pinMode(CLK_PIN, OUTPUT);
pinMode(DAT_PIN, INPUT);
pinMode(SPI_SD_CS, OUTPUT);
pinMode(SPI_TFT_CS, OUTPUT);
pinMode(SPI_DC, OUTPUT);
pinMode(SPI_TFT_RST, OUTPUT);
pinMode(SWITCH_PIN, INPUT );
digitalWrite(SPI_SD_CS, HIGH);
RGBs = (PIXEL_INFO *)malloc(DMRC1_WIDTH * DMRC1_HEIGHT * 3);
if (!RGBs) while(1);
if (SPI_TFT_BL != -1) {
ledcSetup(0,12800,8);
ledcAttachPin(SPI_TFT_BL,0);
ledcWrite(0,64);
}
SPI.begin();
init_tft();
}
void loop()
{
if ( capture() ) {
show();
if (!digitalRead(SWITCH_PIN)) {
saveBmpFile("/dmrc1");
}
}
}
●取得画像

まあ、こんな感じです。CMOSセンサーの経年劣化でしょうか?赤くなっちゃってます。
●参考文献
DMR-C1を使ってみる
ミニカメラをaJileにつなごう
|
Raspberry Pi(ラズベリー パイ)は、ARMプロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
たいていのことは100日あれば、うまくいく。長田英知著
「時間がなくて、なかなか自分のやりたいことができない」
「一念発起して何かを始めても、いつも三日坊主で終わってしまう」
「色んなことを先延ばしにしたまま、時間だけが過ぎていく」
そこで本書では、そんな著者が独自に開発した、
まったく新しい目標達成メソッド「100日デザイン」について、
その知識と技術を、余すところなくご紹介します。
まんがで納得ナポレオン・ヒル 思考は現実化する
OLとして雑務をこなす日々に飽き足らず、科学者だった父が残した薬品を商品化すべく、起業を決意した内山麻由(27)。彼女はセミナーで知り合った謎の女性からサポートを得ながら、彼女と二人三脚でナポレオン・ヒルの成功哲学を実践し、さまざまな問題を乗り越えていく。
ヒル博士の<ゴールデンルール>に従い、仕事に、恋に全力疾走する彼女の、成功への物語。
今日は人生最悪で最高の日 1秒で世界を変えるたったひとつの方法 ひすいこたろう著
偉人の伝記を読むと、最悪な日は、不幸な日ではなく、新しい自分が始まる日であることがわかります。最悪な出来事は、自分の人生が、想像を超えて面白くなる兆しなのです。偉人伝を読むことで、このときの不幸があったおかげで、未来にこういう幸せがくるのかと、人生を俯瞰する視線が立ち上がるのです。
ご飯は私を裏切らない heisoku著
辛い現実から目を背けて食べるご飯は、いつも美味しく幸せを届けてくれる。
29歳、中卒、恋人いない歴イコール年齢。バイト以外の職歴もなく、短期バイトを転々とする日々。ぐるぐると思索に耽るけど、ご飯を食べると幸せになれる。奇才の新鋭・heisokuが贈るリアル労働グルメ物語!
【最新版Gemini 3に対応!】できるGemini (できるシリーズ)
Geminiを「最強の知的生産パートナー」として使いこなすための、実践的なノウハウを凝縮した一冊です。
基本的な操作方法から、具体的なビジネスシーンでの活用、日々の業務を自動化するGoogle Workspaceとの連携、さらには自分だけのオリジナルAIを作成する方法まで余すところなく解説します。
Rustプログラミング完全ガイド 他言語との比較で違いが分かる!
Rustの各手法や考え方を幅広く解説!
500以上のサンプルを掲載。実行結果も確認。
全24章の包括的なチュートリアル。
ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS…
全WEB媒体で効果バツグン!
カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術
小型で便利な Type-C アダプター USB C オス - USB3.1 オスアダプター
Type-C端子のマイコンボードをこのアダプタを介して直接Raspberry Piに挿すことができます。ケーブルなしで便利なツールです。
Divoom Ditoo Pro ワイヤレススピーカー
15W高音質重低音/青軸キーボード/Bluetooth5.3/ピクセルアート 専用アプリ/USB接続/microSDカード
電源供給USBケーブル スリム 【5本セット】
USB電源ケーブル 5V DC電源供給ケーブル スリム 【5本セット】 電源供給 バッテリー 修理 自作 DIY 電子工作 (100cm)
|