Как правильно получать данные через SPI расширенный буферный режим микроконтроллеров Microchip PIC24F?

Я программирую связь SPI с внешним чипом RF. Микроконтроллер - модель PIC24FJ64GA102 от Microchip.

Я хочу использовать расширенный буферный режим SPI.

Проблема: получить полученные байты из приемного буфера.

Используемая функция SPI:

void SPI1_get(uint8_t* data, uint16_t length) {
    uint16_t i = 0, l = length;
    uint8_t dummy;
    while (length > 0) {
        while (SPI1STATbits.SPITBF || SPI1STATbits.SPIRBF) {
            dummy = SPI1STAT;
        }
        do {
            SPI1BUF = 0xff;
        } while (SPI1STATbits.SPIRBF == 0 && --length > 0);
        do {
            while (SPI1STATbits.SRMPT == 0) {
            }
            data[i] = SPI1BUF;
            ++i;
        } while (i < l && SPI1STATbits.SRXMPT != 1);
    }
}

Здесь звонки:

uint8_t cmd[2]; cmd[0] = length; cmd[1] = address;
SPI1_put(cmd,2); // e.g: 0x02, 0x01
SPI1_get(buf,2); // e.g: 0x05, 0x01 (received data)

Связь в порядке, проверено осциллографом и модулем декодирования SPI. Данные на шине SPI аналогичны приведенному выше комментарию: отправлено 0x02 0x01 0xff 0xff, получено 0x00 0x00 0x05 0x01, но функция выше неправильно извлекает данные из буфера приема. Я уже протестировал множество комбинаций проверки флагов и прерываний, но в итоге лучший результат, который я могу получить, это: 0x00 0x01 (правильный только последний байт).

Также уже проверил список ошибок, где упоминаются две проблемы SPI, которые не (не должны) влиять на мой код.

Что, черт возьми, я делаю не так ?!

В соответствии с просьбой здесь функция SPI1_put ():

void SPI1_put(uint8_t* data, uint16_t length) {
    uint16_t i = 0;
    uint8_t dummy;
    for (; i < length; ++i) {
        while (SPI1STATbits.SPITBF)
            ; // maybe change to (_SPI1BEC == 7) ?
        SPI1BUF = data[i];
        dummy = SPI1BUF; //dummy read
    }
}

[последнее изменение: 05.02.2015]

Итак, сегодня я смог уделить больше времени этой конкретной проблеме и придумал порт предложения ElderBug, а также позаботился об ошибках, упомянутых в списке исправлений:

uint8_t out_buf[128];
uint8_t in_buf[128];

void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
    uint16_t len = out_len + in_len;
    uint16_t sent = 0, recv = 0, i = 0;
//  while (!SPI1STATbits.SRXMPT)
    sent = SPI1BUF; // empty buffer
    sent = SPI1BUF; // empty buffer
    sent = 0;
    if (out != out_buf && out != 0)
        memcpy(out_buf, out, out_len);
    while (sent < len && recv < len) {
        if (SPI1STATbits.SPIBEC != 7 && sent < len) {
            SPI1BUF = out_buf[sent++];
        }
        if (!SPI1STATbits.SRXMPT && recv < len) {
            in_buf[recv] = SPI1BUF, recv++;
        }
    }
    if (in != 0) {
        for (i = 0; i < in_len; ++i) {
            in[i] = in_buf[out_len + i];
        }
//      memcpy(in, in_buf + out_len, in_len);
    }
    for (i = 0; i < len; ++i) {
        out_buf[i] = 0xff;
        in_buf[i] = 0xff;
    }
}

Этот код в основном работает. За исключением, с которым мне не удалось обойтись:

Связь работает следующим образом:

  • 1 байт: r / w-бит + длина
  • 1 байт: адрес
  • 1–127 байт: данные

Итак, когда я читаю 1 байт из ведомого чипа, то есть отправляю 3 байта (rw + len, адрес, фиктивный байт), данные, которые у меня есть в моем in_buf буфере, равны 0xFF. А теперь странная вещь: когда я просто прочитал еще один байт, ничего не меняя (еще один фиктивный байт на шине), первый байт моего in_buf получил правильные данные. Но, похоже, это работает не всегда, потому что моя программа все еще застревает в некоторых моментах.

Я остался сидеть здесь с множеством вопросительных знаков.

Странная вещь 2-я: чтение 8 байтов, данные в моем буфере верны до последнего байта, последний байт равен 0xFF, должен быть 0x00. Wtf?

PS: Уже подал заявку в Microchip на поддержку по этому вопросу.


person qwc    schedule 22.01.2015    source источник
comment
Не могли бы вы опубликовать код для SPI1_put? Проблема также может исходить оттуда.   -  person ElderBug    schedule 22.01.2015
comment
Просто дикая догадка: в SPI1_put функции не должно ли в разделе шагов цикла for быть i++ вместо ++i? Потому что теперь вы никогда не обрабатываете 0-й байт буфера.   -  person Eimantas    schedule 22.01.2015
comment
Это просто неправильно. В цикле for не имеет значения, увеличивается ли ваш счетчик на c++ или ++c. И как я уже писал в своем вопросе, данные на самой шине (измеренные с помощью прицела) полностью верны. Проблема здесь в том, чтобы получить полученные данные из расширенного буфера SPI, а не из самого обмена.   -  person qwc    schedule 22.01.2015


Ответы (1)


Проблема в вашем SPI1_put.

В вашей SPI1_put функции вы читаете SPI1BUF сразу после начала передачи, и, таким образом, вы читаете, когда передача не завершена (поправьте меня, если я ошибаюсь, но чтение, когда FIFO пуст, не блокируется и просто возвращает предыдущий байт ). Функция должна быть примерно такой:

void SPI1_put(uint8_t* data, uint16_t length) {
    uint16_t sent = 0;
    uint16_t rec = 0;
    uint8_t dummy;
    while(1)
    {
        if( !SPI1STATbits.SPITBF && sent < length )
            SPI1BUF = data[sent++];
        if( !SPI1STATbits.SRXMPT && rec < length )
            dummy = SPI1BUF, rec++;
        if( sent == length && rec == length )
            break;
    }
}

Насчет SPI1_get, думаю, нормально, но не уверен. Дело в том, что он должен выглядеть очень похоже на SPI1_put, потому что SPI действительно симметричен в том, как он работает:

void SPI1_get(uint8_t* data, uint16_t length) {
    uint16_t sent = 0;
    uint16_t rec = 0;
    while(1)
    {
        if( !SPI1STATbits.SPITBF && sent < length )
            SPI1BUF = 0xff, sent++;
        if( !SPI1STATbits.SRXMPT && rec < length )
            data[rec++] = SPI1BUF;
        if( sent == length && rec == length )
            break;
    }
}

В целом, поскольку вы выполняете только синхронные вызовы, расширенный буфер бесполезен. У вас могут быть более простые функции с теми же результатами, просто выполняя «отправить, подождать, получить» для каждого байта.

ИЗМЕНИТЬ после обновления:

Ваш новый код выглядит правильно, за исключением условия в while, которое вы сделали неправильно. Вот исправленный код с некоторыми изменениями с комментариями, которые могут его улучшить (удалены буферные копии):

// No additional buffers

void SPI1_com(uint8_t* out, uint8_t* in, uint16_t out_len, uint16_t in_len) {
    uint16_t len = out_len + in_len;
    uint16_t sent = 0, recv = 0, i;
    uint16_t dummy;


    // After the call, SPI comms should have ended and RX FIFO should be empty.
    // If SPI1STATbits.SRXMPT is not 1 here,
    // it means there is a problem in the code. Report it somehow for debug ?
    // if( !SPI1STATbits.SRXMPT )
    //     error!

    // This loop is harmless but shouldnt be needed
    //while (!SPI1STATbits.SRXMPT)
        //dummy = SPI1BUF; // empty buffer

    i = 0;
    while (sent < len || recv < len) {
        if (SPI1STATbits.SPIBEC != 7 && sent < len) {
            // Here we are out of the buffer when sent>=out_len,
            // but it's ok because random bytes are fine here
            SPI1BUF = out[sent++];
        }
        if (!SPI1STATbits.SRXMPT && recv < len) {
            if( recv < out_len )
                // Before out_len, discard the data
                dummy = SPI1BUF;
            else
                // After out_len, store the data in in[]
                in[i++] = SPI1BUF;
            recv++;
        }
    }

}

Исправляет ли это какую-нибудь ошибку? Кроме того, это может показаться глупым, но правильно ли вы это называете? Я не знаю, что вы сделали, но для 1 байта чтения должно быть SPI1_com(two_byte_buffer,one_byte_buffer,2,1);.

Еще один момент: кажется, что вы всегда пытаетесь очистить RX FIFO в начале. Есть ли что-то на самом деле в FIFO? SPI1STATbits.SRXMPT ДОЛЖЕН быть 1, даже не пытаясь опустошить. Если это не так, значит, где-то в программном обеспечении есть ошибка. У вас есть другой код, использующий SPI?

