
W55RP20을 이용하 Arduino 개발환경에서 테스트 했던 네트웍 스피커 예제는 소프트웨어 스택 (lwIP)의 한계로 전송률 저하가 발생했고 결국 고음질 음원 전송에는 문제가 있어 Pi-Pico C/C++ SDK 개발 환경에서 코드를 다시 작성하였다.
W55RP20에서 TCP서버(process_tcps)를 구현하고 수신되는 데이터 처리는 ProcessCommand에서 I2S로 출력하는 구조를 택했다.
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "pico/audio_i2s.h"
#include "hardware/i2c.h"
#include "w5500_config_tcp.h"
#include "wizchip_conf.h"
#include "socket.h"
#include "audio_data.h"
#define SAMPLE_RATE 44100
//#define SAMPLE_RATE 96000
#define SINE_WAVE_FREQ 440
#define AMPLITUDE 16000.0f
#define MAX_AMPLITUDE 32767.0f
#define SOCK_TCP_SRV 0
#define PORT 5000
#define RX_BUF_SIZE 4096
#define AUDIO_TIMEOUT_MS 500
uint8_t rx_buf[RX_BUF_SIZE];
float current_volume = 0.2f; // 볼륨 50%
uint32_t last_recv_time = 0;
bool is_playing = false;
#define LED_PIN 9
#define LED_PIN2 8
void ProcessCommand(audio_buffer_pool_t *pool, float volume, uint32_t current_time) {
if (getSn_RX_RSR(SOCK_TCP_SRV) > 0) {
int32_t len = recv(SOCK_TCP_SRV, rx_buf, RX_BUF_SIZE);
if (len == SOCK_BUSY) return;
if (len <= 0) {
printf("클라이언트가 연결을 종료했거나 에러가 발생했습니다. (code: %d)\n", len);
close(SOCK_TCP_SRV); // 소켓을 강제로 닫아 SOCK_CLOSED 상태로 유도
is_playing = false;
return;
}
last_recv_time = current_time;
is_playing = true;
int buf_idx = 0;
while (buf_idx < len) {
if ((len - buf_idx) < 4) {
buf_idx = len;
break;
}
struct audio_buffer *buffer = take_audio_buffer(pool, true);
if (!buffer) break;
int16_t *samples = (int16_t *)buffer->buffer->bytes;
uint i = 0;
while (i < buffer->max_sample_count && (buf_idx + 3) < len) {
int16_t left_sample = (rx_buf[buf_idx + 1] << 8) | rx_buf[buf_idx];
int16_t right_sample = (rx_buf[buf_idx + 3] << 8) | rx_buf[buf_idx + 2];
samples[i * 2 + 0] = (int16_t)(left_sample * volume);
samples[i * 2 + 1] = (int16_t)(right_sample * volume);
buf_idx += 4;
i++;
}
buffer->sample_count = i;
give_audio_buffer(pool, buffer);
}
}
else if (is_playing) {
if (current_time - last_recv_time > AUDIO_TIMEOUT_MS) {
printf("audio time out\n");
is_playing = false;
for (int k = 0; k < 3; k++) {
struct audio_buffer *buffer = take_audio_buffer(pool, false);
if (buffer) {
int16_t *samples = (int16_t *)buffer->buffer->bytes;
for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i * 2 + 0] = 0;
samples[i * 2 + 1] = 0;
}
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(pool, buffer);
}
}
}
}
}
void process_tcps(audio_buffer_pool_t *pool, float volume) {
uint32_t current_time = to_ms_since_boot(get_absolute_time());
switch (getSn_SR(SOCK_TCP_SRV)) {
case SOCK_ESTABLISHED:
ProcessCommand(pool, volume, current_time);
gpio_put(LED_PIN2, 0);
break;
case SOCK_CLOSE_WAIT:
printf("Client Disconnected (CLOSE_WAIT).\n");
gpio_put(LED_PIN2, 1);
is_playing = false;
disconnect(SOCK_TCP_SRV);
close(SOCK_TCP_SRV);
break;
case SOCK_INIT:
is_playing = false;
listen(SOCK_TCP_SRV);
printf("Socket Listen... Ready for connection\n");
break;
case SOCK_CLOSED:
is_playing = false;
socket(SOCK_TCP_SRV, Sn_MR_TCP, PORT, 0x00);
last_recv_time = to_ms_since_boot(get_absolute_time());
break;
default:
break;
}
}
int main() {
uint32_t last_led_time = 0;
bool led_state = false;
stdio_init_all();
printf("Starting I2S Sine Wave Output...\n");
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_init(LED_PIN2);
gpio_set_dir(LED_PIN2, GPIO_OUT);
gpio_put(LED_PIN2, 1);
// Initialize I2C peripheral at 100 kHz
i2c_init(I2C_PORT, 100 * 1000);
// Assign GPIO pins to the I2C peripheral function
gpio_set_function(PIN_SDA, GPIO_FUNC_I2C);
gpio_set_function(PIN_SCL, GPIO_FUNC_I2C);
printf("audio init\n");
audio_format_t audio_format = {
.sample_freq = SAMPLE_RATE,
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
.channel_count = 2,
};
struct audio_buffer_format producer_format = {
.format = &audio_format,
.sample_stride = 4
};
struct audio_i2s_config config = {
.data_pin = I2S_DATA_PIN,
.clock_pin_base = I2S_CLOCK_PIN_BASE,
.dma_channel = 0,
.pio_sm = 0,
};
printf("audio_i2s_setup\n");
const audio_format_t *output_format = audio_i2s_setup(&audio_format, &config);
if (!output_format) {
panic("PicoAudio: Unable to open I2S audio device.\n");
}
printf("audio_new_producer_pool\n");
audio_buffer_pool_t *producer_pool = audio_new_producer_pool(&producer_format, 3, 256);
if (!producer_pool) {
panic("PicoAudio: Failed to create buffer pool.\n");
}
printf("audio_i2s_connect\n");
bool connection_ok = audio_i2s_connect(producer_pool);
if (!connection_ok) {
panic("PicoAudio: Unable to connect to I2S device.\n");
}
printf("audio_i2s_set_enabled\n");
audio_i2s_set_enabled(true);
initTAS5825P();
readTAS5825P_DieId();
uint32_t current_wav_pos = 44;
InitEthernet();
printf("W55RP20 Start\n");
while (true) {
process_tcps(producer_pool, current_volume);
uint32_t current_time = to_ms_since_boot(get_absolute_time());
if (is_playing) {
if (current_time - last_led_time > 200) {
led_state = !led_state;
gpio_put(LED_PIN, led_state);
last_led_time = current_time;
}
} else {
gpio_put(LED_PIN, false);
}
}
return 0;
}
이번에 WAV파일을 전송하는 소프트웨어를 업그레이드 해서 대시보드 형태로 제작했다.
원하는 스피커 IP를 선택하고 원하는 음원을 설정하면 실시간으로 WAV파일로 변환하여 네트웍 스피커로 송출하도록 하였다.
네트웍 방송을 할수 있도록 TTS기능을 추가 해서 텍스트를 WAV파일로 변환해서 바로 송출 할수도 있다.
그리고 유튜브링크를 입력하면 실시간으로 음원을 추출해서 음악을 송출 할수 있는 기능도 추가 하였다.
(참고로 제품을 위해서는 더 많은(?) 기능을 더 추가 해야하지만 우선 간단하게 테스트 해볼 수 있는것들을 최대한 넣어서 테스트 해 보았다.)

W55RP20 기반의 네트웍 스피커 구현 테스트 결과 동영상
앞으로 통신 프로토콜을 네트웍 스피커에 맞는 UDP로 변경하고 네트웍 스피커의 전원 공급을 위한 PoE 기능을 추가 하면 될것 같다.