HOME | Raspberry Pi | ビジネス書籍紹介 | 2024-04-27 (Sat) Today's Access : 1053 Total : 921952. 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 立体視(ステレオグラム)
2024.04.08 自作デジカメ参号機・雑談


Arduinoで学ぶ組込みシステム入門(第2版)
●Arduinoを使って組込みシステム開発を理解する
・ハードウェアやソフトウェアなどの基礎知識/ ・設計から実装までを系統的に説明するモデルベース開発/ ・Arduinoを用いた実際の開発例

最新 使える! MATLAB 第3版
◆◆すぐに「使える!」 全ページフルカラー!◆◆
・MATLAB R2022bに対応し、解説もより詳しく!/ ・コマンド・スクリプトの例が豊富で、動かして学べる!/ ・超基本から解説。これから使いはじめる人にぴったり!/ ・全編フルカラー、スクリーンショットも豊富!

Amazon Web Services基礎からのネットワーク&サーバー構築改訂4版
1.システム構築をインフラから始めるには/ 2.ネットワークを構築する/ 3.サーバーを構築する/ 4.Webサーバーソフトをインストールする/ 5.HTTPの動きを確認する/ 6.プライベートサブネットを構築する/ 7.NATを構築する/ 8.DBを用いたブログシステムの構築/ 9.TCP/IPによる通信の仕組みを理解する

C言語は第二の母国語: 独学学生時代から企業内IT職人時代に培った、独立のための技術とノウハウ 平田豊著
学生時代から独学でプログラミングをはじめ、企業内でデバイスドライバを開発し、そして独立後もたくさんのアプリケーション開発や技術書制作に携わってきた著者。その筆者が大事に使い続ける「C言語」の“昔と今”について、気づいたことや役立つ知識、使ってきたツールなどについて、これまで記してきたことを整理してまとめました。 本書では、現役プログラマーだけでなく、これからプログラミングを学ぶ学生などにも有益な情報やノウハウを、筆者の経験を元に紹介しています。

1冊ですべて身につくJavaScript入門講座
・最初の一歩が踏み出せる! 初心者に寄り添うやさしい解説 ・最新の技術が身につく! 今のJavaScriptの書き方・使い方 ・絶対に知っておきたい! アニメーションとイベントの知識 ・プログラミングの基本から実装方法まですべて学べる

図解! Git & GitHubのツボとコツがゼッタイにわかる本
ソフトウェア開発では欠かすことのできないGit、GitHub。 これからGit、GitHubを使いたいという入門者の方でも、実際に手を動かしながら使い方を学べます。

C自作の鉄則!2023 (日経BPパソコンベストムック)
メーカー製のパソコンはスペックが中途半端で、自分が本当に欲しい機種がない――。そう思っている人には、ぜひ自作パソコンをお薦めします。自作パソコンのパーツは進化が速く、しかも驚くほど種類が豊富。価格も性能も、幅広く用意されているため、満足度100%の“自分だけの1台”を手に入れることができます。

Interface 2023年6月号
特集:第1部 フィルタ設計 基礎の基礎/ 第2部 係数アプリや波形観測アプリで合点!FIR&IIRフィルタ作り/ 第3部 配布プリント基板で体験!マイコンで動くフィルタ作り

日経Linux 2023年5月号
【特集 1】 AI時代の最強フリーソフト ~ 25のやりたいを実現! 【特集 2】 AWS、Azureのうまみを無料で体感!面倒なことはクラウドに任せよう 【特集 3】 新しいRaspberry Pi Cameraで遊んでみよう 【特集 4】 Linuxで旧型PCを復活! 1kg切るモバイルPCを「ChromeOS Flex」でChromebook化

ラズパイマガジン2022年秋号
特集:5大人気ボード 電子工作超入門
「半導体不足で在庫が不足し、電子工作のボードがなかなか買えない…」。そんな今にふさわしい特集を企画しました。5種の人気ボードにすべて対応した電子工作の入門特集です。「GPIO」や「I2C」を使った電子パーツの制御方法は、どのボードでも同じです。手に入れられたボードを使って、今こそ電子工作を始めましょう。

地方で稼ぐ! ITエンジニアのすすめ
学歴、理系の知識、専門スキル……全部なくてもITエンジニアになれる! 地方でも高収入でやりがいをもって働ける!ITエンジニアの魅力を一挙大公開

Raspberry Piのはじめ方2022
本書は、ラズパイやPicoの買い方やインストール、初期設定といった基本から、サーバー、電子工作、IoT、AIといったラズパイならではの活用方法まで、1冊でお届けします。 ラズパイをこれから始める方向けに、全36ページの入門マンガ「女子高生とラズベリーパイ」も巻末に掲載。これを読むだけでラズパイがどんなものなのか、すぐに分かって触れるようになります。

ハッカーの学校 IoTハッキングの教科書
生活にとけこみ、家電機器を便利にするIoT技術。 Webカメラなど、便利の裏側に潜むセキュリティの危険性をハッキングで検証。 専門家がパケットキャプチャからハードウェアハッキングまで、その攻撃と防御を徹底解説。 本書は2018年7月に刊行された「ハッカーの学校IoTハッキングの教科書」に一部修正を加えた第2版です。

攻撃手法を学んで防御せよ! 押さえておくべきIoTハッキング
本書は、経済産業省から2021年4月にリリースされた、IoTセキュリティを対象とした『機器のサイバーセキュリティ確保のためのセキュリティ検証の手引き』の『別冊2 機器メーカに向けた脅威分析及びセキュリティ検証の解説書』をもとに、IoT機器の開発者や品質保証の担当者が、攻撃者の視点に立ってセキュリティ検証を実践するための手法を、事例とともに詳細に解説しました。

ポチらせる文章術
販売サイト・ネット広告・メルマガ・ブログ・ホームページ・SNS… 全WEB媒体で効果バツグン! カリスマコピーライターが教える「見てもらう」「買ってもらう」「共感してもらう」すべてに効くネット文章術

プログラマーは世界をどう見ているのか 西村博之著
イーロン・マスク(テスラ)、ジェフ・べゾス(Amazon)、ラリー・ペイジ(Google)…etc. 世界のトップはなぜプログラマーなのか?

ニーア オートマタ PLAY ARTS改 <ヨルハ 二号 B型 DX版> PVC製 塗装済み可動フィギュア
「NieR:Automata」より、ヨルハ二号B型こと2BがPLAY ARTS改に新たに登場! 高級感の感じられるコスチュームや髪の質感、洗練されたボディバランス、細かなデティールに至るまでこだわり抜かれた逸品。 DX版には通常版のラインナップに加え2Bの随行支援ユニット ポッド042などをはじめ“純白の美しい太刀"白の約定やエフェクトパーツ、自爆モードを再現できる換装用ボディパーツ、シーンに合わせて変えられる顔パーツ2種も付属する豪華な仕様に。 作中のあらゆるシーンを再現することが可能なファン必見の一品となっている。

Newtonライト2.0 ベイズ統計
ベイズ統計は,結果から原因を推定する統計学です。AIや医療などの幅広い分野で応用されています。その基礎となるのは18世紀に考えだされた「ベイズの定理」です。 この本では,ベイズ統計学のきほんをやさしく紹介していきます。

白光(HAKKO) ダイヤル式温度制御はんだ吸取器 ハンディタイプ FR301-81


無水エタノールP 500mlx2個パック(掃除)


ケイバ(KEIBA) マイクロニッパー MN-A04


サンハヤト SAD-101 ニューブレッドボード


白光(HAKKO) HEXSOL 巻はんだ 精密プリント基板用 150g FS402-02


[Amazon限定ブランド]【指定第2類医薬品】PHARMA CHOICE 解熱鎮痛薬 解熱鎮痛錠IP 100錠


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