STM32MP157A-FSMP1A单片机移植Linux系统SPI总线驱动

news/2025/2/26 12:53:11

SPI总线驱动整体上与I2C总线驱动类型,差别主要在设备树和数据传输上,由于SPI是由4根线实现主从机的通信,在设备树上配置时需要对SPI进行设置。

原理图可知,数码管使用的SPI4对应了单片机上的PE11-->SPI4-NSS,PE12-->SPI4-SCK,PE13-->SPI4-MISO,PE14-->SPI4-MOSI

设备树信息

需要注意在SPI4设备树信息中,一定要加spi-max-frequency属性,该属性用于设置spi的主频,如果没有,可能出现无法正常启动spi或启动spi后控制数码管时,数码管显示出现错位现象

驱动源码

#include <linux/init.h>       // 包含内核初始化相关的头文件
#include <linux/module.h>     // 包含内核模块相关的头文件
#include <linux/of.h>         // 包含设备树操作相关的头文件
#include <linux/gpio.h>       // 包含 GPIO 操作相关的头文件
#include <linux/of_gpio.h>    // 包含设备树 GPIO 相关的头文件
#include <linux/fs.h>         // 包含文件操作相关的头文件
#include <linux/uaccess.h>    // 包含用户空间访问内核空间相关的头文件
#include <linux/device.h>     // 包含设备相关的头文件
#include <linux/cdev.h>       // 包含字符设备相关的头文件
#include <linux/slab.h>       // 包含内存分配相关的头文件
#include <linux/spi/spi.h>    // 包含 SPI 相关的头文件
#include "spi_test.h"    // 包含自定义头文件

//创建设备号
static int major;
//创建类
static struct class *cls;
//创建设备
static struct device *device;
//创建SPI设备
static struct spi_device *spi_dev;
//保存字符设备数据
static char spi_buf[128];

static int myspi_open(struct inode *inode, struct file *file)
{
    unsigned int cmajor;
    //保存次设备号
    cmajor = iminor(inode);

    //将次设备号保存到file结构体的private_data中
    file->private_data = (void *)cmajor;

    printk("spi_open\n");
    return 0;
}

static int myspi_close(struct inode *inode, struct file *file)
{
    printk("spi_close\n");
    return 0;
}

static ssize_t myspi_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned int ret;

    //从内核空间读取数据到用户空间,如果读取数据的长度大于spi_buf的长度,按照最大长度读取
    if(count > sizeof(spi_buf))
    {
        count = sizeof(spi_buf);
    }
    ret = copy_to_user(buf, spi_buf, count);
    if(ret < 0)
    {
        printk("copy_to_user failed\n");
        return -1;
    }

    printk("spi_read\n");
    return 0;
}

static ssize_t myspi_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned int ret;

    //将用户空间的数据拷贝到内核空间,如果传输数据的长度大于spi_buf的长度,按照最大长度传输
    if (count > sizeof(spi_buf))
    {
        count = sizeof(spi_buf);
    }
    ret = copy_from_user(spi_buf, buf, count);
    if (ret < 0)
    {
        printk("copy_from_user failed\n");
        return -1;
    }
    
    printk("spi_write\n");
    return 0;
}

static int myspi_write_data(const uint8_t cmd, const uint8_t data)
{
    uint8_t spi_buf1[2] = {cmd, data};
    int ret = 0;
    printk("cmd = %x\n", spi_buf1[0]);
    ret = spi_write(spi_dev, spi_buf1, 2);
    return ret;
}

static long myspi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    uint8_t data;
    unsigned long ret ;

    //从用户空间读取数据
    ret = copy_from_user(&data, (void *)arg, sizeof(data));
    if (ret < 0)
    {
        printk("copy_from_user failed\n");
        return -1;
    }
    //根据cmd的值进行不同的操作    
    switch (cmd)
    {
    case SET_DAT0:
        ret = myspi_write_data(0x01, data);
        break;
    case SET_DAT1:
        ret = myspi_write_data(0x02, data);
        break;
    case SET_DAT2:
        ret = myspi_write_data(0x04, data);
        break;
    case SET_DAT3:
        ret = myspi_write_data(0x08, data);
        break; 
    case SET_DAT4:
        ret = myspi_write_data(0x0f, data);
        break;
    default:
        break;
    }
    if (ret != 0)
    {
        printk("spi_write failed\n");
        return -1;
    }

    printk("spi_ioctl, data = %x\n", data);
    return 0;
}

//定义file_operations结构体
struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = myspi_open,
    .release = myspi_close,
    .read = myspi_read,
    .write = myspi_write,
    .unlocked_ioctl = myspi_ioctl,
};

//编写spi设备驱动
int	myspi_probe(struct spi_device *spi)
{
    //unsigned char buf[]={0X01,0X6D};//0X01表示写数据,0X6D表示写入的数据
    //spi_write(spi,buf,sizeof(buf));//给从机发送一个数据,让最左边数码管显示数字5
    printk("myspi_probe\n");
    
    spi_dev = spi;
    //1.创建设备号
    major = register_chrdev(0, "myspi", &fops);
    if (major < 0)
    {
        printk("register_chrdev failed\n");
        return -1;
    }

    //2.创建类
    cls = class_create(THIS_MODULE, "myspi");
    if (IS_ERR(cls))
    {
        printk("class_create failed\n");
        unregister_chrdev(major, "myspi");
        return -1;
    }
    
    //3.创建设备
    device = device_create(cls, NULL, MKDEV(major, 0), NULL, "myspi");
    if (IS_ERR(device))
    {
        printk("device_create failed\n");
        class_destroy(cls);
        unregister_chrdev(major, "myspi");
        return -1;
    }


    return 0;
}

int	myspi_remove(struct spi_device *spi)
{
    printk("myspi_remove\n");    
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, "myspi");

    return 0;
}

static const struct of_device_id spi_table[] = {
    { .compatible = "johnson,m74hc595", },
    { /* sentinel */ },
};


struct spi_driver myspi_driver = {
    .probe = myspi_probe,
    .remove = myspi_remove,
    .driver = {
        .name = "m74hc595",
        .of_match_table = spi_table,
    },
};

//一键注册宏定义
module_spi_driver(myspi_driver);
MODULE_LICENSE("GPL");  //声明开源许可

头文件

#ifndef __SPITEST_H__
#define __SPITEST_H__

#define SET_DAT0 _IOW('s', 0, int)
#define SET_DAT1 _IOW('s', 1, int)
#define SET_DAT2 _IOW('s', 2, int)
#define SET_DAT3 _IOW('s', 3, int)
#define SET_DAT4 _IOW('s', 4, int)


#endif

应用程序 --> 实现在数码管上依次显示数字

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include "spi_test.h"

const uint8_t num[10] = {0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F};

int main()
{
    int fd = 0;
    int date = 0;

    //打开设备文件
    fd = open("/dev/myspi", O_RDWR);
    if (fd < 0)
    {
        printf("open myspi failed\n");
        return -1;
    }

    while(1)
    {
        ioctl(fd, SET_DAT0, &num[date]);
        sleep(1);
        
        ioctl(fd, SET_DAT1, &num[date]);
        sleep(1);
        
        ioctl(fd, SET_DAT2, &num[date]);
        sleep(1);
        
        ioctl(fd, SET_DAT3, &num[date]);
        sleep(1);
        
        ioctl(fd, SET_DAT4, &num[date]);
        if (++date > 9)
        {
            date = 0;
        }
        sleep(1);
    }   

    //关闭设备文件
    close(fd);

    return 0;
}


http://www.niftyadmin.cn/n/5868722.html

相关文章

Java进阶:SpringMVC中放行静态资源

方法一 <mvc:resources mapping"/js/**" location"/js/"/>mapping&#xff1a;映射地址。访问服务器找资源时候的地址。 location&#xff1a;目录。具体资源所在目录。 方式二 <mvc:default-servlet-handler/>mvc如果找不到静态资源&…

娛閑放鬆篇3

2月的立春打算多添加兩隻貓&#xff0c;本來1月份黑漸層死了…但立春那天三花也撐不下去.....然後去了領貓的地方&#xff0c;只捉到了熟睡的三花&#xff0c;捉不到那隻橘白...幸好三天後捉到了...也是到家後一天才轉移到了貓籠&#xff0c;到家後兩天用吸管擼貓才不哈我&…

Spring Boot + Vue 接入腾讯云人脸识别API(SDK版本3.1.830)

一、需求分析 这次是基于一个Spring Boot Vue的在线考试系统进行二次开发&#xff0c;添加人脸识别功能以防止学生替考。其他有对应场景的也可按需接入API&#xff0c;方法大同小异。 主要有以下两个步骤&#xff1a; 人脸录入&#xff1a;将某个角色&#xff08;如学生&…

[算法--前缀和] 矩阵区域和

目录 1. 二维前缀和的知识铺垫2. 以nums[i][j]为中心计算区域大小.3. dp数组与ret数组之间的逻辑关系.4. 细节: 如果[i,j]为中心的数组越界了呢?下面继续分享一道用前缀和思想解决的算法问题 -> 矩阵区域和 1. 二维前缀和的知识铺垫 实际上, 有一道十分类似的基础题 ->…

免费使用 DeepSeek API 教程及资源汇总

免费使用 DeepSeek API 教程及资源汇总 一、DeepSeek API 资源汇总1.1 火山引擎1.2 百度千帆1.3 阿里百炼1.4 腾讯云 二、其他平台2.1 华为云2.2 硅基流动 三、总结 DeepSeek-R1 作为 2025 年初发布的推理大模型&#xff0c;凭借其卓越的逻辑推理能力和成本优势&#xff0c;迅速…

硬件基础(3):三极管(1):理论基础

目录 一、背景 二、定义 三、分类 四、工作原理 NPN三极管工作原理 基本工作原理 电流放大倍数&#xff08;增益&#xff09; 输入特性 1. 输入特性的基本概念 2. 输入特性曲线的形态 3. 输入特性曲线的具体分析 输出特性 1. 输出特性图的基本概念 2. 输出特性曲…

[ComfyUI]官方已支持Skyreels混元图生视频,速度更快,效果更好(附工作流)

一、介绍 昨天有提到官方已经支持了Skyreels&#xff0c;皆大欢喜&#xff0c;效果更好一些&#xff0c;还有GGUF量化版本&#xff0c;进一步降低了大家的显存消耗。 今天就来分享一下官方流怎么搭建&#xff0c;我体验下来感觉更稳了一些&#xff0c;生成速度也更快&#xf…

springboot实现多文件上传

springboot实现多文件上传 代码 package com.sh.system.controller;import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMap…