pub:dglab_stm32

Eclipse puede utilizarse como entorno de desarrollo integrado con el fin de programar los microcontroladores de 32 bits de la serie STM32 de STMicroelectronics. Para facilitar el desarrollo, se dispone de dos herramientas principales. Por un lado Eclipse para el desarrollo de código y la depuración del sistema y por otro lado STM32CubeMX para la inicialización de perifericos, configuración del sistema e inclusión de las librerías de alto nivel.

Es importante conocer la estructura y utilidad las librerías que pueden utilizarse en el desarrollo de software para dichos microcontroladores. Destacan:

  • STM32Cube: Es un paquete de software que contiene un conjunto de librerías para el desarrollo.
    • Hardware Abstraction Layer (HAL): Capa de abstracción que contiene drivers para todos los periféricos disponibles para la serie del microcontrolador en uso.
    • Board Support Package (BSP): Es una capa al mismo nivel de abstracción que la HAL y que se compone de controladores específicos para la placa de desarrollo escogida. En la mayoría de casos contiene drivers para memorias externas en la placa (como SDRAM o Flash QSPI), soporte para pantallas lcd, pantallas táctiles, acelerómetros, etc.
    • Middleware: Paquetes de software que proporcionan aún mayor abstracción sobre el hardware. Entre ellos, destacan el protocolo TCP/IP (LwIP), un sistema operativo de tiempo real (FreeRTOS), un sistema de archivos FAT (FatFS), soporte de múltiples clases USB y una librería de gráficos (STemWin).

STM32CubeMX es una herramienta gratuita proporcionada por STMicroelectronics, y que nos facilitará mucho los primeros pasos con el entorno mediante una interfaz clara y sencilla de utilizar para pasar directamente al diseño del sistema integrado una vez la inicialización del sistema se ha completado.

La herramienta contiene descripciones tanto de las placas de evaluación de los microcontroladores STM32 como de los propios microcontroladores, de modo que se puede generar código de inicialización tanto para un microcontrolador como para una placa de desarrollo (que además contiene los paquetes de soporte de la placa o BSP).

Ejemplo de uso

En el ejemplo se utilizará la placa STM32F4 Discovery, pero puede tomarse como referencia para la mayoría de microcontroladores de la serie STM32F4. El objetivo de este ejemplo es utilizar un timer para temporizar un intervalo de tiempo tras el cual producirá una interrupción para que el microcontrolador transmita los datos del acelerómetro situado en la placa mediante el puerto usb OTG, que se configurará como dispositivo de la clase de comunicaciones (CDC).

Para generar el código de inicialización utilizaremos la herramienta STM32CubeMx de la que se habló anteriormente.

Si abrimos la aplicación y seleccionamos nuevo proyecto, nos aparecerá la ventana de selección de placa o microcontrolador. En nuestro caso, seleccionaremos la placa correspondiente, como se puede observar en la siguiente imagen.

Se puede observar que es posible obtener el manual o cargar la propia web de STMicroelectronics desde el propio programa una vez seleccionada la placa. En nuestro caso, vamos a obviarlo y continuar adelante con la generación de código de inicialización presionando OK.

Nos aparecerá la siguiente ventana, en la que vemos todos los pines de nuestro microcontrolador.

A la izquierda de la ventana nos aparece la configuración del Middleware y la configuración de los periféricos del microcontrolador.

  • Middleware: Elementos de firmware que se encargan de gestionar la comunicación de los periféricos con nuestra aplicación, creando una capa de abstracción. En algunos casos tienen elementos adicionales de software, como por ejemplo para FATFS se trata además de un sistema de archivos FAT para el acceso y la gestión de archivos de una tarjeta SD, un flash drive, etc.

Se encuentran activados por defecto aquellos en verde. Los que aparecen en amarillo con la señal de advertencia, pueden ser activados y configurados parcialmente, con alguna funcionalidad limitada. Para el caso de los que se encuentran en rojo, no es posible activarlos, dado que existe algún conflicto con otro ya activado que no puede ser resuelto (Porque utilizan el mismo pin, por ejemplo).

Es posible ver que función puede adquirir cada pin simplemente haciendo click sobre el pin en cuestión:

El pin PC6 puede ser configurado entre otros como entrada (digital o analógica), salida, o funciones alternativas como USART o incluso como una entrada de interrupción.

Podemos observar que los pines PD12 a PD15 corresponden con los led's de la placa. Se encuentran activados como salidas de propósito general. Los dejaremos así para utilizarlos posteriormente.

Por otro lado, será necesario configurar un timer. Tomaremos para el ejemplo el Timer 6, que es un timer de tipo básico, de 16 bits con un prescaler de 16 bits también. Este timer permite configurar la carga a cero del registro cuando se alcanza un determinado valor. Utilizando tanto el prescaler como esta característica, conseguiremos temporizar 0.1 segundos, que es el objetivo. Del mismo modo, cada vez que se produce la recarga del timer, se produce un evento de actualización que puede generar interrupciones. Usaremos esta capacidad para obtener la generación de interrupciones cada 100 milisegundos.

Para Activar el timer, hacemos clic a la izquierda sobre TIM6 y en el menú que se despliega seleccionamos Activated.

También necesitamos activar el periférico USB OTG FS, que se encuentra implementado en la placa de evaluación mediante un conector micro-usb. Para activarlo, de nuevo procedemos en la parte izquierda de la ventana y activamos el periférico en modo dispositivo únicamente. En este caso es posible seleccionar entre varios modos.

  • Modo Device: en modo device, el dispositivo se encuentra esperando a que el host inicie todas las comunicaciones. Este es el caso de un dispositivo de almacenamiento masivo (MSC), un dispositivo interfaz humana (HID, tipo ratón, teclado, joystick…), un dispositivo de comunicaciones (CDC)…
  • Modo Host: en modo host, el periférico usb será el encargado de iniciar todas las transacciones de información. En este caso, se puede entender como un anfitrión de diferentes tipos de dispositivos, que se corresponden con los comentados en el modo device.
  • Modo Dual: En este modo, el dispositivo encargado de conmutar entre ambas funcionalidades es el propio micrococontrolador. Es el ejemplo de algunos terminales Android, que son capaces de actuar como host para un dispositivo de almacenamiento masivo y como device de cara al ordenador.

Para el ejemplo a seguir, utilizaremos el modo Device. Una vez activado el periférico, utilizaremos una capa de abstracción correspondiente a una Clase USB de tipo CDC. Para ello, activamos el middleware USB_DEVICE como Comunication Device Class.

La segunda pestaña de la ventana nos indica la configuración y rutado de las señales de reloj que van destinadas tanto al núcleo del procesador como a distintos periféricos y buses del sistema. La configuración establecida en esta pestaña para cada uno de los relojes será transferida en forma de instrucciones a una función en C que se llama desde el programa principal al iniciar la ejecución del sistema.

Se observa que por defecto, el microcontrolador utiliza un oscilador externo de alta velocidad de 8 MHz, que se hace pasar a través de un PLL (Phase Locked Loop) para obtener un total de 168 MHz (que corresponde con el máximo del microcontrolador). Este reloj, denominado reloj del sistema (SYSCLK), se propaga a los demás periféricos y buses con distintas configuraciones. Dado que no tenemos ninguna necesidad concreta, lo dejaremos como está para este ejemplo.

La siguiente imagen muestra la pestaña de configuración.

La pestaña de configuración muestra detalles sobre los periféricos activos y sobre los Middlewares activados. En el proyecto por defecto se encuentran activados el DMA, el NVIC, el RCC y el controlador de los GPIO. Veamos el propósito de cada uno:

  • DMA: Direct Memory Access. Se trata de un periférico utilizado para realizar transferencias de dato en bloques entre la memoria y los periféricos del microcontrolador, descargando al núcleo del microcontrolador del tiempo que suponen dichas transferencias.
  • NVIC: Nested Vectored Interrupt Controller. Es un controlador de interrupciones. En el caso de los microprocesadores ARM Cortex-M, el control de interrupciones es vectorizado, de modo que se establecen directamente las prioridades de las interrupciones en el propio vector, y también las direcciones a las que se debe saltar cuando se produce una interrupción, obteniendo directamente el procesador la dirección de salto mediante el vector concreto. Por otro lado, La tabla de vectores puede cambiar su posición a varias zonas de memoria.
  • RCC (Reset and Clock Control.) Se trata de un controlador del reset y de las fuentes de reloj disponibles y su configuración.
  • GPIO (General Purpose Input Output.) Son una serie de registros asociados a las entradas y salidas de propósito general (Aquellas que no están asociadas a un periférico específico, como por ejemplo una uart).

Adicionalmente, podemos observar los tres recursos añadidos anteriormente. El temporizador TIM6 debe contener la siguiente configuración:

Como se puede ver en la siguiente imagen, se ha utilizado un prescaler de 8400. Esto se debe a que la frecuencia a la que funciona el timer (Que se puede observar en la configuración de la distribución de señales de reloj) es de 84 MHz. De este modo, se consigue reducirlo a 10 kHz. Si contamos hasta 1000 y luego producimos una interrupción, el tiempo desde la activación u overflow hasta que se produzca la interrupción es de 100 ms. Por tanto, cumplimos el objetivo marcado.

La configuración tanto del periférico USB_OTG_FS como del middleware USB_DEVICE se pueden mantener tal y como se encuentran por defecto.

Lo último que falta por configurar antes de pasar a la generación de código son las interrupciones dadas por el NVIC. Se configuran tal y como se muestra en la siguiente imagen:

Se ha configurado la tabla con 4 bits de subprioridad y 0 de prioridad. Esto significa que se priorizan las interrupciones con menor valor en subprioridad. En el caso de tener un bit de prioridad, tendría mas prioridad una de valor 0 en el bit de prioridad. Si ambas tuvieran el mismo bit de prioridad, decidiría la subprioridad.

Hemos activado interrupciones tanto para el USB_FS como para el TIM6. La interrupción que ya aparecía está destinada a manipular las funciones de delay principalmente, actualizando el valor de tick, que se incrementa cada milisegundo.

Generación de código de inicialización

Para la exportación del código a un entorno de desarrollo integrado, primero deberemos seleccionar a qué entorno queremos exportar el código (Genera un archivo de proyecto para el entorno deseado) y configurar algunas opciones adicionales. Para ello, haremos clic en:

Project -> Settings

La toolchain o IDE deberemos configurarla a SW4STM32, que es el entorno libre de desarrollo basado en Eclipse que usamos. Debemos dar un nombre al proyecto, y ubicar el mismo. Lo adecuado es ubicarlo en un espacio de trabajo de eclipse. En cuanto a la generación de código, existen algunas opciones configurables, pero las dejaremos como están para el ejemplo.

Por último, generamos el código junto con el proyecto y las librerías necesarias haciendo click en el siguiente icono:

STM32CubeMx también es capaz de generar un informe que contiene una especificación de los recursos utilizados y habilitados en el proyecto. Es de gran utilidad en la documentación de proyectos y especialmente en lo que reguarda al código de inicialización.

Importación del proyecto a Eclipse

Para importar el proyecto, una vez lo hemos generado a partir del STM32CubeMX, abriremos Eclipse y continuaremos la edición de la aplicación allí.

Debemos hacer clic en:

File -> Import...

Seleccionaremos importar proyecto existente, seleccionamos la ruta del proyecto y por último el proyecto a incluir.

Al importarlo, veremos que el proyecto se ha importado con el nombre “nombre del proyecto” Configuration. Para poder compilar el proyecto, es necesario modificar el nombre del Artifact, que es el archivo de programación que genera el IDE. Esto se puede hacer mediante click derecho en el proyecto, entrando en propiedades, y navegamos hasta la siguiente ventana.

Es necesario quitar la palabra Configuration del Artifact name para que el proyecto construya correctamente. Los autores de SW4STM32 comentan que aunque se podría cambiar el nombre del proyecto, no es tan directo y causa más problemas a la larga.

Arquitectura del proyecto generado

En el proyecto importado veremos la estructura jerárquica de documentos fuente del mismo. En la carpeta Application se encuentran las fuentes de nuestra aplicación. Dentro, en la carpeta User encontraremos el fichero main.c. En este fichero encontraremos la función main(). Vemos que entre otros, se lleva a cabo la inicialización del reloj, configuración de los GPIO y configuración del timer. Debemos introducir nuestro código a continuación de dichas inicializaciones, bien introduciendo más código de inicialización, o bien en el bucle infinito.

Podemos observar etiquetas en comentarios que marcan donde debe ir el código escrito por el usuario. Es buena práctica escribir el código dentro de las mismas porque si se realizan cambios al proyecto de STM32CubeMX, al volver a generar las fuentes se mantendrá todo el código escrito entre dichas etiquetas. Un ejemplo de las etiquetas se muestra en el siguiente fragmento de código.

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
      // El código de usuario va aquí
  /* USER CODE END 2 */
Construcción del proyecto

Si tratamos de construir el proyecto tal y como nos lo ha dado la aplicación STM32CubeMX, observaremos que nos da un error:

/home/usuario/workspace/CDCV2/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:62:22: fatal error: USBD_CDC.h: No such file or directory

Este error se debe a un fallo en la generación del código de inicialización de STM32CubeMX. Para solventarlo, navegamos hasta el archivo en el que se ha producido el error haciendo doble click sobre el mismo. Observamos que el error se debe a que no encuentra el fichero de cabecera USBD_CDC.h para incluirla en el archivo usbd_cdc.c. El error se debe a que el archivo no se encuentra escrito en mayusculas, sino en minúsculas. Procedemos a reemplazar el nombre con los caracteres en minúsculas, obteniendo el siguiente fragmento de código:

  * @file    usbd_cdc.c
 
/* Includes ------------------------------------------------------------------*/
#include "usbd_cdc.h"
#include "usbd_desc.h"
#include "usbd_ctlreq.h"
 
/** @addtogroup STM32_USB_DEVICE_LIBRARY
  * @{
  */

Si probamos de nuevo a construir el proyecto, deberá funcionar sin problemas.

Añadiendo las fuentes del BSP

Una desventaja de STM32CubeMX es que no proporciona opciones de inicialización de periféricos de la placa, ni tampoco la opción de incluir los paquetes de soporte a las placas (BSP). En nuestro caso, dado que utilizaremos el acelerómetro, será necesario incluir los ficheros correspondientes a este de la BSP. Para ello, primero debemos localizar la BSP, que se encuentra en el siguiente directorio:

~/stm32_repository/STM32Cube_FW_F4_V1.9.0/Drivers/BSP

Dentro de ese directorio, encontraremos una carpeta por cada placa de evaluación y una carpeta para los componentes de las placas (acelerómetros, drivers de audio…). Debemos arrastrar la carpeta BSP a nuestro proyecto en Eclipse, dentro de Drivers y especificar Copy files and folders. Posteriormente iremos eliminando ficheros fuente hasta que queden únicamente los necesarios. Finalmente, debe quedar:

Tras incluir las fuentes que se muestran, será necesario marcar los caminos a los includes, para ello, haciendo click derecho sobre el proyecto y click izquierdo sobre properties llegamos a la siguiente ventana:

Es necesario incluir las referencias a los archivos de cabecera añadidos, para ello, se pulsa sobre el botón add… y se marca la ruta para cada archivo de cabecera, siempre que esta no esté ya incluida. Al final debe quedar como la imagen superior.

Si ahora intentamos construir el proyecto, observamos que falla con múltiples mensajes de error. Entre ellos se encuentran:

../Drivers/BSP/STM32F4-Discovery/stm32f4_discovery.c:335:27: error: 'SPI_MODE_MASTER' undeclared (first use in this function)

../Drivers/BSP/STM32F4-Discovery/stm32f4_discovery.c:455:62: error: 'I2C_MEMADD_SIZE_8BIT' undeclared (first use in this function)

../Drivers/BSP/STM32F4-Discovery/stm32f4_discovery.c:474:3: warning: implicit declaration of function 'HAL_I2C_DeInit' [-Wimplicit-function-declaration]

Vemos que son errores referidos a los módulos SPI e I2C. Estos errores se encuentran porque la BSP utiliza los módulos SPI y I2C en el archivo stm32f4_discovery.c. Para solventarlo, ya que nosotros no añadimos estos módulos durante la generación del código con STM32CubeMX, será necesario añadir los archivos fuentes de ambos módulos. Estos se encuentran en:

~/stm32_repository/STM32Cube_FW_F4_V1.9.0/Drivers/STM32F4xx_HAL_Driver/Src/

Debemos buscar en este directorio los archivos:

stm32f4xx_hal_spi.c
stm32f4xx_hal_i2c.c

Arrastraremos los ficheros dentro de la carpeta de Drivers, en la carpeta STM32F4xx_HAL_Driver, que es la correspondiente a la capa de abstracción de hardware de los periféricos del microcontrolador. Debe quedar como se muestra en la siguiente figura:

Pero no basta con simplemente añadir los archivos fuente. Se necesita activar los módulos en el archivo de cabecera de configuración de las librerías HAL: stm32f4xx_hal_conf.h. En este archivo, dentro de la lista de selección de modelos, debemos descomentar las lineas correspondientes a los módulos añadidos.

/**
  ******************************************************************************
  * @file    stm32f4xx_hal_conf.h
  * @brief   HAL configuration file.
  ******************************************************************************
  */
 
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F4xx_HAL_CONF_H
#define __STM32F4xx_HAL_CONF_H
 
#ifdef __cplusplus
 extern "C" {
#endif
 
#include "mxconstants.h"
 
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
 
/* ########################## Module Selection ############################## */
/**
  * @brief This is the list of modules to be used in the HAL driver
  */
#define HAL_MODULE_ENABLED
 
//#define HAL_ADC_MODULE_ENABLED
//#define HAL_CAN_MODULE_ENABLED
//#define HAL_CRC_MODULE_ENABLED
//#define HAL_CRYP_MODULE_ENABLED
//#define HAL_DAC_MODULE_ENABLED
//#define HAL_DCMI_MODULE_ENABLED
//#define HAL_DMA2D_MODULE_ENABLED
//#define HAL_ETH_MODULE_ENABLED
//#define HAL_NAND_MODULE_ENABLED
//#define HAL_NOR_MODULE_ENABLED
//#define HAL_PCCARD_MODULE_ENABLED
//#define HAL_SRAM_MODULE_ENABLED
//#define HAL_SDRAM_MODULE_ENABLED
//#define HAL_HASH_MODULE_ENABLED
#define HAL_I2C_MODULE_ENABLED
//#define HAL_I2S_MODULE_ENABLED
//#define HAL_IWDG_MODULE_ENABLED
//#define HAL_LTDC_MODULE_ENABLED
//#define HAL_RNG_MODULE_ENABLED
//#define HAL_RTC_MODULE_ENABLED
//#define HAL_SAI_MODULE_ENABLED
//#define HAL_SD_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
//#define HAL_UART_MODULE_ENABLED
//#define HAL_USART_MODULE_ENABLED
//#define HAL_IRDA_MODULE_ENABLED
//#define HAL_SMARTCARD_MODULE_ENABLED
//#define HAL_WWDG_MODULE_ENABLED
#define HAL_PCD_MODULE_ENABLED
//#define HAL_HCD_MODULE_ENABLED
//#define HAL_DSI_MODULE_ENABLED
//#define HAL_QSPI_MODULE_ENABLED
//#define HAL_QSPI_MODULE_ENABLED
//#define HAL_CEC_MODULE_ENABLED
//#define HAL_FMPI2C_MODULE_ENABLED
//#define HAL_SPDIFRX_MODULE_ENABLED
//#define HAL_DFSDM_MODULE_ENABLED
//#define HAL_LPTIM_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED

Una vez hecho esto, el proyecto está preparado para escribir nuestro programa.

Edición del programa principal
  • Lo primero que necesitamos hacer es incluir el archivo correspondiente al acelerómetro en nuestro proyecto. Esto debemos hacerlo en la sección de código de usuario de includes.
  • Definiremos una variable global para decidir cuando deben transmitirse los datos por usb. Será necesario añadir la palabra clave volatile en la definición, para que no se haga ningún tipo de optimización con la variable. Se llamará send_data y se inicializará a cero.
  • El programa principal debe contener una función de callback para el evento de actualización del timer6. Esta función será la encargada de poner a uno la variable send_data.
  • En el bucle infinito, se testeará si la variable send_data se encuentra a uno. En tal caso, se obtendrán los datos del acelerómetro y se transmitirán al ordenador a través del protocolo de comunicaciones de la clase CDC usb (por defecto a 9600, 8n1) y se pondrá a cero la variable send_data.
  • Se añadirá código adicional para encender y apagar los leds de forma secuencial cada vez que se produce una transmisión.

Se adjunta el código fuente a continuación:

int main(void)
{
 
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
 
  /* MCU Configuration----------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  MX_USB_DEVICE_Init();
 
  /* USER CODE BEGIN 2 */
 
  HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_SET);
 
  BSP_ACCELERO_Init();
  HAL_TIM_Base_Start_IT(&htim6);
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
 
  /* USER CODE BEGIN 3 */
      if (send_data) {
          int16_t axis[3];
          BSP_ACCELERO_GetXYZ(axis);
 
          char string[50];
          sprintf(string, "X: %d, Y: %d, Z: %d\n\r", axis[0], axis[1], axis[2]);
 
          CDC_Transmit_FS(string, strlen(string));
          send_data = 0;
      }
  }
  /* USER CODE END 3 */
 
}
 
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(HAL_GPIO_ReadPin(GPIOD,LD3_Pin)) {
        HAL_GPIO_TogglePin(GPIOD, LD3_Pin);
        HAL_GPIO_WritePin(GPIOD, LD5_Pin, GPIO_PIN_SET);
    }
    else if (HAL_GPIO_ReadPin(GPIOD,LD4_Pin)) {
        HAL_GPIO_TogglePin(GPIOD, LD4_Pin);
        HAL_GPIO_WritePin(GPIOD, LD3_Pin, GPIO_PIN_SET);
    }
    else if (HAL_GPIO_ReadPin(GPIOD,LD5_Pin)) {
        HAL_GPIO_TogglePin(GPIOD, LD5_Pin);
        HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_SET);
    }
    else if (HAL_GPIO_ReadPin(GPIOD,LD6_Pin)) {
        HAL_GPIO_TogglePin(GPIOD, LD6_Pin);
        HAL_GPIO_WritePin(GPIOD, LD4_Pin, GPIO_PIN_SET);
    }
 
    send_data = 1;
}
/* USER CODE END 4 */

NOTA: Para facilitar la legibilidad del código, se han obviado las funciones de inicialización y sólo se han incluido las funciones main y HAL_TIM_PeriodElapsedCallback.

Ejecución y depuración de la aplicación

Para depurar la aplicación, debemos hacer clic en el icono de debug:

Se nos llevará a una ventana para seleccionar la forma de depuración de la aplicación. Seleccionamos Ac6 STM32 C/C++ Application. Posteriormente, tras unos segundos habremos entrado en el modo de depuración. En este modo tendremos los siguientes controles:

Por último, la salida en la terminal de los datos recibidos por el usb, utilizando minicom o putty, será del tipo:

X: 28, Y: -48, Z: 1045
X: 26, Y: -50, Z: 1028
X: 25, Y: -50, Z: 1044
X: 46, Y: -51, Z: 1024
X: 26, Y: -51, Z: 1026
X: 25, Y: -51, Z: 1027
X: 37, Y: -50, Z: 1028
X: 35, Y: -49, Z: 1027
X: 36, Y: -51, Z: 1028
X: 35, Y: -51, Z: 1028
X: 34, Y: -52, Z: 1026
X: 36, Y: -48, Z: 1021
X: 34, Y: -51, Z: 1027
X: 33, Y: -51, Z: 1028
X: 36, Y: -51, Z: 1027
X: 34, Y: -53, Z: 1027
X: 35, Y: -51, Z: 1027
X: 33, Y: -51, Z: 1027
X: 32, Y: -50, Z: 1027
X: 33, Y: -51, Z: 1027
X: 35, Y: -50, Z: 1027
X: 35, Y: -51, Z: 1028
X: 24, Y: -40, Z: 1026
  • pub/dglab_stm32.txt
  • Última modificación: 2020/09/28 11:44
  • (editor externo)