STM32CubeMX ADC
ADC转换时间:
采样周期+12.5个时钟周期;
例如:采样使用1.5个时钟周期+12.5个时钟周期 = 14个时钟周期
ADC配置为最高时钟周期:14Mhz(f103zet6),那么转换时间为:14/(14*10^6)=1us.
DMA方式:使用ADC1/3测试
1. 选择使用的通道
2. 参数配置 独立模式、数据右对齐、扫描转换(多通道默认配置)、连续扫描、使能常规转换组、6通道、软件触发、Rank单独配置,否则全部默认一样的,到时候采样值全是第一个通道的
3.DMA
F103板子12位AD,半字足够了。
同样的配置,配置了ADC3的3个通道。
4. 中断配置(看一眼,全局中断是否开启是否影响DMA中断,待测试(反正开启没问题))
5.代码修改:在生成的代码中
main.c 在系统初始化函数后面,初始化ADC1、ADC3:
HAL_ADCEx_Calibration_Start(&hadc1);//ADC校准 HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADC1_convert,adc1_convert_num); HAL_ADCEx_Calibration_Start(&hadc3);//ADC校准 HAL_ADC_Start_DMA(&hadc3,(uint32_t *)ADC3_convert,adc3_convert_num);
adc.h 添加:全局定义DMA转存采样值地址与采样数据量、ADC通道数
#define adc1_convert_num 30 //ADC1转化数据量,,每转换30个数据 DMA进行一次数据转移 #define adc1_channel 6 //ADC1使用的通道数 6个 #define adc3_convert_num 9 //ADC3转化数据量,每转换9个数据 DMA进行一次数据转移 #define adc3_channel 3 //ADC3使用的通道数 3个 //extern uint32_t ADC1_convert[10][6];//转换数据缓存,定义60个,用了一半 extern uint16_t ADC1_convert[10][6];//转换数据缓存 //extern uint32_t ADC3_convert[6][3];//转换数据缓存,定义18个,只用了一半 extern uint16_t ADC3_convert[6][3];//转换数据缓存
ADC配置的通道按从小到大的顺序依次写入pData中,例如 channel1,2,3,11,12,13,对应在pData[0] 、pData[1] 、pData[2] 、pData[3] 、pData[4] 、pData[5] 中 ,函数详细说明在任务函数段。
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
adc.c 添加:
/* 对应CUbemx里DMA设置的转存数据位宽 ** Word: uint32_t ** HalfWord: uint16_t, 在开启ADC_DMA时,只需强制转化地址指针,例如/ ** HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADC1_convert,24); */ //uint32_t ADC1_convert[10][6] = {0};//ADC1数据转存,每次可存储10组X6通道数据,每次刷新定义的数据量adc1_convert_num 个数 uint16_t ADC1_convert[10][6] = {0};//ADC1数据转存,每次可存储10组X6通道数据,每次刷新定义的数据量adc1_convert_num 个数 //uint32_t ADC3_convert[6][3];//ADC3数据转存,每次可存储6组X3通道数据,每次刷新定义的数据量adc3_convert_num 个数 uint16_t ADC3_convert[6][3];//ADC3数据转存,每次可存储6组X3通道数据,每次刷新定义的数据量adc3_convert_num 个数 /* USER CODE END 0 */
添加标志位与转存区域(区别于使用DMA将ADC转换值转存区,这里是从转存区再进行一次转移):
uint8_t ADC1_Rec_Flag;//ADC1转换完成标志 uint8_t ADC2_Rec_Flag;//ADC2转换完成标志 uint8_t ADC3_Rec_Flag;//ADC3转换完成标志 uint16_t ADC1_temp[10][6] = {0};//ADC1转化数据转存 //uint32_t ADC1_temp[10][6] = {0};// uint16_t ADC3_temp[6][3] = {0};//ADC3转化数据转存 //uint32_t ADC3_temp[6][3] = {0};//
任务函数添加:
void Start_Task2(void const * argument) { /* USER CODE BEGIN Start_Task2 */ uint16_t task2_ram_availa =0; uint8_t loop1 = 0,loop2 = 0;//循环次数计数 /* Infinite loop */ for(;;) { if(ADC1_Rec_Flag ==1) { ADC1_Rec_Flag = 0; /*停止DMA转存 HAL_ADC_Stop_DMA(); ** 1. 用于将采样数据从缓冲区转移至指定区域,后再开启DMA,期间数据不更新,避免同时读写 ** 2. 处理数据错位问题(经测试,不停止DMA对数据连续性没有影响,该用法保留) */ HAL_ADC_Stop_DMA(&hadc1); for(loop1 = 0;loop1<adc1_convert_num/adc1_channel;loop1++) { for(loop2 =0;loop2<adc1_channel;loop2++) { ADC1_temp[loop1][loop2] = ADC1_convert[loop1][loop2]; } } /* HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length) ** hadc: 当前使用的ADC编号 ** pData:ADC采样后,DMA转存采样值的保存地址, ** Length:数据量,为配置的通道数n的倍数,假设为K,每次完成Length=N*k个数据的转换后,调用回调函数/ ** void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc); */ HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADC1_convert,adc1_convert_num); } if(ADC3_Rec_Flag ==1) { ADC3_Rec_Flag = 0; /*停止DMA转存 HAL_ADC_Stop_DMA(); ** 1. 用于将采样数据从缓冲区转移至指定区域,后再开启DMA,期间数据不更新,避免同时读写 ** 2. 处理数据错位问题(经测试,不停止DMA对数据连续性没有影响,该用法保留) */ HAL_ADC_Stop_DMA(&hadc3); for(loop1 = 0;loop1<adc3_convert_num/adc3_channel;loop1++) { for(loop2 =0;loop2<adc3_channel;loop2++) { ADC3_temp[loop1][loop2] = ADC3_convert[loop1][loop2]; } } /* HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length) ** hadc: 当前使用的ADC编号 ** pData:ADC采样后,DMA转存采样值的保存地址, ** Length:数据量,为配置的通道数n的倍数,假设为K,每次完成Length=N*k个数据的转换后,调用回调函数/ ** void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc); */ HAL_ADC_Start_DMA(&hadc3,(uint32_t *)ADC3_convert,adc3_convert_num); } count_task2++; task2_ram_availa = uxTaskGetStackHighWaterMark(Task2Handle);//获取最高任务栈剩余量 printf("TASK2!__ %d,task2_ram_availa: %d\r\n",count_task2,task2_ram_availa); osDelay(5000); } /* USER CODE END Start_Task2 */ }
重定义AD转换完成回调函数:void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
每个ADC在所有数据量采集完毕后,调用转换完成回调函数;可以将部分数据转移或处理事件放在这里,主函数值关心总数据集处理。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { //判断转换完成的AD号,同时判断转换完成标志位是否设置 EOC :End Of Conversion if((hadc == &hadc1)&&(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC))) { ADC1_Rec_Flag = 1; } // if((hadc == &hadc2)&&(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2),HAL_ADC_STATE_REG_EOC))) // { // ADC2_Rec_Flag = 1; // } if((hadc == &hadc3)&&(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc3),HAL_ADC_STATE_REG_EOC))) { ADC3_Rec_Flag = 1; } }
在hal库中定义的有,右键查看: //只能用于判断单独一位,除需要判断的标志位外,其余为0 如 REG=0x0200 BIT=0x0200 #define HAL_IS_BIT_SET(REG, BIT) (((REG) & (BIT)) != 0U)//REG标志位1 BIT标志位 1,该Flag 有效(无效,自定义) #define HAL_IS_BIT_CLR(REG, BIT) (((REG) & (BIT)) == 0U)//REG标志位1 BIT标志位 0,该Flag 无效(有效,自定义)
普通模式与中断模式:使用ADC2测试
参考自:Stm32cubeMx配置ADC多通道采集 – 电子创客营 (eemaker.com)
问题测试记录:
1. 不使用dma,使用中断模式,多通道,扫描转化模式,既不连续,也不不连续;
结果:只采样一次,多个通道全部完成后,调用回调函数与中断函数,HAL_ADC_GetValue(&hadcx)只采到 最后一个通道值。
正常配置:
1.CubeMX配置,扫描转换模式,非连续模式、非连续模式下,每次转换 1个,通道口、转换时间配置
开启全局中断:
2. 添加函数:
普通方式函数也可以参考: STM32HAL库CubeMX配置ADC多通道选择读取(非DMA)_支吾小白的博客-CSDN博客
#define Adc_Ready_Time_out 8 //等待ADC DR寄存器写入时间 #define Ad_Use_IT //ADC2使用中断方式采样 //#define Ad_Use_Normal//ADC2使用一般模式采样 ...... uint16_t ADC2_temp[3] = {0}; //ADC2转化数据转存 //uint32_t ADC2_temp[3] = {0}; //ADC2转化数据转存 uint16_t timeout = Adc_Ready_Time_out; uint8_t loop3 = 0; //控制循环次数 ...... while(1) { /************** 非DMA方式\中断方式,存在问题:数据错位,前两个被抛弃问题 ************************************** ** 使用HAL_ADC_Start(),HAL_ADC_Start()写在for循环中,循环次数为使用的通道数量,转换数据按照从下到大的顺序依次写入到地址中/ **数据错位原因:for循环中,使用HAL_ADC_Start();之后直接跟HAL_ADC_GetValue()函数,会导致转换还未完成就直接取值,最终导致循环取值数据错位;/ /但使用 HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC);判断,转换完成标志确实被置位了,数据依旧是错位的,所以使用标志位判断:/ /while(!(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC)));不起作用。 **解决: 加一小段延时解决,等待ADC的DR寄存器写入后再取值。测试后,对于f103的板子,最小延时时间应该大于等于0.124ms。 ** 使用HAL_ADC_Start(),不会触发转换完成回调函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc); ** Cubemx配置: ** Scan Conversion Mode :Enable ** Continuous Conversion Mode: Disable ** Discontinuous Conversion Mode: Enable ** Number Of Discontinuous Conversions: 1 */ #ifdef Ad_Use_Normal for(loop3=0;loop3<3;loop3++) { HAL_ADC_Start(&hadc2); //while(!(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC))); while(timeout--); timeout = Adc_Ready_Time_out; ADC2_temp[loop3] = HAL_ADC_GetValue(&hadc2); } HAL_ADC_Stop(&hadc2); #endif /************** 中断方式;存在问题:数据错位,前两个被抛弃问题(与普通方式一致)******************************************************** ** 使用HAL_ADC_Start_IT(),HAL_ADC_Start_IT()写在for循环中,循环次数为使用的通道数量,转换数据按照从下到大的顺序依次写入到地址中/ / 在完成所有通道的数据采集与转换后先调用中断函数: void ADCx_IRQHandler(void),再调用回调函数 / / void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc); 所以说对于单个通道而言,可以采取在中断函数或者回调函数里定义flag来判断是否转换/
/ 进而主函数或任务根据flag来读取值,但对于多通道而言,使用这种方法只能得到最后一通道的值,之前的值被覆盖。还未解决,对于多通道,还是考虑用DMA方式、普通模式、定时器方式
**数据错位解决: 加一小段延时解决,等待ADC的DR寄存器写入后再取值。测试后,对于f103的板子,最小延时时间应该大于等于0.124ms。 ** Cubemx配置: ** Scan Conversion Mode :Enable ** Continuous Conversion Mode: Disable ** Discontinuous Conversion Mode: Enable ** Number Of Discontinuous Conversions: 1 */ #ifdef Ad_Use_IT for(loop3=0;loop3<3;loop3++) { HAL_ADC_Start_IT(&hadc2); while(timeout--); timeout = Adc_Ready_Time_out; ADC2_temp[loop3] = HAL_ADC_GetValue(&hadc2); } HAL_ADC_Stop_IT(&hadc2); #endif }