之前买了个开发板,但是板载没有I2C和SPI的资源,但是公司要用到SPI,想练练手,并且熟悉一下linux下的SPI总线设备的开发,于是买了一款0.96寸的OLED显示屏,驱动芯片型号为SSD1306 来玩
我买的这个oled屏幕是七针的,如下图: 支持SPI和I2C协议,不过要用I2C的话需要改电阻,我这里使用SPI协议。
官方例程有51的,有stm32的,有arduino的,基本是MCU的,需要自己移植到linux下,开搞!
设备树修改
用到SPI3这个spi控制器,于是在设备树中创建: pinctrl_ecspi3 节点
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x100b1 /* MISO*/
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x100b1 /* MOSI*/
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x100b1 /* CLK*/
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x100b0 /* CS*/
>;
};
因为这个oled屏幕有reset脚和dc命令脚,所以再用两个GPIO:
pinctrl_oled96_reset: oled96resetgrp {
fsl,pins = <
/* used for oled96 reset */
MX6UL_PAD_UART3_TX_DATA__GPIO1_IO24 0x10B0
>;
};
pinctrl_oled96_DC: oled96DCgrp {
fsl,pins = <
/* used for oled96 DC */
MX6UL_PAD_UART3_RX_DATA__GPIO1_IO25 0x10B0
>;
};
创建oled96spi 节点:
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
reset-gpio = <&gpio1 24 GPIO_ACTIVE_LOW>;
dc-gpio = <&gpio1 25 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3
&pinctrl_oled96_reset
&pinctrl_oled96_DC>;
status = "okay";
spidev: oled96spi@0 {
compatible = "alientek,oled96spi";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
我把两个新的gpio用这种方式加进来了,有啥更好的方式欢迎大佬评论区留言!
注意:上面修改设备树的时候要看下有没有IO被占用了,把占用的IO注释掉,我这里注释掉了uart2 节点。
编写驱动
这里用SPI总线方式编写设备驱动,主机驱动已经好了。
#include <linux/types.h>
.../*省略头文件*/
/***************************************************************
文件名 : oled96spi.c
作者 : 1997AURORA
版本 : V1.0
描述 : SSD1306 SPI驱动程序
其他 : 无
日志 : 初版V1.0 2021/7/31 1997AURORA创建
***************************************************************/
#define oled96_CNT 1
#define oled96_NAME "oled96spi"
#define OLED_CMD 0
#define OLED_DATA 1
static u8 OLED_GRAM[144][8];
unsigned char BMP1[] =
{
/*这里存放64*16个Bytes的数据,即128*64像素
字数太多,删除。可以使用PCtoLCD2002取模*/
};
unsigned char BMP2[] =
{
/*同理,删除*/
/*"C:\Users\zjh46\Desktop\2.BMP",0*/
};
struct oled96_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
int cs_gpio; /* 片选所使用的GPIO编号 */
int reset_gpio; /* 片选所使用的GPIO编号 */
int dc_gpio; /* 片选所使用的GPIO编号 */
//u8 OLED_GRAM[144][8]; /* 显存 */
};
static struct oled96_dev oled96dev;
static s32 oled96_write_regs(struct oled96_dev *dev, u8 *buf, u8 len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低 */
t->tx_buf = buf;
t->len = len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
kfree(t);
gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放oled96 */
return ret;
}
static void oled96_write_onereg(struct oled96_dev *dev, u8 value, u8 cmd)
{
u8 buf = value;
if(cmd)
gpio_set_value(dev->dc_gpio, 1);
else
gpio_set_value(dev->dc_gpio, 0);
oled96_write_regs(dev, &buf, 1);
gpio_set_value(dev->dc_gpio, 1);
}
//更新显存到OLED
static void OLED_Refresh(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
oled96_write_onereg(&oled96dev, 0xb0+i,OLED_CMD); //设置行起始地址
oled96_write_onereg(&oled96dev, 0x00,OLED_CMD); //设置低列起始地址
oled96_write_onereg(&oled96dev, 0x10,OLED_CMD); //设置高列起始地址
for(n=0;n<128;n++)
oled96_write_onereg(&oled96dev,OLED_GRAM[n][i],OLED_DATA);
}
}
//清屏函数
static void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
for(n=0;n<128;n++)
{
OLED_GRAM[n][i]=0;//清除所有数据
}
}
OLED_Refresh();//更新显示
}
//画点
//x:0~127
//y:0~63
static void OLED_DrawPoint(u8 x,u8 y)
{
u8 i,m,n;
i=y/8;
m=y%8;
n=1<<m;
OLED_GRAM[x][i]|=n;
}
//x,y:圆心坐标
//r:圆的半径
static void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
int a, b,num;
a = 0;
b = r;
while(2 * b * b >= r * r)
{
OLED_DrawPoint(x + a, y - b);
OLED_DrawPoint(x - a, y - b);
OLED_DrawPoint(x - a, y + b);
OLED_DrawPoint(x + a, y + b);
OLED_DrawPoint(x + b, y + a);
OLED_DrawPoint(x + b, y - a);
OLED_DrawPoint(x - b, y - a);
OLED_DrawPoint(x - b, y + a);
a++;
num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
if(num > 0)
{
b--;
a--;
}
}
}
//配置写入数据的起始位置
void OLED_WR_BP(u8 x,u8 y)
{
oled96_write_onereg(&oled96dev, 0xb0+y,OLED_CMD);//设置行起始地址
oled96_write_onereg(&oled96dev, ((x&0xf0)>>4)|0x10,OLED_CMD);//设置低列起始地址
oled96_write_onereg(&oled96dev, (x&0x0f)|0x01,OLED_CMD);//设置高列起始地址
}
//x0,y0:起点坐标
//x1,y1:终点坐标
//BMP[]:要写入的图片数组
void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[])
{
u32 j=0;
u8 x=0,y=0;
if(y%8==0)y=0;
else y+=1;
for(y=y0;y<y1;y++)
{
OLED_WR_BP(x0,y);
for(x=x0;x<x1;x++)
{
oled96_write_onereg(&oled96dev, BMP[j],OLED_DATA);
j++;
}
}
}
static int oled96_open(struct inode *inode, struct file *filp)
{
filp->private_data = &oled96dev;
return 0;
}
static ssize_t oled96_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
return 0;
}
static ssize_t oled96_write(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
unsigned char databuf[1];
unsigned char oledstat;
long err = 0;
struct oled96_dev *dev = (struct oled96_dev *)filp->private_data;
err = copy_from_user(databuf, buf, sizeof(cnt));
if(err < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
oledstat = databuf[0]; /* 获取终端值 */
if(oledstat == 0) {
OLED_Clear();
OLED_DrawCircle(72,20,20);
OLED_Refresh();
printk("draw a 20 circle\r\n");
} else if(oledstat == 1) {
OLED_Clear();
OLED_ShowPicture(0,0,128,8,BMP2);
//OLED_Refresh();
printk("draw a picture\r\n");
}
return 0;
}
static int oled96_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations oled96_ops = {
.owner = THIS_MODULE,
.open = oled96_open,
.read = oled96_read,
.write = oled96_write,
.release = oled96_release,
};
void oled96_reginit(struct oled96_dev *dev)
{
u8 value = 0;
gpio_set_value(dev->reset_gpio, 1);
mdelay(100);
gpio_set_value(dev->reset_gpio, 0);//复位
mdelay(200);
gpio_set_value(dev->reset_gpio, 1);
/*移植自stm32例程的初始化,修改了函数调用*/
oled96_write_onereg(&oled96dev, 0xAE, OLED_CMD);
oled96_write_onereg(&oled96dev, 0xAE,OLED_CMD);//--turn off oled panel
oled96_write_onereg(&oled96dev, 0x00,OLED_CMD);//---set low column address
oled96_write_onereg(&oled96dev, 0x10,OLED_CMD);//---set high column address
oled96_write_onereg(&oled96dev, 0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
oled96_write_onereg(&oled96dev, 0x81,OLED_CMD);//--set contrast control register
oled96_write_onereg(&oled96dev, 0xCF,OLED_CMD);// Set SEG Output Current Brightness
oled96_write_onereg(&oled96dev, 0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
oled96_write_onereg(&oled96dev, 0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
oled96_write_onereg(&oled96dev, 0xA6,OLED_CMD);//--set normal display
oled96_write_onereg(&oled96dev, 0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
oled96_write_onereg(&oled96dev, 0x3f,OLED_CMD);//--1/64 duty
oled96_write_onereg(&oled96dev, 0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
oled96_write_onereg(&oled96dev, 0x00,OLED_CMD);//-not offset
oled96_write_onereg(&oled96dev, 0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
oled96_write_onereg(&oled96dev, 0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
oled96_write_onereg(&oled96dev, 0xD9,OLED_CMD);//--set pre-charge period
oled96_write_onereg(&oled96dev, 0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
oled96_write_onereg(&oled96dev, 0xDA,OLED_CMD);//--set com pins hardware configuration
oled96_write_onereg(&oled96dev, 0x12,OLED_CMD);
oled96_write_onereg(&oled96dev, 0xDB,OLED_CMD);//--set vcomh
oled96_write_onereg(&oled96dev, 0x40,OLED_CMD);//Set VCOM Deselect Level
oled96_write_onereg(&oled96dev, 0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
oled96_write_onereg(&oled96dev, 0x02,OLED_CMD);//
oled96_write_onereg(&oled96dev, 0x8D,OLED_CMD);//--set Charge Pump enable/disable
oled96_write_onereg(&oled96dev, 0x14,OLED_CMD);//--set(0x10) disable
oled96_write_onereg(&oled96dev, 0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
oled96_write_onereg(&oled96dev, 0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
oled96_write_onereg(&oled96dev, 0xAF,OLED_CMD);
OLED_Clear();
}
static int oled96_probe(struct spi_device *spi)
{
int ret = 0;
if (oled96dev.major) {
oled96dev.devid = MKDEV(oled96dev.major, 0);
register_chrdev_region(oled96dev.devid, oled96_CNT, oled96_NAME);
} else {
alloc_chrdev_region(&oled96dev.devid, 0, oled96_CNT, oled96_NAME);
oled96dev.major = MAJOR(oled96dev.devid);
}
cdev_init(&oled96dev.cdev, &oled96_ops);
cdev_add(&oled96dev.cdev, oled96dev.devid, oled96_CNT);
oled96dev.class = class_create(THIS_MODULE, oled96_NAME);
if (IS_ERR(oled96dev.class)) {
return PTR_ERR(oled96dev.class);
}
oled96dev.device = device_create(oled96dev.class, NULL, oled96dev.devid, NULL, oled96_NAME);
if (IS_ERR(oled96dev.device)) {
return PTR_ERR(oled96dev.device);
}
oled96dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(oled96dev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
oled96dev.cs_gpio = of_get_named_gpio(oled96dev.nd, "cs-gpio", 0);
if(oled96dev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
oled96dev.reset_gpio = of_get_named_gpio(oled96dev.nd, "reset-gpio", 0);
if(oled96dev.reset_gpio < 0) {
printk("can't get reset-gpio");
return -EINVAL;
}
oled96dev.dc_gpio = of_get_named_gpio(oled96dev.nd, "dc-gpio", 0);
if(oled96dev.dc_gpio < 0) {
printk("can't get reset-gpio");
return -EINVAL;
}
ret = gpio_direction_output(oled96dev.cs_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
ret = gpio_direction_output(oled96dev.reset_gpio, 1);
if(ret < 0) {
printk("can't set res gpio!\r\n");
}
ret = gpio_direction_output(oled96dev.dc_gpio, 1);
if(ret < 0) {
printk("can't set dc gpio!\r\n");
}
spi->mode = SPI_MODE_0;
spi_setup(spi);
oled96dev.private_data = spi;
/* 初始化oled96内部寄存器 */
oled96_reginit(&oled96dev);
OLED_DrawCircle(72,30,30);
OLED_Refresh();
printk("draw a circle\r\n");
return 0;
}
static int oled96_remove(struct spi_device *spi)
{
cdev_del(&oled96dev.cdev);
unregister_chrdev_region(oled96dev.devid, oled96_CNT);
device_destroy(oled96dev.class, oled96dev.devid);
class_destroy(oled96dev.class);
return 0;
}
static const struct spi_device_id oled96_id[] = {
{"alientek,oled96spi", 0},
{}
};
static const struct of_device_id oled96_of_match[] = {
{ .compatible = "alientek,oled96spi" },
{ /* Sentinel */ }
};
static struct spi_driver oled96_driver = {
.probe = oled96_probe,
.remove = oled96_remove,
.driver = {
.owner = THIS_MODULE,
.name = "oled96spi",
.of_match_table = oled96_of_match,
},
.id_table = oled96_id,
};
static int __init oled96_init(void)
{
return spi_register_driver(&oled96_driver);
}
static void __exit oled96_exit(void)
{
spi_unregister_driver(&oled96_driver);
}
module_init(oled96_init);
module_exit(oled96_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1997AURORA");
编写APP程序
#include <linux/types.h>
.../*省略头文件*/
/***************************************************************
文件名 : oled96spiApp.c
作者 : 1997AURORA
版本 : V1.0
描述 : SSD1306 SPI应用程序
其他 : 无
日志 : 初版V1.0 2021/7/31 1997AURORA创建
***************************************************************/
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); /* 打开oled96spi驱动 */
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作*/
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("oled Control Failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
这个程序可以操作这个设备,使其显示不同的东西,这里程序写的简单,只有两种,一种是画圆,一种是显示图片 。
我这里测试的log是这样的:
/ #
/ # cd /lib/modules/4.1.15/
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe oled96spi.ko
draw a circle
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 1
random: nonblocking pool is initialized
draw a picture
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 0
draw a 20 circle
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 1
draw a picture
/lib/modules/4.1.15 #
显示图片的时候如下图:
|