AN428: программирование синтезаторов Si53xx Печать
Добавил(а) microsin   

В этой документации (перевод [1]) приведен пример программирования синтезатора Si53xx. Это подойдет для синтезаторов на основе DSPLL™ (Any-Frequency Precision Clocks, например Si5326 и Si5368), а также на основе технологии MultiSynth Any-Frequency, генераторы Any-Output Clock (например Si5338, Si5351, Si5356). Применен стартер-кит JumpStart компании Silicon Labs, однако исходный код может быть портирован и на другие платформы.

[2. Использование JumpStart]

1. Используйте оценочную плату разработчика и ПО конфигурирования, чтобы создать конфигурацию и соответствующий дамп регистров (register map) программируемой микросхемы синтезатора.

2. Используя инструментарий Silicon Labs MCU Integrated Development Environment (IDE), выполните компиляцию созданной register map вместе MCU firmware, написанном на языке C, и загрузите полученный двоичный код firmware в память микроконтроллера (MCU).

Примечание: C2 это двухпроводный протокол обмена Silicon Labs для внутрисхемного программирования и отладки процессоров Silicon Labs. Вместе с китами разработчика MCU поставляется соответствующий адаптер USB с интерфейсом C2.

AN428 Sending Register Map to MCU fig02

Рис. 2. Шаг 2: передача Register Map в MCU.

3. После сброса или включения питания MCU запишет данные register map в память целевого устройства (синтезатор Si53xx), после этого оно станет полностью сконфигурированным и начнет генерировать частоты. Для MCU используется свой собственный отдельный генератор, поэтому MCU может работать независимо от тактовых частот, которые генерирует целевое конфигурируемое устройство.

AN428 Programming Device Register Map fig03

Рис. 3. Шаг 3: программирование устройства данными Register Map.

4. Как только получена нужная конфигурация firmware, для процесса производства могут использоваться различные опции программирования MCU. Для получения подробной информации свяжитесь с Silicon Labs, или ознакомьтесь с документом [2], где описаны разные опции, включая методы внутрисхемного программирования (ISP) через интерфейсы C2 или JTAG, и как получить заранее запрограммированные MCU. В целях упрощения отслеживания продукции настроенные на заводе пользовательской прошивкой MCU получают уникальные обозначаемые part number.

[3. Выбор MCU]

Для использования JumpStart рассмотрите следующие функции MCU:

• Физический размер корпуса.
• Размер памяти.
• Последовательный интерфейс для связи с целевыми конфигурируемыми устройствами (I2C или SPI).
• Количество цифровых выводов портов.

Есть возможность перейти на однократно программируемые MCU (one-time programmable, OTP) после того, как код будет полностью разработан и отлажен на перешиваемых MCU (с памятью FLASH). OTP MCU значительно дешевле.

Используйте онлайн-поиск по параметрам [3] для выбора нужного MCU.

[4. Пример использования на основе C8051F30]

Silicon Labs C8051F301 (F301) MCU (с размером корпуса 3x3 мм) и микросхема Any-Frequency, Any-Output Clock Generator (Si5338N), подключенная через I2C, в этом примере конфигурируется для приложения gigabit Ethernet (GigE). Частотный план требует входной частоты кварца 25 МГц и двух частот 25 МГц с уровнями 3.3V CMOS на выходе CLK0 и 125 МГц уровней стандарта 3.3V LVDS на CLK1.

AN428 Example Block Diagram fig04

Рис. 4. Блок схема примера.

4.1. Описание firmware

Полный листинг кода см. в Приложении (врезка в конце статьи), и его также можно скачать с сайта. Чтобы найти исходный код примера, введите в строке поиска "AN428", и загрузите AN428 Jumpstart Software, выбрав его в результатах поиска.

1. Даташит Si5338 [4] содержит дополнительную информацию по записи конфигурации пользователя в RAM устройства Si5338 (см. секцию 3.5.3 даташита).
2. У микроконтроллера F301 есть 8 ножек цифровых порта I/O, сконфигурированных как выходы с открытым стоком и внутренне подключенными к ним слабыми верхними подтягивающими резисторами (weak pull-up). Изначально все сигналы устанавливаются в состояние лог. 1.
3. Машина состояний I2C master адаптирована из документа "AN141: SMBUS Communication for Small Form Factor Device Families" для использования вместе с F301 (см. раздел "6.3 EEPROM Example" в AN141). SDA подключен к ножке порта P0.0, и SCL к ножке порта P0.1 MCU при использовании SMBus (другое название шины I2C). Timer 1 конфигурируется для источника тактов SMBus, и эта частота устанавливается на скорость 400 kbps. Timer 2 конфигурируется для определения события таймаута, которое SMBus использует автоматически в работе своей машины состояний. Используйте этот таймер при необходимости, потому что эта функция не обязательна (для дополнительной информации см. секцию 13.4.1 в даташите C8051F30x и секцию 3.1.2 даташита AN141).
4. Адрес I2C Si5338 устанавливается в значение по умолчанию 0x70 (шестнадцатеричный формат). Функции I2C чтения и записи будут выполнять левый сдвиг адреса и корректное заполнение бита I2C R/W.

4.2. Кодирование Register Map

В этом примере используется кит Si5338-EVB и утилита ClockBuilder Desktop для разработки конфигурации плана частот и сохранения его в текстовый файл register map. В результате получается файл с расширением *.txt, где каждая строка имеет формат:

address,datah

Примечание: более новая версия ClockBuilder Pro 2.23 (релиз 10 апреля 2018 г.) выдает более подробный текстовый файл следующего формата:

Setting Name    Location      Start Address  Start Bit  Num Bits  NVM  Type
--------------  ------------  -------------  ---------  --------  ---  ----
SYS_INIT        0x0000[7]     0x0000         7          1         No   R/O 
LOL_B           0x0000[6]     0x0000         6          1         No   R/O 
LOL_A           0x0000[5]     0x0000         5          1         No   R/O 
CLK_LOS         0x0000[4]     0x0000         4          1         No   R/O 
XO_LOS          0x0000[3]     0x0000         3          1         No   R/O 
REVID           0x0000[1:0]   0x0000         0          2         No   R/O 
SYS_INIT_STKY   0x0001[7]     0x0001         7          1         No   R/W
...
 

Столбец "Setting Name" показывает мнемонику бита или группы бит настроек, "Location" показывает адрес регистра и битовые позиции настройки, что дублируется в столбцах "Start Address", "Start Bit", "Num Bits". Столбец "NVM" показывает, сохраняются ли эти настройки в энергонезависимой памяти (No означает, что настройки хранятся в RAM, Yes в NNM, т. е. Non-Volatile Memory). "Type" означает тип доступа (R/O только для чтения, R/W чтение и запись).

Массив register map в исходном коде MCU firmware будет выглядеть следующим образом:

{address,0xdata,0xmask},

Поле address представляет адрес регистра в памяти конфигурируемого устройства в десятичном формате. Поле data содержит значение, отправляемое в регистр по соответствующему адресу. Поле mask определяет, какие биты в данных регистра должны игнорироваться. Любой бит, в маске которого стоит лог. 1, используется и подлежит модификации, и любой бит, соответствующий лог. 0 в маске, должен быть сохранен, как он был ранее в регистре. Поля data и mask указываются в шестнадцатеричном формате. Все 3 поля это 8-битные целые числа в диапазоне 0 .. 255.

После формирования файлов откройте register_map.h в проекте JumpStart, и вставьте данные register map в декларацию массива Reg_Store между его фигурными скобками. Обновите определение константы NUM_REGS_MAX, чтобы она соответствовала реальному количеству байт в массиве. Обратите внимание, что не все регистры должны быть записаны в Si5338, в зависимости от её используемых функций. Для получения подробной информации по регистрам и их битовым настройкам см. даташит Si5338.

4.3. Функция генерации файла заголовка

Начиная с версии 2.5 ClockBuilder Desktop появилась функция "Header File Generation", она позволяет автоматически сохранить информацию register map в формате заголовочного файла на языке C. Этот файл можно использовать в проекте firmware микроконтроллера для программирования регистров используемой микросхемы Si53xx. Чтобы создать этот файл, кликните Options -> Save C Code Header File.

Примечание: в версии ClockBuilder Pro 2.23 сохранение заголовочного файла осуществляется по-другому. После открытия проекта выберите Export, после чего в окне диалога настройки на закладке Register File выберите Options Export Type: C Code Header File и нажмите кнопку "Save to File...".

AN428 Save C Header File fig05a

AN428 Save C Header File fig05b

Рис. 5. Сохранение заголовочного файла на языке C.

После того, как файл сгенерирован, он должен быть скомпилирован вместе с последней версией JumpStart firmware для использования в MCU, для чего файл register_map.h заменяется на новый.

Сгенерированный файл заголовка имеет корректный синтаксис C, и содержит следующую информацию:

• Всю необходимую информацию настройки выходного драйвера, подсистем Multisynth, конфигурацию входов, расширения выходного спектра, а также настройки частоты и фазы формируемых выходных тактов.
• Адресацию бит, регистров и страниц регистров (чтобы обработать ситуацию, когда адреса регистров больше 255).
• Вычисленное значение NUM_REGS_MAX.

Имейте в виду, что сгенерированный заголовочный файл не оптимизирован по содержимому в с сравнении с данными настроек, которые могли бы быть созданы вручную, когда для настройки используется только ограниченное подмножество регистров. Например, если нет необходимости менять настройки расширения спектра и фазы, автоматически сгенерированный файл будет занимать больше места в памяти программ, чем необходимо в действительности.

4.4. Заголовочный файл с данными Register Map

// Register map для использования вместе с апноутом AN428 (JumpStart)
// http://www.silabs.com/products/clocksoscillators/pages/default.aspx
// Copyright 2010 Silicon Laboratories
// Сгенерировано ClockBuilder Desktop software version 2.7
// Дата создания: среда, 15 сентября 2010 года, 9:56 AM
// Входная частота синтезатора Si5338 (МГц): 25.000000000

#define NUM_REGS_MAX 350

typedef struct Reg_Data
{
   unsigned char Reg_Addr;
   unsigned char Reg_Val;
   unsigned char Reg_Mask;
} Reg_Data;

