当前位置: 首页 > article >正文

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介

前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢,本篇将介绍一个INMP441采集音频并实时播放的应用。

往期相关文章:

ESP32 I2S音频总线学习笔记(一):初识I2S通信与配置基础

ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据

ESP32 I2S音频总线学习笔记(三):I2S音频输出

主要硬件

INMP441全向麦克风模块:
在这里插入图片描述
PCM5102A 立体声DAC模块 :在这里插入图片描述

硬件接线

ESP32和麦克风INMP441:

ESP32INMP441
D13SCK
D12WS
D14SD
3.3VVDD
GNDGND

ESP32和PCM5102A:

ESP32PCM5102A
-VCC
3.3V3.3V
GNDGND
GNDFLT、DMP、SCL (这里SCL悬空可能会有干扰,所以接地)
D27BCK
D25DIN
D26LCK
GNDFMT
3.3VXMT

软件实现

前面两篇文章我们详细介绍了I2S读取和I2S输出的初始化步骤,所以本篇我们就不过多介绍了。我们的目的是实现INMP441采集音频并通过PCM5102A实时播放(可替换为其他DAC模块如MAX98357),使用的协议是I2S,所以首先要分别配置麦克风I2S初始化音频播放模块I2S初始化,前者我们起名为setupI2SMic( ) ,后者起名为setupI2SDac( );因为我们是循环采集音频,所以音频处理逻辑部分放在loop( )函数,首先搭建起整体框架,注意必不可少的是包含I2S驱动头文件:

#include <driver/i2s.h>void setup() {Serial.begin(115200);setupI2SMic();setupI2SDac();
}void loop()
{/*音频处理逻辑部分...待补充*/
}

搭建完整体框架后,我们再来完善setupI2SMic( )和setupI2SDac( )里面的内容。

INMP441读取 I2S初始化

setupI2SMic( )里面的I2S初始化步骤如何配置,可参考往期第二篇文章。这里因为使用到了两个I2S,一个用于麦克风采集,一个用于播放音频,所以我们将I2S0用于麦克风,I2S1用于音频的实时播放。

// 配置 I2S0 用于麦克风采集
#define I2S_MIC_NUM    I2S_NUM_0
#define I2S_MIC_BCK 13
#define I2S_MIC_WS  12
#define I2S_MIC_SD  14#define SAMPLE_RATE    44100void setupI2SMic() {i2s_config_t mic_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 单声道.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,  // 增加 DMA 缓冲区数量.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t mic_pin_config = {.bck_io_num = I2S_MIC_BCK,.ws_io_num = I2S_MIC_WS,.data_out_num = -1,.data_in_num = I2S_MIC_SD};i2s_driver_install(I2S_MIC_NUM, &mic_config, 0, NULL);i2s_set_pin(I2S_MIC_NUM, &mic_pin_config);
}

PCM5102A输出 I2S初始化

// 配置 I2S1 用于 DAC 输出
#define I2S_DAC_NUM    I2S_NUM_1
#define I2S_DAC_BCK 27
#define I2S_DAC_WS  26
#define I2S_DAC_DIN  25void setupI2SDac() {i2s_config_t dac_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立体声.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t dac_pin_config = {.bck_io_num = I2S_DAC_BCK,.ws_io_num = I2S_DAC_WS,.data_out_num = I2S_DAC_DIN,.data_in_num = -1};i2s_driver_install(I2S_DAC_NUM, &dac_config, 0, NULL);i2s_set_pin(I2S_DAC_NUM, &dac_pin_config);
}

音频处理逻辑部分

音频处理逻辑部分主要步骤是从麦克风采集数据,然后播放音频数据,需要用到前面讲过的两个函数:esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);esp_err_t i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait);
除此之外,因为麦克风是单声道的,音频播放模块是双声道的,需要将单声道转化为双声道音频;并且这里测试发现如果不对采集到的声音进行放大,声音是比较小的,所以还需进行音频增益处理。其处理逻辑如下:

void loop(){/*音频处理逻辑部分 *///      从麦克风采集数据//     增益处理,放大音量并限制范围//     单声道转立体声//     播放音频数据
}

首先定义两个音频缓冲区,buffer 用于单声道输入,stereo_buffer 用于立体声输出。BUFFER_SIZE定义为单声道缓冲区大小1024,每次读取1024个样本,双声道缓存区的样本数是单声道缓存区样本数的两倍,所以为BUFFER_SIZE * 2。

#define BUFFER_SIZE    1024int16_t buffer[BUFFER_SIZE];          // 单声道缓冲区
int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立体声缓冲区

然后从麦克风采集数据:

i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);

将采集到的音频数据进行增益处理,这里实测增益因子20~50比较合适,太大了采集到的音频声音会大,但是近距离说话的时候会容易出现破音,对远处的采集声音较好。溢出保护是因为16 位音频范围为 -32768 到 32767,放大后若超出此范围就会导致失真。

 for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子为20,可以根据需要调整// 增加溢出保护if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}

放大音频数据后将单声道音频数据转化为立体声 音频数据,单声道只有一个声道的数据,立体声需要左右两个声道,这里将单声道样本复制到左右声道。

 for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左声道stereo_buffer[2 * i + 1] = buffer[i]; // 右声道}

通过 I2S1 接口将立体声数据发送到PCM5102A播放。

i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);

音频处理逻辑部分的代码如下,这个步骤可以总结为:1. 采集单声道音频 → 2. 放大音量并限制范围 → 3. 转换为立体声 → 4. 播放

void loop() {int16_t buffer[BUFFER_SIZE];          // 单声道缓冲区int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立体声缓冲区size_t bytesRead, bytesWritten;// 从麦克风采集数据i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益处理,放大音量并限制范围for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子为20,可以根据需要调整// 增加溢出保护if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;}// 单声道转立体声for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左声道stereo_buffer[2 * i + 1] = buffer[i]; // 右声道}// 播放音频数据i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);
}

全部整合后的代码如下:

#include <driver/i2s.h>// 配置 I2S0 用于麦克风采集
#define I2S_MIC_NUM    I2S_NUM_0
#define I2S_MIC_BCK 13
#define I2S_MIC_WS  12
#define I2S_MIC_SD  14// 配置 I2S1 用于 DAC 输出
#define I2S_DAC_NUM    I2S_NUM_1
#define I2S_DAC_BCK 27
#define I2S_DAC_WS  26
#define I2S_DAC_DIN  25#define SAMPLE_RATE    44100
#define BUFFER_SIZE    1024void setupI2SMic() {i2s_config_t mic_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 单声道.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,  // 增加 DMA 缓冲区数量.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t mic_pin_config = {.bck_io_num = I2S_MIC_BCK,.ws_io_num = I2S_MIC_WS,.data_out_num = -1,.data_in_num = I2S_MIC_SD};i2s_driver_install(I2S_MIC_NUM, &mic_config, 0, NULL);i2s_set_pin(I2S_MIC_NUM, &mic_pin_config);
}void setupI2SDac() {i2s_config_t dac_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立体声.communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,.dma_buf_count = 8,.dma_buf_len = BUFFER_SIZE};i2s_pin_config_t dac_pin_config = {.bck_io_num = I2S_DAC_BCK,.ws_io_num = I2S_DAC_WS,.data_out_num = I2S_DAC_DIN,.data_in_num = -1};i2s_driver_install(I2S_DAC_NUM, &dac_config, 0, NULL);i2s_set_pin(I2S_DAC_NUM, &dac_pin_config);
}void setup() {Serial.begin(115200);setupI2SMic();setupI2SDac();
}void loop() {int16_t buffer[BUFFER_SIZE];          // 单声道缓冲区int16_t stereo_buffer[BUFFER_SIZE * 2]; // 立体声缓冲区size_t bytesRead, bytesWritten;// 从麦克风采集数据i2s_read(I2S_MIC_NUM, buffer, BUFFER_SIZE * sizeof(int16_t), &bytesRead, portMAX_DELAY);// 增益处理,放大音量并限制范围for (int i = 0; i < BUFFER_SIZE; i++) {buffer[i] = buffer[i] * 20;  // 增益因子为20,可以根据需要调整// 增加溢出保护if (buffer[i] > 32767) buffer[i] = 32767;if (buffer[i] < -32768) buffer[i] = -32768;// }// 单声道转立体声for (int i = 0; i < BUFFER_SIZE; i++) {stereo_buffer[2 * i] = buffer[i];      // 左声道stereo_buffer[2 * i + 1] = buffer[i]; // 右声道}// 播放音频数据i2s_write(I2S_DAC_NUM, stereo_buffer, sizeof(stereo_buffer), &bytesWritten, portMAX_DELAY);
}

功能扩展

添加两个按钮,用于控制实时播放的音量。主要实现思路是通过按钮去控制增益因子的增加或减少。

首先创建一个数组用来存储增益因子,这里我们选择放大的倍数为20, 30, 40, 50, 60, 70, 80可选,然后numGains用于计算元素个数,方便我们后续的判断。并设置默认增益为20。

// 增益因子数组
const int gainFactors[] = {20, 30, 40, 50, 60, 70, 80};
const int numGains = sizeof(gainFactors) / sizeof(gainFactors[0]);
int currentGainIndex = 0;  // 默认增益为 20

整体思路是:如果按键1按下,currentGainIndex++, 如果按键2按下,currentGainIndex- -

考虑到按键抖动的情况,代码如下:

// 按键引脚
#define BUTTON_UP_PIN 33   // 增益增加按键(GPIO 33)
#define BUTTON_DOWN_PIN 32 // 增益减少按键(GPIO 32)// 按键去抖变量
unsigned long lastUpDebounceTime = 0;
unsigned long lastDownDebounceTime = 0;
const unsigned long debounceDelay = 50;void KeyUp() {// 检查增益增加按键bool upReading= digitalRead(BUTTON_UP_PIN);if (upReading!= lastUpButtonState) {lastUpDebounceTime = millis();}if ((millis() - lastUpDebounceTime) > debounceDelay) {if (upReading== LOW && currentGainIndex < numGains - 1) {  // 按下且未达最大增益currentGainIndex++;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切换}}lastUpButtonState = upReading;
}void KeyDn() {// 检查增益减少按键bool downReading = digitalRead(BUTTON_DOWN_PIN);if (downReading != lastDownButtonState) {lastDownDebounceTime = millis();}if ((millis() - lastDownDebounceTime) > debounceDelay) {if (downReading == LOW && currentGainIndex > 0) {  // 按下且未达最小增益currentGainIndex--;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切换}}lastDownButtonState = downReading;}

增加增益因子和减少增益因子的去抖机制相同,以增加为例,机械按键在按下或释放的时候会有产生短暂的电平抖动,通过去抖机制,等待一段时间(这里的debounceDelay)可以确保状态稳定,避免误判为多次按键事件。millis() 这个函数会返回程序启动后经过的毫秒数。lastUpDebounceTime = millis(); 标记按键状态变化的时间点,millis() - lastUpDebounceTime是当前时间与按键状态变化时间的差值,表示从上次状态变化以来经过的毫秒数, 用于判断按键状态是否稳定足够长时间,如果时间差值大于 debounceDelay,说明按键状态已经稳定,其效果类似于delay(50) (但是一个是阻塞等待,一个是非阻塞等待)。

主要代码是这两个, 其中numGains 是增益因子数组元素个数,因为数组是从零开始的,所以numGains个数减1就是表示数组最大索引数, 如果没达到最大索引,即还未达最大增益,currentGainIndex++。

 if (upReading== LOW && currentGainIndex < numGains - 1) {  // 按下且未达最大增益currentGainIndex++;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切换}

currentGainIndex > 0表示还未到最小索引,所以currentGainIndex- -

if (downReading == LOW && currentGainIndex > 0) {  // 按下且未达最小增益currentGainIndex--;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));delay(200);  // 防止快速切换}

按键是否按下是通过判断upReading和downReading是否变为LOW实现。

为了方便控制音量,这里我还尝试了使用旋转编码器来控制音量,主要代码如下:

#include <ESP32Encoder.h>
// 编码器引脚
#define MODE_DT_PIN  32  // A 相
#define MODE_CLK_PIN 35  // B 相#define MODE_STEP    2   // 每 2 个计数触发ESP32Encoder modeEncoder;void KeyUp() {if (currentGainIndex < numGains - 1) {currentGainIndex++;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));}
}void KeyDn() {if (currentGainIndex > 0) {currentGainIndex--;Serial.println("增益切换至: " + String(gainFactors[currentGainIndex]));}
}/*------EC11 控制函数------*/
void EC11_Control() {static int lastModeCount = 0;static unsigned long lastRotateTime = 0;int currentModeCount = modeEncoder.getCount();if (abs(currentModeCount - lastModeCount) >= MODE_STEP) {lastRotateTime = millis();if (currentModeCount > lastModeCount) {KeyUp(); // 顺时针增加增益Serial.println("向下");} else {KeyDn(); // 逆时针减少增益Serial.println("向上");}modeEncoder.setCount(0); // 重置计数lastModeCount = 0;}// 长时间无操作时输出停止if (millis() - lastRotateTime > 1000) {//Serial.println("停止旋转");lastRotateTime = millis(); }
}

理解了按键控制增益因子的原理,这里也是一样的。我们在EC11_Control()调用 KeyUp()和KeyDn()这两个函数。采用编码器的时候还需要注意在setup()函数里面添加初始化编码器的相关代码。

void setup() {
// 初始化编码器pinMode(MODE_CLK_PIN, INPUT_PULLUP);pinMode(MODE_DT_PIN, INPUT_PULLUP);modeEncoder.attachHalfQuad(MODE_CLK_PIN, MODE_DT_PIN);//配置编码器为半四分之一模式,只计数部分状态变化(每步约 1-2 个计数)modeEncoder.setFilter(50); // 滤波值modeEncoder.setCount(0);}

现象

ESP32驱动inmp441采集音频并实时播放

注意事项

  1. 音频输出可以用耳机去接收听到声音,也可以使用AUX线外接功放板。PCM5102A板子上也有L/R左右声道接口,也可以杜邦线接到其他功放上(比如上一期的TDA2030A功放模块)。
  2. INM441是全向麦克风模块,把扬声器和麦克风放在一起使用很容易引起啸叫,有耳机的话基本不会有这个问题。
  3. 根据前面啸叫问题,增益不宜调太大,一是容易引起啸叫,二是靠近说话容易引起破音,增益大了对远距离采集声音较好。

相关文章:

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...