尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

蓝桥杯CT107D单片机例程详解:从锁存器到定时器秒表实验

发布时间:2026/9/16 3:23:20

资讯中心
01
ARTICLE

蓝桥杯CT107D单片机例程详解:从锁存器到定时器秒表实验

蓝桥杯CT107D单片机例程详解:从锁存器到定时器秒表实验
简介面向蓝桥杯单片机竞赛的CT107D实训平台实验例程包覆盖按键控制、定时器与中断、秒表计时等核心实验帮助参赛者掌握IO口读取、寄存器配置、中断服务程序编写以及数码管动态显示等单片机开发技能适合备赛学生与嵌入式初学者对照练习。压缩包内共422个文件以C源码、汇编启动文件、头文件、工程文件uvproj及编译生成的hex、lst、obj等类型为主工程结构清晰便于直接打开、编译与烧录验证。资源整体仅961KB轻量易下载目前已吸引335人学习使用。借助这些实验例程读者可快速理解CT107D平台外设用法并在此基础上扩展实现秒表、按键调节等综合功能有效缩短蓝桥杯备赛中的环境搭建与调试时间。1. 蓝桥杯单片机竞赛里的CT107D为什么值得花时间读例程参加蓝桥杯单片机赛项的选手十有八九会在备赛初期碰上一块蓝绿色底板、型号为CT107D的开发板。这块板子不算复杂核心是一片STC15F2K60S2单片机板载LED、数码管、独立按键、矩阵键盘、DS18B20、DS1302和EEPROM但省赛和国赛的题目几乎都是围绕这些外设组合出题。更关键的是CT107D的外设并不像普通最小系统板那样直接接IO口中间隔着锁存器和译码器这也是很多第一次上手的人卡住的地方明明写了P0赋值LED却不亮。这个资源里的实训例程本质上就是把“IO控制、定时器中断、数码管动态扫描”这三板斧用工程代码串起来适合用来在备考初期建立完整的代码框架。2. CT107D的硬件资源与寄存器位映射CT107D与普通单片机开发板最大的区别在于IO口复用。P0口既驱动8位LED又驱动数码管的8个段码还同时连接到排阻和跳线。如果同一时间只能有一路外设在P0上输出就必须靠锁存器把数据“锁”在目标外设上。这就是CT107D例程里频繁出现SelectHC573函数的原因。2.1 外设与引脚对应关系这张引脚表是从原理图直接整理的写代码前最好先抄到笔记本上外设控制引脚数据/段选备注LED D1-D8P0P0对应8个LED经74HC573锁存Y6C使能数码管段选P0P0作为段码经74HC573锁存Y5C使能数码管位选P0位选码经74HC138译码经74HC573锁存Y4C使能独立按键P3.2-P3.5读P3低电平有效部分引脚同时映射外部中断蜂鸣器P2.0或经三极管低电平或高电平驱动具体看板上丝印例程有用P2.0标准的继电器P0.4等P0口经锁存器与LED共用P0需要锁存器配合这里最容易写错的是位选码。CT107D的位选不是直接给“11111110”这种电平而是通过74HC138译码器把BCD码转成8路低电平选择信号。例如位选第2个数码管要给0x01第3个给0x02而不是直接操作段选锁存器。2.2 锁存器选择函数例程里最常见的一段底层代码是void SelectHC573(unsigned char channel) { switch(channel) { case 4: P2 (P2 0x1f) | 0x80; break; // 打开Y4C位选锁存 case 5: P2 (P2 0x1f) | 0xa0; break; // 打开Y5C段选锁存 case 6: P2 (P2 0x1f) | 0xc0; break; // 打开Y6CLED锁存 case 7: P2 (P2 0x1f) | 0xe0; break; // 打开Y7C外设锁存 default: break; } }这段代码的核心是P2口的低5位保留原有状态高3位变成二进制100、101、110、111分别选中74HC138的Y4到Y7。P2 0x1f先把高位置0再用|设置需要的译码输入。调用方式通常是SelectHC573(6); P0 0x0f; // 点亮低四位LED SelectHC573(0); // 关闭锁存器避免后续P0变化影响当前输出其中最后一个SelectHC573(0)很关键。例程里一般会继续把P2的锁存使能关闭否则下一次P0操作会同时改变LED状态。如果发现LED亮度不均匀或者数码管出现拖影首先要检查是不是漏了这一步。2.2.1 为什么要用P2高三位译码CT107D的74HC138共有8个输出通道Y0-Y3被其他功能占用Y4对应数码管位选Y5对应数码管段选Y6对应LEDY7对应蜂鸣器/继电器等。使用case 4到case 7就是根据硬件连线来的。如果你在别的地方看到有人用P20x80直接赋值效果等价但会破坏P2口低5位原有的功能所以例程统一用“读-修改-写”的方式更安全。这一章的例程还包含一个细节LED和数码管共用P0口但它们是分时工作的。所以在写“流水灯”时每次更新P0前都要先选通Y6C数据输出后立刻关闭锁存然后处理数码管时再切换Y5C。这个开关动作就是CT107D例程里最频繁的底层操作后面的按键和秒表实验也会反复用到。3. 按键控制实验从轮询到状态消抖这一章的实验目标是通过独立按键控制LED的亮灭和闪烁模式。新手容易直接写一个死循环轮询P3端口但在工程例程里按键检测必须解决“按下”和“弹起”时IO电平的不稳定抖动否则一次按下会被当成多次操作。3.1 IO口配置与按键扫描CT107D的独立按键接在P3.2到P3.5上它们同时也被映射到外部中断引脚。例程默认P3口为准双向IO模式STC15系列单片机复位后P3口默认为准双向可以直接读。不需要额外配置P3M0/P3M1。如果需要使用矩阵键盘则要切换P3和P1或根据原理图的输入输出方向那个放到后面进阶实验再说。按键控制LED的例程通常分两部分扫描按键当前状态再根据按键编号执行动作。3.2 按键扫描核心代码下面是一个带消抖的独立按键扫描函数适用于单个P3引脚unsigned char Key_Scan(void) { unsigned char key_value 0; static unsigned char key_last 1; // 上一次电平状态 if(P3 0x0c) { // 检测P3.2/P3.3是否有高电平 if(key_last 0) { // 上一次为低电平说明刚按下 key_value (P3 0x0c); // 记录哪一个键按下 key_last 1; // 更新状态 } } else { key_last 0; // 当前无按键电平为低 } return key_value; }这段代码有两个注意点。第一P3 0x0c是同时检测两个按键返回的值直接就是按键对应的高位例如0x08代表P3.3按下。第二key_last是静态变量函数退出后仍然保留上一次的电平值这样在循环里反复调用时只在“按下沿”返回一次按键值避免持续触发。3.3 消抖的两种实现如果上面的扫描函数在循环中被每隔10ms调用一次那么它本身已经带了一定消抖能力因为只关心上一次所记录的边沿。但更严谨的做法是用定时器产生10ms节拍void Timer0_Init(void) { TMOD 0xf0; // 清零T0模式位 TMOD | 0x01; // T0工作在方式116位定时 TH0 0xd8; // 10ms初值 TL0 0xef; ET0 1; TR0 1; EA 1; }这里0xd8ef是10ms定时初值晶振频率12MHz时的配置。注意STC15系列有1T和12T两种模式CT107D上主频默认是12MHz但STC15的定时器可以通过AUXR寄存器配置为1T或12T。例程里通常默认12T所以初值是0xd8ef。如果你的板子改成了1T模式同样的初值实际只有不到1ms搜索按键就会异常灵敏这时要把初值换算为12倍。3.3.1 实际应用按键切换LED闪烁模式控制LED闪烁的完整逻辑是主循环不断刷新LED状态按键按下时切换模式变量。比如模式0是LED常亮模式1是LED闪烁模式2是流水灯。下列代码展示模式切换部分void main(void) { unsigned char mode 0; P0 0x00; SelectHC573(6); SelectHC573(0); Timer0_Init(); while(1) { if(Key_Scan() 0x08) // 检测到P3.3按键 { mode; if(mode 2) mode 0; } switch(mode) { case 0: P0 0xf0; SelectHC573(6); break; case 1: P0 0x0f; SelectHC573(6); break; case 2: P0 0xaa; SelectHC573(6); break; } } }这个例子看起来简单但它体现了CT107D例程里的一个重要约定所有对LED的写操作都要成对出现先选锁存器再关闭。如果漏了SelectHC573(0)下一次按键扫描时P0口的值可能被数码管段码覆盖导致LED状态错乱。4. 定时器与中断秒表实验的完整实现秒表实验是CT107D实训例程里最能串起知识点的题目用定时器T0产生精确的计时基准用数码管显示时间用独立按键控制启动、暂停和清零。这个实验做完基本上对中断、动态扫描、按键状态机都心里有数了。4.1 功能需求与按键定义实验要求设计一个秒表按下启动键开始计时再按暂停按清零键归零。按键定义如下按键引脚功能S4P3.2启动/暂停S5P3.3清零这里用两个按键实现状态切换而不是三个是因为排序逻辑更简洁。启动和暂停共用一个按键用标志位区分状态。4.2 定时器T0方式1的参数计算秒表的计时分辨率通常是10ms。定时间隔为10ms每100次计为1秒。方式1是16位定时器最大计数6553612MHz晶振下12T模式每条指令周期1us所以10ms需要10000个计数脉冲。初值为65536-1000055536十六进制是0xd8f0。注意前面按键消抖用了0xd8ef差了1这是不同晶振频率或计数误差的常见调整。例程里如果发现计时偏快可以尝试把初值改为10000-10这样的小幅补偿。void Timer0_Init(void) { TMOD 0xf0; TMOD | 0x01; TH0 0xd8; // 高8位初值 TL0 0xf0; // 低8位初值 ET0 1; TR0 1; EA 1; }这里ET01允许T0中断TR01开始计时EA1开总中断。中断服务程序里要重新赋初值因为硬件自动重装初值只在方式2支持方式1需要软件重新加载。4.3 中断服务程序与计时逻辑中断服务程序代码如下volatile unsigned int tick_count 0; volatile unsigned char display_buf[3] {0}; void Timer0_ISR(void) interrupt 1 { TH0 0xd8; TL0 0xf0; // 重装初值 if(running_flag) { tick_count; if(tick_count 100) // 100个10ms 1秒 { tick_count 0; second_count; if(second_count 60) // 简单秒表60秒归零 second_count 0; // 把秒值拆成两位十进制 display_buf[0] second_count / 10; display_buf[1] second_count % 10; } } }volatile关键字在这里非常重要。如果去掉优化器可能把某个变量直接放进寄存器导致中断和主循环之间看不到更新。example_running_flag在中断里被读取在主循环里被修改必须定义为volatile整型。4.4 数码管动态扫描显示部分在main函数主循环里调用扫描频率要足够快避免闪烁。每次扫描一个数码管选通位选发送段码延时1ms左右再切到下一个。段码表需要提前定义unsigned char code seg_code[] {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};这个表是共阳数码管0-9的段码0xc0对应数字0。CT107D的数码管是共阳所以段选输出低电平有效。如果发现数字显示乱码大概率是把共阴共阳搞反了。void Display_Scan(void) { unsigned char i; unsigned char position_code[] {0x01, 0x02, 0x04}; // 位选码 for(i 0; i 3; i) { SelectHC573(4); P0 position_code[i]; SelectHC573(5); P0 seg_code[display_buf[i]]; delay1ms(); } }这里每个数码管显示1ms后立即切换。如果主循环还有按键处理整个循环时间会略大于3ms但因为扫描是循环进行的人眼看到的是三个数码管同时亮。唯一要注意的是位选和段选中间不需要另外关闭锁存器因为每次SelectHC573都会重新改变P2的高三位旧的锁存器自然被关闭。4.5 按键状态机控制启动暂停在秒表例程里按键不能简单地用前面的沿触发扫描因为需要“启动/暂停”交替。用一个状态位void Key_Process(void) { static unsigned char key_state 0; unsigned char key_value Key_Scan(); if(key_value 0x08) // S4 { running_flag !running_flag; // 切换运行状态 if(running_flag) tick_count 0; // 重新开始计时 } else if(key_value 0x04) // S5 { running_flag 0; second_count 0; tick_count 0; display_buf[0] 0; display_buf[1] 0; } }这个处理里清零时会把running_flag清零所以清零后秒表自动停止。启动/暂停按键则只负责切换运行标志位不直接修改秒值这样暂停时再启动会从暂停值继续计时。注意Key_Scan里静态变量会在整个程序中保留状态清零时如果按键未释放下一次扫描会误判新的边沿所以例程里通常会在清零后加一个等待释放的死循环或时间过滤。5. 进阶用逻辑分析仪校准时间与排坑到了这个阶段例程能跑通不代表准确。CT107D最常见的问题是时间偏差明明定时器初值没错但秒表走快或走慢。原因有两种一是STC15的定时器1T/12T模式影响初值换算二是中断服务程序重装初值本身有延迟每次中断服务多出的几条指令没有被补偿。我建议的数字校准方法是用USB逻辑分析仪抓取一个测试引脚的翻转波形。先在定时器中断里加一行P2_0 !P2_0然后逻辑分析仪采样直接测量高电平周期。如果你的秒表走快了10ms就把初值加一个对应的数。这种方式依赖仪器但比肉眼对表可靠得多。另外一个常见的坑是Keil C51的优化级别。例程默认优化级别8或9如果你把初值相关变量定义成unsigned int且未加volatile优化器可能把某些关键代码重排。解决方法是把中断里使用的所有共享变量都加volatile并且对TMOD、TH0这类特殊寄存器不用位操作而直接用字节赋值。下面是一个调试时快速定位问题的检查清单LED不亮先查P0赋值后是否调用了SelectHC573(6)再查锁存关闭是否在数码管显示之后。数码管亮度不均匀检查位选扫描间隔是否小于2ms不要在显示循环里放长延时。按键灵敏度异常检查定时器初值确认12T模式或者用示波器量按键引脚是否有长摆幅。秒表走快在定时器里翻转一个测试IO口逻辑分析仪测量实际中断周期再调整初值。最后一个技巧是CT107D例程里的数码管段码表默认有小数点如果你用0x7f表示数字8会发现显示多一个小数点。这是因为共阳段码第一位是dp端。做秒表时建议自己重新定义一张不带小数点的表避免刷屏时小数点闪个不停。逻辑分析仪抓波形时采样率设到10MHz以上触发设成测试引脚下降沿一次抓取1秒长度就足够判断误差了。把测得的高电平周期减去理论值除以100次中断再换算成计数数就是每次中断应该补偿的数值。这种调过一轮之后你再回去看例程里那些初值常被注释掉的细节就能明白为什么要写成两个字节分开赋值了。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。