STM32mp157字符设备实验—LED驱动(直接操作寄存器版)

使用开发板为正点原子STM32mp157。

参考资料则是正点原子驱动开发教程,以及STM32mp157芯片手册。


跟STM32裸机一样,需要配置相应的硬件寄存器,这里涉及到物理地址映射到虚拟地址。

主要是通过ioremapiounmap这两个函数进行。

先在内核源码中搜索这两个函数,并且跳转到它们的定义。

 

可以发现,映射函数返回值是一个指针,也就是我们映射完的虚拟地址。

取消映射函数没有返回值。

了解到这些,我们就可以使用这两个函数了,当然也可以通过其他途径了解这两个函数的用法,这里只是举个例子。

物理地址

学过stm32裸机开发的人都知道,我们进行寄存器直接操作的时候,是需要知道各个寄存器的物理地址的,当然在固件库编程里面已经都给我们定义好了,只要去结构体里面去找就可以了。

那么在linux驱动开发里面,我们也需要去找到物理地址。

那么怎么找呢?

首先打开我们的STM32mp157参考手册。

 我们可以看到GPIOs和RCC都是在AHB4总线上的,那么就要找到AHB4的地址。

从这张图,我们可以看到AHB4的地址,不过为了以后移植方便,我决定从APB1开始写,然后AHB4用偏移量表示。

通过查看开发板原理图我可以找到LED0所接的GPIO口为GPIOI0

知道是哪个口之后,在芯片手册中再继续往下找,找到AHB4外设映射情况。

分别找到相应的物理地址,为了方便,我依然采用了加偏移量的方式定义物理地址。

找到这两个之后,点进RCC的寄存器设置,因为GPIO是在AHB4总线下面的,所以需要使能AHB4下的时钟。

可以看到偏移量是0XA28,同时在bit8上可以使能GPIOI的时钟,记下来!

RCC设置完了之后,就是对我们GPIO的设置了。

回顾一下在STM32中进行裸机开发的时候,是一个写好的结构体,让我们去定义模式、输入/输出方式、速度、上拉/下拉等等。

不记得也没有关系,我们点进GPIO的寄存器设置,里面有各种寄存器。

 

一个个往下去找,就能找到我们需要的寄存器了,每个下面都有相应的设置,按照这个一个个去设置。

同时这些也都有偏移量,设置好我们的基地址,一个个加起来就行。

设置完就如下:

//寄存器物理地址,通过查芯片参考手册得到
#define PERIPH_BASE                 (0x40000000)
#define MPU_AHB4_PERIPH_BASE        (PERIPH_BASE + 0x10000000)
#define RCC_BASE                    (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR           (RCC_BASE + 0XA28)

#define GPIOI_BASE                  (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER                 (GPIOI_BASE + 0x0000) 
#define GPIOI_OTYPER                (GPIOI_BASE + 0x0004) 
#define GPIOI_OSPEEDR               (GPIOI_BASE + 0x0008) 
#define GPIOI_PUPDR                 (GPIOI_BASE + 0x000C) 
#define GPIOI_BSRR                  (GPIOI_BASE + 0x0018)

尽量按照这种方式,不容易出错,也容易移植。

然后进行地址映射。

static void __iomem *RCC_MP_AHB4ENSETR_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;
//地址映射
static void led_ioremap(void)
{
    RCC_MP_AHB4ENSETR_PI = ioremap(RCC_MP_AHB4ENSETR,4);
    GPIOI_MODER_PI = ioremap(GPIOI_MODER,4);
    GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER,4);
    GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR,4);
    GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR,4);
    GPIOI_BSRR_PI = ioremap(GPIOI_BSRR,4);
}
//取消映射
static void led_iounmap(void)
{
    iounmap(RCC_MP_AHB4ENSETR_PI);
    iounmap(GPIOI_MODER_PI);
    iounmap(GPIOI_OTYPER_PI);
    iounmap(GPIOI_OSPEEDR_PI);
    iounmap(GPIOI_PUPDR_PI);
    iounmap(GPIOI_BSRR_PI);
}

这样我们的地址就映射好了,接下来用之前编写过的框架。

入口函数,出口函数,以及注册和注销函数都全部复制过来。

复制过来之后,编译一下,看看有什么地方不对,头文件缺失等,修改一下。

初始化led

按照上面查的,对led进行初始化。

// 入口函数
static int __init led_init(void)
{
    int ret = 0;
    int val = 0;
    //寄存器地址映射
    led_ioremap();
    //使能GPIO时钟
    val = readl(RCC_MP_AHB4ENSETR_PI);
    val &= ~(0x1 << 8);//将bit8位清0
    val |=(0x1 << 8);//将bit8置1
    writel(val,RCC_MP_AHB4ENSETR_PI);

    //将GPIOI设置为输出
    val = readl(GPIOI_MODER_PI);
    val &= ~(0x3 << 0);//将bit0和1位清0s
    val |=(0x1 << 0);//将bit0和1置为01
    writel(val,GPIOI_MODER_PI);

    //将输出设置为推挽输出
    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0x1 << 0);//将bit0清0,设置为推挽输出
    writel(val,GPIOI_OTYPER_PI);

    //设置速度
    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0x3 << 0);
    val |= (0x3<<0);//设置为超高速
    writel(val,GPIOI_OSPEEDR_PI);  

    //设置上拉下拉
    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0x3 << 0);
    val |= (0x1<<0);//设置为上拉
    writel(val,GPIOI_PUPDR_PI);  

    //将GPIOI默认设置为1,关闭状态
    val = readl(GPIOI_BSRR_PI);
    val &= ~(0x1 << 0);
    val |= (0x1<<0);//设置为1
    writel(val,GPIOI_BSRR_PI);  

    printk("led_init\r\n");

    // 注册字符设备
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if (ret < 0)
    {
        printk("Led driver register failed!\r\n");
        goto fail_register;//要理解查阅的地方
    }


    return 0;

 fail_register:
      return -EIO;

}
// 出口函数
static void __exit led_fini(void)
{
    led_iounmap();
    printk("led_exit\r\n");

    // 注销字符设备
    unregister_chrdev(LED_MAJOR, LED_NAME);
   
}

这里有个goto的用法,有个文章说的还行,这个我还在自我理解中...

http://t.csdn.cn/nqXpp

大致流程就是按照查阅的寄存器一个个设置,这里的相信学过裸机开发的都能够明白,就不介绍了。

这里我们将GPIO0默认设置为高电平,设置为上拉并且初始化置1。

至此,初始化就结束了。

实现灯的亮灭

首先定义两个宏。

 

因为是从应用层写进来数据,所以我们对write函数进行修改,首先还是定义一个缓冲区,只要一个字节就可以了。

unsigned char databuf[1];//数据缓冲区

同时定义一个灯的状态,也可以不定义,看自己的习惯。

unsigned char ledstat;//灯的状态

将用户传进来的数据保存到我们定义的缓冲区中,然后再传给灯的状态,从而去判断灯的亮灭。

 好了,最后我们再来写一个led_switch这个函数,其实没啥,就是一个位的置0和置1。

void led_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LED_ON)
    {

        val = readl(GPIOI_BSRR_PI);
        val &= ~(0x1 << 16);
        val |= (0x1 << 16); // 设置为1
        writel(val, GPIOI_BSRR_PI);
    }
    else if (sta == LED_OFF)
    {
        val = readl(GPIOI_BSRR_PI);
        val &= ~(0x1 << 0);
        val |= (0x1 << 0); // 设置为1
        writel(val, GPIOI_BSRR_PI);
    }
}

至此,驱动文件就写完了,主要就是对芯片手册的查询。

贴一个完整的程序。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

//寄存器物理地址,通过查芯片参考手册得到
#define PERIPH_BASE                 (0x40000000)
#define MPU_AHB4_PERIPH_BASE        (PERIPH_BASE + 0x10000000)
#define RCC_BASE                    (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR           (RCC_BASE + 0XA28)

#define GPIOI_BASE                  (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER                 (GPIOI_BASE + 0x0000) 
#define GPIOI_OTYPER                (GPIOI_BASE + 0x0004) 
#define GPIOI_OSPEEDR               (GPIOI_BASE + 0x0008) 
#define GPIOI_PUPDR                 (GPIOI_BASE + 0x000C) 
#define GPIOI_BSRR                  (GPIOI_BASE + 0x0018)

#define LED_MAJOR 200
#define LED_NAME "LED"

//定义灯的开关状态
#define LED_ON 1
#define LED_OFF 0

//映射后的寄存器虚拟地址指针
static void __iomem *RCC_MP_AHB4ENSETR_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

//地址映射
static void led_ioremap(void)
{
    RCC_MP_AHB4ENSETR_PI = ioremap(RCC_MP_AHB4ENSETR,4);
    GPIOI_MODER_PI = ioremap(GPIOI_MODER,4);
    GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER,4);
    GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR,4);
    GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR,4);
    GPIOI_BSRR_PI = ioremap(GPIOI_BSRR,4);
}
//取消映射
static void led_iounmap(void)
{
    iounmap(RCC_MP_AHB4ENSETR_PI);
    iounmap(GPIOI_MODER_PI);
    iounmap(GPIOI_OTYPER_PI);
    iounmap(GPIOI_OSPEEDR_PI);
    iounmap(GPIOI_PUPDR_PI);
    iounmap(GPIOI_BSRR_PI);
}
void led_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LED_ON)
    {

        val = readl(GPIOI_BSRR_PI);
        val &= ~(0x1 << 16);
        val |= (0x1 << 16); // 设置为1
        writel(val, GPIOI_BSRR_PI);
    }
    else if (sta == LED_OFF)
    {
        val = readl(GPIOI_BSRR_PI);
        val &= ~(0x1 << 0);
        val |= (0x1 << 0); // 设置为1
        writel(val, GPIOI_BSRR_PI);
    }
}


static int led_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    //printk("chrdevbase_open\r\n");
    return ret;
}

static int led_release(struct inode *inode, struct file *filp)
{
    int ret = 0;
    //printk("chrdevbase_release\r\n");
    return ret;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,
                         loff_t *offt)
{
    int ret = 0;
    return ret;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
                          size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char databuf[1];//数据缓冲区
    unsigned char ledstat;//灯的状态
    ret = copy_from_user(databuf,buf,cnt);  
     if (ret < 0)
    {
        printk("kernel receviedata failed!\r\n");
        ret = -EFAULT;
    }
    ledstat = databuf[0];//传进来灯的开关状态

    if(ledstat == LED_ON) //开灯
    {
        led_switch(LED_ON);

    }else if(ledstat == LED_OFF)//关灯
    {
        led_switch(LED_OFF);
    }
    return ret;

}

const struct file_operations led_fops = {

    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .write = led_write,
    .read = led_read,

};



// 入口函数
static int __init led_init(void)
{
    int ret = 0;
    int val = 0;
    //寄存器地址映射
    led_ioremap();
    //使能GPIO时钟
    val = readl(RCC_MP_AHB4ENSETR_PI);
    val &= ~(0x1 << 8);//将bit8位清0
    val |=(0x1 << 8);//将bit8置1
    writel(val,RCC_MP_AHB4ENSETR_PI);

    //将GPIOI设置为输出
    val = readl(GPIOI_MODER_PI);
    val &= ~(0x3 << 0);//将bit0和1位清0s
    val |=(0x1 << 0);//将bit0和1置为01
    writel(val,GPIOI_MODER_PI);

    //将输出设置为推挽输出
    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0x1 << 0);//将bit0清0,设置为推挽输出
    writel(val,GPIOI_OTYPER_PI);

    //设置速度
    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0x3 << 0);
    val |= (0x3<<0);//设置为超高速
    writel(val,GPIOI_OSPEEDR_PI);  

    //设置上拉下拉
    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0x3 << 0);
    val |= (0x1<<0);//设置为上拉
    writel(val,GPIOI_PUPDR_PI);  

    //将GPIOI默认设置为1,关闭状态
    val = readl(GPIOI_BSRR_PI);
    val &= ~(0x1 << 0);
    val |= (0x1<<0);//设置为1
    writel(val,GPIOI_BSRR_PI);  

    printk("led_init\r\n");

    // 注册字符设备
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if (ret < 0)
    {
        printk("Led driver register failed!\r\n");
        goto fail_register;//要理解查阅的地方
    }


    return 0;

 fail_register:
      return -EIO;

}
// 出口函数
static void __exit led_fini(void)
{
    led_iounmap();
    printk("led_exit\r\n");

    // 注销字符设备
    unregister_chrdev(LED_MAJOR, LED_NAME);
   
}



/*驱动的注册与卸载*/
module_init(led_init); // 入口函数
module_exit(led_fini); // 出口函数

应用测试APP

跟之前的程序差不多,直接复制过来,改一下,这个没啥好说的。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LED_ON 1
#define LED_OFF 0

/*
APP运行命令:./ledAPP filename <0>/<1> 如果是1表示打开,如果是0表示关闭
*/
int main(int argc, char *argv[])
{
    int fd, retvalue;                // 定义返回值
    char *filename;                  // 要打开的文件
    unsigned char databuf[1];

    if (argc != 3)                   // 判断输入的参数是否是三个
    {                               
        printf("error usage!\r\n");
        return -1;
    }
    filename = argv[1];              // 第二个参数,既要打开的文件
    /*
    打开文件
    */
    fd = open(filename, O_RDWR);     // 读写的方式打开文件

    if (fd < 0)
    {

        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]); //转为数字
   
    retvalue = write(fd,databuf,1);
    if (retvalue < 0)
    {
        printf("write file failed! %s\r\n", filename);
    } 

    /*
    关闭文件
    */
    retvalue = close(fd);
    if (retvalue < 0)
    {
        printf("Can't close file%s\r\n", filename);
        return -1;
    }
    return 0;
}

上开发板测试

 

 

 可以红灯可以实现亮灭。

成功!!

点灯完成。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值