Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attiny3224: Serial baud calculation not working #1137

Open
aresta opened this issue Aug 29, 2024 · 4 comments
Open

Attiny3224: Serial baud calculation not working #1137

aresta opened this issue Aug 29, 2024 · 4 comments

Comments

@aresta
Copy link

aresta commented Aug 29, 2024

I had a problem to setup the serial communication with Serial.begin(), with the Attiny3224 MCU. After several investigations I found that the problem was the baud calculation. Finally I managed to make it working updating directly the USART0 register with the correct calculation out of the datasheet (and many try and error iterations).

So, I think that the calculation of the baud_setting in UART.cpp, HardwareSerial::begin, is not correct in this case. This line:

baud_setting = (((4 * F_CPU) / baud));

In the Attiny3224 datasheet (24.3.2.2.1 The Fractional Baud Rate Generator) this calculation is shown, but using CLK_PER (peripheral clock) instead of F_CPU. And in the case of this MCU: CLK_PER = F_CPU / 6
It is mentioned in the clock controller section of the datasheet.

The question is that using the adapted calculation it works. This is my code:

#define CLKL_PER (20000000.0/6) 
#define CALC_BAUD_RATE(BAUD_RATE) ((float)(CLKL_PER * 64 / (16 * (float)BAUD_RATE)) + 0.5)

void USART_init(uint32_t BAUDRATE) {
  USART0_BAUD = (uint16_t )CALC_BAUD_RATE(BAUDRATE);
  VPORTB_DIR |= PIN2_bm;   // set pin as output 
  USART0_CTRLB |= USART_TXEN_bm ; //enable tx only
}

Then Serial.println() works fine.

So the baud_setting should be divided by 6. And for some reason F_CPU/6 directly doesn't work if the clock frequency was reduced, e.g: 10MHz. It expects still 20Mhz for the baud calculation. I don't know yet how to calculate it properly.
I don't know if I miss something. The test program is not doing anything else, just blinking a led.
Thanks!

@hmeijdam
Copy link

a problem to setup the serial communication with Serial.begin(),

And you did not use "int main(void)"

instead of "void setup()" and "void loop()" ?

@hmeijdam
Copy link

hmeijdam commented Aug 30, 2024

[edit, sketch replaced by another one] oops, that sketch is not working on the 2 series parts, as the ADC registers have changed. I made it for 0 and 1 parts.

Here is a simpler one, using the baudrate formula. Can you give it a try?

#include <util/delay.h>      //do not use standard delay(). It will hang.

//#define USE_ALTERNATE_TXD  //  uncomment if you want to use the alternate TXD* / RXD* pins

int main(void) {
  F_CPU_init ();
  USART_init(9600);      //Call the USART initialization code and choose ( baudrate )

  char printbuffer[7];   //The ASCII of the integer will be stored in this char array
  const static uint8_t Sometext[] = " and counting !\r\n"; // A string array with some fixed text

  while (1) {      //Infinite loop
    int8_t a = a + 1; // increasing value that will be printed
    itoa(a, printbuffer, 10);    //(integer, yourBuffer, base)
    USART_putstring(printbuffer); // print the
    USART_putstring(Sometext);
  _delay_ms(1000);          //Delay for 2 seconds so it will re-send the string every 2 seconds
  }
}

void F_CPU_init () {
  // reconfigure CPU clock prescaler
#if (F_CPU == 20000000) | (F_CPU == 16000000)
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, 0x00);
#elif (F_CPU == 10000000) | (F_CPU == 8000000) // 20/16MHz prescaled by 2
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_2X_gc));
#elif (F_CPU == 5000000) | (F_CPU == 4000000) // 20/16MHz prescaled by 4
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_4X_gc));
#elif (F_CPU == 2000000) // 16MHz prescaled by 8
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_8X_gc));
#elif (F_CPU == 1000000) // 16MHz prescaled by 16
  _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, (CLKCTRL_PEN_bm | CLKCTRL_PDIV_16X_gc));
#else
#ifndef F_CPU
#error "F_CPU not defined"
#else
#error "F_CPU defined as an unsupported value for untuned internal oscillator"
#endif
#endif
}

void USART_init(uint32_t BAUDRATE) {
  USART0_BAUD = (F_CPU * 4LL / BAUDRATE);
#ifdef USE_ALTERNATE_TXD
  PORTMUX_CTRLB = PORTMUX_USART0_bm; // activate alternate RXD* / TXD* pins
  //  VPORTA_DIR |= PIN1_bm;   // set pin PA1 as output (select manually to save flash)
  pinModeFast(PIN_HWSERIAL0_TX_PINSWAP_1, OUTPUT); // let the core to figure out the pin (uses 100 bytes more memory)
#else
  //  VPORTB_DIR |= PIN2_bm;   // set pin PB2 as output (select manually to save flash)
  //  VPORTA_DIR |= PIN6_bm;   /* set pin 6 of PORT A (TXd) as output*/
  pinModeFast(PIN_HWSERIAL0_TX, OUTPUT); // if you want the core to figure out the pin (uses 100 bytes more memory)
#endif
  USART0_CTRLB |= USART_TXEN_bm ; //enable tx only
}

void USART_send(unsigned char data) {
  while (!(USART0_STATUS & USART_DREIF_bm));
  USART0_TXDATAL = data;
}

void USART_putstring(char* StringPtr) {
  while (*StringPtr != 0x00) {
    USART_send(*StringPtr);
    StringPtr++;
  }
}

@SpenceKonde
Copy link
Owner

This is screaming "main overridden" to me too? Because this definitely works in the general case.. And nothing works if you override main without replacing the functionality you overrode.

(sometimes I wanna put init() or at least initClock() into .init9 or something, so even when people overrode main, the chip would still run at the speed they chose... but I think I'd get a ton of shit over that so...

On sketch startup, the basic things that happen are:
(Dirty reset tested for, reset if dirty reset, otherwise reset cause preserved in GPIOR0- - done in bootloader if using that, otherwise done in the first init I can, because between the adverse event that led to the dirty reset, and the clean reset, the device is executing code, yet is also in a guaranteed-non-working state) - not likely relevant
main is "called". It calls init() (as well as a number of callbacks, see Callbacks,md, which can be used to run very early initialization code either before init, between init and setup or after setup/ but before the first call to loop(),

Callbacks were largely added as it became clear that not many people are going to have a good time if they needed to override main(), This ain't classic AVR, clock speed at startup is 2.66 MHz or 3.33 until initClock() (the first function called by init) runs to set the clock speed to match F_CPU

@aresta
Copy link
Author

aresta commented Aug 31, 2024

Thanks, I've been doing some tests with that sketch and mine, and more investigations, and I think that I found the problem. And another issue or question.

I don't know if I miss something

Yes, as usual.

The problem was my understanding of F_CPU and friends. In the datasheet they indeed talk about CLK_PER, because it's about the USART. But I understand that in this MCU's CLK_PER is always the same than CLK_CPU, and therefor the same than F_CPU . And all them are after the preescaler.

So, to have everything correct, I have to set F_CPU (in platformio.ini) and set the preescaler myself in the code. What I was not doing.
By default the preescaler divides the oscillator (20MHz) by 6, that's why I had to put 20M/6, not matter what I had in platformio.ini. The default F_CPU is also not correct.

So that's OK and clear, I think.

The problem I have is when I set some preescaler values like 8 or 6, then F_CPU is: 2.5M or 3.3M. But some macro in the libraries is complaining that it is not a valid F_CPU value. But all them are valid preescalers. And actually if I comment out the macro #error, everything works fine, including the delays and so on.
Valid preescalers:

0x0 DIV2 CLK_MAIN divided by 2
0x1 DIV4 CLK_MAIN divided by 4
0x2 DIV8 CLK_MAIN divided by 8
0x3 DIV16 CLK_MAIN divided by 16
0x4 DIV32 CLK_MAIN divided by 32
0x5 DIV64 CLK_MAIN divided by 64
0x6-0x7 - Reserved
0x8 DIV6 CLK_MAIN divided by 6
0x9 DIV10 CLK_MAIN divided by 10
0xA DIV12 CLK_MAIN divided by 12
0xB DIV24 CLK_MAIN divided by 24
0xC DIV48 CLK_MAIN divided by 48
other - Reserved

Probably I miss something else :-) But at least now works and makes sense.
Thanks for the great work you are doing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants