こんにちは、kinoppydです。SmartHRは、Kaigi on Rails 2025のブースで早押しクイズを提供しました。Rails/Ruby/Kaigi on Rails/SmartHRに関するクイズがランダムに出されて、2問正解で優勝、2問間違いで失格という形式での実施でした。2マル2バツと呼ばれるやつですね。
SmartHRは本日もKaigi on Railsのブースでお待ちしております😀 早押しクイズもありますよ💡
— SmartHR Developers (@smarthr_dev) September 27, 2025
ぜひ遊びにきてください〜!!#kaigionrails #kaigionrails_booth pic.twitter.com/a0IUYTkiw1
今回私が作ったのは、出題するクイズと、出題用の問題を出題者間で同期するためのアプリと、早押しボタンです。この記事では特に早押しボタンについて詳しくお話ししていこうかなと思います。
なお、この早押しボタンはSmartHRが公開している別コンテンツでも使用されているので、Kaigi on Rails当日に目にしなかった方は、是非こちらでご覧くだださい。こちらのピンポンブー音は後から合成しているので、実機のモノとは異なります。
早押しボタン
テレビ番組やYouTubeでクイズ企画を目にするとき、問題が読み上げられている中で最も早くボタンを押した人が回答できるクイズを見たことがあると思います。あれが早押しクイズで、押しているボタンが早押しボタンです。私はQuizKnockというクイズYouTuberが大好きで、毎日のように見ています。そしてある日気づいたのです。あのボタン、作れるんじゃない……? と。
早押しボタンというモノは、どうやら世の中にいくつか流派みたいなものがあり、QuizKnockが使っているものは早稲田式クイズ早押し機というものらしいです。ですが、特に何か共通の仕様とか規格というものがあるわけではなく、誰が作っているかなどで名称が決まるようでした。なので、じゃあまあ適当に作ってもオッケーだよな、と思い作ってみることにしました。
最低限考え得る仕様として、最も早くボタンを押した人のLEDが光る、それ以外の人のボタンを押しても何も反応しなくなる、リセットするとLEDの点灯を消して再び早押しができるようになる、の3つくらいで作り始めました。他にもピンポンブーの音が鳴るといいねとか、NマルMバツを内部的に覚えておければいいね、とかの仕様を追加していますが、基本的にハードウェアとしては人数分の早押しスイッチと、人数分のLED、リセット用のスイッチが付いていればあとはプログラム側でどうこうできそうだな、という考えです。
ちなみに、高畠式というスイッチを作っている高畠さん曰く、スイッチの標準はオムロンのVAQスイッチらしいですが、僕がこれを知ったのは作った後だったため、めっちゃ安いタクトスイッチを使っています。
PicoRuby
ここ最近、私はPicoRubyというRubyの処理系の一つを使って、電子工作をすることにハマっていました。PicoRubyとは、mruby/cという超軽量の組み込み用Ruby処理系と独自のコンパイラを組み合わせた、低メモリのマイクロコンピュータ上で動くRubyの処理系です。名前の通りかどうかは知りませんが、Raspberry Pi PicoとそのチップであるRP2040をターゲットにしています(もちろん他のチップでも動きますし、動かそうとしている人がたくさんいます)。 きっかけは関西Ruby会議08にプロポーザルを送るネタを考えていたとき、「そういえばPicoRubyってのがあるじゃーん、触ったことないけどなんか電子工作できるんやろ、電子工作やったことないから知らんけど」とかよくわかんないことを言いながら、CFP駆動開発も良いなと思ってプロポーザルを出してみました。ちょうど子供に何かおもちゃを作ってあげたいなと思っていた頃で、何かしらの方法で尻に火を付けないと絶対手を動かさないなとか考えていた記憶があります。結果としてプロポーザルは通らず、そのまま忘れかけていたのですが、その後のPicoRuby Overflow会議というイベントに展示で出しませんか? というお誘いをいただいたので、締め切り駆動開発でPicoRubyを学んでいき、なんかいろんなおもちゃが作れるようになりました。 そんなこんなしているうちに、Kaigi on Rails 2025のブース企画を考えるときが来て、私は気づきました。あれ、今の自分なら早押しボタン作って、会場でクイズやれるんじゃね? と。気づいてから、いくつかあったブース企画のうちの一つであるクイズ案を、「私が早押しボタンを作ります」と言って無理矢理早押しクイズに変貌させ、当日に至りました。
外見

