-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
387 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,387 @@ | ||
#include <DebugLog.h> | ||
#include <RadioLib.h> | ||
#include <Wire.h> | ||
#include <driver/i2s.h> | ||
#include <codec2.h> | ||
#include <CircularBuffer.h> | ||
#include <arduino-timer.h> | ||
|
||
//#define ENABLE_LIGHT_SLEEP true | ||
|
||
// serial1 | ||
#define SERIAL_BAUD_RATE 115200 // USB serial baud rate | ||
|
||
// ptt button | ||
#define PTTBTN_PIN 39 | ||
volatile bool btn_pressed_ = false; | ||
|
||
// lora module | ||
|
||
// lora module pinouts | ||
#define LORA_RADIO_PIN_SS SS | ||
#define LORA_RADIO_PIN_RST 27 | ||
#define LORA_RADIO_PIN_A 12 | ||
#define LORA_RADIO_PIN_B 14 | ||
#define LORA_RADIO_PIN_RXEN 32 | ||
#define LORA_RADIO_PIN_TXEN 33 | ||
|
||
// lora modulation parameters | ||
#define LORA_RADIO_FREQ 433.775 | ||
#define LORA_RADIO_BW 125.0 | ||
#define LORA_RADIO_SF 9 | ||
#define LORA_RADIO_CR 7 | ||
#define LORA_RADIO_PWR 2 | ||
#define LORA_RADIO_CRC 1 | ||
#define LORA_RADIO_EXPL true | ||
#define LORA_RADIO_SYNC 0x34 | ||
|
||
// lora support config | ||
#define LORA_RADIO_BUF_LEN 256 // packet buffer size | ||
#define LORA_RADIO_QUEUE_LEN 512 // queue length | ||
|
||
#define LORA_RADIO_TASK_RX_BIT 0x01 // lora task rx bit command | ||
#define LORA_RADIO_TASK_TX_BIT 0x02 // lora task tx bit command | ||
|
||
// lora task packet queues | ||
CircularBuffer<uint8_t, LORA_RADIO_QUEUE_LEN> lora_radio_rx_queue_; | ||
CircularBuffer<uint8_t, LORA_RADIO_QUEUE_LEN> lora_radio_rx_queue_index_; | ||
CircularBuffer<uint8_t, LORA_RADIO_QUEUE_LEN> lora_radio_tx_queue_; | ||
CircularBuffer<uint8_t, LORA_RADIO_QUEUE_LEN> lora_radio_tx_queue_index_; | ||
|
||
// packet buffers | ||
byte lora_radio_rx_buf_[LORA_RADIO_BUF_LEN]; // tx packet buffer | ||
byte lora_radio_tx_buf_[LORA_RADIO_BUF_LEN]; // rx packet buffer | ||
|
||
TaskHandle_t lora_task_; // lora rx/tx task | ||
volatile bool lora_enable_isr_ = true; // true to enable rx isr, disabled on tx | ||
SX1268 lora_radio_ = new Module(LORA_RADIO_PIN_SS, LORA_RADIO_PIN_A, LORA_RADIO_PIN_RST, LORA_RADIO_PIN_B); | ||
|
||
// audio speaker pinouts | ||
#define AUDIO_SPEAKER_BCLK 26 | ||
#define AUDIO_SPEAKER_LRC 13 | ||
#define AUDIO_SPEAKER_DIN 25 | ||
|
||
// audio mic pinouts | ||
#define AUDIO_MIC_SD 2 | ||
#define AUDIO_MIC_WS 15 | ||
#define AUDIO_MIC_SCK 4 | ||
|
||
// audio support | ||
#define AUDIO_CODEC2_MODE CODEC2_MODE_450 // codec2 mode | ||
#define AUDIO_SAMPLE_RATE 8000 // audio sample rate | ||
#define AUDIO_MAX_PACKET_SIZE 48 // maximum packet size, multiple audio frames are inside | ||
#define AUDIO_TASK_PLAY_BIT 0x01 // task bit flag to start playback | ||
#define AUDIO_TASK_RECORD_BIT 0x02 // task bit flag to start recording | ||
|
||
// audio task | ||
TaskHandle_t audio_task_; /// audio playback/record task | ||
|
||
// codec2 | ||
struct CODEC2* c2_; // codec2 instance | ||
int c2_samples_per_frame_; // how many raw samples in one frame | ||
int c2_bytes_per_frame_; // how many bytes in encoded frame | ||
int16_t *c2_samples_; // buffer for raw samples | ||
uint8_t *c2_bits_; // buffer for encoded frame | ||
|
||
// deep sleep | ||
#define LIGHT_SLEEP_DELAY_MS 5000 // how long to wait before engering light sleep | ||
#define LIGHT_SLEEP_BITMASK (uint64_t)(1 << LORA_RADIO_PIN_A) | (1 << LORA_RADIO_PIN_B) // bit mask for ext1 high pin wake up | ||
|
||
Timer<1> light_sleep_timer_; // light sleep timer | ||
uintptr_t light_sleep_timer_task_; // light sleep timer task | ||
|
||
void setup() { | ||
// setup logging | ||
LOG_SET_LEVEL(DebugLogLevel::LVL_INFO); | ||
//LOG_SET_LEVEL(DebugLogLevel::LVL_DEBUG); | ||
LOG_SET_OPTION(false, false, true); // disable file, line, enable func | ||
|
||
// initialize serial | ||
Serial.begin(SERIAL_BAUD_RATE); | ||
while (!Serial); | ||
LOG_INFO("Board setup started"); | ||
|
||
// setup lora radio | ||
int lora_radio_state = lora_radio_.begin(LORA_RADIO_FREQ, LORA_RADIO_BW, LORA_RADIO_SF, LORA_RADIO_CR, LORA_RADIO_SYNC, LORA_RADIO_PWR); | ||
if (lora_radio_state == RADIOLIB_ERR_NONE) { | ||
LOG_INFO("Lora radio initialized"); | ||
lora_radio_.setCRC(LORA_RADIO_CRC); | ||
lora_radio_.setRfSwitchPins(LORA_RADIO_PIN_RXEN, LORA_RADIO_PIN_TXEN); | ||
lora_radio_.clearDio1Action(); | ||
lora_radio_.explicitHeader(); | ||
lora_radio_.setDio1Action(onLoraDataAvailableIsr); | ||
} else { | ||
LOG_ERROR("Lora radio start failed:", lora_radio_state); | ||
} | ||
|
||
// setup ptt button | ||
pinMode(PTTBTN_PIN, INPUT); | ||
|
||
// setup speaker | ||
i2s_config_t i2s_speaker_config = { | ||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), | ||
.sample_rate = AUDIO_SAMPLE_RATE, | ||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, | ||
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), | ||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||
.dma_buf_count = 8, | ||
.dma_buf_len = 1024, | ||
.use_apll=0, | ||
.tx_desc_auto_clear= true, | ||
.fixed_mclk=-1 | ||
}; | ||
i2s_pin_config_t i2s_speaker_pin_config = { | ||
.bck_io_num = AUDIO_SPEAKER_BCLK, | ||
.ws_io_num = AUDIO_SPEAKER_LRC, | ||
.data_out_num = AUDIO_SPEAKER_DIN, | ||
.data_in_num = I2S_PIN_NO_CHANGE | ||
}; | ||
if (i2s_driver_install(I2S_NUM_0, &i2s_speaker_config, 0, NULL) != ESP_OK) { | ||
LOG_ERROR("Failed to install i2s speaker driver"); | ||
} | ||
if (i2s_set_pin(I2S_NUM_0, &i2s_speaker_pin_config) != ESP_OK) { | ||
LOG_ERROR("Failed to set i2s speaker pins"); | ||
} | ||
|
||
// setup microphone | ||
i2s_config_t i2s_mic_config = { | ||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), | ||
.sample_rate = AUDIO_SAMPLE_RATE, | ||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, | ||
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, | ||
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), | ||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||
.dma_buf_count = 8, | ||
.dma_buf_len = 1024, | ||
.use_apll=0, | ||
.tx_desc_auto_clear= true, | ||
.fixed_mclk=-1 | ||
}; | ||
i2s_pin_config_t i2s_mic_pin_config = { | ||
.bck_io_num = AUDIO_MIC_SCK, | ||
.ws_io_num = AUDIO_MIC_WS, | ||
.data_out_num = I2S_PIN_NO_CHANGE, | ||
.data_in_num = AUDIO_MIC_SD | ||
}; | ||
if (i2s_driver_install(I2S_NUM_1, &i2s_mic_config, 0, NULL) != ESP_OK) { | ||
LOG_ERROR("Failed to install i2s mic driver"); | ||
} | ||
if (i2s_set_pin(I2S_NUM_1, &i2s_mic_pin_config) != ESP_OK) { | ||
LOG_ERROR("Failed to set i2s mic pins"); | ||
} | ||
|
||
// start audio playback/recordign task | ||
xTaskCreate(&audio_task, "audio_task", 32000, NULL, 5, &audio_task_); | ||
|
||
// start lora receive/transmit task task | ||
xTaskCreate(&lora_task, "lora_task", 8000, NULL, 5, &lora_task_); | ||
int state = lora_radio_.startReceive(); | ||
if (state != RADIOLIB_ERR_NONE) { | ||
LOG_ERROR("Receive start error:", state); | ||
} | ||
LOG_INFO("Board setup completed"); | ||
|
||
#ifdef ENABLE_LIGHT_SLEEP | ||
LOG_INFO("Light sleep is enabled"); | ||
#endif | ||
light_sleep_reset(); | ||
} | ||
|
||
// ISR is called when new data is available from radio | ||
ICACHE_RAM_ATTR void onLoraDataAvailableIsr() { | ||
if (!lora_enable_isr_) return; | ||
BaseType_t xHigherPriorityTaskWoken; | ||
uint32_t lora_rx_bit = LORA_RADIO_TASK_RX_BIT; | ||
// notify radio receive task on new data arrival | ||
xTaskNotifyFromISR(lora_task_, lora_rx_bit, eSetBits, &xHigherPriorityTaskWoken); | ||
} | ||
|
||
// start counting timer for light sleep | ||
void light_sleep_reset() { | ||
#ifdef ENABLE_LIGHT_SLEEP | ||
LOG_DEBUG("Reset light sleep"); | ||
if (light_sleep_timer_task_ != NULL) light_sleep_timer_.cancel(light_sleep_timer_task_); | ||
light_sleep_timer_task_ = light_sleep_timer_.in(LIGHT_SLEEP_DELAY_MS, light_sleep); | ||
#endif | ||
} | ||
|
||
// called by timer to enter light sleep | ||
bool light_sleep(void *param) { | ||
#ifdef ENABLE_LIGHT_SLEEP | ||
LOG_INFO("Entering light sleep"); | ||
esp_sleep_enable_ext0_wakeup(GPIO_NUM_39, 0); | ||
esp_sleep_enable_ext1_wakeup(LIGHT_SLEEP_BITMASK, ESP_EXT1_WAKEUP_ANY_HIGH); | ||
delay(100); | ||
esp_light_sleep_start(); | ||
LOG_INFO("Exiting light sleep"); | ||
#endif | ||
return false; | ||
} | ||
|
||
// lora trasmit receive task | ||
void lora_task(void *param) { | ||
LOG_INFO("Lora task started"); | ||
|
||
// wait for ISR notification, read data and send for audio processing | ||
while (true) { | ||
uint32_t lora_status_bits = 0; | ||
xTaskNotifyWaitIndexed(0, 0x00, ULONG_MAX, &lora_status_bits, portMAX_DELAY); | ||
|
||
LOG_DEBUG("Lora task bits", lora_status_bits); | ||
|
||
// lora rx | ||
if (lora_status_bits & LORA_RADIO_TASK_RX_BIT) { | ||
int packet_size = lora_radio_.getPacketLength(); | ||
if (packet_size > 0) { | ||
int state = lora_radio_.readData(lora_radio_rx_buf_, packet_size); | ||
if (state == RADIOLIB_ERR_NONE) { | ||
// process packet | ||
LOG_DEBUG("Received packet, size", packet_size); | ||
if (packet_size % c2_bytes_per_frame_ == 0) { | ||
for (int i = 0; i < packet_size; i++) { | ||
lora_radio_rx_queue_.push(lora_radio_rx_buf_[i]); | ||
} | ||
lora_radio_rx_queue_index_.push(packet_size); | ||
uint32_t audio_play_bit = AUDIO_TASK_PLAY_BIT; | ||
xTaskNotify(audio_task_, audio_play_bit, eSetBits); | ||
} else { | ||
LOG_ERROR("Audio packet of wrong size, expected mod", c2_bytes_per_frame_); | ||
} | ||
} else { | ||
LOG_ERROR("Read data error: ", state); | ||
} | ||
// probably not needed, still in receive | ||
state = lora_radio_.startReceive(); | ||
if (state != RADIOLIB_ERR_NONE) { | ||
LOG_ERROR("Start receive error: ", state); | ||
} | ||
light_sleep_reset(); | ||
} // packet size > 0 | ||
} // lora rx | ||
// lora tx data | ||
else if (lora_status_bits & LORA_RADIO_TASK_TX_BIT) { | ||
lora_enable_isr_ = false; | ||
// take packet by packet | ||
while (lora_radio_tx_queue_index_.size() > 0) { | ||
// take packet size and read it | ||
int tx_bytes_cnt = lora_radio_tx_queue_index_.shift(); | ||
for (int i = 0; i < tx_bytes_cnt; i++) { | ||
lora_radio_tx_buf_[i] = lora_radio_tx_queue_.shift(); | ||
} | ||
// transmit packet | ||
int lora_radio_state = lora_radio_.transmit(lora_radio_tx_buf_, tx_bytes_cnt); | ||
if (lora_radio_state != RADIOLIB_ERR_NONE) { | ||
LOG_ERROR("Lora radio transmit failed:", lora_radio_state); | ||
} | ||
LOG_DEBUG("Transmitted packet", tx_bytes_cnt); | ||
vTaskDelay(1); | ||
} // packet transmit loop | ||
|
||
// switch to receive after all transmitted | ||
int lora_radio_state = lora_radio_.startReceive(); | ||
if (lora_radio_state != RADIOLIB_ERR_NONE) { | ||
LOG_ERROR("Start receive error: ", lora_radio_state); | ||
} | ||
lora_enable_isr_ = true; | ||
light_sleep_reset(); | ||
} // lora tx | ||
} // task loop | ||
} | ||
|
||
// audio record/playback encode/decode task | ||
void audio_task(void *param) { | ||
LOG_INFO("Audio task started"); | ||
|
||
// construct codec2 | ||
c2_ = codec2_create(AUDIO_CODEC2_MODE); | ||
if (c2_ == NULL) { | ||
LOG_ERROR("Failed to create codec2"); | ||
return; | ||
} else { | ||
c2_samples_per_frame_ = codec2_samples_per_frame(c2_); | ||
c2_bytes_per_frame_ = codec2_bytes_per_frame(c2_); | ||
c2_samples_ = (int16_t*)malloc(sizeof(int16_t) * c2_samples_per_frame_); | ||
c2_bits_ = (uint8_t*)malloc(sizeof(uint8_t) * c2_bytes_per_frame_); | ||
LOG_INFO("C2 initialized", c2_samples_per_frame_, c2_bytes_per_frame_); | ||
} | ||
|
||
// wait for data notification, decode frames and playback | ||
size_t bytes_read, bytes_written; | ||
while(true) { | ||
uint32_t audio_bits = 0; | ||
xTaskNotifyWaitIndexed(0, 0x00, ULONG_MAX, &audio_bits, portMAX_DELAY); | ||
|
||
LOG_DEBUG("Audio task bits", audio_bits); | ||
|
||
// audio rx-decode-playback | ||
if (audio_bits & AUDIO_TASK_PLAY_BIT) { | ||
LOG_DEBUG("Playing audio"); | ||
// while rx frames are available and button is not pressed | ||
while (!btn_pressed_ && lora_radio_rx_queue_index_.size() > 0) { | ||
int packet_size = lora_radio_rx_queue_index_.shift(); | ||
LOG_DEBUG("Playing packet", packet_size); | ||
// split by frame, decode and play | ||
for (int i = 0; i < packet_size; i++) { | ||
c2_bits_[i % c2_bytes_per_frame_] = lora_radio_rx_queue_.shift(); | ||
if (i % c2_bytes_per_frame_ == c2_bytes_per_frame_ - 1) { | ||
codec2_decode(c2_, c2_samples_, c2_bits_); | ||
i2s_write(I2S_NUM_0, c2_samples_, sizeof(uint16_t) * c2_samples_per_frame_, &bytes_written, portMAX_DELAY); | ||
vTaskDelay(1); | ||
} | ||
} | ||
} // while rx data available | ||
} // audio decode playback | ||
// audio record-encode-tx | ||
else if (audio_bits & AUDIO_TASK_RECORD_BIT) { | ||
LOG_DEBUG("Recording audio"); | ||
int packet_size = 0; | ||
// record while button is pressed | ||
while (btn_pressed_) { | ||
// send packet if enough audio encoded frames are accumulated | ||
if (packet_size + c2_bytes_per_frame_ > AUDIO_MAX_PACKET_SIZE) { | ||
LOG_DEBUG("Recorded packet", packet_size); | ||
lora_radio_tx_queue_index_.push(packet_size); | ||
uint32_t lora_tx_bits = LORA_RADIO_TASK_TX_BIT; | ||
xTaskNotify(lora_task_, lora_tx_bits, eSetBits); | ||
packet_size = 0; | ||
} | ||
// read and encode one sample | ||
size_t bytes_read; | ||
i2s_read(I2S_NUM_1, c2_samples_, sizeof(uint16_t) * c2_samples_per_frame_, &bytes_read, portMAX_DELAY); | ||
codec2_encode(c2_, c2_bits_, c2_samples_); | ||
for (int i = 0; i < c2_bytes_per_frame_; i++) { | ||
lora_radio_tx_queue_.push(c2_bits_[i]); | ||
} | ||
packet_size += c2_bytes_per_frame_; | ||
vTaskDelay(1); | ||
} // btn_pressed_ | ||
// send remaining tail audio encoded samples | ||
if (packet_size > 0) { | ||
LOG_DEBUG("Recorded packet", packet_size); | ||
lora_radio_tx_queue_index_.push(packet_size); | ||
uint32_t lora_tx_bits = LORA_RADIO_TASK_TX_BIT; | ||
xTaskNotify(lora_task_, lora_tx_bits, eSetBits); | ||
packet_size = 0; | ||
} | ||
vTaskDelay(1); | ||
} // task bit | ||
} | ||
} | ||
|
||
void loop() { | ||
// button | ||
if (digitalRead(PTTBTN_PIN) == LOW && !btn_pressed_) { | ||
LOG_DEBUG("PTT pushed, start TX"); | ||
btn_pressed_ = true; | ||
// notify to start recording | ||
uint32_t audio_bits = AUDIO_TASK_RECORD_BIT; | ||
xTaskNotify(audio_task_, audio_bits, eSetBits); | ||
} else if (digitalRead(PTTBTN_PIN) == HIGH && btn_pressed_) { | ||
LOG_DEBUG("PTT released"); | ||
btn_pressed_ = false; | ||
} | ||
light_sleep_timer_.tick(); | ||
delay(50); | ||
} | ||
|