刚开始写分享,只写常使用功能的步骤和细节。(注意代码注释写的很详细,可多看注释)
功能 | ADC测量 |
通信 | I2C通信 |
电压 | 2.0V ~ 5.0V |
特点 | 封装简单、16位、精度高、多引脚、可差分测量、操作简单 |
一、学习环境:我使用的是STM32F103C8T6最小系统板、1.3寸OLEDI2C显示屏、ADS1115模块。(东西不多,实现就行)
软件I2C.c文件的配置代码(可直接用):
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define GPIO GPIOB
//写SCL
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIO,GPIO_Pin_12,(BitAction)BitValue);
Delay_us(10);
}
//写SDA
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIO,GPIO_Pin_13,(BitAction)BitValue);
Delay_us(10);
}
//读SDA
uint8_t MyI2C_R_SDA(void)
{
uint8_t Data = GPIO_ReadInputDataBit(GPIO,GPIO_Pin_13);
Delay_us(10);
return Data;
}
//初始化I2C
void MyI2C_Init(void)
{
//开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//初始化GPIOB的PB12、PB13引脚(PB1引脚可当报警功能的ALRT引脚)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //配置开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_1; //注意观察芯片引脚的使用功能(软件I2C就无所谓引脚了)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO,&GPIO_InitStructure);
GPIO_SetBits(GPIO,GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_1); //初始化引脚(将引脚拉高)
}
//I2C起始标志位
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
//I2C停止标志位
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
//I2C发送八位数据
void MyI2C_Send8Bit_Data(uint8_t Byte)
{
uint8_t i;
for(i = 0;i < 8;i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i)); //高位数据先发送
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//I2C接收八位数据
uint8_t MyI2C_Recrive8Bit_Data(void)
{
uint8_t i,Data = 0x00;
MyI2C_W_SDA(1);
for(i = 0;i < 8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1)
{
Data |= (0x80 >> i); //高位数据先接受,存到Data中
}
MyI2C_W_SCL(0);
}
return Data; //将读到的数据返回
}
//主机发送应答
void MyI2C_SendAck(uint8_t AckData)
{
MyI2C_W_SDA(AckData); //参数为0或1
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
//主机接受应答
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t Ack_Data;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
Ack_Data = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return Ack_Data; //返回ACK应答
}
读取ALRT报警引脚的信息
//uint8_t Get_ALRT(void)
//{
// return GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_1);
//}
I2C.h文件:
#ifndef __I2C_H
#define __I2C_H
void MyI2C_Init(void); //初始化I2C函数
void MyI2C_Start(void); //I2C起始标志
void MyI2C_Stop(void); //I2C停止标志
void MyI2C_Send8Bit_Data(uint8_t Byte); //发送8位数据函数
uint8_t MyI2C_Recrive8Bit_Data(void); //接收8位数据函数
void MyI2C_SendAck(uint8_t AckData); //发送应答数据
uint8_t MyI2C_ReceiveAck(void); //接收应答数据
#endif
二、接下来配置ADS1115模块的文件,首先搞懂ADS1115的使用资料,主要由以下重点内容:
1、ADS1115模块的引脚使用:
VDD引脚 | 接正极(当前代码接最小系统板的3.3V) |
GND引脚 | 接最小系统板的GND |
SCL引脚 | 为I2C时钟线(当前代码接最小系统板的PB12引脚,若是硬件I2C当前芯片接PB10等硬件支持的引脚) |
SDA引脚 | 为I2C数据线(当前代码接最小系统板的PB13引脚,若是硬件I2C当前芯片接PB11等硬件支持的引脚) |
ADDR引脚 | 这是从机地址选择引脚(当前代码接GND,地址为0x90。可根据需要自行选择,若接VDD则地址为0x91、接SDA则地址为0x92、接SCL则地址为0x93) |
ALRT引脚 | 可接GPIO,作为报警功能使用(当前代码未使用) |
A0引脚 | 看下文 |
A1引脚 | 看下文 |
A2引脚 | 看下文 |
A3引脚 | 看下文 |
2、ADS1115模块有四个寄存器,分别名称、功能和使用:
转换寄存器 Conversion_ADDRESS | 简单说就是读取电压的(但读到的数据为16位,并且需要处理) |
配置寄存器 Config_ADDRESS | 引来配置引脚、功能、量程、模式、速率、比较器等功能 |
低阈值寄存器 Lo_thresh | 如果配置成测量两个引脚之间的电压时可用比较器进行比较,此时该寄存器可设置低阈值 |
高阈值寄存器 Hi_thresh | 如果配置成测量两个引脚之间的电压时可用比较器进行比较,此时该寄存器可设置高阈值 |
3、一下是配置寄存器的各种选择配置信息(可以根据自己需要进行配置,也可以按照我的配置)
#define ADS1115_ADDRESS 0x90 //从机地址(根据ADDR引脚的接线进行判断)
#define Conversion_ADDRESS 0x00 //转换寄存器地址
#define Config_ADDRESS 0x01 //配置寄存器地址
#define Lo_thresh 0x02 //低阈值寄存器地址
#define Hi_thresh 0x03 //高阈值寄存器地址
#define OS_DISABLE 0x00 //关闭(读时:当前正在转换)
#define OS_EABLE 0x01 //开始单次转换配置(读时:当前没有转换)
#define MUX_A0A1 0x00 //A0脚A1差分(默认)
#define MUX_A0A3 0x01 //A0脚A3差分
#define MUX_A1A3 0x02 //A1脚A3差分
#define MUX_A2A3 0x03 //A2脚A3差分
#define MUX_A0 0x04 //A0脚
#define MUX_A1 0x05 //A1脚
#define MUX_A2 0x06 //A2脚
#define MUX_A3 0x07 //A3脚
#define PGA_0256 0x05 //量程0.256V
#define PGA_0512 0x04 //量程0.512V
#define PGA_1024 0x03 //量程1.024V
#define PGA_2048 0x02 //量程2.048V(默认)
#define PGA_4096 0x01 //量程4.096V
#define PGA_6144 0x00 //量程6.144V
#define MODE_Continuous 0x00 //模式配置为连续转换模式
#define MODE_Single 0x01 //模式配置为单次或停电状态模式
#define DR_8 0x00 //速率为每秒采点8个(SPS)
#define DR_16 0x01 //速率为每秒采点16个(SPS)
#define DR_32 0x02 //速率为每秒采点32个(SPS)
#define DR_64 0x03 //速率为每秒采点64个(SPS)
#define DR_128 0x04 //速率为每秒采点128个(SPS)(默认)
#define DR_250 0x05 //速率为每秒采点250个(SPS)
#define DR_475 0x06 //速率为每秒采点475个(SPS)
#define DR_860 0x07 //速率为每秒采点860个(SPS)
#define COMP_MODE_Tradition 0x01 //比较器模式默认为传统比较器
#define COMP_MODE_Window 0x01 //比较器模式默认为窗口比较器
#define COMP_POL_LOW 0x00 //比较器极性默认为低电平有效
#define COMP_POL_HIGH 0x01 //比较器极性默认为低电平有效
#define COMP_LAT_DISABLE 0x00 //默认为非锁存比较器
#define COMP_LAT_ENABLE 0x01 //锁存比较器
#define COMP_QUE_ONE 0x00 //一次转换后断言
#define COMP_QUE_TWO 0x01 //两次转换后断言
#define COMP_QUE_THREE 0x02 //四次转换后断言
#define COMP_QUE_DISABLE 0x03 //差分失能
#define Lo_thresh_LowByte 0x74 //低阈值寄存器低八位的数据
#define Lo_thresh_HighByte 0x40 //低阈值寄存器高八位的数据 16,500 3.09375V
#define Hi_thresh_LowByte 0x5C //高阈值寄存器低八位的数据
#define Hi_thresh_HighByte 0x44 //高阈值寄存器高八位的数据 17,500 3.28125V
//这是我已经配置好的数据,可直接写入寄存器(注意高八位低八位数据)
#define Config_High_Byte (OS_EABLE << 7)|(MUX_A0A1 << 4)|(PGA_6144 << 1)|MODE_Continuous //高八位配置数据
#define Config_Low_Byte (DR_128 << 5)|(COMP_MODE_Window << 4)|(COMP_POL_LOW << 3)|(COMP_LAT_ENABLE << 2)|COMP_QUE_TWO //低八位配置数据
这里简单说明一下(解决A0、A1、A2、A3引脚的问题):
1、如果想要实现单通道测量,将QUE配置为 COMP_QUE_DISABLE 失能比较器就可以了,此时通道引脚就只选择MUX_A0、MUX_A1、MUX_A2、MUX_A3四个当中的任意一个即可。
2、此时接线就将你选择的那个引脚接需要测量的电路,其他的不接就可以了。
3、如果想实现差分测量(可显示正负电压)就可以先跟着我的配置试一遍。
4、注意设置比较器时,高阈值寄存器一定要比低阈值寄存器的值要大。
#define Lo_thresh_LowByte 0x74 //低阈值寄存器低八位的数据
#define Lo_thresh_HighByte 0x40 //低阈值寄存器高八位的数据 16,500 3.09375V
#define Hi_thresh_LowByte 0x5C //高阈值寄存器低八位的数据
#define Hi_thresh_HighByte 0x44 //高阈值寄存器高八位的数据 17,500 3.28125V
(这是我设置的数值)
三、把上面的宏定义放到下面的ADS1115.c文件中就可以了
以下是ADS1115.c文件:
#include "stm32f10x.h" // Device header
#include "I2C.h"
#include "OLED.h"
uint8_t Ack_Flag_Success = 0; //ACK应答状态(0:成功;1:从机地址错误;2:寄存器地址错误;3:高八位数据通信错误;4:低八位数据错误)
//------------------------------------------写数据------------------------------------------
void ADS1115_WriteReg(uint8_t RegAddress,uint8_t High_Byte,uint8_t Low_Byte)
{
MyI2C_Start();
MyI2C_Send8Bit_Data(ADS1115_ADDRESS); //找到从机地址
while(MyI2C_ReceiveAck()) //每个while都是检查从机是否收到数据应答(存在可能卡死的BUG,一般没事,你可以写成超时退出的逻辑,也可以写成其他的检查方式)
{
Ack_Flag_Success = 11;
}
MyI2C_Send8Bit_Data(RegAddress); //告诉从机向哪个地址下的寄存器写数据(写为0x90地址)
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 12;
}
MyI2C_Send8Bit_Data(High_Byte); //发送高八位数据
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 13;
}
MyI2C_Send8Bit_Data(Low_Byte); //发送低八位数据
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 14;
}
MyI2C_Stop();
}
//------------------------------------------读数据------------------------------------------
double ADS1115_ReadReg(uint8_t RegAddress)
{
uint16_t Data;
double ret;
MyI2C_Start();
MyI2C_Send8Bit_Data(ADS1115_ADDRESS); //找到从机地址
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 1;
}
MyI2C_Send8Bit_Data(RegAddress); //告诉从机向哪个地址下的寄存器读数据
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 2;
}
MyI2C_Stop();
MyI2C_Start();
MyI2C_Send8Bit_Data(ADS1115_ADDRESS | 0x01);//找到从机地址,告诉从机是要读数据(读为0x91地址)
while(MyI2C_ReceiveAck())
{
Ack_Flag_Success = 3;
}
Data = (MyI2C_Recrive8Bit_Data() << 8) & 0xff00;//开始读数据
MyI2C_SendAck(0);
Data += MyI2C_Recrive8Bit_Data();
OLED_ShowNum(3,2,Data,8); //调试读到的数据
MyI2C_SendAck(0);
MyI2C_Stop();
//数值计算取决于PGA配置 PGA:FSR = ±6.144 V(1)
if(Data > 0x8000) //如果获取的Data为负值
{
ret = ((float)(0xffff - Data) / 32768.0) * 6.144;
}
else //如果获取的Data为正值
{
ret = ((float)Data / 32768.0) * 6.144;
}
//展示数据
if(Data < 0x8000) //正电压
{
OLED_ShowNum(1,2,ret,6);
OLED_ShowDouble(4,2,ret,6); //这是自己写的OLED屏幕显示小数的函数,需要的可以看下后文
}
else //负电压
{
OLED_ShowChar(1,2,'-');
OLED_ShowChar(4,2,'-');
OLED_ShowNum(1,3,ret,6);
OLED_ShowDouble(4,3,ret,6);
}
return ret;
}
//-------------------------------------初始化I2C通信-----------------------------------------
void ADS1115_Init(void)
{
MyI2C_Init();
}
//------------------------------------初始化配置寄存器----------------------------------------
void ADS115_Config(void)
{
ADS1115_WriteReg(Config_ADDRESS,Config_High_Byte,Config_Low_Byte); //写入我宏定义中配置好的数据
ADS1115_WriteReg(Lo_thresh,Lo_thresh_HighByte,Lo_thresh_LowByte); //设定下限阈值
ADS1115_WriteReg(Hi_thresh,Hi_thresh_HighByte,Hi_thresh_LowByte); //设定上限阈值
}
(为什么比0x8000大的就是负值电压呢?因为ADS1115为16位寄存器,负值最高位为符号位也就是1,换算成16进制就是0x8000,所以只要大于0x8000就相当于最高位为1,也就是为负值电压)
(读到数据为什么这样处理?在只读的转换寄存器介绍中是这样写的:“16位转换寄存器包含二进制二进制补码格式的最后一次转换结果。 上电后,转换寄存器清除为0,并保持0直到第一次转换完成。”此时读到的数据想要转换成电压值,就必须知道分辨率是多少:你选择的量程(当前是6.144)/ 0x8000 = 分辨率(0.0001875),只需要读到的数据 * 分辨率(0.0001875)= 电压值,所以上述代码需要这样处理。)
其他的OLED库函数网上有很多,显示小数的不多,可以直接修改以下函数实现(当然也可以直接进行串口调试):
/**
* @brief OLED显示小数(十进制,正数)
* @param Line行数
* @param Column起始位置
* @param double类型小数
* @param 小数点后n位
* @retval 无
*/
void OLED_ShowDouble(uint8_t Line, uint8_t Column, double Data,int n)
{
int a = (int)Data; //整数部分
int Long = 1; //整数部分的位数
int num = a; //中间变量
while(num >= 10) //判断整数部分有几位
{
num /= 10;
Long++;
}
OLED_ShowNum(Line,Column,a,Long); //显示整数部分(可替换成你文件里的显示整数的库函数)
OLED_ShowChar(Line,(Column + Long),'.'); //显示字符部分(可替换成你文件里的显示字符的库函数)
double x = Data - a;
int y = 1;
int b = 1;
while(n > 0) //循环显示小数部分(显示小数点三位或三位以上会存在误差,如5.678可能会显示为5.677,多打印后几位为5.677999)
{
x = Data - a;
x = x * 10 * b;
OLED_ShowNum(Line,Column + Long + (y++),(int)x,1); //显示整数部分(可替换成你文件里的显示整数的库函数)
n--;
b *= 10;
}
}
接下来是ADS1115.h文件的内容:
#ifndef __ADS1115_H
#define __ADS1115_H
#define ADS1115_ADDRESS 0x90 //从机地址(根据ADDR引脚的接线进行判断)
#define Conversion_ADDRESS 0x00 //转换寄存器地址
#define Config_ADDRESS 0x01 //配置寄存器地址
#define Lo_thresh 0x02 //低阈值寄存器地址
#define Hi_thresh 0x03 //高阈值寄存器地址
#define OS_DISABLE 0x00 //关闭(读时:当前正在转换)
#define OS_EABLE 0x01 //开始单次转换配置(读时:当前没有转换)
#define MUX_A0A1 0x00 //A0脚A1差分(默认)
#define MUX_A0A3 0x01 //A0脚A3差分
#define MUX_A1A3 0x02 //A1脚A3差分
#define MUX_A2A3 0x03 //A2脚A3差分
#define MUX_A0 0x04 //A0脚
#define MUX_A1 0x05 //A1脚
#define MUX_A2 0x06 //A2脚
#define MUX_A3 0x07 //A3脚
#define PGA_0256 0x05 //量程0.256V
#define PGA_0512 0x04 //量程0.512V
#define PGA_1024 0x03 //量程1.024V
#define PGA_2048 0x02 //量程2.048V(默认)
#define PGA_4096 0x01 //量程4.096V
#define PGA_6144 0x00 //量程6.144V
#define MODE_Continuous 0x00 //模式配置为连续转换模式
#define MODE_Single 0x01 //模式配置为单次或停电状态模式
#define DR_8 0x00 //速率为每秒采点8个(SPS)
#define DR_16 0x01 //速率为每秒采点16个(SPS)
#define DR_32 0x02 //速率为每秒采点32个(SPS)
#define DR_64 0x03 //速率为每秒采点64个(SPS)
#define DR_128 0x04 //速率为每秒采点128个(SPS)(默认)
#define DR_250 0x05 //速率为每秒采点250个(SPS)
#define DR_475 0x06 //速率为每秒采点475个(SPS)
#define DR_860 0x07 //速率为每秒采点860个(SPS)
#define COMP_MODE_Tradition 0x01 //比较器模式默认为传统比较器
#define COMP_MODE_Window 0x01 //比较器模式默认为窗口比较器
#define COMP_POL_LOW 0x00 //比较器极性默认为低电平有效
#define COMP_POL_HIGH 0x01 //比较器极性默认为低电平有效
#define COMP_LAT_DISABLE 0x00 //默认为非锁存比较器
#define COMP_LAT_ENABLE 0x01 //锁存比较器
#define COMP_QUE_ONE 0x00 //一次转换后断言
#define COMP_QUE_TWO 0x01 //两次转换后断言
#define COMP_QUE_THREE 0x02 //四次转换后断言
#define COMP_QUE_DISABLE 0x03 //差分失能
#define Lo_thresh_LowByte 0x74 //低阈值寄存器低八位的数据
#define Lo_thresh_HighByte 0x40 //低阈值寄存器高八位的数据 16,500 3.09375V
#define Hi_thresh_LowByte 0x5C //高阈值寄存器低八位的数据
#define Hi_thresh_HighByte 0x44 //高阈值寄存器高八位的数据 17,500 3.28125V
//这是我已经配置好的数据,可直接写入寄存器(注意高八位低八位数据)
#define Config_High_Byte (OS_EABLE << 7)|(MUX_A0A1 << 4)|(PGA_6144 << 1)|MODE_Continuous //高八位配置数据
#define Config_Low_Byte (DR_128 << 5)|(COMP_MODE_Window << 4)|(COMP_POL_LOW << 3)|(COMP_LAT_ENABLE << 2)|COMP_QUE_TWO //低八位配置数据
extern uint8_t Ack_Flag_Success; //用于检查从机是否收到主机的数据(就是检查一下ACK应答)
void ADS1115_WriteReg(uint8_t RegAddress,uint8_t High_Byte,uint8_t Low_Byte);
double ADS1115_ReadReg(uint8_t RegAddress);
void ADS1115_Init(void);
void ADS115_Config(void);
#endif
四、万里长征最后一步,只需要在主函数里面调用一下就可以了(main.c的内容):
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "ADS1115.h"
#include "I2C.h"
#include "Delay.h"
#include "USART.h"
#include <string.h>
#include <stdio.h>
//这个函数是为了看一下从机是否接收到数据从而返回应答,在OLED屏幕的第二行展示一下成功或错误就可以了
//不用也可以
void ACK(void)
{
if(Ack_Flag_Success == 0)
{
OLED_ShowString(2,2,"ACK:Success");
}
else if(Ack_Flag_Success == 1)
{
OLED_ShowString(2,2,"ACK:R ADD ERROR");
}
else if(Ack_Flag_Success == 2)
{
OLED_ShowString(2,2,"ACK:R RegAdd ERROR");
}
else if(Ack_Flag_Success == 3)
{
OLED_ShowString(2,2,"ACK:R Data ERROR");
}
else if(Ack_Flag_Success == 11)
{
OLED_ShowString(2,2,"ACK:W ADD ERROR");
}
else if(Ack_Flag_Success == 12)
{
OLED_ShowString(2,2,"ACK:W RegAdd ERROR");
}
else if(Ack_Flag_Success == 13)
{
OLED_ShowString(2,2,"ACK:WH Data ERROR");
}
else if(Ack_Flag_Success == 14)
{
OLED_ShowString(2,2,"ACK:WL Data ERROR");
}
}
int main(void)
{
OLED_Init(); //OLED初始化
ADS1115_Init(); //初始化ADSM模块
ADS115_Config(); //向ADS1115的配置寄存器中写配置数据
OLED_ShowChar(4,11,'V'); //显示电压单位
while (1)
{
ADS1115_ReadReg(Conversion_ADDRESS); //对转换寄存器中进行读数据
Delay_ms(1);
ACK(); //调用检查应答的函数
Delay_ms(1000); //每间隔1S测量一下
}
}
最后接线:我配置的是 MUX_A0A1 ,也就是A0和A1之间的电压,只需要将A0接到正极,A1接到负极就可以会显示正电压,把A0和A1反转接线就会显示负电压。
(注意若要在程序中切换通道,中间必须延时5毫秒,否则采集值不准)
有需要的可以简单学习,好多细节内容也没讲到,但是跟着把代码敲下来就差不多能弄懂,如果有难度可以直接建好文件复制代码就能实现,然后试着改动一下配置寄存器的参数观察发现的变化,这样也能学到东西。非常用心的编写,希望大家点赞多支持初学者。