我不得不承认自己的毅力实在是太差了,原先准备一星期写一篇博客的目标一致没有实现,挺惭愧的。前几日忙着参加学院里面关于飞思卡尔竞赛的选拔赛,被要求制作一个用51单片机实现的流水灯和呼吸灯,耗费了我不少时间。现将其记录于此。
原本我是不打算参加初试的,让我朋友参加,积累经验,我专心鼓捣制作机器人,但我的朋友说我应该试试,耗费时间,但有利于我实现制作机器人的计划。
初试还算简单, 主要考察了数电模电以及c语音和飞思卡尔小车的实现思路等。我两手空空就去了考场,遭到了朋友的笑话,说我要是能入选就奇了怪了。因为开卷,所以基本上数电模电翻书找即可,实现思路之类的只好跟朋友共享其事先打印的资料了。好在脸皮厚,成功抵挡了无数白眼。c语言部分也不算难,有个位移的还算有趣,就是对<<符号的考察。
初试结果自然出乎我朋友的意料,我通过了,而两位朋友成功落选。我自然很是得意。不过他们向组织方表达了自己对飞思卡尔的向往后,还是被允许参加复试了。
任务:
要求
我为队长,其他两人分别攻硬件DXP与软件编程。
先期我们的主要任务是了解51单片机的大致流程,求广,防止方向的选择错误。
后来因为大家事都挺忙的,我就扛起了大梁。
在网上买了所需的东西后,我便照着葫芦画瓢,在面包板上搭建电路。第一次是在晚上11点左右,果不其然的搭建错了。没有考虑面包板的结构。后来就改正了。
在网上买的PL2303的USB转ttl线出了问题,刚开始是引脚缺杜邦头,后来我自己想办法给焊上去个杜邦头,还是不能用,貌似其内部短路,向周围的人借,也没有借到。只好从网上买了个ch340的usb转ttl线,谢天谢地,这个很好用。usb转ttl线就这样耽误了一个星期。在此处记录一下,usb转ttl线需要驱动,通过该线向单片机里烧录程序需要另外的软件,我用的是STC-ISP(竟然有个每次下载前都重新装载目标文件选项,坑死我了,不知有多少回我忘记点这个选项而没有装载修改后的hex文件去烧录,看着不能按照预期工作的单片机,又投入到keil中,好悲惨的回忆)。
吐槽一下硬件,好多坑啊。电烙铁用到还行,因为没有买用来飞线的导线,用心疼焊锡,只好把排针上金属针拔下来一个一个的焊当导线,好悲催啊。中间好几回虚焊漏焊,其中艰难不足为外人道也。
因为负责软件工作的同学比较忙,所以我接手了程序的开发。他只完成了基本的流水灯部分,而且冗余的代码超多,我又优化了一番。比如说对开关引脚的判断,竟然用了4个if语句,还没有考虑全,我简单几句就搞定了,还有对开关变化的判断。然后开始添加新功能。
因为我觉得单个的呼吸灯可能会比较没有竞争力,所以准备不光实现灯的强度在时间维度上呈三角波,还要实现灯的强度在空间维度上呈三角波,甚至要实现灯的强度在时间、空间上同时呈三角波(就是初相位相差一定值的平行三角波),想想就美好。不光要有三角波,还要有正弦波,再多加个强度相差一定值的正弦波(感觉这个比初相位相差一定知道正弦波计算量小些)。
需求想好了,就该实现了。我需要手动完成PWM。总结了一下实现PWM的几种思路:
其他的好像也没什么可以说的了。好不容易能沉下心写会技术博客的感觉真好!!!
附录:
程序之现实版
#include
#include
#include
#include
/*************全局常量***********/
#define uchar unsigned char
#define uint unsigned int
#define LED P1
/*****************全局变量**********************************/
uchar changed, status_switch = 0x00, status_switch_pre = 0x00; //status_switch记录当前开关状态,status_switch_pre记录此前开关状态,changed记录开关状态是否变化
uchar counter_PWM = 0, length_PWM = 100; //定时器0的两次中断时间为一小周期,length_PWM个小周期组成一个PWM大周期,counter_PWM负责计数,表示进行到了PWM大周期中的第counter_PWM个小周期
uchar duty_cycle, buffer; //duty_cycle是占空比,buffer缓冲LED点亮信息
uchar i, j;
float f;
/*******************部分流水灯模式所需信息**************/
uchar code table1[]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfc,0xff};
//0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f 左移
uchar code table2[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf};
uchar code table3[]={0x7e,0xbd,0xdb,0xe7,0xff};
uchar code table4[]={0xaa,0x55};
/*********函数声明********************/
void delay1ms();
void delay(uint x);
void keyscan();
void close_int();
void init_pwm();
void timer0_init(void);
/********主函数****************************/
void main()
{
while(1) {
keyscan();
switch (status_switch) { //根据status_switch选择灯的点亮模式
case 0x00:
LED = 0xFF;
break;
case 0x01:
for (i=0;i<16;i++) {
LED=table1[i];
delay(200);
keyscan();
if (changed)
break;
}
break;
case 0x02:
for(i=0;i<14;i++) {
LED=table2[i];
delay(300);
keyscan();
if (changed)
break;
}
break;
case 0x03:
for(i=0;i<5;i++) {
LED=table3[i];
delay(250);
keyscan();
if (changed)
break;
}
break;
case 0x04:
for(i=0;i<2;i++){
LED=table4[i];
delay(3000);
keyscan();
if (changed)
break;
}
break;
case 0x05:
for (i=0;i<16;i++) {
LED=~table1[i];
delay(200);
keyscan();
if (changed)
break;
}
break;
case 0x06:
for(i=0;i<14;i++) {
LED=~table2[i];
delay(500);
keyscan();
if (changed)
break;
}
break;
case 0x07:
for(i=0;i<5;i++) {
LED=~table3[i];
delay(250);
keyscan();
if (changed)
break;
}
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x0B:
timer0_init();
while (1) {
init_pwm();
for (i = 100; i > 0; i-=5) {
duty_cycle = i; //改变占空比
delay(25);
keyscan(); //开关检测
if (changed) {
close_int(); //关闭定时器
break; //退出for语句
}
}
break; //退出switch语句
for (i = 0; i < 100; i+=5) {
duty_cycle = i;
delay(25);
keyscan(); //在该case语句中设置两个keyscan是为了更快的对开关变化做出响应
if (changed) {
close_int();
break;
}
}
break;
}
break;
case 0x0C:
case 0x0D:
timer0_init();
while (1) {
init_pwm();
delay(250); //因为不需要改变有时间维度上的变化,所以没有占空比的改变,只需延时即可
keyscan();
if (changed) {
close_int();
break;
}
}
break;
case 0x0E:
srand(counter_PWM); //设置随机数种子
while(1) {
LED = rand() % 256; //保证随机数在0与255之间
delay(200);
keyscan();
if (changed)
break;
}
break;
case 0x0F:
LED = 0x00;
break;
}
}
}
/*****************延时1ms*************/
void delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/**********延时x毫秒***********/
void delay(uint x)
{ uchar k;
for(k=x; k>0; k--)
delay1ms();
}
/****开关检测。修改开关状态(status_switch)以及更改标志(changed)****/
void keyscan()
{
status_switch_pre = status_switch; //记录此前的开关状态
P2 = 0xFF; //准备读取P2
status_switch = (~P2) & 0x0F; //读取P2并提取有用信息
changed = (status_switch != status_switch_pre); //判断是否改变
}
/*****关闭定时器0***********************/
void close_int()
{
TR0 = 0;//关闭Timer0
ET0 = 0;//关闭T0中断
LED = 0xFF;//PWM输出高电平
}
/*************PWM的初始化******/
void init_pwm()
{
buffer = 0x00; //清空缓存区
duty_cycle = 0; //归零占空比
}
/*****************定时器0************************/
void timer0_init(void) //100微秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x02; //设置定时器模式
TL0 = 0xA4; //设置定时初值
TH0 = 0xA4; //设置定时重载值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1; //开启T0中断
EA=1; //开总中断
}
/*************定时中断1处理程序****************/
void timer0_int(void) interrupt 1
{
switch (status_switch) {
case 0x08:
/**********单个灯光强度在时间维度上呈三角形波*********/
buffer = 0x00;
buffer |= (counter_PWM < duty_cycle) << 0;
break;
case 0x09:
/*****两半灯光强度在空间维度上呈互补三角形波**************************/
if (counter_PWM < duty_cycle)
buffer = 0xF0;
else
buffer = 0x0F;
break;
case 0x0A:
/*****交叉灯的灯光强度在空间维度上呈互补三角形波**************************/
if (counter_PWM < duty_cycle)
buffer = 0x55;
else
buffer = 0xAA;
break;
case 0x0B:
/*****全部灯光强度在时间维度上呈三角形波***************/
if (counter_PWM < duty_cycle)
buffer = 0xFF;
else
buffer = 0x00;
break;
case 0x0C:
/***************灯光强度在空间维度上呈中凸三角形波************/
if (counter_PWM < 5) //原谅我不是完美的三角形波
buffer = 0xFF;
else if (counter_PWM < 20)
buffer = 0x7E;
else if (counter_PWM <45)
buffer = 0x3C;
else
buffer = 0x18;
break;
case 0x0D:
/********灯光强度在空间维度上呈中凹三角形波******************/
if (counter_PWM < 5)
buffer = _crol_(0xFF, 4);
else if (counter_PWM < 20)
buffer = _crol_(0x7E, 4);
else if (counter_PWM <45)
buffer = _crol_(0x3C, 4);
else
buffer = _crol_(0x18, 4);
break;
}
LED = ~buffer; //引脚的1代表灯灭,所以反转一下
if (counter_PWM >= length_PWM)
counter_PWM = 0; //将counter_PWM限制在0到length_PWM之间
counter_PWM++;
}
程序之理想版
#include
#include
#include
/*************全局常量***********/
#define uchar unsigned char
#define uint unsigned int
#define LED P1
#define PI 3.1415926
uchar i, j;
float f;
/*****************全局变量**********************************/
uchar changed, status_switch = 0x00, status_switch_pre = 0x00;
uchar counter_PWM = 0, length_PWM = 100, buffer;
uchar duty_cycles[8];
/*******************部分流水灯模式所需信息**************/
uchar code table1[]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfc,0xff};
//0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f 左移
uchar code table2[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf};
uchar code table3[]={0x7e,0xbd,0xdb,0xe7,0xff};
uchar code table4[]={0xaa,0x55};
/*********函数声明********************/
void delay1ms();
void delay(uint x);
void keyscan();
void close_int();
void init_pwm();
void timer0_init(void);
/********主函数*************/
void main()
{
while(1) {
keyscan();
switch (status_switch) {
case 0x00:
LED = 0xFF;
break;
case 0x01:
for (i=0;i<16;i++) {
LED=table1[i];
delay(200);
keyscan();
if (changed)
break;
}
break;
case 0x02:
for(i=0;i<14;i++) {
LED=table2[i];
delay(300);
keyscan();
if (changed)
break;
}
break;
case 0x03:
for(i=0;i<5;i++) {
LED=table3[i];
delay(250);
keyscan();
if (changed)
break;
}
break;
case 0x04:
for(i=0;i<2;i++){
LED=table4[i];
delay(300);
keyscan();
if (changed)
break;
}
break;
case 0x05:
for (i=0;i<16;i++) {
LED=~table1[i];
delay(200);
keyscan();
if (changed)
break;
}
break;
case 0x06:
for(i=0;i<14;i++) {
LED=~table2[i];
delay(300);
keyscan();
if (changed)
break;
}
break;
case 0x07:
for(i=0;i<5;i++) {
LED=~table3[i];
delay(250);
keyscan();
if (changed)
break;
}
break;
case 0x08:
/**********灯光强度在时间维度上呈三角形波*********/
timer0_init();
while (1) {
init_pwm();
for (i = 100; i > 0; i--) {
duty_cycles[j] = i;
buffer |= ((counter_PWM < duty_cycles[j]) << j); //j代表第几盏灯,第一盏灯时j=0,因为没有指明j,所以每次都有不同的灯作为呼吸灯亮
delay(50);
keyscan();
if (changed) { //在此插入changed的判断语句是为了更好的对开关变化做出响应
close_int();
break;
}
}
for (i = 0; i < 100; i++) {
duty_cycles[j] = i;
buffer |= ((counter_PWM < duty_cycles[j]) << j); //j代表第几盏灯,第一盏灯时j=0;
delay(50);
keyscan();
if (changed) {
close_int();
break;
}
}
}
break;
case 0x09:
/***************灯光强度在空间维度上呈三角形波************/
timer0_init();
for (j = 0; j < 4; j++)
duty_cycles[j] = 25 * j;
for (j = 4; j < 8; j++)
duty_cycles[j] = 25 * (8 - j);
while (1) {
init_pwm();
for (j = 0; j < 8; j++) {
buffer |= ((counter_PWM < duty_cycles[j]) << j);
}
// if (counter_PWM < 25)
// buffer = 0xFF;
// else if (counter_PWM < 50)
// buffer = 0x7E;
// else if (counter_PWM < 75)
// buffer = 0x3C;
// else
// buffer = 0x18;
delay(100);
keyscan();
if (changed) {
close_int();
break;
}
}
break;
case 0x0A:
/********灯光强度在时间、空间维度上呈三角形波******************/
timer0_init();
while (1) {
init_pwm();
for (i = 0; i < 100; i++) {
for (j = 0; j < 4; j++)
duty_cycles[j] = (25 * j + i) % 100;
for (j = 4; j < 8; j++)
duty_cycles[j] = (25 * (8 - j) + i) % 100;
for (j = 0; j < 8; j++)
buffer |= ((counter_PWM < duty_cycles[j]) << j);
delay(50);
keyscan();
if (changed) {
close_int();
break;
}
}
for (i = 100; i > 0; i--) {
for (j = 0; j < 4; j++)
duty_cycles[j] = (25 * j + i) % 100;
for (j = 4; j < 8; j++)
duty_cycles[j] = (25 * (8 - j) + i) % 100;
for (j = 0; j < 8; j++)
buffer |= ((counter_PWM < duty_cycles[j]) << j);
delay(50);
keyscan();
if (changed) {
close_int();
break;
}
}
}
break;
case 0x0B:
/*****灯光强度在时间维度上呈正弦***************/
timer0_init();
while (1) {
init_pwm();
for (i = 0; i < 180; i++) {
duty_cycles[j] = (uchar)(sin(PI * i / 180) * 100); //j号灯为呼吸灯
buffer |= ((counter_PWM < duty_cycles[j]) << j);
delay(50);
keyscan();
if (changed) {
close_int();
break;
}
}
}
break;
case 0x0C:
/*****灯光强度在空间维度上呈正弦波**************************/
timer0_init();
while (1) {
init_pwm();
for (j = 0; j < 8; j++) {
duty_cycles[j] = (uchar)(sin(PI * ((int)(i + 22.5 * j) % 180) /180) * 100);
buffer |= (counter_PWM < duty_cycles[j]) << j;
}
delay(100);
keyscan();
if (changed) {
close_int();
break;
}
}
break;
case 0x0D:
/**灯光强度在时间维度上呈三角形波,空间上为相位相差22.5度的数个平行正弦波**/
timer0_init();
while (1) {
init_pwm();
for (i = 0; i < 180; i++) {
buffer = 0x00;
for (j = 0; j < 8; j++) {
duty_cycles[j] = (uchar)(sin(PI * ((int)(i + 22.5 * j) % 180) /180) * 100);
buffer |= (counter_PWM < duty_cycles[j]) << j;
}
delay(10);
keyscan();
if (changed) {
close_int();
break;
}
}
}
break;
case 0x0E:
/*灯光强度在时间维度上呈正弦波,空间上为强度相差22级的平行正弦波*/
timer0_init();
while (1) {
for (i = 0; i < 180; i++) {
buffer = 0x00;
for (j = 0; j < 8; j++) {
// duty_cycles[j] = (uchar)((int)(sin(PI * i / 180) * 100 + 22) % 100);
duty_cycles[j] = (uchar)(sin(PI * i / 180) * 100 + 22);
buffer |= (counter_PWM < duty_cycles[j]) << j;
}
delay(10);
keyscan();
if (changed) {
close_int();
break;
}
}
}
break;
case 0x0F:
LED = 0x00;
break;
}
}
}
/*****************延时1ms*************/
void delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/**********延时x毫秒***********/
void delay(uint x)
{ uchar k;
for(k=x; k>0; k--)
delay1ms();
}
/****开关检测。修改开关状态(status_switch)以及更改标志(changed)****/
void keyscan()
{
status_switch_pre = status_switch;
P2 = 0xFF;
status_switch = (~P2) & 0x0F;
changed = (status_switch != status_switch_pre);
}
/*****关闭定时器0***********************/
void close_int()
{
TR0 = 0;//关闭Timer0
ET0 = 0;//关闭T0中断
LED = 0xFF;//PWM输出高电平
}
/*************PWM的初始化******/
void init_pwm()
{
uchar k;
buffer = 0x00;
for (k = 0; k < 8; k++)
duty_cycles[k] = 0;
}
/*****************定时器0************************/
void timer0_init(void) //100微秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x02; //设置定时器模式
TL0 = 0xA4; //设置定时初值
TH0 = 0xA4; //设置定时重载值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1; //开启T0中断
EA=1; //开总中断
}
/*************定时中断1处理程序****************/
void timer0_int(void) interrupt 1
{
LED = ~buffer; //引脚的1代表灯灭,所以反转一下
if (counter_PWM >= length_PWM)
counter_PWM = 0; //将counter_PWM限制在0到length_PWM之间
counter_PWM++;
}