Advanced Raspberry Pi Development – Writing IO Port Driver Code

For beginners, this is a comprehensive practical course on IoT based on Linux. For details, please click on the article below:

A Comprehensive IoT Project Suitable for Beginners Learning Linux”

Microcomputer Bus Address

Address Bus:

  • Baidu Encyclopedia Explanation: The address bus (Address Bus; also known as: address bus) belongs to a part of the computer bus, which is used by the CPU or units with DMA capability to communicate the physical addresses of the memory components/locations that these units want to access (read/write).
  • Address Bus = the range of memory that the CPU can access: To explain the address bus with a phenomenon: A 32-bit Windows 7 system is installed, and clearly the memory stick is 8G, but the system only recognizes 3.8G. Only by installing a 64-bit system can it recognize 8G. A 32-bit system can represent/access 4,294,967,296 bits.
kbit——mbit——gbit Difference of 1024
bit 4,294,967,296
kbit 4,194,304 K
mbit 4,096 M
gbit 4 G
  • Address Bus = the CPU relies on the data transmitted by the address bus to find external memory units. If the CPU has 8 address lines, each line transmits 0 or 1, then the range of data transmitted is 00000000~ 11111111. Each value corresponds to a memory unit in memory, so it can find the memory unit numbered 00000000~ 11111111. If the transmitted data is 00110011, it will find the memory unit 00110011. If the transmitted data is 10110111, it will find the memory unit 10110111. If the number is not within the range of [00000000, 11111111], the CPU will not be able to find it, for example, the 100000000 memory unit cannot be found by the CPU. The addressing capability is the number of memory units the CPU can find, there are a total of 256 memory units from 00000000~11111111, and each memory unit is 1 byte, so the size of these 256 memory units is 256 bytes.
  • The CPU specifies the storage unit through the address bus.
  • **==The address bus determines the maximum memory space that the CPU can access==. e.g.: 10 address lines can access a maximum memory of 1024** (2 to the power of 10) bits of binary data (1B)
  • The address bus is the sum of the number of address lines. If the CPU’s address bus width is 32 bits, then the CPU’s addressing range is 4G (2 to the power of 32 bits), so it supports a maximum of 4G memory.
  • For example, in the phenomenon mentioned above: A 32-bit Windows 7 system is installed, and clearly the memory stick is 8G, but the system only recognizes 3.8G. The CPU’s access range with a 32-bit operating system is 2^32 bits, which is 4194304 kbit, equal to 4096 Mbit, which equals 4G. The Raspberry Pi is also 32-bit with 1G of memory, but it can only access 949M, with the rest being used for other purposes.

Data Bus:

  • The CPU addresses via the address bus, and then exchanges information with external devices via the data bus.
  • It is the channel for data transfer between the CPU and memory or other devices.
  • The width of the data bus determines the speed of data transfer between the CPU and the outside world.
  • Each transmission line can only transmit 1 bit of binary data at a time. e.g.: 8 data lines can transmit an 8-bit binary data (i.e., one byte) at once.
  • The data bus is the sum of the number of data lines, and the number of bits in the data bus determines the amount of information that the CPU can exchange in a single communication.

The impact of the width of the data bus on CPU performance:

  • First, the speed of the bus (i.e., the CPU’s main frequency, one of the performance indicators of the CPU) determines the speed of information exchange between the CPU and peripherals.
  • Secondly, the width of the data bus is also one of the parameters that indicate CPU performance (usually, when we say “64-bit CPU”, we mean the width of the CPU’s data bus is 64 bits). For example: A CPU with a 64-bit data bus can retrieve 64 bits of data at once, while a CPU with an 8-bit data bus can only retrieve 8 bits of data at once. At the same frequency, a CPU with an 8-bit data bus must retrieve data 8 times to match the amount of data retrieved by a 64-bit data bus at once, resulting in an 8-fold performance difference in data retrieval. Moreover, the bit width of the registers in the CPU usually matches the width of the data bus, so in terms of data processing, a 64-bit CPU is much faster than an 8-bit CPU.
  • The number of bits in the CPU’s address bus and data bus can differ (a typical example is the 51 microcontroller), but generally they are the same. A 16-bit machine has 16 data lines and 20 address lines, can access 1M (2 to the power of 20), a 32-bit machine has 32 data lines and 32 address lines, can access 4G (2 to the power of 32), and a 64-bit machine indeed has 64 data lines.

Physical Address (PA)

  • Baidu Encyclopedia Explanation: The physical address of the network card is the actual address corresponding to the storage unit in memory, which is called the physical address, corresponding to the logical address. The physical address of the network card is usually written into the network card’s EPROM (a type of flash memory chip, usually erasable by a program) by the manufacturer, and it stores the address that truly identifies the computer sending the data and the host receiving the data during data transmission.

  • The physical address mentioned here is the actual address of the memory unit in memory, not the address of other electronic components connected to the external bus! **==The physical address is relatively easy to understand; the physical address is simply the number assigned to each memory unit in memory==**. This number is sequentially arranged, and the size of the physical address determines how many memory units are in memory, and the size of the physical address is determined by the width of the address bus! The physical address is the actual or absolute hardware address

Virtual Address (VA)

  • Virtual addresses are used by Windows programs running in 386 protected mode, so the address used by the program to access memory ==logical address (based on algorithm address [software-level address: false address]) is called virtual address==, similar to the segmented address in real address mode, the virtual address can also be written in the form of “segment: offset”, where the segment refers to the segment selector. Linux does not have various protection modes; it originally uses virtual addresses.
  • The virtual address is a concept in the CPU’s protected mode. The protected mode is the operating mode of the x86-compatible CPU series 80286 and later. After the CPU completes booting the operating system kernel, the operating system kernel enters a CPU protected mode, also known as virtual memory management. After this, all programs run in virtual memory, and all addresses in virtual memory are indirect. Therefore, sometimes you can see that a virtual address corresponds to different physical addresses. For example, the call function entry virtual address in process A is 0x001, and in process B it is also 0x001, but their corresponding physical addresses are different. The operating system adopts this memory management method.
  • It prevents programs from writing data to physical addresses and causing unnecessary issues. For example, if the physical address of process A is known, writing data to that address would cause problems for process A. Programs running in virtual memory never know which physical address segment they are in! Now, even if the operating system is running in protected mode, knowing the physical address of other processes does not allow writing to it! However, functions left by the operating system can be used to gain control permissions over the virtual address space of that process and write specified data.
  • Virtual memory management uses a method of “robbing Peter to pay Paul”; thus, the virtual memory is often much larger than the physical memory. Before entering virtual mode, the CPU and Bootloader (the BootLoader runs before the operating system kernel runs. It can initialize hardware devices, create a memory space mapping diagram, thus bringing the system’s software and hardware environment to a suitable state, preparing the correct environment for the final call to the operating system kernel) run in real mode, directly operating on physical addresses! Virtual memory also has page management, which is to ensure that there are no memory fragments in the memory. After the operating system kernel initializes the page table in memory, the CPU’s page flag is set, which is for the MMU!
  • MMU stands for Memory Management Unit, which is the control line used in the central processing unit (CPU) to manage virtual storage and physical storage, and is responsible for mapping virtual addresses to physical addresses, as well as providing hardware mechanisms for memory access authorization in multi-user, multi-process operating systems. Its functions include address translation and memory protection. ==The MMU translates virtual addresses into physical addresses.==

Blog post introducing various addresses:

Physical addresses, virtual addresses, bus addresses, differences between physical addresses and bus addresses

Page Table (MMU Unit)

Paging Management:Advanced Raspberry Pi Development - Writing IO Port Driver Code

  • Memory paging is actually what we call 4G space. All memory is divided by the operating system kernel into pages of 4G. When our program runs, it is loaded into the 4G space in memory. In reality, saying there is 4G does not mean there is actually 4G space. A small part of the 4G space is mapped to physical memory, or mapped to files on the hard disk (fopen), or not mapped at all. There is also a part in memory that will be divided into stack and heap, and there are large portions of memory that are not mapped. Similarly, physical memory is also paginated to create a mapping relationship with virtual memory. The algorithm that maps virtual addresses to physical addresses (page table) determines which position the virtual address is mapped to the physical address, and the page table is managed by the MMU (the paging memory management unit). After designing the page table, the MMU executes the mapping of virtual addresses to physical addresses.
  • In reality, there is only a 3G user space. If your memory is 4G, then 1G is allocated for the operating system kernel. The so-called 4G space is merely an illusion created by the operating system based on virtual memory management, making it seem that each process has 4G of usable space! Here, I will mention the “robbing Peter to pay Paul” method. When our program is loaded into the 4G space, it actually cannot use the so-called 4G space. There are large amounts of memory that are idle. At this time, when other programs are loaded and find that there is not enough memory, they take the idle parts from the 4G space of other programs for use. Conversely, when this process runs out of memory, it will take the idle space from other processes for its own use. The bank works in a similar way!
  • When we need to operate on physical addresses, for example, an if statement needs to make different jumps based on the CPU’s status register, we must know the physical address. There is an electronic component in memory called MMU, which is responsible for querying the physical address corresponding to the virtual address from the memory mapping table that the operating system has initialized and converting it. For example, mov 0x4h8 is a virtual address. When we write data to this virtual address, the MMU will first check whether the CPU’s paging status register flag is set. If it is set, the MMU will capture this virtual address and query the corresponding physical address from the memory mapping table initialized by the operating system, converting it to the actual physical address, and then giving this actual physical address to the CPU to execute the corresponding command. Conversely, when the CPU reads data into memory, for instance, when process A needs to read data from a certain virtual address in memory, the instruction in process A provides a virtual address. The MMU will first check whether the CPU’s paging status register flag is set. If it is set, the MMU will capture this virtual address and convert it to the corresponding physical address, then submit it to the CPU, which will then go to memory to fetch the data!

For more detailed address issues, see here

BCM2835 Chip Manual

Below is an excerpt from the Raspberry Pi chip manual: Advanced Raspberry Pi Development - Writing IO Port Driver CodeBCM2835 is the CPU model of Raspberry Pi 3B, it is ARM-cortexA53 architecture, CPU Bus is the address bus, 00000000~FFFFFFFF is the range of CPU addressing (4G). DMA is a high-speed copying unit, the CPU can initiate DMA to directly perform data copying, which is a direct memory access unit. Physical Address (PA) 1G, Virtual Address (VA) 4G if the program is larger than the physical address of 1G, does it not run? No, it has a unit called MMU that maps physical addresses to virtual addresses, and the codes we operate on are basically in virtual addresses, with a mapping page table (mentioned above).

  • Through the chip manual, we can learn about the GPIO of Raspberry Pi: There are 54 general-purpose I/O GPIO lines, divided into two rows. The reserved functions are usually peripheral IO, and a peripheral device can appear in each bank, allowing flexible selection of IO voltage. Advanced Raspberry Pi Development - Writing IO Port Driver Code

  • GPIO has 41 registers, all access is 32 bits.

  • Description is the functional description of the register. GPFSEL0 (register name)GPIO Function Select 0 (function select: input or output); GPSET0 (register name)GPIO Pin Output Set 0 (set IO port to 0); GPSET1 (register name)GPIO Pin Output Set 1 (set IO port to 1); GPCLR0 (register name)GPIO Pin Output Clear 0 (clear 0) The address below is: bus address (not the actual physical address) Advanced Raspberry Pi Development - Writing IO Port Driver Code

  • FSELn represents GPIOn, Advanced Raspberry Pi Development - Writing IO Port Driver Code The figure below gives an example of function selection for the ninth pin, configuring registers 29-27 to set the corresponding function. According to the image, the register 0 at the bottom indicates that 0~9 use register 0. Advanced Raspberry Pi Development - Writing IO Port Driver Code

  • The output set register is used to set GPIO pins. The SET{n} field defines which GPIO pins to set, writing “0” to the field has no effect. If the GPIO pin is used in input mode (by default), then the value in the SET{n} field is ignored. However, if the pin is later defined as output, then the bit will be set according to the last set/clear operation. Separating set and clear eliminates the need for read-modify-write operations. GPSETn register is for setting the IO port to 1, setting the fourth pin through the fourth bit of the register. Advanced Raspberry Pi Development - Writing IO Port Driver Code

  • The output clear register is used to clear GPIO pins. The CLR{n} field defines which GPIO pins to clear, writing “0” to the field has no effect. If the GPIO is in input mode (default), then the value in the CLR{n} field is ignored. However, if the pin is later defined as output, then the bit will be set according to the last set/clear operation. Separating set and clear eliminates the need for read-modify-write operations. GPCLRn is the clear function register. Advanced Raspberry Pi Development - Writing IO Port Driver Code

Configuration of the Raspberry Pi pin 4 as an output pin:

Function selection Output/Input (GPIO Function Select Registers) 32 bits
14-12    001    =   GPIO Pin4 is an output

Just set bits 14-12 of GPFSL0 to 001. Just left shift 0x6 (which corresponds to binary 110) by 12 bits, then take the inverse and AND with GPFSL0 to configure bits 13 and 14 to 0, and then left shift 0x6 (which corresponds to binary 110) by 12 bits and OR with GPFSL0 to set bit 12 to 1.

  • Use the function copy_from_user() to read user input commands in the driver code, and use copy_to_user() to let the pin feedback its current state, allowing the user to read it.

If you want to find the Raspberry Pi pin, click here

Raspberry Pi IO Control Driver Code:

ioremap, iounmap:

1. Generally, our peripherals are operated by reading and writing registers on the device, typically including control registers, status registers, and data registers. The registers of peripherals are usually continuously addressed, and depending on the CPU architecture, there are two ways for the CPU to address IO ports:

  • IO mapped (IO-mapped): A typical example is that the X86 processor has a separate address space specifically for peripherals, known as the “IO port space” or “IO address space”. In this case, the CPU can access this “IO port space” through specific instructions (such as IN and OUT for X86).
  • Memory mapped (memory-mapped): CPUs with RISC instruction systems generally only implement one physical address space, where peripheral IO ports become part of memory. In this case, the CPU can access the IO ports of peripherals as easily as accessing its own memory, without needing to set up special instructions for access. In driver development, memory mapping is generally used.

2. In driver development, generally speaking, the physical addresses of the peripheral IO memory resources are known, determined by hardware design. However, the CPU does not pre-assign virtual address values for these known peripheral IO memory resources, so the driver program cannot directly access IO memory through the physical addresses of the peripherals, but must map them to the virtual address space (via page tables) before it can access these IO memory using memory instructions based on the kernel-mapped virtual addresses and perform operations on them.

3. In the Linux kernel’s io.h header file, the ioremap() function is declared to map IO memory resources to the core virtual address space (3Gb~4GB). When not needed, it can be unmapped using iounmap(). These two functions are in the mm/ioremap.c file:

Start mapping: void* ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
//Mapping a device means associating a segment of user space address with device memory, allowing the program to read or write in the allocated address range, which effectively accesses the device.
The first parameter is the starting address of the mapping.
The second parameter is the length of the mapping.
The third parameter is determined by your hardware characteristics.
For example, if you are only mapping a 32-bit register, a length of 4 is sufficient.
(Here, the function setting registers for Raspberry Pi IO ports are all 32-bit registers, so allocating four bytes is enough.)

For example: GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
These three lines are setting the register addresses; volatile acts as an instruction keyword to ensure that this instruction is not omitted due to compiler optimizations, and requires direct reading of the value each time.
The ioremap function converts physical addresses to virtual addresses, mapping IO port registers into ordinary memory units for access.

Unmap: void iounmap(void* addr)//Cancel the IO addresses mapped by ioremap
For example:
     iounmap(GPFSEL0);
     iounmap(GPSET0);
     iounmap(GPCLR0); //Release address mapping when unloading the driver

Raspberry Pi IO Port 4 Driver Code:

#include <linux/fs.h>            //file_operations declaration
#include <linux/module.h>    //module_init  module_exit declaration
#include <linux/init.h>      //__init  __exit macro definition declaration
#include <linux/device.h>        //class  device declaration
#include <linux/uaccess.h>   //copy_from_user header file
#include <linux/types.h>     //device number  dev_t type declaration
#include <asm/io.h>          //ioremap iounmap header file

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;                //device number
static int major =231;             //main device number
static int minor =0;               //sub device number
static char *module_name="pin4";   //module name

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0   = NULL;
volatile unsigned int* GPCLR0   = NULL;
//These three lines are setting the register addresses
//volatile acts as an instruction keyword to ensure that this instruction is not omitted due to compiler optimizations, and requires direct reading of the value each time

//led_open function
static int pin4_open(struct inode *inode,struct file *file)
{
        printk("pin4_open\n");  //Kernel print function similar to printf
       
        //Configure pin4 as an output pin        
        *GPFSEL0 &=~(0x6 <<12); // Set bits 13 and 14 to 0  
        //0x6 is 110  <<12 left shift 12 bits ~take inverse & and
        *GPFSEL0 |=~(0x1 <<12); //Set bit 12 to 1   | or operation
        
        return 0;

}
//read function
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{
        printk("pin4_read\n");  //Kernel print function similar to printf

        return 0;
}

//led_write function
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
        int usercmd;
        printk("pin4_write\n");  //Kernel print function similar to printf
        
        //Get the value from the upper layer write function                
        copy_from_user(&usercmd,buf,count); //Read user input command into usercmd
        //Operate on the IO port based on the value, high or low level
        if(usercmd == 1){
                printk("set 1\n");
                *GPSET0 |= 0x01 << 4;
        }
        else if(usercmd == 0){
                printk("set 0\n");
                *GPCLR0 |= 0x01 << 4;
        }
        else{
                printk("undo\n");
        }
        return 0;
}

static struct file_operations pin4_fops = {

        .owner = THIS_MODULE,
        .open  = pin4_open,
        .write = pin4_write,
        .read  = pin4_read,
};

//static limits the scope of this structure to this file only.
int __init pin4_drv_init(void)   //Real driver entry
{
        int ret;
        devno = MKDEV(major,minor);  //Create device number
        ret   = register_chrdev(major, module_name,&pin4_fops);  //Register driver  Inform the kernel to add this driver to the kernel driver linked list
        pin4_class=class_create(THIS_MODULE,"myfirstdemo");//Automatically create device in dev directory
        pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //Create device file
        
        GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
        GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
        GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);

        printk("insmod driver pin4 success\n");
        return 0;
}

void __exit pin4_drv_exit(void)
{

        iounmap(GPFSEL0);
        iounmap(GPSET0);
        iounmap(GPCLR0); //Release address mapping when unloading the driver

        device_destroy(pin4_class,devno);
        class_destroy(pin4_class);
        unregister_chrdev(major, module_name);  //Unload driver
}
module_init(pin4_drv_init);  //Entry point, when the kernel loads the driver, this macro will be called to invoke pin4_drv_init
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

1. Set the register address

Set the register address, but this way of writing is problematic; we mentioned earlier that in the kernel, code and upper-level code access virtual addresses (VA), and now we are setting physical addresses. **==Must convert physical addresses to virtual addresses==**

//These three lines are setting the register addresses
volatile unsigned int* GPFSEL0 = volatile (unsigned int *)0x3f200000;
volatile unsigned int* GPSET0  = volatile (unsigned int *)0x3f20001C;
volatile unsigned int* GPCLR0  = volatile (unsigned int *)0x3f200028;
//volatile acts as an instruction keyword to ensure that this instruction is not omitted due to compiler optimizations, and requires direct reading of the value each time

We first initialize the address

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0   = NULL;
volatile unsigned int* GPCLR0   = NULL;

Then assign values in the initialization int __init pin4_drv_init(void) //Real driver entry.

//Integer 11
//0xb 11 00010001
Even in hexadecimal, the left side is volatile unsigned int* GPFSEL0, and the right side is forcibly converted to (volatile unsigned int*)

volatile acts as an instruction keyword to ensure that this instruction will not be omitted due to compiler optimizations and requires direct reading of the value each time because it is an address and I want it to be unsigned unsigned

When writing driver programs, the starting address of the IO space is 0x3f000000, plus the GPIO offset 0x2000000, so the physical address of GPIO should start from 0x3f200000 Advanced Raspberry Pi Development - Writing IO Port Driver Code Then perform Linux system MMU memory virtualization management, mapping to virtual addresses. A function ioremap is used.

//Convert physical address to virtual address, mapping IO port registers into ordinary memory units for access
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);   //4 is 4 bytes

2. Configure pin 4 as an output pin

Advanced Raspberry Pi Development - Writing IO Port Driver CodeConfigure pin 4 as an output pin, bits 12-14 set to 001

31 30 ······14 13 12 11 10 9 8 7 6 5 4 3 2 1 
0  0  ······0  0  1  0  0  0 0 0 0 0 0 0 0 0 
 //Configure pin 4 as an output pin, bits 12-14 set to 001  
  *GPFSEL0 &=~(0x6 <<12); // Set bits 13 and 14 to 0  
 //0x6 is 110  <<12 left shift 12 bits ~take inverse & and
  *GPFSEL0 |=~(0x1 <<12); //Set bit 12 to 1   | or operation

Forget and use bitwise AND and OR, click here.

3. Get the value from the upper layer write function and operate on the IO port based on the value, high or low level

Use copy_form_user(char *buf , user_buf , count) to get the value from the upper layer write function.

int usercmd;
copy_from_user(&usercmd,buf,count); //Read user input command into usercmd
       
 //Operate on the IO port based on the value, high or low level
 printk("get value\n");
        if(usercmd == 1){
                printk("set 1\n");        //Set to 1
                *GPSET0 |= 0x01 << 4;      // Use | or operation to avoid affecting other bits
                //Writing 1 means to enable setting 1 to make bit 4 high
        }
        else if(usercmd == 0){           
                printk("set 0\n");        //Clear to 0
                *GPCLR0 |= 0x01 << 4;      // Use | or operation to avoid affecting other bits
                //Writing 1 means to clear the 0 register, enabling to set 0 to make bit 4 low
        }
        else{
                printk("undo\n");  //Indicate that the command is not supported
        }

4. Unmap

Unmap: void iounmap(void* addr);//Cancel the IO addresses mapped by ioremap

void __exit pin4_drv_exit(void)
{
        iounmap(GPFSEL0);   //Unmap GPFSEL0
        iounmap(GPSET0);    //Unmap GPSET0
        iounmap(GPCLR0);   //Unmap GPCLR0

        device_destroy(pin4_class,devno);//Destroy device first
        class_destroy(pin4_class);//Then destroy class
        unregister_chrdev(major, module_name);  //Unload driver
}

Upper layer test code:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>

int main()
{
   int fd;
   int cmd;
   int data;

   fd = open("/dev/pin4",O_RDWR);
   if(fd<0){
           printf("open failed\n");
   }else{
    printf("open success\n");
   }
   
   printf("input command:1/0 \n 1:set pin4 high \n 0 :set pin4 low\n");
   scanf("%d",&cmd);

   printf("cmd = %d\n",cmd);
   fd = write(fd, &cmd,4); //cmd type is int, so write 4
}

Driver Unloading

After installing the driver, you can use the command: sudo rmmod + driver name (no need to write ko) to unload the driver.

IO Port Driver Code Compilation

  1. First, in the system directory /SYSTEM/linux-rpi-4.14.y, use the command: ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules to compile the driver module and generate the .ko file.
  2. Then send the compiled driver to the Raspberry Pi: scp ./drivers/char/pin4driver.ko [email protected]:/home/pi, and then compile the upper layer code: arm-linux-gnueabihf-gcc pin4test.c -o realtest, then send the test code to the Raspberry Pi: scp realtest [email protected]:/home/pi/
  3. Then on the Raspberry Pi, use the command: insmod pin4drive.ko to load the driver (then lsmod to see this driver),
  4. Then use the command: sudo chmod 666 /dev/pin4 to give pin4 this device access permissions. You can also use mk5sum on the virtual machine to check the value of the driver file and use this command on the Raspberry Pi to see if the values are consistent.
  5. dmesg to check kernel print information, as shown in the figure below: Advanced Raspberry Pi Development - Writing IO Port Driver Code
  6. Then run the test code. In a new window, use the command gpio readall to see that the BCM below the 4 pin mode is output mode, and the level is low or high (depending on the input from the upper layer code, input 0 is low, input 1 is high). Here, I input 0, as shown in the figure below: Advanced Raspberry Pi Development - Writing IO Port Driver Code

About the GPIO address issues in the driver code:

About the GPIO address issues in the driver code:Advanced Raspberry Pi Development - Writing IO Port Driver CodeAdvanced Raspberry Pi Development - Writing IO Port Driver Code

  • 7Ennnnn means from 7E00000 to 7EFFFFFF, F2000000 is the virtual address mapped from 3F000000, then 7E00000 and F200000 correspond, the addresses used in the chip manual correspond to the virtual address F200000 — 7E00000, the address offset in the chip manual is the same as the physical address offset.
  • According to the above image description, the physical address range of peripherals is from 0x3F000000 to 0x3FFFFFFF, so the actual physical address corresponding to 7E200000 should be 0x3F000000 + (7E200000-7E000000).
  • GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4); GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
  • 0x3f200000, 0x3f20001C, 0x3f200028 are physical addresses, the starting address of the Raspberry Pi’s peripheral space is 0x3f000000, and the corresponding offset of the registers can be seen from the chip manual, for example, the actual address of the GPFSEL0 register is 0x3f200000=0x3F000000 + (7E200000-7E000000).
  • Then, through the data manual, we can see that the bus addresses of the Raspberry Pi-related registers (and the mapped virtual addresses have a certain correspondence) can be obtained from the following image: Advanced Raspberry Pi Development - Writing IO Port Driver Code

end

One Linux

Follow, reply【1024】massive Linux materials are given away

Exciting article collection

Article recommendations

【Album】ARM
【Album】Fan Q&A
【Album】All original
AlbumLinuxIntroduction
AlbumComputer Network
AlbumLinux Driver
【Dry Goods】Embedded Driver Engineer Learning Path
【Dry Goods】All Knowledge Points of Linux Embedded – Mind Map

Click “Read Original” to see more sharing. Welcome to share, collect, like, and look

Leave a Comment

×