STM32L011K4 with DMA RX using I2C data not appearing in buffer - c

I am trying to query a temperature sensor (TMP007) over an I2C bus using the L011K4's DMA controller. I've managed to get TMP007 to respond with good data, but that data isn't appearing in the buffer array that I pass my I2C DMA receive function.
What I expect to happen is that the I2C hardware is set up by the software in the Init and Read function, then the start condition is generated, the slave's address and register address are sent, then a second start/a restart is sent out followed by the slave's address again, and finally ending with a transmission of two bytes from TMP007 and a stop condition.
The data acquired from the temperature sensor should be appearing in a buffer that is passed into Start_Read (as MemoryBaseAddress), but that data does not appear when viewing the processor in debug mode after the read function has been called.
The Channel 3 transfer complete interrupt is being called, so I know the DMA has activated and managed the RX transfer, but the data doesn't appear in the desired buffer.
Here's an image of the communication being observed by a logic analyzer: https://i.imgur.com/SzySsXl.png
DIO2 is connected to the onboard LED, which tells us that the DMA transfer has completed (it's the only ISR with the LED toggle).
Any help would be greatly appreciated!
STM32L011K4 Datasheet: https://www.st.com/content/ccc/resource/technical/document/datasheet/42/c0/ab/e5/71/7a/47/0b/DM00206508.pdf/files/DM00206508.pdf/jcr:content/translations/en.DM00206508.pdf
STM32L011K4 Reference Manual: https://www.st.com/resource/en/reference_manual/dm00108282-ultralowpower-stm32l0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
void I2C1_DMA_Start_Read(int SlaveAddress, int RegisterAddress, int* MemoryBaseAddress, int BufferSize) {
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1
DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable Channel 3 DMA
DMA1_Channel3->CCR &= ~(0x00007FFF); // Channel 3 DMA mask
// Configure DMA Channel 3 for 16 bit memory and peripheral, and other aliased settings (reference manual page 249, 10.4.3)
DMA1_Channel3->CCR |= (0b11 << 12)|(0b01 << 10)|(0b01 << 8)|DMA_CCR_MINC|DMA_CCR_TEIE|DMA_CCR_TCIE;
DMA1_Channel3->CPAR = (uint32_t) &(I2C1->RXDR);
DMA1_Channel3->CMAR = (uint32_t) MemoryBaseAddress;
DMA1_Channel3->CNDTR = (uint16_t) BufferSize;
DMA1_Channel3->CCR |= DMA_CCR_EN; // Activate DMA channel 3
I2C1->CR1 |= I2C_CR1_NACKIE; // Enable no acknowledge interrupt for I2C1
I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); // Set up the slave address
I2C1->CR2 &= ~I2C_CR2_RD_WRN; // Master requests a write transfer in order to send the register
I2C1->CR1 |= I2C_CR1_PE|I2C_CR1_RXDMAEN; // Enable I2C1 and RX DMA for I2C1
I2C1->CR2 &= ~(0xFF << I2C_CR2_NBYTES_Pos); // Mask byte count
I2C1->CR2 |= (0x01 << I2C_CR2_NBYTES_Pos); // Transfer one byte (the slave's register address)
while(!(I2C1->ISR & I2C_ISR_TXE)); // Wait until TX data register is empty
I2C1->TXDR = RegisterAddress; // Set the transmit data to the desired register address
I2C1->CR2 |= I2C_CR2_START; // Generate start condition
while(I2C1->CR2 & I2C_CR2_START); // Wait until hardware clears the start bit
while(!(I2C1->ISR & I2C_ISR_TC)); // Wait until hardware clears the transmit complete bit
I2C1->CR2 |= I2C_CR2_RD_WRN; // Master requests a read transfer after sending over the register address
I2C1->CR2 &= ~(0xFF << I2C_CR2_NBYTES_Pos); // Mask byte count
I2C1->CR2 |= (0x02 << I2C_CR2_NBYTES_Pos); // Master looks for two bytes from the slave
I2C1->CR2 |= I2C_CR2_START; // Generate start condition
while(I2C1->CR2 & I2C_CR2_START); // Wait until hardware clears the start bit
return;
}
void DMA1_Channel2_3_IRQHandler() {
if(DMA1->ISR & DMA_ISR_TCIF3) {
GPIOB->ODR ^= 1 << 3; // Toggle the onboard LED as an indicator
I2C1->CR2 |= I2C_CR2_STOP; // Generate stop condition
while(I2C1->CR2 & I2C_CR2_STOP); // Wait until hardware clears the stop bit
DMA1->IFCR |= DMA_IFCR_CTCIF3; // Clear transfer complete interrupt bit
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1
DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable Channel 3 DMA
} else if(DMA1->ISR & DMA_ISR_TEIF3) {
I2C1->CR2 |= I2C_CR2_STOP; // Generate stop condition (if I2C comm is already stopped on error, delete this and following line)
while(I2C1->CR2 & I2C_CR2_STOP); // Wait until hardware clears the stop bit
DMA1->IFCR |= DMA_IFCR_CTEIF3; // Clear transfer error interrupt bit
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1
DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable Channel 3 DMA
}
return;
}
void I2C1_IRQHandler() {
if(I2C1->ISR & I2C_ISR_NACKF) {
//GPIOB->ODR ^= 1 << 3; // Toggle the onboard LED as an indicator
I2C1->ICR |= I2C_ICR_NACKCF;
}
return;
}

Related

readinf adc value on nucleo l476rg infiniteu loop problem

I have this problem when trying to read the ADC value (using adc1_in6) on the nucleo-l476rg micro controlle: I get in an infinite loop when checking if the EOC bit of the status register (ISR) "adc_read function".
Here's the adc.c file content:
#include "stm32l476xx.h"
#include "adc.h"
#define ADC1_SEQL1 0X00
void pa1_adc1_init(void)
{
/**************configure adc gpio pin ******************/
//enable clock access to GPIOA
RCC->AHB2ENR |= (1U<<0);
//set the mode of pa1 to analog mode
GPIOA->MODER |=(1U<<2);
GPIOA->MODER |=(1U<<3);
/****************configure adc periph ******************/
// enable clock access to adc module
RCC->AHB2ENR |=(1U<<13);
/*******configure adc parameters****************/
//conversion sequence start
ADC1->SQR1 |=(1U<<7);
ADC1->SQR1 |=(1U<<8);
//conversion sequence length
ADC1->SQR1 &= 0xFFFFFFF0UL;
//Enable adc module
// Check voltage regulator status
ADC1->CR &= 0x00000000;
ADC1->CR |= ADC_CR_ADVREGEN;
while (!(ADC1->CR & ADC_CR_ADVREGEN)) {
// Wait for voltage regulator startup time to pass
}
// Set ADVREGEN bit to 1
ADC1->CR |= ADC_CR_ADVREGEN;
// Wait for voltage regulator startup time to pass
// Set ADEN bit to 1
ADC1->CR |= ADC_CR_ADEN;
}
void start_conversion(void)
{
/*start adc conversion */
ADC1->CR |=ADC_CR_ADSTART;
}
uint32_t adc_read(void)
{
// wait for conversion to be complete
while (!(ADC1->ISR & ADC_ISR_EOC)){}
//read converted result from ADC1 data register
return (ADC1->DR);
}
I reviewed the reference manual to check if there are any specific requirements about the ISR register but it doesn't show anything special to do.
You need to have delay (or readback as access is strongly ordered) when enable the clock as the next register access is very likely to fail.
ADC has more than one clock domain and enabling digital part is not enough
You should calibrate the ADC before use.

Why are my DFSDM register bits not changing on my STM32L476?

I am facing a problem with the DFSDM (Digital Filter for Sigma-Delta Modulation) of my STM32L476RE. Indeed, despite the fact that I have enabled the clock for the DFSDM, all the registers related to the my filter (in the code below: DFSDM1_Filter1->...) never change when I set a bit, whereas the registers related to my channel DFSDM1_Channel1->... do change well. As setting the clock is usuallythe solution for this kind of problem, I am stuck because, as I said, my clock is already enabled for DFSDM. Can anyone help? Thanks in advance!
// Enable DFSDM clock
RCC->CCIPR &= ~RCC_CCIPR_DFSDM1SEL; // Choose PCLK2 as clock
RCC->APB2ENR |= RCC_APB2ENR_DFSDM1EN; // Enable DFSDM clock
// Make sure DFSDM is disabled
DFSDM1_Channel0->CHCFGR1 &= ~DFSDM_CHCFGR1_DFSDMEN;
// Channel 1
DFSDM1_Channel1->CHCFGR1 &= ~DFSDM_CHCFGR1_CHINSEL; // Select own input (DATIN1)
DFSDM1_Channel1->CHCFGR1 &= ~DFSDM_CHCFGR1_DATMPX; // Data is taken from DATIN1 as 1 bit values
DFSDM1_Channel1->CHCFGR1 |= (0x01 <<2U); // Use internal clock for SPI format
DFSDM1_Channel1->CHCFGR1 |= (0x00 <<0U); // Strobe data on rising edge
DFSDM1_Channel1->CHCFGR1 |= DFSDM_CHCFGR1_CHEN; // Enable channel 1
// Filter 1 -> Channel 1, continuous regular conversion
DFSDM1_Filter1->FLTCR1 |= (0x01 <<24U); // Regular channel 1
DFSDM1_Filter1->FLTCR1 |= DFSDM_FLTCR1_FAST; // Fast mode enabled
DFSDM1_Filter1->FLTCR1 |= DFSDM_FLTCR1_RCONT; // Continuous mode
DFSDM1_Filter1->FLTCR1 |= DFSDM_FLTCR1_RDMAEN; // Enable DMA requests on regular conversion
DFSDM1_Filter1->FLTFCR |= ( 0x02 <<29U); // Select Sinc2 filter mode
DFSDM1_Filter1->FLTFCR |= ((1023) <<16U); // with FOSR = 1024
DFSDM1_Filter1->FLTFCR |= ((255) <<00U); // with IOSR = 256
DFSDM1_Filter1->FLTCR1 |= DFSDM_FLTCR1_DFEN; // Enable filter
// Global enable for DFSDM interface
DFSDM1_Channel0->CHCFGR1 |= DFSDM_CHCFGR1_DFSDMEN;
// Start conversion
DFSDM_Filter1->FLTCR1 |= DFSDM_FLTCR1_RSWSTART;

STM32L011K4 with DMA using I2C Start Condition not Occurring

I'm attempting to use the STM32L011K4's DMA controller to communicate with slave devices over I2C. Currently, I have no slave devices and am just trying to get the microcontroller to send the start condition out onto the I2C bus, but that is not happening.
When I run this code in debugging mode through the STM32CubeIDE, I notice that the start bit is set, but it never clears even though the reference manual says it should be cleared by hardware once the start condition occurs (page 656 for I2C_CR2).
Monitoring the SDA and SCL lines on my oscilloscope also show that they are a logical 1. Note: I'm using the NUCLEO-L011K4 on a breadboard, so the IO pins are tied to Vref through 1k resistors. All configuration registers appear to contain the desired value when the code is stuck sending the start condition, so I don't believe they are getting clobbered by a random line of code.
I'm not sure what's preventing the start condition from being sent, so any help would be greatly appreciated.
STM32L011K4 Datasheet:
https://www.st.com/content/ccc/resource/technical/document/datasheet/42/c0/ab/e5/71/7a/47/0b/DM00206508.pdf/files/DM00206508.pdf/jcr:content/translations/en.DM00206508.pdf
STM32L011K4 Reference Manual: https://www.st.com/resource/en/reference_manual/dm00108282-ultralowpower-stm32l0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
Initialization code:
void Init_I2C1_DMA() {
/* Basic I2C Initialization for 100 kHz I2C, 24 MHz SYSCLK, /1 APB1 scaler */
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable peripheral clock for I2C1
I2C1->CR1 &= ~(I2C_CR1_PE); // Disable I2C1
I2C1->CR1 = 0; // Reset CR1
I2C1->TIMINGR = 0; // Reset timer settings
/* APB1 clock (I2C1 clock) is set in RCC_CFGR reg -- keep at divide by 1 -- 24 MHz SYSCLK
* Refer to table 103 for timing value source. t_presc was found to be 250 ns for 100 kHz I2C, so PRESC was set to match that for SYSCLK = 24 MHz
* All subsequent settings are copied from table 103 from STM32L011K4 reference manual
*/
I2C1->TIMINGR |= (0x5 << 28)|(0x4 << 20)|(0x2 << 16)|(0x0F << 8)|(0x13 << 0);
/* Desired settings:
* RXDMAEN enable, ANF enable.
*/
I2C1->CR1 |= (0x8 << I2C_CR1_DNF_Pos)|I2C_CR1_ERRIE;
I2C1->CR2 = 0; // Reset contents (ACKs are enabled by default)
NVIC_EnableIRQ(I2C1_IRQn);
NVIC_SetPriority(I2C1_IRQn, 0);
/* DMA initialization */
/* Since this is peripheral to memory, we use I2C1_RX, which is available on DMA channels 3,7. We used channel 3, but 7 would work the same. */
RCC->AHBENR |= RCC_AHBENR_DMA1EN; // Enable peripheral clock for DMA1
DMA1_Channel3->CCR &= ~(0x00000001); // Disable Channel 3 DMA
// Configure DMA channel mapping
DMA1_CSELR->CSELR &= ~0x00000F00; // Channel 3 re-mapping mask
DMA1_CSELR->CSELR |= 0x00000600; // Channel 3 re-mapped to I2C1_RX
/* Configure NVIC for DMA */
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
return;
}
void I2C1_DMA_Start_Read(uint8_t SlaveAddress, uint8_t RegisterAddress, int* MemoryBaseAddress, int BufferSize) {
// We need to put the device address out on the serial line before we can hand it over to the DMA
I2C1->CR1 &= ~(I2C_CR1_PE); // Disable I2C1
DMA1_Channel3->CCR &= ~DMA_CCR_EN; // Disable Channel 3 DMA
DMA1_Channel3->CCR &= ~(0x00007FFF); // Channel 3 DMA mask
// Configure DMA Channel 3 for 16 bit memory and peripheral, and other aliased settings (reference manual page 249, 10.4.3)
DMA1_Channel3->CCR |= (0b01 << 10)|(0b01 << 8)|DMA_CCR_MINC|DMA_CCR_TEIE|DMA_CCR_TCIE;
DMA1_Channel3->CPAR = (uint32_t) RegisterAddress;
DMA1_Channel3->CMAR = (uint32_t) MemoryBaseAddress;
DMA1_Channel3->CNDTR = (uint16_t) BufferSize;
I2C1->CR1 &= (~I2C_CR1_TXDMAEN); // Disable TX DMA for I2C1
I2C1->CR1 |= I2C_CR1_RXDMAEN; // Enable RX DMA for I2C1
// I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); // Set up the slave address for DMA read
while(!(I2C1->ISR & I2C_ISR_TXE));
I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1)); // Set up the slave address for DMA read
I2C1->CR2 |= I2C_CR2_RD_WRN;
DMA1_Channel3->CCR |= DMA_CCR_EN; // Activate DMA channel 3
I2C1->CR1 |= I2C_CR1_PE; // Enable I2C1
I2C1->CR2 |= I2C_CR2_START; // Generate start condition
while(I2C1->CR2 & I2C_CR2_START); // Wait until hardware clears the start bit
// ???
return;
}
According with reference manual(p. 604)
you need uncomment I2C1->CR2 |= ((uint8_t) (SlaveAddress << 1)); and comment I2C1->TXDR |= ((uint8_t) (SlaveAddress << 1)); for set slave address, and you need set the number of bytes to be transferred.
I can't see your initialisation of GPIO. Check is GPIO settings right (Alternative function and open-drain mode).
Also in reference manual written this
PE must be kept low during at least 3 APB clock cycles in order to perform the software
reset. This is ensured by writing the following software sequence: - Write PE=0 - Check
PE=0 - Write PE=1.
I think you should try to do so.
Also I advise using 4.7k resistor for pulling to VDD.

MSP430 I2C reading a SDP610 differential pressure sensor issue

