SSブログ
前の10件 | -

ATtiny202 正弦波のノイズ問題 TCAで解決! [Arduino]

ATtiny202 正弦波のノイズ問題が解決できました。
Timer/Counter Type A (TCA) は megaTinyCore のデフォルトで millis()/micros() に使われていたため、今まで手を出さなかったのですが、TCBにはない、バッファー付きの PER の PERBUF や、バッファー付きの CMPn の CMPnBUF を使わないとなんともならないようでした。

そこで、以下のサンプルプログラムとデータシートを見ながら作ってみました。

Getting Started with TCA
https://www.microchip.com/content/dam/mchp/documents/MCU08/ApplicationNotes/ApplicationNotes/TB3217-Getting-Started-with-TCA-DS90003217.pdf

cmp0buf.png

★はまったところ
はまったところは、megaTinyCore特有のところでした。
・PORTMUXの代替ピンが使用されていた
   PA3 (megaTinyCore 4番ピン) → PA7 (megaTinyCore 1番ピン)
・TCAの Split Mode が使用されていた
   いくら PERBUF や CMP0BUF を変更しても値が変わらない、、
   なぜなら、Split Mode にはこれらがないから。
   TCA0.SINGLE.CTRLD = 0; でSplit Mode から Normal Mode に戻す必要があった。
   この設定は 「millis()/micros() Timer;"Disabled(save flash)"」にしても有効。
   megaTinyCoreでmillis()/micros()は使わないけど、PWMには使うから。

megaTinyCore_megaavr_extras_TakingOverTCA0.md at master · SpenceKonde_megaTinyCore · GitHub
https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/TakingOverTCA0.md

以下、スケッチ。millis()/micros() の割り込みは解除してあります。

//  megaTinyCore(mTC) ATtiny202/212/402/412 Sine Wave with TCA
#include <avr/io.h>
#include <util/delay.h>

static const uint16_t OCTAVE9[] = { 7023, 7441, 7883, 8352, 8848, 9375, 9932, 10523,11148,11811,12513,13258 };
static const uint8_t  SINE256[] = {
    0,  0,  0,  0,  1,  1,  1,  2,  2,  3,  4,  5,  5,  6,  7,  9,  10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35,
   37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,  79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124,
  128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
  218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
  255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
  218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
  128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82,  79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40,
   37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11,  10,  9,  7,  6,  5,  5,  4,  3,  2,  2,  1,  1,  1,  0,  0,  0 };

void setup(){           // Register Settings
  PORTA.DIRSET    = PIN7_bm;    // PA7 OUTPUT (mTC:1)
  //PORTMUX.CTRLC = 0;          // turn off TCA port multiplexer (PA7(mTC:1) to default PA3(mTC:4))
  TCA0.SINGLE.CTRLD   = 0;      // turn off split mode
  TCA0.SINGLE.CTRLC   = 0;      // PWM output pins override - disable
  TCA0.SINGLE.CTRLB   = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
  TCA0.SINGLE.INTCTRL = 0;      // turn off Arduino(mTC) time system
  TCA0.SINGLE.PERBUF  = 0xff;   // top value = 255
  TCA0.SINGLE.CMP0BUF = 0;      // output value
  TCA0.SINGLE.CTRLA   = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; // 20MHz / 1 / 256 -> 78,125Hz
}

void loop(){
  sineWave( 60,  500);          // C4
  sineWave( 64,  500);          // E4
  sineWave( 67,  500);          // G4
  sineWave( 72, 2000);          // C5
  _delay_ms(1000);
}

void sineWave(uint8_t midiNum, uint16_t msDuration) {
  uint16_t i, di = OCTAVE9[ midiNum % 12 ] >> (10 - midiNum / 12);  // 256 times the Wave Table subscript to advance in one cycle
  uint32_t cycDuration = F_CPU / 256 / 1000 * msDuration;           // Convert duration to number of cycles
  do {
    TCA0.SINGLE.CMP0BUF = SINE256[ (i += di) >> 8 ];        // 8bit PWM (78,125Hz)
      while( !(TCA0.SINGLE.INTFLAGS & TCA_SINGLE_OVF_bm) ); // Waiting for TCA0 overflow
      TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;             // cleared by writing a '1'
  } while( --cycDuration );                                 // Exit if note data is 0
  TCA0.SINGLE.CMP0BUF = 0;                                  // Set output to 0
}

nice!(0)  コメント(0) 

よくある安いI2C OLEDについて調べてみた。 [Arduino]

以下3つのよくあるI2CのOLEDだけど、いろいろ調べたらほぼ同じ操作でいけると判明。

・0.96インチ , 128x64 , SSD1306
・1.3インチ, 128x64, SH1106
・1.5インチ, 128x128, SH1107

3oleds_qr.jpg

★共通なところ
・5VのArduinoで、プルアップ抵抗なしで動く。
・アドレスが 0x78 (Wireライブラリでは 0x3C と1ビットずらす)
・画像メモリーの使い方、縦8ドットで1バイトが横に128個。これが縦に8ページ(あるいは16ページ)
・コマンドも大体同じっぽい

★それぞれで違うところ
*** 0.96'', 128x64 , SSD1306 ***
 初期化コマンドは、0x8D, 0x14 の Enable charge pump と、0xAF の switch on OLEDの2つだけでとりあえず表示できる。
 メモリがページをまたいでリニアになっているので、自然に折り返しできる。
 ピンは GND VCC SCL SDA の順。
*** 1.3'', 128x64, SH1106 ***
 初期化コマンドは、0xAF だけでとりあえずOK。
 メモリはページをまたげない。
 制御チップは132x64ドットでできているようだが、OLEDは128x64でできているので左右に2ドットずつ表示されない領域がある。
 ピンは VCC GND SCL SDA の順。電源が0.96インチと逆で間違えると壊れるので注意。
*** 1.5'', 128x128, SH1107 ***
 こちらも初期化コマンドは、0xAF だけでとりあえずOK。
 同様にメモリはページをまたげない。
 ピンは VCC GND SCL SDA の順。電源注意。
 これはGME128128-01-IIC Ver 2.0での話。Ver 3は SSD1327 ?

