ARM--ADC/SPI
2026/7/29 5:27:11 网站建设 项目流程

ADC

简介

ADC是模拟到数字转换器(Analog-to-Digital Converter)的缩写。它是一种电子设备或模块,2440内部 拥有一个ADC外设。用于将连续变化的模拟信号转换为离散的数字信号,以便数字系统(如微处理器、微控制器等)能够对其进行处理和分析。
模拟信号一般是指连续变化的电压信号,其数值在一定范围内变化。而数字信号是由一系列离散的数字表示,只能取有限的值,通常以二进制形式表示。

采样和量化

ADC的工作原理是将模拟信号分割成一系列离散的取样,并将每个取样值转换为相应的数字表示。这个过程涉及到两个主要步骤:采样和量化。

采样

采样:ADC将连续变化的模拟信号在一定时间间隔内进行取样。取样频率决定了每秒采集的样本数,通常以赫兹(Hz)表示。采样过程通过保持并测量模拟信号在每个采样时间点的电压值来实现。

量化

量化:采样得到的连续模拟信号经过量化转换为数字形式。量化是将每个采样值映射到一个离散的数字值的过程。这通常通过比较采样值与参考电压之间的差异,并将其转换为数字表示。
ADC量化的过程是相对于一个基准值的,这个基准值称之为基准电压。一般采用逐次逼近法的ADC会先拿采用电压Vadc跟基准电压Vref的1/2进行比较,如果Vadc>Vref,则结果为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 |= (1<<7); while((ADC1->GC & (1<<7)) != 0); if((ADC1->GS & (1<<1)) == 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] &= ~(1<<7); ADC1->CFG = 0; ADC1->CFG |= (2<<2) | (3<<0); ADC1->GC = 0; ADC1->GC |= (1<<0); //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 & (1<<0)) == 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 = i+1 ;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 sum=0; int i=0; for(i=0;i<len;i++) { sum += a[i]; } return sum; } //读取ADC电压值 float ADC1_get_value(void) { unsigned short a[100]; int i=0; for(i=0;i<=100;i++) { a[i]=ADC1_get_origin_value(); } sort(a,sizeof(a)/sizeof(a[0])); return sum_of_array(a+10,80) / 80 /4095.0 * 3.3; }
初始化控制寄存器之后需要对ADC进行一次校准,之后就可以使用ADC1_get_value进行数据采集了。需要注意的是,通常对ADC采集的数据需要进行消噪(滤波处理),sort函数以及sum_of_array函数的作用就是对ADC转换的结果做均值滤波。对于复杂的滤波算法这里不再赘述。

SPI

简介

SPI是英语Serial Peripheral interface(串行外设接口)的缩写,顾名思义就是串行外围设备接口。 是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH实时时钟,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的两个重要概念。时钟极性(Clock
Polarity 简称CPOL)和时钟相位(Clock Phase简称CPHA)。注意这两个概念都是跟时钟信号线相关的。

CPOL

CPOL:规定了时钟信号线在空闲时的状态,CPOL=0表示时钟信号线在空闲时为低电平,CPOL=1表示时钟信号线在空闲是为高电平;

CPHA

CPHA:与I2C不同,SPI的数据接收方并不是在时钟高或者低电平时采样的,而是在时钟信号处于沿时,至于是在上升沿还是下降沿取决于CPHA。CPHA=0表示是在时钟信号变化的第一个沿时采样,CPHA=1表示是在时钟信号变化的第二个沿时采样。
因此不难理解,这样的排列组合总共能够组合出四种方式,具体使用哪种方式是由外设厂家规定的,因此在使用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.MX6ULLECSPI可以工作在主模式或从模式,我们使用主模式,I.MX6U4个ECSPI,每个ECSPI支持四个片选信号,也就说,如果你要使用ECSPI的硬件片选信号的话,一个ECSPI 可以支持4个外设。上图相当于把ADXL345连接到了ECSPI3的通道0.

相关寄存器

CONREG

BURST_LENGTH(bit31:20):突发长度,设置 SPI 的突发传输数据长度,在一次 SPI 发送中最大可以 发送 2^12bit 数据。可以设置 0X000~0XFFF,分别对应 1~2^12bit。我们一般设置突发长度为一个 字节,也就是 8bit,BURST_LENGTH=7; 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。

CONFIGREG

HT_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 时钟信号极性设置,也就是 CPOL,SCLK_POL[3:0]分别对应通道 3~0,为 0 的 话 SCLK 高电平有效(空闲的时候为低电平),为 1 的话 SCLK 低电平有效(空闲的时候为高电平); SCLK_PHA(bit3:0):SPI时钟相位设置,也就是CPHA,SCLK_PHA[3:0]分别对应通道3~0,为 0 的话串 行时钟的第一个跳变沿(上升沿或下降沿)采集数据,为 1 的话串行时钟的第二个跳变沿(上升沿或下降沿) 采集数据。

PERIODREG

CSD_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,即CPOL=1.CPHA=1。这里需要注意一个细节,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 |= (1<<20); GPIO1->DR |= (1<<20); //CONREG ECSPI3->CONREG = 0; ECSPI3->CONREG |= (7<<20) | (0x0E<<12) | (2<<8) | (1<<4) | (1<<3) | (1<<0); //CONFIGREG ECSPI3->CONFIGREG = 0; ECSPI3->CONFIGREG |= (1<<20) | (1<<4) | (1<<0); } //SPI读写函数 unsigned int spi3_read_write(unsigned int data) { while((ECSPI3->STATREG & (1<<0)) == 0); ECSPI3->TXDATA = data; while((ECSPI3->STATREG & (1<<3)) == 0); return ECSPI3->RXDATA; }

ADXL345初始化

void adxl345_write(unsigned char reg_address,unsigned char data) { //片选拉低 GPIO1->DR &= ~(1<<20); //发送寄存器地址 spi3_read_write(reg_address); //发送写入的数据 spi3_read_write(data); //片选拉高 GPIO1->DR |= (1<<20); } unsigned char adxl345_read(unsigned char reg_address) { unsigned char ret=0; //片选拉低 GPIO1->DR &= ~(1<<20); //发送寄存器地址 spi3_read_write(reg_address | 0x80); //发送占位字节 ret = spi3_read_write(0xFF); //片选拉高 GPIO1->DR |= (1<<20); 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); #endif
main.c
int 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 t=adxl345_read(0x00); //printf("adxl345_ID:%x\n",t); while (1) { struct ADXL345_Data t=adxl345_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; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询