2026年6月17日水曜日

なんだってー! UIAPduinoのPWMに対応しているピンは3つだけじゃなかった!


  先日の自由研究日記「電子工作用のブザーは2種類あるらしい。せっかくだから両方のせてみた。」をUIAPduinoのDiscordコミュニティに共有したところ、開発者の埋田さんから衝撃的なコメントが届いた。

「あー!!!PWMポートもっとあるのを書き忘れてたの思い出した!!!!(✽ ゚д゚ ✽)!!!!」
「3つだけじゃなくて他のポートも前回のコアアップデートで動くようになったの忘れてたw」

 これには思わず「えーーーーーーーーーーーーーーーーーー!!!」とリプライしてしまうくらいに驚いた。公式サイトのピン配置図(2025年9月版)では、PWM対応として2番・5番・6番の3つが強調されていたからだ。

ピン配置図(2025年9月版)

 今のところ、ゲームベーダーではパッシブブザー以外にPWMを使う予定はないので、どのピンが追加対応されたのかはドキュメントの更新を待つことにする。検証後に更新されるらしい。

 この日記が思わぬところでドキュメントのアップデートに繋がるのだから面白い。今後もコツコツと続けていきたいと思う。

2026年6月14日日曜日

電子工作用のブザーは2種類あるらしい。せっかくだから両方のせてみた。


 LED表示とキー入力の次は、音を出したい。ブザー(電子工作用の小さなスピーカーのようなもの)をつけてみることにする。

 電子パーツショップ aitendoの実店舗(東京都の鶯谷近辺にありました)が閉店する時に、部品の「詰め放題」があった。あまったスペースにブザーを詰め込んだから、大量に在庫がある。

「さっそく単体テストをするか!」 と思って箱からブザーを取り出したら、今まで使っていたブザーとは少し違う気がする。⊕の記号が書かれたシールが貼ってあるし、ピンの長さも違う。そういえば以前読んだ本に、パッシブブザーとアクティブブザーの2種類があるって書いてあったな……。

 ChatGPTに聞いてみると、アクティブブザー(自励式ブザー)は内部に発振回路が入っていて、電気を流すだけで一定の音が出る。

 パッシブブザー(他励式ブザー)は内部に発振回路がないから、電流を流すだけじゃ音がでない。音を出すには高速に電流をON/OFFする必要がある。でもメリットがあって、そのON/OFFの速度をコントロールすることで音階をつくることができる。

 実際に繋いで調べたところ、aitendoでゲットしたのはアクティブブザーだった。電流を流しただけで近所迷惑なくらい大きな音が鳴った。なんでよく調べて買わないのかと言われそうですが……詰め放題に浮かれていたということで。

 ゲームに使うには音階が出せた方がよいので、ChatGPTはパッシブブザーに入れ替えることをお勧めしてくれた。でも自分としては2種類の音がでたら面白いし、同時に鳴らしたり、使い道をわけてみようと考えて、ゲームベーダーのプロトタイプには両方のブザーを載せてみることにした。

 一定の音しかでないアクティブブザーはアラーム(警告音)専用。音階がだせるパッシブブザーは効果音や音楽に使うイメージだ。

うるさいからシールはつけたままにする。抵抗を入れるかも。

 ちなみにパッシブブザーに流す電流のON/OFFは、マイコンのPWM機能(Pulse Width Modulation)を使うのが一般的らしい。

 Arduino開発環境は、tone関数を使って簡単に音階を鳴らせる。UIAPduino Pro Micro CH32V003 V1.4の場合、PWMに対応しているピンは2番・5番・6番の3つだから、今回のゲームベーダーでは6番ピンに接続した。

 テストプログラムは0から9番のボタンを押すと、対応するLEDを点灯させつつ、ドレミファと、音階ありの音をパッシブブザーから鳴らすことにした。右端のボタン(スタートボタン?)を押したときだけは、左端にある大型LEDを点灯させて、アクティブブザーから警告音を鳴らす。

 ボタンを押すと、LEDが光って、音が鳴るだけではあるけれど、素直に動いてくれるとやっぱり嬉しい。ちょっとした楽器気分が楽しめた。

