IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> IMX6ULL实现linux系统下用hrtimer扫描8位数码管 -> 正文阅读

[嵌入式]IMX6ULL实现linux系统下用hrtimer扫描8位数码管

linux系统后台有很多任务,如果我们想要在嵌入式linux环境下实现扫描数码管,并且要求任何情况下,无论linux系统在干什么事情,数码管都不能闪烁,只要肉眼看到一次闪烁就不合格,那就必须采用定时器中断的方式扫描。利用定时器的中断扫描8位数码管(由两片74HC595驱动),定时器中断每触发一次,就点亮一位数码管。

正点原子的教程上讲了linux下的struct timer_list定时器,这个定时器是基于系统tick值的,最小时间单位是10ms,而且实际还有更大的误差,这显然不能满足要求,用这个频率扫描数码管太慢了,数码管闪烁严重!
笔者又试了IMX6ULL的GPT2定时器,也不行。实际测试发现,Linux系统越繁忙,数码管扫描速度越快。系统越空闲,数码管扫描速度越慢。GPT2定时器的运行速度由Linux系统的繁忙程度决定。例如,串口命令行空闲时,数码管扫描速度非常慢,几秒钟才扫描一位。在串口中输入命令时,数码管扫描速度稍快一些,大约一秒钟扫描一位。运行一个冒泡排序的C程序,数码管扫描速度非常快,一秒钟能扫描整个数码管好几遍。按Ctrl+C程序一退出,扫描马上就慢下来了。
为什么IMX6ULL的GPT2定时器选ipg_clk时钟源,计时的快慢由Linux系统的繁忙程度决定?-OpenEdv-开源电子网
最后笔者发现了linux下有hrtimer高精度定时器,最小时间单位是1ns,实测误差在10~60us之间,也就是说设定在t时刻触发中断,实际触发时刻为t+100us到t+150us之间。用这个hrtimer内核定时器扫描数码管,可以实现数码管的动态扫描。
定时器的定时时间要根据实际情况设置,力求做到任何时刻数码管都不能有闪烁。毕竟linux系统内部很复杂,如果定时器定时间隔太短,万一被其他更高优先级的中断打断了,数码管某个数位就会突然很亮,然后又暗下来。(一开始笔者设的500us,就偶尔会有这种情况发生)

CPU型号:MCIMX6Y2CVM08AB
系统环境:uboot2016.03 + linux4.1.15内核 + ubuntu18.04根文件系统
程序利用内核的hrtimer高精度定时器扫描数码管,每2000us扫描一位。效果如下。

程序代码如下。

segdisp_driver.c:

#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/hrtimer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#define SEG_DELAY 2000 // 数码管扫描间隔(us)
#define SEG_DEBUG (SEG_DELAY >= 100000)

// 驱动程序结构体
struct segdisp_state
{
	dev_t devid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct gpio_descs *gpios;
	struct hrtimer timer; // 高精度定时器
	
	int number; // 数码管要显示的数字
	int number_bit; // 数码管正在显示的数位
	int number_tmp; // 数码管正在显示的数字
};

// 设备文件临时结构体
struct segdisp_file
{
	struct segdisp_state *state;
	char content[20]; // 设备文件内容
	int len; // 设备文件内容的长度
};

// 数码管段码表
static const u8 segdisp_table[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

/* 打开设备文件 */
static int segdisp_open(struct inode *inode, struct file *file)
{
	struct segdisp_file *p;
	
	p = kzalloc(sizeof(struct segdisp_file), GFP_KERNEL);
	if (p == NULL)
		return -ENOMEM;
	file->private_data = p;
	
	p->state = container_of(inode->i_cdev, struct segdisp_state, cdev);
	snprintf(p->content, sizeof(p->content), "Number: %d\n", p->state->number);
	p->len = strlen(p->content);
	return 0;
}

/* 关闭设备文件 */
static int segdisp_release(struct inode *inode, struct file *file)
{
	if (file->private_data != NULL)
		kfree(file->private_data);
	return 0;
}

/* 读取设备文件 */
static ssize_t segdisp_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
	int ret;
	struct segdisp_file *p = file->private_data;
	
	if (*off + size > p->len)
		size = p->len - *off;
	if (size > 0)
	{
		ret = copy_to_user(buf, p->content, size);
		if (ret != 0)
			return -EFAULT;
		*off += size;
	}
	return size;
}

/* 写入设备文件 */
static ssize_t segdisp_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	char ch;
	char *s;
	int ret;
	struct segdisp_file *p = file->private_data;
	
	if (size == 0)
		return -EFAULT;
	get_user(ch, &buf[size - 1]);
	if (ch != '\r' && ch != '\n')
		return -EFAULT;
	
	s = kzalloc(size, GFP_KERNEL);
	if (s == NULL)
		return -ENOMEM;
	ret = copy_from_user(s, buf, size - 1);
	if (ret != 0)
	{
		kfree(s);
		return -EFAULT;
	}
	
	p->state->number = simple_strtol(s, NULL, 10);
	if (p->state->number < 0)
		p->state->number = 0;
	else if (p->state->number > 99999999)
		p->state->number = 99999999;
	kfree(s);
	return size;
}

/* 控制设备文件 */
static long segdisp_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	return 0;
}

// 设备文件函数表
static const struct file_operations segdisp_fops = {
	.owner = THIS_MODULE,
	.open = segdisp_open,
	.release = segdisp_release,
	.read = segdisp_read,
	.write = segdisp_write,
	.unlocked_ioctl = segdisp_unlocked_ioctl
};

/* 74HC595串入数据 */
static void segdisp_ser_in(struct segdisp_state *state, u8 data)
{
	int i;
	
	for (i = 0; i < 8; i++)
	{
		gpiod_set_value(state->gpios->desc[0], 0);
		if (data & 0x80)
			gpiod_set_value(state->gpios->desc[2], 1);
		else
			gpiod_set_value(state->gpios->desc[2], 0);
		gpiod_set_value(state->gpios->desc[0], 1);
		data <<= 1;
	}
}

/* 74HC595并出数据(锁存) */
static void segdisp_par_out(struct segdisp_state *state)
{
	gpiod_set_value(state->gpios->desc[1], 0);
	gpiod_set_value(state->gpios->desc[1], 1);
}

/* 高精度定时器中断处理函数 */
// 请注意这个函数是在中断环境下执行的
static enum hrtimer_restart segdisp_timer(struct hrtimer *timer)
{
#if SEG_DEBUG
	ktime_t now, expires, diff;
#endif
	struct segdisp_state *state;
	
	state = container_of(timer, struct segdisp_state, timer);
#if SEG_DEBUG
	// 打印定时器调试信息
	now = ktime_get();
	expires = hrtimer_get_expires(&state->timer);
	diff = ktime_sub(now, expires);
	pr_err("now=%llu, expires=%llu, diff=%lld\n", ktime_to_ns(now), ktime_to_ns(expires), ktime_to_ns(diff));
#endif
	
	// 缓存要显示的数字
	if (state->number_bit == 0)
		state->number_tmp = state->number;
	else
		state->number_tmp /= 10;
		
	// 扫描一次数位
	segdisp_ser_in(state, segdisp_table[state->number_tmp % 10]);
	segdisp_ser_in(state, 1 << state->number_bit);
	segdisp_par_out(state);
	
	// 计算下一次要扫描的数位
	state->number_bit++;
	if (state->number_bit == 8)
		state->number_bit = 0;
		
	// 设定定时器下一次触发的时间
	hrtimer_forward_now(&state->timer, ns_to_ktime(SEG_DELAY * NSEC_PER_USEC));
	return HRTIMER_RESTART; // 声明这是个重复触发的定时器
}

/* 驱动程序入口 */
static int segdisp_probe(struct platform_device *pdev)
{
	int ret;
	struct segdisp_state *state;
	
	// 创建结构体
	dev_err(&pdev->dev, "segdisp_probe(0x%p);\n", pdev);
	state = devm_kzalloc(&pdev->dev, sizeof(struct segdisp_state), GFP_KERNEL);
	if (state == NULL)
	{
		dev_err(&pdev->dev, "out of memory!\n");
		return -ENOMEM;
	}
	platform_set_drvdata(pdev, state);
	
	// 创建设备
	ret = alloc_chrdev_region(&state->devid, 100, 1, "mydeviceid");
	if (ret != 0)
	{
		dev_err(&pdev->dev, "failed to find a device id\n");
		return ret;
	}
	dev_err(&pdev->dev, "Device ID: %d,%d\n", MAJOR(state->devid), MINOR(state->devid));
	cdev_init(&state->cdev, &segdisp_fops);
	ret = cdev_add(&state->cdev, state->devid, 1);
	if (ret != 0)
	{
		dev_err(&pdev->dev, "failed to add cdev device\n");
		goto err_chrdev;
	}
	
	// 创建设备文件
	state->class = class_create(THIS_MODULE, "classname");
	if (IS_ERR(state->class))
	{
		dev_err(&pdev->dev, "failed to create class\n");
		ret = PTR_ERR(state->class);
		goto err_chrdev;
	}
	state->device = device_create(state->class, NULL, state->devid, NULL, "devicename");
	if (IS_ERR(state->device))
	{
		dev_err(&pdev->dev, "failed to create device\n");
		ret = PTR_ERR(state->device);
		goto err_class;
	}
	
	// 获取GPIO端口
	state->gpios = devm_gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW);
	if (IS_ERR(state->gpios))
	{
		dev_err(&pdev->dev, "failed to get gpios\n");
		ret = PTR_ERR(state->gpios);
		goto err_device;
	}
	
	// 初始化定时器
	state->number = 20220921; // 数码管要显示的数字
	hrtimer_init(&state->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); // 初始化定时器结构体
	state->timer.function = segdisp_timer; // 设置定时器中断服务函数
	dev_err(&pdev->dev, "Refresh interval: %dus\n", SEG_DELAY);
	hrtimer_start(&state->timer, ns_to_ktime(SEG_DELAY * NSEC_PER_USEC), HRTIMER_MODE_REL); // 启动定时器
	return 0;
	
err_device:
	device_destroy(state->class, state->devid);
err_class:
	class_destroy(state->class);
err_chrdev:
	cdev_del(&state->cdev);
	unregister_chrdev_region(state->devid, 1);
	return ret;
}

/* 驱动程序出口 */
static int segdisp_remove(struct platform_device *pdev)
{
	struct segdisp_state *state;
	
	dev_err(&pdev->dev, "segdisp_remove(0x%p);\n", pdev);
	state = platform_get_drvdata(pdev);
	if (state != NULL)
	{
		// 关闭定时器并熄灭数码管
		hrtimer_cancel(&state->timer);
		segdisp_ser_in(state, 0xff);
		segdisp_ser_in(state, 0);
		segdisp_par_out(state);
		
		// 删除设备和设备文件
		device_destroy(state->class, state->devid);
		class_destroy(state->class);
		cdev_del(&state->cdev);
		unregister_chrdev_region(state->devid, 1);
		platform_set_drvdata(pdev, NULL);
	}
	return 0;
}

static const struct of_device_id segdisp_match_ids[] = {
	{.compatible = "mygpiotest,myboard2"},
	{}
};

MODULE_DEVICE_TABLE(of, segdisp_match_ids);

static struct platform_driver segdisp_driver = {
	.driver = {
		.name = "mysegdispdriver",
		.of_match_table = of_match_ptr(segdisp_match_ids)
	},
	.probe = segdisp_probe,
	.remove = segdisp_remove
};

module_platform_driver(segdisp_driver);

MODULE_AUTHOR("Oct1158");
MODULE_LICENSE("GPL");

Makefile:

KDIR := /home/oct1158/learn_drivers/kernel

obj-m := segdisp_driver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KDIR) M=$(shell pwd) EXTRA_CFLAGS=-fno-pic modules

clean:
	$(MAKE) -C $(KDIR) M=$(shell pwd) clean
	

myboard.dts(设备树):
(说明:要想使设备树中的pinctrl-*属性生效,这个节点的compatible属性就必须要匹配上某个驱动。所以驱动程序必须写成platform_driver的形式,不能光是module_init,这个在正点原子的教程里面没有说)

#include "imx6ull-14x14-emmc-4.3-800x480-c.dts"

/ {
	mygpiotest {
		compatible = "mygpiotest,myboard2";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_mygpiotest>;
		gpios = <&gpio4 26 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA05, 板上丝印: CAMERA D5  */
		         &gpio4 25 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA04, 板上丝印: CAMERA D4 */
				 &gpio4 28 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA07, 板上丝印: CAMERA D7 */
				 &gpio4 27 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA06, 板上丝印: CAMERA D6 */
		>;
	};
};

&iomuxc {
	imx6ul-evk {
		/delete-node/csi1grp;
		
		pinctrl_mygpiotest: mygpiotestgrp {
			fsl,pins = <
				MX6UL_PAD_CSI_DATA05__GPIO4_IO26 0x10b0 /* 引脚名为CSI_DATA05的引脚复用为GPIO4_IO26 */
				MX6UL_PAD_CSI_DATA04__GPIO4_IO25 0x10b0
				MX6UL_PAD_CSI_DATA07__GPIO4_IO28 0x10b0
				MX6UL_PAD_CSI_DATA06__GPIO4_IO27 0x10b0
			>;
		};
	};
};

/* 删除不需要的节点 */
&i2c2 {
	/delete-node/ov5640@3c;
};
&csi {
	status = "disabled";
	/delete-node/port;
};

编译好程序后,用修改后的设备树重启系统,用insmod命令插入驱动模块,即可看到数码管开始扫描了,显示数字20220921。
用cat命令查看/dev/devicename设备文件的内容,可以看到数码管当前显示的数值。
在root用户下用echo命令修改/dev/devicename设备文件的内容,可以更改数码管显示的数字。

oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$ make
make -C /home/oct1158/learn_drivers/kernel M=/home/oct1158/learn_drivers/dts_myboard2/segdisp_test EXTRA_CFLAGS=-fno-pic modules
make[1]: Entering directory '/home/oct1158/learn_drivers/kernel'
  CC [M]  /home/oct1158/learn_drivers/dts_myboard2/segdisp_test/segdisp_driver.o
  Building modules, stage 2.
  MODPOST 4 modules
  CC      /home/oct1158/learn_drivers/dts_myboard2/segdisp_test/segdisp_driver.mod.o
  LD [M]  /home/oct1158/learn_drivers/dts_myboard2/segdisp_test/segdisp_driver.ko
make[1]: Leaving directory '/home/oct1158/learn_drivers/kernel'
oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$ sudo insmod segdisp_driver.ko
[84835.412446] mysegdispdriver mygpiotest: segdisp_probe(0x940ffe00);
[84835.418668] mysegdispdriver mygpiotest: Device ID: 248,100
[84835.428344] mysegdispdriver mygpiotest: Refresh interval: 2000us
oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$ sudo cat /dev/devicename
Number: 20220921
oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$ sudo su
root@alientek:/home/oct1158/learn_drivers/dts_myboard2/segdisp_test# echo 12345678 > /dev/devicename
root@alientek:/home/oct1158/learn_drivers/dts_myboard2/segdisp_test# echo 12345679 > /dev/devicename
root@alientek:/home/oct1158/learn_drivers/dts_myboard2/segdisp_test#

用rmmod命令移除驱动模块后,数码管熄灭。

oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$ sudo rmmod segdisp_driver
[sudo] password for oct1158:
[87161.766401] mysegdispdriver mygpiotest: segdisp_remove(0x940ffe00);
oct1158@alientek:~/learn_drivers/dts_myboard2/segdisp_test$

将程序中的SEG_DELAY值设为50万(500000),数码管变为0.5秒扫描一次,此时在串口里面可以看到定时器中断的设定触发时间(expires),实际触发时间(now)和误差(diff)。实测误差为10~60us之间。
为什么会有这样的误差呢?笔者推测,是因为CPU执行代码需要一定的时间,从中断触发到执行我们的中断处理函数,再到pr_err打印,刚好需要几十微秒的时间。如果我们把pr_err打印语句再放后面一点,比如放到hrtimer_forward_now语句的前面,就会发现diff值扩大到了100~150us之间。这说明,扫描一次数码管(两个segdisp_ser_in操作再加一个segdisp_par_out操作),就需要100us左右的时间。

[89265.727217] now=89268325619901, expires=89268325598909, diff=20992
[89266.227216] now=89268825640565, expires=89268825598909, diff=41656
[89266.727179] now=89269325619227, expires=89269325598909, diff=20318
[89267.227186] now=89269825641892, expires=89269825598909, diff=42983
[89267.727148] now=89270325618555, expires=89270325598909, diff=19646
[89268.227151] now=89270825639220, expires=89270825598909, diff=40311
[89268.727118] now=89271325619884, expires=89271325598909, diff=20975
[89269.227122] now=89271825640549, expires=89271825598909, diff=41640
[89269.727087] now=89272325621214, expires=89272325598909, diff=22305
[89270.227091] now=89272825640547, expires=89272825598909, diff=41638
[89270.727054] now=89273325620878, expires=89273325598909, diff=21969
[89271.227055] now=89273825639212, expires=89273825598909, diff=40303
[89271.727024] now=89274325620878, expires=89274325598909, diff=21969
[89272.227022] now=89274825635878, expires=89274825598909, diff=36969
[89272.726990] now=89275325620545, expires=89275325598909, diff=21636
[89273.226993] now=89275825639213, expires=89275825598909, diff=40304
[89273.726961] now=89276325621213, expires=89276325598909, diff=22304
[89274.226958] now=89276825634548, expires=89276825598909, diff=35639
[89274.726926] now=89277325621216, expires=89277325598909, diff=22307
[89275.226926] now=89277825635219, expires=89277825598909, diff=36310
[89275.726894] now=89278325619220, expires=89278325598909, diff=20311
[89276.226896] now=89278825639557, expires=89278825598909, diff=40648
[89276.726862] now=89279325619226, expires=89279325598909, diff=20317
[89277.226877] now=89279825653230, expires=89279825598909, diff=54321
[89277.726832] now=89280325620900, expires=89280325598909, diff=21991
[89278.226832] now=89280825639238, expires=89280825598909, diff=40329
[89278.726797] now=89281325622241, expires=89281325598909, diff=23332
[89279.226801] now=89281825639913, expires=89281825598909, diff=41004
[89279.726767] now=89282325618917, expires=89282325598909, diff=20008
[89280.226770] now=89282825639923, expires=89282825598909, diff=41014
[89280.726734] now=89283325620261, expires=89283325598909, diff=21352
[89281.226739] now=89283825641934, expires=89283825598909, diff=43025
[89281.726703] now=89284325620273, expires=89284325598909, diff=21364
[89282.226702] now=89284825636280, expires=89284825598909, diff=37371
[89282.726670] now=89285325618953, expires=89285325598909, diff=20044
[89283.226675] now=89285825641627, expires=89285825598909, diff=42718
[89283.726638] now=89286325623300, expires=89286325598909, diff=24391
[89284.226638] now=89286825636975, expires=89286825598909, diff=38066
[89284.726605] now=89287325620982, expires=89287325598909, diff=22073
[89285.226612] now=89287825640658, expires=89287825598909, diff=41749
[89285.726576] now=89288325620332, expires=89288325598909, diff=21423
[89286.226579] now=89288825642342, expires=89288825598909, diff=43433
[89286.726543] now=89289325621017, expires=89289325598909, diff=22108
[89287.226546] now=89289825641027, expires=89289825598909, diff=42118
[89287.726509] now=89290325618703, expires=89290325598909, diff=19794
[89288.226520] now=89290825646047, expires=89290825598909, diff=47138
[89288.726476] now=89291325622390, expires=89291325598909, diff=23481
[89289.226472] now=89291825630735, expires=89291825598909, diff=31826
[89289.726446] now=89292325620079, expires=89292325598909, diff=21170
[89290.226444] now=89292825635090, expires=89292825598909, diff=36181
[89290.726415] now=89293325621102, expires=89293325598909, diff=22193
[89291.226417] now=89293825639447, expires=89293825598909, diff=40538
[89291.726383] now=89294325619792, expires=89294325598909, diff=20883
[89292.226381] now=89294825636805, expires=89294825598909, diff=37896
[89292.726351] now=89295325621818, expires=89295325598909, diff=22909
[89293.226354] now=89295825641498, expires=89295825598909, diff=42589

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 21:12:15  更:2022-09-24 21:12:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 13:09:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码