Compare commits
2
Commits
4f3eacf9f7
...
3ba60ca611
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ba60ca611 | ||
|
|
3c1e42ac53 |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
.pio
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Change Log KT0803
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.1] - 2025-11-26
|
||||||
|
- update GitHub actions
|
||||||
|
- add derived classes KT0803L and KT0803M (wrappers for now)
|
||||||
|
- update readme.md
|
||||||
|
- minor edits
|
||||||
|
|
||||||
|
## [0.3.0] - 2024-03-27
|
||||||
|
- fix #5, getChannel()
|
||||||
|
- fix KT0803K setChannel()
|
||||||
|
- fix readData()
|
||||||
|
- add several KT0803K specific functions (experimental)
|
||||||
|
- bool setMono()
|
||||||
|
- bool setStereo()
|
||||||
|
- bool isStereo()
|
||||||
|
- bool setBass(uint8_t bass)
|
||||||
|
- uint8_t getBass()
|
||||||
|
- bool powerOK()
|
||||||
|
- bool silenceDetected()
|
||||||
|
- update GitHub actions
|
||||||
|
- update readme.md
|
||||||
|
- update keywords.txt
|
||||||
|
- minor edits
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-03-09
|
||||||
|
- Fix #2 frequency to channel formula
|
||||||
|
- add derived **class KT0803K**
|
||||||
|
- add examples
|
||||||
|
- add parameter checks functions.
|
||||||
|
- add default frequency + mute to **begin()**
|
||||||
|
- update documentation
|
||||||
|
- update GitHub actions
|
||||||
|
- minor edits
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## [0.1.0] - 2023-12-27
|
||||||
|
- initial version
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803.cpp
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// VERSION: 0.3.1
|
||||||
|
// PURPOSE: Arduino Library for KT0803 and KT0803K FM transmitter.
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "src/KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
// REGISTERS on page 7 datasheet
|
||||||
|
|
||||||
|
|
||||||
|
KT0803::KT0803(TwoWire * wire)
|
||||||
|
{
|
||||||
|
_address = 0x3E;
|
||||||
|
_wire = wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::begin(float freq, bool mute)
|
||||||
|
{
|
||||||
|
if (! isConnected()) return false;
|
||||||
|
if (! setMute(mute)) return false;
|
||||||
|
return setFrequency(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::isConnected()
|
||||||
|
{
|
||||||
|
_wire->beginTransmission(_address);
|
||||||
|
return (_wire->endTransmission() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// FREQUENCY
|
||||||
|
//
|
||||||
|
bool KT0803::setFrequency(float MHz)
|
||||||
|
{
|
||||||
|
if ((MHz < 70) || (MHz > 108)) return false;
|
||||||
|
// steps 50 KHz although KT0803 will truncate to 100 KHz.
|
||||||
|
return setChannel(round(MHz * 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MHz
|
||||||
|
float KT0803::getFrequency()
|
||||||
|
{
|
||||||
|
return getChannel() * 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// steps of 50 KHz.
|
||||||
|
bool KT0803::setChannel(uint16_t channel)
|
||||||
|
{
|
||||||
|
if ((channel < 1400) || (channel > 2160)) return false;
|
||||||
|
// need to split over 3 registers
|
||||||
|
// register 2 part skipped (always 0) for KT0803
|
||||||
|
uint16_t ch = channel >> 1;
|
||||||
|
|
||||||
|
uint8_t register0 = ch & 0xFF; // CHSEL[8:1]
|
||||||
|
if (writeData(0x00, register0) == false) return false;
|
||||||
|
|
||||||
|
ch >>= 8;
|
||||||
|
uint8_t register1 = readData(0x01);
|
||||||
|
register1 &= 0xF8; // CHSEL[11:9]
|
||||||
|
register1 |= (ch & 0x07); // CHSEL[11:9]
|
||||||
|
return writeData(0x01, register1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t KT0803::getChannel()
|
||||||
|
{
|
||||||
|
uint16_t channel = readData(0x01) & 0x07;
|
||||||
|
channel <<= 8;
|
||||||
|
channel |= readData(0x00);
|
||||||
|
channel <<= 1;
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// GAIN
|
||||||
|
//
|
||||||
|
bool KT0803::setPGA(uint8_t pga)
|
||||||
|
{
|
||||||
|
if (pga > 7) return false;
|
||||||
|
uint8_t data = readData(0x01);
|
||||||
|
data &= 0xC7; // keep other bits
|
||||||
|
data |= (pga << 3);
|
||||||
|
return writeData(0x01, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t KT0803::getPGA()
|
||||||
|
{
|
||||||
|
return (readData(0x01) >> 3) & 0x07;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::setRFGain(uint8_t rfgain)
|
||||||
|
{
|
||||||
|
if (rfgain > 15) return false;
|
||||||
|
// bits 0 and 1
|
||||||
|
uint8_t data = readData(0x01) & 0x3F;
|
||||||
|
data |= (rfgain & 0x03) << 6;
|
||||||
|
writeData(0x01, data);
|
||||||
|
// bit 2
|
||||||
|
data = readData(0x13) & 0x7F;
|
||||||
|
data |= (rfgain & 0x04) << 5;
|
||||||
|
writeData(0x13, data);
|
||||||
|
// bit 3
|
||||||
|
data = readData(0x02) & 0xBF;
|
||||||
|
data |= (rfgain & 0x08) << 3;
|
||||||
|
writeData(0x02, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t KT0803::getRFgain()
|
||||||
|
{
|
||||||
|
uint8_t data = readData(0x01) >> 6; // bit 0, 1
|
||||||
|
data |= ((readData(0x13) & 0x80) >> 5); // bit 2
|
||||||
|
data |= ((readData(0x02) & 0x40) >> 3); // bit 3
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::setPHTCNST(bool on)
|
||||||
|
{
|
||||||
|
uint8_t data = readData(0x02);
|
||||||
|
// is the bit already OK
|
||||||
|
if ((on == true) && (data & 0x01) == 0x01) return true;
|
||||||
|
if ((on == false) && (data & 0x01) == 0x00) return true;
|
||||||
|
// otherwise flip the bit
|
||||||
|
data = data ^ 0x01;
|
||||||
|
return writeData(0x02, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// MISC
|
||||||
|
//
|
||||||
|
bool KT0803::getPHTCNST()
|
||||||
|
{
|
||||||
|
return (readData(0x02) & 0x01) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::setPilotToneAdjust(uint8_t mode)
|
||||||
|
{
|
||||||
|
if (mode > 1) return false;
|
||||||
|
uint8_t data = readData(0x02);
|
||||||
|
// is the bit already OK
|
||||||
|
if ((mode == 1) && (data & 0x04) == 0x04) return true;
|
||||||
|
if ((mode == 0) && (data & 0x04) == 0x00) return true;
|
||||||
|
// otherwise flip the bit
|
||||||
|
data = data ^ 0x04;
|
||||||
|
return writeData(0x02, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t KT0803::getPilotToneAdjust()
|
||||||
|
{
|
||||||
|
return (readData(0x02) & 0x04) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// MUTE
|
||||||
|
//
|
||||||
|
bool KT0803::setMute(bool mute)
|
||||||
|
{
|
||||||
|
uint8_t data = readData(0x02);
|
||||||
|
// is the bit already OK
|
||||||
|
if ((mute == true) && (data & 0x08) == 0x08) return true;
|
||||||
|
if ((mute == false) && (data & 0x08) == 0x00) return true;
|
||||||
|
// otherwise flip the bit
|
||||||
|
data = data ^ 0x08;
|
||||||
|
return writeData(0x02, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803::getMute()
|
||||||
|
{
|
||||||
|
return (readData(0x02) & 0x08) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// PROTECTED
|
||||||
|
//
|
||||||
|
bool KT0803::writeData(uint8_t reg, uint8_t data)
|
||||||
|
{
|
||||||
|
_wire->beginTransmission(_address);
|
||||||
|
_wire->write(reg);
|
||||||
|
_wire->write(data);
|
||||||
|
return (_wire->endTransmission() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int KT0803::readData(uint8_t reg)
|
||||||
|
{
|
||||||
|
_wire->beginTransmission(_address);
|
||||||
|
_wire->write(reg);
|
||||||
|
_wire->endTransmission(false); // explicit no STOP fig 3 page 4
|
||||||
|
|
||||||
|
if (_wire->requestFrom(_address, (uint8_t) 1) == 1)
|
||||||
|
{
|
||||||
|
uint8_t data = _wire->read();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DERIVED CLASS KT0803K
|
||||||
|
//
|
||||||
|
KT0803K::KT0803K(TwoWire * wire) : KT0803(wire)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803K::setChannel(uint16_t channel)
|
||||||
|
{
|
||||||
|
if ((channel < 1400) || (channel > 2160)) return false;
|
||||||
|
// need to split over 3 registers
|
||||||
|
uint16_t ch = channel;
|
||||||
|
|
||||||
|
uint8_t register2 = readData(0x02) & 0x7F;
|
||||||
|
register2 |= (channel & 0x01) << 7; // CHSEL[0]
|
||||||
|
if (writeData(0x02, register2) == false) return false;
|
||||||
|
ch >>= 1;
|
||||||
|
|
||||||
|
uint8_t register0 = ch & 0xFF; // CHSEL[8:1]
|
||||||
|
if (writeData(0x00, register0) == false) return false;
|
||||||
|
|
||||||
|
ch >>= 8;
|
||||||
|
uint8_t register1 = readData(0x01);
|
||||||
|
register1 &= 0xF8; // CHSEL[11:9]
|
||||||
|
register1 |= (ch & 0x07); // CHSEL[11:9]
|
||||||
|
return writeData(0x01, register1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t KT0803K::getChannel()
|
||||||
|
{
|
||||||
|
uint16_t channel = readData(0x01) & 0x07;
|
||||||
|
channel <<= 8;
|
||||||
|
channel |= readData(0x00);
|
||||||
|
channel <<= 1;
|
||||||
|
channel |= (readData(0x02) >> 7);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// KT0803K SPECIFIC
|
||||||
|
//
|
||||||
|
bool KT0803K::setMono()
|
||||||
|
{
|
||||||
|
uint8_t register4 = readData(0x04);
|
||||||
|
if (register4 & (1 << 6))
|
||||||
|
{
|
||||||
|
register4 &= ~(1 << 6);
|
||||||
|
return writeData(0x04, register4);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803K::setStereo()
|
||||||
|
{
|
||||||
|
uint8_t register4 = readData(0x04);
|
||||||
|
if ((register4 & (1 << 6)) == 0)
|
||||||
|
{
|
||||||
|
register4 |= (1 << 6);
|
||||||
|
return writeData(0x04, register4);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KT0803K::isStereo()
|
||||||
|
{
|
||||||
|
uint8_t register4 = readData(0x04);
|
||||||
|
return (register4 & (1 << 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803K::setBass(uint8_t bass) // 0..3 = 0, 5, 11, 17 dB
|
||||||
|
{
|
||||||
|
if (bass > 3) return false;
|
||||||
|
uint8_t register4 = readData(0x04);
|
||||||
|
register4 &= ~0x03; // mask off bits
|
||||||
|
register4 |= bass;
|
||||||
|
return writeData(0x04, register4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t KT0803K::getBass()
|
||||||
|
{
|
||||||
|
uint8_t register4 = readData(0x04);
|
||||||
|
return register4 & 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803K::powerOK()
|
||||||
|
{
|
||||||
|
uint8_t register0F = readData(0x0F);
|
||||||
|
return (register0F & (1 << 4)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool KT0803K::silenceDetected()
|
||||||
|
{
|
||||||
|
uint8_t register0F = readData(0x0F);
|
||||||
|
return (register0F & (1 << 2)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DERIVED CLASS KT0803L
|
||||||
|
//
|
||||||
|
KT0803L::KT0803L(TwoWire * wire) : KT0803K(wire)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DERIVED CLASS KT0803M
|
||||||
|
//
|
||||||
|
KT0803M::KT0803M(TwoWire * wire) : KT0803K(wire)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#pragma once
|
||||||
|
//
|
||||||
|
// FILE: KT0803.h
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// VERSION: 0.3.1
|
||||||
|
// PURPOSE: Arduino Library for KT0803 and KT0803K FM transmitter
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define KT0803_LIB_VERSION (F("0.3.1"))
|
||||||
|
|
||||||
|
|
||||||
|
class KT0803
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KT0803(TwoWire * wire = &Wire);
|
||||||
|
|
||||||
|
bool begin(float freq = 90.0, bool mute = true);
|
||||||
|
bool isConnected();
|
||||||
|
|
||||||
|
|
||||||
|
// FM FREQUENCY (70-108)
|
||||||
|
// KT0803 truncates to 0.1 MHz
|
||||||
|
// KT0803K (L&M) truncates to 0.05 MHz
|
||||||
|
bool setFrequency(float MHz);
|
||||||
|
float getFrequency();
|
||||||
|
|
||||||
|
|
||||||
|
// FM CHANNEL (1400-2160)
|
||||||
|
// KT0803 only supports even channels
|
||||||
|
// KT0803K (L&M) supports all channels
|
||||||
|
bool setChannel(uint16_t channel);
|
||||||
|
uint16_t getChannel();
|
||||||
|
|
||||||
|
|
||||||
|
// GAIN
|
||||||
|
bool setPGA(uint8_t pga); // 0-3 = 0..-12dB 4-7 = 0..12dB
|
||||||
|
// 111: 12dB
|
||||||
|
// 110: 8dB
|
||||||
|
// 101: 4dB
|
||||||
|
// 100: 0dB
|
||||||
|
// 000: 0dB
|
||||||
|
// 001: -4dB
|
||||||
|
// 010: -8dB
|
||||||
|
// 011: -12dB
|
||||||
|
uint8_t getPGA();
|
||||||
|
|
||||||
|
bool setRFGain(uint8_t rfgain); // 0-15
|
||||||
|
uint8_t getRFgain();
|
||||||
|
|
||||||
|
|
||||||
|
// REGION SELECTION
|
||||||
|
// first four are wrappers
|
||||||
|
void setEurope() { setPHTCNST(true); };
|
||||||
|
void setAustralia() { setPHTCNST(true); };
|
||||||
|
void setUSA() { setPHTCNST(false); };
|
||||||
|
void setJapan() { setPHTCNST(false); };
|
||||||
|
bool setPHTCNST(bool on);
|
||||||
|
bool getPHTCNST();
|
||||||
|
|
||||||
|
|
||||||
|
// PILOT TONE ADJUST (PLTADJ)
|
||||||
|
bool setPilotToneAdjust(uint8_t mode); // HIGH = 1 LOW = 0
|
||||||
|
uint8_t getPilotToneAdjust();
|
||||||
|
|
||||||
|
|
||||||
|
// MUTE software
|
||||||
|
bool setMute(bool mute); // true == muted
|
||||||
|
bool getMute(); // isMuted().
|
||||||
|
|
||||||
|
|
||||||
|
// direct access to registers - debug / develop
|
||||||
|
// to access not implemented functions.
|
||||||
|
bool writeData(uint8_t reg, uint8_t data);
|
||||||
|
int readData(uint8_t reg);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
uint8_t _address = 0x3E; // fixed address for KT0803.
|
||||||
|
TwoWire * _wire = NULL;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// DERIVED CLASSES
|
||||||
|
//
|
||||||
|
class KT0803K : public KT0803
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KT0803K(TwoWire * wire = &Wire);
|
||||||
|
|
||||||
|
// CHANNEL
|
||||||
|
bool setChannel(uint16_t channel);
|
||||||
|
uint16_t getChannel();
|
||||||
|
|
||||||
|
// KT0803K SPECIFIC
|
||||||
|
bool setMono();
|
||||||
|
bool setStereo();
|
||||||
|
bool isStereo();
|
||||||
|
|
||||||
|
bool setBass(uint8_t bass); // 0..3 = 0, 5, 11, 17 dB
|
||||||
|
uint8_t getBass();
|
||||||
|
|
||||||
|
bool powerOK();
|
||||||
|
bool silenceDetected();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class KT0803M : public KT0803K
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KT0803M(TwoWire * wire = &Wire);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class KT0803L : public KT0803K
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KT0803L(TwoWire * wire = &Wire);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023-2025 Rob Tillaart
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,377 @@
|
|||||||
|
|
||||||
|
[](https://github.com/marketplace/actions/arduino_ci)
|
||||||
|
[](https://github.com/RobTillaart/KT0803/actions/workflows/arduino-lint.yml)
|
||||||
|
[](https://github.com/RobTillaart/KT0803/actions/workflows/jsoncheck.yml)
|
||||||
|
[](https://github.com/RobTillaart/KT0803/issues)
|
||||||
|
|
||||||
|
[](https://github.com/RobTillaart/KT0803/blob/master/LICENSE)
|
||||||
|
[](https://github.com/RobTillaart/KT0803/releases)
|
||||||
|
[](https://registry.platformio.org/libraries/robtillaart/KT0803)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# KT0803
|
||||||
|
|
||||||
|
Arduino Library for KT0803 and KT0803K FM transmitter.
|
||||||
|
|
||||||
|
|
||||||
|
### Legal point of attention
|
||||||
|
|
||||||
|
In different countries there are different laws with respect to using transmitting devices
|
||||||
|
and their range.
|
||||||
|
Please inform yourself of the local rules and laws if and how you may or may not use a
|
||||||
|
device like the KT0803 in your projects, either hobby, commercial or otherwise.
|
||||||
|
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
**Experimental**
|
||||||
|
|
||||||
|
This experimental library allows basic control of the KT0803 and / or the KT0803K
|
||||||
|
FM transmitter device.
|
||||||
|
It is primary written to understand the possibilities and the interface of the device.
|
||||||
|
|
||||||
|
The library is not tested by me with hardware yet. See future below.
|
||||||
|
However it is verified to work by others.
|
||||||
|
|
||||||
|
There are newer, more capable, follow up devices like model K, L and M.
|
||||||
|
From these only the KT0803K is supported as a derived class.
|
||||||
|
The L and the M versions of the device will work with the KT0803K class too
|
||||||
|
as their functionality is equal or exceeds the K version.
|
||||||
|
As far as investigated the L and M versions are backwards compatible.
|
||||||
|
|
||||||
|
Version 0.3.0 implements a few KT0803K specific functions, see section below.
|
||||||
|
These will work for L and M devices too.
|
||||||
|
|
||||||
|
For ATTinyX5 series there exists the TinyKT0803 class which is derived from this one.
|
||||||
|
It uses a different I2C implementation. See - https://github.com/RobTillaart/TinyKT0803
|
||||||
|
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
|
||||||
|
Read datasheet for details.
|
||||||
|
|
||||||
|
**Warning**
|
||||||
|
The KT0803 is an 3.3 Volt device and cannot be connected directly to 5V MCU's.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------+
|
||||||
|
| KT0803 |
|
||||||
|
GND --| 1 16 |-- PA_OUT RF analog output
|
||||||
|
Crystal XI --| 2 15 |-- GND
|
||||||
|
Crystal XO --| 3 14 |-- SCL I2C Clock
|
||||||
|
3.3V IOVDD --| 4 13 |-- SCA I2C Data
|
||||||
|
GND --| 5 12 |-- GND
|
||||||
|
in left INL --| 6 11 |-- GND
|
||||||
|
in right INR --| 7 10 |-- RSTB Reset
|
||||||
|
enable SW --| 8 9 |-- GND
|
||||||
|
| |
|
||||||
|
+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Frequency range
|
||||||
|
|
||||||
|
The frequency range stated on the front page of the datasheet ==> 70 MHz - 108 MHz.
|
||||||
|
The frequency range stated in table 2 ==> 76 MHz - 108 MHz.
|
||||||
|
So the datasheet is at least ambiguous on this point.
|
||||||
|
|
||||||
|
Keep in mind that the frequency range allowed differs per country.
|
||||||
|
The library does not provide this filtering, explicit responsibility of the user.
|
||||||
|
|
||||||
|
|
||||||
|
### Differences
|
||||||
|
|
||||||
|
The KT0803K device has far more options, which are not all implemented.
|
||||||
|
There is one important, the resolution or step-size of the frequency.
|
||||||
|
|
||||||
|
| device | step-size | Notes |
|
||||||
|
|:---------:|:-----------:|:--------|
|
||||||
|
| KT0803 | 100 KHz | in code all math is done with 50 KHz
|
||||||
|
| KT0803K | 50 KHz |
|
||||||
|
|
||||||
|
Backwards compatible.
|
||||||
|
According to the datasheet code for the KT0803 should work for the KT0803K.
|
||||||
|
Code with the KT0803K class will probably not work on a KT0803.
|
||||||
|
|
||||||
|
|
||||||
|
### Transmit frequency
|
||||||
|
|
||||||
|
The transmit frequency can be set with **setFrequency(MHz)** or by **setChannel(channel)**.
|
||||||
|
Note that the channel and frequency math of the KT0803 and the KT0803K is aligned
|
||||||
|
in this library. This allows exchange of channel data between device types.
|
||||||
|
|
||||||
|
Note that the KT0803 will internally round to use 100 KHz steps.
|
||||||
|
|
||||||
|
Some examples:
|
||||||
|
|
||||||
|
| Frequency | Channel | Notes |
|
||||||
|
|:------------:|:---------:|:-------:|
|
||||||
|
| 70.00 MHz | 1400 | channel = freq (Mhz) \* 20
|
||||||
|
| 70.05 MHz | 1401 | freq = channel \* 0.05
|
||||||
|
| 70.10 MHz | 1402 |
|
||||||
|
| 76.00 MHz | 1520 |
|
||||||
|
| 80.00 MHz | 1600 |
|
||||||
|
| 89.70 MHz | 1794 | default (see registers datasheet)
|
||||||
|
| 100.00 MHz | 2000 |
|
||||||
|
| 101.30 MHz | 2026 |
|
||||||
|
| 105.70 MHz | 2114 |
|
||||||
|
| 108.00 MHz | 2160 |
|
||||||
|
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
- https://github.com/RobTillaart/KT0803
|
||||||
|
- https://github.com/RobTillaart/TinyKT0803
|
||||||
|
- https://www.hackster.io/hesam-moshiri/stereo-digital-fm-transmitter-circuit-arduino-code-2dbd8d
|
||||||
|
- https://www.hackster.io/hesam-moshiri/full-digital-fm-receiver-with-arduino-and-tea5767-52be37
|
||||||
|
- https://www.hackerstore.nl/Artikel/388
|
||||||
|
- https://en.wikipedia.org/wiki/FM_broadcasting
|
||||||
|
|
||||||
|
|
||||||
|
## Interface KT0803
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "KT0803.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
- **KT0803(TwoWire \*wire = &Wire)** constructor,
|
||||||
|
optional Wire interface.
|
||||||
|
- **KT0803K(TwoWire \*wire = &Wire)** constructor,
|
||||||
|
optional Wire interface.
|
||||||
|
- **KT0803L(TwoWire \*wire = &Wire)** constructor,
|
||||||
|
optional Wire interface.
|
||||||
|
- **KT0803M(TwoWire \*wire = &Wire)** constructor,
|
||||||
|
optional Wire interface.
|
||||||
|
- **bool begin(float freq = 90.0, bool mute = true)** initializes the library.
|
||||||
|
Furthermore it checks if the deviceAddress is available on the I2C bus.
|
||||||
|
Default it sets the frequency to 90 MHz and **mutes the signal**.
|
||||||
|
Returns true if deviceAddress is found on the bus, false otherwise.
|
||||||
|
- **bool isConnected()** test to see if deviceAddress is found on the I2C-bus.
|
||||||
|
|
||||||
|
|
||||||
|
### Frequency
|
||||||
|
|
||||||
|
- **bool setFrequency(float MHz)** converts the frequency in MHz to
|
||||||
|
call **setChannel(channel)**. The value of channel is rounded off depending
|
||||||
|
on the resolution of the device.
|
||||||
|
Returns false if MHz is out of range or **setChannel()** fails.
|
||||||
|
- **float getFrequency()** returns the current frequency in MHz, can be slightly different
|
||||||
|
from the set value due to rounding math mentioned above.
|
||||||
|
The return value is derived from a call to **getChannel()**
|
||||||
|
- **bool setChannel(uint16_t channel)** writes the channel to broadcast on to the device.
|
||||||
|
This involves two or three writes to different device registers.
|
||||||
|
- **uint16_t getChannel()** reads the selected channel from the device and
|
||||||
|
returns it.
|
||||||
|
|
||||||
|
|
||||||
|
### PGA
|
||||||
|
|
||||||
|
Read Datasheet.
|
||||||
|
|
||||||
|
The KT0803K has a **PGA_LSB** (2 bits) setting, which allows setting the gain
|
||||||
|
with single (1) dB steps. This is not yet implemented in the library.
|
||||||
|
|
||||||
|
- **bool setPGA(uint8_t pga)** sets gain according to table below.
|
||||||
|
Returns false if pga is out of range (0..7).
|
||||||
|
- **uint8_t getPGA()** returns 0..7, default 0.
|
||||||
|
|
||||||
|
|
||||||
|
| PGA | gain | notes |
|
||||||
|
|:-----:|:-------:|:-------:|
|
||||||
|
| 111 | 12dB |
|
||||||
|
| 110 | 8dB |
|
||||||
|
| 101 | 4dB |
|
||||||
|
| 100 | 0dB |
|
||||||
|
| 000 | 0dB | default
|
||||||
|
| 001 | -4dB |
|
||||||
|
| 010 | -8dB |
|
||||||
|
| 011 | -12dB |
|
||||||
|
|
||||||
|
|
||||||
|
### RFGain
|
||||||
|
|
||||||
|
Read Datasheet.
|
||||||
|
|
||||||
|
Note: the RFGain value (4 bits) is distributed over three registers.
|
||||||
|
PA_BIAS (register 0x05) is only supported in the KT0803K device.
|
||||||
|
It is not yet supported in the library.
|
||||||
|
|
||||||
|
- **bool setRFGain(uint8_t rfgain)** sets rfgain according to table below.
|
||||||
|
Returns false if rfgain is out of range (0..15).
|
||||||
|
- **uint8_t getRFgain()** returns 0..15, default 15.
|
||||||
|
|
||||||
|
|
||||||
|
| RFGAIN | RFout | PA_BIAS = 1 | notes |
|
||||||
|
|:--------:|:------------:|:------------:|:-------:|
|
||||||
|
| 0000 | 95.5 dBuV | - |
|
||||||
|
| 0001 | 96.5 dBuV | - |
|
||||||
|
| 0010 | 97.5 dBuV | - |
|
||||||
|
| 0011 | 98.2 dBuV | - |
|
||||||
|
| 0100 | 98.9 dBuV | - |
|
||||||
|
| 0101 | 100.0 dBuV | - |
|
||||||
|
| 0110 | 101.5 dBuV | - |
|
||||||
|
| 0111 | 102.8 dBuV | - |
|
||||||
|
| 1000 | 105.1 dBuV | 107.2 dBuV |
|
||||||
|
| 1001 | 105.6 dBuV | 108.0 dBuV |
|
||||||
|
| 1010 | 106.2 dBuV | 108.7 dBuV |
|
||||||
|
| 1011 | 106.5 dBuV | 109.5 dBuV |
|
||||||
|
| 1100 | 107.0 dBuV | 110.3 dBuV |
|
||||||
|
| 1101 | 107.4 dBuV | 111.0 dBuV |
|
||||||
|
| 1110 | 107.7 dBuV | 111.7 dBuV |
|
||||||
|
| 1111 | 108.0 dBuV | 112.5 dBuV | default
|
||||||
|
|
||||||
|
|
||||||
|
### Region selection
|
||||||
|
|
||||||
|
Read datasheet for details.
|
||||||
|
|
||||||
|
Note that not all frequencies are allowed in all regions / countries!
|
||||||
|
|
||||||
|
The first four are convenience wrappers for **setPHTCNST()**
|
||||||
|
If some region is missing please let me know the details and I can add
|
||||||
|
a wrapper for it.
|
||||||
|
|
||||||
|
- **void setEurope()**
|
||||||
|
- **void setAustralia()**
|
||||||
|
- **void setUSA()**
|
||||||
|
- **void setJapan()**
|
||||||
|
- **bool setPHTCNST(bool on)** See table below.
|
||||||
|
- **bool getPHTCNST()** returns set value.
|
||||||
|
|
||||||
|
| PHTCNST | time | Region |
|
||||||
|
|:---------:|:-------:|:--------:|
|
||||||
|
| 0 | 75 μs | USA, Japan, (default)
|
||||||
|
| 1 | 50 μs | Europe, Australia
|
||||||
|
|
||||||
|
|
||||||
|
### PilotToneAdjust
|
||||||
|
|
||||||
|
Read datasheet.
|
||||||
|
|
||||||
|
- **bool setPilotToneAdjust(uint8_t mode)** HIGH = 1 LOW = 0
|
||||||
|
- **uint8_t getPilotToneAdjust()**
|
||||||
|
|
||||||
|
|
||||||
|
### Mute
|
||||||
|
|
||||||
|
Default the device is not muted, but **begin()** will default mute it.
|
||||||
|
See interface section above.
|
||||||
|
|
||||||
|
- **bool setMute(bool mute)** enables or disables the transmitting
|
||||||
|
by muting the signal.
|
||||||
|
- **bool getMute()** returns the current state of muting.
|
||||||
|
|
||||||
|
|
||||||
|
## Preference channels
|
||||||
|
|
||||||
|
The device and library do not implement the persistant store of user
|
||||||
|
selectable preferences (frequencies or channels).
|
||||||
|
This can be implemented by the user in EEPROM or another persistent medium.
|
||||||
|
|
||||||
|
Think of a class that holds an array of channels and optional descriptions.
|
||||||
|
A minimal hardcoded preset sketch is in the examples.
|
||||||
|
|
||||||
|
|
||||||
|
## Derived classes
|
||||||
|
|
||||||
|
Three derived classes are made:
|
||||||
|
|
||||||
|
- KT0803K has some extended functions.
|
||||||
|
- KT0803L is backwards compatible with KT0803K.
|
||||||
|
It has far more registers in use than the KT0803/KT0803K.
|
||||||
|
For now it is a wrapper around KT0803K.
|
||||||
|
- KT0803M is identical to the KT0803K (no new registers).
|
||||||
|
|
||||||
|
|
||||||
|
## Interface KT0803K / KT0803L / KT0803M
|
||||||
|
|
||||||
|
Added functions in 0.3.0 (not tested), check datasheet.
|
||||||
|
|
||||||
|
### Mono Stereo
|
||||||
|
|
||||||
|
- **bool setMono()** idem
|
||||||
|
- **bool setStereo()** idem
|
||||||
|
- **bool isStereo()** idem
|
||||||
|
|
||||||
|
### Bass
|
||||||
|
|
||||||
|
- **bool setBass(uint8_t bass); // 0..3 = 0, 5, 11, 17 dB
|
||||||
|
- **uint8_t getBass()** idem
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- **bool powerOK()** idem
|
||||||
|
- **bool silenceDetected()** idem
|
||||||
|
|
||||||
|
|
||||||
|
## Future
|
||||||
|
|
||||||
|
#### Must
|
||||||
|
|
||||||
|
- improve documentation
|
||||||
|
- buy hardware
|
||||||
|
- test and verify.
|
||||||
|
|
||||||
|
|
||||||
|
#### Should
|
||||||
|
|
||||||
|
- add examples for KT0803K specific functions.
|
||||||
|
- investigate KT0803L
|
||||||
|
- RESET pin as optional parameter in constructor?
|
||||||
|
- SW pin (ON/OFF) as optional parameter in constructor?
|
||||||
|
- add functions for sw on/off,
|
||||||
|
- what is impact on settings?
|
||||||
|
- call begin () again? => default
|
||||||
|
- explain well doc.
|
||||||
|
|
||||||
|
#### Could
|
||||||
|
|
||||||
|
- improve error handling
|
||||||
|
- unit tests possible?
|
||||||
|
- extend settings upon request **bold** are interesting, see table
|
||||||
|
|
||||||
|
| device | setting | register | Notes |
|
||||||
|
|:---------:|:-------------:|:---------------:|:--------|
|
||||||
|
| KT0803 | PA_CTRL | 13, bit 2 | **WARNING in datasheet**
|
||||||
|
| | | | Should it be added in API?
|
||||||
|
| KT0803K | PGA_LSB | 04, bit 4+5 | gain fine tuning -> see PGA_MOD
|
||||||
|
| KT0803K | FDEV | 04, bit 2+3 | Frequency deviation adjustment
|
||||||
|
| KT0803K | PDPA | 0B, bit 5 | Power Amplifier Power Down ?
|
||||||
|
| KT0803K | PA_BIAS | 0E, bit 1 | PA bias current enhancement.
|
||||||
|
| KT0803K | LMTLVL | 10, bit 3+4 | **Internal audio limiter level control**
|
||||||
|
| KT0803K | PGAMOD | 10, bit 0 | PGA mode selection (use PGA_LSB/ not)
|
||||||
|
| KT0803K | SLNCDIS | 12, bit 7 | Silence detection disable
|
||||||
|
| KT0803K | SLNCTHL | 12, bit 4+5+6 | Silence detection low threshold
|
||||||
|
| KT0803K | SLNCTHH | 12, bit 1+2+3 | Silence detection high threshold
|
||||||
|
| KT0803K | SW_MOD | 12, bit 0 | **Switching channel mode selection**
|
||||||
|
| KT0803K | SLNCTIME | 14, bit 5+6+7 | silence detection time
|
||||||
|
| KT0803K | SLNCCNTHIGH | 14, bit 2+3+4 | silence detection count high
|
||||||
|
| KT0803K | SLNCCNTLOW | 15, bit 0+1+2 | silence detection count low
|
||||||
|
|
||||||
|
|
||||||
|
#### Wont (for now)
|
||||||
|
|
||||||
|
- investigate tea5767 FM receiver (Out of scope for this library).
|
||||||
|
- investigate efficiency of register access.
|
||||||
|
- caching all (allowed) registers in **begin()**
|
||||||
|
- 3 bytes for KT0803
|
||||||
|
- 12 bytes for KT0803K
|
||||||
|
- cache frequency.
|
||||||
|
- only writing is needed.
|
||||||
|
- send binary data over FM?
|
||||||
|
- preset frequency array in .h file (hardcoded)
|
||||||
|
- enums for parameters - readability?
|
||||||
|
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you appreciate my libraries, you can support the development and maintenance.
|
||||||
|
Improve the quality of the libraries by providing issues and Pull Requests, or
|
||||||
|
donate through PayPal or GitHub sponsors.
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803K_minimal.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803K FM_SEND;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin();
|
||||||
|
FM_SEND.setChannel(2000); // * 0.05 100.00 MHz
|
||||||
|
FM_SEND.setMute(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803K_minimal.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo hardcoded presets
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803L FM_SEND;
|
||||||
|
|
||||||
|
// hardcoded presets adjust to your need.
|
||||||
|
// note: presets by channel would take half the memory
|
||||||
|
float preset[10] =
|
||||||
|
{
|
||||||
|
88.6, 91.3, 92.2, 96.0, 97.3,
|
||||||
|
98.8, 100.1, 100.3, 105.4, 107.6
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin(preset[0], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
if (Serial.available())
|
||||||
|
{
|
||||||
|
char c = Serial.read();
|
||||||
|
|
||||||
|
if (c == 'm') FM_SEND.setMute(false);
|
||||||
|
if (c == 'M') FM_SEND.setMute(true);
|
||||||
|
// select preset 0..9
|
||||||
|
int p = c - '0'; // convert to digit
|
||||||
|
if ((0 <= p) && (p <= 9)) FM_SEND.setFrequency(preset[p]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803_setFrequency.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803K FM_SEND;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin();
|
||||||
|
FM_SEND.setFrequency(105.75);
|
||||||
|
FM_SEND.setMute(false);
|
||||||
|
|
||||||
|
Serial.print("Freq: ");
|
||||||
|
Serial.println(FM_SEND.getFrequency());
|
||||||
|
Serial.print("Channel: ");
|
||||||
|
Serial.println(FM_SEND.getChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803_minimal.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803 FM_SEND;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin();
|
||||||
|
FM_SEND.setChannel(2000); // * 0.05 = 100.00 MHz
|
||||||
|
FM_SEND.setMute(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803_setFrequency.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803 FM_SEND;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin();
|
||||||
|
FM_SEND.setFrequency(105.75);
|
||||||
|
FM_SEND.setMute(false);
|
||||||
|
|
||||||
|
Serial.print("Freq: ");
|
||||||
|
Serial.println(FM_SEND.getFrequency());
|
||||||
|
Serial.print("Channel: ");
|
||||||
|
Serial.println(FM_SEND.getChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803_sweep_2_seconds.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803 FM_SEND;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
FM_SEND.begin();
|
||||||
|
FM_SEND.setMute(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// adjust to your local allowed frequencies
|
||||||
|
for (float freq = 88.0; freq <= 108.0; freq += 0.1)
|
||||||
|
{
|
||||||
|
FM_SEND.setFrequency(freq);
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// FILE: KT0803_up_down.ino
|
||||||
|
// AUTHOR: Rob Tillaart
|
||||||
|
// PURPOSE: minimal demo
|
||||||
|
// URL: https://github.com/RobTillaart/KT0803
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "KT0803.h"
|
||||||
|
|
||||||
|
|
||||||
|
KT0803 FM_SEND;
|
||||||
|
|
||||||
|
// connect two buttons to GND
|
||||||
|
// adjust pins to your needs,
|
||||||
|
uint8_t UP_PIN = 6;
|
||||||
|
uint8_t DOWN_PIN = 7;
|
||||||
|
float freq = 100.0;
|
||||||
|
float newFreq = 100.0;
|
||||||
|
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println(__FILE__);
|
||||||
|
Serial.print("KT0803_LIB_VERSION: ");
|
||||||
|
Serial.println(KT0803_LIB_VERSION);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
pinMode(UP_PIN, INPUT_PULLUP);
|
||||||
|
pinMode(DOWN_PIN, INPUT_PULLUP);
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
FM_SEND.begin(freq, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// adjust frequency with up and down button.
|
||||||
|
uint8_t u = digitalRead(UP_PIN);
|
||||||
|
uint8_t d = digitalRead(DOWN_PIN);
|
||||||
|
if ((u == 0) && (d == 0))
|
||||||
|
{
|
||||||
|
newFreq = 100.0;
|
||||||
|
}
|
||||||
|
else if (u == 0)
|
||||||
|
{
|
||||||
|
newFreq += 0.1;
|
||||||
|
if (newFreq > 108.0) newFreq = 108.0;
|
||||||
|
}
|
||||||
|
else if (d == 0)
|
||||||
|
{
|
||||||
|
newFreq -= 0.1;
|
||||||
|
if (newFreq < 88.0) newFreq = 88.0;
|
||||||
|
}
|
||||||
|
if (newFreq != freq)
|
||||||
|
{
|
||||||
|
freq = newFreq;
|
||||||
|
FM_SEND.setFrequency(freq);
|
||||||
|
|
||||||
|
Serial.print("Freq: ");
|
||||||
|
Serial.print(FM_SEND.getFrequency());
|
||||||
|
Serial.print("\t");
|
||||||
|
Serial.println(FM_SEND.getChannel());
|
||||||
|
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- END OF FILE --
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Syntax Colouring Map For KT0803
|
||||||
|
|
||||||
|
|
||||||
|
# Data types (KEYWORD1)
|
||||||
|
KT0803 KEYWORD1
|
||||||
|
KT0803K KEYWORD1
|
||||||
|
KT0803L KEYWORD1
|
||||||
|
KT0803M KEYWORD1
|
||||||
|
|
||||||
|
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
begin KEYWORD2
|
||||||
|
isConnected KEYWORD2
|
||||||
|
|
||||||
|
setFrequency KEYWORD2
|
||||||
|
getFrequency KEYWORD2
|
||||||
|
setChannel KEYWORD2
|
||||||
|
getChannel KEYWORD2
|
||||||
|
|
||||||
|
setPGA KEYWORD2
|
||||||
|
getPGA KEYWORD2
|
||||||
|
|
||||||
|
setRFGain KEYWORD2
|
||||||
|
getRFgain KEYWORD2
|
||||||
|
|
||||||
|
setEurope KEYWORD2
|
||||||
|
setAustralia KEYWORD2
|
||||||
|
setUSA KEYWORD2
|
||||||
|
setJapan KEYWORD2
|
||||||
|
setPHTCNST KEYWORD2
|
||||||
|
getPHTCNST KEYWORD2
|
||||||
|
|
||||||
|
setPilotToneAdjust KEYWORD2
|
||||||
|
getPilotToneAdjust KEYWORD2
|
||||||
|
|
||||||
|
setMute KEYWORD2
|
||||||
|
getMute KEYWORD2
|
||||||
|
|
||||||
|
|
||||||
|
# KT0803K specific
|
||||||
|
setMono KEYWORD2
|
||||||
|
setStereo KEYWORD2
|
||||||
|
isStereo KEYWORD2
|
||||||
|
|
||||||
|
setBass KEYWORD2
|
||||||
|
getBass KEYWORD2
|
||||||
|
|
||||||
|
powerOK KEYWORD2
|
||||||
|
silenceDetected KEYWORD2
|
||||||
|
|
||||||
|
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
KT0803_LIB_VERSION LITERAL1
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "KT0803",
|
||||||
|
"keywords": "FM,Broadcast,KT0803K,KT0803L,KT0803M",
|
||||||
|
"description": "Arduino library for the KT0803 and KT0803K FM transmitter.",
|
||||||
|
"authors":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Rob Tillaart",
|
||||||
|
"email": "[email protected]",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repository":
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/RobTillaart/KT0803.git"
|
||||||
|
},
|
||||||
|
"version": "0.3.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"frameworks": "*",
|
||||||
|
"platforms": "*",
|
||||||
|
"headers": "KT0803.h"
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
name=KT0803
|
||||||
|
version=0.3.1
|
||||||
|
author=Rob Tillaart <[email protected]>
|
||||||
|
maintainer=Rob Tillaart <[email protected]>
|
||||||
|
sentence=Arduino library for the KT0803 and KT0803K FM transmitter.
|
||||||
|
paragraph=KT0803K, KT0803L, KT0803M
|
||||||
|
category=Communication
|
||||||
|
url=https://github.com/RobTillaart/KT0803
|
||||||
|
architectures=*
|
||||||
|
includes=KT0803.h
|
||||||
|
depends=
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:rymcu-esp32-s3-devkitc-1]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
|
||||||
|
board = rymcu-esp32-s3-devkitc-1
|
||||||
|
framework = arduino
|
||||||
|
board_build.flash_mode = dio
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
olikraus/U8g2
|
||||||
|
RobTillaart/KT0803
|
||||||
|
https://github.com/pschatzmann/arduino-libhelix.git
|
||||||
|
build_unflags =
|
||||||
|
-std=gnu++11
|
||||||
|
-std=gnu++14
|
||||||
|
-std=gnu++17
|
||||||
|
-std=c++11
|
||||||
|
-std=c++14
|
||||||
|
-std=c++17
|
||||||
|
build_flags =
|
||||||
|
-std=gnu++20
|
||||||
|
-D CORE_DEBUG_LEVEL=0
|
||||||
|
-D BOARD_HAS_PSRAM
|
||||||
+970
@@ -0,0 +1,970 @@
|
|||||||
|
/*
|
||||||
|
AudioFMSynth
|
||||||
|
This project only tested and working on ESP32 (Original) and ESP32-S3.
|
||||||
|
If you would like to use this code to other board. It's necessary to ensue all PINs are able to use for this project.
|
||||||
|
Some code may need to be rewritten to support the board.
|
||||||
|
|
||||||
|
Some code is from my old game project, but now it is almost dead.
|
||||||
|
|
||||||
|
Wei - 2026
|
||||||
|
|
||||||
|
You can do anything you want to this source code. BUT you MUST cite the original source of the code.
|
||||||
|
*/
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <SD.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "KT0803.h"
|
||||||
|
#include <ESP_I2S.h>
|
||||||
|
#include "MP3DecoderHelix.h"
|
||||||
|
|
||||||
|
#define VERSION "1.2"
|
||||||
|
|
||||||
|
KT0803L fm(&Wire);
|
||||||
|
|
||||||
|
// ======================= Display =======================
|
||||||
|
#define SCREEN_W 128
|
||||||
|
#define SCREEN_H 64
|
||||||
|
|
||||||
|
// For esp32 (original)
|
||||||
|
// #define OLED_MOSI 23 // SDA ESP32
|
||||||
|
// #define OLED_CLK 18 // SCK
|
||||||
|
// #define OLED_DC 27
|
||||||
|
// #define OLED_CS 5
|
||||||
|
// #define OLED_RST 14 // RES
|
||||||
|
|
||||||
|
// esp32-s3
|
||||||
|
#define OLED_MOSI 11 // SDA ESP32
|
||||||
|
#define OLED_CLK 12 // SCK
|
||||||
|
#define OLED_DC 16
|
||||||
|
#define OLED_CS 14
|
||||||
|
#define OLED_RST 17 // RES
|
||||||
|
|
||||||
|
U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(
|
||||||
|
U8G2_R0,
|
||||||
|
OLED_CS,
|
||||||
|
OLED_DC,
|
||||||
|
OLED_RST
|
||||||
|
);
|
||||||
|
|
||||||
|
// ======================= Joystick Pins =======================
|
||||||
|
// esp32 (original)
|
||||||
|
// #define JOY_X 34
|
||||||
|
// #define JOY_Y 35
|
||||||
|
// #define JOY_SW 32
|
||||||
|
|
||||||
|
// esp32-s3
|
||||||
|
#define JOY_X 1
|
||||||
|
#define JOY_Y 2
|
||||||
|
#define JOY_SW 7
|
||||||
|
|
||||||
|
// esp32 (original)
|
||||||
|
// #define FM_SDA 21
|
||||||
|
// #define FM_SCL 22
|
||||||
|
|
||||||
|
// esp32-s3
|
||||||
|
#define FM_SDA 8
|
||||||
|
#define FM_SCL 9
|
||||||
|
|
||||||
|
// ======================= SD (microSD) Card =======================
|
||||||
|
// esp32 (original)
|
||||||
|
// #define SD_MOSI 23 // sd reader shares the same spi pin with the display
|
||||||
|
// #define SD_MISO 19
|
||||||
|
// #define SD_SCK 18
|
||||||
|
// #define SD_CS 33
|
||||||
|
|
||||||
|
// esp32-s3
|
||||||
|
#define SD_MOSI 11
|
||||||
|
#define SD_MISO 13
|
||||||
|
#define SD_SCK 12
|
||||||
|
#define SD_CS 15
|
||||||
|
|
||||||
|
// ======================= PCM5102A DAC =======================
|
||||||
|
// esp32 (original)
|
||||||
|
// #define I2S_BCLK 26
|
||||||
|
// #define I2S_LRCK 25
|
||||||
|
// #define I2S_DOUT 17
|
||||||
|
|
||||||
|
// esp32-s3
|
||||||
|
#define I2S_BCLK 4
|
||||||
|
#define I2S_LRCK 5
|
||||||
|
#define I2S_DOUT 6
|
||||||
|
|
||||||
|
// ====================== Button(s) ==========================
|
||||||
|
#define BUTTON_1 10
|
||||||
|
|
||||||
|
using namespace libhelix;
|
||||||
|
|
||||||
|
constexpr uint32_t SAMPLE_RATE = 44100;
|
||||||
|
constexpr float TONE_FREQ = 440.0f;
|
||||||
|
constexpr int MAX_MUSIC_FILES = 20;
|
||||||
|
constexpr int MAX_FILENAME_LEN = 48;
|
||||||
|
|
||||||
|
char musicFiles[MAX_MUSIC_FILES][MAX_FILENAME_LEN];
|
||||||
|
int musicFileCount = 0;
|
||||||
|
int selectedMusic = 0;
|
||||||
|
|
||||||
|
File playingFile;
|
||||||
|
bool musicPlaying = false;
|
||||||
|
bool musicPaused = false;
|
||||||
|
bool musicButtonWasPressed = false;
|
||||||
|
uint32_t currentAudioRate = SAMPLE_RATE;
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
int dacVolume = 12;
|
||||||
|
|
||||||
|
// Option & Menu
|
||||||
|
int selectOptionID = 1;
|
||||||
|
int currentOptionPage = 0; // 0: Home, 1: JoyStick Debug, 2: FM Broadcast, 3: PCM5102 Debug
|
||||||
|
const int optionIDList[] = {1,2,3,4,5};
|
||||||
|
const char* menuNames[] = {
|
||||||
|
"Debug Joystick",
|
||||||
|
"FM Broadcaster",
|
||||||
|
"Debug PCM5102",
|
||||||
|
"Volume",
|
||||||
|
"Play Music"
|
||||||
|
};
|
||||||
|
int buttonPressCount = 0;
|
||||||
|
bool wasPressed = false;
|
||||||
|
unsigned long buttonPressTime = 0;
|
||||||
|
|
||||||
|
I2SClass I2S;
|
||||||
|
|
||||||
|
void mp3DataCallback(MP3FrameInfo &info, int16_t *pcm, size_t len, void*) {
|
||||||
|
if (!pcm || !len || info.nChans < 1) return;
|
||||||
|
|
||||||
|
if ((uint32_t)info.samprate != currentAudioRate) {
|
||||||
|
if (I2S.configureTX(info.samprate, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO))
|
||||||
|
currentAudioRate = info.samprate;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t CHUNK_SAMPLES = 256;
|
||||||
|
int16_t out[CHUNK_SAMPLES * 2];
|
||||||
|
|
||||||
|
if (info.nChans == 1) {
|
||||||
|
for (size_t pos = 0; pos < len;) {
|
||||||
|
size_t n = min((size_t)CHUNK_SAMPLES, len - pos);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
int32_t v = (int32_t)pcm[pos + i] * dacVolume / 255;
|
||||||
|
out[i * 2] = out[i * 2 + 1] = (int16_t)v;
|
||||||
|
}
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<const uint8_t*>(out),
|
||||||
|
n * 2 * sizeof(int16_t)
|
||||||
|
);
|
||||||
|
pos += n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (size_t pos = 0; pos < len;) {
|
||||||
|
size_t n = min((size_t)(CHUNK_SAMPLES * 2), len - pos);
|
||||||
|
for (size_t i = 0; i < n; i++)
|
||||||
|
out[i] = (int16_t)((int32_t)pcm[pos + i] * dacVolume / 255);
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<const uint8_t*>(out),
|
||||||
|
n * sizeof(int16_t)
|
||||||
|
);
|
||||||
|
pos += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MP3DecoderHelix mp3(mp3DataCallback);
|
||||||
|
|
||||||
|
typedef struct CursorPos {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} CursorPos;
|
||||||
|
|
||||||
|
void stopMusic() {
|
||||||
|
if (playingFile) playingFile.close();
|
||||||
|
if (musicPlaying) mp3.end();
|
||||||
|
musicPlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pauseMusic() {
|
||||||
|
if (!musicPlaying) return;
|
||||||
|
musicPaused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resumeMusic() {
|
||||||
|
if (!musicPlaying) return;
|
||||||
|
musicPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void feedMusicDecoder() {
|
||||||
|
if (!musicPlaying || !playingFile || musicPaused) return;
|
||||||
|
|
||||||
|
uint8_t input[1024];
|
||||||
|
if (const size_t n = playingFile.read(input, sizeof(input)); n > 0) {
|
||||||
|
mp3.write(input, n);
|
||||||
|
} else {
|
||||||
|
stopMusic();
|
||||||
|
Serial.println("Playback finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function stores some task that needs to be run in the background */
|
||||||
|
void moduleLoop() {
|
||||||
|
feedMusicDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
void printScreen(int x, int y, const char* text, bool clear_screen, bool send_buffer) {
|
||||||
|
if (clear_screen) {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setCursor(x, y);
|
||||||
|
u8g2.print(text);
|
||||||
|
|
||||||
|
if (send_buffer) {
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JoyStickStatus;
|
||||||
|
|
||||||
|
// Change its value if your joystick's position is not same as you expected.
|
||||||
|
bool swapJoyStickVertical = false;
|
||||||
|
bool swapJoyStickHorizontal = false;
|
||||||
|
|
||||||
|
JoyStickStatus getJoyStickerStatus(int pin_x, int pin_y, int pin_sw);
|
||||||
|
|
||||||
|
struct JoyStickStatus {
|
||||||
|
bool up;
|
||||||
|
bool down;
|
||||||
|
bool left;
|
||||||
|
bool right;
|
||||||
|
bool pressed;
|
||||||
|
};
|
||||||
|
|
||||||
|
int readJoystickAxis(int pin) {
|
||||||
|
constexpr int SAMPLE_COUNT = 4;
|
||||||
|
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < SAMPLE_COUNT; i++) {
|
||||||
|
sum += analogRead(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / SAMPLE_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
JoyStickStatus getJoyStickerStatus(int pin_x, int pin_y, int pin_sw) {
|
||||||
|
JoyStickStatus status = {0};
|
||||||
|
|
||||||
|
constexpr int CENTER_LOW = 1500;
|
||||||
|
constexpr int CENTER_HIGH = 2600;
|
||||||
|
|
||||||
|
int x = readJoystickAxis(pin_x);
|
||||||
|
int y = readJoystickAxis(pin_y);
|
||||||
|
bool pressed = (digitalRead(pin_sw) == LOW);
|
||||||
|
|
||||||
|
status.up = x >= CENTER_LOW && x <= CENTER_HIGH && y < 500;
|
||||||
|
status.down = x >= CENTER_LOW && x <= CENTER_HIGH && y > 3800;
|
||||||
|
status.left = y >= CENTER_LOW && y <= CENTER_HIGH && x < 500;
|
||||||
|
status.right = y >= CENTER_LOW && y <= CENTER_HIGH && x > 3800;
|
||||||
|
|
||||||
|
if (swapJoyStickVertical) {
|
||||||
|
// Wow, c++ have this cool thing
|
||||||
|
std::swap(status.up, status.down);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapJoyStickHorizontal) {
|
||||||
|
std::swap(status.left, status.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
status.pressed = pressed;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getJoyStickerDebugInfo(int start_x, int start_y, int pin_x, int pin_y, int pin_sw, bool print_in_serial) {
|
||||||
|
int x = readJoystickAxis(pin_x);
|
||||||
|
int y = readJoystickAxis(pin_y);
|
||||||
|
bool pressed = (digitalRead(pin_sw) == LOW);
|
||||||
|
|
||||||
|
u8g2.setCursor(start_x, start_y);
|
||||||
|
u8g2.print("X: ");
|
||||||
|
u8g2.print(x);
|
||||||
|
|
||||||
|
u8g2.setCursor(start_x, start_y + 12);
|
||||||
|
u8g2.print("Y: ");
|
||||||
|
u8g2.print(y);
|
||||||
|
|
||||||
|
u8g2.setCursor(start_x, start_y + 24);
|
||||||
|
u8g2.print("SW: ");
|
||||||
|
u8g2.print(pressed ? "PRESSED" : "RELEASED");
|
||||||
|
|
||||||
|
JoyStickStatus status = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
||||||
|
|
||||||
|
u8g2.setCursor(start_x, start_y + 36);
|
||||||
|
if (status.up) {
|
||||||
|
u8g2.print("Direction: UP");
|
||||||
|
}
|
||||||
|
else if (status.down) {
|
||||||
|
u8g2.print("Direction: DOWN");
|
||||||
|
}
|
||||||
|
else if (status.left) {
|
||||||
|
u8g2.print("Direction: LEFT");
|
||||||
|
}
|
||||||
|
else if (status.right) {
|
||||||
|
u8g2.print("Direction: RIGHT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (print_in_serial) {
|
||||||
|
char buffer[100];
|
||||||
|
snprintf(buffer, sizeof(buffer), "X:%d , Y:%d, SW:%s", x, y, pressed ? "PRESSED" : "RELEASED");
|
||||||
|
Serial.println(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float roundToOneDecimal(const float num) {
|
||||||
|
return (num * 10 + 0.5) / 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int handleQuitMenuEvent() {
|
||||||
|
const JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
||||||
|
|
||||||
|
if (joyStatus.pressed && !wasPressed) {
|
||||||
|
// Start time record
|
||||||
|
buttonPressTime = millis();
|
||||||
|
wasPressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!joyStatus.pressed && wasPressed) {
|
||||||
|
// Quit the menu if pressed time more than over 1.5 sec
|
||||||
|
if (const unsigned long duration = millis() - buttonPressTime; duration >= 1000) {
|
||||||
|
currentOptionPage=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasPressed = false;
|
||||||
|
}
|
||||||
|
return currentOptionPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFM(float freq, bool mute) {
|
||||||
|
fm.setUSA();
|
||||||
|
fm.setFrequency(freq);
|
||||||
|
fm.setMute(mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmBroadcast() {
|
||||||
|
float fm_freq = 98.1;
|
||||||
|
float old_freq = 98.1;
|
||||||
|
|
||||||
|
if (!fm.begin(fm_freq, false)) {
|
||||||
|
Serial.println("FM Module KT0803 not found. Exiting...");
|
||||||
|
printScreen(0, 14, "FM Module KT0803 not", true, false);
|
||||||
|
u8g2.setCursor(0, 26);
|
||||||
|
u8g2.print(" found. Exiting...");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(3500);
|
||||||
|
currentOptionPage=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFM(fm_freq, false);
|
||||||
|
|
||||||
|
while (currentOptionPage == 2) {
|
||||||
|
const int adc = analogRead(JOY_X);
|
||||||
|
|
||||||
|
if (const int offset = adc - 2048; abs(offset) > 200)
|
||||||
|
{
|
||||||
|
const float speed = static_cast<float>(offset) / 2048.0f;
|
||||||
|
|
||||||
|
fm_freq += speed * 0.1f;
|
||||||
|
fm_freq = roundToOneDecimal(fm_freq);
|
||||||
|
|
||||||
|
// Max and min value check
|
||||||
|
if (fm_freq < 88.0) {
|
||||||
|
fm_freq = 88.0;
|
||||||
|
}
|
||||||
|
else if (fm_freq > 108.0) {
|
||||||
|
fm_freq = 108.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_freq != fm_freq) {
|
||||||
|
// Update frequency
|
||||||
|
setFM(fm_freq, false);
|
||||||
|
old_freq = fm_freq;
|
||||||
|
Serial.print("Frequency: ");
|
||||||
|
Serial.println(fm_freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
printScreen(0, 14, "Brocasting_", true, false);
|
||||||
|
u8g2.setCursor(0, 26);
|
||||||
|
u8g2.print("Frequency: ");
|
||||||
|
u8g2.print(fm.getFrequency());
|
||||||
|
u8g2.setCursor(0, 38);
|
||||||
|
u8g2.print("Now you can leave");
|
||||||
|
u8g2.setCursor(0, 50);
|
||||||
|
u8g2.print("this page.");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
printScreen(0, 14, "Brocasting ", true, false);
|
||||||
|
u8g2.setCursor(0, 26);
|
||||||
|
u8g2.print("Frequency: ");
|
||||||
|
u8g2.print(fm.getFrequency());
|
||||||
|
u8g2.setCursor(0, 38);
|
||||||
|
u8g2.print("Now you can leave");
|
||||||
|
u8g2.setCursor(0, 50);
|
||||||
|
u8g2.print("this page.");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
currentOptionPage = handleQuitMenuEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void joyStickDebugMenu() {
|
||||||
|
while (currentOptionPage == 1) {
|
||||||
|
printScreen(0, 14, "Debugging joystick.", true, false);
|
||||||
|
getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true);
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
printScreen(0, 14, "Debugging joystick..", true, false);
|
||||||
|
getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true);
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
printScreen(0, 14, "Debugging joystick...", true, false);
|
||||||
|
getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true);
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
printScreen(0, 14, "Debugging joystick....", true, false);
|
||||||
|
getJoyStickerDebugInfo(0, 26, JOY_X, JOY_Y, JOY_SW, true);
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
delay(300);
|
||||||
|
currentOptionPage = handleQuitMenuEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorPos printOptionMenu(int lastX, int lastY, int availableHeight, int textSize) {
|
||||||
|
CursorPos pos = {lastX, lastY};
|
||||||
|
|
||||||
|
constexpr int optionCount = std::size(menuNames);
|
||||||
|
const int maxDisplayOptionCount = availableHeight / textSize;
|
||||||
|
|
||||||
|
// Below code is for scrolling menu support
|
||||||
|
int startIndex = selectOptionID - maxDisplayOptionCount / 2;
|
||||||
|
|
||||||
|
// border check
|
||||||
|
if (startIndex < 0) {
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex + maxDisplayOptionCount > optionCount) {
|
||||||
|
startIndex = optionCount - maxDisplayOptionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIndex < 0) {
|
||||||
|
startIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endIndex = min(startIndex + maxDisplayOptionCount, optionCount);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < endIndex; i++) {
|
||||||
|
pos.y += 12;
|
||||||
|
u8g2.setCursor(pos.x, pos.y);
|
||||||
|
|
||||||
|
// Content
|
||||||
|
u8g2.print(i+1);
|
||||||
|
u8g2.print(": ");
|
||||||
|
u8g2.print(menuNames[i]);
|
||||||
|
|
||||||
|
if (selectOptionID == i + 1) {
|
||||||
|
u8g2.print(" [X]");
|
||||||
|
} else {
|
||||||
|
u8g2.print(" [ ]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
int handleOptionSelectEvent() {
|
||||||
|
const JoyStickStatus joyStatus = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
||||||
|
constexpr size_t lengthOfOptions = std::size(optionIDList);
|
||||||
|
|
||||||
|
if (joyStatus.down) {
|
||||||
|
if (selectOptionID+1 <= lengthOfOptions) {
|
||||||
|
selectOptionID+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (joyStatus.up) {
|
||||||
|
if (selectOptionID-1 >= 1) {
|
||||||
|
selectOptionID-=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joyStatus.pressed) {
|
||||||
|
buttonPressCount+=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do option page replace if button is pressed twice.
|
||||||
|
if (buttonPressCount>=2) {
|
||||||
|
buttonPressCount=0;
|
||||||
|
currentOptionPage=selectOptionID;
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
selectOptionID=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentOptionPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printLogo(int x, int y) {
|
||||||
|
// Draw background
|
||||||
|
u8g2.setDrawColor(1);
|
||||||
|
u8g2.drawBox(x, y, 128 - x, 1);
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
u8g2.setCursor(x + 2, y + 10);
|
||||||
|
|
||||||
|
u8g2.print("AudioFMSynth v");
|
||||||
|
u8g2.print(VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void homeMenu() {
|
||||||
|
bool frameMoment = false;
|
||||||
|
unsigned long lastDisplayUpdate = millis();
|
||||||
|
unsigned long lastTickUpdate = millis();
|
||||||
|
unsigned long lastAnimateUpdate = millis();
|
||||||
|
unsigned long perFrameTime = 33;
|
||||||
|
unsigned long perTickTime = 500;
|
||||||
|
unsigned long perAnimateTime = 500;
|
||||||
|
|
||||||
|
while (currentOptionPage == 0) {
|
||||||
|
moduleLoop();
|
||||||
|
const unsigned long time = millis();
|
||||||
|
if (time - lastTickUpdate >= perTickTime) {
|
||||||
|
lastTickUpdate = millis();
|
||||||
|
currentOptionPage = handleOptionSelectEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time - lastAnimateUpdate >= perAnimateTime) {
|
||||||
|
lastAnimateUpdate = millis();
|
||||||
|
frameMoment = !frameMoment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time - lastDisplayUpdate >= perFrameTime) {
|
||||||
|
lastDisplayUpdate = millis();
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
if (frameMoment) {
|
||||||
|
u8g2.print("Running_");
|
||||||
|
} else {
|
||||||
|
u8g2.print("Running ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto
|
||||||
|
[x, y] = printOptionMenu(0, 14, 36, 12);
|
||||||
|
printLogo(x, y + 2);
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_pcm5102(float sineFreq, float squareFreq, float triangleFreq) {
|
||||||
|
static float sinePhase = 0.0f;
|
||||||
|
static float squarePhase = 0.0f;
|
||||||
|
static float trianglePhase = 0.0f;
|
||||||
|
|
||||||
|
constexpr size_t FRAMES = 256;
|
||||||
|
|
||||||
|
int16_t samples[FRAMES * 2];
|
||||||
|
|
||||||
|
const float sineStep = TWO_PI * sineFreq / SAMPLE_RATE;
|
||||||
|
const float squareStep = TWO_PI * squareFreq / SAMPLE_RATE;
|
||||||
|
const float triangleStep = TWO_PI * triangleFreq / SAMPLE_RATE;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < FRAMES; i++) {
|
||||||
|
float sine = 0.0f;
|
||||||
|
float square = 0.0f;
|
||||||
|
float triangle = 0.0f;
|
||||||
|
|
||||||
|
if (sineFreq > 0.0f) {
|
||||||
|
sine = sinf(sinePhase) * 2500.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squareFreq > 0.0f) {
|
||||||
|
square = (squarePhase < PI ? 1.0f : -1.0f) * 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangleFreq > 0.0f) {
|
||||||
|
triangle = static_cast<float>(trianglePhase < PI ? (2.0f * trianglePhase / PI - 1.0f) : (3.0f - 2.0f * trianglePhase / PI))* 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float mixed = sine + square + triangle;
|
||||||
|
|
||||||
|
mixed = constrain(mixed, -32768.0f, 32767.0f);
|
||||||
|
|
||||||
|
const auto sample = static_cast<int16_t>(mixed);
|
||||||
|
|
||||||
|
samples[i * 2] = sample;
|
||||||
|
samples[i * 2 + 1] = sample;
|
||||||
|
|
||||||
|
sinePhase += sineStep;
|
||||||
|
squarePhase += squareStep;
|
||||||
|
trianglePhase += triangleStep;
|
||||||
|
|
||||||
|
if (sinePhase >= TWO_PI)
|
||||||
|
sinePhase -= TWO_PI;
|
||||||
|
|
||||||
|
if (squarePhase >= TWO_PI)
|
||||||
|
squarePhase -= TWO_PI;
|
||||||
|
|
||||||
|
if (trianglePhase >= TWO_PI)
|
||||||
|
trianglePhase -= TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
I2S.write(
|
||||||
|
reinterpret_cast<uint8_t *>(samples),
|
||||||
|
sizeof(samples)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pcm5102DebugMenu() {
|
||||||
|
unsigned long lastDisplayUpdate = 0;
|
||||||
|
int frameMoment = 0;
|
||||||
|
float frequency = TONE_FREQ;
|
||||||
|
float squareFreq = 0.0f;
|
||||||
|
float triangleFreq = 0.0f;
|
||||||
|
|
||||||
|
while (currentOptionPage == 3) {
|
||||||
|
test_pcm5102(frequency, squareFreq, triangleFreq);
|
||||||
|
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
const int adc = readJoystickAxis(JOY_X);
|
||||||
|
int offset = adc - 2048;
|
||||||
|
|
||||||
|
if (abs(offset) > 200)
|
||||||
|
{
|
||||||
|
const float speed = static_cast<float>(offset) / 2048.0f;
|
||||||
|
|
||||||
|
frequency += speed * 0.1f;
|
||||||
|
frequency = roundToOneDecimal(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int adcY = readJoystickAxis(JOY_Y);
|
||||||
|
offset = adcY - 2048;
|
||||||
|
|
||||||
|
if (offset > 0) {
|
||||||
|
// For triangle
|
||||||
|
offset = abs(offset);
|
||||||
|
|
||||||
|
const float speed = static_cast<float>(offset) / 2048.0f;
|
||||||
|
triangleFreq += speed * 0.1f;
|
||||||
|
triangleFreq = roundToOneDecimal(triangleFreq);
|
||||||
|
} else {
|
||||||
|
// For square
|
||||||
|
offset = abs(offset);
|
||||||
|
|
||||||
|
const float speed = static_cast<float>(offset) / 2048.0f;
|
||||||
|
squareFreq += speed * 0.1f;
|
||||||
|
squareFreq = roundToOneDecimal(squareFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - lastDisplayUpdate >= 300) {
|
||||||
|
lastDisplayUpdate = millis();
|
||||||
|
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
|
||||||
|
switch (frameMoment) {
|
||||||
|
case 0:
|
||||||
|
u8g2.print("Debugging PCM5102.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
u8g2.print("Debugging PCM5102..");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
u8g2.print("Debugging PCM5102...");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
u8g2.print("Debugging PCM5102....");
|
||||||
|
break;
|
||||||
|
default: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setCursor(0, 28);
|
||||||
|
u8g2.print("Sine Freq: ");
|
||||||
|
u8g2.print(frequency);
|
||||||
|
u8g2.setCursor(0, 40);
|
||||||
|
u8g2.print("Square Freq: ");
|
||||||
|
u8g2.print(squareFreq);
|
||||||
|
u8g2.setCursor(0, 52);
|
||||||
|
u8g2.print("Triangle Freq: ");
|
||||||
|
u8g2.print(triangleFreq);
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
|
||||||
|
frameMoment = (frameMoment + 1) % 4;
|
||||||
|
|
||||||
|
currentOptionPage = handleQuitMenuEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void volumeMenu() {
|
||||||
|
static unsigned long lastVolumeUpdate = 0;
|
||||||
|
while (currentOptionPage == 4) {
|
||||||
|
if (millis() - lastVolumeUpdate >= 30) {
|
||||||
|
lastVolumeUpdate = millis();
|
||||||
|
|
||||||
|
const JoyStickStatus status = getJoyStickerStatus(JOY_X, JOY_Y, JOY_SW);
|
||||||
|
int offset=0;
|
||||||
|
if (status.left) {
|
||||||
|
offset = 1;
|
||||||
|
} else if (status.right){
|
||||||
|
offset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dacVolume += offset;
|
||||||
|
|
||||||
|
dacVolume = constrain(dacVolume, 0, 255);
|
||||||
|
|
||||||
|
const int barWidth = (dacVolume * 84) / 255;
|
||||||
|
|
||||||
|
u8g2.firstPage();
|
||||||
|
do {
|
||||||
|
u8g2.setCursor(0, 14);
|
||||||
|
u8g2.print("Volume");
|
||||||
|
|
||||||
|
u8g2.drawFrame(20, 30, 88, 12);
|
||||||
|
u8g2.drawBox(22, 32, barWidth, 8);
|
||||||
|
} while (u8g2.nextPage());
|
||||||
|
|
||||||
|
currentOptionPage = handleQuitMenuEvent();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void scanMusicFiles() {
|
||||||
|
musicFileCount = 0;
|
||||||
|
File dir = SD.open("/music");
|
||||||
|
if (!dir || !dir.isDirectory()) return;
|
||||||
|
while (musicFileCount < MAX_MUSIC_FILES) {
|
||||||
|
File file = dir.openNextFile();
|
||||||
|
if (!file) break;
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
const char* name = file.name();
|
||||||
|
if (const char* ext = strrchr(name, '.'); ext && !strcasecmp(ext, ".mp3")) {
|
||||||
|
strncpy(musicFiles[musicFileCount], name, MAX_FILENAME_LEN - 1);
|
||||||
|
musicFiles[musicFileCount++][MAX_FILENAME_LEN - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMusicSelect() {
|
||||||
|
int y = readJoystickAxis(JOY_Y);
|
||||||
|
if (musicFileCount == 0) return;
|
||||||
|
if (y < 500) { selectedMusic = (selectedMusic + musicFileCount - 1) % musicFileCount; delay(200); }
|
||||||
|
else if (y > 3800) { selectedMusic = (selectedMusic + 1) % musicFileCount; delay(200); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawMusicMenu() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setCursor(0, 10); u8g2.print("Music");
|
||||||
|
if (!musicFileCount) {
|
||||||
|
u8g2.setCursor(0, 30); u8g2.print("No MP3 files");
|
||||||
|
} else {
|
||||||
|
for (int row = -1; row <= 1; row++) {
|
||||||
|
int index = selectedMusic + row;
|
||||||
|
if (index < 0 || index >= musicFileCount) continue;
|
||||||
|
int y = 32 + row * 12;
|
||||||
|
if (index == selectedMusic) { u8g2.setCursor(0, y); u8g2.print('>'); }
|
||||||
|
const char* name = musicFiles[index];
|
||||||
|
if (!strncmp(name, "/music/", 7)) name += 7;
|
||||||
|
char shown[18];
|
||||||
|
strncpy(shown, name, 17); shown[17] = '\0';
|
||||||
|
u8g2.setCursor(10, y); u8g2.print(shown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startMusic(const int index) {
|
||||||
|
stopMusic();
|
||||||
|
|
||||||
|
char path[MAX_FILENAME_LEN + 8];
|
||||||
|
const char* name = musicFiles[index];
|
||||||
|
if (name[0] == '/') snprintf(path, sizeof(path), "%s", name);
|
||||||
|
else if (!strncmp(name, "music/", 6)) snprintf(path, sizeof(path), "/%s", name);
|
||||||
|
else snprintf(path, sizeof(path), "/music/%s", name);
|
||||||
|
|
||||||
|
playingFile = SD.open(path, FILE_READ);
|
||||||
|
if (!playingFile) {
|
||||||
|
Serial.print("Unable to open: ");
|
||||||
|
Serial.println(path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("Playing: ");
|
||||||
|
Serial.println(path);
|
||||||
|
mp3.begin();
|
||||||
|
musicPlaying = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void musicMenu() {
|
||||||
|
scanMusicFiles();
|
||||||
|
musicButtonWasPressed = digitalRead(BUTTON_1) == LOW;
|
||||||
|
int lastMusic = 0;
|
||||||
|
|
||||||
|
while (currentOptionPage == 5) {
|
||||||
|
if (!musicPlaying) handleMusicSelect();
|
||||||
|
drawMusicMenu();
|
||||||
|
|
||||||
|
const bool pressed = digitalRead(BUTTON_1) == LOW;
|
||||||
|
if (pressed && !musicButtonWasPressed && musicFileCount > 0) {
|
||||||
|
if (!musicPlaying || selectedMusic != lastMusic) {
|
||||||
|
// Handle new music play and music switch
|
||||||
|
if (const bool ok = startMusic(selectedMusic); ok) {
|
||||||
|
lastMusic = selectedMusic;
|
||||||
|
musicPaused = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle music pause
|
||||||
|
musicPaused = !musicPaused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
musicButtonWasPressed = pressed;
|
||||||
|
|
||||||
|
feedMusicDecoder();
|
||||||
|
|
||||||
|
currentOptionPage = handleQuitMenuEvent();
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// debug
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// version info
|
||||||
|
Serial.printf("AudioFMSynth v%s\n", VERSION);
|
||||||
|
|
||||||
|
// Initialize display
|
||||||
|
u8g2.begin();
|
||||||
|
u8g2.setContrast(255);
|
||||||
|
|
||||||
|
// fm module
|
||||||
|
Wire.begin();
|
||||||
|
Wire.setClock(100000);
|
||||||
|
|
||||||
|
// ram check
|
||||||
|
if (psramFound()) {
|
||||||
|
Serial.println("PSRAM found!");
|
||||||
|
Serial.printf("PSRAM size: %f MB\n", roundToOneDecimal(ESP.getPsramSize() / (1024 * 1024)));
|
||||||
|
Serial.printf("Free PSRAM: %f MB\n", roundToOneDecimal(ESP.getFreePsram() / (1024 * 1024)));
|
||||||
|
} else {
|
||||||
|
Serial.println("No PSRAM found. Is highly recommend to enable PSRAM if you will use MP3 Player.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan i2c devices
|
||||||
|
int nDevices = 1;
|
||||||
|
|
||||||
|
for(byte address = 1; address < 127; address++ ) {
|
||||||
|
Wire.beginTransmission(address);
|
||||||
|
if (const byte error = Wire.endTransmission(); error == 0) {
|
||||||
|
Serial.print("I2C device found at address 0x");
|
||||||
|
if (address<16) Serial.print("0");
|
||||||
|
Serial.println(address,HEX);
|
||||||
|
nDevices++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nDevices == 0) Serial.println("No I2C devices found");
|
||||||
|
|
||||||
|
// Set font and enable Unicode support
|
||||||
|
u8g2.enableUTF8Print();
|
||||||
|
u8g2.setFont(u8g2_font_6x12_tf);
|
||||||
|
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
|
||||||
|
// Joystick
|
||||||
|
pinMode(JOY_SW, INPUT_PULLUP);
|
||||||
|
pinMode(BUTTON_1, INPUT_PULLUP);
|
||||||
|
analogReadResolution(12);
|
||||||
|
|
||||||
|
// Set center position of the joystick
|
||||||
|
// Serial.println("Correcting joystick... (Don't touch joystick when processing...)");
|
||||||
|
|
||||||
|
// int rawX = analogRead(JOY_X);
|
||||||
|
// int rawY = analogRead(JOY_Y);
|
||||||
|
// int cx = 2048;
|
||||||
|
// int cy = 2048;
|
||||||
|
|
||||||
|
// pcm5102
|
||||||
|
I2S.setPins(I2S_BCLK, I2S_LRCK, I2S_DOUT, -1, -1);
|
||||||
|
|
||||||
|
if (!I2S.begin(
|
||||||
|
I2S_MODE_STD,
|
||||||
|
SAMPLE_RATE,
|
||||||
|
I2S_DATA_BIT_WIDTH_16BIT,
|
||||||
|
I2S_SLOT_MODE_STEREO
|
||||||
|
)) {
|
||||||
|
Serial.println("Unable to init PCM5102 PINs");
|
||||||
|
} else {
|
||||||
|
Serial.println("I2S for PCM5102 is initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SPI bus for sd card reader
|
||||||
|
SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
|
||||||
|
|
||||||
|
// sd card
|
||||||
|
if (!SD.begin(SD_CS, SPI)) {
|
||||||
|
Serial.println("Unable to init SD card module. Did the SD module is installed?");
|
||||||
|
} else {
|
||||||
|
Serial.println("Found SD card module.");
|
||||||
|
|
||||||
|
if (const uint8_t cardType = SD.cardType(); cardType == CARD_NONE) {
|
||||||
|
Serial.println("No SD card detected.");
|
||||||
|
} else {
|
||||||
|
Serial.printf("SD Card Size: %llu MB\n", SD.cardSize() / (1024ULL * 1024ULL));
|
||||||
|
scanMusicFiles();
|
||||||
|
|
||||||
|
for (int i = 0; i < musicFileCount; i++) {
|
||||||
|
Serial.println(musicFiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Initialization Finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (currentOptionPage == 0) {
|
||||||
|
homeMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 1) {
|
||||||
|
joyStickDebugMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 2) {
|
||||||
|
fmBroadcast();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 3) {
|
||||||
|
pcm5102DebugMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 4) {
|
||||||
|
volumeMenu();
|
||||||
|
}
|
||||||
|
else if (currentOptionPage == 5) {
|
||||||
|
musicMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user