C51编程中的自定义“位”及其保存方案

发布者:chaochen最新更新时间:2013-02-17 来源: 21IC 关键字:C51编程  自定义  标志位 手机看文章 扫描二维码
随时随地手机看文章

  引言

  在现有的教课书及相关文章中,都难得提到在单片机C语言编程中对于自定义“位”的状态进行保存的理念。

  当单片机C语言编程中提及“位”的概念时,人们自然会想到状态字PSW中PSW.5的F0与PSW.1的F1两个用户通用标志位。这两个标志位均可参与布尔运算、“位”控操作,也可随状态字PSW一起保存。但是,往往会忽视这一点:在一些特定的情况下,如在C语言编程的中断服务程序中,对状态字PSW中PSW.5的F0与PSW.1的F1这两个用户标志位的操作可能是无效的。如:

  void EX1_ISR() interrupt 2 {//外部中断1

  static unsigned int tempaddr;//定义接收地址缓存

  static unsigned int tempkey;//定义接收数据缓存

  unsigned int timecnt;

  timecnt=TH1*256+TL1;

  TH1=0;

  TL1=0;

  TR1=1;//定时器1启动

  F0=~F0;//取反F0

  if(F0) {

  tempaddr=tempaddr<<1;

  }

  else {

  tempkey=tempkey<<1;

  }

  }

  以上是一段单片机外部中断1的中断服务程序,乍看似乎没什么问题,仿真调试时也能通过“编辑”。但实际上这是一段错误的程序——其中对“F0”用户标志位的“取反”操作是达不到其预期效果的。因为对“F0”用户标志位的“取反”操作是在中断服务程序中进行的。在进入中断时,C语言自动会保护“中断现场”——将程序指针PC、累加器ACC、状态字PSW等压入堆栈保护起来……直到中断返回时弹出堆栈并覆盖了中断服务时的变值,恢复到压入堆栈之前的原样。因此,状态字PSW中的F0也不例外,如果压入堆栈之前F0是处于逻辑“0”状态,中断返回后还是复原成逻辑“0”状态——不管中断服务程序中怎么取反改变——也就是说,在中断服务程序中试图改变F0之值的操作是有失偏颇的。对于上文例举的那段中断服务程序来说,若F0的初始状态为逻辑“0”,即进入中断服务之前和中断返回之后总是逻辑“0”,那么进入执行“F0=~F0”指令后F0总是逻辑“1”,因而接下运行的是“if(F0)”下花括号中“tempaddr=tempaddr<<1;”指令,而“else”下花括号中“tempkey=tempkey<<1”指令永远也运行不到。所以,若要中断服务程序达到预期的效果——“if(F0)”下花括号中的指令与“else”下花括号中的指令轮番运行,必须设立一个不受中断现场保护等影响的自定义标志位。

  1自定义标志“位”的保存

  在C语言编程中,自定义标志位的运用概率很大,有的一个程序中就会有好多的自定义标志位,且其中几个可能是必须要保存的。譬如有些控制器件中对一些控制状态进行保持,即使是停电之后再来电了这种控制状态依然能保持不变——这就牵涉到保存问题。

  例举2:我们曾搞过一个镭射投影屏幕升降的无线遥控装置。这个以单片机为核心的控制装置与屏幕升降的卷动电机等都安装固定在一个直径不足50 mm的狭长铁桶里面,因此装入或拆卸都非常麻烦。为了一次性成功——避免再次拆卸装入的麻烦,在用C语言编程时我特意多用了一个自定义的标志位——翻转标志位“switch_sign”。因为无线遥控手柄上的向上“▲”、暂停“■”、向下“▼”的键标志都已是做定的,因此,如果由于接线等失误导致按向下“▼”键时投影屏幕向上卷、按向上“▲”键时投影屏幕却向下伸……有了“switch_sign”自定义的标志位就不用怕这些了。相关的C语言程序段如下:

  #defineuint unsigned int

  #defineuchar unsigned char

  uint Decode_addr,Decode_key,addr;

  sbit JD1_out=P0^4;//定义继电器1控制输出端

  sbit JD2_out=P0^5;//定义继电器2控制输出端

  sbit BEEP=P0^3;//定义蜂鸣声响输出

  bit bdata switch_sign;//自定义的翻转标志位(应作全局变量定义)

  voidTelecontrol_Data() {

  ……

  if(Decode_addr==0x5535) {//地址码核对

  if(Decode_key==0x00C0) {//“▲”键码核对

  BEEP=1;//蜂鸣声响输出

  if(switch_sign) {//翻转标志位

  JD1_out=0;//继电器1控制输出端

  JD2_out=1; //继电器2控制输出端

  }

  else {

  JD1_out=1;//继电器1控制输出端

  JD2_out=0;//继电器2控制输出端

  }

  }

  if(Decode_key==0x0030) {//“■”键码核对

  BEEP=1;//蜂鸣声响输出

  JD1_out=0;//继电器1控制输出端

  JD2_out=0;//继电器2控制输出端

  }[page]

  if(Decode_key==0x000C) {//“▼”键码核对

  BEEP=1;//蜂鸣声响输出

  if(switch_sign) {//翻转标志位

  JD1_out=1;//继电器1控制输出端

  JD2_out=0;//继电器2控制输出端

  }

  else {

  JD1_out=0;//继电器1控制输出端

  JD2_out=1;//继电器2控制输出端

  }

  }

  }

  if(Decode_addr==0x5D35) {//取反操作纠正时地址码核对

  if(Decode_key==0x00C0) {//“▲”键码核对

  BEEP=1;//蜂鸣声响输出

  switch_sign=~switch_sign; //取反一次翻转标志位

  save_data();//调用保存数据子程序

  }

  }

  }

  从上面这段遥控数据处理子程序可以看到:在任何时候两个继电器的控制输出端JD1_out与JD2_out至多只能开一个。当遥控“▲”键按下有效时,翻转标志位“switch_sign”的逻辑“0”或逻辑“1”状态将决定两个继电器的控制输出端JD1_out与JD2_out的一开一关,或一关一开;同理,当遥控“▼”键按下有效时也会得到与“▲”键按下相反的类同效果……也就是说,只要改变翻转标志位“switch_sign”的逻辑状态,就能改变两个继电器控制输出端谁“开”谁“关”的控制输出状态。亦即,在同一个遥控按键按下时(如“▲”键按下),标志位“switch_sign”的逻辑状态不同,两个继电器控制输出端谁“开”谁“关”的控制输出状态也将不同。其中的蜂鸣声响提示按键操作是否有效。

  投影屏幕升降的动力电机是一个AC 220V的交流电机,图1是电机控制电路的简图。由此可见,当继电器JD1闭合,JD2断开时,电机M中的L1为主绕组,L2为启动副绕组,电机将一个方向运转;当继电器JD1断开,JD2闭合时,电机M中的L1为启动副绕组,而L2为主绕组了,电机将以原来的反方向运转。结合上文,改变翻转标志位“switch_sign”的逻辑状态→改变两个继电器控制输出端谁“开”谁“关”的控制输出状态→改变电机的运转方向→投影屏幕的升降状态。也就是说,改变翻转标志位“switch_sign”的逻辑状态,就可纠正遥控电机运转方向及其投影屏幕的升降状态。将遥控按键与投影屏幕升降的对应关系协调后,必须保存自定义的标志位“switch_sign”当前的逻辑状态,否则,断电后下一次上电复位初始化,又可能要出洋相了。

  从例举2的程序中还可以看到,翻转标志位“switch_sign”的取反操作也是用同一个遥控器上的“▲”键来执行的,只是在遥控器的地址编码上动了点手脚——改变了一下地址编码(0x5D35),待操作对应协调后再改回到原来的地址编码(0x5535)。

  2保存1个字节来复原自定义标志“位”

  自定义标志“位”的保存及其复原有很多种方法,我曾尝试过几种方法。例3是一种保存1个字节来复原1个自定义标志位的方法,具体操作如下:

  static unsigned char current_dat;//定义一个通用的辅助字节变量

  bit bdata switch_sign;//自定义的翻转标志位(应作全局变量定义)

  ……

  switch_sign=~switch_sign;//取反1次翻转标志位

  if(switch_sign) {//判断switch_sign是逻辑“1”还是逻辑“0”

  current_dat=0xA5;//对通用的辅助字节变量赋值

  }

  else {

  current_dat=0x00;//通用的辅助字节变量

  }

  save_data();//调用保存数据子程序

  BEEP=1;//蜂鸣声响输出

  以上程序是:取反翻转标志位“switch_sign”后,若“switch_sign”为逻辑“1”状态,就给通用的辅助字节变量“current_dat”赋值“0xA5”(当然,这数据由你自己随意定);若“switch_sign”为逻辑“0”状态,则给通用的辅助字节变量“current_dat”赋值“0x00”(这数据也是自己随意定的,只要与前面那个不一样就是了),然后调用保存数据程序将通用的辅助字节变量“current_dat”保存起来。这样,即使断电了翻转标志位“switch_sign”的当前状态已间接地被通用的辅助字节变量“current_dat”保存起来了……当下一次上电复位初始化时,应先将保存的辅助变量“current_dat”的数据读出来,并还原成翻转标志位“switch_sign”相应的逻辑状态。上电初始化时若从存储处读出的数据是“0xA5”,则将翻转标志位“switch_sign”置“1”;若读出的数据是“0x00”,则将翻转标志位“switch_sign”置“0”——这就与原来保存时的状态对应起来了。其操作过程如例4:

  static unsigned char current_dat;//定义一个通用的辅助字节变量

  static unsigned char addr;//自定义地址变量缓冲单元

  static unsigned char Rdat;//自定义读数据缓冲单元

  bit bdata switch_sign;//自定义的翻转标志位(应作全局变量定义)

  ……[page]

  addr=0x7F6;//给一个原来的存储地址

  REEPROM();//调用读取E2PROM的子程序

  current_dat=Rdat;//将读出的数据还给通用的辅助字节变量

  if(current_dat==0xA5) {//判断读出的数据是否等于“0xA5”

  switch_sign=1;//将翻转标志位“switch_sign”置“1”

  }

  else {

  switch_sign=0;//将翻转标志位“switch_sign”置“0”

  }

  31个字节保存8个自定义“位”

  用保存一个自定义的字节变量来复原一个自定义标志位的过程上文已叙述了,接下来阐述1个字节变量保存8个自定义“位”的方案。1个字节变量保存8个自定义“位”的方案很多,例5是其中比较理想的一种:

  #defineuint unsigned int

  #defineuchar unsigned char

  uintaddr;

  ucharWdat,Rdat;

  uchar bdatacurrent_dat;//在可位寻址区定义unsigned char类型的字节变量current_dat

  sbitsign_bit1= current_dat^0;//用关键字sbit 定义位变量来独立访问可寻址位对象中的1位

  sbitsign_bit2= current_dat^1;//自定义标志位2

  sbitsign_bit3= current_dat^2;//自定义标志位3

  ……

  sbitsign_bit8= current_dat^7;//自定义标志位8

  ……

  void Bit_save() {//自定义标志位保存子程序

  addr=0x7F6;//给予存储地址

  Wdat= current_dat;//将current_dat赋值给写E2PROM的缓冲单元Wdat

  save_data();//调用保存子程序存储current_dat数据

  }

  void Bit_comeback() {//自定义标志位复原子程序

  addr=0x7F6;//给一个原来的存储地址

  REEPROM();//调用读取E2PROM的子程序

  current_dat=Rdat;

  //将读出的数据还给通用的辅助字节变量

  }

  以上这段程序所阐述的,也许是有关自定义位操作及其保存的一种最简捷的方案了。首先是在可位寻址区定义ucsigned char类型的通用字节变量current_dat,再用关键字“sbit”定义位变量来独立访问可寻址位对象的其中一位。这样将自定义标志位提高到类同于特殊功能寄存器(SFR)中可位访问的方式来操作了——字节变量current_dat中的8个位各自可以独立操作,且其保存或读出复原都只要直接将字节变量current_dat进行保存或读取即可,无须像其他方案那样需要进行逻辑与、逻辑或等的辅助操作。

  结语

  单片机的C语言编程中不一定都要有自定义的标志位,但是在某些场合运用了自定义的标志位,会使整个程序显得简洁而明快。当然,对于自定义标志位的保存也是视其具体情况而定——应该说是不得已而为之的。

