views, comments.

有哪些有意思的,很cool的开源项目 ? - 知乎

Omnivore

Read on Omnivore

Read Original

date_saved: 2024-03-21 01:26:11


Full Content:

有哪些有意思的,很cool的开源项目 ? 请推荐你接触过的比较有意思的开源项目,或者你认为比较有意思的还没有出现,还没有开源的项目。显示全部 ​

关注者

1,140

被浏览

332,763

viggo

viggo

独立开发者 | 设计师

​ 关注

38 人赞同了该回答

本周刊记录有趣好玩的独立产品设计开发相关内容,每周发布,感兴趣的伙伴可以点击订阅我的周刊。为保证每期都能收到,建议**邮件订阅。**欢迎通过 Twitter 私信推荐或投稿。

产品推荐

1. FartHero - 这个 APP 用来评估你的屁。可以分析你放的屁多少分贝,也可以把你的屁分享给你的朋友。之前推荐过一个 Android 版本的 Fartr。太搞笑了 !

2. Tiimo - 这是一个做的很精美的日程表和待办事项工具,用来规划改善你的日常生活并减轻压力。产品主界面是一个可视化的时间线,可以直接看到当天计划的事情概览。在晚上或早上计划一天要做的事情。设定明确的目标,这样每天做可以增强对你最重要的事情的认识,这个过程也可以让自己养成很好的习惯。支持 AndroidiOS

不得不说这类工具功能很简单,但是怎么做到产品不臃肿简单易用,又能解决用户特定的痛点。懂得功能取舍,针对用户的使用场景进行设计,是一个很值得思考的问题。一味的功能堆砌在现在的市场是没有竞争力的。

3. Zones - iOS 上的时区转换工具。可以在 iPhone、iPad 和 Mac 上跟踪不同的时区。售价 $2.99 ,这类工具之前也有推荐过类似的,很简单的小工具,很适合买断制,主要是 Menu Bar App,Reminders,同步 iCloud,Widgets 这些功能。从这个产品的官网就可以看得出来非常简洁,产品做的很完整,适配了苹果全家桶设备,很舒服。

4. ==Hello== - 一个面向开发者的搜索引擎。搜索结果可以直接显示出来相关代码片段

5. Cameron’s World - 90年代的网站设计集合,汇集了成千上万个此类网站的存档资料。很有意思,可以点击这个页面上的链接,有很多古老的网站。

6. desolhar-philo - 作者建了一个网站,来整理在过去 10 年中阅读的 90 多本哲学书籍中的笔记。太强了,作者只是一个业余爱好者

7. 坐火车5小时能走多远? - 这张地图显示可以从欧洲的某个车站行驶 5 小时能到的地方。网站是 @_benjamintd 基于 Direkt Bahn Guru 这个项目来做的。

8. ==Recommend Me a Book== - 这个网站可以通过阅读小说的第一页随机找到一本新书(如果开头让你着迷,你可以点击显示标题和作者)。这是一个很不错的想法,用来找新书

9. Until - 一个倒数日的 APP,可以在 iPad 或者 iPhone 上创建桌面Widget,也可以在手表上看。

10. ==half baked ideas== - “如果有一个应用程序可以xxxx”。这个网站可以提交你自己的想法,也可以给你喜欢的想法投票。是一个寻找灵感的好地方。

11. ==reddxxx== - 这个网站跟踪了 1000 多个 Reddit 上 NSFW 子版块的内容,包含 50 多个类别。支持 Reddit 登录、自动==幻灯片放映==、自动滚动、下载等!记住别在公共场合看,这个就不截图了。

开源项目

1. ==Awesome-GitHub-Repo ==- 收集整理 GitHub 上高质量、有趣的开源项目

2. css-doodle - 一个开源的使用CSS绘制各种纹理图形的 Web 组件。

3. Gradientos - 这个网站展示了很多渐变颜色,点击网站上的一个渐变颜色就可以看到作者展示的一些常见 UI 元素应用渐变的效果。开源的

4. HYPERCOLOR - 基于 Tailwind CSS 的一些渐变颜色集合。轻松复制和粘贴类名、CSS,也可以把渐变保存为图像。开源的

5. ==Pure CSS Cartoon or Not== - 本周的 CSS 艺术作品。作者 ==@Julia Miocene ==已经卷到动画了

6. camera-webgi.vercel.app - 佳能相机的 3D 介绍网站。开源的。在这里预览效果。

7. impress.js - 这是一个 Prezi 演示文稿的前端框架,可以用 CSS 和 HTML 创建演示文稿 PPT。刚刚发布了 2.0 版。可以在这里看看效果案例。

