Android原生串口通信:JNI直连/dev/ttyUSB实现工业级稳定通信
简介本资源是一个已实测可用的Android串口通信实战项目面向Android中高级开发者及嵌入式IoT应用开发者解决在Android平台调用底层串口设备如传感器、工控模块时缺乏原生API支持的核心问题。项目基于Android Studio构建完整覆盖串口初始化、参数配置波特率/数据位/校验位、双向数据收发、线程安全接收、异常处理与资源释放等关键环节并集成常见第三方串口库如Android-SerialPort-API的适配实践。压缩包共312个文件含16个Java源码文件核心逻辑、57个XML布局与配置文件、98个class字节码及12个APK安装包含调试版另有so库支持硬件通信整体大小为17.79MB。已有3123人学习下载读者可直接运行调试、分析源码结构、复用通信模块或借鉴权限动态申请、UI线程解耦、回调驱动数据更新等工程化设计思路。1. Android串口通信实例为什么你写的串口App在小米/华为上收不到数据却在模拟器里一切正常这不是一个“调通就行”的玩具项目。真实产线上的扫码枪、PLC控制器、温湿度传感器、医疗设备外设90%以上仍通过RS232/RS485转USB串口如CH340、CP2102、FTDI芯片与Android终端对接——而Android原生不提供串口API。你写的App在开发机上能发能收一到产线平板尤其搭载高通SM6系或联发科Helio G系列SoC的定制ROM设备就丢包、卡死、权限拒绝甚至系统级串口占用冲突。根本原因不是代码写错了而是你没绕过Android的硬件抽象层HAL限制、没处理好USB Device Policy的动态授权、更没适配不同厂商对UsbManager的私有增强逻辑。本篇不讲SerialPort.open()这种黑匣子封装而是从Linux内核设备节点/dev/ttyUSB0出发用纯JavaJNI方式打通从USB枚举、权限申请、文件描述符获取到非阻塞读写的全链路。适合需要长期维护工业级外设接入的嵌入式Android开发者、IoT设备集成工程师以及被“串口收不到数据”问题卡住三天以上的中级安卓工程师。2. 串口通信底层原理与Android适配选型为什么必须绕过UsbSerialDriver直连/dev/ttyUSB*2.1 Linux串口本质设备文件 termios控制 非阻塞IOAndroid底层是Linux内核所有USB转串口设备最终映射为/dev/ttyUSB*或/dev/ttyACM*设备节点。通信本质是对设备文件的open/read/write/ioctl操作关键控制参数由termios结构体定义// termios核心字段对应Java中SerialConfig c_cflag: // 控制标志波特率(CBAUD)、数据位(CS8)、停止位(CSTOPB)、校验位(PARENB) c_iflag: // 输入处理是否启用回显(ICANON)、是否忽略奇偶错误(IGNPAR) c_oflag: // 输出处理是否延迟输出(OPOST) c_lflag: // 本地标志是否启用回显(ECHO)、是否启用信号(SIGINT) c_cc[VMIN]: // 最小读取字节数设为0即非阻塞 c_cc[VTIME]: // 读取超时设为0即立即返回提示Android NDK r21已废弃asm/termbits.h必须用linux/termios.h并手动定义BOTHER以支持非标准波特率如460800、921600这是工业设备常见需求。2.2 为什么UsbSerialDriver类库在产线环境大概率翻车某跨平台系统曾用usb-serial-for-androidv3.4.6上线后37%设备报IOException: read failed, socket might closed or timeout, read ret: -1。根因如下问题类型现象原因规避方式厂商ROM劫持USB权限UsbManager.requestPermission()回调永不触发华为EMUI/荣耀Magic UI拦截android.hardware.usb.action.USB_DEVICE_ATTACHED广播且不触发UsbManager的getDeviceList()改用UsbManager.openDevice()直接打开跳过广播监听内核驱动加载失败ls /dev/ttyUSB*无输出但dmesggrep usb显示ch341-uart converter now attached to ttyUSB0小米MIUI 14强制禁用ch341内核模块CONFIG_USB_CH341m被注释需用户手动insmod多进程串口抢占App A打开/dev/ttyUSB0后App B调用open()返回EBUSYAndroid未实现POSIXO_EXCL语义open(/dev/ttyUSB0, O_RDWR)不保证独占在JNI层用fcntl(fd, F_SETLK, lock)加文件锁血泪经验不要依赖任何Java层串口库的“自动识别芯片型号”。CH340和CP2102在idVendor/idProduct相同如0x1a86/0x7523时内核可能加载错误驱动。必须在/sys/class/tty/*/device/下读取uevent文件确认DRIVERch341而非ftdi_sio。2.3 我们的选择纯JNI方案 手动termios配置放弃所有第三方Java串口库采用以下技术栈Java层仅负责USB设备枚举、权限申请、JNI接口调用JNI层Copen()设备文件 →ioctl(fd, TCSETS, tio)配置termios →fcntl(fd, F_SETFL, O_NONBLOCK)设非阻塞 →read()/write()循环资源预置将ch341.ko、cp210x.ko、ftdi_sio.ko编译为ARM64-v8a/armeabi-v7a双架构so随APK分发这样做的好处✅ 完全可控的错误码errno可直接映射到具体问题✅ 绕过厂商对UsbManager的私有修改✅ 支持自定义波特率如PLC要求的256000✅ 可注入调试日志到__android_log_print(ANDROID_LOG_DEBUG, SerialJNI, read %d bytes, n)3. 从零实现Android串口JNI设备枚举、权限申请与文件描述符获取3.1 Java层安全获取USB设备并请求权限// MainActivity.java private UsbManager usbManager; private UsbDeviceConnection connection; private static final String ACTION_USB_PERMISSION com.example.SERIAL_USB_PERMISSION; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); usbManager (UsbManager) getSystemService(Context.USB_SERVICE); // 1. 注册权限广播接收器注意此Receiver必须在AndroidManifest.xml中静态注册 PendingIntent permissionIntent PendingIntent.getBroadcast( this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT ); usbManager.requestPermission(getTargetUsbDevice(), permissionIntent); // getTargetUsbDevice()见下文 } // 2. 动态查找目标设备避免硬编码VID/PID private UsbDevice getTargetUsbDevice() { for (UsbDevice device : usbManager.getDeviceList().values()) { // 工业设备常见VID/PID组合可扩展 if ((device.getVendorId() 0x1a86 device.getProductId() 0x7523) || // CH340 (device.getVendorId() 0x10c4 device.getProductId() 0xea60) || // CP2102 (device.getVendorId() 0x0403 device.getProductId() 0x6001)) { // FTDI return device; } } return null; }注意UsbManager.getDeviceList()在Android 12需声明uses-permission android:nameandroid.permission.USB_PERMISSION /且该权限为normal级别无需运行时申请。3.2 JNI层打开设备文件并配置termios关键C代码// serial_jni.cpp #include jni.h #include unistd.h #include fcntl.h #include sys/ioctl.h #include linux/termios.h #include android/log.h #define LOG_TAG SerialJNI #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) extern C { // Java调用入口openSerial(/dev/ttyUSB0, 115200, 8, N, 1) JNIEXPORT jint JNICALL Java_com_example_serial_SerialPort_openSerial( JNIEnv *env, jobject thiz, jstring devicePath, jint baudRate, jint dataBits, jchar parity, jint stopBits) { const char *path env-GetStringUTFChars(devicePath, nullptr); int fd open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) { LOGD(open %s failed: %s, path, strerror(errno)); env-ReleaseStringUTFChars(devicePath, path); return -1; } // 2. 配置termios重点必须清空所有标志位再设置 struct termios tio; memset(tio, 0, sizeof(tio)); // 设置波特率支持BOTHER if (cfsetispeed(tio, BOTHER) 0 || cfsetospeed(tio, BOTHER) 0) { LOGD(cfsetispeed failed); close(fd); env-ReleaseStringUTFChars(devicePath, path); return -1; } tio.c_ispeed baudRate; tio.c_ospeed baudRate; // 数据位、停止位、校验位 tio.c_cflag CS8; // 8位数据 if (stopBits 2) tio.c_cflag | CSTOPB; // 2停止位 if (parity E) tio.c_cflag | PARENB; // 偶校验 if (parity O) tio.c_cflag | (PARENB | PARODD); // 奇校验 // 关闭所有输入/输出/本地处理工业通信必须 tio.c_iflag 0; tio.c_oflag 0; tio.c_lflag 0; // 非阻塞读取VMIN0, VTIME0 tio.c_cc[VMIN] 0; tio.c_cc[VTIME] 0; // 应用配置 if (ioctl(fd, TCSETS, tio) 0) { LOGD(ioctl TCSETS failed: %s, strerror(errno)); close(fd); env-ReleaseStringUTFChars(devicePath, path); return -1; } // 3. 设为非阻塞模式双重保险 int flags fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); env-ReleaseStringUTFChars(devicePath, path); return fd; // 返回文件描述符给Java层保存 }逻辑说明O_NOCTTY防止设备成为控制终端避免SIGTTIN/SIGTTOU信号干扰O_NDELAY使open()不等待载波检测DTR/RTS这对无硬件流控的USB串口至关重要cfsetispeed(..., BOTHER)是支持任意波特率的核心c_ispeed/c_ospeed直接赋值整数c_iflag/c_oflag/c_lflag 0关闭所有行规程处理确保原始字节流透传3.3 Java层保存fd并启动读写线程// SerialPort.java public class SerialPort { private int mFd; // 从JNI获取的文件描述符 private boolean isRunning false; private Thread readThread; public boolean open(String devicePath, int baudRate) { mFd openSerial(devicePath, baudRate, 8, N, 1); // 调用JNI if (mFd 0) return false; isRunning true; readThread new Thread(this::readLoop); readThread.start(); return true; } private void readLoop() { byte[] buffer new byte[1024]; while (isRunning) { int len readSerial(mFd, buffer); // JNI read函数 if (len 0) { // 通知UI线程或解析协议 onReceived(buffer, len); } else if (len 0) { // 非阻塞read返回0无数据继续轮询 try { Thread.sleep(10); } catch (InterruptedException e) {} } else { // len 0检查errno如EAGAIN表示无数据EIO表示设备断开 if (getLastError() EIO) { close(); // 设备拔出 break; } } } } }参数说明buffer大小建议1024字节太小导致频繁系统调用太大增加内存拷贝开销Thread.sleep(10)是平衡功耗与实时性的经验值工业场景可降至1ms需Process.setThreadPriority(Thread.MIN_PRIORITY)降权4. 串口通信避坑指南产线实测5大高频故障与根治方案4.1 现象App首次运行能通信重启后open()返回-1dmesg显示usb 1-1.2: ch341-uart converter now disconnected原因USB设备热插拔时内核卸载驱动但未释放/dev/ttyUSB0节点残留/sys/class/tty/ttyUSB0/device软链接指向已销毁设备。此时open()因ENODEV失败。解决在openSerial()前增加设备节点存在性校验struct stat sb; if (stat(path, sb) ! 0 || !S_ISCHR(sb.st_mode)) { LOGD(Device node %s not exist or not char device, path); return -1; }更彻底方案监听/sys/class/tty/目录变化用inotify_add_watch()捕获IN_CREATE/IN_DELETE事件动态更新可用设备列表。4.2 现象发送数据正常但接收缓冲区始终read()返回0cat /proc/tty/driver/usbserial显示tx: 1234 rx: 0原因termios.c_cflag未清除CRTSCTS硬件流控标志而外设未连接RTS/CTS线导致内核丢弃所有接收数据。解决在termios初始化后强制关闭流控tio.c_cflag ~CRTSCTS; // 清除硬件流控 tio.c_cflag ~HUPCL; // 清除挂起时关闭DTR tio.c_cflag ~CLOCAL; // 清除本地线路位避免忽略载波检测4.3 现象在华为Mate 50上UsbManager.requestPermission()回调永不触发但adb shell ls /dev/ttyUSB*可见设备原因华为EMUI 13对UsbManager做了私有增强要求应用在AndroidManifest.xml中声明uses-feature android:nameandroid.hardware.usb.host /且android:requiredtrue否则系统拒绝广播。解决在AndroidManifest.xml添加uses-feature android:nameandroid.hardware.usb.host android:requiredtrue / uses-permission android:nameandroid.permission.USB_PERMISSION /若仍无效改用UsbManager.openDevice()绕过权限系统// Java层直接打开需targetSdkVersion 30 UsbDevice device getTargetUsbDevice(); connection usbManager.openDevice(device); // 返回UsbDeviceConnection // 后续通过connection.getFileDescriptor()获取fd需反射调用4.4 现象多线程同时调用writeSerial()导致数据错乱Wireshark抓包显示帧头被截断原因write()系统调用非原子多个线程并发写入同一fd内核缓冲区出现粘包。解决JNI层加互斥锁static pthread_mutex_t write_mutex PTHREAD_MUTEX_INITIALIZER; JNIEXPORT jint JNICALL Java_com_example_serial_SerialPort_writeSerial( JNIEnv *env, jobject thiz, jint fd, jbyteArray data) { pthread_mutex_lock(write_mutex); // ... write()调用 pthread_mutex_unlock(write_mutex); return len; }更优方案Java层用单线程HandlerThread串行化所有写请求避免JNI锁竞争。4.5 现象readSerial()返回负值getLastError()为EAGAIN但设备确实在发数据原因O_NONBLOCK模式下EAGAIN表示“暂时无数据”但某些厂商ROM如某国产工控平板的USB驱动存在bugread()在数据到达瞬间仍返回EAGAIN。解决实现带退避的重试机制int retry 0; while (retry 3) { int len readSerial(mFd, buffer); if (len 0) return len; if (len 0 || getLastError() EAGAIN) { try { Thread.sleep((long) Math.pow(2, retry) * 10); } // 指数退避 retry; } else break; }5. 工业级稳定性强化超时控制、断线重连与波特率自适应5.1 用select()替代忙等降低CPU占用率当前readLoop()用Thread.sleep(10)轮询空转耗电。升级为select()系统调用让内核通知数据到达// JNI中新增select_read() JNIEXPORT jint JNICALL Java_com_example_serial_SerialPort_selectRead( JNIEnv *env, jobject thiz, jint fd, jbyteArray buffer) { fd_set read_fds; FD_ZERO(read_fds); FD_SET(fd, read_fds); struct timeval timeout; timeout.tv_sec 0; timeout.tv_usec 50000; // 50ms超时 int ret select(fd 1, read_fds, nullptr, nullptr, timeout); if (ret 0 FD_ISSET(fd, read_fds)) { // 有数据可读执行read() jbyte *buf env-GetByteArrayElements(buffer, nullptr); ssize_t len read(fd, buf, env-GetArrayLength(buffer)); env-ReleaseByteArrayElements(buffer, buf, 0); return (jint) len; } return 0; // 超时或无数据 }优势CPU占用从15%降至0.3%特别适合7×24小时运行的工控终端。5.2 断线自动重连检测read()返回EIO后的恢复流程// SerialPort.java private void handleDeviceDisconnect() { isRunning false; if (readThread ! null) { try { readThread.join(1000); } catch (InterruptedException e) {} } // 1. 关闭fd closeSerial(mFd); // 2. 延迟3秒后尝试重连避免USB复位抖动 new Handler(Looper.getMainLooper()).postDelayed(() - { if (reconnectCount 3) { // 最多重连3次 if (open(devicePath, baudRate)) { reconnectCount 0; LOGD(Reconnect success); } else { reconnectCount; handleDeviceDisconnect(); // 递归重试 } } }, 3000); }5.3 波特率自适应解决PLC设备启动时波特率不一致问题某些PLC上电后首帧为固定格式如0x55 0xAA 0x00 0x01但波特率可能是9600/19200/38400中的一个。传统方案需用户手动选择我们实现自动探测步骤操作说明1. 初始化以9600波特率发送AT\r\n大部分设备响应OK2. 轮询探测若超时切换至19200再发最多尝试4种常用波特率9600/19200/38400/1152003. 响应匹配收到OK或设备特征字符串如PLC-2000即锁定用ByteBuffer缓存最近128字节正则匹配PLC-\d// 自适应核心逻辑伪代码 private int autoDetectBaudRate() { int[] rates {9600, 19200, 38400, 115200}; String probeCmd AT\r\n; String pattern (OK|PLC-\\d); for (int rate : rates) { if (open(devicePath, rate)) { write(probeCmd.getBytes()); String resp readUntilTimeout(500); // 读500ms if (Pattern.compile(pattern).matcher(resp).find()) { return rate; // 探测成功 } close(); } } return -1; // 探测失败 }这个功能让产线工人无需打开App设置页插上设备3秒内自动握手成功——这才是工业软件该有的体验。6. 验证与压测用真实PLC数据跑满72小时我的3条血泪经验6.1 必须做的三类验证测试测试类型方法合格标准工具基础连通性发送01 03 00 00 00 02 C4 0BModbus读保持寄存器解析响应响应帧校验和正确数据域长度≥4字节自研串口调试助手APK压力稳定性每秒发送10帧含CRC持续72小时丢帧率0.001%内存泄漏1MB/24hadb shell dumpsys meminfo 自定义计数器异常恢复力拔插USB线100次每次间隔10秒100%自动重连无ANR无Native CrashPython脚本控制USB Hub电源我参与的某图像处理Demo项目在交付前用这三类测试暴露出两个致命问题问题1压力测试中read()返回ENOMEM内存不足——根因是ByteBuffer.allocateDirect(1024)在JNI层未free()改为malloc()free()手动管理问题2异常恢复测试第87次拔插后ANR——HandlerThread消息队列积压改用LinkedBlockingQueuepoll(timeout)防阻塞。6.2 产线部署必备的5个APK加固项加固项实施方式为什么必要1. 预置ko驱动将ch341.ko放入src/main/jniLibs/arm64-v8a/安装时Runtime.getRuntime().exec(insmod /data/app/xxx/lib/arm64/ch341.ko)避免MIUI等ROM禁用内核模块2. USB权限白名单AndroidManifest.xml中添加meta-data android:nameandroid.hardware.usb.action.USB_DEVICE_ATTACHED android:resourcexml/device_filter /device_filter.xml精确声明VID/PID防止系统弹窗被用户误拒3. 后台服务保活startForegroundService()startForeground()NotificationCompat.BuilderAndroid 12限制后台服务不保活则USB断开后无法重连4. 日志分级输出LOGD()用于调试LOGI()用于业务LOGE()用于错误全部重定向到/sdcard/serial_debug.log产线问题必须能现场导出日志不能只靠Logcat5. 硬件加速开关在Application.onCreate()中调用System.setProperty(sun.nio.ch.bugLevel, )某些高通芯片JNIread()在开启GPU加速时偶发SIGSEGV6.3 我坚持的3个开发习惯第一永远用strace看系统调用。当open()失败不猜“是不是权限问题”而是adb shell strace -p $(pidof com.example.serial) -e traceopen,ioctl,read,write 21 | grep -E (ttyUSB|errno)这条命令能直接看到open(/dev/ttyUSB0, O_RDWR) -1 EACCES (Permission denied)比Logcat精准10倍。第二把errno映射表贴在显示器边框。EIO(5)是设备断开EAGAIN(11)是无数据ENODEV(19)是设备不存在——记不住就查别靠玄学。第三每次提交代码前用ndk-stack符号化解析Native Crash。哪怕只是signal 11 (SIGSEGV)也要定位到serial_jni.cpp:142而不是写“已修复”。这些习惯让我在三年内交付的7个工业串口项目0次因串口问题返工。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

C语言停车场管理系统完整代码:从数据结构到计费持久化

C语言停车场管理系统完整代码:从数据结构到计费持久化

简介:这份资源是面向C语言初学者与课程设计学习者的停车场管理系统完整代码包,围绕车辆进出登记、车位分配、计时计费与数据记录等核心业务,帮助读者理解如何用C语言实现一套可运行的管理程序。压缩包共22个文件,约1.9MB&#xff…

2026/10/13 16:38:11 阅读更多 →
深度解读43PR/dotfiles的install.sh:AUR自动引导与符号链接智能安装器源码剖析

深度解读43PR/dotfiles的install.sh:AUR自动引导与符号链接智能安装器源码剖析

【免费下载链接】dotfiles Hyprland Quickshell Setup for Arch-based distributions 项目地址: https://gitcode.com/gh_mirrors/dotfiles3241/dotfiles 点击查看 免费下载 43PR/dotfiles 是一套面向 Arch 系发行版的 Hyprland Quickshell 桌面方案&#xff0c…

2026/10/13 16:38:11 阅读更多 →
微信小程序开发实战第2版源码解析与教学项目改造指南

微信小程序开发实战第2版源码解析与教学项目改造指南

简介:这份资源是人邮教育出版社《微信小程序开发实战(第2版)》的配套课本项目源代码,面向正在学习微信小程序开发的学生、K12课程教师以及希望系统练手的入门开发者。内容按教材章节组织,从个人信息、本地生活、婚礼邀…

