在MCU的应用场景中,处处都有用到ADC,比如电池电量的采集、温度采集、电机应用中电流检测等等。MM32F013x的ADC模块新增了任意通道工作模式,支持在多种应用场景中更灵活的应用;本文针对任意通道工作模式,分享在MM32F013x上实现任意通道工作模式的使用与具体配置。
1.任意顺序多通道功能
在MM32F013x系列的MCU中新增了ADC对任意通道的支持,在任意通道配置(ADC\_ANY\_CR. CHANY\_MDEN)使能后,其优先级高于常规通道配置,后续的转换按任意通道配置的方式转换。
任意通道模式支持单次转换模式、单周期转换模式和连续扫描模式。
A/D 转换开始条件:
- 软件启动
- 外部触发启动,且软件可配置外部触发延时
- Timer1/2/3 匹配或 TRGO 信号,外部 EXTI 信号源
2.相关的寄存器
具体功能与详细描述,请参考MM32F013x系列的用户手册。
3.任意通道工作模式
3.1 单次转换模式
在单次转换模式下,A/D 转换相应通道上只执行一次,具体流程如下:
- 软件设置寄存器ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,设置转换通道,置位CHANY\_MDEN。(单次转换模式,只需设置CHANY\_SEL0)
- 通过软件、外部触发输入及定时器溢出置位ADCR 寄存器的ADST,开始A/D转换。
- A/D 转换完成时,A/D 转换的数据值将存储于数据寄存器ADDATA 和ADDRn 中。
- A/D 转换完成时,状态寄存器ADSTA 的ADIF 位置1。若此时控制寄存器ADCR 的ADIE位置1,将产生AD 转换结束中断请求。
- A/D 转换期间,ADST 位保持为1。A/D 通道采样结束后,ADST 位自动清0,A/D 转换器进入空闲模式。
- 若在A/D 转换过程中,软件更新ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,然后等待下一次软件置位ADST。
该模式仍然支持通过过配置当外部事件(比如TIM Trig或EXTI)触发转换时序。
3.2 单周期扫描模式
- 在单周期扫描模式下,A/D 转换相应通道上执行一遍按配定顺序的转换,具体流程如下:
- 软件设置寄存器ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,将需要转换的通道、数量设置好,然后置位CHANY\_MDEN。
- 通过软件、外部触发置位ADCR 寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY\_SEL0 到CHANY\_SEL15,转换通道数量由CHANY\_NUM 配置,且CHANY\_SEL0 到CHANY\_SEL15 是任意配置的,可以完全相同,或完全不相同。
- 每路A/D 转换完成时,A/D 转换的数据值将有序装载到相应通道的数据寄存器中,ADIF转换结束标志被设置,若此时控制寄存器ADCR 的ADIE 位置1,将产生AD 转换结束中断请求。
- A/D 最后一个通道采样结束后,ADST 位自动清0,A/D 转换器进入空闲模式。
- 若在A/D 转换过程中,软件更新ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,然后等待下一次软件软件置位ADST。
在一些场景中,需要在执行一遍上述采样后,对采样顺序做调整;或减少采样通道数,以减少采样总体时间,可以通过简单的配置一两个寄存器实现灵活的配置;
3.3 连续扫描模式
在连续扫描模式下,A/D 转换通道依软件配置一直执行,直到软件禁止。具体流程如下:
- 软件设置寄存器ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,将需要转换的通道、数量设置好,然后置位CHANY\_MDEN。
- 通过软件、外部触发置位ADCR 寄存器的ADST,外部触发可软件配置触发延时,A/D转换方向从CHANY\_SEL0 到CHANY\_SEL15,转换通道数量由CHANY\_NUM 配置,
- 且CHANY\_SEL0 到CHANY\_SEL15 是任意配置的,可以完全相同,或完全不相同。
- 每路A/D 转换完成时,A/D 转换的数据值将有序装载到相应通道的数据寄存器中,ADIF转换结束标志被设置,若此时控制寄存器ADCR 的ADIE 位置1,将产生AD 转换结束中断请求。
- 只要ADST 位保持为1,持续进行A/D 转换。当ADST 位被清0,当前A/D 转换完成后停止,A/D 转换器进入空闲状态。
- 若在A/D 转换过程中,软件更新ADC\_ANY\_CFG,ADC\_CHANY0,ADC\_CHANY1,硬件不会立即更新这些配置,只会在当前设置的通道都转换结束时更新,即下一个扫描周期开始新的通道转换。
应用还可以结合外部触发功能与DMA传输功能,实现TIM触发多通道 ADC 转换,DMA装载数据的功能。具体可以参考官方的Lib样例程序: http://www.mindmotion.com.cn/getfile.aspx?id=1219 。
下面通过寄存器配置多个通道,实现多路转换,多次切换任意通道,附上全部Reg版本Demo代码:
// Define to prevent recursive inclusion
#define _ADC_C_
// Files includes
#include "delay.h"
#include "sys.h"
#include "uart.h"
#include "adc.h"
#define ADCSCANNUM 4
#define RESULTLEN 4
vu8 ADCflag = 0;
u16 ADC_flag;
u16 ADCValue[ADCSCANNUM];
u16 varADC_ResultList[RESULTLEN+10][ADCSCANNUM];
void ADC_AnyChanChangeDefault(void)
{
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 0<<ADC1_CHANY0_SEL0_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 2<<ADC1_CHANY0_SEL1_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);
}
void ADC1_AnyChanMultiChannelInit(void)
{
SET_BIT(RCC->AHBENR,RCC_AHBENR_GPIOA); //enable GPIOA clock
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN); //enable ADC1clock
//set PA0,2,5,7 as Analog Input
MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_0_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_0_Pos);
MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_2_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_2_Pos);
MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_5_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_5_Pos);
MODIFY_REG(GPIOA->CRL, (GPIO_CNF_MODE_MASK << GPIO_CRL_CNF_MODE_7_Pos), GPIO_CNF_MODE_AIN << GPIO_CRL_CNF_MODE_7_Pos);
SET_BIT(RCC->APB2RSTR,RCC_APB2RSTR_ADC1RST); //ADC1reset
CLEAR_BIT(RCC->APB2RSTR,(RCC_APB2RSTR_ADC1RST)); //reset end
//ADC configure soft trigger, single period mode
//8 fractional frequency
MODIFY_REG(ADC1->ADCFG, ADC_CFGR_PRE, ADCFG_ADCPRE_8);
MODIFY_REG(ADC1->ADCR, \
ADCR_ADMD_PERIOD | ADCR_ADMD_CONTINUE | ADCR_ALIGN_LEFT, \
ADCR_ADMD_PERIOD);
SET_BIT(ADC1->ADCHS, ADCHS_CHEN0|ADCHS_CHEN2|ADCHS_CHEN5|ADCHS_CHEN7);
//single PERIOD mode , Data right-ALIGNED, discontinue
//enable 4 channels
WRITE_REG(ADC1->ANYCFG, 4);
//Enable chan 0,2,5, 7
ADC_AnyChanChangeDefault();
SET_BIT(ADC1->ADCR, ADC_CR_DMAEN);
SET_BIT(ADC1->ADCFG,ADCFG_ADEN);//ADC1 enable
}
void ADC_AnyChanChangeFirst(void)
{
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 2<<ADC1_CHANY0_SEL0_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 0<<ADC1_CHANY0_SEL1_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);
}
void ADC_AnyChanChangeSecond(void)
{
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 5<<ADC1_CHANY0_SEL0_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 0<<ADC1_CHANY0_SEL1_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 2<<ADC1_CHANY0_SEL2_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 7<<ADC1_CHANY0_SEL3_Pos);
}
void ADC_AnyChanChangeThird(void)
{
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL0, 0<<ADC1_CHANY0_SEL0_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL1, 7<<ADC1_CHANY0_SEL1_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL2, 5<<ADC1_CHANY0_SEL2_Pos);
MODIFY_REG(ADC1->CHANY0,ADC1_CHANY0_SEL3, 2<<ADC1_CHANY0_SEL3_Pos);
}
void M0_NVIC_Init(u32 NVIC_IRQChannelPriority, IRQn_Type NVIC_IRQChannel, FunctionalState NVIC_IRQChannelCmd)
{
if (NVIC_IRQChannelCmd != DISABLE)
{
NVIC->IP[NVIC_IRQChannel >> 0x02] =
(NVIC->IP[NVIC_IRQChannel >> 0x02] &
(~(((u32)0xFF) << ((NVIC_IRQChannel & 0x03) * 8)))) |
((((u32)NVIC_IRQChannelPriority << 6) & 0xFF) << ((NVIC_IRQChannel & 0x03) * 8));
NVIC->ISER[0] = 0x01 << (NVIC_IRQChannel & 0x1F);
}
else
{
NVIC->ICER[0] = 0x01 << (NVIC_IRQChannel & 0x1F);
}
}
void DMA1_Channel1_IRQHandler()
{
if(DMA1->ISR & DMA_ISR_TCIF1)
{
DMA1->IFCR = DMA_IFCR_CTCIF1;
ADCflag = 1;
}
}
void DMAcheckStatus(u32 DMA_FLAG)
{
while(1)
{
if(DMA1->ISR & DMA_FLAG)
{
DMA1->IFCR = DMA_FLAG;
break;
}
}
}
void DMAdisable(DMA_Channel_TypeDef* DMAy_Channelx)
{
//disable DMA_EN
DMAy_Channelx->CCR &= 0xFFFFFFFE;
}
void DMA_AdctoM16_Init(void)
{
DMA_Channel_TypeDef* dma_channel;
dma_channel = DMA1_Channel1;
RCC->AHBENR |= RCC_AHBENR_DMA1EN ;
DELAY_Ms(5); //wait DMAclock stabilization
DMAdisable(dma_channel);
dma_channel->CPAR = (u32) & (ADC1->DR);; //DMA1 external address
dma_channel->CMAR = (u32)ADCValue; //DMA1,memory device address
dma_channel->CCR &= ~DMA_CCR1_DIR;
dma_channel->CNDTR = ADCSCANNUM;
dma_channel->CCR &= ~DMA_CCR1_PINC;
dma_channel->CCR |= DMA_CCR1_MINC;
dma_channel->CCR |= DMA_CCR1_PSIZE_0; //external data 16bit
dma_channel->CCR |= DMA_CCR1_MSIZE_0; //memory device data 16bit
dma_channel->CCR |= DMA_CCR1_PL_0; //Medium priority
dma_channel->CCR |= DMA_CCR_CIRC;
dma_channel->CCR &= ~DMA_CCR1_MEM2MEM; //register memory device to memory device mode
M0_NVIC_Init(0, DMA1_Channel1_IRQn, ENABLE);
dma_channel->CCR |= DMA_CCR1_TCIE;
ADCflag = 0x0;
dma_channel->CCR |= DMA_CCR1_EN; //start DMA transmission
}
void Get_ResultListFun(u16 list_number)
{
u16 chan = 0;
for(chan = 0; chan < ADCSCANNUM; chan++)
{
varADC_ResultList[list_number][chan] = ADCValue[chan];
}
}
void ADC_ConvertSoftwareStart(ADC_TypeDef* adc, FunctionalState state)
{
(state) ? (adc->ADCR |= ADC_CR_ADST) : (adc->ADCR &= ~ADC_CR_ADST);
}
u16 ADC1_MultiChanAnyDemo(void)
{
ADC1_AnyChanMultiChannelInit();
DMA_AdctoM16_Init();
ADC_ConvertSoftwareStart(ADC1, ENABLE);
ADCflag = 0;
while(1)
{
if(ADCflag == 1)
{
ADCflag = 0;
Get_ResultListFun(0);
break;
}
}
ADC_AnyChanChangeFirst();
ADC_ConvertSoftwareStart(ADC1, ENABLE);
while(1)
{
if(ADCflag == 1)
{
ADCflag = 0;
Get_ResultListFun(1);
break;
}
}
ADC_AnyChanChangeSecond();
ADC_ConvertSoftwareStart(ADC1, ENABLE);
while(1)
{
if(ADCflag == 1)
{
ADCflag = 0;
Get_ResultListFun(2);
break;
}
}
ADC_AnyChanChangeThird();
ADC_ConvertSoftwareStart(ADC1, ENABLE);
while(1)
{
if(ADCflag == 1)
{
ADCflag = 0;
Get_ResultListFun(3);
break;
}
}
return 0;
}