一、题目
测量电路可以测量和显示脉冲波信号的平均值、高电平值、低电平值,量程 3.3V,分辨率 0.1V、测量误差不大于 5%。
二、思路
- 使用 ADC + DMA 采集信号,DMA 将采集到的数据存入环形缓冲区数组中
- 使用 TIM3 作为 ADC 采样触发定时器
- 在传输完成回调函数里,对最近一批采样数据进行计算
- 使用 OLED 显示结果
- 关于分辨率:使用 12 位 ADC, 则量化等级有
2
12
2^{12}
212 个,参考电压为
V
r
e
f
V_{ref}
Vref,则每个最小量化步长大小为:
Q = V r e f 2 N . Q = \frac{V_{ref}}{2^{N}}. Q=2NVref. - 注意:怎样使求的平均值准确?
- 使用 FFT:否则处理的信号有频率限制。
DFT 的主要作用是将离散的时域信号转换为频域信号,从而得到信号在不同频率上的幅度与相位分布。
三、CubeMX 配置
-
配置 debug 方式
-
配置外部时钟晶振
-
配置 ADC
-
配置 DMA
-
配置定时器
-
配置 I2C2
-
配置时钟树
-
配置 DSP 库(没有使用)
-
在 Keil 中导入 DSP(没有使用)
添加宏定义(没有使用)
添加三个 include(没有使用)
/* USER CODE BEGIN Includes */
#include "oled.h"
#include <stdio.h>
#include "arm_math.h"
#include "arm_const_structs.h"
/* USER CODE END Includes */
在 Define 添加(没有使用)
,ARM_MATH_CM3
四、代码编写
- 头文件
/* USER CODE BEGIN Includes */
#include "oled.h"
/* USER CODE END Includes */
PRIVATE DEFINE
/* USER CODE BEGIN PD */
#define ADC_BUFFER_LENGTH 1024 // 环形缓冲区数组大小
#define VOLTAGE_MAX 3.3f // 输出电压上限
/* USER CODE END PD */
- 变量定义
/* USER CODE BEGIN PV */
uint16_t adc_buff[ADC_BUFFER_LENGTH] = {0}; // 存放 ADC 采集的数据
float processed_adc_buff[ADC_BUFFER_LENGTH] = {0.0}; // 转换到 0-3.3V 区间的电压
float adc_sum = 0; // 区间电压求和
float adc_max = 0; // 最大电压
float adc_min = 4; // 最小电压
float adc_average = 0; // 平均值
uint32_t adc_low = 0; // 低于 adc_middle 的个数
uint32_t adc_high = 0; // 高于 adc_middle 的个数
int cnt = 0;
const unsigned char str1[5] = "Max:"; // 设成 4 个长度会不正常显示
const unsigned char str2[5] = "Min:";
const unsigned char str3[5] = "Ave:";
/* USER CODE END PV */
处理数据的函数
void process_data(void){
for (uint16_t i = 100; i < ADC_BUFFER_LENGTH - 100; i ++){
processed_adc_buff[i] = (float)adc_buff[i] * 3.3 / 4095.0; // 把 ADC 采集值转换到 0-3.3V
adc_sum += processed_adc_buff[i]; // 对电压求和,为了后面求平均值
if (processed_adc_buff[i] > adc_max){ // 更新最大值
adc_max = processed_adc_buff[i];
}
if (processed_adc_buff[i] < adc_min){ // 更新最小值
adc_min = processed_adc_buff[i];
}
}
adc_average = adc_sum / (ADC_BUFFER_LENGTH - 200); // 求平均值
cnt ++;
if (cnt % 10 == 0){
cnt %= 10;
OLED_Refresh_Gram();
OLED_ShowString(0, 0, str1, 16);
OLED_ShowString(0, 16, str2, 16);
OLED_ShowString(0, 32, str3, 16);
OLED_ShowDecimal(35, 0, adc_max, 1, 3, 16, 1);
OLED_ShowDecimal(35, 16, adc_min, 1, 3, 16, 1);
OLED_ShowDecimal(35, 32, adc_average, 1, 3, 16 , 1);
OLED_ShowChar(78, 0,'V', 16, 1);
OLED_ShowChar(78, 16,'V', 16, 1);
OLED_ShowChar(78, 32,'V', 16, 1);
}
adc_max=0; //最大电压归零
adc_min=4; //最小电压归4
adc_sum = 0; // 区间和
adc_average = 0; // 区间平均
}
ADC 中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){ // ADC 中断回调函数
UNUSED(hadc);
if (hadc == &hadc1){
HAL_ADC_Stop(hadc); // 停止 ADC
process_data(); // 处理数据
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buff, ADC_BUFFER_LENGTH); // 中断结束,重新开始 DMA 采集
}
}
初始化
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim3); // 开启定时器
HAL_ADCEx_Calibration_Start(&hadc1); // ADC 校准
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buff, ADC_BUFFER_LENGTH); // 开始 DMA 采集,采集 1024 个存到 adc_buff 数组; 使用强制类型转换成 32 位
OLED_Init();
OLED_Clear();
/* USER CODE END 2 */
主循环 (不用)
/* USER CODE BEGIN WHILE */
while (1)
{
// HAL_Delay(3000);
if(capture_end_flag == 1)
{
OLED_Refresh_Gram();
OLED_ShowNum(0, 0, TIM3CH1_Freq, 6, 16); // 显示频率
OLED_ShowString(50, 0, str1, 16);
OLED_ShowNum(0, 16, (uint16_t)(TIM3CH1_Duty * 100) % 100, 2, 16); // 显示占空比的整数
OLED_ShowChar(16, 16, '.', 16, 1);
OLED_ShowChar(38, 16, '%', 16, 1);
if ((uint16_t)(TIM3CH1_Duty * 10000) % 100 < 10 && 0 < (uint16_t)(TIM3CH1_Duty * 10000) % 100){ // 显示占空比的小数
OLED_ShowChar(21, 16, '0', 16, 1);
}else if ((uint16_t)(TIM3CH1_Duty * 10000) % 100 == 0){
OLED_ShowChar(21, 16, '0', 16, 1);
OLED_ShowChar(28, 16, '0', 16, 1);
}
else{
OLED_ShowNum(21, 16, (uint16_t)(TIM3CH1_Duty * 10000) % 100, 2, 16);
}
capture_end_flag = 0;
}
/* USER CODE END WHILE */
五、其他
UNUSED()
:告知编译器不要发出“未使用参数”的警告。OLED_ShowString()
设置成刚好字符串长度会不正常显示;设置成字符串长度 + 1 才可以。- OLED 显示小数的代码:
//显示数字
//x,y :起点坐标
//num :要显示的小数字
//z_len :整数数字的位数
//f_len :小数数字的位数
//size:字体大小
//mode:0,反色显示;1,正常显示
//z_len为整数显示位数,f_len为小数显示位数,size1为字体大小
void OLED_ShowDecimal(unsigned char x, unsigned char y, float num,unsigned char z_len,unsigned char f_len,unsigned char size1,unsigned char mode)
{
unsigned char t,temp;
unsigned char enshow=0;
int z_temp,f_temp;
z_temp=(int)num;
//整数部分
for(t=0;t<z_len;t++)
{
temp=(z_temp/OLED_Pow(10,z_len-t-1))%10;
if(enshow==0 && t<(z_len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size1/2)*t,y,' ',size1,mode);
continue;
}
else
enshow=1;
}
OLED_ShowChar(x+(size1/2)*t,y,temp+'0',size1,mode);
}
//小数点
OLED_ShowChar(x+(size1/2)*(z_len),y,'.',size1,mode);
f_temp=(int)((num-z_temp)*(OLED_Pow(10,f_len)));
//小数部分
for(t=0;t<f_len;t++)
{
temp=(f_temp/OLED_Pow(10,f_len-t-1))%10;
OLED_ShowChar(x+(size1/2)*(t+z_len)+5,y,temp+'0',size1,mode);
}
}
- 观察到这个代码可以正常测出脉冲波、锯齿波,但无法正常测出正弦波。