Jump to content

    

Программный SPI

Доброго дня уважаемые...

У кого то есть библиотека реализации программного SPI интерфейса на avr?

 

Нужен именно программный, ибо аппаратный уже занят. Да и девайс разведен уже на на другие выводы Мк.

 

Заранее вам благодарен.

Share this post


Link to post
Share on other sites
Нужен именно программный, ибо аппаратный уже занят. Да и девайс разведен уже на на другие выводы Мк.

 

Чтож там сложного то:

 

#define HI(x) SPI_PORT |= (1<<(x))
#define LO(x) SPI_PORT &= ~(1<<(x))

unsigned int SPIWriteWord(unsigned int cmd) {
  unsigned char i;
  unsigned int recv;
  recv = 0;
  LO(SCK);
  LO(nSS);
  for(i=0; i<16; i++) {
    if(cmd&0x8000) HI(SDI); else LO(SDI);
    HI(SCK);
    recv<<=1;    
    if( SPI_PIN&(1<<SDO) ) {
      recv|=0x0001;
    }
    LO(SCK);
    cmd<<=1;
  }
  HI(nSS);
  return recv;
}

 

Обзовите где нибудь выводы SDI, SDO, SCK. Тут 16битный вариант, сли нужно код легко переделывается на 8 бит.

Share this post


Link to post
Share on other sites

Чтож там сложного то:

 

#define HI(x) SPI_PORT |= (1<<(x))
#define LO(x) SPI_PORT &= ~(1<<(x))

unsigned int SPIWriteWord(unsigned int cmd) {
  unsigned char i;
  unsigned int recv;
  recv = 0;
  LO(SCK);
  LO(nSS);
  for(i=0; i<8; i++) {
    if(cmd&0x80) HI(SDI); else LO(SDI);
    HI(SCK);
    recv<<=1;    
    if( SPI_PIN&(1<<SDO) ) {
      recv|=0x0001;
    }
    LO(SCK);
    cmd<<=1;
  }
  HI(nSS);
  return recv;
}

 

Биг спс. Для 8 ми битного, правильно поправил? =)

Это на запись. А как на чтение? В случае двухстороннего обмена.

Edited by CIIAPTAK

Share this post


Link to post
Share on other sites
Это на запись. А как на чтение? В случае двухстороннего обмена.
Мда, тяжелый случай:) Так это оно и есть - загоняете в функцию байт для записи, автоматом функция вернула то, что прочитала.

Share this post


Link to post
Share on other sites

SPI куда элегантнее программировать на ассемблере, если аппаратно этого сделать по каким-то причинам нельзя. Элегантность заключена в том, что так можно эффективно использовать флаг переноса при сдвигах, строго выдержать меандр по времени (хотя последнее, обычно, не требуется) и сделать правильные задержки. На чистом С такое написать нельзя.

Здесь приведены сочиненные мной процедуры:

void Wr_Reg( char byte); // запись по SPI в регистр АЦП

char Rd_Reg( void); // чтение по SPI регистра АЦП

Вся остальная программа у меня на С, откуда я и вызываю функции Wr_Reg(byte) и Rd_Reg(). Их описание в хидере я только что привела, их тела на ассемблере выглядят так:

        RSEG CODE:CODE:NOROOT(1)

        PUBLIC Wr_Reg
Wr_Reg:
        ldi R17, 8
LoopWrReg:
        sbi PORTD, SCLK; установим SCLK в 1
        rol R16; сдвигаем регистр
        brcs SetSDI
        cbi PORTD, SDI; SDI = 0
        rjmp CliSCLK; 2 clocks
SetSDI:
        sbi PORTD, SDI; SDI = 1
        nop; 1+1 clocks
        nop
CliSCLK:
        cbi PORTD, SCLK; установим SCLK в 0
        dec R17
        brne LoopWrReg; если не 0
        ret

        PUBLIC Rd_Reg
Rd_Reg:
        ldi R17, 8
        clr R16; обнуляем регистр
LoopRdReg:
        sbi PORTD, SCLK; установим SCLK в 1
        nop
        cbi PORTD, SCLK; установим SCLK в 0
        lsl R16; сдвигаем регистр
        sbic PIND, SDO; SDO ?
        ori R16, 1; устанавливаем мл.бит
        dec R17
        brne LoopRdReg; если не 0
        ret

Связь осуществляется через порт D, который можно заменить на любой другой. SDI, SDO и SCLK по назначению соответствуют MOSI, MISO и СLK.

Суть не меняется, если вместо АЦП будет какое-то другое устройство.

P.S. Проект на IAR EWAVR, в котором имеется один ассемблерный файл (этот), а остальные на C. Микроконтроллер ATtiny2313.

Share this post


Link to post
Share on other sites

В тексте программы ничего нету про SDO. Поправьте, плз, чтоб студенты не ошибались:)

И еще (для страшных эстетов) - некая зависимость от тактовой частоты проца.

Share this post


Link to post
Share on other sites
В тексте программы ничего нету про SDO. Поправьте, плз, чтоб студенты не ошибались:)

 

Поправила.

 

И еще (для страшных эстетов) - некая зависимость от тактовой частоты проца.

 

