spi子系统之驱动SSD1306 OLED

时间:2022-06-16 23:46:14

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 }

 

  1. 注册了一个platform_driver
  2. 在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 #include <asm/arch/spi-gpio.h>
2
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 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/device.h>
4 #include <linux/interrupt.h>
5 #include <linux/interrupt.h>
6 #include <linux/mtd/mtd.h>
7 #include <linux/mtd/partitions.h>
8 #include <linux/spi/spi.h>
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

 

实验现象:

spi子系统之驱动SSD1306 OLED

 

spi子系统之驱动SSD1306 OLED

 

spi子系统之驱动SSD1306 OLED