目录:
第三章 用C语言编程.......................... 39 开始一个C 程序.............................39 定义中断入口向量............................40 在 C源程序文件中宣告中断服务子程序的名称和地址......40 在 C源程序文件中定义 ISR.....................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 nonrambank0
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); } 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 }
当在另一个源文件的程序需要访问到定义在数据存储区块 0 的变量, 需要谨慎使用! 如果一个变量在文件 RAMBANK0.C 中被定义在RAM bank 0里,它可以在其他文件中的程序被访问,如 ACCESS0.C and ACCESS1.C。但是这个变量需要被声明为外部变量并且位于数据存储区块 0,否则会产生多余的和不正常的代码。而执行结果是不可预料的。
例如: // assume the ui0, ui are declared in the above example // file RAMBANK0.C // file ACCESS0.C // declare variables to be the same as RAMBANK0.C #pragma rambank0 extern unsigned int ui0; // declare ui0 in RAM bank 0 #pragma norambank extern unsigned int ui; void testB(){ ui0++; // translate to 1 instruction; correct ui++; // translate to 5 instructions; correct } // file ACCESS1.C // declare variables to be not the same as RAMBANK0.C #pragma rambank0 extern unsigned int ui; // declared ui in rambank0 #pragma norambank extern unsigned int ui0; void testC(){ ui0++; // 5 instructions; correct ui++; // 1 instruction; wrong } 在 ACCESS1.C文件中,语句 ui0++会被翻译成 5条指令,比ACCESS0.C文件中多出了 4条。不过这些语句执行的结果是正确的。 但是语句ui++被翻译成一条指令且执行结果是不可预料的。原因是 ui 在文件 RAMBANK0.C 中没有定义在RAM bank 0里,应该通过间接寻址来访问。
定义位变量 如果一个变量只有两个值,那么定义成位变量可节约内存,且生成代码较小。
例如 : //assume the MCU has single RAM bank bit bitflag; unsigned int intflag; void test(){ bitf = 1; // 1 instruction intflag = 1; // 2 instructions if (bitflag); // 2 instructions if (intflag); // 3 instructions }
访问常量地址
当要指向常量地址时,要使用cast。
例如: //assume the MCU has multiple RAM banks int *p1; unsigned char *p2; long *p3;
void main(){ //point to RAM bank 0, offset 0x50 p1 = (int*)0x50; p1 = 0x50; // error, no casting
//point to RAM bank 1, offset 0x60 p2 = (unsigned char *)0x160; //point to RAM bank 2, offset 0x30 p3 = (long *)0x230; } 使用更有效的方法获得余数 通常,使用如下语句获得商和余数: q = d1 / d2; r = d1 % d2; 这会调用2次除法子程序。 其他的获得商和余数的方法是使用内嵌的汇编程序。求商的语句相同,但是求余数语句换作内嵌的汇编程序。 对于 8 位有符号/无符号除法,余数会被存储在系统变量 T3 中; 对于 16 位有符号/无符号除法,余数会被存储在系统变量 T4和 T5。T4是高字节,T5是低字节。
// 注意:HT-MCU是Little-endian的 → 只有一个RamBank的MCU • 8 位除法 unsigned int d1, d2; unsigned int q, r; q = d1 / d2; // get quotient #asm MOV A, T3 ; get remainder MOV _r,A #endasm • 16 位除法 unsigned long d1, d2; unsigned long q, r; q = d1 / d2; #asm MOV A, T5 MOV _r,A ; get low byte remainder MOV A, T4 MOV _r[1], A ; get high byte remainder #endasm → 有多个RamBank的MCU • 8 位除法, r在数据存储区块 0 unsigned int d1, d2; unsigned int q; #pragma rambank 0 unsigned int r; #pragma norambank q = d1 / d2; #asm MOV A, T3 MOV _r,A #endasm
• 16 位除法, r在数据存储区块 0 unsigned long d1, d2; unsigned long q, r; q = d1 / d2; #asm MOV A, T5 MOV _r,A ;get low byte remainder MOV A, T4 MOV _r[1], A ;get high byte remainder #endasm
• 8 位除法, r不在数据存储区块 0 unsigned int d1, d2; unsigned int q; unsigned int r; q = d1 / d2; #asm MOV A, 0E0H AND [04H],A ;BP, clear RAM bank and preserve ROM bank MOV A, BANK _r OR [04H], A ; set bank pointer MOV A, OFFSET _r MOV [03H], A ; move offset to MP1 MOV A, T3 MOV [02H], A ; move T3 to R1 #endasm • 16 位除法, r不在数据存储区块 0 unsigned long d1, d2; unsigned long q, r; q = d1 / d2; #asm MOV A, 0E0H AND [04H],A ;BP, clear RAM bank and preserve ROM bank MOV A, BANK _r OR [04H], A ; set bank pointer MOV A, OFFSET _r MOV [03H], A ; move offset to MP1 MOV A, T5 MOV [02H], A ; store to low byte of remainder INC [03H] ; point to high byte of remainder MOV A, T4 MOV [02H], A ; store to high byte of remainder #endasm
常量变换 / 强制转换 Holtek C 编译器是 8 位编译器。 注意:int=char=1Byte, long=2Byte ! 如果应用要处理 8位常量整型数据, 则需要要将它强制转换成 int/char(或者unsigned int / unsigned char),否则一个8 位的十六进制的整型数据可能会被错误的转换成 16 位的整型数据。 如果没有强制转换,介于 0x80 和0xff之间的常量会被转换成相应的 16 位整型数据而不会符号扩展。
示例: • (unsigned int)0xff相当于一个无符号 8 位整型数据,数值是 255 • (int)0xff 相当于一个有符号 8位整型数据,数值是 -1 • 没有强制转换, 则 0xff 会被暗中地转化成一个 16位长整型数据,数值是255 !
示例: //assume the MCU has a single RAM/ROM bank unsigned int ui; int i; void main(){ //8 bit signed comparison //5 instructions if (i >= 0x7f){ //equals to if (i >= 127) } //0x80 implicitly converted to (long)128 //16 bit signed comparison //16 instructions if (i >= 0x80){ // equals to if (i >= 128) //always false } //explicitly casting 0x80 to (int)-128 //8 bit signed comparison //5 instructions if (i >= (int)0x80){ // equals to if (i >= -128) //always true } //8 bit unsigned comparison //4 instructions if (ui >= 0x7f){ // equals to if (ui >= 127); } //0x80 implicitly converted to (long)128 //16 bit signed comparison //14 instructions if (ui >= 0x80){ //equals to if (ui >= 128L) } //explicitly casting 0x80 to (unsigned int)128 //8 bit unsigned comparison //4 instructions if (ui >= (unsigned int)0x80){ } }
串行端口传输范例 此范例教你如何编写时序严格的程序。
注意:因为翻译 C语言生成怎样的指令代码取决于编译器,下面范例中的延时常数可能会因为编译器版本而不同。所以在第一次使用之前必须检查延时常数,相应的更新 C 编译器和单片机型号。由 C 编译器产生的指令取决于程序存储器/数据存储器是单区块的还是多区块的。
初始程序 串行端口传输协议规定一个启始位 0、8 位数据位、一位结束位 1。
// set address 0x12 bit 1 to be output pin (PA1) #define tx _12_1 unsigned char sent_val; void main(){ _13_1 = 0; //set PA1 as output pin sent_val = ‘a’; transmit(); } void transmit(){ unsigned char sent_bit; unsigned char i; tx = 0; // L1 start bit for(i=0; i<8; i++){ sent_bit = sent_val & 0x1; sent_val >>= 1; if (sent_bit){ tx = 1; // L2 } else { tx = 0; // L3 } } tx = 1; // L4 stop bit } 为了匹配传输波特率,需要调整transmit():计算合适的延迟时间插入传送每一位之前或之后。
因为汇编语言的输出0和 1 的语句是不同的,所以最好使用不同的C 语句分别输出 0和 1。所以将语句 tx = sent_bit; 替换成 if (sent_bit){ tx = 1; // L2 } else { tx = 0; // L3 } 语句 tx = sent_bit不能决定是传送 0 还是传送 1。 //? 调节传输时序 现在我们需要调节时序以使得所有传输过程(L1 到 L2、 L1 到 L3,L2 到 L3、 L3 到 L2,L2 到 L4 或 L3 到 L4)的指令周期数都相同。在 HT-IDE3000编译程序后,进入调试
→ 调节 L1到L2和L1到L3 • 在 L1, L2, L3处设置断点,打开View菜单下的Cycle Count窗口。 • 运行 ICE后会停止在 L1 处。 • 在 Windows菜单下的 Watch窗口1中,更改 sent_val数值为 1。 • 复位Cycle Count的值。 • 运行 ICE后会停在 L2 处, cycle count = 0x11。 • 复位 ICE. • 运行 ICE后会停止在 L1 处。 • 在 Watch窗口中,更改 sent_valís数值为 0。 • 复位Cycle Count的值。 • 运行 ICE后会停在 L3 处,cycle count = 0x12。 现在,我们知道 L1 和 L2 间的指令周期比 L1 和 L3 间的指令周期少 1 个。因此应该在 L2 之前增加一个指令周期的延迟,这样从 L1 到 L2 和从 L1 到 L3的指令周期都等于 0x12。
if (sent_bit){ _delay(1); // add this statement tx = 1; // L2 }
→ 调节L2到L3和L3到L2 使用已修改的代码来做下面的测试 • 在 L1、L2、L3 处设置断点。 • 运行 ICE后会停止在 L1 处。 • 在 Watch窗口中,更改 sent_val数值为 5(00000101b)。 • 运行 ICE后会停在 L2 处。 • 复位Cycle Count的值。 • 运行 ICE后会停止在 L3 处,cycle count = 0x12。 • 复位Cycle Count的值。 • 运行 ICE后会停在 L2 处,cycle count = 0x10。 现在,L2 和 L3 间的指令周期是 0x12, L3 和 L2 间的指令周期是0x 10。因此需要在 L3 之后增加两个指令周期的延迟。
else { tx = 0; // L3 _delay(2); // add this statement }
注意: 在HT-IDE3000 Watch窗口中, 输入点sent_val (.sent_val) 再按回车键。可以看到 如 .sent_val :[xxH] = nn 这样的显示内容. 这时就可以将nn修改成01了。 注意不要 忘记在完成修改后按回车键否则数值不会被改变。
→ 调节L2到L4和L3到L4 • 在 L1、L2、L4 处设置断点。 • 运行 ICE使程序停至 L1。 • 在 Watch窗口中,更改 sent_val数值为 0x80。 • 运行 ICE使程序停至 L2。 (最后的循环) • 复位Cycle Count的值。 • 运行 ICE使程序停至 L4。cycle count = 0x8。
L4 之前的延迟指令周期应该是 10 (0x12-0x8)。
_delay(10); // add this statement tx = 1; // L4 stop bit
至此所有的传输时间都是相同的 18(0x12)个指令周期了。
波特率匹配调节 波特率 =系统时钟频率 / 4 / (传输一位所需指令周期数) 传输一位所需指令周期数= X+18, X 是额外的延迟指令周期。 所以 X 的求取公式为 X = (系统时钟频率/ 波特率 / 4) - 18 例如, 系统时钟频率= 4MHz 且波特率= 9600则X 等于 86. 下面是最终得到的程序。 // This function depends on compiler and MCU. // You MUST adjust the delay constants when different compiler or MCU are used // suppose address 0x12 bit 1 is the output pin (PA1) #define tx _12_1 unsigned char sent_val; void transmit(){ unsigned char sent_bit; unsigned char i; tx = 0; // L1 start bit for(i=0; i<8; i++){ sent_bit = sent_val & 0x1; sent_val >>= 1; _delay(86); // add this statement if (sent_bit){ _delay(1); // add this statement tx = 1; // L2 } else { tx = 0; // L3 _delay(2); // add this statement } } _delay(86+10); // add this statement tx = 1; // L4 stop bit _delay(86); // add this statement } 接收部分和上面类似。
HT-C程序结构示例: //include files #include <ht49C50-1.h> //Interrupt service routines declaration #pragma vector external_isr @ 0x4 #pragma vector timer0_isr @ 0x8 #pragma vector timer1_isr @ 0xc //RAM bank undefined variables unsigned int uia, uib; unsigned long ula, ulb; //RAM bank 0 variables #pragma rambank0 unsigned int uia0, uib0; unsigned long ula0, ulb0; bit flag; #pragma norambank
//RAM bank undefined variables // ... //ISR void external_isr(){ } void timer0_isr(){ } void timer1_isr(){ } //main function void main(){ }
|