数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 525|回复: 33

[工仪] DIY低成本激光+霍尔转速计

[复制链接]
发表于 昨天 15:33 | 显示全部楼层 |阅读模式
本帖最后由 飞向狙沙 于 2025-6-13 16:43 编辑

开源地址:[url]https://oshwhub.com/jusha/ji-guang-zhuan-su-ji[/url]

最近按照坛友推荐25买了一把山寨戴森吹风机,一直好奇实际转速有多少,正好前段时间看到坛友制作的激光转速计,那就复制一个,原贴:制作激光反射式转速仪与红外遥控器解码 - 创意DIY 数码之家

外壳前后盖拼版打印,所以需要分割,懒得打磨了,出现一堆邮票孔



元件:
霍尔传感器:A3144E
激光接收器:ISO0103
激光发射管:6MM 5MW 3V激光管
测量范围:
RPS:1-50000
RPM:6-999999
测量距离:
霍尔:1.5cm
激光:贴白纸10cm,铝箔纸之类的距离会增加,未测

RPS实际应该能到6w左右,通过示波器输出信号有时候会显示异常,不确定是不是示波器输出问题,反正5w以下稳定,也就是RPM上限实际为3000000,不过屏幕就放了6位数,显示不过来,所以超过999999直接显示红色999999了

支持激光+霍尔输入,转速模式RPM,计数器模式CTR,红外遥控模式IR(不支持,突然懒得写代码了)
顶部三个按键,
左边电源键加模式,长按开关机,短按切换模式
中间切换键,短按切换激光/霍尔
右边启动/停止


屏幕顶部显示模式:中间MAX停止时为最大值,启动时显示RPM,下边显示RPS,右下角显示LD激光/HL霍尔


计数器模式


实测:霍尔模式


激光模式



通过频率发生器直接给信号测试,内置RC时钟源,测试结果还算准确,偏差不大










背面,现在测试,所以没贴电池,正常使用可以贴个锂电池,厚度随意,长宽看着能塞进去就行,已经尽量把元件往边上放了。
另外需要注意的就是激光二极管焊接一定要快,磨磨唧唧的焊接很容易挂掉。



硬件没啥好说的,激光二极管发射信号,霍尔和激光接收管接受信号,其他都是屏幕充电类的辅助电路



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x

打赏

参与人数 3家元 +90 收起 理由
jf201006 + 30 優秀文章
慕名而来 + 30 楼主作品都是高大上的东西
8139 + 30 優秀文章

查看全部打赏

 楼主| 发表于 昨天 15:37 | 显示全部楼层
本来准备使用自定义字体,搞了个数码管字体,看着还行,结果发现自定义字体不支持设置背景色,只能先刷一层黑色再输出数字,导致闪屏严重,无奈使用内置字体,有点丑

  1. #include "common.h"

  2. #include "Arduino.h"
  3. #include <Adafruit_GFX.h>      
  4. #include <Adafruit_ST7735.h>
  5. #include "OneButton.h"

  6. // #include "DS_Digital12pt7b.h"
  7. // #include "DS_Digital18pt7b.h"
  8. // #include "Fonts/FreeSerifBold18pt7b.h"
  9. // #include "Fonts/FreeSerifBold12pt7b.h"
  10. // LCD
  11. SPIClass SPI_2(LCD_MOSI,LCD_MISO,LCD_SCK,-1);
  12. Adafruit_ST7735 tft = Adafruit_ST7735(&SPI_2,LCD_CS, LCD_DC, LCD_RES);

  13. //key
  14. OneButton btnPower(POWER_S, true);
  15. OneButton btn2(KEY2,true);
  16. OneButton btn3(KEY3,true);


  17. #define UPPER_LIMIT 999999

  18. // MENU
  19. //菜单选项,取值123,切换菜单是设置为对应负值
  20. signed char menu=-1;
  21. #define menuRPM 1
  22. #define menuCTR 2
  23. #define menuIR 3

  24. //是否激光模式 true激光 false霍尔
  25. bool isLD=true;
  26. //是否启动
  27. bool isStart=false;
  28. //计数器 计数统计
  29. unsigned long count = 0;
  30. //频率计 记录上次触发时间
  31. unsigned long lastTime = 0;
  32. //频率计 触发间隔,用于计算频率
  33. long delayTime = 0;
  34. float rps = 0.0;
  35. float rpsMax= 0.0;
  36. bool isBtn2Click=false;
  37. bool isBtn3Click=false;
  38. //计数器模式 启动时间
  39. int startTime = 0;
  40. long runningTime;

  41. void pulseInterrupt() {
  42.   long micro=micros();
  43.   delayTime=micro-lastTime;
  44.   lastTime=micro;
  45.   count++;
  46. }

  47. //GPIO初始化
  48. void initPin(){
  49.   pinMode(POWER_C,OUTPUT);
  50.   pinMode(POWER_S,INPUT_PULLUP);
  51.   pinMode(KEY2,INPUT_PULLUP);
  52.   pinMode(KEY3,INPUT_PULLUP);

  53.   pinMode(LD_CTRL, OUTPUT);
  54.   pinMode(SIGNAL,INPUT_PULLUP);
  55.   pinMode(LD_POWER,OUTPUT);
  56.   pinMode(HL_POWER,OUTPUT);

  57.   pinMode(LCD_BLK,OUTPUT);
  58.   pinMode(BAT_ADC,INPUT);

  59.   digitalWrite(POWER_C,LOW);
  60.   digitalWrite(LD_CTRL,LOW);
  61.   digitalWrite(LD_POWER, LOW);
  62.   digitalWrite(HL_POWER, HIGH);
  63.   digitalWrite(LCD_BLK, LOW);
  64. }
  65. //电源键单击
  66. void btnPowerClick(){
  67.   menu++;
  68.   if(menu>3)
  69.     menu=1;
  70.   menu=-menu;
  71. }
  72. //电源键长按
  73. void btnPowerLongPress(){
  74.   powerOff();
  75. }
  76. void btn2Click(){
  77.   isBtn2Click=true;
  78. }
  79. void btn3Click(){
  80.   isBtn3Click=true;
  81. }
  82. //屏幕初始化
  83. void initLCD(){
  84. // INITR_GREENTAB
  85. // INITR_144GREENTAB
  86. // INITR_HALLOWING
  87. // INITR_MINI160x80
  88. // INITR_MINI160x80_PLUGIN
  89. // INITR_BLACKTAB
  90. // INITR_HALLOWING
  91. // tft.initR(INITR_MINI160x80_PLUGIN);

  92.   tft.initR(INITR_MINI160x80_PLUGIN);
  93.   // tft.setTextWrap(true);
  94.   tft.setFont(NULL);
  95.   tft.setRotation(1);
  96.   tft.fillScreen(ST7735_BLACK);
  97.   digitalWrite(LCD_BLK,HIGH);
  98. }

  99. //开机
  100. void powerOn(){
  101.   digitalWrite(POWER_C,HIGH);
  102. }

  103. //关机
  104. void powerOff(){
  105.   digitalWrite(POWER_C,LOW);
  106. }
  107. void startLD(){
  108.   digitalWrite(LD_CTRL,HIGH);
  109.   digitalWrite(LD_POWER,LOW);
  110.   attachInterrupt(digitalPinToInterrupt(SIGNAL), pulseInterrupt, FALLING);  // 下降沿触发   
  111. }
  112. void stopLD(){
  113.   digitalWrite(LD_CTRL,LOW);
  114.   digitalWrite(LD_POWER,HIGH);
  115.   detachInterrupt(digitalPinToInterrupt(SIGNAL));
  116. }
  117. void startHALL(){
  118.   digitalWrite(HL_POWER,LOW);
  119.   attachInterrupt(digitalPinToInterrupt(SIGNAL), pulseInterrupt, FALLING);  // 下降沿触发
  120. }
  121. void stopHALL(){
  122.   digitalWrite(HL_POWER,HIGH);
  123.   detachInterrupt(digitalPinToInterrupt(SIGNAL));
  124. }

  125. void stop(int menu){
  126.   isStart=false;
  127.   stopLD();
  128.   stopHALL();
  129. }
  130. // 刷新电池图标
  131. void refreshBat(){
  132.     int analogValue = analogRead(BAT_ADC);
  133.     int endX=156;
  134.     int length=24;
  135.     // 4.15(643)满格  3.5(543)没电
  136.     int max=643;
  137.     int min=543;
  138.     float step=(max-min)/length;
  139.     step=(analogValue-min)/step;
  140.     if(step<0)
  141.       step=0;
  142.     else if(step>length)
  143.       step=length;
  144.     length=round(step);
  145.     //电池头
  146.     tft.fillRect(130,7,2,10,ST7735_WHITE);
  147.     //电池体外壳
  148.     tft.drawRect(132,4,26,16,ST7735_WHITE);
  149.     //清空
  150.     tft.fillRect(133,5,24,14,ST7735_BLACK);
  151.     //电量
  152.     tft.fillRect(endX-length+1,5,length,14,ST7735_GREEN);
  153. }
  154. //菜单切换
  155. void switchMenu (int menu){
  156.   tft.fillScreen(ST7735_BLACK);
  157.   tft.fillRect(0,0,120,24,ST7735_BLACK);
  158.   tft.drawRect(0,0,41,24,ST7735_GREEN);
  159.   tft.drawRect(40,0,41,24,ST7735_GREEN);
  160.   tft.drawRect(80,0,41,24,ST7735_GREEN);
  161.   tft.fillRect((menu-1)*40,0,41,24,ST7735_GREEN);

  162.   // tft.setFont(&FreeSerifBold12pt7b);
  163.   tft.setTextSize(2);

  164.   tft.setTextColor(menu==1?ST7735_RED:ST7735_GREEN);
  165.   tft.setCursor(3,5);
  166.   tft.print("RPM");
  167.   tft.setTextColor(menu==2?ST7735_RED:ST7735_GREEN);
  168.   tft.setCursor(43,5);
  169.   tft.print("CTR");
  170.   tft.setTextColor(menu==3?ST7735_RED:ST7735_GREEN);
  171.   tft.setCursor(88,5);
  172.   tft.print("IR");

  173.   tft.fillRect(0,25,160,55,ST7735_BLACK);
  174. }
  175. //刷新RPM界面
  176. void refreshRPM(){

  177.   float rps;
  178.   char buffer[7]; // 6位数字 + 1位字符串结束符 '\0'
  179.   long lRps; // 四舍五入取整
  180.   long ms=millis();


  181.   //micro时钟溢出或者第一次统计,不计算
  182.   if(delayTime>0){
  183.     rps=1000000.0/delayTime;
  184.     if((rps*60)<6)
  185.       rps=0;
  186.   }

  187.   //最低频率限制 6rpm,小于6rpm时为0
  188.   if((lastTime/1000+1000*10)<ms)
  189.     rps=0;

  190.   if(rpsMax<rps)
  191.     rpsMax=rps;
  192.   if(!isStart){
  193.     rps=rpsMax;
  194.   }
  195.   // tft.setFont(&FreeSerifBold12pt7b);
  196.     tft.setTextSize(2);
  197.   tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);
  198.   tft.setCursor(0,40);
  199.   if(isStart)
  200.     tft.print("RPM:");
  201.   else
  202.     tft.print("MAX:");

  203.   tft.setTextColor(ST7735_MAGENTA,ST7735_BLACK);
  204.   tft.setCursor(0,65);
  205.   tft.print("RPS:");

  206.   tft.setTextColor(ST7735_MAGENTA,ST7735_BLACK);
  207.   tft.setCursor(52,65);
  208.   lRps= (long)round(rps);
  209.   sprintf(buffer, "%05d", lRps);
  210.   //  tft.fillRect(45, 60, 115, 20, ST7735_BLACK);
  211.   tft.print(buffer);

  212.   // tft.setFont(&FreeSerifBold18pt7b);
  213.   tft.setTextSize(3);
  214.   tft.setCursor(52,35);
  215.   lRps= (long)round(rps*60);
  216.   //上限999999,超过999999显示999999红色
  217.   tft.setTextColor(lRps<UPPER_LIMIT?ST7735_YELLOW:ST7735_RED,ST7735_BLACK);
  218.    if(lRps>UPPER_LIMIT){
  219.     lRps=UPPER_LIMIT;
  220.    }
  221.   sprintf(buffer, "%06d", lRps);
  222.   //  tft.fillRect(45, 30, 115, 30, ST7735_BLACK);
  223.   tft.print(buffer);

  224.   tft.setTextSize(2);
  225.   tft.setTextColor(isStart?ST7735_GREEN:ST7735_RED,ST7735_BLACK);
  226.   tft.setCursor(135,65);
  227.   tft.print(isLD?"LD":"HL");
  228.   
  229. }

  230. void refreshCTR(){
  231.   if(isStart){
  232.     runningTime=millis()/1000-startTime/1000;
  233.   }
  234.   char buffer[9]; // 6位数字 + 1位字符串结束符 '\0'

  235.   tft.setTextSize(2);
  236.   tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);
  237.   tft.setCursor(0,40);
  238.   tft.print("CTR:");

  239.   int hours = runningTime / 3600;
  240.   int remainingSeconds = runningTime % 3600;
  241.   int minutes = remainingSeconds / 60;
  242.   int seconds = remainingSeconds % 60;
  243.   sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds);
  244.   tft.setTextColor(ST7735_MAGENTA,ST7735_BLACK);
  245.   tft.setCursor(0,65);
  246.   tft.print(buffer);

  247.   tft.setTextSize(3);
  248.    tft.setTextColor(ST7735_YELLOW,ST7735_BLACK);
  249.    tft.setTextColor(count<UPPER_LIMIT?ST7735_YELLOW:ST7735_RED,ST7735_BLACK);
  250.   tft.setCursor(52,35);
  251.   sprintf(buffer, "%06d", count<UPPER_LIMIT?count:UPPER_LIMIT);
  252.   tft.print(buffer);

  253.   tft.setTextSize(2);
  254.   tft.setTextColor(isStart?ST7735_GREEN:ST7735_RED,ST7735_BLACK);
  255.   tft.setCursor(135,65);
  256.   tft.print(isLD?"LD":"HL");
  257. }

  258. void btnHandler(){

  259.   //菜单切换
  260.   if(menu<0){
  261.     menu=-menu;
  262.     switchMenu(menu);

  263.     isStart=false;
  264.     stop(menu);

  265.     switch(menu){
  266.       case 1:
  267.         rps=0;
  268.         delayTime=100000000;
  269.         rpsMax=0;
  270.         break;
  271.       case 2:
  272.         count=0;  
  273.         startTime=millis();
  274.         runningTime=0;
  275.     }

  276.   }


  277.   //RPM页面按键处理
  278.   if(menu==1){
  279.     if(isBtn2Click){
  280.       isBtn2Click=false;
  281.       isLD=!isLD;
  282.     }
  283.     if(isBtn3Click){
  284.       isBtn3Click=false;
  285.       isStart=!isStart;
  286.       if(isStart){
  287.         rps=0;
  288.         rpsMax=0;
  289.         if(isLD)
  290.           startLD();
  291.         else
  292.           startHALL();
  293.       }else{
  294.         stop(menu);
  295.       }
  296.     }
  297.     //CTR页面按键处理
  298.   }else if(menu==2){
  299.     if(isBtn2Click){
  300.       isBtn2Click=false;
  301.       isLD=!isLD;
  302.     }
  303.     if(isBtn3Click){
  304.       isBtn3Click=false;
  305.       isStart=!isStart;
  306.       if(isStart){
  307.          count=0;  
  308.         startTime=millis();
  309.         if(isLD)
  310.           startLD();
  311.         else
  312.           startHALL();
  313.       }else{
  314.         stop(menu);
  315.       }
  316.     }
  317.   }
  318.   else if(menu==3){

  319.   }
  320. }

  321. void setup() {
  322.   Serial.setTx(SERIAL_TX);
  323.   Serial.setRx(SERIAL_RX);
  324.   Serial.begin(115200);

  325.   initPin();
  326.   delay(800);
  327.   powerOn();

  328.   initLCD();

  329.   btnPower.attachClick(btnPowerClick);
  330.   btnPower.attachLongPressStart(btnPowerLongPress);
  331.   btn2.attachClick(btn2Click);
  332.   btn3.attachClick(btn3Click);
  333. }
  334. long refreshTime;

  335. // the loop function runs over and over again forever
  336. void loop() {
  337.   long ms=millis();

  338.   //屏幕刷新
  339.   if((ms-refreshTime)>200){
  340.     refreshTime=ms;
  341.     refreshBat();
  342.     if(menu==1)
  343.       refreshRPM();
  344.     else if (menu==2){
  345.       refreshCTR();
  346.     }
  347.   }

  348.   //按键tick
  349.   btnPower.tick();
  350.   btn2.tick();
  351.   btn3.tick();

  352.   //按键处理
  353.   btnHandler();
  354. }

复制代码


回复 支持 反对

使用道具 举报

发表于 昨天 15:53 | 显示全部楼层
这个有点儿高级了,不知道打板文件能不能分享一下啊!复刻成本大概在多少啊?
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 16:00 | 显示全部楼层
confessor 发表于 2025-6-12 15:53
这个有点儿高级了,不知道打板文件能不能分享一下啊!复刻成本大概在多少啊?
...

打样文件要是有人玩的话我整理一下可以开源出来,成本为这个屏幕+激光管+激光接收+霍尔+单片机10块左右,剩下的各种容阻三极管有库存就花不了钱,没库存的话采购起来稍微麻烦点,估计也就是十块钱吧。
回复 支持 反对

使用道具 举报

发表于 昨天 16:16 | 显示全部楼层
谢谢分享~进来学习一下
回复 支持 反对

使用道具 举报

发表于 昨天 16:17 来自手机浏览器 | 显示全部楼层
看着非常不错
回复 支持 反对

使用道具 举报

发表于 昨天 16:19 | 显示全部楼层
好东西,楼主玩的都是高科技啊,有需要的坛友可以找你订做成品,估计成本不到30元吧(不含外壳,小锂电)。
回复 支持 反对

使用道具 举报

发表于 昨天 16:25 | 显示全部楼层
飞向狙沙 发表于 2025-6-12 16:00
打样文件要是有人玩的话我整理一下可以开源出来,成本这个屏幕+激光管+激光接收+霍尔+单片机10块左右,剩 ...

