一、前言
学校:合肥工业大学
作者:覃祉禄
MCU:STM32F103C8T6
IDE:Keil u Vision 5
通信模式:I2C
使用器件:ADP910数字差压传感器、STM32F103C8T6单片机、0.96寸4引脚OLED显示屏、面包板、杜邦线、跳线等
二、项目简介
ADP910差压传感器是广州奥松电子公司生产的一款数字型差压传感器。专为高精度测量差压而设计,响应时间达10ms。传感器内部的芯片可以通过气流对其周围的温度场分布所产生的影响,测量空气、氮气、氧气的压力差值,具有高精度、无漂移、稳定性强等特点。同时,在灵敏度、抗冲击和温度变化等方面性能优异。 ADP910传感器具备标准I2C接口,通信方式简单,可轻松连接至微处理器。
三、电路设计
1、引脚定义
ADP910 的引脚和气孔指示如上图。A 和 B 为气孔,A 和 B 均可以根据情况作为进气口和出气 口。当气孔 A 为进气孔时为正差压,气孔 B 为进气孔时为负差压。
2、接线设计
STM32----------ADP910
PB10-----------SCL
PB11----------SDA
VCC----------VCC
GND----------GND
STM32----------OLED
PB8----------SCL
PB9----------SDA
VCC----------VCC
GND----------GND
3、接线说明
STM32单片机与ADP910传感器的接线中我们用到了PB10与PB11这两个接口是单片机上的I2C2片上外设接口,我们通过配置硬件I2C2来实现单片机与传感器的数据传输。
STM32单片机与OLED显示屏的接线为PB8与PB9,这里用的是软件读写I2C的发送编写的OLED显示代码,可配置为任意引脚,相对比较灵活,大家可以用自己的OLED显示代码或是通过串口的形式在电脑上显示也是可以的。我用的OLED显示代码是B站up主江协科技OLED专栏软件读写I2C的代码,相应的OLED代码我也会放在博客主页中。
注意:都的OLED显示器没有内置上拉电阻,需要我们外接4.7k的上拉电阻,不然显示容易出错。下图为电路设计的实物图。
4、通信模式与数据转换
DAP910与单片机之间采用的是标准的I2C通信,ADP910的I2C是使用了7位寻址的I2C,在7位寻址过程中,从机地址在启动信号后的第一个字节开始传输,该字节的前7位为从机地址,第8位为读写位,其中0表示写,1表示读。
主机在发送完7位寻址并接受到DAP910的ACK响应后,可以根据测量需求读取ADP910的各个参数,在读取参数前仅需发送响应的寄存器指令,便可以接收ADP910返回的差压测量数据或产品类型。这里我们仅讲解差压数据的获取。以下是ADP910的指令集:
根据指令集的介绍,我们就可以在开始I2C通信后发送三个字节来读取ADP910的测量结果,首字节为ADP910写入地址0x4A(及二进制为01001010),然后写入0x36与0x1E这两个指令,值得注意的是在I2C通信中每个字节的写入都应接收到ADP910的ACK响应,否则写入的指令是无效的。指令发送完后便可以通过接收函数来读取从机发送回来的6字节数据,接收数据时同样需要向从机发送读取地址0x4B(及二进制01001011),才能获取相应测量参数。并且每获取1字节数据都要给予ADP910相应的ACK应答,直到不需要接收数据为止。根据传感器参考手册,这六个数据的原始意义如下:
通过从机返回的格式表可知,返回数据中两字节的差压数据,两字节我的数据,以及他们对应的CRC校验,这里我们仅对差压原始数据进行处理,不考虑温度数据,根据参考手册我们可以得知该校验采用的是CRC8循环冗余校验,可以根据相应的算法将接收的前两个差压数据的字节换算后与第三个字节,及CRC进行比对,仅当比对成功后将数据打印与OLED显示屏或传输至上位机软件,否则不对无效数据进行处理,保证了数据在准确性。
上图为数据转换的公示表,以下为数据的处理与转化的示例:
主机发送:4A 36 1E
从机返回:4B 09 60 F9 13 66 21
其中0x0960为差压原始数据,0x1508为温度原始数据,与他们对应的CRC8校验值分别是0xF9和 0x21。根据计算公式,可以得出差压为 0x0960/60=2400/60=40(Pa);温度为0x1366/200=4966/200=24.83(℃)。
5、ADP910传感器的CRC计算例程
CRC一般有CRC8、CRC16、CRC32三种标准,在ADP910中采用的是CRC8。以多项式x8 + x5 + x4 +1(0x31)为例,CRC8校验和字节由下表所示属性的CRC算法生成。
四、代码设计
OLED显示代码和延时函数代码这里不再显示,大家可以使用自己的就行,当然也可以到我的博客主页下载相关源代码。
main.c(主函数)
#include "stm32f10x.h"
#include "i2c2.h"
#include "adp910.h"
#include "oled.h"
#include "delay.h"
void OLED_Show_ADP910(void);
//差压
union data_ucharTOint tdata;//接收压差原始数据的共同体
unsigned int ADP910_Value;
float ADP910_Press;//压差数据
const unsigned char adp910_cmd[2] = {0x36,0x1E};//压差传感器获取压差指令
unsigned char adp910_data[6] = {0x00,0x00,0x00,0x00,0x00,0x00};//接收缓冲区
//创建一个小端存储模式的共用体及内存中高位占高地址,低位占低地址(说白了就是倒叙储存~嘻嘻)
union data_ucharTOint //接收压差原始数据的共同体
{
unsigned char a[2];//这两个数据共用一块存储空间,从而实现数据类型的强制转换
short int b;
};
extern union data_ucharTOint tdata;
int main(void)
{
ADP910_Init();//初始化ADP910差压传感器
OLED_Init();//初始化OLED模块
OLED_ShowString(0,0,"DP:",OLED_8X16);
OLED_ShowString(66 ,0,"%",OLED_8X16);
delay_s(1);
while(1)
{
OLED_Show_ADP910();//显示差压
delay_ms(100);//延时
}
}
//传感器数据显示-差压-ADP910
void OLED_Show_ADP910(void)
{
App_I2C2_MasterTransmit(0x4A,adp910_cmd,2);//发送指令
delay_ms(5);//延时5毫秒
App_I2C2_MasterReceive(0x4B,adp910_data,6);//接收数据
//校验数据是否正确,不正确puc
if(Calc_CRC8_ADP910(adp910_data,2) != adp910_data[2])
{
//数据错误无须执行数据处理
}
else
{
tdata.a[0] = adp910_data[1];
tdata.a[1] = adp910_data[0];
ADP910_Value = tdata.b;
ADP910_Press = ((float)tdata.b / 60) + 0.1;
//显示差压数据,误差0.4Pa,/*偏移校准-"+0.1"*/
if(ADP910_Press<10 && ADP910_Press>-10)
{
OLED_ClearArea(24,0,36,7);
OLED_Printf(24,0,OLED_8X16,"%+4.2f",ADP910_Press);
}
else if(ADP910_Press<100 && ADP910_Press>-100)
{
OLED_ClearArea(24,0,36,7);
OLED_Printf(24,0,OLED_8X16,"%+4.1f",ADP910_Press);
}
else
{
OLED_ClearArea(24,0,36,7);
OLED_Printf(24,0,OLED_8X16,"%+4.0f",ADP910_Press);
}
}
OLED_Update();//发送数据至OLED显示
}
ADP910.c
#include "stm32f10x.h"
#include "i2c2.h"
//初始化ADP910
void ADP910_Init(void)
{
App_I2C2_Init();
}
//CRC校准
u8 Calc_CRC8_ADP910(u8 *data, u8 Num)
{
u8 bit, byte, crc = 0xFF;
for(byte = 0; byte < Num; byte++)
{
crc ^= (data[byte]);
for(bit = 8; bit > 0; --bit)
{
if(crc & 0x80) crc = (crc << 1) ^ 0x31;
else crc = (crc << 1);
}
}
return crc;
}
ADP910.h
#ifndef __ADP910_H__
#define __ADP910_H__
#include "stm32f10x.h"
void ADP910_Init(void);//初始化ADP910
u8 Calc_CRC8_ADP910(u8 *data, u8 Num);//CRC校准
#endif
i2c2.c
#include "stm32f10x.h"
void App_I2C2_Init(void)
{
// #1. 初始化SCL和SDA引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// #2. 为I2C2开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C2, DISABLE);
// #3. 配置I2C的参数
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = 100000;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
// I2C_InitStruct.I2C_OwnAddress1 = 0x12;
// I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
// I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &I2C_InitStruct);
// #4. 使能I2C
I2C_Cmd(I2C2, ENABLE);
}
//发送数据
ErrorStatus App_I2C2_MasterTransmit(uint8_t SlaveAddr, const uint8_t *pData, uint16_t Size)
{
// #1. 等待总线空闲
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY) == SET);
// #2. 发送起始位
I2C_GenerateSTART(I2C2, ENABLE);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_SB) == RESET);
// #3. 发送从机地址
I2C_ClearFlag(I2C2, I2C_FLAG_AF);
I2C_SendData(I2C2, SlaveAddr);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_ADDR) == RESET)
{
if(I2C_GetFlagStatus(I2C2, I2C_FLAG_AF) == SET)
{
goto TAG_ERROR;
}
}
I2C_ReadRegister(I2C2, I2C_Register_SR1);
I2C_ReadRegister(I2C2, I2C_Register_SR2);
// #4. 发送数据
uint32_t i;
for(i=0;i<Size;i++)
{
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TXE) == RESET)
{
if(I2C_GetFlagStatus(I2C2, I2C_FLAG_AF) == SET)
{
goto TAG_ERROR;
}
}
I2C_SendData(I2C2, pData[i]);
}
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BTF) == RESET)
{
if(I2C_GetFlagStatus(I2C2, I2C_FLAG_AF) == SET)
{
goto TAG_ERROR;
}
}
// #5. 发送停止位
I2C_GenerateSTOP(I2C2, ENABLE);
return SUCCESS;
TAG_ERROR:
I2C_GenerateSTOP(I2C2, ENABLE);
return ERROR;
}
//接收数据
ErrorStatus App_I2C2_MasterReceive(uint8_t SlaveAddr, uint8_t *pDataOut, uint16_t Size)
{
if(Size == 0)
{
return ERROR;
}
// #1. 等待总线空闲
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY) == SET);
// #2. 发送起始位
I2C_GenerateSTART(I2C2, ENABLE);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_SB) == RESET);
// #3. 发送从机地址
I2C_ClearFlag(I2C2, I2C_FLAG_AF);
I2C_SendData(I2C2, SlaveAddr);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_ADDR) == RESET)
{
if(I2C_GetFlagStatus(I2C2, I2C_FLAG_AF) == SET)
{
goto TAG_ERROR;
}
}
// #4. 接收数据
I2C_AcknowledgeConfig(I2C2, ENABLE);
I2C_NACKPositionConfig(I2C2, I2C_NACKPosition_Current);
if(Size == 1)
{
I2C_ReadRegister(I2C2, I2C_Register_SR1);
I2C_ReadRegister(I2C2, I2C_Register_SR2);
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_NACKPositionConfig(I2C2, I2C_NACKPosition_Current);
I2C_GenerateSTOP(I2C2, ENABLE);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_RXNE)==RESET);
pDataOut[0] = I2C_ReceiveData(I2C2);
}
else if(Size == 2)
{
I2C_AcknowledgeConfig(I2C2, ENABLE);
I2C_ReadRegister(I2C2, I2C_Register_SR1);
I2C_ReadRegister(I2C2, I2C_Register_SR2);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_RXNE) == RESET);
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BTF) == RESET);
pDataOut[0] = I2C_ReceiveData(I2C2);
pDataOut[1] = I2C_ReceiveData(I2C2);
}
else
{
uint32_t i;
I2C_ReadRegister(I2C2, I2C_Register_SR1);
I2C_ReadRegister(I2C2, I2C_Register_SR2);
for(i=0;i<Size-3;i++)
{
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_RXNE) == RESET);
pDataOut[i] = I2C_ReceiveData(I2C2);
}
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BTF) == RESET);
I2C_AcknowledgeConfig(I2C2, DISABLE);
pDataOut[Size-3] = I2C_ReceiveData(I2C2);
I2C_GenerateSTOP(I2C2, ENABLE);
pDataOut[Size-2] = I2C_ReceiveData(I2C2);
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_RXNE) == RESET);
pDataOut[Size-1] = I2C_ReceiveData(I2C2);
}
return SUCCESS;
TAG_ERROR:
I2C_GenerateSTOP(I2C2, ENABLE);
return ERROR;
}
i2c2.h
#ifndef __ADP910_H__
#define __ADP910_H__
#include "stm32f10x.h"
void App_I2C2_Init(void);//初始化I2C2
ErrorStatus App_I2C2_MasterTransmit(uint8_t SlaveAddr, const uint8_t *pData, uint16_t Size);//发送数据
ErrorStatus App_I2C2_MasterReceive(uint8_t SlaveAddr, uint8_t *pDataOut, uint16_t Size);//接收数据
#endif
(也是便是ADP910数字差压传感器的全部说明,如有不足望指正)