树莓派BCM2835芯片手册导读以及IO口驱动代码编写

这篇具有很好参考价值的文章主要介绍了树莓派BCM2835芯片手册导读以及IO口驱动代码编写。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

驱动的两大利器:电路图(通过电路图去寻找寄存器)和芯片手册

树莓派使用的是BCM2835CPU(博通),芯片手册做到哪一章就看哪一章。芯片提供了54个IO口,对应了树莓派的 BCM.

bcm2835,驱动开发,Powered by 金山文档
bcm2835,驱动开发,Powered by 金山文档

一、树莓派GPIO口介绍

根据手册知gpio在第6章,我们来看第89页

bcm2835,驱动开发,Powered by 金山文档

GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此类推,GPFSEL5就是pin50~pin53的配置寄存器。

GPFSEL0

GPIO Function Select 0:功能选择输入或输出

GPSET0

GPIO Pin Output Set 0:输出0

GPSET1

GPIO Pin Output Set 1:输出1

GPCLR0

GPIO Pin Output Clear 0:清零

下图给出第九个引脚的功能选择示例,对寄存器的29-27进行配置,进而设置相应的功能。 根据图片下方的register 0表示0~9使用的是register 0这个寄存器。

bcm2835,驱动开发,Powered by 金山文档

输出集寄存器用于设置GPIO管脚。SET{n}字段定义,分别对GPIO引脚进行设置,将“0”写入字段没有作用。如果GPIO管脚为在输入(默认情况下)中使用,那么SET{n}字段中的值将被忽略。然而,如果引脚随后被定义为输出,那么位将被设置根据上次的设置/清除操作。分离集和明确功能取消对读-修改-写操作的需要。GPSETn寄存器为了使IO口设置为1,set4位设置第四个引脚,也就是寄存器的第四位

bcm2835,驱动开发,Powered by 金山文档

输出清除寄存器用于清除GPIO管脚。CLR{n}字段定义要清除各自的GPIO引脚,向字段写入“0”没有作用。如果的在输入(默认),然后在CLR{n}字段的值是忽略了。然而,如果引脚随后被定义为输出,那么位将被定义为输出根据上次的设置/清除操作进行设置。分隔集与清函数消除了读-修改-写操作的需要。GPCLRn是清零功能寄存器

bcm2835,驱动开发,Powered by 金山文档

二、代码的编写

编写驱动程序时,首先要知道它的地址,IO口空间的起始地址是0x3f00 0000(文档的起始地址是错误的),加上GPIO的偏移量0x200 0000,所以GPIO的物理地址应该是0x3f20 0000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。

bcm2835,驱动开发,Powered by 金山文档

上图尾部的偏移量是正确的,根据gpio的物理地址0x3f200 0000得到

GPFSEL0 0x3f20 0000 //IO口的初始的物理地址,而并不是手册里面的那个总线地址
GPSET0 0x3f20 001c  //地址通过查找芯片手册里面的对应的GPSET0 的总线地址的后两位决定是1c
GPCLR0 0x3f20 0028 //地址是查找GPCLR0在芯片手册里的总线地址确定的28,所以地址后两位是28

1.首先在原来的驱动框架上添加寄存器的定义

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

volatile关键字的作用:确保指令不会因编译器的优化而省略,且要求每次直接读值,在这里的意思就是确保地址不会被编译器更换。

2.然后在pin4_drv_init这个函数里面添加寄存器地址的配置

GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);

ioremap将物理地址转换为虚拟地址

我们前面讲到了在内核里代码和上层代码访问的是虚拟地址(VA),而现在设置的是物理地址,**所以必须把物理地址转换成虚拟地址**

按位与或按位或

  1. 配置引脚4为输出引脚,为了不影响其他引脚,需要使用与运算或运算。

bcm2835,驱动开发,Powered by 金山文档

根据图片可知14-12bit需配置成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

 //配置pin4引脚为输出引脚      bit 12-14  配置成001  
  *GPFSEL0 &= ~(0x6 <<12); // 把bit13 、bit14置为0  
 //0x6是110  <<12左移12位 ~取反 &按位与
  *GPFSEL0 |= (0x1 <<12); //把12置为1   |按位或

4.让引脚拉高

if(userCmd == 1)
    {
        printk("set 1\n");
        *GPSET0 |= (0x1 << 4); 
       //写1左移4位是让寄存器    开启置1  让bit4为高电平
    }
    else if(userCmd == 0)
    {
        printk("set 0\n");
        *GPCLR0 |= (0x1 << 4); 
     //写1左移4位是让清0寄存器 开启置0 让bit4为低电平
    }
    else
    {
        printk("nothing undo\n"); 
    }

补充:ioremap用法

开始映射:void* ioremap(unsigned long phys_addr , unsigned long size , unsigned long flags)
//用map映射一个设备意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或写入,实际上就是对设备的访问。
phys_addr:要映射的起始的IO地址
size:要映射的空间的大小
flags:要映射的IO空间和权限有关的标志

第二个参数怎么定?
这个由你的硬件特性决定。
比如,你只是映射一个32位寄存器,那么长度为4就足够了。
(这里树莓派IO口功能设置寄存器、IO口设置寄存器都是32位寄存器,所以分配四个字节就够了)

比如:GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
      GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
      GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
这三行是设置寄存器的地址,volatile的作用是作为指令关键字
确保本条指令不会因编译器的优化而省略,且要求每次直接读值
ioremap函数将物理地址转换为虚拟地址,IO口寄存器映射成普通内存单元进行访问。
 
解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址
比如:
        iounmap(GPFSEL0);
        iounmap(GPSET0);
        iounmap(GPCLR0); //卸载驱动时释放地址映射

函数copy_from_user用法。

 函数copy_from_user原型:
 copy_from_user(void *to, const void __user *from, unsigned long n)

返回值:失败返回没有被拷贝成功的字节数,成功返回0
参数详解:
1. to 将数据拷贝到内核的地址,即内核空间的数据目标地址指针
2. from 需要拷贝数据的地址,即用户空间的数据源地址指针
3. n 拷贝数据的长度(字节)
也就是将@from地址中的数据拷贝到@to地址中去,拷贝长度是n

三、代码整合

驱动代码

#include <linux/fs.h>         //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>     //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

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

static dev_t devno;                //设备号
static int major =231;             //主设备号
static int minor =0;               //次设备号
static char *module_name="pin4";   //模块名--这个模块名到时候是在树莓派的/dev底下显示相关驱动模块的名字

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

//volatile关键字的作用:确保指令不会因编译器的优化而省略,且要求每次直接读值,在这里的意思就是确保地址不会被编译器更换

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似    
    
    //由于pin4在 14-12位,所以将14-12位分别置为001即为输出引脚,所以下面的那两个步骤分别就是将14,13置为0,12置为1
    *GPFSEL0 &= ~(0x6 << 12); //把13,14位 置为0
    *GPFSEL0 |=  (0x1 << 12); //把12位 置为1 
    
    return 0;
}

//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    int userCmd;
    int copy_cmd;
    
    printk("pin4_write\\n");
    
    //copy_from_user(void *to, const void __user *from, unsigned long n)
    
    copy_cmd = copy_from_user(&userCmd,buf,count); //函数的返回值是,如果成功的话返回0,失败的话就是返回用户空间的字节数
    
    if(copy_cmd != 0)
    {
        printk("fail to copy from user\n");
    }

    if(userCmd == 1)
    {
        printk("set 1\n");
        *GPSET0 |= (0x1 << 4); //这里的1左移4位的目的就是促使寄存器将电平拉高,即变为HIGH
    }
    else if(userCmd == 0)
    {
        printk("set 0\n");
        *GPCLR0 |= (0x1 << 4); //这里的1左移4位也是一样只是为了让寄存器将电平拉低,即变为LOW
    }
    else
    {
        printk("nothing undo\n"); 
    }
    
    return 0;
}

static ssize_t pin4_read(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_read\n");
    return 0;    
}

static struct file_operations pin4_fops = {

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

int __init pin4_drv_init(void)   //设备驱动初始化函数(真实的驱动入口)
{
    int ret;
    
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //这个是让代码在/dev目录底下自动生成设备,自己手动生成也是可以的
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
    
    //由于以下的地址全是物理地址,所以我们要将物理地址转换成虚拟地址 
    GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4); //由于寄存器是32位的,所以是映射4个字节,一个字节为8位
    GPSET0  = (volatile unsigned int *)ioremap(0x3f20001c,4);
    GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
    
    return 0;
}

void __exit pin4_drv_exit(void)  //卸载驱动,即将驱动从驱动链表中删除掉 
{
    iounmap(GPFSEL0);
    iounmap(GPSET0);
    iounmap(GPCLR0);
    
    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动
}

module_init(pin4_drv_init);  //真正的入口
module_exit(pin4_drv_exit);  //卸载驱动
MODULE_LICENSE("GPL v2");    
  

上层代码

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

int main()
{
        int fd;
        int userCmd;
        
        fd = open("/dev/pin4",O_RDWR);
        
        if(fd < 0)
        {
            printf("fail to open the pin4\n");
            perror("the reason:");
        }
        else
        {
            printf("success to open the pin4\n");
        }
            
        printf("please Input 1-HIGH,0-LOW \n");
        scanf("%d",&userCmd);

        write(fd,&userCmd,4); //这里userCmd是一个整型数,所以写的是4个字节

        return 0;
}

如何编译驱动代码并在树莓派运行

树莓派初始引脚

bcm2835,驱动开发,Powered by 金山文档

运行代码后

bcm2835,驱动开发,Powered by 金山文档
bcm2835,驱动开发,Powered by 金山文档
bcm2835,驱动开发,Powered by 金山文档
bcm2835,驱动开发,Powered by 金山文档

学习笔记,仅供参考

优秀博客文章来源地址https://www.toymoban.com/news/detail-799836.html

到了这里,关于树莓派BCM2835芯片手册导读以及IO口驱动代码编写的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 树莓派4B采用设备树(DTS)提供硬件信息,编写platform驱动控制io(LED)

    设备树是一种描述硬件资源的数据结构,它通过bootloader将硬件资源传给内核,使得内核和硬件资源描述相对独立。 1DT:Device Tree //设备树 2FDT:Flattened Device Tree //展开设备树|开放固件,设备树起源于OF,所以我们在设备树中可以看到很多有of字母的函数 3device tree source(dts) //设备

    2024年02月14日
    浏览(45)
  • WT2605C蓝牙音频语音芯片:具备大功率IO驱动能力,引领音频技术新纪元

    在当今的电子科技时代,功率强大的IO驱动能力成为音频设备性能的重要指标。近日,一款名为WT2605C的蓝牙音频语音芯片,以其最高可直接驱动64mA的大功率IO驱动能力,引起业界的广泛关注。这款芯片的出现,无疑将为音频设备的设计与应用带来全新的可能性。 WT2605C蓝牙音

    2024年01月16日
    浏览(58)
  • 博通BCM575系列 RDMA 网卡驱动 bnxt_re 分析(一)

    整个BCM系列驱动分成以太网部分(bnxt_en.ko)和RDMA部分(bnxt_re.ko), 两个模块之间通过内核的auxiliary_bus进行管理.我们主要分析下bnxt_re驱动. 这个驱动的核心是 qplib_fp.c, 这个文件主要包含了驱动的数据路径, 包括Post Send, Post Recv, Poll CQ流程的实现. ib_verbs.c主要是实现了上层的Verbs接口

    2024年02月08日
    浏览(41)
  • 实时时钟芯片DS1307的使用及驱动代码

    DS1307实时时钟芯片的介绍及驱动代码 目录 一、DS1307是什么? 二、DS1307的功能 三、DS1307的寄存器 四、代码 1.读出数据 2.写入数据 3.时间初始化设置 4.获取当前时间 五、注意事项 总结 DS1307是一款基于IIC总线接口的实时时钟芯片,可以独立于MCU工作,芯片具有备用电源自动切

    2024年02月05日
    浏览(42)
  • 讲解STM32驱动WS281x灯珠的多种实现方式:普通IO、SPI+DMA、以及PWM+DMA驱动方法

    STM32作为一款高性能、功能丰富的单片机,其丰富的外设和强大的性能,使其在嵌入式领域得到了广泛的应用。本篇文章将介绍如何利用STM32驱动WS281x系列的LED灯珠。我们会使用三种不同的驱动方式进行实现:一种是普通IO方式驱动,一种是SPI+DMA方式驱动,最后一种是PWM+DMA方

    2024年02月11日
    浏览(40)
  • 基于树莓派实现的IO-Link 项目

       IO-Link 协议 (IEC 61131-9) 是从传感器或执行器到 IO-Link 主站的串行半双工点对点连接。目前IO-Link 的硬应已经越来越普及。具有IO-Link接口的传感器和执行器技术的广泛基础已经可用。如今,市场上总共有超过13,000种不同的IO-Link产品。国外产品以巴鲁夫为代表。如何开发

    2024年02月16日
    浏览(26)
  • 【数据手册】CH340G芯片使用介绍

    CH340是一系列USB总线适配器,它通过USB总线提供串行、并行或IrDA接口。CH340G集成电路提供通用的MODEM信号,允许将UART添加到计算机上,或将现有的UART设备转换为USB接口。 全速USB接口,兼容USB 2.0接口。 使用最小数量的外部组件:一个晶体管和至少四个电容器。 提供了一个虚

    2024年02月03日
    浏览(139)
  • 使用树莓派Pico、DHT11和SSD1306搭建一个温度湿度计(只使用官方库,以及官方案例代码的错误之处和解决方案)

    最近想树莓派 Pico、DHT11 温湿度传感器和 SSD1306 OLED 屏幕做一个温度湿度计,树莓派官方案例也分别有这两个设备的案例,我就想做个简单的温度湿度计作为学习微控制器的开始,结果遇到了一个大坑,所以写本文记录一下整个过程。 本文最后会实现一个能在 SSD1306 OLED 屏幕上

    2024年02月11日
    浏览(42)
  • 【数据手册】LM1117L3芯片的使用

    可调或固定输出 1A 输出电流 低损耗,在 1A 输出电流时最大电压为 1.3V 0.04% 的线路调节 0.2% 负载调节 100% 热极限燃烧 快速瞬态响应      LM1117系列正可调和固定调节器设计提供1A高电流效率。 所有内部电路设计为低至 1.3V 输入输出差。 片内微调将参考电压调整为 1% 高效线性

    2024年02月08日
    浏览(38)
  • W25Q128芯片手册精读

    之前写SPI通信的时候用到了W25Q128,其中对芯片手册的阅读我只写了我们所需要的的部分。 这篇博客就对这个芯片进行详细的阅读并记录,文章可能会比较长,但绝对是结合了自己的理解进行阐述。 芯片手册刚开始看的时候最大的拦路虎其实就是英文,看习惯了中文,直接看

    2024年02月07日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包