发布SPI: 12_编写SPI_DAC模块驱动程序,13_编写DAC驱动_上机实验_IMX6ULL,13_编写DAC驱动_上机实验_STM32MP157

This commit is contained in:
weidongshan
2022-04-26 11:59:43 +08:00
parent 69eac2f1f8
commit 0f865b253c
25 changed files with 1659 additions and 24 deletions

View File

@@ -4,12 +4,25 @@
* DAC芯片手册`TLC5615.pdf`
## 1. 硬件
### 1.1 原理图
## 1. 要做什么事情
![](pic/09_spi_drv_frame.png)
* 查看原理图,编写设备树
* 编写驱动程序注册一个spidrv
* 编写测试程序
## 2. 硬件
### 2.1 原理图
IMX6ULL:
@@ -27,9 +40,9 @@ STM32MP157:
### 1.2 连接
### 2.2 连接
#### 1.2.1 IMX6ULL
#### 2.2.1 IMX6ULL
DAC模块接到IMX6ULL扩展板的SPI_A插座上
@@ -37,11 +50,11 @@ DAC模块接到IMX6ULL扩展板的SPI_A插座上
#### 1.2.2 STM32MP157
#### 2.2.2 STM32MP157
## 2. 编写设备树
## 3. 编写设备树
确认SPI时钟最大频率
@@ -66,7 +79,7 @@ F = 20000000 = 20MHz
### 2.1 IMX6ULL
### 3.1 IMX6ULL
![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png)
@@ -95,7 +108,7 @@ DAC模块接在这个插座上那么要在设备树里spi1的节点下创建
### 2.2 STM32MP157
### 3.2 STM32MP157
![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png)
@@ -129,7 +142,7 @@ DAC模块接在这个插座上那么要在设备树里spi5的节点下创建
## 3. 编写驱动程序
## 4. 编写驱动程序
以前我们基于spidev编写过DAC的应用程序可以参考它
@@ -139,5 +152,5 @@ DAC模块接在这个插座上那么要在设备树里spi5的节点下创建
## 4. 编写测试程序
## 5. 编写测试程序

View File

@@ -0,0 +1,286 @@
# 编写DAC驱动_上机实验 #
* 源码
![image-20220426095853618](pic/73_src_dac_drv.png)
## 1. 硬件
### 1.1 原理图
IMX6ULL:
![image-20220309150927785](pic/33_imx6ull_dac.png)
STM32MP157:
![image-20220309151025637](pic/34_stm32mp157_dac.png)
原理图:
![image-20220309151636533](pic/35_dac_sch.png)
### 1.2 连接
#### 1.2.1 IMX6ULL
DAC模块接到IMX6ULL扩展板的SPI_A插座上
![image-20220309164031109](pic/40_dac_on_imx6ull.png)
#### 1.2.2 STM32MP157
DAC模块接到STM32MP157扩展板的SPI_A插座上
![image-20220311100618477](pic/43_dac_on_stm32mp157.png)
## 2. 编写设备树
确认SPI时钟最大频率
![image-20220309163435541](pic/39_dac_time_param.png)
```shell
T = 25 + 25 = 50ns
F = 20000000 = 20MHz
```
设备树如下:
```shell
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
```
### 2.1 IMX6ULL
![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png)
DAC模块接在这个插座上那么要在设备树里spi1的节点下创建子节点。
代码在`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下:
```shell
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
```
### 2.2 STM32MP157
![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png)
DAC模块接在这个插座上那么要在设备树里spi5的节点下创建子节点。
代码在`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下:
```shell
&spi5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi5_pins_a>;
pinctrl-1 = <&spi5_sleep_pins_a>;
status = "okay";
cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>, <&gpioz 4 GPIO_ACTIVE_LOW>;
spidev: icm20608@0{
compatible = "invensense,icm20608";
interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
interrupt-parent = <&gpioz>;
spi-max-frequency = <8000000>;
reg = <0>;
};
dac_test: dac_test@1{
compatible = "100ask,dac";
spi-max-frequency = <20000000>;
reg = <1>;
};
};
```
## 3. 编译替换设备树
### 3.1 IMX6ULL
#### 3.1.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 3.1.2 编译、替换设备树
* 编译设备树:
在Ubuntu的IMX6ULL内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/100ask_imx6ull-14x14.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
[root@100ask:~]# sync
```
* 重启开发板
### 3.2 STM32MP157
#### 3.2.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 3.2.2 编译、替换设备树
* 编译设备树:
在Ubuntu的STM32MP157内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 确定设备树分区挂载在哪里
由于版本变化STM32MP157单板上烧录的系统可能有细微差别。
在开发板上执行`cat /proc/mounts`后,可以得到两种结果(见下图)
* mmcblk2p2分区挂载在/boot目录下(下图左边):无需特殊操作,下面把文件复制到/boot目录即可
* mmcblk2p2挂载在/mnt目录下(下图右边)
* 在视频里、后面文档里,都是更新/boot目录下的文件所以要先执行以下命令重新挂载
* `mount /dev/mmcblk2p2 /boot`
![](pic/46_boot_mount.png)
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot/
[root@100ask:~]# sync
```
* 重启开发板
## 4. 编译DAC驱动
## 5. 编译APP
```shell
arm-buildroot-linux-gnueabihf-gcc -o dac_test dac_test.c
```
## 6. 上机实验
如果spidev没有被编译进内核那么先执行
```shell
insmod dac_drv.ko
```
确定设备节点:
```shell
ls /dev/100ask_dac
```
假设设备节点为`/dev/100ask_dac`,执行测试程序:
```shell
./dac_test /dev/100ask_dac 500
./dac_test /dev/100ask_dac 600
./dac_test /dev/100ask_dac 1000
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dac_test dac_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dac_test
obj-m += dac_drv.o

View File

@@ -0,0 +1,174 @@
/*
* Simple synchronous userspace interface to SPI devices
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
* Copyright (C) 2007 David Brownell (simplification, cleanup)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *dac;
static int major;
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int val;
int err;
int old_val = 0;
struct spi_message msg;
struct spi_transfer xfer;
int status;
/* copy_from_user */
err = copy_from_user(&val, (const void __user *)arg, sizeof(int));
/* 发起SPI传输: */
/* 1. 把val修改为正确的格式 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
/* 2. 发起SPI传输同时写\读 */
/* 2.1 构造transfer
* 2.2 加入message
* 2.3 调用spi_sync
*/
xfer[0].tx_buf = &val;
xfer[0].rx_buf = &old_val;
xfer[0].len = 2;
spi_message_init(&msg);
spi_message_add_tail(&[0], &msg);
status = spi_sync(dac, &msg);
/* 3. 修改读到的数据的格式 */
old_val >>= 2;
/* copy_to_user */
err = copy_to_user((void __user *)arg, &old_val, sizeof(int));
return 0;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
dac = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_dac", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_dac");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_dac_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_init(void)
{
int status;
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,56 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dac_test dac_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dac_test
obj-m += dac_drv.o

View File

@@ -0,0 +1,186 @@
/*
* Simple synchronous userspace interface to SPI devices
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
* Copyright (C) 2007 David Brownell (simplification, cleanup)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *dac;
static int major;
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int val;
int err;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
struct spi_message msg;
struct spi_transfer xfer[1];
int status;
memset(&xfer[0], 0, sizeof(xfer));
/* copy_from_user */
err = copy_from_user(&val, (const void __user *)arg, sizeof(int));
printk("spidev_ioctl get val from user: %d\n", val);
/* 发起SPI传输: */
/* 1. 把val修改为正确的格式 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff;
tx_buf[0] = (val>>8) & 0xff;
/* 2. 发起SPI传输同时写\读 */
/* 2.1 构造transfer
* 2.2 加入message
* 2.3 调用spi_sync
*/
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
status = spi_sync(dac, &msg);
/* 3. 修改读到的数据的格式 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
/* copy_to_user */
err = copy_to_user((void __user *)arg, &val, sizeof(int));
return 0;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
dac = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_dac", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_dac");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_dac_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_init(void)
{
int status;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,56 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}

View File

@@ -545,13 +545,18 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
```
* 2021.04.25 发布"SPI子系统"
```shell
10_OLED模块上机实验_STM32MP157
```
* 2021.04.26 发布"SPI子系统"
```shell
12_编写SPI_DAC模块驱动程序
13_编写DAC驱动_上机实验_IMX6ULL
13_编写DAC驱动_上机实验_STM32MP157
```

View File

@@ -4,12 +4,25 @@
* DAC芯片手册`TLC5615.pdf`
## 1. 硬件
### 1.1 原理图
## 1. 要做什么事情
![](pic/09_spi_drv_frame.png)
* 查看原理图,编写设备树
* 编写驱动程序注册一个spidrv
* 编写测试程序
## 2. 硬件
### 2.1 原理图
IMX6ULL:
@@ -27,9 +40,9 @@ STM32MP157:
### 1.2 连接
### 2.2 连接
#### 1.2.1 IMX6ULL
#### 2.2.1 IMX6ULL
DAC模块接到IMX6ULL扩展板的SPI_A插座上
@@ -37,11 +50,11 @@ DAC模块接到IMX6ULL扩展板的SPI_A插座上
#### 1.2.2 STM32MP157
#### 2.2.2 STM32MP157
## 2. 编写设备树
## 3. 编写设备树
确认SPI时钟最大频率
@@ -66,7 +79,7 @@ F = 20000000 = 20MHz
### 2.1 IMX6ULL
### 3.1 IMX6ULL
![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png)
@@ -95,7 +108,7 @@ DAC模块接在这个插座上那么要在设备树里spi1的节点下创建
### 2.2 STM32MP157
### 3.2 STM32MP157
![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png)
@@ -129,7 +142,7 @@ DAC模块接在这个插座上那么要在设备树里spi5的节点下创建
## 3. 编写驱动程序
## 4. 编写驱动程序
以前我们基于spidev编写过DAC的应用程序可以参考它
@@ -139,5 +152,5 @@ DAC模块接在这个插座上那么要在设备树里spi5的节点下创建
## 4. 编写测试程序
## 5. 编写测试程序

View File