// tinyOLEDdemo - controlling an I²C OLED (SSD1306/SH1106/SH1107) with an ATtiny202
//
// A big thank you to Stefan Wagner 
// Project Files (Github):  https://github.com/wagiminator
// License: http://creativecommons.org/licenses/by-sa/3.0/

#include <avr/io.h>
#include <util/delay.h>

const uint8_t img_i[] = {   32,   32, // px
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x82, 0xee, 0xfe, 0xfe, 0xfc, 0xf8, 0x70, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0,
  0xf8, 0x7e, 0xff, 0xff, 0xef, 0xc7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x20, 0x20, 0x30, 0x10, 0x18, 0x1c, 0x0e, 0x0f, 0x07, 0x07, 0x03, 0x01, 0x01,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x1e, 0x3f, 0x7f, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const uint8_t img_qr[] = {  23,   23, // px
  0xff, 0x01, 0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0x2b, 0xcb, 0x71, 0xdf, 0x01, 0xff, 0x01,
  0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0xff, 0x59, 0x55, 0x7d, 0x71, 0x49, 0x67, 0x55, 0xf9,
  0x3d, 0xc2, 0x8b, 0x57, 0x91, 0x3d, 0x77, 0xc9, 0x27, 0xb9, 0x93, 0x71, 0xf7, 0xff, 0xff, 0xc0,
  0xdf, 0xd1, 0xd1, 0xd1, 0xdf, 0xc0, 0xff, 0xc1, 0xf3, 0xfa, 0xcd, 0xd5, 0xc8, 0xc5, 0xea, 0xe6,
  0xe8, 0xd8, 0xcb, 0xec, 0xff
};
// ---- I2C Master Implementation (Write only) ---------------------------------

#define I2C_FREQ  800000UL                        // I2C clock frequency in Hz
void I2C_init(void) {                             // I2C init function
  TWI0.MBAUD   = ((F_CPU / I2C_FREQ) - 10) / 2;   //   set TWI master BAUD rate (simplified BAUD calculation)
  TWI0.MCTRLA  = TWI_ENABLE_bm;                   //   enable TWI master
  TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;            //   set bus state to idle
}
void I2C_start(uint8_t addr) {                    // I2C start transmission
  TWI0.MADDR = addr;                              //   start sending address
}
void I2C_stop(void) {                             // I2C stop transmission
  while (~TWI0.MSTATUS & TWI_WIF_bm);             //   wait for last transfer to complete
  TWI0.MCTRLB = TWI_MCMD_STOP_gc;                 //   send stop condition
}
void I2C_write(uint8_t data) {                    // I2C transmit one data byte to the slave, ignore ACK bit
  while (~TWI0.MSTATUS & TWI_WIF_bm);             //   wait for last transfer to complete
  TWI0.MDATA = data;                              //   start sending data byte 
}

// ---- OLED Implementation ----------------------------------------------------

#define OLED_ADDR       0x78             // OLED write address (Wire lib.:0x3c)
#define OLED_CMD_MODE   0x00             // set command mode
#define OLED_DAT_MODE   0x40             // set data mode

const uint8_t OLED_INIT_CMD[] = {        // OLED init settings
//0xA8, 0x7F,           // set multiplex (HEIGHT-1): 0x1F(128x32), 0x3F(128x64), 0x7F(128x128)
//0x22, 0x00, 0x0f,     // set min and max page: 0x07(128x64), 0x0F(128x128)
//0x20, 0x00,           // set horizontal memory addressing mode
//0xDA, 0x12,           // set COM pins hardware configuration to alternative
  0x8D, 0x14,           // enable charge pump (for SSD1306)
  0xAF                  // switch on OLED
};

void OLED_init(void) {                    // OLED init function
  I2C_init();                             //   initialize I2C first
  I2C_start(OLED_ADDR);                   //   start transmission to OLED
  I2C_write(OLED_CMD_MODE);               //   set command mode
  for (uint8_t i=0; i<sizeof(OLED_INIT_CMD); i++) I2C_write(OLED_INIT_CMD[i]); //   send the command bytes
  I2C_stop();                             //   stop transmission
}

void OLED_cursor(uint8_t xpos, uint8_t ypos) {// OLED set the cursor
  xpos +=2;                               //  (for SH1106 (132x64)
  I2C_start(OLED_ADDR);                   //   start transmission to OLED
  I2C_write(OLED_CMD_MODE);               //   set command mode
  I2C_write(xpos & 0x0F);                 //   set low nibble of start column
  I2C_write(0x10 | (xpos >> 4));          //   set high nibble of start column
  I2C_write(0xB0 | (ypos & 0x0f));        //   set start page
  I2C_stop();                             //   stop transmission
}

void OLED_clear(uint16_t p) {             // OLED clear screen with pattern
  if( p < 256 ) p |= p << 8;
  for(uint8_t pg=0; pg<16; pg++){
    OLED_cursor(0, pg);                   //   set cursor at upper left corner
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t c=64; c; c--) {
      I2C_write(p & 0xff);                //   fill the screen
      I2C_write(p >> 8  );
    }
    I2C_stop();                           //   stop transmission
  }
}

void OLED_image(uint8_t xpos, uint8_t ypos, uint8_t *d) {// OLED set image
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0; y<(h+7)/8; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t x=0; x<w; x++) I2C_write( *d++ );
    I2C_stop();                           //   stop transmission    
  }
}

void OLED_image2x(uint8_t xpos, uint8_t ypos, uint8_t *d) {// OLED set 2x image
  const uint8_t x2[]={ 0x0,0x3,0xc,0xf,0x30,0x33,0x3c,0x3f,0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff };
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0; y<(h+3)/4; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t x=0, t; x<w; x++) {
      I2C_write( t = x2[ d[x+(y/2)*w] >> (y%2*4) & 0xf ] );
      I2C_write( t );
    }
    I2C_stop();                           //   stop transmission    
  }
}

void OLED_image4x(uint8_t xpos, uint8_t ypos, uint8_t *d) {// OLED set 2x image
  const uint8_t x4[]={ 0x0,0xf,0xf0,0xff };
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0; y<(h+1)/2; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t x=0, t; x<w; x++) {
      I2C_write( t = x4[ d[x+(y/4)*w] >> (y%4*2) & 0b11 ] );
      I2C_write( t );
      I2C_write( t );
      I2C_write( t );
    }
    I2C_stop();                           //   stop transmission    
  }
}

// ---- Main Function ----------------------------------------------------------

void setup() {
  _delay_ms(100);                         // add delay
  OLED_init();                            // setup I2C OLED
}

void loop() {
  OLED_clear(0x1144);                     // clear screen with pattern
  OLED_image( 48, 2, img_i );
  _delay_ms(1000);
  OLED_image2x( 32, 1, img_i );
  _delay_ms(1000);
  OLED_image4x(  0, 0, img_i );
  _delay_ms(2000);
  OLED_clear(0xff);                       // clear screen with white
  OLED_image( 52, 2, img_qr );
  _delay_ms(1000);
  OLED_image2x( 41, 1, img_qr );
  _delay_ms(1000);
  OLED_image4x( 18, 1, img_qr );
  _delay_ms(4000);
}


nice!(0)  コメント(0) 

ATtiny202 正弦波のノイズについて調べてみた。 [Arduino]

以前、当ブログで、

202duinoで正弦波 → ノイズがのる → ATtiny412でDAC使用:放課後マイコンクラブ:SSブログ
https://hello-world.blog.ss-blog.jp/2023-06-10

というので、PWMでの疑似DACをあきらめ、1シリーズのDACでしのぎましたが、こちらの方もATtiny202のPWMでノイズが出たというのを確認しました。

ATtiny202で正弦波を作ったらノイズが入る
https://www.jh4vaj.com/archives/39505

正弦波の下り坂でノイズが入っています。
つまり、TCB0.CCMPH を減らしたときです。
いろいろ想像して、確認して、原因がわかりました。

簡単にいうと、、、

ランナー(カウンタ)が、ゴール(TCB0.CCMPH)に向かって走っているときに、ゴールが先になる場合(TCB0.CCMPHが増える場合)は問題なし。
ゴールした後に変更しても、すでに出力はLOWになっているので大きな問題はない。
だけど、ゴール(TCB0.CCMPH)が手前に来る場合(TCB0.CCMPHが減る場合)は注意。
ゴールした後(カウンタがTCB0.CCMPHを過ぎた後)にゴールを変更しても出力はLOWのままで問題なし。
あるいは、走者がゴールする前で、変更後のゴールも走者の前のときもHIGHのままで問題なし。
問題なのは、まだ走者がゴールしていないのに(カウンタがTCB0.CCMPHに達していないのに)、走者よりも後ろにゴールを設定してしまったとき(TCB0.CCMPHをカウンタよりも少ない値にしたとき)です。出力がHIGHのまま、カウンタはTOP値まで行ってしまいます。

202_tcb8bitpwm.png

ということで正弦波のとき限定のパッチを当ててみました。
なぜ正弦波限定なのかというと、正弦波は連続性があり前の値と大きく変わることがない(と思われる)からです。(高い音だとサンプリングの関係で値がとぶと思う。)
新しいループに入ったときに、ゴールの変更はすぐにでもできるのですが、ゴールが近い(TCB0.CCMPHの値が小さい)ときは、上記の事故がおこりうるので、ゴールした後(カウンタがTCB0.CCMPHを過ぎた後)にゴールを変更(TCB0.CCMPHを変更)すればいいということで、適当にウェイトをいれてみたところノイズのない正弦波になりました。

TCB0.CCMPHの更新タイミングが、カウンタが0になったときではなく、即時行われるので起こる事故(ノイズ)だったようです。

原因はわかりましたが、一般的な解決法はまだ思いつきません。

//  megaTinyCore ATtiny202/212/402/412 Sine Wave - PWM version
#include <util/delay_basic.h>

static const uint16_t OCTAVE9[] = { 7023, 7441, 7883, 8352, 8848, 9375, 9932, 10523,11148,11811,12513,13258 };
static const uint8_t  SINE256[] = {
    0,  0,  0,  0,  1,  1,  1,  2,  2,  3,  4,  5,  5,  6,  7,  9,  10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35,
   37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,  79, 82, 85, 88, 90, 93, 97,100,103,106,109,112,115,118,121,124,
  128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
  218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
  255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
  218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
  128,124,121,118,115,112,109,106,103,100, 97, 93, 90, 88, 85, 82,  79, 76, 73, 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40,
   37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11,  10,  9,  7,  6,  5,  5,  4,  3,  2,  2,  1,  1,  1,  0,  0,  0 };

void setup(){                   // Register Settings
  TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm;   // 20MHz / 1 / 256 -> 78,125Hz
  TCB0.CTRLB = TCB_CNTMODE_PWM8_gc   | TCB_CCMPEN_bm;   // 8-Bit PWM mode, Output Enable (WO,PA6,megaTinyCore:P0)
  TCB0.CCMPL = 0xff;                                    // top value = 255
  TCB0.CCMPH = 0;                                       // output value
}

void loop(){
  sineWave( 60,  500);          // C4
  sineWave( 64,  500);          // E4
  sineWave( 67,  500);          // G4
  sineWave( 72, 2000);          // C5
  delay(1000);
}

void sineWave(uint8_t midiNum, uint16_t msDuration) {
  uint16_t i, di = OCTAVE9[ midiNum % 12 ] >> (10 - midiNum / 12);  // 256 times the Wave Table subscript to advance in one cycle
  uint32_t cycDuration = F_CPU / 256 / 1000 * msDuration;           // Convert duration to number of cycles
  static uint8_t pwNew, pwNow;
  do {
    pwNew = SINE256[ ( (i += di) >> 8 ) ];
    if( pwNew < pwNow && pwNow <120) _delay_loop_1(40); // <== Noise Reduction (wait 3x40=120cycle)
    TCB0.CCMPH = pwNow = pwNew;                         // 8bit PWM (78,125Hz)
    while( !TCB0.INTFLAGS );                            // Waiting for TCB0 overflow (every 12.8usec(78,125Hz))
    TCB0.INTFLAGS = TCB_CAPT_bm;                        // cleared by writing a '1'
  } while( --cycDuration );                             // Exit if note data is 0
  TCB0.CCMPH = 0;                                       // Set output to 0
}


nice!(0)  コメント(0) 

202duino の SPI で NeoPixel [Arduino]

NTSCビデオ信号のドット幅がなかなかそろわず、SPIに手を出そうと、、。
SPIでデータを送る練習としてNeoPixelで実験。

以前に作ったのと同様にNeoPixel棒を光らせられました。
202spi_neopixel.jpg
202duino で NeoPixel [Arduino]
https://hello-world.blog.ss-blog.jp/2023-07-15

スケッチサイズは582バイトと軽量に。

// ATtiny202 SPI NeoPixel test (20MHz)

#define T0        (0b11100000)  // T0H : 300ns @ 20MHz CLK2X DIV4 
#define T1        (0b11111100)  // T1H : 600ns @ 20MHz CLK2X DIV4
#define NUMPIXELS (12)

uint8_t pixels[NUMPIXELS * 3];  // GRBGRBGRB...

void setup() {
  PORTA.DIRSET = _BV(1);        // PA1(MOSI) OUTPUT (megaTinyCore:D2) 
  SPI0.CTRLA   = SPI_MASTER_bm | SPI_CLK2X_bm | SPI_PRESC_DIV4_gc | SPI_ENABLE_bm;
  SPI0.CTRLB   = SPI_BUFEN_bm | SPI_BUFWR_bm | SPI_SSD_bm | SPI_MODE_1_gc;
}

void loop() {
  for (uint8_t i = 0; i < NUMPIXELS; i++) {
    for (uint8_t c = 0; c < 6; c++) {
        pixels[ ((NUMPIXELS + i - c) * 3 + 2) % (NUMPIXELS * 3) ] = 31 >> c;
    }
    neoPixelShow();
    delay(100);
  }
}

void neoPixelShow() {
  for(uint8_t i = 0; i < NUMPIXELS * 3; i++) {
    for(uint8_t bm = 0b10000000; bm; bm >>= 1) {
      SPI0.DATA = (pixels[i] & bm) ? T1 : T0;
      while( !(SPI0.INTFLAGS & SPI_DREIF_bm) );
    }
  }
}

WS2812Bのデータシートには、

Data transfar Time
 0 : High 220- 380ns / Low 580-1000ns
 1 : High 580-1000ns / Low 580-1000ns
ってあるけと、どうやら Highの時間の長短で0/1を決めているみたいで、Lowの時間は厳密に守らなくてもいいみたい。

問題点は、ATtiny202は実質の有効ピンが5ピンにも関わらず、SPIを使うと、MOSIの他、MISO、SCK も使ってしまうので、他に使える残りのピンが2本になってしまい、あまり実用的ではないかも。
というわけで、NTSCビデオ計画はちょっと保留。
nice!(0)  コメント(0) 

ATtiny412でNTSCビデオ信号 [Arduino]

NTSCでのビデオ出力。
いろいろ調べてみたけど、結構たいへん。

1.情報収集
小さなカラーLCDなども安価になってきていて、いまどきマイコンでNTSCでのビデオ出力する人が少なくなっている。記事をさぐると10年くらい前のものが多く、リンク切れも多数。
カラー、ノンインターレスの説明は見つかるものの、基本のモノクロとノンインターレスの情報が少なめ。PICでの情報が多めな感じ。

2.ソフトウェア
全部あるいは部分的にアセンブラを使わないといけなかったり、SPIを出力につかったり。
とくにカラーは超高難度のよう。
別ICと組み合わせが必要なものも。

3.ハードウェア
簡単なものだと同期信号と画像信号と2本の抵抗を介して出力。
外部クロックを使用しないと画像が揺らぐらしい。


そこで、C言語だけで、tinyAVR 1シリーズのDAC機能を使って、コネクタ以外の外付けパーツなしでビデオ出力できるかやってみました。
ntsc_tiny412.jpg

とりあえずできたけど、ウェイトなしで横16ドットしか表示できていない。
もうちょっとなんとかなりそうな気もする。
cap_ntsc_i_412.jpg

// ATtiny412 NTSC test (20MHz)
#define   IRE_p100  (59)        // = 255 * 1.0V/4.34V * (40+100)/140
#define   IRE_p50   (38)        // = 255 * 1.0V/4.34V * (40+ 50)/140
#define   IRE_0     (17)        // = 255 * 1.0V/4.34V *  40     /140
#define   IRE_m40   (0)

uint8_t const img_i[] = {
  0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,
  0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,
  0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,
  0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
  0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,
  0,0,0,0,1,1,1,0,0,1,1,0,0,0,0,0,
  0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,
  0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

void setup(){                           // Register Settings
  // VIDEO OUTPUT
    VREF.CTRLA |= VREF_DAC0REFSEL_4V34_gc;              // Voltage reference at 4.34V
    VREF.CTRLB |= VREF_DAC0REFEN_bm;                    // DAC0/AC0 reference enable: enabled
    delayMicroseconds(25);                              // Wait VREF start-up time
    PORTA.PIN6CTRL &= ~PORT_ISC_gm;                     // Disable digital input buffer
    PORTA.PIN6CTRL |=  PORT_ISC_INPUT_DISABLE_gc;
    PORTA.PIN6CTRL &= ~PORT_PULLUPEN_bm;                // Disable pull-up resistor
    DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm;          // Enable DAC, Output Buffer
  // H-SYNC
    TCB0.CTRLA  = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm;// 20MHz / 1 / 1270 -> 15,750Hz
    TCB0.CTRLB  = TCB_CNTMODE_INT_gc;                   // periodic Interrupt mode
    TCB0_CCMP   = 1269;                                 // top value
    TCB0.INTCTRL= TCB_CAPT_bm;                          // Capture Interrupt Enable
}

void loop() {
}

ISR(TCB0_INT_vect) {
  static uint16_t scan, x, y, offset;
  DAC0.DATA = IRE_0;                                    // start from -3.2usec
  scan++;
  while(TCB0_CNT <  94);                                // front porch (20MHz*(3.2+1.5    )usec=94)
  DAC0.DATA = IRE_m40;
  if(scan > 3  && scan < 7)   goto END;
  while(TCB0_CNT < 188);                                // back porch  (20MHz*(3.2+1.5+4.7)usec=188)
  DAC0.DATA = IRE_0; 
  if(scan < 48 || scan > 239) goto END;                 // 192 lines
  y = (scan - 48) / 12;                                 // y = 0..15
  offset= y<<4;
  while(TCB0_CNT < 282);                                // H-blanking  (20MHz*(3.2+10.9   )usec=282)
  MAIN:
  for(x=0; x<16; x++) {
    DAC0.DATA = (img_i[x + offset]) ? IRE_p100 : IRE_0;
  }
  DAC0.DATA = IRE_0; 
  END:
  if(scan == 262) scan = 0;
  TCB0.INTFLAGS= TCB_CAPT_bm;                           // Clear Capture Interrupt Flag
}


[2023/11/02]
その後、ちょっと改善
・割り算をなくした
・16ビットでなくてもいい変数は8ビットに
・データを1ドットあたり1byte → 1bitとした
・for文を使わず、配列の添え字を変数にせず、すべて展開
  (横1ドットにつきプログラム1行)
などで、横128ドットまでは行きついた。
同期をなんとかせねば。
cap_ntsc_i_412_128.jpg

[2023/11/03]
ジッター(揺らぎ)の原因がプログラムの問題なのか、クリスタルの有無の問題なのか、SYNCとVIDEOと2本の抵抗を使ってATmega328のArduinoに移植してみた。
1ドット毎の幅が違うから右端がそろわないけど、くっきりになった。
cap_ntsc_i_412_m328p.jpg
やっぱりクリスタルがないとダメってことか。

[2023/11/03]
いろいろ修正しながらATtint202でやってみた。
ジッター(揺らぎ)はあるものの、くっきりはした。エッジがぼやけるのはDACのせいだったらしい。
cap_ntsc_i_202.jpg
もうちょっと形になったらブログに上げたい。

nice!(0)  コメント(0) 

I2C OLED (SSD1306)に画像を出す。Wire版 [Arduino]

Wireライブラリー + delay() を使って一般的なArduinoでだいたい動くようにしました。

・ATtiny 412 (megaTinyCore)
・UNO R1 (ATmega328)
・UNO R4 MINIMA (RA4M1)
・RL78/G15 Fast Prototyping Board

で動くことを確認しました。
ATyiny202には 2269バイトとフラッシュメモリ 2kByteをオーバーしてしまい不可でした。

こんな感じで、、
ssd1306_r4_1.jpg
ブレッドボードなしでつないでみた。
ssd1306_r4_2.jpg

// tinyOLEDdemo - controlling an I²C OLED (SSD1306)
#include <Wire.h>

uint8_t const img_i[] = {   32,   32, // px
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x82, 0xee, 0xfe, 0xfe, 0xfc, 0xf8, 0x70, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0,
  0xf8, 0x7e, 0xff, 0xff, 0xef, 0xc7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x20, 0x20, 0x30, 0x10, 0x18, 0x1c, 0x0e, 0x0f, 0x07, 0x07, 0x03, 0x01, 0x01,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x1e, 0x3f, 0x7f, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t const img_qr[] = {  23,   23, // px
  0xff, 0x01, 0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0x2b, 0xcb, 0x71, 0xdf, 0x01, 0xff, 0x01,
  0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0xff, 0x59, 0x55, 0x7d, 0x71, 0x49, 0x67, 0x55, 0xf9,
  0x3d, 0xc2, 0x8b, 0x57, 0x91, 0x3d, 0x77, 0xc9, 0x27, 0xb9, 0x93, 0x71, 0xf7, 0xff, 0xff, 0xc0,
  0xdf, 0xd1, 0xd1, 0xd1, 0xdf, 0xc0, 0xff, 0xc1, 0xf3, 0xfa, 0xcd, 0xd5, 0xc8, 0xc5, 0xea, 0xe6,
  0xe8, 0xd8, 0xcb, 0xec, 0xff
};

// ---- OLED Implementation ----------------------------------------------------

#define OLED_ADDR       0x3c             // OLED write address (Wire lib.:0x3c)
#define OLED_CMD_MODE   0x00             // set command mode
#define OLED_DAT_MODE   0x40             // set data mode

const uint8_t OLED_INIT_CMD[] = {        // OLED init settings
  0xA8, 0x3F,           // set multiplex (HEIGHT-1): 0x1F for 128x32, 0x3F for 128x64
  0x22, 0x00, 0x07,     // set min and max page                      
  0x20, 0x00,           // set horizontal memory addressing mode
  0xDA, 0x12,           // set COM pins hardware configuration to alternative
  0x8D, 0x14,           // enable charge pump
  0xAF                  // switch on OLED
};

void OLED_init(void) {                    // OLED init function
  Wire.begin();                           //   initialize I2C first
  Wire.beginTransmission(OLED_ADDR);      //   start transmission to OLED
  Wire.write(OLED_CMD_MODE);              //   set command mode
  for (uint8_t i=0; i<sizeof(OLED_INIT_CMD); i++) Wire.write(OLED_INIT_CMD[i]); // send the command bytes
  Wire.endTransmission();                 //   stop transmission
}

void OLED_cursor(uint8_t xpos, uint8_t ypos) {// OLED set the cursor
  Wire.beginTransmission(OLED_ADDR);      //   start transmission to OLED
  Wire.write(OLED_CMD_MODE);              //   set command mode
  Wire.write(xpos & 0x0F);                //   set low nibble of start column
  Wire.write(0x10 | (xpos >> 4));         //   set high nibble of start column
  Wire.write(0xB0 | (ypos & 0x07));       //   set start page
  Wire.endTransmission();                 //   stop transmission
}

void OLED_clear(uint8_t p) {              // OLED clear screen with pattern
  OLED_cursor(0, 0);                      //   set cursor at upper left corner
  for(uint8_t j=1024/16; j; j--) {
    Wire.beginTransmission(OLED_ADDR);    //   start transmission to OLED
    Wire.write(OLED_DAT_MODE);            //   set data mode
    for(uint8_t i=16; i; i--) Wire.write(p);// clear the screen with p
    Wire.endTransmission();               //   stop transmission
  }
}

void OLED_image(uint8_t xpos, uint8_t ypos, const uint8_t *d) {// OLED set image
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0, i=0; y<(h+7)/8; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    for(uint8_t x=0; x<w; x++) {
      if( !i ) {
        Wire.beginTransmission(OLED_ADDR);//   start transmission to OLED
        Wire.write(OLED_DAT_MODE);        //   set data mode
      }
      Wire.write( *d++ );
      if( ++i==31 || w-x==1 ) {
        Wire.endTransmission();           //   stop transmission
        i=0;
      }
    }
  }
}

void OLED_image2x(uint8_t xpos, uint8_t ypos, const uint8_t *d) {// OLED set 2x image
  const uint8_t x2[]={ 0x0,0x3,0xc,0xf,0x30,0x33,0x3c,0x3f,0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff };
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0, i=0; y<(h+3)/4; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    for(uint8_t x=0, t; x<w; x++) {
      if( !i ) {
        Wire.beginTransmission(OLED_ADDR);//   start transmission to OLED
        Wire.write(OLED_DAT_MODE);        //   set data mode
      }
      Wire.write( t = x2[ d[x+(y/2)*w] >> (y%2*4) & 0xf ] );
      Wire.write( t );
      if( (i+=2)>29 || w-x==1 ) {
        Wire.endTransmission();           //   stop transmission
        i=0;
      }
    }
  }
}

// ---- Main Function ----------------------------------------------------------

void setup() {
  delay(100);                             // add delay
  OLED_init();                            // setup I2C OLED
}

void loop() {
  OLED_clear(0x00);                       // clear screen with black
  OLED_image( 48, 2, img_i );
  delay(1000);
  OLED_image2x( 32, 0, img_i );
  delay(2000);
  OLED_clear(0xff);                       // clear screen with white
  OLED_image( 52, 2, img_qr );
  delay(1000);
  OLED_image2x( 40, 1, img_qr );
  delay(4000);
}


タグ:OLED SSD1306 I2C
nice!(0)  コメント(0) 

I2C OLED (SSD1306)に画像を出す。ATtiny202版 [Arduino]

ATtiny202でI2C OLEDに画像を出すスケッチをつくってみた。

GitHub - wagiminator_ATtiny13-TinyOLEDdemo I²C OLED on an ATtiny10_13_202
https://github.com/wagiminator/ATtiny13-TinyOLEDdemo

これを参考に、、といっても、データを流すだけで画像が出るようにデータ配列に変換してあるので大したことはしてません。
そこで、2倍に拡大した画像も出せるようにしてみました。
ssd1306_i.jpg
ややジャギーが気になるけど、、

ssd1306_qr.jpg
QRコードなら問題なし。

スケッチも1247バイトと軽量。
// tinyOLEDdemo - controlling an I²C OLED (SSD1306) with an ATtiny202
//
// A big thank you to Stefan Wagner 
// Project Files (Github):  https://github.com/wagiminator
// License: http://creativecommons.org/licenses/by-sa/3.0/

#include <avr/io.h>
#include <util/delay.h>

const uint8_t img_i[] = {   32,   32, // px
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x82, 0xee, 0xfe, 0xfe, 0xfc, 0xf8, 0x70, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0,
  0xf8, 0x7e, 0xff, 0xff, 0xef, 0xc7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x20, 0x20, 0x30, 0x10, 0x18, 0x1c, 0x0e, 0x0f, 0x07, 0x07, 0x03, 0x01, 0x01,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x1e, 0x3f, 0x7f, 0x3f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const uint8_t img_qr[] = {  23,   23, // px
  0xff, 0x01, 0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0x2b, 0xcb, 0x71, 0xdf, 0x01, 0xff, 0x01,
  0x7d, 0x45, 0x45, 0x45, 0x7d, 0x01, 0xff, 0xff, 0x59, 0x55, 0x7d, 0x71, 0x49, 0x67, 0x55, 0xf9,
  0x3d, 0xc2, 0x8b, 0x57, 0x91, 0x3d, 0x77, 0xc9, 0x27, 0xb9, 0x93, 0x71, 0xf7, 0xff, 0xff, 0xc0,
  0xdf, 0xd1, 0xd1, 0xd1, 0xdf, 0xc0, 0xff, 0xc1, 0xf3, 0xfa, 0xcd, 0xd5, 0xc8, 0xc5, 0xea, 0xe6,
  0xe8, 0xd8, 0xcb, 0xec, 0xff
};
// ---- I2C Master Implementation (Write only) ---------------------------------

#define I2C_FREQ  800000UL                        // I2C clock frequency in Hz
void I2C_init(void) {                             // I2C init function
  TWI0.MBAUD   = ((F_CPU / I2C_FREQ) - 10) / 2;   //   set TWI master BAUD rate (simplified BAUD calculation)
  TWI0.MCTRLA  = TWI_ENABLE_bm;                   //   enable TWI master
  TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;            //   set bus state to idle
}
void I2C_start(uint8_t addr) {                    // I2C start transmission
  TWI0.MADDR = addr;                              //   start sending address
}
void I2C_stop(void) {                             // I2C stop transmission
  while (~TWI0.MSTATUS & TWI_WIF_bm);             //   wait for last transfer to complete
  TWI0.MCTRLB = TWI_MCMD_STOP_gc;                 //   send stop condition
}
void I2C_write(uint8_t data) {                    // I2C transmit one data byte to the slave, ignore ACK bit
  while (~TWI0.MSTATUS & TWI_WIF_bm);             //   wait for last transfer to complete
  TWI0.MDATA = data;                              //   start sending data byte 
}

// ---- OLED Implementation ----------------------------------------------------

#define OLED_ADDR       0x78             // OLED write address (Wire lib.:0x3c)
#define OLED_CMD_MODE   0x00             // set command mode
#define OLED_DAT_MODE   0x40             // set data mode

const uint8_t OLED_INIT_CMD[] = {        // OLED init settings
  0xA8, 0x3F,           // set multiplex (HEIGHT-1): 0x1F for 128x32, 0x3F for 128x64
  0x22, 0x00, 0x07,     // set min and max page                      
  0x20, 0x00,           // set horizontal memory addressing mode
  0xDA, 0x12,           // set COM pins hardware configuration to alternative
  0x8D, 0x14,           // enable charge pump
  0xAF                  // switch on OLED
};

void OLED_init(void) {                    // OLED init function
  I2C_init();                             //   initialize I2C first
  I2C_start(OLED_ADDR);                   //   start transmission to OLED
  I2C_write(OLED_CMD_MODE);               //   set command mode
  for (uint8_t i=0; i<sizeof(OLED_INIT_CMD); i++) I2C_write(OLED_INIT_CMD[i]); //   send the command bytes
  I2C_stop();                             //   stop transmission
}

void OLED_cursor(uint8_t xpos, uint8_t ypos) {// OLED set the cursor
  I2C_start(OLED_ADDR);                   //   start transmission to OLED
  I2C_write(OLED_CMD_MODE);               //   set command mode
  I2C_write(xpos & 0x0F);                 //   set low nibble of start column
  I2C_write(0x10 | (xpos >> 4));          //   set high nibble of start column
  I2C_write(0xB0 | (ypos & 0x07));        //   set start page
  I2C_stop();                             //   stop transmission
}

void OLED_clear(uint8_t p) {              // OLED clear screen with pattern
  OLED_cursor(0, 0);                      //   set cursor at upper left corner
  I2C_start(OLED_ADDR);                   //   start transmission to OLED
  I2C_write(OLED_DAT_MODE);               //   set data mode
  for(uint16_t i=1024; i; i--) I2C_write(p);  // clear the screen
  I2C_stop();                             //   stop transmission
}

void OLED_image(uint8_t xpos, uint8_t ypos, uint8_t *d) {// OLED set image
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0; y<(h+7)/8; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t x=0; x<w; x++) I2C_write( *d++ );
    I2C_stop();                           //   stop transmission    
  }
}