Reg_Data const code Reg_Store[NUM_REGS_MAX] =
{
   { 0,0x00,0x00},
   { 1,0x00,0x00},
   { 2,0x00,0x00},
   { 3,0x00,0x00},
   { 4,0x00,0x00},
   { 5,0x00,0x00},
   { 6,0x08,0x1D},
   { 7,0x00,0x00},
   { 8,0x70,0x00},
   { 9,0x0F,0x00},
   { 10,0x00,0x00},
   { 11,0x00,0x00},
   { 12,0x00,0x00},
   { 13,0x00,0x00},
   { 14,0x00,0x00},
   { 15,0x00,0x00},
   { 16,0x00,0x00},
   { 17,0x00,0x00},
   { 18,0x00,0x00},
   { 19,0x00,0x00},
   { 20,0x00,0x00},
   { 21,0x00,0x00},
   { 22,0x00,0x00},
   { 23,0x00,0x00},
   { 24,0x00,0x00},
   { 25,0x00,0x00},
   { 26,0x00,0x00},
   { 27,0x70,0x80},
   { 28,0x16,0xFF},
   { 29,0x90,0xFF},
   { 30,0xA0,0xFF},
   { 31,0xC0,0xFF},
   { 32,0xC0,0xFF},
   { 33,0xE3,0xFF},
   { 34,0xE3,0xFF},
   { 35,0x00,0xFF},
   { 36,0x06,0x1F},
   { 37,0x03,0x1F},
   { 38,0x06,0x1F},
   { 39,0x06,0x1F},
   { 40,0xE3,0xFF},
   { 41,0x0E,0x7F},
   { 42,0x23,0x3F},
   { 43,0x00,0x00},
   { 44,0x00,0x00},
   { 45,0x00,0xFF},
   { 46,0x00,0xFF},
   { 47,0x14,0x3F},
   { 48,0x3A,0xFF},
   { 49,0x00,0xFF},
   { 50,0xC4,0xFF},
   { 51,0x07,0xFF},
   { 52,0x10,0x7F},
   { 53,0x00,0xFF},
   { 54,0x08,0xFF},
   { 55,0x00,0xFF},
   { 56,0x00,0xFF},
   { 57,0x00,0xFF},
   { 58,0x00,0xFF},
   { 59,0x01,0xFF},
   { 60,0x00,0xFF},
   { 61,0x00,0xFF},
   { 62,0x00,0x3F},
   { 63,0x10,0x7F},
   { 64,0x00,0xFF},
   { 65,0x30,0xFF},
   { 66,0x00,0xFF},
   { 67,0x00,0xFF},
   { 68,0x00,0xFF},
   { 69,0x00,0xFF},
   { 70,0x01,0xFF},
   { 71,0x00,0xFF},
   { 72,0x00,0xFF},
   { 73,0x00,0x3F},
   { 74,0x10,0x7F},
   { 75,0x00,0xFF},
   { 76,0x00,0xFF},
   { 77,0x00,0xFF},
   { 78,0x00,0xFF},
   { 79,0x00,0xFF},
   { 80,0x00,0xFF},
   { 81,0x00,0xFF},
   { 82,0x00,0xFF},
   { 83,0x00,0xFF},
   { 84,0x00,0x3F},
   { 85,0x10,0x7F},
   { 86,0x00,0xFF},
   { 87,0x00,0xFF},
   { 88,0x00,0xFF},
   { 89,0x00,0xFF},
   { 90,0x00,0xFF},
   { 91,0x00,0xFF},
   { 92,0x00,0xFF},
   { 93,0x00,0xFF},
   { 94,0x00,0xFF},
   { 95,0x00,0x3F},
   { 96,0x10,0x00},
   { 97,0x00,0xFF},
   { 98,0x30,0xFF},
   { 99,0x00,0xFF},
   {100,0x00,0xFF},
   {101,0x00,0xFF},
   {102,0x00,0xFF},
   {103,0x01,0xFF},
   {104,0x00,0xFF},
   {105,0x00,0xFF},
   {106,0x80,0xBF},
   {107,0x00,0xFF},
   {108,0x00,0x7F},
   {109,0x00,0xFF},
   {110,0x40,0xFF},
   {111,0x00,0xFF},
   {112,0x00,0x7F},
   {113,0x00,0xFF},
   {114,0x40,0xFF},
   {115,0x00,0xFF},
   {116,0x80,0xFF},
   {117,0x00,0xFF},
   {118,0x40,0xFF},
   {119,0x00,0xFF},
   {120,0x00,0xFF},
   {121,0x00,0xFF},
   {122,0x40,0xFF},
   {123,0x00,0xFF},
   {124,0x00,0xFF},
   {125,0x00,0xFF},
   {126,0x00,0xFF},
   {127,0x00,0xFF},
   {128,0x00,0xFF},
   {129,0x00,0x0F},
   {130,0x00,0x0F},
   {131,0x00,0xFF},
   {132,0x00,0xFF},
   {133,0x00,0xFF},
   {134,0x00,0xFF},
   {135,0x00,0xFF},
   {136,0x00,0xFF},
   {137,0x00,0xFF},
   {138,0x00,0xFF},
   {139,0x00,0xFF},
   {140,0x00,0xFF},
   {141,0x00,0xFF},
   {142,0x00,0xFF},
   {143,0x00,0xFF},
   {144,0x00,0xFF},
   {145,0x00,0x00},
   {146,0xFF,0x00},
   {147,0x00,0x00},
   {148,0x00,0x00},
   {149,0x00,0x00},
   {150,0x00,0x00},
   {151,0x00,0x00},
   {152,0x00,0xFF},
   {153,0x00,0xFF},
   {154,0x00,0xFF},
   {155,0x00,0xFF},
   {156,0x00,0xFF},
   {157,0x00,0xFF},
   {158,0x00,0x0F},
   {159,0x00,0x0F},
   {160,0x00,0xFF},
   {161,0x00,0xFF},
   {162,0x00,0xFF},
   {163,0x00,0xFF},
   {164,0x00,0xFF},
   {165,0x00,0xFF},
   {166,0x00,0xFF},
   {167,0x00,0xFF},
   {168,0x00,0xFF},
   {169,0x00,0xFF},
   {170,0x00,0xFF},
   {171,0x00,0xFF},
   {172,0x00,0xFF},
   {173,0x00,0xFF},
   {174,0x00,0xFF},
   {175,0x00,0xFF},
   {176,0x00,0xFF},
   {177,0x00,0xFF},
   {178,0x00,0xFF},
   {179,0x00,0xFF},
   {180,0x00,0xFF},
   {181,0x00,0x0F},
   {182,0x00,0xFF},
   {183,0x00,0xFF},
   {184,0x00,0xFF},
   {185,0x00,0xFF},
   {186,0x00,0xFF},
   {187,0x00,0xFF},
   {188,0x00,0xFF},
   {189,0x00,0xFF},
   {190,0x00,0xFF},
   {191,0x00,0xFF},
   {192,0x00,0xFF},
   {193,0x00,0xFF},
   {194,0x00,0xFF},
   {195,0x00,0xFF},
   {196,0x00,0xFF},
   {197,0x00,0xFF},
   {198,0x00,0xFF},
   {199,0x00,0xFF},
   {200,0x00,0xFF},
   {201,0x00,0xFF},
   {202,0x00,0xFF},
   {203,0x00,0x0F},
   {204,0x00,0xFF},
   {205,0x00,0xFF},
   {206,0x00,0xFF},
   {207,0x00,0xFF},
   {208,0x00,0xFF},
   {209,0x00,0xFF},
   {210,0x00,0xFF},
   {211,0x00,0xFF},
   {212,0x00,0xFF},
   {213,0x00,0xFF},
   {214,0x00,0xFF},
   {215,0x00,0xFF},
   {216,0x00,0xFF},
   {217,0x00,0xFF},
   {218,0x00,0x00},
   {219,0x00,0x00},
   {220,0x00,0x00},
   {221,0x0D,0x00},
   {222,0x00,0x00},
   {223,0x00,0x00},
   {224,0xF4,0x00},
   {225,0xF0,0x00},
   {226,0x00,0x00},
   {227,0x00,0x00},
   {228,0x00,0x00},
   {229,0x00,0x00},
   {231,0x00,0x00},
   {232,0x00,0x00},
   {233,0x00,0x00},
   {234,0x00,0x00},
   {235,0x00,0x00},
   {236,0x00,0x00},
   {237,0x00,0x00},
   {238,0x14,0x00},
   {239,0x00,0x00},
   {240,0x00,0x00},
   {242,0x00,0x02},
   {243,0xF0,0x00},
   {244,0x00,0x00},
   {245,0x00,0x00},
   {247,0x00,0x00},
   {248,0x00,0x00},
   {249,0xA8,0x00},
   {250,0x00,0x00},
   {251,0x84,0x00},
   {252,0x00,0x00},
   {253,0x00,0x00},
   {254,0x00,0x00},
   {255,1,0xFF},     // Установка бита страницы в значение 1.
   { 0,0x00,0x00},
   { 1,0x00,0x00},
   { 2,0x00,0x00},
   { 3,0x00,0x00},
   { 4,0x00,0x00},
   { 5,0x00,0x00},
   { 6,0x00,0x00},
   { 7,0x00,0x00},
   { 8,0x00,0x00},
   { 9,0x00,0x00},
   { 10,0x00,0x00},
   { 11,0x00,0x00},
   { 12,0x00,0x00},
   { 13,0x00,0x00},
   { 14,0x00,0x00},
   { 15,0x00,0x00},
   { 16,0x00,0x00},
   { 17,0x01,0x00},
   { 18,0x00,0x00},
   { 19,0x00,0x00},
   { 20,0x90,0x00},
   { 21,0x31,0x00},
   { 22,0x00,0x00},
   { 23,0x00,0x00},
   { 24,0x01,0x00},
   { 25,0x00,0x00},
   { 26,0x00,0x00},
   { 27,0x00,0x00},
   { 28,0x00,0x00},
   { 29,0x00,0x00},
   { 30,0x00,0x00},
   { 31,0x00,0xFF},
   { 32,0x00,0xFF},
   { 33,0x01,0xFF},
   { 34,0x00,0xFF},
   { 35,0x00,0xFF},
   { 36,0x90,0xFF},
   { 37,0x31,0xFF},
   { 38,0x00,0xFF},
   { 39,0x00,0xFF},
   { 40,0x01,0xFF},
   { 41,0x00,0xFF},
   { 42,0x00,0xFF},
   { 43,0x00,0x0F},
   { 44,0x00,0x00},
   { 45,0x00,0x00},
   { 46,0x00,0x00},
   { 47,0x00,0xFF},
   { 48,0x00,0xFF},
   { 49,0x01,0xFF},
   { 50,0x00,0xFF},
   { 51,0x00,0xFF},
   { 52,0x90,0xFF},
   { 53,0x31,0xFF},
   { 54,0x00,0xFF},
   { 55,0x00,0xFF},
   { 56,0x01,0xFF},
   { 57,0x00,0xFF},
   { 58,0x00,0xFF},
   { 59,0x00,0x0F},
   { 60,0x00,0x00},
   { 61,0x00,0x00},
   { 62,0x00,0x00},
   { 63,0x00,0xFF},
   { 64,0x00,0xFF},
   { 65,0x01,0xFF},
   { 66,0x00,0xFF},
   { 67,0x00,0xFF},
   { 68,0x90,0xFF},
   { 69,0x31,0xFF},
   { 70,0x00,0xFF},
   { 71,0x00,0xFF},
   { 72,0x01,0xFF},
   { 73,0x00,0xFF},
   { 74,0x00,0xFF},
   { 75,0x00,0x0F},
   { 76,0x00,0x00},
   { 77,0x00,0x00},
   { 78,0x00,0x00},
   { 79,0x00,0xFF},
   { 80,0x00,0xFF},
   { 81,0x00,0xFF},
   { 82,0x00,0xFF},
   { 83,0x00,0xFF},
   { 84,0x90,0xFF},
   { 85,0x31,0xFF},
   { 86,0x00,0xFF},
   { 87,0x00,0xFF},
   { 88,0x01,0xFF},
   { 89,0x00,0xFF},
   { 90,0x00,0xFF},
   { 91,0x00,0x0F},
   { 92,0x00,0x00},
   { 93,0x00,0x00},
   { 94,0x00,0x00},
   {255,0,0xFF}      // Установка бита страницы в значение 0.
};

