2004年04月13日

angGoGo 是一句粤语的瞎话,可以写成“昂勾勾”。


我从来不在乎被别人看作是一个傻瓜,因为我根本就是一个傻瓜。


我做过不少的傻事,其中包括一些我十分后悔但是无法弥补的傻事;当然也有许多只影响自己的傻事。我觉得,对自己傻没什么,反正只是对自己;但是,不要对别人做傻事,影响了别人,那后果就会跟着你一辈子。那种心情你永远放不下,即使你后来后悔了,想对某个人说声对不起,你也未必能找得到那个人。因为事情已经发生了……


所以,千万别对别人做傻事。自己傻自己就算了

About

此Traffic System是利用一块HC11 Chip,需要实现一个十字路口的交通灯变化。Highway 和 Farmroad 相接,Highway 的绿灯至少亮10秒,如果
Farmroad 上有车等待,则Highway变黄灯3秒,再变红灯;而Farmraod就开始绿灯。3秒后如果Farmroad上还有车,则继续绿灯,最多不超过10秒,然后黄灯3秒,切换成Farmroad红灯,就是初始状态。

简单的用code表示如下:

_start:
Highway_green wait at least 10 sec
If car in Farmroad then
Highway_yellow 3 sec
Highway_red Farm_green at least 3 sec
While (car in Farmroad)
begin
total ++
If total > 10 sec break
end
Farmroad_yellow is on 3 sec
Farmroad_red is on
Highway_green is on
goto _start

Reward

完成这个系统可以了解HC11的Timer System已经I/O的运作。因为涉及了Timer,Input Capture和Output Compare等知识点。

Concept

以下会简单而通俗的介绍一些基础的概念。

HC11 board 通电后,里面的Timer System就开始运作,其实就是一些波在里面不停的跑,一边跑也一边影响一些地址(寄存器)的变化。其中,TCNT(main
timer)就是靠里面那个时钟来驱动的了。其它相关的地址(寄存器)如下所列:

TMASK2 (Timer interrupt mask register)

第7位是TOI (timer overflow interrupt enable bit),是用来设置允许中断的。第0位和第1位分别叫PR0 PR1 可以设置时钟的频率,
如下表

PR1 PR0 Prescale Factor 2MHz E clock 1MHz E clock
0 0 1 32.77ms 65.54ms
0 1 4 131.1ms 262.1ms
1 0 8 262.1ms 524.3ms
1 1 16 524.3ms 1.049ms

也许这里的翻译不太正确,但可以这样理解,如果使用的Chip是跑2MHz E clock,而且 TMASK2 的第0和第1位都设置为0,那么每跑一个全波,就是32.77ms。

TFLG2 (timer flag register 2)

第7位是 TOF (timer iverfkiw fkag)。每当tcnt从 $FFFF 变成 $0000 的时候,这一位就会被设。

TCTL2

这个寄存器只有6位用到,可以设置选择对哪一个input capture进行哪种的capture。具体可参照手册。

TFLG1

这个寄存器的分布状态如下:

位数
7 – OC1F
6 – OC2F
5 – OC3F
4 – OC4F
3 – OC5F
2 – IC1F
1 – IC2F
0 – IC3F

OC表示 output-compare,IC表示 input-capture。将相应的位设置成1,表示告诉HC11需要使用相应的捕捉

TMASK1

如果使用interrupt,需要对此寄存器进行设置。分布跟TFLG1的一样。

一个结合了运用 TMASK1, TFLG1, TCTL2 的简单例子就是:

TMASK1 = 0X01;
TFLG1 = 0X01;
TCTL2 = 0X01;
// 这个表示,设置了需要用 interrupt-driven 的方法来对 IC3 接口进行 rising edge 的 capture

TCTL1

这个是进行相应的 output compare 的设置。具体请参考手册。

Sample

以下有两段代码,不是用正规的HC11的汇编语言写,而是用C来书写。因为我觉得C更能清楚的表达整个系统的逻辑关系。两个范例使用了不同的实现方法,但结果都是正确的。

