Author Topic: ATSAMD21G18A UART receive data from console  (Read 2651 times)

0 Members and 1 Guest are viewing this topic.

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
ATSAMD21G18A UART receive data from console
« on: September 14, 2022, 08:49:26 am »
Hi, I'm having trouble reading data on the SAMD21 MCU when sending data from PC console/terminal to the MCU. To send the data I'm using HTerm (setting can be seen in hterm.png), but in short BAUD is 115200, data size is 8 bits, 1 stop bit and no parity. The USB to UART converter I'm using is yp-05, and I tried using a pull up resistor for the RX pin (rx on the MCU side). As always when starting something I'm using https://github.com/ataradov/mcu-starter-projects/tree/master/samd21 ataradov's starter pack. The code to send data to the PC works fine (the base example), and in that example I just added 
Code: [Select]
 
static void uart_init(uint32_t baud)
{
....
 SERCOM3->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC; 
  NVIC_EnableIRQ(SERCOM3_IRQn);
...
}

void SERCOM3_Handler()
{
uart_puts("I");
if (SERCOM3->USART.INTFLAG.bit.RXC)
{
// Got a character
uint16_t rxData = SERCOM3->USART.DATA.reg;
return;
}
}
however when I try to send data from HTerm, the MCU simply stops and doesn't even print "test" any more. Did I forget to add something when creating the interrupt?

The complete main code is attached (but the code is basically the same as the basic UART example with removed LED and buttons..)

« Last Edit: September 14, 2022, 09:59:20 am by WILDERNESSBAGEL »
 

Offline Sauli

  • Contributor
  • Posts: 43
  • Country: fi
Re: ATSAMD21G18A UART receive data from console
« Reply #1 on: September 14, 2022, 10:09:07 am »
Just a quess: The line reading the received data is optimized out because rxData is not used. Therefore the interrupt is not cleared and MCU is constantly calling the interrupt service routine.
 

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #2 on: September 14, 2022, 10:39:51 am »
Thanks for the answer, I tried to use the variable to avoid having that problem, however it didn't change anything, when I try to send data it gets struck. I just print the value every loop. Updated code:
Code: [Select]
/*
 * Copyright (c) 2015, Alex Taradov <alex@taradov.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

//-----------------------------------------------------------------------------
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "samd21.h"
#include "hal_gpio.h"

HAL_GPIO_PIN(UART_TX,  A, 22)
HAL_GPIO_PIN(UART_RX,  A, 23)

#define DelayTicks(ticks)              {volatile uint32_t n=ticks; while(n--);}                   //takes 8 cycles
#define DelayMs(ms)                    DelayTicks(MS_TO_DLYTICKS(ms))
#define F_CPU 8000000
#define CYCLES_IN_DLYTICKS_FUNC        8
#define MS_TO_DLYTICKS(ms)               (F_CPU / (uint64_t)1000 * ms / CYCLES_IN_DLYTICKS_FUNC) // ((float)(F_CPU)) / 1000.0

uint16_t rxData;

static void uart_init(uint32_t baud)
{
  uint64_t br = (uint64_t)65536 * (F_CPU - 16 * baud) / F_CPU;

  HAL_GPIO_UART_TX_out();
  HAL_GPIO_UART_TX_pmuxen(PORT_PMUX_PMUXE_C_Val);
  HAL_GPIO_UART_RX_in();
  HAL_GPIO_UART_RX_pmuxen(PORT_PMUX_PMUXE_C_Val);

  PM->APBCMASK.reg |= PM_APBCMASK_SERCOM3;
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(SERCOM3_GCLK_ID_CORE) | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN(0);
  SERCOM3->USART.CTRLA.reg = SERCOM_USART_CTRLA_DORD | SERCOM_USART_CTRLA_MODE_USART_INT_CLK | SERCOM_USART_CTRLA_RXPO(1/*PAD1*/) | SERCOM_USART_CTRLA_TXPO(0/*PAD0*/);
  SERCOM3->USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_CHSIZE(0/*8 bits*/);
 
  while (SERCOM3->USART.SYNCBUSY.bit.CTRLB) ;
 
  SERCOM3->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC;
 
  NVIC_DisableIRQ(SERCOM3_IRQn);
  NVIC_ClearPendingIRQ(SERCOM3_IRQn);
  NVIC_SetPriority(SERCOM3_IRQn, 0);
  NVIC_EnableIRQ(SERCOM3_IRQn);

  SERCOM3->USART.BAUD.reg = (uint16_t)br;
  SERCOM3->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;
}

static void uart_putc(char c)
{
  while (!(SERCOM3->USART.INTFLAG.reg & SERCOM_USART_INTFLAG_DRE));
  SERCOM3->USART.DATA.reg = c;
}

static void uart_puts(char *s)
{
  while (*s)
    uart_putc(*s++);
}

static void sys_init(void)
{
  // Switch to 8MHz clock (disable prescaler)
  SYSCTRL->OSC8M.bit.PRESC = 0;
}

void SERCOM3_Handler()
{
uart_puts("I");
if (SERCOM3->USART.INTFLAG.bit.RXC)
{
// Got a character
rxData = SERCOM3->USART.DATA.reg;
return;
}
}

