本帖最后由 ricebucket 于 2020-7-12 22:10 编辑
作为51单片机初学者,制作一个电子钟是不可缺少的实践内容。周末用STC89C52RC+DS1302模块+LCD1602显示屏+DS18B20数字温度传感器实现了一个电子钟,
显示效果如下:
可以用串口命令修改RTC时钟:
实践过程中的一些经验分享:
1、DS1302模块的RST,CLK和IO口最好加上4.7k以上的上拉电阻,提高RTC数据读写可靠性,这个问题让我花了不少时间,最后在Vcc2和IO引脚之间焊接了一个10k电阻解决问题,如图所示:
2、DS1302模块的读写接口类似于I2C,使用burst mode+结构体或者数值可以提高读写效率:
- typedef struct rtc_data
- {
- uint8 SS; //秒
- uint8 MI; //分
- uint8 HH; //小时
- uint8 DD; //日
- uint8 MM; //月
- uint8 DOW; //星期
- uint8 YY; //年
- } T_RTC_DATA;
复制代码- void DS1302_Burst_Write(T_RTC_DATA *rtc_dat)
- {
- uint8 i, *p = (uint8 *)rtc_dat;
- DS1302_RST = 1; //使能片选信号
- _nop_();
- DS1302_Write_Byte(DS1302_BURST_WRITE_ADDR);
- for(i=0; i<sizeof(T_RTC_DATA); i++)
- {
- DS1302_Write_Byte(*p++);
- }
- DS1302_RST = 0;
- _nop_();
- }
- void DS1302_Burst_Read(T_RTC_DATA *rtc_dat)
- {
- uint8 i, *p = (uint8 *)rtc_dat;
- DS1302_RST = 1; //使能片选信号
- _nop_();
- DS1302_Write_Byte(DS1302_BURST_READ_ADDR);
- for(i=0; i<sizeof(T_RTC_DATA); i++)
- {
- *p++ = DS1302_Read_Byte();
- }
- DS1302_RST = 0;
- }
复制代码
3、DS18B20温度传感器采用one wire协议,时序要求精确,读写问题不大,主要还是温度读取后的转换和显示,尤其是小数位的显示,使用长度16的lookup table,可以减少重复计算:
- bit DS18B20_Get_Temperature(int *temp, int *sign)
- {
- bit ack;
- uint8 LSB, MSB;
- ack = DS18B20_Get_Ack();
- if(ack == 0)
- {
- DS18B20_Write_Byte(0xCC); //跳过ROM
- DS18B20_Write_Byte(0xBE); //跳过温度采集
- LSB = DS18B20_Read_Byte(); //读低字节温度值
- MSB = DS18B20_Read_Byte(); //读高字节温度值
- *temp = ((int)MSB<<8) + LSB;
- if(0 > *temp)
- {
- *temp -= 1;
- *temp = ~*temp; //对负温度数据取补码
- *sign = -1; // 负数
- }
- else
- {
- *sign = 1; // 正数
- }
- }
- return ~ack;
- }
复制代码- // DS18B20的小数位四舍五入显示结果速查表,4bit=索引取值范围 0 - 15
- uint8 code dect_lookup_tab[] = {0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9};
复制代码- slen = 0;
- sbuf[slen++] = ds18b20_temp_sign_s < 0 ? '-' : '+';
-
- intT %= 100;
-
- if(intT > 10) sbuf[slen++] = '0' + intT / 10;
- sbuf[slen++] = '0' + intT % 10;
- sbuf[slen++] = '.';
-
- sbuf[slen++] = '0' + dect_lookup_tab[decT]; // 使用之前已经算好的四舍五入结果查表,速度更快。
- //sbuf[slen++] = '0' + (decT*10) / 16; //二进制的小数部分转换为1位十进制位, 小数部分转换为可显示的数字字符
-
- sbuf[slen++] = '\0'; //添加字符串结束符
- LCD1602_Show_Str(10, 0, sbuf); //显示到液晶屏上
复制代码
4、uart命令部分,可以使用串口中断接收输入到UART_Rxd_Buf,然后选择一个合适的定时器间隔读取,解析并执行,详见附件代码。
- /* 串口动作函数,根据接收到的命令帧执行响应的动作
- buf-接收到的命令帧指针,len-命令帧长度 */
- void Uart_Cmd_Handler(uint8 *buf)
- {
- int8 slen = 0;
- printf(">cmd recv: [%s]\r\n", buf);
- if(0 == strncmp("rtc set ", buf, 8))
- {
- if(0 != UART_Cmd_Exec_RTC_Set(buf+8))
- {
- printf(">cmd exec: failed.\r\n");
- }
- }
- else
- {
- printf(">cmd unrecognized.\r\n");
- }
- }
复制代码
5、main函数内容:
- void main()
- {
- int8 slen = 0;
- uint8 pdata uart_cmd_buf[64] = {0};
- EA = 1; //开总中断
- DS18B20_Start();
- UART_Config(9600);
- ConfigTimer0(TIMER0_SLICE_MS); //T0定时10ms
- DS1302_Init(); //初始化RTC时钟
- LCD1602_Init(); //初始化液晶
- LCD1602_Show_Str(0, 0, "**:**:**");
- LCD1602_Show_Str(0, 1, "20**#**#**# ???");
- LCD1602_Show_Char(15, 0, 0); //5x7字符 ℃
- LCD1602_Show_Char(4, 1, 1); //5x7字符 年
- LCD1602_Show_Char(7, 1, 2); //5x7字符 月
- LCD1602_Show_Char(10, 1, 3); //5x7字符 日
- while(1)
- {
- Uart_Cmd_Check(&uart_cmd_buf, sizeof(uart_cmd_buf)-1);
- if (timer_flag_250ms) //每250ms读取依次时间
- {
- ReadAndShowRtc();
- timer_flag_250ms = 0;
- }
- if (timer_flag_3s) //每隔3s执行以下分支
- {
- ReadAndShowTemperature();
- timer_flag_3s = 0;
- }
- }
- }
复制代码
附件文件列表如图所示:
以上代码使用C51开发板调试,接线简单,具体端口可参看config.h
初学单片机,难免有错漏之处,还请各位坛友不吝赐教。
DS1302_UART_LCD1602_STC89C52RC.7z
(2.35 MB, 下载次数: 81)
|