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 程序是怎么写的,怎么做到不用管理员权限的……当然想学一下辣
然后就整了这么一个好像没啥用的东西。