采集单片机的程序

例程分析:

首先,我们看main函数:

int main(void)
{    system_init();      //系统初始化,初始化IO口等
    USART_ENABLE_CMD();        //打开串口命令处理
    while(1)     //主消息循环
    {     
        if (sec>1){
            CompADC();    //计算采集AD的平均值
            sprintf(buf,"app.da(\"%d|%d\");\r\n",After_filter[1],After_filter[0]);
            send(buf);
            sec=0;
        }
    }
}

可以看到整个代码实际上分两个部分,第一部分完成整个单片机设备的初始化,第二部分是一个大循环,中间检测sec变量是否被置位,如果置位则通过串口发送当前采集到的AD值;可以看到sec变量是由时间中断程序来置位的,通过单片机的硬时钟中断,可以更精准的控制时间间隔;

第一部分:系统初始化部分

概述

这部分分为: - GPIO的初始化 - 串口的初始化 - 定时器的初始化 - AD采集(DMA模式)的初始化以上都是stm32单片机的基本功,这里只讲述初始化目标,具体如何初始化,请参见stm32的标准资料或例程

GPIO的初始化

void GPIO_init(void)
{    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    //初始化PA  AD
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

该段程序把PA0 PA1 初始化成AD模式;

串口的初始化

这个基本上都是样例代码,把串口初始化成115200波特率的串口,并且配置串口接收中断以及中断处理程序;代码过于标准,不展示了;

定时器的初始化

void TIMER14_Init(void)        
{    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
     NVIC_InitTypeDef NVIC_InitStructure; 
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = TIM14_IRQn;//指定中断源
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;// 指定响应优先级别1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

     TIM_TimeBaseStructure.TIM_Period=500;
     TIM_TimeBaseStructure.TIM_Prescaler=6400;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;    
     TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式

    TIM_TimeBaseInit(TIM14, &TIM_TimeBaseStructure);
     TIM_ITConfig(TIM14,TIM_IT_Update,ENABLE);

     TIM_Cmd(TIM14, ENABLE); //开启时钟

}

void TIM14_IRQHandler(void)      //界面刷新
{    if(TIM_GetITStatus(TIM14 , TIM_IT_Update) == SET)
    {     sec++;
        TIM_ClearITPendingBit(TIM14, TIM_FLAG_Update); //清中断
    }
}

可以看到下面的是定时器中断程序,在中断程序中,sec变量在不停的累加,这样可以给main函数中的主消息循环提供一个时间基准;在定时器设置部分,此mcu工作频率是64M,设置的预分频6400,得到64M/6400=10000hz,在经过500分频,大约每秒20次,当然这个时间是我们按显示效果调出来的,不是严格按照定时器设置的基准;

AD采集的初始化

这个是重头,也是比较复杂的一部分,这个要读懂,最好参考一下stm32的相关文章,首先,我们需要将AD采集设置成自动DMA模式,也就是无需MCU中的CPU代码执行来完成AD采集,而是stm32中的硬件完成一秒数次的采集,并自动将结果放在单片机的RAM内存中,这样设置好的单片机系统,在软件层面就可以直接从数组中取值就可以获取到最新的AD值了

首先,我们要声明以下变量:

#define ADC1_DR_Address                0x40012440
#define N 32 //每通道采样32次  M<=32
#define M 2 //通道数
u16 AD_Value[N][M]; //DMA写入的值
u16 After_filter[M]; //过滤器处理之后的值

这个主要规定采样的存储大小和位置

void ADC_init(void)
{    ADC_InitTypeDef     ADC_InitStruct;
    DMA_InitTypeDef     DMA_InitStruct;
    /* ADC1 Periph clock enable */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    /* DMA1 clock enable */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
    ADC_DeInit(ADC1);
    ADC_StructInit(&ADC_InitStruct);

  /* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  */
  ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; 
  ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStruct.ADC_ScanDirection = ADC_ScanDirection_Upward;
  ADC_Init(ADC1, &ADC_InitStruct); 
  ADC_TempSensorCmd(ENABLE);

  ADC_ChannelConfig(ADC1, ADC_Channel_0  , ADC_SampleTime_239_5Cycles); //1    PA0 A1        1                    
  ADC_ChannelConfig(ADC1, ADC_Channel_1  , ADC_SampleTime_239_5Cycles); //2    PA1    A2        2                    

  ADC_VrefintCmd(ENABLE);
     /* DMA1 Channel1 Config */
  DMA_DeInit(DMA1_Channel1);
  DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
  DMA_InitStruct.DMA_MemoryBaseAddr = (u32)&AD_Value;
  DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
  DMA_InitStruct.DMA_BufferSize =N*M;
  DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStruct.DMA_Priority = DMA_Priority_High;
  DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel1, &DMA_InitStruct);
     /* DMA1 Channel1 enable */
  DMA_Cmd(DMA1_Channel1, ENABLE);
     /* ADC DMA request in circular mode */
  ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
   /* Enable ADC_DMA */
  ADC_DMACmd(ADC1, ENABLE);  
  /* ADC Calibration */
  ADC_GetCalibrationFactor(ADC1);
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);     
  /* Wait the ADCEN falg */
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); 
  /* ADC1 regular Software Start Conv */ 
  ADC_StartOfConversion(ADC1);
}

设置比较复杂,就是开启AD和DMA,并将AD值存储到AD_Value这个数组对应的地址中,并且设置成循环采集;

第二部分:消息循环部分

消息循环部分比较好理解,sec变量控制发送频率这个前面已经讲了,这里面主要看一下CompADC 函数:

void  CompADC(void)
{    int sum = 0;  
    u8 i;
    u8 count;
    for(i=0;i<M;i++) 
    { for ( count=0;count<N;count++) 
        {     sum += AD_Value[count][i]; 
        } 
        After_filter[i]=sum/N; 
        sum=0; 
    } 
}

前面说了,DMA硬件会不停的把采集到的AD值刷新入AD_Value这个数组中,而这段程序实际上就是一个简单的加权平均的滤波算法,实际上就是过采集后求平均值,如果需要更改为其他滤波算法,可以自行修改本函数;函数处理完,存放在After_filter数组中最后:

sprintf(buf,"app.da(\"%d|%d\");\r\n",After_filter[1],After_filter[0]);
send(buf);

通过sprintf把app.da("ad1|ad2");通过串口发送给sHMIctrl控制器;