Teensy4.1 Serial RX to DMA problem

Status
Not open for further replies.

MO_YA_NE

Member
I'm trying to transfer the data received by UART to RAM by DMA, but it fails.

When I execute the code below and enter 4 characters from the terminal software, an unintended result is output.

Code:
#include "DMAChannel.h"

unsigned char  DMA_TxBuf[50];          //transfer buffer 50
unsigned char  DMA_RxBuf[50];         //receive buffer  50

DMAChannel dmachannel1;
void INT_DMA1(void)
{
  int i;
  dmachannel1.clearInterrupt();
  
  for(i=0;i<8;i++){
  Serial8.printf("%d\n\r",DMA_RxBuf[i]);
  }
}

void DMA_Init(void)
{
//*****************DMA1****************************************************
  dmachannel1.begin();
  dmachannel1.source((uint8_t&)LPUART5_DATA);
  dmachannel1.destinationBuffer(DMA_RxBuf,8);
  dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel1.interruptAtCompletion();
  dmachannel1.attachInterrupt(INT_DMA1);

  dmachannel1.enable();
}

void setup() {

  Serial8.begin(115200);           //115200kbps
   LPUART5_BAUD|=0x100000;           //RDMAE
  DMA_Init();

  Serial8.printf("Hellow!!\n\r");
}

void loop() {

  
}


The execution result will be like this...

Hellow!!
97
255
115
255
100
255
102
255

I typed in 4 numbers, so I want the RAM to contain 4 numbers, but for some reason 255 is inserted.
I want someone to point out my mistake or correct it.
 
DMA in Teensy 4.1 is hopelessly difficult.
I wander around the labyrinth but I was able to escape from it temporarily.
I succeeded in receiving 8 bytes from the serial.

Code:
/*
    Receive 8 bytes from serial 8 and transfer to RAM with DMA
    Use a 4-byte FIFO buffer for reception
  
 */
#include "DMAChannel.h"

//unsigned char  DMA_TxBuf[50];          //transfer buffer 50
unsigned char  DMA_RxBuf[50];         //receive buffer  50

DMAChannel dmachannel1,dmachannel2;

void INT_DMA1(void)
{
  dmachannel1.clearInterrupt();
  dmachannel1.clearComplete();

  dmachannel1.disable();
  dmachannel2.enable();

}

void INT_DMA2(void)
{
  int i;
  dmachannel2.clearInterrupt();
  dmachannel2.clearComplete();

  dmachannel2.disable();
  dmachannel1.enable();
  for(i=0;i<8;i++){
  Serial8.printf("%c\n\r",DMA_RxBuf[i]);
  }
}

void DMA_Init(void)
{
//*****************DMA1****************************************************
  dmachannel1.begin();
  dmachannel1.source((uint8_t&)LPUART5_DATA);
  dmachannel1.destinationBuffer(DMA_RxBuf,4);
  dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel1.interruptAtCompletion();
  dmachannel1.attachInterrupt(INT_DMA1);

//  dmachannel1.transferSize(4);    // Set the data size used for each triggered transfer
//  dmachannel1.transferCount(1);   // Set the number of transfers (number of triggers until complete)
  
  dmachannel1.TCD->CITER = 1;
  dmachannel1.TCD->BITER = 1;
  dmachannel1.TCD->NBYTES = 4;

//*****************DMA2****************************************************
  dmachannel2.begin();
  dmachannel2.source((uint8_t&)LPUART5_DATA);
  dmachannel2.destinationBuffer(&DMA_RxBuf[4],4);
  dmachannel2.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel2.interruptAtCompletion();
  dmachannel2.attachInterrupt(INT_DMA2);

//  dmachannel2.transferSize(4);    // Set the data size used for each triggered transfer
//  dmachannel2.transferCount(1);   // Set the number of transfers (number of triggers until complete)
  
  dmachannel2.TCD->CITER = 1;
  dmachannel2.TCD->BITER = 1;
  dmachannel2.TCD->NBYTES = 4;
  
  dmachannel1.enable(); 
//  dmachannel2.enable();
}