void OLED_image2x(uint8_t xpos, uint8_t ypos, uint8_t *d) {// OLED set 2x image
  const uint8_t x2[]={ 0x0,0x3,0xc,0xf,0x30,0x33,0x3c,0x3f,0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff };
  const uint8_t w=*d++, h=*d++;
  for(uint8_t y=0; y<(h+3)/4; y++) {
    OLED_cursor(xpos, ypos + y);          //   set the cursor
    I2C_start(OLED_ADDR);                 //   start transmission to OLED
    I2C_write(OLED_DAT_MODE);             //   set data mode
    for(uint8_t x=0, t; x<w; x++) {
      I2C_write( t = x2[ d[x+(y/2)*w] >> (y%2*4) & 0xf ] );
      I2C_write( t );
    }
    I2C_stop();                           //   stop transmission    
  }
}

// ---- Main Function ----------------------------------------------------------

void setup() {
  _delay_ms(100);                         // add delay
  OLED_init();                            // setup I2C OLED
}

void loop() {
  OLED_clear(0x00);                       // clear screen with black
  OLED_image( 48, 2, img_i );
  _delay_ms(1000);
  OLED_image2x( 32, 0, img_i );
  _delay_ms(2000);
  OLED_clear(0xff);                       // clear screen with white
  OLED_image( 52, 2, img_qr );
  _delay_ms(1000);
  OLED_image2x( 41, 1, img_qr );
  _delay_ms(4000);
}

nice!(0)  コメント(0) 

0.96インチI2C OLED (SSD1306)用に画像をデータ配列に。 [JavaScript]

Aliexpressで安いI2C OLEDディスプレイとなると、0.96インチの制御チップがSSD1306のもの。

とりあえず画像を出してみたいけど、BMPファイルデータをArduinoで動的に変換する必要はないので、SSD1306に合わせて画像をデータ配列にしたほうがいい。

ssd1306_dat.png

自作のスクリプトを作った後、すでに画像変換できるサイトがあることが判明。

image2cpp
http://javl.github.io/image2cpp/

ただ、いろいろできる分、設定もたくさんある。
ぼくの作ったのは、サンプルプログラムの寄せ集めだけど、ドラッグ&ドロップでSSD1306専用のデータが瞬時にできるというのが売り。
高さが8の倍数でないときの余白部分を白にするか黒にするかの選択のみ。

ssd1306-san.png
HTMLソース
<html>
<head><meta charset="utf-8"><title>SSD1306-san</title></head>
<body style="color:#999; text-align:center;">
<h1   style="color:#666;">SSD1306-san</h1>
<div  style="display:flex; justify-content:center; align-items:center;">
    <div id="target" style="width:200px; height:100px; border:5px dashed #eee; padding:10px;">
        Drop image here !<br>
        <img    id="preview" style="max-width:200px; max-height:80px;" onLoad="ssd1306img()"><br></div>
    <div style="width: 50px; "> -&gt; </div>
    <div style="width:200px; height:100px; border:5px solid #eee; padding:10px;">
        <span   id="info" ></span><br>
        <canvas id="cvs"     style="max-width:128px; max-height:64px; border:1px dashed #ccc; display:none"></canvas></div></div>
<div>
    <p>background color : 
        <input  id="bgw" type="radio" name="bg" checked><label for="bgw">white</label>
        <input  id="bgb" type="radio" name="bg"        ><label for="bgb">black</label>
    <p><textarea id="ary" cols=100 rows=20 style="font-size:80%; padding:1em; border:1px solid #ccc;" onClick="this.select()"></textarea></div>
<script>
function $(x) { return document.getElementById(x); }
const ctx = $('cvs').getContext('2d');

$('target').addEventListener('dragover', function (e) {
    e.preventDefault();
    e.stopPropagation();
    e.dataTransfer.dropEffect = 'copy';    } );
$('target').addEventListener('drop', function (e) {
    e.stopPropagation();
    e.preventDefault();
    const reader = new FileReader();
    reader.onload = function (e) { preview.src = e.target.result; }
    reader.readAsDataURL(e.dataTransfer.files[0]);    } );

function ssd1306img() {
    const w = $('cvs').width  = $('preview').naturalWidth;
    const h = $('cvs').height = $('preview').naturalHeight;
    const H = h - (h - 1) % 8 + 7;
    $('info').innerHTML    = "W:" + w + " x H:" + h;
    $('cvs').style.display = "none";
    $('ary').textContent   = " Image size is too large! ";
    if( w > 128 || h > 64 )  return;
    $('cvs').style.display = "inline";
    $('ary').textContent   = "const uint8_t image[] = { " + w + ",   " + h + ", // px\n  ";
    ctx.fillStyle = $('bgw').checked ? '#fff' : '#000';
    ctx.fillRect(0, 0, w, H);
    ctx.drawImage($('preview'), 0, 0);
    const img = ctx.getImageData(0, 0, w, h);
    let i, d, x, y, b;
    for (i = 0; i < img.data.length; i += 4) {
        const gray = img.data[i] * 0.299 + img.data[i+1] * 0.587 + img.data[i+2] * 0.114;
        img.data[i] = img.data[i+1] = img.data[i+2] = gray > 127 ? 255 : 0;
    }
    ctx.putImageData(img, 0, 0);
    for (i = y = 0; y < H; y += 8) {
        for (x = 0; x < w; x++) {
            for (b = d = 0; b < 8; b++) d += img.data[(x+(y+b)*w)*4] ? (1<<b) : 0;
            $('ary').textContent += (d < 16 ? "0x0" : "0x") + d.toString(16) + ((++i % 16) ? ", ": ",\n  ");
        }
    }
    $('ary').textContent = $('ary').textContent.slice( 0, $('ary').textContent.lastIndexOf(',') ) + "\n};";
}
</script>
</body>
</html>


タグ:OLED SSD1306
nice!(0)  コメント(0) 

202duino で I2C OLEDディスプレイ [Arduino]

202oled_1.jpg
ちょっと前にAliexpressで買ったままになっていた I2C OLED ディスプレイを出してみた。
SSD1306という制御チップを使ったもの。
プルアップ抵抗なしでもなんとかなった。

ATtiny202で制御できるかどうか調べてみると、、ありました。

GitHub - wagiminator_ATtiny13-TinyOLEDdemo I²C OLED on an ATtiny10_13_202
https://github.com/wagiminator/ATtiny13-TinyOLEDdemo

ここで使われているのは、128 x 32 ドット用のスケッチなので、128 x 64 ドット用にちょっと変更するとふつうに使えました。
変更箇所の1つめは、
// OLED init settings
const uint8_t OLED_INIT_CMD[] = {
  0xA8, 0x3F,       // set multiplex (HEIGHT-1): 0x1F for 128x32, 0x3F for 128x64                   HEIGHT-1   : 0x1F --> 0x3F
  0x22, 0x00, 0x07, // set min and max page                                                         max page   : 0x03 --> 0x07 
  0x20, 0x01,       // set vertical memory addressing mode
  0xDA, 0x12,       // set COM pins hardware configuration to sequential                            0x02:Sequential --> 0x12:Alternative
  0x8D, 0x14,       // enable charge pump
  0xAF,             // switch on OLED
  0x00, 0x10, 0xB0, // set cursor at home position
  0xA1, 0xC8        // flip the screen
};
のように、初期設定を変えること。
2つめは、// Setup のところで、OLED_init();の前にちょっとdelayを入れること。
3つめは、OLED_clear(void) で、512 → 1024 に変更すること。

です。
delayもArduino標準のを使わずAVRのdelayを使ったりして、スケッチも1kBほどにおさまっています。

試しにこのスケッチを「Wire(I2C)ライブラリ」,「delay()」を使って書き換えてみました。
注意点というか、嵌った点。

・アドレスを 0x78 → 0x3C と1ビットずらした表記で指定する。
・write()というのは書きだすのではなくて溜めておくだけで、endTransmission()で書き出す。
・溜めて置けるのは32バイトまで。

ということで、単純な置換だけではなく、関数の修正も必要でした。
で、それをするとどうなるかというと、、

・そこそこ遅くなっているような気がする
・スケッチが大きくなる(ATtiny202の2kBに収まらなくなり、ATtiny412を使用した。)
・他のArduinoでも使える(UNO R4でも表示できた。)

1.3インチのものも出てきたので使ってみたら、表示できるにはできるけどおかしい。
似て非なる制御チップを使っているらしい。(SH1106?)
0.96インチのと1.3インチのもので電源ピンの順番が逆になっていて、一つ壊してしまった。
202oled_2.jpg

202duinoのピン配列をよくあるやつっぽく作ってみた。

202duino-func2.png



nice!(0)  コメント(0) 

UNO R4 とスピーカをつなぐシールドを作ってみた。 [Arduino]

DACで音声再生ができたものの、圧電サウンダの小さな音ではさみしい。
スピーカーでそこそこの音量での再生にチャレンジ。

使用したのは、10kΩの可変抵抗とNPNトランジスタと3.5mmジャック。
スピーカーは100円ショップで以前に購入したもの(アンプなし)。
回路図は以下のとおり。
r4_dac_q_sp_sch.png
トランジスタのことよく分かっていない、データシート読めない私が作ったので間違っていると思うけど、ごめんなさい。でも動いた。

シールドはこんな感じ。
r4_dac_q_sp_1.jpg
一列をさらに部分的にしか使っていないのでシールドと言っていいかも微妙ですが。
工夫した点は、UNO R4の平らな部分にジャックの底があたるようにすることで、高さ的にはピン+ピンソケットとほぼ同じ高さになりこころもち安定します。
r4_dac_q_sp_2.jpg
r4_dac_q_sp_3.jpg
ボリュームを絞りすぎると音が出ず。開きすぎても音が出ず。
ちょうどいいところに持っていくと、まあまあ大きな音になります。



nice!(0)  コメント(0) 
前の10件 | -