Punto de partida
Se parte del compilador gcc, las binutils y la newlib compilados para el target “arm-none-eabi” detallados en este post y del trabajo realizado anteriormente en este otro post.
DAC
El DAC del microcontrolador Freescale MK20DX256 tiene una resolución de 12 bits (sin signo) y puede ser utilizado tanto de forma sencilla como mediante DMA. En este caso se va a optar por un uso sencillo sin DMA: La escritura de los datos de las muestras la hará el propio código del programa.
... #define SIM_SCGC2 *((uint32_t *) 0x4004802C) #define DACDAT *((uint16_t *) 0x400CC000) #define DAC0_C0 *((uint8_t *) 0x400CC021) #define DAC0_C1 *((uint8_t *) 0x400CC022) ... SIM_SCGC2 |= (1 << 12); // habilitar el generador de reloj para el DAC DAC0_C1 = 0x00; // deshabilitar el modo DMA para el DAC DAC0_C0 = 0xC0; // habilitar el DAC para VREF2 (3.3v) // a partir de ahora ya se puede escribir en el DAC (DACDAT) ...
Hay que tener en cuenta que el registro DACDAT es un registro de 12 bits sin signo (unsigned).
Systick
La interrupción “systick” es una de las interrupciones estándar del núcleo ARM Cortex-M4 (de hecho está presente en todos los procesadores ARM Cortex). Se trata de una interrupción que se dispara cuando un contador de 24 bits llega a cero, dicho contador está gobernado por el reloj del núcleo (cuidado, suele ser diferente al reloj del bus) y carece de divisores (es muy simple).
El vector de la interrupción se encuentra en la dirección de memoria 0x0000003C. En esta dirección de memoria debe alojarse la dirección de memoria donde se encuentre la función que se ejecutará cada vez que el systick llegue a cero y vuelva a cargarse (una indirección).
Para implementar esta funcionalidad con el GCC se modifica el linker script (teensy31.ld) para incluir el nuevo vector de interrupción:
... . = 0x00000000 ; .cortex_m4_vectors : { LONG(0x20007FFC); LONG(0x00000411); } . = 0x0000003C ; .cortex_m4_vector_systick : { LONG(SYSTICK_ADDRESS + 1); } . = 0x00000400 ; .flash_configuration : { LONG(0xFFFFFFFF); LONG(0xFFFFFFFF); LONG(0xFFFFFFFF); LONG(0xFFFFFFFE); } ...
Y para incluir una nueva sección dentro de la memoria de programa con una dirección de memoria prefijada:
... SYSTICK_ADDRESS = . ; .systick : { *(.systick) } ...
Nótese que la dirección de memoria almacenada en 0x0000003C es la siguiente dirección impar después de SYSTICK_ADDRESS. Esto tiene una explicación y es muy sencilla:
Los procesadores ARM soportan dos repertorios de instrucciones: un repertorio muy amplio y potente en el que cada instrucción ocupa 32 bits (modo “arm”) y otro repertorio más reducido en el que cada instrucción ocupa 16 bits (modo “thumb”). El primero es más potente pero ocupa más, mientras que el segundo en menos potente pero ocupa mucho menos. Lo que se puede hacer en modo “arm” se puede hacer también en modo “thumb” aunque es posible que para hacer lo que hace una instrucción “arm” sean necesarias dos o tres instrucciones “thumb”.
La forma en que un procesador ARM sabe si una instrucción a la que apunta el PC forma parte de un repertorio de instrucciones u otro es mediante el bit 0 del PC. Si el bit 0 vale 0, se trata de una instrucción “arm”, mientras que si el bit vale 1 se trata de una instrucción “thumb” (nótese que sea cual sea el modo, todas las instrucciones se encuentran, como mínimo, en direcciones pares, en las direcciones impares nunca hay instrucciones).
Por otro lado según la especificación ARM, las excepciones (interrupciones) se deben ejecutar siempre en modo “thumb”. De todas formas en este caso no tenemos elección ya que la serie Cortex-M de ARM sólo soporta el repertorio de instrucciones “thumb” (http://infocenter.arm.com/help/index.js ... BIBGJ.html).
Con la nueva sección de código llamada “.systick” en el código fuente puede ahora definirse la función que va a manejar la interrupción:
... #define SYST_CSR *((uint32_t *) 0xE000E010) #define SYST_RVR *((uint32_t *) 0xE000E014) #define SYST_CVR *((uint32_t *) 0xE000E018) #define SAMPLE_RATE 44100 // indicamos al compilador que queremos alojar el cuerpo de esta función en la sección “.systick” void systick() __attribute__ ((section(".systick"))); void systick() { // TODO } ... ... // configuramos el systick para que se ejecute SAMPLE_RATE veces por segundo SYST_RVR = F_CPU / SAMPLE_RATE; SYST_CVR = 0; SYST_CSR |= 0x07; ...
F_CPU es la velocidad en Hz del núcleo (en este caso 96 MHz = 96000000 Hz) y hacemos que el systick se ejecute 44100 veces por segundo (la frecuencia de muestreo del sonido a reproducir).
No se debe utilizar el atributo “interrupt” al declarar la función “systick” ya que en ese caso el compilador intenta compilarla en modo “arm” en lugar de “thumb”.
Sonido
Partiendo de uno de los sonidos (un bucle de bateria) publicado con licencia Creative Commons Attribution-ShareAlike por el usuario de Soundcloud “Phantom Hack3r” (AKA Loop Studio, https://soundcloud.com/phantom-hack3r) se ha editado, se ha dejado sólo con un único compás (el inicial) y se ha exportado a WAV (“drum_loop_1.wav”).
A continuación, usando la herramienta de línea de comandos, “sox” se exporta a su vez este fichero WAV a un formato crudo de 8 bits, mono y sin signo:
sox drum_loop_1.wav -u -b 8 -c 1 -r 44100 drum_loop_1.rawLuego el fichero drum_loop_1.raw se convierte a un fichero objeto para meterlo como si fuese código dentro del microcontrolador:
/opt/teensy/bin/arm-none-eabi-objcopy --input binary --output elf32-littlearm --binary-architecture arm --rename-section .data=.text drum_loop_1.raw drum_loop_1.oLa opción “--rename-section .data=.text” es muy importante ya que marca los datos generados para que se alojen en la sección “.text” del fichero de salida. Esta sección es la sección que será alojada en la memoria flash del Teensy.
Ahora en “drum_loop_1.o” hay definidas dos variables “_binary_drum_loop_1_raw_start” y “_binary_drum_loop_1_raw_end” cuya dirección de memoria es el inicio y el final respectivamente de los datos crudos convertidos (“drum_loop_1.raw”).
Circuito de salida
A la hora de conectar la salida analógica del DAC a unos altavoces hay que hacerlo siempre a través de un amplificador ya que la corriente máxima que soporta la salida DAC es muy baja. De entre todas las opciones de amplificación, la más sencilla es, sin duda, el uso de unos altavoces amplificados de PC (solución sugerida por el propio creador del Teensy, Paul Stoffregen, aquí).
Se trata de un sencillo condensador electrolítico (para el desacoplo de continua) entre la salida del DAC y la entrada del amplificador de altavoces.
Resultado final
El código fuente final de main.cc es el siguiente:
#include <stdint.h> using namespace std; #define SYST_CSR *((uint32_t *) 0xE000E010) #define SYST_RVR *((uint32_t *) 0xE000E014) #define SYST_CVR *((uint32_t *) 0xE000E018) #define SIM_SCGC2 *((uint32_t *) 0x4004802C) #define DACDAT *((uint16_t *) 0x400CC000) #define DAC0_C0 *((uint8_t *) 0x400CC021) #define DAC0_C1 *((uint8_t *) 0x400CC022) #define SAMPLE_RATE 44100 extern char _binary_drum_loop_1_raw_start; extern char _binary_drum_loop_1_raw_end; volatile char *p; void systick() __attribute__ ((section(".systick"))); void systick() { DACDAT = ((uint16_t) *p) << 4; p++; if (p == &_binary_drum_loop_1_raw_end) p = &_binary_drum_loop_1_raw_start; } int main() { // configure DAC SIM_SCGC2 |= (1 << 12); // enable DAC clock generator DAC0_C1 = 0x00; // disable DAC DMA DAC0_C0 = 0xC0; // enable DAC for VREF2 (3.3v) // configure SYSTICK p = &_binary_drum_loop_1_raw_start; SYST_RVR = F_CPU / SAMPLE_RATE; SYST_CVR = 0; SYST_CSR |= 0x07; while (1) ; }
A continuación un vídeo donde puede verse (y oírse) el montaje en funcionamiento.
Todo el código fuente puede descargarse de la sección soft.
[ añadir comentario ] ( 1680 visualizaciones ) | [ 0 trackbacks ] | enlace permanente
Tweet | |
En vista de la repentina desaparición del contenido de la web http://kunen.org/uC/gnu_tool.html (“Building the GNU ARM Toolchain for Bare Metal”) que se usó como referencia en este post y en la que se explicaba con gran detalle y facilidad cómo compilar la toolchain de GNU para el target arm-none-eabi, he decidido poner aquí los pasos que di yo (basados en los ahora desaparecidos) para compilar dicha toolchain (yo lo hice para las versiones binutils-2.25, gcc-5.1.0 y snapshot 20150423 de newlib).
Me he basado en los pasos descritos en la web desaparecida y en la web http://wiki.osdev.org/GCC_Cross-Compiler#GCC.
Hay que asegurarse de que se encuentran instalados los paquetes: textinfo, zlib-devel, flex, bison y el resto de herramientas de compilación (gcc, autoconf, etc.). Es recomendable hacer todos los pasos como usuario root.
Descarga
mkdir -p /opt/teensy/src
cd /opt/teensy/src
binutils 2.25
wget ftp://ftp.gnu.org/gnu/binutils/binutils-2.25.tar.bz2
tar xjf binutils-2.25.tar.bz2
gcc 5.1.0
wget ftp://ftp.gnu.org/gnu/gcc/gcc-5.1.0/gcc-5.1.0.tar.bz2
wget https://gmplib.org/download/gmp/gmp-6.0.0a.tar.bz2
wget http://www.mpfr.org/mpfr-current/mpfr-3.1.2.tar.bz2
wget ftp://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gz
wget http://isl.gforge.inria.fr/isl-0.14.tar.bz2
tar xjf gcc-5.1.0.tar.bz2
tar xjf gmp-6.0.0a.tar.bz2
tar xjf isl-0.14.tar.bz2
tar xzf mpc-1.0.3.tar.gz
tar xjf mpfr-3.1.2.tar.bz2
mv gmp-6.0.0 gcc-5.1.0/gmp
mv isl-0.14 gcc-5.1.0/isl
mv mpc-1.0.3 gcc-5.1.0/mpc
mv mpfr-3.1.2 gcc-5.1.0/mpfr
newlib
wget ftp://sourceware.org/pub/newlib/newlib-2.2.0.20150423.tar.gz
tar xzf newlib-2.2.0.20150423.tar.gz
Compilación
binutils 2.25
mkdir -p /opt/teensy/build/binutils-2.25
cd /opt/teensy/build/binutils-2.25
../../src/binutils-2.25/configure --target=arm-none-eabi --prefix=/opt/teensy --enable-interwork --enable-multilib
make
make install
export PATH="/opt/teensy/bin:$PATH"
gcc 5.1.0 (inicial)
mkdir -p /opt/teensy/build/gcc-5.1.0
cd /opt/teensy/build/gcc-5.1.0
../../src/gcc-5.1.0/configure --target=arm-none-eabi --prefix=/opt/teensy --enable-interwork --enable-multilib --enable-languages="c,c++" --with-newlib --with-headers=../../src/newlib-2.2.0.20150423/newlib/libc/include --with-system-zlib
make all-gcc
make install-gcc
newlib
mkdir -p /opt/teensy/build/newlib
cd /opt/teensy/build/newlib
../../src/newlib-2.2.0.20150423/configure --target=arm-none-eabi --prefix=/opt/teensy --enable-interwork --enable-multilib
make
make install
gcc 5.1.0 (final)
cd /opt/teensy/build/gcc-5.1.0
make all
make install
Tras completar todo el proceso, el compilador, las binutils y la newlib para ARM quedan instaladas en la carpeta /opt/teensy.
[ añadir comentario ] ( 1255 visualizaciones ) | [ 0 trackbacks ] | enlace permanente
Tweet | |
Teensy3 es una pequeña placa de desarrollo que se integra perfectamente en el entorno de desarrollo de Arduino y que incluye un potente procesador ARM Cortex-M4 a 96 MHz con 256Kb de Flash, 64Kb de RAM, DAC, ADC, CAN y otros periféricos. A lo largo de este artículo se explicará paso a paso cómo construir y configurar desde cero la toolchain de GNU para programar el Teensy3 en C++.
Compilar la toolchain de GNU
En mi caso he utilizado una placa Teensy 3.1. El corazón de esta placa es el microcontrolador de 32 bits MK20DX256VLH7, un ARM Cortex-M4 a 96MHz. Lo primero que hay que hacer es compilar la toolchain en sí para el target arm-none-eabi. Uno de los mejores tutoriales de compilación dde la toolchain de GNU para ARM es el publicado aquí. En mi caso he utilizado las binutils 2.24, el gcc 4.9.0 y la última versión de newlib disponible por CVS.
El tutorial anterior utiliza una versión de gcc antigua y no echa en falta ninguna librería, sin embargo, si queremos compilar el gcc 4.9.0 hemos de incluir las librerías GMP, MPFR y MPC. En este enlace se indica cómo incluir las librerías en el código fuente del gcc 4.9.0 para que se compilen junto con el gcc.
Una vez compilada la toolchain tendremos el compilador de C++ y el resto de herramientas (ensamblador, enlazador, etc.) disponibles en la carpeta de prefijo de instalación que hayamos utilizado. En mi caso he usado /opt/teensy.
El microcontrolador MK20DX256
Este microcontrolador está basado en un núcleo ARM Cortex-M4. La secuencia de arranque de estos procesadores se describe aquí. De forma resumida, en la direción de memoria 0x00000000 se encuentra el valor que se le asigna al puntero de pila en el arranque, mientras que en la dirección 0x00000004 se encuentra la dirección de memoria donde se ejecuta la primera instrucción, hay más vectores, aunque estos dos son los más importantes. Como se puede ver, no se trata de instrucciones, sino de una tabla de punteros.
Los 256Kb de Flash comienzan a partir de la dirección 0x00000000 mientras que los 64Kb de RAM van desde 0x1FFF8000 hasta 0x20008000. La pila del Cortex-M4 puede configurarse de cualquier forma (ascendente, descendente, etc.). Sin embargo las instrucciones “push” y “pop” por defecto trabajan con una pila descendente (mas información aquí). De esta forma nuestro mapa de memoria queda como sigue:
offset tamaño uso
0x00000000 4 bytes puntero de pila en el arranque
0x00000004 4 bytes puntero a la primera instrucción tras el reset
0x00000400 16 bytes palabras de configuración del microcontrolador
0x00000410 261104 bytes código
0x1FFF8000 32768 bytes variables globales
0x20000000 32768 bytes pila y variables locales
Al ser una pila descendente, el puntero de pila se inicializa con 0x20007FFC, que es la última palabra de 32 bits direccionable en la RAM. Con este mapa de memoria elaboramos el siguiente linker script:
SECTIONS { . = 0x00000000 ; .cortex_m4_vectors : { LONG(0x20007FFC); LONG(0x00000411); } . = 0x00000400 ; .flash_configuration : { LONG(0xFFFFFFFF); LONG(0xFFFFFFFF); LONG(0xFFFFFFFF); LONG(0xFFFFFFFE); } .text : { _linker_code = . ; init.o (.text) *(.text) *(.text.*) *(.rodata*) *(.gnu.linkonce.t*) *(.gnu.linkonce.r*) } .preinit_array : { __preinit_array_start = . ; *(.preinit_array) __preinit_array_end = . ; } .init_array : { __init_array_start = . ; *(.init_array) __init_array_end = . ; } .fini_array : { __fini_array_start = . ; *(.fini_array) __fini_array_end = . ; } .ctors : { __CTOR_LIST__ = . ; LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2) *(.ctors) LONG(0) __CTOR_END__ = . ; } .dtors : { __DTOR_LIST__ = . ; LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2) *(.dtors) LONG(0) __DTOR_END__ = . ; } flash_sdata = . ; . = 0x1FFF8000 ; ram_sdata = . ; .data : AT (flash_sdata) { _linker_data = . ; *(.data) *(.data.*) *(.gnu.linkonce.d*) } ram_edata = . ; data_size = ram_edata - ram_sdata; ram_sbssdata = . ; .bss : AT (LOADADDR(.data) + SIZEOF(.data)) { _linker_bss = . ; *(.bss) *(.bss.*) *(.gnu.linkonce.b.*) *(.COMMON) } ram_ebssdata = . ; bssdata_size = ram_ebssdata - ram_sbssdata; _linker_end = . ; }
El linker script tiene las secciones habituales: .text, .init_array, .fini_array, .ctors, .dtors, .data, .bss, etc. más dos secciones adicionales: .cortex_m4_vectors y .flash_configuration.
Dirección de carga y dirección virtual en el linker script
Cuando se escribe un linker script para un microcontrolador o para un sistema basado en memoria Flash hay que tener en cuenta que las secciones .data y .bss (datos inicializados y sin inicializar, respectivamente) se cargan inicialmente en Flash y que, en el momento de la ejecución, dichas secciones deben copiarse a RAM. El linker script permite definir direcciones de carga y direcciones virtuales para cada sección de forma independiente con lo que es posible hacer de forma sencilla esta funcionalidad:
flash_sdata = . ;
. = 0x1FFF8000 ;
ram_sdata = . ;
.data : AT (flash_sdata) {
*(.data)
}
ram_edata = . ;
data_size = ram_edata - ram_sdata ;
Antes de definir la sección .data guardamos en la variable “flash_sdata” la dirección virtual actual, definimos la dirección virtual actual como 0x1FFF8000 (inicio de la parte de la RAM que hemos decidido que aloje las variables globales) y guardamos en “ram_sdata” esta dirección virtual. A continuación definimos la sección .data con el atributo “AT (flash_sdata)” para indicarle al linker que queremos que se cargue en la dirección “flash_sdata”, que es una dirección dentro de la memoria Flash, aunque la dirección virtual será 0x1FFF8000. Tras la definición de la sección .data generamos símbolos para calcular el tamaño de la sección. Con la sección .bss (datos globales sin inicializar) hacemos lo mismo.
Código de inicialización
El linker script nos ha permitido definir las secciones .data y .bss con direcciones virtuales y de carga diferentes, sin embargo la copia de los datos habrá que hacerla a mano antes de la ejecución de la función “main”.
Este fichero contiene el código de inicialización en ensamblador que vamos a utilizar.
.syntax unified .thumb .thumb_func .global _startup .section .text _init: mov r0,#0 mov r1,#0 mov r2,#0 mov r3,#0 mov r4,#0 mov r5,#0 mov r6,#0 mov r7,#0 mov r8,#0 mov r9,#0 mov r10,#0 mov r11,#0 mov r12,#0 /* disable interrupts */ CPSID i /* unlock_watchdog */ ldr r6, = 0x4005200e /* WDOG_UNLOCK doc: K20P64M50SF0RM.pdf ( Page: 423 ) */ ldr r0, = 0xc520 strh r0, [r6] ldr r0, = 0xd928 strh r0, [r6] /* disable_watchdog */ ldr r6, = 0x40052000 /* WDOG_STCTRLH doc: K20P64M50SF0RM.pdf ( Page: 418 ) */ ldr r0, = 0x01d2 strh r0, [r6] /* enable interrupts */ CPSIE i /* copy data section from flash to ram */ ldr r0, = flash_sdata ldr r1, = ram_sdata ldr r2, = data_size cmp r2, #0 beq skip_copy copy: ldrb r4, [r0], #1 strb r4, [r1], #1 subs r2, r2, #1 bne copy skip_copy: /* fill bssdata section with zeros */ ldr r1, = ram_sbssdata ldr r2, = bssdata_size cmp r2, #0 beq skip_fill mov r4, #0 fill: strb r4, [r1], #1 subs r2, r2, #1 bne fill skip_fill: b _startup .end
Como se puede ver: Inicializamos los registros del procesador, deshabilitamos las interrupciones, desactivamos el watchdog del MK20 (hay que hacerlo dentro de los primeros 256 ciclos del procesador tras el reset), copiamos los datos de la sección .data desde la Flash a la RAM y rellenamos con ceros la sección .bss. Cuando terminamos de rellenar con ceros la sección .bss saltamos a la función “_startup”.
La función "_startup" la definimos en un fichero de C++:
#include <stdint.h> using namespace std; extern "C" { extern void (*__CTOR_LIST__)(); extern void (*__DTOR_LIST__)(); extern void (*__init_array_start)(); extern void (*__init_array_end)(); extern void (*__fini_array_start)(); extern void (*__fini_array_end)(); } void callInitArray() { void (**f)() = &__init_array_start; while (f != &__init_array_end) { (*f)(); f++; } } void callConstructors() { void (**constructor)() = &__CTOR_LIST__; uint32_t total = *(uint32_t *) constructor; constructor++; while (total) { (*constructor)(); total--; constructor++; } } void callDestructors() { void (**destructor)() = &__DTOR_LIST__; uint32_t total = *(uint32_t *) destructor; destructor++; while (total) { (*destructor)(); total--; destructor++; } } void callFiniArray() { void (**f)() = &__fini_array_start; while (f != &__fini_array_end) { (*f)(); f++; } } extern int main(); void _startup() asm("_startup"); void _startup() { callConstructors(); callInitArray(); main(); callFiniArray(); callDestructors(); while (true) ; } extern "C" void __cxa_pure_virtual() {} void *__dso_handle = 0; extern "C" void __cxa_atexit() {}
La función “_startup” se encarga de invocar a los constructores globales y a la función “main”. El fichero Makefile que se utiliza es el siguiente:
BIN_PREFIX=/opt/teensy/bin/ CXX=${BIN_PREFIX}arm-none-eabi-g++ OBJCOPY=${BIN_PREFIX}arm-none-eabi-objcopy CXX_FLAGS=-mtune=cortex-m4 -mthumb -fno-exceptions -fno-rtti -nostdlib -nodefaultlibs -nostartfiles main.hex: main.elf $(OBJCOPY) -O ihex -R .eeprom main.elf main.hex main.elf: main.o init.o startup.o $(CXX) $(CXX_FLAGS) -Wl,-Tteensy31.ld -o main.elf main.o init.o startup.o main.o: main.cc $(CXX) $(CXX_FLAGS) -c -o $@ $< startup.o: startup.cc $(CXX) $(CXX_FLAGS) -c -o $@ $< init.o: init.s $(CXX) $(CXX_FLAGS) -c -o $@ $< clean: rm -f *.elf *.o *.hex *.bin
Prueba de concepto
En este caso simplemente he implementado la típica funcionalidad de led parpadeante (para variar xD)
#include <stdint.h> using namespace std; #define GPIO_ENABLE (1 << 8) #define PULL_UP_ENABLE (1 << 1) #define PULL_UP_SELECT (1 << 0) #define DRIVE_STR (1 << 6) #define PORT_CTRL_FLAGS (DRIVE_STR | GPIO_ENABLE | PULL_UP_ENABLE | PULL_UP_SELECT) #define PORTC_PCR5 *((uint32_t *) 0x4004B014) #define GPIOC_PDDR *((uint32_t *) 0x400FF094) #define GPIOC_PDOR *((uint32_t *) 0x400FF080) #define SIM_SCGC5 *((uint32_t *) 0x40048038) #define WDOG_UNLOCK *((uint32_t *) 0x4005200E) #define WDOG_STCTRLH *((uint32_t *) 0x40052000) #define CLOCKS_ACTIVE_TO_ALL_GPIO 0x00043F82 #define WATCHDOG_UNLOCK_VALUE_1 0xC520 #define WATCHDOG_UNLOCK_VALUE_2 0xD928 #define WATCHDOG_DISABLE_VALUE 0x01D2 class Clase { public: uint32_t n; uint32_t v1, v2; Clase(uint32_t x1, uint32_t x2, uint32_t aux); }; Clase::Clase(uint32_t x1, uint32_t x2, uint32_t aux) { this->v1 = x1; this->v2 = x2; this->n = aux; } Clase objeto(1, 2, 100000); uint32_t a = 50000; //uint32_t a = 100000; int main() { // configure PTC5 SIM_SCGC5 = CLOCKS_ACTIVE_TO_ALL_GPIO; PORTC_PCR5 = PORT_CTRL_FLAGS; // PORTC is output GPIOC_PDDR = (uint32_t) 0xFFFFFFFF; while (1) { volatile uint32_t i; // PORTC to 1 GPIOC_PDOR = (uint32_t) 0xFFFFFFFF; for (i = 0; i < objeto.n; i++) ; // PORTC to 0 GPIOC_PDOR = (uint32_t) 0x00000000; for (i = 0; i < objeto.n; i++) ; } }
El código fuente puede descargarse de la sección soft.
ACTUALIZACIÓN: En la sección "Compilar la toolchain de GNU" se incluye un enlace a una web que ya no se encuentra disponible (http://kunen.org/uC/gnu_tool.html). En dicha web se indicaban los pasos pormenorizados para compilar toda la toolchain de GNU. Afortunadamente he podido rescatar gracias al histórico de mi ordenador los pasos que seguí yo guiándome por esa web y los he recompilado es este post.
ACTUALIZACIÓN: La versión anterior no rellenaba con ceros la sección .bss de la RAM de datos globales. Se ha incluido la corrección en la descripción del texto, en el código mostrado en el post y en el fichero tar.gz de la sección soft.
[ añadir comentario ] ( 1886 visualizaciones ) | [ 0 trackbacks ] | enlace permanente
Tweet | |