第5章_瑞萨MCU零基础入门系列教程之GPIO输入输出

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总https://blog.csdn.net/qq_35181236/article/details/132779862


第5章 GPIO输入输出

本章目标

  • 了解RASC的使用
  • 掌握GPIO的配置与使用

5.1 硬件操作原理

5.1.1 引脚表示方法

GPIO(General-Purpose Input/Output ports,通用输入/输出接口),用于感知外界信号(输入方向)和控制外部设备(输出方向)。

学习单片机时第一个程序往往是点亮一个LED,第二个程序是使用按键控制LED。理解GPIO的操作之后,就可以操作更丰富的模块,比如蜂鸣器、温度传感器等。这些外设模块比较简单,硬件上它只需与MCU的一个GPIO引脚相连。在单个GPIO引脚的基础上,还可以扩展出需要多个引脚才能实现的“协议”,比如UART、I2C、SPI接口等。

如上图所示,如今的MCU大都采用引脚复用技术,一个引脚可以用作普通的GPIO,也可以用作某种接口的引脚,比如用作I2C接口的时钟引脚SCK。此外,有些引脚还能作为ADC引脚用来读取模拟信号,或者作为DAC引脚输出模拟信号。

芯片的引脚,在数据手册里可能被称为PIN或者PAD。怎么表示一个引脚?有两种方法:引脚编号(pin number)、引脚名(pin name)。

芯片的每一个引脚都有一个编号。对于贴片封装的芯片,使用数字编号,比如第100号引脚、PIN100;对于BGA封装的芯片,使用行列编号:使用数字(1、2、3、……)表示行,使用字母(A、B、C、……,忽略字母I、O以免跟数字1、0混淆)表示列,比如A1表示第A列第1行的引脚。如下图所示:

使用引脚编号可以快速找到引脚的位置,但是不容易分辨它的功能。芯片厂商还会给每个引脚赋予一个名字,以表明它的功能。比如LQFP176封装的芯片,它的51号引脚名字是P202,表示它是“Port 2的第2个引脚”;BGA176封装的芯片,它的C4引脚名字是P300/TCK/SWCLK,表示它有三种功能:“Port 3的第0个引脚”、JTAG的TCK引脚、SWD调试接口的时钟引脚。有些引脚的功能有很多种,而引脚名一般都比较短,并不能完全描述它的所有功能。

5.1.2 GPIO操作方法

在RA5M5芯片手册里,可以看到GPIO的框图:

这个框图里有4部分内容:

① 引脚;
② 配置(比如上拉电阻、open-drain等配置)、叁引脚复用;
③ GPIO模块;
④ 其他模块。

这4部分的关系,可以用下面简化的图来概括:

image4

一个引脚,可以对它进行配置,比如使能内部上拉、使用开漏输出等等。

一个引脚有多个功能时,可以通过“引脚复用”选择它的功能:让这个引脚连接到芯片内部的GPIO模块、I2C模块或其他模块。默认情况下,大多数引脚都是连接到GPIO模块。

当引脚用作GPIO时,第1步就是设置它的方向:输入还是输出。接下来还可以进行配置:对于输入引脚可以使能它的上拉电阻、下拉电阻,或者浮空;对于输出引脚,可以让它使用开漏功能。

对于输入引脚,通常可以配置它使能内部上拉电阻,这是为了给引脚一个确定的默认电平。通常情况下,要避免引脚浮空。

image5

在上图中,PIN1被配置为输入方向,用来读取KEY1的状态,本意是:读PIN1得到‘0’表示KEY1被按下,得到‘1’表示KEY1被松开。如果内部上拉电阻、下拉电阻都没有被使能,在KEY1被松开时,它就是浮空的状态,这时读取PIN1的电平可能得到‘0’也可能得到‘1’,是不确定的。这个场景里,应该使能PIN1的内部上拉电阻,或者在芯片之外提供一个上拉电阻。

对于PIN2,它连接到芯片内部的ADC模块,想把PIN2上的模拟信号转换为数值。这个场景里,PIN2的内部上拉电阻、下拉电阻都要禁止,让PIN2处于高阻态,否则会影响模拟信号。

对于输出引脚,它的内部通常是PMOS和NMOS的组合电路,用以实现IO的推挽输出或者开漏输出,例如下图:

当输出引脚被配置为推挽输出时,PMOS和NMOS都会参与工作。当“Output control”输出低电平时NMOS导通使得引脚输出低电平,当“Output control”输出高电平时PMOS导通使得引脚输出高电平。

当输出引脚被配置为开漏输出时,PMOS被禁止。当“Output control”输出低电平时NMOS导通使得引脚输出低电平;但是当“Output control”输出高电平时,PMOS被禁止而NMOS不导通,这使得引脚相当于浮空,它的电平由外接的电路决定。I2C引脚通常被配置为开漏输出。

5.1.3 LED和按键

怎么控制LED?要输出什么电平才能点亮一个LED呢?怎么读取按键状态?读取到什么电平表示按键被按下了?

这完全取决于硬件的设计,需要根据硬件原理图来分析,例如下图的LED和按键的硬件原理图:

  • 通过P400引脚来控制LED:P400输出低电平则点亮LED,输出高电平则熄灭LED。
  • 通过P000引脚读取K2状态:读到0表示K2被按下,读到1表示K2被松开。

5.2 ioport模块的使用

5.2.1 使用RASC配置

如果要从头创建工程,可以参考《3.2.3 创建e2 studio工程》或《3.5.1 使用RASC创建MDK工程》,然后再根据本节内容配置引脚。

本节工程是“0501_led”,User LED的控制引脚是P400。

使用RASC配置引脚时,打开Pins页面,在“Port”下面找到端口P4,进而找到引脚P400,就可以在“Pin Configuration”窗口配置这个引脚了。

各配置项的取值如下图所示(Mode选为“Output mode(Initial Low)”、Outputtype选为“CMOS”):

上图里各个配置参数的含义如下:

配置项 取值/描述
Mode l “Input mode”(输入模式)l “Output mode(Initial Low)”(输出模式,初始电平为低)l “Output mode(Initial High)”(输出模式,初始电平为高)
Pull up(上拉电阻) l “None”(禁止内部上拉)l “input pull-up”(使能内部上拉)当引脚被配置为Output mode时无法设置Pull up参数
IRQ(中断) l “None”(不使用中断)l “IRQ10”(使用中断)
Output type(输出类型) l “CMOS”l “n-ch open drain”(开漏)当引脚被配置为Input mode时无法设置本参数

配置好引脚后,点击右上角的“Generate Project Content”就会生成代码。RASC会为这些引脚生成配置信息,保存在pin_data.c文件里。

5.2.2 配置信息解读

使用RASC配置引脚后,在 0501_LED -> ra_gen -> pin_data.c 中生成如下代码:

 const ioport_pin_cfg_t g_bsp_pin_cfg_data[] = {
    
    
     ......(省略内容)
     {
    
    .pin = BSP_IO_PORT_04_PIN_00,
      .pin_cfg = ((uint32_t) IOPORT_CFG_PORT_DIRECTION_OUTPUT 
                | (uint32_t) IOPORT_CFG_PORT_OUTPUT_LOW)
     },
 };

对于要配置的每一个引脚,都会生成一个ioport_pin_cfg_t数组项,这个结构体类型定义如下:

typedef struct st_ioport_pin_cfg
{
    
    
    uint32_t          pin_cfg;         ///< 引脚的配置值,取值类型为ioport_cfg_options_t
    bsp_io_port_pin_t pin;             // 引脚ID,即:哪个引脚
} ioport_pin_cfg_t;

指定引脚时需要2个参数:它是哪一组?它是这组里的哪一个?比如引脚P400属于第4组里的第0个引脚。使用一个整数来表示引脚:高8位表示组号,低8位表示引脚号,比如P400的引脚ID是“0x070D”。在bsp_io.h里,为每一引脚都事先定义了一个宏,比如:

typedef enum e_bsp_io_port_pin_t
{
    
    
    BSP_IO_PORT_00_PIN_00 = 0x0000,    ///< IO port 0 pin 0
    BSP_IO_PORT_00_PIN_01 = 0x0001,    ///< IO port 0 pin 1
……
    BSP_IO_PORT_04_PIN_00 = 0x0400,    ///< IO port 4 pin 0

指定引脚的配置时,需要设置结构体里的pin_cfg成员,它的可取值也事先定义好了,在r_ioport_api.h中有如下定义:

typedef enum e_ioport_cfg_options
{
    
    
    IOPORT_CFG_PORT_DIRECTION_INPUT  = 0x00000000, // 输入方向
    IOPORT_CFG_PORT_DIRECTION_OUTPUT = 0x00000004, // 输出方向
    IOPORT_CFG_PORT_OUTPUT_LOW       = 0x00000000, // 低电平
    IOPORT_CFG_PORT_OUTPUT_HIGH      = 0x00000001, // 高电平
    IOPORT_CFG_PULLUP_ENABLE         = 0x00000010, // 使能内部上拉电阻
    IOPORT_CFG_PIM_TTL               = 0x00000020, // 使能引脚的输入模式
    IOPORT_CFG_NMOS_ENABLE          = 0x00000040, // NMOS open-drain output,NMOS开漏输出
    IOPORT_CFG_PMOS_ENABLE           = 0x00000080, // PMOS open-drain ouput, PMOS开漏输出
    IOPORT_CFG_DRIVE_MID             = 0x00000400, // 引脚驱动能力为中等
    IOPORT_CFG_DRIVE_HS_HIGH         = 0x00000800, // 引脚驱动能力为高,并且支持高速率
    IOPORT_CFG_DRIVE_MID_IIC      = 0x00000C00, // 设置引脚的输出能力可用于I2C的20mA端口
    IOPORT_CFG_DRIVE_HIGH            = 0x00000C00, ///< Sets pin drive output to high
    IOPORT_CFG_EVENT_RISING_EDGE     = 0x00001000, // 事件触发方式为上升沿
    IOPORT_CFG_EVENT_FALLING_EDGE    = 0x00002000, // 事件触发方式为下降沿
    IOPORT_CFG_EVENT_BOTH_EDGES      = 0x00003000, // 事件触发方式为双边沿
    IOPORT_CFG_IRQ_ENABLE            = 0x00004000, // 使能引脚的中断功能
    IOPORT_CFG_ANALOG_ENABLE         = 0x00008000, // 引脚用作模拟信号
    IOPORT_CFG_PERIPHERAL_PIN        = 0x00010000  // 引脚用作外设的引脚
} ioport_cfg_options_t;

5.2.3 API接口

在r_ioport_api.h中定义了ioport模块的接口,它定义了一个结构体类型ioport_api_t,内容如下:

  typedef struct st_ioport_api
  {
    
    
     fsp_err_t (* open)(ioport_ctrl_t * const p_ctrl, const ioport_cfg_t * p_cfg);
     fsp_err_t (* close)(ioport_ctrl_t * const p_ctrl);
     fsp_err_t (* pinsCfg)(ioport_ctrl_t * const p_ctrl, const ioport_cfg_t * p_cfg);
     fsp_err_t (* pinCfg)(ioport_ctrl_t * const p_ctrl,);
                          bsp_io_port_pin_t pin, uint32_t cfg);
     fsp_err_t (* pinEventInputRead)(ioport_ctrl_t * const p_ctrl, 
                                     bsp_io_port_pin_t pin,
                                    bsp_io_level_t * p_pin_event);
    fsp_err_t (* pinEventOutputWrite)(ioport_ctrl_t * const p_ctrl, 
                                    bsp_io_port_pin_t pin,
                                     bsp_io_level_t pin_value);
     fsp_err_t (* pinRead)(ioport_ctrl_t * const p_ctrl, 
                           bsp_io_port_pin_t pin,
                           bsp_io_level_t * p_pin_value);
     fsp_err_t (* pinWrite)(ioport_ctrl_t * const p_ctrl, 
                            bsp_io_port_pin_t pin,
                            bsp_io_level_t level);
     fsp_err_t (* portDirectionSet)(ioport_ctrl_t * const p_ctrl, 
                                    bsp_io_port_t port,
                                    ioport_size_t direction_values, 
                                    ioport_size_t mask);
     fsp_err_t (* portEventInputRead)(ioport_ctrl_t * const p_ctrl, 
                                      bsp_io_port_t port, 
                                      ioport_size_t * p_event_data);
     fsp_err_t (* portEventOutputWrite)(ioport_ctrl_t * const p_ctrl, 
                                        bsp_io_port_t port,
                                        ioport_size_t event_data,
                                        ioport_size_t mask_value);
     fsp_err_t (* portRead)(ioport_ctrl_t * const p_ctrl, 
                            bsp_io_port_t port,
                            ioport_size_t * p_port_value);
     fsp_err_t (* portWrite)(ioport_ctrl_t * const p_ctrl, 
                             bsp_io_port_t port,
                             ioport_size_t value, ioport_size_t mask);
  } ioport_api_t;

在具体的C文件中,需要实现一个ioport_api_t结构体,比如在r_ioport.c里实现了如下结构体:

  /* IOPort Implementation of IOPort Driver  */
  const ioport_api_t g_ioport_on_ioport =
  {
    
    
  	.open				  = R_IOPORT_Open,
  	.close				  = R_IOPORT_Close,
  	.pinsCfg			  = R_IOPORT_PinsCfg,
  	.pinCfg				  = R_IOPORT_PinCfg,
  	.pinEventInputRead	  = R_IOPORT_PinEventInputRead,
  	.pinEventOutputWrite  = R_IOPORT_PinEventOutputWrite,
  	.pinRead			  = R_IOPORT_PinRead,
  	.pinWrite			  = R_IOPORT_PinWrite,
  	.portDirectionSet	  = R_IOPORT_PortDirectionSet,
  	.portEventInputRead	  = R_IOPORT_PortEventInputRead,
  	.portEventOutputWrite = R_IOPORT_PortEventOutputWrite,
  	.portRead			  = R_IOPORT_PortRead,
  };

要操作某个引脚时,可以调用结构体g_ioport_on_ioport里的各个函数指针,也可以直接调用r_ioport.c里实现的各个函数(比如R_IOPORT_Open、R_IOPORT_PinRead)。

5.2.4 API接口用法

操作一个GPIO引脚时,要先打开它(open),在open函数内部会进行配置(pinsCfg/pinCfg),最后就可以读写了(pinRead/pinWrite)。

  1. 打开IO设备

函数原型:

  /** Initialize internal driver data and initial pin configurations.
  	* Called during startup.  Do not call this API during runtime.
  	* Use @ref ioport_api_t::pinsCfg for runtime reconfiguration of multiple pins.
  	* @par Implemented as
  	* - @ref R_IOPORT_Open()
  	* @param[in]  p_cfg				   Pointer to pin configuration data array.
  	*/
  fsp_err_t (* open)(ioport_ctrl_t * const p_ctrl, const ioport_cfg_t * p_cfg);

此函数指针有两个参数,p_ctrl是一个ioport_ctrl_t指针,它的定义如下:

typedef void ioport_ctrl_t;

所以在r_ioport_api.h文件里,p_ctrl实际上是一个void指针,它可以指向任意类型的数据类型,这是一种良好的编程思想:封装内部实现的细节。在r_ioport.h里,这个参数实际的类型是ioport_instance_ctrl_t结构体,定义如下:

typedef struct st_ioport_instance_ctrl
{
    
    
    uint32_t     open;
    void const * p_context;
} ioport_instance_ctrl_t;

ioport_instance_ctrl_t结构体的open成员,被用来标记这个模块是否已经被打开,p_context成员没有被用到。作为模块的使用者无需了解这个结构体的内部结构,所以在r_ioport_api.h文件里把open函数指针的第1个参数指定为void指针。

第二个参数p_cfg是一个ioport_cfg_t结构体指针,原型如下:

  typedef struct st_ioport_cfg
  {
    
    
  	   uint16_t number_of_pins; ///< Number of pins for which there is configuration data
  	   ioport_pin_cfg_t const * p_pin_cfg_data; ///< Pin configuration data
  } ioport_cfg_t;

这个结构体有两个成员:

  • number_of_pins:要配置的引脚数量,表示后面的p_pin_cfg_data数组里有多少项
  • p_pin_cfg_data:它是一个ioport_pin_cfg_t结构体数组,每个数组项都表示一个引脚的配置参数;

引脚的配置参数也是用一个结构体来表示的,原型如下:

  typedef struct st_ioport_pin_cfg
  {
    
    
  ///< Pin PFS configuration - Use ioport_cfg_options_t parameters to configure
     uint32_t			 pin_cfg;	
     bsp_io_port_pin_t pin;			  ///< Pin identifier
  } ioport_pin_cfg_t;

这个结构体的成员含义是:

  • pin_cfg:GPIO的具体配置值,比如方向、默认输出电平等;
  • pin:具体的GPIO引脚,这是一个枚举类型的成员,该枚举中包括了处理器的所有引脚的宏定义值;

示例代码如下:

  const ioport_pin_cfg_t g_bsp_pin_cfg_data[] =
  {
    
    
      {
    
     .pin = BSP_IO_PORT_01_PIN_06,
        .pin_cfg = ((uint32_t) IOPORT_CFG_DRIVE_HIGH
                  | (uint32_t) IOPORT_CFG_PORT_DIRECTION_OUTPUT
                  | (uint32_t) IOPORT_CFG_PORT_OUTPUT_HIGH)
      },
  };
  const ioport_cfg_t g_bsp_pin_cfg =
  {
    
    
      .number_of_pins = sizeof(g_bsp_pin_cfg_data) / sizeof(ioport_pin_cfg_t),
      .p_pin_cfg_data = &g_bsp_pin_cfg_data[0],
  };
  • 第1~8行,先定义一个ioport_pin_cfg_t结构体数组,每个数组项被用来配置一个引脚。
  • 第9~13定义一个ioport_cfg_t结构体,它会应用前面定义的ioport_pin_cfg_t结构体数组,并表明这个数组多大。ioport_cfg_t结构体就含有这些引脚的配置信息了。

在common_data.c中,使用如下代码定义了一个ioport模块的实例,即ioport_instance_t结构体:

 const ioport_instance_t g_ioport =
 {
    
    
     .p_api = &g_ioport_on_ioport,
     .p_ctrl = &g_ioport_ctrl,
     .p_cfg = &g_bsp_pin_cfg,
 };
  • 第3行,指定API结构体,g_ioport_on_ioport里含有各个API函数。
  • 第4行,指定Ctrl结构体,作用不大,仅仅记录模块的状态(是否open)。
  • 第5行,指定配置结构体,含有多个引脚的配置信息。

如果使用面向对象的编程方法,后续对GPIO的操作可以只使用g_ioport结构体。

在哪里打开引脚、配置引脚呢?在hal_entry.c中有R_BSP_WarmStart函数,代码如下:

  void R_BSP_WarmStart(bsp_warm_start_event_t event)
  {
    
    
      if (BSP_WARM_START_RESET == event)
      {
    
    
  #if BSP_FEATURE_FLASH_LP_VERSION != 0
          /* Enable reading from data flash. */
          R_FACI_LP->DFLCTL = 1U;
  #endif
      }
      if (BSP_WARM_START_POST_C == event)
      {
    
    
          /* C runtime environment and system clocks are setup. */
          /* Configure pins. */
          R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
      }
  }