int main(void)
{

  sys_init();
  uart_init(115200);

  uart_puts("\r\nHello, world!\r\n");

  while (1)
  {
    uart_puts("test\r\n");

char tmp[30];
itoa(rxData, tmp, 10);
uart_puts("\r\ndata received ");
uart_puts(tmp);
uart_puts("\r\n");

DelayMs(1000);
  }

  return 0;
}
« Last Edit: September 14, 2022, 11:29:55 am by WILDERNESSBAGEL »
 

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #3 on: September 14, 2022, 11:31:31 am »
Ok so I replaced

Code: [Select]
void SERCOM3_Handler()
{
uart_puts("I");
if (SERCOM3->USART.INTFLAG.bit.RXC)
{
// Got a character
rxData = SERCOM3->USART.DATA.reg;
return;
}
}
with

Code: [Select]
void irq_handler_sercom3(){
uart_puts("I");
if (SERCOM3->USART.INTFLAG.bit.RXC)
{
// Got a character
rxData = SERCOM3->USART.DATA.reg;
return;
}
}
and everything seems to work fine. Can anyone explain the difference between these two functions, when to use which handler?
 

Offline WatchfulEye

  • Regular Contributor
  • *
  • Posts: 123
  • Country: gb
Re: ATSAMD21G18A UART receive data from console
« Reply #4 on: September 14, 2022, 11:35:49 am »
Look in the startup_sam21.c file. You will find a list of handlers which you can use.
 

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #5 on: September 14, 2022, 12:12:33 pm »
I see that the handlers are defined in the startup_sam21.c, but SERCOM3_Handler is also defined in samd21g18a.h. So why can't I use SERCOM3_Handler for this?
I was looking at this example https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serial and they use it. I do understand that they use arduino stuff, but I thought the basic interrupt that gets triggered is SERCOM3_Handler, no? Is it because I'm using the starter project as the base?
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11727
  • Country: us
    • Personal site
Re: ATSAMD21G18A UART receive data from console
« Reply #6 on: September 14, 2022, 04:19:32 pm »
Unlike with some other MCUs, there are no "standard" names for the interrupt handlers. You can name them whatever you want, but you must list them in the vector table.

SERCOM3_Handler is the name used in the Atmel/Microchip code. I hate that with passion, so I picked names that I liked.

You can even give them descriptive names like console_irq_handler if you want.

Header file just declares prototypes for the handlers Microchip likes. Arguably this should not be in the header file for the device. But there are much bigger issues with them than a couple irrelevant function prototypes.
« Last Edit: September 14, 2022, 04:56:29 pm by ataradov »
Alex
 
The following users thanked this post: WILDERNESSBAGEL

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #7 on: September 15, 2022, 07:26:11 am »
Quote
Unlike with some other MCUs, there are no "standard" names for the interrupt handlers. You can name them whatever you want, but you must list them in the vector table.

Oh, I see now, thanks that's exactly what I wanted to know. Like you said I'll rename it to something like "console_irq_handler" to make it more clear.
Quote
Header file just declares prototypes for the handlers Microchip likes. Arguably this should not be in the header file for the device. But there are much bigger issues with them than a couple irrelevant function prototypes.
Fair enough, it really isn't a big deal, was just wondering how it works. Thanks once again.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 854
Re: ATSAMD21G18A UART receive data from console
« Reply #8 on: September 15, 2022, 08:41:36 am »
Quote
Just a quess: The line reading the received data is optimized out because rxData is not used.
The data register will be a volatile, so will be read whether it is used or not. In fact, just the 'SERCOM3->USART.DATA.reg;' without any assignment to a var will still read the register, which is something you can do when you need a register read to happen but do not need the result.


Quote
SERCOM3_Handler is the name used in the Atmel/Microchip code. I hate that with passion, so I picked names that I liked.
The benefit I guess is they match the IRQn_Type name, although they could probably just tack on _Handler to the enum name, like SERCOM3_IRQn_Handler, to better indicate the relationship.


Another option for vector functions is to eliminate the need to create/override the weak functions (with correct/matching names). A function to both enable the nvic irq and set an isr function based on the IRQn_Type you are already using, can be a nice way to go-

