串行通信協(xié)議詳解 UART IIC SPI 串口-I2C-SPI協(xié)議區(qū)分對比 保姆級解說教程_嗶哩嗶哩_bilibili
以上視頻詳細介紹了三者的特點和工作情況
我們也可以看一下,UART、IIC和SPI對比表
特性 | 串口 | IIC | SPI |
---|---|---|---|
通信速率(最大) | 230400 bps | 400 kbps(快速) | 高達72 MHz(主機) |
通信線數(shù)量 | 2 根 | 2 根 | 4 根 |
通信距離 | 短距離 | 短距離 | 短距離 |
通信協(xié)議 | 異步串行 | 同步串行 | 同步串行 |
硬件復雜度 | 低 | 中等 | 中等 |
軟件復雜度 | 低 | 中等 | 中等 |
主從設備支持 | 支持(從設備) | 支持(主/從設備) | 支持(主/從設備) |
多設備通信 | 支持 | 支持 | 支持 |
數(shù)據(jù)傳輸完整性 | 低 | 高 | 高 |
UART其實也是可以進行多設備通信窄做。在多設備通信時佃迄,需要使用分時復用技術(shù)或者基于協(xié)議的多點通信技術(shù)。其中分時復用技術(shù)將多個設備連接到同一串口构诚,通過在不同時間間隔內(nèi)交替發(fā)送數(shù)據(jù)來實現(xiàn)多設備通信蚌斩;而基于協(xié)議的多點通信技術(shù)則使用特定的通信協(xié)議來允許多個設備連接到同一串口進行通信。因此范嘱,在選擇通信協(xié)議時送膳,需要考慮實際應用需求以及硬件和軟件資源的限制。
6個demo
以下我用51單片機和STM32單片機分別做了6個demo丑蛤,僅供參考
UART叠聋,51版本
這個程序使用定時器1控制串口通信的波特率,其中FREQ和BAUD分別表示單片機的工作頻率和波特率受裹。在初始化函數(shù)init_serial()中碌补,將定時器1配置為模式2,并計算出需要設定的初值棉饶,然后啟動定時器1厦章。同時,將串口配置為模式1照藻,允許接收袜啃,開啟串口中斷并開啟總中斷。在串口中斷處理函數(shù)serial_interrupt()中幸缕,處理接收到的數(shù)據(jù)和發(fā)送下一條數(shù)據(jù)群发。主循環(huán)中可以進行其他任務的處理。注意发乔,需要根據(jù)實際的硬件接口來配置串口相關(guān)的引腳和波特率等參數(shù)熟妓。
#include<reg52.h>
#define FREQ 11059200 // 單片機工作頻率
#define BAUD 9600 // 波特率
void init_serial() {
TMOD &= 0x0F;
TMOD |= 0x20; // 定時器1工作在模式2
TH1 = 256 - FREQ / (BAUD * 32);
TR1 = 1; // 啟動定時器1
SCON = 0x50; // 串口工作在模式1,允許接收
ES = 1; // 開啟串口中斷
EA = 1; // 開啟總中斷
}
void serial_interrupt() interrupt 4 {
if (RI) {
RI = 0; // 清除接收中斷標志
// 處理接收到的數(shù)據(jù)
// ...
}
if (TI) {
TI = 0; // 清除發(fā)送中斷標志
// 發(fā)送下一條數(shù)據(jù)
// ...
}
}
void main() {
init_serial();
// 主循環(huán)
while(1) {
// ...
}
}
UART列疗,STM32版本
這個程序使用USART1控制串口通信的波特率滑蚯,其中FREQ和BAUD分別表示單片機的工作頻率和波特率。在初始化函數(shù)init_serial()中,先使能USART1和GPIOA的時鐘告材,然后配置PA9為復用推挽輸出模式坤次,PA10為浮空輸入模式。接著斥赋,配置USART1的參數(shù)缰猴,包括波特率、字長疤剑、停止位滑绒、校驗位等等,并使能USART1隘膘。發(fā)送一個字節(jié)時疑故,先等待發(fā)送緩沖區(qū)為空,然后調(diào)用USART_SendData()發(fā)送一個字節(jié)弯菊。接收一個字節(jié)時纵势,先等待接收緩沖區(qū)非空,然后調(diào)用USART_ReceiveData()讀取一個字節(jié)管钳。
STm32F103單片機控制串口通信的簡單程序钦铁,波特率為9600:
#include "stm32f10x.h"
#define FREQ 72000000 // 單片機工作頻率
#define BAUD 9600 // 波特率
void init_serial() {
// 使能USART1和GPIOA的時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA9為復用推挽輸出模式,PA10為浮空輸入模式
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Pin = GPIO_Pin_9;
gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
gpio_init.GPIO_Pin = GPIO_Pin_10;
gpio_init.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio_init);
// 配置USART1的參數(shù)
USART_InitTypeDef usart_init;
usart_init.USART_BaudRate = BAUD;
usart_init.USART_WordLength = USART_WordLength_8b;
usart_init.USART_StopBits = USART_StopBits_1;
usart_init.USART_Parity = USART_Parity_No;
usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_init.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &usart_init);
// 使能USART1
USART_Cmd(USART1, ENABLE);
}
void send_byte(uint8_t byte) {
// 等待發(fā)送緩沖區(qū)為空
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
// 發(fā)送一個字節(jié)
USART_SendData(USART1, byte);
}
uint8_t receive_byte() {
// 等待接收緩沖區(qū)非空
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
// 讀取一個字節(jié)
return USART_ReceiveData(USART1);
}
int main() {
init_serial();
// 主循環(huán)
while(1) {
// 發(fā)送數(shù)據(jù)
send_byte(0x55);
// 接收數(shù)據(jù)
uint8_t received = receive_byte();
// 處理接收到的數(shù)據(jù)
// ...
}
return 0;
}
IIC才漆,51單片機
這個程序使用P1_0和P1_1分別控制I2C總線的數(shù)據(jù)線SDA和時鐘線SCL牛曹。在初始化函數(shù)init_i2c()中,先將SDA和SCL置為高電平醇滥。發(fā)送起始信號時黎比,將SDA置為高電平,SCL置為高電平腺办,等待一段時間焰手,然后將SDA置為低電平,等待一段時間后再將SC置為低電平怀喉,以此來發(fā)送起始信號书妻。發(fā)送停止信號時,先將SDA置為低電平躬拢,SCL置為高電平躲履,等待一段時間,然后將SDA置為高電平聊闯,等待一段時間后再將SCL置為高電平工猜,以此來發(fā)送停止信號。
發(fā)送一個字節(jié)時菱蔬,將字節(jié)的每一位從高位到低位依次寫入SDA篷帅,然后將SCL置為高電平史侣,等待一段時間,再將SCL置為低電平魏身,等待一段時間后將字節(jié)向左移位惊橱。寫入字節(jié)的同時,可以將ACK信號讀取出來箭昵,ACK為0表示接收到數(shù)據(jù)税朴,ACK為1表示接收數(shù)據(jù)出現(xiàn)了錯誤。
讀取一個字節(jié)時家制,先將SDA置為高電平正林,然后從高位到低位依次讀取SDA上的數(shù)據(jù),并將它們組成一個字節(jié)颤殴,最后向左移位觅廓。在讀取每一位數(shù)據(jù)時,先將SCL置為高電平诅病,等待一段時間后再將SCL置為低電平哪亿,等待一段時間后再讀取SDA上的數(shù)據(jù)。最后贤笆,將ACK或NAK信號寫入SDA,然后再將SCL置為高電平讨阻,等待一段時間后再將SCL置為低電平芥永,以此來發(fā)送ACK或NAK信號。
注意钝吮,在每次讀取和寫入數(shù)據(jù)之前埋涧,都需要發(fā)送起始信號。這里給出的是一個簡單的I2C通信程序奇瘦,可以根據(jù)具體的應用需求進行修改和擴展棘催。
#include <reg51.h>
#define FREQ 11059200 // 單片機工作頻率
#define SDA P1_0 // I2C數(shù)據(jù)線
#define SCL P1_1 // I2C時鐘線
void delay_us(unsigned int t) {
while (t--) {
/* 空循環(huán) */
}
}
void start_i2c() {
SDA = 1;
SCL = 1;
delay_us(5);
SDA = 0;
delay_us(5);
SCL = 0;
}
void stop_i2c() {
SDA = 0;
SCL = 1;
delay_us(5);
SDA = 1;
delay_us(5);
}
unsigned char write_byte(unsigned char byte) {
unsigned char ack;
unsigned char i;
for (i = 0; i < 8; i++) {
if (byte & 0x80) {
SDA = 1;
} else {
SDA = 0;
}
SCL = 1;
delay_us(5);
SCL = 0;
byte <<= 1;
}
SDA = 1;
SCL = 1;
delay_us(5);
ack = SDA;
SCL = 0;
return ack;
}
unsigned char read_byte(unsigned char ack) {
unsigned char byte = 0;
unsigned char i;
SDA = 1;
for (i = 0; i < 8; i++) {
byte <<= 1;
SCL = 1;
delay_us(5);
if (SDA) {
byte |= 0x01;
}
SCL = 0;
}
if (ack) {
SDA = 0;
} else {
SDA = 1;
}
SCL = 1;
delay_us(5);
SCL = 0;
SDA = 1;
return byte;
}
void init_i2c() {
SDA = 1;
SCL = 1;
delay_us(5);
}
int main() {
init_i2c();
start_i2c();
write_byte(0xA0); // 寫器件地址
write_byte(0x00); // 寫寄存器地址
write_byte(0x55); // 寫數(shù)據(jù)
stop_i2c();
start_i2c();
write_byte(0xA0); // 寫器件地址
write_byte(0x00); // 寫寄存器地址
start_i2c();
write_byte(0xA1); // 讀器件地址
unsigned char data = read_byte(0); // 讀數(shù)據(jù)
stop_i2c();
return 0;
}
IIC,STM32單片機
以下是一個簡單的I2C通信程序耳标,使用STM32 HAL庫來實現(xiàn)醇坝。這個程序可以向I2C設備發(fā)送數(shù)據(jù),并從I2C設備讀取數(shù)據(jù)次坡。
首先需要在STM32的CubeMX中配置I2C外設呼猪,并將其設置為主機模式。在代碼中砸琅,使用了默認的I2C1外設宋距。
#include "main.h"
#include "stm32f1xx_hal.h"
I2C_HandleTypeDef hi2c1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
uint8_t data_to_send = 0xAA; // 要發(fā)送的數(shù)據(jù)
uint8_t data_received; // 接收到的數(shù)據(jù)
while (1)
{
// 發(fā)送數(shù)據(jù)
HAL_I2C_Master_Transmit(&hi2c1, 0x50, &data_to_send, 1, 1000);
// 等待一段時間
HAL_Delay(100);
// 讀取數(shù)據(jù)
HAL_I2C_Master_Receive(&hi2c1, 0x50, &data_received, 1, 1000);
…………
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
SPI,51單片機
下面是一個簡單的SPI程序示例症脂,使用51單片機的SPI接口與一個SPI設備進行通信谚赎。假設單片機時鐘頻率為12MHz淫僻,SPI時鐘頻率為1MHz,SPI設備的CS引腳接在P1.4上壶唤,發(fā)送的數(shù)據(jù)為0x55嘁傀,接收的數(shù)據(jù)存儲在一個變量中:
在這個程序中,首先使用SPI_Init函數(shù)初始化SPI接口视粮,設置了波特率為1MHz细办,時鐘極性為低電平,時鐘相位為第一邊沿蕾殴,SPI時鐘分頻為4笑撞,主機模式。然后在主函數(shù)中钓觉,使用SPI_Transfer函數(shù)向SPI設備發(fā)送一個字節(jié)的數(shù)據(jù)茴肥,同時接收從設備返回的數(shù)據(jù),并將接收到的數(shù)據(jù)存儲在一個變量中荡灾。在SPI_Transfer函數(shù)中瓤狐,首先選中SPI設備(即拉低CS引腳),然后通過串口發(fā)送數(shù)據(jù)批幌,等待發(fā)送和接收完成础锐,并取消SPI設備的選中狀態(tài),最后返回接收到的數(shù)據(jù)荧缘。
需要注意的是皆警,這個程序僅發(fā)送和接收了一個字節(jié)的數(shù)據(jù),如果需要發(fā)送更多的數(shù)據(jù)截粗,可以通過循環(huán)調(diào)用SPI_Transfer函數(shù)來完成信姓。此外,程序中的波特率和SPI時鐘分頻需要根據(jù)具體應用需要進行調(diào)整绸罗,還需要根據(jù)實際連接的SPI設備的時鐘特性進行調(diào)整意推。
#include <reg52.h>
sbit CS = P1^4; // 定義CS引腳
void SPI_Init(void)
{
TMOD = 0x00; // 配置為模式0,同時清除定時器/計數(shù)器的設置
SCON = 0x50; // 配置為模式1
PCON |= 0x80; // 將SMOD置1珊蟀,使串口波特率翻倍
TH1 = 0xFA; // 設置波特率為9600bps
TL1 = 0xFA;
TR1 = 1; // 啟動定時器/計數(shù)器1
ES = 0; // 禁止串口中斷
SS = 1; // 禁止從機模式
CPHA = 0; // 時鐘極性為低電平
CPOL = 0; // 時鐘相位為第一邊沿
SPR1 = 0; // 設置SPI時鐘分頻為4
SPR0 = 0;
MSTR = 1; // 主機模式
SPIEN = 1; // 啟動SPI
}
unsigned char SPI_Transfer(unsigned char data)
{
CS = 0; // 選中SPI設備
SBUF = data; // 發(fā)送數(shù)據(jù)
while(!TI); // 等待發(fā)送完成
TI = 0;
……
}
SPI菊值,STM32單片機
使用STM32單片機的SPI接口與一個SPI設備進行通信。假設單片機時鐘頻率為72MHz系洛,SPI時鐘頻率為1MHz俊性,SPI設備的CS引腳接在PA4上,發(fā)送的數(shù)據(jù)為0x55描扯,接收的數(shù)據(jù)存儲在一個變量中:
在這個程序中定页,首先使用SPI_Init函數(shù)初始化SPI接口,設置了SPI模式為主機模式绽诚,數(shù)據(jù)傳輸大小為8位典徊,時鐘極性為低電平杭煎,時鐘相位為第一邊沿,SPI時鐘分頻為32卒落。然后在主函數(shù)中羡铲,使用SPI_Transfer函數(shù)向SPI設備發(fā)送一個字節(jié)的數(shù)據(jù),同時接收從設備返回的數(shù)據(jù)儡毕,并將接收到的數(shù)據(jù)存儲在一個變量中也切。在SPI_Transfer函數(shù)中,首先選中SPI設備(即拉低CS引腳)腰湾,然后通過HAL庫的HAL_SPI_TransmitReceive函數(shù)發(fā)送和接收數(shù)據(jù)雷恃,并取消SPI設備的選中狀態(tài),最后返回接收到的數(shù)據(jù)费坊。
需要注意的是倒槐,這個程序僅發(fā)送和接收了一個字節(jié)的數(shù)據(jù),如果需要發(fā)送更多的數(shù)據(jù)附井,可以通過循環(huán)調(diào)用SPI_Transfer函數(shù)來完成讨越。此外,程序中的SPI時鐘分頻需要根據(jù)實際連接的SPI設備的時鐘特性進行調(diào)整永毅。
#include "stm32f1xx_hal.h"
SPI_HandleTypeDef hspi1;
void SPI_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi1);
}
unsigned char SPI_Transfer(unsigned char data)
{
unsigned char recv_data;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, &data, &recv_data, 1, 100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
return recv_data;
}
int main(void)
{
unsigned char data;
HAL_Init();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
SPI_Init();
data = SPI_Transfer(0x55);
while (1)
{
………………
}
}