SPI子系统 Linux 设备驱动 -- SPI子系统
OLED显示模块 OLED模块硬件接口具有6个引脚:
SCL,无论是SPI还是I2C接口时,它都是始终引脚 SDA,当使用I2C接口,它是I2C的SDA,当使用SPI,它是MOSI 注意事项:
OLED 显示屏不同于 LCD,OLED 上电是没有反应的,需要程序驱动才会有显示! 具体如何编程查看SSD1306芯片手册
设备树 OLED驱动实现 1、OLED硬件操作 spi写操作:
static int oled_spi_write ( char *buf, uint16_t len) { int status; # if 0 struct spi_message msg ; struct spi_transfer xfer = { .len = len, .tx_buf = buf, }; spi_message_init(&msg); /* 初始化spi_message */ spi_message_add_tail(&xfer, &msg); /* 添加到传输队列 */ status = spi_sync(oled_dev->spi, &msg); /* 同步发送 */ # else status = spi_write(oled_dev->spi, buf, len); # endif return status; } spi_write,封装了spi_message_init、spi_message_add_tail和spi_sync步骤 OLED写操作:
enum { OLED_CMD = 0x00 , OLED_DATA = 0x01 , }; typedef unsigned char oled_cmd_t ; static int oled_write_cmd_data ( uint8_t data, oled_cmd_t cmd) { int ret = 0 ; if (cmd == OLED_CMD) gpio_set_value(oled_dev->dc_gpio, OLED_CMD); /* 拉低,表示写入的是指令 */ else gpio_set_value(oled_dev->dc_gpio, OLED_DATA); /* 拉高,表示写入数据 */ ret = oled_spi_write(&data, sizeof (data)); return ret; } static int oled_write_datas ( uint8_t *datas, uint16_t len) { int ret = 0 ; gpio_set_value(oled_dev->dc_gpio, OLED_DATA); ret = oled_spi_write(datas, len); return ret; } 设置OLED显示坐标:
static int oled_set_pos ( uint16_t x, uint16_t y) { int ret = 0 ; ret = oled_write_cmd_data( 0xb0 + y, OLED_CMD); ret = oled_write_cmd_data((x & 0x0f ), OLED_CMD); ret = oled_write_cmd_data(((x & 0xf0 ) >> 4 ) | 0x10 , OLED_CMD); return ret; } OLED复位 :
static void oled_set_rst ( uint8_t on_off) { gpio_set_value(oled_dev->rst_gpio, on_off); } static void oled_reset ( void ) { oled_set_rst( 0 ); mdelay( 50 ); oled_set_rst( 1 ); } OLED显示开关:
static void oled_disp_on_off ( uint8_t on_off) { if (on_off) oled_write_cmd_data( 0xaf , OLED_CMD); /* set dispkay on */ else oled_write_cmd_data( 0xae , OLED_CMD); /* set dispkay off */ } OLED清屏:
static void oled_disp_clear ( void ) { uint8_t x, y; for (y = 0 ; y < 8 ; y++) { oled_set_pos( 0 , y); for (x = 0 ; x < 128 ; x++) oled_write_cmd_data( 0 , OLED_DATA); /* 清零 */ } } OLED初始化:
static void oled_disp_test ( void ) { uint8_t x, y; for (y = 0 ; y < 8 ; y++) { oled_set_pos( 0 , y); for (x = 0 ; x < 128 ; x++) { if (x % 2 == 0 ) oled_write_cmd_data( 0 , OLED_DATA); else oled_write_cmd_data( 1 , OLED_DATA); } } } static int oled_init ( void ) { int ret = 0 ; oled_reset(); ret = oled_write_cmd_data( 0xae , OLED_CMD); //关闭显示 ret = oled_write_cmd_data( 0x00 , OLED_CMD); //设置 lower column address ret = oled_write_cmd_data( 0x10 , OLED_CMD); //设置 higher column address ret = oled_write_cmd_data( 0x40 , OLED_CMD); //设置 display start line ret = oled_write_cmd_data( 0xB0 , OLED_CMD); //设置page address ret = oled_write_cmd_data( 0x81 , OLED_CMD); // contract control ret = oled_write_cmd_data( 0x66 , OLED_CMD); // 128 ret = oled_write_cmd_data( 0xa1 , OLED_CMD); //设置 segment remap ret = oled_write_cmd_data( 0xa6 , OLED_CMD); // normal /reverse ret = oled_write_cmd_data( 0xa8 , OLED_CMD); // multiple ratio ret = oled_write_cmd_data( 0x3f , OLED_CMD); // duty = 1/64 ret = oled_write_cmd_data( 0xc8 , OLED_CMD); // com scan direction ret = oled_write_cmd_data( 0xd3 , OLED_CMD); // set displat offset ret = oled_write_cmd_data( 0x00 , OLED_CMD); // ret = oled_write_cmd_data( 0xd5 , OLED_CMD); // set osc division ret = oled_write_cmd_data( 0x80 , OLED_CMD); // ret = oled_write_cmd_data( 0xd9 , OLED_CMD); // ser pre-charge period ret = oled_write_cmd_data( 0x1f , OLED_CMD); // ret = oled_write_cmd_data( 0xda , OLED_CMD); // set com pins ret = oled_write_cmd_data( 0x12 , OLED_CMD); // ret = oled_write_cmd_data( 0xdb , OLED_CMD); // set vcomh ret = oled_write_cmd_data( 0x30 , OLED_CMD); // ret = oled_write_cmd_data( 0x8d , OLED_CMD); // set charge pump disable ret = oled_write_cmd_data( 0x14 , OLED_CMD); // ret = oled_write_cmd_data( 0xaf , OLED_CMD); // set dispkay on oled_disp_clear(); oled_set_pos( 0 , 0 ); return ret; } oled_reset,初始化必须调用复位,不然显示不了 oled_disp_clear,初始化还需要清屏,不然可能初始化完屏幕会显示乱码。 2、OLED Linux SPI驱动框架 数据结构、命令定义:oled_def.h
# ifndef _OLED_DEF_H_ # define _OLED_DEF_H_ typedef unsigned char uint8_t ; typedef unsigned short uint16_t ; typedef unsigned int uint32_t ; # define OLED_CMD_SET_XY 0x01 /* 显示开关*/ # define OLED_CMD_WRITE_DATAS 0x02 # define OLED_CMD_SET_XY_WRITE_DATAS 0x03 # define OLED_CMD_DISP_ON_OFF 0x04 # define CMD_COMBINE(cmd, datasize) (cmd | (datasize << 8)) /* 命令和数据大小组合 */ struct oled_disp_buffer { uint8_t x; uint8_t y; uint16_t len; uint8_t *buffer; }; typedef struct oled_disp_buffer oled_disp_buf_t ; # endif /* _OLED_DEF_H_ */ 核心的file_operations:
//static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) //{ // int ret = 0; // oled_disp_test(); // printk("%s %s\r\n", __FUNCTION__, DEV_NAME); // ret = size; // return ret; //} static long _drv_ioctl( struct file *filp, unsigned int cmd, unsigned long arg) { int ret = 0 ; uint8_t buf[ 3 ]; uint16_t size; const void __user *userspace = ( const void __user *)arg; switch (cmd & 0x0f ) /* 最低字节存放命令字段 */ { case OLED_CMD_DISP_ON_OFF: ret = copy_from_user(&buf[ 0 ], userspace, 1 ); oled_disp_on_off(buf[ 0 ]); break ; case OLED_CMD_SET_XY: ret = copy_from_user(&buf, userspace, 2 ); if (ret > 0 ) { ret = -EFAULT; goto exit ; } // printk("x %d, y %d\r", buf[0], buf[1]); oled_set_pos(buf[ 0 ], buf[ 1 ]); break ; case OLED_CMD_WRITE_DATAS: size = ( uint16_t )(cmd & 0xffffff00 ); /* 前三字节存放数据大小 */ size >>= 8 ; // printk("size %d\r", size); ret = copy_from_user(oled_dev->databuf, userspace, size); if (ret > 0 ) { ret = -EFAULT; goto exit ; } oled_write_datas(oled_dev->databuf, size); case OLED_CMD_SET_XY_WRITE_DATAS: ret = copy_from_user(buf, userspace, size); if (ret > 0 ) { ret = -EFAULT; goto exit ; } break ; } exit : return ret; } static int _drv_release( struct inode *node, struct file *filp) { struct oled_device * tmp_oled = filp->private_data; oled_disp_on_off( 0 ); oled_reset(); return 0 ; } static struct file_operations oled_drv_ops = { .owner = THIS_MODULE, .open = _drv_open, // .read = _drv_read, .unlocked_ioctl = _drv_ioctl, .release = _drv_release, }; probe和remove实现:
static int _driver_probe( struct spi_device *spi) { int err = 0 ; struct device *_ dev ; struct device_node *_ dts_node ; // struct device_node *oled_dev_node; oled_dev = ( struct oled_device *)kzalloc( sizeof ( struct oled_device), GFP_KERNEL); if (!oled_dev) { printk( "can't kzalloc mpu6050 dev\n" ); return -ENOMEM; } _dts_node = spi->dev.of_node; if (!_dts_node) { printk( "oled espi can not found!\r\n" ); err = -EINVAL; goto exit_free_dev; } oled_dev->dc_gpio = of_get_named_gpio(_dts_node, DC_GPIO_DTS_NAME, 0 ); /* 获取dc_gpio */ if (!gpio_is_valid(oled_dev->dc_gpio)) { printk( "don't get oled %s!!!\n" , DC_GPIO_DTS_NAME); err = -EINVAL; goto exit_free_dev; } printk( "oled dc-gpio %d" , oled_dev->dc_gpio); gpio_direction_output(oled_dev->dc_gpio, 1 ); /* 设置gpio为输入 */ oled_dev->rst_gpio = of_get_named_gpio(_dts_node, RST_GPIO_DTS_NAME, 0 ); /* 获取rst_gpio */ if (!gpio_is_valid(oled_dev->rst_gpio)) { printk( "don't get oled %s!!!\n" , RST_GPIO_DTS_NAME); err = -EINVAL; goto exit_free_dev; } printk( "oled dc-gpio %d" , oled_dev->rst_gpio); gpio_direction_output(oled_dev->rst_gpio, 1 ); /* 设置gpio为输入 */ /* 内核自动分配设备号 */ err = alloc_chrdev_region(&oled_dev->dev_no, 0 , 1 , DEV_NAME); if (err < 0 ) { pr_err( "Error: failed to register oled, err: %d\n" , err); goto exit_free_dev; } cdev_init(&oled_dev->chrdev, &oled_drv_ops); err = cdev_add(&oled_dev->chrdev, oled_dev->dev_no, 1 ); if (err) { printk( "cdev add failed\r\n" ); goto exit_unregister; } oled_dev-> class = class_create(THIS_MODULE, DEV_NAME); if (IS_ERR(oled_dev->class)) { err = PTR_ERR(oled_dev->class); goto exit_cdev_del; } /* 创建设备节点 */ _dev = device_create(oled_dev->class, NULL , oled_dev->dev_no, NULL , DEV_NAME); if (IS_ERR(_dev)) { /* 判断指针是否合法 */ err = PTR_ERR(_dev); goto exit_class_del; } oled_dev->spi = spi; mutex_init(&oled_dev->m_lock); /* 初始化互斥锁 */ printk( "%s probe success\r\n" , DEV_NAME); goto exit ; exit_class_del: class_destroy(oled_dev->class); exit_cdev_del: cdev_del(&oled_dev->chrdev); exit_unregister: unregister_chrdev_region(oled_dev->dev_no, 1 ); /* 注销设备 */ exit_free_dev: kfree(oled_dev); oled_dev = NULL ; exit : return err; } static int _driver_remove( struct spi_device *spi) { int ret = 0 ; device_destroy(oled_dev->class, oled_dev->dev_no); class_destroy(oled_dev->class); cdev_del(&oled_dev->chrdev); unregister_chrdev_region(oled_dev->dev_no, 1 ); /* 注销设备 */ kfree(oled_dev); printk(KERN_INFO "%s remove success\n" , DEV_NAME); return ret; } 出口入口函数实现:
/* 设备树的匹配列表 */ static struct of_device_id dts_match_table [] = { {.compatible = OLED_DTS_COMPATIBLE}, /* 通过设备树来匹配 */ {}, }; /* 传统匹配方式 ID 列表 */ static const struct spi_device_id spi_dev_id [] = { {.name = OLED_DTS_COMPATIBLE, 0 }, {}}; /* SPI 驱动结构体 */ static struct spi_driver oled_driver = { .probe = _driver_probe, .remove = _driver_remove, .driver = { .owner = THIS_MODULE, .name = OLED_DTS_COMPATIBLE, .of_match_table = dts_match_table, }, .id_table = spi_dev_id, }; module_spi_driver(oled_driver); MODULE_AUTHOR( "Ares" ); MODULE_LICENSE( "GPL" ); 3、驱动编译 oled\Makefile :
KERN_DIR = /home/ares/work/ebf_linux_kernel-ebf_4. 19.35 _imx6ul all: make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o oled_test oled_test.c clean: make -C $(KERN_DIR) M=`pwd` clean rm -rf modules.order oled_test # 参考内核源码drivers/ char /ipmi/Makefile # 要想把a.c, b.c编译成ab.ko, 可以这样指定: # ab-y := a.o b.o # obj-m += ab.o obj-m += oled_drv.o 执行make
测试程序 # include "stdio.h" # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> # include <unistd.h> # include <stdio.h> # include <string.h> # include <sys/ioctl.h> # include <poll.h> # include <stdint.h> # include <stdlib.h> # include "oled_def.h" # include "font.h" # define DEV_NAME "/dev/oled" int oled_fd; void sleep_ms ( unsigned int ms) { struct timeval delay ; delay.tv_sec = 0 ; delay.tv_usec = ms * 1000 ; select( 0 , NULL , NULL , NULL , &delay); } void oled_disp_char ( int x, int y, unsigned char c) { int i = 0 ; /* 得到字模 */ const unsigned char *dots = oled_asc2_8x16[c - ' ' ]; char pos[ 2 ]; # if 0 /* 发给OLED */ OLED_DIsp_Set_Pos(x, y); /* 发出8字节数据 */ for (i = 0 ; i < 8 ; i++) oled_write_cmd_data(dots[i], OLED_DATA); # endif pos[ 0 ] = x; pos[ 1 ] = y; ioctl(oled_fd, CMD_COMBINE(OLED_CMD_SET_XY, 2 ), &pos); ioctl(oled_fd, CMD_COMBINE(OLED_CMD_WRITE_DATAS, 8 ), dots); # if 0 OLED_DIsp_Set_Pos(x, y+ 1 ); /* 发出8字节数据 */ for (i = 0 ; i < 8 ; i++) oled_write_cmd_data(dots[i+ 8 ], OLED_DATA); # endif pos[ 0 ] = x; pos[ 1 ] = y+ 1 ; ioctl(oled_fd, CMD_COMBINE(OLED_CMD_SET_XY, 2 ), pos); ioctl(oled_fd, CMD_COMBINE(OLED_CMD_WRITE_DATAS, 8 ), &dots[ 8 ]); } void oled_disp_string ( uint8_t x, uint8_t y, char *str) { uint8_t j = 0 ; while (str[j]) { oled_disp_char(x, y, str[j]); /* 显示单个字符 */ x += 8 ; if (x > 127 ) { x = 0 ; y += 2 ; } j++; /* 移动显示位置 */ } } static void oled_test ( void ) { oled_disp_string( 0 , 0 , "Sad!" ); oled_disp_string( 0 , 2 , "Bad!" ); oled_disp_string( 0 , 4 , "Moonlight" ); } int main ( int argc, char **argv) { int ret; /* 2. 打开文件 */ oled_fd = open(DEV_NAME, O_RDWR | O_NONBLOCK); // | O_NONBLOCK if (oled_fd < 0 ) { printf ( "can not open file %s, %d\n" , DEV_NAME, oled_fd); return -1 ; } oled_test(); sleep_ms( 5000 ); close(oled_fd); return 0 ; } 测试:
sudo insmod oled_drv.ko /* 安装驱动 */ sudo ./oled_test /* 执行app程序 */ sudo rmmod oled_drv.ko /* 卸载驱动 */