最近在使用stm32l151开发一个项目,我的项目需求是ADC采集电池电量,通过DMA通道传送出来。然而我并不是得到了电池电量数据后就立马连续输出,而是通过tim4定时器每1s访问一次采样得到的电池数据,并显示出来。本来网上关于stm32通过adc通道采集电池电量的代码很多,但要找到和我的需求一样的,还真没有。于是在借鉴其他人代码的基础上,根据我的特殊需求,写了一份这样功能的代码。
因为我的需求涉及到了tim4定时器,adc和dma,所以在最终配置的时候也分为几个部分:
1.首先是定时器tim4。
a)初始化
void vTim4_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = TIMER4_PERIOD_TIMING;
TIM_TimeBaseStructure.TIM_Prescaler = u32Tim4PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
/* TIM IT enable */
TIM_ITConfig(TIM4, TIM_IT_Update |TIM_IT_Trigger, ENABLE);
/* TIM14 disable */
TIM_Cmd(TIM4, DISABLE);
}
b)NVIC中断设置
/* Enable the TIMER4 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 10;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
c)Tim4中断函数书写,我这里是通过给任务一个信号量longtimerTaskSemaphoreHandler,唤醒获取电量的任务来获取电量数据并显示。
void vTimer4_Interrupt(void)
{
BaseType_t xHigherPriorityTaskWoken;
if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
xSemaphoreGiveFromISR(longtimerTaskSemaphoreHandler,&xHigherPriorityTaskWoken);
if(xHigherPriorityTaskWoken == pdTRUE)
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
2.其次是adc。
a)adc的GPIO口初始化
void ADC_GPIO_INIT()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //打开对应GPIO的时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
b)adc通道的初始化设置
void ADC_INIT()
{
ADC_InitTypeDef ADC_InitStructure;
RCC_HSICmd(ENABLE); //开启HSI时钟,非常重要,stm32l151的时钟由HSI提供
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1时钟
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //关闭扫描模式,因为多ADC通道同时使用时才打开
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换模式,因为我需要连续取十次数据
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //不适用外部触发转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //分辨率是12位
ADC_InitStructure.ADC_NbrOfConversion = 1; //要转换的通道数目1
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_48Cycles); //设置adc1的通道0的转换周期为48个采样周期
ADC_DMACmd(ADC1, ENABLE); //启动DMA搬运ADC数值
ADC_Cmd(ADC1, ENABLE); //打开ADC
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADONS) == RESET); //等待ADC1转换完成,必须返回值是SET
ADC_SoftwareStartConv(ADC1); //打开软件触发
}
3.dma的设置。
a)dma初始化设置
void DMA_CONFIG()
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA时钟
DMA_DeInit(DMA1_Channel1); //DMA复位
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(ADC1->DR)); //ADC地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //存储地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //DMA传输方向为设备到内存
DMA_InitStructure.DMA_BufferSize = 10; //存储大小,所以前面的ADC我设置的是连续转换
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外存地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址递增,这样从ADC转换过来的数据才能依次传到目标地址上,否则的话就是固定写在一个地址上,并且前面的数据把后面的覆盖了
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //每次发送是半字,即16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //每次存储数据单位为半字,即16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环AD
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA优先级高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA禁止内存到内存
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //DMA初始化
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //使能DMA发送完中断
DMA_ClearITPendingBit(DMA1_IT_TC1); //清除中断位
DMA_Cmd(DMA1_Channel1, ENABLE); //启动DMA通道1
}
b)dma的中断设置
void DMA_CONFIG()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
c)dma中断函数书写。
void DMA1_Channel1_IRQHandler(void)
{
u32 i;
u32 After_Buff=0;
if (DMA_GetITStatus(DMA_IT_TC) != RESET)
{
DMA_ClearITPendingBit(DMA1_IT_TC1); //清除DMA中断标志位
ADC_DMACmd(ADC1, DISABLE); //停止DMA搬运ADC数值
ADC_Cmd(ADC1,DISABLE); //停止ADC转换,我是通过Tim4定时器产生的中断来开关DMA和ADC。
for(i = 0;i < buff_size;i++)
{
After_Buff = After_Buff+ADC_ConvertedValue[i];
}
After_Filter=After_Buff/10; //强制转换后电量显示正常
After_Buff=0;
}
}
最后回到mian()函数部分了,通过Tim4定时触发读取电池电量的任务,读取并显示。
void AdcTransfer(void)
{
u32 powervalue;
ADC_DMACmd(ADC1, ENABLE); //开启DMA搬运数据
ADC_Cmd(ADC1, ENABLE); //开启ADC转换
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADONS) == RESET) {}; //如果ADC允许使用
ADC_SoftwareStartConv(ADC1); //开启ADC
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)); //如果ADC转换结束
powervalue = (uint16_t)((After_Filter*3300*3)>>12);
}
至此,读取ADC电量步骤圆满完成!