Project Description
This project is mainly based on the CW32L series low-power MCU produced by Wuhan Xinyuan Semiconductor to realize the design of finger-clip oximeter products.
Open Source Agreement
This project complies with the GPL 3.0 open source agreement and can be copied, modified and disseminated.
Project related functions
(1) The main control uses the CW32 low-power series MCU launched by Wuhan Xinyuan Semiconductor Co., Ltd. to design a high-precision hand-clip oximeter;
(2) Use screen display, it is recommended to use a 0.96-inch TFT color screen display;
(3) Lithium battery power supply, rechargeable;
(4) Low perfusion performance, the minimum can reach 0.2%. (It can ensure accurate measurement of low perfusion patients with weak signals, children, heavy blood loss, cold limbs);
(5) Automatic light intensity adjustment. (The emission light intensity can be automatically adjusted according to the size of the patient's fingers to ensure better signal quality and lower power consumption. Different sizes of fingers and different skin colors can be used);
(6) Excellent ambient light cancellation function. (Can be used indoors and in a clinical environment with strong light);
(7) Can measure blood oxygen saturation SpO2, pulse rate PR and perfusion index PI;
(8) Can flip the screen direction;
(9) Quickly output measurement results in 5 seconds;
(10) Blood oxygen saturation and pulse rate over-limit alarm;
(11) Automatic shutdown when no finger is touched;
(12) Battery power alarm and automatic shutdown when the battery power is low.
Project attributes
This project is the first time it has been made public and is my original project. The project has not won any awards in other competitions.
Project schedule
(1) 2023.3.24 Project launch
(2) 2023.4.30 Appearance design, schematic diagram and PCB design
(3) 2023.5.30 Software design
(4) 2023.6.30 Joint debugging and problem rectification
Design principle
(I) Working principle analysis
Since the red blood cells in the blood, oxygenated hemoglobin (HbO2) and reduced hemoglobin (Hb) have different absorption capabilities for red light (660nm) and infrared light (900nm). Reduced hemoglobin (Hb) absorbs more red light and less infrared light. On the contrary, oxygenated hemoglobin (HbO2) absorbs less red light and more infrared light. By setting the red light LED and infrared light LED at the same position of the finger clip oximeter, when the light penetrates from one side of the finger to the other side and is received by the photodiode, a corresponding proportional voltage can be generated. After algorithm conversion, the output result is displayed on the LCD screen, which is intuitively displayed as an instrument to measure the human health index.
(II) Design Requirements Analysis
Overall, the transmission scheme is adopted, which improves the sampling accuracy several times compared with the traditional reflection scheme (commonly used in smart watches).
[Design Requirement 1] According to the design requirements and actual performance requirements, we choose to use the CW32L031C8T6 chip launched by Wuhan Xinyuan Semiconductor Co., Ltd. as the main control of the project. In addition to supporting low power consumption characteristics, the chip integrates an ARM® Cortex®-M0+ core with a main frequency of up to 48MHz and high-speed embedded memory (up to 64K bytes of FLASH and up to 8K bytes of SRAM), provides three sets of UART, one set of SPI and one set of I2C communication interfaces, provides a 12-bit high-speed ADC and five sets of general-purpose, basic timers and a set of advanced control PWM timers, and the peripheral resources fully meet the project requirements.
[Design Requirement 2] The screen is selected to use a 0.96-inch TFT color screen, and the interface method needs to meet the hardware and structural design requirements.
[Design Requirement 3] In terms of power supply, it is necessary to design two power supply methods: USB TYPE-C external connection and lithium battery, and support dynamic path management function; the size and power of the lithium battery need to meet the performance and structural design requirements.
[Design Requirement 4] In order to meet the low and weak perfusion performance requirements, it is necessary to design a signal multi-stage filtering and amplification circuit, and the amplification factor is reasonably selected based on the test data. The perfusion performance is reflected by the perfusion index PI indicator, that is, it is required to be able to measure the AC signal with a DC signal ratio of 2/1000.
[Design Requirement 5] The emission light intensity is automatically adjusted according to the size of the patient's finger. It is necessary to design an optical signal emission circuit that can control the voltage through the PWM signal and then control the constant current.
[Design Requirement 6] Design a special shading mechanism to meet the signal collection needs of strong ambient light application scenarios.
【Design Requirement 7】Design a calculation model for blood oxygen saturation SpO2%: first calculate the R value: R= (RED: AC/DC)/ (IR: AC/DC), then calculate the blood oxygen saturation through the R value: SpO2%=110-R*25; the pulse rate PR can be obtained by filtering the AC part of the PPG signal; design a calculation model for the perfusion index PI%: PI%=AC/DC *100%, with a range of 0.2%-20%.
【Design Requirement 8】Design a function to switch between horizontal and vertical screens by pressing a button.
【Design Requirement 9】Thanks to the efficient data processing capability of CW32L031, it meets the requirements of tasks such as signal sampling and FFT calculation, and can quickly obtain measurement results within 5s.
【Design Requirement 10】Design an over-limit alarm mechanism, using a buzzer and a screen to display red eye-catching values, to instantly remind users of data abnormalities in both sound and light.
【Design Requirement 11】Design a code to monitor the sampling signal and automatically shut down when the threshold is reached to save battery power.
[Design Requirement 12] By designing the battery voltage monitoring function, a low battery alarm and automatic shutdown function when the voltage reaches below the threshold can be realized.
(III) Design plan
Since this plan involves many functional modules and components, in order to ensure the accuracy of the design of each part, a two-stage design method is adopted. In the first stage, a core board and an external circuit expansion board based on CW32L031C8T6 were designed, and each part of the circuit and software code were independently tested and jointly debugged; in the second stage, based on the results of the previous debugging, a power board and a main control board suitable for use in a formal environment were designed. Due to space limitations, only the second stage design plan is shown below.
1. Power board
1.1 Design idea
The power supply supports USB external power supply, battery power supply and battery charging functions. The overall architecture includes three parts: power path management and battery charging circuit, 5V power supply circuit and 3.3V power supply circuit, as shown in the figure below.

1.2 Power path management and battery charging circuit
The power path management circuit uses P-MOS as a switch, and realizes the dynamic switching function between USB power supply and battery power supply through the relationship between the G terminal voltage and the S terminal voltage. The battery charging circuit uses the TC4056A chip as the main control, relying on its programmable charging current control, charging status indication and other functions to realize the single-cell lithium battery charging function. The USB interface adds overvoltage and overcurrent protection circuit design to prevent the impact of the instantaneous spike voltage on the subsequent circuit. The purpose of adding the D3 diode is to accelerate the conduction of the P-MOS to prevent problems such as power-off reset of the main control due to power supply mode switching.
The schematic design is as follows.

1.3 DC 5V power supply circuit
The DC 5V power supply circuit uses the MT3608 chip to build the Sepic circuit to ensure that the 5V voltage can be stably provided when the battery voltage drops.
The schematic design is as follows.

1.4 DC 3.3V power supply circuit
The DC 3.3V power supply circuit uses the AMS1117-3.3 chip to build the LDO step-down circuit to stably provide 3.3V voltage.
The schematic design is as follows.

1.5 PCB design

2. Main control board
2.1 Design ideas
The main control board includes MCU circuit, transmitting circuit, receiving circuit, key circuit, buzzer circuit and TFT display circuit, which are used to realize the main functions of the oximeter.
2.2 MCU circuit
The MCU circuit uses CW32L031C8T6 as the main control chip, designs the BOOT circuit, SWD burning interface and reset button (not soldered), and cancels the external crystal oscillator circuit due to space limitations.
The schematic design is as follows:

2.3 Transmitter circuit
The transmitter circuit adopts the "RS2105+RS622" design scheme. The RS2105 electronic switch chip forms a dual-way switch circuit to control the transmission timing; the two-way operational amplifier contained in the RS622 chip is matched with the N-channel MOS tube to form a constant current source circuit, and the current size is controlled by the PWM signal to achieve the purpose of controlling the strength of the transmission signal. The dual-wavelength transmitting tube of "660nm red light + 900nm infrared light" is used, which is connected in reverse parallel internally, and the transmission timing and transmission power are controlled by the above-mentioned H-bridge circuit.
The schematic diagram is as follows:

2.4 Receiving circuit
The receiving circuit uses the RS622 dual-channel op amp chip as the core. The front stage, 200KΩ resistor and capacitor form a transimpedance amplifier circuit to collect and amplify the "DC + AC" mixed signal; the rear stage forms a signal amplifier circuit through a negative feedback 200KΩ resistor to amplify the AC signal; the front and rear stages are coupled by capacitors, and form a high-pass filter with resistors to effectively filter out DC signals.
The schematic diagram is as follows:

2.5 Key circuit
Independent key design, using 1mm ultra-thin keys, a hardware debouncing circuit through parallel capacitors, connected to the PB03 pin of the MCU through resistors, and the key is pressed to a low level (low level is valid).
The schematic diagram is as follows:

2.6 Buzzer circuit (the current version of PCB has been cancelled due to space limitations)
The buzzer circuit uses a 2KHz passive buzzer as the core component, an N-channel MOS tube as a switch, and drives the buzzer to sound by outputting a PWM signal of a certain frequency.
The schematic design is as follows:

2.7 TFT display circuit
The TFT display circuit is used to drive a 0.96-inch full-color LCD display. An 8P drawer-type FPC interface is designed to connect a display with a flexible cable interface. At the same time, a PNP transistor is used as a switch, and a PWM signal with a certain duty cycle is output by the MCU to achieve screen backlight control.
The schematic design is as follows:

2.8 PCB design
software description
The main control uses the CW32L031C8T6 chip produced by Wuhan Xinyuan Semiconductor. The chip has a main frequency of 48MHz, can provide rich on-chip resources, and its low power consumption is very suitable for battery-powered application scenarios.
1. TFT display
(1) LCD initialization
#include "LCD_INIT.h"
/**********************************************************************************
Function Description: LCD reset function
Entry Data: None
Return Value: None
******************************************************************************/
void Lcd_Reset(void)
{
LCD_RES_Clr();
FirmwareDelay(100);
LCD_RES_Set();
FirmwareDelay(100);
}
/**********************************************************************************
Function Description: LCD_GPIO initialization function
Entry Data: dat serial data to be written
Return Value: None
*********************************************************************************/
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);
}
/******************************************************************************
Function Description: LCD serial data write function
entry data: dat serial data to be written
return value: None
******************************************************************************/
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();
}
/******************************************************************************
Function Description: LCD writes 8-bit data
entry data: dat The written data
return value: None
****************************************************************************/
void Lcd_WriteData(uint8_t dat)
{
LCD_Writ_Bus(dat);
}
/**************************************************************************************
Function Description: LCD writes 16-bit data
entry data: dat The written data
return value: None
******************************************************************************/
void LCD_WR_DATA(uint16_t dat)
{
LCD_Writ_Bus(dat>>8);
LCD_Writ_Bus(dat);
}
/******************************************************************************
Function Description: LCD write command
entry data: dat Write command
return value: None
************************************************************************/
void Lcd_WriteIndex(uint8_t dat)
{
LCD_DC_Clr();//write command
LCD_Writ_Bus(dat);
LCD_DC_Set();//write data
}
/**************************************************************************************
Function Description: Set the start and end address
entry data: x1, x2 Set the start and end address of the column
y1, y2 Set the start and end address of the row
Return value: None
******************************************************************************/
void LCD_Address_Set(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2)
{
if(USE_HORIZONTAL==0)
{
Lcd_WriteIndex(0x2a);//column address setting
LCD_WR_DATA(x1+26);
LCD_WR_DATA(x2+26);
Lcd_WriteIndex(0x2b);//row address setting
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
Lcd_WriteIndex(0x2c);//Storage write
}
else if(USE_HORIZONTAL==1)
{
Lcd_WriteIndex(0x2a);//Column address setting
LCD_WR_DATA(x1+26);
LCD_WR_DATA(x2+26);
Lcd_WriteIndex(0x2b);//Row address setting
LCD_WR_DATA(y1+1);
LCD_WR_DATA(y2+1);
Lcd_WriteIndex(0x2c);//Storage write
}
else if(USE_HORIZONTAL==2)
{
Lcd_WriteIndex(0x2a);//Column address setting
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
Lcd_WriteIndex(0x2b);//Row address setting
LCD_WR_DATA(y1+26);
LCD_WR_DATA(y2+26);
Lcd_WriteIndex(0x2c);//Storage write
}
else
{
Lcd_WriteIndex(0x2a);//Column address setting
LCD_WR_DATA(x1+1);
LCD_WR_DATA(x2+1);
Lcd_WriteIndex(0x2b);//Row address setting
LCD_WR_DATA(y1+26);
LCD_WR_DATA(y2+26);
Lcd_WriteIndex(0x2c);//Storage write
}
}
/**************************************************************************************
Function Description: LCD initialization code
Entry Data: None
Return Value: None
******************************************************************************/
void LCD_Init(void)
{
LCD_GPIO_Init();//Initialize GPIO
LCD_RES_Clr();//Reset
FirmwareDelay(1);
LCD_RES_Set();
//FirmwareDelay(1);
//LCD_BLK_Set();//Turn on backlight
// FirmwareDelay(1);
Lcd_WriteIndex(0x11); //Sleep out
//FirmwareDelay(1); //Delay 120ms
Lcd_WriteIndex(0xB1); //Normal mode
Lcd_WriteData(0x05 ); Data(0x3C)
; Lcd_WriteData (0x3C )
; Lcd_WriteIndex(
0xB2) ; //Idle mode Lcd_WriteData(0x05); Lcd_WriteData (0x3C); Lcd_WriteData(0x3C); 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);
}
(2)LCD main function function
#include "LCD.h" #include "LCD_INIT.h" #include "LCD_FONT.h" /****************************************************************************************** Function Description: Fill the specified area with color Input data: xsta, ysta starting coordinates xend, yend ending coordinates color Color to be filled Return value: None ********************************************************************************/void LCD_Fill(uint16_t void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color)
{ LCD_Address_Set(x,y,x,y);//Set cursor position LCD_WR_DATA(color);}
/********************************************************************************** Function description: Display a single character Entry data: x, y display coordinates num character to be displayed fc character color bc character background color sizey font size mode: 0 non-overlay mode 1 overlay mode Return value: None **********************************************************************************/void LCD_ShowChar(uint16_t x,uint16_t y,uint8_t num,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode){ uint8_t temp,sizex,t,m=0; uint16_t i,TypefaceNum;//The size of a character in bytes uint16_t x0=x; sizex=sizey/2; if(sizey==30)sizex=19; else num=num-' '; //Get the offset value TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey; LCD_Address_Set(x,y,x+sizex-1,y+sizey-1); //Set the cursor position for(i=0;i
/************************************************************************************** Function Description: Display string Entry data: x, y display coordinates *p string to be displayed fc character color bc character background color sizey font size mode: 0 non-overlay mode 1 overlay mode Return value: None ********************************************************************************/void LCD_ShowString(uint16_t x,uint16_t y,const uint8_t *p,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode){ while(*p!='') { LCD_ShowChar(x,y,*p,fc,bc,sizey,mode); x+=sizey/2; p++; }}
/****************************************************************************** Function Description: Auxiliary function used to display numbers Entry data: m base, n exponent Return value: None ************************************************************************************/uint32_t mypow(uint8_t m,uint8_t n){ uint32_t result=1; while(n--)result*=m; return result;}
(Some functions are omitted due to the presence of sensitive words, see the attached code set for details)
// Horizontal screen UI initialization
void transverse_UI_init()
{
LCD_Init();
LCD_Fill(0,0,160,80,BLACK);
LCD_ShowString(0,0,(const unsigned char*)"%Sp0",YELLOW,BLACK,24,1);
LCD_ShowString(48,8,(const unsigned char*)"2",YELLOW,BLACK,16,1);
LCD_ShowString(112,0,(const unsigned char*)"PR",YELLOW,BLACK,24,1);
LCD_ShowString(136,8,(const unsigned char*)"bpm",YELLOW,BLACK,16,1);
LCD_ShowBattey(56,0,1);
LCD_ShowString(63,24,(const unsigned char*)"PI %:",WHITE,BLACK,16,1);
LCD_Fill(12,40,24,46,GREEN);LCD_Fill(30,40,42,46,GREEN);
LCD_Fill(119,40,132,46,GREEN);LCD_Fill(138,40,151,46,GREEN);
}
// 竖屏 UI 初始化
void Vertical_UI_init()
{
LCD_Init();
LCD_Fill(0,0,80,160,BLACK);
LCD_ShowString(0,0,(const unsigned char*)"Sp0",YELLOW,BLACK,24,1);
LCD_ShowString(36,8,(const unsigned char*)"2",YELLOW,BLACK,16,1);
LCD_ShowString(40,0,(const unsigned char*)" %",YELLOW,BLACK,24,1);
LCD_ShowString(0,54,(const unsigned char*)"PR ",YELLOW,BLACK,24,1);
LCD_ShowString(24,62,(const unsigned char*)"bpm",YELLOW,BLACK,16,1);
LCD_ShowBattey(113,58,2);
LCD_ShowString(0,114,(const unsigned char*)"PI %:",WHITE,BLACK,16,1);
LCD_Fill(12,36,26,42,GREEN);LCD_Fill(30,36,44,42,GREEN);
LCD_Fill(12,90,26,96,GREEN);LCD_Fill(30,90,44,96,GREEN);
}
2.时序控制
控制时序说明:每次发射(采样)包括四个阶段(IR发射、停止发射、RED发射、停止发射),每阶段3ms,共计12ms;之后为27ms的延迟(停止发射);上述为一个完整发射循环,每个循环为39ms;完整发射(采样)周期包括128次发射循环,共计4.992秒。

代码如下:(在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); //Set DAC1 duty cycle to 0
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //Set DAC2 duty cycle to 300+adjustment value
GTIM_Cmd(CW_GTIM2, ENABLE);
IRorRED = 0; //Set infrared or red light flag: infrared
ADC_SoftwareStartConvCmd(ENABLE); //Start ADC conversion
break;
}
case 1: //Turn off signal transmission (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); //Set the duty cycle to 0
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //Set the duty cycle to 0
GTIM_Cmd(CW_GTIM2, DISABLE);
//ADC_SoftwareStartConvCmd(DISABLE);
break;
}
case 2: //Emit red light signal (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); //Set DAC1 duty cycle to 300 + adjustment value
GTIM_SetCompare2(CW_GTIM2, DAC2_PWM); //Set DAC2 duty cycle to 0
GTIM_Cmd(CW_GTIM2, ENABLE);
IRorRED = 1; //Set infrared or red light flag: red
lightADC_SoftwareStartConvCmd(ENABLE); //Start ADC conversionbreak
;
}
case 3: //Turn off signal transmission (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); //Set the duty cycle to 0 GTIM_SetCompare2(
CW_GTIM2, DAC2_PWM); //Set the duty cycle to 0
GTIM_Cmd(CW_GTIM2, DISABLE);
//ADC_SoftwareStartConvCmd(DISABLE);
break;
}
}
}
else
{
BTIM1_counter3++;
}
}
}
}
3. Algorithm Design
3.1 FFT Algorithm Principle
FFT is an efficient algorithm of DFT, called fast Fourier transform. Fourier transform is one of the most basic methods in time-frequency domain transform analysis. Discrete Fourier transform (DFT: Discrete Fourier Transform) used in the field of digital processing is the basis of many digital signal processing methods.
The operation of DFT is as follows:

