JH7UBCブログ

アマチュア無線 電子工作 家庭菜園など趣味のブログです

ATtiny402 OLED 表示テスト

2023-09-16 21:56:26 | ATtiny
 ATtiny402でOLEDの表示テストをします。
 OLEDは、秋月電子の0.96インチ128×64ドットのもので、コントローラは、SSD1306です。

 まず、Arduino UNOで<SSD1306AsciiAvrI2c.h >というライブラリで表示テストをしてみるとメモリを4KBほど使ってしまいました。ATtiny402ではこのライブラリでうまく表示できるかどうかもわからないので、ライブラリを使わないで表示テストをすることにしました。

 以前、PIC16F1827でOLEDの表示テストをしたことがあります。記事はこちら。この時のプログラムは、JR3TGS局の記事を参考にしてというかコピーさせていただいています。今回もコピーさせていただきました。(Arduino用に編集しました)また、フォントもコピーさせていただきました。ありがとうございます。

 回路図です。電源は、3V(電池2本)とし、FT-234のRXDとTXDだけを接続してUPDIでプログラムを書き込みます。


 OLEDはI2C接続用なので、SDAとSCLをPA1とPA2に接続します。I2C用のプルアップ抵抗はOLEDモジュールに内蔵されているのでつけていません。

 スケッチです。
 6x8dot用のフォントは、JR3TGS局の記事のものをfont6.hとし、12x16dot用のフォントはfont12.hとして、スケッチと同じフォルダに保存しておきます。

---------------------------------------------------------------------
/*
 * ATtiny402 OLED test
 * 2023.09.16
 * JH7UBC Keiji Hata
 */

#include <Wire.h>
#include "font6.h"
#include "font12.h"

uint8_t Page;
uint8_t Column;
uint8_t Low_col;
uint8_t Hi_col;
uint8_t Size;

//コマンド1bye送信
void oledCommand(uint8_t cmd){
   Wire.beginTransmission(0x3C);
   Wire.write(0x80);
   Wire.write(cmd);
   Wire.endTransmission();
}

//データ1byte送信
void oledData(uint8_t data){
   Wire.beginTransmission(0x3C);
   Wire.write(0xC0);
   Wire.write(data);
   Wire.endTransmission();
}

//OLED初期化
void oledInit(){
   oledCommand(0x8D); //Set charge pump
   oledCommand(0x14); //Enable charge pump
   oledCommand(0xAF); //Display ON
}

//OLED画面消去
void oledClear(){
   Wire.beginTransmission(0x3C);
   Wire.write(0x00);// Control byte Co=0, D/C#=0
   Wire.write(0x20);// Set memory addressing mode
   Wire.write(0x00);// Horizontal addressing mode
   Wire.write(0x21);// Set column address
   Wire.write(0x00);// Column start address 0
   Wire.write(0x7F);// Column end address 127d
   Wire.write(0x22);// Set page address
   Wire.write(0x00);// Page start address 0
   Wire.write(0x07);// Page end address 7d   
   Wire.endTransmission();
   for(int i=0;i<1024;i++){
     oledData(0x00);
  }
}

// 6x8dotフォントの表示開始位置指定
void font6_posi(uint8_t page, uint8_t column){
   uint8_t low_col;
   uint8_t hi_col;
   Size = 0; // printf で使用するputch のフォントサイズ指定
   Page = page; // ページ情報をfont12用にグローバル変数に保存しておく
   Column = column; // カラム情報をfont12用にグローバル変数に保存しておく
   page = page + 0xB0; // ページ情報をコマンドにする為に 0xB0 を加算
   low_col = column & 0x0F; 
   column = column >>4;
   hi_col = column + 0x10;
   Wire.beginTransmission(0x3C);
   Wire.write(0x00);// Control byte Co=0, D/C#=0
   Wire.write(0x20);// Set memory addressing mode
   Wire.write(0x02);// Page addressing mode
   Wire.write(page);// Set page start addres
   Wire.write(low_col);// Set lower column start address
   Wire.write(hi_col); // Set higher column start address
   Wire.endTransmission();
}

void font12_posi(uint8_t page, uint8_t column){
   uint8_t low_col;
   uint8_t hi_col;
   Size = 1; // printf で使用するputch のフォントサイズ指定
   Page = page;            // ページ情報をfont12用にグローバル変数に保存しておく
   Column = column;        // カラム情報をfont12用にグローバル変数に保存しておく
   page = page + 0xB0;     // ページ情報をコマンドにする為に 0xB0 を加算
   low_col = column & 0x0F;
   column = column >> 4;
   hi_col = column + 0x10;
   Wire.beginTransmission(0x3C);
   Wire.write(0x00);    // Control byte Co=0, D/C#=0
   Wire.write(0x20);    // Set memory addressing mode
   Wire.write(0x02);    // Page addressing mode
   Wire.write(page);    // Set page start addres
   Wire.write(low_col);// Set lower column start address
   Wire.write(hi_col);    // Set higher column start address
   Wire.endTransmission();
}

//6x8dotフォントの1文字表示
void chr6(uint8_t c){
   uint8_t i;
  c = c - 0x20;
   Wire.beginTransmission(0x3C);
   Wire.write(0x40);// Control byte Co=0, D/C#=1 (The following data bytes are stored at the GDDRAM)
   for(int i=0;i<6;i++){
     Wire.write(font6[c][i]);
  }
   Wire.endTransmission();
}

void chr12(uint8_t c)        // 12x16dotフォントの1文字表示関数
{
   uint8_t i;
   uint8_t temp;
  c = c + 6 - 0x30;
   Page = Page + 0xB0;
   Low_col = Column & 0x0F;
   temp = Column >> 4;
   Hi_col = temp + 0x10;
   Wire.beginTransmission(0x3C);
   Wire.write(0x00);    // Control byte Co=0, D/C#=0
   Wire.write(0x20);    // Set memory addressing mode
   Wire.write(0x02);    // Page addressing mode
   Wire.write(Page);    // Set page start addres
   Wire.write(Low_col);// Set lower column start address
   Wire.write(Hi_col);    // Set higher column start address
   Wire.endTransmission();
   Wire.beginTransmission(0x3C);
   Wire.write(0x40);    // Control byte Co=0, D/C#=1 (The following data bytes are stored at the GDDRAM)
      for(i=0; i<12; i++)
      {
          Wire.write(font12[c][i]);
      }
   Wire.endTransmission();
   Page = Page + 1;        // グローバル変数ペーシ情報 Page を次の lower 12bytes 描画の為に 1 進める
   Wire.beginTransmission(0x3C);
   Wire.write(0x00);    // Control byte Co=0, D/C#=0
   Wire.write(0x20);    // Set memory addressing mode
   Wire.write(0x02);    // Page addressing mode
   Wire.write(Page);    // Set page start addres
   Wire.write(Low_col);// Set lower column start address
   Wire.write(Hi_col);    // Set higher column start address
   Wire.endTransmission();

   Wire.beginTransmission(0x3C);
   Wire.write(0x40);    // Control byte Co=0, D/C#=1 (The following data bytes are stored at the GDDRAM)
   for(int i=12; i<24; i++){
       Wire.write(font12[c][i]);
  }
   Wire.endTransmission();
   Page = Page - 1 - 0xB0;    // グローバル変数ページ番号 Page を次の1文字描画の為、進めたページ番号を元に戻す (0xB0はコマンドをページ情報に戻す為)
   Column = Column + 12;    // グローバル変数カラム情報を次の次の1文字描画の為、12アドレス進める
}

//文字列表示
void printStr(char *s){
   while(*s){
     if(Size){
       chr12(*s++);
     }else{
       chr6(*s++);
    }
  }
}

void setup(){
   Wire.begin();
   oledInit();
   oledClear();
   font6_posi(0,20);
   printStr("Hello World!");
   font12_posi(3,0);
   printStr("0123456789");
}

void loop() {
}
---------------------------------------------------------------------
 このスケッチでフラッシュメモリ2828バイト(69%)、RAMは101バイト(39%)を使っています。

 フォントの表示開始位置は、下の図のようになります。


 ブレッドボードです。Page0,column20から6x8dotフォントで「Hello World!」が、Page3,column0から12x16フォントで「0123456789」を表示しました。



ATtiny402 AQM0802A表示テスト

2023-09-13 16:36:33 | ATtiny
 ATtiny402でI2C LCD AQM0802Aの表示テストをします。

 AQM0802Aは秋月電子で販売しているモジュールを使います。

 AQM0802Aの電源電圧は。3.3Vですので、ATtinyも3.3V(乾電池2本)で動作させることにします。

 テストの前に、ATtiny402がVDD=3.3V(乾電池2本)でUPDIで書き込めるかどうか試しました。下の回路図のように乾電池から電源を供給し、FT234からはTXDとRXDだけを利用してプログラムの書き込みができました。
 AQM0802AはATtiny402のPA1(SDA)とPA2(SCL)に接続します。I2Cのプルアップ抵抗は、AQM0802Aモジュールに内蔵されているのでつけていません。


 AQM0802A表示のライブラリはいくつかあります。
 FaBo 213 LCD mini AQM0802A と
 arduino_ST7032-master を試しましたが、コントラストの設定が悪いのかうまく表示しませんでした。それではとイニシャライズからプログラムを組んでみました。「NobのArduino日記!」のこちらの記事を参考にさせていただきました。ありがとうございました。

 テストしたスケッチです。1行目に文字列「JH7UBC」を表示し、2行目に数値を0から255まで1秒ごとにカウントアップします。
