[[: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~~