stm32f103 GPIO——一灯大师之I/O口输出模式的寄存器配置

本文详细讲解了如何使用二进制位操作实现寄存器中特定位的置0和置1,以及如何读取和配置GPIO寄存器以控制I/O口的速度和模式。涵盖了GPIOC组I/O口的配置实例和端口配置寄存器的结构,适合STM32开发者深入理解寄存器操作技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在讲述寄存器配置之前,我们先来看看二进制中,在不改变其他位的条件下,对某个位的置1和清零。

先看口诀:

        清零用&            (对某一位,进行"与"操作。与0相与,会清零。与1相与,不改变)

        置1用 |              (对某一位,进行"或"操作。与0相或,不改变。与1相或,会置1)

例如:

        二进制:0001 1100,我想要在不改变其他位的情况下,将第四位清零。因为,清零用&,与0相与,会清零,与1相与,不改变所以:

        0001 1100  &   ~(0x01<<3)  ==  0001 0100

        注意:0x01<<3  ==  0000 1000(这里由于要和8位的二进制数相与,所以这里会自动扩展成8位)(而且注意这里的移动是指16进制转换成二进制的移动,而不是对16进制进行移动),所以 ~(0x01<<3)  ==  1111 0111, 

        所以0001 1100  &   ~(0x01<<3) ==  0001 1100  &  1111 0111 ==  0001 0100

        这样,在不改变其他位的情况下,第四位就被清零了。

         二进制:0001 0100,我想要在不改变其他位的情况下,将第四位置1。因为,置1用 | ,与0相或,不改变。与1相或,会置1。所以:

        0001 0100  |   0x01<<3  ==  0001 1100

        注意:0x01<<3  ==  0000 1000(这里由于要和8位的二进制数相与,所以这里会自动扩展成8位)

        所以0001 0100  |   0x01<<3 ==  0001 0100  |  0000 1000 ==  0001 1100

        这样,在不改变其他位的情况下,第四位就被置1了。

上述是某一位的改变,但是如果我想一下清零四个位呢?那我们就用0x0f来进行左移

如:0x0f << 4 == 0000 1111 << 4  ==  1111 0000

        所以,1011 0100  &   ~(0x0f<<4)   ==  1011 0100  &  0000 1111  ==  0000 0100

        前面的四位一下就被清零了。

如何读取寄存器中某个位的值?

        上面我们讲述了如何置位/清零寄存器中的值,现在我们来看如何读取寄存器中的值

        假设某寄存器的值为:0000011010,这串数据被关在黑黑的寄存器中,我又没开天眼,我要怎样才能知道寄存器中的第4位(注意:第一个数为第0位)是1还是0?

        又上面可知,与0相与,会清零。与1相与,不改变。

        所以:   \frac{0000011010}{0000010000}     0000011010与0000010000进行"&"操作,如果结果为真,则0000010000中的第4位就为1,如果结果为假,则0000011010中的第4位就为0

        就像这样:\frac{0000011010}{0000100000}   这两个数相与,一定为0,即:为假

       

         注意:    \frac{0000011010}{0000010000}     0000011010与0000010000进行"&"操作,其结果是为真

而不是为1,因为"&"操作得出的结果为0000010000,0000010000不等于1,而是等于16

程序:do{

                temp = GPIOA_CRL;      

          }while( ! (temp & (1<<16)))

        该程序是读取GPIOA_CRL中的第16位是1还是0,如果是1,则为真。由于非 "!" 的存在,所以 ! (temp & (1<<16))为假,所以此时do.....while循环不再循环下去。注意:这里需要用do....while而不用while,是因为需要先执行temp = GPIOA_CRL; 将GPIOA_CRL寄存器中的值放在变量temp中,然后再对temp进行真假判断。

下面我们来看I/O口输出模式的寄存器配置:

 1.端口配置低寄存器

        注意:在寄存器配置的时候,我们都是先将对应的位清零后,再置1。

         

         端口配置低位寄存器是用来配置某一组I/O的P0~P7引脚(一组I/O有16个I/O口)的I/O口的模式和最大输出速度的。

        其中MODE是用来配置输出模式的最大输出速度的,而CNF是用来配置I/O口的工作模式的。所以,该寄存器是每4个位 配置一个I/O口。

 

输出速度:   

