Linux驱动开发-编写VS1053芯片音频驱动

举报
DS小龙哥 发表于 2022/01/18 11:38:31 2022/01/18
【摘要】 VS1053是一款硬件编解码的音频芯片,提供SPI接口和IIS接口两种通信协议,这篇文章是介绍在Linux下如果模拟SPI时序来操作VS1053完成录音、播放音频歌曲功能。

1. 前言

VS1053是一款硬件编解码的音频芯片,提供SPI接口和IIS接口两种通信协议,这篇文章是介绍在Linux下如果模拟SPI时序来操作VS1053完成录音、播放音频歌曲功能。但是没有注册标准的音频驱动,没有对接音频框架,只是在驱动层完成VS1053的直接控制,本篇的重点主要是介绍如何初始化开发板的GPIO口,使用Linux的延时函数,模拟SPI时序,代码写了两种版本,一种是直接通过ioremap直接映射GPIO口地址,完成配置,一种是直接调用官方内核提供的库函数接口,完成GPIO口初始化,控制。

当前采用的开发板是友善之臂的Tiny4412,芯片是三星的EXYNOS4412,这款芯片出来有很长一段时间了,之前用在三星的S系列手机上的,最高主频是1.5GZ,稳定推荐主频是1.4GHZ,内核是三星提供的demon,友善之臂在基础上完成了移植适配,也就是现在拿到的Tiny4412开发板内核,Linux 版本是3.5,不支持设备树。

image-20220118112542646

2. VS1053硬件介绍

VS1053这款编码解码芯片在单片机里用的较多,性价比很高,因为支持SPI接口,所以单片机操作起来也比较容易,编码解码都是芯片内部完成,不消耗CPU资源,芯片的电压支持是3.3V。

可以使用VS1053设计MP3播放器,比如:用在跑步机上听歌,用在便携式音箱里放歌,做复读机、录音笔 等等。

解码的音频格式支持: MP3、OGG、WMA、WAV、MIDI、AAC、FLAC(需要加载 patch)

编码的音频格式支持: WAV(PCM/IMA ADPCM)、OGG(需要加载 patch)

VS1053使用的12.288M 的晶振, 在12.288MHz时钟下,最高到48000HZ的所有采样率都可以正常使用。

当前我采用的VS1053是正点原子设计的完整模块,方便杜邦线与开发板进行测试。

image-20220118112013747

模块引出的接口功能: 这是SPI接口引脚

image-20220118112050137

下面是SPI接口硬件的功能描述:

image-20220118112139303

SPI读时序:

image-20220118112228940

SPI写时序:

image-20220118112247295

VS1053模块与单片机之间的连线图:

image-20220118112348505

3. 驱动代码

3.1 驱动端代码

#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/spi/spi.h>
#include <linux/spi/spidev.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include "mp3_data.h"
#include <linux/miscdevice.h>   /*杂项字符设备头文件*/

#define VS_WRITE_COMMAND 	0x02  //写命令
#define VS_READ_COMMAND 	0x03  //读命令

//VS10XX寄存器定义
#define SPI_MODE        	0x00   
#define SPI_STATUS      	0x01   
#define SPI_BASS        	0x02   
#define SPI_CLOCKF      	0x03   
#define SPI_DECODE_TIME 	0x04   
#define SPI_AUDATA      	0x05   
#define SPI_WRAM        	0x06   
#define SPI_WRAMADDR    	0x07   
#define SPI_HDAT0       	0x08   
#define SPI_HDAT1       	0x09 
  
#define SPI_AIADDR      	0x0a   
#define SPI_VOL         	0x0b   
#define SPI_AICTRL0     	0x0c   
#define SPI_AICTRL1     	0x0d   
#define SPI_AICTRL2     	0x0e   
#define SPI_AICTRL3     	0x0f   
#define SM_DIFF         	0x01   
#define SM_JUMP         	0x02   
#define SM_RESET        	0x04   
#define SM_OUTOFWAV     	0x08   
#define SM_PDOWN        	0x10   
#define SM_TESTS        	0x20   
#define SM_STREAM       	0x40   
#define SM_PLUSV        	0x80   
#define SM_DACT         	0x100   
#define SM_SDIORD       	0x200   
#define SM_SDISHARE     	0x400   
#define SM_SDINEW       	0x800   
#define SM_ADPCM        	0x1000   
#define SM_ADPCM_HP     	0x2000 		 

#define I2S_CONFIG			 0XC040
#define GPIO_DDR			 0XC017
#define GPIO_IDATA			 0XC018
#define GPIO_ODATA			 0XC019

/*
Tiny4412与VS1053硬件连接:
	VCC--3V~5V
	GND--0V
	SCK---SCLK:GPB_0
	SI---MOSI:GPB_3
	SO---MISO:GPB_2
	XCS--CS  :GPB_1
	DREQ-----:GPB_5
	XDCS-----:GPB_4
	RST------:GPB_6
*/
void VS1053_Init(void);
u16  VS1053_ReadReg(u8 address);				//读寄存器
u16  VS1053_ReadRAM(u16 addr);	    	  		//读RAM
void VS1053_WriteRAM(u16 addr,u16 val);			//写RAM
void VS1053_WriteData(u8 data);				  	//写数据
void VS1053_WriteCmd(u8 address,u16 data);		//写命令
u8   VS1053_Reset(void);			    		//硬复位
void VS1053_SoftReset(void);           			//软复位
u8 	 VS1053_SPI_ReadWriteByte(u8 data);     	//SPI接口,读写一个字节 
void VS1053_SoftReset(void);					//初始化VS1053	 
u8 	 VS1053_SendMusicData(u8* buf);				//向VS10XX发送32字节 
void VS1053_SetVol(u8 volx);				  	//设置主音量   


/*
函数功能:移植接口--SPI时序读写一个字节
函数参数:data:要写入的数据
返 回 值:读到的数据
*/
u8 VS1053_SPI_ReadWriteByte(u8 tx_data)
{			  	 
   u8 rx_data=0;				 
   u8 i;
   for(i=0;i<8;i++)
	{
		gpio_set_value(EXYNOS4_GPB(0), 0);  
		if(tx_data&0x80){gpio_set_value(EXYNOS4_GPB(3), 1);}
		else {gpio_set_value(EXYNOS4_GPB(3), 0);}
		tx_data<<=1;	
		gpio_set_value(EXYNOS4_GPB(0), 1);
		rx_data<<=1;
		if(gpio_get_value(EXYNOS4_GPB(2)))rx_data|=0x01;
	}
	return rx_data; 
}


/*
函数功能:软复位VS10XX
*/
void VS1053_SoftReset(void)
{	 
	u8 retry=0;  				   
	while(gpio_get_value(EXYNOS4_GPB(5))==0); //等待软件复位结束	   
	VS1053_SPI_ReadWriteByte(0Xff);			  //启动传输
	retry=0;
	while(VS1053_ReadReg(SPI_MODE)!=0x0800)	  // 软件复位,新模式  
	{
		VS1053_WriteCmd(SPI_MODE,0x0804);	  // 软件复位,新模式	    
		msleep(2);//等待至少1.35ms 
		if(retry++>100)break; 	  
	}	
	while(gpio_get_value(EXYNOS4_GPB(5))==0); //等待软件复位结束	 
	retry=0;
	while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800) //设置VS10XX的时钟,3倍频 ,1.5xADD 
	{
		VS1053_WriteCmd(SPI_CLOCKF,0X9800);	  //设置VS10XX的时钟,3倍频 ,1.5xADD
		if(retry++>100)break; 	    
	}	 
	msleep(20);
}


/*
函数 功 能:硬复位MP3
函数返回值:1:复位失败;0:复位成功	
*/
u8 VS1053_Reset(void)
{
	u8 retry=0;
	gpio_set_value(EXYNOS4_GPB(6), 0);
	msleep(20);
	gpio_set_value(EXYNOS4_GPB(4), 1);//取消数据传输
	gpio_set_value(EXYNOS4_GPB(1), 1); //取消数据传输
	gpio_set_value(EXYNOS4_GPB(6), 1);	   
	while(gpio_get_value(EXYNOS4_GPB(5))==0&&retry<200)//等待DREQ为高
	{
		retry++;
		udelay(50);
	};
	msleep(20);	
	if(retry>=200)return 1;
	else return 0;	    		 
}


/*
函数功能:向VS10XX写命令
函数参数:
				address:命令地址
				data   :命令数据
*/
void VS1053_WriteCmd(u8 address,u16 data)
{  
	while(gpio_get_value(EXYNOS4_GPB(5))==0);	//等待空闲		   	   
	gpio_set_value(EXYNOS4_GPB(4), 1); 	 
	gpio_set_value(EXYNOS4_GPB(1), 0); 	 
	VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
	VS1053_SPI_ReadWriteByte(address); 			//地址
	VS1053_SPI_ReadWriteByte(data>>8); 			//发送高八位
	VS1053_SPI_ReadWriteByte(data);	 			//第八位
	gpio_set_value(EXYNOS4_GPB(1), 1);            
} 


/*
函数参数:向VS1053写数据
函数参数:data:要写入的数据
*/
void VS1053_WriteData(u8 data)
{
	gpio_set_value(EXYNOS4_GPB(4), 0);   
	VS1053_SPI_ReadWriteByte(data);
	gpio_set_value(EXYNOS4_GPB(4), 1);      
}


/*
函数功能:读VS1053的寄存器 
函数参数:address:寄存器地址
返回值:读到的值
*/
u16 VS1053_ReadReg(u8 address)
{ 
	u16 temp=0;   	
    while(gpio_get_value(EXYNOS4_GPB(5))==0);//非等待空闲状态   	
	gpio_set_value(EXYNOS4_GPB(4), 1);       
	gpio_set_value(EXYNOS4_GPB(1), 0);        
	VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令
	VS1053_SPI_ReadWriteByte(address);       	//地址
	temp=VS1053_SPI_ReadWriteByte(0xff); 		  //读取高字节
	temp=temp<<8;
	temp+=VS1053_SPI_ReadWriteByte(0xff); 		//读取低字节
	gpio_set_value(EXYNOS4_GPB(1), 1);      
   return temp; 
}  


/*
函数功能:读取VS1053的RAM
函数参数:addr:RAM地址
返 回 值:读到的值
*/
u16 VS1053_ReadRAM(u16 addr) 
{ 
	u16 res;			   	  
 	VS1053_WriteCmd(SPI_WRAMADDR, addr); 
	res=VS1053_ReadReg(SPI_WRAM);  
 	return res;
} 


/*
函数功能:写VS1053的RAM
函数参数:
		addr:RAM地址
		val:要写入的值 
*/
void VS1053_WriteRAM(u16 addr,u16 val) 
{  		   	  
 	VS1053_WriteCmd(SPI_WRAMADDR,addr);	//写RAM地址 
	while(gpio_get_value(EXYNOS4_GPB(5))==0); 							//等待空闲	   
	VS1053_WriteCmd(SPI_WRAM,val); 			//写RAM值 
} 


/*
函数参数:发送一次音频数据,固定为32字节
返 回 值:0,发送成功
				  1,本次数据未成功发送   
*/ 
u8 VS1053_SendMusicData(u8* buf)
{
	u8 n;
	if(gpio_get_value(EXYNOS4_GPB(5))!=0)  //送数据给VS10XX
	{			   	 
		gpio_set_value(EXYNOS4_GPB(4), 0);  
		for(n=0;n<32;n++)
		{
			VS1053_SPI_ReadWriteByte(buf[n]);	 			
		}
		gpio_set_value(EXYNOS4_GPB(4), 1);     				   
	}else return 1;
	return 0;//成功发送了
}


/*
函数功能:设定VS1053播放的音量
函数参数:volx:音量大小(0~254)
*/
void VS1053_SetVol(u8 volx)
{
    u16 volt=0; 			      //暂存音量值
    volt=254-volx;			      //取反一下,得到最大值,表示最大的表示 
	volt<<=8;
    volt+=254-volx;				  //得到音量设置后大小
    VS1053_WriteCmd(SPI_VOL,volt);//设音量 
}


/*
函数功能:VS1053初始化
Tiny4412硬件连接:
	VCC--3V~5V
	GND--0V
	SCK---SCLK:GPB_0
	SI---MOSI:GPB_3
	SO---MISO:GPB_2
	XCS--CS  :GPB_1
	DREQ-----:GPB_5
	XDCS-----:GPB_4
	RST------:GPB_6
*/
void VS1053SpiInit(void)
{
	/*1. 注册GPIO*/
	gpio_request(EXYNOS4_GPB(0), "VS1053_CLK-SCLK");
	gpio_request(EXYNOS4_GPB(1), "VS1053_CS");
	gpio_request(EXYNOS4_GPB(2), "VS1053_MISO");
	gpio_request(EXYNOS4_GPB(3), "VS1053_MOSI");
	gpio_request(EXYNOS4_GPB(4), "VS1053_XDCS");
	gpio_request(EXYNOS4_GPB(5), "gpio_get_value(EXYNOS4_GPB(5))");
	gpio_request(EXYNOS4_GPB(6), "VS1053_RST");
	
	/*2. 配置GPIO口模式*/
	s3c_gpio_cfgpin(EXYNOS4_GPB(0), S3C_GPIO_OUTPUT);  //时钟
	s3c_gpio_cfgpin(EXYNOS4_GPB(1), S3C_GPIO_OUTPUT);  //片选
	s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_INPUT);   //输入模式
	s3c_gpio_cfgpin(EXYNOS4_GPB(3), S3C_GPIO_OUTPUT);  //输出模式
	s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT);  //输出模式
	s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_INPUT);   //输入模式
	s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_OUTPUT);  //输出模式
	
	/*3. 上拉GPIO口*/
	gpio_set_value(EXYNOS4_GPB(0), 1);
	gpio_set_value(EXYNOS4_GPB(1), 1);
	gpio_set_value(EXYNOS4_GPB(3), 1);
	gpio_set_value(EXYNOS4_GPB(4), 1);
	gpio_set_value(EXYNOS4_GPB(6), 1);
}




