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.