1,最大速度----引脚上的电平最大翻转速度
2,频率和周期是互为倒数的关系
        1MHZ=1000000Hz 
        1s=1000Ms=10000000us

I/O口输出模式下有三种输出速度可选(2MHz,10MHz,50MHz),这个速度是指I/O⼝驱动电路的响应速度;I/O管脚内部有多个响应

不同的驱动电路,⽤户可以根据⾃⼰的需要选择合适的驱动电路。

⾼低频⽐较 
⾼频驱动电路:输出频率⾼,噪⾳⼤,功耗⾼,电磁⼲扰强; 
低频驱动电路:输出频率低,噪⾳⼩,功耗低,电磁⼲扰弱;提⾼系统EMI(电磁⼲扰)性能;

总结:通过选择速度来选择不同的输出驱动模块,达到最佳的噪⾳控制和降低功耗的⽬的如果需要选择较⾼频率信号,但是却选择了低频驱
动模块,很有可能会失真的输出信号;所以GPIO的引脚速度应与应⽤匹配。

举⼏个栗⼦: 
1. 对于串⼝来说,加⼊最⼤波特率为115200,这样只需要⽤2M的GPIO的引脚速度就可以了,省电噪⾳⼜⼩; 
2. 对于I2C接⼝,假如使⽤400 000波特率,若想把余量留⼤⼀些,2M的GPIO引脚速度或许是不够,这时可以选⽤10M的GPIO引脚速度; 
3. 对于SPI接⼝,假如使⽤18M或9M的波特率,⽤10M的GPIO⼝也不够⽤了,需要选择呢50M的GPIO引脚速度 
4. GPIO⼝设置为输⼊时,输出驱动电路与端⼝是断开的,所以这时配置输出速度是⽆意义的; 
5. 在复位期间和刚复位后,复位功能未开启,I/O端⼝被配置成浮空输⼊模式; 
6. 所有端⼝都有外部中断能⼒,当使⽤外部中断功能时,端⼝必须设置成输⼊模式; 
7. GPIO的配置具有上锁的功能,当配置好GPIO后,可以通过程序锁住配置组合,知道下次芯⽚复位才能解开;


转载链接:https://wenku.baidu.com/view/fb519748bb4ae45c3b3567ec102de2bd9605de3c.html

        

        图中的x表示,这个寄存器可以对GPIOA~GPIOE I/O组的I/O口进行配置。

 

        

  寄存器中有一个偏移地址:

        我们大家都知道有一个寻址方式叫: 基地址 + 偏移地址 寻址。STM32中就是通过基地址 + 偏移地址寻找方式来找到对应的寄存器的。

        这个偏移地址表示的是在某I/O组中,该寄存器的首地址,就是以I/O口组中的基地址为标准,加上偏移量得到的。我们假设是GPIOA组,从图中我们知道GPIOA组的基地址为0x4001 0800,而端口配置低位寄存器的偏移量为0x00,所以基地址 + 偏移地址 = 0x4001 0800 +  0x00 = 0x4001 0800,这个0x4001 0800就是端口配置低位寄存器的首地址。

寄存器中还有一个复位值:

        复位值,指的是:在单片机复位后,该寄存器中被默认初始化的值。图中,该寄存器的复位值是0x4444 4444 ,所以在复位后,该寄存器被默认赋值为0x4444 4444。

我们现在给GPIOC组中的I/O口Pin7配置为推挽输出模式,输出速度为2MHZ:

        清零用&,与0相与,会清零,与1相与,不改变。 

        置1用|,与0相或,不改变。与1相或,会置1

         GPIOC->CRL  &= ~( (uint32_t) 0xf << 28 );       //将28~31位清零

        GPIOC->CRL |= 0x2 << 28;        //设置I/O口模式

        即:1111 << 28 == 1111 0000 0000 0000 0000 0000 0000 0000,所以 ~(0xf << 28 ) == 0000 1111 1111 1111 1111 1111 1111 1111

        注意:这里要加上(uint32_t) 写成~( (uint32_t) 0xf << 28 ),否则会报警告 warning: #61-D: integer operation result is out of range

        因为编译器默认signed int 即32位有符号整数类型,而0xf << 28实际为0xf0000000,这样就有可能改写了符号位(最高位)。所以,我们将其强转为无符号整数类型uint32_t

 2.端口配置高寄存器

          端口配置高位寄存器是用来配置某一组I/O的P8~P15引脚(一组I/O有16个I/O口)的I/O口的模式和输出速度的。

  3.端口输入数据寄存器

        

  4.端口输出数据寄存器

 ​

        端口输出数据一共有16位,分别对应GPIO组中的Pin0~Pin15,写1则对应的I/O口输出1,写0则I/O口输出0。注意:设置/清除的意思就是置1/清零。

