orangepi3lts linux驱动HC-SR04超声测距模块

本文介绍了一种基于Linux内核的超声波测距驱动的设计与实现,该驱动使用了位原子操作来解决资源竞争问题,并通过GPIO引脚实现了信号的发送与接收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所用资源

  1. 一个位原子操作符
  2. 内核延时delay.h
  3. timer1硬件计时器
  4. gpiod25用于回响信号,gpiod26用于激发信号
  5. 1个misc混杂设备号

文件组成

  1. supersonic.c内核驱动文件,成功加载后会自动创建/dev/supersonic设备文件
  2. main.c用户空间测试程序
  3. Makefile编译驱动模块和main.c的脚本

文件内容

supersonic.c

/*================================================================
*   文件名称:supersonic.c
*   创 建 者:Less_is_More
*   创建日期:2022年09月30日
*   描    述:用超声波测量距离障碍物距离单位mm
*
================================================================*/

/*
 * 使用原子操作解决资源竞态问题
 * 适用于所有竞态
 * #define pos 0 
 * unsigned long bit_num = 0
 * set_bit(pos, &bit_num);
 * change_bit(pos, &bit_num);
 * clear_bit(pos, &bit_num);
 * test_bit(pos, &bit_num);
 * 类似中断一样,可以使用一个位标注资源是否被使用。位原子操作是使用带中断自旋锁实现的,因此才能解决所有竞态问题
 * 接口设备: /dev/supersonic
 * 使用普通GPIO实现,一个输入,一个输出
 * 输出用于触发信号,输入用于回响信号
 * 时序图很简单,给一个10us以上的触发信号,8个40k Hz的等待后,回响信号线会触发一定时间高电平信号。
 * 高电平信号时间越长,表示距离障碍物距离越远
 * 高电平时间和障碍物距离公式: distance = time(us)/58 * 10 (mm)
 * 选用GPIO: PD26触发信号,PD25回响信号
 * 内核延迟函数: delay(), mdelay, udelay();
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <asm/atomic.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Less_Is_More");
MODULE_VERSION("0.0.1");

#define TIMER_BASE 0x03009020
// timer1基地址
#define GPIOD_BASE (0x0300B000 + 3 * 0x24)
// cfg偏移: 0x0C, pul偏移:0x20, data偏移:0x10

// 构造获取距离命令
#define SUPERSONIC_GET   _IOR('t', 0, long)

void __iomem *timer_base = NULL;
void __iomem *gpiod_base = NULL;

# 位原子操作使用
#define BIT_POS 0
volatile unsigned long atomic_num = 0;

/*
 * 描述: 字符设备打开函数
 *       timer1映射
 *       gpiod映射
 *       timer1初始化
 *       gpiod初始化:设置gpiod25输出,gpiod26输入,都设置为下拉
 */
int supersonic_open(struct inode *node, struct file *file){
  printk(KERN_INFO"Supersonic opened\n");
  if(!test_bit(BIT_POS, &atomic_num)){
    return -EBUSY;
  }
  clear_bit(BIT_POS, &atomic_num);
  // 内存映射
  timer_base = ioremap(TIMER_BASE, SZ_32);
  gpiod_base = ioremap(GPIOD_BASE, SZ_64);
  if(IS_ERR(timer_base) || IS_ERR(gpiod_base)){
    printk(KERN_INFO"ioremap error\n");
    return -1;
  }

  // 每计数一个就是1/12us
  // set init value as 12M, 1S
  iowrite32(12000000, timer_base + 0x04);
  // set clock source is 24M, pre-scale is 2
  iowrite32(0x94, timer_base);
  // 设置gpiod_cfg3,以设置gpiod25输入,gpiod26输出
  iowrite32(0x0107, gpiod_base + 0x0C);
  // 都设置为下拉模式
  iowrite32(0x0a << 18, gpiod_base + 0x20);

  return 0;
}

int supersonic_release(struct inode *node, struct file *file){
  printk(KERN_INFO"Supersonic close\n");
  iounmap(timer_base);
  iounmap(gpiod_base);
  set_bit(BIT_POS, &atomic_num);
  return 0;
}

long supersonic_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
  // 障碍物距离mm
  long distance = 0;
  // 回响信号
  long return_sig = 0;
  // 等待次数,最长等待时间23200us,这里取30000us就是30ms
  int loop_count = 0;
  int ret = 0;
  switch(cmd){
    case SUPERSONIC_GET:
      // 清除timer0计时器
      iowrite32(ioread32(timer_base)|(1<<1), timer_base);
      // wait load all right
      while((ioread32(timer_base) >> 1) & 1) ;
	
	// 先拉低gpiod26
      iowrite32(ioread32(gpiod_base + 0x10)&(~(1<<26)), gpiod_base + 0x10);
      // 拉高gpiod26 10us以上
      iowrite32(ioread32(gpiod_base + 0x10)|(1<<26), gpiod_base + 0x10);
      udelay(11);
      // 再次拉低
      iowrite32(ioread32(gpiod_base + 0x10)&(~(1<<26)), gpiod_base + 0x10);

      loop_count = 0;

      // 等待gpiod25被拉高,开启timer0定时器,直到gpio25拉低
      do{
        // 1us误差,0.173mm
        udelay(1);
        loop_count++;
        if(loop_count > 30000){
          printk(KERN_INFO"No Up signal\n");
          goto time_out;
        }
        return_sig = ioread32(gpiod_base + 0x10)>>25 & 1;
      }while(!return_sig);

      printk(KERN_INFO"return signal %lx \n",return_sig);

      loop_count = 0;
      // enable timer1
      iowrite32(ioread32(timer_base)|(1 << 0), timer_base);
      do{
        // 1us误差,0.173mm
        udelay(1);
        loop_count++;
        if(loop_count > 30000){
          printk(KERN_INFO"No Lower signal\n");
          goto time_out;
        }
        return_sig = ioread32(gpiod_base + 0x10)>>25 & 1;
      }while(return_sig);
      // 停止timer1
      iowrite32(ioread32(timer_base) & ~(1 << 0), timer_base);
      // 获取time1用时,计算障碍物距离
      distance = (12000000 - ioread32(timer_base + 0x8)) * 10 / 12 / 58;
      ret = copy_to_user((void *)arg, &distance, sizeof(long));
      printk(KERN_INFO"Get distance %ldmm\n",distance);
      if(ret < 0){
        printk(KERN_INFO"copy to user error\n");
        return -1;
      }
      break;
    default:
      printk(KERN_INFO"Unknow command\n");
      return -EINTR;
      break;
  }
  return 0;
time_out:
      return -ETIMEDOUT;
}

struct file_operations fops = {
  .owner = THIS_MODULE,
  .open = supersonic_open,
  .release = supersonic_release,
  .unlocked_ioctl = supersonic_ioctl,
};

struct miscdevice misc_dev = {
  .minor = MISC_DYNAMIC_MINOR,
  .name = "supersonic",
  .fops = &fops,
};

static int __init supersonic_init(void){
  printk(KERN_INFO"Supersonic device init\n");
  misc_register(&misc_dev);
  set_bit(BIT_POS, &atomic_num);
  return 0;
}

static void __exit supersonic_exit(void){
  printk(KERN_INFO"Supersonic device exit\n");
  misc_deregister(&misc_dev);
}

module_init(supersonic_init);
module_exit(supersonic_exit);

main.c

