[TUT][SOFT][C]Understanding ASF4 Atmel Studio 7 code Part2

1 post / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In part 1 we looked at the USART_0_example function to transmit bytes over the virtual serial com port to the data visualizer. Next we will dig deeper into this function to look at the actual transmission of the hello world string bytes to the data visualizer. You will recall the main function looked like this 

int main(void)
{
 /* Initializes MCU, drivers and middleware */
 atmel_start_init();
 USART_0_example();
 /* Replace with your application code */
 
 while (1) {
 }
}

We covered USART_0_example in part1 but notice there is another function in main the atmel_start_init function. Besides initializing the mcu it contains a function to initialize the USART_0 which also contains functions to transmit the actual hello world bytes out of Sercom3 our virtual com port.

void USART_0_init(void)
{
 USART_0_CLOCK_init();
 usart_sync_init(&USART_0, SERCOM3, (void *)NULL);
 USART_0_PORT_init();
}

We will concentrate on line two as the other two lines of code constitute a whole discussion of their own. These lines initiate the clocks and usart board pins of the mcu necessary for the actual transmission of usart data.The usart_sync_init function returns a 32bit integer zero if the function was successful. The function contains three parameters consisting of a handle in the form of the USART descriptor "descr" a define statement for the memory location of SERCOM (our virtual COM port) "hw" and a void pointer "func". The first line of the function creates a 32bit integer variable "init_status"  for use in the third line of code. The second line is an assert statement which is a Boolean statement in this case an error check looking for both a handle "descr" and a defined SERCOM memory location "hw".The third line is setting the init_status variable equal to the return value of  the "_usart_sync_init" function. The "_usart_sync_init"_function containes an important function called the " _usart_init(hw)" as this is where a number of the SERCOM USART register bits are initialized setting baud rate, checking the  peripheral and bus clocks are synchronized. It is interesting to note the first five lines of the overall "usart_sync_init" function (note: don't confuse the two functions as they are only differentiated by an underscore ) are to configure the SERCOM USART properly for transmission and reception of bytes and check that configuration for errors all before we actually transmit the bytes at the end of the function with the "usart_sync _write" function . 

int32_t usart_sync_init(struct usart_sync_descriptor *const descr, void *const hw, void *const func)
{
 int32_t init_status;
 ASSERT(descr && hw);
 init_status = _usart_sync_init(&descr->device, hw);
 if (init_status) {
  return init_status;
 }

 descr->io.read  = usart_sync_read;
 descr->io.write = usart_sync_write;

 return ERR_NONE;
}

Lets take a closer look at the " _usart_init(void *const hw) " function found inside the "_usart_sync_init" function.

static int32_t _usart_init(void *const hw)

If we expand this function it looks like this 

static int32_t _usart_init(void *const hw)
{
 uint8_t i = _get_sercom_index(hw);

 hri_sercomusart_wait_for_sync(hw, SERCOM_USART_SYNCBUSY_SWRST);
 if (hri_sercomusart_get_CTRLA_ENABLE_bit(hw)) {
  return ERR_DENIED;
 }
 hri_sercomusart_set_CTRLA_SWRST_bit(hw);
 hri_sercomusart_wait_for_sync(hw, SERCOM_USART_SYNCBUSY_SWRST);

 hri_sercomusart_write_CTRLA_reg(hw, _usarts[i].ctrl_a);
 hri_sercomusart_write_CTRLB_reg(hw, _usarts[i].ctrl_b);
 if ((_usarts[i].ctrl_a & SERCOM_USART_CTRLA_SAMPR(0x1)) || (_usarts[i].ctrl_a & SERCOM_USART_CTRLA_SAMPR(0x3))) {
  ((Sercom *)hw)->USART.BAUD.FRAC.BAUD = _usarts[i].baud;
  ((Sercom *)hw)->USART.BAUD.FRAC.FP   = _usarts[i].fractional;
 } else {
  hri_sercomusart_write_BAUD_reg(hw, _usarts[i].baud);
 }

 hri_sercomusart_write_RXPL_reg(hw, _usarts[i].rxpl);
 hri_sercomusart_write_DBGCTRL_reg(hw, _usarts[i].debug_ctrl);

 return ERR_NONE;
}

So _usart_init returns a static 32 bit integer meaning the return value is not visible to other functions outside this file .Its function parameter has a constant void pointer pointing to the hardware usart peripheral the constant means the address the pointer holds cannot be changed the void  means this pointer can hold any data type (float,char,int,ect). The first line of the function creates an unsigned eight bit integer and sets it equal to the Sercom offset index being returned by the "_get_sercom_index(hw);" function. In other words because there are multiple serial communication ports on the SAM D21 chip we need to know which one we are using. My particular board is the SAM D21 xpro which uses the SAMD21J18A mcu so lets look in the _get sercom_index function 

static uint8_t _get_sercom_index(const void *const hw)
{
 uint8_t sercom_offset = _sercom_get_hardware_index(hw);
 uint8_t i;

 for (i = 0; i < ARRAY_SIZE(_usarts); i++) {
  if (_usarts[i].number == sercom_offset) {
   return i;
  }
 }

 ASSERT(false);
 return 0;
}

The first line of the function creates an unsigned eight bit bit integer and sets it equal to the _sercom_get_hardware_index" function this function just gives us an ordinal number for SERCOM3 which is the communications port we are using (remember there are six of them) this will be used just a few lines down to pick one of the many SERCOM configuration macros there are (in fact there is a whole array of them)

static uint8_t _sercom_get_hardware_index(const void *const hw)
{
#ifdef _UNIT_TEST_
 return ((uint32_t)hw - (uint32_t)SERCOM0) / sizeof(Sercom);
#endif

#if defined __SAML21E18B__ || defined __ATSAML21E18B__ || defined __SAML21G18B__ || defined __ATSAML21G18B__           \
    || defined __SAML21J18B__ || defined __ATSAML21J18B__ || defined __ATSAMR30G18A__ || defined __ATSAMR30E18A__
 if ((uint32_t)SERCOM5 == (uint32_t)hw) {
  return 5;
 }
#endif

 return ((uint32_t)hw - (uint32_t)SERCOM0) >> 10;

There are two if defined statements and because my chip isn't listed in the second "#if defined" that leaves us with  the UNIT_TEST "#if defined". SERCOM0 is defined as 

#define SERCOM0                       (0x42000800) /**< \brief (SERCOM0) APB Base Address 

Which shows the base memory address where the USART hardware peripheral resides in this chip. So there is an offset number for each of the six SERCOM peripherals  available on the SAMD21 chip. You could address each SERCOM instance directly but by using a base address and offsets you allow portability of code between MCU chips. The base address may change between chips but the offsets remain the same.

The second line of the _usart_init function 

static inline void hri_sercomusart_wait_for_sync(const void *const hw, hri_sercomusart_syncbusy_reg_t reg)
{
 while (((Sercom *)hw)->USART.SYNCBUSY.reg & reg) {
 };
}

is waiting for synchronization between the main bus clock and the peripheral clock bus as explained in part 1.

The next line below will set the software reset bit to one resetting all the usart peripheral registers to their initial state. Note the memory barrier enter and leave macros explained in part1 surrounding the function code 

 

static inline void hri_sercomusart_set_CTRLA_SWRST_bit(const void *const hw)
{
 SERCOM_CRITICAL_SECTION_ENTER();
 hri_sercomusart_wait_for_sync(hw, SERCOM_USART_SYNCBUSY_SWRST);
 ((Sercom *)hw)->USART.CTRLA.reg |= SERCOM_USART_CTRLA_SWRST;
 SERCOM_CRITICAL_SECTION_LEAVE();
}

before we go any further lets look at the configuration requirements needed before sending data out of the usart peripheral. According to the SAMD21 data sheet there

are ten usart configuration steps

1. Select external or internal clock by writing to the Operating Mode bit CTRLA.MODE ... internal=0x1 .... external=0x0

2.Select Asynchronous (0) or Synchronous (1) mode by writing to CTRLA.CMODE

3.Select pin for receive data by writing to CTRLA.RXP0 bit

4. Select pads for transmitter and external clock by writing to CTRLA.TXPO bit

5. Configure character size field by writing to CTRLB.CHSIZE

6. Set the data order MSB or LSB by writing to CTRLA.DORD

7. Set parity if needed by enabling parity by first writing 0x1 to CTRLA.FORM(frame format field) then set the parity mode bit for even or odd CTRLB.PMODE

8. Configure stop bits CTRLB.SBMODE

9. If using internal clock write to BAUD for baud rate

10. Enable transmitter and receiver by writing to CTRLB.RXEN and CTRLB.TXEN

The next line 

hri_sercomusart_write_CTRLA_reg(hw, _usarts[i].ctrl_a);

will begin writing to the usart registers the configuration parameters we originally selected on the Atmel Studio start page ie. the baud rate ,stop bits ect.

"_usarts[i]" is an array of configuration macros  

#define SERCOM_CONFIGURATION(n)                                                                                        \
 {                                                                                                                  \
  n, SERCOM_USART_CTRLA_MODE(CONF_SERCOM_##n##_USART_MODE)                                                       \
         | (CONF_SERCOM_##n##_USART_RUNSTDBY << SERCOM_USART_CTRLA_RUNSTDBY_Pos)                                 \
         | (CONF_SERCOM_##n##_USART_IBON << SERCOM_USART_CTRLA_IBON_Pos)                                         \
         | SERCOM_USART_CTRLA_SAMPR(CONF_SERCOM_##n##_USART_SAMPR)                                               \
         | SERCOM_USART_CTRLA_TXPO(CONF_SERCOM_##n##_USART_TXPO)                                                 \
         | SERCOM_USART_CTRLA_RXPO(CONF_SERCOM_##n##_USART_RXPO)                                                 \
         | SERCOM_USART_CTRLA_SAMPA(CONF_SERCOM_##n##_USART_SAMPA)                                               \
         | SERCOM_USART_CTRLA_FORM(CONF_SERCOM_##n##_USART_FORM)                                                 \
         | (CONF_SERCOM_##n##_USART_CMODE << SERCOM_USART_CTRLA_CMODE_Pos)                                       \
         | (CONF_SERCOM_##n##_USART_CPOL << SERCOM_USART_CTRLA_CPOL_Pos)                                         \
         | (CONF_SERCOM_##n##_USART_DORD << SERCOM_USART_CTRLA_DORD_Pos),                                        \
      SERCOM_USART_CTRLB_CHSIZE(CONF_SERCOM_##n##_USART_CHSIZE)                                                  \
          | (CONF_SERCOM_##n##_USART_SBMODE << SERCOM_USART_CTRLB_SBMODE_Pos)                                    \
          | (CONF_SERCOM_##n##_USART_CLODEN << SERCOM_USART_CTRLB_COLDEN_Pos)                                    \
          | (CONF_SERCOM_##n##_USART_SFDE << SERCOM_USART_CTRLB_SFDE_Pos)                                        \
          | (CONF_SERCOM_##n##_USART_ENC << SERCOM_USART_CTRLB_ENC_Pos)                                          \
          | (CONF_SERCOM_##n##_USART_PMODE << SERCOM_USART_CTRLB_PMODE_Pos)                                      \
          | (CONF_SERCOM_##n##_USART_TXEN << SERCOM_USART_CTRLB_TXEN_Pos)                                        \
          | (CONF_SERCOM_##n##_USART_RXEN << SERCOM_USART_CTRLB_RXEN_Pos),                                       \
      (uint16_t)(CONF_SERCOM_##n##_USART_BAUD_RATE), CONF_SERCOM_##n##_USART_FRACTIONAL,                         \
      CONF_SERCOM_##n##_USART_RECEIVE_PULSE_LENGTH, CONF_SERCOM_##n##_USART_DEBUG_STOP_MODE,                     \
 }

Notice the double pound signs which concatenates  the SERCOM "n" number 3 in our case to defines on either side of the "n"

 

 

Last Edited: Thu. Aug 31, 2017 - 05:00 PM