参考资料:
参考资料:
-
内核驱动:
drivers\spi\spidev.c
-
内核提供的测试程序:
tools\spi\spidev_fdx.c
-
内核文档:
Documentation\spi\spidev
-
DAC芯片手册:
TLC5615.pdf
一、DAC硬件
1.1 原理图
1.2 扩展板连接图
1.3 DAC原理
(1) 内部框图
引脚:
- CS:片选引脚
- SCLK:时钟引脚
- DIN:输入引脚(接收来自master的数据)
- OUT:模拟信号输出
- DOUT:数字信号输出(可以用于级联下一个DAC芯片的DIN)
寄存器:
- 16 Bit shift Register:16位移位寄存器,DIN收到的是16位数据,最高4位无效,最低2位必须是0,中间10位是真正有效的数据位
- 10 Bit DAC Register:10位DAC寄存器,将10位数字信号转换为模拟信号
(2)时序图
操作过程
- 拉低CS
- 在SCLK的上升沿,从DIN采集16位数据,存入
16-Bit Shift Register
- 在CS的上升沿,把
16-Bit Shift Register
中的10位数据传入10-Bit DAC Register
,作为模拟量在OUT引脚输出 - 在SCLK上升沿发出DOUT信号
- DOUT数据来自
16-Bit Shift Register
- 第1个数据是上次数据遗留下的LSB位(这里逻辑有bug,实际应该是传输最高位)
- 其余15个数据来自
16-Bit Shift Register
的高15位(这里逻辑有bug,实际应该是传输剩下的15位) 16-Bit Shift Register
的LSB在下一个周期的第1个时钟传输- LSB必定是0,所以当前的周期里读出
16-Bit Shift Register
的15位数据也足够了
(3)DAC公式
输出电压 = 2 * VREFIN * n / 1024 = 2 * 2.048 * n / 1024
其中: n为10位数值
二、编写APP
/* 参考: 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>
#include <linux/spi/spidev.h>
/* dac_test /dev/spidevB.D <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
struct spi_ioc_transfer xfer[1]; /* 一个传输结构 */
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/spidevB.D <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); /* 字符串转整型 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff; /* 低8位 */
tx_buf[0] = (val>>8) & 0xff; /* 高8位 */
memset(xfer, 0, sizeof xfer);
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
/* 同时写同时读 */
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
if (status < 0) {
printf("SPI_IOC_MESSAGE\n");
return -1;
}
/* 打印DOUT数据 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
printf("Pre val = %d\n", val);
return 0;
}
三、编写设备树
设备树文件:\arch\arm\boot\dts\100ask_imx6ull-14x14.dts
修改:spi1节点写添加dac
节点。
&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 = "spidev";
reg = <0>; //第一个片选引脚
spi-max-frequency = <20000000>; //最大频率
};
};
最大频率如何确定:
查看芯片手册,可以知道最小时钟周期50ns。
T = 25 + 25 = 50ns
F = 20000000 = 20MHz
四、上机实验
- 编译app
arm-buildroot-linux-gnueabihf-gcc -o dac_test dac_test.c
- 编译设备树
内核路径执行:make dtbs
- 内核配置spidev,并编译内核
内核路径执行:make menuconfig
选上SPI_SPIDEV,保存退出
内核路径执行:make zImage
- 替换开发板设备树和内核镜像
cp /mnt/imx6ullsdk/repo/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/zImage /boot/
cp /mnt/imx6ullsdk/repo/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dtb /boot/
- 重启开发板
- 查看设备节点
$ ls /dev/spidev0.0 -l
crw------- 1 root root 153, 0 Jan 1 05:17 /dev/spidev0.0
- 运行测试程序
$ ./dac_test /dev/spidev0.0 90 //传入的数值不同,led亮度不同
Pre val = 1000 //前一次的数据
$ ./dac_test /dev/spidev0.0 500
Pre val = 90 //前一次的数据
$ ./dac_test /dev/spidev0.0 100
Pre val = 500 //前一次的数据
五、Bug分析
(1)DAC时序图bug
第一个时钟DOUT应该开始传输上次数据的最高位。
(2) spidev.c中的bug
内核警告:
解决:spidev.c中的of_match_table
对应数组补充一项:
六、总结
本文介绍基于spidev驱动的SPI_DAC设备上机实验。