先提两句
这学期刚系统上完单片机的课程,手头又有一个STC15系列的最小系统板,就打算用这个最小系统板搞点事情。先从最简单的智能小车入手,一步一步开发新功能。但是由于未知的原因最小系统板用着烧录不了程序(后来好像因为作死真把他烧了),不能不转换方案拿51单片机接着做下去了。
大一学期在淘宝上买了一块51的学习板,和B站上的视频配套的拿来跟着学。当时是同学推荐刷清翔单片机的教程(没有夹带私货,大家不要锤我),有兴趣学习单片机的可以看一看。第一遍学的很浅只是跟着敲敲例程,然后这学期又开设了单片机课程,通过课程要求的实验算是有了一些理解。但是因为课本是针对15系列,回来再看当时学的51视频,得心应手很多(强烈建议大家多刷视频,多做实验或者具体的项目巩固知识)。过程中也发现15系列与51单片机还是有一定区别的。所以借此机会做个小结。
简介
单片机用的较多且核心的部分应该就是定时器部分了,如果第一遍学的话中断部分学的不错的话,定时器部分可以轻松一些。定时器在各种项目的具体功能甚至延时函数部分都有涉及,51和15的差别我在上篇文章稍微提到并附上了一些不错的博主写的关于定时器的介绍,有感兴趣的可以回去看看。转回正题,这篇主要以串口通信和IIC协议对15和51系列的使用横向对比。
串行通信
串行通信作为比较基础的通信方式,广泛应用于上位机测试、蓝牙等方面,按时钟控制方式分为同步通信和异步通信,按方式分类分为方式0、1、2、3。方式0位移位寄存器功能,可以用于扩展IO口实际应用不多,1、2、3是基于UART协议的异步通信收发器,只是位数有所不同,这里主要以方式1进行介绍。
首先,串行通信的前提是通信双方需要具有相同的波特率,这里可以参考每种生物都有一定的声音接收频率,生物之间相互交流信息不仅需要语言还需要发出的信号在接收频率范围内才能被正常接收。而波特率发生器的实现又绕回了之前我们提到的定时器,通常都是通过定时器设定初值和对串口寄存器设置实现串口功能。
好在我们常用的烧录软件有波特率发生器计算这个模块,具体的计算方法大家只需要在学习的过程中知道原理即可(要是考试考这个当我没说),为了避免重复造轮子,一般只需要在软件做好设定好就能生成。但是还需要注意几个地方才能正常使用。
1. 区别15系列和51系列单片机定时器的区别
STC-ISP这个定时器所有的生成代码的部分应该是基于15系列(博主测试15系列设置好基本不需要更改可以直接使用),其中的类似AUXR寄存器是51系列单片机没有的,需要大家单独设置或者对代码做相应改动删去这部分内容。
15系列定时器无外乎16位自动重装载、16位不可自动重装载、8位自动重装载等模式,根据是否可以引入外部中断还可以细分。
而51系列则是13位定时/技术计数,16位定时/计数和8位自动重装载
其实两个系列定时器的方式0、1都有所区别,初学者很容易误以为定时器功能一样而出现问题。
2.区别两个系列的时钟
一般的,15系列支持12分频和不分频设置,通过AUXR寄存器设置。
而51系列支持12分频和6分频即双倍速,可在烧录软件设置。
如果你要直接使用烧录软件生成的代码,设置好波特率,一般来说12分频且不设置双倍速51是也可以正常用的,但如果你设置不分频生成代码或者设置了双倍速用于51是会出问题的。
使用
一般来说使用波特率生成器只要设置好波特率注意使用的定时器,时钟频率不用特意修改,用于51的话AUXR部分直接删去就可以正常使用。数据位可以根据自己选择的串行通信方式设置。
小小的坑:STC-ISP生成的定时或波特率发生器的代码需要大家自己手动添加开启总中断和响应定时器中断的指令。
IIC协议
IIC协议课本上没有涉及,但是后面利用单片机做项目进行通信应该是绕不开的一个知识点,本人理解不深,只是根据网上的资料提几点需要注意的地方。
IIC协议几种信号表示:
写数据:
读数据:
读写切换时从机地址和起始信号要重复一次后面加读写方向
注意
主从机通信过程中,时钟线SCL的状态只能由主机拉高或释放,而SCL和数据线SDA是通过线与连接在一起的,且数据(SDA)只有在SCL为低电平时改变才有效(起始终止信号不满足这个前提),为了使从机发数据主机接收时正常,往往操作最后需要主机拉低时钟线释放数据线即SCL=0,SDA=1;
附部分代码:
/*5us延时*/
void delay_5us()
{
_nop_();
}
/*I2C初始化*/
void I2C_init()
{
SDA = 1;
_nop_();
SCL = 1;
_nop_();
}
/*I2C起始信号*/
void I2C_Start()
{
SCL = 1;
_nop_();
SDA = 1;
delay_5us();
SDA = 0;
delay_5us();
}
/*I2C终止信号*/
void I2C_Stop()
{
SDA = 0;
_nop_();
SCL = 1;
delay_5us();
SDA = 1;
delay_5us();
}
/*主机发送应答*/
void Master_ACK(bit i)
{
SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
_nop_(); // 让总线稳定
if (i) //如果i = 1 那么拉低数据总线 表示主机应答
{
SDA = 0;
}
else
{
SDA = 1; //发送非应答
}
_nop_();//让总线稳定
SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
delay_5us();
SCL = 0;//拉低时钟总线, 占用总线继续通信
_nop_();
SDA = 1;//释放SDA数据总线。
_nop_();
}
/*检测从机应答*/
bit Test_ACK()
{
SCL = 1;
delay_5us();
if (SDA)
{
SCL = 0;
_nop_();
I2C_Stop();
return(0);
}
else
{
SCL = 0;
_nop_();
return(1);
}
}
/*发送一个字节*/
void I2C_send_byte(uchar byte)
{
uchar i;
for(i = 0 ; i < 8 ; i++)
{
SCL = 0;
_nop_();
if (byte & 0x80)
{
SDA = 1;
_nop_();
}
else
{
SDA = 0;
_nop_();
}
SCL = 1;
_nop_();
byte <<= 1; // 0101 0100B
}
SCL = 0;
_nop_();
SDA = 1;
_nop_();
}
/*I2C 读一字节*/
uchar I2C_read_byte()
{
uchar dat,i;
SCL = 0;
_nop_();
SDA = 1;
_nop_();
for(i = 0 ; i < 8 ; i++)
{
SCL = 1;
_nop_();
if (SDA)
{
dat |= 0x01; //
}
else
{
dat &= 0xfe; //1111 1110
}
_nop_();
SCL = 0 ;
_nop_();
if(i < 7)
{
dat = dat << 1;
}
}
return(dat);
}
/*I2C发送数据*/
bit I2C_TransmitData(uchar ADDR, DAT)
{
I2C_Start();
I2C_send_byte(AT24C02_ADDR+0);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(ADDR);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(DAT);
if (!Test_ACK())
{
return(0);
}
I2C_Stop();
return(1);
}
/*I2C接收数据*/
uchar I2C_ReceiveData(uchar ADDR)
{
uchar DAT;
I2C_Start();
I2C_send_byte(AT24C02_ADDR+0);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(ADDR);
Master_ACK(0);
I2C_Start();
I2C_send_byte(AT24C02_ADDR+1);
if (!Test_ACK())
{
return(0);
}
DAT = I2C_read_byte();
Master_ACK(0);
I2C_Stop();
return(DAT);
}
先提两句
这学期刚系统上完单片机的课程,手头又有一个STC15系列的最小系统板,就打算用这个最小系统板搞点事情。先从最简单的智能小车入手,一步一步开发新功能。但是由于未知的原因最小系统板用着烧录不了程序(后来好像因为作死真把他烧了),不能不转换方案拿51单片机接着做下去了。
大一学期在淘宝上买了一块51的学习板,和B站上的视频配套的拿来跟着学。当时是同学推荐刷清翔单片机的教程(没有夹带私货,大家不要锤我),有兴趣学习单片机的可以看一看。第一遍学的很浅只是跟着敲敲例程,然后这学期又开设了单片机课程,通过课程要求的实验算是有了一些理解。但是因为课本是针对15系列,回来再看当时学的51视频,得心应手很多(强烈建议大家多刷视频,多做实验或者具体的项目巩固知识)。过程中也发现15系列与51单片机还是有一定区别的。所以借此机会做个小结。
简介
单片机用的较多且核心的部分应该就是定时器部分了,如果第一遍学的话中断部分学的不错的话,定时器部分可以轻松一些。定时器在各种项目的具体功能甚至延时函数部分都有涉及,51和15的差别我在上篇文章稍微提到并附上了一些不错的博主写的关于定时器的介绍,有感兴趣的可以回去看看。转回正题,这篇主要以串口通信和IIC协议对15和51系列的使用横向对比。
串行通信
串行通信作为比较基础的通信方式,广泛应用于上位机测试、蓝牙等方面,按时钟控制方式分为同步通信和异步通信,按方式分类分为方式0、1、2、3。方式0位移位寄存器功能,可以用于扩展IO口实际应用不多,1、2、3是基于UART协议的异步通信收发器,只是位数有所不同,这里主要以方式1进行介绍。
首先,串行通信的前提是通信双方需要具有相同的波特率,这里可以参考每种生物都有一定的声音接收频率,生物之间相互交流信息不仅需要语言还需要发出的信号在接收频率范围内才能被正常接收。而波特率发生器的实现又绕回了之前我们提到的定时器,通常都是通过定时器设定初值和对串口寄存器设置实现串口功能。
好在我们常用的烧录软件有波特率发生器计算这个模块,具体的计算方法大家只需要在学习的过程中知道原理即可(要是考试考这个当我没说),为了避免重复造轮子,一般只需要在软件做好设定好就能生成。但是还需要注意几个地方才能正常使用。
1. 区别15系列和51系列单片机定时器的区别
STC-ISP这个定时器所有的生成代码的部分应该是基于15系列(博主测试15系列设置好基本不需要更改可以直接使用),其中的类似AUXR寄存器是51系列单片机没有的,需要大家单独设置或者对代码做相应改动删去这部分内容。
15系列定时器无外乎16位自动重装载、16位不可自动重装载、8位自动重装载等模式,根据是否可以引入外部中断还可以细分。
而51系列则是13位定时/技术计数,16位定时/计数和8位自动重装载
其实两个系列定时器的方式0、1都有所区别,初学者很容易误以为定时器功能一样而出现问题。
2.区别两个系列的时钟
一般的,15系列支持12分频和不分频设置,通过AUXR寄存器设置。
而51系列支持12分频和6分频即双倍速,可在烧录软件设置。
如果你要直接使用烧录软件生成的代码,设置好波特率,一般来说12分频且不设置双倍速51是也可以正常用的,但如果你设置不分频生成代码或者设置了双倍速用于51是会出问题的。
使用
一般来说使用波特率生成器只要设置好波特率注意使用的定时器,时钟频率不用特意修改,用于51的话AUXR部分直接删去就可以正常使用。数据位可以根据自己选择的串行通信方式设置。
小小的坑:STC-ISP生成的定时或波特率发生器的代码需要大家自己手动添加开启总中断和响应定时器中断的指令。
IIC协议
IIC协议课本上没有涉及,但是后面利用单片机做项目进行通信应该是绕不开的一个知识点,本人理解不深,只是根据网上的资料提几点需要注意的地方。
IIC协议几种信号表示:
写数据:
读数据:
读写切换时从机地址和起始信号要重复一次后面加读写方向
注意
主从机通信过程中,时钟线SCL的状态只能由主机拉高或释放,而SCL和数据线SDA是通过线与连接在一起的,且数据(SDA)只有在SCL为低电平时改变才有效(起始终止信号不满足这个前提),为了使从机发数据主机接收时正常,往往操作最后需要主机拉低时钟线释放数据线即SCL=0,SDA=1;
附部分代码:
/*5us延时*/
void delay_5us()
{
_nop_();
}
/*I2C初始化*/
void I2C_init()
{
SDA = 1;
_nop_();
SCL = 1;
_nop_();
}
/*I2C起始信号*/
void I2C_Start()
{
SCL = 1;
_nop_();
SDA = 1;
delay_5us();
SDA = 0;
delay_5us();
}
/*I2C终止信号*/
void I2C_Stop()
{
SDA = 0;
_nop_();
SCL = 1;
delay_5us();
SDA = 1;
delay_5us();
}
/*主机发送应答*/
void Master_ACK(bit i)
{
SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
_nop_(); // 让总线稳定
if (i) //如果i = 1 那么拉低数据总线 表示主机应答
{
SDA = 0;
}
else
{
SDA = 1; //发送非应答
}
_nop_();//让总线稳定
SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
delay_5us();
SCL = 0;//拉低时钟总线, 占用总线继续通信
_nop_();
SDA = 1;//释放SDA数据总线。
_nop_();
}
/*检测从机应答*/
bit Test_ACK()
{
SCL = 1;
delay_5us();
if (SDA)
{
SCL = 0;
_nop_();
I2C_Stop();
return(0);
}
else
{
SCL = 0;
_nop_();
return(1);
}
}
/*发送一个字节*/
void I2C_send_byte(uchar byte)
{
uchar i;
for(i = 0 ; i < 8 ; i++)
{
SCL = 0;
_nop_();
if (byte & 0x80)
{
SDA = 1;
_nop_();
}
else
{
SDA = 0;
_nop_();
}
SCL = 1;
_nop_();
byte <<= 1; // 0101 0100B
}
SCL = 0;
_nop_();
SDA = 1;
_nop_();
}
/*I2C 读一字节*/
uchar I2C_read_byte()
{
uchar dat,i;
SCL = 0;
_nop_();
SDA = 1;
_nop_();
for(i = 0 ; i < 8 ; i++)
{
SCL = 1;
_nop_();
if (SDA)
{
dat |= 0x01; //
}
else
{
dat &= 0xfe; //1111 1110
}
_nop_();
SCL = 0 ;
_nop_();
if(i < 7)
{
dat = dat << 1;
}
}
return(dat);
}
/*I2C发送数据*/
bit I2C_TransmitData(uchar ADDR, DAT)
{
I2C_Start();
I2C_send_byte(AT24C02_ADDR+0);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(ADDR);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(DAT);
if (!Test_ACK())
{
return(0);
}
I2C_Stop();
return(1);
}
/*I2C接收数据*/
uchar I2C_ReceiveData(uchar ADDR)
{
uchar DAT;
I2C_Start();
I2C_send_byte(AT24C02_ADDR+0);
if (!Test_ACK())
{
return(0);
}
I2C_send_byte(ADDR);
Master_ACK(0);
I2C_Start();
I2C_send_byte(AT24C02_ADDR+1);
if (!Test_ACK())
{
return(0);
}
DAT = I2C_read_byte();
Master_ACK(0);
I2C_Stop();
return(DAT);
}
举报