文章目录
废话不多说,我们直接开始
一、STM32cubeMx创建工程
(1) 、相信大家在初学stm32的时候基本都是使用st公司的标准库来学习的吧,但是如果你要学习更加深入的话个人建议你还是学习下hal库或者LL库,ST公司推出的hal库来替代原有的标准库,HAL库开始慢慢的被广大STM32开发者所接受,现在已经在实际的项目开发中大量使用,HAL库使得项目的移植变得很简介易懂,但是里面的坑也有些有....
(2)、 下面是我配置的STM32cubeMX
图1
我们打开cubeMX之后,点击新建工程之后,选择好自己用到的芯片型号,然后点击右上角那个start project 就会进入我上面图1的界面,然后根据提示依次配置好自己需要的功能,这里我用的是c8t6,我配置PB6和PB7分别作为我的驱动IIC的SCL和SDA,关于软件IIC这里就不多说什么了。
图2
时钟树我是将HCLK直接配置为72Mhz,你可以根据你所需要的功能去配置,说实话用起来挺方便的,啊哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,但是要注意的一点的c8t6这里我是在图1中RCC中的HSE配置成ceramic Resonator,如果你不配置RCC中HSE的话,它默认是使用内部高速时钟RC,这样的话你无法将HCLK配置成72Mhz。
至于对project manager的设置的话相信大家看懂是没有问题的。
二、在KEIL中对IIC的配置
1.在你新建的工程中导入IIC驱动文件
(1)、iic.c文件
代码如下(示例):
#include "iic.h"
/*******************************************************************************
* 函 数 名 : IIC_Init
* 函数功能 : IIC初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pins : PB6 PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
IIC_SCL=1;
IIC_SDA=1;
}
/*******************************************************************************
* 函 数 名 : SDA_OUT
* 函数功能 : SDA输出配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pins : PB6 PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*******************************************************************************
* 函 数 名 : SDA_IN
* 函数功能 : SDA输入配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pins : PB7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/*******************************************************************************
* 函 数 名 : IIC_Start
* 函数功能 : 产生IIC起始信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
Delay_us(5);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
Delay_us(6);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
/*******************************************************************************
* 函 数 名 : IIC_Stop
* 函数功能 : 产生IIC停止信号
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
IIC_SCL=1;
Delay_us(6);
IIC_SDA=1;//发送I2C总线结束信号
Delay_us(6);
}
/*******************************************************************************
* 函 数 名 : IIC_Wait_Ack
* 函数功能 : 等待应答信号到来
* 输 入 : 无
* 输 出 : 1,接收应答失败
0,接收应答成功
*******************************************************************************/
uint8_t IIC_Wait_Ack(void)
{
uint8_t tempTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;
Delay_us(1);
IIC_SCL=1;
Delay_us(1);
while(READ_SDA)
{
tempTime++;
if(tempTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
/*******************************************************************************
* 函 数 名 : IIC_Ack
* 函数功能 : 产生ACK应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
Delay_us(2);
IIC_SCL=1;
Delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_NAck
* 函数功能 : 产生NACK非应答
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
Delay_us(2);
IIC_SCL=1;
Delay_us(5);
IIC_SCL=0;
}
/*******************************************************************************
* 函 数 名 : IIC_Send_Byte
* 函数功能 : IIC发送一个字节
* 输 入 : txd:发送一个字节
* 输 出 : 无
*******************************************************************************/
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>0) //0x80 1000 0000
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
Delay_us(2); //三个延时是必须的
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
Delay_us(2);
}
}
/*******************************************************************************
* 函 数 名 : IIC_Read_Byte
* 函数功能 : IIC读一个字节
* 输 入 : ack=1时,发送ACK,ack=0,发送nACK
* 输 出 : 应答或非应答
*******************************************************************************/
uint8_t IIC_Read_Byte(uint8_t ack)
{
uint8_t i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
Delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
Delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
(2)、iic.h文件
代码示例如下:
#ifndef _iic_H
#define _iic_H
#include "system.h"
#include "stm32f103xb.h"
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(uint8_t txd); //IIC发送一个字节
uint8_t IIC_Read_Byte(uint8_t ack);//IIC读取一个字节
uint8_t IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
这里是iic的软件驱动,网上其实有很多类似的软件驱动iic的例子,大家如果感兴趣的话开头去看看
2.增加一些需要的函数
配置SDA的引脚号输入和输出感觉有点麻烦,在这里我用了位带操作
代码如下(示例):
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
当然如果你不嫌麻烦的话你也可以直接用别的来配置。
三、开始ADXL345传感器
ADXL345主要用在对一些特殊活动的检测,用它来做个计步器啥的虽然说有点偏差,但是也大差不差啦。
1.要学会看数据手册
相信很多人跟我一样拿到一些传感器之后第一时间就是上网去找找有没有别人跟自己用同样的传感器做东西吧,啊哈哈哈哈哈,我想说明的是虽然这种方式可取,但是对于别人的东西可能用到的跟你不一样,个人建议,拿到资料之后,最后是找官方的例子结合数据手册来看,学会看数据手册是一件很重要的事情,数据手册里面包含的东西可能比你去网上找的别人理解的还要多,所以如果你是在做项目开发的话,我个人的建议是先仔细看几遍数据手册,以便来得到你想到的功能框架,看懂了数据手册你才算真正的入门这个传感器。
2.我的示例
接下来我会用我的例子来让大家看看,很多东西都可以从数据手册中得来的
(1)我先将ADXL345.h的文件展示出来
里面包含了一些结构体定义以及ADXL345的寄存器地址等等
#ifndef _adxl345_H
#define _adxl345_H
#include "system.h"
#define DEVICE_ID 0X00 //器件ID,0XE5
#define THRESH_TAP 0X1D //敲击阀值
#define OFSX 0X1E
#define OFSY 0X1F
#define OFSZ 0X20
#define DUR 0X21
#define Latent 0X22
#define Window 0X23
#define THRESH_ACK 0X24
#define THRESH_INACT 0X25
#define TIME_INACT 0X26
#define ACT_INACT_CTL 0X27
#define THRESH_FF 0X28
#define TIME_FF 0X29
#define TAP_AXES 0X2A
#define ACT_TAP_STATUS 0X2B
#define BW_RATE 0X2C
#define POWER_CTL 0X2D
#define INT_ENABLE 0X2E
#define INT_MAP 0X2F
#define INT_SOURCE 0X30
#define DATA_FORMAT 0X31
#define DATA_X0 0X32
#define DATA_X1 0X33
#define DATA_Y0 0X34
#define DATA_Y1 0X35
#define DATA_Z0 0X36
#define DATA_Z1 0X37
#define FIFO_CTL 0X38
#define FIFO_STATUS 0X39
typedef struct {
short x;
short y;
short z;
}axis_info_t;
typedef struct {
short x;
short y;
short z;
}sample_xyz;
//如果SD0(12脚)接地,ADXL地址为0X53(不包含最低位).
//如果SD0接V3.3,则ADXL地址为0X1D(不包含最低位).
//这里我的接法是CS和SD0接V3.3,所以转为读写地址后,为0X3B和0X3A(如果接GND,则为0XA7和0XA6)
//可以根据具体情况来配置
#define ADXL_READ 0X3B
#define ADXL_WRITE 0X3A
uint8_t ADXL345_Init(void); //初始化ADXL345
void ADXL345_WR_Reg(uint8_t addr,uint8_t val); //写ADXL345寄存器
uint8_t ADXL345_RD_Reg(uint8_t addr); //读ADXL345寄存器
void ADXL345_RD_XYZ(short *x,short *y,short *z); //读取一次值
void ADXL345_RD_Avval(short *x,short *y,short *z); //读取平均值
void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval);//自动校准
void ADXL345_Read_Average(short *x,short *y,short *z,uint8_t times);//连续读取times次,取平均
short ADXL345_Get_Angle(float x,float y,float z,uint8_t dir);
void Pedometer_Algorithm(axis_info_t *sample);
void Fall_Detection(sample_xyz *data);
void data_pros(void);
#endif
(2)ADXL345_WR_Reg:写ADXL345寄存器
addr:是寄存器的地址 val:是要写入寄存器的值
void ADXL345_WR_Reg(uint8_t addr,uint8_t val)
{
IIC_Start();
IIC_Send_Byte(ADXL_WRITE); //发送写器件指令
IIC_Wait_Ack();
IIC_Send_Byte(addr); //发送寄存器地址
IIC_Wait_Ack();
IIC_Send_Byte(val); //发送值
IIC_Wait_Ack();
IIC_Stop(); //产生一个停止条件
}
(3)ADXL345_RD_Reg:读ADXL345寄存器
addr:同上 返回值:返回读到的值
uint8_t ADXL345_RD_Reg(uint8_t addr)
{
uint8_t temp=0;
IIC_Start();
IIC_Send_Byte(ADXL_WRITE); //发送写器件指令
temp=IIC_Wait_Ack();
IIC_Send_Byte(addr); //发送寄存器地址
temp=IIC_Wait_Ack();
IIC_Start(); //重新启动
IIC_Send_Byte(ADXL_READ); //发送读器件指令
temp=IIC_Wait_Ack();
temp=IIC_Read_Byte(0); //读取一个字节,不继续再读,发送NAK
IIC_Stop(); //产生一个停止条件
return temp; //返回读到的值
}
(4)ADXL345_Init:初始化ADXL345
成功返回0 失败返回1
uint8_t ADXL345_Init(void)
{
IIC_Init(); //初始化IIC总线
if(ADXL345_RD_Reg(DEVICE_ID)==0XE5) //读取器件ID
{
ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程
ADXL345_WR_Reg(BW_RATE,0x09); //数据输出速度为100Hz
ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式
ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断
ADXL345_WR_Reg(OFSX,0x00);
ADXL345_WR_Reg(OFSY,0x00);
ADXL345_WR_Reg(OFSZ,0x00);
return 0;
}
return 1;
}
(5)ADXL345_RD_XYZ:读取x,y,z三个轴的数据
void ADXL345_RD_XYZ(short *x,short *y,short *z)
{
uint8_t buf[6];
uint8_t i;
IIC_Start();
IIC_Send_Byte(ADXL_WRITE); //发送写器件指令
IIC_Wait_Ack();
IIC_Send_Byte(0x32); //发送寄存器地址(数据缓存的起始地址为0X32)
IIC_Wait_Ack();
IIC_Start(); //重新启动
IIC_Send_Byte(ADXL_READ); //发送读器件指令
IIC_Wait_Ack();
for(i=0;i<6;i++)
{
if(i==5)buf[i]=IIC_Read_Byte(0);//读取一个字节,不继续再读,发送NACK
else buf[i]=IIC_Read_Byte(1); //读取一个字节,继续读,发送ACK
}
IIC_Stop(); //产生一个停止条件
*x=(short)(((uint16_t)buf[1]<<8)+buf[0]);
*y=(short)(((uint16_t)buf[3]<<8)+buf[2]);
*z=(short)(((uint16_t)buf[5]<<8)+buf[4]);
}
(6)ADXL345_Read_Average:读取ADXL345的数据times次,再取平均
这种方式能够有效将数据进行滤波,去除掉那些突现的尖峰值
void ADXL345_Read_Average(short *x,short *y,short *z,uint8_t times)
{
uint8_t i;
short tx,ty,tz;
*x=0;
*y=0;
*z=0;
if(times)//读取次数不为0
{
for(i=0;i<times;i++)//连续读取times次
{
ADXL345_RD_XYZ(&tx,&ty,&tz);
*x+=tx;
*y+=ty;
*z+=tz;
Delay_ms(5);
}
*x/=times;
*y/=times;
*z/=times;
}
}
(7)ADXL345_Get_Angle:通过公式计算x,y,z方向的加速度分量后得出三个方面的角度
short ADXL345_Get_Angle(float x,float y,float z,uint8_t dir)
{
float temp;
float res=0;
switch(dir)
{
case 0://与自然Z轴的角度
temp=sqrt((x*x+y*y))/z;
res=atan(temp);
break;
case 1://与自然X轴的角度
temp=x/sqrt((y*y+z*z));
res=atan(temp);
break;
case 2://与自然Y轴的角度
temp=y/sqrt((x*x+z*z));
res=atan(temp);
break;
}
return res*180/3.14;
}
到这里ADXL345基本功能实现,能够得出三个方向的加速度以及三个方向的关于原来方向的偏移角度
注意:
1、 在读取ADXL345寄存器时,要注意读取的是8位字节的数据,所以你要依次将其读出,然后将高八位有效值低八位有效值相加
2、相加后得到的x,y,z 的值是它们的LSB值,如果你设置的是全分辨率的话,就将得到的数据与3.9mg相乘,这样子得到的数据就是它们加速度mg值
3、根据自己的需要配置ADXL345的输出速率
如图:
这里我选择的是输出数据速率为50hz 即每20毫秒输出一组数据
4、对于偏移校准问题,ADXL345的数据手册里面也给出了相应的解决方法
xval=-offx/4;
yval=-offy/4;
zval=-(offz-256)/4;
其中offx,offy,offz分别表示原来的加速度值
总结
对于ADXL345的使用我还只是摸到了一点门槛,对于中断方面的使用我还没有摸透,有些中断用得了,有些中断用不了,啊哈哈哈哈哈,之后我会继续搞下ADXL345的中断。
本文有什么不足的请各位指定一二。