基于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個波形,還算正常。
因此,基本驗證了我的看法。
?
完。
總結
以上是生活随笔為你收集整理的基于TQ2440的SPI驱动学习(OLED)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Package name 'com.ce
- 下一篇: 什么是网盾高防CDN,它的优势有哪些?