在第14行直接调用r_ioport.c里实现的R_IOPORT_Open函数,它的内部会使用了Renesas的库函数r_ioport_pins_config来配置引脚。R_IOPORT_Open函数的代码如下:

  fsp_err_t R_IOPORT_Open (ioport_ctrl_t * const p_ctrl, const ioport_cfg_t * p_cfg)
  {
    
    
      ioport_instance_ctrl_t * p_instance_ctrl = (ioport_instance_ctrl_t *) p_ctrl;

  #if (1 == IOPORT_CFG_PARAM_CHECKING_ENABLE)
      FSP_ASSERT(NULL != p_instance_ctrl);
      FSP_ASSERT(NULL != p_cfg);
      FSP_ASSERT(NULL != p_cfg->p_pin_cfg_data);
      FSP_ERROR_RETURN(IOPORT_OPEN != p_instance_ctrl->open, FSP_ERR_ALREADY_OPEN);
  #else
      FSP_PARAMETER_NOT_USED(p_ctrl);
  #endif
  • 从第15行可以知道,参数p_ctrl仅仅是用来表示状态(模块是否已经被打开)。
  • 从第17行可以知道,引脚的配置的重点在于构造p_cfg参数。

要初始化GPIO,步骤如下:

① 定义一个ioport_pin_cfg_t结构体数组,每个数组项里指定引脚、引脚配置值
② 定义ioport_cfg_t结构体,引用步骤①的数组,并指明数组大小;
③ 调用R_IOPORT_Open

使用RASC时,这几个步骤都是自动生成的。R_IOPORT_Open函数的调用流程如下:

  1. 关闭IO设备

关闭IO设备的函数指针是close,传入的参数是ioport_ctrl_t结构体变量:

  /** Close the API.
   * @par Implemented as
   * - @ref R_IOPORT_Close()
   *
   * @param[in]   p_ctrl  Pointer to control structure.
   **/
  fsp_err_t (* close)(ioport_ctrl_t * const p_ctrl);

这个函数指针在使用FSP生成到工程中会指向R_IOPORT_Close,代码如下:

  fsp_err_t R_IOPORT_Close (ioport_ctrl_t * const p_ctrl)
  {
    
    
      ioport_instance_ctrl_t * p_instance_ctrl = (ioport_instance_ctrl_t *) p_ctrl;
 
  #if (1 == IOPORT_CFG_PARAM_CHECKING_ENABLE)
      FSP_ASSERT(NULL != p_instance_ctrl);
      FSP_ERROR_RETURN(IOPORT_OPEN == p_instance_ctrl->open, FSP_ERR_NOT_OPEN);
  #else
      FSP_PARAMETER_NOT_USED(p_ctrl);
  #endif

      /* Set state to closed */
      p_instance_ctrl->open = IOPORT_CLOSED;

      return FSP_SUCCESS;
  }
  • 第13行:仅仅是修改p_instance_ctrl->open为IOPORT_CLOSED以记录状态,不涉及硬件操作。
  1. 配置多个引脚

在open函数里已经配置所涉及的引脚了。如果想再次配置引脚,可以使用pinsCfg或pinCfg,前者可以配置多个引脚,后者只配置一个引脚。

pinsCfg函数指针的原型如下:

 /** Configure multiple pins.
  * @par Implemented as
  * - @ref R_IOPORT_PinsCfg()
  * @param[in]  p_cfg                Pointer to pin configuration data array.
  */
 fsp_err_t (* pinsCfg)(ioport_ctrl_t * const p_ctrl, const ioport_cfg_t * p_cfg);

所用的参数跟open函数指针是一样的,不再赘述。

  1. 配置单个引脚

使用pinsCfg函数指针来配置单个引脚,原型如下:

/** Configure settings for an individual pin.
 * @par Implemented as
 * - @ref R_IOPORT_PinCfg()
 * @param[in]  pin          Pin to be read.
 * @param[in]  cfg          Configuration options for the pin.
 */
fsp_err_t (* pinCfg)(ioport_ctrl_t * const p_ctrl,

          bsp_io_port_pin_t pin,
          uint32_t cfg);

参数pin表示要配置哪个引脚,参数cfg表示配置值。示例如下:

R_IOPORT_PinCfg(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_06,
       ((uint32_t) IOPORT_CFG_DRIVE_HIGH
       | (uint32_t) IOPORT_CFG_PORT_DIRECTION_OUTPUT
       | (uint32_t) IOPORT_CFG_PORT_OUTPUT_HIGH));

5.读取IO电平

Renesas读取电平支持两种模式:读取单个引脚的电平、读取多个引脚的电平。

读取单个引脚的电平的API原型如下:

/** Read level of a pin.
 * @par Implemented as
 * - @ref R_IOPORT_PinRead()
 * @param[in]  pin                   Pin to be read.
 * @param[in]  p_pin_value           Pointer to return the pin level.
 */
fsp_err_t (* pinRead)(ioport_ctrl_t * const p_ctrl,
                      bsp_io_port_pin_t pin,
                      bsp_io_level_t * p_pin_value);
  • 第1个参数p_ctrl,跟前面的函数类似,只是用来表示是否打开了模块;
  • 第2个参数pin,被用来表示“读取哪个引脚”;
  • 第3个参数p_pin_value,是输出参数,被用来保存读取到电平值。

这个函数指针默认指向库函数:

fsp_err_t R_IOPORT_PinRead (ioport_ctrl_t * const p_ctrl,
                            bsp_io_port_pin_t pin,
                            bsp_io_level_t * p_pin_value);

示例代码如下:

01 bsp_io_level_t level = BSP_IO_LEVEL_LOW;
02 R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_06, &level);
  1. 读取多个IO的电平

还可以读取多个IO的电平,函数原型如下:

/** Read states of pins on the specified port.
 * @par Implemented as
 * - @ref R_IOPORT_PortRead()
 * @param[in]  port         Port to be read.
 * @param[in]  p_port_value     Pointer to return the port value.
 */
fsp_err_t (* portRead)(ioport_ctrl_t * const p_ctrl,
                       bsp_io_port_t port,
                       ioport_size_t * p_port_value);
  • 第2个参数port指“哪一组GPIO”,比如P1、P2等。
  • 第3个参数p_port_value是一个输出参数,被用来保存“这组GPIO”的多个引脚的状态。

这个函数指针默认指向库函数:

 fsp_err_t R_IOPORT_PortRead (ioport_ctrl_t * const p_ctrl,
             				  bsp_io_port_t port,
                              ioport_size_t * p_port_value);

示例代码如下:

 ioport_size_t portr_01_values;
 R_IOPORT_PortRead(&g_ioport_ctrl, BSP_IO_PORT_01, &portr_01_values);
  1. 控制IO电平

怎么控制GPIO引脚的输出电平?函数原型如下:

 /** Write specified level to a pin.
  * @par Implemented as
  * - @ref R_IOPORT_PinWrite()
  * @param[in]  pin               Pin to be written to.
  * @param[in]  level             State to be written to the pin.
  */
 fsp_err_t (* pinWrite)(ioport_ctrl_t * const p_ctrl,
              	      bsp_io_port_pin_t pin,
                        bsp_io_level_t level);

这个函数指针默认指向库函数:

 fsp_err_t R_IOPORT_PinWrite (ioport_ctrl_t * const p_ctrl,
                				bsp_io_port_pin_t pin,
                				bsp_io_level_t level)

示例代码如下:

 bsp_io_level_t level = BSP_IO_LEVEL_LOW;
 R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_06, level);

8.控制多个IO的电平

还可使用一个函数设置多个GPIO引脚的电平,函数原型如下:

 /** Write to multiple pins on a port.
  * @par Implemented as
  * - @ref R_IOPORT_PortWrite()
  * @param[in]  port                 Port to be written to.
  * @param[in]  value                Value to be written to the port.
  * @param[in]  mask         		   Mask controlling which pins on the port are written to.
  */
 fsp_err_t (* portWrite)(ioport_ctrl_t * const p_ctrl,
             			   bsp_io_port_t port,
            			   ioport_size_t value,
             			   ioport_size_t mask);
  • 第3个参数value里,每一位都对应一个GPIO引脚的输出值,并非每一位都被用到,这由mask参数来确定。
  • 第4个参数mask,某位为1,就表示要设置这一位。

