单片机

一提到智能家庭,大家可能首先想到的是各种大佬级公司搞的牛逼产品,或者说是创新产品。想想就觉得很复杂,有的用工控机,有的用树莓派,还有的用arduino,不管用什么,都绕不过服务器进行控制,比如yeelink平台,腾讯的智能硬件平台等等。其实,真实实现起来,并没有想想中这么复杂,我们甚至只用一个小的单片机就能实现。

0×01单片机实现web服务器

探讨用单片机来实现web服务器的文章通过baidu也能找到几篇,但比较详实的实现方法并没有找到,这里考虑有两种思路可以完成,一种是有线的lan模块,如w5500,这种模块本身就带有web服务的部分功能,使用起来比较简单,但是只能基于lan进行访问。另一种是通过stm32等单片机,配合网络模块来完成。当前我能想到的最简单的方法就是用stm32+esp8266来实现。

基于第一种方法,我觉得受模块性能影响比较大,受限于模块,没有开发感。于是考虑用第二种方法。这个方法里,有人用arduino来完成,这要基于c进行编程。另外,就是考虑用micropython,这样直接可以用python来实现。这里依然使用tpyboard v202。

0×02模拟实现家庭智能家居控制平台

因为没有想好要做一个多么复杂的实验,只是想能模拟一下效果,所有在整个的模块过程中,我选用了tpyboard v202开发板做主控制板,用一个发光二极管来模拟一个台灯(现实中,这里其实可以用一个继电器来控制其它设备的通断电),用一个直流小电机加迷你风扇叶表示模拟电风扇。整个实现还用到了一个三极管(S9014,NPN)来控制直流电机。

0×03硬件的搭建与连接

1、发光二极管的使用

利用单片机快速实现家庭智能控制平台

发光二极管使用比较简单,直接看它的两条“腿”,长的那个是正极,反之是负极。

2、直流小电机的使用方法

利用单片机快速实现家庭智能控制平台

上图直流小电机中,红色框内的两个接线端A和B,无论那个接正极或负极都可以,只不过转动的方向不一样而已。本次我是用B端接入正极,正好是顺时针转动。

3、三极管S9014(NPN)的使用方法

利用单片机快速实现家庭智能控制平台

本次我们使用S9014的放大和开关功能,集电极接入v202的3.3V引脚,发射极接入电机某一端,通过给基极高低电平来控制发射极和集电极之间是否导通,从而控制直流电机转动或停止。

4、接线方法

利用单片机快速实现家庭智能控制平台

我的实物连接图

利用单片机快速实现家庭智能控制平台

0×04利用micropython实现web服务器

首先,编辑一个main.py文件。v202 开机自启动main.py 文件

try:
import usocket as socket
except:
import socket
import network
from machine import UART
from machine import Pin

led_flag=Pin(2, Pin.OUT)#esp8266模块上的小灯 高电平:灭 低电平:亮
led = Pin(4, Pin.OUT)#发光二极管的控制引脚
motor = Pin(5, Pin.OUT)#直流电机的控制引脚
#初始化
led.low()
motor.low()
led_flag.high()
def do_connect(ssid,pwd):
sta_if = network.WLAN(network.STA_IF)#STA 模式
sta_if.active(False)
if not sta_if.isconnected():#判断是否连接
sta_if.active(True)
sta_if.connect(ssid,pwd)#ssid:WIFI名称 pwd:WIFI 密码
while not sta_if.isconnected():
pass
if sta_if.isconnected():
return sta_if.ifconfig()[0]
def main(ip_,dev_data,login_data,name,pwd):

s = socket.socket()
ai = socket.getaddrinfo(ip_, 80)
addr = ai[0][-1]
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
led_flag.low()
#s_data=login_data
while True:
res = s.accept()
client_s = res[0]
client_addr = res[1]
led_flag.high()
req =client_s.readline()
while True:
h = client_s.readline()
if h == b"" or h == b"\r\n":
break
#print(h)
req+=(h.decode('utf-8').lower())
print("Request:")
req=req.decode('utf-8').lower().split('\r\n')
#http header 解析
req_data=req[0].lstrip().rstrip().replace(' ','')
print(req_data)
if req_data.find('favicon.ico')>-1:
client_s.close()
continue
else:
if len(req_data) #说明是第一次访问,输入login.html
s_data=login_data
else:
req_data=req_data.replace('get/?','').replace('http/1.1','')
_name=req_data.find('name')
_pwd=req_data.find('pwd')
if _name>-1 and _pwd>-1:
#判断是否是用户登录
if req_data.find(name)>-1 and req_data.find(pwd)>-1:
s_data=dev_data
print('Login Success!')
else:
f=open('fail.html','r')
s_data=f.read()
f.close()
print('Login Fail!')
else:
#判断是否是控制LED
_index=req_data.find('led=')
if _index>-1:
s_data=dev_data
led_val=req_data[_index+4:_index+6].lstrip().rstrip()
print('led:',led_val)
if led_val=='on':
led.value(1)
else:
led.value(0)
#判断是否是控制电机
_index=req_data.find('motor=')
if _index>-1:
s_data=dev_data
motor_val=req_data[_index+6:_index+8].lstrip().rstrip()
print('motor_val:',motor_val)
if motor_val=='on':
motor.value(1)
else:
motor.value(0)
print('-----------')
client_s.send(s_data)
client_s.close()
led_flag.low()

f=open('device.html','r')
dev_html=f.read()
f.close()
f=open('login.html','r')
login_html=f.read()
f.close()
f=open('info.txt','r')
info=f.read()
f.close()
name=info.split(',')[0].lstrip().rstrip()
pwd=info.split(',')[1].lstrip().rstrip()
print('name:',name)
print('pwd:',pwd)
myip_=do_connect('essid','pwd')#家中网络的WIFI名称和密码
print(myip_)
main(myip_,dev_html,login_html,name,pwd)

login.html 登录页面

<html>
<head>
<title>智能家庭网络</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
h2
{
margin-top:4%;
margin-bottom:40px;
}
</style>
</head>
<body>
<center>
<h2>欢迎登录智能家庭网络平台</h2>
<form action="/" method="get" accept-charset="utf-8">
<p>用户名: <input type="text" name="name" /></p>
<p>密 码: <input type="password" name="pwd" /></p>
<input type="Submit" value="登录" />

</form>
</center>
</body>
</html>

device.html控制页面

<html>
<head>
<title>智能家庭网络平台</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
h2
{
margin-top:4%;
margin-bottom:40px;
}
</style>
</head>
<body>
<center>
<h2>欢迎使用智能家庭网络-控制平台</h2>
<form action="/" method="get" accept-charset="utf-8">
<p>灯光: <input type="Submit" value="ON" name="led" /> <input type="Submit" value="OFF" name="led" /></p>
<p>风扇: <input type="Submit" value="ON" name="motor" /> <input type="Submit" value="OFF" name="motor" /></p>

</form>
</center>
</body>
</html>

fail.html登录错误页面(就是把login.html 稍做了一下改动)
<html>
<head>
<title>智能家庭网络</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
h2
{
margin-top:4%;
margin-bottom:40px;
}
</style>
</head>
<body>
<center>
<h2>欢迎登录智能家庭网络平台</h2>
<form action="/" method="get" accept-charset="utf-8">
<p style="color:red">用户名或密码错误!</p>
<p>用户名: <input type="text" name="name" /></p>
<p>密 码: <input type="password" name="pwd" /></p>
<input type="Submit" value="登录" />

</form>
</center>
</body>
</html>

info.txt 这里是用文件存放的用户名和密码(英文逗号分隔),前面是用户名,后面是密码。

这里的用户名和密码是用来登录我们智能家居控制平台的。

admin,123456

0×05程序下载测试

使用MicroPython File Uploader 工具,将源代码下载到v202中。工具下载地址:http://tpyboard.com/download/tool/170.html

1、 使用usb数据线将v202接入到电脑,打开设备管理器,查看加载的端口。我的是COM44

※如果驱动安装失败,可以下载CH340的驱动,手动安装。CH340驱动下载地址:http://tpyboard.com/download/drive/163.html

利用单片机快速实现家庭智能控制平台

2、 打开MicroPython File Uploader 选择端口,点击[Open]。
利用单片机快速实现家庭智能控制平台

3、 取消[Autorun]的打钩,点击红框的文件夹图标,选择源码,点击[Send]等待发送成功。

利用单片机快速实现家庭智能控制平台

