跳到主要内容位置

超声波测距模块

原理#

SR04 模块上面有四个引脚,分别为:VCC、Trig、Echo、GND。

  • Trig 是脉冲触发引脚,即控制该脚让 SR04 模块开始发送超声波。
  • Echo 是回响接收引脚,即 SR04 模块一旦接收到超声波的返回信号则输出回响信号,回响信号的脉冲宽度与所测距离成正比。

距离计算公式

D(cm)=340×1002×109×T(ns)D(cm)=\frac{340 \times 100}{ 2 \times 10^9 } \times T (ns)

引脚图

代码#

GPIO4_19,GPIO4_20编号如下

static struct gpio_desc gpios[2] = {
{
115,
0,
"trig",
1,
}, // GPIO4_19
{
116,
0,
"echo",
2,
},
};

在init函数中初始化trig引脚为gpio输出模式,echo引脚申请中断;出口函数中,free_irq和gpio_free

static int __init sr04_drv_init(void) {
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
// trig pin
err = gpio_request(gpios[0].gpio, gpios[0].name);
gpio_direction_output(gpios[0].gpio, 0);
// echo pin
gpios[1].irq = gpio_to_irq(gpios[1].gpio); //把引脚号转换为中断号,
err = request_irq(gpios[1].irq, sr04_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[1].name,
&gpios[1]);
/* 注册 file_operations */
major =
register_chrdev(0, "100ask_gpio_sr04", &sr04_drv); /* /dev/gpio_desc */
gpio_class = class_create(THIS_MODULE, "100ask_gpio_sr04_class");
if (IS_ERR(gpio_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "100ask_gpio_sr04");
return PTR_ERR(gpio_class);
}
device_create(gpio_class, NULL, MKDEV(major, 0), NULL,
"sr04"); /* /dev/sr04 */
return err;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
*/
static void __exit sr04_drv_exit(void) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(gpio_class, MKDEV(major, 0));
class_destroy(gpio_class);
unregister_chrdev(major, "100ask_gpio_sr04");
// echo pin
free_irq(gpios[1].irq, &gpios[1]);
// trig pin
gpio_free(gpios[0].gpio);
}

read函数不变,添加.unlocked_ioctl到file_operations结构体中

static long sr04_ioctl(struct file *filp, unsigned int command,
unsigned long arg) {
// send trig
switch (command) {
case CMD_TRIG: {
gpio_set_value(gpios[0].gpio, 1);
udelay(20);
gpio_set_value(gpios[0].gpio, 0);
}
}
return 0;
}
/* 定义自己的file_operations结构体 */
static struct file_operations sr04_drv = {
.owner = THIS_MODULE,
.read = sr04_drv_read,
.poll = sr04_drv_poll,
.fasync = sr04_drv_fasync,
.unlocked_ioctl = sr04_ioctl,
};

修改gpio中断函数

static irqreturn_t sr04_isr(int irq, void *dev_id) {
struct gpio_desc *gpio_desc = dev_id;
int val;
static u64 rising_time = 0;
val = gpio_get_value(gpio_desc->gpio);
if (val) //上升沿记录开始时间
{
rising_time = ktime_get_ns();
}
else //下降沿记录结束时间,并计算时间差和距离
{
if(rising_time == 0)
{
printk("missing rising interrupt\n"); //错过了上升沿的时间
return IRQ_HANDLED;
}
rising_time = ktime_get_ns() - rising_time;
put_key(rising_time);
rising_time = 0;
wake_up_interruptible(&gpio_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
return IRQ_HANDLED;
}

实验:

# 查看中断
cat /proc/interrupts
# 查看引脚
cat /sys/kernel/debug/gpio

丢失中断:printk函数可能会导致丢失中断

注意:

  • 不要在中断处理函数里执行printk
  • 不在ioctl发出trig信号后进行printk,在sr04_read里也不要printk
  • APP不要频繁地调用ioctl发出地trig信号

改进#

因为硬件故障失去上升沿或者下降沿,会导致卡死在drv的read函数

从APP角度修改#

加入poll机制,在读之前先poll一下。

/* app */
while (1)
{
ioctl(fd, CMD_TRIG);
/* poll */
fds[0].fd = fd;
fds[0].events = POLLIN;
if(1 == poll(fds, 1, 5000))
{
/* 读取引脚值 */
if (read(fd, &val, 4) == 4)
printf("get distance: %d cm\n", val * 17/1000000);
else
printf("get distance err\n");
}
else
{
printf("get distance poll timeout/err\n");
}
sleep(1);
}

在drv_poll里面和之前一样,把进程(?)放入等待队列,返回状态。休眠和返回状态在sys_poll中实现。

static unsigned int sr04_drv_poll(struct file *fp, poll_table *wait) {
// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &gpio_wait, wait);
return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

从驱动程序修改#

在ioctl里,发出触发信号,并且启动定时器,如果echo中断没有正常唤醒read函数,就通过定时器唤醒。

05_drivers_from_template1/04_SR04_improved/

想使用定时器,需要先在init函数里初始化定时器

/* 在入口函数 */
static int __init sr04_drv_init(void) {
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
// trig pin
...
// echo pin
...
// init timer for echo timer
setup_timer(&gpios[1].key_timer, echo_timer_expire, (unsigned long)&gpios[1]);
gpios[1].key_timer.expires = ~0;
add_timer(&gpios[1].key_timer);
/* 注册file_operations */
...
return err;
}
/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
*/
static void __exit sr04_drv_exit(void) {
...
// echo pin
free_irq(gpios[1].irq, &gpios[1]);
del_timer(&gpios[1].key_timer);
// trig pin
...
}

在ioctl函数里面开始定时器计时,延迟50ms

// ioctl(fd, CMD, ARG)
static long sr04_ioctl(struct file *filp, unsigned int command,
unsigned long arg) {
// send trig
switch (command) {
case CMD_TRIG: {
// send trig
gpio_set_value(gpios[0].gpio, 1);
udelay(20);
gpio_set_value(gpios[0].gpio, 0);
// start timer
mod_timer(&gpios[1].key_timer, jiffies + msecs_to_jiffies(50)); //50ms jiffies + HZ/20
}
}
return 0;
}

如果硬件中断正常,上升沿下降沿正常,就在gpio中断函数中取消定时器,

static irqreturn_t sr04_isr(int irq, void *dev_id) {
struct gpio_desc *gpio_desc = dev_id;
int val;
static u64 rising_time = 0;
val = gpio_get_value(gpio_desc->gpio);
if (val) //上升沿记录开始时间
{
rising_time = ktime_get_ns();
}
else //下降沿记录结束时间,并计算时间差和距离
{
if(rising_time == 0)
{
printk("missing rising interrupt\n"); //错过了上升沿的时间
return IRQ_HANDLED;
}
// stop timer
del_timer(&gpios[1].key_timer);
// save echo time and wake up read function
...
}
return IRQ_HANDLED;
}

否则,会进入定时器中断:放入-1表示超时,唤醒read函数。

static void echo_timer_expire(unsigned long data)
{
put_key(-1);
wake_up_interruptible(&gpio_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

read函数判断读出来的值是否是-1,-1表示没有数据返回

/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t sr04_drv_read(struct file *file, char __user *buf, size_t size,
loff_t *offset) {
// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int err;
int key;
if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
wait_event_interruptible(gpio_wait, !is_key_buf_empty());
key = get_key();
if (key == -1) //no data
{
return -ENODATA;
}
err = copy_to_user(buf, &key, 4);
return 4;
}

请点击左侧菜单(移动端为右下角)选择要查看的所有笔记吧。

最近更新于