cancel
Showing results for 
Search instead for 
Did you mean: 

[STM32f4-Discovery] I2S RX Master with DMA

island1985
Associate II
Posted on March 06, 2013 at 19:44

Hi

I have some questions for the following scenario. I want to configure the dma controller to transfer data from an digital i2s mems microphone to the memory. The I2S (SPI2) is the rx master and the memory should be a software ring buffer. The sample size is 24 bit that will be stored in a 32 bit integer variable. I've already implemented this configuration on a M3 device from texas instruments. Now I want to do the same on the stm32f4-discovery. On the TI device, everytime the i2s rx fifo is full, it will cause a dma request and the cpu jumps in the i2s interrupt handler. The dma is configured for ping pong modus with a primary and a secondary dma configuration structure (similar to the double buffer mode) and in the i2s handler, I check whether the transfers are done. If one transfer is completed, the dma stopps for this transfer configuration. Then, I configure a new transfer with the new destination pointer and enable a new transfer that will start if the other buffer is filled completely. The code looks like this:

void
I2SIntHandler(
void
)
{
//UARTprintf(''I2S ISR\n'');
unsigned 
long
ulStatus;
unsigned 
long
ulMode;
// Interruptstatus abfragen
ulStatus = I2SIntStatus(I2S0_BASE , 1);
//Interruptquelle löschen, damit der Handler am Ende nicht erneut aufgerufen wird
I2SIntClear(I2S0_BASE, ulStatus);
// Status des uDMA I2S Kanals abfragen, primäre Kontrollstruktur
ulMode = uDMAChannelModeGet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT);
// Wenn der uDMA den Transfer abgeschlossen hat, stoppt er automatisch
if
(ulMode == UDMA_MODE_STOP)
{
oldIndex = (index-1) & (RING_BUFFER_SIZE-1);
/*
* Konfiguriert die Übertragungsparamter für den nächsten Transfer des I2SRx Kanals für den uDMA
* Primäre Kontrollstruktur (Ping Pong Modus, Buffer1), Datenquelle ist der I2SRx FIFO
*/
uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT,
UDMA_MODE_PINGPONG, (
void
*) I2S_SOURCE,
(
void
*)I2SPuffer[index], BUFFER_SIZE);
uDMAChannelEnable(UDMA_CHANNEL_I2S0RX);
index++;
index = index & (RING_BUFFER_SIZE-1);
bufferVoll = 1;
}
// Status des uDMA I2S Kanals abfragen, primäre Kontrollstruktur
ulMode = uDMAChannelModeGet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT);
// Wenn der uDMA den Transfer abgeschlossen hat, stoppt er automatisch
if
(ulMode == UDMA_MODE_STOP)
{
oldIndex = (index-1) & (RING_BUFFER_SIZE-1);
/*
* Konfiguriert die Übertragungsparamter für den nächsten Transfer des I2SRx Kanals für den uDMA
* Primäre Kontrollstruktur (Ping Pong Modus, Buffer1), Datenquelle ist der I2SRx FIFO
*/
uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT,
UDMA_MODE_PINGPONG, (
void
*) I2S_SOURCE,
(
void
*)I2SPuffer[index], BUFFER_SIZE);
uDMAChannelEnable(UDMA_CHANNEL_I2S0RX);
index++;
index = index & (RING_BUFFER_SIZE-1);
bufferVoll = 1;
}
}

How does the same functionality work on the stm32?

The data sheet for

stm32f4

and

the

Standard

Peripherals

Library

documentation

could

not yet

answer

these questions

clearly enough

to me. Here are also my current (not complete) configuration for i2s and the dma controller. I'm new to stm32 so I would be glad for all answers.


// I2S Base Address für STM32f407vgt6

#define I2S_PeripheralBaseAdd ((unsigned long)0x40003800 + 0x0C)


#define BUFFERSIZE 48

#define RINGBUFFERSIZE 8


volatile
signed
long
I2SPuffer[RINGBUFFERSIZE][BUFFERSIZE];


void
DMAInit()

{

DMA_InitTypeDef DMA_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;



// DMA 1 mit Takt versorgen

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);



/*

* DMA konfigurieren

* SPI2_RX (=I2S_RX): DMA1 Kanal 0, Stream 3

* Double Buffer Mode (Circular Mode muss enable sein)

*/

DMA_InitStructure.DMA_Channel = DMA_Channel_0;

DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

DMA_InitStructure.DMA_PeripheralBaseAddr = I2S_PeripheralBaseAdd;

DMA_InitStructure.DMA_Priority = DMA_Priority_High;

DMA_InitStructure.DMA_BufferSize = 48;

DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC8;

DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

DMA_InitStructure.DMA_Memory0BaseAddr = (
signed
long
) &I2SPuffer[0];


DMA_Init(DMA1_Stream3,&DMA_InitStructure);



DMA_DoubleBufferModeConfig(DMA1_Stream3, (
signed
long
) &I2SPuffer[1], DMA_Memory_0);


// Double buffer mode aktivieren

DMA_DoubleBufferModeCmd(DMA1_Stream3, ENABLE);


// Transfer complete interrupt aktivieren

DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);



/* Configure and enable DMA interrupt */

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

}


void
I2SInit()

{

// I2S und GPIO TypDefs

GPIO_InitTypeDef I2S_GPIOInitStructure;

I2S_InitTypeDef I2S_InitStructure;


// Port B mit Takt versorgen

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);


// SPI2 mit Takt versorgen

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);


// Hochgenaue PLL für I2S aktivieren

RCC_PLLI2SCmd(ENABLE);


// GPIO Pins für I2S konfigurieren

I2S_GPIOInitStructure.GPIO_Mode = GPIO_Mode_AF;

I2S_GPIOInitStructure.GPIO_OType = GPIO_OType_PP;

I2S_GPIOInitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

I2S_GPIOInitStructure.GPIO_Speed = GPIO_Speed_50MHz;


// PB9 konfigurieren, aktivieren und mit I2S_2_WS verknüpfen

I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_Init(GPIOB,&I2S_GPIOInitStructure);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);


// PB10 konfigurieren, aktivieren und mit I2S_2_SCK verknüpfen

I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_Init(GPIOB,&I2S_GPIOInitStructure);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);


// PB15 konfigurieren, aktivieren und mit I2S_2_WS verknüpfen

I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_15;

GPIO_Init(GPIOB,&I2S_GPIOInitStructure);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2);



// I2S Schnittstelle konfigurieren

I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_8k;

I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_24b;

I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;

I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;

I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;

I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;


// I2S Schnittstelle initialisieren

I2S_Init(SPI2, &I2S_InitStructure);


// I2S2 DMA Interface aktivieren

SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Rx,ENABLE);


// I2S Schnittstelle aktivieren

I2S_Cmd(SPI2, ENABLE);

}

#i2s-rx-master-with-dma #dma-i2s-spi-interrupts
9 REPLIES 9
Posted on March 07, 2013 at 10:07

Looks reasonable so far. I don't use the peripheral library so can't comment on the particulars, but a few comments:

- I see that you already realized your mistake with DMA Ch0/Stream2 :-) -

DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC8;

No. You don't want peripheral bursts. The ''simpler'' peripherals such as the SPI/I2S ''cell'' in STM32 usually don't have FIFO, instead, there is a FIFO in the DMA. While it is true that there are two transfers per received word of 24/32 bits due to the data register in SPI/I2S is 16-bit long, there is no 2-beat burst. You probably want single transfer from peripheral to the DMA's FIFO, and then possibly bursts from DMA to memory. Mind the rules for DMA_SxNDTR (buffer size), buffer alignment, and the rule for 1kB address boundary, when FIFO and memory bursts are employed. -

DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;

Isn't this a typo? I don't know, I don't use the library and have no will to look it up... - you still need to write the interrupt service routine (ISR) for DMA-complete. In that, you *don't* need to restart the DMA as you have set it to circular mode. Be prepared that you'll need to ''decompose'' the received data, if in 24-bit mode. - you might consider using the double-buffering DMA mode, as that gives you considerably more time to handle the incoming data in the DMA-complete ISR - beware, before (re)enabling DMA, you need to clear related flags in _HIFCR/_LIFCR. This might be accomplished by the ''library'' - I don't know I don't use it. - would you move this design to a chip with more than 100 pins, don't use the port I pins for I2S - the reason is complex and involved and could be found somewhere in this forum (and as ST is aware of it, I believe it will appear in some of the future erratas)

- 

// PB15 konfigurieren, aktivieren und mit I2S_2_WS verknüpfen

I know it's ''only'' comment, but that shouldn't lie either :-) -

I2SPuffer

