STM8 и неработающая шина LIN

Для одного проекта мне понадобилось сделать сниффер шины LIN. Для тех кто не знает, сниффер (от англ. sniff — нюхать) это устройство для перехвата данных.

Про LIN шину я как-нибудь расскажу в отдельной статье. А если кратко, то LIN-шина это почти тот же UART, только с напряжением высокого уровня 12В. Чаще всего применяется в современных автомобилях, в тех местах, где использование CAN шины слишком избыточно. Например, LIN шина часто используется в дверях автомобилей для управления локальными блоками: стеклоподъемником, поворотниками на боковом зеркале, мотором складывания боковых зеркал, положением зеркала и прочей локальной электроникой.

LIN-шина является полудуплексной шиной, то есть по этой шине нельзя одновременно передавать и принимать данные. В единицу времени можно
или передавать или принимать данные.

LIN-шина так же является однопроводной. То есть, состоящей из одного сигнального провода. В этом отношении она напоминает шину 1-wire. И так же как и в шине 1-wire, в LIN шине есть одно ведуще устройство (master) и одно или несколько ведомых (slave)

Для работы с LIN шиной я решил использовать микроконтроллер из семейства STM8. Конкретно я выбрал STM8S105K4T6. Почему именно этот микроконтроллер? Во-первых, в нем уже есть встроенный модуль для работы с этой шиной. А во-вторых, именно на этом микроконтроллере уже был разработан контроллер, которому и предстояло взять на себя функцию сниффера шины.

Для того что бы познакомится с LIN шиной я решил сначала написать тестовый код передатчика, который бы имитировал работу master-блока на шине. Код создавался в среде IAR

Итак, для тестирования кода для работы с LIN шиной использовался вот такой код:

int main( void )
{
   CLK_CKDIVR_bit.HSIDIV = 0;    // частота 16 МГц	
	
   UART2_BRR1 = 0x68;     // скорость 9600 at 16 MHz
   UART2_BRR2 = 0x03;
   UART2_CR3_LINEN = 1;   // Разрешение работы LIN
   UART2_CR2_TEN = 1;     // Разрешение передатчика
   UART2_CR2_REN = 1;     // Разрешение приемника
	
   while(1){
        _delay_ms(500);
        UART2_CR2_SBK = 1;  // Отправка start импульса
        outUart(0x55);      // Отправка поля синхронизации
        outUart(0x2E);      // И еще нескольких байт	 
        outUart(0x09);
        outUart(0x03);
        outUart(0x00);
        outUart(0x00);
        outUart(0xF6);
	}
}

void outUart(uint8_t data){    // Передача байта через UART	
  while(UART2_SR_TC == 0) {};  // Ожидание завершения передачи
  UART2_DR = data;             // Отправка байта	
}

В заголовочном файле iostm8s105k4.h есть вот такие структуры

typedef struct
{
  unsigned char SBK         : 1;
  unsigned char RWU         : 1;
  unsigned char REN         : 1;
  unsigned char TEN         : 1;
  unsigned char ILIEN       : 1;
  unsigned char RIEN        : 1;
  unsigned char TCIEN       : 1;
  unsigned char TIEN        : 1;
} __BITS_UART2_CR2;

При помощи этих структур и вот таких объявлений:

#define UART2_CR2_SBK            UART2_CR2_bit.SBK
#define UART2_CR2_RWU            UART2_CR2_bit.RWU
#define UART2_CR2_REN            UART2_CR2_bit.REN
#define UART2_CR2_TEN            UART2_CR2_bit.TEN
#define UART2_CR2_ILIEN          UART2_CR2_bit.ILIEN
#define UART2_CR2_RIEN           UART2_CR2_bit.RIEN
#define UART2_CR2_TCIEN          UART2_CR2_bit.TCIEN
#define UART2_CR2_TIEN           UART2_CR2_bit.TIEN

очень удобно обращаться к отдельным разрядам регистров.

Сделать это можно вот так:

UART2_CR2_TEN = 1; // Установить бит TEN в регистре UART2_CR2 
UART2_CR2_TEN = 0; // Сбросить бит TEN в регистре UART2_CR2

Запускаю код на тест, подключаюсь логическим анализатором и вижу вот такую картину:

Да что за дичь такая?!
И почему пакет не распознается логическим анализатором?
Понятно, что с пакетом что-то не то, но хотя бы стартовый импульс шины должен же был распознаться?

В общем, дальше было тщательное изучение документации, errata sheet (документа с описание ошибок работы микроконтроллера) и около одного часа разных экспериментов.

При очередном этапе отладки я обратил внимание на ассемблерную команду в окне отладки, которая соответствует строке
UART2_CR3_LINEN = 1;

Обратите внимание, что в строке при помощи команды BSET происходит установка 7 разряда регистра UART2_CR3:

Строка UART2_CR3_LINEN = 1 компилируется в единственную команду

Но, так быть не должно!
Судя по документации в регистре UART2_CR3 бит LINEN это 6-ой бит.

Регистр UART_CR3 (UART2_CR3)

Решаю закомментировать строку UART2_CR3_LINEN = 1; и вместо неё прописываю явное указание на установку 6 разряда UART2_CR3 |= 1<<6;

«Вручную» устанавливаем 6 бит регистра

И, наконец, все заработало!

Вот так должен выглядеть правильный пакет данных на LIN-шине

А причина оказалась там, где я и не ожидал.
В файле описаний iostm8s105k4.h была ошибка в указании
номера бита LINEN.

Правильно структура должна выглядеть вот так:

После исправления файла описания, программа со строкой UART2_CR3_LINEN = 1; наконец, заработала!

Самое интересное, что согласно документации бит 7 зарезервирован. Но, при этом есть пометка: Reserved, must be kept cleared. То есть, бит 7 обязан быть сброшенным!

Вот так незначительная ошибка в файле описаний может надолго заставить задуматься.

Одно радует, подобные ошибки в файлах описаний встречаются достаточно редко.