ESP32公网对讲机

一、前言

其实做这个项目最初的痛点是为了改善家里的楼上楼下交流(家里房子带阁楼)太费嗓子的问题,不要说什么打电话、微信语音什么的,不接受!存在即合理!顺便正好用ESP32做点东西,那么就开始吧。

二、介绍

2.1 音频
  • 音道

目前常见的有单声道,双声道这都很好理解

还有2.1声道 就是在双声道上加了个低音声道

5.1声道 早期影院 分别是正前方声道、左前方声道、右前方声道,左声道,右声道和一个低音声道

现在影院一般是7.1声道

  • 采样频率

音频采样就是从模拟信号转化为数字信号的过程,采样率就是每秒对声音采样的次数,采样率越高声音就越自然,人类对频率可识别范围大概在20hz~20000hz间。常见的采样频率44.1khz, 8khz, 16khz等 其实8khz对于人来说已经足够了

  • 采样位数

采样后还要进行量化和编码操作。位数常见的有8位,16位,32位等

  • dma_buf_len及dma_buf_cnt说明

I2S DMA

2.2 方案

使用UDP广播进行通信。将UDP数据包发送至一个特殊地址ip(公网ip),最后通过路由器将数据广播到网络中所有的设备.即可以做到只要设备联网后不管多远都可以进行通信。

使用UDP最大的好处就是可以不用等待对方有没有收到,所有在监听的设备都能收到,繁重的工作都交给路由器处理啦。

2.3 过程
  • 硬件

主控: ESP32 *2

Mic: Inmp411 *2

功放: MAX98357A *2

喇叭 *2

  1. 首先测试Mic和功放的好坏--通过Mic将我说的话通过喇叭播放出来。这里使用的Mic和功放都是I2S 标准飞利浦协议进行数据传输

Inmp411

(引脚只有 对右边VCC GND SD,对左边L/R WS SCK)

L/R为左右声道选择,这里选择的是左声道 注意要接GND

具体引脚定义

#define MIC_I2S_NUM (0)
#define MIC_I2S_BCK_IO GPIO_NUM_32
#define MIC_I2S_WS_IO GPIO_NUM_25
#define MIC_I2S_DO_IO GPIO_NUM_33

Inmp411是走I2S协议的,程序里面使用单声道(左) 所有只有在低电平时候数据有效

我使用的IDF是V4.3的 I2S配置过程:

需要先安装程序 i2s_driver_install();

再配置引脚 i2s_set_pin()

相关需要使用到的其他接口eg: i2s_read(); i2s_write();等

具体配置如下

    void Inmp441_Config(StreamBufferHandle_t mic_stream_buf)
    {
        i2s_config_t i2s_config = {
         // 接收模式
        .mode = I2S_MODE_MASTER | I2S_MODE_RX,
        // 采样率 8000khz
        .sample_rate = SAMPLE_RATE,
        // 采样位数 16位
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        // 采样通道为左通道
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        // I2S通信格式I2S 标准飞利浦
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        //
        .dma_buf_count = 3,
        .dma_buf_len = 256,
        .use_apll = false,
        .tx_desc_auto_clear = false,
        .fixed_mclk = 0,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 // Interrupt level 1
    };
    esp_err_t err = i2s_driver_install(MIC_I2S_NUM, &i2s_config, 0, NULL);
    if (err != ESP_OK)
    {
        ESP_LOGI(TAG, "Error initializing I2S Mic\n");
    }

    i2s_pin_config_t pin_config = {
        .bck_io_num = MIC_I2S_BCK_IO,
        .ws_io_num = MIC_I2S_WS_IO,
        .data_in_num = MIC_I2S_DO_IO};
    err = i2s_set_pin(MIC_I2S_NUM, &pin_config);
    if (err != ESP_OK)
    {
        ESP_LOGI(TAG, "Error setting I2S mic pins\n");
    }

    ESP_LOGI(TAG, "Init audio!\n");
    xTaskCreate(Read_Inmp441_Data_Task, "Read_Inmp441_Data_Task", 4096, (void *)mic_stream_buf, 4, NULL);//(void *)mic_stream_buf
}

为了验证mic数据通信是否正常可以循环读取i2s数据并在上位机上打印出来,说不同的话、不同的音量的打印数值大小对应的数据正常都是会有变化的

i2s_read(MIC_I2S_NUM,
                 &mic_read_buf,
                 READ_BUF_SIZE_BYTES * 2,
                 &i2s_byte_data,
                 ticks_to_wait);
        if (i2s_byte_data > 0)
        {
//          ESP_LOGI(TAG, "mic_read_data = %d", mic_read_data);
            ESP_LOGI(TAG, "mic_read_buf[0] = %d, [1] = %d, [2] = %d, [3] = %d", mic_read_buf[0], mic_read_buf[1], mic_read_buf[2], mic_read_buf[3]);

对于i2s_read()的第三个参数,是以字节为单位!

MAX98357A

引脚定义

#define MAX_I2S_NUM (1)
#define MAX_I2S_BCK_IO GPIO_NUM_4
#define MAX_I2S_WS_IO  GPIO_NUM_5
#define MAX_I2S_DO_IO  GPIO_NUM_18

MAX98357通信也是I2S协议的

如果选用的是左边声道的话,LRC高电平时数据就是无效的。

具体配置如下

void MAX98357_Config(StreamBufferHandle_t playback_stream_buf)

{

i2s_config_t i2s_config = {

        //发送模式
        .mode = I2S_MODE_MASTER | I2S_MODE_TX,
        //采样率 8000khz
        .sample_rate = SAMPLE_RATE,
        //采样位数 16位
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        //采样通道为左通道
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        //I2S通信格式I2S 标准飞利浦
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        //
        .dma_buf_count = 3,
        .dma_buf_len = 256,
        .use_apll = false,
        .tx_desc_auto_clear = false,
        .fixed_mclk = 0,
    };
    esp_err_t err = i2s_driver_install(MAX_I2S_NUM, &i2s_config, 0, NULL);
    if (err != ESP_OK)
    {
        ESP_LOGI(TAG, "Error initializing I2S MAX98357\n");
    }

    i2s_pin_config_t pin_config = { 
        .bck_io_num = MAX_I2S_BCK_IO,
        .ws_io_num = MAX_I2S_WS_IO,
        .data_out_num = MAX_I2S_DO_IO
    };
    err = i2s_set_pin(MAX_I2S_NUM, &pin_config);
    if (err != ESP_OK)
    {
        ESP_LOGI(TAG, "Error setting I2S MAX98357 Pins\n");
    }

    ESP_LOGI(TAG, "Init MAX98357!\n");
    xTaskCreate(Playback_MAX98357_Task, "Playback_MAX98357_Task", 4096, (void *)playback_stream_buf, 5, NULL);
}

我们知道队列虽然可以做任务间消息传递的功能,但不方便实时采集不同数据长度的数据,FreeRTOS中给我们提供了一个流缓冲数据--StreamBuff。所谓流数据就像流水一样不断地产生、不断地传输。最典型的就是这种音频数据。

主要关注这几个API接口

xStreamBufferCreate();

xStreamBufferReceive();

xStreamBufferSend();

首先创建结构体

StreamBufferHandle_t mic_stream_buf = NULL;
StreamBufferHandle_t inm_stream_buf = NULL;

然后创建流缓冲区

//创建流缓冲区
mic_stream_buf = xStreamBufferCreate(1200, 2);
inm_stream_buf = xStreamBufferCreate(1200, 2);
Inmp441_Config(mic_stream_buf);
//    Test_1(mic_stream_buf);
MAX98357_Config(inm_stream_buf);
UDP_Connect_Init(mic_stream_buf, inm_stream_buf);

再在相应的有需要地方进行接收和发送。

2. 测试UDP数据转发--之前已经测试过UDP通信了这里就直接进行数据转发。先可以在局域网内测试,把PC当做Serve(需要在PC上写个服务端程序,反正是测试嘛怎么简单怎么来那就用python随便写几行),ESP32当做Client。ESP32通过UDP把数据先发送给Serve,Serve只做转发把收到的数据再发送给Client Client收到数据后直接通过I2S写入功放 至此就可以通过喇叭将从Mic中读到数据播放出来。所以这里创建了两个任务,一个发送任务、一个接收任务

先看发送端

void Send_Data_Task(void *pvParameters)
{
    StreamBufferHandle_t mic_stream_buf = (StreamBufferHandle_t)pvParameters;

    while(1)
    {
        //接收到mic的数据后 不断将数据发送至对应的服务器地址 端口上
        size_t recv_bytes = xStreamBufferReceive(mic_stream_buf,
                                                 &send_mic_buff,
                                                 sizeof(send_mic_buff),
                                                 portMAX_DELAY);
        if(recv_bytes > 0)
        {   
            sendto(client_socket,
                   send_mic_buff,
                   sizeof(send_mic_buff),
                   0,
                   (struct sockaddr *)&server_addr,
                   sizeof(server_addr)); 
        }
    }
}

接收端任务

void Recv_Data_Task(void *pvParameters)
{
    StreamBufferHandle_t inm_stream_buf = (StreamBufferHandle_t)pvParameters;
    int len = 0;

    while(1)
    {
//        memset(recv_mic_buff, 0x00, sizeof(recv_mic_buff)); 
        //接收到服务器发来的数据
        len = recvfrom(client_socket,
                       recv_mic_buff,
                       sizeof(recv_mic_buff),
                       0,
                       (struct sockaddr *)&server_addr,
                       &socklen);
        ESP_LOGI(TAG, "recv_mic_buff[0] = %d, [1] = %d, [2] = %d, [3] = %d", 
                 recv_mic_buff[0], recv_mic_buff[1], recv_mic_buff[2], recv_mic_buff[3]);
        if(len > 0)
        {
            xStreamBufferSend(inm_stream_buf,
                              (void *)recv_mic_buff,
                              sizeof(recv_mic_buff),
                              portMAX_DELAY); 
        }
    }
}

3. 因为是想做个公网对讲机嘛所以得有个公网ip。害,确实为了UDP服务器还整了个阿里云服务器有点奢侈,不过后面还是会用在MQTT服务器的搭建上。既然在局域网内测试通过了那在云上跑的话逻辑也是一样的,不过这里有两点改动。

  • Serve端bind地址和端口以及Client端要连接的服务器的地址和端口

  • 服务端转发逻辑的改变,要将Client_A发送的音频数据通过Serve在发送给Client_B, B收到后给功放任务 从而把消息从一边带到另一边,反向同理。

三、阿里云服务器使用

第一次接触服务器云服务器相关东西简单总结一下使用过程中需要注意的一些东西。

  1. 注册地址

阿里云

进入之后可以根据引导进行选择购买

  1. 设置你的root 账号和密码

  1. !!!注意 会有两ip,一个是公网ip一个是私有ip

Client要连接的服务器地址是你的公网ip;Serve端bind的服务器地址是私有地址

  1. 设置安全组在安全组中找到配置规则然进来就如图所示的样子,在入网方向中设置协议类型以及端口号等,记得最后需要保存呀。一般默认里面会创建有三个,其中一个是TCP协议端口号22(SSH);这里使用UDP协议的所以要进行手动添加一个。端口号的设定只要符合规则就好

  1. 云服务器相当于一台电脑,你需要把你的代码移动到这台电脑上,可以使用SFTP进行传输。这里用到的工具是MobaXterm

  1. 远程连接这台电脑,可以通过SSH进行连接,也可以在Ubuntu下进行命令形式连接

至此配置就基本结束了,后面启动程序后就可以愉快的玩耍了

四、总结

整个项目断断续续大概花了13天左右时间完成(实际上可以更快点),其实整个项目从硬件、软件再到云端都是涉及的还是学到很多东西。坑填完了...但没有完全填完,希望下一个主线项目比这个更有挑战!

  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值