ARM: tekstas į USART’ą

Posted: 2015-09-23 in Darbeliai
Žymos:,

Kadangi vienas toks bandantis kažkaip maskuotis subjektas užklausė, ar man pavyko kažką išspausdinti iš ARM procesoriuko į GDB serverio konsolę, tai prisiminiau, kad buvau pradėjęs rašyti tekstuką apie teksto išvedimą į USART. Tačiau kai kas atminty uždulkėjo, o kai ko taip iki galo ir nesupratau. Bet pasidalinsiu tuo, ką turiu.

Taigiz. Netgi tas primityvus STM32F103C8T6 procesoriukas yra tiek naglas, kad turi cielus tris USART’us. Palyginkite tai su visokiom Atmegom…

Šiaip buvau radęs kažkokį pavyzduką, kaip visokį debugą spausdinti atgal per JTAG (ar SWD) į GDB serverio konsolę. Bet taip iki jo ir neprisikasiau. Bet va paprastą serijinį spausdinimą į konsolę panorau išbandyti. Tam reikia turėti USART keitiklį ir jį prisijungti prie atitinkamų kojų. Aš apie tai rašiau rašliavoj, kaip per nuosekliąją jungtį įkėlinėti firmwares. Taigi šitam bandymui setupas yra toks pat.

Dabar toliau. Yra variantas, kaip printf funkcijos išvestį nukreipti į USART’ą. Vienas iš jų — perrašyti fputc funkciją, kad siųstų viską į USART1:

#include "stdio.h"
// Nukreipiam fputc į USART:

int fputc(int ch, FILE *f) //FILE *f) {
    // Resetinam IWD 
    IWDG_ReloadCounter();
    // Rašom vieną simboliuką į USART
    USART_SendData(USART1, (u8) ch);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {
        // Vėl resetinam IWD
        IWDG_ReloadCounter();
    }
    // Ir vėl...
    IWD IWDG_ReloadCounter();
    return ch;
}

Ir kaip ir viskas. Dabar kode inicializavus USART1 ir panaudojus printf tekstas turėtų keliauti į USART-USB keitiklį. Linux aš naudoju screen komandą bendravimui su įvairiais nuosekliaisiais prievadais, o ką naudoja Windows vartotojai… reikia kolegų pasiklaust.

Kolegos atsakė, kad naudoja putę. Na, PuTTY 🙂

Dabar kitas reikalas. Aš pamėginau perrašyti fputc, man kažkas nepavyko. Kadangi reikalas vyko seniai, jau nepeprikeliu, kas ten buvo. Todėl nusprendžiau paimt ir pasirašyt į printf panašiai veikiančią funkciją pats sau. Tokią pusiau universalią, kad spausdintų nebūtinai į USART1, bet į bet kurį prievadą. Kodas gavosi maždaug toks:

int print_uart (USART_TypeDef* USARTx, char *ptr)
{
	int i = 0;
	while (*ptr) {
		IWDG_ReloadCounter();
		// Siunčiam simboliuką į USARTą
		USART_SendData(USARTx, *ptr);
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET) {
			// Resetinam IWD
			IWDG_ReloadCounter();
		}
		IWDG_ReloadCounter();
		i++;
		ptr++;
	}
	return i;
}

Viskas su tais pačiais resetinimais ir kitais daiktais. Tik kad šita funkcija visą eilutę išspausdina.

Viskas ok, suveikė šitas daiktas. Tada aš pagalvojau, kad išvedinėti tik vien tik tekstą, aišku, neįdomu. Norisi kokį nors kintamuką įdėti. Juk viso šito reikalo esmė — kažkoks tai grįžtamasis ryšys iš procesoriuko, ne vien tik mirksiukas. Norisi ir teksto, ir kintamųjų pamatyti. Aišku, galima daug išsidebuginti pert JTAG/SWD, bet ne taip smagu, kaip žiūrėti į konsolėj besivyniojantį tekstą. Sutinkate?

Ką gi, šita mano funkcija nemoka formatuoti teksto, tik jį išvedinėti. Kas daryt?

Daryt reikia šitaip: naudoti sptrintf funkciją formatavimui į buferį, o paskui tą buferį išvedinėti. Ok, bandom.

Ir, blė, šūds ant pagaliuko, linkeris apsispjaudo:

Building target: ARM_SERIAL2.elf
Invoking: Cross ARM C++ Linker
arm-none-eabi-g++ -mcpu=cortex-m3 -mthumb -O0 -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -Wall  -g3 -T mem.ld -T sections.ld -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"ARM_SERIAL2.map" --specs=rdimon.specs -Wl,--start-group -lgcc -lc -lc -lm -lrdimon -Wl,--end-group -o "ARM_SERIAL2.elf"  ./src/main.o ./src/milli-timer.o ./src/stm32f10x_it.o ./src/systick-utils.o  ./libs/StdPeriph/src/misc.o ./libs/StdPeriph/src/stm32f10x_adc.o ./libs/StdPeriph/src/stm32f10x_bkp.o ./libs/StdPeriph/src/stm32f10x_can.o ./libs/StdPeriph/src/stm32f10x_cec.o ./libs/StdPeriph/src/stm32f10x_crc.o ./libs/StdPeriph/src/stm32f10x_dac.o ./libs/StdPeriph/src/stm32f10x_dbgmcu.o ./libs/StdPeriph/src/stm32f10x_dma.o ./libs/StdPeriph/src/stm32f10x_exti.o ./libs/StdPeriph/src/stm32f10x_flash.o ./libs/StdPeriph/src/stm32f10x_fsmc.o ./libs/StdPeriph/src/stm32f10x_gpio.o ./libs/StdPeriph/src/stm32f10x_i2c.o ./libs/StdPeriph/src/stm32f10x_iwdg.o ./libs/StdPeriph/src/stm32f10x_pwr.o ./libs/StdPeriph/src/stm32f10x_rcc.o ./libs/StdPeriph/src/stm32f10x_rtc.o ./libs/StdPeriph/src/stm32f10x_sdio.o ./libs/StdPeriph/src/stm32f10x_spi.o ./libs/StdPeriph/src/stm32f10x_tim.o ./libs/StdPeriph/src/stm32f10x_usart.o ./libs/StdPeriph/src/stm32f10x_wwdg.o  ./libs/CMSIS/src/core_cm3.o ./libs/CMSIS/src/startup_stm32f10x.o ./libs/CMSIS/src/system_stm32f10x.o   
/media/VIETA/ARM/gcc-arm-none-eabi-4_8-2014q2/bin/../lib/gcc/arm-none-eabi/4.8.4/../../../../arm-none-eabi/bin/ld: section .ARM.exidx loaded at [080056c4,080056cb] overlaps section .data loaded at [080056c4,08005f4b]
collect2: error: ld returned 1 exit status
make: *** [ARM_SERIAL2.elf] Error 1

Jeigu manote, kad čia labai aišku, kas pasidarė, tai nichuja. Niekas čia neaišku, ypač tokiam žaliam ARMininkui, kaip aš. Tačiau vienok užtaigi. Yra internetas!

Pradžiai aš pradėjau aiškintis, kas čia iš viso per keiksmažodžiai, kad ir tas .ARM.exidx. Nagi labai lengva kodyt visokius Arduinus ar pusiau gatavus ARM devboardus su paruoštais toolkit’ais. O kartais va tokie neaiškūs errorai priverčia šiek tiek pasidomėti, kaip ten viskas yra giliau. Pavyzdžiui, dėl panašių problemų aš išsiaiškinau, kaip organizuojamos AVR firmwarės, kokiais adresais saugomi PROGMEM duomenukai, kur prasideda ir baigiasi bootloader’is ir pan. Pas ARM viskas gerokai sudėtingiau, nes ir architektūra sudėtingesnė. Bet esmė ta pati: galutinis BIN, HEX ar ELF failiukas turi tam tikrą struktūrą, įvairūs kodo, konstantų ir kitokių duomenų kurkulai nugula į tam tikras vietas.

Kas čia gavosi per klaida man? Ogi gavosi taip, kad ganėtinai įmantri sprintf funkcija man į firmwarę prikišo papildomų dalykų. Konkrečiai jai prireikė backtrace’ų,backtrace’ai atitinkamai naudoja .ARM.exidx firmwarės zoną. Na, o dabar spėkite, kaip yr: standartinis sections.ld failiukas STM32 toolkit’e neturi šios sekcijos aprašytos. Žinokit, rankos biškį drebėjo po tą failiuką kapstantis ir bandant kažkaip kažką įdėti, kas man pačiam nelabai aišku. Bet visgi pavyko, įgrūdau tą .ARM.exidx zoną, kad ją įkompiliuotų tarp .flashtext ir .text zonų. Įterpiau va tokį gabaliuką:

    .ARM.exidx : {
    __exidx_start = .;
    *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    __exidx_end = .;
    } >FLASH

Jeigu jumi čia nieks neaišku, nepergyvenkite: man irgi.

Papildymas: kad visokie backtrace’ai nebūtų naudojami, kompiliatoriui galima perduoti nurodymus -funwind-tables irba* -fexceptions. Tik kiek pamenu, man nepavyko, todėl ėjau apturėti intymių santykių su firmwarės zonomis.

Čia paveiksliukas su diff’u, kur įterpiau tą tekstuką:

.ARM.exidx sekcijos įterpimas bactrace'ams ir pan. | ARM | Darau, blė

Tai va kaip ir tiek. O toliau — viso mano bandymo kodas. Yra ten naudojama MilliTimer biblioteka, kurią pasirašiau pats ir galite ją parsisiųsti iš mano senesnio įrašėlio apie sisteminį laikmatį. Taigi kodas su sprintf:

#include "stm32f10x.h"
#include "systick-utils.h"
#include "milli-timer.h"

#include 

char buf[100];

#define BLINK_PORT      GPIOA
#define BLINK_PIN       GPIO_Pin_0
#define BLINK_RCC_BIT   RCC_APB2Periph_GPIOA

MilliTimer tbp1;
uint8_t blink1 = 0;

int print_uart (USART_TypeDef* USARTx, char *ptr)
{
    int i = 0;
    while (*ptr) {
        IWDG_ReloadCounter();
		// Write a character to the USART
		USART_SendData(USARTx, *ptr);
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET) {
			// Reset IWD
			IWDG_ReloadCounter();
		}
		IWDG_ReloadCounter();
        i++;
        ptr++;
    }
  return i;
}


int main(void)
{
	init_systick_utils();

	tbp1 = new_milli_timer();

	USART_InitTypeDef usartConfig;
	GPIO_InitTypeDef GPIO_InitStructure;

	/* GPIO Periph clock enable */
	RCC_APB2PeriphClockCmd(
				RCC_APB2Periph_USART1 |
				BLINK_RCC_BIT,
			ENABLE);

	/* Configure pin in output push/pull mode */
	GPIO_InitStructure.GPIO_Pin = (1 << BLINK_PIN);
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(BLINK_PORT, &GPIO_InitStructure);

	USART_Cmd(USART1, ENABLE);
	usartConfig.USART_BaudRate = 9600;
	usartConfig.USART_WordLength = USART_WordLength_8b;
	usartConfig.USART_StopBits = USART_StopBits_1;
	usartConfig.USART_Parity = USART_Parity_No;
	usartConfig.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	usartConfig.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_Init(USART1, &usartConfig);

	//PA9 = USART1.TX => Alternative Function Output
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	//PA10 = USART1.RX => Input
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	while (1)
	{
		if (mt_poll(&tbp1, 500)) {
			sprintf(buf, "Yohoho ir mirksiukas pirmas: %d", blink1);
			print_uart(USART1, buf);

			if (blink1) {
				GPIO_ResetBits(BLINK_PORT, (1 << BLINK_PIN));
			} else {
				GPIO_SetBits(BLINK_PORT, (1 << BLINK_PIN));
			}
			blink1 = !blink1;
		}
	}
	return 0;
}

Va ir viskas. Sėkmingų bandymų. Tiesa, pastabėlė, sprintf įgalinimas su .ARM.exidx padidina firmwarės dydį nuo 3236 baitų iki cielų 24832! Daugokoka, bet gerai, kad procesoriukas turi 64 kB firmwarei saugot… todėl visgi ateityje reiktų išsiaiškinti, kaip to .ARM.exidx išvengt. Galbūt pasidaryti kažką panašaus į Arduino be gudrių formatavimų, o tik išvedinėjant atskirus duomenų tipus po vieną.

* — toks gražus lietuviškas ir/arba sutrumpinimas. Iš Skirmanto išmokau.

Advertisements
Komentarai
  1. mechadrake parašė:

    Ar tas bandantis maskuotis kartais nebuvo panašus i arklį? 😉

  2. n\a parašė:

    Visdelto pavyko tam kompleksuojanciam subjektui trace outputa iskakoti i konsole 🙂 Problema pasirodo buvo GDB serveryje… Taip jau susikloste, kad naudojau STLINK GDB Server is http://www.emb4fun.de/archive/stlink/index.html, kuris kiek pavyko isgooglinti nepalaiko semihosting’o, todel ten nieks ir neveike. Perejus ant OpenOCD viskas graziai kakojasi i konsole.
    Dar, jei teisingai suprantu, yra kitas variantas vietoj semihosting’o naudoti ITM (Instrumentation Trace Macro cell), taciau jam reikalingas SWO (Serial Wire Output) pin’as ant debugger’io/programmer’io. Deje pas mane kinietiskas st-link/v2 klonas uz pora $ turi tik SWD ir SWIM tai to pin’o neturi ir isbandyti galimybiu neturiu.

    • Darau, Blė parašė:

      He he 🙂 Aišku tada su GDB. Na, tai prašom pasidalinti veikiančiu projekto pavyzduku 🙂

      • n\a parašė:

        Viskas paprasciau nei paprasta 🙂 Pas mane WIN, bet analogiskai turetu buti ir kitur.
        Taigi paleidziam cmd ir bandom leisti OpenOCD:
        cd C:\OpenOCD\bin && C:\OpenOCD\bin\openocd.exe -s ../share/openocd/scripts -f ../share/openocd/scripts/interface/stlink-v2.cfg -f ../scripts/target/stm32f1x.cfg
        -s raktas reikalingas, kad zinotu is kur load’int “subscriptus”
        Kode includinam Trace.h: #include “diag/Trace.h”
        Einam i projekto Properties, ten i C/C++ General->Paths and Symbols, tada i Symbols tab’a ir ten pridedam TRACE ir OS_USE_TRACE_SEMIHOSTING_DEBUG raktus (kam to reikia galima pasiziureti system/src/diag/trace_impl.c failiuke)
        Subuildinam projekta, sukuriam “GDB Hardware Debugging” tipo debug konfiguracija (hostname: localhost, port: 3333 ir Startup tab’e prie “Initialization Commands” irasom: “monitor arm semihosting enable”)
        main metodo pavyzdelis:
        int
        main(int argc, char* argv[])
        {
        uint8_t i = 0;
        while (1) {
        trace_printf(“i=%u\n”, i++);
        }
        }
        ir viskas! Leidziam debug ir ziurim output’a 🙂

        Kitas siektiek civilizuotesnis budas (kad nereiktu paleidineti atskirai OpenOCD, kad output’as matytusi tiesiai Eclipse konsoleje ir maziau skausmo butu del konfiguravimo) yra suinstaliuoti nauja debug type’a.
        Taigi, Eclipse einam Help->Install New Software… irasom http://gnuarmeclipse.sourceforge.net/updates, gaunam sarasa ir suinstalinam “GNU ARM C/C++ OpenOCD Debugging”
        Dabar norint debuginti reikia nepamirsti projekto nustatymuose prideti TRACE ir OS_USE_TRACE_SEMIHOSTING_DEBUG raktus ir sukurti projektui nauja “GDB OpenOCD Debugging” tipo konfiguracija. Startup tab’e uzdeti varnele ant “Enable ARM semihosting”, o Debugger tab’e i “Config options:” irasyti kazka panasaus: -s C:\OpenOCD\share\openocd\scripts -f C:\OpenOCD\share\openocd\scripts\interface\stlink-v2.cfg -f C:\OpenOCD\share\openocd\scripts\target\stm32f1x.cfg
        Tikiuosi nieko nepamirsau 🙂

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s