2017-06-11 22:32:05 lingfeng5 阅读数 1151
  • 多任务的裸机实现(下)

    通过本课程学习,让嵌入式学员对对整个嵌入式系统、CPU内部、操作系统都有一个全局的把握和认识,为后续的Linux内核、驱动开发等高阶课程打下良好的理论基础。掌握这些知识后,会让我们后续的课程更加轻松、学习效率...

    9064课时 0分钟 33人学习 王利涛
    免费试看

1.SPI协议:
SPI, Serial Perripheral  Interface, 串行外围设备接口,

  1. 是Motorola公司推出的一种同步串行接口技术.主要实现CPU与EEPROM、FLASH、实时时钟、AD转换器等外设芯片的通讯。
  2. 采用主-从模式(Master-Slave) 的控制方式。Master 设备可以通过提供 Clock 以及对 Slave设备进行片选 (Slave Select) 来控制多个 Slave 设备,  Slave则 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave设备不能正常工作。
  3. 基于SPI总线的设备由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。

2.Linux SPI 子系统框架学习小结

SPI子系统采用分层分离的思想。

实现流程:
1.注册平台设备
2.注册平台驱动
  .probe()函数中注册描述spi控制器的spi_master
3.注册spi设备:spi_board_info
4.注册spi_driver
  .probe()函数中注册描述外设的设备结构体,如char设备
5.用户操作API实现。read()/write()

画了个流程图:



代码实例:转自博文——http://blog.chinaunix.NET/uid-25445243-id-4026974.html

一、W25Q32BV芯片简介

        W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下:

1.1、基本特性

        该芯片最大支持104MHz的时钟,供电电压范围在2.7~3.6V,SPI的片选信号CS低有效,在操作芯片的时候,

需要将/WP和/HOLD管脚接电源。

        发送地址或数据到设备时,MOSI管脚数据采样在CLK的上升沿,从芯片读数据或者状态时,MISO管脚数据采样在CLK

的下降沿,所以在设置SPI的工作模式时,必须设置为MODE0或者MODE3,本文设置为MODE3。

1.2、存储空间简介

        W25Q32BV总共有16384页(page),每页有256bytes,每次最大可以编程一页。在擦除上,可以一次擦除

4KB、32KB、64KB,或者擦除整个芯片。整个芯片的存储空间如下图:


        W25Q32BV存储空间分为sector和block。一个sector共有4KB,一个block共有32KB。一个sector存储空间如下图:

    

        本文共支持四种擦除方式,分别如下:

        1) cmd = 0x20,sector擦除,一次可以擦除4KB。芯片共有1024个sector。

        2) cmd = 0x52,半个block擦除,一次可以擦除32KB。芯片共有128个半block。

        3) cmd = 0xd8,block擦除,一次可以擦除64KB。芯片共有64个block。

        4) cmd = 0xC7,芯片擦除,擦除整个芯片。

1.3、状态寄存器

        W25Q32BV共有两个字节的状态寄存器,我们需要关心的就是BIT0和BIT1。

        BIT0:busy flag,1:busy,0:free。

        BIT1:write enable latch,1:write enable,0:write disable。

1.4、操作要求

        在操作W25Q32BV时,如果是写数据到芯片,则每写一个字节,都需要读取一个数据。

        在从芯片接收数据时,首先往芯片写一个字节的0xff,然后就是需要读取的数据。

二、设备驱动

2.1、设备注册

        在系统启动的时候,首先会对设备信息进行注册,见《Linux spi驱动分析(一)----总线驱动》中的3.1,所以编写w25q的设备
驱动程序时,首先需要对设备信息进行注册,具体内容如下:

点击(此处)折叠或打开

  1. #if defined(CONFIG_SPI_FLASH_W25Q)
  2.  static struct gsc3280_spi_info w25q_spi1_dev_platdata = {
  3.      .cs_type            = 1,
  4.     .pin_cs            = 87,
  5.     .num_cs            = 1,
  6.     .cs_value            = 0,
  7.     .lsb_flg            = 0,
  8.     .bits_per_word    = 8,
  9. };
  10. #endif
  11. static struct spi_board_info gsc3280_spi_devices[] = {
  12. #if defined(CONFIG_SPI_FLASH_W25Q)
  13.     {
  14.         .modalias        = "spi-w25q",
  15.         .bus_num        = 1,
  16.         .chip_select        = 2,
  17.         .mode            = SPI_MODE_3,
  18.         .max_speed_hz    = 5 * 1000 * 1000,
  19.         .controller_data    = &w25q_spi1_dev_platdata,
  20.     },
  21. #endif

  22. };
  23. static int __init gsc3280_spi_devices_init(void)
  24. {
  25.     spi_register_board_info(gsc3280_spi_devices, ARRAY_SIZE(gsc3280_spi_devices));
  26.     return 0;
  27. }
  28. device_initcall(gsc3280_spi_devices_init);

2.2、初始化函数

        首先我们从设备注册开始,程序如下:

点击(此处)折叠或打开

  1. static struct spi_driver w25q_driver = {
  2.     .driver    = {
  3.         .name    = "spi-w25q",
  4.         .owner    = THIS_MODULE,
  5.     },
  6.     //.id_table    = w25q_ids,
  7.     .probe    = w25q_probe,
  8.     .remove    = __devexit_p(w25q_remove),
  9. };


  10. static int __init w25q_init(void)
  11. {
  12.     return spi_register_driver(&w25q_driver);
  13. }
  14. static void __exit w25q_exit(void)
  15. {
  16.     spi_unregister_driver(&w25q_driver);
  17. }
  18. module_init(w25q_init);
  19. module_exit(w25q_exit);

        由于W25Q32BV使用SPI接口,所以将其注册为SPI驱动,接下来看下探测函数w25q_probe,程序如下:

点击(此处)折叠或打开

  1. static int __devinit w25q_probe(struct spi_device *spi)
  2. {
  3.     int ret = 0;
  4.     struct w25q_dev *w25q;

  5.     DBG("############\n");
  6.     DBG("w25q spi flash probe start.\n");
  7.     w25q = kzalloc(sizeof(struct w25q_dev), GFP_KERNEL);
  8.     if (!w25q) {
  9.         DBG("!!!!kzalloc error!\n");
  10.         return -ENOMEM;
  11.     }
  12.     ret = spi_setup(spi);
  13.     if (ret != 0) {
  14.         DBG("!!!!setup error!\n");
  15.         return ret;
  16.     }
  17.     w25q->spi = spi;
  18.     mutex_init(&w25q->mlock);
  19.     strlcpy(w25q->name, W25Q_SPI_FLASH_NAME, sizeof(w25q->name));
  20.     ret = alloc_chrdev_region(&w25q->devt, 0, W25Q_MAX_MINOR, "w25q");
  21.     if (ret < 0) {
  22.         DBG("!!!!%s: failed to allocate char dev region!\n", __FILE__);
  23.         goto err_kzall;
  24.     }
  25.     w25q->dev.devt = MKDEV(MAJOR(w25q->devt), 1);
  26.     cdev_init(&w25q->cdev, &w25q_fops);
  27.     w25q->cdev.owner = THIS_MODULE;
  28.     ret = cdev_add(&w25q->cdev, w25q->devt, 1);
  29.     if (ret) {
  30.         DBG("!!!!cdev add error!\n");
  31.         goto err_alloc;
  32.     }
  33.     w25q->class = class_create(THIS_MODULE, "w25q-spi");
  34.     if (IS_ERR(w25q->class)) {
  35.         DBG("!!!!failed in create w25q spi flash class!\n");
  36.         goto err_alloc;;
  37.     }
  38.     device_create(w25q->class, NULL, w25q->devt, NULL, "w25q");
  39.     dev_set_drvdata(&spi->dev, w25q);
  40.     DBG("w25q spi flash probe success.\n");
  41.     DBG("############\n");
  42.     return 0;

  43. err_alloc:
  44.     unregister_chrdev_region(w25q->devt, W25Q_MAX_MINOR);
  45. err_kzall:
  46.     kfree(w25q);
  47.     printk(KERN_ERR "!!!!!!w25q spi flash probe error.!!!!!!\n");
  48.     return ret;
  49. }

        说明:
        1) 首先申请设备驱动结构体。
        2) 调用spi_setup(spi)函数对设备信息初始化。
        3) 初始化设备驱动结构体成员变量。
        4) 创建/dev目录下操作文件,操作函数集为w25q_fops。
        5) 将设备驱动结构体中的链表插入本文件全局链表w25q_device_list中,以便在函数操作集的open函数中找到设备驱动结构体。
        remove函数是探测函数的相反过程,具体程序如下:

