目录
[8.串口ARM + Linux 开发板实现](#8.串口ARM + Linux 开发板实现)
一、串口协议
1.串口基本认知
串行接口简称串口,也称串行通信 接口或串行通讯接口 (通常指COM接口),是采用串行通信方 式的扩展接口 。串行接口 (Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简 单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成 本,特别适用于远距离通信,但传送速度较慢。
- 是设备间接线通信的一种方式
- 数据一位一位地顺序传送
- 双向通信,全双工(两条线,一条发送数据(TX),一条接收数据(RX))
- 传送速度相对较慢
串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C、RS-422与RS-485 标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
2.RS-232
RS-232 也称标准串口 ,最常用的一种串行通讯接口,比如我们的电脑主机的9针串口 ,最高速率为20kb/s。
RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其传送距离最大为约15米。所以RS-232适合本地设备之间的通信。
3.RS-422
由于接收器采用高输入阻抗和发送驱动器 比RS232更强的驱动能力 ,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。
RS-422最大传输距离为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比。
4.RS-485
RS-485是从RS-422基础上发展而来的,无论四线还是二线连接方式总线上可多接到32个设备。
5.串口的电平
异步串行是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。 UART包含TTL电平的串口和RS232电平的串口
(1)RS232电平:
逻辑1为 -3 ~ -15 V 的电压, 逻辑0为 3 ~ 15V 的电压
笔记本通过RS232电平和单片机通信:
(2)TTL电平:
TTL是Transistor-Transistor Logic,即晶体管-晶体管逻辑的简称,它是计算机处理器控制的设备 内部各部分之间通信的标准技术。TTL电平信号应用广泛,是因为其数据表示采用二进制规定, +5V等价于逻辑"1",0V等价于逻辑"0"。
数字电路中,由TTL电子元器件组成电路的电平是个电压范围,规定:
输出高电平>=2.4V,输出低电平=2.0V
输入高电平>=2.0V,输入低电平<=0.8V
笔记本通过TTL电平和单片机通信:使用USB转TTL(CH340)模块
6.串口51开发板实现
输入/输出数据缓冲器都叫做SBUF, 都用99H地址码,但是是两个独立的8位寄存器
代码体现为: 想要接收数据 char data_msg = SBUF 想要发送数据 SBUF = data_msg
(1)软件自动配置:
根据软件自动生成:
软件延时一秒:
代码示例:发送字符z
cpp
#include "reg52.h"
#include "intrins.h"
sfr AUXR = 0x8E;
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
char data_msg = 'z';
UartInit();
while(1) {
Delay1000ms();
SBUF = data_msg; // 往发送缓冲区写入数据,完成数据发送
}
}
如图:
(2)自己手动配置
在这里我们得通过查看数据手册了解相关寄存器的配置以及串口的工作模式,例如:
代码示例:发送字符串zgl nb
cpp
#include "reg52.h"
#include "intrins.h"
sfr AUXR = 0x8E;
void UartInit(void) //9600bps@11.0592MHz
{
AUXR = 0x01;
SCON = 0x40; //配置串口工作方式1,REN不使能接收
TMOD &= 0x0F;
TMOD |= 0x20;//定时器1工作方式位8位自动重装
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率的初值
TR1 = 1;//启动定时器
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void sendByte(char data_msg)
{
SBUF = data_msg;
while(!TI);
TI = 0;
}
void sendString(char* str)
{
while( *str != '\0'){
sendByte(*str);
str++;
}
}
void main()
{
//配置C51串口的通信方式
UartInit();
while(1){
Delay1000ms();
sendString("zgl nb\r\n"); // 往发送缓冲区写入数据,就完成数据的发送
}
}
7.串口STM32开发板实现
(1)常用函数介绍
串口发送/接收函数:
- HAL_UART_Transmit(); 串口发送数据,使用超时管理机制
- HAL_UART_Receive(); 串口接收数据,使用超时管理机制
- HAL_UART_Transmit_IT(); 串口中断模式发送
- HAL_UART_Receive_IT(); 串口中断模式接收
cpp
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
作用:以阻塞的方式发送指定字节的数据
- 形参 1:UART_HandleTypeDef 结构体类型指针变量
- 形参 2:指向要发送的数据地址
- 形参 3:要发送的数据大小,以字节为单位
- 形参 4:设置的超时时间,以ms单位
cpp
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size)
作用:以中断的方式接收指定字节的数据
- 形参 1:是 UART_HandleTypeDef 结构体类型指针变量
- 形参 2:是指向接收数据缓冲区
- 形参 3:是要接收的数据大小,以字节为单位,此函数执行完后将清除中断,需要再次调用以重新开启中断。
串口中断回调函数:
- HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
- HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //发送中断回调函数
- HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收中断回调函数
(2)状态标记变量:
USART_RX_STA
从0开始,串口中断接收到一个数据(一个字节)就自增1。当数据读取全部OK 时候(回车和换行 符号来的时候),那么 USART_RX_STA的最高位置1,表示串口数据接收全部完毕了,然后main 函数里面可以处理数据了。
(3)串口接收中断流程
(4)串口通信非中断实现
我用STM32CubeMX配置如下,仅供参考:
RCC:配置外部高速晶振
SYS:Debug设置成Serial Wire
时钟树配置:
串口配置:
代码示例:将接收到的数据发送到串口
cpp
#include <string.h>
// main.c
HAL_UART_Transmit(&huart1, "hello zgl\n", strlen("hello zgl\n"), 100);
unsigned char ch[20] = {0};
while (1)
{
HAL_UART_Receive(&huart1, ch, 19, 100);
HAL_UART_Transmit(&huart1, ch, strlen(ch), 100);
memset(ch, 0, strlen(ch));
}
使用串口重映射功能,打开MicroLIB库
代码示例:printf 替换 HAL_UART_Transmit
cpp
#include <stdio.h>
#include <string.h>
unsigned char ch[20] = {0};
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
// main函数部分
HAL_UART_Transmit(&huart1, "hello zgl\n", strlen("hello zgl\n"), 100);
while (1)
{
HAL_UART_Receive(&huart1, ch, 19, 100);
printf(ch);
memset(ch, 0, strlen(ch));
}
(5)串口通信中断实现
通过中断的方法接受串口调试助手发送的字符串,并将其发送回串口调试助手。
CubeMX新增加一个打开中断,其它同上
代码示例:
cpp
#include <stdio.h>
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
//串口接收缓存(1字节)
uint8_t buf=0;
//定义最大接收字节数 200,可根据需求调整
#define UART1_REC_LEN 200
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];
// 接收状态
// bit15, 接收完成标志
// bit14, 接收到0x0d
// bit13~0, 接收到的有效字节数目
uint16_t UART1_RX_STA=0;
// 接收完成回调函数,收到一个数据后,在这里处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 判断中断是由哪个串口触发的
if(huart->Instance == USART1)
{
// 判断接收是否完成(UART1_RX_STA bit15 位是否为1)
if((UART1_RX_STA & 0x8000) == 0)
{
// 如果已经收到了 0x0d (回车),
if(UART1_RX_STA & 0x4000)
{
// 则接着判断是否收到 0x0a (换行)
if(buf == 0x0a)
// 如果 0x0a 和 0x0d 都收到,则将 bit15 位置为1
UART1_RX_STA |= 0x8000;
else
// 否则认为接收错误,重新开始
UART1_RX_STA = 0;
}
else // 如果没有收到了 0x0d (回车)
{
//则先判断收到的这个字符是否是 0x0d (回车)
if(buf == 0x0d)
{
// 是的话则将 bit14 位置为1
UART1_RX_STA |= 0x4000;
}
else
{
// 否则将接收到的数据保存在缓存数组里
UART1_RX_Buffer[UART1_RX_STA & 0X3FFF] = buf;
UART1_RX_STA++;
// 如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
if(UART1_RX_STA > UART1_REC_LEN - 1)
UART1_RX_STA = 0;
}
}
}
// 重新开启中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
}
}
// main函数部分
// 开启接收中断
HAL_UART_Receive_IT(&huart1, &buf, 1);
while (1)
{
//判断判断串口是否接收完成
if(UART1_RX_STA & 0x8000)
{
printf("收到数据:");
// 将收到的数据发送到串口
HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);
// 等待发送完成
while(huart1.gState != HAL_UART_STATE_READY);
printf("\r\n");
// 重新开始下一次接收
UART1_RX_STA = 0;
}
printf("hello zgl \r\n");
HAL_Delay(1000);
}
8.串口ARM + Linux 开发板实现
以香橙派和树莓派示例,可以看我之前写过的文章(比较简略)
香橙派:
linux下实现串口功能_linux 串口实例-CSDN博客
树莓派:
二、I2C协议
1.IIC协议概述
- IIC全称Inter-Integrated Circuit (集成电路总线)
- 是由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。IIC属于半双工同步通信方式(只有一根双向的数据线SDA)
特点
- 简单性和有效性
由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件
- 多主控(multimastering)
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当 然,在任何时间点上只能有一个主控。
构成
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控 器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线 的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。
2.IIC总线传输
IIC总线在传输数据的过程中一共有三种类型信号,分别为:起始信号、结束信号和应答信号,同时还要进行数据发送。
(1)起始信号和结束信号:
代码示例:
cpp
void IIC_Start()
{
scl = 1;
sda = 1;
_nop_();
sda = 0;
_nop_();
}
void IIC_Stop()
{
scl = 1;
sda = 0;
_nop_();
sda = 1;
_nop_();
}
(2)应答信号:
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
代码示例:
cpp
char IIC_ACK()
{
char flag;
scl = 1; // 在时钟脉冲期间释放数据线
_nop_();
sda = 0;
flag = sda;
_nop_();
sda = 1;
_nop_();
return flag;
}
(3)数据发送的时序
代码示例:
cpp
void IIC_Send_Byte(char dataSend)
{
int i;
/* 发一位字节数据 */
for (i = 0; i < 8; i++)
{
scl = 0; // 时钟线拉低,让数据线做好开始发送准备
sda = dataSend & 0x80; // 数据线获得发送数据最高位
_nop_(); // 延迟一会
scl = 1; // 时钟线拉高,发送数据
_nop_(); // 延迟一会让数据发送
scl = 1; // 发送完毕,时钟线重新拉低
_nop_();
dataSend = dataSend << 1;
}
}
三、SPI协议
有关SPI协议可以看我之前写过的文章:SPI协议和W25Q128详解-CSDN博客
三种协议对比图: