Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32F3 SPI
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
bseyur
Добрый день! Нужна помощь решении проблемы с SPI микроконтроллера STM32F303.

Начал работать с семейством STM32 впервые, до этого плотно заимался с продукцией NXP. Хоть и без негативных эмоций не обошлось, но на чипе без особых проблем удалось запустить ядро, USART, DMA, пару таймеров. А вот с SPI вошел в ступор.

Работаем в режиме мастера. Симптомы наблюдаются следующие:
1) При передаче блока длиной 16 бит в режиме "MSB first" первым почему-то уходит младший байт, затем старший. Такое впечатление, будто ядро little-endian, а блок SPI big-endian. Если это фича, то я не понял ее смысла.
2) SPI странно реагирует на установку размера блока данных (биты 8-11 в регистре CR2). Если в регистр записать значение (7<<8), что соответсвует 8 битам данных, в действительности блок содержит 16 бит. 8 бит можно получить, если установить значение 3<<8, 4 бит. Для эксперимента в регистр SPI->DR производится запись только 1 раз.
3) При попытке принять данные с внешнего устройства считанное значение не соответствует реальному.

Подозреваю, все три случая как-то связаны между собой, но уже не знаю, куда копать. Процедуру инициализации писал сам, глядя в даташит и stmlib, готовые функции используются только для инициализации портов в/в и включения тактовых клоков. На одном модуле SPI сидит 5 устройств, конфигурация у каждого своя, поэтому содержимое регистров CR1 и CR2 для них загружается из выделенных ранее переменных. Оптимизация выключена.

В момент записи в регистр DR конфигурация такая:
CR1 = 0x36F, установлены в 1 биты SPE SSM SSI MSTR CPOL CPHA, BR=5, остальные 0
CR2 = 0x700, все биты равны 0, кроме поля Data size (должно быть 8 бит)

Если кто-то сталкивался с подобным поведением, подскажите, в каком направлении двигаться? sm.gif

Заполнение полей конфигурации для каждого устройства:
Код
TDeviceInfo* devinfo_ptr = SPIdeviceListPtr + device_id;
devinfo_ptr->CR1 = devinfo_ptr->CR2 = 0;
devinfo_ptr->CSEnablePtr = device_info->CSEnablePtr;
devinfo_ptr->CSDisablePtr = device_info->CSDisablePtr;
if (device_info->LSBFirst)
    devinfo_ptr->CR1 |= SPI_FirstBit_LSB;
devinfo_ptr->CR1 |= device_info->BaudRateControl&(7UL<<3);
if (!device_info->SlaveMode)
    devinfo_ptr->CR1 |= SPI_Mode_Master | SPI_NSS_Soft;
if (device_info->CPOL)
    devinfo_ptr->CR1 |= SPI_CPOL_High;
if (device_info->CPHA)
    devinfo_ptr->CR1 |= SPI_CPHA_2Edge;
devinfo_ptr->CR2 |= device_info->DataSize;


Настройка блока SPI:
Код
TDeviceInfo* devinfo_ptr = SPIdeviceListPtr + device_id;
SPI_TypeDef* SPIRegs;
if (SPINumber==SPIDRV_DEVICE1_INDEX)
    SPIRegs = SPI1;
else if (SPINumber==SPIDRV_DEVICE2_INDEX)
    SPIRegs = SPI2;

SPIRegs->CR1 &= ~SPI_CR1_SPE;
SPIRegs->CR1 = devinfo_ptr->CR1;
SPIRegs->CR2 = devinfo_ptr->CR2;
SPIRegs->CR1 = devinfo_ptr->CR1;
SPIRegs->CR2 &= ~SPI_CR2_DS;
SPIRegs->CR2 = devinfo_ptr->CR2;
SPIRegs->CR1 |= SPI_CR1_SPE;


