2011-05-14 20:47:00 rtgchym 阅读数 3263

      众所周知,单片机在上电复位,或程序正在执行时候按下复位键,PC指针就会指向0地址,程序从0地址处开始执行,这是硬件复位,我暂且称它为“硬复位”,因为下文所讲的是程序在运行过程通过调用执行一段代码跳转到0地址处开始执行,这样也可以实现类似单片机硬复位一样的效果,但却是通过软件来实现的,并不是真正意义上的复位,所以称之为“软复位”。

程序如下:

(*(void(*)())0);

        如果你看完这条语句还没有蒙,那你已经完全没必要继续在这个贴上浪费时间了,可以直接关掉这个页面,如果感觉有点蒙,可以选择继续~~

首先需要说明几个概念:

1.函数的调用:函数的调用可以通过函数名调用(这个使用的最普遍),也可以通过函数指针调用(即指向函数的指针变量调用)。如
int a,b,c;
int max(int,int);
int (*p)(int,int);
p=max;
如果要调用max函数,可以使用c=max(a,b);也可以使用c=(*p)(a,b);
      通过max(a,b)就调用了max函数,这是为什么呢?这不过是一种简写,这样写你看有没有感觉:(*max)(a,b); max是什么,*max是什么?对于这一点函数和数组是一样的,数组名表示数组的首地址,函数名也就表示该函数的入口地址,*是什么,指针运算符,来取地址间接访问,*max就表示取max函数的入口地址,也即调用执行这个函数了,为什么在这儿加括号?因为函数运算符()的优先级高于单目运算符*,如果不加,那么它就等同于*(max(a,b)),这个是什么意思?交给你吧,我们继续~

2.类型转换符

      如果我们知道了如何声明一个给定类型的变量,那么就很容易得到该类型的类型转换符:只需要将声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号封装起来就行了。比如:
int i;
float j;
(int)j;
把变量名
i和末尾的分号去掉,再加个括号就是(int),就是把浮点型的j强制转换为int型;再如void(*h)();定义了一个指向返回值为空的函数的指针,(void(*)())就是“指向返回值为空的函数的指针”的类型转换符。

        到了这里,我们就可以写个类型转换符,将0转换为指向无返回值(或返回值为空)的函数的指针:
        首先将0变为一个函数的入口地址,可以写成(void(*)())0,这个表达式就是将0强制转换为指向无返回值的函数的指针,好了,根据1中所述的函数调用方法我们可以尝试调用这个函数指针,(*(void(*)())0)();这条语句也就是执行入口地址为0的函数了,这也正是上面所讲的复位语句。

----详见《C陷阱与缺陷》之“语法陷阱”

 

 

 

2008-10-07 20:30:00 sunmux 阅读数 1568

在单片机的软件抗干扰原理中,常常采用软件复位的方法,以下是两种常用的软件复位程序:
1、常用软件复位程序

void reset(void)
{
(*(void(*)())0x0000)();
}

汇编码:





 

我们来分析一下:
(*)()是函数指针的强制转换
(*(void(*)())0x0000)()就是将函数的入口地址强制指向0x0000处,即进行了软复位。
它的缺点是不能在中断子程序中调用,因为这句指令仅仅使PC指针指向0x0000处,而并没有清除中断标志位。

2、可以在中断程序中调用的软件复位程序

void reset(void)
{
uchar code rst[] = {0xe4, 0xc0,0xe0,0xc0,0xe0,0x32};

(*((void(*)(void))(rst)))();
}

汇编码:









我们来分析一下:
程序中将代码当作数组的数据来存储。
rst是程序代码的首地址
(void(*)(void))是函数指针的强制转换
(void(*)(void))(rst))是将数组名rst强制转换成一个无参数无返回值的函数的指针,指向rst的首地址,只需调用(*((void(*)(void))(rst)))()即可将数组中的数据当作函数代码来运行。
由汇编语言可知,这段程序可以清除掉中断标志位(使用了RETI指令),因此该段代码可以在中断子程序中运行。

2018-09-12 00:16:06 belous_zxy 阅读数 2012
(*((void(code*)(void))0x0000))();

对应的语句就是汇编中的 LJMP 0000H

原理是将0x0000强制类型转换成一个返回值和参数都是void型的函数指针。

函数指针原型是 void (*func)(void)

该函数指针指向的应是ROM区,所以加关键词code。

