[[:start|На главную]], [[sources:start|Исходники]], [[sources:stm8l|Примеры кода с использованием STM8L]] ---- ====== Работа с шиной I2C на STM8L ====== {{tag>исходники stm8l STM8L151}} В этой заметке будет рассматриваться работа контроллера STM8L15x с шиной I2C в режиме мастер. Для этих микроконтроллеров существует библиотека [[https://www.st.com/en/embedded-software/stsw-stm8016.html|STM8L15x/16x/05x/AL3Lx/AL31x standard peripheral library]]. Однако, в силу своей универсальности, код работы с шиной I2C в этой библиотеке занимает относительно много места и имеет низкую производительность. Поэтому, на основе [[https://www.st.com/resource/en/application_note/an3281-stm8-8bit-mcus-ic-optimized-examples-stmicroelectronics.pdf|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; } ---- [[:start|На главную]], [[sources:start|Исходники]] ~~DISCUSSION~~