1

I'm trying to write a class to have the possibility of sending data using UART by TXE (TDR is empty) Interrupt. The class uses only USART1. The class has a ring data buffer to send. I have written a similar class for AVR (ATmega) and I would like to repeat such functionality.

At the moment I don't have the hardware (STM32), but I am using Proteus v.8.6. The start of sending is to enable TXE Interrupt. The main problem is TXE Interrupt is not generated. If I enable RXNE Interrupt and send one byte to the STM32, I will see all my data (from sending buffer) correctly are sent. Hence I can enter in USART1_IRQHandler only due to RXNE Interrupt. I'm not sure, perhaps it's a simulation problem. Also I use Atollic TrueSTUDIO v.9.3.0. I'll try to provide comprehensive information about my code. Sorry for my question, I'm a novice with STM32.

main.cpp

usart_transmitter UsartESP; //Usart class object declaration
int main(void){
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  UsartESP.ini();           //USART1 Initialization
  UsartESP.print("Hello from class!");
  while(1){}
}

stm32f1xx_it.cpp

extern usart_transmitter UsartESP;
void USART1_IRQHandler(void){
    //If Interrupt due to TDR is empty
    if (USART1->SR & USART_SR_TXE){
        UsartESP.handlerTDRE();
    }
}

usart_transmitter.h

const uint8_t BUFFER_SIZE = 100;    //Size of sending data buffer
class usart_transmitter{
  private:
    char _Data[BUFFER_SIZE];  //Data buffer
    uint8_t _PushIndex;
    uint8_t _PopIndex;
    uint8_t _IsOverflow;    

    void push(char symbol);
    char pop();
  public:
    void ini();                 //Configuration Usart1
    void print(char* message);  //Put string message to sending buffer
    void handlerTDRE();         //Interrupt TDR is empty handler
};

usart_transmitter.cpp

void usart_transmitter::ini(){
    for (uint8_t i=0; i<BUFFER_SIZE; i++){
        _Data[i] = 0;
    }
    _PushIndex = 0;
    _PopIndex = 0;
    _IsOverflow = 0;

    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    //Tx - Alternate function push-pull
    GPIOA->CRH &= ~(GPIO_CRH_CNF9_0); //CNF9[0] = 0
    GPIOA->CRH |= GPIO_CRH_CNF9_1;    //CNF9[1] = 1
    GPIOA->CRH |= GPIO_CRH_MODE9_0;   //MODE9[0] = 1
    GPIOA->CRH |= GPIO_CRH_MODE9_1;   //MODE9[1] = 1

    //Rx - Input pull-up
    GPIOA->CRH &= ~(GPIO_CRH_CNF10_0);  //CNF10[0] = 0
    GPIOA->CRH |= GPIO_CRH_CNF10_1;     //CNF10[1] = 1
    GPIOA->CRH &= ~(GPIO_CRH_MODE10_0); //MODE10[0] = 0
    GPIOA->CRH &= ~(GPIO_CRH_MODE10_1); //MODE10[1] = 0
    GPIOA->BSRR |= GPIO_ODR_ODR10;      //PxODR = 1

    USART1->CR1 = USART_CR1_UE;  //Usart1 Enable
    USART1->BRR = 16;            //4500000 baudrate

    USART1->CR1 |=
    (USART_CR1_TE) |    //Enable Transmitter
    (USART_CR1_RE) |    //Enable Receiver
    (USART_CR1_RXNEIE); //Enable RXNE Interrupt

    USART1->CR2 = 0;
    USART1->CR3 = 0;

    NVIC_EnableIRQ(USART1_IRQn);  //Enable USART1 global Interrupt
}

void usart_transmitter::push(char symbol){
    _Data[_PushIndex] = symbol;  //Put character to buffer
    _PushIndex++;
    if (_PushIndex > BUFFER_SIZE-1){
        _PushIndex = 0;
        _IsOverflow = 1;
    }
}