关于 Prezi 的解释:这是一种主要通过缩放动作和快捷动作使想法更加生动有趣的演示文稿软件。它打破了传统 Powerpoint 的单线条时序,采用系统性与结构性一体化的方式来进行演示,以路线的呈现方式,从一个物件忽然拉到另一个物件,配合旋转等动作则更有视觉冲击力。

8. ==Atropos== - 是一个轻量级、免费和==开源==的 JavaScript 库,可以用来制作 3D ==视差悬停==效果。适用于JavaScript、React、Vue.js 和 Svelte。

9. Social-Media-Blocks-Extension - 屏蔽社交网站的浏览器插件,这个想法很不错。这让我有个灵感,不如做成一个家长模式,屏蔽一些成人 网站。目前这个插件是一个初步想法,只能在 chrome 开发者模式下加载。作者在这里讲述了开发过程。

随便看看

1. ==简化生活的 75 个想法== - 这是作家 ==@Barry Fralick 的一篇文章,作者是个极简主义者==,从他的官网和推特就能看出来,另外一篇文章 如何简单地简化你的生活,很短,很有想法,有兴趣可以看他的博客和他的 Newsletter

2. ==Flutter 资源大全== - 组件、导航、模板、插件、框架和引擎,应有尽有

更多内容可以订阅我的周刊: 竹白订阅官网RSS订阅Telegram频道Twitter
另外,感兴趣的伙伴可以看看本周刊的**会员计划**。

发布于 2022-08-01 03:29・IP 属地广东

​赞同 38​​1 条评论​收藏​喜欢收起​

嘉立创EDA

嘉立创EDA

高效的国产PCB设计工具,永久免费!官网:lceda.cn

​ 关注

一个外包项目,悬赏3000元

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

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

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

可别以为大学生经验少!

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

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

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

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

项目功能

  • 采用0.96inch TFT彩屏显示。
  • 锂电池供电,可充电
  • 低弱灌注性能,最低可达到0.2%。可保证在信号弱、儿童、失血多、肢体冰凉的低灌注的患者进行准确测量。
  • 光强自动调节。可根据病人的手指大小自动调节发射光强,保证信号质量更好,功耗更低,可以使用不同大小的手指、不同皮肤颜色。
  • 优秀的环境光抵消功能。可以在室内以及光线较强的临床环境使用。
  • 可测量血氧饱和度SpO2、脉率PR、灌注指数PI。
  • 可进行屏幕方向翻转。
  • 5s快速出测量结果
  • 血氧饱和度和脉率超限报警
  • 无手指自动关机。
  • 电池电量报警以及电池电量低自动关机

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

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

血氧仪工作原理

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

  • 还原血红蛋白(Hb)吸收的红光较多,红外线较少。
  • 含氧血红蛋白(HbO2)吸收的红光较少,红外线较多。

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

在设备的同一位置,设置红光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”设计方案。

  • 由RS2105电子开关芯片构成双路开关电路,用于控制发射时序;
  • 由RS622芯片所包含的两路运算放大器搭配N沟道MOS管形成恒流源电路,通过PWM信号控制电流大小,以实现控制发射信号强弱的目的。

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

原理图设计如下:

2.3 接收电路

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

  • 前级与200KΩ电阻及电容构成跨阻放大电路,采集并放大“直流+交流”混合信号;
  • 后级通过负反馈200KΩ电阻构成信号放大电路,放大交流信号;

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

