【摘要】 本篇文章就介绍如何在linux系统下编写w25q64芯片的驱动,完成数据存储,w25q64支持标准spi总线,当前驱动程序底层的代码写了两种方式,一种是采用内核提供的spi子系统框架,一种直接采用软件模拟spi时序的方式驱动,具体代码在第3章贴出来了。
1. w25qxx介绍 w25q64是一颗spi接口的flash存储芯片,是华邦w25qxx系列里的一个具体型号,这个系列里包含了w25q16,w25q32,w25q64,w5q128等等。编程代码逻辑都差不多,主要是容量的区别。
本篇文章就介绍如何在linux系统下编写w25q64芯片的驱动,完成数据存储,w25q64支持标准spi总线,当前驱动程序底层的代码写了两种方式,一种是采用内核提供的spi子系统框架,一种直接采用软件模拟spi时序的方式驱动,具体代码在第3章贴出来了。
下面是来至w25qxx中文手册的介绍
w25q64 (64m-bit), w25q16(16m-bit)和 w25q32(32m-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行 flash 存储器。 25q 系列比普通的串行 flash 存储器更灵活,性能更优越。基于双倍/四倍的 spi,它们能够可以立即完成提供数据给 ram, 包括存储声音、文本和数据。芯片支持的工作电压 2.7v 到 3.6v,正常工作时电流小于 5ma,掉电时低于 1ua。所有芯片提供标准的封装。
w25q64/16/32 由每页 256 字节组成。 每页的 256 字节用一次页编程指令即可完成。 每次可以擦除 16 页(1 个扇区)、 128 页(32kb 块)、 256 页(64kb 块)和全片擦除。w25q64 的内存空间结构: 一页 256 字节, 4k(4096 字节)为一个扇区, 16 个扇区为 1 块, 容量为 8m 字节,共有 128 个块,2048 个扇区。w25q64/16/32 支持标准串行外围接口(spi),和高速的双倍/四倍输出,双倍/四倍用的引脚:串行时钟、片选端、串行数据 i/o0(di)、 i/o1(do)、 i/o2(wp)和 i/o3(hold)。 spi 最高支持 80mhz,当用快读双倍/四倍指令时,相当于双倍输出时最高速率 160mhz,四倍输出时最高速率 320mhz。这个传输速率比得上 8 位和 16 位的并行 flash 存储器。hold 引脚和写保护引脚可编程写保护。此外,芯片支持 jedec 标准,具有唯一的 64 位识别序列号。
●spi 串行存储器系列
-w25q64:64m 位/8m 字节
-w25q16:16m 位/2m 字节
-w25q32:32m 位/4m 字节
-每256字节可编程页
2. 硬件环境 当前测试使用的开发板采用友善之臂的tiny4412开发板,芯片是三星的exynos-4412,最高主频1.5ghz。
开发板引出了spi的io口,这里使用的w25q64是外置的模块,使用杜邦线与开发板的io口连接。
开发板上引出的io口都是5v和1.8v,为了方便供电,采用了一个usb转ttl模块提供电源,测试驱动。
w25q64模块接在开发板的spi0接口上面的。
linux内核自带有spi子系统的设备端示例代码:
linux 内核自带的 spi 驱动注册示例代码: \drivers\spi\spidev.clinux 内核自带的 spi app 注册示例代码: \documentation\spi 如果要使用内核自带spi驱动,可以在内核编译时配置一下。
root# make menuconfigdevice drivers ---> [*] spi support ---> samsung s3c64xx series type spi [*] samsung s3c64xx channel 0 support. tiny4412自带内核里的spi设备端结构:
spi0的具体gpio口位置:
3. 案例代码 3.1 模拟spi时序-编写驱动 下面是w25q64的驱动测试代码,没有注册字符设备框架,只是在驱动的入口里测试时序是否ok,打印了id,读写了数据进行测试。
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*--------------------------------w25q64相关操作代码---------------------------------------------*//*定义指针,用于接收虚拟地址*/volatile unsigned int *w25q64_gpbcon;volatile unsigned int *w25q64_gpbdat;/*函数功能:w25q64初始化tiny4412硬件连接: do--miso :gpb_2 //输入模式 di--mosi :gpb_3 //输出模式 clk-sclk :gpb_0 //时钟 cs--cs :gpb_1 //片选*/void w25q64_init(void){ /*1. 初始化gpio*/ /*映射物理地址*/ w25q64_gpbcon=ioremap(0x11400040,4); w25q64_gpbdat=ioremap(0x11400044,4); *w25q64_gpbcon &= ~(0xf << 0 * 4);*w25q64_gpbcon |= (0x1 << 0 * 4); *w25q64_gpbcon &= ~(0xf << 1 * 4);*w25q64_gpbcon |= (0x1 << 1 * 4); *w25q64_gpbcon &= ~(0xf << 2 * 4); *w25q64_gpbcon &= ~(0xf << 3 * 4);*w25q64_gpbcon |= (0x1 << 3 * 4); /*2. 上拉gpio口*/ //*w25q64_gpbdat &= ~(1 << 4);//输出0 *w25q64_gpbdat |= (1 << 0); //输出1 *w25q64_gpbdat |= (1 << 1); //输出1 *w25q64_gpbdat |= (1 << 3); //输出1}/*函数功能:spi时序读写一个字节说 明:spi底层时序,程序的移植接口*/u8 w25q64_spi_readwriteonebyte(u8 data_tx){ u8 data_rx=0; u8 i; for(i=0;i<8;i++) { *w25q64_gpbdat &= ~(1 << 0);//输出0 if(data_tx&0x80)*w25q64_gpbdat |= (1 << 3); //输出1 else *w25q64_gpbdat &= ~(1 << 3);//输出0 data_tx<<=1; //继续发送下一个数据 *w25q64_gpbdat |= (1 << 0); //输出1 data_rx<<=1; if((*w25q64_gpbdat & (1 << 2)))data_rx|=0x01; } return data_rx;}/*函数功能:写使能*/void w25q64_writeenabled(void){ *w25q64_gpbdat &= ~(1 << 1); //选中w25q64 w25q64_spi_readwriteonebyte(0x06); *w25q64_gpbdat |= (1 << 1); //取消选中w25q64}/*函数功能:读状态*/void w25q64_getbusystat(void){ unsigned char stat=1; while(stat&0x01) //判断状态最低位 { *w25q64_gpbdat &= ~(1 << 1); w25q64_spi_readwriteonebyte(0x05); stat=w25q64_spi_readwriteonebyte(0xff); //读取状态寄存器的值 *w25q64_gpbdat |= (1 << 1); }}/*函数功能:读取设备id和制造商idw25q64: ef16w25qq128:ef17*/unsigned short w25q64_readdeviceid(void){ unsigned short id; *w25q64_gpbdat &= ~(1 << 1); w25q64_spi_readwriteonebyte(0x90); w25q64_spi_readwriteonebyte(0x0); w25q64_spi_readwriteonebyte(0x0); w25q64_spi_readwriteonebyte(0x0); id=w25q64_spi_readwriteonebyte(0xff)<<8; //制造商id id|=w25q64_spi_readwriteonebyte(0xff); //设备id *w25q64_gpbdat |= (1 << 1); return id;}/*函数功能:全片擦除*/void w25q64_clearall(void){ w25q64_writeenabled(); //写使能 w25q64_getbusystat(); //检测状态寄存器 *w25q64_gpbdat &= ~(1 << 1); w25q64_spi_readwriteonebyte(0xc7); *w25q64_gpbdat |= (1 << 1); w25q64_getbusystat(); //检测状态寄存器}/*函数功能:页编程参 数: unsigned int addr:写入的地址 void *p:将要写入的数据 unsigned int len:写入的长度说 明:每次最多只能写入256字节*/void w25q64_pagewrite(unsigned int addr,void*p,unsigned int len){ unsigned short i; unsigned char *buff=p; w25q64_writeenabled(); //写使能 *w25q64_gpbdat &= ~(1 16); w25q64_spi_readwriteonebyte(addr>>8); w25q64_spi_readwriteonebyte((unsigned char)addr); for(i=0;i>16); w25q64_spi_readwriteonebyte(addr>>8); w25q64_spi_readwriteonebyte((unsigned char)addr); *w25q64_gpbdat |= (1 << 1); w25q64_getbusystat(); //检测状态寄存器}/*函数功能:数据读取参 数:*/void w25q64_readdata(unsigned int addr,void *p,unsigned int len){ unsigned int i=0; unsigned char *buff=p; *w25q64_gpbdat &= ~(1 16); w25q64_spi_readwriteonebyte(addr>>8); w25q64_spi_readwriteonebyte((unsigned char)addr); for(i=0;i256)page_remain=256; else page_remain=len; } }/*函数功能:在任意地址写入任意数据,对扇区进行校验参 数: unsigned int addr:写入数据的地址 void *p :写入的数据 unsigned int len :写入数据的长度说明:一个扇区的空间4096字节*/unsigned char w25q64_buff[1024*4]; //用来检验一个扇区的数据是否需要擦除void w25q64_writedata(unsigned int addr,void *p,unsigned int len){ unsigned int sector_len=4096-addr%4096; //剩余空间大小 unsigned char *buff=p; unsigned int i=0; if(len4096) { sector_len=4096; } else { sector_len=len; } }}static int __init w25q64_init(void){ /*1. 初始化gpio口*/ w25q64_init(); /*2. 打印厂商芯片id*/ unsigned short id=w25q64_readdeviceid(); printk(id=0x%x\n,id); /*3. 写入数据*/ char buff[]=w25q64-test-123456789abcdefg; w25q64_writedata(100,buff,strlen(buff)); printk(write-data:%s\n,buff); /*4. 读出数据*/ char buff_rx[100]; w25q64_readdata(100,buff_rx,strlen(buff)); printk(read-data:%s\n,buff_rx); return 0;}static void __exit w25q64_exit(void){ /*释放虚拟地址*/ iounmap(w25q64_gpbcon); iounmap(w25q64_gpbdat); printk(w25q64 driver exit ok!\n);}module_exit(w25q64_exit);module_init(w25q64_init);module_license(gpl);)>;i++)>;i++)> 3.2 采用spi子系统框架-编写驱动 下面代码使用spi子系统框架编写的驱动测试代码,注册了字符设备框架,但是只是做了简单的测试,目的只是测试w25q64是否可以正常驱动,能读写存储。
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*杂项字符设备头文件*/#include /*文件操作集合*/#include /*--------------------------------w25q64相关操作代码---------------------------------------------*/struct spi_device *w25q64_spi_device;/*函数功能:w25q64初始化tiny4412硬件连接: do--miso :gpb_2 //输入模式 di--mosi :gpb_3 //输出模式 clk-sclk :gpb_0 //时钟 cs--cs :gpb_1 //片选*//*函数功能:读取设备id和制造商idw25q64: ef16w25qq128:ef17参数:0x90表示读取id号的指令*/unsigned short w25q64_readdeviceid(void){ /*使用硬件spi同步读写时序*/ char tx_buf[6]={0x90,0x0,0x0,0x0,0xff,0xff}; char rx_buf[6]; struct spi_message m; struct spi_transfer t= { .tx_buf=tx_buf, .rx_buf=rx_buf, .len=6, .delay_usecs=0, .speed_hz=1000000, .bits_per_word=8 }; spi_message_init(&m); spi_message_add_tail(&t,&m); spi_sync(w25q64_spi_device,&m); return rx_buf[4]16; //以下是地址指令 tx_buf[2]=addr>>8; tx_buf[3]=addr; spi_write(w25q64_spi_device,tx_buf,4); spi_read(w25q64_spi_device,p,len);}/*函数功能:写使能*/void w25q64_writeenabled(void){ /*使用硬件spi同步读写时序*/ char tx_buf[1]={0x06}; struct spi_message m; struct spi_transfer t= { .tx_buf=tx_buf, .len=1, .delay_usecs=0, .speed_hz=1000000, .bits_per_word=8 }; spi_message_init(&m); spi_message_add_tail(&t,&m); spi_sync(w25q64_spi_device,&m);}/*函数功能:读状态*/void w25q64_getbusystat(void){ unsigned char stat=1; /*使用硬件spi同步读写时序*/ char tx_buf[2]={0x05,0xff}; char rx_buf[2]; while(stat&0x01) //判断状态最低位 { struct spi_message m; struct spi_transfer t= { .tx_buf=tx_buf, .rx_buf=rx_buf, .len=2, .delay_usecs=0, .speed_hz=1000000, .bits_per_word=8 }; spi_message_init(&m); spi_message_add_tail(&t,&m); spi_sync(w25q64_spi_device,&m); stat=rx_buf[1]; //得到状态寄存器 }}/*函数功能:扇区擦除参 数: unsigned int addr:扇区的地址说 明:一个扇区是4096字节,擦除一个扇区时间至少150ms*/void w25q64_clearsector(unsigned int addr){ w25q64_writeenabled(); //写使能 w25q64_getbusystat(); //检测状态寄存器 /*使用硬件spi同步读写时序*/ unsigned char tx_buf[4]; tx_buf[0]=0x20; tx_buf[1]=addr>>16; tx_buf[2]=addr>>8; tx_buf[3]=addr; char rx_buf[4]; struct spi_message m; struct spi_transfer t= { .tx_buf=tx_buf, .rx_buf=rx_buf, .len=4, .delay_usecs=0, .speed_hz=1000000, .bits_per_word=8 }; spi_message_init(&m); spi_message_add_tail(&t,&m); spi_sync(w25q64_spi_device,&m); w25q64_getbusystat(); //检测状态寄存器}/*函数功能:页编程参 数: unsigned int addr:写入的地址 void *p:将要写入的数据 unsigned int len:写入的长度说 明:每次最多只能写入256字节*/void w25q64_pagewrite(unsigned int addr,void*p,unsigned int len){ unsigned short i; unsigned char *buff=p; w25q64_writeenabled(); //写使能 /*使用硬件spi同步读写时序*/ unsigned char tx_buf[4]; tx_buf[0]=0x02; //页写指令 tx_buf[1]=(addr>>16)&0xff; //以下是地址指令 tx_buf[2]=(addr>>8)&0xff; tx_buf[3]=(addr&0xff); //写数据 spi_write(w25q64_spi_device,tx_buf,4); //写数据 spi_write(w25q64_spi_device,p,len); w25q64_getbusystat(); //检测状态寄存器}/*函数功能:在任意地址写入任意数据,不进行校验参 数: unsigned int addr:写入数据的地址 void *p :写入的数据 unsigned int len :写入数据的长度*/void w25q64_writedataoncheck(unsigned int addr,void *p,unsigned int len){ unsigned char *buff=p; unsigned short page_remain=256-addr%256; //当前地址开始一页剩下的空间 unsigned short remain_len; //剩余未写入的长度 if(len256)page_remain=256; else page_remain=len; } }/*函数功能:在任意地址写入任意数据,对扇区进行校验参 数: unsigned int addr:写入数据的地址 void *p :写入的数据 unsigned int len :写入数据的长度说明:一个扇区的空间4096字节*/static unsigned char w25q64_buff[1024*4]; //用来检验一个扇区的数据是否需要擦除void w25q64_writedata(unsigned int addr,void *p,unsigned int len){ unsigned int sector_len=4096-addr%4096; //剩余空间大小 unsigned char *buff=p; unsigned int i=0; if(len4096) { sector_len=4096; } else { sector_len=len; } }}/*杂项字符设备注册示例----->led*/static int tiny4412_open(struct inode *my_inode, struct file *my_file){ return 0;}static int tiny4412_release(struct inode *my_inode, struct file *my_file){ return 0;}static ssize_t tiny4412_read(struct file *my_file, char __user *buf, size_t len, loff_t *loff){ /*2. 打印厂商芯片id*/ unsigned short id=w25q64_readdeviceid(); printk(-id=0x%x\n,id); /*3. 写入数据*/ char buff[100]=打印厂商芯片id打印厂商芯片id; w25q64_writedata(0,buff,100); /*4. 读出数据*/ char buff_rx[100]; w25q64_readdata(0,buff_rx,100); printk(read=%s\n,buff_rx); return 0;}static ssize_t tiny4412_write(struct file *my_file, const char __user *buf, size_t len, loff_t *loff){ return 0;}/*文件操作集合*/static struct file_operations tiny4412_fops={ .open=tiny4412_open, .read=tiny4412_read, .write=tiny4412_write, .release=tiny4412_release};/*核心结构体*/static struct miscdevice tiny4412_misc={ .minor=misc_dynamic_minor, /*自动分配次设备号*/ .name=tiny4412_w25q64, /*设备文件,指定/dev/生成的文件名称*/ .fops=&tiny4412_fops};static int __devinit w25q64_probe(struct spi_device *spi){ /*配置spi模式*/ spi->bits_per_word = 8; spi->mode = spi_mode_0; spi->max_speed_hz=1*1000000; //1mhz if(spi_setup(spi)dev.id); /*杂项设备注册*/ misc_register(&tiny4412_misc); return 0;}static int __devexit w25q64_remove(struct spi_device *spi){ /*杂项设备注销*/ misc_deregister(&tiny4412_misc); return 0;}static struct spi_driver w25q64_spi_driver = { .driver = { .name = spidev, .owner =this_module, }, .probe =w25q64_probe, .remove = __devexit_p(w25q64_remove),};/*-------------------------------------------------------------------------*/static int __init w25q64_init(void){ spi_register_driver(&w25q64_spi_driver); printk(w25q64 driver install ok!\n); return 0;}static void __exit w25q64_exit(void){ spi_unregister_driver(&w25q64_spi_driver); printk(w25q64 driver exit ok!\n);}module_exit(w25q64_exit);module_init(w25q64_init);module_license(gpl);)>)>
全力导入国产设备及原材料 昆山华天科技扩建项目预计年内投产
磁致伸缩液位计的常见故障与问题
空调市场迎来了史无前例的大洗牌 奥克斯成为线上空调市场的销售冠军
比特大陆与Nvidia竞争AMD GPU生意
银胶剥离失效分析
Linux驱动开发-编写W25Q64(Flash)驱动
人工智能技术在各个行业和领域的落地应用创造很好的政策环境
欲振乏力iPhone第3季度拉货打9折
蓝牙定位都听说过,那么蓝牙定位信标都知道吗
中国移动表示建设5G同时4G服务不降
可编程逻辑控制器是什么_可编程逻辑控制器原理
汇编语言详解
小米6首发高通835+曲面设计,华为p10恐不是对手
SG1700微型光谱仪的特点有哪些
多链管理该怎样去实现
工业级一体机将助力提高港口的集散利用率
异步电动机滚动轴承好坏的判定方法
铁塔人全力奋战保障通信畅通
电视机的谐波电流和抑制技术
关于手机放大器在安装过程中的一些注意事项