PulseSensor开发文档(一)DMA ADC数据采集

1、PulseSensor简介

工作原理

PulseSensor是一款用于脉搏心率测量的光电反射式模拟传感器。将其佩戴于手指或耳垂等处,通过导线连接可将采集到的模拟信号传输给 stm32、Arduino等单片机,并在单片机上进行模数转换(电压采集)。通过心率计算后就可以得到心率数值,此外还可将心电数据上传到电脑上显示波形。本文将采用STM32F103系列对上述功能进行实现。

PulseSensor的工作流程如下图所示:
在这里插入图片描述

性能参数

PulseSensor的基本性能参数如下所示:

  • 电路板直径:16mm
  • 电路板厚度:1.6mm(普通 PCB 板厚度)
  • LED 峰值波长:515nm(绿光)
  • 供电电压:3.3V 或 5V
  • 输出信号类型:模拟信号
  • 输出信号大小:0~3.3V(3.3V 电源)或 0~5V(5V 电源)

引脚说明

PulseSensor的实物图如下所示:
在这里插入图片描述

电路板下方有3根引脚引出,从左到右分别为:

  • S:Singal,模拟信号输出,接入单片机上的GPIO引脚(配置成模拟输入功能以进行电压采集)
  • +:电源正极,接入单片机的5V引脚或3,3V引脚
  • –:电源负极,接入单片机的GND引脚

2、DMA在PulseSensor开发中的配置

DMA简介

DMA(Direct Memory Access)—直接存储器存取,在单片机中是独立于内核的一个单独外设,其主要功能是在不占用CPU的情况下,实现从存储器到存储器,存储器到外设以及外设到存储器的数据传输

当外设通过DMA向存储器传输数据时,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号。当外设应答后且DMA收到应答信号时,就可以启用DMA的传输,直到传输完毕。

DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求。其中具体的映像对照如下:

DMA1的7个外设通道所连接的外设:

在这里插入图片描述

DMA2的5个外设通道所控制的外设:

在这里插入图片描述

DMA在PulseSensor开发中的注意事项

  1. 数据传输方向

DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。在PulseSensor的开发中我们将DMA传输数据方向定为从外设到存储器。这里的外设是ADC的规则数据寄存器ADC1_DR,存放着转换得到的心电数字量。存储器则是数组ADC_ConvertValue[NUMCHANNEL]。在处理输出信息时我们只需要读取ADC_ConvertValue[ ]即可。本次使用ADC1进行电压采集,所以开启DMA1的通道1。这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR 配置。

  1. 传输的数据单位

由于ADC的采集精度是12位,即在进行数据读取处理中至少应该用一个16位大小的变量(数组)对数据进行存储。即外设数据的大小应该为两个字节(HalfWord)。并且为了确保数据传输正确,我们也同样令存储器的数据宽度为两个字节。

  1. 外设和存储器的地址增量

外设是ADC的规则数据寄存器ADC1_DR,该寄存器只有一个且地址保持不变,所以应配置外设地址增量不变。而存储器为数组ADC_ConvertValue[ ],在数组的某一个位置传入数据时,下一次数据的传入在当前数组地址的下一个位置。所以应该保持存储器地址的递增,

PulseSensor中DMA的相关配置程序

/**这里的NUMCHANNEL由实际需求所需采集通道个数决定*/
__IO uint16_t ADC_ConvertValue[NUMCHANNEL] = {0}; 

DMA_InitTypeDef DMA_InitStructure;
/**开启DMA和ADC的时钟,DMA属于高性能模块,应开启AHB时钟作为DMA的时钟驱动*/
RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
/**将DMA通道配置寄存器重设为默认值*/
DMA_DeInit(ADC_DMA_CHANNEL);

/**外设地址为规则数据寄存器ADC_DR*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)( & (ADC_x ->DR));
/**存储器地址为ADC_ConvertValue*/
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertValue;
/**数据读取方向为外设到存储器*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/**数据缓冲区大小为存储器的大小NUMCHANNEL*/
DMA_InitStructure.DMA_BufferSize = NUMCHANNEL;
/**外设寄存器ADC_DR地址值保持不变,地址不用递增*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/**存储器地址递增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/**外设数据大小为半字,即两个字节*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
/**内存数据大小也为半字,跟外设数据大小相同*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
/**将DMA工作模式设置为循环传输模式,DMA操作将持续进行*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/**DMA传输通道优先级为高*/
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
/**读取数据方向是从外设到存储器,所以禁用存储器到存储器模式*/
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
/**按照上述配置初始化DMA*/
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);

3、ADC在PulseSensor开发中的配置

ADC简介

ADC全称为:analog to digital converter,意为模拟数字转换器。 STM32f103系列有3个ADC,精度为 12 位,每个ADC最多有16个外部通道。其中ADC1和ADC2都有16个外部通道,ADC3根据CPU引脚的不同通道数也不同,一般都有 8 个外部通道。

ADC的电压输入范围为0 ~ 3.3V,如果想让输入的电压范围变宽,可以在输入引脚外接一个电压调理电路,把需要转换的电压抬升或下降到0 ~ 3.3V的范围。这样ADC就可以测量对应的电压数值。然后再将读取的电压数值乘以电压调理电路的调节系数(输入和输出的比值)即可。

3个ADC通道对应的IO口(外设)如下图所示:
在这里插入图片描述

ADC在PulseSensor开发中的注意事项

  1. 转换时间以及引脚模式配置

ADC输入时钟ADC_CLK由PCLK2经过分频产生,最大是14M。而采样周期最短为1.5个周期,即至少为0.11us。ADC的转换时间跟ADC的输入时钟和采样时间有关,公式为:T = 采样时间 + 12.5 个周期。当ADCLK = 14MHZ(最高),采样时间设置为1.5周期(最快),那么总的转换时间(最短)Tmin = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。
ADC的电压采集引脚,GPIO工作模式必须配置成模拟输入。这主要考虑到其余三种输入配置模式(浮空输入,上拉输入,下拉输入)对输入电压均有钳制在高电平或低电平的行为,会使信号造成严重失真。仅有模拟输入模式对于输入电压不会有任何调幅处理。

  1. 转换结束行为

当电压转换结束时。ADC可以有两种方式提醒系统转换结束并且读取数据。一种是中断,而中断触发类型又分成了规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断三种;另一种是DMA请求,把直接转换好的数据直接存储在内存里面。需要注意的是只有ADC1和ADC3才可以产生DMA请求。

  1. 电压转换

模拟电压经过 ADC 转换后,是一个12位的数字值,如果直接以串口的方式打印出来或者直接对其进行数据处理,不仅使可读性降低,还使可读性降低,还增加了数据处理难度。因此我们还需要一套转换公式将12位的数字值转换成实际的电压。考虑到数字值与实际电压值是线性对应的关系且当12位满量程时。对应的电压大小为3.3V。那么久可以得到这样的转换公式:
实际电压值 V = ( 3.3 × 12 位数字测量值 ) ÷ 2 12 实际电压值V = (3.3 \times 12位数字测量值) \div 2^{12} 实际电压值V=(3.3×12位数字测量值)÷212

PulseSensor中ADC的相关配置程序

/**
 *@brief     初始化电压采集GPIO端口的配置
 *@attention 用作ADC采集的IO必须不被其他外设复用,否则会对采集电压造成数值上的影响
 *@param     无
 *@retval    无
 */
static void ADCx_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	/**开启GPIO端口时钟*/
	ADC_GPIO_APBxClock_Cmd(ADC_GPIO_CLK, ENABLE);
    GPIO_InitStructure.GPIO_Pin = ADC_PIN1 | ADC_PIN2;
	/**将GPIO工作模式设为模拟输入模式*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(ADC_PORT, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = ADC_PIN3 | ADC_PIN4 | ADC_PIN5 | ADC_PIN6;
	/**将GPIO工作模式设为模拟输入模式*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(ADC_PORT1, &GPIO_InitStructure);
}
ADC_InitTypeDef ADC_InitStructure;
/**开启ADC的时钟*/
ADC_APBxClock_Cmd(ADC_CLK, ENABLE);

/**将ADC工作模式设为独立工作模式*/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
/**在多通道ADC电压采集中开启扫描模式,在单通道电压采集中禁用*/
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
/**开启连接转换模式*/
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
/**不用外部触发转换,采用软件开启的方式*/
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
/**转换结果右对齐*/
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
/**转换通道个数为NUMCHANNEL*/
ADC_InitStructure.ADC_NbrOfChannel = NUMCHANNEL;
/**按照上述配置初始化ADC*/
ADC_Init(ADC_x, &ADC_InitStructure);
/**配置ADC时钟为PCLK2的8分频,即9MHZ*/
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
	
/**配置ADC通道转换顺序和采样时间,其中采样时间为55.5个周期*/
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL5, 5, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL6, 6, ADC_SampleTime_55Cycles5);
	
/**使能DMA直接存储器读取,不用使能中断*/
ADC_DMACmd(ADC_x, ENABLE);
/**使能ADC1*/
ADC_Cmd(ADC_x, ENABLE);
/**开启ADC,并开始转换*/
ADC_ResetCalibration(ADC_x);
/**等待重置完成*/
while(ADC_GetResetCalibrationStatus(ADC_x));
/**启动所选定的ADC的校准过程*/
ADC_StartCalibration(ADC_x);
/**等待校准完成*/
while(ADC_GetCalibrationStatus(ADC_x));
/**启用ADC软件触发转换*/
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);

4、主函数数据采集与输出

DMA和ADC配置好之后,只需要在主函数内初始化一次,就可以通过读取数组ADC_ConvertValue[ ]来随时获取心电值了。

主函数程序

int main(void)
{
	int a1, a2, a3, a4;
	/**初始化Usart*/
	Usart_Config();
	/**初始化ADC和DMA配置*/
	ADCx_Init();
	/**初始化基本定时器TIM3,配置成1ms产生一次定时器中断*/
	CURRENT_TIM_Init();
	while(1)
	{
		/**15/20ms采样一个数据,Period为采样时间,可自己设定*/
		if(tim3_count >= Period)
		{ 	
			/**采集电压,可以根据实际需要在bsp_adc.h中更改引脚*/
		  ADC_ConvertValueLocal[2] = (int16_t)ADC_ConvertValue[2];
			/**按位拆分是为了上位机能正常接收数据,也可根据实际需要直接发送*/
		  a1 = ADC_ConvertValueLocal[2] / 1000;
		  a2 = ADC_ConvertValueLocal[2] % 1000 / 100;
		  a3 = ADC_ConvertValueLocal[2] % 1000 % 100 / 10;
		  a4 = ADC_ConvertValueLocal[2] % 1000 % 100 % 10;
		  printf("AAA%d%d%d%d", a1, a2, a3, a4);
		  tim3_count = 0;
		}
	}
}

上位机波形显示

将数据上传到上位机再用自制的上位机软件中查看得到:
在这里插入图片描述

5、小结

PulseSensor的数据获取部分比较简单,但对于传感器数据的读取只是第一步。对传感器波形数据的分析,心率的计算乃至上位机的设计才是更加关键的部分,这些都会在后续的开发文档中所涉及。

  • 6
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青渡QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值