STM32识别肌电信号

姓名:杨晶晶 学号: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。


原始肌电信号、频率变换后的信号、50Hz陷波后的信号

对采集到肌电信号进行预处理、提取特征,输入到分类模型,得到的正确率如下表所示:

三种动作识别率

4.STM32F407源码


用到main.c、 adc.c、 adc.h

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);

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容