首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多功能数字时钟

多功能数字时钟
EN

Code Review用户
提问于 2018-01-16 15:49:20
回答 2查看 944关注 0票数 8

我用RTC (DS3231)和LCD (16x2)制作了一个数字时钟。

目前的职能:

  • 显示时间
  • 显示日期(不同格式)
  • 显示温度(摄氏和华氏)
  • 展示一周中的一天(英文和荷兰语)
  • 显示周数
  • 显示一年中的天数
  • 湿度(%)
  • 露点(摄氏和华氏)
  • 更改夏季->冬季时间(这将自动完成,一旦设置正确)

今后的职能:

  • 菜单(使用LCD屏蔽上的按钮进行控制)。
  • 警报(在开始时预先定义它已经是可能的了)
  • 计时
  • 秒表
  • 不同地点的当地时间

我想尽快实现这些未来的功能。请随时给出反馈和你的意见,什么可以更好/更有效率。

Github

Clock.ino:

代码语言:javascript
复制
#include <DS3232RTC.h>      // https://github.com/JChristensen/DS3232RTC
#include <Timezone.h>       // https://github.com/JChristensen/Timezone
#include <TimeLib.h>        // https://github.com/PaulStoffregen/Time
#include <Streaming.h>      // http://arduiniana.org/libraries/streaming/
#include <Wire.h>           // https://www.arduino.cc/en/Reference/Wire
#include <LiquidCrystal.h>  // https://www.arduino.cc/en/Reference/LiquidCrystal
#include <DHT.h>            // https://github.com/adafruit/DHT-sensor-library
#include "Date.h"

#define DHTPIN 2     // what pin we're connected to
#define DHTTYPE DHT22   // DHT 22  (AM2302)

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

DHT dht(DHTPIN, DHTTYPE); //// Initialize DHT sensor for normal 16mhz Arduino

// constants won't change:
const String days[2][7] = {{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}, {"zo", "ma", "di", "wo", "do", "vr", "za"}};
const String months[2][12] = {{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}, {"jan", "feb", "mrt", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"}};
const String labels[5] = {"T", "RH", "DP", "D", "Wk"};

/* EXPLANATION DIFFERENT FUNCTIONS FOR CLOCK (WILL ONLY BE USED ON THE HOMEPAGE)
   TIME
   'h' = hours
   'm' = minutes
   's' = seconds
   'a' = alarm 1       (not atm)
   'A' = alarm 2       (not atm)

   WEATHER
   'T' = temperature
   'H' = humidity      (no sensor atm)
   'P' = dew point     (no sensor atm)

   DATE
   'd' = day of week
   'D' = day
   'M' = month
   'S' = month (short string)
   'Y' = year
   'w' = weeknumber
   'n' = daynumber

   MISCELLANEOUS
   'l' = current location  (need to create a array of enums/strings which can be used in Settings)

   DELIMTERS
   ':' = delimiter
   '-' = delimiter
   '/' = delimiter
   '.' = delimiter
   '|' = delimiter
   ' ' = delimiter
*/
char homepage[][16] = {{"h:m:s"}, {"d D-M-Y"}, {"w n"}, {"S"}, {"T H"}, {"P"}};

enum languages_t {EN, NL};

typedef struct {
  int hourFormat;               // 12 or 24 hour format (AM/PM is not displayed)
  uint8_t language;             // The language for several labels
  char degreesFormat;           // Celcius or Fahrenheit
  boolean labels;               // Display temperature, weeknumber and daynumber with label
  long intervalPage1;           // interval at which to refresh lcd (milliseconds)
  long switchPages;             // interval at which to switchPage 1 to 2 (milliseconds)
} Settings;

Settings default_settings = {24, NL, 'c', true, 1000, 30000};
Settings settings = {24, NL, 'c', true, 1000, 30000};

TimeChangeRule myDST = {"MDT", Last, Sun, Mar, 2, 2 * 60};  //Daylight time/Summertime = UTC + 2 hours
TimeChangeRule mySTD = {"MST", Last, Sun, Oct, 2, 1 * 60};  //Standard time/Wintertime = UTC + 1 hours
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr;        //pointer to the time change rule, use to get TZ abbrev

unsigned long previousMillis = 0;        // will store last time lcd was updated (page 1)
unsigned long oldMillis = 0;             // will store last time lcd switched pages
int language_id;
int rowX = 0;
int rowY = 2;
int numberOfPages = (sizeof(homepage) / sizeof(char)) / 32;
float tem;
float hum;
float dew;

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

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);

  // setSyncProvider() causes the Time library to synchronize with the
  // external RTC by calling RTC.get() every five minutes by default.
  setSyncProvider(RTC.get);
  if (timeStatus() != timeSet) lcd << ("RTC SYNC FAILED");


  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

}

void loop() {
  // check to see if it's time to refresh the lcd; that is, if the difference
  // between the current time and last time you refreshed the lcd is bigger than
  // the interval at which you want to refresh the lcd.
  unsigned long currentMillis = millis();
  defineLanguageId();
  tem = dht.readTemperature();
  hum = dht.readHumidity();
  if (currentMillis - previousMillis >= settings.intervalPage1) {
    // save the last time you refreshed the lcd
    previousMillis = currentMillis;

    // display the date and time according to the specificied order with the specified settings
    displayPage(rowX, rowY);
  }
  if (currentMillis - oldMillis >= settings.switchPages) {
    oldMillis = currentMillis;

    if (rowY == numberOfPages * 2) {
      rowX = 0;
      rowY = 2;
      lcd.clear();
    } else {
      rowX += 2;
      rowY += 2;
      lcd.clear();
    }

  }
}

void displayPage(int rowStart, int rowEnd) {

  time_t utc, local;
  utc = now();

  local = myTZ.toLocal(utc, &tcr);

  // calculate which day and week of the year it is, according to the current local time
  DayWeekNumber(year(local), month(local), day(local), weekday(local));

  lcd.setCursor(0, 0);
  // for-loop which loops through each row
  for (int row = rowStart; row < rowEnd; row++) {
    // if row == odd, then we are on the second line, so move the cursor of the lcd
    if (not(row % 2) == 0) lcd.setCursor(0, 1);
    // for-loop which loops through each char in row
    for (int pos = 0; pos < 15; pos++) {
      displayDesiredFunction(row, pos, local);
    }
  }
  return;
}

void displayDesiredFunction(int row, int pos, time_t l) {
  switch (homepage[row][pos]) {
    case 'h':
      displayHours(l);                  // display hours (use settings.hourFormat)
      break;                            
    case 'm':                           
      printI00(minute(l));              // display minutes
      break;                            
    case 's':                           
      printI00(second(l));              // display seconds
      break;                            
    case 'T':                             
      displayTemperature();             // display temperature (use settings.temperatureFormat)
      break;                            
    case 'H':                           
      displayHumidity();                // display humidity
      break;
    case 'P':
      displayDewPoint();                // display dew point (use settings.temperatureFormat)
      break;
    case 'd':
      displayWeekday(weekday(l));       // display day of week (use settings.lanuague)
      break;
    case 'D':
      printI00(day(l));                 // display day
      break;
    case 'M':
      printI00(month(l));               // display month
      break;
    case 'S':
      displayMonthShortStr(month(l));   // display month as string
      break;
    case 'Y':
      printI00(year(l));                // display year
      break;
    case 'n':
      displayNumber('d');               // display daynumber
      break;
    case 'w':
      displayNumber('w');               // display weeknumber
      break;
    case 'l':
      displayLocation();                // display current location
      break;
    case ':':
      displayDelimiter(':');            // display ':'
      break;
    case '-':
      displayDelimiter('-');            // display '-'
      break;
    case '/':
      displayDelimiter('/');            // display '/'
      break;
    case '.':
      displayDelimiter('.');            // display '.'
      break;
    case '|':
      displayDelimiter('|');            // display '|'
      break;
    case ' ':
      displayDelimiter(' ');            // display ' '
      break;
  }
}

void displayHours(time_t l) {
  (settings.hourFormat == 24) ? printI00(hour(l)) : printI00(hourFormat12(l));
  return;
}

void displayTemperature() {
  if (settings.labels) {
    lcd << labels[0] << ": ";
  }
  (settings.degreesFormat == 'c') ? lcd << int(tem) << (char)223 << "C" : lcd << int(((tem * 9) / 5) + 32) << (char)223 << "F";
}

void displayHumidity() {
  if (settings.labels) {
    lcd << labels[1] << ": ";
  }
  lcd << int(hum) << "%";
}

void displayDewPoint() {
  dew = calculateDewPoint(tem, hum);
  if (settings.labels) {
    lcd << labels[2] << ": ";
  }
  (settings.degreesFormat == 'c') ? lcd << int(dew) << (char)223 << "C" : lcd << int(((dew * 9) / 5) + 32) << (char)223 << "F";
}

float calculateDewPoint(float t, float h) {
  float a = 17.271;
  float b = 237.7;
  float temp = (a * t) / (b + t) + log(h * 0.01);
  float Td = (b * temp) / (a - temp);
  return Td;
}

void displayWeekday(int val)
{
  lcd << days[language_id][val - 1];
  return;
}

void displayNumber(char val) {
  if (val == 'd') {
    if (settings.labels == true) {
      lcd << labels[3] << ": ";
    }
    printI00(DW[0]);
  }
  else {
    if (settings.labels == true) {
      lcd << labels[4] << ": ";
    }
    printI00(DW[1]);
  }
  return;
}

void displayMonthShortStr(int val) {
  lcd << months[language_id][val];
  return;
}

void displayLocation() {
  //lcd << settings.location;
}

void defineLanguageId() {
  switch (settings.language) {
    case EN:
      language_id = 0;
      break;
    case NL:
      language_id = 1;
  }
}

void printI00(int val)
{
  if (val < 10) lcd << '0';
  lcd << _DEC(val);
  return;
}

void displayDelimiter(char delim) {
  lcd << delim;
  return;
}

void setTimeRTC(unsigned int hours, unsigned int minutes, unsigned int seconds, unsigned int d, unsigned int m, unsigned int y) {
  setTime(hours, minutes, seconds, d, m, y);
  RTC.set(now());
  return;
}

Date.h:

代码语言:javascript
复制
short DW[2];

void DayWeekNumber(unsigned int y, unsigned int m, unsigned int d, unsigned int w) {
  int ndays[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};  // Number of days at the beginning of the month in a not leap year.
  //Start to calculate the number of day
  if (m == 1 || m == 2) {
    DW[0] = ndays[(m - 1)] + d;                 //for any type of year, it calculate the number of days for January or february
  }                        // Now, try to calculate for the other months
  else if ((y % 4 == 0 && y % 100 != 0) ||  y % 400 == 0) { //those are the conditions to have a leap year
    DW[0] = ndays[(m - 1)] + d + 1; // if leap year, calculate in the same way but increasing one day
  }
  else {                                //if not a leap year, calculate in the normal way, such as January or February
    DW[0] = ndays[(m - 1)] + d;
  }
  // Now start to calculate Week number
  (w == 0) ? DW[1] = (DW[0] - 7 + 10) / 7 : DW[1] = (DW[0] - w + 10) / 7;
  return;
}
EN

回答 2

Code Review用户

发布于 2018-01-17 06:22:15

这看起来是一个非常有趣的项目!它是用一种相当简单的方式写的,所以我想我得到了其中的大部分。以下是一些改进的建议。

避免幻数

你的代码中有很多数字。有些是显而易见的,但有些不是,也没有解释它们是什么。此外,如果您想要更改它们中的任何一个,则需要找到它们的所有实例并更改每个实例。例如,在常量声明中,使用数字2、7、12和5。

代码语言:javascript
复制
const size_t kNumSupportedLanguages = 2;
const size_t kNumDaysPerWeek = 7;
const size_t kNumMonthsPerYear = 12;
const size_t kNumLabels = 5;

对于毫秒值,我可能也会这样做:

代码语言:javascript
复制
const long kOneSecInMS = 1000;
const long kThirtySecsInMS = 30000;

这一行中的32代表什么:

代码语言:javascript
复制
int numberOfPages = (sizeof(homepage) / sizeof(char)) / 32;

不管它是什么,为它做一个常数!与setup()函数中的9600、16、2和13类似。事实上,如果您这样做了,您甚至不需要在调用lcd.begin()上面的评论。只会是lcd.begin(kNumLCDCols, kNumLCDRows);。在displayTemperature()中,将值233转换为char。为什么?它代表了什么?如果是学位符号,就给它起个名字。

简化为

类型

您的Settings struct有几个字段只能有2个值,但占用更多的空间。我知道嵌入式应用程序通常内存很短,所以保持小内存是很好的。我不确定这里是否是这样的,但您可以轻松地创建一个8位字段,并使用1位作为小时格式,1位用于语言(如果您知道您永远不需要更多的语言),1位用于学位格式,1位用于是否显示标签。这是您当前struct中的7个字节(虽然可能更多是由于对齐问题),压缩成一个字节。

即使您不这样做,我认为对于只有两个可能值的其他3个字段来说,使用类似于enumlanguages_t是个好主意。

避免全局变量

代码中有几十个全局变量。全局变量很难处理,因为当它们中有一个坏值时,就不可能跟踪它的更改位置,因为代码的每个部分都可以访问它们。我在代码中没有看到一个main()函数,所以我不确定这一切是如何在实践中运行的。我建议将所有全局值放在main()函数中(或者任何函数最终调用到您在这里编写的函数中),并将它们传递到每个函数中。通过将它们传递到每个函数中,您可以更容易地看到调试时的数据流。

不声明标头中的变量

Date.h文件中,您声明了一个全局变量DW。这将导致在每个#includes头文件中创建变量。而且,由于文件周围没有头保护,如果包含头部,然后还包括包含Date.h的另一个标头,您将得到一个编译错误,因为您将在相同的范围内重新声明变量。

更清楚地使用if时,请使用

在以下几个地方,您使用了一个非常难以理解的三元运算符:

代码语言:javascript
复制
(w == 0) ? DW[1] = (DW[0] - 7 + 10) / 7 : DW[1] = (DW[0] - w + 10) / 7;

只编写一个简单的if语句就更清楚了:

代码语言:javascript
复制
if (w == 0)
{
    DW[1] = (DW[0] - 7 + 10) / 7;
}
else
{
    DW[1] = (DW[0] - w + 10) / 7;
}

如果您真的出于某种原因想要使用三元操作符,那么至少要按照预期的顺序编写它:

代码语言:javascript
复制
DW[1] = (w == 0) ? (DW[0] - 7 + 10) / 7 : (DW[0] - w + 10) / 7;

甚至:

代码语言:javascript
复制
int subVal = (w == 0) ? 7 : w;
DW [ 1 ] = (DW[0] - subVal + 10) / 7;

为公共任务创建函数

有些代码行很长,您在几个地方重复过。这是另一个领域,您可能只在一个版本中出现一个很难跟踪的bug,否则您将更改其中一个,并需要更新所有这些版本。如果你只是把它放在一个函数中,它会更容易阅读,并且不会受到这些问题的困扰。例如,将华氏温度转换为摄氏。只需做一个功能:

代码语言:javascript
复制
int FahrenheitToCelsius(const float celsius)
{
    return static_cast<int>(celsius * 9 / 5 + 32);
}

这很可能会被编译器内联,所以它的效率不应该降低。

简化

Date.h中的函数过于复杂。我会这样改写它:

代码语言:javascript
复制
void DayWeekNumber(unsigned int y, unsigned int m, unsigned int d, unsigned int w) {
  int ndays[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};  // Number of days at the beginning of the month in a not leap year.
  int numLeapDaysToAdd = 0;
  if ((y % 4 == 0 && y % 100 != 0) ||  y % 400 == 0) {
    numLeapDaysToAdd = 1;
  }

  DW[0] = ndays[(m - 1)] + d + numLeapDaysToAdd;

  // Now start to calculate Week number
  if (w == 0)
  {
     DW[1] = (DW[0] - 7 + 10) / 7;
  } else {
     DW[1] = (DW[0] - w + 10) / 7;
  }
}

另外,不要将return语句添加到void函数的末尾。它没有添加任何内容,因为函数将在没有语句的情况下返回。

票数 4
EN

Code Review用户

发布于 2018-01-17 06:45:14

我只注意到一件事:

代码语言:javascript
复制
if (not(row % 2) == 0)

这段代码可以工作,但只是偶然的。你应该写这两种文字中的任何一种:

代码语言:javascript
复制
if (not(row % 2 == 0))
if (row % 2 != 0)

您当前拥有的代码获取除法的其余部分(0或1),然后对其执行布尔操作( not)。然后将该否定的结果(真或假)转换为整数(1或0),并将其与零进行比较。不同类型之间的转换太多了。

此模式只适用于与0进行比较。与2相比,它失败了:

代码语言:javascript
复制
not(row % 3) == 2

因为not只能返回true或false,所以它永远不能等于2。

另一个:

代码语言:javascript
复制
int numberOfPages = (sizeof(homepage) / sizeof(char)) / 32;

这应该是:

代码语言:javascript
复制
size_t numberOfPages = sizeof homepage / sizeof homepage[0];
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/185229

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档