找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3|回复: 0
打印 上一主题 下一主题
收起左侧

基于51单片机的语音播报时钟程序

[复制链接]
跳转到指定楼层
楼主
ID:1177203 发表于 2026-10-14 20:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
程序源码
  1. /*=====================================================================
  2. * STC15F204EA(28脚) 四位数码管语音时钟
  3. * 语音报时 / DS1302时钟 / 两键校时 / 闹钟蜂鸣
  4. *---------------------------------------------------------------------
  5. * 硬件连接(依原理图):
  6. *   数码管段选(共阳,低电平点亮): a=P2.0 b=P2.1 c=P2.2 d=P2.3
  7. *                                e=P2.4 f=P2.5 g=P2.6 dp=P2.7
  8. *   位选(8550驱动,低电平选通):   DS1=A1/P3.2  DS2=A2/P3.3
  9. *                                DS3=A3/P3.4  DS4=A4/P3.5  (时分显示)
  10. *   DS1302:  CE=P1.0  I/O=P1.1  SCLK=P1.2  (32768晶振,3V电池后备)
  11. *   蜂鸣器:  P1.5 (低电平响,有源蜂鸣器)
  12. *   按键:    S1=P3.1(另一端GND)  S2=P3.0(另一端GND)  与串口复用
  13. *   语音板:  P1四针插座 = P3.1(TXD) P3.0(RXD) GND 5V, 9600bps
  14. *            协议: SYN6288兼容 (0xFD+长度+GBK文本+异或和)
  15. *            若你的语音板协议不同,只需改 voice_build()/voice_send()
  16. *   空闲脚:  P1.3/P1.4(J01/J02) P3.6/P3.7(P3插座) P0.0/P0.1
  17. *---------------------------------------------------------------------
  18. * 按键操作:
  19. *   短按 S1: 小时+1(写入DS1302)      短按 S2: 分钟+1(秒清零)
  20. *   长按 S1(1秒): 进入/确认退出闹钟设置(退出时保存并开启闹钟)
  21. *   长按 S2(1秒): 正常模式-闹钟开/关; 设置模式-取消退出
  22. *   闹钟设置模式下: S1=闹钟时+1  S2=闹钟分+1  10秒无操作自动取消退出
  23. *   同时按住 S1+S2: 语音播报当前时间
  24. *   闹钟触发: 蜂鸣器响60秒,期间任意短按停止;触发时语音播报时间
  25. *   显示小数点(dp): 常亮=闹钟已开启  熄灭=闹钟关闭  快闪=闹钟设置中
  26. *---------------------------------------------------------------------
  27. * 注意: STC-ISP下载时内部IRC请选择 11.0592MHz (与下面 FOSC 一致),
  28. *       若用其他频率,需同步修改 FOSC 及两个定时器初值。
  29. *=====================================================================*/
  30. #include <reg51.h>
  31. #include <intrins.h>

  32. typedef unsigned char u8;
  33. typedef unsigned int  u16;

  34. #define FOSC      11059200UL        /* 内部IRC频率,须与STC-ISP设置一致 */
  35. #define KEY_LONG  200               /* 长按判定 200*5ms = 1秒 */

  36. sbit DS_CE   = P1^0;                /* DS1302 片选 */
  37. sbit DS_IO   = P1^1;                /* DS1302 数据 */
  38. sbit DS_SCLK = P1^2;                /* DS1302 时钟 */
  39. sbit BUZZER  = P1^5;                /* 蜂鸣器,低电平响 */
  40. sbit KEY_S1  = P3^1;                /* S1键(与TXD复用) */
  41. sbit KEY_S2  = P3^0;                /* S2键(与RXD复用) */

  42. /*------------------------- 数码管 -------------------------
  43. * 共阳管,段选低电平点亮: 段码表0-9 (bit0=a ... bit6=g, bit7=dp) */
  44. u8 code SEG_TAB[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
  45. u8 code DIG_SEL[4]  = {0x04,0x08,0x10,0x20};    /* 位选 A1-A4 = P3.2-P3.5 */

  46. u8 disp_buf[4] = {0xFF,0xFF,0xFF,0xFF};         /* 显示缓冲(DS1-DS4) */
  47. u8 dp_mask = 0xFF;                              /* 0xFF=dp灭 0x7F=dp亮 */

  48. /*------------------------- 全局变量 -------------------------*/
  49. volatile u8 tick2ms = 0;                        /* 2ms节拍(按键用) */
  50. volatile u8 rd_tick = 0;                        /* 250ms节拍(读时钟用) */

  51. u8 hh = 12, mm = 0, ss = 0, last_ss = 0xFF;     /* 当前时间 */
  52. u8 alarm_h = 7, alarm_m = 0;                    /* 闹钟时间(存DS1302 RAM) */
  53. bit  alarm_on = 0;                              /* 闹钟使能 */
  54. bit  ring_on = 0, ring_done = 0;                /* 响铃状态/本次已触发 */
  55. u8   ring_sec = 0;

  56. u8   mode = 0;                                  /* 0正常 1闹钟设置 */
  57. u16  mode_idle = 0;                             /* 设置模式无操作计时 */
  58. bit  dp_blink = 0;
  59. u8   blink_t = 0;

  60. u8   k1_db = 0, k2_db = 0;                      /* 按键按下计时(5ms单位) */
  61. bit  k1_long = 0, k2_long = 0, combo = 0;

  62. bit  voice_req = 0, voice_alarm = 0;            /* 语音请求/是否闹钟播报 */
  63. u8   vbuf[36];
  64. u8   vlen;

  65. /*========================= DS1302 驱动 =========================*/
  66. void ds_write_byte(u8 dat)
  67. {
  68.     u8 i;
  69.     for (i = 0; i < 8; i++) {
  70.         DS_SCLK = 0;
  71.         DS_IO = dat & 0x01;
  72.         dat >>= 1;
  73.         _nop_(); _nop_(); _nop_(); _nop_();
  74.         DS_SCLK = 1;
  75.         _nop_(); _nop_(); _nop_(); _nop_();
  76.     }
  77. }

  78. u8 ds_read_byte(void)
  79. {
  80.     u8 i, dat = 0;
  81.     DS_IO = 1;                          /* 准双向口,先写1再读 */
  82.     for (i = 0; i < 8; i++) {
  83.         DS_SCLK = 1;
  84.         DS_SCLK = 0;                    /* 下降沿后DS1302输出数据 */
  85.         _nop_(); _nop_(); _nop_(); _nop_();
  86.         dat >>= 1;
  87.         if (DS_IO) dat |= 0x80;
  88.     }
  89.     return dat;
  90. }

  91. void ds_write(u8 reg, u8 dat)
  92. {
  93.     DS_CE = 0; DS_SCLK = 0; DS_CE = 1;
  94.     ds_write_byte(reg);
  95.     ds_write_byte(dat);
  96.     DS_CE = 0;
  97. }

  98. u8 ds_read(u8 reg)
  99. {
  100.     u8 dat;
  101.     DS_CE = 0; DS_SCLK = 0; DS_CE = 1;
  102.     ds_write_byte(reg | 0x01);
  103.     dat = ds_read_byte();
  104.     DS_CE = 0;
  105.     return dat;
  106. }

  107. u8 dec_to_bcd(u8 v) { return (v / 10) << 4 | v % 10; }
  108. u8 bcd_to_dec(u8 v) { return (v >> 4) * 10 + (v & 0x0F); }

  109. /* 初始化:关写保护;时钟停振或数据无效则写默认12:00:00(24小时制) */
  110. void ds1302_init(void)
  111. {
  112.     u8 s, h, m;
  113.     DS_CE = 0; DS_SCLK = 0;
  114.     ds_write(0x8E, 0x00);
  115.     s = ds_read(0x81);
  116.     h = ds_read(0x85);
  117.     m = ds_read(0x83);
  118.     if ((s & 0x80) || (h & 0x80) ||
  119.         (h & 0x3F) > 0x23 || (m & 0x7F) > 0x59) {
  120.         ds_write(0x84, 0x12);
  121.         ds_write(0x82, 0x00);
  122.         ds_write(0x80, 0x00);           /* CH=0 启动时钟 */
  123.     }
  124. }

  125. void read_time(void)
  126. {
  127.     ss = bcd_to_dec(ds_read(0x81) & 0x7F);
  128.     mm = bcd_to_dec(ds_read(0x83) & 0x7F);
  129.     hh = bcd_to_dec(ds_read(0x85) & 0x3F);
  130. }

  131. /* 闹钟数据存于DS1302内部RAM,掉电不丢失 */
  132. void alarm_save(void)
  133. {
  134.     ds_write(0xC0, alarm_h);
  135.     ds_write(0xC2, alarm_m);
  136.     ds_write(0xC4, (u8)alarm_on);
  137. }

  138. void alarm_load(void)
  139. {
  140.     alarm_h  = ds_read(0xC1);
  141.     alarm_m  = ds_read(0xC3);
  142.     alarm_on = ds_read(0xC5) ? 1 : 0;
  143.     if (alarm_h > 23 || alarm_m > 59) {         /* RAM无效则给默认值 */
  144.         alarm_h = 7; alarm_m = 0; alarm_on = 0;
  145.         alarm_save();
  146.     }
  147. }

  148. /*========================= 语音播报 ==========================
  149. * SYN6288兼容: 0xFD + 长度高 + 长度低 + GBK文本 + 异或校验, 9600bps
  150. * 其他语音板只需改此文件中 T_xxx 文本与 voice_send() 发送格式 */
  151. u8 code T_NOW[]   = {0xCF,0xD6,0xD4,0xDA,0xCA,0xB1,0xBC,0xE4};           /* 现在时间 */
  152. u8 code T_ALARM[] = {0xC4,0xD6,0xD6,0xD3,0xCA,0xB1,0xBC,0xE4,0xB5,0xBD}; /* 闹钟时间到 */
  153. u8 code T_COMMA[] = {0xA3,0xAC};                                         /* ,     */
  154. u8 code T_DIAN[]  = {0xB5,0xE3};                                         /* 点     */
  155. u8 code T_FEN[]   = {0xB7,0xD6};                                         /* 分     */
  156. u8 code T_ZHENG[] = {0xD5,0xFB};                                         /* 整     */

  157. void vpush(u8 code *p, u8 n)
  158. {
  159.     while (n--) vbuf[vlen++] = *p++;
  160. }

  161. void vnum(u8 v)                         /* 数字转ASCII,不补零 */
  162. {
  163.     if (v >= 10) vbuf[vlen++] = '0' + v / 10;
  164.     vbuf[vlen++] = '0' + v % 10;
  165. }

  166. void voice_build(u8 is_alarm)
  167. {
  168.     vlen = 0;
  169.     if (is_alarm) {
  170.         vpush(T_ALARM, sizeof(T_ALARM));
  171.         vpush(T_COMMA, sizeof(T_COMMA));
  172.     }
  173.     vpush(T_NOW, sizeof(T_NOW));
  174.     vnum(hh);
  175.     vpush(T_DIAN, sizeof(T_DIAN));
  176.     vnum(mm);
  177.     vpush(mm ? T_FEN : T_ZHENG, sizeof(T_FEN));
  178. }

  179. void voice_send(void)
  180. {
  181.     u8 i, chk;
  182.     chk = 0 ^ vlen;                     /* 长度高字节为0 */
  183.     TI = 0; SBUF = 0xFD; while (!TI);
  184.     TI = 0; SBUF = 0;    while (!TI);
  185.     TI = 0; SBUF = vlen; while (!TI);
  186.     for (i = 0; i < vlen; i++) {
  187.         chk ^= vbuf[i];
  188.         TI = 0; SBUF = vbuf[i]; while (!TI);
  189.     }
  190.     TI = 0; SBUF = chk; while (!TI);
  191. }

  192. /*========================= 显示与节拍 =========================*/
  193. void Timer0_ISR(void) interrupt 1
  194. {
  195.     static u8 pos = 0;
  196.     TH0 = 0xF8; TL0 = 0xCC;             /* 2ms @11.0592M 12T */
  197.     P3 |= 0x3C;                         /* 关全部位选(P3.2-P3.5) */
  198.     P2 = 0xFF;                          /* 消隐 */
  199.     P2 = disp_buf[pos] & dp_mask;
  200.     P3 &= ~DIG_SEL[pos];                /* 开当前位 */
  201.     pos = (pos + 1) & 0x03;
  202.     tick2ms++;
  203.     rd_tick++;
  204. }

  205. void update_display(void)
  206. {
  207.     u8 h, m;
  208.     if (mode == 0) { h = hh; m = mm; }
  209.     else           { h = alarm_h; m = alarm_m; }
  210.     disp_buf[0] = SEG_TAB[h / 10];
  211.     disp_buf[1] = SEG_TAB[h % 10];
  212.     disp_buf[2] = SEG_TAB[m / 10];
  213.     disp_buf[3] = SEG_TAB[m % 10];
  214. }

  215. void ring_off(void)
  216. {
  217.     ring_on = 0;
  218.     BUZZER = 1;
  219. }

  220. /*========================= 按键处理 ==========================*/
  221. void key_scan(void)                     /* 每5ms调用一次, 0=按下 */
  222. {
  223.     if (mode && (!KEY_S1 || !KEY_S2)) mode_idle = 0;

  224.     /* 同时按住两键>100ms: 语音报当前时间 */
  225.     if (!KEY_S1 && !KEY_S2) {
  226.         if (k1_db >= 20 && k2_db >= 20 && !combo && !k1_long && !k2_long) {
  227.             combo = 1;
  228.             if (ring_on) ring_off();
  229.             voice_req = 1;
  230.             voice_alarm = 0;
  231.         }
  232.     } else if (KEY_S1 && KEY_S2) {
  233.         combo = 0;
  234.     }

  235.     /* ---- S1 ---- */
  236.     if (!KEY_S1) {
  237.         if (k1_db < 250) k1_db++;
  238.         if (k1_db == KEY_LONG && !combo && !k1_long) {
  239.             k1_long = 1;
  240.             if (ring_on)       ring_off();
  241.             else if (mode == 0) { mode = 1; mode_idle = 0; }        /* 进设置 */
  242.             else { alarm_save(); alarm_on = 1; mode = 0; }          /* 确认退出 */
  243.         }
  244.     } else {
  245.         if (k1_db >= 4 && k1_db < KEY_LONG && !combo && !k1_long) { /* 短按 */
  246.             if (ring_on) ring_off();
  247.             else if (mode == 0) {
  248.                 hh++; if (hh > 23) hh = 0;
  249.                 ds_write(0x84, dec_to_bcd(hh));
  250.             } else {
  251.                 alarm_h++; if (alarm_h > 23) alarm_h = 0;
  252.             }
  253.         }
  254.         k1_db = 0; k1_long = 0;
  255.     }

  256.     /* ---- S2 ---- */
  257.     if (!KEY_S2) {
  258.         if (k2_db < 250) k2_db++;
  259.         if (k2_db == KEY_LONG && !combo && !k2_long) {
  260.             k2_long = 1;
  261.             if (mode == 0) {
  262.                 alarm_on = !alarm_on;   /* 长按S2: 闹钟开/关 */
  263.                 alarm_save();
  264.             } else {
  265.                 mode = 0;               /* 设置模式长按S2: 取消退出 */
  266.             }
  267.         }
  268.     } else {
  269.         if (k2_db >= 4 && k2_db < KEY_LONG && !combo && !k2_long) { /* 短按 */
  270.             if (ring_on) ring_off();
  271.             else if (mode == 0) {
  272.                 mm++; if (mm > 59) mm = 0;
  273.                 ds_write(0x82, dec_to_bcd(mm));
  274.                 ds_write(0x80, 0x00);   /* 秒清零,便于对准 */
  275.             } else {
  276.                 alarm_m++; if (alarm_m > 59) alarm_m = 0;
  277.             }
  278.         }
  279.         k2_db = 0; k2_long = 0;
  280.     }
  281. }

  282. /*============================= 主程序 =============================*/
  283. void main(void)
  284. {
  285.     P1 = 0xFF; P2 = 0xFF; P3 = 0xFF;    /* 全部置高(蜂鸣器不响,显示消隐) */

  286.     /* T0: 方式1,12T,2ms中断,用于扫描与节拍 */
  287.     TMOD &= 0xF0;
  288.     TH0 = 0xF8; TL0 = 0xCC;
  289.     ET0 = 1; TR0 = 1;

  290.     /* T1: 方式2,12T,作UART波特率发生器 9600bps @11.0592M */
  291.     TMOD &= 0x0F;
  292.     TMOD |= 0x20;
  293.     TH1 = 0xFD; TL1 = 0xFD;
  294.     SCON = 0x40;                        /* 方式1,不开接收 */
  295.     TR1 = 1;

  296.     ds1302_init();
  297.     alarm_load();
  298.     read_time();
  299.     update_display();
  300.     EA = 1;

  301.     while (1) {
  302.         /*---- 250ms: 读时钟/闹钟判断 ----*/
  303.         if (rd_tick >= 125) {
  304.             rd_tick = 0;
  305.             read_time();
  306.             if (ss != last_ss) {                /* 每秒事件 */
  307.                 last_ss = ss;
  308.                 if (ring_on && ++ring_sec >= 60) ring_off();
  309.             }
  310.             if (alarm_on && !ring_on) {
  311.                 if (hh == alarm_h && mm == alarm_m && ss < 2 && !ring_done) {
  312.                     ring_done = 1;              /* 触发闹钟 */
  313.                     ring_on = 1;
  314.                     BUZZER = 0;
  315.                     ring_sec = 0;
  316.                     voice_req = 1;
  317.                     voice_alarm = 1;
  318.                 }
  319.             } else {
  320.                 if (hh != alarm_h || mm != alarm_m) ring_done = 0;
  321.             }
  322.         }

  323.         /*---- 5ms: 按键/设置超时/小数点闪烁 ----*/
  324.         if (tick2ms >= 5) {
  325.             tick2ms -= 5;
  326.             key_scan();
  327.             if (mode) {                          /* 设置模式10秒无操作退出 */
  328.                 if (++mode_idle >= 2000) { mode = 0; mode_idle = 0; }
  329.             } else {
  330.                 mode_idle = 0;
  331.             }
  332.             if (++blink_t >= 50) {               /* 250ms翻转 */
  333.                 blink_t = 0;
  334.                 dp_blink = !dp_blink;
  335.             }
  336.             if (mode) dp_mask = dp_blink ? 0x7F : 0xFF;
  337.             else      dp_mask = alarm_on ? 0x7F : 0xFF;
  338.         }

  339.         /*---- 语音发送(须等两键都释放,因为按键与串口复用) ----*/
  340.         if (voice_req && KEY_S1 && KEY_S2) {
  341.             voice_build(voice_alarm);
  342.             voice_send();
  343.             voice_req = 0;
  344.         }

  345.         update_display();
  346.     }
  347. }
复制代码

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶顶 踩踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表