我手头有8h1k08a 不知道能否通用?
回复 支持 反对

使用道具 举报

发表于 昨天 16:26 | 显示全部楼层
飞向狙沙 发表于 2025-6-12 16:00
打样文件要是有人玩的话我整理一下可以开源出来,成本这个屏幕+激光管+激光接收+霍尔+单片机10块左右,剩 ...

直接立创开源吧
回复 支持 反对

使用道具 举报

发表于 昨天 16:51 | 显示全部楼层
估计众仪和菲尼瑞斯的测速仪马上就要出来了
回复 支持 反对

使用道具 举报

发表于 昨天 17:43 | 显示全部楼层
好啊,这个感觉很多人想要一个玩
回复 支持 反对

使用道具 举报

发表于 昨天 17:52 | 显示全部楼层
额,工具越来越多
回复 支持 1 反对 0

使用道具 举报

发表于 昨天 17:57 | 显示全部楼层
我的中仪VA8030转速表

量程才3万转,只够测测小风扇
测现在的高速无刷动辄超量程
大佬如果开团购的话真想买个成品了
回复 支持 反对

使用道具 举报

发表于 昨天 18:20 | 显示全部楼层
示波器不错,自己做的吗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 19:45 来自手机浏览器 | 显示全部楼层
majiwu1234 发表于 2025-6-12 16:19
好东西,楼主玩的都是高科技啊,有需要的坛友可以找你订做成品,估计成本不到30元吧(不含外壳,小锂电)。 ...

