デジカメ弐号機 第5回 機能統合
2022.12.02
YouTubeでポイントを説明しています。画像をクリックすると再生できます。
デジカメ弐号機第5回では、これまで単体テストを行った各モジュールを繋ぎ合わせて、統合テストを行います。
自作デジカメ初号機、弐号機とも、同じArduCamカメラモジュールを使用しているのですが、ここで、当初想定していた仕様から変更があります。
■ArduCAM Mini 2MP カメラモジュール
ArduCam Mini 2MP は、オムニビジョン OV2640 2Mピクセルレンズを搭載したカメラモジュールです。
384KBのフレームバッファを搭載しています。レンズマウントはM12マウントで交換も可能です。
【主な仕様】
| 電源電圧: | DC5V |
| 消費電流: | ノーマル70mA、ローパワーモード20mA |
| フレームバッファ: | 384KB |
| 出力解像度: |
1600×1200、800×600、
640×480、320×240、
352×288、176×144 |
| 出力形式: | RAW、YUV、RGB、JPEG |
| 基板サイズ: | 34×24mm |
| サイズ: | 40×24×33mm(厚みはレンズに依存) |
●BMP(COLOR565)形式のストリーミング画像
BMP(COLOR565)形式では、画像1ピクセルは2バイトです。
| 解像度 | 使用バイト数 |
| 320×240 | 153,600 |
| 352×288 | 202,752 |
| 640×480 | 614,400 |
フレームバッファーサイズは384Kバイトなので、BMP(COLOR565)形式のストリーミングで取得できる画像は解像度352×288ピクセルまでです。
●JPEG形式によるキャプチャリング
| 解像度 | JPEG画像 | BMP変換後 | PSRAM |
| 320×240 | 7,172 | 230,400 | 237,572 |
| 640×480 | 13,317 | 921,600 | 934,917 |
| 800×600 | 18,436 | 1,440,000 | 1,458,436 |
| 1280×1024(960) | 50,181 | 3,686,400 | 3,736,581 |
| 1600×1200 | 74,757 | 5,760,000 | 5,834,757 |
JPEG形式で取得できる画像サイズは被写体によって異なるので概算値です。
取得JPEG画像とBMP形式へ変換後のバイト数、2つの画像の合計バイト数を示しています。
Adafruit QT Py ESP32-S2 は 2MバイトのPSRAMを実装しているので、JPEG画像の解像度800×600までは処理可能なことがわかります。
そのため、被写体確認用としてディスプレイ表示を行う際には、取得した画像をそのままディスプレイにSPIバースト転送できるBMPストリーミング画像を用い、
被写体保存にはJPEG形式のキャプチャリング画像を用います。
取得したJPEG画像は、ESP32 のオンメモリー上で、picojpegライブラリを用いて、BMP画像に変換し、そこからサーマルプリンターでの印刷用に縦横384ピクセルの正方形画像を切り出します。
■シャッターボタン
シャッターボタンを押した際には、ArduCamの取得画像形式を一時的に 320x240(BMP) → 800x600(JPEG) に変更し、取得した被写体画像をSDカードに保存します。
保存処理中はLEDが点灯します。

| QT Py ESP32S2 | - | SWITCH/LED |
A7(RX)
GND | -
- | SWITCH ❘ 10KΩ |
A6(TX)
GND | -
- | LED ❘ 1KΩ |
#define LED_PIN A6 // TX
#define BTN_PIN A7 // RX
boolean writeStatus = false;
void capture() {
vTaskDelay(3000);
}
void setup() {
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
if ( digitalRead(BTN_PIN)==LOW ) {
if ( !writeStatus ) {
writeStatus = true;
digitalWrite(LED_PIN, HIGH);
capture();
writeStatus = false;
digitalWrite(LED_PIN, LOW);
}
}
}
■配線
これまで単体テストを行った各モジュールを繋ぎ合わせます。
| PCF8523 | - | ESP32S2 | - | ArduCam | - | ST7789 | - | microSD |
| | | A0 | - | CS | | | | |
| | | A1 | - | ------ | - | D/C | | |
| | | A2 | - | ------ | - | TCS | | |
| | | A3 | - | ------ | - | ------ | - | CS |
| GND | - | GND | - | GND | - | GND | - | GND |
| VIN | - | 3.3V | - | VCC | - | Vin | - | 3.3V |
| | | MOSI | - | MOSI | - | MOSI | - | SI |
| | | MISO | - | MISO | - | ------ | - | SO |
| | | SCK | - | SCLK | - | SCK | - | CLK |
| SDA | - | SDA | - | SDA | | | | |
| SCL | - | SCL | - | SCL | | | | |
| | | | | | | | | |
| | | A7(RX) | - SWITCH - 10KΩ - GND |
| | | A6(TX) | - LED - 1KΩ - GND |
| (デバック時) | A6(TX) | - [10]Raspberry Pi |

■ソースコードの要点
このソースコードは、ArduCAM_Mini_Video_Streaming.ino と ArduCAM_Mini_Capture2SD.ino を元にして、機能を付加しています。
また、JPEG→BMP画像変換には、picojpegライブラリを使用しています。
ArduCAM_Mini_2MP.ino
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
#include <WiFi.h>
#include <time.h>
#include "ArduCAM.h"
#include "memorysaver.h"
#include "RTClib.h"
#include "pgmspace.h"
#include "picojpeg.h"
#define SPI_SCK SCK
#define SPI_MISO MISO
#define SPI_MOSI MOSI
#define SPI_RST -1
#define SPI_DC A1
#define SPI_LIT 7
#define SPI_ARDUCAM_CS A0
#define SPI_TFT_CS A2
#define SPI_SD_CS A3
#define LED_PIN A6 // TX
#define BTN_PIN A7 // RX
boolean writeStatus = false;
#define TIMEZONE_JST (3600 * 9) // 日本標準時:UTC(世界標準時)より9時間早い
#define DAYLIGHTOFFSET_JST (0) // サマータイムなし
const char* ntp_server1 = "ntp.nict.jp";
const char* ntp_server2 = "pool.ntp.org";
static char *ssid[] = {"xxxxxxxx","xxxxxxxx"};
static char *password[] = {"xxxxxxxx","xxxxxxxx"};
int wifi_cnt = 2;
static time_t t = 0;
ArduCAM myCAM( OV2640, SPI_ARDUCAM_CS );
RTC_PCF8523 rtc;
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 XpixelsPerM[4];
uint8_t YpixelsPerM[4];
uint8_t ColorsUsed[4];
uint8_t ImportantColors[4];
} BMP_FORMAT;
BMP_FORMAT Bmp;
typedef struct {
uint8_t R;
uint8_t G;
uint8_t B;
} PIXEL_INFO;
PIXEL_INFO *RGBs;
typedef struct {
uint32_t Width;
uint32_t Height;
uint16_t BitPerPixel;
uint32_t FileSize;
uint32_t DataOffset;
uint32_t ImageSize;
uint32_t ColorsUsed;
} IMAGE_INFO;
IMAGE_INFO img;
#define LOCK_NONE 0
#define LOCK_ARDUCAM 1
#define LOCK_ST7789 2
SPISettings settingsArduCam(8000000, MSBFIRST, SPI_MODE0);
SPISettings settingsST7789(24000000, MSBFIRST, SPI_MODE3);
void spi_lock(int lock) {
static int status = -1;
if (lock==LOCK_NONE) {
SPI.endTransaction();
status = -1;
return;
}
if (lock!=status) {
SPI.endTransaction();
if (lock==LOCK_ARDUCAM) {
SPI.beginTransaction(settingsArduCam);
} else if (lock==LOCK_ST7789) {
SPI.beginTransaction(settingsST7789);
}
status = lock;
}
}
#define TFT_WIDTH 240
#define TFT_HEIGHT 240
#define ST77XX_BLACK 1
#define PRN_WIDTH 384
#define PRN_HEIGHT 384
uint16_t *color565;
#define HBYTE(u) ((u >> 8) & 0xFF)
#define LBYTE(u) (u & 0xFF)
// TFTにコマンドを送信
void tftSendCommand(uint8_t command) {
digitalWrite(SPI_TFT_CS, LOW);
digitalWrite(SPI_DC, LOW); // Command mode
SPI.transfer(command);
}
// TFTにコマンド+1バイトデータを送信
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);
}
// TFTにコマンド+2バイトデータを送信
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);
}
// TFTにコマンド+4バイトデータを送信
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 drawRGBBitmap(uint16_t x, uint16_t y, uint16_t *rgbs, uint16_t width, uint16_t height) {
uint8_t h, l;
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(rgbs, width*height*2);
digitalWrite(SPI_TFT_CS, HIGH);
digitalWrite(SPI_DC, LOW); // Command mode
}
// 表示開始ライン設定
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_lock(LOCK_ST7789);
// --- HARD Ware Reset
if (SPI_RST >= 0) {
digitalWrite(SPI_RST, HIGH);
vTaskDelay(50); // VDD goes high at start, pause for 500 ms
digitalWrite(SPI_RST, LOW); // Bring reset low
vTaskDelay(10); // Wait 100 ms
digitalWrite(SPI_RST, HIGH); // Bring out of reset
vTaskDelay(50); // Wait 500 ms, more then 120 ms
}
// --- SOFT Ware Reset
tftSendCommand(0x01) ; // SOFTWARE RESET
vTaskDelay(50);
// --- Initial Comands
tftSendCommand(0x28) ; // Display OFF
vTaskDelay(50);
tftSendCommand(0x11) ; // Sleep Out
vTaskDelay(50);
tftSendCommand1(0x3A,0x05) ; // 16Bit Pixel Mode
vTaskDelay(10);
tftSendCommand1(0x36,B00000000) ; // MX MY MV ML RGB MH x x:縦向き1
tftSendCommand2(0xB6,0x15,0x02) ; // フレームレート設定
tftSendCommand(0x13) ; // NomalDisplayMode
tftSendCommand(0x21) ; // Display Inversion Off
tftSendCommand(0x29) ; // Display ON
tftSendCommand1(0x36,B11000000) ; // MX MY MV ML RGB MH x x:縦向き2
dispStartLine(80);
spi_lock(LOCK_NONE);
}
void show() {
spi_lock(LOCK_ST7789);
drawRGBBitmap(0,0,color565,TFT_WIDTH,TFT_HEIGHT);
spi_lock(LOCK_NONE);
}
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);
}
#define local_min(a,b) (((a) < (b)) ? (a) : (b))
FILE *stream;
pjpeg_image_info_t image_info;
int jpeg_is_available;
int jpeg_mcu_x;
int jpeg_mcu_y;
uint jpeg_read_offset;
uint jpeg_size;
uint8_t *jpeg_pImage;
int jpeg_MCUSPerRow;
int jpeg_MCUSPerCol;
int jpeg_MCUWidth;
int jpeg_MCUHeight;
int jpeg_MCUx;
int jpeg_MCUy;
static uint8_t pjpeg_need_bytes_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) {
pjpeg_callback(pBuf, buf_size, pBytes_actually_read, pCallback_data);
return 0;
}
uint8_t pjpeg_callback(uint8_t* pBuf, uint8_t buf_size, uint8_t *pBytes_actually_read, void *pCallback_data) {
uint n;
n = local_min(jpeg_size - jpeg_read_offset, buf_size);
fread(pBuf,n,1,stream);
*pBytes_actually_read = (uint8_t)(n);
jpeg_read_offset += n;
return 0;
}
int JpegDec_decode(unsigned long len){
jpeg_mcu_x = 0;
jpeg_mcu_y = 0;
jpeg_is_available = 0;
jpeg_read_offset = 0;
jpeg_size = len;
uint8_t status = pjpeg_decode_init(&image_info, pjpeg_need_bytes_callback, NULL, 0);
if (status) return -1;
jpeg_MCUSPerRow = image_info.m_MCUSPerRow;
jpeg_MCUSPerCol = image_info.m_MCUSPerCol;
jpeg_MCUWidth = image_info.m_MCUWidth;
jpeg_MCUHeight = image_info.m_MCUHeight;
jpeg_pImage = new uint8_t[image_info.m_MCUWidth * image_info.m_MCUHeight * 3];
if (!jpeg_pImage) return -1;
memset(jpeg_pImage, 0, sizeof(jpeg_pImage));
jpeg_is_available = 1 ;
return JpegDec_decode_mcu();
}
int JpegDec_decode_mcu(void) {
uint8_t status = pjpeg_decode_mcu();
if (status) {
jpeg_is_available = 0 ;
if (status != PJPG_NO_MORE_BLOCKS) return -1;
}
return 1;
}
int JpegDec_read(void) {
int y, x;
uint8_t *pDst_row;
if(jpeg_is_available == 0 || jpeg_mcu_y >= image_info.m_MCUSPerCol) return 0;
// Copy MCU's pixel blocks into the destination RGBs.
pDst_row = jpeg_pImage;
for (y = 0; y < image_info.m_MCUHeight; y += 8) {
const int by_limit = local_min(8, image_info.m_height - (jpeg_mcu_y * image_info.m_MCUHeight + y));
for (x = 0; x < image_info.m_MCUWidth; x += 8) {
uint8_t *pDst_block = pDst_row + x * 3;
// Compute source byte offset of the block in the decoder's MCU buffer.
uint src_ofs = (x * 8U) + (y * 16U);
const uint8_t *pSrcR = image_info.m_pMCUBufR + src_ofs;
const uint8_t *pSrcG = image_info.m_pMCUBufG + src_ofs;
const uint8_t *pSrcB = image_info.m_pMCUBufB + src_ofs;
const int bx_limit = local_min(8, image_info.m_width - (jpeg_mcu_x * image_info.m_MCUWidth + x));
int bx, by;
for (by = 0; by < by_limit; by++) {
uint8_t *pDst = pDst_block;
for (bx = 0; bx < bx_limit; bx++) {
pDst[0] = *pSrcR++;
pDst[1] = *pSrcG++;
pDst[2] = *pSrcB++;
pDst += 3;
}
pSrcR += (8 - bx_limit);
pSrcG += (8 - bx_limit);
pSrcB += (8 - bx_limit);
pDst_block += jpeg_MCUWidth * 3;
}
}
pDst_row += (jpeg_MCUWidth * 8 *3 );
}
jpeg_MCUx = jpeg_mcu_x;
jpeg_MCUy = jpeg_mcu_y;
jpeg_mcu_x++;
if (jpeg_mcu_x == image_info.m_MCUSPerRow) {
jpeg_mcu_x = 0;
jpeg_mcu_y++;
}
if(JpegDec_decode_mcu() < 0) jpeg_is_available = 0 ;
return 1;
}
boolean Jpeg2Bmp(uint8_t *data, uint32_t len, PIXEL_INFO *rgb, uint32_t *width, uint32_t *height) {
uint32_t pos;
uint8_t *pImg;
uint8_t *imgpt;
int x,y,bx,by;
if((stream = fmemopen((void*)data, len, "rb"))==NULL) return false;
if (JpegDec_decode(len) == -1) return false;
*width = image_info.m_width;
*height = image_info.m_height;
while(JpegDec_read()){
pImg = jpeg_pImage;
for(by=0; by<jpeg_MCUHeight; by++){
for(bx=0; bx<jpeg_MCUWidth; bx++){
x = jpeg_MCUx * jpeg_MCUWidth + bx;
y = jpeg_MCUy * jpeg_MCUHeight + by;
if(x<image_info.m_width && y<image_info.m_height){
pos = x + (y * image_info.m_width);
rgb[pos].B = pImg[0]<<1;
rgb[pos].G = pImg[1]<<1;
rgb[pos].R = pImg[2]<<1;
}
pImg += 3;
}
}
}
delete[] jpeg_pImage;
return true;
}
boolean saveBMP(PIXEL_INFO *rgb, uint32_t width, uint32_t height, uint32_t ext_width, uint32_t ext_height) {
char filename[32];
File outfile;
int x,y;
int from_x, from_y, to_y;
uint32_t pos;
digitalWrite(SPI_SD_CS, LOW);
SPI.setFrequency(16000000); // 16MHz
if (!SD.begin(SPI_SD_CS, SPI)) {
digitalWrite(SPI_SD_CS, HIGH);
return false;
}
img.BitPerPixel = 24;
img.ColorsUsed = 0;
img.Height = ext_height;
img.Width = ext_width;
img.ImageSize = img.Width * img.Height * 3;
img.DataOffset = sizeof(BMP_FORMAT);
img.FileSize = img.DataOffset + img.ImageSize;
memset(&Bmp, 0x00, sizeof(BMP_FORMAT));
memcpy(&Bmp.Signature,(uint8_t*)"BM",2);
long2byte(Bmp.FileSize, img.FileSize);
long2byte(Bmp.DataOffset, img.DataOffset);
long2byte(Bmp.Size, 40);
long2byte(Bmp.Width, img.Width);
long2byte(Bmp.Height, img.Height);
short2byte(Bmp.Planes, 1);
short2byte(Bmp.BitPerPixel, img.BitPerPixel);
long2byte(Bmp.ImageSize, img.ImageSize);
long2byte(Bmp.ColorsUsed, img.ColorsUsed);
DateTime now = rtc.now();
sprintf(filename,"/%04d%02d%02d%02d%02d%02d.bmp",now.year(),now.month(),now.day(),now.hour(),now.minute(),now.second());
if (SD.exists(filename)) SD.remove(filename);
outfile = SD.open(filename, "wb");
if(!outfile){
SD.end();
return false;
}
digitalWrite(LED_PIN, HIGH);
outfile.write((uint8_t*)&Bmp, sizeof(BMP_FORMAT));
from_x = (width - ext_width) / 2;
from_y = (height - ext_height) / 2;
to_y = from_y + ext_height;
for(y=to_y; y>from_y; y--){
pos = width * y + from_x;
outfile.write((uint8_t*)&RGBs[pos],3*ext_width);
}
outfile.close();
SD.end();
digitalWrite(SPI_SD_CS, HIGH);
digitalWrite(LED_PIN, LOW);
return true;
}
uint32_t captureJpeg(PIXEL_INFO *rgb, uint32_t *width, uint32_t *height) {
uint8_t *jpeg_data, *imgpt;
uint32_t jpeg_length, bmp_width, bmp_height, len;
myCAM.set_format(JPEG);
myCAM.InitCAM();
myCAM.OV2640_set_JPEG_size(OV2640_640x480);
spi_lock(LOCK_ARDUCAM);
for(int i=0; i<2; i++) {
myCAM.flush_fifo(); // Flush the FIFO
myCAM.clear_fifo_flag(); // Clear the capture done flag
myCAM.start_capture(); //Start capture
while(!myCAM.get_bit(ARDUCHIP_TRIG , CAP_DONE_MASK)) vTaskDelay(50);
}
jpeg_length = myCAM.read_fifo_length();
if (jpeg_length >= MAX_FIFO_SIZE) return jpeg_length; //384K
if (jpeg_length == 0 ) return jpeg_length; //0 kb
len = jpeg_length;
if ((jpeg_data = (uint8_t*)malloc(jpeg_length))) {
imgpt = jpeg_data;
// JPEG FILE FFD8~FFD9, skip First Byte 0x00
myCAM.CS_LOW();
myCAM.set_fifo_burst();
*imgpt = SPI.transfer(0x00);
*imgpt = SPI.transfer(0x00); imgpt++;
*imgpt = SPI.transfer(0x00); imgpt++;
if ( (*(imgpt-2)==0xFF)&&(*(imgpt-1)==0xD8) ) {
while (true) {
if ((len--)==0) break;
*imgpt = SPI.transfer(0x00); imgpt++;
if ((*(imgpt-2)==0xFF)&&(*(imgpt-1)==0xD9)) {
break;
}
}
}
myCAM.CS_HIGH();
} else {
spi_lock(LOCK_NONE);
return 0;
}
spi_lock(LOCK_NONE);
Jpeg2Bmp(jpeg_data, jpeg_length, RGBs, &bmp_width, &bmp_height);
*width = bmp_width;
*height = bmp_height;
free(jpeg_data);
return jpeg_length;
}
void get_ntp(int num) {
int cnt = 0;
WiFi.begin(ssid[num], password[num]);
while (WiFi.status() != WL_CONNECTED) {
if ((++cnt)==20) return;
vTaskDelay(100);
}
configTime(TIMEZONE_JST, DAYLIGHTOFFSET_JST, ntp_server1, ntp_server2);
for (cnt=0;cnt<10;++cnt) {
t = time(NULL);
if (t>TIMEZONE_JST) break;
vTaskDelay(100);
}
WiFi.disconnect();
}
void init_rtc() {
if (! rtc.begin()) {
while (1) vTaskDelay(10);
}
for (int i=0;i<wifi_cnt;++i) {
get_ntp(i);
if (t>TIMEZONE_JST) {
struct tm* tm = localtime(&t);
rtc.adjust(DateTime(tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec));
break;
}
}
rtc.start();
}
void captureBMP(uint16_t width, uint16_t height) {
uint16_t *imgpt;
uint16_t from_w, to_w, from_h, to_h;
from_h = (height - TFT_HEIGHT)/2;
to_h = from_h + TFT_HEIGHT;
from_w = (width - TFT_WIDTH)/2;
to_w = from_w + TFT_WIDTH;
spi_lock(LOCK_ARDUCAM);
myCAM.flush_fifo();
myCAM.clear_fifo_flag();
myCAM.start_capture();
while(1) {
if (myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)) {
vTaskDelay(50);
uint32_t length = 0;
length = myCAM.read_fifo_length();
if (length >= MAX_FIFO_SIZE ) {
myCAM.clear_fifo_flag();
spi_lock(LOCK_NONE);
return;
}
if (length == 0 ) { //0 kb
myCAM.clear_fifo_flag();
spi_lock(LOCK_NONE);
return;
}
myCAM.CS_LOW();
myCAM.set_fifo_burst();//Set fifo burst mode
SPI.transfer(0x00);
char VH, VL;
int y = 0, x = 0;
int col = 0;
imgpt = color565;
for (y = 0; y < height; y++) {
imgpt += TFT_HEIGHT;
for (x = 0; x < width; x++) {
VH = SPI.transfer(0x00);
VL = SPI.transfer(0x00);
if ((y >= from_h)&&(y < to_h)&&(x >= from_w)&&(x < to_w)) {
imgpt--;
*imgpt = VH|(VL<<8);
}
// delayMicroseconds(12);
}
imgpt += TFT_HEIGHT;
}
myCAM.CS_HIGH();
myCAM.clear_fifo_flag(); // Clear the capture done flag
break;
} else {
vTaskDelay(100);
}
}
spi_lock(LOCK_NONE);
}
void init_ArduCam() {
uint8_t vid, pid;
uint8_t temp;
static int init_flag = 1;
if (init_flag) {
//Reset the CPLD
myCAM.write_reg(0x07, 0x80);
vTaskDelay(10);
myCAM.write_reg(0x07, 0x00);
vTaskDelay(10);
while(1){
//Check if the ArduCAM SPI bus is OK
myCAM.write_reg(ARDUCHIP_TEST1, 0x55);
temp = myCAM.read_reg(ARDUCHIP_TEST1);
if (temp == 0x55) break;
vTaskDelay(100);
}
while(1){
myCAM.wrSensorReg8_8(0xff, 0x01);
myCAM.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
myCAM.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
if ((vid != 0x26 ) && (( pid != 0x41 ) || ( pid != 0x42 ))){
vTaskDelay(100);
continue;
}
}
init_flag = 0;
}
myCAM.OV2640_set_JPEG_size(OV2640_320x240);
myCAM.set_format(BMP);
myCAM.InitCAM();
myCAM.wrSensorReg16_8(0x3818, 0x81);
myCAM.wrSensorReg16_8(0x3621, 0xA7);
}
void setup() {
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
color565 = (uint16_t*)malloc(800*600*2);
if (!color565) while(1);
// set the CS as an output:
pinMode(SPI_ARDUCAM_CS,OUTPUT);
pinMode(SPI_SD_CS, OUTPUT);
pinMode(SPI_TFT_CS, OUTPUT);
pinMode(SPI_DC, OUTPUT);
digitalWrite(SPI_ARDUCAM_CS, HIGH);
digitalWrite(SPI_SD_CS, HIGH);
digitalWrite(SPI_TFT_CS, HIGH);
init_rtc();
SPI.begin();
init_ArduCam();
init_tft();
RGBs = (PIXEL_INFO *)malloc(640 * 480 * sizeof(PIXEL_INFO));
}
void loop() {
uint32_t width, height, length;
if ( digitalRead(BTN_PIN)==LOW ) {
if ( !writeStatus ) {
writeStatus = true;
length = captureJpeg(RGBs, &width, &height);
saveBMP(RGBs, width, height, PRN_WIDTH, PRN_HEIGHT);
init_ArduCam();
writeStatus = false;
}
} else {
captureBMP(320,240);
show();
}
}
●SPIクロック周波数の切替え
SPI通信を行っているArduCamカメラモジュール、ST7789ディスプレイ、microSDモジュールでクロック周波数を変更しています。
#define LOCK_NONE 0
#define LOCK_ARDUCAM 1
#define LOCK_ST7789 2
SPISettings settingsArduCam(8000000, MSBFIRST, SPI_MODE0);
SPISettings settingsST7789(24000000, MSBFIRST, SPI_MODE3);
void spi_lock(int lock) {
static int status = -1;
if (lock==LOCK_NONE) {
SPI.endTransaction();
status = -1;
return;
}
if (lock!=status) {
SPI.endTransaction();
if (lock==LOCK_ARDUCAM) {
SPI.beginTransaction(settingsArduCam);
} else if (lock==LOCK_ST7789) {
SPI.beginTransaction(settingsST7789);
}
status = lock;
}
}
boolean saveBMP(...) {
digitalWrite(SPI_SD_CS, LOW);
SPI.setFrequency(16000000);
if (!SD.begin(SPI_SD_CS, SPI)) {
●被写体確認用画像表示の高速化
オリジナルのArduCAM_Mini_Video_Streaming.inoでは、シリアル通信を用いてArduCamからの取得画像をロギングしています。
シリアル通信速度を配慮して、1画素毎に24マイクロ秒間隔を置いているので、
解像度320x240でストリーミングを行っている画像を処理する際には、320x240x(12/1000000)=1.84秒の遅延が発生していました。
このロギング機能の削除により、大幅に処理速度が改善されました。
for(i = 0; i < 240; i++) {
for(j = 0; j < 320; j++) {
VH = SPI.transfer(0x00);;
VL = SPI.transfer(0x00);;
Serial.write(VL);
delayMicroseconds(12);
Serial.write(VH);
delayMicroseconds(12);
}
}
↓変更
for (y = 0; y < height; y++) {
imgpt += TFT_HEIGHT;
for (x = 0; x < width; x++) {
VH = SPI.transfer(0x00);
VL = SPI.transfer(0x00);
if ((y >= from_h)&&(y < to_h)&&(x >= from_w)&&(x < to_w)) {
imgpt--;
*imgpt = VH|(VL<<8);
}
}
imgpt += TFT_HEIGHT;
}

デジカメ弐号機 第4回ストリーミングでの「おだてブタ」に対する反応速度が高速化されました。
●取得画像の安定化
被写体確認用BMP画像取得から、保存用JPEG画像取得に切り替えた際に画質が安定しないので、2回読込みを行っています。
uint32_t captureJpeg(......) {
myCAM.set_format(JPEG);
myCAM.InitCAM();
myCAM.OV2640_set_JPEG_size(OV2640_640x480);
spi_lock(LOCK_ARDUCAM);
for(int i=0; i<2; i++) {
myCAM.flush_fifo(); // Flush the FIFO
myCAM.clear_fifo_flag(); // Clear the capture done flag
myCAM.start_capture(); //Start capture
while(!myCAM.get_bit(ARDUCHIP_TRIG,CAP_DONE_MASK)) vTaskDelay(50);
}
●SDカードからの画像読込みをオンメモリーに変更・明るさ調整
picojpegライブラリでは、JPEG画像ファイルを読み込んで、BMP画像に変換するのが一般的ですが、
ここでは、オンメモリーのJPEG画像の読込みを行うようにしています。
また、撮影画像が若干暗いので、RGBそれぞれ1ビット左にシフトして明るくしています。
boolean Jpeg2Bmp(......) {
if((stream=fmemopen((void*)data,len,"rb"))==NULL) return false;
if ( JpegDec_decode(len) == -1) return;
*width = image_info.m_width;
*height = image_info.m_height;
while(JpegDec_read()){
pImg = jpeg_pImage;
for(by=0; by<jpeg_MCUHeight; by++){
for(bx=0; bx<jpeg_MCUWidth; bx++){
x = jpeg_MCUx * jpeg_MCUWidth + bx;
y = jpeg_MCUy * jpeg_MCUHeight + by;
if(x<image_info.m_width && y<image_info.m_height){
pos = x + (y * image_info.m_width);
rgb[pos].B = pImg[0]<<1;
rgb[pos].G = pImg[1]<<1;
rgb[pos].R = pImg[2]<<1;
}
pImg += 3;
}
}
}
return true;
}
●JPEG→BMP画像変換時の上下左右反転
変換後のBMP画像では上下左右が反転しているので補正します。また、正方形画像として切り出します。
切り出しサイズは、サーマルプリンターでの印刷を考慮して縦横384ドットにしています。
boolean saveBMP(.....) {
from_x = (width - ext_width) / 2;
from_y = (height - ext_height) / 2;
to_y = from_y + ext_height;
for(y=to_y; y>from_y; y--){
pos = width * y + from_x;
outfile.write((uint8_t*)&RGBs[pos],3*ext_width);
}


■参考文献
・ArduCAM Mini Cameras Tutorial
・ArduCAM/ArduCAM_ESP32S_UNO
・ArduCAM ESP32 UNO
|
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)
|