void setup() {
   Serial8.begin(256000);           //256000kbps
   LPUART5_CTRL =0xC0000;            //only RE,TE
   LPUART5_FIFO|=0x08;              //RXFE(Receive FIFO Enable)  
   LPUART5_BAUD|=0x200000;         //RDMAE(Receiver Full DMA Enable)
   LPUART5_WATER|=0x30000;         //WATER MARK changed
   DMA_Init();
   Serial8.printf("Hellow!!Input 8 char!!\n\r");
}

void loop() {
}
 
Sorry not sure what your goals here and why necessary to convert Serail8 over to DMA...
There are several different ways to do this. Personally I would not use two DMAChannels for this.
Can probably do it with just one DMAChannel ro with one channel and multiple DMASettings which is what I often do.

That is with DMASettings, you can setup each setting to chain to the next one, and optionally have them setup to interrupt on completion and/or half completion like the main DMAChannel.

There are several examples of this for different devices. For example I use that to output full frames of images over SPI to some different displays. Some of them are installed as part of Teensyduino.
For example if you look at the ST7735_t3 code: https://github.com/KurtE/ST7735_t3/blob/master/ST7735_t3.cpp#L4337
You will see where I am setting up the chain of settings. Then you simply copy the first item of the chain into your one Channel and then start up the channel.

Good luck
 
Thank you for your help KurtE-san.
In order to communicate with other microcomputers, I need to send 8 bytes and then receive 8 bytes.
These communications need to use very little CPU resources.
In addition, communication is very high speed (20 Mbps).


I am dissatisfied with generating an interrupt every 4 bytes.
So as you say, it would be great if I could process 8 bytes in one setting.

I will refer to the sample code you showed.
 
I’m curious why you don’t just receive the 8 bytes from one DMAChannel instead of chaining 2 together that read 4 bytes each?
 
This is because the serial receive FIFO buffer is 4 bytes.
DMA trigger when the 4-byte FIFO buffer is full.

I think that is the most efficient ...
 
This is because the serial receive FIFO buffer is 4 bytes.
DMA trigger when the 4-byte FIFO buffer is full.

I think that is the most efficient ...

That is true, but it's not the end of the story, I see you had these lines in your code so you may or may not have experimented with them too much:
Code:
//  dmachannel1.transferSize(4);    // Set the data size used for each triggered transfer
//  dmachannel1.transferCount(1);   // Set the number of transfers (number of triggers until complete)
The transferSize of course corresponds to the 4 bytes of the FIFO buffer, but you can set the transferCount to more than one and it won't trigger the interrupt until it completes the number supplied to transferCount, in this case it would be 2 for a total of 8 bytes. Of course you also have to change the 4 bytes to 8 bytes in your destinationBuffer as well.
 
This is because the serial receive FIFO buffer is 4 bytes.
DMA trigger when the 4-byte FIFO buffer is full.

I think that is the most efficient ...

I agree with vjmuzik, that you should be able to do it with one DMAChannel, with simply updating the count to 2.

However not really sure if doing the 4 byte DMA transfer versus DMA out each byte as it is received. will make much difference? Maybe but for sure a lot less overhead than using an ISR call for each 4 bytes.
 
The transferSize of course corresponds to the 4 bytes of the FIFO buffer, but you can set the transferCount to more than one and it won't trigger the interrupt until it completes the number supplied to transferCount, in this case it would be 2 for a total of 8 bytes. Of course you also have to change the 4 bytes to 8 bytes in your destinationBuffer as well.

Do you suggest code like this?
The FIFO buffer capacity was set to 1 byte. This works fine.

Code:
/*
    Receive 8 bytes from serial 8 and transfer to RAM with DMA
    Use a 4-byte FIFO buffer for reception
  
 */
#include "DMAChannel.h"

//unsigned char  DMA_TxBuf[50];          //transfer buffer 50
unsigned char  DMA_RxBuf[50];         //receive buffer  50

DMAChannel dmachannel1,dmachannel2;

void INT_DMA1(void)
{
  int i;
  
  dmachannel1.clearInterrupt();
  dmachannel1.clearComplete();

//  dmachannel1.disable();
//  dmachannel2.enable();

  for(i=0;i<8;i++){
  Serial8.printf("%c\n\r",DMA_RxBuf[i]);
  }
  
}

void INT_DMA2(void)
{
  int i;
  dmachannel2.clearInterrupt();
  dmachannel2.clearComplete();

  dmachannel2.disable();
  dmachannel1.enable();
  for(i=0;i<8;i++){
  Serial8.printf("%c\n\r",DMA_RxBuf[i]);
  }
}

void DMA_Init(void)
{
//*****************DMA1****************************************************
  dmachannel1.begin();
  dmachannel1.source((uint8_t&)LPUART5_DATA);
  dmachannel1.destinationBuffer(DMA_RxBuf,8);
  dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel1.interruptAtCompletion();
  dmachannel1.attachInterrupt(INT_DMA1);

  dmachannel1.transferSize(1);    // Set the data size used for each triggered transfer
  dmachannel1.transferCount(8);   // Set the number of transfers (number of triggers until complete)
  
//  dmachannel1.TCD->CITER = 1;
//  dmachannel1.TCD->BITER = 1;
//  dmachannel1.TCD->NBYTES = 4;
/*
//*****************DMA2****************************************************
  dmachannel2.begin();
  dmachannel2.source((uint8_t&)LPUART5_DATA);
  dmachannel2.destinationBuffer(&DMA_RxBuf[4],4);
  dmachannel2.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel2.interruptAtCompletion();
  dmachannel2.attachInterrupt(INT_DMA2);

//  dmachannel2.transferSize(4);    // Set the data size used for each triggered transfer
//  dmachannel2.transferCount(1);   // Set the number of transfers (number of triggers until complete)
  
  dmachannel2.TCD->CITER = 1;
  dmachannel2.TCD->BITER = 1;
  dmachannel2.TCD->NBYTES = 4;
 */
  dmachannel1.enable(); 
//  dmachannel2.enable();
}

void setup() {
   Serial8.begin(256000);           //256000kbps
   LPUART5_CTRL =0xC0000;            //only RE,TE
   LPUART5_FIFO|=0x08;              //RXFE(Receive FIFO Enable)  
   LPUART5_BAUD|=0x200000;         //RDMAE(Receiver Full DMA Enable)
//   LPUART5_WATER|=0x30000;         //WATER MARK set to 3
   LPUART5_WATER&=~0x30000;         //WATER MARK set to 0
   DMA_Init();
   Serial8.printf("Hellow!!Input 8 char!!\n\r");
}

void loop() {
}
 
Last edited:
Again usually I try to keep things like this simple. Assuming you have the watermark set to 0.
Code:
  dmachannel1.begin();
  dmachannel1.source((uint8_t&)LPUART5_DATA);
  dmachannel1.destinationBuffer(DMA_RxBuf,8);
  dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel1.interruptAtCompletion();
  dmachannel1.attachInterrupt(INT_DMA1);
  dmachannel1.enable();
Also depending on your needs, you can have it interrupt at end, disable at end ...

Hopefully at that point the sizes will be correct.
If not you might need to add: dmachannel1.transferCount(8);
Transfer size should already be correct
 
Again usually I try to keep things like this simple.
Hopefully at that point the sizes will be correct.
If not you might need to add: dmachannel1.transferCount(8);
Transfer size should already be correct

O.K.
I understand how it works little by little.
I got rid of the extra code.
This works enough.

Code:
/*
    Receive 8 bytes from serial8 and transfer to RAM with DMA
 */
#include "DMAChannel.h"

unsigned char  DMA_RxBuf[50];         //receive buffer  50

DMAChannel dmachannel1;

void INT_DMA1(void)
{
  int i;
  
  dmachannel1.clearInterrupt();
  dmachannel1.clearComplete();

  for(i=0;i<8;i++){
  Serial8.printf("%c\n\r",DMA_RxBuf[i]);
  }
  
}


void DMA_Init(void)
{
//*****************DMA1****************************************************
  dmachannel1.begin();
  dmachannel1.source((uint8_t&)LPUART5_DATA);
  dmachannel1.destinationBuffer(DMA_RxBuf,8);
  dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_LPUART5_RX);
  dmachannel1.interruptAtCompletion();
  dmachannel1.attachInterrupt(INT_DMA1);
  dmachannel1.enable(); 
}

void setup() {
   Serial8.begin(256000);           //256000kbps
   LPUART5_CTRL =0xC0000;           //only RE,TE
   LPUART5_FIFO|=0x08;              //RXFE(Receive FIFO Enable)  
   LPUART5_BAUD|=0x200000;         //RDMAE(Receiver Full DMA Enable)
   LPUART5_WATER&=~0x30000;         //WATER MARK set to 0
   
   DMA_Init();
   Serial8.printf("Hellow!!Input 8 char!!\n\r");
}

void loop() {
}
 
I don't understand the connection between the Serial8.begin and the LPUART5.
LPUART6 or 8 if used in place of the 5 strings does not work, but 5 does.
I can find no documentation in the IMXRT1062 that shows the connection or how the LPUARTx relates to the Serial ports on the teensy 4.x processors.
Do you know or did you get this working just by testing?
Thanks in advance.
 
Hi,Mike.
I was confused at first, but Teensy4's HW serial uses registers with different numbers.
See HardwareSerialX.h in the teensy4 folder.(X=1~8)
(C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy4)

You should know which number of registers to use in that file.
 
I don't understand the connection between the Serial8.begin and the LPUART5.
LPUART6 or 8 if used in place of the 5 strings does not work, but 5 does.
I can find no documentation in the IMXRT1062 that shows the connection or how the LPUARTx relates to the Serial ports on the teensy 4.x processors.
Do you know or did you get this working just by testing?
Thanks in advance.

Serial8 is an Arduino concept where all of the SerialX are of the class HardwareSerial.

But underlying each one is a Hardware device lpusart: See chapter 49 IMXRT manual.

In this case Serial8 is built using LpUart5...
You can find out this information a few different ways: You can look at what IO pins you are talking to and then map those through the IMXRT reference...
Which I have in my spreadsheets like:
screenshot.jpg
Which you can see Alt1 for those pins are LPUART5 RX and TX pins...
Or you can look at sources as the serial objects are table driven: SO looking at HardwareSerial8.cpp you will see:
Code:
static HardwareSerial::hardware_t UART5_Hardware = {
	7, IRQ_LPUART5, &IRQHandler_Serial8, 
	&serialEvent8, &_serialEvent8_default,
	CCM_CCGR3, CCM_CCGR3_LPUART5(CCM_CCGR_ON),
    {{34,1, &IOMUXC_LPUART5_RX_SELECT_INPUT, 1}, {48, 2, &IOMUXC_LPUART5_RX_SELECT_INPUT, 0}},
    {{35,1, &IOMUXC_LPUART5_TX_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},

	50, // CTS pin
	2, //  CTS
	IRQ_PRIORITY, 38, 24, // IRQ, rts_low_watermark, rts_high_watermark
	XBARA1_OUT_LPUART5_TRG_INPUT
};
HardwareSerial Serial8(&[COLOR="#FF0000"]IMXRT_LPUART5[/COLOR], &UART5_Hardware, tx_buffer8, SERIAL8_TX_BUFFER_SIZE,
	rx_buffer8,  SERIAL8_RX_BUFFER_SIZE);
#endif
Several hints in that table including the one in RED
 
Status
Not open for further replies.
Back
Top