不到30,到时候整理下开源
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 19:46 来自手机浏览器 | 显示全部楼层
confessor 发表于 2025-6-12 16:25
我手头有8h1k08a 不知道能否通用?

主控stm32g030c8t6
回复 支持 反对

使用道具 举报

 楼主| 发表于 昨天 19:56 来自手机浏览器 | 显示全部楼层
just56 发表于 2025-6-12 17:57
我的中仪VA8030转速表

量程才3万转,只够测测小风扇

以前的风扇3w还行,玩高速涵道的话确实不够看,动不动10w+,这几天会开源,就不开团了,没那个心思
回复 支持 反对

使用道具 举报

发表于 昨天 22:11 来自手机浏览器 | 显示全部楼层
飞向狙沙 发表于 2025-6-12 19:56
以前的风扇3w还行,玩高速涵道的话确实不够看,动不动10w+,这几天会开源,就不开团了,没那个心思 ...

很多开源项目,最后反而让中间商赚差价了,绝大多数普通用户不具备焊接贴片的能力,不如动员斑竹把优秀作品加入魅色系列全家桶,既摊薄成本节约时间,也能让玩家花钱支持论坛
回复 支持 3 反对 0

使用道具 举报

发表于 昨天 23:14 | 显示全部楼层
这个有点牛啊
回复 支持 反对

使用道具 举报

发表于 昨天 23:50 | 显示全部楼层
做得挺好的,迟下抄一个
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-6-13 17:31 , Processed in 0.140401 second(s), 10 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表