См. файл Si5351A-proj\Si5351A-RevB-Regmap.h из архива [6].

4.5. Программирование Si5338

MCU и Si5338 в момент старта получают питание одновременно, при этом в спроектированной системе должны быть обеспечены входные тактовые сигналы для MCU и Si5338. MCU выполнит свою инициализацию быстрее, чем Si5338; таким образом, в firmware MCU реализована задержка, чтобы поставить на паузу выполнение кода примерно на 12 мс. После этого MCU начнет обрабатывать данные массива Reg_Store. В результате через I2C будет происходить прямая запись значений в те регистры, у которых маска данных равна 0xFF, и для других значений масок будет выполняться операция read-modify-write, что обеспечит сохранение оригинальных значений некоторых бит. Исходный код C8051F301, который выполняет эти действия, см. во врезке приложения.

[5. Что можно улучшить]

• Настройка холодного старта Si5338. Микросхема Si5338 содержит энергонезависимую память (non-volatile memory, NVM), которую можно настроить для запуска Si5338. Эта возможность позволяет использовать Si5338 для генерации тактовой частоты MCU, в результате снижается количество настроек, который должен делать MCU при своем старте. Настройки холодного старта Si5338 прописываются на заводе, для чего необходимо связаться с Silicon Labs.
• Включение питания, управляемое MCU. В этом варианте применения MCU подает команду на включение питания Si53xx, что может понадобиться в приложениях, где есть особые требования по энергопотреблению.
• Несколько частотных планов. В зависимости от целей приложения MCU может перенастраивать частоты, генерируемые Si53xx. Выбор загружаемого частотного плана может осуществляться различными способами, например путем опроса перемычек при включении питания.
• Несколько подчиненных устройств I2C (или SPI). MCU может посылать индивидуальные данные регистров на несколько подчиненных устройств, подключенных к одной последовательной шине (I2C или SPI). В случае использования I2C выбор нужного подчиненного устройства осуществляется адресацией, предусмотренной в протоколе шины I2C (slave address), что должно быть учтено в firmware микроконтроллера. Почти все микросхемы Si53xx имеют внешние выводы для установки младших бит своего адреса. Например, синтезатор Si5351 [5] имеет для этой цели вывод A0. Для микросхем, подключаемых через SPI, используется специальный сигнал выборки подчиненного устройства (slave select, SS), для которого нужны дополнительные цифровые порты ввода/вывода I/O MCU. При большом количестве внешних подчиненных устройств SPI для выборки может использоваться микросхема дешифратора адреса.

Требования к этому примеру кода:

• C8051F300DK development kit, который имеет в своем составе Silabs IDE, оценочную версию компилятора Keil и USB Debug Adapter.
• Кит Si5338-EVB, включающий ClockBuilder Desktop Software и EVB.

