《嵌入式硬件(三):串口通信》
UART通信基础概念
并行与串行
- 并行:同一时刻可传输多个比特
- 串行:同一时刻仅传输一个比特
通信模式
- 全双工:收发可同时进行
- 半双工:收发不可同时进行
- 单工:单向通信
串口通信格式(TTL电平)
串口通信格式,以TTL为例
- 空闲时数据线为高电平;
- 发送发发送一个低电平表示起始位;
- LSB低位,MSB高位(先发低位),发送的第一个比特是最低为(最右边);
- 校验位分为奇校验,偶校验和无校验。奇校验是指确保数据位加上校验位中"1",1的总数为奇数;偶校验是指确保数据位加上校验位中"1",1的总数为偶数;Odd奇校验,enev,偶校验
- 为保证下一个字节发送前的起始位能够表现出来,校验位之后发送一个停止位1。
参数表示
- 示例:
9600, n, 8, 1
- 波特率:9600
- 校验位:无(
n
),Odd奇校验,enev偶校验 - 数据位:8
- 停止位:1
异步与同步判断
- 同步:存在时钟线(SCL)
- 异步:无时钟线
长距离通信问题与解决方案
主机间通信时的电器物理问题
主机间通信无论采用并行还是串行方式,都无法避免一个物理现象:导线内阻不为零造成的电压衰减。以之前讨论的TTL电平为例,主机之间的距离会造成高电平在接收端出现衰减现象和串扰(指不同信号之间相互干扰导致信号失真)影响。TTL(Transistor-Transistor Logic)通常指的就是芯片引脚产生的电压,这个电压值跟选择的芯片有关,在51单片机系统下是5v;在2440下是3.3v等等。5vTTL通信距离通常被限制在10~20米之间。
解决长距离通信问题
为解决这个问题IEEE(Institute of Electrical and Electronics Engineers)颁布了RS232标准,其中规定了:
逻辑高电平(逻辑1):在-3V到-15V之间
逻辑低电平(逻辑0):在+3V到+15V之间
收发主机间有三根线,分别是收、发和地,因此RS232是全双工的。理论上RS232能够传输20~30米。
RS485的优势
同理RS485使用两根信号线(A和B)来传输数据,通过比较A和B之间的电压差来识别信息,电压范围分别为+7V到+12V和-7V到-12V。正电压表示高电平,负电压表示低电平。这种差分信号传输方式提高了抗干扰能力。RS485的传输距离可达1200米,适用于大范围的数据传输需求。由于采用的是压差,RS485在传输数据的某一时刻,两根线都要用到,所以它是半双工的。
串口寄存器与波特率计算
寄存器配置
- SCON:串口控制寄存器
- PCON:电源控制寄存器(波特率倍增)
波特率公式
2^8-2^smod * focs / 32 / bps / 12
代码示例:数据类型大小测量
#include <reg52.h>
#include "delay.h"
#include <stdio.h>
#include <string.h>void init_uart(void)
{unsigned char t;t = SCON;t &= ~(3 << 6);t |= (1 << 6);t |= (1 << 4);SCON = t;t = IE;t |= (1 << 7);t |= (1 << 3);IE = t;PCON |= (1 << 7);t = TMOD;t &= ~(3 << 4);t |= (2 << 4);t &= ~(3 << 6);TMOD = t;TH1 = 204;TL1 = 204;TCON = (1 << 6);
}void send_char(char ch)
{SBUF = ch;while(0 == (SCON & (1 << 1)));SCON &= ~(1 << 1);
}void send_buffer(const char *p, int len)
{while(len--){send_char(*p++);}
}int main(void)
{int x;char y;const char *s = "Hello World!";int n = 10, m = 20;xdata char buffer[32];xdata char buffer1[1];init_uart();while(1){x = sizeof(int);sprintf(buffer, "size int = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(char);sprintf(buffer, "size char = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(float);sprintf(buffer, "size float = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(short);sprintf(buffer, "size short = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(double);sprintf(buffer, "size double = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(s);sprintf(buffer, "size * = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(short);sprintf(buffer, "size short = %d\n", x);send_buffer(buffer, strlen(buffer));x = sizeof(long);sprintf(buffer, "size long = %d\n", x);send_buffer(buffer, strlen(buffer));x = y;sprintf(buffer1, "big or small = %d", x);send_buffer(buffer1, strlen(buffer1));delay(0x9FFF); }return 0;
}
输出结果示例
size int = 2
size char = 1
size float = 4
size short = 2
size double = 4
size * = 3
size short = 2
big or small = 0(大端)
主从通信协议解析
数据帧格式
- 示例:
AA 01 01 04 D2 82 0D
- 起始符:
AA
- 从机地址:
01
- 功能码:
01
- 数据长度/寄存器地址:
04
- CRC16校验:
D2 82
- 结束符:
0D
- 起始符:
代码实现(解析帧)
#include <reg52.h>
#include "delay.h"
#include <stdio.h>
#include <string.h>
#include "digiter.h"void init_uart(void)
{unsigned char t;t = SCON;t &= ~(3 << 6);t |= (1 << 6) | (1 << 4);SCON = t;PCON |= (1 << 7);IE |= (1 << 7) | (1 << 4);t = TMOD;t &= ~(3 << 4);t |= (2 << 4);t &= ~(3 << 6);TMOD = t;TH1 = 204;TL1 = 204;TCON |= (1 << 6);
}xdata char rcv_buffer[64] = {0};
int pos = 0; void uart_handler(void) interrupt 4
{if((SCON & (1 << 0)) != 0){rcv_buffer[pos++] = SBUF;SCON &= ~(1 << 0);}
}void send_char(char ch)
{SBUF = ch;while((SCON & (1 << 1)) == 0);SCON &= ~(1 << 1);
}void send_buffer(const char *p, int len)
{while(len--){send_char(*p++);}
}int digiter_num = 0;unsigned char sumOfTheArray(unsigned char *p, int len)
{unsigned char sum = 0;int i;for(i = 0;i < len;++i){sum += p[i];}return sum;
}void parse(void) //0xAA 0000 0000 1010 1010 unsigned char
{if((unsigned char)rcv_buffer[0] == 0xAA && (unsigned char)rcv_buffer[pos - 1] == 0x0D){if((unsigned char)rcv_buffer[1] == 0x01){if(sumOfTheArray(rcv_buffer, 5) == (unsigned char)rcv_buffer[5]){unsigned char order;order = rcv_buffer[2];switch(order){case 1:digiter_num = 1234;break;case 2:P2 = 0; break;default:break;}}}}
}int main(void)
{init_uart();while(1){if(pos != 0){delay(0xFFFF); //0x04 0xD2parse(); pos = 0;memset(rcv_buffer, 0, sizeof(rcv_buffer));}show_number(digiter_num);}return 0;
}
关键点总结
- 校验和计算需匹配帧内数据
- 中断处理接收数据时需清标志位