如何为MAXQ610微控制器增加DS1904实时时钟
Abstract
MAXQ610微控制器不具备电池供电的实时时钟(RTC),但是利用1-Wire®网络的灵活性,可方便地为任何基于MAXQ610的应用直接增加一个DS1904 RTC iButton®。MAXQ610能够与DS1904通信、设定时钟和控制,并且能够相互转换时间值与原始的秒计数值,即使采用汇编语言也能支持这些功能。本应用笔记介绍如何为基于MAXQ610的应用增加RTC功能,提供了实现这一功能的例程。本文介绍的原理和方法同样适用于其它基于MAXQ®、具有1-Wire驱动通信协议的通用I/O (GPIO)的微控制器。
本应用笔记正在准备公开,您的申请将自动批准,您会收到该篇应用笔记。
概述
MAXQ610是一款灵活的低功耗微控制器,非常适合便携应用。但是,MAXQ610并未集成电池供电的实时时钟(RTC)。
本应用笔记介绍如何在基于MAXQ610的应用中增加电池供电的DS1904 RTC iButton。应用笔记中的例程采用基于汇编的MAX-IDE环境为MAXQ610编写,设计运行于MAXQ610-KIT评估(EV)板。演示应用的源代码、工程文件以及其它文档可从网站下载。
尽管本应用笔记讨论的程序代码专门针对MAXQ610微控制器编写,但本文讨论的原理和方法也能很好地适用于其它基于MAXQ、具有1-Wire驱动通信协议通用I/O (GPIO)的微控制器。
关于Maxim提供的iButton及其它1-Wire器件的更多信息,请参考iButton产品及1-Wire产品网站。
硬件和软件要求
运行演示程序需要以下硬件支持。
- MAXQ610-KIT评估板
- 5V直流电源
- 串行至JTAG或USB至JTAG接口板
- JTAG编程电缆(2 × 5扁平电缆,带0.100in引脚连接器)
- DB9直通串口电缆
- PC机提供空闲的COM口(或USB至串口转换器)
- DS1904L-F5# RTC iButton
- DS9094F+通孔装配iButton夹
- 将iButton夹的GROUND引脚(夹子顶端引脚位置标有“+”,连接DS1904的背面/无标签面)连接到MAXQ610评估板的一个GND测试点。
- 将iButton夹子的DATA引脚(夹子的内侧引脚,连接DS1904的前面/标签面)连接到MAXQ610评估板的端口引脚P2.0 (插头引脚P3.1)。
- 用于MAXQ的MAX-IDE汇编语言开发环境
- Microcontroller Tool Kit (MTK)或其它具有“哑终端”模式的终端仿真器
DS1904特性
DS1904是一款坚固的RTC iButton模块,占用最少的硬件资源。数据采用1-Wire协议串行传输,仅需1根数据线和1根地回路。DS1904具有唯一的64位工厂激光刻制ROM ID,包含被作为二进制计数器的RTC/日历。坚固耐用的MicroCan封装对恶劣环境具有极高的耐受能力,例如灰尘、潮湿和振动。DS1904借助附件几乎可以安装在任何物体表面,包括:印制电路板(PCB)和塑料钥匙扣。DS1904能够为任何基于微控制器的电子装置或嵌入式应用添加日历、时间和日期标签、秒表、计时表、计时器及日志功能。
DS1904 RTC由一个分辨率为1s的32位计数器组成,可提供大约136年的计时日历。维持时钟运行的所有硬件(包括32kHz晶振和电池)都密封在F5 MicroCan封装内,DS1904能够支持10年以上的工作时间。室温(+25°C)下,时钟精度大约为每月±2分钟。工作模式(暂停或运行状态)及时钟计数值通过1-Wire接口进行读写。这意味着仅需占用MAXQ610的1个GPIO引脚即可与DS1904进行通信。
设计目标
本例程设计的主要目标是利用DS1904的1-Wire接口进行以下操作。
- 读取DS1904的64位ROM ID。
- 启动和停止RTC。
- 读取RTC的当前值。
- 将RTC设定为一个新值。
与其它任何保存原始秒计数值的时钟设计相同,必须选择一个“年份”基线,将原始的秒计数值转换成年/月/日/时间格式。本应用中,所选基线为2000年1月1日12:00:00 am,为原始秒计数值为00000000h时对应的日期/时间。
1-Wire网络驱动
Maxim已经开发了各种1-Wire接口传感器等元件。该接口利用一根数据线和地线进行供电和传输数据,意味着微控制器可通过单个端口与1-Wire传感器通信。
1-Wire工作时采用一个主控制器和多个从器件架构,即多点配置。定时要求非常灵活,允许所有从器件以高达16kbps的通信速率与主控制器同步工作。每个1-Wire传感器都有一个全球唯一的64位ROM ID,所以,1-Wire主控制器可独立、准确地选择从器件,而与网络的物理位置无关。
1-Wire数据线工作在开漏模式:主控制器(以及从器件,请求它们输出数据时)将数据拉低至地电位表示“0”,数据线浮空在高电平表示“1”。通常,这种工作方式采用安装在数据线和VCC之间的分立上拉电阻实现。但在使用MAXQ610等端口引脚具有弱上拉的微控制器时,仅需将端口引脚切换至该模式并使数据线浮空在高电平即可,无需外部上拉电阻。由于主控制器和从器件只用于拉低数据线,从来不会主动将数据线拉高,所以,1-Wire网络工作在“线或”配置下。这种方法可防止多个从器件试图同时在1-Wire总线上传输数据时发生冲突。
为了驱动1-Wire网络,MAXQ610必须在软件控制下通过单个引脚产生下列时隙。由于所有时隙均由1-Wire主控制器启动,MAXQ610不与从器件通信时无需监测1-Wire数据线。关于1-Wire通信的定时要求,请参考DS1904的数据资料。
- 复位时隙宽度约为1ms,该时隙的前半部分,主控制器(也就是MAXQ610)将1-Wire线保持在低电平。经过一半时隙后,主控制器释放1-Wire总线,使其悬浮空在高电平。总线出现的任何1-Wire从器件都通过自身复位并在时隙的后半部分拉低总线进行响应。从器件产生一个应答脉冲,向主控制器表示线上存在一个或多个1-Wire从器件,并已准备就绪进行通信。
- 写时隙的长度大约为60µs至120µs,由主控制器向一个或多个1-Wire从器件发送“0”或“1”。两种写时隙都由主控制器在开始时将数据线拉低至少一个微秒。若发送“1”,主控制器则在时隙的剩余时间内释放1-Wire总线(即悬浮在高电平);若传输“0”,主控制器则继续将数据线保持在低电平,直到时隙结束。
- 读时隙长度大约为60µs至120µs,由主控制器从某个从器件读取“0”或“1”。时隙开始时,由主控制器将数据线拉低至少一个微秒。然后主控制器释放数据线,使从器件能够拉低数据线(表示“0”)或使数据线悬浮在高电平(表示“1”)。时隙期间,主控制器对数据线进行采样,读取从器件的数据位。
#define OWIN M0[0Ah].0 ; PI2.0 #define OWOUT M0[02h].0 ; PO2.0 #define OWDIR M0[12h].0 ; PD2.0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function : Reset1Wire ;; Description : Sends a standard-speed 1-Wire reset pulse ;; and checks for a presence pulse reply. ;; Inputs : None ;; Outputs : C - Cleared on success; set on error (no presence ;; pulse detected) ;; Destroys : PSF, LC[0] ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reset1Wire: move OWDIR, #1 ; Output mode move OWOUT, #0 ; Drive low move LC[0], #RESET_LOW djnz LC[0], $ move OWOUT, #1 ; Snap high move LC[0], #SNAP djnz LC[0], $ move OWDIR, #0 ; Change to weak pullup input move LC[0], #RESET_PRESAMPLE djnz LC[0], $ move C, OWIN ; Check for presence detect move LC[0], #RESET_POSTSAMPLE djnz LC[0], $ ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Function : Write1Wire ;; Description : Writes a standard-speed 1-Wire output byte. ;; Inputs : GRL - Byte to write to 1-Wire. ;; Outputs : None. ;; Destroys : PSF, AP, APC, A[0], LC[0], LC[1] ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Write1Wire: move APC, #080h ; Standard mode, select A[0] as Acc move Acc, GRL move OWDIR, #1 ; Output drive mode move LC[1], #8 ; 8 bits to write Write1Wire_slot: move OWOUT, #0 ; Drive low for start of write slot move LC[0], #WRITE_PREBIT djnz LC[0], $ rrc ; Get the next bit jump C, Write1Wire_one Write1Wire_zero: move OWOUT, #0 ; Keep the line low (zero bit) jump Write1Wire_next Write1Wire_one: move OWOUT, #1 Write1Wire_next: move LC[0], #WRITE_POSTBIT djnz LC[0], $ ; Finish the time slot move OWOUT, #1 ; Drive back high (end of slot) move LC[0], #WRITE_RECOVERY djnz LC[0], $ ; Recovery time period djnz LC[1], Write1Wire_slot ret发送读时隙的功能与此类似。注意,1-Wire总线的所有数据字节均为最低有效位(LSB)在前。
另外,还需要注意利用MAXQ610实现1-Wire总线时隙的特殊性。根据1-Wire网络器件数量的不同,1-Wire总线的上拉电阻会发生变化,典型值大约为4kΩ至5kΩ。而MAXQ610端口引脚的弱上拉电阻接近于15kΩ至40kΩ,取决于工作电压。为避免
启动、停止和设置时钟
在与1-Wire总线器件进行通信时,数据传输涉及两个独立阶段。因为总线上可能挂接了不止一个1-Wire器件,需要通过两个过程执行操作。在第一阶段,总线主控制器选择需要通信的1-Wire器件;在第二阶段,总线主控制器与该器件通信。
关于下列命令的详细信息,请参考DS1904数据资料。
选择1-Wire从器件
一旦总线主控制器发送1-Wire复位脉冲,1-Wire总线上的所有从器件均复位至默认的未选中状态。在第一阶段,总线主控制器可利用几条命令选择在第二阶段需要读取或写入的1-Wire器件。所有1-Wire器件都支持这组与每个从器件64位ROM ID相关的命令集。
Skip ROM [CCh]
该单字节命令用于激活总线上的所有从器件。如果总线上只有一个1-Wire器件,或总线主控制器需要向总线上所有1-Wire器件发送相同命令,即可采用该ROM命令快速激活主控制器需要通信的器件。
由于示例中在总线上只有一个器件(DS1904),所以MAXQ610在绝大多数情况下都可使用该命令激活DS1904,进行后续的读、写操作。
Read ROM [33h]
该单字节命令用于激活总线上的所有从器件,并使其发送64位ROM ID至总线的主控制器。由于该命令激活全部从器件,只有当总线上仅有单个器件时才可使用。若在不止一个从器件的总线上使用该命令,不同器件都将尝试把各自的ROM ID发送至总线主控制器,将导致数据冲突。
由于示例中只在总线上挂接了一个从器件(DS1904),MAXQ610可以在示例开始利用该命令读取DS1904的ROM ID。
Match ROM [55h]
该命令用于从1-Wire总线的多个从器件中选择一个从器件。总线主控制器发送该命令后,紧接着发送需要选择的从器件的64位ROM ID。ROM ID与之相匹配的从器件将进入开启状态,响应总线主控制器,而总线上的其它从器件则保持禁止状态,并等待总线主控制器的下一次1-Wire复位。
该示例没有使用该命令。
Search ROM [F0h]
总线主控制器通过该命令采用迭代查找算法确定1-Wire总线上一个或多个从器件的ROM ID,详细信息请参考DS1904数据资料。
本示例中没有使用该命令。
DS1904时钟和控制的读、写操作
一旦总线主控制器(MAXQ610)通过Skip ROM命令或Read ROM命令选择了1-Wire从器件(DS1904),DS1904即准备就绪接收发给自己的1-Wire命令。以下详细介绍了这些命令,参见图1。
图1. DS1904的时钟功能命令(摘自DS1904数据资料)
Read Clock [66h]
总线主控制器利用该命令从DS1904读取器件控制字节及4字节(32位) RTC数值。器件控制字节决定了32kHz振荡器(驱动RTC)处于运行状态还是停止状态。
如以下代码所示,只用一条命令读取器件控制字节和时钟值。即使不需要两个数值,也必须在器件输出时钟数据之前先读取器件的控制字节。
ds1904_get_control: push Acc call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_READ_CLOCK call Write1Wire call Read1Wire ; Device control byte move A[2], Acc call Reset1Wire pop Acc ret ds1904_get_clock: push Acc push GR call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_READ_CLOCK call Write1Wire call Read1Wire ; Device control byte (ignore) call Read1Wire ; RTC[7:0] move GRL, Acc call Read1Wire ; RTC[15:8] move GRH, Acc move A[2], GR call Read1Wire ; RTC[23:16] move GRL, Acc call Read1Wire ; RTC[31:24] move GRH, Acc move A[3], GR call Reset1Wire pop GR pop Acc ret
Write Clock [99h]
该命令是读时钟命令的补充,它使总线主控制器能够将DS1904的器件控制字节和4字节时钟计数器设定到一个新的数值。注意,只有当5个字节全部写入并且发送一个1-Wire复位脉冲之后,新的数值才生效。
该示例提供了一个分别设置器件控制字节和时钟的例程。代码首先从DS1904读取5个字节(1个器件控制字节和4字节时钟计数器)的数据,然后进行适当改变后再将数据写回器件。
ds1904_set_clock: push Acc push GR call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_READ_CLOCK call Write1Wire call Read1Wire ; Device control byte (save) move GR, Acc call Read1Wire call Read1Wire call Read1Wire call Read1Wire call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_WRITE_CLOCK call Write1Wire move Acc, GR ; Device control byte call Write1Wire move GR, A[2] move Acc, GRL call Write1Wire ; New clock LSB move Acc, GRH call Write1Wire move GR, A[3] move Acc, GRL call Write1Wire move Acc, GRH call Write1Wire ; New clock MSB call Reset1Wire pop GR pop Acc ret ds1904_set_control: push Acc push GR push A[3] push A[4] call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_READ_CLOCK call Write1Wire call Read1Wire ; Device control byte call Read1Wire ; Clock LSB move GRL, Acc call Read1Wire move GRH, Acc move A[3], GR call Read1Wire move GRL, Acc call Read1Wire move GRH, Acc ; Clock MSB move A[4], GR call Reset1Wire move Acc, #OW_SKIP_ROM call Write1Wire move Acc, #OW_WRITE_CLOCK call Write1Wire move Acc, A[2] ; New device control byte call Write1Wire move GR, A[3] move Acc, GRL call Write1Wire ; New clock LSB move Acc, GRH call Write1Wire move GR, A[4] move Acc, GRL call Write1Wire move Acc, GRH call Write1Wire ; New clock MSB call Reset1Wire pop A[4] pop A[3] pop GR pop Acc ret
转换时间和日期值
为了把原始的秒计数值转换成打印格式,需要应用程序分别确定每个日期和时间字段的数值(例如年、月、日、时、分和秒)。通常,实现这一功能的程序首先从最大字段(年)开始,然后逐步向下转换。
- 当秒计数值≥ (每年秒数)时,从秒计数值中减去(每年秒数)并递增年份。
- 当秒计数值≥ (每月秒数)时,从秒计数值中减去(每月秒数)并递增月份。
- 当秒计数值≥ (每天秒数)时,从秒计数值中减去(每天秒数)并递增日期。
- 当秒计数值≥ (每小时秒数)时,从秒计数值中减去(每小时秒数)并递增小时值。
- 当秒计数值≥ 60时,从秒数中减去60并递增分钟值。
- 剩下的秒计数值即为秒字段数值。
例如,从2000年开始(该年为闰年),我们计算如下:
(2000年一年的秒数) = 366 (天数) × 24 (小时/日) × 60 (分钟/小时) × 60 (秒/分钟) = 31,622,400秒而对于标准年份,一年的天数减少一天(365天),这就意味着(秒数/年)变为:(31,622,400 - 86,400) = 31,536,000。由于每4年有一个闰年,所以我们可计算年份如下。
- 如果秒计数值≥ (闰年秒数),从秒计数值中减去(每个闰年的秒数)并递增年份,否则停止。
- 如果秒计数值≥ (每年秒数),从秒计数值中减去(每年秒数)并递增年份,否则停止。
- 如果秒计数值≥ (每年秒数),从秒计数值中减去(每年秒数)并递增年份,否则停止。
- 如果秒计数值≥ (每年秒数),从秒计数值中减去(每年秒数)并递增年份,否则停止。
- 返回至第1行。
- 如果秒计数值≥ (一月秒数),从秒计数值中减去(一月秒数)并递增月份,否则停止。
- 如果秒计数值≥ (二月秒数),从秒计数值中减去(二月秒数)并递增月份,否则停止。
- 如果秒计数值≥ (三月秒数),从秒计数值中减去(三月秒数)并递增月份,否则停止。
- 依次类推。
;; A[6] - Number of days in current month ;; A[7] - Leap-year flag (leap year if 1, regular year if 0) ;; A[8] - RTC value (low 16 bits) ;; A[9] - RTC value (high 16 bits) ;; A[10] - Seconds (00-59) ;; A[11] - Minutes (00-59) ;; A[12] - Hours (00-23) ;; A[13] - Days (01-31, depending on month) ;; A[14] - Months (01-12) ;; A[15] - Years (00-99) convToFields: push A[8] push A[9] call zeroFields convToFields_year: move A[3], #SEC_IN_LEAPYEAR_H move A[2], #SEC_IN_LEAPYEAR_L call Sub32 jump C, convToFields_month call incYear move A[3], #SEC_IN_YEAR_H move A[2], #SEC_IN_YEAR_L call Sub32 jump C, convToFields_month call incYear move A[3], #SEC_IN_YEAR_H move A[2], #SEC_IN_YEAR_L call Sub32 jump C, convToFields_month call incYear move A[3], #SEC_IN_YEAR_H move A[2], #SEC_IN_YEAR_L call Sub32 jump C, convToFields_month call incYear jump convToFields_year convToFields_month: move A[3], #SEC_IN_MON_31_H ; >Jan move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_28_H ; >Feb move A[2], #SEC_IN_MON_28_L move Acc, A[7] jump Z, convToFields_month_noLeap move A[3], #SEC_IN_MON_29_H ; >Feb move A[2], #SEC_IN_MON_29_L convToFields_month_noLeap: call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_31_H ; >Mar move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_30_H ; >Apr move A[2], #SEC_IN_MON_30_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_31_H ; >May move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_30_H ; >Jun move A[2], #SEC_IN_MON_30_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_31_H ; >Jul move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_31_H ; >Aug move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_30_H ; >Sep move A[2], #SEC_IN_MON_30_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_31_H ; >Oct move A[2], #SEC_IN_MON_31_L call Sub32 jump C, convToFields_day call incMonth move A[3], #SEC_IN_MON_30_H ; >Nov move A[2], #SEC_IN_MON_30_L call Sub32 jump C, convToFields_day call incMonth convToFields_day: move A[3], #SEC_IN_DAY_H move A[2], #SEC_IN_DAY_L call Sub32 jump C, convToFields_hour call incDay jump convToFields_day convToFields_hour: move A[3], #SEC_IN_HOUR_H move A[2], #SEC_IN_HOUR_L call Sub32 jump C, convToFields_minute call incHour jump convToFields_hour convToFields_minute: move A[3], #0 move A[2], #60 call Sub32 jump C, convToFields_second call incMinute jump convToFields_minute convToFields_second: move A[10], A[8] pop A[9] pop A[8] ret示例应用程序按照类似的方式将字段转换成秒数。此时,代码将每个字段累计值增加至秒数(而不是减)。
运行示例
运行例程时,请加载并运行应用程序。然后利用DB9串行电缆将MAXQ610评估板的J1 SKT连接至PC的COM1。启动MTK (或其它终端仿真器)并以38400波特率打开COM1。例程的初始化输出类似如下:
@ ID: 24B91231000000B2 AC 18F83065 + 18F83065 Apr 10 2013, 02:15:01 pm + 18F83066 Apr 10 2013, 02:15:02 pm + 18F83067 Apr 10 2013, 02:15:03 pm + 18F83068 Apr 10 2013, 02:15:04 pm + 18F83069 Apr 10 2013, 02:15:05 pm代码的第二行含有DS1904的ROM ID (24B91231000000B2)、器件控制字节(AC)和当前的时钟(18F83065)。在随后几行中,“+”表示时钟正在运行。时间在发生变化后即进行刷新并显示,应该为每秒一次。
按下“-”停止时钟。此时,即可通过按下其它键修改时钟值,如下所示。
+ | 再次启动时钟并开始自动更新。 |
Y | 增加年份(将月份和日期复位为01/01)。 |
M | 增加月份(将日期复位为01)。 |
D | 增加日期(根据当前的月份循环)。 |
h | 增加小时。 |
m | 增加分钟。 |
s | 将秒计数值复位为00。 |
Z | 将计数器归零,将时间复位至2001年1月1日12:00:00 am。 |
结论
尽管MAXQ610不带电池供电的RTC,但是利用1-Wire网络的灵活性,可采用DS1904 RTC iButton方便地为任何基于MAXQ610的应用增加一个RTC。MAXQ610能够与DS1904通信、设定时钟和控制,并且可以转换时间和原始的秒计数值,使用汇编语言即可实现。
参考电路
1 Refer to the DS1904 RTC data sheet for more details on 1-Wire timing requirements.
2 For more details on the following commands, please refer to the DS1904 data sheet.
3 Ibid., https://www.analog.com/en/products/ds1904.html