2018/12/15 追記:タッチセンサーの処理を修正しました。
2018/07/15 追記:NVSに選局、音量調節の状態を保持するようにしました。
ESP32単体でFPGA内のAvalonバスを操作できるAvalonPacketライブラリを作ったので、ESP32のタッチセンサーを使って選局、音量調節をできるようにしました。ESP32から4本の配線(緑色)をプラケースの外に引き出しただけです。メカスイッチだといつも固定方法に悩みますがタッチセンサーは楽ちんです。
使っているESP32は、10本のタッチセンサーT0~T9の内、T1、T2、T5がほかの用途に使われているためタッチセンサーとしては機能しませんでした。FPGAラジオには選局にT4、T6を、音量調節にT8、T9を使っています。
選局は、周波数からNCOの設定値を計算してFPGA内のAvalonバス上のPIO(アドレス0x10)に書き込みます。
音量調節は、PIO(アドレス0x0)に書き込んでI2S DACの出力レベルを操作します。
#include <SPI.h>
#include <AvalonPacket.h>
#include <Wire.h>
#include "SSD1306Wire.h"
#include <nvs.h>
uint8_t volume = 0;
uint8_t station = 0;
const int NUM_STATIONS = 10;
unsigned long freq[NUM_STATIONS] = {
594000,
810000,
954000,
1134000,
80000000,
81300000,
82500000,
90500000,
91600000,
93000000
};
String freqString[NUM_STATIONS] = {
"594 kHz",
"810 kHz",
"954 kHz",
"1134 kHz",
"80.0 MHz",
"81.3 MHz",
"82.5 MHz",
"90.5 MHz",
"91.6 MHz",
"93.0 MHz"
};
String stationName[NUM_STATIONS] = {
"NHK",
"AFN",
"TBS",
"JOQR",
"TOKYO FM",
"J-WAVE",
"NHK FM",
"TBS FM",
"JOQR FM",
"NIPPON FM"
};
SSD1306Wire display(0x3c, 21, 22);
void printBytes(byte *bytes, int bytesSize);
nvs_handle handle = 0;;
AvalonPacket ap = AvalonPacket();
byte response[4];
int responseSize;
int threshold = 60;
volatile unsigned long touchMillis = 0;
volatile int touchId = 0;
void gotTouch(int id) {
touchAttachInterrupt(T4, dummy, 0);
touchAttachInterrupt(T6, dummy, 0);
touchAttachInterrupt(T8, dummy, 0);
touchAttachInterrupt(T9, dummy, 0);
touchMillis = millis();
touchId = id;
}
void dummy() { }
void gotTouch1() {
if (touchRead(T4) < threshold) {
gotTouch(1);
}
}
void gotTouch2() {
if (touchRead(T6) < threshold) {
gotTouch(2);
}
}
void gotTouch3() {
if (touchRead(T8) < threshold) {
gotTouch(3);
}
}
void gotTouch4() {
if (touchRead(T9) < threshold) {
gotTouch(4);
}
}
void setup() {
Serial.begin(57600);
display.init();
display.flipScreenVertically();
nvs_open("FPGA Radio", NVS_READWRITE, &handle);
if (handle != 0) {
nvs_get_u8(handle, "volume", &volume);
nvs_get_u8(handle, "station", &station);
}
if (15 < volume) volume = 15;
if (NUM_STATIONS-1 < station) station = NUM_STATIONS-1;
delay(1000); // Wait until FPGA is configured
sendVolume(volume);
sendFreq(freq[station]);
updateDisplay();
touchAttachInterrupt(T4, gotTouch1, threshold);
touchAttachInterrupt(T6, gotTouch2, threshold);
touchAttachInterrupt(T8, gotTouch3, threshold);
touchAttachInterrupt(T9, gotTouch4, threshold);
}
void loop() {
if (touchId > 0) {
Serial.print("Touch: ");
Serial.println(touchId);
if (touchId == 1) {
if (station > 0) {
station -= 1;
} else {
station = NUM_STATIONS-1;
}
} else if (touchId == 2) {
if (station < NUM_STATIONS-1) {
station += 1;
} else {
station = 0;
}
} else if (touchId == 3) {
if (volume > 0) {
volume -= 1;
}
} else if (touchId == 4) {
if (volume < 15) {
volume += 1;
}
}
sendFreq(freq[station]);
sendVolume(volume);
if (handle != 0) {
nvs_set_u8(handle, "station", station);
nvs_set_u8(handle, "volume", volume);
}
updateDisplay();
touchId = -1;
} else if (touchId < 0 && touchMillis + 300 < millis()) {
touchId = 0;
touchAttachInterrupt(T4, gotTouch1, threshold);
touchAttachInterrupt(T6, gotTouch2, threshold);
touchAttachInterrupt(T8, gotTouch3, threshold);
touchAttachInterrupt(T9, gotTouch4, threshold);
}
}
void updateDisplay() {
display.clear();
display.setFont(ArialMT_Plain_24);
display.drawString(0, 0, stationName[station]);
display.drawString(0, 24, freqString[station]);
display.setFont(ArialMT_Plain_24);
String str = "";
for (int i = 0; i < volume; i++) {
str += "*";
}
display.drawString(0, 48, str);
display.display();
}
void sendVolume(unsigned long volume) { // 0 - 15
responseSize = ap.write(0, volume, response);
//ap.printBytes(response, responseSize);
}
void sendFreq(unsigned long freq) { // in Hz
unsigned long refFreq = 1000000; // 1MHz
unsigned long refValue = 58236845; // NCO phase increment
if (freq >= 73750000) // 73.75MHz
freq -= 73750000;
unsigned long data = (unsigned long)((double)freq / (double)refFreq * (double)refValue);
responseSize = ap.write(0x10, data, response);
//ap.printBytes(response, responseSize);
}