Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32L проблемы с I2C
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
p_kav
Здравствуйте.
Использую плату STM32L Discovery с STM32L152RCT6, среда программирования Keil uVision 5, набор библиотек StdPeriph последней версии.
Успешно запустил GPIO и несколько USART (то есть контроллер исправен), а вот с I2C происходит нечто странное, уже 2 дня убил и решения не нашел.

Использую I2C1 ножки PB8 и PB9, на шине акселерометр MMA7660 и резисторы подтяжки 10К, контроллер тактуется от внутреннего MSI на 2 МГЦ.

Мой недописанный код:

Код
#include "i2c_routines.h"
#include "STM32L1xx.h"

void i2c1_init(void)
{
    GPIO_InitTypeDef PORT;
    I2C_InitTypeDef I2C_INIT;
    
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOBEN, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    I2C_INIT.I2C_Ack = I2C_Ack_Disable;
    I2C_INIT.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_INIT.I2C_ClockSpeed = 10000;
    I2C_INIT.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_INIT.I2C_Mode = I2C_Mode_I2C;
    I2C_INIT.I2C_OwnAddress1 = 0x11;
    
    I2C_DeInit(I2C1);
    
    PORT.GPIO_Mode = GPIO_Mode_AF;
    PORT.GPIO_OType = GPIO_OType_OD;
    PORT.GPIO_PuPd = GPIO_PuPd_UP;
    PORT.GPIO_Speed = GPIO_Speed_40MHz;
    PORT.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
    
    GPIO_Init(GPIOB, &PORT);
    
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_I2C1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1);

    I2C_Init(I2C1, &I2C_INIT);
    I2C_Cmd(I2C1, ENABLE);
}

void I2C1_single_write(uint8_t HW_address, uint8_t addr, uint8_t data)
{
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, HW_address, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

}


Проблемы начинаются уже на этом этапе. Инициализация проходит успешно, но после вызова I2C_GenerateSTART(I2C1, ENABLE); контроллер кладет линию SCL (PB8) на землю и не отпускает её. При этом статусный бит I2C1.SR2 BUSY установлен в 1, т.е. шина занята. То же самое происходит если после инициализации I2C при помощи отладчика записать 1 в бит START регистра I2C1.CR1, а если записать 1 в бит STOP, то контроллер отпускает линию SCL. Пробовал убрать с шины акселерометр и снять установленный на плату LCD - безрезультатно.

С помощью Google находил похожие проблемы, однако там всё решалось исправлением опечаток или же ошибок подключения.
Прошу помощи в решении данной проблемы.
smalcom
не используйте отладчик при работе с системными регистрами кроме случаев когда вы знаете чем это грозит.

там у них... ну, мне не нравится как стм сделал I2C. Вот смотрите примерчик, брал у кого-то(то ли stm32lib, толи opencm3), что-то исправил, что-то добавил. из-за лени там всё ещё есть одна ошибка:
при ошибке обмена не сбрасывается периферия и следующий START не начнётся.

Код
namespace Driver
{

class CI2C : public Driver::II2CDevice
{
private:

    I2C_TypeDef* const mI2C;
    uint16_t mArbitrationTimeout;

public:

    CI2C(I2C_TypeDef* const pI2C)
        : mI2C(pI2C), mArbitrationTimeout(0)
    {
    }

    //////////////////////////////////////////////
    ///////////// General control set ////////////
    //////////////////////////////////////////////

    virtual void SetArbitrationTimeout(const uint16_t pTimeoutMS) final { mArbitrationTimeout = pTimeoutMS; }

    //////////////////////////////////////////////
    ////////////// Data exchange set /////////////
    //////////////////////////////////////////////

    virtual Service::ERetVal Write(const uint8_t pAddress, const uint8_t* pData, const uint8_t pDataSize) final;
    virtual Service::ERetVal Read(const uint8_t pAddress, uint8_t* pData, const uint8_t pDataSize) final;
};

}


Код
namespace Driver
{

Service::ERetVal CI2C::Write(const uint8_t pAddress, const uint8_t* pData, const uint8_t pDataSize)
{
using namespace Service;

uint16_t tto;

    if(pDataSize == 0) return ERetVal::InvalidArgument;

    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BUSY) == SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // Start the config sequence
    I2C_GenerateSTART(mI2C, ENABLE);
    // Test on EV5 and clear it
    tto = mArbitrationTimeout;
    while(I2C_CheckEvent(mI2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // Transmit the slave address and enable writing operation
    I2C_Send7bitAddress(mI2C, pAddress, I2C_Direction_Transmitter);
    // Test on EV6 and clear it
    tto = mArbitrationTimeout;
    while(I2C_CheckEvent(mI2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // EV6
    uint8_t to_send = pDataSize;
    uint8_t i = 0;

    // Write first byte EV8_1
    I2C_SendData(mI2C, pData[i++]);
    while(--to_send)
    {
        // wait on BTF
        tto = mArbitrationTimeout;
        while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BTF) != SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }

        I2C_SendData(mI2C, pData[i++]);
    }
    // wait on BTF
    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BTF) != SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // генерируем STOP
    I2C_GenerateSTOP(mI2C, ENABLE);
    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_STOPF) == SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }

    return Service::ERetVal::Ok;
}

Service::ERetVal CI2C::Read(const uint8_t pAddress, uint8_t* pData, const uint8_t pDataSize)
{
using namespace Service;

uint16_t tto;

    if(pDataSize < 1) return Service::ERetVal::InvalidArgument;

    // Wait for idle I2C interface
    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BUSY) == SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // Enable Acknowledgement, clear POS flag
    I2C_AcknowledgeConfig(mI2C, ENABLE);
    I2C_NACKPositionConfig(mI2C, I2C_NACKPosition_Current);
    // Intiate Start Sequence (wait for EV5
    I2C_GenerateSTART(mI2C, ENABLE);
    tto = mArbitrationTimeout;
    while(I2C_CheckEvent(mI2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
    // Send Address
    I2C_Send7bitAddress(mI2C, pAddress, I2C_Direction_Receiver);
    // EV6
    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_ADDR) == RESET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }

    uint8_t i = 0;

    if(pDataSize == 1)
    {
        // Clear Ack bit
        I2C_AcknowledgeConfig(mI2C, DISABLE);
        // EV6_1 -- must be atomic -- Clear ADDR, generate STOP
        __disable_irq();
        (void)mI2C->SR2;
        I2C_GenerateSTOP(mI2C, ENABLE);
        __enable_irq();
        // Receive data EV7
        tto = mArbitrationTimeout;
        while(I2C_GetFlagStatus(mI2C, I2C_FLAG_RXNE) == RESET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }

        pData[i++] = I2C_ReceiveData(mI2C);
    }
    else if(pDataSize == 2)
    {
        // Set POS flag
        I2C_NACKPositionConfig(mI2C, I2C_NACKPosition_Next);
        // EV6_1 -- must be atomic and in this order
        __disable_irq();
        (void)mI2C->SR2; // Clear ADDR flag
        I2C_AcknowledgeConfig(mI2C, DISABLE); // Clear Ack bit
        __enable_irq();
        // EV7_3 -- Wait for BTF, program stop, read data twice
        tto = mArbitrationTimeout;
        while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BTF) != SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
        __disable_irq();
        I2C_GenerateSTOP(mI2C, ENABLE);
        pData[i++] = mI2C->DR;
        __enable_irq();
        pData[i++] = mI2C->DR;
    }
    else
    {
        uint8_t to_read = pDataSize;
        volatile uint32_t tsr;

        tsr = mI2C->SR2; // Clear ADDR flag
        while(to_read-- != 3)
        {
            // EV7 -- cannot guarantee 1 transfer completion time, wait for BTF instead of RXNE
            tto = mArbitrationTimeout;
            while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BTF) != SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
            pData[i++] = I2C_ReceiveData(mI2C);
        }

        tto = mArbitrationTimeout;
        while(I2C_GetFlagStatus(mI2C, I2C_FLAG_BTF) != SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }
        // EV7_2 -- Figure 1 has an error, doesn't read N-2 !
        I2C_AcknowledgeConfig(mI2C, DISABLE); // clear ack bit
        __disable_irq();
        pData[i++] = I2C_ReceiveData(mI2C); // receive byte N-2
        I2C_GenerateSTOP(mI2C,ENABLE); // program stop
        __enable_irq();
        pData[i++] = I2C_ReceiveData(mI2C); // receive byte N-1
        pData[i++] = I2C_ReceiveData(mI2C); // receive byte N
        to_read = 0;
    }

    // Wait for stop
    tto = mArbitrationTimeout;
    while(I2C_GetFlagStatus(mI2C, I2C_FLAG_STOPF) == SET) { Delay_MS::U8(1); if(--tto == 0) return ERetVal::DeviceBusy; }

    return Service::ERetVal::Ok;
}

}

p_kav
Так у меня, по сути, то же самое, и на строчке
Код
I2C_GenerateSTART(mI2C, ENABLE);


контроллер кладет SCL (PB8) в 0 и не поднимает, хотя по стандарту при передаче старт-бита SDA (PB9) должен упасть в 0 и через некоторое время подняться подтяжкой.

Отладчиком потыкал в регистры периферии чтобы убедиться, что дело не в программе и подозрения подтвердились - при записи 1 в I2C1->CR1 START бит та же ситуация.
smalcom
Цитата
I2C_INIT.I2C_ClockSpeed = 10000;

может 100000?
p_kav
Это да, пытался отлаживать, пробовал разные варианты.
Вот прямо только что проблему выявил и решил, 3 дня убил на неё. Оказалось, что ошибка в StdPeriph и адрес Slave надо указывать сдвинутым на 1 бит вправо и тогда все заработает.
Акселерометр стал отвечать, теперь нужно перевести всё это на работу по прерываниям.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.