采集单片机的程序
- 首先,需要找一个stm32f030的例程框架,此框架最好包含串口通信的部分(一般都有);
- 然后把相应的程序填入就可以了;这部分详见例程分析;
例程分析:
首先,我们看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控制器;