(void(code*)(void))0x0000 把0强制类型转换成code区的函数指针

调用该函数指针,程序计数器PC指向0000H,程序复位。

2010-11-14 14:43:00 gexueyuan 阅读数 607

   1.单片机在系统复位或者上电之后,会在执行main()函数之前先执行一小段由maplab c32连接器自动插入的初始化程序,即所谓的starup(启动)代码或者crt0()代码。启动代码负责基本的内务操作,包括栈的所有重要初始化等。

 

   2.随便调用一个库函数,比如printf(),就可能增加数千字节的执行代码,所以要慎用库函数。

 

   3.crt0代码负责在主程序运行之前,将定义在flash里的数组内容,从flash复制到RAM里,crt0另一个重要的功能是将程序声明的每个全局变量初始化为0,这会使程序更加安全并且容易预测,但这是有代价的。如果ram中要存放一个很大的数组,那么即使你没有要求初始化他们,crt0代码也会花一段时间将他们清零,然后才会执行主程序。然而嵌入式应用是不允许这种延时的。在这些应用中,数毫秒都可能使一枚昂贵的功率MOSFET损坏,或者能迅速而安全的使你的应用系统从紧急复位状态恢复过来。在这些特殊的情况中,就得定义特殊函数_on_reset():

 

void _on_reset(void)

{

 

//your code

 

}

 

   该函数将代替crt0代码在开始初始化前调用的一段空白的程序空间。这段程序应该尽量的短。

 

4.const修饰的变量保存在flash(program)中,而一般的的变量保存在ram中,启动代码执行完毕后,才能初始化并且可用。

 

5.RS232接口允许的波特率范围是正负2%。

 

6.按键去抖代码(以explore16开发板为模型,四个按键_RD6,_RD7,_RA7,_RD13)

 

检测输入信号

int reak(void)

{

 

int c=0;

 

if(!_RD6)

  c |=8;

 

if(!_RD7)

  c |=4;

 

if(!_RA7)

  c |=2;

 

if(!_Rd13)

  c |=1;

 

return c;

}

 

按下去抖:

 

int getK(void)

{

int i=0,r=0,j=0;

int c;

 

 

do{

Dleayms(10);

if(c==readkey())

{

if(c>r)

r=c;

i++;

}

else

i =0;

 

}

while(i<10);

 

}

 

 

 

2010-08-25 18:52:00 Fei20090305 阅读数 1736

31、电源管理寄存器PCON:PCON用来管理单片机的电源部分,包括上电复位检测、掉电模式、空闲模式等。单片机复位时PCON全部被清0。
D7         D6              D5         D4         D3         D2         D1         D0
SMOD (SMOD1)(LVDF)(POF)GF1     GF0          PD         IDL

