准备制作一个旋转时钟,构思了一下,旋转时钟主要包括以下几个部分:
指针板、基板、电机、耦合线圈,用于电力的无线传输、无线串口,用于调试、显示和控制。
2013-10-10
至今天已经完成:
1.AVR单片机对时钟芯片DS1302的读写等操作
2.串口传输部分代码编写完成
准备完成:
1.红外遥控器编码的解码
2.程序匠人的程序原理深入了解,编写LED显示程序
3.购买旋转时钟配件,完成电源的无线传输电路设计与测试,LED板设计。
存在问题:
设置INT0中断为上升沿触发,每触发一次中断,将触发间隔的时间内累加计数传出来。但是用手触碰接收头时,串口会有数据传出。
项目进展:
1.DS1302时钟芯片的读写。
C语言文件:
#include "common.h"
/*-----------------------------------------------------------------
函数名称: void ds1302_init(void)
函数功能: ds1302总线初始化
参 数: 无
返 回 值: 无
-----------------------------------------------------------------*/
void ds1302_init(void)
{
RST_CLR;
SCK_CLR;
RST_OUT;
SCK_OUT;
}
/*-----------------------------------------------------------------
函数名称: void ds1302_write_byte(unsigned int addr,unsigned int data)
函数功能: 向DS1302目标地址中写入一字节数据
参 数: 目标地址 一字节数据
返 回 值: 无
-----------------------------------------------------------------*/
void ds1302_write_byte(unsigned int addr,unsigned int data)
{
unsigned int i;
RST_CLR;
SCK_CLR;
RST_SET;//启动DS1302总线
//传输目标地址
IO_OUT;
addr&=0xfe;
for(i=0;i<8;i++)
{
if(addr&0x01)
{IO_SET;My_Putchar(0x31);}
else
{IO_CLR;My_Putchar(0x30);}
SCK_CLR;
SCK_SET;
addr=addr>>1;
}
//向目标地址中写入数据
IO_OUT;
My_Putstr("data:");
for(i=0;i<8;i++)
{
if(data&0x01)
{IO_SET;My_Putchar(0x31);}
else
{IO_CLR;My_Putchar(0x30);}
SCK_CLR;
SCK_SET;
data=data>>1;
}
RST_CLR;//禁止DS1302总线
}
/*-----------------------------------------------------------------
函数名称: int ds1302_read_byte(unsigned int addr)
函数功能: 从DS1302目标地址中读取一字节数据
参 数: 目标地址
返 回 值: 一字节数据
-----------------------------------------------------------------*/
unsigned int ds1302_read_byte(unsigned int addr)
{
unsigned int i,temp;
temp=0x00;
RST_CLR;
SCK_CLR;
RST_SET;//启动DS1302总线
//写入目标地址
IO_OUT;
addr=addr|0x01;
for(i=0;i<8;i++)
{
SCK_CLR;
if(addr&0x01)
IO_SET;
else
IO_CLR;
SCK_SET;
addr=addr>>1;
}
//读取目标地址数据
IO_IN;
for(i=0;i<8;i++)
{
if(IO_R)
temp=temp|0x80;
else
temp=temp&0x7f;
SCK_SET;SCK_CLR;
temp=temp>>1;
}
RST_CLR;//禁止DS1302总线
return temp;
}
/*-----------------------------------------------------------------
函数名称: void ds1302_write_time(unsigned int *time_data)
函数功能: 将日期信息写入DS1302中
参 数: 数组中的时间信息
返 回 值: 无
-----------------------------------------------------------------*/
void ds1302_write_time(unsigned int *time_data)
{
ds1302_write_byte(ds1302_control_addr,0x00);//解除写禁止;最高位WP清零
ds1302_write_byte(ds1302_sec_addr,0x10);//暂停时钟;CH位置位
time_data++;
ds1302_write_byte(ds1302_year_addr,*time_data++); //只写入后面两位 08
ds1302_write_byte(ds1302_month_addr,*time_data++); //月
ds1302_write_byte(ds1302_date_addr,*time_data++); //日
ds1302_write_byte(ds1302_hr_addr,*time_data++); //时
ds1302_write_byte(ds1302_min_addr,*time_data++); //分
ds1302_write_byte(ds1302_sec_addr,*time_data++); //秒
ds1302_write_byte(ds1302_day_addr,*time_data); //周
ds1302_write_byte(ds1302_control_addr,0x80); //打开写保护
}
/*-----------------------------------------------------------------
函数名称: void ds1302_read_time(unsigned char *time_data)
函数功能: 从DS1302中读取日期时间信息
参 数: 日期时间信息存放数组的地址
返 回 值: 无
-----------------------------------------------------------------*/
void ds1302_read_time(unsigned int *time_data)
{
time_data++;
*time_data=ds1302_read_byte(ds1302_year_addr); //只读出后面两位08
time_data++;
*time_data=ds1302_read_byte(ds1302_month_addr); //月
time_data++;
*time_data=ds1302_read_byte(ds1302_date_addr); //日
time_data++;
*time_data=ds1302_read_byte(ds1302_hr_addr); //时
time_data++;
*time_data=ds1302_read_byte(ds1302_min_addr); //分
time_data++;
*time_data=(ds1302_read_byte(ds1302_sec_addr))&0x7F; //秒
time_data++;
*time_data=ds1302_read_byte(ds1302_day_addr); //周
}
DS1302头文件:
#ifndef _H_INCLUDE_
#define _H_INCLUDE_
//端口修改部分
#define DS1302_RST PA1
#define DS1302_IO PA2
#define DS1302_SCLK PA3
#define DS1302_PORT PORTA
#define DS1302_DDR DDRA
#define DS1302_PIN PINA
//复位脚
#define RST_CLR DS1302_PORT &= ~(1 << DS1302_RST)
#define RST_SET DS1302_PORT |= (1 << DS1302_RST)
#define RST_IN DS1302_DDR &= ~(1 << DS1302_RST)
#define RST_OUT DS1302_DDR |= (1 << DS1302_RST)
//双向数据
#define IO_CLR DS1302_PORT &= ~(1 << DS1302_IO)
#define IO_SET DS1302_PORT |= (1 << DS1302_IO)
#define IO_R DS1302_PIN & (1 << DS1302_IO)
#define IO_IN DS1302_DDR &= ~(1 << DS1302_IO)
#define IO_OUT DS1302_DDR |= (1 << DS1302_IO)
//时钟信号
#define SCK_CLR DS1302_PORT &= ~(1 << DS1302_SCLK)
#define SCK_SET DS1302_PORT |= (1 << DS1302_SCLK)
#define SCK_IN DS1302_DDR &= ~(1 << DS1302_SCLK)
#define SCK_OUT DS1302_DDR |= (1 << DS1302_SCLK)
#define ds1302_sec_addr 0x80
#define ds1302_min_addr 0x82
#define ds1302_hr_addr 0x84
#define ds1302_date_addr 0x86
#define ds1302_month_addr 0x88
#define ds1302_day_addr 0x8a
#define ds1302_year_addr 0x8c
#define ds1302_control_addr 0x8e
#define ds1302_charger_addr 0x90
#define ds1302_clkburst_addr 0xbe
#endif
void ds1302_init(void);
void ds1302_write_byte(unsigned int addr,unsigned int data);
unsigned int ds1302_read_byte(unsigned int addr);
void ds1302_write_time(unsigned int *time_data);
void ds1302_read_time(unsigned int *time_data);
#include
#include
#define fosc 16000000
#define baud 9600
/*-----------------------------------------------------------------
函数名称: void Usart_init(unsigned int baud)
函数功能: 串口初始化
0.2%/8bit/1停止位/无奇偶校验
参 数: 波特率
返 回 值: 无
-----------------------------------------------------------------*/
void Usart_init(void)
{
UBRRL=(fosc/16/(baud+1))%256; /*波特率为:9600*/
UBRRH=(fosc/16/(baud+1))/256;
UCSRC=(1<
void Usart_init(void);
void Usart_Transmit(unsigned char i);
void My_Putstr(char *s);
void My_Putchar(unsigned char data );
2013-10-12
【问题1】昨天晚上红外接收管突然罢工了,对于遥控的呼唤爱理不理。我吧INT0接到按键,然后对案件计数,结果CPU对于按键的输入还是有响应的,可怜的IR3808,你肿么了,待我用示波器看你一看,不行的话就只能把你给换了。
言归正传,今天看了一上午代码,参看的大神程序匠人的程序,对大神深厚而规范的编程功底膜拜一下。
一个地方很不懂,他首先定义了一个结构体。
typedef struct {
unsigned b0 : 1; //结构元素表
unsigned b1 : 1;
unsigned b2 : 1;
unsigned b3 : 1;
unsigned b4 : 1;
unsigned b5 : 1;
unsigned b6 : 1;
unsigned b7 : 1;
} BIT_F;
typedef union {
BIT_F oneBit; //按位寻址
tU08 allBits; //按字节寻址
}FLAG_union; //定义一个既能按位寻址,也可按字节寻址的联合
在后续,匠人进行了一系列的宏定义:
EXT_ FLAG_union PORTA_TEMP; //PORTA 口输出临时缓冲区
EXT_ FLAG_union PORTB_TEMP; //PORTB 口输出临时缓冲区
EXT_ FLAG_union PORTC_TEMP; //PORTC 口输出临时缓冲区
EXT_ FLAG_union DISP_BUF; //字码缓冲区
#define PORTA_TMP PORTA_TEMP.allBits
#define PORTB_TMP PORTB_TEMP.allBits
#define PORTC_TMP PORTC_TEMP.allBits
#define LED01_TMP PORTC_TEMP.oneBit.b4 //外框
#define LED02_TMP PORTB_TEMP.oneBit.b1 //行 1
#define LED03_TMP PORTB_TEMP.oneBit.b2 //行 2
#define LED04_TMP PORTB_TEMP.oneBit.b3 //行 3
#define LED05_TMP PORTB_TEMP.oneBit.b4 //行 4
#define LED06_TMP PORTB_TEMP.oneBit.b5 //行 5
#define LED07_TMP PORTB_TEMP.oneBit.b6 //行 6
#define LED08_TMP PORTB_TEMP.oneBit.b7 //行 7
#define LED09_TMP PORTA_TEMP.oneBit.b1 //行 8
#define LED10_TMP PORTA_TEMP.oneBit.b2 //行 9
#define LED11_TMP PORTA_TEMP.oneBit.b3 //行 10
#define LED12_TMP PORTA_TEMP.oneBit.b4 //行 11
#define LED13_TMP PORTA_TEMP.oneBit.b5 //行 12
#define LED14_TMP PORTC_TEMP.oneBit.b0 //行 13
#define LED15_TMP PORTC_TEMP.oneBit.b1 //行 14
#define LED16_TMP PORTC_TEMP.oneBit.b2 //行 15
#define LED17_TMP PORTC_TEMP.oneBit.b3 //表针
//(特别注意:该口与其他口的控制电平相反.其他口 0=亮,1=灭;该口 0=灭,1=亮)
#define LED29_TMP PORTC_TEMP.oneBit.b5 //内框
#define DISP_BF DISP_BUF.allBits
#define DISP_BF_0 DISP_BUF.oneBit.b0 //bit_0
#define DISP_BF_1 DISP_BUF.oneBit.b1 //bit_1
#define DISP_BF_2 DISP_BUF.oneBit.b2 //bit_2
#define DISP_BF_3 DISP_BUF.oneBit.b3 //bit_3
#define DISP_BF_4 DISP_BUF.oneBit.b4 //bit_4
#define DISP_BF_5 DISP_BUF.oneBit.b5 //bit_5
#define DISP_BF_6 DISP_BUF.oneBit.b6 //bit_6
#define DISP_BF_7 DISP_BUF.oneBit.b7 //bit_7
程序匠人的基本思路我还是清楚,就是通过操作共用体中的成员来达到控制端口电平的作用,我疑惑的是,他只是简单的定义了一下共用体名称,并没有定义共用体成员对应的端口。比如说LED12_TMP对应的是A端口的4位,通过宏定义可以找到
LED12_TMP PORTA_TEMP.oneBit.b4
但是PORTA_TEMP.oneBit.b4只是作了如下定义:
EXT_ FLAG_union PORTA_TEMP; //PORTA 口输出临时缓冲区
并没有指定PORTA_TEMP就是对应的端口A。
【解答1】
估计是在某些头文件里面定义了,但是没有放在文档里.
【问题2】
IT工程师最大的特点就是有耐心,因为就算有脾气,程序也会让他没脾气。今天INT0端口突然不响应我的红外遥控的上升沿中断了,我将PD2(INT0)端口接到按键端口,发现能够触发,但是接到红外接收管端口就是不行?
【解决2】
IO初始化的时候,将PD2初始化为1,也就是输出,改为0就可以了。但是为什么能够响应按键中断却不能响应红外中断呢?PS:我真的毫无脾气了。
2013-10-14
今天终于把红外全部搞定了,至此串口、DS1302、红外三个大部分已经搞定,近段工作还有按键逻辑编程、LED显示界面程序编写。
首先还是来按键逻辑吧。
按键设置的基本操作逻辑如下:
2013-10-21
实现了红外解码、按键逻辑。剩下LED显示模块。
2013-10-31
今天试着考虑LED显示问题。
首先,希望通过宏定义来屏蔽端口差异化,底端LED端口通过类似sbit LED1 PB2类似定义来屏蔽端口差异化,然后通过类似数组形式来统一控制端口LED,如LED={LED1,LED2,......}。大体构想如此,但是如果端口控制电平并不归一将如何解决?如LED1高电平点亮LED,而LED2则是低电平点亮LED?那么在给LED端口统一赋值(假设控制高电平归一控制)之后,通过检测函数来修改异位电平。
然后就是LED点亮的问题了。借用匠人的图来说明吧。
计划一周布置180列,关于LED布置其实我觉得不应该均衡布置,靠近中心的可以稀疏一点,而且外围LED控制可以更加精细一点,比如内部LED显示1ms,外围LED就可以显示0.5ms,以显示点比较匀称,不会造成看起来外部点大,内部点小的情况。好吧,先做一个基本版。
首先,在指针板上的过零中断中的计数器可以统计出旋转一周的数值,在数值稳定之后,则说明电机旋转稳定了,假设这个值为n。那么这一周上180列每一列占的时间为n/180,而LED点亮多久呢?这个需要具体调试。初步想法是先设定一个显示时间的基准值,让旋转时钟能够初步显示出来,然后用红外遥控器来调试,通过改变基准值来控制每一列点亮的时间,设置好之后再存放如eeprom中,然后后面就可以每次通过读取这个值来正常显示了。所以还需要设置一个调试模式了。
关于调试模式:还是通过红外。通过长按MODE键,进入调试模式,调试模式结束后,按EQ保存入EEPROM中,按取消取消设置。
当然关于调试模式的设定都是后面的事情了,现在这里MARK一下。
2013-11-13
最近有开题报告要弄,先中断一下,有点想法,先记下来。
【想法】有关旋转中断:
现有的方案是一周只有一个中断,底板放置一个小磁铁或者对管的发送端,这样做的弊端就是首先一周一次的终端使得显示控制不够精细,另外在机械安装方面,需要仔细的安装校对。我的想法是,红外的发送和接受端口均在指针板,在底板上贴上有黑白间隔或者白色部分间隔彩条的纸片,这样在一周之中,指针板能够检测到多次中断,这样有利于显示控制的精细,而且也不用精细的控制对管的位置。
2013-11-22
nice man!美好的一天!
开题报告搞完,可以动手做东西了。
计划:
周末用万用版搭建电路,并开始显示程序的着手调试。
显示端程序思路如下:
1.指针板旋转一周,定时器计数,设数值为counter_circle;
2.一周共180个显示列,每一列最长显示时间为counter_column=counter_circle/180;
3.实际每列显示时间为 display_column=counter_column-offset;
2013-11-24
程序匠人送显缓冲区程序分析:
1.定义联合体;2.端口位缓冲区赋值;3.按字节端口赋值;
1.定义联合体
typedef union {
BIT_F oneBit; //按位寻址
tU08 allBits; //按字节寻址
}FLAG_union;
2.端口位缓冲区赋值
//========
//IO 口输出临时缓冲区
//========
EXT_ FLAG_union PORTA_TEMP; //PORTA 口输出临时缓冲区
EXT_ FLAG_union PORTB_TEMP; //PORTB 口输出临时缓冲区
EXT_ FLAG_union PORTC_TEMP; //PORTC 口输出临时缓冲区
EXT_ FLAG_union DISP_BUF; //字码缓冲区
#define PORTA_TMP PORTA_TEMP.allBits
#define PORTB_TMP PORTB_TEMP.allBits
#define PORTC_TMP PORTC_TEMP.allBits
#define LED01_TMP PORTC_TEMP.oneBit.b4 //外框
#define LED02_TMP PORTB_TEMP.oneBit.b1 //行 1
#define LED03_TMP PORTB_TEMP.oneBit.b2 //行 2
#define LED04_TMP PORTB_TEMP.oneBit.b3 //行 3
#define LED05_TMP PORTB_TEMP.oneBit.b4 //行 4
#define LED06_TMP PORTB_TEMP.oneBit.b5 //行 5
#define LED07_TMP PORTB_TEMP.oneBit.b6 //行 6
#define LED08_TMP PORTB_TEMP.oneBit.b7 //行 7
#define LED09_TMP PORTA_TEMP.oneBit.b1 //行 8
#define LED10_TMP PORTA_TEMP.oneBit.b2 //行 9
#define LED11_TMP PORTA_TEMP.oneBit.b3 //行 10
#define LED12_TMP PORTA_TEMP.oneBit.b4 //行 11
#define LED13_TMP PORTA_TEMP.oneBit.b5 //行 12
#define LED14_TMP PORTC_TEMP.oneBit.b0 //行 13
#define LED15_TMP PORTC_TEMP.oneBit.b1 //行 14
#define LED16_TMP PORTC_TEMP.oneBit.b2 //行 15
#define LED17_TMP PORTC_TEMP.oneBit.b3 //表针
//(特别注意:该口与其他口的控制电平相反.其他口 0=亮,1=灭;该口 0=灭,1=亮)
#define LED29_TMP PORTC_TEMP.oneBit.b5 //内框
#define DISP_BF DISP_BUF.allBits
#define DISP_BF_0 DISP_BUF.oneBit.b0 //bit_0
#define DISP_BF_1 DISP_BUF.oneBit.b1 //bit_1
#define DISP_BF_2 DISP_BUF.oneBit.b2 //bit_2
#define DISP_BF_3 DISP_BUF.oneBit.b3 //bit_3
#define DISP_BF_4 DISP_BUF.oneBit.b4 //bit_4
#define DISP_BF_5 DISP_BUF.oneBit.b5 //bit_5
#define DISP_BF_6 DISP_BUF.oneBit.b6 //bit_6
#define DISP_BF_7 DISP_BUF.oneBit.b7 //bit_7
3.按字节端口赋值
//========
//送显
//========
PORTA = PORTA_TMP ;
PORTB = PORTB_TMP ;
PORTC = PORTC_TMP ;
2013-11-26
程序匠人显示部分中断分析:
---- DISP_TIME_SET Matches (37 in 4 files) ----
Common.h:EXT_ tU16 DISP_TIME_SET; //单列显示时间设置值(赋值给 TMR1H,TMR1L)
Display.c: DISP_QUEUE1[0] = DISP_TIME_SET / 10000 ;
Display.c: DISP_QUEUE1[1] = ( DISP_TIME_SET / 1000 ) % 10 ;
Display.c: DISP_QUEUE1[2] = ( DISP_TIME_SET / 100 ) % 10 ;
Display.c: DISP_QUEUE1[3] = ( DISP_TIME_SET / 10 ) % 10 ;
Display.c: DISP_QUEUE1[4] = DISP_TIME_SET % 10 ;
Interruption.c: TMR1HL = TMR1HL + DISP_TIME_SET ; //TMR1 重新赋初值
Interruption.c: if ( TIMR1_JSQ > 220 ) DISP_TIME_SET = DISP_TIME_SET - 32 ;
Interruption.c: else if ( TIMR1_JSQ > 200 ) DISP_TIME_SET = DISP_TIME_SET - 16 ;
Interruption.c: else if ( TIMR1_JSQ > 190 ) DISP_TIME_SET = DISP_TIME_SET - 8 ;
Interruption.c: else if ( TIMR1_JSQ > 185 ) DISP_TIME_SET = DISP_TIME_SET - 4 ;
Interruption.c: else if ( TIMR1_JSQ > 182 ) DISP_TIME_SET = DISP_TIME_SET - 2 ;
Interruption.c: else DISP_TIME_SET = DISP_TIME_SET - 1 ;
Interruption.c: if ( DISP_TIME_SET < 400 ) DISP_TIME_SET = 400 ; //钳位
Interruption.c: if ( TIMR1_JSQ < 140 ) DISP_TIME_SET = DISP_TIME_SET + 32 ;
Interruption.c: else if ( TIMR1_JSQ < 160 ) DISP_TIME_SET = DISP_TIME_SET + 16 ;
Interruption.c: else if ( TIMR1_JSQ < 170 ) DISP_TIME_SET = DISP_TIME_SET + 8 ;
Interruption.c: else if ( TIMR1_JSQ < 175 ) DISP_TIME_SET = DISP_TIME_SET + 4 ;
Interruption.c: else if ( TIMR1_JSQ < 178 ) DISP_TIME_SET = DISP_TIME_SET + 2 ;
Interruption.c: else DISP_TIME_SET = DISP_TIME_SET + 1 ;
Interruption.c: if ( DISP_TIME_SET > 65100 ) DISP_TIME_SET = 65100 ; //钳位
Main.c: DISP_TIME_SET = (65536-1000) ; //单列显示时间设置值
Main.c: TMR1HL = DISP_TIME_SET ;//TMR1 赋初值
在源程序中搜索DISP_TIME_SET,便得到了匠人的思路。匠人将定时器设定值定义为:
TMR1HL = DISP_TIME_SET ;
随后在中断部分给出定时器1初始值赋值公式:
TMR1HL = TMR1HL + DISP_TIME_SET ; //TMR1 重新赋初值
其中定时器溢出时间为(65535-TIMR1HL)/晶振频率。最后程序达到的效果为:每个旋转周期,定时器次数为180次。
2013-12-16
POV旋转时钟暂时初步成效:
【存在问题】
1.转速平稳而高速时,溢出中断不能达到一圈180次速率。
2.显示部分为端口直接设定方法,不够模块化,输出英文字符串时,比较复杂。
【解决方法】
1.是否为熔丝位设定问题,贴片MEGA16默认为1M内部晶振
2.参考《51单片机POV趣味单片机制作详解》中显示方法:
(1)创建字符库数组ASCIIDOC[ ];
(2)通过引用向量数组V[ ]来引用ASCIIDOC[ ]字符,输出到端口;
如:
PORTA=ASCIIDOC[V[i]*ZK_LS+j];
表示将ASCIIDOC中向量V[]中第i×ZK_LS+j个字符的取出来,其中ZK_LS为每个显示字符的列数,通过j来将每列逐个显示出来。