IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32 GPIO模拟多路I2C -> 正文阅读

[嵌入式]STM32 GPIO模拟多路I2C

STM32 GPIO模拟多路I2C访问

I2C/IIC/2-WIRE(相同的总线不同的命名方式)访问时序的实现可以通过硬件功能模块的配置控制实现,也可以通过GPIO模拟时序实现。

多路I2C设备的连接,如果每个I2C设备的地址不同,可以通过一路I2C总线连接各个设备,以发送协议的地址激活相应的I2C设备进行响应。

对于I2C设备地址相同的多个设备,则需要用多路并行I2C进行访问,通常硬件功能模块资源有限,就要通过GPIO模拟多路I2C总线实现。

这里的范例代码实现9路I2C总线模拟,因为模拟时序占用MCU时间,所以9路并不是同时进行访问,而是在要访问某一路I2C设备前,将内部代码逻辑对应控制的通道进行切换,所以是轮流而非并行的访问。

管脚的配置

首先建立基础工程并配置时钟后配置9路I2C的管脚,每路I2C配置为Open Drain的输出方式,可以设置为内部上拉,如果电路有外部上拉这里也可以设置为浮空。如:
在这里插入图片描述
保存后生成基础工程代码。

多路I2C代码实现

GPIO模拟多路I2C代码的实现分为几个部分

引入微秒延时函数

用于实现I2C时序延时的微秒延时函数,参见 STM32 HAL us delay(微秒延时)的指令延时实现方式及优化 。在代码里定义如下函数:

__IO float usDelayBase;
void PY_usDelayTest(void)
{
  __IO uint32_t firstms, secondms;
  __IO uint32_t counter = 0;

  firstms = HAL_GetTick()+1;
  secondms = firstms+1;

  while(uwTick!=firstms) ;

  while(uwTick!=secondms) counter++;

  usDelayBase = ((float)counter)/1000;
}

void PY_Delay_us_t(uint32_t Delay)
{
  __IO uint32_t delayReg;
  __IO uint32_t usNum = (uint32_t)(Delay*usDelayBase);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

void PY_usDelayOptimize(void)
{
  __IO uint32_t firstms, secondms;
  __IO float coe = 1.0;

  firstms = HAL_GetTick();
  PY_Delay_us_t(1000000) ;
  secondms = HAL_GetTick();

  coe = ((float)1000)/(secondms-firstms);
  usDelayBase = coe*usDelayBase;
}

void PY_Delay_us(uint32_t Delay)
{
  __IO uint32_t delayReg;

  __IO uint32_t msNum = Delay/1000;
  __IO uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase);

  if(msNum>0) HAL_Delay(msNum);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

在main.c源文件的系统时钟配置代码段后运行 PY_usDelayTest()和PY_usDelayOptimize()两个函数:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  PY_usDelayTest();
  PY_usDelayOptimize();

则可以使用微秒延时函数PY_Delay_us_t(x);

定义通道选择参数

I2C通道选择影响GPIO端口和管脚的对应关系,做如下的选择参数定义:

GPIO_TypeDef * I2C_SCL_PORT = GPIOB;
uint16_t I2C_SCL_PIN = GPIO_PIN_6;

GPIO_TypeDef * I2C_SDA_PORT = GPIOB;
uint16_t I2C_SDA_PIN = GPIO_PIN_7;

#define IIC1 1
#define IIC2 2
#define IIC3 3
#define IIC4 4
#define IIC5 5
#define IIC6 6
#define IIC7 7
#define IIC8 8
#define IIC9 9

void I2C_SEL(uint8_t I2Cn)
{
	switch(I2Cn)
	{
	case IIC1:
	     I2C_SCL_PORT = GPIOB;
	     I2C_SCL_PIN = GPIO_PIN_6;
	     I2C_SDA_PORT = GPIOB;
	     I2C_SDA_PIN = GPIO_PIN_7;
	     break;

	case IIC2:
	     I2C_SCL_PORT = GPIOB;
	     I2C_SCL_PIN = GPIO_PIN_4;
	     I2C_SDA_PORT = GPIOA;
	     I2C_SDA_PIN = GPIO_PIN_15;
	     break;

	case IIC3:
	     I2C_SCL_PORT = GPIOA;
	     I2C_SCL_PIN = GPIO_PIN_8;
	     I2C_SDA_PORT = GPIOB;
	     I2C_SDA_PIN = GPIO_PIN_15;
	     break;

	case IIC4:
	     I2C_SCL_PORT = GPIOB;
	     I2C_SCL_PIN = GPIO_PIN_13;
	     I2C_SDA_PORT = GPIOB;
	     I2C_SDA_PIN = GPIO_PIN_12;
	     break;

	case IIC5:
	     I2C_SCL_PORT = GPIOB;
	     I2C_SCL_PIN = GPIO_PIN_10;
	     I2C_SDA_PORT = GPIOB;
	     I2C_SDA_PIN = GPIO_PIN_2;
	     break;

	case IIC6:
	     I2C_SCL_PORT = GPIOB;
	     I2C_SCL_PIN = GPIO_PIN_0;
	     I2C_SDA_PORT = GPIOA;
	     I2C_SDA_PIN = GPIO_PIN_7;
	     break;

	case IIC7:
	     I2C_SCL_PORT = GPIOA;
	     I2C_SCL_PIN = GPIO_PIN_5;
	     I2C_SDA_PORT = GPIOA;
	     I2C_SDA_PIN = GPIO_PIN_4;
	     break;

	case IIC8:
	     I2C_SCL_PORT = GPIOA;
	     I2C_SCL_PIN = GPIO_PIN_2;
	     I2C_SDA_PORT = GPIOA;
	     I2C_SDA_PIN = GPIO_PIN_1;
	     break;

	case IIC9:
	     I2C_SCL_PORT = GPIOC;
	     I2C_SCL_PIN = GPIO_PIN_15;
	     I2C_SDA_PORT = GPIOC;
	     I2C_SDA_PIN = GPIO_PIN_14;
	     break;

	default: break;

	}
}

定义公共I2C管脚输入输出函数

通过I2C_SEL()函数可以切换 I2C_SCL_PORT,I2C_SCL_PIN,I2C_SDA_PORT, I2C_SDA_PIN的对应关系,从而I2C公共输入输出函数定义为:

void SCL_OUT_H(void)
{
	HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET);
}

void SCL_OUT_L(void)
{
	HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET);
}

void SDA_OUT_H(void)
{
	HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_SET);
}

void SDA_OUT_L(void)
{
	HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PIN_RESET);
}

uint8_t SDA_IN(void)
{
   return HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN);
}

定义公共I2C时序访问函数

通过微秒延时函数的应用,以及I2C输出输入管脚的控制,实现公共I2C时序访问函数。注意us_num定义用于控制访问速度。

#define us_num 10

void I2C_Init(void)
{
	I2C_SEL(IIC1);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC2);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC3);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC4);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC5);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC6);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC7);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC8);
	SDA_OUT_H();
	SCL_OUT_H();
	I2C_SEL(IIC9);
	SDA_OUT_H();
	SCL_OUT_H();
}

void I2C_Start(void)
{
	PY_Delay_us_t(us_num) ;
	SDA_OUT_H();
	SCL_OUT_H();
	PY_Delay_us_t(us_num/2) ;
	SDA_OUT_L();
	PY_Delay_us_t(us_num/2) ;
	SCL_OUT_L();
}

void I2C_Stop(void)
{
	SCL_OUT_L();
	PY_Delay_us_t(us_num) ;
	SDA_OUT_L();
	PY_Delay_us_t(us_num) ;
	SCL_OUT_H();
	PY_Delay_us_t(us_num) ;
	SDA_OUT_H();
	PY_Delay_us_t(us_num) ;
}

void I2C_Write_Ack(void)
{

    PY_Delay_us_t(us_num/2) ;
	SDA_OUT_L();
	PY_Delay_us_t(us_num/2) ;
	SCL_OUT_H();
	PY_Delay_us_t(us_num) ;
	SCL_OUT_L();
	SDA_OUT_H();

}

uint8_t I2C_Read_Ack(void)
{
	uint8_t status=0;

	SCL_OUT_L();
	PY_Delay_us_t(us_num/2) ;
	SDA_OUT_H();
	PY_Delay_us_t(us_num/2) ;
	status = SDA_IN();
	SCL_OUT_H();
	PY_Delay_us_t(us_num) ;
	SCL_OUT_L();
	SDA_OUT_L();

	return status;

}


void I2C_Send_Byte(uint8_t txd)
{


    for(uint8_t i=0;i<8;i++)
    {
    	PY_Delay_us_t(us_num/2) ;
        if((txd&0x80)>>7) SDA_OUT_H();
        else SDA_OUT_L();
        txd<<=1;
        PY_Delay_us_t(us_num/2) ;
        SCL_OUT_H();
        PY_Delay_us_t(us_num) ;
		SCL_OUT_L();
    }

    SDA_OUT_L();
}

uint8_t I2C_Read_Byte(unsigned char rdack)
{
	uint8_t rxd=0;


    for(uint8_t i=0;i<8;i++ )
	{
    	SCL_OUT_L();
    	PY_Delay_us_t(us_num/2) ;
    	SDA_OUT_H();
    	PY_Delay_us_t(us_num/2) ;
    	SCL_OUT_H();
        rxd<<=1;
        if(SDA_IN()) rxd++;
        PY_Delay_us_t(us_num) ;
    }

    SCL_OUT_L();
    SDA_OUT_H();

    if (rdack) I2C_Write_Ack();

    return rxd;
}

使用方式

首先调用I2C初始化函数I2C_Init(),其实就是将各路I2C通道的输出置为上拉高电平状态。
然后调用I2C通道选择函数I2C_SEL()选择要操作的I2C通道,如选择通道1为I2C_SEL(IIC1)。
接着就可以调用I2C_Send_Byte()和I2C_Read_Byte()进行写一个字节和读一个字节的访问。
使用者还需要根据具体I2C设备的协议时序要求,定义函数组合多次写字节和读字节以实现数据访问目的,通常包括写I2C 7位地址,写寄存器地址,写或读数据等。也可以根据需要,将单字节访问函数扩展为多字节访问函数。

SCL的可调特性

9路I2C访问占用了18个GPIO管脚。实际上,由于SCL是单向输出管脚,因此可以将9路I2C的时钟管脚SCL共用一个GPIO(如同一路I2C连接多路不同I2C地址设备场景),这样9路I2C的模拟就只需要用到10个GPIO(每路模拟I2C的SDA管脚必须是单独的)。需要注意SCL采用一拖多的情况下,时序会有所恶化,也就是会降低I2C的最高可访问速度。所以如果要每路I2C都跑得快,则每路I2C的SCL连接到单独的GPIO。

–End–

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:58:04  更:2022-10-08 20:59:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 19:21:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码