ATSAMD21G18A UART receive data from console - Page 1
Forum section: electronics / general

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 14, 2022, 08:49:26 am
content: 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 usinghttps://github.com/ataradov/mcu-starter-projects/tree/master/samd21ataradov's starter pack. The code to send data to the PC works fine (the base example), and in that example I just addedCode: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 characteruint16_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..)

author: Sauli
author_rank: Contributor
timestamp: September 14, 2022, 10:09:07 am
content: 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.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 14, 2022, 10:39:51 am
content: 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:/** 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.0uint16_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 characterrxData = 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;itoa(rxData, tmp, 10);uart_puts("\r\ndata received ");uart_puts(tmp);uart_puts("\r\n");DelayMs(1000);}return 0;}

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 14, 2022, 11:31:31 am
content: Ok so I replacedCode:void SERCOM3_Handler(){uart_puts("I");if (SERCOM3->USART.INTFLAG.bit.RXC){// Got a characterrxData = SERCOM3->USART.DATA.reg;return;}}withCode:void irq_handler_sercom3(){uart_puts("I");if (SERCOM3->USART.INTFLAG.bit.RXC){// Got a characterrxData = 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?

author: WatchfulEye
author_rank: Regular Contributor
timestamp: September 14, 2022, 11:35:49 am
content: Look in the startup_sam21.c file. You will find a list of handlers which you can use.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 14, 2022, 12:12:33 pm
content: 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 examplehttps://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports/creating-a-new-serialand 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?

author: ataradov
author_rank: Super Contributor
timestamp: September 14, 2022, 04:19:32 pm
content: 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.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 15, 2022, 07:26:11 am
content: QuoteOh, 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.QuoteFair enough, it really isn't a big deal, was just wondering how it works. Thanks once again.

author: cv007
author_rank: Frequent Contributor
timestamp: September 15, 2022, 08:41:36 am
content: QuoteThe 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.QuoteThe 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://"some_includes.hpp"//Uart2, TX=PA2,RX=PA3u8 uartBuffer; //a buffer for uart to use, << -> buffer -> tx isr -> outUart 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.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 15, 2022, 10:14:29 am
content: QuoteOh 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 toCTRLB.RXEN will set/clear the RTS pin after a synchronization delay" but I want to be sure.

author: ataradov
author_rank: Super Contributor
timestamp: September 15, 2022, 03:23:31 pm
content: Quote from: WILDERNESSBAGEL on September 15, 2022, 10:14:29 amIt is automatic. That's why it is called hardware flow control. It is very hard to do reliably from the firmware without hardware support.Quote from: WILDERNESSBAGEL on September 15, 2022, 10:14:29 amThis 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.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: September 16, 2022, 07:29:59 am
content: QuoteOh, I see that makes sense, thanks for clarifying.

author: DavidAlfa
author_rank: Super Contributor
timestamp: September 18, 2022, 08:22:49 am
content: Quote from: WILDERNESSBAGEL on September 14, 2022, 11:31:31 amIn 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:bool start=0; // Start is not volatile// volatile bool start=0;// Should be thisvoid 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 optimizationsdo_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:volatile uint8_t RxData; // Receiving buffervolatile bool gotRx; // Receive flagvoid 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 receptiongotRx = 0; // Clear flagdo_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.

author: WILDERNESSBAGEL
author_rank: Contributor
timestamp: October 14, 2022, 07:05:40 am
content: 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.

author: cv007
author_rank: Frequent Contributor
timestamp: October 14, 2022, 10:01:03 pm
content: QuoteThe uart hardware bufferwillbe 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