Among them,

FFT is an efficient algorithm of DFT, called fast Fourier transform.
FFT algorithm can be divided into time extraction algorithm and frequency extraction algorithm. For each K value of X (K) calculated by this method, 4N real number multiplications and (4N-2) additions are required. For N k values, a total of N*N multiplications and N (4N-2) real number additions are required. Improve the DFT algorithm, reduce its computational complexity, and utilize the periodicity and symmetry in DFT to turn the entire DFT calculation into a series of iterative operations, which can greatly improve the calculation process and computational complexity. This is the basic idea of FFT.
3.2 FFT algorithm implementation
(1) Calculate the trigonometric function table
// Save the SIN value
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, 0x 46, 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};
//The following is magnified 128 times The cosine function array table after the cosine function is the same as above, except that cosine is used to generate
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, -0x 2a,
- 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, 0x 77,
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 function
void Fft_Imagclear(void) //fft imaginary part clearing function, you need to run this before running the FFT function
{
unsigned char a; //Note here If the image size is more than 256 points, it should be changed to u16. The following a<128 condition should also be modified accordingly:
for(a=0;a<128;a++)
{
Fft_Image[a]=0;
}
}
signed short Fft_Real[128]; //fft real part, 128 array
signed short Fft_Image[128]; //fft imaginary part, 128 array
void FFT(void)
{
unsigned char i,j,k,b,p;
signed short Temp_Real,Temp_Imag,temp; //intermediate temporary variables, the names are also defined by yourself, but they must correspond to those in the fft function
//unsigned short TEMP1; //used to calculate power, not needed
unsigned char N=7; //here because 128 is 2 to the 7th power, if it is calculating 256 points, it is 2 to the 8th power, N is 8, if it is 512 points, then N=9, and so on
unsigned short NUM_FFT=128; //here you need to calculate as many fft points as you want, the value can only be 2 to the Nth power
for( i=1; i<=N; i++) /* for(1) */
{
b=1;
b <<=(i-1); //Butterfly operation, used to calculate how many rows to calculate. For example, the first level 1 and 2 are calculated,,, the second level
for( j=0; j<=b-1; j++) /* for (2) */
{
p=1;
p <<= (Ni);
p = p*j;
for( k=j; k
{
Temp_Real = Fft_Real[k]; Temp_Imag = Fft_Image[k]; temp = Fft_Real[k+b];
Fft_Real[k] = Fft_Real[k] + ((Fft_Real[k+b]*COS_TAB[p])>>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);
//Shift to prevent overflow. The result is already 1/64 of the original value
Fft_Real[k] >>= 1;
Fft_Image[k] >>= 1;
Fft_Real[k+b] >>= 1;
Fft_Image[k+b] >>= 1;
}
}
}
}
///Note: The above has already calculated the real and imaginary parts of 128 points. Before the next calculation, all imaginary parts need to be reset to zero
signed short Get_fft_value(int n,int m) //Get the real or imaginary part of the FFT result
{
if(n==0) return Fft_Real[m];
else return Fft_Image[m];
}
3.3 Application of FFT results
(1): Directly use the value of a certain frequency point to display the audio spectrum intensity.
The value of the nth frequency point is Fft_Real[n] and Fft_Image[n] in the array.
(2): Find the modulus
value of a certain frequency point = square root (real part square + imaginary part square), that is, sqrt((Fft_Real[n]*Fft_Real[n])+(Fft_Image[n]*Fft_Image[n]))
(3): Clear the component of a specific frequency, generally used in digital filtering algorithms
Fft_Real[0]=Fft_Image[0]=0; // Remove the DC component, that is, clear the value of the 0th item
Fft_Real[63]=Fft_Image[63]=0; // To remove the component of a certain frequency, clear the value of the array item corresponding to the frequency
Fft_Real[0] is the DC component. Fft_Real[1] is the lowest frequency point and the minimum frequency resolution value.
Explanation: Resolution = sampling rate/number of sampling points N, waveform peak size = modulus value/(N/2), N is the number of sampling points.
Appearance Design
This design fully refers to the shell scheme of the mainstream brand finger clip oximeter. Through 3D modeling of the existing public mold shell, the appearance design scheme of this work is completed.
Production process
(I) Core material selection
1. Main control The main
control adopts the CW32L031C8T6 chip produced by Wuhan Xinyuan Semiconductor. The chip has a main frequency of 48MHz, can provide rich on-chip resources, and its low power consumption characteristics are very suitable for battery-powered products.
2. The transmitting tube and receiving
tube blood oxygen infrared pair tube use 660-905nm dual-wavelength transmitting tube and PD90 receiving tube respectively. Among them, the transmitting tube can emit 905nm infrared light when connected positively, and 660nm visible red light when connected reversely.

3. TFT display screen
In terms of human-computer interaction, considering the actual size of the finger clip oximeter and the need to display red eye-catching fonts when the blood oxygen saturation and pulse rate exceed the limit to better remind the user of abnormal values, a 0.96-inch color screen is used, and it supports both horizontal and vertical screen UI displays.

(ii) PCB production
1. Production method
All PCB sizes for this project are within the range of 10cm*10cm, so they can be produced through the free proofing machine provided by Jiali Chuang twice a month. Simply export the PCB file drawn using LiChuang EDA software as a Gerber board file, and then send it to Jiali Chuang through the order assistant to complete it.
2. Physical display
(1) Core board

(2) Expansion board

(3) Power board

(4) Main control board

(iii) Surface mount welding
1. Welding method
Due to the limitations of equipment and PCB space, a large number of 0402 and 0603 surface mount packaging components are used, so welding is mainly carried out on a heating table. For some direct plug-in components such as pin headers, transmitting tubes, receiving tubes, USB interfaces, etc., after the surface mount components are welded, they are manually welded with a soldering iron.
2. Welding difficulties and precautions
The welding difficulties are mainly concentrated in the 0.5mm pin spacing.