STM32 CubeIDE ADC及AD8232模块心电采集实验

Goal

  1. 配置USART1为异步通讯模式,波特率为115200,配置PA1为ADC单通道(中断或者DMA均可)采集模式
  2. 使用AD8232心电采集模块,配置相应的其余IO口
  3. 功能:(1)将测到的心电数据转换成电压,并通过串口输出;  (2) 通过算法计算心率,每一分钟通过串口打印

Background

模数转换器(ADC)【以光敏电阻传感器模块为例】

    图 1 5V容忍I/O端口位的基本结构

        把GPIO口设置为读取模式,信号从GPIO口经过TTL施密特触发器输入数据到寄存器,TTL施密特触发器可以把连续信号通过一个分界线,将模拟信号在界限上的部分转换为高电平脉冲并将界限下的部分转换为低电平脉冲。通过一个“模拟”功能,将GPIO进入的信号输入模拟口也即AD口,把连续信号模拟量转化为数字量接到外设。

        输入GPIO口的模拟量在此以光敏电阻传感器的模拟值变化为例。

图 2 光敏电阻传感器原理图

光敏二极管依照光强改变阻值Rgm,VAD = Rgm/(Rgm+10k)*3.3V,单片机这个AD口就是把接收到的VAD电压转换为程序中读取的值。

心电模块(AD8232)

 表 1 Pins of AD8232

GND 接地

3.3V 给模块供电电源

OUT 运放输出端,接到ADC的输入端

LOD-,LOD+接到身上的三个电极,导联脱落,这两个口某一个口会变为高电平,所以将这两个口接到输入口上

SDN 开启和关闭模块功能,接到GPIO输出口,给它高电平则模块工作,给低电平则不工作。

 图 3 电极连接方式

Experiment Steps

CubeIDE UART的中断配置

根据题意配置USART1为异步通讯模式Asynchronous,波特率为115200 Bits/s,字长为8 Bits。

勾选ADC1为IN1模式。对其进行参数设定。

PCLK2 divided by 4 (板子电压3.3V,对应最高频率为36MHz,在时钟设置时PCLK2为84MHz,分频要小于最高频率,所以选择divided 4)

表 2 ADC 特性

Resolution分辨率:VAD的值转换进单片机有多大是取决于Resolution, 12 bits 表示212,0-4096,把3.3V划分到4096份中,决定采样数据多精细。3.3/4096 = 0.000805V,就是最小能采集到0.000805V的电压。

Alignment:一个字节8位需要两个字节存储,则有4位的空闲,左对齐和右对齐,低八位和高八位,先填充低八位还是高八位的问题。选择Left alignment和Right alignment都行。

Scan Conversion Mode: 扫描模式,不用一个个开启AD口,但是现在只需要一个AD口所以Disabled。

Continuous Conversion Mode:每次采集完一个数据会自动开启下一个采集循环,所以Enabled。

Discontinuous Conversion Mode:和上面相反,选择Disabled。

DMA Continuous Requests:用的是中断所以不使能DMA。

End Of Conversion Selection: 采集单通道进入一次中断,还是把所有通道转化完再进入中断,正常一般选择采集全部通道再进入中断EOC flag at the end of all conversions。

Number Of Conversion: 采集通道数量。

External Trigger Conversion Source:用软件触发还是用定时器触发,选择软件触发。

Rank: 排序,谁先采集谁后采集的排序问题,该例子中只采集一个信号通道故为1。

PCLK2最高为84MHz,分为4份,即21MHz,一个机器周期时间为1/21000000=4.7619e-8 一个机器周期的时间,每一次需要(Sampling Time)三个机器周期采样,需要12个机器周期将数据放到12位,每采集一个数据的转换时间为一个机器周期的时间乘(12+3) = 7.14286e-7s,因为每采集完一个数据就会进入一次中断,程序执行中进入中断次数太多,把采样时间设置大些,到480Cycles。

NVIC Settings:使能中断。

在NVIC中勾选ADC1,ADC2 and ADC3 global... 把中断从main函数分离出来,Code Generator中勾选Generate peripheral Initialization as a pair of ...把函数从main中分离出来。

生成代码。

在main函数中MX_ADC1_Init();进入adc.c,在adc.c中在void MX_ADC1_Init(void)中调用HAL_ADC_Start_IT(&hadc1); 开启中断。因为要HAL_ADC_Star_IT要传入指针,所以加个&给hadc1代表地址。

在it.c中找到ADC_IRQHandler点进去找到HAL_ADC_IRQHandler,在它里面找到HAL_ADC_ConvCpltCallback点进去,然后复制代码到main.c。

变量值没有正负之分则用unsigned,定义全局变量unsigned int ADC_Value = 0, 在函数中写if(hadc == &hadc1){ADC_Value = HAL_ADC_GetValue(hadc)};

(Get一般用在内部模块,Read一般用在GPIO)

在while中写入

sprintf(buff, “V = %.3f V\r\n”, ADC_Value * 3.3 / 4096.0);

HAL_UART_Transmit(&huart1, buff, strlen(buff), 5000);

HAL_Delay(500);

在int main中定义uint8_t buff[1024] = {0}; 通过buff将数据输出到串口。

【如果输入sprintf报错,则在project中的properties中的C/C++ Build中的Settings中的MCU Settings中打开use float with printf from newlib-nano就可以使用C语言中可以使用的包如scanf,sprintf,sprintf为字符串拼接函数,%.3f 保留三位小数的float类型,把”V = %.3f V\r\n”变成字符串传给buff, .3f的值为ADC_Value采集到的值,因为分辨率为4096,再乘3.3V,也即光敏电阻分到的电压到底有几伏。】

单通道DMA配置

系统内部划分了缓冲区,在ADC转换完后把数据丢进缓冲区,当需要时调用,就不用进入中断操作,需要在配置中把ADC1中IN1中Parameter Settings中DMA Continuous Requests改为Enabled,但是不能直接选,要先去DMA中的DMA2中Add一个ADC1,Mode为Circular循环模式,数据放进后循环一直拿进缓冲区,没取掉则不停覆盖。然后在NVIC Settings中勾选DMA2 stream0 global interrupt,取消勾选ADC1,ADC2 and ADC3 global interrupts。

生成代码

在adc.c中不需要加入开启中断,直接在main.c定义全局变量uint32_t ADC_Value = 0;

在int main中while外写HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_Value, 1);

采集到的数据存入ADC_Value中,需要指针形式,length为一次性取多少次uint32的数据,因为DMA中有很多数据,自己定义数组可以读取你想要的数据量,但是因为单通道所以length直接为1。有了这个函数后,每次采样的数据都会自动存入ADC_Value中。

AD8232心电模块配置

初始配置,与以上ADC中断相同,再找PB0,PB1两个口为GPIO_INPUT,接在LOD-和LOD+,把PA2作为GPIO_OUTPUT。

GPIO PA2配置:GPIO output level为High即直接工作

PB0,PB1作为GPIO input默认拉低,分别设置为LO0和LO1

在Connectivity中串口选USART1,选择Asynchronous,其他默认

NVIC勾选ADC1,ADC2...

Code Generator中勾选Generate peripheral initialization...

生成工程

在adc.c加入开启中断的一句话:HAL_ADC_Start_IT(&hadc1);

Q2的思路分析:设定心电中间幅值,当采集到的值大于这个值是计数加一,只有当出现小于这个幅值后再采集到大于这个幅值计数才再加一。

Q1代码如下:

uint16_t ADC_Value = 0; 
int main(void)
{
  uint8_t buff[1024] = {0};//作为模拟信号转换为数字信号的存储
  int time = 0;
  int count = 0;
  bool flag = FALSE;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  while (1)
  {
	  if(HAL_GPIO_ReadPin(LO0_GPIO_Port, LO0_Pin) == GPIO_PIN_SET || HAL_GPIO_ReadPin(LO1_GPIO_Port, LO1_Pin) == GPIO_PIN_SET){
		  sprintf(buff, "Something Error!\r\n");
	  }
	  else{
		  sprintf(buff, "V = %.3f V\r\n", ADC_Value / 4096.0 * 3.3);
	  }
	  HAL_UART_Transmit(&huart1, buff, strlen(buff), 5000);
	  HAL_Delay(500);
  }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  if(hadc == &hadc1){
	  ADC_Value = HAL_ADC_GetValue(hadc);
  }
}

Q2代码如下:

#include "main.h"
#include "adc.h"
#include "usart.h"
#include "gpio.h"
uint16_t ADC_Value = 0;
#define CRIT (1.8/3.3*4096) //作为计算心跳的电压临界值
void SystemClock_Config(void);
int main(void)
{
  uint8_t buff[1024] = {0};//作为数字信号的存储
  int time = 0;
  int count = 0;
  bool flag = FALSE;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  while (1)
  {
	  if(time == 6000){
		  sprintf(buff, "V = %d /minute\r\n", count);
		  time = 0;
		  count = 0;
		  HAL_UART_Transmit(&huart1, buff, strlen(buff), 5000);
	  }
	  else{
		  HAL_Delay(10);
		  time++;
		  if(ADC_Value > CRIT){
			  if(!flag){
				  flag = TRUE;
				  count++;
			  }
		  }
		  else{
			  if(flag){ //1.8V作为计算心电电压阈值
				  flag = FALSE;
			  }
		  }
	  }
  }
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  if(hadc == &hadc1){
	  ADC_Value = HAL_ADC_GetValue(hadc);
  }
}

Result

测得心电信号峰值约为2.3V,且心率约为90/min。

   

 

 

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

生成海报
点赞 0

GiaG*

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

暂无评论

发表评论

相关推荐