SMOD:该位与串口通信波特率有关
SMOD=0:串口方式1,2,3时,波特率正常
SMOD=1:串口方式1,2,3时,波特率加倍
GF1、GF0:两个工作标志位,用户可以自由使用
PD:掉电模式设定位
PD=0:单片机处于正常工作状态
PD=1:单片机处于掉电模式,可由外部中断低电平触发方式或由下降沿触发或者硬件复位模式唤醒,进入掉电模式后,外部晶振、CPU、定时器、串行口全部停止工作,只有外部中断工作
IDL:空闲模式设定位
IDL=0:单片机处于正常工作状态
IDL=1:单片机处于空闲模式,楚CPU不工作外,其余仍继续工作,在空闲模式下可由人一个中断或硬件复位唤醒。
32、
REN=1;
SM0=0;
SM1=1;
这三位都是串行口控制寄存器SCON里面的,单片机刚上电时SCON被清0,因为串口方式为方式0,串行口位同步移位寄存器的输入/输出方式,当执行完REN置1后,它便从RXD引脚接受数据,并不管与他连接的系统有无法送数据,因此有时候会产生错误。改正的方法:
SM0=0;
SM1=1;
REn=1;
33、extern表示在这里申明的是一个外部函数,外部函数的函数体不在本文件中,而是在其他某个文件中写有这个函数的实现部分。
34、prinf();和puts();函数在C51中的使用实例:
ES=0;
TI=1;
printg("The voltage is %fV",ad_vo);
while(!TI);
TI=0;
ES=1;
在代码执行printf();函数时,会等待TI为1才将字符发送出去,否则一直等待,所以在调用printf()和puts()函数之前我们要手动将TI置1。(如上面的程序)
在使用stdio.h这个头文件中的函数之前,必须将串口部分初始化完毕,最好将串口设置为方式1,波特率与上位机一致。
在每次调用完printf()和puts()函数之后,必须检测是否发送完毕,即检测TI是否为1,当发送完毕后要把TI置0,否则程序出错。(还没有验证)
每次调用printf()和puts()函数之前,必须将串口中断先关闭。若不关闭串口中断,没法送一个字节,程序就会神情进入串口中断,从而导致程序错误。
35、rand()函数产生一个0~32767之间的伪随机数,函数运行完后返回这个伪随机数。
srand(int seed)函数可设置一个初值,然后产生一个初值与32767之间的随机数。如
int a;
srand(200);
a=rand();
a的值将是200~32767之间的随机数。
36、IIC总线由数据线SDA和时钟线SCL两条线构成通信线路,既可以发送数据,也可接受数据。在CPU与被控IC之间、IC与IC之间都可进行双向传送,最高传送速率为400kbps,各种被控器件均并联在总线上,但每个期间都有唯一的地址。
37、IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
38、放大倍数与放大增益
电压 倍数=Uo/Ui(倍),增益=20lg(Uo/Ui)(分贝)
电流 倍数=Io/Ii(倍),增益=20lg(Io/Ii)(分贝)
功率 倍数=Po/Pi(倍),增益=10lg(Po/Pi)(分贝)
39、定时器/计数器的方式3只适用于定时器/计数器T0,当设定定时器/计数器T1处于方式3时,定时器不计数。方式3将T0分为两个独立的8位计数器TL0和TH0。
40、其中TL0为正常的8为计数器,计数溢出后置位TF0,并向CPU申请中断,之后再重装初值。TH0也被固定为一个8位计数器,不过由于TL0已经占用了TF0和TR0,因此这里的TH0将占用定时器T1的中断申请标志TF1和定时器控制位TR1。
41、T1照样可以工作在方式0、方式1、方式2下,但无论哪种工作方式都不可以使用它的中断,因为定时器T0要使用T1的中断。通常情况下,T1都被用来当做串行口的波特率发生器。
42、52单片机还多了一个T2定时器/计数器。通过设置T2CON中的工作方式可以将定时器2设置为三种工作模式,分别捕获、自动重新装载(递增和递减计数)和波特率发生器。
43、当定时器/计数器设定为计数器时,计数脉冲来自相应的外部输入引脚TO(P3.4),T1(P3.5)或T2(P1.0)。当输入信号产生由1到0的负跳变时,计数器的值加1。由于确认一次负跳变需要两个机器周期,即2个振荡周期,因此外部输入计数脉冲的最高频率为振荡频率的1/24。例如,6MHz的频率的晶振,允许输入的外部脉冲的最高频率为250kHz。
44、串行口工作模式0并不是一个同步串口工作方式,它的主要用途是与外面的同步移位寄存器相连,已达到扩展单片机输入并行口和输出并行口的目的。
45、串行控制寄存器SCON中的SM2-多机通信控制位SM2主要用于方式2和方式3。当接收机的SM2=1,可以利用收到的RB8来控制是否
激活RI(RB8=0时不激活RI,收到的信息丢弃;RB8=1时收到的数据进入SBUF,并激活RI,进而在中断服务中将数据从SBUF中读走)。当SM2=0时,不论收到的RB8是0还是1,均可以使受到的数据进入SBUF,并激活RI(即此时RB8不具有控制RI激活的功能),通过实现多机通信,在方式0时,SM2必须是0。在方式1时,若SM2=1,则只有都到有效停止位时,RI才置1。在多机通信中,从机SM2都被置1,若要使从机与主机通信,主机发送地址,若从机地址与主机发送的地址匹配,则从机的SM2=0,然后进行通信。
46、指向二维数组的指针
(1)指向二维数组元素的指针
#include<stdio.h>
main()
{
        static int a[3][4]={{2,4,6,8},{10,12,14,16},{18,20,22,24}};
        int *ptr;
        int i,j;
        ptr=a[0];
        for(i=0;i<3;i++)
            for(j=0;j<4;j++)
                printf("%4d",*ptr++);      
}
(2)指向二维数组的指针
类型说明符  (*指针变量名)[长度]
#include<stdio.h>
main()
{
        static int a[3][4]={{2,4,6,8},{10,12,14,16},{18,20,22,24}};
        int (*ptr)[4];
        int i,j;
        ptr=a;
        for(i=0;i<3;i++)
            for(j=0;j<4;j++)
                 printf("%4d",*(*(ptr+i)+j));      
}

47、指向函数的指针
类型说明符  (*指针变量名)()
48、int *ptr[5]与int (*ptr)[5]之间的区别,前者表示一个数组元素都是指针的数组,后者表示一个指向数组的指针变量。
49、p=(int *)malloc(5*sizeof(int));/*在内存中分配10个字节的连续存储单元块,并把这块连续存储单元的首地址赋值给指针变量p*/
50看门狗定时器寄存器(WDT_CONTR)各位设置
D7         D6      D5                D4              D3              D2         D1         D0
-----   ----       EN_WDT             CLR_WDT       IDLE_WDT         PS2       PS1       PS0
EN_WDT:看门狗允许位,当设置为1时,启动看门狗
CLR_WDT:看门狗清0位,当设为1是,看门狗定时器将重新计数,硬件自动将此位清0
IDLE_WDT:看门狗IDLE模式位。1:看门狗定时器在单片机的“空闲模式”计数,当该位清0是,看门狗定时器在“空闲模式”不计数
PS2,PS1,PS0-看门狗分频设置:
12MHz晶振看门狗定时器预分频值
PS2  PS1  PS0  预分频数 看门狗溢出时间
0       0      0           2               65.5 ms
0       0      1           4               131.0 ms
0       1      0           8               262.1 ms
0       1      1           16             524.2 ms
1       0      0           32             1.0485s
1       0      1           64             2.097s
1       1      0           128           4.1943s
1       1      1           256           8.3886s
51、sfr WDT_CONTR=0xe1;定义STC单片机中新加入的看门狗寄存器,因为reg52.h头文件中没有对该寄存器的定义。
52、STC单片机用户只需简单控制ISP/IAP控制寄存器ISP_CINTR特殊功能寄存器的SWB和SWRST两位就可以实现系统复位。
53、如果定义一个变量后,不对这个变量进行初始化,这个变量的默认的初始值就是0.,其实这个结论需要一定的条件的,用keil编写程序时,总程序中所有变量占用的字节和要小于128B,并且存储器模式位small模式的前提下。一旦程序中的总变量超过128B,必须对所有的变量进行初始化,否则没有初始化的变量默认值将是不确定的。当变量总和超过128B时,还必须在编译器中重新设定存储器的存储模式。
存储器的模式一共有三种,分别为small/compact和large模式。
54、为了安全起见,当程序定义多于128B的其他变量时,最好申明为xdata型,如“uchar xdata table[100]”意思是定义一个100个字节容量的数组,将其扩展RAM中分配存储器空间。另外,在large模式下编写程序,定义的变量总数不要超过对应单片机的内部最大RAM字节数,因为即使超过了,编译器也不会提示错误,但是程序必定出错。
55、typedef与#define的区别
typedef:类型定义,其功能是用户为已有的数据类型取“别名”
例如:typedef int a[10];
表示a是整形数组类型,数组长度为10,然后就可用a定义变量
a s1,s2;
完全等效于:
int s1[1],s2[10];
#define:宏定义
#define PI 3.14
意思是以后中
的程序中出现PI的地方用3.14代替
56、目前常用的单片机与外设之间进行数据传输的串口总线主要有IIC、SPI和SCI总线。其中,IIC总线则以同步串行二线方式进行通信(一条时钟线,一条数据线),而SPI总线是以同步串行三线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)而SCI总线是以异步方式通信(一条数据输入线,一条数据输出线)。
57、直流电机的驱动通常有:三极管电流放大驱动电路,电机专用驱动模块(L298)和达林顿驱动器。
58、步进电机分类:永磁式、反应式、混合式
59、步进电机技术指标:
静态指标:相数、步距数、拍数、定位转矩、保持转矩
动态指数:步距角进度、失步、失调角、电机的共振点
60、电容的作用:
应用于电源电路:实现旁路、去耦、滤波、和储能作用
应用于信号电路:主要完成耦合、振荡/同步及时间常数的作用
61、电容的作用就是通高频阻低频。电容越大低频月容易通过,电容越小高频越容易通过。具体用在滤波电路中时,大电容(1000uF)滤低频,小电容(20uF)滤高频。
没有更多推荐了,返回首页