点击(此处)折叠或打开

  1. static int __devexit w25q_remove(struct spi_device *spi)
  2. {
  3.     struct w25q_dev *w25q = dev_get_drvdata(&spi->dev);

  4.     cdev_del(&w25q->cdev);
  5.     unregister_chrdev_region(w25q->devt, W25Q_MAX_MINOR);
  6.     device_destroy(w25q->class, w25q->devt);
  7.     class_destroy(w25q->class);
  8.     kfree(w25q);
  9.     return 0;
  10. }

2.3、操作函数集w25q_fops

        操作函数集结构体具体内容如下:

点击(此处)折叠或打开

  1. static const struct file_operations w25q_fops = {
  2.     .owner = THIS_MODULE,
  3.     .open = w25q_open,
  4.     .write = w25q_write,
  5.     .unlocked_ioctl = w25q_ioctl,
  6.     .read = w25q_read,
  7.     .llseek = w25q_llseek,
  8.     .release = w25q_release,
  9. };

        接下来我们一一讲述。
        首先看下open函数w25q_open,具体程序如下:

点击(此处)折叠或打开

  1. static int w25q_open(struct inode *inode, struct file *file)
  2. {
  3.     struct w25q_dev *w25q = container_of(inode->i_cdev, struct w25q_dev, char_cdev);

  4.     if (test_and_set_bit(W25Q_BIT_LOCK_OPEN, &w25q->bit_lock)) {
  5.         DBG("!!!!w25q open err, busy!\n");
  6.         return -EBUSY;
  7.     }
  8.     file->private_data = w25q;
  9.     return 0;
  10. }

        说明:
        1) 通过container_of找到在探测函数w25q_probe中定义的设备驱动结构体。
        2) 测试并且设置忙标志,如果测试忙,直接忙退出。
        3) 将找到的设备驱动结构体指针指向file->private_data,在函数操作集的其他函数中就可以使用设备驱动结构体了。
        接下来看下写函数w25q_write(),程序如下:

点击(此处)折叠或打开

  1. #define W25Q_BUF_LEN                4096
  2. #define W25Q_PAGE_NUM                256
  3. static ssize_t w25q_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
  4. {
  5.     int ret = 0;
  6.     u8 *buf_start, *buf_tmp, *w25q_buf;
  7.     struct w25q_dev *w25q= file->private_data;
  8.     u32 buf_size = 0, page_num = W25Q_PAGE_NUM, len = 0;
  9.     
  10.     DBG("@@@@w25q write start\n");
  11.     buf_start = buf_tmp = kzalloc(W25Q_BUF_LEN, GFP_KERNEL);
  12.     w25q_buf = w25q->buf = kzalloc(page_num + 4, GFP_KERNEL);
  13.     if (!buf_start || !w25q_buf) {
  14.         DBG("!!!!kzalloc error!\n");
  15.         return -ENOMEM;
  16.     }
  17.     ret = mutex_lock_interruptible(&w25q->mlock);
  18.     if (ret) {
  19.         DBG("!!!!mutex lock error!");
  20.         goto exit_kfree;
  21.     }
  22.     len = W25Q_BUF_LEN;
  23.     buf_size = min(count, len);
  24.     if (copy_from_user(buf_tmp, user_buf, buf_size)) {
  25.         DBG("!!!!copy_from_user() error!\n");
  26.         ret = -EFAULT;
  27.         goto exit_lock;
  28.     }
  29.     DBG("w25q->const_addr = 0x%x\n", w25q->const_addr);
  30.     buf_tmp = buf_start;
  31.     w25q->cmd = W25X_PAGE_PROG;
  32.     w25q->addr = w25q->const_addr;
  33.     while(buf_size) {
  34.         w25q->buf = w25q_buf;
  35.         w25q->len = min(buf_size, page_num);
  36.         memcpy(w25q->buf + 4, buf_tmp, w25q->len);
  37.         ret = w25q_write_date(w25q);
  38.         if (ret != 0) {
  39.             break;
  40.         }
  41.         buf_tmp += w25q->len;
  42.         w25q->addr += w25q->len;
  43.         buf_size -= w25q->len;
  44.     }

  45. exit_lock:
  46.     mutex_unlock(&w25q->mlock);
  47. exit_kfree:
  48.     kfree(buf_start);
  49.     kfree(w25q_buf);
  50.     if (ret != 0)
  51.         DBG("!!!!w25q write error!\n");
  52.     else
  53.         DBG("w25q write success\n");
  54.     return ret;
  55. }

        说明:
        1) 写函数首先申请两段内存,第一段内存用于存储从应用层复制来的待写数据,最大为4KB。第二段内存用于存储每次
            往W25Q32BV写的数据。由于W25Q32BV每次最大能写256bytes,所以page_num= 256,加上4是由于每次
            传输时,需要在最前面加上一个字节的命令和三个字节的地址。
        2) 获取本次可以传输的最大数据长度。
        3) 设置好传输的cmd和起始地址,然后进入while循环。
        4) 在while循环中,获取本次可以传输的最大长度,最长为256bytes,然后将其拷贝到buf中,加上4的目的是因为buf
            的前四个字节需要放置命令和地址。
        5) 调用w25q_write_date(w25q)函数实现数据传输。
        6) 更新变量,为下一次传输做好准备。
        w25q_write_date(w25q)函数具体内容如下:

点击(此处)折叠或打开

  1. static void w25q_write_enable(struct w25q_dev *w25q)
  2. {
  3.     u8 cmd = W25X_WRITE_ENABLE;

  4.     spi_w8r8(w25q->spi, cmd);
  5. }
  6. static int w25q_wait_null(struct w25q_dev *w25q)
  7. {
  8.     uint8_t limit = 5;

  9.     /* wait BUSY bit clear */
  10.     while(((w25q_read_stat_reg(w25q) & 0x01) == 0x01) && (limit != 0)) {
  11.         limit--;
  12.         mdelay(50);
  13.     }
  14.     if (limit == 0) {
  15.         DBG("!!!!w25q_wait_null:time out!\n");
  16.         return -EBUSY;
  17.     }
  18.     else
  19.         return 0;
  20. }
  21. /*
  22.  * when you call this function,
  23.  * the w25q->cmd, w25q->len(tx date len),
  24.  * w25q->addr and w25q->buf(date) are OK
  25.  *
  26.  */
  27. static int w25q_write_date(struct w25q_dev *w25q)
  28. {
  29.     int ret = 0;
  30.     u8 i = 0, rx = 0;
  31.     struct spi_message message;
  32.     struct spi_transfer    x[(w25q->len + 4) * 2];

  33.     w25q_write_enable(w25q);    //SET WEL
  34.     ret = w25q_wait_null(w25q);
  35.     if (ret != 0) {
  36.         DBG("!!!!w25q_write_date: wait null err!\n");
  37.         return ret;
  38.     }
  39.     if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) {
  40.         DBG("!!!!state register write able is 0\n");
  41.         return -EBUSY;    //disable write
  42.     }

  43.     DBG("cmd = 0x%x, addr = 0x%x\n", w25q->cmd, w25q->addr);
  44.     w25q->buf[0] = w25q->cmd;
  45.     w25q->buf[1] = ((u8)(w25q->addr >> 16));
  46.     w25q->buf[2] = ((u8)(w25q->addr >> 8));
  47.     w25q->buf[3] = ((u8)w25q->addr);
  48.     
  49.     spi_message_init(&message);
  50.     memset(x, 0, sizeof x);
  51.     for (i = 0; i < (w25q->len + 4) * 2; i++) {
  52.         x[i].len = 1;
  53.         spi_message_add_tail(&x[i], &message);
  54.         if ((i % 2) == 0) {
  55.             x[i].tx_buf = w25q->buf++;
  56.         } else {
  57.             x[i].rx_buf = &rx;
  58.         }
  59.     }
  60.     /* do the i/o */
  61.     ret = spi_sync(w25q->spi, &message);
  62.     if (ret != 0) {
  63.         DBG("!!!!w25q_write_date: spi_sync() error!");
  64.         return ret;
  65.     }
  66.     ret = w25q_wait_null(w25q);
  67.     if (ret != 0)
  68.         DBG("!!!!w25q_write_date: w25q_wait_null() error!");
  69.     return ret;
  70. }

        说明:
        1) 在调用w25q_write_date(w25q)函数之前,需要首先设置好w25q->cmd, w25q->len(txdatelen)
            w25q->addr和w25q->buf(date)变量。
        2) 设置芯片状态寄存器,使其可写。
        3) 等待芯片不忙。
        4) 读取芯片状态寄存器,查看其是否可写。
        5) 配置发送buf,调用spi_sync(w25q->spi,&message);函数实现写数据。
        6) 等待芯片不忙,退出。
        接下来看下函数操作集中的ioctl函数,程序如下:

点击(此处)折叠或打开

  1. static long w25q_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
  2. {
  3.     int ret = 0;
  4.     u32 get_value = 0;
  5.     struct w25q_dev *w25q= file->private_data;
  6.     void __user *argp = (void __user *)arg;
  7.     int __user *p = argp;

  8.     DBG("@@@@w25q ioctl start.\n");
  9.     ret = mutex_lock_interruptible(&w25q->mlock);
  10.     if (ret) {
  11.         DBG("!!!!mutex lock error!\n");
  12.         return ret;
  13.     }
  14.     if ((_IOC_TYPE(cmd) != W25Q_IOC_MAGIC) || (_IOC_NR(cmd) > W25Q_IOC_MAXNR)) {
  15.         DBG("!!!!ioc type or ioc nr error!\n");
  16.         ret = -ENOTTY;
  17.         goto exit;
  18.     }
  19.     switch(cmd) {
  20.     case W25Q_SECTOR_ERASE:
  21.     case W25Q_HALF_BLOCK_ERASE:
  22.     case W25Q_BLOCK_ERASE:
  23.         if (get_user(get_value, p)) {
  24.             DBG("!!!!get value error!\n");
  25.             ret = -EFAULT;
  26.             goto exit;
  27.         }
  28.         ret = w25q_erase(w25q, get_value, cmd);
  29.         break;
  30.     case W25Q_CHIP_ERASE:
  31.         ret = w25q_chip_erase(w25q);
  32.         break;
  33.     case W25Q_READ_DEVICE_ID:
  34.         ret = w25q_read_id(w25q);
  35.         if (ret == 0)
  36.             put_user(w25q->result, p);
  37.         break;
  38.     default:
  39.         DBG("!!!!cmd error!\n");
  40.         ret = -ENOTTY;
  41.         break;
  42.     }

  43. exit:
  44.     mutex_unlock(&w25q->mlock);
  45.     if (ret != 0)
  46.         DBG("!!!!w25q ioctl error!\n");
  47.     else
  48.         DBG("w25q ioctl success.\n");
  49.     return ret;
  50. }

        说明:
        1) 目前共支持5个命令,包括sector擦除,half block擦除,block擦除,芯片擦除和读取芯片ID。
        2) 前三种擦除方式共用一个函数w25q_erase(w25q, get_value, cmd);,程序如下:

点击(此处)折叠或打开

  1. static int w25q_erase(struct w25q_dev *w25q, u32 num, unsigned int cmd)
  2. {
  3.     int ret = 0;
  4.     u8 *buf_start;

  5.     switch(cmd) {
  6.     case W25Q_SECTOR_ERASE:
  7.         DBG("sector erase cmd\n");
  8.         if (num > W25Q_SECTOR_MAX) {
  9.             DBG("!!!!sector max is over\n");
  10.             return -EFAULT;
  11.         }
  12.         w25q->const_addr = num * W25Q_ONE_SECTOR_ADDR;
  13.         w25q->cmd = W25X_SECTOR_ERASE_CMD;
  14.         break;
  15.     case W25Q_HALF_BLOCK_ERASE:
  16.         DBG("half block erase cmd\n");
  17.         if (num > W25Q_HALF_BLOCK_MAX) {
  18.             DBG("!!!!half block max is over\n");
  19.             return -EFAULT;
  20.         }
  21.         w25q->const_addr = num * W25Q_HALF_BLOCK_ADDR;
  22.         w25q->cmd = W25X_HALF_BLOCK_ERASE_CMD;
  23.         break;
  24.     case W25Q_BLOCK_ERASE:
  25.         DBG("block erase cmd\n");
  26.         if (num > W25Q_BLOCK_MAX) {
  27.             DBG("!!!!block max is over\n");
  28.             return -EFAULT;
  29.         }
  30.         w25q->const_addr = num * W25Q_ONE_BLOCK_ADDR;
  31.         w25q->cmd = W25X_BLOCK_ERASE_CMD;
  32.         break;
  33.     }
  34.     DBG("w25q->const_addr = 0x%x\n", w25q->const_addr);
  35.     w25q->len = 0;
  36.     buf_start = w25q->buf = kzalloc(w25q->len + 4, GFP_KERNEL);
  37.     if (!buf_start) {
  38.         DBG("!!!!kzalloc is error\n");
  39.         return -ENOMEM;
  40.     }
  41.     w25q->addr = w25q->const_addr;
  42.     ret = w25q_write_date(w25q);
  43.     kfree(buf_start);
  44.     if (ret != 0) {
  45.         DBG("!!!!w25q_erase: spi write err!\n");
  46.         return ret;
  47.     }
  48.     DBG("w25q_erase: erase OK\n");
  49.     return ret;
  50. }

        说明:
        1) 首先根据不同的擦除方式,设置命令和地址两个变量。
        2) 调用w25q_write_date(w25q);函数实现数据传输。
        芯片擦除函数w25q_chip_erase()如下:

点击(此处)折叠或打开

  1. static int w25q_chip_erase(struct w25q_dev *w25q)
  2. {
  3.     int ret = 0;
  4.     u8 cmd = W25X_CHIP_ERASE;

  5.     DBG("w25q_chip_erase\n");
  6.     w25q_write_enable(w25q);    //SET WEL
  7.     ret = w25q_wait_null(w25q);
  8.     if (ret != 0) {
  9.         DBG("!!!!chip_erase: wait null err!\n");
  10.         return ret;
  11.     }
  12.     if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) {
  13.         DBG("!!!!state register write able is 0\n");
  14.         return -EBUSY;    //disable write
  15.     }
  16.     spi_w8r8(w25q->spi, cmd);
  17.     return w25q_wait_null(w25q);
  18. }

        读取设备ID函数w25q_read_id()如下:

点击(此处)折叠或打开

  1. static int w25q_read_id(struct w25q_dev *w25q)
  2. {
  3.     int ret = 0;
  4.     u8 *buf_start;

  5.     DBG("w25q_read_id\n");
  6.     w25q->len = 2;
  7.     w25q->addr = 0;
  8.     w25q->cmd = W25X_READ_ID_CMD;
  9.     buf_start = w25q->buf = kzalloc(w25q->len, GFP_KERNEL);
  10.     if (!buf_start) {
  11.         DBG("!!!!kzalloc is error\n");
  12.         return -ENOMEM;
  13.     }
  14.     ret = w25q_read_data(w25q);
  15.     w25q->buf = buf_start;
  16.     w25q->result = *w25q->buf << 8;
  17.     w25q->buf++;
  18.     w25q->result |= *w25q->buf;
  19.     kfree(buf_start);
  20.     if (ret != 0) {
  21.         DBG("!!!!w25q_read_id: w25q_read_data error!\n");
  22.         return ret;
  23.     }
  24.     DBG("w25q_read_id: read id OK\n");
  25.     return ret;
  26. }

        说明:
        1) 首先设置好变量,申请内存
        2) 调用w25q_read_data()函数实现读取数据。
        w25q_read_data函数如下:

点击(此处)折叠或打开

  1. /*
  2.  * when you call this function,
  3.  * the w25q->cmd, w25q->len(receive len)
  4.  * w25q->buf(kzalloc receive) and w25q->addr are OK
  5.  *
  6.  */
  7. static int w25q_read_data(struct w25q_dev *w25q)
  8. {
  9.     int ret = 0;
  10.     struct spi_message message;
  11.     struct spi_transfer    x[(w25q->len + 4) * 2];
  12.     u8 i = 0, rx = 0, dumy_value = 0xff, tx_buff[4] = {0};

  13.     w25q_write_enable(w25q);    //SET WEL
  14.     ret = w25q_wait_null(w25q);
  15.     if (ret != 0) {
  16.         DBG("!!!!chip_erase: wait null err!\n");
  17.         return ret;
  18.     }
  19.     if((w25q_read_stat_reg(w25q) & 0x02) != 0x02) {
  20.         DBG("!!!!state register write able is 0\n");
  21.         return -EBUSY;    //disable write
  22.     }

  23.     DBG("cmd = 0x%x, addr = 0x%x\n", w25q->cmd, w25q->addr);
  24.     tx_buff[0] = w25q->cmd;
  25.     tx_buff[1] = ((uint8_t)(w25q->addr >> 16));
  26.     tx_buff[2] = ((uint8_t)(w25q->addr >> 8));
  27.     tx_buff[3] = ((uint8_t)(w25q->addr));
  28.     
  29.     spi_message_init(&message);
  30.     memset(x, 0, sizeof x);
  31.     for (i = 0; i < 8; i++) {    //cmd
  32.         x[i].len = 1;
  33.         spi_message_add_tail(&x[i], &message);
  34.         if ((i % 2) == 0) {
  35.             x[i].tx_buf = &tx_buff[i / 2];
  36.         } else {
  37.             x[i].rx_buf = &rx;
  38.         }
  39.     }
  40.     for (i = 8; i < (w25q->len + 4) * 2; i++) {
  41.         x[i].len = 1;
  42.         spi_message_add_tail(&x[i], &message);
  43.         if ((i % 2) == 0) {
  44.             x[i].tx_buf = &dumy_value;
  45.         } else {
  46.             x[i].rx_buf = w25q->buf++;
  47.         }
  48.     }
  49.     /* do the i/o */
  50.     return spi_sync(w25q->spi, &message);
  51. }

        说明:
        1) 在从芯片读取数据时的格式为:首先发送一个字节命令+三个字节读取地址,然后就可以接收数据了。
        2) 第一个for循环发送的是命令和地址,第二个for循环是接收数据。
        3) 调用此函数之前,需要设置好w25q->cmd, w25q->len(receivelen),w25q->buf(kzalloc receive)和w25q->addr。
        接下来看下函数操作集中的读数据函数w25q_read(),程序如下:

点击(此处)折叠或打开

  1. static ssize_t w25q_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
  2. {
  3.     int ret = 0;
  4.     u8 *buf_start, *buf_tmp, *w25q_buf;
  5.     struct w25q_dev *w25q = file->private_data;
  6.     u32 buf_size = 0, read_len = 0, page_num = W25Q_PAGE_NUM;

  7.     DBG("@@@@w25q read start\n");
  8.     buf_start = buf_tmp = kzalloc(W25Q_BUF_LEN, GFP_KERNEL);
  9.     w25q_buf = w25q->buf = kzalloc(page_num, GFP_KERNEL);
  10.     if (!buf_start || !w25q_buf ) {
  11.         DBG("!!!!kzalloc error!\n");
  12.         return -ENOMEM;
  13.     }
  14.     ret = mutex_lock_interruptible(&w25q->mlock);
  15.     if (ret) {
  16.         DBG("!!!!mutex lock error!\n");
  17.         goto exit_kfree;
  18.     }
  19.     read_len = W25Q_BUF_LEN;
  20.     buf_size = min(count, read_len);
  21.     read_len = buf_size;
  22.     
  23.     w25q->cmd = W25X_READ_DATA;
  24.     w25q->addr = w25q->const_addr;
  25.     DBG("w25q->addr = 0x%x\n", w25q->addr);
  26.     while (buf_size) {
  27.         w25q->buf = w25q_buf;
  28.         w25q->len = min(buf_size, page_num);
  29.         ret = w25q_read_data(w25q);
  30.         if (ret != 0) {
  31.             goto exit_lock;
  32.         }
  33.         memcpy(buf_tmp, w25q_buf, w25q->len);
  34.         buf_tmp += w25q->len;
  35.         buf_size -= w25q->len;
  36.         w25q->addr += w25q->len;
  37.     }
  38.     ret = copy_to_user(user_buf, buf_start, read_len);
  39.     ret = read_len -ret;

  40. exit_lock:
  41.     mutex_unlock(&w25q->mlock);
  42. exit_kfree:
  43.     kfree(buf_start);
  44.     kfree(w25q_buf);
  45.     DBG("w25q read stop, ret = %d\n", ret);
  46.     return ret;
  47. }

        说明:
        1) 此函数需要申请两段内存空间,第一段用于存放从W25Q32BV接收到的数据,第二段用于存放每次接收的数据。
        2) 设置好变量后,调用w25q_read_data(w25q)读取数据。
        3) 读取完成后,将读取到的数据拷贝到应用层。

三、应用层测试

        应用层测试程序如下:

点击(此处)折叠或打开

  1. /*
  2. * first you must erase,
  3. * then write, then read
  4. * or you can read only
  5. *
  6. */
  7. #include "w25q.h"


  8. int main(int argc, char **argv)
  9. {
  10.     char str[10] = {0};
  11.     int fd = 0, ret = 0;
  12.     unsigned char buffer[BUFSIZE] = {0};
  13.     unsigned int i = 0, idCmd = 0, num = 0;
  14.     
  15.     fd = open("/dev/w25q", O_RDWR);
  16.     if (fd < 0) {
  17.         printf("Open ADC Device Faild!\n");
  18.         exit(1);
  19.     }
  20.     while(1) {
  21.         idCmd = 0;
  22.         printf("please enter the cmd and num :\n");
  23.         scanf("%s%x", str, &num);
  24.         //printf("cmd = %s, idFreq = %d\n", str, idFreq);
  25.         if (num >= 0) {
  26.             if (strcmp(str, "SECTOR") == 0) {
  27.                 idCmd = W25Q_SECTOR_ERASE;
  28.                 ret = ioctl(fd, idCmd, &num);
  29.                 if (ret != 0) {
  30.                     printf("sector erase Faild!\n");
  31.                 }
  32.             } else if(strcmp(str, "HALF") == 0) {
  33.                 idCmd = W25Q_HALF_BLOCK_ERASE;
  34.                 ret = ioctl(fd, idCmd, &num);
  35.                 if (ret != 0) {
  36.                     printf("half block erase Faild!\n");
  37.                 }
  38.             } else if(strcmp(str, "BLOCK") == 0) {
  39.                 idCmd = W25Q_BLOCK_ERASE;
  40.                 ret = ioctl(fd, idCmd, &num);
  41.                 if (ret != 0) {
  42.                     printf("block erase Faild!\n");
  43.                 }
  44.             } else if(strcmp(str, "CHIP") == 0) {
  45.                 idCmd = W25Q_CHIP_ERASE;
  46.                 ret = ioctl(fd, idCmd, &num);
  47.                 if (ret != 0) {
  48.                     printf("chip erase Faild!\n");
  49.                 }
  50.             } else if(strcmp(str, "ID") == 0) {
  51.                 idCmd = W25Q_READ_DEVICE_ID;
  52.                 ret = ioctl(fd, idCmd, &num);
  53.                 if (ret != 0) {
  54.                     printf("read ID Faild!\n");
  55.                 } else {
  56.                     printf("ID = 0x%x\n", num);
  57.                 }
  58.             } else if(strcmp(str, "READ") == 0) {
  59.                 memset(buffer, 0, BUFSIZE);
  60.                 printf("------------\n");
  61.                 for (i = 0; i < WRITE_NUM; i++) {
  62.                     if((i != 0) && ((i % 8) == 0)) {
  63.                         printf("\n");
  64.                     }
  65.                     printf("0x%x ", buffer[i]);
  66.                 }
  67.                 printf("\n------------\n");
  68.                 ret = read(fd, buffer, WRITE_NUM);
  69.                 printf("\n------------\n");
  70.                 for (i = 0; i < WRITE_NUM; i++) {
  71.                     if((i != 0) && ((i % 8) == 0)) {
  72.                         printf("\n");
  73.                     }
  74.                     printf("0x%x ", buffer[i]);
  75.                 }
  76.                 printf("\n------------\n");
  77.             } else if(strcmp(str, "WRITE") == 0) {
  78.                 for (i = 0; i < WRITE_NUM; i++) {
  79.                     buffer[i] = i;
  80.                 }
  81.                 ret = write(fd, buffer, WRITE_NUM);
  82.                 if (ret != 0) {
  83.                     printf("w25q write oper Faild!\n");
  84.                 }
  85.             } else if(strcmp(str, "QUIT") == 0) {
  86.                 break;
  87.             } else {
  88.                 printf("wrong string\n");
  89.             }
  90.         } else {
  91.             printf("wrong input num(< 0)\n");
  92.         }
  93.     }/* end while(1) */
  94.     close(fd);
  95.     return 0;
  96. }

        说明:
        1) 首先从终端接收命令内容。
        2) 比较命令,然后进入不同的处理流程。


参考博文:

1.http://blog.chinaunix.net/uid-20620288-id-3164730.html   //写的很清晰

2.http://blog.chinaunix.net/uid-27041925-id-3576276.html   

3.http://blog.chinaunix.NET/uid-25445243-id-4026974.html

2011-11-20 02:09:00 haiou0 阅读数 0
  • 多任务的裸机实现(下)

    通过本课程学习,让嵌入式学员对对整个嵌入式系统、CPU内部、操作系统都有一个全局的把握和认识,为后续的Linux内核、驱动开发等高阶课程打下良好的理论基础。掌握这些知识后,会让我们后续的课程更加轻松、学习效率...

    9064课时 0分钟 33人学习 王利涛
    免费试看

 定时器驱动初步了解:http://bbs.ednchina.com/BLOG_ARTICLE_313843.HTM

 触摸屏驱动:http://bbs.ednchina.com/BLOG_ARTICLE_305599.HTM

等等其他的:... ...

AD驱动:http://bbs.ednchina.com/BLOG_ARTICLE_296114.HTM

AD转换学习


AD的分类:


时间/数字、电压/数字、机械变量/数字


在嵌入式系统中用的最多的是电压/数字转换器


电压/数字转换器可以分为多种类型:


转换方式:直接转换和间接转换


输出方式:串行、并行、串并行


转换原理:计数式、比较式


转换速度:低速、中速、高速


转换精度:3位、4位、8位、10位、12位、14位、16位等


 


计数式AD


这里以8位计数式AD为例


点击看大图


计数式AD的转换过程:


(1),CLR有效(高电平变成低电平),使计数器输出数字信号为00000000,这个00000000的输出送到8位D/A转换器,8位D/A转换器的输出也是0V


(2),当CLR恢复为高电平时,计数器准备计数,此时,在计数器输入端上待转换的模拟输入电压为Vi大于Vo,比较器输出电压c位1,这样计数器开始计数


(3),计数器的输入不断增加,D/A转换器输出的模拟电压(Vo)也在不断的增加,当Vo小于Vi时,计数器不断的计数


(4),当Vo上升到某值时,出现Vo>Vi的情况,此时,比较器的输出电压为低电平,使计数器控制信号c=0,计数器停止计数,此时,数字量D0~D7就是模拟电压等效。计数器控制信号由高变为低的副跳变也是A/D转换的结束信号,表示完成一次A/D转换。


 


优缺点:结构简单,但是转换速度慢。


逐次逼近式A/D转换


点击看大图


逐次逼近式A/D转换的原理


A/D转换开始前,先将SAR寄存器清0,然后进行转换,时钟信号首先将寄存器的最高位置1,使输出为100….000,这个数字将被DA转换器转换成模拟电压Vo送到比较器与Vi进行比较


当Vo大于Vi的时候,这个位的2进制码将会被改为0,如果Vo小于Vi,那么之前位的1将被保留,一直到最低位为止。比较器的值就是所要求的数值输出。


 


接下来就以ADS7844位例,粗略的分析下linux下编写AD转换驱动


ADS7844的具体的工作原理网上很多,这里就不做介绍了。


ADS7844与s3C2410硬件接口电路


3C2410与ADS7844的~种硬件连接图。其中用GPDl3产生片选信号,GPEl3产生时钟信号,GPG2用来接“忙”信号引脚GPEl2、GPEll分别与ADS7844的DIN和DOUT连接。由于使用的$3C2410的标准SPI接口,所有软件上有2种方法进行处理,一种是使用标准的SPI协议进行数据转换,另一种是使用I/0模拟SPI协议的方式进行数据转换。为了软件简便,使用第2种数据转换方式


点击看大图


驱动程序(基于Linux2.6内核)的整体框架:


 


#include<linuxinith>//模块加载和卸载相关


#include<linuxmaduleh>//MODULE_LICENSE()


#include<linuxkemelh>//printk()函数


#include<asmarchregs_gpioh>//$3C2410内部寄存器宏定义


#include<asmhardwareh>//s3c2410_gpio_cfgpin()等函数


 


//ADS7844字符设备结构体定义


struct ads7844_dev


{


struct cdev cdev


unsigned int channel;//通道号


unsigned int adc[8];//8路采集值


}


 


ADS7844设备注册和卸载


int ads7844_init(void)


{


int resulti


dev_t devno="MKDEV"(ads7844_major0)


if(ads7844_major)


result=register_chrdev_region(devno1DEVICE_NAME)


//指定设备号


else


{


result=alloc_chrdev_region(&devno0,1DEVICE_NAME)//动态生成设备号


ads7844_major=MAJOR(devno)


}


if(result<0)return result


ads7844_setup_cdev(ads7844_devp0);//初始化字符设备


.


.


.


}


void ads7844_exit(void)


{


cdev_del(&ads7844_devp->cdev);//移除字符设备


.


.


.


unregister_chrdev_region(MKDEV(ads7844_major0)1)


//释放设备号


}


 


ADS7844设备具体操作设计与实现


在设备注册过程中,构建了ADS7844_fops结构指针。在file_operations结构中定义了一组函数指针。


static const struct file_operations ads7844_fops=


{


owner=THIS_MODULE,


open=ads7844_open,


read=ads7844_read


write=ads7844-_write,


release=ads7844_release


}


 


系统就是通过这一组函数指针来实现ADS7844设备的操作的。具体说来,主要有2个方面的工作:


一是对ADS7844进行工作方式的设置和通道的选择;


二是从设备中获取采样数据的信息。它们分别对应ADS7844_read  ADS7844_write2个函数。了解整个驱动设计的框架后,读写函数的实现就比较简单


 


读操作中数据移出的过程


.


.


.


for(i=0i<16i++)


{


ads7844_result<<=1;//将刚读出的数据移到高位


s3c2410_gpio_setpin(ads7844_table[0]1);//设置时钟为高


udelay(100)


s3c2410_gpio_setpin(ads7844_table[0]0);//设置时钟为低


 


if(s3c2410_gpio_getpin(ads7844_table[2]))//下降沿读出一位数据


ads7844_resuh |=1;


udelay(100)


}


ads7844_result>>=4;//移出低位多余的4”0”


.


.


.


2015-08-30 09:07:41 Cowena 阅读数 0
  • 多任务的裸机实现(下)

    通过本课程学习,让嵌入式学员对对整个嵌入式系统、CPU内部、操作系统都有一个全局的把握和认识,为后续的Linux内核、驱动开发等高阶课程打下良好的理论基础。掌握这些知识后,会让我们后续的课程更加轻松、学习效率...

    9064课时 0分钟 33人学习 王利涛
    免费试看
Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。

    Linux I2C驱动涉及的知识点还是挺多的,主要分为Linux I2C的总线驱动(I2C BUS Driver)和设备驱动(I2C Clients Driver),本文主要关注如何快速地完成一个具体的I2C设备驱动(I2C Clients Driver)。关于Linux I2C驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。

    本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。

1.   i2c_driver结构体对象     

    每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C设备探测和注销的一些基本方法和信息,示例如下:

  1. static struct i2c_driver tvp5158_i2c_driver = {  
  2.         .driver = {  
  3.             .name = "tvp5158_i2c_driver",  
  4.         },   
  5.         .attach_adapter = &tvp5158_attach_adapter,  
  6.         .detach_client  = &tvp5158_detach_client,  
  7.         .command        = NULL,  
  8. };  

    其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detach_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。

2.   i2c_client 结构体对象

    上面定义的i2c_driver对象,抽象为一个i2c的驱动模型,提供对i2C设备的探测和注销方法,而i2c_client结构体则是代表着一个具体的i2c设备,该结构体有一个data指针,可以指向任何私有的设备数据,在复杂点的驱动中可能会用到。示例如下:   

  1. struct tvp5158_obj{        
  2.     struct i2c_client client;        
  3.     int users; // how many users using the driver    
  4. };  
  5.  
  6. struct tvp5158_obj* g_tvp5158_obj;  

    其中,users为示例,用户可以自己在tvp5158_obj这个结构体里面添加感兴趣的字段,但是i2c_client字段不可少。具体用法后面再详细讲。

3.   设备注册及探测功能

    这一步很关键,按照标准的要求来写,则Linux系统会自动调用相关的代码去探测你的I2C设备,并且添加到系统的I2C设备列表中以供后面访问。

    我们知道,每一个I2C设备芯片,都通过硬件连接设定好了该设备的I2C设备地址。因此,I2C设备的探测一般是靠设备地址来完成的。那么,首先要在驱动代码中声明你要探测的I2C设备地址列表,以及一个宏。示例如下:

  1. static unsigned short normal_i2c[] = {  
  2.         0xbc >> 1,  
  3.         0xbe >> 1,  
  4.         I2C_CLIENT_END  
  5. };  
  6. I2C_CLIENT_INSMOD;  

    normal_i2c 数组包含了你需要探测的I2C设备地址列表,并且必须以I2C_CLIENT_END作为结尾,注意,上述代码中的0xbc和0xbe是我在硬件上为我的tvp5158分配的地址,硬件上我支持通过跳线将该地址设置为 0xbc 或者 0xbe,所以把这两个地址均写入到探测列表中,让系统进行探测。如果你的I2C设备的地址是固定的,那么,这里可以只写你自己的I2C设备地址,注意必须向右移位1。

    宏 I2C_CLIENT_INSMOD 的作用网上有许多文章进行了详细的讲解,这里我就不详细描述了,记得加上就行,我们重点关注实现。

    下一步就应该编写第1步中的两个回调函数,一个用于注册设备,一个用于注销设备。探测函数示例如下:

  1. static int tvp5158_attach_adapter(struct i2c_adapter *adapter)  
  2. {  
  3.     return i2c_probe(adapter, &addr_data, &tvp5158_detect_client);  

    这个回调函数系统会自动调用,我们只需要按照上述代码形式写好就行,这里调用了系统的I2C设备探测函数,i2c_probe(),第三个参数为具体的设备探测回调函数,系统会在探测设备的时候调用这个函数,需要自己实现。示例如下:

  1. static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind)  
  2. {  
  3.     struct tvp5158_obj *pObj;  
  4.     int err = 0;  
  5.  
  6.     printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address);  
  7.  
  8.     if( g_tvp5158_obj != NULL  ) {  
  9.         //already allocated,inc user count, and return the allocated handle  
  10.         g_tvp5158_obj->users++;  
  11.         return 0;  
  12.     }  
  13.  
  14.     /* alloc obj */ 
  15.     pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL);  
  16.     if (pObj==0){  
  17.         return -ENOMEM;  
  18.     }  
  19.     memset(pObj, 0, sizeof(struct tvp5158_obj));  
  20.     pObj->client.addr    = address;  
  21.     pObj->client.adapter = adapter;  
  22.     pObj->client.driver  = &tvp5158_i2c_driver;  
  23.     pObj->client.flags   = I2C_CLIENT_ALLOW_USE;  
  24.     pObj->users++;  
  25.  
  26.     /* attach i2c client to sys i2c clients list */ 
  27.     if((err = i2c_attach_client(&pObj->client))){  
  28.         printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address);  
  29.         return err;  
  30.     }  
  31.  
  32.     // store the pObj  
  33.     g_tvp5158_obj = pObj;  
  34.  
  35.     printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address);  
  36.  
  37.     return 0;  

    到此为止,探测并且注册设备的代码已经完成,以后对该  I2C 设备的访问均可以通过 g_tvp5158_obj 这个全局的指针进行了。

4.    注销I2C设备 

    同理,设备注销的回调函数也会自动被系统调用,只需要按照模板写好设备注销代码,示例如下:    

  1. static int tvp5158_detach_client(struct i2c_client *client)  
  2. {  
  3.     int err;  
  4.  
  5.     if( ! client->adapter ){  
  6.         return -ENODEV;  
  7.     }  
  8.  
  9.     if( (err = i2c_detach_client(client)) ) {  
  10.         printk( KERN_ERR "Client deregistration failed (address=%x), client not detached.\n", client->addr);  
  11.         return err;  
  12.     }  
  13.  
  14.     client->adapter = NULL;  
  15.  
  16.     if( g_tvp5158_obj ){  
  17.         kfree(g_tvp5158_obj);  
  18.     }  
  19.  
  20.     return 0;  

    到此为止,设备的注册和注销代码已经全部完成,下面要做的就是提供读写I2C设备的方法。

 5.   I2C设备的读写      

    对I2C设备的读写,Linux系统提供了多种接口,可以在内核的 i2c.h 中找到,这里简单介绍其中的两种接口。

   【接口一】:

  1. extern int i2c_master_send(struct i2c_client *,const char* ,int);  
  2.  
  3. extern int i2c_master_recv(struct i2c_client *,char* ,int);  

    第一个参数是 i2c_client 对象指针,第二个参数是要传输的数据buffer指针,第三个参数为buffer的大小。

   【接口二】:

  1. extern int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num); 

    这个接口支持一次向I2C设备发送多个消息,每一个消息可以是读也可以是写,读或者写以及读写的目标地址(寄存器地址)均包含在msg消息参数里面。

    这些接口仅仅是最底层的读写方法,关于具体怎么与I2C设备交互,比如具体怎么读芯片的某个特定寄存器的值,这需要看具体的芯片手册,每个I2C芯片都会有具体的I2C寄存器读写时序图。因此,为了在驱动中提供更好的访问接口,还需要根据具体的时序要求对这些读写函数进行进一步封装,这些内容将在后面的文章中讲述。

6.  模块初始化及其他

    下一步就是整个模块的初始化代码和逆初始化代码,以及模块声明了。    

  1. static int __init tvp5158_i2c_init(void
  2.     g_tvp5158_obj = NULL; 
  3.      
  4.     return i2c_add_driver(&tvp5158_i2c_driver); 
  5.  
  6. static void __exit tvp5158_i2c_exit(void
  7.     i2c_del_driver(&tvp5158_i2c_driver); 
  8.  
  9. module_init(tvp5158_i2c_init); 
  10. module_exit(tvp5158_i2c_exit); 
  11.  
  12. MODULE_DESCRIPTION("TVP5158 i2c driver"); 
  13. MODULE_AUTHOR("Lujun @hust"); 
  14. MODULE_LICENSE("GPL"); 

    在初始化的代码里面,添加本模块的 i2c driver 对象,在逆初始化代码里面,删除本模块的 i2c driver 对象。

7.   总结

    到此为止,算是从应用的角度把编写一个I2C的设备驱动代码讲完了,很多原理性的东西我都没有具体分析(其实我也了解的不深),以后会慢慢更深入地学习和了解,文中有什么讲述不正确的地方,欢迎留言或者来信lujun-hust@gmail.com交流。

    读到最后,大家可能还有一个疑问,这个驱动写完了怎么在用户空间(应用层)去使用它呢?由于本文不想把代码弄得太多太复杂,怕提高理解的难度,所以就没有讲,其实要想在用户空间使用该I2C设备驱动,则还需要借助字符设备驱动来完成,即为这个I2C设备驱动封装一层字符设备驱动,这样,用户空间就可以通过对字符设备驱动的访问来访问I2C设备,这个方法我会在后面的文章中讲述。

2019-01-14 20:47:43 wghkemo123 阅读数 0
  • 多任务的裸机实现(下)

    通过本课程学习,让嵌入式学员对对整个嵌入式系统、CPU内部、操作系统都有一个全局的把握和认识,为后续的Linux内核、驱动开发等高阶课程打下良好的理论基础。掌握这些知识后,会让我们后续的课程更加轻松、学习效率...

    9064课时 0分钟 33人学习 王利涛
    免费试看

S5P6818 ADC 的使用(三星芯片6818型号)

视频讲解及资料链接:链接:https://pan.baidu.com/s/1rPjq2goHFZf2ArBuo99Ehg      提取码:fdqk 
 


 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#include <linux/io.h>
#include <linux/gpio.h>
#include <cfg_type.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>

//------------------------------------------------
#define GEC6818_ADC_IN0		_IOR('A',  1, unsigned long)
#define GEC6818_ADC_IN1		_IOR('A',  2, unsigned long)

static void __iomem		*adc_base_va;		//adc的虚拟地址基址
static void __iomem		*adccon_va;		
static void __iomem		*adcdat_va;	
static void __iomem		*prescalercon_va;
//---------------------------------------------

static int  gec6818_adc_open (struct inode * inode, struct file *file)
{
	printk("gec6818_adc_open \n");
	
	return 0;
}

static int  gec6818_adc_release (struct inode * inode, struct file *file)
{
	printk("gec6818_adc_release \n");
	
	return 0;
}


static long gec6818_adc_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
	unsigned long adc_val=0,adc_vol=0; 
	
	int rt=0;
	//adc通道选择
	switch(cmd)
	{
		case GEC6818_ADC_IN0:
				iowrite32(ioread32(adccon_va)&(~(7<<3)),adccon_va);
		break;
		
		
		case GEC6818_ADC_IN1:
		{
				iowrite32(ioread32(adccon_va)&(~(7<<3)),adccon_va);
				iowrite32(ioread32(adccon_va)|(1<<3),adccon_va);
		}break;		
		
		default:
			printk("IOCTLCMD failed\n");
			return -ENOIOCTLCMD;
	}
	
	//将电源开启
	iowrite32(ioread32(adccon_va)&(~(1<<2)),adccon_va);
	
	//预分频值=199+1,ADC的工作频率=200MHz/(199+1)=1MHz
	iowrite32(ioread32(prescalercon_va)&(~(0x3FF<<0)),prescalercon_va);
	iowrite32(ioread32(prescalercon_va)|(199<<0),prescalercon_va);
	
	//预分频值使能
	iowrite32(ioread32(prescalercon_va)|(1<<15),prescalercon_va);
	
	//ADC使能
	//iowrite32(ioread32(adccon_va)&(~(1<<0)),adccon_va);
	iowrite32(ioread32(adccon_va)|(1<<0),adccon_va);
	
	//等待AD转换结束
	while(ioread32(adccon_va)&(1<<0));
	
	//读取12bit数据
	adc_val = ioread32(adcdat_va)&0xFFF;
	
	//关闭CLKIN时钟输入
	iowrite32(ioread32(prescalercon_va)&(~(1<<15)),prescalercon_va);
	
	//关闭ADC电源
	iowrite32(ioread32(adccon_va)|(1<<2),adccon_va);
	
	//将AD转换的结果值 换算为 电压值
	adc_vol = adc_val*1800/4095;
	
	rt = copy_to_user((void *)args,&adc_vol,4);
	
	if(rt != 0)
		return -EFAULT;
	return 0;
}

static ssize_t gec6818_adc_read (struct file *file, char __user *buf, size_t len, loff_t * offs)
{
	return 0;
}

static const struct file_operations gec6818_adc_fops = {
 	.owner 			= THIS_MODULE,
	.unlocked_ioctl = gec6818_adc_ioctl,
	.open 			= gec6818_adc_open,
	.release 		= gec6818_adc_release,
	.read 			= gec6818_adc_read,
};


static struct miscdevice gec6818_adc_miscdev = {
	.minor		= MISC_DYNAMIC_MINOR,	//MISC_DYNAMIC_MINOR,动态分配次设备号
	.name		= "adc_drv",		//设备名称,/dev/adc_drv	
	.fops		= &gec6818_adc_fops,	//文件操作集
};

//入口函数
static int __init gec6818_adc_init(void)
{
	int rt=0;
	
	struct resource *adc_res=NULL;
	
	//混杂设备的注册
	rt = misc_register(&gec6818_adc_miscdev);
	
	if (rt) 
	{
		printk("misc_register fail\n");
		return rt;
	}
//-------------------------------------------------------------	
	//IO内存动态映射,得到物理地址相应的虚拟地址
	adc_base_va = ioremap(0xC0053000,0x14);
	
	if(adc_base_va == NULL)
	{
		
		printk("ioremap 0xC0053000,0x14 fail\n");
		
		goto fail_ioremap_adc;		
		
	}	
	
	//得到每个寄存器的虚拟地址
	adccon_va 		= adc_base_va+0x00;
	adcdat_va 		= adc_base_va+0x04;	
	
	prescalercon_va = adc_base_va+0x10;	
	
	printk("gec6818 adc init\n");
//------------------------------------------------------------	
	return 0;
	
fail_ioremap_adc:

	misc_deregister(&gec6818_adc_miscdev);
	
	return rt;
}


//出口函数
static void __exit gec6818_adc_exit(void)
{	
	iounmap(adc_base_va);

	
	misc_deregister(&gec6818_adc_miscdev);
	
	printk("gec6818 adc exit\n");
}

//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_adc_init。
module_init(gec6818_adc_init);

//驱动程序的出口:rmsmod led_drv调用module_exit,module_exit又会去调用gec6818_adc_exit。
module_exit(gec6818_adc_exit)


//模块描述
MODULE_AUTHOR("stephenwen88@163.com");			//作者信息
MODULE_DESCRIPTION("gec6818 adc driver");		//模块功能说明
MODULE_LICENSE("GPL");							//许可证:驱动遵循GPL协议

 

 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

#define GEC6818_ADC_IN0		_IOR('A',  1, unsigned long)
#define GEC6818_ADC_IN1		_IOR('A',  2, unsigned long)

int main(int argc, char **argv)
{
	int fd=-1;

	int rt;

	int i=0;
	
	unsigned long adc_vol = 0;
	
	//打开adc设备
	fd = open("/dev/adc_drv",O_RDWR);
	
	if(fd < 0)
	{
		perror("open /dev/adc_drv:");
		
		return fd;
		
	}
	
	
	while(1)
	{
		//读取ADC通道0的电压值
		rt=ioctl(fd,GEC6818_ADC_IN0,&adc_vol);
		
		if(rt != 0)
		{
			printf("adc in0 read filed\r\n");
			
			usleep(50*1000);
			
			continue;
		}
		
		printf("adc in0 vol: %lu=mv\r\n",adc_vol);

		sleep(1);
	}
	
	close(fd);
	
	
	return 0;
}

程序文件结构
 
2018-07-18 22:34:49 xiezhi123456 阅读数 658
  • 多任务的裸机实现(下)

    通过本课程学习,让嵌入式学员对对整个嵌入式系统、CPU内部、操作系统都有一个全局的把握和认识,为后续的Linux内核、驱动开发等高阶课程打下良好的理论基础。掌握这些知识后,会让我们后续的课程更加轻松、学习效率...

    9064课时 0分钟 33人学习 王利涛
    免费试看

1、驱动源代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <asm/irq.h>
#include <asm/arch/regs-timer.h>
#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/map.h>
#include <asm/arch/regs-irq.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/clk.h>
#include <linux/cdev.h>


#define ADC_WRITE(ch, prescale)    ((ch) << 16 | (prescale))

#define ADC_WRITE_GETCH(data)    (((data) >> 16) & 0x7)
#define ADC_WRITE_GETPRE(data)    ((data) & 0xff)

#define DEVICE_NAME    "adc"

static void __iomem *base_addr;

typedef struct {                    //adc转换的设备结构体
    wait_queue_head_t wait; //等待队列
    int channel;            //ADC转换通道
    int prescale;
}ADC_DEV;

DECLARE_MUTEX(ADC_LOCK); //定义互斥信号量
static int ADC_OK = 0;

static ADC_DEV adcdev;
static volatile int ev_adc = 0;
static int adc_data;

static struct clk    *adc_clock;

#define ADCCON     (*(volatile unsigned long *)(base_addr + S3C2410_ADCCON))    //ADC control
#define ADCTSC     (*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC))    //ADC touch screen control
#define ADCDLY     (*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY))    //ADC start or Interval Delay
#define ADCDAT0     (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0))    //ADC conversion data 0
#define ADCDAT1     (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1))    //ADC conversion data 1
#define ADCUPDN     (*(volatile unsigned long *)(base_addr + 0x14))        //Stylus Up/Down interrupt status

#define PRESCALE_DIS        (0 << 14)
#define PRESCALE_EN        (1 << 14)   //使能预分频
#define PRSCVL(x)            ((x) << 6)//设定的预分频值
#define ADC_INPUT(x)        ((x) << 3)  //选择ADC转换通道
#define ADC_START        (1 << 0)   //    启动ADC转换
#define ADC_ENDCVT        (1 << 15)

//AD转换中断处理函数
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
    if (ADC_OK)
    {
        adc_data = ADCDAT0 & 0x3ff; //提取AD转换后的数据

        ev_adc = 1;  //转换结束唤醒等待的进程
        wake_up_interruptible(&adcdev.wait);
    }

    return IRQ_HANDLED;
}

