一、ADS8688概述
二、前言
ADS8688功能强大,但是强大的功能往往也意味着复杂的驱动程序,相信看到这篇文章的读者朋友一定已经尝试移植了许多现成的驱动代码,最终的结果不是报错频出,就是读不懂代码不知道如何正确驱动ADS8688。这篇文章就将教会各位读者如何阅读ADS8688的芯片手册,并且从零开始写出一个自己的ADS8688驱动。首先声明,仅仅引出ADS8688芯片的管脚应该是不够的(笔者也不知道为什么,笔者是硬件白痴,笔者用过的ADS8688芯片和引出的管脚之间都有外围电路)。如果此时读者的手中仅仅只有一块ADS8688芯片,建议先去学习一下如何搭建外围电路。同时,读者应该具备写SPI通信代码,阅读芯片手册中SPI通信时序图的能力,为了节省篇幅,本文并没有涉及到SPI通信以及时序图阅读的讲解。
三、阅读芯片手册
1.状态总览
在芯片手册的36页有一张状态图,可以看到上电之后芯片默认处于IDLE状态,需要我们配置程序寄存器和命令寄存器来进入其他状态,接下来我们就要讲解程序寄存器和状态寄存器。我们要实现的采样功能就在状态图的右下角。
2.命令寄存器和程序寄存器
命令寄存器:命令寄存器用于选择通道采样模式(全自动:AUTO_RST或单通道:MAN_Ch_n),配置设备处于待机(STDBY)或下电(PWR_DN)模式,并复位(RST)程序寄存器到各自的寄存器
默认值。
命令寄存器的指令表。
程序寄存器:程序寄存器用于选择AUTO_RST模式的通道序列,选择SDO输出
格式,并配置单个通道的采样范围。
配置程序寄存器时,需要通过SDI端口向芯片输入16位数据,前七位是数据在寄存器中的地址,第八位决定是读数据还是写数据,后八位是写入对应地址的数据。
配置程序寄存器时序图
程序寄存器配置表
3.手动扫描模式和自动扫描模式
手动扫描模式:可以理解为单次、单通道扫描模式。通过书写命令寄存器中的(MAN_Ch_n),我们可以得到某一个特定通道的电压值。详情见命令寄存器指令表
自动扫描模式:可以理解为多通道同时进行模数转换,按顺序依次将使能的通道得到的电压数据传输给控制平台。未经过书写程序寄存器中AUTO SCAN SEQUENCING CONTROL来使通道掉电即为使能,也就是说向AUTO SCAN SEQUENCING CONTROL输入0x00即为使能所有通道。
两种模式都需要通过书写程序寄存器来确定输入电压范围。
四、代码
1.头文件
#ifndef _ADS8688_H_
#define _ADS8688_H_
#include "sys.h"
#define ADS_8688_DAISY_IN_H GPIO_SetBits(GPIOA,GPIO_Pin_5)
#define ADS_8688_DAISY_IN_L GPIO_ResetBits(GPIOA,GPIO_Pin_5)
#define ADS_8688_SCLK_H GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define ADS_8688_SCLK_L GPIO_ResetBits(GPIOA,GPIO_Pin_4)
#define ADS_8688_nCS_H GPIO_SetBits(GPIOA,GPIO_Pin_7) /GPIO_SetBits///GPIO_ResetBits//GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
#define ADS_8688_nCS_L GPIO_ResetBits(GPIOA,GPIO_Pin_7)
#define ADS_8688_RST_PD_H GPIO_SetBits(GPIOA,GPIO_Pin_2)
#define ADS_8688_RST_PD_L GPIO_ResetBits(GPIOA,GPIO_Pin_2)
#define ADS_8688_SDI_H GPIO_SetBits(GPIOA,GPIO_Pin_3)
#define ADS_8688_SDI_L GPIO_ResetBits(GPIOA,GPIO_Pin_3)
#define ADS_8688_SDO GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)//Command Register
#define NO_OP 0X0000
#define STDBY 0X8200
#define PWR_DN 0X8300
#define RST 0X8500
#define AUTO_RST 0XA000
#define MAN_Ch_1 0XC000
#define MAN_Ch_2 0XC400
#define MAN_Ch_3 0XC800
#define MAN_Ch_4 0XCC00
#define MAN_Ch_5 0XD000
#define MAN_Ch_6 0XD400
#define MAN_Ch_7 0XD800
#define MAN_Ch_8 0XDC00
#define MAN_AUX 0XE000
#define CH1 0x05
#define CH2 0x06
#define CH3 0x07
#define CH4 0x08
#define CH5 0x09
#define CH6 0x0a
#define CH7 0x0b
#define CH8 0x0c
void ADS8688_Init_Single(void);
u16 get_ADS_ch1(void);
void ADS8688_Init_Mult(void);
double get_realdata(u16 x);
void ADS8688_GPIO_Init(void);
void get_ADS_allch(float *result);
void Get_AUTO_RST_Mode_Data(uint16_t* outputdata, uint8_t chnum);
void AUTO_RST_Mode(void);
void ADS8688_SPI_WB(uint8_t com) ;
uint8_t ADS8688_SPI_RB(void) ;
void ADS8688_WriteCommandReg(uint16_t command);
void ADS8688_Write_Program_Register(uint8_t Addr,uint8_t data);
u8 ADS8688_READ_Program_Register(uint8_t Addr);
void Enter_RESET_MODE(void);//软件复位模式,复位 program registers
void Set_CH_Range_Select(uint8_t ch,uint8_t range); //设置各个通道的范围
u16 get_ADS_ch3(void);
#endif
养成一个良好的习惯,将用到的变量用通俗易懂的变量名在头文件中定义,能大大增加代码可读性和可移植性。
头文件中的所有宏定义、函数、变量都是芯片手册中的英文名,有不懂是什么意思的可以自行查询芯片手册。
2. .c文件
#include "ADS8688.H"
#include "sys.h"
#include "delay.h"
extern u16 ADS_Results[8];
void ADS8688_SPI_WB(uint8_t com)
{
uint8_t com_temp=com,s;
ADS_8688_nCS_L;
for(s=0;s<8;s++)
{
if(com_temp&0x80)
{
ADS_8688_SDI_H;
}
else
{
ADS_8688_SDI_L;
}
ADS_8688_SCLK_H;
com_temp<<=1;
ADS_8688_SCLK_L;
}
}
uint8_t ADS8688_SPI_RB(void)
{
uint8_t Rdata=0,s;
ADS_8688_nCS_L;
for(s=0;s<8;s++)
{
Rdata<<=1;
ADS_8688_SCLK_H;
if(ADS_8688_SDO)
{
Rdata|=0x01;
}
else
{
Rdata&=0xFE;
}
ADS_8688_SCLK_L;
}
return Rdata;
}
void ADS8688_WriteCommandReg(uint16_t command)//写ADS8688命令寄存器
{
ADS_8688_nCS_L;
ADS8688_SPI_WB(command>>8 & 0XFF);
ADS8688_SPI_WB(command & 0XFF);
ADS_8688_nCS_H;
}
void ADS8688_Write_Program_Register(uint8_t Addr,uint8_t data)//对程序寄存器的书写有地址和指令两部分,详情查看程序寄存器指令表
{
ADS_8688_nCS_L;
ADS8688_SPI_WB(Addr<<1| 0X01);
ADS8688_SPI_WB(data);
ADS_8688_nCS_H;
}
u8 ADS8688_READ_Program_Register(uint8_t Addr)
{
u8 data = 0;
ADS_8688_nCS_L;
ADS8688_SPI_WB(Addr<<1);
data = ADS8688_SPI_RB();
data = ADS8688_SPI_RB();
ADS_8688_nCS_H;
return data;
}
void Enter_RESET_MODE(void)//软件复位模式,复位 program registers
{
ADS8688_WriteCommandReg(RST);
}
void AUTO_RST_Mode(void)//自动扫描模式
{
ADS8688_WriteCommandReg(AUTO_RST);
}
void MAN_Ch_n_Mode(uint16_t ch)//手动模式
{
ADS8688_WriteCommandReg(ch);
}
void Set_CH_Range_Select(uint8_t ch,uint8_t range) //设置各个通道的电压范围
{
ADS8688_Write_Program_Register(ch,range);
}
void Get_AUTO_RST_Mode_Data(uint16_t* outputdata, uint8_t chnum) //保存自动扫描中各个通道的数据
{
u8 i=0,datal=0,datah=0;
u16 data=0;
for (i=0; i<chnum; i++)
{
ADS_8688_nCS_L; ADS8688_SPI_WB(0X00);
ADS8688_SPI_WB(0X00);
datah = ADS8688_SPI_RB();;
datal = ADS8688_SPI_RB();;
ADS_8688_nCS_H;
data = datah<<8 | datal; //高位在前,低位在后
*(outputdata+i) = data;
}
}
uint16_t Get_MAN_Ch_n_Mode_Data(void) //得到手动扫描的数据
{
u8 datah=0,datal=0;
ADS_8688_nCS_L;
ADS8688_SPI_WB(0X00);
ADS8688_SPI_WB(0X00);
datah = ADS8688_SPI_RB();
datal = ADS8688_SPI_RB();
ADS_8688_nCS_H;
return (datah<<8 | datal);
}
void ADS8688_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_Initure.GPIO_Pin=GPIO_Pin_7;
GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出模式
GPIO_Initure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.GPIO_Pin=GPIO_Pin_2;
GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.GPIO_Pin=GPIO_Pin_4;
GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.GPIO_Pin=GPIO_Pin_6;
GPIO_Initure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_Init(GPIOA,&GPIO_Initure);
}
//初始化通道1,默认正负10V范围
void ADS8688_Init_Single()
{
ADS8688_GPIO_Init();
ADS_8688_RST_PD_L;
Delay_us(2);
ADS_8688_RST_PD_H;
ADS_8688_DAISY_IN_L;
Enter_RESET_MODE();
ADS8688_Write_Program_Register(0X01,0XFF);
ADS8688_READ_Program_Register(0X01);
Delay_us(2);
ADS8688_Write_Program_Register(0x02,0x00);//设置power dowm的通道
ADS8688_Write_Program_Register(0x01,0xff);//使能所有通道
Set_CH_Range_Select(CH1,0x00);//设置通道1的输?范围:+-2.5*Vref
Set_CH_Range_Select(CH3,0x00);
// //0x00 -> +-2.5*ref
// //0x01 -> +-1.25*ref
// //0x02 -> +-0.625*ref
// //0x03 -> +2.5*ref
// //0x04 -> +1.25*ref
Delay_ms(300);
}
u16 get_ADS_ch1(void)
{
MAN_Ch_n_Mode(MAN_Ch_1);//需要手动采集哪个通道就将函数的变量改为那个通道
return Get_MAN_Ch_n_Mode_Data();//读取通道1数据,具体通道数由函数 MAN_Ch_n_Mode()所决定的
}
u16 get_ADS_ch3(void)
{
MAN_Ch_n_Mode(MAN_Ch_3);
return Get_MAN_Ch_n_Mode_Data();
}
double get_realdata(u16 x)//采集到的数据和真实电压之间的关系
{
double y;
y=x-32768;
if(y<0) y = y * 0.0001907+0.02;
else y = y * 0.0001907+0.02;
return y;
}
五、结语
本文虽然只涉及到了实现ADS8688的基本功能,但笔者更希望能通过这篇文章告诉大家如何阅读芯片手册,读懂了芯片手册后能写出属于自己的驱动代码,或者至少懂得如何移植他人的代码,以此提高工作效率。笔者是在电赛时使用的这款芯片,如果读者现在处在电赛的准备阶段,那建议你去自己阅读一下芯片手册,尝试实现一下本文并没有提到的功能,以达到练习的目的,这样,即使电赛期间需要临时写其他芯片的驱动程序也可以游刃有余了。