一、题目
BSP模块验证初体验:测量A28timer
模块的精度
测量方式简介:通过timer模块完成指定延时计数之后触发中断,在中断处理中修改gpio管脚输出的电平,然后在示波器上抓获gpio变化的时间点,测量出timer的实际延时值,与理论设计的延时值进行比较,从而标定timer延时的精度。
二、实现
小组设计了内核模块measure
,用于实现以上功能,该测量模块的文件树如下图所示。
开发板上电启动后,会自动执行程序。measure.c
实现了以下功能:调用了timer计时器,开始计时20ms,计时器计时结束后触发中断并执行回调函数,回调函数则输出“Time is over”
的提示,并切换GPIO30
管脚的电平输出,每次切换电平成功后会输出“==========Modify successfully==========30”
的提示,30
即为GPIO管脚号。随后计时器再次启动循环执行上述步骤,因此实现了每隔20ms的时间切换一次电平,可以向示波器输出周期为40ms的波形,测量结果如下图所示。
理论时延 = 20.00 m s 测量时延 = 60.40 m s − 39.60 m s = 20.80 m s 相对偏差 = ( 20.80 m s − 20.00 m s ) ÷ 20.00 m s = 4.00 % 精度 Δ X = 测量值 X O − 理论值 X A = 0.80 m s \begin{aligned} & 理论时延=20.00ms\\ & 测量时延=60.40ms-39.60ms=20.80ms\\ & 相对偏差=(20.80ms-20.00ms)÷20.00ms=4.00\%\\ & 精度ΔX=测量值XO-理论值XA=0.80ms \end{aligned} 理论时延=20.00ms测量时延=60.40ms−39.60ms=20.80ms相对偏差=(20.80ms−20.00ms)÷20.00ms=4.00%精度ΔX=测量值XO−理论值XA=0.80ms
三、源码
measure.c
#include <linux/io.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#define MODULE_NAME "measure"
#define INTERVAL 10
#define GPIO_PIN 30
#define H_LEVEL 1
#define L_LEVEL 0
int level=0;//记录当前的电平
static struct timer_list measure_timer;
//回调函数,计时结束时触发中断后执行
static void measure_timer_callback(unsigned long data)
{
printk(KERN_INFO "Time is over\n");
//修改GPIO输出
if(level==H_LEVEL){
gpio_set_value(GPIO_PIN, L_LEVEL);
level=0;
}
else{
gpio_set_value(GPIO_PIN, H_LEVEL);
level=1;
}
printk(KERN_ERR "==========Modify successfully========== %d\n", GPIO_PIN);
mod_timer(&measure_timer, jiffies + msecs_to_jiffies(INTERVAL));//再次启动计时器
}
static int __init measure_init(void)
{
// 初始化GPIO管脚
if (gpio_request(GPIO_PIN, "my_gpio") < 0) {
printk(KERN_ERR "Failed to request GPIO %d\n", GPIO_PIN);
return -1;
}
// 设置GPIO管脚为输出模式
if (gpio_direction_output(GPIO_PIN, L_LEVEL) < 0) {//一开始输出低电平
printk(KERN_ERR "Failed to set GPIO direction\n");
gpio_free(GPIO_PIN);
return -1;
}
setup_timer(&measure_timer, measure_timer_callback, 0);//初始化定时器
mod_timer(&measure_timer, jiffies + msecs_to_jiffies(INTERVAL));//设置定时器的触发时间
printk(KERN_INFO "measure module loaded\n");
return 0;
}
static void __exit measure_exit(void)
{
// 释放GPIO管脚
gpio_free(GPIO_PIN);
del_timer(&measure_timer);
printk(KERN_INFO "measure module unloaded\n");
}
module_init(measure_init);
module_exit(measure_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Group 4");
MODULE_DESCRIPTION("measure Module");
PS:代码电平切换部分其实可以通过反转的方式更快地实现。即
~1=0
,~0=1
。
外部Makefile
#
# Copyright (C) 2008-2012 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=measure
PKG_RELEASE:=3
PKG_LICENSE:=GPL-2.0
include $(INCLUDE_DIR)/package.mk
define KernelPackage/measure
SUBMENU:=Other modules
TITLE:=Simple GPIO Button Hotplug driver
FILES:=$(PKG_BUILD_DIR)/measure.ko
AUTOLOAD:=$(call AutoLoad,30,measure,1)
KCONFIG:=
endef
define KernelPackage/measure/description
This is a replacement for the following in-kernel drivers:
1) gpio_keys (KEYBOARD_GPIO)
2) gpio_keys_polled (KEYBOARD_GPIO_POLLED)
Instead of generating input events (like in-kernel drivers do) it generates
uevent-s and broadcasts them. This allows disabling input subsystem which is
an overkill for OpenWrt simple needs.
endef
define Build/Compile
$(KERNEL_MAKE) M="$(PKG_BUILD_DIR)" modules
endef
$(eval $(call KernelPackage,measure))
内部Makefile
obj-m += measure.o