• spi子系统之驱动SSD1306 OLED


    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
    ",
    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
    ",
     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! 
    ");
    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 != '') {       
    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
    ");
    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
    ");
    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

    实验现象:

  • 相关阅读:
    LayUI上传图片
    快递查询
    安装 Python
    HTML5 WebSocket
    反射实例
    反射
    工厂模式之工厂方法案例
    工厂模式之简单工厂案例
    第三方登录
    封装条形码MaHelper
  • 原文地址:https://www.cnblogs.com/hackfun/p/6082489.html
Copyright © 2020-2023  润新知