您的位置 首页 制造

第45节:主机的串口收发归纳程序结构

开场白:在大部分的项目中,串口都需要一收一应答的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答

开场白:

在大部分的项目中,串口都需求“一收一应对”的握手协议,主机先发一串数据,从机收到数据后进行校验判别,假如校验正确则回来正确应对指令,假如校验过错则回来过错应对指令,主机收到应对指令后,假如发现是正确应对指令则持续发送其它的新数据,假如发现是过错应对指令,或许超时没有接纳到任何应对指令,则持续重发,假如接连重发三次都是过错应对或许无应对,主机就进行报错处理。
上一节现已讲了从机,这节就讲主机的收发端程序实例。要教会咱们四个知识点:
榜首个:为了确保串口中止接纳的数据不丢掉,在初始化时有必要设置IP= 0x10,相当于把串口中止设置为最高优先级,这个时分,串口中止能够打断任何其他的中止服务函数,完结中止嵌套。
第二个:主机端的收发端程序结构。包含重发,超时检测等等。
第三个:主机的状况指示程序结构。能够指示待机,通讯中,超时犯错三种状况。
第四个:其实上一节的LED灯闪耀的时刻里,我忘了加原子锁,不加原子锁的结果是,闪耀的时刻有时分会不共同,所以这节多添加一个原子锁变量ucLedLock,再次感谢“红金龙吸味”关于原子锁的主张,真的很好用。

具体内容,请看源代码解说。

(1)硬件渠道:
依据朱兆祺51单片机学习板

(2)完结功用:
显现和独立按键部分依据第29节的程序来改编,用朱兆祺51单片机学习板中的S1,S5,S9,S13作为独立按键。
一共有4个窗口。每个窗口显现一个参数。串口能够把当时设置的4个数据发送给从机。从机端能够用电脑的串口帮手来模仿。
榜首:按键更改参数:
第8,7,6,5位数码管显现当时窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显现当时窗口被设置的参数。规模是从0到9999。S1是加按键,按下此按键会顺次添加当时窗口的参数。S5是减按键,按下此按键会顺次削减当时窗口的参数。S9是切换窗口按键,按下此按键会顺次循环切换不同的窗口。S13是发动发送数据和复位按键,当体系处于待机状况时,按下此按键会发动发送数据;当通讯超时蜂鸣器报警时,能够按下此键铲除报警,回来到待机的状况。

第二:通过串口把更改的参数发送给从机。

波特率是:9600.
通讯协议:EB00 55GG 00 02 XX XXCY
其间第1,2,3位EB00 55便是数据头
其间第4位GG便是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其间第5,6位0002便是有用数据长度。高位在左,低位在右。
其间从第7,8位XXXX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面一切字节的累加。
一个完好的通讯有必要发送完4串数据,每串数据之间的间隔时刻不能超越10秒钟,不然以为通讯超时主时机重发数据,假如接连三次都没有回来,则引发蜂鸣器报警。假如接纳到得数据校验正确,主机持续发送新的一串数据,直到把4串数据发送结束中止。
体系处于待机状况时,LED灯一向亮,
体系处于非待机状况时,LED灯闪耀,
体系处于犯错状况时,LED灯闪耀,而且蜂鸣器间歇鸣叫报警。

通过电脑的串口帮手来模仿从机,回来不同的应对

从机回来校验正确应对:eb 00 55 f5 00 00 35
从机回来校验犯错应对:eb00 55 fa 00 00 3a

(3)源代码解说如下:
  1. #include “REG52.H”
  2. #define const_voice_short40 //蜂鸣器短叫的持续时刻
  3. #define const_key_time120 //按键去颤动延时的时刻
  4. #define const_key_time220 //按键去颤动延时的时刻
  5. #define const_key_time320 //按键去颤动延时的时刻
  6. #define const_key_time420 //按键去颤动延时的时刻
  7. #define const_led_0_5s200 //大约0.5秒的时刻
  8. #define const_led_1s 400 //大约1秒的时刻
  9. #define const_send_time_out 4000//通讯超时犯错的时刻 大约10秒
  10. #define const_rc_size20//接纳串口中止数据的缓冲区数组巨细
  11. #define const_receive_time5//假如超越这个时刻没有串口数据过来,就以为一串数据现已悉数接纳完,这个时刻依据实践情况来调整巨细
  12. #define const_send_size10//串口发送数据的缓冲区数组巨细
  13. void initial_myself(void);
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  19. void display_drive(void); //显现数码管字模的驱动函数
  20. void display_service(void); //显现的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  23. void T0_time(void);//守时中止函数
  24. void usart_receive(void); //串口接纳中止函数
  25. void usart_service(void);//串口接纳服务程序,在main函数里
  26. void communication_service(void); //一发一收的通讯服务程序
  27. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时
  28. void key_service(void); //按键服务的应用程序
  29. void key_scan(void);//按键扫描函数 放在守时中止里
  30. void status_service(void);//状况显现的应用程序
  31. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  32. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  33. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  34. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  35. sbit key_gnd_dr=P0^4; //模仿独立按键的地GND,因而有必要一向输出低电平
  36. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  37. sbit led_dr=P3^5;//作为状况指示灯 亮的时分表明待机状况.闪耀表明非待机状况,处于正在发送数据或许犯错的状况
  38. sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
  39. sbit dig_hc595_st_dr=P2^1;
  40. sbit dig_hc595_ds_dr=P2^2;
  41. sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
  42. sbit hc595_st_dr=P2^4;
  43. sbit hc595_ds_dr=P2^5;
  44. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组
  45. unsigned intuiSendCnt=0; //用来辨认串口是否接纳完一串数据的计时器
  46. unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接纳完一串数据只处理一次
  47. unsigned intuiRcregTotal=0;//代表当时缓冲区现已接纳了多少个数据
  48. unsigned char ucRcregBuf[const_rc_size]; //接纳串口中止数据的缓冲区数组
  49. unsigned intuiRcMoveIndex=0;//用来解析数据协议的中心变量
  50. unsigned charucSendCntLock=0; //串口计时器的原子锁
  51. unsigned char ucRcType=0;//数据类型
  52. unsigned intuiRcSize=0;//数据长度
  53. unsigned char ucRcCy=0;//校验累加和
  54. unsigned char ucLedLock=0; //原子锁
  55. unsigned intuiLedCnt=0;//操控Led闪耀的延时计时器
  56. unsigned intuiSendTimeOutCnt=0; //用来辨认接纳数据超时的计时器
  57. unsigned char ucSendTimeOutLock=0; //原子锁
  58. unsigned char ucStatus=0; //当时状况变量 0代表待机 1代表正在通讯进程 2代表发送犯错
  59. unsigned char ucSendStep=0; //发送数据的进程进程
  60. unsigned char ucErrorCnt=0; //累计过错总数
  61. unsigned char ucSendTotal=0; //记载当时现已发送了多少串数据
  62. unsigned char ucReceiveStatus=0; //回来的数据状况 0代表待机 1代表校验正确 2代表校验犯错
  63. unsigned char ucKeySec=0; //被触发的按键编号
  64. unsigned intuiKeyTimeCnt1=0; //按键去颤动延时计数器
  65. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  66. unsigned intuiKeyTimeCnt2=0; //按键去颤动延时计数器
  67. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  68. unsigned intuiKeyTimeCnt3=0; //按键去颤动延时计数器
  69. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  70. unsigned intuiKeyTimeCnt4=0; //按键去颤动延时计数器
  71. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
  72. unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时刻计数器
  73. unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
  74. unsigned char ucDigShow8;//第8位数码管要显现的内容
  75. unsigned char ucDigShow7;//第7位数码管要显现的内容
  76. unsigned char ucDigShow6;//第6位数码管要显现的内容
  77. unsigned char ucDigShow5;//第5位数码管要显现的内容
  78. unsigned char ucDigShow4;//第4位数码管要显现的内容
  79. unsigned char ucDigShow3;//第3位数码管要显现的内容
  80. unsigned char ucDigShow2;//第2位数码管要显现的内容
  81. unsigned char ucDigShow1;//第1位数码管要显现的内容
  82. unsigned char ucDigDot8;//数码管8的小数点是否显现的标志
  83. unsigned char ucDigDot7;//数码管7的小数点是否显现的标志
  84. unsigned char ucDigDot6;//数码管6的小数点是否显现的标志
  85. unsigned char ucDigDot5;//数码管5的小数点是否显现的标志
  86. unsigned char ucDigDot4;//数码管4的小数点是否显现的标志
  87. unsigned char ucDigDot3;//数码管3的小数点是否显现的标志
  88. unsigned char ucDigDot2;//数码管2的小数点是否显现的标志
  89. unsigned char ucDigDot1;//数码管1的小数点是否显现的标志
  90. unsigned char ucDigShowTemp=0; //暂时中心变量
  91. unsigned char ucDisplayDriveStep=1;//动态扫描数码管的进程变量
  92. unsigned char ucWd1Update=1; //窗口1更新显现标志
  93. unsigned char ucWd2Update=0; //窗口2更新显现标志
  94. unsigned char ucWd3Update=0; //窗口3更新显现标志
  95. unsigned char ucWd4Update=0; //窗口4更新显现标志
  96. unsigned char ucWd=1;//本程序的中心变量,窗口显现变量。类似于一级菜单的变量。代表显现不同的窗口。
  97. unsigned intuiSetData1=0;//本程序中需求被设置的参数1
  98. unsigned intuiSetData2=0;//本程序中需求被设置的参数2
  99. unsigned intuiSetData3=0;//本程序中需求被设置的参数3
  100. unsigned intuiSetData4=0;//本程序中需求被设置的参数4
  101. unsigned char ucTemp1=0;//中心过渡变量
  102. unsigned char ucTemp2=0;//中心过渡变量
  103. unsigned char ucTemp3=0;//中心过渡变量
  104. unsigned char ucTemp4=0;//中心过渡变量
  105. //依据原理图得出的共阴数码管字模表
  106. code unsigned char dig_table[]=
  107. {
  108. 0x3f,//0 序号0
  109. 0x06,//1 序号1
  110. 0x5b,//2 序号2
  111. 0x4f,//3 序号3
  112. 0x66,//4 序号4
  113. 0x6d,//5 序号5
  114. 0x7d,//6 序号6
  115. 0x07,//7 序号7
  116. 0x7f,//8 序号8
  117. 0x6f,//9 序号9
  118. 0x00,//无 序号10
  119. 0x40,//- 序号11
  120. 0x73,//P 序号12
  121. };
  122. void main()
  123. {
  124. initial_myself();
  125. delay_long(100);
  126. initial_peripheral();
  127. while(1)
  128. {
  129. key_service(); //按键服务的应用程序
  130. usart_service();//串口接纳服务程序
  131. communication_service(); //一发一收的通讯服务程序
  132. display_service(); //显现的窗口菜单服务程序
  133. status_service();//状况显现的应用程序
  134. }
  135. }
  136. void communication_service(void) //一发一收的通讯服务程序
  137. {
  138. unsigned int i;
  139. if(ucStatus==1)//处于正在通讯的进程中
  140. {
  141. switch(ucSendStep)
  142. {
  143. case 0: //通讯进程0发送一串数据
  144. switch(ucSendTotal)//依据当时现已发送到第几条数据来决议发送哪些参数
  145. {
  146. case 0: //发送参数1
  147. ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
  148. ucSendregBuf[1]=0x00;
  149. ucSendregBuf[2]=0x55;
  150. ucSendregBuf[3]=0x01; //代表发送参数1
  151. ucSendregBuf[4]=0x00;
  152. ucSendregBuf[5]=0x02; //代表发送2个字节的有用数据
  153. ucSendregBuf[6]=uiSetData1>>8;//把int类型的参数分化成两个字节的数据
  154. ucSendregBuf[7]=uiSetData1;
  155. break;
  156. case 1://发送参数2
  157. ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
  158. ucSendregBuf[1]=0x00;
  159. ucSendregBuf[2]=0x55;
  160. ucSendregBuf[3]=0x02; //代表发送参数2
  161. ucSendregBuf[4]=0x00;
  162. ucSendregBuf[5]=0x02; //代表发送2个字节的有用数据
  163. ucSendregBuf[6]=uiSetData2>>8;//把int类型的参数分化成两个字节的数据
  164. ucSendregBuf[7]=uiSetData2;
  165. break;
  166. case 2://发送参数3
  167. ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
  168. ucSendregBuf[1]=0x00;
  169. ucSendregBuf[2]=0x55;
  170. ucSendregBuf[3]=0x03; //代表发送参数3
  171. ucSendregBuf[4]=0x00;
  172. ucSendregBuf[5]=0x02; //代表发送2个字节的有用数据
  173. ucSendregBuf[6]=uiSetData3>>8;//把int类型的参数分化成两个字节的数据
  174. ucSendregBuf[7]=uiSetData3;
  175. break;
  176. case 3://发送参数4
  177. ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
  178. ucSendregBuf[1]=0x00;
  179. ucSendregBuf[2]=0x55;
  180. ucSendregBuf[3]=0x04; //代表发送参数4
  181. ucSendregBuf[4]=0x00;
  182. ucSendregBuf[5]=0x02; //代表发送2个字节的有用数据
  183. ucSendregBuf[6]=uiSetData4>>8;//把int类型的参数分化成两个字节的数据
  184. ucSendregBuf[7]=uiSetData4;
  185. break;
  186. }
  187. ucSendregBuf[8]=0x00;
  188. for(i=0;i<8;i++)//最终一个字节是校验和,是前面一切字节累加,溢出部分不必咱们管,体系会有规则的主动处理
  189. {
  190. ucSendregBuf[8]=ucSendregBuf[8]+ucSendregBuf[i];
  191. }
  192. for(i=0;i<9;i++)
  193. {
  194. eusart_send(ucSendregBuf[i]);//把一串完好的数据发送给下位机
  195. }
  196. ucSendTimeOutLock=1; //原子锁加锁
  197. uiSendTimeOutCnt=0;//超时计时器计时清零
  198. ucSendTimeOutLock=0; //原子锁解锁
  199. ucReceiveStatus=0;//回来的数据状况清零
  200. ucSendStep=1;//切换到下一个进程,等候回来的数据
  201. break;
  202. case 1: //通讯进程1判别回来的指令
  203. if(ucReceiveStatus==1)//校验正确
  204. {
  205. ucErrorCnt=0; //累计校验过错总数清零
  206. ucSendTotal++;//累加当时发送了多少串数据
  207. if(ucSendTotal>=4) //现已发送完悉数4串数据,结束
  208. {
  209. ucStatus=0;//切换到结束时的待机状况
  210. }
  211. else//还没发送完4串数据,则持续发送下一串新数据
  212. {
  213. ucSendStep=0;//回来上一个进程,持续发送新数据
  214. }
  215. }
  216. else if(ucReceiveStatus==2||uiSendTimeOutCnt>const_send_time_out)//校验犯错或许超时犯错
  217. {
  218. ucErrorCnt++; //累计过错总数
  219. if(ucErrorCnt>=3)//累加重发次数3次以上,则报错
  220. {
  221. ucStatus=2;//切换到犯错报警状况
  222. }
  223. else//重发还没超越3次,持续回来重发
  224. {
  225. ucSendStep=0;//回来上一个进程,重发一次数据
  226. }
  227. }
  228. break;
  229. }
  230. }
  231. }
  232. void status_service(void)//状况显现的应用程序
  233. {
  234. if(ucStatus!=0) //处于非待机的状况,Led闪耀
  235. {
  236. if(uiLedCnt
  237. {
  238. led_dr=1;//前半秒亮
  239. if(ucStatus==2)//处于发送数据犯错的状况,则蜂鸣器间歇鸣叫报警
  240. {
  241. ucVoiceLock=1;//原子锁加锁,维护主函数与中止函数的同享变量uiVoiceCnt
  242. uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
  243. ucVoiceLock=0;//原子锁解锁,维护主函数与中止函数的同享变量uiVoiceCnt
  244. }
  245. }
  246. else if(uiLedCnt
  247. {
  248. led_dr=0; //前半秒灭
  249. }
  250. else
  251. {
  252. ucLedLock=1; //原子锁加锁
  253. uiLedCnt=0; //延时计时器清零,让Led灯处于闪耀的重复循环中
  254. ucLedLock=0; //原子锁解锁
  255. }
  256. }
  257. else//处于待机状况,Led一向亮
  258. {
  259. led_dr=1;
  260. }
  261. }
  262. void usart_service(void)//串口接纳服务程序,在main函数里
  263. {
  264. unsigned int i;
  265. if(uiSendCnt>=const_receive_time&&ucSendLock==1) //阐明超越了必定的时刻内,再也没有新数据从串口来
  266. {
  267. ucSendLock=0; //处理一次就锁起来,不必每次都进来,除非有新接纳的数据
  268. //下面的代码进入数据协议解析和数据处理的阶段
  269. uiRcMoveIndex=0; //由所以判别数据头,所以下标移动变量从数组的0开端向最尾端移动
  270. while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  271. {
  272. if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)//数据头eb 00 55的判别
  273. {
  274. ucRcType=ucRcregBuf[uiRcMoveIndex+3]; //数据类型一个字节
  275. uiRcSize=ucRcregBuf[uiRcMoveIndex+4]; //数据长度两个字节
  276. uiRcSize=uiRcSize<<8;
  277. uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  278. ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]; //记载最终一个字节的校验
  279. ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;//清零最终一个字节的累加和变量
  280. for(i=0;i<(3+1+2+uiRcSize);i++) //核算校验累加和
  281. {
  282. ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  283. }
  284. if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])//假如一串数据校验正确,则进入以下数据指令的判别
  285. {
  286. switch(ucRcType) //依据不同的数据类型来做不同的数据处理
  287. {
  288. case 0xf5: //回来的是正确的校验指令
  289. ucReceiveStatus=1;//代表校验正确
  290. break;
  291. case 0xfa: //回来的是过错的校验指令
  292. ucReceiveStatus=2;//代表校验过错
  293. break;
  294. }
  295. }
  296. break; //退出循环
  297. }
  298. uiRcMoveIndex++; //由于是判别数据头,游标向着数组最尾端的方向移动
  299. }
  300. uiRcregTotal=0;//清空缓冲的下标,便利下次从头从0下标开端承受新数据
  301. }
  302. }
  303. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  304. {
  305. ES = 0; //关串口中止
  306. TI = 0; //清零串口发送完结中止请求标志
  307. SBUF =ucSendData; //发送一个字节
  308. delay_short(400);//每个字节之间的延时,这儿十分要害,也是最简单犯错的当地。延时的巨细请依据实践项目来调整
  309. TI = 0; //清零串口发送完结中止请求标志
  310. ES = 1; //答应串口中止
  311. }
  312. void display_service(void) //显现的窗口菜单服务程序
  313. {
  314. switch(ucWd)//本程序的中心变量,窗口显现变量。类似于一级菜单的变量。代表显现不同的窗口。
  315. {
  316. case 1: //显现P–1窗口的数据
  317. if(ucWd1Update==1)//窗口1要悉数更新显现
  318. {
  319. ucWd1Update=0;//及时清零标志,防止一向进来扫描
  320. ucDigShow8=12;//第8位数码管显现P
  321. ucDigShow7=11;//第7位数码管显现-
  322. ucDigShow6=1; //第6位数码管显现1
  323. ucDigShow5=10;//第5位数码管显现无
  324. //先分化数据
  325. ucTemp4=uiSetData1/1000;
  326. ucTemp3=uiSetData1%1000/100;
  327. ucTemp2=uiSetData1%100/10;
  328. ucTemp1=uiSetData1%10;
  329. //再过渡需求显现的数据到缓冲变量里,让过渡的时刻越短越好
  330. if(uiSetData1<1000)
  331. {
  332. ucDigShow4=10;//假如小于1000,千位显现无
  333. }
  334. else
  335. {
  336. ucDigShow4=ucTemp4;//第4位数码管要显现的内容
  337. }
  338. if(uiSetData1<100)
  339. {
  340. ucDigShow3=10;//假如小于100,百位显现无
  341. }
  342. else
  343. {
  344. ucDigShow3=ucTemp3;//第3位数码管要显现的内容
  345. }
  346. if(uiSetData1<10)
  347. {
  348. ucDigShow2=10;//假如小于10,十位显现无
  349. }
  350. else
  351. {
  352. ucDigShow2=ucTemp2;//第2位数码管要显现的内容
  353. }
  354. ucDigShow1=ucTemp1;//第1位数码管要显现的内容
  355. }
  356. break;
  357. case 2://显现P–2窗口的数据
  358. if(ucWd2Update==1)//窗口2要悉数更新显现
  359. {
  360. ucWd2Update=0;//及时清零标志,防止一向进来扫描
  361. ucDigShow8=12;//第8位数码管显现P
  362. ucDigShow7=11;//第7位数码管显现-
  363. ucDigShow6=2;//第6位数码管显现2
  364. ucDigShow5=10; //第5位数码管显现无
  365. ucTemp4=uiSetData2/1000; //分化数据
  366. ucTemp3=uiSetData2%1000/100;
  367. ucTemp2=uiSetData2%100/10;
  368. ucTemp1=uiSetData2%10;
  369. if(uiSetData2<1000)
  370. {
  371. ucDigShow4=10;//假如小于1000,千位显现无
  372. }
  373. else
  374. {
  375. ucDigShow4=ucTemp4;//第4位数码管要显现的内容
  376. }
  377. if(uiSetData2<100)
  378. {
  379. ucDigShow3=10;//假如小于100,百位显现无
  380. }
  381. else
  382. {
  383. ucDigShow3=ucTemp3;//第3位数码管要显现的内容
  384. }
  385. if(uiSetData2<10)
  386. {
  387. ucDigShow2=10;//假如小于10,十位显现无
  388. }
  389. else
  390. {
  391. ucDigShow2=ucTemp2;//第2位数码管要显现的内容
  392. }
  393. ucDigShow1=ucTemp1;//第1位数码管要显现的内容
  394. }
  395. break;
  396. case 3://显现P–3窗口的数据
  397. if(ucWd3Update==1)//窗口3要悉数更新显现
  398. {
  399. ucWd3Update=0;//及时清零标志,防止一向进来扫描
  400. ucDigShow8=12;//第8位数码管显现P
  401. ucDigShow7=11;//第7位数码管显现-
  402. ucDigShow6=3;//第6位数码管显现3
  403. ucDigShow5=10; //第5位数码管显现无
  404. ucTemp4=uiSetData3/1000; //分化数据
  405. ucTemp3=uiSetData3%1000/100;
  406. ucTemp2=uiSetData3%100/10;
  407. ucTemp1=uiSetData3%10;
  408. if(uiSetData3<1000)
  409. {
  410. ucDigShow4=10;//假如小于1000,千位显现无
  411. }
  412. else
  413. {
  414. ucDigShow4=ucTemp4;//第4位数码管要显现的内容
  415. }
  416. if(uiSetData3<100)
  417. {
  418. ucDigShow3=10;//假如小于100,百位显现无
  419. }
  420. else
  421. {
  422. ucDigShow3=ucTemp3;//第3位数码管要显现的内容
  423. }
  424. if(uiSetData3<10)
  425. {
  426. ucDigShow2=10;//假如小于10,十位显现无
  427. }
  428. else
  429. {
  430. ucDigShow2=ucTemp2;//第2位数码管要显现的内容
  431. }
  432. ucDigShow1=ucTemp1;//第1位数码管要显现的内容
  433. }
  434. break;
  435. case 4://显现P–4窗口的数据
  436. if(ucWd4Update==1)//窗口4要悉数更新显现
  437. {
  438. ucWd4Update=0;//及时清零标志,防止一向进来扫描
  439. ucDigShow8=12;//第8位数码管显现P
  440. ucDigShow7=11;//第7位数码管显现-
  441. ucDigShow6=4;//第6位数码管显现4
  442. ucDigShow5=10; //第5位数码管显现无
  443. ucTemp4=uiSetData4/1000; //分化数据
  444. ucTemp3=uiSetData4%1000/100;
  445. ucTemp2=uiSetData4%100/10;
  446. ucTemp1=uiSetData4%10;
  447. if(uiSetData4<1000)
  448. {
  449. ucDigShow4=10;//假如小于1000,千位显现无
  450. }
  451. else
  452. {
  453. ucDigShow4=ucTemp4;//第4位数码管要显现的内容
  454. }
  455. if(uiSetData4<100)
  456. {
  457. ucDigShow3=10;//假如小于100,百位显现无
  458. }
  459. else
  460. {
  461. ucDigShow3=ucTemp3;//第3位数码管要显现的内容
  462. }
  463. if(uiSetData4<10)
  464. {
  465. ucDigShow2=10;//假如小于10,十位显现无
  466. }
  467. else
  468. {
  469. ucDigShow2=ucTemp2;//第2位数码管要显现的内容
  470. }
  471. ucDigShow1=ucTemp1;//第1位数码管要显现的内容
  472. }
  473. break;
  474. }
  475. }
  476. void key_scan(void)//按键扫描函数 放在守时中止里
  477. {
  478. if(key_sr1==1)//IO是高电平,阐明按键没有被按下,这时要及时清零一些标志位
  479. {
  480. ucKeyLock1=0; //按键自锁标志清零
  481. uiKeyTimeCnt1=0;//按键去颤动延时计数器清零,此行十分奇妙,是我实战中探索出来的。
  482. }
  483. else if(ucKeyLock1==0)//有按键按下,且是榜首次被按下
  484. {
  485. uiKeyTimeCnt1++; //累加守时中止次数
  486. if(uiKeyTimeCnt1>const_key_time1)
  487. {
  488. uiKeyTimeCnt1=0;
  489. ucKeyLock1=1;//自锁按键置位,防止一向触发
  490. ucKeySec=1; //触发1号键
  491. }
  492. }
  493. if(key_sr2==1)//IO是高电平,阐明按键没有被按下,这时要及时清零一些标志位
  494. {
  495. ucKeyLock2=0; //按键自锁标志清零
  496. uiKeyTimeCnt2=0;//按键去颤动延时计数器清零,此行十分奇妙,是我实战中探索出来的。
  497. }
  498. else if(ucKeyLock2==0)//有按键按下,且是榜首次被按下
  499. {
  500. uiKeyTimeCnt2++; //累加守时中止次数
  501. if(uiKeyTimeCnt2>const_key_time2)
  502. {
  503. uiKeyTimeCnt2=0;
  504. ucKeyLock2=1;//自锁按键置位,防止一向触发
  505. ucKeySec=2; //触发2号键
  506. }
  507. }
  508. if(key_sr3==1)//IO是高电平,阐明按键没有被按下,这时要及时清零一些标志位
  509. {
  510. ucKeyLock3=0; //按键自锁标志清零
  511. uiKeyTimeCnt3=0;//按键去颤动延时计数器清零,此行十分奇妙,是我实战中探索出来的。
  512. }
  513. else if(ucKeyLock3==0)//有按键按下,且是榜首次被按下
  514. {
  515. uiKeyTimeCnt3++; //累加守时中止次数
  516. if(uiKeyTimeCnt3>const_key_time3)
  517. {
  518. uiKeyTimeCnt3=0;
  519. ucKeyLock3=1;//自锁按键置位,防止一向触发
  520. ucKeySec=3; //触发3号键
  521. }
  522. }
  523. if(key_sr4==1)//IO是高电平,阐明按键没有被按下,这时要及时清零一些标志位
  524. {
  525. ucKeyLock4=0; //按键自锁标志清零
  526. uiKeyTimeCnt4=0;//按键去颤动延时计数器清零,此行十分奇妙,是我实战中探索出来的。
  527. }
  528. else if(ucKeyLock4==0)//有按键按下,且是榜首次被按下
  529. {
  530. uiKeyTimeCnt4++; //累加守时中止次数
  531. if(uiKeyTimeCnt4>const_key_time4)
  532. {
  533. uiKeyTimeCnt4=0;
  534. ucKeyLock4=1;//自锁按键置位,防止一向触发
  535. ucKeySec=4; //触发4号键
  536. }
  537. }
  538. }
  539. void key_service(void) //按键服务的应用程序
  540. {
  541. switch(ucKeySec) //按键服务状况切换
  542. {
  543. case 1:// 加按键 对应朱兆祺学习板的S1键
  544. switch(ucWd)//在不同的窗口下,设置不同的参数
  545. {
  546. case 1:
  547. uiSetData1++;
  548. if(uiSetData1>9999) //最大值是9999
  549. {
  550. uiSetData1=9999;
  551. }
  552. ucWd1Update=1;//窗口1更新显现
  553. break;
  554. case 2:
  555. uiSetData2++;
  556. if(uiSetData2>9999) //最大值是9999
  557. {
  558. uiSetData2=9999;
  559. }
  560. ucWd2Update=1;//窗口2更新显现
  561. break;
  562. case 3:
  563. uiSetData3++;
  564. if(uiSetData3>9999) //最大值是9999
  565. {
  566. uiSetData3=9999;
  567. }
  568. ucWd3Update=1;//窗口3更新显现
  569. break;
  570. case 4:
  571. uiSetData4++;
  572. if(uiSetData4>9999) //最大值是9999
  573. {
  574. uiSetData4=9999;
  575. }
  576. ucWd4Update=1;//窗口4更新显现
  577. break;
  578. }
  579. ucVoiceLock=1;//原子锁加锁,维护主函数与中止函数的同享变量uiVoiceCnt
  580. uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
  581. ucVoiceLock=0;//原子锁解锁,维护主函数与中止函数的同享变量uiVoiceCnt
  582. ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
  583. break;
  584. case 2:// 减按键 对应朱兆祺学习板的S5键
  585. switch(ucWd)//在不同的窗口下,设置不同的参数
  586. {
  587. case 1:
  588. uiSetData1–;
  589. if(uiSetData1>9999)
  590. {
  591. uiSetData1=0;//最小值是0
  592. }
  593. ucWd1Update=1;//窗口1更新显现
  594. break;
  595. case 2:
  596. uiSetData2–;
  597. if(uiSetData2>9999)
  598. {
  599. uiSetData2=0;//最小值是0
  600. }
  601. ucWd2Update=1;//窗口2更新显现
  602. break;
  603. case 3:
  604. uiSetData3–;
  605. if(uiSetData3>9999)
  606. {
  607. uiSetData3=0;//最小值是0
  608. }
  609. ucWd3Update=1;//窗口3更新显现
  610. break;
  611. case 4:
  612. uiSetData4–;
  613. if(uiSetData4>9999)
  614. {
  615. uiSetData4=0;//最小值是0
  616. }
  617. ucWd4Update=1;//窗口4更新显现
  618. break;
  619. }
  620. ucVoiceLock=1;//原子锁加锁,维护主函数与中止函数的同享变量uiVoiceCnt
  621. uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
  622. ucVoiceLock=0;//原子锁解锁,维护主函数与中止函数的同享变量uiVoiceCnt
  623. ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
  624. break;
  625. case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  626. ucWd++;//切换窗口
  627. if(ucWd>4)
  628. {
  629. ucWd=1;
  630. }
  631. switch(ucWd)//在不同的窗口下,在不同的窗口下,更新显现不同的窗口
  632. {
  633. case 1:
  634. ucWd1Update=1;//窗口1更新显现
  635. break;
  636. case 2:
  637. ucWd2Update=1;//窗口2更新显现
  638. break;
  639. case 3:
  640. ucWd3Update=1;//窗口3更新显现
  641. break;
  642. case 4:
  643. ucWd4Update=1;//窗口4更新显现
  644. break;
  645. }
  646. ucVoiceLock=1;//原子锁加锁,维护主函数与中止函数的同享变量uiVoiceCnt
  647. uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
  648. ucVoiceLock=0;//原子锁解锁,维护主函数与中止函数的同享变量uiVoiceCnt
  649. ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
  650. break;
  651. case 4:// 发动发送数据和复位按键 对应朱兆祺学习板的S13键
  652. switch(ucStatus)//在不同的状况下,进行不同的操作
  653. {
  654. case 0://处于待机状况,则发动发送数据
  655. ucErrorCnt=0; //累计过错总数清零
  656. ucSendTotal=0; //现已发送串数据总数清零
  657. ucSendStep=0; //发送数据的进程进程清零,回来开端的进程待命
  658. ucStatus=1; //发动发送数据,1代表正在通讯进程
  659. break;
  660. case 1://处于正在通讯的进程
  661. break;
  662. case 2: //发送数据犯错,比方中心超时没有接纳到数据
  663. ucStatus=0; //切换回待机的状况
  664. break;
  665. }
  666. ucVoiceLock=1;//原子锁加锁,维护主函数与中止函数的同享变量uiVoiceCnt
  667. uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
  668. ucVoiceLock=0;//原子锁解锁,维护主函数与中止函数的同享变量uiVoiceCnt
  669. ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
  670. break;
  671. }
  672. }
  673. void display_drive(void)
  674. {
  675. //以下程序,假如加一些数组和移位的元素,还能够紧缩容量。可是鸿哥寻求的不是容量,而是明晰的解说思路
  676. switch(ucDisplayDriveStep)
  677. {
  678. case 1://显现第1位
  679. ucDigShowTemp=dig_table[ucDigShow1];
  680. if(ucDigDot1==1)
  681. {
  682. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  683. }
  684. dig_hc595_drive(ucDigShowTemp,0xfe);
  685. break;
  686. case 2://显现第2位
  687. ucDigShowTemp=dig_table[ucDigShow2];
  688. if(ucDigDot2==1)
  689. {
  690. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  691. }
  692. dig_hc595_drive(ucDigShowTemp,0xfd);
  693. break;
  694. case 3://显现第3位
  695. ucDigShowTemp=dig_table[ucDigShow3];
  696. if(ucDigDot3==1)
  697. {
  698. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  699. }
  700. dig_hc595_drive(ucDigShowTemp,0xfb);
  701. break;
  702. case 4://显现第4位
  703. ucDigShowTemp=dig_table[ucDigShow4];
  704. if(ucDigDot4==1)
  705. {
  706. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  707. }
  708. dig_hc595_drive(ucDigShowTemp,0xf7);
  709. break;
  710. case 5://显现第5位
  711. ucDigShowTemp=dig_table[ucDigShow5];
  712. if(ucDigDot5==1)
  713. {
  714. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  715. }
  716. dig_hc595_drive(ucDigShowTemp,0xef);
  717. break;
  718. case 6://显现第6位
  719. ucDigShowTemp=dig_table[ucDigShow6];
  720. if(ucDigDot6==1)
  721. {
  722. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  723. }
  724. dig_hc595_drive(ucDigShowTemp,0xdf);
  725. break;
  726. case 7://显现第7位
  727. ucDigShowTemp=dig_table[ucDigShow7];
  728. if(ucDigDot7==1)
  729. {
  730. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  731. }
  732. dig_hc595_drive(ucDigShowTemp,0xbf);
  733. break;
  734. case 8://显现第8位
  735. ucDigShowTemp=dig_table[ucDigShow8];
  736. if(ucDigDot8==1)
  737. {
  738. ucDigShowTemp=ucDigShowTemp|0x80;//显现小数点
  739. }
  740. dig_hc595_drive(ucDigShowTemp,0x7f);
  741. break;
  742. }
  743. ucDisplayDriveStep++;
  744. if(ucDisplayDriveStep>8)//扫描完8个数码管后,从头从榜首个开端扫描
  745. {
  746. ucDisplayDriveStep=1;
  747. }
  748. }
  749. //数码管的74HC595驱动函数
  750. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  751. {
  752. unsigned char i;
  753. unsigned char ucTempData;
  754. dig_hc595_sh_dr=0;
  755. dig_hc595_st_dr=0;
  756. ucTempData=ucDigStatusTemp16_09;//先送高8位
  757. for(i=0;i<8;i++)
  758. {
  759. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  760. else dig_hc595_ds_dr=0;
  761. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  762. delay_short(1);
  763. dig_hc595_sh_dr=1;
  764. delay_short(1);
  765. ucTempData=ucTempData<<1;
  766. }
  767. ucTempData=ucDigStatusTemp08_01;//再先送低8位
  768. for(i=0;i<8;i++)
  769. {
  770. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  771. else dig_hc595_ds_dr=0;
  772. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  773. delay_short(1);
  774. dig_hc595_sh_dr=1;
  775. delay_short(1);
  776. ucTempData=ucTempData<<1;
  777. }
  778. dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上而且锁存起来
  779. delay_short(1);
  780. dig_hc595_st_dr=1;
  781. delay_short(1);
  782. dig_hc595_sh_dr=0; //拉低,抗干扰就增强
  783. dig_hc595_st_dr=0;
  784. dig_hc595_ds_dr=0;
  785. }
  786. //LED灯的74HC595驱动函数
  787. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  788. {
  789. unsigned char i;
  790. unsigned char ucTempData;
  791. hc595_sh_dr=0;
  792. hc595_st_dr=0;
  793. ucTempData=ucLedStatusTemp16_09;//先送高8位
  794. for(i=0;i<8;i++)
  795. {
  796. if(ucTempData>=0x80)hc595_ds_dr=1;
  797. else hc595_ds_dr=0;
  798. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  799. delay_short(1);
  800. hc595_sh_dr=1;
  801. delay_short(1);
  802. ucTempData=ucTempData<<1;
  803. }
  804. ucTempData=ucLedStatusTemp08_01;//再先送低8位
  805. for(i=0;i<8;i++)
  806. {
  807. if(ucTempData>=0x80)hc595_ds_dr=1;
  808. else hc595_ds_dr=0;
  809. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  810. delay_short(1);
  811. hc595_sh_dr=1;
  812. delay_short(1);
  813. ucTempData=ucTempData<<1;
  814. }
  815. hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上而且锁存起来
  816. delay_short(1);
  817. hc595_st_dr=1;
  818. delay_short(1);
  819. hc595_sh_dr=0; //拉低,抗干扰就增强
  820. hc595_st_dr=0;
  821. hc595_ds_dr=0;
  822. }
  823. void usart_receive(void) interrupt 4 //串口接纳数据中止
  824. {
  825. if(RI==1)
  826. {
  827. RI = 0;
  828. ++uiRcregTotal;
  829. if(uiRcregTotal>const_rc_size)//超越缓冲区
  830. {
  831. uiRcregTotal=const_rc_size;
  832. }
  833. ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接纳到的数据缓存到接纳缓冲区里
  834. if(ucSendCntLock==0)//原子锁判别
  835. {
  836. ucSendCntLock=1; //加锁
  837. uiSendCnt=0;//及时喂狗,虽然在守时中止那儿此变量会不断累加,可是只需串口的数据还没发送结束,那么它永久也长不大,由于每个串口接纳中止它都被清零。
  838. ucSendCntLock=0; //解锁
  839. }
  840. }
  841. else//我在其它单片机上都不必else这段代码的,可能在51单片机上多添加” TI = 0;”稳定性会更好吧。
  842. {
  843. TI = 0;//假如不是串口接纳中止,那么必定是串口发送中止,及时铲除发送中止的标志,不然一向发送中止
  844. }
  845. }
  846. void T0_time(void) interrupt 1 //守时中止
  847. {
  848. TF0=0;//铲除中止标志
  849. TR0=0; //关中止
  850. /* 注释一:
  851. * 此处多添加一个原子锁,作为中止与主函数同享数据的维护,实践上是学习了”红金龙吸味”关于原子锁的主张.
  852. */
  853. if(ucSendCntLock==0)//原子锁判别
  854. {
  855. ucSendCntLock=1; //加锁
  856. if(uiSendCnt
  857. {
  858. uiSendCnt++; //表面上这个数据不断累加,可是在串口中止里,每接纳一个字节它都会被清零,除非这个中心没有串口数据过来
  859. ucSendLock=1; //开自锁标志
  860. }
  861. ucSendCntLock=0; //解锁
  862. }
  863. if(ucVoiceLock==0) //原子锁判别
  864. {
  865. if(uiVoiceCnt!=0)
  866. {
  867. uiVoiceCnt–; //每次进入守时中止都自减1,直到等于零中止。才中止鸣叫
  868. beep_dr=0;//蜂鸣器是PNP三极管操控,低电平就开端鸣叫。
  869. }
  870. else
  871. {
  872. ; //此处多加一个空指令,想保持跟if括号句子的数量对称,都是两条指令。不加也能够。
  873. beep_dr=1;//蜂鸣器是PNP三极管操控,高电平就中止鸣叫。
  874. }
  875. }
  876. if(ucStatus!=0) //处于非待机的状况,Led闪耀
  877. {
  878. if(ucLedLock==0)//原子锁判别
  879. {
  880. uiLedCnt++; //Led闪耀计时器不断累加
  881. }
  882. }
  883. if(ucStatus==1) //处于正在通讯的状况,
  884. {
  885. if(ucSendTimeOutLock==0)//原子锁判别
  886. {
  887. uiSendTimeOutCnt++; //超时计时器累加
  888. }
  889. }
  890. key_scan(); //按键扫描函数
  891. display_drive();//数码管字模的驱动函数
  892. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  893. TL0=0x0b;
  894. TR0=1;//开中止
  895. }
  896. void delay_short(unsigned int uiDelayShort)
  897. {
  898. unsigned int i;
  899. for(i=0;i
  900. {
  901. ; //一个分号相当于履行一条空句子
  902. }
  903. }
  904. void delay_long(unsigned int uiDelayLong)
  905. {
  906. unsigned int i;
  907. unsigned int j;
  908. for(i=0;i
  909. {
  910. for(j=0;j<500;j++)//内嵌循环的空指令数量
  911. {
  912. ; //一个分号相当于履行一条空句子
  913. }
  914. }
  915. }
  916. void initial_myself(void)//榜首区 初始化单片机
  917. {
  918. /* 注释二:
  919. * 矩阵键盘也能够做独立按键,条件是把某一根公共输出线输出低电平,
  920. * 模仿独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  921. * 朱兆祺51学习板的S1便是本程序中用到的一个独立按键。
  922. */
  923. key_gnd_dr=0; //模仿独立按键的地GND,因而有必要一向输出低电平
  924. led_dr=1;//点亮独立LED灯
  925. beep_dr=1; //用PNP三极管操控蜂鸣器,输出高电平时不叫。
  926. hc595_drive(0x00,0x00);//封闭一切通过别的两个74HC595驱动的LED灯
  927. TMOD=0x01;//设置守时器0为工作方式1
  928. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  929. TL0=0x0b;
  930. //装备串口
  931. SCON=0x50;
  932. TMOD=0X21;
  933. /* 注释三:
  934. * 为了确保串口中止接纳的数据不丢掉,有必要设置IP = 0x10,相当于把串口中止设置为最高优先级,
  935. * 这个时分,串口中止能够打断任何其他的中止服务函数完结嵌套,
  936. */
  937. IP =0x10;//把串口中止设置为最高优先级,有必要的。
  938. TH1=TL1=-(11059200L/12/32/9600);//串口波特率为9600。
  939. TR1=1;
  940. }
  941. void initial_peripheral(void) //第二区 初始化外围
  942. {
  943. ucDigDot8=0; //小数点悉数不显现
  944. ucDigDot7=0;
  945. ucDigDot6=0;
  946. ucDigDot5=0;
  947. ucDigDot4=0;
  948. ucDigDot3=0;
  949. ucDigDot2=0;
  950. ucDigDot1=0;
  951. EA=1; //开总中止
  952. ES=1; //答应串口中止
  953. ET0=1; //答应守时中止
  954. TR0=1; //发动守时中止
  955. }

总结陈词:
前面花了很多篇幅具体地解说了串口收发数据的程序结构,从下一节开端我解说单片机掉电后数据保存的内容,欲知概况,请听下回分化—–使用AT24C02进行掉电后的数据保存。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/bandaoti/zhizao/262077.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部