原理图设计如下:

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<yend;i++)
 {
 for(j=xsta;j<xend;j++)
 {
 LCD_WR_DATA(color);
 }
 }
}
/******************************************************************************
 函数说明:在指定位置画点
 入口数据:x,y 画点坐标
 color 点的颜色
 返回值: 无
******************************************************************************/
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color)
{
 LCD_Address_Set(x,y,x,y);//设置光标位置
 LCD_WR_DATA(color);
}
/******************************************************************************
 函数说明:显示单个字符
 入口数据:x,y显示坐标
 num 要显示的字符
 fc 字的颜色
 bc 字的背景色
 sizey 字号
 mode: 0非叠加模式 1叠加模式
 返回值: 无
******************************************************************************/
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;//一个字符所占字节大小
 uint16_t x0=x;
 sizex=sizey/2;
 if(sizey==30)sizex=19;
 else num=num-' '; //得到偏移后的值
 TypefaceNum=(sizex/8+((sizex%8)?1:0))*sizey;
 LCD_Address_Set(x,y,x+sizex-1,y+sizey-1); //设置光标位置
 for(i=0;i<TypefaceNum;i++)
 {
 if(sizey==24)temp=ascii_2412[num][i]; //调用12x24字体
 else if(sizey==16)temp=ascii_1608[num][i]; //调用16x32字体
 else if(sizey==30)temp=int_1930[(num+1)*90+i]; //调用16x32字体
 else return;
 for(t=0;t<8;t++)
 {
 if(!mode)//非叠加模式
 {
 if(temp&(0x01<<t))LCD_WR_DATA(fc);
 else LCD_WR_DATA(bc);
 m++;
 if(m%sizex==0)
 {
 m=0;
 break;
 }
 }
 else//叠加模式
 {
 if(temp&(0x01<<t))LCD_DrawPoint(x,y,fc);//画一个点
 x++;
 if((x-x0)==sizex)
 {
 x=x0;
 y++;
 break;
 }
 }
 }
 }
}
/******************************************************************************
 函数说明:显示字符串
 入口数据:x,y显示坐标
 *p 要显示的字符串
 fc 字的颜色
 bc 字的背景色
 sizey 字号
 mode: 0非叠加模式 1叠加模式
 返回值: 无
******************************************************************************/
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!='\0')
 {
 LCD_ShowChar(x,y,*p,fc,bc,sizey,mode);
 x+=sizey/2;
 p++;
 }
}
/******************************************************************************
 函数说明:显示数字所用的辅助函数
 入口数据:m底数,n指数
 返回值: 无
******************************************************************************/
uint32_t mypow(uint8_t m,uint8_t n)
{
 uint32_t result=1;
 while(n--)result*=m;
 return result;
}
(因提示存在敏感词省略部分函数,详见附件代码集)
// 横屏 UI 初始化
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); //设置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<NUM_FFT; k=k+2*b) /* for (3) 基二fft */
{
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);
//移位,防止溢出。结果已经是本值的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]基于CW32L系列MCU的指夹式血氧仪 - 嘉立创EDA开源硬件平台

[2]星火计划_外包赛道_每周更新外包项目_奖金2000-8000不等

— 完 —

嘉立创EDA·知乎号

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

展开阅读全文​

​赞同 597​​51 条评论​收藏​喜欢

知乎用户

知乎用户

收集了几个:

金山卫士

链接: https://pan.baidu.com/s/1Vyxg2Yc_HN4J_XoGER5_aQ 提取码: wh56

电驴

链接: https://pan.baidu.com/s/1Oahu_uBEV2zs-1BhDKNMjw 提取码: rt14

开源 FTP 软件 —— filezilla

链接: https://pan.baidu.com/s/1znA4dEVlvc6GqMWfXWi0JA 提取码: sca3

某大型 MOBA 游戏

登录界面

进入后台配置的对战服务器:

设置自己的昵称:

支持新手教学、人机对战和联网对战:

部署在我的云主机上后,和女朋友一起对战的效果图:

ps~被我女朋友虐的体无完肤。。。。。。

服务器端有非常多的模块,代码质量非常高,这里先截一张主要模块的项目图示:

上述游戏完整的服务器和客户端完整代码及安装部署教程:

链接: https://pan.baidu.com/s/1TgZTpqTwTgf2QEnfEpoyLg 提取码: gge3

点赞数超过 100,我来继续更新。

书单推荐

最后分享下我的书单(少即是精):

原创不易,觉得有用点个赞并关注

@张小方

我呗~

展开阅读全文​

​赞同 120​​9 条评论​收藏​喜欢


Highlights

4. Hello - 一个面向开发者的搜索引擎。搜索结果可以直接显示出来相关代码片段 ⤴️

8. Recommend Me a Book - 这个网站可以通过阅读小说的第一页随机找到一本新书(如果开头让你着迷,你可以点击显示标题和作者)。这是一个很不错的想法,用来找新书 ⤴️

10. half baked ideas - “如果有一个应用程序可以xxxx”。这个网站可以提交你自己的想法,也可以给你喜欢的想法投票。是一个寻找灵感的好地方。 ⤴️

11. reddxxx - 这个网站跟踪了 1000 多个 Reddit 上 NSFW 子版块的内容,包含 50 多个类别。支持 Reddit 登录、自动幻灯片放映、自动滚动、下载等!记住别在公共场合看,这个就不截图了。 ⤴️

1. Awesome-GitHub-Repo - 收集整理 GitHub 上高质量、有趣的开源项目 ⤴️

5. Pure CSS Cartoon or Not - 本周的 CSS 艺术作品。作者 @Julia Miocene 已经卷到动画了 ⤴️

8. Atropos - 是一个轻量级、免费和开源的 JavaScript 库,可以用来制作 3D 视差悬停效果。适用于JavaScript、React、Vue.js 和 Svelte。 ⤴️

1. 简化生活的 75 个想法 - 这是作家 @Barry Fralick 的一篇文章,作者是个极简主义者 ⤴️

2. Flutter 资源大全 - 组件、导航、模板、插件、框架和引擎,应有尽有 ⤴️