【分享】配合前几天发的SPI Flash的驱动,分析MQX驱动架构!

2020-02-20 20:02发布

本帖最后由 FSL_TICS_ZJJ 于 2014-9-11 16:05 编辑

首先声明:以下都是我个人在MQX学习、使用过程中的一些见解,当然,里面可能会有一些说的不是很明白,我希望我扔出来的这块砖头,能引来他山之玉

在参考了MQX自带的内部Flash驱动中,我看到Flash其实可以分区进行不同区域来做驱动,比如我自己的这个驱动,就是将一个8M的SPI Flash分为两块,前2M是一块,后面6M是一块,而每个块我们都需要不同的参数来进行标记,我做了一个Flash Block的结构体如下:
  1. typedef struct spi_flash_block
  2. {
  3.     char *block_name;                                //分块名称
  4.     uint32_t block_start_address;                //块起始地址
  5.     uint32_t block_end_address;                //块结束地址
  6.     uint32_t sector_address;                 //当前操作扇区地址
  7.     uint32_t sector_size;                        //驱动大小
  8.     uint8_t *sector_buff;                        //CACHE指针
  9.     bool sector_change_flag;                        //写标志
  10. }SPI_FLASH_BLOCK, *SPI_FLASH_BLOCK_PTR;
复制代码

然后定义两个区,我将一个命名为系统区,另一个命名为用户区:
  1. static SPI_FLASH_BLOCK spi_flash_block_descr[] = {
  2.     {"system", 0x00000000, 0x001FFFFF, 0x00000000, SPI_MEMORY_SECTOR_SIZE, NULL, FALSE},
  3.     {"user",   0x00200000, 0x007FFFFF, 0x00200000, SPI_MEMORY_SECTOR_SIZE, NULL, FALSE},
  4.     {0,        0,          0,          0,          0,                      0         }
  5. };
复制代码

当然还有一个比较重要的参数,就是SPI口了,这个参数可以用来改变SPI口,因为我的Flash挂接在SPI0上,所以如下定义:
  1. #if BSP_SPI_MEMORY_CHANNEL == 0
  2.         #define SPI_CHANNEL SPI0_BASE_PTR
  3. #elif BSP_SPI_MEMORY_CHANNEL == 1
  4.         #define SPI_CHANNEL SPI1_BASE_PTR
  5. #elif BSP_SPI_MEMORY_CHANNEL == 2
  6.         #define SPI_CHANNEL  SPI2_BASE_PTR
  7. #else
  8.      #error Unsupported SPI channel number. Please check settings of BSP_SPI_MEMORY_CHANNEL in BSP.
  9. #endif
复制代码

而为了更方便将来对驱动的修改,我添加了一个公共的数据结构,虽然目前没有其它参数,但如果以后要使用,可以在此结构体里面添加:

  1. typedef struct spi_flash_struct
  2. {
  3.     SPI_MemMapPtr SPIx;
  4. }SPI_FLASH_STRUCT, *SPI_FLASH_STRUCT_PTR;
复制代码

好了,前期的准备工作我们做好了,接下来我们要做驱动了,其实OS的驱动不外乎就是几个函数,我要完成以下几个函数的编写:
  1. _mqx_uint _spi_flash_install(char *identifier, SPI_FLASH_STRUCT const *init_ptr);
  2. _mqx_int _spi_flash_init(const void *init_data_ptr, void **io_info_ptr_ptr);
  3. _mqx_int _spi_flash_open(MQX_FILE_PTR fd_ptr, char *open_name_ptr, char *flags);
  4. _mqx_int _spi_flash_close(MQX_FILE_PTR fd_ptr);
  5. _mqx_int _spi_flash_read(MQX_FILE_PTR fd_ptr, char *data_ptr, _mqx_int num);
  6. _mqx_int _spi_flash_write(MQX_FILE_PTR fd_ptr, char *data_ptr, _mqx_int num);
  7. _mqx_int _spi_flash_ioctl(MQX_FILE_PTR fd_ptr, _mqx_uint cmd, void *param_ptr);
  8. _mqx_int _spi_flash_uninstall(IO_DEVICE_STRUCT_PTR   io_dev_ptr);
复制代码

第一个当然是install函数,我写的函数如下:
  1. _mqx_uint _spi_flash_install ( char *identifier, SPI_FLASH_STRUCT const   *init_ptr)
  2. {
  3.     SPI_FLASH_STRUCT_PTR spi_flash_data = _mem_alloc_system(sizeof(SPI_FLASH_STRUCT));        //定义了公共的变量
  4.    [color=Red] spi_flash_init(spi_flash_data, NULL);                //初始化端口以及数据
  5. [/color]    return (_io_dev_install_ext(                               //进行驱动注册
  6.         identifier,
  7.         _spi_flash_open,
  8.         _spi_flash_close,
  9.         _spi_flash_read,
  10.         _spi_flash_write,
  11.         _spi_flash_ioctl,
  12.         _spi_flash_uninstall,
  13.         spi_flash_data));
  14. }
复制代码
这里我要注明一下,其实很多驱动都可以在驱动OPEN的时候再进行初始化,但是考虑到我初始里面有个公共的变量SPI口的选择,所以我将初始化功能放在了此处。此函数要在使用SPI Flash之前调用,用来注册驱动。使用方法:
  1. _spi_flash_install("spi_flash:", NULL);
复制代码

第二个函数是相对install来说的,就是uninstall函数:
  1. _mqx_int _spi_flash_uninstall( IO_DEVICE_STRUCT_PTR   io_dev_ptr)
  2. {
  3.     SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)io_dev_ptr->DRIVER_INIT_PTR;
  4.     _mem_free(user_data);        //因为在install函数中申请了内存,所以在此要将内存释放掉,不然会造成内存泄漏
  5.     io_dev_ptr->DRIVER_INIT_PTR = NULL; //将驱动数据清空,防止野指针的出现
  6.    return MQX_OK;
  7. }
复制代码

第三个函数当然就是初始化了:
  1. _mqx_int _spi_flash_init( const void  *init_data_ptr, void **io_info_ptr_ptr )
  2. {
  3.     SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)init_data_ptr;
  4.     user_data->SPIx = SPI_CHANNEL;
  5.     hal_spi0_init(user_data->SPIx);
  6.    
  7.     return SPI_FLASH_OK;
  8. }
复制代码
初始化函数就比较简单,我只是选择了SPI接口,然后配置硬件SPI口。

第四个函数是OPEN:
  1. _mqx_int _spi_flash_open ( MQX_FILE_PTR fd_ptr, char   *open_name_ptr, char   *flags)
  2. {
  3.     SPI_FLASH_BLOCK_PTR user_block = NULL;
  4.     int i = 0;
  5.     for(i = 0; spi_flash_block_descr[i].block_name != 0; i++)
  6.     {
  7.         [color=Red]if(0 != strstr(open_name_ptr, spi_flash_block_descr[i].block_name))        //用块名称对块进行查找
  8.         {
  9.             user_block = (SPI_FLASH_BLOCK_PTR)_mem_alloc_system(sizeof(SPI_FLASH_BLOCK));         //找到块之后申请内存作为当前打开块的初始化数据
  10.             memcpy(user_block, &(spi_flash_block_descr[i]), sizeof(SPI_FLASH_BLOCK));        //对块数据进行初始化
  11.             fd_ptr->DEV_DATA_PTR = user_block;     //将块指针赋值给当前打开的驱动数据指针
  12.             break;
  13.         }
  14. [/color]    }
  15.     if(user_block == NULL)
  16.     {
  17.         return IO_ERROR;
  18.     }
  19.     user_block->sector_buff = _mem_alloc_system(user_block->sector_size);    //给CACHE申请内存
  20.     fd_ptr->SIZE = user_block->block_end_address - user_block->block_start_address + 1;  //初始化当前驱动块的尺寸
  21.     fd_ptr->LOCATION = user_block->block_start_address;   //初始化LOCATION
  22.     read_sector(fd_ptr);  //初始化当前CACHE
  23.     return MQX_OK;
  24. }
复制代码
这个函数我也是参考原MQX中Flash驱动中的OPEN函数,这块有点说道,因为Flash分块了,所以在OPEN的时候需要做一件事情,驱动我目前要OPEN的是哪一个块,使用如下:
  1. fopen("spi_flash:system", NULL);
