So Tired !_! 逆水行舟, 不进则退!

6May/11

Python入门教程 超详细1小时学会Python

Posted by Nick Xu

为什么使用Python
假设我们有这么一项任务:简单测试局域网中的电脑是否连通.这些电脑的ip范围从192.168.0.101到192.168.0.200.

思路:用shell编程.(Linux通常是bash而Windows是批处理脚本).例如,在Windows上用ping ip 的命令依次测试各个机器并得到控制台输出.由于ping通的时候控制台文本通常是"Reply from ... " 而不通的时候文本是"time out ... " ,所以,在结果中进行字符串查找,即可知道该机器是否连通.

实现:Java代码如下:

String cmd="cmd.exe ping ";
String ipprefix="192.168.10.";
int begin=101;
int end=200;
Process p=null;

for(int i=begin;i

这段代码运行得很好,问题是为了运行这段代码,你还需要做一些额外的工作.这些额外的工作包括:

* 编写一个类文件
* 编写一个main方法
* 将之编译成字节代码
* 由于字节代码不能直接运行,你需要再写个小小的bat或者bash脚本来运行.

当然,用C/C++同样能完成这项工作.但C/C++不是跨平台语言.在这个足够简单的例子中也许看不出C/C++和Java实现的区别,但在一些更为复杂的场景,比如要将连通与否的信息记录到网络数据库.由于Linux和Windows的网络接口实现方式不同,你不得不写两个函数的版本.用Java就没有这样的顾虑.

同样的工作用Python实现如下:

import subprocess

cmd="cmd.exe"
begin=101
end=200
while begin

对比Java,Python的实现更为简洁,你编写的时间更快.你不需要写main函数,并且这个程序保存之后可以直接运行.另外,和Java一样,Python也是跨平台的.

有经验的C/Java程序员可能会争论说用C/Java写会比Python写得快.这个观点见仁见智.我的想法是当你同时掌握Java和Python之后,你会发现用Python写这类程序的速度会比Java快上许多.例如操作本地文件时你仅需要一行代码而不需要Java的许多流包装类.各种语言有其天然的适合的应用范围.用Python处理一些简短程序类似与操作系统的交互编程工作最省时省力.

Python应用场合
足够简单的任务,例如一些shell编程.如果你喜欢用Python设计大型商业网站或者设计复杂的游戏,悉听尊便.

2 快速入门
2.1 Hello world
安装完Python之后(我本机的版本是2.5.4),打开IDLE(Python GUI) , 该程序是Python语言解释器,你写的语句能够立即运行.我们写下一句著名的程序语句:

print "Hello,world!"

并按回车.你就能看到这句被K&R引入到程序世界的名言.

在解释器中选择"File"--"New Window" 或快捷键 Ctrl+N , 打开一个新的编辑器.写下如下语句:

print "Hello,world!"
raw_input("Press enter key to close this window");

保存为a.py文件.按F5,你就可以看到程序的运行结果了.这是Python的第二种运行方式.

找到你保存的a.py文件,双击.也可以看到程序结果.Python的程序能够直接运行,对比Java,这是一个优势.

2.2 国际化支持
我们换一种方式来问候世界.新建一个编辑器并写如下代码:

print "欢迎来到奥运中国!"
raw_input("Press enter key to close this window");

在你保存代码的时候,Python会提示你是否改变文件的字符集,结果如下:

# -*- coding: cp936 -*-

print "欢迎来到奥运中国!"
raw_input("Press enter key to close this window");

将该字符集改为我们更熟悉的形式:

# -*- coding: GBK -*-

print "欢迎来到奥运中国!" # 使用中文的例子
raw_input("Press enter key to close this window");

程序一样运行良好.
2.3 方便易用的计算器
用微软附带的计算器来计数实在太麻烦了.打开Python解释器,直接进行计算:

a=100.0
b=201.1
c=2343
print (a+b+c)/c

2.4 字符串,ASCII和UNICODE

可以如下打印出预定义输出格式的字符串:

print """
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
"""

字符串是怎么访问的?请看这个例子:

word="abcdefg"
a=word[2]
print "a is: "+a
b=word[1:3]
print "b is: "+b # index 1 and 2 elements of word.
c=word[:2]
print "c is: "+c # index 0 and 1 elements of word.
d=word[0:]
print "d is: "+d # All elements of word.
e=word[:2]+word[2:]
print "e is: "+e # All elements of word.
f=word[-1]
print "f is: "+f # The last elements of word.
g=word[-4:-2]
print "g is: "+g # index 3 and 4 elements of word.
h=word[-2:]
print "h is: "+h # The last two elements.
i=word[:-2]
print "i is: "+i # Everything except the last two characters
l=len(word)
print "Length of word is: "+ str(l)

请注意ASCII和UNICODE字符串的区别:

print "Input your Chinese name:"
s=raw_input("Press enter to be continued");
print "Your name is : " +s;
l=len(s)
print "Length of your Chinese name in asc codes is:"+str(l);
a=unicode(s,"GBK")
l=len(a)
print "I'm sorry we should use unicode char!Characters number of your Chinese \
name in unicode is:"+str(l);

2.5 使用List
类似Java里的List,这是一种方便易用的数据类型:

word=['a','b','c','d','e','f','g']
a=word[2]
print "a is: "+a
b=word[1:3]
print "b is: "
print b # index 1 and 2 elements of word.
c=word[:2]
print "c is: "
print c # index 0 and 1 elements of word.
d=word[0:]
print "d is: "
print d # All elements of word.
e=word[:2]+word[2:]
print "e is: "
print e # All elements of word.
f=word[-1]
print "f is: "
print f # The last elements of word.
g=word[-4:-2]
print "g is: "
print g # index 3 and 4 elements of word.
h=word[-2:]
print "h is: "
print h # The last two elements.
i=word[:-2]
print "i is: "
print i # Everything except the last two characters
l=len(word)
print "Length of word is: "+ str(l)
print "Adds new element"
word.append('h')
print word

2.6 条件和循环语句

# Multi-way decision
x=int(raw_input("Please enter an integer:"))
if x<0:
x=0
print "Negative changed to zero"

elif x==0:
print "Zero"

else:
print "More"


# Loops List
a = ['cat', 'window', 'defenestrate']
for x in a:
print x, len(x)

2.7 如何定义函数

# Define and invoke function.
def sum(a,b):
return a+b


func = sum
r = func(5,6)
print r

# Defines function with default argument
def add(a,b=2):
return a+b
r=add(1)
print r
r=add(1,5)
print r

并且,介绍一个方便好用的函数:

# The range() function
a =range(5,10)
print a
a = range(-2,-7)
print a
a = range(-7,-2)
print a
a = range(-2,-11,-3) # The 3rd parameter stands for step
print a

2.8 文件I/O

spath="D:/download/baa.txt"
f=open(spath,"w") # Opens file for writing.Creates this file doesn't exist.
f.write("First line 1.\n")
f.writelines("First line 2.")

f.close()

f=open(spath,"r") # Opens file for reading

for line in f:
print line

f.close()

2.9 异常处理

s=raw_input("Input your age:")
if s =="":
raise Exception("Input must no be empty.")

try:
i=int(s)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unknown exception!"
else: # It is useful for code that must be executed if the try clause does not raise an exception
print "You are %d" % i," years old"
finally: # Clean up action
print "Goodbye!"

2.10 类和继承

class Base:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)

# Child extends Base
class Child(Base):
def plus(self,a,b):
return a+b

oChild =Child()
oChild.add("str1")
print oChild.data
print oChild.plus(2,3)

2.11 包机制
每一个.py文件称为一个module,module之间可以互相导入.请参看以下例子:

# a.py
def add_func(a,b):
return a+b
# b.py
from a import add_func # Also can be : import a

print "Import add_func from module a"
print "Result of 1 plus 2 is: "
print add_func(1,2) # If using "import a" , then here should be "a.add_func"

module可以定义在包里面.Python定义包的方式稍微有点古怪,假设我们有一个parent文件夹,该文件夹有一个child子文件夹.child中有一个module a.py . 如何让Python知道这个文件层次结构?很简单,每个目录都放一个名为_init_.py 的文件.该文件内容可以为空.这个层次结构如下所示:
parent
--__init_.py
--child
-- __init_.py
--a.py

b.py

那么Python如何找到我们定义的module?在标准包sys中,path属性记录了Python的包路径.你可以将之打印出来:

import sys

print sys.path

通常我们可以将module的包路径放到环境变量PYTHONPATH中,该环境变量会自动添加到sys.path属性.另一种方便的方法是编程中直接指定我们的module路径到sys.path 中:

import sys
sys.path.append('D:\\download')

from parent.child.a import add_func


print sys.path

print "Import add_func from module a"
print "Result of 1 plus 2 is: "
print add_func(1,2)

总结
你会发现这个教程相当的简单.许多Python特性在代码中以隐含方式提出,这些特性包括:Python不需要显式声明数据类型,关键字说明,字符串函数的解释等等.我认为一个熟练的程序员应该对这些概念相当了解,这样在你挤出宝贵的一小时阅读这篇短短的教程之后,你能够通过已有知识的迁移类比尽快熟悉 Python,然后尽快能用它开始编程.

当然,1小时学会Python颇有哗众取宠之嫌.确切的说,编程语言包括语法和标准库.语法相当于武术招式,而标准库应用实践经验则类似于内功,需要长期锻炼.Python学习了Java的长处,提供了大量极方便易用的标准库供程序员"拿来主义".(这也是Python成功的原因),在开篇我们看到了 Python如何调用Windows cmd的例子,以后我会尽量写上各标准库的用法和一些应用技巧,让大家真正掌握Python.

但不管怎样,至少你现在会用Python代替繁琐的批处理写程序了.希望那些真的能在一小时内读完本文并开始使用Python的程序员会喜欢这篇小文章,谢谢!

Tagged as: , No Comments
10Dec/10

以远程桌面访问Windows Azure虚拟机(转+译)

Posted by Nick Xu

本周Windows Azure Team发布了新的开发门户和1.3版本的SDK。新版SDK中有许多非常酷的特性,其中之一便是以远程桌面访问正在运行的Windows Azure虚拟机,该特性也是我之前非常期待的的一个新功能。

配置远程桌面访问

为Windows Azure服务启用远程桌面访问是非常简单的。首先,使用Visual Studio创建一个新的Windows Azure项目,在这个例子中我仅创建一个常规的没有任何修改的MVC2 web role。 然后在解决方案窗口右击该Windows Azure项目选择"Publish"。

选中"Deploy your Windows Azure project to Windows Azure"单选按钮,提供证书,部署服务/槽、存储和标签等信息。

为了使用一键部署的特性,您必须将Management API证书上传至到您的Windows Azure帐户内,并在计算机上安装该证书。
您会在对话框下方发现有一个"Configure Remote Desktop connections"的连接。您需要通过该链接来开启远程桌面访问功能。

点击这该连接,我们将配置远程桌面访问的权限信息。配置过程有4个步骤:

  • 证书:我们需要创建或选择一个证书文件来加密访问凭证。在这个例子中我将证书文件用于我的Management API。
  • 用户名:访问虚拟机的远程桌面用户名。
  • 密码:登录密码。
  • 有效期限:登录凭证默认在1个月后过期,但我们可以修改该默认值。

在这之后我们单击OK按钮,返回发布对话

下一步访问新版Windows Azure门户,找到托管服务清单。创建一个新的托管服务并上传证书文件到该服务。用于访问Windows Azure虚拟机的用户名和密码必须在本地机器上加密,并在Windows Azure端以相同的证书文件进行解密。这就是我们为什么需要上传证书文件到Windows Azure上的原因了。
在左侧面板的"Hosted Services, Storage Accounts & CDN"中,创建一项新的托管服务,命名为"SDK13,然后点击"Certificates"。然后我们点击"Add Certificates"按钮。

选择本地证书文件和密码,将其安装至Windows Azure服务。

最后一步会返回到Visual Studio,点击发布对话框中的OK按钮。然后Visual Studio会将程序包,以及带有远程桌面设置的配置文件上传至Windows Azure。

远程桌面访问Windows Azure虚拟机

完成所有上述步骤后,让我们来回到新版Windows Azure开发门户。选择刚部署至Windows Azure的web role,在工具栏上我们可以看到有一名为"Remote Access"工具区。在该工具区,我们看到Enable复选框已经被勾上了,这意味着该web role已经开启了远程桌面访问功能。

如果想要修改连接凭证,我们可以直接点击Configure按钮,便可以更新用户名、密码、证书和有效期限。

选择web role下的实例节点,我们可以看到,我们可以点击Connect按钮来启动远程桌面访问。

点击该按钮后,我们会下载一个RDP文件被。这是一个远程桌面的配置文件, 可以用来访问我们的Windows Azure虚拟机。我们将其下载到本地,并运行。

输入先前的用户名和密码,点击OK按钮。

此时可能出现证书警告对话框。这是因为我们用来加密的证书并不是由可信赖的提供商签署的。此时选择OK。因为我们清楚知道该证书对我们来说是安全的。

最后,我们成功的以远程桌面访问Windows Azure虚拟机。

快速的浏览Windows Azure虚拟机概况

我们可以浏览一下我们的虚拟机。这里有3个磁盘可供我们使用:C、D、E。

  • C盘: 储存本地资源、诊断信息等。
  • D盘: 包含OS,IIS, .NET Frameworks等文件的系统磁盘。
  • E盘: 存储我们的应用程序代码。

托管了服务的IIS。

Windows Azure虚拟机的IP配置。

总结

在这篇帖子中我讲解了Windows Azure 1.3版本SDK其中的一个新特性-远程桌面访问。我们可以按照每个服务来设置远程桌面访问权限,这样该服务下的每个实例都能被远程桌面访问。该特性让我 们能深入我们实例的虚拟机, 来查看实例内部信息,例如系统事件、IIS日志、系统信息等。

但是我们在修改系统设置时,应注意如下2:

  • 1. 如果某个服务有超过一个的实例,我们就应该确保所有的虚拟机/实例有着相同的系统设置。否则, 由于实例间的不同设置会使负载均衡代理无法正常工作。
  • 2. 当虚拟机中遇到了一些问题,需要被转移至另一个物理计算机时,我们需要对该新机器进行设置。

本文翻译自:http://geekswithblogs.net/shaunxu/archive/2010/12/03/remote-desktop-to-your-azure-virtual-machine.aspx

Windows Azure SDK 1.3 allows customers to connect to their VM’s on the cloud. Below is the step-by-step article to connect to VM’s via remote desktop.

1) Create simple cloud project with ASP.net role

2) In Visual Studio Solution Explorer,  Right click on the Cloud Service Project and Click “Publish”, It will bring up screen similar to below. Now, Click on “Configure Remote Desktop Connections” as highlighted below

3) Select “Enable connections for all roles” checkbox, Create certificate or use existing certificate. This certificate will be used to encrypt the credentials

Note: Certificate created in this step can be found in Current User\personal store.

4) Configure Credentials, Expiry date as shown below

5) ServiceConfiguration.cscfg , ServiceDefinition.csdef looks similar to below. In case you would like to manually configure the settings, below highlighted settings should be configured to enable remote desktop connection.

ServiceConfiguration.cscfg

<?xml version="1.0" encoding="utf-8"?>

<ServiceConfiguration serviceName="RemoteConnect" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="1" osVersion="*">

<Role name="HelloCloud">

<Instances count="1" />

<ConfigurationSettings>

<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />

<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />

<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="user" />

<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcNAQcDoIIBjjCCAYoCAQAxggFOMIIBSgIBADAyMB4xHDAaBgNVBAMME1dpbmRvd3MgQXp1cmUgVG9vbHMCECY7YujxTkiRToi1balVmo8wDQYJKoZIhvcNAQEBBQAEggEAPqVUbmF5jkFaLnsK1o1VowCgBT/5VYrI7HiVCmKh8OAnqLarN/YVvisqGVmL0XSjBxbk9JfRd/dS3kH9/JYp+G1STWoG4TsWGNRy6LX6OMQao/1t/QbUtaIreMAflYVjrF+AiUMPsLWwABphx9x48hNX970EXIylmINfpCLXpUtSCatoafS2Z260tS9ngszvjj+VD98Qn1GvRi6FwVQW8LQASEB3/h5n1M8oRFIXpAJx57Kj4IwTxGagKCucvMFO05vxFJGdNMGxmfP5mKRCLzXx6BxyiKmV4fMn/2PCJr7W4sSGycGzBXQz3e8JmDTsc7668Hida5147B78zHCvejAzBgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBujO0y+1cv8gBCNjmulM1+bvMPGdWpkBfOO" />

<Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2011-01-01T23:59:59.0000000+05:30" />

<Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />

</ConfigurationSettings>

<Certificates>

<Certificate name="Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" thumbprint="E5B7C551309D21D26DEA8464380C61D7EC8FC4D1" thumbprintAlgorithm="sha1" />

</Certificates>

</Role>

</ServiceConfiguration>

Note: You would need to specify the encrypted password if you are configuring AccountEncryptedPassword manually. Following this article to encrypt the password using PowerShell.

ServiceDefinition.csdef

<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition name="RemoteConnect" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

<WebRole name="HelloCloud">

<Sites>

<Site name="Web">

<Bindings>

<Binding name="Endpoint1" endpointName="Endpoint1" />

</Bindings>

</Site>

</Sites>

<Endpoints>

<InputEndpoint name="Endpoint1" protocol="http" port="80" />

</Endpoints>

<Imports>

<Import moduleName="Diagnostics" />

<Import moduleName="RemoteAccess" />

<Import moduleName="RemoteForwarder" />

</Imports>

</WebRole>

</ServiceDefinition>

Note: If you are configuring these settings manually, remember to add certificate to web role

6) Export the certificate created in step 3 to pfx format

a)       Open certificate snap in for “My user account” (current user)

b)       Navigate to Current User\Personal\Certificates

c)       Right click on the certificate that we created in step 3

d)       Choose All Tasks è Export è Choose Next

e)       Choose options as shown in screen shots

7) Upload the certificate to the Windows Azure portal

8) Deploy the application to cloud either via portal or VS2010 Publish option

9) Once the deployment is successful, Go to Windows Azure portal

10) Click on Connect to create .RDP file , save it locally and open the rdp file by double clicking on it

11) When prompted for credentials, specify the credentials mentioned in step 4

Voila! Connected to the VM!

22May/10

STM32 入门教程 基于 MDK 的 SWD 两线串行仿真

Posted by Nick Xu

(1) SWD 仿真模式概念简述
先所说 SWD 和传统的调试方式有什么不一样:

首先给大家介绍下经验之谈:
(一): SWD 模式比 JTAG 在高速模式下面更加可靠. 在大数据量的情况下面 JTAG 下载程序会 失败, 但是 SWD 发生的几率会小很多. 基本使用 JTAG 仿真模式的情况下是可以直接使用 SWD 模式的, 只要你的仿真器支 持. 所以推荐大家使用这个模式.
(二): 在大家 GPIO 刚好缺一个的时候, 可以使用 SWD 仿真, 这种模式支持更少的引脚.
(三): 在大家板子的体积有限的时候推荐使用 SWD 模式, 他需要的引脚少, 当然需要的 PCB 空间就小啦. 比如: 你可以选择一个很小的 2.54 间距的 5 芯端子做仿真接口.

(2) 仿真器对 SWD 模式支持情况
再说说市面上的常用仿真器对 SWD 仿真的支持情况.

(1)  JLINKV6 支持 SWD 仿真模式. 速度较慢.
(2)  JLINKV7 比较好的支持 SWD 仿真模式, 速度有了明显的提高. 速度是 JLINKV6 的 6 倍.
(3)  JLINKV8 非常好的支持 SWD 仿真模式, 速度可以到 10M.
(4)  ULINK1  不支持 SWD 模式
(5)  盗版 ULINK2  非常好的支持 SWD 模式. 速度可以达到 10M.
(6)  正版 ULINK2  非常好的支持 SWD 模式. 速度可以达到 10M.

再所说硬件上的不同:
(1)  JLINKV6 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(2)  JLINKV7 需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(3)  JLINKV8 需要的硬件接口为: VCC, GND, RST, SWDIO, SWDCLK
(4)  ULINK1  不支持 SWD 模式
(5)  盗版 ULINK2  需要的硬件接口为: GND, RST, SWDIO, SWDCLK
(6)  正版 ULINK2  需要的硬件接口为: GND, RST, SWDIO, SWDCLK
由此可以看到只有 JLINKV8 需要 5 个引脚. 那么给大家介绍下为什么有了 VCC 这个引脚时候有好处, 我的个人理解: 我认为有这个引脚是最合适的, 仿真器对目标板子的仿真需要用到 RST 引脚, 其实使用仿真器内部的 VCC 做这个功能其实并不是非常美妙. 因此 JLINKV8 选择了只和目标板共 GND, 但是不共 VCC. 因此我觉得这种模式最合理, 当然通常情况下仿真器和目标板共 GND 和 VCC 是没有错的.

下面两张演示就是我使用 JLINKV8 进行下载程序到 Mini-STM32 开发板上仿真的图片. 程序代码中使用 了 uCGUI , 一共程序代码 120K 左右. 使用 JLINKV8 下载到芯片中只需要 5 秒左右.

SWD1.jpg

下 载 (61.11 KB)

2009-9-5 20:32

SWD2.jpg

下 载 (62.06 KB)

2009-9-5 20:32

SWD3.jpg

下 载 (49.7 KB)

2009-9-5 20:32

(3) 在 MDK 中SWD 模式设置

接下来告诉大家怎么使用 SWD 设置:

打开工程 Option 设置:

SWD4.jpg

下 载 (54.8 KB)

2009-9-5 21:16

SWD5.jpg

下 载 (64.5 KB)

2009-9-5 21:16

在设置中按照上图设置成 SWD 模式, 速度你可以按照你的实际需求来设置, 如果你的板子供电系统不是特别稳定, 纹波比较大或者仿真线比较长可以设置成 500K 或者 1M , 如果环境很好当然可以选择 10M , 当然速度会飞起来.

记得不要忽略了左下方的那个

USB

还是 TCP 模式, 当然我们是 USB 模式, 因为有的时候默认是 TCP 模式, 这个时候我们忽略这个设置后会仿真常常连接不上的.

注意: 上面这个界面是在仿真器插在电脑上才会出现的界面, 就是说没有实际插入仿真器的话不会出现这个界面的.
当然虽然上面是 JLINKV8, ULINK2 下的设置也是一样的.

22May/10

STM32 入门教程 RTC 实时时钟

Posted by Nick Xu

(一) STM32 RTC 实时时钟概要
STM32内部RTC功能非常实用,它的供电和时钟是独立于内核的,可以说是STM32内部独立的外设模块,有加上RTC内部寄存器不受系统复位掉电的影 响,我们可以才用外部电池供电和32768表振晶体来实现真正RTC(实时时钟)功能。

这里引用手册里一段概述:

RTC由两个主要部分组成。第一部分(APB1接口)用来和 APB1总线相连。此单元还包含一组 16位寄存器,可通过 APB1总线对其进行读写操作。APB1接口以 APB1总线时钟为时钟,用来与 APB1总线接口。

另一部分(RTC核)由一系列可编程计数器组成,分成两个主要模块。第一个模块是 RTC的预分频模块,它可编程产生最长为 1秒的 RTC时间基准 TR_CLK。RTC的预分频模块包含了一个 20位的可编程分频器(RTC预分频器)。在每个TR_CLK周期中,如果在 RTC_CR 寄存器中设置了相应允许位,则 RTC产生一个中断(秒中断)。第 2个模块是一个 32位的可编程的计数器,它可被初始化为当前的系统时间。系统时间以 TR_CLK速度增长并与存储在 RTC_ALR寄存器中的可编程的时间相比较,如果 RTC_CR控制寄存器中设置了相应允许位,则比较匹配时将产生一个闹钟中断。

3.jpg

下载 (66.99 KB)

2009-8-24 21:46

2.jpg

下载 (67.56 KB)

2009-8-24 21:44

(二) 程序编写

(1) 系统启动后检查RTC是否已设置。由于RTC在BKP区域,当Vdd掉电之后可由后备电源提供电源,当后备电源连接到针脚VBAT上时,RTC的设置不会 由于外部电源的断开而丢失。在本例中写一个值到BKP_DR1中以标示RTC是否已配置,在启动之后程序检查BKP_DR1的值。

(2)  若BKP_DR1的值不正确:(BKP_DR1的值有误或者由于是第一次运行值还未写进去),则需要配置时间并且询问用户调整时间。

(3) 若BKP_DR1的值正确,则意味着RTC已配置,此时将在超级终端上显示时间。

(4) 整个工程包含3个源文件:STM32F10x.s、stm32f10x_it.c和main.c,其中STM32F10x.s为启动

代码

,所有中断服务子程序均在stm32f10x_it.c中,其它函数则在main.c中。下面分别介绍相关的函数,具体程序清单见参考程序。

函数RTC_IRQHandler用于处理秒中断事件,在每次遇到23:59:59时将时钟回零。

函数RTC_Configuration用于配置RTC模块。

函数USART_Scanf用于从PC超级终端中获取数字值,Time_Regulate利用函数USART_Scanf从超级终端获取新的RTC 时间 值,函数Time_Adjust则利用函数USART_Scanf设置新的RTC时间。函数Time_Display和Time_Show用于将RTC时 间转换了字符串送往USART1。

源文件其他函数,例如

GPIO

、RCC、NVIC、USART的配置,在此不作冗述。

(三) 仿真调试

(1) 使用Keil uVision3 通过ULINK 2

仿真器

连接实验板,打开实验例程目录Example10-RTC子目录下的RTC.Uv2例程,编译

链接

工程;

(2) 使用STM32开发板附带的串口线,连接开发板上的COM0和PC机的串口;

(3) 在PC机上运行Windows自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;

(4) 选择硬件调试模式,点击

MDK

的Debug菜单,选择Start/Stop Debug Session项或Ctrl+F5键,远程连接目标板并下载调试代码到目标系统中;

(5) 例程正常运行之后会在超级终端显示以下信息:

麦思网原创

教程
www.mystm32.com/bbs

MINI-STM32  STM32F103RBT6 RTC

RTC not yet configured....

RTC configured....

============TimeSettings===================

Please Set Hours:

在PC机上依次输入时钟、分钟、秒钟之后每隔1秒在超级终端上显示一次时间:

Please Set Hours:  12

Please Set Minutes:  0

Please Set Seconds:  0

Time: 12:00:00

(6) 程序正常运行并在开发外部电源保持的情况下,按下Reset

按钮

,PC超级终端上将继续显示正常时间:

External Reset occurred....

No need to configure RTC....

Time: 12:03:09

(7) 程序正常运行时断开开发板外部电源,然后重新接上外部电源,PC超级终端上也将继续显示正常时间:

Power On Reset occurred....

No need to configure RTC....

Time: 12:05:57

(8) 取下处理器板上的纽扣电池,并断开外部电源,然后重新接上外部电源,PC超级终端上将无法继续正常显示时间,PC超级终端将出现第(5)步所出现内容。

(9) 也可采用

软件

调试模式,利用USART窗口来

模拟

实现COM0的输入和输出。

请参考下图: 你需要在超级终端中敲击按键来设置时钟.

1.jpg

下载 (45.33 KB)

2009-8-24 21:17
22May/10

STM32 入门教程 内部温度传感器

Posted by Nick Xu

(一) STM32 内部温度传感器概要

STM32 芯片内部一项独特的功能就是内部集成了一个温度传感器, 因为是内置, 所以测试的是芯片内部的温度, 如果芯片外接负载一定的情况下, 那么芯片的发热也基本稳定, 相对于外界的温度而言, 这个偏差值也是基本稳定的. 也就是说用 STM32 内部传感器来测量外界环境的温度.

在一些恶劣的应用环境下面, 可以通过检测芯片内部而感知设备的工作环境温度, 如果温度过高或者过低了 则马上睡眠或者停止运转. 可以保证您的设备工作的可靠性.

1.  STM32内部温度传感器与ADC的通道16相连,与ADC配合使用实现温度测量;
2.  测量范围–40~125℃,精度±1.5℃。
3.  温度传感器产生一个随温度线性变化的电压,转换范围在2V < VDDA < 3.6V之间。

转换公式如下图所示:

1.jpg

下载 (64.9 KB)

2009-8-23 21:50

手册中对于公式中的参数说明:

2.jpg

下载 (48.4 KB)

2009-8-23 21:24

(二) 程序编写

代码

的时候, 在测量要求不怎么高的情况下, 公式可以简化.

简化的公式:

Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;

程序编写:

1. 初始化ADC , 初始化

DMA

可以参考贴子:

[原创] MINI-STM32 开发板

入门教程

(六) 基于 DMA 的 ADC
http://www.mystm32.com/bbs/viewthread.php?tid=42&extra=page%3D1

主意:  内部温度传感器是使用了 ADC1 的第 16 通道哦.

2.  ADC_TempSensorVrefintCmd(ENABLE);

使能温度传感器和内部参考电压通道

3. 按照刚才列出的公式计算

Temperature= (1.42 - ADC_Value*3.3/4096)*1000/4.35 + 25;

第二步是做什么的呢?  参考下图:

3.jpg

下载 (20.42 KB)

2009-8-23 21:50

(三) 仿真调试

(1) 使用Keil uVision3 通过ULINK 2

仿真器

连接实验板,使用MINI-STM32 开发板附带的串口线,连接实验板上的 UART1 和 PC 机的串口,打开实验例程目录下的ADC.Uv2例程,编译

链接

工程;

(2) 在 PC 机上运行 windows 自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;

(3) 点击

MDK

的Debug菜单,点击Start/Stop Debug Session;

(4) 全速运行程序, 显示结果如下所示。

麦思网原创教程
www.mystm32.com/bbs

1.jpg

下载 (54.75 KB)

2009-8-23 22:53
22May/10

STM32 入门教程 Unique Device ID

Posted by Nick Xu

每一片 STM32 芯片内部拥有一个独一无二的 Unique Device ID, 96 Bit.

这个 ID 号可以提供给开发者很多优越的功能, 例如:
1. 可以把 ID 做为用户最终产品的序列号,帮助用户进行产品的管理。
2. 在某些需要保证安全性的功能代码运行前,通过校验此 ID,保证最终产品的某些功能的安全性。
3. 用 ID 配合加解密算法,对芯片内部的代码进行加加密,以保证用户产品的安全性和不可复制性。

这项功能相信对那些不希望自己的产品不被别人抄袭来说肯定是个非常不错的。

下面我就先大家介绍如何读出这个 ID:

这个 ID 号是放在片内 Flash 中的固定的位置,直接读取出来就行了. 96 位的独特 ID 位于地址 0x1FFFF7E8 ~ 0x1FFFF7F4 的系统存储区, 由 ST 公司在工厂中写入 (用户不能修改) 用户可以以字节、半字、或字的方式单独读取其间的任一地址.

(一) 配置好串口, 用于调试演示, 这一部份前面的章节我们已经介绍过了.
可以参考贴子:
[原创] MINI-STM32 开发板入门教程 (六) 基于 DMA 的 ADC
http://www.mystm32.com/bbs/viewthread.php?tid=42&extra=page%3D1

当然也可以直接下载我们的例程去读这部分的程序.

(二) 读取 Unique Device ID

u32 Dev_Serial0, Dev_Serial1, Dev_Serial2;

Dev_Serial0 = *(vu32*)(0x1FFFF7E8);
Dev_Serial1 = *(vu32*)(0x1FFFF7EC);
Dev_Serial2 = *(vu32*)(0x1FFFF7F0);

(三) 仿真调试

(1) 使用Keil uVision3 通过ULINK 2仿真器连接实验板,使用MINI-STM32 开发板附带的串口线,连接实验板上的 UART1 和 PC 机的串口,打开实验例程目录下的ADC.Uv2例程,编译链接工程;
(2) 在 PC 机上运行 windows 自带的超级终端串口通信程序(波特率115200、1位停止位、无校验位、无硬件流控制);或者使用其它串口通信程序;
(3) 点击MDK 的Debug菜单,点击Start/Stop Debug Session;
(4) 全速运行程序, 显示结果如下所示。

麦思网原创教程
www.mystm32.com/bbs
MINI-STM32  STM32F103RBT6 Unique Device ID:
066C0033 32353958 43195835

备注: 其中最后一行的数字就是我的 MINI-STM32 开发板中 STM32F103RBT6 的 Unique Device ID. 您的 ID 当然肯定会和我的不一样哦.

1.jpg

下载 (69.73 KB)

2009-8-23 20:16
22May/10

STM32 入门教程 工业现场总线 CAN

Posted by Nick Xu

(一) 工业现场总线 CAN 的基本介绍以及 STM32 的 CAN 模块简介

首先通读手册中关于CAN的文档,必须精读。
STM32F10xxx 参考手册Rev7V3.pdf
http://www.mystm32.com/bbs/redirect.php?tid=255&goto=lastpost#lastpost

需要精读的部分为 RCC 和 CAN 两个章节。
为什么需要精读 RCC 呢?因为我们将学习 CAN 的波特率的设置,将要使用到 RCC 部分的设置,因此推荐大家先复习下这部分中的几个时钟。

关于 STM32 的 can 总线简单介绍
bxCAN 是基本扩展 CAN (Basic Extended CAN) 的缩写,它支持 CAN 协议 2.0A 和 2.0B 。它的设计目标是,以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。
对于安全紧要的应用,bxCAN 提供所有支持时间触发通信模式所需的硬件功能。

主要特点
· 支持 CAN 协议 2.0A 和 2.0B 主动模式
· 波特率最高可达 1 兆位 / 秒
· 支持时间触发通信功能

发送
· 3 个发送邮箱
· 发送报文的优先级特性可软件配置
· 记录发送 SOF 时刻的时间戳

接收
· 3 级深度的2个接收 FIFO
· 14 个位宽可变的过滤器组 - 由整个 CAN 共享
· 标识符列表
· FIFO 溢出处理方式可配置
· 记录接收 SOF 时刻的时间戳

可支持时间触发通信模式
· 禁止自动重传模式
· 16 位自由运行定时器
· 定时器分辨率可配置
· 可在最后 2 个数据字节发送时间戳

管理
· 中断可屏蔽
· 邮箱占用单独 1 块地址空间,便于提高软件效率

(二) STM32 CAN 模块工作模式

STM32 的 can 的工作模式分为:
/* CAN operating mode */
#define CAN_Mode_Normal                   ((u8)0x00)  /* normal mode */
#define CAN_Mode_LoopBack               ((u8)0x01)  /* loopback mode */
#define CAN_Mode_Silent                     ((u8)0x02)  /* silent mode */
#define CAN_Mode_Silent_LoopBack     ((u8)0x03)  /* loopback combined with silent mode */

在此章我们的 Mini-STM32 教程中我们将使用到 CAN_Mode_LoopBackCAN_Mode_Normal 两种模式。
我们第一步做的就是使用运行在 CAN_Mode_LoopBack 下进行自测试。

在参考手册中 CAN_Mode_LoopBack (环回模式) 的定义如下:
环回模式可用于自测试。为了避免外部的影响,在环回模式下 CAN 内核忽略确认错误 (在数据 / 远程帧的确认位时刻,不检测是否有显性位) 。在环回模式下,bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CANRX 引脚的实际状态。发送的报文可以在 CANTX 引脚上检测到。
因此这种模式也特别适合大家做好硬件后自测程序。

2.jpg

下载 (37.38 KB)

2009-8-16 11:46

(三) CAN 接口端口映射

STM32 中的 CAN 物理引脚脚位可以设置成三种:

默认模式,重定义地址1模式,重定义地址2模式

2.jpg

下载 (28.94 KB)

2009-8-16 11:51

在我们的 Mini-STM32 上面没有接出 CAN 的接口芯片, 所以我们可以利用

RealView MDK

的 CAN 软件

模拟

模块来做实验.

-------------------------------------------------------------------------
默认模式

/* Configure CAN pin: RX */

GPIO

_InitStructure.GPIO_Pin = GPIO_Pin_11;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure CAN pin: TX */

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOA, &GPIO_InitStructure);

------------------------------------------------------------------------
重定义地址1模式

/* Configure CAN pin: RX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

//GPIO_Init(GPIOB, &GPIO_InitStructure);

/* Configure CAN pin: TX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

//GPIO_Init(GPIOB, &GPIO_InitStructure);

/* Configure CAN Remap  重影射 */

//GPIO_PinRemapConfig(GPIO_Remap1_CAN, ENABLE);

-------------------------------------------------------------------------
重定义地址2模式

/* Configure CAN pin: RX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

//GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Configure CAN pin: TX */

//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

//GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Configure CAN Remap  重影射 */

//GPIO_PinRemapConfig(GPIO_Remap2_CAN, ENABLE);

-------------------------------------------------------------------------

设置完 CAN 的引脚之后还需要打开 CAN 的时钟:

/* CAN Periph clock enable */

RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE);

(四) CAN 波特率设置

4、我们需要搞明白CAN波特率的设置,这个章节也是使用CAN的最重要的部分之一,因为这实际应用中我们需要根据我们实际的场合来选择 CAN 的波特率。

一般情况下面1M bps 的速率下可以最高可靠传输 40 米以内的距离。

在 50K 以下的波特率中一般可以可靠传输数公里远。

对于波特率的设置需要详细学习参考手册对应部分的解释。我们在调试软件的时候可以使用示波器来测试 CANTX 引脚上的波形的波特率,这样可以得到事半功倍的效果,大大的缩短调试学习的时间。

// ***************************************************************

//         BaudRate = 1 / NominalBitTime

//         NominalBitTime = 1tq + tBS1 + tBS2

//         tq = (BRP[9:0] + 1) x tPCLK

//         tPCLK = CAN's clock = APB1's clock

// ****************************************************************

也就是BaudRate = APB1 / ((BS1 + BS2 + 1) * Prescaler)

这里注意的是采用点的位置,也就时BS1,BS2的设置问题,这里我也找了一些资料,抄录下来给大家,是 CANopen 协议中推荐的设置。

1Mbps 速率下,采用点的位置在6tq位置处,BS1=5, BS2=2

500kbps 速率下,采用点的位置在8tq位置处,BS1=7, BS2=3

250kbps 速率下,采用点的位置在14tq位置处,BS1=13, BS2=2

125k, 100k, 50k, 20k, 10k 的采用点位置与 250K 相同。

因此我们需要重视的有软件中的这么几个部分:

// 设置 AHB 时钟(HCLK)

// RCC_SYSCLK_Div1  AHB 时钟 =  系统时钟

RCC_HCLKConfig(RCC_SYSCLK_Div8);

// 设置低速 AHB 时钟(PCLK1)

// RCC_HCLK_Div2  APB1 时钟  = HCLK / 2

RCC_PCLK1Config(RCC_HCLK_Div2);

// PLLCLK = 8MHz * 8 = 64 MHz

// 设置 PLL 时钟源及倍频系数

RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_8);

CAN 波特率设置中需要的就是PCLK1 的时钟。

CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;

CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;

CAN_InitStructure.CAN_BS1=CAN_BS1_8tq;

CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;

CAN_InitStructure.CAN_Prescaler=5;

通过上面部分的时钟设置我们已经可以算出我们的波特率了

CAN_bps = PCLK1 / ((1 + 7 + 8) * 5) = 25K bps

大家也可以实际测试中修改时钟值来通过示波器测试我们需要的波特率是否正确例如将PLLCLK 设置降低一半:

// PLLCLK = 8MHz * 4 = 32 MHz

// 设置 PLL 时钟源及倍频系数

RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4);

那么我们得到的CAN_bps也会降低一半。

接下来还可以修改 HCLK 和 PCLK1 ,其实最终这几个分频和倍频值最终影响的都是 PCLK1。

通过几次试验,相信大家应该很容易掌握波特率的设置了。

设置完波特率我们直接测试函数:

/* CAN transmit at 100Kb/s and receive by polling in loopback mode*/

TestRx = CAN_Polling();

if (TestRx == FAILED)

{

/* Turn on led connected to PA.00 pin (LD1) */

GPIO_SetBits(GPIOA, GPIO_Pin_0);

}

else

{

/* Turn off led connected to PA.00 pin (LD1) */

GPIO_ResetBits(GPIOA, GPIO_Pin_0);

}

/* CAN transmit at 500Kb/s and receive by interrupt in loopback mode*/

TestRx = CAN_Interrupt();

if (TestRx == FAILED)

{

/* Turn on led connected to PA.01 pin (LD2) */

GPIO_SetBits(GPIOA, GPIO_Pin_1);

}

else

{

/* Turn off led connected to PA.01 pin (LD2) */

GPIO_ResetBits(GPIOA, GPIO_Pin_1);

}

CAN 软件仿真模拟器

调用出来.

4.JPG

下载 (61.84 KB)

2009-8-16 00:27

大家可以仿真程序,当程序中 Test 等于 Passed 那么说明 Loopback 模式测试通过了。

并且在 CAN 通讯框中我们可以看到发送和接收到的数据:

5.jpg

下载 (51.48 KB)

2009-8-16 12:27

回循模式下的源代码, 基于 MDK3.5:
Example7.1-CAN LoopBack Mode.rar

(493.79 KB)

下载次数: 83
阅读权限: 10

2009-8-16 12:35

到此时说明如果大家只有一块CAN模块的时候学习可以告一个段落了,不过这个并不代表大家就已经掌握了 CAN 了,正真要掌握它,大家还是需要看大量的 CAN 部分的资料,参考手册部分的也是不够的,市面上有几本专门介绍现场总线和CAN总线的书,推荐大家买来经常翻翻看看,这样到需要实际应用的时候才可以做到 如鱼得水。

(五) 正常模式

完成了 loopback 模式的测试之后接下来我们需要学习的就是多机通讯了,当然由于我们的 Mini-STM32 没有将 CAN 接口引出来, 所以我们没有办法在板子上面做这部分的试验了,只能在 RealView MDK 的软件中进行模拟。

如果您拥有两块带 CAN 硬件的 STM32 的板子,您需要自己构建硬件的物理层的连接, 使用三根线将 CANH,CANL,GND 三根线直连,当然你要接好终端电阻才能保证通讯的正常通讯,当两块板子都跳好后我们使用万用表测量下 CANH和CANL之间的电阻是否为 60 欧姆。多块板子多机通讯的是否你只需要在总线的主机端和最后一端接上终端电阻就可以了.

3.jpg

下载 (24.18 KB)

2009-8-15 23:11

在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文。软件可以通过对 CAN_MCR 寄存器的INRQ位清 '0',来请求从初始化模式进入正常模式,然后要等待硬件对 CAN_MSR 寄存器的 INAK 位置 '1' 的确认。在跟 CAN 总线取得同步,即在 CANRX 引脚上监测到 11 个连续的隐性位 (等效于总线空闲) 后,bxCAN 才能正常接收和发送报文。

不需要在初始化模式下进行过滤器初值的设置,但必须在它处在非激活状态下完成 (相应的 FACT 位为 '0' ) 。而过滤器的位宽和模式的设置,则必须在初始化模式中进入正常模式前完成。

准备工作做完我们需要设置 CAN 通讯部份软件。

我们把 TestStatus CAN_Polling(void) 函数和 TestStatus CAN_Interrupt(void) 函数中的 LoopBack 模式修改为 Normal 模式.

//CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;

CAN_InitStructure.CAN_Mode=CAN_Mode_Normal;

接下来我们就可以做实验了. 但是由于 RealView MDK 的 CAN 模块没有办法接收, 所以我们只做发送的例子.

我们的例子中分别发送两帧数据:
(1) TestStatus CAN_Polling(void) 查询发送

第一帧数据为:  ID 为 0x11, 数据为 8 个字节的一个数据包.

TxMessage.StdId=0x11;

TxMessage.RTR=CAN_RTR_DATA;

TxMessage.IDE=CAN_ID_STD;

TxMessage.DLC=8;

TxMessage.Data[0]=0x01;

TxMessage.Data[1]=0x02;

TxMessage.Data[2]=0x03;

TxMessage.Data[3]=0x04;

TxMessage.Data[4]=0x05;

TxMessage.Data[5]=0x06;

TxMessage.Data[6]=0x07;

TxMessage.Data[7]=0x08;

(2) TestStatus CAN_Interrupt(void) 中断发送

第二帧数据为:ID 为 0x1234, 数据为 8 个字节的一个数据包.

TxMessage.StdId=0x12;

TxMessage.ExtId=0x34;

TxMessage.IDE=CAN_ID_EXT;

TxMessage.RTR=CAN_RTR_DATA;

TxMessage.DLC=8;

TxMessage.Data[0]=0x11;

TxMessage.Data[1]=0x22;

TxMessage.Data[2]=0x33;

TxMessage.Data[3]=0x44;

TxMessage.Data[4]=0x55;

TxMessage.Data[5]=0x66;

TxMessage.Data[6]=0x77;

TxMessage.Data[7]=0x88;

CAN_Transmit(&TxMessage);

在主函数中初始化之后加上这两句发送函数:

/* CAN transmit at 100Kb/s and receive by polling in Normal mode*/

CAN_Polling();

while(i++ < 1000);

/* CAN transmit at 500Kb/s and receive by interrupt in Normal mode*/

CAN_Interrupt();

程序改完了, 我们需要编译通过后, 点软件仿真.

CAN 软件仿真模拟器

调用出来.

接下来我们全速运行到 while(1) 就可以看到结果了.

1.jpg

下载 (87.37 KB)

2009-8-16 00:24
21May/10

mozilla firefox 插件开发 快速入门

Posted by Nick Xu

如果想讓你的 binary 軟体元件在瀏覽器上能夠執行, 你需要實做一些規定的介面. 這樣的程式就能像 Flash 一樣, 嵌入在網頁中, 讓使用者使用. 然而不同的瀏覽器要實做的介面是不一樣的.

若你希望在 IE 上要能執行, 則你的元件必須實做 ActiveX 的介面

若你希望在 Firefox 或 Opera 甚至是 Google 的 Chrome 瀏覽器上也能執行你的程式,  那麼你的元件必須實做 NPAPI 介面.

這一切都要怪微軟是屬於封閉架構, 這使得一些跨平台的軟体開發組織, 不願意實做 ActiveX. 所以如果你要讓 firefox 或新的 Goolge 瀏覽器 Chrome 上面寫 plugin, 你能用的技術是 NPAPI.

這份文件內容包含

       1. 如何選擇正確的方式, 撰寫 scriptable plugins

       2. 如何下載正確的 NPAPI 範例

       3. 如何在 Visual Studio .Net IDE 下, 編譯範例

       4. 如何測試你的 plugin

------------------------------------------------------------------

選擇正確的方式, 撰寫 scriptable plugins

我想你應該知道 Plugins 與 Extensions  是不一樣的, 你想知道 Firefox Plugin 的最新發展, 應該到

http://developer.mozilla.org/en/Plugins

你可以用 plugin 多媒體應用程式, 例如監控系統, 人物自動追蹤 等應用,  全部都可以利用 NPAPI 這個介面讓你的應用程式網頁化.

官方網頁裡面詳細的告訴你,  NPAPI plugins 可以利用 java script 進行操控, 而舊的技術 XPCOMLiveConnect 已經不適合用來開發 NPAPI plugins 了.

要讓 plugins 能被 script 操控, 你應該使用 npruntime

網址: http://developer.mozilla.org/en/Gecko_Plugin_API_Reference/Scripting_plugins

 

有圖有真相

寫程式也是一樣, 與其看一堆文件, 先給我一個能執行的範例. 再談後面的優秀架構與API 文件.

所以呢 ...

有程式還要能 work 才有真相! 你看看, 菜不就端上來了嗎 ....

我知道你在想什麼, 下面的範例支援 Firefox 3.0.

如何寫程式?

Step 1: 下載 Gecko_SDK: xurlrunner

網址: http://developer.mozilla.org/en/Gecko_SDK
* 我下載的是 Gecko 1.9 (Firefox 3.0) 版本

Step 2: 下載範例程式

網址:  http://mxr.mozilla.org/seamonkey/source/modules/plugin/samples/

點選 npruntime 範例

每個檔案都有 Raw file 可以讓你下載, 把所有的檔案下載回去吧!

snap003

注意: 你會在 Samples and Test Cases 發現, 範例程式有兩個載點, 其中第二個mozilla/modules/plugin/tools/sdk/samples 裡面, scriptable 使用的是舊的技術 XPCOM, 請不要使用.  否則你編出來的 dll 在 Firefox 3.0 會無法執行.

Step 3: 建立一個簡單 Visual Studio 專案, 把剛剛抓到的程式放進去.

mozilla 官方網頁有教學: 你可以去看一下 (link)

下面是我寫的簡單修正中文版 (別擔心, 這些流程都很簡單)

----------------------------------------

1. 建立新的專案: project 名稱設定為 nprt

VC 的操作:  New Project -> Vistual C++ -> Win32 Project 
  

2. Application Settings 選 DLL 並且設定為 Empty project

3. 把範例程式加入專案中
  (a) 把從 http://mxr.mozilla.org/seamonkey/source/modules/plugin/samples/
      下載回來的所有檔案 copy 到 nprt/nprt 目錄中
  (b) 加入 nprt 專案中

4. 解開 xulrunner-sdk:  放在 C:xulrunner-sdk
網址:

http://developer.mozilla.org/en/docs/Gecko_SDK
  

5. 設定 Include Path

     VC 的操作: C/C++ -> Additional Include Directories
    "C:xulrunner-sdkinclude";"C:xulrunner-sdkincludeplugin";"C:xulrunner-sdkincludenspr";"C:xulrunner-sdkincludejava"

6. 直接設定下面的定義
     VC 的操作: C/C++ -> Preprocessor -> Preprocessor Definitions
   WIN32;_WINDOWS;XP_WIN32;MOZILLA_STRICT_API;XPCOM_GLUE;XP_WIN;_X86_;NPSIMPLE_EXPORTS
   _DEBUG
   

7. 關掉 precompiled 選項 (如果你剛剛選的是 Empty Project, 則 precompiled 選項應該已經關閉)
  VC 的操作: C/C++ -> Precompiled Headers -> Create/Use Precompiled Header: 設定為 Not Using Precompiled Headers

8. 設定 Module Definition File: nprt.def
  VC 的操作: Linker -> Input -> Module Definition File:

9. 把 plugin.cpp 的 DrawText 改成 DrawTextA

10. 修改  plugin.cpp 裡面的 Invoke method 改成下面這樣,

否則當 firefox 呼叫你的 plugin 時, 會 當掉.

------------------------------------------------------------------

bool ScriptablePluginObject::Invoke(NPIdentifier name, const NPVariant *args,
                               uint32_t argCount, NPVariant *result) {
  if (name == sFoo_id) {
    printf ("foo called!n");
    MessageBox(NULL,L"foo 被呼叫 ",L"Java Script 呼叫範例",MB_OK);
    
    return PR_TRUE;
  }

  return PR_FALSE;
}

------------------------------------------------------------------

11. 修改 npp_gate.cpp , 把 _GetJavaClass  拿掉

------------------------------------------------------------------

/*  加入註解 (感謝網友 Chui-Wen Chiu 提醒)

jref NPP_GetJavaClass (void)
{
return NULL;
}

*/

------------------------------------------------------------------

編譯應該會通過, 產生 nprt.dll

----------------------------------------

測試:

     Step 1: 把 nprt.dll 放到 firefox 的 plugins 目錄底下

     Step 2: 開啟 firefox 在網址輸入

                    about:plugins

                   看看你的 plugins 是否在裡面.

                   長相應該是這樣.

snap003

      Step 3: 執行 測試 test.html

       注意 1: 你下載的 test.html 已經嚴重過期了. 所以我的作法是自己寫一個

       test2.html

文章的例子针对的是Firefox 3.0(貌似用的XULRunner 1.9.0),我电脑上装的Firefox版本为3.5.5,开发用的XULRunner版本为1.9.1.4,发现代码有一些问题,如下所示:

1、npupp.h被npfunctions.h取代了,所以要将npupp.h替换成npfunctions.h。
2、 编译是会提示int16,int32未被定义。首先包含nptypes.h头文件,打开该头文件,发现其中使用了typedef重定义几个简单类型,比如 int16_t,int32_t等。将源代码中所有的int16,int32都替换成int16_t,int32_t。
3、NP_TRUE/NP_FALSE未被定义,直接将其替换成true/false便可。
       修改源代码时不要出现中文,否则编译器会死掉。做到这几点之后编译工程就可以正常生产DLL文件,将nprt.dll拷贝到Firefox安装目录的plugins子目录下,在Firefox的地址栏输入"about:plugins"就可以看到插件了。
       文章中提到Mozilla官方提供的测试页面严重过期,我测试了下还基本正常吧,只不过我下不到作者提供的测试页面,也不知道作者改成什么样子了。测试页 面的代码偏多,它使用embed来嵌入相应类型的东东来调用插件,不知道为什么Firefox依旧显示缺失插件,插件中生成的窗口并没有正确地显示出来, 不过别的部分都是正常的,可以调用foo函数,查看bar属性。
       这个nprt.dll使用nprt.def只向外暴露三个接口,分别是NP_GetEntryPoints、NP_Initialize和 NP_Shutdown,这应该是所有NPAPI插件的规范吧,我是用dumpbin看了看淘宝的Chrome NPAPI插件中npwangwang.dll导出的接口,发现也只有这三个。dll中实现了许多NPP函数,大量的NPN函数都是直接调用NPP函数。 我在代码中加入了许多MessageBox语句以跟踪程序的运行,发现有点头晕。首先NPP_NEW函数会被调用,然后它会生成一个CPlugin对象, 而CPlugin构造函数中又会生成ScriptablePluginObject对象,当关闭测试页面时,相应的对象会被析构,不过有时候有点乱。在脚 本中调用foo函数,会触发ScriptablePluginObject::invoke函数运行,invoke函数内部根据名字实现foo对应的操 作。
bool
ScriptablePluginObject::HasMethod(NPIdentifier name)
{
  return name == sFoo_id;
}
 
bool
ScriptablePluginObject::HasProperty(NPIdentifier name)
{
  return (name == sBar_id ||
          name == sPluginType_id);
}
       这两个函数返回对应的拥有的方法和属性。每次调用方法访问属性时都会调用这两个方法判断相应的方法和属性是否存在。下面给出一个最简单的测试页面,点一个按钮可以调用foo方法,点第二个按钮可以获取bar属性。
<HTML>
<HEAD>
<TITLE>Scriptable Plug-in Test</TITLE>
</HEAD>
<BODY id="bodyId">
 
<object id="pluginobj" type="application/mozilla-npruntime-scriptable-plugin"></object>
 
<br>
<form name="formname">
<input type=button value="Call pluginobj.foo()" onclick='alert(pluginobj.foo())'>
<input type=button value="Get pluginobj.bar" onclick='alert(pluginobj.bar)'>
</form>
 
</BODY>
</HTML>
       呵呵,现在知道怎么做NPAPI插件了,可以为Chrome写一个插件,在右上角的扩展图标栏以一个图标的方式显示电池剩余使用时间,有空的时候就尝试下吧 ^_^
   
site
site