Le record de pièges de RT-Thread dans le matériel STM32 I2C


Articles de référence :
1. "Graffer" intelligemment le matériel I2C au cadre de pilote I2C analogique natif de RTT
2. Notes d'implémentation du pilote matériel I2C basées sur la plate-forme STM32F4
3. "Analyse du cadre de pilote rt-thread" - pilote i2c

0.Préface

  J'ai récemment prévu d'utiliser RT-Thread pour faire une petite démo, qui nécessite une communication I2C pour piloter un écran OLED. Cependant, après avoir cherché, je n'ai trouvé aucune méthode de support ni aucun cas d'utilisation pour le matériel I2C en RTT. Il semble que tout le monde le comprend tacitement.Utilisez ce logiciel I2C facile à utiliser mais pas facile à utiliser. Je ne peux toujours pas m'empêcher de me plaindre ici. Même le matériel SPI est déjà pris en charge, et il prend même en charge le mode SPI DMA. Le matériel I2C n'a pas été adapté depuis tant d'années. J'espère également que quelqu'un pourra contribuer et créer un pilote matériel I2C tiers que les joueurs bricoleurs pourront utiliser.

1. La différence entre le logiciel et le matériel I2C

  Je ne présenterai pas grand-chose sur les principes du protocole de communication I2C. Il s'agit d'un protocole de communication très courant. Vous pouvez en rechercher de nombreux sur le forum CSDN, et le RT Thread Document Center propose également une introduction plus détaillée .
  Le logiciel I2C utilise le retournement de niveau GPIO pour simuler les signaux I2C. Son avantage est qu'il est facile à transplanter. Il peut être appliqué à 51 microcontrôleurs et plates-formes Linux tant qu'il y a GPIO (bien sûr, personne ne l'utilisera sous Linux). L'inconvénient est que la vitesse est très faible et qu'il y a des retards et des problèmes inévitables lorsque le logiciel opère le changement de niveau GPIO. Afin d'éliminer l'impact de ce phénomène, un intervalle de temps légèrement plus grand est nécessaire entre les signaux I2C simulés. La fréquence du signal du logiciel I2C est généralement de 30 KHz ~ 50 KHz. Même si l'optimisation est assez bonne, elle se situe presque à ce niveau. Utilisé pour faire fonctionner un écran OLED 128x64, la fréquence d'images est essentiellement d'environ 2 images.
  Le matériel I2C communique en exploitant le propre registre de la puce. L'inconvénient est que le pilote n'est pas universel entre les différentes puces. L'avantage est qu'il est plus rapide et peut s'adapter au mode DMA pour réduire la charge du processeur. Le STM32RCT6 utilisé par l'auteur a une fréquence de signal en mode standard matériel I2C de 100 KHz et un mode rapide de 400 KHz. Certaines puces avec de meilleures performances ont également un mode vitesse extrême de 1 MHz. La fréquence d'images du fonctionnement OLED 128x64 à 400 kHz est d'environ 25 images, ce qui peut être considéré comme une énorme amélioration.

2. Pilote I2C dans RT Thread

Concernant l'implémentation du framework de pilotes I2C dans RT Thread, vous pouvez vous référer au troisième article de référence   mentionné ci-dessus . Personnellement, je pense qu'il est très détaillé et facile à comprendre. RT Thread est un système d'exploitation en temps réel de type Linux, donc l'implémentation du framework I2C est similaire à celle de Linux : le pilote I2C fournit certaines fonctions opérationnelles liées au fonctionnement et est enregistré dans le noyau, et le périphérique I2C peut être Monté via la fonction de sonde. Sur le bus, la communication I2C est effectuée via la fonction d'opération ops.
Insérer la description de l'image ici
Et dans cet article, l'auteur a ignoré le bit_ops d'origine, a repensé une implémentation matérielle I2C, a monté le pilote directement dans le noyau du noyau et a également implémenté le pilote matériel I2C en tant que périphérique maître. Cependant, l'auteur estime que cette méthode n'est pas très compatible avec les structures générales, j'ai donc trouvé d'autres méthodes.
Insérer la description de l'image ici

3. Essayez d'adapter le matériel I2C

Référez-vous aux articles 1 et 2 pour « greffer » une implémentation matérielle du pilote I2C en modifiant la fonction d'implémentation du bus I2C. Commençons par mettre le code ici. Tout d'abord, créez drv_hard_i2c.c et drv_hard_i2c.h dans le même répertoire que drv_soft_i2c.c et drv_soft_i2c.h d'origine :
drv_hard_i2c.h :

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-08     balanceTWK   first version
 */

#ifndef __DRV_I2C__
#define __DRV_I2C__

#include <rtthread.h>
#include <rthw.h>
#include <rtdevice.h>

#ifdef BSP_USING_HARD_I2C

/* stm32 config class */
typedef void (*pI2cConfig)(void);
struct stm32_hard_i2c_config
{
    
    
    rt_uint8_t scl;                     /* scl pin */
    rt_uint8_t sda;                     /* sda pin */
    const pI2cConfig pFunc;             /* i2c init function */
    const char* pName;                  /* i2c bus name */
    I2C_HandleTypeDef* pHi2c;           /* i2c handle */
    struct rt_i2c_bus_device i2c_bus;   /* i2c bus device */
};
/* stm32 i2c dirver class */
struct stm32_i2c
{
    
    
    struct rt_i2c_bit_ops ops;
    struct rt_i2c_bus_device i2c2_bus;
};

#define HARD_I2C_CONFIG(x)  \
{
      
      
    .scl        = BSP_I2C##x##_SCL_PIN,    \
    .sda        = BSP_I2C##x##_SDA_PIN,    \
    .pFunc      = MX_I2C##x##_Init,         \
    .pHi2c      = &hi2c##x,                 \
    .pName      = "i2c"#x,                  \
    .i2c_bus    = {
    
    
            .ops = &i2c_bus_ops,
    },
}
    
int rt_hw_i2c_init(void);

#endif

#endif /* RT_USING_I2C */

Parmi eux, stm32_hard_i2c_config peut être compris comme un objet d'instance i2c, et ses attributs incluent les broches scl et sda, le nom du bus et la fonction d'initialisation, etc. (Remarque : la vitesse du bus, le sémaphore et le verrouillage mutex dans l'article de référence 2 ne sont pas requis, car la fonction d'initialisation générée à l'aide de CubeMx a déjà la vitesse du bus et la fonction d'opération I2C dans la bibliothèque HAL a déjà le verrouillage du bus à l'intérieur) stm32_i2c
Il encapsule les fonctions de fonctionnement du périphérique et les bus pour l'interface avec le noyau.
La macro de fonction HARD_I2C_CONFIG(x) est utilisée pour créer ultérieurement un objet périphérique I2C.

drv_hard_i2c.c:

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-08     balanceTWK   first version
 */

#include <board.h>
#include "drv_hard_i2c.h"
#include "drv_config.h"
#include<rtthread.h>
#include<rtdevice.h>

#ifdef BSP_USING_HARD_I2C

//#define DRV_DEBUG
#define LOG_TAG              "drv.i2c"
#include <drv_log.h>

static const struct stm32_hard_i2c_config hard_i2c_config[] =
{
    
    
#ifdef BSP_USING_HARD_I2C1
    HARD_I2C_CONFIG(1),
#endif
#ifdef BSP_USING_HARD_I2C2
    HARD_I2C_CONFIG(2),
#endif
#ifdef BSP_USING_HARD_I2C3
    HARD_I2C_CONFIG(3),
#endif
#ifdef BSP_USING_HARD_I2C4
    HARD_I2C_CONFIG(4),
#endif
};

static struct stm32_i2c i2c_obj[sizeof(hard_i2c_config) / sizeof(hard_i2c_config[0])];

/**
 * This function initializes the i2c pin.
 *
 * @param Stm32 i2c dirver class.
 */
static void stm32_i2c_gpio_init(struct stm32_i2c *i2c)
{
    
    
    struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)i2c->ops.data;

    rt_pin_mode(cfg->scl, PIN_MODE_OUTPUT_OD);
    rt_pin_mode(cfg->sda, PIN_MODE_OUTPUT_OD);

    rt_pin_write(cfg->scl, PIN_HIGH);
    rt_pin_write(cfg->sda, PIN_HIGH);
}

/**
 * The time delay function.
 *
 * @param microseconds.
 */
static void stm32_udelay(rt_uint32_t us)
{
    
    
    rt_uint32_t ticks;
    rt_uint32_t told, tnow, tcnt = 0;
    rt_uint32_t reload = SysTick->LOAD;

    ticks = us * reload / (1000000 / RT_TICK_PER_SECOND);
    told = SysTick->VAL;
    while (1)
    {
    
    
        tnow = SysTick->VAL;
        if (tnow != told)
        {
    
    
            if (tnow < told)
            {
    
    
                tcnt += told - tnow;
            }
            else
            {
    
    
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks)
            {
    
    
                break;
            }
        }
    }
}

/**
 * if i2c is locked, this function will unlock it
 *
 * @param stm32 config class
 *
 * @return RT_EOK indicates successful unlock.
 */
static rt_err_t stm32_i2c_bus_unlock(const struct stm32_soft_i2c_config *cfg)
{
    
    
    rt_int32_t i = 0;

    if (PIN_LOW == rt_pin_read(cfg->sda))
    {
    
    
        while (i++ < 9)
        {
    
    
            rt_pin_write(cfg->scl, PIN_HIGH);
            stm32_udelay(100);
            rt_pin_write(cfg->scl, PIN_LOW);
            stm32_udelay(100);
        }
    }
    if (PIN_LOW == rt_pin_read(cfg->sda))
    {
    
    
        return -RT_ERROR;
    }

    return RT_EOK;
}

/* I2C initialization function */
int rt_hw_i2c_init(void)
{
    
    
    rt_int8_t ret = RT_ERROR;
    rt_size_t obj_num = NR(hard_i2c_config);
    rt_err_t result;

    for (int i = 0; i < obj_num; i++)
    {
    
    
        //GPIO初始化
        stm32_i2c_gpio_init(&hard_i2c_config[i]);

        //检测SDA是否为低电平,低电平则通过管脚模拟9个CLK解锁
        stm32_i2c_bus_unlock(&hard_i2c_config[i]);

        //调用Hal库MX_I2Cx_Init(),配置硬件I2C
        hard_i2c_config[i].pFunc();

        //向内核注册I2C Bus设备
        if(rt_i2c_bus_device_register(&(hard_i2c_config[i].i2c_bus), hard_i2c_config[i].pName) != RT_EOK)
        {
    
    
            LOG_E("%s bus init failed!\r\n", hard_i2c_config[i].pName);
            ret |= RT_ERROR;
        }
        else
        {
    
    
            ret |= RT_EOK;
            LOG_I("%s bus init success!\r\n", hard_i2c_config[i].pName);
        }
    }
    return ret;
}
//INIT_BOARD_EXPORT(rt_hw_i2c_init);

#endif /* BSP_USING_HARD_I2C */

Dans ce fichier, les objets d'instance I2C sont principalement créés sur la base de commutateurs de définition de macro et initialisés. La fonction principale est rt_hw_i2c_init(). Les fonctions gpio init, delay, etc. requises dans cette fonction conservent les opérations d'initialisation dans le logiciel i2c.

user_i2c.h:

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-08-27     14187       the first version
 */
#ifndef DRIVERS_HARD_I2C_H_
#define DRIVERS_HARD_I2C_H_

//硬件i2c宏开关
//#define     BSP_USING_HARD_I2C
#ifdef      BSP_USING_HARD_I2C
//      #define BSP_USING_HARD_I2C1
//      #define BSP_USING_HARD_I2C2
//      #define BSP_USING_HARD_I2C3
//      #define BSP_USING_HARD_I2C4

        #if defined(BSP_USING_HARD_I2C1 || BSP_USING_HARD_I2C2 || BSP_USING_HARD_I2C3 || BSP_USING_HARD_I2C4)
            //#define BSP_USING_DMA_I2C_TX
            //#define BSP_USING_DMA_I2C_RX
        #endif
#endif

#endif /* DRIVERS_HARD_I2C_H_ */

Afin de ne pas écraser et actualiser ma configuration à chaque fois que j'enregistre les paramètres du thread RT, je définis un fichier d'en-tête supplémentaire pour enregistrer le commutateur macro I2C personnalisé, de sorte que je n'ai besoin que de l'inclure dans board.h après chaque actualisation. déposer.

À ce stade, la création du commutateur macro I2C matériel personnalisé et de l'objet périphérique est terminée. Il ne reste plus qu'à remplacer la fonction d'opération bit_ops dans le noyau.

4. Remplacement de la fonction d'exploitation i2c-bit-ops

Dans le répertoire rt-thread/components/drivers/i2c/ sous le répertoire racine du projet rt thread, il existe un fichier i2c-bit-ops.c, qui stocke les fonctions d'opération ops enregistrées dans le cadre du pilote i2c :

static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,
                              struct rt_i2c_msg         msgs[],
                              rt_uint32_t               num)
{
    
    
    struct rt_i2c_msg *msg;
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    rt_int32_t i, ret;
    rt_uint16_t ignore_nack;

    if (num == 0) return 0;

    for (i = 0; i < num; i++)
    {
    
    
        msg = &msgs[i];
        ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
        if (!(msg->flags & RT_I2C_NO_START))
        {
    
    
            if (i)
            {
    
    
                i2c_restart(ops);
            }
            else
            {
    
    
                LOG_D("send start condition");
                i2c_start(ops);
            }
            ret = i2c_bit_send_address(bus, msg);
            if ((ret != RT_EOK) && !ignore_nack)
            {
    
    
                LOG_D("receive NACK from device addr 0x%02x msg %d",
                        msgs[i].addr, i);
                goto out;
            }
        }
        if (msg->flags & RT_I2C_RD)
        {
    
    
            ret = i2c_recv_bytes(bus, msg);
            if (ret >= 1)
            {
    
    
                LOG_D("read %d byte%s", ret, ret == 1 ? "" : "s");
            }
            if (ret < msg->len)
            {
    
    
                if (ret >= 0)
                    ret = -RT_EIO;
                goto out;
            }
        }
        else
        {
    
    
            ret = i2c_send_bytes(bus, msg);
            if (ret >= 1)
            {
    
    
                LOG_D("write %d byte%s", ret, ret == 1 ? "" : "s");
            }
            if (ret < msg->len)
            {
    
    
                if (ret >= 0)
                    ret = -RT_ERROR;
                goto out;
            }
        }
    }
    ret = i;

out:
    if (!(msg->flags & RT_I2C_NO_STOP))
    {
    
    
        LOG_D("send stop condition");
        i2c_stop(ops);
    }

    return ret;
}
...
static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
    
    
    i2c_bit_xfer,
    RT_NULL,
    RT_NULL
};

Ce code implémente le processus d'envoi du message i2c correspondant à chaque appareil i2c et le modifie en méthode d'envoi matériel i2c :

static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus,
                              struct rt_i2c_msg     msgs[],
                              rt_uint32_t           num)
{
    
    
    rt_uint32_t i;
    struct rt_i2c_msg *msg;
    struct stm32_hard_i2c_config *Pconfig = rt_container_of(bus, struct stm32_hard_i2c_config, i2c_bus);

    fot(i = 0;i < num;i++)
    {
    
    
        msg = &msgs[i];
        if(msg->flags & RT_I2C_RD)
        {
    
    
#if defined(BSP_USING_DMA_I2C_RX)
            HAL_I2C_Master_Receive_DMA(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len);
            rt_hw_us_delay(100);
#else
            HAL_I2C_Master_Receive(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len, 100);
#endif
        }
        else
        {
    
    
#if defined(BSP_USING_DMA_I2C_TX)
            HAL_I2C_Master_Transmit_DMA(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len);
            rt_hw_us_delay(100);
#else
            HAL_I2C_Master_Transmit(Pconfig->pHi2c, (msg->addr)<<1, msg->buf, msg->len, 100);
#endif
        }
    }
    return i;
}

static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
    
    
    i2c_xfer,
    RT_NULL,
    RT_NULL
};

Reportez-vous à la méthode d'envoi du logiciel i2c, créez une nouvelle fonction d'envoi rt_size_t i2c_xfer() et modifiez la méthode d'envoi correspondante dans rt_i2c_bus_device_ops par cette méthode.

À ce stade, le pilote matériel I2C est partiellement terminé. Le périphérique peut être monté sur le bus matériel I2C via les mêmes méthodes de déclaration et de montage que le logiciel i2c.

Attention, s'il vous plaît !

问题1:Dans l'implémentation ci-dessus, les messages I2C peuvent être envoyés via HAL_I2C_Master_Transmit() ou HAL_I2C_Master_Transmit_DMA() selon la définition de la macro, mais aucun jugement n'est porté sur la réussite de la transmission.
问题2:Dans la bibliothèque HAL officielle de ST, il existe trois façons pour I2C d'envoyer des messages : le mode d'interrogation (polling), le mode d'interruption et le mode DMA. HAL_I2C_Master_Transmit() correspond au mode d'interrogation. Bien que ce mode ait une vitesse améliorée par rapport au logiciel I2C, il est L'effet d'amélioration réel n'est en fait pas particulièrement important. Pour le mode interruption, vous devez transplanter et implémenter la fonction de traitement d'interruption correspondante, qui peut être implémentée selon l'article de référence 2. Cependant, l'auteur estime qu'il y a beaucoup de choses auxquelles il faut prêter attention dans cet article, comme le fonctionnement de libérer le sémaphore dans la fonction de traitement des interruptions, ce qui peut entraîner des dangers cachés (le sémaphore peut être supprimé directement). Pour le mode DMA, il est théoriquement également nécessaire de transplanter certaines fonctions de traitement des interruptions, mais l'auteur n'a pas utilisé cette méthode jusqu'à présent, je ne l'ai donc pas étudiée en détail. Donc en théorie, il ne peut rester qu'en mode interrogation.
问题3:Dans drv_hard_i2c.c, INIT_BOARD_EXPORT(rt_hw_i2c_init); cette étape d'enregistrement doit être déterminée en fonction de la situation réelle. Si vous souhaitez utiliser le mode DMA, vous devez enregistrer MX_DMA_Init() avant cette étape d'enregistrement. Cette fonction est générée pour CubeMX. Utilisez pour initialiser la fonction DMA. Il en va de même pour le mode interruption.

6. Résumé

  À l'heure actuelle, il semble qu'il soit encore difficile de transplanter le pilote matériel I2C de ST, l'auteur a donc choisi de changer de plate-forme (j'ai échappé...). J'ai modifié le circuit OLED en mode SPI et changé la plate-forme de la puce. J'ai encore une carte de développement LPC54110 et A CH32. Les packages de support RTT BSP de ces deux cartes semblent avoir des pilotes matériels I2C adaptés. Au revoir ST. J'espère que quelqu'un adaptera le matériel I2C la prochaine fois.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_45682654/article/details/132571753
conseillé
Classement