I am trying to read a SDP610 sensiron differential pressure sensor via a Texas Instruments msp430.
I am having the issue of the sensor not acknowledging the command and thus, not communicating the pressure value itself. Note I have confirmed that the sensor works by hooking it up to an arduino via an opensource library and, I can see the data via this. Note my IDE is code composer. My chips is MSP430FR2311 (a launch pad breakout board).
My hardware setup is 4 wires. Vcc(3.3V), Ground(0V), SDK and SCL. The SDK and SCL lines are pulled to VCC with a 4.7Kohm resistor as per specification.
I have the following code for my MSP430 see below:
However, I do not see the response of the sensor via a logic analyser. Here is my capture. You will have to click the link. Note the top line is clock and bottom is the data.
MSP430 output.
The logic flow for reading the sensor from the datasheet and from the arduino code is as follows:
Write address of the device to the I2C line(8 bit h81)
Wait for slave acknowledge
Write command for reading (8 bit hF1)
Wait for slave acknowledge
Slave holds the master
Slave outputs 3 bytes (2 data one msb and 1 lsb then a check sum)
acknowledge
This is the datasheet for the sensor
Any tips to why the sensor is not responding.
CODE
void Read_Diff_pressure(void)
{
int rx_byte;
UCB0CTL1 |= UCTXSTT+ UCTR; // Generating START + I2C transmit (write)
UCB0I2CSA = SDP610Address; // SDP610 7 bit address 0x40
UCB0TXBUF = SDP610Read; // sending the read command 0x78
while(!(UCB0IFG & UCTXIFG)); //wait until reg address got sent
while( UCB0CTL1 & UCTXSTT); //wait till START condition is cleared
UCB0CTL1 |= UCTXSTT; //generate RE-START
UCB0I2CSA = SDP610Address; // SDP610 7 bit address 0x40
UCB0CTL1 &=~ UCTR; //receive mode
while( UCB0CTL1 & UCTXSTT); //wait till START condition is cleared
rx_byte = UCB0RXBUF; //read byte
//while(!(UCB0IFG & UCRXIFG)); //wait while the Byte is being read
UCB0CTL1 |= UCTXNACK; //generate a NACK
UCB0CTL1 |= UCTXSTP; //generate stop condition
while(UCB0CTL1 & UCTXSTP); //wait till stop condition got sent```
Pressure_result = rx_byte;
}
void InitI2C_diff(void)
{
PAOUT |= I2C_SCL_PIN|I2C_SDA_PIN;//P1.2(SDA) - P1.3(SCL) as per silk screen defined in a header
PADIR |= I2C_SCL_PIN|I2C_SDA_PIN;
PASEL0 |= (I2C_SCL_PIN|I2C_SDA_PIN); // configure I2C pins (device specific)
UCB0CTLW0 |= UCSWRST; // put eUSCI_B in reset state
UCB0CTLW0 |= UCMODE_3 | UCSYNC | UCMST; // I2C master mode, SMCL
UCB0CTL1 = UCSSEL_2 + UCSWRST; //use SMCLK + still reset
UCB0BR0 = 10; // default SMCLK 1M/10 = 100KHz
UCB0BR1 = 0; //
UCB0I2CSA = SDP610Address; //The address of the device
UCB0CTLW0 &= ~UCSWRST; // eUSCI_B in operational state
//UCB0BRW = 64; // baudrate = SMCLK / 64
}
int main(void)
{
InitI2C_diff();//Init the i2c
while (1) { // Mainloop
Read_Diff_pressure();
delay(1000);//1 Second delay before re looping
}
}
A few parts were missing compared to an old Project implementation of mine (VCNL3020 + MSP430).
For example:
set the 7-bit addressing mode, single-master environment, I2C Master, synchronous mode,..Maybe I have overlooked it
Does the sensor need itself an init?
The Init Part of the I2C only looked like this:
void I2CInit( void )
{
P1SEL |= BIT6 + BIT7; // Assign I2C pins to USCI_B0
P1SEL2|= BIT6 + BIT7;
UCB0CTL1 |= UCSWRST; // Enable SW reset
UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // 7-bit addressing, single-master environment, I2C Master, synchronous mode
UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
UCB0BR0 = 16; // fSCL = SMCLK/UCB0BR1
UCB0BR1 = 0;
UCB0I2CIE |= UCNACKIE; // Enable not-acknowledge interrupt
UCB0I2CSA=slave_adress;
UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
IE2 |= UCB0TXIE + UCB0RXIE; // Enable TX&RX interrupts
}
To not make it unnecessary complicated, you could check my implementation on github and see if it helps Github Link I2C MSP430 Main
I hope this helps a bit- have fun!
I'm not sure what your hardware looks like, but your I2C pull-ups sound too large.I know of lot of app notes talk about about 4.7K, but I'd look at the rise time of the lines with an oscilloscope. If you don't have access to a scope, I'd use 1K or 2 K and see what happens.

Getting DMA USART to work on STM32L053R8T6

I am having an issue in getting my computer (virtual COM port, to be exact) to communicate with my STM32L053R8T6 (Nucleo) board by DMA and USART. Here is my code for the DMA and USART part:
#include "Device/Include/stm32l0xx.h" // Device header
#include "JB.h"
#include <string.h>
#define PCLK 32000000
#define BAUD 19200
uint8_t stringtosend[] = "test\n";
uint8_t stringtoreceive[] = " ";
void ENABLE_UART_DMA(void){
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //enable periph.clk for DMA1
/**Enabling DMA for transmission
* DMA1, Channel 4 mapped for USART2TX
* USART2 TDR for peripheral address
* stringtosend for data address
* Memory increment, memory to peripheral | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C4S) | (4 << (3 * 4));
DMA1_Channel4->CPAR = (uint32_t)&(USART2->TDR);
DMA1_Channel4->CMAR = (uint32_t)stringtosend;
DMA1_Channel4->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;
/**Enabling DMA for reception
* DMA1, Channel 5 mapped for USART2RX
* USART2 RDR for peripheral address
* stringtoreceive for data address
* Data size given
* Memory increment, peripheral to memory | 8-bit transfer | transfer complete interrupt**/
DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~DMA_CSELR_C5S) | (4 << (4 * 4));
DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);
DMA1_Channel5->CMAR = (uint32_t)stringtoreceive;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);
DMA1_Channel5->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_EN;
NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 0); //NVIC enabled, max priority, channels 4-7
NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn);
}
void CONFIGURE_UART_PARAM(void){
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
RCC->APB1ENR |= ( 1ul << 17); //Enable USART#2 clock
GPIOA->AFR[0] &= ~((15ul << 4* 3) | (15ul << 4* 2) ); //Clear PA2,PA3
GPIOA->AFR[0] |= (( 4ul << 4* 3) | ( 4ul << 4* 2) ); //Set PA2,PA3
GPIOA->MODER &= ~(( 3ul << 2* 3) | ( 3ul << 2* 2) ); //Same as above
GPIOA->MODER |= (( 2ul << 2* 3) | ( 2ul << 2* 2) );
USART2->BRR = PCLK/BAUD;
USART2->CR3 = USART_CR3_DMAT | USART_CR3_DMAR; //Enable DMA mode in transmit and receive
/*UART enabled for transmission and reception*/
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
while((USART2->ISR & USART_ISR_TC) != USART_ISR_TC)
{
/* add time out here for a robust application */
}
USART2->ICR = USART_ICR_TCCF;
}
void CONFIGURE_EXTI(void){
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
EXTI->FTSR |= EXTI_FTSR_TR13; //falling edge trigger
EXTI->IMR |= EXTI_IMR_IM13; //unmask
NVIC_SetPriority(EXTI4_15_IRQn, 0); //def interrupt
NVIC_EnableIRQ(EXTI4_15_IRQn);
}
/*************************************************************************************************************************************************************************************************************************/
/*************************************************************************************************************************************************************************************************************************/
/*Interrupt Handlers*/
void DMA1_Channel4_5_6_7IRQHandler(void){
if((DMA1->ISR & DMA_ISR_TCIF4) == DMA_ISR_TCIF4){
DMA1->IFCR = DMA_IFCR_CTCIF4; //Clear Channel 4 Transfer Complete flag
}
else if((DMA1->ISR & DMA_ISR_TCIF5) == DMA_ISR_TCIF5){
DMA1->IFCR = DMA_IFCR_CTCIF5; //Clear Channel 5 Transfer Complete flag
DMA1_Channel5->CCR &= ~DMA_CCR_EN;
DMA1_Channel5->CNDTR = sizeof(stringtoreceive);/* Data size */
DMA1_Channel5->CCR |= DMA_CCR_EN;
}
}
void EXTI4_15_IRQHandler(void){
if(!(GPIOC->IDR & (1 << 13))){
/* Clear EXTI 13 flag */
EXTI->PR = EXTI_PR_PIF13;
/* start 8-bit transmission with DMA */
DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
}
}
//void EXTI4_15_IRQHandler(void){
// if((EXTI->PR & EXTI_PR_PIF13) == EXTI_PR_PIF13){
// /* Clear EXTI 13 flag */
// EXTI->PR = EXTI_PR_PIF13;
//
// /* start 8-bit transmission with DMA */
// DMA1_Channel4->CCR &= ~DMA_CCR_EN; //channel disable
// DMA1_Channel4->CNDTR = sizeof(stringtosend);/* Data size */
// DMA1_Channel4->CCR |= DMA_CCR_EN; //channel enable
// }
//}
Now then, this specific code is based on an example from the STM32L0 snippets package 1.20, USART/Communcation Using DMA. USART 1 was simply redefined to USART 2 (as that is the one used by the virtual COM port), and the DMA channels were redefined according to that as well. However, the problem here is very simple: it will only print stringtosend once (would like to do it every time button B1 is pressed), and will not receive data by RX either - as if it completely ignores the DMA interrupt handler - which I am not sure how to test (no trace features available on this board). What I have seems to reflect the reference manual well enough, and all the main does is:
int main(){
SystemCoreClockInit();
CONFIGURE_UART_PARAM();
ENABLE_UART_DMA();
pushbutton_def();
CONFIGURE_EXTI();
while(1){
}
...which should just react to the defined interrupts, however it does not, and for the life of me, I cannot see why. I would love if you could help me - I would also like to avoid HAL or LL APIs - this is not a complex enough project to warrant their usage (several inputs, outputs, comms between two boards by USART/DMA), plus I would prefer to learn working closer to the register level.
Thanks!
edit (in response to Berendi's suggestions):
1. GPIOC was defined in another file, called with pushbutton_def():
RCC->IOPENR |= (1UL << 2); //enable GPIOC
I understand exactly what you mean by your explanation (indeed, the register referred by those two is "the same", 0x00000020U), but I am not sure as to how to redefine it: here is my attempt after looking at the reference manual (SYSCFG part) and the source (still, it does not work):
SYSCFG->EXTICR[3] = ((SYSCFG->EXTICR[3] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC);
As suggested, I have added USART2->ICR = USART_ICR_TCCF; to the EXTIhandler, right after the DMA channels. I have kept it in the USART definition. The message is still only being sent once, though.
GPIOC is not enabled
Here,
RCC->IOPENR |= ( 1ul << 0); //Enable GPIOA clock
you should enable GPIOC too.
EXTI13 is mapped to PA13
Here,
SYSCFG->EXTICR[0] = ((SYSCFG->EXTICR[0] & 0x0000) | SYSCFG_EXTICR4_EXTI13_PC); //clear EXTICR and set to PC13(B1)
you are setting the configuration register for EXTI0-EXTI3, actually mapping EXTI1 to PC1. EXTI13 remains mapped to PA13, which is actually SWDIO, connected to the onboard debugger. I guess the traffic on SWDIO triggers the EXTI interrupt, the handler checks PC13 which is always reading 0 because the port is disabled, and enables DMA. DMA transmit works only once though, because
USART_ISR_TC is not cleared in the interrupt
but only once at startup. You should move this line
USART2->ICR = USART_ICR_TCCF;
to the EXTI interrupt handler.
I'm not sure why receiving doesn't work, perhaps the DMA handler has no chance to run, because EXTI is constantly retriggered by SWD traffic. Both interrupts have the same priority, the one with lower interrupt number wins, which is the EXTI handler. If it's always retriggered before it finishes, then it will be called again, not letting the other handler to run.

Resources