STM8S GPIO模拟I2C的底层代码

此GPIO模拟I2C代码已在STM8S103K3上测试通过,测试所用下位机为SHT20温湿度传感器。SHT20测量代码请参考其数据手册。此篇Blog只提供GPIO模拟I2C的代码。
文中,假定MCU叫做主机,SHT20叫做从机。

【调试日志】

  1. @2020-03-15,STM8S–SHT20联调通过

底层代码

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

GPIO初始化设置

这颗STM8S上,具有真·开漏功能的引脚是PB4和PB5,这两个引脚也正是片上I2C外设的默认引脚。端口设置如下:

  1. PB4 :I2C的SCL时钟线,开漏输出模式
  2. PB5 :I2C的SDA数据线,开漏输出模式
  3. SCL和SDA初始化时,推荐选用高阻态输出
/**
  * @brief  I2C端口初始化
  * @param  None
  * @retval None
  */
void I2C_GPIO_Init(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
  I2C_SDA_OUT(1);
  I2C_SCL_OUT(1);
}

GPIO输出电平设置,输出/输入方向设置

  1. 电平设置
/**
  * @brief  SDA输出电平
  * @param  value:1=输出高电平  0=输出低电平 
  * @retval None
  */
void I2C_SDA_OUT(unsigned char value)
{
  if(value==1)
    GPIO_WriteHigh(I2C_PORT, I2C_SDA_PIN);
  else if(value==0)
    GPIO_WriteLow(I2C_PORT, I2C_SDA_PIN);
}

/**
  * @brief  SCL输出电平
  * @param  value:1=输出高电平  0=输出低电平 
  * @retval None
  */
void I2C_SCL_OUT(unsigned char value)
{
  if(value==1)
    GPIO_WriteHigh(I2C_PORT, I2C_SCL_PIN);
  else if(value==0)
    GPIO_WriteLow(I2C_PORT, I2C_SCL_PIN);
}
  1. I2C主机向从机发送一个字节,都会等待从机通过SDA线返回一个“已接收(ACK)”指令。此时主机的SDA应设置为输入,而在其他环节中应该保持开漏输出。而根据不同从机的I2C时序,有时也需要将SCL设置为输入。
/**
  * @brief  SDA设为输出
  * @param  None
  * @retval None
  */
void I2C_SDA_SET_OUTPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
}

/**
  * @brief  SCL设为输出
  * @param  None
  * @retval None
  */
void I2C_SCL_SET_OUTPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_OUT_OD_HIZ_SLOW);
}

/**
  * @brief  SDA设为输入
  * @param  None
  * @retval None
  */
void I2C_SDA_SET_INPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SDA_PIN, GPIO_MODE_IN_FL_NO_IT);
}

/**
  * @brief  SCL设为输入
  * @param  None
  * @retval None
  */
void I2C_SCL_SET_INPUT(void)
{
  GPIO_Init(I2C_PORT, I2C_SCL_PIN, GPIO_MODE_IN_FL_NO_IT);
}

GPIO输入检测

既然有了SDA或SCL的输入设置,自然需要有判断输入值的函数。

/**
  * @brief  SDA输入电平检测
  * @param  None
  * @retval 1=输入为高电平  0=输入为低电平
  */
unsigned char I2C_SDA_IN(void)
{
  return (!!GPIO_ReadInputPin(I2C_PORT, I2C_SDA_PIN));
}

/**
  * @brief  SCL输入电平检测
  * @param  None
  * @retval 1=输入为高电平  0=输入为低电平
  */
unsigned char I2C_SCL_IN(void)
{
  return (!!GPIO_ReadInputPin(I2C_PORT, I2C_SCL_PIN));
}

模拟I2C代码

包括:

  1. 起始条件,结束条件
  2. 回从机ACK,回从机NACK: 用于主机从从机读1字节(此时主从的身份对调)后,还要向其发送请继续(ACK)请停下(NACK) 信号
  3. 等待从机回ACK: 主机向从机发送1字节后,需要等待从机返回接收到(ACK) ,如果接收到的是 接收到(ACK)
  4. 从从机读1字节: 完成读1字节后,需要向从机发送请继续(ACK)请停下(NACK) 信号
  5. 向从机写1细节: 完成写1字节后,需要等待从机返回接收到(ACK) 信号。

起始条件,结束条件

/**
  * @brief  I2C传输开始
  * @param  None
  * @retval None
  */
