USB программатор для AVR и интерфейс обмена данными с ПК в одном флаконе

Авторы: 

Речь пойдет о том как получить USB программатор для AVR и интерфейс обмена данными с ПК в одном флаконе

Что понадобится из железа:

Схему можно собрать согласно автору AVReal (см. рисунок) - http://real.kiev.ua/old/avreal/ru/adapters.html

Что понадобится из ПО:

Проверить что устройство собрано и работает правильно можно выполнив следующую команду с использованием AVReal:

           avreal32.exe +mega324p -pd="DLP2232M A",s="FTPOH771A" -aft2232:reset=adbus3:enable=adbus4 -r test.hex >test.txt

Пример приведен для FT2232D в связке с Atmega324p. Подробности по командам программирования здесь: http://real.kiev.ua/old/avreal/ru/description.html

Код под микроконтроллер может быть следующим (что бы далеко за примером не ходить - взял из 3DO DiagBlastera под Atmega324p):

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

typedef unsigned char uint8;  

//входной и выходной кольцевые буферы
volatile uint8 dbi_ring[256];
volatile uint8 dbo_ring[256];
volatile uint8 dbo_down, dbo_up, dbi_down, dbi_up;

void SPI_SlaveInit(void)
{
   /* Set MISO output, all others input */
   DDRB = (1<<PB6);
   /* Разрешаем работу SPI и прерывание по окнчанию передачи байта */
   SPCR = (1<<SPE)|(1<<DORD)|(1<<SPIE);//|(1<<SPR0); // режим 0, LSB - см. Atmega324p Datasheet С. 168
   SPDR=0;  //обнуляем регистр данных
}

ISR(SPI_STC_vect) //прерывание по получению байта
{
 uint8 tmp=SPDR; //читаем данные

       //отправляем данние если есть, если нет - можно просто отправить любое значени
       //в данном примере подразумевается текстовый протокол, поэтому ноль используется как незначащий символ
   if( dbo_down==dbo_up )  //исходящий буфер пуст
      SPDR=0;
   else  //есть что отправить
      SPDR=dbo_ring[dbo_down++];        

   if(!tmp)return;  //ничего не получили - выход
   dbi_ring[dbi_up++]=tmp;  //получили - запись в кольцевой буфер для обработки в главной процедуре
}

void Debug_Init()
{
   dbo_down=dbo_up=0;   //обнулим выходной кольцевой буфер
   dbi_down=dbi_up=0;   //обнулим входной кольцевой буфер
   SPI_SlaveInit(); //инициализация ведомого SPI интерфейса
}

int main() // пример главной процедуры
{
   Debug_Init();  //инициализация SPI интерфейса
   DDRA = 0xff; // на порт А можно повесить светодиоды для проверки
   PORTA = 0;        
   sei();   //разрешаем прерывания  

   while(1)   //бесконечный цикл
   {
                //если пришли данные выведем их на порт А
      if(dbi_down!=dbi_up) PORTA = dbi_ring[dbi_down++];
   }
   return 0;
}

Для компиляции примера необходимо использовать AVR Studio и WinAVR.

Код со стороны ПК привожу в виде простейшего и потому надеюсь понятного примера:

#include <windows.h>
#include "FTD2XX.h"
#pragma comment(lib, "FTD2XX.lib")

//Класс для работы с AVR через FT2232 в режиме 0, LSB - см. Atmega164p datasheet С. 168
class SPIDeb
{
private:
        FT_HANDLE ftHandle;       //хендл устройства - надо отметить, что тип FTDI устройства в данном примере не проверяется, но это не сложно добавить )
        FT_STATUS ftStatus;       //переменная для статуса исполнения команд
        bool opened;        //признак что устройство открыто
public:
        SPIDeb(){opened=false;}; //конструктор
        ~SPIDeb(){Close();};  //деструктор
        bool Init(unsigned int divval);   //подключение устройства, divval - делитель для получения частоты = 12М/((divval+1)*2)
        int Send(const unsigned char *buff, int size); //отправка буфера по SPI
        int Read(unsigned char *buff, int size); //чтение принятых от устройства данных
        void Close();       //отключение устройства
};

bool SPIDeb::Init(unsigned int divval)
{
 DWORD sended=0; //переменная в которую пишется число отправленных байт
 char sendbuff[]={0x86,0x2,0x00};

        sendbuff[1]=divval&0xff;  //запишем в команду установки делителя частоты переданное значение
        sendbuff[2]=divval>>8;   //с помощью данной команды можно регулировать частоту SPI обмена

        if(opened)return true;
        ftStatus = FT_Open(0,&ftHandle); //открываем первое попавшееся устройство
        if (ftStatus != FT_OK) return false; //если ничего не открылось - информируем об ошибке

        UCHAR Mask = 0x13; // здесь указывается направление сигнала (1 - выход, 0 - вход)
                                          //соответствие сигналам FT2232: bit0(AD0)...bit7(AD7) - относительно контроллера будет: SCK,MOSI,MISO,RESET,CS.....
        UCHAR Mode = 2; // режим MPSSE см. док. FT_000109

        ftStatus = FT_SetBitMode(ftHandle,Mask,Mode); //установка режима
        FT_Write (ftHandle, (void*)sendbuff, 3, &sended); //отправим устройству команду установки частоты

        opened=true;
        if (ftStatus == FT_OK) return opened;

        //если режим MPSSE открытым устройством не поддерживается - сворачиваем деятельность
        Close();
        return false;
}

int SPIDeb::Send(const unsigned char *buff, int size)
{
 DWORD sended=0;   //переменная в которую пишется число отправленных байт
 unsigned char *sbf;   //буфер для отправки данных
        if(!opened)return 0;
        sbf=new unsigned char[size*12];  //выделяем память под поток команд для устройства

        //поскольку контроллеру нужно время на обработку принятого байта и извлечение из исходящего буфера данных для отправки
        //использовались четыре команды на каждый обмен байтами с устройством

        for(int i=0;i<size;i++)  //формирование массива команд для FT2232  см. док. FT_000109 с сайта производителя
        {
                sbf[i*12]=0x80;        //выбор SPI устройства - установка CS в низкий уровень
                sbf[i*12+1]=0x02;    
                sbf[i*12+2]=0x13;

                sbf[i*12+3]=0x3f;   //посылка байта в контроллер с одновременным чтением, полученный байт затем можно считать функцией Read
                sbf[i*12+4]=0x07;
                sbf[i*12+5]=buff[i];

                sbf[i*12+6]=0x80;  //отключение SPI устройства - CS в высокий уровень
                sbf[i*12+7]=0x12;
                sbf[i*12+8]=0x13;

                sbf[i*12+9]=0x1b;  //фиктивная запись в устройство - для задержки - что бы контроллер успел приготовиться к следующему обмену
                sbf[i*12+10]=0x07;
                sbf[i*12+11]=buff[i];
        }

        FT_Write (ftHandle, (void*)sbf, 12*size, &sended);  //запись блока команд в устройство

        delete []sbf; //чистим память - конечно лучше выделить фиксированный буфер, но на скорую руку и так сойдет

        return sended/12;  //вернем число переданных байт

}

int SPIDeb::Read(unsigned char *buff, int size)
{
 DWORD inquery=0,readed=0;
        if(!opened)return 0;
        FT_GetQueueStatus(ftHandle,&inquery);  //проверка - есть ли данные в очереди?
        if(inquery>size)inquery=size; //если данных больше чем размер буфера - примем столько сколько сможем
        if(!inquery)return 0; //нет данных - на выход
        FT_Read(ftHandle,buff,inquery,&readed); //чтение
        return readed; //возврат количества полученных байт
}

void SPIDeb::Close()  
{
        if(!opened)return;
        FT_SetBitMode(ftHandle,0,0); //сбрасываем устройство
        FT_Close(&ftHandle); //отключаем драйвер
}