首页资源分类嵌入式处理器其它 > 合泰单片机C语言

合泰单片机C语言

已有 444994个资源

下载专区

上传者其他资源

    文档信息举报收藏

    标    签:合泰C语言

    分    享:

    文档简介

    合泰单片机C语言规则

    文档预览

    盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 1 页 共 189 页 Holtek 微控制器应用范例 – 使用 Holtek C 语言 目录 第一章 内容简介 第二章 选定 Holtek C 语言的使用环境 2.1 进入 HT-IDE3000 建立新的专案时, 选定 Holtek C 编译器 2.2 已开启专案后, 选用 Holtek C 编译器 第三章 微控制器 C 语言程式的速成 3.1 定义主函式 main() 3.2 定义副函式 (sub-function) 3.3 定义全域变数 (global variable) 3.4 定义中断服务函式 (Interrupt Service Routine : ISR) 3.5 其他 第四章 C 语言程式 4.1 C 程式架构 4.2 开始用 C 语言设计一个程式 4.2.1 定义主函式 main 4.2.2 将标头档引入 (include a header file) 4.2.3 定义文字符号及变数 4.2.4 设定微控制器及装置的初始状态 4.2.5 设计子函式 4.2.6 设计中断服务函式 4.3 变数 (variable) 及资料型态 (data type) 4.3.1 变数名 4.3.2 资料型态 4.3.3 变数的有效范围 (scope) 4.3.4 变数的资料型态 (data type) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 2 页 共 189 页 整数型 (integer) 浮点型 (floating point) 4.3.5 bit 资料型态 4.3.6 储存类别 (storage class) 与修饰词 (qualifier) 储存类别 (storage class) 修饰词 (qualifier) 4.3.7 绝对变数 (absolute variable) 4.3.8 常数 (constant) 4.3.9 指标 (pointer) 与阵列 (array) 指标的运算子 & 与 * 阵列 (array) 4.3.10 结构 (struct) 与等位 (union) 结构的运算子 -> 与 . 4.4 运算子 (Operators) 运算前的型态转换 4.5 程式流程控制 (program flow control) 4.5.1 if-else 叙述 4.5.2 switch 叙述 4.5.3 for 叙述 4.5.4 while 叙述 4.5.5 do-while 叙述 4.5.6 goto 叙述 4.5.7 break 与 continue 叙述 4.6 函式 (Functions) 4.6.1 参数 (arguments) 4.6.2 返回值 (return values) 4.7 中断服务函式 (Interrupt Service Routines) 4.8 在 C 语言程式中嵌入组合语言 (in-line assembly code) 从组合语言的程式去存取 C 语言的物件(变数) 4.9 前置处理指令 (Preprocessor) 4.9.1 定义文字符号 (#define) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 3 页 共 189 页 4.9.2 引入档案 (#include) 4.9.3 内嵌组合语言 (inline assembly) 4.9.4 条件式编译 (#if/#endif) 4.9.5 编译器的特殊选项 pragma 4.10 Holtek C 编译器的内建函式 (built-in functions) 第五章 基本 C 语言程式 5.1 语法观念 5.2 回圈的应用 (loop) 5.3 撰写 MCU 应用程式的注意事项 5.4 可供微控制器应用程式使用的范本 5.5 设计微控制器应用程式的小技巧 第六章 程式范例 – 初级 6.1 LED 跑马灯 6.2 LED 霹雳灯 6.3 单颗七段显示器 6.4 5*5 点矩阵 LED 显示 6.5 HT48 微控制器控制 HT1621 LCD 的显示 6.6 HT48 微控制器控制 LCD 模组的显示 6.7 具 LCD 驱动功能的微控制器之显示应用程式 – HT46R63 6.8 显示器的通用函式 – HT44780 LCM 6.9 键盘扫描程式 第七章 程式范例 – 中断函式 7.1 用时钟控制 LED 的亮与灭 7.2 类比/数位转换 (ADC) 的应用 第八章 HT46R52A 应用于镍氢电池充电器 (HA0084T) 第九章 程式范例 – HT46R74D-1 胎压计 (HA0105T) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 4 页 共 189 页 第一章 内容简介 盛群半导体公司(Holtek)开发一系列的八位元微控制器(micro-controller, MCU). 当开发 微控制器的应用程式时, 除了可使用盛群提供的组合语言(assembly language),也可使用 标准的 C 语言编译器 (C compiler). 由于八位元微控制器的记忆体空间, 不论是程式记忆体(program memory space)或是资料 记忆体(ram memory space), 皆是有限制的, 通常会使用组合语言开发应用程式. 但是越 来越多的微控制器支援更多的记忆体以及更多的功能, 使得程式也相对的扩大. 如果仍 然使用组合语言开发程式, 不但费时费力, 未来在维护及扩增功能的工作上也相当困难. 因此, 使用高阶程式语言, 例如 C 语言, 来开发应用程式就是一种可行的趋势. C 语言是高阶程式语言中的一种, 它具有高度的的可读性及可移植性(portability),除了 能够快速地完成应用程式的开发与侦错, 也很容易移植到其他的微控制器上. 当程式需 要缩减或扩充功能时, 也很容易的完成, 因此很适合于微控制器的程式开发. 本书主要是以 Holtek C 语言为主, 说明如何使用 Holtek C 语言撰写盛群微控制器的 应用程式, 包括 C 的程式架构, C 语言的一般用法, 特殊用法及应用范例 书中将说明在开发微控制器的应用程式时需要注意的地方及如何撰写会比较恰当, 并配 以实例解释. 读者可以参考修改或直接采用到自己的程式中, 再用发展工具 HT-ICE, HT-IDE3000 验证之. 第二章介绍选用 Holtek C 编译器的步骤, 指引 HT-IDE3000 呼叫 Holtek C 编译器去 编译 C 语言的原始程式. 第三章提供一种快速撰写 C 程式的方法, 对 ANSI C 语言熟悉的用者, 可于阅读本章 之后即开始撰写微控制器的 C 语言程式 第四章介绍 C 语言, 未曾使用过 C 语言的读者应仔细阅读本章以了解 C 语言的用法 第五章介绍使用 C 语言写程式的基本观念, 注意事项及建议的写作方法 第六章到第九章则是应用范例, 针对盛群各系列的微控制器, 以 C 语言撰写的应 用程式. 包含有功能说明, 应用电路及程式说明. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 5 页 共 189 页 第二章 选定 Holtek C 编译器的使用环境 2.1 进入 HT-IDE3000, 建立新的专案时, 选定 Holtek C 编译器 进入 HT-IDE3000 开发环境后, 依照下列方法建立一个新的专案 (project) → 移动滑鼠游标到 Project 选单, 按左键 → 移动滑鼠游标到 New 命令, 按左键 → 出现如下的视窗, 在 Language Tool 之处勾选 Enhanced Holtek C compiler/Assembler 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 6 页 共 189 页 2.2 已开启专案后, 如何选用 Holtek C 编译器 若专案 (project) 已开启之后, 可以点选 (click) Option 选单下的 Project Setting 命令, 在 Language Tool 中点选 Enhanced Holtek C Compiler/Assembler 以设定使用 Holtek C 的 Enhance 版编译器 Enhance 版的C compiler 包括 ehcc32srsc.exe , ehcc32mrsc.exe 与 ehcc32mrmc.exe 三个执行档 此版本必须在 HT-IDE3000 V7.0 或以上的系统才能执行 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 7 页 共 189 页 第三章 微控制器 C 语言程式的速成 本章介绍如何快速撰写微控制器的 C 语言应用程式. 已熟悉 ANSI C 标准语言的用法 或有撰写的经验者, 在阅读此章后即可开始设计撰写微控制器的 C 应用程式, 以下各节是 基本的 C 程式成员, 某些是必须要有的, 如 3.1, 其他的则视微控制器的功能及应用来决定 是否需要 3.1 定义主函式 main() #include “ht46r63.h” void main(void) { int Flag ; …… TurnOn_LCD() ; Flag = LCD_display(cstr) ; TurnOff_LCD() ; …… } 主函式的返回资料型态(return type)必须是 void, 而且不能有参数 档案 ht46r63.h 定义与微控制器有关的常数, 例如暂存器的位址定义, 将之引入(include) 可增加程式的可读性. 3.2 定义副函式 (sub-function) 视程式的大小及功能决定是否需要定义副函式. 基本上, 主函式应将应用程式的架构做成 模组化, 不需要将所有的程式皆放在主函式中. 为了能很快的完成及了解应用程式, 主函 式中只需要包含(呼叫) 定义各功能的副函式即可, 无论在设计或维护程式时皆能很快的 进入与完成. 例如, 关于 LCD 的开启, 显示及关闭等功能就可分别定义为单独的副函式, 如下例. 任何其他的函式或其他的应用专案都可去呼叫这些副函式. 若设计成通用型的, 也可藉由程式馆管理器 (Library Manager) 将之建入程式馆档案, 以供其他应用专案使用. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 8 页 共 189 页 void TurnOn_LCD(void) { } int LCD_display(char *cstr) { } void TurnOff_LCD(void) { } 3.3 定义全域变数 (global variable) 程式在运行中会需要一些变数做为资料存放的地方, 由于微控制器资料记忆体大小的限制 及 C 编译器的设计, 最好将常需使用的变数定义为全域型的变数, 在编译程式的大小与 执行上皆较佳. 例如定义常数型指标变数 cstr 指到字串 “Hello!”, 则可如下 const char *cstr = “Hello!” ; 3.4 定义中断服务函式 (Interrupt Service Routine : ISR) 若微控制器的周边装置具有中断功能, 程式也需要此中断机能以完成工作时, 则必须定义 此周边装置的中断服务函式 (Interrupt Service Routine, ISR), 如下的格式 #pragma vector ISR_tmr0 @ 0x0c void ISR_tmr0(void) { tick++ ; } 中断服务函式必须遵守下列规定 → 返回的资料型态必须是 void → 不能有参数 (必须为 void) → 必须使用前置处理指令 #pragma vector 设定中断向量值 (interrupt vector), 在函式名称 (本例子是 ISR_tmr0) 之后加上 @ 及中断向量值 (本例是 0x0c). 也可使用先前定义 好的常数, 例如 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 9 页 共 189 页 #define VECTOR_TMR0 0x0c #pragma vector ISR_tmr0 @ VECTOR_TMR0 void ISR_tmr0(void) { } 3.5 其他 上述的主函式, 副函式及中断服务函式不需要定义在同一个原始程式档案内. 为缩短编译的 时间, 最好是分别定义在不同的档案中, 并使用有意义的档名, 方便日后找寻所要的函式. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 10 页 共 189 页 第四章 C 程式语言 基本上, Holtek C 是仿 ANSI (美国国家标准局) 标准的 C 语言, 为配合盛群八位元微 控制器的架构, 将提供一些特殊的语法去存取或控制微控制器的资源. 另外, 本章会从八位 元微控制器的角度, 说明如何使用 C 语言设计及撰写微控制器的应用程式 4.1 C 程式架构 C 语言的程式是由叙述 (statements) 所组成, 每个叙述的最后必须有分号 ‘;’ 做为结束符号. 叙述分为四类 : → 宣告 (declaration), 宣告变数及资料型态, 资料结构. 例如 char flag, tick_cnt ; // 宣告变数 flag 与 tick_cnt 为 char 型态 → 定义 (definition), 定义变数数值及位址 例如 int total = 10 ; // 定义变数 total, 设定值为 10 → 描述式 (expression), 执行数学及逻辑运算, 控制程式的流程 例如 count = ( input > 10) ? 10 : input ; → 函式呼叫 (function), 执行函式的功能 例如 putchar(ch) ; // 写出一个字元到输出口 每个叙述可以附加注解做说明. C 编译器不会对注解做编译, 下列为两种可被接受的注解 → 介于符号 /* 与 符号 */ 之间的数据及文字, 包括换行字 ‘\n’ 如果 /* 与 */ 不在同一行, 则其间所有的行皆会被视为注解 例如 /* this is a comment 1 */ → 从符号 // 开始到本行的结束皆被视为注解 例如 // 这是注解的新写法 在 C 语言中, 程式执行区 (program block)是以函式的格式定义, 因此, 所有要执行的叙述 皆需定义(包含)于某个函式 (function) 中, 例如描述式. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 11 页 共 189 页 4.2 开始用 C 语言设计一个程式 依照下列步骤¸ 使用 C 语言实作一个简单的应用程式 4.2.1 定义主函式 main 范例 void main(void) { } 在 C 语言中, 主函式 main 是程式执行的起点, 有如组合语言程式中的 start ORG 00 jmp start start : void 是资料型态, main 与 void 皆是保留字, 必须用此字, 小写字母 4.2.2 将标头档引入 (include a header file) 范例 #include “ht46r63.h” // 引入标头档 ht46r63.h void main(void) { } 标头档 ht46r63.h 中定义许多与微控制器有关的变数及文字符号(symbol). 接下来写程 式时可以使用这些变数与文字符号(symbol), 好处是写程式或维护程式时会很容易了解 程式的功能, 增加程式的易读性. 例如 unsigned char _pa @0x12 ; 定义 _pa 是一个 unsigned char 型态的变数, 它的位址在 RAM 的 0x12 (就是A埠, port A). 所以程式中如下的叙述 _pa = 0 ; 则与组合语言的 CLR PA ; (PA=[12H]) 有相同的功能 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 12 页 共 189 页 4.2.3 定义文字符号及变数 在程式中使用文字符号能够更容易的读懂程式及修改, 例如定义文字符号 _pa0 如下 #define _pa0 _12_0 表示 _pa0 是 RAM 位址 12H 的位元 0 (bit 0), 就是 A 埠的位元 0. 下列叙述 _pa0 = 1 ; 表示将 A 埠的位元 0 设为 1, 与组合语言程式的 SET [12H].0 有相同的功能 前置处理指令 #define 是定义一个文字符号代表数值, 或是文字串, 或是巨集指令. C 编译器的前置处理器 (preprocessor) 在编译前, 会先替换这些定义的文字符号. 前置处理指令 #undef 是将先前定义过的文字符号取消, 变成无效. 详细的说明请参阅 第 4.9 节 微控制器中其他暂存器的变数或文字符号, 皆定义于对应之微控制器的标头档案内, 在设计程式时可参考之. 可以定义一些程式需要, 但是在标头档案中没有定义的变数或 文字符号, 方便程式的开发及维护, 例如 #include “ht48R50A-1.h” #define scl _pa3 // SCL (时钟线) 接到 MCU 的 A 埠的第 3 位元 #define scl_c _13_3 // A 埠之控制暂存器的第 3 位元 (bit 型变数) #define sda _pa1 // SDA (资料线) 接到 MCU 的 A 埠的第 1 位元 #define sda_c _13_1 // A 埠之控制暂存器的第 1 位元 void main(void) { } 定义四个文字符号 scl, scl_c, sda, sda_c 分别代表不同的输出/输入埠 定义变数会占用 RAM/ROM 的空间, 如果又指定位址, 则此变数占用此位址, 否则 Linker 在做连结时才会分派位址给变数. 其效果有如组合语言的 _pa DB ? 定义文字符号的效果则与组合语言的 EQU 相同, 例如 #define scl _pa3 与 scl EQU _pa3 有相同的效果 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 13 页 共 189 页 4.2.4 设定微控制器及装置的初始状态 根据程式的功能, 设定微控制器中各装置的初始值, 例如周边设备的初始状态, 暂存器 的型态等. scl_c = 0 ; // 设定 SCL (= PAC) 的状态为输出 sda_c = 0 ; // 设定 SDA (=PA) 的状态为输出 4.2.5 设计子函式 独立之功能可分别用子函式完成, 在程式的侦错, 维护及重复使用上皆有好处. 在设计 时需要注意函式的参数,返回值等. 通常会将主函式 main() 放在程式档的最后, 各个 子函式定义在前面或其他的程式档案内. 下列范例只是部份, 详细的说明可参阅 4.6 节. #include “ht48r50a-1.h” // 引入标头档 #define scl _pa3 // SCL (时钟线) 接到 MCU 的 A 埠的第 3 位元 #define scl_c _13_3 // A 埠(位址 0x13)之控制暂存器的第 3 位元 (bit 型变数) #define sda _pa1 // SDA (资料线) 接到 MCU 的 A 埠的第 1 位元 #define sda_c _13_1 // A 埠 (位址 0x13)之控制暂存器的第 1 位元 // 函式 : StartCondition() 子函式 // 功能 : 开始一个命令 // 输入 : 无 // 输出 : 无 void StartCondition(void) { sda = 1 ; // SDA 输出 high scl = 1 ; // SCL 拉 high sda = 0 ; // SDA 输出 low scl = 0 ; // 完成 Start of command } // 函式 : StopCondition() // 功能 : 结束先前的命令 // 输入 : 无 // 输出 : 无 void StopCondition(void) { 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 14 页 共 189 页 } // 函式 : main() // 功能 : 主函式 // 输入 : 无 // 输出 : 无 void main (void) { unsigned char Rdata, type ; // 暂存器的初始设定 scl_c = sda_c = 0 ; // 将 A 埠的位元 1, 3 设为输出型态 (SCL, SDA 为输出) StartCondition() ; // 呼叫子函式 …… } 4.2.6 设计中断服务函式 针对有硬体中断的微控制器, 需要设计中断服务函式以处理中断事件 定义周边装置的中断服务函式 (Interrupt Service Routine, ISR) 如下格式 #pragma vector ISR_tmr0 @ 0x0c void ISR_tmr0(void) { tick++ ; } 中断服务函式必须遵守下列规定 → 返回的资料型态必须是 void → 不能有参数 → 必须设定中断向量值 (interrupt vector), 在函式名称(本例子是 ISR_tmr0之后 加上 @ 及中断向量值 (本例是 0x0c). 也可使用先前定义好的常数, 例如 #define VECTOR_TMR0 0x0c #pragma vector ISR_tmr0 @ VECTOR_TMR0 void ISR_tmr0(void) { } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 15 页 共 189 页 4.3 变数 (variable) 及资料型态 (data type) 程式执行过程中, 可能会需要暂存一些资料, 例如旗标, 执行次数, 延迟秒数等, 因此就必须 定义变数以储存这些资料. 由于变数要占用程式记忆体 (PROM) 或资料记忆体(RAM), 因此在使用变数之前, 定义变数的资料型态, 以便让编译器正确的编译程式及配置记忆体 空间. 除了资料型态之外, 还可加入储存类别 (storage class) 及修饰词 (qualifier), 对变数做 更详细的安置. 4.3.1 变数名 变数名的规则 → 第一个字元必须是英文字母或底线符号 (underscore), 之后可紧接着字母或数字 → 变数名的前 32 个字元有效 → 变数名内不可有 +, - , *, / , ……等符号字元 → 英文字母的大小写是有区别的 (case-sensitive), 例如 count 与 Count 是不同的 变数名 范例, number, total_tick, _tick 是合法的变数名, 而 2num, $dot, line\n 是非法的变数名 4.3.2 资料型态 Type (资料型态) Size (bits) Arithmetic Type 范围大小 bit 1 unsigned integer 0, 1 char 8 signed integer -128 ~ +127 signed char 8 signed integer -128 ~ +127 unsigned char 8 unsigned integer 0 ~ 255 short 16 signed integer -32768 ~ +32767 unsigned short 16 unsigned integer 0 ~ 65535 int 16 signed integer -32768 ~ +32767 unsigned int 16 unsigned integer 0 ~ 65535 long 32 signed integer -2147483648 ~ +2147483647 unsigned long 32 unsigned integer 0 ~ 4294967295 void 0 - - float 32 real -3.48e-38 ~ 3.48e+38 * 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 16 页 共 189 页 double 32 real -3.48e-38 ~ 3.48e+38 * * float, double 皆使用 IEEE754 32 位元的格式 4.3.3 变数的有效范围 (scope) 根据变数定义的所在¸ 决定此变数的有效范围. 可分为 → 区域变数 (local variable) 定义在程式区块内 (program block, 例如函式)的变数皆是区域变数. 只有当此程式 区块被执行时, 区域变数才会有效, 而在执行完毕并离开此程式区块后, 这些区域 变数将无效. 程式区块是指包含在左右大括号 ‘{‘ 及 ‘}’ 之间的叙述行 定义在函式中的 static 变数则是全域变数, 参考 4.3.2 说明 → 全域变数 (global variable) 定义在所有函式之外的变数为全域变数. 当程式在执行时, 此变数皆有效, 任何函 式都可以存取或修改这个变数 范例 #include “ht48r50a-1.h” unsigned char flag ; // 全域变数 void main(void) { char type ; // 区域变数, 只有在此函式被执行时才有效 static status = 0 ; // static 变数, 只在第一次执行时设为 0 …… } 4.3.4 变数的资料型态 (data type) 当宣告变数时,必须指定它的资料型态, 以告知编译器此变数所需记忆体的大小. 资料型态分为整数型(integer type)及浮点数型 (floating point type). 整数型又可区分为 有正负号 (signed) 及无正负号 (unsigned). 整数型 (integer) → char 占用一个位元组 (byte) 的记忆体空间. 如加上 signed 则表示有正负号, 其大小 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 17 页 共 189 页 范围是 –128 到 127. 若加上 unsigned, 则表示没有正负号, 其大小范围是 0 到 255. 如果没有 signed 或 unsigned, 则被视为 signed. 可用此型态定义字元, 如 ‘A’, ‘d’, ‘$’, ‘3’ 等 → short 占用两个位元组 (2 bytes) 的记忆体空间, 如加上 signed 则表示有正负号, 其大小 范围是 –32768 到 32767. 若加上 unsigned, 则表示没有正负号, 其大小范围是 0 到 65535. 如果没有 signed 或 unsigned, 则被视为 signed. Holtek C 采用 little-endian 格式, 就是变数的低位元组 (least significant byte) 存 放在记忆体的低位址. 例如变数 count = 0x1234 是存放于记忆体 40H 的位址, 则低位元组的数值 0x34 存放于位址 40H, 高位元数值 0x12 存放于位址 41H. → int 与 short 型态相同 → long 占用四个位元组 (4 bytes) 的记忆体空间, 如加上 signed 则表示有正负号, 其大小 范围是 –2147483648 到 2147483647. 若加上 unsigned, 则表示没有正负号, 其大 小范围是 0 到 4294967295. 如果没有 signed 或 unsigned, 则被视为 signed 在 little-endian 格式中, 32位元的变数, 则是先存低字元 (least significant word) 的 低位元组 (least significant byte)到记忆体的低位址, 再存放低字元的高位元组 (high byte), 再存放高字元 (high word)的低位元组, 最后才是高字元的高位元组 浮点型 (floating point) Holtek C 支援 IEEE 754 32 位元的格式. 包括 float 及 double 两个资料型态, 浮点数 值是以下表的格式储存在记忆体 sign biased exponent mantissa IEEE 754 32 bit x xxxx xxxx xxx xxxx xxxx xxxx xxxx xxxx 与浮点数值的关系式为 number = (-1) sign x 2(exponent – 127) x 1.mantissa 例如, 浮点数为 2.77000e+37 在储存到记忆体时换成 7DA6B69B 共占 32 位元 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 18 页 共 189 页 4.3.5 bit 资料型态 类似整数型, 但是只有 0 或 1 两个值, 所以只会取整数的最低位值 (LSB: least significant bit) 需要注意下列用法 → bit 型态不可与 auto 一起使用, bit 型态的变数不可当函式的参数, 不可用于指标 (pointer) 的数据型态, 不可设定静态初始值 (static) → bit 型态可以设为函式的返回型态, 它是存于累加器 (accumulator) 的相对位置 → 程式开始执行时, 不会设定 bit 型变数的初始值, 因此程式必须自行设定初始值 → 以下是合法的使用方式 static bit init_flag ; // 定义于函式内则被视为 区域变数 bit toggle_flag ; → 范例 int data = 0x54 ; bit flag ; flag = data ; 则 flag = 0 (取 data 的 LSB) 如果微控制器具有一个以上的 RAM bank, 例如 HT46R63, 在定义 bit 型态的变数时 需要使用前置处理指令 #pragma rambank0 指定在 RAM bank 0, 如下 #pragma rambank0 bit flag ; #pragma norambank 记忆体储存值 sign Biased exponent 1.mantissa 十进位数值 32bit 7DA6B69Bh 0 1111 1011b (=251) 1.0100110101101101001101b (=1.302447676659) 2.77000e+37盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 19 页 共 189 页 4.3.6 储存类别 (storage class) 与修饰词 (qualifier) 储存类别 (storage class) auto, register, static, extern 修饰词 (qualifier) const, volatile, persistent 指定词 (specifier) typedef 变数于宣告或定义时必须指定其资料型态, 但是储存类别及修饰词是可选择的, 可根据应用时的需要去设定或不使用. 储存类别 (storage class) 储存类别与区域变数 (local variable)及全域变数(global variable)有关. → 储存类别 auto auto 是给区域变数使用的, 没有指定储存类别的区域变数皆是 auto 写与不写 auto 都是相同效力. 区域变数是存放在 RAM bank 0 的空间 → 储存类别 register register 与 auto 类似, 是给区域变数用的, 当变数的存取很频繁时, 可将之 设为 register, C 编译器会使用暂存器而非资料记忆体空间来存放此变数, 如此 可增加存取的速度及减少编码. 目前并未实做此功能. → 储存类别 static static 的变数会一直有效到整个程式结束后才失效. 它的初始值只会在程式开始 执行时被设定一次. 虽然 static 的变数在程式结束前皆有效, 但是定义在函式内的 static 变数仍然是区域变数, 必须要在它所定义的函式中才可以读写 → 储存类别 extern extern 通知 C 编译器此变数是定义在其他的程式档内, 需要经由连结器 (Linker) 连结定义此变数的档案后, 才知道变数的所在. 以目前在微控制器的应用程式上, 比较需要使用 extern, 其他三种不具特别的优势 可用定义全域变数(global variabl)的方式即可达到相同效果. 建议不要使用. 修饰词 (qualifier) → 修饰词 const C 编译器会将 const 的变数放置于程式记忆体 (PROM). 在定义 const 变数时, 必须要设定其值, 而程式在执行中不能修改此变数的值 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 20 页 共 189 页 → 修饰词 constant 这个修饰词是 Holtek C 编译器特别提供的. 它会将 constant 的变数放置于程式记 忆体 (PROM) 的最后一页 (last page). 定义 constant 变数时,必须要设定其值, 而 程式在执行中不能修改此变数的值. 使用此修饰词要注意下列三点 ● 只能使用在 int 或 unsigned int 的资料型态 ● 设定值必须配合微控制器程式记忆体的宽度, 例如, 若使用在 HT48R50A-1 时, 因为此微控制器的宽度为 15 个位元, 最高位元是无效的, 所以 0x9A 会被 C 编译器改成 0x1A. 最高位元, 位元 15 被清除为 0 ● 所有设定此修饰词的变数或阵列, 总共占用的字位元组 (word) 不可超过 256 个 指定词 (specifier) → 指定词 typedef typedef 是针对资料型态做新名称的宣告, 不是宣告资料型态的新变数, 而是宣告一 个新的名字. 例如将 UCHAR (新名字) 宣告为 unsigned char 的资料型态, 可使用 typedef unsigned char UCHAR ; // UCHAR 为 unsigned char 的新名字 UCHAR count ; // 变数 count 的资料型态为 unsigned char // 等同于 unsigned char count ; 使用 typedef 宣告资料型态的新名字可以让程式的可读性更高, 更易了解. 例如, typedef unsigned int WORD ; // 使用 WORD 代表 unsigned int, 16 bits typedef unsigned long DWORD ; // DWORD 代表 32 bit 的 double word 4.3.7 绝对变数 (absolute variable) 可以将全域变数或 static 变数指定一个固定的记忆体的位址, 例如 unsigned char PortA @ 0x12 ; 在变数名的后面再加上 ‘@’ 及位址 C 编译器在编译时会将程式中出现绝对变数的程式改为此位址, 但并未在记忆体中 保留位置给此变数, 所以从连结器 (Linker) 产出的对映档 (map file)中找不到 此变数 C 编译器会将之翻成组合语言的 EQU 指令,如下 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 21 页 共 189 页 _PortA EQU 12h 此种用法主要是对微控制器内的暂存器做定义, 方便阅读程式 4.3.8 常数 (constant) 整数型常数 (integral constant) 可使用基底(radix)格式表示之. Radix (进位制) 常数格式 范例 二进制 0bnumber 或 0Bnumber 0b10111110 八进制 0number 0276 十进制 number 190 十六进制 0xnumber 或 0Xnumber 0xBE 若常数的最后一字为 l 或 L, 则表示它使用 signed long 或 unsigned long 的型态. 字尾是 u 或 U, 则表示常数为 unsigned 型态. 浮点型 (floating point)常数的型态是 double, 若它的字尾是 f 或 F, 则是 float. 字元常数 (character constant)必须以单引号 ‘ ‘ 框住, 例如 ‘a’ 字串常数 (string constant) 必须以双引号 “ “ 框起, 例如 “Hello!”. 字串常数的定义 会影响它所储存的记忆体位址, 如下 char *cp = “one” ; // C 编译器会发出错误 const char *sptr = “Hello” ; // “Hello” 储存在程式记忆体 (PROM) 常数型的变数或阵列 (array)必须要设定其值, 否则C 编译器会发出错误讯息,如上例 4.3.9 指标 (pointer) 与阵列 (array) 指标本身是一个变数, 它的内容是另一个变数存放的位址, 类似组合语言的间接定址. 在使用上, 指标必须要指到一个已定义 (存放于记忆体) 的变数, 否则在程式执行时 会发生错误. 指标的宣告格式 资料型态 *指标名 [, *指标名 ,….] ; 资料型态是这个指标所指的变数的资料型态, 例如 char. 指标名类似变数名, 可以在一 行中, 宣告指向相同资料型态的不同指标名, 例如 char *tptr, *array_ptr ; int *line_ptr ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 22 页 共 189 页 以上只是宣告指标变数, 必须要将这些指标指向已定义的变数才能使用 指标的运算子 & 与 * 运算子 & 如果紧邻在变数的前面, 例如 &line, 则是代表取得此变数的记忆体位址 例如, int line ; // 定义变数 int *line_ptr ; // 宣告指标 line = 12 ; line_ptr = &line ; 若变数 line 被安置于 RAM 的位址 64, 则指标 line_ptr 等于 64 运算子 * 紧邻指标变数之前是表示取得这个指标指到的变数的内容, 承上例, int total ; // 定义变数 total = *line_ptr ; // 取得指标 line_ptr 所指到的变数 line 的内容 则 变数 total 等于 12 指标的大小会根据微控制器具有之记忆体空间大小而定, 如果微控制器具有一个以上 的 RAM bank, 则指标本身会使用两个位元组来储存被指到的变数的位址 如果变数有 const 或 constant 修饰词, 则是指向程式记忆体 (PROM), 而且此变数的内容不能被修 改. 如果所指向的变数不具有 const 或 constant, 则指标会指向资料记忆体 (data memory, RAM) 内的变数. 阵列 (array) 阵列是由相同资料型态的元素组成的, 例如 char array_name[32] ; 是由 32 个 char 型态的元素组成的, 这些元素的名字是以阵列名 array_name 为准, 而以索引 (index) 区分各个元素, 例如 array_name[3] 是第 4 个元素. 阵列的索引是 正整数, 从 0 开始直到元素的总个数减一, 上例中, 最后一个元素是 array_name[31]. 这种资料型态在建表格 (table) 时非常有用 阵列也可当做指标的一种, 只是使用时格式不同, 下列范例说明指标与阵列之间的 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 23 页 共 189 页 使用方法 char *nptr, *fptr ; // 宣告指标 char ch, tbl ; // 定义变数 char table[5] = { ‘a’, ‘b’, ‘c’, ‘d’, ‘e’ } ; // 定义阵列 nptr = table ; // 指标 nptr 指到阵列 table 的第一个元素 table[0] ch = *nptr ; // 将字元 ‘a’ 存入变数 ch tbl = table[0] ; // 将字元 ‘a’ 存入变数 tbl , 所以变数 ch 与变数 tbl 的内容相同 fptr = &table[4] ; // 指标 fptr 指到阵列 table 的第 5 个元素, table[4] ch = *fptr ; // 将字元 ‘e’ (第 5 个元素) 存入变数 ch 内 tbl = *(nptr+4) ; // nptr 指到阵列 table 的第一个元素, 加 4, 所以 tbl = ‘e’ 4.3.10 结构 (struct) 与等位 (union) 结构是一个或多个成员的集合, 每个成员的资料型态可以不同, 并且使用结构的名字去 读写这些成员. 结构的宣告是等同于定义一个新的资料型态, 当变数宣告为一个结构的 型态时, 便可用结构的名字去读写各成员的内容. 成员的资料型态不可以使用 bit 型态, 但是可以用 bit-field 方式宣告成员的型态, 例如 struct str_name { unsigned flag : 1 ; // 此成员放于最低位元 least significant bit unsigned no_used : 7 ; unsigned stack : 5 ; // 此成员置于最高位元 high bits } usage ; 每个 bit field 会置放于 16 位元的单位内, 不会横跨两个16位元的单位 等位 (union) 的格式与结构相同, 唯一不同的是记忆体空间的分配方式. 等位是安排 共用的空间来存放不同资料型态的成员. 每个成员必须要宣告其资料型态, 而等位 的大小则是所有成员中占用最大位元组(bytes)的型态大小. 每个成员的开始 位址 皆相同, 就是等位的位址. union union_name { char num_byte ; // 占用 1 个位元组 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 24 页 共 189 页 int num_int ; // 占用 2 个位元组 long num_long ; // 占用 4 个位元组 } number ; 等位 number 的大小为 4 个位元组 结构与等位的型态也可使用修饰词 (qualifier), 例如 const, 其效果与一般的资料 型态相同. 结构的运算子 -> 与 . 指标所指变数的资料型态若是结构或等位, 可使用右箭头运算子 “->”去读写其成员. 也可以使用此变数名以及句点 (dot .)去读写成员变数 范例 : struct tag { char flag ; int number ; unsigned ac : 1 ; unsigned z : 1 ; } ; struct tag status ; // 定义变数 status 是结构 tag 的资料型态 struct tag *sptr ; // 宣告 指标 sptr 为结构 tag 的资料型态 bit vac, pac ; // 定义变数 vac, pac 是 bit 的资料型态 status.flag = 1 ; // 写 1 到 结构的成员 flag status.ac = 1 ; // 写 1 到 结构成员 ac vac = status.ac ; // vac = 1 sptr = &status ; // 指标 sptr 指到结构 status pac = sptr->ac ; // pac = 1 4.4 运算子 (Operators) 运算子是针对变数所存的数据加以运算的符号, 运算式(expression) 是运算子, 数据, 变数, 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 25 页 共 189 页 函式及运算式的组合表示式. 有下列的运算子 → 数学运算子 (arithmetic operators) +, -, *, /, % 分别为加法, 减法, 乘法, 除法, 余数运算子 → 比较运算子 (comparison operators) 将两个运算元做比较, 回传比较后的结果为真 (非零) 或假 (零) > 大于 >= 大于或等于 < 小于 <= 小于或等于 == 等于 != 不等于 → 逻辑运算子 (logical operators) 取运算子的真假值做逻辑运算, 传回结果是真 (非零) 或假 (零) && 逻辑 AND | | 逻辑 OR ! 逻辑 NOT → 位元逻辑运算子 (bitwise logical operators) 位元的逻辑运算 & 位元 AND | 位元 OR ^ 位元 XOR ~ 位元反相 (complement) >> 向右移位元 << 向左移位元 → 算式设定运算元 (assignment operators) += ; 变数加上 expr 的值, 将结果存入变数 -= ; 变数减去 expr 的值, 将结果存入变数 *= ; 变数乘以 expr 的值, 将结果存入变数 /= ; 变数除以 expr 的值, 将商数存入变数 %= ; 变数除以 expr 的值, 将余数存入变数 &= ; 变数与 expr 的值做位元 AND, 将结果存入变数 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 26 页 共 189 页 |= ; 变数与 expr 的值做位元 OR, 将结果存入变数 ^= ; 变数与 expr 的值做位元 XOR, 将结果存入变数 >>= ; 变数向右移 expr 个位元, 将结果存入变数 <<= ; 变数向左移 expr 个位元, 将结果存入变数 → 递增 (increment) 与递减 (decrement) 运算子 ++ 变数先加 1, 再做运算 ++ 先做运算, 变数再加 1 -- 变数先减 1, 再做运算 -- 先做运算, 变数再减 1 → 条件运算子 (conditional operator). ? : ; 若 运算的结果为真, 则执行 , 否则就执行 → 运算子的优先性 (priority)与结合性 (associativity) 一个运算式中包含多个运算子时, 必须注意运算子执行的优先顺序 (priority). 避免运算结果与预期的不合, 下表为运算子的优先顺序及结合性, 前面栏位内的运算子, 具有较高的优先权. 同一栏位中的运算子则由结合性决定优先 运算子 说明 结合性 [ ] ( ) -> sizeof 阵列元素 函式或运算式中的括号 结构成员或等式成员的间接存取 数据所占的位元组大小 (byte) 由左到右 ++ -- ~ ! - + & * 增 1 减 1 取 1 补数 (one’s complement) 否定 负号 (unary minus sign) 正号 (unary plus sign) 取变数的位址 存取指标所指位址的内容 由右到左 * 乘法 由左到右 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 27 页 共 189 页 / % 除法 余数 + - 加法 减法 由左到右 << >> < <= > >= 左移 右移 小于 小于或等于 大于 大于或等于 由左到右 == != & ^ | && || ? : 相等 不相等 位元 AND 位元 XOR 位元 OR 逻辑 AND 逻辑 OR 条件运算 由左到右 = *= /= %= += -= <<= >>= &= |= ^= 设定 相乘再存入变数 相除再将商数存入变数 取余数再存入变数 相加再存入变数 相减再存入变数 左移再存入变数 右移再存入变数 位元 AND后, 再存入变数 位元 OR 后, 再存入变数 位元 XOR 后, 再存入变数 由右到左 , 分隔变数或算式 由左到右 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 28 页 共 189 页 运算前的型态转换 当运算子要处理不只一个运算元 (operand) 时, 这些运算元必须是相同的资料型态 C 编译器会自动将运算元的型态转换成一致, 为避免因转换而造成资料的遗漏, 将以转成 较大的型态为原则. 有些情况, 即使运算元的型态是一样, 但是在执行运算前也会先做型态 的转换, C 编译器会根据运算的状况自动做转换. 如果希望运算能照所要的方式运作, 可以 对运算元的资料型态做强制性的转换 (type cast). 范例 : char count, a = 0 , b = 50 ; if( a – b < 10 ) count++ ; C 编译器做减法及比较, 结果是 –50, 因此 count++ 会被执行. 若改成 if( (unsigned int)(a – b) < 10 ) count++ ; 则因为先转成 unsigned int, 运算结果是 206, 所以 count++ 不会被执行 位移运算子 (shift operators) 会根据运算元的型态做不同的处理. 右移运算子 (right shift operator, >>) 对 signed char/int/short/long 型态的运算会将原先的最 高位元 (most significant bit, MSB) 复制到右移后的最高位元, 例如 signed int 0x0124 在右移一个位元后, 变成 0x0092 (MSB=0) signed int 0x8024 在右移一个位元后, 变成 0xC012 (MSB=1) 右移运算元对 unsigned char/int/short/long 型态的运算, 会将 MSB 填 0 左移运算元对 unsigned/signed 型态的运算, 皆会将最低位元 (least significant bit) 清为零 4.5 程式流程控制 (program flow control) 在熟悉程式架构, 变数及其资料型态, 运算式及运算子之后, 根据功能需求去设计最恰当的 程式流程, 使用最适合的流程控制叙述式.下列各叙述的语法中, 粗字是保留字, 使用到此 叙述时必须用这个字. 不可使用这些保留字当做变数名. 语法内的中括号代表被围住的部分 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 29 页 共 189 页 是可选择的, 可有可无, 视需要决定. 4.5.1 if-else 叙述 语法 : if( cond-expression ) statement1 ; [ else statement2 ; ] 说明 : 这是一个条件判断的控制叙述. 在不同的条件下会需要不同的处理时, 可以使用 这种叙述做不同的流程控制. 如果条件 cond-expression 的结果为真 (不等于零) 则 statement1 会被执行. 否则, 若有 else 部分, 则会执行 else 部分的叙述 statement2. 中括号代表此部分可有可无, 完全视程式有无需要. statement1 及 statement2 可以不只一个叙述, 如果超过一个叙述时, 必须要以 左右大括号包含这些叙述. 范例 : if( seconds > 59 ) { minutes++ ; seconds = 0 ; } else seconds++ ; 4.5.2 switch 叙述 语法 : switch( expression ) { case constant1 : statement1 ; break ; case constant2 : statement2 ; break ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 30 页 共 189 页 …… [ default : statementX ; break ; ] } 说明 : if-else 是针对只有两种结果的运算式 (expression) 导出对应的流程, 如果运算 式的结果超过两个值, 使用 switch-case 叙述会让程式增加可读性, 更容易了解 程式的功能. 如果 expression 的结果是 constant1, 则 statement1 会被执行. 若 expression 的结果等于 constant2, 则 statement2 会被执行. 如果没有任何的 case 与 expression 的结果相等, 则 default 部分的 statementX 会被执行. default 部分可有可无. 每个 case (或 default) 中可以有多个叙述, 例如 case constant2 : statement20 ; statement21 ; …… break ; expression 可以是一个变数也可以是一个能计算出数值的运算式. 不要忘记在每个 case 的最后一个叙述后, 加上 break ; 叙述行, 否则一直 到下一个 break 叙述之前或 switch 右括号之前的所有叙述皆会被执行, 除非 程式确实要如此执行. 下例中, 如果 case ‘1’ 中没有 break ; 则当 input_key 等于 1 时, color=RED 被执行, 因为没有 break, 所以下一个叙述 color = GREEN 也被执行, 结果就不是程式所要的 范例 : switch( input_key ) { case 1 : color = RED ; break ; case 2 : color = GREEN ; break ; …… 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 31 页 共 189 页 default : color = WHITE ; break ; } 4.5.3 for 叙述 语法 : for( initial-expression ; cond-expression ; update-expression ) statement1 ; 说明 : for 叙述是重复执行相同的动作 (叙述). 做条件回圈的功能. 首先会执行 initial-expression, 通常是初始设定一些变数, 这个运算式只会执行 一次. 如果没有此运算式, 则会直接做 cond-expression 的比较. cond-expression 是条件判断式, 与 if-else 的 cond-expression 相同. 当条件 成立时, 则会去执行 statement1. 否则会离开此回圈, 跳到 statement1 之后的 叙述去执行. 在条件成立并执行 statement1 之后, 接着会去执行 update-expression 的部分, 再回到 cond-expression 做比较. 重复此动作, 直到 条件不成立, 就跳出此回圈, 回到下一个叙述. 如果 update-expression 不存在, 则会直接对 cond-expression 做比较. statement1 可以是一个以上的叙述, 但必须要以左右大括号包含这些叙述. 范例 : 要将阵列变数的每个元素清为零, int idx ; char cbuf[20] ; for( idx = 0 ; idx < 20 ; idx++ ) cbuf[idx] = 0 ; 4.5.4 while 叙述 语法 : while( cond-expression ) statement1 ; 说明 : 与叙述 for 有类似的条件回圈功能. 但是没有 initial-expression 与 update-expression. 只要 cond-expression 条件成立就执行 statement1 直到 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 32 页 共 189 页 条件不成立, 则跳出回圈. statement1 可以不只一个叙述, 但要用左右大括号 包含之. 范例 : int idx ; idx = 0 ; while( idx < 20 ) { cbuf[idx] = 0 ; idx++ ; } 4.5.5 do-while 叙述 语法 : do { statement1 ; } while( cond-expression ) ; 说明 : do-while 会先执行 statement1, 之后再做 cond-expression 的比较, 若条件 成立, 就再执行 statement1, 一直到条件不成立, 就跳出回圈. 这个叙述与 while 叙述相似, 不同点是 statement1 至少会执行一次. statement1 可以不只 一个叙述 范例 : idx = 0 ; do { cbuf[idx] = 0 ; idx++ ; } while( idx < 20 ) ; 4.5.6 goto 叙述 语法 : goto label ; 说明 : goto 是一种强制性的流程控制叙述. 可以直接跳出回圈将程式移转到标名 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 33 页 共 189 页 label 的叙述或是从 switch-case 中移转到 label 处. 标名 label 必须和 goto 叙述在同一个函式内, 也就是不允许跳到其他的函式里. 范例 : 参看 4.5.7 范例 4.5.7 break 与 continue 叙述 语法 : break ; continue ; 说明 : break 是从回圈或是 switch case 跳出, 一次只能跳出一层 continue 是跳过 continue 叙述之后的其他叙述, 再从下一回圈继续执行 范例 : idx = 0 ; while( idx < 20 ) { if( cbuf[idx] == 0 ) // cbuf[] 没有字元 break ; // 跳出 while 的回圈 idx++ ; } for( idx =0 ; idx < 20 ; idx++ ) { if( cbuf[idx] != 0 ) continue ; // cbuf[] 仍有字元, 回到 idx++ 继续 goto count_l ; // 没有字元, 计算字元总数 } …… // 其他叙述 count_l : // 标号 // idx = 字元总数 4.6 函式 (Functions) 函式是叙述的集合. 所有可被执行的叙述必须定义于函式中 (main() 是主函式). 使用函式 前, 必须要宣告及定义函式, 否则编译器会发出错误讯息. 除了内容的叙述, 函式最重要的是 参数 (arguments) 及返回值 (return values). 它的格式如下 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 34 页 共 189 页 return-type function_name(var-type arg1, var-type arg2, …) { statements ; } 其中, return-type 是函式的返回值的型态,可使用 4.2 节中的资料型态 function_name 是函式名, 可由字元(character), 数字(0~9), 底线(underscore) 所组成. 采用 case-sensitive. 大写字元与小写字元是不同的. var_type 是参数的资料型态, 除了 bit 型态不可使用外, 其余皆可 arg1, arg2, .. 是参数名称, 采用 case-sensitive. 大写字元与小写字元是不同的. 4.6.1 参数 (arguments) 根据参数的资料型态 (data type), 是以下列的存放方式传入被呼叫的函式. → 所有参数皆存放于内部变数中, 变数的名字是以函式名为准, 再加上数字, 从 0 开 始, 例如函式名字为 multiple, 则第一个参数会存入变数 multiple0, 第二个参数则 存放于变数 multiple1, 以此类推. 如果参数型态为 int 或 unsigned int, 则使用两个 位元组储存, 例如第二个参数是 int 型态, 则变数 multiple1 储存参数的低位元组, 变数 multiple1+1 的位置储存参数的高位元组 4.6.2 返回值 (return values) 从组合语言的角度看 → 返回值的大小为一个位元组 (byte), 则此值会放入累加器 (accumulator) 传回 呼叫函式的叙述行 → 返回值是两个位元组 (int, short), 则返回值的低位元组 (low byte) 存放在累加器, 高位元组 (high byte) 则放在变数 RH. → 返回值是四个位元组 (long, float), 则返回值的低字低位元组 (low byte of low word) 存放在累加器, 返回值的低字高位元组 (high byte of low word) 放在变数 RH. 返回值的高字低位元组 (low byte of high word) 放在变数 RM. 返回值的高字高位 元组 (high byte of high word) 放在变数 RU. 为了让程式更模组化及可读性更高, 将程式的各功能分别以独立之函式实作之, 会是最佳的 方式. 主函式负责管理及呼叫各功能的函式, 这些函式可以不要与主函式放在同一个程式档. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 35 页 共 189 页 4.7 中断服务函式 (Interrupt Service Routines) 微控制器的硬体中断可以用 C 语言撰写它的中断服务函式 (Interrupt service routine, ISR). 必须遵照下列的规则来定义中断服务函式 → 函式的返回值型态必须是 void → 函式不可有参数 (parameter) → 使用 #pragma vector 设定中断服务函式的中断向量 (interrupt vector) 使用 @ 指定中断向量 → 最好不要从程式的其他地方呼叫中断服务函式 → 针对不具有中断可重叠(nested)发生的微控制器, 则在中断服务函式内不可开启中断功能 范例 #pragma vector ISR_Timer @ 0x08 void ISR_Timer(void) { } // 定义 : 返回值的型态, 没有参数, 设定中断向量为 0x08 C 编译器会根据中断服务函式对暂存器的需要, 在进入中断服务函式后, 将这些暂存器的内 容储存. 等执行完中断服务函式的工作后, 再恢复先前所储存的暂存器内容. 最后回到被中 断的地方继续执行, 同时中断功能也打开, 允许中断产生. 在程式的其他地方若需要将中断 功能开启 (enable), 可自行设定相关的暂存器. 当中断服务函式正在处理此中断时, 另一个更优先的中断发生并需要尽快处理, 则微控制器 必须暂停目前的中断服务函式, 转而处理新发生并且是更优先的中断事件. 亦即微控制器具 有中断可重叠 (interrupt nested)的特性. 在编译中断服务函式时, Holtek C compiler 会将一些 常用的暂存器的内容保存于资料记忆空间 (RAM space), 当中断服务函式完成其工作后, 在 返回原先被中断处之前, 再将这些内容还原到暂存器. 由于要支援可重叠的中断事件, Holtek C compiler 对于每一个中断向量保留一组资料记忆空间, 例如中断向量一 (INT 1)的中断服 务函式会将暂存器的内容保存于 V1xx 的记忆空间, 中断向量二 (INT 2)的中断服务函式会 保存在 V2xx 记忆空间, 其他的中断以此类推. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 36 页 共 189 页 中断服务函式会保存的暂存器内容包括 累加暂存器(accumulator, ACC), 状态暂存器 (status, S), 记忆体区块指标 (bank pointer, BP), 间接位址指标 (MP0, IAR0) 以及在算术运算 中使用的中间变数 (以 T 开头的, 例如 T2, T3,…). 这些暂存器或中间变数将会保存于对应 的变数, V1A, V1S, V1BP, V1MP0, V1MP1, V1T2, V1T3,…(中断向量一), V2A,V2S,V2BP, V2MP0,V2MP1,V2T2,V2T3,… (中断向量二), 其他的累推. 如果微控制器不具有 BP, MP1的 则不需要保存这些暂存器的内容. 虽然不同的中断事件可以重叠发生, 但是同一个中断事件 并不可以重叠产生, 必须等候前一个发生被处理完成后, 才能认可下一个中断事件. 可以在 中断服务函式中将同一个中断除能 (disable interrupt) 如果微控制器中不同类的中断事件不可重叠发生, 则 Holtek C compiler 可以只使用一组记 忆体空间保存这些暂存器的内容, 但是必须在定义中断服务函式的程式中指明之. (使用 #pragma novectornest) 而在中断服务函式中要将中断功能停止 (disable interrupt, _emi=0) 4.8 在 C 语言程式中嵌入组合语言 (in-line assembly code) 如果想要让编译后的程式码更为精简, 执行上更有效, 可以在C程式中加入组合语言的指令. 格式如下 #asm 组合语言指令 …… 组合语言指令 #endasm #asm 与 #endasm 是前置处理指令, 指示 C 编译器将其中的组合语言指令取出 当做输出的执行码 在 C 语言的条件结构中, 如 if, while, do 之类, 不要使用 #asm block 的格式去内嵌 组合语言指令. 4.9 前置处理指令 (Preprocessor) 前置处理指令是以 # 为字首的文字串, 程式中所有的前置处理指令会最先被前置处理器 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 37 页 共 189 页 (preprocessor) 处理. 这些指令提供的功能有定义文字符号 (define), 巨集指令 (macro), 引入档案 (include file), 条件式编译 (conditional compiler) 与特殊选项 (pragma) 4.9.1 定义文字符号 (#define) → #define 语法 : #define sym_name replaced_text 说明 : 定义 sym_name 为 replaced_text. 而 replaced_text 可以是数值, 运算式 或文字串. 前置处理器会将程式中所有含 sym_name 的叙述, 更换成 replaced_text. 范例 : #define TOTAL_COUNT 40 #define PA0 _12_0 → #undef 语法: #undef sym_name 说明 : 取消先前定义的文字符号或前置处理巨集 范例 : #undef PA0 // 取消先前定义的 PA0 4.9.2 引入档案 (#include) → #include 语法: #include “file_name” 或 #include 说明 : 将指定档案的内容嵌入所在的程式处. 当 file_name 以双引号 “ 包含时, 编译器先到工作目录中找寻此档, 若找不到, 则到目前的目录找寻, 若找不到就发出错误讯息 当 file_name 是以角号 < > 包含时, 则只到环境参数设定的目录中去找寻 4.9.3 内嵌组合语言 (inline assembly) → #asm #endasm 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 38 页 共 189 页 语法 : #asm 组合语言的指令, 例如 MOV A, 1 #endasm 说明 : 在 #asm 与 #endasm 之间嵌入组合语言指令. 范例 : #asm AND A, 0Fh SUB A, 09h #endasm 4.9.4 条件式编译 (#if/#endif) → #if #else #endif 语法: #if expression statements1 ; [#else statements2 ; ] #endif 说明 : 控制编译器体有条件地编译程式. 当 expression 为真时, statements1 的 程式会被编译, 否则 statements1 会被忽略, 若有 #else 部分, 则会编译 statements2. #else 部分可有可无, 视需要 范例 : #if MODE > 0 #define DISP_MODE MODE #else #define DISP_MODE 7 #endif → #ifdef 语法 : #ifdef symbol statements1 ; [ #else 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 39 页 共 189 页 statements2 ; ] #endif 说明 : 如果 symbol 在前面已被定义 (用 #define ), 则 statements1 被编译, 否则 会编译 statements2. #else 部分可有可无 → #ifndef 语法 : #ifndef symbol statements1 ; [ #else statements2 ; ] #endif 说明 : 与 #ifdef 相反, 如果 symbol 在前面未被定义, 则 statements1 被编译, 否则会编译 statements2. #else 部分可有可无 → #elif 语法 : #if expression1 statements1 ; #elif expression2 statements2 ; #endif 说明 : 如果 expression 为真, 则 statements1 被编译, 否则若 expression2 为真, 则会编译 statements2. #elif 是 #else #if 的缩写 前置处理指令 功能 范例 #asm 指示在此行之后是”内嵌组合语言 的指令” (in-line assembly) #asm add a, 1 #endasm #define 定义符号或前置处理的巨集指令 #define COUNT 20 #define OK #define add(a,b) ((a)+(b)) #elif 是 #else #if 的缩写 参看 #ifdef 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 40 页 共 189 页 #else 定义 “条件不成立的”原始程式行 参看 #if #endasm 指示 ”内嵌组合语言指令” 的结束 参看 #asm #endif 结束 “条件型的原始程式行” 的嵌入 参看 #if #error 产出错误讯息 #error Size too big #if 定义 “条件成立的” 原始程式行 如果条件成立, 则将其后的原始程 式行嵌入到程式中 #if tick < 10 tick++ ; #else tick = 0 ; #endif #ifdef 如果前置处理符号已定义, 则将其后 的原始程式行嵌入 #ifdef COUNT delay() ; #else nothing() ; #endif #ifndef 如果前置处理符号未定义, 则将其后 的原始程式行嵌入 #ifndef COUNT nothing() ; #endif #include 将标头档的内容嵌入 #include “ht46R23.h” #pragma 编译器的特殊选项 参看本节说明 #undef 取消先前定义的符号或前置处理巨集 #undef COUNT 4.9.5 编译器的特殊选项 pragma 格式 #pragma keyword [ options ] 某些 keyword 会有 options, 表格列出 pragma 的 keywords keyword options 功能说明 bp_free 函式中不做 BP 暂存器的改变 ❷ bp_nofree 取消函式中不做 BP 暂存器改变的功能 ❷ function V 设定函式被安置的 ROM 位址 nobp 中断服务函式中不储存 BP 暂存器的内容 ❷ 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 41 页 共 189 页 nolocal V 设定函式的参数及内部变数为一般型, 非 LOCAL ❷ nomp0 中断服务函式中不储存 MP0 暂存器的内容 ❷ nomp1 中断服务函式中不储存 MP1 暂存器的内容 ❷ rambank0 norambank 设定变数安置于 RAM bank 0 的功能, 例如, bit 型态 ➌ 取消变数安置于 RAM bank 0 的功能 ➌ rombank0 norombank 设定函式安置于 ROM bank 0 的功能 ❶ 取消函式安置于 ROM bank 0 的功能 ❶ rombank V 指定函式安置于那个 ROM bank ❶ vector V 定义中断服务函式 novectornest 中断服务函式的暂存器保存空间只有一组, 中断事件不可 重叠 (no nested) ❶ 凡具有多个 ROM bank 记忆体的微控制器 (例如 HT46RU67) 此功能才有效 ❷ 凡具备多个 RAM bank 及只有一个 ROM bank 记忆体的微控制器 (例如 HT82M99AE ) 此功能才有效 ➌ 凡具备多个 RAM bank 记忆体的微控制器 (例如 HT46R63) 此功能才有效 下列为各 keyword 的功能 → #pragma bp_free #pragma bp_nofree 对于这两个前置处理指令所包含的叙述式 (statements), 编译器会将产出之指令码 中所有会改变 BP 暂存器的指令删除, 也就是 MOV BP, A 指令不会产出. 主要是 缩减编译产出的指令码大小. 前题是必须确定变数所在的记忆体皆是固定的 RAM bank. 从 #pragma bp_free 之后的叙述开始, 一直到 #pragma bp_nofree 为止 可将两个前置处理指令加在函式中的任何叙述的前后 → #pragma function function_name @rom_address 指定函式 function_name 安置于 ROM 记忆体的 rom_address 位址处. rom_address 可使用十六进制, 例如 0x100 代表指定函式放于 ROM记忆体的 256 位址处. 如果要放在 ROM bank 1 的 256 位址, 则将 rom_address 设为 0x2100 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 42 页 共 189 页 → #pragma nobp 只对中断服务函式有效. 指示编译器不要在中断服务函式中保存BP暂存器的内容. 这个指令会对单一程式档案内所有的中断服务函式有效 → #pragma nolocal function_name 针对一般的函式. 对于指定的函式 , function_name 在编译时将函式使用到的局部 变数与内部暂存器皆设成一般型态的变数, 而非会与其他变数共用RAM 记忆空间 的共用型态 (common type). 如果中断服务函式会呼其他函式时, 这些被呼叫的函 式中使用的局部或内部变数若与其他函式共用RAM 空间, 会有资料被破坏的风险. 这个前置处理指令将排除这种风险. → #pragma nomp0 #pragma nomp1 只对中断服务函式有效. 指示编译器不要在中断服务函式中保存 MP0 或 MP1 暂存器的内容. 这个指令会对单一程式档案内所有的中断服务函式有效 编译器会根据微控制器的架构决定是否在中断服务函式内保存 BP, MP0, MP1 暂 存器的内容 → #pragma rambank0 #pragma norambank 指定变数存放在 RAM bank 0 的空间内. 所有定义在这两个前置处理指令内的变数 皆被安排在 RAM bank 0 空间. 如果微控制器只具备一个 RAM bank, 则不需要使 用这个指令. 如果微控制器具有一个以上的 RAM bank 时, 可以使用. 若变数是 bit 资料型态时, 则必须要将变数定义在 RAM bank 0 之中. → #pragma rombank0 #pragma norombank 将函式设置于 ROM bank 0 内. 所有定义于这两个前置处理指令中的函式皆被安置 ROM bank 0 的空间内 → #pragma rombank banknum function_name1 [, function_name2 [, ..]] 这个前置处理指令会将所指定的函式安置在指定的 ROM bank 之中. banknum 是 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 43 页 共 189 页 ROM bank 的编号, 可依据微控制器的 ROM 架构设定之. function_name1, function_name2 是函式名, 可以同时指定一个以上的函式. → #pragma vector isr_name @vector_address 宣告中断服务函式的名字及中断向量值. 每个中断服务函式皆要预先宣告. isr_name 是函式名称, vector_address 是中断向量数值 → #pragma novectornest 指示中断服务函式中, 保留暂存器的记忆空间只有一组. 当微控制器的中断事件不允 许重叠发生, 必须处理完成前一个之后才能处理下一个时, 使用此前置处理指令可以 节省资料记忆空间 (RAM space), 避免 RAM bank 0 的空间满溢 (overflow) 请参阅 4.7 节 – 中断服务函式 – 的说明 4.10 Holtek C 编译器的内建函式 (built-in functions) 直接编译成组合语言指令的内建函式 C 函式 组合语言指令码 (assembly instruction) void _clrwdt( ) CLR WDT void _clrwdt1( ) CLR WDT1 void _clrwdt2( ) CLR WDT2 void _halt( ) HALT void _nop( ) NOP 其他内建函式 void _rr (char *p) 将指标 p 指到的位元组 (1 byte) 向右旋转一个位元 (bit) 例如, ch = 0xA5 ; _rr(&ch) ; 则 ch = 0xD2 void _lrr (int *pl) 将指标 pl 指到的字元组 (2 byte) 向右旋转一个位元 (bit) 例如, cnt = 0xA5A5 ; _lrr(&cnt) ; 则 cnt = 0xD2D2 void _rl (char *p) 将指标 p 指到的位元组 (1 byte) 向左旋转一个位元 (bit) 例如, ch = 0xA5 ; _rl (&ch) ; 则 ch = 0x4B void _lrl (int *pl) 将指标 pl 指到的字元组 (2 byte) 向左旋转一个位元 (bit) 例如, cnt = 0xA5A5 ; _lrl (&cnt) ; 则 cnt = 0x4B4B 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.20 日 期 2008/5/26 第 44 页 共 189 页 void _swap(char *p) 将指标 p 指到的位元组 (1 byte) 四个低位元与四个高位元互换 例如, ch = 0xA5 ; _swap (&ch) ; 则 ch = 0x5A void _delay(unsigned long tick) 延时 tick 个指令周期 (instruction cycle) tick <= 263690, 若 tick = 0, 则表示无限延时 (infinite loop) (请参考 5.2.2 节的说明) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 45 页 共 189 页 第五章 基本 C 语言程式 本章主要以范例介绍 C 语言程式的写法, 从最简易的语法开始, 配合微控制器的特性, 撰写易懂并且较常用的程式. 当熟悉这些基本的用法后, 后面的章节将介绍进阶的使用 语法. 5.1 C 语法观念 → C 叙述式中的等号 ‘=’ (assignment) 具有写入, 指定, 输出的动作及功能. 例如 PortAC = 0x01 ; 若 PortAC 是输出输入埠A 的控制暂存器 (0x13), 上式则表示设定埠A 位元0 为1 其他位元为 0, 就是设定埠A位元0 是输入口, 其他位元是输出口. 这个叙述相当于 组合语言的 MOV A, 01 MOV PAC, A ;; PAC = [13H] → 如果要使用 条件语句的相等, 则使用两个相连的等号 == 而不相等则用 != if( count == 10 ) { } 代表如果变数 count 等于 10 则执行括号内的叙述, 否则直接跳到括号之后 如果上式写成 if( count = 10 ) 则会先将 10 写到变数 count 中, 再检查 count 是否为真 (不为 0) 本例一定为真, 括号内的叙述一定会被执行, 所以可能是一个错误. → 数值的写法与组合语言的差异 十六进位是以 0x 开头, 其后可以用 0 到 9 与 A 到 F, 大小写皆可 例如, 0x13, 0xad2, 0x2B → 在等号 ‘=’ 右边的变数或暂存器, 具有读出的动作及功能, 例如 status = PortA ; 从埠 A 读进输入值并存入变数 status. 它的功能有如组合语言的 MOV A, PortA ;; PortA = [12H] 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 46 页 共 189 页 MOV status, A 5.2 回圈的应用 (loop) 当程式需要重复执行相同或类似的动作时, 可以用回圈的语法完成. 例如, 要将资料记忆 体的内容清除为零, 或是对输出/输入埠的每个位元轮流做设定或清除等. 这些语法的关 键字包括 for, while, do-while 等, 下列范例将说明如何使用 5.2.1 范例一 : 8 LED 霹雳灯 功能说明 : 使用 HT48R06A-1 控制八颗 LED 构成的霹雳灯 电路说明 : 将 HT48R06A-1 埠 A 的八个引脚接上电阻及LED, 从埠 A 控制 LED 的亮与灭 程式及说明 : 程式将对埠 A 的八个位元依序设定, 分别点亮连结的LED #include “ht48r06A-1.h” // 引入标头档 ht48r06a-1.h, 需要用到其中已定义的变数 // _pa 是埠 A 资料埠的位址, _pac 是埠A的控制暂存器 void main(void) // 定义主函式 { char status, cnt ; // 定义区域变数, 当作 LED 点亮的控制码 _pac = 0 ; // 写 0 到埠 A 的控制暂存器, 设定埠A 为输出型态 for( cnt = 0 ; cnt < 8 ; cnt++ ) // 回圈, 共执行 8 次点亮 LED的动作 { status = (1 << cnt) ; // 第一次是PA0, 第二次是PA1, … 的LED被点亮 _pa = status ; // 输出到埠A, 点亮一个LED及熄灭其他 LED } } 尝试 : 将上面程式中的 for 回圈改为 while 回圈 5.2.2 延迟(等候)时序 (Delay) 当处理微控制器的周边装置时会有严格的时序要求, 利用 C 语言设计一个好的时序 控制程式将会对 C 语言的应用专案有很大的帮助, 下列的方式可以达到此目的 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 47 页 共 189 页 → 用 in-line assembly 方式将组合语言的时序控制程式嵌入或引入 C 程式中 若以呼叫函式的方式, 则需要了解如何混合使用 C 语言及组合语言的程式. 若以巨集指令的方式, 则会增加程式的大小, 不利 PROM 空间的灵活使用 这种写法比较适合延迟时间小于 5 个指令周期的应用上 → 以 C 语言实作时序控制程式, 则需要计算编译后的组合指令实际执行的时间 有些时序并无法做到精准的程度, 需要做适度的调整 本节将以 C 语言实作一个时序控制程式, 分析及提供一个较适合的函式, 可让 用者轻松地控制所要的时序. 此函式是以 Holtek C compiler 版本编译后的指令码 为准. 若用其他 C编译器来编译此函式, 则需要做类似的分析, 结果可能会有差异 5.2.2.1 时序控制程式 (一) for 回圈的写法 → 定义时序控制程式 delay void delay (unsigned int cycle) { unsigned int count ; for( count = cycle ; count > 0 ; count-- ) ; } 输入参数 cycle 将决定延迟的指令周期 (instruction cycle). 根据微控制器的频率 可以计算出执行所需要的时间. 1 代表 26 cycle, 2 是 34 cycle,.. 编译后的组合语 言程式如下 呼叫 delay(count) 的参数会存入变数 delay0 (低位元组) 及 delay0+1 (高位元组) for( count=cycle ; count > 0 ; count-- ) 0000 0700 R MOV A,delay0 0001 0080 R MOV count,A 0002 0700 R MOV A,delay0[1] 0003 0080 R MOV count[1],A 0004 2800 R JMP L5 0005 L2: 0005 0700 R MOV A,count 0006 0A01 SUB A,01h 0007 0080 R MOV count,A 0008 380A SNZ [0AH].0 0009 1580 R DEC count[1] 000A L5: 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 48 页 共 189 页 000A 1080 R SZ count 000B 2800 R JMP L2 000C 1080 R SZ count[1] 000D 2800 R JMP L2 000E L1: 000E 0003 RET // CALL, JMP, RET 需要 2 个 instruction cycle // SZ 如果会 skip 下一个指令时, 也需要 2 个 instrutcion cycle 上述的范例仅供参考, 因为编译后的指令较难满足实际精确的等候时间, 主要是介 绍如何撰写C语言程式 5.2.2.2 时序控制程式 (二) – while 回圈写法 → 定义时序控制程式 delayw void delayw (unsigned int count) { while ( count != 0 ) count-- ; } 更换成使用 while 的回圈方式, 编译后的指令码如下 0000 2800 R JMP L8 0001 L7: 0001 0700 R MOV A,count 0002 0A01 SUB A,01h 0003 0080 R MOV count,A 0004 380A SNZ [0AH].0 0005 1580 R DEC count[1] 0006 L8: 0006 1080 R SZ count 0007 2800 R JMP L7 0008 1080 R SZ count[1] 0009 2800 R JMP L7 000A L6: 000A 0003 RET // CALL, JMP, RET 需要 2 个 instruction cycle // SZ, SNZ 如果会 skip 下一个指令时, 也需要 2 个 instrutcion cycle count 为 1 时会等候 22 个指令周期, count 为 2 时等候 30 个周期. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 49 页 共 189 页 5.2.2.3 时序控制程式 (三) – 呼叫编译器的内建函式 _delay() void _delay(unsigned long clocks) 参数 clocks 是指延迟的指令周期次数 (instruction cycle count), 例如若 MCU 的频率是 4MHz, 则每个指令周期的执行时间为 1us, 即可计算出 需要的次数. 有效的次数为 1 ~ 263690, 如果是 0 则表示要无限期的 延迟. 可以使用这种方式以等候硬体中断的发生 这个函式可以精准计算出确切的时间并实际的执行, 如应用上需要精确的 时间, 可以呼叫此函式. 如果所需之时间超出 263690 次数, 可以重复呼 叫此函式. 5.3 撰写 MCU 应用程式的注意事项 a) 应用程式模组化. 将各功能以函式(function) 独立出来, 分别定义于不同的档案中. 以功能来命名函式及档案名, 容易记住及了解程式的功能. 可减少各档案编译的时间, 程式编译后的错误不致过多. 日后的维护及增修也相对的容易. b) 变数名要定义的有意义, 在撰写程式及维护时较方便使用并且不易出错, 节省撰写 时间 c) 尽可能不要对下列的 MCU 暂存器 (register) 做读写的动作 C 编译器所编出的组合语言指令会使用到这些暂存器, 如果应用程式中也使用, 会造成混乱并影响程式执行的正确性. 暂存器名称 记忆体(RAM)位址 C语言的变数名字 (定义在标头档 .h 中) IAR0 0x00 _iar0 MP0 0x01 _mp0 IAR1 0x02 _iar1 MP1 0x03 _mp1 BP 0x04 _bp ACC 0x05 _acc PCL 0x06 _pcl STATUS 0x0a _status 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 50 页 共 189 页 另外, 标头档 (.h) 中定义的符号 _c, _ac, _z, _ov, _pdf 与 _to 皆不要使用. 例如, if( _c ) a = ah + bh + 1 ; 就是不好的写法 d) 在 Holtek C compiler 下, 若程式需用下列的内建函式 (built-in function) 时, 要注意 参数的资料型态, 而且需要做函式的宣告. 例如 extern void _delay(unsigned long) ; _rr(char *) , _rrc(char *), _lrr(int *), _lrrc(int *), _rl(char *), _rlc(char *), _lrl(int *), _lrlc(int *), _swap(char *), _delay(unsigned long) 上述内建函式的返回值型态 (return type) 皆是 void. 如果需要对变数做 左移/右移 的动作时, 可以使用下列的运算子 count = time >> 3 ; count = entry << 4 ; C 语言中没有旋转 (rotate)的运算子, 可以使用 signed 型态及左右移运算子 来完成 e) 记忆体的清空 (initialize to zero) Holtek C 编译器提供一个执行阶段的函式库函式 (run-time library) ClearMemory(). 使用者需要在档案的最前面将标头档引入(include). 例如若要使用HT48R06A-1的微控 制器, 则写入 #include “HT48R06A-1.h” 即可使用清除记忆的函式. 但是要注意下列 的程式写法, 某些会造成执行时的错误 有关设定变数的初始值, 下列的方式皆可行 1) 设定全域变数的初始值 (global variables) 在函式外面定义及设定初始值的变数皆属于这类. 此类设定不影响清除记忆体函 式的执行正确性. 所以如要使用ClearMemory()函式及设定变数的初始值, 最好使 用此方式. const char text[4] = “test” ; // 必须是 const 的资料型态 const int number[5] = {0x41, 0x42, 0x43, 0x44, 0x45 } ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 51 页 共 189 页 2) 设定区域变数的初始值 (local variables) 如下列则是定义区域变数 #include “HT48R06A-1.h” void main(void) { char *logo1 = “Holtek“ ; // 方式一, 区域变数, 设定初始值 char logo2[7] = “Holtek” ; // 方式二, 区域变数, 设定初始值 char logo3[7] = {‘H’, ‘o’, ‘l’, ‘t’, ‘e’, ‘k’ } ; // 方式三, 区域变数, 设定初始值 char dst[10] ; ClearMemory() ; // 清除记忆体空间 strcpy(a, logo1) ; // logo1字串已被清除, strcpy(a, logo2) ; // logo2字串已被清除, strcpy(a, logo3) ; // logo3字串已被清除, strcpy(a, “Holtek”) ; // 方式四, “Holtek” 字串已被清除 } void strcpy(char *t, const char *s) { } 在执行 ClearMemory()函式之前, 设定区域变数初始值的三种方式会先将初始值 “Holtek” 复制到资料记忆体(RAM)空间, 因此当执行完ClearMemory()函式之后, 这些资料就被清除, 接下来的 strcpy() 函式中参数 logo1, logo2, logo3所指到的 记忆体内容将会是 0. 如果是使用方式四, 直接以参数设定其值, 即使没有方式一, 二及三的定义, 复制 到资料记忆体的字串也已被清除 特别注意, 方式一及方式四的定义, 其复制到资料记忆体的动作会在进入main() 主程式之前就已完成, 因此如果要使用ClearMemory()函式, 则不可在任何函式中 撰写方式一或方式四的叙述. 避免使用区域变数并设定其初始值的方式 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 52 页 共 189 页 f) 某些 16 bits 的资料是由两个暂存器组成的, 例如 ADC 的 adrl, adrh, 当读取后可用 一个 unsigned int 型态的变数储存之. unsigned int voltage ; voltage = (unsigned int)_adrh ; voltage = (voltage << 4) + (unsigned int)(_adrl >> 4) ; // 12 bit ADC g) 少用 goto 叙述, 因为会需要定义 label, 让程式不具模组化. 若 label 过多, 程式的 可读性就降低了. h) 避免使用 #include 将 .c 的档案加在另一个 .c 的档案中. #include 前置指令是将标 头档 (header file, .h) 引入, 指引 C 编译器将标头档中定义的常数,宣告的变数与函式 等读入, 让原始程式档中的叙述有所参考, 并产出正确的指令码. 如果将.c 程式档加 另一个.c 程式档时, 则等于将程式档扩大到两个档案的大小, 不但会增加编译的时 间, 也增加阅读,维护的不便. 5.4 可供微控制器应用程式使用的范本 微控制器的应用程式中常会需要执行一些动作, 以下的范例是以 C 语言撰写, 读者可将之 直接加入到程式中或是另外建入副函式, 再呼叫此副函式完成所需的动作. a) 针对资料记忆体空间做初始化的动作, 亦即清为零. 通常在刚进入主程式时就会执行此动作, 可以将之加入主函式 main( ) 中或是另建一个 副函式 (subroutine)并从主函式呼叫之. 将资料记忆体 bank 1 的内容清为零 unsigned char *mptr ; for( mptr = (unsigned char *)0x140 ; mptr < (unsigned char *)0x153 ; mptr++ ) *mptr = 0 ; // 将 RAM bank 1 的 0x40 到 0x53 位址清为 0 b) 延迟(等候)时间 应用程式中有时会需要等候(或延迟)一段时间再做之后的处理, 因此等候的时间就需要 做准确的计算. 以微控制器的频率为 4MHz 为例, 一个指令周期是 1us (微秒). 如果只 需要等候数个微秒的时间, 可以直接呼叫编译器的内建函式 _nop(). 或是 _delay() 函式 _nop() 函式只需一个指令周期, 若是要等候 2 微秒, 则只需呼叫 2 次 _nop() 即可. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 53 页 共 189 页 若需要等候较多的时间, 可以直接呼叫内建函式 _delay() _delay( clocks ) 函式中的参数 clocks 是 unsigned long 的资料型态, 当 clocks = 0 则 表示要无限的延迟, 可以使用此函式当做等候中断事件的发生, 类似无限回圈的方式. clocks 可以从 1 到 263690, 若需要超出此时间, 可以连续呼叫. c) 读取表格资料的方式 应用程式中常有资料表格以供程式运行时参考之需, 可以使用C语言的阵列(array)或是 指标(pointer)将表格内的资料读取出. unsigned char WordTable[16] = { 0x41, 0xE7, 0x52, 0x62, 0xE4, 0x68, 0x48, 0xE3, 0x40, 0x60, 0xC0, 0x4C, 0x59, 0x46, 0x58, 0xD8 } ; // 定义阵列,显示 ‘0’, ‘1’, ‘2’, …., ‘9’,’A’, ‘b’, ‘C’, ‘d’, ‘E’, ‘F’ int k ; LedPortCtrl = 0 ; // 设定 pa 埠为输出 LedPort = 0xff ; // 熄灭所有 for( k = 0 ; k < 16 ; k++ ) // 总共显示 16 的字元 { LedPort = WordTable[k] ; // 点亮第 k 个字, 从表格中读取资料 _delay(50000) ; // 延迟 50 毫秒 (ms) } 另外一种写法是使用指标, 表格的最后资料 0x00 代表表格的结束 unsigned char WordTable[17] = { 0x41, 0xE7, 0x52, 0x62, 0xE4, 0x68, 0x48, 0xE3, 0x40, 0x60, 0xC0, 0x4C, 0x59, 0x46, 0x58, 0xD8, 0x00 } ; unsigned char *tptr ; tptr = WordTable ; while( *tptr != 0 ) { LedPort = *tptr++ ; _delay(50000) ; } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 54 页 共 189 页 d) 读写两个位元组资料的方式 使用ADC 中断方式去读取转换后的数位资料, 以HT46R62 的 9 位元为例 unsigned int AdcValue ; // 定义一个全域变数以储存转换后的数值 void ADC_ISR( ) // 定义 ADC 中断服务函式, 将转换后的数值读出及储存 { AdcValue = ((unsigned int)_adrh <<1) + ((unsigned int)_adrl >> 7) ; // 读出高位元组资料 D8~D1 并向左移动一个位元, // 读出低位元组, 向右移 7 个位元, 合并组成一个 int 的资料型态, // 并存入变数 } 如果需要从时钟读出目前的计数 (timer count), 以HT48R70A-1 为例, Timer 0 是16 位元 unsigned int TimerCount ; // 储存读出的计数 _t0on = 0 ; // 先将 timer 0 停止计数 TimerCount = ((unsigned int)_tmr0h <<8) + (unsigned int)_tmr0l ; // 读出高位元向左移动 8 位元, 读出低位元计数并加入 int 的变数储存之 _t0on = 1 ; // 将 timer 0 打开, 继续计数 e) 针对RAM bank 0 以外的变数或暂存器, 可以利用指定变数位址的方式读写, 例如 要将数值写入 RAM bank 1 的 0x40 位址, unsigned char Bank0140 @ 0x140 ; // 定义变数 Bank0140, 并指定其位址 unsigned char out ; Bank0140 = 0x20 ; // 将 0x20 写入 RAM bank 1 的 0x40 位址内 out = Bank0140 ; // 将 RAM bank 1 位址 0x40 的内容储存到变数 out 5.5 设计微控制器应用程式的小技巧 a) 多层记忆体微控制器的程式精致化 (code optimization of multi-ROM-bank MCU) 在具备多个程式记忆体的微控制器 (multiple ROM bank)上开发程式时, 常会需要加入 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 55 页 共 189 页 变更BP暂存器的指令 (MOV BP,A)以便切换到其他的 ROM bank去执行, 因而增加整 个程式码的大小. 为能将程式码的大小减少, 可以对程式做以下的安排 (两个步骤) ■ 将所有会呼叫到的函数皆指定在同一个 ROM bank, 例如 #pragma rombank 1 func1, func2, func3 // 将函数 func1, func2 与 func3 指定放在 ROM bank 1, 请参考 4.9.5 节 void func1(void) { } void func2(void) { ⋯⋯ func1( ) ; // 函数 func1 被呼叫, 指定其与 func2 放在同一 ROM bank ⋯⋯ switch( ) { case xx : } } void func3(void) { func2( ) ; // 指定与函数 func3 放在同一个 ROM bank 内 if( ) { } else ⋯. func1( ) ; ⋯. } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 56 页 共 189 页 ■ 将函数中使用到的全域变数, 指定在 ROM bank 0, 使用 #pragma rambank0 #pragma rambank0 char count ; // 指定 变数 count 放置于 RAM bank 0 #pragma norambank b) const 常数的使用 在微控制器的应用程式中, 常常会使用 const array (常数表格)去定义固定的资料, 程式中 再将之读出使用. 但是只能被与常数表格同一个档案的程式所用, 定义在其他档案中的程 式则无法使用. 为能让所有档案中的程式皆可使用, Holtek C 编译器支援 const array 可 以被其他档案中的程式所使用, 方法与一般的 extern variable 类似, 如下范例 档案 Global.c 中定义常数表格 const unsigned char LedTable[ ] = { 0x11, 0x22, 0x33, 0x44 }; 而在其他档案中的程式, 若要取得 LedTable[ ] 中的资料, 例如档案 Sub.c 中 extern const unsigned char LedTable[ ] ; void sub1( ) { unsigned char ldata ; …. ldata = LedTable[1] ; // 读取常数表格的第二个成员 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 57 页 共 189 页 第六章 程式范例 –初级 本章将以基本的 C 语言介绍如何开发微控制器的应用程式. 从最浅显的程式架构及叙述 并配合微控制器的功能, 设计适当的应用程式. 每个范例会以较常用的语法及叙述完成之. 从设计显示器的控制应用程式及键盘处理程式开始, 包括 LED, 七段显示器, 液晶显示器 (LCD), 4*4 点矩阵按键输入等. 其中有些程式(函式)可以直接移植到其他的应用专案或只 需修改输出/输入埠的设定并重新编译就可使用 6.1 LED 跑马灯 6.1.1 目的 学习 for/while 回圈执行时间延迟及连续输出资料到 port A以达到控制 8 颗 LED 轮流点亮,其他则灭的目标. 亮灭的间隔时间为 500 毫秒 (ms) 6.1.2 周边元件 HT48R06A-1 的输出输入埠 A 连接到 8 颗 LED 的阴极. 所以当要点亮 LED时, 只需对其相连的 PA 埠输出低电位. 6.1.3 电路图 PA0 ~ PA7 各连结到 LED 的阴极 , MCU 频率为 4MHz 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 58 页 共 189 页 6.1.4 微控制器的架构设定 (configuration option) 可选择使用 ICE 内部的振荡器, 频率设为 4000KHz (4MHz) 可以设定埠 A 有内部的 pull-high, 就不需要在外面加 pull-high 电阻 WDT : disable WDT Instruction : one clear instruction PA wake up : none Pull high : all WDT OSC : on chip RC OSC : crystal Sys volt : 5.000V Sys freq : 4000kHz, Internal 6.1.5 程式流程 开始 埠 A 设为输出型态 输出高电位到埠 A, 熄灭LED 从 pa0 开始, 一一点亮 LED 结束 6.1.6 原始程式 1 #include “ht48r06a-1.h” 2 #define LedPort _pa // pa 埠 3 #define LedPortCtrl _pac // pa 控制埠 4 void main(void) // 主函式 5 { 6 int k ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 59 页 共 189 页 7 LedPortCtrl = 0 ; // 设定 pa 埠为输出 8 LedPort = 0xff ; // 熄灭所有 LED 9 for( k = 0 ; k < 8 ; k++ ) 10 { 11 LedPort = ~ (1 << k) ; // 点亮第 k 个 LED 12 _delay(250000) ; // 延迟 500 毫秒 (ms) 13 _delay(250000) ; 14 } 15 LedPort = 0xff ; // 熄灭所有 LED 16 } 6.1.7 程式说明 1 引入标头档, 每个程式档的最开始, 最好加入此行, 告诉C编译器要加入此档案 中的宣告及定义 2~3 定义变数 LedPort 为埠 A 的资料暂存器, LedPortCtrl 为埠 A 的控制暂存器 7 写 0 到埠 A 控制暂存器, 以设定埠 A 为输出型态, 此叙述与下一行组合语言 指令有相同的功能 : CLR PAC 8 写 0xff 到埠 A 资料暂存器, 输出 11111111b, 表示熄灭所有的 LED 9 for 回圈, 一次点亮一个 LED, 从 PA0 的 LED 开始, 一直到 PA7 的 LED 11 将 1 向左移 k 位元, k 代表埠 A 的第 k 支引脚 (k=0,1,2,…7). 再做 1 的补数 (1’s complement) 将 1 改为 0 , 其他 7 个零改为 1. 之后写到埠 A 资料暂存器 对第 k 支引脚输出低电位, 点亮其上的 LED. 其它引脚为高电位, 所以不亮. 12~13 延迟 500 毫秒让 LED 维持点亮. 再做下一个 LED 的点亮. 相邻两个 LED 点亮熄灭的间隔时间会大于 500 毫秒, 因为 for 回圈还要执行点亮的前端工作 15 将所有的 LED 熄灭 如果想要让 LED 再从头轮流点亮, 可以在第 15 行之处改为 goto again ; 而在第 9 行 前面加上 again: (要有冒号) 或在第 14 行加上 while(1) 无限回圈 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 60 页 共 189 页 6.2 LED 霹雳灯 6.2.1 目的 学习 for 回圈执行时间延迟及连续输出资料到 port A以达到控制 8 颗 LED 依照 设定之次序轮流点亮. 点亮次序是从阵列中读出, 按照点亮LED0, LED0+LED1, LED0+LED1+LED2 的顺序,直到8个LED都点量为止. 亮灭的间隔时间为 500 ms 学习使用 C 的阵列变数, 取代组合语言程式使用读表指令 (TABRDC, TABRDL) 读取显示格式资料的方式, 增高程式可读性 6.2.2 周边元件 HT48R06A-1 的输出输入埠 A 连接到 8 颗 LED 的阴极. 所以当要点亮 LED时, 只需对其相连的 PA 埠输出低电位. 6.2.3 电路图 PA0 ~ PA7 各连结到 LED 的阴极 , MCU 频率为 4MHz 6.2.4 微控制器的架构设定 (configuration option) 可选择使用 ICE 内部的振荡器, 频率设为 4000KHz (4MHz) 可以设定埠 A 有内部的 pull-high, 就不需要在外面加 pull-high 电阻 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 61 页 共 189 页 WDT : disable WDT Instruction : one clear instruction PA wake up : none Pull high : all WDT OSC : on chip RC OSC : crystal Sys volt : 5.000V Sys freq : 4000kHz, Internal 6.2.5 程式流程 开始 埠 A 设为输出型态 输出高电位到埠 A, 熄灭LED 从阵列读出显示资料并点亮 LED 结束 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 62 页 共 189 页 6.2.6 原始程式 1 #include “ht48r06a-1.h” 2 #define LedPort _pa // pa 埠 3 #define LedPortCtrl _pac // pa 控制埠 4 char LedTable[8] = { 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00 } ; 5 // 阵列变数, 设定显示资料, 点亮 LED0, LED0+LED1, LED0+LED1+LED2, …. 6 void main(void) // 主函式 7 { 8 int k ; 9 LedPortCtrl = 0 ; // 设定 pa 埠为输出 10 LedPort = 0xff ; // 熄灭所有 LED 11 for( k = 0 ; k < 8 ; k++ ) 12 { 13 LedPort = LedTable[k] ; // 输出 LED 控制码, 点亮 LEDx 14 _delay(250000) ; // 间隔 250 ms 15 _delay(250000) ; // 间隔 250 ms 16 } 17 LedPort = 0xff ; // 熄灭所有 LED 18 } 6.2.7 程式说明 除了第 13 行,其他与 6.1 节的程式皆相同. 第13 行是从阵列 LedTable 中取得显示资料, 再将之输出到 LED 埠. 第 14, 15 行是呼叫内建函式 _delay(), 参数 250000 表示要延迟 的 clock 数为 250000, 此例 1 clock = 1us, 一次呼叫则延迟 250 ms 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 63 页 共 189 页 6.3 单颗七段显示器 6.3.1 目的 以共阳极七段显示器显示 0 ~ 9, A ~ F 等十六个字, 间隔 50 ms. 本范例与上一范例 相似, 唯一不同处为阵列内容, 需配合七段显示器的排列, 设计字型的显示码. 6.3.2 周边元件 将微控制器的输出输入埠 pa0 ~ pa7 连结到七段显示器, 各字的显示码如下两表. 输出 low 到某段则点亮该段. 6.3.3 电路图 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 64 页 共 189 页 6.3.4 微控制器的架构设定 (configuration options) 可选择使用 ICE 内部的振荡器, 频率设为 4000KHz (4MHz) 可以设定埠 A 有内部的 pull-high, 就不需要在外面加 pull-high 电阻 WDT : disable WDT Instruction : one clear instruction PA wake up : none Pull high : all WDT OSC : on chip RC OSC : crystal Sys volt : 5.000V Sys freq : 4000kHz, Internal 6.3.5 程式流程 相同于 6.2.5 程式流程 6.3.6 原始程式 1 #include “ht48r06a-1.h” 2 #define LedPort _pa // pa 埠 3 #define LedPortCtrl _pac // pa 控制埠 4 unsigned char WordTable[16] = { 0x41, 0xE7, 0x52, 0x62, 0xE4, 0x68, 0x48, 0xE3, 5 0x40, 0x60, 0xC0, 0x4C, 0x59, 0x46, 0x58, 0xD8 } ; 6 // 定义阵列,显示 ‘0’, ‘1’, ‘2’, …., ‘9’,’A’, ‘b’, ‘C’, ‘d’, ‘E’, ‘F’ 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 65 页 共 189 页 7 void main(void) // 主函式 8 { 9 int k ; 10 LedPortCtrl = 0 ; // 设定 pa 埠为输出 11 LedPort = 0xff ; // 熄灭所有 12 for( k = 0 ; k < 16 ; k++ ) // 总共显示 16 的字元 13 { 14 LedPort = WordTable[k] ; // 点亮第 k 个字 15 _delay(50000) ; // 延迟 50 毫秒 (ms) 16 } 17 LedPort = 0xff ; // 熄灭所有 18 } 6.3.7 程式说明 14 将显示资料从阵列 WordTable 中读出, 再输出到 LED 埠显示之 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 66 页 共 189 页 6.4 5*5 点矩阵 LED 显示 6.4.1 目的 控制 5 * 5 点矩阵 LED, 显示字串 “HOLTEK”, 每字间隔 50 毫秒(ms), 每字在 24 ms 内将 5 行(row) 显示完成. 本范例使用C的指标型态(pointer)及阵列(array). 以指标方式读取阵列中的元素并显示出. 目前HI-TECH 未支援二维阵列 (2-dimension array), 可以使用本范例的方式读取二维阵列的资料. 6.4.2 周边元件 pa0 ~ pa4 连接点矩阵的列 (row0 ~ row 4), pb0 ~ pb4 连接点矩阵的行 (column 0~4) 使用 25 颗 LED 排列成 5*5 的矩阵,如下图. 再将之与微控制器的埠A, 埠B连接. ● ○ ○ ○ ● ○ ● ● ● ○ ● ○ ○ ○ ○ ● ● ● ● ● ● ● ● ● ● ● ○ ○ ○ ● ● ○ ○ ○ ● ● ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ● ○ ○ ● ○ ○ ○ ○ ● ○ ○ ● ○ ● ● ● ● ● ● ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ● ○ ○ ● ● ● ● ○ ● ● ● ○ ○ ● ○ ○ ○ ● ● ○ ○ ○ ● ● ○ ○ ○ ○ ○ ○ ● ○ ○ ● ○ ○ ○ ○ ● ○ ○ ● ○ ● ○ ○ ○ ● ○ ● ● ● ○ ● ● ● ● ● ○ ○ ● ○ ○ ● ● ● ● ● ● ○ ○ ○ ● 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 67 页 共 189 页 6.4.3 电路图 6.4.4 微控制器的架构设定 (configuration option) 可选择使用 ICE 内部的振荡器, 频率设为 4000KHz (4MHz) 可以设定埠 A 及埠 B 有内部的 pull-high, 就不需要在外面加 pull-high 电阻 WDT : disable WDT Instruction : one clear instruction PA wake up : none Pull high : all WDT OSC : on chip RC OSC : crystal Sys volt : 5.000V Sys freq : 4000kHz, Internal 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 68 页 共 189 页 6.4.5 程式流程 开始 设定埠A, 埠B为输出型态 熄灭 5*5 点矩阵 LED NO 已显示 YES 五列 ? 结束 6.4.6 原始程式 1 #include “ht48r06a-1.h” 2 #define LedRowPort _pa // row, pa 埠 3 #define LedRowPortCtrl _pac // pa 控制埠 4 #define LedColPort _pb // column, pb 埠 5 #define LedColPortCtrl _pbc // pb 控制埠 6 char charH[5] = { 0x1f, 0x4, 0x4, 0x4, 0x1f } ; // 高电位(high level) 表示点亮 7 char charO[5] = { 0x0e, 0x11, 0x11, 0x11, 0x0e } ; 8 char charL[5] = { 0x1f, 0x10, 0x10, 0x10, 0x10 } ; 9 char charT[5] = { 0x01, 0x01, 0x1f, 0x01, 0x01 } ; 10 char charE[5] = { 0x1f, 0x15, 0x15, 0x15, 0x11 } ; 11 char charK[5] = { 0x1f, 0x4, 0x4, 0x0a, 0x11 } ; 12 char *LedTable[6] = { charH, charO, charL, charT, charE, charK } ; // 指标阵列 从表格中取出一个指标 从指标读出字型码 从第一列开始, 共五列 点亮一列与此列的五行 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 69 页 共 189 页 13 14 void main(void) // 主函式 15 { 16 int k ; 17 char *tptr ; 18 LedRowPortCtrl = 0 ; // 设定 pa 埠为输出 19 LedColPortCtrl = 0 ; // 设定 pb 为输出 20 LedColPort = LedRowPort = 0 ; // 熄灭 LED 21 for( k = 0 ; k < 6 ; k++ ) // 总共显示 6 个字元 22 { 23 tptr = LedTable[k] ; // 读出第 k 个字的指标 pointer 24 for( j = 0 ; j < 5 ; j++ ) 25 { 26 LedColPort = (1 << j ) ; // 点亮第k个字的 column j 27 LedPort = tptr[j] ; // 输出 row的字型, 点亮相对的字型 28 _delay(5000) ; // 每列间隔 5 ms 29 } 30 _delay(50000) ; // 每字间隔 50 ms 31 } 32 LedColPort = 0 ; // 熄灭所有 LED 33 } 6.4.7 程式说明 6 ~ 11 定义 ”HOLTEK” 六个字元的字型码 (pattern) 12 阵列 LedTable 定义上述六个阵列的指标 21 从第一个字元开始, 共显示 6 个字元 23 从阵列 LedTable 取出字元的指标 24 每个字元有 5*5 的点矩阵, 以每列 (row) 为显示单位 26 输出列的显示资料 27 输出此列的五个行 (column)的资料 28 每列的间隔为 5 毫秒 (ms) 30 每字的间隔为 50 毫秒 (ms) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 70 页 共 189 页 6.5 HT48 微控制器控制 HT1621 LCD 的显示 6.5.1 目的 本范例是使用 HT48 系列的微控制器控制 HT1621 LCD液晶显示器. 透过发送命令 给 HT1621, 控制液晶显示器在液晶面板(panel)上显示字元或图形. 经由 HT48 的 输出/输入埠与 HT1621 的引脚连接, 发送 HT1621 的命令. 本范例是以 HT48R10A-1 微控制器来控制 HT1621, 并介绍如何点亮及清除 LCD 所有位元. 6.5.2 周边元件 HT1621 是一个 128 位元的 LCD 控制元件, 内部 RAM 直接对应 LCD 的显示单元. 软体可使它适用于LCD模组或显示系统的多功能应用上. 微控制器与HT1621的介 面只需 4 到 5 根线. 内置的省电模式极大的降低了功率耗损. . 资料记忆体 HT1621 LCD驱动器内部具备一个显示资料的记忆体, 大小为 32*4 位元, 它的位置 排列如下 共有 32 segments 与 4 commons. 当 LCD 打开后, 将数据写入这些记忆体便可控 制各对应的点是发亮或熄灭. D0 控制 common 0 与 segment0 ~ segment31 交叉点 的发亮与否, D1 控制 common 1 与segment0 ~ segment31 交叉点的发亮与否. D2 控 制 common 2 与segment0 ~ segment31 交叉点的发亮与否. D3 控制 common 3 与 segment0 ~ segment31 交叉点的发亮与否. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 71 页 共 189 页 命令格式 HT1621 LCD 驱动器支持两类模式, 命令模式及资料模式. 命令模式是对 HT1621 下达开启或关闭 LCD 的显示及设定 LCD 显示的架构等, 以二进位的 100 开始的 数字是命令模式的识别码(ID). 而资料模式则是从 LCD 记忆体中读出资料或写资 料到 LCD 记忆体, 它的识别码是以二进位的 110 或 101 开头. 下表为本范例所 用的命令, 详细的命令请参阅 HT1621 data sheet. 命令模式 命令名称 识别码 命令码 功能 电源重置 SYS DIS 100 0000-0000-x 关闭系统振荡器及LCD偏压产生器 V SYS EN 100 0000-0001-x 打开系统振荡器 LCD OFF 100 0000-0010-x 关闭 LCD 偏压产生器 V LCD ON 100 0000-0011-x 打开 LCD 偏压产生器 BIAS& COM 100 0010-abxc-x 设定 LCD 的偏压及 common 选项 c=0, 偏压为 1/2 c=1, 偏压为 1/3 ab=00, 2 common ab=01, 3 common ab=10, 4 common 资料模式 命令名称 识别码 命令码 功能 READ 110 A5A4A3A2A1A0D0D1D2D3 从RAM读出资料 WRITE 101 A5A4A3A2A1A0D0D1D2D3 写资料到RAM READ-MODIFY- WRITE 101 A5A4A3A2A1A0D0D1D2D3 读和写资料到 RAM x : don’t care bit A5 ~ A0 : RAM address 记忆体位址 D3 ~ D0 : RAM 资料 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 72 页 共 189 页 在使用 LCD 之前必须要先送出两个命令, SYS EN 与 LCD ON 给 HT1621 以便 开启 LCD. 由于是串列通讯, 所有的命令皆是一个位元接着送出, 顺序是先送出识 别码 (3个位元), 然后是命令码 (9 或 10 位元). 最后要将 /CS 准位拉高做为结束. 重复此顺序, 再送出下一个命令. 如果接下来的也是相同的命令与连续的位址时, 可以不用将 /CS 准位拉高, 而是直接做资料读取或写入,等全部完成后再拉到高位 准. 命令时序 (command timing) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 73 页 共 189 页 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 74 页 共 189 页 6.5.3 电路 连结 HT48R10A-1 的输出/输入埠到 HT1621的pin脚, 如下 HT48R10A-1 I/O port HT1621 pin PB1 DATA PB2 /WR PB3 /CS PB4 /RD 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 75 页 共 189 页 6.5.4 微控制器的架构设定 (configuration option) HT48R10A-1 的 configuration option 设定 WDT clock source : disable OSC : Ext. Crystal Pull-high PA : Pull-high Pull-high PB : Pull-high Input type PA : Schmitt Trigger BZ/BZB : Disable Fsys = 4M 6.5.5 程式流程 开始 设定 LCD 为 NORMAL 状态 对 LCD 做初始动作 设定 LCD 为 SYS EN 致能 设定 LCD 为 4 common, 1/3 bias 设定 LCD ON (开启) 结束 清除 LCD RAM 资料 (熄灭 LCD) 写入所有 LCD RAM 的空间 将 LCD 点亮 从 LCD RAM 读出 segment 4 的 内容. 检查是否与上述写入的资料 相同 (0x0f)盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 76 页 共 189 页 6.5.6 原始程式 1 #include “ht48r10a-1.h” 2 #define CMD_NORMAL 0xE3 // normal command 3 #define CMD_SYSEN 0x01 // system enable 4 #define CMD_SYSDIS 0x00 // system disable 5 #define CMD_LCDOFF 0x02 // LCD off 6 #define CMD_LCDON 0x03 // LCD on 7 #define csb _pb3 // HT1621的 CSB 脚 = bit 3 of port B 8 #define wrb _pb2 // HT1621 的 WRB 脚 = bit 2 of port B 9 #define rdb _pb4 // HT1621 的 RDB 脚 = bit 4 of port B 10 #define ldata _pb1 // HT1621 的 DATA 脚 = bit 1 of port B 11 #define ldata_ctrl _15_1 // bit 1 of port B control register控制 DATA的输出/输入 12 13 void DelayTime(unsigned int count) // 10 * count + 11, if count > 256 14 { 15 while( count != 0 ) count-- ; 16 } 17 void nop(void) // 呼叫此函式会用掉 5 个指令周期 18 { 19 asm(“NOP”) ; 20 } 21 void SendCommand(unsigned char cmd) // 送出命令给 HT1621 22 { 23 unsigned int CmdCode ; 24 int idx ; 25 // 将命令识别码 100b 及命令码组成 12 bit 26 CmdCode = ((unsigned int)100b << 9) | ((unsigned int)cmd <<1) ; 27 ldata_ctrl = 0 ; // 设定 port B bit 1 (DATA) 为输出型态 28 csb = 0 ; // CSB 拉 low 29 for ( idx = 11 ; idx >= 0 ; idx-- ) 30 { 31 if( (CmdCode & (1 << idx)) != 0 ) // 检查各位元是 1 或 0, 从 bit11 ~ bit0 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 77 页 共 189 页 32 ldata = 1 ; // 写出 1 33 else 34 ldata = 0 ; // 写出 0 35 36 wrb = 0 ; // WRB -> low -> high 37 _delay(5) ; // 延迟 5 毫秒 (5 instruction clock) 38 wrb = 1 ; 39 _delay(5) ; 40 } 41 csb = 1 ; // CSB 拉高, 结束送出命令 42 } 43 // 将 odata 的 bit0 ~ bit3 换位置, 改为 bit3 ~ bit0. 例如, 将 1110 改为 0111 44 char SwapBit(char odata) 45 { 46 char ndata ; 47 int k ; 48 ndata = 0 ; 49 for( k = 0 ; k < 4 ; k++ ) 50 ndata |= ((odata >> k) & 0x1) << (3- k) ; 51 return (ndata) ; 52 } 53 // 将资料 dataN 的最低 4 个位元 写到 LCD RAM 的 addr 位址 (bit0~bit5 有效) 54 // bit 0, 1, 2, 3 of dataN 是 com0, com1, com2, com3 的资料 55 void WriteLCD(char addr, char dataN) 56 { 57 unsigned int DataCode ; // 16 bits 58 char NewData ; 59 int idx ; 60 DataCode = ((unsigned int)101b << 6) | (addr & 0x3f) ; // 将 addr 6 个低位元并入 DataCode 61 NewData = SwapBit(dataN) ; // 将 dataN 的 bit0~bit3 换位置为 bit3~bit0 62 DataCode = (DataCode <<4) | NewData ; // 将 dataN 四个低位元并入 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 78 页 共 189 页 63 ldata_ctrl = 0 ; // 设定 port B bit 1 (DATA) 为输出型态 64 csb = 0 ; // CSB 拉 low 65 for ( idx = 12 ; idx >= 0 ; idx-- ) // 送出 13 个位元 66 { 67 if( (DataCode & (1 << idx)) != 0 ) // 检查各位元是 1 或 0, 从 bit12 ~ bit0 68 ldata = 1 ; // 写出 1 69 else 70 ldata = 0 ; // 写出 0 71 72 wrb = 0 ; // WRB -> low -> high 73 _delay(5) ; // 延迟 5 毫秒 (5 instruction clock) 74 wrb = 1 ; 75 _delay(5) ; 76 } 77 csb = 1 ; // CSB 拉 high, 结束送出命令 78 } 79 // 从 LCD RAM 的 addr 位址 (bit0~bit5 有效) 读出一个 nibble (4 bits) 传回 80 // 回传值的 bit 0, 1, 2, 3 是 com0, com1, com2, com3 值 81 char ReadLCD(char addr) 82 { 83 unsigned int DataCode ; // 16 bits 84 char retcode ; 85 int idx ; 86 87 DataCode = ((unsigned int)110b << 6) | (addr & 0x3f) ; // 将 addr 6 个低位元并入 DataCode 88 ldata_ctrl = 0 ; // 设定 port B bit 1 (DATA) 为输出型态 89 csb = 0 ; // CSB 拉 low 90 for ( idx = 8 ; idx >= 0 ; idx-- ) // 送出 9 个位元 91 { 92 if( (DataCode & (1 << idx)) != 0 ) // 检查各位元是 1 或 0, 从 bit8 ~ bit0 93 ldata = 1 ; // 写出 1 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 79 页 共 189 页 94 else 95 ldata = 0 ; // 写出 0 96 97 wrb = 0 ; // WRB -> low -> high 98 _delay(5) ; // 5 us 的延迟 (5 instruction clock) 99 wrb = 1 ; 100 _delay(5) ; 101 } 102 // 读出 4 个位元 103 ldata_ctrl = 1 ; // 设定 port B bit 1 (DATA) 为输入型态 104 retcode = 0 ; 105 for( idx = 0 ; idx < 4 ; idx++ ) // idx=0 读出 D0, idx=1, 读出 D1, … 106 { 107 rdb = 0 ; // RDB 拉 low 108 _delay(5) ; 109 rdb = 1 ; // RDB 拉 high 110 if( ldata == 1 ) retcode |= (1 << idx) ; // 读一个位元, 如果是 1 111 } 112 csb = 1 ; // CSB 拉 high, 结束: 送出命令 113 return retcode ; 114 } 115 // 将 LCD RAM 的内容清为 0 116 void CleanLCD(void) 117 { 118 char seg ; 119 for( seg = 0 ; seg < 32 ; seg++ ) 120 WriteLCD(seg, 0) ; 121 } 122 // 将 LCD 全部点亮 123 void DisplayLCD(void) 124 { 125 char seg ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 80 页 共 189 页 126 for( seg = 0 ; seg < 32 ; seg++ ) 127 WriteLCD(seg, 0xf) ; // 点亮 com0, com1, com2, com3 128 } 129 130 void main(void) 131 { 132 char DataR ; 133 _pb = 0 ; // 埠 B 资料为 0 134 csb = 1 ; // CSB 拉 high -Æ disable LCD 135 _pbc = 0 ; // 设定埠 B 为输出 136 while(1) // 无限回圈, 只有当执行错误才会跳出回圈 137 { 138 SendCommand(CMD_NORMAL) ; // 送出 NORMAL 命令 139 SendCommand(CMD_SYSEN) ; // 送出 SYS EN 命令 140 SendCommand(0x29) ; // 设定 LCD 为 4 common, 1/3 bias 141 SendCommand(CMD_LCDON) ; // turn LCD on 142 CleanLCD( ) ; // 将 LCD RAM 清为0, 所有点皆不亮 143 DisplayLCD( ) ; // 点亮所有点, 写 0x0f 到 LCD RAM 144 145 DataR = ReadLCD(4) ; // 读出第 segment 4 的资料 146 if ( DataR != 0x0f ) // 读出的与写入的不同, error 147 break ; // 跳出回圈 148 SendCommand(CMD_LCDOFF) ; // 送出 LCD OFF 命令 149 } 150 } 6.5.7 程式说明 2 ~ 6 定义 HT1621 命令的命令码常数. 即命令 100 之后的 8 个位元 7 ~ 11 定义 HT48R10A-1 的输出输入埠 连接到 HT1621 的引脚 17~20 函式 nop() 会使用 5 个指令周期,可根据系统频率使用此函式做时间延迟 21~42 函式 SendCommand 是将命令传送到 HT1621, 参数 cmd 是命令码 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 81 页 共 189 页 26 100b 是命令ID, 三个位元往左移九位到位元 11~9, 加上输入的参数 cmd 左移 一位放到位元 8 ~ 1, 共八个位元, 最后的位元 0 则不管何值皆可, 此例存放 0 27 设定 DATA 引脚为输出型态 28 CSB 引脚拉到低电位 29~40 输出 12 位元的命令到 HT1621, 从位元 11 开始 31~34 如果位元的值是 1 则输出 1, 是 0 则输出 0 36~39 将 WRB 控制脚拉低电位, 再拉到高电位, 将上述的资料写到HT1621 可使用内建函式 _delay(5), 延迟 5 毫秒. 时间要根据微控制器的频率决定 41 CSB 引脚拉到高电位, 结束命令的发送 44~52 函式 SwapBit 是将输入参数的位元 0~3 移换到位元 3~0. 例如将1110 改为 0111 主要是将资料部分改成 HT1621 的格式, 从 D0,D1,D2,D3 的顺序送出 49 总共四个位元做换位 50 位元0 移到位元3, 位元1移到位元2, 位元2移到位元1, 位元3移到位元0 55~78 将输入参数 dataN 的最低四个位元写到HT1261 RAM 的位址 addr 中 addr 只有 bit0~bit5 是有效的位址. DataN 的位元0~3 是 com0, com1, com2, com3 的资料 60 变数 DataCode 是要输出的命令. 先将命令ID, 101b, 左移 6 个位元再加上 addr 的低 6 个位元所组成的 61 将输入参数 dataN 的 bit0~bit3 换到位置 bit3~bit0. 按照命令的格式 62 将第 60 行的 DataCode 左移 4 个位元, 再加上第 61 行的资料. 产出要写入 HT1621 RAM 的资料 63 设定埠 B 的位元 1 (DATA 引脚) 为输出型态 64 CSB 拉为低电位 65~78 将 13 个位元, bit 12 到 bit 0, 写到 HT1621 的 RAM 67~70 如果位元的值是 1 则输出 1, 是 0 则输出 0 72~75 将 WRB 引脚拉到低电位, 延迟时间, 再拉到高电位, 以写出此位元 77 CSB 拉到高电位, 结束送出命令 81~114 函式 ReadLCD 是从 HT1621 RAM 的位址 addr 中读出一个位元组 addr 只有最低的 6 个位元(bit 0 ~ bit 5) 是有效的. 回传值只有最低的四个位元 有效 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 82 页 共 189 页 87 产出输出的数值 DataCode, 由命令识别码 110b 左移 6 位元, 加上 addr 的 最低 6 位元 88 设定 埠 B 位元 1 (DATA 引脚) 为输出型态 89 CSB 拉到低电位 90 发送 9 个位元, 包括识别码 110 及 6 个位元的位址 92~95 检查各位元是 1 或 0, 将之送出 97~100 WRB 引脚拉到低电位, 再拉到高电位, 完成传送的动作 103 设定 port B bit 1 (DATA) 为输入型态 105 读出四个位元. idx=0 读出 D0, idx=1, 读出 D1, … 107~109 RDB 拉到低电位, 等候, 再拉到高电位 110 将读到的位元储存到变数etcode |= (1 << idx) ; // 读一个位元, 如果是 1 112 CSB 拉到高电位. 结束及送出命令 113 传回 retcode 116~121 函式 CleanLCD 将 LCD RAM 的内容清除为 0, LCD 不亮 119 从第一个 segment (seg = 0) 开始清除为0, 直到第32个segment 120 写 0 到一个 segment. 清除为 0 123~128 函式 DisplayLCD 将 LCD RAM 皆写入 1, 点亮所有 common 与 segment 126 从第一个 segment 开始到第 32 个 segment 127 写 1 到 LCD RAM segment 的所有 common 内 130~150 主函式. 功能是控制 HT1621 液晶显示器, 从开启, 熄灭, 点亮到关闭的动作 检查对 HT1621 RAM 的读写动作是否正确 133 埠 B 资料为 0 134 CSB 拉到高电位Æ 将 HT1621 除能 135 设定埠 B 为输出型态 136 无限回圈, 会重复做 138 行到 148行的动作. 只有当执行错误才会跳出回圈 138 送出 NORMAL 命令到 HT1621 139 送出 SYS EN 命令到 HT1621, 系统致能 140 设定 LCD 为 4 common 及 1/3 bias 141 开启 LCD 142 将 LCD RAM 清为0, 所有点皆不亮 143 点亮所有点, 写 0x0f 到 LCD RAM 145 读出 segment 4 的资料 146~147 若读出的资料与写入的不同, error. 跳出无限回圈 148 送出 LCD OFF 命令 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 83 页 共 189 页 6.6 HT48 微控制器控制 LCD 模组的显示 6.6.1 目的 利用 HT48 系列微控制器来控制DV16100NRB液晶显示驱动器, 此LCM模组由内 置的 Hitachi HD44780进行驱动及控制. 本范例是以C语言的程式让微控制器产生 正确的讯号以符合LCM所需的时序并在 LCM上显示文字. 本范例的一些函式可当作 公用函式, 凡使用此类LCM做显示的应用皆可呼叫这些函式. 详细的指令及时序资料 请查阅LCM相关资料. 6.6.2 周边元件 使用 HT48R10A-1 微控制器去控制 LCM 显示文字. 它的输出/输入埠 PB0~PB7 连接到 HD44780 的 D0~D7, 藉以传送资料. 埠C 用来控制 LCM, PC0 连接 E 脚 控制 LCM 的致能(E=1)与除能(E=0). PC1连接 R/W, 控制对 LCM 的读(R/W=1)与 写(R/W=0). PC2 连接 RS, 控制 LCM 暂存器的选择, 指令暂存器 (RS=0)或资料 暂存器 (RS=1). 如果应用电路使用其他的输出/输入埠与 HD44780相连, 则只要 修改程式中 #define 的叙述, 其他则不用修改 HT48R10A-1 脚位 HD44780 脚位 说明 PC0 E 致能控制, E=1(致能), =0 (除能) PC1 R/W 读取 (=1), 写入 (=0) PC2 RS LCM暂存器的选取, =0 指令暂存器, =1 资料暂存器 PB0~PB7 D0 ~ D7 资料线 → 在每次对 HD44780 做读写动作前,必须先检查LCD是否仍然忙线(busy). 可透过 读取忙碌旗号(BF)和位址(AC)指令去读出指令暂存器(IR)的内容 (RS=0, R/W=1) → 要从 LCD 读取一个位元组(byte)时, 流程如下 设定 RS = 1 如从资料暂存器读出, = 0 如果从指令暂存器读出 设定 R/W = 1 设定 E = 1, 将 LCD 致能 从 D0~D7 读出资料并存放 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 84 页 共 189 页 设定 E = 0, 将 LCD 除能 → 要将一个位元组(byte)dr11资料写入 LCD 时, 流程如下 设定 RS = 1 如果是写入资料暂存器, = 0 如果是写入指令暂存器 设定 R/W = 0 设定 E = 1, 将 LCD 致能 将资料放到 D0~D7 设定 E = 0, 将 LCD 除能 6.6.3 电路图 使用微控制器去控制 LCM 显示文字. 它的输出/输入埠为 PB0~PB7 6.6.4 微控制器的架构设定 (configuration option) WDT clock source : disable OSC : Ext. Crystal Pull-high PA : Non-Pull-high Pull-high PB : Non-Pull-high Pull-high PC : Non-Pull-high Input type PA : Schmitt Trigger BZ/BZB : Disable Fsys = 4M 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 85 页 共 189 页 6.6.5 程式流程 开始 1. 设定 PC0/PC1/Pc2 为输出型态 2. 设定 LCD 功能: 8 位元控制 LCD 3. LCD ON/OFF 设定:显示所有资料 initialization 4. 设定进入模式: AC 自动加一 5. 清除显示器, 位址计数器为 0 设定位址计数器为第一行 从’A’ 到 ‘p’ 写入 LCD DD RAM 设定位址计数器为第二行 从’a’ 到 ‘p’ 写入 LCD DD RAM 结束 6.6.7 原始程式 1 #include “ht48r10a-1.h” 2 #define LcdData _pb // LCD D0~D7 = PB0~PB7 3 #define LcdDataCtrl _pbc // PB埠的控制暂存器, data 为输出或输入 4 #define LcdE _pc0 // E 控制脚位 = PC0 5 #define LcdRW _pc1 // R/W 控制脚位 = PC1 6 #define LcdRS _pc2 // RS 控制脚位 = PC2 7 #define LcdPortCtrl _pcc // PC 埠的控制暂存器 8 #define CMD_READ 1 // R/W 常数, 读 9 #define CMD_WRITE 0 // R/W 常数, 写 10 #define REG_IR 0 // RS 常数, 指令暂存器 IR 11 #define REG_DR 0 // RS 常数, 资料暂存器 IR 12 #define ENABLE 1 // E 常数, 致能 LCD 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 86 页 共 189 页 13 #define DISABLE 0 // E 常数, 除能 LCD 14 // 检查 LCD 是否仍然忙碌, 若是则会一直检查到不忙碌 15 // 若LCD故障是否会造成无穷回圈 ?? 16 void CheckLCDBusy(void) 17 { 18 LcdDataCtrl = 0xff ; // 设定 DATA 为输入型态 19 LcdRS = REG_IR ; // 选定 指令暂存器 (IR) 20 LcdRw = CMD_READ ; // Read 动作 21 LcdE = ENABLE ; // LCD enable 22 while( (LcdData & 0x80) != 0 ) ; // BF=1 是仍忙碌, 一直检查 23 24 // LCD 不忙碌 25 LcdE = DISABLE ; 26 } 27 // 写资料到 LCD 指令暂存器 (IR) 或资料暂存器 (DR) 28 // 输入参数 regflag = 1 写入资料暂存器(DR), =0 写入指令暂存器 (IR) 29 // wdata = 写入的资料 30 void WriteToLCD(char regflag, char wdata) 31 { 32 CheckLCDBusy() ; // 检查及等候 LCD 不忙碌 33 LcdRS = regflag ; // 选定暂存器 IR/DR 34 LcdRW = CMD_WRITE ; // 写入动作 35 LcdDataCtrl = 0 ; // 设定 DATA 为输出型态 36 LcdData = wdata ; // 将资料放入 DATA 37 LcdE = ENABLE ; // 致能 LCD 38 LcdE = DISABLE ; // 不需做延迟, high -> low 完成写入 39 } 40 // 主函式 41 void main(void) 42 { 43 LcdPortCtrl = 0 ; // 3 个 LCD 控制脚位设定为输出型态 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 87 页 共 189 页 44 WriteToLCD(REG_IR, 0x38) ; // 设定功能, 8位元控制, 双行显示, 5*7点矩阵 45 WriteToLCD(REG_IR, 0x0f) ; // ON/OFF控制, 显示所有资料及游标, 游标闪烁 46 WriteToLCD(REG_IR, 0x06) ; // 进入模式, 外部读写资料后,位址记数器AC 47 // 会自动加一, 游标会向右移动 48 WriteToLCD(REG_IR,0x01) ; // 清除显示器,位址计数器设定为0, I/D=1 49 WriteToLCD(REG_IR, 0x80) ; // DD RAM位址设定, 位址计数器设为 0 50 // 游标设在第一行,第一个位址 51 // 开始将显示的字写入 LCD DD RAM 让LCD显示出 52 for( k = 0, char1 = ‘A’ ; k < 16 ; k++ ) // 一行可显示 16 个字, 从 ‘A’ 开始 53 { 54 WriteToLCD(REG_DR, char1) ; // 显示 char1 中的字, 从 ‘A’ 开始 55 char1++ ; // 下一个字母 56 } 57 WriteToLCD(REG_IR, 0xC0) ; // DD RAM位址设定, 位址计数器设为 0x40 58 // 游标设在第二行,第一个位址 59 for( k = 0, char1 = ‘a’ ; k < 16 ; k++ ) // 一行可显示 16 个字, 从 ‘a’ 开始 60 { 61 WriteToLCD(REG_DR, char1) ; // 显示 char1 中的字, 从 ‘a’ 开始 62 char1++ ; // 下一个字母 63 } 64 WriteToLCD(REG_IR, 0x08) ; // ON/OFF控制, LCD 不显示 65 } 6.6.7 程式说明 2~3 定义埠B为资料输出/输入埠, 与 HD44780 引脚相连接 4~7 定义埠C为控制讯号线, 与 HD44780的控制引脚相连接 8~13 定义常数给控制指令使用, 主要目的是让程式容易阅读 16~27 函式 CheckLCDBusy 检查 LCM 是否空闲, 若仍忙碌则会等到空闲才返回 18 设定 DATA (D0 ~D7) 为输入型态 19 选定 指令暂存器 (IR) 20 选定读取 (Read)动作 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 88 页 共 189 页 21 激活 LCM 功能 (enable) 22 回圈 : 从 LCM 读取资料, 检查BF位元是否为1 (忙碌中). 如果是则继续读取及 检查. 直到 LCM不再忙碌. 若要更严谨的作法, 应该要有次数或时间的限制. 在合理的时间或次数后, 若仍然忙碌(LCM 出现某种错误)则应回覆错误码 25 停止 LCM 功能 30~39 函式 WriteToLCD 将资料 wdata 写入指令暂存器(IR, regflag=0) 或是资料 暂存器(DR, regflag=1) 32 检查 LCD 是否空闲, 等候 LCD 不忙碌 33 选定暂存器为 IR 或 DR 34 写入动作 35 设定 DATA 的埠为输出型态 36 将资料放入 DATA (D0~D7) 线 37 将 LCD 激活引脚 (E) 拉到高电位 38 将 LCD 激活引脚 (E) 拉到低电位. 不需做延迟, high -> low 完成写入 41~65 主函式 43 将 3 个 LCD 控制引脚设定为输出型态 44 设定 LCD 功能 -> 8位元控制, 双行显示, 5*7点矩阵 45 设定 LCD 的 ON/OFF 控制 -> 显示所有资料及游标, 游标闪烁 46 设定 LCD 的进入模式 -> 外部读写资料后,位址记数器AC会自动加一, 游标会向右移动 48 清除显示器, 位址计数器设定为0, I/D=1 49 DD RAM位址的设定 -> 位址计数器设为 0, 游标设在第一行,第一个位址 52 开始将显示的字元写入 LCD DD RAM 让 LCD 显示出, 一行可显示 16 个字 从 ‘A’ 开始 54 将字元码写入LCD RAM, 显示此字元 55 下一个字元 57 将 DD RAM 的位址设定为 0x40, 代表将游标设在第二行,第一个位址 59 在第二行显示从 ‘a’ 开始的 16 个字元 61 将需要显示的字元码写入 LCD RAM 62 下一个字元 64 LCD ON/OFF 控制, LCD 不显示 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 89 页 共 189 页 6.7 具 LCD 驱动功能的微控制器之显示应用程式 – HT46R63 本节将以 HT46R63 为本说明如何使用 C 语言程式控制 LCD 驱动器以便将数字显示在 面板上. 可以使用 HT-IDE3000 的 LCD simulator 去模拟 LCD 面板 (panel), 不需实际 制作 LCD 面板. 请参阅 HT-IDE300 使用手册中的 LCD Simulator 章节 6.7.1 目的 使用 C 语言撰写程式去控制 HT46R63 的 LCD 驱动器以便将数字显示在 LCD 面板上. 显示的数字为八位元时钟计时的数值, 以秒为单位. 6.7.2 周边元件 控制 LCD 各点 (pixel, COMx/SEGy)是否显示的资料是储存在 HT46R63 的 LCD 记忆体. 此记忆体是位于微控制器的 Bank 1 资料记忆体, 位址从 40H 到 53H. SEGMENT, COMMON与位址的关系如下图 当写 1 到位址的位元时, 其对应的 SEGx/COMy 的点将会显示 在开发应用程式及侦错的过成中, 可以不接上真实的LCD面板 (panel), 而是使用 HT-IDE3000 LCD Simulator 提供的模拟系统, 在电脑萤幕上模拟 LCD 面板. 在设计程式之前, 先设定LCD面板的架构. 本范例使用 lcddemo.lcd 当做面板的 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 90 页 共 189 页 架构档, 直接从 HT-LCDS 软体开启此档案, 将出现如下图的画面 图的上半是 LCD面板的图样, 根据程式写入 LCD RAM 的控制资料来显示数字, 从 0 到 9. 数字最多由七段组成, 每段则由对应的 SEG/COM 点 (pixel) 所控制. 面板上的图样是从 bmp 格式的图档取得, 在设定 LCD 架构时必须指定. 在图下方的表格内则是定义 SEG/COM 与各段之间的对应关系. 例如位址 40H, 41H,42H 等三个位元组控制第一个七段图样的显示, 此范例使用 3 个 common, 所以每个位元组最多只能三个位元是有效的. 范例将以固定的延迟时间累加数字, 再将之显示于面板. 数字的个位数字显示在 面板的第三个图样 (3a~3g), 十位数字显示于第二个图样 (2a~2g), 百位数字显示 于第一个图样 (1a~1g). 6.7.3 电路图 HT46R63 微控制器 6.7.4 微控制器的架构设定 (configuration option) HT46R63 的架构设定, 需要选定的如下, 其他可以 disable 或用 default LCD duty: 3 COM 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 91 页 共 189 页 LCD segment: 20 segments SEG7-SEG10: LCD output SEG11-SEG14: Logical output SEG15-SEG18: Logical output 6.7.5 程式流程 开始 结束 6.7.6 原始程式 1 #include “ht46r63.h” 2 // 定义数字 ‘0’ ~ ‘9’ 的 LCD RAM 资料, 7 个位元控制各段的显示 3 // 图样各段 gacf bdc 4 char digit[10] = { 0b01111101, 0b00011000, 0b01110011, 0b01111010, //‘0’, ‘1’,’2’,’3’ 5 0b00011110, 0b01101110, 0b01101111, 0b00111000, // ‘4’,’5’,’6’,’7’ 6 0b01111111, 0b01111110 } ; // ‘8’, ‘9’ 7 char LcdRam[20] @ 0x140 ; // LCD RAM 记忆体 8 void DelayTime(unsigned int count) // 10 * count + 11, if count > 256 9 { 10 while( count != 0 ) count-- ; 清除 LCD RAM (熄灭 LCD) 回圈, 从 0 到 999 针对每个数, 将十位 数字,百位数字及个位 数字显示于 LCD上 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 92 页 共 189 页 11 } 12 // addr (IN) = LCD RAM 位址 = 0x140+x 13 // datal = 写入 LCD RAM 的资料, bit0~2 写入 addr, bit3~5 写入 addr+1 14 // bit 6 写入 addr+2 的 bit2 15 void DisplayLcd(unsigned char addr, unsigned char datal) 16 { 17 LcdRam[addr - 0x140] = datal & 0x7 ; // 取位元 0 ~ 2 18 LcdRam[addr - 0x140+1] = (data1 >> 3) & 0x7 ; // 取位元 3~5 19 LcdRam[addr – 0x140+2] = ((data >> 6) & 0x7) << 2 ; // 取位元6, 在左移2位 20 } 21 // 主函式 22 void main(void) 23 { 24 int k, count ; 25 26 for( k=0 ; k < 20 ; k++ ) LcdRam[k] = 0 ; // 将 LCD记忆体清为0, LCD不显示 27 for( count=0 ; count < 1000 ; count++ ) // 显示 数字从 0 到 999 28 { 29 k = (count /100) % 10 ; // 取出百位数 30 DisplayLcd(0x146, digit[k]) ; 31 k = (count / 10) % 10 ; // 取出十位数 32 DisplayLcd(0x143, digit[k]) ; // 显示数字 33 k = count % 10 ; // 取出个位数 34 DisplayLcd(0x140, digit[k]) ; 35 _delay(250000) ; // 延长 1 秒 36 _delay(250000) ; 37 _delay(250000); 38 _delay(250000) ; 39 } 40 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 93 页 共 189 页 6.7.7 程式说明 4~6 定义 0 到 9 的数字¸ 在 LCD RAM 内的控制码. 显示数字 7 定义阵列 LcdRam[20] 的位址在 RAM bank 1 的 0x40. 这是 LCD RAM 记忆体 的启始位址. 要显示的控制码必须写入对应的位址. LcdRam[0] 的位址是 0x140, LcdRam[1] 的位址是 0x141, …. LcdRam[19]的位址是 0x153 15~20 函式 DisplayLcd 将控制码 datal 各部分写入 LCD RAM 的位址 addr, addr+1, addr+2. 其中 data1的位元 0~2 写入 addr 的位元 0~2, data1 的位元 3~5 写入 addr+1 的位元 0~2, datal 的位元 6 写入 addr+2 的位元 2 一旦写入 LCD RAM, 对应的点就会反应, 显示或不显示图样 22~38 主函式 main 26 将 LCD 记忆体清为0, LCD不显示 27~37 回圈, 在 LCD 面板上显示数字, 从 0 开始, 到 999 为止 29 取出百位数 30 从阵列 digit[]中取得百位数字的控制码, 将LCD RAM 位址 0x146与控制码传给 函式 DisplayLcd 将百位数的数字显示 31 取出十位数 32 从阵列 digit[]中取得十位数字的控制码, 将LCD RAM 位址 0x143与控制码传给 函式 DisplayLcd 将十位数的数字显示 33 取出个位数 34 从阵列 digit[]中取得个位数字的控制码, 将LCD RAM 位址 0x140与控制码传给 函式 DisplayLcd 将个位数的数字显示 35~38 _delay 延长显示时间 1 秒 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 94 页 共 189 页 6.8 显示器之通用函式 – HD44780 LCM 本节将范例 6.6 的函式做一整理, 以较容易使用的格式与介面方式, 提供其他欲使用此 LCD 的应用程式一些公用函式 6.8.1 initialLCD( ) 定义常数, 于初始设定 LCD 的特性时, 以这些常数的组合值传入函式设定 LCD 如特性未被定义如下, 则是其固有值, 若选定此特性, 则不需设其值 (default=0) #define LCD_MODE_RIGHT 0x2 // entry mode: I/D=1 游标向右移 #define LCD_MODE_MOVE 0x1 // entry mode: S=1 显示器会跟着移动 #define LCD_FUNC_5X10 0x04 // F=1 5*10 点矩阵 #define LCD_FUNC_2LINE 0x08 // N=1 双行显示 #define LCD_FUNC_8 0x10 // DL=1 八位元控制(DB7~DB0) #define LCD_DISP_B 0x01 // ON/OFF控制: B=1 游标闪烁 #define LCD_DISP_C 0x02 // C=1 显示游标 #define LCD_DISP_D 0x04 // D=1 显示所有资料 // 函式的输入参数及其定义 // funcset : functionset 命令, bit 4,3,2 = DL, N, F // DL=1 使用四位元(DB7~DB4) 控制模式 // =0 使用八位元(DB7~DB0) 控制模式 // N=0 单行显示 // =1 双行显示 // F=0 5*7 点矩阵字型 // =1 5*10 点矩阵字型 // dispon : Display ON/OFF 命令, bit 2, 1,0 = D, C, B // D =0 所有资料不显示 // =1 显示所有资料 // C =0 不显示游标 // =1 显示游标 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 95 页 共 189 页 // B =0 游标不闪避 // =1 游标闪避 // entrymode :进入模式命令 bit 1, 0 = I/D, S // I/D =0 当资料写入 DD RAM 或从DD RAM 读出后, 位址计数器 (AC)会 // 减一, 因此游标会向左移 // =1 当资料写入 DD RAM 或从DD RAM 读出后, 位址计数器 (AC)会 // 加一, 因此游标会向右移 // S =1 当资料写入 DD RAM 后, 整个显示器会向左移(若 I/D=0)或向右移 // (若I/D=1), 但从 DD RAM 读取资料时, 显示器不会移动 // =0 显示器不会移动 void InitialLCD(char funcset, char dispon, char entrymode) { char bcode ; LcdPortCtrl = 0 ; // 3 个 LCD 控制脚位设定为输出型态 bcode = (funcset & 0x1C) | 0x20 ; // 取 funcset 的位元 2, 3, 4 (functionset 指令) WriteToLCD(REG_IR, bcode) ; // 功能设定, 4/8位元,单/双行显示, 5*7/5*10点矩阵 bcode = (dispon & 0x7) | 0x08 ; // ON/OFF控制, dispon 位元0,1,2 是 ON/OFF的 0,1,2 WriteToLCD(REG_IR, bcode) ; // ON/OFF控制, 显示所有资料及游标, 游标闪烁 bcode = (entrymode & 0x03) | 0x04 ; // entry mode, entrymode 位元 1, 0 WriteToLCD(REG_IR, bcode) ; // 进入模式, 外部读写资料后,位址记数器AC会变 WriteToLCD(REG_IR,0x01) ; // 清除显示器,位址计数器设定为0, I/D=1 WriteToLCD(REG_IR, 0x80) ; // DD RAM位址设定, 位址计数器设为 0 // 游标设在第一行,第一个位址 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 96 页 共 189 页 6.8.2 PutChar( ) 将一个字母写入LCD DD RAM 做显示 void PutChar(char bdata) { WriteToLCD(REG_DR, bdata) ; // 写入LCD DD RAM } 6.8.3 MoveAt( ) 将游标移到指定的LCD位置. 亦即将位址计数器(AC)改为新的位址. 位置以 x, y 表 示第 y 行的第 x 位置 (x = 0~19, y=0~3) 一行最多 20字, 最多可显示四行 void MoveAt(char x, char y) { char bdata ; bdata = x + ((y & 2)==0 ? 0 : 20) + ((y&1)==0 ? 0 : 0x40) + 0x80 ; WriteToLCD(REG_IR, bdata) ; } 6.8.4 main( ) void main(void) { char funcset, disp, entrymode ; funcset = LCD_FUNC_2LINE | LCD_FUNC_8 ; // 双行显示, 8位元传送 entrymode = LCD_MODE_RIGHT ; disp = LCD_DISP_B | LCD_DISP_C | LCD_DISP_D ; InitialLCD(funcset, disp, entrymode) ; // initialize LCD } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 97 页 共 189 页 6.9 键盘扫瞄程式 6.9.1 目的 本范例介绍如何使用输出/输入埠支援一个 4*4 的键盘阵列. 每一个按键皆赋予 唯一的十六进位数值, 当一个按键被按下后, LED会显示此按键所赋予的数值, 以二进制显示. 如果同时有数个按键按下时, 只会显示第一个被扫瞄到的按键数值. C 语言方面使用 switch/case 的叙述格式以取代多个 if/else if 的格式, 增加程式的 可读性. 6.9.2 周边元件 十六个按键连接到埠 A, PA0~PA7. 四个LED连接到埠 B 的 PB0~PB3 输出/输入埠 PA 的 PA0~PA3 设为输出口, 输出扫瞄码(scan code), PA4~PA7 设为 输入口, 输入按键码 (key code). 当程式扫瞄到被按下的按键时, 再经由连接到 PB 的 LED 显示此按键值. 扫瞄方式是从第一列第一行开始, 先找出被按下之按键所在的列, 再找出所在的行. 经由 PA0~PA3 送出扫瞄码, 目标列送出 0, 其他列则送出 1, 例如先找寻第一列, 则从 PA0~PA3 送出 0111, 再从 PA4~PA7 读入, 若其值不为 1111, 则表示此列 中有一个按键被按下, 若 0 出现在 PA5 则表示第二行有按键被按下. 因此按下的 键是在第一列, 第二行. 6.9.3 电路图 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 98 页 共 189 页 从埠A 读入按键码 6.9.4 微控制器的架构设定 (configuration option) HT48R10A-1 的 configuration option 设定 WDT clock source : disable OSC : Ext. Crystal Pull-high PA : Pull-high Pull-high PB : Pull-high Input type PA : Schmitt Trigger BZ/BZB : Disable Fsys = 4M 6.9.5 程式流程 开 始 设定PB, PA0~PA3 为输出型态 设定 PA4~PA7 为输入型态 超过第四列 是 不是 某列中有按键 没有 有 输出到埠B, 将按键对应的 LED点亮 回圈 : 输出 各列的扫描码 从第一列开始到第四列 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 99 页 共 189 页 6.9.6 原始程式 1 #include “ht48r10a-1.h” 2 char scan_code[4] = { 0xfe, 0xfd, 0xfb, 0xf7 } ; 3 void main(void) 4 { 5 char key_code, pos ; 6 int row ; 7 _pbc = 0 ; // 设定 埠B 为输出型态, 以便点亮 LED 8 _pac = 0xf0 ; // 设定 PA0~PA3 是输出型态, PA4 ~ PA7 是输入型态 9 _pa = _pb = 0 ; // 清除 PA, PB 10 scan_l: 11 for( row = 0 ; row < 4 ; row++ ) // 从第一列开始扫描 12 { 13 _pa = scan_code[row] ; // 输出此列的扫瞄码 14 key_code = _pa ; // 从 PA 读入按键码 15 if( (key_code ^ scan_code[row]) != 0 ) // 此列有按键被按下 16 { 17 switch ( key_code & 0xf0 ) // 取最高的4位元 (PA7~PA4) 18 { 19 case 0xe0 : pos = row * 4 ; // PA4=0 20 break ; 21 case 0xd0 : pos = row * 4 + 1 ; // PA5=0 22 break ; 23 case 0xb0 : pos = row * 4 + 2 ; // PA6=0 24 break ; 25 case 0x70 : pos = row * 4 + 3 ; // PA7=0 26 break ; 27 default : // error 28 goto scan_l ; // 可用其他方式显示错误 29 } 30 pos ^= 0x0f ; // 将最低四位元转成以 low 才点亮 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 100 页 共 189 页 31 _pb = pos ; // 点亮 LED 32 break ; // 跳出 for 回圈 33 } 34 } 35 // 没有按键被按下或已完成一次LED显示 36 goto scan_l ; // 从头开始扫瞄 37 } 6.9.7 程式说明 2 定义四个扫瞄码的阵列 scan_code[4] 做为按键的扫瞄之用 7 设定 埠B 为输出型态, 以便点亮 LED 8 设定 PA0~PA3 是输出型态, PA4 ~ PA7 是输入型态 9 清除 PA, PB 10 重复执行的起点 11 回圈, 从第一列开始扫描, 共有四列 13 输出此列的扫瞄码到埠 A 14 从埠 A 读入按键码 15 检查此列是否有按键被按下 17 有按键被按下, 检查是那一行的按键被按下 // 取最高的4位元 (PA7~PA4) 19 第一行被按下, 计算按键的位置为 行数 * 4 20 跳出 switch 21 第二行的按键被按下, 计算其位置为 行数 * 4 + 1 22 跳出 switch 23 第三行的按键被按下, 计算其位置 行数 * 4 + 2 24 跳出 switch 25 第四行的按键被按下, 计算其位置 行数 * 4 + 3 26 跳出 switch 27~28 其他情况, 正常状况不应该执行到此处, 但为了避免不正常的情况发生, 此处为 预防之处, 设定位置为不正确值 (-1) 30 将位置的最低四位元转成以 low 才能点亮 31 输出到不 B, 点亮 LED 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(二) 版 别 1.20 日 期 2008/5/26 第 101 页 共 189 页 32 跳出 for 回圈. 会到第 36 行 36 没有按键被按下或已完成一次LED显示. // 从头开始扫瞄 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 102 页 共 189 页 第七章 应用范例 – 中断程式 本章介绍以C 语言程式处理微控制器的中断事件. 第四章 4.7 中断服务函式 中说明如何 撰写中断服务函式以及注意事项. 必须遵照下列的规则来定义中断服务函式 → 函式的返回值型态必须是 void interrupt → 函式不可有参数 (argument) → 设定中断服务函式的中断向量 (interrupt vector) 使用 @ 指定中断向量 → 最好不要从程式的其他地方呼叫中断服务函式 → 不要在中断服务函式内做开启本中断功能 (interrupt reentrance) 相同的中断不要重复进入 格式 void interrupt ISR_Timer(void) @ 0x08 { } // 定义 : 返回值的型态, 没有参数, 设定中断向量为 0x08 C 编译器会根据中断服务函式对暂存器的需要, 在进入中断服务函式后, 将这些暂存器的内 容储存. 等执行完中断服务函式的工作后, 再恢复先前所储存的暂存器内容. 最后回要被中 断的地方继续执行, 同时中断功能也打开, 允许中断产生. 如果为了节省程式编码的大小, 在 中断服务函式中也可不要保存某些暂存器, 例如 BP, MP0, MP1.可参考第四章的 4.9 节关于 #pragma 的说明及使用. 在中断服务函式中不要将自身的中断功能再打开, 避免重复进入同一 个中断服务函式. 但是可以打开其他的中断功能, 只是要特别注意两个中断服务函式有无呼叫 到相同函式的状态, 以致发生变数共用造成资料错误的风险. 7.1. 用时钟控制 LED 的亮与灭 7.1.1 目的 本范例是使用时钟控制 LED 亮与灭的时间. 亮灭的时间相同并且固定. 是以C语言 撰写中断服务函式. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 103 页 共 189 页 7.1.2 周边元件 使用单颗 LED 接到微控制器 HT48R10A-1 的埠 A, PA0 引脚 需要使用微控制器 HT48R10A-1 的暂存器 INTC, TMRC 与 TMR 等, 定义如下 TMRC (Timer Control Register) 时钟控制暂存器控制时钟的功能与启动, TMR暂存器是储存时钟启始数值的地方. 当写入TMR暂存器时,也会存入preload 暂存器. 若此时时钟是在停止状态中, 则此数值会被写入时钟计数器(timer counter). 每当时钟被启动, 它便将时钟计数器的数值往上加, 一直加到 FF, 之后便发生时钟 满溢(timer overflow). 这时, 下列的事情会发生 → 产生中断讯号 (interrupt signal) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 104 页 共 189 页 → 将 preload 暂存器中的数值重新载入时钟计数器, 并且继续往上计数 如果需要时钟在每 count 个 clock 时产生满溢, 可将 256 – count 写入 TMR 暂存器. 如需要正确的时间, 则需要设定根据系统频率设定TMRC暂存器中的 PSC0~PSC2. 计算出对应的 count 并将 256 – count 写入 TMR 暂存器 如需要产生时钟中断并处理之, 则需要在 INTC 暂存器中打开时钟的中断功能, 将 ETI 设为 1. 当时钟计数满溢时, 就会产生时钟中断, 并跳到中断向量 08H处. 中断函式会对埠A, PA0 的 LED做亮灭的控制 7.1.3 电路图 将埠A 的 PA0 连接到 LED 的阴极 7.1.4 微控制器的架构设定 (configuration option) HT48R10A-1 的 configuration option 设定 WDT clock source : disable OSC : Ext. Crystal Pull-high PA : Pull-high Input type PA : Schmitt Trigger BZ/BZB : Disable Fsys = 4M 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 105 页 共 189 页 7.1.5 程式流程 开始 时钟中断处理函式 设定 PA0为输出型态 设定 INTC 的EMI, ETI 毫秒变数加一 设定 TMRC 为 fsys/256 与 时钟模式 小于或等于62 毫秒变数 设定 TON, 启动时钟 大于 62 毫秒变数设为 0 进入无限回圈, 让时钟中 LED 点亮或熄灭 断函式处理 LED 亮与灭 return 7.1.6 原始程式 1 #include “ht48r10a-1.h” 2 #define _ton _0e_04 // 暂存器TMRC 的位元 4, TON 3 char sec_count ; // 记录秒数 4 void interrupt ISR_TIMER(void) @ 0x08 // 定义中断服务函式 ISR 在位址 0x08 5 { 6 sec_count++ ; // 每 16 毫秒 (ms) 增加一 7 if( sec_count > 62 ) // 超过 16*62 = 992 毫秒, 一秒 8 { 9 sec_count = 0 ; // 重开始 10 _pa0 ^= 0x01 ; // toggle LED, 每一秒, 轮流亮灭 11 } 12 } 13 void main(void) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 106 页 共 189 页 14 { 15 _pac = 0x01; // 设定 PA0 为输出型态 16 _pa = 0xff ; // 熄灭 LED 17 _intc = 0x05 ; // 设定 EMI, ETI 致能. 打开中断功能 18 _tmrc = 0x87 ; // 设定时钟控制暂存器, fint=fsys/256, timer mode 19 _tmr = (256 – 250) ; // 每 250 clock 产生一次时钟中断 20 sec_count = 0 ; // 设定初始值 21 _ton = 1 ; // 启动时钟开始计时 22 while(1) ; // 无限回圈 23 } 7.1.7 程式说明 2 定义变数 _ton 为时钟控制暂存器 TMRC 的位元 4, TON 控制时钟的开始或停止 3 定义变数 sec_count 记录秒数 4~12 定义时钟中断服务函式 ISR_TIMER(void) 放置于位址 0x08 处 6 每 16 毫秒 (ms) 增加一 7 如果累计的毫秒数超过 62 次 (超过 16*62 = 992 毫秒, 一秒) 9 sec_count 重设为 0, 重开始计数 10 toggle LED, 每一秒会轮流为亮灭 12~23 主程式 main(void) 15 将 PA0 设为为输出型态, PA0 接到 LED 16 设定 PA0 为 1, 熄灭 LED 17 设定中断控制暂存器 (INTC) 的 EMI 及 ETI, 让中断致能 18 设定时钟控制暂存器 (TMRC), fint=fsys/256, timer mode, fsys = 4MHz, 1 clock=64us 时钟频率 = 4M /256, clock = 1/时钟频率 = 64 us 19 设定时钟每隔 250 clock, 产生一次时钟中断. 250 * 64us = 16ms 20 设定初始值 21 设定 _ton = 1 , 启动时钟开始计时 22 无限回圈 while(1) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 107 页 共 189 页 7.2 类比/数位转换 (ADC) 的应用 7.2.1 目的 本范例利用 HT46R63 微控制器的类比/数位转换电路(analog to digital converter) 将外界的类比讯号转换为数位,并从 LCD 面板显示其值. 以C语言撰写 ADC 中断 服务函式 (ISR) 及 LCD 显示程式 7.2.2 周边元件 LCD 可使用盛群公司 HT-IDE3000 所附之 LCD Simulator 的液晶模拟面板档 LcdDemo.lcd 及 LCD 各段码图案以方便调试, 参阅第六章 6.7 节的 LCD 显示 HT46R63 的类比/数位转换电路, 包含下列的暂存器, 使用前必须先设定 → ADR 暂存器 (22H) Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 D7 D6 D5 D4 D3 D2 D1 D0 类比讯号转换为数位的数值会储存在 ADR 暂存器中. 转换的数值从 0 ~ 255. → ACSR 暂存器 (23H) 设定A/D转换的速度 Bit No. Label Functions 0 1 ADCS0 ADCS1 ADCS1,ADCS0: 选取A/D转换器的时钟来源 (clock source) 0,0 : fsys/2 0,1 : fsys/8 1,0 : fsys/32 1,1 : unsdefined 2 CMPL Comparator control (this bit is 0 druing reset) 0 : disable 1 : enable 3-6 -- 未使用, 若读此位元, 会得到 “0” 7 TEST 只在测试时使用 → ADCR 暂存器 (22H) 选定A/D转换的输入通道, 埠 B 的设定及启动转换的控制 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 108 页 共 189 页 Bit No. Label Functions 0 1 2 ACS0 ACS1 ACS2 ACS2, ACS1, ACS0 : 选定 A/D 的输入通道 0,0,0 : AN0 0,0,1 : AN1 0,1,0 : AN2 0,1,1 : AN3 1,0,0 : AN4 1,0,1 : AN5 1,1,0 : AN6 1,1,1 : AN7 3 4 5 PCR0 PCR1 PCR2 PCR2, PCR1, PCR0 : 设定 PB7~PB0 的功能 0,0,0 : PB7,PB6,PB5,PB4,PB3,PB2,PB1,PB0 0,0,1 : PB7,PB6,PB5,PB4,PB3,PB2,PB1,AN0 0,1,0 : PB7,PB6,PB5,PB4,PB3,PB2,AN1,AN0 0,1,1 : PB7,PB6,PB5,PB4,PB3,AN2,AN1,AN0 1,0,0 : PB7,PB6,PB5,PB4,AN3,AN2,AN1,AN0 1,0,1 : PB7,PB6,PB5,AN4,AN3,AN2,AN1,AN0 1,1,0 : PB7,PB6,AN5,AN4,AN3,AN2,AN1,AN0 1,1,1 : AN7,AN6,AN5,AN4,AN3,AN2,AN1,AN0 6 EOCB A/D转换的指标 (=0 代表转换结束) 每当变更位元3~5的设定后, 应该发出一个 START讯号以便 将A/D转换器重做初始化. 否则旗标EOCB会在不确定的状态 7 START 启动A/D转换 (0->1->0 =>启动, 0->1 =>重置A/D转换器,而且 将 EOCB 设为 1 当要做 A/D 转换时, 需要先选定转换通道, 埠 B 的设定及时钟选取. 之后要将 ADCR 暂存器 (22H) 的位元 7 (START) 设为 0 -> 1 -> 0, 则 A/D 转换器就开始 做转换的动作. 当转换完成时, ADCR 暂存器的位元 6 (EOCB)会被清为 0, 若A/D 转换的中断功能是致能的 (enable), 则同时会产生中断. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 109 页 共 189 页 如果变更转换通或埠 B 的设定, 则需对 A/D 做初始化的动作, 如下 在变更埠 B 设定的 10 个指令周期内将 ADCR 暂存器的位元 7 (START) 设为 1 再清为 0 即可 为了要利用 HT46R62 A/D 转换器的中断功能, 也需要设定中断向量及中断控制. → INTC0 暂存器 位元 0 (EMI) 需要设为 1, 打开中断的总开关 → INTC1 暂存器 位元 1 需要设为 1, 将 A/D 转换器的中断功能致能(enable) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 110 页 共 189 页 7.2.3 电路图 7.2.4 微控制器的架构设定 (Configuration Options) HT46R63 的 configuration option 设定 WDT clock source : disable OSC : Ext. Crystal Fsys = 4M LCD duty: 3 COM LCD segment: 20 segments SEG7-SEG10: LCD output SEG11-SEG14: Logical output SEG15-SEG18: Logical output Comparator : disable 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 111 页 共 189 页 7.2.5 程式流程 开始 AD 转换完成 ? 未完成 已完成 7.2.6 原始程式 1 #include “ht46r63.h” 2 // 标头档中定义有 变数 _emi = _intc0.0 , _eadi = _1e_1 (INTC1 bit 1) , 3 // A/D 转换器的暂存器 _adr (0x21), _adcr (0x22), _acsr (0x23) 4 #define adc_start _22_7 // bit 7 (START) of ADCR 暂存器 5 #define FSYS8 0x01 // ADC 时钟来源及 fsys/8 6 #define CH_AN0 0 // ADC 转换通道 7 #define ADC_2CH 2 // ADC 总通道个数 : 2, AN0 (PB0), AN1 (PB1) 8 char intflag = 0 ; // ADC 中断旗标 9 unsigned char adcvalue = 0 ; // ADC 转换后的数值 10 void interrupt ADC_ISR(void) @ 0x14 // ADC 中断服务函式, 中断向量为 0x14 11 { 12 intflag = 1 ; // 设定ADC 中断旗标 LCD RAM清为 0, 不显示 打开 ADC中断及总中断功能 设定 ADC 时钟,频率,转换通 道, 总通道数, 启动 AD 转换 将转换完成的数值显示在 LCD面板上 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 112 页 共 189 页 13 adcvalue = _adr ; // 读取转换后的数值 14 } 15 // 启动 ADC 转换功能 16 // adc_clk = clock source of ADC, fsys/2, fsys/8, fsys/32 (bit0~1 of ACSR) 17 // channel = channel number of AN, 0 ~ 7 (bit 0~2 of ADCR) 18 // port_cfg = port B setting (bit 3~5 of ADCR) 19 void StartADCTrans(char adc_clk, unsigned char channel, unsigned char port_cfg) 20 { 21 _acsr = adc_clk ; // 设定 时钟来源及频率 22 adcr = (port_cfg << 3) | channel ; // 设定通道, 埠B设定 23 adc_start = 1 ; adc_start = 0 ; // 改变 ADC通道及埠B 设定 24 adc_start = 0 ; adc_start = 1 ; adc_start = 0 ; // 启动 ADC 转换 25 } 26 // 以下函式 (除了主函式 main) 与 第六章 6.7 节相同 27 // 定义数字 ‘0’ ~ ‘9’ 的 LCD RAM 资料, 7 个位元控制各段的显示 28 // 图样各段 gacf bdc 29 char digit[10] = { 0b01111101, 0b00011000, 0b01110011, 0b01111010, //‘0’, ‘1’,’2’,’3’ 30 0b00011110, 0b01101110, 0b01101111, 0b00111000, // ‘4’,’5’,’6’,’7’ 31 0b01111111, 0b01111110 } ; // ‘8’, ‘9’ 32 char LcdRam[20] @ 0x140 ; // LCD RAM 记忆体 33 void DelayTime(unsigned int count) // 10 * count + 11, if count > 256 34 { 35 while( count != 0 ) count-- ; 36 } 37 // addr (IN) = LCD RAM 位址 = 0x140+x 38 // datal = 写入 LCD RAM 的资料, bit0~2 写入 addr, bit3~5 写入 addr+1 39 // bit 6 写入 addr+2 的 bit2 40 void DisplayLcd(unsigned char addr, unsigned char datal) 41 { 42 LcdRam[addr - 0x140] = datal & 0x7 ; // 取位元 0 ~ 2 43 LcdRam[addr - 0x140+1] = (data1 >> 3) & 0x7 ; // 取位元 3~5 44 LcdRam[addr – 0x140+2] = ((data >> 6) & 0x7) << 2 ; // 取位元6, 在左移2位 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 113 页 共 189 页 45 } 46 // 主函式 47 void main(void) 48 { 49 int k ; 50 51 for( k=0 ; k < 20 ; k++ ) LcdRam[k] = 0 ; // 将 LCD记忆体清为0, LCD不显示 52 _emi = 0 ; // 停止所有中断的发生 53 _eadi = 1 ; // ADC 中断功能生效 54 _emi = 1 ; // 所有中断功能生效 55 intflag = 0 ; // ADC 中断旗标 56 StartADCTrans(FSYS8, CH_AN0, ADC_2CH) ; // 开始 ADC 转换 57 while(1) // 无限回圈, 等候 ADC 中断产生 58 { 59 if( intflag == 1 ) break ; // ADC 中断产生, 跳出回圈 60 } 61 // 将转换好的数字显示于 LCD 面板 62 k = adcvalue / 100 ; // 取出百位数字 63 DisplayLcd(0x146, digit[k]) ; // 显示百位数字 64 k = (adcvalue / 10) % 10 ; // 取出十位数字 65 DisplayLcd(0x143, digit[k]) ; // 显示数字 66 k = adcvalue % 10 ; // 取出个位数 67 DisplayLcd(0x140, digit[k]) ; 68 _delay(250000) ; // 延长 1 秒 69 _delay(250000) ; 70 _delay(250000) ; 71 _delay(250000) ; 72 for( k=0 ; k < 20 ; k++ ) LcdRam[k] = 0 ; // 将 LCD记忆体清为0, LCD不显示 73 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 114 页 共 189 页 7.2.7 程式说明 4 定义 暂存器 ADCR 的位元 7 (START) 5~7 定义本范例使用的 ADC 时钟来源及频率, 转换通道及通道总个数 8 定义 ADC 中断旗标, 当等于 1 时表示 ADC 转换成功 9 定义变数, 储存 ADC 转换后的数值 10~14 定义 ADC 中断服务函式 ADC_ISR(void)及中断向量为 0x14 12 设定 ADC 中断旗标 13 从暂存器读出转换后的数值并存入变数 19~25 定义函式 StartADCTrans() 执行 ADC转换 21 设定 ADC 转换时钟及频率 22 选定 ADC 转换通道及通道总数 23 埠 B 设定更改 24 启动 ADC 转换功能 26~45 与第六章 6.7 节相同, LCD 显示函式 47~73 主函式, 设定及启动 ADC 转换并将转换后的数值从 LCD 面板显示 51 将 LCD记忆体清为0, LCD不显示 52 停止所有中断的发生 53 ADC 中断功能生效 54 所有中断功能有效 55 ADC 中断旗标清为 0 56 开始 ADC 转换 57~60 无限回圈, 等候 ADC 中断产生则跳出回圈 59 产生 ADC 中断, 跳出回圈 62~67 将转换后的数值显示在 LCD 面板 62~63 取出百位数字, 显示百位数字 64~65 取出十位数字, 显示数字 66~67 取出个位数, 显示数字 68~69 延长显示时间为 1 秒 72 将 LCD显示器关闭 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 115 页 共 189 页 第八章 HT46R52A 应用于镍氢(NiMH)电池充电器 (HA0084T) 8.1 目的 电子产品迈向小型化发展,携带型产品越来越普及与流行,如移动电话、数位相机、PDA、 MP3 随身听等,此类产品需要大量使用电池,所以可重复使用的可充电电池就显得十分 重要。在各类可充电电池中,镍氢电池被用来取代传统的一次电池,具有容量大、价格低 及环保考量的优势。Holtek 提供了镍氢电池充电器展示板,方便让使用者设计镍氢电池 充电器。 充电器特性 → 以定电流方式充电,慢充时电流70mA,快充时电流575mA → 提供电池放电的选择,来改善电池的记忆效应,放电时放电电流50mA → 以侦测−ΔV 方式侦测电流是否充饱 → 充电时间到达 6 小时,自动停止充电 → 两个 LED 显示充电状态 → 一次只能充一颗电池 充电器简介 利用定电流方式对一颗 NiMH 电池充电,当电池放入时,会立刻对电池充电,可选择在 充电之前,是否要对 NiMH 放电,放电完毕会立刻转入充电模式;在充电之前,会判断 电压是否大于1.25V,若是大于1.25V,则进入快速充电模式,否则则进入慢速充电模式, 以保护电池,当慢速充电结束会自动进入快速充电模式。 充电方法说明 利用 HT46R52A 及定电流方式做一个 NiMH 电池充电器, HT46R52A 提供一个PWM 输 出,可搭配一个ADC input 做电流控制,HT46R52A 提供 4 channel ADC来读取外部模拟 讯号,可用来侦测电池电压,当侦测到电池发生−ΔV 时,则表示电池已经充饱, 12-bit ADC 可提供到 1.2mV 的解析度。 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 116 页 共 189 页 充电流程说明 → 电源未接上时,DEMO Board 上的LED 灯不亮 → 电源接上DEMO Board,绿色 LED 持续亮着,其他 LED 亮一秒后关掉,此时DEMO Board 等待 NiMH 电池置入 → NiMH 电池放入后,则开始充电,两颗LED 显示电池的充电状态 → 当 NiMH 电池达到充饱的条件时,则停止充电,两颗LED 显示充饱的状态 → NiMH 电池充饱后,电池移走,两颗 LED 显示等待 NiMH 电池置入的状态 两颗LED 显示充电状态讯息 → 黄色及红色 LED 都不亮:等待电池置入 → 黄色亮、红色 LED 不亮:快速充电中 → 黄色及红色 LED 都亮:电池已充饱电或已充6 小时 → 黄色亮、红色闪烁:慢速充电中 → 红色亮、黄色闪烁:放电中 下表为充电过程所使用的一些参考值 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 117 页 共 189 页 8.2 暂存器及周边元件 本范例将使用到 PWM (Pulse Width Measurement) 来控制电池充电时的电流大小. 在设定微控制器的架构时要将埠 D (port D)的位元0 (PD.0)设为 PWM 模式. 程式 中要将 PD0 设为输出模式 (利用PDC控制暂存器). 输出 PWM 之前要先将 PD0 写入 1, 再将数值写入 PWM 暂存器. 下表为 PD0 的输出/输入功能 上表为 PWM (6+2) 模式时每个模组周期(modulation cycle) 的 duty cucle ACSR 暂存器是设定 AD转换器的时钟频率 ADCR 暂存器控制 AD转换器的开始执行及停止. 设定讯号通道及 PB 输入埠 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 118 页 共 189 页 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 119 页 共 189 页 AD转换器执行的结果会储存在 ADRL与ADRH 两个暂存器 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 120 页 共 189 页 8.3 电路图 8.4 微控制器的架构设定 (Configuration Options) HT46R52 的 configuration option 设定: WDT Disable CLRWDT Two clear instructions WDT clock source WDTOSC Pull-high PD0 Non-Pull-high LVR Disable OSC RC PD0/PWM Enable PWM 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 121 页 共 189 页 PA3/PFD Disable PFD ----------------------------------------------------------------------------------- 7 6 5 4 3 2 1 0 Wake-up PA0- 0 0 0 0 0 0 0 0 1:Wake-up, 0:Non-Wake-up Pull-high PA0- 1 1 1 1 1 1 1 1 1:Pull-high, 0:Non-Pull-high Pull-high PB0- - - - 1 1 1 1 1 1:Pull-high, 0:Non-Pull-high ----------------------------------------------------------------------------------- 8.5 程式流程 主流程 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 122 页 共 189 页 初始化程式 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 123 页 共 189 页 快速充电处理程式 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 124 页 共 189 页 慢速充电处理程式 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 125 页 共 189 页 8.6 原始程式与程式说明 档案 main.c 1 2 void main() 3 { 4 5 cinitial(); // initial all RAM & IO, turn LED (green) on 6 ch0_old = 0 ; // a new run 7 8 while(1) // infinite loop 9 { 10 while(1) // check battery status : exist/not exist 11 { 12 bat_main_sub(); // get battery's status, the battery is placed or not 13 // set the battery's LED flag 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 126 页 共 189 页 14 if( bat0_on_flag && !ch0_old ) // battery is placed, and it's a new run 15 break ; 16 17 ch0_old = 0 ; // flag : this is new run 18 } 19 20 // bat0_on_flag=1 battery is placed 21 // 1. check discharge switch, PB3 22 if( !_pb3 ) 23 Ch0_Discharging(); // start to discharge 24 25 // ch0_battery_charging_sub_lp: 26 // 2. do battery charging 27 28 Ch0_DoCharging() ; 29 } 30 } 程式说明 2 主程式 5 呼叫函式 cinitial() 对 RAM 记忆体做清理, 设定 I/O 口的状态, 点亮绿灯 6 设定旗标 ch0_old=0 : 新一轮处理 8~29 外层无限回圈. 程式永远在此回圈中执行 10~18 内层回圈, 检测是否有电池放置于充电座, 若有则跳出回圈, 否则就继续检测 12 呼叫函式 bat_main_sub() 检测电池的状况 14~15 如果有电池放置, 并且不是前一次的电池, 则跳出此回圈做适当的处理 17 否则, 设定旗标, 代表等候下一颗电池, 并继续检测 22 执行到此, 代表已有电池放于电池座等待处理, 检查埠 B 的 pb3 是否为低电压 (代表要先做放电) 23 是的, 则呼叫函式 Ch0_Discharging() 执行放电功能 28 呼叫函式 Ch0_DoCharging() 执行充电功能 //------------------------------------- // Ch0_DoCharging() // 1. battery charging with slow speed // 2. battery charging with fast speed //------------------------------------- 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 127 页 共 189 页 1 void Ch0_DoCharging() 2 { 3 while(1) 4 { 5 bat0_flag_initial(); // initialize flags, set PWM 6 madc_on_sub(); // check battery's status 7 8 if( !ch0_old ) show_bat0_led(); //show led which flag for battery 9 10 _delay(250000) ; // delay 0.25s 11 _delay(250000) ; 12 _delay(250000) ; 13 _delay(250000) ; // total delay 1s 14 15 if( !bat0_on_flag ) // when have no battery in,jump 16 { 17 led0_on_flag = 0 ; 18 led1_on_flag = 0 ; // clear led show 19 ch0_old = 0 ; // the battery charging finished flag clear 20 return ; // jump back 21 } 22 23 if( bat0_voltage_over_flag ) // when battery charging finish,jump 24 { 25 led0_on_flag = 1 ; // charging finished,led0&led1 on 26 led1_on_flag = 1 ; // RED, Yellow are lit 27 ch0_old=1; // old battery 28 return ; // check battery status again 29 } 30 31 if( bat0_1c_charging_flag ) // fast charging 32 { 33 Ch0_FastCharging() ; 34 return ; 35 } 36 37 // slow charging mode 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 128 页 共 189 页 38 bat0_flag_initial(); // initial charging. 39 bat0_charging_initial(); // load initial battery's average value 40 41 // bat0_01c_charging_ap: 42 while(1) 43 { 44 bat0_50ma_charging(); // charging battery by 50ma current 45 stop_charging(); // stop charging 46 _delay(250000) ; // delay 0.25s 47 _delay(250000) ; // total delay 0.5s 48 49 madc_on_sub(); // check battery status 50 led0_on_flag = 1 ; 51 led1_on_flag ^= 1 ; // toggle flag (Red LED flash) 52 53 ad_4_data(); // load battery's average 54 get_Vbat0_inc80mv(); 55 get_bat0_Vpeak(); 56 57 if(!bat0_on_flag) // if no batttery in 58 { 59 led0_on_flag=0; // battery remove,led0&led1 off 60 led1_on_flag=0; 61 ch0_old=0; // flag for battery remove already 62 return ; // check battery status again 63 } 64 65 if( time_1min >= charging_time ) // battery charging's time over 66 { 67 led0_on_flag=1; //charging finished,led0&led1 on 68 led1_on_flag=1; 69 ch0_old=1; // flag for battery : no change of battery 70 return ; // check battery status again 71 } 72 73 if( bat0_1c_charging_flag ) 74 break ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 129 页 共 189 页 75 } 76 77 led0_on_flag=1; 78 led1_on_flag=0; 79 } 80 } 程式说明 1 此函式为无限回圈, 执行电池的充电动作. 完成后根据不同的状态设定 LED 点亮 或熄灭的旗标, 再回到主程式 3 无限回圈 5 呼叫函式 bat0_flag_initial() 设定参数的初始值, 设定 PWM 6 呼叫函式 madc_on_sub(), 从 AD 转换器读出此电池的电压, 并设定相对的旗标 8 如果这是刚才放入的电池, 则以红色 LED 显示其现状 10~13 延迟 1 秒, 让 LED 闪烁 以下则根据电池的电压决定要如何进行 15~21 如果电池的电压小于 0.1V, 代表没有电池, 设定熄灭红,黄 LED 的旗标, 并返回 23~29 如果电池的电压超过 1.55V, 代表已完成充电, 即设定 LED 旗标,并返回 31~35 如果电池的电压介于 1.25V 与 1.55V 之间,表示正在充电过程中, 则呼叫函式 Ch0_FastCharging() 做快速充电. 完成后返回 38~79 电池的电压在 0.1V 与 1.25V 之间, 则用慢速充电方式 38 bat0_flag_initial() 设定 LED 旗标, PWM 值. 39 bat0_charging_initial() 设定充电的初始值 42~75 回圈, 执行慢速充电 44 bat0_50ma_charging() 利用调整 PWM 的方式控制电流对电池充电 6 小时 45~47 停止充电并等候 0.5 秒 49 madc_on_sub() 检查电池充电后的电压状态 50~51 设定 LED 显示旗标 (红闪,黄亮) 53 ad_4_data() 计算电压平均值 54~55 调整电压值与锋值 57~63 再次检查电池状况, 若是电池被移走, 则设定旗标并返回 65 如果充电时间已达 6 小时, 设定完成的 LED 旗标并返回 73 如果电压仍在未饱满的状态下, 将回到第 3 行的回圈, 重新开始充电的步骤 //--------------------------- // bat0_1c_charging_ap_lp: // fast charging mode 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 130 页 共 189 页 //---------------------------- 1 void Ch0_FastCharging() 2 { 3 bat0_flag_initial(); // initial charging. 4 bat0_charging_initial(); // load initial battery's average value 5 6 while(1) 7 { 8 bat0_500ma_charging(); // charging battery by 500ma current 9 stop_charging(); // stop charging 10 madc_on_sub(); // check battery status 11 12 if( bat0_voltage_over_flag ) // charging has been finished 13 { 14 led0_on_flag=1; // charging finished, Yellow and Red LED on 15 led1_on_flag=1; 16 ch0_old=1; // flag for battery 17 return ; // check battery status again 18 } 19 20 // set LED status 21 led0_on_flag=1; // Yellow LED is lit 22 led1_on_flag=0; // Red LED extinguish 23 24 ad_4_data(); // load battery's average 25 get_Vbat0_inc80mv(); 26 get_bat0_Vpeak(); 27 28 if(!bat0_on_flag) // battery is removed 29 { 30 led0_on_flag=0; // battery remove,led0&led1 off 31 led1_on_flag=0; 32 ch0_old=0; // flag for battery remove already 33 return ; // check battery status again 34 } 35 36 if(time_1min >= charging_time || // battery charging's time over 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 131 页 共 189 页 37 bat0_Vpeak_charging_ok ) // battery charging ok 38 { 39 led0_on_flag=1; //charging finished,led0&led1 on 40 led1_on_flag=1; 41 ch0_old=1; // flag for battery 42 return ; //check battery status again 43 } 44 } //charging go on 45 } 程式说明 快速充电 3~4 设定充电的初始值 // initial charging. 6~44 无限回圈 8 使用电流充电, 并根据电池电压调整电流大小充电, 持续 6 小时 9~10 完成后, 检查充电后电池的电压 12~18 如果电池已充满, 则设定完成的 LED 旗标, 并返回 21~22 设定 LED 旗标为黄色 LED 亮, 红色 LED 熄灭 (表示快速充电状态) 24~26 计算及保存平均电压值, 电压峰值 28~34 检查若电池已移走, 则设定 LED 旗标为熄灭状态, 并返回 36~42 如果称电时间已达 6 小时或是已充电到电压峰值, 则设定完成的 LED 旗标 并返回 44 如果不是上述状态, 则回到第 6 行重做快速充电的动作 //-------------------------------- // timer_isr //-------------------------------- 1 void interrupt timer_isr(void) @ 0x08 2 { 3 unsigned char tmp ; 4 5 tmp = _pa ^ 0x40 ; // read from PA and XOR PA6 6 _pa = tmp ; // output PA6 7 8 // turn on/off LED 9 if( (tmp & 0x40) != 0 ) // current PA6=1 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 132 页 共 189 页 10 { 11 _pa5 = 1 ; 12 if( led2_on_flag ) _pa5 = 0 ; 13 14 _pa4 = 1 ; // Red LED extinguish 15 if( led1_on_flag ) 16 _pa4 = 0 ; // Red LED lit 17 } 18 else // PA6=0 19 { 20 _pa5 = 0 ; 21 if( led3_on_flag ) _pa5 =1 ; 22 23 _pa4 = 0 ; // Yellow LED extinguish 24 if( led0_on_flag ) 25 _pa4 = 1 ; // Yellow LED is lit 26 } 27 28 // calculate the time 29 time_4ms++; // count 30 if(time_4ms==250) // 1 second 31 { 32 time_4ms=0; // clear ms 33 time_1s++; // 1s count add 34 Vpeak_cx++; // Vpeak_cx add 35 if(time_1s==90) // 90 seconds 36 { 37 time_1s=0; // time for 90s 38 time_1min++; 39 charging_cx++; // total charging time, increase 1 for every 1.5 minutes 40 } 41 } 42 } 程式说明 时钟中断服务函式 timer_isr 主要是控制红, 黄两色 LED 的点量与熄灭, 另外一个 功能是累计充电时间 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 133 页 共 189 页 5 将目前的 LED 控制码 (PA6) 读出, 再反转之 6 设定反转后的控制状态 9~17 如果此次轮到处理红色 LED (PA6 为高电位) 将红色 LED 先熄灭, 若 LED 旗标为要点亮时, 再将红色 LED 点亮 18~26 如果此次轮到处理黄色 LED (PA6 为低电位) 将黄色 LED 先熄灭, 若 LED 旗标为要点亮时, 再将黄色 LED 点亮 28 以下将累计充电时间 29 每 4 个毫秒中断一次, 累加毫秒次数 30~41 若已累加到 1 秒时, 继续处理, 累加秒次数, 电压峰值计数 35~40 若以累加到 90 秒, 则累计 90 秒次数与充电次数 原始程式 sub.c #define RAM_START 0x28 #define RAM_END 0x7F //------------------------------------- // cinitial() // turn LED (green) on for 1 second //------------------------------------- 1 void cinitial() 2 { 3 char *addr ; 4 5 _pa=0x03; 6 _pac=0x80; // PA7:I/P, others are O/P 7 _pbc=0xff; // PB are In 8 _pb=0x00; 9 _pdc=0x00; // PD are Out 10 _pd=0x00; 11 12 _acsr = 0x02; // AD clock source = system clock / 32 13 _adcr = ch3+a0; // set AD channel number and PB 14 _pwm = 0x00; // pwm output 0 15 16 for( addr=(char *)RAM_START ; addr <= (char *)RAM_END ; addr++ ) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 134 页 共 189 页 17 *addr = 0 ; // clear RAM 18 19 stop_charging(); // stop PWM 20 _intc = 0x05; // enable Timer/Counter 0 21 _tmr = (256-250); // set tmr's value for 4ms tmr (250*16us = 4ms) 22 _tmrc = timer_mode+timer_on+T1_64; // set TMR0 : timer mode, timer on and 23 // Fsys/64, Fsys=4MHz 24 led0_on_flag=1; // set LED flag 25 led1_on_flag=1; 26 led2_on_flag=0; 27 led3_on_flag=0; 28 29 _delay(250000) ; // delay 0.25s 30 _delay(250000) ; // total delay 0.5s for LED on 31 32 led0_on_flag=0; 33 led1_on_flag=0; 34 led2_on_flag=0; 35 led3_on_flag=0; // clear all led flag, turn all LEDs off 36 _delay(250000) ; // delay 0.25s 37 _delay(250000) ; // delay 0.25s 38 } 程式说明 此函式针对暂存器及记忆空间做初始化, 设定所需要的架构 5~10 设定埠 A: PA7 为输入,其它为输出型态; 埠 B 为输入型态 埠 D 为输出型态, 并且停止 PWM 动作 12 选择 (系统频率/32) 为 AD 转换器的时钟来源 13 选择 AD 转换器的通道 0 与埠 B 的 PB0, PB1, PB2 做为讯号输入通道 14 PWM 输出为零 16~17 将资料记忆空间, 位址从 28H 到 7FH, 清为零 19 停止充电动作 20 打开时钟中断功能 21 设定时钟的中断相隔时间为 4 毫秒 (250 次) (250*16us = 4ms) 22 设定时钟为计时模式, 时钟频率为系统频率除以 16, 并启动时钟开始计数 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 135 页 共 189 页 24~27 设定 LED 状态为红, 黄色皆点亮 29~30 LED 状态持续 0.5 秒 32~35 改变 LED 状态为红黄色 LED 熄灭 36~37 LED 状态持续 0.5 秒 //---------------------------- // bat_main_sub() // get battery status //---------------------------- 1 void bat_main_sub() 2 { 3 stop_charging(); // stop PWM 4 madc_on_sub(); // get battery's voltage and check status 5 6 if( !ch0_old ) show_bat0_led(); // show led 7 8 _delay(250000) ; // delay 0.25s 9 _delay(250000) ; 10 _delay(250000) ; 11 _delay(250000) ; // total delay 1s 12 } 程式说明 此函式检测座子上是否有电池等候处理 3 停止 PWM 输出 4 从 AD 转换器读取是否放置有电池, 并设定对应的旗标 6 如先前没有电池被处理或是已移走, 则以 LED 显示目前的状态 8~11 持续 1 秒 LED 的现状 //-------------------------------- // bat0_flag_initial() //------------------------------- 1 void bat0_flag_initial() 2 { 3 Vpeak_cx=0; 4 bat0_Vpeak_charging_ok=0; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 136 页 共 189 页 5 time_1min=0; 6 _pwm = 0x10 ; 7 time_4ms = 0 ; 8 time_1s = 0 ; 9 } 程式说明 此函式电池充电旗标及时间计数 3 设定固定之电压峰值的持续时间为 0, 当充电电压在一定的时间内没有变化 则会设定旗标代表已充饱 4 设定峰值旗标为 0, 此旗标为 1 时, 代表电池已充饱 5~8 时间计数的初值归零, 及 PWM 为 0 //--------------------------------- // bat0_charging_initial() //--------------------------------- 1 void bat0_charging_initial() 2 { 3 int i ; 4 5 for( i = 0 ; i < 8 ; i++ ) 6 ad_4_data(); 7 8 Vbat0_peak = Vbat0_old = Vbat0 ; 9 } 程式说明 此函式设定电池充电前的电压值与电压峰值, 做比较之用 5~6 计算之前 8 个电压平均值做参考之用 8 记录参考值 //--------------------------------------- // madc_on_sub() // get current battery voltage and status //--------------------------------------- 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 137 页 共 189 页 1 void madc_on_sub() 2 { 3 Vbat0_old = Vbat0 ; // save old battery value 4 GetBatteryVoltage(); // get battery voltage from ADC channel 0 5 check_battery0_status(); //check battery's status, set/reset flags 6 } 程式说明 此函式读取电池的电压值及设定相对的旗标 3 储存前一电压值 4 读取电池的电压值 5 根据电压值设定相关的旗标 //---------------------------------------- // GetBatteryVoltage() adc_on() // read battery's voltage from channel 0 //---------------------------------------- 1 void GetBatteryVoltage() 2 { 3 stop_charging(); // stop pwm output 4 _delay(10000) ; // delay 10ms (4MHz) 5 // start to convert by using ADC 6 _adcr = ch3+a0; // enable ADC CH0 7 _emi=0 ; // disable all interrupts 8 _start=0; 9 _start=1; 10 _start=0; // start ADC 11 while(_eoc); // wait for ADC conversion completion 12 _emi=1; // enable interrupt 13 14 // read the conversion result 12 bits, adrl = (D3,D2,D1,D0,0,0,0,0), 15 // adrh=(D11,D10,D9,D8,D7,D6,D5,D4) 16 17 bat0 = (unsigned int)_adrh ; 18 bat0 = (bat0 << 4) + (unsigned int)(_adrl >> 4) ; // read battery's voltage 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 138 页 共 189 页 19 bat0_voltage = bat0 ; 20 } 程式说明 此函式会读出电池的电压值并储存 3 暂停 PWM 的输出 4 等候 10 毫秒 6 打开 AD 转换器的通道 0 7 禁止所有中断的发生 8~10 启动 AD 转换器, 开始动作 11 等候 AD 传换器完成转换动作 12 打开控制中断发生的开辟 17~19 读出转换值, 合成一个 16 位元的电压值, 并储存 //------------------------------ // a0_discharging() //--------------------------- void a0_discharging() // 此函式是执行 电池的放电 { _pd0 = 0 ; // 停止 PWM 功能 _pa = (_pa & 0x0f3) | 0x07 ; // PA2=1 : 将 PA2 设为 1, 启动放电动作 } void a0_charging() // 此函式为开启电池的充电功能 { _pa=(_pa&0x0f1)|0x01; // 停止放电, 打开充电电路 _pd0=1; // 开启 PWM 的输出 } //-------------------- // stop_charging() //-------------------- void stop_charging() // 此函式是停止充电 { _pd0 = 0 ; // 停止 PWM 的输出机制 _pa = (_pa & 0x0f3)|0x03; // 停止充电 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 139 页 共 189 页 //----------------------------------------------- // check_battery0_status() // check the status of battery 0, set/reset flags // I/P: bat0_voltage //------------------------------------------------ 1 void check_battery0_status() 2 { 3 if( bat0_voltage < V01 ) // battery < 0.1V 4 bat0_on_flag = bat0_1c_charging_flag = bat0_voltage_over_flag = 0 ; 5 else if( bat0_voltage < V125 ) // 0.1V <= battery < 1.25V 6 { 7 bat0_on_flag = 1 ; 8 bat0_1c_charging_flag=0; 9 bat0_voltage_over_flag=0; 10 } 11 else if( bat0_voltage < V155 ) // 1.25 <= battery <1.55 12 { 13 bat0_on_flag = 1 ; 14 bat0_1c_charging_flag=1; 15 bat0_voltage_over_flag=0; 16 } 17 else 18 { 19 bat0_on_flag=1; 20 bat0_1c_charging_flag=0; 21 bat0_voltage_over_flag=1; // battery >= 1.55 22 } 23 } 程式说明 此函式将根据所读到的电池电压值设定相关之旗标, 做为之后如何进行的依据 3~4 如果电池电压小于 0.1V, 则设定旗标为 0 (电池不存在) 5~10 如果电池电压介于 0.1V 到 1.25V 之间, 代表有电池放置, 但尚未充电 11~16 如果电池电压介于 1.25V 到 1.55V 之间, 代表有电池放置并且在充电过程中 17 如果电池电压大于 1.55V, 代表此电池已完成充电 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 140 页 共 189 页 //------------------------------------ // show_bat0_led() // set the battery 0 LED flag //------------------------------------ void show_bat0_led() // 根据电池电压的现状, 设定红色 LED 的点亮与否 { led0_on_flag = 0 ; // 黄色 LED 熄灭 led1_on_flag = bat0_on_flag ; // 红色 LED, 视电池是否放置与座子 } //-------------------------------------------------------- // ad_4_data() : compute the average voltage of battery //-------------------------------------------------------- void ad_4_data() { int i ; for( i = 0 ; i < 7 ; i++ ) // move battery value forward Vbattery[i] = Vbattery[i+1] ; Vbattery[i] = bat0_voltage ; // store current voltage Vbat0_old = Vbat0 ; // save old Vbat0 for( i = 0 , Vbat0 = 4 ; i < 8 ; i++ ) Vbat0 += Vbattery[i] ; Vbat0 >>= 3 ; // div 8 for average value save to Vbat0 } 程式说明 此函式是将前面读出之电池电压值计算出其平均值 //-------------------------------------- // get_Vbat0_inc80mv() //-------------------------------------- void get_Vbat0_inc80mv() { if( Vbat0 >= (Vbat0_old + V80mv) ) // 如果电压超过前次 80mV 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 141 页 共 189 页 Vbat0 = Vbat0_old + V80mv ; // 变更目前的电压值 } //----------------------------------- // get_bat0_Vpeak() //-------------------------------- void get_bat0_Vpeak() { if( Vbat0 < Vbat0_peak ) // 如果电压小于电压峰值 { if( Vpeak_cx >= Vpk_time ) // 此电压峰值已持续 64 秒 { // 在 64 秒内充电之电压未高于此峰值 bat0_Vpeak_charging_ok = 1 ; // charging is ok when time>64 Vpeak_cx = 0 ; } } else { Vbat0_peak = Vbat0 ; // 更新电压峰值 Vpeak_cx = 0 ; // 峰值计时归零 bat0_Vpeak_charging_ok = 0 ; // 峰值旗标归零 } } //--------------------------------------------- // SetDischargingLEDflag -- bat0_discharging() //--------------------------------------------- void SetDischargingLEDflag() // 设定放电时的 LED 旗标 { led1_on_flag = 1 ; // 红色 LED 点亮 led0_on_flag ^= 1; // 黄色 LED 闪烁 } //--------------------------------- // bat0_500ma_charging() //--------------------------------- 1 void bat0_500ma_charging() 2 { 3 unsigned int temp; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 142 页 共 189 页 4 5 charging_cx = 0; 6 a0_charging(); 7 _adcr = a2+ch3; // ADC channel 2 8 9 while(1) 10 { 11 _emi=0; 12 _start=0; 13 _start=1; 14 _start=0; // start AD conversion 15 while(_eoc); 16 _emi=1; 17 18 temp = (unsigned int)_adrh ; 19 temp = (temp << 8)+(unsigned int)_adrl ; // read AD converted value 20 if((temp >= V260mv) && (_pwm != 0)) _pwm-- ; 21 if((temp < V250mv) && (_pwm != 0)) _pwm++ ; // if battery 22 // current<566ma,add pwm 23 if( charging_cx >= 240 ) return ; // if charging time >= 6 hours, return 24 } 25 } 程式说明 此函式执行快速充电, 过成中会随者电池电压调整电流 5 充电时间归零 6 开启充电机制 7 设定 AD 转换器的通道 2 为电池的电压输入通道 9~20 回圈, 将电池充电过程中的电压读入, 并依此而做电流的调整 11 禁止任何中断的发生 12~14 开启 AD 转换器的运作 15 等候 AD 转换器完成运作 16 允许中断可以发生 18~19 读出电压值 20 如果电池电压超过 293mv 而且 PWM 不为零, 则将 PWM 值减一 21 如果电池电压小于 281mv 而且 PWM 不为零, 则将 PWM 值加一 23 如果充电时间已达 6 小时, 则停止并返回 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 143 页 共 189 页 24 继续充电 //---------------------------------- // bat0_50ma_charging() //---------------------------------- 1 void bat0_50ma_charging() 2 { 3 unsigned int temp; 4 5 charging_cx=0; 6 a0_charging(); //set led status's flag 7 _adcr = a2+ch3; //set AD channel 2 8 9 while(1) 10 { 11 _emi=0; 12 _start=0; 13 _start=1; 14 _start=0; // start ADC conversion 15 while(_eoc); 16 _emi=1; 17 18 temp = (unsigned int)_adrh ; 19 temp = (temp << 8) + (unsigned int)_adrl ; // read AD conversion value 20 if( temp < V20mv ) 21 { 22 if(_pwm) _pwm++; // battery's current<58 mA,pwm add 23 else _pwm = 0x10; // minimum 24 } 25 else if( temp >= V30mv ) 26 { 27 if( _pwm ) _pwm--; // battery's current>=78 mA,pwm dec 28 else _pwm=0x10; 29 } 30 31 if( charging_cx >= 240 ) return ; // charging's time out 6 hours 32 } 33 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 144 页 共 189 页 程式说明 此函式处理慢速充电 5 设定充电时间为 0 6 开启 PWM 及充电电路 7 设定使用 AD 转换器的通道 2 9~32 回圈. 执行电池的充电流程 11 禁止所有中断的发生 12~14 开动 AD 转换器的运作 15 等候 AD 转换运作的完成 16 允许中断可以发生 18~19 读出 AD 传换后的资料 20~24 如果电池电压小于 29mv, 则将 PWM 加一 25~29 如果电池电压大于或等于 39mv, 则将 PWM 减一 31 如果充电时间已达 6 小时, 则停止并返回 32 若还未达到, 则继续充电 //-------------------------------------- // Ch0_Discharging() //-------------------------------------- 1 void Ch0_Discharging() 2 { 3 madc_on_sub(); // get battery's voltage and status 4 led0_on_flag = 0; // set LED flags 5 led1_on_flag = 1; // turn RED LED on, Yellow flash 6 bat0_charging_initial(); // initial 7 while(1) 8 { 9 a0_discharging(); // to discharge (PA2=1) 10 11 _delay(250000) ; // delay 0.25s 12 _delay(250000) ; 13 _delay(250000) ; 14 _delay(250000) ; // total delay 1s 15 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 145 页 共 189 页 16 madc_on_sub(); // get battery's voltage 17 SetDischargingLEDflag(); // set LED0 flag 18 19 ad_4_data(); // get battery's average voltage 20 if( Vbat0 >= (Vbat0_old + V80mv) ) 21 Vbat0 = Vbat0_old + V80mv ; 22 23 get_bat0_Vpeak(); // check peak value 24 bat0_discharging_ok_flag = (Vbat0 < V155) ? 1 : 0 ; // if battery < 25 // 1.15,discharging end 26 27 // if no battery in or battery charging is over or battery discharging is ok, then return 28 if( !bat0_on_flag || bat0_voltage_over_flag || bat0_discharging_ok_flag ) 29 return ; // battery discharging ok,jump 31 } 32 } 程式说明 此函式将对座上的电池做放电动作, 红色 LED 点亮, 黄色 LED 闪烁 3 读取电池的电压并设定相关的旗标 4~5 设定红色 LED 为点亮, 黄色 LED 熄灭 6 计算放电过程中的参考电压值 7~31 回圈. 放电流程 9 开启放电开关 11~14 等候 1 秒 16 启动 AD 转换器,读取电池电压,并设定相关旗标 17 设定 LED 旗标, 红色 LED 点亮, 黄色 LED 闪烁 (交互亮灭) 19 计算放电后的电压平均值 20~21 如果此电压比参考电压还低 80mv, 则将电压更新为参考电压加 80mV 23 与电压峰值做比较或更新 24 检查, 若电压小于 1.55V 则设定放电完成旗标 27 若电持已移出, 或已完成电池放电, 则返回 31 继续放电流程 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 146 页 共 189 页 第九章 程式范例 — HT46R74D-1 胎压计 (HA0105T) 9.1 目的 本装置是一个以HT46R74D-1为主晶片,配合传感器组成的汽车轮胎气压检测装置,最 终取高三位有效值显示于 LCD,有四种显示单位供切换(Psi、Bar、Kpa、Kgf/cm2 )。 本装置仅为参考范例,使用时,为提高准确度,建议多量测些组数据来建成 Sensor Output Voltage - Discharge Time (V-T)表格,并作相应校准。 9.2 功能说明 按键 一个单位切换设置按键: SELECT 一个控制I/O口:高电位执行的IO口(驱动工作指示灯) 功能描述 9.2.1 MCU 未上电或休眠(halt)状态下,接通电源或按‘SELECT’键,叫醒 MCU,系统 会点亮工作指示灯,同时测量空载气压值,并保存此校准值(校准值以固定充电 时间 Ti 下的放电时间 Tc 值保存) 9.2.2 若无按键,则取10次放电时间的平均值减去校准值作爲此次测量的Tc值(放电时 间 Tc 一秒钟刷新一次),查表后,依所选单位进行运算,取运算结果的高三位 有效值显示于 LCD;若检测到按键发生,则进入相应的按键处理流程。 9.2.3 若显示数据大于 999,则显示“---”,表示测试值超出显示的数值范围。 9.2.4 MCU 在工作状态下,每按一次‘SELECT’键,则会变换一种显示单位。显示单位按 Psi => Bar => Kpa => Kgf/cm2 的顺序循环变换。单位变换同时,显示数值也作 相应改变(单位之间的转换关系请参閲附录)。 9.2.5 MCU 在工作状态下,若在 60 秒内没有检测到按键,则关闭 LCD 显示,将驱动 LCD 的 IO 口设为 0,MCU 进入睡眠状态。 9.2.6 某些参数的定义,要根据 Sensor 和所建的 Sensor Output Voltage - Discharge Time (V-T) 表格作相应修改,如下说明: 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 147 页 共 189 页 #DEFINE PER 100 #DEFINE PSI_FULL 10000 #DEFINE BAR_FULL 689 #DEFINE KPA_FULL 0D54H #DEFINE KPA_FULL_H16 01H #DEFINE KGFCM2_FULL 703 #DEFINE TABLE_NUM 13 #DEFINE TABLE_PER 12 Note: PER: Sensor 满量程值放大100倍 PSI_FULL: Sensor 满量程值 100(PSI)*100=10000 BAR_FULL: Sensor 满量程值 6.89(PSI)*100=689 KPA_FULL: Sensor满量程值 689.48(PSI)*100=68948的低16位 KPA_FULL_H16: Sensor满量程值689.48(PSI)*100=68948的高16位 KGFCM2_FULL: Sensor满量程值7.03(PSI)*100=703 TABLE_NUM: V-T表格数据个数 TABLE_PER:本装置V-T表格中将 Sensor 输出分为12份,即每隔 5mV 建立 一组数据 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 148 页 共 189 页 9.3 电路图 (图一) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 149 页 共 189 页 电路说明 HT46R74D-1 为双积分类比数位转换型(ADC)微控制器,晶片内含有放大器,电压跟 随器,积分器和比较器。充电时,内部多路开关切换到放大器输出端,经由积分电 路为充放电电容Vc充电;放电时,多路开关切换到 VDSO,Vc即开始放电,当其电 压降至1/6VDSO时,比较器即输出低电平,视爲放电结束。 HT46R74D-1的内建 3.3V 基准电压可作爲 Sensor的电压源,VOBGP PIN 可为Sensor 提供1.5V的参考电压。Sensor的输出电压需经Amplifier放大,Amplifier模块的连 接方式具体如下(图二): (图二) 其中,VDOPAO=VOBGP+(VA-VB)*(R2/R1)=1.5V+10*(VA-VB) (and R1=R3, R2=R4, R2=10R1) 本装置所选用的 Sensor规格为:3.3V工作电压下满量程(100PSI)输出 60mV。 V-T 表格以(Sensor输出电压/5mV+10)作爲偏移位址,共包含13组数据。 在量测轮胎气压时,与所制表格数据相同充电时间下,每量测一组放电时间,即可 查表来确定与其值相近的表格偏移位址和数值,计算后,Sensor 输出电压值需取 至小数点后两位,以增加准确度。再通过单位转换,取运算结果的高三位有效值显 示于 LCD。 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 150 页 共 189 页 注意事项 9.3.1 LCD 显示介面(如图三): 设其自左往右的排布为:数据位1,Dot1,数据位2,Dot2,数据位三,并列的 四个转换单位。则其与程式相对应的分布如下表: (图三) Pin Seg0 Seg1 Seg2 Seg3 Seg4 Seg5 Seg6 Seg7 Seg8 Com0 1f 1a Bar 2f 2a Kpa 3f 3a Kgf/cm2 Com1 1e 1g 1b 2e 2g 2b 3e 3g 3b Com2 1d 1c Dot1 2d 2c Dot2 3d 3c Psi 9.3.2 请注意要根据所选用Sensor的规格来设置Amplifier的放大倍数,Sensor输出信号 经 Amplifier放大后,应不超过4/6VDSO。 9.3.3 要选用合适的充放电电阻Vr,电容Vc和充电时间Ti,使Vc电压介于1/6VDSO和 5/6VDSO之间。 9.3.4 为增加准确度,建议使用时多量测些组数据来建成表格。 9.3.5 充电时间改变,Regular 3.3V稳压输出不同等因素都会影响到放电时间值,应注意 Sensor Output Voltage - Discharge Time (V-T)表格作相应校准。 有两个中断控制与状态暂存器, INTC0, INTC1, 可用来产生ADC完成后的中断事件 其他有关的暂存器及定义列于后面, 包括 CHPRC (Charge Pump/Regulator Control) 暂存器用来控制charge pump及电压调整器的开关功能. 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 151 页 共 189 页 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 152 页 共 189 页 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 153 页 共 189 页 暂存器 ADCR(位址 18H) 与 ADCD(位址1AH) 则是控制Dual Slope A/D转换器的开 关, 充放电的设定, ADC电阻的选定以及 chopper 的频率设定. 9.4 微控制器的架构设定 (Configuration Options) //------------------------------------------------------------ // Configuration Options: //----------------------------------------------------------- //Osc: RC //Fsys: 4MHz //Wake_Up PA.0-7: PA.0 Wake_Up // PA.1-PA.7 Non Wake_Up //Pull-high PA: PULL-HIGH //Pull-high PB: PULL-HIGH //Pull-high PC: PULL-HIGH //Pull-high PD: PULL-HIGH //PA Buzzer Function: BZ/BZB Disable //Clock Source: T1 //Wdt: Disable //WDT Clock Source: T1 //CLR WDT: One clear instruction //WDT time-out period: 2^12/fs-2^13/fs 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 154 页 共 189 页 //LVR: Disable //LVD: Disable //Int Function: Disable //LCD Driver Clock: IRCOSC/3 //LCD ON/OFF At HALT: LCD OFF At HALT //LCD Duty: 1/3 duty(3 com) //LCD Bias: 1/2 bias //----------------------------------------------------------- 9.5 程式流程图 START CALL INI_RAM CALL INI_IO CALL SBR_KEY_SCAN CALL SBR_KEY_JUMP F_HALT=0? CLR LED_IO,LED灯熄灭 HALT CLR F_HALT, 设置唤醒后各寄存器和IO口状态 Y N 设置各相关寄存器 CALL SBR_ADC,测固定充电时间条件下的放电时间 进行十次充放电,取其平均值保存于[R_TMR1H,R_TMR1L] CALL SBR_TABLE,查表得放电时间所对应的偏移地址和相关表格值 CALL SBR_CALT,计算得二进制压力值并保存 CALL SBR_DIS,LCD 显示 1S计时到? SET F_ON N Y 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 155 页 共 189 页 SBR_TABLE [R_TMR1H,R_TMR1L]> [R_TABLE1_H,R_TABLE1_H]? 分别保存V-T表格的初始地址偏移量和末地址偏移量于R_ADDR1,R_ADDR2 保存该偏移地址下的表格值于[R_TABLE1_H,R_TABLE1_L]和[R_TALBLE2_H,R_TABLE2_L] RET 分别保存V-T表格的初始地址偏移量和末地址偏移量于R_ADDR1,R_ADDR2 设置标志位或校准值 [R_TMR1H,R_TMR1L]< [R_TABLE2_H,R_TABLE2_H]? 取R_ADDR1与R_ADDR2的中间地址保存于ACC ACC=0? Y ACC->TBLP,查表表格值低八位保存于R_TABLE_L Y N Y N N TBLH=R_TMR1H? TBLH>R_TMR1H? R_TABLE_L= R_TMR1L? R_TABLE_L> R_TMR1L? TBLP->R_ADDR2 R_TABLE_L->R_TABLE2_L TBLH->R_TABLE2_H TBLP->R_ADDR1 设置标志位 TBLP->R_ADDR1 R_TABLE_L->R_TABLE1_L TBLH->R_TABLE1_H A A 设置标志位或校准值 RET RET YN Y N N Y Y N 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 156 页 共 189 页 SBR_CALT F_ON=1? {[R_TABLE1_H,R_TABLE1_L] - [R_TMR1_H,R_TMR1_L}*100 {[R_TABLE1_H,R_TABLE1_L] - [R_TMR1_H,R_TMR1_L}*100/ (R_TABLE1-R_TABLE2) F_ZERO_OUTPUT=1? F_FULL_OUTPUT=1? 压力值为0.00 RET R_ADDR1-R_ADJUST_H ->R_ADDR1 CLR R_TEMP0 CLR R_TEMP1 CLR R_TEMP2 将结果保存为校准值 CLR F_ON TO0-R_ADJUST_L ->DATA0 C=1? R_ADDR1-1 R_ADDR1- R_ADJUST_H>1? 压力值为0.00 RET R_ADDR1- R_ADJUST_H>0? 压力值为0.00 RET 计算小数部分放大一百倍后与 各显示单位满量程数值的乘积 上述结果/100,得小数部分与各 显示单位满量程数值的乘积 将所得成绩高到低位依次保存于 [R_TEMP2, R_TEMP1,R_TEMP0] 计算整数部分与各显示单位 满量程数值的乘积 将小数和整数部分结果纍加保存于 [R_TEMP2, R_TEMP1,R_TEMP0] B Y N YN Y N N Y N Y YN 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 157 页 共 189 页 所得值>999 ? 999>所得值>100 ? 100>所得值>10 ? 10>所得值>1 ? 所得值<1 ? LCD显示'- - -' 设置小数标志位和寄存器 DATA0<-TO0,DATA1<-TO1 设置小数标志位 [R_TEMP2, R_TEMP1, R_TEMP0]/10- >[DATA2,DATA1,DATA0] 设置小数标志位和寄存器 设置小数标志位 [DATA2,DATA1,DATA0] /TABLE_PER CALL DA999,将十六进制书转换为十进制数 R_LCD3<- TO0 R_LCD2<- TO1 R_LCD1<- TO2 RET RET Y N Y N Y N Y N Y N B [R_TEMP2, R_TEMP1,R_TEMP0] /100/TABLE_PER 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 158 页 共 189 页 SBR_KEY_SCAN 关timer0,清timer0标志位 4ms定时结束? 是否有键被按下? 标志位F_KEY置1 Y N Y 消抖计数单元减1 F_KEY=F_KEY_TEM? 消抖计数单元=0? F_KEY->F_KEY_TEM, 还原消抖计数值 N Y N F_KEY=F_KEY_PREV ? SET F_REPEAT F_KEY->F_KEY_PREV CLR F_REPEAT RET N Y Y N 为消抖计数单元赋初值 timer0寄存器初始化,开timer 标志位F_KEY清0 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 159 页 共 189 页 SBR_KEY_JUMP 60S计时加1 F_KEY=0 ? YN 60计时结束? 标志位F_HALT置1 RET CLR R_60S_L CLR R_60S_H F_REPEAT=0 ? RET 单位转换,设置标志 位,但不能影响共用单 元的其他标志位 Y N 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 160 页 共 189 页 SBR_DIS RET R_LCD1->TBLP SET BP.0 40H->MP1 CALL SBR_DIS_LOOP 点亮/熄灭'BAR'&第一个小数点 R_LCD2->TBLP MP1+1 CALL SBR_DIS_LOOP 显示/熄灭'KPA'&第二个小数点 R_LCD3->TBLP MP1+1 CALL SBR_DIS_LOOP 显示/熄灭'KGF/CM2'&'PSI' SBR_DIS_LOOP 读取表格值 显示数码管‘D,E,F’段 MP1+1 显示数码管‘C,G,A’段 MP1+1 显示数码管‘B’段 RET 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 161 页 共 189 页 9.6 原始程式与程式说明 main.c 1 #include 2 #include "pubdef.h" 3 4 void InitialMCU(); // initial MCU 5 unsigned int SBR_ADC(); 6 void SBR_CALT_LOOP(); 7 void SBR_TABLE(); 8 unsigned long SBR_Calculate();//calculate the pressure value with different unit 9 void SBR_Display(unsigned int); //display 10 void SBR_KEY_SCAN(); //detect key 11 void SBR_KEY_JUMP(); //deal with key 12 13 unsigned char CurState ; // current state 14 unsigned int Tmr1Count ; // TMR1 count 15 unsigned int DischargeTime ; // discharge time 16 17 #pragma rambank0 18 bit bHalt ; // halt flag 19 extern bit bKeyPrev ; 20 #pragma norambank 21 22 void main(void) 23 { 24 unsigned char Seconds ; 25 unsigned int TotalTime, num ; 26 unsigned long pressure ; 27 char i ; 28 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 162 页 共 189 页 29 InitialMCU() ; 30 CurState |= POWER_ON ; //set power on or wake up state 31 32 while(1) 33 { 34 Seconds = 0 ; 35 TotalTime = 0L ; 36 DischargeTime = 0 ; 37 38 // calculate the average value of discharge time - average of 10 times 39 for( i = 0 ; i < 10 ; i++ ) 40 { 41 // get the discharge time Tc during the charge time Ti be fixed 42 TotalTime += SBR_ADC() ; 43 } 44 45 DischargeTime = (TotalTime / 10) ; // get the average time 46 while(1) 47 { 48 SBR_TABLE(); // to get the offset address of table with dichotomy 49 pressure = SBR_Calculate(); // calculate the pressure value with 50 // different unit 51 num = (unsigned int)pressure ; 52 SBR_Display(num); // display 53 54 if( bHalt ) //if at the mode of halt,jump to the halt mode 55 { 56 LED_IO = 0 ; // PA1 57 _halt(); //halt 58 bHalt=0; //wake up 59 bKeyPrev = 1; //set the previous key flag 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 163 页 共 189 页 60 _chprc = 0x63; //set 3.3V regular output 61 _adcr=0x41; //set the related registor 62 _adcd=0x07; 63 _tmr1c=0x88; 64 CurState = (PSI_STATE | POWER_ON) ; // set to 'psi' unit & power 65 // on/wake up 66 LED_IO=1; 67 break ; 68 } 69 70 SBR_KEY_SCAN(); //detect key 71 SBR_KEY_JUMP(); //deal with key 72 if(++Seconds == 50 ) //1S timing,jump to test the discharge time 73 break ; 74 } 75 } 76 } 程式说明 main.c 1 引入标头档 ht46r74d-1.h 其中定义微控制器的暂存器位址 2 引入标头档 public.h, 定义常数, 宣告全域变数 (global variables) 4~11 宣告各函式及参数的资料型态 13~15 定义全域变数 17~20 bit 型态的变数必须定义在 RAM bank 0, 因此定义或宣告的前后需要加上 前置词 #pragma rambank0 与 #pragma norambank 22 主程式的开始 24~27 宣告区域变数 (local variables) 29 呼叫 InitialMCU 函式, 执行 RAM 记忆体空间的清理及I/O输出入的设定 30 设定目前的状态为 POWER ON 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 164 页 共 189 页 33~75 主回圈. 程式进入此回圈后, 则一直在此回圈中执行, 不会跳出来 34~36 设定变数的初始值, Seconds, TotalTime 与 DischargeTime 归零 39~43 回圈 10 次, 在固定之充电时间的条件下, 取得10 次放电所需的时间 Tc, 并加总 45 将10次放电总时间除以10, 取得平均放电时间 46~74 内回圈 48 根据上述计算出的平均放电时间, 到 VT 表格中找到对应的时间 (及电压) 49 呼叫函式 SBR_Calculate() 计算出此放电时间对应的胎压值,并根据当时所选定的单 位, 将胎压值划分成三个十进位数字 52 呼叫函式 SBR_Display(num) 将此数字显示于 LCD 上 54 在 60 秒内若无按键被按下时, 则系统会进入休眠状态 bHalt 旗标会被设为1 55 系统将进入休眠状态 56 将工作指示灯 (LED) 熄灭 57 系统进入休眠暂停. 一直等到有按键被按下后, 系统才被叫醒, 并且回到此处之后 58~59 系统被叫醒后, 回到此处. 将暂停的旗标清为零, 按键旗标设为 1 60~63 重设系统架构为 3.3V 的电压调整输出, AD dual slop 的初始值, 时钟初始值 64 设定目前的状态为 胎压单位是 PSI 而且系统是 POWER ON 66 将工作指示灯 LED 点亮 67 跳回主回圈, 再次读取按键或放电时间 70 如果系统并非于休眠状态, 则继续检查是否有按键输入. 呼叫函式 SBR_KEY_SCAN 71 呼叫函式 SBR_KEY_JUMP 检查及处理输入按键, 并设定相关旗标 72 如已经过一秒的时间, 则重回主回圈再次检测放电时间及状态.否则即继续内回圈 显示上回的压力值及检测输入按键 subroutine.c 原始程式 1 #include 2 #include "pubdef.h" 3 unsigned long PresUnit ; 4 unsigned char DischargeIdx ; 5 unsigned char AdjustValue ; 6 unsigned char AdjustIdx ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 165 页 共 189 页 7 unsigned char LcdBuf[0x10] @ 0x140 ; 8 9 #pragma rambank0 10 bit bLED_Dot1 ; 11 bit bLED_Dot2 ; 12 bit bKeyPrev ; 13 bit bKeyFlag ; 14 bit bRepeat ; 15 #pragma norambank 16 17 extern unsigned char CurState ; // current state 18 extern unsigned int Tmr1Count ; // TMR1 count 19 extern unsigned int DischargeTime ; // discharge time 20 21 const unsigned char LEDDigitalPattern[11] = 22 { 23 0x5f,0x48,0x3e,0x7c,0x69, /*0~4*/ 24 0x75,0x77,0x58,0x7f,0x7d, /*5~9*/ 25 0x20 /*'-'*/ 26 } ; 27 28 const unsigned int VT_Table[TABLE_NUM] = { 29 /* ************* V-T table *************** */ 30 0x197E, 0x180b, 0x16ce, 0x1590, 0x1453, /*0,5,10,15,20;;0~4mv*/ 31 0x1315, 0x11d8, 0x109a, 0x0f5d, 0x0E1f, /*25,30,35,40,45;;5~9mv*/ 32 0x0ce2, 0x0ba4, 0x0a66 /*50,55,60;;10~14mv*/ 33 }; 34 void InitialMCU() 35 { 36 char i ; 37 char *mptr ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 166 页 共 189 页 38 39 ClearMemory() ; // 清除 RAM bank 40 41 42 _intc0 = 0; 43 _intc1 = 0 ; // interrupt disable 44 _chprc = 0x63; // set 3.3V regular output, clock divider=12 45 _adcr = 0x41; // set the related registor 46 _adcd = 0x07; 47 _tmr1c = 0x88; // set TMR1 : Timer mode, prescale stages: f(int1)=f(t1) 48 49 _pac=0xff; // set PA to input mode 50 _pbc=0xff; // set PB to input mode 51 LED_IO_C=0; // set PA.1 to output mode 52 LED_IO=1; // turn LED off 53 54 CurState = PSI_STATE ; // initial state : PSI unit 55 } 程式说明 InitialMCU 此函式是对系统做初始设定 3~6 定义全域变数 PresUnit (单位压力), DischargeIdx (放电时间所在的表格所引数), AdjustValue (调整值), AdjustIdx (调整值的表格索引数) 7 定义 LCD 的记忆体缓冲区, 从位址 0x140 到 0x14F 9~15 使用前置词定义 bit 型态的全域变数, 此类型态的变数必须定义在 RAM 的 bank 0 如果使用 HI-TECH C 编译器, 则必须将第 9 与第15 行删除 17~19 宣告使用的外部变数 CurState, Tmr1Count, DischargeTime 21~26 定义常数型的 LED 字型表格 LEDDigitalPattern, 代表数字 ‘0’ ~ ‘9’ 与 ‘-‘ 28~33 定义常数型的 VT 表格, Tc 时间从大到小, 代表气压从小到大 34~44 函式 InitialMCU, 将 RAM 记忆体 bank 0 与 bank 1 设定为 0 设定其他暂存器的初始值. 关闭硬体中断功能 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 167 页 共 189 页 设定为 3.3V regular output 与 clock divider 为 12 45 设定 ADCR 暂存器(0x18) 为放电状况, power source 是从regulator 提供 46 设定 ADCD 暂存器(0x1A), 设定chopper的频率为 ((Fsys/32)/128) 47 设定时钟 1 为计时模式 (Timer mode) 与 prescale stages 为 f(int1)=f(t1) 49~50 设定I/O的输出/输入, 埠 A 及埠 B 皆为输入模式 51 工作指示灯 (由埠 A 的第一位元控制), 设为输出模式 52 将工作指示灯熄灭 54 设定现状为使用 PSI 做为气压单位 原始程式 SBR_ADC() //----------------------------------------------- //--- SBR_ADC --- // get the discharge time Tc during the charge time Ti be fixed // O/P: time //----------------------------------------------- 1 unsigned int SBR_ADC() 2 { 3 unsigned int TcTime ; 4 5 _t1on=0; // turn TMR1 off 6 _tmr0c=0x8f; // 32us(Fsys=4MHz), TMR0 -> timer mode, Fsys 7 _tmr0=0; 8 _adcr=0x4b; // at charge mode, ADC OP chopper clock source 9 _t0on=1; // TMR0 on 10 11 while(!_t0f) ; //charge and let Vc work over 4/6 VDSO, TMR0 12 //interrupt (delay) 13 _t0f=0; 14 _t0on=0; 15 _adcr=0x4d; // set : discharge mode 16 while(_adcmpo) ; // ADCMPO high->low if integrator output is less 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 168 页 共 189 页 17 // than the reference voltage 18 _tmr0=0x83; 19 _adcr=_adcr^0x06; // set : charge mode 20 _t0on=1; // trun TMR0 on 21 _t1on=0; 22 _tmr1l=0; // clear TMR1 23 _tmr1h=0; 24 while(!_t0f) ; // delay for charge 25 26 _adcr=_adcr^0x06; // set: discharge mode 27 _t1on=1; // TMR1 on, TMR0 off 28 _t0on=0; 29 _t0f=0; 30 while(_adcmpo) ; //discharge 31 _t1on=0; // TMR1 off 32 33 // return discharge time 34 TcTime = (unsigned int)(_tmr1h) ; // read TMR1 high byte 35 TcTime = (TcTime<<8) | _tmr1l ; // read TMR1 low byte and append to int 36 // variable 37 return( TcTime ) ; // get discharge time 38 } 程式说明 此函式是从 AD dual slope 取得在固定充电时间下的放电时间, 以转换成气压数值 5 将时钟 1 停止 6 设定时钟 0 为计时模式 (timer mode), 系统 (Fsys)提供时钟振荡来源, 预除比率 为 128 (当系统频率为 4MHz时, 时钟 0 的 clock tick 为 32us) 7 设定时钟 0 的计数暂存器为 0 8 启动 ADC OP chopper, 进入充电模式 9 启动时钟 0, 开始计时 (256*32us=8ms) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 169 页 共 189 页 11 以轮询方式 (polling) 等候时钟 0 产生溢位中断 (等候充电完成) 13 清除中断旗标 14 将时钟 0 停止计时 15 设定 ADC OP chopper 为放电模式 16 检查 ADC dual slope 的 last stage comparator的输出是否低于参考电压 以回圈等候比较器的输出低于参考电压时即离开回圈 18 设定时钟 0 的计数暂存器 (0x83) 19 启动 ADC OP chopper, 进入充电模式 20 启动时钟 0 的计时功能 21~23 关闭时钟 1, 将时钟 1 的计数暂存器归零 24 等候时钟 0 产生溢位中断 (0xff-0x83+1)*32= 4ms) 26 启动 ADC OP chopper, 进入放电模式 27 打开时钟 1 28 关闭时钟 0 29 清除时钟 0 的中断旗标 30 检查 ADC dual slope 的 last stage comparator的输出是否低于参考电压 以回圈等候比较器的输出低于参考电压时即离开回圈 31 关闭时钟 1 34~35 将时钟 1 的计数暂存器内容读出 (即为放电时间) 37 回传放电时间 原始程式 SBR_TABLE //---------------------------------------------------------- //--- SBR_TABLE --- // get the opposite offset address of table with dichotomy // if DischargeTime >= VT_TABLE[i], then i-1 is the canditate // the order in VT_TABLE[] is decreasing order //----------------------------------------------------------- 1 void SBR_TABLE() 2 { 3 char i ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 170 页 共 189 页 4 5 AdjustValue = 0 ; // adjust value 6 AdjustIdx = 0 ; // adjust index 7 8 for( i = 0 ; i < TABLE_NUM ; i++ ) 9 { 10 if( DischargeTime >= VT_Table[i] ) // find the proper discharge time 11 { 12 DischargeIdx = !i ? i : i-1 ; 13 CurState &= ~KEY_TEMP ; // reset key temp flag 14 CurState &= ~FULL_OUTPUT ; // clear FULL output state 15 if( !i ) 16 CurState |= ZERO_OUTPUT ; // set zero output 17 18 if( (CurState & POWER_ON) != 0 ) // power on state 19 CurState &= ~POWER_ON ; // clear bit 20 21 return; 22 } 23 } 24 25 // i = TABLE_NUM 26 DischargeIdx = TABLE_NUM -1 ; 27 CurState &= ~( KEY_TEMP | ZERO_OUTPUT) ; // reset key temp flag & zero output 28 CurState |= FULL_OUTPUT ; // set FULL output 29 if( (CurState & POWER_ON) != 0 ) 30 { 31 CurState &= ~POWER_ON ; 32 AdjustIdx = DischargeIdx ; 33 } 34 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 171 页 共 189 页 程式说明 此函式是从 VT 表格中找出与实际放电时间相等的时间或是介于两相邻时间之间. VT 表格共 有 13 个时间参考值, 相对应于不同的气压. 将找到的时间存入参考时间变数, 以及将此时 间位于表格的索引值(index) 存入参考指标 5~6 将校正值及校正指标设为 0 8~23 回圈, 从 VT 表格中找寻与实际放电时间相等或相近的数值 此表格共有 13 个预先测量好的压力-放电时间对应值, 放电时间从大排列到小 检查的顺序是从表格的第一个数值开始比对, 直到最后一个. 如果中间有符合条件 的数值, 则会停止比较, 并返回原呼叫处 10~22 如果放电时间大于或等于表格中的数值, 则表示已找到 记录参考指标 (DischargeIdx) 为此数值所在的表格索引值 (index) 删除按键旗标 放电时间不会小于表格的最后一个数值, 所以气压不会超出表格的最高, 清除旗标 FULL_OUTPUT (超过最高气压) 如果放电时间大于或等于表格中第一个数值, 代表气压值低于表格内的13个有效值 所以设定旗标 ZERO_OUTPUT, 代表气压小于最低标准 18~19 若目前的工作状态是 POWER ON, 则清除POWER ON 旗标 21 返回原呼叫处 25 若执行到此处, 则代表放电时间低于表格内的数值 (气压超出) 26 设定参考指标 (DischargeIdx) 为表格的最后一个索引值 27 清除 KEY_TEMP 与 ZERO_OUTPUT 旗标 28 设定 FULL_OUTPUT 旗标 29~33 若目前的工作状态是 POWER ON, 清除POWER ON 旗标, 设定校正指标 34 返回原呼叫处 原始程式 SBR_CALCULATE //----------------------------------------------- //--- SBR_Calculate --- // calculate pressure with different unit and pressure number // O/P: pressure number 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 172 页 共 189 页 //----------------------------------------------- 1 unsigned long SBR_Calculate() 2 { 3 unsigned long rtmp, rtmp1, Tslice ; 4 unsigned char rate ; 5 6 if( (CurState & ZERO_OUTPUT) == ZERO_OUTPUT ) // it's Zero output 7 { //for zero output of sensor 8 bLED_Dot1 = 1 ; //to display 0.00 9 bLED_Dot2 = 0 ; 10 return(0L) ; 11 } 12 13 if( (CurState & FULL_OUTPUT) == FULL_OUTPUT ) 14 { 15 DischargeIdx -= AdjustIdx ; //for full output of sensor 16 rtmp = 0 ; 17 } 18 else 19 { 20 Tslice = (unsigned long)((VT_Table[DischargeIdx] - DischargeTime) * 21 100) ; 22 rate = (unsigned char)(Tslice / (VT_Table[DischargeIdx] - 23 VT_Table[DischargeIdx+1])) ; 24 25 if( CurState & POWER_ON ) 26 { 27 CurState &= ~POWER_ON ; // clear flag 28 AdjustValue = rate ; 29 AdjustIdx = DischargeIdx ; 30 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 173 页 共 189 页 31 32 if( rate < AdjustValue ) 33 { 34 if( DischargeIdx >= AdjustIdx ) 35 { 36 bLED_Dot1 = 1 ; //to display 0.00 37 bLED_Dot2 = 0 ; 38 return(0L) ; 39 } 40 DischargeIdx--; 41 } 42 else rate -= AdjustValue ; 43 44 if( DischargeIdx < AdjustIdx ) // DischargeIdx R_ADJUST_H 52 SBR_CALT_LOOP(); // O/P: PresUnit 53 rtmp = (PresUnit * rate) / 100 ; 54 } 55 56 SBR_CALT_LOOP(); 57 rtmp += PresUnit * DischargeIdx ; 58 rtmp1 = rtmp / (PER * TABLE_PER) ; // 12 * 100 R_TO0, R_TO1 = rtmp1 59 60 if( rtmp1 > 100 ) // rtmp1 > 100 61 { 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 174 页 共 189 页 62 bLED_Dot1 = bLED_Dot2 = 0 ; 63 return(rtmp1) ; 64 } 65 if( rtmp1 > 10 ) // 100>= rtmp > 10 66 { 67 bLED_Dot1 = 0 ; //>10 & <100 68 bLED_Dot2 = 1 ; 69 return( rtmp / (10 * TABLE_PER) ) ; 70 } 71 72 bLED_Dot1=1; 73 bLED_Dot2=0; 74 return( rtmp / TABLE_PER ) ; 75 } 程式说明 SBR_CALCULATE 此函式将根据上述找到的参考时间, 参考指标及实际放电时间, 配合所选定的气压单位 计算出气压值. 计算原理是 VT 表格共有 13 个时间值, 代表从气压 0.00 到最高气压值 的 12 个相等间隔, 各气压单位有其满量值. 因此第 n 个间隔的气压值则是 (n=0,1,..12) n * (满量值 / 12) 而实际放电时间不一定刚好等于某个参考时间 (会大于某个参考时间) 所以要将此小部分的气压加入, 先计算它占此间隔的百分比, 再乘以间隔的平均气压值. 由于计算时使用 char, long 等整数型态的变数 (可考虑使用 float 型态)所以会先做乘法 运算, 将数值扩大 100 倍. 其公式如下 rate = [(参考时间 – 实际放电时间) * 100] / [参考时间 – 下一个参考时间] pressure1 = [rate * (满量值/12)] / 100 (rate 与满量值皆已乘上 100) [参考时间 – 下一个参考时间] 是指实际放电时间介于的范围, 也就是 参考时间 > 实际放电时间 >= 下一个参考时间 程式中的满量值已经乘过 100, 以便与 presure1 相加. 所以最后总值要除以100 最后的总气压值 = (n * (满量值/12) + pressure1) / 100 程式中的计算顺序有些不同, 原理则相同 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 175 页 共 189 页 7~11 如果旗标 ZERO_OUTPUT 被设定, 代表放电时间超过 VT 表格的最高值(气压低于 有效值). 无法显示压力数值, 所以设定显示 0.00 第一个小数点要显示 返回 0 数值 13~17 如果旗标 FULL_OUTPUT 被设定, 代表放电时间低于 VT 表格的最低值(气压高于 表格中的有效值) 将参考指标 减去 校正值指标 设定气压偏量为 0, 并跳到第 57 行, 计算总气压值 18~54 不是 ZERO, 也不是 FULL. 表示放电时间介于表格中的数值, 执行下列 20~23 计算 气压偏量比率 rate = ((参考时间 – 实际放电时间) * 100) / 参考时间差 参考时间差 = VT_Table[参考指标] - VT_Table[参考指标+1] 25~30 如果目前之工作状态为 POWER_ON, 则清除 POWER_ON 旗标, 设定校正值 = 气压偏量比率. 设定校正指标 32~41 如果气压偏量比率小于校正值, 则检查参考指标 如果参考指标小于或等于校正指标, 则气压显示值设为 0.00 设定要显示第一个小数点, 返回呼叫处 否则, 将参考指标减一 42 如果气压偏量比率大于校正值, 则将气压偏量比率减去校正值 44~49 检查参考指标, 如果小于校正指标, 则显示值为 0.00 设定要显示第一个小数点, 返回呼叫处 51 参考指标减去校正指标 52 取得目前的气压单位的满量值 53 计算 气压偏量值 = (气压偏量 * 满量值) / 100 (此时并未除以 12) 56 SBR_CALT_LOOP() 取得气压单位的满量值 57~58 气压总值 = [气压偏量值 + (满量值 * 参考指标)] / (12*100) 除以 12 是满量值分成均等的 12 间隔, 100 则是计算过程中放大, 此时要改回 60 根据气压总值, 决定是否要显示小数点 60~64 如果气压值大于或等于 100 (有三位数字), 则不需显示任一小数点 返回此气压值 65~70 如果气压值介于 99 与 10 之间 (两位数), 则需要显示第二个小数点 同时将气压总值重计算为 只除以 10 (取三位数字), 在返回原呼叫处 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 176 页 共 189 页 气压总值 = [气压偏量值 + (满量值 * 参考指标)] / (12*10) 72~74 如果气压总值介于 9 与 1 之间, 则需要显示第一个小数点 Dot1=1 同时气压总值不需要除以 100, 而改为 气压总值 = (气压偏量值 + (满量值 * 参考指标)) / 12 返回原呼叫处 原始程式 SBR_CALT_LOOP //----------------------------------------------- //--- SBR_CALT_LOOP --- //SBR for SBR_CALT get the pressure of current unit //----------------------------------------------- 1 void SBR_CALT_LOOP(void) 2 { 3 switch( CurState & 0x0f ) // check bit0~4 4 { 5 case PSI_STATE : // if unit is Psi 6 PresUnit = PSI_FULL ; // unsigned long 7 break ; 8 case BAR_STATE : //if Bar 9 PresUnit = BAR_FULL ; 10 break ; 11 case KPA_STATE : //if Kpa 12 PresUnit = KPA_FULL_H16 ; 13 PresUnit = KPA_FULL | (PresUnit << 16) ; 14 break ; 15 case KGFCM2_STATE : //if Kgf/cm2 16 PresUnit = KGFCM2_FULL ; 17 break ; 18 } 19 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 177 页 共 189 页 程式说明 SBR_CALT_LOOP 此函式将依照目前指定之气压单位, 给予此单位的满量程数值 3 分析目前所指定的气压单位 5~7 单位是 PSI, 将 PSI 的满量程数值给予变数 PresUnit 8~10 单位是 Bar, 将 Bar 的满量程数值给予变数 PresUnit 11~14 单位是 Kpa, 将 Kpa 的满量程数值给予变数 PresUnit 15~17 单位是 Kgf/cm2, 将 Kgf/cm2 的满量程数值给予变数 PresUnit 原始程式 SBR_KEY_SCAN //----------------------------------------------- //--- SBR_KEY_SCAN --- //--- detect key --- //----------------------------------------------- 1 void SBR_KEY_SCAN() 2 { 3 unsigned char Debounce_count ; 4 5 Debounce_count = R_20MS ; // detect key for 20mS 6 CurState &= ~KEY_TEMP ; // clear KEY_TEMP flag 7 _tmr0c = 0x0A0; // set TMR0 to Timer mode, internal clock (Intl. 8 // RC), disable 9 while(1) 10 { // wait TMR0 time out 11 _tmr0 = 208 ; // 4mS 12 _t0on = 1 ; // turn TMR0 on 13 while( !_t0f ) ; // wait internal timer/event counter 0 14 // interrupt flag 15 _t0f = 0 ; // clear internal timer/event counter 0 request flag (0b.5) 16 _t0on = 0 ; // turn TMR0 off 17 // check input key 18 bKeyFlag = KEY_IO ? 0 : 1 ; // check if key press or not(key no), PA.0 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 178 页 共 189 页 19 // set flag 20 if( (bKeyFlag && (CurState & KEY_TEMP)==0) || 21 (!bKeyFlag && (CurState & KEY_TEMP)!=0) ) 22 { 23 CurState ^= KEY_TEMP ; // toggle flag KEY_TEMP 24 Debounce_count = R_20MS; // renew 25 continue ; 26 } 27 28 if( --Debounce_count == 0 ) 29 break ; 30 } 31 32 Debounce_count = R_20MS; 33 if( bKeyFlag ) // input key 34 { 35 if( bKeyPrev ) // has pressed previously 36 bRepeat = 1; // set repeat flag 37 else 38 { 39 bRepeat =0; 40 bKeyPrev = 1; 41 } 42 } 43 else // no input key 44 { 45 if( bKeyPrev ) 46 bRepeat = bKeyPrev = 0 ; 47 else 48 bRepeat = 1; 49 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 179 页 共 189 页 50 51 CurState &= ~(ZERO_OUTPUT | FULL_OUTPUT | KEY_TEMP) ; // clear flags 52 } 程式说明 SBR_KEY_SCAN 检测是否有按键输入 5 设定检测时间为 20ms (时钟 0 每隔 4ms 中断一次, Debounce_count=5 共 5 次, 20ms) 检测在此时间内按键确实被按下 (消除按键弹跳的误认) 6 清除目前工作状态的 KEY_TEMP 旗标 7 设定时钟 0 为计时模式, 使用内部时钟振荡 (12KHz), prescaler 1:1 暂停计时 9~30 回圈, 等候按键输入 11 设定时钟 0 的中断时间间隔为 4ms ( TMR0 = 208) (256-208)*(1/12KHz)=4ms 12 启动时钟 0 13 回圈, 等候时钟 0 产生溢位中断 15 清除时钟 0 的中断要求旗标 16 停止时钟 0 的计时 18 检查是否有按键输入 (埠 A 位元 0) 并设定旗标 bKeyFlag 20~26 如果此次有按键输入但前次并无按键 或是前次有按键输入而此次无输入 代表没有确实有效的按键输入 则将目前工作状态变更为此次的按键状态, 重设检测时间 回到回圈开始, 继续检查按键状态 28~29 如果检测时间已减到零, 则跳离回圈 30 否则就继续检测按键 32 设定检测为 5 次 33~42 如果按键输入被确认有效, 分析输入按键的状态 35~36 如果此键先前已按过, 则设定 bRepeat 旗标 (重复) 37~41 否则, 清除 bRepeat 旗标, 设定 bKeyPrev 旗标 (已有按键输入) 43~49 若没有按键输入, 分析状态如下 45~46 如果前次有按键输入, 则清除旗标 bRepeat 与 bKeyPrev 47~48 否则, 设定旗标 bRepeat 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 180 页 共 189 页 51 清除目前工作状态中的旗标 ZERO_OUTPUT, FULL_OUTPUT 与 KEY_TEMP 52 返回原呼叫处 原始程式 SBR_KEY_JUMP //----------------------------------------------- //--- SBR_KEY_JUMP --- //--- deal with key --- //----------------------------------------------- 1 unsigned int ElapseTime ; 2 3 void SBR_KEY_JUMP() 4 { 5 unsigned char tmp ; 6 7 if( !bKeyFlag ) // no input key 8 { 9 ElapseTime++ ; 10 if( ElapseTime >= 0x0bb8 ) // 60 seconds 11 bHalt = 1 ; // set halt flag 12 return ; 13 } 14 // has input key 15 ElapseTime = 0 ; 16 if( !bRepeat ) // if no repeat key, advance to next unit 17 { 18 tmp = CurState & 0x0f; // current unit 19 tmp = (tmp == KGFCM2_STATE) ? PSI_STATE : (tmp << 1) ; 20 // change to next unit state 21 CurState &= 0xf0 ; // clear bit0 ~ 3 22 CurState |= tmp ; // set to next unit 23 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 181 页 共 189 页 24 } 程式说明 SBR_KEY_JUMP 1 定义 ElapseTime 为经过的时间 7~13 当没有按键输入时, 则累计时间并检查是否已历经 60 秒还没有按键输入 10~11 如果是已过 60 秒 (0x0bb8=3000, 3000*20ms=60s) 没有按键输入, 则设定旗标 bHalt = 1 13 返回呼叫处 14 有按键输入时, 清除变数 ElapseTime, 分析按键 16~23 如果按键并非重复的, 则执行下列之工作 将气压单位变换成下一种计数单位, 依照 Psi => Bar => Kpa => Kgf/cm2 的顺序 循环变换. 将新的单位设定在目前工作状态的变数中 24 若是重复的按键, 则不做处理 原始程式 SBR_DISPLAY //----------------------------------------------------------------- //--- SBR_Display --- //--- display the presure value( 3 decimal digit and unit scaler) --- // I/P: value = number to be displayed (decimal) // write digit pattaern to LCD RAM buffer, 140H ~ 148H //----------------------------------------------------------------- 1 const char divi[3] = { 100, 10, 1 } ; 2 3 void SBR_Display(unsigned int value) 4 { 5 char i, num, pattern, flag ; 6 // display digit 7 for( i = flag = 0 ; i < 3 ; i++ ) // one digit uses 3 LCD RAM bytes 8 { 9 num = value / divi[i] ; // separate the input value 10 value = value % divi[i] ; 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 182 页 共 189 页 11 if( (num > 9) || (flag==1) ) 12 { 13 flag = 1 ; 14 num = 10 ; 15 } 16 pattern = LEDDigitalPattern[num] ; // map the pattern of digit number 17 LcdBuf[3*i] = pattern & 0x7 ; // low nibble for d/e/f segment 18 LcdBuf[3*i+1] = (pattern >> 4) & 0x07 ; // high nibble for c/g/a segment 19 20 num = ((pattern & 0x08)!=0) ? 0x02 : 0 ; // b segment 21 switch( i ) 22 { 23 case 0 : // first digit (left) 24 if( (CurState & BAR_STATE) != 0 ) num |= 0x01 ; 25 if( bLED_Dot1 ) num |= 0x04 ; 26 break ; 27 case 1 : // second digit 28 if( (CurState & KPA_STATE) != 0 ) num |= 0x01 ; 29 if( bLED_Dot2 ) num |= 0x04 ; 30 break ; 31 case 2 : // third digit (right) 32 if( (CurState & KGFCM2_STATE) != 0 ) num |= 0x01 ; 33 if( (CurState & PSI_STATE) != 0 ) num |= 0x04 ; 34 break ; 35 } 36 LcdBuf[3*i+2] = num ; // write (display) the 3rd byte 37 } 38 } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 183 页 共 189 页 程式说明 SBR_DISPLAY 1 定义除数,分别为 100, 10 与 1 3 此函式会将输入的数值分成十进位数字显示于LCD上 7~37 回圈, 显示三个数字及小数点 9 轮流计算出百位数字, 十位数字及个位数字 (从百位开始) 10 保留剩下的数值 11~15 如果数字超过 9 或是输入数值大于 999, 则会转成显示 ‘---‘ flag = 1 记录数值大于999; num = 显示的数 16 从表格 LEDDigitalPattern 中取出对应的数字字型 17~18 将此字型写入LCD 的记忆体位址, 显示出此数字. 每个数字的字型分别写入 LCD 记忆体的三个位元组. 可参考前面 LCD 数字图形. 第一个位元组的位元 0,1,2 存放 segment f,e,d 第二个位元组的位元 0,1,2 存放 segment a,g,c. 第三个位元组的位 元 1 存放 segment b, 其他两个位元则存放第一个小数及第二个小数点及气压的单 位名称 20 从字型取出 segment b 的值 21~35 根据数值, 决定 dot1, dot2 及气压单位是否要显示 如果是百位 (i=0) 数字, 则分析是否显示第一个小数点及是否显示 BAR 单位 如果是十位 (i=1) 数字, 则分析是否显示第二个小数点及是否显示 KPA 单位 如果是个位 (i=2) 数字, 则分析是否显示单位 KGF/CM 或 PSI 36 写入 LCD 记忆体的第三个位元组资料 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 程式语言及应用范例(三) 版 别 1.10 日 期 2007/11/14 第 184 页 共 189 页 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.10 日 期 2007/11/14 第 185 页 共 189 页 第十章 组合语言与 C 语言的互用 (Mixed Language) 微控制器(micro-controller, MCU) 周边装置的控制及状态读取, 有时使用组合语言编写 程式时, 会产出更佳的执行效果. 一种是在 C 语言程式中使用 inline assembly的方式, 另外则是使用函式呼叫 (function call) 的方式. 前者在第四章的 4.9.3 节介绍. 本章将 介绍第二种方式. 此方式包含从 C 语言程式中呼叫组合语言程式以及从组合语言程式 中呼叫 C 语言程式等两种. 下列的项目及其规则必须遵守, 否则无法达到预期的功能. Æ 变数, 函式与参数的命名规则 (naming of the variables, functions and parameters) Æ 参数的传递 (parameters passing) Æ 返回值的设定 (return value) 10.1 变数, 函式与参数的命名规则 名字的差异 → C 语言的英文字母大小写被视为不同的字 (case-sensitive), 例如 count 与 Count 是两个 不同的名字 → 组合语言的英文字母则是不分大小写皆被视为相同的字, 例如 Length 与 length 代表 同一个的名字. 编译后的全域变数与函式名字 → C 编译器在编译全域变数 (global variable) 及函式时, 会在原名之前附加一个底线字元 (underscore), 例如 全域变数 count 编译后改为 _count 全域变数 Length 变译后改为 _Length 函式 GetTotalSize( ) 编译后改为 _GetTotalSize( ) → 组译器 (Assembler) 在编译组合语言程式后,会将名字转换成大写字母 (upper case) 例如, 变数 count 变译后改为 COUNT 变数 Length 变译后改为 LENGTH 函式 GetTotalSize( ) 编译后改为 GETTOTALSIZE( ) 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.10 日 期 2007/11/14 第 186 页 共 189 页 编译后的 C 函式的参数名字 (Function Parameters Name) 函式的参数名字在编译后改为与函式名字相同, 但要附加索引值 (index number). 例如 函式 char GetSum(char x, char y) 的参数 x 将改为 GetSum0, 而参数 y 将改为 GetSum1. 当 C 程式呼叫函式时, 例如 z = GetSum(1, 4) ; C 编译器会将第一个参数的值 1 存放到 GetSum0 变数, 将第二个参数的值 4 存放到 GetSum1 中. 10.2 参数的传递 由于 MCU 的堆叠(stack) 不能被应用程式使用, 所以C 编译器会将函式的参数存放于资料记 忆体 (RAM) 中做为参数传递的媒介. 不论参数的资料型态为何, 皆是以位元组的格式定义, 例如 void SetPos(int xpos, int ypos) ; 其中参数 xpos与 ypos的资料型态为 int (2 个位元组), 经编译后, xpos 与 ypos 分别改为 SetPos0 与 SetPos1, 其定义在 RAM 的格式为 SetPos0 DB 2 dup(?) ; xpos SetPos1 DB 2 sup(?) ; ypos 函式 SetPos 将从 SetPos0 与 SetPos1变数中读取参数值. 10.3 返回值的设定 C 函式的返回值需视返回资料型态(return type)来决定使用那些资源做传递的媒介. 下表为返回值存放的暂存器或变数 返回的资料型态 返回值大小 LBLW HBLW LBHW HBHW void 0 - - - - char 1 (位元组) ACC - - - int/short 2 (位元组) ACC RH - - long/float 4 (位元组) ACC RH RM RU ACC Æ A 暂存器 RH, RM, RU Æ 变数 低位字低位元组 Æ Low Byte of Low Word (LBLW) of return value 低位字高位元组 Æ High Byte of Low Word (HBLW) of return value 高位字低位元组 Æ Low Byte of High Word (LBHW) of return value 高位字高位元组 Æ High Byte of High Word (HBHW) of return value 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.10 日 期 2007/11/14 第 187 页 共 189 页 10.4 在 C 语言程式中呼叫组合语言函式 (Calling Assembly Function From C Program) 跨越不同语言程式的函式呼叫, 在 C 程式中执行函式的呼叫, 而在组合语言程式中定义 被呼叫的函式及变数. 其撰写规则如下 组合语言程式的函式定义规则 Æ 将底线字母(underscore)加入函式名字之前, 并且宣告为公用变数 (public variable) (A-1) Æ 如果函式具有参数, 则将所对应的变数设定于 RAM bank 0, 并宣告为公用变数 (A-2) Æ 如果函式具有返回值, 且其资料型态是 int/short, 则宣告变数 RH为外部变数(external variable), 如果资料型态为 long/float, 则宣告 RH, RM 及RU为外部变数 (A-3) Æ 视函式返回值的资料型态, 在函式返回前设定相对的变数, A, RH, RM 或 RU (A-4) 范例 : EXTERN RH:byte ;; 宣告返回值的高位元组为外部变数 (A-3) PUBLIC _DISPLACE ;; 函式名字前加上底线字母, 并宣告为公用 (A-1) PUBLIC DISPLACE0, DISPLACE1 ;; 宣告参数为公用变数 (A-2) RAMBANK0 DISPDATA ;; 定义参数存放的记忆体为 RAM bank 0 (A-2) DISPDATA .section ‘data’ ;; (A-2) DISPLACE0 DB ? ;; 定义第一个参数 row (char , 1 位元组) (A-2) DISPLACE1 DB 2 dup(?) ;; 定义第二个参数 col (int, 2 位元组) (A-2) RESULT DB ? ;; 执行函式的结果, 暂时变数的低位元组 ;; 定义函式 _DISPLACE CODE .section ‘code’ _DISPLACE: CLR [0Ah].0 ;; 清除 CF 旗标 RLA DISPLACE0 ;; 读取第一个参数 row, 向左旋转一位元,存入 A ADD A, DISPLACE1 ;; 读取第二个参数 col 的低位元组, 并加到 A MOV RESULT, A ;; 将结果的低位元存入暂时变数 MOV A, 0 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.10 日 期 2007/11/14 第 188 页 共 189 页 ADC A, DISPLACE1[1] ;; 将第二个参数的高位元组加入 MOV RH, A ;; 将返回值的高位元组存入 RH (A-4) MOV A, RESULT ;; 返回值的低位元组存入 A (A-4) RET C 程式的呼叫规则 Æ 以大写字母定义并宣告要呼叫的函式名字 (C-1) Æ 呼叫此函式 (C-2) 范例 : extern int DISPLACE(char row, int col) ; // 以大写字母定义函式名字 (C-1) void main( ) { int disp ; // 返回值 disp = DISPLACE(10, 20) ; // 呼叫函式 (C-2) } 上例, 在 C 程式中呼叫 DISPLAY(10,20) 后, 返回值为 40 10.5 在组合语言程式中呼叫 C 语言函式 (Calling C Funftion from Assembly Progam) C 程式的呼叫规则 Æ 以大写字母定义并宣告被呼叫的函式名字 (C-1) 范例: int DISPLAY(char row, int col) ; // 宣告函式的类型 (C-1) int DISPLAY(char row, int col) // 定义函式 (C-1) { int retval ; retval = (int)(row << 1) + col ; return retval ; } 盛群半导体股份有限公司 HOLTEK SEMICONDUCTOR INC C50微电脑工具事业处部 撰 写 人 : 文 件 编 号 版 别 1.10 日 期 2007/11/14 第 189 页 共 189 页 组合语言程式的函式定义规则 Æ 将以底线字母(underscore)为首的函式名字宣告为外部名字 (external function) (A-1) Æ 如果函式具有参数, 则将所对应的变数宣告为外部变数 (external variable) (A-2) Æ 如果函式具有返回值, 且其资料型态是 int/short, 则宣告变数 RH 为外部变数(external variable), 如果资料型态为 long/float, 则宣告 RH, RM 及 RU 为外部变数 (A-3) Æ 呼叫 C 函式后, 从 A, RH, RM 或 RU 读出返回值 (A-4) 范例 : EXTERN RH:byte ;; 宣告返回值的高位元组为外部变数 (A-3) EXTERN _DISPLACE : near ;; 函式名字前加上底线字母, 并宣告为外部 (A-1) EXTERN DISPLACE0 : byte ;; 宣告参数为外部变数 (A-2) EXTERN DISPLACE1 : byte ;; 宣告参数为外部变数 (A-2) ;; 呼叫函式 _DISPLACE CODE .section ‘code’ Start : MOV A, 10h MOV DISPLACE0, A ;; 存值到第一个参数 row MOV A, 20h MOV DISPLACE1, A ;; 存值到第二个参数 col 的低位元组 CLR DISPLACE1[1] ;; 第二个参数的高位元组设为 0 CALL _DISPLACE ;; 呼叫 C 函式 _DISPLACE ;; C 函式返回值 40 会存放于 A, RH 内容为 0 (A-4) …… RET

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高员外,我们会尽快处理。