基于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作为输入的外部中断,就不需要这个子程序了。

 

posted @ 2022-12-18 22:24  银色的音色  阅读(291)  评论(0编辑  收藏  举报