亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

仅使用 Arduino 的精确时钟

组件和用品

Arduino Nano R3
我使用的是 Nano,但应该适用于任何 Arduino
× 1
字母数字 LCD,16 x 2
任何显示器都可以工作,我用过这个 https://www.amazon.co.uk/ gp/product/B00N8K2BYM/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1
× 1
触觉开关,顶部驱动
× 3
微调电位器,10 kohm
任何 10k 修剪器都可以
× 1
跳线
× 1

关于这个项目

我开始这是一个学术练习,但最终得到了一个非常准确的时钟。运行了5天,没有失去也没有得到任何时间。

仅使用 Arduino 的主要问题是其内部时钟速度不是 100% 准确。因此,如果您仅依赖于此,那么经过的毫秒数将减少一小部分,并且您创建的时钟将丢失或获得时间。我的方法是测试我使用的 Arduino 的准确性,并确定它每小时损失或增加多少毫秒。然后只需要对速度调整进行编程,以从每小时内部跟踪的毫秒数中增加或减少此差异。

我的另一个担忧是 Arduino 时钟是否一直不准确,但正如所指出的,我编程的时钟在 5 天内保持了非常准确的时间,所以看起来不准确是一致的。

第二个问题是内部的 millis() 函数每 50 天左右重置一次,您无法操纵毫秒计数。因此,答案是使用一个我可以操纵的计数器来替换 millis() 中断,该计数器将从午夜开始计算毫秒数,每天重置以消除任何运行时间限制。

评估不准确性

为了评估不准确性,我假设我的计算机时钟以及 Processing 中的 millis() 是准确的。因此,我为 Arduino 创建了一个程序,将自握手以来经过的毫秒数每 2 秒发送一次到 Processing过去。这给出了在一小时内丢失或增加的毫秒数,因此给出了用于时钟程序中速度调整的值。

下面提供了Arduino程序和处理脚本的代码。

如果您没有安装 Processing,请访问 https://processing.org,您可以在那里下载并了解它。

时钟代码

对时钟代码感兴趣的主要领域是中断的设置、中断的使用方式以及保存和操作日期的方式。

中断

下面的代码将设置一个每毫秒触发一次的中断。这转移了用于维护millis()的中断,因此millis()和delay()将不再起作用。

 // 设置时间中断 - millis() 在 50 天后翻转,所以 // 我们使用我们自己的毫秒计数器,我们可以在每天结束时重置 // 设置 CTC 模式比较时间和触发中断 TCCR0A =(1 < 

这是每秒都会调用的代码:

// 当达到比较时间时调用此中断// 因此将根据// OCR0A 寄存器设置每毫秒调用一次。ISR(TIMER0_COMPA_vect) { if (currentMode !=SET_TIME)当前时间++; elapsed++;}  

currentTime 和 elapsed 是无符号长变量。请注意,这些在定义时被限定为 volatile,因为我们也在主代码中操作变量。这会强制系统在每次使用变量时读取该变量,而不使用缓存值。

currentTime 存储自午夜以来的毫秒数,并且有一些例程可以将其转换为 HH:MM:SS 并在您设置时间时将其重置。

当 24 小时过去后,系统从时间中减去一天中的毫秒数,并将日期增加 1 天。因此时钟不受变量可以存储的最大值的影响,与millis()不同。

 // 如果在一天结束时重置时间并增加日期 if ((currentMode ==SHOW_TIME) &&(currentTime> millisecondsInADay)) { //第二天 // 在重置时间时停止中断 noInterrupts(); currentTime -=毫秒InADay;中断();当前日期++; } 

请注意,我们在操作 currentTime 变量时禁用中断,否则可能会在计算中间触发中断调用以扣除破坏计算的毫秒数。

每小时过去后,系统会根据我们之前计算的速度调整调整经过的毫秒数,调整当前时间以补偿内部时钟的快或慢。

 // 在每个小时结束时调整 // Arduino 时钟不准确的经过时间 if (elapsed>=millisecondsInHour) { noInterrupts(); // 调整慢/快运行 Arduino 时钟的时间 currentTime +=speedCorrection; // 重置以计算下一小时的时间 =0;中断(); } 

日期存储和计算

该日期为儒略日期,即自公元前 4713 年 1 月 1 日星期一以来经过的天数。包括用于计算儒略日期并将其转换回公历的例程。

float JulianDate(int iday, int imonth, int iyear) { // 计算儒略日期(测试到 20,000 年) unsigned long d =iday;无符号长 m =imonth;无符号长 y =iyear;如果 (m <3) { m =m + 12; y =y - 1; } unsigned long t1 =(153 * m - 457) / 5;无符号长t2 =365 * y + (y / 4) - (y / 100) + (y / 400); return 1721118.5 + d + t1 + t2;}void GregorianDate(float jd, int &iday, int &imonth, int &iyear) { // 注意 2100 是下一个跳过的闰年 - 补偿跳过的闰年 unsigned long f =jd + 68569.5;无符号长 e =(4.0 * f) / 146097;无符号长 g =f - (146097 * e + 3) / 4;无符号长 h =4000ul * (g + 1) / 1461001;无符号长 t =g - (1461 * h / 4) + 31;无符号长 u =(80ul * t) / 2447;无符号长 v =u / 11; iyear =100 * (e - 49) + h + v; imonth =u + 2 - 12 * v; iday =t - 2447 * u / 80;} 

调整按钮

模式按钮将当前模式从放映时间推进到设置时间、设置年份、设置日期、设置速度调整,然后返回到放映时间。这些都是不言自明的,可以使用其他 2 个按钮来调整当前设置。

一旦时钟开始运行,如果它正在增加或减少时间,您可以更改速度调整,方法是进入设置速度调整模式并使用向上和向下按钮每次增加或减少 5 秒。

代码

  • 时钟程序
  • Arduino 定时器程序
  • 处理定时器测试脚本
时钟程序Arduino
带有日期的精确时钟仅使用和 Arduino
// Paul Brace - 2021 年 2 月// 带有日期的简单时钟仅使用 Arduino 创建 - 无 RTC 模块// 程序包含时间校正调整以补偿内部// 时钟速度不是 100% 准确。// 一旦正确的速度调整设置,时钟就非常准确。// 在我的测试中,它在 5 天内没有丢失或增加任何时间。// 在 16x2 LCD 显示屏上显示时间// 按钮设置时间//模式按钮(引脚2)切换设置时间、设置日期和运行//按钮1(引脚3)增加分钟和月份并减少年/速度调整//按钮2(引脚4)增加小时和日和增加年。/速度调整// 24 小时显示// 包括用于显示的库驱动程序:#include // LiquidCrystal lcd( RS, EN, D4,D5, D6, D7)LiquidCrystal lcd(12, 13 , 6, 7, 8, 9); // 创建一个 lcd 对象并分配引脚 // 定义按钮和蜂鸣器连接#define MODE_BUTTON 2#define HOUR_BUTTON 3 // 相同的按钮不同定义到#define UP_BUTTON 3 // 使代码更容易理解 #define DAY_BUTTON 3#define MINUTE_BUTTON 4 // 相同按钮不同定义#define DOWN_BUTTON 4 // 使代码更易于理解#define MONTH_BUTTON 4// 当前模式设置#define SHOW_TIME 1 // 1 =运行-显示时间#define SET_TIME 2 // 2 =时间设置#define SET_YEAR 3 // 3 =年设置#define SET_DATE 4 // 4 =日/月设置 #define SET_SPEED_ADJ 5 // 5 =修改 speedCorrection 变量int speedCorrection =3545; // 我的 Nano 时钟每小时运行缓慢的毫秒数 // 此处为负数,如果它运行得很快 // 更改以匹配您的 Arduino // 在中断中更改的易失性变量,我们 // 需要强制系统读取实际变量//当在中断之外使用并且不使用缓存版本时,volatile unsigned long currentTime; // 从午夜开始的持续时间(以毫秒为单位)unsigned long lastTime =-1000; // 调用 ShowTime 的 lastTime 初始化为 -1000,因此立即显示 volatile unsigned long elapsed; // 用于延迟和小时计数的定时器unsigned long millisecondsInADay; // 24 小时内的毫秒数 unsigned long millisecondsInHour; // 1 小时内的毫秒数 int currentMode; // 1 =运行 - 显示时间// 2 =时间设置// 3 =年份设置// 4 =日/月 setfloat currentDate; // Julian datefloat lastDate =0.0; // 调用 ShowDate 的最后日期int currentDay;int currentMonth;int currentYear;char *dayArray[] ={ "Tue. ", // 将显示编译器警告但工作正常 "Wed. ", "Thur. ", "Fri . ", "Sat. ", "Sun. ", "Mon. " };void setup() { // 设置时间中断 - 毫秒()在 50 天后滚动,所以 // 我们使用我们自己的毫秒计数器我们可以在 // 每天结束时重置 TCCR0A =(1 < millisecondsInADay)) { //Next day // 在重置时间时停止中断 noInterrupts(); currentTime -=毫秒InADay;中断();当前日期++; } // 在每小时结束时调整 // Arduino 时钟不准确的经过时间 if (elapsed>=millisecondsInHour) { noInterrupts(); // 调整慢/快运行 Arduino 时钟的时间 currentTime +=speedCorrection; // 重置以计算下一小时的时间 =0;中断(); } // 检查是否有任何按钮被按下 CheckButtons(); // 根据当前模式切换显示显示 (currentMode) { case SHOW_TIME:// 显示当前时间和日期 ShowTime(currentTime);显示日期(当前日期);休息; case SET_TIME:// 显示屏幕设置时间 ShowTimeSet(currentTime);休息; case SET_YEAR:// 设置年份的显示画面 ShowYearSet(currentDate);休息; case SET_DATE:// 设置日月的显示画面 ShowDDMMSet(currentDate);休息; case SET_SPEED_ADJ:// 调整速度修正的显示画面 ShowSpeedSet();休息; } Wait(150);}// 这是在比较时间到达时调用的中断// 因此将根据// OCR0A 寄存器设置每毫秒调用一次。ISR(TIMER0_COMPA_vect) { if (currentMode !=SET_TIME ) 当前时间++; elapsed++;}float JulianDate(int iday, int imonth, int iyear) { // 计算儒略日期(测试到 20,000 年) unsigned long d =iday;无符号长 m =imonth;无符号长 y =iyear;如果 (m <3) { m =m + 12; y =y - 1; } unsigned long t1 =(153 * m - 457) / 5;无符号长t2 =365 * y + (y / 4) - (y / 100) + (y / 400); return 1721118.5 + d + t1 + t2;}void GregorianDate(float jd, int &iday, int &imonth, int &iyear) { // 注意 2100 是下一个跳过的闰年 - 补偿跳过的闰年 unsigned long f =jd + 68569.5;无符号长 e =(4.0 * f) / 146097;无符号长 g =f - (146097 * e + 3) / 4;无符号长 h =4000ul * (g + 1) / 1461001;无符号长 t =g - (1461 * h / 4) + 31;无符号长 u =(80ul * t) / 2447;无符号长 v =u / 11; iyear =100 * (e - 49) + h + v; imonth =u + 2 - 12 * v; iday =t - 2447 * u / 80;}void SplitTime(unsigned long curr, unsigned long &ulHour, unsigned long &ulMin, unsigned long &ulSec) { // 从毫秒数计算 HH:MM:SS ulSec =curr / 1000; ulMin =ulSec / 60; ulHour =ulMin / 60; ulMin -=ulHour * 60; ulSec =ulSec - ulMin * 60 - ulHour * 3600;}unsigned long SetTime(unsigned long ulHour, unsigned long ulMin, unsigned long ulSec) { // 设置从午夜到当前时间的毫秒数 return (ulHour * 60 * 60 * 1000) + (ulMin * 60 * 1000) + (ulSec * 1000);}void Wait(unsigned long value) { // 创建我们自己的交易函数 // 我们已经在 TCCR0A 上设置了我们自己的中断 // 因此millis() 和delay() 将不再起作用 unsigned long startTime =elapsed; while ((elapsed - startTime)  12) { iMonth =1; } // 根据当前设置设置存储日期 currentDate =JulianDate(iDay, iMonth, iYear); } if (digitalRead(DAY_BUTTON) ==LOW) { // 提前一天 int iDay;内 iMonth;国际年; GregorianDate(currentDate, iDay, iMonth, iYear); iDay++;如果 (iDay> 31) { iDay =1; } if (((iMonth ==4) || (iMonth ==6) || (iMonth ==9) || (iMonth ==11)) &&(iDay> 30)) { iDay =1; } if ((iMonth ==2) &&(iDay> 29)) { iDay =1; } if ((iMonth ==2) &&((iYear % 4) !=0) &&(iDay> 28)) { iDay =1; } // 根据当前设置设置存储日期 // 如果随后调整月份使日期无效 // 然后显示将前进到下一个有效日期 currentDate =JulianDate(iDay, iMonth, iYear); } 休息; case SET_SPEED_ADJ:// 增加或减少修正 5 毫秒 if (digitalRead(UP_BUTTON) ==LOW) { speedCorrection +=5; } if (digitalRead(DOWN_BUTTON) ==LOW) { speedCorrection -=5; } 休息; } }}String FormatNumber(int value) { // 如果需要,添加前导 0 if (value <10) { return "0" + String(value); } else { 返回字符串(值); }}void ShowTime(unsigned long value) { // 每秒更新一次显示 // 或在午夜滚动时 if ((value> lastTime + 1000) || (value  
Arduino 定时器程序Arduino
此程序每隔 2 秒将经过的毫秒数发送到串行端口。
// Paul Brace Feb 2021// 与相应的处理脚本一起使用// 将此处的millis() 与// Processing 中的millis() 进行比较使用计算机clockint inByte =0;unsigned long firstReading =100000; // 首次读取时的millis()sentvoid setup() { Serial.begin(9600); // 将 hello 字节发送到 Processing sayHello();}void loop() { // 如果在串行端口上接收到一个字节 // 然后读取并丢弃它并发送当前的 Millis() 值 if (Serial. available()> 0){ // 获取传入字节 inByte =Serial.read(); // 发送自第一次读取处理后经过的时间 Serial.print(millis() - firstReading); Serial.print('E'); // 每 2 秒重复一次 delay(2000); }}void sayHello(){ // 等待串口可用 // 然后发送 hello 字节开始握手 while (Serial.available() <=0){ Serial.print('Z'); // 发送 Z 到处理说 Hello delay(200); } firstReading =millis();}
处理定时器测试脚本Processing
这是用于处理的脚本,它将读取从 Arduino 发送的毫秒数并将其与处理中经过的毫秒数进行比较。
// Paul Brace 2021 年 2 月// 接受来自 Arduino 的millis() 的脚本// 并将其与内部millis() // 评估Arduino 时钟的不准确性。// 假设计算机时钟是准确的// -ve =Arduino 运行缓慢,因此在时钟程序中输入+ve 调整// +ve =Arduino is运行速度很快,所以输入 -ve 调整以减慢时钟 downimport processing.serial.*;Serial theSerialPort; // 创建串口 objectint[] serialBytesArray =new int[15]; // 存储传入 bytesint bytesCount =0 的数组; // 当前接收的字节数boolean init =false; // false 直到通过接收字符 Zint fillColor =255 完成握手; // 定义初始填充 colourlong mills =0; // 上次读取接收时间长第一个 =0; // 收到第一批工厂的时间,所以我们现在可以计算一小时内的差异; // 自收到第一个mills以来经过的毫秒数long firstReading =100000; // 正在处理从 Arduinolong DiffPerHour =0 接收到的第一条消息的millis(); // 第一个小时后的差异 int inByte; // 最后一个字节 readvoid setup() { // 定义一些画布和绘图参数 size(500, 500);背景(70); noStroke(); // 打印所有串行设备的列表,以便您知道为 Arduino 设置哪一个 // 如果未在 printArray(Serial.list()) 下方设置正确的端口,则需要运行程序并进行编辑; // 实例化串行通信字符串 thePortName =Serial.list()[1]; theSerialPort =new Serial(this, thePortName, 9600);}void draw() { // 显示时间设置 background(70);填充(填充颜色);文本大小(25); text(hour() + ":" + minute() + ":" + second(), 50, 50); // Arduino 文本发送的最后一次读取毫秒数("传入已过:" + mills, 50, 100); // 在 Processing text("Local elapsed:" + (now - firstReading), 50, 150); // 显示当前差异 text("Diff:" + (mills - (now - firstReading)), 50, 200); // 检查 1 小时是否已经过去,第一小时是否存储差异 if ((((now - firstReading)>=3600000) &&(DiffPerHour ==0)){ DiffPerHour =mills - (now - firstReading); } // 显示第一个差值和第一个小时后的差值 text("Diff after 1 hour:" + DiffPerHour, 50, 300);}void serialEvent(Serial myPort) { // 从串口读取一个字节 inByte =myPort.read(); if (init ==false) { // 如果尚未握手,则查看是否握手字节 if (inByte =='Z') { // 如果读取的字节为 Z myPort.clear(); // 清除串口缓冲区init =true; // 存储我们有第一个 hello myPort.write('Z'); // 告诉 Arduino 发送更多 if (first ==0){ first =millis(); } } } else { // 如果已经有第一个 hello // 将串口的最新字节添加到数组 if (inByte !=69) { // 不检查消息字符 E 的结尾 if (bytesCount <14) { serialBytesArray[bytesCount] =inByte;字节数++; } } if (inByte ==69) { // 消息结束 // 存储经过的本地时间 now =millis(); // 计算传入的millis() mills =0; for (int i =1; i <=bytesCount; i++) { mills +=(serialBytesArray[i - 1] - 48) * pow(10, (bytesCount - i)); } // 假设我们准备接受下一条消息 // 如果这是第一次读取,则设置第一个差值 if (firstReading ==100000) { firstReading =now; } myPort.write('Z'); // 重置 bytesCount:bytesCount =0; } }}

示意图


制造工艺

  1. Arduino pov 视觉时钟
  2. 仅使用 Arduino 的 DTMF 解码器
  3. 使用 Arduino 制作流光溢彩监视器
  4. 使用 Adafruit 1/4 60 Ring Neopixel 的简单挂钟
  5. 简单字时钟(Arduino)
  6. 带有伊斯兰祈祷时间的 Arduino 时钟
  7. 主时钟
  8. 使用 Arduino 和智能手机的 DIY 电压表
  9. 使用物联网的心率监测器
  10. WebServerBlink 使用 Arduino Uno WiFi
  11. 7 段阵列时钟
  12. 使用 arduino 的自动化恐龙游戏