用过一段时间国民技术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检测结果正常后电源才打开输出。
在这里我也只是记录下出现问题后要根据现象找出原因,积极思考,再找解决办法就是比较容易些的事情了。