char usart_transmitter::pop(){
    char data = _Data[_PopIndex];
    _PopIndex++;
    if (_PopIndex > BUFFER_SIZE-1){
        _PopIndex = 0;
        _IsOverflow = 0;
    }
    return data;
}

void usart_transmitter::print(char* message){
    uint8_t i = 0;
    //Placing string to buffer
    while(message[i] != '\0'){
        push(message[i]);
        i++;
    }
    USART1->CR1 |= USART_CR1_TXEIE; //Enable TXE Interrupt
}

void usart_transmitter::handlerTDRE(){
    char byte = pop();
    USART1->DR = byte;
    //Checking criterion to stop sending
    if (_PopIndex == _PushIndex){
        if (!(_IsOverflow)){
            //Disable TXE Interrupt to avoid circularity of interruption
            USART1->CR1 &= ~(USART_CR1_TXEIE);
        }
    }
}

simulation results

German
  • 11
  • 3
  • Have you ever had any interrupt working? If the answer is no, then check that you are properly using the correct ISR handler name while preventing C++ function name mangling? – DKNguyen May 21 '20 at 18:19
  • Yes. I have a project with using Usart RXNE Interrupt like an echo in c++. The handler function name is correctly. As you can see at the picture my code correctly working only after receiving one byte to initiate entry to the handler function. I can't enter there only setting bit TXEIE in CR1 register. – German May 21 '20 at 19:11
  • I've just created a simple project (using c) to send one byte using Interrupt and I got the same result - my code doesn't enter the handler function until enabled RXNE Interrupt and it got at least one byte. – German May 21 '20 at 19:24
  • Most likely emulation issue of Proteus. TXE should go high shortly after setting TE. Have you tried sending one byte, and then checking if the TXE bit gets set, and with TXEIE enabled, it should generate an interrupt? – Justme May 21 '20 at 20:52
  • I don't know how to check the TXE bit state using Proteus. However, if I understood right after the first byte of a message has been sent following bytes are being sent by means TXE Interrupt (because my class handler method is called only if TXE bit is set). I'm inclined to think it's Proteus emulation issues. Unfortunately, I don't have the possibility to check real registers states. – German May 21 '20 at 21:20
  • check the txe bit state using your software. did you try to send a character (or many) by writing to the tx buffer so that the empty state changes (and then it may cause an interrupt). If already empty that is not an event I wouldnt expect an interrupt until it goes from not empty to empty...but I would have to read up on it and experiment... – old_timer May 22 '20 at 01:42
  • write until the tx buffer is not empty (may be more than one write) then stop and see if the state change back to empty fires an interrupt. – old_timer May 22 '20 at 01:43
  • According to Proteus debugging: after simulation launching, SR has value 0хС0 (TXE:1, TC:1). This is consistent with the reference manual (p.827). After that, as a result, of USART initialization CR1 got values 0xAC, 0x20 (TE:1, RE:1, RXNIE:1, TXEIE:1, UE:1). Thus I have TXEIE:1, TXE:1 simultaneously. However, Interruption is not generated (reference manual p.792). When I sent the character 'q' to MK then after his delivering SR got value 0xE0 (TXE:1, TC:1, RXNE:1). It caused Interruption. The Interruption handler was entered in. – German May 22 '20 at 11:02
  • The value of the first character (‘H’ – 0x48) was put to the register DR. Register SR got value 0х20 (TXE:0, TC:0, RXNE:1). After finishing of first character transmission SR got value 0хE0 (TXE:1, TC:1, RXNE:1) and interruption was generated again. The register DR got the next value (‘e’ – 0x65) to transmit. This is repeated until the entire message is transmitted. I've noticed a strange feature of Proteus. After transmitting the entire message register CR1 is written value 0x24 (TE:0). That is the transmitter is disabled. – German May 22 '20 at 11:03
  • The last statement is no more true. I checked my hypothesis and reset bit TE after transmission completion. I've forgotten about it. – German May 23 '20 at 08:14

0 Answers0