RK3588s+AD9833的驱动开发

前言

AD9833是一款DDS芯片,能够产生方波、三角波和正弦波,支持频率和相位字的设置。
单片机的驱动很多,arm linux上的驱动也许也有一些,无论如何我想分享一下最近编写的内核驱动。
还在学习中请指正
该驱动支持:

  • 切换方波、三角波和正弦波
  • 设置频率字
  • 字符型驱动,提供测试脚本

设备树节点

使用spi0作为驱动,使能spi0,并配置pinctrl,所使用的pin:&spi0m2_cs0 &spi0m2_pins在rk3588s-pinctrl.dtsi中可以搜到
并配置子节点的compatible,该属性用于匹配驱动模块,以便在设备树解析后能转化为设备,reg用于选择片选的编号。

&spi0{
	status = "okay";
	pinctrl-names = "default";	
	pinctrl-0 = <&spi0m2_cs0 &spi0m2_pins>;
	num-cs = <1>;
	myspidemo@0{
		compatible = "AD9833,spi_demo";
		reg=<0>;//使用第0号片选
		spi-max-frequency = <24000000>; //spi output clock
	};
};

内核模块编写

基于spi子系统的架构,由于AD9833为3线SPI,而3588的spi_device->mode不支持设置成#define SPI_3WIRE 0x10。所以采用4线,仅使用MOSI,并设置为16bit的模式,注意发送时以MSB优先。

#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/cdev.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#define DEV_NAME "dds_ad9833"
#define DEV_CNT (1)
//设备编号
static dev_t devno;
//字符设备cdev
static struct cdev chr_dev;
//保存创建的类
static struct class *this_class;
//保存创建的设备,创建设备 DEV_NAME 指定设备名
static struct device *this_device;
/*------------------SPI设备内容----------------------*/
struct spi_device	*this_spi_device; //保存oled设备对应的spi_device结构体,匹配成功后由.prob函数带回。
bool is_dev_match = false; 
/*------------------字符设备操作----------------------*/
static int chr_dev_open(struct inode *inode, struct file *filp)
{
    if(is_dev_match==false)
    {
        printk("open failed\n");
        return -1;
    }
    printk("open\n");
    return 0;
}
static ssize_t chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *off)
{

    char charlist[2];
    uint16_t datas[1];
    int error;

    if(cnt!=2)
    {
        printk("cnt !=2\n");
        return -1;
    }
    error=copy_from_user(charlist,buf,2);
    if((is_dev_match==false)||(error<0))
    {
        printk("copy_from_user failed\n");
        return -1;
    }
    printk("writing:%x %x\n",charlist[1],charlist[0]);
    datas[0]=charlist[1];
    datas[0]=(datas[0]<<8);
    datas[0]|=(charlist[0]);
    printk("writing:%x\n",datas[0]);
    error=spi_write(this_spi_device,datas,sizeof(datas));//u16
    if(error<0)
    {   
        printk("write failed 2\n");
        return error;
    }
    return 0;
}
static struct file_operations chr_dev_fops={
    .owner = THIS_MODULE,
    .open = chr_dev_open,
    .write = chr_dev_write,
};
/*------------------SPI驱动内容-------------------*/
int spi_drv_probe (struct spi_device *spidev)
{
    int ret;
    //1.通知probe
    printk("probe ok!\n");
    //2.设置spi
    this_spi_device=spidev;
    this_spi_device->max_speed_hz = 200000;
    this_spi_device->mode=SPI_MODE_2;//SPI2+MSB_FIRST
    this_spi_device->bits_per_word=16;
    ret=spi_setup(this_spi_device);
    if(ret<0)
    {
        printk("spi_setup failed\n");
        is_dev_match=false;
        return ret;
    }
    printk("spi_setup ok\n");
    //3.打印spi设备
    printk("max_speed_hz = %d\n", this_spi_device->max_speed_hz);
	printk("chip_select = %d\n", (int)this_spi_device->chip_select);
	printk("bits_per_word = %d\n", (int)this_spi_device->bits_per_word);
	printk("mode = %02X\n", this_spi_device->mode);
	printk("cs_gpio = %02X\n", this_spi_device->cs_gpio);
    //4.注册字符设备
    //4.1设备编号注册
    //动态分配,获取设备编号,次设备号为0
    ret=alloc_chrdev_region(&devno,0,DEV_CNT,DEV_NAME);
    if(ret<0)
    {
        printk("alloc failed\n");
        return ret;
    }
    printk("cdev is %d\n",devno);
    //4.2注册ops函数们
    chr_dev.owner=THIS_MODULE;
    cdev_init(&chr_dev,&chr_dev_fops);
    //4.3增加设备到cdev_map,添加1个设备
    ret=cdev_add(&chr_dev,devno,DEV_CNT);
    if(ret<0)
    {
        printk("fail to add cdev\n");
        goto add_err;//不能简单return,因为已经初始化设备,需要收回设备号
    }
    //4.4创建类和设备,旨在/dev/下弄个节点
    this_class=class_create(THIS_MODULE,DEV_NAME);
    this_device=device_create(this_class,NULL,devno,NULL,DEV_NAME);
    is_dev_match=true;
    return 0;
add_err:
    unregister_chrdev_region(devno,DEV_CNT);
    printk("add error\n");
    return -1;
}
int spi_drv_remove(struct spi_device *spidev)
{
    //卸载字符设备
    //1.清除设备
    device_destroy(this_class,devno);
    //2.清除类
    class_destroy(this_class);
    //3.归还设备号
    cdev_del(&chr_dev);
    //4.卸载字符设备
    unregister_chrdev_region(devno,DEV_CNT);
    printk("spi_drv has been removed\n");
    is_dev_match=false;
    //释放中断(如果有的话)
    return 0;
}
static const struct of_device_id spi_of_match_table[]={
    {.compatible="AD9833,spi_demo"},
    {}
};
//spi总线驱动的结构体
struct spi_driver spimod_driver = {
	.probe = spi_drv_probe,
	.remove = spi_drv_remove,//设备卸载时被调用
	.driver = {
		.name = "spi_drv",
		.of_match_table = spi_of_match_table,
	}
};
static int __init thismod_init(void)
{
    //注册spi驱动
    int Driver_status=spi_register_driver(&spimod_driver);
    printk("SPI mod init,state:%d\n",Driver_status);
    return 0;
}
static void __exit thismod_exit(void)
{
    //卸载字符设备
    //1.清除设备
    device_destroy(this_class,devno);
    //2.清除类
    class_destroy(this_class);
    //3.归还设备号
    cdev_del(&chr_dev);
    //4.卸载字符设备
    unregister_chrdev_region(devno,DEV_CNT);
    //最后卸载spi驱动
    spi_unregister_driver(&spimod_driver);
}


module_init(thismod_init);
module_exit(thismod_exit);
MODULE_LICENSE("GPL");

测试程序

测试程序执行的方法:
sudo ./testdds <输出波形> <输出频率>
<输出波形>:0-方波,1-三角波,2-正弦波

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define CLK_FREQ 25000000.0//25MHz
#define CONST_DIV 268435456.0 //2^28
//参考手册table 9,使用FREQ0,而没有用到FREQ1,D11=0
void set_freq(char buf[4],int freq)
{
    double code=freq*(CONST_DIV/CLK_FREQ);
    int codeint=(int)code;//取整数,忽略小数
    //lsb
    //低14位为数据,
    buf[1]=(codeint>>8)&0x3F;
    buf[0]=(codeint)&0xFF;
    //D14=1,D15=0;
    buf[1]|=0x40;
    buf[1]&=0x7F;
    //msb
    //低14为数据
    codeint=codeint>>14;
    buf[3]=(codeint>>8)&0x3F;
    buf[2]=(codeint)&0xFF;
    buf[3]|=0x40;
    buf[3]&=0x7F;
}
/*
参数1
方波:0;三角波:1;正弦波:2
参数2
频率,十进制,单位Hz
*/
int main(int argc, char *argv[])
{
    char type;
    char cmds[4];
    char *endptr;
    int freq=0;
    printf("dds test\n");
    /*判断输入的命令是否合法*/
    if(argc != 3)
    {
        printf(" command error ! \n");
        return -1;
    }
    type=argv[1][0];
    if((type>'2')||(type<'0'))
    {
        printf(" command error ! \n");
    }
    freq=strtol(argv[2],&endptr,10);
    if(*endptr!='\0')
    {
        printf(" command freq error ! \n");
        return -1;
    }
    printf("input freq:%d Hz\n",freq);
    /*打开文件*/
    int fd = open("/dev/dds_ad9833", O_RDWR);
    if(fd < 0)
    {
		printf("open file : %s failed !\n", argv[0]);
		return -1;
	}
    
    int error;
    switch(type)
    {
        //D13=1
        case '0':
        {//方波
           cmds[1]=0x20;
           cmds[0]=0x28;
        }
        break;
        case '1':
        {//三角波
           cmds[1]=0x20;
           cmds[0]=0x02;            
        }break;
        case '2':
        {
           cmds[1]=0x20;
           cmds[0]=0x00;
        }break;
        default:
        {
           cmds[1]=0x00;
           cmds[0]=0x0c;            
        }
        break;
    }
    write(fd,cmds,2);
    set_freq(cmds,freq);
    printf("sending:%x %x\n",cmds[0],cmds[1]);
    write(fd,cmds,2);//lsb
    write(fd,cmds+2,2);//msb
    /*关闭文件*/
    error = close(fd);
    if(error < 0)
    {
        printf("close file error! \n");
    }
        return 0;
}

测试程序编译

生成testdds的可执行文件,拷贝到开发板上,并插入内核模块

aarch64-linux-gnu-gcc -o testdds testdds.c

测试与结果

1.输出1kHz的方波

sudo ./testdds 0 1000

在这里插入图片描述

2.输出123456Hz的三角波

sudo ./testdds 1 123456

在这里插入图片描述

3.输出65535Hz的正弦波

sudo ./testdds 2 65535

在这里插入图片描述

硬件连接图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值