为了祢补单片机软件设计教材资源的缺乏,本资料着重从软件设计的角度介绍MC68HC908GP32单片机的软件编写方法和应用示例。利用独立的文章的形式介绍了此单片机的端口、琐相环、AD转换、串行通信、键盘中断、定时器以及外连芯片MC11489等应用。 本着用多少学多少的原则,删除了许多不常用的寄存器值得含义,对重要的知识点均以加粗方式标出。达到速成的目的。 此资料所有代码经作者精心组织编写并在北京理工大学卡尔飞思实验室BMTC——MC68HC08单片机系统V3.0实验板调试通过。 MC68HC08端口简介和程序的一般框架 作为第一篇从软件介绍MC68HC08的文章,我们先从软件的角度认识认识这个四十多只脚的东东吧。 虽然它只有四十多只管脚,但是,几乎所有的管脚都是复用的,即身兼两职。主要可分为电源,晶振,ABCDE五组端口,中断,复位等等。 趁着大家精力还算旺盛,先说最有用的,最容易的。ABCDE五组端口 每组端口都有两个寄存器,一个叫方向寄存器,用来说明这个端口用作输入还是输出。五个端口的方向寄存器分别叫做:DDRA,DDRB,DDRC,DDRD,DDRE。每个寄存器都有八位,分别为对应管脚的输入输出方向。如DDRB=0b01100000,(b指二进制数。此数亦可以写成十六进制数0x60)规定B端口的第五第六两管脚作为输出,其它作为输入。 另一个叫数据寄存器,也是八位。分别是PTA到PTE。端口当作输出的时候,可以写寄存器。端口当作输入的时候可以读寄存器。当某位为1的时候,端口为管脚高电平,当为、0的时候,端口管脚为低电平。比如,我想让B端口的第五位(作输出)为低电平,则写作PTB_PTB5=0; 每个端口都能作为平常的输入输出端口。另外,每个端口都有第二功能。 A端口:八个管脚 常用作键盘输入。此时,端口作为输入。硬件自动置A口所有位为高电平。某键被按下时,产生低电平,从而达到检测按键的目的。键盘操作见《外部中断和键盘中断》一文。 B端口:八个管脚 常用作A/D转换器输入端口。对应了八个输入通道。 C端口:六个管脚 可用作LED指示灯的输出接口。如本实验室的开发板将PB0-PB4与五个指示灯相连。低电平点亮。 D端口:六个管脚 功能比较多,有计数器输入管脚2个,SPI通信管脚2个等 E端口:只有两个管脚 经常作为串口收发端,PTE0为TX,PTE1为RX 中断信号: 管脚,平常为高电平,当收到低电平信号时产生一个外部中断。 晶振:OSC2 OSC1 和COMXFC 复位信号管脚 。 电源管脚:略 第二个主题:程序的基本框架 MC68HC08程序的框架比较统一,下面介绍两种最常用的。 查询式程序框架: //step0 变量定义 #include <hidef.h> #include <MC68HC908GP32.h> void main(void){ //step1 初始化CONFIG控制字 CONFIG1=0x01; CONFIG2=0x01; //step3 初始化锁相环 PCTL=0X00; PBWC=0X80; PMSH=0X03; PMSL=0XD1; PMRS=0XD0; PMDS=0X01; PCTL=0X32; DDRC=0XFF; PTC=0XFF; //step4设置其它初始化控制字 //step4 进入循环 for(;;){ //step5 在循环中作端口输入输出的工作 if (寄存器可以读) 读寄存器; if(寄存器可以写)写寄存器; … } 中断式程序框架: Step1-3 与查询式一样 //Step4 设置其它初始化控制字,并开启有关中断。 //step5开中断 EnableInterrupts; //step6 进入死循环 For(;;); } //step7 设计中断服务程序,就是中断响应函数,有几个中断向量就写几个 Interrupt 中断号 intResponce(void){ 禁止有关中断; 进行寄存器读写操作等; 允许有关中断; } //其中intResponce是函数的名称,可以自行订制。 熟悉查询与中断的基本工作方式是了解单片机程序的前提。下面就要介绍各种很有用的功能啦。不过为了先拣好玩的说,另外为了给以后的文章做基础。先说一说它最友好的输出方式——五位七段数码的显示方法 五位七段数码管驱动芯片MC14489编程说明
MC14489是一个将五个七段数码管和MPU相连的驱动器,它与MPU通过SPI串行外设通讯协议进行通讯。 它有两个状态:显示状态和设置数值状态,当PTE_PTE1==0时,切换到设置状态,当PTE_PTE==1 时,切换到显示状态。 显示字符 数码管显示的值与输入到底是什么关系呢?首先,看它的显示字符表 可见,它有两种显示模式,当我们输入一个十六进制的数字时,若为第一种模式,就直接显示出它,若为第二种模式,则显示对应的字符。 那么怎么切换这两种模式呢?在传递完五个十六进制数字之后,还要传递一个模式选择的8位二进制数,这个数字规定了每一个七段显示器到底用第一个模式还是第二个模式。
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | | | 第五管模式 | 第四管模式 | 第三管模式 | 第二管模式 | 第一管模式 | 1 | 1到5位中,置0代表十六进制模式,置1代表扩展模式。 好了,显示什么字符已经很清楚了,那么小数点怎么操纵呢? 操纵小数点 在传递五个十六进制数字之前,要先传递一个十六进制数。其对应模式如下 0代表全都不亮 1 2 3 4 5 分别代表从右向左第几个小数点灯亮; 6代表最后两个小数点灯亮 7代表全都亮 8代表全都不亮 9 A B C D分别代表从右向左第几个小数点灯亮; E代表最后两个小数点灯亮 好的,小数点的问题解决了,下面我们来熟悉一下它的工作流程。 点亮数码管8步走 (1) 设置初始化工作 (2) 禁止显示器工作 (3) 将小数点控制数作高位,第一个数码管的值做低位,送至SPDR,并延时 (4) 第2个数码管的值做高位,第3个数码管的值做低位,送至SPDR并延时 (5) 第4个数码管的值做高位,第5个数码管的值做低位,送至SPDR并延时 (6) 令PTE_PTE1=1,切换到显示状态,至此五个管子的数值和小数点输出完毕 (7) 令PTE_PTE1=0,切换到设置状态 (8) 将模式控制数送至SPDR并延时 (9) 令PTE_PTE1=1,切换到显示状态 下面分析一个实例 假程序中有一int型变量ans=0x9b; SPCR=0x3a; //STEP1初始化设置,见串行接口相关文章,这两句话是死的,照写就行了 SPSCR=0x01; DDRD=0xff; DDRE=0xff; PTE_PTE1=0; //STEP2,从显示工作状态切换到设置状态。 SPDR=0x80; //STEP3,8是小数点控制数,表示全不亮。0是第一个数码管的数值 delay(); SPDR=0xaf; //STEP4 ,a是第二个数码管的数值,由于选择了扩展模式,所以对照本文开头//表格,显示为U;f为第三个数码管的数值。 delay(); SPDR=ans; //STEP5,ans的值为9b,所以分别为第四个和第五个数码管的值 delay(); PTE_PTE1=1; //STEP6,五个数码管的值都设完了,切换工作状态表明传输完毕,这步必须有 PTE_PTE1=0; //STEP7,再次切换到设置状态 SPDR=0b000010001; //STEP8,设置每一数码管工作在十六进制模式还是扩展模式,见第二个表格 delay(); PTE_PTE1=1; //STEP9,切换到显示状态 显示为 。 显示十进制数 如果我想显示的是十进制数,怎么办呢?做一个转换函数即可,假设要显示的为b变量 b=23412; b1=(int)b/10000;b1r=(int)b%10000; b2=(int)b1r/1000;b2r=(int)b%1000; b3=(int)b2r/100;b3r=(int)b%100; b4=(int)b3r/10;b4r=(int)b%10; b5=(int)b4r; PTE_PTE1=0; SPDR=b1; delay(5); SPDR=b2*16+b3; delay(5); SPDR=b4*16+b5; delay(5); PTE_PTE1=1; PTE_PTE1=0; SPDR=0x01; delay(5); PTE_PTE1=1; 这个例子要注意,由于八位机的处理能力有限,五位数不能大于32767。否则会发生回滚错误。 相信您读完这篇文章,再也不用去翻它的说明书了,一本看半天也不知道咋回事的书。 MC68HC08的外部中断和键盘中断 这次给大家带来中断的话题。中断可以说是计算机的法宝。如果想让计算机听从你的实时命令,又不耽误它自己的工作的话,就用中断吧。 中断的定义: 当某种条件满足时,中断源产生中断申请,cpu在当前指令执行完毕后会检测是否有中断申请,如果有的话,立即将有关寄存器值存入堆栈。转去执行中断源所对应的中断处理程序。中断处理执行完毕后,回到原来的程序中继续执行。 中断向量:每一个中断源都有一个标识号,叫中断向量,当cpu检测到中断申请时,就根据中断向量号寻找对应的中断处理程序(函数),一个向量号对应一个处理程序(函数)。 干说没有用,下面我们来看看MC68HC08是怎么处理外部中断的. 外部中断处理: 有个寄存器需要说明: //头文件 #include <hidef.h> #include <MC68HC908GP32.h> void main(void){ //初始化 CONFIG1=0x39; DDRC=0xff; //c口用于led显示,所以作为输出口 PTC=0b011100000; //c口低五位作为输出 INTSCR_IMASK=0; //开外部中断 INTSCR_MODE=1; //下降沿出发 EnableInterrupts; //开中断cli for(;;); //进入死循环 } interrupt 2 void IRQ_ISR(void){ //中断处理程序 INTSCR_IMASK=1; //禁止外部中断 PTC=0b011111111; //将所有的灯都点亮,作为中断程序的可视化输出 INTSCR_ACK=1; //表明中断已响应 INTSCR_IMASK=0; //允许外部中断 } 如果你看懂了外部触发,那么键盘触发就易如反掌了 键盘中断处理: 有两个寄存器需要说明 首先INTKBSCR寄存器,实际上用法跟外部中断所用的INTSCR非常相像。参照上例即可。 其次是INTKBIER,由于MC68HC08中默认用A口作为键盘输入端口的。究竟A口作为普通口,还是键盘输入端口,通过INTKBIER控制,A口八个管脚对应它的八个位。为0时作普通端口,为1时作键盘输入,此时硬件置该管脚为高电平,当键被按下时,产生低电平,引发键盘中断。 本例是利用中断方式响应按键,按下哪个键就点亮哪个led指示灯,并把按键的值显示在七段数码管上。 #include <hidef.h> #include <MC68HC908GP32.h> int i,ans; void main(void) { CONFIG2=0x01; CONFIG1=0x01; INTKBSCR_MODEK = 1; //选择触发方式为下降沿和低电平 INTKBSCR_IMASKK = 1; //禁止键盘中断 INTKBIER = 0xff; //A口都作为键盘输入 INTKBSCR_ACKK = 1; //键盘响应置1,用于清除IREQ INTKBSCR_IMASKK = 0; //允许键盘中断 DDRA=0x00; //A口都为输入 DDRC=0xff; //c口连指示灯,作为输出 PTC=0b11100000; //c口灯全部点亮 EnableInterrupts; //开中断对应汇编的cli for(;;){}; //死循环,等待中断 } void print(void){ //显示中断 SPCR=0x3a; //显示管驱动芯片通信初始化 SPSCR=0x01; DDRB=0x7f; DDRD=0xff; DDRE=0xff; ans=0; //将键值转换为数字 switch (PTA){ case 0b011111110:ans=1;break; case 0b011111101:ans=2;break; case 0b011111011:ans=3;break; case 0b011110111:ans=4;break; case 0b011101111:ans=5;break; case 0b011011111:ans=6;break; case 0b010111111:ans=7;break; case 0b001111111:ans=8;break; } PTE_PTE1=0; //送交显示 SPDR=0x80; for (i=1;i<=5;i++){;} SPDR=0x00; for (i=1;i<=5;i++){;} SPDR=ans; for (i=1;i<=5;i++){;} PTE_PTE1=1; PTE_PTE1=0; SPDR=0x01; for (i=1;i<=5;i++){;} PTE_PTE1=1; } interrupt void KBI_ISR(void) { INTKBSCR_IMASKK = 1; //屏蔽键盘中断 PTC=PTA; //将键盘的值用c口led灯显示 print(); //同时将键盘值用数字的形式在数码管显示 INTKBSCR_ACKK = 1; //清除中断确认 INTKBSCR_IMASKK = 0; //开启键盘中断 } 细心的朋友可能能看出来,其实外部中断和键盘中断都是一回事,能不能把键盘作为外部中断用呢,没问题。这就能大大扩展外部中断数量了。 MC68HC08的锁相环编程指导 锁相环是什么?是一种倍频器,它将时钟基频(CGMXCLK)进行分频,产生PLL倍频信号(CGMVCLK),然而,由于它基础理论抽象,很难懂。本文以尽量简明的原则,帮大家学好锁相环初始值的设定 寄存器: PCTL:锁相环控制寄存器 所在地址 $036 通过PRE预分频值查此表可计算出第三位,第二位值以及PRE值
PRE1 | PRE2 | PRE值 | 预分频系数 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 2 | 1 | 0 | 2 | 4 | 1 | 1 | 3 | 8 | 通过VCD电源系数查此表可计算出第一位,第零位以及VPR系数 VPR1 | VPR0 | VPR值 | VCD电源系数 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 2 | 1 | 0 | 2 | 4 | 1 | 1 | 3 | 8 | 其中PRE和VCD由fbus(内部时钟速率)确定,输出时钟为4*fbus,确定方式见下表
Fbus | PMSH值 | PMSL值 | PMRS值 | PRE值 | VPR值 | 2 | 0 | F5 | D1 | 0 | 0 | 2.4576 | 1 | 2c | 80 | 0 | 1 | 2.5 | 1 | 32 | 83 | 0 | 1 | 4.0 | 1 | E9 | D1 | 0 | 1 | 4.9152 | 2 | 58 | 80 | 0 | 2 | 5.0 | 2 | 63 | 82 | 0 | 2 | 7.3728 | 3 | 84 | C0 | 0 | 2 | 8.0 | 3 | D1 | D0 | 0 | 2 | 典型应用: PCTL=0x00;//禁止PLL PCTL=0x22;//00100010,开启PLL,选择PRE=0,VPR=1 PCTL_PLLON=1;//使能PLL PCTL_BCS=1;//输出时钟分频选择 PBWC:PLL控制寄存器 所在地址 $037
地址 | 7 | 6 | 5 | | | AUTO 带宽控制方式 | LOCK 带宽是否已稳定(只读) | | | 置1 | 自动 | 锁定 | | | 置0 | 手动 | 不锁定 | | | 默认 | 0 | 0 | | | 还有几个寄存器,一般保持复位的值不变,知道即可 PMSH,PMSL:PLL倍率选择器 所在地址$038,$039 PMSH低四位有效,PMSL全部有效。复位后都为0 PMRS:电源范围系数 复位后为0 PMDS:PLL基时钟分频选择器 所在地址$3B 锁相环设置七步走: (1) 禁止PLL:PCTL=0; (2) 选择自动控制方式 PBWC=0x80; (3) 设置分频系至PMSH和PMSL (4) 设置输出范围系数至PMRS (5) 根据典型参数表设置PCTL,其中包括PRE值(由Vpr1,Vpr0联合确定),VPR值(由PRE1,PRE0联合确定)和 锁相环使能PCTL_PLLON=1,等待稳定,稳定后,设PBWC_LOCK=0; (6) 设置输出时钟为CGMVCLK的二分频PCTL_BCS=1 实例: PCTL=0x00;//step1 PBWC=0x80;//step2 PMSH=0x02;//step3 PMSL=0x58; PMRS=0x80; //step4 PCTL=0x22; //step5 PCTL_PLLON=1; //step6 PBWC_LOCK=0; PCTL_BCS=1; //step7 注意锁相环要经过两次使能,如上所示PCTL=0x22; (第一次) PCTL_PLLON=1;(第二次) 其实作为初学,可以直接把典型的代码复制过来,如果想DIY的话,就通过“常用PLL系数表”进行设置即可。 MC68HC08定时器编程简述 计时器的寄存器组 T1SC,T2SC:TIM状态寄存器
PS2 | PS1 | PS0 | 计时器时钟 | PS2 | PS1 | PS0 | 计时器时钟 | 0 | 0 | 0 | 内部总线时钟/1 | 1 | 0 | 0 | 内部总线时钟/16 | 0 | 0 | 1 | 内部总线时钟/2 | 1 | 0 | 1 | 内部总线时钟/32 | 0 | 1 | 0 | 内部总线时钟/4 | 1 | 1 | 0 | 内部总线时钟/64 | 0 | 1 | 1 | 内部总线时钟/8 | 1 | 1 | 1 | 无效 | TMODH,TMODL:终止值预置寄存器 TMODH和TMODL和在一起组成一个四位十六进制数,如$9600,计数器复位后从$0000开始工作,当工作到这个数值时产生中断,如果没有设置这个数,则按默认$ffff处理。 TSC0,TSC1 :定时器通道状态和控制寄存器 有一些输入捕捉和输出比较等用的设置。暂时不提。 典型例子: 这个例子实现了一个以秒计时的秒表,每隔一秒钟产生一个定时器中断,在七段显示屏上显示出当前的秒数。并有LED灯相映闪烁,非常好看。 #include <hidef.h> #include <MC68HC908GP32.h> int counter=0; int b1,b2,b3,b4,b5,b1r,b2r,b3r,b4r,b; void main(void) { CONFIG2=0x01; CONFIG1=0x09; T1SC=0b01000110; //溢出中断使能,总线分频/64 T1MODH=0x96; //9600是一秒钟的对应值。比如除以2可以得到0.5秒,以此类推 T1MODL=0x00; b=0; SPCR=0x3a; //七段数码管芯片通信初始化 SPSCR=0x01; DDRD=0xff; //端口方向 DDRE=0xff; EnableInterrupts; //开中断 for(;;){} //死循环 } void delay(int k){ //延时子程序 int i; for (i=0;i<=k;i++); } void printLED(void){ //显示到C口的LED上 counter++; switch (counter) { case 0:PTC=0b11111111;break; case 1:PTC=0b11111110;break; case 2:PTC=0b11111100;break; case 3:PTC=0b11111000;break; case 4:PTC=0b11110000;break; case 5:PTC=0b11100000; } if (counter==5) counter=-1; } void printNum(void){ //显示到数码管上 b++; b1=(int)b/10000;b1r=(int)b%10000; b2=(int)b1r/1000;b2r=(int)b%1000; b3=(int)b2r/100;b3r=(int)b%100; b4=(int)b3r/10;b4r=(int)b%10; b5=(int)b4r; PTE_PTE1=0; SPDR=b1; delay(5); SPDR=b2*16+b3; delay(5); SPDR=b4*16+b5; delay(5); PTE_PTE1=1; PTE_PTE1=0; SPDR=0x01; delay(5); PTE_PTE1=1; } interrupt 6 void OverFlow(){ //计时器中断,中断向量6 T1SC_TOIE=0; //计时器中断禁止 T1SC_TOF=0; //复位溢出标志 printLED(); //显示到LED printNum(); //显示到数码管 T1SC_TOIE=1; //允许计时中断 } //下载完毕请将PC0,PC1,PC3三个开关置于左侧 MC68HC08 A/D数据采集编程指导 ADSCR:ADC状态控制寄存器 ADICLK:ADC时钟控制寄存器 ADC时钟分频
ADIV2 | ADIV1 | ADIV0 | ADC时钟分频 | 0 | 0 | 0 | 输入时钟/1 | 0 | 0 | 1 | 输入时钟/2 | 0 | 1 | 0 | 输入时钟/4 | 1 | x | x | 输入时钟/8 | ADR:ADC 数据寄存器 所在地址$03D 八位数据存储。 典型例子: 许多时候,我们总是去网上和书上找例子,然而多半与其他的内容掺合在一起,本文就举一例,引导大家提炼知识点,之所以要给出一个综合性的例子,是因为采集出来的值如果不显示的话看不到实验效果。实在没有办法,只能把它和数码管联合起来才能观看到结果。
查询式数据采集: 任务:采集变化的电压与VCC的比值(百分数),并通过五位七段数码管显示。 #include <hidef.h> #include <MC68HC908GP32.h> int i,b; void delay(int n){ for (i=0;i<=n;i++){;} } void main(void) { CONFIG2=0x01; //初始化设置 CONFIG1=0x01; PCTL=0x00; //锁相环设置 PBWC=0x80; PMSH=0x02; PMSL=0x58; PMRS=0x80; PCTL=0x22; PCTL_PLLON=1; PBWC_LOCK=0; PCTL_BCS=1; SPCR=0x3a; //与MC14489数码管驱动器的串行通信初始化 SPSCR=0x01; DDRB=0x7f; //端口方向 DDRD=0xff; DDRE=0xff; ADCLK=0x40; //A/D数据采集 ADSCR=0x27; while(1) { while(ADSCR_COCO==0){;} //当A/D转换器未完成转换时,做死循环。 b=ADR; //将A/D转换后的数字值取出 PTE_PTE1=0; //送至七段数码管显示 SPDR=0; delay(5); SPDR=0; delay(5); SPDR=0; delay(5); PTE_PTE1=1; PTE_PTE1=0; SPDR=b; delay(5); PTE_PTE1=1; } } 试验结果:当转动可变电阻时,数码管的值随之变化。 这么长的例子(作为一个能运行的程序,实在不好简化了),怎么分析呢? 不管你是不是内行,蒙也能蒙出来,这种单片机工作之初总是要初始化的,初始化的特点就是一连串的寄存器等于某些值。如本例,从main函数一开始,就进入了初始化阶段。很典型的做了CONFIG初始化和PLL锁相环的初始化。随后是七段数码管的驱动芯片的通讯初始化。如果知道端口寄存器的朋友应该知道随后的是端口方向初始化。我们让PTB7作A/D信号的输入端。随后进入了我们今天的主题,A/D数据采集4步走 (1) 设置ADCLK (2) 设置ADSCR (3) 进入一个死循环。 (4) 检测如果A/D转换器转换成功一个值,则取出该值,并送交显示。如果没有,则等待。 如果你能看懂上面的代码结构的话,请分析下面一段非常类似的代码: 这段代码的作用是将采集上来的值通过串口传递出去。 #include <hidef.h> #include <MC68HC908GP32.h> long a; void main(void) { CONFIG2=0x39; CONFIG1=0x01; PCTL=0x00; PBWC=0x80; PMSH=0x02; PMSL=0x58; PMRS=0x80; PCTL=0x22; PCTL_PLLON=1; PBWC_LOCK=0; PCTL_BCS=1; SCBR_SCR1=1; //串口初始化 SCBR_SCR0=1; SCC1_ENSCI=1; SCC2_TE=1; SCC2_RE=1; DDRB=0x7f; DDRC=0xff; ADCLK=0x40; ADSCR=0x27; while(1) { while(ADSCR_COCO==0){;} a=ADR; //将采集来的值放入a变量 if(SCS1_SCTE==1) //如果串行接口发送器空 SCDR=a; //将此值发送 } } 相信你读完这篇文章后,对简单的A/D采集编程方法已经很熟悉了,以后的工作无非就是数据处理了。当然,如果你有兴趣的话,建议学习中断方式的AD采集,毕竟,查询和中断总是做事情的两个大套路 MC68HC08串行通信接口概述 设计串口通信程序,主要是掌握四个寄存器,设置好初始化。本着有用的原则,我们不对所有的寄存器含义进行说明,只介绍用得着的。 硬件电路: 将MPU与RS232相连 寄存器: SCDR:SCI数据寄存器 所在地址: @0x00000018 功能: 一个八位的寄存器,当微控制器向外发送时,将要发送的数据放入SCDR,当要接收外来数据时,外来数据会被暂存入SCDR,微控制器从SCDR中取出此数据,可以把SCDR看成一个交换数据的缓冲区。 主要用法: byte data; data=SCDR; //将值取出 SCDR=data; //将值放入 SCC1:SCI控制寄存器1 所在地址: @0x00000013 功能速查表:(红色代表比较重要的)
地址 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | | ENSCI 串口使能 | | M 字符长度 | | | PEN 奇偶校验使能 | PTY 奇偶校验方式 | 置1 | | SCI允许 | | 9位 | | | 允许 | 寄校验 | 置0 | | SCI禁止 | | 8位 | | | 禁止 | 偶校验 | 默认 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 虽然表中有的放矢的删去了很多不常用的位功能,但是还是很难记,怎么办呢?其实,每一位的含义在头文件中都有详细地说明,学会阅读头文件,必将达到好学好记,事半功倍的效果。因此,作为本资料的最后一篇文章,此文带领你阅读头文件。以便对以后的工作有一个交待,实现可持续发展。 头文件定义: /*** SCC1 - SCI 寄存器1 ***/ typedef union { byte Byte; struct { byte PTY :1; /* Parity Bit */ byte PEN :1; /* Parity Enable Bit */ byte ILTY :1; /* Idle Line Type Bit */ byte WAKE :1; /* Wakeup Condition Bit */ byte M :1; /* Mode (Character Length) Bit */ byte TXINV :1; /* Transmit Inversion Bit */ byte ENSCI :1; /* Enable SCI Bit */ byte LOOPS :1; /* Loop Mode Select Bit */ } Bits; } SCC1STR; extern volatile SCC1STR _SCC1 @0x00000013; 你会发现,它从第0位开始,到第七位,每一位名称和表中一致,每一位含义用注释给出。 最后一句说明了SCC1使这种结构的一个实际的变量,它叫SCC1, 地址位于 @0x00000013,以后我们就用SCC1代表这个寄存器。 主要用法: 由于SCC1支持位赋值,所以可以对单个位进行操作。 请先不要看注释,对照表格和头文件讲出含义,然后和注释对照。 (1) SCC1_ENSCI=1; // 开启SCI传输 (2) SCC1=0x53; //八位含义是 0,1使能SCI,0,1九位数据,00,1使能校验,1寄校验 SCC2:SCI控制寄存器2 所在地址@0x00000014 功能速查表:
地址 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | SCITE 发送中断使能 | | SCITE 接收中断使能 | | TE 发送使能 | RE 接收使能 | | SBK 发送中止位 | 置1 | 允许 | | 允许 | | 允许 | 允许 | | 发送 | 置0 | 禁止 | | 禁止 | | 禁止 | 禁止 | | 不发送 | 默认 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 头文件定义: /*** SCC2 - SCI控制寄存器2 ***/ typedef union { byte Byte; struct { byte SBK :1; /* Send Break Bit */ byte RWU :1; /* Receiver Wakeup Bit */ byte RE :1; /* Receiver Enable Bit */ byte TE :1; /* Transmitter Enable Bit */ byte ILIE :1; /* Idle Line Interrupt Enable Bit */ byte SCRIE :1; /* SCI Receive Interrupt Enable Bit */ byte TCIE :1; /* Transmission Complete Interrupt Enable Bit */ byte SCTIE :1; /* SCI Transmit Interrupt Enable Bit */ } Bits; } SCC2STR; extern volatile SCC2STR _SCC2 @0x00000014; 主要用法: (1) SCC2=0xac;//八位含义:1发送中断使能,0,1接收中断使能,0,1发送使能1接收使能,00,这种初始化方式主要用于中断方式传输 (2) SCC2_TE=1;SCC_RE=1;//使能发送和接收器,用于非中断方式 SCC3:SCI控制寄存器2 所在地址@0x00000014 功能:主要用于线路状态检测。简单的实现一般用不到。有兴趣的参考头文件定义 /*** SCC3 - SCI 控制寄存器3 ***/ typedef union { byte Byte; struct { byte PEIE :1; /* Receiver Parity Error Interrupt Enable Bit */ byte FEIE :1; /* Receiver Framing Error Interrupt Enable Bit */ byte NEIE :1; /* Receiver Noise Error Interrupt Enable Bit */ byte ORIE :1; /* Receiver Overrun Interrupt Enable Bit */ byte :1; byte :1; byte T8 :1; /* Transmitted Bit 8 */ byte R8 :1; /* Received Bit 8 */ } Bits; } SCC3STR; extern volatile SCC3STR _SCC3 @0x00000015; SCS1状态寄存器1 所在地址@0x00000016 功能速查表:
地址 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | SCTE 发送器空 | | SCRF 接收器满 | | | | | | 置1 | 空,可以放数据 | | 满,可以取数据 | | | | | | 置0 | 满 | | 没数据 | | | | | | 默认 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 头文件定义: /*** SCS1 - SCI状态寄存器1 ***/ typedef union { byte Byte; struct { byte PE :1; /* Receiver Parity Error Bit */ byte FE :1; /* Receiver Framing Error Bit */ byte NF :1; /* Receiver Noise Flag Bit */ byte OR :1; /* Receiver Overrun Bit */ byte IDLE :1; /* Receiver Idle Bit */ byte SCRF :1; /* SCI Receiver Full Bit */ byte TC :1; /* Transmission Complete Bit */ byte SCTE :1; /* SCI Transmitter Empty Bit */ } Bits; } SCS1STR; extern volatile SCS1STR _SCS1 @0x00000016; 主要用法: if(SCS1_SCTE==1) SCDR=data; //如果发送器空,则把数(data)放进去 if(SCS1_SCRF==1) data=SCDR; //如果接收器满,则把数取出来 SCBR:SCI波特率寄存器 所在地址@0x00000019 5,4两位确定了预分频,2,1,0三位确定了波特率分频,两者通过公式 波特率=SCI时钟频率/(64*预分频*波特率分频) 计算出波特率。由此,可作出下列常用波特率设置表,此表只列出常见的波特率
SCP1 | SCP0 | 预分频 | SCR2 | SCR1 | SCR0 | 波特率分频 | 波特率 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 38400 | 0 | 0 | 1 | 0 | 1 | 0 | 2 | 19200 | 0 | 0 | 1 | 0 | 1 | 1 | 8 | 9600 | 0 | 0 | 1 | 1 | 0 | 0 | 16 | 4800 | 0 | 0 | 1 | 1 | 0 | 1 | 32 | 2400 | 0 | 0 | 1 | 1 | 1 | 0 | 64 | 1200 | 1 | 0 | 4 | 0 | 0 | 0 | 1 | 19200 | 1 | 0 | 4 | 0 | 1 | 0 | 4 | 4800 | 1 | 0 | 4 | 0 | 1 | 1 | 8 | 2400 | 1 | 0 | 4 | 1 | 0 | 1 | 32 | 600 | 1 | 0 | 4 | 1 | 1 | 1 | 128 | 300 | 头文件定义: /*** SCBR - SCI Baud Rate Register ***/ typedef union { byte Byte; struct { byte SCR0 :1; /* SCI Baud Rate Select Bit 0 */ byte SCR1 :1; /* SCI Baud Rate Select Bit 1 */ byte SCR2 :1; /* SCI Baud Rate Select Bit 2 */ byte :1; byte SCP0 :1; /* SCI Baud Rate Prescaler Bit 0 */ byte SCP1 :1; /* SCI Baud Rate Prescaler Bit 1 */ byte :1; byte :1; } Bits; struct { byte SCR :3; byte :1; byte SCP :2; byte :1; byte :1; } MergedBits; } SCBRSTR; extern volatile SCBRSTR _SCBR @0x00000019; 主要用法: SCBR=0x04;// 波特率为4800
好了,也许已经被一大堆寄存器搞晕了,或者勉强读完第一个表格,浏览完第二个表格,就看到这里了,通过程序学寄存器,是最好的方式,毕竟我们最终都是用程序来实现。下面,我们一起动手自己做两个小程序,一个是查询方式串口传输的最简形式,一个是中断方式传输的最简形式,中断方式时,用13号中断向量代表接收中断,14号代表发送中断。分别调用不同的中断服务程序。之所以研究最简方式,是为了避免与一堆无关的知识点混淆,提取出精华。(中断就是让CPU停下当前的工作,转向中断服务程序处理中断任务。如果仍不懂什么是中断的话,强烈建议去寻找有关文档) 串口传输最简实现: 程序的任务:把收到的字符发回去 查询方式: 查询方式串口通信几步走: (1) 设置CONFIG1,CONFIG2等,其作用与本文无关,不进行讨论 (2) 根据波特率,进行查表,设置SCBR (3) 开启SCI传输。确定是否校验,从而设置SCC1 (4) 发送器,接收器使能,即设置SCC2 (5) 进入一个死循环 (6) 循环中检测,如果接收器满,就把数据从接收器取出来。如果发送器空,就把数据回送到发送器,如果发送器有数据,就等待; 好了,现在我们可以下笔了。 #include <hidef.h> #include <MC68HC908GP32.h> int data; void main(void) { CONFIG1=0x01; //step1 SCBR_SCR1=1; //step2 SCBR_SCR0=1; SCC1_ENSCI=1; //step3 SCC2_TE=1; //step4 SCC2_RE=1; for(;;){ //step5 if (SCS1_SCRF==1){ //step6 data=SCDR; while(SCS1_SCTE==0); SCDR=data; } } } 中断方式串口通讯几步走:和上边得很像 (1) 设置CONFIG (2) 根据波特率,进行查表,设置SCBR (3) 开启SCI传输。确定是否校验,从而设置SCC1 (4) 发送器,接收器使能,接收中断使能,即设置SCC2 (5) 开中断。 (6) 主程序进入一个死循环。 (7) 定义一个接收中断服务程序,用于处理接收字符,接收完毕开启发送中断 (8) 定义一个发送中断服务程序,用于发送,把接收到的字符发出去 思路理清,代码自然就出来了。 #include <hidef.h> #include <MC68HC908GP32.h> int j; void main(void) { CONFIG1=0x01; //step1 SCBR_SCR1=1; //step2 SCBR_SCR0=1; SCC1_ENSCI=1; //step3 SCC2=0b000101100; //step4 EnableInterrupts; //step5 for(;;){ //step6 } } interrupt 13 void Rx(void){ j=SCDR; while(SCS1_SCTE==0); SCC2_SCTIE=1; //step7 } interrupt 14 void Tx(void){ SCDR=j; SCC2_SCTIE=0; //step8 } 好了,对MC68HC08微控制器的理解是不是加深了呢,其实它的工作很像可编程控制器,设置好工作方式,就能按我们的需求工作了。
|