esp8266-i2s无线音频--转

I2S 3-Wire Connection

原网址 https://circuitdigest.com/microcontroller-projects/i2s-communication-on-esp32-to-transmit-and-receive-audio-data-using-max98357a

The I2S protocol uses three wires for communication. The connection between the transmitter and the receiver is shown below.

I2S 3-Wire Connection

Word Select (WS) or Frame Select (FS) wire:

Since the I2S protocol uses both stereo operations, the channel left or Right can be selected using the WS or Word Select pin. Generally, If the WP pin is in Low state, Channel 1 or the Left channel is selected, on the other hand, if the WS pin is in the High State, the right channel is used.

To summarise: 

 If WS pin = 0 then Channel 1 or left channel is activated

If WS pin = 1 then Channel 2 or Right channel is activated
Irrespective of the above channel selection pin, the I2S protocol uses two additional pins that are most common in any serial interface.

Serial Data Pin or SD:

This second line of the I2S communication protocol is the Serial Data or the SD Pin that carries the data signal. The transmission of data through this line uses the 2 complements method.

In I2S data transfer, the most significant bit (MSB) is transferred first making it an MSB first data line. This is done for an obvious reason. In I2S, the data can be in different word lengths that are transmitted from the transmitter to the receiver.

Thus, if the MSB is first transmitted, there would be no dependencies for the transmitter and the receiver to know how many bits are transferred or coming in.

This opens a new problem, since the receiver and the transmitter do not know the word length, it becomes difficult to match the data since it can be lost during data transmission due to noise and other factors. This is solved using the WS pin or WS line.

If the WS of the receiver is greater than the WS of the transmitter, the word is truncated where the least significant data bits are set to 0. If the WS of the receiver is less than the WS of the transmitter, the bits after the LSB are ignored.

Bit clock line or BCLK:

The last and most important pin of the I2S communication is the Serial Clock (SCK) also called the bit clock line (BCLK). As suggested in the name, it is a clock pin and it is important in the serial data communication protocol.

It is used to get all components on the same cycle. This BCLK line frequency is dependent on the sample rate, Bits per channel, and the number of channels it is using.

The formula that can be used to get the frequency is:

Frequency = Sample Rate x Bits per channel x Number of channels.

For an example:

Sample rate: 44.1 kHz

Bits per channel: 8

Number of channels: 2 [Stereo]

Therefore, the serial clock has a frequency of 44.1 kHz * 8 * 2 = 705 kHz.

I2S Timing Diagram

In the below diagram, the timing sequence is shown:

I2S Timing Diagram

In the above timing diagram, three lines are shown, BCLK, WS, SD. The BCLK will provide the required clock cycles for the I2S line. The WS is changing its state from the Right channel to the Left channel.

The data is transmitted over the I2S and it is sent on every clock cycle. The WS pin changes its state from one channel to another channel before one clock cycle of the I2S data line starts transmitting the MSB. This is due to the receiver requiring time to store the word that is previously sent and clear the register.

I2S Controller Features in ESP32

Since we are familiar with I2S, let’s see how this can be used in a microcontroller. ESP32 is a widely popular low-cost, WiFi and Bluetooth enabled 32-Bit microcontroller unit, that supports I2S features.

Below, is a comprehensive list of I2S controller features of ESP32 I2S Driver.

The ESP32 I2S controller driver could operate as a system master or slave.
It is also capable of acting as a transmitter or receiver in the I2S Bus.
ESP32 has a dedicated DMA controller that could stream sample data without being dependent on the CPU to copy and check each data sample.
ESP32 I2S peripherals also support LCD. In this mode, the I2S data starts communicating over a parallel bus. It is required in some LCDs and camera modules.
In the LCD mode, the I2S controller could operate in the following modes.

LCD as master transmitting mode.
Camera as slave receiving mode.
ADC/DAC mode.
To start the I2S program and how to write codes for the I2C, let’s select an I2S module and make an application.

NOKIA Tone Generator using I2S

Let’s connect a speaker in the I2S protocol and generate the iconic NOKIA tune using the ESP32 I2S.

However, since the speaker is an analog device and to drive this, we need an I2S supported amplifier; we chose to use a MAX98357A, I2S based Mono Amplifier module.

The below image is showing the pinout of the module.

Three important pins of the module are BCLK, DIN, and LRC. The DIN is the data input pin which is the same as the SD and the LRC is the Left-Right Channel selection pin, the same as the WS.

The Circuit will go like this.

BCLK, Word Select, and the Serial Data pins are selected as the below pin.

BCLK pin of I2S = BCLK Pin of the Module = ESP32 Pin 27
Word Select pin of I2S = LRC Pin of the Module = ESP32 Pin 26
Serial Data pin of I2S = DIN Pin of the Module = ESP32 Pin 25
The schematic can be seen below.

ESP32 I2S Communication Schematic

I2S Sample Code for ESP32

Well, to run the code, the sampleaac.h header file holds the aac of the Nokia tune that is converted to the HEX format.

The header file has a format like this.

const unsigned char sampleaac[] PROGMEM = { 0xFF, 0xF9, 0x5C, 0x80, 0x2E, …………. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF2
};
All header files are included first
#include “AudioGeneratorAAC.h”
#include “AudioOutputI2S.h”
#include “AudioFileSourcePROGMEM.h”
#include “sampleaac.h”
We are using the audio generator AAC library that uses the I2S protocol to generate the audio. This is available in the below GitHub.

https://github.com/earlephilhower/ESP8266Audio

After that, the pins are set as per the pin diagram provided before.

#define Bit_Clock_BCLK 27
#define Word_Select_WS 26
#define Serial_Data_SD 25
Then in the setup, the I2S device pinout is set on the I2S driver.

in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
aac = new AudioGeneratorAAC();
out = new AudioOutputI2S();
out -> SetGain(GAIN);
out -> SetPinout(Bit_Clock_BCLK,Word_Select_WS,Serial_Data_SD);
aac->begin(in, out);
}
The while loop is playing the audio on the sample AAC.

void loop(){
if (aac->isRunning()) {
aac->loop();
} else {
aac -> stop();
Serial.printf(“Sound Generator\n”);
delay(1000);
}
}
Playing Nokia Tune on ESP32 using I2S Communication

Now that we know how I2S communication on ESP32 works, we can make the connections, upload the code and play the test audio. For this article, I have played the Nokia tune for example. As we can see, all the things are connected properly and are ready for testing.

ESP32 I2S Audio

The complete working of this project can be found in the video below. Hope this article helps you to use ESP32 I2S Audio for all your future projects. If you have any questions, you can leave them in the comment section below or post them on our forums.

Code

#include “AudioGeneratorAAC.h”
#include “AudioOutputI2S.h”
#include “AudioFileSourcePROGMEM.h”
#include “sampleaac.h”
#define Bit_Clock_BCLK 27
#define Word_Select_WS 26
#define Serial_Data_SD 25
#define GAIN 0.125
AudioFileSourcePROGMEM *in;
AudioGeneratorAAC *aac;
AudioOutputI2S *out;
void setup(){
Serial.begin(115200);
in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
aac = new AudioGeneratorAAC();
out = new AudioOutputI2S();
out -> SetGain(GAIN);
out -> SetPinout(Bit_Clock_BCLK,Word_Select_WS,Serial_Data_SD);
aac->begin(in, out);
}
void loop(){
if (aac->isRunning()) {
aac->loop();
} else {
aac -> stop();
Serial.printf(“Sound Generator\n”);
delay(1000);
}
}

esp8266 语音播放 //Priorities of the reader and the decoder thread. Higher = higher prio. #define PRIO_READER 11 #define PRIO_MAD 1 //The mp3 read buffer size. 2106 bytes should be enough for up to 48KHz mp3s according to the sox sources. Used by libmad. #define READBUFSZ (2106) static char readBuf[READBUFSZ]; static long bufUnderrunCt; //Reformat the 16-bit mono sample to a format we can send to I2S. static int sampToI2s(short s) { //We can send a 32-bit sample to the I2S subsystem and the DAC will neatly split it up in 2 //16-bit analog values, one for left and one for right. //Duplicate 16-bit sample to both the L and R channel int samp=s; samp=(samp)&0xffff; samp=(samp<65535) samp=65535; if (samp>11]; err=(samp&0x7ff); //Save rounding error. return samp; } //2nd order delta-sigma DAC //See http://www.beis.de/Elektronik/DeltaSigma/DeltaSigma.html for a nice explanation static int sampToI2sDeltaSigma(short s) { int x; int val=0; int w; static int i1v=0, i2v=0; static int outReg=0; for (x=0; x<32; x++) { val<0) w-=32767; else w+=32767; //Difference 1 w+=i1v; i1v=w; //Integrator 1 if (outReg>0) w-=32767; else w+=32767; //Difference 2 w+=i2v; i2v=w; //Integrator 2 outReg=w; //register if (w>0) val|=1; //comparator } return val; } //Calculate the number of samples that we add or delete. Added samples means a slightly lower //playback rate, deleted samples means we increase playout speed a bit. This returns an //8.24 fixed-point number int recalcAddDelSamp(int oldVal) { int ret; long prevUdr=0; static int cnt; int i; static int minFifoFill=0; i=spiRamFifoFill(); if (i<minFifoFill) minFifoFill=i; //Do the rest of the calculations plusminus every 100mS (assuming a sample rate of 44KHz) cnt++; if (cnt<1500) return oldVal; cnt=0; if (spiRamFifoLen()<10*1024) { //The FIFO is very small. We can't do calculations on how much it's filled on average, so another //algorithm is called for. int tgt=1600; //we want an average of this amount of bytes as the average minimum buffer fill //Calculate underruns this cycle int udr=spiRamGetUnderrunCt()-prevUdr; //If we have underruns, the minimum buffer fill has been lower than 0. if (udr!=0) minFifoFill=-1; //If we're below our target decrease playback speed, and vice-versa. ret=oldVal+((minFifoFill-tgt)*ADD_DEL_BUFFPERSAMP_NOSPIRAM); prevUdr+=udr; minFifoFill=9999; } else { //We have a larger FIFO; we can adjust according to the FIFO fill rate. int tgt=spiRamFifoLen()/2; ret=(spiRamFifoFill()-tgt)*ADD_DEL_BUFFPERSAMP; } return ret; } //This routine is called by the NXP modifications of libmad. It passes us (for the mono synth) //32 16-bit samples. void render_sample_block(short *short_sample_buff, int no_samples) { //Signed 16.16 fixed point number: the amount of samples we need to add or delete //in every 32-sample static int sampAddDel=0; //Remainder of sampAddDel cumulatives static int sampErr=0; int i; int samp; #ifdef ADD_DEL_SAMPLES sampAddDel=recalcAddDelSamp(sampAddDel); #endif sampErr+=sampAddDel; for (i=0; i(1<<24)) { sampErr-=(1<<24); //...and don't output an i2s sample } else if (sampErr<-(1<<24)) { sampErr+=(1<bufend-stream->next_frame; memmove(readBuf, stream->next_frame, rem); while (rem<sizeof(readBuf)) { n=(sizeof(readBuf)-rem); //Calculate amount of bytes we need to fill buffer. i=spiRamFifoFill(); if (i<n) n=i; //If the fifo can give us less, only take that amount if (n==0) { //Can't take anything? //Wait until there is enough data in the buffer. This only happens when the data feed //rate is too low, and shouldn't normally be needed! // printf("Buf uflow, need %d bytes.\n", sizeof(readBuf)-rem); bufUnderrunCt++; //We both silence the output as well as wait a while by pushing silent samples into the i2s system. //This waits for about 200mS for (n=0; nerror, mad_stream_errorstr(stream)); return MAD_FLOW_CONTINUE; } //This is the main mp3 decoding task. It will grab data from the input buffer FIFO in the SPI ram and //output it to the I2S port. void ICACHE_FLASH_ATTR tskmad(void *pvParameters) { int r; struct mad_stream *stream; struct mad_frame *frame; struct mad_synth *synth; //Allocate structs needed for mp3 decoding stream=malloc(sizeof(struct mad_stream)); frame=malloc(sizeof(struct mad_frame)); synth=malloc(sizeof(struct mad_synth)); if (stream==NULL) { printf("MAD: malloc(stream) failed\n"); return; } if (synth==NULL) { printf("MAD: malloc(synth) failed\n"); return; } if (frame==NULL) { printf("MAD: malloc(frame) failed\n"); return; } //Initialize I2S i2sInit(); bufUnderrunCt=0; printf("MAD: Decoder start.\n"); //Initialize mp3 parts mad_stream_init(stream); mad_frame_init(frame); mad_synth_init(synth); while(1) { input(stream); //calls mad_stream_buffer internally while(1) { r=mad_frame_decode(frame, stream); if (r==-1) { if (!MAD_RECOVERABLE(stream->error)) { //We're most likely out of buffer and need to call input() again break; } error(NULL, stream, frame); continue; } mad_synth_frame(synth, frame); } } } int getIpForHost(const char *host, struct sockaddr_in *ip) { struct hostent *he; struct in_addr **addr_list; he=gethostbyname(host); if (he==NULL) return 0; addr_list=(struct in_addr **)he->h_addr_list; if (addr_list[0]==NULL) return 0; ip->sin_family=AF_INET; memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr)); return 1; } //Open a connection to a webserver and request an URL. Yes, this possibly is one of the worst ways to do this, //but RAM is at a premium here, and this works for most of the cases. int ICACHE_FLASH_ATTR openConn(const char *streamHost, const char *streamPath) { int n, i; while(1) { struct sockaddr_in remote_ip; bzero(&remote_ip, sizeof(struct sockaddr_in)); if (!getIpForHost(streamHost, &remote_ip)) { vTaskDelay(1000/portTICK_RATE_MS); continue; } int sock=socket(PF_INET, SOCK_STREAM, 0); if (sock==-1) { continue; } remote_ip.sin_port = htons(streamPort); printf("Connecting to server %s...\n", ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr)); if (connect(sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr))!=00) { close(sock); printf("Conn err.\n"); vTaskDelay(1000/portTICK_RATE_MS); continue; } //Cobble together HTTP request write(sock, "GET ", 4); write(sock, streamPath, strlen(streamPath)); write(sock, " HTTP/1.0\r\nHost: ", 17); write(sock, streamHost, strlen(streamHost)); write(sock, "\r\n\r\n", 4); //We ignore the headers that the server sends back... it's pretty dirty in general to do that, //but it works here because the MP3 decoder skips it because it isn't valid MP3 data. return sock; } } //Reader task. This will try to read data from a TCP socket into the SPI fifo buffer. void ICACHE_FLASH_ATTR tskreader(void *pvParameters) { int madRunning=0; char wbuf[64]; int n, l, inBuf; int t; int fd; int c=0; while(1) { fd=openConn(streamHost, streamPath); printf("Reading into SPI RAM FIFO...\n"); do { n=read(fd, wbuf, sizeof(wbuf)); if (n>0) spiRamFifoWrite(wbuf, n); c+=n; if ((!madRunning) && (spiRamFifoFree()0); close(fd); printf("Connection closed.\n"); } } //Simple task to connect to an access point, initialize i2s and fire up the reader task. void ICACHE_FLASH_ATTR tskconnect(void *pvParameters) { //Wait a few secs for the stack to settle down vTaskDelay(3000/portTICK_RATE_MS); //Go to station mode wifi_station_disconnect(); if (wifi_get_opmode() != STATION_MODE) { wifi_set_opmode(STATION_MODE); } //Connect to the defined access point. struct station_config *config=malloc(sizeof(struct station_config)); memset(config, 0x00, sizeof(struct station_config)); sprintf(config->ssid, AP_NAME); sprintf(config->password, AP_PASS); wifi_station_set_config(config); wifi_station_connect(); free(config); //Fire up the reader task. The reader task will fire up the MP3 decoder as soon //as it has read enough MP3 data. if (xTaskCreate(tskreader, "tskreader", 230, NULL, PRIO_READER, NULL)!=pdPASS) printf("Error creating reader task!\n"); //We're done. Delete this task. vTaskDelete(NULL); } //We need this to tell the OS we're running at a higher clock frequency. extern void os_update_cpu_frequency(int mhz); void ICACHE_FLASH_ATTR user_init(void) { //Tell hardware to run at 160MHz instead of 80MHz //This actually is not needed in normal situations... the hardware is quick enough to do //MP3 decoding at 80MHz. It, however, seems to help with receiving data over long and/or unstable //links, so you may want to turn it on. Also, the delta-sigma code seems to need a bit more speed //than the other solutions to keep up with the output samples, so it's also enabled there. #if defined(DELTA_SIGMA_HACK) SET_PERI_REG_MASK(0x3ff00014, BIT(0)); os_update_cpu_frequency(160); #endif //Set the UART to 115200 baud UART_SetBaudrate(0, 115200); //Initialize the SPI RAM chip communications and see if it actually retains some bytes. If it //doesn't, warn user. if (!spiRamFifoInit()) { printf("\n\nSPI RAM chip fail!\n"); while(1); } printf("\n\nHardware initialized. Waiting for network.\n"); xTaskCreate(tskconnect, "tskconnect", 200, NULL, 3, NULL); }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值