平台简介

开发板: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. 1: static struct resource s3c_spi0_resource[] = {

  1. 2: [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32),

  1. 3: [1] = DEFINE_RES_IRQ(IRQ_SPI0),

  1. 4: };

  1. 5: 

  1. 6: static void s3c24xx_spi_set_cs(struct s3c2410_spi_info *spi, int cs, int pol)

  1. 7: {

  1. 8: gpio_set_value(cs, pol);

  1. 9: }

  1. 10: 

  1. 11: static struct s3c2410_spi_info s3c_spi_info[] = {

  1. 12: {

  1. 13: .num_cs = S3C_GPIO_END,

  1. 14: .bus_num = 0,

  1. 15: .set_cs = s3c24xx_spi_set_cs,

  1. 16: }

  1. 17: };

  1. 18: 

  1. 19: struct platform_device s3c_device_spi0 = {

  1. 20: .name = "s3c2410-spi",

  1. 21: .id = 0,

  1. 22: .num_resources = ARRAY_SIZE(s3c_spi0_resource),

  1. 23: .resource = s3c_spi0_resource,

  1. 24: .dev = {

  1. 25: .dma_mask = &samsung_device_dma_mask,

  1. 26: .coherent_dma_mask = DMA_BIT_MASK(32),

  1. 27: .platform_data = (void *)s3c_spi_info,

  1. 28: }

  1. 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. 1: MODULE_ALIAS("platform:s3c2410-spi");

  1. 2: static struct platform_driver s3c24xx_spi_driver = {

  1. 3: .probe = s3c24xx_spi_probe,

  1. 4: .remove = s3c24xx_spi_remove,

  1. 5: .driver = {

  1. 6: .name = "s3c2410-spi",

  1. 7: .owner = THIS_MODULE,

  1. 8: .pm = S3C24XX_SPI_PMOPS,

  1. 9: },

  1. 10: };

  1. 11: module_platform_driver(s3c24xx_spi_driver);

  1. 12: 

OLED 板级信息

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

1、板级信息

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

  1. 1: /* SPI OLED */

  1. 2: static struct spi_board_info tq2440_spi_board_info[] __initdata = {

  1. 3: {

  1. 4: .modalias = "oled",

  1. 5: .max_speed_hz = 10000000,

  1. 6: .bus_num = 0,

  1. 7: .mode = SPI_MODE_0,

  1. 8: .chip_select = S3C2410_GPG(1),

  1. 9: .platform_data = (const void *)S3C2410_GPF(3),

  1. 10: },

  1. 11: };

  1. 12: 

  1. 13: static struct platform_device *tq2440_devices[] __initdata = {

  1. 14: ......

  1. 15: &s3c_device_spi0,

  1. 16: };

  1. 17: 

  1. 18: static void __init tq2440_machine_init(void)

  1. 19: {

  1. 20: ......

  1. 21: spi_register_board_info(tq2440_spi_board_info, ARRAY_SIZE(tq2440_spi_board_info));

  1. 22: ......

  1. 23: }

  1. 24: 

  1. 25: MACHINE_START(TQ2440, "TQ2440")

  1. 26: ......

  1. 27: .init_machine = tq2440_machine_init,

  1. 28: ......

  1. 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. 1: #include <linux/init.h>

  1. 2: #include <linux/fs.h>

  1. 3: #include <linux/slab.h>

  1. 4: #include <linux/module.h>

  1. 5: #include <linux/kernel.h>

  1. 6: #include <linux/device.h>

  1. 7: #include <sound/core.h>

  1. 8: #include <linux/spi/spi.h>

  1. 9: #include <asm/uaccess.h>

  1. 10: 

  1. 11: #include <mach/hardware.h>

  1. 12: #include <mach/regs-gpio.h>

  1. 13: 

  1. 14: #include <linux/gpio.h>

  1. 15: #include <plat/gpio-cfg.h>

  1. 16: 

  1. 17: /* 构造注册 spi_driver */

  1. 18: 

  1. 19: static int major;

  1. 20: static struct class *class;

  1. 21: 

  1. 22: static int spi_oled_dc_pin;

  1. 23: static struct spi_device *spi_oled_dev;

  1. 24: static unsigned char *ker_buf;

  1. 25: 

  1. 26: static void OLED_Set_DC(char val)

  1. 27: {

  1. 28: gpio_set_value(spi_oled_dc_pin, val);

  1. 29: }

  1. 30: 

  1. 31: static void OLEDWriteCmd(unsigned char cmd)

  1. 32: {

  1. 33: OLED_Set_DC(0); /* command */

  1. 34: spi_write(spi_oled_dev, &cmd, 1);

  1. 35: OLED_Set_DC(1); /* */

  1. 36: }

  1. 37: 

  1. 38: static void OLEDWriteDat(unsigned char dat)

  1. 39: {

  1. 40: OLED_Set_DC(1); /* data */

  1. 41: spi_write(spi_oled_dev, &dat, 1);

  1. 42: OLED_Set_DC(1); /* */

  1. 43: }

  1. 44: 

  1. 45: static void OLEDSetPageAddrMode(void)

  1. 46: {

  1. 47: OLEDWriteCmd(0x20);

  1. 48: OLEDWriteCmd(0x02);

  1. 49: }

  1. 50: 

  1. 51: static void OLEDSetPos(int page, int col)

  1. 52: {

  1. 53: OLEDWriteCmd(0xB0 + page); /* page address */

  1. 54: 

  1. 55: OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */

  1. 56: OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */

  1. 57: }

  1. 58: 

  1. 59: 

  1. 60: static void OLEDClear(void)

  1. 61: {

  1. 62: int page, i;

  1. 63: for (page = 0; page < 8; page ++)

  1. 64: {

  1. 65: OLEDSetPos(page, 0);

  1. 66: for (i = 0; i < 128; i++)

  1. 67: OLEDWriteDat(0);

  1. 68: }

  1. 69: }

  1. 70: 

  1. 71: void OLEDClearPage(int page)

  1. 72: {

  1. 73: int i;

  1. 74: OLEDSetPos(page, 0);

  1. 75: for (i = 0; i < 128; i++)

  1. 76: OLEDWriteDat(0);

  1. 77: }

  1. 78: 

  1. 79: void OLEDInit(void)

  1. 80: {

  1. 81: /* 向OLED发命令以初始化 */

  1. 82: OLEDWriteCmd(0xAE); /*display off*/

  1. 83: OLEDWriteCmd(0x00); /*set lower column address*/

  1. 84: OLEDWriteCmd(0x10); /*set higher column address*/

  1. 85: OLEDWriteCmd(0x40); /*set display start line*/

  1. 86: OLEDWriteCmd(0xB0); /*set page address*/

  1. 87: OLEDWriteCmd(0x81); /*contract control*/

  1. 88: OLEDWriteCmd(0x66); /*128*/

  1. 89: OLEDWriteCmd(0xA1); /*set segment remap*/

  1. 90: OLEDWriteCmd(0xA6); /*normal / reverse*/

  1. 91: OLEDWriteCmd(0xA8); /*multiplex ratio*/

  1. 92: OLEDWriteCmd(0x3F); /*duty = 1/64*/

  1. 93: OLEDWriteCmd(0xC8); /*Com scan direction*/

  1. 94: OLEDWriteCmd(0xD3); /*set display offset*/

  1. 95: OLEDWriteCmd(0x00);

  1. 96: OLEDWriteCmd(0xD5); /*set osc division*/

  1. 97: OLEDWriteCmd(0x80);

  1. 98: OLEDWriteCmd(0xD9); /*set pre-charge period*/

  1. 99: OLEDWriteCmd(0x1f);

  1. 100: OLEDWriteCmd(0xDA); /*set COM pins*/

  1. 101: OLEDWriteCmd(0x12);

  1. 102: OLEDWriteCmd(0xdb); /*set vcomh*/

  1. 103: OLEDWriteCmd(0x30);

  1. 104: OLEDWriteCmd(0x8d); /*set charge pump enable*/

  1. 105: OLEDWriteCmd(0x14);

  1. 106: 

  1. 107: OLEDSetPageAddrMode();

  1. 108: 

  1. 109: OLEDClear();

  1. 110: 

  1. 111: OLEDWriteCmd(0xAF); /*display ON*/

  1. 112: }

  1. 113: 

  1. 114: 

  1. 115: #define OLED_CMD_INIT 0x100001

  1. 116: #define OLED_CMD_CLEAR_ALL 0x100002

  1. 117: #define OLED_CMD_CLEAR_PAGE 0x100003

  1. 118: #define OLED_CMD_SET_POS 0x100004

  1. 119: 

  1. 120: static long oled_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

  1. 121: {

  1. 122: int page;

  1. 123: int col;

  1. 124: 

  1. 125: switch (cmd)

  1. 126: {

  1. 127: case OLED_CMD_INIT:

  1. 128: {

  1. 129: OLEDInit();

  1. 130: break;

  1. 131: }

  1. 132: case OLED_CMD_CLEAR_ALL:

  1. 133: {

  1. 134: OLEDClear();

  1. 135: break;

  1. 136: }

  1. 137: case OLED_CMD_CLEAR_PAGE:

  1. 138: {

  1. 139: page = arg;

  1. 140: OLEDClearPage(page);

  1. 141: break;

  1. 142: }

  1. 143: case OLED_CMD_SET_POS:

  1. 144: {

  1. 145: page = arg & 0xff;

  1. 146: col = (arg >> 8) & 0xff;

  1. 147: OLEDSetPos(page, col);

  1. 148: break;

  1. 149: }

  1. 150: }

  1. 151: return 0;

  1. 152: }

  1. 153: 

  1. 154: static ssize_t oled_write(struct file *file,

  1. 155: const char __user *buf,

  1. 156: size_t count, loff_t *ppos)

  1. 157: {

  1. 158: int ret;

  1. 159: 

  1. 160: if (count > 4096)

  1. 161: return -EINVAL;

  1. 162: ret = copy_from_user(ker_buf, buf, count);

  1. 163: OLED_Set_DC(1); /* data */

  1. 164: spi_write(spi_oled_dev, ker_buf, count);

  1. 165: return 0;

  1. 166: }

  1. 167: 

  1. 168: 

  1. 169: static struct file_operations oled_ops = {

  1. 170: .owner = THIS_MODULE,

  1. 171: .unlocked_ioctl = oled_ioctl,

  1. 172: .write = oled_write,

  1. 173: };

  1. 174: 

  1. 175: static int spi_oled_probe(struct spi_device *spi)

  1. 176: {

  1. 177: int ret;

  1. 178: 

  1. 179: spi_oled_dev = spi;

  1. 180: spi_oled_dc_pin = (int)dev_get_platdata(&spi->dev);

  1. 181: 

  1. 182: ret = devm_gpio_request(&spi->dev, spi_oled_dc_pin, "OLED_DC");

  1. 183: if (ret < 0)

  1. 184: return ret;

  1. 185: gpio_direction_output(spi_oled_dc_pin, 0);

  1. 186: 

  1. 187: #ifndef CONFIG_TQ2440_USE_SPI_GPIO

  1. 188: ret = devm_gpio_request(&spi->dev, spi->chip_select, "OLED_CHIP_SELECT");

  1. 189: if (ret < 0)

  1. 190: return ret;

  1. 191: gpio_direction_output(spi->chip_select, 1);

  1. 192: #endif

  1. 193: 

  1. 194: ker_buf = kmalloc(4096, GFP_KERNEL);

  1. 195: 

  1. 196: /* 注册一个 file_operations */

  1. 197: major = register_chrdev(0, "oled", &oled_ops);

  1. 198: 

  1. 199: class = class_create(THIS_MODULE, "oled");

  1. 200: 

  1. 201: /* 为了让mdev根据这些信息来创建设备节点 */

  1. 202: device_create(class, NULL, MKDEV(major, 0), NULL, "oled"); /* /dev/oled */

  1. 203: 

  1. 204: return 0;

  1. 205: }

  1. 206: 

  1. 207: static int spi_oled_remove(struct spi_device *spi)

  1. 208: {

  1. 209: device_destroy(class, MKDEV(major, 0));

  1. 210: class_destroy(class);

  1. 211: unregister_chrdev(major, "oled");

  1. 212: 

  1. 213: kfree(ker_buf);

  1. 214: 

  1. 215: return 0;

  1. 216: }

  1. 217: 

  1. 218: static struct spi_driver spi_oled_drv = {

  1. 219: .driver = {

  1. 220: .name = "oled",

  1. 221: .owner = THIS_MODULE,

  1. 222: },

  1. 223: .probe = spi_oled_probe,

  1. 224: .remove = spi_oled_remove,

  1. 225: };

  1. 226: 

  1. 227: static int spi_oled_init(void)

  1. 228: {

  1. 229: return spi_register_driver(&spi_oled_drv);

  1. 230: }

  1. 231: 

  1. 232: static void spi_oled_exit(void)

  1. 233: {

  1. 234: spi_unregister_driver(&spi_oled_drv);

  1. 235: }

  1. 236: 

  1. 237: module_init(spi_oled_init);

  1. 238: module_exit(spi_oled_exit);

  1. 239: MODULE_DESCRIPTION("OLED SPI Driver");

  1. 240: MODULE_AUTHOR("weidongshan@qq.com,www.100ask.net");

  1. 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. 1: #include <stdlib.h>

  1. 2: #include <stdio.h>

  1. 3: #include <string.h>

  1. 4: #include <sys/types.h>

  1. 5: #include <sys/stat.h>

  1. 6: #include <fcntl.h>

  1. 7: #include <unistd.h>

  1. 8: #include <sys/ioctl.h>

  1. 9: 

  1. 10: #include "oledfont.h"

  1. 11: 

  1. 12: /* oled_test init

  1. 13: * oled_test clear

  1. 14: * oled_test clear <page>

  1. 15: * oled_test <page> <col> <string>

  1. 16: */

  1. 17: 

  1. 18: #define OLED_CMD_INIT 0x100001

  1. 19: #define OLED_CMD_CLEAR_ALL 0x100002

  1. 20: #define OLED_CMD_CLEAR_PAGE 0x100003

  1. 21: #define OLED_CMD_SET_POS 0x100004

  1. 22: 

  1. 23: 

  1. 24: 

  1. 25: /* page: 0-7

  1. 26: * col : 0-127

  1. 27: * 字符: 8x16象素

  1. 28: */

  1. 29: void OLEDPutChar(int fd, int page, int col, char c)

  1. 30: {

  1. 31: int i = 0;

  1. 32: /* 得到字模 */

  1. 33: const unsigned char *dots = oled_asc2_8x16[c - ' '];

  1. 34: 

  1. 35: /* 发给OLED */

  1. 36: //OLEDSetPos(page, col);

  1. 37: //ioctl(fd, OLED_CMD_CLEAR_PAGE, page);

  1. 38: ioctl(fd, OLED_CMD_SET_POS, page | (col << 8));

  1. 39: /* 发出8字节数据 */

  1. 40: //for (i = 0; i < 8; i++)

  1. 41: // OLEDWriteDat(dots[i]);

  1. 42: write(fd, &dots[0], 8);

  1. 43: 

  1. 44: //OLEDSetPos(page+1, col);

  1. 45: //ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);

  1. 46: ioctl(fd, OLED_CMD_SET_POS, (page+1) | (col << 8));

  1. 47: /* 发出8字节数据 */

  1. 48: //for (i = 0; i < 8; i++)

  1. 49: // OLEDWriteDat(dots[i+8]);

  1. 50: write(fd, &dots[8], 8);

  1. 51: }

  1. 52: 

  1. 53: 

  1. 54: 

  1. 55: /* page: 0-7

  1. 56: * col : 0-127

  1. 57: * 字符: 8x16象素

  1. 58: */

  1. 59: void OLEDPrint(int fd, int page, int col, char *str)

  1. 60: {

  1. 61: int i = 0;

  1. 62: 

  1. 63: ioctl(fd, OLED_CMD_CLEAR_PAGE, page);

  1. 64: ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);

  1. 65: while (str[i])

  1. 66: {

  1. 67: OLEDPutChar(fd, page, col, str[i]);

  1. 68: col += 8;

  1. 69: if (col > 127)

  1. 70: {

  1. 71: col = 0;

  1. 72: page += 2;

  1. 73: ioctl(fd, OLED_CMD_CLEAR_PAGE, page);

  1. 74: ioctl(fd, OLED_CMD_CLEAR_PAGE, page+1);

  1. 75: }

  1. 76: i++;

  1. 77: }

  1. 78: }

  1. 79: 

  1. 80: 

  1. 81: void print_usage(char *cmd)

  1. 82: {

  1. 83: printf("Usage:\n");

  1. 84: printf("%s init\n", cmd);

  1. 85: printf("%s clear\n", cmd);

  1. 86: printf("%s clear <page>\n", cmd);

  1. 87: printf("%s <page> <col> <string>\n", cmd);

  1. 88: printf("eg:\n");

  1. 89: printf("%s 2 0 100ask.taobao.com\n", cmd);

  1. 90: printf("page is 0,1,...,7\n");

  1. 91: printf("col is 0,1,...,127\n");

  1. 92: }

  1. 93: 

  1. 94: int main(int argc, char **argv)

  1. 95: {

  1. 96: int do_init = 0;

  1. 97: int do_clear = 0;

  1. 98: int do_show = 0;

  1. 99: int page = -1;

  1. 100: int col;

  1. 101: 

  1. 102: int fd;

  1. 103: 

  1. 104: if (argc == 2 && !strcmp(argv[1], "init"))

  1. 105: do_init = 1;

  1. 106: if ((argc == 2) && !strcmp(argv[1], "clear"))

  1. 107: {

  1. 108: do_clear = 1;

  1. 109: }

  1. 110: if ((argc == 3) && !strcmp(argv[1], "clear"))

  1. 111: {

  1. 112: do_clear = 1;

  1. 113: page = strtoul(argv[2], NULL, 0);

  1. 114: }

  1. 115: if (argc == 4)

  1. 116: {

  1. 117: do_show = 1;

  1. 118: page = strtoul(argv[1], NULL, 0);

  1. 119: col = strtoul(argv[2], NULL, 0);

  1. 120: }

  1. 121: 

  1. 122: if (!do_init && !do_clear && !do_show)

  1. 123: {

  1. 124: print_usage(argv[0]);

  1. 125: return -1;

  1. 126: }

  1. 127: 

  1. 128: fd = open("/dev/oled", O_RDWR);

  1. 129: if (fd < 0)

  1. 130: {

  1. 131: printf("can't open /dev/oled\n");

  1. 132: return -1;

  1. 133: }

  1. 134: 

  1. 135: if (do_init)

  1. 136: ioctl(fd, OLED_CMD_INIT);

  1. 137: else if (do_clear)

  1. 138: {

  1. 139: if (page == -1)

  1. 140: ioctl(fd, OLED_CMD_CLEAR_ALL);

  1. 141: else

  1. 142: {

  1. 143: if (page < 0 || page > 7)

  1. 144: {

  1. 145: printf("page is 0,1,...,7\n");

  1. 146: return -1;

  1. 147: }

  1. 148: ioctl(fd, OLED_CMD_CLEAR_PAGE, page);

  1. 149: }

  1. 150: }

  1. 151: else if (do_show)

  1. 152: {

  1. 153: if (page < 0 || page > 7)

  1. 154: {

  1. 155: printf("page is 0,1,...,7\n");

  1. 156: return -1;

  1. 157: }

  1. 158: if (col < 0 || col > 127)

  1. 159: {

  1. 160: printf("col is 0,1,...,127\n");

  1. 161: return -1;

  1. 162: }

  1. 163: 

  1. 164: OLEDPrint(fd, page, col, argv[3]);

  1. 165: }

  1. 166: return 0;

  1. 167: }

  1. 168: 

SPI驱动实现之软件控制器

一、驱动框架

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

二、代码

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

SPI GPIO软件控制器

这里采用的也是platform架构。

1、platform_device
  1. 1: static struct spi_gpio_platform_data s3c_spi0_gpio_info = {

  1. 2: .num_chipselect = S3C_GPIO_END,

  1. 3: .miso = S3C2410_GPE(11),

  1. 4: .mosi = S3C2410_GPE(12),

  1. 5: .sck = S3C2410_GPE(13),

  1. 6: };

  1. 7: 

  1. 8: static struct platform_device s3c_device_spi0_gpio = {

  1. 9: .name = "spi_gpio",

  1. 10: .id = 0,

  1. 11: .dev = {

  1. 12: .platform_data = (void *)&s3c_spi0_gpio_info,

  1. 13: }

  1. 14: };

  1. 15: 

  1. 16: static struct platform_device *tq2440_devices[] __initdata = {

  1. 17: ......

  1. 18: &s3c_device_spi0_gpio

  1. 19: };

  1. 20: 

  1. 21: static void __init tq2440_machine_init(void)

  1. 22: {

  1. 23: ......

  1. 24: platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));

  1. 25: ......

  1. 26: }

  1. 27: 

  1. 28: MACHINE_START(TQ2440, "TQ2440")

  1. 29: ......

  1. 30: .init_machine = tq2440_machine_init,

  1. 31: ......

  1. 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. 1: #define DRIVER_NAME "spi_gpio"

  1. 2: ......

  1. 3: 

  1. 4: static struct platform_driver spi_gpio_driver = {

  1. 5: .driver = {

  1. 6: .name = DRIVER_NAME,

  1. 7: .owner = THIS_MODULE,

  1. 8: .of_match_table = of_match_ptr(spi_gpio_dt_ids),

  1. 9: },

  1. 10: .probe = spi_gpio_probe,

  1. 11: .remove = spi_gpio_remove,

  1. 12: };

  1. 13: module_platform_driver(spi_gpio_driver);

OLED驱动

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

1、OLED板级信息
  1. 1: /* SPI OLED */

  1. 2: static struct spi_board_info tq2440_spi_board_info[] __initdata = {

  1. 3: {

  1. 4: .modalias = "oled",

  1. 5: .max_speed_hz = 10000000,

  1. 6: .bus_num = 0,

  1. 7: .mode = SPI_MODE_0,

  1. 8: .chip_select = S3C2410_GPG(1),

  1. 9: .platform_data = (const void *)S3C2410_GPF(3),

  1. 10: #ifdef CONFIG_TQ2440_USE_SPI_GPIO

  1. 11: .controller_data= (void *)S3C2410_GPG(1),

  1. 12: #endif

  1. 13: },

  1. 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

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

操作

  1. 1: [root@TQ2440 sky]# ./oled_test init

  1. 2: [root@TQ2440 sky]# ./oled_test clear

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

  1. 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. STM32—4线SPI驱动SSD1306 OLED

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

  2. spi子系统之驱动SSD1306 OLED

    spi子系统之驱动SSD1306 OLED 接触Linux之前,曾以为读源码可以更快的学习软件,于是前几个博客都是一边读源码一边添加注释,甚至精读到每一行代码,实际上效果并不理想,看过之后就忘记了.主 ...

  3. 联盛德 HLK-W806 (八): 4线SPI驱动SSD1306/SSD1315 128x64 OLED液晶屏

    目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...

  4. spi驱动框架全面分析,从master驱动到设备驱动

    内核版本:linux2.6.32.2  硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括:     1.I2C 驱动框架回顾     2.SPI 框架简单介绍     3.maste ...

  5. 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ

    基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ 0. 导语 学习进入到了下一个阶段,还是以AD9833为例,这次学习是向设备申请中断,实现触发,在未来很多场景,比如做用 ...

  6. STC8H开发(十): SPI驱动Nokia5110 LCD(PCD8544)

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  7. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

  8. 基于Mvc3,Ef,领域驱动电子商务系统的EShop开发

    分享自己从代码小工一步步走向搭架子,ING... 简单了解UnitOfWork 摘要: UnitOfWorkUnit Of Work模式,即工作单元,它是一种数据访问模式.它是用来维护一个由已经被业务 ...

  9. 详解Linux2.6内核中基于platform机制的驱动模型 (经典)

    [摘要]本文以Linux 2.6.25 内核为例,分析了基于platform总线的驱动模型.首先介绍了Platform总线的基本概念,接着介绍了platform device和platform dri ...

随机推荐

  1. ASP.NET MVC系列:Model

    1. Model任务 Model负责通过数据库.AD(Active Directory).Web Service及其他方式获取数据,以及将用户输入的数据保存到数据库.AD.Web Service等中. ...

  2. Android数据存储之SQLCipher数据库加密

    前言: 最近研究了Android Sqlite数据库(文章地址:Android数据存储之Sqlite的介绍及使用)以及ContentProvider程序间数据共享(Android探索之ContentP ...

  3. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...

  4. Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  5. Linux下的解压命令小结

    Linux下常见的压缩包格式有5种:zip tar.gz tar.bz2 tar.xz tar.Z 其中tar是种打包格式,gz和bz2等后缀才是指代压缩方式:gzip和bzip2 filename. ...

  6. Node学习笔记(四):gulp+express+io.socket部署angularJs2(填坑篇)

    这篇就先暂停下上篇博客--你画我猜的进度,因为在做这个游戏的时候,想采用最新的ng2技术,奈何坑是一片又一片,这边就先介绍下环境部署和填坑史 既然要用ng2,首先要拿到资源,我这边用的是angular ...

  7. 【Asp.Net Core】二、添加控制器和视图

    控制器Controller 在添加控制器前,我们先看下它为我们自动生成的一些Controller,我们看下AccountController.cs 来看下登录验证方法Login async这个应该是异 ...

  8. 【C#公共帮助类】 Log4net 帮助类

    首先,我们要在Common类库中引用log4net.dll ExtLogImpl.cs using System; using System.Collections.Generic; using Sy ...

  9. Is-A,Has-A,Use-A(转载)

    原文地址:http://blog.csdn.net/loveyou128144/article/details/4749576 而Is-A,Has-A,Use-A则是用来描述类与类之间关系的.简单的说 ...

  10. Struts2入门(三)——数据类型转换

    一.前言 笔者一直觉得,学习一个知识点,你首先要明白,这东西是什么?有什么用?这样你才能了解.好了,不说废话. 1.1.类型转换为何存在?什么是类型转换? 在MVC框架中,都是属于表示层解决方案,都需 ...