找回密码
 立即注册
楼主: yuyy1989

冲哥32位8051视频学习日记-已看到27集-实验箱到了

[复制链接]
  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-14 16:21:32 | 显示全部楼层
第15集,外部中断
1.中断和中断系统
手册里介绍了什么是中断和中断系统
QQ截图20230514145001.png

支持的中断源
QQ截图20230514145130.png QQ截图20230514145154.png QQ截图20230514145207.png

2.外部中断
外部中断就是在单片机的一个引脚上,由于外部因素导致了一个电平的变化 (比如由高变低),而通过捕获这个变化,单片机内部自主运行的程序就会被暂时打断,转面去执行相应的中断处理程序,执行完后又回到原来中断的地方继续执行原来的程序。
INT0-INT4的中断号
QQ截图20230514152831.png

测试例程
  1. #include "config.h"
  2. //USB调试及复位所需定义
  3. char *USER_DEVICEDESC = NULL;
  4. char *USER_PRODUCTDESC = NULL;
  5. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  6. void test_int0(void) interrupt 0 //INT0 P3.2可触发
  7. {
  8.     P20 = !P20;
  9. }
  10. void test_int1(void) interrupt 2 //INT1 P3.3可触发
  11. {
  12.     P21 = !P21;
  13. }
  14. void test_int2(void) interrupt 10 //INT2 P3.6可触发
  15. {
  16.     P22 = !P22;
  17. }
  18. void test_int3(void) interrupt 11 //INT3 P3.7可触发
  19. {
  20.     P23 = !P23;
  21. }
  22. void test_int4(void) interrupt 16 //INT4 P3.0可触发
  23. {
  24.     P24 = !P24;
  25. }
  26. /******************** 主函数 **************************/
  27. void main(void)
  28. {
  29.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  30.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  31.     CKCON = 0; //提高访问XRAM速度
  32.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  33.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  34.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  35.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  36.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  37.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  38.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  39.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  40.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  41.     //USB调试及复位所需代码-----
  42.     P3M0 &= ~0x03;
  43.     P3M1 |= 0x03;
  44.     IRC48MCR = 0x80;
  45.     while (!(IRC48MCR & 0x01));
  46.     usb_init();
  47.     //-------------------------
  48.     IE0  = 0;   //外中断0标志位
  49.     IE1  = 0;   //外中断1标志位
  50.     EX0 = 1;    //INT0 使能
  51.     EX1 = 1;    //INT1 使能
  52.     EX2 = 1;    //INT2 使能
  53.     EX3 = 1;    //INT3 使能
  54.     EX4 = 1;    //INT4 使能
  55.     IT0 = 1;        //INT0 下降沿中断      
  56.     IT1 = 1;        //INT1 下降沿中断      
  57.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  58.     EA = 1;     //打开总中断
  59.     LED0 = 1;
  60.    
  61.     printf_hid("STC32G 测试程序\r\n");
  62.     while(1)
  63.     {
  64.     }
  65. }
复制代码
在外部触发某个需要及时处理的事件时又不能频繁轮询IO状态时需要用到外部中断,例如一些使用电池供电的设备处理按键或者获取传感器数据,由于低功耗的要求不能像以前课程里那样每10ms查询一次IO状态



回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-16 16:49:43 | 显示全部楼层
第16集,IO中断

QQ截图20230514163808.png

外部中断只有指定的几个IO口可以触发,IO中断所有的IO都能触发,屠龙刀三.2可以使用下降沿中断

Keil的中断号有限制,可以借助工具扩展Keil的中断号

QQ截图20230516151434.png

IO中断配置需要用到的寄存器
QQ截图20230514163857.png QQ截图20230514171612.png

各端口使用的中断号
QQ截图20230516152338.png

演示代码,没有数码管只能使用虚拟数码管

  1. uint16_t timercount = 0;
  2. uint8_t seg0num = 0;
  3. uint8_t seg1num = 0;
  4. void test_usbseg()
  5. {
  6.     uint8_t segdatas[8]={0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40};
  7.     segdatas[0] = ledsegcodes[seg0num];
  8.     segdatas[1] = ledsegcodes[seg1num];
  9.     SEG7_ShowCode(segdatas);
  10. }
  11. void test_timer0_cb(void)
  12. {
  13.     if(timercount < 50)
  14.     {
  15.         timercount++;
  16.     }
  17.     else
  18.     {
  19.         timercount = 0;
  20.         test_usbseg();
  21.     }
  22. }
  23. void test_P3int(void) interrupt 40 //P3端口IO中断
  24. {
  25.     uint8_t intf;
  26.     intf = P3INTF;
  27.     if(intf)
  28.     {
  29.         P3INTF = 0;//清除中断标识
  30.         if(intf&0x20) //P35产生中断
  31.         {
  32.             seg0num++;
  33.             if(seg0num > 9)
  34.                 seg0num = 0;
  35.         }
  36.     }
  37. }
  38. /******************** 主函数 **************************/
  39. void main(void)
  40. {
  41.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  42.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  43.     CKCON = 0; //提高访问XRAM速度
  44.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  45.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  46.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  47.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  48.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  49.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  50.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  51.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  52.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  53.     //USB调试及复位所需代码-----
  54.     P3M0 &= ~0x03;
  55.     P3M1 |= 0x03;
  56.     IRC48MCR = 0x80;
  57.     while (!(IRC48MCR & 0x01));
  58.     usb_init();
  59.     //-------------------------
  60.     P3IM0 = 0x00;
  61.     P3IM1 = 0x00; //P35下降沿中断,屠龙刀三.2可以使用下降沿中断
  62.     P3INTE = 0x20; //P35中断使能
  63.     P5IM0 = 0x00;
  64.     P5IM1 = 0x00;//P54下降沿中断,屠龙刀三.2可以使用下降沿中断
  65.     P5INTE = 0x10;//P54中断使能
  66.     PIN_IPH = 0x00;
  67.     PIN_IP = 0x20; //P5端口设置优先级1
  68.    
  69.     yuyy_timer0init(10000,test_timer0_cb); //10ms定时
  70.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  71.     EA = 1;     //打开总中断
  72.     while(1)
  73.     {
  74.     }
  75. }
复制代码


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-17 10:43:02 | 显示全部楼层
第17集,模数转换器ADC
模数转换器即 A/D 转换器,或简称 ADC(Analog-to-digital converter),通常是指一个将模拟信号转变为数字信号的电子元件。
各通道对应IO
QQ截图20230516224947.png

计算公式
QQ截图20230517102744.png

QQ截图20230517102757.png

演示代码
  1. //USB调试及复位所需定义
  2. char *USER_DEVICEDESC = NULL;
  3. char *USER_PRODUCTDESC = NULL;
  4. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  5. void delay_ms(uint16_t ms)
  6. {
  7.     uint16_t i;
  8.     do{
  9.         i = MAIN_Fosc / 6000;
  10.         while(--i);   //6T per loop
  11.     }while(--ms);
  12. }
  13. /******************** 主函数 **************************/
  14. void main(void)
  15. {
  16.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  17.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  18.     CKCON = 0; //提高访问XRAM速度
  19.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  20.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  21.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  22.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  23.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  24.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  25.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  26.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  27.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  28.     //USB调试及复位所需代码-----
  29.     P3M0 &= ~0x03;
  30.     P3M1 |= 0x03;
  31.     IRC48MCR = 0x80;
  32.     while (!(IRC48MCR & 0x01));
  33.     usb_init();
  34.     //-------------------------
  35.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  36.     EA = 1;     //打开总中断
  37.     P1M0 = 0x00;
  38.     P1M1 = 0x01;   //P1.0高阻输入,作为ADC的引脚必须设置为高阻输入
  39.     ADCTIM = 0x3F; //设置ADC内部时序
  40.     ADCCFG = 0x2F; //结果右对齐,设置ADC时钟为系统时钟/2/16
  41.     ADC_POWER = 1; //使能ADC模块
  42.     ADC_CONTR &= 0xF0; //选择通道0;
  43.     while(1)
  44.     {
  45.         uint16_t adcresult;
  46.         float adcv;
  47.         ADC_START = 1;//启动ADC转换
  48.         _nop_();
  49.         _nop_();
  50.         _nop_();
  51.         while(ADC_FLAG == 0);   //wait for ADC finish
  52.         ADC_FLAG = 0;     //清除ADC结束标志
  53.         adcresult = (ADC_RES<<8)&0x0F00;
  54.         adcresult |= ADC_RESL;
  55.         adcv = 5.0*adcresult/4096; //计算为电压
  56.         printf_hid("ADC结果: 0x%03X ADC电压: %.2fV\r\n",adcresult,adcv);
  57.         delay_ms(1000);
  58.     }
  59. }
复制代码
没有2.5v的电压源,通过杜邦线将Vref连接到VCC,P1.0接一个从USB转串口引出来的3.3v,与屠龙刀开发板共地,运行截图
QQ截图20230517103349.png

比万用表测得电压高0.08V左右

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-17 20:41:40 | 显示全部楼层
第18集,ADC采集电源电压和ADC按键上
利用ADC测量VCC供电电压
演示代码
  1. #define VREFH_ADDR CHIPID7
  2. #define VREFL_ADDR CHIPID8
  3. bit busy;
  4. int BGV;
  5. //USB调试及复位所需定义
  6. char *USER_DEVICEDESC = NULL;
  7. char *USER_PRODUCTDESC = NULL;
  8. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  9. void delay_ms(uint16_t ms)
  10. {
  11.     uint16_t i;
  12.     do{
  13.         i = MAIN_Fosc / 6000;
  14.         while(--i);   //6T per loop
  15.     }while(--ms);
  16. }
  17. void ADCInit()
  18. {
  19.     ADCTIM = 0x3F;   //设置 ADC 内部时序
  20.     ADCCFG = 0x2F;   //结果右对齐,设置 ADC 时钟为系统时钟/2/16
  21.     ADC_CONTR = 0x8F; //使能 ADC 模块,并选择第 15 通道
  22. }
  23. int ADCRead()
  24. {
  25.     int res;
  26.     ADC_START = 1; //启动 AD 转换
  27.     _nop_();
  28.     _nop_();
  29.     while (!ADC_FLAG); //查询 ADC 完成标志
  30.     ADC_FLAG = 0; //清完成标志
  31.     res = (ADC_RES << 8) | ADC_RESL; //读取 ADC 结果
  32.     return res;
  33. }
  34. /******************** 主函数 **************************/
  35. void main(void)
  36. {
  37.     int res;
  38.     int vcc;
  39.     int i;
  40.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  41.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  42.     CKCON = 0; //提高访问XRAM速度
  43.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  44.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  45.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  46.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  47.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  48.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  49.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  50.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  51.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  52.     //USB调试及复位所需代码-----
  53.     P3M0 &= ~0x03;
  54.     P3M1 |= 0x03;
  55.     IRC48MCR = 0x80;
  56.     while (!(IRC48MCR & 0x01));
  57.     usb_init();
  58.     //-------------------------
  59.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  60.     EA = 1;     //打开总中断
  61.     BGV = (VREFH_ADDR << 8) + VREFL_ADDR; //从 CHIPID 中读取内部参考电压值
  62.     ADCInit(); //ADC 初始化
  63.     ADCRead();
  64.     ADCRead();//前两个数据建议丢弃
  65.     while(1)
  66.     {
  67.         res = 0;
  68.         for (i=0; i<8; i++)
  69.         {
  70.             res += ADCRead(); //读取 8 次数据
  71.         }
  72.         res >>= 3; //取平均值
  73.         vcc = (int)(4096L * BGV / res); //(12 位 ADC 算法)计算 VREF 管脚电压,即电池电压 注意,此电压的单位为毫伏(mV)
  74.         printf_hid("VCC电压:%dmv\r\n",vcc);
  75.         delay_ms(1000);
  76.     }
  77. }
复制代码
Vref连接到VCC,USB供电,测量结果

QQ截图20230517142346.png


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-18 17:40:21 | 显示全部楼层
第18集,ADC采集电源电压和ADC按键中
ADC按键,按下按键时,通过电阻分压得到不同的电压值,ADC采集在各个范围内的值来判定是哪个按键按下
优点是只需要一个IO就能实现多个按键的识别
缺点是识别精度差,不能识别同时按下多个按键,程序要一直保持扫描来识别按键按下
示例代码,和之前的普通按键和矩阵按键集成到一起了,因为没有ADC按键所以ADC按键部分未经验证
yuyy_key.h
  1. #ifndef __YUYY_KEY_H_
  2. #define __YUYY_KEY_H_
  3. #include "config.h"
  4. #define KEYSCANINTERVAL 10//按键扫描间隔
  5. #define DEBOUNCETIME 30 //去抖时间
  6. #define LONGPRESSTIME 2000 //长按识别时间
  7. //普通按键
  8. #define GPIOKEYNUMS 4 //按键总数
  9. #define KEYPORT P3 //按键io端口号
  10. #define KEYIOSTART 2 //按键io起始值 屠龙刀的按键从P3.2开始
  11. #define KEYDOWNLEV 0 //按键按下的io电平
  12. //矩阵按键
  13. #define MATRIXKEYCOLS 4 //按键列数
  14. #define MATRIXKEYROWS 2 //按键行数
  15. #define MATRIXKEYNUMS MATRIXKEYCOLS*MATRIXKEYROWS //矩阵按键总数
  16. /**
  17. *按键分布示例
  18. *     P00 P01 P02 P03
  19. * P06  0   1   2   3
  20. * P07  4   5   6   7   
  21. */
  22. #define MATRIXKEYROW0PIN P06 //第0行IO
  23. #define MATRIXKEYROW1PIN P07 //第1行IO
  24. #define MATRIXKEYCOL0PIN P00 //第0列IO
  25. #define MATRIXKEYCOL1PIN P01 //第1列IO
  26. #define MATRIXKEYCOL2PIN P02 //第2列IO
  27. #define MATRIXKEYCOL3PIN P03 //第3列IO
  28. //ADC按键
  29. #define ADCKEYNUMS 16  //ADC按键总数
  30. #define ADCKEYPIN P10 //ADC按键io
  31. #define ADCKEYCHANNEL 0//
  32. #define ADCKEYOFFSET 64//允许的误差值
  33. enum YUYY_KEYSTATE
  34. {
  35.     KEY_UP = 0,      //按键未按下
  36.     KEY_DOWN,        //按键按下
  37.     KEY_SINGLECLICK, //单击按键 按下按键后在LONGPRESSTIME之前松开
  38.     KEY_LONGPRESS,   //长按按键 按下按键的时间超过LONGPRESSTIME
  39.     KEY_LONGPRESSUP  //长按后松开
  40. };
  41. enum YUYY_KEYTYPE
  42. {
  43.     KEYTYPE_GPIO = 0,      //普通按键
  44.     KEYTYPE_MATRIX,        //矩阵按键
  45.     KEYTYPE_ADC,           //ADC按键
  46. };
  47. //定义回调
  48. typedef void (*yuyy_key_cb)(enum YUYY_KEYTYPE keytype,uint8_t keynum,enum YUYY_KEYSTATE state);
  49. /**
  50. * 初始化按键,设置回调函数
  51. * 外部文件通过回调函数监控按键状态
  52. */
  53. void yuyy_keyinit(yuyy_key_cb cb);
  54. /**
  55. * 10ms一次检查按键状态
  56. */
  57. void yuyy_keyloop(void);
  58. #endif
复制代码
yuyy_key.c
  1. #include "yuyy_key.h"
  2. #include "yuyy_delay.h"
  3. #if(GPIOKEYNUMS)
  4. uint16_t gpiokeydowncount[GPIOKEYNUMS] = {0};
  5. #else
  6. uint16_t gpiokeydowncount[1] = {0};
  7. #endif
  8. #if(MATRIXKEYNUMS)
  9. uint16_t matrixkeydowncount[MATRIXKEYNUMS] = {0};
  10. #else
  11. uint16_t matrixkeydowncount[1] = {0};
  12. #endif
  13. #if(ADCKEYNUMS)
  14. uint16_t adckeydowncount[ADCKEYNUMS] = {0};
  15. #else
  16. uint16_t adckeydowncount[1] = {0};
  17. #endif
  18. uint16_t adcperkey;
  19. yuyy_key_cb keycb;
  20. void sendkeystate(enum YUYY_KEYTYPE keytype,uint8_t keynum,enum YUYY_KEYSTATE state)
  21. {
  22.     if(keycb)
  23.     {
  24.         keycb(keytype,keynum,state);
  25.     }
  26. }
  27. void yuyy_refreshkey(enum YUYY_KEYTYPE keytype,uint8_t key,uint8_t down)
  28. {
  29.     uint16_t *keydowncount;
  30.     if(keytype == KEYTYPE_GPIO)
  31.     {
  32.         keydowncount = gpiokeydowncount;
  33.     }
  34.     else if(keytype == KEYTYPE_MATRIX)
  35.     {
  36.         keydowncount = matrixkeydowncount;
  37.     }
  38.     else if(keytype == KEYTYPE_ADC)
  39.     {
  40.         keydowncount = adckeydowncount;
  41.     }
  42.     else
  43.     {
  44.         return;
  45.     }
  46.     if(down)
  47.     {
  48.         if(keydowncount[key] < 0xFFFF)
  49.         {
  50.             keydowncount[key]++;
  51.         }
  52.         if(keydowncount[key]*KEYSCANINTERVAL == DEBOUNCETIME)
  53.         {
  54.             sendkeystate(keytype,key,KEY_DOWN);
  55.         }
  56.         if(keydowncount[key]*KEYSCANINTERVAL == LONGPRESSTIME)
  57.         {
  58.             sendkeystate(keytype,key,KEY_LONGPRESS);
  59.         }
  60.     }
  61.     else
  62.     {
  63.         if(keydowncount[key]*KEYSCANINTERVAL > DEBOUNCETIME)
  64.         {
  65.             if(keydowncount[key]*KEYSCANINTERVAL < LONGPRESSTIME)
  66.             {
  67.                 sendkeystate(keytype,key,KEY_SINGLECLICK);
  68.             }
  69.             else
  70.             {
  71.                 sendkeystate(keytype,key,KEY_LONGPRESSUP);
  72.             }
  73.         }
  74.         keydowncount[key] = 0;
  75.     }
  76. }
  77. void yuyy_gpiokeyloop(void)
  78. {
  79.     uint8_t key = 0;
  80.     while (key<GPIOKEYNUMS)
  81.     {
  82.         if(((KEYPORT >> (key+KEYIOSTART)) & 0x01) == KEYDOWNLEV)
  83.         {
  84.             yuyy_refreshkey(KEYTYPE_GPIO,key,1);
  85.         }
  86.         else
  87.         {
  88.             yuyy_refreshkey(KEYTYPE_GPIO,key,0);
  89.         }
  90.         key++;
  91.     }
  92. }
  93. void yuyy_matrixkeyloop(void)
  94. {
  95.     uint8_t key = 0;
  96.     uint8_t i,j,col = 0,row = 0;
  97.     MATRIXKEYROW0PIN = 1;
  98.     MATRIXKEYROW1PIN = 1;
  99.     MATRIXKEYCOL0PIN = 0;
  100.     MATRIXKEYCOL1PIN = 0;
  101.     MATRIXKEYCOL2PIN = 0;
  102.     MATRIXKEYCOL3PIN = 0;
  103.     yuyy_delay_us(10);
  104.     if (MATRIXKEYROW0PIN == 0)
  105.     {
  106.         row |= 0x01;
  107.     }
  108.     if (MATRIXKEYROW1PIN == 0)
  109.     {
  110.         row |= 0x02;
  111.     }
  112.     MATRIXKEYROW0PIN = 0;
  113.     MATRIXKEYROW1PIN = 0;
  114.     MATRIXKEYCOL0PIN = 1;
  115.     MATRIXKEYCOL1PIN = 1;
  116.     MATRIXKEYCOL2PIN = 1;
  117.     MATRIXKEYCOL3PIN = 1;
  118.     yuyy_delay_us(10);
  119.     if (MATRIXKEYCOL0PIN == 0)
  120.     {
  121.         col |= 0x01;
  122.     }
  123.     if (MATRIXKEYCOL1PIN == 0)
  124.     {
  125.         col |= 0x02;
  126.     }
  127.     if (MATRIXKEYCOL2PIN == 0)
  128.     {
  129.         col |= 0x04;
  130.     }
  131.     if (MATRIXKEYCOL3PIN == 0)
  132.     {
  133.         col |= 0x08;
  134.     }
  135.     MATRIXKEYROW0PIN = 1;
  136.     MATRIXKEYROW1PIN = 1;
  137.     MATRIXKEYCOL0PIN = 1;
  138.     MATRIXKEYCOL1PIN = 1;
  139.     MATRIXKEYCOL2PIN = 1;
  140.     MATRIXKEYCOL3PIN = 1;
  141.     for(i=0;i<MATRIXKEYROWS;i++)
  142.     {
  143.         for (j = 0; j < MATRIXKEYCOLS; j++)
  144.         {
  145.             if((row&(1<<i)) && (col&(1<<j)))
  146.             {
  147.                 yuyy_refreshkey(KEYTYPE_MATRIX,key,1);
  148.             }
  149.             else
  150.             {
  151.                 yuyy_refreshkey(KEYTYPE_MATRIX,key,0);
  152.             }
  153.             key++;
  154.         }
  155.         
  156.     }
  157. }
  158. uint16_t yuyy_readadc(uint8_t channel)
  159. {
  160.     uint16_t res;
  161.     ADC_CONTR &= 0xF0;
  162.     ADC_CONTR |= (channel&0x0F);
  163.     ADC_START = 1; //启动 AD 转换
  164.     _nop_();
  165.     _nop_();
  166.     while (!ADC_FLAG); //查询 ADC 完成标志
  167.     ADC_FLAG = 0; //清完成标志
  168.     res = ((ADC_RES << 8)&0x0F00) | ADC_RESL; //读取 ADC 结果
  169.     return res;
  170. }
  171. void yuyy_adcKeyloop(void)
  172. {
  173.     uint16_t keyadc,key = 0,adccmp = adcperkey;
  174.     keyadc = yuyy_readadc(ADCKEYCHANNEL);
  175.     while (key < ADCKEYNUMS)
  176.     {
  177.         if(keyadc>=adccmp-ADCKEYOFFSET && keyadc<adccmp+ADCKEYOFFSET)
  178.         {
  179.             //按键按下
  180.             yuyy_refreshkey(KEYTYPE_ADC,key,1);
  181.         }
  182.         else
  183.         {
  184.             //按键未按下
  185.             yuyy_refreshkey(KEYTYPE_ADC,key,0);
  186.         }
  187.         key++;
  188.         adccmp += adcperkey;
  189.     }
  190. }
  191. void yuyy_keyloop(void)
  192. {
  193.     #if(GPIOKEYNUMS)
  194.     yuyy_gpiokeyloop();
  195.     #endif
  196.     #if(MATRIXKEYNUMS)
  197.     yuyy_matrixkeyloop();
  198.     #endif
  199.     #if(ADCKEYNUMS)
  200.     yuyy_adcKeyloop();
  201.     #endif
  202. }
  203. void yuyy_adcKeyinit(void)
  204. {
  205.     P1M0 = 0x00;
  206.     P1M1 = 0x01;   //P1.0高阻输入,作为ADC的引脚必须设置为高阻输入
  207.     ADCTIM = 0x3F; //设置ADC内部时序
  208.     ADCCFG = 0x2F; //结果右对齐,设置ADC时钟为系统时钟/2/16
  209.     ADC_POWER = 1;
  210.     adcperkey = 4096/ADCKEYNUMS;
  211. }
  212. void yuyy_keyinit(yuyy_key_cb cb)
  213. {
  214.     #if(ADCKEYNUMS)
  215.     yuyy_adcKeyinit();
  216.     #endif
  217.     keycb = cb;
  218. }
复制代码
main.c
  1. #include "config.h"
  2. #include "string.h"
  3. #include "yuyy.h"
  4. //USB调试及复位所需定义
  5. char *USER_DEVICEDESC = NULL;
  6. char *USER_PRODUCTDESC = NULL;
  7. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  8. void test_timer0cb(void)
  9. {
  10.     yuyy_keyloop();
  11. }
  12. void test_keycb(enum YUYY_KEYTYPE keytype,uint8_t keynum,enum YUYY_KEYSTATE state)
  13. {
  14.     printf("按键类型:%d 按键号:%d 按键行为:%d\r\n",keytype,keynum,state);
  15. }
  16. /******************** 主函数 **************************/
  17. void main(void)
  18. {
  19.     int res;
  20.     int vcc;
  21.     int i;
  22.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  23.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  24.     CKCON = 0; //提高访问XRAM速度
  25.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  26.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  27.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  28.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  29.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  30.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  31.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  32.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  33.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  34.     //USB调试及复位所需代码-----
  35.     P3M0 &= ~0x03;
  36.     P3M1 |= 0x03;
  37.     IRC48MCR = 0x80;
  38.     while (!(IRC48MCR & 0x01));
  39.     //如果使用USB-CDC需要下面的两行代码
  40.     USBCLK = 0x00;
  41.     USBCON = 0x90;
  42.     //如果使用USB-HID注释掉上面两行代码
  43.     usb_init();
  44.     //-------------------------
  45.     yuyy_timer0init(10000,test_timer0cb); //10ms触发一次回调
  46.     yuyy_keyinit(test_keycb);
  47.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  48.     EA = 1;     //打开总中断
  49.     while(1)
  50.     {
  51.     }
  52. }
复制代码
只验证了普通按键和矩阵按键的工作情况
QQ截图20230518173900.png


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-19 19:45:35 | 显示全部楼层
第18集,ADC采集电源电压和ADC按键下
没有ADC键盘,用屠龙刀上的P32-P354个按键做了个时钟,用P20上的LED模拟蜂鸣器,USB-CDC模拟数码管显示
P32 长按进入时钟设置,短按设置位右移
P33 点击改变数字,长按自动增加数字
P34 长按设置闹钟,短按设置位左移
P25 点击退出设置
运行效果



代码实现

  1. #include "config.h"
  2. #include "string.h"
  3. #include "yuyy.h"
  4. //USB调试及复位所需定义
  5. char *USER_DEVICEDESC = NULL;
  6. char *USER_PRODUCTDESC = NULL;
  7. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  8. const uint8_t ledsegcodes[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};
  9. uint8_t clock10ms = 0; //毫秒
  10. uint8_t clocksec = 0; //秒
  11. uint8_t clockmin = 0; //分
  12. uint8_t clockhour = 0; //时
  13. uint8_t alertsec = 30; //闹钟秒
  14. uint8_t alertmin = 0; //闹钟分
  15. uint8_t alerthour = 0; //闹钟时
  16. uint8_t settingmode = 0; // 1配置时钟 2配置闹钟
  17. uint8_t settingpos = 0; //配置的数码管位
  18. uint8_t settingtimechanged = 0;
  19. uint8_t settingautoaddtimecount = 0;
  20. uint8_t settingautoadd = 0;
  21. uint8_t settingclock10ms = 0;
  22. void test_showclock(void)
  23. {
  24.     uint8_t segdatas[8]={0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00};
  25.     segdatas[7] = ledsegcodes[clocksec % 10];
  26.     segdatas[6] = ledsegcodes[clocksec / 10];
  27.     segdatas[4] = ledsegcodes[clockmin % 10];
  28.     segdatas[3] = ledsegcodes[clockmin / 10];
  29.     segdatas[1] = ledsegcodes[clockhour % 10];
  30.     segdatas[0] = ledsegcodes[clockhour / 10];
  31.     SEG7_ShowCode(segdatas);
  32. }
  33. void test_showclocksetting(void)
  34. {
  35.     uint8_t segdatas[8]={0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00};
  36.     if(settingmode == 1)
  37.     {
  38.         segdatas[7] = ledsegcodes[clocksec % 10];
  39.         segdatas[6] = ledsegcodes[clocksec / 10];
  40.         segdatas[4] = ledsegcodes[clockmin % 10];
  41.         segdatas[3] = ledsegcodes[clockmin / 10];
  42.         segdatas[1] = ledsegcodes[clockhour % 10];
  43.         segdatas[0] = ledsegcodes[clockhour / 10];
  44.     }
  45.     else if(settingmode == 2)
  46.     {
  47.         segdatas[7] = ledsegcodes[alertsec % 10];
  48.         segdatas[6] = ledsegcodes[alertsec / 10];
  49.         segdatas[4] = ledsegcodes[alertmin % 10];
  50.         segdatas[3] = ledsegcodes[alertmin / 10];
  51.         segdatas[1] = ledsegcodes[alerthour % 10];
  52.         segdatas[0] = ledsegcodes[alerthour / 10];
  53.     }
  54.     if(settingclock10ms == 0)
  55.     {
  56.         segdatas[settingpos] = 0x00;
  57.         SEG7_ShowCode(segdatas);
  58.     }
  59.     else if(settingclock10ms == 50)
  60.     {
  61.         SEG7_ShowCode(segdatas);
  62.     }
  63.     if(settingtimechanged)
  64.     {
  65.         if(settingclock10ms < 50)
  66.             segdatas[settingpos] = 0x00;
  67.         SEG7_ShowCode(segdatas);
  68.         settingtimechanged = 0;
  69.     }
  70.     if(settingclock10ms < 100)
  71.         settingclock10ms++;
  72.     else
  73.         settingclock10ms = 0;
  74. }
  75. void test_settingposright(void)
  76. {
  77.     if(settingpos == 0)
  78.     {
  79.         if(settingmode == 1 && clockhour > 23)
  80.         {
  81.             clockhour = 23;
  82.         }
  83.         else if(settingmode == 2 && alerthour > 23)
  84.         {
  85.             alerthour = 23;
  86.         }
  87.         settingpos = 1;
  88.     }
  89.     else if(settingpos == 1)
  90.         settingpos = 3;
  91.     else if(settingpos == 3)
  92.         settingpos = 4;
  93.     else if(settingpos == 4)
  94.         settingpos = 6;
  95.     else if(settingpos == 6)
  96.         settingpos = 7;
  97.     else
  98.         settingpos = 0;
  99.     settingtimechanged = 1;
  100. }
  101. void test_settingposleft(void)
  102. {
  103.     if(settingpos == 0)
  104.     {
  105.         if(settingmode == 1 && clockhour > 23)
  106.         {
  107.             clockhour = 23;
  108.         }
  109.         else if(settingmode == 2 && alerthour > 23)
  110.         {
  111.             alerthour = 23;
  112.         }
  113.         settingpos = 7;
  114.     }
  115.     else if(settingpos == 1)
  116.         settingpos = 0;
  117.     else if(settingpos == 3)
  118.         settingpos = 1;
  119.     else if(settingpos == 4)
  120.         settingpos = 3;
  121.     else if(settingpos == 6)
  122.         settingpos = 4;
  123.     else
  124.         settingpos = 6;
  125.     settingtimechanged = 1;
  126. }
  127. void test_settingadd(void)
  128. {
  129.     uint8_t setnum = 0;
  130.     uint8_t *times,*timem,*timeh;
  131.     if(settingmode == 1)
  132.     {
  133.         times = &clocksec;
  134.         timem = &clockmin;
  135.         timeh = &clockhour;
  136.     }
  137.     else if(settingmode == 2)
  138.     {
  139.         times = &alertsec;
  140.         timem = &alertmin;
  141.         timeh = &alerthour;
  142.     }
  143.     if(settingpos == 0)
  144.     {
  145.         setnum = *timeh/10;
  146.         setnum++;
  147.         if(setnum > 2)
  148.         {
  149.             setnum = 0;
  150.         }
  151.         *timeh = setnum*10 + *timeh%10;
  152.     }
  153.     else if(settingpos == 1)
  154.     {
  155.         setnum = *timeh%10;
  156.         *timeh -= setnum;
  157.         setnum++;
  158.         if(*timeh > 19)
  159.         {
  160.             if(setnum > 3)
  161.                 setnum = 0;
  162.         }
  163.         else if(setnum > 9)
  164.             setnum = 0;
  165.         *timeh = setnum + *timeh;
  166.     }
  167.     else if(settingpos == 3)
  168.     {
  169.         setnum = *timem/10;
  170.         setnum++;
  171.         if(setnum > 5)
  172.             setnum = 0;
  173.         *timem = setnum*10 + *timem%10;
  174.     }
  175.     else if(settingpos == 4)
  176.     {
  177.         setnum = *timem%10;
  178.         *timem -= setnum;
  179.         setnum++;
  180.         if(setnum > 9)
  181.             setnum = 0;
  182.         *timem = setnum + *timem;
  183.     }
  184.     else if(settingpos == 6)
  185.     {
  186.         setnum = *times/10;
  187.         setnum++;
  188.         if(setnum > 5)
  189.             setnum = 0;
  190.         *times = setnum*10 + *times%10;
  191.     }
  192.     else if(settingpos == 7)
  193.     {
  194.         setnum = *times%10;
  195.         *times -= setnum;
  196.         setnum++;
  197.         if(setnum > 9)
  198.             setnum = 0;
  199.         *times = setnum + *times;
  200.     }
  201.     settingtimechanged = 1;
  202.     settingclock10ms = 50;
  203. }
  204. void test_clockadd10ms(void)
  205. {
  206.     uint8_t timechanged = 0;
  207.     if(clock10ms < 100)
  208.         clock10ms++;
  209.     else
  210.     {
  211.         clock10ms = 0;
  212.         clocksec++;
  213.         timechanged = 1;
  214.     }
  215.     if(clocksec == 60)
  216.     {
  217.         clocksec = 0;
  218.         clockmin++;
  219.     }
  220.     if(clockmin == 60)
  221.     {
  222.         clockmin = 0;
  223.         clockhour++;
  224.     }
  225.     if(clockhour == 24)
  226.         clockhour = 0;
  227.     if(timechanged)
  228.     {
  229.         if(settingmode == 0)
  230.             test_showclock();
  231.         if(clocksec == alertsec && clockmin == alertmin && clockhour == alerthour)
  232.         {
  233.             yuyy_beep(300);
  234.         }
  235.     }
  236. }
  237. void test_timer0cb(void)
  238. {
  239.     if(settingmode == 0)
  240.         test_clockadd10ms();
  241.     else
  242.     {
  243.         if(settingautoadd)
  244.         {
  245.             if(settingautoaddtimecount < 25)
  246.             {
  247.                 settingautoaddtimecount++;
  248.             }
  249.             else
  250.             {
  251.                 settingautoaddtimecount = 0;
  252.                 test_settingadd();
  253.             }
  254.         }
  255.         test_showclocksetting();
  256.         if(settingmode == 2) //设置闹钟时时间正常走
  257.         {
  258.             test_clockadd10ms();
  259.         }
  260.     }
  261.     yuyy_keyloop();
  262.     yuyy_beep_loop();
  263. }
  264. void test_keycb(enum YUYY_KEYTYPE keytype,uint8_t keynum,enum YUYY_KEYSTATE state)
  265. {
  266.     if(keytype == KEYTYPE_GPIO)
  267.     {
  268.         switch (keynum)
  269.         {
  270.         case 0://P32
  271.             if(state == KEY_LONGPRESS)
  272.             {
  273.                 //长按进入配置,时钟暂停
  274.                 settingmode = 1;
  275.                 settingpos = 0;
  276.                 settingclock10ms = 0;
  277.             }
  278.             else if(state == KEY_SINGLECLICK)
  279.             {
  280.                 if(settingmode)
  281.                 {
  282.                     test_settingposright();//右移1位
  283.                 }
  284.             }
  285.             break;
  286.         case 1://P33
  287.             if(state == KEY_SINGLECLICK)
  288.             {
  289.                 //点击增加数字
  290.                 test_settingadd();
  291.             }
  292.             else if(state == KEY_LONGPRESS)
  293.             {
  294.                 settingautoaddtimecount = 0;
  295.                 settingautoadd = 1;
  296.             }
  297.             else if(state == KEY_LONGPRESSUP)
  298.             {
  299.                 settingautoaddtimecount = 0;
  300.                 settingautoadd = 0;
  301.             }
  302.             break;
  303.         case 2://P34
  304.             if(state == KEY_LONGPRESS)
  305.             {
  306.                 //长按进入闹钟配置
  307.                 settingmode = 2;
  308.                 settingpos = 0;
  309.                 settingclock10ms = 0;
  310.             }
  311.             else if(state == KEY_SINGLECLICK)
  312.             {
  313.                 if(settingmode)
  314.                 {
  315.                     test_settingposleft();//左移1位
  316.                 }
  317.             }
  318.             break;
  319.         case 3://P35
  320.             if(state == KEY_SINGLECLICK)
  321.             {
  322.                 settingmode = 0;//退出设置
  323.                 if(settingmode == 1)
  324.                     clock10ms = 0;
  325.             }
  326.             break;
  327.         default:
  328.             break;
  329.         }
  330.     }
  331. }
  332. /******************** 主函数 **************************/
  333. void main(void)
  334. {
  335.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  336.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  337.     CKCON = 0; //提高访问XRAM速度
  338.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  339.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  340.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  341.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  342.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  343.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  344.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  345.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  346.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  347.     //USB调试及复位所需代码-----
  348.     P3M0 &= ~0x03;
  349.     P3M1 |= 0x03;
  350.     IRC48MCR = 0x80;
  351.     while (!(IRC48MCR & 0x01));
  352.     //如果使用USB-CDC需要下面的两行代码
  353.     USBCLK = 0x00;
  354.     USBCON = 0x90;
  355.     //如果使用USB-HID注释掉上面两行代码
  356.     usb_init();
  357.     //-------------------------
  358.     yuyy_timer0init(10000,test_timer0cb); //10ms触发一次回调
  359.     yuyy_keyinit(test_keycb);
  360.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  361.     EA = 1;     //打开总中断
  362.     while(1)
  363.     {
  364.     }
  365. }
复制代码


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-20 16:59:24 | 显示全部楼层
第19集,NTC温度采集
NTC(Negative Temperature Coe ficient)是指随温度上升电阻呈指数关系减小、具有负温度系数的热敏电阻现象和材料。
利用这一特性可以通过ADC来测量环境温度,将测量出的adc值通过查表的方式逆推为温度值。
QQ截图20230519153332.png


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-21 15:06:12 | 显示全部楼层
第20集,串口通信
通信指设备之间通过一定的协议进行的信息交换。
每次发送一位数据的称为串行通信,多位一起传输的称为并行通信。串口通信是串行通信的其中的一种
串口通信(Serial Communication),是指外设和计算机间,通过数据信号线、地线、控制线等,按位进行传输数据的一种通讯方式。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。
接线方式
QQ截图20230519153919.png

课后练习
QQ截图20230521145433.png

由于没有数码管和测温模块,所以用usb-hid模拟数码管,用存储的几个数据模拟温度值,用P35上的LED亮灭模拟蜂鸣器开关
运行效果


代码
  1. //USB调试及复位所需定义
  2. char *USER_DEVICEDESC = NULL;
  3. char *USER_PRODUCTDESC = NULL;
  4. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  5. const uint8_t ledsegcodes[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};
  6. #define FOSC 24000000UL
  7. #define BRT (65536 - (FOSC / 115200+2) / 4)
  8. //加 2 操作是为了让 Keil 编译器
  9. //自动实现四舍五入运算
  10. bit busy;
  11. uint8_t rxbuffer[16];
  12. uint8_t rxcount = 0;
  13. int temps[] = {2650,-1035,40,-10,560,-100};//模拟温度值 26.50 -10.35
  14. uint8_t tempindex = 0;
  15. uint8_t rxfinishflag = 0;
  16. void Uart2Init()
  17. {
  18.     P_SW2 = 0x80; //串口2 rxP10 txP11
  19.     S2CFG = 0x01; //无帧错检测功能,波特率不加倍,固定为 Fosc/12
  20.     S2CON = 0x50; //可变波特率8位数据方式,允许串口接收数据
  21.     T2L = BRT;
  22.     T2H = BRT >> 8;
  23.     T2x12 = 1;
  24.     T2R = 1;
  25.     busy = 0;
  26. }
  27. void Uart2Send(uint8_t dat)
  28. {
  29.     while (busy);
  30.     busy = 1;
  31.     S2BUF = dat;
  32. }
  33. void Uart2SendStr(char *p)
  34. {
  35.     while (*p)
  36.     {
  37.         Uart2Send(*p++);
  38.     }
  39. }
  40. void procrxdata()
  41. {
  42.     int temp;
  43.     char txbuffer[8] = {0};
  44.     uint8_t segdatas[8]={0x00};
  45.     uint8_t segindex = 7;
  46.     switch (rxbuffer[0])
  47.     {
  48.     case 'A'://点亮对应的LED
  49.         if(rxcount == 2 && rxbuffer[1] >= '0' && rxbuffer[1] < '8')
  50.         {
  51.             P2 = ~(1<<(rxbuffer[1] - '0'));
  52.         }
  53.         break;
  54.     case 'B'://数码管显示数字
  55.         if(rxcount > 1 || rxcount < 9)
  56.         {
  57.             while (rxcount > 1)
  58.             {
  59.                 rxcount--;
  60.                 if(rxbuffer[rxcount] >= '0' && rxbuffer[rxcount] <= '9')
  61.                 {
  62.                     segdatas[segindex] = ledsegcodes[rxbuffer[rxcount]-'0'];
  63.                 }
  64.                 segindex--;
  65.             }
  66.             SEG7_ShowCode(segdatas);
  67.         }
  68.         break;
  69.     case 'C'://打开关闭蜂鸣器
  70.         if(rxcount == 2 && (rxbuffer[1] == '0' || rxbuffer[1] == '1'))
  71.         {
  72.             P35 = '1' - rxbuffer[1];
  73.         }
  74.         break;
  75.     case 'D'://发送温度
  76.         if(rxcount == 2)
  77.         {
  78.             temp = temps[tempindex];
  79.             rxcount = 0;
  80.             if(temp < 0)
  81.             {
  82.                 txbuffer[rxcount++] = '-';
  83.                 temp = 0 - temp;
  84.             }
  85.             if(temp > 999)
  86.             {
  87.                 txbuffer[rxcount++] = '0' + (temp / 1000);
  88.                 temp = temp%1000;
  89.             }
  90.             txbuffer[rxcount++] = '0' + (temp / 100);
  91.             temp = temp%100;
  92.             txbuffer[rxcount++] = '.';
  93.             txbuffer[rxcount++] = '0' + (temp / 10);
  94.             temp = temp%10;
  95.             txbuffer[rxcount++] = '0' + temp;
  96.             Uart2SendStr(txbuffer);
  97.             tempindex++;
  98.             if(tempindex > 5)
  99.             {
  100.                 tempindex = 0;
  101.             }
  102.         }
  103.         break;
  104.     case 'Z'://发送"Hello STC"
  105.         if(rxcount == 1)
  106.         {
  107.             Uart2SendStr("Hello STC");
  108.         }
  109.         break;
  110.     default:
  111.         break;
  112.     }
  113.     rxcount = 0;
  114.     yuyy_delay_ms(1);
  115. }
  116. void Uart2Isr() interrupt 8
  117. {
  118.     if (S2TI)
  119.     {
  120.         S2TI = 0;
  121.         busy = 0;
  122.     }
  123.     if (S2RI)
  124.     {
  125.         S2RI = 0;
  126.         rxbuffer[rxcount] = S2BUF;
  127.         if(rxcount > 1 && rxbuffer[rxcount-1]=='\r' && rxbuffer[rxcount]=='\n')
  128.         {
  129.             rxcount-=1;
  130.             rxfinishflag = 1;
  131.             S2REN = 0;//关闭接收
  132.         }
  133.         else
  134.             rxcount++;
  135.         if(rxcount > 15)
  136.             rxcount = 0;
  137.     }
  138. }
  139. /******************** 主函数 **************************/
  140. void main(void)
  141. {
  142.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  143.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  144.     CKCON = 0; //提高访问XRAM速度
  145.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  146.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  147.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  148.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  149.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  150.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  151.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  152.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  153.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  154.     //USB调试及复位所需代码-----
  155.     P3M0 &= ~0x03;
  156.     P3M1 |= 0x03;
  157.     IRC48MCR = 0x80;
  158.     while (!(IRC48MCR & 0x01));
  159.     //如果使用USB-CDC需要下面的两行代码
  160.     // USBCLK = 0x00;
  161.     // USBCON = 0x90;
  162.     //如果使用USB-HID注释掉上面两行代码
  163.     usb_init();
  164.     //-------------------------
  165.    
  166.     Uart2Init();
  167.     IE2 = 0x01;
  168.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  169.     EA = 1;     //打开总中断
  170.     while (P32 != 0);
  171.     Uart2SendStr("STC32G test");
  172.     S2REN = 1;//开启接收
  173.     while(1)
  174.     {
  175.         if(rxfinishflag)
  176.         {
  177.             procrxdata();
  178.             rxfinishflag = 0;
  179.             S2REN = 1;//开启接收
  180.         }
  181.     }
  182. }
复制代码


回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-22 08:58:58 | 显示全部楼层
第21集,串口应用
MCU串口出来的信号都是TTL电平
TTL电平信号规定,+5V等价于逻辑“1”,0V等价于逻辑“0”(采用二进制来表示数据时)。这样的数据通信及电平规定方式,被称做TTL(晶体管-晶体管逻辑电平)信号系统。这是计算机处理器控制的设备内部各部分之间通信的标准技术。
232或485的通讯距离更长抗干扰能力更强
C库函数 int sprintf(char *str, const char *format, ...) 发送格式化输出到 str 所指向的字符串。
课后练习
QQ截图20230521233509.png

使用串口1和串口2互相通讯,由于没有那么多按键,使用USB-HID虚拟键盘上的0-9ABC按键,按键按下后串口1向串口2发送数据,串口2如果有数据返回,串口1接收后通过USB-HID打印出来

运行效果



代码实现

  1. //USB调试及复位所需定义
  2. char *USER_DEVICEDESC = NULL;
  3. char *USER_PRODUCTDESC = NULL;
  4. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  5. const uint8_t ledsegcodes[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};
  6. #define FOSC 24000000UL
  7. #define BRT (65536 - (FOSC / 115200+2) / 4)
  8. //加 2 操作是为了让 Keil 编译器
  9. //自动实现四舍五入运算
  10. bit u1busy;
  11. uint8_t u1rxbuffer[16];
  12. uint8_t u1rxcount = 0;
  13. uint8_t u1rxfinishflag = 0;
  14. bit u2busy;
  15. uint8_t u2rxbuffer[16];
  16. uint8_t u2rxcount = 0;
  17. uint8_t u2rxfinishflag = 0;
  18. int temps[] = {2650,-1035,40,-10,560,-100};//模拟温度值 26.50 -10.35
  19. uint8_t tempindex = 0;
  20. void Uart1Init()
  21. {
  22.     P_SW1 = 0x80; //串口1 rxP16 txP17
  23.     SCON = 0x50;
  24.     T2L = BRT;
  25.     T2H = BRT >> 8;
  26.     S1BRT = 1;
  27.     T2x12 = 1;
  28.     T2R = 1;
  29.     u1busy = 0;
  30. }
  31. void Uart1Send(uint8_t dat)
  32. {
  33.     while (u1busy);
  34.     u1busy = 1;
  35.     SBUF = dat;
  36. }
  37. void Uart1SendStr(char *p)
  38. {
  39.     while (*p)
  40.     {
  41.         Uart1Send(*p++);
  42.     }
  43. }
  44. void Uart2Init()
  45. {
  46.     P_SW2 = 0x80; //串口2 rxP10 txP11
  47.     S2CFG = 0x01; //无帧错检测功能,波特率不加倍,固定为 Fosc/12
  48.     S2CON = 0x50; //可变波特率8位数据方式,允许串口接收数据
  49.     T2L = BRT;
  50.     T2H = BRT >> 8;
  51.     T2x12 = 1;
  52.     T2R = 1;
  53.     u2busy = 0;
  54. }
  55. void Uart2Send(uint8_t dat)
  56. {
  57.     while (u2busy);
  58.     u2busy = 1;
  59.     S2BUF = dat;
  60. }
  61. void Uart2SendStr(char *p)
  62. {
  63.     while (*p)
  64.     {
  65.         Uart2Send(*p++);
  66.     }
  67. }
  68. void procrxdata()
  69. {
  70.     int temp;
  71.     char txbuffer[10] = {0};
  72.     uint8_t segdatas[8]={0x00};
  73.     uint8_t segindex = 7;
  74.     switch (u2rxbuffer[0])
  75.     {
  76.     case 'A'://点亮对应的LED
  77.         if(u2rxcount == 2 && u2rxbuffer[1] >= '0' && u2rxbuffer[1] < '8')
  78.         {
  79.             P2 = ~(1<<(u2rxbuffer[1] - '0'));
  80.         }
  81.         break;
  82.     case 'B'://数码管显示数字
  83.         if(u2rxcount > 1 || u2rxcount < 9)
  84.         {
  85.             while (u2rxcount > 1)
  86.             {
  87.                 u2rxcount--;
  88.                 if(u2rxbuffer[u2rxcount] >= '0' && u2rxbuffer[u2rxcount] <= '9')
  89.                 {
  90.                     segdatas[segindex] = ledsegcodes[u2rxbuffer[u2rxcount]-'0'];
  91.                 }
  92.                 segindex--;
  93.             }
  94.             SEG7_ShowCode(segdatas);
  95.         }
  96.         break;
  97.     case 'C'://打开关闭蜂鸣器
  98.         if(u2rxcount == 2 && (u2rxbuffer[1] == '0' || u2rxbuffer[1] == '1'))
  99.         {
  100.             P35 = '1' - u2rxbuffer[1];
  101.         }
  102.         break;
  103.     case 'D'://发送温度
  104.         if(u2rxcount == 2)
  105.         {
  106.             temp = temps[tempindex];
  107.             u2rxcount = 0;
  108.             if(temp < 0)
  109.             {
  110.                 txbuffer[u2rxcount++] = '-';
  111.                 temp = 0 - temp;
  112.             }
  113.             if(temp > 999)
  114.             {
  115.                 txbuffer[u2rxcount++] = '0' + (temp / 1000);
  116.                 temp = temp%1000;
  117.             }
  118.             txbuffer[u2rxcount++] = '0' + (temp / 100);
  119.             temp = temp%100;
  120.             txbuffer[u2rxcount++] = '.';
  121.             txbuffer[u2rxcount++] = '0' + (temp / 10);
  122.             temp = temp%10;
  123.             txbuffer[u2rxcount++] = '0' + temp;
  124.             txbuffer[u2rxcount++] = '\r';
  125.             txbuffer[u2rxcount++] = '\n';
  126.             Uart2SendStr(txbuffer);
  127.             tempindex++;
  128.             if(tempindex > 5)
  129.             {
  130.                 tempindex = 0;
  131.             }
  132.         }
  133.         break;
  134.     case 'Z'://发送"Hello STC"
  135.         if(u2rxcount == 1)
  136.         {
  137.             Uart2SendStr("Hello STC\r\n");
  138.         }
  139.         break;
  140.     default:
  141.         break;
  142.     }
  143.     u2rxcount = 0;
  144.     yuyy_delay_ms(1);
  145. }
  146. void Uart1Isr() interrupt 4
  147. {
  148.     if (TI)
  149.     {
  150.         TI = 0;
  151.         u1busy = 0;
  152.     }
  153.     if (RI)
  154.     {
  155.         RI = 0;
  156.         u1rxbuffer[u1rxcount] = SBUF;
  157.         if(u1rxbuffer[u1rxcount-1]=='\r' && u1rxbuffer[u1rxcount]=='\n')
  158.         {
  159.             u1rxfinishflag = 1;
  160.             REN = 0;//关闭接收
  161.         }
  162.         else
  163.             u1rxcount++;
  164.         if(u1rxcount > 15)
  165.             u1rxcount = 0;
  166.     }
  167. }
  168. void Uart2Isr() interrupt 8
  169. {
  170.     if (S2TI)
  171.     {
  172.         S2TI = 0;
  173.         u2busy = 0;
  174.     }
  175.     if (S2RI)
  176.     {
  177.         S2RI = 0;
  178.         u2rxbuffer[u2rxcount] = S2BUF;
  179.         if(u2rxcount > 1 && u2rxbuffer[u2rxcount-1]=='\r' && u2rxbuffer[u2rxcount]=='\n')
  180.         {
  181.             u2rxcount-=1;
  182.             u2rxfinishflag = 1;
  183.             S2REN = 0;//关闭接收
  184.         }
  185.         else
  186.             u2rxcount++;
  187.         if(u2rxcount > 15)
  188.             u2rxcount = 0;
  189.     }
  190. }
  191. /******************** 主函数 **************************/
  192. void main(void)
  193. {
  194.     char u1txdats[] = {'A','0','\r','\n'};
  195.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  196.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  197.     CKCON = 0; //提高访问XRAM速度
  198.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  199.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  200.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  201.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  202.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  203.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  204.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  205.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  206.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  207.     //USB调试及复位所需代码-----
  208.     P3M0 &= ~0x03;
  209.     P3M1 |= 0x03;
  210.     IRC48MCR = 0x80;
  211.     while (!(IRC48MCR & 0x01));
  212.     //如果使用USB-CDC需要下面的两行代码
  213.     // USBCLK = 0x00;
  214.     // USBCON = 0x90;
  215.     //如果使用USB-HID注释掉上面两行代码
  216.     usb_init();
  217.     //-------------------------
  218.    
  219.     Uart1Init();
  220.     Uart2Init();
  221.     ES = 1;
  222.     ES2 = 1;
  223.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  224.     EA = 1;     //打开总中断
  225.     REN = 1;
  226.     S2REN = 1;//开启接收
  227.     while(1)
  228.     {
  229.         if (DeviceState != DEVSTATE_CONFIGURED)        //判断USB设备识别是否完成
  230.             continue;
  231.         if (bUsbOutReady)
  232.         {
  233.             if ((UsbOutBuffer[0] == 'K') &&
  234.                 (UsbOutBuffer[1] == 'E') &&
  235.                 (UsbOutBuffer[2] == 'Y') &&
  236.                 (UsbOutBuffer[3] == 'P'))
  237.             {
  238.                 switch (UsbOutBuffer[5])
  239.                 {
  240.                     case 0x30:
  241.                     case 0x31:
  242.                     case 0x32:
  243.                     case 0x33:
  244.                     case 0x34:
  245.                     case 0x35:
  246.                     case 0x36:
  247.                     case 0x37: //按键0-7
  248.                         u1txdats[1] = '0' + (UsbOutBuffer[5]-0x30);
  249.                         Uart1SendStr(u1txdats);
  250.                         break;
  251.                     case 0x38: //按键8
  252.                         Uart1SendStr("B0000\r\n");
  253.                         break;
  254.                     case 0x39: //按键9
  255.                         Uart1SendStr("Z\r\n");
  256.                         break;
  257.                     case 0x41: //按键A
  258.                         Uart1SendStr("C0\r\n");
  259.                         break;
  260.                     case 0x42: //按键B
  261.                         Uart1SendStr("C1\r\n");
  262.                         break;
  263.                     case 0x43: //按键C
  264.                         Uart1SendStr("D0\r\n");
  265.                         break;
  266.                     default:
  267.                         break;
  268.                 }
  269.                 usb_OUT_done();
  270.             }
  271.         }
  272.         if(u1rxfinishflag)
  273.         {
  274.             //接收到的数据通过usb-hid打印
  275.             printf(u1rxbuffer);
  276.             u1rxfinishflag = 0;
  277.             u1rxcount = 0;
  278.             memset(u1rxbuffer,0);
  279.             REN = 1;//开启接收
  280.         }
  281.         if(u2rxfinishflag)
  282.         {
  283.             procrxdata();
  284.             u2rxfinishflag = 0;
  285.             S2REN = 1;//开启接收
  286.         }
  287.     }
  288. }
