На главную, Исходники, Примеры кода с использованием 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;
}
Обсуждение