在上一章讲解到,通过在头文件里设置寄存器组的基地址、寄存器结构体、重定义指针,以此来进行在函数调用中的通过结构体指针访问操作寄存。在上一章中以此方式操作了CLK和GPIO部分结构体成员,在此章中,会对更普遍化的GPIO进行结构体指针操作,并以此模块化代码对BEEP进行初始化并实现其按键功能。
GPIO通用初始化方法
在(4)中已经提到——
1.GPIO的外设寄存器组基地址为:
#define GPIO1_BASE (0x0209C000)
#define GPIO2_BASE (0x020A0000)
#define GPIO3_BASE (0x020A4000)
#define GPIO4_BASE (0x020A8000)
#define GPIO5_BASE (0x020AC000)
2.GPIO的重定义指针为:
#define GPIO1 ((GPIO_Type *)GPIO1_BASE)
#define GPIO2 ((GPIO_Type *)GPIO2_BASE)
#define GPIO3 ((GPIO_Type *)GPIO3_BASE)
#define GPIO4 ((GPIO_Type *)GPIO4_BASE)
#define GPIO5 ((GPIO_Type *)GPIO5_BASE)
3.GPIO_Type(GPIO的寄存器布局定义)
/** GPIO - Register Layout Typedef */
typedef struct {
__IO uint32_t DR; /**< GPIO data register, offset: 0x0 */
__IO uint32_t GDIR; /**< GPIO direction register, offset: 0x4 */
__I uint32_t PSR; /**< GPIO pad status register, offset: 0x8 */
__IO uint32_t ICR1; /**< GPIO interrupt configuration register1, offset: 0xC */
__IO uint32_t ICR2; /**< GPIO interrupt configuration register2, offset: 0x10 */
__IO uint32_t IMR; /**< GPIO interrupt mask register, offset: 0x14 */
__IO uint32_t ISR; /**< GPIO interrupt status register, offset: 0x18 */
__IO uint32_t EDGE_SEL; /**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;
以上,对于当前仅考虑输入/输出的GPIO而言,则仅需考虑GPIO方向(GDIR)和输出电平(DR)。
为此,需要设计一个enum类型来确定输入/输出方向,再设计一个结构体类型来保存方向和输出电平。
/*enum枚举类型 设置IO输入/输出方向*/
typedef enum _gpio_pin_direction
{
kGPIO_Digitalinput = 0U, //INPUT输入
kGPIO_Digitaloutput = 1U, //OUTPUT输出
}gpio_pin_direction_t;
/*GPIO结构体 IO输入/输出+输出电平 */
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; //enum类型 GPIO方向
uint8_t outputLogic;//输出电平
}gpio_pin_config_t;
以上准备工作完成以后,思考:GPIO的init需要哪些参数——
- 首先需要GPIO的基地址(*base),即确定是哪个GPIOx(GPIO1~GPIO5);
- 然后确定是哪个管脚(pin);
- 最后通过config_t结构体确定GPIO输入输出方向(gpio_pin_config_t->direction),以及如果是输出,则输出电平(gpio_pin_config_t->outputLogic);
/*通用gpio初始化:设置管脚+输入输出方向+输出电平*/
void gpio_init(GPIO_Type *base,int pin,gpio_pin_config_t *config)
{
if(config->direction == kGPIO_Digitalinput) /*GPIO为输入模式*/
{
base->GDIR &= ~(1<<pin); //GDIR清零,输入
}
else /*GPIO为输出模式*/
{
base->GDIR |= (1<<pin); //GDIR置1,输出
/*设置默认输出电平*/
gpio_pinwrite(base,pin,config->outputLogic);
}
}
与此相对应的,有写GPIO和读取GPIO的位移函数。
/*写入GPIO高/低电平*/
void gpio_pinwrite(GPIO_Type *base,int pin,int value)
{
if(value == 0) /*写入0*/
{
base->DR &= ~(1<<pin);
}
else /*写入1*/
{
base->DR |= (1<<pin);
}
}
/*读取GPIO高/低电平*/
int gpio_pinread(GPIO_Type *base,int pin)
{
//将读取的dr右移pin位,移动到bit0上,只需要bit0的值
return (((base->DR) >> pin )& 0x1);
}
以上,实现了GPIO的通用初始化模板、写入高低电平、读取GPIO电平这三个基本的操作函数。了解其基本原理即可,工作中还是直接调用以下两个函数更为常用。
IOMUXC_SetPinMux();
IOMUXC_SetPinConfig();
所以其实自己写的目的只是为了更熟悉底层原理,知道自己要干嘛:找GPIO+管脚+方向;其实工作中需要更多的配置。
如下为NXP的官方SDK提供的Mux和Config;但这也不影响我们对于自己写初始化的成就感的磨灭。熟悉底层原理后再用好东西,不亏。至少以后自己看着这个自己写的底层后,也能够有更通透的感觉。
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t inputOnfield)
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t configValue)
BEEP初始化及其使用
查询datasheet以及.sch原理图得知。
原理图中通过一个 PNP 型的 8550三极管来驱动蜂鸣器,通过 SNVS_TAMPER1 这个 IO来控制三极管 Q1 的导通。
- 当 SNVS_TAMPER1 输出低电平的时候 Q1 导通,相当于蜂鸣器的正极连接到 DCDC_3V3,蜂鸣器形成一个通路,因此蜂鸣器会鸣叫。
- 当 SNVS_TAMPER1输出高电平的时候 Q1 不导通,那么蜂鸣器就没有形成一个通路,因此蜂鸣器也就不会鸣叫。
- 简而言之,低电平-导通-鸣叫,高电平-断开-不鸣叫。
BEEP是和SNVS_TAMPER1复用管脚。
在fsl_iomuxc.h中可以查询到,SNVS_TAMPER1是和GPIO5_IO01进行管脚复用,故设置GPIO5_IO01输出低电平,则蜂鸣器响动;输出高电平则无反应。
根据上文部分的GPIO的通用初始化函数进行如下设置:
/*BEEP_init*/
void beep_init(void)
{
/*gpio复用*/
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0);
/*gpio电气属性设置*/
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0X10B0);
/*gpio初始化*/
GPIO5->GDIR |=(1<<1); /*bit1设置为1,设置为输出*/
GPIO5->DR &=~(1<<1);/*默认关闭*/
}
void beep_switch(int status)
{
if(status == OFF)
{
/*蜂鸣器关 则输出高电平 DR=1*/
GPIO5->DR |= (1<<1);
}
else if(status == ON)
{
/*蜂鸣器开 则输出低电平 DR=0*/
GPIO5->DR &= ~(1<<1);
}
}
最后主函数里简单调用即可。
int main(void)
{
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
beep_init();
while(1) /* 死循环 */
{
led_on(); /* 关闭LED */
beep_switch(ON);
delay(1500); /* 延时1500ms */
led_off(); /* 打开LED */
beep_switch(OFF);
delay(1500); /* 延时1500ms */
}
return 0;
并且此时start.s里面写得很简单,就是进入SVC模式,设置栈顶指针就直接从汇编跳转到C语言。之后进入了中断板块,那才是真的学到离谱,不管怎样,记录,加油!23.1.16
.global _start /* 全局标号 */
/*
* 描述: _start函数,程序从此函数开始执行,此函数主要功能是设置C
* 运行环境。
*/
_start:
/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */
/*设置SP指针*/
ldr sp,=0X80200000 /* 设置栈指针 */
b main /* 跳转到main函数 */