Рекомендуемая документация:

• C8051F30x Data Sheet
• Si5338 Data Sheet
• AN141: SMBUS Communication for Small Form Factor Device Families
• C8051F30x Development Kit User's Guide
• Si5330/34/38 Evaluation Board User's Guide

//-----------------------------------------------------------------------------
// F300_JumpStart.c
//-----------------------------------------------------------------------------
// Copyright 2010 Silicon Laboratories, Inc.
// http://www.silabs.com
//
// Описание программы:
//
// - Реализация шины SMBus под управлением прерываний
// - Определены состояния только master (нет арбитража или отслеживание
//   состояния slave)
// - Timer1 используется в качестве источника тактов SMBus
// - Timer2 используется SMBus для детектирования таймаута лог. 0 SCL
// - Частота SCL определяется константой < SMB_FREQUENCY>
// - Адрес для slave-устройства I2C определен константой SLAVE_ADDR
// - Реализована поддержка ARBLOST
// - Используемые порты (все другие ножки портов не задействованы):
// P0.0 -> SDA (SMBus)
// P0.1 -> SCL (SMBus)
//
// Целевой MCU: C8051F301
// Tool chain: Keil 7.20
//
// Release 1.0
// - Начальная версия (ACA)
// - Июнь 2009
//
// Release 1.1
// - Адрес I2C перенесен в этот файл из register_map.h
// - Этот проект работает с новой генерацией файла register_map.h
//   в утилите Multisynth Clock Programmer
//
// Release 1.2 (август 2010)
// - Добавлен тест для ситуации, когда mask = 0x00, чтобы пропустить
//   обращение к этому регистру.
// - Обновлен список масок регистров Si5338 в файле register_map.h
//
// Release 1.3 (сентябрь 2010)
// - Обновлен файл register_map.h и main(), чтобы отразить изменения
//   в даташите Si5338
//
//-----------------------------------------------------------------------------
// Подключаемые файлы
//-----------------------------------------------------------------------------
#include < compiler_defs.h>
#include < c8051F300_defs.h>  // декларации SFR
#include < register_map.h>

//-----------------------------------------------------------------------------
// Глобальные константы
//-----------------------------------------------------------------------------
// Адрес устройства (7 бит) для подчиненного устройства I2C.
// Для Si5338 адрес по умолчанию 0x70, для Si5351 адрес по умолчанию 0x60.
#define SLAVE_ADDR 0x70

#define SYSCLK 24500000       // Системная тактовая частота в Гц.
#define SMB_FREQUENCY 400000  // Скорость передачи I2C (частота тактов SCL)
                              // может быть 100 кГц или 400 кГц.

#define WRITE 0x00            // Команда SMBus WRITE
#define READ 0x01             // Команда SMBus READ

// Вектор статуса - только 4 старшие бита
#define SMB_MTSTA 0xE0  // (MT) передан start
#define SMB_MTDB  0xC0  // (MT) передан байт данных
#define SMB_MRDB  0x80  // (MR) принят байт данных
// Конец определений вектора состояния

//#define SI5338_DELAY 4800     //2 мс
//#define SI5338_DELAY 24000    //10 мс
#define SI5338_DELAY 28800    //12 мс

#define LOCK_MASK    0x15
#define LOS_MASK     0x04

//-----------------------------------------------------------------------------
// Глобальные переменные
//-----------------------------------------------------------------------------
U8* pSMB_DATA_IN;          // Глобальный указатель на данные SMBus.
                           // Сюда записываются все принятые данные.
U8 SMB_SINGLEBYTE_OUT;     // Глобальное хранилище для одного записываемого байта.
U8* pSMB_DATA_OUT;         // Глобальный указатель на данные SMBus.
                           // Отсюда берутся все передаваемые данные.
U8 SMB_DATA_LEN;           // Глобальное хранилище для количества байт для отправки
                           // или приема в текущей пересылке данных SMBus.
U8 WORD_ADDR;              // Глобальное хранилище для слова адреса, к которому
                           // будет осуществляться доступ при следующей пересылке.
U8 TARGET;                 // Адрес целевого подчиненного устройства SMBus.
volatile bit SMB_BUSY = 0; // Программный флаг, показывающий задействование
                           // функций I2C_ByteRead() или I2C_ByteWrite()
                           // шиной SMBus.
bit SMB_RW;                // Программный флаг, показывающий направление
                           // текущего перемещения данных.
bit SMB_SENDWORDADDR;      // Установка этого флага приводит к отправке
                           // в коде ISR 8-битного < WORD_ADDR> после отправки
                           // адреса подчиненного устройства.
bit SMB_RANDOMREAD;        // Установка этого флага приводит к отправке кодом ISR
                           // сигнала START после отправки слова адреса.
                           // ISR обрабатывает это изменение, если установлен
                           // бит < SMB_RANDOMREAD>.
bit SMB_ACKPOLL;           // Установка этого флага приводит к отправке кодом ISR
                           // повторного START до тех пор, пока подчиненное устройство
                           // не подтвердит свой адрес.
SBIT(SDA, SFR_P0, 0);      // SMBus на P0.0
SBIT(SCL, SFR_P0, 1);      //  и P0.1
SBIT (P0_5, SFR_P0, 5);
SBIT (P0_6, SFR_P0, 6);

//-----------------------------------------------------------------------------
// Прототипы функций
//-----------------------------------------------------------------------------
void SMBus_Init (void);
void Timer1_Init (void);
void Timer2_Init (void);
void Port_Init (void);

INTERRUPT_PROTO(SMBus_ISR, INTERRUPT_SMBUS0);
INTERRUPT_PROTO(Timer2_ISR, INTERRUPT_TIMER2);
void I2C_ByteWrite (U8 addr, U8 dat);
U8 I2C_ByteRead (U8 addr);

