第五章 单片机的C语言程序设计及仿真调试
任课教师:刘忠国 山东大学课程中心网站: http://course.sdu.edu.cn/G2S/stcmcu.cc 宏晶官方网站:http://www.stcmcu.com STC单片机编译(汇编)/编程(烧录)/仿真工具说 明书; stc15系列单片机器件手册等 keil μvision软件下载及指导手册(Help→μvision Help) http://www.keil.com/ Keil Software –Cx51 编译器用户手册: Cx51编译 器--对传统和扩展的8051微处理器的优化的C 编译器和库参考
第五章 单片机的C语言程序设计及仿真调试 本章学习目标 掌握单片机C语言程序中的常用功能 掌握Keil C的程序设计 掌握STC15F2K60S2单片机C语言程序调试过程
第五章 单片机的C语言程序设计及仿真调试 目 录 §5.1 单片机C语言程序中的常用功能 5.1.1 逻辑运算和位运算 5.1.2 预处理 目 录 §5.1 单片机C语言程序中的常用功能 5.1.1 逻辑运算和位运算 5.1.2 预处理 §5.2 Keil C 和 ANSI C 5.2.1 Keil C51扩展关键字 5.2.2 扩展I/O口的使用 5.2.3 Keil C51函数 5.2.4 STC15F2K60S2单片机C51程序框架 §5.3 单片机C语言程序调试
汇编语言和C语言的选择问题 设计规模较小的嵌入式应用系统时,可以使用汇 编语言。因为代码一般不长,且较简单。 当程序比较复杂,且没有很好的注释时,使用汇 编语言编写的程序,可读性和可维护性会很差, 代码的可重用性也比较低。 使用C语言编程,编写简单、直观易读、便于维护、 通用性好。 在控制任务比较复杂或者具有大量运算的系统中, C语言优势明显。由于模块化,用C语言编写的程 序具有很好的可移植性。
§5.1 单片机C语言程序中的常用功能 5.1.1 逻辑运算和位运算 1、逻辑运算符 5.1.1 逻辑运算和位运算 1、逻辑运算符 逻辑运算符包括与(&&), 或(||), 非(!)3种, 用于逻辑运算。 逻辑运算符的表达式, 返回0表示“假”, 返回1表示“真”。 按布尔逻辑值(真假)运算 与运算符(&&) 功能:两个条件同时满足时(即两个条件都为真时), 结果才为真。 如一个程序在同时满足条件a<10和b==7时,则执行某 些操作,应使用关系运算符和逻辑与运算符(&&) 来 写这个条件的代码: (a<10)&&(b==7)。
1、逻辑运算符 或运算符(||) 功能:检查两个条件中是否有一个为真,只要有一个 条件为真,运算结果就为真。 对条件a<10和b==7, 若任一条件为真,程序执行某操作,则条件代码为: (a<10) || (b==7); 逻辑非运算符(!) 功能:表示对表达式的真值取反。 例如,如果变量s小于10,程序需执行某些操作,则 条件代码如下: (s<10) 也可以写成: (!(s>=10)) //s不大于等于10
2、位运算符 很多系统程序常要求进行位(bit)运算或处理。C语言 提供了六种位运算符:按位与(&)、按位或(|)、按位 异或(^)、取反(~)、左移(<<)和右移(>>) 。 (1)按位“与”运算: 按位与运算符“&” 是双目运算符。 功能: 参与运算的两数各对应二进制位相与。只有对 应的两位均为1时,结果位才为1,否则为0。 例如,9&5的算式:00001001&00000101=00000001 按位与运算通常用来对某些位清0或保留某些位。 例把16位数a的高8位清0,保留低8位,可作运算:a&255 (255的二进制数为0000000011111111)。
(2)按位“或”运算 按位或运算符“|”是双目运算符。 功能是参与运算的两数各对应的二进制位相或。 只要对应的两个位有一个为1时,结果位就为1。 例如,9|5的算式: 00001001(十进制为9) | 00000101(十进制为5) 00001101 (十进制13) 或运算通常用来对某些位置1。
(3)按位“异或”运算 按位异或运算符“^”是双目运算符。 功能是参与运算的两数各对应的二进制位相异或。 当两个对应的位相异时,结果为1。 例如,9^5的算式: 00001001(十进制为9) ^ 00000101(十进制为5) 00001100 (十进制12) 异或运算通常用来对某些位取反。
(4)求反运算 (5)左移运算 求反运算符“~”为单目运算符,具有右结合性。 功能是对参与运算的数的各二进制位按位求反。 例如,~9的运算为: ~(0000000000001001)结果为:1111111111110110 (5)左移运算 左移运算符“<<”是双目运算符。 功能:把“<<”符左边运算数的各二进制位都左移若干位, “<<” 符右边的数指定移动位数,高位丢弃,低位补0。 例如:a<<4 指把a的各二进位向左移动4位。 如a=00000011(十进制3), 左移4位后为00110000(十进制48)。
(6)右移运算 右移运算符“>>”是双目运算符。 功能是把“>>”左边的运算数的各二进制位全部右 移若干位,“>>”右边的数指定移动的位数。 例如,设 a=15,a>>2 000001111右移2位为00000011(十进制3)。 对于有符号数,在右移时,符号位将随同移动。当 为正数时,最高位补0,而为负数时,符号位为1, 最高位是补0或是补1 取决于编译系统的规定。
5.1.2 预处理 以“#”号开头的命令是预处理命令。C语言的预处理功 能包括宏定义#define, 文件包含#include, 条件编译等。 合理地使用预处理功能,可以使得编写的程序便于阅 读、修改、移植和调试,也利于模块化程序设计。 1、宏定义(#define) 在C语言源程序中允许用一个标识符来表示一个字符 串,称为宏。被定义为宏的标识符称为宏名。 在编译预处理时,对程序中所有出现的宏名,都用宏定 义中的字符串去代换,这称为宏代换或宏展开。 宏代换是由预处理程序自动完成的。在C语言中,宏 分为有参数和无参数两种。
(1)无参宏定义 无参宏的宏名后不带参数。其定义的一般形式为: #define 标识符 字符串 其中,标识符为所定义的宏名。字符串可以是常数,表 达式,格式串等。 若要终止宏定义,可用#undef命令: #undef 标识符 符号常量的定义就是一种无参宏定义。此外,常对程 序中反复使用的表达式进行宏定义。 例如: #define PI 3.14159 #define M (y*y+3*y) 程序中的语句: L=2*PI; s=3*M+M/2; 经宏代换后为: L=2*3.14159; s=3*(y*y+3*y)+(y*y+3*y)/2
(2)带参宏定义 在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。 带参宏定义的一般形式为: #define 宏名(形参表) 字符串 在字符串中含有各个形参。例如: #define MAX(a,b) (a>b)?a:b //取a和b的最大数 带参宏调用的一般形式为: 宏名(实参表); 例如,程序中的语句: max=MAX(x,y); 经宏代换后为: max=(x>y)?x:y;
2、文件包含(#include) 文件包含的一般形式为: #include "文件名" 功能是把指定文件插入该命令行位置取代该命令行,从 而把指定的文件和当前的源程序文件连成一个源文件。 说明: (头)文件名可用双引号括起来(用户头文件), 也可 用尖括号括起来(系统头文件)。如:#include “stdio.h”, #include <math.h> 。 二者区别: 用尖括号表示在包含文件目录中去查找(包含 目录可由用户在开发环境中设置, 一般在\INC\), 而不在 源文件目录去查找;用双引号则表示首先在当前源文件 目录中查找, 若未找到才到包含目录中去查找。
3、条件编译 条件编译就是按不同的条件去编译不同的程序部分, 从而产生不同的目标代码文件。 条件编译对于程序的移植和调试(可以分段调试)非常 有用。特别在操作系统的裁减中,经常使用条件编译。 (1)第一种形式:#ifdef 标识符 程序段1 #else 程序段2 #endif 功能是:如果标识符已被#define命令定义过,则对 程序段1进行编译;否则对程序段2进行编译。如果没 有程序段2(它为空),本格式中的#else可以没有。
3、条件编译 (2)第二种形式: #ifndef 标识符 程序段1 #else 程序段2 #endif 功能是:如果标识符未被#define命令定义过则对 程序段1进行编译,否则对程序段2进行编译。这 与第一种形式的功能正相反。
3、条件编译 (3)第三种形式: #if 常量表达式 程序段1 #else 程序段2 #endif 功能是,如果常量表达式的值为真(非0),则对程 序段1进行编译,否则对程序段2进行编译。因此可 以使程序在不同条件下,完成不同的功能。
§5.2 Keil C 和 ANSI C Keil C51基本语法与ANSI C相同,针对8051内核单片 机硬件等进行了扩展。 _at_, sbit, sfr, bit, sfr16, code, idata, bdata, data, xdata, pdata, small, compact, large, using, reentrant, interrupt, alien, _task_, (keil帮助文件介绍21个) _priority_, far alien(声明与Intel编译器PL/M51兼容的函数)。far声明3字节扩展地址。 _task_, _priority_分别定义实时多任务操作系统的实时任务及其优先级。 下面分类介绍常用关键字: (参考keil软件帮助文件books window →Cx51 Compiler User's Guide) (或参考keil官网http://www.keil.com/support/man/docs/c51/) (或参考“keil software Cx51 编译器用户手册09.2001-对传统和扩展的8051微处理器的优化的C编译器和库参考”)
1、存储区域(Memory Areas) (1)程序存储器 8051结构支持几个物理分开的程序和数据存储空间, 这 些存储空间可能: 可读不可写; 可读写; 读写比别的存储 空间快。 (1)程序存储器: 程序(CODE)存储区只读不能写 ,可在8051CPU片内或 片外或都有, 根据8051派生硬件决定, STC15F2K60S只 在片内。 最多可有64K字节的程序存储区。存储程序代码, 包括 所有函数和库, 常数变量。8051可执行程序只保存在程 序存储区。 C51编译器中, 通过存储类型标识符code可访问程序存 储器。 关键字
1、存储区域(Memory Areas) (1)程序存储器 (1)程序存储器: 通过存储类型标识符code可访问程序存储器。 code用来限定常量或函数的存储类型, 而程序的函数默 认为code存储类型, 并不需code限定符(也可写上)。code 一般只用来定义常量, 例: static code unsigned char DPY_TAB[16]={0X3f, 0X06, 0X5b, 0X4f, 0X66, 0X6d, 0X7d, 0X07, 0X7f, 0X6f, 0X77, 0X7c, 0X39, 0X5e, 0X79, 0X71}; // 用code定义共阴极数码管显示0~9,A~F时对应的字 模 关键字
1、存储区域(Memory Areas) (2)内部RAM 位于51片内,可读写。根据51种类不同,最多256字节。 低128字节可直接寻址,而高128字节只能间接寻址。 可用以下关键字分为3种存储类型: data:直接寻址区,内部RAM的低128字节, 地址范围是: 00H~7FH。 idata: 间接寻址区, 包括整个内部RAM区, 256字节, 地 址范围为00H~0FFH。 bdata: 可位寻址区,地址范围为20H~2FH。 特殊功能寄存器(0x80~0xFF)单独一类,见后。
1、存储区域(Memory Areas)(3)外部数据存储器 (3)外部数据存储器:可读写。采用数据指针访问,比 访问内部数存要慢,最多可达64K字节。外部RAM可 由以下关键字标识分为2种存储类型: : xdata:可指定多达64KB的外部直间接寻址区, 地址范 围0000H~0FFFFH。 xdata 可访问外部数存64K字节的任意位置, 只能通 过数据指针(MOVX @DPTR)来访问。 pdata: 可访问外部数存的一页即256字节, 通过R0和 R1 (MOVX @Ri)来访问(有时可能需P2指出高8位 地址)。 (片内XRAM只能访问00- 0FFh)。 (对物理上(且逻辑上)在外部的数 存)。 51对外设采取统一编址, 外设也占用此空间, 可通过访问外部数存来控制外设----存储器映射I/O, 所以可用空间可能不足64K字节。
1、存储区域(Memory Areas) 存储模式 (Small, large, Compact) 数据存储类型的指定: 变量或函数参数存储类型可由存储模式 (Small, large, Compact) (Options for Target ‘Target 1’...选项)指定缺 省存储类型; 在small模式下, 函数参数和局部变量位于由data定 义的单片机片内数据RAM(00~7FH)中; 在compact模式下, 函数参数和局部变量位于pdata 定义的扩展数据RAM中(访问用MOVX @Ri)。 在large模式下, 函数参数和局部变量位于xdata定义 的扩展数据RAM中(访问用MOVX @DPTR) 数据
1、存储区域(Memory Areas) 存储模式 (Small, large, Compact) 数据存储类型的指定: 变量或函数参数存储类型可由存储模式 (Small, large, Compact) (Options for Target ‘Target 1’...选项)指定缺 省存储类型; 也可由关键字code、data、idata、bdata、xdata、 pdata直接声明指定。 例如: unsigned char data buffer; char code array[ ]=“hello!”; unsigned char xdata arr[10][4][4]; 数据
1、存储区域(Memory Areas) (4)特殊功能寄存器 (4)特殊功能寄存器(SFR) (128字节: 0x80~0xFF) 单片机特殊功能寄存器(SFR)可位寻址,字节寻址或字 寻址, 用来控制定时器, 计数器, 串口, I/O及其他部件。 通过关键字sfr, sfr16和sbit声明数据类型,访问SFR。 sfr:字节寻址。语法如下: sfr sfr_name = int_constant; 如 sfr P0=0x80; 0x80为P0口寄存器的地址, “=” 后为 常数用来指定地址, 且其范围必须位于80H到~FFH。 sfr16:字寻址。 如sfr16 DPTR=0x82 ;指定DPTR地址DPL=0x82, DPH=0x83。
(4)特殊功能寄存器(SFR) (128字节: 0x80~0xFF) sbit:位寻址 用于声明可位寻址的特殊功能寄存器位变量,三种方法: 方法1:sbit bitname=sfr_name^bit_number; sfr_name必须是已定义SFR的名字, bit_number是位号(0~7)。 如:sbit CY=PSW^7; //定义CY为PSW的第7位。 方法2:sbit bitname=sfr_address^bit_number; sfr_address是SFR所在地址(0x80~0xff), bit_number是位号(0~7)。 如:sbit OV=0xD0^2;//定义PSW中的OV位 方法3:sbit bitname=bit_address; 其中,bit_address是位地址。 如: sbit EA=0xAF; //第0xAF位为IE寄存器的EA位 (见46页特殊功能寄存器SFR中位地址) IE字节地址A8H IE位地址 AFH AEH ADH ACH ABH AAH A9H A8H 寄存器IE位名称 EA ELVD EADC ES ET1 EX1 ET0 EX0
1、存储区域(Memory Areas) (4)特殊功能寄存器 Keil Cx51没有预先定义SFR的名字, 而是提供了一个 包含所有特殊功能寄存器和它们的位定义的头文件 reg51.h (在安装文件夹 ...\Keil\C51\INC)。通过包含头 文件可很容易的进行新的扩展。 附录C提供了STC15F2K60S2单片机的头文件stc15.h 的内容,其中包含了标准8051单片机寄存器的定义, 编程时只需包含这一个文件即可。 该文件可从www.mcu001stcmcu. com中下载。在STC 新ISP软件V6.85I中选择菜单选项 “头文件”即可找到 STC各系列的头文件(包括stc15.h)。
1、存储区域(Memory Areas) (4)特殊功能寄存器 STC15F2K60S2单片机的头文件stc15.h的内容, /*--------- 8051内核特殊功能寄存器 -------------*/ sfr ACC = 0xE0; //累加器 sfr B = 0xF0; //B 寄存器 sfr PSW = 0xD0; //程序状态字寄存器 sbit CY = PSW^7; //进位标志位 sbit AC = PSW^6; //辅助进位标志位 sbit F0 = PSW^5; //用户标志位0 sbit RS1 = PSW^4; //工作寄存器组选择控制位 sbit RS0 = PSW^3; //工作寄存器组选择控制位 sbit OV = PSW^2; //溢出标志位 sbit F1 = PSW^1; //用户标志位1 sbit P = PSW^0; //奇偶标志位 sfr SP = 0x81; //堆栈指针寄存器 sfr DPL = 0x82; //数据指针0低字节 sfr DPH = 0x83; //数据指针0高字节 /*------------ 系统管理特殊功能寄存器 -------------*/ sfr PCON = 0x87; //电源控制寄存器 sfr AUXR = 0x8E; //辅助寄存器 sfr INT_CLKO = 0x8F; //外部中断和时钟输出控制寄存器 sfr AUXR1 = 0xA2; //辅助寄存器1 sfr CLK_DIV = 0x97; //时钟分频控制寄存器 sfr BUS_SPEED = 0xA1; //总线速度控制寄存器 sfr WKTCL= 0xAA; //掉电唤醒专用定时器低字节 sfr WKTCH= 0xAB; //掉电唤醒专用定时器高字节 /*----------- 中断控制特殊功能寄存器 --------------*/ sfr IE = 0xA8; //中断允许寄存器 sbit EA = IE^7; //总中断允许位 sbit ELVD = IE^6; //低电压检测中断控制位 sbit EADC = IE^5; //ADC中断允许控制位 sbit ES = IE^4; //串口1中断允许位 sbit ET1 = IE^3; //定时器1溢出中断允许位 sbit EX1 = IE^2; //外部中断1允许位 sbit ET0 = IE^1; //定时器0溢出中断允许位 sbit EX0 = IE^0; //外部中断0允许位 sfr IE2 = 0xAF; //中断允许寄存器2 sfr IP = 0xB8; //中断优先级寄存器 sbit PPCA = IP^7; // PCA中断优先级控制位 sbit PLVD = IP^6; //低电压检测中断优先级控制位 sbit PADC = IP^5; //ADC中断优先级控制位 sbit PS = IP^4; //串口1中断优先级控制位 sbit PT1 = IP^3; //定时器1中断优先级控制位 sbit PX1 = IP^2; //外部中断1优先级控制位 sbit PT0 = IP^1; //定时器0中断优先级控制位 sbit PX0 = IP^0; //外部中断0优先级控制位 sfr IP2 = 0xB5; //第二中断优先级寄存器低字节 /*--------------- I/O 口特殊功能寄存器 -------------------*/ sfr P0 = 0x80; //P0口寄存器 sfr P0M1 = 0x93; //P0口工作模式寄存器1 sfr P0M0 = 0x94; //P0口工作模式寄存器0 sfr P1 = 0x90; //P1口寄存器 sfr P1M1 = 0x91; //P1口工作模式寄存器1 sfr P1M0 = 0x92; //P1口工作模式寄存器0 sfr P1ASF = 0x9D; //P1口模拟量功能设置寄存器 sfr P2 = 0xA0; //P2口寄存器 sfr P2M1 = 0x95; //P2口工作模式寄存器1 sfr P2M0 = 0x96; //P2口工作模式寄存器0 sfr P3 = 0xB0; //P3口寄存器 sbit T1 = P3^5; //定时器1外部输入 sbit T0 = P3^4; //定时器0外部输入 sbit INT1 = P3^3; //外部中断1 sbit INT0 = P3^2; //外部中断0 sbit TXD = P3^1; //串行输入通道 sbit RXD = P3^0; //串行输出通道 sfr P3M1 = 0xB1; //P3口工作模式寄存器1 sfr P3M0 = 0xB2; //P3口工作模式寄存器0 sfr P4 = 0xC0; //P4口寄存器 sfr P4M1 = 0xB3; // P4口工作模式寄存器1 sfr P4M0 = 0xB4; // P4口工作模式寄存器0 sfr P5 = 0xC8; //P5口(只有P5.3 P5.2 P5.1 P5.0) sfr P5M1 = 0xC9; // P5口工作模式寄存器1 sfr P5M0 = 0xCA; // P5口工作模式寄存器0 sfr P_SW2=0xBA; //外设功能切换控制寄存器 /*----------------- 定时器特殊功能寄存器 -----------------*/ sfr TCON = 0x88; //定时/计数控制寄存器 sbit TF1 = TCON^7; //定时器1溢出中断标志 sbit TR1 = TCON^6; //定时器1运行控制位 sbit TF0 = TCON^5; //定时器0溢出中断标志 sbit TR0 = TCON^4; //定时器0运行控制位 sbit IE1 = TCON^3; //外部中断1请求标志 sbit IT1 = TCON^2; //选择外部中断请求1为边沿触发方式的控制位 sbit IE0 = TCON^1; //外部中断0请求标志 sbit IT0 = TCON^0; //选择外部中断请求0为边沿触发方式的控制位 sfr TMOD = 0x89; //定时/计数模式控制寄存器 sfr TL0 = 0x8A; //定时/计数器0低字节 sfr TH0 = 0x8C; //定时/计数器0高字节 sfr TL1 = 0x8B; //定时/计数器1低字节 sfr TH1 = 0x8D; //定时/计数器1高字节 sfr T2H=0xD6; //定时器2重新装载时间常数高字节 sfr T2L=0xD7; //定时器2重新装载时间常数低字节 /*-------------- 串行口特殊功能寄存器 ------------------*/ sfr SCON = 0x98; //串行口控制寄存器 sbit SM0 = SCON^7; //串行口工作方式设定控制位0(与FE功能复用) sbit FE = SCON^7; sbit SM1 = SCON^6; //串行口工作方式设定控制位1 sbit SM2 = SCON^5; //UART的SM2设定 sbit REN = SCON^4; //接收允许位 sbit TB8 = SCON^3; //发送数据的第九位 sbit RB8 = SCON^2; //接收数据的第九位 sbit TI = SCON^1; //发送中断标志 sbit RI = SCON^0; //接收中断标志 sfr SBUF = 0x99; //串口数据缓冲器 sfr SADEN = 0xB9; //从机地址掩码寄存器 sfr SADDR = 0xA9; //从机地址寄存器 sfr S2CON = 0x9A; //串行口2控制寄存器 sfr S2BUF = 0x9B; //串行口2数据缓冲器 /*---------------- 看门狗定时器寄存器 ------------------*/ sfr WDT_CONTR = 0xC1; //看门狗定时器控制寄存器 /*---------------- PCA 寄存器 -----------------*/ sfr CCON = 0xD8; //PCA控制寄存器 sbit CF = CCON^7; //PCA计数器溢出(CH,CL由FFFFH变为0000H)标志 sbit CR = CCON^6; //PCA计数器计数允许控制位 sbit CCF2 = CCON^2; //PCA模块2中断标志 sbit CCF1 = CCON^1; //PCA模块1中断标志 sbit CCF0 = CCON^0; //PCA模块0中断标志 sfr CMOD = 0xD9; //PCA工作模式寄存器 sfr CL = 0xE9; //PCA计数器低8位 sfr CH = 0xF9; //PCA计数器高8位 sfr CCAPM0 = 0xDA; //PAC模块0的工作模式寄存器 sfr CCAPM1 = 0xDB; //PAC模块1的工作模式寄存器 sfr CCAPM2 = 0xDC; //PAC模块2的工作模式寄存器 sfr CCAP0L = 0xEA; //PAC模块0捕捉/比较寄存器低8位 sfr CCAP0H = 0xFA; //PAC模块0捕捉/比较寄存器高8位 sfr CCAP1L = 0xEB; //PAC模块1捕捉/比较寄存器低8位 sfr CCAP1H = 0xFB; //PAC模块1捕捉/比较寄存器高8位 sfr CCAP2L = 0xEC; //PAC模块2捕捉/比较寄存器低8位 sfr CCAP2H = 0xFC; //PAC模块2捕捉/比较寄存器高8位 sfr PCA_PWM0 = 0xF2; //PCA模块0 PWM寄存器 sfr PCA_PWM1 = 0xF3; //PCA模块1 PWM寄存器 sfr PCA_PWM2 = 0xF4; //PCA模块2 PWM寄存器 /*----------------- ADC 寄存器 -----------------*/ sfr ADC_CONTR = 0xBC; //ADC控制寄存器, 本寄存器不支持位操作 sfr ADC_RES = 0xBD; //ADC转换结果高8位寄存器 sfr ADC_RESL = 0xBE; //ADC转换结果低2位寄存器 /*---------------- SPI 寄存器 --------------------*/ sfr SPSTAT = 0xCD; //SPI状态寄存器,本寄存器不支持位操作 sfr SPCTL = 0xCE; //SPI控制寄存器 sfr SPDAT = 0xCF; //SPI数据寄存器 /*----------------- ISP_IAP_EEPROM 寄存器 ------------------*/ sfr IAP_DATA = 0xC2; //ISP/IAP Flash数据寄存器 sfr IAP_ADDRH = 0xC3; //ISP/IAP Flash地址高字节 sfr IAP_ADDRL = 0xC4; //ISP/IAP Flash地址低字节 sfr IAP_CMD = 0xC5; //ISP/IAP Flash命令寄存器 sfr IAP_TRIG = 0xC6; //ISP/IAP Flash命令触发器 sfr IAP_CONTR = 0xC7; //ISP/IAP控制寄存器
1、存储区域(Memory Areas)(4)特殊功能寄存器 /*--------- 8051内核特殊功能寄存器 -------------*/ sfr ACC = 0xE0; //累加器 sfr B = 0xF0; //B 寄存器 sfr PSW = 0xD0; //程序状态字寄存器 sbit CY = PSW^7; //进位标志位 sbit AC = PSW^6; //辅助进位标志位 sbit F0 = PSW^5; //用户标志位0 sbit RS1 = PSW^4; //工作寄存器组选择控制位 sbit RS0 = PSW^3; //工作寄存器组选择控制位 sbit OV = PSW^2; //溢出标志位 sbit F1 = PSW^1; //用户标志位1 sbit P = PSW^0; //奇偶标志位 sfr SP = 0x81; //堆栈指针寄存器 sfr DPL = 0x82; //数据指针0低字节 sfr DPH = 0x83; //数据指针0高字节 /*------------ 系统管理特殊功能寄存器 -------------*/ sfr PCON = 0x87; //电源控制寄存器 sfr AUXR = 0x8E; //辅助寄存器 sfr INT_CLKO = 0x8F; //外部中断和时钟输出控制寄存器 sfr AUXR1 = 0xA2; //辅助寄存器1 sfr CLK_DIV = 0x97; //时钟分频控制寄存器 sfr BUS_SPEED = 0xA1; //总线速度控制寄存器 sfr WKTCL= 0xAA; //掉电唤醒专用定时器低字节 sfr WKTCH= 0xAB; //掉电唤醒专用定时器高字节 /*----------- 中断控制特殊功能寄存器 --------------*/ sfr IE = 0xA8; //中断允许寄存器 sbit EA = IE^7; //总中断允许位 sbit ELVD = IE^6; //低电压检测中断控制位 sbit EADC = IE^5; //ADC中断允许控制位 sbit ES = IE^4; //串口1中断允许位 sbit ET1 = IE^3; //定时器1溢出中断允许位 sbit EX1 = IE^2; //外部中断1允许位 sbit ET0 = IE^1; //定时器0溢出中断允许位 sbit EX0 = IE^0; //外部中断0允许位 sfr IE2 = 0xAF; //中断允许寄存器2 sfr IP = 0xB8; //中断优先级寄存器 sbit PPCA = IP^7; // PCA中断优先级控制位 sbit PLVD = IP^6; //低电压检测中断优先级控制位 sbit PADC = IP^5; //ADC中断优先级控制位 sbit PS = IP^4; //串口1中断优先级控制位 sbit PT1 = IP^3; //定时器1中断优先级控制位 sbit PX1 = IP^2; //外部中断1优先级控制位 sbit PT0 = IP^1; //定时器0中断优先级控制位 sbit PX0 = IP^0; //外部中断0优先级控制位 sfr IP2 = 0xB5; //第二中断优先级寄存器低字节 /*--------------- I/O 口特殊功能寄存器 -------------------*/ sfr P0 = 0x80; //P0口寄存器 sfr P0M1 = 0x93; //P0口工作模式寄存器1 sfr P0M0 = 0x94; //P0口工作模式寄存器0 sfr P1 = 0x90; //P1口寄存器 sfr P1M1 = 0x91; //P1口工作模式寄存器1 sfr P1M0 = 0x92; //P1口工作模式寄存器0 sfr P1ASF = 0x9D; //P1口模拟量功能设置寄存器 sfr P2 = 0xA0; //P2口寄存器 sfr P2M1 = 0x95; //P2口工作模式寄存器1 sfr P2M0 = 0x96; //P2口工作模式寄存器0 sfr P3 = 0xB0; //P3口寄存器 sbit T1 = P3^5; //定时器1外部输入 sbit T0 = P3^4; //定时器0外部输入 sbit INT1 = P3^3; //外部中断1 sbit INT0 = P3^2; //外部中断0 sbit TXD = P3^1; //串行输入通道 sbit RXD = P3^0; //串行输出通道 sfr P3M1 = 0xB1; //P3口工作模式寄存器1 sfr P3M0 = 0xB2; //P3口工作模式寄存器0 sfr P4 = 0xC0; //P4口寄存器 sfr P4M1 = 0xB3; // P4口工作模式寄存器1 sfr P4M0 = 0xB4; // P4口工作模式寄存器0 sfr P5 = 0xC8; //P5口(只有P5.3 P5.2 P5.1 P5.0) sfr P5M1 = 0xC9; // P5口工作模式寄存器1 sfr P5M0 = 0xCA; // P5口工作模式寄存器0 sfr P_SW2=0xBA; //外设功能切换控制寄存器 /*----------------- 定时器特殊功能寄存器 -----------------*/ sfr TCON = 0x88; //定时/计数控制寄存器 sbit TF1 = TCON^7; //定时器1溢出中断标志 sbit TR1 = TCON^6; //定时器1运行控制位 sbit TF0 = TCON^5; //定时器0溢出中断标志 sbit TR0 = TCON^4; //定时器0运行控制位 sbit IE1 = TCON^3; //外部中断1请求标志 sbit IT1 = TCON^2; //选择外部中断请求1为边沿触发方式的控制位 sbit IE0 = TCON^1; //外部中断0请求标志 sbit IT0 = TCON^0; //选择外部中断请求0为边沿触发方式的控制位 sfr TMOD = 0x89; //定时/计数模式控制寄存器 sfr TL0 = 0x8A; //定时/计数器0低字节 sfr TH0 = 0x8C; //定时/计数器0高字节 sfr TL1 = 0x8B; //定时/计数器1低字节 sfr TH1 = 0x8D; //定时/计数器1高字节 sfr T2H=0xD6; //定时器2重新装载时间常数高字节 sfr T2L=0xD7; //定时器2重新装载时间常数低字节 /*-------------- 串行口特殊功能寄存器 ------------------*/ sfr SCON = 0x98; //串行口控制寄存器 sbit SM0 = SCON^7; //串行口工作方式设定控制位0(与FE功能复用) sbit FE = SCON^7; sbit SM1 = SCON^6; //串行口工作方式设定控制位1 sbit SM2 = SCON^5; //UART的SM2设定 sbit REN = SCON^4; //接收允许位 sbit TB8 = SCON^3; //发送数据的第九位 sbit RB8 = SCON^2; //接收数据的第九位 sbit TI = SCON^1; //发送中断标志 sbit RI = SCON^0; //接收中断标志 sfr SBUF = 0x99; //串口数据缓冲器 sfr SADEN = 0xB9; //从机地址掩码寄存器 sfr SADDR = 0xA9; //从机地址寄存器 sfr S2CON = 0x9A; //串行口2控制寄存器 sfr S2BUF = 0x9B; //串行口2数据缓冲器 /*---------------- 看门狗定时器寄存器 ------------------*/ sfr WDT_CONTR = 0xC1; //看门狗定时器控制寄存器 /*---------------- PCA 寄存器 -----------------*/ sfr CCON = 0xD8; //PCA控制寄存器 sbit CF = CCON^7; //PCA计数器溢出(CH,CL由FFFFH变为0000H)标志 sbit CR = CCON^6; //PCA计数器计数允许控制位 sbit CCF2 = CCON^2; //PCA模块2中断标志 sbit CCF1 = CCON^1; //PCA模块1中断标志 sbit CCF0 = CCON^0; //PCA模块0中断标志 sfr CMOD = 0xD9; //PCA工作模式寄存器 sfr CL = 0xE9; //PCA计数器低8位 sfr CH = 0xF9; //PCA计数器高8位 sfr CCAPM0 = 0xDA; //PAC模块0的工作模式寄存器 sfr CCAPM1 = 0xDB; //PAC模块1的工作模式寄存器 sfr CCAPM2 = 0xDC; //PAC模块2的工作模式寄存器 sfr CCAP0L = 0xEA; //PAC模块0捕捉/比较寄存器低8位 sfr CCAP0H = 0xFA; //PAC模块0捕捉/比较寄存器高8位 sfr CCAP1L = 0xEB; //PAC模块1捕捉/比较寄存器低8位 sfr CCAP1H = 0xFB; //PAC模块1捕捉/比较寄存器高8位 sfr CCAP2L = 0xEC; //PAC模块2捕捉/比较寄存器低8位 sfr CCAP2H = 0xFC; //PAC模块2捕捉/比较寄存器高8位 sfr PCA_PWM0 = 0xF2; //PCA模块0 PWM寄存器 sfr PCA_PWM1 = 0xF3; //PCA模块1 PWM寄存器 sfr PCA_PWM2 = 0xF4; //PCA模块2 PWM寄存器 /*----------------- ADC 寄存器 -----------------*/ sfr ADC_CONTR = 0xBC; //ADC控制寄存器, 本寄存器不支持位操作 sfr ADC_RES = 0xBD; //ADC转换结果高8位寄存器 sfr ADC_RESL = 0xBE; //ADC转换结果低2位寄存器 /*---------------- SPI 寄存器 --------------------*/ sfr SPSTAT = 0xCD; //SPI状态寄存器,本寄存器不支持位操作 sfr SPCTL = 0xCE; //SPI控制寄存器 sfr SPDAT = 0xCF; //SPI数据寄存器 /*----------------- ISP_IAP_EEPROM 寄存器 ------------------*/ sfr IAP_DATA = 0xC2; //ISP/IAP Flash数据寄存器 sfr IAP_ADDRH = 0xC3; //ISP/IAP Flash地址高字节 sfr IAP_ADDRL = 0xC4; //ISP/IAP Flash地址低字节 sfr IAP_CMD = 0xC5; //ISP/IAP Flash命令寄存器 sfr IAP_TRIG = 0xC6; //ISP/IAP Flash命令触发器 sfr IAP_CONTR = 0xC7; //ISP/IAP控制寄存器 1、存储区域(Memory Areas)(4)特殊功能寄存器 STC15F2K60S2单片机的头文件stc15.h的内容,
2、_at_关键字 若要实现变量的绝对定位(称为绝对变量),可以直接 在数据定义后加上“_at_ 常数地址”即可。 注意: (1)绝对变量不能被初始化(不能赋初值); (2)bit型函数及变量不能用_at_指定。 例如: unsigned char idata ADCdata _at_ 0x40; //指定ADCdata变量在40H处 unsigned char xdata buffer[20] _at_ 0x0010; //指定buffer数组从XRAM的0010H单元开始
3、存储模式 存储模式决定了没有明确指定存储类型的变量时、函 数参数等的缺省存储区域,有Small、Compact和 Large三种模式。 指定存储模式 图5-1 指定存储模式
3、存储模式 (1)Small模式 在该模式中所有变量都默认位于单片机内部数据存 储器(00~7FH) ,这和使用data指定存储器类型的方式 一样。 此模式访问变量的效率很高,但所有的数据对象和 堆栈必须适合内部RAM堆栈的大小。 如果将变量都能配置在内部数据存储器内,Small模 式是最佳选择。 该模式的优点是访问速度快,缺点是空间有限,只 适用于小程序。
(2)Compact模式 所有缺省变量均位于外部RAM区的一页内(256字节), 这和使用pdata指定存储器类型一样。 该模式通过寄存器R0和R1(@R0,@R1)间接寻址。 R0和R1只提供地址低字节,如果COMPACT模式使 用多于256字节的外部存储区,高字节地址或页由8051 的P2(口地址0A0H)提供; 初始化PORT2及指定PDATA起始地址的工作可在起 始代码中STARTUP.A51文件中说明。 该模式空间比Small宽裕,速度比Small慢,比Large 快,是一种中间状态。
3、存储模式 (3)Large模式 所有缺省变量可放在多达64KB的外部RAM区,这和 使用xdata指定存储器类型一样,使用数据指针 DPTR进行寻址。 通过数据指针访问外部数据存储器的效率较低,特别 是当变量为2个字节或更多字节(用一般指针)时。 Large模式的数据访问比Small和Compact产生更多 的代码。 优点是空间大,可存变量多,缺点是速度较慢。
4、变量或数据类型 表5-1 C51数据类型 数据类型 含 义 取值范围 bit* 位型 1 1/8 0或1 signed char 8 含 义 位数 (bit) 字节数 (byte) 取值范围 bit* 位型 1 1/8 0或1 signed char 带符号字符型 8 -128~+127 unsigned char 无符号字符型 0~255 enum 8/16 1 or 2 -128~+127or-32768~+32767 signed short 带符号短型 16 2 -32768~+32767 unsigned short 无符号短型 0~65535 signed int 带符号整型 unsigned int 无符号整型 signed long 带符号长整型 32 4 -2147483648~ +2147483647 unsigned long 无符号长整型 0~4294967295 float 浮点型 +1.175494E38~ +3.402823E+38 sbit* 0-1 sfr* 0x80-0xff sfr16* 0x0000~0xffff C51特有特殊功能寄存器类型
4、变量或数据类型 C51提供以下几种扩展数据类型: bit:位变量值为0或1。格式: bit 位名 [= value]; 例: static bit done_flag = 0; /* bit variable */ sbit:从可位寻址的字节中定义的位变量(0或1)。 sfr:sfr字节地址(0x80~0xff)。 sfr16:sfr字地址(0x80~0xff, 其实占用两个连续地址)。 例: sfr16 DPTR=0x82h; 其余数据类型如:char、enum、short、int、long、 float等与ANSI C相同。下面着重介绍位变量及其声明。 0或1
位寻址区 20H~2FH之间单 元既可按字节存 取, 也可按位存取, 共128位, 位地址 范围: 00H~7FH。 图3-10 内部RAM中的位地址 03:43
(#pragma的作用是设定编译器的状态或者指示编译器完成一些特定的动作) 4、变量或数据类型 (1) bit型变量 (1)bit型变量 bit型变量可用于变量类型声明、函数参数列表和函 数返回值等, 存储于内部RAM的20H~2FH单元中。 例: bit flag1 注意: 1) 使用禁止中断 (#pragma disable) 或用一个明确的 寄存器组声明 (using n) 的函数不能返回位值,否则, 编译器会识别出来并产生一个错误信息。 2) 位不能声明为一个指针。如bit *bit_poiter;是错 误的。 3) 不能有bit数组如:bit arr[5];是错误的。 4) bit型函数及变量不能用'_at_ '定位到一个绝对地址。 (#pragma的作用是设定编译器的状态或者指示编译器完成一些特定的动作)
4、变量或数据类型 (2)可位寻址区的说明 (2)可位寻址区的说明 4、变量或数据类型 (2)可位寻址区的说明 (2)可位寻址区的说明 使用sbit声明可独立访问可位寻址对象的位。sbit声明要 求基址对象的存贮器类型为“bdata”(特殊功能寄存器除 外), 否则只有绝对的位声明方法是合法的。 例: sbit Fg1=0x20; //错误使用sbit, 位地址须在80H~FFH之间 sbit EA = 0xAF; //位地址可大于80H sbit Fg2=0x20^2; //错误使用sbit, bit Fg3=0x20 ; //既不能用bit定义Fg3的位地址是0x20, //也不能用sbit定义, 除非用bdata声明: int bdata bitram _at_ 0x20; //用“bdata”声明后可用sbit sbit Fg4=bitram^2; //相当于定义了Fg4=0x20^2 bit Fg4=bitram^2; //bit不能这样使用, 虽然sbit可以. sbit可绝对定位位变量, 但bit不可 (^1) 此指令是给位变量Fg3赋值1(0x20非0是真,值=1) 总结
4、变量或数据类型 (2)可位寻址区的说明 (2)可位寻址区的说明 4、变量或数据类型 (2)可位寻址区的说明 (2)可位寻址区的说明 使用sbit声明可独立访问可位寻址对象的位。 sbit声明 要求基址对象的存贮器类型为“bdata”(特殊功能寄存器 除外): int bdata bitram _at_ 0x20; //用“bdata”声明后可用sbit位寻 址 sbit Fg15=bitram^15; //相当于定义了Fg15=0x20^15 位的位置( ‘ ^ ’操作符号后的数字)的最大值依赖于指定 的基址变量类型: 对于char/uchar而言是0~7, 对于int/uint/short/ushort而言是0~15, 对于long/ulong而言是0~31。
4、变量或数据类型 下面举例说明位寻址的声明方法。 定义为char就不存在此不对应问题 下面举例说明位寻址的声明方法。 例, int bdata bittest _at_ 0x20; //也可省略“_at_ 0x20” sbit bit0= bittest ^0; //0x20单元的第0位(bittest的第8位) sbit bit15= bittest ^15; //0x21单元的第7位(bittest的第7位) 参见“Cx51 Compiler User‘s Guide ”--“Bit-Addressable Objects” 最后一段“Physical bit position 0 refers to bit position 0 of the first byte. Physical bit position 8 refers to bit position 0 of the second byte. Because int variables are stored high-byte first, bit 0 of the integer is located in bit position 0 of the second byte. This is physical bit position 8 when accessed using an sbit data type.” 注意:可位寻址对象的位的声明只能放到main函数的外部, 作 为全局变量使用, 否则, 编译会出错。bit无限制。 先存高字节, 再存低字节 0x20单元的0~7位是bittest的第8~15位 实际位的位置与位号bit0~7不相符 0x21单元的0~7位是bittest的第0~7位
5.2.2 扩展I/O口的使用 STC15F2K60S2单片机除了芯片上的I/O口外,还可 在片外扩展I/O端口。 由于使用C语言访问外部I/O时用到指针的功能,因 此,首先介绍Keil C51的指针。 1、Keil C51指针 Keil C51支持一般指针(Generic Pointer)和存 储器指针(Memory Specific Pointer)。 一般指针的声明和使用均与标准C相同,同时还可 以说明指针的存储类型。
pt本身是2 (或3)字节的存储单元, 其中的值是地址(指针) 1、Keil C51指针 存储器指针 例如,下面的语句都声明pt为指向保存在外部RAM中 unsigned char数据的指针, 但pt本身的保存位置却不同: unsigned char xdata *pt; //pt本身依存储模式存放 unsigned char xdata * data pt; //pt被保存在内部RAM中 unsigned char xdata * xdata pt; //pt被保存在外部RAM中 pt本身是2 (或3)字节的存储单元, 其中的值是地址(指针)
1、Keil C51指针 一般指针本身用3个字节存放, 分别为存储器类型, 高位偏移量和低位偏移量。 存放数据的存储器类型 一般指针本身用3个字节存放, 分别为存储器类型, 高位偏移量和低位偏移量。 char data * str; //str指向data区中char型数据 int xdata * pow; //pow指向外部RAM的int型整数 这种指针存放时, 只需1(或2)个字节就够了, 因只需存放偏移 量。 基于存储器的指针, 说明时即指定了存储类型, 例如 存储器类型是在编译时需要的, 既已指出,所以指针只需存偏移量即可, 存储类型 Idata/data/bdata xdata pdata Code 编码值 0x00 0x01 0xFE 0xFF 参考:Keil Help: Cx51 Compiler User's Guide→Language Extensions→ Pointers
关于堆栈指针SP的设定 一般情况下, 用户不需在C语言程序中修改堆栈指针SP, 但要关心一下SP的位置。C51为变量分配好内部RAM 后, 将SP放在第一个空闲的内部RAM处; 可在编译后生成的.m51文件中观察到栈顶的位置,一 般程序编译连接成功后要习惯性地看一下.m51文件, 看一下是否有足够的栈空间可用。 另外, C51是在startup.A51中设置SP指针的, 用CODE选 项生成的汇编代码(位于.lst文件)中是找不到这段代码的。 startup.A51是C51的初始化代码, 单片机复位后先执行 这段代码, 完成初始化后由它调用main( )函数。特殊需 要时, 可修改这段代码, 然后连接到用户的程序中去。 编译命令选项
.M51 文件与编译命令的CODE选项 .M51 文件是连接器影射文件, 包含存储器的使用情况 和其他静态量信息, 及交叉参考列表。 可生成汇编代码列表的编译命令选项——CODE选项: Cx51编译器提供许多控制命令控制编译。控制命令由 一个或多个字母或数字组成 ,除非特别规定,都可在 命令行中,在文件名后指定,例如: C51 testfile.c CODE 或在源文件中用#pragma命令,例如: #pragma CODE CODE属于Listing类指令, 即加一个汇编代码列表到 列表文件。该命令在命令行或源文件开头的#pragma 中只指定一次 在一个源文件中不能使用多次。
若与单片机接口的电路图同图8-56, 需修改地址值, 详见312页的修改。 2、外部扩展I/O口的访问 在C51中有两种方法访问外部I/O端口。 方法1:使用自定义指针。 由于片外I/O端口与片外存储器统一编址,所以可以定 义xdata类型的指针访问外部I/O端口。 见“ 8.3 并行接口扩展方法” 例,单片机应用系统中,用可编程并行I/O接口芯片8255 扩展I/O端口,采用线选法对8255进行地址译码,单片机 P2.7 (A15)接8255的片选引脚,8255的命令口字地址为 7FF3H, PA口地址为7FF0H,(需改为7CF0H ) PB口地址为7FF1H,(需改为7DF1H ) PC口地址为7FF2H,(需改为7EF2H ) 访问8255的C程序如下: 若与单片机接口的电路图同图8-56, 需修改地址值, 详见312页的修改。 若不修改地址值, 也可用下页接口图 7CFFH 7DFFH 7EFFH
设8255A的命令口地址为7FF3H, PA口地址为7FF0H, PB口地址为7FF1H, PC口地址为7FF2H。(非唯一) 8255A与STC15F2K60S2单片机的接口电路。 设8255A的命令口地址为7FF3H, PA口地址为7FF0H, PB口地址为7FF1H, PC口地址为7FF2H。(非唯一) P2作高8位地址总线A15 ~A8, A15=0,没用到的位为1, P0作低8位(A7 ~A0)地址, 除A3A2=00, 没用到的位为1,低8位地址: P0.0—— P0.1—— A1A0 1111 0000: PA口 1111 0001: PB口 1111 0010: PC口 1111 0011: 命令口 图8-56 8255A与STC15F2K60S2单片机的接口电路
方法1:使用自定义指针访问外部扩展I/O口 写端口程序: char xdata *com8255; //定义指向外部存储区的指针 com8255=0x7ff3; //使指针指向8255的控制口(命令口)地址7FF3H *com8255=0x81; //输出命令字81H到命令口寄存器 // 81H: PA, PB口都是模式0,直接输出, PC口直接输入 以上C程序相当于下面的汇编语言程序: MOV DPTR,#7FF3H MOV A,#81H MOVX @DPTR,A 具体命令介绍见8.3 并行接口扩展方法,309页图8-52
方法1:使用自定义指针访问外部扩展I/O口 读端口程序: char xdata *com8255; //定义指针 com8255=0x7ff0; //使指针指向8255的PA口地址7FF0H (需改为7CF0H) char i; i=*com8255; //读PA端口到变量i 0x7CF0 若与单片机接口的电路图同图8-56, 需修改地址值, 详见312页的修改。
方法2:使用C51预定义指针访问外部扩展I/O口 为了方便访问外部存储器及I/O端口, 在C51的absacc.h 头文件做了如下定义, 利用这些定义可以方便地访问外 部I/O端口。 #define CBYTE ((unsigned char volatile code *) 0) #define DBYTE ((unsigned char volatile data *) 0) #define PBYTE ((unsigned char volatile pdata *) 0) #define XBYTE ((unsigned char volatile xdata *) 0) volatile修饰了的变量随程序的执行其值会被改变, “易变”
方法2:使用C51预定义指针访问外部扩展I/O口 #define XBYTE ((unsigned char volatile xdata *) 0) 例如: #include <absacc.h> #define PORTA XBYTE[0x7ff0] //其中,PORTA为程序定义的I/O端口名称,[ ]内的内容 //7ff0H为PORTA的地址 void main(void) { char a; PORTA=0x81; //*输出81H到端口7ff0H a=PORTA; //读端口7ff0H到变量a } 定义XBYTE数组的首地址是0 XBYTE [0x7ff0]的中括号内的值0x7ff0, 指出了(定义)数组XBYTE首地址的偏移地址
5.2.3 Keil C51函数 C51的函数声明对ANSI C作了扩展,具体包括: 1、中断函数声明 中断号 中断源 入口地址 外部中断INT0 0003H 1 T0溢出中断 000BH 2 外部中断INT1 0013H 3 T1溢出中断 001BH 4 串行口UART1中断 0023H 5 ADC中断 002BH 6 LVD中断 0033H 7 PCA中断 003BH 8 串行口UART2中断 0043H 9 SPI中断 004BH 10 外部中断INT2 0053H 11 外部中断INT3 005BH 12 T2溢出中断 0063H 16 外部中断INT4 0083H 中断函数通过使用 interrupt关键字和中 断号(0~31)来声明。 中断号告诉编译器中 断服务程序入口地址。 STC15F2K60S2单片机的中断号及中断服务程序入口地址如表所示。
1、中断函数声明 例如,串行口1的中断函数(4号中断)可以声明如下: void UART1_ISR (void) interrupt 4 [using 1] { /* 中断服务程序的代码 */ } 上述代码声明了串行口1中断服务函数。其中, interrupt 4指明是串行口1的中断, using 1指明采用工 作寄存器区1区, using 1在中括号中, 说明该部分可省 略。 其他中断函数的定义与此类似。中断函数具体是哪个 中断的函数,与中断号有关,而与函数名无关 。 无返回参数 无输入参数
5.2.3 Keil C51函数-——2、指定工作寄存器区 2、指定工作寄存器区 当需要指定函数中使用的工作寄存器区时,使用关 键字using后跟一个0到3的数,对应着工作寄存器0 到3区。 例如,在下面的函数中使用了工作寄存器1区(相当 于PSW.4=0, PSW.3=1): unsigned char GetKey(void) using 1 { /*用户程序代码*/ }
5.2.3 Keil C51函数——3、指定存储模式 3、指定存储模式 用户可以使用small,compact 及large说明存储模式。 例如: void fun1(void) small { } 提示:small说明的函数内部变量全部使用内部RAM。 关键的、经常性的、耗时的地方可以这样声明,以提 高运行速度。
5.2.3 Keil C51函数——4、函数的参数传递规则 4、函数的参数传递规则 最多只能有3个参数通过寄存器传递,规律如表5-3所示。 表5-3 函数的参数传递规则 Argument Number 参数序号 char int long, float 一般指针 1 R7 R6 & R7 R4~R7 R1~R3 2 R5 R4 & R5 3 R3 R2 & R3 不能出现在第 3个参数位置 可能出现的参数位置, 其余参数是其他数据类型
char/unsigned char, 1_byte指针 5.2.3 Keil C51函数——5、函数返回值的规定 5、函数返回值的规定 函数返回值一律放于寄存器中,规则如下表5-4所示。 返回值类型 寄存器 说明 bit Cy char/unsigned char, 1_byte指针 R7 单字节由R7返回 int/unsigned int 2_byte指针 R6, R7 R6放高位,R7放低位 long&unsigned long R4~R7 R4放最高位,R7放最低位 float IEEE标准 R7放符号位及阶码 一般指针 R1,R2,R3 R3放存储空间码, R2放高位, R1放低位 存储类型
5.2.3 Keil C51函数——6、函数的重入 6、函数的重入 typedef unsigned char uchar; #pragma disable /* Disable Interrupts */ uchar dfunc (uchar p1, uchar p2) { return (p1 * p2 + p2 * p1);} 5.2.3 Keil C51函数——6、函数的重入 6、函数的重入 可在函数前声明函数的可重入性(不可中断性)。如果声 明为不可重入(中断)的, 说明该函数调用过程中将不可被 中断。 一个可重入(或递归)函数可在同一时间被几个进程共享。 当一个可重入函数运行时, 别的进程(如中断)可中断执 行, 并开始执行相同的可重入函数(即递归调用) 。 正常情况, Cx51 编译器中的函数不能重入 , 原因是单片 机一般使用寄存器传递参数, 内部变量一般在固定的 RAM中, 函数重入时会破坏上次调用的数据。 而PC可重入是因为, PC使用(调用)堆栈传递参数, 且静 态变量以外的内部变量都在堆栈中; void func(param...) reentrant {... }
6、函数的重入 可以用以下两种方法解决函数的重入(及不可中断)问题: 第一种方法: 在相应的函数前使用“#pragma disable”声明, 即只允许 主程序或中断之一调用该函数(函数调用后不再被中断)。 第二种方法: 将该函数说明为可重入的。如下: void func(param...) reentrant {... }; Keil C51编译后将生成一个可重入变量堆栈,然后就 可以模拟通过调用堆栈传递变量的方法。 因为单片机内部堆栈空间的限制,C51没有像大系统那 样使用调用堆栈: 一般在C语言中调用过程时,会把过 程的参数和过程中使用的局部变量入栈。
6、函数的重入 不常使用 为了提高效率,C51没有提供这种调用堆栈,而是提供一种压 缩栈。每个过程被给定同一个空间,用于存放局部变量。过程 中的每个变量都存放在这个空间的固定位置。当递归调用这个 过程时,会导致变量被覆盖。 在某些实时应用中,非重入函数是不可取的。因为,函数调 用时可能会被中断程序中断,而在中断程序中可能再次调用这 个函数,所以C51允许将函数定义成重入函数。 重入函数可被递归调用和多重调用, 而不用担心变量被覆盖, 因C51可模拟生成一个可重入变量堆栈 {随存储模式(small, compact, large)不同, 堆栈可在不同的存储器区(idata, pdata, xdata)}, 每次函数调用时的局部变量都会被单独保存。因为这 些堆栈是模拟的,重入函数一般都比较大,运行起来也比较慢。
6、函数的重入 由于一般可重入函数由主程序和中断调用(即递归调用), 所以通常中断程序使用与主程序不同的工作寄存器组。 由于一般可重入函数由主程序和中断调用(即递归调用), 所以通常中断程序使用与主程序不同的工作寄存器组。 另外,对可重入函数,在相应的函数前面加上开关 #pragma noaregs,以禁止编译器使用绝对寄存器寻址, 可生成不依赖于寄存器组的代码。 所以对不依赖于寄存器组的函数可被其他使用了不同的 寄存器组的多个函数调用。 extern char func (); #pragma NOAREGS noaregfunc () { k = func () + func (); } #pragma AREGS aregfunc () using 3 { k = noaregfunc() + func (); } 不常使用
5.2.4 STC15F2K60S2单片机C51程序框架 #include “stc15.h” /* stc15.h为单片机寄存器定义头文件, 具体见附录C */ void delay(long delaytime); //声明子函数,子函数可以有返回值 void main(void) { //…此处可存放应用系统的初始化代码 while(1) //主程序循环 //根据需要填入适当的内容 delay(100); //可以调用用户自定义的子函数 }
// 各个子函数声明, 各个中断函数的实现 void delay(long delaytime) { while(delaytime>0) delaytime--; //子函数的实现代码 } //---------各个中断函数的实现---------- void INT0_ISR(void) interrupt 0 //外部中断0服务子函数 //根据需要填入程序代码 void T0_ISR(void) interrupt 1 //定时器0中断服务子函数 void INT1_ISR(void) interrupt 2 //外部中断1服务子函数 void T1_ISR(void) interrupt 3 //定时器1中断服务子函数
// 各个中断函数的实现 void UART1_ISR(void) interrupt 4 //串口1中断服务子函数 { //根据需要填入程序代码,注意中断请求标志的清零 } void ADC_ISR (void) interrupt 5 //ADC中断服务子函数 void LVD_ISR (void) interrupt 6 //低电压检测中断子函数 void PCA_ISR (void) interrupt 7 //PCA中断子函数 void UART2_ISR (void) interrupt 8 //串口2中断子函数 void SPI_ISR (void) interrupt 9 //SPI中断子函数
// 各个中断函数的实现 void INT2_ISR(void) interrupt 10 //外部中断2服务子函数 { //根据需要填入程序代码 } void INT3_ISR(void) interrupt 11 //外部中断3服务子函数 void T2_ISR(void) interrupt 12 //定时器2中断服务子函数 void INT4_ISR(void) interrupt 16 //外部中断4服务子函数
【例5-1】编程实现通过延时函数,P1.0输出方波信号, 并通过示波器观察程序输出波形的周期。 #include “stc15.h” //STC15F2K60S2寄存器定义头文件 sbit P10=P1^0; //定义P1.0引脚, stc15.h中没定义 void delay(unsigned long cnt); //延时函数声明 void main(void) { P10=1; while(1) //主程序循环 delay(60000); P10=~P10; } void delay(unsigned long cnt) //延时函数 while(cnt>0) cnt--;
§5.3 单片机C语言程序调试 可用集成开发环境(IDE)对单片机程序进行软件模拟调 试。由于无需任何硬件与开发,可降低程序开发成本, 并且程序开发可以在系统硬件完成之前开始。 模拟仿真调试的方法和过程与汇编语言模拟仿真调试 过程相同。 但是,软件模拟调试无法仿真精确的硬件信号,难以 仿真过程控制中的通信网络时序及实时转换,因此当 软件开发过程进入必须有最终硬件参与共同完成的阶 段时,就需要进行在系统调试。 传统的仿真方法是使用仿真器进行仿真和调试。下面 介绍如何使用仿真器进行程序的仿真和调试。
§5.3 单片机C语言程序调试 使用宏晶单片机仿真器适用于STC单片机应用技术的 学习和实验程序的调试,使用过程如下: 1、硬件设置 目前仿真方式为双CPU仿真: 监控CPU和仿真CPU。仿 真CPU芯片须是宏晶的IAP系列(支持 IAP15F(L)2K61S2, IAP15W4K58S4,IRC15f2k63s2(内部 24MHz时钟或外晶振)等,仿真完成后, 可将程序下载到 STC15F2K60S2 单片机中)。 计算机USB接口 USB数据线 仿真器 学习板 图5-2 计算机、仿真器和学习板连接示意图
2、软件设置 无须设置 用户程序中需要在0x73的地址处保留6个字节。 C语言程序,需在代码中添加如下语句: char code reserved[6] _at_ 0x73; //在程序中进行声明 汇编语言程序,需在代码中添加如下语句: CSEG AT 73H ;在代码段73H定址 RESERVED: DS 6 ;保留6字节 或者 ORG 73H ;在代码段73H定址 DB 0FFH,0FFH,0FFH,0FFH,0FFH,0FFH ;保留6字节
2、软件设置 对于汇编语言程序,复位入口的程序必须为跳转指令 (建议使用长跳转),如: ORG 0 ;复位入口地址 LJMP RESET ;使用LJMP指令 … ;其它中断向量 ORG 73H ;保留字节地址 RESERVED: DB 0FFH,0FFH,0FFH,0FFH,0FFH,0FFH ;保留6字节 … ORG 0100H ;用户代码地址 RESET: ;复位入口 … ;用户代码
2、软件设置 按上述要求,将例5-1中程序进行改写后的程序如下: #include “stc15.h” //STC15F2K60S2单片机寄存器定义头文件 char code reserved[6] _at_ 0x73; //在程序中进行声明 sbit P10=P1^0; //定义P1.0引脚 void delay(unsigned long cnt); //延时函数声明 void main(void) { P10=1; while(1) //主程序循环 delay(60000); P10=~P10; } void delay(unsigned long cnt) //延时函数 { while(cnt>0) cnt--;
XRAM: 768B (0x0400~0x06FF, 用户在程序中不能使用) RAM : 0字节 I/O : P3.0/P3.1 3、仿真代码占用的资源 仿真代码占用程序区最后6K字节 (0xDC00~0xF3FF)空间, 若用IAP15F(L)2K61S2单片机仿真时,用户程序只能占55K (0x0000~0xDBFF)空间; XRAM: 768B (0x0400~0x06FF, 用户在程序中不能使用) RAM : 0字节 I/O : P3.0/P3.1 在此编辑框中输入晶振频率 4、Keil环境中的设置 ①设置晶振频率 用Project—>Options for Target ‘Target’命令打开 选项设置窗口, 如图所示。 参考: STC单片机编译(汇编)/编程(烧录)/仿真工具说明书
4、Keil环境中的设置 ②设置Output选项 在“Output” 选项中按如图所示设置。选中“Create HEX File” 复选框,Keil每进行一次Build,都生成可以下载到单片机 的HEX文件。 选中“Create HEX File”复选框 图5-4 设置 “Output” 属性
③选择硬件仿真 在“Debug”选项卡中, 选中右半部分中“ Use”, 从下拉列表框 中选择“Keil Monitor-51 Driver”, 并选中“Run to main”项。 如图所示。 选择Keil Monitor-51 Driver 选中“Run to main” 图5-5 选择 硬件仿真
④设置串口 图5-5中, 单击“ Settings”按钮, 弹出串口设置对话框, 如图所示。 图5-5 选择硬件仿真 选择串口号 图5-5 选择硬件仿真 图5-6 设置串口对话框
④设置串口 其中,DTR (Data Terminal Ready数据终端就绪)和 RTS (Request To Send请求发送(数据)) 是PC机在和 单片机通信时给单片机的握手信号。我们主要的任 务是设置串口号,其他可以不改变。 串口号是仿真时,仿真器所使用的串口号。 如果采用了USB转RS232芯片,需要特别注意串口号 的选择。 选中“Serial Interrupt”选项可以在全速运行程序时 暂停用户程序的执行。 设置完成后,就可以进行程序的调试了。
使用仿真器进行程序的仿真调试时,需要注意问题 仿真器与计算机USB连接须等待10秒以上,等仿真器上 电稳定后再进行程序的调试。 每次调试程序之前,需要重新上电。 对IAP_TRIG寄存器写0x5A和0xA5的语句不可单步调 试, 即不要仿真EEPROM功能。 不能写PCON寄存器的第5位LVDF(PCON.5) 。 程序中不能对P3.0和P3.1进行写操作。([P3.0, P3.1](仅 此口)用于下载/仿真接口 ) 不要关中断。 目前的版本不能全速运行程序。一旦全速运行,只能通 过复位单片机来停止调试过程。 EEPROM第一个扇区不能用。
使用仿真器进行程序的仿真调试 调试时,有时可能出现如图5-7所示的情形。 图5-7无法调试时的弹出窗口
使用仿真器进行程序的仿真调试 调试时若出现前图所示情况,可能原因有以下几点 keil的选项设置不正确,按照步骤4)重新设置。 串口设置不正确,重新设置串口。 程序正在全速运行或调试前单片机未复位,给 学习板重新上电复位后再进行程序调试。 把仿真器与电脑重新连接,等待10秒以上稳定 后再调试程序。 设置完成后,就可以采用与第四章中介绍的类似的 方法进行程序的调试了。
第五章 作业 5.3,(5.6),5.8