[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-06 TTC定时器

文档创建者:LINUX课程
浏览次数:321
最后更新:2024-09-09
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-11 11:06 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 概述
本章将会讲述关于TTC定时器的相关内容,学习linux内核提供的定时器api函数,通过这些api函数可以完成很多要求的定时应用。
实验目的:
  • 掌握linux定时器的驱动的编写。
  • 了解MPSOC的定时器功能特性。
  • 了解TTC定时器的寄存器。
2 系统框图
        在开始之前,我们先来看下PS端的系统定时器。PS端有很多不同类型的定时器和计数器。
image.jpg
Zynq的PS的CPU0和CPU1各包含1个私有定时器以及一个私有看门狗定时器并且共享一个全局定时器。另外Zynq的PS还有TTC0以及TTC1定制控制器。每个TTC定时器里面包含三个定时器/计数器。本文所讲解的是CPU0的私有定时器中断的使用。
本方案中只对CPU0的私有定时器进行说明。
        Zynq的私有定时器和看门狗定时器具有以下共同的特性:
1-定时计数器位宽为32位,当达到0时产生中断
2-参考时钟为CPU时钟的1/2,比如CPU是666.6666M 那么定时器的参考时钟为333.3333M
3-有一个8bit预分频器,可以对参考时钟进一步分频
4-有一个可配置的预加载寄存器
5-可配置为单次模式或者自动重载模式
3 介绍
3.1 中断资源概述
image.jpg
PS的CPU0和CPU1各有1个私有定时器。上图中,红色部分为PS的私有中断资源,本文中demo就是以CPU0的定时器中断使用作为演示方案。CPU0的定时器中断经过中断控制派发器派发给CPU0。CPU0的私有定时器IRQ ID=29。
image.jpg
3.2 私有定时器的寄存器
在UG585中没有关于私有定时器的详细说明,我们通过SDK代码的理解补充了这部分说明。私有定时器的基地址为0xF8F00600。CPU的私有定时器一共有4个寄存器:
XSCUTIMER_LOAD寄存器XSCUTIMER_LOAD_OFFSET        (0x00U)
Field Name
Bits
Type
Reset Value
Description
XSCUTIMER_LOAD
31:0
至少
可写
0x0
私有定时器的预加载值
XSCUTIMER_COUNTER寄存器XSCUTIMER_COUNTER_OFFSET        (0x04U)
Field Name
Bits
Type
Reset Value
Description
XSCUTIMER_COUNTER
31:0
至少
可读
0x0
私有定时器的计数器
XSCUTIMER_CONTROL寄存器XSCUTIMER_CONTROL_OFFSET        (0x08U)
Field Name
Bits
Type
Reset Value
Description
XSCUTIMER_CONTROL
31:0
R/W
0x0
私有定时器的控制寄存器
15~8bit:预分频设置
2bit:当为1中断使能
1bit:当为1自动重载使能
0bit:当为1表示定时器使能
XSCUTIMER_ISR寄存器XSCUTIMER_ISR_OFFSET        (0x0CU)
Field Name
Bits
Type
Reset Value
Description
XSCUTIMER_ISR
31:0
R/W
0x0
私有定时器的中断寄存器
0bit:当为1的时候表示发生中断
3.3 节拍与延时时间的互换
        系统之所以能够记录时间,是因为每个系统都有一个节拍器。在已经部署好开发环境的虚拟机的uisrc-lab-xlnx目录中打开终端输入“source scripts/mzuxcfg.sh”之后,再输入“make_kernel_menuconfig.sh”。可以调整内核中的节拍频率。
image.jpg
在这里我们设置了250HZ的频率,也就是1秒250个节拍,节拍约高精度约高,但是同样过高的频率也会导致过高的CPU占用,因此除非是需要高精度,100HZ或250HZ已经足矣。
image.jpg
4 搭建工程
4.1 vivado工程的搭建
详细的vivado搭建参考“[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-01 Hello World”vivadovitis工程搭建。如果使用的是附件中的工程生成的系统文件,或者使用的是附件中提供的相关的boot文件夹中的文件。那么,就无需改动vivado工程的设置。
image.jpg
4.2 上传文件至虚拟机
        将文件“Timer”上传至虚拟机,可以使用U盘,scp,vm-tools等一系列方式实现。
image.jpg
至此准备工作,已经完成。
5 程序分析
5.1 驱动程序分析
  1. //添加头文件
  2. #include <linux/init.h>
  3. #include <linux/module.h>
  4. #include <linux/ide.h>
  5. #include <linux/cdev.h>
  6. #include <linux/timer.h>

  7. //设置一个设备全局变量
  8. struct timer_device
  9. {
  10.     dev_t devno;
  11.     struct cdev cdev;
  12.     struct class *class;
  13.     struct device *device;
  14.     unsigned long count;
  15.     struct timer_list timer;
  16. };
  17. struct timer_device timer_dev;

  18. //timer回调函数,倒计时结束后会运行回调函数
  19. static void timer_callback(struct timer_list *timer)
  20. {
  21.     printk(KERN_CRIT "timer_callback.\n");
  22. }

  23. //打开设备函数
  24. static int timer_open(struct inode *inode, struct file *filp)
  25. {
  26.     printk("timer_open success.\n");
  27.     return 0;
  28. }
  29. //接收用户空间传来的数据
  30. static ssize_t timer_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
  31. {
  32.     int flag = 0, i = 0; //flag用来判断是否读取成功,i为一个计数器
  33.     unsigned long temp;  //用来临时存放计算值
  34.     timer_dev.count = 0;
  35.     flag = copy_from_user(&temp, buf, count); //使用copy_from_user读取用户态发送过来的数据
  36.     if (flag != 0)
  37.     {
  38.         printk("Kernel receive data failed!\n");
  39.         return 1;
  40.     }
  41.     //将char*转为unsigned long
  42.     for (i = 0; i < count; i++)
  43.     {
  44.         timer_dev.count *= 10;
  45.         timer_dev.count += temp % 256 - 48;
  46.         temp = temp >> 8;
  47.     }
  48.     printk(KERN_CRIT "wait time = %ld\n", timer_dev.count);
  49.     //设置定时器的延时
  50.     mod_timer(&timer_dev.timer, jiffies + msecs_to_jiffies(timer_dev.count));
  51.     return 0;
  52. }
  53. //关闭设备函数
  54. static int timer_release(struct inode *inode, struct file *filp)
  55. {
  56.     printk("timer_release success.\n");
  57.     return 0;
  58. }

  59. static struct file_operations timer_fops = {
  60.     .owner = THIS_MODULE,
  61.     .open = timer_open,
  62.     .write = timer_write,
  63.     .release = timer_release,
  64. };

  65. //实现装载入口函数
  66. static __init int timer_drv_init(void)
  67. {
  68.     int ret = 0;
  69.     printk("timer_drv_init start.\n");

  70.     //动态申请设备号
  71.     ret = alloc_chrdev_region(&timer_dev.devno, 0, 1, "timer_device");
  72.     if (ret < 0)
  73.     {
  74.         printk("alloc_chrdev_region fail!\n");
  75.         return 0;
  76.     }

  77.     //设备初始化
  78.     cdev_init(&timer_dev.cdev, &timer_fops);
  79.     timer_dev.cdev.owner = THIS_MODULE;

  80.     //申请创建设备类
  81.     timer_dev.class = class_create(THIS_MODULE, "timer_class");
  82.     if (IS_ERR(timer_dev.class))
  83.     {
  84.         printk("class_create fail!\n");
  85.         return 0;
  86.     }

  87.     //建立设备节点
  88.     timer_dev.device = device_create(timer_dev.class, NULL, timer_dev.devno, NULL, "timer_device");
  89.     if (IS_ERR(timer_dev.device))
  90.     {
  91.         printk("device_create fail!\n");
  92.         return 0;
  93.     }

  94.     //向系统注册一个字符设备
  95.     cdev_add(&timer_dev.cdev, timer_dev.devno, 1);

  96.     //初始化定时器
  97.     timer_setup(&timer_dev.timer, timer_callback, 0);

  98.     printk("timer_drv_init success.\n");
  99.     return 0;
  100. }

  101. //实现卸载入口函数
  102. static __exit void timer_drv_exit(void)
  103. {
  104.     printk("timer_drv_exit start.\n");
  105.     //删除定时器
  106.     del_timer_sync(&timer_dev.timer);
  107.     //注销字符设备
  108.     cdev_del(&timer_dev.cdev);
  109.     //删除设备节点
  110.     device_destroy(timer_dev.class, timer_dev.devno);
  111.     //删除设备类
  112.     class_destroy(timer_dev.class);
  113.     //注销设备号
  114.     unregister_chrdev_region(timer_dev.devno, 1);
  115.     printk("timer_drv_exit success.\n");
  116. }

  117. //声明装载入口函数和卸载入口函数
  118. module_init(timer_drv_init);
  119. module_exit(timer_drv_exit);

  120. //添加GPL协议
  121. MODULE_LICENSE("GPL");
  122. MODULE_AUTHOR("msxbo");
复制代码
行8~18,设置了一个设备的全局变量,里面包含了设备的所有属性。
行20~24,计时器的回调函数,用来处理定时器结束时触发的。
行33~55,驱动程序读取操作,先拷贝用户空间数据,然后转换类型,最后通过mod_timer函数设置定时器。
行63~68,驱动程序接口。
行70~112,驱动入口函数,完成的任务有申请设备号,设备初始化,创建设备类,建立设备节点,注册字符设备,初始化定时器。
行114~129,驱动出口函数,与入口函数中的步骤相反,一步步注销申请的资源。
1:timer_list结构体
  1. struct timer_list {
  2.     struct hlist_node entry;
  3.     unsigned long  expires;
  4.     void   (*function)(struct timer_list *);
  5.     u32   flags;
  6. #ifdef CONFIG_LOCKDEP
  7.     struct lockdep_map lockdep_map;
  8. #endif
  9. };
复制代码
含义:该结构体规定了一个定时器所需要的各个参数。
2:timer_setup函数
  1. #define timer_setup(timer, callback, flags)
复制代码
含义:定时器初始化函数,内容包括了初始化定时器、绑定回调函数。初始化另外还有init_timer()函数和add_timer()函数,这一对函数组合适用和本函数的效果是一样的,所以我们使用timer_setup函数来代替繁琐的设置工作。
具体分析:
  • timer:9.3.1中的timer_list结构体
  • callback:回调函数,在定时器倒计时结束时会自动调用
  • flags:TIMER_*标志
  • 返回值:无
3:del_timer函数
  1. int del_timer(struct timer_list * timer)
复制代码
含义:该函数是与初始化对应的注销函数,这个函数会删除一个定时器,但是定时器可能运行在其他处理器上,所以在调用这个函数的时候需要确定这个定时器已经退出。
具体分析:
  • timer:初始化完成的timer_list结构体
  • 返回值:0,定时器还没被激活。1,定时器已经激活。
4:del_timer_sync函数
  1. int del_timer(struct timer_list * timer)
复制代码
含义:这是del_timer 函数的sync版本,区别就是在注销时,不需要手动去确认定时器是否已经退出,它会自动等待定时器退出后注销定时器,一般我们采用这个函数来编写我们的驱动。
具体分析:
  • timer:初始化完成的timer_list结构体
  • 返回值:0,定时器还没被激活。1,定时器已经激活。
5:mod_timer函数
  1. int del_timer_sync(struct timer_list *timer)
复制代码
含义:用来修改定时器的值,设置的同时还会启动定时器。
具体分析:
  • timer:设定好的timer_list结构体
  • expires:超时时间
  • 返回值:0,调用 mod_timer 函数前定时器未被激活。1,调用 mod_timer 函数前定时器已被激活。
6:节拍与时间互换的函数
  1. /*毫秒互换*/
  2. int jiffies_to_msecs(const unsigned long j)
  3. long msecs_to_jiffies(const unsigned int m)
  4. /*微秒互换*/
  5. int jiffies_to_usecs(const unsigned long j)
  6. long usecs_to_jiffies(const unsigned int u)
  7. /*纳秒互换*/
  8. u64 jiffies_to_nsecs(const unsigned long j)
  9. unsigned long nsecs_to_jiffies(u64 n)
复制代码
含义:将节拍与毫秒,微秒,纳秒互换的相关函数。
5.2 应用程序分析
  1.    #include <stdio.h>
  2.    #include <unistd.h>
  3.    #include <sys/types.h>
  4.    #include <sys/stat.h>
  5.    #include <fcntl.h>
  6.    #include <stdlib.h>
  7.    #include <string.h>

  8.    int main(int argc, char *argv[])
  9. {
  10.     int fd, ret = 0;
  11.     char *filename;
  12.     char writebuf[10] = {0};

  13.     filename = argv[1];

  14.     fd = open(filename, O_RDWR); //打开设备
  15.     if (fd < 0)
  16.     {
  17.         printf("Can't open file %s\n", filename);
  18.         return -1;
  19.     }

  20.     memcpy(writebuf, argv[2], strlen(argv[2])); //将内容拷贝到缓冲区
  21.     ret = write(fd, writebuf, strlen(argv[2])); //写数据
  22.     if (ret < 0)
  23.     {
  24.         printf("Write file %s failed!\n", filename);
  25.     }
  26.     else
  27.     {
  28.         printf("Write file success!\n");
  29.     }

  30.     ret = close(fd); //关闭设备
  31.     if (ret < 0)
  32.     {
  33.         printf("Can't close file %s\n", filename);
  34.         return -1;
  35.     }

  36.     return 0;
  37. }
复制代码
第24行将从终端输入的第二个参数复制进writebuff中。
第25行把writebuff中的内容写入到文件描述符fd中。
写入成功后关闭设备。
6 程序编译
在已部署好开发环境的虚拟机上进行交叉编译。
6.1 Makefile文件分析
  1. #已经编译过的内核源码路径
  2. KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel

  3. export ARCH=arm
  4. export CROSS_COMPILE=arm-linux-gnueabihf-

  5. #当前路径
  6. CURRENT_DIR = $(shell pwd)

  7. MODULE =  ttc_drv
  8. APP = ttc_app

  9. all :
  10. #进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
  11. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
  12. rm -rf *.mod.c *.mod.o *.symvers *.order *.o
  13. ifneq ($(APP), )
  14. $(CROSS_COMPILE)gcc $(APP).c -o $(APP)
  15. endif

  16. clean :
  17. make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
  18. rm -rf $(APP) *.tmp

  19. #指定编译哪个文件
  20. obj-m += $(MODULE).o
复制代码
详细内容参考第一章“在虚拟机上进行交叉编译”的相关内容。
如果是自定义的文件名,在第10行和第11行,可以进行修改。
7 演示
7.1 硬件准备
SD2.0 启动 01 而模式开关为 ON OFF(7100 需要先将系统烧录进qspi,然后才能从qspi启动sd卡,“[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-04 从vitis移植Ubuntu实现二次开发”)
2f5038eb9880afd532753935815b079.jpg
将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。
image.jpg
7.2 程序准备
将驱动与程序上传至开发板,查看文件是否传输成功。
在终端输入“ls”命令。确认当前所需文件,已经被上传到当前的文件夹内了。
image.jpg
然后在root 状态下 insmod 安装驱动。“lsmod”查看当前安装的驱动。
image.jpg
在管理员权限下,终端输入“chmod 777 ttc_app”,改变应用程序的权限。
image.jpg
通过以上操作可以确定驱动已经安装,并且应用程序可以运行。
7.3 实验结果
在终端输入“./ttc_app /dev/timer_device 3000”,三秒后定时器触发。
image.jpg
测试成功。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则