移植MPU6050时与CRH和CRL寄存器相关的问题

本文介绍在STM32单片机中通过IIC与MPU6050进行通信时,如何正确配置引脚的输入输出模式。详细解析了CRH和CRL寄存器的使用方法,以及如何通过修改这些寄存器来设置PB12引脚的工作模式。

暑假在根据例程对单片机与MPU6050进行通信的代码进行更改引脚时遇到一些问题,后来发现是控制IO工作模式的寄存器代码没有修改正确,下面是对CRH和CRL寄存器进行更改的方法。

//IO方向设置
#define MPU_SDA_IN()  {GPIOB->CRH&=0XFFF0FFFF;GPIOB->CRH|=8<<4*4;}
#define MPU_SDA_OUT() {GPIOB->CRH&=0XFFF0FFFF;GPIOB->CRH|=3<<4*4;

这是单片机与MPU6050通过IIC进行通信时对SDA引脚进行IO输入输出设置,其中SDA引脚用的STM32的PB12。

GPIOB->CRH和GPIOB->CRL是2个控制IO模式的32位寄存器。其中CRL对应引脚的0-7引脚,CRH对应引脚的8-15引脚。所以每个引脚的模式通过四个bit位进行控制,详细的控制模式可以参考下图,这是我从STM32F10X参考手册上截的与CRH相关的图,CRL与其基本相同,只是控制的引脚不同。

GPIOB->CRH&=0XFFF0FFFF;GPIOB->CRH|=8<<4*4; 的意思是先将PB12对应的四个位置0,其他GPIO设置保持不变,得0000, 然后将8对应的二进制数1000左移16位, 得1000,就是将CRH的19到16位置为1000,通过上图可知1000配置为上拉/下拉输入模式。

GPIOB->CRH&=0XFFF0FFFF;GPIOB->CRH|=3<<4*4; 的意思是先将PB12对应的四个位置0,其他GPIO设置保持不变,得0000, 然后将3对应的二进制数0011左移16位, 得0011,就是将CRH的19到16位置为0011,通过上图可知0011配置为推挽输出模式,最大速度50MHz。

对于CRH,左移的位数为引脚对应数值减去八再乘以四,如PB12为(12-8)*4。同理,在对PB0到PB7引脚进行配置时只要将CRH改为CRL,左移位数为引脚对应数值乘以4,如PB6为6*4。

 

 

 

 

 

 

<think>我们注意到用户提供的宏定义是针对STM32F1系列(标准外设库)直接操作寄存器来配置GPIO模式的。现在需要将其转换为适用于STM32F4 HAL库的形式。 原宏定义的功能: - MPU_SDA_IN():将PB12(因为操作的是CRH寄存器的12-15位)配置为上拉输入模式(1000,即0x8- MPU_SDA_OUT():将PB12配置为通用推挽输出模式,最大速度50MHz(0011,即0x3) 在HAL库中,我们不再直接操作寄存器,而是通过调用HAL库提供的函数来配置GPIO。具体步骤: 1. 首先,我们需要在初始化配置好GPIO的初始状态(比如输出类型、速度、上下拉等)。但是注意,原代码是在运行动态切换引脚方向(输入/输出),这在软件IIC中很常见。 2. HAL库提供了`HAL_GPIO_Init`函数来配置GPIO,但每次调用都会重新配置整个GPIO端口(多个引脚)。如果我们需要动态切换一个引脚的方向,我们可以: a) 每次切换重新初始化该引脚(但会影响同一端口的其他引脚) b) 或者,使用寄存器操作(不推荐,因为HAL库旨在抽象硬件) c) 另一种方法是,在初始化将引脚配置为开漏模式并同使能上拉,这样在输出模式下设置高电平实际上会释放总线(高阻态),从而实现双向IO。但这种方法在软件IIC中常用,不需要切换方向。 然而,用户提供的原代码是在运行切换方向(输入输出)。在HAL库中,我们可以通过以下方式实现: 方案1:使用HAL_GPIO_Init函数动态重配置(注意:这会重新配置整个端口,如果同一端口有其他引脚在使用,可能会被干扰) 方案2:直接操作F4的寄存器(类似于原代码,但针对F4系列),因为HAL库没有提供单独改变一个引脚方向的函数。 由于HAL库没有提供动态切换单个引脚方向的API,而在软件IIC中需要高效切换方向,所以通常采用直接操作寄存器的方式。但是,为了保持HAL库的兼容性,我们可以使用HAL库提供的宏来访问寄存器,这样代码在不同型号间可移植性稍好。 针对STM32F4系列,GPIO配置相关寄存器是: - GPIOx_MODER(模式寄存器,2位控制一个引脚) - GPIOx_OTYPER(输出类型寄存器,1位控制一个引脚) - GPIOx_OSPEEDR(速度寄存器,2位控制一个引脚) - GPIOx_PUPDR(上下拉寄存器,2位控制一个引脚) 原代码中,输入模式配置为:1000 -> 即上拉输入(在F1中,CNF[1:0]=10,MODE=00;在F4中,上拉输入对应PUPDR=01,MODER=00) 输出模式配置为:0011 -> 即推挽输出,最大速度50MHz(在F1中,CNF[1:0]=00,MODE=11;在F4中,推挽输出对应OTYPER=0,MODER=01(通用输出)或11(高速输出)?,速度寄存器需单独设置) 具体转换思路: 对于F4的PB12: - 输入模式(上拉输入): MODER[25:24] = 00 (输入模式) PUPDR[25:24] = 01 (上拉) - 输出模式(推挽输出,50MHz): MODER[25:24] = 01 (通用输出模式) OTYPER[12] = 0 (推挽) OSPEEDR[25:24]= 10 (高速,50MHz) 或者11(超高速,但通常50MHz对应10即可) 但是,原代码中输出模式使用的是3(0011),在F1中表示输出模式,最大速度50MHz,推挽。在F4中,输出模式需要配置多个寄存器。 因此,我们可以写出对应的寄存器操作: 注意:在操作寄存器,我们需要清除原来的配置位,然后设置新的配置位。 由于用户要求使用HAL库,但HAL库没有提供动态切换方向的函数,我们只能采用直接操作寄存器的方式,但可以使用HAL库定义的寄存器地址(通过GPIOB->MODER等访问)。 步骤: 1. 定义两个宏,分别用于设置PB12为输入输出。 2. 在输入模式下,配置为上拉输入(也可以不需要上拉,由外部电路决定,但原代码是上拉输入)。 3. 在输出模式下,配置为推挽输出,高速(50MHz),注意输出模式不需要改变上下拉(可以保持上拉,但输出模式下上拉是否有效?通常输出模式下上拉电阻也会起作用)。 但是,原代码在切换模式,只修改了CRH寄存器中对应PB12的4位,其他位保留。我们在F4中也需要精确操作,不能影响同一端口的其他引脚。 具体寄存器操作(针对PB12): 输入模式: - 设置MODER[25:24]为00(输入) - 设置PUPDR[25:24]为01(上拉) 输出模式: - 设置MODER[25:24]为01(通用输出) - 设置OTYPER[12]为0(推挽) - 设置OSPEEDR[25:24]为10(高速,50MHz) - 注意:PUPDR可以保持不变?或者也可以设置为00(无上下拉)?原代码在输出模式,没有配置上下拉,但在F1中,输出模式下上拉/下拉寄存器仍然有效。为了保持原代码一致,我们输出模式不改变上下拉。 但是,原宏定义在切换并没有修改上下拉(输出模式,原代码设置的3<<12,在F1中,0011表示推挽输出,最大速度50MHz,没有配置上下拉,所以上下拉保持原来的值?)。所以我们在切换输出模式,只改变模式、输出类型速度,不改变上下拉。 然而,我们在输入模式配置了上拉,那么在切换回输出模式,我们希望保持上拉吗?在IIC通信中,一般开漏输出加上拉电阻,但这里原代码是推挽输出。实际上,软件IIC在输出模式,通常应该配置为开漏输出(这样才可以实现线),但原代码是推挽输出。这可能是因为原设计在输出0直接驱动为低,输出1释放总线(改为输入模式)?或者使用推挽输出高,这样会有问题(不能实现线)。所以,实际上软件IIC应该使用开漏模式,并配合上拉电阻。 但用户提供的原代码是推挽输出,我们这里按照原代码意图转换为输出模式为推挽。 因此,我们重新设计:输入模式配置上拉,输出模式不改变上下拉(即保留之前的状态)。但是,第一次切换,上下拉可能是未知的,所以最好在初始化配置好上下拉,然后在切换模式只切换MODEROTYPER、OSPEEDR(输出模式需要设置速度,输入模式不需要速度)?但这样每次切换输出模式都要设置速度吗?可以初始化设置一次,然后切换只改方向?这样更高效。 考虑到效率,我们可以: - 初始化将PB12配置为开漏模式(或推挽)并上拉,速度设置为高速,然后通过切换MODER寄存器的方向位(输入或输出)来切换方向。但是,HAL库没有提供这样的函数。 另一种做法(推荐):在初始化配置为开漏输出,并启用上拉。这样在输出模式下,当输出1,引脚实际上处于高阻态(靠上拉电阻拉高),此可以当作输入来读取电平。这样就不需要切换方向了。这是模拟IIC常用的方法。 但用户原代码是分别设置输入输出,且输出模式是推挽,所以这里我们按照用户的要求来实现切换。 由于用户没有说明使用的是开漏还是推挽,我们按照原代码(推挽输出)实现。但请注意,推挽输出高电平,如果总线上有设备拉低,可能会造成短路。所以软件IIC通常使用开漏。 这里,我们假设用户需要保持原代码的行为(推挽输出)。 所以,我们写出的宏定义如下(针对PB12): 注意:STM32F4的PB12对应的寄存器位: 模式寄存器MODER: 位[25:24](每个引脚占2位,从0开始,12*2=24,所以是25:24) 输出类型寄存器OTYPER: 位12(每个引脚1位) 输出速度寄存器OSPEEDR: 位[25:24](同MODER的位置) 上拉下拉寄存器PUPDR: 位[25:24] 输入模式(上拉输入): MODER[25:24] = 00 PUPDR[25:24] = 01 输出模式(推挽输出,高速50MHz): MODER[25:24] = 01 OTYPER[12] = 0 OSPEEDR[25:24] = 10 (注意:10表示高速,01表示中速,00表示低速,11表示超高速) 但是,由于我们每次切换都要设置多个寄存器,而且不能影响同一寄存器其他位,所以操作步骤: 1. 清除要设置的位 2. 设置新的位 由于这些寄存器都是可读写的,我们可以使用读-修改-写的方式。 注意:在F4中,上电复位后,GPIO寄存器默认值通常为0,所以PUPDR默认是无拉。 我们按照上述思路编写宏: 定义输入模式宏: ```c #define MPU_SDA_IN() do{ \ GPIOB->MODER &= ~(3U << (12*2)); /* 清除模式位 */ \ GPIOB->MODER |= (0U << (12*2)); /* 输入模式(00) */ \ GPIOB->PUPDR &= ~(3U << (12*2)); /* 清除上下拉位 */ \ GPIOB->PUPDR |= (1U << (12*2)); /* 上拉(01) */ \ } while(0) ``` 定义输出模式宏(推挽输出,高速50MHz): ```c #define MPU_SDA_OUT() do{ \ GPIOB->MODER &= ~(3U << (12*2)); /* 清除模式位 */ \ GPIOB->MODER |= (1U << (12*2)); /* 通用输出模式(01) */ \ GPIOB->OTYPER &= ~(1U << 12); /* 推挽输出(0) */ \ GPIOB->OSPEEDR &= ~(3U << (12*2)); /* 清除速度位 */ \ GPIOB->OSPEEDR |= (2U << (12*2)); /* 高速(10) */ \ } while(0) ``` 但是,上述代码在切换输出模式,没有处理上下拉寄存器(我们希望保留原来的上下拉设置?)。原代码在切换输出模式,并没有设置上下拉,所以这里我们不改变上下拉寄存器。因此,在输出模式宏中,我们去掉对PUPDR的操作。 另外,注意:输入模式宏中,我们设置了上拉。如果之前输出模式上下拉设置被改变了,那么切换回输入模式,我们重新设置为上拉。 这样,两个宏就可以实现动态切换方向。 然而,在软件IIC中,通常只需要初始化一次,然后在运行过程中动态切换方向。初始化,我们可以将引脚初始化为默认状态(比如输入上拉)。然后,在需要输出切换为输出模式,需要读取切换为输入模式。 注意:由于输出模式下,我们使用的是推挽输出,在输出高电平会主动输出高(而不是开漏模式下的高阻态)。这样在IIC总线上,如果两个设备同输出,一个推挽的高电平一个低电平就会冲突。因此,通常软件IIC使用开漏模式(OTYPER=1)加上拉电阻。 所以,建议用户使用开漏模式(将输出模式宏中OTYPER设置为1)。这样,在输出高电平,实际上相当于引脚处于高阻态(靠上拉电阻拉高),此其他设备可以拉低总线。 因此,如果用户希望使用开漏模式,输出模式宏应修改为: ```c #define MPU_SDA_OUT() do{ \ GPIOB->MODER &= ~(3U << (12*2)); \ GPIOB->MODER |= (1U << (12*2)); /* 通用输出模式 */ \ GPIOB->OTYPER |= (1U << 12); /* 开漏输出(1) */ \ GPIOB->OSPEEDR &= ~(3U << (12*2)); \ GPIOB->OSPEEDR |= (2U << (12*2)); /* 高速 */ \ } while(0) ``` 同,在输入模式,由于开漏输出高电平相当于释放总线(高阻态),所以可以直接读取总线状态,不需要切换为输入模式?但为了明确,我们仍然在读取切换为输入模式(或者也可以不切换,因为开漏输出高已经是高阻)。但按照常规做法,我们还是会切换方向。 综上所述,我们给出两种方案(推挽开漏)的代码,但建议使用开漏。 但是,用户原代码是推挽输出(3<<12,在F1中表示推挽输出),所以我们按照用户原代码意图给出推挽输出的版本。如果用户需要开漏,可以自行修改。 最后,为了代码的健壮性,我们使用do{...}while(0)来包裹宏定义,避免语法错误。 因此,最终的HAL库兼容的宏定义(直接操作寄存器)如下(PB12,推挽输出): ```c // 设置PB12为上拉输入模式 #define MPU_SDA_IN() do{ \ GPIOB->MODER &= ~(3U << (12*2)); /* 清除模式位 */ \ GPIOB->MODER |= (0U << (12*2)); /* 输入模式(00) */ \ GPIOB->PUPDR &= ~(3U << (12*2)); /* 清除上下拉位 */ \ GPIOB->PUPDR |= (1U << (12*2)); /* 上拉(01) */ \ } while(0) // 设置PB12为推挽输出模式,高速 #define MPU_SDA_OUT() do{ \ GPIOB->MODER &= ~(3U << (12*2)); /* 清除模式位 */ \ GPIOB->MODER |= (1U << (12*2)); /* 通用输出模式(01) */ \ GPIOB->OTYPER &= ~(1U << 12); /* 推挽输出(0) */ \ GPIOB->OSPEEDR &= ~(3U << (12*2)); /* 清除速度位 */ \ GPIOB->OSPEEDR |= (2U << (12*2)); /* 高速(10) */ \ } while(0) ``` 注意:上述代码假设PB12在GPIOB端口。如果实际端口不同,请修改GPIOB为对应的端口。 另外,在F4中,我们还需要确保GPIOB钟已经使能。这通常在初始化完成。 最后,我们给出使用HAL库初始化的示例(在初始化函数中,配置PB12的初始状态,比如输入上拉): ```c // 初始化GPIOB钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 初始化PB12为输入上拉(初始状态) GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ``` 然后在软件IIC的函数中,使用上面定义的宏来切换方向。 注意:由于我们使用宏直接操作寄存器,它不会影响同一端口其他引脚(因为我们只操作PB12对应的位)。但是,如果同一端口其他引脚也在频繁初始化,可能会有冲突。软件IIC通常只操作两个引脚(SCLSDA),并且通常这两个引脚在初始化后就不会被其他函数修改,所以是安全的。 总结:在HAL库中,没有提供动态切换一个引脚方向的函数,所以我们使用直接操作寄存器的宏来实现。这种方法在软件IIC中很常见。 如果用户坚持使用纯HAL函数,那么每次切换方向都需要调用HAL_GPIO_Init来重新配置整个端口,这样会降低效率,并且会干扰同一端口的其他引脚。因此,直接操作寄存器是更优的选择。 我们给出最终答案。 </think>在 STM32F4 HAL 库中,您需要将直接寄存器操作的 GPIO 配置转换为使用 HAL 库的标准接口。以下是针对 PB12 引脚的配置转换: ### 寄存器操作转 HAL 库实现 #### 1. **初始化配置** 在初始化代码中配置 GPIO 引脚(推荐在 `MX_GPIO_Init` 函数中添加): ```c // 使能 GPIOB 钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 配置 PB12 初始化为输出模式 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉(保留原设计的上拉特性) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 高速模式(50MHz) HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ``` #### 2. **方向切换函数** 使用 HAL 库的 GPIO 模式切换函数: ```c // 设置为输入模式(上拉) void MPU_SDA_IN(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } // 设置为输出模式(推挽) void MPU_SDA_OUT(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 输出模式通常不需要上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 高速模式 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } ``` ### 使用示例 ```c // 发送数据前设置为输出模式 MPU_SDA_OUT(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // 接收数据设置为输入模式 MPU_SDA_IN(); uint8_t data = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12); ``` ### 设计差异说明 | 特性 | 寄存器直接操作 | HAL 库实现 | |------------------|-----------------------------------|-------------------------------------| | **方向切换** | 直接修改 CRH 寄存器 | 调用 `HAL_GPIO_Init()` 重配置引脚 | | **模式配置** | 位运算组合模式值 | 使用预定义枚举类型 | | **可移植性** | 依赖具体芯片型号 | 跨 F4 系列通用 | | **代码可读性** | 需理解寄存器布局 | 语义化配置 | | **执行效率** | 较高(单指令操作) | 较低(函数调用开销) | ### 性能优化建议 如果切换频率较高(如软件 I²C),可采用混合方案: ```c // 快速方向切换(仅修改 MODER 寄存器) #define MPU_SDA_IN() (GPIOB->MODER &= ~(3U << (12*2))) // MODER[25:24]=00 #define MPU_SDA_OUT() (GPIOB->MODER |= (1U << (12*2))) // MODER[25:24]=01 ``` > ⚠️ 此方案仅修改方向寄存器,需确保其他配置(上拉/速度)已在初始化中设置正确[^2][^3]。 ### 相关问题 1. 如何优化 HAL 库的 GPIO 切换速度? 2. F4 系列 GPIO 寄存器 F1 系列有哪些主要差异? 3. 在中断服务函数中使用 HAL_GPIO_Init 是否安全? 4. 推挽输出开漏输出在 I²C 应用中的区别? 5. 如何配置复用功能模式下的 GPIO? [^1]: 寄存器操作示例展示了 STM32F1 的配置方式 [^2]: F4 系列使用 MODER 寄存器替代 F1 的 CRL/CRH [^3]: 快速切换方案仅修改必要寄存器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值