一、程序流程说明
程序中使用了嵌入式实时操作系统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:程序源码和硬件电路图都在之前发的这个系列博文第一篇中,大家可以在那篇博文的最下面,点击网盘链接进行下载。