Процедура отправки 16 бит данных в виде "2 раза по 8 бит":
Код
void CSPIDriver::SendData(uint16_t data)
    {
    SPI_TypeDef* SPIRegs;
    if (SPINumber==SPIDRV_DEVICE1_INDEX)
        SPIRegs = SPI1;
    else if (SPINumber==SPIDRV_DEVICE2_INDEX)
        SPIRegs = SPI2;

    uint8_t byte1;
    uint8_t byte2;
    if ((SPIdeviceListPtr+CurrentDeviceID)->CR1&SPI_CR1_LSBFIRST)
        {
        byte1 = ((uint8_t*)&data)[0];
        byte2 = ((uint8_t*)&data)[1];
        }
    else
        {
        byte1 = ((uint8_t*)&data)[1];
        byte2 = ((uint8_t*)&data)[0];
        }

    while (SPIRegs->SR&SPI_SR_BSY);
    while (SPIRegs->SR&SPI_SR_RXNE)
        {volatile uint32_t dummy = SPIRegs->DR;}

    SPIRegs->DR = byte1;
    SPIRegs->DR = byte2;
    while (!(SPIRegs->SR&SPI_SR_TXE));
    }


Процедура приема данных:
Код
int32_t CSPIDriver::ReadMass(uint8_t* data, uint32_t count)
    {
    if (!data || !count)
        return 0;
    SPI_TypeDef* SPIRegs;
    if (SPINumber==SPIDRV_DEVICE1_INDEX)
        SPIRegs = SPI1;
    else if (SPINumber==SPIDRV_DEVICE2_INDEX)
        SPIRegs = SPI2;

    while (SPIRegs->SR&SPI_SR_BSY);
    while (SPIRegs->SR&SPI_SR_RXNE)
        {volatile uint32_t dummy = SPIRegs->DR;}

    uint32_t items_sent = 0;
    uint32_t items_recieved = 0;
    while (items_recieved<count)
        {
        while (items_sent<count && (SPIRegs->SR&SPI_SR_FTLVL)!=SPI_TransmissionFIFOStatus_Full)
            {
            SPIRegs->DR = 0xFF;
            items_sent++;
            }
        while (SPIRegs->SR&SPI_SR_RXNE)
            data[items_recieved++] = SPIRegs->DR;
        }

    return items_recieved;
    }
adnega
Цитата(bseyur @ Jul 1 2013, 15:10) *
Если кто-то сталкивался с подобным поведением, подскажите, в каком направлении двигаться? sm.gif

Многое зависит от того каким образом вы записываете данные в регистр DR.
Если число бит в посылке от 4 до 8, то к нему нужно обращаться как к байту (т.е. читать и писать uint8_t).
Если число бит в посылке от 9 до 16, то к нему нужно обращаться как к полуслову (т.е. читать и писать uint16_t).
Для этого в StdLib я поправил структуру для SPI:
CODE

/**
* @brief Serial Peripheral Interface
*/

typedef struct
{
__IO uint16_t CR1; /*!< SPI Control register 1 (not used in I2S mode), Address offset: 0x00 */
uint16_t RESERVED0; /*!< Reserved, 0x02 */
__IO uint16_t CR2; /*!< SPI Control register 2, Address offset: 0x04 */
uint16_t RESERVED1; /*!< Reserved, 0x06 */
__IO uint16_t SR; /*!< SPI Status register, Address offset: 0x08 */
uint16_t RESERVED2; /*!< Reserved, 0x0A */
union
{
__IO uint16_t DR16; /*!< SPI data register, Address offset: 0x0C */
__IO uint8_t DR8; /*!< SPI data register, Address offset: 0x0C */
};

uint16_t RESERVED3; /*!< Reserved, 0x0E */
__IO uint16_t CRCPR; /*!< SPI CRC polynomial register (not used in I2S mode), Address offset: 0x10 */
uint16_t RESERVED4; /*!< Reserved, 0x12 */
__IO uint16_t RXCRCR; /*!< SPI Rx CRC register (not used in I2S mode), Address offset: 0x14 */
uint16_t RESERVED5; /*!< Reserved, 0x16 */
__IO uint16_t TXCRCR; /*!< SPI Tx CRC register (not used in I2S mode), Address offset: 0x18 */
uint16_t RESERVED6; /*!< Reserved, 0x1A */
__IO uint16_t I2SCFGR; /*!< SPI_I2S configuration register, Address offset: 0x1C */
uint16_t RESERVED7; /*!< Reserved, 0x1E */
__IO uint16_t I2SPR; /*!< SPI_I2S prescaler register, Address offset: 0x20 */
uint16_t RESERVED8; /*!< Reserved, 0x22 */
} SPI_TypeDef;



