На главную, Исходники, Примеры кода с использованием STM8L


Работа с шиной I2C на STM8L

В этой заметке будет рассматриваться работа контроллера STM8L15x с шиной I2C в режиме мастер. Для этих микроконтроллеров существует библиотека STM8L15x/16x/05x/AL3Lx/AL31x standard peripheral library. Однако, в силу своей универсальности, код работы с шиной I2C в этой библиотеке занимает относительно много места и имеет низкую производительность.

Поэтому, на основе AN3281 Application note. STM8 8-bit MCUs I2C optimized examples был написан код, фрагменты которого приведены ниже. Код рассчитан на работу в режиме Master с одним Slave устройством. При несложной доработке, он подойдет и для работы с несколькими slave'ами.

Код писался под микроконтроллер STM8L151C6/STM8L151C8 но будет вполне работоспособен и на других чипах семейства STM8L.

Для начала, макросы зависящие от конкретного контроллера и/или включения на плате:

// Выводы используемые под I2C
#define TS_I2C_GPIO_PORT       GPIOC
#define TS_I2C_SDA_PIN         GPIO_Pin_0
#define TS_I2C_SCL_PIN         GPIO_Pin_1
// Идентификатор для используемого интерфейса I2C
// Чаще всего, у контроллеров STM8L один интерфейс I2C, но я обычно в коде не использую прямое имя интерфейса. 
#define TS_I2C                 I2C1
// Peripheral Clock Enable for I2C1
#define TS_I2C_CLK             CLK_Peripheral_I2C1

// Скорость в Гц
#define TS_I2C_SPEED           50000
// Slave адрес устройства с которым будем работать.
#define TS_I2C_SLAVE_ADDR      (0xDA)

#define ON_I2C_GPIO_PORT    GPIOE
// Этот вывод управляет ключом для питания I2C периферии. Актуально для батарейного питания. 
#define ON_I2C_GPIO_PIN     GPIO_Pin_2
#define I2C_PowerOn()       GPIO_ResetBits(ON_I2C_GPIO_PORT, ON_I2C_GPIO_PIN);
#define I2C_PowerOff()      GPIO_SetBits(ON_I2C_GPIO_PORT, ON_I2C_GPIO_PIN)
//! Get I2C Power State: true - Power on, false - Power off
#define I2C_PowerState()    ((GPIO_ReadInputData(ON_I2C_GPIO_PORT) & ON_I2C_GPIO_PIN) ? FALSE : TRUE )

Определим ряд макросов для повышения читабельности кода:

#include "stm8l15x.h"

  /* Disable the STOP condition generation */
#define TS_I2C_STOP_Disable()  TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_STOP)
  /* Generate a STOP condition */
#define TS_I2C_STOP_Enable()   TS_I2C->CR2 |= I2C_CR2_STOP

    /* Generate a START condition */
#define TS_I2C_START_Enable()   TS_I2C->CR2 |= I2C_CR2_START;
    /* Disable the START condition generation */
#define TS_I2C_START_Disable()  TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_START);

/* Enable I2C peripheral */
#define  TS_I2C_Enable()   TS_I2C->CR1 |= I2C_CR1_PE
/* Disable I2C peripheral */
#define  TS_I2C_Disable()  TS_I2C->CR1 &= (uint8_t)(~I2C_CR1_PE)

#define REPEATE_CNT 10

// Число повторных попыток чтения датчика
static uint8_t Rep = REPEATE_CNT;

Функции инициаизации и сброса интерфейса:


void TS_I2CInfReset(void)
{
  //I2C_SoftwareResetCmd(TS_I2C, ENABLE);
      /* Peripheral under reset */
  TS_I2C->CR2 |= I2C_CR2_SWRST;

  //I2C_SoftwareResetCmd(TS_I2C, DISABLE);
  /* Peripheral not under reset */
  TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_SWRST);
}

void TS_I2C_Init(uint32_t OutputClockFrequency )
{
    uint32_t result = 0x0004;
    uint8_t input_clock = 0;

    /* Get system clock frequency */
    input_clock = (uint8_t) (CLK_GetClockFreq() / 1000000);

    /*------------------------- I2C FREQ Configuration ------------------------*/
    /* Clear frequency bits */
    TS_I2C->FREQR &= (uint8_t)(~I2C_FREQR_FREQ);
    /* Write new value */
    TS_I2C->FREQR |= input_clock;

    /*--------------------------- I2C CCR Configuration ------------------------*/
    /* Disable I2C to configure TRISER */
    TS_I2C->CR1 &= (uint8_t)(~I2C_CR1_PE);

    /* Clear CCRH & CCRL */
    TS_I2C->CCRH &= (uint8_t)(~(I2C_CCRH_FS | I2C_CCRH_DUTY | I2C_CCRH_CCR));
    TS_I2C->CCRL &= (uint8_t)(~I2C_CCRL_CCR);

    /* Calculate standard mode speed */
    result = (uint16_t)((input_clock * 1000000) / (OutputClockFrequency << (uint8_t)1));

    /* Verify and correct CCR value if below minimum value */
    if (result < (uint16_t)0x0004)
    {
      /* Set the minimum allowed value */
      result = (uint16_t)0x0004;
    }

    /* Set Maximum Rise Time: 1000ns max in Standard Mode
    = [1000ns/(1/input_clock.10e6)]+1
    = input_clock+1 */
    TS_I2C->TRISER = (uint8_t)((uint8_t)input_clock + (uint8_t)1);


    /* Write CCR with new calculated value */
    TS_I2C->CCRL = (uint8_t)result;
    TS_I2C->CCRH = (uint8_t)((uint8_t)((uint8_t)((uint8_t)result >> 8) & I2C_CCRH_CCR));

    /* Enable I2C and  Configure its mode*/
    TS_I2C->CR1 |= (uint8_t)(I2C_CR1_PE );

    /* Configure I2C acknowledgement */
    TS_I2C->CR2 |= (uint8_t)I2C_CR2_ACK;

    /*--------------------------- I2C OAR Configuration ------------------------*/
    TS_I2C->OARL = (uint8_t)(0);
    TS_I2C->OARH = (uint8_t)((uint8_t)(0 | I2C_OARH_ADDCONF ) | \
                           (uint8_t)((uint16_t)( (uint16_t)0 &  (uint16_t)0x0300) >> 7));

}

/**
  * @brief  Configures I2C peripheral for Termo Sensor . 
  * @param  None
  * @retval None
  */
void TS_I2C_Config(void)
{
    CLK_PeripheralClockConfig(TS_I2C_CLK, ENABLE);
    TS_I2C_Disable();

    if (Rep != REPEATE_CNT) {
        TS_I2CInfReset();
    }
    TS_I2C_Init( TS_I2C_SPEED );
    TS_I2C_Enable();
}

void TS_I2C_DeInit(I2C_TypeDef* I2Cx)
{
  TS_I2C->CR1 = I2C_CR1_RESET_VALUE;
  TS_I2C->CR2 = I2C_CR2_RESET_VALUE;
  TS_I2C->FREQR = I2C_FREQR_RESET_VALUE;
  TS_I2C->OARL = I2C_OARL_RESET_VALUE;
  TS_I2C->OARH = I2C_OARH_RESET_VALUE;
  TS_I2C->OAR2 = I2C_OAR2_RESET_VALUE;
  TS_I2C->ITR = I2C_ITR_RESET_VALUE;
  TS_I2C->CCRL = I2C_CCRL_RESET_VALUE;
  TS_I2C->CCRH = I2C_CCRH_RESET_VALUE;
  TS_I2C->TRISER = I2C_TRISER_RESET_VALUE;
}

Для экономии энергии (при батарейном питании) может быть актуально отключать I2C когда в ней нет необходимости:

/**
Отключение I2C перед переходом в режим микропотребления
*/
void TS_I2C_Stop(void)
{
    TS_I2C_DeInit(TS_I2C);
    CLK_PeripheralClockConfig(TS_I2C_CLK, DISABLE);
    I2C_PowerOff();
//    GPIO_Init(TS_I2C_GPIO_PORT, TS_I2C_SCL_PIN | TS_I2C_SDA_PIN, GPIO_Mode_Out_OD_Low_Slow);
}

/**
Включение I2C при выходе из режима микропотребления
*/
void TS_I2C_Start(void)
{
    GPIO_Init(TS_I2C_GPIO_PORT, TS_I2C_SCL_PIN | TS_I2C_SDA_PIN, GPIO_Mode_In_FL_No_IT);
    I2C_PowerOn();
    TS_I2C_Config();
}

Чтение и запись в периферийное I2C устройство. Тут надо оговорить один момент. Функции чтения и записи, в качестве последнего аргумента, принимают указатель на переменную типа uint16_t. Если значение этой переменной достигнет 0, то выполнение операции будет прервано по таймауту.
В идеале, создается volatile переменная, например Delay1ms. Инициализируется таймер на прерывания, например, 1мс. В обработчике прерывания переменная Delay1ms уменьшается, пока не достигнет 0. Указатель на переменную Delay1ms и передается в функции I2C.


#define Wait_While_Flag_Set(SReg,Flag,break_cond)     while ( (TS_I2C->SReg & (Flag)) )    \
                                                if (break_cond) {            \
                                                               return FALSE;    \
                                                }

#define Wait_Flag(SReg,Flag,break_cond)     while ( !(TS_I2C->SReg & (Flag)) )  \
                                                if (break_cond) {               \
                                                        TS_I2C_STOP_Enable();   \
                                                        return FALSE;           \
                                                }

  /* Write in the DR register the data to be sent */
#define TS_I2C_SendData(Data)  TS_I2C->DR = Data

#define TS_I2C_ReceiveData()    ((uint8_t)TS_I2C->DR)

/**
 * Запись в периферийное I2C устройство 
 * 
 * @param Offset - адрес внутреннего регистра
 * @param dat - указатель на массив данных
 * @param len - длина массива данных для записи
 * @param timeout - таймаут операции в мс.
 * 
 * @return bool TRUE - успешно, FALSE - ошибка или таймаут
 */
bool TS_I2C_Write(uint8_t Offset, uint8_t *dat, uint8_t len, uint16_t volatile *timeout)
{
  uint8_t k;
  /*!< While the bus is busy */
  Wait_While_Flag_Set(SR3,I2C_SR3_BUSY,(*timeout == 0));
  /*
  while ( (TS_I2C->SR3 & I2C_FLAG_BUSY) )  ;
  if (*timeout == 0) {
          return FALSE;
  }
  */
  //I2C_GenerateSTOP(TS_I2C, DISABLE);
  TS_I2C_STOP_Disable();

  // Clear   Acknowledge failure  flag
  TS_I2C->SR2 &= (uint8_t)(~I2C_SR2_AF);

  /*!< Send START condition */
  TS_I2C_START_Enable();

  /* Poll SB bit */
  Wait_Flag(SR1,I2C_SR1_SB,*timeout == 0);

  /* Send TS_I2C slave address for write */
  //I2C_Send7bitAddress(TS_I2C, TS_I2C_SLAVE_ADDR, I2C_Direction_Transmitter);
  /* Send the address with direction bit for write */
  TS_I2C->DR = TS_I2C_SLAVE_ADDR & 0xFE;

  Wait_Flag(SR1,I2C_SR1_ADDR,(*timeout == 0) || (TS_I2C->SR2 & I2C_SR2_AF));
  // Clear ADDR flag
  (void)TS_I2C->SR3; // This bit is cleared by software reading SR1 register followed reading SR3, or by hardware when PE=0.

  Wait_Flag(SR1,I2C_SR1_TXE,*timeout == 0);

  TS_I2C_SendData( Offset);
  k=0;
  while (1) {
      if (TS_I2C->SR2 & I2C_SR2_AF) {
          TS_I2C->SR2 &= (uint8_t)(~I2C_SR2_AF);
          /* Send I2C STOP Condition */
          TS_I2C_STOP_Enable();
          return FALSE;
      }
      if (len == 0) {
          Wait_Flag(SR1,I2C_SR1_TXE | I2C_SR1_BTF,*timeout == 0);
          /* Send I2C STOP Condition */
          TS_I2C_STOP_Enable();
          break;
      } else {
          Wait_Flag(SR1,I2C_SR1_TXE,*timeout == 0);
          TS_I2C_SendData( dat[k++] );
          len--;
      }
  } 
  return TRUE;
}