@@ -0,0 +1,286 @@
# 编写DAC驱动_上机实验 #
* 源码
![image-20220426095853618](pic/73_src_dac_drv.png)
## 1. 硬件
### 1.1 原理图
IMX6ULL:
![image-20220309150927785](pic/33_imx6ull_dac.png)
STM32MP157:
![image-20220309151025637](pic/34_stm32mp157_dac.png)
原理图:
![image-20220309151636533](pic/35_dac_sch.png)
### 1.2 连接
#### 1.2.1 IMX6ULL
DAC模块接到IMX6ULL扩展板的SPI_A插座上
![image-20220309164031109](pic/40_dac_on_imx6ull.png)
#### 1.2.2 STM32MP157
DAC模块接到STM32MP157扩展板的SPI_A插座上
![image-20220311100618477](pic/43_dac_on_stm32mp157.png)
## 2. 编写设备树
确认SPI时钟最大频率
![image-20220309163435541](pic/39_dac_time_param.png)
```shell
T = 25 + 25 = 50ns
F = 20000000 = 20MHz
```
设备树如下:
```shell
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
```
### 2.1 IMX6ULL
![image-20220311101017666](pic/44_imx6ull_pro_extend_spi_a.png)
DAC模块接在这个插座上那么要在设备树里spi1的节点下创建子节点。
代码在`arch/arm/boot/dts/100ask_imx6ull-14x14.dts`中,如下:
```shell
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
```
### 2.2 STM32MP157
![image-20220311101127305](pic/45_stm32mp157_pro_extend_spi_a.png)
DAC模块接在这个插座上那么要在设备树里spi5的节点下创建子节点。
代码在`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts`中,如下:
```shell
&spi5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&spi5_pins_a>;
pinctrl-1 = <&spi5_sleep_pins_a>;
status = "okay";
cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>, <&gpioz 4 GPIO_ACTIVE_LOW>;
spidev: icm20608@0{
compatible = "invensense,icm20608";
interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
interrupt-parent = <&gpioz>;
spi-max-frequency = <8000000>;
reg = <0>;
};
dac_test: dac_test@1{
compatible = "100ask,dac";
spi-max-frequency = <20000000>;
reg = <1>;
};
};
```
## 3. 编译替换设备树
### 3.1 IMX6ULL
#### 3.1.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 3.1.2 编译、替换设备树
* 编译设备树:
在Ubuntu的IMX6ULL内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/100ask_imx6ull-14x14.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
[root@100ask:~]# sync
```
* 重启开发板
### 3.2 STM32MP157
#### 3.2.1 设置工具链
```shell
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
```
#### 3.2.2 编译、替换设备树
* 编译设备树:
在Ubuntu的STM32MP157内核目录下执行如下命令,
得到设备树文件:`arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb`
```shell
make dtbs
```
* 复制到NFS目录
```shell
$ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
```
* 开发板上挂载NFS文件系统
```shell
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
```
* 确定设备树分区挂载在哪里
由于版本变化STM32MP157单板上烧录的系统可能有细微差别。
在开发板上执行`cat /proc/mounts`后,可以得到两种结果(见下图)
* mmcblk2p2分区挂载在/boot目录下(下图左边):无需特殊操作,下面把文件复制到/boot目录即可
* mmcblk2p2挂载在/mnt目录下(下图右边)
* 在视频里、后面文档里,都是更新/boot目录下的文件所以要先执行以下命令重新挂载
* `mount /dev/mmcblk2p2 /boot`
![](pic/46_boot_mount.png)
* 更新设备树
```shell
[root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot/
[root@100ask:~]# sync
```
* 重启开发板
## 4. 编译DAC驱动
## 5. 编译APP
```shell
arm-buildroot-linux-gnueabihf-gcc -o dac_test dac_test.c
```
## 6. 上机实验
如果spidev没有被编译进内核那么先执行
```shell
insmod dac_drv.ko
```
确定设备节点:
```shell
ls /dev/100ask_dac
```
假设设备节点为`/dev/100ask_dac`,执行测试程序:
```shell
./dac_test /dev/100ask_dac 500
./dac_test /dev/100ask_dac 600
./dac_test /dev/100ask_dac 1000
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dac_test dac_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dac_test
obj-m += dac_drv.o

View File

@@ -0,0 +1,174 @@
/*
* Simple synchronous userspace interface to SPI devices
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
* Copyright (C) 2007 David Brownell (simplification, cleanup)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *dac;
static int major;
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int val;
int err;
int old_val = 0;
struct spi_message msg;
struct spi_transfer xfer;
int status;
/* copy_from_user */
err = copy_from_user(&val, (const void __user *)arg, sizeof(int));
/* 发起SPI传输: */
/* 1. 把val修改为正确的格式 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
/* 2. 发起SPI传输同时写\读 */
/* 2.1 构造transfer
* 2.2 加入message
* 2.3 调用spi_sync
*/
xfer[0].tx_buf = &val;
xfer[0].rx_buf = &old_val;
xfer[0].len = 2;
spi_message_init(&msg);
spi_message_add_tail(&[0], &msg);
status = spi_sync(dac, &msg);
/* 3. 修改读到的数据的格式 */
old_val >>= 2;
/* copy_to_user */
err = copy_to_user((void __user *)arg, &old_val, sizeof(int));
return 0;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
dac = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_dac", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_dac");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_dac_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_init(void)
{
int status;
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,56 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}

View File

@@ -0,0 +1,22 @@
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o dac_test dac_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order dac_test
obj-m += dac_drv.o

View File

@@ -0,0 +1,186 @@
/*
* Simple synchronous userspace interface to SPI devices
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
* Copyright (C) 2007 David Brownell (simplification, cleanup)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *dac;
static int major;
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int val;
int err;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
struct spi_message msg;
struct spi_transfer xfer[1];
int status;
memset(&xfer[0], 0, sizeof(xfer));
/* copy_from_user */
err = copy_from_user(&val, (const void __user *)arg, sizeof(int));
printk("spidev_ioctl get val from user: %d\n", val);
/* 发起SPI传输: */
/* 1. 把val修改为正确的格式 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff;
tx_buf[0] = (val>>8) & 0xff;
/* 2. 发起SPI传输同时写\读 */
/* 2.1 构造transfer
* 2.2 加入message
* 2.3 调用spi_sync
*/
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
status = spi_sync(dac, &msg);
/* 3. 修改读到的数据的格式 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
/* copy_to_user */
err = copy_to_user((void __user *)arg, &val, sizeof(int));
return 0;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
dac = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_dac", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_dac");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_dac_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_init(void)
{
int status;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,56 @@
/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}