Linux驱动 | OLED显示模块驱动(SPI)
设备树驱动实现测试程序
·
SPI子系统
linux 驱动 | SPI子系统_★_仰望星空_★的博客-CSDN博客
https://blog.csdn.net/qq_36413982/article/details/123783511
OLED显示模块
- 0.96寸
- 分辨率128 x 64
- 使用驱动芯片SSD1306
- 支持I2C、4线SPI、3线SPI接口
OLED模块硬件接口具有6个引脚:
- VCC
- GND
- SCL,无论是SPI还是I2C接口时,它都是始终引脚
- SDA,当使用I2C接口,它是I2C的SDA,当使用SPI,它是MOSI
- RST,复位引脚
- DC,命令数据选择引脚
- 当DC引脚拉低,表示写入的是命令
- 当DC引脚拉高,表示写入的是数据
注意事项:
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 /* 卸载驱动 */
更多推荐
已为社区贡献3条内容
所有评论(0)