UART в ATtiny13 или Как вывести данные из МК за 52р
(цена за 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
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<
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
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 — может даже худее получится… Но это уже не я.