(цена за 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] = '';
// 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 — может даже худее получится… Но это уже не я.
Автор: Himura