2015-08-22 22:19:38 h_xl88 阅读数 899
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3418 人正在学习 去看看 朱有鹏

单片机项目开发的一些心得

1:拿到项目需求后,需统计完成项目功能需要多少个IO口和其他的一些外设,另一项重要指标就是单片机系统的功耗问题;

2:完成第1步之后,进行单片机选型,此时除了考虑单片机的功能需要满足步骤1统计的要求之外,还需要考虑的问题是单片机的成本、供货最小批量以及供货周期;另外单片机选型的时候,还需要考虑产品升级,单片机资源应当有一定的冗余。对于程序开发者而言,选择的单片机品牌最好是自己熟悉的,这样可以加快项目的完成。

3:根据项目需求,设计电子电路原理图。原理图设计完成后,应当进行评审。评审可以避免一些原理上的错误,以及可以听取别人的建议和经验。

4:单片机程序设计人员需仔细阅读项目需求,画出编程流程图。好的流程图可以指导程序设计人员快速完成代码设计,并且方便程序的维护。

5:自测和测试。

2017-12-31 13:55:32 ylzmm 阅读数 393
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3418 人正在学习 去看看 朱有鹏

在一个论坛中看到,觉得很不错。记录学习,分享给大家。

“分层思想”并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用。看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种恍然大悟的感觉。如果说我不懂LCD怎么驱动,那好办,看一下datasheet,参考一下别人的程序,很快就可以做出来。但是如果不懂程序设计的思想的话,会给你做项目的过程中带来很多很多的困惑。

参考了市面上各种各样的嵌入式书籍,MCS-51,AVR ,ARM 等都有看过,但是没有发现有哪本是介绍设计思想的,就算有也是凤毛麟角。写程序不难,但是程序怎么样才能写的好,写的快,那是需要点经验积累的。结构化模块化的程序设计的思想,是最基本的要求。然而怎么将这个抽象的概念运用到工程实践当中呢?那需要在做项目的过程中经历磨难,将一些东西总结出来,抽象升华为理论,对经验的积累和技术的传播都大有裨益。所以在下出来献丑一下,总结一些东西。就我个人的经验而谈,有两个设计思想是非常重要的。

一个就是“时间片轮的设计思想”,这个对实际中解决多任务问题非常有用,通常可以用这个东西来判断一个人是单片机学习者,还是一个单片机工程师。这个必须掌握。由于网上介绍这个的帖子也不少,所以这里就不多说了。

第二个就是我今天想说的主题“分层屏蔽的设计思想”。下面用扫描键盘程序例子作为引子,引出今天说的东西。

问题的提出 :单片机学习板一般为了简单起见,将按键分配的很好,例如整个 4*4 的键盘矩阵分配到P1口上面,8条控制线,刚好。这样的话程序也非常好写。只需要简单的

KEY_DAT = P1;

端口的数据就读进来了。诚然,现实中没有这么好的事情。在实际的项目应用当中,单片机引脚的复用相当厉害,这跟那些所谓的单片机学习板就有很大的差别了。

另外一个原因,一般设计来说,是“软件配合硬件”的设计流程,简单点说就是,先确定好硬件原理图,硬件布线,最后才是软件的开发,因为硬件修改起来比较麻烦,相对来说软件修改的时候比较好改。这个就是中国传统的阴阳平衡哲学原理。硬件设计和软件设计本来就是鱼和熊掌的关系,两者不可兼得。方便了硬件设计,很可能给写软件带来很大的麻烦。反过来说,方便了软件设计,硬件设计也会相当的麻烦。如果硬件设计和软件设计同时方便了,那只有两种可能,一是这个设计方案非常简单,二是设计师已经达到了一个非常高的境界。我们不考虑那么多情况,单纯从常用的实际应用的角度来看问题。 硬件为了布线的方便,很多时候会可能将IO口分配到不同的端口上面,例如上面说的4*4键盘,8根线分别分配到 P0 P1 P2 P3 上面去了。那么,开发板的那些扫描键盘程序可以去见鬼了。怎么扫按键?我想起了我刚开始学习的时候,分成3段非常相似的程序,一个一个按键的扫描的经历...... 或许有人不甘心,“那些东西我花了很长时间学习的,也用的好好的,怎么能说一句不用就不用?”虽然有点残忍,但是我还是想说“兄弟,接受现实吧,现实是残酷的......”

不过,人区别于低等动物的差别,是人会创造,在碰到困难的时候会想办法解决,于是我们开始了沉思...... 后我们引入初中数学学的“映射”的概念来解决问题。基本思想就是,将不同端口的按键映射到相同端口上面。

这样按键扫描程序就分成3个层次了。

1)最底层的是硬件层,完成端口扫描,20ms延时消抖,将端口的数据映射到一个KEY_DAT寄存器上面,KEY_DAT作为对上层驱动层的一个接口。

2)中间的一层是驱动层,驱动层只对 KEY_DAT 寄存器的数值进行操作。简单点说,我们无论底层的硬件是怎么接线的,在驱动层都不需要关心,只需要关心 KEY_DAT 这个寄存器的数值是什么就可以了。这样出来的间接效果就是“屏蔽了底层硬件的差异”,所以驱动层写的程序就可以通用了。驱动层的另外一个功能是为了上层提供消息接口。我们用了类似window程序的消息的概念。这里可以提供一些按键消息,例如:按下消息,松开消息,长按键消息,长按键的时候的步进消息,等等。

3)应用层。这里就是根据项目的不同分别写按键功能程序,属于最上层的程序。它使用的是驱动层提供的消息接口。在应用层写程序的思想就是,我不管下层是怎么工作的,我只关心按键消息。有按键消息来的时候我就执行功能,没有消息来的时候,我就什么也不做。

下面用一个简单的常用的例子,说明我们这个设计思想的用法。秒表调整时间的时候,要求按着某个按键不放,时间能连续的向上增加。这个东西很实用,实际的家电中用途很广泛。在看下面的东西之前,大家可以想一下,这东西难吗?相信大家都会很响亮的回答,“不难!!”,然而我再问:“这东西麻烦吗?”我相信很多人肯定会说“很麻烦!!” 这不禁让我想起开始学单片机的时候写这种按键的那程序,乱七八糟的结构。如果不相信的话,可以自己用51写一下哦,那样就更加能体会本文说的分层结构的优越性。

项目要求:两个按键,分别分配在P10 和P20,分别是“加”“减”按键,要求长按键的时候实现连续加和连续减的功能。

实战:假设:按键上拉,没有按键的时候高电平,有按键的时候低电平,另外,为了突出问题,这里没有将延时消抖的程序写上去,在实际项目中应该加上。C语言函数参数的传递多种多样,这里作为例子,用了最简单的全局变量来传递参数,当然你也可以用 unsigned char ReadPort(void) 返回一个读键结果,甚至还可以 void ReadPort(unsigned char *pt) 用一个指针变量传递地址而达到直接修改变量的目的。方法是多种多样的,这个决定于每个人的程序风格。

1)开始写硬件层程序,完成映射

#define KYE_MIN 0X01

#define KEY_PLUS 0X01

unsigned char KeyDat;

void ReadPort(void)

{

if (P1 & KEY_PLUS == 0 ){

KeyDat |= 0x01 ;

}

if (P2 & KEY_MIN == 0 ){

KeyDat |= 0x02 ;

}

}

C语言应该很容易看懂吧?如果 KEY_PLUS 按下,P10口读到低电平,则 P1 & KEY_PLUS 的结果为 0 ,满足if 的条件,进入KeyDat |= 0x01 是将 KeyDat 的bit0 置一,也就是说,将 KEY_PLUS 映射到 KeyDat 的 bit0

KEY_MIN 是同样的道理映射到 KeyDat 的 bit1

如果 KeyDat 的 bit0 为 1 ,则说明 KEY_PLUS 按下,反则亦然。

不需要想的很神秘,映射就是这么一回事。如果还有其他按键的话,用同样办法,将他们全部映射到 KeyDat 上面。

2)驱动层程序编写 【【硬件层给驱动层提供最基本的一些屏蔽掉底层的一些硬件的相关数据,这样可以把硬件层的数据结构(比如这里是KeyDat)提供给驱动层,驱动层负责调用硬件层,应用层负责调用驱动层】】

如果将 KeyDat想象成 P1 口,那么这个跟学习板那标准的扫描程序不就是一样了吗?对的,这个就是底层映射的目的了。

3)应用层程序编写

根据消息,硬件层是必须分离出来,然而驱动层和应用层的要求就不那么严格了,事实上一些简单的项目没有必要将这两层分离开来,根据实际应用灵活应对就可以了。其实这样写程序是很方便移植的,根据板子的不同而适当的修改一下硬件层那个 ReadPort 函数就完成了,驱动层和应用层很多代码可以不经过修改直接用,很能提高开发效率的。当然这个按键程序会存在一定的问题,特别是遇到常闭按键和点触按键的混合使用的场合。这个留给大家自己去想了,反正问题总是能找到解决办法的,尽管方法有好有坏。

结束语

以按键为媒介,介绍了程序设计当中的“分层屏蔽”的思想的原理和应用,按键只是一个例子,其实分层的思想普遍存在着程序设计当中。细心留意一下的话发现其实window,linux,网络的tcp/ip 结构全部都是分层的。这东西不是绣花枕头,而是实际用在工程上面的,只是平时不多见帖子介绍,或者没有人特意这样来总结,又或者是有经验的工程师作为藏在心中的法宝吧,这个就不得而知。不过好东西应该共享,菜鸟应该共勉,一起来学飞吧。



2019-12-01 21:56:19 weixin_44793883 阅读数 300
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3418 人正在学习 去看看 朱有鹏

背景介绍

(这是本人第一次写博客,会有一些牢骚,我保证以后不会这么废话了)。本人做为一名大三学生,这个学期在学长的建议下选修了单片机课程。老师要求完成一篇大作业(三选一),我就选择了最简单的车速里程表的设计。至于我为什么要写这篇博客,是因为我在网上找到相关的资料时(类似于霍尔传感器的车速里程表设计),但是CSDN上的整个项目的源代码都要付费查看,于是我很气愤,遂有此文。

备选的三个题目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为目测题目二最简单,所以我就选了题目二。

脉冲波部分(单片机模拟信号发生器)

由于老师实验室一下子找不出来信号发生器,所以我找同学借了一个单片机作为信号发生器,用跳线连接信号发生器的P37引脚和车速里程表的P35引脚(老师因此还表扬了我)。

源代码1

#include "reg51.h"
#include "intrins.h"

typedef unsigned int u16;	 
typedef unsigned char u8;

sbit pulse = P3^7; //用p37引脚发出脉冲

void Time0Init() //中断初始化函数
{
	TMOD |= 0x01;
	TH0 = 0xfc;
	TL0 = 0x18; //中断定时1秒
	ET0 = 1;
	EA = 1;
	TR0 = 1;
}

void main()
{
	Time0Init();
	while(1); //无限循环,等待中断
}

void Time0() interrupt 1
{
	pulse = ~pulse;
}

接收端主程序

基本思路

引用一个定时器和一个计数器(我的单片机是通过p35引脚输入的,不同的单片机可能不同),定时一秒钟,计算这一秒钟内接收的脉冲个数,乘以一个系数即可得到速度(根据题目描述可计算得这个系数为1,单位为km/h),对速度进行累加即可得到里程。

源代码2

/*
车速里程表项目
输入:信号模拟器产生脉冲,通过引脚P35接入单片机
功能:显示车速,通过按键k1控制显示单次里程与总里程的切换,通过k2长按将单次里程清零
*/
#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16;
typedef unsigned char u8;
typedef unsigned long int ul;

sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit k1=P3^1;
sbit k2=P3^0;
sbit led=P2^0; //用一个LED灯作为指示信号

static u16 single_mileage=0; //单次里程
static u16 total_mileage=0; //总里程
static u16 speed=0;
static u16 realspeed=0; //speed通过realspeed传出
static u16 j=0;
static u16 temp1=0;
static u16 temp2=0;
static u16 temp3=0; //三个中间变量,在按键判断时会用到

u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
					0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void delay(u16 i)
{
	while(i--);	
}

void Time0Init()  //定时器初始化函数,定时10ms
{
	TMOD|=0x51;
	TH0=0xd8;
	TL0=0xf0;
	ET0=1;
	EA=1;
	TR0=1;
}

void Count1Init()  //计数器初始化函数
{
	TMOD|=0x51;
	TH1=0x00;
	TL1=0x00;
	ET1=1;
	EA=1;
	TR1=1;
}
/*------------------------------------------------
函数功能:获取xxxx的每一位并显示在数码管的高四位
------------------------------------------------*/
void DisplaySpeed(u16 x)	
{
	u16 x_gewei=x/1%10;
	u16 x_shiwei=x/10%10;
	u16 x_baiwei=x/100%10;
	u16 x_qianwei=x/1000%10;
	
	u8 i;
	for(i=0;i<4;i++)
	{
		switch(i)
		{
		    case(0):
				LSA=1;LSB=1;LSC=1;P0=smgduan[x_qianwei]; break;
			case(1):
				LSA=0;LSB=1;LSC=1;P0=smgduan[x_baiwei]; break;
			case(2):
				LSA=1;LSB=0;LSC=1;P0=smgduan[x_shiwei]; break;
			case(3):	
				LSA=0;LSB=0;LSC=1;P0=smgduan[x_gewei]; break;
			
		}
		//P0=smgduan[i];
		delay(100);
		P0=0x00;
	}
}

/*------------------------------------------------
函数功能:获取xxxx的每一位并显示在数码管的低四位
------------------------------------------------*/
void DisplayMileage(u16 x)	 
{
	u16 x_gewei=x/1%10;
	u16 x_shiwei=x/10%10;
	u16 x_baiwei=x/100%10;
	u16 x_qianwei=x/1000%10;
	
	u8 i;
	for(i=0;i<4;i++)
	{
		switch(i)
		{
		    case(0):
				LSA=1;LSB=1;LSC=0;P0=smgduan[x_qianwei]; break;
			case(1):
				LSA=0;LSB=1;LSC=0;P0=smgduan[x_baiwei]; break;
			case(2):
				LSA=1;LSB=0;LSC=0;P0=smgduan[x_shiwei]; break;
			case(3):	
				LSA=0;LSB=0;LSC=0;P0=smgduan[x_gewei]; break;
			
		}
		//P0=smgduan[i];
		delay(100);
		P0=0x00;
	}
}
/*-----------------------------------------------------------
主函数:将定时器和计数器初始化,并无限循环等待中断
-----------------------------------------------------------*/
void main()
{
	Time0Init();  //计时器0初始化
	Count1Init();  //计数器1初始化
	while(1);	//无限循环
}
	
/*-----------------------------------------------------------
中断服务函数:每过1s将计数器的值传给speed,并通过realspeed传出
-----------------------------------------------------------*/
void Time0() interrupt 1
{
	TH0=0xd8;
	TL0=0xf0;
	j++;
	
	if(k2==0)
	{
		temp2++;
	}
	if(temp2==300)
	{
		temp2=0;
		single_mileage=0;
	}
	if(j==100)	  //1s
	{
		led=~led;
		j=0;
		speed=(TH1*256+TL1);	  //计数传给speed
		single_mileage+=speed;
		total_mileage+=speed;
		realspeed=speed;
		//speed=1;
		TH1=0x00;	  //每秒计数后,需将计数器初始化
		TL1=0x00;
		speed=0;
	}
	DisplaySpeed(realspeed);	//显示速度
	if(k1==0)
	{
		_nop_();
		if(k1==0)
		{
			temp1++;
		}
	}
	if(temp1<=40 && temp1>=15)
	{
		temp1=0;
		temp3++;
	}
	if(temp3%2==1)
	{
		DisplayMileage(total_mileage);
	}
	else
	{
		DisplayMileage(single_mileage);
	}
}

设计过程中遇到的问题和解决的办法

  1. 最开始的设计思路是在main函数中显示速度和里程并用delay函数判断按键的短按和长按,但是那样没办法按下k1切换里程显示,只能按下的时候显示总里程,不按的时候显示单次里程,而且在长按清零过程中数码管会闪烁。后来老师告诉我将显示函数放到中断函数中,并将单次中断改10ms,这样在长按将single_mileage清零的时候数码管就不会闪烁。(最开始我设计的中断时是1ms,在显示函数中的delay函数会对中断产生影响,这个问题将单次中断时间变长就能解决)
  2. 按键函数为什么不用书上的delay函数?同样是因为按键判断的软件消抖过程delay会对中断产生影响。因此定义一个变量temp,在按键按下的时候自加一,加到10就是按键按下100ms(短按),加到300就是按键按下3s(长按)。(temp1<=40 && temp1>=15)是我调试多次之后确定的基本符合短按规律的边界值。
  3. 第一版代码快完成的时候,发现DisplayMileage只显示0,想了半天才发现每计数都已经把speed清零了,因此定义一个变量realspeed将每次的计数结果传出。

结束语

这是我的第一篇博客,终于理解为什么程序员喜欢写博客了,写博客是真滴快乐。当然还是报着学习的态度,如有错误望网友轻喷,欢迎与我交流。QQ:37892436

2019-03-05 08:49:39 bornpride 阅读数 539
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3418 人正在学习 去看看 朱有鹏

我们学习单片机的目的就是为了进行嵌入式系统的开发,学好单片机首先要有一个整体认识,下面将简要介绍一下单片机应用系统的开发流程,如图1所示。
单片机开发流程图

图1 单片机系统开发流程

1、明确任务

分析和了解项目的总体要求,并综合考虑系统使用环境、可靠性要求、可维护性及产品的成本等因素,制定出可行的性能指标。

2、划分软、硬件功能

单片机系统由软件和硬件两部分组成。在应用系统中,有些功能既可由硬件来实现,也可以用软件来完成。硬件的使用可以提高系统的实时性和可靠性;使用软件实现,可以降低系统成本,简化硬件结构。因此在总体考虑时,必须综合分析以上因素,合理地制定硬件和软件任务的比例。

3、确定希望使用的单片机及其他关键部件

根据硬件设计任务,选择能够满足系统需求并且性价比高的单片机及其他关键器件,如A/D、D/A转换器、传感器、放大器等,这些器件需要满足系统精度、速度以及可靠性等方面的要求。

4、硬件设计

根据总体设计要求,以及选定的单片机及关键器件,利用Protel等软件设计出应用系统的电路原理图。

5、软件设计

在系统整体设计和硬件设计的基础上,确定软件系统的程序结构并划分功能模块,然后进行各模块程序设计。

单片机程序设计语言可分为三类:
➢机器语言 :又称为二进制目标代码,是CPU硬件唯一能够直接识别的语言(在设计CPU时就已经确定其代码的含义)。人们要计算机所执行的所有操作,最终都必须转换成为相应的机器语言由CPU识别、控制执行。CPU系列不同,其机器语言代码的含义也不尽相同。
➢ 汇编语言 :由于机器语言必须转换为二进制代码描述,不便于记忆、使用和直接编写程序,为此产生了与机器语言相对应的汇编语言。用汇编语言编写的程序执行速度快,占用存储单元少,效率高。
➢ 高级语言 :高级语言具有很好的可读性,使程序的编写和操作都十分方便,目前广泛使用的高级语言是C51。

注意:汇编语言和高级语言都必须被翻译成机器语言之后才能被CPU识别

6、仿真调试

软件和硬件设计结束后,需要进行进行进入两者的整合调试阶段。为避免浪费资源,在生成实际电路板之前,可以利用Keil C51和Proteus软件进行系统仿真,出现问题可以及时修改。

7、系统调试

完成系统仿真后,利用Protel等绘图软件,根据电路原理图绘制PCB(Printed Circuit Board),即印刷电路板图,然后将PCB图交给相关厂商生产电路板。拿到电路板后,为便于更换器件和修改电路,可首先在电路板上焊接所需芯片插座,并利用编程器将程序写入单片机。然后将单片机及其他芯片插到相应的芯片插座中,接通电源及其他输入、输出设备,进行系统联调,直至调试成功。

8、测试修改、用户试用

经测试检验符合要求后,将系统交给用户试用,对于出现的实际问题进行修改完善,系统开发完成。

2018-06-29 22:10:58 a568713197 阅读数 19277
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3418 人正在学习 去看看 朱有鹏

        其实前几周就已经将小车调好并且也发了视频,但是每天忙于复习,也没有时间来对小车的流程设计、硬件设计、程序编写进行一个总结,正好周五可以休息一下,就分三个模块对这个智能小车项目进行一个总结。

        这个小项目完全是我一个人搞得,因为组内的小伙伴们都还没学32,他们使用arduino搭的小车,但是毕竟实现的功能还是很简单的,也只是做了一周不到的时间。

        首先是小车的流程设计,文老师总是教导,每做一个项目,一开始要写需求文档、画流程图,项目中要写开发文档,项目完成后要写总结。因为这次的项目是一个人搞的,所以说开发文档在制作过程中并没有写。

        需求以及所用的模块如下 

            功能要求:

            1、 走直线

            2、 寻线(S弯)

            3、 避障碍

            4、 蓝牙控制 

            模块清单:

            电机驱动L298N

            红外对管       *2

            超声波测距 HC-SR04

            电源模块 12->3.3 / 5 / 12

            减速直流电机(6V)

            蓝牙模块 HC-05

            LM2596 DC-DC稳压模块

            SG90 9克微型舵机

            //码盘测速模块

            单片机: stm32f103c8t6

        具体的模块讲解以及使用心得会在后面的硬件设计博客中记录。

       接下来是流程图

        1、避障碍

       

        2、巡线

            

        3、蓝牙控制

        


没有更多推荐了,返回首页