La fréquence du signal SCL est générée selon l'équation suivante :
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 :
À 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 :
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 :
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.