学习内容:
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_10
是 1024
),然后你再对 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
)的指针,这意味着函数返回一个内存地址,该地址指向一个整数值)
,该函数接受两个整数参数 x
和 y
,将它们的和存储在全局变量 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*/
参考:
c语言中typedef的用法_typedef int bool-CSDN博客