Linux 一种调用蜂鸣器的方法

最近想试试能不能把蜂鸣器精度提高到微秒级,然后学习了一下 ioctl 的代码……

和系统中断次数有关……也就是说现在很难提高到微秒级。然后使用了较为底层的实现,发现跑 10000 次还是要 0.6s ……

终究败在了 io 速度上……?(当然我不太懂内核,如果有 dalao 知道具体的原因请教教我)

不过还是发现了一个较为奇特的写法。

首先我用的驱动是 pcspkr,然后我去 /drivers/input/misc/pcspkr.c 里找到了这么一段代码(撰写该文的时候版本为 5bfc75d92e):

static int pcspkr_event(struct input_dev *dev, unsigned int type,
			unsigned int code, int value)
{
	...
	if (count) {
		/* set command for counter 2, 2 byte write */
		outb_p(0xB6, 0x43);
		/* select desired HZ */
		outb_p(count & 0xff, 0x42);
		outb((count >> 8) & 0xff, 0x42);
		/* enable counter 2 */
		outb_p(inb_p(0x61) | 3, 0x61);
	} else {
		/* disable counter 2 */
		outb(inb_p(0x61) & 0xFC, 0x61);
	}
	...
	return 0;
}

然后发现和 dev 无关,这意味着我们只需要有 outb 等函数即可。

于是我们就可以魔改出一个 beep 函数(当然我没加锁):

#include <unistd.h> // usleep
#include <linux/input.h> // SND_BELL, SND_TONE
#include <sys/io.h> // ioperm, inb_p, outb, outb_p
#include <iostream>
#include <vector>

#define PIT_TICK_RATE 1193182

int beep(unsigned int code, int value) {
	unsigned int count = 0;

	switch (code) {
		case SND_BELL:
			if (value) value = 1000;
			break;
		case SND_TONE: break;
		default: return -1;
	}

	if (value > 20 && value < 32767)
		count = PIT_TICK_RATE / value;

	if (count) {
		outb_p(0xB6, 0x43);
		outb_p(count & 0xff, 0x42);
		outb((count >> 8) & 0xff, 0x42);
		outb_p(inb_p(0x61) | 3, 0x61);
	} else {
		outb(inb_p(0x61) & 0xFC, 0x61);
	}

	return 0;
}

int main() {
	std::vector<int> init{0x42, 0x43, 0x61, 0x80};
	for (auto t : init) {
		int ret = ioperm(t, 1, 1);
		std::cout << std::hex << t << " : " << ioperm(t, 1, 1) << std::endl;
	}
	beep(SND_BELL, 1000); // 开始响
	beep(SND_TONE, 600); // 调声高
	usleep(1000000);
	beep(SND_BELL, 0); // 停止
	return 0;
}

注意到要使用 outb 等函数之前要 ioperm,而我这边库的 outb_p 实现中还用到了 0x80。这样我们就可以在用户态中使用 outb 这种函数了!

然后还是要注意这东西需要管理员权限……我也不知道人家 beep 程序是怎么写的,怎么做到不用管理员权限的……当然想学一下辣

然后就整了这么一个好像没啥用的东西。

posted @ 2021-06-24 08:47  daklqw  阅读(612)  评论(0编辑  收藏  举报