[PIC®/AVR®/dsPIC®产品] 【CuriosityNano测评报告】06.驱动MAX30102血氧传感器的尝试

[复制链接]
46835|49
 楼主| hu9jj 发表于 2021-9-14 15:28 | 显示全部楼层 |阅读模式
#申请原创#

    多年前曾经测试过美信MAX30102血氧脉搏传感器,使用的是STM32F103C8T6最小系统板,显示屏使用LCD5110。当时为了测试方便,我将MAX30102传感器固定在一个瓶盖内,手指正好可以塞进瓶盖内进行测量。下图为当时的测试装置:


    当手指塞进瓶盖内,指腹贴在传感器芯片时,稍等几秒钟便可稳定显示出血氧饱和度及脉搏数,同时屏幕上还模拟出脉搏跳动的图形:


    本次准备将这个测试移植到PIC18F16Q41核心板,但移植过程非常坎坷。首先在代码移植和分析过程中发现定义了多个下标为500的大数组,由于PIC18F16Q41的资源有限,编译出错,只好将这些数组的下标改为100。然后在测试过程中发现I只要挂接上MAX30102,I2C总线就会读写出错,万般无奈,只好另外启用两个引脚来模拟I2C操作。但模拟I2C也无法得到器件的回应信号,调试了几天都没有结果。下面是调试时用逻辑分析仪抓取的时序图:


    这是血氧脉搏传感器在STM32F103C8T6最小系统板上运行时抓取的用于对比分析的时序图:


    下面是模拟I2C的代码,这个代码是原来驱动DS1307日历模块的,应该不会有太大的问题:

  1. #include "i2c.h"


  2. /******************************************************************************************************************************************
  3. * 函数名称: I2C_Start()
  4. * 功能说明:        产生I2C传输的Start信号
  5. * 输    入: 无
  6. * 输    出: 无
  7. ******************************************************************************************************************************************/
  8. void SI2C_Start(void)
  9. {
  10.     SDA_OUT;       //SDA输出
  11.     SDA_1;
  12.     SCL_1;            //scl = 1;
  13.     DELAY_microseconds(2);
  14.     SDA_0;           //sda = 0;        scl为高时sda的下降沿表示“起始”
  15.     DELAY_microseconds(1);
  16.     SCL_0;           //scl = 0; 钳住I2C总线,准备发送或接收数据
  17. }

  18. /******************************************************************************************************************************************
  19. * 函数名称:        I2C_Stop()
  20. * 功能说明:        产生I2C传输的Stop信号
  21. * 输    入: 无
  22. * 输    出: 无
  23. ******************************************************************************************************************************************/
  24. void SI2C_Stop(void)
  25. {
  26.     SDA_OUT;
  27.     SCL_0;           // scl = 0;
  28.     SDA_0;          // STOP:when CLK is high DATA change form low to high
  29.     DELAY_microseconds(2);
  30.     SCL_1;           // scl = 1;
  31.     DELAY_microseconds(1);
  32.     SDA_1;          // sda = 1;        sclk为高时sdat的上升沿表示“停止”
  33. }


  34. /******************************************************************************************************************************************
  35. * 函数名称: I2C_Send()
  36. * 功能说明:        向IIC总线发送一个字节的数据
  37. * 输    入: byte dat         要发送的数据
  38. * 输    出: 无
  39. ******************************************************************************************************************************************/
  40. void SI2C_Send(uint8_t dat)
  41. {
  42.     uint8_t i;
  43.     SDA_OUT;
  44.     SCL_0;                     //拉低时钟开始数据传输
  45.     for(i=0; i<8; i++)
  46.     {
  47.         if((dat&0x80)>>7)//准备好SDA数据
  48.             SDA_1;
  49.         else
  50.             SDA_0;
  51.         dat <<= 1;         //dat <<= 1;
  52.         SCL_1;               //拉高时钟等待从设备读取数据
  53.         DELAY_microseconds(3);
  54.         SCL_0;               //拉低时钟准备下一位数据
  55.         DELAY_microseconds(2);
  56.     }
  57. }


  58. /******************************************************************************************************************************************
  59. * 函数名称:        I2C_Receive()
  60. * 功能说明:        从IIC总线接收一个字节的数据
  61. * 输    入: 无
  62. * 输    出: byte                从IIC总线上接收到得数据
  63. * 注意事项: 无
  64. ******************************************************************************************************************************************/
  65. uint8_t SI2C_Receive(void)
  66. {
  67.     uint8_t i,dat;
  68.     SDA_IN;        //设置为输入       
  69.     for(i=0;i<8;i++)
  70.     {
  71.         SCL_0;
  72.         DELAY_microseconds(1);
  73.         dat<<=1;
  74.         if(1 == SDA_X)
  75.             dat|=0x01;
  76.     }
  77.     return dat;
  78. }

  79. /******************************************************************************************************************************************
  80. * 函数名称: I2CDoAck()
  81. * 功能说明:        在应答位位置产生应答,从而继续连续传输
  82. * 输    入: 无
  83. * 输    出: 无
  84. ******************************************************************************************************************************************/
  85. void SI2CDoAck(void)
  86. {
  87.     SCL_0;
  88.     SDA_OUT;
  89.     SDA_0;              //sda = 0;        /拉低数据线,即给于应答
  90.     DELAY_microseconds(1);
  91.     SCL_1;              //scl = 1;
  92.     DELAY_microseconds(1);
  93.     SCL_0;              //scl = 0;
  94. }

  95. /******************************************************************************************************************************************
  96. * 函数名称: I2CNoAck()
  97. * 功能说明:        在应答位位置不产生应答,从而终止连续传输
  98. * 输    入: 无
  99. * 输    出: 无
  100. ******************************************************************************************************************************************/
  101. void SI2CNoAck(void)
  102. {
  103.     SCL_0;
  104.     SDA_OUT;
  105.     SDA_1;                  // sda = 1;        不拉低数据线,即不给于应答
  106.     DELAY_microseconds(1);
  107.     SCL_1;                   // scl = 1;
  108.     DELAY_microseconds(1);
  109.     SCL_0;              // scl = 0;
  110. }

  111. /******************************************************************************************************************************************
  112. * 函数名称: I2CIsAck()
  113. * 功能说明:        检测从机应答位
  114. * 输    入: 无
  115. * 输    出: uint8_t        0=ACK_OK 从机产生了应答;1=ACK_NO 从机没有产生应答
  116. ******************************************************************************************************************************************/
  117. uint8_t SI2CIsAck(void)
  118. {
  119.     uint8_t i;
  120.     SDA_OUT;
  121.     SDA_1;                     // sda = 1; 释放数据线
  122.     DELAY_microseconds(1);
  123.     SDA_IN;
  124.     SCL_1;                     // scl = 1;
  125.     DELAY_microseconds(1);
  126.     while(SDA_X){
  127.         i++;
  128.         if(i>250){
  129.             SI2C_Stop();    //数据线未被拉低,即未收到应答
  130.             return 1;
  131.         }
  132.     }
  133.     SCL_0;
  134.     return 0;
  135. }


  136. /******************************************************************************************
  137. * 函数名称: I2C_8bitByteRead()
  138. * 功能说明: 从指定地址addr开始获取1个字节的数据
  139. * 输    入: uint8_t I2Caddr 器件地址
  140. *           uint8_t addr        数据地址
  141. * 输    出: uint8_t 返回读取的数值
  142. * 备    注:
  143. ******************************************************************************************/
  144. uint8_t I2C_8bitByteRead(uint8_t I2Caddr,uint8_t addr)
  145. {
  146.     uint8_t dat;

  147.     SI2C_Start();                   //产生起始信号
  148.     SI2C_Send(I2Caddr);       //发送器件地址及读写位,0表示写
  149.     if(SI2CIsAck())                 //检测器件是否有响应
  150.     {
  151.         SI2C_Stop();               //产生停止信号
  152.         return 2;
  153.     }
  154.     SI2C_Send(addr);           //发送读取数据的起始地址
  155.     if(SI2CIsAck())               //检测器件是否有响应
  156.     {
  157.         SI2C_Stop();              //产生停止信号
  158.         return 3;
  159.     }

  160.     SI2C_Start();                  //产生Repeated Start
  161.     SI2C_Send(I2Caddr|1);   //发送器件芯片地址及读写位,1表示读
  162.     if(SI2CIsAck())                //检测器件是否有响应
  163.     {
  164.         SI2C_Stop();              //产生停止信号
  165.         return 4;
  166.     }

  167.     dat = SI2C_Receive();
  168.     SI2CDoAck();

  169.     SI2CNoAck();                //器件要求必须使用NOAck来结束数据读取

  170.     SI2C_Stop();                 //产生停止信号

  171.     return dat;
  172. }

  173. /******************************************************************************************
  174. * 函数名称: I2C_8bitBuffRead()
  175. * 功能说明: 从指定地址addr开始获取size个字节的数据,获取的数据存储在全局变量数组I2C_Buff中
  176. * 输    入: uint8_t I2Caddr 器件地址
  177. *           uint8_t addr        获取数据从addr开始
  178. *                        uint8_t size        要获取的数据个数(1~8)
  179. * 输    出: uint8_t 操作状态
  180. * 备    注:长度不得超过16个字节
  181. ******************************************************************************************/
  182. uint8_t I2C_8bitBuffRead(uint8_t I2Caddr,uint8_t addr,uint8_t size,uint8_t *buff)
  183. {
  184.     uint8_t i;

  185.     SI2C_Start();                   //产生起始信号
  186.     SI2C_Send(I2Caddr);             //发送器件地址及读写位,0表示写
  187.     if(SI2CIsAck())                 //检测器件是否有响应
  188.     {
  189.         SI2C_Stop();                //产生停止信号
  190.         return 2;
  191.     }
  192.     SI2C_Send(addr);                //发送读取数据的起始地址
  193.     if(SI2CIsAck())                 //检测器件是否有响应
  194.     {
  195.         SI2C_Stop();                    //产生停止信号
  196.         return 3;
  197.     }

  198.     SI2C_Start();                   //产生Repeated Start
  199.     SI2C_Send(I2Caddr|1);           //发送器件芯片地址及读写位,1表示读
  200.     if(SI2CIsAck())                 //检测器件是否有响应
  201.     {
  202.         SI2C_Stop();                //产生停止信号
  203.         return 4;
  204.     }

  205.     for(i=0;i<size;i++)             //从addr处读取size个字节的数据
  206.     {
  207.         buff[i] = SI2C_Receive();
  208.         SI2CDoAck();
  209.     }
  210.     SI2CNoAck();                    //器件要求必须使用NOAck来结束数据读取

  211.     SI2C_Stop();                    //产生停止信号
  212.    
  213.     return *buff;
  214. }


  215. /******************************************************************************************
  216. * 函数名称: I2C_8bitByteWrite()
  217. * 功能说明: 从指定地址addr开始写入1个字节的数据
  218. * 输    入: uint8_t I2Caddr 器件地址
  219. *           uint8_t addr        数据地址
  220. * 输    出: uint8_t 返回读取的数值
  221. * 备    注:
  222. ******************************************************************************************/
  223. uint8_t I2C_8bitByteWrite(uint8_t I2Caddr,uint8_t addr,uint8_t dat)
  224. {
  225.     SI2C_Start();                   //产生起始信号
  226.     SI2C_Send(I2Caddr|0);           //发送从设备芯片地址及读写位,0表示写
  227.     if(SI2CIsAck())                 //检测从设备是否有响应
  228.     {
  229.         SI2C_Stop();                //产生停止信号
  230.         return 1;
  231.     }

  232.     SI2C_Send(addr);                //发送数据要写入的地址
  233.     if(SI2CIsAck())                 //检测从设备是否有响应
  234.     {
  235.         SI2C_Stop();                //产生停止信号
  236.         return 2;
  237.     }

  238.     SI2C_Send(dat);
  239.     if(SI2CIsAck())                 //检测从设备是否有响应
  240.     {
  241.         SI2C_Stop();                //产生停止信号
  242.         return 3;
  243.     }

  244.     SI2C_Stop();                    //产生停止信号
  245.    
  246.      return 0;

  247. }


  248. /**********************************************************************************************
  249. * 函数名称:        I2C_8bitBuffWrite()
  250. * 功能说明: 向I2C器件的地址addr开始写入size个字节的数据,将要写入的数据存储在全局变量数组I2C_Buff中
  251. * 输    入: uint8_t addr        数据被写入从addr开始的地址处
  252. *                        uint8_t size        要设置的数据个数(1~8)
  253. * 输    出: uint8_t                0= 成功  1=出现错误
  254. **********************************************************************************************/
  255. uint8_t I2C_8bitBuffWrite(uint8_t I2Caddr,uint8_t addr,uint8_t size,uint8_t *buff)
  256. {
  257.     uint8_t i = 0;
  258.        
  259.     SI2C_Start();                   //产生起始信号
  260.     SI2C_Send(I2Caddr|0);           //发送器件芯片地址及读写位,0表示写
  261.      if(SI2CIsAck())                 //检测器件是否有响应
  262.     {
  263.         SI2C_Stop();                //产生停止信号
  264.         return 1;
  265.     }

  266.     SI2C_Send(addr);                //发送数据要写入的地址
  267.     if(SI2CIsAck())                 //检测器件是否有响应
  268.     {
  269.         SI2C_Stop();                //产生停止信号
  270.         return 2;
  271.     }

  272.     for(i=0;i<size;i++)
  273.     {
  274.         SI2C_Send(buff[i]);
  275.         if(SI2CIsAck())             //检测器件是否有响应
  276.         {
  277.             SI2C_Stop();            //产生停止信号
  278.             return 3;
  279.         }
  280.     }

  281.     SI2C_Stop();                    //产生停止信号

  282.     return 0;
  283. }


  284. /******************************************************************************************
  285. * 函数名称: I2C_16bitByteRead()
  286. * 功能说明: 从指定地址addr开始获取1个字节的数据
  287. * 输    入: uint8_t I2Caddr 器件地址
  288. *           uint16_t addr        双字节数据地址
  289. * 输    出: uint8_t 返回读取的数值
  290. * 备    注:
  291. ******************************************************************************************/
  292. uint8_t I2C_16bitByteRead(uint8_t I2Caddr,uint16_t addr)
  293. {
  294.     uint8_t dat;

  295.     SI2C_Start();                   //产生起始信号
  296.     SI2C_Send(I2Caddr);             //发送器件地址及读写位,0表示写
  297.     if(SI2CIsAck())                 //检测从设备是否有响应
  298.     {
  299.         SI2C_Stop();                //产生停止信号
  300.         return 2;
  301.     }
  302.     SI2C_Send(addr>>8);             //发送读取数据的起始地址
  303.     if(SI2CIsAck())                 //检测从设备是否有响应
  304.     {
  305.         SI2C_Stop();                //产生停止信号
  306.         return 3;
  307.     }
  308.     SI2C_Send(addr|0x0F);           //发送读取数据的起始地址低8位
  309.     if(SI2CIsAck())                 //检测器件是否有响应
  310.     {
  311.         SI2C_Stop();                //产生停止信号
  312.         return 3;
  313.     }
  314.    
  315.     SI2C_Start();                   //产生Repeated Start
  316.     SI2C_Send(I2Caddr|1);           //发送从设备芯片地址及读写位,1表示读
  317.     if(SI2CIsAck())                 //检测从设备是否有响应
  318.     {
  319.         SI2C_Stop();                //产生停止信号
  320.         return 4;
  321.     }

  322.     dat = SI2C_Receive();
  323.     SI2CDoAck();

  324.     SI2CNoAck();                    //从设备要求必须使用NOAck来结束数据读取

  325.     SI2C_Stop();                    //产生停止信号

  326.     return dat;
  327. }

  328. /******************************************************************************************
  329. * 函数名称: I2C_16bitBuffRead()
  330. * 功能说明: 从指定地址addr开始获取size个字节的数据,获取的数据存储在全局变量数组I2C_Buff中
  331. * 输    入: uint8_t I2Caddr 器件地址
  332. *           uint16_t addr        获取数据从addr开始(16位地址)
  333. *                        uint8_t size        要获取的数据个数(1~8)
  334. * 输    出: uint8_t 操作状态
  335. * 备    注:长度不得超过16个字节
  336. ******************************************************************************************/
  337. uint8_t I2C_16bitBuffRead(uint8_t I2Caddr,uint16_t addr,uint8_t size,uint8_t *buff)
  338. {
  339.     uint8_t i;

  340.     SI2C_Start();                   //产生起始信号
  341.     SI2C_Send(I2Caddr);             //发送器件地址及读写位,0表示写
  342.     if(SI2CIsAck())                 //检测从设备是否有响应
  343.     {
  344.         SI2C_Stop();                //产生停止信号
  345.         return 2;
  346.     }
  347.    
  348.     SI2C_Send(addr>>8);             //发送读取数据的起始地址
  349.     if(SI2CIsAck())                 //检测从设备是否有响应
  350.     {
  351.        SI2C_Stop();                //产生停止信号
  352.        return 3;
  353.     }
  354.     SI2C_Send(addr|0x0F);           //发送读取数据的起始地址低8位
  355.     if(SI2CIsAck())                 //检测器件是否有响应
  356.     {
  357.         SI2C_Stop();                //产生停止信号
  358.         return 3;
  359.     }

  360.     SI2C_Start();                   //产生Repeated Start
  361.     SI2C_Send(I2Caddr|1);           //发送从设备芯片地址及读写位,1表示读
  362.     if(SI2CIsAck())                 //检测从设备是否有响应
  363.     {
  364.         SI2C_Stop();                //产生停止信号
  365.         return 4;
  366.     }

  367.     for(i=0;i<size;i++)             //从addr处读取size个字节的数据
  368.     {
  369.         buff[i] = SI2C_Receive();
  370.         SI2CDoAck();
  371.     }
  372.     SI2CNoAck();                    //从设备要求必须使用NOAck来结束数据读取

  373.     SI2C_Stop();                    //产生停止信号
  374.    
  375.     return *buff;
  376. }


  377. /******************************************************************************************
  378. * 函数名称: I2C_16bitByteWrite()
  379. * 功能说明: 从指定地址addr开始写入1个字节的数据
  380. * 输    入: uint8_t I2Caddr 器件地址
  381. *           uint16_t addr        双字节数据地址
  382. * 输    出: uint8_t 返回读取的数值
  383. * 备    注:
  384. ******************************************************************************************/
  385. uint8_t I2C_16bitByteWrite(uint8_t I2Caddr,uint16_t addr,uint8_t dat)
  386. {
  387.     SI2C_Start();                   //产生起始信号
  388.     SI2C_Send(I2Caddr|0);           //发送从设备芯片地址及读写位,0表示写
  389.     if(SI2CIsAck())                 //检测从设备是否有响应
  390.     {
  391.         SI2C_Stop();                //产生停止信号
  392.         return 1;
  393.     }
  394.     SI2C_Send(addr>>8);             //发送读取数据的起始地址
  395.     if(SI2CIsAck())                 //检测从设备是否有响应
  396.     {
  397.         SI2C_Stop();                //产生停止信号
  398.         return 3;
  399.     }
  400.     SI2C_Send(addr|0x0F);           //发送读取数据的起始地址低8位
  401.     if(SI2CIsAck())                 //检测器件是否有响应
  402.     {
  403.         SI2C_Stop();                //产生停止信号
  404.         return 3;
  405.     }

  406.     SI2C_Send(dat);
  407.     if(SI2CIsAck())                 //检测从设备是否有响应
  408.     {
  409.         SI2C_Stop();                //产生停止信号
  410.         return 3;
  411.     }

  412.     SI2C_Stop();                    //产生停止信号
  413.    
  414.     return 0;

  415. }


  416. /**********************************************************************************************
  417. * 函数名称:        I2C_16bitBuffWrite()
  418. * 功能说明: 向I2C器件的地址addr开始写入size个字节的数据,将要写入的数据存储在全局变量数组I2C_Buff中
  419. * 输    入: uint16_t addr        数据被写入从addr开始的16位地址处
  420. *                        uint8_t size        要设置的数据个数(1~8)
  421. * 输    出: uint8_t                0= 成功  1=出现错误
  422. **********************************************************************************************/
  423. uint8_t I2C_16bitBuffWrite(uint8_t I2Caddr,uint16_t addr,uint8_t size,uint8_t *buff)
  424. {
  425.     uint8_t i = 0;
  426.       
  427.     SI2C_Start();                   //产生起始信号
  428.     SI2C_Send(I2Caddr|0);           //发送从设备芯片地址及读写位,0表示写
  429.     if(SI2CIsAck())                 //检测从设备是否有响应
  430.     {
  431.         SI2C_Stop();                //产生停止信号
  432.         return 1;
  433.     }

  434.     SI2C_Send(addr>>8);             //发送读取数据的起始地址
  435.     if(SI2CIsAck())                 //检测从设备是否有响应
  436.     {
  437.         SI2C_Stop();                //产生停止信号
  438.         return 3;
  439.     }
  440.     SI2C_Send(addr|0x0F);           //发送读取数据的起始地址低8位
  441.     if(SI2CIsAck())                 //检测器件是否有响应
  442.     {
  443.         SI2C_Stop();                //产生停止信号
  444.         return 3;
  445.     }

  446.     for(i=0;i<size;i++)
  447.     {
  448.         SI2C_Send(buff[i]);
  449.         if(SI2CIsAck())             //检测从设备是否有响应
  450.         {
  451.             SI2C_Stop();            //产生停止信号
  452.             return 3;
  453.         }
  454.     }

  455.     SI2C_Stop();                    //产生停止信号

  456.     return 0;
  457. }



    先后折腾了十多天了,代码移植尚未完成,模拟I2C也得不到器件的回应,现在仍在继续查找原因中。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
598330983 发表于 2021-9-14 22:03 | 显示全部楼层
你这传感器给力。
daichaodai 发表于 2021-9-15 07:47 来自手机 | 显示全部楼层
这个传感器有点简陋啊!
 楼主| hu9jj 发表于 2021-9-15 08:31 | 显示全部楼层
本帖最后由 hu9jj 于 2021-9-17 09:01 编辑

    此传感器只有指甲盖大小,见下面的照片:


    为了使用方便,我将其固定在一个瓶盖里面,使用时只要将手指塞进就行:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
cyclefly 发表于 2021-9-16 19:40 | 显示全部楼层
这传感器可以啊~~有意思
yangxiaor520 发表于 2021-9-17 07:39 来自手机 | 显示全部楼层
这个传感器有点大
海洋无限 发表于 2021-9-24 14:07 | 显示全部楼层
你这小瓶盖真给力,点赞,说实话模拟I2C确实不行
kkzz 发表于 2021-10-2 12:44 | 显示全部楼层
max30102有没有放大滤波的功能?  
hudi008 发表于 2021-10-2 12:45 | 显示全部楼层
有计算心率的功能吗   
lzmm 发表于 2021-10-2 12:45 | 显示全部楼层
max30102模块内部的LED满足什么条件会亮
minzisc 发表于 2021-10-2 12:45 | 显示全部楼层
美信官方提供的两个算法PBA和SKA
selongli 发表于 2021-10-2 12:46 | 显示全部楼层
采集PPG,包括信号处理及心率血氧算法
fentianyou 发表于 2021-10-2 12:46 | 显示全部楼层
MAX30102心率血氧显示例程   
xiaoyaodz 发表于 2021-10-2 12:46 | 显示全部楼层
有工程文件吗      
febgxu 发表于 2021-10-2 12:46 | 显示全部楼层
做滤波算法了吗     
sdlls 发表于 2021-10-2 12:47 | 显示全部楼层
有max30102或者30100的算法详解吗   
pixhw 发表于 2021-10-2 12:47 | 显示全部楼层
max30102实现脉搏检测吗   
febgxu 发表于 2021-10-2 12:47 | 显示全部楼层
uiint 发表于 2021-10-3 20:13 | 显示全部楼层
楼主使用的是什么算法?     
hellosdc 发表于 2021-10-3 20:14 | 显示全部楼层
用过这个测量心率,太跳了。   
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:Microchip
简介:让我们来为您提供帮助。我们可提供各种资源来帮助您解决一切问题。是否需要与我们的客户支持团队联系?您可以通过电话、在线聊天功能或电子邮件与他们联系。

151

主题

1063

帖子

11

粉丝
快速回复 在线客服 返回列表 返回顶部