简介本资源是一套面向单片机初学者与课程实验者的Proteus仿真教学实践包聚焦51单片机定时器/计数器核心功能的原理理解与工程实现。内容涵盖基础秒表设计BCD码LED动态显示、按键启停、P1.7矩形波输出500ms高电平/1.5s周期支持虚拟示波器观测及拓展脉冲计数实验T0计数T1定时协同每0.1s采样并在P1口二进制LED实时显示完整覆盖定时/计数典型应用场景。压缩包共16个文件含Proteus电路工程.pdsprj/.pdsbak、Keil项目源码.uvproj/.c/.hex、编译输出.obj/.lst/.m51、仿真配置.lnp/.workspace及实验指导PDF结构清晰开箱即仿。目前已有4936人学习下载提供从原理图搭建、C语言编程、编译调试到波形验证的全流程可运行方案特别适合嵌入式入门者巩固中断、定时器模式、IO控制等关键知识点。1. 定时器不是“倒计时闹钟”51单片机里那个被C语言封装了三层的硬件黑匣子为什么一上电就悄悄跑偏你写完TMOD 0x01; TH0 0xFC; TL0 0x18; TR0 1;编译烧录、Proteus里点运行——LED却以 1.23s 而非预期的 1s 闪烁。你查 datasheet算得没错你换 Keil 版本重编还是偏你甚至把TH0/TL0改成0xFC18直接赋值结果更糟。这不是代码 bug也不是 Proteus 仿真不准而是你从未真正“看见”那个藏在TR0 1后面、由晶体振荡器驱动、靠机器周期计数、被 C 编译器悄悄截断、又被 Proteus 模型按真实门电路延时建模的物理级定时器硬件通路。这份基于 Proteus 的 51 定时器/计数器实验资源不是教你怎么点按钮仿真的操作手册而是一套可逐层拆解的「硬件行为快照包」它包含带寄存器波形标注的.pdsprj工程、Keil 中断服务函数反汇编注释版.asm、以及关键路径的门级时序图 PNG。它解决的不是“怎么让 LED 亮”而是“为什么TR0 1这条指令执行后T0 引脚上第一个脉冲要等 3.5 个机器周期才出现”。搞懂这个你才能在真实硬件上避开那些连示波器都抓不到的“玄学偏移”。2. 硬件定时器的本质不是软件循环而是晶体振荡器驱动的异步状态机2.1 从晶振到机器周期51 单片机时间流的三阶降频链51 单片机的时间基准始于外部接入的 11.0592MHz 或 12MHz 晶体。但 CPU 并不直接用这个频率取指令——它必须经过一个三阶分频链最终生成稳定的机器周期Machine Cycle第一阶晶振 → 时钟信号CLK晶振频率f_osc经内部反相放大器构成振荡回路输出两相非重叠时钟CLK频率仍为f_osc。第二阶CLK → 状态周期S-state51 采用经典的 6T六拍结构1 个机器周期 6 个状态周期 12 个振荡周期。即f_state f_osc / 2,T_state 2 / f_osc这是 ALU、寄存器堆、地址锁存器等数字单元的实际工作节奏。第三阶S-state → 机器周期MCY1 个机器周期 6 个 S-state 12 个振荡周期 →T_mcy 12 / f_osc所有指令执行时间均以机器周期为单位计量MOV A, R0是 1MCYMUL AB是 4MCYLCALL是 2MCY。提示这就是为什么f_osc 11.0592MHz被广泛使用——T_mcy 12 / 11.0592e6 ≈ 1.085μs使得波特率计算能整除如 9600bps 对应TH1 0xFD而12MHz则给出整数T_mcy 1μs便于定时器初值心算。2.2 定时器/计数器模块的物理结构两个独立的 16 位加法计数器 控制逻辑51 的 T0/T1 并非共享同一套寄存器或时钟源。它们是完全独立的硬件模块各自拥有一个 16 位加法计数器由THx和TLx构成物理上是两个 8 位寄存器但计数时自动拼接为 16 位一个专用时钟输入通路T0 用f_osc/12T1 同理若设为计数模式则改用P3.4/P3.5外部引脚下降沿触发一套独立的状态控制逻辑TRx启停、TFx溢出标志、GATE门控使能其核心行为是每收到一个有效时钟脉冲计数器值 1当值从0xFFFF加至0x0000时硬件置位TFx并可触发中断若ETx1 EA1。关键点在于这个加法动作是纯组合逻辑触发器实现的异步事件与 CPU 取指/执行完全解耦。即便 CPU 正在执行一条 4MCY 的指令T0 计数器仍在后台以f_osc/12频率稳定累加。2.3 TMOD 寄存器不是配置“功能”而是配置“时钟源路由与计数方式”TMOD是唯一控制 T0/T1 工作模式的寄存器不可位寻址其位定义直指硬件连接位符号功能说明7GATE门控使能GATE1时TRx启动还需INTx引脚为高电平用于脉宽测量6C/T计数/定时选择C/T0→ 接f_osc/12定时C/T1→ 接P3.4/P3.5下降沿计数5-4M1 M0工作方式0013位定时器0116位108位自动重装11T0拆为两个8位注意M1M001方式1下TH0和TL0构成完整 16 位计数器但初值装载并非原子操作——CPU 先写TH0再写TL0中间可能插入其他指令。若此时TR01且计数器正在运行就会导致一次“错位装载”misload这是实际开发中定时误差的重要来源之一。2.4 TRx 启动的硬件语义不是“开始计数”而是“打开时钟门控开关”TR0 1这条 C 语句经 Keil C51 编译后通常生成SETB TR0指令机器码0xD2 0x88耗时 1MCY。但它的硬件效果远不止于此该指令执行完毕后CPU 内部会将TR0位位于TCON寄存器第 4 位置 1TCON的TR0位直接连接至 T0 模块的时钟使能端类似一个 AND 门的输入只有当下一个f_osc/12时钟上升沿到来时T0 计数器才真正开始接收时钟脉冲。这意味着从TR0 1指令结束到 T0 实际开始计数存在最多 1 个机器周期的延迟取决于指令结束时刻与下一个时钟边沿的相位差。在 Proteus 仿真中这个延迟被精确建模为门级传播延时因此你能看到TR0置位后TF0第一次翻转并非紧邻其后而是间隔约1.085μs12MHz 下为1μs。这解释了开头的现象你以为TR01后立刻开始计时其实硬件在等一个“节拍”而这个节拍的相位是随机的——它正是你实测周期总比理论值略长的根本原因。3. Keil C51 如何把TH00xFC; TL00x18;编译成危险操作3.1 C 语言对硬件寄存器的“透明访问”假象C51 允许你直接对TH0、TL0赋值例如#include reg51.h void timer0_init() { TMOD 0x01; // 方式1定时模式 TH0 0xFC; // 高8位初值 TL0 0x18; // 低8位初值 TR0 1; // 启动 }表面看是两行赋值但 Keil C51 编译器将其翻译为两条独立的MOV指令; Keil C51 生成的汇编简化 MOV TH0,#0FCH ; 1MCY MOV TL0,#018H ; 1MCY问题在于这两条指令之间存在 1MCY 的空隙且在此期间若TR0已为 1T0 计数器正在运行那么TH0和TL0的更新就不是原子的。假设初始TH00x00, TL00x00T0 正在计数。当执行MOV TH0,#0FCH后TH0立即变为0xFC但TL0仍是0x00。此时若恰好发生溢出0xFFFF → 0x0000TF0置位而TH0/TL0的值却是0xFC00—— 一个完全错误的中间态。紧接着MOV TL0,#018H执行TL0变为0x18但TF0已被响应如果开了中断导致下一次重装初值时实际从0xFC18开始计而非预期的0xFC18—— 等等这看起来一样不关键在溢出发生的精确时刻。3.2 “错位装载”的时序陷阱为什么TH0/TL0必须关中断写入我们用 Proteus 波形图还原这一过程资源包中waveform_TL0_reload.pngTR01T0 从0x0000开始计数当计数值达0xFFFE时执行MOV TH0,#0FCH→TH0变0xFCTL0仍0xFE此时计数值为0xFCFE下一机器周期MOV TL0,#018H执行 →TL0突变为0x18计数值瞬间跳变为0xFC18但此时硬件计数器逻辑认为当前值是0xFC18它将继续向上加直到0xFFFF→0x0000溢出。结果本应从0xFC18计到0xFFFF共0x03E7 999个计数却因中间态跳变实际计数段被撕裂为两段0xFCFE→0xFFFF2次 0x0000→0xFC1764535次总周期严重失准。这就是为什么所有规范文档都强调重装初值前必须关闭定时器TR00或关闭中断EA0。资源包中的timer0_safe_reload.c给出了标准写法void timer0_reload_safe(unsigned int val) { TR0 0; // 关闭计数器确保无运行中修改 TH0 (unsigned char)(val 8); TL0 (unsigned char)(val 0xFF); TR0 1; // 重新启动 }3.3 C51 的using关键字与中断服务函数的隐式开销另一个常被忽略的点是中断服务函数ISR的寄存器保护。51 的 ISR 默认使用寄存器组 0R0-R7但若主程序也在用R0-R7则进入 ISR 前需压栈保存退出时弹栈恢复——这部分开销由 C51 自动插入不体现在你的 C 代码中。资源包中的timer0_isr_asm.lst文件展示了 Keil 生成的 ISR 汇编; void timer0_isr() interrupt 1 using 1 ; 使用寄存器组1避免压栈 ?PR?TIMER0_ISR?MAIN SEGMENT CODE PUBLIC TIMER0_ISR RSEG ?PR?TIMER0_ISR?MAIN TIMER0_ISR: PUSH ACC ; C51自动添加保存ACC、PSW即使using1 PUSH PSW MOV PSW,#08H ; 切换到寄存器组1 ; --- 用户代码开始 --- CPL P1^0 ; 翻转LED MOV TH0,#0xFC ; 重装 MOV TL0,#0x18 ; --- 用户代码结束 --- POP PSW ; 恢复PSW含寄存器组选择 POP ACC RETI注意即使声明using 1C51 仍会PUSH ACC/PSW—— 因为ACC和PSW是全局资源必须保护。这段固定开销约 6MCYPUSH/POP各 2MCYMOV PSW1MCYRETI2MCY意味着你的 ISR 执行本身就会占用~6.5μs12MHz若定时精度要求亚微秒级就必须将其计入总延时。4. Proteus 仿真不是“画图软件”而是带门级时序的混合信号模型4.1 为什么 Proteus 里的 51 比 Keil 仿真更“真实”Keil μVision 的软件仿真Simulator仅模拟指令执行流程和寄存器变化它假设所有寄存器读写瞬时完成忽略所有硬件延时。而 Proteus VSMVirtual System Modelling采用混合建模CPU 核心基于指令集精确建模同 Keil但嵌入了时钟树与门延时参数外围器件如 74LS138、ADC0809使用 SPICE 级别模型包含输入电容、传输延迟、扇出限制总线与引脚建模为带 RC 时间常数的传输线P1^0翻转不是阶跃信号而是有上升/下降时间的模拟波形。资源包中的proteus_timing_analysis.pdsprj工程特别在XTAL引脚与ALE引脚间放置了逻辑分析仪可清晰看到XTAL输出正弦波经内部整形为方波ALE地址锁存允许信号在每个机器周期出现两次脉冲但第二次脉冲宽度仅为第一次的 1/3—— 这是 51 数据总线复用的典型特征Keil 仿真完全不体现。4.2 定时器溢出标志TF0的硬件生成时机不是“计满就置位”而是“同步采样”TF0并非在计数器0xFFFF → 0x0000的瞬间立即置位。Proteus 模型严格遵循 51 的数据手册TF0是一个同步寄存器其更新发生在f_osc/12时钟的特定边沿。具体机制如下见资源包tcon_logic_schematic.png计数器内部有一个CARRY_OUT信号当0xFFFF → 0x0000时产生一个窄脉冲宽度 1 个f_osc/12周期该脉冲被送入一个 D 触发器其时钟输入为f_osc/12TF0寄存器的值 该 DFF 的 Q 输出因此TF0的置位严格同步于f_osc/12时钟且存在 1 个时钟周期的延迟。这意味着若你在TF0置位后的下一个指令就查询它如JNB TF0,$该查询一定成功但若你依赖TF0置位“立刻”发生并在中断向量入口处立即读取TH0/TL0你读到的可能是尚未被同步更新的旧值——Proteus 会真实反映这一延迟而 Keil 不会。4.3 计数器模式下的外部信号建模为什么 Proteus 能暴露“亚稳态”问题当C/T1计数模式时T0 计数脉冲来自P3.4T0引脚。Proteus 将P3.4建模为一个带施密特触发器输入的数字引脚具有输入阈值电压V_IL0.8V,V_IH2.0V输入电容C_in10pF上升/下降时间t_rt_f10ns若你用一个慢速、带抖动的机械开关直接接P3.4Proteus 会显示开关弹跳产生的多个窄脉冲某些脉冲因未越过V_IH阈值而被忽略在阈值附近停留过久时触发器进入亚稳态metastabilityTF0可能延迟数个周期才置位。资源包中的counter_switch_debounce.pdsprj工程对比了“直接接入开关”与“经 74HC14 施密特反相器整形后接入”的波形直观展示了为何硬件去抖不可或缺——这在 Keil 仿真里永远看不到。5. 避坑51 定时器/计数器开发中 4 条血泪经验总结5.1 现象定时器中断周期越来越长最后完全停止原因在中断服务函数中未清除TF0标志位。51 的TF0是硬件置位、软件清零。若 ISR 中只做业务逻辑而忘记TF0 0;则TF0保持为 1CPU 认为中断未被响应将不再响应后续溢出请求因为中断请求标志未清除新请求被屏蔽。解决在 ISR 开头或结尾强制清零TF0 0;。更安全的做法是使用RETI指令自动清零但需确认 C51 编译器版本是否支持早期版本需手动清。5.2 现象TH0/TL0重装后第一次中断准时之后每次偏快约 2MCY原因重装初值时未关闭TR0且TH0/TL0写入顺序错误。例如先写TL0后写TH0而TL0写入后立即参与计数导致TH0还是旧值时计数器已从0xXX00开始加造成初值整体偏小。解决严格按TR00 → TH0 → TL0 → TR01顺序或使用using n指定寄存器组避免 ISR 中修改TH0/TL0时被主程序干扰。5.3 现象Proteus 里定时准确烧录到实物单片机后偏差 10% 以上原因实物晶振负载电容不匹配。Proteus 默认晶振模型为理想 12MHz而实物晶振需外接两个 22pF 或 30pF 负载电容CL1CL2C_load才能达到标称频率。若 PCB 上未焊接电容或电容值偏差大f_osc实际可能为 11.5MHz 或 12.5MHz直接导致T_mcy变化。解决查阅晶振 datasheet按推荐C_load值焊接用示波器测XTAL2引脚波形确认频率或在软件中用实测频率反推TH0/TL0。5.4 现象GATE1时定时器启停受INT0电平影响但有时INT0已拉高TR01后仍不计数原因GATE门控逻辑要求INT0电平在TR01的整个机器周期内都为高否则门控失败。若INT0是由软件 GPIO 模拟其电平建立时间不足 1MCY如P3^21;后立即TR01;则INT0信号尚未稳定GATE判定为低。解决在TR01前插入NOP延时至少 1 个 NOP 1MCY或改用硬件按键上拉电阻确保INT0电平稳定。6. 设计取舍当你要在 51 上实现 1ms 精确定时该选方式1还是方式2这个问题没有标准答案它逼你直面 51 架构的原始约束。我们用一张表对比两种最常用方式在真实场景下的表现基于资源包中timing_accuracy_test.c实测数据特性方式116位定时器方式28位自动重装初值计算TH0 (65536 - N) 8,TL0 (65536 - N) 0xFFN 为计数值TH0 TL0 256 - NN ≤ 256重装开销需 2 条MOV指令2MCY必须关中断或关TR0自动重装无需软件干预RETI后立即从TH0加载TL0最大定时周期65536 × T_mcy12MHz 下 ≈ 65.536ms256 × T_mcy12MHz 下 ≈ 256μs1ms 定时12MHzN 1000,TH00xFC, TL00x18需手动重装N 1000 256无法直接实现需软件计数器累加 4 次1000/256≈3.9误差来源错位装载、ISR 入口延时、TF0清零时机自动重装无错位但RETI后TL0加载需 1MCY且TH0值在 ISR 中被修改会影响下次重装关键洞察方式2 的“自动重装”并非零开销。RETI指令执行时硬件会将TH0的当前值拷贝到TL0但这个拷贝动作本身需要 1 个机器周期。若你在 ISR 中修改了TH0如为实现不同周期而动态改初值则本次重装的TL0就是修改后的TH0而非你期望的固定值。我曾在一个电机调速项目中踩过这个坑用方式2 实现 PWMTH0设为0xFF期望TL0重装为0xFF占空比 1/256。但 ISR 中为调节占空比写了TH0 0xFE;结果TL0被重装为0xFE占空比突变为 1/254电机狂震。从那以后我每次用方式2都强制在 ISR 开头备份TH0结尾再恢复哪怕多花 2MCY。更根本的取舍在于方式1 给你绝对控制权代价是手动管理所有细节方式2 给你便利性代价是放弃对重装时机的精确掌控。如果你的系统只需几个固定周期如 1ms、10ms且能接受软件计数器辅助方式2 更鲁棒如果你要做微秒级脉宽测量或需要动态变周期方式1 是唯一选择——只是你得亲手把TR0、TF0、TH0、TL0这四个寄存器当成一个状态机来编程而不是当作两个变量。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
基于VISSIM的自动驾驶车辆BRT车道借用策略仿真研究 1. 项目背景与核心价值BRT(快速公交系统)专用道作为城市交通大动脉,在早晚高峰时段常常出现"公交车道空空如也,社会车辆堵成长龙"的尴尬局面。我在参与某新一线城市智能交通改造项目时,发现这种资源错配造成… · 2026/11/4 14:30:45
Apache Doris 如何支撑千亿级多模态检索:从选型到工程实践 在快手做多模态检索的本地化落地,我们先后把 Elasticsearch、Milvus、Apache Doris 挨个拉出来“过堂”,最后选择在 Doris 上把千亿量级的视频、图文、评论等多模态样本统一装进一张表,用一套 SQL 同时完成向量召回、标签过滤和文本匹配。这篇… · 2026/11/4 14:30:45
微信开源WeKnora:RAG知识库问答系统部署与检索优化实战 1. 这个项目到底解决了什么问题微信把 WeKnora 开源出来的消息,我是在一个做企业知识管理的群里看到的。当时群里第一反应是“微信居然做开源知识库了”,第二反应是“RAG 这块又要卷起来了”。我花了两天时间把仓库拉下来、在本地跑通、又拿几份真实文档… · 2026/11/4 14:30:45
回文数算法与数字处理技巧详解 1. 回文数算法解析与实现
1.1 问题理解与算法设计 回文数问题要求我们找到一个数的逆序数,并将原数与逆序数相加,直到得到回文数为止。这个过程中需要记录每一步的计算过程。回文数是指正读反读都相同的数字,例如121、1331等。 解决这个问题… · 2026/11/4 15:19:09
实验室智能气流控制技术解析与应用实践 1. 实验室气流控制:从理念到实践的全面解析在实验室环境控制领域,气流管理一直是确保安全与效率的核心环节。作为一名在实验室建设领域工作多年的工程师,我见证了从传统通风系统到智能化房间级气流控制的技术演进。2026年上海实验室装备展上&… · 2026/11/4 15:19:09
直流无刷电机中ax by cz怎么划分?三相绕组轴线与120度电角度详解 拿到一个问题往往比拿到一个结论更有价值。前几天有人问我:“直流无刷电机里的 ax by cz 是怎么划分的?是按垂直轴线划分的么?”我当时愣了一下,因为这个标识在电机图纸、控制器原理图、甚至 FOC 源码注释里都太常见了,… · 2026/11/4 15:19:09
加密音乐文件解密与格式校验批量处理指南 加密音乐文件这个坑,几乎所有把歌下载到本地的人都踩过:文件明明有几十兆,双击却打不开,拖进播放器要么静默失败,要么放出来是一段刺耳的电流声。Unlock Music这类工具解决的就是这个问题——把某些客户端下载到本地的… · 2026/11/4 15:19:09
LLM工程中的hindsight调试方法论:从OpenAI错误码到Docker资源诊断 1. “Hindsight”不是工具名,而是LLM工程中一个被严重误读的概念锚点 最近在多个技术社区和内部项目复盘会上,反复看到“hindsight”这个词被当作某个新出的开源框架、Docker镜像名,甚至API服务来讨论——有人在GitHub搜 hindsight-llm &am… · 2026/11/4 15:19:09
流媒体服务器日志轮转方案与优化实践 1. 项目背景与需求解析在流媒体服务器运维过程中,日志管理是个容易被忽视却至关重要的工作环节。以ZLMediaKit这类高性能流媒体服务器为例,默认配置下日志会持续追加到单个文件中,长期运行后可能产生几个GB甚至更大的日志文件,不仅… · 2026/11/4 15:18:09
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38