一、 原理&作用
1. 作用
模拟电压信号转换为数字信号
(1) 原理
(2) 积分型
(3) 逐次比较型
(4) 并行比较型
二、 数据手册阅读
阅读《S5PC100_UM_REV104.pdf》如下内容:
章节 |
目的 |
10.07 ADC |
了解S5PC100芯片的ADC的功能性能及使用方法 |
三、名词解释
(1) Resolution
分辨率,每个电压值转换之后的二进制位数,位数越高,精度越高
(2) Differential Linearity Error
微分线性误差,转化出来的每个值,相对于正确值的误差
(3) Integral Linearity Error
积分线性误差,转化出来的曲线,相对于正确曲线的误差
四、实验
(1) 目的
(2) 查看原理图
(3) 设置
1. 多功能管脚设置
ADC使用的是专用模拟信号管脚,不需要设置
2. 时钟设置
(1) 开启时钟
置CLK_GATE_D1_5[7]为1
(2) 时钟分频
置ADCCON[6-14]为0x1ff
3. ADC参数设置
(1) 分辨率
置ADCCON[16]为1(12bit)
4. ADC通道选择
置ADCMUX [0:3]为0b0000
5. 使能
置ADCCON[1:2]为0b10
6. 读取
等ADCCON[15]为1时,读取ADCDAT0[0:11]值
注意:设置完使能ADC后,需要直接读取一次ADCDAT0[0:11]
一、作用&原理
1. 作用
(1) 波形调制(Pulse Width Modulation)
(2) 定时器
2. 原理
利用时钟信号实现定时功能,然后利用定时功能控制输出高电平或低电平的时间。具体实现如下:
1. 分频
利用Prescaler和Divider对输入时钟进行分频
2. 定时
利用Down Counter和分频产生的时钟信号实现定时,时钟信号每过一个周期,Down Counter的值减1,Down Counter值到0时,定时结束, Down Counter值存储在TCNT寄存器
3. 波形调制
TCNT寄存器的值递减到等于TCMP寄存器的值时,输出管脚电平反转。TCNT的值到达0时,PWM会自动加载TCNTB寄存器的值到TCNT,TCMPB寄存器的值到TCMP,开始一个新的定时周期,从而实现周期性方波
二、 数据手册阅读
阅读《S5PC100_UM_REV104.pdf》如下内容:
章节 |
目的 |
7.01 PWM |
了解S5PC100芯片的PWM功能性能及使用方法 |
三、名词解释
(1) 占空比
占空比 = 高电平时间/(高电平时间+低电平时间)
(2) Prescaler
输出时钟频率 = 输入时钟频率 / 分频因子(factor)
(3) Divider
输出时钟频率 = 输入时钟频率 / 除法因子(factor )
(4) Mux
多路选择,Divider一定要和Mux联合使用
四、 实验
1. 目的
(1) 理解PWM基本原理
(2) 会使用S5PC100中的PWM
2. 查看原理图
查看《FS_S5PC100_DEV.pdf》文档的beep电路图
3. 设置
1. 多功能管脚设置
置GPDCON[ 4:7]为0b0010(PWM管脚控制)
2. 时钟设置
(1) 时钟开关
置CLK_GATE_D1_3[6]为1(打开PCLK至PWM开关)
(2) 配置分频器
置TCFG0[0:7]为0xff(Prescaler的分频因子为256)
(3) 配置除法器
置TCFG1[ 4:7] 为0b0100(除法因子1/16)
(4) 配置定时器
置TCNTB1寄存器的值决定定时时间
置TCMPB1寄存器的值,决定电平反转时间
(5) 手动加载
置TCON[9]为1,然后置TCON[9]为0
(加载TCNTB0寄存器到TCNT0寄存器,加载TCMPB0寄存器到TCMP0寄存器)
(6) 开启PWM
置TCON[8:11]为0b1001
【IIC标准手册】
1. 原理&作用
(1) 作用
用于主从设备数据传输的总线,它支持挂接多个主设备和多个从设备,但是一次通讯只能有一个主设备;
通讯速度为标准速度100kbps,快速400kbps,高速3.4Mbps
(2) 原理
SCL:时钟管脚,时钟由主设备产生
SDA:数据管脚,数据可以双向传输
Rp:上拉电阻,空闲时,将SCL和SDA上拉成高电平
2.查看数据手册
阅读《S5PC100_UM_REV104.pdf》如下内容:
章节 |
目的 |
8.02章 |
理解I2C总线原理及s5pc100中I2C总线使用 |
3.名词解释
(1) Transfer
(2) Start和Stop
1. 等待数据(wait)
2. 从端请求停止(Abort)
3. ACK
四、 驱动原理
五、 接口驱动
1. 初始化
1. 多功能管脚设置
置GPDCON [12:15][16:19]为0b0010
2. 时钟(速度)设置(100k)
(1) 时钟分频
置I2CCON0 [0:3]为0
(2) 时钟除法
置I2CCON0 [6]为1
3. ACK设置
置I2CCON0[7]为1
4. 使能发送接收中断
置I2CCON0[5]为1
2. 数据传送
(1) 发送地址
置I2CDS0[1:7]为地址
置I2CDS0[0]为0
(2) 设置主发送模式
置I2CSTAT0[6:7]为3
(3) 使能发送接收
置I2CSTAT0 [4]为1
(4) 启动发送
置I2CSTAT0 [5]为1
(5) 等待发送完成
等待I2CCON0[4]为1
(6) 发送数据
置I2CDS0为数据
(7) 清除中断标志
置I2CCON0[4]为0
(8) 等待发送完成
等待I2CCON0[4]为1
3 接收数据
(1) 发送地址
置I2CDS0[1:7]为地址
置I2CDS0[0]为1
(2) 设置主接收模式
置I2CSTAT0[6:7]为2
(3) 使能发送接收
置I2CSTAT0 [4]为1
(4) 启动接收
置I2CSTAT0 [5]为1
(5) 清除中断标志
置I2CCON0[4]为0
(6) 等待地址发送完成
等待I2CCON0[4]为1
(7) 清除中断标志
置I2CCON0[4]为0
(8) 等待接收完成
等待I2CCON0[4]为1
(9) 读取接收数据
读取I2CDS0中的数据
4 停止发送接收
置I2CSTAT0 [5]为0
置I2CCON0 [4]为0
/***************************************************I2C设备驱动**********************************************************************/
/*linux的I2C驱动分为3部分:
*1:I2C核心:
*I2C核心核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm(算法))上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
*
*2:I2C总线驱动:
* I2C总线驱动是对I2C硬件体系结构中适配器(就是i2c控制器)端的实现,适配器可由CPU控制。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构
*i2c_algorithm和控制I2C适配器产生通信信号的函数经由I2C总线驱动的代码,我们可控制I2C适配器以产生主控方式的开始位、停止位、读写周期以及以从设备方式被读写、产生ACK等。
*
*3:I2C设备驱动:
*I2C设备驱动(也称客户程序)是对I2C硬件体系结构中设备端的实现,设备一般挂接在I2C适配器上,I2C设备驱动主要包括数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
*
*在linux2.6内核中,所有的I2C设备都在sysfs文件系统中显示,存于/sys/bus/i2c/目录下。
*
*linux内核源代码中的drivers下的i2c目录包含如下文件和目录:
*1:i2c-core.c 这个文件实现了I2C核心的功能以及/proc/bus/i2c接口
*
*2:i2c-dev.c 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,此设备号为0-255,应用程序通过“i2c-%d”(i2c-0,i2c-1,......)文件名
* 并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问设备,从而访问挂接在I2C适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
*
*3:chip目录 这个目录包含了一些特定的I2C设备驱动,如 DS1337实时时钟芯片、I2C接口的EEPROM驱动等,在具体的I2C设备驱动中,调用的都是I2C核心提供的接口,因 此,这使得具体的I2C设备驱动不依赖于
* CPU的类型和I2C控制器的硬件特性。
*
*4:busses目录 这个文件中包含一些I2C总线的驱动,如针对S3C2410、S3C2440、S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c。
*
*5:algos目录 实现了一些I2C总线适配器的algorithm。
*
*此外在i2c.h中对i2c_driver 、i2c_client 、i2c_adapter 、i2c_algorithm这4个数据结构进行了定义,理解这4个结构体非常重要
*/
--------------------------------------------struct i2c_adapter----------------------------------------------------
struct i2c_adapter { //该结构对应物理上的一个适配器(I2C控制器),一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期,缺少i2c_algorithm的i2c_adapter什么都做不了,
//因此该结构中包含了i2c_algorithm指针
struct module *owner; //THIS_MODULE
unsigned int id; //algorithms的类型,定义于i2c-id.h,以I2C_ALGO_开始
unsigned int class; /* classes to allow probing for */ //允许探测的类
const struct i2c_algorithm *algo; /* the algorithm to access the bus */ //总线通信方法结构体指针
void *algo_data; //algorithm数据,多数I2C总线驱动会定义设备私有结构体,通常用此指针指向它
/* --- administration stuff. */
int (*client_register)(struct i2c_client *) __deprecated; //client注册时调用
int (*client_unregister)(struct i2c_client *) __deprecated; //client注销时调用
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock; //控制并发的信号量
struct mutex clist_lock;
int timeout; /* in jiffies */
int retries; //重试次数
struct device dev; /* the adapter device */ //适配器设备
int nr; //适配器的个数,若超过一个则应该调用int i2c_add_numbered_adapter(struct i2c_adapter *adap)函数来添加适配器
struct list_head clients; /* DEPRECATED */ //client链表头(由于一个适配器上可以连接多个I2C设备,所以一个i2c_adapter可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client的链表)
char name[48]; //适配器名称
struct completion dev_released; //用于同步
};
-----------------------------------------------------------------------------------------------------------------
--------------------------------------------struct i2c_algorithm-------------------------------------------------
struct i2c_algorithm { //该结构对应一套通信方法
/* If an adapter algorithm can‘t do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
//I2C传输函数指针,用于产生I2C访问周期需要的信号,以i2c_msg(I2c消息)为单位,该结构也非常重要,其定义如下:
//struct i2c_msg { //早期I2C设备上读写数据的时序和数据通常通过该结构体数组来组织,最后通过i2c_transfer发送(实际还是通过master_xfer发送)
// __u16 addr; //低7位是设备地址,
// __u16 flags; //标志,标志读(flags=1)还是写(flags=0)
// #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ //芯片的地址是10位
// #define I2C_M_RD 0x0001 /* read data, from slave to master */ //从设备读取数据
// #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
// #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
// #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
// #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
// #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
// __u16 len; //消息长度
// __u8 *buf; // 指向消息数据的指针
// };
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, //smbus传输函数指针,SMbus大部分基于I2C总线规范,SMbus不需要增加额外的引脚,与I2C总线相比,
//SMbus增加了一些新的功能特性,在访问时序也有一定的差异
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *); //返回适配器支持的功能
};
----------------------------------------------------------------------------------------------------------------
--------------------------------------------struct i2c_driver---------------------------------------------------
struct i2c_driver { //该结构提供了一套驱动的方法,其主要成员函数probe()、remove()、suspend()、resume()等
int id;
unsigned int class;
/* Notifies the driver that a new bus has appeared. This routine
* can be used by the driver to test if the bus meets its conditions
* & seek for the presence of the chip(s) it supports. If found, it
* registers the client(s) that are on the bus to the i2c admin. via
* i2c_attach_client. (LEGACY I2C DRIVERS ONLY)
*/
int (*attach_adapter)(struct i2c_adapter *);
//依附i2c_adapter函数指针,I2C driver在调用I2C_add_driver() 注册时,
//对发现的每一个I2C adapter都要调用该函数,检查该I2C adapter是否符合I2C driver的特定条件,
//如果符合条件则连接此I2C adapter,并通过I2C adapter来实现对I2C总线
//及I2C设备的访问( 直接调用I2C核心的i2c_probe函数)。
int (*detach_adapter)(struct i2c_adapter *);
//脱离i2c_adapter函数指针,I2C driver在删除一个I2C device时调用该函数,
//清除描述这个I2C device的数据结构,这样以后就不能访问该设备了
/* tells the driver that a client is about to be deleted & gives it
* the chance to remove its private data. Also, if the client struct
* has been dynamically allocated by the driver in the function above,
* it must be freed here. (LEGACY I2C DRIVERS ONLY)
*/
int (*detach_client)(struct i2c_client *) __deprecated; //i2c_client脱离函数指针
/* Standard driver model interfaces, for "new style" i2c drivers.
* With the driver model, device enumeration is NEVER done by drivers;
* it‘s done by infrastructure. (NEW STYLE DRIVERS ONLY)
*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //探测函数
int (*remove)(struct i2c_client *); //移除函数
/* driver model interfaces that don‘t relate to enumeration */
void (*shutdown)(struct i2c_client *); //关闭设备
int (*suspend)(struct i2c_client *, pm_message_t mesg);//挂起设备
int (*resume)(struct i2c_client *); //恢复设备
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
//该驱动所支持的设备类型,是该驱动所支持的I2C设备的ID表,在i2c总线驱动i2c_bus_type的mach()函数
//i2c_device_match()中会调用i2c_match_id()
//函数比较i2c_client中的name和i2c_driver的id_table->name是否相同,若相同则支持。
//
//struct i2c_device_id {
// char name[I2C_NAME_SIZE];
// kernel_ulong_t driver_data /* Data private to the driver */
// __attribute__((aligned(sizeof(kernel_ulong_t))));
// };
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
----------------------------------------------------------------------------------------------------------------
--------------------------------------------struct i2c_client---------------------------------------------------
struct i2c_client {
//该结构对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述,i2c_driver与i2c_client的关系是一对多,
//一个i2c_driver上可以支持多个同等类型的i2c_client。由于一个适配器上可以连接多个
//I2C设备,所以一个i2c_adapter可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client的链表。
unsigned short flags; /* div., see below */ //标志
unsigned short addr; /* chip address - NOTE: 7bit */ //低7位为芯片地址
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE]; //设备名称,在i2c总线驱动i2c_bus_type的mach()函数
//i2c_device_match()中会调用i2c_match_id()函数比较name和i2c_driver
//的id_table->name是否相同,若相同则支持
struct i2c_adapter *adapter; /* the adapter we sit on */ //依附的i2c_adapter
struct i2c_driver *driver; /* and our access routines */ //依附的i2c_driver
struct device dev; /* the device structure */ //设备结构体
int irq; /* irq issued by device */ //使用的中断号
struct list_head list; /* DEPRECATED */ //链表头
struct list_head detected;
struct completion released; //用于同步
};
----------------------------------------------------------------------------------------------------------------
/*
*如何写linux的I2C设备驱动程序:
*
*首先适配器可能是linux内核本身还不包含的,其次,挂接在适配器上的具体设备驱动可能也是linux内核还不包含的,
*因此需要实现的主要工作如下:
*
*1:(总线驱动)提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的
* I2C适配器从硬件上产生各种信号以及处理I2C中断等
*
*2:(总线驱动)提供I2C适配器的algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,
* 并把i2c_algorithm指针赋值给i2c_adapter的algo指针
*
*3:(设备驱动)实现I2C设备驱动中的i2c_driver接口,用具体设备xxx的xxx_probe() 、xxx_remove() 、xxx_suspend() 、
* xxx_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、suspend、resume和id_table指针
*
*4:(设备驱动)实现I2C设备所对应类型的具体驱动,i2c_driver只是实现了设备与总线的挂接,而挂接在总线上的设备
* 则是千差万别。例如,如果是字符设备,就实现文件系统接口(read,write等系统调用的操作函数)如果是声卡,就实现ALSA驱动
*
*I2C核心中提供了一组不依赖于硬件平台的接口函数:
*/
int i2c_add_adapter(struct i2c_adapter *adapter); //增加i2c_adapter(该函数中注册了i2c_adapter)
int i2c_del_adapter(struct i2c_adapter *adap); //删除i2c_adapter(该函数中注销了i2c_adapter)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver); //注册
void i2c_del_driver(struct i2c_driver *driver); //删除i2c_driver(该函数中注销了i2c_driver)
int i2c_add_driver(struct i2c_driver *driver); //增加i2c_driver(该函数中注册了i2c_driver)
int i2c_attach_client(struct i2c_client *client);
//依附i2c_client,当一个具体的client被侦测到被关联的时候,设备和sysfs文件将被注册(device_register(&client->dev))
int i2c_detach_client(struct i2c_client *client);
//脱离i2c_client,当一个具体的client被脱离的时候,sysfs文件和设备被注销(device_unregister(&client->dev))
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
//i2c的传输(该函数用于进行I2C适配器和I2C设备之间的一组消息互传,该函数实际使用了i2c_algorithm的master_xfer函数真正驱动硬件),num为i2c_msg的个数
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
//i2c的发送(该函数会调用i2c_transfer来完成一条消息的写),buf为指向要发送数据的指针,count为要发送的数据的大小(单位为字节)
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
//i2c的接收(该函数会调用i2c_transfer来完成一条消息的读)
---------------------------------------------------------------------------------------------------------------
/*
*I2C总线驱动:
*
*加载模块的任务:
*1:初始化I2C适配器所使用的硬件资源,如申请I/O地址,中断号等
*2:初始化i2c_adapter数据结构成员
*3:通过i2c_add_adapter添加i2c_adapter的数据结构
*
*卸载模块:
*1:释放I2C适配器所使用的硬件资源,如释放I/O地址、中断号等
*2:通过i2c_del_adapter删除i2c_adapter数据结构
*
*另外我们还需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer函数和functionality函数:
*
*1:functionality函数非常坚持,用于返回algorithm所支持的协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR等在i2c.h中定义
*2:master_xfer函数在i2c适配器上完成传输给它的i2c_msg数组中的每个I2C信息,其模板如下(当然也可以中断方式实现):
*/
static int i2c_adapter_xxx_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
.......
for(i=0;i<num;i++)
{
i2c_adapter_xxx_start(); //产生开始位,需要根据具体硬件实现
if(msgs[i]->flags&I2C_M_RD) //判断是如果是读
{
i2c_adapter_xxx_setaddr((msgs->addr<<1)|1); //发送从设备地址,需要根据具体硬件实现
i2c_adapter_xxx_wait_ack(); //等待应答信号,需要根据具体硬件实现
i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);
//读取长度为msgs[i]->len的数据到msgs[i]->buf,用于从设备接收一串数据,该函数内部也会涉及应答,需要根据具体硬件实现
}
else //否则是写数据
{
i2c_adapter_xxx_setaddr(msgs->addr<<1);//发送从设备地址,需要根据具体硬件实现
i2c_adapter_xxx_wait_ack(); //等待应答信号,需要根据具体硬件实现
i2c_adapter_xxx_writebytes(msgs[i]->buf,msgs[i]->len);
//写长度为msgs[i]->len的数据到msgs[i]->buf,用于向设备写入一串数据,该函数内部也会涉及应答,需要根据具体硬件实现
}
}
i2c_adapter_xxx_stop(); //产生停止位,需要根据具体硬件实现
}
//参考总线驱动:i2c-s3c2410.c
-------------------------------------------------------------------------------------------------------------
//I2C设备驱动:
//1:I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数,i2c_client一般被包含在设备的私有信息结构体中,
//而i2c_driver则合适被定义为全局变量并初始化,如下:
static struct i2c_driver xxx_driver = {
.driver = {
.name = "xxx",
.owner = THIS_MODULE,
},
.probe = xxx_probe,
.remove = __devexit_p(xxx_remove),
.id_table = xxx_ids,
.attach_adapter = xxx_attach_adapter,
.detach_adapter = xxx_detach_adapter,
};
//2:定义分配一个i2c_client数据结构并初始化它 ,例如:
xxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
xxx_client->addr = address;
xxx_client->adapter = adapter;
xxx_client->driver = &xxx_driver;
strcpy(xxx_client->name, "xxx");
i2c_attach_client(xxx_client);
//2:通过i2c_add_driver函数添加i2c_driver(也即注册)
//3:通过i2c_del_driver函数删除i2c_driver(也即注销)
//4:定义和设置i2c_msg数组,最后调用i2c_transfer函数来传输数据,例如:
msg[0].addr = xxx_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+数据=2 byte */
msg[0].flags = 0; /* 表示写 */
ret = i2c_transfer(xxx_client->adapter, msg, 1);
/*
*参考设备驱动:i2c-dev.c
*关于i2c-dev.c 的说明:该文件可以被看作一个I2C设备驱动,不过,它实现了一个虚拟i2c_client是虚拟、临时的,
*该文件实现了i2c_driver的成员函数已经文件操作接口,所以该文件的主体是“i2c_driver成员函数+字符设备驱动”
*但是很遗憾,大多数稍微复杂一点I2C设备的读写并不对应于一条消息,往往需要两条以上的消息来进行一次读写周期,
*因此该文件下 i2cdev_read和i2cdev_write函数不具备太强的通用性,对于两条以上消息组成的读写,在用户空间
*需要组织i2c_msg消息数组并调用I2C_RDWRIOCTL命令(ioctl系统调用)
*
*常用的IOCTL包括I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备ACK情况下重试次数,默认为1)、
*I2C_TIMEOU(超时)、I2C_RDWR。
*/
/*********************************************************end*****************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <plat/adc.h>
#include <plat/regs-adc.h>
MODULE_LICENSE ("GPL");
int adc_major = 250;
int adc_minor = 0;
int number_of_devices = 1;
struct s3c_adc_client *client;
struct cdev cdev;
dev_t devno = 0;
static ssize_t adc_convert_read(struct file *file, char __user *buff, size_t count, loff_t *offset)
{
unsigned data;
unsigned ch;
data = 10;
ch = 0;
/*
* @brief 读取adc的采样值
* @param[in] client adc客户端(从系统注册过来)
* @param[in] ch 读取的通道
* @return 读取的采样值(电压值 = 采样值 / (2 ^ 量化位数) * 电压范围)
*/
// int s3c_adc_read(struct s3c_adc_client *client, unsigned int ch);
data = s3c_adc_read(client, ch);
printk("data0 = %d\n", data);
// 拷贝数据到用户空间
if(copy_to_user(buff, (char *)&data, sizeof(data)))
return -EFAULT;
return 0;
}
static int adc_convert_open(struct inode *inode, struct file *file)
{
return 0;
}
static int adc_convert_release(struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations adc_convert_fops = {
.owner = THIS_MODULE,
.read = adc_convert_read,
.open = adc_convert_open,
.release = adc_convert_release,
};
/*
* @brief 探测函数,当设备和驱动匹配,由总线的probe函数调用
* @param[in] pdev 平台设备(BSP)
* @return @li 0 加载成功
* @li < 0 错误码
* @notes __devinit 修饰probe为设备初始化函数
*/
int __devinit adc_convert_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = -EINVAL;
printk("function = %s\n", __func__);
// 求设备号
devno = MKDEV(adc_major, adc_minor);
// 申请设备号
ret = register_chrdev_region(devno, number_of_devices, "adc_convert");
if( ret )
{
dev_err(dev, "failed to register device number\n");
goto err_register_chrdev_region;
}
// 初始化字符设备
cdev_init(&cdev, &adc_convert_fops);
cdev.owner = THIS_MODULE;
// 添加字符设备到内核
ret = cdev_add(&cdev, devno, number_of_devices);
if( ret )
{
dev_err(dev, "failed to add device\n");
goto err_cdev_add;
}
// 1. 注册adc客户端
/*
* @brief 注册ADC设备(client)
* @param[in] pdev 平台设备(BSP中添加)
* @param[in] select 选择函数(一般使用默认)
* @param[in] conv 转换函数(一般不使用,在外面转)
* @param[in] is_ts 是否是触摸屏
* @return 返回adc客户端 三星的adc使用架构是client-->server
*
struct s3c_adc_client *s3c_adc_register(
struct platform_device *pdev,
void (*select)(struct s3c_adc_client *client, unsigned int selected),
void (*conv)(struct s3c_adc_client *client, unsigned d0,
unsigned d1,unsigned*samples_left),
unsigned int is_ts);
*/
client = s3c_adc_register (pdev, NULL, NULL, 0);
if(IS_ERR( client ))
{
dev_err(dev, "failed to register adc client\n");
goto err_s3c_adc_register;
}
return 0;
err_s3c_adc_register:
cdev_del( &cdev );
err_cdev_add:
unregister_chrdev_region(devno, number_of_devices);
err_register_chrdev_region:
return ret;
}
static int __devexit adc_convert_remove(struct platform_device *pdev)
{
// 2. 注销
/*
* @brief 注销adc客户端
* @param[in] client 注销的客户端
*/
s3c_adc_release(client);
cdev_del( &cdev );
unregister_chrdev_region(devno, number_of_devices);
return 0;
}
static struct platform_driver adc_convert_driver = {
.driver = {
.name = "adc_convert",
.owner = THIS_MODULE,
},
.probe = adc_convert_probe,
.remove = __devexit_p(adc_convert_remove)
};
static int __init adc_convert_init (void)
{
return platform_driver_register( &adc_convert_driver );
}
static void __exit adc_convert_exit (void)
{
platform_driver_unregister( &adc_convert_driver );
}
module_init (adc_convert_init);
module_exit (adc_convert_exit);
【PWM的Platform实现】
【驱动头文件pwm.h】
#ifndef __PWM_H__
#define __PWM_H__
#define BEEP_ON _IO(‘k‘, 0)
#define BEEP_OFF _IO(‘k‘, 1)
#define SET_CNT _IO(‘k‘, 2)
#define SET_PRE _IO(‘k‘, 3)
#endif // __PWM_H__
#ifndef __PWM_MUSIC_H__
#define __PWM_MUSIC_H__
#define BIG_D
#define PCLK (66750000)
typedef struct
{
int pitch;
int dimation;
}Note;
// 1 2 3 4 5 6 7
// C D E F G A B
//261.6256 293.6648 329.6276 349.2282 391.9954 440 493.8833
//C大调
#ifdef BIG_C
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SOL 392
#define LA 440
#define SI 494
#define TIME 6000
#endif
//D大调
#ifdef BIG_D
#define DO 293
#define RE 330
#define MI 370
#define FA 349
#define SOL 440
#define LA 494
#define SI 554
#define TIME 6000
#endif
Note MumIsTheBestInTheWorld[] =
{
//6. //_5 //3 //5
{LA, TIME + TIME / 2}, {SOL, TIME / 2}, {MI, TIME}, {SOL, TIME},
//1^ //6_ //_5 //6-
{DO * 2, TIME}, {LA, TIME / 2}, {SOL, TIME / 2} ,{LA, 2 * TIME},
// 3 //5_ //_6 //5
{MI, TIME}, {SOL, TIME / 2}, {LA, TIME / 2}, {SOL, TIME},
// 3 //1_ //_6,
{MI, TIME}, {DO, TIME / 2}, {LA / 2, TIME / 2},
//5_ //_3 //2- //2.
{SOL, TIME / 2}, {MI, TIME / 2}, {RE, TIME * 2}, {RE, TIME + TIME / 2},
//_3 //5 //5_ //_6
{MI, TIME / 2}, {SOL, TIME}, {SOL, TIME / 2}, {LA, TIME / 2},
// 3 //2 //1- //5.
{MI, TIME}, {RE, TIME}, {DO, TIME * 2}, {SOL, TIME + TIME / 2},
//_3 //2_ //_1 //6,_
{MI, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME / 2},
//_1 //5,--
{DO, TIME / 2}, {SOL / 2, TIME * 3}
};
Note GreatlyLongNow[] = {
// 2 3 3 3. _2 1
{RE, TIME}, {MI, TIME}, {MI, TIME}, {MI, TIME + TIME / 2}, {RE, TIME / 2}, {DO, TIME},
//6, 1 2 1-- 2 3 3
{LA / 2, TIME}, {DO, TIME}, {RE, TIME}, {DO, TIME * 3}, {RE, TIME}, {MI, TIME}, {MI, TIME},
//3. _5 3 3 2 3
{MI, TIME + TIME / 2}, {SOL, TIME / 2}, {MI, TIME}, {MI, TIME}, {RE, TIME}, {MI, TIME},
//3-- 5 6 6 6. _5
{MI, TIME * 3}, {SOL, TIME}, {LA, TIME}, {LA, TIME}, {LA, TIME + TIME / 2}, {SOL, TIME / 2},
// 3 3 5 6 5--- 2 3
{MI, TIME}, {MI, TIME}, {SOL, TIME}, {LA, TIME}, {SOL, TIME * 3}, {RE, TIME}, {MI, TIME},
// 3 2. _3 3 2 3
{MI, TIME}, {RE, TIME + TIME / 2}, {MI, TIME / 2}, {MI, TIME}, {RE, TIME}, {MI, TIME},
//6, 1_ _6, 6,-
{LA / 2, TIME}, {DO, TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 2},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
// 2 3 1 2. _3 5
{RE, TIME}, {MI, TIME}, {DO, TIME}, {RE,TIME + TIME / 2}, {MI, TIME / 2}, {SOL, TIME},
//6_ _6 6_ _5 3
{LA, TIME / 2}, {LA, TIME / 2}, {LA, TIME / 2}, {SOL, TIME / 2}, {MI, TIME},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
//6, 5,. _6, 6,--
{LA / 2, TIME}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 3},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
// 2 3 1 2. _3 5
{RE, TIME}, {MI, TIME}, {DO, TIME}, {RE, TIME + TIME / 2}, {MI, TIME / 2}, {SOL, TIME},
//6_ _6 6_ _5 3
{LA, TIME / 2}, {LA, TIME / 2}, {LA, TIME / 2}, {SOL, TIME / 2}, {MI, TIME},
//2_ _2 2_ _1 6,
{RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},
//6, 5,. _6, 6,--
{LA / 2, TIME}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 3}
};
Note FishBoat[]={ //3. _5 6._ =1^ 6_
{MI, TIME + TIME / 2}, {SOL, TIME / 2}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2},
//_5 3 -. 2 1. _3 2._
{SOL, TIME / 2}, {MI, TIME * 3}, {RE, TIME}, {DO, TIME + TIME / 2}, {MI, TIME / 2},{RE, TIME / 2 + TIME / 4},
//=3 2_ _1 2-- 3. _5
{MI, TIME / 4}, {RE, TIME / 2}, {DO, TIME / 2}, {RE, TIME * 4}, {MI, TIME + TIME / 2}, {SOL, TIME / 2},
// 2 1 6._ =1^ 6_ _5
{RE, TIME}, {DO, TIME}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2}, {SOL, TIME / 2},
//6- 5,. _6, 1._ =3
{LA, TIME * 2}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {DO, TIME / 2 + TIME / 4}, {MI, TIME / 4},
//2_ _1 5,--
{RE, TIME / 2}, {DO, TIME / 2}, {SOL / 2, TIME * 4},
//3. _5 6._ =1^ 6_
{MI, TIME + TIME / 2}, {SOL, TIME / 2}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2},
//_5 3-. 5_ _6 1^_ _6
{SOL, TIME / 2}, {MI, TIME * 3}, {SOL, TIME / 2}, {LA, TIME / 2}, {DO * 2, TIME + TIME / 2}, {LA, TIME / 2},
//5._ =6 5_ _3 2--
{SOL, TIME / 2 + TIME / 4}, {LA, TIME / 4}, {SOL, TIME / 2}, {MI, TIME / 2}, {RE, TIME * 4},
//3. _5 2._ =3 2_ _1
{MI, TIME + TIME / 2}, {SOL, TIME / 2}, {RE, TIME / 2 + TIME / 4}, {MI, TIME / 4}, {RE, TIME / 2}, {DO, TIME / 2},
//6._ =1^ 6_ _5 6- 1.
{LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2}, {SOL, TIME / 2},{LA, TIME * 2}, {DO, TIME + TIME / 2},
//_2 3_ _5 2_ _3 1--
{RE, TIME / 2}, {MI, TIME / 2}, {SOL, TIME / 2}, {RE, TIME / 2}, {MI, TIME / 2}, {DO, TIME * 4}
};
#endif
/*
* 1. 使用platform总线上的字符设备框架
* 2. 添加pwm操作(本身按照面向对象的来写)
* 3. 添加并发处理
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
// 1. 信号量
#include <linux/semaphore.h>
#include "pwm.h"
struct pwm_t {
// 设备类型(字符设备、块设备、网络设备)
struct cdev cdev;
// 设备号
dev_t devno;
// 设备类
struct class *class;
// 设备
struct device *dev;
// 设备本身特征
// 信号量
struct semaphore sem;
};
void pwm_init(struct pwm_t *pwm)
{
unsigned long reg = 0;
// 多功能管脚
reg = readl(S5PC100_GPD_BASE);
reg &= ~(0xF << 4);
reg |= 0x2 << 4;
writel(reg, S5PC100_GPD_BASE);
// 清空分频器
reg = readl(S3C2410_TCFG0);
reg &= ~0xff;
writel(reg, S3C2410_TCFG0);
// 配置除法器为1
reg = readl(S3C2410_TCFG1);
reg &= ~(0xf << 4);
reg |= (0x2 << 4);
writel(reg, S3C2410_TCFG1);
// 配置定时器
writel(65, S3C2410_TCNTB(1));
writel(65 / 2, S3C2410_TCMPB(1));
// 手动更新
reg = readl(S3C2410_TCON);
reg |= (0x1 << 9);
writel(reg, S3C2410_TCON);
reg &= ~(0x1 << 9);
writel(reg, S3C2410_TCON);
// 开启
reg = readl(S3C2410_TCON);
reg &= ~(0xf << 8);
reg |= (0x9 << 8);
writel(reg, S3C2410_TCON);
}
void pwm_on(struct pwm_t *pwm)
{
unsigned long reg = 0;
reg = readl(S3C2410_TCON);
reg &= ~(0xf << 8);
reg |= (0x9 << 8);
writel(reg, S3C2410_TCON);
}
void pwm_off(struct pwm_t *pwm)
{
unsigned long reg = 0;
reg = readl(S3C2410_TCON);
reg &= ~(0xf << 8);
writel(reg, S3C2410_TCON);
}
void set_cnt(struct pwm_t *pwm, unsigned long arg)
{
printk("arg: %ld\n", arg);
writel(arg, S3C2410_TCNTB(1));
writel(arg / 2, S3C2410_TCMPB(1));
}
void set_pre(struct pwm_t *pwm, unsigned long arg)
{
unsigned long reg = 0;
reg = readl(S3C2410_TCFG0);
reg &= ~0xff;
reg |= (arg & 0xff);
writel(reg, S3C2410_TCFG0);
}
int pwm_open(struct inode *inode, struct file *filp)
{
struct pwm_t *pwm = container_of(inode->i_cdev, struct pwm_t, cdev);
filp->private_data = pwm;
printk("pwm: device open\n");
// 3. 添加互斥访问
if (down_interruptible(&pwm->sem)) {
return -ERESTARTSYS;
}
pwm_init(pwm);
set_pre(pwm, 255);
up(&pwm->sem);
return 0;
}
int pwm_release(struct inode *inode, struct file *filp)
{
struct pwm_t *pwm = (struct pwm_t *)filp->private_data;
printk("pwm: device close\n");
// 3. 添加互斥访问
if (down_interruptible(&pwm->sem)) {
return -ERESTARTSYS;
}
pwm_off(pwm);
up(&pwm->sem);
return 0;
}
long pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct pwm_t *pwm = (struct pwm_t *)filp->private_data;
// 3. 添加互斥访问
if (down_interruptible(&pwm->sem)) {
ret = -ERESTARTSYS;
goto exit;
}
printk("pwm: device ioctl\n");
switch(cmd)
{
case BEEP_ON:
printk("pwm: BEEP ON\n");
pwm_on(pwm);
break;
case BEEP_OFF:
printk("pwm: BEEP OFF\n");
pwm_off(pwm);
break;
case SET_CNT:
printk("pwm: SET CNT\n");
set_cnt(pwm, arg);
break;
case SET_PRE:
printk("pwm: SET PRE\n");
set_pre(pwm, arg);
break;
default:
printk("pwm: available command\n");
ret = -ENOTTY;
break;
}
up(&pwm->sem);
exit:
return ret;
}
struct file_operations fops =
{
.owner = THIS_MODULE,
.open = pwm_open,
.release = pwm_release,
.unlocked_ioctl = pwm_ioctl,
};
int pwm_probe(struct platform_device *dev)
{
int ret = 0;
struct pwm_t *pwm;
printk("platform: match ok!\n");
pwm = (struct pwm_t*)kmalloc(sizeof(struct pwm_t), GFP_KERNEL);
if (NULL == pwm) {
ret = -ENOMEM;
goto exit;
}
memset(pwm, 0, sizeof(struct pwm_t));
platform_set_drvdata(dev, pwm);
cdev_init(&pwm->cdev, &fops);
pwm->cdev.owner = THIS_MODULE;
// 只需要初始化一次的资源
// 初始化硬件 无
// 初始化信号量
sema_init(&pwm->sem, 1);
ret = alloc_chrdev_region(&pwm->devno, 0, 1, "pwm device");
if (ret) {
goto alloc_chrdev_region_err;
}
ret = cdev_add(&pwm->cdev, pwm->devno, 1);
if (ret) {
goto cdev_add_err;
}
pwm->class = class_create(THIS_MODULE, "pwm");
if (IS_ERR(pwm->class)) {
ret = PTR_ERR(pwm->class);
goto class_create_err;
}
pwm->dev = device_create(pwm->class, NULL, pwm->devno, NULL, "pwm");
if (IS_ERR(pwm->dev)) {
ret = PTR_ERR(pwm->dev);
goto device_create_err;
}
goto exit;
device_create_err:
class_destroy(pwm->class);
class_create_err:
cdev_del(&pwm->cdev);
cdev_add_err:
unregister_chrdev_region(pwm->devno, 1);
alloc_chrdev_region_err:
kfree(pwm);
exit:
return ret;
}
int pwm_remove(struct platform_device *dev)
{
struct pwm_t *pwm = (struct pwm_t *)platform_get_drvdata(dev);
printk("platform: driver remove\n");
device_destroy(pwm->class, pwm->devno);
class_destroy(pwm->class);
cdev_del(&pwm->cdev);
unregister_chrdev_region(pwm->devno, 1);
kfree(pwm);
return 0;
}
struct platform_driver pwm_driver = {
.probe = pwm_probe,
.remove = __devexit_p(pwm_remove),
.driver = {
.name = "pwm_device",
},
};
int __init pwm_module_init(void)
{
return platform_driver_register(&pwm_driver);
}
void __exit pwm_module_exit(void)
{
platform_driver_unregister(&pwm_driver);
}
module_init(pwm_module_init);
module_exit(pwm_module_exit);
MODULE_LICENSE("GPL");
/*
* main.c : test demo driver
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "pwm_music.h"
#include "pwm.h"
int main()
{
int i = 0;
int n = 2;
int fd;
int div;
fd = open("/dev/pwm", O_RDWR | O_NONBLOCK);
if (fd == -1) {
perror("open");
exit(1);
}
ioctl(fd, SET_PRE, 255);
for (i = 0; i < sizeof(MumIsTheBestInTheWorld) / sizeof(Note); i++)
{
div = (PCLK / 256 / 16) / (MumIsTheBestInTheWorld[i].pitch);
ioctl(fd, SET_CNT, div);
usleep(MumIsTheBestInTheWorld[i].dimation * 100);
}
for (i = 0; i < sizeof(GreatlyLongNow) / sizeof(Note); i++)
{
div = (PCLK / 256 / 16) / (GreatlyLongNow[i].pitch);
ioctl(fd, SET_CNT, div);
usleep(GreatlyLongNow[i].dimation * 100);
}
for (i = 0; i < sizeof(FishBoat) / sizeof(Note); i++)
{
div = (PCLK / 256 / 16) / (FishBoat[i].pitch);
ioctl(fd, SET_CNT, div);
usleep(FishBoat[i].dimation * 100);
}
return 0;
}
注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!