STM8单片机的触角——I/O口的应用 非原创但是 讲的很不错 拿来给大家 学习!

图片 懒得 弄了 , 想要的 留下邮箱

STM8单片机的触角——I/O口的应用

4.1 玩转 I/O口必备的“五器”

STM8S208RB单片机的I/O口分布情况如图4‑1所示

图4‑1 STM8S208RB管脚分布图

从图4‑1中我们可以看到,STM8S208RB共有64个引脚,其中52个通用输入/输出口(GPIO),它们分别是PA1~PA6、PB0~PB7、PC1~PC7、PD0~PD7、PE0~PE7、PF0、PF3~PF7、PG0~PG7、PI0。其中,每个端口都有一个输出数据寄存器(ODR),一个输入引脚寄存器(IDR),一个数据方向寄存器(DDR),一个控制寄存器1(CR1),一个控制寄存器2(CR2),这就是STM8单片机I/O的“五器”。“五器”到手了,该如何使用,如表4‑1。

配置模式

数据方向寄存器

DDR

控制寄存器1

CR1

控制寄存器2

CR2

配置模式

输入

0

0

0

悬浮输入

0

1

0

上拉输入

0

0

1

中断悬浮输入

0

1

1

中断上拉输入

输出

1

0

0

开漏输出

1

1

0

推挽输出

1

x

1

输出(最快速度10MHz)

x

x

x

真正的开漏输出(特定引脚)

表4‑1 I/O口配置表

由表4‑1中看出,当Px_DDRn为“1”,Pxn配置为输出,否则为输入。

当配置为输入时,若Px_CR1为“1”,上拉电阻使能,否则为悬浮输入。而Px_CR2为“1”时,开启当前I/O口的外部中断功能,为“0”时关闭外部中断功能。若想读取该I/O引脚上的数据,只需读取相应的Px_IDR寄存器即可。

当配置为输出时,若Px_CR1为“1”,Pxn配置为推挽输出,否则为开漏输出。而Px_CR2为“1”时,当前I/O口的最大输出速率为10MHz,否则最大输出速率为2MHz。想要某个I/O口输出高电平,则向Px_ODR中写入0xff,否则写入0x00;

注:针对STM8S208RB而言,x为A、B、C、D、E、F、G、I;n为0~7。

下面的例子演示了如何设置PB口低四位为推挽输出,最快速度10MHz,并输出高电平,高四位为上拉输入。

unsigned char i;

/*设置输出高电平*/

PB_ODR = (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0);

/*设置端口方向*/

PB_DDR = (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0);

/*设置推挽输出和定义上拉电阻*/

PB_CR1 = 0xff;

/*设置端口最大速度和关闭中断*/

PB_CR2 = (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0);

/*读取端口输入数据*/

i = PB_IDR & 0xf0;

4.1.1 悬浮与上拉

悬浮输入与上拉输入是两种输入方式,不同之处在于上拉输入时,引脚内部有个上拉电阻。当引脚悬空时,上拉输入的引脚电平是确定的,即高电平;而悬浮输入则不同,它的电平时不确定的,即使外部的一个很小的信号都会使其发生改变。

上拉输入最典型的应用就是外部按键,当按键未按下时,我们要保证它是高电平,当按键按下时才被拉低;而悬浮输入的典型应用就是模数转换,外部的任何一个小信号都要经过A/D采样转换为数字信号。

4.1.2 开漏与推挽

1. 开漏输出

说开漏输出之前,我们先来看看什么是集电极开路输出。

图4‑2 集电极开路

集电极开路输出的结构图如图4‑2所示,三极管Q1的集电极就是单片机的I/O口,什么都不接,所以叫做集电极开路。当控制端输入为“0”时,三极管Q2截止,及集电极与发射机之间断开,所以5V电压通过R1接到Q1的基级,Q1导通,即相当于管脚直接接地;当控制端输入“1”时,三极管Q2导通,Q1截止,输出引脚与地之间断开。我们将图4‑2简化为图4‑3所示。

图4‑3 集电极开路简化图

图4‑3中的开关受软件控制,“1”时断开,“0”时闭合。很明显可以看出,当开关闭合时,输出直接接地,所以输出低电平。而当开关断开时,则输出端悬空,即引脚为高阻态。这时电平状态未知,如果后面一个电阻负载(即使很轻的负载)到地,那么输出端的电平就被这个负载拉到低电平了,所以这个电路是不能输出高电平的。

图4‑4 带上拉电阻的开漏输出

图4‑4中的10K电阻即是上拉电阻。当开关闭合,输出管脚直接接地,输出为低电平,当开关断开,电流经过10K电阻流入负载,相当于管脚输出高电平。

明白了集电极开路,那么开漏输出就简单了,只要把三极管换成场效应管即可,这样,集电极就变成了漏极,而原理分析是一样的。

开漏输出有这么几个特点:

(1) 利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经上拉电阻到负载,IC内部仅需很小的栅极驱动电流。
(2) 因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的沿的速度。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。)
(3) 开漏结构提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。
(4) 可以将多个开漏输出的引脚连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成与逻辑关系。这也是I2CSMBus等总线判断总线占用状态的原理。
2. 推挽输出

我们同样以三极管为例,来看看推挽输出的结构,如图4‑5所示。

图4‑5 推挽输出

输入端由软件控制,当软件写“1”时,输入高电平,三极管Q1导通,Q2截止;当软件写“0”时,输入低电平,三极管Q1截止,当输出端的电平高于0.7V时,Q2导通。我们可将上面电路图简化为图4‑6所示。

图4‑6 推挽输出简化图

当软件写“1”时,开关S1闭合,S2断开,VCC连接到引脚上,输出高电平;当软件写“0”时,开关S1断开,S2闭合,引脚接到GND,输出低电平。

4.2 LED孤独的闪着

在点亮小灯之前我们先来了解一下LED的一些基础知识,图4‑7是普通发光二极管的外形图及电路符号,长脚为阳极。我们其实也可以看二极管里面大片的一侧是阴极,但是也有些黄色LED是相反的。

图4‑7 LED硬件结构

图4‑8 闪烁LED电路图

图4‑8是闪烁LED的电路原理图,PB0口通过一个330Ω电阻连接发光二极管的阴极,即低电平LED亮,高电平灭。

程序代码如下。

#include "stm8s208r.h"

/***************函数声明***************/

void delay(unsigned int time);

/***************主函数*****************/

int main(void)

{  

    PB_DDR |= 0x01; //选择输出模式

    PB_CR1 |= 0x01; //推挽输出模式

    PB_CR2 |= 0x00; //低速输出模式

    while (1)

    {

        PB_ODR &= 0xfe;  // 小灯亮

        delay(50000);

        PB_ODR |= 0x01;  //小灯灭

        delay(50000);

    }

}

/************延时子程序************************/

void delay(unsigned int time)

{

    while(time--);     

}

问:

老大,这个程序不算难,原理我都懂.就是先把IO口的工作方式等都设置好,然后给小灯一个低电平,小灯就亮,然后延时一会,再给小灯一个高电平,小灯就灭,再延时.如此反复,就会有闪烁的效果.但是问题是为什么给管脚赋值的时候要用”与等于”和”或等于”呢?我就是理解不了这个.

答:

放心,作为老大.我一定不会让你受苦!Relax.Everything is OK.这种赋值方法.无论是“与等于”或者“或等于”,目的都是在改变某一位或者某几位的电平的同时,保持剩余位置的电平不变.

因为进行“与”运算的时候.无论1或者0“与”1后得到的结果没有发生改变(还是1或者0).当进行“或”运算的时候,无论1或者0“或”0后得到的结果也没有发生改变(还是1或者0).讲到这里.应该能明白其中的道理了吧?很简单.当你需要对某位置高的时候就“或”1,需要保持原来的电平的位置,则“或”0.当需要对某位置低的时候就“与”0,需要保持就“与”1这就通过程序达到了“位操作”的效果.

 

Come on baby.Go on reading the most important paragraph!

1.注意,这里的位操作并不是真正意义的位操作.要注意区分两者的本质区别!

2.虽然在本例中不使用这种赋值方法,直接赋值的话对程序不会造成任何影响.但是养成一个好的习惯对以后的进一步学习非常有好处,因为以后程序代码量增加之后,此类这种位操作,经常出现,如果不养成这种习惯,会很难以找出程序中的错误.对调试程序产生极大的影响.既费时又费力.所以极力推荐大家使用这种方法

4.3 跑马灯是怎么跑的?

4.3.1 应用switch-case语句设计跑马灯

大家在前面一节初步的接触了单个IO口寄存器设置及其使用方法,在本节我们来用PB的8个I/O口来进行跑马灯的程序设计。

我们简单的回顾一下switch-case的用法。如图4‑9所示,switch相当于开关的拨口,i选择被连端口,case相当于开关的被连端口,当i为某值时,则有且只导通该路case i。

跑马灯的电路原理图如图4‑10所示.

图4‑9 switch-case示例图

图4‑10 跑马灯电路原理图

程序代码如下。

#include"stm8s208r.h"   //  开始时的头文件包含

/***************函数声明***************/

void delay(unsigned int time);   //声明延时函数

/***************主函数*****************/

Into main(void)

{  

    unsigned char i = 0;   

    PB_DDR = 0XFF;      //设置IO口B为输出

    PB_CR1 = 0XFF;      //设置IO口B为推挽方式

    PB_CR1 = 0X00;      //输出最快速度为2MHz

    PB_ODR = 0XFF;      //设置输出寄存器的输出数值,初始化小灯全灭

    while (1)

    {

        switch (i)        //让小灯从低位到高位依次亮

        {

            case 0:  PB_ODR = 0XFE;

                   Delay(40000);      //延时约20mS

                   i++;              //让i加1下次执行程序时进入到case 2

                   break;            //跳出case 0,重新到switch处检查i值

            case 1:  PB_ODR = 0XFD;

                   Delay(40000);

                   i++;

                   break;          

            case 2:  PB_ODR = 0XFB;

                   Delay(40000);

                   i++;

                   break;      

            case 3:  PB_ODR = 0XF7;

                   Delay(40000);

                   i++;

                   break;  

            case 4:  PB_ODR = 0XEF;

                   Delay(40000);

                   i++;

                   break;      

            case 5:  PB_ODR = 0XDF;

                   Delay(40000);

                   i++;

                   break;      

            case 6:  PB_ODR = 0XBF;

                   Delay(40000);

                   i++;

                   break;      

            case 7:  PB_ODR = 0X7F;

                   Delay(40000);

                   i = 0;

                   break;  

            default : PB_ODR = 0XFF;

                   Delay(40000);

        }

    }

}

/************延时子程序************************/

void delay(unsigned int time)

{

    while (t--);              //让while执行t次空程序达到延时的目的

}

问:

我说老大呀我又懵了,上面的程序里IO口必须设置为推挽输出吗?速度就只能设置为最快2MHz吗?

答:

懵啥呀有我在不用怕,其实它还可以设置为开漏方式,即PB_CR1 = 0x00,小灯是低电平点亮,电路图中我们在小灯正极外接了电源在这种情况下开漏与推挽两者的灌电流都很大所以没有什么区别,具体参考本章第一节,除此之外,还可以设置最快速度10MHz,即PB_CR2 = 0XFF。为了程序的规范编写,在定义函数时要完整,如unsigned char Delay(unsigned char t),如果在没有返回值时,前面最好加void,括号内没有定义行参时也定义void,如void Delay(void)。在写每个case时,一定不要忘了在相应的case语句后加跳出语句break,否则会接着执行下个case语句。

4.3.2 用for循环遛马

硬件电路图与图4‑10一样。

初级骑马技巧代码如下。

#include"stm8s208r.h"     //包含stm8头文件

/***************函数声明***************/

void delay(unsigned int x);   //延时函数

 

/***************主函数*****************/

main()

{

    unsigned char i;

    PB_DDR=0xff;      //I/O口初始化,将PB口设为输出状态

    PB_CR1=0xff;      //PB口(CR1)置1推挽输出

    PB_CR2=0X00;      //CR2置0低速模式,初始化完成

    PB_ODR = 0XFE;    //低电平有效,点亮第一个LED

    while (1)        

    {

        PB_ODR = 0XFE;      //赋初值配合for循环使用

        for(i=0;i<8;i++)

        {

            delay(50000);    

            PB_ODR <<= 1;     //左移一位,点亮下一个LED

            PB_ODR |= 0x01;   //左移后最低位自动补0,故将最低为置高

        }

    }

}

/************延时子程序************************/

void delay(unsigned int x)      

{

  while(x--);

}

4.4 数组与万能流水灯

利用数组让你腾云驾雾但需要反复磨练方能修成正果,原理图等同上节马圈,下面是你期待已久的至高技能。

#include "stm8s208r.h"               //包含头文件使软件识别特殊寄存器像PB_DDR等

/**************定义一个二维数组************/

/*装入使小灯花样闪亮的数据                */

/******************************************/

unsigned char dis[5][8]=

{

{0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f},

{0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe},

{0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff},

{0x7f,0x3f,0x1f,0x0f,0x07,0x03,0x01,0x00},

{0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f}

};

/***************函数声明***************/

void GPIO_Init(void);               // GPIO口的初始化子程序

void display(void);                 // 用PB口显示花样小灯子程

void delay(unsigned int time) ;     // 延时子程序

 

/***************主函数*****************/

int main(void)

{

  GPIO_Init();           //端口初始化

  while(1)                //死循环

   {

    display();           //始终让小灯显示花样

   }

}

/**************GPIO口的初始化*****************/

void GPIO_Init(void)

{

 PB_ODR=0xff;        //初始使小灯全灭

 PB_DDR=0xff;        //DDR为0是输入,1是输出。此处为PB的8个端口均为输出

 PB_CR1=0xff;        //推挽输出模式

 PB_CR2=0x00;       //低速输出模式

}

/************小灯花样显示子程序***************/

void display(void)

{

    unsigned char i,j;

    for(i = 0; i < 5; i++)         //循环二维数组的行数

    {

        for(j = 0; j < 8; j++)        //循环二维数组每行中的数据

        {

            PB_ODR = dis[i][j];   //把数据给PB口使小灯显示       

            delay(50000);

        }

    }

}

/************延时子程序************************/

void delay(unsigned int time)

{

   while(time--);

}

4.5 考验STM8的“臂力”

STM8推挽输出能力很强,我们就用数码管来检验一下STM8的“ 臂力”。

4.5.1 STM8直接驱动一个数码管

数码管是一种最常见的显示器件,可以显示一个8字型的数字,其内部其实是由8个发光二极管组成,每个发光二极管称为一个字段,分为a、b、c、d、e、f、g、dp八段,其中dp为小数点,有共阴极和共阳极两种形式。下面我们就以共阳极的数码管为例作具体讲解。

我们来做一个实验:用STM8直接驱动一个数码管,让数码管显示数字0-9。

硬件电路设计如图4‑11。

图4‑11 STM8点亮一个数码管电路图

程序代码如下。

#include "stm8s208r.h"

/* --------   函数声明  ------------*/

void delay(unsigned int delay_time); 

void display_SMG(void);

/* SMG_table   共阳数码管段选编码*/

unsigned char SMG_table[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

/* 对应显示数字               0 , 1, 2,  3,  4,  5,  6,  7,   8,   9  */

main()

{     /*端口初始化-------------------------------------------------*/

PB_DDR = 0XFF; //定义PB口为输出,用于控制数码管段选

PB_ODR = 0XFF; //定义PB初始输出为高,数码管初始不亮

PB_CR1 = 0XFF; //定义推挽输出

PB_CR2 = 0XFF; //定义快速输出

/*-----------------------------------------------------------*/

while (1)

{

 display_SMG(); //用一个数码管显示数字

}

}

/* 数码管显示函数 */

void display_SMG(void)  

{

unsigned int i;

/*用一个数码管显示 0--9 */

 for(i = 0; i < 10; i++) 

{

PB_ODR = SMG_table[i];

delay(50000);      

}

}

/* 延时函数 */

void delay(unsigned int delay_time)

{

while (delay_time != 0)

{

delay_time--;

}

}

程序分析:

共阳极数码管,顾名思义,就是公共端接到阳极(正极),改变另一端的电平高低来控制其各个段的亮灭,不同的亮灭组合就出现了不同的数字。程序中的SMG_table就是一个存放0~9亮灭组合的数组。用for语句循环十次,显示数字0~9。这里要注意的是,在显示一个数字时,必须保持其亮一段时间,如果数字的变化太快则肉眼无法识别,只能看到一个8字。

4.5.2 STM8驱动8个数码管显示生日

4.5.3  

之前,我们用8个I/O口引脚控制了1个数码管的显示,那我们想控制8个数码管,是不是要用64个I/O口引脚呢?当然不是啦,教你一招,用16个引脚和8个三极管就能控制8个数码管。如图4‑12【所示。

图4‑12数码管动态显示8个数码管

我这一招的秘诀其实是利用了人眼的“视觉暂留”效应实现的。原理是用PB口控制数码管显示显示的内容,用PG口控制哪个数码管亮,其实任意时刻只有一个数码管在亮,一个灭了另一个再亮,这样轮回显示。当数码管的亮灭达到一定频率时,肉眼无法识别,认为8个数码管一起亮的,这就是动态显示。这里的三极管起到了两个作用,一是放大作用,二是开关作用。用PG口控制三极管的通断,导通的三极管对应的数码管有电流流过,就能发亮显示,并且将电流放大,将一个管脚的输出电流放大提供给1个数码管中的8段led发光。

程序代码:

#include "stm8s208r.h"

/* --------   函数声明  ------------*/

void delay(unsigned int delay_time); 

void display_SMG(void);

/* SMG_table   共阳数码管段选编码*/

unsigned char SMG_table[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

/* 对应数码管显示数字          0 ,  1,  2,  3,  4,  5,  6,  7,  8,  9 */

main()

{   /*端口初始化-------------------------------------------------*/

     PB_DDR = 0XFF; //定义PB口为输出,用于控制数码管段选

     PB_ODR = 0XFF; //定义PB初始输出为高,数码管初始不亮

     PB_CR1 = 0XFF; //定义推挽输出

     PB_CR2 = 0XFF; //定义快速输出

     PG_DDR = 0XFF; //定义PG口为输出,用于控制数码管位选

     PG_ODR = 0XFF; //定义PG初始输出为高,数码管初始不亮

     PG_CR1 = 0XFF; //定义推挽输出

     PG_CR2 = 0XFF; //定义快速输出

     /*-----------------------------------------------------------*/   

     while (1)

     {

       display_SMG(); //用数码管显示数字

}

}

/* 数码管显示函数 */

void display_SMG(void)

{   /* 第8个数码管显示2 */

     PB_ODR = SMG_table[2];        //段选,让全部数码管显示2

     PG_ODR = 0X7F;                //位选,0111 1111 让第8个数码管亮

     delay(500);   

     /* 第7个数码管显示0 */

     PB_ODR = SMG_table[0];        //段选,让全部数码管显示0

     PG_ODR = 0XBF;                //位选,1011 1111 让第7个数码管亮

     delay(500);   

     /* 第6个数码管显示1 */

     PB_ODR = SMG_table[1];        //段选,让全部数码管显示1

     PG_ODR = 0XDF;                //位选,1101 1111 让第6个数码管亮

     delay(500);   

    /* 第5个数码管显示1 */

     PB_ODR = SMG_table[1];        //段选,让全部数码管显示1

     PG_ODR = 0XEF;                //位选,1110 1111 让第5个数码管亮

     delay(500);

     /* 第4个数码管显示0 */

  PB_ODR = SMG_table[0];       //段选,让全部数码管显示0

     PG_ODR = 0XF7;                //位选,1111 0111 让第4个数码管亮

     delay(500);   

     /* 第3个数码管显示1 */

     PB_ODR = SMG_table[1];        //段选,让全部数码管显示1

     PG_ODR = 0XFB;                //位选,1111 1011 让第3个数码管亮

     delay(500);   

     /* 第2个数码管显示1 */

     PB_ODR = SMG_table[1];        //段选,让全部数码管显示1

     PG_ODR = 0XFD;                //位选,1111 1101 让第2个数码管亮

     delay(500);   

     /* 第1个数码管显示0 */

     PB_ODR = SMG_table[0];        //段选,让全部数码管显示0

     PG_ODR = 0XFE;                //位选,1111 1110 让第1个数码管亮

     delay(500);

}

/* 延时函数 */

void delay(unsigned int delay_time)

{

  while (delay_time != 0)

  {

    delay_time--;

  }

}

程序分析:

与一个数码管显示不同,显示8个数码管不仅有段选(即显示的内容),还有位选(即选择让哪个数码管亮),位选和段选配合使用,每个数码管亮的内容都是相同的,每次只让一个数码管亮,一个数码管灭了之后另一个马上亮,同时显示内容发生变化,依次由右向左亮灭,这样8个数码管跑起来了。值得注意的是,这里的延时delay(500)不应太长,不宜太短,若延时太长,数码管显示看起来就是一个一个分别亮的,不能达到同时亮的效果。如果延时太短,数码管显示就会很暗,效果不好。

4.6 独立按键的应用

独立按键在单片机中应用非常广泛,用按键输入信息,数码管显示信息,是最简单的一种人机交互方式。

STM8输入总共有4种输入方式,分别是:上拉输入,中断上拉输入,悬浮输入,中断悬浮输入。我们在检测按键时,为了得到稳定的电平,需要用上拉电阻,我们可以通过程序设置启用芯片内部的上拉电阻,也可以设置为悬浮输入,然后接外部上拉电阻。表4‑2为STM8输入方式配置表。

配置模式

数据方向寄存器

DDR

控制寄存器1

CR1

控制寄存器2

CR2

配置模式

输入

0

0

0

悬浮输入

0

1

0

上拉输入

0

0

1

中断悬浮输入

0

1

1

中断上拉输入

表4‑2 输入模式配置

4.6.1 启用内部上拉检测按键状态

接下来我们就设计一个实验,用一个按键控制数码管显示数字的变化,当PE0所接的独立按键按下时,数码管中显示的数字加1,显示0~9范围的数字。

硬件电路:

图4‑13启用内部上拉电阻时接独立按键

程序代码如下:

#include "stm8s208r.h"

/* --------   函数声明  ------------*/

void delay(unsigned int delay_time); 

void display_SMG(void);

void key_press(void);

/*----------------------------------*/

/* SMG_table   共阳数码管段选编码*/

unsigned char SMG_table[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

/* 对应显示数字               0, 1,  2,  3,  4,   5,   6,  7,  8,  9 */

unsigned char Number; //显示的数字

main()

{

    /*端口初始化-------------------------------------------------*/

     PB_DDR = 0XFF;  // 1111 1111 定义PB口为输出,用于控制数码管段选

     PB_ODR = 0XFF;  // 1111 1111 定义PB初始输出为高,数码管初始不亮

     PB_CR1 = 0XFF;  // 1111 1111 定义推挽输出

     PB_CR2 = 0XFF;  // 1111 1111 定义快速输出

     PE_DDR &= 0XFE; // 1111 1110 定义PC口为输入,检测按键输入

     PE_CR1 |= 0X01; // 0000 0001 定义为上拉输入,芯片内部启用上拉电阻

     PE_CR2 &= 0XFE; // 1111 1110 定义不使能中断

    /*-----------------------------------------------------------*/

    while (1)

    {

     key_press();   //用查询法判断按键是否按下

     display_SMG(); //用数码管显示Number值

 }

}

/* 按键处理函数 */

 

void key_press()

{

     /* 判断按键是否按下 */

   if((PE_IDR & 0x01) == 0)

  { 

         delay(13000);             //延时10--20ms 去除抖动

    if((PE_IDR & 0x01) == 0) //再次判断按键是否按下,若按下,Number+1

           {

              if(Number < 9)      

            Number++;

              else

               Number = 0;               

            }

}

}

/* 数码管显示函数 */

void display_SMG(void)  

{

    /*用一个数码管显示 Number */

    PB_ODR = SMG_table[Number];

}

/* 延时函数 */

void delay(unsigned int delay_time)

{

  while (delay_time != 0)

  {

    delay_time--;

   }

}

程序分析:

这个程序就是在之前用一个数码管显示数字的基础上加了独立按键控制显示内容,注意端口初始化中PE0口的配置,PE_DDR &= 0XFE,设置PE0为输入方式,PE_CR1 |= 0X01,设置PE0为启用上拉电阻,PE_CR2 &= 0XFE,设置PE0 为不使用中断。当没有按键按下时,PE0口检测到高电平,if((PE_IDR & 0x01) == 0)不成立,若按键按下,PE0口检测到低电平,条件成立,执行相应操作(即显示数字加1)。在key_press()函数里,做两次if判断和延时10~20ms的目的是去除抖动干扰。

4.6.2 浮空+外部上来检测按键状态

刚才我们设置了上拉输入方式,现在我来试一下浮空输入方式。

将前面用上拉方式接独立按键的程序稍加修改,只需在端口初始化时,将PE的端口的配置方式改一下即可。

/*端口初始化-------------------------------------------------*/

    PB_DDR = 0XFF;  //1111 1111 定义PB口为输出,用于控制数码管段选

    PB_ODR = 0XFF;  //1111 1111 定义PB初始输出为高,数码管初始不亮

    PB_CR1 = 0XFF;  //1111 1111 定义推挽输出

    PB_CR2 = 0XFF;  //1111 1111 定义快速输出 

    PE_DDR &= 0XFE; //1111 1110 定义PE口为输入,检测按键输入

    PE_CR1 &= 0XFE; //1111 1110 定义为浮空输入,需要外部接上拉电阻

    PE_CR2 &= 0XFE; //1111 1110 定义不使能中断

    /*-----------------------------------------------------------*/

更改了程序,我们重新下载,实验一下,在配置浮空方式并且外部没有启用上拉电阻的情况下,独立按键非常不稳定,不能正确的反应按键的状态。这时候我们就需要外加上拉电阻使PE0得到稳定的电平。如图 4‑14所示

图 4‑14 悬浮输入外部加上拉电阻

在原先浮空输入的基础上,PE0引脚接了10k的上拉电阻。当没有按键按下时,PE0引脚因为有上拉而得到高电平,当按键按下时,PE0和GND相连得到低电平。我们通过外部自己加上拉电阻,将原本浮空、不确定电平的引脚变成了确定的高电平或者低电平,保证了按键的可靠性和稳定性。

对比总结:

经过“上拉输入”和“浮空输入”的对比实验,我可以得出结论:上拉输入可以使引脚在不接任何东西的状态下得到确定的电平,而浮空输入在不接任何东西的情况下电平不确定,在接按键或者一些其他需要判断引脚高低电平的场合,最好启用内部上拉电阻。

4.7 使用固件库点亮神灯

4.7.1 什么是固件库

“世界因懒人而变化!”人们因为懒得走路而发明了汽车,因……而……,因……而……,因懒得直接操作寄存器而制作的固件库。

每初始化一个I/O口就需要操作相应的DDR、CR1、CR2寄存器,使用的时候就需要操作ODR、IDR寄存器,有没有办法简化一些呢?有,那就是编写一个函数,每次想用I/O口的时候就调用这些函数,当这样的函数编多了,组合起来,就成了固件库。固件库一般由芯片生产厂商提供。

接下来,我们就看看意法半导体为STM8s做的固件库(下载地址:)

文件“stm8s_fwlib_um.chm”是库函数的帮助文件,“version.txt”是版本信息,文件夹“FWLib”下面又有三个文件夹:“examples”中包含的是各功能模块的例子,“library”中包含的就是今后要用到的库了,“project”则包含了一个例子工程。

4.7.2 建立第一个包含库的工程

第一步:按常规方法建立空工程。

第二步:复制“library”文件夹复制到前面的工程文件夹中,复制“project”文件夹中的“main.c”、“stm8s_conf.h”到工程文件夹中。

第三步:在“Source Files”上点右键,选择“Add Files to Folder...”选择“library\src”中的“stm8s_gpio.c”。

第四步:选择“Project”下的“Settings...”或使用快捷键“Shift+F7”打开“Project Settings”对话框,选择“C Compiler”选项卡,如图选择“Preprocessor”,在出现的页面中添加路径“library\inc”,如图所示。

完成:点击编译或快捷键F7,出现“0 error(s), 0 warning(s)”就成功了!

4.7.3 重新点亮神灯

工程建立好了,让我们重新点亮神灯吧。

首先,在工程列表的“External Dependencies”文件夹中,打开“stm8s.h”确定第45行的“#define STM8S208”未注释。

45  #define STM8S208

46  /* #define STM8S207 */

47  /* #define STM8S105 */ 

48  /* #define STM8S103 */

接着,打开“stm8s_conf.h”,去掉第80行的“#define _GPIO (1) ”的注释。

79  /*********************************** GPIO *********************************/

80  #define _GPIO (1)

81 

82  /*********************************** I2C **********************************/

83  /* #define _I2C  (1) */

去掉“main.c”文件的只读属性,下面就开始编写主程序。

#include "stm8s.h"

void delay(unsigned int time);

void main(void)

{

    /* 设置PB0为推挽高速输出,初始值为高电平 */

    GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST);

    while (1)

    {

        /* 置低PB0,点亮LED */

        GPIO_WriteLow(GPIOB, GPIO_PIN_0);

        delay(50000);

        /* 置高PB0,熄灭LED */

        GPIO_WriteHigh(GPIOB, GPIO_PIN_0);

        delay(50000);

    }

}

void delay(unsigned int time)

{

    while(time--)

    {

        ;

    }

}

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值