姓名:杨晶晶 学号:21011210420 学院:通信工程学院
转载自:https://blog.csdn.net/malele4th/article/details/79361030
【嵌牛导读】
运动性肌疲劳是体育界和运动医学界十分关注的课题。目前,许多研究致力于寻找*定和预防肌肉疲劳产生的方法,大多数研究是从全身的生理、生化状况来推断肌肉的功能状况,直接进行局部肌肉的研究还很少。表面肌电信号(sEMG信号)是从皮肤表面通过电极引导、放大、显示和记录下来的神经肌肉系统活动时的生物电信号,信号形态具有较大的随机性和不稳定性。它与肌肉的活动状态和功能状态之间存在着不同程度的关联性,因而能在一定的程度上反映神经肌肉的活动,在康复医学领域的肌肉功能*价以及在体育科学中的疲劳判定、运动技术合理性分析等方面均有重要的实用价值。表面肌电信号采集属无创性,操作简单,病人易接受,有着广泛的应用前景。
【嵌牛鼻子】明确肌电信号的采集需要的单片机功能,编写代码,对代码进行分析。
【嵌牛提问】如何用A/D、Timer、DMA采集肌电信号?如何利用STM32比编写程序?
【嵌牛正文】
STM32采集肌电信号
目录
1.采集方式ADCTimerDMA
2.采集程序的配置
3.对采集的sEMG的分析
4.STM32F407源码
1采集方式ADC+Timer+DMA
(1)肌电信号采集板有双通道,信号的放大倍数可调,采样频率可调
(2)使用STM32的ADC多通道+Timer触发+DMA传输模式采集肌电信号
(3)通过串口将数据实时发送给上位机。
2采集程序的配置
肌电信号采集的ADC通道配置子程序如下:
(1)初始化ADC通道的引脚复用功能
(2)设置传输数据的DMA方式
(3)设置ADC通道的采样频率,触发模式,扫描模式等
(4)设置定时器和定时器中断
void ADCInit(void)
{
ADCInit_GPIO();
ADCInit_DMA();
ADCInit_ADC();
ADCInit_Nvic();
ADCInit_Timer();
}
3.对采集的sEMG的分析
肌电信号的采样频率是500HZ,对原始信号进行频率变换后可以看到50HZ的工频噪声干扰较大,采用50HZ的数字陷波器滤除工频噪声干扰。采集到的肌电信号最主要的能量集中在20-200HZ。
对采集到肌电信号进行预处理、提取特征,输入到分类模型,得到的正确率如下表所示:
4.STM32F407源码
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "pwm.h"
#include "adc.h"
int main(void)
{
char buff1[5],buff2[5];
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init();
ADCInit();
while(1)
{
if(dateFlag==1) //判断数据是否已经更新完成
{
sprintf(buff1,"%.6f,",ch1); //sprintf()打印到字符串中,printf打印到命令行输出
printf("%s",buff1);
sprintf(buff2,"%.6f,",ch2);
printf("%s",buff2);
dateFlag=0;
}
}
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
#include "usart.h"
#define N 5 //每通道采5次
extern double ch1,ch2; //用来存放采集结果
extern u8 dateFlag; //数据转换完成标志
static void ADCInit_GPIO(void);
static void ADCInit_ADC(void);
static void ADCInit_DMA(void);
static void ADCInit_NVIC(void);
void ADCInit_Timer(void);
void ADCInit(void);
double Get_Adc1(vu16 advalue);
#endif
adc.c
#include "adc.h"
#include "delay.h"
/* 数据定义 */
vu16 AD_Value[N]; //用来存放ADC转换结果,也是DMA的目标地址
double ch1,ch2; //用来存放采集的结果
u8 dateFlag=0;
u8 UpdataTIM = 0;
/*
* Function : static void ADCInit_GPIO(void)
* Description : ADC GPIO初始化
*/
static void ADCInit_GPIO(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //使能GPIOA时钟
//ADC通道初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //PA0,PA1 ADC通道
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //不带上下拉
GPIO_Init(GPIOF, &GPIO_InitStructure); //初始化
}
/*
* Function : static void ADCInit_ADC(void)
* Description : ADC模式初始化
*/
static void ADCInit_ADC(void)
{
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE); //使能ADC3时钟
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3,ENABLE); //ADC3复位
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3,DISABLE); //复位结束
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; //两个采样阶段之间的延迟5个时钟
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; //DMA失能(对于多个ADC通道)
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4; //预分频4分频
ADC_CommonInit(&ADC_CommonInitStructure); //初始化
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //12位模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式(多通道ADC采集需要用扫描模式)
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //关闭连续扫描
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising; //上升沿触发
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; //定时器事件2触发ADC
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 2; //2个转换在规则序列中
ADC_Init(ADC3, &ADC_InitStructure); //ADC初始化
//连续模式下通道的配置
ADC_RegularChannelConfig(ADC3, ADC_Channel_4, 1, ADC_SampleTime_15Cycles); //PA0,VIN1,通道0,rank=1,表示连续转换中第一个转换的通道
ADC_RegularChannelConfig(ADC3, ADC_Channel_5, 2, ADC_SampleTime_15Cycles); //PA1,VIN2,通道1
ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE); //连续使能DMA
ADC_DMACmd(ADC3, ENABLE); //使能ADC_DMA
ADC_Cmd(ADC3, ENABLE); //开启AD转换器
}
/*
* Function : static void ADCInit_DMA(void)
* Description : ADC使能DMA模式
*/
static void ADCInit_DMA(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); //时钟使能
//DMA设置
DMA_InitStructure.DMA_Channel = DMA_Channel_2; //选择通道号
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC3->DR); //外围设备地址,ADC_DR_DATA规则数据寄存器
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)(u16 *)AD_Value; //DMA存储器地址,自己设置的缓存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //传输方向:外设到存储器
DMA_InitStructure.DMA_BufferSize = N*2; //DMA缓存大小,数据传输量N*2
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_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure); //初始化DMA2_Stream0,对应为ADC3
//设置DMA中断
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TC); //清除中断标志
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE); //传输完成中断
DMA_Cmd(DMA2_Stream0, ENABLE); //使能DMA
}
/*
* Function : void ADCInit_Timer(void)
* Description : ADC触发定时器的设置
*/
void ADCInit_Timer(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
TIM_Cmd(TIM2, DISABLE); //失能时钟
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); //初始化定时器
TIM_TimeBaseStructure.TIM_Prescaler = 168-1;
TIM_TimeBaseStructure.TIM_Period = 200-1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//使能定时器中断
TIM_ARRPreloadConfig(TIM2, ENABLE); //允许TIM2定时重载
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
TIM_Cmd(TIM2, ENABLE); //使能TIM2
}
/*
* Function : void ADCInit_Nvic(void)
* Description : 中断初始化
*/
static void ADCInit_Nvic(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
//定时器中断设置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //定时器TIM2中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
//DMA中断设置
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; //DMA2_Stream0中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/*
* Function : void ADCInit(void)
* Description : ADC初始化函数
*/
void ADCInit(void)
{
ADCInit_GPIO();
ADCInit_DMA();
ADCInit_ADC();
ADCInit_Nvic();
ADCInit_Timer();
}
/*
* Function : void TIM2_IRQHandler(void)
* Description : TIM2??????
*/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update))
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
/*
* Function : void DMA2_Stream0_IRQHandler(void)
* Description : DMA2_Stream0中断
*/
void DMA2_Stream0_IRQHandler(void)
{
u16 period = 0;
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //判断DMA传输完成中断
{
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
ch1=Get_Adc1(AD_Value[0]);
ch2=Get_Adc1(AD_Value[1]);
dateFlag=1;
//判断是否更新TIM2
if(UpdataTIM)
{
period = 200-1;
TIM_ARRPreloadConfig(TIM2, DISABLE);
TIM2->ARR = period ;
TIM_ARRPreloadConfig(TIM2, ENABLE);
}
}
}
double Get_Adc1(u16 adValue)
{
return (double)(adValue * 3.3 / 4096);
}