C51编译器
51单片机开发编译有很多,现在比较有名的是IAR for 8051编译器和KEIL for C51编译器,但这两个软件都不是免费的,官网提高的免费版是有使用限制的。另外还有一些编译器是免费的,但需要一定基础,初学者就不推荐使用了,等你翅膀硬了之后在自己配置编译环境吧。
简单介绍两个免费编译器...
C51编译器
51单片机开发编译有很多,现在比较有名的是IAR for 8051编译器和KEIL for C51编译器,但这两个软件都不是免费的,官网提高的免费版是有使用限制的。另外还有一些编译器是免费的,但需要一定基础,初学者就不推荐使用了,等你翅膀硬了之后在自己配置编译环境吧。
简单介绍两个免费编译器:可跨平台使用的开源编译器sdcc,有空可以自己查资料搭建一下,后期如果有时间我再做个教程吧。另外微软公司提供的Visual Studio Code这个免费又强大的代码编辑器配合一些三方插件工具也可以实现各种单片机编译功能,效果也不错,这个也以后再另说吧。IAR for 8051编译器和KEIL for C51编译器在官网都可以免费下载,可自行下载安装,我这里后续的内容以KEIL for C51编译器为例。
C语言变量类型和数据范围
有C语言基础的朋友可以不看或简单看一下。
什么是变量?变量自然和常量是相对的。常量就是1、2、3、4.5、10.6…等已经确定的数据或字符(串),而变量则跟中小学阶段方程式中的a,b,c……x,y,z是一样,它可以是1,也可以让它是2,或者任何其他数字。
以前数学里边有的正数、负数、整数和小数可以是无穷的。但在计算机系统中由于内部空间的限制,在C语言里,除名字和数学里学的不一样外,还对数据范围也进行了一定限制。
在C语言中数据基本类型分为字符型、整型、长整型以及浮点型,它们的数值范围不一。并且C51中的数据范围和常规计算机平台的略有不同,以后可能在其他单片机中又会有些不一样,这都是单片机硬件属性决定的,这点大家不用死记,做个了解,以后编程时多注意一下就行,具体问题具体分析。
在我们平时编程的时候也要考虑合理使用数据类型,为变量合理分配数据类型一个可以避免数据超限,另外也可以节约内存空间。优化程序的时候也应考虑数据类型的合理合规。
C51数据类型和范围查看下图:
C语言基本运算符简介
和小学学习数学时的加、减、乘、除等运算符号一样,在C语言中也包含这些运算,但是有些表达方法不一样,并且还有其他额外的一些运算符号。在C语言编程中,加、减、乘、除和取余数的符号分别是:+、-、*、/、%。此外,C语言中还有额外的两个运算符++和--,它们的用法是一样的,一个是自加1,一个是自减1,以上的运算符称为算术运算符,它的具体功能如下:
与数学中的关系运算符一样,C语言中也有关系运算符只是他们的写法你不一定认识,比如>,Vcc时,Vin对电流源不起控制作用,这是由运放本身固有特性所决定的。如果要增大调节范围,就必须提高运放的Vcc和Vee。
2.R5的数值与电流大小有关,但不是式(4)给出的那种线性关系。当R5减小到一定数值的时候(如R5=50 Ω),电压控制恒流源的最大电流达到负载最大电流(当Vin=Vcc时,Iout=Iout,max),而且Iout动态范围会变小。R5每减小一半,动态范围就缩小一半。当R5=0Ω时,电压控制恒流源的电流为负载最大电流(Iout,max),不随输入电压Vin变化。
3.R1影响恒流源起始电流的大小(即Vin=0V时),当R1=1MΩ 时,起始电流为0mA;R1=1.6MΩ,起始电流为6.2mA;R1=1.9MΩ时,起始电流为14.2mA。故为避免零点漂移,应使R1在1MΩ左右。
4.R4也影响起始电流的大小,当R4>1MΩ时,R1起主要调节作用。
5.R3的变化影响调节的范围,如R3=300KΩ时,可调的电压为5V~10V。
三极管构成VCCS的电流大小与工作偏置电压直接相关。当Vcc改变的时候,流入负载电阻的电流会随之发生改变,而且其输出电阻较小,这些因素使得VCCS的工作特性变差。由于每个三极管的伏安特性并不完全一致,其放大倍数β也不完全相同,三级管的参数也会随温度变化,致使最后的恒流特性差异更大。此外,电流比较大时,三极管功耗非常大,使得电路效率不高,容易烧坏三极管。
这个系统使用运算放大器搭建VCCS,由于运放有差分对输入,可以抑止共模信号,对温度漂移有良好的抑制作用,有利于减少干扰。另外,恒流源的工作电流与Vcc、Vee无关,只与和它构成反馈的电阻阻值有关。运算放大器的放大倍数差异并不会影响其最终的恒流特性,稳定性较好。
首先问大家两个问题:
8位单片机定义一个int型变量,占几个字节?
32位单片机定义一个char型变量,占几个字节?
我敢说很多人都不知道,或者存在疑惑。具体占多少字节,其实通过手动验证一下就知道了。 今天结合Keil C51(AT89C51),以及Keil MDK(STM32)为大家验证一下 char、short、int、float、double 到底占几字节空间。
Keil C51、8位单片机
这一节基于Keil C51工具,8位单片机(AT89C51)进行测试。
//char a;
void main(void)
{
while(1)
{
}
}
这是一个很简单的Demo,只针对变量占用空间大小进行测试,主要是对比data的大小,初始值为9。(其中系统【启动文件】占用了9字节RAM空间) 1.char占1字节
定义变量:
char a;
编译结果:
Program Size: data=10.0 xdata=0 code=17
2.short占2字节
定义变量:
short a;
编译结果:
Program Size: data=11.0 xdata=0 code=17
3.int占2字节
定义变量:
int a;
编译结果:
Program Size: data=11.0 xdata=0 code=17
4.float占4字节
定义变量:
float a
编译结果:
Program Size: data=13.0 xdata=0 code=17
5.double占4字节
定义变量:
double
编译结果:
Program Size: data=13.0 xdata=0 code=17
Keil MDK、32位单片机
这一节基于Keil MDK工具,32位单片机(STM32)进行测试。
//char a;
int main(void)
{
while(1)
{
// a++;
}
}
这也是一个很简单的Demo,主要是对比RW-data的大小,初始值为0。(其中 ZI-data=1024 为系统分配的栈空间大小) 1.char占1字节
定义变量:
char a;
编译结果:
Program Size: Code=512 RO-data=436 RW-data=4 ZI-data=1028
啥?占了4个字节? 答案:只占了一个字节,因为32位机是4字节为一个“单元”,一个 char 只占用了其中1字节(类似结构体占用空间大小,这里还牵涉到大小端对齐模式)。 再举一个例子:定义4个 char型变量,还是占用4字节空间(short原理一样)再次提示:
注意4字节为一个“单元”,超过这个单元就分配到“下一个单元”,比如:
char a;
int b;
char c;
这里就会占用12(3 * 4)字节空间(a、b、c各自占4字节)。
2.short占2字节
定义变量:
short a;
编译结果:
ProgramSize:Code=512RO-data=436RW-data=4ZI-data=1028
这里和 char 类似,如果定义两个 short 也是占用4个字节。 3.int占4字节
定义变量:
int a;
编译结果:
ProgramSize:Code=512RO-data=436RW-data=4ZI-data=1028
32位机,int占用4字节没什么说的。 4.float占4字节
定义变量:
float a;
编译结果:
ProgramSize:Code=520RO-data=436RW-data=4ZI-data=1028
5.double占8字节
定义变量:
double a;
编译结果:
ProgramSize:Code=1156RO-data=436RW-data=8ZI-data=1024
总结
上面验证情况在Keil C51、 8位单片机(AT89C51)中:
char:占1字节
short、int:占2字节
float、double:占4字节
在Keil MDK、 32位单片机(STM32)中:
char:占1字节
short:占2字节
int、float:占4字节
double:占8字节
通过对比上面几个变量,以及编译结果,得出一些结论。 1.变量占多少字节,与处理器(以及编译器)有关; 2.浮点数运算更占代码空间,且double比float更占空间。上面基于32位机中进行了a++;运算,明显float、double打码量(code)更大。 3.变量数据对齐规则和结构体一样。经典的面试题:计算下面结构体占用多少字节?
struct Str
{
char a;
short b;
int c;
char d;
};
这里面还有很多细节内容,可能很多人都没有深入研究过,感兴趣的朋友可以自己实验研究一下。
上一课我们的第一个项目完成了,可能有懂C语言的朋友会说,"这和PC机上的C语言没有多大的区别呀"。的确没有太大的区别,C语言只是一种程序语言的统称,针对不同的处理器相关的C语言都会有一些细节的改变。编写PC机的C程序时,如要对硬件编程你就必须对硬件要有一定的认识,51单片机编程就更是如此,因它的开发应用是不可与硬件脱节的,所以我们先要来初步认识一下51苾片的结构和引脚功能。MSC51架构的芯片种类很多,具体特点和功能不尽相同(在以后编写的附录中会加入常用的一些51芯片的资料列表),在此后的教程中就以Atmel公司的AT89C51和AT89C2051为中心对象来进行学习,两者是AT89系列的典型代表,在爱好者中使用相当的多,应用资料很多,价格便宜,是初学51的首选芯片。嘿嘿,口水多多有点卖广告之嫌了。图2-1 AT89C51和AT89C2051引脚功能图
4KB可编程Flash存储器(可擦写1000次)
2KB可编程Flash存储器(可擦写1000次)
三级程序存储器保密
两级程序存储器保密
静态工作频率:0Hz-24MHz
静态工作频率:0Hz-24MHz
128字节内部RAM
128字节内部RAM
2个16位定时/计数器
2个16位定时/计数器
一个串行通讯口
一个串行通讯口
6个中断源
6个中断源
32条I/O引线
15条I/O引线
片内时种振荡器
1个片内模拟比较器
表2-1 AT89C51和AT89C2051主要性能表
图2-1中是AT89C51和AT89C2051的引脚功能图。而表2-1中则是它们的主要性能表。以上可以看出它们是大体相同的,由于AT89C2051的IO线很少,导致它无法外加RAM和程序ROM,片内Flash存储器也少,但它的体积比AT89C51小很多,以后大家可根据实际需要来选用。它们各有其特点但其核心是一样的,下面就来看看AT89C51的引脚具体功能。
1.电源引脚Vcc 40 电源端GND 20 接地端*工作电压为5V,另有AT89LV51工作电压则是2.7-6V, 引脚功能一样。
2.外接晶体引脚图2-2 外接晶体引脚
XTAL1 19XTAL2 18XTAL1是片内振荡器的反相放大器输入端,XTAL2则是输出端,使用外部振荡器时,外部振荡信号应直接加到XTAL1,而XTAL2悬空。内部方式时,时钟发生器对振荡脉冲二分频,如晶振为12MHz,时钟频率就为6MHz。晶振的频率可以在1MHz-24MHz内选择。电容取30PF左右。
*型号同样为AT89C51的芯片,在其后面还有频率编号,有12,16,20,24MHz可选。大家在购买和选用时要注意了。如AT89C51 24PC就是最高振荡频率为24MHz,40P6封装的普通商用芯片。
3.复位 RST 9在振荡器运行时,有两个机器周期(24个振荡周期)以上的高电平出现在此引腿时,将使单片机复位,只要这个脚保持高电平,51芯片便循环复位。复位后P0-P3口均置1引脚表现为高电平,程序计数器和特殊功能寄存器SFR全部清零。当复位脚由高电平变为低电平时,芯片为ROM的00H处开始运行程序。常用的复位电路如图2-3所示。
*复位操作不会对内部RAM有所影响。图2-3 常用复位电路4.输入输出引脚
(1) P0端口[P0.0-P0.7] P0是一个8位漏极开路型双向I/O端口,端口置1(对端口写1)时作高阻抗输入端。作为输出口时能驱动8个TTL。
对内部Flash程序存储器编程时,接收指令字节;校验程序时输出指令字节,要求外接上拉电阻。
在访问外部程序和外部数据存储器时,P0口是分时转换的地址(低8位)/数据总线,访问期间内部的上拉电阻起作用。
(2) P1端口[P1.0-P1.7] P1是一个带有内部上拉电阻的8位双向I/0端口。输出时可驱动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。
对内部Flash程序存储器编程时,接收低8位地址信息。
(3) P2端口[P2.0-P2.7] P2是一个带有内部上拉电阻的8位双向I/0端口。输出时可驱动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。
对内部Flash程序存储器编程时,接收高8位地址和控制信息。
在访问外部程序和16位外部数据存储器时,P2口送出高8位地址。而在访问8位地址的外部数据存储器时其引脚上的内容在此期间不会改变。
(4) P3端口[P3.0-P3.7] P2是一个带有内部上拉电阻的8位双向I/0端口。输出时可驱动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。
对内部Flash程序存储器编程时,接控制信息。除此之外P3端口还用于一些专门功能,具体请看 表2-2.。
*P1-3端口在做输入使用时,因内部有上接电阻,被外部拉低的引脚会输出一定的电流。
P3.0
串行通讯输入(RXD)
P3.1
串行通讯输出(TXD)
P3.2
外部中断0( INT0)
P3.3
外部中断1(INT1)
P3.4
定时器0输入(T0)
P3.5
定时器1输入(T1)
P3.6
外部数据存储器写选通WR
P3.7
外部数据存储器写选通RD
表2-2 P3端口引脚兼用功能表
呼!一口气说了那么多,停一下吧。嗯,什么?什么叫上拉电阻?上拉电阻简单来说就是把电平拉高,通常用4.7-10K的电阻接到Vcc电源,下拉电阻则是把电平拉低,电阻接到GND地线上。具体说明也不是这里要讨论的,接下来还是接着看其它的引脚功能吧。
5.其它的控制或复用引脚
(1) ALE/PROG 30 访问外部存储器时,ALE(地址锁存允许)的输出用于锁存地址的低位字节。即使不访问外部存储器,ALE端仍以不变的频率输出脉冲信号(此频率是振荡器频率的1/6)。在访问外部数据存储器时,出现一个ALE脉冲。对Flash存储器编程时,这个引脚用于输入编程脉冲PROG
(2) PSEN 29 该引是外部程序存储器的选通信号输出端。当AT89C51由外部程序存储器取指令或常数时,每个机器周期输出2个脉冲即两次有效。但访问外部数据存储器时,将不会有脉冲输出。
(3) EA/Vpp 31 外部访问允许端。当该引脚访问外部程序存储器时,应输入低电平。要使AT89C51只访问外部程序存储器(地址为0000H-FFFFH),这时该引脚必须保持低电平,而要使用片内的程序存储器时该引脚必须保持高电平。对Flash存储器编程时,该引脚用于施加Vpp编程电压。Vpp电压有两种,类似芯片最大频率值要根据附加的编号或芯片内的特征字决定。具体如表2-3所列。
印刷在芯片面上的型号
AT89C51
xxxx
YYWW
AT89LV51
xxxx
YYWW
AT89C51
xxxx-5
YYWW
AT89LV51
xxxx-5
YYWW
片内特征字
030H=1EH
030H=1EH
030H=1EH
030H=1EH
031H=51H
031H=61H
031H=51H
031H=61H
032H=FFH
032H=FFH
032H=05H
032H=05H
表2-3 Vpp与芯片型号和片内特征字的关系
看到这您对AT89C51引脚的功能应该有了一定的了解了,引脚在编程和校验时的时序我们在这里就不做详细的探讨,通常情况下我们也没有必要去撑握它,除非你想自己开发编程器。下来的课程我们要开始以一些简单的实例来讲述C程序的语法和编写方法技巧,中间穿插相关的硬件知识如串口,中断的用法等等.
ISD4004语音芯片C51驱动程序源代码
/*spi isd4004.h*/#include "reg51.h"#include "intrins.h"sbit _cs = p0^0;sbit _sclk= p0^3;sbit _mosi= p0^1;sbit _miso= p0^2;sbit _rac = p0^4;sbit _int = p0^5;void delay(unsigned int i) //延时程序{while(i--);}void stopmode() //停止{unsigned char m,i,j;_cs=1;_sclk=0;_cs=0;m=0x30;for(i=0;i{m=_cror_(m,1);j=m_mosi=cy;_sclk=0;_sclk=1;}_cs=1;}void powerdown() //下电{unsigned char m,i,j;_cs=1;_sclk=0;_cs=0;m=0x10;for(i=0;i{m=_cror_(m,1);j=m_mosi=cy;_sclk=0;_sclk=1;}_cs=1;}void powerup() 上电{unsigned char m,i,j;_cs=1;_sclk=0;_cs=0;m=0x20;for(i=0;i{m=_cror_(m,1);j=m_mosi=cy;_sclk=0;_sclk=1;}_cs=1;}void record4004(unsigned int address) 录音 address--录音地址 0---2400{unsigned char i,m,j;unsigned int datasoute=0;powerup();delay(5118);//上电延时powerup();delay(5118);//上电延时delay(5118);//上电延时_cs=1;_sclk=0;m=0xa0;_cs=0;for(i=0;i{address=_iror_(address,1);datasoute=address_mosi=cy;_sclk=0;_sclk=1;}for(i=0;i{m=_cror_(m,1);j=m_mosi=cy;_sclk=0;_sclk=1;}_cs=1;_sclk=0;m=0xb0;_cs=0;for(i=0;i{m=_cror_(m,1);j=m_mosi=cy;_sclk=0;_sclk=1;}_cs=1;p0=0xff;datasoute=0;while(_int==1) //存储地址换行标志{if(_rac) {delay(20000); datasoute++; } //记录本次录音所占的行数(也就是本次录音有多大)//// 在这里应该加上自己的程序,就是录音退出程序//}}//while end;void audioout(unsigned int address) //放音程序{unsigned char i,m,j;unsigned int datasoute;powerup();_cs=1;_sclk=0;_cs=0;m=0xe0;for(i=0;i{address=_iror_(address,1);datasoute=address_sclk=0;_mosi=cy;_sclk=1;}for(i=0;i{m=_cror_(m,1);j=m_sclk=0;_mosi=cy;_sclk=1;}_cs=1;_sclk=0;m=0xf0;_cs=0;for(i=0;i{m=_cror_(m,1);j=m_sclk=0;_mosi=cy;_sclk=1;}_cs=1;p0=0xff;while(_int==1){}//while end;}main(){record4004(0);audioout(0);while(1);}