我正在尝试编写一个C程序,用于闪烁Beaglebone上的LED。我知道我可以使用sysfs方式...但是我想看看是否可以使用/ dev / mem获得与物理地址空间映射相同的结果。
我有一个头文件,beaglebone_gpio.h,内容如下:
#ifndef _BEAGLEBONE_GPIO_H_
#define _BEAGLEBONE_GPIO_H_
#define GPIO1_START_ADDR 0x4804C000
#define GPIO1_END_ADDR 0x4804DFFF
#define GPIO1_SIZE (GPIO1_END_ADDR - GPIO1_START_ADDR)
#define GPIO_OE 0x134
#define GPIO_SETDATAOUT 0x194
#define GPIO_CLEARDATAOUT 0x190
#define USR0_LED (1<<21)
#define USR1_LED (1<<22)
#define USR2_LED (1<<23)
#define USR3_LED (1<<24)
#endif
然后我有我的C程序,gpiotest.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "beaglebone_gpio.h"
int main(int argc, char *argv[]) {
volatile void *gpio_addr = NULL;
volatile unsigned int *gpio_oe_addr = NULL;
volatile unsigned int *gpio_setdataout_addr = NULL;
volatile unsigned int *gpio_cleardataout_addr = NULL;
unsigned int reg;
int fd = open("/dev/mem", O_RDWR);
printf("Mapping %X - %X (size: %X)\n", GPIO1_START_ADDR, GPIO1_END_ADDR, GPIO1_SIZE);
gpio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);
gpio_oe_addr = gpio_addr + GPIO_OE;
gpio_setdataout_addr = gpio_addr + GPIO_SETDATAOUT;
gpio_cleardataout_addr = gpio_addr + GPIO_CLEARDATAOUT;
if(gpio_addr == MAP_FAILED) {
printf("Unable to map GPIO\n");
exit(1);
}
printf("GPIO mapped to %p\n", gpio_addr);
printf("GPIO OE mapped to %p\n", gpio_oe_addr);
printf("GPIO SETDATAOUTADDR mapped to %p\n", gpio_setdataout_addr);
printf("GPIO CLEARDATAOUT mapped to %p\n", gpio_cleardataout_addr);
reg = *gpio_oe_addr;
printf("GPIO1 configuration: %X\n", reg);
reg = reg & (0xFFFFFFFF - USR1_LED);
*gpio_oe_addr = reg;
printf("GPIO1 configuration: %X\n", reg);
printf("Start blinking LED USR1\n");
while(1) {
printf("ON\n");
*gpio_setdataout_addr= USR1_LED;
sleep(1);
printf("OFF\n");
*gpio_cleardataout_addr = USR1_LED;
sleep(1);
}
close(fd);
return 0;
}
输出结果为:
Mapping 4804C000 - 4804DFFF (size: 1FFF)
GPIO mapped to 0x40225000
GPIO OE mapped to 40225134
GPIO SEDATAOUTADDR mapped to 0x40225194
GPIO CLEARDATAOUTADDR mapped to 0x40225190
GPIO1 configuration: FE1FFFFF
GPIO1 configuratino: FE1FFFFF
Start blinking LED USR1
ON
OFF
ON
OFF
...
但是我看不到指示灯闪烁。
从程序输出中可以看出配置正确,FE1FFFFF, 因为GPIO1_21,GPIO1_22,GPIO1_23和GPIO1_24被配置为输出,所以是一致的, 每个人驾驶一个LED。
有关于这个原因的想法吗?
答案 0 :(得分:12)
小心点。这可以乍一看,但它直接写入GPIO控制器驱动程序认为它拥有的寄存器。无论是在GPIO线路上还是在同一组中的GPIO上,都会导致奇怪和难以追踪的副作用。为了使其可靠地工作,您需要从内核GPIO驱动程序中禁用整个存储区。
答案 1 :(得分:8)
修复方法是:
pio_addr = mmap(0, GPIO1_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1_START_ADDR);
答案 2 :(得分:8)
原始帖子中显示的代码不适用于最新的Beaglebone Black及其相关的3.12内核。控制寄存器偏移似乎已经改变;验证以下代码是否正常工作:
#define GPIO0_BASE 0x44E07000
#define GPIO1_BASE 0x4804C000
#define GPIO2_BASE 0x481AC000
#define GPIO3_BASE 0x481AE000
#define GPIO_SIZE 0x00000FFF
// OE: 0 is output, 1 is input
#define GPIO_OE 0x14d
#define GPIO_IN 0x14e
#define GPIO_OUT 0x14f
#define USR0_LED (1<<21)
#define USR1_LED (1<<22)
#define USR2_LED (1<<23)
#define USR3_LED (1<<24)
int mem_fd;
char *gpio_mem, *gpio_map;
// I/O access
volatile unsigned *gpio;
static void io_setup(void)
{
// Enable all GPIO banks
// Without this, access to deactivated banks (i.e. those with no clock source set up) will (logically) fail with SIGBUS
// Idea taken from https://groups.google.com/forum/#!msg/beagleboard/OYFp4EXawiI/Mq6s3sg14HoJ
system("echo 5 > /sys/class/gpio/export");
system("echo 65 > /sys/class/gpio/export");
system("echo 105 > /sys/class/gpio/export");
/* open /dev/mem */
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
printf("can't open /dev/mem \n");
exit (-1);
}
/* mmap GPIO */
gpio_map = (char *)mmap(
0,
GPIO_SIZE,
PROT_READ|PROT_WRITE,
MAP_SHARED,
mem_fd,
GPIO1_BASE
);
if (gpio_map == MAP_FAILED) {
printf("mmap error %d\n", (int)gpio_map);
exit (-1);
}
// Always use the volatile pointer!
gpio = (volatile unsigned *)gpio_map;
// Get direction control register contents
unsigned int creg = *(gpio + GPIO_OE);
// Set outputs
creg = creg & (~USR0_LED);
creg = creg & (~USR1_LED);
creg = creg & (~USR2_LED);
creg = creg & (~USR3_LED);
// Set new direction control register contents
*(gpio + GPIO_OE) = creg;
}
int main(int argc, char **argv)
{
io_setup();
while (1) {
// Set LEDs
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR0_LED;
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR1_LED;
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR2_LED;
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) | USR3_LED;
sleep(1);
// Clear LEDs
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR0_LED);
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR1_LED);
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR2_LED);
*(gpio + GPIO_OUT) = *(gpio + GPIO_OUT) & (~USR3_LED);
sleep(1);
}
return 0;
}
我在这里发布,因为看起来mmap-ed访问在3.8内核中停止工作,从那以后没有人发布过工作解决方案。我不得不使用/ sys / class / gpio接口对控制寄存器偏移进行反向工程;我希望这个答案减少了将BeagleBone GPIO与新内核一起使用所带来的一些挫败感。
该代码根据BSD许可证授权 - 随时随地都可以使用。
编辑:user3078565在上面的答案中是正确的。您需要通过将其触发器设置为none或通过编辑设备树将它们完全隐藏在内核中来禁用默认用户LED GPIO驱动程序。如果不这样做,将导致LED按预期闪烁,但偶尔会被内核GPIO驱动程序覆盖其状态。
这不是我原始应用程序的问题,因为它使用GPIO bank 0,内核GPIO驱动程序很大程度上忽略了它。
答案 3 :(得分:4)
您可能还需要为要在用户空间中控制的任何硬件启用时钟。幸运的是,您可以使用dev / mem和mmap()来调整特定硬件的时钟控制寄存器,就像我编写的启用SPI0的代码一样: (定义值全部来自spruh73i.pdf寄存器描述)
#define CM_PER_BASE 0x44E00000 /* base address of clock control regs */
#define CM_PER_SPI0_CLKCTRL 0x4C /* offset of SPI0 clock control reg */
#define SPIO_CLKCTRL_MODE_ENABLE 2 /* value to enable SPI0 clock */
int mem; // handle for /dev/mem
int InitSlaveSPI(void) // maps the SPI hardware into user space
{
char *pClockControl; // pointer to clock controlregister block (virtualized by OS)
unsigned int value;
// Open /dev/mem:
if ((mem = open ("/dev/mem", O_RDWR | O_SYNC)) < 0)
{
printf("Cannot open /dev/mem\n");
return 1;
}
printf("Opened /dev/mem\n");
// map a pointer to the clock control block:
pClockControl = (char *)mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, mem, CM_PER_BASE);
if(pClockControl == (char *)0xFFFFFFFF)
{
printf("Memory map failed. error %i\n", (uint32_t)pClockControl);
close( mem );
return 2;
}
value = *(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL);
printf("CM_PER_SPI0_CLKCTRL was 0x%08X\n", value);
*(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL) = SPIO_CLKCTRL_MODE_ENABLE;
value = *(uint32_t *)(pClockControl + CM_PER_SPI0_CLKCTRL);
printf("CM_PER_SPI0_CLKCTRL now 0x%08X\n", value);
munmap( pClockControl, 4096 ); // free this memory map element
执行此代码片段后,我可以使用另一个mmap()指针访问SPI0寄存器。如果我没有先启用SPI0模块时钟,那么当我尝试访问这些SPI寄存器时会出现总线错误。启用时钟是持久的:一旦启用它,它将保持打开直到您禁用它,或者可能直到您使用spidev然后关闭它,或重新启动。因此,如果您的应用程序已完成启用的硬件,您可能需要禁用它以节省电量。
答案 4 :(得分:2)
启用GPIO银行....
enableClockModules () {
// Enable disabled GPIO module clocks.
if (mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
// Wait for the enable complete.
while (mapAddress[(CM_WKUP_GPIO0_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
}
if (mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
// Wait for the enable complete.
while (mapAddress[(CM_PER_GPIO1_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
}
if (mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
// Wait for the enable complete.
while (mapAddress[(CM_PER_GPIO2_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
}
if (mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK) {
mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] |= MODULEMODE_ENABLE;
// Wait for the enable complete.
while (mapAddress[(CM_PER_GPIO3_CLKCTRL - MMAP_OFFSET) / GPIO_REGISTER_SIZE] & IDLEST_MASK);
}
}
WHERE ...
MMAP_OFFSET = 0x44C00000
MMAP_SIZE = 0x481AEFFF - MMAP_OFFSET
GPIO_REGISTER_SIZE = 4
MODULEMODE_ENABLE = 0x02
IDLEST_MASK =(0x03&lt;&lt; 16)
CM_WKUP = 0x44E00400
CM_PER = 0x44E00000
CM_WKUP_GPIO0_CLKCTRL =(CM_WKUP + 0x8)
CM_PER_GPIO1_CLKCTRL =(CM_PER + 0xAC)
CM_PER_GPIO2_CLKCTRL =(CM_PER + 0xB0)
CM_PER_GPIO3_CLKCTRL =(CM_PER + 0xB4)
我写了一篇small library,或许你可能会感兴趣。目前只适用于数字引脚。
此致
答案 5 :(得分:1)
REF:madscientist159
// OE: 0 is output, 1 is input
#define GPIO_OE 0x14d
#define GPIO_IN 0x14e
#define GPIO_OUT 0x14f
should be
// OE: 0 is output, 1 is input
#define GPIO_OE 0x4d
#define GPIO_IN 0x4e
#define GPIO_OUT 0x4f
从unsigned char地址派生的unsigned int偏移地址
答案 6 :(得分:1)
这种异常似乎是AM335x芯片中不完整地址解码的假象。有意义的是,0x4D,0x4E和0x4F作为基址的偏移用于访问这些寄存器。 C / C ++指针算法将这些偏移乘以4,以产生0x134,0x138和0x13C的真实偏移。然而,一个阴影&#39;可以通过0x14D,0x14E和0x14F访问这些寄存器的副本。我已经确认这两组补偿都有效。我没有打扰尝试0x24D等。
可以使用偏移量0x64访问GPIO_CLEARDATAOUT寄存器,并使用偏移量0x65访问GPIO_SETDATAOUT寄存器。