news 2026/9/13 21:04:38

Android APP控制51单片机智能小车:蓝牙串口通信与PWM调速实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android APP控制51单片机智能小车:蓝牙串口通信与PWM调速实战

简介:这是一个基于 Android 设计 APP 控制 51 单片机多功能智能小车的完整项目包,属于课程设计/单片机类高分资源,面向计算机、自动化、电子信息、物联网等专业在校生,以及需要完成毕设、课设或初期项目演示的开发者。包体共 133 个文件、压缩后约 3.74MB,核心内容包括 Android 客户端 Java/XML 源码、单片机端 C/H 底层驱动、可直接安装验证的 APK、工程配置文件以及说明文档,并附有界面截图与工程目录树,整体结构清晰,便于从 APP 界面到小车控制逻辑逐层对照学习。目前已有 114 人学习下载。该项目已通过 95 分答辩评审,核心代码经过运行测试,可直接在现有功能上修改扩展;对入门者而言,也能借助其中的前后端通信实现、硬件驱动接口和完整配置思路,快速理解 Android 与 51 单片机联合开发的典型流程。

1. 拿到这个高分项目,先看清“手机—蓝牙—单片机”这条链路

周五答辩前夜,手机蓝牙搜不到小车模块,51单片机主板上的电源灯却亮着,这是做“基于Android设计APP控制51单片机多功能智能小车”课程设计时几乎必然遇到的一幕。这个高分项目包里放着可直接安装的APK成品、Android工程缓存(resources.ap_、jarlist.cache这一类),以及单片机端的main.c源码和配套文档。资源拿过来之后,重点不是“能跑”,而是搞清楚Android Studio编译出来的APP、HC-05蓝牙模块、51单片机三者之间的通信链路,因为答辩老师最喜欢沿着这条链路往下问。下面按工程实际顺序拆解串口通信协议、PWM调速、Android蓝牙收发、Keil5烧录四个环节,并给出能直接抄的代码和调试参数,适合课设答辩党、单片机初学者和想把资料改造成毕业设计的读者。

2. 串口通信协议与蓝牙透传:帧同步、命令字与校验设计

2.1 为什么课程设计选“51 + HC-05 + Android SPP”

课程设计的选型逻辑很实际。51单片机(STC89C52或AT89S52)在智能小车项目里有三个不可替代的优势:寄存器少、开发资料多、Keil5 C51工具链稳定,对“多功能智能小车”这种低速移动平台,8位机处理一个蓝牙串口中断和两路PWM完全够用。Android端的作用是提供图形界面,把按钮点按转换成字节流;蓝牙模块只是透明传输通道,不解析内容,手机发给它的字节原样出现在51单片机串口,单片机回传的数据也原样回到手机。相比WiFi方案(ESP8266透传或自建TCP),蓝牙SPP方案不需要组网、不需要配置IP,HC-05模块十几块钱,属于课程设计普遍采用、答辩老师又最容易接受的方案。

文件里会出现两个APK(Kyr1eSmartCarControl.apk和Carcontrol.apk),以及两个同名main.c。经验来看,Kyr1eSmartCarControl.apk命名更完整,可以当作较新的版本先装;重复的main.c多半是固件在不同阶段留下的备份,解压后按文件修改时间选新的那份编译。项目里出现的resources.ap_、jarlist.cache、.classpath这些文件,是Eclipse ADT时代构建Android工程时生成的中间产物,拿到Android Studio里一般不会直接参与构建,所以不要被它们干扰。

2.2 指令帧定义:0xAA帧头、命令字、异或校验

51单片机串口收到的是一个字节一个字节进入SBUF的数据流,如果没有帧格式,一旦第一个字节丢失,后面所有指令都会错位。因此协议设计要解决三个问题:帧同步、命令表达、错误检测。常见做法是采用“帧头+命令字+数据区+校验字”的固定长度帧,本项目的蓝牙控制指令帧可以简化为4字节:

字节位置名称取值说明
0帧头FRAME_HEADER固定0xAA,接收方用它重新对齐同步
1命令字CMD0x00停止,0x01前进,0x02后退,0x03左转,0x04右转,0x05调速
2数据DATA速度值(0~100)或辅助参数,用不到时填0x00
3校验CHECK第1字节与第2字节异或,即CMD^DATA

校验字用异或而不是累加和,原因是单片机里异或运算只需要一条指令,累加和还要考虑进位处理,在中断里执行会拉长响应时间。帧头固定为0xAA是因为它的二进制是10101010,与常见的0xFF、0x00相比,更不容易在总线空闲态或乱码流中被误判。数据区虽然只有1字节,但足够表达0~100的速度档位,以及蜂鸣器、灯光这类扩展功能的开关状态。

生成帧的C代码放在51端:

#define FRAME_HEADER 0xAA #define CMD_STOP 0x00 #define CMD_FORWARD 0x01 #define CMD_BACKWARD 0x02 #define CMD_LEFT 0x03 #define CMD_RIGHT 0x04 unsigned char txFrame[4]; void buildFrame(unsigned char cmd, unsigned char data) { txFrame[0] = FRAME_HEADER; // 帧头 txFrame[1] = cmd; // 命令字 txFrame[2] = data; // 速度或辅助参数 txFrame[3] = cmd ^ data; // 异或校验 }

这段代码在后面调试时可以直接复制到Keil5里。需要注意,buildFrame只是做发往手机的回显或状态上报,真正控制电机不需要它;接收端校验逻辑更重要,见下一节。参数上,如果命令字和数据区的长度将来要扩展,帧格式从4字节改成6字节,状态机同步逻辑也要同步调整,不要只改一边。单片机端的串口初始化也要一并确认:

void UART_Init_9600() { SCON = 0x50; // 串口方式1,8位UART,REN=1允许接收 TMOD &= 0x0F; // 保留定时器0原有配置 TMOD |= 0x20; // 定时器1工作方式2:8位自动重装 TH1 = 0xFD; // 11.0592MHz晶振,9600波特率 TL1 = 0xFD; TR1 = 1; // 启动波特率发生器 ES = 1; EA = 1; }

重装值0xFD的来源是11.0592MHz晶振:这个频率专门为了让9600和115200等波特率的误差为0而设计,换成12MHz晶振后同样的0xFD会产生明显偏差,蓝牙透传后误码率剧增。因此main.c里的串口常量明确按11.0592MHz配置,后面提到的PWM定时器初值则按12MHz计算,两者并不冲突,因为用的是两个不同的定时器。

2.3 串口中断状态机:从一个字节到一个完整控制指令

接收端更需要在意字节边界。不要在中断里等整帧到达,否则缓冲区管理复杂而且容易死锁;常见做法是写一个状态机,每收到一个字节就改变一次状态。下面这段代码可以直接作为main.c的串口中断服务函数:

unsigned char rxBuf[4]; unsigned char rxState = 0; void UART_ISR() interrupt 4 { unsigned char byte; if (!RI) return; // 没有接收到数据 RI = 0; // 清接收中断标志 byte = SBUF; // 读串口数据 switch (rxState) { case 0: // 等待帧头 if (byte == 0xAA) { rxBuf[0] = byte; rxState = 1; } break; case 1: // 已收到帧头,等待命令字 rxBuf[1] = byte; rxState = 2; break; case 2: // 已收到命令字,等待数据区 rxBuf[2] = byte; rxState = 3; break; case 3: // 已收到数据区,等待校验并执行 rxBuf[3] = byte; if ((rxBuf[1] ^ rxBuf[2]) == rxBuf[3]) { executeCommand(rxBuf[1], rxBuf[2]); } rxState = 0; // 无论校验是否通过都回到空闲 break; } }

这段代码的核心思路是:只有当第一个字节匹配0xAA时才进入状态1,否则一直停留在状态0。蓝牙模块上电时偶尔会吐出几个乱码字节,状态机会自动丢弃它们,下一个正确帧头到来后立刻恢复同步,不需要在main函数里做复位处理。校验失败时直接丢弃整帧,不做任何容错性执行,目的是避免电机误动作造成安全隐患。状态机的性能足够,因为每收到一个字节最多执行4次比较和赋值,在11.0592MHz晶振下用时远小于下一位的到来间隔。

提示:如果把帧头改成0xFF这类全1字节,在总线空闲时可能被误认为有效数据,所以0xAA是更稳妥的选择。这也是很多串口协议把帧头设计成0xAA或0xA5的原因。

3. 51单片机 main.c 实战:PWM调速、指令执行与循迹避障

3.1 L298N电机驱动与定时器PWM调速参数计算

小车的动力部分通常用L298N驱动模块,既能驱动两个直流电机,又自带使能逻辑,输入引脚直接兼容TTL电平。L298N与51的接口常见接法是:P1口低四位接IN1~IN4,P1^4和P1^5分别接ENA、ENB。IN1和IN2控制左电机方向,IN3和IN4控制右电机方向,ENA和ENB用PWM波控制有效电平的时间比例。电机正转、反转和刹车的逻辑组合如下表:

功能IN1IN2IN3IN4ENA/ENB
前进1010PWM调速
后退0101PWM调速
左转1001两侧PWM差速
右转0110两侧PWM差速
刹车1111无PWM

PWM的频率不是越高越好,也不是越低越好。频率太高,电机线圈感抗会让平均电流上不去,驱动力不足;频率太低,电机会发出明显啸叫,而且小车运行不平稳。10ms周期、100个档位的方案,也就是100Hz的PWM,在低速小车上是个标准起点。定时器初值基于12MHz晶振计算:每档0.1ms,初值0xFF9C;如果换成11.0592MHz晶振,直接套用这个初值会产生定时误差,左右轮转速不一致的直接表现就是小车跑不直。

unsigned char pwmTick = 0; unsigned char pwmDutyLeft = 60; // 左轮占空比 60% unsigned char pwmDutyRight = 60; // 右轮占空比 60% void Timer0_ISR() interrupt 1 { TH0 = 0xFF; // 12MHz晶振,100us定时初值高位 TL0 = 0x9C; // 100us定时初值低位 pwmTick++; if (pwmTick >= 100) pwmTick = 0; // 100 * 100us = 10ms,PWM频率100Hz ENA = (pwmTick < pwmDutyLeft) ? 1 : 0; ENB = (pwmTick < pwmDutyRight) ? 1 : 0; }

这段代码把ENA和ENB当作普通IO口直接赋值,是51端最常见的做法。pwmDutyLeft和pwmDutyRight是全局变量,后面executeCommand里可以通过修改它们实现调速。参数调整的核心是左右轮对称性问题:同一个占空比下,左侧电机和右侧电机的机械特性可能不同,实际行驶会向一侧偏,所以要在实测中微调两个初值,而不是一味追求左右一致。调试时可以先在代码里把占空比固定为80,让小车直线跑一段距离,观察偏移方向后再回调。

3.2 executeCommand:把命令字转换成四个IO引脚电平

在2.3小节的校验成功分支里调用了executeCommand,这个函数才是真正驱动电机逻辑的地方。它做的事很简单:根据命令字设置IN1~IN4的电平,再按数据区内容更新PWM占空比。用switch比用一串if else更清晰:

void executeCommand(unsigned char cmd, unsigned char data) { switch (cmd) { case CMD_STOP: IN1 = 1; IN2 = 1; IN3 = 1; IN4 = 1; // 刹车 break; case CMD_FORWARD: IN1 = 1; IN2 = 0; IN3 = 1; IN4 = 0; if (data >= 10 && data <= 100) { // 数据保护 pwmDutyLeft = data; pwmDutyRight = data; } break; case CMD_BACKWARD: IN1 = 0; IN2 = 1; IN3 = 0; IN4 = 1; if (data >= 10 && data <= 100) { pwmDutyLeft = data; pwmDutyRight = data; } break; case CMD_LEFT: IN1 = 1; IN2 = 0; IN3 = 0; IN4 = 1; // 左轮正转,右轮反转 pwmDutyLeft = 40; pwmDutyRight = 60; break; case CMD_RIGHT: IN1 = 0; IN2 = 1; IN3 = 1; IN4 = 0; // 左轮反转,右轮正转 pwmDutyLeft = 60; pwmDutyRight = 40; break; default: break; } }

代码里的data范围保护值得单独说明。如果把手机端的调速滑块直连到PWM占空比,用户拖到0时电机完全截止,但拖到200时占空比超过100%,程序里如果没做边界处理,pwmDutyLeft就会大于pwmTick的判断范围,导致电机一直全速。把输入限制在10~100之间,既避免最小占空比下电机转不起来,又防止溢出,是工程实现里常见的“输入合理化”处理。

转向的实现方式有两种,一种是转弯半径较大的两轮同向差速,另一种是原地转向。上面左转代码让左轮正转、右轮反转,电机对顶,小车可以原地掉头,适合狭窄场地演示;但原地转向对电机和电源的瞬时电流冲击更大,电池电压不足时会出现突然复位。答辩演示场合建议把速度调低一些。

3.3 循迹、避障传感器如何并入现有主循环

“多功能”这三个字,通常落在一组传感器上:循迹用红外反射传感器,避障用红外避障模块或超声波测距。它们不需要占用串口,只需要占用几个普通IO口,所以可以在main函数的循环里轮询。以两路循迹传感器为例,接P2.0和P2.1,黑线反射率不同,传感器输出电平也不同,代码可以这样组织:

sbit TRACK_LEFT = P2^0; // 左循迹传感器 sbit TRACK_RIGHT = P2^1; // 右循迹传感器 void trackLine(void) { if (TRACK_LEFT == 1 && TRACK_RIGHT == 0) { // 左侧偏离黑线,向右纠偏 pwmDutyLeft -= 5; pwmDutyRight += 5; } else if (TRACK_LEFT == 0 && TRACK_RIGHT == 1) { // 右侧偏离黑线,向左纠偏 pwmDutyLeft += 5; pwmDutyRight -= 5; } if (pwmDutyLeft < 20) pwmDutyLeft = 20; if (pwmDutyRight > 100) pwmDutyRight = 100; }

这段代码只做增量调节,每次轮询只调整一个很小的量,避免小车在赛道上来回大幅摆头。实际项目中,循迹模式和蓝牙遥控模式一般不能同时生效,我会加一个mode全局变量:mode为0时蓝牙指令直接控制电机,mode为1时主循环只跑trackLine,蓝牙接收只用于切换模式。这个做法很朴素,但比在中断里切来切去可靠得多,也容易在答辩现场讲清楚。

4. Android端蓝牙控制:Android Studio导入与指令发送

4.1 解压后先区分APK与Android工程缓存文件

安卓端的文件需要分类看待。Kyr1eSmartCarControl.apk和Carcontrol.apk是编译好的安装包,想快速演示的直接装前者;resources.ap_、jarlist.cache、.classpath、index.db这些是Eclipse ADT或本地索引生成的中间产物,在Android Studio里打开工程时会被忽略一部分,但其中.classpath记录了原工程的源码目录和依赖库,如果原工程没有gradle配置,反而能从中看到src和libs的相对路径。我的常见做法是:在Android Studio里新建project,把原工程中com开头的Java目录、res目录、AndroidManifest.xml按原路径复制进去,再让Android Studio自动生成gradle配置。这个过程比折腾Eclipse工程导入快得多,也避开了旧构建工具的版本问题,对课程设计时间紧张的人更友好。

Android SDK的版本选择有讲究。targetSdkVersion如果定在22,蓝牙权限模型还是旧规则,在Android 12以上手机也能跑,但只能用于演示,无法上架应用市场;如果定在33,需要处理运行时权限。课程设计场景建议targetSdkVersion用33,正好把Android 12新的蓝牙权限模型一起演示出来,显得项目有更新价值。

4.2 Android 6到Android 13的蓝牙权限差异

蓝牙权限是这套代码里最容易让新手在答辩现场翻车的点。Android 6以上,扫蓝牙设备必须申请定位权限;Android 12以上,新的BLUETOOTH_SCAN和BLUETOOTH_CONNECT取代了旧的BLUETOOTH和BLUETOOTH_ADMIN。AndroidManifest.xml里需要同时保留两组权限,因为老版本系统不认识新权限,新版本系统又建议用新权限:

<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

权限申请不能只在Manifest里写,Android 6以上还需要运行时弹窗确认。Java里封装一个通用方法:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN }, 1001); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.ACCESS_COARSE_LOCATION }, 1002); } }

代码按系统版本分了两段,实际上Android 6~11走ACCESS_COARSE_LOCATION,Android 12以上走两个新蓝牙权限。需要注意,即使在Android 12上,如果应用没有声明neverForLocation标志,扫描蓝牙时系统仍可能把它解释为需要定位服务,所以部分测试机上还要同时打开系统的“位置信息”开关。这一点在演示前一定要确认,否则会出现“权限全给了,还是扫不到HC-05”的情况。

4.3 BluetoothSocket发送4字节控制帧的Java实现

蓝牙连接使用SPP协议,UUID 00001101-0000-1000-8000-00805F9B34FB是串口服务标准UUID。连接成功后,发送控制指令的核心代码与2.2小节的帧格式完全对应:

private BluetoothSocket socket; private OutputStream outputStream; public void sendCommand(byte cmd, byte data) { if (socket == null || !socket.isConnected()) { Log.e("CarControl", "蓝牙未连接,请先完成配对"); return; } byte[] frame = new byte[4]; frame[0] = (byte) 0xAA; // 帧头 frame[1] = cmd; // 命令字 frame[2] = data; // 数据区 frame[3] = (byte) (cmd ^ data); // 异或校验 try { outputStream.write(frame); outputStream.flush(); Log.d("CarControl", "TX: " + String.format("%02X %02X %02X %02X", frame[0], frame[1], frame[2], frame[3])); } catch (IOException e) { Log.e("CarControl", "发送失败: " + e.getMessage()); } }

sendCommand里先判断socket是否连接,再构造4字节帧,最后写入发送流。这里必须强调,write方法的参数是byte[],不能直接写字符串“AA015051”,否则手机端发出去的是一串ASCII字符0x41、0x41、0x30、0x31,51端按照0xAA帧头匹配时一个都匹配不上。车不动的时候,先通过logcat里打印的TX日志确认发出去的字节是不是AA开头,这个排查几乎能挡住一半的问题。

按钮与指令的映射关系建议在界面设计文档里画清楚,答辩时直接展示这张表:

按钮/操作命令字数据区完整帧(十六进制)
前进0x010x50AA 01 50 51
后退0x020x50AA 02 50 52
左转0x030x00AA 03 00 03
右转0x040x00AA 04 00 04
停止0x000x00AA 00 00 00
加速档0x050x14AA 05 14 11

从表里能看出,完整帧的计算并不需要电脑帮忙,异或结果当场手算也行。答辩时如果老师问“为什么前进是AA 01 50 51”,可以直接把0x01和0x50的二进制异或过程写出来,这比讲界面的按钮布局更有说服力。

5. Keil5 烧录、串口助手联调与参数对齐

5.1 Keil5 C51编译与STC-ISP烧录要点

Keil5默认安装时不带C51编译器,需要额外勾选C51支持包。新建工程后选择Atmel AT89C52或STC对应型号,选项里要打开“Create HEX File”,否则烧录软件拿不到可执行文件。烧录用STC-ISP,先选择芯片和COM口,点击下载后给目标板重新上电。整个过程最关键的是下载时序:STC单片机要在冷启动瞬间进入下载模式,如果板子上P3.0、P3.1接了蓝牙模块,下载时最好把蓝牙的RXD、TXD拔掉,避免模块拉低串口电平导致下载失败。

5.2 去掉蓝牙模块,先用PC串口助手验证固件

蓝牙模块容易被误判为故障点,但最常见的问题其实是它两侧的波特率不一致。有效的排错方法是把HC-05从电路上断开,用USB转TTL模块直接接51单片机串口。接线是USB转TTL的TXD接单片机P3.0,RXD接P3.1,GND共地。打开PC串口助手,设为9600波特率、8个数据位、1个停止位、无校验,发送十六进制帧AA 01 50 51。

如果小车前进,说明单片机端固件链路完整,问题只出在蓝牙模块或手机端;如果小车没反应,就要查串口中断是否配置正确、PWM占空比变量是否被意外清零。这里有个先后逻辑:先用PC串口助手绕开蓝牙,把固件链路隔离出来验证,再装回蓝牙模块,就不会在“手机问题还是固件问题”之间反复横跳,这也是整个项目调试里信息量最大的一个动作。

5.3 HC-05配对参数与联调排错顺序

HC-05有两种工作模式,AT模式和透传模式。按住模块上的按键再上电,指示灯慢闪进入AT模式,此时串口助手发送AT+UART=9600,0,0可以设置通信波特率,发送AT+NAME=Car51可以改蓝牙名称。如果不在AT模式下,手机串口助手里发出的字符会直接透传到51单片机,这相当于一个检测手段:向手机串口助手发一个0xAA,单片机端能通过状态机收到才算打通。

联调到这一步,把常见问题和处理顺序列成一张表,答辩现场照顺序排查思路很清晰:

现象排查点处理思路
手机搜不到HC-05模块是否进入配对模式重新上电,确认指示灯慢闪
能配对但车不动发送的帧格式对不对用蓝牙串口助手发AA 01 50 51验证
APP安装后闪退缺少运行时权限检查BLUETOOTH_CONNECT和定位权限
行驶轨迹明显跑偏左右轮PWM初值不一致在main.c中微调pwmDutyLeft/Right
负载时单片机复位电源电压不足换大容量电池,电机驱动与逻辑共地

最后补一个有价值的演示技巧:在executeCommand入口处增加一个帧计数变量frameCount,每收到一帧有效指令就自增,然后通过串口把计数回传给手机。

unsigned int frameCount = 0; void executeCommand(unsigned char cmd, unsigned char data) { frameCount++; // 帧计数,用于联调定位 // 原有电机控制逻辑保持不变 switch (cmd) { // ... } }

手机端logcat里看到回传计数持续增长,就能立刻区分“手机没发出去”“单片机没收到”“单片机收到但执行失败”三种情况。这个回传链路还可以延伸成状态上报:把电量、避障传感器状态一并编码进数据区,结尾就落在“收到一帧,回显一帧,链路状态一眼可知”这个调试闭环上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 21:04:26

四旋翼ADRC仿真:LADRC与ESO的Simulink实现与调参

简介&#xff1a;面向四旋翼无人机控制研究与开发者&#xff0c;这套工程包提供了完整的自抗扰控制&#xff08;ADRC&#xff09;实现方案&#xff0c;涵盖无人机动力学建模、扩展状态观测器设计、扰动抑制与稳定控制策略。压缩包共3个文件&#xff0c;体积仅113KB&#xff0c;…

作者头像 李华
网站建设 2026/9/13 21:04:16

从51到DSP:五套单片机示波器方案与设计要点全解析

简介&#xff1a;基于51、STM32、TMS320F28033和Arduino四种平台的示波器设计资料包&#xff0c;面向电子爱好者、单片机学习者以及需要完成课程设计或电子竞赛的开发者。资料提供五套完整方案&#xff0c;从OLED显示的51简易示波器&#xff0c;到STM32数字示波器、20MHz手持式…

作者头像 李华
网站建设 2026/9/13 21:02:47

8款高性价比一键生成论文工具横向实测,本硕博避坑全流程指南

前言&#xff1a;AI 写论文乱象频发&#xff0c;实测 8 款工具理清适配边界 每到毕业季&#xff0c;本科生、硕博生都会集中寻找 AI 论文辅助工具&#xff0c;市面各类写作软件层出不穷。然而&#xff0c;许多工具存在明显短板&#xff1a;虚假参考文献、无法匹配本校格式、不支…

作者头像 李华
网站建设 2026/9/13 20:58:28

ODOO采购七大核心流程落地实战指南

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

作者头像 李华
网站建设 2026/9/13 20:57:44

OpenAI Agents SDK 流式运行中途怎么取消并拿到已完成结果?

OpenAI Agents SDK 流式运行中途怎么取消并拿到已完成结果&#xff1f; 【免费下载链接】openai-agents-python A lightweight, powerful framework for multi-agent workflows 项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python 用 OpenAI Agent…

作者头像 李华