4、将上面的源码文件都下载到v202中,下载完毕后,点击[Run/Reset]就会开始执行代码

利用单片机快速实现家庭智能控制平台

5、开始运行后,红色框内打印的是我们存放在info.txt里的用户名和密码,这个可以自定义。

6、下面桃红色框内打印的是我们v202从路由器那里获取到的IP地址,只要打印了IP地址,说明就成功接入网络了。我的v202获取的IP地址是192.168.1.192。

7、到此,我们的web服务器就搭建完成了。

0×06 智能家庭网络平台的使用

1、在家庭局域网内,我们可以选用pc或者手机,通过浏览器,打开192.168.1.192 就可以看到登录界面。

2、默认用户名 admin 密码123456 ,大家可以通过修改info.txt 文件来进行修改。

(1)输入错误的用户名和密码会进入错误界面

利用单片机快速实现家庭智能控制平台

(2) 输入正确的,进入控制平台

利用单片机快速实现家庭智能控制平台

3、接下来,我们就可以通过网页开控制灯光和小风扇了,看我的实验效果图。

利用单片机快速实现家庭智能控制平台

利用单片机快速实现家庭智能控制平台

这里,我只是做了一个实例,受时间限制,没有再做更深入的开发。大家可以自己结合自己的创意再深入去做。如果能够通过路由器给tpyboard v202设一个外网Ip,这样就可以从外网进行访问,从而完成外网对家内设备的控制。

转自: 小五义的博客

围观 7
15

三极管在数字电路里的开关特性,最常见的应用有 2 个:一个是控制应用,一个是驱动应用。

所谓的控制就是如图 3-7 里边介绍的,我们可以通过单片机控制三极管的基极来间接控制后边的小灯的亮灭,用法大家基本熟悉了。还有一个控制就是进行不同电压之间的转换控制,比如我们的单片机是 5V 系统,它现在要跟一个 12V 的系统对接,如果 IO 直接接 12V电压就会烧坏单片机,所以我们加一个三极管,三极管的工作电压高于单片机的 IO 口电压,用 5V 的 IO 口来控制 12V 的电路,如图 3-8 所示。

三极管在单片机中的应用图解
图 3-8 三极管实现电压转换

图 3-8 中,当 IO 口输出高电平 5V 时,三极管导通,OUT 输出低电平 0V,当 IO 口输出低电平时,三极管截止,OUT 则由于上拉电阻 R2 的作用而输出 12V 的高电平,这样就实现了低电压控制高电压的工作原理。

所谓的驱动,主要是指电流输出能力。我们再来看如图 3-9 中两个电路之间的对比。

三极管在单片机中的应用图解
图 3-9 LED 小灯控制方式对比

图 3-9 中上边的 LED 灯,和我们第二课讲过的 LED 灯是一样的,当 IO 口是高电平时,小灯熄灭,当 IO 口是低电平时,小灯点亮。那么下边的电路呢,按照这种推理,IO 口是高电平的时候,应该有电流流过并且点亮小灯,但实际上却并非这么简单。

单片机主要是个控制器件,具备四两拨千斤的特点。就如同杠杆必须有一个支点一样,想要撑起整个地球必须有力量承受的支点。单片机的 IO 口可以输出一个高电平,但是他的输出电流却很有限,普通 IO 口输出高电平的时候,大概只有几十到几百 uA 的电流,达不到1mA,也就点不亮这个 LED 小灯或者是亮度很低,这个时候如果我们想用高电平点亮 LED,就可以用上三极管来处理了,我们板上的这种三极管型号,可以通过 500mA 的电流,有的三极管通过的电流还更大一些,如图 3-10 所示。

三极管在单片机中的应用图解
图 3-10 三极管驱动 LED 小灯

图 3-10 中,当 IO 口是高电平,三极管导通,因为三极管的电流放大作用,c 极电流就可以达到 mA 以上了,就可以成功点亮 LED 小灯。

虽然我们用了 IO 口的低电平可以直接点亮 LED,但是单片机的 IO 口作为低电平,输入电流就可以很大吗?这个我想大家都能猜出来,当然不可以。单片机的 IO 口电流承受能力,不同型号不完全一样,就 STC89C52 来说,官方手册的 81 页有对电气特性的介绍,整个单片机的工作电流,不要超过 50mA,单个 IO 口总电流不要超过 6mA。即使一些增强型 51 的IO 口承受电流大一点,可以到 25mA,但是还要受到总电流 50mA 的限制。那我们来看电路图的 8 个 LED 小灯这部分电路,如图 3-11 所示。

三极管在单片机中的应用图解
图 3-11 LED 电路图(一)

这里我们要学会看电路图的一个知识点,电路图右侧所有的 LED 下侧的线最终都连到一根黑色的粗线上去了,大家注意,这个地方不是实际的完全连到一起,而是一种总线的画法,画了这种线以后,表示这是个总线结构。而所有的名字一样的节点是一一对应的连接到一起,其他名字不一样的,是不连在一起的。比如左侧的 DB0 和右侧的最右边的 LED2 小灯下边的DB0 是连在一起的,而和 DB1 等其他线不是连在一起的。

那么我们把图 3-11 中现在需要讲解的这部分单独摘出来看,如图 3-12 所示。

三极管在单片机中的应用图解
图 3-12 LED 电路图(二)

现在我们通过 3-12 的电路图来计算一下,5V 的电压减去 LED 本身的压降,减掉三极管e 和 c 之间的压降,限流电阻用的是 330 欧,那么每条支路的电流大概是 8mA,那么 8 路 LED如果全部同时点亮的话电流总和就是 64mA。这样如果直接接到单片机的 IO 口,那单片机肯定是承受不了的,即使短时间可以承受,长时间工作就会不稳定,甚至导致单片机烧毁。

有的同学会提出来可以加大限流电阻的方式来降低这个电流。比如改到 1K,那么电流不到 3mA,8 路总的电流就是 20mA 左右。首先,降低电流会导致 LED 小灯亮度变暗,小灯的亮度可能关系还不大,但因为我们同样的电路接了数码管,后边我们要讲数码管还要动态显示,如果数码管亮度不够的话,那视觉效果就会很差,所以降低电流的方法并不可取。其次,对于单片机来说,他主要是起到控制作用,电流输入和输出的能力相对较弱,P0 的 8 个口总电流也有一定限制,所以如果接一两个 LED 小灯观察,可以勉强直接用单片机的 IO 口来接,但是接多个小灯,从实际工程的角度去考虑,就不推荐直接接 IO 口了。那么我们如果要用单片机控制多个 LED 小灯该怎么办呢?

除了三极管之外,其实还有一些驱动 IC,这些驱动 IC 可以作为单片机的缓冲器,仅仅是电流驱动缓冲,不起到任何逻辑控制的效果,比如我们板子上用的 74HC245 这个芯片,这个芯片在逻辑上起不到什么别的作用,就是当做电流缓冲器的,我们通过查看其数据手册,74HC245 稳定工作在 70mA 电流是没有问题的,比单片机的 8 个 IO 口大多了,所以我们可以把他接在小灯和 IO 口之间做缓冲,如图 3-13 所示。

三极管在单片机中的应用图解
图 3-13 74HC245 功能图

从图 3-13 我们来分析,其中 VCC 和 GND 就不用多说了,细心的同学会发现这里有个0.1uF 的去耦电容哦。

74HC245 是个双向缓冲器,1 引脚 DIR 是方向引脚,当这个引脚接高电平的时候,右侧所有的 B 编号的电压都等于左侧 A 编号对应的电压。比如 A1 是高电平,那么 B1 就是高电平,A2 是低电平,B2 就是低电平等等。如果 DIR 引脚接低电平,得到的效果是左侧 A 编号的电压都会等于右侧 B 编号对应的电压。因为我们这个地方控制端是左侧接的是 P0 口,我们要求 B 等于 A 的状态,所以 1 脚我们直接接的 5V 电源,即高电平。图 3-13 中还有一排电阻 R10 到 R17 是上拉电阻,这个电阻的用法我们在后边介绍。

还有最后一个使能引脚 19 脚 OE,叫做输出使能,这个引脚上边有一横,表明是低电平有效,当接了低电平后,74HC245 就会按照刚才上边说的起到双向缓冲器的作用,如果 OE接了高电平,那么无论 DIR 怎么接,A 和 B 的引脚是没有关系的,也就是 74HC245 功能不能实现出来。

从下面的图 3-14 可以看出来,单片机的 P0 口和 74HC245 的 A 端是直接接起来的。这个地方,有个别同学有个疑问,就是我们明明在电源 VCC 那地方加了一个三极管驱动了,为何还要再加 245 驱动芯片呢。这里大家要理解一个道理,电路上从正极经过器件到地,首先必须有电流才能正常工作,电路中任何一个位置断开,都不会有电流,器件也就不会参与工作了。其次,和水流一个道理,从电源正极到负极的电流水管的粗细都要满足要求,任何一个位置的管子过细,都会出现瓶颈效应,电流在整个通路中细管处会受到限制而降低,所以在电路通路的每个位置上,都要保证通道足够畅通,这个 74HC245 的作用就是消除单片机IO 这一环节的瓶颈。

三极管在单片机中的应用图解
图 3-14 单片机与 74HC245 的连接

来源: 畅学单片机

围观 6
14

单片机仿真器是指以调试单片机软件为目的而专门设计制作的一套专用的硬件装置。目前已经得到了广泛的运用,那么单片机仿真器有什么作用?

单片机仿真器发展

最早的单片机仿真器是一套独立装置,具有专用的键盘和显示器,用于输入程序并显示运行结果;随着PC机的普及,新一代的仿真器大多数都是利用PC机作为标准的输入输出装置,而仿真器本身成为微机和目标系统之间的接口而已,仿真方式也从最初的机器码发展到汇编语言、C语言仿真,仿真环境也与PC机上的高级语言编程与调试环境非常类似了。

仿真机一般具有一个仿真头,用于取代目标系统中的单片机,也就是用这个插头模仿单片机,这也是单片机仿真器名称的由来。

目前,随着单片机的小型化,贴片化和具有ISP,IAP等功能的单片机的广泛应用,传统单片机仿真器的应用范围也有所缩小。而软件单片机仿真器(即单片机仿真程序)的应用逐渐广泛,单片机仿真程序即在个人计算机上运行的特殊程序,可在一定程度上模拟单片机运行的硬件环境,并在该环境下运行单片机目标程序,并可对目标程序进行调试、断点、观察变量等操作,可大大提升单片机系统的调试效率。纯软件单片机仿真器往往与硬件设计程序集成在一起发布,使得开发者可以对单片机硬件与软件进行同步开发。

仿真器使用方法

1. 将仿真器插入需仿真的用户板的CPU插座中,仿真器由用户板供电;
2. 将仿真器的串行电缆和PC机接好,打开用户板电源;
3. 通过Keil C 的IDE开发仿真环境UV2 下载用户程序进行仿真、调试。
4. 仿真器硬件说明:
 a.使用用户板的晶振
仿真器晶振旁有两组跳线用来切换内部晶振和用户板晶振,当两个短路块位于仿真器晶振一侧时,默认使用仿真板上的晶振(11.0592MHz), 当两个短路块位于电容一侧时,使用用户板的晶振。
 b.为便于调试带看门狗的用户板,仿真器的复位端未与用户板复位端相连;故仿真器的复位按 钮只复位仿真器,不复位用户板;若要复位用户板,请使用用户板复位按钮。

单片机仿真器优势

1、配合集成开发环境使用仿真器可以对单片机程序进行单步跟踪调试,也可以使用断点、全速等调试手段,并可观察各种变量、RAM及寄存器的实时数据,跟踪程序的执行情况。同时还可以对硬件电路进行实时的调试。

2、利用单片机仿真器可以迅速找到并排除程序中的逻辑错误,大大缩短单片机开发的周期。在现场只利用烧录器反复烧写单片机,而通过肉眼观察结果进行开发的方法大大增加了调试的难度,延长了整个开发周期,且不易发现程序中隐含的错误。

3、对初学者来说,降低了调试与开发难度。非常有利于初学者学习。

单片机仿真器作用

由于单片机的应用场合问题,其不具备标准的输入输出装置,受存储空间限制,也难以容纳用于调试程序的专用软件,因此要对单片机软件进行调试,就必须使用单片机仿真器。单片机仿真器具有基本的输入输出装置,具备支持程序调试的软件,使得单片机开发人员可以通过单片机仿真器输入和修改程序,观察程序运行结果与中间值,同时对与单片机配套的硬件进行检测与观察,可以大大提高单片机的编程效率和效果。

单片机仿真器原理

单片机在体系结构上与PC机是完全相同的,也包括中央处理器,输入输出接口,存储器等基本单元,因而与PC机等设备的软件结构也是类似的。因而单片机在软件开发的过程中也需要对软件进行调试,观察其中间结果,排除软件中存在的问题。但是由于单片机的应用场合问题,其不具备标准的输入输出装置,受存储空间限制,也难以容纳用于调试程序的专用软件,因此要对单片机软件进行调试,就必须使用单片机仿真器。单片机仿真器具有基本的输入输出装置,具备支持程序调试的软件,使得单片机开发人员可以通过单片机仿真器输入和修改程序,观察程序运行结果与中间值,同时对与单片机配套的硬件进行检测与观察,可以大大提高单片机的编程效率和效果。

总结

目前,随着单片机的小型化,贴片化和具有ISP,IAP等功能的单片机的广泛应用,传统单片机仿真器的应用范围也有所缩小。而软件单片机仿真器(即单片机仿真程序)的应用逐渐广泛,单片机仿真程序即在个人计算机上运行的特殊程序,可在一定程度上模拟单片机运行的硬件环境,并在该环境下运行单片机目标程序,并可对目标程序进行调试、断点、观察变量等操作,可大大提升单片机系统的调试效率。纯软件单片机仿真器往往与硬件设计程序集成在一起发布,使得开发者可以对单片机硬件与软件进行同步开发。

来源: 捷配电子

围观 3
22

在单片机系统里,按键是常见的输入设备,在本文江介绍几种按键硬件、软件设计方面的技巧。一般的在按键的设计上,一般有四种方案:
一是GPIO口直接检测单个按键,如图1.1所示;
二是按键较多则使用矩阵键盘,如图1.2所示;
三是将按键接到外部中断引脚上,利用按键按下产生的边沿信号进行按键检测,如图1.3所示;
四是利用单片机的ADC,在不同的按键按下后,能够使得ADC接口上的电压不同,根据电压的不同,则可以识别按键,如图1.4所示。

单片机按键设计的四个方案详解
图1.1方案一

单片机按键设计的四个方案详解
图1.2方案二

单片机按键设计的四个方案详解
图1.3方案三

单片机按键设计的四个方案详解
图1.4方案四

在以上四种设计上,各有优点和不足。第一种是最简单和最基础的,对于单片机初学者很容易理解和使用,但是缺点是,需要在主循环中不断检测按键是否按下,并且需要做消抖处理。若主循环中某个函数任务占用时间较长,则按键会有不同程度的“失灵”。第二种,优点是能够在有限的GPIO情况下,扩展尽可能多的按键。但缺点同上,需要不停检测按键是否按下。第三种方式是效率最高,不需要循环检测按键是否按下,但是缺点是,需要单片机有足够的外部中断接口以供使用;第四种的优点是,只需要单片机的一个ADC接口,一根线,就能对多个按键进行识别,缺点是按键一旦内部接触不良,则可能按键串位,且按键产生的抖动,会造成一定的识别错误。

在以上的三种常见按键设计的基础上,现在分享我学习和工作中总结的按键方案。

改进一:在原方案一的基础上,加上与门电路,使得任何一个按键按下,都能产生中断,然后在中断里面识别是哪个按键被按下。因此不需要循环扫描,大大提高了效率。方案如图1.5所示。只需要每个按键对应地增加一个二极管,利用二极管的线与特性,可以实现按下任何按键,都能产生中断信号,但是按键之间互不影响。二极管选用普通整流二极管即可,本人亲测可行。

单片机按键设计的四个方案详解
图1.5 改进一

改进二:在原有的ADC按键的基础上,也可用增加二极管的方式,实现按键中断,并在中断服务程序里进行AD转换,从而识别按键。电路如图1.6所示。

单片机按键设计的四个方案详解
图1.6 改进二

改进三:因为按键不可避免的有抖动,因此按键消抖可以通过硬件消痘和软件消抖。现在分享一个十分简单且有效的硬件消痘方法:给按键并联一个104左右的电容。软件上基本不用处理即可避免抖动。
改进四:在按键扫描检测的方案下,如果主循环中有某个函数占用时间较长,则按键会发生或长或短的“失灵”,现分享我的一个解决方案。将按键扫描放到定时器中断里面,这样就可周期性地检测按键按下情况,不受主循环的影响。并且,能解析出按键的不同状态,即按下、按住、弹起、为按下这四种状态,用以实现更丰富的功能。但需注意两点,一是定时器的定时时间,不可过长也不可过短,过长容易检测不到按下,过短会占用大量时间资源。二是中断服务程序需简单明了,只做检测用,通过全局变量传递,在主循环内完成按键响应,中断服务函数内尽量不要占用太多时间。

来源: 21ic.com

围观 14
32

前 言

嵌入式系统是指以应用为中心,以计算机技术为基础,软、硬件可裁剪,适应应用系统对功能、体积、成本、可靠性、功耗严格要求的专用计算机系统。嵌入式系统是面向应用的,系统的硬件选型和软件开发模式都必须根据具体的应用确定。

永磁无刷直流电动机是电机控制研究领域的热点之一,这与其自身固有的技术优势密切相关:以电子换相取代了有刷直流电动机的机械换相。从根本上革除了普通有刷直流电动机由于电刷换相带来的火花、噪音、高故障率等一系列问题,同时又使系统的性能能够与普通有刷直流电动机相媲美,因此得到了广泛的应用。永磁无刷直流电动机的电子换相离不开电机的转子位置信号,传统的方法是采用霍尔器件或其他位置传感器检测位置信号,这使得系统的维护和制造都不方便,并且由于传感器的工作特性不稳定,给系统的安全运行带来了一些隐患。因此,无位置传感器方案引起了人们的极大兴趣。

本文结合无位置传感器永磁无刷直流电动机控制系统的开发,以 MICroChip 公司的 PIC18F452 单片机 为主控器件,并采用嵌入式实时操作系统μ C/OS-II 作为软件开发平台,详细讨论了嵌入式系统的开发模式与流程。

2. 系统硬件平台设计

嵌入式系统设计的第一步是结合具体的应用,综合考虑系统对成本、性能、可扩展性、开发周期等各个方面的要求,确定系统的主控器件,并以之为核心搭建系统硬件平台。

基于PIC18F系列单片机的嵌入式系统设计

无位置传感器永磁无刷直流电动机控制系统的关键问题是位置检测。目前已经有了很多位置检测方案,其中,反电势法由于简单实用而得以广泛采用。反电势法的原理是:基于电机的三相端电压,通过硬件检测电路或软件算法得到三相反电势过零信号,然后用软件移相得到换相时刻,并在换相时刻按换相逻辑完成换相,触发逆变桥以合适的导通时序工作,从而保证了电机的正常运行。

反电势法的永磁无刷直流电动机无位置传感器控制对系统硬件提出了更高的要求:

① 三个外部中断输入引脚,便于捕捉三相反电势过零信号;

② 至少一个 PWM模块,实现电机的斩波调速;

③ 丰富的定时器资源,完成软件移相、测速等功能;

④ 多通道的 AD转换模块,能够采样速度给定及主电路的电流、电压信号;

⑤ 硬件乘法器,保证速度、电流调节器的快速性;

⑥ 足够的程序和数据存储器,便于系统扩展;

⑦ 高速的系统工作频率,保证系统的强实时性;

⑧ 丰富的通信模块,便于系统与其他嵌入式系统的互连。

对于无位置传感器永磁无刷直流电动机控制系统的设计,有很多专用芯片可供选用,但为了进一步提高系统性能,增强设计的灵活性,多采用 DSP 或专用 单片机 等智能器件。但是,这样在提高系统性能的同时却增加了系统开发成本。为了设计一个高性能、低成本的开发平台,针对应用对系统硬件的要求,考虑到 PIC18F452 单片机的高性价比,选用其作为主控器件。

PIC18F452 是 Microchip 公司推出的一款增强型 8 位 单片机 ,采用精简指令集( RISC )的设计,有两级流水线,最高运行频率可达到 10MIPS ,能够满足系统对实时性的要求;指令总线 16 位宽,数据总线 8 位宽;单片机内部有 32K 字节的 FLASH 程序存储器, 1.5K 字节的数据存储器和 256 字节的 EEPROM ,便于系统的扩展;自带 8 × 8 硬件乘法器;中断资源丰富,提供 18 个中断源,两个中断优先级,并且中断优先级可配置。 PIC18F452 单片机配备了丰富的外围模块,极大地简化了单片机外围电路的设计。同时, Microchip 公司为 PIC18F 系列单片机提供了功能强大的指令集,共 77 条指令,绝大部分指令为单字( 2 个字节)存储,单周期执行,应用代码的存储压缩率高,指令执行效率高。

以 PIC18F452 为主控器件构成的系统硬件框图如图 1所示。

值得说明的是:

① 系统中的换相逻辑由可编程逻辑器件完成,主要是为了提高系统的可靠性,从功能上讲,完全可由 单片机 实现;

② 电机的速度检测,可根据位置信号利用软件计算得来,故省略了速度传感器;

③ 模拟输入为电机的速度给定信号。

3. 嵌入式系统软件开发模式

对于简单的应用系统,系统的软件开发模式通常如图 2 所示,称为前后台系统(也叫无限循环系统)。

基于PIC18F系列单片机的嵌入式系统设计

前后台系统中,应用程序就是一个无限循环。循环中调用函数完成相应的操作,这些操作称为后台任务;中断服务程序处理异步事件,这部分称为前台行为。因为中断服务程序提供的信息一直要等到后台程序运行到该处理这个信息时才得到处理,所以最坏情况下的任务响应时间等于整个循环的执行时间。因为后台循环的执行周期不是常数,所以基于前后台模式的应用软件开发,虽然设计过程简单,但系统的实时性得不到保障。

基于PIC18F系列单片机的嵌入式系统设计

为了提高系统的实时性,可以采用基于嵌入式实时操作系统( RTOS )的软件开发模式。 RTOS 分为两类:非可剥夺型内核和可剥夺型内核,一般商用的都是可剥夺型内核,所以本文只讨论此类 RTOS ,其内核结构如图 3 所示。

RTOS 将整个应用细分为多个任务,每个任务完成特定的功能,并被赋予一定的优先级,拥有自己的任务控制块和栈空间。一般地,每个任务在程序结构上都是一个无限循环,它有多个状态——休眠态、就绪态、运行态、挂起态和中断态等。系统内核总是让就绪态的高优先级任务先运行,中断服务程序可抢占 CPU ,中断服务程序完成时,系统内核让此时就绪态中优先级最高的任务运行(不一定是被中断的任务)。可见,基于 RTOS 的软件开发模式使系统的任务响应时间得到了最优化。更重要的是,这种开发模式将以往面向功能的应用开发转化为面相任务的应用开发,简化了系统设计的逻辑结构;同时,由于有了 RTOS ,屏蔽了应用软件对底层硬件的可见性,将以往软件系统的两层结构转化为三层结构(如图 4 所示),极大地方便了系统的软件扩展与硬件升级。

基于PIC18F系列单片机的嵌入式系统设计

对于 PIC18F 系列 单片机 ,目前常用的嵌入式实时操作系统有:μ C/OS-II 、 Salvo 、 CMX 、 PIC18OS 等。它们都是可剥夺型的实时内核,详细的比较如表 1 所示。

结合本文的具体应用,综合考虑系统硬件资源及上述几种实时操作系统的特点,最终选用基于操作系统的软件开发模式,并选择μ C/OS-II 作为系统软件平台。

4. 基于μ C/OS-II 的应用软件开发

μ C/OS-II 是一个可移植、可固化、可裁剪及可剥夺型的多任务实时内核,应用开发时首先必须完成其在特定硬件上的移植。μ C/OS-II 在编写的过程中就充分考虑到了可移植性,它的绝大部分代码都由 ANSI C 写成,与处理器相关的代码集中在 OS_CPU.H 、 OS_CPU_A.ASM 、 OS_CPU_C.C 这三个文件中,因此只要针对具体的硬件改写这些文件,就可以完成移植工作。

表 1 适用于 PIC18F 系列 单片机 的几种嵌入式实时操作系统

基于PIC18F系列单片机的嵌入式系统设计

来源: 广电电器

围观 16
39

1 引言

