文章目录
- 一、IOPORT简介
- 二、IOPORT的框图分析
- 三、IOPORT的寄存器描述
- 1. 端口引脚功能选择寄存器
- 2. 端口输出数据寄存器
- 3. 端口输入数据寄存器
- 4. 端口输出置位/复位寄存器
- 5. 写保护寄存器
- 四、点亮LED灯(寄存器)
从本文开始,我将以瑞萨RA系列 野火_启明6M5开发板(R7FA6M5BH3CFC)为例,与大家一起分享学习的乐趣
一、IOPORT简介
芯片的引脚可以被粗略地分为 IO 引脚和非 IO 引脚。 非 IO 引脚就是电源引脚、晶振引脚等的那些引脚,他们不具备 GPIO(通用输入输出)功能。 而 IO 引脚是那些具备 GPIO 功能的引脚,他们可以配置为各种模式、实现各种通用功能。
IO 引脚最基本的输出功能是输出高、低电平,实现开关控制(比如开关LED灯、继电器或三极管等); 最基本的输入功能是检测外部输入电平(比如通过引脚电平的高低区分按键是否被按下)。 IO 引脚还可以用来连接外部设备,与外部设备进行通讯,发送控制指令,采集传感器数据等等。
IOPORT 即 I/O Port,在代码里面为了方便而写成 “IOPORT”,表示输入输出端口。 IOPORT 是 RA MCU 的一个外设模块,它用来控制芯片的引脚,对每一个引脚进行详细的配置。 具体地说,IOPORT 可以对引脚进行以下几个方面的配置:
-
配置引脚为普通 IO 功能,即输入或输出高电平或者低电平。
-
控制引脚的输入上拉电阻。
-
控制引脚的驱动能力。
-
控制引脚是否检测上升沿/下降沿/双边沿。
-
控制引脚是否作为中断输入引脚。
-
配置引脚为模拟输入功能或者将引脚在内部连接到其他外设模块。
瑞萨 RA 系列芯片的 IO 端口从理论上被分成16个组(0~9, A, B, C, D, E, F),每组有16个引脚(0~15)。 然而,实际情况并非是完整的“16个组+每组16个引脚”的配置,而是因实际的芯片型号而异。 以野火启明6M5板子上的 R7FA6M5BH3CFC 芯片为例,它是 LQFP 176-pin 封装,一共有 176 个引脚, 其中的大部分引脚都是可以被 IOPORT 模块控制的 IO 引脚。 而这些 IO 引脚被分成了 0~9, A, B 共11组引脚(注:A, B 是 10, 11 的十六进制表示), 每组一般有 16 个引脚(引脚号为0~15),实际上有些组不到16个引脚。 对于引脚数比较少的封装(LQFP 144-pin 和 LQFP 100-pin 封装)来说,芯片的可用 IO 引脚数也会相对减少。
二、IOPORT的框图分析
RA6M5、RA4M2、RA2L1 这三者的 IOPORT 模块框图结构是基本一致的。
以 RA6M5 为例,下图是 RA6M5 的外设 IOPORT 的功能结构框图,标有字母 A 处表示的是芯片实际引出的 IO 引脚。
接下来我们对 IOPORT 外设的结构框图进行分析,将不难得出 IOPORT 有如下几种工作模式:
-
通用输入输出(GPIO)模式
输入模式(浮空/上拉)
输出模式(推挽/开漏) -
模拟输入功能模式
-
复用功能模式
1. IO端口方向
见图中标注 ① 处。
PDR (Port Direction Register) 是端口方向寄存器,它控制端口的 GPIO 方向。 当 IO 引脚需要控制输出高电平或者低电平时,可设置引脚的 GPIO 方向为 GPIO 输出; 而当需要读取 IO 引脚的电平时,可设置引脚的 GPIO 方向为 GPIO 输入。
2. IO输入上拉控制
见图中标注 ② 处。
PCR (Pull-up Control Register) 是上拉控制寄存器,它控制 IO 引脚的 GPIO 输入是否使能上拉。 当设置为允许上拉时,实际上会使得图中字母 B 处的弱上拉电阻连接到VCC电源正极,从而使得引脚处于弱上拉输入模式。 需要注意的是,当引脚的 GPIO 方向被配置为“输入”时,才可以设置使用弱上拉电阻。 从上图还可以看出,RA6M5 的 IO 端口是没有下拉电阻的。
3. IO驱动能力和开漏输出控制
见图中标注 ③ 处。
DSCR (Port Drive Capability Register) 是端口驱动能力寄存器,它控制 IO 引脚的驱动能力。 驱动能力指的是IO驱动的电流强度和IO的最大翻转速率。
NCODR (N-Channel Open-Drain Control Register) 是开漏输出控制寄存器,它控制 IO 引脚是否使能开漏模式。 当引脚的 GPIO 方向被配置为“输出”时,可以配置 IO 引脚输出的模式是推挽输出还是使能开漏输出。
4. IO端口输出数据
见图中标注 ④ 处。
这部分看似比较复杂,但是实际上 EOSR、POSR、PORR、EORR 的箭头最终都指向 PODR, 这意味着操作 EOSR、POSR、PORR、EORR 这些寄存器,实际上将最终操作的是 PODR 寄存器。
-
图中 PODR (Port Output Data Register) 是端口输出数据寄存器,它控制 GPIO 引脚输出的电平。 当引脚的 GPIO 方向被配置为“输出”时,可以配置引脚输出高电平或者低电平。
-
图中 EOSR (Event Output Set Register) 是事件输出置位寄存器(该寄存器我们暂且忽略它)。
-
图中 POSR (Pmn Output Set Register) 是端口输出数据寄存器,它控制 GPIO 引脚输出为高电平,但却不能控制输出低电平。
-
图中 PORR (Pmn Output Reset Register) 是端口输出数据寄存器,它控制 GPIO 引脚输出为低电平,但却不能控制输出高电平。
-
图中 EORR (Event Output Reset Register) 是事件输出复位寄存器(该寄存器我们暂且忽略它)。
5. IO端口输入数据
见图中标注 ⑤ 处。
PIDR (Port Input Data Register) 是端口输入数据寄存器,可以通过它读取 GPIO 引脚的电平状态。 当引脚的 GPIO 方向被配置为“输入”时,程序可以读出输入引脚的电平是高电平还是低电平。
6. 模拟输入模式
见图中标注 ⑥ 处。
ASEL (Analog Input Enable) 是模拟输入选择控制位,可以通过它来将引脚配置为模拟输入模式。 当使用 ADC 功能时,需将引脚配置为模拟输入模式。
7. 端口模式控制和外设复用选择
见图中标注 ⑦ 处。
PMR (Port Mode Control) 是端口模式控制位,可以通过它来将引脚配置为 GPIO 输入/输出模式,或者配置作为复用外设功能引脚。
PSEL (Peripheral Select) 是外设复用选择,可以通过它来选择将引脚连接到某一个外设功能上。
8. IO边沿检测与中断
见图中标注 ⑧ 处。
EOFR (Event on Falling/Event on Rising) 是事件触发选择,可以通过它来让引脚检测边沿信号,如果检测到指定信号将触发一个事件。
ISEL (IRQ Input Enable) 是IRQ输入使能控制位,可以配置是否产生中断。
三、IOPORT的寄存器描述
IOPORT 模块由于功能相对简单,寄存器相对来说比较少,下面我们就来看看它的这几个寄存器。
1. 端口引脚功能选择寄存器
我们在前面说过,瑞萨 RA 系列芯片的 IO 端口从理论上被分成16个组、每组有16个引脚(实际上可能会更少), 而下图中所描述的端口引脚功能选择寄存器(PmnPFS)便是用来配置 IO 引脚的,一个这样的寄存器对应配置一个 IO 引脚。
注:上图中的 “Pmn”,“m = 0 to 9, A, B”表示的是 IO 端口 m 的范围,而“n = 00 to 15”表示的是 IO 引脚 n 的范围。
PmnPFS 寄存器的位域说明如下:
2. 端口输出数据寄存器
上图所示为端口输出数据寄存器的相关描述。该寄存器的位域说明如下:
3. 端口输入数据寄存器
上图所示为端口输入数据寄存器的相关描述。该寄存器的位域说明如下:
4. 端口输出置位/复位寄存器
上图所示为端口输出置位/复位寄存器的相关描述。该寄存器的位域说明如下:
5. 写保护寄存器
当我们要配置RA单片机的 I/O 引脚时,我们主要需要配置的是 PmnPFS 寄存器, 但是所有的 PmnPFS 寄存器默认是被保护的,因此要想写入引脚 Pmn 对应的 PmnPFS 寄存器,需要先通过写保护寄存器解除写保护。
上图所示为写保护寄存器的相关描述。该寄存器的位域说明如下:
四、点亮LED灯(寄存器)
1. 硬件设计
野火启明6M5开发板的 LED 电路图如图所示。图中 RA6M5 芯片的 P400、P403、P404 引脚分别通过一个 2.2 KΩ 的限流电阻连接到 LED1、LED2、LED3 这三个用户 LED 灯的阴极,LED 灯的阳极连接到 3.3V 电源。 而 LED4 是电源指示灯,只要开发板通电就会亮。
2. 软件设计
拷贝一份之前新建的 Keil 工程模板 “06_Template”, 然后将工程文件夹重命名为 “08_Register_LED”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
(1)寄存器定义头文件
当新建工程完成之后,工程里已经自动包含了这个定义寄存器的头文件,比如:R7FA6M5BH.h 头文件。 在这个头文件里面,已经包含了芯片所有的寄存器定义,包括 IOPORT 外设的寄存器。
以启明6M5开发板的 RA6M5 工程为例, 我们在这里列出 IOPORT 部分寄存器定义(它们存在于寄存器定义头文件 R7FA6M5BH.h 中)。
R7FA6M5BH.h 文件中的 IOPORT 部分寄存器定义
/**
* @brief I/O Ports (R_PORT0)
*/
typedef struct { /*!< (@ 0x40080000) R_PORT0 Structure */
union {
union {
__IOM uint32_t PCNTR1; /*!< (@ 0x00000000) Port Control Register 1 */
struct {
__IOM uint32_t PDR : 16; /*!< [15..0] Pmn Direction */
__IOM uint32_t PODR : 16; /*!< [31..16] Pmn Output Data */
} PCNTR1_b;
} ;
struct {
union {
__IOM uint16_t PODR; /*!< (@ 0x00000000) Output data register */
/* ... 代码过长省略 ... */
} ;
union {
__IOM uint16_t PDR; /*!< (@ 0x00000002) Data direction register */
/* ... 代码过长省略 ... */
} ;
};
};
union {
union {
__IM uint32_t PCNTR2; /*!< (@ 0x00000004) Port Control Register 2 */
struct {
__IM uint32_t PIDR : 16; /*!< [15..0] Pmn Input Data */
__IM uint32_t EIDR : 16; /*!< [31..16] Pmn Event Input Data */
} PCNTR2_b;
} ;
struct {
union {
__IM uint16_t EIDR; /*!< (@ 0x00000004) Event input data register */
/* ... 代码过长省略 ... */
} ;
union {
__IM uint16_t PIDR; /*!< (@ 0x00000006) Input data register */
/* ... 代码过长省略 ... */
} ;
};
};
union {
union {
__OM uint32_t PCNTR3; /*!< (@ 0x00000008) Port Control Register 3 */
struct {
__OM uint32_t POSR : 16; /*!< [15..0] Pmn Output Set */
__OM uint32_t PORR : 16; /*!< [31..16] Pmn Output Reset */
} PCNTR3_b;
} ;
struct {
union {
__OM uint16_t PORR; /*!< (@ 0x00000008) Output set register */
/* ... 代码过长省略 ... */
} ;
union {
__OM uint16_t POSR; /*!< (@ 0x0000000A) Output reset register */
/* ... 代码过长省略 ... */
} ;
};
};
union {
union {
__IOM uint32_t PCNTR4; /*!< (@ 0x0000000C) Port Control Register 4 */
struct {
__IOM uint32_t EOSR : 16; /*!< [15..0] Pmn Event Output Set */
__IOM uint32_t EORR : 16; /*!< [31..16] Pmn Event Output Reset */
} PCNTR4_b;
} ;
struct {
union {
__IOM uint16_t EORR; /*!< (@ 0x0000000C) Event output set register */
/* ... 代码过长省略 ... */
} ;
union {
__IOM uint16_t EOSR; /*!< (@ 0x0000000E) Event output reset register */
/* ... 代码过长省略 ... */
} ;
};
};
} R_PORT0_Type; /*!< Size = 16 (0x10) */
/**
* @brief I/O Ports-PFS (R_PFS)
*/
typedef struct { /*!< (@ 0x40080800) R_PFS Structure */
__IOM R_PFS_PORT_Type PORT[15]; /*!< (@ 0x00000000) Port [0..14] */
} R_PFS_Type; /*!< Size = 960 (0x3c0) */
/**
* @brief I/O Ports-MISC (R_PMISC)
*/
typedef struct { /*!< (@ 0x40080D00) R_PMISC Structure */
union {
__IOM uint8_t PFENET; /*!< (@ 0x00000000) Ethernet Control Register */
/* ... 代码过长省略 ... */
} ;
__IM uint8_t RESERVED[2];
union {
__IOM uint8_t PWPR; /*!< (@ 0x00000003) Write-Protect Register */
/* ... 代码过长省略 ... */
} ;
__IM uint8_t RESERVED1;
union {
__IOM uint8_t PWPRS; /*!< (@ 0x00000005) Write-Protect Register for Secure */
/* ... 代码过长省略 ... */
} ;
__IM uint16_t RESERVED2[5];
__IOM R_PMISC_PMSAR_Type PMSAR[12]; /*!< (@ 0x00000010) Port Security Attribution Register */
} R_PMISC_Type; /*!< Size = 40 (0x28) */
/** @addtogroup Device_Peripheral_peripheralAddr
* @{ 外设首地址
*/
#define R_PORT0_BASE 0x40080000UL
#define R_PORT1_BASE 0x40080020UL
#define R_PORT2_BASE 0x40080040UL
#define R_PORT3_BASE 0x40080060UL
#define R_PORT4_BASE 0x40080080UL
#define R_PORT5_BASE 0x400800A0UL
#define R_PORT6_BASE 0x400800C0UL
#define R_PORT7_BASE 0x400800E0UL
#define R_PORT8_BASE 0x40080100UL
#define R_PORT9_BASE 0x40080120UL
#define R_PORT10_BASE 0x40080140UL
#define R_PORT11_BASE 0x40080160UL
#define R_PORT12_BASE 0x40080180UL
#define R_PORT13_BASE 0x400801A0UL
#define R_PORT14_BASE 0x400801C0UL
#define R_PFS_BASE 0x40080800UL
#define R_PMISC_BASE 0x40080D00UL
/** @addtogroup Device_Peripheral_declaration
* @{ 外设寄存器声明(定义结构体指针,指向 IOPORT 寄存器首地址)
*/
#define R_PORT0 ((R_PORT0_Type*) R_PORT0_BASE)
#define R_PORT1 ((R_PORT0_Type*) R_PORT1_BASE)
#define R_PORT2 ((R_PORT0_Type*) R_PORT2_BASE)
#define R_PORT3 ((R_PORT0_Type*) R_PORT3_BASE)
#define R_PORT4 ((R_PORT0_Type*) R_PORT4_BASE)
#define R_PORT5 ((R_PORT0_Type*) R_PORT5_BASE)
#define R_PORT6 ((R_PORT0_Type*) R_PORT6_BASE)
#define R_PORT7 ((R_PORT0_Type*) R_PORT7_BASE)
#define R_PORT8 ((R_PORT0_Type*) R_PORT8_BASE)
#define R_PORT9 ((R_PORT0_Type*) R_PORT9_BASE)
#define R_PORT10 ((R_PORT0_Type*) R_PORT10_BASE)
#define R_PORT11 ((R_PORT0_Type*) R_PORT11_BASE)
#define R_PORT12 ((R_PORT0_Type*) R_PORT12_BASE)
#define R_PORT13 ((R_PORT0_Type*) R_PORT13_BASE)
#define R_PORT14 ((R_PORT0_Type*) R_PORT14_BASE)
#define R_PFS ((R_PFS_Type*) R_PFS_BASE)
#define R_PMISC ((R_PMISC_Type*) R_PMISC_BASE)
3. hal_entry入口函数
一般来说,接下来我们应该在 main 函数里编写我们的程序, 但是使用 FSP 库却不一样,在没有使用 RTOS 的情况下,它规定以名为 hal_entry 的函数作为用户应用程序的入口, 因此我们应该在 hal_entry 入口函数下编写我们的代码。
实际上,当使用 RTOS 时,程序是从 main 函数开始进行线程调度; 当没有使用 RTOS 时,C语言程序的入口函数 main 函数调用了 hal_entry 函数。 我们新建的工程是没有选用 RTOS 的,因此,用户程序是从 hal_entry 函数开始执行。 我们打开 “\src\hal_entry.c” 文件,在 hal_entry 函数里面编写我们的代码。
以启明6M5开发板为例,RA6M5 工程的 hal_entry 函数代码如下所示。
hal_entry.c文件
void hal_entry(void)
{
/* TODO: add your own code here */
/* 取消写保护 */
R_PMISC->PWPR = 0; ///< Clear BOWI bit - writing to PFSWE bit enabled
R_PMISC->PWPR = 1U << BSP_IO_PWPR_PFSWE_OFFSET; ///< Set PFSWE bit - writing to PFS register enabled
/* LED1:配置引脚 P400 对应的PFS寄存器 */
R_PFS->PORT[BSP_IO_PORT_04_PIN_00>>8].PIN[BSP_IO_PORT_04_PIN_00 & 0xFF].PmnPFS =
IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;
/* LED2:配置引脚 P403 对应的PFS寄存器 */
R_PFS->PORT[BSP_IO_PORT_04_PIN_03>>8].PIN[BSP_IO_PORT_04_PIN_03 & 0xFF].PmnPFS =
IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;
/* LED3:配置引脚 P404 对应的PFS寄存器 */
R_PFS->PORT[BSP_IO_PORT_04_PIN_04>>8].PIN[BSP_IO_PORT_04_PIN_04 & 0xFF].PmnPFS =
IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;
/** 此时3个LED灯的引脚默认输出的是低电平
* 所以3个LED灯都会默认亮起来
* 我们在 while 循环里让 LED1 闪烁:每秒钟翻转一次状态
*/
while(1)
{
/* 翻转LED灯:LED1 */
//R_PORT4->PODR |= 1<<(BSP_IO_PORT_04_PIN_00 & 0xFF);
//R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);
//R_PORT4->PODR &= (uint16_t)~(1 << (BSP_IO_PORT_04_PIN_00 & 0xFF));
//R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);
/* 或者也可以这样用位异或操作来翻转LED1 */
R_PORT4->PODR ^= 1<<(BSP_IO_PORT_04_PIN_00 & 0xFF);
R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);
}
//这后面的代码无需理会
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
4. 下载验证
编写好上述代码,然后将程序编译并下载到开发板之后,按下复位按键来复位开发板, 可以观察到开发板上面除了电源指示灯之外的3个 LED 灯当中有两个灯常亮,还有一个灯在缓慢闪烁。 闪烁着的 LED 灯为 LED1,它每秒钟(1000毫秒)便改变一次亮灭的状态。