如:GPIOC->ODR |= 0x111 << 6;

即:使GPIOC组中的Pin6~Pin8都输出高电平。

5.端口位设置/清除寄存器

     

    端口位设置/清除寄存器的功能和ODR寄存器的功能一样,都是为了设置I/O口的输出电平。实际上往端口位设置/清除寄存器写1/0后,端口位设置/清除寄存器就会往ODR寄存器对应的位写1/0,所以对端口位设置/清除寄存器操作,就是间接的对ODR寄存器进行操作。

   注意:我们由上述可以知道,每个I/O口都有上面所描述的寄存器组。

寄存器版——点灯函数:

void LED_Config(void)        //初始化函数,配置GPIOC的PC6~PC8为通用推挽输出模式,输出速度为2Mhz
{
    RCC->APB2ENR |=0X1<<4;        //打开GPIOC时钟

    GPIOC->CRH &= ~0xf;                //设置PC8
    GPIOC->CRH |= 0x1<<1;

    GPIOC->CRL &= ~((uint32_t)0xff<<24);        //设置PC6与PC7
    GPIOC->CRL |= 0x22<<24;

    GPIOC->ODR |= 0x7 << 6;    //关灯  
    
}

void LED_Open(void)
{

    GPIOC->ODR &= ~(0x7 << 6);     //开灯,即I/O口输出低电平

}

void LED_Close(void)
{

    GPIOC->ODR |= 0x7 << 6;    //关灯,即I/O口输出高电平    
    
}