/**
 * Чтение периферийного I2C устройства 
 * 
 * @param Offset  - адрес внутреннего регистра     
 * @param dat    - указатель на массив данных        
 * @param len    - длина массива данных для записи   
 * @param timeout - таймаут операции в мс.         
 * 
 * @return bool 
 */
bool TS_I2C_Read(uint8_t Offset, uint8_t *dat, uint8_t len, uint16_t volatile *timeout)
{
    uint8_t k;

    if (TS_I2C_Write(Offset,0,0,timeout) ) {
        /**************************************************************************/
        /*!< While the bus is busy */
        Wait_While_Flag_Set(SR3,I2C_SR3_BUSY,*timeout == 0);
        TS_I2C_STOP_Disable();
        // Clear   Acknowledge failure  flag
        TS_I2C->SR2 &= (uint8_t)(~I2C_SR2_AF);

        /***** Wait DataReady *******/
        /*!< Send START condition */
        TS_I2C_START_Enable();

        /* Poll SB bit */
        Wait_Flag(SR1,I2C_SR1_SB,*timeout == 0);

        /* Send TS_I2C slave address for write */
        //I2C_Send7bitAddress(TS_I2C, TS_I2C_SLAVE_ADDR, I2C_Direction_Transmitter);
        /* Send the address with direction bit for read */
        TS_I2C->DR = TS_I2C_SLAVE_ADDR | 0x01;

        Wait_Flag(SR1,I2C_SR1_ADDR,(*timeout == 0) || (TS_I2C->SR2 & I2C_SR2_AF));
        // Clear ADDR flag
        (void)TS_I2C->SR3;
        // Set ACK
        TS_I2C->CR2 |= (uint8_t)(I2C_CR2_ACK);

        k=0;
        if (len>2) {
            do {
                if (len == 3) {
                    //Poll BTF
                    Wait_Flag(SR1,I2C_SR1_BTF,*timeout == 0);
                    // Clear ACK
                    TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_ACK);
                    disableInterrupts();
                    dat[k++] = TS_I2C_ReceiveData();
                    len--;
                    TS_I2C_STOP_Enable();
                    dat[k++] = TS_I2C_ReceiveData();
                    len--;
                    enableInterrupts();
                    //Poll RXNE
                    Wait_Flag(SR1,I2C_SR1_RXNE,*timeout == 0);
                    dat[k++] = TS_I2C_ReceiveData();
                    len--;
                } else {
                    Wait_Flag(SR1,I2C_SR1_BTF,*timeout == 0);
                    /*!< Read a byte from the Sensor */
                    dat[k++] = TS_I2C_ReceiveData();
                    len--;
                }
            } while (len);
        } else {
            if (len == 2) {
                // Set POS
                TS_I2C->CR2 |= I2C_CR2_POS;
                disableInterrupts();
                // Clear ACK
                TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_ACK);
                enableInterrupts();
                //Poll BTF
                Wait_Flag(SR1,I2C_SR1_BTF,*timeout == 0);
                disableInterrupts();
                TS_I2C_STOP_Enable();
                dat[k++] = TS_I2C_ReceiveData();
                enableInterrupts();
                dat[k++] = TS_I2C_ReceiveData();
            } else {
                // Clear ACK
                TS_I2C->CR2 &= (uint8_t)(~I2C_CR2_ACK);
                disableInterrupts();
                // Clear ADDR flag
                (void)TS_I2C->SR3;
                TS_I2C_STOP_Enable();
                enableInterrupts();
                Wait_Flag(SR1,I2C_SR1_RXNE,*timeout == 0);
                dat[k++] = TS_I2C_ReceiveData();
            }
        }
        return TRUE;
    } else {
        return FALSE;
    }
}

Пример применения будет выглядеть примерно так (на примере чтения термодатчика WF5803F):

typedef enum {
    ts_Start,
    ts_InProgress,
    ts_Ready
} TS_State_t;

static TS_State_t  TS_State = (TS_State_t)0;

uint8_t data[10];

bool TS_I2C_StartConvertion(uint8_t timeout)
{
    bool res;
    // Установка счетчика таймаута (он декрементируется в прерывании таймера каждую миллисекунду)
    disableInterrupts();
    i2cTimeoutDelay1ms = timeout;
    enableInterrupts();
    /* Send Start Convertion 
    | 0x30 | CMD | RW | Sleep_time<3:0> | Sco | Measurement<2:0> | */
    data[0] = 0x0A;

    res = TS_I2C_Write(0x30,data,1,&i2cTimeoutDelay1ms);
    
    /*!< While the bus is busy */
    // Это ожидание не обязательно, если контроллеру "есть чем заняться". 
    // Без этого, уходит "в сон" раньше чем заканчивается передача последнего байта
    Wait_While_Flag_Set(SR3,I2C_SR3_BUSY,(i2cTimeoutDelay1ms == 0));
    return res;
}

int16_t TS_I2C_ReadTemperature(uint8_t* Result, uint8_t timeout)
{
    uint16_t T;
    disableInterrupts();
    i2cTimeoutDelay1ms = timeout;
    enableInterrupts();

    do {
        *Result = TS_I2C_Read(0x02,data,1,&i2cTimeoutDelay1ms);
        if (*Result && 
            (data[0] & 0x01) ) {
            //Data Ready

        }
    } while (*Result && i2cTimeoutDelay1ms && !(data[0] & 0x01));

    if (*Result) {
        *Result = TS_I2C_Read(0x06,data,5,&i2cTimeoutDelay1ms);
    }

    if (*Result) {
        T = calculatePress(data);
    }
  return T;
}

/**
  * @brief Чтение температуры. Вызывается из главного цикла несколько раз с интервалом 20мс (пока Result не примет истинное значение)
  * @param Result указателдь на код результата операции: TRUE - 
  *               успешно, FALSE - ошибка датчика
  *  
  * @return int16_t Температура (q12,4). 
  * 
*/
int16_t TS_I2C_GetTemperature(uint8_t * Result)
{
    uint16_t T = 0;
    bool res;
    *Result = FALSE;

    switch (TS_State) {
    case ts_Start : 
        TS_I2C_Start();
        TS_State = ts_InProgress;
        break;
    case ts_InProgress :
        TIM3_On();
        res = TS_I2C_StartConvertion(3);
        TIM3_Off();
        if (res) {
            TS_State = ts_Ready;
        } else {
            if ( --Rep ) {
                TS_I2C_Stop();
                TS_State = ts_Start;
            } else {
                flags.flNeedTermo = 0;
                Rep = REPEATE_CNT;
                TS_I2C_Stop();
                SET_ALARM(TempSemsorFail);
                TS_State = ts_Start;
            }
        }
        break;
    case ts_Ready : 
        TIM3_On();
        T = TS_I2C_ReadTemperature(Result,4);
        TIM3_Off();
        if (*Result) {
            Rep = REPEATE_CNT;
            TS_I2C_Stop();
            CLEAR_ALARM(TempSemsorFail);
            TS_State = ts_Start;
        } else {
            if ( --Rep ) {
                TS_I2C_Stop();
                TS_State = ts_Start;
            } else {
                flags.flNeedTermo = 0;
                Rep = REPEATE_CNT;
                TS_I2C_Stop();
                SET_ALARM(TempSemsorFail);
                TS_State = ts_Start;
            }
        }
        break;
    default: 
        TS_State = ts_Start;
    }
    return T;
}


На главную, Исходники