复制代码
或者
  1. fopen("spi_flash:user, NULL);
复制代码

第五个函数close,这个函数相对比较简单,就是释放Open时申请的所有资源,然后将指针清空:
  1. _mqx_int _spi_flash_close ( MQX_FILE_PTR fd_ptr )
  2. {
  3.     SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
  4.     fflush(fd_ptr);
  5.    [color=Red] _mem_free(user_block->sector_buff);
  6.     _mem_free(user_block);
  7.     fd_ptr->DEV_DATA_PTR = NULL;
  8. [/color]    return MQX_OK;   
  9. }
复制代码

第六个函数read,这个函数其实也没什么,直接从Flash中读数据,但是这里需要注意的是要改变CACHE的切换:
  1. _mqx_int _spi_flash_read ( MQX_FILE_PTR fd_ptr,char   *data_ptr, _mqx_int   num )
  2. {   
  3.     SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
  4.     uint32_t sector_start = user_block->sector_address;
  5.     uint32_t sector_next_start = user_block->sector_address + user_block->sector_size;
  6.     uint8_t *buff_ptr;
  7.     _mqx_int location = fd_ptr->LOCATION;
  8.     _mqx_int over_num = num;
  9.     _mqx_int read_num_one_time;
  10.     do
  11.     {
  12.         if((location < sector_start) || (location >= sector_next_start))
  13.         {
  14.             [color=Red]uint32_t error = switching_sector(fd_ptr, location);                //如果地址范围超出当前CACHE,要进行切换
  15. [/color]            if(error != SPI_FLASH_OK)
  16.             {
  17.                 return error;
  18.             }
  19.             sector_start = user_block->sector_address;
  20.             sector_next_start = user_block->sector_address + user_block->sector_size;
  21.         }
  22.         buff_ptr = user_block->sector_buff + (location & (user_block->sector_size - 1));
  23.         read_num_one_time = ((location + over_num) >= sector_next_start) ? sector_next_start - location : over_num;
  24.         memcpy(data_ptr, buff_ptr,  read_num_one_time);
  25.         data_ptr += read_num_one_time;
  26.         location += read_num_one_time;
  27.         over_num -= read_num_one_time;
  28.     }while(over_num > 0);
  29.     fd_ptr->LOCATION += num;
  30.     return num;
  31. }
复制代码

第七个函数write,写函数跟读函数一样,要注意CACHE切换,另外,写函数还要注意一定要将写标志置起来,此标志是为了CACHE切换时知道Flash是否需要改变:
  1. _mqx_int _spi_flash_write (MQX_FILE_PTR fd_ptr,char     *data_ptr, _mqx_int     num )
  2. {
  3.     SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
  4.     uint32_t sector_start = user_block->sector_address;
  5.     uint32_t sector_next_start = user_block->sector_address + user_block->sector_size;
  6.     uint8_t *buff_ptr;
  7.     _mqx_int location = fd_ptr->LOCATION;
  8.     _mqx_int write_num_one_time;
  9.     _mqx_int over_num = num;
  10.     do
  11.     {
  12.         if((location < sector_start) || (location >= sector_next_start))
  13.         {
  14.             [color=Red]uint32_t error = switching_sector(fd_ptr, location);[/color]
  15.             if(error != SPI_FLASH_OK)
  16.             {
  17.                 return error;
  18.             }
  19.             sector_start = user_block->sector_address;
  20.             sector_next_start = user_block->sector_address + user_block->sector_size;
  21.         }
  22.         buff_ptr = user_block->sector_buff + (location & (user_block->sector_size - 1));
  23.         write_num_one_time = ((location + over_num) >= sector_next_start) ? sector_next_start - location : over_num;
  24.         memcpy(buff_ptr, data_ptr, write_num_one_time);
  25.        [color=Red] user_block->sector_change_flag = TRUE;
  26. [/color]        data_ptr += write_num_one_time;
  27.         location += write_num_one_time;
  28.         over_num -= write_num_one_time;
  29.         if(location >= sector_next_start)
  30.         {
  31.             write_sector(fd_ptr);
  32.         }
  33.     }while(over_num > 0);
  34.     fd_ptr->LOCATION += num;
  35.     return num;
  36. }
复制代码

最后一个IOCTRL函数,在驱动中,此函数的重要性我想我就不用说了,下面是我的函数,因为只用到了一部分命令,所以函数不算太大,MQX中Flash的IOCTRL函数那才叫个大呢:
  1. _mqx_int _spi_flash_ioctl (MQX_FILE_PTR fd_ptr, _mqx_uint    cmd,void        *param_ptr )
  2. {
  3.     SPI_FLASH_STRUCT_PTR user_data = (SPI_FLASH_STRUCT_PTR)fd_ptr->DEV_PTR->DRIVER_INIT_PTR;
  4.     SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
  5.     _mqx_int                           result = MQX_OK;
  6.     _mqx_uint_ptr                      uparam_ptr;
  7.     switch(cmd)
  8.     {
  9.         [color=Red]case IO_IOCTL_FLUSH_OUTPUT:                        //Flush功能,这个功能很有用,我一开始没有加此命令,结果文件写完后下次开机就不见了,大家知道原因吧,哈哈
  10.             write_sector(fd_ptr);
  11.             break;[/color]
  12.         case FLASH_IOCTL_ERASE_CHIP:            
  13.             hal_spi_dev_flash_erase_dev(user_data->SPIx);
  14.             hal_spi_dev_flash_read(user_data->SPIx, user_block->sector_address, user_block->sector_buff, user_block->sector_size);
  15.             break;

  16.         case IO_IOCTL_DEVICE_IDENTIFY:
  17.             /*
  18.             ** This is to let the upper layer know what kind of device this is.
  19.             ** It's a physical flash device, capable of being erased, read, seeked,
  20.             ** and written. Flash devices are not interrupt driven, so
  21.             ** IO_DEV_ATTR_POLL is included.
  22.             */   
  23.             uparam_ptr = (_mqx_uint_ptr)param_ptr;
  24.             uparam_ptr[0] = IO_DEV_TYPE_PHYS_FLASHX;
  25.             uparam_ptr[1] = IO_DEV_TYPE_LOGICAL_MFS;
  26.             uparam_ptr[2] = IO_DEV_ATTR_ERASE | IO_DEV_ATTR_POLL
  27.                              | IO_DEV_ATTR_READ | IO_DEV_ATTR_SEEK |
  28.                              IO_DEV_ATTR_WRITE;
  29.             break;
  30.         
  31.         case IO_IOCTL_GET_NUM_SECTORS:
  32.             *(uint32_t *)param_ptr = fd_ptr->SIZE / SPI_MEMORY_SECTOR_SIZE;
  33.             break;
  34.         case IO_IOCTL_GET_BLOCK_SIZE:      
  35.         case FLASH_IOCTL_GET_SECTOR_SIZE:
  36.             /* returns the fixed size for MFS sector size */
  37.             *(uint32_t *)param_ptr = user_block->sector_size;
  38.             break;
  39. [color=Red]        case IO_IOCTL_SEEK:                     //这个命令一定要添加,因为我们Flash分块了,所以起始地址不一样,如果不加此功能,那么它起始地址都是默认从0开始,那第二个                                                                   //块肯定会读写错误[/color]
  40.             fd_ptr->LOCATION += user_block->block_start_address;
  41.             break;
  42.         default:
  43.             result = IO_ERROR_INVALID_IOCTL_CMD;
  44.     }
  45.     return result;
  46. }
复制代码

最后的最后,还有一个函数我必须得说明一下,切换扇区,需要注意的是一开始定义的写标志在这里起了作用,看代码:
  1. uint32_t switching_sector(MQX_FILE_PTR fd_ptr, _mqx_int address)
  2. {
  3.     SPI_FLASH_BLOCK_PTR user_block = (SPI_FLASH_BLOCK_PTR)fd_ptr->DEV_DATA_PTR;
  4.     uint32_t error_code;
  5.     //取旧扇区的起始地址
  6.     _mqx_int sector_start_address = address & (~(user_block->sector_size - 1));
  7.     //写Flash
  8. [color=Red]    if(user_block->sector_change_flag)
  9.          error_code = write_sector(fd_ptr);[/color]
  10.     if(error_code != SPI_FLASH_OK)
  11.     {
  12.         return error_code;
  13.     }
  14.     //更新扇区的起始地址参数
  15.     user_block->sector_address = sector_start_address;
  16.     //重新填充CACHE
  17.     error_code = read_sector(fd_ptr);
  18.     if(error_code != SPI_FLASH_OK)
  19.     {
  20.         return error_code;
  21.     }
  22.     return SPI_FLASH_OK;
  23. }
复制代码

OK了,其它的一些再底层的函数我就没有必要一一说明了!尾部再将源文件传上来:
MQX_SPI_Flash_device.rar (6.62 KB, 下载次数: 26) 2014-8-31 16:59 上传 点击文件名下载附件

砖来了,玉在哪?

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
97条回答
wangpengcheng
2020-02-27 06:53
浪里白条 发表于 2014-9-1 22:46
高手 请教一下呢
比如我有了
uart.c uart.h

你要分两个对像来处理的话,为了移植方便,比如下次你用gsm模块调用的是SPI模块的同样的一个函数,我一般的做法是在GSM模块中添加一个函数指针,然后在初始化的时候从UART模块或SPI模块中把函数地址拿出来初始化此指针,而此指针就是GSM模块中的变量,需要注意的是在用此函数的之前要判断他是否已经被赋值,不然很容易出现野指针,呵呵!

一周热门 更多>