一直有一个做机器人的梦,所以从去年起放弃了十多年的软件开发,开始进入嵌入式领域,先后在.Net Micro Framework 项目中完成了Ti DM335上的GPIO、I2C、USB等驱动,方浅浅地了解了什么叫嵌入式开发。
对非软件也非硬件出身的我,学硬件当然从单片入手最简单,如果直接从ARM开始,就像学语言直接从VB,VC开始似的,刚开始可能觉得很有成就感,但是学久了,才知道浮在上面很难深下去了。
正好开发USB驱动期间看了一本介绍USB的书,该书还附送PCB板,所以就从焊接这个电路板开始吧(记得最早焊过的相对复杂的电路板是大学金工实习时的收音机,不过和这个相比就是大巫见小巫了)。去了中发电子市场一两次,总算把该买的零件和工具置办齐,现在就要开始动手了(参见下图)。
焊接后的成品(参见下图)
对没有多少焊接经验的我来说,焊接过程即充满波折也充满乐趣。
一开始我很担心,怕焊接时间过长烧坏了芯片,其实这种担心是多余的,一般的芯片还是比较耐高温的,上网查了些资料,说芯片最怕的是静电,所以焊接时一定记得带防静电手腕带。
焊接完毕后,一上电,电源灯正常点亮,可没想到运行ISP程序竟无法下载,用示波器查看,发现主晶振没有起振(也可以用万用表量两管脚电压来判断)。仔细用万用表排查,发现两个问题,一是CPU有几个管脚虚焊,二是串口条线设置有些问题(看原理图理解有误),重新又补焊了CPU的几个管脚和调整了跳线,一上电ISP程序就可以正常下载了,编写了一个小测试程序,果然按钮、LED、蜂鸣器一切正常。
接着测试USB芯片,但是很不妙,读出的ID号为0。又用万用表仔细排查,又是焊接问题,USB芯片一个管脚没有焊好,重新补焊,读ID正常。
看来对我们新手来说,焊接这步很关键,宁愿焊的慢一些,也要焊接的牢一点。
(不过下载了鼠标,U盘等程序,设备还是不能正常运转,用USB分析仪监控了一下,发现设备可以正常接收数据,但是无法向PC返回数据,出现总线超时错误。看来USB芯片还是有些问题,不过这有可能不是焊接的问题了,有可能和时序相关,等有时间再深入研究吧)。
下面是我根据书中和网上的资料重新编写了测试程序:
----------------
STC89C52.h
----------------
#ifndef __STC89C52_H__
#define __STC89C52_H__
#include <REGX52.H>
//--
#define TRUE 1
#define FALSE 0
#define BOOL unsigned char
#define UINT8 unsigned char
#define UINT16 unsigned short int
#define UINT32 unsigned long int
#define UINT64 unsigned long long int
#define INT8 signed char
#define INT16 signed short int
#define INT32 signed long int
#define INT64 signed long long int
//--
#define Fclk 22118400UL //主频
#define BitRate 9600UL //串口波特率
//--
void STC89C52_Init(void);
//led 0-7
#define Leds P2
void SetLed(UINT8 led,BOOL ON);
BOOL GetLed(UINT8 led);
//key 0-7
extern volatile UINT8 idata KeyPress,KeyValue;
BOOL GetKey(UINT8 key);
void Delay(UINT16 millisecond);
void Sound(UINT16 millisecond);
void Print(char * info);
void PutHex(UINT32 x,UINT8 Num);
#endif
---------------------------
STC89C52.c
---------------------------
#include "stc89c52.h"
void Keyboard_Init(void);
void Uart_Init(void);
//--
void STC89C52_Init(void)
{
P2=0xFF; //LED全灭
EA=1; //允许中断
Keyboard_Init();
Uart_Init();
}
//--
void SetLed(UINT8 led,BOOL ON)
{
if(ON)
{
P2 &= ~(0x1<<led);
}
else
{
P2 |= 0x1<<led;
}
}
BOOL GetLed(UINT8 led)
{
return ~((P2>>led) & 0x1);
}
//--
volatile UINT8 idata KeyPress,KeyCurrent,KeyOld,KeyNoChangedTime;
void Keyboard_Init(void)
{
P1 = 0xFF; //键盘对应的IO设为输入状态
KeyPress = 0; //无按键按住
KeyNoChangedTime = 0;
KeyOld=0;
KeyCurrent=0;
TMOD &= 0xF0; //TMOD低四位控制定时器0
TMOD |= 0x01; //选择16位定时模式
ET0 = 1; //允许定时器0中断
TR0 = 1; //启动定时器0
}
//定时器0中断处理
volatile UINT8 idata KeyValue=0;Flag=0,KeyX=0,KeyY=0,KeyXY=0;
code KeyMap[]={0x44,0x81,0x41,0x21,0x11,0x82,0x42,0x22,0x12,0x84,0x24,0x14,0x88,0x48,0x28,0x18};
void Timer0_ISR(void) interrupt 1
{
UINT8 i;
//定时
TH0=(65536-Fclk/1000/12*5+15)/256;
TL0=(65536-Fclk/1000/12*5+15)%256;
//开始按键扫描
KeyCurrent=~P1;
//按键发生了变化
if(KeyCurrent != KeyOld)
{
KeyNoChangedTime=0;
KeyOld=KeyCurrent;
return;
}
else
{
if(++KeyNoChangedTime>=1) //时间到
{
KeyNoChangedTime=1;
KeyPress=KeyOld;
}
}
//---------------
switch(Flag)
{
case 0:
P0=0x0F;
Flag=1;
break;
case 1:
KeyX=~P0 & 0x0F;
if(KeyX != 0x0) Flag=2;
else KeyXY=0;
break;
case 2:
P0=0xF0;
Flag=3;
break;
case 3:
KeyY=(~P0 & 0xF0)>>4;
if(KeyY != 0x0) Flag=4;
else KeyXY=0;
break;
case 4:
Flag=0;
if(KeyXY==0)
{
KeyXY= KeyY<<4 | KeyX;
for(i=0;i<16;i++)
{
if(KeyXY==KeyMap[i])
{
KeyValue=i;
break;
}
}
}
break;
}
}
BOOL GetKey(UINT8 key)
{
return (BOOL)(KeyPress>>key & 0x1);
}
void Delay(UINT16 millisecond)
{
if(millisecond<10)
{
UINT8 ms =(UINT8)millisecond;
UINT8 num=200;
while(ms--) while(num--);
}
else
{
UINT8 num=10;
while(millisecond--)while(num--);
}
}
//--
sfr P4 = 0xE8;
sbit P4_0=P4^0;
void Sound(UINT16 millisecond)
{
P4_0=0;
Delay(millisecond);
P4_0=1;
}
//--
void Uart_Init(void)
{
EA=0; //暂时关闭中断
TMOD &=0x0F; //TMOD低四位控制定时器1
TMOD |=0x20; //自动重装模式
SCON=0x50; //串口工作在模式1
TH1=256-Fclk/(BitRate*12*16);
TL1=256-Fclk/(BitRate*12*16);
PCON|=0x80; //串口波特率加倍
ES=1; //串行中断允许
TR1=1; //启动定时器1
REN=1; //允许接收
EA=1; //允许中断
}
volatile BOOL Sending;
void Uart_ISR(void) interrupt 4
{
if(RI) //收到数据
{
RI=0; //清中断
}
else
{
TI=0;
Sending=FALSE; //清正在发送数据
}
}
void PutChar(UINT8 c)
{
SBUF=c; //把字符写入发送缓冲区
Sending=TRUE;
while(Sending); //等待发送完毕
}
code UINT8 HexTable[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
void PutHex(UINT32 x,UINT8 Num)
{
INT8 i;
UINT8 Hexs[9]={'0','/0','/0','/0','/0','0','/0','/0','/0'};
if(Num<1)Num=1;
if(Num>8)Num=8;
for(i=Num-1;i>=0;i--)
{
Hexs[i]=HexTable[(x & 0xF)];
x >>= 4;
}
Print(Hexs);
}
void Print(char * str)
{
while((*str)!='/0')
{
PutChar(*str);
str++;
}
}
----------------
main.c
----------------
#include <REGX52.H>
#include "../common/stc89c52.h"
void main(void)
{
UINT8 i;
STC89C52_Init();
//发送信息
Print("Hello C51!/r/n");
//蜂鸣器自检
Sound(200);
//LED自检
for(i=0;i<8;i++)
{
SetLed(i,TRUE);
Delay(100);
SetLed(i,FALSE);
}
while(TRUE)
{
Leds=~KeyPress;
if(KeyValue!=0xFF)
{
PutHex(KeyValue,9); Print("/r/n");
KeyValue=0xFF;
}
}
}
其实上面的C51程序很简单,有C功底的人一看就会。不过学ARM却不这么容易了,想在ARM上编写一个最简单的“Hello world!”,就需要做很多初始化工作。做了近一年的.Net Micro Framework porting工作的我,要想实现这一步还真不容易(不过真正学好单片也不容易)。可见站在别人战车上习惯了,自己下来走两步,竟不知道如何举步了。VS2008、VS2010等高级开发工具的出现,对我们来说,是福?是祸?我们不难想见。
十年软件,十年硬件,一步一个脚印,只要努力就有希望!