现在,人们对工作和生活环境不仅要求舒适健康、可靠便利,而且更加看重安全性,并利用安防系统来提高家庭抵御各种意外情况的能力。现在的安防系统可借助计算机技术、IC 卡技术、通信技术等来实现,CAN总线应用于安防系统对家居智能化发展起到了良好的促进作用。CAN总线是一种应用较为广泛的现场总线,它支持多主节点,有完善的错误处理机制,通信速率快,传送距离远,可挂接控制设备多。而把DTMF 技术应用于安防系统,不需要专门的布线,不占用无线电频率资源,没有电磁污染。文中设计了一种基于CAN 总线和DTMF技术的以AT89S52单片机为核心的新型智能家居安防系统,使原来小区安防系统的实时性和可靠性有了一个新层次的提高。本系统可以对整个家居的安全环境进行实时监控,监控的范围包括室内防盗、火灾报警、煤气泄露等一系列不安全因素。一旦有上述事故发生,该报警系统就会发出相应的报警信息,用语音播出警情类别,向远方用户和相关部门提供警情语音。

2 系统总体构成

系统框图如图1所示。单片机控制DTMF收发电路、数字语音电路、摘挂机控制电路。探测器能够快速、准确地监测到住宅的异常状况,经确认后及时通知控制器,再由单片机来控制电话接口电路,实现模拟摘机,自动拨打预先设置的电话号码进行语音报警并通知管理中心。当监测到对方回应后,自动恢复警戒状态。

基于单片机的家居安防系统设计

3 硬件设计

系统主控部分采用AT89S52单片机,无需扩展外部存储器。看门狗电路采用具有可编程的串行EEPROM - X25045。X25045依次存储了标志字段、话机号码、警情代号、系统设置等数据信息。数字语音电路采用数字语音芯片ISD1420。系统中ISD1420仅作为基本录放音电路,所以所有的地址线全部置为0, 因此放音的起始地址是0。语音信号由驻极话筒拾取,从M IC和M IC REF两端输入芯片内部的放大器放大,经过功放后的音频信号从SP+ 被用来与通话电路相连,以送出语音信号。

3. 1 振铃检测及模拟摘挂机单元

系统并于电话线两端,时刻处于监控状态,不会影响电话的正常工作。当系统接收到振铃信号时,会进行振铃检测。振铃信号经三个反相器后接入AT89S52的P3. 4口。若5次振铃后无人接听,则系统进入自动摘机状态。单片机P1. 2 引脚输出高电平,三极管V501导通则继电器K1动作,将负载电阻接入电路实现模拟摘机。此后电话线上就会出现大于10mA的电流,交换中心检测到这一电流后就不再输出振铃信号而是转为接通电话。如果振铃信号没有达到预设值就消失,则单片机的计数值清零,控制器不动作。

3. 2 DTMF收发单元

DTMF收发电路采用DTMF信号编/解码芯片MT8880[ 5] 芯片,单片机通过DTMF收发电路拨出电话号码进行电话报警,DTMF收发电路如图2所示。

基于单片机的家居安防系统设计
图2 DTMF收发电路

MT8880提供了与微处理器相连的接口,以对其发送、接收和工作模式进行控制。它的接收部分采用单端输入,由R27、R28和C16 组成,其输入电压增益为1, 通过改变R28可调节输入信号的增益。它的发送部分由R29、C17、C18和XTAL2构成。它的控制部分由R30 和C19 构成。IRQ /CP与单片机P3. 5 脚相连。当MT8880接收到有效的双音多频信号时,单片机进行中断处理。MT8880的IN - 端和通话电路TEA 1062 的QR 端相连,MT8880的TONE端和TEA1062的DTMF端相连。

3. 3 通话单元

通话电路使用电话机专用通话集成电路TEA1062。送话时,语音信号(来自ISD1420)通过M IC + 引脚输入,DTMF信号(来自MT8880)通过DTMF引脚输入,经过TEA1062 放大后从LN 引脚一起送到电话外线上。受话时,信号通过消侧音网络,从IR 引脚输入,放大后从引脚QR 输出,分两路:一路送到ISD1420的ANA IN端供语音录制用,另一路送到MT8880的IN-端提取DTMF信号。

3. 4 CAN 总线数据传输单元

CAN 总线数据传输单元由两部分构成,一部分是CAN控制器,实现对总线数据的交互与控制,另一部分是CAN 数据收发器,实现数据的网络传输。

单片机AT89S52通过控制CAN控制器来实现对总线的访问,同时还负责功能单元的测量和控制, CAN 总线接口电路如图3所示。

基于单片机的家居安防系统设计
图3 CAN总线接口电路

AT89S52通过中断方式访问CAN控制器SJA1000,为了增强CAN总线节点的抗干扰能力,SJA1000通过高速光耦6N137与CAN总线驱动器PCA82C50相连。PCA82C50的CANH和CANL引脚各自接了一个5的电阻与CAN总线相连,可以起到限流作用,以免PCA82C50受到过电流冲击。

4 软件设计

系统的软件采用模块化设计,主要包括主程序模块、CAN通信模块、振铃检测模块、语音报警模块、DTMF收发模块等,这里主要介绍主程序和CAN通信模块设计。

4. 1 主程序设计

主程序主要完成各功能模块的调用,检测系统输入,然后根据系统状态进行判断处理。程序进行主循环之前还要进行必要的初始化,如MT8880、ISD1420、SJA1000相关标志位等。主程序流程如图4所示。

基于单片机的家居安防系统设计
图4 主程序流程图

4. 2 CAN通信模块设计

CAN通信模块包括控制器初始化、数据接收和发送子程序。SJA1000有复位模式和工作模式两种状态,两种状态下寄存器配置不同。当参数设置完后,CPU发出命令,SJA1000处于工作状态,进行正常通信。如果通信出错,CPU会使SJA1000回到复位模式。接收模块负责节点报文的接收及相关处理。接收过程中CPU会读数据,根据命令字判断数据帧的类型进行不同处理。发送模块负责报文的发送,SJA1000发送数据前,要判断是否满足发送条件,如果满足,则把报文帧信息、标识符和要发送的数据写入缓冲区,即可发送。JA 1000的收发流程图如图5所示。

基于单片机的家居安防系统设计
图5 JA 1000的收发流程图

5 结束语

本系统以AT89S52单片机为核心,不用对电话网进行任何改造,实现对家居设防点进行自动检测和语音报警。设计中用CAN总线结构组成安防系统,有较好的灵活性和扩展性,同时利用CAN 总线引入实时的数据处理,提高了系统的可靠性。可对楼宇可视对讲、智能小区管理、门禁管理等有较大的应用推广价值。

来源: ofweek

围观 10
37

1 引言

当今社会,随着经济的发展,人们生活水平的提高,肥胖的人越来越多,也就导致了越来越多的疾病产生,因此,人们越来越关注健康问题,而锻炼身体是让自己健康的最有效的方法。因此计步器应运而生,就成了时下流行的趋势。步行时,通过伸缩肌肉,血液在流动时的抵抗值下降,血压下降且稳定。经常步行的人很少患高血压或低血压病。坚持步行能减少血管内附着的脂肪性物质,使体重减轻,也逐渐减少心脏的负荷。而基于单片机为核心控制的计步器有着精确,可靠,稳定,方便等优点,已被大多数人所接受。通过计步器人们可以知道自己跑了多少步,实时掌握自己的锻炼情况。

2 总体设计方案

计步器由振荡电路、复位电路、显示电路以及按键电路几个部分组成,由电池进行供电。系统结构图如图1 所示。

基于单片机的智能计步器设计
图1 系统结构图

3 硬件的设计

3.1 振荡电路

AT89C51 单片机内设有一个由反向放大器所构成的振荡电路,振荡电路是单片机系统正常工作的保证,如果振荡器不起振,系统将会不能工作。假如振荡器运行不规律,系统执行程序的时候就会出现时间上的误差,这在通信中会体现的很明显,电路将无法通信。

它是由一个晶振和两个瓷片电容组成的。时钟电路中的两个电容用作补偿,使得晶振更容易起振,频率更加稳定。如图2 所示。

基于单片机的智能计步器设计
图2 振荡电路

3.2 复位电路

为确保微机系统中电路稳定可靠工作,复位电路是必不可少的一部分,复位电路的第一功能是上电复位。一般微机电路正常工作需要供电电源为5V±5%,即4.75~5.25V。由于微机电路是时序数字电路,它需要稳定的时钟信号,因此在电源上电时,只有当VCC 超过4.75V 低于5.25V 以及晶体振荡器稳定工作时,复位信号才被撤除,微机电路开始正常工作。系统的复位采用了上电复位的形式,上电过程中微控制器复位引脚保证10ms 以上的高电平就能可靠的将微控制器复位。如图3 所示。

基于单片机的智能计步器设计
图3 复位电路

3.3 显示电路

本次设计采用4 位LED 共阴极数码管显示屏做为系统的显示界面,如图4 所示。常用的LED 显示器为8 段或7 段(8 段比7 段多了一个小数点“dp”段)。每一个段对应一个发光二极管。这种显示器由共阳极和共阴极两种。如图4 所示。共阴极LED 显示器的发光二极管的阴极连接在一起,通常次共阴极接地。当某个发光二极管的阳极为高电平时,发光二极管点亮,相应的段被现实。为了使LED显示器显示不同的符号和数字,就要把不同段的发光二极管点亮,这样就要为LED 显示器提供代码,因为这些代码可使LED 相应的段发光,从而显示不同字型,因此该代码称之为段码(或称为字型代码)。7 段发光二极管在加上一个小数点,共计8 段。因此提供给LED 显示器的段码正好是1B。

基于单片机的智能计步器设计
图4 显示器连接电路

3.4 按键电路

本次设计是以按键的形式来代替人走步所产生的震动,每按键一次即表示人走动一步,其电路如图5 所示。

基于单片机的智能计步器设计
图5 按键电路

3.5 ADXL202 传感器电路

ADXL022 传感器模块电路如图6 所示。

基于单片机的智能计步器设计
图6 ADXL202传感器模块电路

4 系统软件

计步开始,内部程序准备就位。人走动一步,传感器检测到峰值,经四种电路,由显示器显示出来,再走一步,由累加器累加1,由此走几步依次加1,由显示器显示。单片机复位系统产生外部中断,显示器置零。系统流程图如图7 所示。

基于单片机的智能计步器设计
图7 系统流程图

5 软件仿真

系统中将按键电路中按键K1 与单片机的P4.4 进行连接,专用的按键电路产生振荡电路,将电信号通过电路转换给微控制器,微控制器将表征当前步数的数字量按照10 进制等处理后通过直观LED 显示。当按键按下一次的时候,显示器显示1,按几次则显示多少。计步器仿真效果图如图8 所示。

基于单片机的智能计步器设计
图8 仿真效果图

6 结束语

本文主要设计中包含了微控制器、显示部件、输入部件和实时时钟等部分。在整个设计系统中充分掌握各模块电路的工作原理,对硬件电路、软件程序进行设计,最后进行仿真。

转自: 至秦单片机

围观 6
49

一、五大内存分区:

内存分成5个区,它们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

1、栈区(stack):FIFO就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。

2、堆区(heap):就是那些由new分配的内存块,它们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

3、自由存储区:就是那些由malloc等分配的内存块,它和堆是十分相似的,不过它是用free来结束自己的生命。

4、全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

5、常量存储区:这是一块比较特殊的存储区,它们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

code/data/stack
内存主要分为代码段,数据段和堆栈。代码段放程序代码,属于只读内存。数据段存放全局变量,静态变量,常量等,堆里存放自己malloc或new出来的变量,其他变量就存放在栈里,堆栈之间空间是有浮动的。数据段的内存会到程序执行完才释放。调用函数先找到函数的入口地址,然后计算给函数的形参和临时变量在栈里分配空间,拷贝实参的副本传给形参,然后进行压栈操作,函数执行完再进行弹栈操作。字符常量一般放在数据段,而且相同的字符常量只会存一份。

二、C语言程序的存储区域

1、由C语言代码(文本文件)形成可执行程序(二进制文件),需要经过编译-汇编-连接三个阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,连接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。

2、C语言编写的程序经过编译-连接后,将形成一个统一文件,它由几个部分组成。在程序运行时又会产生其他几个部分,各个部分代表了不同的存储区域:

1)代码段(Code或Text)
代码段由程序中执行的机器代码组成。在C语言中,程序语句执行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。

2)只读数据段(RO data)
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。

3)已初始化读写数据段(RW data)
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。

4)未初始化数据段(BBS)
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。

5)堆(heap)
堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后会后内存。

6)栈(statck)
堆内存只在程序运行时出现,在函数内部使用的变量,函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。

单片机中C语言的程序与数据存储

3、代码段、只读数据段、读写数据段、未初始化数据段属于静态区域,而堆和栈属于动区域。代码段、只读数据段和读写数据段将在连接之后产生,未初始化数据段将在程序初始化的时候开辟,而对堆和栈将在程序饿运行中分配和释放。

4、C语言程序分为映像和运行时两种状态。在编译-连接后形成的映像中,将只包含代码段(Text)、只读数据段(R0 Data)和读写数据段(RW Data)。在程序运行之前,将动态生成未初始化数据段(BSS),在程序的运行时还将动态生成堆(Heap)区域和栈(Stack)区域。

注:
1、一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment)。如果不详细区分,统称为段。
2、C语言在编译连接后,将生成代码段(TEXT),只读数据段(RO Data)和读写数据段(RW Data)。在运行时,除了上述三个区域外,还包括未初始化数据段(BBS)区域和堆(heap)区域和栈(Stack)区域。

三、C语言程序的段

1、段的分类

每一个源程序生成的目标代码将包含源程序所需要表达的所有信息和功能。目标代码中各段生成情况如下:
1)代码段(Code)
代码段由程序中的各个函数产生,函数的每一个语句将最终经过编译和汇编生成二进制机器代码

2)只读数据段(RO Data)
只读数据段由程序中所使用的数据产生,该部分数据的特点在运行中不需要改变,因此编译器会将数据放入只读的部分中。C语言的一些语法将生成只读数据数据段。

2、只读数据段(RO Data)

只读数据段(RO Data)由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将数据放入只读的部分中。以下情况将生成只读数据段。

1)只读全局变量
定义全局变量const char a[100]=”abcdefg”将生成大小为100个字节的只读数据区,并使用字符串“abcdefg”初始化。如果定义为const char a[]=”abcdefg”,没有指定大小,将根据“abcdefgh”字串的长度,生成8个字节的只读数据段。

2)只读局部变量
例如:在函数内部定义的变量const char b[100]=”9876543210”;其初始化的过程和全局变量。

3)程序中使用的常量
例如:在程序中使用printf("information\n”),其中包含了字串常量,编译器会自动把常量“information \n”放入只读数据区。

注:在const char a[100]={“ABCDEFG”}中,定义了100个字节的数据区,但是只初始化了前面的8个字节(7个字符和表示结束符的‘\0’)。在这种用法中,实际后面的字节米有初始化,但是在程序中也不能写,实际上没有任何用处。因此,在只读数据段中,一般都需要做完全的的初始化。

3、读写数据段(RW Data)

读写数据段表示了在目标文件中一部分可以读也可以写的数据区,在某些场合它们又被称为已初始化数据段。这部分数据段和代码,与只读数据段一样都属于程序中的静态区域,但是具有科协的特点。

1)已初始化全局变量
例如:在函数外部,定义全局的变量char a[100]=”abcdefg”

2)已初始化局部静态变量
例如:在函数中定义static char b[100]=”9876543210”。函数中由static定义并且已经初始化的数据和数组将被编译为读写数据段。

说明:
读写数据区的特点是必须在程序中经过初始化,如果只有定义,没有初始值,则不会生成读写数据区,而会定义为未初始化数据区(BSS)。如果全局变量(函数外部定义的变量)加入static修饰符,写成static char a[100]的形式,这表示只能在文件内部使用,而不能被其他文件使用。

4、未初始化数据段(BSS)

未初始化数据段常被称之为BSS(英文名为Block start by symbol的缩写)。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。因此它只会在目标文件中被标识,而不会真正称为目标文件中的一个段,该段将会在运行时产生。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。

四、在C语言的程序中,对变量的使用还有以下注意

1、在函数体中定义的变量通常是在栈上,不需要在程序中进行管理,由编译器处理。

2、用malloc,calloc,realoc等分配分配内存的函数所分配的内存空间在堆上,程序必须保证在使用后使用后freee释放,否则会发生内存泄漏。

3、所有函数体外定义的是全局变量,加了static修饰符后的变量不管在函数内部或者外部存放在全局区(静态区)。

4、使用const定义的变量将放于程序的只读数据区。

说明:
在C语言中,可以定义static变量:在函数体内定义的static变量只能在该函数体内有效;在所有函数体外定义的static变量,也只能在该文件中有效,不能在其他源文件中使用;对于没有使用 static修饰的全局变量,可以在其他的源文件中使用。这些区别是编译的概念,即如果不按要求使用变量,编译器会报错。使用static 和没使用static修饰的全局变量最终都将放置在程序的全局去(静态去)。

五、程序中段的使用

C语言中的全局区(静态区),实际上对应着下述几个段:
只读数据段:RO Data
读写数据段:RW Data
未初始化数据段:BSS Data
一般来说,直接定义的全局变量在未初始化数据区,如果该变量有初始化则是在已初始化数据区(RW Data),加上const修饰符将放置在只读区域(RO Data).
例如:
const char ro[ ]=”this is a readonlydata”; //只读数据段,不能改变ro数组中的内容,ro存放在只读数据段。
char rw1[ ]=”this is global readwrite data”; //已初始化读写数据段,可以改变数组rw1中的内容。应为数值/是赋值不是把”this is global readwrite data” 地址给了rw1,不能改变”this is global readwrite data”的数值。因为起是文字常量放在只读数据段中
char bss_1[100];//未初始化数据段
const char *ptrconst = “constant data”; //”constant data”放在只读数据段,不能改变ptrconst中的值,因为其是地址赋值。ptrconst指向存放“constant data”的地址,其为只读数据段。但可以改变ptrconst地址的数值,因其存放在读写数据段中。

实例讲解:
int main( )
{
short b;//b放置在栈上,占用2个字节
char a[100];//需要在栈上开辟100个字节,a的值是其首地址
char s[]=”abcde”;
//s在栈上,占用4个字节,“abcde”本身放置在只读数据存储区,占6字节。s是一个地址
//常量,不能改变其地址数值,即s++是错误的。
char *p1; //p1在栈上,占用4个字节
char *p2 ="123456"; //"123456"放置在只读数据存储区,占7个字节。p2在栈上,p2指向的内容不能更
//改,但是p2的地址值可以改变,即p2++是对的。
static char bss_2[100]; //局部未初始化数据段
static int c=0 ; //局部(静态)初始化区
p1 = (char *)malloc(10*sizeof(char)); //分配的内存区域在堆区
strcpy(p1,”xxx”); //”xxx”放置在只读数据存储区,占5个字节
free(p1); //使用free释放p1所指向的内存
return 0;
}
说明:

1、只读数据段需要包括程序中定义的const型的数据(如:const char ro[]),还包括程序中需要使用的数据如“123456”。对于const char ro[]和const char * ptrconst的定义,它们指向的内存都位于只读数据据区,其指向的内容都不允许修改。区别在于前者不允许在程序中修改ro的值,后者允许在程序中修改ptrconst本身的值。对于后者,改写成以下的形式,将不允许在程序中修改ptrconst本身的值:
const char * const ptrconst = “const data”;

2、读写数据段包含了已经初始化的全局变量static char rw1[]以及局部静态变量static char
rw2[]。rw1和rw2的差别在于编译时,是在函数内部使用的还是可以在整个文件中使用。对于前者,static修饰在于控制程序的其他文件时候可以访问rw1变量,如果有static修饰,将不能在其他的C语言源文件中使用rw1,这种影响针对编译-连接的特性,但无论有static,变量rw1都将被放置在读写数据段。对于后者rw2,它是局部的静态变量,放置在读写数据区;如果不使用static修饰,其意义将完全改变,它将会是开辟在栈空间局部变量,而不是静态变量。

3、未初始化数据段,事例1中的bss_1[100]和 bss_2[200]在程序中代表未初始化的数据段。其区别在于前者是全局的变量,在所有文件中都可以使用;后者是局部的变量,只在函数内部使用。未初始化数据段不设置后面的初始化数值,因此必须使用数值指定区域的大小,编译器将根据大小设置BBS中需要增加的长度。

4、栈空间包括函数中内部使用的变量如short b和char a[100],以及char *p1中p1这个变量的值。
1)变量p1指向的内存建立在堆空间上,堆空间只能在程序内部使用,但是堆空间(例如p1指向的内存)可以作为返回值传递给其他函数处理。
2)栈空间主要用于以下3类数据的存储:
a、函数内部的动态变量
b、函数的参数
c、函数的返回值
3)栈空间主要的用处是供函数内部的动态变量使用,变量的空间在函数开始之前开辟,在函数退出后由编译器自动回收。看一个例:
int main( )
{
char *p = "tiger";
p[1] = 'I';
p++;
printf("%s\n",p);
}
编译后提示:段错误
分析:
char *p = "tiger";系统在栈上开辟了4个字节存储p的数值。"tiger"在只读存储区中存储,因此"tiger"的内容不能改变,*p="tiger",表示地址赋值,因此,p指向了只读存储区,因此改变p指向的内容会引起段错误。但是因为p是存放在栈上,因此p的数值是可以改变的,因此p++是正确的。

六、const的使用

1、前言:
const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程序上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解别人的程序有所帮助。

2、const变量和常量
1)const修饰的变量,其值存放在只读数据段中,其值不能被改变。称为只读变量。
其形式为 const int a=5;此处可以用a代替5
2)常量:其也存在只读数据段中,其数值也不能被改变。其形式为"abc" ,5

3、const 变量和const限定的内容,先看一个事例:
typedef char* pStr;
int main( )
{
char string[6] = “tiger”;
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
printf(“p1=%s\np2=%s\n”,p1,p2);
}
程序经过编译后,提示错误为
error:increment of read-only variable ‘p2’
1)const 使用的基本形式为:const char m;
//限定m 不可变
2)替换1式中的m,const char *pm;
//限定*pm不可变,当然pm是可变的,因此p1++是对的。
3)替换1式中的char,const newType m;
//限定m不可变,问题中的pStr是一种新类型,因此问题中p2不可变,p2++是错误的。

4、const 和指针
类型声明中const用来修饰一个常量,有如下两种写法:
1)const在前面
const int nValue;//nValue是const
const char *pContent;//*pContent是const,pConst可变
const (char *)pContent;//pContent是const,*pContent可变
char *const pContent;//pContent是const,*pContent可变
const char * const pContent;//pContent和*pContent都是const
2)const 在后面与上面的声明对等
int const nValue; // nValue是const
char const *pContent; //*pContent是const, pContent可变
(char *) constpContent; //pContent是const, *pContent可变
char* const pContent; // pContent是const, *pContent可变
char const* const pContent; //pContent和*pContent都是const
说明:const和指针一起使用是C语言中一个很常见的困惑之处,下面是两天规则:
1)沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
2)对于const (char *) ; 因为char *是一个整体,相当于一个类型(如char),因此,这是限定指针是const。

七、单片机C语言中的data,idata,xdata,pdata,code

从数据存储类型来说,8051系列有片内、片外程序存储器,片内、片外数据存储器,片内程序存储器还分直接寻址区和间接寻址类型,分别对应code、data、xdata、idata以及根据51系列特点而设定的pdata类型,使用不同的存储器,将使程序执行效率不同,在编写C51程序时,最好指定变量的存储类型,这样将有利于提高程序执行效率(此问题将在后面专门讲述)。与ANSI-C稍有不同,它只分SAMLL、COMPACT、LARGE模式,各种不同的模式对应不同的实际硬件系统,也将有不同的编译结果。
在51系列中data,idata,xdata,pdata的区别:
data:固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。
idata:固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中idata做指针式的访问效果很好)
xdata:外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。
pdata:外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG,建议少用。但也有他的优点,具体用法属于中级问题,这里不提。

单片机C语言unsigned char code table[] code 是什么作用?

code的作用是告诉单片机,我定义的数据要放在ROM(程序存储区)里面,写入后就不能再更改,其实是相当与汇编里面的寻址MOVX(好像是),因为C语言中没办法详细描述存入的是ROM还是RAM(寄存器),所以在软件中添加了这一个语句起到代替汇编指令的作用,对应的还有data是存入RAM的意思。

程序可以简单的分为code(程序)区,和data (数据)区,code区在运行的时候是不可以更改的,data区放全局变量和临时变量,是要不断的改变的,cpu从code区读取指令,对data区的数据进行运算处理,因此code区存储在什么介质上并不重要,象以前的计算机程序存储在卡片上,code区也可以放在rom里面,也可以放在ram里面,也可以放在flash里面(但是运行速度要慢很多,主要读flash比读ram要费时间),因此一般的做法是要将程序放到flash里面,然后load到 ram里面运行的;DATA区就没有什么选择了,肯定要放在RAM里面,放到rom里面改动不了。

bdata如何使用它呢?

若程序需要8个或者更多的bit变量,如果你想一次性给8个变量赋值的话就不方便了,(举个例子说说它的方便之处,想更深入的了解请在应用中自己琢磨)又不可以定义bit数组,只有一个方法
char bdata MODE;
sbit MODE_7 = MODE^7;
sbit MODE_6 = MODE^6;
sbit MODE_5 = MODE^5;
sbit MODE_4 = MODE^4;
sbit MODE_3 = MODE^3;
sbit MODE_2 = MODE^2;
sbit MODE_1 = MODE^1;
sbit MODE_0 = MODE^0;
8个bit变量MODE_n 就定义好了
这是定义语句,Keilc 的特殊数据类型。记住一定要是sbit
不能 bit MODE_0 = MODE^0;
赋值语句要是这么写C语言就视为异或运算。
Flash相对单片机里的RAM属于外部存取器,虽其结构位置装在单片机中,其实xdata是放在相对RAM的外面,而flash正是相对RAM外面。
int a 变量定义在内部RAM,xdata int a 定义在外部RAM或flash,uchar code a 定义在flash。
uchar code duma[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40,0x00}; //共阴的数码管段选,P2口要取的数值
若定义 uchar aa[5],aa[5]中的内容是存放在数据存储区(RAM)中的,在程序运行工程中各个数组元素的值可以被修改,掉电后aa[5]中的数据无法保存。
若定义 uchar code bb[5]中的内容是存放在程序存储区(如flash)中的,只有在烧写程序时,才能改变bb[5]中的各元素的值,在程序运行工程中无法修改,并且掉电后bb[5]中的数据不消失。

八、C语言中堆和栈的区别

C语言程序经过编译连接后形成编译、连接后形成的二进制映像文件由栈、堆、数据段(由三部分部分组成:只读数据段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成,如下图所示:

单片机中C语言的程序与数据存储

1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。

2、堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。注堆和数据结构中的堆栈不一样,其类是与链表。

3、程序代码区:存放函数体的二进制代码。

4、数据段:由三部分组成:

1)只读数据段:
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中。
2)已初始化的读写数据段:
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局部变量(static修饰的已经初始化的变量)
3)未初始化段(BSS):
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。

堆和栈的区别

1、申请方式

(1)栈(satck):由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。
(2)堆(heap):需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。容易产生memory leak.

eg:char p;
p = (char *)malloc(sizeof(char)); //但是,p本身是在栈中。

2、申请大小的限制

1)栈:在windows下栈是向底地址扩展的数据结构,是一块连续的内存区域(它的生长方向与内存的生长方向相反)。栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示overflow。
2)堆:堆是高地址扩展的数据结构(它的生长方向与内存的生长方向相同),是不连续的内存区域。这是由于系统使用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由底地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。

3、系统响应:

1)栈:只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
2)堆:首先应该知道操作系统有一个记录空闲内存地址的链表,但系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
说明:对于堆来讲,对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,

4、申请效率

1)栈由系统自动分配,速度快。但程序员是无法控制的
2)堆是由malloc分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。

5、堆和栈中的存储内容

1)栈:在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入栈的,然后是函数中的局部变量。注:静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。
2)堆:一般是在堆的头部用一个字节存放堆的大小。

6、存取效率

1)堆:char *s1=”hellow tigerjibo”;是在编译是就确定的
2)栈:char s1[]=”hellow tigerjibo”;是在运行时赋值的;用数组比用指针速度更快一些,指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上读取。

补充:

栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

7、分配方式:

1)堆都是动态分配的,没有静态分配的堆。
2)栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现。

来源: eeworld.com

围观 3
52

用单片机来控制LCM模块,方式十分简单,LCM模块其内部可以看成两组寄存器, 一个为指令寄存器IR,一个为数据寄存器DR,由RS引脚来控制。所有对指令寄存器或 数据寄存器的存取均需检查LCM内部的忙碌标志BF,此标志用来告知LCM内部正在 工作,并不允许接收任何的控制命令。而此位的检查可以令RS=0,用读取D7来加以判 断,当D7为0时,才可以写入指令或数据寄存器。LCM控制指令共有11组,以下分别介 绍。

1. 清屏

清屏指令格式如下:

单片机控制lcm液晶模块指令

指令代码为01H,将DDRAM数据全部填入“空白”的ASCII代码20H,执行此指令 将清除显示器的内容,同时光标移到左上角。

2. 光标归位

光标归位指令格式如下:

单片机控制lcm液晶模块指令

指令代码为02H,地址计数器AC被清0,DDRAM数据不变,光标移到左上角。× 表示可以为0或1。

3. 输入方式设置

输入方式设置指令格式如下:

单片机控制lcm液晶模块指令

该指令用来设置光标、字符移动的方式。具体情况如下表所示。

单片机控制lcm液晶模块指令

4. 显示开关控制

显示开关控制指令格式如下:

单片机控制lcm液晶模块指令

指令代码为08H~0FH。该指令控制字符、光标及闪烁的开与关,有3个状态位 D、 C、B,这3个状态位分别控制着字符、光标和闪烁的显示状态。

D是字符显示状态位。D=1时,为开显示;D=0时,为关显示。注意关显示仅是字 符不出现,而DDRAM内容不变。这与清屏指令不同。

C是光标显示状态位。C=1时,为光标显示;C=0时,为光标消失。光标为底线形 式(5×1点阵),光标的位置由地址指针计数器AC确定,并随其变动而移动。当AC值超 出了字符的显示范围,光标将随之消失。

B是光标闪烁显示状态位。B=1时,光标闪烁;B=0时,光标不闪烁。

5. 光标、字符位移

光标、字符位移指令的格式如下:

单片机控制lcm液晶模块指令

执行该指令将产生字符或光标向左或向右滚动一个字符位。如果定时间隔地执行该 指令,将产生字符或光标的平滑滚动。 具体情况如下表所示。

单片机控制lcm液晶模块指令

6. 功能设置

功能设置指令格式如下:

单片机控制lcm液晶模块指令

该指令用于设置控制器的工作方式,有3个参数DL、N和F,它们的作用是:

DL用于设置控制器与计算机的接口形式。接口形式体现在数据总线长度上。DL= 1设置数据总线为8位长度,即D7~D0有效;DL=0设置数据总线为4位长度,即 D7~ D4有效。在该方式下8位指令代码和数据将按先高4位后低4位的顺序分两次传输。

N用于设置显示的字符行数。N=0为一行字符行;N=1为两行字符行。

F用于设置显示字符的字体。F=0为5×7点阵字符体;F=1为5×10点阵字符体。

7. CGRAM地址设置

CGRAM地址设置指令格式如下:

单片机控制lcm液晶模块指令

该指令将6位的CGRAM地址写入地址指针计数器AC内,随后,单片机对数据的操 作是对CGRAM的读/写操作。

8. DDRAM地址设置

DDRAM地址设置指令格式如下:

单片机控制lcm液晶模块指令

该指令将7位的DDRAM地址写入地址指针计数器AC内,随后,单片机对数据的操 作是对DDRAM的读/写操作。

9. 读BF及AC值

读BF及AC指令的格式如下:

单片机控制lcm液晶模块指令

LCD的忙碌标志BF用以指示 LCD目前的工作情况。当 BF=1时,表示正在进行内 部数据的处理,不接受单片机送来的指令或数据;当 BF=0时,则表示已准备接收命令或 数据。当程序读取此数据的内容时,D7为忙碌标志,而另外 D6~D0的值表示 CGRAM 或 DDRAM中的地址,至于是指向哪一地址则根据最后写入的地址设定指令而定。

10. 写数据到CGRAM或DDRAM

写数据到CGRAM或DDRAM的指令格式如下:

单片机控制lcm液晶模块指令

先设定CGRAM或DDRAM地址,再将数据写入 D7~D0中,以使 LCD显示出字形。也可将使用者自创的图形存入CGRAM。

11. 从CGRAM或DDRAM读取数据

从CGRAM或DDRAM读取数据的指令格式如下:

单片机控制lcm液晶模块指令

先设定CGRAM或DDRAM地址,再读取其中的数据

转自: 畅学电子网

围观 4
64

页面

订阅 RSS - 单片机