NJUPT电赛培训暨综合方向第二次积分赛报告
一、积分赛的任务和要求
任务是将输入的具有一定持续时间的周期性信号采集后再现到示波器上。
需要注意的是,输入的信号有一定的时间限制,输入与输出的信号有着峰峰值的幅度限制,负载也存在带不动负载的问题。
同时,还有要求测量某信号最大谐波的频率以及幅度值。
二、实现方案的分析
本次积分赛我队采用的是基于单片机STM32G431的方案。
对于前几问的周期信号再现的处理,输入信号经过模拟电路的比例放大器调整至单片机可以接受的范围送入单片机的ADC模块,通过G431自带的模拟看门狗,当检测到阈值电压时进入看门狗中断,在看门狗中断内部开启一个周期的信号采集工作。当但周期信号采集完毕,经过后期的信号处理送至DAC输出,经比例放大器与功放电路输出实验结果。
对于后一问的某周期信号的最大谐波频率与幅值测量,为减少频谱泄露造成的影响,用了第二块G431重新配比时钟树。
采用了ST的DSP库带有的FFT算法,将输入的信号经前述的采集过程之后,利用FFT,在FFT后的数组中遍历找到最大谐波,利用多次实验寻找到的配比调测出可有效减小频谱泄露效应的参数,最后,将这些数据利用OLED屏幕输出。
三、总体方案的设计与实现
单片机部分
单片机处理流程图如下
ADC部分代码
uint32_t stasticnum = 200;//实际有效的采样值个数
uint16_t adcdone1[100] = {0};
uint16_t adcdone2[100] = {0};
uint16_t adcdone3[100] = {0};
uint16_t adcdone4[FFT_LENGTH] = {0};
volatile bool adc_finish = false;
volatile bool adc = false;
extern uint16_t adcnew[200];
extern uint16_t adc2[10];
/*
* 函数作用:将有效的数据值筛选出来,存入另一个数组中
*
* 函数参数:传入adc直接采集存放的数组地址,传入simple函数之后要存放的数组首地址,传入
*
* 函数返回值:空
*
* */
void adc_simple(uint16_t adc_buf[][200], uint16_t adcsta[][200],uint32_t cyclenum,uint32_t stas)
{
for (int j = 0; j < cyclenum-1; j++)
{//将采样的最后一个周期略去,保持精度
for (int i = 0; i < 196; i++) {
if (adc_buf[j][i]==0&&adc_buf[j][i+1]==0&&adc_buf[j][i+2]==0&&adc_buf[j][i+3]==0)
{
if(stas > i-1)
{
stas= i - 1;
}
}
break;
}
for (int k = 0; k < stas; k++)
{//将精准有效的采样值放入新的数组adcstastic中
adcsta[j][k] = adc_buf[j][k];
}
}
}
/*
* 函数作用:将传入的数组进行滤波:改版滑动滤波器(平均值)
*
* 函数参数:传入的需要进行滤波的数组首地址,传出的滤波操作完毕的接收数组首地址
*
* 函数返回值:空
* */
void adc_filting(uint16_t adcstas[][200],uint16_t adcDone[200],uint32_t cyclenum)
{
for (int j = 0; j < stasticnum; j++)
{
uint16_t num=0;
for (int i = 0; i < cyclenum-1; i++)
{
if (adcstas[i][j]==0&&adcstas[i][j+1]==0&&adcstas[i][j+2]==0&&adcstas[i][j+3]==0&&adcstas[0][j]!=0)
{
adcstas[i][j] = adcstas[0][j];
}
num += adcstas[i][j];
}
adcDone[j] = 1.0*num/(cyclenum-1);
}
}
/*
* 函数作用:中位值滤波 连续采样N次(N取奇数)把N次采样值按大小排列取中间值为本次有效值
*
* 函数参数:传入采样中位值数据,待处理数组,数组实际需要个数
* */
void middleValueFilter(uint32_t N,uint16_t adcDone[FFT_LENGTH],uint32_t st)
{
st=stasticnum;
N = 3;
uint16_t value_buffer[4] = {0};
int i, j, k, temp;
for (int m = 0; m < st-3; m++)
{
for (int l = 0; l < N; ++l)
{
value_buffer[l] = adcDone[m+l];
}
for (j = 0; j < N - 1; ++j)
{
for (k = 0; k < N - j - 1; ++k)
{
//从小到大排序,冒泡法排序
if (value_buffer[k] > value_buffer[k + 1])
{
temp = value_buffer[k];
value_buffer[k] = value_buffer[k + 1];
value_buffer[k + 1] = temp;
}
}
}
adcDone[m]=value_buffer[(N-1)/2];
}
adcDone[st-1] = adcDone[0];
}
/*
* 函数作用:重置下关于adc测量的标识符以及数组,但是不重置已经装载好的adc测量数组
*
* 函数参数:暂无
*
* 函数返回值:暂无
*
* */
void adc_reset()
{
memset(adcnew,0x0, sizeof(adcnew));
memset(adc2,0x0, sizeof(adc2));
stasticnum = 200;
adc_finish = false;
adc = false;
}
/**/
void adc_get_value(uint16_t adcne[1024],uint16_t adcdon[1024],uint32_t stas)
{
for (int i = 0; i < stas; i++) {
adcdon[i] = adcne[i];
}
adcdon[0] = (adcdon[stas-1]+adcdon[0])/2;
}
FFT部分代码
//
// Created by Silencecmsj on 2024/4/8.
//
#include "fft.h"
float fft_input[FFT_LENGTH*2];
float fft_output[FFT_LENGTH];
float freq = 0;
float Vpp = 0;
uint16_t Freq[20] = {0};
uint16_t V[20] = {0};
arm_cfft_radix4_instance_f32 scfft;
/*
* 函数作用:将fft变换的数据装载到进行fft变换的数组中
*
* 函数参数:传入需要fft变换的数组,传出处理完毕的fft代变换的数组
*
* 函数返回值:空
* */
void fft_loading(uint16_t fftbu[FFT_LENGTH],float fft_in[FFT_LENGTH*2])
{
for (int i = 0; i < FFT_LENGTH; ++i)
{
fft_in[i * 2] = (float )fftbu[i]/4096*3.3f;
fft_in[i * 2 + 1] = 0;
}
}
/*
* 函数作用:运行fft
*
* 函数参数:传入fft装载好的数组,传出fft运行后的数组
*
* 返回值:空
* */
void fft_over(float ff_in[],float ff_out[])
{
arm_cfft_radix4_init_f32(&scfft, FFT_LENGTH, 0, 1);//初始化fft
//arm_cfft_f32(&scfft, ff_in, 0, 1);
arm_cfft_radix4_f32(&scfft, ff_in);
arm_cmplx_mag_f32(ff_in, ff_out, FFT_LENGTH);
ff_out[0] /= (float )FFT_LENGTH;//根据傅里叶变换原理,直流分量幅频曲线中幅值与时域的幅值相等(好吧我也不太清楚)
for (int i = 1; i < FFT_LENGTH; i++)//输出各次谐波幅值
{
ff_out[i] /=(float ) FFT_LENGTH/2;
}
}
main函数部分代码
/*按键1按下,执行题目1操作,若已经采集过波形,则按下输出*/
if (KeyFlag == 1 || keyPressed == 1)
{
if (KeyFlag)
{
adc_single = true;
KeyFlag = 0;
keyPressed = 1;
HAL_TIM_Base_Stop(&htim3);
HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
TIM1->ARR =30 - 1;//测10khz,用1Mhz采样率
stasticnum = 400;
HAL_TIM_Base_Start(&htim1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) adc2, 10);
// printf("fuck you\r\n");
}
if (!adc_data1_process)//当初次按下按键
{
adc_reset();//重置下关于adc测量的标识符以及数组,但是不重置已经装载好的adc测量数组
stasticnum = 400;
KEY1_PRESS = true;
//printf("fuck you\r\n");
while(adc == false);
if (adc)
{
adc_get_value(adcnew, adcdone1, stasticnum);
adc_data1_process = true;
for (int i = 0; i < stasticnum; i++)
{
printf("%d,%d\r\n", i, adcdone1[i]);
}
}
if (adc_data1_process == true)
{
KEY1_PRESS = false;//停止再次进入gpio的中断
//dac_dma开的循环模式,只需开启一次dac即可
do {
adc_single = false;
TIM3->ARR = 30 - 1;
stasticnum = 400;
dac_output(adcdone1);
} while (KEY1_PRESS);
}
}
if (adc_data1_process == true&&adc_single==true){
adc_single = false;
TIM3->ARR = 30 - 1;
dac_output(adcdone1);
}
}
if (KeyFlag == 5|| keyPressed == 5)//重置按钮
{
KeyFlag = 0;
keyPressed = 0;
HAL_TIM_Base_Stop(&htim3);
HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
everything_reset();
}
watchdog中断回调函数部分代码
void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc)
{
/*
if (KEY4_PRESS)
{
if (adc_finish == false)
{
HAL_TIM_Base_Stop(&htim1);
HAL_ADC_Stop_DMA(&hadc1);
HAL_TIM_Base_Start(&htim1);//tim1用作ADC的采集
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)fft_buffer, stasticnum);
adc_finish = true;
}
}
*/
if (KEY1_PRESS||KEY2_PRESS||KEY3_PRESS)
{
if (adc_finish == false)
{
HAL_TIM_Base_Stop(&htim1);
HAL_ADC_Stop_DMA(&hadc1);
HAL_TIM_Base_Start(&htim1);
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adcnew,stasticnum);
adc_finish = true;
}
}
}
模拟电路原理图部分
四、整体指标
周期误差符合要求,周期误差控制在1%,符合题目标准。
五、附录
六、号外
准备的两片STM32G431全部烧坏,本次积分赛胎死临产的前一夜。
悲