I'd recommend ''I2SBuffer'', especially since you use ''Buffer'' even in the German-language comments (and maybe it's a good idea to write the comments in English, too). - Philips is spelled with one l, they corrected it in the manual since rev.1 :-) JW
Posted on March 07, 2013 at 10:12

... ah, I see, double-buffer mode already considered.

Do you plan to use more than two buffers (you have defined 8) and switch them in the ISR? Any particular reason to do that?

JW

island1985
Associate II
Posted on March 07, 2013 at 11:27

Thank you very much for your reply :-)

> - I see that you already realized your mistake with DMA Ch0/Stream2 :-) Did I? ;-) The ref manual say it's DMA1/Ch0/Stream3 for SPI2_RX >-

DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC8;

>No. You don't want peripheral bursts. The ''simpler'' peripherals such as the SPI/I2S ''cell'' in STM32 usually don't have FIFO, instead, there is a FIFO in the DMA. While it is >true that there are two transfers per received word of 24/32 bits due to the data register in SPI/I2S is 16-bit long, there is no 2-beat burst. >You probably want single transfer from peripheral to the DMA's FIFO, and then possibly bursts from DMA to memory. Mind the rules for DMA_SxNDTR (buffer size), buffer >alignment, and the rule for 1kB address boundary, when FIFO and memory bursts are employed. That makes sense. But I don't realy understand that rule entirely. Can you give me an example for my case here? >-

DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;

>Isn't this a typo? I don't know, I don't use the library and have no will to look it up... Oh, you're right. It's a typo. It should be ''

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

'' for writing the sample in a 32 bit long variable. >- you still need to write the interrupt service routine (ISR) for DMA-complete. In that, you *don't* need to restart the DMA as you have set it to circular mode. Be prepared that you'll need to >''decompose'' the received data, if in 24-bit mode. Can you explain this with some more details? >... ah, I see, double-buffer mode already considered. > >Do you plan to use more than two buffers (you have defined 8) and switch them in the ISR? Any particular reason to do that? Yes, indeed. But maybe the CPU might be fast enough to process the data, even if I only use two bigger buffers. The sample rate for the microphone is 8kHz. > - Philips is spelled with one l, they corrected it in the manual since rev.1 :-) The stm32f4xx_spi.h only knows Phillips ;-)
island1985
Associate II
Posted on March 07, 2013 at 13:47

Hi

This is my current ISR for changing the new destination memory location ''on-the-fly''. Might this work as I'd expect it?


/**

* ISR for I2S2 DMA Stream Interrupt

*/

void
DMA1_Stream3_IRQHandler()

{

// Stream3 transfer complete interrupt?

if
(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3))

{

// clear pending interrupt

DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3);


if
(DMA_GetCurrentMemoryTarget(DMA1_Stream3))

{

// Memory 1 is current target

DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_0);


// New buffer index for the next destination

BufferIndex++;

BufferIndex = BufferIndex & (BUFFERSIZE-1);


}

else

{

// Memory 0 is current target

DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_1);


// New buffer index for the next destination

BufferIndex++;

BufferIndex = BufferIndex & (BUFFERSIZE-1);

}


}


}

Posted on March 07, 2013 at 16:56

>> - I see that you already realized your mistake with DMA Ch0/Stream2 :-)

> Did I? ;-) The ref manual say it's DMA1/Ch0/Stream3 for SPI2_RX

In the original post you had DMA1/Ch0/Stream2; you have edited it one hour later, haven't you?

>>You probably want single transfer from peripheral to the DMA's FIFO, and then possibly bursts from DMA to memory.

>>Mind the rules for DMA_SxNDTR (buffer size), buffer alignment, and the rule for 1kB address boundary, when FIFO and memory bursts are employed.

> That makes sense. But I don't realy understand that rule entirely. Can you give me an example for my case here?

Read the note in 9.3.8 and chapter 9.3.11. Specifically,

''The address pointers (DMA_SxPAR or DMA_SxM0AR registers) must be chosen so as to ensure that all transfers within a burst block are aligned on the address boundary equal to the size of the transfer.'' In other words, if you intend to transfer words in 8-beat bursts, the address of buffer has to be aligned to 32-byte boundary. Refer to the manual of toolchain you use to find out how to achieve that. Alternatively, don't use DMA bursts - I don't know what is the performance penalty but I would be surprised if it would be more than one cycle per transfer IF a simultaneous DMA and processor access to the RAM would occur at every DMA transfer - and given the likelihood of this in ''normal'' applications times the ratio of I2S data flow to total processing speed results IMHO in infinitesimally small value... In other words, there's almost no reason to be bothered with burst transfers.

