ADC简介ADC是模拟到数字转换器Analog-to-Digital Converter的缩写。它是一种电子设备或模块2440内部 拥有一个ADC外设。用于将连续变化的模拟信号转换为离散的数字信号以便数字系统如微处理器、微控制器等能够对其进行处理和分析。模拟信号一般是指连续变化的电压信号其数值在一定范围内变化。而数字信号是由一系列离散的数字表示只能取有限的值通常以二进制形式表示。采样和量化ADC的工作原理是将模拟信号分割成一系列离散的取样并将每个取样值转换为相应的数字表示。这个过程涉及到两个主要步骤采样和量化。采样采样ADC将连续变化的模拟信号在一定时间间隔内进行取样。取样频率决定了每秒采集的样本数通常以赫兹Hz表示。采样过程通过保持并测量模拟信号在每个采样时间点的电压值来实现。量化量化采样得到的连续模拟信号经过量化转换为数字形式。量化是将每个采样值映射到一个离散的数字值的过程。这通常通过比较采样值与参考电压之间的差异并将其转换为数字表示。ADC量化的过程是相对于一个基准值的这个基准值称之为基准电压。一般采用逐次逼近法的ADC会先拿采用电压Vadc跟基准电压Vref的1/2进行比较如果VadcVref则结果为1否则结果为0。之后继续拿Vadc和Vref的1/4或Vref的3/4继续比较。这个过程有点像二分法每次比较都会使量化的结果逼近真实值。很明显比较的次数决定了测量的精度这个精度被称之为ADC的分辨率。比如一个比较了8次的ADC外设它就称为8位ADC其结果是0~255之间的一个数值设该数值为n那么实际电压就是Vref * (n/255)。如果把比较次数增加到10次结果就是0~1023之间的一个数。常见的分辨率包括8位10位12位和16位。相关配置IMX6ULL内部具有两个ADC控制器。都是采用逐次逼近法设计的。每路ADC的分辨率可选分别为8/10/12位。 I.MX6ULL内部ADC具有自动校准功能因此能够保证更高的测量精度最大转换速率为1MHz即完成一次转换所需的时间最快只需要1us。 I.MX6ULL的ADC还具有硬件平均功能和比较功能大大方便了软件设计。实验所使用的开发板将基准电压设置为3.3V。每个ADC拥有10个通道这里的通道就是指输入的电压信号是从哪个引脚输入进来的意思。10个引脚其实就是GPIO1_IO00~GPIO1_IO09这10个引脚。我们在配置引脚功能时只需将引脚配置为GPIO就行电器配置时可以保持默认配置下使能Keeper即可。时钟配置IMX6ULL的时钟源可以是igpclk、igpclk/2和ADACK其中ADACK是I.MX6ULL内部提供的时钟源只能提供给ADC外设使用这样做的目的是保证系统处于低功耗状态时ADC依旧能够运行。 ADACK的时钟默认为20MHz。相关步骤IMX6ULL的ADC使用还是很简单的首先在初始化的时候配置好引脚例如GPIO1_IO01这个引脚之后配置ADCx_CFG和ADCx_GC寄存器。然后启动一次自动校准系统校准完成后每次向ADCx_HC0寄存器中写入一次数据(其实就是切换到别的通道之后再切换回来)就会 启动一次ADC转换。转换结束之后的数据就保存再ADCx_R0寄存器中。//ADC自校准 int ADC1_calibration(void) { ADC1-GC | (17); while((ADC1-GC (17)) ! 0); if((ADC1-GS (11)) 0) { return 1; } else { return 0; } } //ADC初始化 void ADC1_channel1_init(void) { //引脚复用 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO01_GPIO1_IO01,0); //电气属性 IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO01_GPIO1_IO01,IOMUXC_SW_PAD_CTL_PAD_PKE(1)); IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO01_GPIO1_IO01,IOMUXC_SW_PAD_CTL_PAD_PUE(0)); //配置ADC ADC1-HC[0] ~(17); ADC1-CFG 0; ADC1-CFG | (22) | (30); ADC1-GC 0; ADC1-GC | (10); //ADC校准 printf(ADC1_calibration()?calibration completed normally!\n : calibration completed failed!\n); } //读取ADC原始值 unsigned short ADC1_get_origin_value(void) { //启动ADC转换 ADC1-HC[0] 0; ADC1-HC[0] 1; //等待ADC转换完成 while ((ADC1-HS (10)) 0); //读取ADC原始值 return ADC1-R[0] 0xFFF; } void sort(unsigned short *a,int len) { int i 0; int j 0; int temp 0; for (i 0; i len; i) { for(j i1 ;j len;j) { if(a[i] a[j]) { temp a[i]; a[i] a[j]; a[j] temp; } } } } //ADC采样值求和 unsigned int sum_of_array(unsigned short *a,int len) { unsigned int sum0; int i0; for(i0;ilen;i) { sum a[i]; } return sum; } //读取ADC电压值 float ADC1_get_value(void) { unsigned short a[100]; int i0; for(i0;i100;i) { a[i]ADC1_get_origin_value(); } sort(a,sizeof(a)/sizeof(a[0])); return sum_of_array(a10,80) / 80 /4095.0 * 3.3; }初始化控制寄存器之后需要对ADC进行一次校准之后就可以使用ADC1_get_value进行数据采集了。需要注意的是通常对ADC采集的数据需要进行消噪(滤波处理)sort函数以及sum_of_array函数的作用就是对ADC转换的结果做均值滤波。对于复杂的滤波算法这里不再赘述。SPI简介SPI是英语Serial Peripheral interface(串行外设接口)的缩写顾名思义就是串行外围设备接口。 是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI接口主要应用在 EEPROMFLASH实时时钟AD 转换器还有数字信号处理器和数字信号解码器之间。SPI 是一种高速的全双工同步的串行通信总线并且在芯片的管脚上只占用四根线节约了芯片的管脚同时为 PCB 的布局上节省空间提供方便正是出于这种简单易用的特性现在越来越多的芯片集成了这种通信协议。标准SPI采用四线组网方式如下图SCLK时钟信号线。与I2C类似的所有设备都都需要时钟信号线进行同步该信号由主机负责提供MOSI主出从入相当于主机的数据发送线MISO主入从出相当于主机的数据接收线CS片选信号与I2C不同SPI设备没有所谓的从机地址连接在总线上的设备通过CS片选信号选择。在同一时刻总线上只有一个外设被选择CS信号线通常是低电平有效的。以上的四线方式构成了SPI全双工串行同步通信方式。需要注意的是对于MOSI和MISO来说不是必须存在的可以只有MOSI或者只有MISO。当然这样的话就只能实现单工通信方式了这样的SPI成为三线SPI。至于到底采用三线方式还是四线方式取决于外设本身。例如OLCD只需要主机发送数据而不需要从机发送数据就可以使用三线SPI的连接方式。SPI传输时序SPI的数据传输的时序要理解SPI的通信时序。需要搞清楚SPI的两个重要概念。时钟极性(ClockPolarity 简称CPOL)和时钟相位(Clock Phase简称CPHA)。注意这两个概念都是跟时钟信号线相关的。CPOLCPOL规定了时钟信号线在空闲时的状态CPOL0表示时钟信号线在空闲时为低电平CPOL1表示时钟信号线在空闲是为高电平CPHACPHA与I2C不同SPI的数据接收方并不是在时钟高或者低电平时采样的而是在时钟信号处于沿时至于是在上升沿还是下降沿取决于CPHA。CPHA0表示是在时钟信号变化的第一个沿时采样CPHA1表示是在时钟信号变化的第二个沿时采样。因此不难理解这样的排列组合总共能够组合出四种方式具体使用哪种方式是由外设厂家规定的因此在使用SPI之前就不得不读懂外设的说明文档。以下就是四种方式的时序图。SPI四种工作方式CPOL 1, CPHA 0SCLK空闲为高电平SPI下降沿采样CPOL 1, CPHA 1SCLK空闲为高电平SPI上升沿采样CPOL 0CPHA 0SCLK空闲为低电平SPI上升沿采样CPOL 0, CPHA 1SCLK空闲为低电平SPI下降沿采样注意无论哪种工作方式SPI这种串行通信方式都是MSB先行的。SPI读写特点写即是读读即是写概括为主机写主机通过MOSI发送寄存器地址-主机通过MOSI发送数据- 从机通过MISO回复数据主机读主机通过MOSI发送寄存器地址-主机通过MOSI发送dummy(0x00 0xFF)- 从机通过MISO回复传感器数据相关外设I.MX6ULL的ECSPI可以工作在主模式或从模式我们使用主模式I.MX6U有4个ECSPI每个ECSPI支持四个片选信号也就说如果你要使用ECSPI的硬件片选信号的话一个ECSPI 可以支持4个外设。上图相当于把ADXL345连接到了ECSPI3的通道0.相关寄存器CONREGBURST_LENGTH(bit31:20)突发长度设置 SPI 的突发传输数据长度在一次 SPI 发送中最大可以 发送 2^12bit 数据。可以设置 0X000~0XFFF分别对应 1~2^12bit。我们一般设置突发长度为一个 字节也就是 8bitBURST_LENGTH7 CHANNEL_SELECT(bit19:18)SPI 通道选择一个 ECSPI 有四个硬件片选信号每个片选信号是一个 硬件通道刚才查看了I.MX6U-ALPHA 开发板上的 ICM-20608 的片选信号接的是ECSPI3_SS0也就是 ECSPI3 的通道 0所以本实验设置为 0 DRCTL(bit17:16)SPI 的 SPI_RDY 信号控制位用于设置 SPI_RDY 信号为 0 的话不关心SPI_RDY 信 号为 1 的话 SPI_RDY 信号为边沿触发为 2 的话 SPI_DRY 是电平触发。 SPI_DRY 决定了如何启动一 次数据传输手册第796页有SPI_RDY 的详细说明 PRE_DIVIDER(bit15:12)SPI 预分频ECSPI 时钟频率使用两步来完成分频此位设置的是第一步可 设置 0~15分别对应 1~16 分频。从时钟树可以看出ECSPI时钟源是pll3_sw_clk 480MHz经过一 个8分频的静态分频器实际工作频率是60MHz POST_DIVIDER(bit11:8)SPI 分频值ECSPI 时钟频率的第二步分频设置分频值为2^POST_DIVIDER CHANNEL_MODE(bit7:4)SPI 通道主/从模式设置CHANNEL_MODE[3:0]分别对应 SPI通道 3~0 为 0 的话就是设置为从模式如果为 1 的话就是主模式。比如设置为 0X01 的话就是设置通道 0 为主模 式 SMC(bit3)开始模式控制此位只能在主模式下起作用为 0 的话通过 XCH 位来开启 SPI突发访问 为 1 的话只要向 TXFIFO 写入数据就开启 SPI 突发访问 XCH(bit2)此位只在主模式下起作用当 SMC 为 0 的话此位用来控制 SPI 突发访问的开启 HT(bit1)HT 模式使能位I.MX6ULL 不支持 EN(bit0)SPI 使能位为 0 的话关闭 SPI为 1 的话使能 SPI。CONFIGREGHT_LENGTH(bit28:24)HT 模式下的消息长度设置I.MX6ULL 不支持 SCLK_CTL(bit23:20)设置 SCLK 信号线空闲状态电平SCLK_CTL[3:0]分别对应通道3~0为 0 的话 SCLK 空闲状态为低电平为 1 的话 SCLK 空闲状态为高电平 DATA_CTL(bit19:16)设置 DATA 信号线空闲状态电平DATA_CTL[3:0]分别对应通道3~0为 0 的话 DATA 空闲状态为高电平为 1 的话 DATA 空闲状态为低电平 SS_POL(bit15:12)设置 SPI 片选信号极性设置SS_POL[3:0]分别对应通道 3~0为 0 的话片选信号 低电平有效为 1 的话片选信号高电平有效 SCLK_POL(bit7:4)SPI 时钟信号极性设置也就是 CPOLSCLK_POL[3:0]分别对应通道 3~0为 0 的 话 SCLK 高电平有效(空闲的时候为低电平)为 1 的话 SCLK 低电平有效(空闲的时候为高电平) SCLK_PHA(bit3:0)SPI时钟相位设置也就是CPHASCLK_PHA[3:0]分别对应通道3~0为 0 的话串 行时钟的第一个跳变沿(上升沿或下降沿)采集数据为 1 的话串行时钟的第二个跳变沿(上升沿或下降沿) 采集数据。PERIODREGCSD_CTL(bit21:16)片选信号延时控制位用于设置片选信号和第一个 SPI 时钟信号之间的时间间隔 范围为 0~63 CSRC(bit15)SPI 时钟源选择为 0 的话选择 SPI CLK 为 SPI 的时钟源为 1 的话选择32.768KHz 的 晶振为 SPI 时钟源。我们一般选择 SPI CLK 作为 SPI 时钟源 SPI CLK 的时钟频率为60MHz从在时钟 树上对应的是ECSPI_CLK_ROOT该时钟将pll3_sw_clk经由静态8分频之后到达一个选通器由CSCDR2 的ECSPI_CLK_SEL决定我们肯定是选择60MHz再经过CSCDR2的ECSPI_CLK_PODF进行分频我们 配置为0选择SPI CLK。 SAMPLE_PERIO(bit14:0)采样周期寄存器可设置为 0~0X7FFF 分别对应 0~32767 个周期。 接下来看一下寄存器 ECSPIx_STATREG这个是 ECSPI 的状态寄存器 TC(bit7)传输完成标志位为 0 表示正在传输为 1 表示传输完成 RO(bit6)RXFIFO 溢出标志位为 0 表示 RXFIFO 无溢出为 1 表示 RXFIFO 溢出 RF(bit5)RXFIFO 空标志位为 0 表示 RXFIFO 不为空为 1 表示 RXFIFO 为空 RDR(bit4)RXFIFO 数据请求标志位此位为 0 表示 RXFIFO 里面的数据不大于RX_THRESHOLD此 位为 1 的话表示 RXFIFO 里面的数据大于 RX_THRESHOLD RR(bit3)RXFIFO 就绪标志位为 0 的话 RXFIFO 没有数据为 1 的话表示 RXFIFO 中至少有一个字的 数据。 TF(bit2)TXFIFO 满标志位为 0 的话表示 TXFIFO 不为满为 1 的话表示 TXFIFO 为满 TDR(bit1)TXFIFO 数据请求标志位为 0 表示 TXFIFO 中的数据大于 TX_THRESHOLD为 1 表示 TXFIFO 中的数据不大于 TX_THRESHOLD TE(bit0)TXFIFO 空标志位为 0 表示 TXFIFO 中至少有一个字的数据为 1 表示 TXFIFO为空。最后就是两个数据寄存器ECSPIx_TXDATA 和 ECSPIx_RXDATA这两个寄存器都是 32位的如果要发送数据就向寄存器 ECSPIx_TXDATA 写入数据读取及存取 ECSPIx_RXDATA里面的数据就可以得到刚刚接收到的数据。ADXL345通信时序ADXL345采用的是SPI通信模式3即CPOL1.CPHA1。这里需要注意一个细节ADXL345在向寄存器写入和读取时寄存器的最高位是不一样的读的时候最高位为1写的时候最高位为0。例如写数据到0x20寄存器时先发0x20再发要写入的数据读取时则需要先发0xA0之后进入读取时序。SPI3初始化//SPI3初始化 void spi3_init(void) { //引脚复用 IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK,0); IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO,0); IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI,0); IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20,0); //电气属性 IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK,0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO,0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI,0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20,0x10B1); //通信前先将CS拉高 GPIO1-GDIR | (120); GPIO1-DR | (120); //CONREG ECSPI3-CONREG 0; ECSPI3-CONREG | (720) | (0x0E12) | (28) | (14) | (13) | (10); //CONFIGREG ECSPI3-CONFIGREG 0; ECSPI3-CONFIGREG | (120) | (14) | (10); } //SPI读写函数 unsigned int spi3_read_write(unsigned int data) { while((ECSPI3-STATREG (10)) 0); ECSPI3-TXDATA data; while((ECSPI3-STATREG (13)) 0); return ECSPI3-RXDATA; }ADXL345初始化void adxl345_write(unsigned char reg_address,unsigned char data) { //片选拉低 GPIO1-DR ~(120); //发送寄存器地址 spi3_read_write(reg_address); //发送写入的数据 spi3_read_write(data); //片选拉高 GPIO1-DR | (120); } unsigned char adxl345_read(unsigned char reg_address) { unsigned char ret0; //片选拉低 GPIO1-DR ~(120); //发送寄存器地址 spi3_read_write(reg_address | 0x80); //发送占位字节 ret spi3_read_write(0xFF); //片选拉高 GPIO1-DR | (120); return ret; } //ADXL345初始化 void adxl345_init(void) { //关闭中断 adxl345_write(0x2E,0x08); //满分辨率13位 adxl345_write(0x31,0x0B); //速率25HZ adxl345_write(0x2C,0x08); //工作模式 adxl345_write(0x2D,0x0B); } struct ADXL345_Data adxl345_get_value(void) { struct ADXL345_Data ret; //x轴数据 ret.x adxl345_read(0x32); ret.x | adxl345_read(0x33)8; //y轴数据 ret.y adxl345_read(0x34); ret.y | adxl345_read(0x35)8; //z轴数据 ret.z adxl345_read(0x36); ret.z | adxl345_read(0x37)8; ret.xg ret.x * 0.0039; ret.yg ret.y * 0.0039; ret.zg ret.z * 0.0039; return ret; }adxl345.h#ifndef _ADXL345_H_ #define _ADXL345_H_ struct ADXL345_Data { short x; short y; short z; float xg; float yg; float zg; }; extern void adxl345_write(unsigned char reg_address,unsigned char data); extern unsigned char adxl345_read(unsigned char reg_address); extern void adxl345_init(void); extern struct ADXL345_Data adxl345_get_value(void); #endifmain.cint main(void) { //时钟初始化 clock_init(); // 初始化 system_interrupt_init(); beep_init(); led_init(); //key_init(); //epit_init(); gpt_init(); uart_init(); I2C_init(); //ADC1_channel1_init(); spi3_init(); adxl345_init(); led_off(); beep_off(); //unsigned char tadxl345_read(0x00); //printf(adxl345_ID:%x\n,t); while (1) { struct ADXL345_Data tadxl345_get_value(); int i,j,k; i t.xg * 10; j t.yg * 10; k t.zg * 10; printf(x_accel:%d.%d y_accel:%d.%d z_accel:%d.%d\n,i / 10,i % 10,j / 10,j % 10,k / 10,k % 10); delay_ms(1000); } return 0; }