static ssize_t s3c2410_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
    char str[20];
    int value;
    size_t len;
    if (down_trylock(&ADC_LOCK) == 0) //获得信号量 :获得不到信号量不会阻塞
    {
        ADC_OK = 1;//启动AD转换
        ADCCON = PRESCALE_EN | PRSCVL(adcdev.prescale) | ADC_INPUT((adcdev.channel)) ;
        //使能预分频    设置预分频值  选择ADC转换通道
        ADCCON |= ADC_START; //启动AD转换
        
        wait_event_interruptible(adcdev.wait, ev_adc);  //阻塞等待AD转换

        ev_adc = 0;//清除转换标志位

//        printk("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ADCCON & 0x80 ? 1:0);

        value = adc_data; //AD转换的数据
        sprintf(str,"%5d", adc_data);//数据转换
        //copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

        ADC_OK = 0;
        up(&ADC_LOCK);//释放信号量
    }
    else
    {
        value = -1;
    }

    len = sprintf(str, "%d\n", value);
    if (count >= len)
    {
        int r = copy_to_user(buffer, str, len); //把AD转换数据送回用户空间
        return r ? r : len;
    }
    else
    {
        return -EINVAL;
    }
}

static int s3c2410_adc_open(struct inode *inode, struct file *filp)
{
    init_waitqueue_head(&(adcdev.wait));  //初始化等待队列

    adcdev.channel=0;        //选择ADC转换的通道    //AIN 0 or (0~7)
    adcdev.prescale=0xff;          //AD转换延时

    printk( "ADC opened\n");
    return 0;
}

static int s3c2410_adc_release(struct inode *inode, struct file *filp)
{
    printk( "EmbedSky-ADC closed\n");
    return 0;
}

static struct file_operations Adc_fops = {
    owner:    THIS_MODULE,
    open:    s3c2410_adc_open,
    read:    s3c2410_adc_read,    
    release:    s3c2410_adc_release,
};

static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &Adc_fops,
};

static char __initdata banner[] = "S3C2440 ADC\n";

static int __init Adc_init(void)
{
    int ret;
    printk(banner);

    base_addr=ioremap(S3C2410_PA_ADC,0x20);//映射把物理地址映射为虚拟地址
    if (base_addr == NULL)  //返回值检测
    {
        printk(KERN_ERR "Failed to remap register block\n");
        return -ENOMEM;
    }

    adc_clock = clk_get(NULL, "adc"); //获得ADC的时钟
    if (!adc_clock)
    {
        printk(KERN_ERR "failed to get adc clock source\n");
        return -ENOENT;
    }
    clk_enable(adc_clock);//使能ADC的时钟
    
    /* normal ADC */
    ADCTSC = 0;//不使用触摸屏

    ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);//注册中断
    if (ret)
    {
        iounmap(base_addr);
        return ret;
    }

    ret = misc_register(&misc);//注册一个混杂设备

    printk(DEVICE_NAME " initialized\n");
    return ret;
}

static void __exit Adc_exit(void)
{
    free_irq(IRQ_ADC, &adcdev);//注销中断
    iounmap(base_addr);   //解除映射

    if (adc_clock)
    {
        clk_disable(adc_clock);//关闭adc转换的时钟
        clk_put(adc_clock);     
        adc_clock = NULL;
    }

    misc_deregister(&misc);//注销混杂设备
}

EXPORT_SYMBOL(ADC_LOCK);


module_init(Adc_init);
module_exit(Adc_exit);


MODULE_LICENSE("GPL");                    
2、应用程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <linux/input.h>
int main(void)
{
    int adc_fd;
    int count;
    char adc[6];
    adc_fd = open("/dev/adc", O_RDWR); //以阻塞的方式打开设备文件
    if (adc_fd < 0) {
        perror("open device adc_fd");
        exit(1);
    }

    for (;;) {
        count = read(adc_fd,&adc,sizeof(adc));
        adc[count]='\0';
        printf(" adc = %s\n", adc);
                sleep(2);//2秒钟采样一次

    }

    close(adc_fd);
    return 0;
}