spi子系统之驱动SSD1306 OLED
接触Linux之前,曾以为读源码可以更快的学习软件,于是前几个博客都是一边读源码一边添加注释,甚至精读到每一行代码,实际上效果并不理想,看过之后就忘记了。主要原因是没理解透程序架构,各个模块之间的关系,如何联系在一起,再加上没有实例验证。后来逐渐发现,理解框架能达到事半功倍的效果,理解框架之后,反而代码也更容易看懂,甚至可以猜部分代码的作用,印象更加深刻。
理解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做了些什么。
1 static int s3c2410_spigpio_probe(struct platform_device *dev) 2 { 3 ... ... 4 /* [cgw]: 分配一个SPI主机 */ 5 master = spi_alloc_master(&dev->dev, sizeof(struct s3c2410_spigpio)); 6 ... ... 7 8 sp = spi_master_get_devdata(master); 9 10 platform_set_drvdata(dev, sp); 11 12 /* [cgw]: 分配与spi硬件相关的配置,如指定哪些IO为MISO,MOSI,SCLK,CS,SPI工作模式,最大时钟等等 */ 13 /* copy in the plkatform data */ 14 sp->info = dev->dev.platform_data; 15 16 /* [cgw]: 提供实现SPI各种模式的时序的基本方法,和CS的激活方法 */ 17 /* setup spi bitbang adaptor */ 18 sp->bitbang.master = spi_master_get(master); 19 sp->bitbang.chipselect = s3c2410_spigpio_chipselect; 20 21 sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0; 22 sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1; 23 sp->bitbang.txrx_word[SPI_MODE_2] = s3c2410_spigpio_txrx_mode2; 24 sp->bitbang.txrx_word[SPI_MODE_3] = s3c2410_spigpio_txrx_mode3; 25 26 /* [cgw]: 配置相关io为输入输出 */ 27 /* set state of spi pins */ 28 s3c2410_gpio_setpin(sp->info->pin_clk, 0); 29 s3c2410_gpio_setpin(sp->info->pin_mosi, 0); 30 31 s3c2410_gpio_cfgpin(sp->info->pin_clk, S3C2410_GPIO_OUTPUT); 32 s3c2410_gpio_cfgpin(sp->info->pin_mosi, S3C2410_GPIO_OUTPUT); 33 s3c2410_gpio_cfgpin(sp->info->pin_miso, S3C2410_GPIO_INPUT); 34 35 /* [cgw]: 设置spi的收发,如注册一个工作队列,收发时序的方法,8/16/32的spi数据等等 */ 36 ret = spi_bitbang_start(&sp->bitbang); 37 ... ... 38 39 /* [cgw]: 注册sp->info->board_size个spi设备,这几个spi设备都是挂接在统一spi总线上的 */ 40 /* register the chips to go with the board */ 41 for (i = 0; i < sp->info->board_size; i++) { 42 dev_info(&dev->dev, "registering %p: %s\n", 43 &sp->info->board_info[i], 44 sp->info->board_info[i].modalias); 45 46 sp->info->board_info[i].controller_data = sp; 47 spi_new_device(master, sp->info->board_info + i); 48 } 49 ... ... 50 }
- 注册了一个platform_driver
- 在s3c2410_spigpio_probe中,分配并注册了一个spi主机,并注册了挂接在这个SPI主机上的所有spi设备
要想s3c2410_spigpio_probe得到调用,即探测到有效的platform_device,我们需要一个与platform同名("s3c24xx-spi-gpio")的platform_device。
1 static struct spi_board_info board_info[1] = { 2 { 3 .modalias = "spi_ssd1306", /* [cgw]: spi设备名,和设备驱动名对应 */ 4 .bus_num = 0, /* [cgw]: spi总线号,即spi0 */ 5 .chip_select = 2, /* [cgw]: spi总线上的设备号,即spi0.2 */ 6 .max_speed_hz = 50000, /* [cgw]: spi时钟 */ 7 .mode = SPI_MODE_3, /* [cgw]: spi数据模式 */ 8 }, 9 }; 10 11 static struct s3c2410_spigpio_info spi_dev = { 12 .pin_clk = S3C2410_GPG7, 13 .pin_mosi = S3C2410_GPG5, 14 .pin_miso = S3C2410_GPG6, 15 .board_size = 1, /* [cgw]: 设置板上spi接口数量为1 */ 16 .board_info = &board_info[0], 17 .chip_select = ssd1306_chip_select 18 }; 19 20 static struct platform_device spi_platform_dev = { 21 .name = "s3c24xx-spi-gpio", /* [cgw]: 设置平台设备名,和平台驱动名对应 */ 22 .id = -1, 23 .dev = { 24 .release = spi_dev_release, 25 .platform_data = (void *)&spi_dev, /* [cgw]: 通过platform_data传递spi_dev给平台驱动 26 * 平台驱动可以访问spi_dev 27 */ 28 }, 29 }; 30 31 static int spi_dev_init(void) 32 { 33 /* [cgw]: 注册spi_platform_dev平台设备 */ 34 platform_device_register(&spi_platform_dev); 35 return 0; 36 }
spi_bitbang.c提供了spi底层一些实现细节,注册工作队列(SPI数据的传送最终是通过调用工作队列实现的),spi工作模式,工作频率等。
1 int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t) 2 { 3 struct spi_bitbang_cs *cs = spi->controller_state; 4 u8 bits_per_word; 5 u32 hz; 6 7 if (t) { 8 /* [cgw]: spi驱动指定几位数据模式,和传送速度 */ 9 bits_per_word = t->bits_per_word; 10 hz = t->speed_hz; 11 } else { 12 bits_per_word = 0; 13 hz = 0; 14 } 15 16 /* [cgw]: 根据spi位数,选择合适的时序 */ 17 /* spi_transfer level calls that work per-word */ 18 if (!bits_per_word) 19 bits_per_word = spi->bits_per_word; 20 if (bits_per_word <= 8) 21 cs->txrx_bufs = bitbang_txrx_8; 22 else if (bits_per_word <= 16) 23 cs->txrx_bufs = bitbang_txrx_16; 24 else if (bits_per_word <= 32) 25 cs->txrx_bufs = bitbang_txrx_32; 26 else 27 return -EINVAL; 28 29 /* [cgw]: 设置SCLK的时钟频率 */ 30 /* nsecs = (clock period)/2 */ 31 if (!hz) 32 hz = spi->max_speed_hz; 33 if (hz) { 34 cs->nsecs = (1000000000/2) / hz; 35 if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000)) 36 return -EINVAL; 37 } 38 39 return 0; 40 } 41 42 int spi_bitbang_setup(struct spi_device *spi) 43 { 44 struct spi_bitbang_cs *cs = spi->controller_state; 45 struct spi_bitbang *bitbang; 46 int retval; 47 48 bitbang = spi_master_get_devdata(spi->master); 49 50 /* REVISIT: some systems will want to support devices using lsb-first 51 * bit encodings on the wire. In pure software that would be trivial, 52 * just bitbang_txrx_le_cphaX() routines shifting the other way, and 53 * some hardware controllers also have this support. 54 */ 55 /* [cgw]: 默认不支持LSB模式,要想使用LSB模式,只要bitbang_txrx_le_cphaX()改变移位的方向即可 */ 56 if ((spi->mode & SPI_LSB_FIRST) != 0) 57 return -EINVAL; 58 59 if (!cs) { 60 cs = kzalloc(sizeof *cs, GFP_KERNEL); 61 if (!cs) 62 return -ENOMEM; 63 spi->controller_state = cs; 64 } 65 66 /* [cgw]: 设置spi的默认位数 */ 67 if (!spi->bits_per_word) 68 spi->bits_per_word = 8; 69 70 /* per-word shift register access, in hardware or bitbanging */ 71 /* [cgw]: 设置spi的工作模式,四种 */ 72 cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)]; 73 if (!cs->txrx_word) 74 return -EINVAL; 75 76 /* [cgw]: 调用spi_bitbang_setup_transfer */ 77 retval = bitbang->setup_transfer(spi, NULL); 78 if (retval < 0) 79 return retval; 80 81 dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n", 82 __FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA), 83 spi->bits_per_word, 2 * cs->nsecs); 84 85 /* NOTE we _need_ to call chipselect() early, ideally with adapter 86 * setup, unless the hardware defaults cooperate to avoid confusion 87 * between normal (active low) and inverted chipselects. 88 */ 89 90 /* [cgw]: spi忙的话,通过改变CS的状态释放SPI */ 91 /* deselect chip (low or high) */ 92 spin_lock(&bitbang->lock); 93 if (!bitbang->busy) { 94 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 95 ndelay(cs->nsecs); 96 } 97 spin_unlock(&bitbang->lock); 98 99 return 0; 100 } 101 102 103 static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t) 104 { 105 struct spi_bitbang_cs *cs = spi->controller_state; 106 unsigned nsecs = cs->nsecs; 107 108 /* [cgw]: 具体数据收发就是这里实现的 */ 109 return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t); 110 } 111 112 int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m) 113 { 114 struct spi_bitbang *bitbang; 115 unsigned long flags; 116 int status = 0; 117 118 m->actual_length = 0; 119 m->status = -EINPROGRESS; 120 121 bitbang = spi_master_get_devdata(spi->master); 122 123 spin_lock_irqsave(&bitbang->lock, flags); 124 if (!spi->max_speed_hz) 125 status = -ENETDOWN; 126 else { 127 /* [cgw]: 入队一个工作到工作队列 */ 128 list_add_tail(&m->queue, &bitbang->queue); 129 queue_work(bitbang->workqueue, &bitbang->work); 130 } 131 spin_unlock_irqrestore(&bitbang->lock, flags); 132 133 return status; 134 } 135 136 int spi_bitbang_start(struct spi_bitbang *bitbang) 137 { 138 int status; 139 140 if (!bitbang->master || !bitbang->chipselect) 141 return -EINVAL; 142 143 /* [cgw]: 注册一个工作队列 */ 144 INIT_WORK(&bitbang->work, bitbang_work); 145 spin_lock_init(&bitbang->lock); 146 INIT_LIST_HEAD(&bitbang->queue); 147 148 /* [cgw]: 配置相关方法 */ 149 if (!bitbang->master->transfer) 150 bitbang->master->transfer = spi_bitbang_transfer; 151 if (!bitbang->txrx_bufs) { 152 bitbang->use_dma = 0; 153 bitbang->txrx_bufs = spi_bitbang_bufs; 154 if (!bitbang->master->setup) { 155 if (!bitbang->setup_transfer) 156 bitbang->setup_transfer = 157 spi_bitbang_setup_transfer; 158 bitbang->master->setup = spi_bitbang_setup; 159 bitbang->master->cleanup = spi_bitbang_cleanup; 160 } 161 } else if (!bitbang->master->setup) 162 return -EINVAL; 163 164 /* [cgw]: 创建一个单线程,用于调度工作队列 */ 165 /* this task is the only thing to touch the SPI bits */ 166 bitbang->busy = 0; 167 bitbang->workqueue = create_singlethread_workqueue( 168 bitbang->master->cdev.dev->bus_id); 169 if (bitbang->workqueue == NULL) { 170 status = -EBUSY; 171 goto err1; 172 } 173 174 /* [cgw]: 注册一个spi主机 */ 175 /* driver may get busy before register() returns, especially 176 * if someone registered boardinfo for devices 177 */ 178 status = spi_register_master(bitbang->master); 179 if (status < 0) 180 goto err2; 181 182 return status; 183 184 err2: 185 destroy_workqueue(bitbang->workqueue); 186 err1: 187 return status; 188 }
因为在s3c2410_spigpio_probe中注册了spi的设备,因此我们还需为这些设备提供驱动,以被这些设备探测到,探测这些驱动的条件也是设备和驱动的名字同名,即spi_ssd1306。我们这里提供了一个ssd1306 OLED的驱动
1 static struct spi_driver spi_ssd1306_driver = { 2 .driver = { 3 .name = "spi_ssd1306", 4 .bus = &spi_bus_type, 5 .owner = THIS_MODULE, 6 }, 7 .probe = spi_ssd1306_probe, 8 .remove = __devexit_p(spi_ssd1306_remove), 9 }; 10 11 static int spi_ssd1306_init(void) 12 { 13 return spi_register_driver(&spi_ssd1306_driver); 14 }
到这里,基本工作已经完成。怎样驱动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的状态来表示数据或命令的,他们的做法如下:
1 void ssd1306_write_cmd(uint8_t cmd) 2 { 3 ssd1306_dc_clr(); 4 spi_write(cmd); 5 ssd1306_dc_set(); 6 } 7 8 void ssd1306_write_data(uint8_t data) 9 { 10 ssd1306_dc_set(); 11 spi_write(data); 12 ssd1306_dc_clr(); 13 }
我本人认为是不正确的,至少不符合这个spi框架的逻辑,因为spi数据的收发并不是直接在spi_write()实现,而是在工作队列bitbang_work()中实现。尽管这样仍然能驱动ssd1306 OLED,但理论上不应该这么做。要改的话应该改bitbang_work()中改,添加DC状态的控制。
1 static void bitbang_work(struct work_struct *work) 2 { 3 struct spi_bitbang *bitbang = 4 container_of(work, struct spi_bitbang, work); 5 unsigned long flags; 6 7 spin_lock_irqsave(&bitbang->lock, flags); 8 bitbang->busy = 1; 9 /* [cgw]: 队列不为空 */ 10 while (!list_empty(&bitbang->queue)) { 11 struct spi_message *m; 12 struct spi_device *spi; 13 unsigned nsecs; 14 struct spi_transfer *t = NULL; 15 unsigned tmp; 16 unsigned cs_change; 17 int status; 18 int (*setup_transfer)(struct spi_device *, 19 struct spi_transfer *); 20 21 /* [cgw]: 取出spi_message */ 22 m = container_of(bitbang->queue.next, struct spi_message, 23 queue); 24 /* [cgw]: 删除这个节点 */ 25 list_del_init(&m->queue); 26 /* [cgw]: 进入临界区 */ 27 spin_unlock_irqrestore(&bitbang->lock, flags); 28 29 /* FIXME this is made-up ... the correct value is known to 30 * word-at-a-time bitbang code, and presumably chipselect() 31 * should enforce these requirements too? 32 */ 33 nsecs = 100; 34 35 spi = m->spi; 36 tmp = 0; 37 cs_change = 1; 38 status = 0; 39 setup_transfer = NULL; 40 41 /* [cgw]: 历遍spi_message中的收发列表 */ 42 list_for_each_entry (t, &m->transfers, transfer_list) { 43 44 /* override or restore speed and wordsize */ 45 /* [cgw]: 如果驱动指定了spi速度,和位数,重新调用spi_bitbang_setup_transfer 46 * 更改默认设置 47 */ 48 if (t->speed_hz || t->bits_per_word) { 49 setup_transfer = bitbang->setup_transfer; 50 if (!setup_transfer) { 51 status = -ENOPROTOOPT; 52 break; 53 } 54 } 55 if (setup_transfer) { 56 status = setup_transfer(spi, t); 57 if (status < 0) 58 break; 59 } 60 61 /* set up default clock polarity, and activate chip; 62 * this implicitly updates clock and spi modes as 63 * previously recorded for this device via setup(). 64 * (and also deselects any other chip that might be 65 * selected ...) 66 */ 67 /* [cgw]: 激活spi */ 68 if (cs_change) { 69 bitbang->chipselect(spi, BITBANG_CS_ACTIVE); 70 ndelay(nsecs); 71 } 72 /* [cgw]: 驱动指定收发完一帧数据要不要改变恢复CS为空闲 */ 73 cs_change = t->cs_change; 74 /* [cgw]: 收发包为空,则无效 */ 75 if (!t->tx_buf && !t->rx_buf && t->len) { 76 status = -EINVAL; 77 break; 78 } 79 80 /* transfer data. the lower level code handles any 81 * new dma mappings it needs. our caller always gave 82 * us dma-safe buffers. 83 */ 84 if (t->len) { 85 /* REVISIT dma API still needs a designated 86 * DMA_ADDR_INVALID; ~0 might be better. 87 */ 88 if (!m->is_dma_mapped) 89 t->rx_dma = t->tx_dma = 0; 90 /* [cgw]: 这里才是真正的实现spi收发时序 */ 91 status = bitbang->txrx_bufs(spi, t); 92 } 93 if (status != t->len) { 94 if (status > 0) 95 status = -EMSGSIZE; 96 break; 97 } 98 m->actual_length += status; 99 status = 0; 100 101 /* protocol tweaks before next transfer */ 102 if (t->delay_usecs) 103 udelay(t->delay_usecs); 104 105 /* [cgw]: 收发完一帧,不改变CS状态 */ 106 if (!cs_change) 107 continue; 108 109 /* [cgw]: 收发列表已经没有数据,结束 */ 110 if (t->transfer_list.next == &m->transfers) 111 break; 112 113 /* sometimes a short mid-message deselect of the chip 114 * may be needed to terminate a mode or command 115 */ 116 /* [cgw]: 释放spi */ 117 ndelay(nsecs); 118 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 119 ndelay(nsecs); 120 } 121 122 m->status = status; 123 m->complete(m->context); 124 125 /* restore speed and wordsize */ 126 /* [cgw]: 速度和位数恢复默认 */ 127 if (setup_transfer) 128 setup_transfer(spi, NULL); 129 130 /* normally deactivate chipselect ... unless no error and 131 * cs_change has hinted that the next message will probably 132 * be for this chip too. 133 */ 134 if (!(status == 0 && cs_change)) { 135 ndelay(nsecs); 136 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 137 ndelay(nsecs); 138 } 139 140 spin_lock_irqsave(&bitbang->lock, flags); 141 } 142 bitbang->busy = 0; 143 /* [cgw]: 退出临界区 */ 144 spin_unlock_irqrestore(&bitbang->lock, flags); 145 }
代码:
spi_platform_dev.c
1 #include2 3 4 static struct spi_board_info board_info[1] = { 5 { 6 .modalias = "spi_ssd1306", /* [cgw]: spi设备名,和设备驱动名对应 */ 7 .bus_num = 0, /* [cgw]: spi总线号,即spi0 */ 8 .chip_select = 2, /* [cgw]: spi总线上的设备号,即spi0.2 */ 9 .max_speed_hz = 50000, /* [cgw]: spi时钟 */ 10 .mode = SPI_MODE_3, /* [cgw]: spi数据模式 */ 11 }, 12 }; 13 14 15 static void ssd1306_chip_select(struct s3c2410_spigpio_info *spi, int cs) 16 { 17 /* [cgw]: 选中设备号为2的spi设备 */ 18 if (spi->board_info->chip_select == 2) { 19 s3c2410_gpio_cfgpin(S3C2410_GPG2, S3C2410_GPIO_OUTPUT); 20 /* [cgw]: 选中设备 */ 21 if (BITBANG_CS_ACTIVE == cs) { 22 s3c2410_gpio_setpin(S3C2410_GPG2, 0); 23 /* [cgw]: 释放设备 */ 24 } else if (BITBANG_CS_INACTIVE == cs) { 25 s3c2410_gpio_setpin(S3C2410_GPG2, 1); 26 } 27 } 28 } 29 30 /* [cgw]: */ 31 static struct s3c2410_spigpio_info spi_dev = { 32 .pin_clk = S3C2410_GPG7, 33 .pin_mosi = S3C2410_GPG5, 34 .pin_miso = S3C2410_GPG6, 35 .board_size = 1, /* [cgw]: 设置板上spi接口数量为1 */ 36 .board_info = &board_info[0], 37 .chip_select = ssd1306_chip_select 38 }; 39 40 static void spi_dev_release(struct device * dev) 41 { 42 printk("spi_dev_release! \n"); 43 } 44 45 /* [cgw]: 分配一个平台设备 */ 46 static struct platform_device spi_platform_dev = { 47 .name = "s3c24xx-spi-gpio", /* [cgw]: 设置平台设备名,和平台驱动名对应 */ 48 .id = -1, 49 .dev = { 50 .release = spi_dev_release, 51 .platform_data = (void *)&spi_dev, /* [cgw]: 通过platform_data传递spi_dev给平台驱动 52 * 平台驱动可以访问spi_dev 53 */ 54 }, 55 }; 56 57 58 static int spi_dev_init(void) 59 { 60 /* [cgw]: 注册spi_platform_dev平台设备 */ 61 platform_device_register(&spi_platform_dev); 62 return 0; 63 } 64 65 static void spi_dev_exit(void) 66 { 67 /* [cgw]: 注销spi_platform_dev平台设备 */ 68 platform_device_unregister(&spi_platform_dev); 69 } 70 71 module_init(spi_dev_init); 72 module_exit(spi_dev_exit); 73 74 MODULE_LICENSE("GPL");
spi_ssd1306_drv.c
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 #define SSD1306_CMD 0 11 #define SSD1306_DAT 1 12 13 #define SSD1306_WIDTH 128 14 #define SSD1306_HEIGHT 64 15 16 static uint8_t s_chDispalyBuffer[128][8]; 17 18 const uint8_t c_chFont1608[95][16] = { 19 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/ 20 {0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/ 21 {0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/ 22 {0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/ 23 {0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/ 24 {0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/ 25 {0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/ 26 {0x08,0x00,0x68,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/ 27 {0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x18,0x18,0x20,0x04,0x40,0x02,0x00,0x00},/*"(",8*/ 28 {0x00,0x00,0x40,0x02,0x20,0x04,0x18,0x18,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00},/*")",9*/ 29 {0x02,0x40,0x02,0x40,0x01,0x80,0x0F,0xF0,0x01,0x80,0x02,0x40,0x02,0x40,0x00,0x00},/*"*",10*/ 30 {0x00,0x80,0x00,0x80,0x00,0x80,0x0F,0xF8,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00},/*"+",11*/ 31 {0x00,0x01,0x00,0x0D,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",12*/ 32 {0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"-",13*/ 33 {0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/ 34 {0x00,0x00,0x00,0x06,0x00,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x18,0x00,0x20,0x00},/*"/",15*/ 35 {0x00,0x00,0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"0",16*/ 36 {0x00,0x00,0x08,0x04,0x08,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"1",17*/ 37 {0x00,0x00,0x0E,0x0C,0x10,0x14,0x10,0x24,0x10,0x44,0x11,0x84,0x0E,0x0C,0x00,0x00},/*"2",18*/ 38 {0x00,0x00,0x0C,0x18,0x10,0x04,0x11,0x04,0x11,0x04,0x12,0x88,0x0C,0x70,0x00,0x00},/*"3",19*/ 39 {0x00,0x00,0x00,0xE0,0x03,0x20,0x04,0x24,0x08,0x24,0x1F,0xFC,0x00,0x24,0x00,0x00},/*"4",20*/ 40 {0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",21*/ 41 {0x00,0x00,0x07,0xF0,0x08,0x88,0x11,0x04,0x11,0x04,0x18,0x88,0x00,0x70,0x00,0x00},/*"6",22*/ 42 {0x00,0x00,0x1C,0x00,0x10,0x00,0x10,0xFC,0x13,0x00,0x1C,0x00,0x10,0x00,0x00,0x00},/*"7",23*/ 43 {0x00,0x00,0x0E,0x38,0x11,0x44,0x10,0x84,0x10,0x84,0x11,0x44,0x0E,0x38,0x00,0x00},/*"8",24*/ 44 {0x00,0x00,0x07,0x00,0x08,0x8C,0x10,0x44,0x10,0x44,0x08,0x88,0x07,0xF0,0x00,0x00},/*"9",25*/ 45 {0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0C,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/ 46 {0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/ 47 {0x00,0x00,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x00,0x00},/*"<",28*/ 48 {0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,0x00,0x00},/*"=",29*/ 49 {0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40,0x00,0x80,0x00,0x00},/*">",30*/ 50 {0x00,0x00,0x0E,0x00,0x12,0x00,0x10,0x0C,0x10,0x6C,0x10,0x80,0x0F,0x00,0x00,0x00},/*"?",31*/ 51 {0x03,0xE0,0x0C,0x18,0x13,0xE4,0x14,0x24,0x17,0xC4,0x08,0x28,0x07,0xD0,0x00,0x00},/*"@",32*/ 52 {0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04},/*"A",33*/ 53 {0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x11,0x04,0x0E,0x88,0x00,0x70,0x00,0x00},/*"B",34*/ 54 {0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x1C,0x10,0x00,0x00},/*"C",35*/ 55 {0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"D",36*/ 56 {0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x04,0x17,0xC4,0x10,0x04,0x08,0x18,0x00,0x00},/*"E",37*/ 57 {0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x17,0xC0,0x10,0x00,0x08,0x00,0x00,0x00},/*"F",38*/ 58 {0x03,0xE0,0x0C,0x18,0x10,0x04,0x10,0x04,0x10,0x44,0x1C,0x78,0x00,0x40,0x00,0x00},/*"G",39*/ 59 {0x10,0x04,0x1F,0xFC,0x10,0x84,0x00,0x80,0x00,0x80,0x10,0x84,0x1F,0xFC,0x10,0x04},/*"H",40*/ 60 {0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x04,0x00,0x00,0x00,0x00},/*"I",41*/ 61 {0x00,0x03,0x00,0x01,0x10,0x01,0x10,0x01,0x1F,0xFE,0x10,0x00,0x10,0x00,0x00,0x00},/*"J",42*/ 62 {0x10,0x04,0x1F,0xFC,0x11,0x04,0x03,0x80,0x14,0x64,0x18,0x1C,0x10,0x04,0x00,0x00},/*"K",43*/ 63 {0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x0C,0x00,0x00},/*"L",44*/ 64 {0x10,0x04,0x1F,0xFC,0x1F,0x00,0x00,0xFC,0x1F,0x00,0x1F,0xFC,0x10,0x04,0x00,0x00},/*"M",45*/ 65 {0x10,0x04,0x1F,0xFC,0x0C,0x04,0x03,0x00,0x00,0xE0,0x10,0x18,0x1F,0xFC,0x10,0x00},/*"N",46*/ 66 {0x07,0xF0,0x08,0x08,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x08,0x07,0xF0,0x00,0x00},/*"O",47*/ 67 {0x10,0x04,0x1F,0xFC,0x10,0x84,0x10,0x80,0x10,0x80,0x10,0x80,0x0F,0x00,0x00,0x00},/*"P",48*/ 68 {0x07,0xF0,0x08,0x18,0x10,0x24,0x10,0x24,0x10,0x1C,0x08,0x0A,0x07,0xF2,0x00,0x00},/*"Q",49*/ 69 {0x10,0x04,0x1F,0xFC,0x11,0x04,0x11,0x00,0x11,0xC0,0x11,0x30,0x0E,0x0C,0x00,0x04},/*"R",50*/ 70 {0x00,0x00,0x0E,0x1C,0x11,0x04,0x10,0x84,0x10,0x84,0x10,0x44,0x1C,0x38,0x00,0x00},/*"S",51*/ 71 {0x18,0x00,0x10,0x00,0x10,0x04,0x1F,0xFC,0x10,0x04,0x10,0x00,0x18,0x00,0x00,0x00},/*"T",52*/ 72 {0x10,0x00,0x1F,0xF8,0x10,0x04,0x00,0x04,0x00,0x04,0x10,0x04,0x1F,0xF8,0x10,0x00},/*"U",53*/ 73 {0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",54*/ 74 {0x1F,0xC0,0x10,0x3C,0x00,0xE0,0x1F,0x00,0x00,0xE0,0x10,0x3C,0x1F,0xC0,0x00,0x00},/*"W",55*/ 75 {0x10,0x04,0x18,0x0C,0x16,0x34,0x01,0xC0,0x01,0xC0,0x16,0x34,0x18,0x0C,0x10,0x04},/*"X",56*/ 76 {0x10,0x00,0x1C,0x00,0x13,0x04,0x00,0xFC,0x13,0x04,0x1C,0x00,0x10,0x00,0x00,0x00},/*"Y",57*/ 77 {0x08,0x04,0x10,0x1C,0x10,0x64,0x10,0x84,0x13,0x04,0x1C,0x04,0x10,0x18,0x00,0x00},/*"Z",58*/ 78 {0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x40,0x02,0x40,0x02,0x40,0x02,0x00,0x00},/*"[",59*/ 79 {0x00,0x00,0x30,0x00,0x0C,0x00,0x03,0x80,0x00,0x60,0x00,0x1C,0x00,0x03,0x00,0x00},/*"\",60*/ 80 {0x00,0x00,0x40,0x02,0x40,0x02,0x40,0x02,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00},/*"]",61*/ 81 {0x00,0x00,0x00,0x00,0x20,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00},/*"^",62*/ 82 {0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01},/*"_",63*/ 83 {0x00,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/ 84 {0x00,0x00,0x00,0x98,0x01,0x24,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xFC,0x00,0x04},/*"a",65*/ 85 {0x10,0x00,0x1F,0xFC,0x00,0x88,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"b",66*/ 86 {0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x00},/*"c",67*/ 87 {0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x11,0x08,0x1F,0xFC,0x00,0x04},/*"d",68*/ 88 {0x00,0x00,0x00,0xF8,0x01,0x44,0x01,0x44,0x01,0x44,0x01,0x44,0x00,0xC8,0x00,0x00},/*"e",69*/ 89 {0x00,0x00,0x01,0x04,0x01,0x04,0x0F,0xFC,0x11,0x04,0x11,0x04,0x11,0x00,0x18,0x00},/*"f",70*/ 90 {0x00,0x00,0x00,0xD6,0x01,0x29,0x01,0x29,0x01,0x29,0x01,0xC9,0x01,0x06,0x00,0x00},/*"g",71*/ 91 {0x10,0x04,0x1F,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"h",72*/ 92 {0x00,0x00,0x01,0x04,0x19,0x04,0x19,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"i",73*/ 93 {0x00,0x00,0x00,0x03,0x00,0x01,0x01,0x01,0x19,0x01,0x19,0xFE,0x00,0x00,0x00,0x00},/*"j",74*/ 94 {0x10,0x04,0x1F,0xFC,0x00,0x24,0x00,0x40,0x01,0xB4,0x01,0x0C,0x01,0x04,0x00,0x00},/*"k",75*/ 95 {0x00,0x00,0x10,0x04,0x10,0x04,0x1F,0xFC,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00},/*"l",76*/ 96 {0x01,0x04,0x01,0xFC,0x01,0x04,0x01,0x00,0x01,0xFC,0x01,0x04,0x01,0x00,0x00,0xFC},/*"m",77*/ 97 {0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x00,0x01,0x00,0x01,0x04,0x00,0xFC,0x00,0x04},/*"n",78*/ 98 {0x00,0x00,0x00,0xF8,0x01,0x04,0x01,0x04,0x01,0x04,0x01,0x04,0x00,0xF8,0x00,0x00},/*"o",79*/ 99 {0x01,0x01,0x01,0xFF,0x00,0x85,0x01,0x04,0x01,0x04,0x00,0x88,0x00,0x70,0x00,0x00},/*"p",80*/ 100 {0x00,0x00,0x00,0x70,0x00,0x88,0x01,0x04,0x01,0x04,0x01,0x05,0x01,0xFF,0x00,0x01},/*"q",81*/ 101 {0x01,0x04,0x01,0x04,0x01,0xFC,0x00,0x84,0x01,0x04,0x01,0x00,0x01,0x80,0x00,0x00},/*"r",82*/ 102 {0x00,0x00,0x00,0xCC,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x24,0x01,0x98,0x00,0x00},/*"s",83*/ 103 {0x00,0x00,0x01,0x00,0x01,0x00,0x07,0xF8,0x01,0x04,0x01,0x04,0x00,0x00,0x00,0x00},/*"t",84*/ 104 {0x01,0x00,0x01,0xF8,0x00,0x04,0x00,0x04,0x00,0x04,0x01,0x08,0x01,0xFC,0x00,0x04},/*"u",85*/ 105 {0x01,0x00,0x01,0x80,0x01,0x70,0x00,0x0C,0x00,0x10,0x01,0x60,0x01,0x80,0x01,0x00},/*"v",86*/ 106 {0x01,0xF0,0x01,0x0C,0x00,0x30,0x01,0xC0,0x00,0x30,0x01,0x0C,0x01,0xF0,0x01,0x00},/*"w",87*/ 107 {0x00,0x00,0x01,0x04,0x01,0x8C,0x00,0x74,0x01,0x70,0x01,0x8C,0x01,0x04,0x00,0x00},/*"x",88*/ 108 {0x01,0x01,0x01,0x81,0x01,0x71,0x00,0x0E,0x00,0x18,0x01,0x60,0x01,0x80,0x01,0x00},/*"y",89*/ 109 {0x00,0x00,0x01,0x84,0x01,0x0C,0x01,0x34,0x01,0x44,0x01,0x84,0x01,0x0C,0x00,0x00},/*"z",90*/ 110 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x3E,0xFC,0x40,0x02,0x40,0x02},/*"{",91*/ 111 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/ 112 {0x00,0x00,0x40,0x02,0x40,0x02,0x3E,0xFC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"}",93*/ 113 {0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/*"~",94*/ 114 }; 115 116 struct spi_device *spi_ssd1306_dev; 117 118 static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) 119 { 120 struct spi_transfer t; 121 struct spi_message m; 122 123 uint16_t data = chData; 124 125 memset(&t,0,sizeof(struct spi_transfer)); 126 127 if (chCmd) { 128 data |= (1 << 8); 129 } else { 130 data &= ~(1 << 8); 131 } 132 133 t.tx_buf = &data; 134 t.len = 2; 135 t.bits_per_word = 9; 136 //t.cs_change = 1; 137 spi_message_init(&m); 138 spi_message_add_tail(&t, &m); 139 spi_sync(spi_ssd1306_dev, &m); 140 } 141 142 143 void ssd1306_display_on(void) 144 { 145 ssd1306_write_byte(0x8D, SSD1306_CMD); 146 ssd1306_write_byte(0x14, SSD1306_CMD); 147 ssd1306_write_byte(0xAF, SSD1306_CMD); 148 } 149 150 /** 151 * @brief OLED turns off 152 * 153 * @param None 154 * 155 * @retval None 156 **/ 157 void ssd1306_display_off(void) 158 { 159 ssd1306_write_byte(0x8D, SSD1306_CMD); 160 ssd1306_write_byte(0x10, SSD1306_CMD); 161 ssd1306_write_byte(0xAE, SSD1306_CMD); 162 } 163 164 void ssd1306_refresh_gram(void) 165 { 166 uint8_t i, j; 167 168 for (i = 0; i < 8; i ++) { 169 ssd1306_write_byte(0xB0 + i, SSD1306_CMD); 170 ssd1306_write_byte(0x02, SSD1306_CMD); 171 ssd1306_write_byte(0x10, SSD1306_CMD); 172 for (j = 0; j < 128; j ++) { 173 ssd1306_write_byte(s_chDispalyBuffer[j][i], SSD1306_DAT); 174 } 175 } 176 } 177 178 179 void ssd1306_clear_screen(uint8_t chFill) 180 { 181 memset(s_chDispalyBuffer,chFill, sizeof(s_chDispalyBuffer)); 182 ssd1306_refresh_gram(); 183 } 184 185 /** 186 * @brief Draws a piont on the screen 187 * 188 * @param chXpos: Specifies the X position 189 * @param chYpos: Specifies the Y position 190 * @param chPoint: 0: the point turns off 1: the piont turns on 191 * 192 * @retval None 193 **/ 194 195 void ssd1306_draw_point(uint8_t chXpos, uint8_t chYpos, uint8_t chPoint) 196 { 197 uint8_t chPos, chBx, chTemp = 0; 198 199 if (chXpos > 127 || chYpos > 63) { 200 return; 201 } 202 chPos = 7 - chYpos / 8; // 203 chBx = chYpos % 8; 204 chTemp = 1 << (7 - chBx); 205 206 if (chPoint) { 207 s_chDispalyBuffer[chXpos][chPos] |= chTemp; 208 209 } else { 210 s_chDispalyBuffer[chXpos][chPos] &= ~chTemp; 211 } 212 } 213 214 /** 215 * @brief Fills a rectangle 216 * 217 * @param chXpos1: Specifies the X position 1 (X top left position) 218 * @param chYpos1: Specifies the Y position 1 (Y top left position) 219 * @param chXpos2: Specifies the X position 2 (X bottom right position) 220 * @param chYpos3: Specifies the Y position 2 (Y bottom right position) 221 * 222 * @retval 223 **/ 224 225 void ssd1306_fill_screen(uint8_t chXpos1, uint8_t chYpos1, uint8_t chXpos2, uint8_t chYpos2, uint8_t chDot) 226 { 227 uint8_t chXpos, chYpos; 228 229 for (chXpos = chXpos1; chXpos <= chXpos2; chXpos ++) { 230 for (chYpos = chYpos1; chYpos <= chYpos2; chYpos ++) { 231 ssd1306_draw_point(chXpos, chYpos, chDot); 232 } 233 } 234 235 ssd1306_refresh_gram(); 236 } 237 238 239 /** 240 * @brief Displays one character at the specified position 241 * 242 * @param chXpos: Specifies the X position 243 * @param chYpos: Specifies the Y position 244 * @param chSize: 245 * @param chMode 246 * @retval 247 **/ 248 void ssd1306_display_char(uint8_t chXpos, uint8_t chYpos, uint8_t chChr, uint8_t chSize, uint8_t chMode) 249 { 250 uint8_t i, j; 251 uint8_t chTemp, chYpos0 = chYpos; 252 253 chChr = chChr - ' '; 254 for (i = 0; i < chSize; i ++) { 255 if (chMode) { 256 chTemp = c_chFont1608[chChr][i]; 257 } else { 258 chTemp = ~c_chFont1608[chChr][i]; 259 } 260 261 for (j = 0; j < 8; j ++) { 262 if (chTemp & 0x80) { 263 ssd1306_draw_point(chXpos, chYpos, 1); 264 } else { 265 ssd1306_draw_point(chXpos, chYpos, 0); 266 } 267 chTemp <<= 1; 268 chYpos ++; 269 270 if ((chYpos - chYpos0) == chSize) { 271 chYpos = chYpos0; 272 chXpos ++; 273 break; 274 } 275 } 276 } 277 } 278 279 /** 280 * @brief Displays a string on the screen 281 * 282 * @param chXpos: Specifies the X position 283 * @param chYpos: Specifies the Y position 284 * @param pchString: Pointer to a string to display on the screen 285 * 286 * @retval None 287 **/ 288 void ssd1306_display_string(uint8_t chXpos, uint8_t chYpos, const uint8_t *pchString, uint8_t chSize, uint8_t chMode) 289 { 290 while (*pchString != '\0') { 291 if (chXpos > (SSD1306_WIDTH - chSize / 2)) { 292 chXpos = 0; 293 chYpos += chSize; 294 if (chYpos > (SSD1306_HEIGHT - chSize)) { 295 chYpos = chXpos = 0; 296 ssd1306_clear_screen(0x00); 297 } 298 } 299 300 ssd1306_display_char(chXpos, chYpos, *pchString, chSize, chMode); 301 chXpos += chSize / 2; 302 pchString ++; 303 } 304 } 305 306 void ssd1306_init(void) 307 { 308 ssd1306_write_byte(0xAE, SSD1306_CMD);//--turn off oled panel 309 ssd1306_write_byte(0x00, SSD1306_CMD);//---set low column address 310 ssd1306_write_byte(0x10, SSD1306_CMD);//---set high column address 311 ssd1306_write_byte(0x40, SSD1306_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) 312 ssd1306_write_byte(0x81, SSD1306_CMD);//--set contrast control register 313 ssd1306_write_byte(0xCF, SSD1306_CMD);// Set SEG Output Current Brightness 314 ssd1306_write_byte(0xA1, SSD1306_CMD);//--Set SEG/Column Mapping 315 ssd1306_write_byte(0xC0, SSD1306_CMD);//Set COM/Row Scan Direction 316 ssd1306_write_byte(0xA6, SSD1306_CMD);//--set normal display 317 ssd1306_write_byte(0xA8, SSD1306_CMD);//--set multiplex ratio(1 to 64) 318 ssd1306_write_byte(0x3f, SSD1306_CMD);//--1/64 duty 319 ssd1306_write_byte(0xD3, SSD1306_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) 320 ssd1306_write_byte(0x00, SSD1306_CMD);//-not offset 321 ssd1306_write_byte(0xd5, SSD1306_CMD);//--set display clock divide ratio/oscillator frequency 322 ssd1306_write_byte(0x80, SSD1306_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec 323 ssd1306_write_byte(0xD9, SSD1306_CMD);//--set pre-charge period 324 ssd1306_write_byte(0xF1, SSD1306_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock 325 ssd1306_write_byte(0xDA, SSD1306_CMD);//--set com pins hardware configuration 326 ssd1306_write_byte(0x12, SSD1306_CMD); 327 ssd1306_write_byte(0xDB, SSD1306_CMD);//--set vcomh 328 ssd1306_write_byte(0x40, SSD1306_CMD);//Set VCOM Deselect Level 329 ssd1306_write_byte(0x20, SSD1306_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) 330 ssd1306_write_byte(0x02, SSD1306_CMD);// 331 ssd1306_write_byte(0x8D, SSD1306_CMD);//--set Charge Pump enable/disable 332 ssd1306_write_byte(0x14, SSD1306_CMD);//--set(0x10) disable 333 ssd1306_write_byte(0xA4, SSD1306_CMD);// Disable Entire Display On (0xa4/0xa5) 334 ssd1306_write_byte(0xA6, SSD1306_CMD);// Disable Inverse Display On (0xa6/a7) 335 ssd1306_write_byte(0xAF, SSD1306_CMD);//--turn on oled panel 336 337 ssd1306_display_on(); 338 ssd1306_clear_screen(0xff); 339 340 } 341 342 343 static int __devinit spi_ssd1306_probe(struct spi_device *spi) 344 { 345 printk("spi_ssd1306_probe\n"); 346 spi_ssd1306_dev = spi; 347 spi_ssd1306_dev->bits_per_word = 9; 348 349 ssd1306_init(); 350 ssd1306_clear_screen(0x00); 351 ssd1306_display_off(); 352 353 ssd1306_display_string(18, 0, "hello, Linux!", 16, 1); 354 ssd1306_display_string(0, 16, "this is a spi driver demo!", 16, 1); 355 ssd1306_refresh_gram(); 356 ssd1306_display_on(); 357 358 return 0; 359 } 360 361 362 static int __devexit spi_ssd1306_remove(struct spi_device *spi) 363 { 364 printk("ssd1306_remove\n"); 365 366 ssd1306_clear_screen(0x00); 367 ssd1306_display_off(); 368 return 0; 369 } 370 371 372 static struct spi_driver spi_ssd1306_driver = { 373 .driver = { 374 .name = "spi_ssd1306", 375 .bus = &spi_bus_type, 376 .owner = THIS_MODULE, 377 }, 378 .probe = spi_ssd1306_probe, 379 .remove = __devexit_p(spi_ssd1306_remove), 380 }; 381 382 383 static int spi_ssd1306_init(void) 384 { 385 return spi_register_driver(&spi_ssd1306_driver); 386 } 387 388 389 static void spi_ssd1306_exit(void) 390 { 391 spi_unregister_driver(&spi_ssd1306_driver); 392 } 393 394 395 module_init(spi_ssd1306_init); 396 module_exit(spi_ssd1306_exit); 397 398 MODULE_LICENSE("GPL");
makefile
1 KERN_DIR = /work/system/linux-2.6.22.6 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += spi_platform_dev.o 11 obj-m += spi_s3c24xx_gpio.o 12 obj-m += spi_bitbang.o 13 obj-m += spi_ssd1306_drv.o
实验现象: