I2C簡介
I2C 是 PHILIPS 公司開發(fā)的一種半雙工、雙向二線制同步串行總線。
兩線制代表 I2C 只需兩根信號線,一根數(shù)據(jù)線 SDA,另一根是時(shí)鐘線 SCL。這個(gè)也是I2C的優(yōu)勢所在,雖然傳輸速率較慢,但是占用引腳數(shù)量少,在引腳資源緊張的芯片上就特別好用。
I2C 總線允許掛載多個(gè)主設(shè)備,但總線時(shí)鐘同一時(shí)刻只能由一個(gè)主設(shè)備產(chǎn)生,并且要求每個(gè)連接到總線上的器件都有唯一的 I2C 地址,從設(shè)備可以被主設(shè)備尋址。那么我們想要在同一個(gè)I2C總線上使用多個(gè)主設(shè)備。
這里善于思考的同學(xué)可能就會想到如果我們想要在同一個(gè)I2C總線上并聯(lián)兩個(gè)一樣的設(shè)備ID沖突怎么辦呢?
之后帶大家看傳感器的Datasheet的時(shí)候,就會發(fā)現(xiàn)傳感器上面有一個(gè)ADDR引腳,根據(jù)它引腳電平高低會切換ID號,這樣就可以避免ID沖突了。
I2C通信具有幾類信號
開始信號S:當(dāng)SCL處于高電平時(shí),SDA從高電平拉低至低電平,代表數(shù)據(jù)傳輸?shù)拈_始
結(jié)束信號P:當(dāng)SCL處于高電平時(shí),SDA從低電平拉高至高電平,代表數(shù)據(jù)傳輸結(jié)束
數(shù)據(jù)信號:數(shù)據(jù)信號每次都傳輸8位數(shù)據(jù),每一位數(shù)據(jù)都在一個(gè)時(shí)鐘周期內(nèi)傳遞,當(dāng)SCL處于高電平時(shí),SDA數(shù)據(jù)線上的電平需要穩(wěn)定,當(dāng)SCL處于低電平的時(shí)候,SDA數(shù)據(jù)線上的電平才允許改變。
應(yīng)答信號ACK/NACK:應(yīng)答信號是主機(jī)發(fā)送8bit數(shù)據(jù),從機(jī)對主機(jī)發(fā)送低電平,表示已經(jīng)接收數(shù)據(jù)。
整個(gè)I2C通信過程理解成收發(fā)快遞的過程,設(shè)備I2C地址理解成學(xué)??爝f柜的地址,讀寫位代表寄出和簽收快遞,寄存器地址則是快遞柜上的箱號,而數(shù)據(jù)便是需要寄出或者簽收的快遞。整個(gè)過程便是如同到學(xué)校的快遞柜(從機(jī) I2C 地址),對第幾號柜箱(寄存器地址), 進(jìn)行寄出或者簽收快遞(數(shù)據(jù))的過程。
IST8310簡介
IST8310 是一款由 ISentek 公司推出的 3 軸磁場傳感器,尺寸為 3.03.01.0mm,支持快速 I2C 通信,可達(dá) 400kHz,14 位磁場數(shù)據(jù),測量范圍可達(dá)1600uT(x,y-axis)和 2500uT(z-axis), 最高 200Hz 輸出頻率。使用IST8310磁力計(jì)可以檢測地磁場強(qiáng)度,用于計(jì)算磁場角度。
下圖為IST8310的引腳功能表
此外我們可以整理等下我們會用到的GPIO引腳
以及我們可以看一下從機(jī)地址的設(shè)置,在這個(gè)芯片之后從機(jī)地址的設(shè)置是通過CAD0和CAD1兩個(gè)引腳設(shè)置的,這樣可以實(shí)現(xiàn)設(shè)置四個(gè)不同的地址。
下面是開發(fā)板的原理圖,我們可以看到下面CAD0和CAD1引腳都浮空了,根據(jù)上圖可以得知,這個(gè)IST8310的從機(jī)地址為0x0E與原理圖中標(biāo)注的一致。
CubeMX配置
首先我們開始看到我們的原理圖上。
根據(jù)上面兩個(gè)圖我們可以發(fā)現(xiàn)這個(gè)IST8310是掛載在I2C總線上面的。
STM32上I2C3_SCL在PA8,I2C_SDA在PC9,DRDY數(shù)據(jù)準(zhǔn)備引腳在PG3,復(fù)位RSTN引腳在PG6,接著在CubeMX進(jìn)行相應(yīng)的配置。
之后我們可以看到我們在CubeMX中配置的東西實(shí)際上都在board文件之中,其中board.c文件里面為我們的時(shí)鐘樹配置,stm32f4xx_hal_msp.c里的就是我們上面設(shè)置的那些引腳配置
這里就是剛剛I2C3配置的函數(shù),為啥突然開始講這個(gè)主要也是實(shí)習(xí)時(shí)發(fā)現(xiàn)大部分情況下是沒有STM32可以用的,也就不能用CubeMX那么輕松的配置了,pintopin替換直接把***當(dāng)作STM32使用總感覺會出問題,因此還是學(xué)習(xí)自己配置的方法。
else if(hi2c->Instance==I2C3)
{
/* USER CODE BEGIN I2C3_MspInit 0 /
/ USER CODE END I2C3_MspInit 0 */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**I2C3 GPIO Configuration
PC9 ------> I2C3_SDA
PA8 ------> I2C3_SCL
/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/ Peripheral clock enable /
__HAL_RCC_I2C3_CLK_ENABLE();
/ USER CODE BEGIN I2C3_MspInit 1 /
/ USER CODE END I2C3_MspInit 1 */
}
代碼編寫
首先在RT-Thread Settings組件中打開I2C設(shè)備驅(qū)動(dòng)程序
menuconfig BSP_USING_I2C3
bool "Enable I2C3 BUS (software simulation)"
default n
select RT_USING_I2C
select RT_USING_I2C_BITOPS
select RT_USING_PIN
if BSP_USING_I2C3
comment "Notice: PA8 --> 8; PC9 --> 41"
config BSP_I2C3_SCL_PIN
int "i2c1 scl pin number"
default 8
config BSP_I2C3_SDA_PIN
int "I2C1 sda pin number"
default 41
endif
配置完后我們來看一下幾個(gè)重要的實(shí)現(xiàn)函數(shù)
i2c寫入寄存器的函數(shù)
rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number)
{
rt_uint8_t buf[2];
buf[0] = write_addr;
buf[1] = data;
rt_size_t result;
result = rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, buf, 2);
rt_thread_mdelay(10);
if (result == 2)
{
rt_kprintf("IST8310 write failed,ERR is:%drn", result);
return -RT_ERROR;
}
}
這里我們函數(shù)的實(shí)現(xiàn)也是按照這個(gè)原理,rt_i2c_master_send首先將設(shè)備的地址IST8310_ADDR,加上讀寫位RT_I2C_WR發(fā)送
之后我們定義的buf緩沖區(qū)中裝著的就是我們要發(fā)送的寄存器地址write_addr,和要寫入的數(shù)據(jù)data。
需要注意的是rt_i2c_master_send返回的是發(fā)送的消息的個(gè)數(shù),且不包含一開始發(fā)送的設(shè)備地址IST8310_ADDR的。
下面的讀取函數(shù)也是同理的,先發(fā)送想要讀取的read_addr,然后利用rt_i2c_master_recv函數(shù)進(jìn)行讀取。
rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf)
{
//通知要讀哪個(gè)設(shè)備的哪個(gè)內(nèi)存地址的內(nèi)容,(告知是需要讀read_addr)
rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, &read_addr, 1);
//讀取到的內(nèi)容存入buf
rt_i2c_master_recv(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_RD, buf, len); //地址讀數(shù)據(jù)
}
這樣看完大家應(yīng)該對于i2c的通信方式更加熟悉了。
下面介紹的是讀取磁力計(jì)值函數(shù)ist8310_read_mag
void ist8310_read_mag(float mag[3])
{
uint8_t buf[6];
int16_t temp_ist8310_data = 0;
//read the "DATAXL" register (0x03)
ist8310_iic_read(0x03, 6, buf);
temp_ist8310_data = (int16_t) ((buf[1] << 8) | buf[0]);
mag[0] = MAG_SEN * temp_ist8310_data;
temp_ist8310_data = (int16_t) ((buf[3] << 8) | buf[2]);
mag[1] = MAG_SEN * temp_ist8310_data;
temp_ist8310_data = (int16_t) ((buf[5] << 8) | buf[4]);
mag[2] = MAG_SEN * temp_ist8310_data;
}
我們這里對照表格就可以看到數(shù)據(jù)寄存器是從0x03開始到0x08結(jié)束的,我們使用ist8310_iic_read實(shí)際上雖然我們一開始只說明了讀取0x03的值,但是根據(jù)消息長度的大小,它繼續(xù)讀取下面寄存器的值的。
最后給大家詳細(xì)一下初始化函數(shù)ist8310_init
rt_uint8_t ist8310_init(const char*name)
{
rt_uint8_t temp[2] = { 0, 0 };
ist8310_i2c_bus = (struct rt_i2c_bus_device *) rt_device_find(name);
rt_uint8_t res[2] = { 0, 0 };
rt_uint8_t writeNum = 0;
if (ist8310_i2c_bus == RT_NULL)
{
rt_kprintf("can't find %s device!n", name);
}
else
{
ist8310_RST_L();
rt_thread_mdelay(sleepTime);
ist8310_RST_H();
rt_thread_mdelay(sleepTime);
ist8310_IIC_read_single_reg(IST8310_WHO_AM_I, 1, res);
if (res[0] != IST8310_WHO_AM_I_VALUE)
{
initialized = RT_TRUE;
return IST8310_NO_SENSOR;
}
for (writeNum = 0; writeNum < IST8310_WRITE_REG_NUM; writeNum++)
//開啟中斷,并且設(shè)置低電平,平均采樣兩次,200Hz輸出頻率
{
ist8310_IIC_write_single_reg(ist8310_write_reg_data_error[writeNum][0],
ist8310_write_reg_data_error[writeNum][1]);
ist8310_delay_us(wait_time);
ist8310_IIC_read_single_reg(ist8310_write_reg_data_error[writeNum][0], 1, res);
ist8310_delay_us(wait_time);
if (res[0] != ist8310_write_reg_data_error[writeNum][1])
{
return ist8310_write_reg_data_error[writeNum][2];
}
}
initialized = RT_TRUE;
return IST8310_NO_ERROR;
}
}
第一個(gè)特殊點(diǎn)是讀取WHO AM I寄存器,這里是為了確認(rèn)讀取的是ist8310傳感器,避免ID沖突或者沒有傳感器導(dǎo)致后續(xù)的通信錯(cuò)誤。這里WHO AM I里的值默認(rèn)是10我們讀取到10則說明沒有問題。
第二個(gè)特殊點(diǎn)就是for循環(huán)里面的那些操作,ist8310_write_reg_data_error這個(gè)二維數(shù)組里面的內(nèi)容大家可以翻一下最后完整的ist8310.h代碼
這里做的事情實(shí)際上是對配置寄存器進(jìn)行寫入配置,對應(yīng)的寄存器即意義如下。大家根據(jù)下面的表格就可以計(jì)算出相應(yīng)要寫入的值了。
ist8310.c完整代碼
//
// Created by Goldengrandpa on 2022/11/4.
//
#include "ist8310.h"
rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number)
{
rt_uint8_t buf[2];
buf[0] = write_addr;
buf[1] = data;
rt_size_t result;
result = rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, buf, 2);
rt_thread_mdelay(10);
if (result == 2)
{
rt_kprintf("IST8310 write failed,ERR is:%drn", result);
return -RT_ERROR;
}
}
void ist8310_delay_us(uint16_t us)
{
uint32_t ticks = 0;
uint32_t told = 0, tnow = 0, tcnt = 0;
uint32_t reload = 0;
reload = SysTick->LOAD;
ticks = us * 72;
told = SysTick->VAL;
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow;
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks)
{
break;
}
}
}
}
rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf)
{
//通知要讀哪個(gè)設(shè)備的哪個(gè)內(nèi)存地址的內(nèi)容,(告知是需要讀read_addr)
rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, &read_addr, 1);
//讀取到的內(nèi)容存入buf
rt_i2c_master_recv(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_RD, buf, len); //地址讀數(shù)據(jù)
}
rt_err_t ist8310_IIC_write_single_reg(rt_uint8_t reg, rt_uint8_t data)
{
rt_uint8_t buf[2];
buf[0] = reg;
buf[1] = data;
if (rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, 0, buf, 2) == 2)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
uint8_t ist8310_IIC_read_single_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t buf)
{
struct rt_i2c_msg msgs[2];
msgs[0].addr = IST8310_ADDR; / 從機(jī)地址 /
msgs[0].flags = RT_I2C_WR; / 寫標(biāo)志 /
msgs[0].buf = ? / 從機(jī)寄存器地址 /
msgs[0].len = 1; / 發(fā)送數(shù)據(jù)字節(jié)數(shù) /
msgs[1].addr = IST8310_ADDR; / 從機(jī)地址 /
msgs[1].flags = RT_I2C_RD; / 讀標(biāo)志 /
msgs[1].buf = buf; / 讀取數(shù)據(jù)指針 /
msgs[1].len = len; / 讀取數(shù)據(jù)字節(jié)數(shù) /
if (rt_i2c_transfer(ist8310_i2c_bus, msgs, 2) == 2)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
void ist8310_RST_H(void)
{
rt_pin_write(IST8310_RSTN_PIN_NUM, 1);
}
void ist8310_RST_L(void)
{
rt_pin_write(IST8310_RSTN_PIN_NUM, 0);
}
void ist8310_read_mag(float mag[3])
{
uint8_t buf[6];
int16_t temp_ist8310_data = 0;
//read the "DATAXL" register (0x03)
ist8310_iic_read(0x03, 6, buf);
temp_ist8310_data = (int16_t) ((buf[1] << 8) | buf[0]);
mag[0] = MAG_SEN * temp_ist8310_data;
temp_ist8310_data = (int16_t) ((buf[3] << 8) | buf[2]);
mag[1] = MAG_SEN * temp_ist8310_data;
temp_ist8310_data = (int16_t) ((buf[5] << 8) | buf[4]);
mag[2] = MAG_SEN * temp_ist8310_data;
}
rt_uint8_t ist8310_init(const charname)
{
rt_uint8_t temp[2] = { 0, 0 };
ist8310_i2c_bus = (struct rt_i2c_bus_device *) rt_device_find(name);
rt_uint8_t res[2] = { 0, 0 };
rt_uint8_t writeNum = 0;
if (ist8310_i2c_bus == RT_NULL)
{
rt_kprintf("can't find %s device!n", name);
}
else
{
ist8310_RST_L();
rt_thread_mdelay(sleepTime);
ist8310_RST_H();
rt_thread_mdelay(sleepTime);
ist8310_IIC_read_single_reg(IST8310_WHO_AM_I, 1, res);
if (res[0] != IST8310_WHO_AM_I_VALUE)
{
initialized = RT_TRUE;
return IST8310_NO_SENSOR;
}
for (writeNum = 0; writeNum < IST8310_WRITE_REG_NUM; writeNum++)
{
ist8310_IIC_write_single_reg(ist8310_write_reg_data_error[writeNum][0],
ist8310_write_reg_data_error[writeNum][1]);
ist8310_delay_us(wait_time);
ist8310_IIC_read_single_reg(ist8310_write_reg_data_error[writeNum][0], 1, res);
ist8310_delay_us(wait_time);
if (res[0] != ist8310_write_reg_data_error[writeNum][1])
{
return ist8310_write_reg_data_error[writeNum][2];
}
}
initialized = RT_TRUE;
return IST8310_NO_ERROR;
}
}
static void i2c_ist8310_sample(int argc, char argv[])
{
rt_uint8_t buf;
rt_uint8_t result=0;
float msg[3] = { 0, 0, 0 };
char name[RT_NAME_MAX];
if (argc == 2)
{
rt_strncpy(name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(name, IST8310_I2C_BUS_NAME, RT_NAME_MAX);
}
if (!initialized)
{
/ 傳感器初始化 /
result=ist8310_init(name);
}
if (initialized)
{
ist8310_read_mag(msg);
rt_kprintf("read ist8310 sensor x:%f y:%f z:%fn", msg[0], msg[1], msg[2]);
}
else
{
rt_kprintf("%dn",result);
rt_kprintf("initialize sensor failed!n");
}
}
/ 導(dǎo)出到 msh 命令列表中 */
MSH_CMD_EXPORT(i2c_ist8310_sample, ist8310_sample);
ist8310.h
//
// Created by Goldengrandpa on 2022/11/4.
//
#ifndef RTTHREAD_IST8310_H
#define RTTHREAD_IST8310_H
#include
#include
#include "board.h"
#define IST8310_I2C_BUS_NAME "i2c3" /* 傳感器連接的I2C總線設(shè)備名稱 /
#define IST8310_ADDR 0X0E / 從機(jī)地址 /
#define IST8310_CALIBRATION_CMD 0xE1 / 校準(zhǔn)命令 /
#define IST8310_NORMAL_CMD 0xA8 / 一般命令 /
#define IST8310_GET_DATA 0xAC / 獲取數(shù)據(jù)命令 */
#define IST8310_WRITE_REG_NUM 4
#define IST8310_WHO_AM_I 0x00 //ist8310 "who am I "
#define IST8310_WHO_AM_I_VALUE 0x10 //device ID
#define IST8310_DATA_READY_BIT 2
#define IST8310_NO_ERROR 0x00
#define IST8310_NO_SENSOR 0x40
static const uint8_t wait_time = 150;
static const uint8_t sleepTime = 50;
#define IST8310_RSTN_PIN_NUM GET_PIN(G,6)
#define MAG_SEN 0.3f //raw int16 data change to uT unit. 原始整型數(shù)據(jù)變成 單位ut
typedef struct ist8310_real_data_t
{
uint8_t status;
float mag[3];
} ist8310_real_data_t;
//the first column:the registers of IST8310. 第一列:IST8310的寄存器
//the second column: the value to be writed to the registers.第二列:需要寫入的寄存器值
//the third column: return error value.第三列:返回的錯(cuò)誤碼
static const rt_uint8_t ist8310_write_reg_data_error[IST8310_WRITE_REG_NUM][3] ={
{0x0B, 0x08, 0x01}, //enalbe interrupt and low pin polarity.開啟中斷,并且設(shè)置低電平
{0x41, 0x09, 0x02}, //average 2 times.平均采樣兩次
{0x42, 0xC0, 0x03}, //must be 0xC0. 必須是0xC0
{0x0A, 0x0B, 0x04}}; //200Hz output rate.200Hz輸出頻率
static struct rt_i2c_bus_device ist8310_i2c_bus = RT_NULL; / I2C總線設(shè)備句柄 /
static rt_bool_t initialized = RT_FALSE; / 傳感器初始化狀態(tài) */
rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number);
rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf);
void ist8310_read_mag(float mag[3]);
static void read_mag(struct rt_i2c_bus_device *bus,float *cur_mag);/ 讀取磁場 /
void ist8310_RST_H(void);/ 設(shè)置RSTN引腳為1 /
void ist8310_RST_L(void);/ 設(shè)置RSTN引腳為0 /
void ist8310_delay_us(rt_uint16_t us);
rt_err_t ist8310_IIC_write_single_reg(rt_uint8_t reg, rt_uint8_t data);
rt_uint8_t ist8310_IIC_read_single_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf);
static void i2c_ist8310_sample(int argc, char *argv[]);
#endif //RTTHREAD_IST8310_H
運(yùn)行結(jié)果:
-
I2C總線
+關(guān)注
關(guān)注
8文章
386瀏覽量
60791 -
GPIO
+關(guān)注
關(guān)注
16文章
1189瀏覽量
51837 -
磁場傳感器
+關(guān)注
關(guān)注
0文章
41瀏覽量
12014 -
磁力計(jì)
+關(guān)注
關(guān)注
1文章
71瀏覽量
20824 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1261瀏覽量
39840
發(fā)布評論請先 登錄
相關(guān)推薦
評論