STM32F0 USART přes DMA/IT podivné chování příjmu (HAL API)

Implementuju si pro větší projekt modul na práci se sériovou linkou, který poskytuje funkce jako getchar a putchar apod pracující s vlastními kruhovými buffery. Vycházel jsem z dokumentace k STM32 HAL API (která je pěkně na hovno někdy) a examplů z STM32CubeF0 (www2.st.com/content/st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software/stm32cube-embedded-software/stm32cubef0.html)

Zkoušel jsem několik kombinací nastavení DMA UARTu(normal nebo circular mode) nebo přes klasické přerušení RX/TX.
Nejspolehlivěji funguje DMA v kruhovým módu…ostatní nějak blbnou (viz dále) a netuším jestli to je vlastnost těch periferií nebo mám prostě něco shnilého v kódu.

Používám nejnovější ST CubeMX pro generování výchozího kódu a nastavení periferií pro toolchain SW4STM32. Kód pak dopisuju a ladím v Embitzu (nástupce Emblocks, projektový soubor se dá z cubemx přepsat ručně nebo vygenerovat utilitou cube2block arts-union.ru/node/32 ). Zdrojáky (i s examply z STM32CubeF) s projektem pro embitz (emblocks) jsou v příloze.

int main(void)
{
    /* 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_DMA_Init();
    MX_USART1_UART_Init();

    serial_init();
    uint8_t c;
    char Txbufferlong]="123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789KONEC\n";
    char Txbuffershort]="123456789\n";
    while (1)
    {
        //bez ukoncovaciho znaku \0 retezce
        serial_transmit((uint8_t*)Txbufferlong, sizeof(Txbufferlong)/sizeof(Txbufferlong[0]) - 1);
        serial_transmit((uint8_t*)Txbuffershort, sizeof(Txbuffershort)/sizeof(Txbuffershort[0]) - 1);  
        while(serial_getByte(&c))
        {
            serial_transmit(&c,1);
            //HAL_Delay(10);
        }
    }

}

Pokud mám UART RX DMA v kruhovém módu, tak tenhle kód funguje bez problému. V hlavní smyčce dokola posílá dlouhou a krátkou zprávu a mezitím kontroluje pomocí uživatelské funkce int serial_getByte(uint8_t*) jestli v přijímacím kruhovým bufferu jsou nějaký bajty. Zkoušel jsem RX zahlcovat všelijakými blbostma (od krátkých 10B zpráv po 128 B těsně za sebou) a bez problému. Vše kontrolováno logickým analyzýtorem a jednoduše v terminálu na PC.

Pokud ale DMA pro RX nastavím do normal módu, tak mi přeposílání přijatých znaků zpět po čase zamrzne. Hlavní smyčka jede dál (stále odesílám krátké a dlouhé řetězce), ale přijímací část je nějak mrtvá. Zkoušel jsem to debugovat plus používat semihosting na posílání testovacích zpráv a nic jsem nezjistil. Zachytil jsem, že po přijmutí znaků obdobně jako při správcné funkci (DMA circular mode) program skáče do handlerů v tom API.
Zjistil jsem, že pokud přijme krátké zprávy s nějakou prodlevou nebo přidám řádek s HAL_Delay(10) za vysílání, tak to nějak žije - zase zamrzne, pokud přijme několik dlouhých zpráv za sebou…

Když vypnu DMAčko a nechám klasické přerušení (v modulu serial.c je třeba místo API funkcí HAL_UART_Receive/Transmit_DMA používat HAL_UART_Receive/Transmit_IT), tak program opět funguje, ale jenom pokud je odkomentován řádek se zpožděním.

Zkrátka je sice fajn, že mi to zřejmě bez problému pojede pro RX DMAčko v kruhovým módu, ale vadí mi, že pro klasické přerušení se to chová takhle divně. Už nad tím hniju několik hodin a jsem zkrátka v koncích :unamused: .
uart_test.zip (1.14 MB)

ahoj,
nevim co presne tahle funkce dela a jak vypada (nestahoval jsem cely zdroj) serial_transmit(&c,1); ale je ten pointer dobry? Ja jen, ze mozna c by mohlo byt *c a nikoli &c.

tuto funkci jsem si napsal takhle (je napsána v serial.c). Využívá už funkce samotné API

void serial_transmit(uint8_t *data, uint16_t amount)
{
    HAL_UART_Transmit_DMA(&huart1, (uint8_t*)data,amount);
    while (UartReady != SET)
        ;
    UartReady = RESET;
}

Callback pro dokonečně vysílání vypadá takhle

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    UartReady = SET;
}

Nezkoumal jsem Tvůj kód. Raději napiš čeho se snažíš dosáhnout. S DMA v M0 i M3 na STM32 jsem už lecos dělal…

Snažím se napsat knihovnu/modul pro seriovou komunikaci, která by spolehlivě fungovala pro U(S)ART v DMA módu tak klasickém přes ISR.
Prostě mezivrstva nad API, která poskytuje pohodlnější fce typu putchar, getchar (pokud bylo něco vůbec přijato).
Můj momentální výtvor spolehlivě funguje pro DMAčko v circular mode. Pro ISR blbne, pokud mezi přijatými znaky a jejich okamžitým vysíláním z vyrovnávacího kruhového bufferu není nějaká menší prodleva.

Já nikoho nenutím číst moje zdrojáky (ono to nejduležitější je v serial.c, kde je jenom cca 50 řádků), ale kdo je ochoten mi poradit, tak si to pročíst bude muset. :smiley:

Nějak nemůžu najít kde povoluješ IRQ od USART. Vidím povolený IRQ systick a dma. Pokud se normálně hákneš na TXE a RXNE tak při 48MHz systém clock a nějaké rozumné prioritě IRQ na USART nepříjdeš o žádná data a nic se nebude zasekávat.

Ah moje chyba. Tohle je projekt nakonfigurovaný na DMA. Pro IRQ je to třeba překonfigurovat. Prioritu mám o jednu nižší jak od Systick (Systick má největší přednost)

Jedna osobní zkušenost: Měl jsem systém kde běželo 2x IRQ od USART (1x9600 + 1x115200) , IRQ systik (1000Hz) , 1xIRQ EXI (10Hz) na cortex m3 na 8MHz. Když jsem měl prioritu na nejrychlejším usartu stejnou nebo menší než systik, tak už jsem občas přišel o data. Systick dávám vždycky s prioritou nejnižší.

to bude možná jádro pudla :laughing:

To možná nebude. Máš 6x výšší systémový hodiny. Pravda, záleží co máš nastrkaný do systicku, ale jinak takovou latenci to nemá abys nestihnul 80us na jeden znak.