В зависимости от разрядности обращаюсь либо к SPIx->DR8, либо SOIx->DR16.
bseyur
Благодарю за ответ! Завтра попробую.
Genadi Zawidowski
1) Как вы к data ragister будете обращаться ниаче, чем описано в хедерах? ССЗБ!
2) Попробуйте функции разбиения на байты (и сборки слов) вместо применённых конструкций переписать в виде сдвигов, умножений, делений.

upd: посмотрел код ещё раз... Нет необходимости вообще в коде программы менять байты!
Код
    if ((SPIdeviceListPtr+CurrentDeviceID)->CR1&SPI_CR1_LSBFIRST)
        {
        byte1 = ((uint8_t*)&data)[0];
        byte2 = ((uint8_t*)&data)[1];
        }
    else
        {
        byte1 = ((uint8_t*)&data)[1];
        byte2 = ((uint8_t*)&data)[0];
        }

Просто передавайте data в 16-ти битном режиме.
adnega
Цитата(Genadi Zawidowski @ Jul 1 2013, 16:29) *
1) Как вы к data ragister будете обращаться ниаче, чем описано в хедерах? ССЗБ!

Ну... тут либо правленые хедеры и отсутствие совместимости, либо исползование стандартной библиотекой со всеми вытекающими
Цитата
[..] The read access of the SPI_DR register can be done using
SPI_ReceiveData8() (when data size is equal or inferior than 8bits) and.
SPI_I2S_ReceiveData16() (when data size is superior than 8bits)function
and returns the Rx buffered value. Whereas a write access to the SPI_DR
can be done using SPI_SendData8() (when data size is equal or inferior than 8bits)
and SPI_I2S_SendData16() (when data size is superior than 8bits) function
and stores the written data into Tx buffer.

Код
void SPI_SendData8(SPI_TypeDef* SPIx, uint8_t Data)
{
  uint32_t spixbase = 0x00;
  /* Check the parameters */
  assert_param(IS_SPI_ALL_PERIPH(SPIx));
  spixbase = (uint32_t)SPIx;
  spixbase += 0x0C;
  *(__IO uint8_t *) spixbase = Data;
}


Но это вопрос второй. Главное, что поведение SPI в F3 зависит от того, как к ним обращаться))
bseyur
Цитата(Genadi Zawidowski @ Jul 1 2013, 19:42) *
....
Просто передавайте data в 16-ти битном режиме.

Изначально я так и делал. Только в 16-битной передаче контроллер почему-то меняет местами младший и старший байты, т.е. таким образом: D7-D0, D15-D8. Но должна идти старшим битом вперед! Соответственно пришлось поменять байты вручную.
Я до сих пор не понимаю, почему так!
Golikov A.
ну как бы в литле ендиан сначала идет младший байт вроде как...
он в память наверное по 8 бит передает, через указатель, вот сначала и ушел младший байт...
Genadi Zawidowski
Цитата(bseyur @ Jul 1 2013, 17:12) *
Изначально я так и делал. Только в 16-битной передаче контроллер почему-то меняет местами младший и старший байты, т.е. таким образом: D7-D0, D15-D8. Но должна идти старшим битом вперед! Соответственно пришлось поменять байты вручную.
Я до сих пор не понимаю, почему так!


Судя по даташиту, в режиме LSB FIRST пойдёт DO...D7, затем D8..D15 после одной записи в регистр данных. Я использовал только 16-ти битный MSB FIRST, в котором идёт D15..D0
bseyur
Цитата(adnega @ Jul 1 2013, 18:52) *
Многое зависит от того каким образом вы записываете данные в регистр DR.
.....

Действительно, как только я начал обращаться к регистру DR как к байту, все глюки ушли. Благодарю за наводку. sm.gif Порядок байт теперь также верный.
По правде говоря, сначала я посчитал эту версию несостоятельной, к примеру, чипы NXP честно отбрасывают лишние разряды. Но позже увидел такую расплывчатую формулировку:
Цитата
When the SPIx_DR register is accessed, data frames are always right-aligned
into either a byte (if the data fits into a byte) or a word (see Figure 293). During
communication, only bits within the data frame are clocked and transferred.

Спрашивается, для чего тогда необходимо поле Data size?
Golikov A.
Бились сегодня с подобной проблемой на LPC1678, думаю механизмы SPI схожи
если в регистр SPI пихать данные типа так
SPI -> DR = 0x00FF, уходит верно

а если иметь char buf[10];
short int *buf_p =(short int *)buf;
SPI -> DR = buf_p++;
то уходит в поменянном порядке байт, то есть в момент выборки из памяти байты меняются местами, так как стоит little endian, что с одной стороны логично, но с другой вообще не дает работать...

внимание вопрос есть возможность это как то победить кроме как менять байты местами в памяти? Есть какие то способы выбирать из памяти данные 16 битными словами, но сохраняя порядок? Пробовали как советовали для STM обращаться к регистру и чарами и 16 битными указателями, и чего только не пробовали, все одно...
jcxz
А зачем? Если для ускорения, то гораздо лучше использовать DMA+SSP
Golikov A.
так там и есть ДМА, он 16 битными словами пихает приходящий по езернет буфер, в итоге порядок байт меняется местами. Попытка зарядить дма на биг ендиан режим, поменяло местами 16 битные слова)...

перешли на 8 битный режим, но 16 битный быстрее.

там 2 SPI на самом деле, один на DMA, другой ручками, на обоих та же фигня.
jcxz
Я тоже пробовал (и на 1768 и на 1758), тоже не удалось запустить в 16бит.
Пробовал делать перепаковку с помощью разных ширин источника и приёмника (DMACCxControl::SWidth/...DWidth) - тоже не получилось.
В результате забил - сделал 8-битную пересылку.

PS: Если найдёте решение - стукните мне тоже, пожалуйста
Golikov A.
похоже решения нет, только свапануть начальные массивы...
или передавать начальный массив не чарами а интами, ну то есть опять же свапануть начальный массив...
bseyur
Цитата(Golikov A. @ Sep 14 2013, 03:08) *
Бились сегодня с подобной проблемой на LPC1678, думаю механизмы SPI схожи
если в регистр SPI пихать данные типа так
SPI -> DR = 0x00FF, уходит верно

а если иметь char buf[10];
short int *buf_p =(short int *)buf;
SPI -> DR = buf_p++;
то уходит в поменянном порядке байт, то есть в момент выборки из памяти байты меняются местами, так как стоит little endian, что с одной стороны логично, но с другой вообще не дает работать...

А в вашем случае бит "MSB first" установлен?
Никогда не сталкивался с такими трудностями на NXP, всегда пользовался примитивнейшими конструкциями. Может, дело в объявлении регистров? У меня регистр DR объявлен с типом unsigned long.
Чтение 16-битного слова: data_ptr[index++] = (unsigned short)SSP1DR&0xFFFF;
Запись еще проще, что-то вроде: unsigned int value = 0xAABB; SSP1DR = value;
Передача идет старшим битом вперед, не помню, чтобы использовал обратный порядок. Соответственно, первым при передаче идет старший байт.
Golikov A.
дело в том что
unsigned int value = 0xAABB
в памяти лежит как 0xBB 0xАА 0x00 0x00
то есть если вы память заполните чарами
в порядке 0xAA, 0xBB, 0xCC, 0xDD, а потом считаете ее int-ом получите 0xDDCCBBAA, и так оно и пошлется.
приходят данные чарами, фактически в бигендиан последовательности, а на СПИ передается 16 битными словами, и из-за общего литл ендиана все переворачивает.

То есть как бы по логике все верно, а по реалу использовать трудно...
bseyur
Понятно, здесь SSP как бы ни при чем. Правильно будет организовать побайтную передачу.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.