目录
一、前言
学校:合肥工业大学
作者:覃祉禄
MCU:STM32F103C8T6
IDE:Keil u Vision 5
通信模式:I2C
使用器件:AFM3000气体流量传感器、0.96寸4引脚OLED显示屏、面包板、杜邦线等
二、项目简介
AFM3000气体流量传感器是奥松电子公司生产的一款数字流量计,能够以超高的精确度测量空气,氧气和其他非侵蚀性气体的气体流量,采用5V电源电压工作,具有标准I2C通信接口。
三、电路设计
1、引脚定义
2、接线设计
STM32----------AFM3000
PB8----------SCL
PB9----------SDA
+5V----------VDD
GND----------GND
STM32----------OLED
PA1-----------SDA
PA2-----------SCL
VCC----------VCC
GND----------GND
3、接线说明
STM32F103C8T6单片机与AFM3000传感器的接线中用到了PB8与PB9接口,这两个接口为I2C1的重映射接口,是官方提供的模板代码里设计的数据传输接口,可根据代码自行更改其他I2C接口,不过官网代码基本使用寄存器所写,难度较大。
STM32F103C8T6单片机与OLED显示器的接线中用到了PA1与PA2接口,这两个接口使用的是软件读写I2C,可以配置为任意引脚,相对比较灵活,这里我配置成了PA1和PA2,大家可以自己写或使用其他的OLED显示器代码,或者使用串口的形式在电脑上显示也是可以的,我用的OLED显示器的代码是B站up主江协科技OLED专栏软件读写的代码,大家可以到自己选择使用,相应代码我也会放在我的博客主页。
注意:有的OLED显示器没有内置上拉电阻需要我们外接4.7k的上拉电阻,不然显示容易出问题。
4、AFM3000 传感器 I2C 命令定义
关于I2C的通信协议这里不做重点讲解,官网代码已经将相应的I2C初始化写好,只需调用一次写好初始化即可。从上表中我们可以看到通过向传感器写入相应的命令即可接收传输回来的数据。命令都是16bit的数据传输的时候我们需要将其拆分为两个字节,依次发送高低字节命令,上表也有相应的传输格式,可根据格式传输。当然如果使用的是官方代码,上表命令已经在头文件中通过枚举类型定义好了,调用官网代码中相应获取数数据的函数时直接传入相应的枚举变量作为参数即可,通过接收返回值来获取传感器数据。
5、AFM3000 传感器 CRC 计算例程
AFM3000 传感器 CRC 效验采用 CRC8,初始值为 0X00,多项式为 0x131 (x8 + x5 + x4 + 1),具体请看下面的代码:
6、流量换算公式
(更多详尽内容可以自行参考官方手册)
四、代码设计
由于OLED代码沉长,这里下文仅展示官网的传感器代码与I2C通信代码,以及主函数代码,OLED代码以及所有的设计文档代码,我都将放在主页博客中,可前往主页寻找下载。由于本人能力有限,如有不足还望指正。如果对您有帮助还请一键三连,您的点赞是我最大的动力。
main.c (主函数)
#include "stm32f10x.h"
#include "oled.h"
#include "delay.h"
#include "afm.h"
//气体流量
u16t afm_data;//接收AFM3000传感器数据
float afm_data1;//转换为浮点数显示输出
int main(void)
{
OLED_Init();//初始化OLED模块
AFM_Init();//初始化AFM3000气体流量传感器
delay_s(1);
while(1)
{
//调用afm.h的数据读取函数
AFM_WriteCommand(FLOW_MEASUREMENT);//向传感器写入读取气体流量的命令
delay_ms(1);//延迟1毫秒
//设置5次最大接收直到读取准确值后输出
while(AFM_ReadCommandResultWithTimeout(5,&afm_data) == CHECKSUM_ERROR);//读取命令结果获取流量
/*偏移校准-"-205.71" 根据情况偏移校准可能有所不同,可自行校准*/
afm_data1 = ((float)afm_data - 3200) / 140.0 - 205.71;
//更加美观输出
if(afm_data1<10 && afm_data1>-10)
{
OLED_ClearArea(20,36,36,7);//清空显示区域
OLED_Printf(20,36,OLED_6X8,"%+4.2f",afm_data1);//显示气体流量数据
}
else if(afm_data1<100 && afm_data1>-100)
{
OLED_ClearArea(20,36,36,7);
OLED_Printf(20,36,OLED_6X8,"%+4.1f",afm_data1);//显示气体流量数据
}
else
{
OLED_ClearArea(20,36,36,7);
OLED_Printf(20,36,OLED_6X8,"%+4.0f",afm_data1);//显示气体流量数据
}
OLED_Update();//发送数据至OLED显示
delay_s(1);
//OLED显示可以自行设计
}
}
delay.c (延时函数源文件)
#include "stm32f10x.h"
#include "Delay.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void delay_ms(uint32_t xms)
{
while(xms--)
{
delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void delay_s(uint32_t xs)
{
while(xs--)
{
delay_ms(1000);
}
}
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void delay_us(uint32_t us);
void delay_ms(uint32_t ms);
void delay_s(uint32_t s);
#endif
afm.c (传感器源文件)
//==============================================================================
// Guangzhou Aosong
//==============================================================================
// Project : AFM Sample Code (V1.0)
// File : i2c_hal.c (V1.0)
// Author : RFU
// Date : 2020-04-01
// Controller: STM32F100RB
// IDE : 礦ision V4.60.0.0
// Compiler : Armcc
// Brief : I2C hardware abstraction layer
//==============================================================================
//-- Includes ------------------------------------------------------------------
#include "i2c_hal.h"
extern void DelayMicroSeconds(u32t nbrOfUs);
//==============================================================================
void I2c_Init(void){
//==============================================================================
// RCC->APB2ENR |= 0x00000010; // I/O port C clock enabled
// GPIOC->CRL &= 0x00FFFFFF; // set open-drain output for SDA and SCL
// GPIOC->CRL |= 0x55000000; // port C, bit 6,7
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //PB8-CLK PB9-SDA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //高速输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //输出模式
GPIO_Init(GPIOB,&GPIO_InitStructure);
SDA_OPEN(); // I2C-bus idle mode SDA released
SCL_OPEN(); // I2C-bus idle mode SCL released
}
//==============================================================================
void I2c_StartCondition(void){
//==============================================================================
SDA_OPEN();
DelayMicroSeconds(1);
SCL_OPEN();
DelayMicroSeconds(1);
SDA_LOW();
DelayMicroSeconds(10); // hold time start condition (t_HD;STA)
SCL_LOW();
DelayMicroSeconds(10);
}
//==============================================================================
void I2c_StopCondition(void){
//==============================================================================
SCL_LOW();
DelayMicroSeconds(1);
SDA_LOW();
DelayMicroSeconds(1);
SCL_OPEN();
DelayMicroSeconds(10); // set-up time stop condition (t_SU;STO)
SDA_OPEN();
DelayMicroSeconds(10);
}
//==============================================================================
etError I2c_WriteByte(u8t txByte){
//==============================================================================
u8t mask;
etError error = NO_ERROR;
for(mask = 0x80; mask > 0; mask >>= 1)// shift bit for masking (8 times)
{
if((mask & txByte) == 0)
SDA_LOW(); // masking txByte, write bit to SDA-Line
else
SDA_OPEN();
DelayMicroSeconds(1); // data set-up time (t_SU;DAT)
SCL_OPEN(); // generate clock pulse on SCL
DelayMicroSeconds(5); // SCL high time (t_HIGH)
SCL_LOW();
DelayMicroSeconds(1); // data hold time(t_HD;DAT)
}
SDA_OPEN(); // release SDA-line
SCL_OPEN(); // clk #9 for ack
DelayMicroSeconds(1); // data set-up time (t_SU;DAT)
if(SDA_READ)
error = ACK_ERROR; // check ack from i2c slave
SCL_LOW();
DelayMicroSeconds(10); // wait to see byte package on scope
return error; // return error code
}
//==============================================================================
u8t I2c_ReadByte(etI2cAck ack){
//==============================================================================
u8t mask;
u8t rxByte = NO_ERROR;
SDA_OPEN(); // release SDA-line
for(mask = 0x80; mask > 0; mask >>= 1) // shift bit for masking (8 times)
{
SCL_OPEN(); // start clock on SCL-line
DelayMicroSeconds(5); // SCL high time (t_HIGH)
if(SDA_READ) rxByte = rxByte | mask; // read bit
SCL_LOW();
DelayMicroSeconds(1); // data hold time(t_HD;DAT)
}
if(ack == ACK)
SDA_LOW(); // send acknowledge if necessary
else
SDA_OPEN();
DelayMicroSeconds(1); // data set-up time (t_SU;DAT)
SCL_OPEN(); // clk #9 for ack
DelayMicroSeconds(5); // SCL high time (t_HIGH)
SCL_LOW();
SDA_OPEN(); // release SDA-line
DelayMicroSeconds(10); // wait to see byte package on scope
return rxByte; // return error code
}
afm.h
//==============================================================================
// Guangzhou Aosong
//==============================================================================
// Project : AFM Sample Code (V1.0)
// File : afm.h (V1.0)
// Author : RFU
// Date : 2020-04-01
// Controller: STM32F100RB
// IDE : 礦ision V4.60.0.0
// Compiler : Armcc
// Brief : Sensor Layer: Definitions of commands and functions for sensor
// access.
//==============================================================================
#ifndef AFM_H
#define AFM_H
#include "stm32f10x.h"
//-- Includes ------------------------------------------------------------------
//#include "system.h"
//-- Defines -------------------------------------------------------------------
// Offset and scale factors from datasheet (AFM3000).
#define OFFSET_FLOW 32000.0F // offset flow
#define SCALE_FLOW 140.0F // scale factor flow
#if 1
//-- Enumerations --------------------------------------------------------------
// Error codes
typedef enum{
NO_ERROR = 0x00, // no error
ACK_ERROR = 0x01, // no acknowledgment error
CHECKSUM_ERROR = 0x02 // checksum mismatch error
}etError;
#endif
typedef unsigned char u8t; ///< range: 0 .. 255
typedef signed char i8t; ///< range: -128 .. +127
typedef unsigned short u16t; ///< range: 0 .. 65535
typedef signed short i16t; ///< range: -32768 .. +32767
typedef unsigned long u32t; ///< range: 0 .. 4'294'967'295
typedef signed long i32t; ///< range: -2'147'483'648 .. +2'147'483'647
typedef float ft; ///< range: +-1.18E-38 .. +-3.39E+38
typedef double dt; ///< range: .. +-1.79E+308
#define bool _Bool
//#define I2C_ADR 64
#define FW_VER 1
extern unsigned short device_status;
//-- Defines -------------------------------------------------------------------
// CRC
#define POLYNOMIAL 0x131 // P(x) = x^8 + x^5 + x^4 + 1 = 100110001
//-- Enumerations --------------------------------------------------------------
// Sensor Commands
typedef enum{
FLOW_MEASUREMENT = 0x1000, // command: flow measurement
READ_SERIAL_NUMBER_HIGH = 0x31AE, // command: read serial number (bit 31:16)
READ_SERIAL_NUMBER_LOW = 0x31AF, // command: read serial number (bit 15:0)
SOFT_RESET = 0X2000 // command: soft reset
}etCommands;
//==============================================================================
void AFM_Init(void);
//==============================================================================
// Initializes the I2C bus for communication with the sensor.
//------------------------------------------------------------------------------
//==============================================================================
etError AFM_WriteCommand(etCommands cmd);
//==============================================================================
// Writes command to the sensor.
//------------------------------------------------------------------------------
// input: cmd command which is to be written to the sensor
//
// return: error: ACK_ERROR = no acknowledgment from sensor
// NO_ERROR = no error
//==============================================================================
etError AFM_ReadCommandResult(u16t *result);
//==============================================================================
// Reads command results from sensor.
//------------------------------------------------------------------------------
// input: *result pointer to an integer where the result will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//==============================================================================
etError AFM_ReadCommandResultWithTimeout(u8t maxRetries, u16t *result);
//==============================================================================
// Reads command results from sensor. If an error occurs, then the read will be
// repeated after a short wait (approx. 10ms).
//------------------------------------------------------------------------------
// input: maxRetries maximum number of retries
// *result pointer to an integer where the result will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//
// remark: This function is usefull for reading measurement results. If not yet
// a new valid measurement was performed, an acknowledge error occurs
// and the read will be automatical repeated until a valid measurement
// could be read.
//==============================================================================
etError AFM_GetFlow(ft offset, ft scale, ft *flow);
//==============================================================================
// Gets the flow from the sensor in a predefined unit. The "flow measurement"
// command will be automatical written to the sensor, if it is not already set.
//------------------------------------------------------------------------------
// input: offset offset flow
// scale scale factor flow
// *flow pointer to a floating point value, where the calculated
// flow will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//
// remark: The result will be converted according to the following formula:
// flow in predefined unit = (measurement_result - offset) / scale
//==============================================================================
etError AFM_GetSerialNumber(u32t *serialNumber);
//==============================================================================
// Gets the serial number from the sensor.
//------------------------------------------------------------------------------
// input: *serialNumber pointer to a 32-bit integer, where the serial number
// will be stored
//
// return: error: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//==============================================================================
etError AFM_SoftReset(void);
//==============================================================================
// Forces a sensor reset without switching the power off and on again.
//------------------------------------------------------------------------------
// return: error: ACK_ERROR = no acknowledgment from sensor
// NO_ERROR = no error
//==============================================================================
etError AFM_CheckCrc(u8t data[], u8t nbrOfBytes, u8t checksum);
//==============================================================================
// Calculates checksum for n bytes of data and compares it with expected
// checksum.
//------------------------------------------------------------------------------
// input: data[] checksum is built based on this data
// nbrOfBytes checksum is built for n bytes of data
// checksum expected checksum
//
// return: error: CHECKSUM_ERROR = checksum does not match
// NO_ERROR = checksum matches
#endif
i2c_hal.c (I2C源文件)
//==============================================================================
// Guangzhou Aosong
//==============================================================================
// Project : AFM Sample Code (V1.0)
// File : afm.h (V1.0)
// Author : RFU
// Date : 2020-04-01
// Controller: STM32F100RB
// IDE : 礦ision V4.60.0.0
// Compiler : Armcc
// Brief : Sensor Layer: Definitions of commands and functions for sensor
// access.
//==============================================================================
#ifndef AFM_H
#define AFM_H
#include "stm32f10x.h"
//-- Includes ------------------------------------------------------------------
//#include "system.h"
//-- Defines -------------------------------------------------------------------
// Offset and scale factors from datasheet (AFM3000).
#define OFFSET_FLOW 32000.0F // offset flow
#define SCALE_FLOW 140.0F // scale factor flow
#if 1
//-- Enumerations --------------------------------------------------------------
// Error codes
typedef enum{
NO_ERROR = 0x00, // no error
ACK_ERROR = 0x01, // no acknowledgment error
CHECKSUM_ERROR = 0x02 // checksum mismatch error
}etError;
#endif
typedef unsigned char u8t; ///< range: 0 .. 255
typedef signed char i8t; ///< range: -128 .. +127
typedef unsigned short u16t; ///< range: 0 .. 65535
typedef signed short i16t; ///< range: -32768 .. +32767
typedef unsigned long u32t; ///< range: 0 .. 4'294'967'295
typedef signed long i32t; ///< range: -2'147'483'648 .. +2'147'483'647
typedef float ft; ///< range: +-1.18E-38 .. +-3.39E+38
typedef double dt; ///< range: .. +-1.79E+308
#define bool _Bool
//#define I2C_ADR 64
#define FW_VER 1
extern unsigned short device_status;
//-- Defines -------------------------------------------------------------------
// CRC
#define POLYNOMIAL 0x131 // P(x) = x^8 + x^5 + x^4 + 1 = 100110001
//-- Enumerations --------------------------------------------------------------
// Sensor Commands
typedef enum{
FLOW_MEASUREMENT = 0x1000, // command: flow measurement
READ_SERIAL_NUMBER_HIGH = 0x31AE, // command: read serial number (bit 31:16)
READ_SERIAL_NUMBER_LOW = 0x31AF, // command: read serial number (bit 15:0)
SOFT_RESET = 0X2000 // command: soft reset
}etCommands;
//==============================================================================
void AFM_Init(void);
//==============================================================================
// Initializes the I2C bus for communication with the sensor.
//------------------------------------------------------------------------------
//==============================================================================
etError AFM_WriteCommand(etCommands cmd);
//==============================================================================
// Writes command to the sensor.
//------------------------------------------------------------------------------
// input: cmd command which is to be written to the sensor
//
// return: error: ACK_ERROR = no acknowledgment from sensor
// NO_ERROR = no error
//==============================================================================
etError AFM_ReadCommandResult(u16t *result);
//==============================================================================
// Reads command results from sensor.
//------------------------------------------------------------------------------
// input: *result pointer to an integer where the result will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//==============================================================================
etError AFM_ReadCommandResultWithTimeout(u8t maxRetries, u16t *result);
//==============================================================================
// Reads command results from sensor. If an error occurs, then the read will be
// repeated after a short wait (approx. 10ms).
//------------------------------------------------------------------------------
// input: maxRetries maximum number of retries
// *result pointer to an integer where the result will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//
// remark: This function is usefull for reading measurement results. If not yet
// a new valid measurement was performed, an acknowledge error occurs
// and the read will be automatical repeated until a valid measurement
// could be read.
//==============================================================================
etError AFM_GetFlow(ft offset, ft scale, ft *flow);
//==============================================================================
// Gets the flow from the sensor in a predefined unit. The "flow measurement"
// command will be automatical written to the sensor, if it is not already set.
//------------------------------------------------------------------------------
// input: offset offset flow
// scale scale factor flow
// *flow pointer to a floating point value, where the calculated
// flow will be stored
//
// return: errror: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//
// remark: The result will be converted according to the following formula:
// flow in predefined unit = (measurement_result - offset) / scale
//==============================================================================
etError AFM_GetSerialNumber(u32t *serialNumber);
//==============================================================================
// Gets the serial number from the sensor.
//------------------------------------------------------------------------------
// input: *serialNumber pointer to a 32-bit integer, where the serial number
// will be stored
//
// return: error: ACK_ERROR = no acknowledgment from sensor
// CHECKSUM_ERROR = checksum mismatch
// NO_ERROR = no error
//==============================================================================
etError AFM_SoftReset(void);
//==============================================================================
// Forces a sensor reset without switching the power off and on again.
//------------------------------------------------------------------------------
// return: error: ACK_ERROR = no acknowledgment from sensor
// NO_ERROR = no error
//==============================================================================
etError AFM_CheckCrc(u8t data[], u8t nbrOfBytes, u8t checksum);
//==============================================================================
// Calculates checksum for n bytes of data and compares it with expected
// checksum.
//------------------------------------------------------------------------------
// input: data[] checksum is built based on this data
// nbrOfBytes checksum is built for n bytes of data
// checksum expected checksum
//
// return: error: CHECKSUM_ERROR = checksum does not match
// NO_ERROR = checksum matches
#endif
i2c_hal.h
//==============================================================================
// Guangzhou Aosong
//==============================================================================
// Project : AFM Sample Code (V1.0)
// File : i2c_hal.h (V1.0)
// Author : RFU
// Date : 2020-04-01
// Controller: STM32F100RB
// IDE : 礦ision V4.60.0.0
// Compiler : Armcc
// Brief : I2C hardware abstraction layer
//==============================================================================
#ifndef I2C_HAL_H
#define I2C_HAL_H
//-- Includes ------------------------------------------------------------------
//#include "system.h"
#include "stm32f10x.h"
#include "afm.h"
#if 0
// Error codes
typedef enum{
NO_ERROR = 0x00, // no error
ACK_ERROR = 0x01, // no acknowledgment error
CHECKSUM_ERROR = 0x02 // checksum mismatch error
}etError;
#endif
//-- Defines -------------------------------------------------------------------
// I2C IO-Pins
// SDA on port C, bit 6
#define SDA_LOW() (GPIOB->BSRR = 0x02000000) // set SDA to low
#define SDA_OPEN() (GPIOB->BSRR = 0x00000200) // set SDA to open-drain
#define SDA_READ (GPIOB->IDR & 0x0200) // read SDA
// SCL on port C, bit 7
#define SCL_LOW() (GPIOB->BSRR = 0x01000000) // set SCL to low
#define SCL_OPEN() (GPIOB->BSRR = 0x00000100) // set SCL to open-drain
#define SCL_READ (GPIOB->IDR & 0x0100) // read SCL
//-- Enumerations --------------------------------------------------------------
// I2C header
typedef enum{
I2C_ADR = 64, // default sensor I2C address
I2C_WRITE = 0x00, // write bit in header
I2C_READ = 0x01, // read bit in header
I2C_RW_MASK = 0x01 // bit position of read/write bit in header
}etI2cHeader;
// I2C acknowledge
typedef enum{
ACK = 0,
NO_ACK = 1,
}etI2cAck;
//==============================================================================
void I2c_Init(void);
//==============================================================================
// Initializes the ports for I2C interface.
//------------------------------------------------------------------------------
//==============================================================================
void I2c_StartCondition(void);
//==============================================================================
// Writes a start condition on I2C-Bus.
//------------------------------------------------------------------------------
// remark: Timing (delay) may have to be changed for different microcontroller.
// _____
// SDA: |_____
// _______
// SCL: |___
//==============================================================================
void I2c_StopCondition(void);
//==============================================================================
// Writes a stop condition on I2C-Bus.
//------------------------------------------------------------------------------
// remark: Timing (delay) may have to be changed for different microcontroller.
// _____
// SDA: _____|
// _______
// SCL: ___|
//==============================================================================
etError I2c_WriteByte(u8t txByte);
//==============================================================================
// Writes a byte to I2C-Bus and checks acknowledge.
//------------------------------------------------------------------------------
// input: txByte transmit byte
//
// return: error: ACK_ERROR = no acknowledgment from sensor
// NO_ERROR = no error
//
// remark: Timing (delay) may have to be changed for different microcontroller.
//==============================================================================
u8t I2c_ReadByte(etI2cAck ack);
//==============================================================================
// Reads a byte on I2C-Bus.
//------------------------------------------------------------------------------
// input: ack Acknowledge: ACK or NO_ACK
//
// return: rxByte
//
// remark: Timing (delay) may have to be changed for different microcontroller.
#endif
(以上便是AFM3000传感器的全部说明,如有不足望指正)