>>- you still need to write the interrupt service routine (ISR) for DMA-complete. In that, you *don't* need to restart the DMA as you have set it to circular mode. Be prepared that you'll need to

>>''decompose'' the received data, if in 24-bit mode.

>Can you explain this with some more details?

Refer to figure 291. The received 24-bit data are stored in 32-bit register MSB-first, then split to two halfwords in most-significant-halfword-first, so if stored and then read from memory as 32-bit word, the 0x8EAA33 will turn to 0x33008EAA. Before using you'll need to ''swap-and-shift it back''.

> Yes, indeed. But maybe the CPU might be fast enough to process the data, even if I only use two bigger buffers.

> The sample rate for the microphone is 8kHz.

That's upon you to decide. But if the CPU is not fast enough, smaller buffers won't help either - smaller chunks of data would be needed to process more often, that's the only difference.

> This is my current ISR for changing the new destination memory location ''on-the-fly''. > Might this work as I'd expect it?

Looks good. But still, why??? :-)

JW

island1985
Associate II
Posted on March 08, 2013 at 09:42

>>> - I see that you already realized your mistake with DMA Ch0/Stream2 :-)

>> Did I? ;-) The ref manual say it's DMA1/Ch0/Stream3 for SPI2_RX

>In the original post you had DMA1/Ch0/Stream2; you have edited it one hour later, haven't you?

Indeed, I didn't expect that you had seen that version.

>> Yes, indeed. But maybe the CPU might be fast enough to process the data, even if I only use two bigger buffers.

>> The sample rate for the microphone is 8kHz.

>That's upon you to decide. But if the CPU is not fast enough, smaller buffers won't help either - smaller chunks of data would be needed to process more often, that's the only difference

Of course, you're right. But it's also a question of the available memory space to store the incomming data at one time. But I think that this is manageable even with two bigger buffers.

>>>- you still need to write the interrupt service routine (ISR) for DMA-complete. In that, you *don't* need to restart the DMA as you have set it to circular mode. Be prepared that you'll need to

>>>''decompose'' the received data, if in 24-bit mode.

>>Can you explain this with some more details?

>Refer to figure 291. The received 24-bit data are stored in 32-bit register MSB-first, then split to two halfwords in most-significant-halfword-first, so if stored and then read from memory as 32-bit >word, the 0x8EAA33 will turn to 0x33008EAA. Before using you'll need to ''swap-and-shift it back''.

Oh, I wouldn't have thought about that point. In my TI device, there is a nativ i2s peripheral with a 8x32 bit input fifo. So I didn't needed to worry about something like that. But more important for me might be figure 292, because I'm only interested in receiving data from i2s. How does the dma handle that? Puts he the 16 bit of a 24 bit wide sample of the i2s register in a 32 bit variable and the last 8 bits in a new 32 bit variable? Or puts he both incoming 16 bit (half-word) data in one 32 bit variable because I told him that the source is half-word and the destination (memory) is word?

>> This is my current ISR for changing the new destination memory location ''on-the-fly''.

>> Might this work as I'd expect it?

>Looks good. But still, why??? :-)

Why not? ;-) ''Why'' to change the destination because of using several small buffers or what do you mean?

Because of the '' the rules for DMA_SxNDTR'', I will take a deeper look at that point first. Maybe it's clear afterwards or some new questions are coming up.

Thank you very much, your answers are really helpful to me :-)

Posted on March 08, 2013 at 10:46

>>>>''decompose'' the received data, if in 24-bit mode.

>>>Can you explain this with some more details?

>>Refer to figure 291. [...]

>Oh, I wouldn't have thought about that point. In my TI device, there is a nativ i2s

>peripheral with a 8x32 bit input fifo. So I didn't needed to worry about something like that.

The STM32 SPI/I2S ''cell'' is more simplistic than most of the other manufacturers' implementation.

The I2S receiver is not much more than a 16-bit shift register, with clock gated by a flip/flop reset during I2S off and set by the LR signal. See related erratum (which is harmless for the clock master, i.e. your case). Plus the 16/24/32-tick counter to trigger the copy the received data from shift to the holding register.

> But more important for me might be figure 292,

I was using rev.3 at the moment of writing that post (there is a shift in numbering the figures between the two revisions), only finding out there is already rev.4 a few moments later. See my ''new RM'' thread...

> because I'm only interested in receiving data from i2s. How does the dma handle that?

> Puts he the 16 bit of a 24 bit wide sample of the i2s register in a 32 bit variable and the

> last 8 bits in a new 32 bit variable? Or puts he both incoming 16 bit (half-word) data in

> one 32 bit variable because I told him that the source is half-word and the destination

> (memory) is word?

You should re-read chapter 9, particularly 9.3.10. I know, it's no entertaining reading... :-)

Basically, you either use FIFO or not. If you don't use it, the DMA reads and writes the same data width, ignoring what you set for the memory side.

If you use FIFO, the DMA will perform data packing/unpacking, but it does not have MSB/LSB switch so the data are always packed ''first-come-stored-lower'', exactly as if it would store it immediately to the memory with incrementing the address (i.e. the behaviour with or withou FIFO is the same). In other words, in your case, if the DMA reads 16-bit halfwords from the peripheral eg.  0x0123, 0x4567, 0x89AB, 0xCDEF..., it will pack them into 32-bit words and store them to memory as 0x45670123, 0xCDEF89AB etc.

JW

feng
Associate
Posted on August 26, 2014 at 19:22

Hi,

How to register your ISR routin?

 Thanks

 

 

m: waclawek.jan

Posted: Thursday, March 07, 2013 4:56 PM

Subject: [STM32f4-Discovery] I2S RX Master with DMA

>> - I see that you already realized your mistake with DMA Ch0/Stream2 :-)

> Did I? ;-) The ref manual say it's DMA1/Ch0/Stream3 for SPI2_RX

In the original post you had DMA1/Ch0/Stream2; you have edited it one hour later, haven't you?

>>You probably want singletransfer from peripheral to the DMA's FIFO, and then possibly burstsfrom DMA to memory.

>>Mind the rules for DMA_SxNDTR (buffersize), buffer alignment, and the rule for 1kB address boundary, whenFIFO and memorybursts are employed.

> That makes sense. But I don't realy understand that rule entirely. Can you give me an example for my case here?

Read the note in 9.3.8 and chapter 9.3.11. Specifically,

''Theaddress pointers (DMA_SxPAR or DMA_SxM0AR registers) must be chosen soas to ensure that all transfers within a burst block are aligned on theaddress boundary equal to the size of the transfer.'' In other words, ifyou intend to transfer words in 8-beat bursts, the address of buffer hasto be aligned to 32-byte boundary. Refer to the manual of toolchain youuse to find out how to achieve that. Alternatively, don't use DMAbursts - I don't know what is the performance penalty but I would besurprised if it would be more than one cycle per transfer IF asimultaneous DMA and processor access to the RAM would occur at everyDMA transfer - and given the likelihood of this in ''normal'' applicationstimes the ratio of I2S data flow to total processing speed results IMHOin infinitesimally small value... In other words, there's almost noreason to be bothered with burst transfers.

>>- you still need to write the interrupt service routine (ISR) forDMA-complete. In that, you *don't* need to restart the DMA as you haveset it to circular mode. Be prepared that you'll need to

>>''decompose'' the received data, if in 24-bit mode.

>Can you explain this with some more details?

Referto figure 291. The received 24-bit data are stored in 32-bit registerMSB-first, then split to two halfwords inmost-significant-halfword-first, so if stored and then read from memoryas 32-bit word, the 0x8EAA33 will turn to 0x33008EAA. Before usingyou'll need to ''swap-and-shift it back''.

> Yes,indeed. But maybe the CPU might be fast enough to process the data,even if I only use two bigger buffers.

> The sample rate for themicrophone is 8kHz.

That's upon you to decide. But if the CPU is notfast enough, smaller buffers won't help either - smaller chunks of datawould be needed to process more often, that's the only difference.

> This is my current ISR for changing the new destination memorylocation ''on-the-fly''. > Might this work as I'd expect it?

Looks good. But still, why??? :-)

JW

Posted on August 26, 2014 at 20:34

How to register your ISR routine?

What's the question here?

The interrupt handlers have defined names, and are referenced via the vector table in startup.s

You have to enable the interrupt in the NVIC, and on the peripheral.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..