APM32F411使用IIS外设驱动es8388实现自录自播
前言:
从零开始学习I2s外设,配置Es8288寄存器实现录音播放。本文章使用主控芯片是APM32F411系类。音频相关的概念比较多,就不再次做过多的介绍,本文章只是简单实现边录边播功能。APM系类兼容st的芯片,所以用st的hal库来实现。本文的介绍顺序从es8388这款芯片开始,再到I2s协议,再到最后代码讲解。
一、Es8388芯片介绍
1、Es8388介绍
Es8388有 2 个 ADC 通道和 2 个 DAC 通道,麦克风放大器,耳机放大器,数字音效以及模拟混合和增益功能组成。
主要特点:
I2S 接口,支持最高 192K,24bit 音频播放。
DAC 信噪比 96dB;ADC 信噪比 95dB。
支持主机和从机模式。
支持立体声差分输入/麦克风输入。
支持左右声道音量独立调节。
支持 40mW 耳机输出,无爆音。
2、通路
设计通入从es8388的RIN1 进行数据采集编码,解码数据从ROUT1 发送至喇叭。主时钟又外部12.288M的晶振电路提供。
3、采样率相关寄存器
本文设置的采样率是8K,作为正常通话,8K的采样已足够清晰。依据主时钟与采样的关系后续如做更改需要依照下表同步更新。
4、寄存器介绍
寄存器介绍采用正点原子提供的手册做参考,并对其他可能影响的进行另外介绍。
更新补充其他可能影响录音功能的寄存器:(以下寄存器只针对实现我上述所说的单右声道录音右声道播放)
4.1问题介绍
首先说一下我遇到的问题,我通过移植正点原子的历程实现边录边播功能,在探索者上完全没有问题,可以实现,但是在自己打的板子上却存在在录音上电时发出 “哒哒哒哒哒哒” 的声音,其中有一个难以理解的点时,因为寄存器高通滤波开启了导致的,依据笔者的理解,高通滤波时需要开的,但是他会带来“哒哒哒”的噪音不太理解,找了好久,一个一个尝试才解决的,但是至今不太理解,可能是硬件设计问题,硬件抄的成熟的方案。
默认开启,设置关闭就解决“哒哒哒”的声音
第二个问题就是在说话的时候也会伴随的杂音“哒哒哒”的杂音声音比较大后续通过一下寄存器减小了杂音,之前上电就出现,后者是持续存在,至今没完全找到原因,如果有知道的可以在评论区交流学习。
说一下以下寄存器:
左DAC清零静音,关闭无关寄存器,防止干扰,因为采用的是单声道,所以无关的寄存器需要全部关闭,禁止立体声也需要设置,这里不做赘述,相信各位英语应该都可以,直接拿出相关截图给各位。
麦克风相关寄存器,硬件原理图设置那边,其他的就需要断电,也符合他的特点,“低功耗”
总结:
Es8388 中的ALC调节,MIC增益调节,喇叭声音等一些列寄存器设置都会影响到声音播放时的杂音和啸叫,提醒各位调试时可以先去掉喇叭,通过示波器区测试输入输出声波,保护耳朵要经。
二、I2S介绍
1、I2S简介
做一下另外补充:
(1)WS:字选择,映射到spi的nss引脚,及帧时钟用于切换左右声道数据,对应设置的采样率,可以通过示波器查看是否配置正确。
(2)SD:串行数据,映射到mosi引脚,用于发送或接收两个时分复用的数据通道上的 数据。
主时钟的256=8*16*2
(4)I2S2ext_SD 和 I2S3ext_SD:用于控制 I2S 全双工模式的附加引脚(映射到 MISO 引脚)。
提示:大家可以将MCK引脚接入MCU,通过MCU提供主时钟,可能会有一点点小误差,如果想要省下材料,可以直接查询手册
APMF411的I2S2_MCK引脚为PA6.大家可以作为参考,不过追求准确的话可以直接将MCK引脚连接至晶振电路,后续通过寄存器去合理设计。本文采用的是外部12.288M晶振解决的。
I2S的时钟发生器如下介绍,摘抄自正点原子手册,作为学习记录
2、I2s 相关寄存器介绍:
2.1 RCC_PLLI2SCFGR
2.2 SPI_I2SCFGR
2.3 SPI_I2SPR
三、Es8388配置,I2S代码讲解
1、Es8388初始化
/*****************************************************************************函 数 名 : Es8388Init功能描述 : Es8388初始化输入参数 : void返 回 值 : void作 者 : GCC
*****************************************************************************/
void Es8388Init(void)
{AudioI2cInit(); /* 初始化IIC接口 */ es8388I2cWriteReg(0, 0x80); /* 软复位ES8388 */es8388I2cWriteReg(0, 0x00);DelayMs(100); /* 等待复位 */es8388I2cWriteReg(0x01, 0x58); /*默认0x5c,设置0x58复位模拟软件部分*/es8388I2cWriteReg(0x01, 0x50); /*PdnAna 为0,模拟部分可工作,*/// es8388I2cWriteReg(0x01, 0x53);//模拟部分不工作es8388I2cWriteReg(0x02, 0xF3); /*断电ADC,DAC*/es8388I2cWriteReg(0x02, 0xF0); /*0xf3 重新设置adc,dac,重新设置状态机复位,开启ADC DAC*/// es8388I2cWriteReg(0x03, 0x00);//ADCL power down,left analog input power downes8388I2cWriteReg(0x03, 0x09); /* 麦克风偏置电源关闭 */es8388I2cWriteReg(0x00, 0x06); /* 使能参考 500K驱动使能,默认500K */es8388I2cWriteReg(0x04, 0x00); /* DAC电源管理,不打开任何通道 */es8388I2cWriteReg(0x05,0x88); //LPDACL low powrer, LPLOUT2 low power
// es8388I2cWriteReg(0x04, 0x90);
// es8388I2cWriteReg(0x0A,0xF0);es8388I2cWriteReg(0x08, 0x05); /* MCLK 6分频产生2.048M时钟 */es8388I2cWriteReg(0x2B, 0x80); /* DAC控制 DACLRC与ADCLRC相同 */es8388I2cWriteReg(0x09, 0x00); // 0x66 ADC L/R PGA增益配置为+24dB//es8388I2cWriteReg(0x09, 0x88); /* ADC L/R PGA增益配置为+24dB */
// es8388I2cWriteReg(0x0C, 0x8C); //都来源源于右通道es8388I2cWriteReg(0x0C, 0x0C); //左右声道数据分开es8388I2cWriteReg(0x0D, 0x0A); /* ADC配置 MCLK/采样率=1536,8K采样率 外部晶振12.288M*/
// es8388I2cWriteReg(0x0D, 0x02); /* ADC配置 MCLK/采样率=256 */es8388I2cWriteReg(0x10, 0x00); /* ADC数字音量控制将信号衰减 L 设置为最小!!! */es8388I2cWriteReg(0x11, 0x00); /* ADC数字音量控制将信号衰减 R 设置为最小!!! */
// es8388I2cWriteReg(0x13,0xD5); //lxlvl -1.5dB 1.36ses8388I2cWriteReg(0x16,0xF3);//Noise gate threshold -30dB, PGA gain held constant Noise gate function enable es8388I2cWriteReg(0x17, 0x18); /* DAC 音频数据为16bit */es8388I2cWriteReg(0x18, 0x0A); /* DAC 配置 MCLK/采样率=256 */ es8388I2cWriteReg(0x1A, 0x00); /* DAC数字音量控制将信号衰减 L 设置为最小!!! */es8388I2cWriteReg(0x1B, 0x00); /* DAC数字音量控制将信号衰减 R 设置为最小!!! */
// es8388I2cWriteReg(0x1C,0xC0);//除重音模式es8388I2cWriteReg(0x27, 0xB8); /* L混频器 */es8388I2cWriteReg(0x2A, 0xB8); /* R混频器,-15 */es8388I2cWriteReg(0x0A,0);//选择输入通道es8388I2cWriteReg(0x0B,0x10);//es8388I2cWriteReg(0x0E,0x00); /*关闭高通滤波防止哒哒哒声(如果存在可选择)*/es8388I2cWriteReg(0x03, 0xA9); /* 麦克风偏置电源关闭 */es8388I2cWriteReg(0x06,0x02);es8388I2cWriteReg(0x04,0x90);DelayMs(100);
}
/*****************************************************************************函 数 名 : Es8388I2sCfg功能描述 : 设置ES8388工作模式输入参数 : fmt : 工作模式* @arg 0, 飞利浦标准I2S;* @arg 1, MSB(左对齐);* @arg 2, LSB(右对齐);* @arg 3, PCM/DSPlen : 数据长度 * @arg 0, 24bit* @arg 1, 20bit * @arg 2, 18bit * @arg 3, 16bit * @arg 4, 32bit 返 回 值 : void作 者 : GCC
*****************************************************************************/
void Es8388I2sCfg(uint8_t fmt, uint8_t len)
{fmt &= 0x03;len &= 0x07; /* 限定范围 */es8388I2cWriteReg(23, (fmt << 1) | (len << 3)); /* R23,ES8388工作模式设置 */
}
/*****************************************************************************函 数 名 : Es8388Rout1Set功能描述 : 右声道声音输出大小设置输入参数 : voluem : 音量大小(0 ~ 33)返 回 值 : void作 者 : GCC
*****************************************************************************/
void Es8388Rout1Set(uint8_t volume)
{if (volume > 33){volume = 33;}es8388I2cWriteReg(0x2F, volume);//ROUT1VOl右声道1
}
/*****************************************************************************函 数 名 : Es8388Rout2Set功能描述 : MIC_P 声音设置输入参数 : voluem : 音量大小(0 ~ 33)返 回 值 : void作 者 : GCC
*****************************************************************************/
void Es8388Rout2Set(uint8_t volume)
{if (volume > 33){volume = 33;}es8388I2cWriteReg(0x31, volume);
}
/*** @brief 设置3D环绕声* @param 修改设置单声道* @retval 无*/
/*****************************************************************************函 数 名 : Es8388Channel_3d_set功能描述 : 通道设置,3d立体音设置输入参数 : Channel 0 表示立体音 1表示单左声道,2表示设置右声道depth : 0 ~ 7(3D强度,0关闭,7最强)返 回 值 : void作 者 : GCC
*****************************************************************************/
void Es8388Channel_3d_set(uint8_t depth,u8 Channel)
{ switch(Channel){case 0:depth &= 0x7; /* 限定范围 */es8388I2cWriteReg(0x1D, depth << 2); /* R7,3D环绕设置 */break;case 1:depth=(3<<5);es8388I2cWriteReg(0x1D,depth);break;case 2:depth=0xA0;//单声道关闭立体音es8388I2cWriteReg(0x1D,0xA0);break;}
}/*** @brief ES8388 DAC/ADC配置* @param dacen : dac使能(1)/关闭(0)* @param adcen : adc使能(1)/关闭(0)* @retval 无*/
void Es8388AddaCfg(uint8_t dacen, uint8_t adcen)
{uint8_t tempreg = 0;tempreg |= ((!dacen) << 0);tempreg |= ((!adcen) << 1);tempreg |= ((!dacen) << 2);tempreg |= ((!adcen) << 3);es8388I2cWriteReg(0x02, tempreg);
}
/*** @brief ES8388 DAC输出通道配置* @param o1en : 通道1使能(1)/禁止(0)* @param o2en : 通道2使能(1)/禁止(0)* @retval 无*/
void Es8388OutputCfg(uint8_t o1en, uint8_t o2en)
{uint8_t tempreg = 0;tempreg |= o1en * (3 << 4);tempreg |= o2en * (3 << 2);es8388I2cWriteReg(0x04, tempreg);
}
/*** @brief ES8388 MIC左右通道增益设置(MIC PGA增益)* @param gain : 0~8, 对应0~24dB 3dB/Step* @retval 无*/
void Es8388MicGain(uint8_t gain)
{gain &= 0x0F; //设置低4位gain |= gain << 4;//设置高位4位es8388I2cWriteReg(0x09, gain); /* R9,左右通道PGA增益设置 */
}
/*** @brief ES8388 ALC设置* @param sel* @arg 0,关闭ALC* @arg 1,右通道ALC* @arg 2,左通道ALC* @arg 3,立体声ALC* @param maxgain : 0~7,对应-6.5~+35.5dB* @param mingain : 0~7,对应-12~+30dB 6dB/STEP* @retval 无*/
void Es8388AlcCtrl(uint8_t sel, uint8_t maxgain, uint8_t mingain)
{uint8_t tempreg = 0;tempreg = sel << 6;tempreg |= (maxgain & 0x07) << 3;//set maximum gain of PGA 17.5dbtempreg |= mingain & 0x07; //12dbes8388I2cWriteReg(0x12, tempreg); /* R18,ALC设置 */
}
/*** @brief ES8388 ADC输出通道配置* @param in : 输入通道* @arg 0, 通道1输入* @arg 1, 通道2输入* @retval 无*/
void Es8388InputCfg(uint8_t in)
{es8388I2cWriteReg(0x0A, (5 * in) << 4); /* ADC1 输入通道选择L/R INPUT1 */
}
2、I2S 代码
/***************************************************************************************/
I2S_HandleTypeDef gAudioI2s; /* I2S句柄 */
I2S_HandleTypeDef gAudioI2sSext; /* I2S句柄 */
DMA_HandleTypeDef gI2sTxDma; /* I2S发送DMA句柄 */
DMA_HandleTypeDef gI2sRxDma; /* I2S接收DMA句柄 */
/************************************************************************************/
/*** @brief I2S初始化* @param i2s_standard : I2S标准* @note 可以设置 : I2S_STANDARD_PHILIPS/I2S_STANDARD_MSB/* I2S_STANDARD_LSB/I2S_STANDARD_PCM_SHORT/I2S_STANDARD_PCM_LONG* @param i2s_mode : I2S工作模式* @note 可以设置 : I2S_MODE_SLAVE_TX/I2S_MODE_SLAVE_RX/I2S_MODE_MASTER_TX/I2S_MODE_MASTER_RX* @param i2s_clock_polarity : 空闲状态时钟电平* @param i2s_dataformat : 数据长度* @note 可以设置 : I2S_DATAFORMAT_16B/I2S_DATAFORMAT_16B_EXTENDED/I2S_DATAFORMAT_24B/I2S_DATAFORMAT_32B* @retval 无*/
void AudioI2sInit(void)
{gAudioI2s.Instance = I2S_SPI;gAudioI2s.Init.Mode = I2S_MODE_MASTER_TX; /* IIS模式 */gAudioI2s.Init.Standard = I2S_STANDARD_PHILIPS; /* IIS标准 */gAudioI2s.Init.DataFormat = I2S_DATAFORMAT_16B; /* IIS数据长度 */gAudioI2s.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE; /* 主时钟输出使能 */gAudioI2s.Init.AudioFreq = I2S_AUDIOFREQ_8K; /* IIS频率设置 */gAudioI2s.Init.CPOL = I2S_CPOL_LOW; /* 空闲状态时钟电平 */gAudioI2s.Init.ClockSource = I2S_CLOCK_PLL; /* IIS时钟源为PLL */HAL_I2S_Init(&gAudioI2s);I2S_SPI->CR2 |= 1<<1; /* SPI2/I2S2 TX DMA请求使能. */__HAL_I2S_ENABLE(&gAudioI2s); /* 使能I2S2 */
}
/*** @brief I2SEXT初始化* @param i2s_standard : I2S标准* @note 可以设置 : I2S_STANDARD_PHILIPS/I2S_STANDARD_MSB/* I2S_STANDARD_LSB/I2S_STANDARD_PCM_SHORT/I2S_STANDARD_PCM_LONG* @param i2s_mode : I2S工作模式* @note 可以设置 : I2S_MODE_SLAVE_TX/I2S_MODE_SLAVE_RX/I2S_MODE_MASTER_TX/I2S_MODE_MASTER_RX* @param i2s_clock_polarity : 显示数字的位数* @param i2s_dataformat : 数据长度* @note 可以设置 : I2S_DATAFORMAT_16B/I2S_DATAFORMAT_16B_EXTENDED/I2S_DATAFORMAT_24B/I2S_DATAFORMAT_32B* @retval 无*/
void AudioI2sSextInit(void)
{gAudioI2sSext.Instance = I2SEXT_SPI;gAudioI2sSext.Init.Mode = I2S_MODE_SLAVE_RX; /* IIS模式 */gAudioI2sSext.Init.Standard = I2S_STANDARD_PHILIPS; /* IIS标准 */gAudioI2sSext.Init.DataFormat = I2S_DATAFORMAT_16B; /* IIS数据长度 */gAudioI2sSext.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE; /* 主时钟输出使能 */gAudioI2sSext.Init.AudioFreq = I2S_AUDIOFREQ_8K; /* IIS频率设置 */gAudioI2sSext.Init.CPOL = I2S_CPOL_LOW; /* 空闲状态时钟电平 */gAudioI2sSext.Init.ClockSource = I2S_CLOCK_PLL; /* IIS时钟源为PLL */HAL_I2S_Init(&gAudioI2sSext); I2SEXT_SPI->CR2 |= 1<<0; /* SPI2/I2S2 TX DMA请求使能. */__HAL_I2S_ENABLE(&gAudioI2sSext); /* 使能I2S2 */
}/*** @brief I2S底层驱动,时钟使能,引脚配置,DMA配置* @note 此函数会被HAL_I2S_Init()调用* @param hi2s:I2S句柄* @retval 无*/
void HAL_I2S_MspInit(I2S_HandleTypeDef *hi2s)
{GPIO_InitTypeDef gpio_init_struct;I2S_SPI_CLK_ENABLE(); /* 使能SPI2/I2S2时钟 */I2S_WS_GPIO_CLK_ENABLE(); /* 使能I2S_WS时钟,左右声道 */I2S_SCLK_GPIO_CLK_ENABLE(); /* 使能I2S_SCLK时钟 */I2S_SDOUT_GPIO_CLK_ENABLE(); /* 使能I2S_SDOUT时钟 */I2S_SDIN_GPIO_CLK_ENABLE(); /* 使能I2S_SDIN时钟 */
// I2S_MCLK_GPIO_CLK_ENABLE(); /* 使能I2S_MCLK时钟,当MCLK有内部提供 */gpio_init_struct.Pin = I2S_WS_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */gpio_init_struct.Speed = GPIO_SPEED_HIGH; /* 高速 */gpio_init_struct.Alternate = GPIO_AF_I2S_SPI; /* 复用为SPI/I2S */HAL_GPIO_Init(I2S_WS_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_LRCK引脚 */gpio_init_struct.Pin = I2S_SCLK_GPIO_PIN;HAL_GPIO_Init(I2S_SCLK_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_SCLK引脚 */// gpio_init_struct.Pin = I2S_MCLK_GPIO_PIN;
// HAL_GPIO_Init(I2S_MCLK_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_MCLK引脚 */gpio_init_struct.Pin = I2S_SDIN_GPIO_PIN;HAL_GPIO_Init(I2S_SDIN_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_SDIN引脚 */gpio_init_struct.Pin = I2S_SDOUT_GPIO_PIN;HAL_GPIO_Init(I2S_SDOUT_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_SDOUT引脚 */gpio_init_struct.Pin = I2S_SDOUT_GPIO_PIN;gpio_init_struct.Alternate = GPIO_AF_I2S_EXT_SPI; /* 复用为I2Sext */HAL_GPIO_Init(I2S_SDOUT_GPIO_PORT, &gpio_init_struct); /* 初始化I2S_SDOUT引脚 */RCC->CR |= 1 << 26; /* 开启I2S时钟 */while((RCC->CR & 1 << 27) == 0); /* 等待I2S时钟开启成功. */
}/*** @brief I2S TX DMA配置* @note 设置为双缓冲模式,并开启DMA传输完成中断* @param buf0 : M0AR地址.* @param buf1 : M1AR地址.* @param num : 每次传输数据量* @retval 无*/
void I2sTxDmaInit(uint8_t* buf0, uint16_t num)
{ I2S_TX_DMA_CLK_ENABLE(); /* 使能I2S TX DMA时钟 */__HAL_LINKDMA(&gAudioI2s, hdmatx, gI2sTxDma); /* 将DMA与I2S联系起来 */gI2sTxDma.Instance = I2S_TX_DMASx; /* 设置I2S TX DMA数据流 */gI2sTxDma.Init.Channel = I2S_TX_DMASx_Channel; /* 设置I2S TX DMA通道 */gI2sTxDma.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 存储器到外设模式 */gI2sTxDma.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */gI2sTxDma.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */gI2sTxDma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据长度:16位 */gI2sTxDma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */gI2sTxDma.Init.Mode = DMA_CIRCULAR; /* 使用循环模式 */gI2sTxDma.Init.Priority = DMA_PRIORITY_MEDIUM; /* 高优先级 */gI2sTxDma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 不使用FIFO */gI2sTxDma.Init.MemBurst = DMA_MBURST_SINGLE; /* 存储器单次突发传输 */gI2sTxDma.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 外设突发单次传输 */HAL_DMA_DeInit(&gI2sTxDma); /* 先清除以前的设置 */HAL_DMA_Init(&gI2sTxDma); /* 初始化DMA */HAL_DMA_Start(&gI2sTxDma,(uint32_t)buf0,(uint32_t)&I2S_SPI->DR,num);//单缓存 /* 10us延时,防止-O2优化出问题 */DelayUs(10); __HAL_DMA_ENABLE_IT(&gI2sTxDma, DMA_IT_TC); /* 开启传输完成中断 */__HAL_DMA_CLEAR_FLAG(&gI2sTxDma, I2S_TX_DMASx_FLAG); /* 清除DMA传输完成中断标志位 */HAL_NVIC_SetPriority(I2S_TX_DMASx_IRQn,I2sTxPrePriority,I2sTxSubPriority); /* DMA中断优先级 */HAL_NVIC_EnableIRQ(I2S_TX_DMASx_IRQn);__HAL_DMA_DISABLE(&gI2sTxDma); /* 先关闭DMA */
}
/*** @brief I2Sext RX DMA配置* @note 设置为双缓冲模式,并开启DMA传输完成中断* @param buf0 : M0AR地址.* @param buf1 : M1AR地址.* @param num : 每次传输数据量* @retval 无*/
void I2sSextRxDmaInit(uint8_t* buf0, uint32_t num)
{ I2SEXT_RX_DMA_CLK_ENABLE(); /* 使能I2S RX DMA时钟 */__HAL_LINKDMA(&gAudioI2sSext, hdmarx, gI2sRxDma); /* 将DMA与I2S联系起来 */gI2sRxDma.Instance = I2SEXT_RX_DMASx; /* 设置I2S RX DMA数据流 */gI2sRxDma.Init.Channel = I2SEXT_RX_DMASx_Channel; /* 设置I2S RX DMA通道 */gI2sRxDma.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 外设到存储器模式 */gI2sRxDma.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */gI2sRxDma.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */gI2sRxDma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据长度:16位 */gI2sRxDma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */gI2sRxDma.Init.Mode = DMA_CIRCULAR; /* 使用循环模式 */gI2sRxDma.Init.Priority = DMA_PRIORITY_HIGH; /* 中等优先级 */gI2sRxDma.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 不使用FIFO */gI2sRxDma.Init.MemBurst = DMA_MBURST_SINGLE; /* 存储器单次突发传输 */gI2sRxDma.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 外设突发单次传输 */HAL_DMA_DeInit(&gI2sRxDma); /* 先清除以前的设置 */HAL_DMA_Init(&gI2sRxDma); /* 初始化DMA */HAL_DMA_Start(&gI2sRxDma,(uint32_t)&I2S3ext->DR,(uint32_t)buf0,num);/* 10us延时,防止-O2优化出问题 */DelayUs(10); __HAL_DMA_ENABLE_IT(&gI2sRxDma, DMA_IT_TC); /* 开启传输完成中断 */__HAL_DMA_CLEAR_FLAG(&gI2sRxDma, I2SEXT_RX_DMASx_FLAG); /* 清除DMA传输完成中断标志位 */HAL_NVIC_SetPriority(I2SEXT_RX_DMASx_IRQn,I2sRxPrePriority,I2sRxSubPriority); /* DMA中断优先级 */HAL_NVIC_EnableIRQ(I2SEXT_RX_DMASx_IRQn);__HAL_DMA_DISABLE(&gI2sRxDma); /* 先关闭DMA */
}void (*i2s_tx_callback)(void); /* I2S DMA TX 回调函数指针 */
void (*i2s_rx_callback)(void); /* I2S DMA RX 回调函数指针 */
/*** @brief I2S TX DMA 中断服务函数* @param 无* @retval 无*/
void I2S_TX_DMASx_Handle(void)
{ if (__HAL_DMA_GET_FLAG(&gI2sTxDma, I2S_TX_DMASx_FLAG) != RESET) /* DMA传输完成 */{__HAL_DMA_CLEAR_FLAG(&gI2sTxDma, I2S_TX_DMASx_FLAG); /* 清除DMA传输完成中断标志位 */if (i2s_tx_callback != NULL){i2s_tx_callback(); /* 执行回调函数,读取数据等操作在这里面处理 */}}
}/*** @brief I2S RX DMA 中断服务函数* @param 无* @retval 无*/
void I2SEXT_RX_DMASx_Handle(void)
{ if(__HAL_DMA_GET_FLAG(&gI2sRxDma,I2SEXT_RX_DMASx_FLAG) != RESET) /* DMA传输完成 */{__HAL_DMA_CLEAR_FLAG(&gI2sRxDma, I2SEXT_RX_DMASx_FLAG); /* 清除DMA传输完成中断标志位 */if (i2s_rx_callback != NULL){i2s_rx_callback(); /* 执行回调函数,读取数据等操作在这里面处理 */}}
}
/*** @brief I2S开始播放* @param 无* @retval 无*/
void AudioI2sStart(void)
{__HAL_DMA_ENABLE(&gI2sTxDma); /* 开启DMA TX传输 */
}
/*** @brief I2S停止播放* @param 无* @retval 无*/
void AudioI2sStop(void)
{__HAL_DMA_DISABLE(&gI2sTxDma); /* 关闭DMA TX传输 */
}
/*** @brief I2S开始录音* @param 无* @retval 无*/
void AudioI2sSextStart(void)
{__HAL_DMA_ENABLE(&gI2sRxDma); /* 开启DMA RX传输 */
}
/*** @brief I2S停止录音* @param 无* @retval 无*/
void AudioI2sSextStop(void)
{__HAL_DMA_DISABLE(&gI2sRxDma); /* 关闭DMA RX传输 */
}
3、I2S 头文件
#ifndef __AUDIOIIS__H
#define __AUDIOIIS__H
#include "sys/sys.h"
/**********************************宏定义**********************************************/
#define GPIO_AF_I2S_SPI GPIO_AF6_SPI3
#define GPIO_AF_I2S_EXT_SPI GPIO_AF7_I2S3ext#define I2S_WS_GPIO_PORT GPIOA
#define I2S_WS_GPIO_PIN GPIO_PIN_15
#define I2S_WS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */#define I2S_SCLK_GPIO_PORT GPIOB
#define I2S_SCLK_GPIO_PIN GPIO_PIN_3
#define I2S_SCLK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define I2S_SDOUT_GPIO_PORT GPIOB
#define I2S_SDOUT_GPIO_PIN GPIO_PIN_4
#define I2S_SDOUT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define I2S_SDIN_GPIO_PORT GPIOB
#define I2S_SDIN_GPIO_PIN GPIO_PIN_5
#define I2S_SDIN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define I2S_SPI SPI3
#define I2SEXT_SPI I2S3ext
#define I2S_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI3_CLK_ENABLE(); }while(0) /* I2S3时钟使能*/#define I2S_TX_DMASx DMA1_Stream5
#define I2S_TX_DMASx_Channel DMA_CHANNEL_0
#define I2S_TX_DMASx_Handle DMA1_Stream5_IRQHandler
#define I2S_TX_DMASx_IRQn DMA1_Stream5_IRQn
#define I2S_TX_DMASx_FLAG DMA_FLAG_TCIF1_5
#define I2S_TX_DMA_CLK_ENABLE() do{ __HAL_RCC_DMA1_CLK_ENABLE(); }while(0) /* I2S3 TX DMA时钟使能 */#define I2SEXT_RX_DMASx DMA1_Stream0
#define I2SEXT_RX_DMASx_Channel DMA_CHANNEL_3
#define I2SEXT_RX_DMASx_Handle DMA1_Stream0_IRQHandler
#define I2SEXT_RX_DMASx_IRQn DMA1_Stream0_IRQn
#define I2SEXT_RX_DMASx_FLAG DMA_FLAG_TCIF0_4
#define I2SEXT_RX_DMA_CLK_ENABLE() do{ __HAL_RCC_DMA1_CLK_ENABLE(); }while(0) /* I2S3 RX DMA时钟使能 */#define REC_SAMPLERATE 8000 /* 采样率,8Khz *//*****************************************全局函数***********************************/extern void AudioI2sInit(void);
extern void AudioI2sSextInit(void);
extern void I2sTxDmaInit(uint8_t* buf0, uint16_t num);
// void I2sSextRxDmaInit(uint8_t* buf0, uint8_t *buf1, uint16_t num);
extern void (*i2s_tx_callback)(void);
extern void (*i2s_rx_callback)(void);
extern void AudioI2sStart(void);
extern void AudioI2sStop(void);
extern void AudioI2sSextStart(void);
extern void AudioI2sSextStop(void);
extern void I2sSextRxDmaInit(uint8_t* buf0, uint32_t num);
//extern void I2sTxDmaInit(uint8_t* buf0,uint8_t *buf1, uint16_t num);
//extern void I2sSextRxDmaInit(uint8_t* buf0, uint8_t *buf1, uint16_t num);
extern void i2s_init(uint32_t i2s_standard, uint32_t i2s_mode, uint32_t i2s_clock_polarity, uint32_t i2s_dataformat);
extern void i2sext_init(uint32_t i2sext_standard, uint32_t i2sext_mode, uint32_t i2sext_clock_polarity, uint32_t i2sext_dataformat);
#endif
4、main函数
#include "config.h"
/****************************全局变量*************************************************/
uint8_t DmrUartTxbufData[REC_I2S_RX_DMA_BUF_SIZE]; /*DMR发送到MCU的PCM数据发送至I2S发声*/
uint8_t DmrUartRxbufData[REC_I2S_RX_DMA_BUF_SIZE];
/*****************************************************************************函 数 名 : 功能描述 : 录音功能初始化输入参数 : void注 意 :设置发发送数据初始化返 回 值 : void作 者 : GCC
*****************************************************************************/
void I2sTxCallback(void)
{
// printf("I2sTxCallback\r\n");
}
void rec_i2s_dma_rx_callback(void)
{}
void recoder_enter_rec_mode(void)
{Es8388AddaCfg(1, 1); /* 开启ADC,DAC */Es8388InputCfg(0); /* 开启输入通道(通道1,MIC所在通道) */Es8388MicGain(4); /* MIC增益设置为最大 */Es8388AlcCtrl(1, 5,6); /* 开启立体声ALC控制,以提高录音音量 5:23.5dB最大,最小+24dB*/
// Es8388OutputCfg(1,0); /* 关闭通道1和2的输出 */Es8388Rout1Set(15); /* 关闭喇叭. */Es8388I2sCfg(0, 3); /* 飞利浦标准,16位数据长度 */Es8388Channel_3d_set(0,2); //输出右声道DelayMs(100);//主传发送AudioI2sInit();AudioI2sSextInit();I2sSextRxDmaInit(DmrUartRxbufData, REC_I2S_RX_DMA_BUF_SIZE);i2s_rx_callback = rec_i2s_dma_rx_callback;I2sTxDmaInit(DmrUartRxbufData,REC_I2S_RX_DMA_BUF_SIZE);//i2s_tx_callback = I2sTxCallback;AudioI2sStart(); /* 开始I2S数据发送(主机) */AudioI2sSextStart(); /* 开始I2S数据接收(从机) */}
void RecorderStart(void)
{recoder_enter_rec_mode(); /*进入录音模式 */while(1);
}
总结:
总体上比较算是比较容易,就是看手册的时候得耐着性子看,还有比较多的概念啥的,慢慢理解吧,看的久了自然,不懂得慢慢百度,看的多了自然就知道了。
相关文章:
APM32F411使用IIS外设驱动es8388实现自录自播
前言: 从零开始学习I2s外设,配置Es8288寄存器实现录音播放。本文章使用主控芯片是APM32F411系类。音频相关的概念比较多,就不再次做过多的介绍,本文章只是简单实现边录边播功能。APM系类兼容st的芯片,所以用st的hal库来…...
flink SQL实现mysql source sink
接上文:一文说清flink从编码到部署上线 环境说明:MySQL:5.7;flink:1.14.0;hadoop:3.0.0;操作系统:CentOS 7.6;JDK:1.8.0_401。 1.代码实现 1.1 E…...
【C#】实现Json转Lua (Json2Lua)
关键词: C#、JsonToLua、Json2Lua、对象序列化Lua 前提需引入NewtonsofJson,引入方法可先在Visual Studio 2019 将Newtonsoft.Json.dll文件导入Unity的Plugins下。 Json格式字符串转Lua格式字符串,效果如下: json字符串 {"1": &q…...
使用 Vue3 实现摄像头拍照功能
参考资料:MediaDevices.getUserMedia() - Web API | MDN 重要: navigator.mediaDevices.getUserMedia 需要在安全的上下文中运行。现代浏览器要求摄像头和麦克风的访问必须通过 HTTPS 或 localhost(被视为安全的本地环境)进行,如果上传服务器地址是http…...
ARM学习(38)多进程多线程之间的通信方式
ARM学习(38)ARM学习(38)多进程多线程之间的通信方式 一、问题背景 笔者在调试模拟器的时候,碰到进程间通信的问题,一个进程在等另外一个进程ready的时候,迟迟等不到,然后通过调试发现,另外一个进程变量已经变化了,但是当前进程变量没变化,需要了解进程间通信的方式…...
CEF127 编译指南 MacOS 篇 - 拉取 CEF 源码(五)
1. 引言 在完成了所有必要工具的安装和配置后,我们进入到获取 CEF 源码的阶段。对于 macOS 平台,CEF 的源码获取过程需要特别注意不同芯片架构(Intel 和 Apple Silicon)的区别以及版本管理。本文将详细介绍如何在 macOS 系统上获…...
网络安全笔记
#### 网络各层安全协议 链路层:链路隧道协议、加密技术 网络层:包过滤机制、NAT、IPsec协议、 VPN 传输层/会话层 :SSL/TLS 协议 应用层:SHTTP、HTTPS、PGP、S/MIME等 ### 网络安全技术 第二代安全技术 - 保护 - 响应 - 检测…...
LNMP+discuz论坛
0.准备 文章目录 0.准备1.nginx2.mysql2.1 mysql82.2 mysql5.7 3.php4.测试php访问mysql5.部署 Discuz6.其他 yum源: # 没有wget,用这个 # curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo[rootlocalhost ~]#…...
python 曲线拟合,曲线拟合交点
目录 效果图: 源代码: 效果图: 源代码: import json import os import shutilimport cv2 import numpy as npfrom numpy.polynomial.polynomial import Polynomialdef calculate_distance(x1, y1, x2, y2):return np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)def get_new_g…...
【2025最新计算机毕业设计】基于SSM框架的宠物领养系统【提供源码+答辩PPT+文档+项目部署】
作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...
C语言经典100例
文章目录 前言123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525355565859606162636465 前言 以下题目大部分来自于C语言经典100例 1 题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的…...
利用 Jsoup 进行高效 Web 抓取与 HTML 处理
Jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 JQuery 的操作方法来取出和操作数据。 官网:https://jsoup.org/ 中文文档:Jsou…...
线上问题——频繁 Full GC 问题的排查思路
文章目录 一、查看 GC 日志二、分析内存泄漏三、检查对象生命周期四、优化代码五、调整垃圾回收策略六、使用监控工具 一、查看 GC 日志 启用 GC 日志 在 Java 应用中,需要在启动参数中添加适当的参数来启用 GC 日志记录。可以使用-XX:PrintGCDetails、-XX:PrintGCD…...
ParrotOS,一个与kali类似的渗透测试操作系统
介绍 Parrot Security(ParrotOS,Parrot)是一个基于 Debian Stable 的免费开源 GNU/Linux 发行版,专为安全专家、开发人员和注重隐私的人设计。 它包括一个完整的便携式武器库,用于 IT 安全和数字取证操作。它还包括开…...
网络视频监控平台/安防监控/视频综合管理Liveweb视频汇聚平台解决方案
一、当前现状分析 当前视频资源面临以下问题: 1)不同单位在视频平台建设中以所属领域为单位,设备品牌众多,存在的标准不一,各系统之间也没有统一标准; 2)各单位视频平台建设分散、统筹性差&am…...
《Java核心技术I》Swing选择组件中的复选框
选择组件 除了输入,也需要选择组件,接下来介绍,复选框、单选按钮、选项列表以及滑块。 复选框 需要紧邻标签来说明其用途。 bold new JCheckBox("Bold"); 调用setSelected方法来选中或取消复选框 bold.setSelected(true); isSelec…...
ES6学习Generator 函数(生成器)(八)
这里写目录标题 一、基本概念二、代码三、Generator 函数的异步应用三级目录 一、基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 函数有多种理解角度。语法上,首先可以把它理解成&#x…...
练习题 最小栈
最小栈 最小栈 class MinStack {private Stack<Integer> stack;private Stack<Integer> minstack;public MinStack() {stacknew Stack<>();minstacknew Stack<>();}public void push(int val) {stack.push(val);if(minstack.empty()){minstack.push(…...
windows环境下pytorch安装踩坑
目录 1 前言2 安装Anaconda3 安装CUDA4 创建Python3.9环境5 安装Pytorch环境5.1 conda方式5.2 pip方式 6 验证是否安装成功7 注意事项7.1 no module named torch问题7.12 torch.cuda.is_available()返回False问题 8 最佳实践9 总结 1 前言 这两天由于要使用Genesis,…...
从图纸泄密到全面安全防护 —— 域智盾软件在设计公司的应用
从图纸泄密到全面安全防护 —— 域智盾软件在设计公司的应用 作为一家设计公司的老板,我深知设计图纸对公司来说有多么重要。每一份设计图纸不仅凝聚着我们团队的智慧和辛勤劳动,也代表着公司的技术创新和核心竞争力。 然而,前段时间的一次…...
【ELK】Filebeat采集Docker容器日志
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 介绍filebeat是如何工作的 使用部署filebeat 介绍 Filebeat 是一个用于转发和集中日志数据的轻量级传送器。 Filebeat 作为agent安装在服务器上,监视指…...
基于java web在线商城购物系统源码+论文
一、环境信息 开发语言:JAVA JDK版本:JDK8及以上 数据库:MySql5.6及以上 Maven版本:任意版本 操作系统:Windows、macOS 开发工具:Idea、Eclipse、MyEclipse 开发框架:SpringbootHTMLjQueryMysq…...
MONI后台管理系统-swagger3(springdoc-openapi)集成
springdoc-openapi Java 库有助于使用 Spring Boot 项目自动生成 API 文档。springdoc-openapi 通过在运行时检查应用程序来根据 Spring 配置、类结构和各种注释推断 API 语义。 该库会自动生成 JSON/YAML 和 HTML 格式的页面文档。生成的文档可以使用swagger-api注释进行补充。…...
常见八股文04
63.索引的优缺点 优点 1.提高了查询性能 2.支持唯一性约束,避免插入重复数据 3.支持唯一性约束:在多表连接时,索引能够减少连接所需的时间和资源 缺点 1.占用额外存储空间:特别是在大型数据表中,索引可能会占用大量的空间 …...
php各个版本的特性以及绕过方式
一.php各个版本的特性 二.绕过正则匹配的常见方式 1.绕过空格 a.空变量$ l$s b.环境变量IFS(默认情况下IFS为空格、制表符和换行符) l${IFS}s c.重定向符(<,>) cat < file.txt //把file.txt的内容给cat命令&…...
允许某段网络访问Linux服务器上的MariaDB
在Linux服务器上安装了MariaDB,默认情况下,只允许本机访问。在某些特殊的情况下,要允许外部访问。具体操作流程如下: 1 修改服务器配置 vi /etc/my.cnf.d/server.cnf取消下面的注释,以便允许外来的主机访问。 bind-…...
【C语言】信号
【C语言】信号 信号1. 信号状态2. 信号处理方式3. 信号注册相关函数4. 信号集相关函数 信号 1. 信号状态 信号有三种状态:产生、未决和递达 信号产生方式: 按键产生,ctrlc 产生 中断信号SIGINT,ctrl \ 产生退出信号 SIGQUIT并…...
2023年下半年软考信息安全工程师案例分析及答案解析
试题一(16分) 回答问题1至问题6,将解答填入答题纸对应的解答栏内。 问题1(4分) 已知DES算法S盒如下,请补全S盒空缺的数据(1)、(2)、(3)、(4)。 【参考答案】3、13、15、0 问题2(2分) 已知S盒的输入为110011,请计算经过S盒变换之后的二进制输出。 【参考…...
攻防世界easyphp
<?php highlight_file(__FILE__); $key1 0; $key2 0;$a $_GET[a]; $b $_GET[b];if(isset($a) && intval($a) > 6000000 && strlen($a) < 3){if(isset($b) && 8b184b substr(md5($b),-6,6)){$key1 1;}else{die("Emmm...再想想&quo…...
【WRF教程第3.6期】预处理系统 WPS 详解:以4.5版本为例
预处理系统 WPS 详解:以4.5版本为例 Geogrid/Metgrid 插值选项详解1. 插值方法的工作机制2. 插值方法的详细说明2.1 四点双线性插值(four_pt)2.2 十六点重叠抛物线插值(sixteen_pt)2.3 简单四点平均插值(av…...
图解HTTP-HTTP协议
HTTP HTTP是一种不保存状态,即无状态的协议。HTTP协议自身不对请求和响应之间的通信进行保存。为了保存状态因此后面也有一些技术产生比如Cookies技术。 HTTP是通过URI定位网上的资源,理论上将URI可以访问互联网上的任意资源。 如果不是访问特定的资源…...
Linux基本命令
Linux基本命令 一条Linux命令由:命令本身 [可选项] [参数] ls 展示 ls命令的选项: -a 选项,可以展示出隐藏的内容 以 . 开头的文件或文件夹默认被隐藏,需要-a才能显示出来 **-l **选项,以列表的形式展示内容,并展示更多细节-h 选项&…...
【win10+RAGFlow+Ollama】搭建本地大模型助手(教程+源码)
一、RAGFlow简介 RAGFlow是一个基于对文档深入理解的开源RAG(Retrieval-augmented Generation,检索增强生成)引擎。 主要作用: 让用户创建自有知识库,根据设定的参数对知识库中的文件进行切块处理,用户向大…...
.ejs 后缀文件 - 嵌入式JavaScript模板
嵌入式JavaScript模板(Embedded JavaScript templates)文件是以.ejs 后缀。它是一种模板引擎,它允许你在你的HTML文件中直接嵌入JavaScript代码。EJS模板可以包含HTML代码、JavaScript表达式、控制结构(如if语句和循环)…...
springboot461学生成绩分析和弱项辅助系统设计(论文+源码)_kaic
摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装学生成绩分析和弱项辅助系统软件来发挥其高效地信息处理的作…...
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
文章目录 一、as类型转化和is类型检查1、as 关键字使用场景:语法:示例:特点: 2、is 关键字使用场景:语法:示例:特点: 3、总结 二、向上转型和向下转型1、向上转型示例: 2…...
“魔法糖果盒的秘密:用朴素贝叶斯算法猜糖果颜色”
想象一下,你有一个神奇的糖果盒,这个糖果盒里有两种糖果:红色的和蓝色的。你闭上眼睛,从盒子里拿出一个糖果,然后尝一尝,你想知道这个糖果是红色的还是蓝色的。朴素贝叶斯算法就像是一个魔法规则࿰…...
使用“NodeMCU”、“红外模块”实现空调控制
项目思路 空调遥控器之所以能够实现对空调的控制,是因为它能够向空调发射出特定的红外信号。从理论上来说,任何能够发射出这种相同红外信号的红外发射器,都可以充当空调遥控器(这也正是手机能够控制多种不同品牌空调的原因所在&a…...
了解cuda的统一内存
1. CUDA 6中的统一内存 在CUDA 6中,从Kepler GPU架构(计算能力3.0或更高)开始,在64位Windows 7、8和Linux操作系统(内核2.6.18)上开始支持统一内存. 从CUDA 6开始,NVIDIA推出了CUDA平台历史上…...
MySQL索引
2.1 索引概述 2.1.1 介绍 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足 特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就…...
会话控制(cookie、session 和 token)
1. 介绍 所谓会话控制就是 对会话进行控制HTTP 是一种无状态的协议,它没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户,而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题。 常见的会话控制…...
stm32 rtc 详解
目录 L151 RTC 唤醒代码 方式一 通过 RTC Alarm Interrupt:(基本和F1系列一样): L151 RTC 唤醒代码 方式二 通过 RTC WakeUp Interrupt F103VE RTC 闹钟唤醒代码 (103RC 没有闹钟中断): RTC(real time…...
D 咖智能饮品机器人:开启商业新篇
在科技迅猛发展的当下,智能机器人正逐步渗透到各个商业领域,D 咖智能饮品机器人便是其中的佼佼者,它的出现为饮品行业带来全新的发展契机,有望开启商业新篇。 从大环境来看,消费者对于饮品的需求日益多元化和个性化。他…...
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
以下代码来自Let’s Go further节选。具体说明均为作者本人理解。 编辑邮件模版 主要包含三个template: subject:主题plainBody: 纯文本正文htmlBody:超文本语言正文 {{define "subject"}}Welcome to Greenlight!{{end}} {{def…...
重拾设计模式--模板方法模式
文章目录 一、模板方法模式概述二、模板方法模式UML图三、优点1代码复用性高2可维护性好3扩展性强 四、缺点五、使用场景六、C 代码示例1七、 C 代码示例2 一、模板方法模式概述 定义:定义一个操作中的算法骨架,而降一些步骤延迟到子类中。模板方法使得…...
二、windows环境下vscode使用wsl教程
本篇文件介绍了在windows系统使用vscode如何连接使用wsl,方便wsl在vscode进行开发。 1、插件安装 双击桌面vscode,按快捷键CtrlShiftX打开插件市场,搜索【WSL】点击安装即可。 2、开启WSL的linux子系统 点击左下方图标【Open a Remote Win…...
我们的电视Our tv 3.6.0安卓+TV 一款全新电视直播软件-内置稳定直播源
应用简介 我们的电视(ourtv)是一款完全无广告的电视直播软件,清晰度可选择高清,超清,蓝光等播放。安装即可使用,再也不用费劲去找各种不稳定的直播源了。 “我们的电视”播放线路(直播源&…...
批处理理解
初识批处理 如何批处理: 命名:.bat 方法:创建一个记事本文件,然后将其扩展改为.bat 批处理作用:自上而下成批处理每一条DOS命令,直到执行到最后一条。运行环境:当然是我们cmd了 回归我学过的…...
高效准确的PDF解析工具,赋能企业非结构化数据治理
目录 准确性高:还原复杂版面元素 使用便捷:灵活适配场景 贴心服务:快速响应机制 在数据为王的时代浪潮中,企业数据治理已成为组织优化运营、提高竞争力的关键。随着数字化进程的加速,企业所积累的数据量呈爆炸式增长…...
Go框架比较:goframe、beego、iris和gin
由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web"框架"为主。这里稍微打了个引号,因为大部分"框架"从设计和功能定位上来讲,充其量都只能算是一个组件&…...