嵌入式经典通信总线协议一、串口通信1、串口通信串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,如SPI通信、USART通信、EEPROM通信和CAN通信等。简单讲,串口通信实现了上位机(PC)与下位机(如STM32)之间的信息交互

2、串口通信的分类处理器与外部设备通信有两种方式:

2.1、串行通信:数据按位顺序依次传输(只有一根数据线进行传输),如8个数据位依次传输,速度慢,但占用引脚资源少

按照数据传送方向,又分为:

单工:数据传输只支持数据在一个方向上传输(只收不发或者只发不收,模式固定)

单双工(半双工):允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输(能发能收,但不能同时进行)

全双工:允许数据同时在两个方向上传输。(能发能收,且能同时进行)

2.2、并行通信:数据各个位同时传输(利用多根数据线进行数据传输),如8个数据位同时传输,占用引脚资源多,但速度快,一般会加一根时钟线,用于同步传输

3、串行通信的分类串行通信按通信的方式可分为:

同步通信:带时钟同步信号传输,如SPI、IIC通信等

异步通信:不带时钟同步信号,如UART(通用异步收发器)、USART(通用同步/异步收发器,两种模式可切换)、单总线等

4、串口通信的缺点串口通信这种传递高低电平信号(TTL)的方式很微弱,如果直接用这种信号去通讯的话,非常容易收到干扰,通讯距离是非常短的,所以为了增强串口通信的抗干扰能力,增长通讯距离,一般会加一个电平转换芯片(比如MAX232EPE),将TTL信号转换成232信号

二、RS232(物理层)1、RS232的由来:在串口通信的基础上,一般会加一个电平转换芯片(比如MAX232EPE),将TTL信号(电平信号)转换成232信号

2、TTL电平中+5V表示逻辑“1”,0V表示逻辑“0”(TTL是正逻辑),RS232电平中-15V ~ -3V表示逻辑“1”,+3V ~ +15V表示逻辑“0”(RS232是负逻辑),RS232电平适合干扰大、距离远的情况,最大传输距离为15米,用于工业上;TTL电平适合距离近且干扰小的情况,一般用在电路板内部的两个芯片之间

通讯标准

电平标准(发送端)

5V TTL

逻辑“1”:2.4V-5V 逻辑“0”:0~0.5V

RS-232

逻辑“1”:-15V — -3V 逻辑“0”:+3V — +15V

2、RS232是DB-9连接器(9根通信线),在工业控制中RS232接口一般只用RXD,TXD和GND三条线

3、RS232抗干扰能力强(提高点平幅度)、传输距离可达15米(比TTL更强)

4、RS232和串口通信只能进行点到点的简单通信(一对一),支持单工、半双工、全双工通信

三、RS485(物理层)1、RS485的由来:在串口通信的基础上,增加了一个485电平转换芯片,把输入的电平信号(TTL信号)转换成差分信号

2、差分信号的传输线中没有参考电平线,所有都是信号线,然后1和0的表达两根信号线之间的电压差。

逻辑

电压范围

逻辑1

2V < A-B <6V

逻辑0

-6V

3、RS485采用双绞线,这种接线方式为总线式拓扑结构,在同一总线上可以同时存在多个节点。因为采用两线制,数据的发送和接收都要使用这对差分信号线,发送和接收不能同时进行,所以只能采用半双工的方式工作,编程时也需要加以处理

4、RS485抗干扰能力强、通信速度快(传输速度快是因为抗干扰能力强,能采用很高的波特率)、传输距离可达1500米和可实现多节点组网

5、RS485可支持一对多组网,采用半双工的方式工作

四、UART(物理层和链路层)CH340:USB转串口模块,这里的串口就指的是UASRT(TTL电平)

1、UART特点

即通用异步收发器(Universal Asynchronous Receiver/Transmitter),是一种通用的串行、异步通信总线。该总线有两条数据线(TX和RX),可以实现全双工的发送和接收

具体实现可能依赖于底层的物理连接方式(例如RS-232, RS-485)等

2、数据传输的格式(通信基础)当两个设备使用UART通讯时,双方要事先约定好波特率、数据位、奇偶校验位以及停止位后才能进行数据交互。

一帧串口数据包括以下内容:

1、起始位

当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符(数据线电平由高到底表示通信的开始,默认为高电平)

2、数据位

数据位表示真正要发送或接收的信息,位数一般有8位或9位(由低到高)

3、奇偶校验位

数据位末尾可以选择是否添加奇偶校验位,用于检测数据传输是否正确

4、停止位

代表信息传输结束的标志位,可以是1位,1.5位或2位。停止位的位数越多,数据传输的速率也越慢

5、波特率

波特率:描述串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位

约定好帧格式,就需要设置波特率:115200(一秒钟以内,串口可以传输115200个高低电平)、19200、9600

6、通信双方要知道对方的波特率、数据长度、开始位和停止位才可以开始通信

3、配置驱动3.1、裸机(裸核、STM32)串口设置的一般步骤可以总结为如下几个步骤(利用中断触发):

串口时钟使能和GPIO 时钟使能

串口复位

GPIO 端口模式设置

串口参数初始化

开启中断 并且初始化 NVIC(如果需要开启中断才需要这个步骤)

使能串口

编写中断处理函数

123456789101112131415161718192021222324252627282930313233343536373839404142434445//1、串口时钟使能 GPIO 时钟使能RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);//2、串口复位USART_DeInit(USART1); // 复位串口 1//3、GPIO端口模式设置//USART1_TX GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 //4、串口参数初始化//USART结构体初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式//使用USART_Init函数对串口进行初始化USART_Init(USART1, &USART_InitStructure); //初始化串口1//5、开启中断并且初始化NVIC(这里使用中断触发数据收发)//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断//6、使能串口USART_Cmd(USART1, ENABLE); //使能串口1 //7、编写中断处理函数//数据收发逻辑的编写UART_WriteByte和UART_ReadByte

参考链接:STM32之USART-串口通信(含串口实验详细解析)_stm32 串口11bit-CSDN博客

