[原创] STM8l最白菜的入门笔记(2)——gpio篇

辛昕   2012-4-4 01:14 楼主


我们先来观察一下 例程里是怎么操作 gpio的。

我们打开discover这个例程。

 

我们看到main刚开始的几句就是gpio初始化。

(因为我曾折腾过STM32,所以多少熟一点,一看到这个,我就知道,事情就在这,所以不会再看太多。)

 

我们截取其中几句看看。

* USER button init: GPIO set in input interrupt active mode */

  GPIO_Init( BUTTON_GPIO_PORT, USER_GPIO_PIN, GPIO_Mode_In_FL_IT);

        

/* Green led init: GPIO set in output */

  GPIO_Init( LED_GREEN_PORT, LED_GREEN_PIN, GPIO_Mode_Out_PP_High_Fast);

        

/* Blue led init: GPIO set in output */

  GPIO_Init( LED_BLUE_PORT, LED_BLUE_PIN, GPIO_Mode_Out_PP_High_Fast);

        

/* Counter enable: GPIO set in output for enable the counter */

  GPIO_Init( CTN_GPIO_PORT, CTN_CNTEN_GPIO_PIN, GPIO_Mode_Out_OD_HiZ_Slow);

        

/* Wake up counter: for detect end of counter GPIO set in input interupt active mode */

  GPIO_Init( WAKEUP_GPIO_PORT, ICC_WAKEUP_GPIO_PIN, GPIO_Mode_In_FL_IT);

 

注释已经写得很明白了。

玩过430或者stm32的童鞋应该多少都知道,这些比较新款的单片机,跟以前的51不太一样,那就是外部中断源贼多,而且是跟着gpio走的。

坦白说,现在回想起来,其实51的外部中断也是跟着io口走的,想起来了吗?

INT0INT1就是P3.0P3.1,只不过它少一点而已。

但鉴于我们现在是要入门,所以,我们先不管中断——说起来,玩这个还真费了我不少劲,不过说起来已经不错了,哈哈,那时候STM32还不会定时中断呢(当然,现在也没弄定时,但既然外部中断都会了,想来也差不多吧~~~

所以,我们的目标先设得简单一点:

我们只要实现基本的gpio读写功能,读用来读按键,写用来点亮LED

 

右键就可以点开 相关函数 的定义位置(这里说一下,所谓 定义,对函数来说,就是函数实现,与函数声明区分,在讨论程序时,我提到 实现,大多是指的都是这个意义上的 实现,而不再是 一般说的那个 实现 的意思。)

 

void GPIO_Init(GPIO_TypeDef* GPIOx,

               uint8_t GPIO_Pin,

               GPIO_Mode_TypeDef GPIO_Mode)

没事,我们不会去看源码。

我们只要看函数接口。

一个是gpioport口,一个是gpiopin数,一个是gpio的配置模式。

 

对单片机比较熟悉的朋友基本不用多说,这里还是简单说一下,在单片机里,io口都是按组划分。

比如说,最常见的8位机一组8个,然后可能有46组。

当然也有16位机的16个一组,让我有点奇怪的是STM3232ARM,一组却也只有16个。

更有甚者,比如我玩的一个32位系统,居然只有8个一组,看来这个跟位数没必然关系。

 

说了这么多,我们来看看我们这个stm8l-discover开发套件上的gpio口情况。

用不着看pdf,从开发板引出的管脚就知道。

它总共有41io口,

共分6组,五组8个,最后一组1个,它以A~F按顺序命名。

分别是GPIOA~GPIOF.这就是我们的port

然后每组分8位,这就是我们的pin。好了,现在我们搞清楚数目上的状况了。

我们再看第三个参数,IO口类型。

我们可以通过查看gpio.h这个头文件获取相关的信息。

 

看的时候不妨多看一些我们曾见到的熟面孔,这样会加快熟悉对与其相关的宏和操作函数的了解程度。

 

比如说,首先我们看到的就是io口类型

typedef enum

{

  GPIO_Mode_In_FL_No_IT      = (uint8_t)0x00,   /*!< Input floating, no external interrupt */

  GPIO_Mode_In_PU_No_IT      = (uint8_t)0x40,   /*!< Input pull-up, no external interrupt */

  GPIO_Mode_In_FL_IT         = (uint8_t)0x20,   /*!< Input floating, external interrupt */

  GPIO_Mode_In_PU_IT         = (uint8_t)0x60,   /*!< Input pull-up, external interrupt */

  GPIO_Mode_Out_OD_Low_Fast  = (uint8_t)0xA0,   /*!< Output open-drain, low level, 10MHz */

  GPIO_Mode_Out_PP_Low_Fast  = (uint8_t)0xE0,   /*!< Output push-pull, low level, 10MHz */

  GPIO_Mode_Out_OD_Low_Slow  = (uint8_t)0x80,   /*!< Output open-drain, low level, 2MHz */

  GPIO_Mode_Out_PP_Low_Slow  = (uint8_t)0xC0,   /*!< Output push-pull, low level, 2MHz */

  GPIO_Mode_Out_OD_HiZ_Fast  = (uint8_t)0xB0,   /*!< Output open-drain, high-impedance level, 10MHz */

  GPIO_Mode_Out_PP_High_Fast = (uint8_t)0xF0,   /*!< Output push-pull, high level, 10MHz */

  GPIO_Mode_Out_OD_HiZ_Slow  = (uint8_t)0x90,   /*!< Output open-drain, high-impedance level, 2MHz */

  GPIO_Mode_Out_PP_High_Slow = (uint8_t)0xD0    /*!< Output push-pull, high level, 2MHz */

}GPIO_Mode_TypeDef;

 

这是个结构体,如果你熟悉,光看名字就猜到了,如果你不熟悉,看看英文注释也差不多了,当然,假如你对电路的了解不深,那可能不知其所然。

而对我来说,尽管我了解这些都是什么玩意,但是对于部分模式,我并不了解它的作用和意义。

于是我另外花了一些时间,去找这方面的的信息看,最后找到一份 周立功 的文档,感觉相当不错,比起那些坑爹的强太多了——说的都是废话。基本就是把STC那种乱七八糟的pdf里的内容半通不通地翻译成中文而已。没有任何解释。

具体的文档可以上百度搜索,我记得我是请朋友帮我在百度文库里下的,刚找了找,没找着,下次看看补上链接什么的。

 

下面是简单总结:

基本输入电路

 

基本IO输入电路

施密特触发输入电路

弱上拉输入电路

 

基本IO输入,三态缓冲器,只在 读取 时,外部状态会反映到内部总线上,其余时刻不影响内部电路。

读取时,CPU发出一个外部选通信号

 

施密特输入电路

对外部输入脉冲进行整形,它可以去除某种程度的抖动。

 

带外部弱上拉的输入电路

弱上拉的好处是对外部干扰信号有较好的抵抗能力,但输入阻抗显著降低。

而悬空态的输入电路,对于干扰信号抵抗能力较差。

 

至于输出,实际上只有两种:

1 开漏输出

2 内部上拉输出。

 

对这两个我简单解释一下,开漏和开集电极 很接近的。

这里的漏 说的是 场效应管的 漏极,它在功能上类似于三极管的 集电极。

英文称之为 open drain open collection,也就是别人总是神侃的OD门和OC门。

它就是少了一个 上拉电阻,我个人认为它的最大意义有两个:

1 第一,它不怕外部IO短路,可以起相当的保护作用。

2 它适合不同电平之间的匹配,比如常见的5V系统和3.3V系统。

但它的缺陷却是,输出的电平是不定的。也就是 高不一定能高到电源电压,低不能低到地的零电平。

 

而外部上拉,它则可以保证输出永远是稳定的高或者低电平,但是,很显然它遇到IO短路,会有烧毁IO口的危险。

 

关于这一部分,其实我并没说的很明白,这其中的内容,咱们有需要再多找资料看吧,嘿嘿,我懂的也就这么多了。

 

我们这里普普通通,只用 外部上拉输入 外部上拉输入输出。

 

接着看一下这个不是很长的头文件。

 

typedef enum

{

  GPIO_Pin_0    = ((uint8_t)0x01),   /*!< Pin 0 selected */

  GPIO_Pin_1    = ((uint8_t)0x02),   /*!< Pin 1 selected */

  GPIO_Pin_2    = ((uint8_t)0x04),   /*!< Pin 2 selected */

  GPIO_Pin_3    = ((uint8_t)0x08),   /*!< Pin 3 selected */

  GPIO_Pin_4    = ((uint8_t)0x10),   /*!< Pin 4 selected */

  GPIO_Pin_5    = ((uint8_t)0x20),   /*!< Pin 5 selected */

  GPIO_Pin_6    = ((uint8_t)0x40),   /*!< Pin 6 selected */

  GPIO_Pin_7    = ((uint8_t)0x80),   /*!< Pin 7 selected */

  GPIO_Pin_LNib = ((uint8_t)0x0F),   /*!< Low nibble pins selected */

  GPIO_Pin_HNib = ((uint8_t)0xF0),   /*!< High nibble pins selected */

  GPIO_Pin_All  = ((uint8_t)0xFF)    /*!< All pins selected */

}GPIO_Pin_TypeDef;

 

首先是这个 结构体,注意观察,从07很有规律的是十六进制的 0180,显然这是位操作模式。

进一步可以在下面三个体现到,高四位和低四位,还有全八位操作,这是为了提供方面,高四和低四,有点类似51里的SWAP互换高低四位的指令。

 

我们可以在那几句初始化io的函数调用里看到这个结构体成员(也就是那些 GPIO_Pin_0,由此可见,我们选择相应的IO口时,直接通过它就可以了,无须自己去位移或者计算选中相应的IO口线。

 

下面还有一个经常看到 模式判断宏,这个我还没想到有啥可玩,先不管他。

接下来就是几个函数声明了。

 

都是名副其实的东西。

来来去去就是三大类:

1 初始化,它是用来设置相应IO口的工作模式的;

2 写操作,分port口和pin线

3 读操作,同样分port口和pin线。

 

至此,我们已经基本把这个gpio的固件库大致理了个头绪了。

 

再回头看主函数,我们见到他使用的操作gpio函数——有一些实际上就是 带参的宏定义。

比如说这句

 

  /* LED Green ON */

  GPIO_HIGH(LED_GREEN_PORT,LED_GREEN_PIN);

同样可以右键找到这个宏的定义。

在它的 discover_board.h里,显然,这是他自己定义的。

打开一看,如下

 

#define GPIO_HIGH(a,b)                  a->ODR|=b

#define GPIO_LOW(a,b)           a->ODR&=~b

以后等你熟悉了STM的寄存器,你也会知道,这实际上是直接操作它的输出寄存器,IDR就是输入寄存器。

但我们这里,暂时不考虑这么多,我们首先坚持只用固件库的原有函数做开发。

既然我们已经分析了 gpio操作函数分三大类,也就是说,我们可以直接调用。

 

好了,经过以上分析,我们是时候真正展开动手来做一个属于自己的gpio项目文件了。

 

项目的建立见上一篇。

上一篇我们建立了一个只有空函数的可编译的项目文件,现在我们在它的基础上,增加gpio的固件库。

 

因为我们只要操作gpio,所以我们只需要添加gpio.cgpio.h

对于文件夹的布置,我个人很欣赏stm例程的做法,于是,也按照它的做法做。

 


首先是一个 库,命名为 Library

然后是我们自己的项目文件,命名为 Project

再有一个文件夹属于开发中的文档——以后你就会知道,一个完整的项目必须有一些必要的简洁但完整的开发文档,否则,该程序的可维护性和可读性将大打折扣。

 

Library下,仍然学习例程,下分inc存放头文件,src存放源文件。






 

由于我的项目文件中还有别的内容,所以我添加了其它头文件,实际上,对于我们这个gpio来说,除了gpio.h以外,只再需要一个stm8l115x.h。千万别忘了,因为在结构上,如第一篇分析,它是所有外设头文件的基础,没有它,无法完成项目文件的编译。

 

Src中,则只有一个gpio.c

 

当然了,它们的全名是 stm8l115_gpio.c stm8l115x_gpio.h

 

现在,来写我们的主函数。

我们在Project下建立一个main.c源文件,内容很简单,如下:

  1. #include "stm8l15x.h"

    #include "stm8l15x_gpio.h"

    //#include "stm8l15x_exti.h"

    //#include "stm8l15x_it.h"





    #define BUTTON_GPIO_PORT GPIOC

    #define USER_GPIO_PIN GPIO_Pin_1



    #define LD4_GPIO_PORT GPIOC

    #define LD4 GPIO_Pin_7



    int main( void )

    {

    GPIO_Init( BUTTON_GPIO_PORT, USER_GPIO_PIN, GPIO_Mode_In_FL_IT);



    //EXTI_SetPortSensitivity(EXTI_Port_B, EXTI_Trigger_Falling);



    //EXTI_SetPinSensitivity(EXTI_Pin_0, EXTI_Trigger_Falling);



    //enableInterrupts();



    while(1)

    {



    if (GPIO_ReadInputDataBit(BUTTON_GPIO_PORT, USER_GPIO_PIN) )

    GPIO_Init(LD4_GPIO_PORT,LD4, GPIO_Mode_Out_PP_High_Fast);

    else

    GPIO_Init(LD4_GPIO_PORT, LD4, GPIO_Mode_Out_PP_Low_Fast);



    }


这段代码本来是已经做到用gpio口的外部中断了,所以有一些被我注释掉的内容,你可以直接去掉。

我们这个代码实现的功能就是,按着按键时,灯是灭的,松开手时,灯是亮的。

 

具体理解,如同我前面介绍一样,一句话,查看我所调用的函数的函数声明。

你只需要理解它的接口和返回值即可。

 

 

对了,差点忘了两件很重要的事。

1 指定 头文件搜索的相对路径 --- 这对我来说,曾经是非常蛋疼的一件事,现在其实说白了很简单的事情,让我一下就捅破它吧。

 

我们的编译器在包含头文件时,需要给它指定一个路径,才能准确在特定位置找到我们要的头文件。

指定路径有两种方式:

1 绝对路径

2 相对路径

绝对路径就是死的,它就是我们在文件夹的地址栏里看到的这个东西:

C:\Documents and Settings\Administrator\桌面\STM8资料\stm8l-gpio2\Project

它的缺陷是,假设下次你把这个项目文件移到另一个位置,比如说D\下,那它就全傻了,届时你会看到一堆的警告和错误,实在颇为状况和…..让人心惊胆战。

所以,一般推荐相对路径。

相对路径以项目文件中的工作环境文件.eew为起点,依次寻找搜寻对应的文件夹,由此,不管我们把文件夹移到哪去,都可以正确找到我们放在里面的头文件。

 

无论是相对路径还是绝对路径都在一个地方设置。

右键点击 WorkPlace 里那个灰蓝色 实体,选择 Option


选择 C/C++ Compiler下的 Preprocessor选项卡

因为 把头文件包含进代码里,这一步正是 预编译阶段,所以要设置的是 预编译器

其中

$PROJ_DIR$\正是我们前面说的 .eew项目环境文件。

此处要注意的是

\..\它的意思是指,回到上一级文件夹。

回忆一下我们的文件夹是如何布置的。

.eew是存在Project文件夹下,而头文件是在与Project同级的Library下的inc里。

所以,首先要回到上一级文件夹,再找到Library下的inc

这个过程的演变是

$PROJ_DIR$\..\           回到上一级文件夹

$PROJ_DIR$\..\Library\inc  找到该文件夹里Library下的inc

对于其他位置,而是类似的方法,但要注意的是,一个位置的相对地址要单独写一行。

 


2 选择ST-LINK编译器,这个简单很多

  差点又忘了,首先首先要选择 器件型号。

同样在这个Option下,选择第一个 General Option

然后在Device下拉菜单里选择,对于我们的stm8l152c6t6,我们能选到的最接近型号就是

 

选择其中的 STM8L152C6

至于另两个选项是设置 程序存储器 数据存储器 的模式,这个暂时不会玩,先按默认的选即可。


3点击Debugger


Driver一项选择 ST-LINK

好了,OK了,现在点击OK

然后开始编译和运行我们的程序吧。

 

首先编译程序。方法如前所说,右键 Rebuild All

然后插上开发板,直接点那个 绿色小三角,将会看到它在链接和下载。

这时自动处于debug模式,你可以点那些蓝色箭头,实现单步,全速等调试功能。

也可以直接关掉,即可让开发板直接跑程序了。

 

今晚就写到这里吧。

强者为尊,弱者,死无葬身之地

回复评论 (14)

回复 楼主 辛昕 的帖子

这么细心的整理,啥也不说了。谢谢!!
点赞  2012-4-4 08:08

回复 沙发 IC爬虫 的帖子

诶哟,其实也没啥。
就是弄这玩意,真费了不少时间。
而且图片插进去还很麻烦啊.....
强者为尊,弱者,死无葬身之地
点赞  2012-4-4 23:23

回复 板凳 辛昕 的帖子

可以同时上传很多图片,然后在需要插图的位置点击“插入”即可。
加油!在电子行业默默贡献自己的力量!:)
点赞  2012-4-5 08:29

回复 4楼 soso 的帖子

哦.....
强者为尊,弱者,死无葬身之地
点赞  2012-4-7 00:47
无意搜到一个挺不错的 中文索引 stm8l资料 下载页面。先把 链接存在这里。

http://tec.icbuy.com/topic/STM8L.html
强者为尊,弱者,死无葬身之地
点赞  2012-5-3 14:39
不知道这个152C6到底有没有PC口外部中断?库函数了里面没有,datasheet上好像说是有。
typedef enum
{
  EXTI_Port_B = (uint8_t)0x00, /*!< GPIO Port B */
  EXTI_Port_D = (uint8_t)0x02, /*!< GPIO Port D */
  EXTI_Port_E = (uint8_t)0x04, /*!< GPIO Port E */
  EXTI_Port_F = (uint8_t)0x06, /*!< GPIO Port F */
  EXTI_Port_G = (uint8_t)0x10, /*!< GPIO Port G */
  EXTI_Port_H = (uint8_t)0x12  /*!< GPIO Port H */
} EXTI_Port_TypeDef;
点赞  2013-11-19 10:33

回复 7楼cunlingwang 的帖子

stm8s的gpio每一个都有 外部中断
点赞  2013-11-19 10:48
学习了
点赞  2016-3-25 17:24
为什么我操作GPIO的外部中断失败?
void Board_GPIO_Init(void) //
{
        GPIO_InitTypeDef GPIO_InitStructure;
        EXTI_InitTypeDef EXTI_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;

        //init GPIOC.13
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC,ENABLE);
        GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
        // Connect Button EXTI Line to  GPIO Pin 13
        SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource13);
       
        EXTI_InitStructure.EXTI_Line = EXTI_Line13 ;  
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
       
  NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

void EXTI15_10_IRQHandler(void)
{
  
        static uint8_t state;//,Sensor_Module_Address;
        //uint8_t i;
        BOARD_LED_ON(LED1);
        if(EXTI_GetITStatus(EXTI_Line13) != RESET)  
  {         
                if(state == 1){
                        state=0;BOARD_LED_ON(LED3);
                }
                else{
                        state = 1;BOARD_LED_OFF(LED3);
                }
        EXTI_ClearITPendingBit(EXTI_Line13);
   }
}
点赞  2016-3-25 18:39
楼主是个有心人!
点赞  2016-3-31 10:17
原来我对代码的组织思路,不是从零到有的,只是我已经忘了大部分过程而已。

stm32,难怪是真爱,因为它曾经影响了我的所有思路。
强者为尊,弱者,死无葬身之地
点赞  2016-6-16 23:23
学习了…………
点赞  2016-6-17 15:13
学习了
点赞  2016-6-17 17:46
学习了,感谢分享!
点赞  2016-11-9 18:58
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复