5.串口通信
串口的介绍
UART(通用异步收发器)是一种双向、串行、异步的通信总线,仅用一根数据接收线(RX)和一根数据发送线(TX)就能实现全双工通信
R:Receiver(接收),T:Transmit(发送)
UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位
如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据,校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。(奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时, 对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数)
UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认), 1.5或2位
串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等
如波特率9600则代表每秒传输9600bit数据,以串口发送1个字节10bit算(起始位1bit+数据8bit+停止位1bit+NO校验位),则传输1个字节需要的时间是1*10/9600秒
USART:通用同步/异步串行接收/发送器
TTL
一般单片机产生的都是TTL电平。无论是51、32、还是各种跑Linux的ARM芯片,TTL满足绝大多数调试需求
TTL串口采用的是单一的信号线 (TX线和RX线) 进行数据传输,其中TX线用于单片机发送数据,RX线用于单片机接收数据。数据传输速率通常可以达到几-kbps至上百kbps的速率,传输距离较短,一般不超过数十米
TTL转USB
TTL的逻辑电平通常是0V和5V,其中0V表示逻辑“0”,5V表示逻辑“1”
RS-232
RS-232转USB
规定逻辑“1”的电平为-5V~-15 V,逻辑“0”的电平为+5 V~+15 V
由于RS -232采用串行传送方式,并且将TTL电平(某芯片)转换为RS-232C电平,其传送距离一般可达30 m。若采用光电隔离20 mA的电流环进行传送,其传送距离可以达到1000 m
RS-485
传输速度可以达到10Mb/s以上,传输距离可以达到3000米左右
传输方式为:差分方式
分类方式
串口并口
同步异步
异步:通信双方各自约定通信速率
UART:TX、RX
1 Wire
同步:通信双方靠一根时钟线来约定通信速率
IIC:SDA、SCL
SPI:MOSI、MISO、SCK、CS
单工:指消息只能单方向传输的工作方式
半双工:指信息即可从A到B,也可以从B到A,任一时刻只能有一个方向上的传输存在
全双工:指在任意时刻线路上存在A到B和B到A的双向信号传输
相关寄存器
SCON
工作模式:01(8位UART波特率可变)其他不用为0
REN = 1,接收信息,一开始发送中断标志位、接收中断标志位置0
TB8:发送校验位,RB8:接收校验位,TI:发送中断,RI:接收中断
则SCON = 0x50
PCON
TMOD
模式(M1、M0):这里选10(8位自动重装载定时器),其他在定时器有讲解
SBUF
接收和发送的数据都存在SBUF
IE
中断允许寄存器
中断处理函数
中断源 | 中断处理函数 |
---|---|
UART | UART_Routine(void) interrupt 4 |
代码编写
为何需要使用定时器观看链接讲解(9.34):波特率发生器
main.c
#include #include "delay.h" //声明头文件,可在此文件使用头文件声明的函数、变量等 #include "Uart.h" uchar recv; //全局变量用于接收数据 void main() { UART_Init(); while(1) { Delay1000ms(); UART_send_str("i am xingzai"); //发送字符 } } //串口中断处理函数 UART_Routine(void) interrupt 4 { if(1 == RI) { RI = 0; //接收中断请求标志位软件复位 recv = SBUF; //接收数据赋值给变量 switch(recv) { case 0x1: LED1 = 0;break; case 0x2: LED2 = 0;break; case 0x3: LED3 = 0;break; case 0x4: LED4 = 0;break; case 0x5: P1 |= 0x0F;break; //这里采用或等于,目的不让P1.6蜂鸣器为0,保留高位原数据 case 0x6: BEEP = 0;break; case 0x7: BEEP = 1;break; case 0x8: JDQ1 = 0;break; case 0x9: JDQ1 = 1;break; } } }
delay.c
#include "delay.h" //声明对应头文件,用于声明里面此文件函数、变量等 #include #include "main.h" //延时函数 void Delay1000ms() //@11.0592MHz { uchar data i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); }
delay.h
//头文件固定格式,防止头文件重复包含 #ifndef __DELAY_H__ #define __DELAY_H__ void Delay1000ms(); #endif
Uart.c
#include #include "Uart.h" void UART_Init() { //配置相关寄存器 SCON = 0x50; //配置串口寄存器 PCON=0x00; TMOD |= 0x20; //配置时间寄存器定时器1 //定时器初值 TH1 = 0xFD; TL1 = 0xFD; EA = 1; //打开中断总允许位 ES = 1; //打开串口中断允许位 TR1 = 1; //打开定时器1运行控制位 } //字符发送函数 void UART_send_char(uchar send_char) { //将字符赋值给数据缓存寄存器 SBUF = send_char; while(!TI); //发送时为0,发送完触发发送中断时为1,为1时则数据发送完 TI = 0; //发送中断请求标志位软件复位 } //接收传过来的字符串 void UART_send_str(uchar *send_str) { //判断字符到最后'\0'则停止发送 while(*send_str != '\0') UART_send_char(*send_str++); //将字符逐个传给这个函数发送 }
Uart.h
#ifndef __UART_H__ #define __UART_H__ //因为main.c中已经声明了Uart.h,在Uart.h声明了main.h则main.c中也可以使用mian.h #include "main.h" void UART_Init(); void UART_send_char(unsigned char send_char); void UART_send_str(unsigned char *send_str); #endif
main.h
#ifndef __MAIN_H__ #define __MAIN_H__ #include "reg52.h" typedef unsigned char uchar; //给unsigned char取别名uchar //定义对应元器件引脚 sbit LED1 = P1^0; sbit LED2 = P1^1; sbit LED3 = P1^2; sbit LED4 = P1^3; sbit BEEP = P1^6; sbit JDQ1 = P1^7; #endif
回环
#include "reg52.h" #include unsigned char recv; unsigned char send_buf[20]; sbit LED1 = P1^0; sbit LED2 = P1^1; sbit LED3 = P1^2; sbit LED4 = P1^3; sbit BEEP = P1^6; sbit JDQ1 = P1^7; void Delay1000ms() //@11.0592MHz { unsigned char data i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); } void UART_send_byte(unsigned char send_byte) { SBUF = send_byte; while(!TI); TI=0; } void UART_send_str(unsigned char *send_str) { while(*send_str != '\0') { UART_send_byte(*send_str++); } } void main() { SCON = 0x50; PCON=0x00; TMOD |= 0x20; TH1 = 0xFD; TL1 = 0xFD; EA = 1; ES = 1; TR1 = 1; while(1) { Delay1000ms(); // UART_send_str("i am wfeng!\r\n"); } } UART_Routine(void) interrupt 4 { if(1 == RI) //if(RI) { RI=0; recv = SBUF; SBUF = recv; while(!TI); TI=0; } }
继电器
继电器模块的基本工作原理:
电磁线圈: 继电器内部包含一个电磁线圈,通常由绕制在绝缘芯片上的细导线组成。当通过线圈通电时,产生电磁场。
磁性吸引: 电磁场会使继电器中的铁芯(或磁性材料)受到磁性吸引,导致铁芯在电磁力的作用下移动。
触点操作: 铁芯的移动会导致机械部分的运动,最终使触点(开关)发生动作。继电器通常有常开(Normally Open,NO)和常闭(Normally Closed,NC)两组触点。
常开触点: 在继电器未通电时处于闭合状态,当电磁线圈通电时,触点打开。
常闭触点: 在继电器未通电时处于打开状态,当电磁线圈通电时,触点闭合。
电气隔离: 继电器的主要作用之一是提供电气隔离。通过电磁原理,可以在控制信号与被控制电路之间提供隔离,从而使得不同电路之间的电流不会相互影响。继电器模块常用于控制高电流或高电压的电路。
ESP8266
AP模式:无线接入点,它是一个无线网络的中心节点,可以看成是一个服务器。它作为一个网络的中心节点,提供无线接入服务,其他的无线设备允许接入该节点,所有接入该节点设备的无线信号数据都要通过它才能进行交换和互相访问。一般的无线路由器、网关、热点就是工作在AP模式下,AP节点和AP节点之间允许相互连接。
STA模式:无线网络中的一个终端站点设备,可以看成是一个客户端,一般来说,处在STA模式下的设备本身不接受无线的接入,该设备连接到AP节点进行网络访问,STA模式下的设备之间的通信可以通过AP进行转发实现
AT指令
可在官网寻找对应的指令集:安信可科技
配置 WiFi 模式:AT+CWMODE=3 //模式3:STA+AP 连接路由器:AT+CWJAP="wfeng","wf05430543" 查询 ESP8266的IP 地址: AT+CIFSR //PC端使用网络调试工具,建⽴一个 TCP 服务器器 ESP8266 作为Client 连接到服务器:AT+CIPSTART="TCP","192.168.31.118",8090 发送数据:AT+CIPSEND=4
代码编写
电脑当服务端,单片机当客户端,电脑发指令控制单片机(接收),单片机发送字符给电脑
main.c
#include "define.h" #include "send.h" #include "delay.h" void main() { define_bl(); do{ send_str("AT+CWMODE=3\r\n"); //发送AT指令配置ESP8266模块,选择模式3 Delay1000ms(); }while(Esp_Ok_flag); Esp_Ok_flag = 1; do{ send_str("AT+CWJAP=\"qiji\",\"12345678\"\r\n"); //连接WiFi Delay1000ms(); }while(Esp_Ok_flag); Esp_Ok_flag = 1; do{ send_str("AT+CIPSTART=\"TCP\",\"192.168.43.10\",8085\r\n"); //连接服务器 Delay1000ms(); }while(Esp_Ok_flag); do{ send_str("AT+CIPSEND=4\r\n"); //发送4个字节数据 Delay1000ms(); }while(Esp_Ok_flag); send_str("xinz\r\n"); //发送的数据 Esp_Ok_flag = 1; while(1) { Delay1000ms(); } }
define.c
#include int Esp_Ok_flag = 1; void define_bl() { SCON = 0x50; PCON = 0x00; TMOD = 0x20; TH1 = 0xFD; TL1 = 0xFD; TR1 = 1; ES = 1; EA = 1; }
define.h
#ifndef __DELAY_H__ #define __DELAY_H__ #include "reg52.h" void define_bl(); typedef unsigned char uchar; //取别名 extern int Esp_Ok_flag; //定义一个全局变量方便不同.c文件使用 //定义对应元器件引脚 sbit LED1 = P1^0; sbit LED2 = P1^1; sbit LED3 = P1^2; sbit LED4 = P1^3; sbit BEEP = P1^6; sbit JDQ1 = P1^7; #endif
send.c
#include //接收需要发送的字符 void send_str(uchar *send_s) { while(*send_s != '\0') //当字符串!='\0'时把字符一个个传给send_char函数发送 { send_char(*send_s++); //将每个字符逐一发送 } } void send_char(uchar send_c) { SBUF = send_c; //将单个字符赋值给SBUF则发送 while(!TI); //当TI为0,则发送未完成,当发送完成为1,取反为0跳出循环 TI = 0; //复位,发送请求中断标志位为0 }
send.h
#ifndef __SEND_H__ #define __SEND_H__ #include "define.h" //声明两个发送函数 void send_str(uchar *send_s); void send_char(uchar send_c); #endif
receive.c
#include uchar Recv_Buf[20]; char recv; //串口中断函数 UART_Routine(void) interrupt 4 { static int i = 0; //static:全局使用不重新 char Recv; if(1 == RI) //if(RI) { RI=0; Recv=SBUF; if(Recv == 'O'|| Recv == '+') { i = 0; Recv_Buf[i] = Recv; Recv_Buf[i+1] = Recv; }else{ i++; Recv_Buf[i] = Recv; } if(Recv_Buf[0] == 'O'&&Recv_Buf[1] == 'K') //收到回复OK则说明指令执行成功 { Esp_Ok_flag = 0; //让标志为0跳出循环,执行下一个指令 i = 0; //重置数组指向位数 } //接收到服务器,接收到的指令会以 +IPD 开头,以及第七个是接收的指令开始,判断接收到哪个指令执行下方的操作 if(Recv_Buf[0] == '+'&&Recv_Buf[1] == 'I'&&Recv_Buf[2] == 'P'&&Recv_Buf[3] == 'D') { if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '1'&&Recv_Buf[11] == '1') LED1 = 1; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '1'&&Recv_Buf[11] == '0') LED1 = 0; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '2'&&Recv_Buf[11] == '1') LED2 = 1; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '2'&&Recv_Buf[11] == '0') LED2 = 0; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '3'&&Recv_Buf[11] == '1') LED3 = 1; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '3'&&Recv_Buf[11] == '0') LED3 = 0; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '4'&&Recv_Buf[11] == '1') LED4 = 1; if(Recv_Buf[7] == 'L'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'D'&&Recv_Buf[10] == '4'&&Recv_Buf[11] == '0') LED4 = 0; if(Recv_Buf[7] == 'B'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'E'&&Recv_Buf[10] == 'P'&&Recv_Buf[11] == '1') BEEP = 1; if(Recv_Buf[7] == 'B'&&Recv_Buf[8] == 'E'&&Recv_Buf[9] == 'E'&&Recv_Buf[10] == 'P'&&Recv_Buf[11] == '0') BEEP = 0; if(Recv_Buf[7] == 'J'&&Recv_Buf[8] == 'D'&&Recv_Buf[9] == 'Q'&&Recv_Buf[10] == '1'&&Recv_Buf[11] == '1') JDQ1 = 1; if(Recv_Buf[7] == 'J'&&Recv_Buf[8] == 'D'&&Recv_Buf[9] == 'Q'&&Recv_Buf[10] == '1'&&Recv_Buf[11] == '0') JDQ1 = 0; } if(i>18) i=18; } }
delay.c
#include #include //延时函数 void Delay1000ms() //@11.0592MHz { unsigned char data i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); }
delay.h
#ifndef __DELAY_H__ #define __DELAY_H__ //声明延时函数 void Delay1000ms(); #endif