HOME | Raspberry Pi | ビジネス書籍紹介 | 2026-01-04 (Sun) Today's Access : 315 Total : 1261013. Since 10 Sep. 2019

デジカメ弐号機 第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×240153,600
352×288202,752
640×480614,400
フレームバッファーサイズは384Kバイトなので、BMP(COLOR565)形式のストリーミングで取得できる画像は解像度352×288ピクセルまでです。

●JPEG形式によるキャプチャリング
解像度JPEG画像BMP変換後PSRAM
320×240 7,172 230,400237,572
640×480 13,317 921,600934,917
800×60018,4361,440,0001,458,436
1280×1024(960)50,1813,686,4003,736,581
1600×1200 74,7575,760,0005,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プロセッサを搭載したシングルボードコンピュータ。イギリスのラズベリーパイ財団によって開発されている。
2020.05.28 画像処理 第1回トイカメラ
2020.06.09 画像処理 第2回カメラモジュール制御
2020.06.28 画像処理 第3回リアルタイムクロック
2020.07.08 画像処理 第4回電源回路
2020.10.27 画像処理 第5回自作デジカメ初号機完成
2020.11.10 画像処理 第6回ドーナツデジカメ
2021.05.16 画像処理・基本変換
2021.07.10 M5Stackアプリの移植
2022.04.04 減色処理 雑談
2022.04.18 減色処理 均等量子化法とK平均法
2022.05.04 減色処理 グレースケール・二値化
2022.05.18 減色処理 二値化画像印刷
2022.08.12 京セラ feelH” Treva カメラ
2022.10.15 デジカメ弐号機 1.仕様変更
2022.10.21 デジカメ弐号機 2.SDカードとRTC
2022.11.04 デジカメ弐号機 3.SPI DISPLAY
2022.11.18 デジカメ弐号機 4.ストリーミング
2022.12.02 デジカメ弐号機 5.機能統合
2022.12.17 デジカメ弐号機 6.完成
2023.05.15 アナログ風ゲージ
2023.06.01 ADS1115デジタル電圧計
2023.10.01 立体視(ステレオグラム)
2023.10.16 漢字フォントの表示と拡張
2024.04.08 自作デジカメ参号機・雑談
2024.08.24 シリアルカメラ
2025.07.23 C3.jsによるグラフ描画
2025.09.09 東芝デジタルカメラユニット DMR-C1
2025.10.10 AQM1248A小型液晶ボード
2025.10.11 Monochrome OLED
2025.12.31 キャラクターディスプレイで遊ぶ

たいていのことは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)

Copyright © 2011-2027 Sarako Tsukiyono All rights reserved®.