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

1、PulseSensor简介

(1)工作原理

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

(2)性能参数

PulseSensor的基本性能参数如下所示:
电路板直径:16mm
电路板厚度:1.6mm(普通 PCB 板厚度)
LED 峰值波长:515nm(绿光)
供电电压:3.3V 或 5V
输出信号类型:模拟信号
输出信号大小:0~3.3V(3.3V 电源)或 0~5V(5V 电源)

(3)引脚说明

PulseSensor的背面如下图所示:
在这里插入图片描述
电路板下方有3根引线引出,其从左到右分别为:
S:Singal,模拟信号输出,接入单片机上的GPIO引脚(配置成模拟输入功能以进行电压采集)
+:电源正极,接入单片机的5V引脚或3,3V引脚
–:电源负极,接入单片机的GND引脚

2、DMA在PulseSensor开发中的配置

(1)DMA简介

DMA(Direct Memory Access)—直接存储器存取,在单片机中是独立于内核的一个单独外设,其主要功能是在不占用CPU的情况下****,实现从存储器到存储器,存储器到外设以及外设到存储器的快速传输
当外设想通过DMA向存储器传输数据时,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号。当外设应答后且DMA收到应答信号时,就好启用DMA的传输,直到传输完毕。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求。其中具体的映像对照如下:
DMA1的7个外设通道所控制的外设:
在这里插入图片描述
DMA2的5个外设通道所控制的外设:
在这里插入图片描述

(2)DMA在PulseSensor开发中的注意事项

1、数据传输方向
DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。在PulseSensor的开发中我们将DMA传输数据方向定为**从外设到存储器。这里的外设是ADC的规则数据寄存器ADC1_DR,存放着转换得到的心电。存储器则是自己定义的数组ADC_ConvertValue[NUMCHANNEL]。**在处理输出信息时我们只需要操作数组ADC_ConvertValue[ ]即可。由于开启的是ADC1进行电压采集,所以对应着DMA1的通道1.这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR 配置。
2、传输的数据单位
由于ADC的采集精度是12位,即在进行数据读取处理中至少应该用一个16位大小的变量(数组)对数据进行存储。即外设数据的大小应该为两个字节(HalfWord)。并且为了确保数据传输正确,我们也同样令存储器的数据宽度为两个字节。
3、外设和存储器的地址增量
外设是ADC的规则数据寄存器ADC1_DR,该寄存器只有一个且地址保持不变。而存储器为数组ADC_ConvertValue[ ],在数组的某一个位置传入数据时,下一次数据的传入在当前数组地址的下一个位置。所以应该保持存储器地址的递增,

(3)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开发中的配置

(1)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口(外设)如下图所示:
在这里插入图片描述

(2)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工作模式必须配置成模拟输入。这主要考虑到其余三种输入配置模式(浮空输入,上拉输入,下拉输入)对输入电压均有钳制在高电平或低电平的行为,会使信号造成严重失真。仅有模拟输入模式对于输入电压不会有任何调幅处理。
2、转换结束行为
当电压转换结束时。ADC可以有两种方式提醒系统转换结束并且读取数据。**一种是中断,**而中断触发类型又分成了规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断三种;**另一种是DMA请求,**把直接转换好的数据直接存储在内存里面。需要注意的是只有ADC1和ADC3才可以产生DMA请求。
3、电压转换
模拟电压经过 ADC 转换后,是一个12位的数字值,如果直接以串口的方式打印出来或者直接对其进行数据处理,不仅使可读性降低,还使可读性降低,还增加了数据处理难度。因此我们还需要一套转换公式将12位的数字值转换成实际的电压。考虑到数字值与实际电压值是线性对应的关系且当12位满量程时。对应的电压大小为3.3V。那么久可以得到这样的转换公式:
实际电压值V = (3.3 * 12位数字测量值X)/ 2^12。

(3)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[ ]来随时获取心电值了。

(1)主函数程序

int main(void)
{
	int a1, a2, a3, a4;
	/**初始化Usart*/
	Usart_Config();
	/**初始化ADC和DMA配置*/
	ADCx_Init();
	/**初始化基本定时器TIM3,配置成1ms产生一次定时器中断*/
	CURRENT_TIM_Init();
	while(1)
	{
		/**20ms传一个数据*/
		if(tim3_count >= 20)
		{ 	
			/**采集电压,可以根据实际需要在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;
		}
	}
}

(2)上位机波形显示

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

5、小结

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

版权声明:本文为CSDN博主「青渡QAQ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48206270/article/details/121482117

生成海报
点赞 0

青渡QAQ

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

TCRT5000循迹模块原理及应用

前言 本文将讲述TCRT5000循迹模块的原理及应用。本文应用于STM32,对于使用循迹模块的你有一定的帮助。 以下是本篇文章的正文内容 一、TCRT5000循迹模块介绍 TCRT5000就是一个红外发射和接收器&#xff0

STM32驱动BMP280模块

BMP280大气压传感器(我直接叫他高度传感器)看似很冷门,或许大家都觉得,大气压不是一个地区就那么一个值.测量它有什么用?但是这个模块很神奇,它测量精度很高,大气压和所处海拔关系密切,但