Chinaunix首页 | 论坛 | 博客
  • 博客访问: 830961
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: LINUX

2017-02-21 21:05:35

3. 用户态 SPI 编程
    Linux 的 SPI 总线设备文件名通常为/dev/spidevN.P( N=0, 1, 2..., P=0, 1, 2...),
    其中 N 表示第几路 SPI 总线,而 P 表示在该路 SPI 总线中使用哪个 CS 信号线。
    EasyARM-i.MX283A 提供了 1 路 SPI 总线,在该总线中只有 1 个 CS 信号线,其设备文
    件名为/dev/spidev1.0。
    root@EasyARM-iMX283 /# ls /dev/spi*
    /dev/spidev1.0
    
3.1 SPI 编程接口
    1. 打开设备
    在使用 SPI 设备时,需要调用 open()函数打开设备文件,获得文件描述符,如程序清单3.1 所示。
        程序清单 3.1 打开 SPI 设备文件
        fd = open(―/dev/spidev1.0‖, O_RDWR);
        if (fd < 0) {
            perror( "can not open SPI device\n" );
        }
        
    2. 关闭设备
    设备使用完成后,调用 close()函数关闭设备,如下所示:
    close(fd);
    
    3. 总线控制
    通过调用 ioctl()函数使用不同的命令,应用程序可以配置 SPI 总线的极性和相位、设置
    总线速率、数据字长度以及实现数据收/发。
    
    (1) 设置总线极性和相位
        SPI 总线极性及相位设置是通过 SPI_IOC_WR_MODE 命令实现的, 该命令的用法参考表 3.1。
        表 3.1 SPI_IOC_WR_MODE 命令
        ---------------------------------------------------------
        命 令             SPI_IOC_WR_MODE
        调用方式         ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
        功能描述         设置 SPI 总线的极性和相位
        输入参数说明
                        mode 的可选值为: SPI_MODE_0、SPI_MODE_1、SPI_MODE_2、SPI_MODE_3,
                        这些值的说明参考下面内容。
        返回值说明
                        0:设置成功
                        1:设置不成功
        
        
        SPI_MODE_0 定义的模式为 POLARITY( 极性) =0、 PHASE( 相位) =0。
        SPI_MODE_1 定义的模式为 POLARITY=0、 PHASE=1。
        SPI_MODE_2 定义的模式为 POLARITY=1、 PHASE=0。
        SPI_MODE_3 定义的模式为 POLARITY=1、 PHASE=1。
    
        设置 SPI 总线极性和相位为 SPI_MODE_0 模式的方法可以参考如程序清单 3.2 所示的代码。
        程序清单 3.2 设置 SPI 总线极性和相位示例
            int mode = SPI_MODE_0;
            ret = ioctl(fd_spi, SPI_IOC_WR_MODE, &mode);
            if (ret == -1) {
                printf("can't set wr spi mode\n");
                return -1;
            }
    
    (2)设置每字的数据位长度
        设置 SPI 总线上每字的数据位长度是使用 SPI_IOC_WR_BITS_PER_WORD 命令实现,
        该命令的用法参考表 3.2。
            表 3.2 SPI_IOC_WR_BITS_PER_WORD 命令
            ---------------------------------------------------------------------
            命 令                 SPI_IOC_WR_BITS_PER_WORD
            调用方式             ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
            功能描述             设置 SPI 总线上每字的数据位长度
            输入参数说明         bits 为每字的二制位数,取值
            返回值说明             0 为成功,其它值为失败
        
        
        设置 SPI 总线的每字数据位长为 8 位的方法可以参考如程序清单 3.3所示的代码。
        ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &bits); /* 设置 SPI 的数据位 */
        if (ret == -1) {
            printf("can't set bits per word\n");
            return -1;
        }
    
    (3) 设置最大总线速率
        设置 SPI 总线的最大速率是通过使用 SPI_IOC_WR_MAX_SPEED_HZ 命令实现,该命
        令用法参考表 3.3。
            表 3.3 SPI_IOC_WR_MAX_SPEED_HZ
            --------------------------------
            命 令             SPI_IOC_WR_MAX_SPEED_HZ
            调用方式         ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
            功能描述         设置 SPI 总线的最大速率
            输入参数说明     speed 为需要设置的 SPI 总线的最大频率, 单位为 Hz
            返回值说明         恒为 0:设置成功
            
        注意:SPI 总线的最大速率设置后,在使用过程并不是只能使用该频率收/发数据,而仅仅约束
        收/发数据时的最大频率。
        
    (4) 数据接收/发送命令
        在 SPI 总线实现数据收/发是使用 SPI_IOC_MESSAGE(n)命令实现,该命令用法参考表3.4。
        表 3.4 SPI_IOC_MESSAGE(n)命令
        ------------------------------------------------------------------------------
        命 令             SPI_IOC_MESSAGE(n)
        调用方式         ret = ioctl(fd, SPI_IOC_MESSAGE(n), &tr);
        功能描述         实现在 SPI 总线接收/发送数据操作,其中 n 的值可变
        输入/输出参数说明
                        struct spi_ioc_transfer 结构体用于封装要收/发的数据。 tr 参数指定向 struct
                        spi_ioc_transfer 结构体的数组,数组长度为 n。
        返回值说明
                        0:操作成功
                        1:操作失败
                        
        使用SPI_IOC_MESSAGE(n)命令收/发的数据都需要使用 struct spi_ioc_transfer结构体封
        装,该结构体的定义如程序清单 3.4 所示。
        
        程序清单 3.4 struct spi_ioc_transfer 结构体的定义
        struct spi_ioc_transfer {
            __u64 tx_buf;         /* 指向发送数据的缓冲区 */
            __u64 rx_buf;         /* 指向接收数据的缓冲区 */
            __u32 len;             /* 收/发缓冲区中数据的长度 */
            __u32 speed_hz;     /* 总线速率 */
            __u16 delay_usecs;
            __u8 bits_per_word; /* 收/发数据的二进制位数 */
            __u8 cs_change;
            __u32 pad;
        }
        speed_hz 不能大于在 SPI_IOC_WR_MAX_SPEED_HZ 命令中设置的总线速率。
        
    注意:
        由于 iMX28xx 处理器的 SPI 控制器只支持半双工,因此 struct spi_ioc_transfer 结构体中
        的 tx_buf 和 rx_buf 只能设置一个有效, 另一个必须设置为 0, 否则调用 ioctl 时会返回非零
        值提示操作错误。
        
3.2 编程范例
    1. 电路原理
        在 AP-283Demo 板上,通过 74HC595 作为 SPI 从机器件驱动数码管。
            硬件原理图
        
        在 74HC595 芯片中,如果要将 8 位串行输入数据并行输出到 QA、 QB、 QC、 QD、 QE、
        QF、 QG、 QH,则需要满足以下条件:
        ? 首先必须保证在 SCK 引脚输入连续的时钟信号;
        ? 在 SCK 引脚输入信号的上升沿,在 SI 引脚输入的数据被送入 QA 的第 1 级移位寄
        存器, QA 移位寄存器原有的值移入 QB 移位寄存器, QB 移位寄存器原有的值移
        入 QC 移位寄存器,以此类推;
        ? 在 RCK 引脚输入信号的上升沿,移位寄存器中的数据被送入锁存器;
        ? 若 OE 引脚输入低电平,则锁存器的值将在 QA~QH 引脚输出。    
        
        在 AP-283Demo 板上, MCU 的 SPI 接口控制 2 片 74HC595 带锁存的移位寄存器驱动 2
        个共阴式的 LN3461BS 数码管,其中 U4 控制 8 位数据管的位选位, U6 控制 4 位数码管的
        段选位,也就是说只要给数码管的位选位输送低电平,给数码管的段选位输送高电平,即可
        点亮数码管。
        MCU 作为主机通过 SPI 总线发送数据, 74HC595 作为人机接收数据,采用级联的方式
        对 2 片 74HC595 进行操作,其数据的传递方式如下:
        (1) 发送 8 位“位选”数据,且被保存在 U6 的移位寄存器中;
        (2) 紧接再发送“段选”数据时,刚才发送的“位选”数据将通过级联方式移位到 U4
        的移位寄存器中,后发送的“段选”数据则被保存在 U6 的移位寄存器吕;
        (3) 当数据移位完成后,在 RCK 产生一个上升沿将移位寄存器中的数据移位到锁存器;
        (4) 由于 OE 为低电平,锁存器的数据送到 U4、 U6 的 QA~QH 数据引脚上。
        其中 U4、 U6 的 RCK 引脚连接到 i.MX283 处理器的 GPIO3.21 引脚。    
        
    2. 示例程序代码
        在应用程序通过 SPI 总线控制数码管的示例程序代码如程序清单 3.5 所示。该程序接
        受两个输入参数:显示数值( 0 ~ 9)和数字选择值( 0 ~ 3)。程序先打开 SPI 总线设备文件
        和 GPIO 属性文件,然后设置 SPI 总线参数:总线极性、总线的最大频率、数据字的大小。
        设置完成后,调用 show_led_num 函数执行数据发送操作,在该函数中把显示数值转化为位
        选值、把数字选择值转化为段选值,再把位选值和段选值通过 SPI 总线发送到 U4 和 U6 的
        74HC595 芯片的移位寄存器,最后通过 GPIO 产生一个上升沿信号使 74HC595 移位寄存器
        的值输出到相关引脚,以控制数码管的点亮。
        
        见源代码。
        

点击(此处)折叠或打开

  1. #include <stdint.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <getopt.h>
  6. #include <fcntl.h>
  7. #include <sys/ioctl.h>
  8. #include <linux/types.h>
  9. #include <linux/spi/spidev.h>

  10. #define SPI_DEVICE         "/dev/spidev1.0"
  11. #define GPIO_DEVICE     "/sys/class/gpio/gpio117/value" /* gpio3.21 的属性文件 */


  12. /* 显示数值和位选值的对照表 0 1 2 3 4 5 6 7 8 9 */
  13. uint8_t led_value_table[] =
  14. {
  15.     0xC0, 0xF9, 0xa4, 0xb0, 0x99,
  16.     0x92, 0x82, 0xF8, 0x80, 0x90
  17. };

  18. static uint8_t     mode = 0;
  19. static uint8_t     bits = 8;
  20. static uint32_t speed = 10000;
  21. static uint16_t delay = 0;

  22. static void show_led_num(int fd_spi, int fd_gpio, int value, int num)
  23. {
  24.     int ret;
  25.     uint8_t tx[] = {
  26.         led_value_table[value],     /* 把显示数值转化为位选值 */
  27.         (1 << num),                 /* 把数字选择值转化为段选值 */
  28.     };
  29.     
  30.     struct spi_ioc_transfer tr_txrx[] =
  31.     {
  32.         {
  33.             .tx_buf = (unsigned long)tx,
  34.             .rx_buf = 0,
  35.             .len     = 2,
  36.             .delay_usecs     = delay,
  37.             .speed_hz         = speed,
  38.             .bits_per_word     = bits,
  39.         },
  40.     };

  41.     /* 把位选值和段选值通过 SPI 总线发送到 U4 和 U6 的移位寄存器 */
  42.     ret = ioctl(fd_spi, SPI_IOC_MESSAGE(1), &tr_txrx[0]);
  43.     if (ret == 1) {
  44.         printf("can't revieve spi message");
  45.         return;
  46.     }
  47.     
  48.     /*
  49.     * 通过 GPIO 产生上升沿信号, 使 U4 和 U6 的移位寄存器的值输出到相关引脚,
  50.     * 以控制数码管的点亮
  51.     */
  52.     write(fd_gpio, "0", 1);
  53.     usleep(100);
  54.     write(fd_gpio, "1", 1);
  55.     usleep(100);
  56. }


  57. int main(int argc, char *argv[])
  58. {
  59.     int ret = 0;
  60.     int fd_spi = 0;
  61.     int fd_gpio = 0;
  62.     long led_value = 0;
  63.     int led_num = 0;
  64.     int i;
  65.     int disData[4];
  66.     
  67.     if (argc != 2) { /* 输入参数必须为一个 */
  68.         printf("cmd : ./spi_led_test led_num \n ");
  69.         return -1;
  70.     }
  71.     
  72.     led_value = atoi(argv[1]);     /* 获取程序输入参数的数码管的显示值 */
  73.     if ((led_value) < 0 || (led_value > 10000)) { /* 该值必须在 0 ~ 9 之间 */
  74.         printf("led num just in 0 ~ 9999 \n");
  75.         return -1;
  76.     }
  77.     disData[0] = led_value/1000;
  78.     disData[1] = (led_value%1000)/100;
  79.     disData[2] = (led_value%100)/10;
  80.     disData[3] = led_value%10;
  81.     
  82.     fd_spi = open(SPI_DEVICE, O_RDWR); /* 打开 SPI 总线的设备文件 */
  83.     if (fd_spi < 0) {
  84.         printf("can't open %s \n", SPI_DEVICE);
  85.         return -1;
  86.     }
  87.     
  88.     fd_gpio = open(GPIO_DEVICE, O_RDWR); /* 打开 GPIO 设备的属性文件 */
  89.     if (fd_gpio < 0) {
  90.         printf("can't open %s device\n", GPIO_DEVICE);
  91.         return -1;
  92.     }
  93.     
  94.     /*
  95.     * 这里 mode 的值为 0,这里SPI 总线的 SPI_CLK 在上升沿阶段,
  96.     * SPI_DIN 的信号有效,
  97.     * 这符合 74HC595 芯片把输入数据送入移位寄存器的要求。
  98.     */
  99.     ret = ioctl(fd_spi, SPI_IOC_WR_MODE, &mode);
  100.     if (ret == -1) {
  101.         printf("can't set wr spi mode\n");
  102.         return -1;
  103.     }
  104.     
  105.     ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &bits); /* 设置 SPI 的数据位 */
  106.     if (ret == -1) {
  107.         printf("can't set bits per word\n");
  108.         return -1;
  109.     }

  110.     ret = ioctl(fd_spi, SPI_IOC_WR_MAX_SPEED_HZ, &speed); /* 设置 SPI 的最大总线频率 */
  111.     if (ret == -1) {
  112.         printf("can't set max speed hz\n");
  113.         return -1;
  114.     }

  115.     while(1)
  116.     {
  117.         for(i = 0 ; i < 4; i++)
  118.         {
  119.             show_led_num(fd_spi, fd_gpio, disData[i], i); /* 实现数码管的控制 */    
  120.             usleep(100);
  121.         }
  122.     }

  123.     //show_led_num(fd_spi, fd_gpio, led_value, led_num); /* 实现数码管的控制 */
  124.     close(fd_spi);
  125.     
  126.     return ret;
  127. }


    3. 测试
    3.1 可以单独控制某个数码管
        上述代码可以通过交叉编译成 spi_led_test 程序文件,测试方法如下:
        (1) 把 spi_led_test 文件上传到 EasyARM-i.MX283A 的任意目录;
        (2) 在 AP-283Demo 板上的 J11A 和 J11C 的 CS、 CLK、 MISO、 MOSI 跳线用短路器短
            接, 把 J7A 和 J7B 的 WCLK 跳线用短路器短接。
        (3) 导出 GPIO3.21 的设备属性文件,并设置为出输出工作模式:
            root@EasyARM-iMX283 /sys/class/gpio# echo 117 > export
            root@EasyARM-iMX283 /sys/class/gpio# ls
                export       gpio68       gpiochip128  gpiochip64   unexport
                gpio117      gpiochip0    gpiochip32   gpiochip96
            root@EasyARM-iMX283 /sys/class/gpio# echo out > gpio117/direction

        (4) 运行 spi_led_test 程序:
            root@EasyARM-iMX28x /mnt# ./spi_led_test 9 2
            该命令将控制数码管的第 3 个数字显示 9。
            
    3.2 修改代码,让四个数码管同时显示,输入的命令为:
        ./spi_led_test number   // number就是在数码管上显示的数字,比如如果为1234,
                                // 则在数码管上显示的就是 1234 。
                                
    3.3 秒表的实现                        
        有空再修改吧....
阅读(5782) | 评论(0) | 转发(0) |
0

上一篇:2nd_gpio编程

下一篇:4th_用户态 I2C 编程

给主人留下些什么吧!~~