JH7UBCブログ

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

Arduino Pro Mini Si5351A OLED 7MHz VFO

2020-12-30 08:36:17 | Arduino
 ここまで、Arduino Pro Miniを使って、ロータリーエンコーダとOLEDのテストをしました。それらに、3チャンネルクロックジェネレーターSi5351Aを加えて、7MHz VFOを作ってみます。

 回路図です。



 ロータリーエンコーダは、スイッチ付きの中華製を使いました。スイッチは、STEP切り替え用に使っています。ロータリーエンコーダ用のライブラリは、Ben Buxton のものを使い、D2とD2に接続して、外部割込みを利用しています。なお、詳細は、当ブログの記事「ロータリーエンコーダ その2」を参照してください。

 OLEDの表示には、SSD1306Ascii ライブラリを使いました。使い方は前の記事を見てください。

 Si5351Aのコントロール用のライブラリもたくさんありますが、今回は私がこれまで使っていたスケッチを使っています。
スケッチです。
----------------------------------------------------------------------------------------------------
/*
* Arduino Si5351A 7MHz VFO OLED Display
* 2020.12.29
* JH7UBC Keiji Hata
*/

#include <Wire.h>
#include <SSD1306AsciiAvrI2c.h>
#include <Rotary.h>

SSD1306AsciiAvrI2c oled;
Rotary r = Rotary(2, 3);

//Si5351A関係の定義
#define Si5351A_ADDR 0x60
#define MSNA_ADDR 26
#define MSNB_ADDR 34
#define MS0_ADDR 42
#define MS1_ADDR 50
#define MS2_ADDR 58
#define CLK0_CTRL 16
#define CLK1_CTRL 17
#define CLK2_CTRL 18
#define OUTPUT_CTRL 3
#define XTAL_LOAD_C 183
#define PLL_RESET 177
uint32_t XtalFreq = 25000000;
uint32_t divider;
uint32_t PllFreq;
uint8_t mult;
uint32_t num;
uint32_t denom;
uint32_t l;
float f;
uint32_t P1;
uint32_t P2;
uint32_t P3;
char PLL;
uint8_t PLL_ADDR;
uint8_t MS_ADDR;

//周波数STEP
#define SW_STEP 4 //周波数STEPボタン

//周波数設定
const long LOW_FREQ = 7000000; //下限周波数
const long HI_FREQ = 7200000; //上限周波数
unsigned long FREQ = 7000000; //VFO周波数初期値
unsigned long FREQ_OLD = FREQ; //周波数の前の値
int dF = 120; //周波数補正値
int STEP = 1000; //STEP 初期値

//Rotary Encoder 外部割込み処理ルーチン
void rotary_encoder(){
unsigned char result = r.process();
if(result){
if(result == DIR_CW){
FREQ = FREQ + STEP;
}else{
FREQ = FREQ - STEP;
}
}
FREQ = constrain(FREQ,LOW_FREQ,HI_FREQ); //VFOの下限と上限を超えないように
}

//STEP SWが押された時の処理
void Fnc_Stp()
{
if(STEP == 10){
STEP = 1000;
}
else{
STEP /= 10;
}
delay(10);
Step_Disp(STEP);
while(digitalRead(SW_STEP) == LOW){
delay(10);
}
}

void setup() {
r.begin();//ロータリーエンコーダ初期化
attachInterrupt(0,rotary_encoder,CHANGE); //外部割込み設定
attachInterrupt(1,rotary_encoder,CHANGE);
pinMode(SW_STEP,INPUT_PULLUP); //STEP SW 入力に設定しプルアップ
oled.begin(&Adafruit128x64, 0x3C); //OLED初期化

Si5351_init(); //Si5351Aの初期化
PLL_Set('A',FREQ + dF); //VFO周波数初期値セット
MS_Set(0);

//初期画面表示
oled.setFont(font5x7);
oled.setCursor(5,0);
oled.print("Arduino Si5351A VFO");
oled.setFont(font8x8);
oled.setCursor(5,6);
oled.print("STEP");
Freq_Disp(FREQ); //周波数表示
Step_Disp(STEP); //STEP表示
}

//Main program
void loop() {
if(digitalRead(SW_STEP) == LOW){Fnc_Stp();} //STEP SWが押されたら、周波数STEPを変更
if(FREQ != FREQ_OLD){ //周波数FREQが変わったら、Si5351Aの周波数を変更
PLL_Set('A',FREQ + dF);
MS_Set(0);
Freq_Disp(FREQ); //周波数を表示する。
FREQ_OLD = FREQ; //変更された周波数を保存
}
delay(10);
}

