学习《8051U深度入门到32位51大型实战教学视频》得实验箱
本帖最后由 严西湖里的鱼 于 2024-12-2 15:13 编辑重新启程,上个学习的课程现在好像不再赠送实验箱,耽误了半个月的时间。现在学习冲哥的视频看看能不能得到实验箱,如果可以希望能提前拿到箱子哟。{:smile:}
第一课、序
课程主要内容:
AI8051U 单片机的深度入门与实战应用。课程从零开始,详细介绍了 AI8051U 的功能与优势,包括屏幕显示、视频播放、IPHONE 录音、频谱分析仪和手写计算器等。通过实例演示,展示了 AI8051U 在图形界面、音频处理和数据计算等方面的强大能力。此外,视频还介绍了使用 AI8051U 作为编程器的功能,展示了其在视频播放方面的应用。
学习感悟:
芯片的功能确实强大,性能的提升也对应用领域进行了扩展,不过不知道后续视频是不是都是在实验箱上完成演示,如果这样像我这样没有箱子的岂不是没法做实验?
------------------------------------------------------
学习视频链接:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=11902
第二课、硬件及工具介绍
课程主要内容:
关于 AI8051U 单片机的深度入门课程,重点介绍了实验箱的硬件配置。实验箱包括 USB 下载接口、示波器输入、录音放音设备、OLED 显示屏、流水灯、数码管、TFT 彩屏、掉电检测模块、红外接收器、矩阵键盘和 ADC 键盘等组件。课程基于 AI8051U 主控芯片,强调了 T0T1 和 intoint1 按键的区别,并介绍了电源按键的功能。介绍硬件及工具,包括按键、QSPI flash, LCD 对比度调节、RTC 电池、插针和晶振等。解释 RTC 功能及重要性,提到实时时钟和电池供电,确保单片机在断电后仍能正常运行。详细描述板子背面的硬件,包括无源晶振、24C02 存储芯片、DS18B20 温度传感器、无源蜂鸣器等。视频第二部分进行了软件安装及工具使用方法介绍。比如:下载 AI8051U 规格书和手册与Keil开发工具,下载最新版本的ISP软件,解压软件并安装Keil,添加头文件到 Keil中,扩展中断号的程序下载与安装等等。最后介绍 Ai8051U 单片机实验箱硬件及工具,如何操作、下载、运行并展示流水灯效果。在其中强调了如何设置频率,比如视频中就是选择24兆时钟。课后建议:对照使用说明书和代码包进行学习。
学习感悟:
看着箱子流口水,然后就听到“后续的课程都是围绕这箱子展开”,听到这句话感觉完蛋了{:4_199:},以后的程序又只能靠脑洞来模拟了。TFT屏讲课的冲哥就在卖,说的挺婉转的。链接在后面,以后有箱子的都要买的。
https://shop204481741.taobao.com ... info.5e517dd6b11eMJ
第三课、编写第一个代码
课程主要内容:
开头介绍本期内容,回顾了上节课的硬件和软件介绍后,开始准备编写第一个代码。紧接着详细介绍如何使用Keil新建工程、添加头文件、输入代码、设置项目参数等等,而后添加源代码到项目,完成项目创建后,设置 CPU 模式,Ai8051U现在只支持source a251,中断也要勾选4字节支持,后续继续选择合适的项目,完成后结束工程创建。第二步添加头文件,介绍头文件的使用方法,推荐使用双引号引用本地文件。而后介绍如何操作的详细步骤:创建头文件、引用头文件、编写主函数、输入代码、设置编码避免汉字乱码,完成第一个工程的创建。确认代码无误,介绍最小代码结构及注释用法。后续编写点亮 LED 的代码,解释代码功能及执行顺序。并详细介绍如何下载到仿真箱,而后讲解如何排查程序并点亮LED。在原理讲解中用灯泡电源电路进行演示,在视颇中,讲解了如何通过开关(三极管) 控制 LED 灯的亮灭,强调开关打开条件是电源从正指向负,即高电压指向低电压。继续介绍了三极管的两种箭头方向,以及在实验板上蜂鸣器的电路应用,解释了如何通过单片机控制三极管打开。而后讲解了单片机IO口的配置,包括GPIO的全称配置模式以及通过PnM0、PnM1寄存器设置端口模式,最后通过代码演示了如何控制LED灯点亮,在后续介绍中如何详细使用IO口。代码敲完后,为了美观,使用Tab键对齐注释。而后的演示点亮第二个灯,并布置作业要求点亮8个灯。最后讲到使用工具AIapp-ISP设置端口模式提高效率,并建议设置为准双向口。
学习感悟:
本次学习如何使用Keil编程软件对 A18051U 单片机进行第一个代码的编程,通过实际例子对IO端口进行深入并详细的讲解。在这个过程中带入了编程软件工具的使用与单片机软硬件的结构与其它编程技巧等等进行了深入浅出的讲解,整个过程清晰明了,适合初学者学习。
本帖最后由 严西湖里的鱼 于 2024-11-27 13:45 编辑
第四课、USB不停电下载
课程主要内容:
本课课程中,重点介绍了如何实现 USB 不停电下载功能。通过演示,展示了传统下载程序的繁琐步骤,包括每次需要手动按下按钮进入下载模式。接着,讲解了如何通过下载特定的程序文件,实现无需手动按下按钮的自动下载功能。课程还详细讲解了如何从 STC 官网下载所需的库文件和历程代码,并进行了代码的移植和调试。最后,强调了在代码运行中,查询模式和中断模式的区别,以及如何选择最稳定高效的运行方式。
学习感悟:
本节课通过
1、实验对比。
2、下载所需文件。
3、移植关键部分到工程:
(1)添加头文件。
#include <stc32_stc8_usb.h>
(2)USB初始化函数
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
(3)命令参数
while(DeviceState != DEVSTATE_CONFIGURED); //检测USB是否准备完毕
if (bUsbOutReady)
{
USB_SendData(UsbOutBuffer,OutNumber); //发送数据缓冲区长度
usb_OUT_done();
}
(4)打开P_SW2寄存器和IE2寄存器
P_SW2 |= 0x80; //EAXFR使能
IE2 |= 0x80; //EUSB使能
等方式使程序自动下载,大大提高调试效率,并且让人加深了对USB口通讯的进一步认识。
第五课、C语言基础
课程主要内容:
视频首先提到本章节主要介绍C语言的基础知识,为后续课程做准备。而后重点强调printf函数在开发中的重要性,并演示如何在单片机中使用和配置该函数,以便快速打印变量信息。后续详细讲解USB CDC串口的printf函数实现,介绍了如何使用printf语句进行数据输出,包括接收任意数据并返回特定字符串STC YYDS的实现。最后,提示避免在循环外使用print f以防止系统崩溃。强调了数据类型和格式化的重要性,讲解了如何使用百分号替换符号(如%S、%D)进行字符串和数字的动态输出。在数据类型与进制转换中,讨论整形、浮点数及字符在编程中的应用。讲解了文本模式与十六进制模式的区别,如何将不同进制数(如二进制、十进制、十六进制)之间进行转换,并提供了使用程序员计算器的实用方法。最后,介绍了变量类型和长度,特别是如何在程序中使用64位变量的注意事项。
后续继续介绍了数据类型和变量的定义,强调了不同数据类型的取值范围,如无符号8位数(0~255)和16位数(0~65535)。还讲解了如何使用宏定义简化变量的定义过程,强调变量作用域的重要性。此外,提到基本运算符的使用,包括加、减、乘、除及取余操作,展示了如何通过示例进行代码实现。在运算符与条件判断中使用程序讲解了if语句的基本结构及其在条件判断中的应用。通过示例,介绍了逻辑运算符的应用,如与、或、取反等,说明了条件判断的基本逻辑。同时,简述了赋值运算及其简写形式,与后面的位运算和条件运算符。首先介绍了异或操作的基本原理,接着阐述了取反、左移和右移的具体操作及其效果,就是左右补几个零。最后总结了运算符的使用,特别是课程介绍的几个表格中的内容,以及变量类型对运算结果的影响,强调了避免变量溢出的重要性。课程结束时提醒学生复习相关知识点。
学习感悟:
在下面的内容是我觉得重点的要注意的,在printf(fmt)中fmt作为输出的参数是文本格式,其中有普通字符与转换说明。百分号作为替换符号(如%S、%D)可以进行字符串和数字的动态输出,与fmt中的参数个数要与使用格式字符(%)的个数一一对应。后续对常用的格式字符与转义字符进行列表。视频中提到要求将15以内的二进制与十六进制最好背下来。如果要使用64位需要声明语句"#pragma float64"。C语言中0为假,非0为真,100也是真。逻辑运算符(“&&”、“||”、“!”)和位运算符(“&”、“|”、“^”、“~”、“<<”、“>>”)要区分。
第六课、IO输入输出
课程主要内容:
本视频介绍了AI8051U单片机的GPIO(通用输入输出端口)功能,阐述了高低电平的定义及其在单片机中的应用。讲解了四种IO口模式及其电流输出特点,重点讨论了按键输入检测和按键抖动的处理方法,提供了代码示例实现按键控制灯的亮灭。视频还布置了两个课后练习,以巩固所学知识。
学习感悟:
在下面的内容是我觉得重点的需要注意:通用输入输出端口(General Purpose I/O Port)简称GPIO,我先还以为这个简称是可编程端口。本单片机端口的电压极限不能超过5.5V,如果工作电压是3.3V,IO口外接3.6V以内的才行,因为Vdd+0.3V就是IO口对地电压的最大值。输入电平在Vcc为3.3v上时,最大要低于0.99v才是认可的低电平。#define与typedef定义的语句必须放在函数的第一行,因为C51不完全支持C99。如果使用_nop_()函数必须加上#include <intrins.h>头文件。ALT键先按下后用鼠标左键可以选择多行。
作业:
1、按下P32按钮,P0端口左边4灯亮右边4灯灭,按下P33按钮,P0端口右边4灯亮边左4灯灭。
#include <AI8051U.H>
#include <intrins.h>
#include "stc32_stc8_usb.h"
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
void Delay20ms(void) //@24.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 119998UL;
while (i) i--;
}
void main()
{
EAXFR = 1; //打开扩展特殊功能寄存器,等同:P_SW2 |= 0x80;
CKCON = 0; //提高访问XRAM速度
WTST = 0; //设置程序指令延时参数
P0M0 = 0x00; P0M1 = 0x00; //初始化各个端口为准双向口模式
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init(); //USB口初始化配置
IE2 |= 0x80; //EUSB位置位,打开USB
EA = 1; //总中断置位,打开中断
P40 = 0; //打开P0端口LED总开关
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
while(1)
{
if(P32==0) //P32按键触发延时工作
{
Delay20ms();
if(P32==0) //判断确实P32已经按下进入稳态,而不是抖动,进行程序运行
{
P00 = 0;
P01 = 0;
P02 = 0;
P03 = 0;
P04 = 1;
P05 = 1;
P06 = 1;
P07 = 1;
while(P32 == 0); //等待P32松开
}
}
if(P33==0) //P33按键触发延时工作
{
Delay20ms();
if(P33==0) //判断确实P33已经按下进入稳态,而不是抖动,进行程序运行
{
P00 = 1;
P01 = 1;
P02 = 1;
P03 = 1;
P04 = 0;
P05 = 0;
P06 = 0;
P07 = 0;
while(P33 == 0); //等待P33松开
}
}
}
}
2、P33按一下亮一个灯,再按一下亮两颗灯,直到全亮。
#include <AI8051U.H>
#include <intrins.h>
#include "stc32_stc8_usb.h"
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
void Delay20ms(void) //@24.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 119998UL;
while (i) i--;
}
void main()
{
u8 state = 0,count = 1;
EAXFR = 1; //打开扩展特殊功能寄存器,等同:P_SW2 |= 0x80;
CKCON = 0; //提高访问XRAM速度
WTST = 0; //设置程序指令延时参数
P0M0 = 0x00; P0M1 = 0x00; //初始化各个端口为准双向口模式
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init(); //USB口初始化配置
IE2 |= 0x80; //EUSB位置位,打开USB
EA = 1; //总中断置位,打开中断
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
P40 = 0; //打开P0端口LED总开关
P0 = 0xff;
while(1)
{
if(P33==0) //P33按键触发延时工作
{
Delay20ms();
if(P33==0) //判断确实P33已经按下进入稳态,而不是抖动,进行程序运行
{
if(count <= 8) //没有点亮全部LED则继续左移
{
P0 <<= 1;
count++;
}
else
{
count = 1; //全部点亮LED重新初始化
P0 = 0xff;
}
while(P33 == 0); //等待P33松开
}
}
}
} 第七课、定时器中断
课程主要内容:
本视频介绍了8051U单片机的定时器中断应用,通过一个炒菜和烧火的场景引入定时器的概念。视频中展示了如何编写代码实现LED每三秒闪烁,以及如何通过按键中断实现实时按键计数。讲解了定时器的配置与使用,以及如何解决按键无反应的问题。最后,视频讨论了函数的定义、声明和调用,并给出了实践任务,帮助观众理解中断和定时器的实际应用。
学习感悟:
在下面的内容是我觉得重点的需要注意,以T0定时器为例:
TM0PS(8位寄存器)、
AUXR(B7位T0x12,高电平除以12)、
TMOD(B2位T0_C/T,高电平对P3.4引脚脉冲进行计数,低电平使能内部定时器。B3位T0_GATE,高电平对外部INT0开放控制,容许外部控制定时器。低电平锁住外部控制)、
TL0、TH0(16位初始值)、
TF0(溢出中断标志,在TCON中B3位,硬件自动清零)、
TR0(定时器0开始计时)、
ET0(使能定时器0中断)
等各个寄存器及控制位的作用。在ISP中可以点击“定时器计算器”中的“使能定时器”可以直接设置中断回调函数,避免搞错interrupt端口号。"\xfd"转意符可以修正汉字乱码的问题。
第二部分函数的定义,包括函数名、函数返回置类型、入口参数、函数体等概念。
作业:
电子功德箱:
1、按下按钮1,串口显示“双倍功德时间”,再次按下显示“单倍功德时间”;
2、按下按键2,如果是双倍功德时间下串口显示“功德+2 当前功德:XXX”;
3、按下按键2,如果是单倍功德时间下串口显示“功德+1 当前功德:XXX”;
4、功德+1时,LED点亮1秒后熄灭表示功德成功点亮;
5、功德+2时,LED点亮2秒后熄灭表示功德成功点亮。
```
#include <AI8051U.H>
#include "stc32_stc8_usb.h"
#include <intrins.h>
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
u8 Count_GongDe = 0; //功德计数
u8 State_GongDe = 0; //功德状态
u8 State_Delay = 1; //延时状态
void Delay20ms(void) //@24.000MHz
{
unsigned long edata i;
_nop_();
_nop_();
i = 119998UL;
while (i) i--;
}
void Timer1_Isr(void);
void Timer1_Init(void) //1秒@24.000MHz
{
TM1PS = 0x1E; //设置定时器时钟预分频 ( 注意:并非所有系列都有此寄存器,详情请查看数据手册 )
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xFC; //设置定时初始值
TH1 = 0x03; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器1中断
}
void main() //按键1为P33按键,按键2为P35按键,在实验箱的右下角两个键。
{
EAXFR = 1; //打开扩展特殊功能寄存器,等同:P_SW2 |= 0x80;
CKCON = 0; //提高访问XRAM速度
WTST = 0; //设置程序指令延时参数
P0M0 = 0x00; P0M1 = 0x00; //初始化各个端口为准双向口模式
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
usb_init(); //USB口初始化配置
IE2 |= 0x80; //EUSB位置位,打开USB
EA = 1; //总中断置位,打开中断
P40 = 0; //使能LED总开关
while(DeviceState != DEVSTATE_CONFIGURED); //等待USB完成配置
while(1)
{
if(P33 == 0) //按下P33触发程序
{
Delay20ms(); //延时消抖
if(P33 == 0) //确实按下按钮
{
switch(State_GongDe) //判断功德状态 0=没有功德;1=单倍功德;2=双倍功德
{
case 0: //
case 1:
TR1 = 0; //停止定时器1计时
printf("单倍功德时间\r\n");
P00 = 0; //点亮P00
State_GongDe = 1; //功德状态设为1
Timer1_Init(); //开始延时
State_GongDe = 2; //功德状态设为2
break;
case 2:
TR1 = 0; //停止定时器1计时
printf("双倍功德时间\r\n");
P00 = 0; //点亮P00
Timer1_Init(); //开始延时
State_GongDe = 1; //功德状态设为1
break;
}
while(P33 == 0); //等待按键恢复
}
}
if(P35 == 0) //按下P35触发程序
{
Delay20ms(); //延时消抖
if(P35 == 0) //确实按下按钮
{
switch(State_GongDe) //判断功德状态
{
case 0:
printf("没有功德哟! 当前功德:%d\r\n",Count_GongDe);
break;
case 2: //注意这个模式2是指的P33按键进入单倍功德状态
Count_GongDe++; //功德计数加1
printf("功德+1 当前功德:%d\r\n",Count_GongDe);
break;
case 1: //注意这个模式1是指的P33按键进入双倍功德状态
Count_GongDe += 2; //功德计数加2
printf("功德+2 当前功德:%d\r\n",Count_GongDe);
break;
}
while(P35 == 0); //等待按键恢复
}
}
}
}
void Timer1_Isr(void) interrupt 3 //定时器1回调函数,每秒调用一次
{
switch(State_GongDe)
{
case 2: //注意这个模式2是指的P33按键进入单倍功德状态
P00 = 1; //关闭P00
TR1 = 0; //停止定时器1计时
State_GongDe = 0; //功德置0
break;
case 1: //注意这个模式1是指的P33按键进入双倍功德状态
if(State_Delay == 1) //判断是否是第一次循环
{
State_Delay++;
break;
}
else
{
P00 = 1; //关闭P00
TR1 = 0; //停止定时器1计时
State_GongDe = 0; //功德置0
State_Delay = 1; //重新置1
break;
}
}
}
```
<p>第八课、定时器周期性调度任务<br />
课程主要内容:<br />
本视频讲解了8051U单片机的定时器周期性任务调度,重点分析了常见编码错误,如大小写和符号问题。通过使用定时器实现LED灯的周期性闪烁(0.3秒、0.6秒、0.9秒),并介绍了如何使用数组和for循环简化代码。此外,视频还展示了如何创建和引用C和H文件,以及如何利用结构体实现多任务调度,确保各个任务独立运行。最后强调了对周期性任务调度的理解与应用。</p>
<p>学习感悟:<br />
周期任务就是每隔一定的时间进行一次任务。数组的声明: “数据类型 数组名称[数组大小] = {数值1,数值2,...};“ ,数组是从0开始,比如数组大小是10,那么数值的位置就是0~9。数据的缩进可以按住shift + tab键向前缩进。严格按视频中老师的程序进行编写,否则自由发挥以为更简洁,结果与后面老师的代码衔接不上,搞的排查花的时间更长。头文件(.H)中进行声明,在库文件(.C)中进行定义,在组织头文件后一定记得调用的路径要加入编译器。<br />
在周期性的任务调度演示程序中的核心就是task.c:</p>
<pre><code>#include "task.h"
static TASK_COMPONENTS Task_Comps[]=
{
//状态(0不执行/1执行)计数(计数置进行递减,为0时将状态位置1)周期(等计数位为0时重新将本位的数赋值给计数位)函数(要执行的函数名称)
{0, 300, 300, LED0_Blink}, //task 1 Period:300ms
{0, 600, 600, LED1_Blink}, //task 2 Period:600ms
{0, 900, 900, LED2_Blink}, //task 3 Period:900ms
{0, 10, 10, KEY_Task}, //task 4 Period:10ms
};
u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps); //计算数组有多少元素,在这段程序中就是计算有多少条任务
//========================================================================
// 函数: Task_Handler_Callback
// 描述: 任务标记回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
// 注意:在主程序的时间回调函数中执行即可控制时间频率,本程序就是1ms执行一次。
//========================================================================
void Task_Marks_Handler_Callback(void) //计数位进行递减到0时将Run的状态置1
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.TIMCount) /* If the time is not 0 */
{
Task_Comps.TIMCount--; /* Time counter decrement */
if(Task_Comps.TIMCount == 0) /* If time arrives */
{
/*Resume the timer value and try again */
Task_Comps.TIMCount = Task_Comps.TRITime;
Task_Comps.Run = 1; /* The task can be run */
}
}
}
}
//========================================================================
// 函数: Task_Pro_Handler_Callback
// 描述: 任务处理回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
// 注意:在主程序的循环体中执行即可判断何时对指定任务的调用。
//========================================================================
void Task_Pro_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.Run) /* If task can be run */ //如果Run的状态为1(真),就执行函数
{
Task_Comps.Run = 0; /* Flag clear 0 */
Task_Comps.TaskHook(); /* Run task */
}
}
}
</code></pre>
<p>第九课、数码管<br />
课程主要内容:<br />
本视频介绍了8051U单片机与数码管的应用。首先,讲解了数码管的基本类型和工作原理,包括共阴与共阳的连接方式。接着,演示了如何通过595移位寄存器控制数码管的显示,实现静态和动态显示数字。视频中还分享了如何使用虚拟显示工具进行调试和显示,以及如何编写相关代码。</p>
<p>学习感悟:<br />
数码管的共阴、共阳要分清。595的功能就是译码。11脚的SCK是时钟;12脚的RCK是锁存寄存器;14脚SER数据输入,上一片的9脚数据输出到它可以无限连接下去;15,1~7脚数据并行输出。时序图是从左往右走,本课中数据是从高位先传,低位数据后传。字库生成工具中有数码管的快捷生成功能。有了问题要多看资料,多看例程,比如我在Send_595这个函数中,错把dat<<=1写成dat<<=i,就是通过学习实验箱例程发现问题的。LED40_SendData函数的声明在stc32_stc8_usb.h文件中,如果需要使用调试仿真接口就需要调用它。这个仿真调用感觉就是通过MCU相应的串口不停向上位机传送数据,上位机接收到数据后通过软件模拟显示相应的内容。这种方式首先要占用一个串口,而且容易掉数据。我将视频中的例子运行时就看见,有时仿真的数字是跳着走,而仿真箱中的数字则一直很稳定。</p>
<p>作业:<br />
简易10秒免单计数器<br />
1、在前四位数码管上显示目标时间,即“10.00”表示定时时间10秒钟。<br />
2、后四位显示当前的计时00.00,最小单位为10ms。<br />
3、按下开始按钮后,每10ms最末尾的数字+1;直到按下结束按钮后停止计数。</p>
<p>下面的程序按下P34键后将进入计数,如果是在9990ms与10009ms之间将认为成功,而后程序进入数码管流水灯闪烁以表示胜利等待重新启动。</p>
<p>io.c</p>
<pre><code>#include "io.h"
bit pauseTag = 1; //暂停计数标志位
bit winTag = 0; //胜利标志位
bit blinkTag = 0; //闪烁标志位
u8 Seg_no = 0; //段码位置
u16 Key_Vol = 0; //按钮延时时间
u16 timeVol = 0; //计时计数
u8 SEG_NUM[]= //段码字库
{
0x3F, /*'0', 0*/
0x06, /*'1', 1*/
0x5B, /*'2', 2*/
0x4F, /*'3', 3*/
0x66, /*'4', 4*/
0x6D, /*'5', 5*/
0x7D, /*'6', 6*/
0x07, /*'7', 7*/
0x7F, /*'8', 8*/
0x6F, /*'9', 9*/
0x40, /*'-', 10*/
0x00, /*' ', 11*/
0x80, /*'.', 12*/
};
u8 T_NUM[]= //位码,使用时要取反
{
0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
};
void Init_595(void)
{
HC595_SER = 0;
HC595_RCK = 0;
HC595_SCK = 0;
}
void Send_595(u8 dat)
{
u8 i;
for(i = 0;i < 8;i++)
{
dat <<= 1; //溢出位到CY
HC595_SER = CY; //数据输入
HC595_SCK = 1; //产生上升沿
HC595_SCK = 0; //复位
}
}
void Display_Seg(u8 HC595_1,u8 HC595_2)
{
Send_595(HC595_1); //段码输出,低电平点亮
Send_595(HC595_2); //位码输出,高电平点亮
HC595_RCK = 1; //数据输出
HC595_RCK = 0; //复位
}
void blinkDisplay(void) //流水灯闪烁显示
{
if(winTag == 1)
{
if(blinkTag == 0)
{
u16 num = 0;
if(Seg_no == 0)
{
Display_Seg(SEG_NUM,~T_NUM); //第一位显示固定的“1”
}
else if(Seg_no == 1)
{
Display_Seg(SEG_NUM + SEG_NUM,~T_NUM); //第二位显示“0+小数点”
}
else if(Seg_no == 2)
{
Display_Seg(SEG_NUM,~T_NUM); //第三位显示固定的“0”
}
else if(Seg_no == 3)
{
Display_Seg(SEG_NUM,~T_NUM); //第四位显示固定的“0”
}
else if(Seg_no == 4)
{
Display_Seg(SEG_NUM,~T_NUM); //第五位显示固定的“1”
}
else if(Seg_no == 5)
{
Display_Seg(0xBF,~T_NUM); //第六位显示,0xBF为段码的“0+小数点”
}
else if(Seg_no == 6)
{
Display_Seg(SEG_NUM,~T_NUM); //第七位显示固定的“0”
}
else if(Seg_no == 7)
{
Display_Seg(SEG_NUM,~T_NUM); //第八位显示固定的“0”
}
Seg_no++;
if(Seg_no > 7)
Seg_no = 0;
blinkTag = 1;
}
else
{
char i;
for(i=0; i<8; i++)
{
Display_Seg(0x00,~T_NUM); //清空显示
}
blinkTag = 0;
}
}
}
void Seg_Task(void)
{
if(winTag == 0)
{
u16 num = 0;
if(Seg_no == 0)
{
Display_Seg(SEG_NUM,~T_NUM); //第一位显示固定的“1”
}
else if(Seg_no == 1)
{
Display_Seg(SEG_NUM + SEG_NUM,~T_NUM); //第二位显示“0+小数点”
}
else if(Seg_no == 2)
{
Display_Seg(SEG_NUM,~T_NUM); //第三位显示固定的“0”
}
else if(Seg_no == 3)
{
Display_Seg(SEG_NUM,~T_NUM); //第四位显示固定的“0”
}
else if(Seg_no == 4)
{
num = timeVol/10000; //取五位上的数值
Display_Seg(SEG_NUM,~T_NUM);
}
else if(Seg_no == 5)
{
num = (timeVol/1000)%10; //取六位上的数值
Display_Seg(SEG_NUM + SEG_NUM,~T_NUM); //显示“数值+小数点”
}
else if(Seg_no == 6)
{
num = (timeVol/100)%10; //取七位上的数值
Display_Seg(SEG_NUM,~T_NUM);
}
else if(Seg_no == 7)
{
num = (timeVol/10)%10; //取八位上的数值
Display_Seg(SEG_NUM,~T_NUM);
}
Seg_no++;
if(Seg_no > 7)
Seg_no = 0;
}
}
void TIMECOUNT_Task(void)
{
if(pauseTag == 0)
{
timeVol++;
if(timeVol > 10009) //大于10000ms清零
timeVol = 0;
}
}
void KEY_Task(void)
{
if(P34 == 0)
{
Key_Vol++;
if(Key_Vol == 5) //延时5ms
{
if(winTag == 0) //没有胜利继续游戏
{
if(pauseTag == 1)
{
pauseTag = 0; //开始计时
}
else
{
pauseTag = 1;
if(timeVol > 9990 && timeVol <10009) //如果水平不够可以调小第一个数值,我就是调小测试的。
winTag=1; //进入胜利状态
}
}
}
}
else
{
Key_Vol = 0;
}
}
</code></pre>
<p>task.c</p>
<pre><code>#include "task.h"
static TASK_COMPONENTS Task_Comps[]=
{
//状态(0不执行/1执行)计数(计数置进行递减,为0时将状态位置1)周期(等计数位为0时重新将本位的数赋值给计数位)函数(要执行的函数名称)
{0, 1, 1, KEY_Task}, //检测开始按键是否按下,本程序为P35
{0, 1, 1, Seg_Task}, //LED显示刷新
{0, 1, 1, TIMECOUNT_Task}, //延时计数
{0, 100, 100, blinkDisplay}, //胜利后数码管以流水灯形式闪烁显示
};
u8 Tasks_Max = sizeof(Task_Comps)/sizeof(Task_Comps); //计算数组有多少元素,在这段程序中就是计算有多少条任务
//========================================================================
// 函数: Task_Handler_Callback
// 描述: 任务标记回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
// 注意:在主程序的时间回调函数中执行即可控制时间频率,本程序就是1ms执行一次。
//========================================================================
void Task_Marks_Handler_Callback(void) //计数位进行递减到0时将Run的状态置1
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.TIMCount) /* If the time is not 0 */
{
Task_Comps.TIMCount--; /* Time counter decrement */
if(Task_Comps.TIMCount == 0) /* If time arrives */
{
/*Resume the timer value and try again */
Task_Comps.TIMCount = Task_Comps.TRITime;
Task_Comps.Run = 1; /* The task can be run */
}
}
}
}
//========================================================================
// 函数: Task_Pro_Handler_Callback
// 描述: 任务处理回调函数.
// 参数: None.
// 返回: None.
// 版本: V1.0, 2012-10-22
// 注意:在主程序的循环体中执行即可判断何时对指定任务的调用。
//========================================================================
void Task_Pro_Handler_Callback(void)
{
u8 i;
for(i=0; i<Tasks_Max; i++)
{
if(Task_Comps.Run) /* If task can be run */ //如果Run的状态为1(真),就执行函数
{
Task_Comps.Run = 0; /* Flag clear 0 */
Task_Comps.TaskHook(); /* Run task */
}
}
}
</code></pre>
不错不错,后面的程序怎么才能写成这种格式
页:
[1]
2