Buffer Address for DMA
Posted: Mon Dec 18, 2023 10:13 am
To avoid busy-waiting at a serial output peripheral (USART), I am implementing buffered text output to a terminal. The intention is to use the DMA peripheral on the M0. Among other configuration, the DMA channel expects 1) a memory address of the buffer, and 2) the number of data items to transfer from this address upwards. The DMA device will then output the buffer contents on a pure hardware level, while the CPU can handle other tasks.
A common use case is
The string is stored in flash memory with the program code. So I would like to pass the string's address and length to the DMA channel, and avoid copying the flash-stored data to a RAM-based buffer with a known address. Since the DMA device nicely reads from a flash address, this would be unnecessary overhead, and also opens the question about the buffer size.
With a serial device driver API procedure like so... [2]
... 'Out.String' can easily pass along the required data ('chan' is the DMA channel number, 'nc' the number of characters).[1]
But -- how can 'PrintString' determine the address of 's'?
I see two solutions, both work,[3] but neither "looks nice".
Any thoughts about a "clean" Oberon solution for this problem? Thanks.
***
[1] The determination of the string length could also be delegated to 'PrintString', but output procedures such as 'Out.Hex' already have this data available due to how the corresponding output strings are constructed.
[2] I could also implement a more "raw" device driver procedure like this:
But this would only shift the problem to another level, ie. into 'Out.String'.
[3] Evidenced by checking the generated code, as well as via test programs.
Variant (* 1 *)
Variant (* 2 *) is leaner, not least as no index bound checking needs to be done.
A common use case is
Code: Select all
Out.String("this is a test")
With a serial device driver API procedure like so... [2]
Code: Select all
PROCEDURE PrintString*(chan: INTEGER; s: ARRAY OF CHAR; nc: INTEGER);
But -- how can 'PrintString' determine the address of 's'?
I see two solutions, both work,[3] but neither "looks nice".
Code: Select all
(* 1 *)
PROCEDURE PrintString*(chan: INTEGER; s: ARRAY OF CHAR; nc: INTEGER);
BEGIN
DMA.SetMemAddr(chan, SYSTEM.ADR(s[0])); (* set memory address for DMA channel *)
DMA.SetNumData(chan, nc); (* set number of data items for DMA channel *)
(* ... *)
END PrintString;
(* 2 *)
PROCEDURE PrintString*(chan: INTEGER; s: ARRAY OF CHAR; nc: INTEGER);
BEGIN
DMA.SetMemAddr(chan, SYSTEM.REG(1));
DMA.SetNumData(chan, nc);
(* ... *)
END PrintString;
***
[1] The determination of the string length could also be delegated to 'PrintString', but output procedures such as 'Out.Hex' already have this data available due to how the corresponding output strings are constructed.
[2] I could also implement a more "raw" device driver procedure like this:
Code: Select all
PROCEDURE PrintStringBuffer(chan: INTEGER; bufAddr: INTEGER; nc: INTEGER);
[3] Evidenced by checking the generated code, as well as via test programs.
Variant (* 1 *)
Code: Select all
PROCEDURE PutString*(chan: INTEGER; s: ARRAY OF CHAR; nc: INTEGER);
BEGIN
. 4 04H 0B50FH push { r0, r1, r2, r3, lr }
DMA.SetMemAddr(chan, SYSTEM.ADR(s[0]));
. 6 06H 09800H ldr r0,[sp]
. 8 08H 02100H movs r1,#0
. 10 0AH 09A02H ldr r2,[sp,#8]
. 12 0CH 04291H cmp r1,r2
. 14 0EH 0D301H bcc.n 2 -> 20
. 16 010H 0DF01H svc 1
. 18 <LineNo: 13>
. 20 014H 09A01H ldr r2,[sp,#4]
. 22 016H 01851H adds r1,r2,r1
. 24 018H 004050000H bl.w Ext Proc #5
Code: Select all
PROCEDURE PutString*(chan: INTEGER; s: ARRAY OF CHAR; nc: INTEGER);
BEGIN
. 4 04H 0B50FH push { r0, r1, r2, r3, lr }
DMA.SetMemAddr(chan, SYSTEM.REG(1));
. 6 06H 09800H ldr r0,[sp]
. 8 08H 004050000H bl.w Ext Proc #5