`

红红纪念日记录器(原创)

阅读更多

红红纪念日记录器(原创)

 

                                                             
由 王宇 原创并发布 

 

 

       首先解释一下红红的由来:红红是源自美国动画片《急速蜗牛》中花园里的西红柿,由此寄托着一个IT匠人的希望和愿景。
 
红红纪念日记录器献给我即将六周岁的儿子乔乔。
 
一 功能说明
     1、倒计时最近纪念日的天数。
     2、显示最近四个纪念日列表。
     3、显示当前年、月、日、星期和时间。
     4、人体感应控制背光和切换屏幕。
     5、通过串口(USB模拟)通讯,在PC上设置日期和时间。
     6、显示红红Logo。

 

 

 

图片处理的不好,LCD12864实际显示的效果要比这个好的多。
 
二 硬件组成
     1、Arduino主控板:Arduino Mega2560
     2、DS1307时钟模块
     3、LCD12864显示器
     4、人体感应传感器HC-SR501
     电路连线:
                                                                                   
               Mega2560   LCD12864                        Mega2560    DS1307                  Mega2560     HC-SR501            
          
                    GND --------- PIN1                           GND --------- GND                         GND --------- GND
                      5V ----------PIN2                             5V ----------VCC                           5V ----------VCC
                      8   ----------RS(CS)                     20 ----------SDA                             7 ----------OUT
                      9   ----------RW(SID)                   21 ----------SCL
                      3   ----------EN(CLK)
                    GND-----------PSB
                      6  -----------PIN19 (背光)

 

 

调试飞线状态:

 

 

三 软件开发环境
     Arduino 1.5.4
     
     Vim 7.3 + c-support + ctags
 
     我比较习惯在Linux(这个项目是在Win7下开发的)环境下开发软件,所以使用了几个小时的Arduino编辑器就开始不耐烦了,目前使用vim来编写Arduino的代码。先展示一下vim的开发界面:


 
哎!,怎么看怎么感觉舒服。 Vim plugin的配置和使用方法,我不在这里啰嗦了,网上多的是。这里我要多说一下的是,vim会识别文件的扩展名".c" ,如果你直接打开Arduino的".ino"文件,vim会把这个文件当成一个普通txt文件处理,所有的c-support Tlist 等功能则会失效。我的做法是使用vim 编辑一个".c"的文件,当需要编译的时候,通过一个批处理文件(.bat),将".c"文件复制成".ino"文件。 再用Arduino编辑器打开".ino"文件,然后Ctrl + R开始编译,Ctrl + U将程序下载到板卡上。习惯了以后还是蛮方便的。
 
四 程序逻辑介绍
 
     1、初始化
                      initRTC();             /* Initial RTC. */
                  LCDA.Initialise();     /* Initial screen. */
            
 
     2、设置日期和时间
          日期和时间的设置是通过串口来实现的。例如设置 2014-03-18 12:10:00  在Arduino编辑器的串口输入框中分别输入字符串: "sd 2014-03-18" 和 "st 12:10:00" 程序会解析这两个字符串,并分别设置DS1307的日期和时间。要完成这个功能,首先是要收集串口传送的字符串,然后通过'\n'和'r'来判断输入的结束。这里需要注意的是需要将Arduino串口设置成"Both NL & CR" 模式,采用默认模式无法获得'\n'和'\r' 。 如下图:


 
     代码如下:
  
  /* Handle command from serial. */
     if ( Serial.available() )
     {
          char c;
          c = Serial.read();
          if ( bufferSerialCount >= BUFFER_LENGTH-1 || c == '\n' || c == '\r'  )
          {
               executeCommand();
               bufferSerialCount = 0;
               bufferSerial[bufferSerialCount] = '\0';
          }
          else
          {
               bufferSerial[bufferSerialCount] = c;
               bufferSerialCount++;
               bufferSerial[bufferSerialCount] = '\0';
          }
     }
  
     解析设置字符串和设置时间:
 
/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  executeCommand
 *  Description:  Parse command, then set data and time.  
 *  			  "sd" means set data. format: "sd 2014-03-18" 
 *  			  "st" means set time. format: "st 12:10:00"
 * =====================================================================================
 */
int executeCommand(void)
{
	String inputString;
	String commandString;
	String year, month, day;
	String hour, minute, second;

	int indexOfcommand;

	Serial.println(bufferSerial);
	inputString = String(bufferSerial);

	if(inputString.length() < 1)
	{
		return 0;	
	}

	/* Get command */
	indexOfcommand = inputString.indexOf(' ');
	commandString = inputString.substring(0, indexOfcommand);

	if(commandString == "sd") // Set date
	{
		year = inputString.substring(indexOfcommand + 1, indexOfcommand + 5);	

		if(inputString.charAt(indexOfcommand + 5) != '-')
		{
			Serial.println(year);
			Serial.println("error: Get year.");
			return 0;	
		}

		month = inputString.substring(indexOfcommand + 6, indexOfcommand + 8);	

		if(inputString.charAt(indexOfcommand + 8) != '-')
		{
			Serial.println(month);
			Serial.println("error: Get month.");
			return 0;	
		}

		day = inputString.substring(indexOfcommand + 9, indexOfcommand + 11);	

		Serial.println(year + "-" + month + "-" + day);

		RTC.stopClock();
		RTC.fillByYMD((uint16_t)year.toInt(), (uint8_t)month.toInt(), (uint8_t)day.toInt());

		RTC.setTime();
		RTC.startClock();
		return 1;		
	}
	else if(commandString == "st") // Set time
	{

		hour = inputString.substring(indexOfcommand + 1, indexOfcommand + 3);	

		if(inputString.charAt(indexOfcommand + 3) != ':')
		{
			Serial.println(hour);
			Serial.println("error: Get hour.");
			return 0;	
		}

		minute = inputString.substring(indexOfcommand + 4, indexOfcommand + 6);	

		if(inputString.charAt(indexOfcommand + 6) != ':')
		{
			Serial.println(minute);
			Serial.println("error: Get minute.");
			return 0;	
		}

		second = inputString.substring(indexOfcommand + 7, indexOfcommand + 9);	

		Serial.println(hour + ":" + minute + ":" + second);

		RTC.stopClock();
		RTC.fillByHMS((uint8_t)hour.toInt(), (uint8_t)minute.toInt(), (uint8_t)second.toInt());

		RTC.setTime();
		RTC.startClock();

		return 1;		
	}
	else if(commandString == "i") // Display currently date and time 
	{
		displayInfo();
	}
	else if(commandString == "h")
	{
		displayHelpInfo();
	}

	Serial.print("Command is error, please check it!");
	return 0;
}
 
     3、显示内容
 
               (1)数据结构:    
               
                    注意一下ScreenProperty 成员中,关联了DisplayContent。                
 typedef struct DisplayContent
{

     int                  row;
     int                  column;
     unsigned char*       content;
     int                      contentLength;

}DisplayContent;

typedef struct ScreenProperty
{
     int                     type;    // type: 1: picture; 2: time and word
     DisplayContent*     content;    
     int                     contentCount;
     int                     timerChange;
}ScreenProperty;

typedef struct MemorialDay
{
     unsigned char*  content;
     int                 contentLength;
     char*               date;
}MemorialDay;

 
 
               (2)分屏显示:
                    分屏显示控制是由一个全局变量currentScreenNo和switch-case 在loop()中实现的。每屏显示时间的长短是由timerChangeScreen来控制的,这个变量是由每屏幕属性的timerChange来赋值的。
 
               (3)显示一屏内容:
                    通过遍历以下数据结构定义,来显示单独一屏幕中的内容:
 
MemorialDay memorialDayList[MEMORIALDAY_COUNT] =
{
     {wordGrandFather, AR_SIZE(wordGrandFather), "1945-02-04"},
     {wordMonther, AR_SIZE(wordGrandMonther), "1982-03-21"},
     {wordDavidBirthday, AR_SIZE(wordDavidBirthday), "2008-04-14"},
     {wordGrandMonther, AR_SIZE(wordGrandMonther), "1945-06-21"},    
     {wordGrandMonther2, AR_SIZE(wordGrandMonther2), "1953-08-04"},    
     {wordFather, AR_SIZE(wordFather), "1976-09-10"},
     {wordGrandFather2, AR_SIZE(wordGrandFather2), "1953-09-27"}
};

DisplayContent contentScreen1[] =
{
     {1, 0, wordRedRed, AR_SIZE(wordRedRed)},
     {2, 0, wordMemorialDayRecord, AR_SIZE(wordMemorialDayRecord)}
};


DisplayContent contentScreen2[] =
{
     {0, 0, (unsigned char*)dateCharString, AR_SIZE(dateCharString)},
     {0, 6, (unsigned char*)weekCharString, AR_SIZE(weekCharString)},
     {1, 2, (unsigned char*)timeCharString, AR_SIZE(timeCharString)},
     {2, 0, wordNextMemorialDay, AR_SIZE(wordNextMemorialDay)},
     {3, 3, wordResidue, AR_SIZE(wordResidue)},
     {3, 7, wordDay, AR_SIZE(wordDay)}
};

DisplayContent contentScreen3[] =
{
     {0, 0, memorialDayList[2].content, memorialDayList[2].contentLength},
     {0, 5, (unsigned char*)(memorialDayList[2].date + 5), 5},
     {1, 0, memorialDayList[3].content, memorialDayList[3].contentLength},
     {1, 5, (unsigned char*)(memorialDayList[3].date + 5), 5},
     {2, 0, memorialDayList[4].content, memorialDayList[4].contentLength},
:tabf D:\1_WorkSpace\2_Develop\10_Arduino_Project\MemorialDay\MemorialDay\


     {2, 5, (unsigned char*)(memorialDayList[4].date + 5), 5},
     {3, 0, memorialDayList[5].content, memorialDayList[5].contentLength},
     {3, 5, (unsigned char*)(memorialDayList[5].date + 5), 5}
    
};

ScreenProperty screenList[] =
{
     {1, contentScreen1, 2, 5},
     {2, contentScreen2, 6, 5},
     {2, contentScreen3, 8, 5}

};
  
     4、计算纪念日的剩余时间
          (1)通过函数getNextMemorialDay()获得离当前日期最近的纪念日日期。
          (2)通过函数calculateDay()来计算倒计时剩余天数。这里考虑了同年,跨年,闰年等情况。
               
 
     5、控制屏幕背光
          控制背光的关键问题,是延时读取人体传感器。如果实时读取,屏幕会异常闪烁。虽然传感器具有延时功能,但是我还是在程序中处理的延时。
 
 /* Detecting HC_SR501 and close or open back light.  */
     if(backLightDelay != LIGHTDELAY)
     {
          backLightDelay--;
          if(backLightDelay == 0)
          {
               backLightDelay = LIGHTDELAY;     
          }       
     }
     else if(digitalRead(pin_HCSR501) && backLightDelay == LIGHTDELAY)  // It is 1 about having somebody. It is 0 about nobody. 
     {
          digitalWrite(pin_Lcd12864BackLight, HIGH);
          backLightDelay--;     
     }

     else if( !digitalRead(pin_HCSR501) && backLightDelay == LIGHTDELAY )     
     {
          digitalWrite(pin_Lcd12864BackLight, LOW);     
     }
  
五 制作过程中遇到的问题
     1、Arduino 主控板的选择:
          我除了使用Mega 2650外,还使用过其他两块主控板。分别是Arduino Leonardo 和Arduino nano。 Leonardo的主要问题是:DS1307和LCD12864连接到此板后,不能同时工作,每个外设单独连接时是没有问题的,目前没有找到是什么原因。 Nano的主要问题是SRAM太小了,只有2K, 而我的程序中一个logo图片就需要1.006K,程序其他部分还需要1.2K左右的SRAM,这样就超出2K的限制。我曾经尝试将图片数据保存到flash memory 和 eeprom中,但都没有成功。
 
     2、LCD12864:
          这个显示屏的出厂设置是不能够直接用在Arduino上的,需要将标签为R9(0欧姆)的一个电阻去掉,否则连接上Arduino主控板就会短路。这个问题困扰了我一段时间。
 
          期初,我选择屏幕时,采用的是诺基亚Nokia5110,这块屏幕是昔日的经典,但是目前都是拆卸或翻新的,最大的问题是残影。通过软件调节屏幕的亮度可以缓解,但是稳定性不好。我测试的那块屏,上电后显示的非常暗淡,过了几分钟后才逐渐显示清晰。最终放弃了使用这个屏幕。
          
     3、DS1307时钟模块
          这个模块的制作工艺比较低劣,问题主要有如下几个:
          (1)备用电池电压低于3V,时钟模块不能正常工作。
          (2)晶振质量不过关,导致时间不准确,过快或过慢。
 
     4、人体感应器的输入电压是5V, 输出电压是3.3V,曾经误认为输入的工作电压是3.3V,导致感应器不能稳定工作。 此外这个模块的探头有所谓的进口和国产之分,进口的探头窗口略大些,感应的效果比所谓国产的要好一些。
 
     在此感叹一下,仅仅是DIY如此简单的一个东西,却遇到了这么多的困难和问题。如果是开发一个产品将会如何呀?
 
 
六 一点点体会
 
          制作这个小东西是需要一些基本知识和技能的。我总结了以下几点供喜欢DIY的朋友参考:
 
     1、较好的英文阅读能力。Arduino目前没有什么好的中文资料,我所使用的资料都是英文的。
     2、能够科学上网。习惯从Github 和 Strack Overfolow等国外网站查找资料。
     3、具有一定的单片机知识,理解I2C、SPI和UART等通讯协议。
     4、具有较好的C C++语言基础。
     5、具有较好的动手能力。
     6、克服困难的决心和毅力
 
     呵呵。。。怎么有点像是招聘广告呀! 算了,就算是广告吧, 如果哪位兄弟需要人手或是合作伙伴,可以联系我:wangyuxxx@163.com  wangyuxxx@gmail.com
 
 
 
 
                   
 
 
 
  • 大小: 588.9 KB
  • 大小: 139.2 KB
  • 大小: 115.1 KB
  • 大小: 281.2 KB
  • 大小: 40.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics