基于反相代理的Web缓存加速——可缓存的CMS系统设计

本文探讨了网站缓存优化策略,包括静态与动态缓存的区别、反向代理的应用、缓存服务器配置及页面设计原则,旨在大幅提升网站访问速度。

内容摘要:
对于一个日访问量达到百万级的网站来说,速度很快就成为一个瓶颈。除了优化内容发布系统的应用本身外,如果能把不需要实时更新的动态页面的输出结果转化成静态网页来发布,速度上的提升效果将是显著的,因为一个动态页面的速度往往会比静态页面慢2-10倍,而静态网页的内容如果能被缓存在内存里,访问速度甚至会比原有动态网页有2-3个数量级的提高

后台的内容管理系统的页面输出遵守可缓存的设计,这样就可以把性能问题交给前台的缓存服务器来解决了,从而大大简化CMS系统本身的复杂程度。

静态缓存和动态缓存的比较

静态页面的缓存可能有2种形式:其实主要区别就是CMS是否自己负责关联内容的缓存更新管理。

  1. 静态缓存:是在新内容发布的同时就立刻生成相应内容的静态页面,比如:2003年3月22日,管理员通过后台内容管理界面录入一篇文章后,就立刻生成http://www.chedong.com/tech/2003/03/22/001.html这个静态页面,并同步更新相关索引页上的链接。
  2. 动态缓存:是在新内容发布以后,并不预先生成相应的静态页面,直到对相应内容发出请求时,如果前台缓存服务器找不到相应缓存,就向后台内容管理服务器发出请求,后台系统会生成相应内容的静态页面,用户第一次访问页面时可能会慢一点,但是以后就是直接访问缓存了。

    如果去ZDNet等国外网站会发现他们使用的基于
    Vignette内容管理系统都有这样的页面名称:0,22342566,300458.html。其实这里的0,22342566,300458就是用逗号分割开的多个参数:
    第一次访问找不到页面后,相当于会在服务器端产生一个doc_type= 0&doc_id=22342566&doc_template=300458的查询,
    而查询结果会生成的缓存的静态页面: 0,22342566,300458.html

静态缓存的缺点:

  • 复杂的触发更新机制:这两种机制在内容管理系统比较简单的时候都是非常适用的。但对于一个关系比较复杂的网站来说,页面之间的逻辑引用关系就成为一个非常非常复杂的问题。最典型的例子就是一条新闻要同时出现在新闻首页和相关的3个新闻专题中,在静态缓存模式中,每发一篇新文章,除了这篇新闻内容本身的页面外,还需要系统通过触发器生成多个新的相关静态页面,这些相关逻辑的触发也往往就会成为内容管理系统中最复杂的部分之一。
  • 旧内容的批量更新: 通过静态缓存发布的内容,对于以前生成的静态页面的内容很难修改,这样用户访问旧页面时,新的模板根本无法生效。

在动态缓存模式中,每个动态页面只需要关心,而相关的其他页面能自动更新,从而大大减少了设计相关页面更新触发器的需要。

以前做小型应用的时候也用过类似方式:应用首次访问以后将数据库的查询结果在本地存成一个文件,下次请求时先检查本地缓存目录中是否有缓存文件,从而减少对后台数据库的访问。虽然这样做也能承载比较大的负载,但这样的内容管理和缓存管理一体的系统是很难分离的,而且数据完整性也不是很好保存,内容更新时,应用需要把相应内容的的缓存文件删除。但是这样的设计在缓存文件很多的时候往往还需要将缓存目录做一定的分布,否则一个目录下的文件节点超过3000,rm *都会出错。

这时候,系统需要再次分工,把复杂的内容管理系统分解成:内容输入和缓存这2个相对简单的系统实现。

  • 后台:内容管理系统,专心的将内容发布做好,比如:复杂的工作流管理,复杂的模板规则等……
  • 前台:页面的缓存管理则可以使用缓存系统实现
______________________             ___________________
|Squid Software cache| |F5 Hardware cache|
---------------------- -------------------
/ /
/ ________________ /
|ASP |JSP |PHP |
Content Manage System
----------------

所以分工后:内容管理和缓存管理2者,无论哪一方面可选的余地都是非常大的:软件(比如前台80端口使用SQUID对后台8080的内容发布管理系统进行缓存),缓存硬件,甚至交给akamai这样的专业服务商。

面向缓存的站点规划

一个利用SQUID对多个站点进行做WEB加速http acceleration方案:
原先一个站点的规划可能是这样的:
200.200.200.207 www.chedong.com
200.200.200.208 news.chedong.com
200.200.200.209 bbs.chedong.com
200.200.200.205 images.chedong.com
面向缓存服务器的设计中:所有站点都通过外部DNS指向到同一个IP:200.200.200.200/201这2台缓存服务器上(使用2台是为了冗余备份)
                          _____________________   ________
www.chedong.com 请求 / | cache box | | | / 192.168.0.4 www.chedong.com
news.chedong.com 请求 -| 200.200.200.200/201 |-|firewall| - 192.168.0.4 news.chedong.com
bbs.chedong.com 请求 / | /etc/hosts | | box | / 192.168.0.3 bbs.chedong.com
--------------------- --------
工作原理:
外部请求过来时,设置缓存根据配置文件进行转向解析。这样,服务器请求就可以转发到我们指定的内部地址上。
在处理多虚拟主机转向方面:mod_proxy比squid要简单一些:可以把不同服务转向后后台多个IP的不同端口上。
而squid只能通过禁用DNS解析,然后根据本地的/etc/hosts文件根据请求的域名进行地址转发,后台多个服务器必须使用相同的端口。
使用反向代理加速,我们不仅可以得到性能上的提升,而且还能获得额外的安全性和配置的灵活度:
  • 配置灵活性提高:可以自己在内部服务器上控制后台服务器的DNS解析,当需要在服务器之间做迁移调整时,就不用大量修改外部DNS配置了,只需要修改内部DNS实现服务的调整。
  • 数据安全性增加:所有后台服务器可以很方便的被保护在防火墙内。
  • 后台应用设计复杂程度降低:原先为了效率常常需要建立专门的图片服务器images.chedong.com和负载比较高的应用服务器 bbs.chedong.com分离,在反向代理加速模式中,所有前台请求都通过缓存服务器:实际上就都是静态页面,这样,应用设计时就不用考虑图片和应用本身分离了,也大大降低了后台内容发布系统设计的复杂程度,由于数据和应用都存放在一起,也方便了文件系统的维护和管理。

基于Apache mod_proxy的反向代理缓存加速实现

Apache包含了mod_proxy模块,可以用来实现代理服务器,针对后台服务器的反向加速
安装apache 1.3.x 编译时:
--enable-shared=max --enable-module=most
注:Apache 2.x中mod_proxy已经被分离成mod_proxy和mod_cache:同时mod_cache有基于文件和基于内存的不同实现
创建/var/www/proxy,设置apache服务所用户可写
mod_proxy配置样例:反相代理缓存+缓存
架设前台的www.example.com反向代理后台的www.backend.com的8080端口服务。
修改:httpd.conf
<VirtualHost *>
ServerName www.example.com
ServerAdmin admin@example.com
# reverse proxy setting
ProxyPass / http://www.backend.com:8080/
ProxyPassReverse / http://www.backend.com:8080/
# cache dir root
CacheRoot "/var/www/proxy"
# max cache storage
CacheSize 50000000
# hour: every 4 hour
CacheGcInterval 4
# max page expire time: hour
CacheMaxExpire 240
# Expire time = (now - last_modified) * CacheLastModifiedFactor
CacheLastModifiedFactor 0.1
# defalt expire tag: hour
CacheDefaultExpire 1
# force complete after precent of content retrived: 60-90%
CacheForceCompletion 80
CustomLog /usr/local/apache/logs/dev_access_log combined
</VirtualHost>

基于Squid的反向代理加速实现

Squid是一个更专用的代理服务器,性能和效率会比Apache的mod_proxy高很多。
如果需要combined格式日志补丁:
http://www.squid-cache.org/mail-archive/squid-dev/200301/0164.html
squid的编译:
./configure --enable-useragent-log  --enable-referer-log --enable-default-err-language=Simplify_Chinese / --enable-err-languages="Simplify_Chinese English" --disable-internal-dns 
make
#make install
#cd /usr/local/squid
make dir cache
chown squid.squid *
vi /usr/local/squid/etc/squid.conf
在/etc/hosts中:加入内部的DNS解析,比如:
192.168.0.4 www.chedong.com
192.168.0.4 news.chedong.com
192.168.0.3 bbs.chedong.com
---------------------cut here----------------------------------
# visible name
visible_hostname cache.example.com
# cache config: space use 1G and memory use 256M
cache_dir ufs /usr/local/squid/cache 1024 16 256
cache_mem 256 MB
cache_effective_user squid
cache_effective_group squid

http_port 80
httpd_accel_host virtual
httpd_accel_single_host off
httpd_accel_port 80
httpd_accel_uses_host_header on
httpd_accel_with_proxy on
# accelerater my domain only
acl acceleratedHostA dstdomain .example1.com
acl acceleratedHostB dstdomain .example2.com
acl acceleratedHostC dstdomain .example3.com
# accelerater http protocol on port 80
acl acceleratedProtocol protocol HTTP
acl acceleratedPort port 80
# access arc
acl all src 0.0.0.0/0.0.0.0
# Allow requests when they are to the accelerated machine AND to the
# right port with right protocol
http_access allow acceleratedProtocol acceleratedPort acceleratedHostA
http_access allow acceleratedProtocol acceleratedPort acceleratedHostB
http_access allow acceleratedProtocol acceleratedPort acceleratedHostC
# logging
emulate_httpd_log on
cache_store_log none
# manager
acl manager proto cache_object
http_access allow manager all
cachemgr_passwd pass all

----------------------cut here---------------------------------
创建缓存目录:
/usr/local/squid/sbin/squid -z
启动squid
/usr/local/squid/sbin/squid
停止squid:
/usr/local/squid/sbin/squid -k shutdown
启用新配置:
/usr/local/squid/sbin/squid -k reconfig
通过crontab每天0点截断/轮循日志:
0 0 * * * (/usr/local/squid/sbin/squid -k rotate)

可缓存的动态页面设计

什么样的页面能够比较好的被缓存服务器缓存呢?如果返回内容的HTTP HEADER中有"Last-Modified"和"Expires"相关声明,比如:
Last-Modified: Wed, 14 May 2003 13:06:17 GMT
Expires: Fri, 16 Jun 2003 13:06:17 GMT
前端缓存服务器在期间会将生成的页面缓存在本地:硬盘或者内存中,直至上述页面过期。
因此,一个可缓存的页面:
  • 页面必须包含Last-Modified: 标记
    一般纯静态页面本身都会有Last-Modified信息,动态页面需要通过函数强制加上,比如在PHP中:
    // always modified now
    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  • 必须有Expires或Cache-Control: max-age标记设置页面的过期时间:
    对于静态页面,通过apache的mod_expires根据页面的MIME类型设置缓存周期:比如图片缺省是1个月,HTML页面缺省是2天等。
    <IfModule mod_expires.c>
        ExpiresActive on
        ExpiresByType image/gif "access plus 1 month"
        ExpiresByType text/css "now plus 2 day"
        ExpiresDefault "now plus 1 day"
    </IfModule>

    对于动态页面,则可以直接通过写入HTTP返回的头信息,比如对于新闻首页index.php可以是20分钟,而对于具体的一条新闻页面可能是1天后过期。比如:在php中加入了1个月后过期:
    // Expires one month later
    header("Expires: " .gmdate ("D, d M Y H:i:s", time() + 3600 * 24 * 30). " GMT");
  • 如果服务器端有基于HTTP的认证,必须有Cache-Control: public标记,允许前台
ASP应用的缓存改造 首先在公用的包含文件中(比如include.asp)加入以下公用函数:
<%
' Set Expires Header in minutes
Function SetExpiresHeader(ByVal minutes)
    ' set Page Last-Modified Header:
    ' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT)
    Response.AddHeader "Last-Modified", DateToHTTPDate(Now())
   
    ' The Page Expires in Minutes
    Response.Expires = minutes
   
    ' Set cache control to externel applications
    Response.CacheControl = "public"
End Function
' Converts date (19991022 11:08:38) to http form (Fri, 22 Oct 1999 12:08:38 GMT)
Function DateToHTTPDate(ByVal OleDATE)
  Const GMTdiff = #08:00:00#
  OleDATE = OleDATE - GMTdiff
  DateToHTTPDate = engWeekDayName(OleDATE) & _
    ", " & Right("0" & Day(OleDATE),2) & " " & engMonthName(OleDATE) & _
    " " & Year(OleDATE) & " " & Right("0" & Hour(OleDATE),2) & _
    ":" & Right("0" & Minute(OleDATE),2) & ":" & Right("0" & Second(OleDATE),2) & " GMT"
End Function
Function engWeekDayName(dt)
    Dim Out
    Select Case WeekDay(dt,1)
        Case 1:Out="Sun"
        Case 2:Out="Mon"
        Case 3:Out="Tue"
        Case 4:Out="Wed"
        Case 5:Out="Thu"
        Case 6:Out="Fri"
        Case 7:Out="Sat"
    End Select
    engWeekDayName = Out
End Function
Function engMonthName(dt)
    Dim Out
    Select Case Month(dt)
        Case 1:Out="Jan"
        Case 2:Out="Feb"
        Case 3:Out="Mar"
        Case 4:Out="Apr"
        Case 5:Out="May"
        Case 6:Out="Jun"
        Case 7:Out="Jul"
        Case 8:Out="Aug"
        Case 9:Out="Sep"
        Case 10:Out="Oct"
        Case 11:Out="Nov"
        Case 12:Out="Dec"
    End Select
    engMonthName = Out
End Function
%>
然后在具体的页面中,比如index.asp和news.asp的“最上面”加入以下代码:HTTP Header
<!--#include file="../include.asp"-->
<%
'页面将被设置20分钟后过期
SetExpiresHeader(20)
%>

应用的缓存兼容性设计


经过代理以后,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。但是在转发请求的HTTD头信息中,增加了HTTP_X_FORWARDED_????信息。用以跟踪原有的客户端IP地址和原来客户端请求的服务器地址:
下面是2个例子,用于说明缓存兼容性应用的设计原则:
    '对于一个需要服务器名的地址的ASP应用:不要直接引用HTTP_HOST/SERVER_NAME,判断一下是否有HTTP_X_FORWARDED_SERVER
function getHostName ()
dim hostName as String = ""
hostName = Request.ServerVariables("HTTP_HOST")
if not isDBNull(Request.ServerVariables("HTTP_X_FORWARDED_HOST")) then
if len(trim(Request.ServerVariables("HTTP_X_FORWARDED_HOST"))) > 0 then
hostName = Request.ServerVariables("HTTP_X_FORWARDED_HOST")
end if
end if
return hostNmae
end function

//对于一个需要记录客户端IP的PHP应用:不要直接引用REMOTE_ADDR,而是要使用HTTP_X_FORWARDED_FOR,
function getUserIP (){
$user_ip = $_SERVER["REMOTE_ADDR"];
if ($_SERVER["HTTP_X_FORWARDED_FOR"]) {
$user_ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
}

注意:HTTP_X_FORWARDED_FOR如果经过了多个中间代理服务器,有何能是逗号分割的多个地址,
比如:200.28.7.155,200.10.225.77 unknown,219.101.137.3
因此在很多旧的数据库设计中(比如BBS)往往用来记录客户端地址的字段被设置成20个字节就显得过小了。
经常见到类似以下的错误信息:

Microsoft JET Database Engine 错误 '80040e57'

字段太小而不能接受所要添加的数据的数量。试着插入或粘贴较少的数据。

/inc/char.asp,行236

原因就是在设计客户端访问地址时,相关用户IP字段大小最好要设计到50个字节以上,当然经过3层以上代理的几率也非常小。
如何检查目前站点页面的可缓存性(Cacheablility)呢?可以参考以下2个站点上的工具:
http://www.ircache.net/cgi-bin/cacheability.py

附:SQUID性能测试试验


phpMan.php是一个基于php的man page server,每个man
page需要调用后台的man命令和很多页面格式化工具,系统负载比较高,提供了Cache
Friendly的URL,以下是针对同样的页面的性能测试资料:
测试环境:Redhat 8 on Cyrix 266 / 192M Mem
测试程序:使用apache的ab(apache benchmark):
测试条件:请求50次,并发50个连接
测试项目:直接通过apache 1.3 (80端口) vs squid 2.5(8000端口:加速80端口)

测试1:无CACHE的80端口动态输出:
ab -n 100 -c 10 http://www.chedong.com:81/phpMan.php/man/kill/1
This is ApacheBench, Version 1.3d <$Revision: 1.2 $> apache-1.3
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,
http://www.zeustech.net/
Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/

Benchmarking localhost (be patient).....done
Server Software:       
Apache/1.3.23                                     
Server Hostname:        localhost
Server
Port:           
80

Document Path:         
/phpMan.php/man/kill/1
Document Length:        4655 bytes

Concurrency Level:      5
Time taken for tests:   63.164 seconds
Complete requests:      50
Failed requests:        0
Broken pipe errors:     0
Total transferred:      245900 bytes
HTML transferred:       232750 bytes
Requests per second:    0.79 [#/sec] (mean)
Time per request:       6316.40 [ms]
(mean)
Time per request:       1263.28 [ms]
(mean, across all concurrent requests)
Transfer rate:         
3.89 [Kbytes/sec] received

Connnection Times (ms)
             
min  mean[+/-sd] median   max
Connect:        0   
29  106.1      0   553
Processing:  2942  6016
1845.4   6227 10796


Waiting:    
2941  5999 1850.7   6226 10795


Total:      
2942  6045 1825.9   6227 10796


Percentage of the requests served within a certain time (ms)
  50%   6227
  66%   7069
  75%   7190
  80%   7474
  90%   8195
  95%   8898
  98%   9721
  99%  10796
 100%  10796 (last request)

测试2:SQUID缓存输出
/home/apache/bin/ab -n50 -c5
"http://localhost:8000/phpMan.php/man/kill/1"
This is ApacheBench, Version 1.3d <$Revision: 1.2 $> apache-1.3
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,
http://www.zeustech.net/
Copyright (c) 1998-2001 The Apache Group, http://www.apache.org/

Benchmarking localhost (be patient).....done
Server Software:       
Apache/1.3.23                                     
Server Hostname:        localhost
Server
Port:           
8000

Document Path:         
/phpMan.php/man/kill/1
Document Length:        4655 bytes

Concurrency Level:      5
Time taken for tests:   4.265 seconds
Complete requests:      50
Failed requests:        0
Broken pipe errors:     0
Total transferred:      248043 bytes
HTML transferred:       232750 bytes
Requests per second:    11.72 [#/sec] (mean)
Time per request:       426.50 [ms] (mean)
Time per request:       85.30 [ms] (mean,
across all concurrent requests)
Transfer rate:         
58.16 [Kbytes/sec] received

Connnection Times (ms)
             
min  mean[+/-sd] median   max
Connect:       
0     1   
9.5      0    68
Processing:    
7    83  537.4     
7  3808


Waiting:       
5    81  529.1     
6  3748


Total:         
7    84  547.0     
7  3876


Percentage of the requests served within a certain time (ms)
  50%      7
  66%      7
  75%      7
  80%      7
  90%      7
  95%      7
  98%      8
  99%   3876
 100%   3876 (last request)

结论:No Cache / Cache = 6045 / 84 = 70
结论:对于可能被缓存请求的页面,服务器速度可以有2个数量级的提高,因为SQUID是把缓存页面放在内存里的(因此几乎没有硬盘I/O操作)。

小节:

  • 大访问量的网站应尽可能将动态网页生成静态页面作为缓存发布,甚至对于搜索引擎这样的动态应用来说,缓存机制也是非常非常重要的。

  • 在动态页面中利用HTTP Header定义缓存更新策略。

  • 利用缓存服务器获得额外的配置和安全性

  • 日志非常重要:SQUID日志缺省不支持COMBINED日志,但对于需要REFERER日志的这个补丁非常重要:http://www.squid-cache.org/mail-archive/squid-dev/200301/0164.html


参考资料:
HTTP代理缓存
http://vancouver-webpages.com/proxy.html

 


可缓存的页面设计
http://linux.oreillynet.com/pub/a/linux/2002/02/28/cachefriendly.html
运用ASP.NET的输出缓冲来存储动态页面 -  开发者 - ZDNet China
http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39110239-2,00.htm
相关RFC文档:

可缓存性检查
http://www.web-caching.com/cacheability.html
缓存设计要素
http://vancouver-webpages.com/CacheNow/detail.html

// Device: FT61F14X #include "SYSCFG.h" //Variable definition volatile char W_TMP @ 0x70 ;//系统占用不可以删除和修改 volatile char BSR_TMP @ 0x71 ;//系统占用不可以删除和修改 void interrupt ISR(void) { #asm;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 NOP;//系统设置不可以删除和修改 #endasm;//系统设置不可以删除和修改 //user_isr(); //调用用户中断函数 } /*------------------------------------------------- * 函数名:POWER_INITIAL * 功能: 上电系统初始化 * 输入: 无 * 输出: 无 --------------------------------------------------*/ void POWER_INITIAL(void) { OSCCON = 0B01110001; //16MHz 1:1 INTCON = 0; PORTA = 0B00000000; TRISA = 0B00000000; //PA输入输出 0-输出 1-输入 PA0输出 PORTB = 0B00000000; TRISB = 0B00000000; //PB输入输出 0-输出 1-输入 PORTC = 0B00000000; TRISC = 0B00000000; //PC输入输出 0-输出 1-输入 PC0输出 WPUA = 0B00000000; //PA端口上拉控制 1-开上拉 0-关上拉 WPUB = 0B00000000; //PB端口上拉控制 1-开上拉 0-关上拉 WPUC = 0B00000000; //PC端口上拉控制 1-开上拉 0-关上拉 WPDA = 0B00000000; //PA端口下拉控制 1-开下拉 0-关下拉 WPDB = 0B00000000; //PB端口下拉控制 1-开下拉 0-关下拉 WPDC = 0B00000000; //PC端口下拉控制 1-开下拉 0-关下拉 PSRC0 = 0B11111111; //PORTA 源电流设置最大 0:最小,1:最大 PSRC1 = 0B11111111; //PORTB 源电流设置最大 0:最小,1:最大 PSRC2 = 0B11111111; //PORTC 源电流设置最大 00:最小 11:最大 PSINK0 = 0B11111111; //PORTA灌电流设置最大 0:最小,1:最大 PSINK1 = 0B11111111; //PORTB灌电流设置最大 0:最小,1:最大 PSINK2 = 0B11111111; //PORTC灌电流设置最大 0:最小,1:最大 ANSELA = 0B00000000; } /*------------------------------------------------- * 函数名:Time1Initial * 功能: 时钟1初始化 * 输入: 无 * 输出: 无 --------------------------------------------------*/ void Time1Initial(void) { PCKEN |= 0B00000010; //使能timer1时钟模块 CKOCON = 0B00100000; TCKSRC = 0B00000011; //TIM1时钟为HIRC的2倍频 //Bit7:低频内振模式: 1 = 256K 振荡频率模式,0 = 32K 振荡频率模式 //Bit[6:4]:TIM2时钟源选择位 //值 时钟源 //0 系统时钟/主时钟 //1 HIRC //2 XT时钟/外部时钟 //3 HIRC的2倍频 //4 XT时钟/外部时钟的2倍频 //5 LIRC //6 LP时钟/外部时钟 //7 LP时钟/外部时钟的2位频 //Bit3: 保留位 //Bit[2:1]:TIM1时钟源选择位 //值 时钟源 //0 系统时钟/主时钟 //1 HIRC //2 XT时钟/外部时钟 //3 HIRC的2倍频 //4 XT时钟/外部时钟的2倍频 //5 LIRC //6 LP时钟/外部时钟 //7 LP时钟/外部时钟的2位频 TIM1CR1 =0B10000101; //预载允许,边沿对齐向上计数器,计数器使能 //Bit7:自动预装载允许位 //0: TIM1_ARR寄存器没有缓冲,它可以被直接写入; //1: TIM1_ARR寄存器由预装载缓冲器缓冲。 //Bit[6:5]:选择对齐模式 //00: 边沿对齐模式。计数器依据方向位(DIR)向上或向下计数。 //01: 中央对齐模式1。 //10: 中央对齐模式2。 //11: 中央对齐模式3。 //Bit4:方向 //0: 计数器向上计数。 //1: 计数器向下计数。 //Bit3:单脉冲模式 //0: 在发生更新事件时,计数器不停止; //1: 在发生下一次更新事件(清除CEN位)时,计数器停止。 //Bit2:更新请求源 //0: 如果UDIS允许产生更新事件,则下述任一事件产生一个更新中断: //寄存器被更新(计数器上溢/下溢) //软件设置UG位 //时钟/触发控制器产生的更新 //1: 如果UDIS允许产生更新事件,则只有当下列事件发生时才产生更新中断,UIF置1: //寄存器被更新(计数器上溢/下溢) //Bit1:禁止更新 //0: 一旦下列事件发生,产生更新(UEV)事件: //计数器溢出/下溢 //产生软件更新事件 //时钟/触发模式控制器产生的硬件复位被缓存的寄存器被装入它们的预装载值。 //1: 不产生更新事件,影子寄存器(ARR、PSC、CCRx)保持它们的值。 //若设置了UG位或时钟/触发控制器发出了一个硬件复位,则计数器和预分频器被重新初始化. //Bit0:允许计数器 //0: 禁止计数器; //1: 使能计数器。 TIM1CR2 =0B00000000; //Bit7: TIM2计数器使能选择位 //0: TIM2CR1中的CEN位由其本身控制 //1: TIM2CR1中的内部使用的CEN跟随TIM1CR1中的T1CEN位,而TIM2CR1寄存器中的T2CEN不会随T1CEN变化 //Bit[6:3]: 保留 //Bit2: 捕获/比较控制位的更新控制选择 //0: 当捕获/比较的控制位为预装载时(T1CCPC=1),只有在T1COMG位置1的时候这些控制位才被更新 ; //1: 当捕获/比较的控制位为预装载时(T1CCPC=1),只有在T1COMG位置1或触发事件到来时这些控制位才被更新; //注: 该位只对拥有互补输出的通道有效。 //Bit1: 保留。 //Bit0: 捕获/比较预装载控制位 //0: 1SMOD, T1GP, T1CCxE,T1CCxNE,T1CCxP,T1CCxNP位(TIM1CCERx寄存器)和T1OCxM位(TIM1CCMRx寄存器) //1: 1SMOD, T1GP, T1CCxE,T1CCxNE,T1CCxP,T1CCxNP和T1OCxM位是预装载的;设置该位后,它们只在设置了 //T1COMG位(TIM1EGR寄存器)后或触发事件发生时才被更新。 //注: 该位只对具有互补输出的通道起作用。 TIM1SMCR=0B00000000; //Bit7: 主/从模式 //0: 无作用; //1: 触发输入(TRGI)上的事件被延迟了,以允许定时器1与它的从定时器间的完美同步(通过TRGO)。 //Bit[6:4]: 触发选择,这3位选择用于选择同步计数器的触发输入。 //000: 内部触发ITR0连接到TIM6 TRGO (此设计没有TIM6,所以固定接0) //001: 保留 //010: 内部触发ITR2连接到TIM5 TRGO(此设计没有TIM5,所以固定接0) //011: 保留 //100: TI1的边沿检测器(TI1F_ED) //101: 滤波后的定时器输入1(TI1FP1) //110: 滤波后的定时器输入2(TI2FP2) //111: 外部触发输入(ETRF) //注: 这些位只能在未用到(如SMS=000)时被改变,以避免在改变时产生错误的边沿检测。 //Bit3:保留 //Bit[2:0]: 时钟/触发/从模式选择,当选择了外部信号,触发信号(TRGI)的有效边沿与选中的外部输入极性相关 //000: 时钟/触发控制器禁止 – 如果CEN=1,则预分频器直接由内部时钟驱动。 //001: 编码器模式1 – 根据TI1FP1的电平,计数器在TI2FP2的边沿向上/下计数。 //010: 编码器模式2 – 根据TI2FP2的电平,计数器在TI1FP1的边沿向上/下计数。 //011: 编码器模式3 – 根据另一个输入的电平,计数器在TI1FP1和TI2FP2的边沿向上/下计数。 //100: 复位模式 – 在选中的触发输入(TRGI)的上升沿时重新初始化计数器,且产生一个更新寄存器的信号。 //101: 门控模式 – 当触发输入(TRGI)为高时,计数器的时钟开启。一旦触发输入变为低,则计数器停止(但不复位)。 //110: 触发模式 – 计数器在触发输入TRGI的上升沿启动(但不复位),只有计数器的启动是受控的。 //111: 外部时钟模式1 – 选中的触发输入(TRGI)的上升沿驱动计数器。 //注:如果TI1F_ED被选为触发输入(TS=100)时,不要使用门控模式。这是因为TI1F_ED在每次 //TI1F变化时只是输出一个脉冲,然而门控模式是要检查触发输入的电平。 TIM1ETR =0B00000000; //Bit7: 外部触发极性,该位决定是ETR还是ETR 用于触发操作。 //0: ETR不反相,即高电平或上升沿有效; //1: ETR反相,即低电平或下降沿有效。 //Bit6: 外部时钟使能,该位用于使能外部时钟模式2。 //0: 禁止外部时钟模式2; //1: 使能外部时钟模式2,计数器的时钟为ETRF的有效沿。 //注1: ECE位置1的效果与选择把TRGI连接到ETRF的外部时钟模式1相同(TIM1_SMCR寄存器中,SMS=111,TS=111)。 //注2: 外部时钟模式2可与下列模式同时使用: 触发标准模式;触发复位模式;触发门控模式。 //但是,此时TRGI决不能与ETRF相连(TIM1_SMCR寄存器中,TS不能为111)。 //注3: 外部时钟模式1与外部时钟模式2同时使能,外部时钟输入为ETRF。 //Bit[5:4]: 外部触发预分频器,外部触发信号EPRP的频率最大不能超过fMASTER/4。可用预分频器来降低ETRP的频率, //当EPRP的频率很高时,它非常有用: //00: 预分频器关闭; //01: EPRP的频率/2; //02: EPRP的频率/4; //03: EPRP的频率/8 //Bit[3:0]: 外部触发滤波器选择,该位域定义了ETRP的采样频率及数字滤波器长度。 //数字滤波器由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变: //0000: 无滤波器,以fMASTER采样 //1000: 采样频率fSAMPLING=fMASTER/8,N=6 //0001: 采样频率fSAMPLING=fMASTER,N=2 //1001: 采样频率fSAMPLING=fMASTER/8,N=8 //0010: 采样频率fSAMPLING=fMASTER,N=4 //1010: 采样频率fSAMPLING=fMASTER/16,N=5 //0011: 采样频率fSAMPLING=fMASTER,N=8 //1011: 采样频率fSAMPLING=fMASTER/16,N=6 //0100: 采样频率fSAMPLING=fMASTER/2,N=6 //1100: 采样频率fSAMPLING=fMASTER/16,N=8 //0101: 采样频率fSAMPLING=fMASTER/2,N=8 //1101: 采样频率fSAMPLING=fMASTER/32,N=5 //0110: 采样频率fSAMPLING=fMASTER/4,N=6 //1110: 采样频率fSAMPLING=fMASTER/32,N=6 //0111: 采样频率fSAMPLING=fMASTER/4,N=8 //1111: 采样频率fSAMPLING=fMASTER/32,N=8 TIM1IER =0B00000000;//禁止所有中断 //Bit7: 允许刹车中断 //0: 禁止刹车中断; //1: 允许刹车中断。 //Bit6: 触发中断使能 //0: 禁止触发中断; //1: 使能触发中断。 //Bit5: 允许COM中断 //0: 禁止COM中断; //1: 允许COM中断。 //Bit4: 允许捕获/比较4中断 //0: 禁止捕获/比较4中断; //1: 允许捕获/比较4中断。 //Bit3: 允许捕获/比较3中断 //0: 禁止捕获/比较3中断; //1: 允许捕获/比较3中断。 //Bit2: 允许捕获/比较2中断 //0: 禁止捕获/比较2中断; //1: 允许捕获/比较2中断。 //Bit1: 允许捕获/比较1中断 //0: 禁止捕获/比较1中断; //1: 允许捕获/比较1中断。 //Bit0: 允许更新中断 //0: 禁止更新中断; //1: 允许更新中断。 TIM1SR1 =0B00000000; //Bit7: 刹车中断标记,一旦刹车输入有效,由硬件对该位置1。 // 如果刹车输入无效,则该位可由软件清0。 //0: 无刹车事件产生; //1: 刹车输入上检测到有效电平 //Bit6: 触发器中断标记,当发生触发事件(当从模式控制器处于除门控模式外的其它模式时, //在TRGI输入端检测到有效边沿,或门控模式下的任一边沿)时由硬件对该位置1。 //它由软件清0。 //0: 无触发器事件产生; //1: 触发中断等待响应。 //Bit5: COM中断标记,一旦产生COM事件(当捕获/比较控制位: CciE、CciNE、OciM已被更新)该位由硬件置1。它由软件清0 。 //0: 无COM事件产生; //1: COM中断等待响应。 //Bit4: 捕获/比较4中断标记 //参考CC1IF描述。 //Bit3: 捕获/比较3中断标记 //参考CC1IF描述。 //Bit2: 捕获/比较2中断标记 //参考CC1IF描述。 //Bit1: 捕获/比较1中断标记 如果通道CC1配置为输出模式: 当计数器值与比较值匹配时该位由硬件置1, //但在中心对称模式下除外(参考TIM1_CR1寄存器的CMS位)。它由软件清0。 //0: 无匹配发生; //1: TIMx_CNT的值与TIMx_CCR1的值匹配。 //注: 在中心对称模式下,当计数器值为0时,向上计数,当计数器值为ARR时,向下计数(它从0向上计数到ARR-1,再由ARR向下计数到1)。 //因此,对所有的SMS位值,这两个值都不置标记。但是,如果CCR1>ARR,则当CNT达到ARR值时,CC1IF置1。 //如果通道CC1配置为输入模式: 当捕获事件发生时该位由硬件置1,它由软件清0或通过读TIM1_CCR1L清0。 //0: 无输入捕获产生; //1: 计数器值已被捕获(拷贝)至TIM1_CCR1(在IC1上检测到与所选极性相同的边沿)。 //Bit0: 更新中断标记,当产生更新事件时该位由硬件置1。它由软件清0。 //0:更新事件产生; //1: 更新事件等待响应。当寄存器被更新时该位由硬件置1: //若TIM1_CR1寄存器的UDIS=0,当计数器上溢或下溢时; //若TIM1_CR1寄存器的UDIS=0、URS=0,当设置TIM1_EGR寄存器的UG位软件对计数器 //CNT重新初始化时; //若TIM1_CR1寄存器的UDIS=0、URS=0,当计数器CNT被触发事件重新初始化时 //(参考0从模式控制寄存器TIM1_SMCR)。 TIM1SR2 =0B00000000; TIM1EGR =0B00000000; //Bit7: 产生刹车事件,该位由软件置1,用于产生一个刹车事件,由硬件自动清0。 //0: 无动作; //1: 产生一个刹车事件。此时MOE=0、BIF=1,若开启对应的中断(BIE=1),则产生相应的中断。 //Bit6: 产生触发事件,该位由软件置1,用于产生一个触发事件,由硬件自动清0。 //0: 无动作; //1: TIM1_SR寄存器的TIF=1,若开启对应的中断(TIE=1),则产生相应的中断。 //Bit5: 捕获/比较事件,产生控制更新该位由软件置1,由硬件自动清0。 //0: 无动作; //1: 当CCPC=1,允许更新CCIE、CCINE、CciP,CciNP,OCIM位。 //注: 该位只对拥有互补输出的通道有效。 //Bit4: 产生捕获/比较4事件 //参考CC1G描述。 //Bit3: 产生捕获/比较3事件 //参考CC1G描述。 //Bit2: 产生捕获/比较2事件 //参考CC1G描述。 //Bit1: 产生捕获/比较1事件 //该位由软件置1,用于产生一个捕获/比较事件,由硬件自动清0。 //0: 无动作; //1: 在通道CC1上产生一个捕获/比较事件: 若通道CC1配置为输出: //设置CC1IF=1,若开启对应的中断,则产生相应的中断。若通道CC1配置为输入: //当前的计数器值被捕获至TIM1_CCR1寄存器,设置CC1IF=1,若开启对应的中断,则产生相应的中断。若CC1IF已经为1,则设置CC1OF=1。 //Bit0: 产生更新事件 //该位由软件置1,由硬件自动清0。 //0: 无动作; //1: 重新初始化计数器,产生一个更新事件。注意预分频器的计数器也被清0(但是预分频系数不变)。 //若在中心对称模式下或DIR=0(向上计数)则计数器被清0;若DIR=1(向下计数)则计数器取TIM1_ARR的值。 TIM1CCMR1 =0B01101000;//CC1通道被配置为输出 //Bit7: 输出比较1清零使能 //该位用于使能使用TIM1_TRIG引脚上的外部事件来清通道1的输出信号(OC1REF),参考17.5.9在外部事件发生时清除OCREF信号 //0: OC1REF 不受ETRF输入(来自TIM1_TRIG引脚)的影响; //1: 一旦检测到ETRF输入高电平,OC1REF=0。 //Bit[6:4]: 输出比较1模式,该3位定义了输出参考信号OC1REF的动作,而OC1REF决定了OC1的值。OC1REF是高电平有效,而OC1的有效电平取决于CC1P位。 //000: 冻结。输出比较寄存器TIM1_CCR1与计数器TIM1_CNT间的比较对OC1REF不起作用; //001: 匹配时设置通道1的输出为有效电平。当计数器TIM1_CNT的值与捕获/比较寄存器1(TIM1_CCR1)相同时,强制OC1REF为高。 //010: 匹配时设置通道1的输出为无效电平。当计数器TIM1_CNT的值与捕获/比较寄存器1(TIM1_CCR1)相同时,强制OC1REF为低。 //011: 翻转。当TIM1_CCR1=TIM1_CNT时,翻转OC1REF的电平。 //100: 强制为无效电平。强制OC1REF为低。 //101: 强制为有效电平。强制OC1REF为高。 //110: PWM模式1- 在向上计数时,一旦TIM1_CNT<TIM1_CCR1时通道1为有效电平,否则为无效电平; //在向下计数时,一旦TIM1_CNT>TIM1_CCR1时通道1为无效电平(OC1REF=0), 否则为有效电平(OC1REF=1)。 //111: PWM模式2- 在向上计数时,一旦TIM1_CNT<TIM1_CCR1时通道1为无效电平,否则为有效电平; //在向下计数时,一旦TIM1_CNT>TIM1_CCR1时通道1为有效电平,否则为无效电平。 //注1: 一旦LOCK级别设为3(TIM1_BKR寄存器中的LOCK位)且CC1S=00(该通道配置成输出) 则该位不能被修改。 //注2: 在PWM模式1或PWM模式2中,只有当比较结果改变了或在输出比较模式中从冻结模式切换到PWM模式时,OC1REF电平才改变。 //注3: 在有互补输出的通道上,这些位是预装载的。如果TIM1_CR2寄存器的CCPC=1,OCM 位只有在COM事件发生时,才从预装载位取新值。 //Bit3: 输出比较1预装载使能 //0: 禁止TIM1_CCR1寄存器的预装载功能,可随时写入TIM1_CCR1寄存器,且新写入的数值立即起作用。 //1: 开启TIM1_CCR1寄存器的预装载功能,读写操作仅对预装载寄存器操作,TIM1_CCR1的预装载值在更新事件到来时被加载至当前寄存器中。 //注1: 一旦LOCK级别设为3(TIM1_BKR寄存器中的LOCK位)且CC1S=00(该通道配置成输出) 则该位不能被修改。 //注2: 为了操作正确,在PWM模式下必须使能预装载功能。但在单脉冲模式下(TIM1_CR1寄存器的OPM=1),它不是必须的。 //Bit2: 输出比较1 快速使能,该位用于加快CC输出对触发输入事件的响应。 //0: 根据计数器与CCR1的值,CC1正常操作,即使触发器是打开的。当触发器的输入有一个有效沿时,激活CC1输出的最小延时为5个时钟周期。 //1: 输入到触发器的有效沿的作用就象发生了一次比较匹配。因此,OC被设置为比较电平而与比较结果无关。 //采样触发器的有效沿和CC1输出间的延时被缩短为3个时钟周期。 //OCFE只在通道被配置成PWM1或PWM2模式时起作用。 //Bit[1:0]: 捕获/比较1 选择。这2位定义通道的方向(输入/输出),及输入脚的选择: //00: CC1通道被配置为输出; //01: CC1通道被配置为输入,IC1映射在TI1FP1上; //10: CC1通道被配置为输入,IC1映射在TI2FP1上; //11: CC1通道被配置为输入,IC1映射在TRC上。此模式仅工作在内部触发器输入被选中 //(由TIM1_SMCR寄存器的TS位选择)。 //注: CC1S仅在通道关闭时(TIM1_CCER1寄存器的CC1E=0)才是可写的。 TIM1CCMR2 =0B00000000; TIM1CCMR3 =0B00000000; TIM1CCMR4 =0B00000000; TIM1CCER1 =0B00001111; //比较1互补输出使能,低电平有效;比较器1输出使能,低电平有效 //Bit7: 输入捕获/比较2互补输出极性。参考CC1NP的描述。 //Bit6: 输入捕获/比较2互补输出使能。参考CC1NE的描述。 //Bit5: 输入捕获/比较2输出极性。参考CC1P的描述。 //Bit4: 输入捕获/比较2输出使能。参考CC1E的描述。 //Bit3: 输入捕获/比较1互补输出极性 //0: OC1N高电平有效; //1: OC1N低电平有效。 //注1: 一旦LOCK级别(TIM1_BKR寄存器中的LCCK位)设为3或2且CC1S=00(通道配置为输出) 则该位不能被修改。 //注2: 对于有互补输出的通道,该位是预装载的。如果CCPC=1(TIM1_CR2寄存器),只有在 //COM事件发生时,CC1NP位才从预装载位中取新值。 //Bit2: 输入捕获/比较1互补输出使能 //0: 关闭- OC1N禁止输出,因此OC1N的输出电平依赖于MOE、OSSI、OSSR、OIS1、 //OIS1N和CC1E位的值。 //1: 开启- OC1N信号输出到对应的输出引脚,其输出电平依赖于MOE、OSSI、OSSR、 //OIS1、OIS1N和CC1E位的值。 //注: 对于有互补输出的通道,该位是预装载的。如果CCPC=1(TIM1_CR2寄存器),只有在 //COM事件发生时,CC1NE位才从预装载位中取新值。 //Bit1: 输入捕获/比较1输出极性CC1通道配置为输出: //0: OC1高电平有效; //1: OC1低电平有效。 //CC1通道配置为触发(参考图61): //0: 触发发生在TI1F的高电平或上升沿; //1: 触发发生在TI1F的低电平或下降沿。 //CC1通道配置为输入(参考图61): //0: 捕捉发生在TI1F的高电平或上升沿; //1: 捕捉发生在TI1F的低电平或下降沿。 //注1: 一旦LOCK级别(TIM1_BKR寄存器中的LCCK位)设为3或2,则该位不能被修改。 //注2: 对于有互补输出的通道,该位是预装载的。如果CCPC=1(TIM1_CR2寄存器),只有在 //COM事件发生时,CC1P位才从预装载位中取新值。 //Bit0: 输入捕获/比较1输出使能 //CC1通道配置为输出: //0: 关闭- OC1禁止输出,因此OC1的输出电平依赖于MOE、OSSI、OSSR、OIS1、 //OIS1N和CC1NE位的值。 //1: 开启- OC1信号输出到对应的输出引脚,其输出电平依赖于MOE、OSSI、OSSR、 //OIS1、OIS1N和CC1NE位的值。 //该位决定了计数器的值是否能捕获入TIM1_CCR1寄存器。 //0: 捕获禁止; //0: 捕获使能。 //注: 对于有互补输出的通道,该位是预装载的。如果CCPC=1(TIM1_CR2寄存器),只有在 //COM事件发生时,CC1E位才从预装载位中取新值。 TIM1CCER2 =0B00000000; TIM1CNTRH =0B00000000; //TIM1计数器 TIM1CNTRL =0B00000000; TIM1PSCRH =0B00000000; TIM1PSCRL =0B00000000; TIM1ARRH =0x03; //自动重载,周期 TIM1ARRL =0xe8; TIM1RCR =0B00001111; //重复计数器的值 TIM1CCR1H =0x01; //PWM脉宽 TIM1CCR1L =0xf4; TIM1BKR =0B11000000; //输出使能,禁止刹车 TIM1DTR =0B00000111; //死区发生器 //Bit[7:0]: 死区发生器设置,这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间,tCK_PSC为TIM1的时钟脉冲: //DTG[7:5]=0xx => DT=DTG[7:0]x tdtg,其中: tdtg=tCK_PSC. (f1) //DTG[7:5]=10x => DT=(64+DTG[5:0])x tdtg,其中:tdtg= tCK_PSC. (f2) //DTG[7:5]=110 => DT=(32+DTG[4:0])x tdtg, 其中:tdtg=8x tCK_PSC. (f3) //DTG[7:5]=111 => DT=(32+DTG[4:0])x tdtg, 其中:tdtg=16x tCK_PSC. (f4) TIM1OISR =0B00000000; //空闲输出状态设置 //Bit1:输出空闲状态1(OC1N输出)。 //0:当MOE=0时,则在一个死区时间后,OC1N=0; //1:当MOE=0时,则在一个死区时间后,OC1N=1。 //注:已经设置了LOCK(TIM1_BKR寄存器)级别1、2或3后,该位不能被修改。 //Bit0:输出空闲状态1(OC1输出)。 //0:当MOE=0时,如果OC1N使能,则在一个死区后,OC1=0; //1:当MOE=0时,如果OC1N使能,则在一个死区后,OC1=1。 //注:已经设置了LOCK(TIM1_BKR寄存器)级别1、2或3后,该位不能被修改。 LEBCON =0B00000000; //前沿消隐禁止 //Bit7 前沿消隐使能位(仅当ADGO=0时可进行切换,否则ADC工作异常) //1 = 使能 //0 = 禁止 //Bit[6:5]: 前沿消隐通道选择 //00 = TIM1_CH1 //01 = TIM1_CH2 //10 = TIM1_CH3 //11 = TIM1_CH4 //Bit4: N/A 保留位,读0 //Bit3: PWM消隐沿选择 //0 = PWM上升沿 //1 = PWM下降沿 //Bit[2:0]:TIM1的故障源使能,高有效 //BKS2:选择ADC阈值比较 //BKS1:选择LVD检测 //BKS0:选择BKIN管脚 } /*------------------------------------------------- * 函数名:main * 功能: 主函数 * 输入: 无 * 输出: 无 --------------------------------------------------*/ void main(void) { POWER_INITIAL(); Time1Initial(); //初始化timer1 while(1) { NOP(); } } 详细解释代码
最新发布
08-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值