基于I.MX6UL平台的ADS1256驱动开发四.IC操作
在前面章节我们已经完成了最基础的驱动框架的搭建,下面就需要在GPIO可以正常工作的条件下进行IC的读写操作。首先我们要完成最基础的读写操作,然后所有的操作都是基于这个读写操作都基础上完成的。
写入值
对IC进行的所有操作都基于写入数据的基础上衍生来的,比如想要读取AD转换后的码值也要写入对应的指令。不管是写入命令还是写入数据,方法都是一样的。可以对照前面第一章节里的串口时序图,看一下写入过程的时序要求
注意下SPI的工作模式:时钟在空闲时候为低电平,即CPOL=0;数据位在时钟的第一个跳变沿进行采样,即CPHA=0。
然后t1的值需要满足大于4个CLKIN,也就是0.53us;小鱼10个采样周期。采样周期在30K的时候最小,大约是0.033ms,10个DATA周期就是0.3ms。为并且高低电平的最小时长要满足200ns。
我们先写一段宏,可以在后面的程序中判定或设置GPIO高低的状态。
#define CS_0() gpio_set_value(ads1256_dev.cs_gpio, 0) #define CS_1() gpio_set_value(ads1256_dev.cs_gpio, 1) #define SCK_0() gpio_set_value(ads1256_dev.clk_gpio, 0) #define SCK_1() gpio_set_value(ads1256_dev.clk_gpio, 1) #define DI_0() gpio_set_value(ads1256_dev.mosi_gpio, 0) #define DI_1() gpio_set_value(ads1256_dev.mosi_gpio, 1) #define DO_IS_HIGH() gpio_get_value(ads1256_dev.miso_gpio)==1 #define DRDY_IS_LOW() gpio_get_value(ads1256_dev.drdy_gpio)==0
我们要写个延时函数,用来满足SCLK信号的延时
/** * @brief 时钟延时 * */ static void ADS1256_DelaySCLK(void) { uint16_t i; for (i = 0; i < 100; i++); }
在默认的主频下(800MHz),for循环100次时间大概是3.5us,并且高低电平都满足不小于200ns的要求。
满足要求。然后是发送8个bit数据的流程
1 // /** 2 // * @brief 发送8bit数据 3 // * 4 // * @param _data 5 // */ 6 static void ADS1256_Send8Bit(uint8_t _data) 7 { 8 uint8_t i; 9 /* 连续发送多个字节时,需要延迟一下 */ 10 ADS1256_DelaySCLK(); 11 12 for(i = 0; i < 8; i++) 13 { 14 if (_data & 0x80) //10000000b 15 { 16 DI_1(); 17 } 18 else 19 { 20 DI_0(); 21 } 22 SCK_1(); //时钟拉高 23 ADS1256_DelaySCLK(); 24 _data <<= 1; 25 SCK_0(); /* <---- ADS1256 是在SCK下降沿采样DIN数据, 数据必须维持 50nS */ 26 ADS1256_DelaySCLK(); 27 } 28 DI_0(); 29 }
注意一下,这里我们并没有对CS信号进行操作,因为这个函数是用来发送8个bit的,但是一般情况我们需要发送两个字节中间就不再操作cs信号了。发送8个bit的过程是在一个for循环里完成的,把传递进来的data和0x80进行与运算,也就是data的最高位如果是1就执行DI_1()的指令,MOSI输出高电平,否则MOSI输出低电平。该bit发送完成后data左移1位,进行第2个bit的判定,直至8个bit发送完毕。for循环中每次if或else语句执行完成后把时钟拉高,进行延时后拉低,重新延时,即一次循环体。
写命令
ADS1256的命令集合基本上都是8个bit的格式,我们可以直接调用发送的函数就可以
/** * @brief 发送命令 * * @param _cmd */ static void ADS1256_WriteCmd(uint8_t _cmd) { CS_0(); /* SPI片选 = 0 */ ADS1256_Send8Bit(_cmd); CS_1(); /* SPI片选 = 1 */ }
就是在原有的函数上加上CS信号的操作。完成函数后我们可以在初始化的结尾加上这个函数测试一下
这里贴了个图主要是要表明这个发送命令的函数要在哪调用。我们直接发送一个0xAB,可以测一下信号
由于系统资源的问题,这个时钟周期要稍微长一些,大概是8.5us。
下面那个白色的就是MOSI发送出去的值,解析出来就是0xAB(时钟信号上升沿)。说明发送过程没问题。
写寄存器
写寄存器的过程要参考时序图
说白了就是分别写入三个部分的数据,第一个是第一个命令字,包括写命令和寄存器地址,第二个部分是写入寄存器的数量,后面是写入数据。我们先按照这个思路完成单个寄存器写入的流程。
1 /** 2 * @brief 写单个寄存器 3 * 4 * @param _RegID 5 * @param _RegValue 6 */ 7 static void ADS1256_WriteReg(uint8_t _RegID, uint8_t _RegValue) 8 { 9 CS_0(); /* SPI片选 = 0 */ 10 ADS1256_Send8Bit(CMD_WREG | _RegID); /* 写寄存器的命令, 并发送寄存器地址 */ 11 ADS1256_Send8Bit(0x00); /* 寄存器个数 - 1, 此处写1个寄存器 */ 12 13 ADS1256_Send8Bit(_RegValue); /* 发送寄存器值 */ 14 CS_1(); /* SPI片选 = 1 */ 15 }
先把片选信号拉低,然后发送包含地址和写命令的第一部分(第10行),然后发送写入寄存器-1,我们只写1个寄存器所以发送0x00,最后发送数据。
读取值
下面要做值的读取,先看一下时序图(分读取AD码值和读取寄存器值)
上面的两个时序图,左边的是读取码值,需要结合DRDY信号进行操作,右边的是读取寄存器的值,两个操作都是要在向ADS1256写入命令后等待t6后(最少50个clkin,大约6.5us)后进行操作。
我们先根据发送8bit的方式写一个接收8bit的程序
1 /** 2 * @brief 接收8bit数据 3 * 4 * @return uint8_t 5 */ 6 static uint8_t ADS1256_Recive8Bit(void) 7 { 8 uint8_t i; 9 uint8_t read = 0; 10 11 ADS1256_DelaySCLK(); 12 /* ADS1256 要求 SCL高电平和低电平持续时间最小 200ns */ 13 for (i = 0; i < 8; i++) 14 { 15 SCK_0(); 16 ADS1256_DelaySCLK(); 17 read = read<<1; 18 SCK_1(); 19 if (DO_IS_HIGH()) 20 { 21 read++; 22 } 23 ADS1256_DelaySCLK(); 24 } 25 return read; 26 }
流程很清楚,在一个8次的循环中根据判定DO的状态(MISO)生成数据,并且在一个循环体内操作时钟信号的震荡。
寄存器读取
然后就可以按照时序图完成读取1个寄存器的流程
1 /** 2 * @brief 读取寄存器 3 * 4 * @param _RegID 寄存器地址 5 * @return uint8_t 6 */ 7 static uint8_t ADS1256_ReadReg(uint8_t _RegID) 8 { 9 uint8_t read; 10 11 CS_0(); /* SPI片选 = 0 */ 12 ADS1256_Send8Bit(0x10 | _RegID); /* 写寄存器的命令, 并发送寄存器地址 */ 13 ADS1256_Send8Bit(0x00); /* 寄存器个数 - 1, 此处读1个寄存器 */ 14 15 udelay(10); 16 17 read = ADS1256_Recive8Bit(); /* 读寄存器值 */ 18 CS_1(); /* SPI片选 = 1 */ 19 20 return read; 21 }
读取寄存器的指令格式如下:
8个bit中高4为位0x1,低4位是寄存器的地址。第12行的做了个与预算,将高4bit的读指令和低4bit的地址合并。读写寄存器指令后面有第二个byte是操作寄存器的个数。第13行发送的0000就是读取1个寄存器。
第15行我们等待了10us,然后进行读取操作。
我们可以通过读取STATE寄存器来获取IC的ID值,还是在GPIO初始化后面,调用读取函数
加载驱动文件,看看效果
STATE寄存器高4位是ID,默认值就是0x3。说明读取的过程也没有问题。
然后可以结合寄存器写入,测试一下是否工作正常
加载驱动模块
我们想配置寄存器写入0x13,然后读取这个寄存器,返回值为0x13,说明读写寄存器的流程都没问题。
其余操作
除了上面那些寄存器的读写什么的 ,还有一些其他常用的操作我把他拿出来封装成单独的程序,只需要调用就行了。
切换通道
ADS1256在每次输出数据前需要对MUX寄存器进行配置来选择通道。为了简化代码我们这里采用单端的形式来测量数据(但是貌似差分的流程差不多)。
1 /** 2 * @brief 通道切换 3 * 4 * @param _ch 5 */ 6 static void ADS1256_SetChannal(uint8_t _ch) 7 { 8 if (_ch > 7) 9 { 10 return; 11 } 12 ADS1256_WriteReg(REG_MUX, (_ch << 4) | (1 << 3)); /* Bit3 = 1, AINN 固定接 AINCOM */ 13 }
因为ADS1256在单端模式下有8个通道,所以我们开始做个判定,当切换的通道值大于7就直接返回,配置的依据可以参考MUX寄存器定义
在单端模式下我们只用考虑高4位就可以。可以观察一下规律,通道几对应的值就是几,比如正端接的是AIN6,我们就将6左移4位然后把低4位的最高位置1就行了。
下面我们就可以根据手册来实现AD的读取了。
码值读读
IC在AD转换后我们需要读取码值
1 /** 2 * @brief 码值读取 3 * 4 * @return int32_t 5 */ 6 static int32_t ADS1256_ReadData(void) 7 { 8 uint32_t read = 0; 9 10 CS_0(); /* SPI片选 = 0 */ 11 12 ADS1256_Send8Bit(CMD_RDATA); /* 读数据的命令 */ 13 14 // ADS1256_DelayDATA(); /* 必须延迟才能读取芯片返回数据 */ 15 udelay(10); 16 /* 读采样结果,3个字节,高字节在前 */ 17 read = ADS1256_Recive8Bit() << 16; 18 read += (ADS1256_Recive8Bit() << 8); 19 read += ADS1256_Recive8Bit(); 20 21 CS_1(); /* SPI片选 = 1 */ 22 23 /* 负数进行扩展。24位有符号数扩展为32位有符号数 */ 24 if (read & 0x800000) 25 { 26 read += 0xFF000000; 27 } 28 return (int32_t)read; 29 }
数据一共是24bit的,所以需要连续3次读取数据然后拼接出来所需要的数据。并且由于数据时采用补码形式提现的,在输入电压为负值的情形下最高位为1,剩下的就是补码和原码之间的换算了。
DYDR延时
为了简化程序,这里我们先不用中断来获取DYDR的状态,我们写一个循环,在DYDR被拉低的时候跳出循环就可以了。
1 /** 2 * @brief 等待DRDY信号拉低 3 * 4 */ 5 static void ADS1256_WaitDRDY(void) 6 { 7 uint32_t i; 8 9 for (i = 0; i < 40000000; i++) 10 { 11 if (DRDY_IS_LOW()) 12 { 13 break; 14 } 15 } 16 if (i >= 40000000) 17 { 18 printk("ADS1256_WaitDRDY() Time Out ...\r\n"); /* 调试语句. 用语排错 */ 19 } 20 }
后面在完善驱动架构的时候,我会调整DYDR作为输入的外部中断,就不需要这个子程序了。