平台简介

开发板:TQ2440 (NandFlash:256M  内存:64M)

u-boot版本:u-boot-2015.04

内核版本:Linux-3.14

作者:彭东林

邮箱:pengdonglin137@163.com

摘要

这篇博客的目的是简要分析两种spi驱动的实现,一种是利用Samsung的S3C2440自带的硬件SPI控制器,另一种是利用Linux内核已经写好的用GPIO模拟SPI时序,实现一个软件SPI控制器。操作的外设是韦东山的SPI视频教程中提供的OLED模块,同时分享一下在使用逻辑分析仪Saleae16调试SPI时遇到的问题。

相关的内核代码已经上传:git@code.csdn.net:pengdonglin137/linux-3-14-y.git

可以看看代码提交记录。

正文

SPI驱动实现之硬件控制器

一、驱动框架

二、代码

SPI硬件控制器

这里采用的是platform架构,分为device和driver两个部分。

1、platform_device

文件:arch/arm/plat-samsung/devs.c

   1: static struct resource s3c_spi0_resource[] = {
   2:     [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32),
   3:     [1] = DEFINE_RES_IRQ(IRQ_SPI0),
   4: };
   5:  
   6: static void s3c24xx_spi_set_cs(struct s3c2410_spi_info *spi, int cs, int pol)
   7: {
   8:     gpio_set_value(cs, pol);
   9: }
  10:  
  11: static struct s3c2410_spi_info s3c_spi_info[] = {
  12:     {
  13:         .num_cs  = S3C_GPIO_END,
  14:         .bus_num = 0,
  15:         .set_cs = s3c24xx_spi_set_cs,
  16:     }
  17: };
  18:  
  19: struct platform_device s3c_device_spi0 = {
  20:     .name        = "s3c2410-spi",
  21:     .id        = 0,
  22:     .num_resources    = ARRAY_SIZE(s3c_spi0_resource),
  23:     .resource    = s3c_spi0_resource,
  24:     .dev        = {
  25:         .dma_mask        = &samsung_device_dma_mask,
  26:         .coherent_dma_mask    = DMA_BIT_MASK(32),
  27:         .platform_data        = (void *)s3c_spi_info,
  28:     }
  29: };

第15行是片选函数,它的第二个参数cs来自spi从设备的板级信息,表示这个从设备的片选引脚;

第14行表示spi控制器的编号是0,将来在spi从设备的板级信息中有体现,意思是将来这个spi从设备挂载在编号为0的spi总线下面;

第27行,在linux原生的代码中没有实现platform_data,在调用probe函数的时候会报错;

2、platform_driver

文件:drivers/spi/spi-s3c24xx.c

   1: MODULE_ALIAS("platform:s3c2410-spi");
   2: static struct platform_driver s3c24xx_spi_driver = {
   3:     .probe        = s3c24xx_spi_probe,
   4:     .remove        = s3c24xx_spi_remove,
   5:     .driver        = {
   6:         .name    = "s3c2410-spi",
   7:         .owner    = THIS_MODULE,
   8:         .pm    = S3C24XX_SPI_PMOPS,
   9:     },
  10: };
  11: module_platform_driver(s3c24xx_spi_driver);
  12:  

OLED 板级信息

这里调用了spi子系统提供的函数接口。

1、板级信息

文件:arch/arm/mach-s3c24xx/mach-tq2440.c

   1: /* SPI OLED */
   2: static struct spi_board_info tq2440_spi_board_info[] __initdata = {
   3:     {
   4:         .modalias    = "oled",
   5:         .max_speed_hz    = 10000000,
   6:         .bus_num    = 0,
   7:         .mode        = SPI_MODE_0,
   8:         .chip_select    = S3C2410_GPG(1),
   9:         .platform_data    = (const void *)S3C2410_GPF(3),
  10:     },
  11: };
  12:  
  13: static struct platform_device *tq2440_devices[] __initdata = {
  14:     ......
  15:     &s3c_device_spi0,
  16: };
  17:  
  18: static void __init tq2440_machine_init(void)
  19: {
  20: ......
  21:     spi_register_board_info(tq2440_spi_board_info, ARRAY_SIZE(tq2440_spi_board_info));
  22: ......
  23: }
  24:  
  25: MACHINE_START(TQ2440, "TQ2440")
  26: ......
  27:     .init_machine    = tq2440_machine_init,
  28: ......
  29: MACHINE_END

第4行,将来会跟驱动中的name进行匹配;

第5行,表示通信速率,这里设置的是10MHz;

第6行,表示使用的spi总线的编号是0;

第7行,表示使用的spi模式是0,这里要根据oled的芯片手册(SSD1306-Revision 1.1 (Charge Pump).pdf)

第8行,oled使用的片选引脚;

第9行,用于区分命令和数据模式的GPIO资源,这个会在驱动中解析;

第21行,注册spi从设备板级信息;

2、oled驱动

文件:drivers/spi/oled/spi_oled_drv.c

   1: #include <linux/init.h>
   2: #include <linux/fs.h>
   3: #include <linux/slab.h>
   4: #include <linux/module.h>
   5: #include <linux/kernel.h>
   6: #include <linux/device.h>
   7: #include <sound/core.h>
   8: #include <linux/spi/spi.h>
   9: #include <asm/uaccess.h>
  10:  
  11: #include <mach/hardware.h>
  12: #include <mach/regs-gpio.h>
  13:  
  14: #include <linux/gpio.h>
  15: #include <plat/gpio-cfg.h>
  16:  
  17: /* 构造注册 spi_driver */
  18:  
  19: static int major;
  20: static struct class *class;
  21:  
  22: static int spi_oled_dc_pin;
  23: static struct spi_device *spi_oled_dev;
  24: static unsigned char *ker_buf;
  25:  
  26: static void OLED_Set_DC(char val)
  27: {
  28:     gpio_set_value(spi_oled_dc_pin, val);
  29: }
  30:  
  31: static void OLEDWriteCmd(unsigned char cmd)
  32: {
  33:     OLED_Set_DC(0); /* command */
  34:     spi_write(spi_oled_dev, &cmd, 1);
  35:     OLED_Set_DC(1); /*  */
  36: }
  37:  
  38: static void OLEDWriteDat(unsigned char dat)
  39: {
  40:     OLED_Set_DC(1); /* data */
  41:     spi_write(spi_oled_dev, &dat, 1);
  42:     OLED_Set_DC(1); /*  */
  43: }
  44:  
  45: static void OLEDSetPageAddrMode(void)
  46: {
  47:     OLEDWriteCmd(0x20);
  48:     OLEDWriteCmd(0x02);
  49: }
  50:  
  51: static void OLEDSetPos(int page, int col)
  52: {
  53:     OLEDWriteCmd(0xB0 + page); /* page address */
  54:  
  55:     OLEDWriteCmd(col & 0xf);   /* Lower Column Start Address */
  56:     OLEDWriteCmd(0x10 + (col >> 4));   /* Lower Higher Start Address */
  57: }
  58:  
  59:  
  60: static void OLEDClear(void)
  61: {
  62:     int page, i;
  63:     for (page = 0; page < 8; page ++)
  64:     {
  65:         OLEDSetPos(page, 0);
  66:         for (i = 0; i < 128; i++)
  67:             OLEDWriteDat(0);
  68:     }
  69: }
  70:  
  71: void OLEDClearPage(int page)
  72: {
  73:     int i;
  74:     OLEDSetPos(page, 0);
  75:     for (i = 0; i < 128; i++)
  76:         OLEDWriteDat(0);    
  77: }
  78:  
  79: void OLEDInit(void)
  80: {
  81:     /* 向OLED发命令以初始化 */
  82:     OLEDWriteCmd(0xAE); /*display off*/ 
  83:     OLEDWriteCmd(0x00); /*set lower column address*/ 
  84:     OLEDWriteCmd(0x10); /*set higher column address*/ 
  85:     OLEDWriteCmd(0x40); /*set display start line*/ 
  86:     OLEDWriteCmd(0xB0); /*set page address*/ 
  87:     OLEDWriteCmd(0x81); /*contract control*/ 
  88:     OLEDWriteCmd(0x66); /*128*/ 
  89:     OLEDWriteCmd(0xA1); /*set segment remap*/ 
  90:     OLEDWriteCmd(0xA6); /*normal / reverse*/ 
  91:     OLEDWriteCmd(0xA8); /*multiplex ratio*/ 
  92:     OLEDWriteCmd(0x3F); /*duty = 1/64*/ 
  93:     OLEDWriteCmd(0xC8); /*Com scan direction*/ 
  94:     OLEDWriteCmd(0xD3); /*set display offset*/ 
  95:     OLEDWriteCmd(0x00); 
  96:     OLEDWriteCmd(0xD5); /*set osc division*/ 
  97:     OLEDWriteCmd(0x80); 
  98:     OLEDWriteCmd(0xD9); /*set pre-charge period*/ 
  99:     OLEDWriteCmd(0x1f); 
 100:     OLEDWriteCmd(0xDA); /*set COM pins*/ 
 101:     OLEDWriteCmd(0x12); 
 102:     OLEDWriteCmd(0xdb); /*set vcomh*/ 
 103:     OLEDWriteCmd(0x30); 
 104:     OLEDWriteCmd(0x8d); /*set charge pump enable*/ 
 105:     OLEDWriteCmd(0x14); 
 106:  
 107:     OLEDSetPageAddrMode();
 108:  
 109:     OLEDClear();
 110:  
 111:     OLEDWriteCmd(0xAF); /*display ON*/    
 112: }
 113:  
 114:  
 115: #define OLED_CMD_INIT       0x100001
 116: #define OLED_CMD_CLEAR_ALL  0x100002
 117: #define OLED_CMD_CLEAR_PAGE 0x100003
 118: #define OLED_CMD_SET_POS    0x100004
 119:  
 120: static long oled_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 121: {
 122:     int page;
 123:     int col;
 124:  
 125:     switch (cmd)
 126:     {
 127:     case OLED_CMD_INIT:
 128:         {
 129:             OLEDInit();
 130:             break;
 131:         }
 132:     case OLED_CMD_CLEAR_ALL:
 133:         {
 134:             OLEDClear();
 135:             break;
 136:         }
 137:     case OLED_CMD_CLEAR_PAGE:
 138:         {
 139:             page = arg;
 140:             OLEDClearPage(page);
 141:             break;
 142:         }
 143:     case OLED_CMD_SET_POS:
 144:         {
 145:             page = arg & 0xff;
 146:             col  = (arg >> 8) & 0xff;
 147:             OLEDSetPos(page, col);
 148:             break;
 149:         }
 150:     }
 151:     return 0;
 152: }
 153:  
 154: static ssize_t oled_write(struct file *file,
 155:     const char __user *buf,
 156:     size_t count, loff_t *ppos)
 157: {
 158:     int ret;
 159:  
 160:     if (count > 4096)
 161:         return -EINVAL;
 162:     ret = copy_from_user(ker_buf, buf, count);
 163:     OLED_Set_DC(1); /* data */
 164:     spi_write(spi_oled_dev, ker_buf, count);
 165:     return 0;
 166: }
 167:  
 168:  
 169: static struct file_operations oled_ops = {
 170:     .owner            = THIS_MODULE,
 171:     .unlocked_ioctl   = oled_ioctl,
 172:     .write            = oled_write,
 173: };
 174:  
 175: static int spi_oled_probe(struct spi_device *spi)
 176: {
 177:     int ret;
 178:  
 179:     spi_oled_dev = spi;
 180:     spi_oled_dc_pin = (int)dev_get_platdata(&spi->dev);
 181:  
 182:     ret = devm_gpio_request(&spi->dev, spi_oled_dc_pin, "OLED_DC");
 183:     if (ret < 0)
 184:         return ret;
 185:     gpio_direction_output(spi_oled_dc_pin, 0);
 186:  
 187: #ifndef CONFIG_TQ2440_USE_SPI_GPIO
 188:     ret = devm_gpio_request(&spi->dev, spi->chip_select, "OLED_CHIP_SELECT");
 189:     if (ret < 0)
 190:         return ret;
 191:     gpio_direction_output(spi->chip_select, 1);
 192: #endif
 193:  
 194:     ker_buf = kmalloc(4096, GFP_KERNEL);
 195:  
 196:     /* 注册一个 file_operations */
 197:     major = register_chrdev(0, "oled", &oled_ops);
 198:  
 199:     class = class_create(THIS_MODULE, "oled");
 200:  
 201:     /* 为了让mdev根据这些信息来创建设备节点 */
 202:     device_create(class, NULL, MKDEV(major, 0), NULL, "oled"); /* /dev/oled */
 203:  
 204:     return 0;
 205: }
 206:  
 207: static int spi_oled_remove(struct spi_device *spi)
 208: {
 209:     device_destroy(class, MKDEV(major, 0));
 210:     class_destroy(class);
 211:     unregister_chrdev(major, "oled");
 212:  
 213:     kfree(ker_buf);
 214:  
 215:     return 0;
 216: }
 217:  
 218: static struct spi_driver spi_oled_drv = {
 219:     .driver = {
 220:         .name    = "oled",
 221:         .owner    = THIS_MODULE,
 222:     },
 223:     .probe        = spi_oled_probe,
 224:     .remove        = spi_oled_remove,
 225: };
 226:  
 227: static int spi_oled_init(void)
 228: {
 229:     return spi_register_driver(&spi_oled_drv);
 230: }
 231:  
 232: static void spi_oled_exit(void)
 233: {
 234:     spi_unregister_driver(&spi_oled_drv);
 235: }
 236:  
 237: module_init(spi_oled_init);
 238: module_exit(spi_oled_exit);
 239: MODULE_DESCRIPTION("OLED SPI Driver");
 240: MODULE_AUTHOR("weidongshan@qq.com,www.100ask.net");
 241: MODULE_LICENSE("GPL");

第187行,如果使用的是gpio模拟的spi的话,这个宏CONFIG_TQ2440_USE_SPI_GPIO会配置,这里我们使用的不是gpio模拟的,所以这个宏没有配置;

第182行,申请gpio,这里使用的函数是devm_gpio_request,它的好处是你不用再考虑gpio资源的释放了,系统会自动帮助你完成,类似的还有devm_kmalloc;

内核配置

System Type  --->

SAMSUNG S3C24XX SoCs Support  --->

[ ]   TQ2440 use spi gpio to communicate with peripherals

Device Drivers  --->

[*] SPI support  --->

<*>   Samsung S3C24XX series SPI

<*>   Support TQ2440 OLED (from 100ask.com)

应用

1、oled_test.c
   1: #include <stdlib.h>
   2: #include <stdio.h>
   3: #include <string.h>
   4: #include <sys/types.h>
   5: #include <sys/stat.h>
   6: #include <fcntl.h>
   7: #include <unistd.h>
   8: #include <sys/ioctl.h>
   9:  
  10: #include "oledfont.h"
  11:  
  12: /* oled_test init
  13:  * oled_test clear
  14:  * oled_test clear <page>
  15:  * oled_test <page> <col> <string>
  16:  */
  17:  
  18: #define OLED_CMD_INIT       0x100001
  19: #define OLED_CMD_CLEAR_ALL  0x100002
  20: #define OLED_CMD_CLEAR_PAGE 0x100003
  21: #define OLED_CMD_SET_POS    0x100004
  22:  
  23:  
  24:  
  25: /* page: 0-7
  26:  * col : 0-127
  27:  * 字符: 8x16象素
  28:  */
  29: void OLEDPutChar(int fd, int page, int col, char c)
  30: {
  31:     int i = 0;
  32:     /* 得到字模 */
  33:     const unsigned char *dots = oled_asc2_8x16[c - ' '];
  34:  
  35:     /* 发给OLED */
  36:     //OLEDSetPos(page, col);
  37:     //ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
  38:     ioctl(fd, OLED_CMD_SET_POS, page | (col << 8));
  39:     /* 发出8字节数据 */
  40:     //for (i = 0; i < 8; i++)
  41:     //    OLEDWriteDat(dots[i]);
  42:     write(fd, &dots[0], 8);
  43:  
  44:     //OLEDSetPos(page+1, col);
  45:     //ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
  46:     ioctl(fd, OLED_CMD_SET_POS, (page+1) | (col << 8));
  47:     /* 发出8字节数据 */
  48:     //for (i = 0; i < 8; i++)
  49:     //    OLEDWriteDat(dots[i+8]);
  50:     write(fd, &dots[8], 8);
  51: }
  52:  
  53:  
  54:  
  55: /* page: 0-7
  56:  * col : 0-127
  57:  * 字符: 8x16象素
  58:  */
  59: void OLEDPrint(int fd, int page, int col, char *str)
  60: {
  61:     int i = 0;
  62:  
  63:     ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
  64:     ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
  65:     while (str[i])
  66:     {
  67:         OLEDPutChar(fd, page, col, str[i]);
  68:         col += 8;
  69:         if (col > 127)
  70:         {
  71:             col = 0;
  72:             page += 2;
  73:             ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
  74:             ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);
  75:         }
  76:         i++;
  77:     }
  78: }
  79:  
  80:  
  81: void print_usage(char *cmd)
  82: {
  83:     printf("Usage:\n");
  84:     printf("%s init\n", cmd);
  85:     printf("%s clear\n", cmd);
  86:     printf("%s clear <page>\n", cmd);
  87:     printf("%s <page> <col> <string>\n", cmd);
  88:     printf("eg:\n");
  89:     printf("%s 2 0 100ask.taobao.com\n", cmd);
  90:     printf("page is 0,1,...,7\n");
  91:     printf("col is 0,1,...,127\n");
  92: }
  93:  
  94: int main(int argc, char **argv)
  95: {
  96:     int do_init  = 0;
  97:     int do_clear = 0;
  98:     int do_show  = 0;
  99:     int page = -1;
 100:     int col;
 101:  
 102:     int fd;
 103:  
 104:     if (argc == 2 && !strcmp(argv[1], "init"))
 105:         do_init = 1;
 106:     if ((argc == 2) && !strcmp(argv[1], "clear"))
 107:     {
 108:         do_clear = 1;
 109:     }
 110:     if ((argc == 3) && !strcmp(argv[1], "clear"))
 111:     {
 112:         do_clear = 1;
 113:         page = strtoul(argv[2], NULL, 0);
 114:     }
 115:     if (argc == 4)
 116:     {
 117:         do_show = 1;
 118:         page = strtoul(argv[1], NULL, 0);
 119:         col = strtoul(argv[2], NULL, 0);
 120:     }
 121:  
 122:     if (!do_init && !do_clear && !do_show)
 123:     {
 124:         print_usage(argv[0]);
 125:         return -1;
 126:     }
 127:  
 128:     fd = open("/dev/oled", O_RDWR);
 129:     if (fd < 0)
 130:     {
 131:         printf("can't open /dev/oled\n");
 132:         return -1;
 133:     }
 134:  
 135:     if (do_init)
 136:         ioctl(fd, OLED_CMD_INIT);
 137:     else if (do_clear)
 138:     {
 139:         if (page == -1)
 140:             ioctl(fd, OLED_CMD_CLEAR_ALL);
 141:         else
 142:         {
 143:             if (page < 0 || page > 7)
 144:             {
 145:                 printf("page is 0,1,...,7\n");
 146:                 return -1;
 147:             }
 148:             ioctl(fd, OLED_CMD_CLEAR_PAGE, page);
 149:         }
 150:     }
 151:     else if (do_show)
 152:     {
 153:         if (page < 0 || page > 7)
 154:         {
 155:             printf("page is 0,1,...,7\n");
 156:             return -1;
 157:         }
 158:         if (col < 0 || col > 127)
 159:         {
 160:             printf("col is 0,1,...,127\n");
 161:             return -1;
 162:         }
 163:  
 164:         OLEDPrint(fd, page, col, argv[3]);
 165:     }
 166:     return 0;
 167: }
 168:  

SPI驱动实现之软件控制器

一、驱动框架

从图中可以看出,只替换了两个部分,在硬件上使用几个GPIO,不再使用SPI硬件控制器,所以在驱动上也需要做相应的变更,这部分在kernel中已经支持了。

二、代码

下面我们只列一下不同的部分。

SPI GPIO软件控制器

这里采用的也是platform架构。

1、platform_device
   1: static struct spi_gpio_platform_data s3c_spi0_gpio_info = {
   2:     .num_chipselect = S3C_GPIO_END,
   3:     .miso        = S3C2410_GPE(11),
   4:     .mosi        = S3C2410_GPE(12),
   5:     .sck        = S3C2410_GPE(13),
   6: };
   7:  
   8: static struct platform_device s3c_device_spi0_gpio = {
   9:     .name        = "spi_gpio",
  10:     .id        = 0,
  11:     .dev        = {
  12:         .platform_data        = (void *)&s3c_spi0_gpio_info,
  13:     }
  14: };
  15:  
  16: static struct platform_device *tq2440_devices[] __initdata = {
  17: ......
  18:     &s3c_device_spi0_gpio
  19: };
  20:  
  21: static void __init tq2440_machine_init(void)
  22: {
  23: ......
  24:     platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));
  25: ......
  26: }
  27:  
  28: MACHINE_START(TQ2440, "TQ2440")
  29: ......
  30:     .init_machine    = tq2440_machine_init,
  31: ......
  32: MACHINE_END

第3/4/5行,表示需要spi软件控制器需要使用的gpio引脚,至少需要MISO、SCK、MOSI;

第10行,表示模拟出的spi软件控制器的编号,也就是spi总线编号;

第9行,将来会跟驱动中的name进行匹配;

2、platform_driver

文件:drivers/spi/spi-gpio.c

   1: #define DRIVER_NAME    "spi_gpio"
   2: ......
   3:  
   4: static struct platform_driver spi_gpio_driver = {
   5:     .driver = {
   6:         .name    = DRIVER_NAME,
   7:         .owner    = THIS_MODULE,
   8:         .of_match_table = of_match_ptr(spi_gpio_dt_ids),
   9:     },
  10:     .probe        = spi_gpio_probe,
  11:     .remove        = spi_gpio_remove,
  12: };
  13: module_platform_driver(spi_gpio_driver);

OLED驱动

下面只列出需要注意的地方。

1、OLED板级信息
   1: /* SPI OLED */
   2: static struct spi_board_info tq2440_spi_board_info[] __initdata = {
   3:     {
   4:         .modalias    = "oled",
   5:         .max_speed_hz    = 10000000,
   6:         .bus_num    = 0,
   7:         .mode        = SPI_MODE_0,
   8:         .chip_select    = S3C2410_GPG(1),
   9:         .platform_data    = (const void *)S3C2410_GPF(3),
  10: #ifdef CONFIG_TQ2440_USE_SPI_GPIO
  11:         .controller_data= (void *)S3C2410_GPG(1),
  12: #endif
  13:     },
  14: };

第11行,这个表示片选信号,具体参见drivers/spi/spi-gpio.c的实现;

内核配置

System Type  --->

SAMSUNG S3C24XX SoCs Support  --->

[*]   TQ2440 use spi gpio to communicate with peripherals

Device Drivers  --->

[*] SPI support  --->

<*>   GPIO-based bitbanging SPI Master

<*>   Support TQ2440 OLED (from 100ask.com)

测试

编译app

arm-linux-gcc -Wall oled_test.c -o oled_test

操作

   1: [root@TQ2440 sky]# ./oled_test init
   2: [root@TQ2440 sky]# ./oled_test clear
   3: [root@TQ2440 sky]# ./oled_test 0 0 "pengdonglin137"
   4: [root@TQ2440 sky]# 

结果(使用SPI驱动的两种实现方式的实验现象是一样的,只是驱动的内部实现机理不同)

用Saleae16分析SPI时序

上面我们在设置oled板级信息的时候将spi通信的速率设置为了10MHz,我在抓取spi波形的时候,遇到了问题。

现象如下:

上面的图中,CLOCK时钟有些异常,可以看到只抓到7个波形,并且波形不是很均匀,出现很多类似的波形。刚开始我还以为spi控制器出问题了,后来发现,原来我把采样频率从16M提高到50M以后,全都正常了。

我想就是采用率太低的可能,记得有一个香农采样定理,采样信号的频率至少应该是被采信号的两倍。为了印证这个看法,我又做了下面几个测试。

1、将采样频率设置为25M,通信速率为10M

整个波形都没有问题。

2、将采样频率设置为16M,将通讯速率设置为7M

可以看到,至少抓到的还是8个波形,还算正常。

因此,基本验证了我的看法。

完。

基于TQ2440的SPI驱动学习(OLED)相关推荐

  1. Linux SPI驱动学习——调用SPI读写函数

    Linux SPI驱动学习--调用SPI读写函数 博客说明 开发环境 1. 基于W25Q16型FLASH来看SPI的读写函数 附录 博客说明 撰写日期 2019.10.25 完稿日期 未完稿 最近维护 ...

  2. STM32—4线SPI驱动SSD1306 OLED

    文章目录 一.OLED简介 二.驱动SSD1306所需知识 1.引脚介绍 2.通信时序 3.显存GRAM 4.字库 5.SSD1306基本命令 三.代码讲解 1.相关引脚配置 2.模拟SPI通信 3. ...

  3. DJYOS驱动开发系列三:基于DJYOS的SPI驱动编写指导手册

    1.贡献者列表 深圳市秦简计算机系统有限公司DJYOS驱动开发团队. 2.概述 DJYOS的DjyBus总线模型为IIC.SPI之类的器件提供统一的访问接口,SPIBUS模块是DjyBus模块的一个子 ...

  4. 内核自带的基于GPIO的LED驱动学习(一)

    为什么学习内核自带的LED驱动? 前面已经学习过了基于纯字符设备的LED驱动,也学习过了基于平台驱动的LED驱动,但是感觉都是按照教程在生搬硬套,到底我们写出来的驱动能不能拿得上台面,是否能在实际的生 ...

  5. 基于IIC和SPI协议的OLED显示(STM32)

    文章目录 一.介绍SPI 二.使用OLED显示屏显示数据 1.介绍OLED 2.连接开发板和显示屏 3.运行结果 三.修改程序实现显示 四.结果展示 五.参考 一.介绍SPI SPI(Serial P ...

  6. 基于STM32F103,SPI驱动GC9306屏幕

    下面我将代码放在下面仅供参考,本人实测过哟,希望对你有用. 因为是驱动屏幕,所以是三线spi. gc9306.c文件 #include "gc9306.H" #include &q ...

  7. 内核自带的基于GPIO的LED驱动学习(三)

    上篇文章讲到了gpio_leds_create函数(),其定义位于drivers/leds/leds-gpio.c,如下: static struct gpio_leds_priv *gpio_led ...

  8. 内核自带的基于GPIO的LED驱动学习(二)

    2)分析平台驱动的probe函数 好,既然这个LED驱动使用的是平台驱动框架,当设备和驱动匹配上之后,就会执行指定的probe函数,那接下来的工作就转移到分析对应的probe函数了.为了直观,我把pr ...

  9. 基于RK3399PRo的串口驱动学习-XR21V1414IM48

    目录 原理图 XR21V1414IM48简介 重点代码 函数入口初始化 tty串口操作集 USB转串初始化 设备ID 测试代码 头文件 宏定义 输入参数提示 打开设备 设置波特率 配置数据位.停止位. ...

  10. 【基于Linux系统设备树的SPI驱动编写方法】

    文章目录 前言 一.SPI驱动编写 1.修改设备树  a.设备树文件是什么?  b.设备树怎么改? 2.编写驱动 二.完善和测试 1.编译和应用程序  a.编译 && 拷贝到开发板命令 ...

最新文章

  1. Python Qt GUI设计:窗口布局管理方法【基础】(基础篇—5)
  2. bazel 链接第三方动态库_Linux 动态库与静态库制作及使用详解
  3. 【杂谈】参加有三AI秋季划4个月,薪资翻倍,我在有三AI都学了啥?
  4. Cocoa原理指南-学习和实践1
  5. different styles of button in footer area
  6. 公安计算机技能测试题库,2018公安文职考试题库:行政职业能力测验
  7. 销售面销五个最基础步骤步骤_3个步骤,可提供强有力的反馈
  8. 罗永浩“真还传”再出番外篇,被执行1800万,交个朋友回应来了…
  9. Properties工具类
  10. c语言命令行参数怎么输入_C语言程序设计——这个函数原型你看懂了吗
  11. java课程设计简易记事本
  12. 教你如何制作网页木马
  13. java 数组和集合的区别
  14. python计算方位角_利用python计算太阳天顶角、方位角、高度角
  15. 性能测试七种常用方法,以及四大应用领域
  16. 昨日种种皆成今我,切莫思量切莫哀
  17. nonlocal和global的区别
  18. M1 Mac 上安装 python mysqlclient
  19. 谷歌的android官方刷机,一加手机谷歌原生Android ROM包刷机教程
  20. 大数据工资这么高,零基础可以学习吗?

热门文章

  1. 常用App URL Scheme
  2. ​独家 | 为什么程序员不应该长期留在一家公司?
  3. C#.NET 将日期转换为大写
  4. 游戏服务端究竟解决了什么问题?
  5. 信息炸弹——Message Boom
  6. web网页设计期末课程大作业:基于HTML+CSS+JavaScript个人书画作品展示HTML模板(6页)
  7. 服务器装系统提示未找到任何驱动器,u盘装系统win7显示未找到任何驱动器怎么办?未找到任何驱动器解决步骤...
  8. VMWare虚拟机Ubantu20.10添加中文智能拼音输入法
  9. windows下安装you-get的简要记录
  10. _initialize() 区别 __construct()