[STM32] Carte Nucleo F411

[STM32] Carte Nucleo F411

La carte Nucleo F411 doit :

  • Assurer le pilotage des moteurs en boucle fermée.
  • Mesurer les distances à l’avant et à l’arrière pour repérer un éventuel obstacle.
  • Afficher des informations sur l’écran LCD.
  • Communiquer avec le RPI.

Brochage

Selection des Broches


Sources Projet

WORKSPACE_F411_uROS.zip

REMARQUE : Le paramètre ROS_DOMAIN_ID ( défini dans main.c ) sera à ajuster en fonction du numéro indiqué sur le Robot.


Environnement de Développement

IDE Eclipse


Acquisition des Capteurs de distance avant ( Périphérique ADC )

Stratégies d’utilisation de l’ADC

Les convertisseurs sont suffisamment rapides pour scruter la fin de conversion plutôt que de réagir à une interruption de fin de conversion qui prendrait plus de temps.

  • void captDistIR_Init(void)
  • int captDistIR_Get(int* tab)
    • retourne la valeur des voies ADC 12 et 13
    • tab doit être l’adresse d’un tableau de 2 entiers
    • Exemple d’appel :
int table[2];			// Déclaration d'un tableau de 2 entiers situés à l'adresse table
captDistIR_Get(table);
code captDistIR
/*
 * IRMeasure.h
 */

#ifndef INC_CAPTDISTIR_H_
#define INC_CAPTDISTIR_H_

#include "main.h"


void captDistIR_Init(void);
int captDistIR_Get(int*);


#endif /* INC_CAPTDISTIR_H_ */
/*
 * IRMeasure.c
 */


#include "captDistIR.h"

ADC_HandleTypeDef 	adcHandle;
ADC_HandleTypeDef 	adcHandle_12;
ADC_HandleTypeDef 	adcHandle_13;
ADC_ChannelConfTypeDef   sConfig;

//=================================================================
//			ADC INIT FOR IR SENSOR SHARP GP2D12
//=================================================================

void  captDistIR_Init(void)
{
	adcHandle.Instance		= ADC1;

	adcHandle.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV2;
	adcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
	adcHandle.Init.Resolution = ADC_RESOLUTION12b;
	// Don't do continuous conversions - do them on demand
	adcHandle.Init.ContinuousConvMode    = DISABLE;  // Continuous mode disabled to have only 1 conversion at each conversion trig
	// Disable the scan conversion so we do one at a time */
	adcHandle.Init.ScanConvMode = DISABLE;
	//Say how many channels would be used by the sequencer
	adcHandle.Init.NbrOfConversion = 2;
	adcHandle.Init.DiscontinuousConvMode = DISABLE;  // Parameter discarded because sequencer is disabled
	adcHandle.Init.NbrOfDiscConversion = 2;
	adcHandle.Init.ExternalTrigConvEdge =  ADC_EXTERNALTRIGCONVEDGE_NONE ;
	//Start conversion by software, not an external trigger
	adcHandle.Init.ExternalTrigConv = 0;
	adcHandle.Init.DMAContinuousRequests = DISABLE;
	adcHandle.Init.EOCSelection = DISABLE;

	HAL_ADC_Init(&adcHandle);
}

//=================================================================
//			IR GET (POLL METHOD)
//=================================================================

int  captDistIR_Get(int* tab)
{
	sConfig.Channel      = ADC_CHANNEL_12;
	sConfig.Rank         = 1;
	sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
	HAL_ADC_ConfigChannel(&adcHandle, &sConfig);

	HAL_ADC_Start(&adcHandle);					//Start the conversion
	HAL_ADC_PollForConversion(&adcHandle,10);	//Processing the conversion
	tab[0]=HAL_ADC_GetValue(&adcHandle); 		//Return the converted data

	sConfig.Channel      = ADC_CHANNEL_13;
	sConfig.Rank         = 1;
	sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
	HAL_ADC_ConfigChannel(&adcHandle, &sConfig);

	HAL_ADC_Start(&adcHandle);						//Start the conversion
	HAL_ADC_PollForConversion(&adcHandle,10);		//Processing the conversion
	tab[1]=HAL_ADC_GetValue(&adcHandle); 			//Return the converted data

	return 0;
}
//=================================================================

Acquisition du Capteur de distance arrière ( Périphérique I2C )

Initialisation :

  • VL53L0X_init();
  • VL53L0X_validateInterface() : interroge de manière séquentielle une série de registres du capteur contenant des constantes. La comparaison de ces constantes avec les valeurs attendues permet de valider la communication avec le capteur.
  • VL53L0X_startContinuous(T_ms); : mesure toutes les T_ms, si T_ms=0 mesures en continu

Récupération de la Distance Mesurée :

  • VL53L0X_readRangeContinuousMillimeters();

Affichage sur l’écran LCD ( Périphérique I2C )

code groveLCD
/*
 * groveLCD.h
 *
 *  Created on: Oct 16, 2019
 *      Author: kerhoas
 */
#ifndef INC_GROVELCD_H_
#define INC_GROVELCD_H_

#include "main.h"

// Device I2C Arress
#define LCD_ADDRESS     (0x7c)
#define RGB_ADDRESS     (0xc4)


// color define
#define WHITE           0
#define RED             1
#define GREEN           2
#define BLUE            3

#define REG_RED         0x04        // pwm2
#define REG_GREEN       0x03        // pwm1
#define REG_BLUE        0x02        // pwm0

#define REG_MODE1       0x00
#define REG_MODE2       0x01
#define REG_OUTPUT      0x08

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

void groveLCD_test();
void groveLCD_begin(uint8_t cols, uint8_t lines, uint8_t dotsize);
void groveLCD_setColorAll();
void groveLCD_setColorWhite();
void groveLCD_clear();
void groveLCD_home();
void groveLCD_setCursor(uint8_t col, uint8_t row);
void groveLCD_noDisplay();
void groveLCD_display() ;
void groveLCD_noCursor();
void groveLCD_cursor() ;
void groveLCD_noBlink();
void groveLCD_blink();
void groveLCD_scrollDisplayLeft(void);
void groveLCD_scrollDisplayRight(void);
void groveLCD_leftToRight(void);
void groveLCD_rightToLeft(void);
void groveLCD_autoscroll(void);
void groveLCD_noAutoscroll(void);
void groveLCD_createChar(uint8_t location, uint8_t charmap[]);
void groveLCD_blinkLED(void);
void groveLCD_noBlinkLED(void);
void groveLCD_command(uint8_t value);
int groveLCD_write(uint8_t value);
void groveLCD_setReg(unsigned char addr, unsigned char dta);
void groveLCD_setRGB(unsigned char r, unsigned char g, unsigned char b);
void groveLCD_setColor(unsigned char color);
void groveLCD_putString(char* s);
void groveLCD_term_printf(const char* fmt, ...);



#endif /* INC_GROVELCD_H_ */
/*
 * groveLCD.c
 *
 *  Created on: Jan 8, 2020
 *      Author: kerhoas
 */

#include "groveLCD.h"
#include "math.h"
#include "util.h"

  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;
  uint8_t _initialized;
  uint8_t _numlines,_currline;

//=================================================================
void groveLCD_test()
{
	int8_t tab[1];
	tab[1] = 100;
  i2c1_WriteRegBuffer(RGB_ADDRESS, REG_RED,  tab, 1);

}
//=================================================================
void i2c_send_byte(unsigned char dta)
{
	i2c1_WriteBuffer(LCD_ADDRESS, &dta, 1);
}
//=================================================================
void i2c_send_byteS(unsigned char *dta, unsigned char len)
{
	i2c1_WriteBuffer(LCD_ADDRESS, dta, len);
}
//=================================================================
void groveLCD_begin(uint8_t cols, uint8_t lines, uint8_t dotsize)
{
    if (lines > 1) {
        _displayfunction |= LCD_2LINE;
    }
    _numlines = lines;
    _currline = 0;

    // for some 1 line displays you can select a 10 pixel high font
    if ((dotsize != 0) && (lines == 1)) {
        _displayfunction |= LCD_5x10DOTS;
    }

    // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
    // according to datasheet, we need at least 40ms after power rises above 2.7V
    // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
    HAL_Delay(50);


    // this is according to the hitachi HD44780 datasheet
    // page 45 figure 23

    // Send function set command sequence
    groveLCD_command(LCD_FUNCTIONSET | _displayfunction);
    HAL_Delay(5);  // wait more than 4.1ms

    // second try
    groveLCD_command(LCD_FUNCTIONSET | _displayfunction);
    HAL_Delay(5);

    // third go
    groveLCD_command(LCD_FUNCTIONSET | _displayfunction);


    // finally, set # lines, font size, etc.
    groveLCD_command(LCD_FUNCTIONSET | _displayfunction);

    // turn the display on with no cursor or blinking default
    _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
    groveLCD_display();

    // clear it off
    groveLCD_clear();

    // Initialize to default text direction (for romance languages)
    _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
    // set the entry mode
    groveLCD_command(LCD_ENTRYMODESET | _displaymode);


    // backlight init
    groveLCD_setReg(REG_MODE1, 0);
    // set LEDs controllable by both PWM and GRPPWM registers
    groveLCD_setReg(REG_OUTPUT, 0xFF);
    // set MODE2 values
    // 0010 0000 -> 0x20  (DMBLNK to 1, ie blinky mode)
    groveLCD_setReg(REG_MODE2, 0x20);

    groveLCD_setColorWhite();

}
//=================================================================
void groveLCD_setColorAll(){groveLCD_setRGB(0, 0, 0);}
void groveLCD_setColorWhite(){groveLCD_setRGB(255, 255, 255);}
//=================================================================

