La fréquence du signal SCL est générée selon l'équation suivante :

fSCL.png

Avec :

  • fSCL la fréquence du bus TWI ;
  • fCPU la fréquence du CPU ;
  • TWBR le bit rate ;
  • PrescalerValue la valeur du prescaler.

Si les fréquences CPU et SCL sont connues, il faut calculer le bit rate et la valeur du prescaler.

Afin de simplifier la manipulation des bits de statut du bus TWI, Atmel recommande, dans la mesure du possible, de laisser la valeur du prescaler à 1 et de privilégier l'ajustement du bit rate.

Les différentes valeurs de prescaler disponibles sont indiquées dans le tableau ci-dessous :

TWPS_-_TWI_Prescaler_Bits.png

À chaque valeur de prescaler correspondent des valeurs des bits TWPS0 et TWPS1 du registre TWSR.

Algorithme

Le calcul du bit rate se fait selon l'équation suivante :

TWBR.png

En suivant les recommandations d'Atmel, on commence par calculer le bit rate avec une valeur de prescaler de 1. Tant que la valeur du bit rate est supérieure à 255, on augmente la valeur du prescaler.

Implémentation

Sur un ATmega, la fréquence maximale du bus TWI est de 400 kHz. On définit la macro TWI__MAX_FREQUENCY :

/**************************************************************************//**
 * \def     TWI__MAX_FREQUENCY
 * \brief   Max frequency of TWI bus.
 ******************************************************************************/
#define TWI__MAX_FREQUENCY  400000

On spécifie les différentes valeurs de prescaler disponibles à l'aide de l'énumération twi__prescaler_values et on crée le type twi__prescaler_value_t :

/**************************************************************************//**
 * \enum    twi__prescaler_values
 * \brief   Available TWI bus prescaler values.
 *
 * \typedef twi__prescaler_value_t
 * \brief   TWI bus prescaler value.
 ******************************************************************************/
typedef enum twi__prescaler_values
{
    TWI__PRESCALER_VALUE__1,
    TWI__PRESCALER_VALUE__4,
    TWI__PRESCALER_VALUE__16,
    TWI__PRESCALER_VALUE__64,
    TWI__PRESCALER_VALUE__INVALID
} twi__prescaler_value_t;

À chaque valeur de prescaler correspond une valeur des bits TWPS0 et TWPS1 du registre TWSR. On définit la structure twi__prescaler_configuration et le type twi__prescaler_configuration_t pour représenter cette correspondance :

/**************************************************************************//**
 * \struct  twi__prescaler_configuration
 * \brief   TWI bus prescaler configuration.
 *
 * \typedef twi__prescaler_configuration_t
 * \brief   TWI bus prescaler configuration.
 ******************************************************************************/
typedef struct twi__prescaler_configuration
{
    uint8_t value;
    uint8_t bits;
} twi__prescaler_configuration_t;

On définit un tableau constant contenant les différentes configurations de prescaler disponibles :

/**************************************************************************//**
 * \var     prescaler_configurations
 * \brief   Available prescaler configurations.
 ******************************************************************************/
static const twi__prescaler_configuration_t prescaler_configurations[] =
{
    [TWI__PRESCALER_VALUE__1] =
    {
        .value  = 1,
        .bits   = 0
    },
    [TWI__PRESCALER_VALUE__4] =
    {
        .value  = 4,
        .bits   = _BV(TWPS0)
    },
    [TWI__PRESCALER_VALUE__16] =
    {
        .value  = 16,
        .bits   = _BV(TWPS1)
    },
    [TWI__PRESCALER_VALUE__64] =
    {
        .value  = 64,
        .bits   = _BV(TWPS1) | _BV(TWPS0)
    }
};

Enfin on implémente la fonction d'initialisation du bus TWI en mode maître twi__initialize. Celle-ci configure le bit rate et la valeur du prescaler.

/**************************************************************************//**
 * \fn void twi__initialize(uint32_t frequency)
 *
 * \brief Initialize TWI.
 *
 * \param   frequency   TWI frequency (up to 400 kHz).
 ******************************************************************************/
void twi__initialize(uint32_t frequency)
{
    // Check the preconditions.
    assert(TWI__MAX_FREQUENCY >= frequency);

    uint16_t bit_rate = UINT16_MAX;
    twi__prescaler_value_t prescaler_value = TWI__PRESCALER_VALUE__1;

    // Compute bit rate and prescaler value.
    do
    {
        bit_rate = twi__compute_bit_rate
        (
            frequency,
            prescaler_configurations[prescaler_value].value
        );
    } while (UINT8_MAX < bit_rate && TWI__PRESCALER_VALUE__INVALID > prescaler_value++);

	// Set bit rate.
	TWBR = bit_rate;

	// Set prescaler value.
	TWSR = prescaler_configurations[prescaler_value].bits;
}

Le calcul du bit_rate est réalisé par la fonction twi__compute_bit_rate :

/**************************************************************************//**
 * \fn static inline uint8_t twi__compute_bit_rate(
 * uint32_t f_scl,
 * uint8_t  prescaler_value)
 *
 * \brief
 *
 * \param   f_scl           SCL frequency.
 * \param   prescaler_value Prescaler value.
 ******************************************************************************/
static inline
uint8_t
twi__compute_bit_rate
(
    uint32_t    f_scl,
    uint8_t     prescaler_value
){
    uint8_t bit_rate = (F_CPU - 16 * f_scl) / (2 * f_scl * prescaler_value);

    return bit_rate;
}

Programme de test

La note d'application Atmel AVR315 - Using the TWI module as I2C master on tinyAVR and megaAVR device indique des configurations typiques du Bit Rate Generator Unit :

CPU_and_SCL_frequencies_versus_Bit_Rate_Generator_register_settings.png

Il semble judicieux d'écrire un programme de test capable de calculer tous les couples de valeurs de bit rate/prescaler en fonction de fréquences CPU et SCL données.

On définit la structure twi__setting et le type twi__setting_t pour représenter ces couples de valeurs :

/**************************************************************************//**
 * \struct  twi__setting
 * \brief   TWI bus setting.
 *
 * \typedef twi__setting_t
 * \brief   TWI bus setting.
 ******************************************************************************/
typedef struct twi__setting
{
    uint32_t    f_cpu;
    uint8_t     twbr;
    uint8_t     twps;
    uint32_t    f_scl;
} twi__setting_t;

On définit un tableau contenant les configurations typiques de la note d'application. Les valeurs des registres TWBR et TWPS seront calculées par le programme.

/**************************************************************************//**
 * \var     settings
 * \brief   Bit Rate Generator Unit Settings.
 ******************************************************************************/
static twi__setting_t settings[] =
{
    { .f_cpu = 16000000, .twbr = 0, .twps = 0, .f_scl = 400000 },
    { .f_cpu = 16000000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu = 14400000, .twbr = 0, .twps = 0, .f_scl = 400000 },
    { .f_cpu = 14400000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu = 12000000, .twbr = 0, .twps = 0, .f_scl = 400000 },
    { .f_cpu = 12000000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu =  8000000, .twbr = 0, .twps = 0, .f_scl = 400000 },
    { .f_cpu =  8000000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu =  4000000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu =  3600000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu =  2000000, .twbr = 0, .twps = 0, .f_scl = 100000 },
    { .f_cpu =  2000000, .twbr = 0, .twps = 0, .f_scl = 50000  },
    { .f_cpu =  1000000, .twbr = 0, .twps = 0, .f_scl = 50000  }
};

La fonction principale main appelle la fonction de calcul des paramètres du Bit Rate Generator Unit pour chaque configuration et affiche les résultats sur la sortie standard.

/**************************************************************************//**
 * \fn int main(void)
 *
 * \brief Main function.
 ******************************************************************************/
int main(void)
{
    printf
    (
        ".----------.------.------.--------.\n"
        "| F_CPU    | TWBR | TWPS | F_SCL  |\n"
        "|----------+------+------|--------|\n"
    );
    for (uint8_t i = 0; i < ITEMS_IN_ARRAY(settings); i++)
    {
        compute_settings(&(settings[i]));
        printf
        (
            "| %08u |  %02u  |  %02u  | %06u |\n",
            settings[i].f_cpu, settings[i].twbr, settings[i].twps, settings[i].f_scl
        );
    }
    printf("'----------'------'------'--------'");

    return EXIT_SUCCESS;
}

Pour une configuration donnée, la fonction compute_settings calcule le bit_rate et la valeur de prescaler.

/**************************************************************************//**
 * \fn static void compute_settings(twi__setting_t* p_setting)
 *
 * \brief Compute Bit Rate Generator Unit settings.
 *
 * \param[in]   p_setting   Settings to compute.
 ******************************************************************************/
static void compute_settings(twi__setting_t* p_setting)
{
    uint16_t bit_rate = UINT16_MAX;
    twi__prescaler_value_t prescaler_value = TWI__PRESCALER_VALUE__1;

    do
    {
        bit_rate = compute_bit_rate
        (
            p_setting->f_cpu,
            p_setting->f_scl,
           prescaler_configurations[prescaler_value].value
       );
    } while (UINT8_MAX < bit_rate && TWI__PRESCALER_VALUE__INVALID > prescaler_value++);

    p_setting->twbr = bit_rate;
    p_setting->twps = prescaler_configurations[prescaler_value].bits;
}

Le calcul du bit_rate est réalisé par la fonction compute_bit_rate :

/**************************************************************************//**
 * \fn static inline uint8_t compute_bit_rate(
 * uint32_t f_cpu,
 * uint32_t f_scl,
 * uint8_t  prescaler_value)
 *
 * \brief
 *
 * \param   f_cpu           CPU frequency
 * \param   f_scl           SCL frequency.
 * \param   prescaler_value Prescaler value.
 ******************************************************************************/
static inline
uint8_t
compute_bit_rate
(
    uint32_t    f_cpu,
    uint32_t    f_scl,
    uint8_t     prescaler_value
){
    uint8_t bit_rate = (f_cpu - 16 * f_scl) / (2 * f_scl * prescaler_value);

    return bit_rate;
}

Le programme de test indique sur la sortie standard les différentes valeurs de bit rate et de prescaler calculées pour obtenir les différentes fréquences du signal SCL :

.----------.------.------.--------.
| F_CPU    | TWBR | TWPS | F_SCL  |
|----------+------+------+--------|
| 16000000 |  12  |  00  | 400000 |
| 16000000 |  72  |  00  | 100000 |
| 14400000 |  10  |  00  | 400000 |
| 14400000 |  64  |  00  | 100000 |
| 12000000 |  07  |  00  | 400000 |
| 12000000 |  52  |  00  | 100000 |
| 08000000 |  02  |  00  | 400000 |
| 08000000 |  32  |  00  | 100000 |
| 04000000 |  12  |  00  | 100000 |
| 03600000 |  10  |  00  | 100000 |
| 02000000 |  02  |  00  | 100000 |
| 02000000 |  12  |  00  | 050000 |
| 01000000 |  02  |  00  | 050000 |
'----------'------'------'--------'

Les valeurs de TWBR et TWPS sont conformes à celles données dans la note d'application. Algorithme validé !

[Télécharger le programme complet]

Conclusion

Telle que proposée dans cet article, la configuration de la fréquence du bus TWI est totalement automatique, il suffit de préciser la fréquence désirée. Cette configuration est aussi dynamique et peut être changée à la volée pour s'adapter à chaque périphérique TWI que l'on souhaite adresser.

Bien que la démarche exposée soit basée sur un ATmega328P, celle-ci est applicable à la plupart des microcontrôleurs de la famille megaAVR.

Références