latera

  • 2026-04-25
  • 发表了主题帖: 【STM32MP257F-DK测评】speexdsp和webrtc 的NS算法比较

    在上一篇文章中,使用speexdsp的库文件来实现单通道音频的降噪测试。总体效果还挺好。另外在以前工作中也有使用到webrtc项目中的降噪处理。2种降噪的处理方式有所不同。 我也可以将它移植到stm32mp2上。用同样的方式测试。 即 stm32mp2枚举成usb声卡,电脑同时播放音乐和录音。在stm32mp2中,用speexdsp的方式处理左声道,用webrtc ns的方式来处理右声道。这样电脑的录音文件刚好包含2个声道为不同处理结果的音频。   一、移植webrtc ns算法 首先,因为stm32mp2 sdk中并没有现成的webrtc ns库。需要自己重新下载源代码和编译文件。 源代码可下载 https://github.com/ROAD2018/AEC-ANS-AGC/blob/master/NS/ 中的文件 noise_suppression.c/noise_suppression.h。 在webrtc的原项目中noise_suppression由多个文件组成,移植比较麻烦。而这git仓库的文件已经将多个文件合并成1个源文件。方便我们直接使用和测试。   webrtc ns的使用,实际就是调用noise_suppression.h中的API函数。大致流程为: 1. 创建WebRtcNs 降噪器(右声道)     NsHandle *ns_handle_right;     ns_handle_right = WebRtcNs_Create(); 2. 初始化WebRtcNs 降噪器,实际就是设置采样率(16K)     WebRtcNs_Init(ns_handle_right, NS_SAMPLE_RATE) ; 3. 设置降噪等级(0-3,3为最大降噪)     WebRtcNs_set_policy(ns_handle_right, 3); 4. 分析输入音频的噪声(循环执行)     WebRtcNs_Analyze(ns_handle_right, right_buffer_16k); 5. 处理音频数据,应用降噪(循环执行)     WebRtcNs_Process(ns_handle_right, webrtns_buffer, 1, webrtns_buffer); 6. 最后,在处理结束后,将WebRtcNs 降噪器做资源释放     WebRtcNs_Free(ns_handle_right); 其中,WebRtcNs的的采样率只能设置为16K或者8K。由于声卡的采样率为48000。在我测试中,是直接将48000音频进行3分之1抽样后给到webrtcNs处理,处理完再进行3倍插值处理。 代码可以参照:   三、降噪处理效果对比 处理后的结果文件如下:(左声道为speexdsp结果,右声道为webrtcns结果,和原文件对比,可以看出2个结果都有降噪的效果)   原始音频文件:   以下是我对2个处理结果的个人的看法: 1. webrtc的采样率为16K,speexdsp的采样率为48K。speexdsp的语音还原度更高。 2. webrtc的最大降噪效果大概为24db,speexdsp的降噪效果可达到40db。单从噪声消除的降低效果上speexdsp更好。 3. webrtc的处理数据块是固定,为160个采样点处理一次。speexdsp的处理数据块是可调的,相对而言,延时更短。 4. webrtc的处理时间更短,暂用算力更低。 5. speexdsp存在以下问题,当完全没有输入信号后,speexdsp处理会重新收敛。此时重新有输入信号,会出现噪声泄露的现象。如下图:     四、算力评估 以上的代码同时运行了speexdsp ns 和 webrtc ns的处理。这个时候,可以同时测试一下stm32mp2上的实际算力如何。 可以在程序执行中,在另一个终端来执行top命令,查看程序占用的cpu百分比。 以下为我的测试结果: 1. 在程序使用 -g 选项编译,即不使用优化等级编译。程序执行占用的cpu百分比为10.6%。   1. 在程序使用 -O2 选项编译,即使用较优的优化等级编译。程序执行占用的cpu百分比为6.6%。   这样的算力消耗比较理想。stm32mp2的算力还是很给力的。   以上便是我对speexdsp 和 webrtc 降噪处理的学习内容。  

  • 2026-04-24
  • 回复了主题帖: 【STM32MP257F-DK测评】speexdsp的NS算法移植和测试

    Jacktang 发表于 2026-4-18 09:57 speexdsp对平缓噪声的抑制效果不错 那如何判断一段音频是否包含人声有什么思路? 这得用ai人声识别

  • 2026-04-14
  • 发表了主题帖: 【STM32MP257F-DK测评】speexdsp的NS算法移植和测试

    本帖最后由 latera 于 2026-4-14 22:53 编辑 一、speexdsp库说明 SpeexDSP 是一个开源的音频信号处理库,专注于为语音通信提供实时处理功能。它是经典语音编解码器项目 Speex 的一部分,但独立于编解码器,专门处理原始音频信号。 它的核心功能包括: 回声消除 (AEC):消除在语音通话中,自己扬声器播放的声音被麦克风再次采集产生的回声。 噪声抑制 (NS):降低背景环境噪声,提升语音清晰度。 自动增益控制 (AGC):自动调整录音音量,使声音大小保持稳定。 静音检测 (VAD):判断一段音频是否包含人声,常用于节省带宽或触发语音识别。 二、speexdsp库使用 STM32MP2的文件系统默认包含speexdsp的库文件和头文件。     在程序中,包含头文件,并按初始化设置 --> 设置参数 --> 周期处理 --> 最后销毁 的步骤来编写程序便可。如我要使用噪声抑制的方法: 1. 初始化预处理器(噪声抑制)     SpeexPreprocessState *preprocess_state;    preprocess_state = speex_preprocess_state_init(frame_size, sample_rate);        // frame_size 表示一帧音频数据的长度 2.   配置噪声抑制参数和噪声抑制强度     speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled);        // denoise_enabled = 1 (1表示使能)     speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noise_suppress_level); // noise_suppress_level = -25 (单位db,) 3. 应用噪声抑制     speex_preprocess(preprocess_state, samples, NULL);    // samples 为一帧单声道的数据。 程序参考(在后文有测试说明): #include <stdio.h> #include <stdlib.h> #include <alsa/asoundlib.h> #include <pthread.h> #include <semaphore.h> #include <speex/speex_preprocess.h> #include <string.h> #define DEVICE "hw:1,0" #define SAMPLE_RATE 48000 #define CHANNELS 2 #define SAMPLE_SIZE 2 /* 16-bit */ #define PERIOD_SIZE 256 #define BUFFER_SIZE (8 * PERIOD_SIZE) #define RING_BUFFER_SIZE 16 /* 环形缓冲区大小(周期数) */ // 环形缓冲区结构 typedef struct { char *data; int size; int period_size; int write_pos; int read_pos; sem_t empty; sem_t full; pthread_mutex_t mutex; } RingBuffer; // 全局变量 snd_pcm_t *capture_handle, *playback_handle; RingBuffer ring_buffer; SpeexPreprocessState *preprocess_state; // 初始化环形缓冲区 int init_ring_buffer(RingBuffer *rb, int period_size, int num_periods) { rb->period_size = period_size * CHANNELS * SAMPLE_SIZE; rb->size = num_periods; rb->data = (char *)malloc(rb->period_size * num_periods); if (!rb->data) { fprintf(stderr, "无法分配环形缓冲区\n"); return -1; } rb->write_pos = 0; rb->read_pos = 0; sem_init(&rb->empty, 0, num_periods); sem_init(&rb->full, 0, 0); pthread_mutex_init(&rb->mutex, NULL); return 0; } // 写入环形缓冲区 void write_ring_buffer(RingBuffer *rb, char *data) { sem_wait(&rb->empty); pthread_mutex_lock(&rb->mutex); memcpy(rb->data + rb->write_pos * rb->period_size, data, rb->period_size); rb->write_pos = (rb->write_pos + 1) % rb->size; pthread_mutex_unlock(&rb->mutex); sem_post(&rb->full); } // 从环形缓冲区读取 void read_ring_buffer(RingBuffer *rb, char *data) { sem_wait(&rb->full); pthread_mutex_lock(&rb->mutex); memcpy(data, rb->data + rb->read_pos * rb->period_size, rb->period_size); rb->read_pos = (rb->read_pos + 1) % rb->size; pthread_mutex_unlock(&rb->mutex); sem_post(&rb->empty); } // 清理环形缓冲区 void cleanup_ring_buffer(RingBuffer *rb) { free(rb->data); sem_destroy(&rb->empty); sem_destroy(&rb->full); pthread_mutex_destroy(&rb->mutex); } // 采集线程函数 void *capture_thread(void *arg) { short *buffer = (short *)malloc(PERIOD_SIZE * CHANNELS * SAMPLE_SIZE); if (!buffer) { fprintf(stderr, "无法分配采集缓冲区\n"); return NULL; } short *samples = (short *)malloc(PERIOD_SIZE*SAMPLE_SIZE); int err; while (1) { // 从捕获设备读取数据 if ((err = snd_pcm_readi(capture_handle, buffer, PERIOD_SIZE)) != PERIOD_SIZE) { if (err == -EPIPE) { fprintf(stderr, "捕获设备发生溢出,正在恢复...\n"); snd_pcm_prepare(capture_handle); continue; } else { fprintf(stderr, "读取捕获设备失败: %s\n", snd_strerror(err)); break; } } // 使用 speexdsp 进行降噪处理 for(int i=0;i<PERIOD_SIZE;i++){ samples[i] = buffer[CHANNELS*i]; } speex_preprocess(preprocess_state, samples, NULL); for(int i=0;i<PERIOD_SIZE;i++){ buffer[CHANNELS*i] = samples[i] ; } // 写入环形缓冲区 write_ring_buffer(&ring_buffer, buffer); } free(buffer); return NULL; } // 播放线程函数 void *playback_thread(void *arg) { char *buffer = (char *)malloc(PERIOD_SIZE * CHANNELS * SAMPLE_SIZE); if (!buffer) { fprintf(stderr, "无法分配播放缓冲区\n"); return NULL; } int err; while (1) { // 从环形缓冲区读取数据 read_ring_buffer(&ring_buffer, buffer); // 写入播放设备 if ((err = snd_pcm_writei(playback_handle, buffer, PERIOD_SIZE)) != PERIOD_SIZE) { if (err == -EPIPE) { fprintf(stderr, "播放设备发生下溢,正在恢复...\n"); snd_pcm_prepare(playback_handle); continue; } else { fprintf(stderr, "写入播放设备失败: %s\n", snd_strerror(err)); break; } } } free(buffer); return NULL; } int main() { int err; snd_pcm_hw_params_t *hw_params; pthread_t capture_tid, playback_tid; // 初始化环形缓冲区 if (init_ring_buffer(&ring_buffer, PERIOD_SIZE, RING_BUFFER_SIZE) < 0) { return EXIT_FAILURE; } // 打开捕获设备 if ((err = snd_pcm_open(&capture_handle, DEVICE, SND_PCM_STREAM_CAPTURE, 0)) < 0) { fprintf(stderr, "无法打开捕获设备: %s\n", snd_strerror(err)); cleanup_ring_buffer(&ring_buffer); return EXIT_FAILURE; } // 打开播放设备 if ((err = snd_pcm_open(&playback_handle, DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "无法打开播放设备: %s\n", snd_strerror(err)); snd_pcm_close(capture_handle); cleanup_ring_buffer(&ring_buffer); return EXIT_FAILURE; } // 配置捕获设备 if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { fprintf(stderr, "无法分配硬件参数结构: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) { fprintf(stderr, "无法初始化硬件参数: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { fprintf(stderr, "无法设置访问类型: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { fprintf(stderr, "无法设置格式: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, SAMPLE_RATE, 0)) < 0) { fprintf(stderr, "无法设置采样率: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, CHANNELS)) < 0) { fprintf(stderr, "无法设置通道数: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_period_size(capture_handle, hw_params, PERIOD_SIZE, 0)) < 0) { fprintf(stderr, "无法设置周期大小: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params, BUFFER_SIZE)) < 0) { fprintf(stderr, "无法设置缓冲区大小: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) { fprintf(stderr, "无法设置捕获设备参数: %s\n", snd_strerror(err)); goto cleanup; } // 配置播放设备 if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0) { fprintf(stderr, "无法初始化播放设备硬件参数: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { fprintf(stderr, "无法设置播放设备访问类型: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { fprintf(stderr, "无法设置播放设备格式: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_rate(playback_handle, hw_params, SAMPLE_RATE, 0)) < 0) { fprintf(stderr, "无法设置播放设备采样率: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, CHANNELS)) < 0) { fprintf(stderr, "无法设置播放设备通道数: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_period_size(playback_handle, hw_params, PERIOD_SIZE, 0)) < 0) { fprintf(stderr, "无法设置播放设备周期大小: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params_set_buffer_size(playback_handle, hw_params, BUFFER_SIZE)) < 0) { fprintf(stderr, "无法设置播放设备缓冲区大小: %s\n", snd_strerror(err)); goto cleanup; } if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0) { fprintf(stderr, "无法设置播放设备参数: %s\n", snd_strerror(err)); goto cleanup; } snd_pcm_hw_params_free(hw_params); // 初始化 speex 降噪器 preprocess_state = speex_preprocess_state_init(PERIOD_SIZE, SAMPLE_RATE); if (!preprocess_state) { fprintf(stderr, "无法初始化 speex 降噪器\n"); goto cleanup; } // 设置降噪参数 int denoise_enabled = 1; speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_DENOISE, &denoise_enabled); int noise_suppression = -40; speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noise_suppression); // 启动播放设备(填充初始缓冲区) char *init_buffer = (char *)calloc(PERIOD_SIZE * CHANNELS * SAMPLE_SIZE, 1); if (init_buffer) { for (int i = 0; i < 3; i++) { snd_pcm_writei(playback_handle, init_buffer, PERIOD_SIZE); } free(init_buffer); } // 创建线程 if (pthread_create(&capture_tid, NULL, capture_thread, NULL) != 0) { fprintf(stderr, "无法创建采集线程\n"); goto cleanup; } if (pthread_create(&playback_tid, NULL, playback_thread, NULL) != 0) { fprintf(stderr, "无法创建播放线程\n"); goto cleanup; } // 主循环 printf("开始音频环回测试...\n"); printf("按Ctrl+C退出\n"); while (1) { sleep(1); } cleanup: // 清理资源 if (preprocess_state) { speex_preprocess_state_destroy(preprocess_state); } if (hw_params) { snd_pcm_hw_params_free(hw_params); } snd_pcm_close(capture_handle); snd_pcm_close(playback_handle); cleanup_ring_buffer(&ring_buffer); return EXIT_FAILURE; } Makefile文件为 # Makefile for ALSA loopback test # Ensure cross-compilation environment is set up ifeq ($(CC),) $(warning Cross-compilation environment not set up!) $(warning Please run: source ../../SDK/environment-setup-cortexa35-ostl-linux) $(warning before building this project.) endif CFLAGS = -Wall -Wextra -g LDFLAGS = -lasound -lspeexdsp -pthread TARGET = loopback SOURCES = loopback.c all: $(TARGET) $(TARGET): $(SOURCES) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) clean: rm -f $(TARGET) help: @echo "ALSA Loopback Test Makefile" @echo "============================" @echo "make - 编译程序" @echo "make clean - 清理编译产物" @echo "make help - 显示此帮助信息" .PHONY: all clean help 三、speexdsp NS 测试 我同样使用 usb声卡来测试处理效果,声卡修改参考:【STM32MP257F-DK测评】usb声卡修改 https://bbs.eeworld.com.cn/thread-1345436-1-1.html 在上面的程序中,我将uac声卡设置成16bit的双声道音频。uac同时播放和录音。其中,读取uac声卡数据后,取出左声道来作为speexdsp ans 处理信号,右声道直接不做处理。最后将这2声道的数据从声卡播放出去。电脑的录音,就能获得处理前后的效果对比文件。   测试音频为一段歌曲和白噪声的混音音频。测试结果如下:   可以看出speexdsp对平缓噪声的抑制效果还是比较好的。测试音频见附件:    

  • 2026-04-12
  • 回复了主题帖: 【STM32MP257F-DK测评】usb声卡修改

    Jacktang 发表于 2026-4-12 09:25 开始要将usb设备枚举成一个虚拟网卡,是为了后面进行USB Gadget USB好驱动么 原厂镜像是已经枚举成一个虚拟网卡,我只是想分析它是怎么做到,好仿照它原脚本的做法。

  • 2026-04-01
  • 发表了主题帖: 【STM32MP257F-DK测评】usb声卡修改

    本帖最后由 latera 于 2026-4-1 22:09 编辑 前面文章提到,st的原厂镜像启动后,usb设备为一个usb ncm虚拟网卡设备。   可以理解为开发板在系统启动时,会执行一个程序或脚本,将usb设备枚举成一个虚拟网卡。执行这个程序或脚本的前提是内核或者驱动支持这类usb设备的创建和运行。所以,我以下修改的内容,实际就是让内核支持uac设备,并且写一个启动脚本,让usb枚举成一个uac设备。 一、内核编译与uac编译选项修改 内核编译的步骤可以参考:https://wiki.stmicroelectronics.cn/stm32mpu/wiki/Getting_started/STM32MP2_boards/STM32MP257x-DK/Develop_on_Arm_Cortex-A35/Modify,_rebuild_and_reload_the_Linux_kernel 大致步骤为: 1. 下载解压 开发包源码 https://www.st.com/en/embedded-software/stm32mp2dev.html#get-software (MP2-DEV-SRC),里面包含kernel的源代码 2. source SDK/environment-setup-cortexa35-ostl-linux,配置交叉工具链的环境变量,方便接下来编译kernel时,系统使用正确的gcc路径。 3. make O="${OUTPUT_BUILD_DIR}" defconfig fragment*.config,配置内核。文件夹其中的多个.config文件和defconfig文件将最终组成内核编译的配置文件。   4. make Image.gz vmlinux dtbs O="${OUTPUT_BUILD_DIR}",编译内核镜像 5. make modules O="${OUTPUT_BUILD_DIR}",编译内核模块。 6. 将编译的内核镜像和内核模块.ko文件,传输到开发板系统中的/boot。就完成了系统的替换。   一、usb ncm的实现分析 首先需要了解USB Gadget USB驱动的原理。 USB 设备驱动按照设备端关联的 USB 控制器是工作在主模式还是从模式,分为 USB 设备主机侧驱动 (主模式驱动),或者 USB 设备从机侧驱动 (从模式驱动)。 Linux 下将 USB 设备从机侧驱动称为 USB Gadget 驱动。USB Gadget 驱动是通过USB 来模拟其它类型的设备,如USB Gadget UAC 驱动 用来模拟声卡外设;USB Gadget Serial 驱动用来模拟串口外设等等。 USB Gadget 驱动,包括USB设备控制器(UDC) 驱动 和 Gadget功能(function)驱动,两大部分。 其中,USB设备控制器(UDC) 驱动,负责USB 设备控制器(UDC) 和主机侧 USB 控制器(UHC) 之间的数据传输。 而 Gadget 功能驱动(function) 负责实现功能协议(如 UDC 等)实现。USB 设备控制器(UDC) 驱动和Gadget 功能驱动(function) 彼此之间也会进行数据交互。 下面是查看开发板系统,推出usb ncm是怎么配置出来的。 1. 通过udev热插拔机制,调用/etc/udev/rules.d/97-usbotg.rules规则。   97-usbotg.rules 中实际调用的是/sbin/stm32_usbotg_eth_via_udev.sh ACTION=="add", SUBSYSTEM=="udc", RUN+="/sbin/stm32_usbotg_eth_via_udev.sh start" ACTION=="remove", SUBSYSTEM=="udc", RUN+="/sbin/stm32_usbotg_eth_via_udev.sh stop" 2. /sbin/stm32_usbotg_eth_via_udev.sh 实际是调用 /sbin/stm32_usbotg_eth_config.sh   3. stm32_usbotg_eth_config.sh中的作用大概为: 在 /sys/kernel/config/usb_gadget 文件夹中生成相应usb配置文件。来告诉usb gadget框架需要配置成什么类型的usb设备。   二、修改内核支持uac 为了实现uac功能,首先需要内核支持uac设备。使用make menuconfig修改usb gadget的配置。 需要将USB_CONFIGFS_F_UAC1 和 USB_CONFIGFS_F_UAC2选上     因为 USB_GADGET 默认选择为编译成内核模块,那依赖它的USB_CONFIGFS_F_UAC1 和 USB_CONFIGFS_F_UAC2也将作为内核模块。这样的话,需要重新编译内核模块,而不是重新编译内核。 make modules编译后,新增出新的文件为: u_audio.ko, usb_f_uac1.ko, usb_f_uac2.ko   将这3个文件传输到开发板的内核模块文件夹中。并命令行执行 /sbin/depmod -a。刷新内核模块的依赖关系。   三、编写启动文件 配置文件stm32_usbotg_uac_config.sh如下: 大致内容: 1. 配置usb的vid,pid 2. 在config文件夹设置usb的描述符和供电等配置 3. 在function文件夹中新增uac文件夹,usb gadget将自动产生uac相应的配置文件 4. 设置UDC,以启动USB,如下: # usb_gadget依赖于libcomposite模块 modprobe libcomposite # 挂载config文件系统 mount -t configfs none /sys/kernel/config # 创建g1目录,实例化一个新的gadget模板 echo "mkdir /sys/kernel/config/usb_gadget/g1" mkdir -m 0770 /sys/kernel/config/usb_gadget/g1 # 设置产品的VendorID、ProductID及USB规范版本号 echo "Setting Vendor Product ID's and bcdDevice" echo 0x0032 > /sys/kernel/config/usb_gadget/g1/idVendor echo 0x0019 > /sys/kernel/config/usb_gadget/g1/idProduct # 设备版本号 echo 0x0200 > /sys/kernel/config/usb_gadget/g1/bcdDevice # USB 1.1: 0x0110 # USB 2.0: 0x0200, USB 2.1: 0x0210, USB 2.5: 0x0250 # USB 3.0: 0x0300, USB 3.1: 0x0310, USB 3.2: 0x0320 # echo 0x0210 > /sys/kernel/config/usb_gadget/g1/bcdUSB # 实例化英语ID,开发商、产品和序列号字符串写入内核 echo "Setting English strings" mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/strings/0x409 echo "0123456789ABCDEF" > /sys/kernel/config/usb_gadget/g1/strings/0x409/serialnumber echo "rockchip" > /sys/kernel/config/usb_gadget/g1/strings/0x409/manufacturer echo "USB Audio Device" > /sys/kernel/config/usb_gadget/g1/strings/0x409/product # Creating Config echo "Creating Config" mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1 mkdir -m 0770 /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409 echo "uac2" > /sys/kernel/config/usb_gadget/g1/configs/c.1/strings/0x409/configuration echo 500 > /sys/kernel/config/usb_gadget/g1/configs/c.1/MaxPower # bind functions # uac2 need to put before uvc, otherwise uvc + uac2 enumerate failed in win10 echo "Creating UAC1 gadget functionality : uac1.0" mkdir /sys/kernel/config/usb_gadget/g1/functions/uac1.0 echo 48000 > /sys/kernel/config/usb_gadget/g1/functions/uac1.0/c_srate # 捕获采样率 echo 48000 > /sys/kernel/config/usb_gadget/g1/functions/uac1.0/p_srate # 播放采样率 ln -s /sys/kernel/config/usb_gadget/g1/functions/uac1.0 /sys/kernel/config/usb_gadget/g1/configs/c.1 # Binding USB Device Controller echo "Binding USB Device Controller" echo 48300000.usb > /sys/kernel/config/usb_gadget/g1/UDC   三、测试声卡播放和录音 在重启usb前先关闭usb ncm的配置,执行 /sbin/stm32_usbotg_eth_config.sh stop 然后执行stm32_usbotg_uac_config.sh start,并连接usb到电脑,电脑将显示新的录音设备和播放设备。   同时在开发板系统中执行aplay -l,将查看到系统新增了一个card 1的uac设备。   1.使用apaly recorder测试 在电脑播放音乐(选择新增的声卡作为播放设备),用录音软件来同时录制声卡的声音。 开发板上执行arecord -D hw:1,0 -c 2 -r 48000 -f S16_LE -v  |  aplay -D hw:1,0 -f S16_LE -c 2 -r 48000 -v。其中hw:1,0为uac声卡。   将录音保存下来,可以重新播放出来,如果声音正常,便可以判断uac能正常播放和录音。 2.使用alsa库测试 同样,可以用alsa库在c代码上实现一个loopback 的环回测试程序,代码见附件: 大致程序为使用snd_pcm_readi 读取声卡数据(电脑播放), snd_pcm_writei 写入声卡数据(电脑播放)。 while (1) { // 从捕获设备读取数据 if ((err = snd_pcm_readi(capture_handle, buffer, PERIOD_SIZE)) != PERIOD_SIZE) { if (err == -EPIPE) { fprintf(stderr, "捕获设备发生溢出,正在恢复...\n"); snd_pcm_prepare(capture_handle); continue; } else { // fprintf(stderr, "读取捕获设备失败: %s\n", snd_strerror(err)); // goto cleanup; } } // 写入播放设备 if ((err = snd_pcm_writei(playback_handle, buffer, PERIOD_SIZE)) != PERIOD_SIZE) { if (err == -EPIPE) { fprintf(stderr, "播放设备发生下溢,正在恢复...\n"); snd_pcm_prepare(playback_handle); continue; } else { // fprintf(stderr, "写入播放设备失败: %s\n", snd_strerror(err)); // goto cleanup; } } } 测试现象如下:   这样就完成uac的测试了。上位机实现的是uac1.0,也可以修改stm32_usbotg_uac_config.sh改为uac2.0来测试,因为默认依然为2通道声卡,所有测试测试效果是一样的。  

  • 2026-03-29
  • 发表了主题帖: 【STM32MP257F-DK测评】SDK开发环境搭建及测试实时性

    本帖最后由 latera 于 2026-3-29 23:58 编辑 一、搭建环境 前面文章提到ST为MPU开发定义了3种开发包,对应3种开发阶段。之前说的starter package 是原厂标配的烧录镜像。第2种开发包是SDK + 内核 uboot等源码。SDK里包含编译应用 和 内核的交叉工具链,也包括编译时用的头文件和库文件。 其实,可以在开发板系统中,通过apt-get install 安装各种工具和软件。但是实践下来并不能轻易下载到。主要是要开发板能连接到外网(获取 http://packages.openstlinux.st.com/6.2 软件包)。可以参考Package repository for OpenSTLinux distribution - stm32mpu,来试试能不能下载。因为我现在并不需要下载大量的软件和工具。并且我想测试一下交叉编译的工具链。 安装SDK的官方参考教程:Install the SDK - stm32mpu,其中: SDK下载:https://www.st.com/en/embedded-software/stm32mp2dev.html#get-software 中MP2-DEV-x86,我是在x86电脑的虚拟机安装ubuntu22来开发程序的。 源码下载:https://www.st.com/en/embedded-software/stm32mp2dev.html#get-software 中的MP2-DEV-SRC。   二、测试hello world SDK解压后,并根据官方教程执行一个脚本,交叉工具链便安装成功。并不需要重新编译生成。同时文件夹中包含一个配置文件 environment-setup-cortexa35-ostl-linux   这个文件中指定gcc等工具的文件名和路径,也指定了头文件和库文件的路径。在编译应用程序前可以先source一下这个文件。 例如: 新建一个hello_world.c 文件 #include "stdio.h" int main(int argc, char **argv) { printf("Hello World\n"); return 0; } 并新建一个Makefile。 PROG = hello_world SRCS = hello_world.c CLEANFILES = $(PROG) all: $(PROG) $(PROG): $(SRCS) $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) clean: rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS)) 执行 source xxxx/SDK/environment-setup-cortexa35-ostl-linux 再执行 make 将得到一个hello_world 的文件,放到开发板上,执行将得到如下结果   这样便表示gcc的编译是完全正确的。   三、交叉编译rt-test 为了测试STM32MP2的实时性,需要 用到2个软件工具,cyclictest + stress-ng。  cyclictest是用于测试中断延时加调度延时,中断延时(interrupt latency),即中断发生到进入中断处理程序ISR的延时。调度延时(scheduling latency),即当任务被唤醒到任务真正获得CPU使用权中间的延时。 stress-ng是系统压力测试工具。如 CPU(各种指令集、加密、浮点、乱序执行等) 内存(分配、页面错误、缓存跳跃、碎片化等) 磁盘(顺序/随机读写、fsync、fallocate、mmap 等) I/O(hdd、aio、ioprio、sync 等) 网络、调度、虚拟内存、管道、零设备等几乎所有子系统 实际作用是为了在cyclictest 测试时,让内存,磁盘,cpu进行各种工作,增多系统的工作内容,增大系统调度延时的可能性。这样能更准确地获取整个系统实时性能。 cyclictest的源码仓库为https://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git  我选择下载的是rt-tests-2.2.tar.gz   下载后解压,需要修改Makefile。 1. 注释掉CC 和 AR定义,在make之前通过 source environment-setup-cortexa35-ostl-linux来指定。   2. 修改make install 的文件夹路径为当前文件夹下   这样执行 make 和 make install,便能在当前文件夹的usr/local/bin 得到一个cyclictest的执行文件,将它传输到开发板。   四、交叉编译stress-ng stress-ng 的源码仓库在https://github.com/ColinIanKing/stress-ng/releases ,我选择的是stress-ng-0.13.01.tar.gz。 下载并解压,因为Makefile并没有指定CC,这样反而简单,执行source environment-setup-cortexa35-ostl-linux就可以,再将make install的路径改为本文件夹内即可。   make 、make install后,在本文件夹内就可以得到stree-ng的可执行文件,同样将它传输到开发板中。     四、cpu实时性能测试 首先将cpu的主频提到最高: root@stm32mp2-e3-d6-87:/# echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 查看cpu主频为1.5GHZ     1. 空载测试 终端执行:./cyclictest -m -Sp99 -i1000 -h800 -D10m  其中各个命令参数表示 -m:锁定当前和未来的内存分配,防止测试进程被交换到磁盘,避免换页操作引入额外延迟。 -Sp99:将测试线程的调度策略设置为 SCHED_FIFO(实时先进先出策略),并将其优先级设置为 99(这是Linux中用户空间可设置的最高实时优先级)。 -i1000:指定测试的基本间隔为 1000微秒(1毫秒)。测试线程将尝试每隔1毫秒唤醒一次并计算实际延迟。 -h800:输出一个大小为 800​ 个桶的直方图,用于统计不同延迟区间的发生次数,更细致地展示延迟分布。 -D10m:指定测试总运行时长为 10分钟。 运行结果如下:平均延时9us,但是最大延时有1646us。     2. 满载测试 stress-ng -c 2 --io 2 --vm 2 --vm-bytes 32M --timeout 100s & 其中命令为: -c 2:启动 2 个工作者进程,专门执行 CPU 压力测试(例如计算素数)。 --io 2:启动 2 个工作者进程,专门执行 I/O 压力测试(例如频繁的文件读写)。 --vm 2:启动 2 个工作者进程,专门执行内存压力测试。 --vm-bytes 32M:指定每个内存压力测试进程使用 32MB 的内存块进行操作。 --timeout 100s:指定整个压力测试持续运行 100 秒后自动停止 测试结果如下,平均延时22us,但是最大延时有1717us。     之前使用rk3308(4核A35@1.3GHZ)进行同样测试,空载的平均延时为20us,最大延时为1400us。满载的平均延时为30us,最大延时为2000us。 可看出STM32MP2在高主频的加持下,平均系统延时更短,不过因为标准linux内核并不是实时内核,存在抖动,造成系统的最大延时较大,但是1ms多的延时基本满足实时要求。 后续也可以尝试打上实时补丁,试试效果。 另外这里测试的时长是为10分钟,并不十分严谨,实际测试需要数小时或1天多。  

  • 回复了主题帖: 【STM32MP257F-DK测评】性能测试

    秦天qintian0303 发表于 2026-3-29 19:02 有没有那种比较符合标准的测试工具?    memtester 算是内存压测的标配

  • 2026-03-28
  • 发表了主题帖: 【STM32MP257F-DK测评】性能测试

    本帖最后由 latera 于 2026-3-28 18:02 编辑 上一篇只是将开发板跑起来。并没有详细说开发板如何使用和开发。不过在开发之前可以先了解一下开发板的配置和性能。毕竟stm32mp2芯片是st近年来开发的最强mpu。得看看这个开发板的用料是否能配得上这个mpu性能。 一、开发板性能 下表为开发板的主要芯片的型号和配置: 芯片型号 芯片类型 型号/规格 关键性能与说明 STM32MP257FA MPU主控(SoC) 双核 Arm Cortex-A35 + Cortex-M33 + M0+ A35最高1.5GHz + M33 400MHz (STMicroelectronics) 支持Linux + 实时控制(异构架构) 内置GPU(OpenGL/Vulkan)、NPU- 支持DDR3/DDR4/LPDDR4(最大4GB) 丰富接口:Ethernet、USB3.0、PCIe、MIPI等 MT53E1G32D2FW-046 DDR 运行内存 LPDDR4  1Gbit x32(4Gbytes) Micron DDR4 数据率约4266MT/s 用于系统主内存 高带宽支持Linux系统 THGBMUG6C1LBAIL eMMC存储 eMMC 5.1 NAND Flash 8GB Kioxia(原东芝)eMMC 容量常见 8GB/16GB/32GB 支持HS400高速模式 用于系统启动+文件系统 LBEE5KL1YN-81 无线通信模块 IEEE802.11b/g/n W-LAN Bluetooth® v5.2 (BR/EDR/BLE)  Murata模块 SDIO (WLAN) UART (BT)  STLINK_V3EC 调试/下载器 ST官方调试接口模块 支持SWD/JTAG调试 RTL8211F-CG 以太网PHY 千兆以太网PHY 支持10/100/1000 Mbps RGMII接口 低功耗设计 常用于工业/嵌入式网络 其中stlink应该是用调试stm32mp2中的M33核。ddr和emmc的容量和速度都这些年开发板的高配了。开发各种项目完全没有问题。   二、连接方式 开发之前,首先要了解怎么连接到开发板,方便调试和传输文件。之前开发一般会使用串口调试内核启动和打印信息,usb adb 和网络来传输文件和调试应用。stm32mp257f-dk的文件系统没有并没有adb,可能要重新构建yocto的文件系统或者自己编译一个才行。所以,下面介绍系统已经有的连接开发板的方式。 1. 串口终端连接 上面提到板载的stlink有串口连接到stm32mp2主芯片上,并且stlink在电脑上枚举出了串口设备。可以通过串口终端软件连接到开发板。 stlink的驱动在在安装stm32CubeProg软件时已经自动安装了。需要注意的是,stlink连接电脑时,会出现2个串口,选择串口号大的一个。 我是MobaXterm软件,在串口配置上选择如下。波特率为115200     串口波特率可以在系统的启动打印信息上找到对应的。登录时不需要密码   2. usb 虚拟网络连接 通过ifconfig,可以看到usb虚拟网口(usb0)的IP为192.168..7.1   同样,可以通过MobaXterm软件,ssh连接到开发板的终端。也可以通过SFTP或者SCP发送和下载文件。用户名为root,没有密码。 3. 网络连接 网口也是可以连接的,不过开发板系统默认是dhcp, 直连电脑时,要先在开发板上设置好IP,如下,用ifconfig设置网口end0的IP为169.25.1.100。即可用MobaXterm的ssh连接开发板   4.wifi连接热点 无线连接可以参考How to setup a WLAN connection - stm32mpu 进行配置,实际系统已经默认配置了大部分,只需要最后的基本步骤的命令。执行时会提示需要删除/var/run/wpa_supplicant/wlan0,先删掉即可。 wpa_passphrase <your_ssid_name> <your_ssid_key> >> /etc/wpa_supplicant.conf wpa_supplicant -B -iwlan0 -c /etc/wpa_supplicant.conf iw wlan0 link   这样,就可以连接到外部网络。ping一下百度     三、基本命令测试 1. 查看系统信息 (下面的信息我已经重新编译了kernel) root@stm32mp2-e3-d6-87:/# uname -a Linux stm32mp2-e3-d6-87 6.6.116 #2 SMP PREEMPT Wed Mar 25 21:59:12 CST 2026 aarch64 GNU/Linux 2. 查看GCC版本信息 root@stm32mp2-e3-d6-87:/# cat /proc/version Linux version 6.6.116 (root@ubuntu) (aarch64-ostl-linux-gcc (GCC) 13.4.0, GNU ld (GNU Binutils) 2.42.0.20240723) #2 SMP PREEMPT Wed Mar 25 21:59:12 CST 2026 3. 查看CPU信息命令 root@stm32mp2-e3-d6-87:/# cat /proc/cpuinfo processor : 0 BogoMIPS : 80.00 Features : fp asimd evtstrm crc32 cpuid CPU implementer : 0x41 CPU architecture: 8 CPU variant : 0x1 CPU part : 0xd04 CPU revision : 0 processor : 1 BogoMIPS : 80.00 Features : fp asimd evtstrm crc32 cpuid CPU implementer : 0x41 CPU architecture: 8 CPU variant : 0x1 CPU part : 0xd04 CPU revision : 0 BogoMIPS:在系统内核启动时粗略测算的 CPU 每秒运行百万条指令数(MillionInstructionsPerSecond)。   4. 查看 cpu的当前温度 root@stm32mp2-e3-d6-87:/# cat /sys/class/thermal/thermal_zone0/temp 47264 上面显示数字为千分之一度,除以 1000 就是当前温度值,单位为摄氏度。   5. CPU  压力测试 root@stm32mp2-e3-d6-87:/# echo "scale=5000;4*a(1)" | bc -l -q & 上述命令将在后台计算的 PI,并精确到小数点后 5000 位。计算过程需要一段时间,此时,可以通过 top 命令检查 CPU 利用率的变化,如下所示,几乎占用了一个CPU的所有算力   约 2 分钟后,PI 结果被计算出来。在此期间 CPU 单核使用率达满载,没有发生异常,说明 CPU 压力测试通过。还可以继续增加精确值,可进一步提高测试压力。   6.   查看内存信息 root@stm32mp2-e3-d6-87:/# cat /proc/meminfo MemTotal: 3868068 kB MemFree: 3453700 kB MemAvailable: 3484912 kB Buffers: 15856 kB Cached: 144852 kB SwapCached: 0 kB Active: 48176 kB Inactive: 172368 kB Active(anon): 1248 kB Inactive(anon): 67664 kB Active(file): 46928 kB Inactive(file): 104704 kB Unevictable: 102400 kB Mlocked: 102400 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 4 kB Writeback: 0 kB AnonPages: 162236 kB Mapped: 41240 kB Shmem: 9076 kB KReclaimable: 22928 kB Slab: 53532 kB SReclaimable: 22928 kB SUnreclaim: 30604 kB KernelStack: 2992 kB PageTables: 2140 kB SecPageTables: 0 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 1934032 kB Committed_AS: 613800 kB VmallocTotal: 133141626880 kB VmallocUsed: 16228 kB VmallocChunk: 0 kB Percpu: 584 kB HardwareCorrupted: 0 kB AnonHugePages: 112640 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB CmaTotal: 131072 kB CmaFree: 128376 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB MemTotal:所有可用的 RAM 大小,物理内存减去预留位和内核使用   7. 内存压力测试 通过给定测试内存的大小和次数,可以对系统现有的内存进行压力上的测试。可使用系统工具 memtester 进行测试,如指定内存大小 100MB,测试次数为 1,测试命令为“memtester 100M 1"。这里只是简单测试100MB空间稳定性。 root@stm32mp2-e3-d6-87:/# memtester 100M 1 memtester version 4.6.0 (64-bit) Copyright (C) 2001-2020 Charles Cazabon. Licensed under the GNU General Public License version 2 (only). pagesize is 4096 pagesizemask is 0xfffffffffffff000 want 100MB (104857600 bytes) got 100MB (104857600 bytes), trying mlock ...locked. Loop 1/1: Stuck Address : ok Random Value : ok Compare XOR : ok Compare SUB : ok Compare MUL : ok Compare DIV : ok Compare OR : ok Compare AND : ok Sequential Increment: ok Solid Bits : ok Block Sequential : ok Checkerboard : ok Bit Spread : ok Bit Flip : ok Walking Ones : ok Walking Zeroes : ok Done.   8. 查看 eMMC  容量 root@stm32mp2-e3-d6-87:/# fdisk -l Disk /dev/mmcblk2: 7.28 GiB, 7818182656 bytes, 15269888 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: C61F2ECD-0DCD-4DD4-BB20-27D8C38A3BBA Device Start End Sectors Size Type /dev/mmcblk2p1 1024 2047 1024 512K unknown /dev/mmcblk2p2 2048 3071 1024 512K unknown /dev/mmcblk2p3 3072 11263 8192 4M unknown /dev/mmcblk2p4 11264 19455 8192 4M unknown /dev/mmcblk2p5 19456 20479 1024 512K unknown /dev/mmcblk2p6 20480 151551 131072 64M Linux filesystem /dev/mmcblk2p7 151552 663551 512000 250M Linux filesystem /dev/mmcblk2p8 663552 6955007 6291456 3G Linux filesystem /dev/mmcblk2p9 6955008 15268863 8313856 4G Linux filesystem Disk /dev/mmcblk2boot0: 4 MiB, 4194304 bytes, 8192 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk /dev/mmcblk2boot1: 4 MiB, 4194304 bytes, 8192 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes 显示 emmc 大小约为 7.28GB。   8.  查看 eMMC  分区信息   root@stm32mp2-e3-d6-87:~# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 1.8G 0 1.8G 0% /dev /dev/mmcblk2p8 2.8G 614M 2.1G 23% / tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs 756M 8.9M 747M 2% /run tmpfs 1.9G 4.0K 1.9G 1% /tmp /dev/mmcblk2p6 55M 21M 30M 41% /boot /dev/mmcblk2p7 228M 27M 185M 13% /vendor /dev/mmcblk2p9 3.8G 64M 3.5G 2% /usr/local tmpfs 1.9G 28K 1.9G 1% /var/volatile tmpfs 378M 4.0K 378M 1% /run/user/0 /dev/mmcblk2p8:emmc 根文件系统大小。 9. eMMC  的性能测试 写文件测试 root@stm32mp2-e3-d6-87:/# time dd if=/dev/zero of=test_file bs=10M count=10 conv=fsync 10+0 records in 10+0 records out 104857600 bytes (105 MB, 100 MiB) copied, 1.73408 s, 60.5 MB/s real 0m 1.80s user 0m 0.00s sys 0m 0.63s 读文件测试 root@stm32mp2-e3-d6-87:/# time dd if=test_file of=/dev/null bs=10M count=10 iflag=direct,nonblock 10+0 records in 10+0 records out 104857600 bytes (105 MB, 100 MiB) copied, 1.17674 s, 89.1 MB/s real 0m 1.18s user 0m 0.00s sys 0m 0.18s 由此得出 emmc 的写速度为60.5 MB/s,读速度为 89.1 MB/s。   10. 查看EMMC寿命 root@stm32mp2-e3-d6-87:~# mmc extcsd read /dev/mmcblk2 | grep Life eMMC Life Time Estimation A [EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A]: 0x01 eMMC Life Time Estimation B [EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B]: 0x00 其中关于这些参数的解释: eMMC Life Time Estimation A:类型 A 主要针对 SLC(单层单元)用户分区的擦除块寿命估计。这种分区通常具有更高的耐久性,适合需要频繁写入的场景。 eMMC Life Time Estimation B:类型 B 则针对 MLC(多层单元)引导分区的擦除块寿命估计。MLC 的耐久性相对较低,但其存储密度更高,适用于需要较大存储空间的应用。 以上参数的值均以 10%的步长参考,例如:0x01 表示使用了 0%-10%的设备寿命。可以看出emmc几乎没有使用。   以上开发板的性能测试。总体下来,这个开发板还是相对给力的。    

  • 发表了主题帖: 【STM32MP257F-DK测评】开箱测试

    本帖最后由 latera 于 2026-3-28 11:28 编辑 非常感谢EEWorld提供的这次STM32MP257F-DK开发板的评测机会。之前学习和工作中。一直使用STM32的MCU来开发项目,算是依靠学习STM32才找到工作的。从接触STM32F4到STM32F7,再到STM32U5系列,但是从来没有用过STM32的MPU系列。而这次可以申请到STM32最为新出stm32mp2的MPU系列芯片的开发板,也特别幸运。 这次评测的stm32mp257f-dk是通过易络盟下单购买的,可能是国外订单,到手的时间比较长。拿到手发现装上SD卡启动不了,先看一下配置,再重新烧录系统。 一、开箱 开发板整体设计的确一眼看上去就是ST的开发,规规整整。 其中开发板的布置如下:   需要留意的是开发板可以通过 usb(cn15),串口(cn21连接板载stlink,并且stlink串口连接stm32mp2),和网口(cn16)连接到的电脑并登录到开发板的系统终端。cn21连接到电脑可以为开发板供电。如果要hdmi(cn1)连接显示屏,最好用电源适配器连接CN21。保证供电可靠。左下方有多个按键和led。其中B1为复位键。LD11为供电指示led。LED1和LD9连接到STM32MP2主芯片上,烧录系统后将闪烁,指示系统启动正常。开发板上方(hdmi接口右侧)的拨码开关用来指示用emmc或者SD卡启动,或者用usb烧录镜像。启动方式如下:   二、烧录镜像 开始想使用的SD烧录镜像,但是发现烧录一半卡住了,改成emmc烧录。两种启动的烧录方式都都一样,都是通过stm32cubeprogramer来完成。可以在https://www.st.com/en/development-tools/stm32cubeprog.html#下载软件,linux和window版本都可以。使用方法一样。stm32mp2的原厂镜像可以在https://www.st.com/en/embedded-software/stm32mp2starter.html#get-software下载. 另外,st给MPU定义了三种不同的开发阶段的开发包:Starter Package,Developer Package,Distribution Package ,其中,starter package就是原厂打包开发镜像,里面有不同的启动方式,不同加密方式,不同开发板的镜像。 将拨码开发拨到usb烧录方式,用2个usb typeC连接电脑,并启动开发板。打开stm32cubeprog。其中usb(cn15)是负责烧录镜像的,cn21在这里只是负责供电。 按如下步骤下载镜像。其中下载到开发板的镜像其实是各个不同的文件组成,包括uboot,内核,文件系统等等,tsv文件就像是这各个镜像文件的目录,指定文件在文件夹的位置。所以第5步中的文件夹路径不能忽略,并且在第4步中选择tsv文件会自动选择文件夹,但是这个文件夹路径可能是不对的。要重新选择。    我一开始使用的是emmc烧录 tsv文件:xxxxx\FLASH-stm32mp2-openstlinux-6.6-yocto-scarthgap-mpu-v26.02.18\stm32mp2-openstlinux-6.6-yocto-scarthgap-mpu-v26.02.18\images\stm32mp2\flashlayout_st-image-weston\optee\FlashLayout_emmc_stm32mp257f-dk-optee.tsv 镜像文件夹:xxxxx\FLASH-stm32mp2-openstlinux-6.6-yocto-scarthgap-mpu-v26.02.18\stm32mp2-openstlinux-6.6-yocto-scarthgap-mpu-v26.02.18\images\stm32mp2 经过很漫长等待,整个镜像才下载完。我接的是usb3.0接口,花了20多分钟。看网上说如果用usb2.0的可能时间需要1小时。 三、demo演示 烧录成功后,选择emmc启动,使用开发板hdmi接口连接显示器,并且将usb鼠标连接到开发板的cn19的usb接口上,重启开发板就可以启动镜像。启动界面和主界面如下:   镜像提供了5个演示功能: 网络数据获取:通过电脑浏览器连接开发板,获取后台数据。连接usb后,开发板会枚举成一个usb ncm设备(虚拟网卡),电路可以通过IP连接开发板。 摄像头演示:以为我没有摄像头,就没有测试了。 视频播放演示 3D图片播放演示 蓝牙连接音乐播放,连接蓝牙耳机,可以播放开发板上的音频文件 以上就是我的开箱测评。下一个评测会测试开发的基本功能和搭建SDK开发环境。                  

  • 2026-03-05
  • 回复了主题帖: 测评入围名单:STM32MP257F-DK

    个人信息无误,确认可以完成评测计划,已知晓测评文章主打要求

  • 2025-12-23
  • 发表了主题帖: 【Follow me第三季第3期】 Seeed Studio XIAO ESP32S3 Sense 任务完结汇总

           能够参加这个Follow me的活动,非常开心。之前并没有使用ESP32,也没有使用过Arduino来做开发。能够通过这次机会学习之前没有接触过的知识,确实拓宽自己的知识面,以后用到的时候也不至于匆匆忙忙,手忙脚乱。 一、硬件入门和环境搭建 1. 这提供的开发套件包括: Seeed Studio XIAO ESP32S3一套 MCP4725 DAC输出模块 SH45 温湿度检测模块 其中,Seeed Studio XIAO ESP32S3集成了摄像头传感器、数字麦克风和 SD 卡支持。Xtensa LX7 双核,32 位处理器,运行频率高达 240 MHz。摄像头传感器为OV2640,分辨率 1600x1200。引脚图如下:   原理图可参考如下: 2. 环境搭建       从https://www.arduino.cc/en/software下载最新Arduino的安装包,我使用的2.3.6。启动安装程序,一路点击下一步便可顺利安装,需要注意的是首次打开Arduino。Arduino会继续从网上下载相关组件。因为连接的是外网,这里下载和安装组件的时间可能较长。     导航到 File > Preferences,并在 "Additional Boards Manager URLs" 中填入以下网址: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json     导航到 Tools > Board > Boards Manager...,在搜索框中输入关键词 esp32,选择最新版本的 esp32,并安装它。     通过端口选择栏选设备,Seeed Studio XIAO ESP32S3内部虚拟了一个串口设备,在“Select Other Board and Port”中选择板卡和端口。Arduino自动检测板卡是否连接。   二、点亮核心板,串口显示“Hello World” Seeed Studio XIAO ESP32S3的核心板上带一个led(在usb的一侧)。可以试验一下控制led。因为arduino中已经带有很多例程。这次使用简单的blink的例程。在Arduino上,导航到 File > Examples > 01.Basics > Blink,打开程序。并让如下修改代码: // the setup function runs once when you press reset or power the board void setup() { // initialize digital pin LED_BUILTIN as an output. pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); Serial.println("Hello World!"); } // the loop function runs over and over again forever void loop() { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second Serial.println("led on!"); digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second Serial.println("led off!"); } 流程大致如下: Arduino 应用窗口可以完成代码的编辑,编译和下载。其中,工具栏上从左往右三个按钮分别为verify,upload,debug。点击verify,表示进行工程检查和编译。点击upload,表示将编译好的固件上传到芯片上。因为Seeed Studio XIAO ESP32S3没有调试功能,debug按钮无法使用。 测试现象如下:   三、使用内置ADC Seeed Studio XIAO ESP32S3中内置ADC外设,可以读取外部Pin脚的电压。默认使用的Pin脚为A0,XIAO ESP32S3的 A0 对应 D0 引脚。 通过编写代码读取adc值,并将数值转换成对应的电压值。输出到串口中。 如下为示例代码,其中,adc检测值与电压的转换关系为: 实际电压值 = (检测值 / 4095) * 3.3V。 int sensorPin = A0; // select the input pin for the potentiometer int ledPin = 13; // select the pin for the LED int sensorValue = 0; // variable to store the value coming from the sensor void setup() { // declare the ledPin as an OUTPUT: pinMode(ledPin, OUTPUT); Serial.begin(115200); } void loop() { float voltage = 0; // read the value from the sensor: sensorValue = analogRead(sensorPin); voltage = (sensorValue / 4095.0) * 3.3; Serial.print(voltage); Serial.println(); // turn the ledPin on digitalWrite(ledPin, HIGH); // stop the program for <sensorValue> milliseconds: delay(200); sensorValue = analogRead(sensorPin); voltage = (sensorValue / 4095.0) * 3.3; Serial.print(voltage); Serial.println(); // turn the ledPin off: digitalWrite(ledPin, LOW); // stop the program for <sensorValue> milliseconds: delay(200); } 大致流程为: 实验效果: 可以通过将D0连接到3V3或GND来判断检测电压正确,并且在Arduino的Serial Plotter 窗口查看电压的波形图。另外,在MCP4725 DAC会再次使用到ADC。   四、使用MCP4725 DAC MCP4725 是一款低功耗、高精度、单通道、12位带缓冲电压输出的数模转换器(DAC),使用I2C通讯。这次活动购买的MCP4725模块已经贴心的将默认I2C地址写在模块之上。并且Arduino实际有挺多支持MCP4725的库。我们可以导入这些库,并在提供的示例中,直接修改代码来测试。 我导入的库为比较简单的MCP4725库。并且使用MCP4725_wave_generator例程来修改。 代码的基本功能为,ESP32通过I2C控制MCP4725模块,对MCP4725初始化配置,根据串口输入的命令,输出对应波形。可以输出正弦波,三角波,方波等,这里主要测试正弦波。为了可以判断输出结果是否正常,将MCP4725的输出引脚接到示波器来查看输出波形,也可以直接接到XIAO模块的D0上,结合上一个实验的代码用ADC同时读取MCP4725的输出电压值。MCP4725主要的代码如下: MCP4725 MCP(0x62): 设置I2C地址 MCP.begin(): 配置及启动MCP4725 MCP.setValue(xx): 设置当前输出值。   硬件连接: MCP4725 引脚 XIAO ESP32S3 引脚 VIN 3.3V GND GND SDA D4 SCL D5 VOUT D0   如下图用杜邦线连接: 代码如下: // // FILE: MCP4725_wave_generator.ino // AUTHOR: Rob Tillaart // PURPOSE: demo function generators // URL: https://github.com/RobTillaart/MCP4725 // URL: https://github.com/RobTillaart/FunctionGenerator // // depending on the platform, the range of "smooth" sinus is limited. // other signals are less difficult so have a slightly larger range. // // PLATFORM SINUS SQUARE SAWTOOTH TRIANGLE // UNO -100 Hz // ESP32 -200 Hz -1000 -250 -100 // #include "MCP4725.h" #include "Wire.h" // frequency // use + - * / to control it uint16_t freq = 100; uint32_t period = 0; uint32_t halvePeriod = 0; // q = square z = zero // s = sinus m = mid // w = sawtooth h = high // t = stair // r = random char waveFrom = 's'; MCP4725 MCP(0x62); // 默认I2C地址为0x62 uint16_t count; uint32_t lastTime = 0; int sensorPin = A0; // select the input pin for the potentiometer int sensorValue = 0; // variable to store the value coming from the sensor // LOOKUP TABLE SINE uint16_t sine[361]; void setup() { // while(!Serial); Serial.begin(2000000); Serial.println(__FILE__); Serial.print("MCP4725_VERSION: "); Serial.println(MCP4725_VERSION); Serial.println(); Wire.begin(); // I2C 初始化 // Wire.setClock(3400000); // fill table for (int i = 0; i < 361; i++) { sine[i] = 2047 + round(2047 * sin(i * PI / 180)); } MCP.begin(); // MCP4725 初始化 Wire.setClock(800000); MCP.setValue(0); if (!MCP.isConnected()) { Serial.println("err"); while (1); } period = 1e6 / freq; halvePeriod = period / 2; while (1) { yield(); uint32_t now = micros(); count++; if (now - lastTime > 100000) { lastTime = now; // show # updates per 0.1 second // Serial.println(count); count = 0; if (Serial.available()) { int c = Serial.read(); switch (c) { case '+': freq++; break; case '-': freq--; break; case '*': freq *= 10; break; case '/': freq /= 10; break; case '0' ... '9': freq *= 10; freq += (c - '0'); break; case 'c': freq = 0; break; case 'A': break; case 'a': break; case 'q': case 's': case 'w': case 't': case 'r': case 'z': case 'm': case 'h': waveFrom = c; break; default: break; } period = 1e6 / freq; halvePeriod = period / 2; Serial.print(freq); // Serial.print('\t'); // Serial.print(period); // Serial.print('\t'); // Serial.print(halvePeriod); Serial.println(); } } uint32_t t = now % period; switch (waveFrom) { case 'q': if (t < halvePeriod ) MCP.setValue(4095); else MCP.setValue(0); break; case 'w': MCP.setValue(t * 4095 / period ); break; case 't': if (t < halvePeriod) MCP.setValue(t * 4095 / halvePeriod); else MCP.setValue( (period - t) * 4095 / halvePeriod ); break; case 'r': MCP.setValue(random(4096)); break; case 'z': // zero MCP.setValue(0); break; case 'h': // high MCP.setValue(4095); break; case 'm': // mid MCP.setValue(2047); break; default: case 's': // reference // float f = ((PI * 2) * t)/period; // MCP.setValue(2047 + 2047 * sin(f)); // int idx = (360 * t) / period; MCP.setValue(0.5*sine[idx]); // fetch from lookup table break; } sensorValue = analogRead(sensorPin); Serial.print("\t"); Serial.print(now); Serial.print("\t"); Serial.println(sensorValue, 1); } } void loop() { } // -- END OF FILE -- 大致流程为: 实验结果: 在Arduino中的Seril Plotter窗口可以看到正弦波的输出       五、连接WiFi,抓取网页信息 ESP32S3支持WIFI,可以连接WIFI路由的网络上,和其他设备通讯或连接到外部网络上。也可以设置为软路由,作为wifi热点,手机或其他设备连接到这热点上。这实验是使用ESP32连接wifi热点,并通过热点登录baidu官网,直接抓取首页内容。来验证ESP32的wifi功能。 大致流程如下:   代码如下: #include "WiFi.h" #include <WiFiClient.h> // Replace with your network credentials const char* ssid = "xxx"; // 更换你的路由器wifi名称 const char* password = "xxxxxx"; // 更换你的wifi密码 const char* host = "www.baidu.com"; // 目标服务器域名 const int httpPort = 80; // HTTP标准端口 void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(); Serial.println(WiFi.localIP()); } void sendClientRequest() { // 建立WiFi客户端对象 WiFiClient client; // 构造HTTP请求报文 String httpRequest = String("GET /") + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n" + "\r\n"; Serial.print("正在连接服务器:"); Serial.println(host); // 尝试连接服务器 if (client.connect(host, httpPort)) { Serial.println("连接成功!"); // 发送HTTP请求 client.print(httpRequest); Serial.println("发送的HTTP请求: "); Serial.println(httpRequest); Serial.println("服务器响应:"); // 等待并打印所有服务器返回的数据 while (client.connected() || client.available()) { if (client.available()) { String line = client.readStringUntil('\n'); Serial.println(line); } } client.stop(); Serial.print("已从服务器断开连接"); } else { Serial.println("连接服务器失败!"); client.stop(); } } void setup() { Serial.begin(115200); // Set WiFi to station mode and disconnect from an AP if it was previously connected WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); initWiFi(); sendClientRequest(); } void loop() { } 主要代码说明 1. WIFI连接: WiFi.mode(WIFI_STA) 设置为Station模式, WiFi.begin(ssid, password) 连接WIFI热点 2. httpclient 使用: WiFiClient client 声明客户端 client.connect    连接服务端 client.print 发送http请求 client.readStringUntil 读取http响应 client.stop 断开连接。 抓取内容如下可见附件 可以保存成html文件,并通过浏览器来访问这个文件,验证是否和百度首页的内容一致。   六、获取SHT45的温度和湿度信息并显示。 同样SHT45的使用,Arduino也提供相应的库,给我们参考和使用。我下载的是SHT4x的简易库。 SHT45同样通过I2C硬件和ESP32S3连接。硬件连接如下: SH45 引脚 XIAO ESP32S3 引脚 VIN 3.3V GND GND SDA D4 SCL D5   测试也比较简单,通过I2C连接SHT45模块,读取SHT45的温度和湿度值,并通过串口打印这2个值,可以在Arduino上显示这2个值的变化趋势。启动SHT4x库中的默认I2C地址为0x44,和购买的模块的默认地址完全一样。可以不设置SHTx的I2C地址。这样SHTx使用到指令如下: sht.begin() 初始化 sht.read() 启动读取,并缓存SHT45的寄存器数据 sht.getTemperature() 返回检测温度值 sht.getHumidity() 返回检测湿度值 代码如下: #include "Wire.h" #include "SHT4x.h" #define SHT_DEFAULT_ADDRESS 0x44 uint32_t start; uint32_t stop; SHT4x sht; void setup() { // while(!Serial); // uncomment if needed Serial.begin(115200); Serial.println(); Serial.println(__FILE__); Serial.print("SHT4x_LIB_VERSION: \t"); Serial.println(SHT4x_LIB_VERSION); Serial.println(); Wire.begin(); Wire.setClock(100000); sht.begin(); } void loop() { start = micros(); sht.read(); // default SHT4x_MEASUREMENT_SLOW and true for CRC check stop = micros(); Serial.print("\t"); Serial.print(stop - start); Serial.print("\t"); Serial.print(sht.getTemperature(), 1); Serial.print("\t"); Serial.println(sht.getHumidity(), 1); delay(100); } // -- END OF FILE -- 大致流程: 实验结果:(为了测试数据是否有变化,我用手轻按住芯片)   七、使用摄像头和麦克风 Seeed Studio XIAO ESP32S3套件的中摄像头 PDM麦克风和SD卡插槽组成独立模组,可和ESP32主板连接在一起。 1. 麦克风使用 PDM麦克风第引脚连接如下,ESP32可以通过I2S控制麦克风实现音频接收。可以将录制的音频,保存在SD卡中。 引脚编号 功能描述 GPIO 41 PDM 麦克风 DATA GPIO 42 PDM 麦克风 CLK 大致流程如下: 代码如下: #include "ESP_I2S.h" #include "FS.h" #include "SD.h" void setup() { // Create an instance of the I2SClass I2SClass i2s; // Create variables to store the audio data uint8_t *wav_buffer; size_t wav_size; // Initialize the serial port Serial.begin(115200); while (!Serial) { delay(10); } Serial.println("Initializing I2S bus..."); // Set up the pins used for audio input i2s.setPinsPdmRx(42, 41); // start I2S at 16 kHz with 16-bits per sample if (!i2s.begin(I2S_MODE_PDM_RX, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO)) { Serial.println("Failed to initialize I2S!"); while (1); // do nothing } Serial.println("I2S bus initialized."); Serial.println("Initializing SD card..."); // Set up the pins used for SD card access if(!SD.begin(21)){ Serial.println("Failed to mount SD Card!"); while (1) ; } Serial.println("SD card initialized."); Serial.println("Recording 20 seconds of audio data..."); // Record 20 seconds of audio data wav_buffer = i2s.recordWAV(20, &wav_size); // Create a file on the SD card File file = SD.open("/arduinor_rec.wav", FILE_WRITE); if (!file) { Serial.println("Failed to open file for writing!"); return; } Serial.println("Writing audio data to file..."); // Write the audio data to the file if (file.write(wav_buffer, wav_size) != wav_size) { Serial.println("Failed to write audio data to file!"); return; } // Close the file file.close(); Serial.println("Application complete."); } void loop() { delay(1000); Serial.printf("."); } 其中,主要有2部分操作,一个是PDM设置和数据PDM数据。一个是SD的文件操作。 i2s.setPinsPdmRx(42, 41),设置PIN模式为PDM接收。 i2s.begin(I2S_MODE_PDM_RX, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),设置I2S格式为PDM接收,16K采样率,16bit位宽,单声道。 i2s.recordWAV(20, &wav_size), 录制20s的wav格式音频。 SD.begin 启动SD卡。 SD.open("/arduinor_rec.wav", FILE_WRITE) 打开或创建文件 file.write(wav_buffer, wav_size) != wav_size) 将wav音频数据写入文件。 注意的是,我们需要使用 ESP-32 芯片的 PSRAM 功能,主要用于保存实验中的20s录音数据。可以在Tool中打开:   测试效果(可以查看录制的视频) 2. 摄像头使用 Seeed Studio XIAO ESP32S3的摄像头传感器为OV2640,分辨率 1600x1200。摄像头占用ESP32的14个PIN脚。具体如下: ESP32-S3 GPIO 摄像头 ESP32-S3 GPIO 摄像头 GPIO10 XMCLK GPIO11 DVP_Y8 GPIO12 DVP_Y7 GPIO13 DVP_PCLK GPIO14 DVP_Y6 GPIO15 DVP_Y2 GPIO16 DVP_Y5 GPIO17 DVP_Y3 GPIO18 DVP_Y4 GPIO38 DVP_VSYNC GPIO39 CAM_SCL GPIO40 CAM_SDA GPIO47 DVP_HREF GPIO48 DVP_Y9 esp32的摄像头主要使用的指令如下: esp_err_t esp_camera_init(const camera_config_t* config); 初始化摄像头 camera_fb_t* esp_camera_fb_get(); 获取指向帧缓冲区的指针,用于读取当前帧内容 void esp_camera_fb_return(camera_fb_t * fb); 返回帧缓冲区以便再次使用 实验的大致流程和麦克风的测试一样,大致流程就是将获取的图像保存到SD中。详细代码见附件。 #include "esp_camera.h" #include "FS.h" #include "SD.h" #include "SPI.h" #define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM #include "camera_pins.h" unsigned long lastCaptureTime = 0; // Last shooting time int imageCount = 1; // File Counter bool camera_sign = false; // Check camera status bool sd_sign = false; // Check sd status // Save pictures to SD card void photo_save(const char * fileName) { // Take a photo camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Failed to get camera frame buffer"); return; } // Save photo to file writeFile(SD, fileName, fb->buf, fb->len); // Release image buffer esp_camera_fb_return(fb); Serial.println("Photo saved to file"); } // SD card write file void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){ Serial.printf("Writing file: %s\r\n", path); File file = fs.open(path, FILE_WRITE); if(!file){ Serial.println("Failed to open file for writing"); return; } if(file.write(data, len) == len){ Serial.println("File written"); } else { Serial.println("Write failed"); } file.close(); } void setup() { Serial.begin(115200); while(!Serial); // When the serial monitor is turned on, the program starts to execute camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.frame_size = FRAMESIZE_UXGA; config.pixel_format = PIXFORMAT_JPEG; // for streaming config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; config.fb_location = CAMERA_FB_IN_PSRAM; config.jpeg_quality = 12; config.fb_count = 1; // if PSRAM IC present, init with UXGA resolution and higher JPEG quality // for larger pre-allocated frame buffer. if(config.pixel_format == PIXFORMAT_JPEG){ if(psramFound()){ config.jpeg_quality = 10; config.fb_count = 2; config.grab_mode = CAMERA_GRAB_LATEST; } else { // Limit the frame size when PSRAM is not available config.frame_size = FRAMESIZE_SVGA; config.fb_location = CAMERA_FB_IN_DRAM; } } else { // Best option for face detection/recognition config.frame_size = FRAMESIZE_240X240; #if CONFIG_IDF_TARGET_ESP32S3 config.fb_count = 2; #endif } // camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } camera_sign = true; // Camera initialization check passes // Initialize SD card if(!SD.begin(21)){ Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); // Determine if the type of SD card is available if(cardType == CARD_NONE){ Serial.println("No SD card attached"); return; } Serial.print("SD Card Type: "); if(cardType == CARD_MMC){ Serial.println("MMC"); } else if(cardType == CARD_SD){ Serial.println("SDSC"); } else if(cardType == CARD_SDHC){ Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } sd_sign = true; // sd initialization check passes Serial.println("Photos will begin in one minute, please be ready."); } void loop() { // Camera & SD available, start taking pictures if(camera_sign && sd_sign){ // Get the current time unsigned long now = millis(); //If it has been more than 1 minute since the last shot, take a picture and save it to the SD card if ((now - lastCaptureTime) >= 60000) { char filename[32]; sprintf(filename, "/image%d.jpg", imageCount); photo_save(filename); Serial.printf("Saved picture: %s\r\n", filename); Serial.println("Photos will begin in one minute, please be ready."); imageCount++; lastCaptureTime = now; } } } 拍照效果如下:   八、视频流测试 最后,可以通过 XIAO ESP32S3 Sense 创建的网页上查看实时视频流。这个项目来自CameraWebServer_for_esp-arduino_3.0.x 这个项目是一个综合的例子,用到ESP32S3的wifi和摄像头功能,并且它还有一个人脸识别的功能,但是我只是演示和了解了代码的大概功能。并没有深入学习。 总结 通过这次学习,了解到使用XIAO ESP32S3 Sense在arduino上开发的流程。arduino开发的确很便捷,里面有很多使用的库,像wifi,摄像头这些外设,可以轻松配置和使用。而像SHT45等这些小模块,就更方便了,也不需要先学习模块的芯片手册,只需要调用适当的库api就可以。这对于小白或者希望快速出成效的开发者都比较友好。 而 XIAO ESP32S3 Sense这个开发板小巧精致,但又五脏俱全。上面的wifi,摄像头,pdm麦克风都有相应的例程和学习资料。这次学习过程也很顺利。 视频链接如下:   代码链接如下:download.eeworld.com.cn/detail/latera/637870  

  • 上传了资料: [Follow me第三季第3期】 Seeed Studio XIAO ESP32S3 Sense 任务完结汇总

  • 2025-12-17
  • 回复了主题帖: 【Stratasys有奖直播】如何通过3D打印稳定交付小批量工程零件?

    报名

  • 2025-09-07
  • 上传了资料: 【Follow me第三季第2期】任务完结汇总

  • 回复了主题帖: 【回帖赢50元京东卡】机器人技术讨论篇(9月新活动)

    NanoEdgeAIStudio:面向 STM32 开发者的自动化机器学习工具 - DigiKey 电子百科 - DigiKey 技术论坛 - 电子物料和组件方案的专业技术论坛  

  • 2025-08-24
  • 发表了主题帖: [Follow me第三季第2期】任务完结汇总

    本帖最后由 latera 于 2025-9-13 17:25 编辑 任务汇总         首先再次感谢digikey的Follow me活动提供的这次学习MCXN947开发板的机会。在这次活动中,我对N947的基础架构,硬件外设都有一定的了解。并且通过尝试在MCUXpresso IDE上开发程序。而且尝试了恩智浦提供的AI实例,进一步学习到深度学习在嵌入式开发中的应用。 任务介绍 [localvideo]bc6c935be852430ff6f7be9763ccbe23[/localvideo]   任务说明      以下为我在论坛先后提交作品: 1. MCUXpresso IDE的环境搭建,开发板实现点灯和串口打印的基本功能。 【Follow me第三季第2期】搭建环境 调试示例程序 hello_world + 添加led_blinky (1)硬件 (2)功能说明  程序相对简单,主要通过串口打印信息。和通过3个GPIO控制板载上的三色灯。程序中通过简单的延时,切换三个灯的高低电平,来实现灯光闪烁的效果。 (3)学习心得 通过这次了解MCUxpresso开发环境和例程的导入方法。并且了解串口和GPIO的基本使用。   2. 通过串口实现控制台功能,还通过板载的P3T1755DP温度传感器获取环境温度。并且在文章中详细了解的N947启动文件结构和启动流程。 【Follow me第三季第2期】shell命令例程测试+温度获取 (1)硬件   (2)功能说明 文章详细分析了N947从上电到进入main函数的启动步骤。 从电路图开始说明串口的配置方法,并且分析示例中uart读写操作 和 调试打印输出输入的嵌套关系。 根据shell例程的步骤,实现在串口终端输入获取温度的命令后,在终端显示当前传感器检测的温度。传感器为P3T1755DP。使用I3C接口通讯。   (3)学习心得 了解到N947的M33启动,和之前cortex M4的启动方式基本相同。并对更熟悉了解到SDK配置外设的方法。   3. 恩智浦的AI例程测试N947的NPU功能。 Follow me第三季第2期】CIFAR10图像识别方案NPU测试 (1)硬件 (2)功能说明 这个例程使用Softmax分类器深度学习图像分类方法,数据集为CIFAR10,是例程训练好的,但也可用eiq软件,添加需要区分的物体图片,来重新训练出新的模型。 程序大致流程如下,将摄像头获取到的图片数据传给模型处理器,最终将结果显示在lcd上。 (3)学习心得 测试N947的LCD显示和摄像头数据获取的功能,基本了解的N947的机器学习上的应用。当然感觉这个图像识别的模型识别能力并不是很好。部分图片要识别多次才能识别正确。   4. 这是一个测试NPU的深度学习的Demo。使用MPU6050获取风扇转动时的六轴数据,通过训练模型了判断风扇是否异常。 【Follow me第三季第2期】风扇异常检测方案测试 (1)硬件 (2)功能说明 程序使用的是支持向量机深度模型来区分风扇是否正常运行,采集和分析数据来之MPU6050的六轴加速度角速度检测数据。并且程序支持在线训练数据。大致流程如下: (3)学习心得 学习到N947在线训练模型和模型推理的功能。对支持向量机(support vector machines, SVM)这种深度学习模型有了一定了解。   代码提交 代码同样上传到了         

  • 2025-08-07
  • 评论了课程: EEWORLD大学堂----直播回放: 利用MPS在线仿真软件轻松掌握电源环路参数设计

    谢谢分享

  • 2025-08-05
  • 回复了主题帖: Follow me第三季第2期】CIFAR10图像识别方案NPU测试

    xingkong911 发表于 2025-8-4 20:56 从Application Code Hub上面下的工程可以运行, 但是更新了训练数据提示不兼容, 请问你的SDK,eIQ ... MCXN947: How to Train and Deploy Customer ML model to NPU - NXP Community 训练数据可以看这个,里面有个视频

  • 2025-08-02
  • 回复了主题帖: Follow me第三季第2期】CIFAR10图像识别方案NPU测试

    xingkong911 发表于 2025-8-1 22:01 把这三个地方改了之后, LCD确实好了 但识别率还是有点低,不知道自带工程训练得不够还是怎么着 自带工程的确识别率不高,特别是青蛙

  • 2025-07-28
  • 发表了主题帖: 【Follow me第三季第2期】风扇异常检测方案测试

    本帖最后由 latera 于 2025-7-28 23:08 编辑 本文是测试NXP中的官方AI例程,这个例程是通过6轴姿态传感器来检查风扇转动,并通过支持向量机的机器模型来判断风扇是否异常,比如说停止转动,或是转动卡顿的异常情况。而且,这个例程的模型是直接在端侧,即N947开发板上训练模型。 以下为需要使用到的硬件 LCD显示屏 LCD-PAR-S035 MPU60506轴姿态传感器模块 FRDM-MCXN947开发板 电脑 小风扇   硬件连接 MPU6050的6轴姿态传感器模块是在网上购买。实际本例程中支持2中6轴姿态传感器,一个是MPU6050,另外还有FXLS8974CF。FXLS8974CF是NXP自己出品的传感器芯片,但是网上能购买到的现成模块比较少,而MPU6050就很常见。所有就直接选择了MPU6050。不过,无论是MPU6050还是FXLS89CF,都是使用板载的I2C接口,所以2种模块的连接到核心板的方式都一样。以下是我购买到的模块。 LCD模块依然使用NXP的开源硬件 LCD-PAR-S035显示屏。 硬件连接方式如下图,MPU6050的 VCC,GND,SCL,SDA,四个引脚分别通过杜邦线连接到开发板上的J9排座的(pin21,22,19,20)即可。 需要注意的是,因为开发板上的SDA和SCL连接上拉电阻(4.7k),6050模块是上也连接了上拉电阻,可能会影响通讯,所有我直接将模块上的上拉电阻去掉。   例程下载和编译 这个同样可以MCUXpresso IDE中的“Import from Application Code Hub...”直接导入。导入方式可以参考我上一篇文章。   也可以直接在github上下载,链接如下: https://www.github.com/NXP-APPCODEHUB/dm-on-device-training-fan-anomaly-on-mcxn947.git 下载后,工程如下: 同样,因为编译选项默认要求数据格式显式转换,和函数显式声明,不然编译报错。 可以按如下修改,修改内容在右侧 其中实际主要修改的只有main.c文件。修改后便可以编译成功。 测试发现,有时候,MPU6050初始化时不能正常读取6050的ID寄存器。所以我直接将判断ID的步骤去掉。如下修改mpu6050.c的代码: u8 MPU_Init(void) { u8 res; MPU_IIC_Init(); MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X81); u8 mgmt1 = MPU_Read_Byte(MPU_PWR_MGMT1_REG); delay_ms(100); MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00); MPU_Set_Gyro_Fsr(3); MPU_Set_Accel_Fsr(0); MPU_Set_Rate(SAMPLE_RATE); MPU_Write_Byte(MPU_INT_EN_REG,0X00); MPU_Write_Byte(MPU_USER_CTRL_REG,0X00); MPU_Write_Byte(MPU_FIFO_EN_REG,0X00); MPU_Write_Byte(MPU_INTBP_CFG_REG,0X80); res=MPU_Read_Byte(MPU_DEVICE_ID_REG); // if(res==MPU_ADDR) // 去除判断。 // { MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01); MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00); MPU_Set_Rate(SAMPLE_RATE); // }else return 1; MPU_Set_Fifo(ACCEL_FIFO_EN); return 0; } 实际程序中的主要步骤如下,程序开始新建三个主要任务,分别为是lvgl显示和触控处理的AppTask,负责算法的模型训练和结果预测的app_algo_task,还是完成传感器数据采集的app_sensor_task。程序主要对应2大功能:模型训练和结果预测显示。 示例演示 模型训练 [localvideo]f1ece599bfe1b82435e970737d3b9476[/localvideo] 风扇正常和异常预测结果演示 [localvideo]e763a17536d70b01d50ebeb4c4dea64f[/localvideo]     例程的AI模型说明 本例程中使用的模型为支持向量机(support vector machines, SVM)。支持向量机是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器。在本文的功能就是将区分风扇异常和正常,而对应的特征就是6轴姿态传感器的3轴加速度和3轴角速度。这6个数据(特征)组成一个向量(点)。模型通过收集这些大量的数据,并且标识出正常点和异常点,并找到一个最优超平面,将不同类别(本文中正常的6轴数据和异常6轴数据)的数据分开。在预测时,对获取到的新数据进行判断,是在超平面哪一侧。 并且,本文使用的是One-Class SVM 的异常检测算法。One-Class SVM是一种无监督算法,我理解就是不用外部去标识那些是异常点,那些是正常点,由模型自行判断,模型在训练中根据不断输入的数据,判断新进入的数据是否和之前以及采集到的数据是否区别很大,从而判断是否为异常点。 训练中,如何判断新采集是否区别很大,还是很小,就需要通过模型训练中2个超参数的设定: Gamma 这个参数会将数据集参数理解成多个数据簇,如风扇有2个档位,应该收集到的数据会趋向于2个范围。Gamma越大就越趋向于将数据理解成多个数据范围。 Nu 代表模型训练时的采集数据的灵敏度。Nu越大,训练数据中越容易得出异常数据。   以上学习内容,只是我对机器学习初浅的理解。还要继续学习才行。。  

最近访客

< 1/3 >

统计信息

已有24人来访过

  • 芯积分:97
  • 好友:--
  • 主题:13
  • 回复:14

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言