歡迎來到我的博客。今天我想向大家介紹一下STM32軟件I2C功能。
首先,讓我們來了解一下I2C(Inter-Integrated Circuit)總線。I2C是一種串行通信總線,最初由Philips公司開發(fā)。它允許多個設(shè)備使用同一條總線進行通信,并且每個設(shè)備都有唯一的地址。I2C通常用于連接微控制器、傳感器和其他外設(shè)。
在STM32中,I2C總線被實現(xiàn)為硬件和軟件兩種方式。硬件I2C功能可以直接使用STM32芯片上的I2C外設(shè),而軟件I2C需要通過編程實現(xiàn)。由于某些應用場景不適宜使用硬件I2C功能,所以軟件I2C在STM32中也變得非常重要。
STM32軟件I2C功能與硬件I2C功能類似,它們之間的主要區(qū)別在于數(shù)據(jù)傳輸?shù)倪^程。軟件I2C需要使用GPIO口模擬I2C通信過程,因此實現(xiàn)起來相對復雜。但是軟件I2C具有很高的靈活性,可以根據(jù)需要進行修改和擴展。
在STM32中,軟件I2C驅(qū)動程序通常由以下幾個部分組成:
初始化:這一步包括配置GPIO口、設(shè)置時序等操作,以確保I2C通信正常進行。
啟動:啟動信號是I2C總線上的一個信號,用于指示傳輸開始。為了在軟件I2C中實現(xiàn)“啟動”信號,我們需要將SDA(數(shù)據(jù)線)從高電平拉到低電平,然后將SCL(時鐘線)從高電平拉到低電平。
停止:停止信號用于指示傳輸結(jié)束。在軟件I2C中,我們需要將SCL從低電平拉到高電平,然后將SDA從低電平拉到高電平。
數(shù)據(jù)傳輸:數(shù)據(jù)傳輸通過向SDA寫入位來完成。在傳輸數(shù)據(jù)之前,我們需要向SCL寫入一個脈沖來獲取ACK(應答)信號,以確保數(shù)據(jù)已被正確接收。
雖然軟件I2C比硬件I2C更加復雜,但它具有很高的靈活性和可擴展性。此外,在某些情況下,軟件I2C可以提供更好的性能和功耗優(yōu)化。
下面上代碼。根據(jù)野火例程修改而來,已驗證。
bsp_i2c_gpio.c
/**
******************************************************************************
* @file bsp_i2c_ee.c
* @version V1.0
* @date 2023-4-12
* @brief 用gpio模擬i2c總線, 適用于STM32系列CPU。該模塊不包括應用層命令幀,僅包括I2C總線基本操作函數(shù)。
******************************************************************************
#include "bsp_i2c_gpio.h"
#include "stm32f4xx.h"
#include <stdio.h>
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_Delay
* 功能說明: I2C總線位延遲,最快400KHz
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
uint8_t i;
/*
下面的時間是通過邏輯分析儀測試得到的。
工作條件:CPU主頻72MHz ,MDK編譯環(huán)境,1級優(yōu)化
循環(huán)次數(shù)為10時,SCL頻率 = 205KHz
循環(huán)次數(shù)為7時,SCL頻率 = 347KHz, SCL高電平時間1.5us,SCL低電平時間2.87us
循環(huán)次數(shù)為5時,SCL頻率 = 421KHz, SCL高電平時間1.25us,SCL低電平時間2.375us
*/
for (i = 0; i < 10; i++)
;
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_Start
* 功能說明: CPU發(fā)起I2C總線啟動信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 當SCL高電平時,SDA出現(xiàn)一個下跳沿表示I2C總線啟動信號 */
BSP_I2C_SDA_1();
BSP_I2C_SCL_1();
i2c_Delay();
BSP_I2C_SDA_0();
i2c_Delay();
BSP_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_Stop
* 功能說明: CPU發(fā)起I2C總線停止信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 當SCL高電平時,SDA出現(xiàn)一個上跳沿表示I2C總線停止信號 */
BSP_I2C_SDA_0();
BSP_I2C_SCL_1();
i2c_Delay();
BSP_I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_SendByte
* 功能說明: CPU向I2C總線設(shè)備發(fā)送8bit數(shù)據(jù)
* 形 參:_ucByte : 等待發(fā)送的字節(jié)
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先發(fā)送字節(jié)的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
BSP_I2C_SDA_1();
}
else
{
BSP_I2C_SDA_0();
}
i2c_Delay();
BSP_I2C_SCL_1();
i2c_Delay();
BSP_I2C_SCL_0();
if (i == 7)
{
BSP_I2C_SDA_1(); // 釋放總線
}
_ucByte <<= 1; /* 左移一個bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_ReadByte
* 功能說明: CPU從I2C總線設(shè)備讀取8bit數(shù)據(jù)
* 形 參:無
* 返 回 值: 讀到的數(shù)據(jù)
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 讀到第1個bit為數(shù)據(jù)的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
BSP_I2C_SCL_1();
i2c_Delay();
if (BSP_I2C_SDA_READ())
{
value++;
}
BSP_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_WaitAck
* 功能說明: CPU產(chǎn)生一個時鐘,并讀取器件的ACK應答信號
* 形 參:無
* 返 回 值: 返回0表示正確應答,1表示無器件響應
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
BSP_I2C_SDA_1(); /* CPU釋放SDA總線 */
i2c_Delay();
BSP_I2C_SCL_1(); /* CPU驅(qū)動SCL = 1, 此時器件會返回ACK應答 */
i2c_Delay();
if (BSP_I2C_SDA_READ()) /* CPU讀取SDA口線狀態(tài) */
{
re = 1;
}
else
{
re = 0;
}
BSP_I2C_SCL_0();
i2c_Delay();
return re;
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_Ack
* 功能說明: CPU產(chǎn)生一個ACK信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_Ack(void)
{
BSP_I2C_SDA_0(); /* CPU驅(qū)動SDA = 0 */
i2c_Delay();
BSP_I2C_SCL_1(); /* CPU產(chǎn)生1個時鐘 */
i2c_Delay();
BSP_I2C_SCL_0();
i2c_Delay();
BSP_I2C_SDA_1(); /* CPU釋放SDA總線 */
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_NAck
* 功能說明: CPU產(chǎn)生1個NACK信號
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_NAck(void)
{
BSP_I2C_SDA_1(); /* CPU驅(qū)動SDA = 1 */
i2c_Delay();
BSP_I2C_SCL_1(); /* CPU產(chǎn)生1個時鐘 */
i2c_Delay();
BSP_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 數(shù) 名: i2c_CfgGpio
* 功能說明: 配置I2C總線的GPIO,采用模擬IO的方式實現(xiàn)
* 形 參:無
* 返 回 值: 無
*********************************************************************************************************
*/
void i2c_CfgGpio(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
I2Cx_SCL_GPIO_CLK_ENABLE();
I2Cx_SDA_GPIO_CLK_ENABLE();
/**I2C2 GPIO Configuration
PB10 ------> I2C2_SCL
PB9 ------> I2C2_SDA
*/
GPIO_InitStruct.Pin = BSP_I2C_SCL_PIN | BSP_I2C_SDA_PIN;
;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(BSP_GPIO_PORT_I2C, &GPIO_InitStruct);
/* 給一個停止信號, 復位I2C總線上的所有設(shè)備到待機模式 */
i2c_Stop();
}
bsp_i2c_gpio.h
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H
#include <inttypes.h>
#define BSP_I2C_WR 0 /* 寫控制bit */
#define BSP_I2C_RD 1 /* 讀控制bit */
/* 定義I2C總線連接的GPIO端口時鐘控制 */
#define I2Cx_SDA_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2Cx_SCL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
/* 定義I2C總線連接的GPIO端口, 用戶只需要修改下面3行代碼即可任意改變SCL和SDA的引腳 */
#define BSP_GPIO_PORT_I2C GPIOB /* GPIO端口 */
#define BSP_I2C_SCL_PIN GPIO_PIN_8 /* 連接到SCL時鐘線的GPIO */
#define BSP_I2C_SDA_PIN GPIO_PIN_9 /* 連接到SDA數(shù)據(jù)線的GPIO */
/* 定義讀寫SCL和SDA的宏,已增加代碼的可移植性和可閱讀性 */
#if 0 /* 條件編譯: 1 選擇GPIO的庫函數(shù)實現(xiàn)IO讀寫 */
#define BSP_I2C_SCL_1() digitalH(BSP_GPIO_PORT_I2C, BSP_I2C_SCL_PIN) /* SCL = 1 */
#define BSP_I2C_SCL_0() digitalL(BSP_GPIO_PORT_I2C, BSP_I2C_SCL_PIN) /* SCL = 0 */
#define BSP_I2C_SDA_1() digitalH(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* SDA = 1 */
#define BSP_I2C_SDA_0() digitalL(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* SDA = 0 */
//#define BSP_I2C_SDA_READ() GPIO_ReadInputDataBit(BSP_GPIO_PORT_I2C, BSP_I2C_SDA_PIN) /* 讀SDA口線狀態(tài) */
#define BSP_I2C_SDA_READ() ((BSP_GPIO_PORT_I2C->IDR & BSP_I2C_SDA_PIN) != 0) /* 讀SDA口線狀態(tài) */
#else /* 這個分支選擇直接寄存器操作實現(xiàn)IO讀寫 */
/* 注意:如下寫法,在IAR最高級別優(yōu)化時,會被編譯器錯誤優(yōu)化 */
#define BSP_I2C_SCL_1() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SCL_PIN /* SCL = 1 */
#define BSP_I2C_SCL_0() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SCL_PIN << 16U /* SCL = 0 */
#define BSP_I2C_SDA_1() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SDA_PIN /* SDA = 1 */
#define BSP_I2C_SDA_0() BSP_GPIO_PORT_I2C->BSRR = (uint32_t)BSP_I2C_SDA_PIN << 16U /* SDA = 0 */
#define BSP_I2C_SDA_READ() ((BSP_GPIO_PORT_I2C->IDR & BSP_I2C_SDA_PIN) != 0) /* 讀SDA口線狀態(tài) */
#endif
/* 直接操作寄存器的方法控制IO */
#define digitalH(p, i) \
{ \
p->BSRR = i; \
} // 設(shè)置為高電平
#define digitalL(p, i) \
{ \
p->BSRR = (uint32_t)i << 16; \
} // 輸出低電平
void i2c_CfgGpio(void);
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
#endif
最后,不要忘記在主程序中調(diào)用 i2c_CfgGpio();
完成用于模擬I2C的GPIO初始化。
應用實例
這里我使用的是lis2dw12加速度傳感器,在數(shù)據(jù)手冊中給出了I2C通信時序如下。
Master:主機
Slave:從機
ST:起始信號 START signal
SAD:從機地址 Slave Address
SAK:從機應答 slave acknowledge
DATA :8位的數(shù)據(jù)內(nèi)容
SP:停止信號 STOP signal
NMAK :非主機應答 No Master Acknowledge
SUB:8位的子地址 8-bit sub-address
W :讀操作
R:寫操作
軟件模擬這個流程就能實現(xiàn)通訊,對照時序圖和程序的每一個步驟閱讀,方便理解。
使用前記得包含頭文件
#include "bsp_i2c_gpio.h"
軟件模擬讀操作
/*
* @brief Read generic device register (platform dependent)
*
* @param handle customizable argument. In this examples is used in
* order to select the correct sensor bus handler.
* @param reg register to read
* @param bufp pointer to buffer that store the data read
* @param len number of consecutive register to read
*
*/
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
uint16_t len)
{
uint16_t i;
/* 第1步:發(fā)起I2C總線啟動信號 */
i2c_Start();
/* 第2步:發(fā)送控制字節(jié),高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
i2c_SendByte(BSP_I2C_ADD | BSP_I2C_WR); /* 寫指令 */
/* 第3步:等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail;
}
/* 第4步: 發(fā)送SUB */
i2c_SendByte(reg);
/* 第5步: 等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail;
}
/* 第6步: 發(fā)送SR (repeated START) */
i2c_Start();
/* 第7步: 發(fā)送控制字節(jié) */
i2c_SendByte(BSP_I2C_ADD | BSP_I2C_RD); /* 讀指令 */
/* 第8步: 發(fā)送ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail;
}
/* 第9步: 循環(huán)讀取數(shù)據(jù) */
for (i = 0; i < len; i++)
{
bufp[i] = i2c_ReadByte(); /* 讀1個字節(jié) */
/* 每讀完1個字節(jié)后,需要主機發(fā)送ACK,最后一個字節(jié)發(fā)送NACK */
if (i != len - 1)
{
i2c_Ack(); /* 中間字節(jié)讀完后,CPU產(chǎn)生ACK信號(驅(qū)動SDA = 0) */
}
else
{
i2c_NAck(); /* 最后1個字節(jié)讀完后,CPU產(chǎn)生NACK信號(驅(qū)動SDA = 1) */
}
}
/* 第10步:發(fā)送停止信號 */
i2c_Stop();
return 0;
cmd_fail:
i2c_Stop();
return 1;
}
軟件模擬寫操作
文章來源:http://www.zghlxwxcb.cn/news/detail-741649.html
/*
* @brief Write generic device register (platform dependent)
*
* @param handle customizable argument. In this examples is used in
* order to select the correct sensor bus handler.
* @param reg register to write
* @param bufp pointer to buffer that store the data read
* @param len number of consecutive register to read
*
*/
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
uint16_t len)
{
uint16_t i;
/* 第0步: 發(fā)送停止信號 */
i2c_Stop();
/* 第1步: 發(fā)起I2C總線啟動信號 */
i2c_Start();
/* 第2步: 發(fā)送控制字節(jié) */
i2c_SendByte(BSP_I2C_ADD | BSP_I2C_WR);
/* 第3步: 等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail;
}
/* 第4步: 發(fā)送SUB */
i2c_SendByte(reg);
/* 第5步: 等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail;
}
/* 第6步: 循環(huán)發(fā)送DATA */
for (i = 0; i < len; i++)
{
i2c_SendByte(bufp[i]); /* 發(fā)一個數(shù)據(jù) */
i2c_Ack(); /*發(fā)完一個數(shù)據(jù)后等待ACK*/
}
/* 第7步: 發(fā)送停止信號 */
i2c_Stop();
return 0;
cmd_fail:
i2c_Stop();
return 1;
}
總而言之,STM32軟件I2C是一種非常重要的通信方式,尤其適用于那些不適合使用硬件I2C的應用場景。希望本文對你了解STM32軟件I2C功能有所幫助,如果有不理解的地方歡迎私信留言。文章來源地址http://www.zghlxwxcb.cn/news/detail-741649.html
到了這里,關(guān)于【STM32】軟件I2C的使用 —— 看這一篇就夠了(附代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!