PROGCONT.RU

Вход Регистрация
О САЙТЕ ГЛАВНАЯ
 

STM32F SPI описание и как применять CRC.

 Решил написать отдельно статью про этот не понятный и мутный CRC или циклический избыточный код, если кто не знает что это и как его едят или как его использовать, читаем статью.
 Задача CRC контроль сохранности передаваемых данных, он зависит от передаваемых данных, полинома( CRCPolynomial) и некого алгоритма вычисления.нет рисунка
 Давайте начнем изучение этой темы с выяснения что такое полином так как играет огромную роль во всем процессе, это некое число которое непосредственно участвует в вычисление CRC путем выполнения логической операции XOR( исключающее ИЛИ) в определенном алгоритме над битами принимаемых или отправляемых данных, единственное условие полином в передающем и принимающем устройстве должен быть одинаковым, значение его может быть любым но лучше выбирать из принятых так как дает больший процент не прохода ошибочных данных.

нет рисунка

 Теперь разберемся с алгоритмом вычисления CRC который изображен ниже.

нет рисунка

 Алгоритм вычисления работает следующим образом, после включения модуля вычисления CRC принимает начальное значение которое равно 255( 0xFF) или 65535(0xFFFF) далее ожидает прихода первого байта данных, после прихода выполнит логическую функцию XOR между байтом данных и CRC. Следующим этапом будет цикл который выполняется восемь или шестнадцать повторений( в зависимости сколько бит передаете за раз 8 или 16) над битами данных, в цикле проверяется первый бит данных если он равен 1 то байт сдвигается влево и выполняется XOR с полиномом, из за другого значения бита произойдет просто сдвиг байта. После выполнения полного цикла наш CRC готов, далее модуль будет ожидать прихода новых данных для продолжения вычисления нового CRC.

 Внимание! Если вы отправляете данные начиная с младшего бита то приходить в регистр модуля будут в перевернутом виде. Например отправляем 3( бинарный 00000011) в регистр придет 192( бинарный 11000000), на работу обоих устройств влиять не будет если настроены одинаково.

 Вот пример где полином будет равен 0xCB(бинарный 11001011), данные 0xC1(бинарный 11000001) и результат на выходе CRC равный 0x4C(бинарный 01001100).

нет рисунка

 Думаю вы поняли как происходит расчет этого CRC, теперь попробуем разобраться как его отправить и принять. Начнем с отправки, осуществляется это следующим образом, сбрасываем значение CRC выполнив подряд две функции SPI_CalculateCRC( SPIx, DISABLE) и SPI_CalculateCRC(SPI_TypeDef* SPIx, ENABLE), потом отправляем байты данных, после отправки последнего выполним функцию SPI_TransmitCRC( SPIx), CRC будет отправлен автоматически в итоге получится данные+1 байт, для корректной отправки выполняйте проверку флагов как у меня в примере ниже.
 Как подключать устройства читайте в SPI_Direction_1Line_Rx и SPI_Direction_1Line_Tx.

 Теперь как принять и проверить, что бы контроллер смог это сделать отправляющее устройство должен передать не менее двух байтов+1 байт CRC( передается автоматически), так как должны выполнить SPI_TransmitCRC( SPIx) перед приходом последнего байта данных и еще приходящий CRC байт не является данными но прочесть его вы обязаны иначе будут проблемы с флагом SPI_I2S_FLAG_RXNE.
 В примере ниже я использовать отладочную плату где установлен STM32F105RB( SPI1 отправляет SPI2 принимает), если случится ошибка передачи то штатный светодиод поменяет состояние, проверить можно отправив в место CRC любой байт.