<think>嗯,用户现在的问题是关于Jetson Nano控制激光模块时引脚电压无变化的原因。用户之前提到过测量5V和物理引脚7之间电压为1.6V的情况,现在又结合了激光模块的控制问题。看来用户正在做一个嵌入式项目,可能是在搭建智能小车或者机器人平台。 从用户提供的引用内容看,ta应该是个电子爱好者或工科学生,正在做基于Jetson Nano的移动设备。引用[3]提到使用12V电池和LM2596降压模块供电,说明是电池供电系统。引用[2]显示ta之前遇到过硬件烧毁问题,现在又遇到GPIO控制异常,说明在电路设计方面可能还需要更多指导。 用户的核心困惑在于:明明用GPIO控制激光模块,为什么测量5V和引脚7的电压总是1.6V不变?这其实是个典型的电压测量误区。让我仔细分析: 首先确认关键信息:物理引脚7对应GPIO216,工作电压3.3V。当它输出高电平时,与5V引脚的压差理论值是5-3.3=1.7V,与用户测得的1.6V基本吻合。这说明引脚7很可能始终保持在3.3V高电平状态。 那么为什么控制激光时电压不变呢?可能有几个原因: 1. 程序问题:GPIO没有真正切换状态。可能初始化配置错误,或者输出值没更新 2. 电路问题:如果激光模块直接接在GPIO上,当模块电流超过16mA就可能拉低电压 3. 测量方式:用户仍在测量5V与GPIO的压差而非对地电压 特别要注意引用[1]强调的功耗问题:Jetson Nano在移动设备中必须严格管理功耗。如果激光模块功率较大,直接由GPIO驱动可能超过单引脚最大电流(16mA),不仅导致电压异常,还可能损坏主板——这正好解释了用户之前烧毁STM32的问题。 解决方案需要分层次: 1. 诊断:先用万用表测量引脚7对地电压,排除测量方法问题 2. 验证:用简单LED电路测试GPIO输出功能是否正常 3. 电路改造:必须为激光模块添加驱动电路。根据引用[3]的12V供电特性,建议用MOSFET或三极管搭建开关电路,GPIO仅提供控制信号 4. 代码检查:确认GPIO配置输出模式,并验证控制逻辑 用户可能没意识到:当ta测量5V与GPIO的压差时,本质上测的是(5V - GPIO电压)。只要GPIO输出高电平,这个值永远在1.7V左右,与激光状态无关——这才是电压“不变”的真正原因。要检测激光开关状态,应该测量激光模块供电端的对地电压。 最后要提醒安全事项:Jetson Nano的GPIO绝对不可直接驱动功率器件!用户之前烧过STM32,说明电路设计存在风险。建议在激光模块电源回路添加保险丝,GPIO串联限流电阻,并确保接地可靠。</think>以下是针对 Jetson Nano GPIO 控制激光模块时电压无变化的分析及解决方案,结合硬件特性和用户实际场景: --- ### 问题原因分析 1. **GPIO 工作模式错误** - 物理引脚 7(对应 GPIO216)默认可能被配置为 **输入模式** 或 **非输出功能**(如 I2C/SPI),导致输出电平无法切换[^1]。 - 验证命令: ```bash sudo /opt/nvidia/jetson-io/config-by-pin.py ``` 查看引脚 7 的当前功能(需为 `GPIO Output`)。 2. **驱动能力不足** - Jetson Nano 的 GPIO 驱动电流仅 **16mA**,若激光模块工作电流超过此值(常见激光模块需 20-100mA),电压会被拉低至异常值(如 1.6V)[^3]。 - *典型表现*:空载时输出正常(3.3V),接激光模块后电压骤降。 3. **电路设计缺陷** - 直接连接 GPIO 与激光模块: ```mermaid graph LR GPIO --> 激光模块 ``` 未使用缓冲电路(如三极管/MOSFET驱动),导致 GPIO 过载。 4. **电源干扰** - 12V 电池通过 LM2596 降压至 5V 时,若未添加滤波电容,电压波动可能干扰 GPIO 输出[^3]。 --- ### 解决方案 #### 1. 确认 GPIO 配置 ```bash # 安装 GPIO 控制工具 sudo apt install gpiod # 查看引脚 7 状态 gpioinfo | grep 216 ``` - 若功能非 `output`,需通过 `config-by-pin.py` 或代码重配置输出模式。 #### 2. 添加驱动电路(必需) ```mermaid graph LR GPIO -->|信号| MOSFET[IRLZ34N] -->|驱动| 激光模块 5V电源 --> 激光模块 GND --> MOSFET ``` - **电路说明**: - GPIO 连接 MOSFET 栅极(Gate) - 激光模块正极接 **5V 电源**(非 GPIO 5V 引脚) - 激光模块负极接 MOSFET 漏极(Drain) - MOSFET 源极(Source)接地 - **优点**:GPIO 仅提供控制信号,负载电流由外部电源承担,避免过载。 #### 3. 代码控制示例(Python) ```python import Jetson.GPIO as GPIO LASER_PIN = 7 # 物理引脚 7 GPIO.setmode(GPIO.BOARD) GPIO.setup(LASER_PIN, GPIO.OUT) try: while True: GPIO.output(LASER_PIN, GPIO.HIGH) # 开启激光 time.sleep(1) GPIO.output(LASOR_PIN, GPIO.LOW) # 关闭激光 time.sleep(1) finally: GPIO.cleanup() ``` #### 4. 电源优化 - 在 LM2596 输出端并联 **100μF 电解电容 + 0.1μF 陶瓷电容**,抑制电压波动[^3]。 - 单独用一路 5V 为激光模块供电,避免与 Jetson Nano 共用电源。 --- ### 关键验证步骤 1. **空载测试** 断开激光模块,测量引脚 7 对地电压: - 高电平应为 **3.3V±0.2V** - 低电平应为 **0V** *若异常 → 检查 GPIO 配置* 2. **带载测试** 接入激光模块后: - 若电压仍为 1.6V → **驱动能力不足**(需添加 MOSFET) - 若电压在 0V/3.3V 跳变但激光不亮 → **供电不足**(检查 5V 电源电流) --- ### 总结 | 现象 | 根本原因 | 解决方案 | |---------------------|----------------------|------------------------| | 电压固定 1.6V | GPIO 过载或配置错误 | 添加 MOSFET 驱动电路 | | 激光间歇性熄灭 | 电源波动 | 增加滤波电容 | | GPIO 发热 | 短路或电流超标 | 立即断电,检查电路接线 | > ⚠️ **警告**:切勿将激光模块直接接 GPIO!可能烧毁 Jetson Nano 主控[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值