「これで、LEDゲームをつくる準備ができた!」はず……いよいよ明日以降は、シンプルなゲームプログラムをつくってみることにする。

※参考までにChatGPTによるテストプログラムを掲載しておきます。 

//------------------------------------------------
// LED + ボタン + ブザー実験用スケッチ
//
// 目的:
// ・74HC595を2個使って、LEDを最大11個制御する
// ・74HC165を2個使って、ボタンを最大11個読み取る
// ・ボタン0〜9は、対応するLEDを点灯し、音階を鳴らす
// ・ボタン10は、LED10を点灯し、自励式ブザーを鳴らす
//
// 接続概要:
// ・LED出力側:74HC595 x2
// ・ボタン入力側:74HC165 x2
// ・音出力:他励式ブザー / スピーカー
// ・警告音:自励式ブザー
//------------------------------------------------


//------------------------------------------------
// 74HC595 側の接続
//
// 74HC595は「少ないマイコンピンでたくさんのLEDを制御する」IC。
// 今回は2個カスケード接続して、LEDを11個まで扱う。
//
// PIN_SER   : 74HC595のSER / DS
// PIN_LATCH : 74HC595のRCLK / ST_CP
// PIN_CLK   : 74HC595のSRCLK / SH_CP
//------------------------------------------------

const int PIN_SER   = 11;
const int PIN_LATCH = 8;
const int PIN_CLK   = 12;


//------------------------------------------------
// 74HC165 側の接続
//
// 74HC165は「たくさんのボタン入力を少ないマイコンピンで読む」IC。
// 今回は2個カスケード接続して、16bit分の入力を読む。
// 実際に使っているのは、そのうち11個。
//
// PIN_165_DATA : 74HC165のQH
// PIN_165_LOAD : 74HC165のSH/LD
// PIN_165_CLK  : 74HC165のCLK
//
// ボタン入力はプルアップ前提。
// つまり、押していない時はHIGH、押すとLOWになる。
//------------------------------------------------

const int PIN_165_DATA = 7;
const int PIN_165_LOAD = 5;
const int PIN_165_CLK  = 10;


//------------------------------------------------
// LED表示用の仮想画面
//
// vram[0]〜vram[10] が LED0〜LED10 に対応。
// 直接74HC595へ書き込まず、いったんvramに状態を作ってから
// drawVRAM()でまとめてLEDへ反映する。
//------------------------------------------------

const int SCREEN_WIDTH = 11;
bool vram[SCREEN_WIDTH];


//------------------------------------------------
// ブザー接続
//
// PIN_SPEAKER : 他励式ブザー、または小型スピーカー
//               tone()で周波数を指定して鳴らす。
// PIN_ALARM   : 自励式ブザー
//               HIGHにするだけで鳴るタイプ。
//------------------------------------------------

const int PIN_SPEAKER = 6;  // 他励式ブザー / スピーカー
const int PIN_ALARM   = 0;  // 自励式ブザー


//------------------------------------------------
// ボタン0〜9に対応する音階
//------------------------------------------------

const int noteTable[10] =
{
  262, // Button0 : ド(C4)
  294, // Button1 : レ
  330, // Button2 : ミ
  349, // Button3 : ファ
  392, // Button4 : ソ
  440, // Button5 : ラ
  494, // Button6 : シ
  523, // Button7 : ド(C5)
  587, // Button8 : レ
  659  // Button9 : ミ
};


//------------------------------------------------
// VRAM操作
//------------------------------------------------

void clearVRAM()
{
  for (int i = 0; i < SCREEN_WIDTH; i++)
  {
    vram[i] = false;
  }
}

void setPixel(int x, bool on)
{
  if (x < 0 || x >= SCREEN_WIDTH) return;

  vram[x] = on;
}


//------------------------------------------------
// 74HC595へLED状態を出力
//
// vram[0]〜vram[7]  → 1個目の74HC595
// vram[8]〜vram[10] → 2個目の74HC595
//
// shiftOutは、先に2個目ぶんのデータを送り、
// 次に1個目ぶんのデータを送る。
// その後、LATCHをHIGHにしてLEDへ反映する。
//------------------------------------------------

void drawVRAM()
{
  byte b1 = 0;
  byte b2 = 0;

  for (int i = 0; i < SCREEN_WIDTH; i++)
  {
    if (vram[i])
    {
      if (i < 8)
      {
        b1 |= (1 << i);
      }
      else
      {
        b2 |= (1 << (i - 8));
      }
    }
  }

  digitalWrite(PIN_LATCH, LOW);

  shiftOut(PIN_SER, PIN_CLK, MSBFIRST, b2);
  shiftOut(PIN_SER, PIN_CLK, MSBFIRST, b1);

  digitalWrite(PIN_LATCH, HIGH);
}


//------------------------------------------------
// 74HC165 x2 読み取り
//
// 2個の74HC165から16bitぶんのボタン状態を読む。
// LOADをLOWにすると、各入力ピンの状態がIC内部に取り込まれる。
// その後、CLKを動かしながらDATAピンから1bitずつ読む。
//
// プルアップ接続なので、
// ・押していない → 1
// ・押している   → 0
// になる。
//------------------------------------------------

unsigned int read165x2()
{
  unsigned int data = 0;

  digitalWrite(PIN_165_CLK, LOW);
  delayMicroseconds(20);

  digitalWrite(PIN_165_LOAD, LOW);
  delayMicroseconds(20);

  digitalWrite(PIN_165_LOAD, HIGH);
  delayMicroseconds(20);

  data = digitalRead(PIN_165_DATA) ? 1 : 0;

  for (int i = 1; i < 16; i++)
  {
    digitalWrite(PIN_165_CLK, HIGH);
    delayMicroseconds(20);

    digitalWrite(PIN_165_CLK, LOW);
    delayMicroseconds(20);

    data <<= 1;

    if (digitalRead(PIN_165_DATA))
    {
      data |= 1;
    }
  }

  return data;
}


//------------------------------------------------
// ボタン割り当て
//
// read165x2()で読んだ16bitのうち、
// どのbitをどのボタンとして扱うかを定義する。
//
// Button0〜Button7  : 1個目の74HC165
// Button8〜Button10 : 2個目の74HC165
//
// H, G, F... の順番で読んでいるため、
// Button0 = #1 H になっている。
//------------------------------------------------

const unsigned int buttonMask[11] =
{
  0x8000, // Button0  = #1 H
  0x4000, // Button1  = #1 G
  0x2000, // Button2  = #1 F
  0x1000, // Button3  = #1 E
  0x0800, // Button4  = #1 D
  0x0400, // Button5  = #1 C
  0x0200, // Button6  = #1 B
  0x0100, // Button7  = #1 A
  0x0080, // Button8  = #2 H
  0x0040, // Button9  = #2 G
  0x0020  // Button10 = #2 F
};


//------------------------------------------------
// setup
//------------------------------------------------

void setup()
{
  // UIAPduino / CH32V003用の初期化処理。
  // 書き込みやデバッグピンの都合で必要になる場合がある。
  if (FLASH->STATR & (1<<14)) NVIC_SystemReset();
    SystemReset_StartMode(Start_Mode_BOOT);
    pinMode(PD4, OUTPUT);

  pinV32_DisconnectDebug(PD_1);

  pinMode(PIN_SPEAKER, OUTPUT);
  pinMode(PIN_ALARM, OUTPUT);
  digitalWrite(PIN_ALARM, LOW);

  pinMode(PIN_SER, OUTPUT);
  pinMode(PIN_LATCH, OUTPUT);
  pinMode(PIN_CLK, OUTPUT);

  pinMode(PIN_165_DATA, INPUT);
  pinMode(PIN_165_LOAD, OUTPUT);
  pinMode(PIN_165_CLK, OUTPUT);

  digitalWrite(PIN_LATCH, HIGH);
  digitalWrite(PIN_CLK, LOW);

  digitalWrite(PIN_165_LOAD, HIGH);
  digitalWrite(PIN_165_CLK, LOW);

  clearVRAM();
  drawVRAM();
}


//------------------------------------------------
// loop
//
// 1. 74HC165からボタン状態を読む
// 2. 押されたボタンに対応するLEDを点灯する
// 3. Button0〜Button9なら音階を鳴らす
// 4. Button10なら自励式ブザーを鳴らす
// 5. 74HC595へLED状態を反映する
//------------------------------------------------

void loop()
{
  unsigned int sw = read165x2();

  clearVRAM();

  int note = -1;

  // Button0〜Button9
  // 押されているボタンに対応するLEDを点灯し、
  // 最初に見つかったボタンの音を鳴らす。
  for (int i = 0; i < 10; i++)
  {
    if (!(sw & buttonMask[i]))
    {
      setPixel(i, true);

      if (note < 0)
      {
        note = i;
      }
    }
  }

  // Button10
  // LED10を点灯し、自励式ブザーを鳴らす。
  if (!(sw & buttonMask[10]))
  {
    setPixel(10, true);
    digitalWrite(PIN_ALARM, HIGH);
  }
  else
  {
    digitalWrite(PIN_ALARM, LOW);
  }

  drawVRAM();

  if (note >= 0)
  {
    tone(PIN_SPEAKER, noteTable[note]);
  }
  else
  {
    noTone(PIN_SPEAKER);
  }

  delay(10);
}

2026年6月12日金曜日

シフトレジスタ[74HC165]で大量のキー入力に挑戦! なんで資料が少ないの?

 シフトレジスタ[74HC595]を使って11個のLEDを点灯させることができたので、次は入力だ! ということでシフトレジスタ[74HC165]を使った11個のキー入力に挑戦したのだけれど、これが苦戦の連続だった。

 出力側につかうシフトレジスタ[74HC595]の参考資料やサンプルプログラムは、公式サイトやあちこちのブログに書かれている。でも入力側につかう[74HC165]に関しては、(自分が理解できる)ちょうど良いものを見つけることができなかったのだ。

 もちろん資料がゼロというわけではない。データシートや海外の解説記事も見つかった。でも初心者向けに「こう繋げば動くよ」と説明してくれる記事は少なく、自分には少しハードルが高かった。

 それから苦戦した原因のもう一つ。これは完全に自分が悪い。すぐに動くだろうと考えて、単体テストを行わずに、いきなり制作中の基板に[74HC165]を2つも繋げてしまった。

 そのせいで追加した回路が悪いのか? ICが壊れているのか? 元々動いているLED表示側回路が実は間違っていたのか? テストプログラムが間違っているのか? 原因を特定することができなくなってしまった……。

 さんざん試行錯誤をしたあとに冷静になって、入力側のシフトレジスタ[74HC165]を1つだけでテストする別の基板をつくってみた。ChatGPTに回路図やテストプログラムを何度も見てもらいながら、なんとか動かすことができるようになり、その後で[74HC165]を更に追加した。

 本当はブレッドボード2枚に全部の回路を納めようと思って詰め込んでいたけれど、また修正する可能性が高い。3枚構成にすることにした。一番下はユーザーインタフェース専用基板、2枚目はLED出力管理専用基板、3枚目はキー入力管理+UIAPduinoを設置する基板となった。

 ひとまず11個のLEDを、対応する11個のタクトスイッチを押すと光らせる所までは、つくることができた。次はブザーをつけて、音を鳴らすつもり。

 今度は絶対に単体テストをしてからやるぞー!


ブレッドボード3枚になってしまった!


人気の投稿