Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Управление мегой от ПК (через SPI)
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
zi4rox
Как управлять ATMega с компьютера, средствами SPI ?

Необходимо твердо в этом вопросе разобраться и для себя уяснить что и как. Поэтому поставил себе следующие условия простой задачи:

Мега, получает с ПК управляющий код (байт), анализирует его и выполняет какое то определенное действие (пускай будет моргать светодиодами на порту). Используется SPI, мега получается в режиме salve.

Схема к данной задачи:


После моих проб, ошибок и советов добрых людей получилось 2 реализации:
* Программирую в CodeVisionAVR

1. С использованием примера из даташита atmega16 (стр. 132)

Код
#include <mega16.h>

#define DDR_SPI DDRB
#define DD_MISO PORTB.6


// Управляющие коды, которые могут быть переданы
#define CMD1 0x01
#define CMD2 0x02
#define CMD3 0x03
#define CMD4 0x04

void SPI_SlaveInit(void)
{
/* Set MISO output, all others input */
DDR_SPI = (1 << DD_MISO);
/* Enable SPI */
SPCR = (1 << SPE);
}

unsigned char SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1 << SPIF)))
;
/* Return data register */
return SPDR;
}

void main (void){
SPI_SlaveInit();
DDRA = 0xFF; // сделали порт A выходом


while (1){

// анализируем полученный ответ
switch(SPI_SlaveReceive())
{
case CMD1:
PORTA = 0x01;
break;
case CMD2:
PORTA = 0x01;
break;
case CMD3:
PORTA = 0x03;
break;
case CMD4:
PORTA = 0x04;
break;
default:
PORTA = 0xFF;
break;
}

};
}


2. С использованием стандартных функций в spi.h (а также codewizard'а в cvavr):

Код
#include <mega16.h>
#include <spi.h>


// Управляющие коды, которые могут быть переданы
#define CMD1 0x01
#define CMD2 0x02
#define CMD3 0x03
#define CMD4 0x04

unsigned char read;

void main (void){

// инициализируем порты
PORTB=0x00;
DDRB=0x40; //тут SPI порт
PORTF=0x00;
DDRF=0xFF;

// инициализируем SPI
SPCR=0x40;
SPSR=0x00;

while (1){

read = spi(0x00);
/* [b]вот здесь мне непонятно что писать в качестве аргумента spi()? [без аргумента выдает ошибку]. [/b]*/

switch(read)
{
    case CMD1:
        PORTA = 0x01;
        break;
    case CMD2:
        PORTA = 0x02;
        break;
    case CMD3:
        PORTA = 0x03;
        break;
    case CMD4:
        PORTA = 0x04;
        break;
    default:
        PORTA = 0xFF;
        break;
}
         };
}


Проблема в том, что оба этих варианта не работают, и цели поставленной задачи достичь не удалось :cry:

Прошу помочь, натолкнуть в сторону где можно найти ошибку.
SysRq
(стёр, ибо поиском наешл сообщения автора и посмотрел что у него со стороны компа за софт)

--

Дежавю! Еще тема: http://electronix.ru/forum/index.php?showtopic=55022

На SS должен подаваться низкий уровень, чтобы SPI работал.
zi4rox
С компа, самописной программкой. В ней не сомневаюсь (я выдернул кусочек, из когда то написанного программатора для той же меги). Вся загвоздка имхо в прошивке контроллера.

*если требуется код проги выложу
SysRq
Цитата(zi4rox @ Apr 6 2009, 22:00) *
В ней не сомневаюсь.

Частоту оценить не забудьте: длительность низкого\высокого уровня клоков должна быть не менее 2 тактов МК.

Цитата(zi4rox @ Apr 6 2009, 22:00) *
Вся загвоздка имхо в прошивке контроллера.

Может быть и в аппаратуре ;) Если AVCC не подключен к VCC, порт А работать не будет. Про SS я выше написал (отердактировал, м.б. не заметили).
andrikk
последовательно сигналам - резисторы Ом по 100, для согласования уровней и SS на GND (mega8 без SS не работает точно, не знаю как 16-я).
=GM=
Цитата(zi4rox @ Apr 6 2009, 15:38) *
Проблема в том, что оба этих варианта не работают, и цели поставленной задачи достичь не удалось

Обратите внимание, как вы выталкиваете данные из лпт и как заталкиваете в ведомое устройство, возможно, проблема в этом. Сделайте симметричную команду. Если не сработает, то попробуйте просто принять любой байт и вывести его в портА на светодиоды, сразу будет видно, принимаете вы данные в принципе или нет.
Genadi Zawidowski
Цитата(SysRq @ Apr 6 2009, 22:37) *
Если AVCC не подключен к VCC, порт А работать не будет.

