3个大学生顶个工程师?为3千元奖金,压缩项目成本,却做出好产品

一个外包项目,悬赏3000元

要求:用指定芯片做一个指夹式血氧仪

你猜项目成本可以被压缩到多少?

本次的项目作者,是3个大学生。

可别以为大学生经验少!

他们不但将成本压缩到了三位数,同时还保证了质量,做出了产品级指压式血氧仪。

最近,他们将项目进行了开源。

他们是如何实现产品级功能的?是如何进一步压缩成本的?

我们结合他们发布的开源资料,一起看看!


项目功能

那么,想要在压缩成本的基础上,实现这些功能,该如何设计硬件代码?如何选型元器件?

在那之前,我们得先理解它的工作原理!


血氧仪工作原理

血液中,血红细胞的含氧血红蛋白(HbO2)和还原血红蛋白(Hb),对红光(660nm)和红外线(900nm)有不同的吸收能力。

指夹式血氧仪的工作原理就是:

在设备的同一位置,设置红光LED和红外线LED灯,测量血氧饱和度。

光线从手指的一面穿透到另一面,就能检测两种血红蛋白对不同波长的光吸收的区别,所测出来的数据差被光敏二极管接收后,可产生对应比例的电压。就可以测出实际含氧量下,血氧饱和度最基本的数据比值

实际上要做到更高的精度,除了两个波长以外还要增加,甚至高达8个波长。

本项目为两个波长。


硬件设计思路

血氧仪由电源板主控板组成,两块板子尺寸都不超过10cm*10cm,可以在嘉立创EDA免费打板,这样一来,就省下了PCB电路板的钱

1.电源板

电源部分的设计,需要实现USB外接供电电池供电电池充电等功能。

因此整体架构包括——电源路径管理及电池充电电路、5V供电电路、3.3V供电电路

电源板就主要围绕这三个部分,讲解设计思路。

1.1 电源路径管理及电池充电电路

电源路径管理电路采用P-MOS作为开关,通过G端电压与S端电压关系,实现USB供电与电池供电的动态切换功能。

电池充电电路采用TC4056A芯片作为主控,依托其可编程充电电流控制、充电状态指示等功能,实现单节锂电池充电功能。

USB接口增加过压过流保护电路设计,防止插入瞬间尖峰电压对后级电路的冲击。

增加D3二极管的目的是加速P-MOS导通,防止因供电方式切换,导致主控掉电复位等问题。

原理图设计如下。

1.2 直流5V供电电路

直流5V供电电路采用MT3608芯片搭建Sepic电路,确保在电池电压下降时也能稳定提供5V电压。

原理图设计如下。

1.3 直流3.3V供电电路

直流3.3V供电电路采用AMS1117-3.3芯片构建LDO降压电路,稳定提供3.3V电压。

原理图设计如下。

1.4 PCB设计

2.主控板

主控板包括MCU电路、发射电路、接收电路、按键电路、蜂鸣器电路、TFT显示屏电路。

这六部分用于实现血氧仪主要功能

下面也主要围绕这6个部分,讲解设计思路。

2.1 MCU电路

MCU电路采用CW32L031C8T6作为主控芯片,设计BOOT电路、SWD烧录接口及复位按钮(不焊接),受空间限制,取消外部晶振电路。

原理图设计如下:

2.2 发射电路

发射电路采用“RS2105+RS622”设计方案。

采用“660nm红光+900nm红外光”的双波长发射管,内部反向并联连接,通过上述H桥电路控制发射时序发射功率

原理图设计如下:

2.3 接收电路

接收电路采用RS622双路运放芯片作为核心。

前后级之间通过电容耦合,并与电阻构成高通滤波器,有效滤除直流信号。

原理图设计如下:

2.4 按键电路

独立按键设计,采用1mm超薄按键,通过并联电容构成硬件消抖电路,通过电阻接入MCU的PB03引脚,按键按下为低电平(低电平有效)。

原理图设计如下:

2.5 蜂鸣器电路(当前版本PCB受空间限制已取消)

蜂鸣器电路采用2KHz无源蜂鸣器作为核心元件,以N沟道MOS管作为开关,通过输出一定频率的PWM信号驱动蜂鸣器发声。

原理图设计如下:

2.6 TFT显示屏电路

TFT显示屏电路用于驱动0.96寸全彩LCD显示屏

设计8P抽屉式下接FPC接口,用于连接带软排线接口的显示屏。同时以PNP三极管作为开关,通过MCU输出一定占空比的PWM信号实现屏幕背光控制

原理图设计如下:

2.7 PCB设计

软件说明

软件部分,虽然需要很多的时间成本,但不怎么需要花钱。

而作为有彩屏“智能交互”功能的“产品”,软件部分尤为重要。

本章说明一下这三个部分:TFT显示屏、FFT算法实现、FFT结果运用。

1. TFT显示屏(2部分)

1.1 LCD初始化

#include "LCD_INIT.h"

/******************************************************************************
函数说明:LCD复位函数
入口数据:无
返回值: 无
******************************************************************************/
void Lcd_Reset(void)
{
LCD_RES_Clr();
FirmwareDelay(100);
LCD_RES_Set();
FirmwareDelay(100);
}

/******************************************************************************
函数说明:LCD_GPIO初始化函数
入口数据:dat 要写入的串行数据
返回值: 无
******************************************************************************/
void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;

__RCC_GPIOA_CLK_ENABLE();

GPIO_InitStruct.IT = GPIO_IT_NONE;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pins = GPIO_PIN_2| GPIO_PIN_3| GPIO_PIN_4| GPIO_PIN_5|GPIO_PIN_8;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
}


/******************************************************************************
函数说明:LCD串行数据写入函数
入口数据:dat 要写入的串行数据
返回值: 无
******************************************************************************/
void LCD_Writ_Bus(uint8_t dat)
{
uint8_t i;
LCD_CS_Clr();
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_Set();
}
else
{
LCD_MOSI_Clr();
}
LCD_SCLK_Set();
dat<<=1;
}
LCD_CS_Set();
}


/******************************************************************************
函数说明:LCD写入8位数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void Lcd_WriteData(uint8_t dat)
{
LCD_Writ_Bus(dat);
}


/******************************************************************************
函数说明:LCD写入16位数据
入口数据:dat 写入的数据
返回值: 无
******************************************************************************/
void LCD_WR_DATA(uint16_t dat)
{
LCD_Writ_Bus(dat>>8);
LCD_Writ_Bus(dat);
}


/******************************************************************************
函数说明:LCD写入命令
入口数据:dat 写入的命令
返回值: 无
******************************************************************************/
void Lcd_WriteIndex(uint8_t dat)
{
LCD_DC_Clr();//写命令
LCD_Writ_Bus(dat);
LCD_DC_Set();//写数据
}
/******************************************************************************
函数说明:设置起始和结束地址
入口数据:x1,x2 设置列的起始和结束地址
y1,y2 设置行的起始和结束地址
返回值: 无
******************************************************************************/
void LCD_Address_Set(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2)
{
if(USE_HORIZONTAL==0)
{
Lcd_WriteIndex(0x2a);//列地址设置
LCD_WR_DATA(x1+26);
LCD_WR_DATA(x2+26);
Lcd_WriteIndex(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
Lcd_WriteIndex(0x2c);//储存器写
}
else if(USE_HORIZONTAL==1)
{
Lcd_WriteIndex(0x2a);//列地址设置
LCD_WR_DATA(x1+26);
LCD_WR_DATA(x2+26);
Lcd_WriteIndex(0x2b);//行地址设置
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
Lcd_WriteIndex(0x2c);//储存器写
}
else if(USE_HORIZONTAL==2)
{
Lcd_WriteIndex(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
Lcd_WriteIndex(0x2b);//行地址设置
LCD_WR_DATA(y1+26);
LCD_WR_DATA(y2+26);
Lcd_WriteIndex(0x2c);//储存器写
}
else
{
Lcd_WriteIndex(0x2a);//列地址设置
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
Lcd_WriteIndex(0x2b);//行地址设置
LCD_WR_DATA(y1+26);
LCD_WR_DATA(y2+26);
Lcd_WriteIndex(0x2c);//储存器写
}
}

/******************************************************************************
函数说明:LCD初始化代码
入口数据:无
返回值: 无
******************************************************************************/
void LCD_Init(void)
{
LCD_GPIO_Init();//初始化GPIO

LCD_RES_Clr();//复位
FirmwareDelay(1);
LCD_RES_Set();
//FirmwareDelay(1);

//LCD_BLK_Set();//打开背光
// FirmwareDelay(1);

Lcd_WriteIndex(0x11); //Sleep out
//FirmwareDelay(1); //Delay 120ms
Lcd_WriteIndex(0xB1); //Normal mode
Lcd_WriteData(0x05);
Lcd_WriteData(0x3C);
Lcd_WriteData(0x3C);
Lcd_WriteIndex(0xB2); //Idle mode
Lcd_WriteData(0x05);
Lcd_WriteData(0x3C);
Lcd_WriteData(0x3C);
Lcd_WriteIndex(0xB3); //Partial mode
Lcd_WriteData(0x05);
Lcd_WriteData(0x3C);
Lcd_WriteData(0x3C);
Lcd_WriteData(0x05);
Lcd_WriteData(0x3C);
Lcd_WriteData(0x3C);
Lcd_WriteIndex(0xB4); //Dot inversion
Lcd_WriteData(0x03);
Lcd_WriteIndex(0xC0); //AVDD GVDD
Lcd_WriteData(0xAB);
Lcd_WriteData(0x0B);
Lcd_WriteData(0x04);
Lcd_WriteIndex(0xC1); //VGH VGL
Lcd_WriteData(0xC5); //C0
Lcd_WriteIndex(0xC2); //Normal Mode
Lcd_WriteData(0x0D);
Lcd_WriteData(0x00);
Lcd_WriteIndex(0xC3); //Idle
Lcd_WriteData(0x8D);
Lcd_WriteData(0x6A);
Lcd_WriteIndex(0xC4); //Partial+Full
Lcd_WriteData(0x8D);
Lcd_WriteData(0xEE);
Lcd_WriteIndex(0xC5); //VCOM
Lcd_WriteData(0x0F);
Lcd_WriteIndex(0xE0); //positive gamma
Lcd_WriteData(0x07);
Lcd_WriteData(0x0E);
Lcd_WriteData(0x08);
Lcd_WriteData(0x07);
Lcd_WriteData(0x10);
Lcd_WriteData(0x07);
Lcd_WriteData(0x02);
Lcd_WriteData(0x07);
Lcd_WriteData(0x09);
Lcd_WriteData(0x0F);
Lcd_WriteData(0x25);
Lcd_WriteData(0x36);
Lcd_WriteData(0x00);
Lcd_WriteData(0x08);
Lcd_WriteData(0x04);
Lcd_WriteData(0x10);
Lcd_WriteIndex(0xE1); //negative gamma
Lcd_WriteData(0x0A);
Lcd_WriteData(0x0D);
Lcd_WriteData(0x08);
Lcd_WriteData(0x07);
Lcd_WriteData(0x0F);
Lcd_WriteData(0x07);
Lcd_WriteData(0x02);
Lcd_WriteData(0x07);
Lcd_WriteData(0x09);
Lcd_WriteData(0x0F);
Lcd_WriteData(0x25);
Lcd_WriteData(0x35);
Lcd_WriteData(0x00);
Lcd_WriteData(0x09);
Lcd_WriteData(0x04);
Lcd_WriteData(0x10);
Lcd_WriteIndex(0xFC);
Lcd_WriteData(0x80);
Lcd_WriteIndex(0x3A);
Lcd_WriteData(0x05);
Lcd_WriteIndex(0x36);
if(USE_HORIZONTAL==0)Lcd_WriteData(0x08);
else if(USE_HORIZONTAL==1)Lcd_WriteData(0xC8);
else if(USE_HORIZONTAL==2)Lcd_WriteData(0x78);
else Lcd_WriteData(0xA8);
Lcd_WriteIndex(0x21); //Display inversion
Lcd_WriteIndex(0x29); //Display on
Lcd_WriteIndex(0x2A); //Set Column Address
Lcd_WriteData(0x00);
Lcd_WriteData(0x1A); //26
Lcd_WriteData(0x00);
Lcd_WriteData(0x69); //105
Lcd_WriteIndex(0x2B); //Set Page Address
Lcd_WriteData(0x00);
Lcd_WriteData(0x01); //1
Lcd_WriteData(0x00);
Lcd_WriteData(0xA0); //160
Lcd_WriteIndex(0x2C);
}

1.2 LCD主要功能函数

#include "LCD.h"
#include "LCD_INIT.h"
#include "LCD_FONT.h"
/******************************************************************************
 函数说明:在指定区域填充颜色
 入口数据:xsta,ysta 起始坐标
 xend,yend 终止坐标
 color 要填充的颜色
 返回值: 无
******************************************************************************/
void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color)
{
 uint16_t i = ysta;
 uint16_t j = xsta;
 LCD_Address_Set(xsta,ysta,xend-1,yend-1);//设置显示范围
 for(i=ysta;i


2.时序控制

控制时序说明

代码如下:(在BTIM1定时器中断回调函数中实现)

void BTIM1_IRQHandlerCallback(void)
{
if(SET == BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
if(IsCycleEnd == 1) //128次采样周期结束标志:1为采样中,0为采样结束
{
if(BTIM1_counter3 > 2) //计时达到3ms
{
BTIM1_counter3 = 0;
switch(SEND_status)
{
case 0: //发射红外信号(3ms)
{
SEND_status ++;
GPIO_WritePin(bsp_IN1_port, bsp_IN1_pin, GPIO_Pin_RESET);
GPIO_WritePin(bsp_IN2_port, bsp_IN2_pin, GPIO_Pin_SET);
DAC1_PWM = 0;
DAC2_PWM = 300 + DAC_PWM_PLUS;
GTIM_SetCompare1(CW_GTIM2, DAC1_PWM); //设置DAC1占空比为0
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //设置DAC2占空比为300+调整值
GTIM_Cmd(CW_GTIM2, ENABLE);
IRorRED = 0; //设置红外或红光标志:红外
ADC_SoftwareStartConvCmd(ENABLE); //启动ADC转换
break;
}
case 1: //关闭信号发射(3ms)
{
SEND_status ++;
GPIO_WritePin(bsp_IN1_port, bsp_IN1_pin, GPIO_Pin_RESET);
GPIO_WritePin(bsp_IN2_port, bsp_IN2_pin, GPIO_Pin_RESET);
DAC1_PWM = 0;
DAC2_PWM = 0;
GTIM_SetCompare1(CW_GTIM2, DAC1_PWM); //设置占空比为0
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //设置占空比为0
GTIM_Cmd(CW_GTIM2, DISABLE);
//ADC_SoftwareStartConvCmd(DISABLE);
break;
}
case 2: //发射红光信号(3ms)
{
SEND_status ++;
GPIO_WritePin(bsp_IN1_port, bsp_IN1_pin, GPIO_Pin_SET);
GPIO_WritePin(bsp_IN2_port, bsp_IN2_pin, GPIO_Pin_RESET);
DAC1_PWM = 300 + DAC_PWM_PLUS;
DAC2_PWM = 0;
GTIM_SetCompare1(CW_GTIM2, DAC1_PWM); //设置DAC1占空比为300+调整值
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //设置DAC2占空比为0
GTIM_Cmd(CW_GTIM2, ENABLE);
IRorRED = 1; //设置红外或红光标志:红光
ADC_SoftwareStartConvCmd(ENABLE); //启动ADC转换
break;
}
case 3: //关闭信号发射(4ms)
{
SEND_status = 0;
GPIO_WritePin(bsp_IN1_port, bsp_IN1_pin, GPIO_Pin_RESET);
GPIO_WritePin(bsp_IN2_port, bsp_IN2_pin, GPIO_Pin_RESET);
DAC1_PWM = 0;
DAC2_PWM = 0;
GTIM_SetCompare1(CW_GTIM2, DAC1_PWM); //设置占空比为0
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //设置占空比为0
GTIM_Cmd(CW_GTIM2, DISABLE);
//ADC_SoftwareStartConvCmd(DISABLE);
break;
}
}
}
else
{
BTIM1_counter3++;
}
}
}
}


3.算法设计(3部分)

3.1 FFT算法原理

FFT是一种DFT的高效算法,称为快速傅立叶变换(fast Fourier transform)。

DFT的运算如下:

FFT算法可分为按时间抽取算法和按频率抽取算法

  • 这种方法计算DFT对于X(K)的每个K值,需要进行4N次实数相乘和(4N-2)次相加。
  • 对于N个k值,共需N*N乘和N(4N-2)次实数相加。

改进DFT算法,减小它的运算量,利用DFT中的周期性和对称性,使整个DFT的计算变成一系列迭代运算,可大幅度提高运算过程和运算量,这就是FFT的基本思想。

3.2 FFT算法实现

(1)计算三角函数表

//保存SIN值
signed char SIN_TAB[128]={
0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x2a,
0x30, 0x36, 0x3b, 0x41, 0x46, 0x4b, 0x50, 0x55,
0x59, 0x5e, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x72,
0x75, 0x77, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x7e,
0x7f, 0x7e, 0x7e, 0x7d, 0x7c, 0x7b, 0x79, 0x77,
0x75, 0x72, 0x70, 0x6c, 0x69, 0x66, 0x62, 0x5e,
0x59, 0x55, 0x50, 0x4b, 0x46, 0x41, 0x3b, 0x36,
0x30, 0x2a, 0x24, 0x1e, 0x18, 0x12, 0x0c, 0x06,
0x00, -0x06, -0x0c, -0x12, -0x18, -0x1e, -0x24, -0x2a,
-0x30, -0x36, -0x3b, -0x41, -0x46, -0x4b, -0x50, -0x55,
-0x59, -0x5e, -0x62, -0x66, -0x69, -0x6c, -0x70, -0x72,
-0x75, -0x77, -0x79, -0x7b, -0x7c, -0x7d, -0x7e, -0x7e,
-0x7f, -0x7e, -0x7e, -0x7d, -0x7c, -0x7b, -0x79, -0x77,
-0x75, -0x72, -0x70, -0x6c, -0x69, -0x66, -0x62, -0x5e,
-0x59, -0x55, -0x50, -0x4b, -0x46, -0x41, -0x3b, -0x36,
-0x30, -0x2a, -0x24, -0x1e, -0x18, -0x12, -0x0c, -0x06};

//以下是放大128倍后的cos余弦函数数组表格,这里注意事项与上面相同,只不过选择余弦来生成
signed char COS_TAB[128]={
0x7f, 0x7e, 0x7e, 0x7d, 0x7c, 0x7b, 0x79, 0x77,
0x75, 0x72, 0x70, 0x6c, 0x69, 0x66, 0x62, 0x5e,
0x59, 0x55, 0x50, 0x4b, 0x46, 0x41, 0x3b, 0x36,
0x30, 0x2a, 0x24, 0x1e, 0x18, 0x12, 0x0c, 0x06,
0x00, -0x06, -0x0c, -0x12, -0x18, -0x1e, -0x24, -0x2a,
-0x30, -0x36, -0x3b, -0x41, -0x46, -0x4b, -0x50, -0x55,
-0x59, -0x5e, -0x62, -0x66, -0x69, -0x6c, -0x70, -0x72,
-0x75, -0x77, -0x79, -0x7b, -0x7c, -0x7d, -0x7e, -0x7e,
-0x7f, -0x7e, -0x7e, -0x7d, -0x7c, -0x7b, -0x79, -0x77,
-0x75, -0x72, -0x70, -0x6c, -0x69, -0x66, -0x62, -0x5e,
-0x59, -0x55, -0x50, -0x4b, -0x46, -0x41, -0x3b, -0x36,
-0x30, -0x2a, -0x24, -0x1e, -0x18, -0x12, -0x0c, -0x06,
0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x2a,
0x30, 0x36, 0x3b, 0x41, 0x46, 0x4b, 0x50, 0x55,
0x59, 0x5e, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x72,
0x75, 0x77, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x7e};

unsigned char LIST_TAB[128]={
0,64,32,96,16,80,48,112,
8,72,40,104,24,88,56,120,
4,68,36,100,20,84,52,116,
12,76,44,108,28,92,60,124,
2,66,34,98,18,82,50,114,
10,74,42,106,26,90,58,122,
6,70,38,102,22,86,54,118,
14,78,46,110,30,94,62,126,
1,65,33,97,17,81,49,113,
9,73,41,105,25,89,57,121,
5,69,37,101,21,85,53,117,
13,77,45,109,29,93,61,125,
3,67,35,99,19,83,51,115,
11,75,43,107,27,91,59,123,
7,71,39,103,23,87,55,119,
15,79,47,111,31,95,63,127};

(2)FFT函数

void Fft_Imagclear(void) //fft虚部清零函数,在运行FFT函数之前需要先运行这个
{
unsigned char a; //注意这里如果是256点以上要改成u16,下面的a<128条件也要相应的修改
for(a=0;a<128;a++)
{
Fft_Image[a]=0;
}
}

signed short Fft_Real[128]; //fft实部,128数组
signed short Fft_Image[128]; //fft虚部,128数组
void FFT(void)
{
unsigned char i,j,k,b,p;
signed short Temp_Real,Temp_Imag,temp; //中间临时变量,名称也是自己定义的,但要与fft函数里面的对应
//unsigned short TEMP1; //用于求功率的,可不需要
unsigned char N=7; //这里因为128是2的7次方,如果是计算256点,则是2的8次方,N就是8,如果是512点则N 9,如此类推
unsigned short NUM_FFT=128; //这里要算多少点的fft就赋值多少,值只能是2的N次方
for( i=1; i<=N; i++) /* for(1) */
{
b=1;
b <<=(i-1); //蝶式运算,用于计算 隔多少行计算。例如第一级 1和2行计算,,,第二级
for( j=0; j<=b-1; j++) /* for (2) */
{
p=1;
p <<= (N-i);
p = p*j;
for( k=j; k>7) + ((Fft_Image[k+b]*SIN_TAB[p])>>7);
Fft_Image[k] = Fft_Image[k] - ((Fft_Real[k+b]*SIN_TAB[p])>>7) + ((Fft_Image[k+b]*COS_TAB[p])>>7);
Fft_Real[k+b] = Temp_Real - ((Fft_Real[k+b]*COS_TAB[p])>>7) - ((Fft_Image[k+b]*SIN_TAB[p])>>7);
Fft_Image[k+b] = Temp_Imag + ((temp*SIN_TAB[p])>>7) - ((Fft_Image[k+b]*COS_TAB[p])>>7);
//移位,防止溢出。结果已经是本值的1/64
Fft_Real[k] >>= 1;
Fft_Image[k] >>= 1;
Fft_Real[k+b] >>= 1;
Fft_Image[k+b] >>= 1;
}
}
}
}
///注意:以上已经把128点的实部和虚部求完,下一次运算前需要把所有虚部重新清零

signed short Get_fft_value(int n,int m) //获取FFT结果的实部或虚部
{
if(n==0) return Fft_Real[m];
else return Fft_Image[m];
  }

3.3 FFT结果运用

(1):直接用某个频率点的值,可以做音频频谱强度显示

第n个频率点的值是数组上的Fft_Real[n]和Fft_Image[n]

(2):求某个频率点的模

模值=根号(实部平方+虚部平方),即sqrt((Fft_Real[n]*Fft_Real[n])+(Fft_Image[n]*Fft_Image[n]))

(3):清除特定频率的分量,一般用于数字滤波算法

Fft_Real[0]=Fft_Image[0]=0; //去掉直流分量,即将第0项的值清零

Fft_Real[63]=Fft_Image[63]=0; //要去除某个频率的分量,可将该频率对应的数组项的值清零

Fft_Real[0]是直流分量。Fft_Real[1]是最低频率点,也是最小频率分辨率值

说明:分辨率=采样率/采样点数N波形峰值大小=模值/(N/2) N为采样点数

很好,搞定了软硬件,再最后盘一下完整的选型+制作+测试过程吧!

相信看过后,在我公布成本价前,你心里也会有一个大概的成本价了!


制作过程

1.设计外观

外观设计参考主流品牌外壳方案。

用3D打印的方式制作外壳,尺寸小,成本也低。

2.核心物料选择

主控采用CW32L031C8T6芯片,单买11元1个。

血氧红外对管分别采用:

  • 660-905nm双波长发射管
  • PD90接收管

其中发射管正接可发射905nm红外光,反接则可发射660nm可见红光。

TFT显示屏适合人机交互,我采用0.96寸彩屏,支持横竖屏两种UI展示。

3.PCB制作与焊接

  • 本项目的PCB尺寸在嘉立创免费打样范围内
  • 本次焊接主要通过加热台,因为大量采用了0402及0603贴片封装元件。贴片焊接完成后,再通过烙铁手工焊接排针、发射管、接收管、USB接口等直插元件。
  • 对于LQFP封装的芯片,如果引脚连锡,可以在引脚部位涂一点助焊剂,然后使用小刀口的烙铁沿着引脚自内向外的方向多刮几下,即可顺利去除多余的焊锡。

4.功能测试与参数调试

利用逻辑分析仪对发射管控制信号进行了测试。从图中可以看出:

  • 当第0通道为高电平(开关开启)时,第3通道输出PWM波形,然后所有通道关闭;
  • 当第1通道为高电平(开关开启)时,第2通道输出PWM波形,然后所有通道关闭,如此持续循环。

上述信号符合设计方案要求

具体时序控制逻辑详见软件设计部分。

在弱光环境下,通过示波器测量接收管接收到的信号波形如下所示。

进一步放大后,得到如下信号波形,符合预期采样效果。

将上述接收波形,经放大、ADC采样、滤波等处理后,得到一系列采样值。

将这些采样值,通过EXCEL处理并可视化后,得到如下折线图。

在AC信号图上可以清晰看出脉冲信号的波形。

以128个采样数据为一组,经过FFT及相关公式计算,最终可以获得脉搏、PI及SPO2%等计算结果,并在显示屏上显示出来。

全部算下来,项目的总成本仅100元

怎么样,是不是感觉这3位大学生非常优秀?未来可期呢?

据说团队中的两人正在参加电赛,预祝他们此次比赛顺利!

文章的最后,小编想说,希望这样的外包项目活动能多多举办,让刚好想做项目的人,有目标,有作品,还能有奖金回血一举三得

作者本人的项目总结

参考资料:

[1]https://oshwhub.com/ilex_35/CW32L-xue-yang-yi

— 完 —

嘉立创EDA·头条号

关注我,看一手优质开源项目

页面更新:2024-03-08

标签:红光   波形   算法   显示屏   奖金   函数   电路   频率   信号   成本   工程师   地址   大学生   项目   数据

1 2 3 4 5

上滑加载更多 ↓
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top