用过一段时间国民技术N32G031系列单片机,编程模式几乎类同STM32系列(另外一个国产32位单片机品牌兆易创新好像也是仿STM32的编程模式,看来STM32的确是一款极其优秀的产品),但是价格实惠,功能也很齐全,软件支持包在官网都可以下载,所以总体感觉还是可以的,只是稳定性还有待验证。
最近在用N32G031做一款电源产品软件时发现了一个情况,这种情况以前几乎都是不关注的,就是AD转换的时间。我有10个通道要进行AD转换,每个通道采样64次,我的AD初始化函数和采样函数如下:
初始化函数:
/**
*@name: ADC_Initial
*@description: ADC initialization
*@params: none
*@return: none
*/
void ADC_Initial(void)
{
ADC_InitType ADC_InitStructure;
ADC_GPIO_Configuration();
/* ADC configuration ------------------------------------------------------*/
ADC_InitStructure.MultiChEn = DISABLE;
ADC_InitStructure.ContinueConvEn = DISABLE;
ADC_InitStructure.ExtTrigSelect = ADC_EXT_TRIGCONV_NONE;
ADC_InitStructure.DatAlign = ADC_DAT_ALIGN_R;
ADC_InitStructure.ChsNumber = 1;
ADC_Init(ADC, &ADC_InitStructure);
//ADC1 regular channel13 configuration
//ADC_ConfigRegularChannel(ADC, ADC_CH_13_PC2, 1, ADC_SAMP_TIME_55CYCLES5);
//Enable ADC DMA
//ADC_EnableDMA(ADC, ENABLE);
//Enable ADC
ADC_Enable(ADC, ENABLE);
// Check ADC Ready
while (ADC_GetFlagStatusNew(ADC, ADC_FLAG_RDY) == RESET);
while (ADC_GetFlagStatusNew(ADC, ADC_FLAG_PD_RDY));
channel = IS1;
}
获取每次AD转换值函数:这里设置每次采样的时钟周期是56
/**
*@name: ADC_GetData
*@description: get the converted AD value
*@params: ADC_Channel: channel to be converted
*@return: AD value we need
*/
uint16_t ADC_GetData(uint8_t ADC_Channel)
{
uint16_t data;
ADC_ConfigRegularChannel(ADC, ADC_Channel, 1, ADC_SAMP_TIME_56CYCLES5);
// Start ADC Software Conversion
ADC_EnableSoftwareStartConv(ADC, ENABLE); //start software conversion
while (ADC_GetFlagStatus(ADC, ADC_FLAG_ENDC) == 0) //wait for the conversion to be finished
{
}
ADC_ClearFlag(ADC, ADC_FLAG_ENDC); //clear the end conversion flag
ADC_ClearFlag(ADC, ADC_FLAG_STR);
data = ADC_GetDat(ADC);
return (data);
}
应用程序采样所有通道AD值的函数(共10个通道,每个通道采样64次):
/**
*@name: ADC_Sample
*@description: sample all the channels
According to the ADC sampling clock(1 MHz) and the cycles(56 cycles) one sampling operation needs,
here we need to sample 10 channels, and each channel has to be sampled 64 times. If the ADC sampling
clock is 1 MHz, the total time it takes to sample all the 10 channels is 64*10*56/1M = 0.03584(S)
about 36 milli-seconds.
*@params: none
*@return: none
*/
void ADC_Sample(void)
{
uint16_t ad_data = 0;
if (!sample_flag)
{
if (sample_cnt == 0) //the first value should be discarded when switching channel
{
sample_cnt = 1;
return;
}
switch (channel)
{
case IS1:
{
ad_data = ADC_GetData(IS1);
is1_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is1_sample_value_filt = is1_sample_sum >> 6; //get the average AD value
is1_sample_sum = 0;
channel = IS2; //next channel
}
break;
}
case IS2:
{
ad_data = ADC_GetData(IS2);
is2_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is2_sample_value_filt = is2_sample_sum >> 6;
is2_sample_sum = 0;
channel = IS3;
}
break;
}
case IS3:
{
ad_data = ADC_GetData(IS3);
is3_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is3_sample_value_filt = is3_sample_sum >> 6;
is3_sample_sum = 0;
channel = IS4;
}
break;
}
case IS4:
{
ad_data = ADC_GetData(IS4);
is4_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is4_sample_value_filt = is4_sample_sum >> 6;
is4_sample_sum = 0;
channel = IS5;
}
break;
}
case IS5:
{
ad_data = ADC_GetData(IS5);
is5_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is5_sample_value_filt = is5_sample_sum >> 6;
is5_sample_sum = 0;
channel = IS6;
}
break;
}
case IS6:
{
ad_data = ADC_GetData(IS6);
is6_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is6_sample_value_filt = is6_sample_sum >> 6;
is6_sample_sum = 0;
channel = IS7;
}
break;
}
case IS7:
{
ad_data = ADC_GetData(IS7);
is7_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is7_sample_value_filt = is7_sample_sum >> 6;
is7_sample_sum = 0;
channel = IS8;
}
break;
}
case IS8:
{
ad_data = ADC_GetData(IS8);
is8_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
is8_sample_value_filt = is8_sample_sum >> 6;
is8_sample_sum = 0;
channel = TEMP;
}
break;
}
case TEMP:
{
ad_data = ADC_GetData(TEMP);
otp_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
otp_ad_value = otp_sample_sum >> 6;
otp_sample_sum = 0;
channel = VOS;
}
break;
}
case VOS:
{
ad_data = ADC_GetData(VOS);
vos_sample_sum += ad_data;
if (++sample_cnt > SAMPLE_COUNT)
{
sample_cnt = 0;
vdc_ad_value = vos_sample_sum >> 6;
vos_sample_sum = 0;
sample_flag = 1;//sampleing finished flag
channel = IS1;
}
break;
}
default:
{
channel = IS1;
sample_cnt = 0;
is1_sample_sum = 0;
is2_sample_sum = 0;
is3_sample_sum = 0;
is4_sample_sum = 0;
is5_sample_sum = 0;
is6_sample_sum = 0;
is7_sample_sum = 0;
is8_sample_sum = 0;
vos_sample_sum = 0;
otp_sample_sum = 0;
break;
}
}
}
}
系统时钟及ADC模块的时钟配置函数(其中配置AD模块的1 MHz时钟是必须的):
/**
* @brief Configures the different system clocks.
*/
static void RCC_Configuration(void)
{
// PCLK1 = HCLK/4, configure the clock of APB1 and times uses the prescaled APB1
RCC_ConfigPclk1(RCC_HCLK_DIV4);
// Enable peripheral clocks ------------------------------------------------
// Enable TIM1 clocks
//RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_TIM1, ENABLE);
//Enable DMA clocks
//RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA, ENABLE);
//Enable GPIO clocks
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_GPIOB | RCC_APB2_PERIPH_GPIOC | RCC_APB2_PERIPH_GPIOF, ENABLE);
//Enable ADC clocks
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_ADC, ENABLE);
//RCC_ADCHCLK_DIV16
ADC_ConfigClk(ADC_CTRL3_CKMOD_AHB, RCC_ADCHCLK_DIV16);
//enable ADC1M clock
//RCC_EnableHsi(ENABLE);
RCC_ConfigAdc1mClk(RCC_ADC1MCLK_SRC_HSE, RCC_ADC1MCLK_DIV8);
//enable USART1 clock
RCC_EnableAPB2PeriphClk(USARTy_GPIO_CLK, ENABLE);
RCC_EnableAPB2PeriphClk(USARTy_CLK, ENABLE);
//TIM3 and TIM clock enable
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_TIM3, ENABLE);
//RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_TIM6, ENABLE);
}
AD检测滤波函数(每20毫秒设置相关警告位,主要是输入电压是否欠压或者过压):
/**
*@name: ADC_Status_Check
*@description: ADC filter, the input voltage and OTP check
*@params: none
*@return: none
*/
static void ADC_Status_Check(void)
{
if (vdc_ad_value <= VDC_UV_OFF_AD) //DC under and over voltage filter
vdc_uvp_off++;
if (vdc_ad_value >= VDC_UV_ON_AD)
vdc_uvp_recovered++;
if (vdc_ad_value >= VDC_OV_OFF_AD)
vdc_ovp_off++;
if (vdc_ad_value <= VDC_OV_ON_AD)
vdc_ovp_recovered++;
if (otp_ad_value >= OTP_OFF_AD) //OTP
otp_off++;
if (otp_ad_value <= OTP_RECOVER_AD)
otp_recovered++;
if (adc_filter_cnt >= IO_FILTER_CNT)
{
if (vdc_uvp_off >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.VDC_UVP == 0) //VDC
AlarmStatus.alarm.VDC_UVP = 1;
if (vdc_uvp_recovered >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.VDC_UVP == 1)
AlarmStatus.alarm.VDC_UVP = 0;
if (vdc_ovp_off >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.VDC_OVP == 0)
AlarmStatus.alarm.VDC_OVP = 1;
if (vdc_ovp_recovered >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.VDC_OVP == 1)
AlarmStatus.alarm.VDC_OVP = 0;
if (otp_off >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.OTP == 0) //OTP
AlarmStatus.alarm.OTP = 1;
if (otp_recovered >= IO_FILTER_VALID_CNT && AlarmStatus.alarm.OTP == 1)
AlarmStatus.alarm.OTP = 0;
if (AlarmStatus.alarm.VDC_OVP == 0 && AlarmStatus.alarm.VDC_UVP == 0)
{
AlarmStatus.alarm.VIN_STATUS_ERR = 0;
}
else
{
AlarmStatus.alarm.VIN_STATUS_ERR = 1;
}
adc_filter_cnt = 0;
vdc_uvp_off = 0;
vdc_uvp_recovered = 0;
vdc_ovp_off = 0;
vdc_ovp_recovered = 0;
otp_off = 0;
otp_recovered = 0;
}
else
{
adc_filter_cnt++;
}
}
根据手册说明及咨询芯片厂的相关技术人员,ADC模块的采样时钟频率固定是1MHz,每次采样的时间软件设置为56个时钟周期(这个可以修改),另外每次AD转换还额外需要12.5个时钟周期,所以完成10个通道采样(每个通道采样64次)所需要的总时间是:(56+12.5)*10*64/1000000 = 0.04384秒,差不多就是44毫秒。
我的程序在做AD检测滤波(非AD转换,对AD转换完成后的平均值再次进行一段时间的滤波处理,避免电源输出产生误动作)时只有20毫秒,并且初始时所有警告标志位都是清零的,尤其是输入电压欠压和过压标志,所以在电源开机时会有一个20毫秒输出正常打开,然后又有一个40毫秒左右的关闭,最后又是完全正常的现象。结合这个现象和AD转换10个通道所需的总时间,那20毫秒就是AD滤波的时间,在此期间由于所有标志位都清零,电源输出正常打开,20毫秒滤波过后,由于此时AD转换还未完成(需要40毫秒左右),所以输入电压欠压警告位被置位(AD检测未完成,AD平均值为0),结果输出关闭,只有等到一轮AD检测完全完成且AD滤波也检测到完整的AD值后警告标志位才会清除,之后输出就一直正常了。
如果要解决这个问题,可以把AD滤波的时间加长,至少要大于AD转换一轮所需要的时间,或者如果对AD转换精度要求不高的话,可以把采样次数减少或者把每次采样所需要的时钟数设置得小一些。最为直接的做法就是初始将所有警告标志位置1。这样开机时是异常的,只有AD检测结果正常后电源才打开输出。
在这里我也只是记录下出现问题后要根据现象找出原因,积极思考,再找解决办法就是比较容易些的事情了。