I enabled DMA peripheral to memory tranfer for ADC1 in CubeMX and generated the code. However I'm confused as to where the data from the ADC will be written to? Should I explicitly define a variable to contain this data? How can I retrieve the data in the DMA Channel 1 ISR?
The DMA does not manage memory nor choose a valid address to set the data. General speaking, the DMA allows data transfers without using the CPU, but no more.
The STM32 microcontrollers provide transfers from:
memory to memory
memory to peripheral
peripheral to memory
In all of them, the developers have to be aware about them purpose in order to configure (besides of DMA) the source and destination places, such as address of the peripherals, reserve memory (and what kind of memory), etc.
In your particular case (check RM, AN, docs, etc), the main actors in an ADC to memory (peripheral to memory) transfer are:
Source: ADC peripheral, the developer has to know where the ADC peripheral is located and configure (besides of ADC) the DMA based on the ADC parameters as the source of information.
Destination: memory, the developer has to reserve a bunch of memory (heap/stack/global/etc) and configure the DMA according to the already allocated space of memory. Doing that, DMA will allow you to set the values in different ways (depending on the device), such as continuous ring buffer, one cycle, ping-pong buffer (stm32 uses the term "circular double buffer"), etc.
DMA and ADC configuration: there are vast amount of factors which for the sake of simplicity I am not going to include, usually simplified by the manufacturer's HAL (it is up to you to use it).
You instruct the HAL DMA ADC driver where to put the sample data when you start the conversion:
volatile uint32_t adcBuffer[SAMPLE_COUNT];
HAL_ADC_Start_DMA( &hadc,
adcBuffer,
SAMPLE_COUNT );
Note that some STM32 parts have SRAM divided across multiple buses with one section very much smaller than others. There are performance benefits to be had in reserving this section for DMA buffers since it reduces bus contention with normal software data fetches. So you may want to customise your linker script to create sections and explicitly place DMA buffers in one while excluding placement of application data there.
If you have a look at the HAL documents and examples you findet an example how to use the ADC with DMA.
In short :
To start the conversion you use the function:
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
Where pData is your variable / array where the DMA should put the data.
DMA and uC do not know anything about the variables. DMA peripheral has two configuration registers where you store the peripheral address and the memory address. If you start from reading the uC documentation instead of HAL everything would be clear instantly
Related
I'm using an STM32H743. I have an external clock signal coming in on a GPIO pin, and I want to very accurately measure elapsed time between each rising (or falling) edge in the external clock signal. So I set things up so that TIM4 is triggered by the external clock, and TIM5 is triggered by the internal oscillator.
I wrote an IRQ so that whenever TIM4 triggers, an interrupt runs that captures TIM5's value. It seems to work OK, but I'm wondering if I can do it through DMA to avoid all the context switching and free up the CPU. Basically I want to set up a DMA so that each TIM4 event initiates a DMA transfer that copies the TIM5 counter value to a circular buffer somewhere.
I've searched through forums and the DMA documentation but I'm hazy on whether a timer register can be a valid DMA source. I was thinking maybe I could do something like this:
hDma->PAR = (uint32_t) &htim5.Instance->CNT;
hDma->M0AR = (uint32_t) myBufferPtr;
hDma->NDTR = myBufferSize;
hDma->CR |= (uint32_t)DMA_SxCR_EN;
But I'm not sure if this can work.
Short version: Can I use the timer's CNT register as a DMA transfer source? Would it be a peripheral-to-memory transfer? Or a memory-to-memory transfer? Are there other flags I need to make this work? Or is it not possible? Or is there another STM32 feature that would make it easier to count time between pulses?
Disclaimer
I must confess that my long practical experience with STM32 by now stayed with mainstream controller families like STM32F0, STM32F3, STM32F4 and STM32L4.
Therefore I'm answering based on what those controllers would offer you in your situation.
The STM32H7 series is much stronger, let alone it offers several additional DMA technologies like DMA2D, MDMA and lots of other stuff that I'm not sure about.
But I think a simplified answer might also help you for now, so I'm daring to write it.
Can I use the timer's CNT register as a DMA transfer source? Would it be a peripheral-to-memory transfer? Or a memory-to-memory transfer? Are there other flags I need to make this work? Or is it not possible?
I would expect this to work.
I don't see a reason not to read the TIMx_CNT register in a DMA transfer.
The CNT register is definitely a peripheral address so you have to configure it as a peripheral-to-memory transfer.
I believe that the peripheral/memory separation refers to the bus from which the DMA controller fetches the data (or to which bus one it delivers them) in the bus matrix implemented in every STM32.
Or is there another STM32 feature that would make it easier to count time between pulses?
Yes, there is:
Many of the TIM peripherals (not all are the same) offer you a feature called "Input Capture" that connects the channel (sub-)peripheral of the TIM instance to the input and has the main part of the (same!) TIM peripheral do the internal clocking.
A prerequisite of this is, that the pin you'd like to measure has a TIMx_CHy alternate function, not "only" a TIMx_ETR one.
The TIM peripherals offer a wealthy range of different configuration options - and a complicated mess as long as you haven't got used to it.
As an introduction and a good overview, I recommend two application notes from ST:
AN4013 Application note. "STM32 cross-series timer overview", Rev.8
Which timers you have on your µC, and which features are offered by which one.
AN4776 Application note. "General-purpose timer cookbook for STM32 microcontrollers", Rev.3
How to use the timers you have. Check out section 2.6, input capture is on page 27.
Looking up those two, I found a third one you might want to check out for better precision, related to HRTIM timers:
AN4539 Application note. "HRTIM cookbook", Rev.4
It is easily done using STM32CubeIDE configurator:
configure timer, enable input capture channel, enable DMA (mode
circular, peripheral to memory,data width word/word). Enable
interrupts.
Prepare buffer for storing captured counter values
Start IC in DMA mode before main loop
For high speed operation you may copy data from timerCaptureBuffer
to timerCaptureBufferSafe inside these callbacks. For example, DMA memory to memory transfer to minimize time spent in HAL_TIM_IC_CaptureHalfCpltCallback and HAL_TIM_IC_CaptureCallback interrupts. Process adjacent captured values stored in timerCaptureBufferSafe after DMA memory to memory callback signals data is ready. You may use signaling flags so timerCaptureBufferSafe will not be overwritten.
Here is an example:
#define TIM_BUFFER_SIZE 128
uint32_t timerCaptureBuffer[TIM_BUFFER_SIZE];
uint32_t timerCaptureBufferSafe[TIM_BUFFER_SIZE];
// ...
HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream2,
HAL_DMA_XFER_CPLT_CB_ID,
myDMA_Callback22);
// ...
HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, uint32_t*)timerCaptureBuffer,TIM_BUFFER_SIZE);
// ...
void HAL_TIM_IC_CaptureHalfCpltCallback(TIM_HandleTypeDef *htim)
{
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream2,
(uint32_t)&timerCaptureBuffer[0],
(uint32_t)&timerCaptureBufferSafe[0],
sizeof(timerCaptureBuffer)/2/4);
// ...
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream2,
(uint32_t)&timerCaptureBuffer[TIM_BUFFER_SIZE/2],
(uint32_t)&timerCaptureBufferSafe[TIM_BUFFER_SIZE/2],
sizeof(timerCaptureBuffer)/2/4);
// ...
}
void myDMA_Callback22(DMA_HandleTypeDef *_hdma)
{
//...
}
I'm attempting to create a data logger that intakes data from a sensor node with three modalities and transmits that data using three different SPI busses to a Nucleo-f746zg. I would then like to use DMA peripheral to memory mode to take the incoming data from each bus and put it into one of three buffers that will eventually get written to a USB flash drive using FATFS. I'm having issues initializing the DMA in double buffer mode. When I look at each register it seems to be configured correctly but none of the interrupts fire and the data transfer doesn't occur.
I know that the SPI data is coming in. I initially tried this using interrupts but the data rate was too fast and I was losing data so I pivoted to DMA. The Data, once it's in the register correctly writes to the USB drive as well. I'm using the "LL" drivers for both SPI and DMA.
This is my current DMA initialization code:
uint16_t TEST_BUFFER_0[10000];
uint16_t TEST_BUFFER_1[10000];
LL_DMA_SetChannelSelection(DMA1,LL_DMA_STREAM_3, LL_DMA_CHANNEL_0);
LL_DMA_SetDataTransferDirection(DMA1,LL_DMA_STREAM_3, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_EnableDoubleBufferMode(DMA1,LL_DMA_STREAM_3);
LL_DMA_SetPeriphIncMode(DMA1,LL_DMA_STREAM_3, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1,LL_DMA_STREAM_3, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA1,LL_DMA_STREAM_3, LL_DMA_PDATAALIGN_HALFWORD);
LL_DMA_SetMemorySize(DMA1,LL_DMA_STREAM_3, LL_DMA_MDATAALIGN_HALFWORD);
LL_DMA_DisableFifoMode(DMA1,LL_DMA_STREAM_3);
LL_DMA_SetMemoryAddress(DMA1,LL_DMA_STREAM_3,TEST_BUFFER_1);
LL_DMA_SetMemory1Address(DMA1,LL_DMA_STREAM_3, TEST_BUFFER_0);
LL_DMA_SetDataLength(DMA1,LL_DMA_STREAM_3, 10000);
LL_DMA_SetPeriphAddress(DMA1,LL_DMA_STREAM_3, LL_SPI_DMA_GetRegAddr(SPI2));
LL_DMA_EnableIT_HT(DMA1,LL_DMA_STREAM_3);
LL_DMA_EnableIT_TC(DMA1,LL_DMA_STREAM_3);
LL_DMA_EnableStream(DMA1,LL_DMA_STREAM_3);
LL_SPI_EnableDMAReq_RX(SPI2);
LL_SPI_Enable(SPI2);
I'm not seeing either of the interrupts firing nor am I seeing my buffers get full. I'm not one hundred percent sure what actually initiates the DMA transfer other than the documentation alluding to this occurring automatically when data comes in. I'm kind of stuck at this point and I'm not sure what else to try.
I want to understand how the registers of various peripherals/IPs are mapped to the ARM processor memory map in a microcontroller.
Say, I have a CONTROL register for UART block. When I do a write access to address (40005008), this register gets configured. Where does this mapping happens: Within the peripheral block code itself or while integrating this peripheral to the SoC/microcontroller.
For a simple peripheral like a UART it's straightforward - taking the ARM PL011 UART as an example (since I know where its documentation lives):
The programmer's model defines a bunch of registers at word-aligned offsets in a 4k block.
In terms of the actual hardware, we see the bus interface matches what the programmer's model suggests - PADDR[11:2] means only bits 11:2 of the address are connected, meaning it can only understand word-aligned addresses from 0x000 to 0xffc (similarly, note that only 16 bits of read/write data are connected, since no register is wider than that).
The memory-mapping between the UART's 12-bit address and the full 32-bit address that the CPU core spits out happens in the interconnect hardware between them. At design time, the interconnect address map will be configured to say "this 4k region at 0x40005000 is assigned to the UART0 block", etc., and the resulting bus circuitry will be generated for that.
More complex things like e.g. DMA-capable devices typically have separate interfaces for configuration and data access, so the registers can be mapped in a small relocatable block on a low-speed peripheral bus much like the UART.
Most significant bits are defined by your ASIC design, least significant bits are defined by the IP design. Your IP has several registers. The number of register, their order, is defined by the IP design. Here, your register is at address 8. Then when designing the ASIC, the peripherals are connected to the memory bus, and the way they are connected define their address. Your UART is at 40005000. You may have an other instance of the same IP at (for instance) 40006000. The two UART would be strictly identical, and you would be able to access CONTROL register of your second UART at address 40006008.
I want to transfer 8 bit parallel data from IO to memory ,the data is coming very fast at speed of roughly 5 Mhz ,I am using embedded linux on ARM9 based kit by friendly arm which is using S3C2440(400Mhz) processor can any body pleas tell me where to start,my data is a video signal that is coming from a adc
I have read the on internet that I can do this using DMA but I need a start ...
Forget about DMA on this device. The ADC is not available as a DMA source. One reason for this is that DMA is only useful for transferring multiple bytes/words/whatever - the overhead of setting up, starting the DMA and handling an OnCompletion interrupt makes it pointless for occasional transfers of one item. Your ADC has no buffering, just the one output register with 10 sig. bits.
Use an FIQ handler to extract the ADC result. How you buffer the output and signal it for further processing is up to you and the linux driver framework.
have a look at these articles to for brieif theroy
http://my.opera.com/richasn/blog/2011/01/15/application-of-dma-way-in-data-acquisition-in-arm-system
http://my.opera.com/richasn/blog/2011/01/14/application-of-dma-way-in-data-acquisition-in-arm-system
i'm having hard time trying to understand how interrupts work.
the code below initialize the Programmable Interrupt Controller
#define PIC0_CTRL 0x20 /* Master PIC control register address. */
#define PIC0_DATA 0x21 /* Master PIC data register address. */
/* Mask all interrupts*/
outb (PIC0_DATA, 0xff);
/* Initialize master. */
outb (PIC0_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */
outb (PIC0_DATA, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */
outb (PIC0_DATA, 0x04); /* ICW3: slave PIC on line IR2. */
outb (PIC0_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */
/* Unmask all interrupts. */
outb (PIC0_DATA, 0x00);
can someone explain to me how it works:
-the role of outb (i didn't understand the linux man)
-the addresses and their meaning
another unrelated question,i read that outb and inb are for port-mapped I/O, can we use memory-mapped I/o for doing Input/output communication?
thanks.
outb() writes the byte specified by its second argument to the I/O port specified by its first argument. In this context, a "port" is a means for the CPU to communication with another chip.
The specific C code that you present relates to the 8259A Programmable Interrupt Controller (PIC).
You can read about the PIC here and here.
If that doesn't provide enough details to understand the commands and the bit masks, you could always refer to the chip's datasheet.
Device specific code is best read in conjunction with the corresponding datasheet. For example, the "8259A Programmable Interrupt Controller" datasheet (http://pdos.csail.mit.edu/6.828/2005/readings/hardware/8259A.pdf) clearly (but concisely) explains almost everything.
However, this datasheet will only explain how the chip can be used (in any system), and won't explain how the chip is used in a specific system (e.g. in "PC compatible" 80x86 systems). For that you need to rely on "implied de facto standards" (as a lot of the features of the PIC chips aren't used on "PC compatible" 80x86 systems, and may not be supported on modern/integrated chipsets).
Normally (for historical reasons) the PIC chip's IRQs are mapped to interrupts in a strange/bad way. For example, IRQ 0 is mapped to interrupt 8 and conflicts with the CPU's double fault exception. The specific code you've posted remaps the PIC chips so that IRQ0 is mapped to interrupt 0x20 (and IRQ1 to interrupt 0x21, ..., IRQ 15 mapped to IRQ 0x2F). It's something an OS typically does to avoid the conflicts (e.g. so that each interrupt is used for an IRQ or an exception and never both).
To understand "outb()", look at the "OUT" instruction in Intel's manuals. It's like there's 2 address spaces - one for normal physical addresses and a completely separate one for IO ports, where normal instructions (indirectly) access normal physical memory; and the IO port instructions (IN, OUT, INSB/W/D, OUTSB/W/D) access the separate "IO address space".
The traditional 8088/86 had/has a memory control signal that is essentially another address bit tied directly to the instruction. The control signal separating the accesses into I/O and Memory, creating two separate address spaces. Not unlike CS, DS, etc creating separate memory spaces inside the chip (before hitting the external memory space). Other processor families use what is called memory mapped I/O.
These days the memory controllers/system is chopped up inside and outside the chip in all different ways, sometimes for example with many control signals that indicate instruction vs data, cache line fills, write through vs write back, etc. To save on external circuitry the memory mapping happens inside the chip and for example dedicated rom interfaces, separate from ram, etc are found on the edge, far more complicated and separate than I/O space vs Memory space of the old 8088/86.
the out and in instruction and a few family members change whether you are doing an I/O access or memory access, and traditinally the interrupt controller was a chip that decoded the memory bus looking for I/O access with the address allocated for that device. Decades of reverse compatibility later and you have the present code you are looking at.
If you really want to understand it you need to find the datasheets for the device that contains the interrupt controller, likely to be combined with a bunch of other logic on a big support chip. Other datasheets may be required as well.