-----------------------------------------------------------------------------------
/*
 * ATtiny402 AQM0802A test
 * 2023.9.13
 * JH7UBC Keiji Hata
 */

 #include <Wire.h>
 bool zflag = true;

//コマンド書き込み
void writeCommand(byte com){
   Wire.beginTransmission(0x3E);
   Wire.write(0x00);
   Wire.write(com);
   Wire.endTransmission();
}

//データ書き込み
void writeData(byte data){
   Wire.beginTransmission(0x3E);
   Wire.write(0x40);
   Wire.write(data);
   Wire.endTransmission();
 }

 //カーソルセット
 void setCursor(byte x,byte y){
     if(y==0){
       writeCommand(0x80+x);
     }else if(y==1){
       writeCommand(0xC0+x);
    }
 }

//1文字表示
void printChar(char c){
   writeData((int)(c));
}

//文字列表示
void printStr(char *s){
   while(*s){
     printChar(*s++);
  }
}

//数字表示(0消しあり)
void printN(byte num){
   if(zflag==true){
     writeData(0x20);//space表示
   }else{
     writeData(num + 0x30);//ASCII code
  }
}

//数値表示
void printNum(int n){
   zflag = true;
   byte d2 = n / 100;
   if(d2 != 0)zflag=false;
   printN(d2);
   byte d1 = (n % 100)/10;
   if(d1 != 0)zflag=false;
   printN(d1);
   byte d0 = n % 10;
   writeData(d0 + 0x30);
}

void lcd_init(){
   writeCommand(0x38);//Function set
   writeCommand(0x39);//Function set
   writeCommand(0x14);//Internal OSC frequency
   writeCommand(0x73);//Contrast set
   writeCommand(0x56);//Power/ICON/Contrast control
   writeCommand(0x6C);//Fllower control
   delay(200);
   writeCommand(0x38);//Function set
   writeCommand(0x0C);//Display ON/OFF control
   writeCommand(0x01);//Clear display
   delay(1);
}

void setup() {
   Wire.begin();
   lcd_init();
   printStr("JH7UBC");
}

void loop() {
   for(int i=0;i<256;i++){
     setCursor(0,1);
     printNum(i);
     delay(1000);
  }
 }
----------------------------------------------------------------

 ATtiny402のクロックは16MHzとしてコンパイルしました。
 VDD=5Vの時は、Contrast setは0x7A、Power/ICON/Contrast controlは0x54とすればよいようです。
 このプログラムで、フラッシュメモリ1583バイト(38%)、RAMは98バイト(38%)を使っています。

ブレッドボードです。

 数値の表示は。強引に割り算と剰余を利用しています。もっとうまい方法があると思うのですが、今回はとりあえず3桁までの数値を表示するだけなので、この方法にしました。

ATtiny402 エレキー

2023-08-28 16:13:20 | ATtiny
 ATtiny402でエレキーを作ってみます。

 回路図です。
 パドルはDOTをD1(PA7)に、DASHをD2(PA1)に接続し、各ピンは、10kΩでプルアップしています。
 スピード調節用に10kΩボリュームの電圧をAD変換して0~1023の値で読み込みます。
 送信機用はD3に出力し、それをトランジスタのオープンコレクタで送信機のKEY接続します。出力のモニターとしてLEDを接続しています。
 サイドートーンは、PWM4(PA3)にPWM信号で出力し、スピーカーで音を聞きます。
 


 エレキーのアルゴリズムです。
 このフローチャートは、以前Raspberry Pi Picoでエレキーを作った時のものです。ATtiny402では、Timer割り込みを使わないで、whileループ内で処理を行います。

 それでは、DOT出力とDASH出力は、どれだけの時間なのでしょうか。
 DOTとSPACEは同じ時間、DASHはDOTの3倍です。

 アマチュア無線の場合、CWの送信速度は一般的に20wpm(20 word per minute)程度です。1wordはPARISという符号を基準にしますので5文字、50DOTに相当します。したがって、20wpmの場合は、1DOTは60msとなります。
 
 CWスピードの調整範囲は、
 1DOT=40ms(30wpm=150字/分)から1DOT=200ms(6wpm)としました。
 ADコンバータから得られる0~1023を4で割った値0~255を利用します。

 スケッチです。
 乾電池2本(3V)動作とPWMの周波数の関係で、クロックは16MHzとしてコンパイルしました。
-------------------------------------------
/*
 * ATtiny402 TLEKEY
 * 2023.8.28
 * JH7UBC Keiji Hata
 */

#define SPEED 0
#define DOTKEY 1
#define DASHKEY 2
#define TX 3
#define SIDETONE 4
bool Dot_flag = false;
bool Dash_flag = false;
uint16_t dot_time;//dot_time=0~255

void setup() {
   pinMode(DOTKEY,INPUT);
   pinMode(DASHKEY,INPUT);
   pinMode(TX,OUTPUT);
   pinMode(SIDETONE,OUTPUT);
   digitalWrite(TX,LOW);
   analogWrite(SIDETONE,0);//サイドトーンを出さない
}

void loop() {
   dot_time = analogRead(SPEED)>>2;//Speedを読み込む
   //speedの上限と下限を設定
   if(dot_time <= 40){
     dot_time = 40;
   }else if(dot_time>= 200){
     dot_time = 200;
  }

   //DOTKEYが押されたかDot_flagが立っていれば、DOTとSPACEを送出する。
   if(digitalRead(DOTKEY)==LOW || Dot_flag==true){
     Dot_out();
     Dot_flag = false;//Dot_flagを下す
  }
   //DASHKEYが押されたかDash_flagが立っていれば、DASHとSPACEを送出する。
   if(digitalRead(DASHKEY)==LOW || Dash_flag==true){
     Dash_out();
     Dash_flag = false;//Dash_flagを下す
    }
}

void Space_out(){
   digitalWrite(TX,LOW);//TX off
   analogWrite(SIDETONE,0);//サイドトーンを出さない
   uint16_t c = dot_time;
   while(c){
     c--;
     delay(1);
    }
}

//Dotとspaceを送出
void Dot_out(){
   digitalWrite(TX,HIGH);//TX on
   analogWrite(SIDETONE,127);//サイドトーンを出す
   uint16_t c = dot_time;
   while(c){
     if(digitalRead(DASHKEY)==LOW){
       Dash_flag = true;
    }
       c--;
       delay(1);
  }
       Space_out();
}

//Dashとspaceを送出
void Dash_out(){
   digitalWrite(TX,HIGH);//TX on
   analogWrite(SIDETONE,127);//サイドトーンを出す
   uint16_t c =(dot_time <<1) + dot_time;
   while(c){
     if(digitalRead(DOTKEY)==LOW){
       Dot_flag = true;
    }
       c--;
       delay(1);
       }
       Space_out();
  }
-------------------------------------------
 ブレッドボードでテストしている様子です。
 サイドトーン用のPWMの周波数は約1000Hzです。
 ATtiny402は、結構力持ちでPWM出力ピンに直接スピーカーを接続するとかなり大きな音が出ます。音量の調節と出力電流を制限するため直列に300Ωの抵抗を入れています。


 テストなので、トランジスタはつけず、LEDと音で動作を確認しています。

 乾電池2本で動作させています。

 このエレキーの打ち心地は、普通のエレキーと同じで問題なく使えます。
 自作送信機に組み込むなどして使うと良いと思います。
 プログラムサイズは、フラッシュメモリが958バイト(23%)、RAMが14バイト(5%)です。ATtiny202でも十分組めるサイズです。

ATtiny402 ロータリーエンコーダテスト

2023-08-20 11:15:42 | ATtiny
 ATtiny402でロータリーエンコーダーを使うテストをします。

 Arduino UNOなどでは、Rotary.hというライブラリを利用することができます。確認はしていないのですが、ライブラリでは割り込みを使っているのではと考えています。

 ATtiny402では、ピン状態割り込みが使えないようなので、割り込みを使わない方法でロータリーエンコーダの制御プログラムを組みました。このプログラムは「wsnakのブログ」のロータリーエンコーダの記事のプログラムを利用させていただきました。ありがとうございます。

 今回のテストでは、ロータリーエンコーダはAmazonで購入した安価なものを使い、ロータリーエンコーダの回転方向が時計回りの時にカウントアップ、反時計回りの時にカウントダウンするようにして、カウント値をLCD1602に表示するようにしました。

 テストした回路図です。ロータリーエンコーダのA端子をD0(PA6)にB端子をD1(PA7)に接続し、それぞれを10KΩでプルアップします。
 

 ロータリーエンコーダの回転方向の判定方法を簡単に説明します。
 下の図のように、ロータリーエンコーダのA,Bの値を読み取り、Aをbit1、Bをbit0としてCurDatの値に加えます。A,Bの値が変わったらCurDatの値を左に2ビットシフトし、A,Bの値を加えます。エンコーダの1クリックで、A,Bの値が4回変わりますので、その都度同じ操作をします。



 1クリックが終わったとき、エンコーダが時計回りの時、CurDatの値は0x4Bに、反時計回りの時は0x87になりますので、この値で回転方向が判定できます。

 スケッチです。
 機械的なロータリーエンコーダを使う場合、チャタリング対策が必要です。
 スケッチでは、その対策をソフトで行っています。ひとつは、入力が安定するまで1msのdelayを入れました。それから、入力が同じかどうかmatchCntでカウントして、2回以上一致したら処理するようにしています。wsnackさんは5回以上の一致を確認していますが、1msのdelayを入れたので2回で十分のようです。
---------------------------------------------------
/*
    ATtiny402 Rotary Encoder test
    2023.8.19
    JH7UBC Keiji Hata
*/

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

//Rotary Encoder関係定義
#define REA 0 //D0
#define REB 1 //D1
byte curDat;
byte befDat = 0;
byte rotDir = 0;
int Count = 0;
byte inputMatch;
byte matchCnt;
byte rotPat = 0;

void setup() {
   pinMode(REA, INPUT);
   pinMode(REB, INPUT);
  //ロータリーエンコーダ初期値設定
   curDat = 0;
   if(digitalRead(REA)){
     befDat |= 2;
  }
  if (digitalRead(REB)){
     befDat |= 1;
  }
   //LCD初期設定と初期画面表示
   lcd.init();
   lcd.backlight();
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print("Rotary Encoder");
   lcd.setCursor(0,1);
   lcd.print(Count);
}

//Rotary Encoderの回転方向判定
signed char CheckEnc(byte dat) {
   rotPat <<= 2;//左に2bitシフト
   rotPat |= (dat & 0x03);//datの下位2bitを加える
  if (rotPat == 0x4B) {
     return 1; //時計回り(CW))
  } else if (rotPat == 0x87) {
     return -1; //反時計回り(CCW)
  } else {
     return 0; //どちらでもない
  }
}

void loop() {
     signed char val;
     curDat = 0;
     if (digitalRead(REA)) {
       curDat |= 2;
    }
     if (digitalRead(REB)) {
       curDat |= 1;
    }
     if (befDat == curDat) {
       //befDatとcurDatが一致したときの処理
       if (!inputMatch) {
         matchCnt++;
         if (matchCnt >= 2) { //2回以上一致したらフラッグを立てる
           inputMatch = 1;
           val = CheckEnc(curDat);
           if (val != 0) {
             Count += val;
             lcd.setCursor(0, 1);
             lcd.print("    ");
             lcd.setCursor(0, 1);
             lcd.print(Count);
           }
         }
       }
    } else {
       //befDatとcuDatが一致しなかった時の処理
       delay(1);//1ms待つ(チャタリング対策)
       befDat = curDat;
       matchCnt = 0;
       inputMatch = 0;
    }
}
---------------------------------------------------
ブレッドボードです。ロータリーエンコーダを回すごとに科カウントアップ、カウントダウンされます。このプログラムは、クリックの取りこぼしも少なく、けっこう優秀です。



ATtiny402 ADCテスト

2023-08-06 20:26:34 | ATtiny
 ATtiny402でADCのテストをします。

 Attiny402は、6チャンネルのADCを持ち、下の図のように割り当てられています。(茶色のANALOG|DIGITAL 0から6)
  PIN PinName ADC
  2  PA6   AIN0
  3  PA7   AIN1
  4  PA1   AIN2
  5  PA2   AIN3
  6  PA0   AIN5
  7  PA3   AIN4
 です。

 前の記事のLCD1602表示を利用して、ADCのテストをします。
 
 回路図です。


 ADCはAIN0(PA6)を使い、10KΩのボリュームの電圧をADCで読み取り、その値を0.5secごとにLCD1602に表示します。

 スケッチです。

 ブレッドボードです。ボリュームを回すと
 2行目に0V~5Vの電圧値が、0~1023の値で0.5秒ごとに読み込まれて表示されます。