前言
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