案发条件
ADC连续单通道模式AD采集,触发DMA获取数据。程序如下
void ADC_DMAInit(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_HSICmd(ENABLE); // Enable HSI Clock
ADC_IO_Init();
while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_DeInit(ADC1);
//AD转换结果为8位
ADC_InitStructure.ADC_Resolution = ADC_Resolution_8b;
//AD工作在单通道模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
//模数转换工作在连续模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
//转换由定时器触发启动ADC_ExternalTrigConv_T2_CC2
//ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//上升沿下降沿触发ADC_ExternalTrigConvEdge_RisingFalling
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//ADC数据右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//顺序进行规则转换的ADC通道的数目
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间(取最大值)
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_4Cycles); // CH0
//ADC_InterruptInit();
ADC_ClearFlag(ADC1,ADC_FLAG_EOC);
//使能ADC1的DMA请求
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
//等待ADC1准备好
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADONS) == RESET)
{
}
//循环通道没有准备好
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_RCNR) != RESET);
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConv(ADC1);
}
DMA工作在从AD到内存的单次工作模式,开启完成触发中断,程序如下
/* 针对ADC的应用来配置DMA */
void DMA_ADCInit(void)
{
DMA_InitTypeDef DMA_InitStructure;
//开启DMA的第一通道的时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//复位DMA1通道的设置
DMA_DeInit(DMA1_Channel1);
//DMA对应的外设基地址,AD采样的数据存放地址
DMA_InitStructure.DMA_PeripheralBaseAddr = ((uint32_t)ADC1_DR_ADDRESS);
//DMA对应的内存基地址
DMA_InitStructure.DMA_MemoryBaseAddr =(uint32_t)g_u16ADC_DataArr;
//DMA缓存大小
DMA_InitStructure.DMA_BufferSize = DMA_BUFF_SIZE;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//DMA的转换模式是SRC模式,就是从外设向内存中搬运
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//接收一次数据后,设备地址是否后移
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//接收一次数据后,目标内存地址是否后移
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//转换结果的数据大小
DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
//DMA搬运的数据尺寸,16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//DMA优先级,高
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
//M2M模式禁止
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//使能DMA传输完成中断中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
/* Configure the SysTick Handler Priority: Preemption priority and subpriority */
IRQ_DMA1_Channel1_Config();
/* Enable DMA channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
}
想要解决的问题:
在DMA触发完成中断后,如果想重新开启AD和DMA继续采集数据。如何做?请继续向下看。
DMA中断处理程序见下
void DMA1_Channel1_IRQHandler(void)
{
DMA_InitTypeDef DMA_InitStructure;
if(DMA_GetITStatus(DMA1_IT_TC1))
{
DMA_ClearITPendingBit(DMA1_IT_TC1);
DMA_ClearITPendingBit(DMA1_IT_HT1);
//复位DMA1通道的设置
/* Disable the selected DMAy Channelx */
DMA1_Channel1->CCR &= (uint16_t)(~DMA_CCR1_EN);
//DMA缓存大小
DMA_SetCurrDataCounter(DMA1_Channel1 , DMA_BUFF_SIZE);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_ClearFlag(ADC1,ADC_SR_OVR);
ADC_ClearFlag(ADC1,ADC_SR_STRT);
ADC_SoftwareStartConv(ADC1);
}
上面的中断处理程序是重新开启AD和DMA的最少处理语句,缺一不可。
带Clear的语句是清除DMA全部完成中断标志,完成一半中断标志,AD的OVR异常标志和regular通道转换完成标志。这是必须的。
两个重新开启AD和DMA的命令也不多说。
由于DMA中的剩余个数在全部完成中断里面时已经为0,需要对它进行重新配置也可以理解。
其中让人不理解的就是 DMA1_Channel1->CCR &= (uint16_t)(~DMA_CCR1_EN);和没有重新设置DMA设置内存目的地址得语句。
芯片硬件又不是我们自己设计的,这么用,估且我们赞同这种现象。但通过这个现象我们能重新理解DMA的运行机制。
我总结如下:
DMA1_Channel1->CCR &= (uint16_t)(~DMA_CCR1_EN);表明:虽然,DMA进入中断以后,去除掉了中断标志,但这是DMA已经停止,但此时通过仿真可以查看到CCR寄存器的中最低位EN是1,虽然如此,但实际上DMA已经关闭,此时必须通过先复位EN位,就是上一行写的语句,再置位EN位,这种方式来重新开启DMA。
没有重新设置DMA设置内存目的地址得语句:表明:在DMA参考手册中有一句话在DMA运行时源地址和目的地址是不可以读取的,这个例子DMA在运行时是源地址不变,目的地址在不断加2个字节。但当DMA停止后目的地址又恢复到最初初始化给它的值,或者是在DMA运行时目的地址改变的是芯片内部一个我们看不到的地址。