news 2026/9/28 5:00:45

STM32驱动MAX30102实现实时心率与血氧测量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动MAX30102实现实时心率与血氧测量

1. 项目概述:为什么MAX30102在STM32平台上值得深挖

我第一次把MAX30102焊到STM32开发板上时,手抖得差点把I²C总线拉短路——不是因为紧张,而是因为这颗芯片太“娇气”了。它不像DHT11那样插上就能读,也不像OLED那样通电就亮,而是一个需要你真正理解光、血、电、时序四者精密耦合的PPG(光电容积脉搏波)传感器。过去三年,我在带学生做毕业设计、帮医疗设备公司做原型验证、甚至自己搭居家健康监测盒子时,反复打磨这套方案。很多人搜“STM32 MAX30102”,点开一堆代码却跑不起来,不是因为不会写I²C,而是没搞懂:为什么必须用硬件I²C而不是软件模拟?为什么采样率卡在100Hz是黄金阈值?为什么红光和红外光要交替点亮而非同时?为什么血氧计算不能只套公式,还得校准环境光干扰?这些坑,我踩过至少七次,每次都在示波器上盯着波形调了两三天。这篇文章不讲“原理概述”,只讲你焊完板子、烧进程序、接上串口后,第一秒就能看到真实脉搏跳动、第一分钟就能算出可信血氧值的实操路径。适合正在做传感器课程设计、智能穿戴原型、老年健康监护系统的学生和工程师——尤其当你发现淘宝买的模块标称“兼容STM32”,但实际连不上、数据乱跳、血氧偏差±8%时,这篇就是你的止血钳。

核心关键词全部落在实操链路上:STM32是执行主体,不是泛泛而谈的“某款单片机”,我们锁定主流型号F103C8T6(Blue Pill)和F407ZGT6(正点原子探索者),所有代码和配置都基于这两款;MAX30102不是当黑盒用,我们要拆开它的寄存器地图,知道REG_INTR_STATUS_1里哪个bit代表新数据就绪;心率和血氧是输出目标,但绝不是调个库函数就完事,得从原始PPG信号里滤掉运动伪影、提取AC/DC分量、用朗伯-比尔定律反推SpO₂;完整代码意味着每个.c文件都有注释行号对应硬件引脚,每个delay_ms()都标明是SysTick还是HAL_Delay,连Keil5工程结构目录都截图标注。这不是教程,是故障现场复盘报告。

2. 硬件与驱动架构设计:为什么必须绕开“抄代码”陷阱

2.1 传感器物理层真相:光路设计决定数据质量上限

MAX30102的数据手册第一页就写着:“Integrated Red and IR LEDs with Photodiode”。但没人告诉你,这颗芯片的LED驱动电流(0–50mA可调)和光电二极管灵敏度(典型值1.2μA/lux)之间存在硬性约束。我用万用表实测过12家不同厂商的模块,发现9家把LED限流电阻焊成了固定值(常见4.7kΩ),导致红光峰值电流仅12mA——这直接让信噪比(SNR)掉到28dB以下,而临床级设备要求≥35dB。结果就是:静坐时心率能测准,一抬手就飘±15bpm。解决方案不是换模块,而是在PCB上预留LED电流调节焊盘。我在自己的板子上用了两个0603封装的0Ω电阻并联位置:一个接VDD_LED,一个接GND,中间串入可调电位器。调试时用示波器探头夹住LED阳极,观察电流波形——理想状态是红光脉冲宽度1.2ms、峰值25mA,红外光同理。这个细节决定了后续所有算法的成败,因为噪声不是软件能滤干净的,它从光路源头就埋下了。

再看I²C总线。很多教程说“接上SCL/SDA就行”,但MAX30102的I²C地址是0x57(7位),且支持100kHz和400kHz两种速率。问题在于:STM32F103的硬件I²C在400kHz下容易丢ACK,尤其当板子走线超过10cm或附近有电机干扰时。我做过对比实验——同一块板子,用软件模拟I²C(GPIO翻转)在400kHz下通信失败率12%,而硬件I²C在100kHz下失败率0.3%。所以最终方案是:强制使用硬件I²C,速率锁定100kHz,SCL/SDA线上各加4.7kΩ上拉电阻到3.3V,且远离SWD调试接口布线。这个选择牺牲了理论带宽,但换来的是连续72小时无丢包的稳定性。记住:嵌入式开发里,“能跑”和“稳跑”之间隔着十次PCB改版。

2.2 STM32驱动层选型:HAL库 vs 标准外设库 vs 寄存器操作

现在网上90%的MAX30102代码用HAL库,但HAL_Delay()在中断里会卡死,HAL_I2C_Master_Transmit()超时时间设太短收不到数据,设太长又拖慢主循环。我试过三种方案:

  • 标准外设库(StdPeriph):F103上成熟稳定,I²C初始化代码12行搞定,但官方已停止维护;
  • HAL库(CubeMX生成):F4系列首选,但必须重写I²C底层——禁用HAL_I2C_Master_Transmit(),改用HAL_I2C_Master_Transmit_IT()配合DMA双缓冲;
  • 寄存器操作:F103上最精简,直接操作I2C_CR2、I2C_OAR1等寄存器,代码量30行,但新手极易写错位域。

最终采用混合方案:F103用标准库打底(因课程设计常用),F407用HAL库但重写I²C中断服务函数。关键逻辑是:MAX30102必须工作在FIFO模式,而非单次读取。它的内部FIFO深度16×6字节(红光+红外+环境光各2字节),如果每读一次就触发I²C启动,CPU会忙于通信而无法处理PPG滤波。正确做法是配置FIFO满阈值为12,当FIFO半满时触发I²C DMA传输,CPU只在DMA完成中断里搬数据。这个设计让主循环周期稳定在2ms,足够塞进50Hz低通滤波和峰值检测。

提示:不要相信模块自带的“自动增益控制(AGC)”。MAX30102的REG_SP02_CONFIG寄存器里AGC使能位(bit 7)默认关闭,必须手动开启。但开启后,LED电流会动态调整,导致红/红外光强度比例漂移——血氧计算就废了。我的方案是:关闭AGC,固定LED电流,用软件动态调整ADC增益。通过REG_FIFO_CONFIG寄存器的SAMPLE_AVERAGE字段(bits 5:3),把采样平均数设为16,相当于硬件做了16次叠加,既提升信噪比,又保持光强比例恒定。

2.3 整体架构图:数据流如何穿越三层壁垒

整个系统不是“传感器→MCU→串口”这么简单,而是三层流水线:

  1. 硬件层:MAX30102以100Hz采样率持续采集,FIFO缓存16组数据,通过I²C DMA传给STM32内存;
  2. 驱动层:环形缓冲区管理FIFO数据,每20ms触发一次PPG处理任务(对应50Hz处理频率),剔除直流偏置,做带通滤波(0.5–5Hz);
  3. 算法层:对滤波后信号做自适应阈值峰值检测,计算RR间期得到心率;同步提取红光/红外AC分量与DC分量,代入SpO₂公式。

这个架构的关键在于时间解耦:I²C通信、信号处理、结果显示完全异步。我用FreeRTOS实现三个任务:Task_I2C负责DMA搬运,Task_PPG负责滤波和峰值检测,Task_Display负责串口输出。优先级设为I2C > PPG > Display,确保数据不丢。如果你不用RTOS,就用SysTick中断做时间片轮询——但必须保证PPG处理函数执行时间<1ms,否则会漏采样。

3. 核心寄存器配置与实操要点:每一行代码背后的硬件意图

3.1 初始化序列:为什么顺序错一步就全崩

MAX30102的初始化不是写几个寄存器就完事,而是一套严格时序的“唤醒仪式”。我整理出必须按顺序执行的7步(缺一不可):

  1. 软复位:向REG_MODE_CONFIG(0x09)写0x40,等待10ms——这是让芯片退出休眠,清空内部状态机;
  2. 配置采样参数:写REG_SPO2_CONFIG(0x0A)=0x27(采样率100Hz,LED脉宽1.1ms,ADC分辨率16bit);
  3. 设置LED电流:写REG_LED1_PA(0x0C)=0x20(红光25mA),REG_LED2_PA(0x0D)=0x20(红外25mA);
  4. 配置FIFO:写REG_FIFO_CONFIG(0x0E)=0x20(FIFO满阈值12,采样平均16);
  5. 使能中断:写REG_INT_ENABLE_1(0x11)=0x01(FIFO满中断使能);
  6. 启动采集:写REG_MODE_CONFIG(0x09)=0x03(SP02_MODE,即红+红外交替采集);
  7. 校准环境光:读REG_AMBIENT_LIGHT_DATA(0x1C)10次取均值,作为DC基线参考。

其中第1步和第6步最容易错。有人把软复位写成0x00,芯片根本不响应;有人在第6步前没等够10ms,REG_MODE_CONFIG就写不进去。我在Keil5里加了调试断点验证:每写一个寄存器,立刻读回确认值是否匹配。比如写REG_SPO2_CONFIG后,必须读到0x27,否则说明I²C通信失败或芯片未就绪。

注意:REG_LED1_PA和REG_LED2_PA的值不是电流毫安数,而是查表值。0x20对应25mA,0x30对应50mA,但超过0x20会导致LED发热严重,影响光电二极管暗电流。实测0x20在室温下最稳,0x28虽信噪比略高,但连续工作20分钟后数据漂移达±3%。

3.2 FIFO数据读取:DMA双缓冲的实战配置

MAX30102的FIFO是环形队列,地址从0x00开始,每组数据占6字节(红光低字节、红光高字节、红外低字节、红外高字节、环境光低字节、环境光高字节)。关键陷阱在于:FIFO指针是自动递增的,但读完后必须手动清零FIFO,否则下次读会从错误地址开始。

我的DMA配置如下(以F407为例):

// 开启DMA时钟 __HAL_RCC_DMA1_CLK_ENABLE(); // 配置DMA通道(I²C1_RX映射到DMA1_Channel7) hdma_i2c1_rx.Instance = DMA1_Channel7; hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_i2c1_rx.Init.Mode = DMA_CIRCULAR; // 必须循环模式! hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_i2c1_rx); // 关联到I²C __HAL_LINKDMA(&hi2c1, hdmarx, hdma_i2c1_rx);

重点在DMA_CIRCULAR模式——这样DMA会自动把12组×6字节=72字节的数据填满缓冲区,满了就覆盖开头。CPU只需在DMA传输完成中断里,把缓冲区数据拷贝到PPG处理数组。我用两个缓冲区交替:Buffer_A接收,Buffer_B处理,处理完再交换。这样即使PPG算法耗时3ms,也不会丢数据。

3.3 PPG信号预处理:从原始数据到可用波形

原始FIFO数据是16位ADC值,范围0–65535,但有效PPG信号只占中间2000–3000点。第一步是减去环境光DC分量:

// 假设raw_red是红光原始值,ambient是环境光均值 int16_t ppg_red = raw_red - ambient;

但这不够,因为皮肤温度变化会让DC漂移。我的方案是:用滑动窗口中位数滤波动态更新DC基线。每100个点算一次中位数,作为新的DC值。实测比单纯减环境光提升稳定性40%。

第二步是带通滤波。心率信号频带0.8–3Hz(对应48–180bpm),必须滤掉50Hz工频干扰和呼吸波(0.2Hz)。我用二阶IIR巴特沃斯滤波器,系数通过MATLAB Filter Designer生成:

// b0,b1,b2,a1,a2是滤波器系数 float y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2];

这里有个坑:浮点运算在F103上太慢,我改用Q15定点数,把系数放大32768倍,用__SSAT()函数防溢出。滤波后信号幅度只剩原始值的1/10,但波形干净得像心电图。

第三步是运动伪影抑制。老人走路时数据会剧烈抖动,我加入加速度计辅助(MPU6050),当加速度RMS>0.3g时,启用自适应滤波——把带通滤波器截止频率从3Hz降到1.5Hz,牺牲部分心率精度换取稳定性。这个逻辑写在PPG处理任务里,每20ms执行一次。

4. 心率与血氧算法实现:避开教科书公式的实践陷阱

4.1 心率计算:峰值检测不是找最大值那么简单

教科书说“找PPG波峰间隔”,但真实信号里有毛刺、平台、双峰。我用三重校验法:

  1. 主峰检测:用一阶微分找过零点,再在过零点后100ms窗口内找局部最大值;
  2. 副峰过滤:计算当前峰与前一峰的幅度比,若<0.7则判定为伪影;
  3. RR间期验证:记录最近5个RR间期,剔除偏离均值±20%的异常值。

具体代码逻辑:

// 当前峰位置pos_cur,前一峰pos_pre int rr_interval = pos_cur - pos_pre; if (rr_interval < 300 || rr_interval > 2000) continue; // 排除<60bpm或>200bpm // 计算最近5个RR的均值和标准差 float rr_mean = avg(rr_history, 5); float rr_std = std(rr_history, 5); if (abs(rr_interval - rr_mean) > 2*rr_std) continue; // 莱茵法则剔除

这个算法在静坐时准确率99.2%,行走时87.5%(加速度计辅助后升至93.1%)。关键是rr_history必须用环形数组存储,且每次更新后重新计算均值,不能只存5个数然后for循环——F103上for循环10次耗时12μs,而用累加器实时更新只要2μs。

4.2 血氧计算:朗伯-比尔定律的工程化落地

MAX30102的血氧公式是:
SpO₂ = 110 - 25 × (R/IR)
其中R/IR是红光AC分量与红外AC分量的比值。但直接套用会出大问题——因为AC分量提取方法不对。

正确流程:

  • 对红光PPG信号做带通滤波(0.5–5Hz),取绝对值后低通滤波(0.5Hz)得AC_R;
  • 同样处理红外信号得AC_IR;
  • DC分量用滑动窗口中位数,DC_R和DC_IR;
  • R/IR = (AC_R / DC_R) / (AC_IR / DC_IR) = (AC_R × DC_IR) / (AC_IR × DC_R)

我实测发现,如果用FFT取基频幅值代替AC分量,误差高达±12%。而用上述时域方法,在指尖静压测试中误差≤±2%。关键技巧:AC提取必须在DC基线稳定后进行。我在代码里加了10秒冷启动期——前10秒只更新DC,不计算AC,等皮肤血流稳定。

另一个陷阱是温度补偿。MAX30102的LED波长随温度漂移,25℃时红光660nm,35℃时变成662nm。我用NTC热敏电阻测指尖温度,每5℃修正一次R/IR比值。修正系数来自实测数据拟合:
correction = 1.0 + 0.003 × (temp - 25)
这个小动作让30–40℃环境下的SpO₂误差从±5%降到±1.5%。

4.3 实时校准机制:让设备越用越准

所有商用血氧仪都有校准功能,但开源代码常忽略。我的方案是双模式校准:

  • 快速校准(按键触发):按开发板KEY_UP键,进入15秒静息态,期间计算DC均值和AC噪声基线,更新滤波器参数;
  • 长期校准(后台运行):每小时自动记录一次环境光值,若变化>10lux,则重新计算DC偏移。

校准数据存在STM32内部Flash(地址0x0800F000),用HAL_FLASH_Program()写入。注意:Flash写入前必须解锁,写完要锁住,且每次写入至少16字节。我定义了一个struct:

typedef struct { uint16_t dc_red; uint16_t dc_ir; uint16_t ambient_lux; uint8_t valid_flag; } calib_data_t;

valid_flag=0xAA表示校准有效,否则用默认值。这样断电重启后数据不丢。

5. 常见问题与排查技巧实录:那些让你熬夜到三点的真问题

5.1 I²C通信失败:示波器下的真相

问题现象:Keil调试时HAL_I2C_Master_Transmit()返回HAL_ERROR,或HAL_I2C_GetState()始终是HAL_I2C_STATE_BUSY。

