Pull to refresh

UART в ATtiny13 или Как вывести данные из МК за 52р

Reading time3 min
Views76K
(цена за 10 шт магазина Чип и Дип на момент публикации)

Я никогда не мог удержаться от покупки разных электронных штук, и однажды у меня стало на 10 очень мелких МК больше. Я люблю ATtiny13 — дешево и сердито. Когда я их покупал, я твердо помнил, что у них «Даже АЦП есть, не то что таймер!» и сильно радовался их малой цене.
Однако, когда я столкнул ATtiny13 с реальной задачей, оказалось что одной очень важной штуки в нем нету, а именно, интерфейсов для передачи данных (разумеется, не считая GPIO). Ну а если GPIO есть, то написать все что угодно можно! Подумал я и пошел гуглить… И красивого готового решения под avr-gcc не нагуглил… О создании (надеюсь) такого решения, данная статья — добро пожаловать под кат.

На самом деле, нагуглилось примерно три варианта, но в одном пишут на БЭЙСИКЕ (я вообще не знал что так можно), в другом под CVAVR (привет моему первому мигающему светодиоду) и вообще там весь смысловой код на страшном ассемблере, а вот третий вариант вроде бы подходит… Но какой-то очень странный код… Но заработал сполпинка. Но жииирный…
Program Memory Usage: 508 bytes 49,6 % Full

Ну да ладно, главное работает и вмещается, а там уже можно и почитать, и порефакторить… JumpStart состоялся — это главное.



После вдумчивого чтения кода становится ясно, что его автор достоин глубокого уважения… Этот код похож на код человека, который программирует ОЧЕНЬ недавно, а задача то решена вполне мощная. Работает же. Очень хотелось для начинающих описать несколько тривиальных ошибок, но после третьей понял что сильно оффтоп, так что, увы…

В оригинале, код занимался и приёмом и отправкой данных, но реально, моя задача не требует приёма (тем более, в главном цикле). Мне достаточно просто отловить Pin Change Interrupt на любой ноге и выплюнуть результаты A->D преобразования. По этому, в целях похудения, было принято решение выпилить приём. Вот, что получилось после не очень долгого и не очень вдумчивого рефакторинга:

#define F_CPU 9600000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

uint8_t temp, count, start;
volatile uint8_t c;

#define BAUD_C 123
#define TxD PB4

#define T_START TCCR0B = (1 << CS01) // F_CPU/8
#define T_STOP TCCR0B = 0
#define T_RESET TCNT0 = 0

ISR(TIM0_COMPA_vect){
	OCR0A = BAUD_C;
	c = 1;
	T_RESET;
}

void send(uint8_t data) { //Что вообще такое "lov"? 0_о
	if (count >= 8) {
		PORTB |= (1<<TxD);
		start = 0; temp = 0; c = 0; count = 0;
		T_STOP;
		OCR0A = 0;
		return;
	}
	if(c == 1) {
		if (start == 0) {
			temp = 0x80;
			start = 1;
			count--;
		}
		else {
			temp = data;
			temp = temp >> count;
			temp = temp << 7;
		}
		switch(temp) {
			case 0x80 : PORTB &= ~(1 << TxD);	break;
			case 0 : PORTB |= (1 << TxD);	break;
		}
		count++;
		c = 0;
	}
}

void send_ch(uint8_t data){
	uint8_t f;
	data = ~data;
	T_START;
	for(f = 0; f < 10; f++){
		while(c == 0);
		send(data);
	}
}


Не хочется много думать, так что, я оставил всю смысловую часть как есть, выкинул лишнее и подправил вырвиглазные штуки вроде TIMSK0=0x04. В этом коде мне сильно понравилась реализация интервалов! Весь геморрой с высчитыванием констант для baud rate сводится к одному числу BAUD_C, которое подбирается чуть ли не экспериентально и корректируется в зависимости от неточностей кварца (у автора она была равна 115 и на скриншоте вроде бы довольно точно работала. Возможно ли вообще такое хардовое уплывание?). Скорее всего, это не самое оптимальное, надежное и верное бла бла бла решение, но мне оно кажется очень простым и красивым!
Чтож, каков результат?
Program Memory Usage: 304 bytes 29,7 % Full

«Фух, живём. Еще и на АЦП хватит...» Но вообще, это еще не конец. Сейчас код умеет передавать только один символ, а надо передавать строки и значения регистров. А значит, время рыться в старых проектах! Эта задача уже не специфична для МК без UART и была решена неоднократно.

void send_str(char *text){
	while(*text) {
		send_ch(*text++);
	}
}

 void itoa(uint16_t n, char s[]) {
	 uint8_t i = 0;
	 do { s[i++] = n % 10 + '0'; } 
	 while ((n /= 10) > 0);
	 s[i] = '\0';
	 // Reversing
	 uint8_t j;
	 char c;
	 for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
		 c = s[i];
		 s[i] = s[j];
		 s[j] = c;
	 }
 }
 
  void send_num(char *text, uint16_t n){
	char s[6];
	itoa((uint16_t)n, s);
	send_str(text);
	send_str(s);
 }


Очень удобно передавать числа сразу с пояснениями, что это за числа, поэтому — send_num(char *text, uint16_t n).
И, с учетом примера использования:

int main(void){
	DDRB |= (1 << TxD);
	TIMSK0 = (1 << OCIE0A);
	sei();
	
	while(1){
		_delay_ms(1);
		send_num("Habr:", 4242);
	}
}


Получается
Program Memory Usage: 538 bytes 52,5 % Full

Чуть больше, чем в оригинале. Но насколько полезнее!


Хочу сразу предупредить, что цели сделать красивую и популярную статью не было. Писалась она очень быстро, перечитывалась очень мало и может содержать очень ошибки. Прошу не троллить — я всё исправлю. Главное, чтобы эта проблема перестала существовать и все, кто захочет построить свой датчик на ATtiny13 — имели готовое решение для интерфейса.

P.S.: Сначала я смотрел в строну I2C и даже нашел очень многообещающий репозиторий, но не разобрался почему Atmel Studio говорит overflow. Было бы классно и этот интерфейс запилить на t13 — может даже худее получится… Но это уже не я.

P.S.S.: Мой логический анализатор. Он шикарен!
Tags:
Hubs:
Total votes 24: ↑22 and ↓2+20
Comments78

Articles