现在的位置: 首页 > 操作系统 > 正文

SPI子系统之驱动SSD1306OLED

2020年02月13日 操作系统 ⁄ 共 28036字 ⁄ 字号 评论关闭

接触Linux之前,曾以为读源码可以更快的学习软件,于是前几篇文章都是一边读源码一边添加注释(http://www.xuebuyuan.com/Linux/2016-10/136246.htm),甚至精读到每一行代码,实际上效果并不理想,看过之后就忘记了。主要原因是没理解透程序架构,各个模块之间的关系,如何联系在一起,再加上没有实例验证。后来逐渐发现,理解框架能达到事半功倍的效果,理解框架之后,反而代码也更容易看懂,甚至可以猜部分代码的作用,印象更加深刻。

理解SPI的驱动框架,还是从最基本的三个入口点触发,platform_device,platform_bus,platform_driver。

其中内核一提供给platform_bus,platform_driver在spi_s3c24xx_gpio.c和spi_s3c24xxc.c中,其中spi_s3c24xx_gpio.c用于IO模拟SPI (本例讨论的是IO模拟SPI),spi_s3c24xxc.c用于s3c24xx的硬件SPI。因此,我们需要动手写一个platform_device。

看看spi_s3c24xx_gpio.c做了些什么。

static int s3c2410_spigpio_probe(struct platform_device *dev){ ... ... /* [cgw]: 分配一个SPI主机 */ master = spi_alloc_master(&dev->dev, sizeof(struct s3c2410_spigpio)); ... ...

sp = spi_master_get_devdata(master);

platform_set_drvdata(dev, sp);

/* [cgw]: 分配与spi硬件相关的配置,如指定哪些IO为MISO,MOSI,SCLK,CS,SPI工作模式,最大时钟等等 */ /* copy in the plkatform data */ sp->info = dev->dev.platform_data;

/* [cgw]: 提供实现SPI各种模式的时序的基本方法,和CS的激活方法 */ /* setup spi bitbang adaptor */ sp->bitbang.master = spi_master_get(master); sp->bitbang.chipselect = s3c2410_spigpio_chipselect;

sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0; sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1; sp->bitbang.txrx_word[SPI_MODE_2] = s3c2410_spigpio_txrx_mode2; sp->bitbang.txrx_word[SPI_MODE_3] = s3c2410_spigpio_txrx_mode3;

/* [cgw]: 配置相关io为输入输出 */ /* set state of spi pins */ s3c2410_gpio_setpin(sp->info->pin_clk, 0); s3c2410_gpio_setpin(sp->info->pin_mosi, 0);

s3c2410_gpio_cfgpin(sp->info->pin_clk, S3C2410_GPIO_OUTPUT); s3c2410_gpio_cfgpin(sp->info->pin_mosi, S3C2410_GPIO_OUTPUT); s3c2410_gpio_cfgpin(sp->info->pin_miso, S3C2410_GPIO_INPUT);

/* [cgw]: 设置spi的收发,如注册一个工作队列,收发时序的方法,8/16/32的spi数据等等 */ ret = spi_bitbang_start(&sp->bitbang); ... ...

/* [cgw]: 注册sp->info->board_size个spi设备,这几个spi设备都是挂接在统一spi总线上的 */ /* register the chips to go with the board */ for (i = 0; i < sp->info->board_size; i++) { dev_info(&dev->dev, "registering %p: %s\n", &sp->info->board_info[i], sp->info->board_info[i].modalias);

sp->info->board_info[i].controller_data = sp; spi_new_device(master, sp->info->board_info + i); } ... ...}

    注册了一个platform_driver在s3c2410_spigpio_probe中,分配并注册了一个spi主机,并注册了挂接在这个SPI主机上的所有spi设备

要想s3c2410_spigpio_probe得到调用,即探测到有效的platform_device,我们需要一个与platform同名("s3c24xx-spi-gpio")的platform_device。

static struct spi_board_info board_info[1] = { { .modalias = "spi_ssd1306", /* [cgw]: spi设备名,和设备驱动名对应 */ .bus_num = 0, /* [cgw]: spi总线号,即spi0 */ .chip_select = 2, /* [cgw]: spi总线上的设备号,即spi0.2 */ .max_speed_hz = 50000, /* [cgw]: spi时钟 */ .mode = SPI_MODE_3, /* [cgw]: spi数据模式 */ },};

static struct s3c2410_spigpio_info spi_dev = { .pin_clk = S3C2410_GPG7, .pin_mosi = S3C2410_GPG5, .pin_miso = S3C2410_GPG6, .board_size = 1, /* [cgw]: 设置板上spi接口数量为1 */ .board_info = &board_info[0], .chip_select = ssd1306_chip_select};

static struct platform_device spi_platform_dev = { .name = "s3c24xx-spi-gpio", /* [cgw]: 设置平台设备名,和平台驱动名对应 */ .id = -1, .dev = { .release = spi_dev_release, .platform_data = (void *)&spi_dev, /* [cgw]: 通过platform_data传递spi_dev给平台驱动 * 平台驱动可以访问spi_dev */ },};

static int spi_dev_init(void){ /* [cgw]: 注册spi_platform_dev平台设备 */ platform_device_register(&spi_platform_dev); return 0;}

spi_bitbang.c提供了spi底层一些实现细节,注册工作队列(SPI数据的传送最终是通过调用工作队列实现的),spi工作模式,工作频率等。

int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t){ struct spi_bitbang_cs *cs = spi->controller_state; u8 bits_per_word; u32 hz;

if (t) { /* [cgw]: spi驱动指定几位数据模式,和传送速度 */ bits_per_word = t->bits_per_word; hz = t->speed_hz; } else { bits_per_word = 0; hz = 0; }

/* [cgw]: 根据spi位数,选择合适的时序 */ /* spi_transfer level calls that work per-word */ if (!bits_per_word) bits_per_word = spi->bits_per_word; if (bits_per_word <= 8) cs->txrx_bufs = bitbang_txrx_8; else if (bits_per_word <= 16) cs->txrx_bufs = bitbang_txrx_16; else if (bits_per_word <= 32) cs->txrx_bufs = bitbang_txrx_32; else return -EINVAL;

/* [cgw]: 设置SCLK的时钟频率 */ /* nsecs = (clock period)/2 */ if (!hz) hz = spi->max_speed_hz; if (hz) { cs->nsecs = (1000000000/2) / hz; if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000)) return -EINVAL; }

return 0;}

int spi_bitbang_setup(struct spi_device *spi){ struct spi_bitbang_cs *cs = spi->controller_state; struct spi_bitbang *bitbang; int retval;

bitbang = spi_master_get_devdata(spi->master);

/* REVISIT: some systems will want to support devices using lsb-first * bit encodings on the wire. In pure software that would be trivial, * just bitbang_txrx_le_cphaX() routines shifting the other way, and * some hardware controllers also have this support. */ /* [cgw]: 默认不支持LSB模式,要想使用LSB模式,只要bitbang_txrx_le_cphaX()改变移位的方向即可 */ if ((spi->mode & SPI_LSB_FIRST) != 0) return -EINVAL;

if (!cs) { cs = kzalloc(sizeof *cs, GFP_KERNEL); if (!cs) return -ENOMEM; spi->controller_state = cs; }

/* [cgw]: 设置spi的默认位数 */ if (!spi->bits_per_word) spi->bits_per_word = 8;

/* per-word shift register access, in hardware or bitbanging */ /* [cgw]: 设置spi的工作模式,四种 */ cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)]; if (!cs->txrx_word) return -EINVAL;

/* [cgw]: 调用spi_bitbang_setup_transfer */ retval = bitbang->setup_transfer(spi, NULL); if (retval < 0) return retval;

dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n", __FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA), spi->bits_per_word, 2 * cs->nsecs);

/* NOTE we _need_ to call chipselect() early, ideally with adapter * setup, unless the hardware defaults cooperate to avoid confusion * between normal (active low) and inverted chipselects. */

/* [cgw]: spi忙的话,通过改变CS的状态释放SPI */ /* deselect chip (low or high) */ spin_lock(&bitbang->lock); if (!bitbang->busy) { bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(cs->nsecs); } spin_unlock(&bitbang->lock);

return 0;}

static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t){ struct spi_bitbang_cs *cs = spi->controller_state; unsigned nsecs = cs->nsecs; /* [cgw]: 具体数据收发就是这里实现的 */ return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t);}

int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m){ struct spi_bitbang *bitbang; unsigned long flags; int status = 0;

m->actual_length = 0; m->status = -EINPROGRESS;

bitbang = spi_master_get_devdata(spi->master);

spin_lock_irqsave(&bitbang->lock, flags); if (!spi->max_speed_hz) status = -ENETDOWN; else { /* [cgw]: 入队一个工作到工作队列 */ list_add_tail(&m->queue, &bitbang->queue); queue_work(bitbang->workqueue, &bitbang->work); } spin_unlock_irqrestore(&bitbang->lock, flags);

return status;}

int spi_bitbang_start(struct spi_bitbang *bitbang){ int status;

if (!bitbang->master || !bitbang->chipselect) return -EINVAL; /* [cgw]: 注册一个工作队列 */ INIT_WORK(&bitbang->work, bitbang_work); spin_lock_init(&bitbang->lock); INIT_LIST_HEAD(&bitbang->queue);

/* [cgw]: 配置相关方法 */ if (!bitbang->master->transfer) bitbang->master->transfer = spi_bitbang_transfer; if (!bitbang->txrx_bufs) { bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; if (!bitbang->master->setup) { if (!bitbang->setup_transfer) bitbang->setup_transfer = spi_bitbang_setup_transfer; bitbang->master->setup = spi_bitbang_setup; bitbang->master->cleanup = spi_bitbang_cleanup; } } else if (!bitbang->master->setup) return -EINVAL;

/* [cgw]: 创建一个单线程,用于调度工作队列 */ /* this task is the only thing to touch the SPI bits */ bitbang->busy = 0; bitbang->workqueue = create_singlethread_workqueue( bitbang->master->cdev.dev->bus_id); if (bitbang->workqueue == NULL) { status = -EBUSY; goto err1; }

/* [cgw]: 注册一个spi主机 */ /* driver may get busy before register() returns, especially * if someone registered boardinfo for devices */ status = spi_register_master(bitbang->master); if (status < 0) goto err2;

return status;

err2: destroy_workqueue(bitbang->workqueue);err1: return status;}

因为在s3c2410_spigpio_probe中注册了spi的设备,因此我们还需为这些设备提供驱动,以被这些设备探测到,探测这些驱动的条件也是设备和驱动的名字同名,即spi_ssd1306。我们这里提供了一个ssd1306 OLED的驱动

static struct spi_driver spi_ssd1306_driver = { .driver = { .name = "spi_ssd1306", .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = spi_ssd1306_probe, .remove = __devexit_p(spi_ssd1306_remove),};

static int spi_ssd1306_init(void){ return spi_register_driver(&spi_ssd1306_driver);}

到这里,基本工作已经完成。怎样驱动ssd1306 OLED呢?

ssd1306 OLED的使用方法,请参考相关的手册。

本例提供的ssd1306 OLED驱动,只需要我们提供一个基本9位spi数据收发的接口即可。

static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) { struct spi_transfer t; struct spi_message m;

uint16_t data = chData; /* [cgw]: 情况spi_transfer */ memset(&t,0,sizeof(struct spi_transfer)); /* [cgw]: 第9位表示前8位是命令还是数据,1:数据,0:命令 */ if (chCmd) { data |= (1 << 8); } else { data &= ~(1 << 8); }

/* [cgw]: 要发送的数据 */ t.tx_buf = &data; /* [cgw]: 长度,2字节 */ t.len = 2; /* [cgw]: 9位spi */ t.bits_per_word = 9; //t.cs_change = 1; /* [cgw]: 把数据添加到收发列表,工作队列调度时会从收发队列中取出,并进行收发 * 注意这里并没有直接收发 */ spi_message_init(&m); spi_message_add_tail(&t, &m); spi_sync(spi_ssd1306_dev, &m);}

注意,在网上看到一些例子,用8位模式驱动ssd1306 OLED的,需要用DC的状态来表示数据或命令的,他们的做法如下:

void ssd1306_write_cmd(uint8_t cmd){ ssd1306_dc_clr(); spi_write(cmd); ssd1306_dc_set();}

void ssd1306_write_data(uint8_t data){ ssd1306_dc_set(); spi_write(data); ssd1306_dc_clr();}

我本人认为是不正确的,至少不符合这个spi框架的逻辑,因为spi数据的收发并不是直接在spi_write()实现,而是在工作队列bitbang_work()中实现。尽管这样仍然能驱动ssd1306 OLED,但理论上不应该这么做。要改的话应该改bitbang_work()中改,添加DC状态的控制。

static void bitbang_work(struct work_struct *work){ struct spi_bitbang *bitbang = container_of(work, struct spi_bitbang, work); unsigned long flags;

spin_lock_irqsave(&bitbang->lock, flags); bitbang->busy = 1; /* [cgw]: 队列不为空 */ while (!list_empty(&bitbang->queue)) { struct spi_message *m; struct spi_device *spi; unsigned nsecs; struct spi_transfer *t = NULL; unsigned tmp; unsigned cs_change; int status; int (*setup_transfer)(struct spi_device *, struct spi_transfer *);

/* [cgw]: 取出spi_message */ m = container_of(bitbang->queue.next, struct spi_message, queue); /* [cgw]: 删除这个节点 */ list_del_init(&m->queue); /* [cgw]: 进入临界区 */ spin_unlock_irqrestore(&bitbang->lock, flags);

/* FIXME this is made-up ... the correct value is known to * word-at-a-time bitbang code, and presumably chipselect() * should enforce these requirements too? */ nsecs = 100;

spi = m->spi; tmp = 0; cs_change = 1; status = 0; setup_transfer = NULL;

/* [cgw]: 历遍spi_message中的收发列表 */ list_for_each_entry (t, &m->transfers, transfer_list) {

/* override or restore speed and wordsize */ /* [cgw]: 如果驱动指定了spi速度,和位数,重新调用spi_bitbang_setup_transfer * 更改默认设置 */ if (t->speed_hz || t->bits_per_word) { setup_transfer = bitbang->setup_transfer; if (!setup_transfer) { status = -ENOPROTOOPT; break; } } if (setup_transfer) { status = setup_transfer(spi, t); if (status < 0) break; }

/* set up default clock polarity, and activate chip; * this implicitly updates clock and spi modes as * previously recorded for this device via setup(). * (and also deselects any other chip that might be * selected ...) */ /* [cgw]: 激活spi */ if (cs_change) { bitbang->chipselect(spi, BITBANG_CS_ACTIVE); ndelay(nsecs); } /* [cgw]: 驱动指定收发完一帧数据要不要改变恢复CS为空闲 */ cs_change = t->cs_change; /* [cgw]: 收发包为空,则无效 */ if (!t->tx_buf && !t->rx_buf && t->len) { status = -EINVAL; break; }

/* transfer data. the lower level code handles any * new dma mappings it needs. our caller always gave * us dma-safe buffers. */ if (t->len) { /* REVISIT dma API still needs a designated * DMA_ADDR_INVALID; ~0 might be better. */ if (!m->is_dma_mapped) t->rx_dma = t->tx_dma = 0; /* [cgw]: 这里才是真正的实现spi收发时序 */ status = bitbang->txrx_bufs(spi, t); } if (status != t->len) { if (status > 0) status = -EMSGSIZE; break; } m->actual_length += status; status = 0;

/* protocol tweaks before next transfer */ if (t->delay_usecs) udelay(t->delay_usecs); /* [cgw]: 收发完一帧,不改变CS状态 */ if (!cs_change) continue; /* [cgw]: 收发列表已经没有数据,结束 */ if (t->transfer_list.next == &m->transfers) break;

/* sometimes a short mid-message deselect of the chip * may be needed to terminate a mode or command */ /* [cgw]: 释放spi */ ndelay(nsecs); bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(nsecs); }

m->status = status; m->complete(m->context);

/* restore speed and wordsize */ /* [cgw]: 速度和位数恢复默认 */ if (setup_transfer) setup_transfer(spi, NULL);

/* normally deactivate chipselect ... unless no error and * cs_change has hinted that the next message will probably * be for this chip too. */ if (!(status == 0 && cs_change)) { ndelay(nsecs); bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(nsecs); }

spin_lock_irqsave(&bitbang->lock, flags); } bitbang->busy = 0; /* [cgw]: 退出临界区 */ spin_unlock_irqrestore(&bitbang->lock, flags);}

代码:

spi_platform_dev.c

#include <asm/arch/spi-gpio.h>

static struct spi_board_info board_info[1] = { { .modalias = "spi_ssd1306", /* [cgw]: spi设备名,和设备驱动名对应 */ .bus_num = 0, /* [cgw]: spi总线号,即spi0 */ .chip_select = 2, /* [cgw]: spi总线上的设备号,即spi0.2 */ .max_speed_hz = 50000, /* [cgw]: spi时钟 */ .mode = SPI_MODE_3, /* [cgw]: spi数据模式 */ },};

static void ssd1306_chip_select(struct s3c2410_spigpio_info *spi, int cs){ /* [cgw]: 选中设备号为2的spi设备 */ if (spi->board_info->chip_select == 2) { s3c2410_gpio_cfgpin(S3C2410_GPG2, S3C2410_GPIO_OUTPUT); /* [cgw]: 选中设备 */ if (BITBANG_CS_ACTIVE == cs) { s3c2410_gpio_setpin(S3C2410_GPG2, 0); /* [cgw]: 释放设备 */ } else if (BITBANG_CS_INACTIVE == cs) { s3c2410_gpio_setpin(S3C2410_GPG2, 1); } }}

/* [cgw]: */static struct s3c2410_spigpio_info spi_dev = { .pin_clk = S3C2410_GPG7, .pin_mosi = S3C2410_GPG5, .pin_miso = S3C2410_GPG6, .board_size = 1, /* [cgw]: 设置板上spi接口数量为1 */ .board_info = &board_info[0], .chip_select = ssd1306_chip_select};

static void spi_dev_release(struct device * dev){ printk("spi_dev_release! \n");}

/* [cgw]: 分配一个平台设备 */static struct platform_device spi_platform_dev = { .name = "s3c24xx-spi-gpio", /* [cgw]: 设置平台设备名,和平台驱动名对应 */ .id = -1, .dev = { .release = spi_dev_release, .platform_data = (void *)&spi_dev, /* [cgw]: 通过platform_data传递spi_dev给平台驱动 * 平台驱动可以访问spi_dev */ },};

static int spi_dev_init(void){ /* [cgw]: 注册spi_platform_dev平台设备 */ platform_device_register(&spi_platform_dev); return 0;}

static void spi_dev_exit(void){ /* [cgw]: 注销spi_platform_dev平台设备 */ platform_device_unregister(&spi_platform_dev);}

module_init(spi_dev_init);module_exit(spi_dev_exit);

MODULE_LICENSE("GPL");

spi_ssd1306_drv.c

#include <linux/init.h>#include <linux/module.h>#include <linux/device.h>#include <linux/interrupt.h>#include <linux/interrupt.h>#include <linux/mtd/mtd.h>#include <linux/mtd/partitions.h>#include <linux/spi/spi.h>

#define SSD1306_CMD 0#define SSD1306_DAT 1

#define SSD1306_WIDTH 128#define SSD1306_HEIGHT 64

static uint8_t s_chDispalyBuffer[128][8];

const uint8_t c_chFont1608[95][16] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/{0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/{0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/{0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/{0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/{0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/{0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/{0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/{0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/{0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/{0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/{0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/{0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/{0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/{0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/{0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/{0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/{0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/{0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/{0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/{0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/{0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/{0x00,0x00,0x0E,0x38,0x11,0x44,0x10,0x84,0x10,0x84,0x11,0x44,0x0E,0x38,0x00,0x00},/*"8",24*/{0x00,0x00,0x07,0x00,0x08,0x8C,0x10,0x44,0x10,0x44,0x08,0x88,0x07,0xF0,0x00,0x00},/*"9",25*/{0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0C,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/{0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/{0x00,0x00,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x00,0x00},/*"<",28*/{0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x00,0x00},/*"=",29*/{0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40,0x00,0x80,0x00,0x00},/*">",30*/{0x00,0x00,0x0E,0x00,0x12,0x00,0x10,0x0C,0x10,0x6C,0x10,0x80,0x0F,0x00,0x00,0x00},/*"?",31*/{0x03,0xE0,0x0C,0x18,0x13,0xE4,0x14,0x24,0x17,0xC4,0x08,0x28,0x07,0xD0,0x00,0x00},/*"@",32*/{0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x11,0x04,0x0E,0x88,0x00,0x70,0x00,0x00},/*"B",34*/{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x1C,0x10,0x00,0x00},/*"C",35*/{0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"D",36*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x17,0xC4,0x10,0x04,0x08,0x18,0x00,0x00},/*"E",37*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x17,0xC0,0x10,0x00,0x08,0x00,0x00,0x00},/*"F",38*/{0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x44,0x1C,0x78,0x00,0x40,0x00,0x00},/*"G",39*/{0x10,0x04,0x1F,0xFC,0x10,0x84,0x00,0x80,0x00,0x80,0x10,0x84,0x1F,0xFC,0x10,0x04},/*"H",40*/{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x00,0x00,0x00,0x00},/*"I",41*/{0x00,0x03,0x00,0x01,0x10,0x01,0x10,0x01,0x1F,0xFE,0x10,0x00,0x10,0x00,0x00,0x00},/*"J",42*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x03,0x80,0x14,0x64,0x18,0x1C,0x10,0x04,0x00,0x00},/*"K",43*/{0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x0C,0x00,0x00},/*"L",44*/{0x10,0x04,0x1F,0xFC,0x1F,0x00,0x00,0xFC,0x1F,0x00,0x1F,0xFC,0x10,0x04,0x00,0x00},/*"M",45*/{0x10,0x04,0x1F,0xFC,0x0C,0x04,0x03,0x00,0x00,0xE0,0x10,0x18,0x1F,0xFC,0x10,0x00},/*"N",46*/{0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"O",47*/{0x10,0x04,0x1F,0xFC,0x10,0x84,0x10,0x80,0x10,0x80,0x10,0x80,0x0F,0x00,0x00,0x00},/*"P",48*/{0x07,0xF0,0x08,0x18,0x10,0x24,0x10,0x24,0x10,0x1C,0x08,0x0A,0x07,0xF2,0x00,0x00},/*"Q",49*/{0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x11,0xC0,0x11,0x30,0x0E,0x0C,0x00,0x04},/*"R",50*/{0x00,0x00,0x0E,0x1C,0x11,0x04,0x10,0x84,0x10,0x84,0x10,0x44,0x1C,0x38,0x00,0x00},/*"S",51*/{0x18,0x00,0x10,0x00,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x00,0x18,0x00,0x00,0x00},/*"T",52*/{0x10,0x00,0x1F,0xF8,0x10,0x04,0x00,0x04,0x00,0x04,0x10,0x04,0x1F,0xF8,0x10,0x00},/*"U",53*/{0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",54*/{0x1F,0xC0,0x10,0x3C,0x00,0xE0,0x1F,0x00,0x00,0xE0,0x10,0x3C,0x1F,0xC0,0x00,0x00},/*"W",55*/{0x10,0x04,0x18,0x0C,0x16,0x34,0x01,0xC0,0x01,0xC0,0x16,0x34,0x18,0x0C,0x10,0x04},/*"X",56*/{0x10,0x00,0x1C,0x00,0x13,0x04,0x00,0xFC,0x13,0x04,0x1C,0x00,0x10,0x00,0x00,0x00},/*"Y",57*/{0x08,0x04,0x10,0x1C,0x10,0x64,0x10,0x84,0x13,0x04,0x1C,0x04,0x10,0x18,0x00,0x00},/*"Z",58*/{0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x40,0x02,0x40,0x02,0x40,0x02,0x00,0x00},/*"[",59*/{0x00,0x00,0x30,0x00,0x0C,0x00,0x03,0x80,0x00,0x60,0x00,0x1C,0x00,0x03,0x00,0x00},/*"\",60*/{0x00,0x00,0x40,0x02,0x40,0x02,0x40,0x02,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00},/*"]",61*/{0x00,0x00,0x00,0x00,0x20,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00},/*"^",62*/{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01},/*"_",63*/{0x00,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/{0x00,0x00,0x00,0x98,0x01,0x24,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xFC,0x00,0x04},/*"a",65*/{0x10,0x00,0x1F,0xFC,0x00,0x88,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"b",66*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x00},/*"c",67*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x11,0x08,0x1F,0xFC,0x00,0x04},/*"d",68*/{0x00,0x00,0x00,0xF8,0x01,0x44,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xC8,0x00,0x00},/*"e",69*/{0x00,0x00,0x01,0x04,0x01,0x04,0x0F,0xFC,0x11,0x04,0x11,0x04,0x11,0x00,0x18,0x00},/*"f",70*/{0x00,0x00,0x00,0xD6,0x01,0x29,0x01,0x29,0x01,0x29,0x01,0xC9,0x01,0x06,0x00,0x00},/*"g",71*/{0x10,0x04,0x1F,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"h",72*/{0x00,0x00,0x01,0x04,0x19,0x04,0x19,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"i",73*/{0x00,0x00,0x00,0x03,0x00,0x01,0x01,0x01,0x19,0x01,0x19,0xFE,0x00,0x00,0x00,0x00},/*"j",74*/{0x10,0x04,0x1F,0xFC,0x00,0x24,0x00,0x40,0x01,0xB4,0x01,0x0C,0x01,0x04,0x00,0x00},/*"k",75*/{0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"l",76*/{0x01,0x04,0x01,0xFC,0x01,0x04,0x01,0x00,0x01,0xFC,0x01,0x04,0x01,0x00,0x00,0xFC},/*"m",77*/{0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"n",78*/{0x00,0x00,0x00,0xF8,0x01,0x04,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0xF8,0x00,0x00},/*"o",79*/{0x01,0x01,0x01,0xFF,0x00,0x85,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"p",80*/{0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x05,0x01,0xFF,0x00,0x01},/*"q",81*/{0x01,0x04,0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x04,0x01,0x00,0x01,0x80,0x00,0x00},/*"r",82*/{0x00,0x00,0x00,0xCC,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x98,0x00,0x00},/*"s",83*/{0x00,0x00,0x01,0x00,0x01,0x00,0x07,0xF8,0x01,0x04,0x01,0x04,0x00,0x00,0x00,0x00},/*"t",84*/{0x01,0x00,0x01,0xF8,0x00,0x04,0x00,0x04,0x00,0x04,0x01,0x08,0x01,0xFC,0x00,0x04},/*"u",85*/{0x01,0x00,0x01,0x80,0x01,0x70,0x00,0x0C,0x00,0x10,0x01,0x60,0x01,0x80,0x01,0x00},/*"v",86*/{0x01,0xF0,0x01,0x0C,0x00,0x30,0x01,0xC0,0x00,0x30,0x01,0x0C,0x01,0xF0,0x01,0x00},/*"w",87*/{0x00,0x00,0x01,0x04,0x01,0x8C,0x00,0x74,0x01,0x70,0x01,0x8C,0x01,0x04,0x00,0x00},/*"x",88*/{0x01,0x01,0x01,0x81,0x01,0x71,0x00,0x0E,0x00,0x18,0x01,0x60,0x01,0x80,0x01,0x00},/*"y",89*/{0x00,0x00,0x01,0x84,0x01,0x0C,0x01,0x34,0x01,0x44,0x01,0x84,0x01,0x0C,0x00,0x00},/*"z",90*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x3E,0xFC,0x40,0x02,0x40,0x02},/*"{",91*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/{0x00,0x00,0x40,0x02,0x40,0x02,0x3E,0xFC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"}",93*/{0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/*"~",94*/};

struct spi_device *spi_ssd1306_dev;

static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) { struct spi_transfer t; struct spi_message m;

uint16_t data = chData; memset(&t,0,sizeof(struct spi_transfer)); if (chCmd) { data |= (1 << 8); } else { data &= ~(1 << 8); }

t.tx_buf = &data; t.len = 2; t.bits_per_word = 9; //t.cs_change = 1; spi_message_init(&m); spi_message_add_tail(&t, &m); spi_sync(spi_ssd1306_dev, &m);}

void ssd1306_display_on(void){ ssd1306_write_byte(0x8D, SSD1306_CMD); ssd1306_write_byte(0x14, SSD1306_CMD); ssd1306_write_byte(0xAF, SSD1306_CMD); } /** * @brief OLED turns off * * @param None * * @retval None**/void ssd1306_display_off(void){ ssd1306_write_byte(0x8D, SSD1306_CMD); ssd1306_write_byte(0x10, SSD1306_CMD); ssd1306_write_byte(0xAE, SSD1306_CMD); }

void ssd1306_refresh_gram(void){ uint8_t i, j; for (i = 0; i < 8; i ++) { ssd1306_write_byte(0xB0 + i, SSD1306_CMD); ssd1306_write_byte(0x02, SSD1306_CMD); ssd1306_write_byte(0x10, SSD1306_CMD); for (j = 0; j < 128; j ++) { ssd1306_write_byte(s_chDispalyBuffer[j][i], SSD1306_DAT); } } }

void ssd1306_clear_screen(uint8_t chFill) { memset(s_chDispalyBuffer,chFill, sizeof(s_chDispalyBuffer)); ssd1306_refresh_gram();}

/** * @brief Draws a piont on the screen * * @param chXpos: Specifies the X position * @param chYpos: Specifies the Y position * @param chPoint: 0: the point turns off 1: the piont turns on * * @retval None**/

void ssd1306_draw_point(uint8_t chXpos, uint8_t chYpos, uint8_t chPoint){ uint8_t chPos, chBx, chTemp = 0; if (chXpos > 127 || chYpos > 63) { return; } chPos = 7 - chYpos / 8; // chBx = chYpos % 8; chTemp = 1 << (7 - chBx); if (chPoint) { s_chDispalyBuffer[chXpos][chPos] |= chTemp; } else { s_chDispalyBuffer[chXpos][chPos] &= ~chTemp; }} /** * @brief Fills a rectangle * * @param chXpos1: Specifies the X position 1 (X top left position) * @param chYpos1: Specifies the Y position 1 (Y top left position) * @param chXpos2: Specifies the X position 2 (X bottom right position) * @param chYpos3: Specifies the Y position 2 (Y bottom right position) * * @retval **/

void ssd1306_fill_screen(uint8_t chXpos1, uint8_t chYpos1, uint8_t chXpos2, uint8_t chYpos2, uint8_t chDot) { uint8_t chXpos, chYpos; for (chXpos = chXpos1; chXpos <= chXpos2; chXpos ++) { for (chYpos = chYpos1; chYpos <= chYpos2; chYpos ++) { ssd1306_draw_point(chXpos, chYpos, chDot); } } ssd1306_refresh_gram();}

/** * @brief Displays one character at the specified position * * @param chXpos: Specifies the X position * @param chYpos: Specifies the Y position * @param chSize: * @param chMode * @retval **/void ssd1306_display_char(uint8_t chXpos, uint8_t chYpos, uint8_t chChr, uint8_t chSize, uint8_t chMode){ uint8_t i, j; uint8_t chTemp, chYpos0 = chYpos; chChr = chChr - ' '; for (i = 0; i < chSize; i ++) { if (chMode) { chTemp = c_chFont1608[chChr][i]; } else { chTemp = ~c_chFont1608[chChr][i]; } for (j = 0; j < 8; j ++) { if (chTemp & 0x80) { ssd1306_draw_point(chXpos, chYpos, 1); } else { ssd1306_draw_point(chXpos, chYpos, 0); } chTemp <<= 1; chYpos ++; if ((chYpos - chYpos0) == chSize) { chYpos = chYpos0; chXpos ++; break; } } } }

/** * @brief Displays a string on the screen * * @param chXpos: Specifies the X position * @param chYpos: Specifies the Y position * @param pchString: Pointer to a string to display on the screen * * @retval None**/void ssd1306_display_string(uint8_t chXpos, uint8_t chYpos, const uint8_t *pchString, uint8_t chSize, uint8_t chMode){ while (*pchString != '') { if (chXpos > (SSD1306_WIDTH - chSize / 2)) { chXpos = 0; chYpos += chSize; if (chYpos > (SSD1306_HEIGHT - chSize)) { chYpos = chXpos = 0; ssd1306_clear_screen(0x00); } } ssd1306_display_char(chXpos, chYpos, *pchString, chSize, chMode); chXpos += chSize / 2; pchString ++; }}

void ssd1306_init(void){ ssd1306_write_byte(0xAE, SSD1306_CMD);//--turn off oled panel ssd1306_write_byte(0x00, SSD1306_CMD);//---set low column address ssd1306_write_byte(0x10, SSD1306_CMD);//---set high column address ssd1306_write_byte(0x40, SSD1306_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) ssd1306_write_byte(0x81, SSD1306_CMD);//--set contrast control register ssd1306_write_byte(0xCF, SSD1306_CMD);// Set SEG Output Current Brightness ssd1306_write_byte(0xA1, SSD1306_CMD);//--Set SEG/Column Mapping ssd1306_write_byte(0xC0, SSD1306_CMD);//Set COM/Row Scan Direction ssd1306_write_byte(0xA6, SSD1306_CMD);//--set normal display ssd1306_write_byte(0xA8, SSD1306_CMD);//--set multiplex ratio(1 to 64) ssd1306_write_byte(0x3f, SSD1306_CMD);//--1/64 duty ssd1306_write_byte(0xD3, SSD1306_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) ssd1306_write_byte(0x00, SSD1306_CMD);//-not offset ssd1306_write_byte(0xd5, SSD1306_CMD);//--set display clock pide ratio/oscillator frequency ssd1306_write_byte(0x80, SSD1306_CMD);//--set pide ratio, Set Clock as 100 Frames/Sec ssd1306_write_byte(0xD9, SSD1306_CMD);//--set pre-charge period ssd1306_write_byte(0xF1, SSD1306_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock ssd1306_write_byte(0xDA, SSD1306_CMD);//--set com pins hardware configuration ssd1306_write_byte(0x12, SSD1306_CMD); ssd1306_write_byte(0xDB, SSD1306_CMD);//--set vcomh ssd1306_write_byte(0x40, SSD1306_CMD);//Set VCOM Deselect Level ssd1306_write_byte(0x20, SSD1306_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) ssd1306_write_byte(0x02, SSD1306_CMD);// ssd1306_write_byte(0x8D, SSD1306_CMD);//--set Charge Pump enable/disable ssd1306_write_byte(0x14, SSD1306_CMD);//--set(0x10) disable ssd1306_write_byte(0xA4, SSD1306_CMD);// Disable Entire Display On (0xa4/0xa5) ssd1306_write_byte(0xA6, SSD1306_CMD);// Disable Inverse Display On (0xa6/a7) ssd1306_write_byte(0xAF, SSD1306_CMD);//--turn on oled panel

ssd1306_display_on(); ssd1306_clear_screen(0xff); }

static int __devinit spi_ssd1306_probe(struct spi_device *spi){ printk("spi_ssd1306_probe\n"); spi_ssd1306_dev = spi; spi_ssd1306_dev->bits_per_word = 9;

ssd1306_init(); ssd1306_clear_screen(0x00); ssd1306_display_off();

ssd1306_display_string(18, 0, "hello, Linux!", 16, 1); ssd1306_display_string(0, 16, "this is a spi driver demo!", 16, 1); ssd1306_refresh_gram(); ssd1306_display_on(); return 0;}

static int __devexit spi_ssd1306_remove(struct spi_device *spi){ printk("ssd1306_remove\n"); ssd1306_clear_screen(0x00); ssd1306_display_off(); return 0;}

static struct spi_driver spi_ssd1306_driver = { .driver = { .name = "spi_ssd1306", .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = spi_ssd1306_probe, .remove = __devexit_p(spi_ssd1306_remove),};

static int spi_ssd1306_init(void){ return spi_register_driver(&spi_ssd1306_driver);}

static void spi_ssd1306_exit(void){ spi_unregister_driver(&spi_ssd1306_driver);}

module_init(spi_ssd1306_init);module_exit(spi_ssd1306_exit);

MODULE_LICENSE("GPL");

makefile

KERN_DIR = /work/system/linux-2.6.22.6

all: make -C $(KERN_DIR) M=`pwd` modules

clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order

obj-m += spi_platform_dev.oobj-m += spi_s3c24xx_gpio.oobj-m += spi_bitbang.oobj-m += spi_ssd1306_drv.o

实验现象:

本文永久更新链接地址:http://www.xuebuyuan.com/Linux/2016-12/138512.htm

以上就上有关SPI子系统之驱动SSD1306OLED的全部内容,学步园全面介绍编程技术、操作系统、数据库、web前端技术等内容。

抱歉!评论已关闭.