Serial port on the ATtiny85

I have a small radio serial interface project for which I have working prototype using a Micro (32u4). Since it only needs one GPIO pin for output and one for a LED activity indicator, the Micro is a bit overkill, so I thought to have a go at uploading the sketch onto an ATtiny85.

I have downloaded the ATTinyCore board package and compiled the sketch. My understanding is that even though the ATtiny85 does not have a UART, Serial is recognised and SoftwareSerial is automatically deployed to implement it. The SoftwareSerial library does not need to be specifically included. The fact that the sketch compiles without error seems to confirm that. By default, apparently the PB0 and PB1 pins are configured to be used for Tx and Rx.

I am using a TL866II programmer to upload the sketch as this makes things a lot easier. The chip just drops into the programmer, the ATtiny85 chip is selected for programming, the binary code loaded into the programmer and then uploaded to the chip by clicking the Prog button. For this reason and to save program memory space I chose not to use the boot-loader, so the board selection is ATtiny 24/45/85 (No bootloader).

The binaries was exported using the Export Compiled Binary option in the IDE. The .hex file was then loaded into the programmer selecting Intel Hex as the file type. The program upload was apparently successful. I tried connecting an FT232RL adapter to the PB0 and PB1 pins which are supposed to the the default pins used for Rx and Tx (although I believe that other pins can be used), but am getting no response. I tried swapping the two wires around in case I had got them in the wrong order, but it made no difference. Ground is common between adapter and chip, VCC set to 5V and connected to the VDD of the chip to power it.

I kept everything at defaults. For example, the clock source selection is 8MHz (internal), millis()/micros() are enabled as they are needed for the sketch in any case. BOD is disabled. I have not changed any fuses. SPI is enabled. I am not sure what I have missed.

Fuses checked:

Low byte:
CKDIV8=0
SUT0=0
CKSEL3=0
CKSEL2=0
CKSEL0=0

High byte:
SPIEN=0

Options set:
Pin detect
Erase before
Verify after
Check ID
Addr.Range - All

All other fuses and options are unchecked. The sketch is being uploaded to address 00000000. EEPROM is empty - all FFs.

I am still quite unfamiliar with programming these ATtiny chips so am wondering whether there is anything else that I need to configure for SoftwareSerial to work?

const char version[] = "ArKEYP1 v0.02.01";


// Nano
//const uint8_t hftxpin = 7;

// Micro
//const uint8_t hftxpin = 14;
//#define HFTX_PIN 14
//#define ACTV_PIN 7

// ESP-C3
//const uint8_t hftxpin = 10;

// ATtiny1626
//const uint8_t hftxpin = PIN_PA7;

// ATtiny85
#define HFTX_PIN PB4
#define ACTV_PIN PB3


#define REPETITIONS 3
#define BURST_INTRVL 35


// Key bit pattern array: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, <Enter>, <Cancel>, <Store>, <Fine>
uint8_t pulseData[14] = {0x05, 0x1B, 0x1D, 0x19, 0x0B, 0x0D, 0x09, 0x13, 0x15, 0x11, 0x01, 0x03, 0x0F, 0x1F};

const uint8_t sbSize = 64;
//const uint8_t repetitions = 3;
const uint16_t t[4] = {2100, 3150, 350, 6500}; // t0, t1, tp, tg
uint8_t sbCnt = 0;
char serBuf[sbSize];
bool idconf = false;
//bool inverted = false;
unsigned long lastTime = millis();
uint8_t hfMode = 225;


void sendId(){
  Serial.println(version);
}


void transmitPreamble(){
  const uint16_t tp = t[2];
  digitalWrite(ACTV_PIN, HIGH);
  for (uint8_t i=0; i<3; i++) {
    digitalWrite(HFTX_PIN, HIGH);
    delayMicroseconds(tp); 
    digitalWrite(HFTX_PIN, LOW);
    delayMicroseconds(t[1]);
  }
  delayMicroseconds(t[1]);
  digitalWrite(ACTV_PIN, LOW);
}


void transmitPostamble(){
  const uint16_t tp = t[2];
  digitalWrite(ACTV_PIN, HIGH);
  delayMicroseconds(t[3]);
  digitalWrite(HFTX_PIN, HIGH);
  delayMicroseconds(tp); 
  digitalWrite(HFTX_PIN, LOW);
  digitalWrite(ACTV_PIN, LOW);
}


void transmitWord(uint8_t code, uint8_t bitlen) {
  const uint16_t tp = t[2];
  digitalWrite(ACTV_PIN, HIGH);
  if (hfMode == 150) transmitPreamble();
  for (uint8_t i=0; i<bitlen; i++) {
    digitalWrite(HFTX_PIN, HIGH);
    delayMicroseconds(tp); 
    digitalWrite(HFTX_PIN, LOW);
    delayMicroseconds(t[(code>>i)&0x01]);
  }
  digitalWrite(HFTX_PIN, HIGH);
  delayMicroseconds(tp); 
  digitalWrite(HFTX_PIN, LOW);
  if (hfMode == 150) transmitPostamble();
  digitalWrite(ACTV_PIN, LOW);
}


void transmitCode(uint8_t code){
  const uint16_t tiwg = t[3];
  for (uint8_t i=0; i<REPETITIONS; i++){
    transmitWord(code, 5);
    delayMicroseconds(tiwg);
  }
}


void transmitValue(uint8_t val){
  const uint16_t tiwg = t[3];
  for (uint8_t i=0; i<REPETITIONS; i++){
    transmitWord(val, 8);
    delayMicroseconds(tiwg);
  }
}


void transmitTest(){
  for (uint8_t i=0; i<12; i++){
    transmitCode(pulseData[i]);
    delay(10);
  }
}


void sendSeq(char * charstr, bool enter){
  // Note: min 30mS delay required
  uint8_t len = strlen(charstr);
  uint8_t num;
  for (uint8_t i=0; i<len; i++){
    num = charstr[i] - 48;
    if (num<10) transmitCode(pulseData[num]);
    delay(BURST_INTRVL);
  }
  if (enter) transmitCode(pulseData[10]);
  delay(BURST_INTRVL);
}


void decode(char * line){
  if (strlen(line) < 3) return;   // Line too short
  if (strncasecmp(line, "ID?", 3)==0){
    if (strlen(line)==3) Serial.println(version);
    idconf = true;
    return;
  }
  if (!idconf) return;
  if (strncasecmp(line, "FQ:", 3)==0){
    char * keychars = strchr(line, ':');
    keychars++;
    if (keychars) {
      sendSeq(keychars, true);
      Serial.println(F("FRQ"));
    }
    return;
  }
  if (strncasecmp(line, "SQ:", 3)==0){
    char * keychars = strchr(line, ':');
    keychars++;
    if (keychars) {
      sendSeq(keychars, false);
      Serial.println(F("FRQ"));
    }
    return;
  }
  if (strncasecmp(line, "EN:", 3)==0){
    transmitCode(pulseData[10]);
    Serial.println(F("ENT"));
    return;
  }
  if (strncasecmp(line, "CL:", 3)==0){
    transmitCode(pulseData[11]);
    Serial.println(F("CLR"));
    return;
  }
  if (strncasecmp(line, "KY:", 3)==0){
    uint8_t key = line[3] - 48;
    if (key < 10){
      transmitCode(pulseData[key]);
    }
    Serial.println(F("KEY"));
    return;
  }
  if (strncasecmp(line, "ST:", 3)==0){
    transmitCode(pulseData[12]);
    Serial.println(F("STO"));
    return;
  }
  if (strncasecmp(line, "FT:", 3)==0){
    transmitCode(pulseData[13]);
    Serial.println(F("TUN"));
    return;
  }
  if (strncasecmp(line, "BY:", 3)==0){
    idconf = false;
    Serial.println(F("BYE"));
    return;
  }
  if (strncasecmp(line, "TT:", 3)==0){
    transmitTest();
    Serial.println(F("TST"));
    return;
  }
  /*
  if (strncasecmp(line, "FN:", 3)==0){
//    const uint16_t tiwg = t[3];
    char * token = strtok(line, ":");
    Serial.print(F("Running: "));
    Serial.println(token);
    uint8_t code = 0;
    while (token) {
      token = strtok(NULL, ",");
      code = atoi(token);
      Serial.print(F("Token: "));
      Serial.println(token);
      Serial.print(F("Code: "));
      Serial.println(code);
      if (token) transmitCode(code);
      delay(35);
//      delayMicroseconds(tiwg);
    }
    return;
  }
  */
  if (strncasecmp(line, "HF:", 3)==0){
    char * valchrs = strchr(line, ':');
    valchrs++;
    uint8_t val = atoi(valchrs);
    if ( (val == 150 || val == 225) ) {
      hfMode = val;
      Serial.println(valchrs);
      return;
    }
  }
  if (strncasecmp(line, "V8:", 3)==0){
    char * valchrs = strchr(line, ':');
    valchrs++;
    uint8_t val = atoi(valchrs);
    transmitValue(val);
    Serial.println(F("8BV"));
    return;
  }
  Serial.println(F("ERR"));
}


void parse(char c){

  if ( (c==0x0A) | (c==0x0D) ) {
    if (sbCnt) {
      decode(serBuf);
      memset(serBuf, '\0', sbSize);
      sbCnt = 0;
    }
  }else{
    if (sbCnt < (sbSize-1)) {
      serBuf[sbCnt] = c;
      sbCnt++;
    }
  }

}


void serialIn() {
  while(Serial.available()){
    parse(Serial.read());
  }
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(38400);
  delay(500);
  pinMode(HFTX_PIN, OUTPUT);
  digitalWrite(HFTX_PIN, LOW);
  pinMode(ACTV_PIN, OUTPUT);
  digitalWrite(ACTV_PIN, LOW);
  memset(serBuf, '\0', sbSize);
//  Serial.println("Hello :-)");
}


void loop() {

  unsigned long currentTime = millis();

  if (currentTime - lastTime > 20) {
    serialIn();
    lastTime = currentTime;
  } 

  delay(20);

}

BTW, the jury is still out on whether to use const uint8_t var = 99 or #define VAR 99 for some variables. It seems to me that a pointer to the address of the constant would take up two bytes, whereas the literal value just one. A handful of bytes may not seem to make that much difference, but dynamic memory usage on these is rather tight.

Even if you’re not using a bootloader, you need to do the burn bootloader function to set the fuses to the desired board configuration (clock speed, etc.) you’ve chosen. Then the MCU will run at the clock speed you expect. As it is now it is running at the factory set fuse settings and 1 MHz internal clock (divide by 8 is “programmed” if it is 0).

Ha! I connected at 4800 baud (38400/8) and sure enough it responded! I then re-programmed it unchecking CKDIV8 and tried again. This time I was able to connect to it at 38400 baud. I then tested the output from the PB3 and PB4 GPIO pins. The expected pulses are being generated and controlling the radio works as expected.

Is slightly confusing that setting to zero enables the fuse, but then again, one has to check the box to enable so that seems logical.

Anyway, it was just a matter of the clock being set incorrectly, so thanks for pointing that out.

Although I understand that burning the bootloader to set the fuses needs to be done when programming using a USBASP or another Arduino as an ISP programmer, this does not appear to be necessary as a separate task when using the Xgpro the TL866II. The fuse settings still need to be configured on the Config tab prior to programming, but the TL866II seems to automatically take care of setting the fuses somehow during the Programming process.

That does not appear to be the case with the minipro program on Linux as that has a separate parameter for reading/writing/verifying fuses, so I will have to figure that out eventually.