代码一

此范例是使用了 interrupt-driven 的方法来进行 input capture,而用了 polling method
进行 delay 操作

// 设置各寄存器的别名
#define porta *(volatile unsigned char *)(0x1000)
#define portd *(volatile unsigned char *)(0x1008)
#define ddrd *(volatile unsigned char *)(0x1009)
#define tflg1 *(volatile unsigned char *)(0x1023)
#define tflg2 *(volatile unsigned char *)(0x1025)
#define tctl1 *(volatile unsigned char *)(0x1020)
#define tctl2 *(volatile unsigned char *)(0x1021)
#define tic1 *(volatile unsigned char *)(0x1010)
#define tmask1 *(volatile unsigned char *)(0x1022) 

#define hw_red 0x40
#define hw_yellow 0x20
#define hw_green 0x10
#define fr_red 0x08
#define fr_yellow 0x10
#define fr_green 0x20 

// 因为需要设置 portD 为 output,则有必要设置 DDRD
#define outputD 0x3f    // set DDRD
#define cTOF 0x80   // clear TOF flag 

// 32.77 ms for 2MHz E clock
const short int tensec=305; // 10sec (10 divided by 32.77)
const short int threesec=91; // 3sec
const short int sevensec=213; // 7sec 

// ------------------------------------------------
// function prototype
// ------------------------------------------------
void delay(unsigned short int n);
void press_intr() __attribute__((trap)); 

void setHWGreen();
void setHWYellow();
void setFRYellow();
void setFRGreen(); 

// global variable, to capture user input
unsigned short int pressed; 

// ------------------------------------------------
// main function
//
// description:
//   OC2 -- highway red; set porta = hw_red
//   OC3 -- highway yellow; set porta = hw_yellow
//   OC4 -- highway green; set porta = hw_green
//
//   PD3 -- farm road red; set portd = fr_red
//   PD4 -- farm road yellow; set portd = fr_yellow
//   PD5 -- farm road green; set portd = fr_green
//
// flow:
//   Use interrupt the capture the user input.
//   Everytime the user presses the button, it will
//   set a flag to 1. The highway light will not
//   turn red until it finds the flag is 1.
//
//   After the farm road green light in on, if
//   no further press, then it turns to yellow light
//   after 3 seconds; otherwise it will keep on for
//   more 7 seconds.
// ------------------------------------------------
unsigned char main() { 

   // set d to be output
   ddrd = outputD; 

   //initialize all lights to turn off
   portd = 0;
   porta = 0; 

   // initialize the interrupt for input capture
   __asm__("cli"); 

   // initialize the interrupt jump table
   // 设置中断向量表, 7E是jmp
   *(unsigned char *)0xe2 = 0x7e;
   *(void (**)())0xe3 = press_intr; 

   // clear ic3f flag
   tflg1 = 0x01;
   tctl2 = 0x01; 

   // enable ic3 interrupt
   tmask1 = 0x01; 

   while (1)
   {
      // initialize the pressed flag is 0
      pressed = 0; 

      // turn highway green light on for 10 sec
      setHWGreen(); 

      // reset ic3 flag
      tflg1 = 0x01;
      tctl2 = 0x01; 

      // wait until the user press
      while (1)
      {
         if (pressed>0)
         {
           delay(1);
           break;
         }
      } 

      // turn highway yellow light on for 3 sec
      setHWYellow(); 

      // reset the pressed flag
      pressed = 0; 

      // turn farm road green light on for 3 sec
      setFRGreen(); 

      // reset ic3 flag
      tflg1 = 0x01;
      tctl2 = 0x01; 

      // if there are other cars on the farm road
      // delay more 7 second (totally 10 sec)
      if (pressed>0) {
        tflg1 = 0x01;
        delay(sevensec);
      } 

      // reset the pressed flag
      pressed = 0; 

      setFRYellow();
   } 

   __asm__("swi");
   return(0);
} 

