6.3 用户态SPI编程接口
参考内核提供的测试程序:“tools\spi\spidev_fdx.c”。把它编译之后,可以得到可执行程序。
注意:下列函数只实现了数据的读写,至于读写什么数据、达到效果,需要用户阅读芯片手册。
6.3.1 用法
spidev_fdx的用法如下:
spidev_fdx [-h] [-m N] [-r N] /dev/spidevB.D
* -h: 打印用法
* -m N:先写 1 个字节 0xaa,再读 N 个字节,**注意:**不是同时写同时读
* -r N:读 N 个字节
6.3.2 显示属性
代码如下:
6.3.3 读数据
代码如下:
6.3.4 先写再读数据
代码如下:
6.3.5 同时读写
代码如下:
6.4 Linux SPI设备驱动
本节源码位于如下目录:
源码如下所示:
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ //用于处理 SPI 设备的 ioctl 命令。它接收用户空间传递的参数,并执行 SPI 传输。
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,
};
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;
}
//定义了 SPI 驱动的相关信息,包括设备名、探测函数、移除函数等。
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.
*/
};
/*-------------------------------------------------------------------------*/
//在模块初始化时被调用,用于注册 SPI 驱动
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);
//退出时被调用,用于反注册 SPI 驱动
static void __exit spidev_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
spi_unregister_driver(&spidev_spi_driver);
}
6.5 SPI接口传感器编程示例
1. 连线
在开始前,请先按照下图所示连接TLC5615模块:
2. 代码
本节源码位于如下目录:
源码如下所示:
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
\n" , argv[0]);return 0;
}
fd = open(argv[1], O_RDWR); //打开指定的设备文件
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
//将命令行参数转换为整数,并将其左移两位,保留最低的 10 位。
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);// 发起 SPI 传输,将数据写入设备
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}
编写对应的Makefile:
KERN_DIR = /home/ubuntu/remi-pi/myir-renesas-linux
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CC) -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
3. 编译
由于我们需要编译驱动程序,那么需要先编译内核,并且准备内核源代码树以便构建外部模块那么在编译应用程序前需要在SDK目录下执行:
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ source /opt/remi-sdk/environment-set
up-aarch64-poky-linux
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ make ARCH=arm64 Image dtbs -j4
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ make modules_prepare
编译好内核后,进入驱动和APP目录,执行如下命令:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
make
编译成功,可以在当前文件夹下查看到dac_drv.ko和dac_test,将这两个文件上传到开发板。
假设设置开发板的IP为:192.168.5.9,上传文件到开发板上。
scp ./input_read_fasync root@192.168.5.9:/mnt/
4. 修改设备树
在内核根目录下进入目录arch/arm64/boot/dts/myir/,修改 mys-rzg2l-smarc-base.dtsi设备树文件。
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ cd arch/arm64/boot/dts/myir/
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ vi mys-rzg2l-smarc-base.dtsi
修改spi0设备节点的内容如下所示:
&spi0 {
pinctrl-0 = <&spi0_pins>;
pinctrl-names = "default";
status = "okay";
spidev@0{
compatible = "rohm,dh2228fv";
spi-max-frequency = <80000000>;
reg = <0>;
status = "disabled";
};
mydac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <1000000>;
status = "okay";
};
};
修改完成后在内核根目录下编译设备树:
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ make ARCH=arm64 dtbs -j8
编译完成后需要手动替换开发板的设备树,替换完成后需要重启开发板,如下操作:
1. 在开发板端执行
root@myir-remi-1g:~
root@myir-remi-1g:~
2. 在Ubuntu端执行
ubuntu@ubuntu2004:~/remi-pi/myir-renesas-linux$ cp arch/arm64/boot/dts/myir/mys-rzg2
l-*.dtb ../build/
ubuntu@ubuntu2004:~/remi-pi$ scp ./build/*.dtb root@192.168.5.9:/mnt
3. 重启开发板
root@myir-remi-1g:~
root@myir-remi-1g:~
4. 在Ubuntu端执行
ubuntu@ubuntu2004:~/C-Test/dac_use_mydrv$ scp ./dac_test root@192.168.5.9:/mnt/
ubuntu@ubuntu2004:~/C-Test/dac_use_mydrv$ scp ./dac_drv.ko root@192.168.5.9:/mnt/
5. 测试
进入/mnt目录运行程序
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
dac_drv.ko dac_test
装载内核模块,并执行程序。
root@myir-remi-1g:/mnt
root@myir-remi-1g:/mnt# ./dac_test /dev/100ask_dac 1000
root@myir-remi-1g:/mnt# ./dac_test /dev/100ask_dac 500
root@myir-remi-1g:/mnt# ./dac_test /dev/100ask_dac 0
执行后面三行测试程序时,输入1000时模块的红灯最亮,输入500时模块的红灯偏暗,输入0时模块的红灯熄灭。
如您在使用瑞萨MCU/MPU产品中有任何问题,可识别下方二维码或复制网址到浏览器中打开,进入瑞萨技术论坛寻找答案或获取在线技术支持。
https://community-ja.renesas.com/zh/forums-groups/mcu-mpu/
未完待续
推荐阅读
RS485应用编程&USB串口编程 - RZ MPU工业控制教程连载(7)
Linux串口应用编程 - RZ MPU工业控制教程连载(6)
赢瑞米派 | 瑞萨RZ/G通用MPU研讨会火热报名中