//周波数表示
void Freq_Disp(unsigned long Fre){
String freqt = String(Fre);
oled.setFont(lcdnums14x24);
oled.setCursor(0,2);
oled.print(freqt.substring(0,1) + "." + freqt.substring(1,4) + "." + freqt.substring(4));
}

//STEP表示
void Step_Disp(int Stp){
oled.setFont(font8x8);
oled.setCursor(40,6);
if(Stp == 1000){
oled.print(" 1K");
}else if(Stp == 100){
oled.print("100");
}else{
oled.print(" 10");
}
}

//レジスタに1バイトデータを書き込む。
void Si5351_write(byte Reg , byte Data){
Wire.beginTransmission(Si5351A_ADDR);
Wire.write(Reg);
Wire.write(Data);
Wire.endTransmission();
}


//Si5351Aの初期化
void Si5351_init(){
Si5351_write(OUTPUT_CTRL,0xFF); //Disable Output
Si5351_write(CLK0_CTRL,0x80); //CLOCK0 Power down
Si5351_write(CLK1_CTRL,0x80); //CLOCK1 Power down
Si5351_write(CLK2_CTRL,0x80); //CLOCK2 Power down
Si5351_write(XTAL_LOAD_C,0x80); //Crystal Load Capasitance=8pF
Si5351_write(PLL_RESET,0xA0); //Reset PLLA and PLLB
Si5351_write(CLK0_CTRL,0x4F); //CLOCK0 Power up 8mA
Si5351_write(CLK1_CTRL,0x4F); //CLOCK1 Power up
Si5351_write(CLK2_CTRL,0x4F); //CLOCK2 Power up
Si5351_write(OUTPUT_CTRL,0xFE); //Enable CLOCK0
}

//PLLの設定
void PLL_Set(char Pll,uint32_t Frequency){
divider = 900000000 / Frequency;
if (divider % 2) divider--;
PllFreq = divider * Frequency;
mult = PllFreq / XtalFreq;
l = PllFreq % XtalFreq;
f = l;
f *= 1048575;
f /= XtalFreq;
num = f;
denom = 1048575;
P1 = (uint32_t)(128 * ((float)num /(float)denom));
P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num -denom * P2);
P3=denom;

if (Pll == 'A'){
PLL_ADDR = MSNA_ADDR;
}else{
PLL_ADDR = MSNB_ADDR;
}
Parameter_write(PLL_ADDR,P1,P2,P3);
}

//MultiSynth(分周器)のセット
void MS_Set(uint8_t MS_No){
P1 = 128 * divider - 512;
P2 = 0;
P3 = 1;
switch(MS_No){
case 0:
MS_ADDR = MS0_ADDR;
break;
case 1:
MS_ADDR = MS1_ADDR;
break;
case 2:
MS_ADDR = MS2_ADDR;
break;
default:
MS_ADDR = MS0_ADDR;
}
Parameter_write(MS_ADDR,P1,P2,P3);
}

//レジスタにパラメータP1,P2,P3を書き込む。
void Parameter_write(uint8_t REG_ADDR,uint32_t Pa1,uint32_t Pa2,uint32_t Pa3)
{
Si5351_write(REG_ADDR + 0,(Pa3 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 1,(Pa3 & 0x000000FF));
Si5351_write(REG_ADDR + 2,(Pa1 & 0x00030000) >> 16);
Si5351_write(REG_ADDR + 3,(Pa1 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 4,(Pa1 & 0x000000FF));
Si5351_write(REG_ADDR + 5,((Pa3 & 0x000F0000) >> 12) | ((Pa2 & 0X000F0000) >> 16));
Si5351_write(REG_ADDR + 6,(Pa2 & 0x0000FF00) >> 8);
Si5351_write(REG_ADDR + 7,(Pa2 & 0x000000FF));
}
----------------------------------------------------------------------------------------------------
 CLK0の出力を周波数カウンタで測定しています。

 Si5351Aの発振周波数は、設定と若干ずれますので、周波数補正値dFの値で調整します。

 ブレッドボードです。コンパクトです。


Arduino OLED 表示テスト

2020-12-26 12:44:50 | Arduino
 ArduinoでOLEDの表示テストをします。使うのは、Amazonから購入したHeiLetgoの0.96インチ128×64ドットのI2C接続のもので、文字色は白です。
このOLEDに使われてるコントローラは、SSD1306です。同じようなものが秋月電子でも販売されています。



 SSD1306を使ったOLEDの表示に利用できるArduinoのライブラリは、いくつかありますが、今回は、Adafruit_SSD1306.h u8glib.h SSD1306AsciiAvrI2c.h で文字「Hello World!」を表示させてみます。

 Arduinoは、Arduino Pro Miniを使い次の回路図のように配線します。


 まず、Adafruitのライブラリを使ってみます。まず、ライブラリーマネージャ、またはGitHubから「Adafruit GFX Library」と「Adafruit SSD1306」をインストールします。 GitHubは、こちら

 Qiitaのこちらのページのスケッチで。「Hello World!」を表示させてみました。


  フォントがやや粗いのですが、setCursor(x,y) (x=0~128,y=0~63)でディスプレイ内のどの位置からも表示可能です。文字の大きさは、setTextSize(n) n=1,2,3・・で設定できます。グラフィックスの表示できますが、今回は文字だけのテストをしました。

 このライブラリーは、メモリをたくさん使います。この文字を表示するだけで、フラッシュメモリの42%を使いました。
==========================================================
 次に、u8glibを使って「Hello World!」を表示してみます。
 u8glibをライブラリマネージャまたはGitHubからインストールします。GitHubは、こちら
 サンプルスケッチの中から「HelloWorld.ino」をArduino Pro Miniに書き込んでOLEDに表示させました。


 Adafruitより、なめらかな表示になりました。サンプルスケッチから必要な部分だけ取り出したスケッチは次のようになります。
-------------------------------------------------------------------------------
#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWI

void draw(void) {
u8g.setFont(u8g_font_unifont);
u8g.drawStr( 0, 22, "Hello World!");
}

void setup() {
u8g.setColorIndex(1); // pixel on
}

void loop() {
// picture loop
u8g.firstPage();
do {
draw();
} while( u8g.nextPage() );

delay(50);
}
-------------------------------------------------------------------------------
 文字の位置は、u8g.drawStr( x, y, "    ");のx=0~128,y=0~63で設定します。
 フォントもいろいろ利用でき、グラフィックスも表示できます。
 この表示で、フラッシュメモリの約30%を使います。
 このライブラリは、コンパイルに長い時間がかかりました。
============================================================
 最後に、SSD1306Asciiというライブラリを使ってみます。
 このライブラリは、文字表示専用のライブラリで、グラフィックスは使えないのですが、様々なフォントが使え、メモリの消費量が少ないのが特徴です。
 これもライブラリーマネージャまたは、GitHubからライブラリをインストールします。GitHubは、こちら
 サンプルスケッチの中から、HelloWorldAvrI2c.inoを選び、フォントをTimesNewRoman16にして、「Hello World!」を表示させました。



 サンプルスケッチから必要な部分だけを抜き出したスケッチです。
----------------------------------------------------------------------------------
#include <SSD1306AsciiAvrI2c.h>
SSD1306AsciiAvrI2c oled;

void setup() {
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(TimesNewRoman16);
oled.clear();
oled.setCursor(0, 2);
oled.print("Hello World!");
}

void loop() {
}
----------------------------------------------------------------------------------
  このスケッチで、フラッシュメモリの11%を使いました。さすがに文字だけだとメモリの消費量は少ないです。
 前の2つのライブラリがグラフィックスで、文字の表示位置を任意に設定できたのに対し、このライブラリは、x方向は、0~127で設定できますが、y方向は0~7の設定で、8ドットごとの位置になります。これは、SSD1306のPage0~Page7に相当します。

 以上、3つのライブラリでテストしました。グラフィックスも使う場合は、ug8libが良さそうです。文字だけでメモリの消費量を小さくするには、SSD1306Asciiが良さそうです。
 この後、Arduino Pro Mini+Si5351A+OLEDで7MHz VFOを試作してみようと思っています。OLED表示は、SSD1306Asciiを使おうと考えています。

Arduino ロータリーエンコーダ その2

2020-12-23 07:41:53 | Arduino
 Arduinoの外部割込みを利用する方法です。この方法は、Si4735 DSP ラジオのスケッチにも使われています。

 Arduinoの外部割込みは、外部割込み0(ピン2)と外部割込み1(ピン3)です。
attachInterrupt()関数は、外部割込みがあった時に呼び出す関数を指定します。
使い方は、attachInterrupt(interrupt,function,mode);で、
interruptは、0または1です。
functionは、割込みがあった時に呼び出す関数です。
modeは、LOW,CHANGE,RISING,FALLINGのいずれかを指定します。

ロータリーエンコーダをピン2,3に接続し、回転方向をRight,Leftでシリアルモニタに表示させるスケッチです。
digitalPinToInterrupt()は、デジタルピンを割込み番号に変換する関数です。
ENC_A = 2,ENC_B = 3ですので、digitalPinToInterrupt(ENC_A)は0で、digitalPinToInterrupt(ENC_B)は1となります。interruptは、0,1と直接指定してもかまいません。
----------------------------------------------------------------------------
#include <Rotary.h>

#define ENC_A 2
#define ENC_B 3

Rotary r = Rotary(ENC_A,ENC_B);

void setup() {
r.begin();
attachInterrupt(digitalPinToInterrupt(ENC_A),rotary_encoder,CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_B),rotary_encoder,CHANGE);
// pinMode(ENC_A,INPUT_PULLUP);
// pinMode(ENC_B,INPUT_PULLUP);
Serial.begin(9600);
Serial.println("Rotary Encoder test");
}

void loop() {
}

void rotary_encoder(){
unsigned char result = r.process();
if(result){
if(result == DIR_CW){
Serial.println("Right");
}else{
Serial.println("Left");
}
}
}
----------------------------------------------------------------------------

 setup()において、r.begin();で初期化すれば、2,3ピンをプルアップしてくれるので、コメントアウトした
pinMode(ENC_A,INPUT_PULLUP);
pinMode(ENC_B,INPUT_PULLUP);
は、なくとも動作します。
逆に、r.begin();を省略した場合は、上記のプルアップ設定が必要になります。

 以上、「ロータリーエンコーダその1」と「ロータリーエンコーダその2」で紹介した方法のいずれも非常に良く動作します。

 割込みを使わないで、簡単にロータリーエンコーダを利用したい場合は、polling.inoを利用します。どのピンでも動作します。

 ピン変化割込みを使う場合は、interrupt.inoを利用します。ただし、他のピンを使用する場合は、割込み設定を変更する必要があります。

 外部割込みを使う場合は、今回紹介したスケッチを利用します。ただし、使うピンは、2と3で変更できません。

 ということで、目的ケースに応じて、それぞれのスケッチを利用することにします。

今年の家庭菜園のまとめ

2020-12-22 10:08:31 | 家庭菜園と花
 我が家の畑には、今30cmほどの雪が積もっています。約半年に渡って楽しんだ家庭菜園のまとめをしておくことにします。

 家庭菜園での作業は、4月30日に畑を耕して、5月11日にナスとトマトの苗を植え付けるところから始まり、12月14日のオータムポエムの収穫までの約半年です。

 畑の大きさは、幅約7m、長さ約13mで、面積約90㎡です。13mのうち両側2mは使わず、間の11mを1m間隔で畝を作り、連作を避けるようにして、毎年作付け場所を計画的に決定しています。


 野菜の栽培方法は、完全無農薬栽培で、農薬は一切使用していません。従って、ハツカダイコンや葉物は、害虫を防ぐために写真のような防虫ネット(1m×5m)を使用しています。肥料は、牛ふん堆肥と化成肥料を使っています。

 今年の収穫量です。
ナス(コメリの超やわらかナス8株)2129個 1株当たり266個
ピーマン(4株)421個 1株当たり105個
シシトウ(2株)879個 1株当たり439個
ミニトマト(8株)1436個 1株当たり179個
オクラ(16株)535個 1株当たり33個
ハツカダイコン(4.5mの畝で4畝)996個 1畝当たり249個
インゲン(16株) 1146個
トウモロコシ 19本
他にケール、コマツナ、ホウレンソウ、ルッコラ、シュンギク、ミズナ、オータムポエムなどの葉物を栽培しました。

 今年は台風に一度も遭わなかったので、収穫量は比較的多くなりました。

 今年予想外だったのが、トウモロコシの鳥害でした。アンテナに止まったカラスが、収穫時になったトウモロコシを狙って急降下してきてトウモロコシに止まり、ちょんちょんと食べてしまうのです。これで半数以上のトウモロコシを食べられてしましました。来年はどうしようかな・・・



Arduino ロータリーエンコーダ その1

2020-12-21 21:50:09 | Arduino
 Arduinoで使えるロータリーエンコーダ用のライブラリーは、様々あります。
 
 その中で私のお気に入りは、Ben Buxton氏のライブラリーで、GitHubからダウンロードできます。こちらからRotary-master.zipをダウンロードして、Arduino IDEでライブラリーに登録します。

 サンプルスケッチで使い方を見てみましょう。まず、polling.inoです。
 デジタルポートD2とD3にロータリーエンコーダのA,B端子を接続します。
 Arduino Mini Proの場合の接続回路図です。


 スケッチです。
----------------------------------------------------------------------------------------------------
#include <Rotary.h>

Rotary r = Rotary(2, 3);

void setup() {
Serial.begin(9600);
r.begin(true);
}

void loop() {
unsigned char result = r.process();
if (result) {
Serial.println(result == DIR_CW ? "Right" : "Left");
}
}
-----------------------------------------------------------------------------
 このスケッチは、割込みを使わない方法で、プルアップは、ライブラリー内で行われます。ロータリーエンコーダを時計回りに回転させるとRight、反時計回りに回転させるとLeftとシリアルモニタに表示されます。



 Arduino Mini Proを使用した場合の様子です。ロータリーエンコーダはAmazonで購入した中華製です。USBシリアル変換は、FT232RLです。



 他のピンを使いたい場合は、Rotary r = Rotary(2, 3); の( )の中のピン番号を変更します。例えばD4とD5を使う場合は、Rotary r = Rotary(4, 5);とします。

 次にピン変化割込みを利用するサンプルスケッチInterrupt.inoを書き込んで使ってみます。使用するピンは、前と同じです。

 スケッチです。
-------------------------------------------------------------------------
#include <Rotary.h>

Rotary r = Rotary(2, 3);

void setup() {
Serial.begin(9600);
r.begin();
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
sei();
}

void loop() {
}

ISR(PCINT2_vect) {
unsigned char result = r.process();
if (result == DIR_NONE) {
// do nothing
}
else if (result == DIR_CW) {
Serial.println("ClockWise");
}
else if (result == DIR_CCW) {
Serial.println("CounterClockWise");
}
}
-------------------------------------------------------------------------
ロータリーエンコーダを時計回りに回転させると「ClockWise」、反時計回りに回転させると「CounterClockWise」とシリアルモニタに表示されます。


 setup()内の
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
sei();
について説明します。
 PCICRは、ピン変化割込み許可を設定するレジスタ(Pin Change Interrupt Control Register)です。
 Arduinoのピンは、8ピンごとにPCIE0,PCIE1,PCIE2の3つのグループに分けられていて、どのグループの割込みを許可するかを設定します。

 なお、各グループのピンは、ピンの名称で示すと
PCIE0 (D13,D12,D11,D10,D9,D8)
PCIE1 (RESET,A5,A4,A3,A2,A1,A0)
PCIE2 (D7,D6,D5,D4,D3,D2,D1,D0)
です。
 今回D2とD3のピン変化を使いますので、グループは、PCIE2です。これをPCICRレジスタにセットするのが
 PCICR  |= (1 << PCIE2);
です。
 次に、各グループのピンにうちどのピンの割込みを許可するかを設定するのが、PCMSKレジスタ(Pin Change Enable Mask Register)です。
このレジスタは、各グループごとにPCMSK0,PCMSK1,PCMAK2に分けられます。各レジスタで設定できるグループとPCINT名は、
PCMSK0 (PCIE0) (PCINT7,PCINT6,PCINT5,PCINT4,PCINT3,PCINT2,PCINT1,PCINT0)
PCMSK1(PCIE1) (PCINT14,PCINT13,PCINT12,PCINT11,PCINT10,PCINT9,PCINT8)
MCMSK0(PCIR0) (PCINT23,PCINT22,PCINT21,PCINT20,PCINT19,PCINT18,PCINT17,PCINT16)
それぞれ、上記のピンに対応しています。
今回のD2とD3は、PCINT18とPCINT19ですので、これらのピンの割込みを許可する設定は
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
となります。

 次の行のsei(); で割込みを許可しています。

 最後に、
ISR(PCINT2_vect) {
}
について説明します。
 ISR(Interrupt Service Routine)は、割込み(今回はピン変化割込み)が発生した場合に呼び出される関数で、引数には、割込みの発生元を指定します。発生元とシンボルは
シンボル    発生元
INT0_vect   D2ピンの外部割込み発生
INT1_vect   D3ピンの外部割込み発生
PCINT0_vect  PCIE0のピン変化が発生
PCINT1_vect  PCIE1のピン変化が発生
PCINT2_vect  PCIE2のピン変化が発生
です。
 今回は、PCIE2のグループですので、シンボルはPCINT2_vectとなります。

 この関数内に割込みが発生した時の処理を記入します。

 他のピンを使う場合は、以上を考慮して割込みの設定をすることになります。