https://godbolt.org/z/qj3fbrzY7
(just a quick example to show the idea, using the default handler to take care of all irq's)

I dislike having to create these fixed-name functions, partly because I use c++ and have most code (classes) in headers so becomes awkward to deal with these vector functions.

If you are counting every last clock cycle or ram use is precious, then the example above is probably not for you, although for speed you can still override a weak vector function which will then act as it normally does skipping the code in the default handler.

A simple example (stm32 in this case), there are only 2 source files, a main.cpp (which is seen here) and a startup.cpp (normal init code), the rest are headers (including the << code). The class simply populates the appropriate ram table function pointer based on IRQn_Type and I never have to directly deal with the vector functions.
Code: [Select]
//"some_includes.hpp"
//Uart2, TX=PA2,RX=PA3
u8 uartBuffer[64]; //a buffer for uart to use, << -> buffer -> tx isr -> out
Uart uart{ Uart2_A2A3, 230400, uartBuffer };
int main(){
    while(1){ uart << FG BLUE "Hello " FG GREEN "World" << endl; }
}

Not a big deal either way, so this info is just to show there are other options, and applies to many mcu's. Even the newer avr's can also do something like the example since it is now possible to know what irq you are in.
« Last Edit: September 15, 2022, 08:45:17 am by cv007 »
 
The following users thanked this post: WILDERNESSBAGEL

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #9 on: September 15, 2022, 10:14:29 am »
Quote
The data register will be a volatile, so will be read whether it is used or not. In fact, just the 'SERCOM3->USART.DATA.reg;' without any assignment to a var will still read the register, which is something you can do when you need a register read to happen but do not need the result.

Oh that is actually really good to know.

Just one more quick UART related question. When using full UART (flow control with RTS and CTS), once I set everything up is the handshake automatic? Or do I manually set RTS/CTS to high and low when the data is ready? And lastly can I use flow control along with interrupts?

I assume it's done manually since this is in the datasheet "Enabling and disabling the receiver by writing to
CTRLB.RXEN will set/clear the RTS pin after a synchronization delay" but I want to be sure.
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11727
  • Country: us
    • Personal site
Re: ATSAMD21G18A UART receive data from console
« Reply #10 on: September 15, 2022, 03:23:31 pm »
Just one more quick UART related question. When using full UART (flow control with RTS and CTS), once I set everything up is the handshake automatic?
It is automatic. That's why it is called hardware flow control. It is very hard to do reliably from the firmware without hardware support.

"Enabling and disabling the receiver by writing to CTRLB.RXEN will set/clear the RTS pin after a synchronization delay" but I want to be sure.
This just describes what happens in the edge case of disabling the receiver. Timing of when the RTS pin state is actually changed may be important in some cases. In most cases it does not matter.
Alex
 
The following users thanked this post: WILDERNESSBAGEL

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #11 on: September 16, 2022, 07:29:59 am »
Quote
This just describes what happens in the edge case of disabling the receiver. Timing of when the RTS pin state is actually changed may be important in some cases. In most cases it does not matter.

Oh, I see that makes sense, thanks for clarifying.
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6237
  • Country: es
Re: ATSAMD21G18A UART receive data from console
« Reply #12 on: September 18, 2022, 08:22:49 am »
Can anyone explain the difference between these two functions, when to use which handler?
In the first example, you declare a local variable RxData, store the buffer, then do nothing with it, that variable can't be seen by anything else in the program so it's like you did nothing, also if the compiler optimizes and omits that (Because he knows that variable is unused), the uart buffer will not be read and the next received byte will cause a buffer overflow.
In the second example RxData is global, so the whole program can see it.

Remember to declare variables modified by interrupts as volatile, otherwise you might start to see gremlins everywhere.
This would fail in a lot of scenarios:
Code: [Select]
bool start=0;            // Start is not volatile
// volatile bool start=0;// Should be this

void interrupt(void){
    start=1;             // So even if the variable is set here
}
void program(void){
    if (start)           // The compiler is unaware it can change anytime, this is likely to never work when enabling optimizations
        do_something();  // It sees start=always 0, as there's no program flow connected to the interrupt
}
Volatile tells the compiler the variable can change anytime, not just by normal program flow, so it doesn't "suppose" anything and won't optimize it.

The basic interrupt handling is something like this:
Code: [Select]
volatile uint8_t RxData;              // Receiving buffer
volatile bool gotRx;                  // Receive flag

void rx_interrupt(void){
    if(hardware_rx_flag){
        hardware_rx_flag=0;
        RxData = uart_buffer;
        gotRx=1;
     }
}

void normal_program(void){
    if(gotRx){
        uint8_t received = RxData;    // Store RxData so the buffer can be freed up for a new reception
        gotRx = 0;                    // Clear flag
        do_something_with(received);  // Do something with data
  }
}

Of course you can add a little more processing withing the interrupt to discard data, parsing few bytes won't hurt a lot.
« Last Edit: September 18, 2022, 08:32:25 am by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 
The following users thanked this post: WILDERNESSBAGEL

Offline WILDERNESSBAGELTopic starter

  • Contributor
  • Posts: 28
  • Country: hr
Re: ATSAMD21G18A UART receive data from console
« Reply #13 on: October 14, 2022, 07:05:40 am »
Sorry for the really late response, just wanted to say that I had gremlins in another project, because my variable wasn't volatile. Thanks for the explanation.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 854
Re: ATSAMD21G18A UART receive data from console
« Reply #14 on: October 14, 2022, 10:01:03 pm »
Quote
that variable can't be seen by anything else in the program so it's like you did nothing, also if the compiler optimizes and omits that (Because he knows that variable is unused), the uart buffer will not be read and the next received byte will cause a buffer overflow.
The uart hardware buffer will be read since the register will be defined as a volatile. What is done with the result, if anything, is a separate issue.

Simple example-
https://godbolt.org/z/8qa338rMc
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf