嵌入式linux笔记--2021-01-17--基于野火i.mx6ullPRO的 ADC字符设备驱动程序
发布日期:2021-06-30 13:42:21 浏览次数:2 分类:技术文章

本文共 10100 字,大约阅读时间需要 33 分钟。

代码下载地址

https://gitee.com/jeasonb/imx6ull_adc

注意: 以下的所有的代码框架来自韦东山老师的代码。

今天来尝试一下ADC驱动的开发。平台是基于野火的 imx6ullpro 开发板。 内核是4.1.15版本。注意:这只是一个简单的demo,我可能会忽略那些adc时钟倍频之类的东西,毕竟作为一个测试的demo,应该是用不上太高的采样。

1.整体的思路

第一步就是字符设备驱动的框架,这一部分不需要去纠结,直接就是 init/exit/open/read/write/close 这几个函数,按照套路来就行了。

第二步就是和硬件打交道的一些 开时钟、复用 、ADC相关的寄存器操作。

2. 开发流程

2.1 字符设备驱动框架的迁移

这一部分没什么技术含量,就是简单的代码的复制。我们需要去实现以下的函数

static int major = 0;static struct class *adc_class;static struct file_operations adc_drv = {
.owner = THIS_MODULE, .open = adc_drv_open, .read = adc_drv_read, //.write = adc_drv_write, .release = adc_drv_close,};static int adc_drv_open (struct inode *node, struct file *file); // 启动adc,配置必要的参数static ssize_t adc_drv_read (struct file *file, char __user *buf, siz_t size, loff_t *offset); // 开启adc转换//static ssize_t adc_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset);// 空函数static int adc_drv_close (struct inode *node, struct file *file); // 关闭adc,释放引脚,时钟等static int __init adc_init(void); // 初始化函数 ioremapstatic void __exit adc_exit(void); // 释放函数 iounmapmodule_init(adc_init);module_exit(adc_exit);MODULE_LICENSE("GPL");

由于这一部分确实没啥好说的,一笔带过。

2.2 硬件操作分析

在进行硬件操作之前我们需要先分析开发板给我们提供的学习环境,也就是ADC功能他是接到了哪一个引脚上去了。

野火原理图如下:(git上doc路径下有原理图以及用户手册)

在这里插入图片描述
通过原理图我们可以看到 接入的是GPIO1_IO03,使用的ADC是ADC1的通道3。
在这里插入图片描述接下来就应该去配置相应的寄存器了按照之前的单片机开发经验来。

2.2.1.开时钟

imx6ull的时钟是CCM管理的,这一点跟stm32F4的RCC很相似,在我们的驱动中需要做的就是在init的时候把这个时钟寄存器ioremap,然后打开对应的时钟,在exit函数中把这个时钟关闭然后iounmap。接下来找一下 adc1的时钟。

在这里插入图片描述根据这个 声明一个先声明指针

// i.mx6ull CCM_CCGR1  0x020C 406Cvolatile unsigned int *CCM_CCGR1; // adc1 时钟开启bit 位于此

开时钟的这个过程我是在加载驱动的时候就进行的,在驱动加载的时候直接打开adc的时钟

其中的相关的代码片段如下:

volatile unsigned int *CCM_CCGR1 = NULL; // adc1 时钟开启bit 位于此// adc_init()  中  CCM_CCGR1 = ioremap(0x020C406C,sizeof(volatile unsigned int));	//adc_drv_open() 中if(CCM_CCGR1)	*CCM_CCGR1 |= (3<<16);  // 使能ADC时钟//adc_drv_close()if(CCM_CCGR1)   *CCM_CCGR1 &= ~(3<<16); // 关ADC 时钟// adc_exit()  中  iounmap(0x020C406C,1);

2.2.2 配置复用

// io realated  IOmux//  IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03    20E_0068volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;//  IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03    20E_02F4volatile unsigned int *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;//adc_init()IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(0x020E0068,sizeof(unsigned int));IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(0x020E02F4,sizeof(unsigned int));//adc_drv_open()if(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03)	*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 1<<4;	if(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03)	*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0;//adc_drv_close()if(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03)	*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0;	if(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03)	*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0;

2.2.3 配置ADC

//  i.mx6ull adc1 base 0x0219 8000//  i.mx6ull adc2 base 0x0219 c000struct imx6ull_adc {
volatile unsigned int adc_hc0; //Control register volatile unsigned int adc_hs; //Status register volatile unsigned int adc_r0; //Data result registe volatile unsigned int adc_cfg; //Configuration register volatile unsigned int adc_gc; //General control register volatile unsigned int adc_gs; //General status register volatile unsigned int adc_cv; //Compare value registe volatile unsigned int adc_ofs; //Offset correction value register volatile unsigned int adc_cal; //Calibration value register };struct imx6ull_adc *adc1;//adc_init()adc1 = ioremap(0x02198000,sizeof(struct imx6ull_adc)); //adc_drv_open() if(adc1) {
adc1->adc_cfg = (2<<2); // 默认的情况下也能用,如果对性能有要求才需要修改参数 // adc_cfg[16] : OVWREN Data Overwrite Enable = false // adc_cfg[15:14]: 00 4 samples averaged 四倍过采样 // adc_cfg[13] : Conversion Trigger Select, 0 Software trigger selected // adc_cfg[12:11]: Voltage Reference Selection,00 Selects VREFH/VREFL as reference voltage. // adc_cfg[10] :High Speed Configuration, 0 Normal conversion selected // adc_cfg[9:8] :ADSTS,00 Sample period (ADC clocks) = 2 if ADLSMP=0b // adc_cfg[7] : Low-Power Configuration,0 ADC hard block not in low power mode. // adc_cfg[6:5] : Clock Divide Select,00 Input clock // adc_cfg[4] : Long Sample Time Configuration,0 Short sample mode. // adc_cfg[3:2] : Conversion Mode Selection,00 8-bit conversion // adc_cfg[1:0] : Input Clock Select,00 IPG clock adc1->adc_hc0 = 3; // 通道选择为3,并且不使能中断 adc1->adc_gc = 0;// 一些进阶功能,目前应该用不到。 }//adc_drv_read() adc1->adc_hc0 = 3;// 选择通道 while(adc1->adc_hs == 0) //等待 状态寄存器 {
if(cnt++ > 100000) {
printk("over time!\n"); break; } } if(size > 4) size = 4; // 最多允许读四字节 value = 0xfff & adc1->adc_r0; err = copy_to_user(buf, &value, size); //adc_exit()iounmap(adc1);

2.3 测试程序

#include 
#include
#include
#include
#include
#include
int main(int argc, char **argv){ int fd; unsigned int buf[2]; int bytes = 0; fd = open("/dev/adc3", O_RDWR); while(1) { bytes = read(fd, buf, 4); if(bytes) printf("%d\n",buf[0]); else break; usleep(100000); } printf("done !\n"); close(fd); return 0;}

完整版 驱动代码 框架来自韦东山老师的代码框架

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 1. 确定主设备号 */static int major = 0;static struct class *adc_class = NULL;// i.mx6ull CCM_CCGR1 0x020C 406Cvolatile unsigned int *CCM_CCGR1 = NULL; // adc1 时钟开启bit 位于此// i.mx6ull adc1 base 0x0219 8000// i.mx6ull adc2 base 0x0219 c000struct imx6ull_adc { volatile unsigned int adc_hc0; //Control register volatile unsigned int notused1;// 04 volatile unsigned int adc_hs; //Status register volatile unsigned int adc_r0; //Data result registe volatile unsigned int notused2;// 10 volatile unsigned int adc_cfg; //Configuration register volatile unsigned int adc_gc; //General control register volatile unsigned int adc_gs; //General status register volatile unsigned int adc_cv; //Compare value registe volatile unsigned int adc_ofs; //Offset correction value register volatile unsigned int adc_cal; //Calibration value register };struct imx6ull_adc *adc1 = NULL;// io realated IOmux// IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 20E_0068volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = NULL;// IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 20E_02F4volatile unsigned int *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = NULL;#define MIN(a, b) (a < b ? a : b)/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */static ssize_t adc_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){ int err; //unsigned long copy_to_user(void *to, const void *from, unsigned long n) //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unsigned int value = 0; int cnt=0; value = 0xfff & adc1->adc_r0; adc1->adc_hc0 = 3; while(adc1->adc_hs == 0) //等待 状态寄存器 { if(cnt++ > 100000) { printk("over time!\n"); break; } } if(size > 4) size = 4; // 最多允许读四字节 value = 0xfff & adc1->adc_r0; err = copy_to_user(buf, &value, size); return size;}/* write(fd, &val, 1); *///static ssize_t adc_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)//{ // return 0;//}static int adc_drv_open (struct inode *node, struct file *file){ //int minor = iminor(node); //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */// p_led_opr->init(minor); //printk("here should init adc!\n"); if(CCM_CCGR1) *CCM_CCGR1 |= (3<<16); if(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03) *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 1<<4; if(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03) *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0; if(adc1) { adc1->adc_cfg = (2<<2); // 默认的情况下也能用,如果对性能有要求才需要修改参数 // adc_cfg[16] : OVWREN Data Overwrite Enable = false // adc_cfg[15:14]: 00 4 samples averaged 四倍过采样 // adc_cfg[13] : Conversion Trigger Select, 0 Software trigger selected // adc_cfg[12:11]: Voltage Reference Selection,00 Selects VREFH/VREFL as reference voltage. // adc_cfg[10] :High Speed Configuration, 0 Normal conversion selected // adc_cfg[9:8] :ADSTS,00 Sample period (ADC clocks) = 2 if ADLSMP=0b // adc_cfg[7] : Low-Power Configuration,0 ADC hard block not in low power mode. // adc_cfg[6:5] : Clock Divide Select,00 Input clock // adc_cfg[4] : Long Sample Time Configuration,0 Short sample mode. // adc_cfg[3:2] : Conversion Mode Selection,00 8-bit conversion // adc_cfg[1:0] : Input Clock Select,00 IPG clock adc1->adc_hc0 = 3; // 通道选择为3,并且不使能中断 adc1->adc_gc = 0;// 一些进阶功能,目前应该用不到。 } return 0;}static int adc_drv_close (struct inode *node, struct file *file){ //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); if(CCM_CCGR1) *CCM_CCGR1 &= ~(3<<16); // 关ADC 时钟 if(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03) *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0; if(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03) *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0; //printk("here should uninit adc!\n"); return 0;}/* 2. 定义自己的file_operations结构体 */static struct file_operations adc_drv = { .owner = THIS_MODULE, .open = adc_drv_open, .read = adc_drv_read, //.write = adc_drv_write, .release = adc_drv_close,};/* 4. 把file_operations结构体告诉内核:注册驱动程序 *//* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */static int __init adc_init(void){ //printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); major = register_chrdev(0, "adc", &adc_drv); /* /dev/led */ adc_class = class_create(THIS_MODULE, "adc_class"); device_create(adc_class, NULL, MKDEV(major, 3), NULL, "adc%d", 3); CCM_CCGR1 = ioremap(0x020C406C,sizeof(unsigned int)); adc1 = ioremap(0x02198000,sizeof(struct imx6ull_adc)); IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(0x020E0068,sizeof(unsigned int)); IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(0x020E02F4,sizeof(unsigned int)); return 0;}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */static void __exit adc_exit(void){ //int i; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(adc_class, MKDEV(major, 3)); class_destroy(adc_class); unregister_chrdev(major, "adc"); iounmap(CCM_CCGR1); iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03); iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03); iounmap(adc1);}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(adc_init);module_exit(adc_exit);MODULE_LICENSE("GPL");

转载地址:https://jeason.blog.csdn.net/article/details/112755702 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:嵌入式linux学习笔记-- ubuntu欢迎信息分析
下一篇:工作笔记--安装twincat之后网卡不能正常使用的解决方法

发表评论

最新留言

很好
[***.229.124.182]2024年04月24日 19时50分14秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

ubuntu安装caffe整个流程 2019-05-01
ubuntu 更改 gcc g++ 版本 2019-05-01
ERROR: Package ‘imageio‘ requires a different Python: 2.7.13 not in ‘>=3.5‘ 2019-05-01
ValidationError: Value for sha256 cannot be None. 2019-05-01
38. 外观数列 2019-05-01
Java开发对泛型的认识和理解 2019-05-01
Android开发之Context认识和运用 2019-05-01
Android数据库GreenDao的使用完全解析 2019-05-01
简单选择排序 2019-05-01
冒泡排序 2019-05-01
快速排序 2019-05-01
【Java习题】 -- 设计⼀个⽅法,将⼀个字符串中的⼤⼩写字⺟翻转 2019-05-01
【大数据开发】Java基础-总结1-可变参数的注意点 2019-05-01
【Java习题】 -- 设计⼀个⽅法,将⼀个数组中的元素倒序排列(注意,不是降序) 2019-05-01
【Java习题】(难)设计⼀个⽅法,找出⼀个整型数组中的第⼆⼤的值 2019-05-01
【Java习题】-- 已知⽅法 public static int[] combine(int[] arr1, int[] arr2) 的作⽤是,合并 两个数组,并对合并后的数组进⾏升序排序 2019-05-01
【Java习题】(难)已知⽅法 public static int[] delete(int[] arr, int ele) 的作⽤是删除数组中第 ⼀次出现的ele元素,并返回删除后的数组 2021-07-04
【大数据开发】Java基础-总结2-面向对象与面向过程的区别 2021-07-04
【大数据开发】Java基础-总结3-面向对象的特点和注意点 2021-07-04
MySQL5.7版本修改了my.ini配置文件后mysql服务无法启动问题 2021-07-04