这个函数指针默认指向库函数:

 fsp_err_t R_IOPORT_PortWrite (ioport_ctrl_t * const p_ctrl,
                bsp_io_port_t port,
                ioport_size_t value,
                ioport_size_t mask)

假设要控制P0这组IO的P1_01为高,P1_03为低,P1_05为高,示例代码如下:

 ioport_size_t value = (1<<1) | (0<<3) | (1<<5);
 ioport_size_t mask = (1<<1) | (1<<3) | (1<<5);

 R_IOPORT_PortWrite(&g_ioport_ctrl, BSP_IO_PORT_00, value, mask);

5.3 LED实验

本实验的源码是“0501_led”,它让LED1循环闪烁。

5.3.1 配置引脚

参考《5.2.1 使用RASC配置》进行配置。

5.3.2 应用程序

在0501_led\src\hal_entry.c文件中的hal_entry()函数里添加LED的控制代码。可以使用面向对象的方式,编写如下代码:

   /* TODO: add your own code here */
   bsp_io_level_t level = BSP_IO_LEVEL_LOW;
   
   while (1)
   {
    
    
     // 让P400引脚输出level电平
     g_ioport.p_api->pinWrite(g_ioport.p_ctrl, BSP_IO_PORT_04_PIN_00, level);
     // 延时1秒
     R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
     // 电平反转
     level = !level;
   }

也可使用比较直观的方法,直接调用函数:

   /* TODO: add your own code here */
   bsp_io_level_t level = BSP_IO_LEVEL_LOW;
   
   while (1)
   {
    
    
     // 让P400引脚输出level电平
     R_IOPORT_PinWrite((g_ioport.p_ctrl, BSP_IO_PORT_04_PIN_00, level);
     // 延时1秒
     R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
     // 电平反转
     level = !level;
   }

5.3.3 上机实验

实际上的现象是LED在快速的闪烁,这是因为我们的程序中设计的时间间隔只有100ms,间隔很短,闪烁太快,不好观察,读者可以将间隔时间拉长一点。

5.4 按键实验

本实验的源码是“0502_key”,它的功能是:按下K2按键,就点亮LED;松开则熄灭。

5.4.1 配置引脚

本实验源码是在“0501_led”的基础上增加输入引脚:K2按键的引脚是P000。

在RASC配置界面点击“Pins”页面,找到P000引脚,先把它的Mode选择为“Input mode”;然后就可以点击“Generate Project Content”生成代码了。如下图所示:

5.4.2 源码分析

使用FSP配置引脚生成工程内容后,在0502_key\src_gen\pin_data.c文件中生成了ioport_pin_cfg_t结构体数组,代码如下:

  const ioport_pin_cfg_t g_bsp_pin_cfg_data[] = {
    
    
      {
    
    .pin = BSP_IO_PORT_00_PIN_00,
       .pin_cfg = ((uint32_t) IOPORT_CFG_PORT_DIRECTION_INPUT)
      },
      ......(省略内容)
  };

可以看到比起“0501_led”工程,新增了一个.pin是BSP_IO_PORT_00_PIN_00,它被配置为输入模式。

5.4.3 应用程序

在0502_key\src\hal_entry.c文件中的hal_entry()函数里添加如下代码:

  /* TODO: add your own code here */
  bsp_io_level_t level;
  while(1)
  {
    
    
      /* 读按键状态 */
      g_ioport.p_api->pinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_00, &level);
      /* 根据按键状态设置LED */
      g_ioport.p_api->pinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, level);
  }

5.4.4 上机实验

按下K2后LED被点亮,松开K2后LED熄灭。


本章完

猜你喜欢

转载自blog.csdn.net/qq_35181236/article/details/132780257