第三章 用C语言编程.......................... 39
开始一个C 程序.............................39
定义中断入口向量............................40
在 C源程序文件中宣告中断服务子程序的名称和地址......40
在 C源程序文件中定义 40
限制..................................40
在程序存储器中定义表格与标号....................41
在数据存储器内定义变量........................42
指定变量地址............................42
在多个数据存储器区块访问变量...................42
在程序存储器区块 0中指定变量(提高性能) ............43
指针范围................................44
访问 LCD数据存储空间.......................45
单片机特殊功能寄存器..........................42
访问特殊功能寄存器........................42
访问输入输出端口..........................47
内置函数..................................48
类似汇编语句的内置函数.......................48
移位函数................................49
高低位交换函数............................49
延迟周期函数............................50
编程提示..................................50
定义变量为无符号数据类型.....................50
将变量定义在数据存储区块 0....................51
定义位变量..............................52
分配地址给指针............................52
使用更有效的方法获得模数.....................53
常量变换 / 强制转换.........................54
串行端口传输范例............................56
初始程序................................56
调节传输时序............................57
波特率匹配调节............................58
框架程序范例................................59
数据类型..................................60
数据类型................................60
第三章 用 C 语言编程
此章节包括以下部分:
• 开始一个C 程序
• 定义中断服务入口向量(ISR)
• 在程序存储器中定义表格与标号
• 在数据存储器内定义变量
• 单片机特殊功能寄存器
• 内置功能
• 编程小窍门
• 串口通信例程
• 程序主要架构
• 数据类型
HT-MCU集成开发环境 IDE3000。
中断向量
MCU复位后程序从ROM的0地址开始执行。这里有一条跳转指令,使程序跳转到主程序 main。
从0地址开始有若干特殊地址,都是中断发生时程序自动跳转到的地址,这些地方也放置跳转指令引导MCU进入相应的中断响应函数中。
定义中断入口向量
当项目中要使用单片机中断以及相关的中断服务程序(ISR), 那么在编写相关的C 程序中要注意适当的使用以及限制。
没有必要保存系统寄存器, Holtek的 C编译器会自动地将使用到的
系统寄存器进行保存。
声明ISR函数:
#pragma vector IsrRoutineName @ address
其中
pragma 和 vector是关键字。
IsrRoutineName是中断服务子程序的名字。
Address是中断服务子程序在程序存储器中的入口地址。
地址 0 是为函数 main 保留的不能使用。
限制
使用C 语言编写 ISR 时,要记住有以下几项限制
• ISR 没有参数并且返回类型为 void。
• ISR 不可以重复进入,不要在 ISR内允许中断。
• 当中断发生时系统会自己响应。用户不要调用它。
• ISR 中不要调用任何 C 自定义函数,但是内置的函数没有关系。ISR 中可调用汇编函数。
• 如果 ISR 内包含嵌入汇编指令,那么由于执行这些指令而影响到的寄存器就需要在执行这些指令之前预先保留,待执行完毕恢复寄存器。 因为Holtek的 C编译器只保存由于C语句造成影响的寄存器。
示例:
#include <ht47c20.h>
#pragma vector _ExternISR @ 0x4
#pragma vector _TimeBaseISR @ 0x8
#pragma vector _RTCISR @ 0xc
#pragma vector _TimerISR @ 0x10
unsigned count;
void _ExternISR(void){
}
void _TimeBaseISR(void){
count=count>>7|count<<1;
_pa = count;
}
void _RTCISR(void){
}
void _TimerISR(void){
}
void main()
{
count=0xee;
_intc0=0x05; //EMI&ETBI ENABLE
while(1);
}
用 const 在ROM中定义常量表格与标号,这些常数的最大值为 255,如果需要更大的数值,它们可以分为好几个常数。
示例:
// Define Table/Constant in Program Memory
const unsigned char ascii[16]=”0123456789ABCDEF”;
const unsigned char pattern[16]={0,1,2,3,4,5,6,7,8, 9,10,11,12,13,14,15};
const unsigned int cl = 0x8B;
// Define Variables in Data Memory
#pragma rambank0
unsigned char str[2];
void itoa(unsigned int v, unsigned char *s){
*s = ascii[v & 0xf]; // Lookup Table
_swap(&v); // swap nibble
*(s+1) = ascii[v & 0xf];
}
void main(){
unsigned int val;
val = cl;
itoa(val, str);
}
在数据存储器内定义变量
HT-MCU中Ram有2/3个功能块:Special RAM,General RAM,LCD RAM。
RAM可能分为多个Bank。
Special RAM 是常驻单片机存储器的功能寄存器。
General RAM 为程序变量提供空间。
LCD RAM 保存的数据是为 LCD 显示用的。
一般地,变量由 C编译器在通用数据存储器中分配。可使用 @ 为变量指定地址。
对于特殊存储器和 LCD 数据存储空间来说,需要给出明确的专有地址!
• 语法
data_type varaible_name @ memory_address
• 描述
memory_address的高位表示RamBank,低位表示Bank内的地址。
示例:
int v1 @ 0x50; // v1 is in address 0x50 of RAM bank 0
int v3 @ 0xef0; // v3 is in address 0xf0 of RAM bank 14
在多个数据存储器区块访问变量
ASM中,必须通过设置区块指
针并且间接寻址来访问高位数据存储器区块中的变量。
C中,用户不需关心这些,因为CCompiler已经做了。
• 示例
int v1 @ 0x5B; // v1 is in address 0x5B of RAM bank 0
int v2 @ 0x2F0; // v2 is in address 0xF0 of RAM bank 2
int v3; // Bank number is unknown
void main(){
v1 = 10; //access bank 0 variable
v2 = 10; //access high bank variable
v3 = 10;
}
指定RamBank0
直接访问比间接访问高效,所以尽量将访问频率较高的变量定义在Bank0。
#pragma rambank0
/
/ 这里定义的变量要求在Bank0。 HT-C要求位变量必须定义在Bank0。
#pragma norambank
// 终结 rambank0 功能
// 这里定义的变量由CCompiler安排
如果该单片机只有一个数据存储器区块的话,那么编译器会忽略这两个关键字。
• 示例
// default is norambank
unsigned int v1; //v1’s bank number is unknown
//switch to rambank0
#pragma rambank0
unsigned int i, j; //i, j located at RAM bank 0
unsigned char uc0 @ 0x83; //In rambank0 area the address cannot be larger than 0x100
// back to norambank
#pragma norambank
unsigned int iflag; //bank number of iflag is unknown
unsigned char uc @ 0x140;
//switch to rambank0 linking mode
#pragma rambank0
bit bitflag; //bit variable should always be declared in rambank0 block
void main(){
...
}
指针范围
当指针运算溢出时,它将重叠! 不会访问到相邻bank.
• 范例
#pragma rambank0
unsigned char *p1;
unsigned long *p2;
void main(){
p1 = (unsigned char *)0x2f0;
p1 += 0x20; //now p1 points to address 0x210, not 0x310 !
p1 = (unsigned char *)0x100;
p1--; //now p1 points to address 0x1ff, not 0xff
p2 = (unsigned long*)0x3fe;
p2++; // ’long’ occupies two bytes.
// now p2 is pointed to 0x300 not 0x400.
}
HT-C不支持长整型指针变量的运算。
• 例如:
#pragma rambank0
unsigned char *p1, *p2;
unsigned int i;
unsigned long len;
void main(){
p1 = p2+10; //ok
p1 = p2+0x100; // !error, 0x100 is a long integer
p1 += i; //ok
p1 += len; // !error, len is a long integer
}
访问LCD数据存储空间
如果用 @ 将变量定义在 LCD RAM 中,这样就能通过变量名访问 LCD RAM。
以下示例展示如何访问 LCD RAM。
• 示例
// LCD data memory is at RAM bank 14 (0x0e)
// lcd_day is at address 0x80 of RAM bank 14
/
/ lcd_mon is at address 0x82 of RAM bank 14
#include <HTG2190.H>
// delcared lcd_day、lcd_mon at LCD data area
unsigned char lcd_day @ 0xe80;
unsigned char lcd_mon @ 0xe82;
#pragma rambank0
unsigned int i, j;
unsigned char *lcd_ptr;
/*
Delcared non RAM bank 0 variables
A realistic scenario is that the variables are declared within the rambank0 block
if the memory is available.
*/
#pragma nonra
mbank0
unsigned int tmp;
void main(){
lcd_mon = 0x10; // put value 0x10 to LCD data memory 0xe82
lcd_ptr = &lcd_day; // lcd_ptr points to LCD data memory 0xe80
* lcd_ptr = 0xff; // put value 0xff to LCD data memory 0xe80
*(lcd_ptr+1) = 0xa0; // put value 0xa0 to LCD data memory 0xe81
}
单片机特殊功能寄存器
HT-MCU的特殊功能寄存器在RamBank 0 的前面部分。这些数据存储器不能作为通用变量使用。
访问特殊功能寄存器
要访问特殊功能寄存器,必须用一个变量来限定这个寄存器。Holtek C提供一个非常简便的方式来访问所有特殊功能寄存器的字节或者位。
• 字节变量
定义一个字节的特殊功能寄存器变量的语法与定义一个数据变量确定位置的方法一样。
data_type varaible_name @ memory_location
推荐宣告数据类型为 unsigned char。例如,
unsigned char _a @ 0x05;
unsigned char _pcl @ 0x06;
unsigned char _intc @ 0x0b;
unsigned char _tmr0h @ 0x0c;
unsigned char _pa @ 0x12;
unsigned char _pb @ 0x14;
特殊功能寄存器的使用方法与普通的数据变量的方法一样,例如:
_pa = 0xff; //set PA
if (_pb == (unsigned char)0x80){
...
}
• 位变量
Holtek的 C编译器为特殊功能寄存器提供内置的位变量。 这些位变量的命名规则如下:
_xx_n
例如:
_0a_0是地址0aH的第0位的位变量,状态寄存器的进位位
用户可用 #define 为位变量定义一个有意义的名字(通常mcu的头文件中已经这么做了)
例如:
// The HT48C50-1
#define _c _0a_0
#define _ac _0a_1
#define _emi _0b_0
#define _pa0 _12_0
#define _pa1 _12_1
位变量的用法与普通的位数据变量一样,例:
bit bflag;
...
_emi = 1; //enable interrupt
_c = 1; //set carry
if (_pa0){ //if port A bit 0 set
...
}
bflag = _eei ;
_pa0 = _pa2; //bit assignment
_pa1 = bflag;
访问输入输出端口
用户可以用访问特殊功能寄存器的方法来访问输入输出端口。它包含字节变量和位变量。
例:
unsigned char _pac @ 0x13;
unsigned char _pbc @ 0x15;
#define _pa0 _12_0
#define _pa5 _12_5
#define _pb5 _14_5
void main(){
_pac = 0xff; // set port A control register
_pbc = 0x40; // set port B control register
_pa0 = 1; // set port A bit 0
if (_pa5){ //if bit 5 of port A == 1
...
}
while(! _pb2){ //while bit 2 of port B == 0
...
}
}
内置函数
Holtek的 C编译器提供一些内置的函数,与直接用汇编指令编写的相似。
类似汇编语句的内置函数
C 子程序 汇编指令
void _clrwdt( ) CLR WDT
void _clrwdt1( ) CLR WDT1
void _clrwdt2( ) CLR WDT2
void _halt( ) HALT
void _nop( ) NOP
例如:
//assume the watchdog timer is enabled
//and use two clear WDT instructions
void dotest()
{
...
}
void main(){
unsigned int i;
for(i=0; i<100; i++){
_clrwdt1(); // CLR WDT1
_clrwdt2(); // CLR WDT2
dotest();
}
}
移位函数
C没有循环移位函数,CCompiler提供了
void _rr(int*); //rotate 8 bits data right
void _rrc(int*); //rotate 8 bits data right through carry
void _lrr(long*); //rotate 16 bits data right
void _lrrc(long*); //rotate 16 bits data right through carry
void _rl(int*); //rotate 8 bits data left
void _rlc(int*); //rotate 8 bits data left through carry
void _lrl(long*); //rotate 16 bits data left
void _lrlc(long*); //rotate 16 bits data left through carry
例如:
#include <HT48C50-1.h>
unsigned int ui;
unsigned long ul;
void error(){
while(1);
} c语言编译器ide代码编辑
void main(){
ui = 0x1;
_rr(&ui); //rotate right
if (ui != (unsigned int)0x80) error();
_c = 1; //set carry
_rrc(&ui); //rotate right through carry
if (ui != (unsigned int)0xc0) error();
ul = 0xc461;
_lrl(&ul); //long rotate left
if (ul != 0x88c3) error();
_c = 0; //clear carry
_lrlc(&ul); //long rotate left through carry
if (ul != 0x1186) error();
}
注意:要传递正确的参数类型!如果你给_rrl()传递一个1字节变量地址,IDE3k会让你郁闷死!
高低位交换函数
void _swap(int *); //swap nibbles of 8 bit data
例如:
unsigned int ui;
void error(){
while(1);
}
void main(){
ui = 0xab;
_swap(&ui);
if (ui != (unsigned int)0xba) error();
}
延迟周期函数
void _delay(unsigned long)
_delay函数会使单片机执行指定的指令周期(instruction cycle)。
值为0时相当于while(1);(infinite loop)
注意:_delay的参数只能为一个常数数值,它不接受一个变量。
通常不直接使用 _delay(),而将其封装如下:
#define delayMs(ms) _delay(...)
编程提示
定义变量为无符号数据类型
通常,无符号变量的运算要比有符号变量的运算简单。所以如果一个变量没有
负值时,建议将其定义为无符号数据类型。
例如:
int i,j;
unsigned int ui, uj;
void test(){
if (i >= j); // translate to 8 instructions
if (ui >= uj); // translate to 4 instructions
}
将变量定义在数据存储区块 0
位于数据存储区块 0以上区块的数据需要间接寻址访问,因此会产生一些低效率的代码。
所以对于有着多个数据存储区块的单片机,最好将使用频繁的变量定义在数据存储区块 0 里.
例如:
//file RAMBANK0.C
//assume the MCU has multiple RAM banks
#pragma rambank0
unsigned int ui0; // ui0 is in RAM bank 0
#pragma norambank
unsigned int ui; // ui is relocatable, may not in RAM
// bank 0
void test(){
ui0++; // translate to 1 instruction
ui++; // translate to 5 instructions
}
当在另一个源文
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论