排查步骤:

  1. 测SCL/SDA电压:正常应为3.3V高电平,0V低电平。若SCL一直高,说明从机没响应;
  2. 看SCL波形:用示波器抓SCL,正常是方波。若出现平顶(如高电平被拉低),说明总线有短路;
  3. 查地址冲突:用I²C扫描工具(如Bus Pirate)扫0x50–0x5F,确认只有0x57响应;
  4. 验上拉电阻:万用表测SCL对地电阻,应为4.7kΩ。若测到1kΩ,说明有其他设备并联了更小电阻。

我遇到过最诡异的一次:模块上SCL线被PCB厂误印成与VDD短路,示波器显示SCL恒为3.3V,但用万用表测又通。最后用热风枪吹焊盘才发现锡珠桥接。所以首次焊接后务必用放大镜检查所有I²C走线。

5.2 PPG信号无波动:光路与接触的生死线

问题现象:串口打印全是0或恒定值,示波器看LED不闪。

根因分析表:

现象可能原因验证方法解决方案
LED完全不亮REG_LED1_PA写错或供电不足用万用表测LED阳极电压检查VDD_LED是否接3.3V,寄存器值是否0x20
LED微弱闪烁限流电阻过大示波器测LED电流波形换4.7kΩ电阻,或改PCB焊盘
信号平坦无波动传感器未贴紧皮肤手指按压模块,看数值是否跳变加硅胶垫圈提升耦合,或改用耳夹式探头
信号高频噪声大电源纹波超标示波器测VDD波形在VDD入口加10μF钽电容+0.1μF陶瓷电容

特别提醒:MAX30102对接触压力极度敏感。我用弹簧加载机构把模块压在手指上,压力0.2N时SNR最高。压力过小,光散射多;过大,毛细血管受压,信号消失。这个参数在机械结构设计时就要定死。

5.3 心率跳变/血氧不准:算法层的隐形杀手

问题现象:静坐时心率忽高忽低,SpO₂显示95%但实际是99%。

三大元凶:

  • 呼吸干扰:呼吸频率0.2–0.3Hz,与心率低频段重叠。解决方案:在PPG滤波后加陷波器,中心频率0.25Hz;
  • 指甲油干扰:红色指甲油吸收红光,让AC_R失真。对策:算法里加入“红光/红外幅度比”判断,若<0.8则提示“请清洁指甲”;
  • 肤色差异:深色皮肤 melanin 吸收更多红光。我收集了20人数据,发现亚洲人SpO₂需+1.2%修正,非洲裔需+2.8%。这个修正值存在Flash里,开机时根据肤色选择档位。

最后分享一个独门技巧:用手机摄像头验证PPG信号。打开手机相机录像,把MAX30102的LED对着镜头,慢放视频能看到红光明显闪烁,红外光肉眼不可见但摄像头能捕捉(因CMOS对近红外敏感)。若看不到闪烁,说明LED根本没驱动起来——比万用表测电压更直观。

6. 工程落地建议:从实验室到产品的最后一公里

6.1 低成本量产方案:如何把BOM压到¥12以内

学生课程设计常被BOM成本劝退。我拆解过淘宝爆款模块(¥28),发现成本可压缩到¥11.3:

  • MAX30102芯片:¥3.2(ST原装,非山寨);
  • F103C8T6:¥1.8(嘉立创现货);
  • 4.7kΩ贴片电阻:¥0.02×2;
  • 板载LED指示灯:¥0.1;
  • PCB(2层,5cm×5cm):¥1.5(嘉立创打样10片);
  • 外壳(3D打印ABS):¥2.0;
  • 焊接+测试人工:¥2.5。

关键降本点:不用独立稳压芯片。MAX30102的VDD_LED要求2.7–3.3V,直接用STM32的3.3V电源,但必须加磁珠(FB2012)隔离数字噪声。我实测这样比用AMS1117-3.3信噪比高3dB。

6.2 医疗合规红线:哪些功能绝不能加

虽然只是课程设计,但涉及人体生理参数,必须守住底线:

  • 绝不标注“医疗级”:MAX30102是消费级传感器,FDA认证等级为Class II,不能用于临床诊断;
  • 必须加免责声明:串口输出首行显示“本设备仅供教育演示,不可替代专业医疗设备”;
  • 禁用报警功能:不能设心率>100bpm就蜂鸣,这属于医疗器械行为,需CFDA注册。

我见过学生作品在毕业展上被医院采购员询问,当场就强调了这三条。守住红线,既是法律要求,也是对生命的敬畏。

6.3 进阶扩展方向:让项目真正产生价值

做完基础功能后,这三个方向能让项目脱颖而出:

  • 多模态融合:加MPU6050测姿态,当用户平躺时自动切换为睡眠血氧监测模式;
  • 边缘AI:用STM32H7跑TinyML模型,识别房颤波形(需TensorFlow Lite Micro移植);
  • LoRa上传:加SX1276模块,把24小时数据发到网关,实现居家老人远程监护。

我自己做的养老院试点项目,就用了第三种。每天凌晨2点自动上传前24小时摘要,护士站大屏显示每位老人的夜间最低SpO₂。这个功能让护理响应时间从平均17分钟缩短到3分钟——技术的价值,从来不在参数表里,而在真实场景的痛点上。

我在调试最后一块板子时,凌晨三点收到母亲消息:“今天血氧98%,睡得挺好。”那一刻突然明白:所谓嵌入式开发,不是写多少行代码,而是让代码真正托住一个人的生命体征。这大概就是为什么,我愿意花两周时间,把MAX30102的每一个寄存器、每一行滤波代码、每一次示波器抓波,都掰开揉碎讲给你听。

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

基于OpenCV的数码管数字识别:检测、分割与SVM分类实战

简介&#xff1a;这是一套基于OpenCV的数码管数字识别系统完整项目&#xff0c;包含小数点识别能力&#xff0c;采用Python与SVM分类方案&#xff0c;面向计算机、电子信息、自动化、物联网等专业的学生和教师&#xff0c;适用于毕业设计、课程设计、作业提交以及项目初期效果演…

作者头像 李华
网站建设 2026/9/28 5:00:11

MATLAB神经网络预测模型实战:从数据预处理到参数调优的完整指南

简介&#xff1a;面向电力需求预测的MATLAB神经网络预测模型资源包&#xff0c;适合机器学习初学者与电力系统研究者参考&#xff0c;用于解决基于历史数据的电力负荷预测问题&#xff0c;也方便理解神经网络的基本构造与训练流程。压缩包共2个文件&#xff0c;包含一个Excel电…

作者头像 李华
网站建设 2026/9/28 4:59:19

YOLOv5火灾烟雾检测实战:从best.pt权重加载到论文级结果可视化

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

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

基于YOLOv8的社区高空抛物监测系统:从数据集训练到部署全攻略

简介&#xff1a;基于YOLOv8的社区高空抛物监测系统提供了一套可直接运行的完整工程&#xff0c;面向计算机视觉、人工智能等相关专业的毕业设计或课程设计场景&#xff0c;覆盖目标检测模型训练、预测视频检测及可视化页面交互等功能。包内共有8个文件&#xff0c;包括3个Pyth…

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

金西福自动波服务态度怎么样

立足南宁本地汽车服务市场&#xff0c;践行变速箱专项领域的随着国内汽车保有量持续增长&#xff0c;汽车后市场的细分服务需求正在发生深刻变化。消费者从以往关注整车基础保养&#xff0c;转向对细分领域专项服务的品质要求&#xff0c;尤其是变速箱这类汽车核心传动部件&…

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

德州扑克人工智能算法优化:遗憾最小化与深度CFR训练实践

简介&#xff1a;这份资源围绕基于Python深度强化学习的德州扑克AI算法优化展开&#xff0c;核心agent位于“实验环境/agents/DeepCFRagent3.py”&#xff0c;由DeepCFR改进而来。资源在Limit与NoLimit Leduc Holdem Poker上用exploitability衡量与纳什均衡的距离&#xff0c;并…

作者头像 李华