/********** high level commands, for the user! */
void groveLCD_clear()
{
	groveLCD_command(LCD_CLEARDISPLAY);        // clear display, set cursor position to zero
    HAL_Delay(2000);          // this command takes a long time!
}
//=================================================================
void groveLCD_home()
{
	groveLCD_command(LCD_RETURNHOME);        // set cursor position to zero
    HAL_Delay(2000);        // this command takes a long time!
}
//=================================================================
void groveLCD_setCursor(uint8_t col, uint8_t row)
{
    col = (row == 0 ? col|0x80 : col|0xc0);
    unsigned char dta[2] = {0x80, col};
    i2c_send_byteS(dta, 2);
}
//=================================================================
// Turn the display on/off (quickly)
void groveLCD_noDisplay()
{
    _displaycontrol &= ~LCD_DISPLAYON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
void groveLCD_display() {
    _displaycontrol |= LCD_DISPLAYON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
// Turns the underline cursor on/off
void groveLCD_noCursor()
{
    _displaycontrol &= ~LCD_CURSORON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
void groveLCD_cursor() {
    _displaycontrol |= LCD_CURSORON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
// Turn on and off the blinking cursor
void groveLCD_noBlink()
{
    _displaycontrol &= ~LCD_BLINKON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
void groveLCD_blink()
{
    _displaycontrol |= LCD_BLINKON;
    groveLCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}
//=================================================================
// These commands scroll the display without changing the RAM
void groveLCD_scrollDisplayLeft(void)
{
	groveLCD_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
//=================================================================
void groveLCD_scrollDisplayRight(void)
{
	groveLCD_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}
//=================================================================
// This is for text that flows Left to Right
void groveLCD_leftToRight(void)
{
    _displaymode |= LCD_ENTRYLEFT;
    groveLCD_command(LCD_ENTRYMODESET | _displaymode);
}
//=================================================================
// This is for text that flows Right to Left
void groveLCD_rightToLeft(void)
{
    _displaymode &= ~LCD_ENTRYLEFT;
    groveLCD_command(LCD_ENTRYMODESET | _displaymode);
}
//=================================================================
// This will 'right justify' text from the cursor
void groveLCD_autoscroll(void)
{
    _displaymode |= LCD_ENTRYSHIFTINCREMENT;
    groveLCD_command(LCD_ENTRYMODESET | _displaymode);
}
//=================================================================
// This will 'left justify' text from the cursor
void groveLCD_noAutoscroll(void)
{
    _displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
    groveLCD_command(LCD_ENTRYMODESET | _displaymode);
}
//=================================================================
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void groveLCD_createChar(uint8_t location, uint8_t charmap[])
{
    location &= 0x7; // we only have 8 locations 0-7
    groveLCD_command(LCD_SETCGRAMADDR | (location << 3));

    unsigned char dta[9];
    dta[0] = 0x40;
    for(int i=0; i<8; i++)
    {
        dta[i+1] = charmap[i];
    }
    i2c_send_byteS(dta, 9);
}
//=================================================================
// Control the backlight LED blinking
void groveLCD_blinkLED(void)
{
    // blink period in seconds = (<reg 7> + 1) / 24
    // on/off ratio = <reg 6> / 256
	groveLCD_setReg(0x07, 0x17);  // blink every second
	groveLCD_setReg(0x06, 0x7f);  // half on, half off
}
//=================================================================
void groveLCD_noBlinkLED(void)
{
	groveLCD_setReg(0x07, 0x00);
	groveLCD_setReg(0x06, 0xff);
}
//=================================================================
/*********** mid level commands, for sending data/cmds */

// send command
void groveLCD_command(uint8_t value)
{
    unsigned char dta[2] = {0x80, value};
    i2c_send_byteS(dta, 2);
}
//=================================================================
// send data
int groveLCD_write(uint8_t value)
{
    unsigned char dta[2] = {0x40, value};
    i2c_send_byteS(dta, 2);
    return 1; // assume sucess
}
//=================================================================
void groveLCD_putString(char* s)
{
	while(*s != '\0')
	{
		groveLCD_write(*s);
		s++;
	}
}
//=================================================================
void groveLCD_setReg(unsigned char addr, unsigned char dta)
{
    i2c1_WriteRegBuffer(RGB_ADDRESS, addr,  &dta, 1);
}
//=================================================================
void groveLCD_setRGB(unsigned char r, unsigned char g, unsigned char b)
{
	groveLCD_setReg(REG_RED, r);
	groveLCD_setReg(REG_GREEN, g);
	groveLCD_setReg(REG_BLUE, b);
}
//=================================================================
const unsigned char color_define[4][3] =
{
    {255, 255, 255},            // white
    {255, 0, 0},                // red
    {0, 255, 0},                // green
    {0, 0, 255},                // blue
};
//=================================================================
void groveLCD_setColor(unsigned char color)
{
    if(color > 3)return ;
    groveLCD_setRGB(color_define[color][0], color_define[color][1], color_define[color][2]);
}
//============================================================
void groveLCD_term_printf(const char* fmt, ...)
{
	__gnuc_va_list         ap;
	char          *p;
	char           ch;
	unsigned long  ul;
	unsigned long long ull;
	unsigned long  size;
	unsigned int   sp;
	char           s[60];
	int first=0;

	va_start(ap, fmt);

	while (*fmt != '\0') {
		if (*fmt =='%') {
			size=0; sp=1;
			if (*++fmt=='0') {fmt++; sp=0;}	// parse %04d --> sp=0
			ch=*fmt;
			if ((ch>'0') && (ch<='9')) {	// parse %4d --> size=4
				char tmp[10];
				int i=0;
				while ((ch>='0') && (ch<='9')) {
					tmp[i++]=ch;
					ch=*++fmt;
				}
				tmp[i]='\0';
				size=str2num(tmp,10);
			}
			switch (ch) {
				case '%':
					groveLCD_write('%');
					break;
				case 'c':
					ch = va_arg(ap, int);
					groveLCD_write(ch);
					break;
				case 's':
					p = va_arg(ap, char *);
					groveLCD_putString(p);
					break;
				case 'd':
					ul = va_arg(ap, long);
					if ((long)ul < 0) {
						groveLCD_write('-');
						ul = -(long)ul;
						//size--;
					}
					num2str(s, ul, 10, size, sp);
					groveLCD_putString(s);
					break;
				case 'u':
					ul = va_arg(ap, unsigned int);
					num2str(s, ul, 10, size, sp);
					groveLCD_putString(s);
					break;
				case 'o':
					ul = va_arg(ap, unsigned int);
					num2str(s, ul, 8, size, sp);
					groveLCD_putString(s);
					break;
				case 'p':
					groveLCD_write('0');
					groveLCD_write('x');
					ul = va_arg(ap, unsigned int);
					num2str(s, ul, 16, size, sp);
					groveLCD_putString(s);
					break;
				case 'x':
					ul = va_arg(ap, unsigned int);
					num2str(s, ul, 16, size, sp);
					groveLCD_putString(s);
					break;
				case 'f':
					if(first==0){ ull = va_arg(ap, long long unsigned int); first = 1;}
					ull = va_arg(ap, long long unsigned int);
					int sign = ( ull & 0x80000000 ) >> 31;
					int m = (ull & 0x000FFFFF) ; // should be 0x007FFFFF
					float mf = (float)m ;
					mf = mf / pow(2.0,20.0);
					mf = mf + 1.0;
					int e = ( ull & 0x78000000 ) >> 23 ; // should be int e = ( ul & 0x7F800000 ) >> 23;
					e = e | (( ull & 0x000F00000 ) >> 20);
					e = e - 127;
					float f = mf*myPow(2.0,e);
					if(sign==1){ groveLCD_write('-'); }
					float2str((char*)s, f, 5);
					groveLCD_putString((char*)s);
					break;

				default:
					groveLCD_write(*fmt);
			}
		} else groveLCD_write(*fmt);
		fmt++;
	}
	va_end(ap);
}
//=================================================================




REMARQUE : Les fonctions groveLCD_clear() ou groveLCD_home() prennent beaucoup de temps du fait des temporisations.
Il peut être plus intéressant d’utiliser groveLCD_setCursor(0,0) suivi de groveLCD_term_printf(), en gardant en tête que les fonctions d’affichage prennent du temps ( tout affichage ne doit pas perturber les autres éléments du robot ).


Communication avec le RPI ( liaison UART1 )

Du fait de la sérialisation, la gestion de l’envoi ou de la réception de données par l’ UART prend du temps.
Pour libérer le Processeur, le DMA ( Direct Memory Access ) permet de stocker directement en mémoire les données reçues par l’UART, sans perturber le processeur.
Une fois la zone mémoire remplie, le processeur peut alors traiter l’ensemble des données disponibles ( sans attendre la réception de chaque caractère ).
Il en va de même pour l’émission : le processeur remplit une zone mémoire dédiée, et le DMA se charge de transmettre chaque caractère à l’UART, qui le sérialise.

dma_uart.svg

La communication avec le RPI se fera au formalisme ROS2, la fonction rmw_uros_set_custom_transport() permet d’indiquer le périphérique UART utilisé par la bibliothèque microros

rmw_uros_set_custom_transport( true, (void *) &huart1, cubemx_transport_open,  cubemx_transport_close,  cubemx_transport_write, cubemx_transport_read);

Les fonctions publish et subscribe permettent alors d’envoyer un message ou de s’abonner à la réception d’un message.


Communication avec PC HOST pour le DEBUG ( liaison UART2 )

La fonction printf() est configurée pour envoyer des caractères au périphérique UART2.
En effet la fonction élémentaire __io_putchar() utilisée par printf() est redéfinie afin d’envoyer un caractère sur UART2 :

drv_uart.c
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

Côté PC Host

Un terminal série ( minicom ou gtkterm ) permet, côté PC, d’afficher le message :

$ minicom -D /dev/ttyACM0 -b 115200  

REMARQUE : Afin de respecter la période d’échantillonage pour le contrôle des moteurs en boucle fermée, éviter de placer trop de printf dans le code ( cette fonction a un temps d’exécution non négligeable ).