3.2、带操作系统(系统核,Linux,I.MX6ULL)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126//1、定义串口引脚以及引脚PAD属性/* 定义 UART1 RX 引脚 */#define UART1_RX_GPIOG PIO1#define UART1_RX_GPIO_PIN (17U)#define UART1_RX_IOMUXC IOMUXC_UART1_RX_DATA_UART1_RX/* 定义 UART1 TX 引脚 */#define UART1_TX_GPIO GPIO1#define UART1_TX_GPIO_PIN (16U)#define UART1_TX_IOMUXC IOMUXC_UART1_TX_DATA_UART1_TX// 配置串口使用的引脚 (PAD属性)#define UART_RX_PAD_CONFIG_DATA (SRE_0_SLOW_SLEW_RATE| \ DSE_6_R0_6| \ SPEED_1_MEDIUM_100MHz| \ ODE_0_OPEN_DRAIN_DISABLED| \ PKE_1_PULL_KEEPER_ENABLED| \ PUE_1_PULL_SELECTED| \ PUS_3_22K_OHM_PULL_UP| \ HYS_0_HYSTERESIS_DISABLED)/* 转换速率: 转换速率慢 驱动强度: R0/6 带宽配置 : medium(100MHz) 开漏配置: 关闭 拉/保持器配置: 使能 拉/保持器选择: 上下拉 上拉/下拉选择: 22K 欧姆上拉 (选择了保持器此配置无效) 滞回器配置: 禁止 */#defineUART_TX_PAD_CONFIG_DATA (SRE_0_SLOW_SLEW_RATE|\ DSE_6_R0_6|\ SPEED_1_MEDIUM_100MHz|\ ODE_0_OPEN_DRAIN_DISABLED|\ PKE_1_PULL_KEEPER_ENABLED|\ PUE_0_KEEPER_SELECTED|\ PUS_3_22K_OHM_PULL_UP|\ HYS_0_HYSTERESIS_DISABLED)/* 转换速率:转换速率慢 驱动强度:R0/6 带宽配置:medium(100MHz) 开漏配置:关闭 拉/保持器配置:使能 拉/保持器选择:保持器 上拉/下拉选择:22K欧姆上拉(选择了保持器此配置无效) 滞回器配置:禁止*///2、串口初始化配置void uart_init(void){ /********************** 第一部分 ********************/ /* 时钟初始化,设置 UART 根时钟,并设置为 40MHz*/ CCM->CSCDR1 &= ~(0x01 << 6); //设置 UART 选择 PLL3 / 6 = 80MHz CCM->CSCDR1 &= ~(0x3F); //清零 //设置串口根时钟分频值为 1,UART 根时钟频率为:80M / (dev + 1) = 40MHz CCM->CSCDR1 |= (0x01 << 0); /********************** 第二部分 ********************/ /* 开启 UART1 的时钟 */ CCM->CCGR5 |= CCM_CCGR5_CG12(0x3); //开启 UART1 的时钟 UART1->UCR1 &= ~UART_UCR1_UARTEN_MASK; //禁用 UART1 /* 软件复位 */ UART1->UCR2 &= ~UART_UCR2_SRST_MASK; while ((UART1->UCR2 & UART_UCR2_SRST_MASK) == 0) { } /********************** 第三部分 ********************/ UART1->UCR1 = 0x0; UART1->UCR2 = UART_UCR2_SRST_MASK; UART1->UCR3 = UART_UCR3_DSR_MASK | UART_UCR3_DCD_MASK | UART_UCR3_RI_ →MASK; UART1->UCR4 = UART_UCR4_CTSTL(32); UART1->UFCR = UART_UFCR_TXTL(2) | UART_UFCR_RXTL(1); UART1->UESC = UART_UESC_ESC_CHAR(0x2B); UART1->UTIM = 0x0; UART1->ONEMS = 0x0; UART1->UTS = UART_UTS_TXEMPTY_MASK | UART_UTS_RXEMPTY_MASK; UART1->UMCR = 0x0; /********************** 第四部分 ********************/ /* 引脚初始化 */ IOMUXC_SetPinMux(UART1_RX_IOMUXC, 0); IOMUXC_SetPinConfig(UART1_RX_IOMUXC, UART_RX_PAD_CONFIG_DATA); IOMUXC_SetPinMux(UART1_TX_IOMUXC, 0); IOMUXC_SetPinConfig(UART1_TX_IOMUXC, UART_TX_PAD_CONFIG_DATA); /********************** 第五部分 ********************/ /*******uart 初始化 ******/ /* 设置控制寄存器到默认值 */ UART1->UCR2 |= (1 << 5); //8 位数宽度 UART1->UCR2 &= ~(1 << 6); //一位停止位 UART1->UCR2 &= ~(1 << 8); //禁用奇偶校验位 UART1->UCR2 |= (1 << 2); //使能发送 UART1->UCR2 |= (1 << 1); //使能接收 UART1->UCR2 |= (1 << 14); //忽略流控 /********************** 第六部分 ********************/ /* For imx family device, UARTs are used in mode, so that this bit should always be set.*/ UART1->UCR3 |= UART_UCR3_RXDMUXSEL_MASK; UART1->UFCR = \ (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置发送FIFO 阀值 UART1->UFCR = \ (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置接收FIFO 阀值 UART1->UCR1 &= ~UART_UCR1_ADBR_MASK; //禁用可变波特率 // UART1->UCR1 |= UART_UCR1_ADBR_MASK; /**********************第七部分********************/ /*波特率设置方式1。使用官方SDK设置波特率函数*/ //UART_SetBaudRate(UART1,115200,40000000); /*波特率设置方式2。手动计算,填入寄存器*/ /*设置串口波特率 *RefFreq时钟40MHz *UFCR RFDIV 110 0x067分频5.714MHz *BaudRate 115200bps *UBMR 31-1=0x09 *UBIR 10-1=0x1E */ UART1->UFCR&=~(0x07 << 7); //清零分频值 UART1->UFCR|=(0x06<< 7); //设置分频值,40MHz/7= 5.714MHz UART1->UBIR= 0x09; UART1->UBMR= 0x1E; /**********************第八部分********************/ /*开启串口*/ UART1->UCR1|=UART_UCR1_UARTEN_MASK; }

