学习STM32第三天---自己写库函数

学习内容:

1、寄存器结构体的定义,typedef函数指针 ;

2、set和reset库函数的定义,头文件的编写;

3、初始化结构体的编写,增强代码的可读性;

4、使用初始化结构体,编写初始化函数;

5、新建库函数,可板间移动的程序。

----->>构建GPIO库函数,用库函数点灯;

学习笔记:

寄存器结构体的定义:

1、构建库函数的时候,重点要学习函数指针,其中((GPIO_TypeDef *)GPIOH_BASE) 表示将GPIOH_BASE强转换为指针类型的结构体,#define用GPIOH代表上述,
GPIOH就表示 以 GPIOH_BASE为基地址、类型为GPIO_TypeDef结构体的一个指针,可以用GPIOH->ODR等方式访问,(也可以(*GPIOH).ODR,但是一般不这么用)

typedef struct
{
...
}GPIO_TypeDef;

#define GPIOH					((GPIO_TypeDef *)GPIOH_BASE) 

2、后面想实现多彩流水灯的时候,发现对于IO口的模式还是不熟悉IO口的输出配置,点亮PH11和PH12的时候还是会忘记配置IO成为输出模式。要注意的是,复位之后默认是低电平,所以开启之后,三个灯都是默认打开的

	/*打开时钟*/
	RCC_AHB1ENR |=(1<<7);
	
	/*======================================PH10输出模式=================================*/
	/*先清空,防止有11干扰,配置为输出*/
	GPIOH->MODER &=~(3<<(2*10));
	GPIOH->MODER |=(1<<(2*10));
	/*======================================PH11输出模式=================================*/
	GPIOH->MODER &=~(3<<(2*11));
	GPIOH->MODER |=(1<<(2*11));
	
	/*======================================PH12输出模式=================================*/
	GPIOH->MODER &=~(3<<(2*12));
	GPIOH->MODER |=(1<<(2*12));	

复位和置位函数的编写:

随着头文件的增加,需要把不同功能的头函数拿出去,在这一小节要学习的是在多头函数的情况下避免重定义

#ifudef _STM32F4xx_H
#define _STM32F4xx_H

/*
 *这里是.h文件内部的定义
 */

#endif /*_STM32F4xx_H*/

以及编写程序的美观性

/*
 *函数功能:置位引脚
 *参数说明:GPIOx:要控制的GPIO口
 *		   Pinx:某一位引脚
 */
void GPIO_SetBits(GPIO_Typdef *GPIOx, unsigned int Pinx)
{
    GPIOx->BSRRL |= (1<<Pinx);
}

 初始化结构体的定义:

同时为了增加可读性,定义引脚如下:

/*定义引脚操作*/
#define GPIO_Pin_10   ((uint16_t)(1<<10))

/*原函数修改为*/
GPIO_SetBits(GPIOH,GPIO_Pin_10);

 也需要修改函数代码,我在这里犯了错误:这里,Pinx 是一个宏定义的位掩码值(例如 GPIO_Pin_101024),然后你再对 Pinx 进行位移操作 (1 << Pinx)这就相当于 (1 << 1024),超出了正常的位移范围,导致不正确的结果。

/*正确的*/
void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t Pinx)
{
	GPIOx->BSRRL |=Pinx;
}

/*错误的*/
void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t Pinx)
{
    GPIOx->BSRRL |= (1 << Pinx); 
    // 这里的 Pinx 是 1024(如果传入的是 GPIO_Pin_10)
}

初始化函数:

按照需要实现的寄存器定义,定义其他功能如下:

1、引脚号2、输入输出模式3、上拉下拉4、推挽or开漏5、输出速度

首先需要思考,应该传入怎么样的参数:1、GPIO端口的所有需要用到的寄存器(定义在GPIO_TypeDef)2、初始化结构体的枚举定义(定义在GPIO_InitDef中)他们本质上是两个地址

其次是写代码的时候,为什么需要用一个pinpos来找到当前引脚,为什么不可以直接?我的理解是方便位移操作,GPIO_Pin_10是一个(1<<10)直接操作的话是1024;

再者在写代码的时候,应该多从寄存器的角度来思考问题,例如:

GPIOx->MODER      &=~(3<<(2*pinpos));
GPIOx->MODER      |=(GPIO_InitDef_struct->GPIO_MODER<<(2*pinpos));

这句先把GPIOx->MODER寄存器清零,然后用结构体GPIO_InitDef_struct->GPIO_MODER传递参数,要把底层的结构体弄清楚。

 最后是一些模范化、严谨性的问题:只有在输出模式下才能配置输出速度、在.c文件中写的函数需要在.h文件中申明一下等……(还需要积累经验)

/*
 *函数功能:用参数初始化LED
 *参数说明:GPIO_TypeDef *GPIOx:用GPIOx调用GPIO_TypeDef类型的寄存器
 * 		   GPIO_InitDef *GPIO_InitDef_struct:调用GPIO_InitDef类型的输出模式
 */
void LED_Intit(GPIO_TypeDef *GPIOx, GPIO_InitDef *GPIO_InitDef_struct)
{
	uint16_t pinpos					 = 0x00; 
	uint16_t pose	   				 = 0x00; 
	uint32_t Current_Pin = 0;
	
	/*找到对应的引脚号码pinpos*/
	for(pinpos=0x00;pinpos<0x0F;pinpos++)
	{
		/*~(3<<(2*pinpos));为什么不直接写成~(3<<(2*(GPIO_InitStruct->GPIO_Pin));*/
		Current_Pin = GPIO_InitDef_struct->GPIO_Pin;
        //Current_Pin = (1<<pinpos) & GPIO_InitDef_struct->GPIO_Pin; /*更加严谨*/
		pose 				= (uint16_t)(1<<pinpos); 
		/*找到引脚之后*/
		if(pose==Current_Pin)
		{
			GPIOx->MODER 	 &=~(3<<(2*pinpos));
			GPIOx->MODER 	 |=(GPIO_InitDef_struct->GPIO_MODER<<(2*pinpos));
			
			GPIOx->OTYPER  &=~(1<<pinpos);
			GPIOx->OTYPER  |=(GPIO_InitDef_struct->GPIO_OTYPER<<pinpos);
			
			/*为了严谨,配置输出速度的时候先判断是不是输出*/
			if(GPIO_InitDef_struct->GPIO_MODER == MODER_OUT || GPIO_InitDef_struct->GPIO_MODER == MODER_Multi)
			{
			GPIOx->OSPEEDR &=~(3<<(2*pinpos));
			GPIOx->OSPEEDR |=(GPIO_InitDef_struct->GPIO_OSPEEDR<<(2*pinpos));
			}
			
			GPIOx->PUPDR   &=~(3<<(2*pinpos));
			GPIOx->PUPDR   |=(GPIO_InitDef_struct->GPIO_PUPDR<<(2*pinpos));
		}
	}
}
	
/*
 *函数功能:LED初始化GPIO端口的参数
 *参数说明:见GPIO_InitDef结构体申明
 */
void LED_Intit_Config(uint16_t Pinx)
{
	GPIO_InitDef GPIO_InitDef_struct;
	
	GPIO_InitDef_struct.GPIO_Pin     = Pinx;
	GPIO_InitDef_struct.GPIO_MODER   = MODER_OUT;
    GPIO_InitDef_struct.GPIO_OTYPER  = OTYPER_PP; 
	GPIO_InitDef_struct.GPIO_OSPEEDR = SPEED_2MHz;
	GPIO_InitDef_struct.GPIO_PUPDR   = UP;
	
	LED_Intit(GPIOH, &GPIO_InitDef_struct); /*注意这里应该给一个地址,参考#define GPIOH	((GPIO_TypeDef *)GPIOH_BASE) */
}

最终的main函数为:

int main()
{
	/*打开时钟*/  
	RCC_AHB1ENR |=(1<<7);
	
	/*初始化*/
  LED_Intit_Config(GPIO_Pin_10);
  LED_Intit_Config(GPIO_Pin_11);
	LED_Intit_Config(GPIO_Pin_12);
	
	
	/*要注意的是,复位之后默认是低电平,所以开启之后,三个灯都是默认打开的*/
	GPIO_SetBits(GPIOH,GPIO_Pin_10);
	GPIO_SetBits(GPIOH,GPIO_Pin_11);
	GPIO_SetBits(GPIOH,GPIO_Pin_12);	

  while(1)
	{

		GPIO_ResetBits(GPIOH,GPIO_Pin_10);	
		LED_delay(0xEFFFF);
		GPIO_SetBits(GPIOH,GPIO_Pin_10);
		LED_delay(0xEFFFF);
		
		GPIO_ResetBits(GPIOH,GPIO_Pin_11);	
		LED_delay(0xEFFFF);
		GPIO_SetBits(GPIOH,GPIO_Pin_11);
		LED_delay(0xEFFFF);
		
		GPIO_ResetBits(GPIOH,GPIO_Pin_12);	
		LED_delay(0xEFFFF);
		GPIO_SetBits(GPIOH,GPIO_Pin_12);
		LED_delay(0xEFFFF);
	}	
			
}
void SystemInit(void)
{
	
}

遇到问题:        

1、什么是指针函数?什么是函数指针?

指针函数函数指针
返回指向类型的指针定一个指向函数地址的指针
//指针函数 括号优先级高,f(int x,int y)作为一个函数,返回一个指针
int* add(int x,int y){
    z=x+y;
    return &z;
}

定义一个返回 int* 的函数 add(指一个函数在执行完毕后返回一个指向整数(int)的指针,这意味着函数返回一个内存地址,该地址指向一个整数值,该函数接受两个整数参数 xy,将它们的和存储在全局变量 z 中,并返回 z 的地址。

int main()
{
    f=&add; //f指向add函数
    printf("1+2=%d \n",*(*f)(1,2)); //(*f)取出指向的add函数
}

调用 f 指向的函数(此时是 add),由于add函数返回一个地址,还需要解引用获得返回的指针所指向的值,即 z 的值。所以下面这个也可以:

int main()
{
    f=*(&add); //f指向add函数
    printf("3+2=%d \n",*f(3,2)); //(*f)取出指向的add函数
}

对与函数指针,f 指向的函数(此时是 add),(*f)就调出来add这个函数。

int add(int x,int y){
    return x+y;
}

// 函数指针 (*f)优先级高,f是一个指针,指向一个函数(int x,int y)
int (*f)(int x,int y);
int main()
{
    f=&add; //f指向add函数
    printf("1+2=%d \n",(*f)(1,2)); //(*f)取出指向的add函数
}

2、定义初始化结构体时候的枚举?

用于取代#define的工作,注意枚举里面的数据要用逗号“,”隔开,否则报错。

而且枚举的定义要在初始化之前,例如:

/*通过enum枚举 来限定初始化状态,保证类型安全*/
typedef enum
{
	MODER_IN      =  0x00,
	MODER_OUT     =  0x01,
	MODER_Multi   =  0x02,
	MODER_Digital =  0x03,
}GPIO_MODERDef;

/*用于初始化类型*/
typedef struct
{
	GPIO_MODERDef   GPIO_MODER;
}GPIO_InitDef;

新建库函数的注意事项:

需要在“魔术棒”的c/c++中配置 Include Paths

为了防止重复定义,需要在.h的文件中写,我的文件名是bsp.h 

#ifndef _BSP_H
#define _BSP_H

#endif /*_BSP_H*/

 

参考:

指针函数与函数指针_哔哩哔哩_bilibili

c语言中typedef的用法_typedef int bool-CSDN博客

C语言中typedef和指针连用实用讲解 - 极客子羽 - 博客园 (cnblogs.com)

C语言--enum,typedef enum 枚举类型详解 - 无天666 - 博客园 (cnblogs.co)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值