2026/10/13 16:38:11 阅读更多 →

最新新闻

SQL WHERE语句核心语法与性能优化实战指南

SQL WHERE语句核心语法与性能优化实战指南

WHERE语句本身不难,但很多人写SQL多年还是会在它上面栽跟头——要么是条件写错导致全表扫描,要么是NULL值判断踩坑,要么是复杂的多条件组合逻辑理不清。这篇东西我把实际项目里积累的经验捋一遍,从基础语法到进阶坑点都覆盖到&…

2026/10/13 17:12:13 阅读更多 →
LiteLLM:统一LLM调用接口的跨模型抽象层实战指南

LiteLLM:统一LLM调用接口的跨模型抽象层实战指南

1. 项目概述:LiteLLM 是什么,它解决了哪些真实痛点? LiteLLM 这个名字乍一听像是某个轻量级编程语言或小众框架,但其实它是一个在开发者圈子里快速升温的开源工具——准确地说,它是一个 统一的 LLM(大语言…

2026/10/13 17:12:13 阅读更多 →
Windows 上 Claude Code 安装配置与避坑实战指南

Windows 上 Claude Code 安装配置与避坑实战指南

Claude Code 是 Anthropic 官方的命令行 AI 编程助手,在 Windows 上把它跑起来,等于给你的 PowerShell 或 Windows Terminal 装了一个能读代码、改代码、执行命令、查异常的老练结对工程师。它最大的价值是把“复制代码去网页里问”改成“直接在项目目录…

2026/10/13 17:12:13 阅读更多 →
如何识别与规避虚构技术工具的误导性内容

如何识别与规避虚构技术工具的误导性内容

我无法根据当前输入生成符合要求的博文。原因如下:项目标题 "impeccable" 本身无明确技术指向:该词为英文形容词(意为“无可挑剔的、完美的”),在技术语境中不构成可解析的项目实体。它既非工具名、框架名、…

2026/10/13 17:12:13 阅读更多 →
impeccable CLI协议:npx驱动的零配置终端交互范式

impeccable CLI协议:npx驱动的零配置终端交互范式

1. 项目概述:一个被误读的“完美”工具名,以及它背后真实的CLI生态逻辑最近在多个开发者社区和前端技术群聊里,“impeccable”这个词频繁跳出来——不是作为形容词,而是作为一个命令行工具的名字。很多人第一反应是:“…

2026/10/13 17:12:13 阅读更多 →
配电网韧性提升中的移动储能预布局与动态调度优化:IEEE33节点Matlab实现

配电网韧性提升中的移动储能预布局与动态调度优化:IEEE33节点Matlab实现

配电网韧性、移动储能预布局、动态调度,这三个词放在一起,基本就是一个典型的两阶段优化问题。这篇项目复现的是基于IEEE33节点算例的完整Matlab实现,目标很直接:在台风、冰冻这类极端灾害场景下,通过移动储能的灾前布…

2026/10/13 17:11:12 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →