Linux驱动开发——SPI

SPI基础知识

SPI全称是serial peripheral interface,叫串行外设接口,是一种同步串行传输规范。

特点

1、高速,全双工,同步串行总线

并行通信:数据各位同时传送

串行:数据一位一位顺序传送

同步:主机和从机共用同一个时钟

异步:收发之间没有时钟

单工:信号只能在一个方向上传输,也就是智能发送和接收

半双工:信号可以在两个方向上传输,但是同一只可只允许一个方向

全双工:允许数据在两个方向上同时进行传输

2、主从两种模式,通常由一个主设备和多个从设备组成,SPI不支持多主机SPI不支持多主机SPI不支持多主机

3、SPI通信至少需要4条线,MISO,MOSI,SCLK,CS/SS

硬件连接方式


SPI通信原理

SPI没有起始信号,终止信号,应答信号,传输完一个字节之后立刻传输另外一个字节,速度较快(因为没有应答信号),数据安全性较低(因为没有应答信号),因此本人认为这个和UDP通信非常类似。

SPI总线驱动模型

驱动编写

我们将OLED挂载在spi总线下面

核心板引脚:

引脚引出来是:

所以设备树编写如下

复制代码
&ecspi3 {
        fsl,spi-num-chipselects = <1>;
        cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";
	
	oled:oled{
		compatible = "100ask,oled";
		reg = <0>;
	}

驱动编写流程

  1. init函数
  2. exit函数
  3. spi_driver结构体
    该结构体内包含.driver 、.probe 、.remove等属性
  4. of_device_id结构体
    该结构体内包含多组compatible属性,匹配成功后会进入spi_driver结构体内的probe 函数
  5. file_operations结构体
    可以在该结构体内注册操作函数,例如read,write,ioctl等函数
  6. probe函数:
    在probe函数内部可以注册字符设备等操作具体外设的功能函数
    完整的驱动代码如下:
c 复制代码
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/list.h>
#include <linux/spi/spi.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#define OLED_CMD 0
#define OLED_DATA 1
#define OLED_SET_XY            99
#define OLED_SET_XY_WRITE_DATA 100
#define OLED_SET_XY_WRITE_DATAS 101
#define OLED_SET_DATAS          102 /*102为低8位,高16伟表示长度*/

struct spi_device *oled_dev;
static struct gpio_desc *oled_dc;
static int major;
static struct class *oled_class;

static void oled_write_datas(unsigned char *buf,int len){

    gpiod_set_value(oled_dc,1);
    
    spi_write(oled_dev,buf,len);
}

static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd){
    // unsigned char uc_read=0;
    if(uc_cmd == 0){
        gpiod_set_value(oled_dc,0);
    }
    else{
        gpiod_set_value(oled_dc,1);
    }
    spi_write(oled_dev,&uc_data,1);
}

static int oled_hardware_init(void){
    unsigned char uc_dev_id = 0;
    // GPIO4_GDIR_s = (volatile unsigned int *)(0x20a8000+0x4);
    // GPIO4_DR_s = (volatile unsigned int *)(0x20a8000+0x4);
    // spi_init(ESCPI1_BASE);

    oled_write_cmd_data(0xae,OLED_CMD);//关闭显示
    oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address
    oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address

    oled_write_cmd_data(0x40,OLED_CMD);//设置 higher column address
    oled_write_cmd_data(0x80,OLED_CMD);//设置 page address

    oled_write_cmd_data(0x81,OLED_CMD);//contract control
    oled_write_cmd_data(0x66,OLED_CMD);//128

    oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap

    oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse

    oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
    oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64

    oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction

    oled_write_cmd_data(0xd3,OLED_CMD);//set osc division
    oled_write_cmd_data(0x00,OLED_CMD);//

    oled_write_cmd_data(0xd9,OLED_CMD);//set pre-charge period
    oled_write_cmd_data(0x1f,OLED_CMD);//

    oled_write_cmd_data(0xda,OLED_CMD);//set com pins
    oled_write_cmd_data(0x12,OLED_CMD);//

    oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
    oled_write_cmd_data(0x30,OLED_CMD);//

    oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable
    oled_write_cmd_data(0x14,OLED_CMD);//
    
    oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on

    return 0;
}

static void OLED_DIsp_Set_Pos(int x,int y){
    oled_write_cmd_data(0xb0+y,OLED_CMD);
    oled_write_cmd_data( (x&0x0f),OLED_CMD);
    oled_write_cmd_data( ((x&0x0f)>>4)|0x10,OLED_CMD);
}

static void OLED_DIsp_Clear(void){
    unsigned char x,y;
    for(y=0;y<8;y++){
        OLED_DIsp_Set_Pos(0,y);
        for(x=0;x<128;x++){
            oled_write_cmd_data((y<4)?0:0xff,OLED_DATA);
        }
    }
}

static long oled_ioctl(struct file *file, unsigned int cmd,unsigned long arg){
    const void __user *from = (const void __user *)arg;
    char param_buf[3];
    char data_buf[1024]; 
    int size;
    switch (cmd & 0xff)
    {
        case OLED_SET_XY:
        {
             copy_from_user(param_buf,from, 2);
            OLED_DIsp_Set_Pos(param_buf[0],param_buf[1]);
            break;;
        }

        case OLED_SET_XY_WRITE_DATA:
        {
            copy_from_user(param_buf,from, 3);
            OLED_DIsp_Set_Pos(param_buf[0],param_buf[1]);
            oled_write_cmd_data(param_buf[2], OLED_DATA);
            /* code */
            break;
        
        }
        case OLED_SET_XY_WRITE_DATAS:
        {
            copy_from_user(param_buf,from, 3);
            size = param_buf[2];
            copy_from_user(data_buf,from+3, size);

            OLED_DIsp_Set_Pos(param_buf[0], param_buf[1]);
            oled_write_datas(data_buf,size);
            break;
        } 
        case OLED_SET_DATAS:
        {
            size = cmd >> 8; 
             copy_from_user(data_buf,from, size);
            oled_write_datas(data_buf,size);
            break;
        }
    
        
    }
    return 0;
}

static struct  file_operations oled_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = oled_ioctl,
};
 

static int oled_probe(struct spi_device *spi)
{
    printk("%s %s %d",__FILE__, __FUNCTION__, __LINE__);
    printk("\n");

    oled_dev = spi;

    major = register_chrdev(0,"oled",&oled_fops);

    oled_class = class_create(THIS_MODULE,"oled_class");

    device_create(oled_class, NULL, MKDEV(major,0), NULL, "myoled");

    /*spi oled init */
    oled_dc = gpiod_get(&spi->dev,"dc",GPIOD_OUT_HIGH);
    oled_hardware_init();

    OLED_DIsp_Clear();
    return 0;
}

static int oled_remove(struct spi_device *spi){
    printk("%s %s %d",__FILE__, __FUNCTION__, __LINE__);
    printk("\n");

    device_destroy(oled_class, MKDEV(major,0) );
    class_destroy(oled_class);
    unregister_chrdev(major, "oled");

    return 0;
}

// static const struct spi_device_id oled_spi_ids[]=
// {
//     {"100ask,oled",},
//     {}
// };

static const struct of_device_id oled_of_match[] =
{
    {.compatible = "100ask,oled"},
    {}
};


static struct spi_driver oled_driver = {
    .driver = {
        .name = "100ask,oled",
        .of_match_table = oled_of_match,
    },
    .probe = oled_probe,
    .remove = oled_remove,
    // .id_table = oled_spi_ids,
};

int oled_init(void){
    return spi_register_driver(&oled_driver);
}

static void oled_exit(void){
    spi_unregister_driver(&oled_driver);
}

module_init(oled_init);
module_exit(oled_exit);
MODULE_LICENSE("GPL v2");
相关推荐
大聪明-PLUS1 小时前
C++中的恒定性
linux·嵌入式·arm·smarc
b***59431 小时前
在 Ubuntu 22.04 上安装和配置 Nginx 的完整指南
linux·nginx·ubuntu
赖small强2 小时前
【音视频开发】Linux UVC (USB Video Class) 驱动框架深度解析
linux·音视频·v4l2·uvc
多恩Stone2 小时前
【系统资源监控-1】Blender批量渲染中的负载、CPU、GPU和进程管理
linux·python
莽夫搞战术2 小时前
Linux NAS 迁移避坑指南:放弃 chown -R,ID 映射让权限配置秒完成
linux·服务器
好好沉淀2 小时前
IDEA如何设置以新窗口打开新项目
linux·windows·intellij-idea
大聪明-PLUS2 小时前
C++中变量的声明和初始化
linux·嵌入式·arm·smarc
被制作时长两年半的个人练习生2 小时前
如何调试llama.cpp及判断是否支持RVV
linux·服务器·llama