关键字:C51编程  自定义  标志位 引用地址:C51编程中的自定义“位”及其保存方案

上一篇:μC/OS-II在C8051F 系列单片机上的移植
下一篇:基于RS485接口Modbus协议的PLC与多机通讯

推荐阅读最新更新时间:2024-03-16 13:18

C语言在C51单片机中的编程和常用c语言有什么异同
通用c语言和C51单片机c语言基本没做什么大的改变?只是要注意下面几点。 1.数据类型的差别(着不同说在不同芯片上所支持的类型是有些差别,用的时候注意看一下) 2.c51对c语言的数据类型做了扩充。 如下: sfr 特殊功能寄存器数据声明,声明一个8位的寄存器。 sfr16 16特殊功能寄存器的数据声明 sbit 特殊功能位声明 bit 位变量声明
[单片机]
单片机在清除标志时不要用操作
这两天在调试一个项目的时候,一台主机与两台从机通信,但是有一台从机的报文收不到,仔细阅读代码,发现寄存器等配置都正常,自信阅读代码发现在清接收完成寄存器的时候用到了位域,猜想这就是问题所在,于是修改了程序,下载后一切正常。在单片机中一般的标志寄存器都是写1清0,好多中断标志等都是这样设计的,这时候就要注意在清除标志的时候不能用位域,因为好多单片机不不支持位操作的。 比如要清除IrqReg寄存器的BIT5,IrqReg.bit5 = 1;在没有位操作的单片机中会编译成 IrqReg |= BIT5;这条语句分三步执行,先读IrqReg寄存器,进行位或操作,最后把位或的结果写入该寄存器,这样问题就产生了,如果这句操作之前IrqRe
[单片机]
C51单片机模块化编程万年历设计
原文地址: C51单片机模块化编程万年历设计 作者: 给力哈 哈 程序如下: main.h #ifndef __MAIN_H__ #define __MAIN_H__ #include regx52.h #include intrins.h #include absacc.h #define uchar unsigned char #define uint unsigned int #define DQ P3_7 #endif ds18b20.h #include main.h uint sec; uint min=41; uint hour=18; uint day=20; uint month=
[单片机]
C51编程经验三则
在单片机的开发应用中,已逐渐开始引入高级语言,C语言就是其中的一种。用惯了汇编的人,总觉得高级语言 可控性 不好,不如汇编那样随心所欲。以下是笔者在C51编程中的几点经验,希望对初学C51者有所帮助。   一、C51热启动代码的编制   工业控制计算机,往往设有看门狗电路,看门狗动作,计算机复位,这就是热启动。热启动时,一般不允许程序从头开始,因为这将使测量或计算值复位,导致系统工作异常。故程序必须判断是热启动还是冷启动。常用的方法是:设定某内存单位为标志位(如0x7f位和0x7e位),启动时首先读该内存单元的内容,如果它等于一个特定的值(例如两个内存单元的都是0xaa),就认为是热启动,否则就是冷启动,程序执行初始化部分,并将0
[单片机]
单片机+LCD1602显示℃ 这种自定义符号
#include all.h u8 xdata LCD1602_Write_Buffer1 ; u8 xdata LCD1602_Write_Buffer2 ; u8 code LCD1602_5x8 ={0x08,0x00,0x07,0x08,0x08,0x08,0x08,0x07}; u8 xdata LCD1602_Write_Cursor_Add; void LCD1602_Delay(u8 len) { u8 idata i; while(len--) { i=15; while(i--); } } void LCD1602_Write_Add(u8 Ad
[单片机]
Azure ARM (17) 基于角色的访问控制 - 自定义Role
  在上面一篇博客中,笔者介绍了如何在RBAC里面,设置默认的Role。   这里笔者将介绍如何使用自定的Role。   主要内容有:   一.了解Role中的Action和NotAction   二.通过PowerShell,查看相应的Action   三.编辑json Template,自定义Role   四.设置相应的Role   五.删除自定义Role   一.了解Role中的Action和NotAction   比如SQL DB Contributor这个Role,权限如下   允许的操作是Actions的操作,减去NotActions的操作。这个概念非常非常重要。   允许的
[单片机]
Azure ARM (17) 基于角色的访问控制 - <font color='red'>自定义</font>Role
开发自定义的LabVIEW插件
插件能够丰富一种工具的功能,使之能够被应用于更多的领域。对LabVIEW而言,它既是一种程序设计语言,同时也是一个程序设计环境。从后者的角度来看,LabVIEW提供了一系列接口供第三方的插件调用。NI通常把这种用于专业的附加于LabVIEW开发环境的插件称为工具包(Toolkits),并且往往价格不菲。如Report Generation Toolkit就是将LabVIEW与Microsoft Office结合起来,使程序员能够使用它更方便地生成doc或者xls格式的报表。由于LabVIEW并没有完全的开放,NI对第3方开发的工具包(插件)也进行了比较严格的管制,只有经过NI核查并且认证的工具包才能够在NI的产品目录中发布出去。 尽
[测试测量]
开发<font color='red'>自定义</font>的LabVIEW插件
C51和汇编混合编程函数声明
最近看了一些C51的书,遇到了困难,查阅后终于理解了,望对大家有帮助! 先看个例子: ?PR?CLRME SEGMENT CODE; //在程序存储区中定义段 PUBLIC CLRME; //声明函数 RSEG ?PR?CLRME; //函数可被连接器放置在任何地方 CLRME: MOV R0,#7FH CLR A LOOP: MOV @R0,A DJNZ R0,LOOP RET END 只需给存放功能函数的段指定一个段名; ?PR? CLRME SEGMENT CODE;作用是在程序存储区中定义段,CLRME为段名,?PR?表示段位于程序存储区内 PUBLIC CLRME;作用是声明函数为公共函数 RSEG ?PR?CLRME
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved