基于TQ2440的SPI驱动学习(OLED)

王朝学院·作者佚名  2016-05-26
窄屏简体版  字體: 小  |  中  |  大  |  超大  

基于TQ2440的SPI驱动学习(OLED)平台简介开发板: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个波形,还算正常。

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

完。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航