void I2C_START(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(1);
  I2C_SCL_OUT(1);
  Delay_1us(10);/////////////
  I2C_SDA_OUT(0);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

/**
  * @brief  I2C传输结束
  * @param  None
  * @retval None
  */
void I2C_STOP(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(0);
  I2C_SCL_OUT(0);
  Delay_1us(10);//////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SDA_OUT(1);
  Delay_1us(10);
}

回从机ACK,回从机NACK

/**
  * @brief  MCU回复IC ACK信号
  * @param  None
  * @retval None
  */
void I2C_SEND_ACK(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(0);
  Delay_1us(10);//////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

/**
  * @brief  MCU回复NACK信号
  * @param  None
  * @retval None
  */
void I2C_SEND_NACK(void)
{
  I2C_SDA_SET_OUTPUT();
  
  I2C_SDA_OUT(1);
//  Delay_1us(10);///////////////
  I2C_SCL_OUT(1);
  Delay_1us(10);
  I2C_SCL_OUT(0);
  Delay_1us(10);
}

从从机读1字节

/**
  * @brief  I2C MCU接收1字节
  * @param  ACK_CHOICE:  数据读取后主机如何回复,I2C_ACK=回复ACK  I2C_NACK=回复NACK  I2C_JUMP_REPLY=跳过回复
  * @retval read_data:   接收的字节
  */
unsigned char I2C_READ_BYTE(unsigned char ACK_CHOICE)
{
  unsigned char read_data=0;
  
  I2C_SDA_SET_INPUT();
  
//  I2C_SCL_OUT(0);//////////////////
//  Delay_1us(10);///////////////////
  
  for(unsigned char i=0x80; i!=0; i>>=1)
  {
    I2C_SCL_OUT(1);
    Delay_1us(10);
    if(I2C_SDA_IN() == 1)
      read_data |= i;
    I2C_SCL_OUT(0);
    Delay_1us(10);
  }
  I2C_SDA_SET_OUTPUT();
  I2C_SDA_OUT(1);//?????????????????
  
  if(ACK_CHOICE==I2C_ACK)       I2C_SEND_ACK();
  if(ACK_CHOICE==I2C_NACK)      I2C_SEND_NACK();
  
  return read_data;
}

向从机写1字节

/**
  * @brief  I2C MCU发送1字节
  * @param  send_data:  发送的字节
  * @retval ack回复:    I2C_NACK=异常=检测到NACK  I2C_ACK=正常=检测到ACK
  */
unsigned char I2C_SEND_BYTE(unsigned char send_data)
{
  unsigned char ack=I2C_NACK;   //初始值
  unsigned char time_out=200;   //ACK查询次数
  

  I2C_SDA_SET_OUTPUT();
//  I2C_SCL_SET_OUTPUT();
  
//  I2C_SCL_OUT(0);////////////////
//  Delay_1us(10);/////////////////////////
  
  for(unsigned char i=0x80; i!=0; i>>=1)
  {
    if(send_data&i)     I2C_SDA_OUT(1);
    else                I2C_SDA_OUT(0);
    I2C_SCL_OUT(1);
    Delay_1us(10);
    I2C_SCL_OUT(0);
    Delay_1us(10);
  }
  
  //开始ACK查询
  I2C_SDA_SET_INPUT();
  I2C_SCL_OUT(1);
  while(time_out--)
  {
    if(I2C_SDA_IN()==1)
      ack=I2C_NACK;
    else
    {
      ack=I2C_ACK;
      break;
    }
    Delay_1us(1);
  }
  
  I2C_SCL_OUT(0);
  
  I2C_SDA_SET_OUTPUT();
  I2C_SDA_OUT(1);//???????????????????
  
  return ack;
}

其他代码

上述全部代码位于i2c.c 源文件中,而下列代码则全部位于i2c.h 头文件。

#ifndef __I2C_H
#define __I2C_H
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private defines -----------------------------------------------------------*/
#define I2C_PORT                GPIOB
#define I2C_SDA_PIN             GPIO_PIN_5
#define I2C_SCL_PIN             GPIO_PIN_4

#define I2C_ACK                 0	//用于接收:SDA=L=从机有回复  用于发送:回复从机ACK
#define I2C_NACK                1	//用于接收:SDA=H=从机未回复  用于发送:回复从机ACK
#define I2C_JUMP_REPLY          2	//调试用
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
void I2C_GPIO_Init(void);
void I2C_SDA_SET_OUTPUT(void);
void I2C_SCL_SET_OUTPUT(void);
void I2C_SDA_SET_INPUT(void);
void I2C_SCL_SET_INPUT(void);

void I2C_SDA_OUT(unsigned char value);
void I2C_SCL_OUT(unsigned char value);
unsigned char I2C_SDA_IN(void);
unsigned char I2C_SCL_IN(void);

void I2C_START(void);
void I2C_STOP(void);

void I2C_SEND_ACK(void);
void I2C_SEND_NACK(void);

unsigned char I2C_READ_BYTE(unsigned char ACK_CHOICE);
unsigned char I2C_SEND_BYTE(unsigned char send_data);


#endif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章