复制代码



回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2024-05-24 21:11:31

3

主题

64

回帖

1196

积分

金牌会员

积分
1196
发表于 2023-5-22 19:03:26 | 显示全部楼层
第22集,CDC串口通信
无需USB转TTL,一根数据线就能通讯,P30和P31连接USB的D-和D+加上单片机外围即可实现下载与通讯
CDC串口的优点
QQ截图20230522090415.png

CDC不停电下载回顾,本帖6楼https://www.stcaimcu.com/forum.p ... =2110&pid=13843
CDC串口通信练习
QQ截图20230522093612.png

运行效果


代码实现
  1. //USB调试及复位所需定义
  2. char *USER_DEVICEDESC = NULL;
  3. char *USER_PRODUCTDESC = NULL;
  4. char *USER_STCISPCMD = "@STCISP#"; //设置自动复位到ISP区的用户接口命令
  5. const uint8_t ledsegcodes[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};
  6. int temps[] = {2650,-1035,40,-10,560,-100};//模拟温度值 26.50 -10.35
  7. uint8_t tempindex = 0;
  8. /******************** 主函数 **************************/
  9. void main(void)
  10. {
  11.     int temp;
  12.     char txbuffer[10] = {0};
  13.     uint8_t segdatas[8]={0x00};
  14.     uint8_t segindex = 7;
  15.     WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
  16.     EAXFR = 1; //扩展寄存器(XFR)访问使能
  17.     CKCON = 0; //提高访问XRAM速度
  18.     RSTFLAG |= 0x04;   //设置硬件复位后需要检测P3.2的状态选择运行区域,否则硬件复位后进入USB下载模式
  19.     P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
  20.     P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
  21.     P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
  22.     P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
  23.     P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
  24.     P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
  25.     P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
  26.     P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口
  27.     //USB调试及复位所需代码-----
  28.     P3M0 &= ~0x03;
  29.     P3M1 |= 0x03;
  30.     IRC48MCR = 0x80;
  31.     while (!(IRC48MCR & 0x01));
  32.     //如果使用USB-CDC需要下面的两行代码
  33.     USBCLK = 0x00;
  34.     USBCON = 0x90;
  35.     //如果使用USB-HID注释掉上面两行代码
  36.     usb_init();
  37.     //-------------------------
  38.    
  39.     EUSB = 1;   //IE2相关的中断位操作使能后,需要重新设置EUSB
  40.     EA = 1;     //打开总中断
  41.     while(1)
  42.     {
  43.         if (DeviceState != DEVSTATE_CONFIGURED)        //判断USB设备识别是否完成
  44.             continue;
  45.         if (bUsbOutReady)
  46.         {
  47.             // printf("rec len %d \r\n",OutNumber);
  48.             // USB_SendData(UsbOutBuffer,OutNumber);
  49.             switch (UsbOutBuffer[0])
  50.             {
  51.                 case 'A'://点亮对应的LED
  52.                     if(OutNumber == 2 && UsbOutBuffer[1] >= '0' && UsbOutBuffer[1] < '8')
  53.                     {
  54.                         P2 = ~(1<<(UsbOutBuffer[1] - '0'));
  55.                     }
  56.                     break;
  57.                 case 'B'://数码管显示数字
  58.                     if(OutNumber > 1 || OutNumber < 9)
  59.                     {
  60.                         segindex = 7;
  61.                         while (OutNumber > 1)
  62.                         {
  63.                             OutNumber--;
  64.                             if(UsbOutBuffer[OutNumber] >= '0' && UsbOutBuffer[OutNumber] <= '9')
  65.                             {
  66.                                 segdatas[segindex] = ledsegcodes[UsbOutBuffer[OutNumber]-'0'];
  67.                             }
  68.                             segindex--;
  69.                         }
  70.                         SEG7_ShowCode(segdatas);
  71.                     }
  72.                     break;
  73.                 case 'C'://打开关闭蜂鸣器
  74.                     if(OutNumber == 2 && (UsbOutBuffer[1] == '0' || UsbOutBuffer[1] == '1'))
  75.                     {
  76.                         P35 = '1' - UsbOutBuffer[1];
  77.                     }
  78.                     break;
  79.                 case 'D'://发送温度
  80.                     if(OutNumber == 2)
  81.                     {
  82.                         temp = temps[tempindex];
  83.                         OutNumber = 0;
  84.                         if(temp < 0)
  85.                         {
  86.                             txbuffer[OutNumber++] = '-';
  87.                             temp = 0 - temp;
  88.                         }
  89.                         if(temp > 999)
  90.                         {
  91.                             txbuffer[OutNumber++] = '0' + (temp / 1000);
  92.                             temp = temp%1000;
  93.                         }
  94.                         txbuffer[OutNumber++] = '0' + (temp / 100);
  95.                         temp = temp%100;
  96.                         txbuffer[OutNumber++] = '.';
  97.                         txbuffer[OutNumber++] = '0' + (temp / 10);
  98.                         temp = temp%10;
  99.                         txbuffer[OutNumber++] = '0' + temp;
  100.                         USB_SendData(txbuffer,OutNumber);
  101.                         tempindex++;
  102.                         if(tempindex > 5)
  103.                         {
  104.                             tempindex = 0;
  105.                         }
  106.                     }
  107.                     break;
  108.                 case 'Z'://发送"Hello STC"
  109.                     if(OutNumber == 1)
  110.                     {
  111.                         USB_SendData("Hello STC",9);
  112.                     }
  113.                     break;
  114.                 default:
  115.                     break;
  116.             }
  117.             usb_OUT_done();
  118.         }
  119.     }
  120. }
复制代码


回复 支持 反对

使用道具 举报 送花

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

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-7 05:54 , Processed in 0.164057 second(s), 107 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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