<body>
{time()}
{php}<?php
//php必须和标签在同一行
echo date('Y-m-d H:i:s');
?>{/php}
系统 我是搜索页<br>
博客网址:{$host}<br>
博客标题:{$name}<br>
博客副标题:{$subname}<br>
主题名称:{$theme}<br>
样式名称:{$style}<br>
版权说明:{$copyright}<br>
Z-BlogPHP 版本号:{$version}<br>
Z-BlogPHP 版本信息:{$zblogphp}<br>
Z-BlogPHP 链接(长):{$zblogphphtml}<br>
Z-BlogPHP 链接(短):{$zblogphpabbrhtml}<br>
网站语言:{$language}<br>
页面头部变量:{$header}<br>
页面尾部变量:{$footer}<br>
页面类型:{$type}<br>
当前页面页码:{$page}<br>
社会化评论区:{$socialcomment}<br>
cookie的路径:{$cookiespath}<br>
网站目录物理路径:{$path}<br>
<br>
<br>
module-by-name 模块的一些信息<br>
导航栏:<br>
内部ID:{$modules["navbar"].ID}<br>
模块名称:{$modules["navbar"].Name}<br>
文件名:{$modules["navbar"].FileName}<br>
模块内容:{$modules["navbar"].Content}<br>
htmlID:{$modules["navbar"].HtmlID}<br>
模块类型:{$modules["navbar"].Type}<br>
ul类型模块显示的最大列数:{$modules["navbar"].MaxLi}<br>
模块属性 system=系统模块,theme=主题模块,plugin=插件模块:{$modules["navbar"].Source}<br>
是否隐藏标题:{$modules["navbar"].IsHideTitle}<br>
<br>
{if $pagebar}
分页<br>
分页长度:{$pagebar.PageBarCount}<br>
每页显示文章数量:{$pagebar.PageCount}<br>
总页码数:{$pagebar.PageAll}<br>
当前页码:{$pagebar.PageNow}<br>
起始页码:{$pagebar.PageFirst}<br>
页尾页码:{$pagebar.PageLast}<br>
上一页ID:{$pagebar.PagePrevious}<br>
下一页ID:{$pagebar.PageNext}<br>
上一页Url:{$pagebar.prevbutton}<br>
下一页Url:{$pagebar.nextbutton}<br>
{/if}
{if $type == 'author'}
<br>
用户页? author user article.Author<br>
用户页面链接:{$author.Url}<br>
用户名:{$author.Name}<br>
用户别名:{$author.Alias}<br>
用户静态名:{$author.StaticName}<br>
用户级别:{$author.Level}<br>
用户级别名:{$author.LevelName}<br>
用户邮箱:{$author.Email}<br>
用户主页:{$author.HomePage}<br>
用户摘要:{$author.Intro}<br>
用户头像:{$author.Avatar}<br>
用户股发表文章数量:{$author.Articles}<br>
用户创建页面数:{$author.Pages}<br>
用户发表评论数:{$author.Comments}<br>
用户上传文件数:{$author.Uploads}<br>
{/if}
{if $type == 'tag'}
<br>
标签页?:<br>
标签名:{$tag.Name}<br>
标签链接:{$tag.Url}<br>
标签下文章数:{$tag.Count}<br>
{/if}
{if $type == 'category'}
<br>
分类页? category article.Category<br>
分类ID:{$category.ID}<br>
分类名称:{$category.Name}<br>
分类别名:{$category.Alias}<br>
分类链接:{$category.Url}<br>
分类文章数量:{$category.Count}<br>
分类摘要:{$category.Intro}<br>
分类序号:{$category.Order}<br>
分类层级:{$category.Level}<br>
分类的顶级ID:{$category.RootID}<br>
分类的上级ID:{$category.ParentID}<br>
分类的上一级类实例:{$category.Parent}<br>
分类所用模板:{$category.Template}<br>
分类下文章所用模板:{$category.LogTemplate}<br>
分类扩展数据:{$category}<br>
子分类实例数组:{if $category.SubCategorys == null} 子分类为空{/if}<br>
{/if}
<br>
模块 module-content<br>
导航栏:{module:navbar}<br>
日历:{module:calendar}<br>
控制面板:{module:controlpanel}<br>
网站分类:{module:catalog}<br>
搜索:{module:searchpanel}<br>
最新留言:{module:comments}<br>
文章归档:{module:archives}<br>
站点信息:{module:statistics}<br>
网站收藏:{module:favorite}<br>
友情链接:{module:link}<br>
图标汇集:{module:misc}<br>
作者列表:{module:authors}<br>
最近发表:{module:previous}<br>
标签列表:{module:tags}<br>
文章页<br> 搜索结果页 列表 分类列表页
文章ID:{$article.ID}<br>
文章标题:{$article.Title}<br>
文章别名:{$article.Alias}<br>
文章别名或标题:{$article.AliasFirst}<br>
文章链接:{$article.Url}<br>
文章是否置顶:{$article.IsTop}<br>
文章置顶类型:{$article.TopType}<br>
文章摘要:{$article.Intro}<br>
文章正文:{$article.Content}<br>
文章第一张图片:{if $article->AllImages}{$article.AllImages[0]}{/if}<br>
文章图片计数:{$article.ImageCount}<br>
文章第一张图片的缩略图:{if $article->AllImages}{$article.Thumbs(640, 360, 1, false)[0]}{/if}<br>
文章状态(数字):{$article.Status}<br>
文章状态(名):{$article.StatusName}<br>
是否禁止评论:{$article.IsLock}<br>
文章评论数:{$article.CommNums}<br>
文章浏览数:{$article.ViewNums}<br>
文章发布时间:{$article.Time("PostTime","Y-m-d H:i:s")}<br>
文章创建时间:{$article.Time("CreateTime","Y-m-d H:i:s")}<br>
文章更新时间:{$article.Time("UpdateTime","Y-m-d H:i:s")}<br>
文章分类数据:{$article.Category}<br>
文章作者数据:$article {"ID":"15","CateID":"1","AuthorID":"1","Tag":"","Status":"0","Type":"0","Alias":"","IsTop":"0","IsLock":false,"Title": Intro Content<br>
文章标签数组:<br>
第一个标签对象:{$article.FirstTag}<br>
文章标签计数:{$article.TagsCount}<br>
逗号分隔的文章标签:{$article.TagsName}<br>
文章类型:{$article.Type}<br>
文章类型:{$article.TypeName} ARTICLE 为普通文章,PAGE 为普通页面。<br>
<br>
{foreach $comments as $key => $comment}
{foreach $comment.Comments as $comment}
有子评论
{/foreach}
评论<br>
评论ID:{$comment.ID}<br>
评论楼号:{$comment.FloorID}<br>
评论层级:{$comment.Level}<br>
评论父ID:{$comment.ParentID}<br>
评论根ID:{$comment.RootID}<br>
评论所属文章/页面ID:{$comment.LogID}<br>
评论作者:{$comment.Author.Name}<br>
评论作者网址:{$comment.Author.HomePage}<br>
评论作者邮箱:{$comment.Author.Email}<br>
评论作者头像地址:{$comment.Author.Avatar}<br>
评论所属文章的链接:{$comment.Post.Url}<br>
评论所属文章的标题:{$comment.Post.Title}<br>
评论正文:{$comment.Content}<br>
评论者IP:{$comment.IP}<br>
评论者客户端信息:{$comment.Agent}<br>
评论发布时间:{$comment}<br>
{/foreach}
$backtrace = debug_backtrace();
// 移除当前函数调用的层级
echo "调用栈信息:\n";
foreach ($backtrace as $level => $call) {
$className = (isset($call['class'])) ? $call['class'] : '';
$methodName = $call['function'];
$fileName = $call['file'];
$lineNumber = $call['line'];
echo "层级 {$level}:\n";
echo " 类名:{$className}\n";
echo " 方法名:{$methodName}\n";
echo " 文件名:{$fileName}\n";
echo " 行号:{$lineNumber}\n";
echo "<br>";
}
看门狗是一种能够在程序正常执行出现异常时自动重置程序的功能
在需要长期执行的代码中代码总是不可避免的因为某些原因导致执行异常,如果不能从异常中恢复正常执行过程可能会导致项目的停止。所以为程序代码添加上看门狗功能是很有用的。
在51单片机中,头文件并没有定义这个寄存器地址,不过查看单片机手册可以发现是有这个功能的(在STM32单片机中可以直接使用),看门狗的寄存器地址是 0xe1,在代码中直接定义即可
sfr WDT_CONTR = 0xe1;
这个寄存器使用0-5位共6位可以设置
其中
第6位是看门狗是否开启位,为1打开看门狗
第5位是看门狗清零位,设置为1表示重新计数,硬件会自动将此位清零
第4位是看门狗执行环境,设置为1表示在空闲模式计数,0为在空闲模式不计数
第1-3位是看门狗的定时器预分配值,也就是看门狗重置的时长,超过这个时间没有重置看门狗的寄存器就会直接导致程序被重置
以下是12M晶振下的溢出时间
000 = 2分频 = 65.5毫秒
001 = 4分频 = 131毫秒
010 = 8分频 = 262.1毫秒
011 = 16分频 = 524.2毫秒
100 = 32分频 = 1.0485秒
101 = 64分频 = 2.0971秒
110 = 128分频 = 4.1943秒
111 = 256分频 = 8.3886秒
计算公式 = (12 X 预分频 X 32768)/12000000
所以这里设置为 0x111110 = 0x3e;
表示开启看门狗,设置为重新计数,设置为在空闲模式计数,预分频110=大概4秒溢出时间
代码如下:
#include <reg52.h>
//看门狗的寄存器地址
sfr WDT_CONTR = 0xe1;
//LED灯展示效果
#define LED P2
unsigned char P32_STATUS = 1; //外部中断1的状态
sbit K1 = P1^4;
sbit K2 = P1^5;
sbit K3 = P1^6;
sbit K4 = P1^7;
sbit K5 = P3^5;
void delay10ms(unsigned int);
void main(void){
unsigned char num = 0;
WDT_CONTR = 0x3e;
IT0 = 1;
EX0 = 1; //允许中断
IT1 = 1;
EX1 = 1; //允许中断
EA = 1; //开启总中断
//开机闪烁
P2 = 0xff;
delay10ms(100);
P2 = 0xaa;
delay10ms(100);
P2 = 0x55;
delay10ms(100);
while(1){
//如果看门狗没有起效,将在这里死循环
P2 = num;
num++;
delay10ms(10);
WDT_CONTR = 0x3e;//重置看门狗计数值,防止程序被重置
}
}
void delay10ms(unsigned int c)
{
unsigned char a,b;
for(;c>0;c--)
for(b=38;b>0;b--)
for(a=130;a>0;a--);
}
使用普通IO口来模拟串口通信,串口通信中使用两根信号线来进行发送和接收,RXD获取数据TXD发送数据,两个设备的RXD和TXD要交叉连接
然后发送频需要根据波特率来设置,比如9600的波特率在11.0592Mhz晶振的单片机中的发送延时就是104us
也就是发送1比特为需要的高电平时间,这个延时需要比较精确否则就有可能通信失败
USART串口协议如下,开始是空闲信号线高电平变为低电平且保存104us(以9600波特率为例)为开始信号,之后依次发送每一位比特的高低电平,发送是以低电平开始发送,直到发送完8字节,然后如果没有奇偶校验等的话就是结束信号,将电平保存高电平大于104us即为结束信号
接收和发送情况是一样的,接收的数据是从低字节到高字节顺序获取每一位
怎么判断接收完成?因为结束标志完毕之后如果继续发送数据,电平会被拉低,所以只需要获取到结束标志之后循环判断一定的时间(大于104us,比如取110us),如果在次判断是低电平说明数据还有需要继续接收,否则就是接收完毕。
代码比较简单,一般去读串口数据都是取延时中值的结果,(比如104us的一半时间52us,电平为低时延时52us,之后间隔104us)这样比较准确,也可以使用中断的方式来获取。
关于缓存数组,因为串口数据的发送时间间隔很短,没有多余的时间用于对数据的处理,所以一般都是先将数据放入到一个缓存中,待数据接收完毕之后再处理,否则可能会导致不能及时获取数据导致数据丢失。
代码如下,经过测试可以完成发送和接收:
#include <reg52.h>
#include <intrins.h>
sbit RXD2 = P2^2;
sbit TXD2 = P2^3;
unsigned char str[21] = "http://192.168.0.112/";
unsigned char buffer[50] = {0}; //接收缓存
int getbuffernum,putbuffernum,num;
void delay(void);
void delay2(void);
void Delay10ms(unsigned int c);
void putdata(unsigned char str);
void getdata(void);
void main(){
unsigned char i;
while(1){
getbuffernum = 0;
//读取串口数据
if(RXD2 == 0){
int maxnum = 1000;
while(maxnum--){
if(RXD2 == 0){
getdata();
maxnum = 1000;
}
};
}else{
if(buffer[0] != 0){
for(i=0;i<49;i++){
if(buffer[i] != 0){
putdata(buffer[i]);
buffer[i] = 0;
}else{
break;
}
}
getbuffernum = 0;
putdata(0x0d);
putdata(0x0a);
}
num = 0;
}
}
}
void getdata(){
unsigned int i;
unsigned char str = 0x0;
if(RXD2 == 0){//起始信号
num = 0;
delay2();
if(RXD2 == 0){
//接收数据
for(i=0;i<8;i++){
str >>= 1;
delay();
if(RXD2 == 1){
str |= 0x80;
}
}
}
delay();
buffer[getbuffernum] = str;
getbuffernum++;
}else{
num++;
}
}
void putdata(unsigned char str){
TXD2 = 0;
delay();
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
str >>= 1;
TXD2 = str & 0x1;
delay();
TXD2 = 1;
delay();
}
//104us 9600波特率是通过计算得出来的 num为7 表示 104us 误差 0.05us
void delay(){
//i=134 2400 (415us)通过 i=67 4800 (208us)通过 i=32 9600 (104us)通过
unsigned char i;
for(i=0;i<134;i++){}
}
void delay2(){
unsigned char i;
for(i=0;i<67;i++){}
}
////误差 0us
void Delay10ms(unsigned int c){
unsigned char a,b;
for(;c>0;c--){
for(b=38;b>0;b--){
for(a=130;a>0;a--){
}
}
}
}
1.安装 keil 开发软件,打开安装程序直接点击下一步直到完成
2.激活 点击 file - licensemanagement - 复制 CID
3.打开激活工具 将CID 复制到对应的框 后点击Generate ,将生产的注册码复制粘贴到激活页面对应框点击右侧按钮确定即可成功注册到2032年
4.安装CH340串口驱动
5.新建一个项目
点击projcet - 第一行 new projcet . 选择项目所在文件夹,也可以直接新建文件夹后再选择,选择文件夹后输入项目的名称点击确定
弹出CPU选择atmel - AT89C52点击确定
弹出 是否添加启动文件到项目,点击否,因为后面再生产 hex 文件时会自动添加
左侧项目列表显示target1^如果没有可以看一下下面选项卡,在
点击 file 下面的选项新建一个c文件,保存到对应项目文件夹中,文件名称可以自己设置,比如 led.c 之后就可以编写代码了
点击代码文件上方的魔法棒按钮,切换到output选项卡,把create hex file 选项左侧的选项勾上后点击OK
点击target1 - 在 source group 1 上右键 - add file to ...把对应的 .c 文件添加进来
点击第三行第二个 build 或者第三个 rebuild ,出现 create hex file from 和 0error 0warning 说明文件已经成功编译
6.使用烧录工具将对应文件烧录到单片机即可
独立按键属于一种开关类器件,一端接地一端接IO口,所以单片机中读取到对应IO口为低电平表示按键已经按下,然后可以在代码中通过循环查询或者中断方式进行判断从而执行不同的代码
#include <reg52.h>
#include <intrins.h>
#define LED P2
sbit K1 = P1^4;
sbit K2 = P1^5;
sbit K3 = P1^6;
sbit K4 = P1^7;
sbit K5 = P3^5;
void Delay10ms(unsigned int); //误差 0us
void main(void){
unsigned char n;
unsigned char i;
while(1){
if(K1 == 0){
Delay10ms(1);
//循环右移
if(K1 == 0){
LED = 0x7f;
for(n=0;n<8;n++){
Delay10ms(50);
LED=_cror_(LED,1);
}
}
LED = 0xff;
}
//循环左移
if(K2 == 0){
Delay10ms(1);
if(K2 == 0){
LED = 0xfe;
for(n=0;n<8;n++){
Delay10ms(50);
LED=_crol_(LED,1);
}
}
LED = 0xff;
}
//循环点亮
if(K3 == 0){
LED = 0x7f;
Delay10ms(1);
if(K3 == 0){
i = 0x7f;
for(n=0;n<8;n++){
Delay10ms(50);
i = _cror_(i,1);
LED = LED & i;
}
}
LED = 0xff;
}
//闪缩7次
if(K4 == 0){
Delay10ms(1);
if(K4 == 0){
i = 0x80;
for(n=0;n<8;n++){
LED = 0xaa;
Delay10ms(50);
LED = 0x55;
Delay10ms(50);
}
LED = 0xff;
}
}
//灯全亮LED
if(K5 == 0){
Delay10ms(1);
if(K5 == 0){
LED = 0;
}
}
}
}
void Delay10ms(unsigned int c) //误差 0us
{
unsigned char a^b;
for(;c>0;c--)
for(b=38;b>0;b--)
for(a=130;a>0;a--);
}
LCD1602液晶显示屏通过三根信号线和八根数据线和MCU连接
RS和RW控制数据是读取还是写入,是命令还是数据,一般液晶显示屏只写数据,所以RS和RW为0表示写命令,RS为1 RW为0表示写数据
之后对显示屏初始化之后就可以使用LCDE线即可将数据写到显示屏了
示例代码如下
#include <reg52.h>
#define LCD P0
sbit RS = P2^6;
sbit RW = P2^5;
sbit LCDE = P2^7;
unsigned char datas[] = "Hello World";
void writecom(unsigned char i);
void writedata(unsigned char i);
void Delay(unsigned int a);
void main(void){
unsigned char num;
//初始化LCD1602
writecom(0x38);
writecom(0x0c);
writecom(0x06);
writecom(0x01);
writecom(0x80);
for(num = 0;num < 11;num++){
writedata(datas[num]);
}
while(1){
}
}
void writecom(unsigned char i){
RS = 0;
RW = 0;
LCD = i;
Delay(10);
LCDE = 1;
Delay(10);
LCDE = 0;
}
void writedata(unsigned char i){
RS = 1;
RW = 0;
LCD = i;
Delay(10); //这里需要延时时间长一点
LCDE = 1;
Delay(10);
LCDE = 0;
}
void Delay(unsigned int a){
unsigned char b;
for(;a>0;a--)
{
for(b=110;b>0;b--);
}
}
串口通信使用的是USART协议,使用两根控制线进行读写操作
需要初始化串口和打开时间计数器中断
初始化串口,需要设置如下配置,计时器是用于产生波特率的
SCON = 0x50; //串口工作方式
TMOD = 0x20; //定时器模式2
PCON = 0x80; //波特率加倍
TH1=0XF3; //计数器初始值设置,注意波特率是4800的
TL1=0XF3;
TR1 = 1; //打开计数器1中断
ES = 1; //打开串口中断
EA = 1; //打开总中断
完成之后再代码中对 SBUF 寄存器读写完成都会进入中断,在中断函数中需要执行一些操作,如复位中断标志,判断是读中断还是写中断
#include <reg52.h>
#define LCD P0
sbit RS = P2^6;
sbit RW = P2^5;
sbit LCDE = P2^7;
sbit DS1302_SCLK = P3^6;
sbit DS1302_CE = P3^5;
sbit DS1302_DISO = P3^4;
unsigned char datas[] = "Hello World";
unsigned char status = 0;//发送usart信息标记
unsigned char number = 0;//已经发送的字节
void writecom(unsigned char i);
void writedata(unsigned char i);
void Delay(unsigned int a);
unsigned char readds1302(unsigned char dat);
void writeds1302(unsigned char addr,unsigned char dat);
void main(void){
unsigned char data1,temp;
//初始化LCD1602
writecom(0x38);
writecom(0x0c);
writecom(0x06);
writecom(0x01);
writecom(0x80);
//初始化串口
SCON = 0x50; //串口工作方式
TMOD = 0x20; //定时器模式2
PCON = 0x80; //波特率加倍
TH1=0XF3; //计数器初始值设置,注意波特率是4800的
TL1=0XF3;
TR1 = 1; //打开计数器1中断
ES = 1; //打开串口中断
EA = 1; //打开总中断
//初始化DS1302
writeds1302(0x8e,0x24);//关写保护
writeds1302(0x80,0x39);
writeds1302(0x82,0x11);
writeds1302(0x84,0x17);
writeds1302(0x86,0x30);
writeds1302(0x88,0x11);
writeds1302(0x8a,0x04);
writeds1302(0x8c,0x23);
writeds1302(0x8e,0x80);//开写保护
while(1){
writecom(0x80); //重新定位
//循环显示时间
temp = readds1302(0x81);
temp &= 0x7f;
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
temp = readds1302(0x83);
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
temp = readds1302(0x85);
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
writecom(0x80 + 0x40); //重新定位
temp = readds1302(0x8d);
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
temp = readds1302(0x89);
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
temp = readds1302(0x87);
data1 = temp / 16;
writedata('0'+data1);
data1 = temp & 0xf;
writedata('0'+data1);
writedata('-');
Delay(100); //这个延时会影响串口信息的接收速度
if(status == 1 && number < 11){
status = 0;
SBUF = datas[number];
number++;
}else if(number == 11){
status = 0;
number = 0;
}
}
}
unsigned char readds1302(unsigned char dat){
unsigned char i,temp,data1;
DS1302_SCLK = 0;
Delay(1);
DS1302_CE = 0;
Delay(1);
DS1302_CE = 1;
Delay(1);
for(i=0;i<8;i++){
DS1302_DISO = dat & 0x01;
dat >>= 1;
DS1302_SCLK = 1;
Delay(1);
DS1302_SCLK = 0;
}
for(i=0;i<8;i++){
temp = DS1302_DISO; //这里只是读取了一位,而且是先读的低位
data1 >>= 1;
temp <<= 7;
data1 |= temp;
DS1302_SCLK = 1;
Delay(1);
DS1302_SCLK = 0;
}
//复位需要的时间
DS1302_SCLK = 0;
Delay(1);
DS1302_CE = 0;
Delay(1);
DS1302_DISO = 0;
Delay(1);
return data1;
}
void writeds1302(unsigned char addr,unsigned char dat){
unsigned char i;
DS1302_SCLK = 0;
Delay(1);
DS1302_CE = 0;
Delay(1);
DS1302_CE = 1;
Delay(1);
for(i=0;i<8;i++){
DS1302_DISO = addr & 0x01;
addr >>= 1;
DS1302_SCLK = 1;
Delay(1);
DS1302_SCLK = 0;
}
for(i=0;i<8;i++){
DS1302_DISO = dat & 0x01;
dat >>= 1;
DS1302_SCLK = 1;
Delay(1);
DS1302_SCLK = 0;
}
DS1302_SCLK = 0;
Delay(1);
DS1302_CE = 0;
}
void writecom(unsigned char i){
RS = 0;
RW = 0;
LCD = i;
Delay(10);
LCDE = 1;
Delay(10);
LCDE = 0;
}
void writedata(unsigned char i){
RS = 1;
RW = 0;
LCD = i;
Delay(10); //这里需要延时时间长一点
LCDE = 1;
Delay(10);
LCDE = 0;
}
void Delay(unsigned int a){
unsigned char b;
for(;a>0;a--)
{
for(b=110;b>0;b--);
}
}
void usart0() interrupt 4{
unsigned char date1;
if(RI){
if(SBUF == 54){ //表示接收到最后一个字符 数字6
status = 1; //接收到数据,
}
date1 = SBUF;
RI = 0; //硬件中断后自动置1,软件值0防止多次响应 表示接收完毕
}
if(TI){
while(!TI);
status = 1;
TI = 0; //表示发送完毕
}
}
因为90C52单片机没有硬件I I C,所以需要通过IO口来模拟I I C 协议
先要实现I I C协议功能之后
然后再和AHT10温度传感器通信获取温度和湿度数据
如果使用其他STM单片机的话可以直接使用自带的硬件I I C
这里先要来实现I I C协议
I I C 协议代码基本上都是通用的,可以参考别人的代码和I I C 的时序图来实现
我这里使用P2.0和P2.1来模拟SDA信号和SCL信号
这个协议看着比较简单,但是如果是自己第一次实现这个协议基本上都会失败
这里先要实现一个协议的起始信号
SCL在高电平的时候SDA信号由高到低代表开始,所以SCL先要为高电平,然后SDA由高到低跳变表示开始信号
然后是结束信号,和开始信号相反,SDA信号由低到高代表结束
完成开始信号之后两根信号线应该都是低电平的,表示准备发送数据
完成结束信号之后两根信号线都是高电平,表示总线空闲
信号保持高电平的时间需要大于4.7微秒
然后是发送一个字节
因为I I C 只能一位一位比特的发送,所以这里需要一个for循环
发送逻辑1需要在SCL为高电平期间SDA保持高电平,发送逻辑0则需要在SCL为高电平期间SDA保持为低电平
且需要注意SDA高低电平变化只能在SCL为低电平期间改变,不然就会变成开始信号或者结束信号导致通信错误
发送完8个字节之后在第九个时钟周期需要等待设备响应ACK
以下为参考链接
!a=https://www.cnblogs.com/aspirs/p/13081835.html
响应方式就是在SCL为低电平时先把SDA拉高,等待设备将它拉低表示响应,然后SCL变为高电平时看到SDA为低电平说明响应了ACK,否则就是不响应
然后就是读取数据
在设备响应ACK之后通过控制时钟线在高电平获取SDA数据
也是需要一个for循环来获取8位比特数据
因为响应ACK之后SCL线是低电平,此时设备可以将数据放入SDA线等待SCL拉高获取数据
如此循环直到读取完8位数据
然后还需要主动发送一个ACK响应给设备表示确认收到数据
确认ACK就是在接受到第8位之后将SDA线拉低
这样设备就会继续发送下一个字节的数据给我们
如果不发送ACK的话设备就会认为不需要继续发送数据了,然后停止发送,我们就可以发送一个停止信号结束本次通信了
这个协议都是先发送一个设备地址然后在发送指令或者读取数据的
然后就是读取AHT10温度传感器的数据了
读取方式为,首先需要初始化设备,然后发送测量数据,最后读取数据,一直循环,需要注意的是发送测量之后需要等待至少75ms等待数据测量再获取,设备初始化也是至少需要20ms
这里发送的是一个 38 地址加第8位0表示写(0x70),响应之后依次发送 ac 33 00 触发测量
等待75ms以上之后
然后发送一个 38 地址位加一个读取位1(0x71)等待设备响应准备读取
读取6次,第一字节是状态信息,如果返回 1c 说明正常
之后的五个字节是湿度和温度信息,各占用20位长度
前20位是速度信息,后20位是温度信息
然后根据提供的格式计算即可获取到温度和湿度的信息
湿度计算公式为 数据 / 1048576 * 100 = 湿度百分比
例如获取到的数据如下 80 87 35 61 5C
湿度部分为 0x80873 温度部分为 0x5615c
0x80873 = 526451 / 1048576 * 100 = 50.206%
0x5615c = 552604 / 1048576 * 200 - 50 = 17.25℃
以下代码IIC和AHT数据获取都是正常的可以参考,但是串口发送数据不对,等待后续研究
#include <reg52.h>
#define LCD P0
sbit RS = P2^6;
sbit RW = P2^5;
sbit LCDE = P2^7;
sbit SDA = P2^0;
sbit SCL = P2^1;
sbit SDA1 = P2^4;
sbit SCL1 = P2^3;
void Delay(unsigned int a);
unsigned char IIC_send(unsigned char data1);
void IIC_end_ack();
unsigned char IIC_read();
void IIC_stop();
void IIC_start();
unsigned char shidu[] = "shidu";
unsigned char wendu[] = "wendu";
unsigned char status = 0;
unsigned char k;
void main(void){
long data1;
unsigned char i;
unsigned char datas[6];
unsigned char data2[4];
float temp;//临时变量
//初始化串口
SCON = 0x50; //串口工作方式
TMOD = 0x20; //定时器模式2
PCON = 0x80; //波特率加倍
TH1=0XF3; //计数器初始值设置,注意波特率是4800的
TL1=0XF3;
TR1 = 1; //打开计数器1中断
ES = 1; //打开串口中断
EA = 1; //打开总中断
//初始化aht10
IIC_start();
IIC_send(0x70);
IIC_send(0xba);
IIC_stop();
Delay(3000);
while(1){
//请求测量
IIC_start();
IIC_send(0x70); //0x38
IIC_send(0xac);
IIC_send(0x33);
IIC_send(0x00);
IIC_stop();
Delay(5000); //延时82ms
//获取数据
IIC_start();
Delay(1); //延时82ms
IIC_send(0x71);
datas[0] = IIC_read();
IIC_end_ack();
datas[1] = IIC_read();
IIC_end_ack();
datas[2] = IIC_read();
IIC_end_ack();
datas[3] = IIC_read();
IIC_end_ack();
datas[4] = IIC_read();
IIC_end_ack();
datas[5] = IIC_read();
IIC_end_ack();
IIC_stop();
data1 = (datas[3] & 0xf) * 65536 + datas[4] * 256 + datas[5];
temp = data1 / 1048576 * 200 - 50;
data1 = temp * 1;
data2[0] = (data1 / 10) + 48;
data2[1] = (data1 % 10) + 48;
status = 0;
k=1;
while(status < 2){
if(k){
SBUF = data2[status]+48;
k = 0;
}
}
Delay(50000); //4000ms
}
}
//起始信号
void IIC_start(){
SCL = 0;
SCL1 = 0;
Delay(1); //6.33us左右,必须大于4.7us
SDA = 1;
SDA1 = 1;
Delay(1);
SCL = 1;
SCL1 = 1;
Delay(1);
SDA = 0;
SDA1 = 0;
Delay(1);
SCL = 0; //scl拉低准备发送数据
SCL1 = 0;
}
void IIC_stop(){
SCL = 0;
SCL1 = 0;
Delay(1);
SDA = 0;
SDA1 = 0;
Delay(1);
SCL = 1; //接收时将SCL拉高表示总线空闲
SCL1 = 1;
Delay(1);
SDA = 1;
SDA1 = 1;
}
unsigned char IIC_read(){
unsigned char i,data1;
SDA = 1; //这里设置为1应该是为了保险起见,因为一方发送完之后 SDA 和 SCL 都是高电平的
for(i=0; i<8; i++){
Delay(1);
SCL = 1;
SCL1 = 1;
Delay(1);
data1 <<= 1; //低位先接收
data1 |= SDA; //低位或运算获取数据
SDA1 = SDA;
Delay(1);
SCL = 0; //拉低等待对方设置数据吗?
SCL1 = 0;
Delay(1);
}
return data1;
}
void IIC_end_ack(){
SCL = 0;
SCL1 = 0;
Delay(1);
SDA = 0; //讲道理这里应该就是响应了
SDA1 = 0;
Delay(1);
SCL = 1;
SCL1 = 1;
Delay(1);
SCL = 0;
SCL1 = 0;
Delay(1);
}
unsigned char IIC_send(unsigned char data1){
unsigned char i,num;
SCL = 0;
SCL1 = 0;
Delay(1);
for(i=0; i<8; i++){
//因为SCL现在时低电平,所以可以设置SDA 高位先发
SDA = data1 >> 7; //第一位 12345678 >> 7 = 00000001 ←----┒
data1 = data1 << 1; // 12345678 << 1 = 23456780 ---------┘
SDA1 = SDA;
Delay(1);
SCL = 1;
SCL1 = 1;
Delay(1);
SCL = 0; //拉高再拉低
SCL1 = 0;
}
SDA = 1;
SDA1 = 1;
Delay(1);
SCL = 1; //发送完一个字节,等待从机响应 ACK
SCL1 = 1;
num = 0;
while(SDA){
num++;
Delay(2);
if(num > 200){//防止从机不响应死循环 1.几毫秒左右
SCL = 0;
SCL1 = 0;
return 2;
}
}
SCL = 0; //从机响应
SCL1 = 0;
Delay(1);
return 1;
}
void Delay(unsigned int a){ //6.33us
unsigned char b;
for(;a>0;a--)
{
for(b=3;b>0;b--);
}
}
//串口中断
void usart0() interrupt 4{
unsigned char date1,i;
if(RI){
RI = 0; //硬件中断后自动置1,软件值0防止多次响应 表示接收完毕
}
if(TI){
status++;
while(!TI);
TI = 0; //表示发送完毕
k=1;
}
}