# imx6ull_ADC **Repository Path**: jeasonb/imx6ull_adc ## Basic Information - **Project Name**: imx6ull_ADC - **Description**: 基于野火的imx6ull pro 开发板的ADC字符设备驱动程序代码 - **Primary Language**: Unknown - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-01-17 - **Last Updated**: 2021-01-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 嵌入式linux笔记--2021-01-17--ADC驱动 今天来尝试一下ADC驱动的开发。平台是基于野火的 imx6ullpro 开发板。 内核是4.1.15版本。注意:最为一个简单的demo,我可能会忽略那些adc时钟倍频之类的东西,毕竟作为一个测试的demo,应该是用不上太高的采样率,采样精度也会限制在8bit. ## 1.整体的思路 第一步就是字符设备驱动的框架,这一部分不需要去纠结,直接就是 init/exit/open/read/write/close 这几个函数,按照套路来就行了。 第二步就是和硬件打交道的一些 开时钟、复用 、ADC相关的寄存器操作。 ## 2. 开发流程 ### 2.1 字符设备驱动框架的迁移 这一部分没什么技术含量,就是简单的代码的复制。我们需要去实现以下的函数 ```c 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); // 初始化函数 ioremap static void __exit adc_exit(void); // 释放函数 iounmap module_init(adc_init); module_exit(adc_exit); MODULE_LICENSE("GPL"); ``` 由于这一部分确实没啥好说的,一笔带过。 ### 2.2 硬件操作分析 在进行硬件操作之前我们需要先分析开发板给我们提供的学习环境,也就是ADC功能他是接到了哪一个引脚上去了。 野火原理图如下:(git上doc路径下有原理图以及用户手册) ![](picture/fire_adc_in.png) 通过原理图我们可以看到 接入的是GPIO1_IO03,使用的ADC是ADC1的通道3。![](picture/ADC复用引脚的位置.png) 接下来就应该去配置相应的寄存器了按照之前的单片机开发经验来。 #### 2.2.1.开时钟 imx6ull的时钟是CCM管理的,这一点跟stm32F4的RCC很相似,在我们的驱动中需要做的就是在init的时候把这个时钟寄存器ioremap,然后打开对应的时钟,在exit函数中把这个时钟关闭然后iounmap。接下来找一下 adc1的时钟。 ![](picture/ADC1_clock_source.png) 根据这个 声明一个先声明指针 ```c // i.mx6ull CCM_CCGR1 0x020C 406C volatile unsigned int *CCM_CCGR1; // adc1 时钟开启bit 位于此 ``` 开时钟的这个过程我是在加载驱动的时候就进行的,在驱动加载的时候直接打开adc的时钟 其中的相关的代码片段如下: ```c 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 配置复用 ```c // io realated IOmux // IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 20E_0068 volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03; // IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 20E_02F4 volatile 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 ```c // i.mx6ull adc1 base 0x0219 8000 // i.mx6ull adc2 base 0x0219 c000 struct 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 测试程序 ```c #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; } ```