2017-03-09 58 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