Да-да! Это очень важно! И вовсе не для эстетов. В ассеблерную процедеру есть возможноть натолкать столько nop (пустых операций), чтобы сделать любую дополнительную задержку. Это очень даже может понадобится тогда, когда МК работает на более высокой частоте кварца, чем необходимо, чтобы соблюсти требования ведомого устройства. И тут дело не только в частоте его кварца, сколько в технических требованиях к обмену. Это весьма типичная задача при работе с АЦП, в даташите которых расписаны тайминги на SPI обмен.

Ассемблер очень хорош как раз для соблюдения всех этих таймингов и латентного времени между передачей команды и приемом данных. Однако смысл имеет писать на ассемблере только такую интерфейсную часть программы. Полностью переходить на ассемлер не советую - ошибки на нем совершаются гораздо чаще, чем на C, и программировать на нем медленнее.

Share this post


Link to post
Share on other sites
SPI куда элегантнее программировать на ассемблере...

А откомпилировать несколько сишных строк и посмотреть на результат было нельзя?

есть возможноть натолкать столько nop (пустых операций)

__no_operation();

или

asm ( "NOP" );

Share this post


Link to post
Share on other sites
Вот мой вариант софтового/железного SPI, с задержками без всякого ассемблера.

/*****************************************************************************
Обмен данными по SPI
записывает в SPI порт данные wr_data и возвращает прочитанные при обмене данные
******************************************************************************/
UCHAR ExSPI(UCHAR wr_data)
{    
      // проверяем джампер low_sck
      if(ISP_PIN & (1 << PIN_LOW_SCK)) // не замкнут, аппаратный SPI
    {    
          SPI_ON();    // включаем SPI
          SPDR = wr_data;    // загружаем SPI данными
        while(!(SPSR & (1<<SPIF))){};    // ждем окончания передачи
        return SPDR;    // возвращаем принятые данные
    }
    else // иначе софтверный SPI
    {    
          UCHAR rd_data;
        SPI_OFF();    // выключаем аппаратный SPI
          for(UCHAR i = 0; i < 8; i++)
        {    
              // отправляем старший бит отправляемого байта
              if(0 != (wr_data & 0x80))
                ISP_PORT |= (1 << PIN_MOSI); // = 1
            else
                ISP_PORT &= ~(1 << PIN_MOSI);    // = 0

            // подготавливаем следующий бит
            wr_data <<=1;

            // принимаем очередной (начиная со старшего) бит
            // принимаемого байта
            rd_data <<= 1;
            if(0 != (ISP_PIN & (1 << PIN_MISO)))
                rd_data++; // устанавливаем единичный бит, если высокий принимаемый уровень

            // формируем импульс на выводе SCK МК
            ISP_PORT |= (1 << PIN_SCK);    // высокий
            __delay_cycles(LOW_SCK_DELEY); // задержка
            ISP_PORT &= ~(1 << PIN_SCK);    // низкий
            __delay_cycles(LOW_SCK_DELEY); // задержка
        }

        return rd_data;
    }
}

Share this post


Link to post
Share on other sites
А откомпилировать несколько сишных строк и посмотреть на результат было нельзя?

 

__no_operation();

или

asm ( "NOP" );

На языке С даже задачу нельзя сформулировать так, чтобы его компилятор сгенерировал код подобной плотности. Причина в том, что понятие флагов переноса или переполнения на этом языке никак не отражено. А следовательно и воспользваться этим удобным механизмом нет возможности.

 

Натолкать задержек, конечно же, язык С позволяет. И не только указанным вами способом, но и вообще любого иного рода мусорным кодом.

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

 

Я не думаю, что длительное смотрение в откомпилированный результат позволит вам написать код на С, который работает быстрее, но в тоже время был бы полностью надежен. На С такие вещи пишут только тогда, когда торопиться некуда. Этот же код можно использовать даже в прерывании! (хотя и не рекомендуется). Не перевелись еще любители опрашивать АЦП прямо в той процедуре обработки прерывания, которое возникает от сигнала готовности данных DREADY.

 

Вот мой вариант софтового/железного SPI, с задержками без всякого ассемблера.

В этой теме никого ваш вариант не интересует. В начальном сообщении четко сформулирована задача - аппаратный SPI не использовать.

Share this post


Link to post
Share on other sites
Я не думаю...

Я же не просил Вас думать :( я просил просто откомпилировать, что-то вроде

void Wr_Reg( unsigned char cmd ) 
{
    for( char i=8; i; i-- ) 
      {    (cmd & 0x80) ? ( HI( MOSI ) ) : ( LO( MOSI ) );
        HI( SCK );
        cmd <<= 1;
        LO( SCK );
      }
}

и посмотреть, что там с "кодом подобной плотности"...

Share this post


Link to post
Share on other sites
Я же не просил Вас думать :( я просил просто откомпилировать, что-то вроде

...

и посмотреть, что там с "кодом подобной плотности"...

У вас "лишняя" проверка cmd & 0x80, т.к. тестировать быстрее при сдвиге cmd <<= 1, поскольку этот бит выпадающий.

К другим недостаткам вашего кода можно отнести отсутствие однотактной задержки между установкой MOSI и подачей клока SCK. Здесь желательно тоже пропустить один такт, чтобы MOSI успел достичь своего максимального или минимального значения.

 

И вообще, потуги вроде ваших :), возникают исключительно вследствии того, что вы знаете, какой должна быть данная процедура в кодах МК, но пыжитесь достигнуть этого, укрощая С. В таких случаях было бы более эффективно, если бы вы воплощали свое знание сразу на том языке (в данном случае ассемблере), который плозволяет учитывать все необходимые нюансы. А так вы вместо этого боретесь с языком С, пытаясь подогнать его под требуемую ассемлерную кодировку. Язык должен быть помощником, не противником. Не стоит мучить С, чтобы он выдавал тот код, который без напряга можно было бы написать на ассеблере. А на языке С пишут обычно то, в отношении чего у вас не будет в дальнейшем притензий к компилятору.

Share this post


Link to post
Share on other sites
У вас "лишняя" проверка....

Ближе к делу, т.е. поминаемой Вами "плотности кода". Сколько команд получили из сишного исходника и насколько это "не плотнее" зачем-то писанного на ASM.

И вообще, потуги вроде ваших , возникают исключительно вследствии того, что вы знаете, какой должна быть данная процедура в кодах МК

Потуги??? Абсолютно в лоб писанный сишный исходник. Какой должна в кодах MK мне по барабану - я пишу на в данном случае на 'C' тупейший алгоритм банального ногомахания и с трудом представляю,как можно на 'C' зачем-то написать по другому. Я даже, то что написал не компилировал ввиду полной для меня очевидности получения абсолютно приемлимого кода из под IAR компилятора.

отсутствие однотактной задержки между установкой MOSI и подачей клока SCK

Прочитайте предыдущий пост и добавьте сколько и куда хотите.

Share this post


Link to post
Share on other sites
Язык должен быть помощником, не противником.

Просто откомпильте - никакого оверхеда там не полУчите. Уже не те времена на дворе, чтоб было стыдно за компиляторы в таких простых ситуациях. А отсутствие операций с битом переноса - это только оттого, что операция типа sbrc Rx,7 ничуть не хуже. Необходимость в использовании бита переноса при приеме последовательных данных у меня лично возникала, но это было в другом контексте - когда читается capture register и полученное значение сравнивается с константой, соотвествующей некой центральной частоте. В общем, это совсем из оффтопа.

По поводу НОПов - имхо достаточно в асме проверку #if(F_CPU > 8000000UL) сделать (как там по-ИАРовски - не уверен, но в winavr сделал бы так) - и добавить один лишний НОП

Share this post


Link to post
Share on other sites
У вас "лишняя" проверка cmd & 0x80, т.к. тестировать быстрее при сдвиге cmd <<= 1, поскольку этот бит выпадающий.
Это смотря чего хотеть.

Если скважности SCK 2.000 и предельно точной настройки частоты, то да, лучше асм.

Если "лишь бы более-менее близко к 2 и побыстрее", то это пишется левой задней, быстрее заново написать,

чем искать откуда скопипастить, так как писалось уже неоднократно, в том числе ещё для 51-го.

Да, в зависимости от данных слегка дрожит длительность 1-ки.

Что-то не могу придумать пример, где это будет мешать.

Зато одна процедура и на ввод, и на вывод, довольно компактно и быстро.

 

#include <avr/io.h>
#include "pin_macros.h"

#define SCK B,0,H
#define MOSI B,1,H
#define MISO B,2,H

/*  ну и где-то в h-файле, если кого-то "полнота набора" волнует
*  static inline uint8_t spi_in(void) { retuirn spi_io(0xFF); }
*  static inline void spi_out(uint8_t b) { spi_io(b); }
*/

/* Это под SPI, у которого сдвиг по спаду, приёмник фиксирует по фронту, ну 
* данная подпрограмма - после фронта, лишь бы до спада.
*/
uint8_t spi_io(uint8_t b)
{
    uint8_t i = 8;
    do {
        OFF(SCK);
        OFF(MOSI);
        if(b & 0x80) ON(MOSI);
        b <<= 1;            // а вот она задержка от выдачи до фронта
        ON(SCK);
        if( ACTIVE(MISO) ) ++b;
    } while(--i);
    OFF(SCK);
    return b;
}

И компилируется в такое

.global    spi_io
    .type    spi_io, @function
spi_io:
/* prologue: frame size=0 */
/* prologue end (size=0) */
    ldi r25,lo8(8)
.L2:
    cbi 56-0x20,0  ; SCK = 0
    cbi 56-0x20,1  ; MOSI = 0
    sbrc r24,7
    sbi 56-0x20,1  ; MOSI = 1 если надо
.L3:
    lsl r24             ; задержка перед фронтом
    sbi 56-0x20,0  ; SCK = 1
    sbic 54-0x20,2 ; семплирование входа
    subi r24,lo8(-(1))
.L5:
    subi r25,lo8(-(-1))
    brne .L2
    cbi 56-0x20,0
    ldi r25,lo8(0)
/* epilogue: frame size=0 */
    ret

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now