早押しボタンです。親機が1台、子機が3台あり、待機状態のときに子機のうち最も早くボタンを押した1台のランプを点灯させます。それ以外の子機は一切操作ができなくなり、親機の方で正解/不正解/リセットのボタンを押すことで再び待機状態に戻ります。
コード
コード全体はGistに貼り付けています。実際には内部に点数計算用のコードと、7セグLEDでそれを表示するためのコードが書かれているのですが、本体に7セグを取り付けていないためデッドコードになっており、ここでは削除しました。
基本的には、ループを使ってGPIOの信号を順番に読み、状態が変わったものを「ボタンを押した」と検出することでループを脱出します。GPIOの割り込み処理は使っていません。
def scan puts "Scanning" candidate = nil until candidate @answer_switches.each_with_index do |answer_switch, i| if answer_switch.switch.low? puts "Push button #{i + 1} detected" candidate = answer_switch break end end check_all_reset end candidate end
その理由としては、ボタンを作った時点ではPicoRubyは割り込み処理に対応していなかったからです。このボタンを完成させたのはKaigi on Rails 開催より遡ること1ヶ月ほど前なのですが、完成から開催までの1ヶ月の間に、PicoRubyの作者のhasumikinさんが割込を実装していたらしく、タイミングがかみ合わなかったことが無念です。というか、実は当日までIRQが実装されてリリースされていたことを知らず、当日会場でhachiさんに「えっ、実装されたのに割込使ってないの」と告げられたときは膝から崩れ落ちました。
PicoRubyにIRQを実装しました。
— 篠笛練習中 (@hasumikin) August 20, 2025
とりあえずRP2040/RP2350のGPIOだけ。UARTやADCなど他のペリフェラルとかESP32移植のプルリク歓迎です!(まだexperimentalというか、mrubyファミリのガイドライン策定前に作ってしまったので、デザイン変更あるかも。でも気にせずPRください)https://t.co/EnzUvrFkZs
そのうちもう一台作る予定なので、そのときには割込をつかって処理を書き直したいなと思っています。
他には、PicoRubyというかmruby/cはthrowが使えないため、大域脱出に専用のエラーを作成して逐一チェックしていたりします。この逐一チェックもあまりよくないので、割り込みでどうにかなる気がします。
回路
回路図です。ちゃんと描けてるかちょっと自信がありません。

大量にボタンが付いているだけで、全体としては非常にシンプルな構成になっています。謎のチップが付いていますが、これに関しては後で解説します。実際の写真で見るとこんな感じです。

合計で7個のスイッチ(画面上は6個ですが、実際にはフルリセットスイッチの7個目があります)を内部プルアップでGPIOに繋いで、そのスイッチが押されたことをプログラムが検知し、内部で処理を行います。具体的な処理は、対応しているLEDを光らせて、スイッチが押されたことを知らせる音を鳴らし、他のスイッチの押下に反応しないようにします。その状態を維持することで、正誤判定待ちモードとしています。
左側の赤いスイッチは早押し用のスイッチなので、実際には非常に長いケーブルをつかって本体とは別のユニバーサル基板上に実装されています。XHコネクタという、ケーブルを抜き差しするためのポストとハウジングに、自分でシリコン被覆の銅線ケーブルを圧着してケーブルを作成しています。そのままでは3本のバラバラな線なので、ポリエステルの網で作られたスリーブで覆って一本の太いケーブルにしています。
一般的に、スイッチを使うような回路を作るときには、スイッチの電気的なチャタリングなどの誤動作に対応する必要があります。チャタリングとは、かいつまんで言うとスイッチを押した直後に回路を流れる電気が急激に上がったり下がってりして、ボタンを連打しているような挙動になってしまうことです。そのため、通常はハードやソフト側で何かしらの対策を入れるのですが、早押しボタンは早く押せたことだけを検知すればよく、ボタンが連打されてようがなんだろうが知ったことでは無いので、そのあたりのケアをしなくて良いのが非常に楽でした。
謎のチップの正体
この早押しボタンは、ボタンを押したとき、正解したとき、誤答したときにそれぞれピンポンブーの音が鳴るのですが、この音を出すのは案外難しく、期限までに自分で作るのは無理だなと思いました。そこで、市販のピンポンブーボタンの部品を取り出し、音を鳴らす箇所だけを乗っ取り部品の一部として取り込んでしまおうと考えました。 検索したところ、100円ショップのダイソーで売っているピンポンブーボタン(ちなみに100円ではない、300円くらいします)を分解して内部のチップの仕様を分析しているサイトがあったので、それを参考にしました。取り出したチップの特定のピンに3Vを印加すると音が鳴るそうなので、チップと付属のスピーカーを取り出し、PicoRubyからGPIOでそれらの印加をコントロールできるようにしました。
M5Stack BasicとM5Stamp Pico 、UIFlowでダイソー「ピンポンブザー」を早押し対応にする - 内部構造の調査|shintaro
結果として音はちゃんと鳴ってくれるようにはなったのですが、PicoRubyでは非同期処理ができないため、音を鳴らす間ずっと印加する必要がありました。このチップの音をコントロールするためだけにsleepを使ってしまい、もっと良い方法はないかなと思ったりしました。まだ試していませんが、これも割り込み処理があれば解決するのかも知れません。

他には、音が意外と小さいという問題があったので、次に作るときはアンプが必須のようです。
筐体
早押しボタンの親機と子機の筐体は、3Dプリンタをつかって作成しました。PicoRuby Overflow会議に参加する駆動で買ったBambu A1 miniを使って自作しました。本体の方はまあ安定してボタンが押せれば何でもいいんですが、子機の方は人が握ってスイッチを押すので、なんとなく持ち心地が良さそうなサイズをいくつか試作してみました。

ボタンの内部にはこれが入っています。カードサイズのユニバーサル基板上にLED、スイッチ、XHのポストを固定してあります。これを、ネジでケースに固定していましたが、当日いきなり強く押しすぎて1個破損するという事故があったので、ネジではなく本体にユニバーサル基板がすっぽり収まるスリットなどを用意して固定するべきだったなと反省しました。そういう小さなトライアンドエラーを繰り返せるのも、3Dプリンタの良いところです。
改善点
次に作るとしたら、早押しの検知処理は割込を使いたいです。ループでも十分に動きますが、コードがかなり状態に依存するのであんまり好ましくないかなと思っています。組み込みのコードは、通常のWebアプリなどと違ってあまり潤沢にメモリは使えないので、状態を持つコードになるのはある程度仕方ないとは思います。ですが、動くのであればそれが少なければ少ない方が良いかなと思ってます。
何かしらのディスプレイも装備したいなと思っています。会場でも誰が何マルで何バツか結構忘れるケースが多かったので、人が入れ替わり立ち替わりするような場所で使うためには人間の脳メモリを省略してあげたいです。
親機と子機を繋ぐケーブルの取り回しが難しかったので、既製品のTRSケーブル(ヘッドホンや分割キーボードを繋いでるアレです)を使っても良かったかなと思いました。とはいえ、理想の長さのTRSケーブルってどうやって手に入れるんだろう? という疑問はありますが。圧着を使ってXHのハウジングを作るのも面倒でしたし、既製品を使いたい。耐久性もあるでしょうし。
他にも、プログラム的に様々なクイズのモードに対応できるはずなので、対応したいです。たとえば押した順番が記憶されているとか、そういうやつですね。真面目に複雑なことをやると、今回逃げてしまったチャタリング対策が必要になりますが。
貸し出ししますよ
あとどうでもいい話かも知れませんが、もし早押しボタンをイベントで使いたいと思ってるRubyの会社がありましたら、是非ご相談ください。お貸しします。その場合には、是非SmartHRは積極採用中ですよという宣伝とともにお届けしたいと思います。