串口通信

UART、I2C、SPI、USB的异同点 #通信协议#​​​​​

名称 引脚 双工 时钟 电平 设备 通信距离 传输速率
USART TX、RX 全双工 异步 单端 点对点 远(最多1200m) 慢(波特率设置)
I2C SCL、SDA 半双工 同步 单端 多设备(一主多从,寻址)
SPI SCLK、MOSI、MISO、CS 全双工 同步 单端 多设备(一主多从,片选 选从机)
CAN CAN_H、CAN_L 半双工 异步 差分 多设备(多主机)
USB DP、DM 半双工 异步 差分 点对点
  • 单端电平:指电平高低根据对GND的电位差确定,通信双方必须共地才可通信

  • 差分电平:无需共地,抗干扰能力强

  • 异步通信没有时钟线,需要双方约定一个采样频率,并添加帧头帧尾等进行采样位置的对齐

  • UART:通用异步串行口,速率不快,可全双工,结构上一般由波特率产生器、UART发送器、UART接收器组成,硬件上两线,一收一发;

  • IIC:双向、两线、串行、多主控接口标准。速率不快,半双工,同步接口,具有总线仲裁机制,非常适合器件间近距离经常性数据通信,可实现设备组网;

  • SPI:高速同步串行口,高速,可全双工,收发独立,同步接口,可实现多个SPI设备互联,硬件3~4线;

  • USB通用串行总线,高速,半双工,由主机、hub、设备组成。设备可以与下级hub相连构成星型结构。

  • 串口对比图(老)

    • image

同步通讯和异步通讯的区别

异步通信

  • 优点:节省一根时钟线

  • 缺点:对时钟要求严格,对硬件电路的依赖比较严重

同步通信

  • 优点:对时间要求不严格,对硬件电路不怎么依赖,可以使用软件模拟时序

  • 缺点:多一根时钟线

UART

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。比UART(通用异步收发传输器(Universal Asynchronous Receiver/Transmitter))多了个同步通信功能

  • USART的同步时钟信号只能输出,不能输入。所以其同步模式更多是未来兼容其他协议而设计的,不支持两个USART之间进行同步通信。我们一般只使用异步通信

    • 时钟可以模拟SPI,或者根据周期测量波特率

  • 硬件自动处理收发寄存器中的值,发送/接收时自动添加/删除起始、停止位。

    • USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里

通信双方的波特率需一致,代表码元传输速率,即通信速度。波特率越高,传输速度越快,距离越短

  • 自带波特率发生器,最高达4.5Mbits/s

  • 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)

  • 可选校验位(无校验/奇校验/偶校验)

  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

当电平标准不一致时,需要加电平转换芯片

  • 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

    • TTL电平:+3.3V或+5V表示1,0V表示0

    • RS232电平:-3 ~ -15V表示1,+3 ~ +15V表示0

    • RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号)

STM32F103C8T6 USART资源: USART1、 USART2、 USART3

通信协议

空闲电平为高电平,起始位拉低电平,停止位置高电平

  • 波特率:串口通信的速率

  • 起始位:标志一个数据帧的开始,固定为低电平

  • 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

  • 校验位:用于数据验证,根据数据位计算得来 奇偶校验码

  • 停止位:用于数据帧间隔,固定为高电平

image

一般选择 8位字长(无校验) or 9位字长(有校验)。这样每帧的有效载荷都是1字节

USART框图

image

  • 流控:控制传输速度,避免发送方发送过快,接收方来不及接收

    • TX与CTS是一对,RX与RTS是一对

    • 来不及接收时,接收方RTS置高电平,发送方的CTS收到后就会暂停发送

  • 唤醒单元:用来实现多设备通讯,有寻址功能

USART基本结构

image

串口工作的三种方式

  1. 查询:不断地循环查询标志,看看当前有没有数据要它传送或接收,有的话进行相应的操作

  2. 中断:如果发现有一个中断来,则意味着有数据需要接收(接收中断)或数据已经发送完成(发送中断),最常用

  3. DMA:设置好DMA工作方式(接收和发送缓冲位置),由DMA来自动接收或发送数据,可以最小化占用CPU时间

数据包封装

HEX数据包:传输原始数据

  • 优点:数据解析简单,比较适合模块发送原始数据,如陀螺仪、温湿度传感器。

  • 缺点:灵活性不足,载荷容易与包头包尾重复

  • 格式
    image

  • 接收过程(使用状态机思想)
    image

文本数据包:

  • 优点:数据直观易理解,适合输入指令与人机交互的场合。如蓝牙模块的AT指令。

  • 缺点:解析效率低

  • 格式
    image

  • 接收过程
    image

常用函数

void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct); // 时钟输出设置
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct); // 时钟输出设置
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState); // 中断
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState); // DMA

// 发送 及 接收数据(读写寄存器)
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

// 读写标志位
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

基于HAL库

image

代码实例

串口发送
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h" // 封装sprintf


// 使用USART1,PA9为TX,PA10为RX
void Serial_Init(void) {
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
//	GPIO_InitStructure.GPIO_Mode = ;
//	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//	GPIO_Init(GPIOA, &GPIO_InitStructure);

	// 配置串口发送
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600; // 直接写波特率数值,Init函数自动算好对应的分频系数
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);

	USART_Cmd(USART1, ENABLE);
} 

void Serial_SentByte(uint8_t Byte) {
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 看手册得知不需手动清零,下一次发送时,该标志位会自动清零
}

void Serial_SendArray(uint8_t *Array, uint16_t Length) {
	for (uint16_t i = 0; i < Length; i++) {
		Serial_SentByte(Array[i]);
	}
}

void Serial_SendString(char* String) {
	for (uint16_t i = 0; i < String[i] != '\0'; i++) {
		Serial_SentByte(String[i]);
	}
}

// 返回X的Y次方
uint32_t Serial_Pow(uint32_t X, uint32_t Y) {
	uint32_t Result = 1;
	while (Y--) {
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length) {
	for (uint16_t i = 0; i < Length; i++) {
		Serial_SentByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

// fputc 是 printf 的底层函数,次数重写fputc,实现printf重定向输出到串口的功能
int fputc(int ch, FILE *f) {
	Serial_SentByte(ch);
	return ch;
}

// 对sprintf函数的封装
void Serial_Printf(char *format, ...) {
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

在main.c中

#include "stm32f10x.h"                  // Device header
#include "Delay.h"   
#include "OLED.h"
#include "Serial.h"

int main() {

	OLED_Init();
	Serial_Init();

//	uint8_t Arr[] = {97,98, 99, 100};

	while(1) {
//		Serial_SendArray(Arr, 4);
//		Serial_SendString("Hello~\r\n");
//		Serial_SendNumber(12345, 6);
	
		// 重定向printf到串口1
//		printf("Num = %d \r\n", 666);
	
//		char String[100];
//		sprintf(String, "Num = %d \r\n", 777);
//		Serial_SendString(String);
	
		Serial_Printf("Num = %d \r\n", 888);
	
		Serial_Printf("你好世界\r\n");
	
		Delay_ms(1000);

	}
}

注意:要想显示汉字不乱码,两边编码格式必须一样(如都是UTF-8),且项目设置C/C++​中加上选项--no-multibyte-chars

image

串口发送接收
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h" // 封装sprintf

uint8_t Serial_RxData, Serial_RxFlag;

// 使用USART1,PA9为TX,PA10为RX
void Serial_Init(void) {
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入(浮空也可)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	// 配置串口发送
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600; // 直接写波特率数值,Init函数自动算好对应的分频系数
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 发送 + 接收
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);

	// 开启中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收数据触发中断

	// 配置NVIC (misc.h)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	USART_Cmd(USART1, ENABLE);
} 

void Serial_SentByte(uint8_t Byte) {
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 看手册得知不需手动清零,下一次发送时,该标志位会自动清零
}

void Serial_SendArray(uint8_t *Array, uint16_t Length) {
	for (uint16_t i = 0; i < Length; i++) {
		Serial_SentByte(Array[i]);
	}
}

void Serial_SendString(char* String) {
	for (uint16_t i = 0; i < String[i] != '\0'; i++) {
		Serial_SentByte(String[i]);
	}
}

// 返回X的Y次方
uint32_t Serial_Pow(uint32_t X, uint32_t Y) {
	uint32_t Result = 1;
	while (Y--) {
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length) {
	for (uint16_t i = 0; i < Length; i++) {
		Serial_SentByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

// fputc 是 printf 的底层函数,次数重写fputc,实现printf重定向输出到串口的功能
int fputc(int ch, FILE *f) {
	Serial_SentByte(ch);
	return ch;
}

// 对sprintf函数的封装
void Serial_Printf(char *format, ...) {
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void) {
	if (Serial_RxFlag == 1) {
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void) {
	return Serial_RxData;
}

void USART1_IRQHandler(void) {
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"   
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main() {

	OLED_Init();
	Serial_Init();

//	uint8_t Arr[] = {97,98, 99, 100};

	while(1) {

		// 查询方式 接收
//		if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
//			RxData = USART_ReceiveData(USART1);
//			OLED_ShowHexNum(1, 1, RxData, 2);
//		}
		if (Serial_GetRxFlag() == 1) {
			RxData = Serial_GetRxData();
			Serial_SentByte(RxData);
			OLED_ShowHexNum(1, 1, RxData, 2);
		}
	}
}

串口收发HEX数据包(定长)
uint8_t Serial_TxPacket[4];				//FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4]; // Tx和Rx缓冲区均声明为全局变量
uint8_t Serial_RxFlag;

// 自动加上包头包尾发送
void Serial_SendPacket(void) {
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}

uint8_t Serial_GetRxFlag(void) {
	if (Serial_RxFlag == 1) {
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

void USART1_IRQHandler(void) {
	static uint8_t RxState = 0; // 状态码S
	static uint8_t pRxPacket = 0; // 接收的长度
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		uint8_t RxData = USART_ReceiveData(USART1);
	
		if (RxState == 0) {
			pRxPacket = 0; // 重置接收的长度
			if (RxData == 0xFF) RxState = 1;
		} else if (RxState == 1) {
			Serial_RxPacket[pRxPacket] = RxData;
			pRxPacket++;
			if (pRxPacket >= 4) RxState = 2;
		} else if (RxState == 2) {
			if (RxData == 0xFE) RxState = 0;
			Serial_RxFlag = 1;
		}
	
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}
串口收发文本数据包(不定长)

效果:解析不定长文本数据包,若收到“@LED_ON\r\n”则点亮LED,并返回“LED_ON_OK\r\n”给串口

// 作为全局变量,在Serial.h中需声明为extren
char Serial_RxPacket[100]; // 接收数据包
uint8_t Serial_RxFlag;

// 状态机接收数据包
void USART1_IRQHandler(void) {
	static uint8_t RxState = 0; // 状态码S
	static uint8_t pRxPacket = 0; // 接收的长度
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		uint8_t RxData = USART_ReceiveData(USART1);

		if (RxState == 0) {
			if (RxData == '@' && Serial_RxFlag == 0) { // Serial_RxFlag为0时才开始接收,避免旧数据包被覆盖
				RxState = 1;
				pRxPacket = 0; // 重置接收的长度
			}
		} else if (RxState == 1) {
			if (RxData == '\r') {
				RxState = 2;
			} else {
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket++;
			}
	
		} else if (RxState == 2) {
			if (RxData == '\n') {
				RxState = 0;
				Serial_RxPacket[pRxPacket] = '\0'; // 添加字符串结束位
				Serial_RxFlag = 1; // 消息解析完成后才置1
			}
		}

		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

main.c中

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include "string.h"

int main(void)
{
	OLED_Init();
	LED_Init();
	Serial_Init();

	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");

	while (1)
	{
		if (Serial_RxFlag == 1)
		{
			OLED_ShowString(4, 1, "                ");
			OLED_ShowString(4, 1, Serial_RxPacket);
		
			if (strcmp(Serial_RxPacket, "LED_ON") == 0)
			{
				LED1_ON();
				Serial_SendString("LED_ON_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");
			}
			else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
			{
				LED1_OFF();
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");
			}
			else
			{
				Serial_SendString("ERROR_COMMAND\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND");
			}
		
			Serial_RxFlag = 0;
		}
	}
}

I2C IIC

IIC(Inter-Intergrated Circuit)芯片与芯片之间的通讯

同步 半双工通信协议(两线通信,大端),带数据应答,支持挂载多设备(一主多从、多主多从)

  1. SCL 时钟线:只由主设备发送,从设备接收

  2. SDA 数据线:设备内部有两个引脚(发送引脚/接受引脚),它们都连接到外部的SDA线上

两种工作方式(按字节发送)

  1. 主发从收(主机控制SCL和SDA)

    1. 流程:主start -> 主发地址 -> 从ACK -> [ 主发数据 -> 从ACK (循环)] -> 主stop

  2. 主收从发(主控SCL,从控SDA)

    1. 流程:主start -> 从发地址 -> 主ACK -> [ 从发数据 -> 主ACK (循环)] -> 接受至最后一个字节,主NACK -> 主stop

    2. 从设备在数据未准备好时,拉低SCL

I2C总线上的器件有唯一的地址,D7~D1为从机地址(由固定部分 + 可编程部分组成,其中可编程部分决定了从机的数量),D0 为数据传输方向(0表主机发,1表主机收)

I2C总线协议

  • 空闲状态:SCL 与 SDA 均为高电平;

  • 启停信号:当SCL为高时,SDA由高电平变低电平(启动) or SDA低变高(停止)

    总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
    image

  • ​发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

  • 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

  • 应答信号:发送器每发一个字节,发送器总是需等待一个应答信号,在时钟信号第9个脉冲时,检查ACK信号,为1代表非应答NACK,为0代表应答ACK。

    • 从机发完数据后,若主机不应答,从机会觉得“可能主机不想要数据了”,就停止发送

  • 数据有效性:靠时钟来保证,仅当SCL为低时,SDA才可被改变;当SCL为高时,SDA保持不变(每位数据的传输由SCL上升沿触发)

    image

    • 通过总线仲裁应对同时到来的请求。​

  • 数据帧格式:SDA线上每个字节必须是8位长,字节数没有限制。8位数据中,先传输最高有效位(大端MSB)传输
    image

    • 从机地址为7位。一般从机地址前4位固定,后几位可以通过芯片外接端口置0/1

I2C时序

读写后,地址指针会自增

  • 指定地址写

    • 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
      image
      此处从机地址为0xD0,第一个字节最后一位位0,代表写;在地址0x19写入数据0xAA

  • 当前地址读

    • 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
      image

    • 若主机读完给从机应答了,从机会再次读取数据(从下一个地址指针的位置);

    • 主机在连续读写时,需要在读取的最后一个字节给非应答,让从机知道主机不想读了

  • 指定地址读(复合数据帧:起始+重复起始+停止)

    • 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
      image

    • Sr(Start Repeat)代表重复起始条件,相当于另起一个时序

硬件电路

imageimage

  • 所有I2C设备的SCL连在一起,SDA连在一起

  • 设备的SCL和SDA均要配置成开漏输出模式

    • 为了避免电路短路(两段分别推挽输出高低电平,信号撞在一起),I2C禁止所有设备输出强上拉的高电平

    • 总线会有线与的特性,只要有一个或多个设备输出了低电平,总线就处于低电平;只有所有设备都输出高电平时总线才为高。这个特征可以执行多主机模式下的时钟同步和总线总裁

  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

传输速率:在标准模式下可以达到100kb/s,快速模式下可以达到400kb/s。

24C02 存储芯片

image

MPU6050 六轴姿态传感器

MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景

image

  • 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度

  • 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

MPU6050参数
  • 16位ADC采集传感器的模拟信号,量化范围:-32768~32767

  • 加速度计满量程选择:±2、±4、±8、±16(g)

    • 量程选择越大,精度越低

  • 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)

  • 可配置的数字低通滤波器

  • 可配置的时钟源

  • 可配置的采样分频

  • I2C从机地址:1101000(AD0=0)or 1101001(AD0=1)

硬件电路

image

  • XCL、XDA:是MPU6050作为主机,连接其他从机外设(如磁力计、气压计等)而设计的,可直接读取这些外设数据,并在MPU6050内部的DMP单元,进行数据融合和姿态解算

    • 实际上,因为I2C可以挂载多设备的特性,气压计等外设也可以一起连接到SCL、SDA引脚上(此时无法使用MPU6050的解算功能)

  • 左上角部分是稳压器,VCC_5V可以输入3.3V ~ 5V电压,输出稳定的3.3V,给芯片端供电

SPI

SPI(Serial Peripheral Interface)串行外设接口,用于芯片与芯片之间的通讯

一种同步的串行全双工通信协议,一般用于芯片间的通讯,一主多从,四线通信。传输时高位在前(大端)

image

  1. SCLK:系统时钟(同步通信协议,则需要有时钟)

    1. 设置选项:(有四种工作方式)

      1. 时钟极性(CPOL):设置空闲时的电平

      2. 时钟相位(CPHA):设置有效沿触发方向是第一次还是第二次有效沿触发image

  2. MOSI:主发从收

  3. MISO:主收从法

  4. NSS(CS):使能
    SS一般是主机的使能,CS是从机的使能

工作原理:按位循环

image

读写过程

image​​

image

CAN总线

Controller Area Network 控制器 域 网络,

CAN通讯 需要专门的CAN收发芯片,其负责 逻辑信号 与 物理信号(差分电平)的转换。

#TODO#(191条消息) CAN通信知识梳理及在Stm32上的应用(HAL库)_冬瓜~的博客-CSDN博客

image

差分信号 使用两根线表示。使用差分信号(双绞线)可以避免信号干扰(受干扰时压差不变)

  • 单片机发送低电平时,CAN两根线分别输出3.5V和1.5V,差值为2V,表示逻辑0

  • 当信号差为0时,两根线都是2.5V,压差0V,表示逻辑1

数据帧格式

image

  • 起始位:都为0

  • 识别码:相当于 设备地址,信号同时到来时,识别码小的优先处理

  • 控制码:控制数据长度

    • IDE位:区分标准格式和扩展格式
      image

    • DLC:Data Link Control 数据长度代码

      • 4位二进制码表示,转换为十进制n表示数据码有n个字节

  • CRC:循环冗余校验

  • CRC界定符:一定为1,用于将帧区分为两个部分

    • 后两位:ACK确认码

      • 应答码:与发送方相反

      • ACK界定位:一定为1

  • 结束位:7位1

posted @ 2023-08-03 15:24  盼归汀  阅读(106)  评论(0编辑  收藏  举报