CPU Zeit sparen mit Direct Memory Access

Manchmal wird es eng mit dem Timing auf dem Mikrocontroller, dann ist es vorteilhaft einiges von der Hardware direkt erledigen zu lassen. Dafür gibt es den ‚Direct Memory Access‘, den man dafür nutzen kann. Die Atmel AVR Ptozessoren haben das leider nicht. So nehme ich diesmal einen STM Controller.

Hardware
– Nucleo-F411RE Board
– USB-UART Konverter
– PC

RX1 und TX1 am Nucleo Board werden über Kreuz verbunden mit RX und TX des USB-UART Konverters. Und GND mit GND.

Software
– STM32CubeIDE
– PC Terminalprogramm

Die Beispielaufgabe
Übertragen von Daten per DMA:
– aus dem Flash zur UART
– aus dem Flash ins RAM
– Initialisieren aus 1 Byte in ein RAM-Array

Das Programm
wird erstellt in der STM32CubeIDE mit folgenden Einstellungen:
– 16 MHz interner Takt (HSI)
– USART1 Asynchron, 115200, 8N1, DMA global interrupt, DMA Request = USART1_TX
– DMA MEMTOMEM, Byte, Normal, Source und Destination increment, Burst Size jeweils Single (Flash to RAM)
– DMA, MEMTOMEM, Byte, nur Destination increment, Burst Size jeweils Single (RAM to RAM)

Hier die eingefügten Teile der Sourcen in main.c:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stm32f4xx.h>
#include <string.h>
#include <stdbool.h>
/* USER CODE END Includes */
.
.
.
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define STRLENGTH 33 // mit 0 als endekennung
#define SNDLEN 32 // nur die textlaenge
/* USER CODE END PD */
.
.
.
/* USER CODE BEGIN PV */
// mit const bleibt die variable im Flash (.rodata)
const char msg[STRLENGTH] = "Wie heisst ein Wort mit 5 tz ?\r\n";
const char msg1[STRLENGTH] = "Atzventzkrantzkertzenglantz !!\r\n";
const char msg2[STRLENGTH] = "Diese Nachricht ist im RAM !!!\r\n";
// im RAM
char rmsg[STRLENGTH];
char rmsg2[STRLENGTH] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
char *pmsg = (char *)msg;
bool once = false;
char init = (char)0;
/* USER CODE END PV */
.
.
.
/* USER CODE BEGIN PFP */
void UART_DMA_TransferComplete(DMA_HandleTypeDef * hdma_tx); /*!< DMA transfer complete callback */
/* USER CODE END PFP */
.
.
.
/* USER CODE BEGIN 2 */
HAL_DMA_RegisterCallback(&hdma_usart1_tx, HAL_DMA_XFER_CPLT_CB_ID, &UART_DMA_TransferComplete);
// Kopieren von Flash nach RAM
HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)msg2, (uint32_t)rmsg, strlen(msg2)); // Start DMA Transfer
while( HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream0, HAL_DMA_FULL_TRANSFER, 10) != HAL_OK) // Wait until Ready
{
}
// Initialisieren aus einem Byte im RAM
HAL_DMA_Start(&hdma_memtomem_dma2_stream1, (uint32_t)&init, (uint32_t)rmsg2, strlen(rmsg2)); // Start DMA Transfer
while( HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_stream1, HAL_DMA_FULL_TRANSFER, 10) != HAL_OK) // Wait until Ready
{
}
/* USER CODE END 2 */
.
.
.
/* USER CODE BEGIN WHILE */
while (1)
{
huart1.Instance->CR3 |= USART_CR3_DMAT; // Enable DMA Transfer
if (once == false)
{
HAL_DMA_Start_IT(&hdma_usart1_tx, (uint32_t)rmsg, (uint32_t)&huart1.Instance->DR, strlen(rmsg)); // Start DMA Transfer
once = true;
}
else
{
HAL_DMA_Start_IT(&hdma_usart1_tx, (uint32_t)pmsg, (uint32_t)&huart1.Instance->DR, strlen(pmsg)); // Start DMA Transfer
}
HAL_Delay(1000);
if (pmsg == msg)
{
pmsg = (char *)msg1;
}
else
{
pmsg = (char *)msg;
}
/* USER CODE END WHILE */
.
.
.
/* USER CODE BEGIN 4 */
void UART_DMA_TransferComplete(DMA_HandleTypeDef * hdma_tx)
{
// Disable UART DMA mode
huart1.Instance->CR3 &= ~USART_CR3_DMAT;
}
/* USER CODE END 4 */


Ergebnis
Es funktioniert! Die Ausgaben erscheinen prompt auf dem Terminalprogramm am PC. Der Adressraum für Flash und RAM scheint zusammenzuhängen, siehe Datenblatt. Es wird keine Unterscheidung gemacht, wie bei den AVR Prozessoren. Es ist also einfacher den RAM Verbrauch zu kontrollieren. Es gibt sogar die Auswahl ob im Polling oder Interrupt Verfahren gearbeitet werden soll. Zum Sparen von CPU Zeit ist da der Interrupt von Vorteil.

Weitere Möglichkeiten
DMA lässt sich auch im Timer für Input Capture und Output Compare nutzen. Allerdings nur in Richtung Peripheral To Memory.


Links und Literaturhinweise

Datenblatt STM32F411
https://www.st.com/resource/en/datasheet/stm32f411re.pdf

User Manual STM32F411
https://www.st.com/resource/en/reference_manual/DM00119316-.pdf

STM32 CubeIDE
https://www.st.com/en/development-tools/stm32cubeide.html

HTerm
https://www.der-hammer.info/pages/terminal.html

CoolTerm
https://coolterm.en.lo4d.com/windows

 

Veröffentlicht von

Jürgen

Ich bin Software Ingenieur und habe meine Schwerpunkte in allen Aktivitäten, die zur Software Entwicklung gehören. Am längsten bin ich als Software Entwickler von Embedded Software in C tätig.