STM8S SPI описание и как применять CRC.
Решил написать отдельно статью про этот не понятный и мутный CRC или циклический избыточный код, если кто не знает что это и как его едят или как его использовать, читаем статью.
Задача CRC контроль сохранности передаваемых данных, он зависит от передаваемых данных, полинома( CRCPolynomial) и некого алгоритма вычисления.
Давайте начнем изучение этой темы с выяснения что такое полином так как играет огромную роль во всем процессе, это некое число которое непосредственно участвует в вычисление CRC путем выполнения логической операции XOR( исключающее ИЛИ) в определенном алгоритме над битами принимаемых или отправляемых данных, единственное условие полином в передающем и принимающем устройстве должен быть одинаковым, значение его может быть любым но лучше выбирать из принятых так как дает больший процент не прохода ошибочных данных.
Теперь разберемся с алгоритмом вычисления CRC который изображен ниже.
Алгоритм вычисления работает следующим образом, после включения модуля вычисления CRC принимает начальное значение которое равно 255 или 0xFF далее ожидает прихода первого байта данных, после прихода выполнит логическую функцию XOR между байтом данных и CRC. Следующим этапом будет цикл который выполняется восемь раз над битами данных, в цикле проверяется первый бит данных если он равен 1 то байт сдвигается влево и выполняется XOR с полиномом, из за другого значения бита произойдет просто сдвиг байта. После выполнения полного цикла наш CRC готов, далее модуль будет ожидать прихода новых данных для продолжения вычисления нового CRC.
Внимание! Если вы отправляете данные начиная с младшего бита то приходить в регистр модуля будут в перевернутом виде. Например отправляем 3( бинарный 00000011) в регистр придет 192( бинарный 11000000), на работу обоих устройств влиять не будет если настроены одинаково.
Вот пример где полином будет равен 0xCB(бинарный 11001011), данные 0xC1(бинарный 11000001) и результат на выходе CRC равный 0x4C(бинарный 01001100).
Думаю вы поняли как происходит расчет этого CRC, теперь попробуем разобраться как его отправить и принять. Начнем с отправки, осуществляется это следующим образом, сбрасываем значение CRC выполнив подряд две функции SPI_CalculateCRCCmd( DISABLE) и SPI_CalculateCRCCmd( ENABLE), потом отправляем байты данных, после отправки последнего выполним функцию SPI_TransmitCRC( ), CRC будет отправлен автоматически в итоге получится данные+1 байт, для корректной отправки выполняйте проверку флагов как у меня в примере ниже.
Как подключать устройства читайте в Подключение и передача данных в режиме SPI_DATADIRECTION_1LINE_RX и SPI_DATADIRECTION_1LINE_TX.
#include "stm8s.h"
void pause(uint32_t p);
int main( void ){
//Настраиваем вывод для кнопки.
GPIO_Init( GPIOB, GPIO_PIN_7, GPIO_MODE_IN_FL_NO_IT);
//Настройка выводов для SPI.
GPIO_Init( GPIOC, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST);
GPIO_Init( GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST);
//Подготавливаем SPI и включаем.
SPI_Init( SPI_FIRSTBIT_LSB, SPI_BAUDRATEPRESCALER_64, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_1LINE_TX, SPI_NSS_SOFT, 7);
SPI_Cmd( ENABLE);
while(1){
if(!GPIO_ReadInputPin( GPIOB, GPIO_PIN_7)){
//Сбрасываем CRC.
SPI_CalculateCRCCmd( DISABLE);
SPI_CalculateCRCCmd( ENABLE);
//Тут вся отправка двух байтов 65, 66 и нашего CRC.
GPIO_WriteLow( GPIOC, GPIO_PIN_1);
SPI_SendData(65);
while(!SPI_GetFlagStatus( SPI_FLAG_TXE));
SPI_SendData(66);
while(!SPI_GetFlagStatus( SPI_FLAG_TXE));
//После отправки последнего байта отошлеться CRC.
SPI_TransmitCRC();
while(SPI_GetFlagStatus( SPI_FLAG_BSY));
GPIO_WriteHigh( GPIOC, GPIO_PIN_1);
pause(30000);
}
}}
void pause( uint32_t p){
for( uint32_t i=0; i<p; i++){ }
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1){ }
}
#endif
Теперь как принять и проверить, что бы контроллер смог это сделать отправляющее устройство должна передать не менее двух байтов+1 байт CRC, так как должны выполнить SPI_TransmitCRC() перед приходом последнего байта данных и еще приходящий CRC байт не является данными но прочесть его вы обязаны иначе будут проблемы с флагом SPI_FLAG_RXNE.
В примере ниже я использую отладочную плату STM8S Value line discovery если случится ошибка передачи то штатный светодиод поменяет состояние, проверить можно отправив в место CRC любой байт.
#include "stm8s.h"
void pause(uint32_t p);
uint8_t data_1;
uint8_t data_2;
uint8_t CRC_3;
int main( void ){
//Настройка светодиода.
GPIO_Init( GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_OD_LOW_SLOW);
//Настройки выводов для работы SPI.
GPIO_Init( GPIOC, GPIO_PIN_5, GPIO_MODE_IN_FL_NO_IT);
GPIO_Init( GPIOC, GPIO_PIN_7, GPIO_MODE_IN_FL_NO_IT);
GPIO_Init( GPIOE, GPIO_PIN_5, GPIO_MODE_IN_FL_NO_IT);
//Настраиваем SPI как SLAVE для приема данных.
SPI_Init( SPI_FIRSTBIT_LSB, SPI_BAUDRATEPRESCALER_64, SPI_MODE_SLAVE, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_1LINE_RX, SPI_NSS_HARD, 7);
//Включаем два прерывания приема данных и ошибки CRC.
SPI_ITConfig( SPI_IT_RXNE, ENABLE);
SPI_ITConfig( SPI_IT_CRCERR, ENABLE);
SPI_ClearFlag( SPI_FLAG_CRCERR);
enableInterrupts();
SPI_CalculateCRCCmd( ENABLE);
SPI_Cmd( ENABLE);
while(1){
}}
//Обработчик прерываний.
INTERRUPT_HANDLER(SPI_IRQHandler, 10){
//Данные пришли.
if(SPI_GetFlagStatus( SPI_FLAG_RXNE)){
SPI_ITConfig( SPI_IT_RXNE, DISABLE);
data_1=SPI_ReceiveData();
//Обязательно выполняем перед последним байтом.
SPI_TransmitCRC();
while(!SPI_GetFlagStatus( SPI_FLAG_RXNE));
data_2=SPI_ReceiveData();
while(!SPI_GetFlagStatus( SPI_FLAG_RXNE));
//Обязательно читаем CRC.
CRC_3=SPI_ReceiveData();
SPI_ITConfig( SPI_IT_RXNE, ENABLE);
//Сбрасываем CRC.
SPI_CalculateCRCCmd( DISABLE);
SPI_CalculateCRCCmd( ENABLE);
}
//Если произошла ошибка.
if(SPI_GetFlagStatus( SPI_FLAG_CRCERR)){
GPIO_WriteReverse( GPIOD, GPIO_PIN_0);
SPI_ClearFlag( SPI_FLAG_CRCERR);
}
}
void pause( uint32_t p){
for( uint32_t i=0; i<p; i++){ }
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1){ }
}
#endif