2017-03-09 61 views
1

我有一个问题,试图让连接到gpio pin 18在树莓派上的LED。我已经用C测试了我的设置,并确认这不是问题,但是我的汇编代码是问题。使用ARM组件的闪存树莓pi LED

这是我到目前为止的代码:

.global main 

main: 
    SUB SP, SP, #16   @ Create 16 bytes storage 
    LDR R0, .addr_file  @ get GPIO Controller addr 
    LDR R1, .flags   @ set flag permissions 
    BL open    @ call to get file handle 

    STR R0, [SP, #12]  @ File handle number 
    LDR R3, [SP, #12]  @ Get File handle 
    STR R3, [SP, #0]  @ Store file handle on top stack 
    LDR R3, .gpiobase  @ get GPIO_Base address 
    STR R3, [SP, #4]  @ store on SP+4 
    MOV R0, #0    @ R0=0 
    MOV R1, #4096   @ R1=page 
    MOV R2, #3    @ R2=3 
    MOV R3, #1    @ R3=1 (stdouts) 
    BL mmap    @ call libc fct for mmap 

    STR R0, [SP, #16]  @ store virtual mem addr 
    LDR R3, [SP, #16]  @ get virtual mem addr 

fctsel: 
    ADD R3, R3, #4   @ add 4 for block 1 (GPFSEL1) 
    LDR R2, [SP, #16]  @ get virtual mem addr 
    ADD R2, R2, #16   @ add 4 for block 1 (GPFSEL1) 
    LDR R2, [R2, #0]  @ load R2 with value at R2 
    BIC R2, R2, #0b111<<24 @ Bitwise clear of three bits 
    STR R2, [R3, #0]  @ Store result in Register [set input] 
    LDR R3, [SP, #16]  @ Get virtual mem address 
    ADD R3, R3, #4   @ Add 4 for block 1 (GPFSEL1) 
    LDR R2, [SP, #16]  @ Get virtual mem addr 
    ADD R2, R2, #4   @ add 4 for block 1 (GPFSEL1) 
    LDR R2, [R2, #0]  @ Load R2 with value at R2 
    ORR R2, R2, #1<<24  @ Set bit.... 
    STR R2, [R3, #0]  @ ...and make output 

on: 
    LDR R3, [SP, #16]  @ get virt mem addr 
    MOV R4, #1    @ get 1 
    MOV R2, R4, LSL#18  @ Shift by pin number 
    STR R2, [R3, #0]  @ write to memory 
    LDR R0, [SP, #12]  @ get file handle 
    BL close    @ close file 

    ADD SP, SP, #16   @ restore SP 

    MOV R7, #1 
    SWI 0 


.addr_file: .word .file 
.flags:  .word 1576962 
@.gpiobase: .word 0x20200000 @ GPIO_Base for Pi 1 
.gpiobase: .word 0x3F200000 @ GPIO_Base for Pi 2 

.data 
.file: .ascii "/dev/mem\000" 

我设法得到它与GPIO引脚47的工作。但是,当我改变它与第18针一起工作时,这是我遇到问题的地方。预先感谢任何帮助,非常感谢!

+0

忘了问你你正在使用哪种Raspberry Pi。 GPIO引脚布局很重要,它们都不相同。 – InfinitelyManic

+0

您是否使用存储/重载('STR R0,[SP,#16]'/'LDR R3,[SP,#16]')作为某种有意的延迟?如果没有,那么*未优化*编译器生成的代码不是一个很好的示例。它在C语句之间溢出/重新加载到内存中,仅用于支持在断点停止时使用调试器修改C变量。普通代码应该只保留寄存器中的变量; ARM有16个,其中14个是通用的。无论如何,我不认为你需要将任何东西都泄露给记忆,所以唯一的商店应该是实际的MMIO写作。 –

+0

来自仅供链接的答案:http://enigmater.blogspot.ca/p/blog-page_13.html?m = 1描述了GPIO引脚,可能会有用。 –

回答

4

看着BCM2835文档 - 博通BCM2835 ARM外设https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

是看来你的代码是基于树莓派,汇编语言,布鲁斯·史密斯。

我不是为什么我们要清除3位,因为文档说是设置输入。

***我确认它可以在我的RPi 2 B上工作,引脚上有3伏以上的电压。

目标销= 18 集GPIO FUNC: 26-24 FSEL18 FSEL18 - 功能选择18 R/W 0 其在GPIO备用功能选择寄存器1 所以:0X 7E20 0004 GPFSEL1 GPIO功能选择1条32r上/ W |偏移#为0x4

init_output:      // init for OUTPUT 
      ldr r3, [sp, #8]  // virt GPIO base 
      add r3, r3, #0x4  // offset to GPFSEL1 
      ldr r2, [r3]   // get contents of GPFSEL1 
      orr r2, r2, #0b001<<18 // set 3 bits re FSEL18 output 
      str r2, [r3]   // store set bits at GPFSEL1 

设置输出: 31-0 SETN(N = 0..31)0 =无效果1 =设置GPIO管脚n个R/W 0 其在GPIO输出设定寄存器0 因此:0x7E20 001C GPSET0 GPIO引脚输出设置0 32 W |偏移#为0x1C == 28

set_pin: 
     ldr r3, [sp, #8]   // virt GPIO base 
     add r3, r3, #0x1C   // GPSET0 
     ldr r2, [r3]    // get content of GPSET0 
     orr r2, r2, #1<<18   // set PIN 18 
     str r2,[r3]     // set PIN 18 @ GPSET0 

清除输出: 31-0 CLRN(N = 0..31)0 =无效果1 =清除GPIO引脚n个R/W 0 哪个GPIO输出清除寄存器0 So:0x7E20 0028 GPCLR0 GPIO引脚输出清零0 32 W |偏移0×28#40 ==

clear_pin: 
     ldr r3, [sp, #8]   // virt GPIO base 
     add r3, r3, #0x28   // GPCLR0 
     ldr r2, [r3]    // get content of GPCLR0 
     orr r2, r2, #1<<18   // set PIN 18 
     str r2,[r3]     // set PIN 18 @ GPCLR0    
+0

感谢您的帮助,我很感激! – fdbdcbc

2

这里的一个修订后的备选答案,在C.

作为澄清InfinitelyManic溶液工作得很好上的丕0运行弹力。在运行Jessie(K 4.9.35)或Openelec 8.0.4 = Kodi 17.3(内核4.9.30)的Pi-1B上,“set”操作工作正常,但“清除”操作清除了引脚但挂起了系统。

我使用从fdbdcbc和InfinitelyManic合并的汇编代码作为C程序的原型来做同样的事情。我的希望是C版本可能更便携。它使用上述技术对引脚进行直接内存控制(不使用wiringPi或其他库,我无法将其加载到Openelec上)。在调试过程中,我发现设置SET/CLR寄存器只需要写入表示要设置的引脚的一位/ clr;复制和重写寄存器导致系统在CLR上挂起。

我已经评论了很多,并且使用标准的C包含定义将一些从汇编器到C的代码进行了逆向工程。

下面的代码适用于运行Stretch的Pi-0(版本号920093)和运行Jessie或Openelec的Pi-1B(版本号000e)。

/* gblink.c 
    Procedures to set and clear Raspberry Pi GPIO pins through direct 
    memory access rather than through major libraries (such as wiringPi). 

    Advantages: speed and portability (requiring no special libraries). 
    Disadvantage: limited capability, long-term maintenance, flexibility. 

    If running on Pi-2, change ref to gpioBASE to gpioBASE2 in mmap call 
    Don't know what to use if Pi-3 

    With testing program. 
    Compile as 
     gcc -o gblink gblink.c 
    run as 
     sudo gblink <pin number> 

    Written by hdtodd, 12 Dec 2017, based on Stack Overflow ARM 
    assembler examples by InfinitelyManic on 10 Mar 2017: 
    https://stackoverflow.com/questions/42702056/ 
     flash-raspberry-pi-led-using-arm-assembly 
    Corrected 23 Dec 2017 to write rather than rewrite SET/CLR registers. 

    See also: 
     https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf 
*/ 

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <unistd.h> 
#include <ctype.h> 
#include <strings.h> 
#include <time.h> 
#include <signal.h> 
#include <sys/mman.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <errno.h> 

#define memFile "/dev/mem"   // memory device for mapping 
#define memFlags O_RDWR | O_SYNC  // flags for opening mem 
#define baseGPIO 0x20200000   // GPIO_Base address for Pi 1 
#define baseGPIO2 0x3F200000   // GPIO_Base address for Pi 2 
#define pinMAX  32     // max # of GPIO pins we'll support (for simplicity) 
#define pinsPerSEL 10     // 3 bits/field, 10 fields/register in GFSELn's 
#define outMask 0b111     // 3-bit field mask to clear GFSEL register 
#define outEnable 0b001     // 3-bit pattern for GFSEL registers to enable output 
#define offsetSET 7     // word-address offset to GPSET0 word to set pin (0x1c bytes) 
#define offsetCLR 10     // word-address offset to GPCLR0 word to clear pin (0x28 bytes) 
typedef enum {false=0, true=~0} boolean; 

static uint32_t *vAddrGPIO;    // mapped address to physical GPIO register base 
int  memFH;       // file handle for /dev/mem 
boolean alreadyMapped = false; 
boolean keepBlinking = true; 
struct sigaction act;     // catch CNTL-C to terminate cleanly 
uint32_t gpFSEL, fSELn;     // offset to GPIO Function SELect register for this 
             // pin & Field SEL loc in that register for this pin 
boolean gblinkDebug = false;   // enables printing debug information 

// cntlCHandler() triggers end of main loop if a ^C is received on controlling terminal 
void cntlCHandler(int sigType) { 
    keepBlinking = false; 
}; 

// enableGPIO maps memory to GPIO registers and sets pin 'pinNum' to be an output pin 
boolean enableGPIO(int pinNum) { 
    if (pinNum < 0 || pinNum > pinMAX-1) { 
    printf("[enableGPIO:] Requested pin # %d out of range 0 to %d\n", pinNum, pinMAX-1); 
    return false; 
    }; 

    // if we haven't already done it, map GPIO registers to our memory space 
    if (!alreadyMapped) { 
    if ((memFH = open(memFile,memFlags)) < 0) { 
     perror("[?enableGPIO:] Can't open '/dev/mem' file; exiting. Rerun as root?"); 
     exit(EXIT_FAILURE); 
    } 
    vAddrGPIO = (uint32_t *)mmap(NULL,getpagesize(),PROT_WRITE|PROT_READ,MAP_SHARED,memFH,baseGPIO); 
    if (vAddrGPIO == MAP_FAILED) { 
     perror("[?enableGPIO:] Can't map to GPIO register addresses; exiting. Rerun as root?"); 
     exit(EXIT_FAILURE); 
    }; 
    alreadyMapped = true; 
    }; 

    // locate the register and field for this pin and set it for output 
    if (gblinkDebug) 
    printf("[enableGPIO] Memory-mapped base address of GPIO register set is vAddrGPIO = %p\n", vAddrGPIO); 
    gpFSEL = pinNum/pinsPerSEL;   // offset to register for this pin 
    fSELn = 3*(pinNum%pinsPerSEL);  // location in register of 3-bit field for this pin 
    if (gblinkDebug) { 
    printf("[enableGPIO] GPIO pin mapped to GPFSEL%d and 3-bit FSEL field at bit %d in that register.\n", gpFSEL, fSELn); 
    printf("[enableGPIO] Will use gpioSEL register at %p\n", (vAddrGPIO+gpFSEL)); 
    }; 
    *(vAddrGPIO+gpFSEL) = (*(vAddrGPIO+gpFSEL) & ~(outMask<<fSELn)) | (outEnable<<fSELn); 
    if (gblinkDebug) 
    printf("[enableGPIO] After setting, GPFSEL%d register contents = %x\n", gpFSEL, *(vAddrGPIO+gpFSEL)); 
    return true; 
}; 

// sets the pin voltage high (+5v) 
boolean setOutput(int pinNum) { 
    if (pinNum < 0 || pinNum >= pinMAX) { 
    printf("[setOutput] Requested pin # %d is outside of allowed range 0 to %d\n", pinNum, pinMAX-1); 
    return false; 
    }; 
    if (gblinkDebug) 
    printf("[setOutput] Writing to pin field %d at GPIO register address %p\n", pinNum, (vAddrGPIO+offsetSET)); 
    *(vAddrGPIO+offsetSET) = 1<<pinNum; // write to "set" bit 
    return true; 
}; 

// sets the pin voltage low (+0v) 
boolean clrOutput(int pinNum) { 
    if (pinNum < 0 || pinNum >= pinMAX) { 
    printf("[clrOutput] Requested pin # %d is outside of allowed range 0 to %d\n", pinNum, pinMAX-1); 
    return false; 
    }; 
    if (gblinkDebug) 
    printf("[clrOutput] Writing to pin field %d at GPIO register address %p\n", pinNum, (vAddrGPIO+offsetCLR)); 
    *(vAddrGPIO+offsetCLR) = 1<<pinNum; // write to "clr" bit 
    return true; 
}; 

void main(int argc, char *argv[]) { 
    int pinNum; 

    // validate arguments and/or provide help 
    if ((argc < 2) || (strcasecmp(argv[1],"-h")==0) || !isdigit(argv[1][0])) { 
    printf("gblink: program to test direct memory control of GPIO pins by toggling pin voltage high<-->low\n"); 
    printf("  usage: gblink [ pin number, >=0, <=%d ]\n", pinMAX-1); 
    printf("    where pin number is BCM2835 numbering system. Not GPIO or Pi!\n"); 
    printf("  Install wiringPi and run 'gpio readall' to see pin map\n"); 
    printf("  Debug with 'sudo GBLINK_DEBUG=1 gblink <pin number>'\n"); 
    return; 
    }; 

    // we need to run as root to open and map memory 
    if (geteuid() != 0) { 
    fprintf(stderr, "[?%s:] Must be run as root. Try 'sudo %s <pin number>'\n", argv[0], argv[0]); 
    exit(EXIT_FAILURE); 
    }; 

    // do we want to provide debugging information along the way? 
    if (getenv("GBLINK_DEBUG") != NULL) { 
    printf("[gblink:] Debug mode enabled\n"); 
    gblinkDebug = true; 
    }; 

    // OK, ready to go. Catch ^C's to exit cleanly 
    act.sa_handler = cntlCHandler; 
    sigaction(SIGINT, &act, NULL); 

    // which pin are we working with? 
    pinNum = atoi(argv[1]); 
    if (gblinkDebug) 
    printf("[gblink:] Using GPIO pin %d\n", pinNum); 

    // enable that pin for output 
    if (!enableGPIO(pinNum)) { 
    perror("[gblink:] Can't enable GPIO output. "); 
    exit(EXIT_FAILURE); 
    }; 

    // until we're told to stop, just keep blinking 
    while (keepBlinking) { 
    if (gblinkDebug) printf("[gblink:] Turning pin ON\n"); 
    setOutput(pinNum); 
    sleep(2); 
    if (gblinkDebug) printf("[gblink:] Turning pin OFF\n"); 
    clrOutput(pinNum); 
    sleep(5); 
    }; 

    /* ^C terminate the loop above and drops into this cleanup code */ 
    printf("[gblink:] CNTL-C halt. Clean up and exit.\n"); 
    if (alreadyMapped) { 
    munmap(vAddrGPIO,getpagesize()); 
    close(memFH); 
    }; 

    return; 
}; 
+0

为了更好地处理错误,你可以用'perror'而不是'printf'来解码'errno'。以非root用户身份运行,您可能会得到“错误的文件句柄”,因为memFH将为'-1'。当然,使用'strace ./a.out'会给你更多的信息,并且是用这样的简单工具编写完整的错误检测的懒惰选择:P –

+0

'exit;'不退出你的进程,但它确实编译。这与编写'(void)&exit'是一回事,因为'exit'是一个库函数。我想你想'退出(1);''你可能应该从'-Wall'得到一个警告,说明这个语句没有效果,因为你没有把它抛出去。 –

+1

谢谢,彼得。这两个建议都包含在内,我发现了挂起的来源。 –

2

我相信上面的InfinitelyManic的代码需要一个小的修正。

在set_pin:/ clear_pin:段,而不是

//  ldr r2, [r3]   // get content of GPCLR0                    
//  orr r2, r2, #1<<23 // set PIN 23 to 1 ==> set pin voltage to low 

相信单行,以取代他们应该

mov r2, #1<<23   // set PIN 23 to 1 ==> set pin voltage to low (clear) 

之前

str r2,[r3]    // set PIN 23 @ GPCLR0 
的写寄存器

也就是说,写入而不是复制并重写该寄存器。 (我已经将电流限制电阻连接到了23而不是像InfinitelyManic那样使用引脚18,因此对于SEL和SET/CLR寄存器参考都要相应地调整您自己的代码。)

此更改只设置SET中的一位/ CLR寄存器(写入其他位为0)而不是复制,然后重写整个寄存器(可能会再次设置其他位)。我原以为重写一些其他代码正在管理和已经设置好的位是没有害处的,但在Raspberry Pi的所有模型中都不是这样。数据手册非常清楚:将1写入寄存器插槽会导致该引脚电压被设置或复位。不清楚的是,在不同型号的Pi上,该寄存器中表示的其他引脚可能会连接到其他硬件,并且再次设置/清除它们可能会在这些设备中产生意想不到的后果(显然)。

InfinitelyManic的复制和重写寄存器的代码在Pi-0模型(修订代码920093)上工作,我的C语言翻译(之前的发布,随后编辑为此更改)也是如此。但在Pi-1B(修订版代码000e)上,SET工作但CLR失败。或者,CLR将该引脚上的电压重置为0,但也会挂起系统,从而强制循环上电。

很多实验导致了这样一种说法 - 复制和重写vs只写 - 作为问题的根源。测试并验证了运行Stretch的Pi-0,K 4.9.66+,Pi-1B运行的Jessie,K 4.9.35和运行Openelec 8.0.4的Pi-1B(又名Kodi 17.3,K 4.9.30)上的两个代码版本。这两种形式的代码都在Pi-0上运行;复制和改写版本将Jessie和Openelec都挂在了Pi-1B上。

+0

我认为ORR(ing)为了设置/清除目标位不会设置/清除任何其他位。 – InfinitelyManic

+0

向SET或CLR寄存器中的任何位写入1会分别将相应的引脚设置为高电平或低电平。我曾经想过,通过复制SET或CLR寄存器,将该位设置为1,然后重写寄存器,就可以重写这些位,因为你不会除了你想要的位/针之外别无所物。而这似乎在Pi-0上工作得很好。但是Pi-1B似乎有些不同,并且重写CLR寄存器中的其他一些位似乎会影响BCM连接的其他硬件。 –

+0

我的设备是Pi 2 Model B Rev 1.1,运行Debian;技术上是BCM2836。我不认为BCM2836文档当时可用/发布。尽管如此,我的Pi没有受到锁定。好工作。 – InfinitelyManic