#include "stm32f10x.h"
void pause(uint32_t i){while(i>0){i--;}}
void config_spi1(void);
void config_spi2(void);
//Массив для приема данных.
uint8_t data[3];
//Счетчик принимаемых данных.
uint8_t data_counter=0;
int main()
{
//Сбрасываем настройки тактирования.
RCC_DeInit();
//Настраиваем SPI1 и SPI2.
config_spi1();
config_spi2();
//Включаем тактирование CRC модуля.
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_CRC, ENABLE);
//Настройки для светодиода.
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef led;
led.GPIO_Pin=GPIO_Pin_7;
led.GPIO_Speed=GPIO_Speed_10MHz;
led.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &led);
//Настройка кнопки.
GPIO_InitTypeDef button;
button.GPIO_Pin=GPIO_Pin_0;
button.GPIO_Speed=GPIO_Speed_2MHz;
button.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &button);
while(1){
//Обработчик кнопки, если нажата выполним отправку данных и CRC.
if(GPIO_ReadInputDataBit( GPIOA, GPIO_Pin_0)){
//Двумя функциями сбрасываем счетчик CRC SPI1.
SPI_CalculateCRC( SPI1, DISABLE);
SPI_CalculateCRC( SPI1, ENABLE);
//Разрешаем работу SLAVE.
GPIO_ResetBits( GPIOA, GPIO_Pin_8);
//Отправляем 65.
SPI_I2S_SendData( SPI1, 65);
while(!SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ));
//Отправляем 66.
SPI_I2S_SendData( SPI1, 66);
while(!SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ));
//Отправляем CRC.
SPI_TransmitCRC( SPI1);
while(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY));
//Запрещаем работу SLAVE.
GPIO_SetBits( GPIOA, GPIO_Pin_8);
pause(400000);
}
}
return 0;
}
//Обработчик прерываний SPI2_IRQHandler.
void SPI2_IRQHandler(void){
if (SPI_I2S_GetFlagStatus( SPI2, SPI_I2S_FLAG_RXNE)){
//Должны выполнить перед приемом предпоследнего байта, тут после приема первого.
if(data_counter==0){SPI_TransmitCRC( SPI2);}
data[data_counter]=SPI_I2S_ReceiveData(SPI2);
data_counter++;
//Если приняли два байта данных плюс CRC то выполним сброс счетчика CRC и принятых данных.
if( data_counter==3){
data_counter=0;
//Сбрасываем счетчик CRC SPI2.
SPI_CalculateCRC( SPI2, DISABLE);
SPI_CalculateCRC( SPI2, ENABLE);
}
}
//Если возникнет ошибка CRC то выполним.
if (SPI_I2S_GetFlagStatus( SPI2, SPI_FLAG_CRCERR)){
GPIOC->ODR ^= GPIO_Pin_7;
SPI_I2S_ClearFlag( SPI2, SPI_FLAG_CRCERR);
}
}
//Конфигурация для SPI1 .
void config_spi1(void){
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef gpio_spi1;
gpio_spi1.GPIO_Pin=GPIO_Pin_5;
gpio_spi1.GPIO_Speed=GPIO_Speed_50MHz;
gpio_spi1.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &gpio_spi1);
gpio_spi1.GPIO_Pin=GPIO_Pin_7;
gpio_spi1.GPIO_Speed=GPIO_Speed_50MHz;
gpio_spi1.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_Init(GPIOA, &gpio_spi1);
GPIO_InitTypeDef Port_Out;
Port_Out.GPIO_Pin=GPIO_Pin_8;
Port_Out.GPIO_Speed=GPIO_Speed_50MHz;
Port_Out.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &Port_Out);
GPIO_SetBits( GPIOA, GPIO_Pin_8);
SPI_InitTypeDef spi1;
spi1.SPI_Direction=SPI_Direction_1Line_Tx;
spi1.SPI_Mode=SPI_Mode_Master;
spi1.SPI_DataSize=SPI_DataSize_8b;
spi1.SPI_CPOL=SPI_CPOL_Low;
spi1.SPI_CPHA=SPI_CPHA_1Edge;
spi1.SPI_NSS=SPI_NSS_Soft;
spi1.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
spi1.SPI_FirstBit=SPI_FirstBit_LSB;
spi1.SPI_CRCPolynomial=7;
SPI_Init( SPI1, &spi1);
SPI_Cmd( SPI1, ENABLE);
}
//Конфигурация для SPI2.
void config_spi2(void){
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE);
GPIO_InitTypeDef gpio_spi2;
gpio_spi2.GPIO_Pin=GPIO_Pin_12|GPIO_Pin_13;
gpio_spi2.GPIO_Speed=GPIO_Speed_50MHz;
gpio_spi2.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &gpio_spi2);
gpio_spi2.GPIO_Pin=GPIO_Pin_14;
gpio_spi2.GPIO_Speed=GPIO_Speed_50MHz;
gpio_spi2.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &gpio_spi2);
SPI_InitTypeDef spi2;
spi2.SPI_Direction=SPI_Direction_1Line_Rx;
spi2.SPI_Mode=SPI_Mode_Slave;
spi2.SPI_DataSize=SPI_DataSize_8b;
spi2.SPI_CPOL=SPI_CPOL_Low;
spi2.SPI_CPHA=SPI_CPHA_1Edge;
spi2.SPI_NSS=SPI_NSS_Hard;
spi2.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
spi2.SPI_FirstBit=SPI_FirstBit_LSB;
spi2.SPI_CRCPolynomial=7;
SPI_Init( SPI2, &spi2);
SPI_CalculateCRC( SPI2, ENABLE);
SPI_I2S_ITConfig( SPI2, SPI_I2S_IT_RXNE, ENABLE);
SPI_I2S_ClearFlag( SPI2, SPI_FLAG_CRCERR);
SPI_I2S_ITConfig( SPI2, SPI_I2S_IT_ERR, ENABLE);
NVIC_EnableIRQ( SPI2_IRQn);
SPI_Cmd( SPI2, ENABLE);
}
Статья №23 Дата:04/04/2018

Комментарии.