GPIO简介

本文介绍了如何使用S3C2440的GPIO接口控制LED灯的亮灭,并通过按键改变LED状态。详细讲述了GPIO寄存器配置及硬件操作方法。

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

GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平。

GPIO口一是个比较重要的概念,用户可以通过GPIO口和硬件进行数据交互(如UART),控制硬件工作(如LED、蜂鸣器等),读取硬件的工作状态信号(如中断信号)等。GPIO口的使用非常广泛。

GPIO的优点(端口扩展器)

  • 低功耗:GPIO具有更低的功率损耗(大约1µA,µC的工作电流则为100µA)。
  • 集成I²C从机接口:GPIO内置I²C从机接口,即使在待机模式下也能够全速工作。
  • 小封装:GPIO器件提供最小的封装尺寸—3mm x 3mm QFN!
  • 低成本:您不用为没有使用的功能买单!
  • 快速上市:不需要编写额外的代码、文档,不需要任何维护工作!
  • 灵活的灯光控制:内置多路高分辨率的PWM输出。
  • 可预先确定响应时间:缩短或确定外部事件与中断之间的响应时间。
  • 更好的灯光效果:匹配的电流输出确保均匀的显示亮度。
  • 布线简单:仅需使用2条I²C总线或3条SPI总线。

S3C2410共有117个I/O端口,共分为A~H共8组:GPA、GPB、...、GPH。S3C2440共有130个I/O端口,分为A~J共9组:GPA、GPB、...、GPJ。可以通过设置寄存器来确定某个引脚用于输入、输出还是其他特殊功能。比如:可以设置GPH6作为输入、输出、或者用于串口。

1 GPIO硬件介绍

1.1 通过寄存器来操作GPIO引脚

       GPxCON用于选择引脚功能,GPxDAT用于读/写引脚数据;另外,GPxUP用于确定是否使用内部上拉电阻。x为B、...、 H/J,没有GPAUP寄存器。

1.1.1 GPxCON寄存器

       从寄存器的名字可以看出,它用于配置(Configure)-选择引脚功能。

       PORTA与PORTB~PORT H/J在功能选择方面有所不同,GPACON中每一位对应一根引脚(共23根引脚)。当某位被设为0时,相应引脚为输出引脚,此时我们可以在GPADAT 中相应位写入0或是1让此引脚为低电平或高电平;当某位被设为1时,相应引脚为地址线或用于地址控制,此时GPADAT无用。一般而言,GPACON通常 被设为全1,以便访问外部存储器件。

       PORT B~ PORT H/J在寄存器操作方面完全相同。GPxCON中每两位控制一根引脚:00表示输入、01表示输出、10表示特殊功能、11保留不用。

 

1.1.2 GPxDAT寄存器

       GPxDAT用于读/写引脚;当引脚被设为输入时,读此寄存器可知相应引脚的电平状态是高还是低;当引脚被设为输出时,写此寄存器相应位可以令此引脚输出高电平或是低电平。

 

1.1.3 GPxUP寄存器

       GPxUP:某位为1时,相应引脚无内部上拉电阻;为0时,相应引脚使用内部上拉电阻。

       上拉电阻的作用在于:当GPIO引脚处于第三态(即不是输出高电平,也不是输出低电平,而是呈高阻态,即相当于没接芯片)时,它的电平状态由上拉电阻、下拉电阻确定。

 

1.2 访问硬件

1.2.1 访问单个引脚

       单个引脚的操作无外乎3种:输出高低电平、检测引脚状态、中断。对某个引脚的操作一般通过读、写寄存器来完成。

       访问这些寄存器是通过软件来读写它们的地址。比如:S3C2410和S3C2440的GPBCON、GPBDAT寄存器地址都是0x56000010、0x56000014,可以通过如下的指令让GPB5输出低电平。

#define GPBCON (*volatile unsigned long *)0x56000010)   //long=int 4字节;char 1字节;short 2字节

#define GPBDAT (*volatile unsigned long *)0x56000014)

#define GPB5_out (1<<(582))

GPBCON = GPB5_out;

GPBDAT &= ~(1<<5);

 

1.2.2 以总线方式访问硬件

       并非只能通过寄存器才能发出硬件信号,实际上通过访问总线的方式控制硬件更为常见。如下图所示S3C2410/S3C2440与NOR Flash的连线图,读写操作都是16位为单位。

       图中缓冲器的作用是以提搞驱动能力、隔离前后级信号。NOR Flash(AM29LV800BB)的片选信号使用nGCS0信号,当CPU发出的地址信号处于0x00000000~0x07FFFFFF之间 时,nGCS0信号有效(为低电平),于是NOR Flash被选中。这时,CPU发出的地址信号传到NOR Flash;进行写操作时,nWE信号为低,数据信号从CPU发给NOR Flash;进行读操作时,nWE信号为高,数据信号从NOR Flash发给CPU。

       ADDR1~ADDR20 ------------------>   >--------------------A0~A19

       DATA0~DATA15 <----------------->   <------------------->D0~D15

               nOE  ------------------>   -------------------->nOE

               nWE  ------------------>   -------------------->nWE

              nGCS0 ------------------>   -------------------->nCE

         S3C2410/S3C2440              缓冲器                   NOR Flash(AM29LV800BB)

 

       软件如何发起写操作呢,下面有几个例子的代码进行讲解。

1)地址对齐的16位读操作

unsigned short *pwAddr = (unsigned short *)0x2;

unsigned short uwVal;

uwVal = *pwAddr;

       上述代码会向NOR Flash发起读操作:CPU发出的读地址为0x2,则地址总线ADDR1~ADDR20、A0~A19的信号都是1、0...、0(CPU的ADDR0 为0,不过ADDR0没有接到NOR Flash上)。NOR Flash的地址就是0x1,NOR Flash在稍后的时间里将地址上的16位数据取出,并通过数据总线D0~D15发给CPU。

 

2)地址位不对齐的16位读操作

unsigned short *pwAddr = (unsigned short *)0x1;

unsigned short uwVal;

uwVal = *pwAddr;

       由于地址是0x1,不是2对齐的,但是BANK0的位宽被设为16,这将导致异常。我们可以设置异常处理函数来处理这种情况。在异常处理函数中,使用 0x0、0x2发起两次读操作,然后将两个结果组合起来:使用地址0x0的两字节数据D0、D1;再使用地址0x02读到D2、D3;最后,D1、D2组 合成一个16位的数字返回给wVal。如果没有地址不对齐的异常处理函数,那么上述代码将会出错。如果某个BANK的位宽被设为n,访问此BANK时,在 总线上永远只会看到地址对齐的n位操作。

 

3)8位读操作

unsigned char *pwAddr = (unsigned char *)0x6;

unsigned char ucVal;

ucVal = *pwAddr;

       CPU首先使用地址0x6对NOR Flsh发起16位的读操作,得到两个字节的数据,假设为D0、D1;然后将D0取出赋值给变量ucVal。在读操作期间,地址总线 ADDR1~ADDR20、A0~A19的信号都是1、1、0、...、0(CPU的ADDR0为0,不过ADDR0没有接到NOR Flash上)。CPU会自动丢弃D1。

 

4)32位读操作

unsigned int *pwAddr = (unsigned int *)0x6;

unsigned int udwVal;

udwVal = *pwAddr;

       CPU首先使用地址0x6对NOR Flsh发起16位的读操作,得到两个字节的数据,假设为D0、D1;再使用地址0x8发起读操作,得到两字节的数据,假设为D2、D3;最后将这4个数据组合后赋给变量udwVal。

 

5)16位写操作

unsigned short *pwAddr = (unsigned short *)0x6;

*pwAddr = 0x1234;

       由于NOR Flash的特性,使得NOR Flash的写操作比较复杂——比如要先发出特定的地址信号通知NOR Flash准备接收数据,然后才发出数据等。不过,其总线上的电信号与软件指令的关系与读操作类似,只是数据的传输方向相反。

2、使用软件来访问硬件

当个引脚的操作有3种:输出高低电平、检测引脚状态、中断。对某个引脚的操作一般通过读写寄存器实现

首先我们从点亮LED开始,下图选自mini2440原理图,LED1-4分别对应GPB5-8

如果要控制这些LED,那么我们首先要把GPBCON寄存器中GPB5-8对应的位设为输出功能,然后写GPBDAT寄存器的相应位,使这4个引脚输出高低电平

一般是低电平有效,即高电平时,对应LED熄灭,低电平时,对应LED点亮

访问寄存器的时候,通过S3C2440的数据手册查到GPBCON和GPBDAT寄存器的地址,附数据手册  点击下载

 GPBCON为0x56000010,GPBDAT为0x56000014

通过下面的代码让GPB5输出低电平,点亮LED1

 

#define GPBCON (*(volatile unsigned long *) 0x56000010)        //volatile修饰符确保每次去内存中读取变量的值,还不是从cache或者寄存器中

#define GPBDAT (*(volatile unsigned long *) 0x56000014)       

#define GPB5_OUT (1<<(5*2))        //两位控制一个引脚,那么GPB5就是GPBCON的[11:10]位,1左移10位,则[11:10]为01,表示GPB5为输出

GPBCON = GPB5_OUT;

GPBDAT &= ~(1<<5);        //1左移5位取反,那么第5位为0,即GPB5输出低电平,点亮LED1

 

二、GPIO操作实例

1、使用汇编代码点亮一个LED

先看源程序 led_on.S

 

.text

.global _start

_start:     

            LDR     R0,=0x56000010      @ R0设为GPBCON寄存器

            MOV     R1,#0x00000400      @ 设置GPB5为输出口, 位[11:10]=0b01

            STR     R1,[R0]             

            LDR     R0,=0x56000014      @ R0设为GPBDAT寄存器

            MOV     R1,#0x00000000      @ 此值改为0x00000020,可让LED1熄灭

            STR     R1,[R0]                        @ GPB5输出0,LED1点亮

MAIN_LOOP:

            B       MAIN_LOOP                  @无限循环

 再来看程序的Makefile

 

led_on.bin : led_on.S

arm-Linux-gcc -g -c -o led_on.o led_on.S

arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf

arm-linux-objcopy -O binary -S led_on_elf led_on.bin

clean:

rm -f   led_on.bin led_on_elf *.o

led_on.S生成led_on.bin

第一行做汇编

第二行做连接,指定代码段起始地址为0x00000000

第三行把ELF格式转为二进制格式

clean用于清除编译生成的文件

 

2、使用c语言代码点亮LED
汇编可读性比C差,我们用C来实现
 
@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************       
 
.text
.global _start
_start:
            ldr     r0, =0x53000000      @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str   r1, [r0]                           @ 写入0,禁止WATCHDOG,否则CPU会不断重启
        
            ldr     sp, =1024*4              @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K,这4k是steppingstone,后面会介绍
                                                          @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
            bl      main                           @ 调用C程序中的main函数
halt_loop:
            b       halt_loop
 
下面是led_on_c.c
 
#define GPBCON      (*(volatile unsigned long *)0x56000010)
#define GPBDAT      (*(volatile unsigned long *)0x56000014)

int main()
{
    GPBCON = 0x00000400;     // 设置GPB5为输出口, 位[11:10]=0b01
    GPBDAT = 0x00000000;     // GPB5输出0,LED1点亮

    return 0;
}
 

 最后是Makefile

 

led_on_c.bin : crt0.S  led_on_c.c

arm-linux-gcc -g -c -o crt0.o crt0.S

arm-linux-gcc -g -c -o led_on_c.o led_on_c.c

arm-linux-ld -Ttext 0x0000000 -g  crt0.o led_on_c.o -o led_on_c_elf

arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.bin

arm-linux-objdump -D -m arm  led_on_c_elf > led_on_c.dis

clean:

rm -f led_on_c.dis led_on_c.bin led_on_c_elf *.o

分别汇编crt0.S和led_on_c.c

连接目标到led_on_c_elf,代码段起始地址位0x00000000

转换ELF格式到二进制led_on_c.bin

最后转换结果为汇编码方便查看

 

3、测试程序

在先前搭建的编译环境中进入代码目录

#make

得到的bin文件,在win中使用dnw下载到开发板,设置串口波特率,对应端口,8N1,下载地址0x00000000

开关拨到nor flash,打开电源,出现菜单以后,选择a

 

然后选择USB PORT-transmit/restore,选择编译好的bin文件

然后开关拨到nand启动,效果如下:(设置LED1和LED4亮)

 

 

4、使用按键来控制LED

K1-K6如上图对应GPG,我们使用K1-K4操作LED1-LED4

 

 

 

@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************       
 
.text
.global _start
_start:
            ldr     r0, =0x56000010      @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str   r1, [r0]                           @ 写入0,禁止WATCHDOG,否则CPU会不断重启
        
            ldr     sp, =1024*4              @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K,这4k是steppingstone,后面会介绍
                                                          @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
            bl      main                           @ 调用C程序中的main函数
halt_loop:
            b       halt_loop
 
下面是key_led.c文件
 
#define GPBCON      (*(volatile unsigned long *)0x56000010)
#define GPBDAT      (*(volatile unsigned long *)0x56000014)

#define GPGCON      (*(volatile unsigned long *)0x56000060)
#define GPGDAT      (*(volatile unsigned long *)0x56000064)

/*
 * LED1-4对应GPB5、GPB6、GPB7、GPB8
 */
#define GPB5_out        (1<<(5*2))
#define GPB6_out        (1<<(6*2))
#define GPB7_out        (1<<(7*2))
#define GPB8_out        (1<<(8*2))

/*
 * K1-K4对应GPG0、GPG3、GPG5、GPG6
 */
#define GPG7_in    ~(3<<(6*2))
#define GPG6_in     ~(3<<(5*2))
#define GPG3_in     ~(3<<(3*2))
#define GPG0_in     ~(3<<(0*2))

int main()
{
        unsigned long dwDat;
         // LED1-LED4对应的4根引脚设为输出
        GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out ;

        // K1-K4对应的2根引脚设为输入
        GPGCON = GPG0_in & GPG3_in & GPG6_in & GPG7_in ;
        

        while(1){
             //若Kn为0(表示按下),则令LEDn为0(表示点亮)
            dwDat = GPGDAT;              // 读取GPG管脚电平状态
        
            if (dwDat & (1<<0))         // K1没有按下
                GPBDAT |= (1<<5);        // LED1熄灭
            else    
                GPBDAT &= ~(1<<5);       // LED1点亮
                
            if (dwDat & (1<<3))          // K2没有按下
                GPBDAT |= (1<<6);        // LED2熄灭
            else    
                GPBDAT &= ~(1<<6);       // LED2点亮
            
            if (dwDat & (1<<5))          // K3没有按下
                GPBDAT |= (1<<7);        // LED3熄灭
            else    
                GPBDAT &= ~(1<<7);       // LED3点亮
    
            if (dwDat & (1<<6))          // K4没有按下
                GPBDAT |= (1<<8);        // LED4熄灭
            else    
                GPBDAT &= ~(1<<8);       // LED4点亮
    }

    return 0;
}
### GPIO编程使用指南 #### 1. GPIO简介 GPIO(General Purpose Input Output),即通用输入输出端口,是一种可由软件控制其行为的硬件资源。它允许微控制器与其他外部设备通信,既可以作为输入也可以作为输出。 #### 2. STM32中的GPIO配置流程 STM32系列单片机提供了丰富的外设功能,其中GPIO是最基础也是最常用的外设之一。以下是基于STM32的标准库或HAL库实现GPIO初始化的一般过程: - **启用时钟**:任何外设操作前都需要先开启对应的时钟。 - **定义结构体变量**:用于存储GPIO的具体参数设置。 - **配置引脚属性**: - 设置引脚号(Pin Number)。 - 定义工作模式(Mode)。 - 设定速度(Speed)、上下拉电阻状态(Pull-up/Pull-down)以及驱动方式(Push-pull/Open-drain)。 - **调用初始化函数**:完成实际的寄存器写入动作。 具体到代码层面,在标准库环境下可以参照如下模板[^1];而在较新的HAL库框架下则有另一种形式[^2]。 #### 3. 示例程序分析 这里给出两个版本的例子来说明如何通过不同的开发环境去操控一个连接在PD12上的LED灯泡亮灭情况。 ##### (1)Standard Peripheral Library Example ```c #include "stm32f4xx.h" int main(void){ // Enable clock access to GPIOD port. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // Initialize structure used for configuring the pin settings. GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // Select PD12 as target pin. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // Set it into output mode. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;// Define maximum switching frequency. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // Choose push pull type operation. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // No internal resistor connection. // Apply these configurations onto physical hardware component. GPIO_Init(GPIOD,&GPIO_InitStructure); while(1){ // Turn ON LED connected at PD12 by setting its value high. GPIO_SetBits(GPIOD, GPIO_Pin_12); // Simple delay loop without using any specialized timing functions. for(int i=0;i<1000000;i++); // Now turn OFF that same led again but this time clearing bit instead of setting one. GPIO_ResetBits(GPIOD, GPIO_Pin_12); // Another simple busy waiting period implemented via counting iterations inside a dummy cycle construct. for(int j=0;j<1000000;j++); } } ``` ##### (2)Hardware Abstraction Layer (HAL) Library Approach For projects utilizing STMicroelectronics' more contemporary Hardware Abstraction Layers approach here's an alternative method which leverages their prebuilt helper macros and routines making things slightly easier compared with raw register manipulations seen previously under standard peripheral libraries context above: ```c void GPIO_Configuration(void){ GPIO_InitTypeDef GPIO_InitStruct; /* Enable Clock Access To Port A */ __HAL_RCC_GPIOA_CLK_ENABLE(); /** Configure PA0 Pin As Push Pull Output With Internal Resistor Pulled Up At High Speed Operation Mode **/ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* Perform Actual Initialization On Physical Component Level Through Provided API Call */ HAL_GPIO_Init(GPIOA ,&GPIO_InitStruct ); } /* Inside Main Function After Calling Above Setup Routine You Can Simply Toggle State Of That Particular Bit Using Below Macro Definitions Directly Without Needing Additional Loops Etc..*/ while(true){ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET ); Delay(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);Delay(); } ``` 以上两种方法展示了利用不同工具链针对相同任务所采取的不同策略。前者更加贴近底层细节而后者封装程度更高从而简化了一些常见重复性的劳动强度但是同时也隐藏掉了部分内部工作机制原理因此理解两者区别有助于开发者根据项目需求灵活选用合适的技术路线[^1].
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值