2019年TI杯 简易电路特性测试仪 制作过程(4)——程序总体分析 20/04/20

一、程序流程说明
  程序中使用了嵌入式实时操作系统FreeRTOS,如果以前没有使用过嵌入式实时操作系统(RTOS)的同学,阅读或修改代码的时候可能会有点吃力。带RTOS的编程方式和传统的不带操作系统的编程还是有很大的区别的,如果已经接触STM32编程有段时间的同学,可以去学习一下RTOS,如果在一些较为复杂的项目中,使用RTOS进行编程会相对简单一些。推荐可以学习FreeRTOS,这个操作系统是完全开源并且免费的。
/************************************************* main函数说明 ************************************************/

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//中断分组4
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO复用时钟
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试 这两句话非常重要,因为开发板中的按键和EEPROM中的引脚和JTAG引脚复用
	DBGMCU->CR  &= ~((uint32_t)1<<5);   //关闭异步调试模式 不加这两句的话 PA15 PB3 PB4输出没问题 输入的话 恒为低电平!!!!
	AFIO->MAPR = (AFIO->MAPR & ~((uint32_t)0x7 << 24)) | (2 << 24);     /*  PA15 PB3 PB4 */
	delay_init();		//初始化延时函数
	LED_Init();			//初始化led
	KEY_Init();			//按键初始化
	Display_Init();	//显示初始化
	Menu_Init(); 		//菜单初始化
	MY_ADC_Init();	//ADC初始化
	MYTIMER_Init(); //定时器初始化
	DO_Init();
	AD9850_Init();
	AT24CXX_Init();
	
	//创建开始任务
	xTaskCreate((TaskFunction_t )start_task,            //任务函数
							(const char*    )"start_task",          //任务名称
							(uint16_t       )START_STK_SIZE,        //任务堆栈大小
							(void*          )NULL,                  //传递给任务函数的参数
							(UBaseType_t    )START_TASK_PRIO,       //任务优先级
							(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
	vTaskStartScheduler();          //开启任务调度
}

       main函数中先是设定中断分组,然后禁用JTAG开启了SWD(因为引脚冲突),在之后初始化一些需要用到的外设,最后创建开始任务再开启FreeRTOS(就是开启任务调度)。main函数比较简单就是开启了一些外设和系统初始化。

/************************************************* start_task说明 ************************************************/

void start_task(void *pvParameters)
{
	
	/********* cpu使用率统计功能 **********/
	#if (usercfgCPU_USAGE_CALCULATE==1)
    uTaskCPUUsageInit();  //不可以在临界区内调用
//	此种方法计算CPU使用率的原理为:
//	1、在系统启动后,所有用户任务都未开始运行时,统计一段时间T(如1s)内空闲任务被调用的次数M,此时可认为这个次数是CPU占用率最小(接近0)时能够调用空闲任务的最大次数。
//	2、任务开始运行后,在滴答时钟中断处理函数中,每隔T时间,记录空闲任务被调用的次数m。
//	3、CPU占用率为:(1-m/M)*100%
    #endif
	
	taskENTER_CRITICAL();           //进入临界区
	/************** 创建BASIC任务 ***************/
	xTaskCreate((TaskFunction_t )basic_task,     	
							(const char*    )"bac",   	
							(uint16_t       )BASIC_STK_SIZE, 
							(void*          )NULL,				
							(UBaseType_t    )BASIC_TASK_PRIO,	
							(TaskHandle_t*  )&BASICTask_Handler);
	/************** 创建MENU任务 ***************/
	xTaskCreate((TaskFunction_t )menu_task,     	
							(const char*    )"meu",   	
							(uint16_t       )MENU_STK_SIZE, 
							(void*          )NULL,				
							(UBaseType_t    )MENU_TASK_PRIO,	
							(TaskHandle_t*  )&MENUTask_Handler); 
	/************** 创建MAESURE任务 ***************/
	xTaskCreate((TaskFunction_t )measure_task,     	
							(const char*    )"CAL",   	
							(uint16_t       )MEASURE_STK_SIZE, 
							(void*          )NULL,				
							(UBaseType_t    )MEASURE_TASK_PRIO,	
							(TaskHandle_t*  )&MEASURETask_Handler); 

							
	Key_QueueHandle = xQueueCreate(KEY_QUEUELEN,KEY_QUEUESIZE); /* 创建key消息队列 */
	while(Key_QueueHandle == NULL); /* 队列创建不成功 */
							
	Measure_SemaphoreHandle = xSemaphoreCreateCounting( MEASURE_MAXCOUNT, MEASURE_INITCOUNT );//创建measure_task信号量
	while(Measure_SemaphoreHandle == NULL);
							
	Read_Gain(); //读取EEPROM中的增益数据

	vTaskDelete(StartTask_Handler); //删除开始任务
	taskEXIT_CRITICAL();            //退出临界区
}

       start_task任务中主要是进行其他任务、信号量、消息队列的创建,可以看到分别创建了basic_task、menu_task、measure_task三个任务,三个任务的具体内容会在下文进行说明。创建了句柄为Key_QueueHandle的消息队列,用来向menu_task任务发送key消息,按键扫描在basic_task中进行,任务间的消息传递用消息队列实现。创建了句柄为Measure_SemaphoreHandle的计数型信号量,这个信号量用于任务同步,每当ADC采集完成了一组数据之后,发送这个信号量给measure_task任务,这个任务主要进行数据的计算和判断。Read_Gain函数读取保存的校准参数,之后删除开始任务(开始任务主要就是创建其他任务,创建完成之后就用不到了,进行删除。)
       usercfgCPU_USAGE_CALCULATE宏定义用于开启CPU使用率统计,FreeRTOS是没有CPU使用率统计功能的,这个功能是我额外加入的,开启这个宏就可以查看CPU的使用率统计了。一般调试的时候可以开启,正式程序发布之后关闭这个功能(这个功能还是会占用CPU使用率的)。

关于如何在FreeRTOS中添加CPU使用率统计功能,可以参考这篇博客:
https://blog.csdn.net/wenyuexunyin/article/details/68937150

二、basic_task任务分析

void basic_task(void *pvParameters)
{
	uint8_t i = 0,key;
	Measure_Rs.R6_value = 3015;   //电阻值根据实际值填写 电阻精度较差时可用万用表测量之后填写精确值
	Measure_Rs.R8_value = 20150;
	Measure_Rs.R40_value = 1000;
	Measure_Rs.R42_value = 3000;
	if(Measure_Dc.dc_gain == 0)
		Measure_Dc.dc_gain = 40;//设为默认4.0
	while(1)
	{
		i++;
		fptx_rand++;//用于随机选择幅频曲线显示
		if(i == 100)
		{
			i = 0;
			LED0 = !LED0; //run灯闪
		}
		key = KEY_Scan(0); /* 按键扫描 */
		if(key != 0) /* 有按键按下了 */
		{
			if(Current_Menu == NULL) //界面
			{
				if((key == KEY0_PRES) && (device_mode == DEVICE_MODE_MEASURE)) //测量模式 向上键
				{
					Measure_data.data_reg.Measure = 1;//开始测量
					DC_OFFSET_MEASURE();//测直流偏置
					vTaskDelay(100);
					measure_sta = MEASURE_DC_STA; //测量直流偏置
				}
				else if(key == KEY1_PRES)//向下键
				{
					if(device_mode == DEVICE_MODE_MEASURE)
					{
						device_mode = DEVICE_MODE_JUDGE; //判断
						Measure_data.data_reg.Measure = 1;//开始测量
						DC_OFFSET_MEASURE();//测直流偏置
						vTaskDelay(100);
						measure_sta = MEASURE_DC_STA; //测量直流偏置
					}
					else
						device_mode = DEVICE_MODE_MEASURE; //测量
						
					DDS_reg.fre_num = 1000;
					DDS_reg.fre_sta = 1;
					Display_Clear();
				}
			}
			if(Key_QueueHandle != NULL)
			{
				xQueueSend(Key_QueueHandle,&key,0);
			}
		}
		//DDS频率变化
		if(DDS_reg.fre_sta == 1)
		{
			DDS_reg.fre_sta = 0;
			ad9850_wr_serial(0x00,DDS_reg.fre_num);//改变频率输出
		}
		vTaskDelay(10);
	}
}

       basic_task任务中主要进行一些基本工作,比如运行指示灯、按键扫描、改变DDS频率输出,本任务10ms会被调用一次。
       key1进行模式切换,一个是测量模式,一个是判断模式,测量模式下按下key0按键进行一次数据测量。如果按键被按下,就会将按下按键的键值插入创建的消息队列进行发送。

三、menu_task任务分析

void menu_task(void *pvParameters)
{
	uint8_t key_que,que_res;
	while(1)
	{
		que_res = xQueueReceive( Key_QueueHandle,&key_que,50); /* 等key消息50ms */
		if(que_res == pdTRUE)
		{
			Menu_Handler(key_que);
			Menu_Display();
		}
		else
		{
			Main_Display();//主界面显示
		}
	}
}

       menu_task任务主要是处理显示一块的任务,Menu_Handler(key_que); Menu_Display();两个函数主要是处理菜单显示和菜单操作逻辑的,实现菜单结构,这里不做详细的分析,有兴趣的同学可以仔细研究一下菜单实现的原理(后续有空的话写一篇讲讲实现多级菜单的方法,为自己挖个坑。)。菜单中的任务函数会进行装置的校准,根据选择进入不同的菜单,进行不同参数的校准,校准的过程会在后面的博客中进行详细分析。
       Main_Display()函数主要是主界面的显示,测量模式下显示测量的数据,故障判断模式下显示电路的故障种类。

四、measure_task任务分析

void measure_task(void *pvParameters)
{
	uint8_t cal_res;
	while(1)
	{
		xSemaphoreTake(Measure_SemaphoreHandle,portMAX_DELAY); //死等信号量
		if(measure_sta != MEASURE_NONE_STA) //需要测量
		{
			switch(measure_sta) //根据状态来采样
			{
				case(MEASURE_DC_STA): //测量直流偏置电压
					cal_res = DC_Cal_Handle();
					if(cal_res == 1) //直流偏置电压计算完成
					{
						Measure_data.data_reg.Dc_sta = 1; //DC测量完毕
						Measure_data.Dc = Measure_Dc.dc_value;
						measure_sta = MEASURE_NONE_STA;//停止采样
						/***************** 测量输入电阻 ******************/
						R6_MEASURE_UI2_IN();//切继电器 接入R6
						Measure_Rs.Rs_Ri_sta = 0;//R6
						DDS_reg.fre_num = 1000;
						ad9850_wr_serial(0x00,DDS_reg.fre_num);//1K 默认输出
						vTaskDelay(200);//延时 等待继电器切换
						memset(&Measure_Ri_Jun,0,sizeof(Measure_Ri_Jun));//清空缓存
						Measure_Dc.dc_sample_count = 0;
						measure_sta = MEASURE_RI_STA; //测量输入电阻
						Measure_Dc.dc_sample_count = 0;
					}
					break;
				case(MEASURE_RI_STA): //测量输入电阻
					if(DDS_reg.fre_num == 1000) //1K
						cal_res = Ri_Cal_1KHz_Handle();
					if(cal_res == 1) //直流输入电阻计算完成
					{
						Measure_data.data_reg.Ri_sta = 1; //输入电阻测量完毕
						measure_sta = MEASURE_NONE_STA;//停止采样
						/***************** 测量输出电阻 ******************/
						MEASURE_UO1_OUT();//Uo1
						Measure_Rs.Rs_Ro_sta = 1;//R42
						DDS_reg.fre_num = 1000;
						ad9850_wr_serial(0x00,DDS_reg.fre_num);//1K 默认输出
						vTaskDelay(200);//延时 等待继电器切换
						if(DDS_reg.fre_num == 1000) //1K
							memset(&Measure_Ri_Jun,0,sizeof(Measure_Ri_Jun));//清缓存
						measure_sta = MEASURE_RO_STA; //测量输出电阻
					}
					break;
				case(MEASURE_RO_STA): //测量输出电阻
					if(DDS_reg.fre_num == 1000) //1K
						cal_res = Ro_Cal_1KHz_Handle();
					if(cal_res == 1) //直流输出电阻计算完成
					{
						Measure_data.data_reg.Ro_sta = 1; //输出电阻测量完毕
						measure_sta = MEASURE_NONE_STA;//停止采样
						/***************** 测量增益 ******************/
						AV_MEASURE_UO_IN() ;//切继电器 Ui
						DDS_reg.fre_num = 1000;
						ad9850_wr_serial(0x00,DDS_reg.fre_num);//1K 默认输出
						vTaskDelay(200);//延时 等待继电器切换
						if(DDS_reg.fre_num == 1000) //1K
							memset(&Measure_Ro_Jun,0,sizeof(Measure_Ro_Jun));//清缓存
						measure_sta = MEASURE_AV_STA; //测量增益
					}
					break;
				case(MEASURE_AV_STA): //测量增益
					if(DDS_reg.fre_num == 1000) //1K
						cal_res = Av_Cal_1KHz_Handle();
					if(cal_res == 1) //直流增益计算完成
					{
						
						if(device_mode == DEVICE_MODE_MEASURE) //测量模式
						{
							Measure_data.data_reg.Av_sta = 1; //增益测量完毕
							measure_sta = MEASURE_FPTX_STA; //测量幅频特性
							//这里需要将里面的缓存里的数据显示出XY图
							fptx_bmp_sta = fptx_rand%3;
							FPTX_MEASURE();//幅频特性
							/***************** 测量幅频特性 ******************/
							memset(&Measure_Av_Jun,0,sizeof(Measure_Av_Jun));//清缓存
							DMA_Cmd(DMA2_Channel5,ENABLE);//使能
						}
						else //故障判断模式
						{
							measure_sta = MEASURE_NONE_STA; //不测量
							if(Judge_5K_Av_Sta == 0)//5K增益没测
								DDS_reg.fre_num = 5000; //5k
							else
								DDS_reg.fre_num = 200000; //200k
							DDS_reg.fre_sta = 1; //改变频率
							FPTX_MEASURE() ;//切继电器 幅频特性
							vTaskDelay(50);//延时 等待继电器切换 和增益继电器一样不用等待
							/***************** 测量幅频特性 ******************/
							memset(&Measure_Av_Jun,0,sizeof(Measure_Av_Jun));//清缓存
							DMA_Cmd(DMA2_Channel5,ENABLE);//使能
							measure_sta = MEASURE_FPTX_STA; //测量幅频特性
						}
					}
					break;
				case(MEASURE_FPTX_STA): //测量幅频特性
					if(device_mode == DEVICE_MODE_MEASURE)
					{
						cal_res = FPTX_Cal_Scan_Handle();//扫描法
						if(cal_res == 1) //找到截止频率了
						{
							Measure_data.fre_stop = DDS_reg.fre_num;//上限频率
							measure_sta = MEASURE_NONE_STA; //停止测量
							Measure_data.data_reg.Measure = 0;//测试停止
							//这里需要将里面的缓存里的数据显示出XY图
							fptx_bmp_sta = fptx_rand%3;
							NORMAL_STATE();//正常状态
							Measure_data.data_reg.Fptx_sta = 1;
							DDS_reg.fre_num = 1000;
							ad9850_wr_serial(0x00,DDS_reg.fre_num);//1K 默认输出
							max_av = 0;
						}
						else //没找到
						{
							if(DDS_reg.fre_num < 30000)//30k以下
								DDS_reg.fre_num += 1000;
							if(DDS_reg.fre_num >= 30000)//30k以上
								DDS_reg.fre_num = DDS_reg.fre_num*103/100;//3%
							ad9850_wr_serial(0x00,DDS_reg.fre_num);//改变频率输出
							delay_ms(10);
							DMA_Cmd(DMA2_Channel5,ENABLE);//使能
						}
					}
					else //判断模式
					{
						if(Judge_5K_Av_Sta == 0)//5K增益没测
						{
							if(FPTX_Cal_5K_Handle() == 0)
							{
								Judge_5K_Av_Sta = 1;
								DDS_reg.fre_num = 200000; //200k
								ad9850_wr_serial(0x00,DDS_reg.fre_num);//改变频率输出
								delay_ms(50);
								DMA_Cmd(DMA2_Channel5,ENABLE);//使能
							}
						}
						else
						{
							if(DDS_reg.fre_num == 200000)
							{
								if(Judge_Change() == 0)
								{
									cal_res = FPTX_Cal_200K_Handle();
									if(cal_res == 0) //C3开路
									{
										//C3开路
										Judge_Change_Mark.Judge_type = C3_OPEN;
										Judge_Change_Mark.Jude_sta = 1;
									}
									else if(cal_res == 2) //C3to2
									{
										Judge_Change_Mark.Judge_type = C3_TO_2;
										Judge_Change_Mark.Jude_sta = 1;
									}
									else //C1to2 C2To2 或者无故障
									{
										TIM_Cmd(TIM2,DISABLE);//失能定时器2
										TIM_SetAutoreload(TIM2,1250);//6.4K 200Hz32个点的均方根
										TIM_SetCounter(TIM2,0);
										DDS_reg.fre_num = 200; //200Hz
										ad9850_wr_serial(0x00,DDS_reg.fre_num);//改变频率输出
										
										delay_ms(100);
										TIM_Cmd(TIM2,ENABLE);//使能定时器2
									}
								}
							}
							else if(DDS_reg.fre_num == 200)
							{
								cal_res = Av_Cal_200Hz_Handle();
								if(cal_res == 0)
								{
									Judge_Change_Mark.Judge_type = C2_TO_2;
									Judge_Change_Mark.Jude_sta = 1;
								}
								if(cal_res != 2)
									measure_sta = MEASURE_NONE_STA;
							}
						}
						if((Judge_Change_Mark.Jude_sta == 1) || (measure_sta == MEASURE_NONE_STA))//找到故障或者幅频测试完毕
						{
							TIM_Cmd(TIM2,DISABLE);//失能定时器2
							TIM_SetAutoreload(TIM2,250);//32K 1KHz32个点的均方根
							TIM_SetCounter(TIM2,0);
							TIM_Cmd(TIM2,ENABLE);//使能定时器2
							
							Judge_Change_Mark.Judge_Over = 1;//循环一次
							
							DC_OFFSET_MEASURE();//测直流偏置
							vTaskDelay(50);
							DDS_reg.fre_num = 1000; //1000
							DDS_reg.fre_sta = 1; //改变频率
							measure_sta = MEASURE_DC_STA; //测量直流偏置 //循环
							memset(&Measure_200HzAv_Jun,0,sizeof(Measure_200HzAv_Jun));//清缓存
						}
					}
					break;
			}
		}
		Check_Cal_Data();//校准时的测量
	}
}

       measure_task任务是本程序的重点,这个任务中进行各个量的测量,采用状态机的方式实现,通过判断状态标志得知当前测量的是哪个量,然后进入到对应的计算函数中进行计算。
       序中测量模式下的测量顺序分别为直流量、输入电阻、输出电阻、1kHz下的增益Av、截止频率。由于我使用的显示屏为OLED12864,没办法显示详细的幅频特性曲线,而且评分标准中主要要求测量截止频率,所以程序中并未测量实际的幅频特性曲线,只是测量完成之后会随机显示一幅事先准备好的幅频特性曲线图片。测量完成所有量之后,程序将状态标志设置为MEASURE_NONE_STA停止测量,然后显示测量值。
       断模式下,程序中的测量顺序基本和测量模式下一样,只不过在幅频特性测量环节会有所差异,并且判断模式下程序不会主动停止测量,会进行循环测量,当测量的到的数据异常时,程序会根据异常显示出具体是什么故障。切换到测量模式后,程序才会停止循环测量。
       个任务之后还会进行具体分析,这里只做简单的介绍。

五、ADC配置及数据采集说明

void MY_ADC_Init(void)
{
	ADC_InitTypeDef ADC_InitStructure; 
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC3,ENABLE);//开启ADC1 ADC3的时钟
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	
	ADC_IO_Init();
	
	ADC_DeInit(ADC1);  //复位ADC1
	ADC_DeInit(ADC3);  //复位ADC3 ADC_DeInit需要放置在所有ADC初始化配置之前 不然会造成数据错乱 配置错误
	
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;	//模数转换工作在多通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	//模数转换工作在多次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = ADC1_CHANNL_NUM;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
	

  
	#if (PCB_TYPE == 1)
	ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_7Cycles5 );	//测量放大器输入信号  PC0->UI_ADC
	ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_7Cycles5 );	//测量放大器输出直流  PC1->UO_DC
	ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_7Cycles5 );	//测量放大器输出信号  PC2->UO_1
	ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 4, ADC_SampleTime_7Cycles5 );	//测量放大器输出信号  PC3->UO_2 均方根
	
	#endif
	
	#if (PCB_TYPE == 2)

	#endif
	
	#if (PCB_TYPE == 3)

	#endif
	/******************** ADC1 *****************/
	ADC_DMACmd(ADC1,ENABLE);//开启ADC1的DMA
	ADC_Cmd(ADC1, ENABLE);	//使能指定ADC1
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	ADC_StartCalibration(ADC1);	 //开启AD校准
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件开启ADC1转换

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE ;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	//模数转换工作在多次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC3, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 
	ADC_RegularChannelConfig(ADC3, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5 );	//测量放大器输出信号  PA0->UO_2 扫描
	/******************** ADC3 *****************/
	ADC_DMACmd(ADC3,ENABLE);//开启ADC3的DMA
	ADC_Cmd(ADC3, ENABLE);	//使能指定ADC3
	ADC_ResetCalibration(ADC3);	//使能复位校准  
	while(ADC_GetResetCalibrationStatus(ADC3));	//等待复位校准结束
	ADC_StartCalibration(ADC3);	 //开启AD校准
	while(ADC_GetCalibrationStatus(ADC3));	 //等待校准结束
	
	ADC_SoftwareStartConvCmd(ADC3,ENABLE);//软件开启ADC3转换
	ADC_DMA_Init();
}

       序中一共需要测量5路信号,其中ADC1测量4路信号,ADC3测量1路信号,两个ADC都工作在独立模式下。
       ADC1配置为多通道、多次转换、数据右对齐、软件触发转换,调用ADC_RegularChannelConfig函数配置4个通道。然后开启ADC1的DMA,配置为循环模式,16位数据,传输量为4,不开启中断,这样配置后缓存数组的的4个值依次就是ADC_RegularChannelConfig函数配置的通道顺序,所对应的AD转换值最新值。使用TIM2中断进行数据采样,中断中只需要读取缓存数组中的对应元素值即可,TIM2工作在32k的频率下,1k的信号可以采集32个点做均方根计算。——定时器触发ADC采样
       ADC3配置为单通道、多次转换、数据右对齐、软件触发转换,调用ADC_RegularChannelConfig函数配置1个通道,ADC3_Channel_0上的信号和ADC1_Channel_12的信号是相同的,都是Uo_1信号,ADC1_Channel_12只在信号频率不超过1k时进行均方根计算时使用。ADC3_Channel_0通道则是进行扫描法,专门用来测量截止频率的,信号频率大于1k时使用。由于ADC3_Channel_0完成单次转换需要(12.5+1.5)/12M=1.167us,所以要保证1kHz下的信号可以完成一个周期的测量,DMA的传输数据量必须大于(1000/1.167=857)。最后ADC3的DMA配置为单次模式,传输量设置为2000,开启传输完成中断。
       200k频率下周期为5us,5us和 7 6 u s \frac{7}{6}us 67us的最小公倍数为35us,也就是转换30次即可得到一组最值,所以2000次的转换中可以得到多组波形信号的最值,将最值做差即可得到信号的峰峰值。——高频信号测量原理

       篇博客对程序流程等进行了整体分析,之后的博客会对程序进行更为具体细致的分析,主要是测量、判断和校准部分的分析。

       PS:程序源码和硬件电路图都在之前发的这个系列博文第一篇中,大家可以在那篇博文的最下面,点击网盘链接进行下载。

  • 15
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
制作简易LCR测试仪可以参考以下步骤: 1. 准备材料和工具:Arduino开发板、LCD1602显示屏、电阻、电容、电感、杜邦线、面包板、电源等。 2. 连接电路:根据引用\[2\]提供的原理图和PCB文件,将Arduino开发板、LCD1602显示屏和其他元件按照电路图连接起来。使用杜邦线将各个元件连接到正确的引脚上。 3. 编写代码:使用Arduino开发环境,编写代码来控制LCD1602显示屏和进行测试。可以参考引用\[2\]提供的原项目地址中的代码示例。 4. 进行自校准:在测试前的准备阶段,按照引用\[3\]中的说明进行自校准操作。确保测试仪的准确性。 5. 开始测试:将待测的电阻、电容或电感连接到测试仪上,按下启动键开始测量。测试仪会发出蜂鸣音并显示测量结果。 需要注意的是,制作简易LCR测试仪需要一定的电子知识和编程能力。如果你是初学者,建议先学习Arduino的基础知识和电路原理,再进行制作。同时,确保安全操作,避免电路短路或其他意外情况的发生。 #### 引用[.reference_title] - *1* *2* [【Arduino开源项目】LCR(电感/电容/电阻)电桥测试仪](https://blog.csdn.net/weixin_42880082/article/details/122489695)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [TI 2019全国大学生电子设计竞赛题](https://blog.csdn.net/wzy15965343032/article/details/98743653)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落叶凋凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值