//-----------------------------------------------------------------------------
// Основное тело программы
//-----------------------------------------------------------------------------
void main (void)
{
   U16 counter;
   U8 curr_chip_val, clear_curr_val, clear_new_val, combined, reg;
   Reg_Data curr;
   
   U8 i;             // Переменная временного счетчика, используемая в циклах.
   PCA0MD &= ~0x40;  // WDTE = 0 (запрет таймера watchdog)
   OSCICN |= 0x03;   // Конфигурирование внутреннего генератора на его
                     // максимальную частоту (24.5 МГц).
   
   // Цикл, если подчиненное устройство удерживает SDA=0 из-за неправильного
   // сброса SMBus reset или ошибки:
   while(!SDA)
   {
      // Предоставление тактовых импульсов, чтобы позволить подчиненному
      // устройству выйти из своего текущего состояния и освободить SDA.
      XBR1 = 0x40;               // Enable Crossbar
      SCL = 0;                   // Перевести тактовый сигнал в 0
      for(i = 0; i < 255; i++);  // Удержание в нуле сигнала тактов
      SCL = 1;                   // Освобождение тактов.
      while(!SCL);               // Ожидание размыкания открытого стока тактов,
                                 // чтобы сигнал тактов перешел в лог. 1.
      for(i = 0; i < 10; i++);   // Удержание тактового сигнала не некоторое
                                 // время в лог. 1.
      XBR1 = 0x00;               // Disable Crossbar
   }
   Port_Init ();                 // Инициализация Crossbar и GPIO.
   CKCON = 0x10;                 // Timer 1 тактируется от sysclk.
                                 // Timer 2 тактируется от sysclk/12 (см. TMR2CN).
   Timer1_Init ();               // Конфигурирование Timer1 в качестве источника
                                 // тактов для SMBus.
   Timer2_Init ()                // Конфигурирование Timer2 в качестве источника
                                 // тактов отслеживания таймаута SMBus.
   SMBus_Init ();                // Конфигурирование и разрешение работы SMBus.
   EIE1 |= 0x01;                 // Разрешение прерывания SMBus.
   EA = 1;                       // Глобальное разрешение прерываний (это разрешение
                                 // прерываний должно быть последним!).
 
   //----------------------------------------------------------------
   // См. рис. 9 из даташита Si5338, для получения большей информации
   // по задержке этой процедуры, чтобы подождать готовности Si5338
   // к обмену данными после включения питания.
   counter = 0;
   while(counter < SI5338_DELAY) { counter++; }
   I2C_ByteWrite(230, 0x10);  //OEB_ALL = 1
   I2C_ByteWrite(241, 0xE5);  //DIS_LOL = 1
   // Для всех значений регистров в массиве Reg_Store будет выбрано
   // value и mask, и применено к Si5338:
   for(counter=0; counter < NUM_REGS_MAX; counter++)
   {
      curr = Reg_Store[counter];
      if(curr.Reg_Mask != 0x00)
      {
         if(curr.Reg_Mask == 0xFF)
         {
            // Выполнение транзакции записи только тогда, когда в
            // маске все единицы:
            do a write transaction only since the mask is all ones
            I2C_ByteWrite(curr.Reg_Addr, curr.Reg_Val);
         }
         else
         {
            // Иначе будет выполнена операция read-modify-write:
            curr_chip_val = I2C_ByteRead(curr.Reg_Addr);
            clear_curr_val = curr_chip_val & ~curr.Reg_Mask;
            clear_new_val = curr.Reg_Val & curr.Reg_Mask;
            combined = clear_new_val | clear_curr_val;
            I2C_ByteWrite(curr.Reg_Addr, combined);
         }
      }
   }
   // Проверка ошибки LOS для входа XTAL на IN1 и IN2 (и IN3, если
   // это необходимо) - поменяйте эту маску, если используете
   // входы на IN4, IN5, IN6:
   reg = I2C_ByteRead(218) & LOS_MASK;
   while(reg != 0)
      reg = I2C_ByteRead(218) & LOS_MASK;
   I2C_ByteWrite(49, I2C_ByteRead(49) & 0x7F);  //FCAL_OVRD_EN = 0
   I2C_ByteWrite(246, 2);                       //soft reset
   I2C_ByteWrite(241, 0x65);                    //DIS_LOL = 0
   // Ожидание готовности Si5338 после внутренней калибровки 
   // (например, вызванной soft reset):
   counter = 0;
   while(counter < SI5338_DELAY) { counter++; }
   counter = 0;
   while(counter < SI5338_DELAY) { counter++; }
   // Убедимся, что произошел захват ФАПЧ проверкой PLL_LOL и SYS_CAL:
   reg = I2C_ByteRead(218) & LOCK_MASK;
   while(reg != 0)
      reg = I2C_ByteRead(218) & LOCK_MASK;
   // Копирование значений FCAL:
   I2C_ByteWrite(45, I2C_ByteRead(235));
   I2C_ByteWrite(46, I2C_ByteRead(236));
   // Очистка бит 0 и 1 из регистра 47, и комбинирование их
   // с битами 0 и 1 из регистра 237:
   reg = (I2C_ByteRead(47) & 0xFC) | (I2C_ByteRead(237) & 3);
   I2C_ByteWrite(47, reg);
   I2C_ByteWrite(49, I2C_ByteRead(49) | 0x80);  // FCAL_OVRD_EN = 1
   I2C_ByteWrite(230, 0x00);                    // OEB_ALL = 0
 
   //------------------------------------------------------------
   // Бесконечное ожидание:
   while(1);   // Закомментируйте эту строку и раскомментируйте код ниже,
               // если используется опция выключения (power-down) MCU.
   /*
   // Выключение MCU:
   while(SMB_BUSY);  // Ожидание завершения транзакций I2C.
   EA = 0;           // Выключение прерываний.
   SMB0CF &= ~0x80;  // Выключение SMBus.
   RSTSRC = 0x00;    // Выключение источника сброса MCD.
   PCON = 0x02;      // Остановка MCU - из этого состояния его выведет только reset.
   */
}

//-----------------------------------------------------------------------------
// Подпрограммы инициализации
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// SMBus_Init
//-----------------------------------------------------------------------------
// Значение возврата: нет
// Параметры: нет
//
// Периферийного устройство SMBus конфигурируется следующим образом:
// - Разрешается SMBus.
// - Запрещается режим Slave.
// - Timer1 используется в качестве источника тактов. Максимальная частота
//   SCL будет примерно 1/3 от частоты переполнения Timer1.
// - Разрешаются расширения времени установки и удержания (setup and hold time).
// - Разрешается определение таймаута освобождения SMBus и таймаута SCL==0.
void SMBus_Init (void)
{
   SMB0CF = 0x5D;    // Переполнение Timer1 используется в качестве тактов SMBus;
                     // Запрет slave mode;
                     // Разрешение расширений времени setup & hold;
                     // Разрешение SMBus Free timeout detect;
                     // Разрешение SCL low timeout detect;
   SMB0CF |= 0x80;   // Разрешение SMBus;
}
//-----------------------------------------------------------------------------
// Timer1_Init
//-----------------------------------------------------------------------------
// Значение возврата: нет
// Параметры: нет
//
// Timer1 конфигурируется в качестве источника тактов SMBus следующим образом:
// - Timer1 в режиме 8-bit auto-reload.
// - SYSCLK / 12 в качестве источника тактов для Timer1.
// - Частота переполнения Timer1 => 3 * SMB_FREQUENCY
// - Максимальная частота тактов SCL будет примерно ~1/3 от частоты
//   переполнения Timer1.
// - Разрешается работа Timer1.
void Timer1_Init (void)
{
   // Гарантируем, что Timer1 может генерировать подходящую частоту в 8-битном режиме.
   // Поддерживаемые SMBus частоты лежат в диапазоне 10 .. 100 кГц. Могут потребоваться
   // изменения установок CKCON, для частот вне этого диапазона.
   TMOD = 0x20;                                 // Timer1 в режиме 8-bit auto-reload.
   TH1 = 0xFF - (SYSCLK/SMB_FREQUENCY/3) + 1;   // 100 кГц или 400 кГц для SCL.
   TL1 = TH1;                                   // Инициализация Timer1.
   TR1 = 1;                                     // Разрешение Timer1.
}

//-----------------------------------------------------------------------------
// Timer2_Init
//-----------------------------------------------------------------------------
// Значение возврата: нет
// Параметры: нет
//
// Timer2 конфигурируется для детектирования таймаута SCL==0 следующим образом:
// - Timer2 в режиме 16-bit auto-reload
// - SYSCLK/12 в качестве источника тактов Timer2
void Timer2_Init (void)
{
   TMR2CN = 0x00; // Timer2 конфигурируется для 16-bit auto-reload,
                  // запрет прерывания от младшего байта.
                  // Timer2 использует SYSCLK/12 (см. CKCON).
   TMR2RLL = 0x5F;
   TMR2RLH = 0x88;
   TMR2L = TMR2RLL;
   TMR2H = TMR2RLH;
   TF2LEN = 0;
   TF2H = 0;
   TF2L = 0;
   ET2 = 1;       // Разрешение прерывания Timer2.
   TR2 = 1;       // Запуск Timer2.
}

//-----------------------------------------------------------------------------
// Port_Init
//-----------------------------------------------------------------------------
// Значение возврата: нет
// Параметры: нет
//
// Конфигурирование Crossbar и портов GPIO.
//
// P0.0 цифровой порт с открытым стоком для сигнала SMBus SDA
// P0.1 цифровой порт с открытым стоком для сигнала SMBus SCL
//
// Все другие ножки портов не используются.
void Port_Init (void)
{
   P0MDOUT = 0x00;   // Все выводы P0 работают как выходы с общим стоком.
   XBR1 = 0x04;      // Разрешение SMBus.
   XBR2 = 0x40;      // Разрешение Crossbar и внутренних "слабых" верхних
                     // подтягивающих резисторов (pull-up).
   P0 = 0xFF;        // Явная установка всех выходов в лог. 1.
}

//-----------------------------------------------------------------------------
// Обработчик прерывания SMBus (Interrupt Service Routine, ISR)
//-----------------------------------------------------------------------------
// Машина состояния SMBus ISR
// - Реализован только режим Master - не определены состояния slave или арбитража.
// - Все приходящие данные записываются, начиная с глобального указателя < pSMB_DATA_IN>.
// - Все уходящие данные берутся из глобального указателя < pSMB_DATA_OUT>.
INTERRUPT(SMBus_ISR, INTERRUPT_SMBUS0)
{
   bit FAIL = 0;              // Используется ISR для ошибок при перемещении данных.
   static char i;             // Используется ISR для подсчета переданных или
                              // отправленных байт данных.
   static bit SEND_START = 0; // Отправка start.
   switch (SMB0CN & 0xF0)     // Анализ вектора состояния
   {
   case SMB_MTSTA:
      // Master Transmitter/Receiver: передан сигнал START.
      SMB0DAT = TARGET;       // Загрузка адреса подчиненного устройства.
      SMB0DAT &= 0xFE;        // Очистка LSB в адресе для бита R/W.
      SMB0DAT |= SMB_RW;      // Загрузка бита R/W.
      STA = 0;                // Ручная очистка бита START.
      i = 0;                  // Сброс счетчика байт данных.
      break;
   case SMB_MTDB:
      // Master Transmitter: передан байт данных (или адрес Slave).
      if (ACK) // Slave Address или Data Byte
      {  // Было подтверждение?
         if (SEND_START)
         {
            STA = 1;
            SEND_START = 0;
            break;
         }
         if(SMB_SENDWORDADDR) // Мы отправляем адрес слова?
         {
            SMB_SENDWORDADDR = 0;   // Очистка флага
            SMB0DAT = WORD_ADDR;    // Отправка слова адреса
            if (SMB_RANDOMREAD)
            {
               SEND_START = 1;      // Отправка START после следующего цикла ACK
               SMB_RW = READ;
            }
            break;
         }
         if (SMB_RW==WRITE) // WRITE?
         {
            if (i < SMB_DATA_LEN) // Есть данные для отправки?
            {
               // отправка байта данных
               SMB0DAT = *pSMB_DATA_OUT;
               // инкремент указателя на уходящие данные
               pSMB_DATA_OUT++;
               // инкремент количества отправленных данных
               i++;
            }
            else
            {
               STO = 1;       // Установка STO, чтобы остановить передачу
               SMB_BUSY = 0;  // Очистка флага программной занятости
            }
         }
         else {}  // Если это READ, то не предпринимается
                  // никаких действий. Был передан адрес Slave.
                  // Определена отдельная ветка case для
                  // принятого байта.
      }
      else // Подчиненное устройство ответило NACK
      {
         if(SMB_ACKPOLL)
         {
            STA = 1;    // Перезапуск перемещения данных
         }
         else
         {
            FAIL = 1;   // Показать ошибку перемещения данных
         }              // для обработки в конце ISR.
      }
      break;
   case SMB_MRDB:
      // Master Receiver: принят байт
      if ( i < SMB_DATA_LEN )       // Нужно еще принимать данные?
      {
         *pSMB_DATA_IN = SMB0DAT;   // Сохранение принятого байта.
         pSMB_DATA_IN++;            // Инкремент указателя на принимаемые данные.
         i++;                       // Инкремент количества принятых байт.
         ACK = 1;                   // Установка бита ACK (может быть позже
                                    // очищен в этом коде).
      }
      if (i == SMB_DATA_LEN)        // Это последний байт
      {
         ACK = 0;                   // Отправка NACK, чтобы показать последний байт
                                    // этой передачи.
         STO = 1;                   // Отправка STOP для завершение передачи.
         SMB_BUSY = 0;              // Освобождение интерфейса SMBus.
      }
      break;
   default:
      FAIL = 1;                     // Показать ошибку, чтобы обработать это
                                    // в конце ISR.
      break;
   }
   if (FAIL)   // Если перемещение потерпело ошибку,
   {
      SMB0CF &= ~0x80;              // Сброс обмена.
      SMB0CF |= 0x80;
      STA = 0;
      STO = 0;
      ACK = 0;
      FAIL = 0;
      SMB_BUSY = 0;                 // Освобождение SMBus.
   }
   SI = 0;  // Очистка флага прерываний.
}

//-----------------------------------------------------------------------------
// Обработчик прерываний Timer2 (Interrupt Service Routine, ISR)
//-----------------------------------------------------------------------------
// Прерывание Timer2 показывает таймаут SMBus события SCL==0.
// Если произошел этот таймаут, то SMBus запрещается и разрешается заново.
INTERRUPT(Timer2_ISR, INTERRUPT_TIMER2)
{
   SMB0CF &= ~0x80;     // Запрет SMBus.
   SMB0CF |= 0x80;      // Повторное разрешение SMBus.
   TF2H = 0;            // Очистка флага ожидания обработки прерывания Timer2.
   SMB_BUSY = 0;        // Освобождение шины.
}

//-----------------------------------------------------------------------------
// Функции поддержки
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// I2C_ByteWrite
//-----------------------------------------------------------------------------
// Значение возврата: нет
// Параметры:
// 1) unsigned char addr - адрес для записи в устройство через I2C
//    (может быть в диапазоне 0 .. 255).
//
// 2) unsigned char dat - данные для записи в устройство по адресу < addr>
//    (может быть в диапазоне 0 .. 255).
//
// Эта функция записывает значение dat в устройство по адресу addr,
// после чего опрашивает устройство, пока запись не завершится.
void I2C_ByteWrite (U8 addr, U8 dat)
{
   while (SMB_BUSY);    // Ожидание освобождения SMBus.
   SMB_BUSY = 1;        // Занятие SMBus (установка состояния занятости)
   // Установка параметров SMBus для ISR
   TARGET = SLAVE_ADDR << 1;  // Установка целевого адреса подчиненного устройства
   SMB_RW = WRITE;            // Пометка следующей транзакции как записи.
   SMB_SENDWORDADDR = 1;      // Отправка Word Address после Slave Address.
   SMB_RANDOMREAD = 0;        // Не отправлять сигнал START после Word Address.
   SMB_ACKPOLL = 1;           // Разрешение опроса подтверждения (ISR будет
                              // автоматически перезапускать транзакцию, если
                              // подчиненное устройство не подтвердило свой адрес).
   // Указание исходящих данных
   WORD_ADDR = addr;          // Установка целевого адреса во внутреннем
                              // пространстве памяти устройства.
   SMB_SINGLEBYTE_OUT = dat;  // Сохранение dat (локальная переменная) в глобальную
                              // переменную, чтобы ISR смог прочитать её после того,
                              // как произойдет выход из этой функции.
   // Указатель на уходящие данные указывает на переменную dat
   pSMB_DATA_OUT = &SMB_SINGLEBYTE_OUT;
   SMB_DATA_LEN = 1;          // Указать для ISR, что следующая передача
                              // будет содержать 1 байт данных.
   // Инициирование SMBus Transfer
   STA = 1;
}

//-----------------------------------------------------------------------------
// I2C_ByteRead
//-----------------------------------------------------------------------------
// Возвращаемое значение:
// 1) unsigned char data - данные, прочитанные по адресу addr в устройстве.
//
 // Параметры:
// 1) unsigned char addr - адрес для чтения данных из устройства.
//    Может быть в диапазоне 0 .. 255.
//
// Эта функция вернет 1 байт из внутренней памяти устройства по адресу addr,
// когда флаг < SMB_BUSY> покажет завершения чтения.
U8 I2C_ByteRead (U8 addr)
{
   U8 return_val;             // Переменная для хранения возвращаемого значения.
   while (SMB_BUSY);          // Ожидание освобождения SMBus.
   SMB_BUSY = 1;              // Занятие SMBus (установка состояния занятости).
   // Установка параметров SMBus для ISR
   TARGET = SLAVE_ADDR << 1;  // Установка целевого адреса подчиненного устройства
   SMB_RW = WRITE;            // Чтение по произвольному адресу начинается как запись,
                              // затем меняется на чтение после отправки повторного
                              // start. ISR обрабатывает это переключение, если
                              // установлен бит < SMB_RANDOMREAD>.
   SMB_SENDWORDADDR = 1;      // Отправка Word Address после Slave Address.
   SMB_RANDOMREAD = 1;        // Отправка START после Word Address.
   SMB_ACKPOLL = 1;           // Разрешение опроса подтверждения.
   // Указание приходящих данных
   WORD_ADDR = addr;          // Установка целевого адреса во внутренней памяти
                              // устройства.
   pSMB_DATA_IN = &return_val;// Указатель приходящих данных указывает на переменную
                              // return_val.
   SMB_DATA_LEN = 1;          // Указывает ISR, что следующее перемещение данных
                              // будет содержать 1 байт данных.
   // Инициация SMBus Transfer
   STA = 1;
   while(SMB_BUSY);           // Ожидание завершения чтения данных.
   return return_val;
}

[Ссылки]

1. AN428 site:silabs.com.
2. AN136: Production Programming Options for Silicon Labs Devices site:silabs.com.
3. Parametric Search site:silabs.com.
4. Si5338 site:silabs.com.
5. Si5351: программируемый генератор на любую частоту.
6. 180524an428.ZIP - документация, дистрибутив ClockBuilder Pro 2.23, пример заголовочного файла для программирования Si5351A (проект ClockBuilder Pro).