所用资源
- 一个位原子操作符
- 内核延时delay.h
- timer1硬件计时器
- gpiod25用于回响信号,gpiod26用于激发信号
- 1个misc混杂设备号
文件组成
- supersonic.c内核驱动文件,成功加载后会自动创建/dev/supersonic设备文件
- main.c用户空间测试程序
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