UART программный на Atiny13A

AVR

Как то понадобился мне при отладке, UART в крошечной ATiny13, а нормального готового найти не смог.
Пришлось заняться написанием собственного софтого.
(В таком виде работает на 9600, относительно легко перестраивается на любую другую. Нужно поменять делитель таймера и подобрать значение OCR1A)


PORTB 4 используется для передачи, PORTB 1 для приема.

Прием с терминала символа «H» и отправка его же обратно на терминал

Отправлял-принимал свежеспаянным преобразователем на cp2102

Исходник напичкан комментариями

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

volatile uint8_t uart;
uint8_t temp;
volatile uint8_t count;
volatile uint8_t start;
volatile uint8_t c;
volatile uint8_t uart_data;
volatile uint8_t Rece_bit;
volatile uint8_t rec;
volatile uint8_t usart_r;
volatile uint8_t coef;

ISR(INT0_vect){
		rec=1;} // Прерывание чисто для определения стартового бита при приеме, 
		        // используется редко, можно сюда повесить что-либо еще

ISR(TIM0_COMPA_vect){
	TIMSK0=0x00; 
	TCCR0B=0x00;   // Единственный Таймер, используется для формирования четких промежуток 
	OCR0A=0;       // между битами, как при приеме так и при передачи
	c=1;           
	TCNT0=0;
	TIMSK0=0x04;
	TCCR0B=0x02;
	              // Значение "сброс при совпадении" загружается каждый раз из переменной
 OCR0A=coef;      // Можно быстро менять скорости UART
 	Rece_bit=1; 
}

int lov (uint8_t data2) {
	if (count>=8){
	PORTB|=(1<<4); start=0; temp=0; c=0; count=0;
	TIMSK0=0; TCCR0B=0; OCR0A=0;goto nah;}
	
	if(c==1){
		if (start==0){temp=0x80; start=1;
		count--; goto razvet;
	}
	temp=data2;
	temp=temp>>count;
	temp=temp<<7;
	razvet:
	switch(temp){
		case 0x80 : PORTB&=~(1<<4);  break;
		case 0x00 : PORTB|=(1<<4);   break;
	}
	count++; c=0;
   }
nah:;
}	

int UART_trans(uint8_t data){
	uint8_t f;	
	data=~data;	
	coef=115;
	TIMSK0=0x04;
	TCCR0B=0x02;
	for(f=0;f<10;f++){
		while(c==0);
		lov(data);
	}
	start=0; temp=0; c=0; count=0;
	TIMSK0=0; TCCR0B=0; OCR0A=0;
	coef=0;
}

int UART_receiv(void){
	uint8_t a;
	usart_r=0;
	
	MCUCR=0x02; // INT0 Interrupt
	GIMSK=0x40; // INT0 Interrupt
	
	while(rec==0); // Ждать, пока не случится стартовый бит
	MCUCR=0; // INT0 Interrupt
	GIMSK=0; // INT0 Interrupt
	coef=115;
	TIMSK0=0x04;
	TCCR0B=0x02;
	rec=0;
	TCNT0=0xDC;
	for(a=0; a<9; a++){
		while(Rece_bit==0);

		if(bit_is_set(PINB,1)){usart_r |=(1<<7);} else {usart_r &=~(1<<7);}
		usart_r=usart_r>>1;
		Rece_bit=0;
            }
}

int main(void)
{
	DDRB&=~(1<<1); // 
	DDRB|=(1<<4);  // 
		
   asm("sei");   
	while(1)
    {	    
	   UART_receiv(); // Сперва принимаем байт
	   _delay_ms(10);  // Пауза для наглядности
	   UART_trans(usart_r);  // Отправляем обратно
	}
}




PS: Всем спасибо
  • +2
  • 08 октября 2012, 23:36
  • khomin
  • 1
Файлы в топике: Akystik_Tiny13.zip

Комментарии (39)

RSS свернуть / развернуть
Готов к критике :)
0
Совсем недавно была срачстатья о GOTO. Так вот, этот код отлично иллюстрирует неправильное использование оператора безусловного перехода.
goto nah
заменить на break;
goto razvet;
вообще нон ин вагина, нон ин рота когорта. Переставить закрывающую скобку три строчки ниже.
0
заменить на break;
Не break, а return, цикла тут нет =) Но да.

Переставить закрывающую скобку три строчки ниже.
Я бы сделал else. Там фигурные скобочки поставлены очень путающе и непонятно, новый блок
if (start==0){
внутри одной из строчек открывается.

Да тут много чего ещё есть бросающегося в глаза, и по коду, и по оформлению его, но не суть.
0
И сколько флеша занимает?
0
Почти 40%, на 6-й студии :)
0
многовато как-то. Программка на бейсике для тини13, которая принимает символ из терминала и шлет его обратно заняла 30% флеш. А вывод в виртуальный UART значения АЦП заняло 54%
0
Надо попробовать на 4-ой студии, там ывходной Hex по любому будет меньше…
0
Размер hex зависит от установленной версии тулчейна, и если она одинаковая, то сами понимаете…
P.S. Оптимизацию по рзмеру не пробовали?
0
Не только ее. Еще смартлинк важен.
0
Насчёт библиотеки: надо сделать проект «AVR Static Library Project» или типа того, добавить туда нужные функции и сделать к ним заголовочный файлик (.h который). После чего подключать его и добавлять библиотеку в список линковки.

Но кроме как для обучения я бы так делать не стал, особенно на таком маленьком контроллере. Вынесение кода в библиотеки может добавить проблем с отладкой, особенно, если ошибка закрадётся в библиотечные функции, что может быть запросто. А если ещё библиотека без отладочной информации будет, то вообще тушите свет =D
+1
Спасибо, интересовался так на будущее. Никогда еще не создавал библиотечные файлы.
Возможно скоро понадобится
0
какое-то бестолковое использование прерываний, что внешнего, что таймера. зачем их использовать если всё равно крутимся в цикле ничего не делаем, только опрашиваем переменную-флаг которая взводится в прерывании, проще тогда сразу нужные регистры и опрашивать. а задержку по таймеру вообще заменить тупо NOPами если не страшно запретить прерывания на время приёма/передачи. Или если уж использовать прерывания, то и передачу целиком в них делать по нормальному, чтобы не мешать выполнению программы, и еще с кольцевым буфером под данные хотя это, наверное, не для tiny13 c её 64 байтами памяти. Я бы сделал как-то так:

typedef unsigned char u8;
typedef unsigned int u16;

#define MCU_FREQ 8000000L
#define UART_BAUDRATE 9600

// эту цифирку возможно надо поправить:
#define CYCLES_LOOP 5            

#define CYCLES_HALF_BIT ((MCU_FREQ+CYCLES_LOOP*UART_BAUDRATE/2)/(CYCLES_LOOP*UART_BAUDRATE))

//IO макросы им. Аскольда Волкова. on, off, toggle, is_active, dir_out, dir_in
#define UART_RX B,(1<<4),L
#define UART_TX B,(1<<1),L

//#define UART_TIMER_IRQ

#ifndef UART_TIMER_IRQ
inline void DelayHalfBit(){
    u16 delay=CYCLES_HALF_BIT;
    while (delay--)asm("nop");
}
#else
inline void DelayHalfBit(){
    TIMSK0=0x04;
    TCCR0B=0x02;
    TCNT0=0xDC;
    while ((TCCR0B & TIFR) == 0);
}
#endif

void UartSend(u8 data){
#ifndef UART_TIMER_IRQ
    __disable_interrupt();
#endif    
    on(UART_TX); //start bit
    for (u8 i = 0; i < 8; i++){
        DelayHalfBit(); DelayHalfBit();
        if (data & 0x01) on(UART_TX) else off(UART_TX);
        data >>= 1;
    }
    DelayHalfBit(); DelayHalfBit();
    off(UART_TX); //stop bit
    DelayHalfBit(); DelayHalfBit();
#ifndef UART_TIMER_IRQ
    __enable_interrupt();
#endif    
}

u8 UartReceive(){
    u8 data = 0;
    while (!is_active(UART_RX));
#ifndef UART_TIMER_IRQ
    __disable_interrupt();
#endif    
    DelayHalfBit();
    for (u8 i = 0; i < 8; i++){
        DelayHalfBit(); DelayHalfBit();
        data >>= 1;
        if (is_active(UART_RX)) data |= 0x80;
    }
#ifndef UART_TIMER_IRQ
    __enable_interrupt();
#endif    
    return data;
}

на работоспособность не проверял.

комментирование UART_TIMER_IRQ экономит таймер, но запрещает прерывания на время приёма/передачи.
только цифирки в инициализации таймера в DelayHalfBit надо правильные поставить, тупо скопировал из кода выше, лень в даташит лезть. и еще небольшое замечание по коду: инициализировать регистры константами лучше в виде TCCR0A = (1<<SOME_REG_BIT) | (1<<SOME_OTHER_REG_BIT). иначе код ставится плохочитаемым.
+1
  • avatar
  • _pv
  • 09 октября 2012, 14:09
Спасибо большое. Хоть мне до такого уровня и учится всю жизнь
0
А что это за магия и как она работает?

//IO макросы им. Аскольда Волкова. on, off, toggle, is_active, dir_out, dir_in
#define UART_RX B,(1<<4),L
#define UART_TX B,(1<<1),L
0
под рукой оказались для MSP430:
gpio.h:

#ifndef __GPIO_H_
#define __GPIO_H_

#define _setL(port,mask)          do { P##port##OUT &= ~mask; } while(0)
#define _setH(port,mask)          do { P##port##OUT |= mask; } while(0)
#define _clrL(port,mask)          do { P##port##OUT |= mask; } while(0)
#define _clrH(port,mask)          do { P##port##OUT &= ~mask; } while(0)
#define _bitL(port,mask)          (!(P##port##IN & mask))
#define _bitH(port,mask)          (P##port##IN & mask)
#define _inv(port,mask,val)       do { P##port##OUT ^= mask; } while(0)
#define _dir_out(port,mask,val)   do { P##port##DIR |= mask; } while(0)
#define _dir_in(port,mask,val)    do { P##port##DIR &= ~mask; } while(0)
#define _dir(port,mask,val)       (P##port##DIR & mask)
#define _pull_off(port,mask,val)  do {P##port##REN &= ~mask;} while(0)
#define _pull_on(port,mask,val)   do {P##port##REN |= mask;} while(0)
#define _set(port,mask,val)       _set##val(port,mask)
#define _clr(port,mask,val)       _clr##val(port,mask)
#define _bit(port,mask,val)       _bit##val(port,mask)
#define _setA(port,mask,val)      _setH(port,mask)
#define _clrA(port,mask,val)      _clrH(port,mask)

////////////////////////////////////////////////////////////////////////////////////

#define dir_out(x)                _dir_out(x)
#define dir_in(x)                 _dir_in(x)
#define dir(x)                    _dir(x)
#define on(x)                     _set(x)
#define off(x)                    _clr(x)
#define is_active(x)              (!! _bit(x))
#define toggle(x)                 _inv(x)
#define pull_off(x)               _pull_off(x)
#define pull_on(x)                _pull_on(x)
#define pull_up(x)                _setA(x)
#define pull_down(x)              _clrA(x)

#endif // __GPIO_H_

для авров и других легко ищутся в гугле.
смысл в том чтобы только один раз указать для ноги её порт, маску, и активный уровень L или H, и больше не греть голову, на каком именно порте сидит та или иная нога и даже какой у неё активный уровень 0 или 1.

например:
#define LED 1,BIT6,H
#define CS 2,BIT3,L
#define BUTTON 2,BIT2,L
#define OE1 1,BIT1,H
#define OE2 1,BIT2,H
#define OE 1,(BIT1|BIT2),H

on(CS);
SPITransfer(0xAA);
off(CS);

dir_out(LED);
dir_in(BUTTON);
pull_up(BUTTON);
pull_on(BUTTON);

toggle(LED);
if (is_active(BUTTON)) on(OE);
0
Спасибо, полезно.
0
а можно на один #define вешать сразу несколько действий?
0
Вопрос некорректен.
О каком #define ты говоришь и что подразумеваешь под действиями? Задать одной строчкой #define LED 1,BIT6,H несколько пинов? Нет, макросы Аскольда Волкова этого не позволяют.
0
Т.е. через; точку запятую нельзя перечислять команды? в одну строку
или только в функцию засовывать и ее вызывать?
0
телепатирую. вот примерно о таком:
#define SYS_TIMER_SETUP() TCCR1 = ((0<<CTC1)|(0<<COM1A1)|(0<<COM1A0)|(0<<CS13)|(0<<CS12)|(0<<CS11)|(1<<CS10));\
                          TCNT1 = 0x38;\
                          OCR1A = 0x00;\
                          TIMSK &= ~((1<<OCIE1A)|(1<<OCIE1B));\
                          TIMSK |= (1<<TOIE1) //NORMAL PORT OP, PRESCALER 1, 25uS, OVF INT

?
+1
обрати внимание, что слеша в последней строчке нет.
+1
это то что нужно)
Спасибо
0
это еще надо бы в do {...} while(0); завернуть, иначе вот такой, например, код:
if (flag) SYS_TIMER_SETUP();
сделает совсем не то, что подразумевалось.
0
в моем случае не надо, т.к. она увлеченно функцией прикидывается. ;)
а по уму — да, надо.
но я обычно ленюсь и тренирую память… ;)
0
именно здесь и надо. никто никакой функцией не прикидывается — это препроцессор.
с приведённым выше дефайном строка
if (flag) SYS_TIMER_SETUP();
развернётся в
if (flag) TCCR1 = ((0<<CTC1)|(0<<COM1A1)|(0<<COM1A0)|(0<<CS13)|(0<<CS12)|(0<<CS11)|(1<<CS10));
TCNT1 = 0x38;
OCR1A = 0x00;
и при активном flag выполнится только первая строчка из макроса, остальные будут выполняться всегда независимо от состояния flag.
лень и память тут непричем, будет просто неправильное поведение кода.
0
Я это пользую исключительно в виде
SYS_TIMER_SETUP();

но вообще — да, надо.
хотя кто настройку таймера в условии делать будет?..
0
«это» — понимать как приведенный кусок.
0
Почему именно do { } while(0);? =)

Я обычно оборачиваю просто в фигурные скобочки подобные ряды команд в определении… Хотя, что значит, обычно? Если надо много команд, надо делать функцию.
0
Дык вроде С не понимает одни лишь фигурные скобочки, без do/while/if/for/etc. Ну или не все версии понимают.
0
Да? Странно, всегда блоки отделял ими, где мне нужны новые локальные переменные а ля:

{
    uint8_t i;

    for(i = 0; i < CHANNEL_COUNT; i++)
    {
        // smth code
    }
}


Надо проверить будет в стандарте…
0
0
Хм, логично. Впрочем, мне это не надо, ибо в макросы гирлянды не пихаю и страдаю (хотя и не очень) выделением конструкций в блоки =)

То есть такого:

    if(nya)
        SomeFunc();
    else
        SomeAnotherFunc();

я никогда себе не позволю. Только хардкод:

    if(nya)
    {
        SomeFunc();
    }
    else
    {
        SomeAnotherFunc();
    }


Отсутствие загогулек для меня допустимо лишь при отсутствии else.

Впрочем, этот лаг, что очень хорошо, заметный. Не даст скомпилировать файл, если так уж выйдет.
0
Отсутствие загогулек для меня допустимо лишь при отсутствии else.
а вот это большая буква Ж.
один раз прое… л скобочки, и вместо того чтобы пить пиво, пялишься в монитор.
тогда уж разумнее допустить в любом уместном случае отсутствие скобок — держит в тонусе.
0
Пока такого ни разу не случалось =)
Ибо конструкцию if() else пишу сразу пустую и со скобочками =D

Да и макросы у меня теперь (раньше-то всякое было) только как какие-то заданные величины вместо магических чисел, настройки компиляции и, иногда, инициализация структур, особенно однотипных. Типа того:
#define PIN(Port, Pin) { PORT ## Port, DDR ## Port, PIN ## Port, (1 << Pin) }


Да и от
#define Название Число
я тоже отказался в пользу enum, за исключением несериализуемых параметров и настроек компиляции. Так что всё хорошо ^^
0
Если ты про макросы вообще, то макросам поровну. Это просто автозамена — пройтись по тексту, найти все вхождения MACRO_NAME и заменить их телом макроса. Что там внутри макроса препроцессору глубоко пофигу, так что вопрос только в том, чтобы после подстановки макросов получился корректный текст на С.
Для удобства можно делать многострочные макросы. Для этого нужно ставить символ «переноса» — бэкслеш. treasure уже привел пример. Тогда вместо имени макроса подставится вся эта портянка (уже без слешей, разумеется).
0
если на одном порте то почему бы и нет:
#define LED0 1,BIT0,H
#define LED1 1,BIT1,H
#define LED2 1,BIT2,H
#define LED3 1,BIT3,H
#define LEDS 1,(BIT0|BIT1|BIT2|BIT3),H

toggle(LED1);
on(LED2)
off(LEDS);
+1
Ну, работать-то они все равно как один пин будут при этом.
0
В таком виде работает на 9600
чуть прогрейте/охладите — о опа! — уже не работает…
0
Что-то странно много volatile-переменных? Или показалось?

>>Люди, как мне все это реализовать подключаемой библиотекой, на AVRstudio 6?
Сомнительная цель. Если конечно не хотите продавать их без исходников, что вряд ли.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.