// interrupt handle the IC3
// if capture the user input, set the flag to 1
void press_intr()
{
  //clear IC3F flag
  pressed = 1; 

  //clear IC3F flag
  tflg1 = 0x01;
  tctl2 = 0x01; 

  //wchar(pressed + 'Y');
  //wcrlf();
} 

// turn highway green light on
// turn farm road red light on
void setHWGreen()
{
   porta = hw_green;
   portd = fr_red; 

   // minimum highway green light is 10 sec
   delay(tensec);
} 

// turn highway yellow light on
// keep farm way red light on
void setHWYellow()
{
   porta = hw_yellow;
   portd = fr_red; 

   // highway yellow stays 3 sec
   delay(threesec);
} 

// turn farm road yellow light on
// keep highway red light on
void setFRYellow()
{
   porta = hw_red;
   portd = fr_yellow; 

   // farm road yellow stays 3 sec
   delay(threesec);
} 

// turn farm road green light on
// keep highway red light on
void setFRGreen()
{
   porta = hw_red;
   portd = fr_green; 

   // farm road green stays 3 sec
   delay(threesec);
} 

// measure the pulse width of an unknown signal.
// delay for a while.
// assume the prescale factor is 1 and the pulse width is shorter than 32 ms
void delay(unsigned short int n)
{
   unsigned short int i=0;
   while(i < n)
   {
     tflg2 = cTOF;
     while (!(tflg2 & cTOF));
     i++;
   }
} 

代码二

此范例是使用了 timeroverflow interrupt 同时进行 input capture 和 output
capture。 因篇幅问题,就不把完整的贴出,只贴关键部分。

void TCNT_INTERRUPT(void) __attribute__((trap)); 

// 全局变量
int level;
int isPressed;
int currTime;
int farmTime;    //farm light will stay green for farmTime seconds 

void _start(void){ 

  __asm__("lds #_stack");
  dToInput = 0x3F;     //sets port d to input
  TCTL2 = 0x04;         // sets IC2 for rising edge
  currTime = 0;
  farmTime = 0;
  isPressed = 0;
  PortA = 0;
  PortD = 0;
  *(unsigned char *) 0xD0 = 0x7E;
  *(void (**) ()) 0xD1 = TCNT_INTERRUPT;
  highwayToGreen();
  farmToRed();
  level = 1;           //in level one: highway is green, farm road is red
  TMSK2 |= 0x80;
  __asm__("cli");
  while(1);            //waiting for timer overflow interrupt
  return(0);
}

void TCNT_INTERRUPT(void)
{
  if(TFLG1 & 0x02){
    isPressed = 1;
  } 

  if(level == 1){           //highway turns red to yellow, farm road stays at red
    if(currTime < tenSec){
      currTime++;
    }else{
      if(isPressed){               //in case no cars on farm road
        highwayToYellow();
        level = 2;                 //in level two: highway yellow, farm red
        currTime = 0;
        isPressed = 0;
      }
    }
  }else if(level == 2){     //highway turns yellow to red, farm road turns red to green
    if(currTime < threeSec){
      currTime++;
    }else{
      highwayToRed();
      farmToGreen();
      level = 3;                   //in level three: highway red, farm green
      currTime = 0;
      farmTime = threeSec;        //farm light will stay green for farmTime seconds
      isPressed = 0;
    }
  }else if(level == 3){     //highway stays red, farm road turns green to yellow
    if(currTime < farmTime){
      currTime++;
      if(isPressed){
        if((currTime + threeSec) < tenSec){
          farmTime = (currTime + threeSec);
        }else{
          farmTime = tenSec;
        }
        isPressed = 0;
      }
    }else{
      farmToYellow();
      level = 4;                //in level four: highway red, farm yellow
      currTime = 0;
    }
  }else if(level == 4){     //highways turns red to green, farm road turns yellow to red
    if(currTime < threeSec){
      currTime++;
    }else{
      highwayToGreen();
      farmToRed();
      level = 1;               //in level one: back to original
      currTime = 0;
    }
  } 

  TFLG2 = 0x80;
  TFLG1 &= 0x02;
}

HC11 是什么?


HC11是摩托罗拉的一个非常著名的8位的microcontroller;学习它是学习所有其他的microcontroller的基础和精髓。国外始终都存在不少HC11的狂热分子,学院派的许多机器人和人工智能设计都是基于HC11的芯片开发的。事实上,如果你研究过8088的那些芯片,再来学习HC11,你会发现这类的chip真的非常好玩


HC11原理的chip是和 Intel 出产的 chip 完全不同,最大的区别在于IO。Intel Chip是属于 isolate I/O,他们的 microprocessors 有明确的指令控制 input & output。比如In 2表示从输入设备读入;Out 2表示从寄存器输出到输出设备。这种模式由于有独立的I/O instruction,所以非常不灵活(因为只有少数的指令);而且寻址模式也不灵活,因为I/O指令和其他指令是分离的。而HC11是一种 memory-mapped I/O,他们没有独立的 I/O 指令,所以指令都可以用作 I/O,I/O设备和主内存共用内存空间。假如 $1000 是一个 I/O address, LDAA $1000 则表示接受输入, STAA $1000 表示输出。这种模式使得 I/O 编程非常灵活;但它的不足也是明显的,会令到程序对软件错误非常敏感。比如你输入输出时写错了地址,就可能导致系统的崩溃。


但无论如何,HC11是一种非常有趣的microcontroller,否则gnu下也不会有专门为HC11的gcc compiler了。有兴趣的朋友可以通过这个网站 (HC11 Programming) 了解一下它的基本结构。


后话


我手头上刚好一本1993年版中国科技大学的8088/8086的汇编语言设计,是我很多年前在中国念大学时的教科书;我拿着它和国外用HC11做汇编语言教学的教科书对比一下就会发现国内的那本有相当大的不足。一个比较明显的问题就是中断的章节,中科大的那本书第七章是《输入/输出与中断》,仅仅用了50页不到的内容谈了谈8088芯片的中断方式以及各种设备的输入输出,还有串行通讯。接着便用了大量的篇幅去谈在汇编方式下的图形与音乐。整本书有许多的范例,我记得我和当年的同学门的确对那些范例很感兴趣,花许多时间在那些范例基础上创造新程序,比如用汇编来实现GUI的操作。但却真正忽视了对芯片的学习,结果我后来很长的一段时间都觉得汇编只是一种语言,一种最底层的语言,如果希望提高程序的性能不妨加入汇编语句;根本只是把汇编当成了一个单纯的工具。其实不然。因为汇编是最接近机器的语言,除了object code外。学习汇编是应该把语言和机器结合起来,用语言去理解chip的运作,而不是单纯的学习如何编写它。这是我当年在中国大学的汇编课上没有学到的。


对比一下,我手头上另外一本美版的《MC68HC11:Introduction Software and Hardware Interfacing》就和中国那本教科书的性质完全不同。说实在的,整本书砖头一样厚,范例却并不会太多,有大量的篇幅放在HC11运作的原理上,包括它的 Timer System,Parallel I/O,Serial Communication Interface等等。范例不多,但都非常精。而且也不是单纯的使用了汇编,还包含了C的代码。GNU的m6811-elf-as本来就可以直接把代码编译成汇编,所以用C倒直接来写也无所谓,因为原理都是一样的,反而可以更清晰。不像中国那本的范例,拜托,谁会用汇编代码来写一辆用字符串画出来的车在屏幕上走来走去。有那本书的朋友如果找来看看第八章,倒真的觉得有点可笑。


本来这两样东西没有什么可比性,只是纯粹有感而发,因为觉得当年在学校的时间都白花了。当然也许只是因为是我笨,以前也真的学得不好,也许,是以前国内大学白花时间在我身上了……