嵌入式经典通信总线协议
一、串口通信
1、串口通信
串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,如SPI通信、USART通信、EEPROM通信和CAN通信等。简单讲,串口通信实现了上位机(PC)与下位机(如STM32)之间的信息交互
2、串口通信的分类
处理器与外部设备通信有两种方式:
2.1、串行通信:数据按位顺序依次传输(只有一根数据线进行传输),如8个数据位依次传输,速度慢,但占用引脚资源少
按照数据传送方向,又分为:
单工:数据传输只支持数据在一个方向上传输(只收不发或者只发不收,模式固定)
单双工(半双工):允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输(能发能收,但不能同时进行)
全双工:允许数据同时在两个方向上传输。(能发能收,且能同时进行)
2.2、并行通信:数据各个位同时传输(利用多根数据线进行数据传输),如8个数据位同时传输,占用引脚资源多,但速度快,一般会加一根时钟线,用于同步传输
3、串行通信的分类
串行通信按通信的方式可分为:
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 <A-B <-2V |
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(如果需要开启中断才需要这个步骤)
- 使能串口
- 编写中断处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
USART_DeInit(USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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(USART1, &USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
UART_WriteByte和UART_ReadByte
|
参考链接:STM32之USART-串口通信(含串口实验详细解析)_stm32 串口11bit-CSDN博客
3.2、带操作系统(系统核,Linux,I.MX6ULL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
#define UART1_RX_GPIOG PIO1 #define UART1_RX_GPIO_PIN (17U) #define UART1_RX_IOMUXC IOMUXC_UART1_RX_DATA_UART1_RX
#define UART1_TX_GPIO GPIO1 #define UART1_TX_GPIO_PIN (16U) #define UART1_TX_IOMUXC IOMUXC_UART1_TX_DATA_UART1_TX
#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)
#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)
void uart_init(void) { CCM->CSCDR1 &= ~(0x01 << 6); CCM->CSCDR1 &= ~(0x3F); CCM->CSCDR1 |= (0x01 << 0); CCM->CCGR5 |= CCM_CCGR5_CG12(0x3); UART1->UCR1 &= ~UART_UCR1_UARTEN_MASK; 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); UART1->UCR2 |= (1 << 5); UART1->UCR2 &= ~(1 << 6); UART1->UCR2 &= ~(1 << 8); UART1->UCR2 |= (1 << 2); UART1->UCR2 |= (1 << 1); UART1->UCR2 |= (1 << 14);
UART1->UCR3 |= UART_UCR3_RXDMUXSEL_MASK; UART1->UFCR = \ (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); UART1->UFCR = \ (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); UART1->UCR1 &= ~UART_UCR1_ADBR_MASK;
UART1->UFCR&=~(0x07 << 7); UART1->UFCR|=(0x06<< 7); 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、配置驱动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; 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_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); 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、软件模拟IIC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| #include "stm32f10x.h" #include "Delay.h"
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; }
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_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; MyI2C_W_SDA(1); MyI2C_W_SCL(1); AckBit = MyI2C_R_SDA(); MyI2C_W_SCL(0); return AckBit; }
|
3.2、硬件模拟IIC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| #include "stm32f10x.h" #include "MPU6050_Reg.h" #define MPU6050_ADDRESS 0XD0
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; } } }
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); }
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); 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; }
void MPU6050_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 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_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 10000; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_Init(I2C2,&I2C_InitStructure); 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); }
uint8_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位列表模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
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; sFilterConfig.FilterIdHigh = StdId<<5; sFilterConfig.FilterIdLow = 0|CAN_ID_STD; sFilterConfig.FilterMaskIdHigh = ((ExtId<<3)>>16)&0xffff; sFilterConfig.FilterMaskIdLow = (ExtId<<3)&0xffff|CAN_ID_EXT; sFilterConfig.FilterFIFOAssignment = 0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(hcan, &sFilterConfig) ; }
|
2.3、16位列表模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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; sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT; sFilterConfig.FilterIdHigh = StdId1<<5; sFilterConfig.FilterIdLow = StdId2<<5; sFilterConfig.FilterMaskIdHigh = StdId3<<5; sFilterConfig.FilterMaskIdLow = StdId4<<5; sFilterConfig.FilterFIFOAssignment = 0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(&hcan, &sFilterConfig); }
|
2.4、32位掩码模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
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) { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh =ID<<5; sFilterConfig.FilterIdLow =0; sFilterConfig.FilterMaskIdHigh =(Mask<<5); sFilterConfig.FilterMaskIdLow =0|0x02; sFilterConfig.FilterFIFOAssignment = 0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.BankNumber = 14; HAL_CAN_ConfigFilter(hcan, &sFilterConfig); } else { CAN_FilterConfTypeDef sFilterConfig; sFilterConfig.FilterNumber = Filter_Nunber; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; 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、报文的发送和接受
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
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); }
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); } }
uint8_t date_CAN1[8]; uint8_t date_CAN2[8]; void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { if(hcan->Instance ==CAN1) { CAN_RxHeaderTypeDef RxHeader; HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, date_CAN1); return ; } else if(hcan->Instance == CAN2) { CAN_RxHeaderTypeDef RxHeader; HAL_CAN_GetRxMessage(&hcan2, CAN_RX_FIFO0, &RxHeader, date_CAN2); } }
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、IEC101
1、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蜂窝网络还是其他任何类型的网络连接)