Are you sure? В смысле - вы это точно знаете?
SysRq
Цитата(Genadi Zawidowski @ Apr 7 2009, 22:09) *
Are you sure?


ATmega16 datasheet Rev. 2466R-06/08 says:
Цитата
AVCC is the supply voltage pin for Port A and the A/D Converter. It should be externally connected
to VCC, even if the ADC is not used. If the ADC is used, it should be connected to VCC
through a low-pass filter.
zi4rox
Цитата(=GM= @ Apr 7 2009, 20:12) *
попробуйте просто принять любой байт и вывести его в портА на светодиоды


Так и делал PORTA = SPDR;
На светодиоды выводит передаваемы байт. Т.е передача работает. Однако, не удается обработать полученный байт и уже оперировать им.

Код наподобе этого, и вариации, что я перепробывал - отказываются работать:

while (1){

Код
// анализируем полученный ответ
switch(SPI_SlaveReceive())
{
case CMD1:
PORTA = 0x01;
break;
case CMD2:
PORTA = 0x01;
break;
case CMD3:
PORTA = 0x03;
break;
case CMD4:
PORTA = 0x04;
break;
default:
PORTA = 0xFF;
break;
}


Так же не работает и попытка счиатать значение SPDR в переменную =(

Код
unsigned char read;
read = SPDR;


Подскажите, в чем здесь может быть заковырка?
SasaVitebsk
Только что сделал бут ч/з SPI на базе ByteBlaster.
Если надо, то могу процедуры нижнего уровня открыть. И про подводные камни рассказать.
zi4rox
Отписал Вам в личное сообщение, но не уверен, что оно дошло. Был бы очень благодарен, если бы поделились вашим опытом и информацией
SasaVitebsk
Дело в том, что mega работает в режиме slaveSPI. В этом режиме должна использоваться нога SS. Это надо не забывать. По ней осуществляется синхронизация. Опыт показывает, что достаточно синхронизировать пакет, а не каждый байт. Тем не менее...

У меня, как я и писал, связь осуществляется посредством ByteBlaster-а. Того же, который используется при программировании в AvReal. Я им пользуюсь, как и многие (спасибо Real-у). В данном адаптере не используется нога SS. В связи с этим ей надо управлять каким то другим макаром. У меня к этой ноге есть доступ непосредственно у самой меги. Чем я и пользуюсь. В противном случае надо реализовать хотя бы заземление её.

У меня SPI используется несколько не стандартно. Я его использую полудуплексно. То есть посылаю пакет меге, а потом получаю пакет ответа. В связи с некоторыми особенностями схемы, а также благодаря моей программной ошибке у меня иногда происходила рассинхронизация между пакетами. Я ввёл дополнительную синхронизацию, дабы обеспечить устойчивую работу. В последствии, когда ошибка была устранена, я не стал убирать синхронизацию, так как убедился в её эффективности. Сейчас стабильность 100% за счёт контроля и повторов.

Разрабатывалась в связи с тем, что требовалось задействовать в изделии ногу RESET. После соответственного прошивания фуза, ISP программирование перестаёт функционировать. Для того, чтобы можно было изделие перешивать, и был написан данный бутлоадер.

Это было краткое описание. Теперь сами процедуры.

На AVR.
Код
// Сбросить SPI
void ClrSPI(void)
{
  PORTB = 0xff;                                            // Передёрнуть SS
  PORTB = 0xfc;                                            // Обнулить
  SPSR;                                                    // сбросить флаги
  SPDR;        
}

Используется после каждого исполнения команды. То есть после цикла приём-передача. У вас может и не быть. Но должен быть SS на земле.
Код
// Ждать прихода символа по SPI
void WaitSPSR(void)
{
  while(!(SPSR & (1<<SPIF)))                             // ждать освобождения буфера
  {
     __watchdog_reset();                                // сбросить собаку
     if((_TIFR1 &(1<<TOV1))!=0)                            // Если пришло время моргнуть
     {
       _TIFR1 = 1<<TOV1;                                // перезарядить таймер
       BLINK;                                            // и моргнуть
     }
  }
}

То что вам не надо - типа моргания светодиодом - можете выкинуть. Просто беру "как есть", чтобы исключить ошибки.
Код
// Вывод символа на SPI
void TxByte(uint8_t    outbyte)                            // передача
{
  SPDR = outbyte;                                        // вывести
  WaitSPSR();                                              // ждать Завершения операции
  SPDR;
}


Ввод делаю прямо в тексте проги типа так...
Код
....
      SPDR=1;                                            // Для синхронизации
      WaitSPSR();                                          // ждать прихода символа
      c=SPDR;                                            // Читаем символ
....

Обратите внимание на "1".
Для самого приёма, естественно, без разницы что туда передаётся. А вот на IBM, я по этой "1" ловлю синхронизацию.


Теперь IBM.
Я использовал Delfi7 и SmallPort. Сам SmallPort я уже где-то выкладывал, но если надо, то прикреплю. Работает очень устойчиво и весит мало. Идёт под XP и 98/Me. Под другими просто не проверял. В вызовах вы легко разберётесь и просто переделаете под себя.
Проект был очень малобюджетный, поэтому я не изголялся. У заказчика тоже должен быть стимул. Чтобы получить качественный продукт - необходимо нормально его оплатить.

Код
Procedure WaitMks;
var
  i  : integer;
  a  : integer;
begin
  for i:=0 to FTime do
    a:=a+Form1.SmallPort.Port[$378];
end;

function TxRx(data:byte):byte;
var
  i     : integer;
  a,b   : byte;
begin
b:=0;
for i:=0 to 7 do begin
   b := b shl 1;                                // следующий бит
   a := (data shr 1) and $40;                   // текущий бит
   Form1.SmallPort.Port[$378]:= a;              // выдать в MOSI
   WaitMks;
   Form1.SmallPort.Port[$378]:= a or 1;         // стробировать (SCK=1)
   WaitMks;
   if((Form1.SmallPort.Port[$379] and $80)=0)
   then b := b or  1;                           // принять новый бит
   WaitMks;
   Form1.SmallPort.Port[$378]:= a;              // стробировать (SCK=0)
   data := data shl 1;                          // следующий бит
end;
result := b;
end;


Покажу ещё две процедуры - приём/передача пакета. Разобрав их поймёте как осуществлялась синхронизация.
Код
procedure WriteMes;
var
    i    : integer;
    a,
    data : byte;
begin
  for i:=0 to 31 do begin
    data := TxRx(SPACE);
    if data = 1 then break;
    a := SPACE and $40;                                 // текущий бит
    Form1.SmallPort.Port[$378]:= a;                     // выдать в MOSI
    WaitMks;
    Form1.SmallPort.Port[$378]:= a or 1;                // стробировать (SCK=1)
    WaitMks;
    Form1.SmallPort.Port[$378]:= a;                     // стробировать (SCK=0)
  end;
  Mes.crc8 := Mes.Address;                              // инициализировать CRC
  TxRx(FEND);                                           // Передаём символ FEND
  Mes.crc8 := calcCRC(Mes.crc8,FEND);                    // Подсчитать CRC8
  TxRx(Mes.Address or $80);                             // Передаём ADRWAKE
  Mes.crc8 := calcCRC(Mes.crc8,Mes.Address);            // Подсчитать CRC8
  TxRx(Mes.Command);                                    // Передаём команду
  Mes.crc8 := calcCRC(Mes.crc8,Mes.Command);            // Подсчитать CRC8
  OutByteWake(Mes.DataSize);                            // Передаём длину
  for i:=0 to Mes.DataSize-1 do
        OutByteWake(Mes.Data[i]);                       // Передаём данные
  OutByteWake(Mes.crc8);                                // Передаём CRC
end;

procedure ReadMes;
label mend;
var
    i   : integer;
    d,a : byte;
    err : boolean;
    nerr: integer;
begin
  Mes.Err := 0;                                         // Пока нет ошибок
  Mes.crc8 := Mes.Address;                              // инициализировать CRC
  nerr := 0;
  d:=0;
  repeat
    d := d shl 1;                                       // следующий бит
    a := SPACE and $40;                                 // текущий бит
    Form1.SmallPort.Port[$378]:= a;                     // выдать в MOSI
    WaitMks;
    Form1.SmallPort.Port[$378]:= a or 1;                // стробировать (SCK=1)
    WaitMks;
    if((Form1.SmallPort.Port[$379] and $80)=0)
    then d := d or  1;                                  // принять новый бит
    Form1.SmallPort.Port[$378]:= a;                     // стробировать (SCK=0)
    WaitMks;
    nerr := nerr+1;
    if(nerr>800)then begin
       Mes.Err := 11;                                    // Нет ответа
       goto mend;
    end;
  until(d=FEND);                                        // Ждём начала пакета
  Mes.crc8 := calcCRC(Mes.crc8,FEND);                    // Подсчитать CRC8
  d := TxRx(SPACE) and $7f;                             // Читаем адрес
  Mes.crc8 := calcCRC(Mes.crc8,d);                      // Подсчитать CRC8
  if(d<>Mes.Address)then begin
    Mes.Err := 6;                                       // Ошибка адреса
    goto mend;
  end;
  d := TxRx(SPACE);                                     // Читаем команду
  Mes.crc8 := calcCRC(Mes.crc8,d);                      // Подсчитать CRC8
  if((d and $80)<>0)then begin
    Mes.Err := 10;                                      // Ошибка команды
    goto mend;
  end;
  Mes.Command := d;
  d := GetByteWake;                                     // Читаем длину
  Mes.DataSize := d;
  for i:=0 to Mes.DataSize-1 do
        Mes.Data[i] := GetByteWake;                     // Читаем данные
  d := GetByteWake;                                     // Читаем CRC
  if(Mes.crc8<>0)then begin
    Mes.Err := 9;                                       // Ошибка CRC
  end;
  mend:
end;


В завершение прошу здесь опустить разбор моего стиля программирования. Я не выкладываю эти тексты, как образец стилистики. Цель - помочь. Пусть пользуется тот, кому это нужно. Писалось на скорую руку за 2 ночи (переделывалось), поэтому прошу извинение за стилистику написания.

PS: Совсем забыл. Прошу отметить что у меня откинута нога RESET. В противном случае надо соответствующий бит в процедурах установить в "1".
sigmaN
Я, конечно, мегу никогда не кодил, но как-то странно....
Может тип не тот...Или там оптимизатор выделывается
Может добавить volatile к read....

Там какая-то очень простая ошибка должна быть по идее

Это я в ответ на
Так же не работает и попытка счиатать значение SPDR в переменную =(
unsigned char read;
read = SPDR;
zi4rox
2SasaVitebsk

Спасибо, за столь развернутый пост. Буду разбираться теперь
andrikk
инициализация
Код
void spi_init(void)
{
  volatile unsigned char tmp;
  spi_state = SPI_STATE_IDLE;
  spi_count = 0;
  DDRB |= _BV(PB4);        //MISO as output
  DDRB &= ~(_BV(PB2) | _BV(PB3) | _BV(PB5));    //MOSI, SCK, SS as input
  PORTB |= (_BV(PB2) | _BV(PB3) | _BV(PB5));    //enable pullup MOSI, SCK, SS
  SPCR = _BV(SPE) | _BV(SPIE);        //enable SPI & interrupt
  tmp = SPDR;
  tmp = SPSR;
  SPDR = 0;
}

прерывание
Код
ISR(SPI_STC_vect)
{
  unsigned char data;
  data = SPDR;            //read data register
  if(spi_state == SPI_STATE_IDLE)
  {
    switch(data)
    {
      case SPI_CMD_RX:
        spi_state = SPI_STATE_RX;
        SPDR = 0x1;
        break;
      case SPI_CMD_TX:
        spi_state = SPI_STATE_TX;
        break;
....
    }
  }
  else
  {
    switch(spi_state)
    {
...
    }
  }
}


на компе часть кода от avrdude - ppiwin.c, ppi.h
и функция битбанг
Код
#define get_MISO() (ppi_get(&fd, PPISTATUS, 0x40))
#define set_MOSI() (ppi_set(&fd, PPIDATA, 0x20))
#define clear_MOSI() (ppi_clr(&fd, PPIDATA, 0x20))
#define set_SCK() (ppi_set(&fd, PPIDATA, 0x40))
#define clear_SCK() (ppi_clr(&fd, PPIDATA, 0x40))
#define set_rst() (ppi_clr(&fd, PPIDATA, 0x10))
#define clear_rst() (ppi_set(&fd, PPIDATA, 0x10))

unsigned char SPI_rxtx_byte(unsigned char data)
{
  unsigned char rx = 0;
  char i;
  
  for(i=0; i<8; i++) // loop for the 8 data bits
  {
    // send one bit
    if(data & 0x80) set_MOSI();
    else clear_MOSI();
    data <<= 1; // shift next bit in place

    set_SCK();  // SCK high
    usleep(SPI_SCK_DELAY);

    // receive one bit
    rx <<= 1; // shift next bit in place
    if(get_MISO())
    {
      rx |= 1;
    }
    clear_SCK();  // SCK lOW
    usleep(SPI_SCK_DELAY);
  }
  return rx;
}




повторюсь, без согласования входов по уровню (похоже КМОП защелкивались) - на двух компах из трех связь сбоила и не работала как надо.
zi4rox
Всё вроде делаю правильно, а нужного результата не получаю. Попробую собрать всё в кучу, и описать что получается.

Итак на данный момент код прошивки такой:

Код
#include <mega16.h>
#include <io.h>

#define DDR_SPI DDRB
#define DD_MISO PORTB.6

void SPI_SlaveInit(void)
{
/* Set MISO output, all others input */
DDR_SPI = (1 << 6);
/* Enable SPI */
SPCR = 0x40;
}

unsigned char SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1 << 7)));
/* Return data register */
return SPDR;
}

void main (void){

SPI_SlaveInit();
DDRA = 0xFF;

while (1){
//SPI_SlaveReceive();
PORTA = SPDR;
}

}


Как в реальности работает этот код:
1. МК запускается (светодиоды выключены)
2. Я посылаю байт на МК (светодиоды выключены)
// Т.е - по сути ничего не происходит
3. Если я делаю RESET то на светодиодах появляется переданный в пункте 2 байт (PORTA = SPDR;)
// Значит всё таки хоть и как-то криво, но байт передается
4. Если после пункта 3 (когда горят светодиоды), я посылаю ещё раз байт на МК - то всё снова гаснет, и отобразится лишь при следующем перезапуске

Ещё стоит заметить, что в коде я закомментировал и не использую процедуру ожидания завершения передачи байта (из даташита):
Код
unsigned char SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1 << 7)));
/* Return data register */
return SPDR;
}


Путем отладки - выяснил, что условие while(!(SPSR & (1 << 7))) - никогда не будет пройдено => ничего не произойдет

Вот такая вот загогулина
SasaVitebsk
Тем не менее у вас всё абсолютно правильно. Инициализация верна и приём символа абсолютно верен.

Согласно схемы приведенной в посте 1 у вас SS вывод в обрыве. SS - слэйв селект. При 1 обязательно, и при обрыве с высокой степенью вероятности у вас SPI в режиме слэйв не будет работать. Реально этот вывод у вас заземлён?
zi4rox
Да, я SS посадил на землю - без этого даже кривая передача байта была невозможной
SasaVitebsk
Блин просмотрел. У вас ошибка в голове.
Надо так

void main (void){

SPI_SlaveInit();
DDRA = 0xFF;

while (1){
PORTA = SPI_SlaveReceive();
}

}
SysRq
Питается от чего оно у вас? Фильтры по питанию стоят?
zi4rox
Цитата(SasaVitebsk @ Apr 20 2009, 21:55) *
Блин просмотрел. У вас ошибка в голове.
Надо так

void main (void){

SPI_SlaveInit();
DDRA = 0xFF;

while (1){
PORTA = SPI_SlaveReceive();
}

}


Так я уже пробывал =( Попробывал ещё раз - результат тотже плачевный. Никакой реакции. Мне кажется что загвоздка в самой функции ожидания окончания приёма - SPI_SlaveReceive() - на ней программа стопориться и не выполняется

Цитата(SysRq @ Apr 20 2009, 21:55) *
Питается от чего оно у вас? Фильтры по питанию стоят?


От блока питания, что сам собирал там фильтрация есть - всё нормально, думаю затык не в этом.

Что самое интересное, как всегда вылезет, что причина в чем то очень простом и возможно банальном =)
andrikk
попробуйте использовать 4094 (8–STAGE SHIFT-AND-STORE BUS REGISTER).
для Ваших задач, как нарисовано на схеме, самое оно.
SasaVitebsk
Да не может быть никаких затыков. Это не медицина. Здесь всё чётко до безобразия. Включил - хомуты устранил - работает.

Итак если у вас так как вы писали, а именно:
Код
#include <mega16.h>
#include <io.h>

#define DDR_SPI DDRB
#define DD_MISO PORTB.6

void SPI_SlaveInit(void)
{
/* Set MISO output, all others input */
DDR_SPI = (1 << 6);
/* Enable SPI */
SPCR = 0x40;
}

unsigned char SPI_SlaveReceive(void)
{
/* Wait for reception complete */
while(!(SPSR & (1 << 7)));
/* Return data register */
return SPDR;
}

void main (void){

SPI_SlaveInit();
DDRA = 0xFF;

while (1){
PORTA = SPI_SlaveReceive();
}

}


То всё будет работать.

Для того, чтобы исключить ошибки связи предлагаю подать на MOSI единицу (если светодиоды зажигаются единицей или 0 - если нулём) и "поискрить" SCK. Предварительно подперев её 0 (поискрить на 1).

Я уверен, что ваши светодиоды - загорятся.


PS: Кстати о порте А. Надеюсь питание на AVCC вы подали?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.