/*================================================================
*   文件名称:main.c
*   创 建 者:Less_is_More
*   创建日期:2022年09月30日
*   描    述:用混杂设备驱动supersonic设备,获取距离障碍物距离
*
================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>

// 构造获取距离命令
#define SUPERSONIC_GET   _IOR('t', 0, long)

int main(int argc, char **argv){
  int fd = 0; 
  int ret = 0;
  long distance = 0;
  char c = 0;

  if(0 > (fd = open("/dev/supersonic", O_RDWR))){
    fprintf(stderr,"Open /dev/wdt_dev error\n");
    return -EINVAL;
  }

  printf("Please input the handle(quit:q,other continue):");
  do{
    c = getchar();
    getchar();
    ret = ioctl(fd, SUPERSONIC_GET, &distance);
    if(ret < 0){
      fprintf(stderr,"ioctl get supersonic distance erroe\n");
      return -1;
    }
    printf("The wall distance is %ldmm \n", distance);
    printf("Please input the handle(quit:q,other continue):");
  }while('q' != c);
  printf("\n");
  close(fd);

  return 0;
}

Makefile

CONFIG_MODULE_SIG=n
KERNDIR:=/lib/modules/`uname -r`/build
PWD:= $(shell pwd)

obj-m:= supersonic.o

all:
	make -C $(KERNDIR) M=$(PWD) modules  ;	\
	gcc -o main main.c

clean:
	make -C $(KERNDIR) M=$(PWD) clean		; \
	rm main

测试方法

硬件连接

orangepi3lts 引出的引脚如下:

 +------+-----+----------+------+---+   OPi 3  +---+------+----------+-----+------+
 | GPIO | wPi |   Name   | Mode | V | Physical | V | Mode | Name     | wPi | GPIO |
 +------+-----+----------+------+---+----++----+---+------+----------+-----+------+
 |      |     |     3.3V |      |   |  1 || 2  |   |      | 5V       |     |      |
 |  122 |   0 |    SDA.0 |  OUT | 0 |  3 || 4  |   |      | 5V       |     |      |
 |  121 |   1 |    SCL.0 |   IN | 0 |  5 || 6  |   |      | GND      |     |      |
 |  118 |   2 |    PWM.0 |  OFF | 0 |  7 || 8  | 0 | OFF  | PL02     | 3   | 354  |
 |      |     |      GND |      |   |  9 || 10 | 0 | OFF  | PL03     | 4   | 355  |
 |  120 |   5 |    RXD.3 |  OFF | 0 | 11 || 12 | 0 | OFF  | PD18     | 6   | 114  |
 |  119 |   7 |    TXD.3 |  OFF | 0 | 13 || 14 |   |      | GND      |     |      |
 |  362 |   8 |     PL10 |  OFF | 0 | 15 || 16 | 0 | OFF  | PD15     | 9   | 111  |
 |      |     |     3.3V |      |   | 17 || 18 | 0 | OFF  | PD16     | 10  | 112  |
 |  229 |  11 |   MOSI.1 |  OFF | 0 | 19 || 20 |   |      | GND      |     |      |
 |  230 |  12 |   MISO.1 |  OFF | 0 | 21 || 22 | 0 | OFF  | PD21     | 13  | 117  |
 |  228 |  14 |   SCLK.1 |  OFF | 0 | 23 || 24 | 0 | OFF  | CE.1     | 15  | 227  |
 |      |     |      GND |      |   | 25 || 26 | 0 | OFF  | PL08     | 16  | 360  |
 +------+-----+----------+------+---+----++----+---+------+----------+-----+------+
 | GPIO | wPi |   Name   | Mode | V | Physical | V | Mode | Name     | wPi | GPIO |
 +------+-----+----------+------+---+   OPi 3  +---+------+----------+-----+------+

上面驱动程序用到4个引脚,分别的3.3V,GND,SDA.0,SCL.0。因为我买的HC-SR04模块支持3.3V驱动,所以我连接的3.3V的电源,据说第一个版本的模块只支持5V电源,所以还请根据自己的情况选择适当电源信号。
SDA.0,SCL.0分别连接到的Allwinner H6芯片的gpiod26和gpiod25引脚,用2根线连接到HC-SR04的Trig和Echo(激发和回响)引脚。

使用

接下来就是编译和使用模块了,我的orangepi3lts上是刷的是orangepi3-lts 5.10.75-sunxi64的镜像所以可以直接使用内核中的/lib/modules/uname -r/build编译外部驱动模块

cd 驱动代码所在目录
make 
sudo dmesg -C
sudo insmod supersonic.ko
sudo ./main
# 显示如下
The wall distance is 3505mm 
Please input the handle(quit:q,other continue):c
The wall distance is 3505mm 
Please input the handle(quit:q,other continue):v
The wall distance is 151mm 
Please input the handle(quit:q,other continue):c   
The wall distance is 571mm 
Please input the handle(quit:q,other continue):q
The wall distance is 569mm 
Please input the handle(quit:q,other continue):
# 可以测出不同的距离了
#dmesg 内容
[ 6323.158500] Supersonic device init
[ 6325.028305] My misc device opened
[ 6327.793349] return signal 1 
[ 6327.813704] Get distance 3505mm
[ 6330.241300] return signal 1 
[ 6330.261655] Get distance 3505mm
[ 6336.121211] return signal 1 
[ 6336.122109] Get distance 151mm
[ 6340.952864] return signal 1 
[ 6340.956196] Get distance 571mm
[ 6342.736572] return signal 1 
[ 6342.739901] Get distance 569mm
[ 6342.740003] My misc device close
# return signal 1 是用于确认Echo信号脚有返回高电平信号,可以在驱动中删除

# 还可以测试下竞态的情况,开启一个程序后按Ctrl + Z进入后台运行再开一个./main程序会报以下错误
$ sudo ./main
#此时按Ctrl + Z
$ sudo ./main
Open /dev/wdt_dev error
$ jobs
[2]+  Stopped                 ./main
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值