以BCM2835 C library 解說關於硬體暫存器的設定方式, 手邊請備妥Raspberry Pi "BCM2835 ARM Peripherals" 以方便查閱。底下以GPIO 方式 解說,, 先說明要寫入或讀取的方法, 不解釋其設定後的物理意義.
先跳到page 89 第六章 General Purpose I/O (GPIO) 閱讀, 將有助於下列code的理解.
// Physical addresses for various peripheral register sets
/// Base Physical Address of the BCM 2835 peripheral registers
#define BCM2835_PERI_BASE 0x20000000
/// Base Physical Address of the System Timer registers
#define BCM2835_ST_BASE
(BCM2835_PERI_BASE + 0x3000)
/// Base Physical Address of the Pads registers
#define BCM2835_GPIO_PADS (BCM2835_PERI_BASE + 0x100000)
/// Base Physical Address of the Clock/timer registers
#define BCM2835_CLOCK_BASE (BCM2835_PERI_BASE + 0x101000)
/// Base Physical Address of the GPIO registers
#define BCM2835_GPIO_BASE (BCM2835_PERI_BASE + 0x200000)
/// Base Physical Address of the SPI0 registers
#define BCM2835_SPI0_BASE (BCM2835_PERI_BASE + 0x204000)
.....................
/// \brief bcm2835PortFunction
/// Port function select modes for bcm2835_gpio_fsel()
typedef enum
{
BCM2835_GPIO_FSEL_INPT = 0b000, ///< Input
BCM2835_GPIO_FSEL_OUTP = 0b001, ///< Output
BCM2835_GPIO_FSEL_ALT0 = 0b100, ///< Alternate function 0
BCM2835_GPIO_FSEL_ALT1 = 0b101, ///< Alternate function 1
BCM2835_GPIO_FSEL_ALT2 = 0b110, ///< Alternate function 2
BCM2835_GPIO_FSEL_ALT3 = 0b111, ///< Alternate function 3
BCM2835_GPIO_FSEL_ALT4 = 0b011, ///< Alternate function 4
BCM2835_GPIO_FSEL_ALT5 = 0b010, ///< Alternate function 5
BCM2835_GPIO_FSEL_MASK = 0b111 ///< Function select bits mask
} bcm2835FunctionSelect;
// Low level convenience functions
//
// Defines for GPIO
// The BCM2835 has 54 GPIO pins.
// BCM2835 data sheet, Page 90 onwards.
/// GPIO register offsets from BCM2835_GPIO_BASE. Offsets into the GPIO Peripheral block in bytes per 6.1 Register View
#define BCM2835_GPFSEL0 0x0000 ///< GPIO Function Select 0
#define BCM2835_GPFSEL1 0x0004 ///< GPIO Function Select 1
#define BCM2835_GPFSEL2 0x0008 ///< GPIO Function Select 2
#define BCM2835_GPFSEL3 0x000c ///< GPIO Function Select 3
#define BCM2835_GPFSEL4 0x0010 ///< GPIO Function Select 4
#define BCM2835_GPFSEL5 0x0014 ///< GPIO Function Select 5
#define BCM2835_GPSET0 0x001c ///< GPIO Pin Output Set 0
#define BCM2835_GPSET1 0x0020 ///< GPIO Pin Output Set 1
#define BCM2835_GPCLR0 0x0028 ///< GPIO Pin Output Clear 0
#define BCM2835_GPCLR1 0x002c ///< GPIO Pin Output Clear 1
// Initialise this library.
int bcm2835_init(void)
{
if (debug)
{
bcm2835_pads = (uint32_t*)BCM2835_GPIO_PADS;
bcm2835_clk = (uint32_t*)BCM2835_CLOCK_BASE;
bcm2835_gpio = (uint32_t*)BCM2835_GPIO_BASE;
bcm2835_pwm = (uint32_t*)BCM2835_GPIO_PWM;
bcm2835_spi0 = (uint32_t*)BCM2835_SPI0_BASE;
bcm2835_bsc0 = (uint32_t*)BCM2835_BSC0_BASE;
bcm2835_bsc1 = (uint32_t*)BCM2835_BSC1_BASE;
bcm2835_st = (uint32_t*)BCM2835_ST_BASE;
return 1; // Success
}
int memfd = -1;
int ok = 0;
// Open the master /dev/memory device
if ((memfd = open("/dev/mem", O_RDWR | O_SYNC) ) < 0)
{
fprintf(stderr, "bcm2835_init: Unable to open /dev/mem: %s\n",
strerror(errno)) ;
goto exit;
}
// GPIO:
//mapmem內部即是進行mmap 系統呼叫, 將kenel map的硬體暫存器位址map到user-space裡,以方便進行 user space driver 開發
bcm2835_gpio = (volatile uint32_t *)mapmem("gpio", BCM2835_BLOCK_SIZE, memfd, BCM2835_GPIO_BASE);
if (bcm2835_gpio == MAP_FAILED) goto exit;
..............................
}
// Function select
// pin is a BCM2835 GPIO pin number NOT RPi pin number
// There are 6 control registers, each control the functions of a block
// of 10 pins.
// Each control register has 10 sets of 3 bits per GPIO pin:
//
// 000 = GPIO Pin X is an input
// 001 = GPIO Pin X is an output
// 100 = GPIO Pin X takes alternate function 0
// 101 = GPIO Pin X takes alternate function 1
// 110 = GPIO Pin X takes alternate function 2
// 111 = GPIO Pin X takes alternate function 3
// 011 = GPIO Pin X takes alternate function 4
// 010 = GPIO Pin X takes alternate function 5
//
// So the 3 bits for port X are:
// X / 10 + ((X % 10) * 3)
設定GPIO 功能
void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode)
{
//除以4 , 是因為bcm2835_gpio
為一個 int pointer, 所以加1 隠函移動4個byte了, 故除以4才能得 到正確位址
//除以10 是為了計算GPIO pin 號碼在那一個 Function selects 的暫存器裡
每一個暫存器(32 bit word) 管 GPIO 10 根 ,(3 bits per pin)
volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10);
// 找到pin 的暫存器, 還要再找到這個pin 對應的bit, 故要再 shift ((X % 10) * 3) bits
uint8_t shift = (
pin % 10) * 3;
uint32_t mask = BCM2835_GPIO_FSEL_MASK << shift;
uint32_t value =
mode << shift;
bcm2835_peri_set_bits(paddr, value, mask);
}
-----------------
// Set/clear only the bits in value covered by the mask
void bcm2835_peri_set_bits(
volatile uint32_t* paddr, uint32_t
value, uint32_t
mask)
{
// 先讀出原來的值 v, 再去改變要更新的欄位', 而其他欄位仍須保留原來的值
uint32_t v = *paddr;
// 要改的欄位先清成0 ==> v & ~mask, 再去設定其value ==> OR (value & mask)
v = (v & ~mask) | (value & mask);
*paddr=v;
}
Note:
volatile uint32_t* paddr ,volatile 這個修飾字使compiler 針對paddr所指的硬體位址的存取, 不要進行任何最佳化的處理,以免產生其他site effect.