/*****************************************************************************************************/
static int tiny4412_open(struct inode *my_inode, struct file *my_file)
{
	printk("VS1053 open函数调用成功!\r\n");
	return 0;
}


static int tiny4412_release(struct inode *my_inode, struct file *my_file)
{
	printk("VS1053 release函数调用成功!\r\n");
	return 0;
}


static u8 Music_buff[32];
static ssize_t tiny4412_write(struct file *my_file, const char __user *buf, size_t len, loff_t *loff)
{
	if(0!=copy_from_user(Music_buff,buf,len))printk("拷贝错误!\r\n");  //每次接收32个字节数据			 
	while(VS1053_SendMusicData(Music_buff));    //给VS10XX发送音频数据
	return len;
}

#define VS1053_INIT_SET 188
static long tiny4412_unlocked_ioctl(struct file *my_file, unsigned int cmd, unsigned long data)
{
	switch(cmd)
	{
		case VS1053_INIT_SET:
			VS1053_Reset();      								//硬复位MP3
			VS1053_SoftReset(); 								//软复位VS10XX
			VS1053_SetVol(250);								    //设置音量	
			printk("VS1053设置成功!\r\n");
			break;
	}
	return 0;
}


/*文件操作集合*/
static struct file_operations tiny4412_fops=
{
	.open=tiny4412_open,
	.write=tiny4412_write,
	.release=tiny4412_release,
	.unlocked_ioctl=tiny4412_unlocked_ioctl
};


/*
核心结构体
*/
static struct miscdevice tiny4412_misc=
{
	.minor=MISC_DYNAMIC_MINOR,  /*自动分配次设备号*/
	.name="tiny4412_vs1053",       /*设备文件,指定/dev/生成的文件名称*/
	.fops=&tiny4412_fops
};


static int __init VS1053_init(void)
{
	VS1053SpiInit(); //初始化GPIO口
	/*杂项设备注册*/
    misc_register(&tiny4412_misc);
	return 0;
}


static void __exit VS1053_exit(void)
{
	/*释放GPIO口*/
	gpio_free(EXYNOS4_GPB(0));
	gpio_free(EXYNOS4_GPB(1));
	gpio_free(EXYNOS4_GPB(2));
	gpio_free(EXYNOS4_GPB(3));
	gpio_free(EXYNOS4_GPB(4));
	gpio_free(EXYNOS4_GPB(5));
	gpio_free(EXYNOS4_GPB(6));
	/*杂项设备注销*/
	misc_deregister(&tiny4412_misc);
	
	printk("VS1053 driver exit ok!\n");
}

module_exit(VS1053_exit);
module_init(VS1053_init);
MODULE_LICENSE("GPL");

3.2 应用层代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define VS1053_INIT_SET 188
int main(int argc,char **argv)
{
	char buff[32];
	int cnt,i=0;
    int vs1053_fd,file_fd;
	if(argc!=2)
	{
		printf("argv: ./app <mp3_file_name>\r\n");
		return -1;
	}
	vs1053_fd=open("/dev/tiny4412_vs1053",O_RDWR);
	file_fd=open(argv[1],2);
	if(vs1053_fd<0||file_fd<0)      /*判断文件是否打开成功*/
	{
		printf("vs1053 driver open error!\n");
		return -1;
	}
	
	ioctl(vs1053_fd,VS1053_INIT_SET);
	while(1)
	{
		cnt=read(file_fd,buff,32);
		write(vs1053_fd,buff,cnt);
		if(cnt!=32)break;
		i++;
	}
	close(vs1053_fd);
	close(file_fd);
	return 0;
}

3.3 Makefile 代码

KER_DRI=/work/Tiny4412/linux-3.5/
all:
	make -C $(KER_DRI) M=`pwd` modules
	cp ./*.ko /work/rootfs/tmp/
	make -C $(KER_DRI) M=`pwd` modules clean
	rm ./*.ko -rf
	arm-linux-gcc vs1053_app.c -o vs1053_app
	cp vs1053_app /work/rootfs/tmp/ -f
obj-m +=vs1053_drv.o
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200