SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
563 stars 148 forks source link

Attiny3224: Serial baud calculation not working #1137

Closed aresta closed 5 days ago

aresta commented 2 months ago

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 _baudsetting 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 commented 2 months ago

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 commented 2 months ago

[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 commented 2 months ago

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 commented 2 months ago

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 _FCPU and friends. In the datasheet they indeed talk about _CLKPER, because it's about the USART. But I understand that in this MCU's _CLKPER is always the same than _CLKCPU, and therefor the same than _FCPU . And all them are after the preescaler.

So, to have everything correct, I have to set _FCPU (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 _FCPU 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 _FCPU is: 2.5M or 3.3M. But some macro in the libraries is complaining that it is not a valid _FCPU 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!