本帖最后由 FSL_TICS_ZJJ 于 2014-9-11 16:05 编辑
首先声明:以下都是我个人在MQX学习、使用过程中的一些见解,当然,里面可能会有一些说的不是很明白,我希望我扔出来的这块砖头,能引来他山之玉
在参考了MQX自带的内部Flash驱动中,我看到Flash其实可以分区进行不同区域来做驱动,比如我自己的这个驱动,就是将一个8M的SPI Flash分为两块,前2M是一块,后面6M是一块,而每个块我们都需要不同的参数来进行标记,我做了一个Flash Block的结构体如下:
- typedef struct spi_flash_block
- {
- char *block_name; //分块名称
- uint32_t block_start_address; //块起始地址
- uint32_t block_end_address; //块结束地址
- uint32_t sector_address; //当前操作扇区地址
- uint32_t sector_size; //驱动大小
- uint8_t *sector_buff; //CACHE指针
- bool sector_change_flag; //写标志
- }SPI_FLASH_BLOCK, *SPI_FLASH_BLOCK_PTR;
复制代码
然后定义两个区,我将一个命名为系统区,另一个命名为用户区:
- static SPI_FLASH_BLOCK spi_flash_block_descr[] = {
- {"system", 0x00000000, 0x001FFFFF, 0x00000000, SPI_MEMORY_SECTOR_SIZE, NULL, FALSE},
- {"user", 0x00200000, 0x007FFFFF, 0x00200000, SPI_MEMORY_SECTOR_SIZE, NULL, FALSE},
- {0, 0, 0, 0, 0, 0 }
- };
复制代码
当然还有一个比较重要的参数,就是SPI口了,这个参数可以用来改变SPI口,因为我的Flash挂接在SPI0上,所以如下定义:
- #if BSP_SPI_MEMORY_CHANNEL == 0
- #define SPI_CHANNEL SPI0_BASE_PTR
- #elif BSP_SPI_MEMORY_CHANNEL == 1
- #define SPI_CHANNEL SPI1_BASE_PTR
- #elif BSP_SPI_MEMORY_CHANNEL == 2
- #define SPI_CHANNEL SPI2_BASE_PTR
- #else
- #error Unsupported SPI channel number. Please check settings of BSP_SPI_MEMORY_CHANNEL in BSP.
- #endif
复制代码
而为了更方便将来对驱动的修改,我添加了一个公共的数据结构,虽然目前没有其它参数,但如果以后要使用,可以在此结构体里面添加:
- typedef struct spi_flash_struct
- {
- SPI_MemMapPtr SPIx;
- }SPI_FLASH_STRUCT, *SPI_FLASH_STRUCT_PTR;
复制代码
好了,前期的准备工作我们做好了,接下来我们要做驱动了,其实OS的驱动不外乎就是几个函数,我要完成以下几个函数的编写:
- _mqx_uint _spi_flash_install(char *identifier, SPI_FLASH_STRUCT const *init_ptr);
- _mqx_int _spi_flash_init(const void *init_data_ptr, void **io_info_ptr_ptr);
- _mqx_int _spi_flash_open(MQX_FILE_PTR fd_ptr, char *open_name_ptr, char *flags);
- _mqx_int _spi_flash_close(MQX_FILE_PTR fd_ptr);
- _mqx_int _spi_flash_read(MQX_FILE_PTR fd_ptr, char *data_ptr, _mqx_int num);
- _mqx_int _spi_flash_write(MQX_FILE_PTR fd_ptr, char *data_ptr, _mqx_int num);
- _mqx_int _spi_flash_ioctl(MQX_FILE_PTR fd_ptr, _mqx_uint cmd, void *param_ptr);
- _mqx_int _spi_flash_uninstall(IO_DEVICE_STRUCT_PTR io_dev_ptr);
复制代码
第一个当然是install函数,我写的函数如下:
- _mqx_uint _spi_flash_install ( char *identifier, SPI_FLASH_STRUCT const *init_ptr)
- {
- SPI_FLASH_STRUCT_PTR spi_flash_data = _mem_alloc_system(sizeof(SPI_FLASH_STRUCT)); //定义了公共的变量
- [color=Red] spi_flash_init(spi_flash_data, NULL); //初始化端口以及数据
- [/color] return (_io_dev_install_ext( //进行驱动注册
- identifier,
- _spi_flash_open,
- _spi_flash_close,
- _spi_flash_read,
- _spi_flash_write,
- _spi_flash_ioctl,
- _spi_flash_uninstall,
- spi_flash_data));
- }
复制代码
这里我要注明一下,其实很多驱动都可以在驱动OPEN的时候再进行初始化,但是考虑到我初始里面有个公共的变量SPI口的选择,所以我将初始化功能放在了此处。此函数要在使用SPI Flash之前调用,用来注册驱动。使用方法:
- _spi_flash_install("spi_flash:", NULL);
复制代码
第二个函数是相对install来说的,就是uninstall函数:
- _mqx_int _spi_flash_uninstall( IO_DEVICE_STRUCT_PTR io_dev_ptr)
- {
- SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)io_dev_ptr->DRIVER_INIT_PTR;
- _mem_free(user_data); //因为在install函数中申请了内存,所以在此要将内存释放掉,不然会造成内存泄漏
- io_dev_ptr->DRIVER_INIT_PTR = NULL; //将驱动数据清空,防止野指针的出现
- return MQX_OK;
- }
复制代码
第三个函数当然就是初始化了:
- _mqx_int _spi_flash_init( const void *init_data_ptr, void **io_info_ptr_ptr )
- {
- SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)init_data_ptr;
- user_data->SPIx = SPI_CHANNEL;
- hal_spi0_init(user_data->SPIx);
-
- return SPI_FLASH_OK;
- }
复制代码
初始化函数就比较简单,我只是选择了SPI接口,然后配置硬件SPI口。
第四个函数是OPEN:
- _mqx_int _spi_flash_open ( MQX_FILE_PTR fd_ptr, char *open_name_ptr, char *flags)
- {
- SPI_FLASH_BLOCK_PTR user_block = NULL;
- int i = 0;
- for(i = 0; spi_flash_block_descr[i].block_name != 0; i++)
- {
- [color=Red]if(0 != strstr(open_name_ptr, spi_flash_block_descr[i].block_name)) //用块名称对块进行查找
- {
- user_block = (SPI_FLASH_BLOCK_PTR)_mem_alloc_system(sizeof(SPI_FLASH_BLOCK)); //找到块之后申请内存作为当前打开块的初始化数据
- memcpy(user_block, &(spi_flash_block_descr[i]), sizeof(SPI_FLASH_BLOCK)); //对块数据进行初始化
- fd_ptr->DEV_DATA_PTR = user_block; //将块指针赋值给当前打开的驱动数据指针
- break;
- }
- [/color] }
- if(user_block == NULL)
- {
- return IO_ERROR;
- }
- user_block->sector_buff = _mem_alloc_system(user_block->sector_size); //给CACHE申请内存
- fd_ptr->SIZE = user_block->block_end_address - user_block->block_start_address + 1; //初始化当前驱动块的尺寸
- fd_ptr->LOCATION = user_block->block_start_address; //初始化LOCATION
- read_sector(fd_ptr); //初始化当前CACHE
- return MQX_OK;
- }
复制代码
这个函数我也是参考原MQX中Flash驱动中的OPEN函数,这块有点说道,因为Flash分块了,所以在OPEN的时候需要做一件事情,驱动我目前要OPEN的是哪一个块,使用如下:
- fopen("spi_flash:system", NULL);
复制代码
或者
- fopen("spi_flash:user, NULL);
复制代码
第五个函数close,这个函数相对比较简单,就是释放Open时申请的所有资源,然后将指针清空:
- _mqx_int _spi_flash_close ( MQX_FILE_PTR fd_ptr )
- {
- SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
- fflush(fd_ptr);
- [color=Red] _mem_free(user_block->sector_buff);
- _mem_free(user_block);
- fd_ptr->DEV_DATA_PTR = NULL;
- [/color] return MQX_OK;
- }
复制代码
第六个函数read,这个函数其实也没什么,直接从Flash中读数据,但是这里需要注意的是要改变CACHE的切换:
- _mqx_int _spi_flash_read ( MQX_FILE_PTR fd_ptr,char *data_ptr, _mqx_int num )
- {
- SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
- uint32_t sector_start = user_block->sector_address;
- uint32_t sector_next_start = user_block->sector_address + user_block->sector_size;
- uint8_t *buff_ptr;
- _mqx_int location = fd_ptr->LOCATION;
- _mqx_int over_num = num;
- _mqx_int read_num_one_time;
- do
- {
- if((location < sector_start) || (location >= sector_next_start))
- {
- [color=Red]uint32_t error = switching_sector(fd_ptr, location); //如果地址范围超出当前CACHE,要进行切换
- [/color] if(error != SPI_FLASH_OK)
- {
- return error;
- }
- sector_start = user_block->sector_address;
- sector_next_start = user_block->sector_address + user_block->sector_size;
- }
- buff_ptr = user_block->sector_buff + (location & (user_block->sector_size - 1));
- read_num_one_time = ((location + over_num) >= sector_next_start) ? sector_next_start - location : over_num;
- memcpy(data_ptr, buff_ptr, read_num_one_time);
- data_ptr += read_num_one_time;
- location += read_num_one_time;
- over_num -= read_num_one_time;
- }while(over_num > 0);
- fd_ptr->LOCATION += num;
- return num;
- }
复制代码
第七个函数write,写函数跟读函数一样,要注意CACHE切换,另外,写函数还要注意一定要将写标志置起来,此标志是为了CACHE切换时知道Flash是否需要改变:
- _mqx_int _spi_flash_write (MQX_FILE_PTR fd_ptr,char *data_ptr, _mqx_int num )
- {
- SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
- uint32_t sector_start = user_block->sector_address;
- uint32_t sector_next_start = user_block->sector_address + user_block->sector_size;
- uint8_t *buff_ptr;
- _mqx_int location = fd_ptr->LOCATION;
- _mqx_int write_num_one_time;
- _mqx_int over_num = num;
- do
- {
- if((location < sector_start) || (location >= sector_next_start))
- {
- [color=Red]uint32_t error = switching_sector(fd_ptr, location);[/color]
- if(error != SPI_FLASH_OK)
- {
- return error;
- }
- sector_start = user_block->sector_address;
- sector_next_start = user_block->sector_address + user_block->sector_size;
- }
- buff_ptr = user_block->sector_buff + (location & (user_block->sector_size - 1));
- write_num_one_time = ((location + over_num) >= sector_next_start) ? sector_next_start - location : over_num;
- memcpy(buff_ptr, data_ptr, write_num_one_time);
- [color=Red] user_block->sector_change_flag = TRUE;
- [/color] data_ptr += write_num_one_time;
- location += write_num_one_time;
- over_num -= write_num_one_time;
- if(location >= sector_next_start)
- {
- write_sector(fd_ptr);
- }
- }while(over_num > 0);
- fd_ptr->LOCATION += num;
- return num;
- }
复制代码
最后一个IOCTRL函数,在驱动中,此函数的重要性我想我就不用说了,下面是我的函数,因为只用到了一部分命令,所以函数不算太大,MQX中Flash的IOCTRL函数那才叫个大呢:
- _mqx_int _spi_flash_ioctl (MQX_FILE_PTR fd_ptr, _mqx_uint cmd,void *param_ptr )
- {
- SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)fd_ptr->DEV_PTR->DRIVER_INIT_PTR;
- SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
- _mqx_int result = MQX_OK;
- _mqx_uint_ptr uparam_ptr;
- switch(cmd)
- {
- [color=Red]case IO_IOCTL_FLUSH_OUTPUT: //Flush功能,这个功能很有用,我一开始没有加此命令,结果文件写完后下次开机就不见了,大家知道原因吧,哈哈
- write_sector(fd_ptr);
- break;[/color]
- case FLASH_IOCTL_ERASE_CHIP:
- hal_spi_dev_flash_erase_dev(user_data->SPIx);
- hal_spi_dev_flash_read(user_data->SPIx, user_block->sector_address, user_block->sector_buff, user_block->sector_size);
- break;
- case IO_IOCTL_DEVICE_IDENTIFY:
- /*
- ** This is to let the upper layer know what kind of device this is.
- ** It's a physical flash device, capable of being erased, read, seeked,
- ** and written. Flash devices are not interrupt driven, so
- ** IO_DEV_ATTR_POLL is included.
- */
- uparam_ptr = (_mqx_uint_ptr)param_ptr;
- uparam_ptr[0] = IO_DEV_TYPE_PHYS_FLASHX;
- uparam_ptr[1] = IO_DEV_TYPE_LOGICAL_MFS;
- uparam_ptr[2] = IO_DEV_ATTR_ERASE | IO_DEV_ATTR_POLL
- | IO_DEV_ATTR_READ | IO_DEV_ATTR_SEEK |
- IO_DEV_ATTR_WRITE;
- break;
-
- case IO_IOCTL_GET_NUM_SECTORS:
- *(uint32_t *)param_ptr = fd_ptr->SIZE / SPI_MEMORY_SECTOR_SIZE;
- break;
- case IO_IOCTL_GET_BLOCK_SIZE:
- case FLASH_IOCTL_GET_SECTOR_SIZE:
- /* returns the fixed size for MFS sector size */
- *(uint32_t *)param_ptr = user_block->sector_size;
- break;
- [color=Red] case IO_IOCTL_SEEK: //这个命令一定要添加,因为我们Flash分块了,所以起始地址不一样,如果不加此功能,那么它起始地址都是默认从0开始,那第二个 //块肯定会读写错误[/color]
- fd_ptr->LOCATION += user_block->block_start_address;
- break;
- default:
- result = IO_ERROR_INVALID_IOCTL_CMD;
- }
- return result;
- }
复制代码
最后的最后,还有一个函数我必须得说明一下,切换扇区,需要注意的是一开始定义的写标志在这里起了作用,看代码:
- uint32_t switching_sector(MQX_FILE_PTR fd_ptr, _mqx_int address)
- {
- SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
- uint32_t error_code;
- //取旧扇区的起始地址
- _mqx_int sector_start_address = address & (~(user_block->sector_size - 1));
- //写Flash
- [color=Red] if(user_block->sector_change_flag)
- error_code = write_sector(fd_ptr);[/color]
- if(error_code != SPI_FLASH_OK)
- {
- return error_code;
- }
- //更新扇区的起始地址参数
- user_block->sector_address = sector_start_address;
- //重新填充CACHE
- error_code = read_sector(fd_ptr);
- if(error_code != SPI_FLASH_OK)
- {
- return error_code;
- }
- return SPI_FLASH_OK;
- }
复制代码
OK了,其它的一些再底层的函数我就没有必要一一说明了!尾部再将源文件传上来:
MQX_SPI_Flash_device.rar
(6.62 KB, 下载次数: 26)
2014-8-31 16:59 上传
点击文件名下载附件
砖来了,玉在哪?
让我先 理解理解~~
你要分两个对像来处理的话,为了移植方便,比如下次你用gsm模块调用的是SPI模块的同样的一个函数,我一般的做法是在GSM模块中添加一个函数指针,然后在初始化的时候从UART模块或SPI模块中把函数地址拿出来初始化此指针,而此指针就是GSM模块中的变量,需要注意的是在用此函数的之前要判断他是否已经被赋值,不然很容易出现野指针,呵呵!
一周热门 更多>