提示:本文属于技术的交流,如有抄袭请联系删除。
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
本文主要利用ADXL345陀螺仪、LCD1602液晶显示器以及STC15W408AS单片机是设计了一款X、Y、Z三轴坐标数据显示器。设计简单,结合了单片机的IIC通讯知识、51单片机的多C文件的建立以及LCD1602液晶的使用方法,充分能够让初学者更加深刻的了解单片机的编程。
提示:以下是本篇文章正文内容,下面案例可供参考
一、STC15W408AS单片机
单片机特性:
-
增强型 8051 CPU,1T,单时钟/机器周期,指令代码完全兼容传统8051
-
工作电压:2.5V - 5.5V
-
1K / 2K / 4K / 8K / 13K / 15.5K字节片内Flash程序存储器,擦写次数10万次以上
-
ISP/IAP,在系统可编程/在应用可编程,无需编程器/仿真器,支持RS485下载
-
共8通道10位高速ADC,速度可达30万次/秒,3路PWM还可当3路D/A使用
-
共3通道捕获/比较单元(CCP/PWM/PCA)
-
利用CCP/PCA高速脉冲输出功能可实现3路9~16位PWM (每通道占用系统时间小于0.6%)
-
利用定时器T0的时钟输出功能可实现高精度的8~16位PWM (占用系统时间小于0.4%)
-
不需外部晶振和外部复位,还可对外输出时钟和低电平复位信号
-
可将掉电模式/停机模式唤醒的资源有:
INT0/P3.2, INT1/P3.3 (INT0/INT1上升沿下降沿 中断均可), INT2 /P3.6, INT3/P3.7, INT4/P3.0(INT2 / INT3 /INT4 仅可下降沿中断);管脚RxD(可在RxD/ P3.0和RxD_2/P3.6之间切换);管脚T0/T2(下降 沿,不产生中断,前提是在进入掉电模式/停机 模式前相应的定时器中断已经被允许);内部低 功耗掉电唤醒专用定时器。 -
STC15W408AS单片机选型表:
-
除此之外还有很多特性没有列出,如有需要了解更多可点击STC15W408AS。
二、总体设计
1.硬件设计
硬件设计部分主要是设计PCB,中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气相互连接的载体。由于它是采用电子印刷术制作的,故被称为“印刷”电路板。需要了解更多有关PCB的知识可点击PCB。
绘制原理图以及PCB的软件采用AD9,也可以采用其他的PCB绘制软件,绘制的原理图纸张大小才有用A4,原理图封装可以根据自己的需要进行DIY设计,PCB封装可以去立创商城进行获取。
(1)原理图设计
原理图设计的内容主要有MCU、传感器接口电路、液晶显示电路以及电源供电电路。
a.MCU设计
由于STC15W408AS单片机不同于一般的51单片机,它不需要复位电路和晶振电路,因此在MCU上就显得非常的简单,设计如下:
b.传感器接口设计
本次使用的陀螺仪是ADXL345,区别于MPU605陀螺仪的是陀螺仪和加速度计的结合,而ADXL345只有加速度计,两者相比MPU6050要高级的多,但这也并不能代表ADXL345毫无用处,作为入门级的陀螺仪学习它是一个很好地选择,同时它也具备姿态检测的功能,在价格上也便宜于MPU6050。ADXL345在驱动是需要两个电阻作为上拉进行工作,设计图如下:
c.液晶显示
液晶显示由于单片机的管脚有限,因此采用LCD1602进行显示。其实修改液晶的数据传输方式可以节省一部分的管脚进而可以采用LCD12864来进行更后的设计的。如果将原本的8位数据传输口改为4位数据口的话就能够采用LCD12864来进行设计,LCD12864液晶可以用来做界面显示,所设计的界面更加的美观,而作者手边恰好没有LCD12864只有LCD1602,所以有兴趣的伙伴可以采用LCD12864来设计。LCD1602的显示需要采用排阻将数据传输端口上拉,设计图如下:
d.电源
电源部分采用接口式电源供电,即插孔。插孔也就是单片机开发板上的供电部分之一,这种方法简单但如果想要便携式的设计感觉,可以将其改为电池供电,若不想加入电池充电电路可以采用带充电口的电池。这里我采用手边现有的火牛接口插座进行供电,外加LED小灯用于判断是否有电源接通作为提示,供电电路如下:
e.总体图样
综上所述,结合以上的设计图可以汇总出如下的整体原理图:
(2)PCB设计
PCB的设计其实就是将原理图绘制完成后,选择元器件的封装,导入到PCB中,根据PCB中的网络信号线进行连接就可以。说着简单其实在这之前还需要设置好绘制的规制,其中包括了线宽、报警界限以及过孔等,只有在设置好的规则下才能够画好PCB。本次的PCB绘制的是双层板,当然由于连接线部分较为简单采用单层板也是可以的,主要考虑单层与双层价格上大同小异所以干脆就设计成了双层板,然而在现实生活中采用单层板的绘制是很少的,多数采用的绘制是双层板以及四层或者更高的板数,所以即使简单,但采用双层不仅可以学习双层板的绘制还可以加深绘制的能力。
a.2D绘制展示(顶层和底层)
顶层
底层
b.3D绘制展示(正面和反面)
正面
反面
2.软件设计
本次程序设计采用的是多C文件进行设计,分开写程序可以清楚的梳理程序的流程,在出错的时候可以轻松的找出错误,可大大的提高程序的管理和编程的思路。多文件编程是必要的,甚至是必须的。简单来说就是把功能相近或者相关的函数变量定义添加到.c和.h文件中,这样的两个文件称为一个模块。
特点:
1、方便代码复用: 模块化的代码可以很方便的迁移到其他项目中去,改写模块比重写模块更快;
2、方便分工合作: 各个功能模块分成多个文件同时编辑,可以有效的提高团队开发的分工协作效率;
3、方便后续维护: 项目源码交接时,不管是客户还是同事,对于划分明确的文件是不会排斥的;
4、保证了库支持: 个人函数库都是由c文件产生库,采用“库文件+h文件”的形式,可以实现快速编译和保护源码的效果。
(1)IIC程序设计
IIC程序主要是驱动ADXL345的通讯,程序参考一般单片机上的IIC程序。如果对IIC还有不了解的可以参考文章I2C协议—I2C时序图解析。IIC通讯时序如下:
写时序
读时序
代码如下(IIC.h):
#ifndef _IIC_H_
#define _IIC_H_
#include <REGX52.H>
sbit SCL=P2^7;
sbit SDA=P2^6;
void Delay5ms(); //延时5us
void I2cStart(); //起始信号
void I2cStop(); //终止信号
void I2C_SendACK(bit ack); //发送应答信号
bit I2C_RecvACK(); //接收应答信号
//通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
void I2cSendByte(unsigned char dat);
unsigned char I2cReadByte(); //使用I2c读取一个字节
#endif
代码如下(IIC.c):
#include "IIC.h"
#include "intrins.h"
/*******************************************************************************
* 函数名 : Delay5us()
* 函数功能 : 延时10us
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Delay5us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 11;
while (--i);
}
/*******************************************************************************
* 函数名 : I2cStart()
* 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入 : 无
* 输出 : 无
* 备注 : 起始之后SDA和SCL都为0
*******************************************************************************/
void I2cStart()
{
SDA = 1; //拉高数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 0; //产生下降沿
Delay5us(); //延时
SCL = 0; //拉低时钟线
}
/*******************************************************************************
* 函数名 : I2cStop()
* 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入 : 无
* 输出 : 无
* 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void I2cStop()
{
SDA = 0; //拉低数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 1; //产生上升沿
Delay5us(); //延时
}
/**************************************
发送应答信号
入口参数:ack (0:ACK 1:NAK)
**************************************/
void I2C_SendACK(bit ack)
{
SDA = ack; //写应答信号
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
/**************************************
接收应答信号
**************************************/
bit I2C_RecvACK()
{
SCL = 1; //拉高时钟线
Delay5us(); //延时
CY = SDA; //读应答信号
SCL = 0; //拉低时钟线
Delay5us(); //延时
return CY;
}
/*******************************************************************************
* 函数名 : I2cSendByte(unsigned char dat)
* 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入 : num
* 输出 : 0或1。发送成功返回1,发送失败返回0
* 备注 : 发送完一个字节SCL=0,SDA=1
*******************************************************************************/
void I2cSendByte(unsigned char dat)
{
unsigned char i;//最大255,一个机器周期为1us,最大延时255us。
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1; //移出数据的最高位
SDA = CY; //送数据口
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
I2C_RecvACK();
}
/*******************************************************************************
* 函数名 : I2cReadByte()
* 函数功能 : 使用I2c读取一个字节
* 输入 : 无
* 输出 : dat
* 备注 : 接收完一个字节SCL=0,SDA=1.
*******************************************************************************/
unsigned char I2cReadByte()
{
unsigned char i=0,dat=0;
SDA = 1; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL = 1; //拉高时钟线
Delay5us(); //延时
dat |= SDA; //读数据
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
return dat;
}
(2)LCD1602程序设计
同理LCD1602的驱动主要依赖于LCD_RS、LCD_EN、LCD_R/W三个控制端口,根据时序图的变化将数据通多8位数据传输端口或者4位数据传输的方式写入显示屏中,需要深刻了解LCD1602的时序的可以参考文章1602液晶与串口的应用实例,LCD1602的时序如下:
由于代码部分太多这里只展示点C文件的代码。
代码如下(LCD1602.c):
#include "lcd1602.h"
/*******************************************************************************
* 函 数 名 : Lcd1602_Delay1ms
* 函数功能 : 延时函数,延时1ms
* 输 入 : c
* 输 出 : 无
* 说 名 : 该函数是在12MHZ晶振下,12分频单片机的延时。
*******************************************************************************/
void Lcd1602_Delay1ms(uint c) //误差 0us
{
uchar a,b;
for (; c>0; c--)
{
for (b=199;b>0;b--)
{
for(a=1;a>0;a--);
}
}
}
/*******************************************************************************
* 函 数 名 : LcdWriteCom
* 函数功能 : 向LCD写入一个字节的命令
* 输 入 : com
* 输 出 : 无
*******************************************************************************/
void LcdWriteCom(uchar com,uchar Attribc) //写入命令
{
if(Attribc)WaitForEnable();
LCD1602_E = 0; //使能清零
LCD1602_RS = 0; //选择写入命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
LCD1602_DATAPINS = com << 4; //发送低四位
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
}
/*******************************************************************************
* 函 数 名 : LcdWriteData
* 函数功能 : 向LCD写入一个字节的数据
* 输 入 : dat
* 输 出 : 无
*******************************************************************************/
void LcdWriteData(uchar dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择写入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //由于4位的接线是接到P0口的高四位,所以传送高四位不用改
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
LCD1602_DATAPINS = dat << 4; //写入低四位
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5);
LCD1602_E = 0;
}
/*******************************************************************************
* 函 数 名 : LcdInit()
* 函数功能 : 初始化LCD屏
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x32,1); //将8位总线转为4位总线
LcdWriteCom(0x28,1); //在四位线下的初始化
LcdWriteCom(0x0c,1); //开显示不显示光标
LcdWriteCom(0x06,1); //写一个指针加1
LcdWriteCom(0x01,1); //清屏
LcdWriteCom(0x80,1); //设置数据指针起点
}
//判断LCD1602是否处于忙状态
void WaitForEnable(void)
{
P1=0xff;
LCD1602_RS=0;
LCD1602_RW=1;
Lcd1602_Delay1ms(1);
LCD1602_E=1;
Lcd1602_Delay1ms(1);
Lcd1602_Delay1ms(1);
while(P1&0x80);
LCD1602_E=0;
}
//显示字符
/***********************************/
void DisplayOneChar(uchar X,uchar Y,uchar DData)
{
Y&=1;
X&=15;
if(Y)X|=0x40;
X|=0x80;
LcdWriteCom(X,0);
LcdWriteData(DData);
}
(3)ADXL345程序设计
IIC驱动程序和LCD1602的显示驱动写完后,接下来就可以编写ADXL345的数据处理以及如何将三轴数据显示到LCD1602上进行显示。
代码如下(ADXL345.h):
#ifndef _ADXL345_H_
#define _ADXL345_H_
#include <REGX52.H>
#define SlaveAddress 0xA6 //定义器件在IIC总线中的从地址,根据ALT ADDRESS地址引脚不同修改
//ALT ADDRESS引脚接地时地址为0xA6,接电源时地址为0x3A
typedef unsigned int uint;
typedef unsigned char BYTE;
typedef unsigned short WORD;
void Init_ADXL345(void); //初始化ADXL345
void delay(unsigned int k);
void conversion(uint temp_data); //ADXL345数据处理函数
void Single_Write_ADXL345(BYTE REG_Address,BYTE REG_data); //单个写入数据
BYTE Single_Read_ADXL345(BYTE REG_Address); //单个读取内部寄存器数据
void Multiple_Read_ADXL345(); //连续的读取内部寄存器数据
//显示x轴
void display_x();
//显示y轴
void display_y();
//显示z轴
void display_z();
#endif
代码如下(ADXL345.c):
#include "ADXL345.h"
#include "iic.h"
#include "lcd1602.h"
#include <math.h> //Keil library
#include <stdio.h> //Keil library
BYTE BUF[8]; //接收数据缓存区
BYTE ge,shi,bai,qian,wan; //显示变量
uint dis_data; //变量
void conversion(uint temp_data)
{
wan=temp_data/10000+0x30 ;
temp_data=temp_data%10000; //取余运算
qian=temp_data/1000+0x30 ;
temp_data=temp_data%1000; //取余运算
bai=temp_data/100+0x30 ;
temp_data=temp_data%100; //取余运算
shi=temp_data/10+0x30 ;
temp_data=temp_data%10; //取余运算
ge=temp_data+0x30;
}
void delay(unsigned int k)
{
unsigned int i,j;
for(i=0;i<k;i++)
{
for(j=0;j<121;j++)
{;}}
}
//******单字节写入*******************************************
void Single_Write_ADXL345(BYTE REG_Address,BYTE REG_data)
{
I2cStart(); //起始信号
I2cSendByte(SlaveAddress); //发送设备地址+写信号
I2cSendByte(REG_Address); //内部寄存器地址,请参考中文pdf22页
I2cSendByte(REG_data); //内部寄存器数据,请参考中文pdf22页
I2cStop(); //发送停止信号
}
//********单字节读取*****************************************
BYTE Single_Read_ADXL345(BYTE REG_Address)
{
BYTE REG_data;
I2cStart(); //起始信号
I2cSendByte(SlaveAddress); //发送设备地址+写信号
I2cSendByte(REG_Address); //发送存储单元地址,从0开始
I2cStart(); //起始信号
I2cSendByte(SlaveAddress+1); //发送设备地址+读信号
REG_data=I2cReadByte(); //读出寄存器数据
I2C_SendACK(1);
I2cStop(); //停止信号
return REG_data;
}
//*********************************************************
//
//连续读出ADXL345内部加速度数据,地址范围0x32~0x37
//
//*********************************************************
void Multiple_read_ADXL345(void)
{
BYTE i;
I2cStart(); //起始信号
I2cSendByte(SlaveAddress); //发送设备地址+写信号
I2cSendByte(0x32); //发送存储单元地址,从0x32开始
I2cStart(); //起始信号
I2cSendByte(SlaveAddress+1); //发送设备地址+读信号
for (i=0; i<6; i++) //连续读取6个地址数据,存储中BUF
{
BUF[i] = I2C_RecvACK(); //BUF[0]存储0x32地址中的数据
if (i == 5)
{
I2C_SendACK(1); //最后一个数据需要回NOACK
}
else
{
I2C_SendACK(0); //回应ACK
}
}
I2cStop(); //停止信号
delay(5);
}
//初始化ADXL345,根据需要请参考pdf进行修改************************
void Init_ADXL345()
{
Single_Write_ADXL345(0x31,0x0B); //测量范围,正负16g,13位模式
Single_Write_ADXL345(0x2C,0x08); //速率设定为12.5 参考pdf13页
Single_Write_ADXL345(0x2D,0x08); //选择电源模式 参考pdf24页
Single_Write_ADXL345(0x2E,0x80); //使能 DATA_READY 中断
Single_Write_ADXL345(0x1E,0x00); //X 偏移量 根据测试传感器的状态写入pdf29页
Single_Write_ADXL345(0x1F,0x00); //Y 偏移量 根据测试传感器的状态写入pdf29页
Single_Write_ADXL345(0x20,0x05); //Z 偏移量 根据测试传感器的状态写入pdf29页
}
//***********************************************************************
//显示x轴
void display_x()
{
float temp;
dis_data=(BUF[1]<<8)+BUF[0]; //合成数据
if(dis_data<0)
{
dis_data=-dis_data;
DisplayOneChar(2,0,'-'); //显示正负符号位
}
else DisplayOneChar(2,0,' '); //显示空格
temp=(float)dis_data*3.9; //计算数据和显示,查考ADXL345快速入门第4页
conversion(temp); //转换出显示需要的数据
DisplayOneChar(0,0,'X'); //第0行,第0列 显示X
DisplayOneChar(1,0,':');
DisplayOneChar(3,0,qian);
DisplayOneChar(4,0,'.');
DisplayOneChar(5,0,bai);
DisplayOneChar(6,0,shi);
DisplayOneChar(7,0,'g');
}
//***********************************************************************
//显示y轴
void display_y()
{
float temp;
dis_data=(BUF[3]<<8)+BUF[2]; //合成数据
if(dis_data<0){
dis_data=-dis_data;
DisplayOneChar(2,1,'-'); //显示正负符号位
}
else DisplayOneChar(2,1,' '); //显示空格
temp=(float)dis_data*3.9; //计算数据和显示,查考ADXL345快速入门第4页
conversion(temp); //转换出显示需要的数据
DisplayOneChar(0,1,'Y'); //第1行,第0列 显示y
DisplayOneChar(1,1,':');
DisplayOneChar(3,1,qian);
DisplayOneChar(4,1,'.');
DisplayOneChar(5,1,bai);
DisplayOneChar(6,1,shi);
DisplayOneChar(7,1,'g');
}
//***********************************************************************
//显示z轴
void display_z()
{
float temp;
dis_data=(BUF[5]<<8)+BUF[4]; //合成数据
if(dis_data<0){
dis_data=-dis_data;
DisplayOneChar(10,1,'-'); //显示负符号位
}
else DisplayOneChar(10,1,' '); //显示空格
temp=(float)dis_data*3.9; //计算数据和显示,查考ADXL345快速入门第4页
conversion(temp); //转换出显示需要的数据
DisplayOneChar(10,0,'Z'); //第0行,第10列 显示Z
DisplayOneChar(11,0,':');
DisplayOneChar(11,1,qian);
DisplayOneChar(12,1,'.');
DisplayOneChar(13,1,bai);
DisplayOneChar(14,1,shi);
DisplayOneChar(15,1,'g');
}
(4)main程序设计
以上程序敲定后,接下来就是在主函数中进行总体的程序流程编写。
代码如下(main.c):
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay_time(u16 i)
{
while(i--);
}
//主函数
int main(void){
u8 i;
delay(500); //上电延时
LcdInit(); //液晶初始化
Init_ADXL345(); //初始化ADXL345
i=Single_Read_ADXL345(0X00);//读出的数据为0XE5,表示正确
while(1){
Multiple_Read_ADXL345(); //连续读出数据,存储在BUF中
display_x(); //---------显示X轴
display_y(); //---------显示Y轴
display_z(); //---------显示Z轴
delay_time(200); //延时
}
}
综上就是本次设计的全部内容。
三、设计结果展示
1、程序运行结果(Keil)
2、实物展示
由于实物部分不在身边,后面有时间补上。
四、总结
本次是基于ADXL345的数据读取设计,采集三轴数据后还可以扩展到具体的应用中。例如将三轴数据应用于老年人的防摔倒、平衡车以及简单的飞行状态检测等。
项目连接:
点击这里