五、SPI (物理层和链路层)1、SPI特点

SPI(Serial Peripheral lnterface):串行外设接口,SPI是一种高速的、全双工、同步的串行通信总线

SPI采用主从方式工作,一般有一个主设备和一个或多个从设备(一主一从,一主多从)

SPI需要至少4根线,分别是MISO(主设备输入从设备输出,接收信号线)、MOSI(主设备输出从设备输入)、SCLK(时钟)、CS(片选)

2、SPI4根逻辑线的作用

MISO:接收信号线,主机接受,从机发送(从机发送数据到主机)

MOSI:发送信号线,主机发送,从机接受(主机发送数据到从机)

3、SPI的工作模式SPI总线有四种不同的工作模式,取决于极性(CPOL)和相位(CPHL)这两个因素

1、CPOL表示SCLK空闲时的状态

CPOL=O,空闲时SCLK为低电平

CPOL=1,空闲时SCLK为高电平

2、CPHA表示采样时刻

CPHA=0,每个周期的上升沿(第一个时钟沿)采样

CPHA=1.每个周期的下降沿(第二个时钟)沿采样

例如:CPOL=0,CPHA=0

采集的数据:取决于采样时刻MOSI线上的电平状态

4、配置驱动123456789101112131415161718192021222324252627282930313233//以下是SPI模块的初始化代码,配置成主机模式 //SPI口初始化//这里针是对SPI1的初始化void SPI1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; /* SPI的IO口和SPI外设打开时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /* SPI的IO口设置 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //PA5-SPI1_SCK PA6-SPI1_MISO PA7-SPI1_MOSI GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(下降)数据被采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式 SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 SPI_Cmd(SPI1, ENABLE); //使能SPI外设 SPI1_ReadWriteByte(0xff);//启动传输 }

参考链接:【STM32】SPI程序示例_stm32 spi例程-CSDN博客

六、IIC(物理层和链路层)1、IIC特点

IIC总线(lnter-Integrated Circuit):集成电路总线,是一种串行、半双工总线,主要用于近距离、低速的芯片之间的通信;

IIC总线:有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步

IIC通信一般采用一主多从的工作模式。

2、数据传输格式2.1、写数据帧

1、空闲状态:空闲状态的时候,SCL线和SDA线都处于高电平

2、起始位:时钟信号处于高电平期间,数据信号完成由高到低的跳变,这样起始信号就发送完成了

3、设备地址:表明主机与那个从机进行通信

设备地址的传输时SCL和SDA的状态表示:SCL时钟线始终处于高平点的时候,读取SDA数据线上的内容

4、读/写数据位:读数据置1,写数据置0(针对的是主机)

5、应答信号位:从机发给主机的,从机收到主机发送的设备地址,收到置0,没收到置1(确认设备地址是否正确)——>(应答信号位:0:数据被正确接受,1:从机忙;接受错误;主机读取完成)

6、寄存器地址位:设备的存储数据的内部寄存器地址

7、应答信号位:从机发给主机的,从机收到主机发送的内部寄存器地址,收到置0,没收到置1(确认设备内部寄存器地址是否正确)

8、要写入的数据:设备存储器的寄存器要写入的八位数据

9、应答信号位:从机发给主机的,告诉主机发送成功,收到置0,没收到置1(确认写入是否成功)

10、停止位:时钟信号为高,数据信号完成由低到高的跳变

2.2、读数据帧

1、起始位

2、设备地址:读取目标数据的设备地址

3、读写位:写,主机写入

4、应答位:从机发给主机的,从机收到主机发送的设备地址,收到置0,没收到置1(确认设备地址是否正确)

5、寄存器地址:读取目标数据的寄存器地址

6、应答信号:从机发给主机的,从机收到主机发送的内部寄存器地址,收到置0,没收到置1(确认设备内部寄存器地址是否正确)

7、起始位

8、设备地址:读取目标数据的设备地址

9、读写位:读数据,主机读

10、要接受的数据:从设备的存储数据的寄存器中取出数据

11、应答位:主机发送给从机的,表示收到从机的数据

12、停止位

3、配置驱动3.1、软件模拟IIC123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113#include "stm32f10x.h" // Device header#include "Delay.h"//参数给1或0,就可以释放或拉低SCL了。void MyI2C_W_SCL(uint8_t BitValue){ GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue); Delay_us(10);} //写数据void MyI2C_W_SDA(uint8_t BitValue){ GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue); Delay_us(10);} //读与写不是一个寄存器//读数据uint8_t MyI2C_R_SDA(void){ uint8_t BitValue; BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11); Delay_us(10); return BitValue;} //IIC的SDA与SCL引脚初始化void MyI2C_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); //将两根线置为高电平,初始化为开漏输出且释放总线 GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);} //开始void MyI2C_Start(void){ MyI2C_W_SDA(1); MyI2C_W_SCL(1); MyI2C_W_SDA(0); MyI2C_W_SCL(0);} //结束void MyI2C_Stop(void){ MyI2C_W_SDA(0); MyI2C_W_SCL(1); MyI2C_W_SDA(1);} //发送单元void MyI2C_SendByte(uint8_t Byte){ uint8_t i; for(i = 0; i < 8; i++) { MyI2C_W_SDA(Byte & (0x80 >> i)); MyI2C_W_SCL(1); MyI2C_W_SCL(0); }} //接收单元uint8_t MyI2C_ReceiveByte(void){ uint8_t i,Byte = 0x00; //MyI2C_W_SCL(1); MyI2C_W_SDA(1); for (i = 0; i< 8;i++) { MyI2C_W_SCL(1); if (MyI2C_R_SDA() == 1) { Byte |= (0x80>>i); } MyI2C_W_SCL(0); } return Byte; } //发送应答void MyI2C_SendAck(uint8_t AckBit){ MyI2C_W_SDA(AckBit); MyI2C_W_SCL(1); MyI2C_W_SCL(0); } //接收应答uint8_t MyI2C_ReceiveAck(void){ uint8_t AckBit; //主机释放SDA MyI2C_W_SDA(1); MyI2C_W_SCL(1); AckBit = MyI2C_R_SDA(); MyI2C_W_SCL(0); return AckBit; }

3.2、硬件模拟IIC123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134#include "stm32f10x.h" // Device header#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0XD0 //带有超时退出机制的WaitEvent函数void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT){ uint32_t Timeout; Timeout = 10000; while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) { Timeout --; if (Timeout == 0) { break; } }} //主机给从机写数据(单片机给MPU6050写数据)void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data){ I2C_GenerateSTART(I2C2,ENABLE); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //在库函数中,发送数据自带接收应答,接收数据自带发送应答。 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C2,RegAddress); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING); I2C_SendData(I2C2,Data); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED); I2C_GenerateSTOP(I2C2,ENABLE); } //主机读从机的数据(单片机读MPU6050的数据)uint8_t MPU6050_ReadReg(uint8_t RegAddress){ uint8_t Data; I2C_GenerateSTART(I2C2,ENABLE); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //在库函数中,发送数据自带接收应答,接收数据自带发送应答。 MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C2,RegAddress); //问题:如果使用硬件操作I2C,在重复开始条件时,EVENT_MASTER_BYTE_TRANSMITTING这个状态正在发送数据(数据并没有发生完成),但是实验结果显示数据也能正常的接收,这是为什么? MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED); I2C_GenerateSTART(I2C2,ENABLE); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); I2C_AcknowledgeConfig(I2C2,DISABLE); I2C_GenerateSTOP(I2C2,ENABLE); MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED); Data = I2C_ReceiveData(I2C2); I2C_AcknowledgeConfig(I2C2,ENABLE); return Data; } //获取数据寄存器的函数void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ, int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ){ uint8_t DataH,DataL; DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); *AccX = (DataH << 8) | DataL; DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); *AccY = (DataH << 8) | DataL; DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); *AccZ = (DataH << 8) | DataL; DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); *GyroX = (DataH << 8) | DataL; DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); *GyroY = (DataH << 8) | DataL; DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); *GyroZ = (DataH << 8) | DataL;} //初始化MPU6050void MPU6050_Init(void){ //第一步:开启时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //第二步:配置pin引脚为复用开漏输出模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); //第三步:初始化I2C外设 I2C_InitTypeDef I2C_InitStructure; //接收一个字节后是否给从机应答 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //stm32从机可以响应几位的地址(stm32作为从机才会用到) I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 10000; //占空比:这里指的是SCL低电平与高电平的比,因为SCL是弱上拉,所以在快速状态下,SDA波形翻转需要一些时间(低电平需要更多的时间) I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //自身地址1(stm32作为从机才会用到),指定stm32的自身地址,方便别的主机呼叫。 I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_Init(I2C2,&I2C_InitStructure); //第三步:I2C使能 I2C_Cmd(I2C2,ENABLE); MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); MPU6050_WriteReg(MPU6050_CONFIG, 0x06); MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); } //获取MPU6050的IDuint8_t MPU6050_GetID(void){ return MPU6050_ReadReg(MPU6050_WHO_AM_I);}

参考链接:STM32——IIC(I2C)附代码_stm32 i2c demo-CSDN博客

七、CAN(物理层和链路层)1、CAN特点

CAN总线(Controller Area Network):控制器(在汽车领域称为ECU,Electronic Control Unit)域网络,CAN总线应用最多的是汽车领域

CAN通信采用的是差分信号,是两根线共同作用的,是双绞线缠绕的,这样的结构,抗干扰能力强,可进行长距离传输

2、CAN总线上的电平信号CAN采用的是双绞线,用的是差分信号表示逻辑“1”和逻辑“0”

逻辑

CAN_H

CAN_L

两条线上的电压差

逻辑1

2.5V

2.5V

0V

逻辑0

3.5V

1.5V

2V

3、数据传输格式

1、起始位:占1位,一定是0

2、识别码:占11位,数据发送至哪个设备的识别码

3、RTR位:占1位,数据帧为0,远程请求帧为1

3、控制码:占6位,控制数据长度

IDE位:第1位,标准帧(识别码11位)为0,拓展帧(识别码29位)为1

空闲位:第2位,保留

DLC(Data Link Control)位:剩下4位,决定了数据码的所占字节数(DLC=1,则数据码只占1个字节,DLC=8,则数据码占8个字节)

4、数据码:所占位数由DLC决定

5、CRC:占16位,循环冗余校验位,确保数据的准确性

CRC校验码:占15位,

CRC界定符:占1位,固定为1,为了与后面的数据隔开

6、ACK码:占两位, 第一位为ACK确认槽,发送端发送逻辑1,接收端回复逻辑0,第二位为ACK界定位,作用也是隔开,固定为1

7、结束位,占7位,全都是1,表示数据帧的结束

4、数据传输优先级根据数据帧的识别码来判断,识别码中0多的优先级高。如果识别码一致,RTR位为0的数据帧具有优先权

5、配置驱动1、配置CAN的基础设置

波特率的配置:数据的传输速率

中断开启:打开中断是为了CPU能够及时接受和处理放在 “接受邮箱” 中的报文

2、配置CAN的过滤器

2.1、CAN过滤器有两种工作模式:列表模式和掩码模式

列表模式:列出ID名字,过滤器通过判断报文ID与其是否一致来决定是接受还是舍弃这份报文。

掩码模式:通过确定ID特定位的值来判断报文的接受与丢弃。

2.2、32位列表模式

123456789101112131415161718192021/** * @brief 32位列表模式过滤器配置(这里举一个标准ID一个扩展ID) * @param hcan CAN的句柄 * @param StdId 标准ID * @param ExtId 扩展ID */void CANFilterConfig_Scale32_IdList(CAN_HandleTypeDef * hcan,uint32_t StdId,uint32_t ExtId,uint8_t Filter_Nunber ) { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber ; //使用过滤器编号 sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; //设为列表模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //配置为32位宽 sFilterConfig.FilterIdHigh = StdId<<5; //基本ID放入到STID中 sFilterConfig.FilterIdLow = 0|CAN_ID_STD; //设置IDE位为0 sFilterConfig.FilterMaskIdHigh = ((ExtId<<3)>>16)&0xffff; sFilterConfig.FilterMaskIdLow = (ExtId<<3)&0xffff|CAN_ID_EXT;//设置IDE位为1 sFilterConfig.FilterFIFOAssignment = 0; //接收到的报文放入到FIFO0中 sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(hcan, &sFilterConfig) ;}

2.3、16位列表模式

1234567891011121314151617181920/** * @brief 16位列表模式过滤器配置 * @param hcan CAN的句柄 * @param StdId1~StdId4 4个标准ID */void CANFilterConfig_Scale16_IdList(CAN_HandleTypeDef * hcan,uint32_t StdId1,uint32_t StdId2,uint32_t StdId3,uint32_t StdId4,uint8_t Filter_Nunber) { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber; //使用过滤器1 sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; //设为列表模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; //位宽设置为16位 sFilterConfig.FilterIdHigh = StdId1<<5; //4个标准CAN ID分别放入到4个存储中 sFilterConfig.FilterIdLow = StdId2<<5; sFilterConfig.FilterMaskIdHigh = StdId3<<5; sFilterConfig.FilterMaskIdLow = StdId4<<5; sFilterConfig.FilterFIFOAssignment = 0; //接收到的报文放入到FIFO0中 sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(&hcan, &sFilterConfig); }

2.4、32位掩码模式

123456789101112131415161718192021222324252627282930313233343536373839404142/** * @brief 32位掩码模式过滤器配置 * @param hcan CAN的句柄 * @param ID 验证码 * @param Mask 屏蔽码 * @param ID_Type ID类型(标准帧为0,其他则为扩展帧) */void CANFilterConfig_Scale32_IdMask(CAN_HandleTypeDef * hcan,uint32_t ID,uint32_t Mask,uint8_t ID_Type,uint8_t Filter_Nunber) { if(ID_Type==0) //标准ID { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber; //使用过滤器2 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; //配置为掩码模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //设置为32位宽 sFilterConfig.FilterIdHigh =ID<<5; sFilterConfig.FilterIdLow =0; sFilterConfig.FilterMaskIdHigh =(Mask<<5); sFilterConfig.FilterMaskIdLow =0|0x02; //只接收数据帧 sFilterConfig.FilterFIFOAssignment = 0; //设置通过的数据帧进入到FIFO0中 sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(hcan, &sFilterConfig); } else //扩展ID { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber; //使用过滤器3 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; //配置为掩码模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //设为32位宽 sFilterConfig.FilterIdHigh =((ID<<3) >>16) &0xffff; sFilterConfig.FilterIdLow =((ID<<3)&0xffff) | CAN_ID_EXT; sFilterConfig.FilterMaskIdHigh = (Mask>>16)&0xffff; sFilterConfig.FilterMaskIdLow = (Mask&0xffff)|0x02; //只接收数据帧 sFilterConfig.FilterFIFOAssignment = 0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(hcan, &sFilterConfig); }}

3、报文的发送和接受

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677/** * @brief 发送标准ID的数据帧 * @param hcan CAN的句柄 * @param ID 数据帧ID * @param pData 数组指针 * @param Len 字节数0~8 */uint8_t CANx_SendStdData(CAN_HandleTypeDef* hcan,uint16_t ID,uint8_t *pData,uint16_t Len){ static CAN_TxHeaderTypeDef Tx_Header; Tx_Header.StdId=ID; Tx_Header.ExtId=0; Tx_Header.IDE=0; Tx_Header.RTR=0; Tx_Header.DLC=Len; /*找到空的发送邮箱,把数据发送出去*/ if(HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK) // { if(HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX1) != HAL_OK) { HAL_CAN_AddTxMessage(hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX2);}/** * @brief 发送扩展ID的数据帧 * @param hcan CAN的句柄 * @param ID 数据帧ID * @param pData 数组指针 * @param Len 数据长度0~8 */ uint8_t CANx_SendExtData(CAN_HandleTypeDef* hcan,uint32_t ID,uint8_t *pData,uint16_t Len){ static CAN_TxHeaderTypeDef Tx_Header; Tx_Header.RTR=0; Tx_Header.DLC=Len; Tx_Header.StdId=0; Tx_Header.ExtId=ID; Tx_Header.IDE=CAN_ID_EXT; /*找到空的发送邮箱,把数据发送出去*/ if(HAL_CAN_AddTxMessage(&hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK) // { if(HAL_CAN_AddTxMessage(&hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX1) != HAL_OK) { HAL_CAN_AddTxMessage(&hcan, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX2); }}/** * @brief CAN FIFO0的中断回调函数,在里面完成数据的接收 * @param hcan CAN的句柄 */ uint8_t date_CAN1[8];//设为全局变量,用于接收CAN1数据 uint8_t date_CAN2[8];//设为全局变量,用于接收CAN2数据 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){ if(hcan->Instance ==CAN1) { CAN_RxHeaderTypeDef RxHeader; //接受句柄 HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, date_CAN1); //接收,CAN邮箱为0 return ; } else if(hcan->Instance == CAN2) { CAN_RxHeaderTypeDef RxHeader; //接受句柄 HAL_CAN_GetRxMessage(&hcan2, CAN_RX_FIFO0, &RxHeader, date_CAN2); //接收,CAN邮箱为0 }}void CAN_Start(CAN_HandleTypeDef *hcan){ HAL_CAN_ActivateNotification(hcan ,CAN_IT_RX_FIFO0_MSG_PENDING); HAL_CAN_Start(hcan);}

参考链接:CAN通信知识梳理及在Stm32上的应用(HAL库)-CSDN博客

八、Modbus、IEC61850、IEC104、IEC103、IEC1011、modbus

Modbus协议主要运行在应用层

支持多种通信方式,包括ModBus RTU、ModBus ASCII和ModBus TCP

ModBus RTU:采用二进制数据传输,使用串行通信,支持RS232和RS485两种接口(物理传输介质:物理上通过串口线)

ModBus ASCII:采用ASCII码传输文本信息,使用串行通信,并支持RS232和RS485两种接口(物理传输介质:物理上通过串口线)

ModBus TCP:使用TCP/IP协议进行通信(物理传输介质:物理层上通过网口)

2、IEC61850

作用:IEC 61850 用于变电站自动化系统的通信。它旨在提供一种通用的框架,以实现不同制造商设备之间的互操作性

协议组成:MMS+GOOSE+SV(61850将通过对象分为三层),站控层、间隔层、过程层

MMS:用于装置和后台之间的数据交互

GOOSE:用于装置之间的通讯

SV:用于采样值传输

3、IEC103

作用:IEC 103专门针对保护继电器的应用,用于监控和记录电力系统保护装置的动作行为。它可以用来配置保护设备、读取故障报告等。

通信方式:运行在应用层的报文,IEC 103可以使用不同的物理传输介质,如串行接口或以太网

4、IEC104和IEC101

作用:IEC 101是一种面向遥测和遥控应用的基本远动任务配套标准。它规定了如何在远动设备之间进行简单而高效的数据交换

101通信方式:主要是在数据链路层(Layer 2)和应用层(Layer 7)。IEC 101通常通过串行通信链路工作,例如RS-232或RS-485(物理传输介质:物理上通过串口线)

IEC 104是基于IEC 60870-5-101标准,并使用TCP/IP网络协议进行传输的扩展版本

TCP(传输控制协议)和UDP(用户数据报协议)是两种不同的传输层协议,它们并不直接指定物理传输媒介(网线(以太网)、Wi-Fi、4G/5G蜂窝网络还是其他任何类型的网络连接)