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

Arduino Nano + 1602 LCD 打造高精度时钟:软件校准内部时钟

元器件与材料

项目简介

最初是学术练习,最终做出极高精度时钟:连续运行 5 天零误差。

Arduino 内部时钟频率不完全准确,直接用 millis() 计时会累积偏差。我的方法:实测单片机每小时快/慢多少毫秒,在代码中每小时修正该偏差。

经 5 天验证,内部时钟偏差稳定一致,软件修正有效。

第二问题:millis() 约 50 天溢出且不可修改。解法:重写 Timer0 中断,自维护毫秒计数器,午夜归零,彻底规避溢出。

测量单片机偏差

假设电脑时钟准确。写 Arduino 程序每 2 秒发送毫秒数给 Processing,Processing 对比自家 millis(),一小时后得出偏差值,即为时钟代码中的 speedCorrection。

Arduino 测试程序与 Processing 脚本见代码章节。无 Processing 者可至 processing.org 下载。

时钟核心代码解析

自定义毫秒中断

// 占用 Timer0,millis()/delay() 将失效
TCCR0A = (1 << WGM01);           // CTC 模式
OCR0A = 0xF9;                    // 249 -> 1ms @ 16MHz/64
TIMSK0 |= (1 << OCIE0A);         // 开启比较匹配中断
TCCR0B |= (1 << CS01) | (1 << CS00); // 64 分频
TCNT0 = 0;
sei();

中断服务程序 (每毫秒调用):

volatile unsigned long currentTime = 0; // 午夜起毫秒数
volatile unsigned long elapsed = 0;     // 小时计数器

ISR(TIMER0_COMPA_vect) {
  if (currentMode != SET_TIME) currentTime++;
  elapsed++;
}

变量加 volatile 防止编译器优化导致读取缓存值。

日期处理:儒略日算法

日期存儒略日数 (自公元前 4713-01-01 起天数),含互转格里高利历函数:

float JulianDate(int d, int m, int y) {
  if (m < 3) { m += 12; y -= 1; }
  unsigned long t1 = (153 * m - 457) / 5;
  unsigned long t2 = 365 * y + y/4 - y/100 + y/400;
  return 1721118.5 + d + t1 + t2;
}
void GregorianDate(float jd, int &d, int &m, int &y) {
  unsigned long f = jd + 68569.5;
  unsigned long e = (4 * f) / 146097;
  unsigned long g = f - (146097 * e + 3) / 4;
  unsigned long h = 4000 * (g + 1) / 1461001;
  unsigned long t = g - (1461 * h / 4) + 31;
  unsigned long u = (80 * t) / 2447;
  unsigned long v = u / 11;
  y = 100 * (e - 49) + h + v;
  m = u + 2 - 12 * v;
  d = t - 2447 * u / 80;
}

每日归零与每小时修正

const unsigned long MS_DAY = 24UL*60*60*1000;
const unsigned long MS_HOUR = 60UL*60*1000;
int speedCorrection = 3545; // 实测每小时慢 3545ms,快则填负数

void loop() {
  // 跨天处理
  if (currentMode == SHOW_TIME && currentTime > MS_DAY) {
    noInterrupts();
    currentTime -= MS_DAY;
    interrupts();
    currentDate++;
  }
  // 每小时修正
  if (elapsed >= MS_HOUR) {
    noInterrupts();
    currentTime += speedCorrection;
    elapsed = 0;
    interrupts();
  }
  // ... 按键检测、显示更新等
}

修正时关中断防止计数器被 ISR 破坏。

三按键交互

代码

主时钟程序

// Paul Brace - Feb 2021
// 无 RTC 模块的高精度时钟,含软件时钟修正
#include 
LiquidCrystal lcd(12, 13, 6, 7, 8, 9);

#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
#define SET_TIME 2
#define SET_YEAR 3
#define SET_DATE 4
#define SET_SPEED_ADJ 5

int speedCorrection = 3545; // 根据实测修改
volatile unsigned long currentTime = 0;
unsigned long lastTime = -1000;
volatile unsigned long elapsed = 0;
unsigned long millisecondsInADay = 24UL*60*60*1000;
unsigned long millisecondsInHour = 60UL*60*1000;
int currentMode = SHOW_TIME;
float currentDate = JulianDate(1,1,2021);
float lastDate = 0;
int currentDay, currentMonth, currentYear;
char *dayArray[] = {"Tue. ","Wed. ","Thur. ","Fri. ","Sat. ","Sun. ","Mon. "};

void setup() {
  // Timer0 中断设置 (同上)
  TCCR0A = (1<

Arduino 偏差测试程序

// 每 2 秒发送毫秒数给 Processing
int inByte = 0;
unsigned long firstReading = 100000;

void setup() {
  Serial.begin(9600);
  sayHello();
}

void loop() {
  if (Serial.available() > 0) {
    inByte = Serial.read();
    Serial.print(millis() - firstReading);
    Serial.print('E');
    delay(2000);
  }
}

void sayHello() {
  while (Serial.available() <= 0) {
    Serial.print('Z'); delay(200);
  }
  firstReading = millis();
}

Processing 对比脚本

// Processing 3.x
import processing.serial.*;
Serial theSerialPort;
int[] serialBytesArray = new int[15];
int bytesCount = 0;
boolean init = false;
long mills = 0, first = 0, now, firstReading = 100000, DiffPerHour = 0;

void setup() {
  size(500,500); background(70); noStroke();
  printArray(Serial.list());
  theSerialPort = new Serial(this, Serial.list()[1], 9600);
}

void draw() {
  background(70); fill(255); textSize(25);
  text(hour()+":"+minute()+":"+second(), 50, 50);
  text("Incoming: "+mills, 50, 100);
  text("Local: "+(now-firstReading), 50, 150);
  text("Diff: "+(mills-(now-firstReading)), 50, 200);
  if ((now-firstReading)>=3600000 && DiffPerHour==0)
    DiffPerHour = mills - (now-firstReading);
  text("Diff after 1hr: "+DiffPerHour, 50, 300);
}

void serialEvent(Serial myPort) {
  int inByte = myPort.read();
  if (!init) {
    if (inByte == 'Z') { myPort.clear(); init=true; myPort.write('Z'); if(first==0) first=millis(); }
  } else {
    if (inByte != 69 && bytesCount < 14) {
      serialBytesArray[bytesCount++] = inByte;
    }
    if (inByte == 69) {
      now = 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 = 0;
    }
  }
}

原理图

Arduino Nano + 1602 LCD 打造高精度时钟:软件校准内部时钟
Arduino Nano + 1602 LCD 打造高精度时钟:软件校准内部时钟

制造工艺

  1. Arduino POV 时钟:自制视觉暂留 LED 显示器
  2. 仅用 Arduino 构建 DTMF 解码器 – 无需额外 IC
  3. 用 Arduino 打造绚丽显示器 Ambilight 系统
  4. 用 Adafruit 60 LED NeoPixel 环形灯和 Arduino 制作时尚挂钟
  5. DIY Arduino单词时钟 – 极简优雅的实时文字显示
  6. 自制 Arduino 伊斯兰祷告时间实时时钟
  7. 自制大尺寸LED主时钟带闹钟功能 — 12米外清晰可见
  8. Arduino 与智能手机打造智能电压表——简单易做的 DIY 项目
  9. 基于 Arduino 与 MAX30100 的实时物联网心率监测系统
  10. Arduino Uno WiFi 网页服务器:通过浏览器控制 LED 开关
  11. 用 Arduino Pro Mini 与 DS1302 RTC 打造精准七段数码管时钟
  12. Arduino 自动化 Chrome 恐龙游戏 - LDR 光敏电阻 + Python 实现全自动跳跃