person ElderBug    schedule 22.01.2015
comment
Старая версия моего программного обеспечения выполняет цикл «отправка, ожидание, получение» для каждого байта. Но в части отправки / получения есть задержка ожидания, которую я надеюсь устранить через буфер, потому что она составляет ~ 1,5 мкс на каждый переданный байт. - person qwc; 22.01.2015
comment
@qwc Какая скорость связи? 1,5 мкс - это хорошо, если частота <10 МГц. - person ElderBug; 22.01.2015
comment
Скорость соединения составляет 8 МГц. Устройство должно экономить время и энергию, поэтому цель состоит в том, чтобы устранить задержку в 1,5 мкс между байтами SPI. По словам руководства и специалистов производителя, ведомое устройство не заботится о задержках. - person qwc; 22.01.2015
comment
@qwc О, задержка в 1,5 мкс между байтами действительно не оптимальна (я думал, что в целом она составляет 1,5 мкс на байт). Тем не менее, хорошо выполненная команда «отправить, ждать, получить» (ожидание правого бита состояния) не должна вызывать задержки (потому что SP1BUF действует как 1-байтовый буфер), но улучшенный буфер тоже хорош. Я не тестировал опубликованный мной код, поэтому скажите мне, если у вас все еще есть проблемы. - person ElderBug; 22.01.2015
comment
Я не использовал ваш код, но нашел частичное решение, см. Свой вопрос. Когда это больше не будет частичным, я отправлю ответ ...;) Надеюсь, что скоро это сработает ... - person qwc; 22.01.2015
comment
@qwc В опубликованном вами решении все еще есть проблемы. В функции 'положить' вы на самом деле не ждете байта, вы просто читаете все, что нужно прочитать. Для последнего байта FIFO все еще пуст, и вы выходите, не получив. В «get» вы не должны очищать FIFO в начале, потому что, если вам нужно, это означает, что в другом месте что-то не так. Код, который я опубликовал, является только примером, но, если я не ошибся, он должен охватывать все случаи и использовать максимальную буферизацию (даже с длиной ›8). - person ElderBug; 22.01.2015
comment
Кратко протестировал ваш код, но сторожевой таймер пришел, когда использовался без изменений. Придется поработать над другой ошибкой до конца дня, поэтому я оставлю эту ветку на некоторое время там, где она сейчас. - person qwc; 22.01.2015
comment
qwc: попробуйте DMA, у вас есть двухбитная задержка между байтами. 1 бит на байт в 16-битном режиме. - person Marco van de Voort; 26.01.2015
comment
@MarcovandeVoort При правильном использовании буферизации задержек быть не должно. DMA может быть хорошей идеей (хотя и немного избыточен для синхронной функции), но его PIC не имеет DMA, так что об этом не может быть и речи. - person ElderBug; 26.01.2015
comment
IIRC есть некоторые накладные расходы из-за вложенного цикла. (Я использую dspic33e, бесплатный компилятор и тактовую частоту spi 16 МГц, кстати), но для коротких последовательностей вы можете добиться большего, имея особый случай наполнения fifo (желательно в mode16) и последующего чтения. Поскольку в конце такой процедуры вы оставляете FIFO пустым, это довольно безопасно для синхронных приложений. - person Marco van de Voort; 26.01.2015
comment
Использовал ваше предложение @ElderBug и немного оптимизировал его в отношении ошибок из списка исправлений. Но у меня все еще есть странные ошибки. - person qwc; 05.02.2015
comment
@qwc Повторно обновил свой ответ, потому что внезапно обнаружил фатальную ошибку. - person ElderBug; 05.02.2015
comment
О, черт возьми !!!!!!!! Как, черт возьми, я мог не заметить ЭТО! Мне чертовски стыдно за это. Я стал слеп из-за этой очевидной ошибки. Прошу прощения за это от всей души! - person qwc; 06.02.2015
comment
Что касается вашего беспокойства по поводу опорожнения приемного буфера, пожалуйста, посмотрите список ошибок моего типа PIC24: ww1.microchip.com/downloads/en/DeviceDoc/80000486h.pdf - person qwc; 06.02.2015
comment
@qwc Да, я посмотрел на ошибки, и это не должно повлиять на функцию, так как это происходит только тогда, когда PIC просыпается с уже включенным SPI. Предлагаемый обходной путь - просто отключить SPI перед сном. - person ElderBug; 06.02.2015
comment
@qwc О, и я не знаю, использовали ли вы код, который я репостил, но recv++ должен быть в конце (отредактирован). - person ElderBug; 06.02.2015
comment
Я редко использую предоставленный код напрямую. Итак, я переработал свой код после прочтения вашего предложения и сразу же распознал этот небольшой сбой. К сожалению, я не осознал основную проблему простого логического ИЛИ ....... - person qwc; 06.02.2015
comment
@qwc Если вы используете свой код с буферами, обратите внимание, что вначале вы тестируете out != out_buf, но в конце цикл очистки может сломать все, если out == out_buf или in == in_buf. Не обязательно проблема, посмотрите сами, если она есть. - person ElderBug; 06.02.2015