0

I have tried to implement TX only uart for ATTiny85 and receive the bits using arduino micro. (similiar question did not help since it is quite different to my situation)

My intend is to be able to print via attiny85 -> arduino -> console so I can debug the attiny85, since I don't have oscilloscope available.

attiny85 fuses are: "efuse:w:0xff:m -U hfuse:w:0xdf:m -U lfuse:w:0xf1:m" aka. 16MHz F_CPU arduino also seems to have 16MHz F_CPU

Similiar to the mentioned question attiny85 sends bits via timer0 ISR one bit at time:

#ifndef F_CPU
#define F_CPU 16000000UL // 16 MHz
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define TX_PIN  PB0

volatile uint16_t tx_shift_reg = 0;

ISR(TIMER0_COMPA_vect) {
   uint16_t local_tx_shift_reg = tx_shift_reg;
   if( local_tx_shift_reg & 0x01 ) {
      PORTB |= _BV(TX_PIN);
   } else {
      PORTB &= ~_BV(TX_PIN);
   }
   local_tx_shift_reg >>= 1;
   if(!local_tx_shift_reg) {
      // Stop timer0.
      GTCCR |= (1<<TSM) | (1<<PSR0);
   }
   tx_shift_reg = local_tx_shift_reg;
}

void UART_tx(char byte) {
   uint16_t local_tx_shift_reg = tx_shift_reg;
   local_tx_shift_reg = (0b1<<9) | ((uint16_t)byte<<1);
   tx_shift_reg = local_tx_shift_reg;
   TCNT0 = 0;
   TCCR0B |= (1<<CS02)|(1<<CS00); // 1024 prescaler
   GTCCR &= ~(1<<TSM);
}

void UART_tx_char(char c) {
   UART_tx( c );
   // wait until transmission is finished
   while(tx_shift_reg);
}

void UART_init() {
   cli()
   // set TX pins as output
   DDRB |= (1<<TX_PIN);
   PORTB |= (1<<TX_PIN);
   // set timer0 to CTC mode, keep it halted.
   TCCR0A |= (1<<WGM01);
   TCCR0B = 0;
   // enable interrupt
   TIMSK |= (1<<OCIE0A);
   OCR0A = 128;
   OCR0B = 128;
   TCNT0 = 0;
   GTCCR |= (1<<TSM);
   sei();
}

void main(void)
{
   UART_init();
   while(1) {
      for(char c = 1; c < 128; ++c) {
         UART_tx_char(c);
         _delay_ms(100);
      }
   }
}

Then arduino receives the bits:

/*
 * ATtiny85 bit-bang uart monitor for ATmega32u4
 */
#define LED_PIN 13
#define RX_PIN 7
// rx_state == 0 // timer1 not running
// rx_state == 1 // receive in progress
// rx_state == 2 // data ready in rx_data_reg
volatile int rx_state = 0;
volatile int rx_bit_nro = 0;
volatile uint16_t rx_shift_reg = 0;
volatile uint16_t rx_data_reg = 0;

void rx_start_interupt() {
  if(rx_state == 0) {
    digitalWrite(LED_PIN, HIGH);
    while(digitalRead(RX_PIN));
    // Start timer1
    rx_state++;
    TCNT1  = 0;
    TCCR1B = (1 << WGM12) | (1 <<CS12) | (1 << CS10);
  }
}

ISR(TIMER1_COMPA_vect) {
  uint16_t bit = digitalRead(RX_PIN) > 0;
  rx_shift_reg >>= 1;
  rx_shift_reg |= (bit << 7);
  ++rx_bit_nro;
  if(rx_bit_nro == 9) {
    // Stop timer.
    TCCR1B = 0;
    TCNT1  = 0;
    rx_data_reg = rx_shift_reg;
    rx_shift_reg = 0;
    rx_bit_nro = 0;
    rx_state++;
    digitalWrite(LED_PIN, LOW);
  }
}

void setup() {
  noInterrupts();
  // Program timer1 for UART bit bang receive.
  TCCR1A = 0; // set entire TCCR1A register to 0 (stops it)
  TCCR1B = 0; // same for TCCR1B
  TCNT1  = 0; // initialize counter value to 0
  OCR1A = 128;
  TIMSK1 |= (1 << OCIE1A);
  interrupts();
  
  pinMode(LED_PIN, OUTPUT);
  pinMode(RX_PIN, INPUT);

  // Attach RX start interupt.
  pinMode(digitalPinToInterrupt(RX_PIN), INPUT);
  attachInterrupt(digitalPinToInterrupt(RX_PIN), rx_start_interupt, FALLING);
  
  Serial.begin(9600);
  while(!Serial);
  Serial.print("F_CPU:");
  Serial.println(F_CPU,DEC);
  Serial.println("Waiting for data from attiny85...");
}

void loop() {
  if(rx_state == 2) {
    uint16_t local_rx_data = rx_data_reg;
    rx_state = 0;
    Serial.println(local_rx_data,HEX);
  }
}

I have tried pretty much everything to make it work, but received bytes come back garbace. What am I missing?

Note: I'm using timer0 on attiny85 and timer1 on arduino. Solved: switching ISR to TIMER1_COMPAB_vect and OCR1B = 64 actually works! Yay!

Community
  • 1
  • 1
JATothrim
  • 842
  • 1
  • 8
  • 24

1 Answers1

1

I recently ran into this issue of serial comms coming out as garbage on the other end. In my case, the ATtiny85 was sending bits to my laptop using an USB to TTL UART converter which had worked beautifully in other situations, but, I was just getting garbage in the Arduino IDE serial monitor.

I found a forum post which mentioned the possibility of calibrating OSCCAL.

I get a little bit fancier in my related blog post, but I tested the theory that I should calibrate OSCCAL using this code:

#include <SoftwareSerial.h>

SoftwareSerial comm(-1, 0);

static const int anchor = 128;

void
print_osccal(int v) {
  comm.println(F("********************************"));
  comm.print(F("OSCCAL = "));
  comm.println(v);
  comm.println(F("********************************"));
}

void
setup() {
  delay(5000);
  comm.begin(300);
  OSCCAL = anchor;
  print_osccal(anchor);
  delay(5000);
}

void
loop() {
  int x;
  for (int i = 1; i < 128; ++i) {
    x = anchor + i;
    OSCCAL = x;
    print_osccal(x);
    delay(1000);
    x = anchor - i;
    OSCCAL = x;
    print_osccal(x);
    delay(1000);
  }
}

It was a revelation seeing garbage all of a sudden transform to nicely formatted messages in the serial monitor (and, of course, back to garbage as the search is an infinite loop).

Now, the ATtiny85's internal oscillator can only support 1 MHz and 8 MHz. As I understand it, OSCCAL exists because this internal oscillator is 1) not very accurate and 2) is very sensitive the temperature.

If the ATtiny85 is set to run at 16MHz, it needs a reliable, external oscillator, and no amount of fiddling with OSCCAL might help. But, it did in my case allow me to discover the value(s) which made SoftwareSerial tick at 8 MHz and a range of baud rates from 300 to 38400.

This allowed me to get the right value for the bit banging UART to work with the 1MHz clock which is not supported by SoftwareSerial.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • 1
    Very intresting that ATtiny85 clocked at 16MHz may be unreliable. This probaly explains why at higher serial speeds the clocks de-sync pretty quickly. Thanks! – JATothrim Feb 11 '21 at 21:17