极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 22497|回复: 15

VBLEARN 项目1: 万年历制作,已基本完成开发,上传了源代码.

[复制链接]
发表于 2015-2-16 13:38:24 | 显示全部楼层 |阅读模式
本帖最后由 vblearn 于 2015-2-21 08:29 编辑

刚接触ARDUINO. 想做个万年历.
硬件,使用NANO328  DS3231, 1602显示, 蜂鸣器 按钮S
支持日历,时钟,计时,倒计时 等.
支持背光的开关
可以通过串口对时钟进行校准.

在此开贴,记录开发过程,也希望共同学习进步.
回复

使用道具 举报

发表于 2015-2-16 20:46:41 | 显示全部楼层
欢迎楼主将开发过程发进帖子,共同学习共同进步。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-16 22:37:39 | 显示全部楼层
按开发步骤,先写点设计.
>需要的功能
1钟表功能: 显示当前的日期,时间,星期及温度。显示公历和农历。
2计时功能 : 可以打开一个计时器
3倒计时器 :定时间,倒计时, 到时间提醒.
>需要解决的技术问题
1 使用I2C驱动1602模块. 控制背光, 显示两行字符. (已解决) 包括三个功能的不同界面.
2 使用I2C驱动DS3231. 包括时间的设置和时间温度的读取 (已解决) (两个I2C设备使用同一套 SAD,SCL线,靠设备号区分)
3 使用蜂鸣器发声提示. (已解决),但如果是播放一段音符,还需要进一步开发.
4 使用最少的键,完成模式的切换, 倒计时的调整, 开始/停止等. 这里需要处理的问题有: 键盘去抖, 长按与短按的区分, 背光的控制(长亮,有键盘时亮一段时间) 等.
5 农历的计算,可能需要参考一些其它程序,将代码移植过来.
6 设置的保存,考虑断电后某些设置, 可以保存在EEPROM中.
7 使用串口对时间进行校正. (已解决)
8 系统的总体结构, 由于存在多个"进程",需要一个总体的调度方式, 计划参考操作系统中的进程处理方式.
>可能的扩展.
如果作为连接到计算机的电路,可能由计算机开始倒计时等功能,或定时提示一些内容.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-16 22:48:04 | 显示全部楼层
先完成了技术中的1,2,7 上个图.
本想用NANO. 但可能是接错,烧了一块. 还是先用UNO了.
现在能够使用串口设置时间, 使用1602显示DS3231中的时间.

显示部分代码,但农历的程序还没有写.
  1. void ReadDS3231()
  2. {
  3.   int second,minute,hour,date,month,year,week,temperature,NLMonth,NLDate;
  4.   second=Clock.getSecond();
  5.   minute=Clock.getMinute();
  6.   hour=Clock.getHour(h12, PM);
  7.   date=Clock.getDate();
  8.   month=Clock.getMonth(Century);
  9.   year=Clock.getYear();
  10.   week=Clock.getDoW();
  11.   NLMonth = 12; //Chinese date
  12.   NLDate = 13;
  13.   temperature=Clock.getTemperature();
  14.   char sState[1];
  15.   sState[0]='C';
  16.   sprintf(m_s0, "20%02d-%02d-%02d W%1d %2d", year,month,date,week,temperature);
  17.   sprintf(m_s1, "%02d:%02d:%02d %.1s %2d#%2d", hour,minute, second,sState,NLMonth,NLDate);
  18. //  lcd.clear();
  19.   lcd.setCursor(0,0);
  20.   lcd.print(m_s0);
  21.   lcd.setCursor(0,1);
  22.   lcd.print(m_s1);
  23.   
  24. }
复制代码

另外串口部分也值得说说. 我使用的编码方式是一个"C"后面跟12个数字,表示年月日时分秒. 在分解串时保留了扩展其它命令的可能.
  1. void S_ReadSerial()
  2. {
  3.   byte bTreated;
  4.   while (Serial.available() > 0) {
  5.     byte inChar = Serial.read();
  6.     bTreated = 0;
  7.     if ((inChar>='a' && inChar<='z') || (inChar>='A' && inChar<='Z')) //command
  8.     {
  9.       m_cCommand=inChar; //record the command
  10.       m_p = 0; //
  11.       m_inString=""; //      
  12.       bTreated = 1;//mark treated
  13.     }
  14.     if (isDigit(inChar)) {
  15.       // convert the incoming byte to a char
  16.       // and add it to the string:
  17.       m_inString += (char)inChar;
  18.       m_p=m_p+1;
  19.       bTreated = 2; //mark treated as digit
  20.       if (m_p>=12)
  21.       {
  22.         //send command and number
  23.         
  24.         FCommand(m_cCommand);
  25.         m_p=0;
  26.         m_cCommand='#'; //no command
  27.       }
  28.     }
  29.     if (bTreated==0)
  30.     {
  31.       m_cCommand='#'; //clear Command
  32.     }
  33.   }
  34. }
复制代码

这部分处理命令,现在只有一个命令.
  1. int FCommand(char c)
  2. {
  3.   switch (c)
  4.   {
  5.   case 'C': //Change time calibrate
  6.     //split string and go
  7.         S_SetTime();
  8.     break;
  9.   default:
  10.   break;
  11.   }
  12. }
复制代码


分解字符串和设置时间部分.
  1. void S_SetTime()
  2. {
  3.   //split the  m_inString
  4.   byte y, m, d, h, minute, s;
  5.   y = F_GetValueFromString(0,2);
  6.   m = F_GetValueFromString(2,2);
  7.   d = F_GetValueFromString(4,2);
  8.   h = F_GetValueFromString(6,2);
  9.   minute = F_GetValueFromString(8,2);
  10.   s = F_GetValueFromString(10,2);

  11.   Clock.setSecond(s);//Set the second
  12.   Clock.setMinute(minute);//Set the minute
  13.   Clock.setHour(h);  //Set the hour
  14.   Clock.setDate(d);  //Set the date of the month
  15.   Clock.setMonth(m);  //Set the month of the year
  16.   Clock.setYear(y);  //Set the year (Last two digits of the year)
  17. }
  18. byte F_GetValueFromString(int p,int len)
  19. {
  20.   byte v = 0;
  21.   byte i,j;
  22.   for (i=p;i<p+len;i++)
  23.   {
  24.     v=v*10+(m_inString[i]-'0');
  25.   }
  26.   return v;
  27. }
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-17 14:45:11 | 显示全部楼层
<多线程的处理>
由于本程序要涉及几个模块的依次执行,而且每个模块的重复时间又不一样.所以必须使用一个调度的程序,对各模块的运行进行管理.
在开始部分定义了如下的常量和变量.  记录了线程数, 各线程的下标等. 每个线程记录是否运行及下一次调用的时间. 基于millis.
  1. #define NumberOfProcess 5
  2. #define procReadTime 0 //Read time from DS3231
  3. #define procDisplay 1 //Display
  4. #define procKeyInput 2 //Key
  5. #define procMusic 3 //Play Music
  6. #define procSerial 4 //treat Serial

  7. byte m_aProcEnable[NumberOfProcess]; //0 to disable, 1 to enable;
  8. unsigned long m_aProcMillis[NumberOfProcess]; //wait to run after this time

  9. unsigned long m_currentmillis;//current time
复制代码


初始化部分
  1.   //init process
  2.   byte i;
  3.   m_aProcEnable[procReadTime] = 1;
  4.   m_aProcEnable[procDisplay] = 1;
  5.   m_aProcEnable[procKeyInput] = 1;
  6.   m_aProcEnable[procMusic] = 0; //default disable
  7.   m_aProcEnable[procSerial] = 1;
  8.   for (i=0;i<NumberOfProcess;i++)
  9.   {
  10.     m_aProcMillis[i]=m_currentmillis; //define to current time
  11.   }
复制代码



实际的LOOP函数
  1. void loop()
  2. {
  3.   //call each process
  4.   byte i;
  5.   m_currentmillis=millis();//current time
  6.   for (i=0;i<NumberOfProcess;i++)
  7.   {
  8.     if (m_aProcEnable[i]==1)
  9.     {
  10.       if ( m_aProcMillis[i]<m_currentmillis) //not consider the overroll of millis
  11.       {
  12.         //call the process
  13.         switch (i)
  14.         {
  15.         case procSerial:
  16.           S_ProcSerial();
  17.           break;
  18.         case procDisplay:
  19.           S_ProcDisplay();
  20.           break;
  21.         case procKeyInput:
  22.           S_ProcKeyInput();
  23.           break;
  24.         case procMusic:
  25.           S_ProcMusic();
  26.           break;
  27.         case procReadTime:
  28.           S_ProcReadTime();
  29.           break;
  30.         default:
  31.           Serial.print ("Unknow process"); //Report error
  32.           Serial.println(i);
  33.         }
  34.       }
  35.     }
  36.    
  37.   }
  38.   //no delay here
  39. }
复制代码


对一个线程, 在结束自己的任务后,预约下次运行的时间.
例如 这样就可以预约100ms后运行.
  1. void S_ProcMusic()
  2. {
  3.   //play music here
  4.   //Set Next Process time
  5.   m_aProcMillis[procMusic]=m_currentmillis+100;
  6. }
复制代码

还要说明的是,这里没有处理MILLIS的回滚问题.如果回滚(50天之后).  则由于当前的时间大于下一次预约的时间, 所以会每次循环都运行所有的可用线程. 但不会有太大的问题. 只不过是多运行几次. 当然可以修改程序处理这种问题. (比如判断两个时间的最高位). 这个问题之后再进一步处理.

回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-17 14:53:38 | 显示全部楼层
本帖最后由 vblearn 于 2015-2-17 14:54 编辑

<键盘问题的处理>
按计划,键盘中需要处理防抖, 短按长按的区分等. 如果只有简单的程序,可以直接用DELAY来处理,但现在需要多线程处理. (否则的话,长按的过程中,LCD就不会更新).
使用了如下的方式;
全局变量
其中 ShakeTime 表示防抖的时间, LongTime表示长按的时间,在这两者之间会被判断为短按.
  1. //////////////////Keyboard Public Variables
  2. #define NumberOfKey 4
  3. byte m_aKeyPort[NumberOfKey]; //keep IO port for each key
  4. unsigned long m_aKeyMillis[NumberOfKey]; //last state change time of key
  5. byte m_aKeyState[NumberOfKey]; //Key state
  6. #define KeyShakeTime 20  //Anti-Shake time
  7. #define KeyLongTime 2000  //For long push
复制代码




初始化部分
  1. void S_InitKeyInput()
  2. {
  3.   //init the port for keys
  4.   m_aKeyPort[0]=8;
  5.   m_aKeyPort[1]=9;
  6.   m_aKeyPort[2]=10;
  7.   m_aKeyPort[3]=11;
  8.   for (byte i=0; i<NumberOfKey;i++)
  9.   {
  10.     pinMode(m_aKeyPort[i], INPUT);  
  11.     m_aKeyMillis[i]=m_currentmillis;
  12.     m_aKeyState[i]=digitalRead(m_aKeyPort[i]); //record current state
  13.   }
  14. }
复制代码


每线程调用时,如下处理.
  1. void S_ProcKeyInput()
  2. {
  3.   //Check each port
  4.   byte i,b;
  5.   for (i=0;i<NumberOfKey;i++)
  6.   {
  7.     b=digitalRead(m_aKeyPort[i]);
  8.     //calculate the time between change
  9.     if (b!=m_aKeyState[i])
  10.     { //some thing change
  11.       Serial.println(m_currentmillis);
  12.       if (b==HIGH) {
  13.            //未处理低变高
  14.       }
  15.       else //b==LOW 处理高变低
  16.       { //calculate the time
  17.         unsigned long dt=m_currentmillis-m_aKeyMillis[i]; //time between this change and last change.

  18.         if (dt>KeyShakeTime) //not a shake
  19.         {
  20.           if (dt>KeyLongTime) // long press
  21.           {
  22.             S_RunKey(i,1);
  23.           }
  24.           else //short click
  25.           {
  26.             S_RunKey(i,0);
  27.           }
  28.         }
  29.       }
  30.       //record the time
  31.       m_aKeyMillis[i]=m_currentmillis;
  32.       m_aKeyState[i]=b;
  33.     }
  34.   }

  35.   //Set Next Process time
  36.   m_aProcMillis[procKeyInput]=m_currentmillis+5; //check each time
  37. }
复制代码


最后,用一个函数来处理键盘的命令
现在只是使用串口调试.
  1. //Run function for the key, i is index, bLong is Long or Short click
  2. void S_RunKey(byte i, byte bLong)
  3. {
  4.   if (bLong==0)
  5.   {
  6.     Serial.print ("short key");
  7.     Serial.println (i);
  8.   }
  9.   else
  10.   {
  11.     Serial.print ("long key");
  12.     Serial.println (i);
  13.   }
  14. }
复制代码
回复 支持 反对

使用道具 举报

发表于 2015-2-17 21:25:03 | 显示全部楼层
农历程序,拿好不谢!我叫雷锋!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2015-2-17 21:27:30 | 显示全部楼层
哦,还有一个日出日落的程序。不过要经纬度,可以设置自己当地的经纬来使用。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-18 14:27:13 | 显示全部楼层
本帖最后由 vblearn 于 2015-2-18 14:38 编辑

下面还处理了 蜂鸣器的控制, 背光的控制, 农历的实现, 键盘功能的定义等.
不一一细说了.
在此附上程序,希望与大家多交流.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2015-2-19 12:34:12 | 显示全部楼层
做的还真好。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-20 22:30:04 | 显示全部楼层
最后上个简化壳的图,不要见笑.
以后再用其它的材料吧. {:soso_e104:}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2015-2-20 23:14:32 | 显示全部楼层
支持好作品~
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-2-22 22:49:18 | 显示全部楼层
进行了一些修改和升级。原来使用两个按钮来调节增减,正好手头有旋转编码器,这样在调节倒计时的时间设置时比较方便。
正在实际使用中。有需求变化再进一步修改。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2016-3-18 15:29:52 | 显示全部楼层
谢谢 好人
回复 支持 反对

使用道具 举报

发表于 2016-3-18 16:01:36 | 显示全部楼层
板子型号?自带温度检测?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-5-3 19:37 , Processed in 0.047119 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

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