STM32 – лучше поздно, чем никогда! #3 (подключаем Si5351A)

Автор: | 27.08.2023

Пробуем подключить генератор Si5351. Взял всем известную платку модуля Si5351 от Adafruit и немного модернизировал. Так как питание STM32 осуществляется напряжением +3,3v то на платке модуля нам не нужен преобразователь уровней с +5v до +3,3v на шине i2c. Я, беспощадно, феном “сдул” два подтягивающих резистора номиналом 10k по входу платки модуля линий SCL и SDA и два транзистора BSS138, вместо транзисторов установил перемычки, чтобы использовать родные PLS пины на платке модуля

К “синей таблетке” модуль подключил по следующей схеме

единственное на схеме не указано что пин +5v “синей таблетки” заведен на пин VIN платки модуля Si5351.

Далее изменил конфигурацию пинов STM32 для включения модуля i2c

Портировал код управления Si5351 с AVR в текущий проект, в проекте на AVR у меня создана простая библиотека управления сишкой, ниже листинги файлов si5351a.h и si5351a.с

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* si5351а.h
*
* Author: R9OFG (ex R0AEK)
* Version - 2.2 beta
*
* info@r9ofg.ru
* https://r9ofg.ru
*/


#ifndef SI5351A_H
#define SI5351A_H

//#include <inttypes.h>

/*установки значений регистров*/
#define SI_CLK0_CONTROL 16
#define SI_CLK1_CONTROL 17
#define SI_CLK2_CONTROL 18
#define SI_SYNTH_PLL_A 26
//#define SI_SYNTH_PLL_B 34
#define SI_SYNTH_MS_0 42
//#define SI_SYNTH_MS_1 50
//#define SI_SYNTH_MS_2 58
#define SI_PLL_RESET 177

/*установки R делителя*/
#define SI_R_DIV_1 0x00
#define SI_R_DIV_2 0x10
#define SI_R_DIV_4 0x20
#define SI_R_DIV_8 0x30
#define SI_R_DIV_16 0x40
#define SI_R_DIV_32 0x50
#define SI_R_DIV_64 0x60
#define SI_R_DIV_128 0x70

#define SI_CLK_SRC_PLL_A 0x00
#define FREQ_PLLA 600000000
#define ACTUAL_MULTIPLIER 1048575

void si5351aOutputOff();
void si5351aSetFrequency(uint32_t frequency);

#endif /* SI5351A_H */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
* si5351а.с
*
* Author: R9OFG (ex R0AEK)
*
* base on code from Hans Summers, 2015
* Website: http://www.hanssummers.com
* A very very simple Si5351a demonstration
* using the Si5351a module kit http://www.hanssummers.com/synth
* Please also refer to SiLabs AN619 which describes all the registers to use
*
* Version - 2.2 beta
*
* info@r9ofg.ru
* https://r9ofg.ru
*/


#include "Main.h"
#include "si5351a.h"

#define SI5351A_I2C_ADDRESS 0x60
#define I2C_HANDLE hi2c1

extern I2C_HandleTypeDef I2C_HANDLE;

uint32_t xtall_FREQ = 25000000; //частота в Гц используемого кварцевого резонатора для тактирвоания Si5351A
uint8_t si_DSC; //значение тока драйвера выхода Si5351A - 0/1/2/3

/*
записываем 8-битное значение регистра SI5351A по шине I2C
*/

void si5351_write_data(uint8_t reg, uint8_t value) {
while (HAL_I2C_IsDeviceReady(&I2C_HANDLE, (uint16_t)(SI5351A_I2C_ADDRESS<<1), 3, HAL_MAX_DELAY) != HAL_OK) { }

HAL_I2C_Mem_Write(&I2C_HANDLE, //i2c указатель на структуру, в которой хранится информация о модуле I2C
(uint8_t)(SI5351A_I2C_ADDRESS<<1), //i2c адрес, выравнивание по левому краю
(uint8_t)reg, //адрес регистра
I2C_MEMADD_SIZE_8BIT, //si5351 используем 8-битные адреса регистров
(uint8_t*)(&value), //записываем возвращенные данные в эту переменную
1, //сколько байтов ожидать возврата
HAL_MAX_DELAY); //тайм-аут
}

/*
настройки для PLL с mult, num и denom
mult в диапазоне 15..90
num в диапазоне 0..1,048,575 (0xFFFFF)
denom в диапазоне 0..1,048,575 (0xFFFFF)
*/

void setupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
{
uint32_t P1; // PLL конфигурационный регистр P1
uint32_t P2; // PLL конфигурационный регистр P2
uint32_t P3; // PLL конфигурационный регистр P3

P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;

//передаем в регистры si5351a
si5351_write_data(pll + 0, (P3 & 0x0000FF00) >> 8);
si5351_write_data(pll + 1, (P3 & 0x000000FF));
si5351_write_data(pll + 2, (P1 & 0x00030000) >> 16);
si5351_write_data(pll + 3, (P1 & 0x0000FF00) >> 8);
si5351_write_data(pll + 4, (P1 & 0x000000FF));
si5351_write_data(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
si5351_write_data(pll + 6, (P2 & 0x0000FF00) >> 8);
si5351_write_data(pll + 7, (P2 & 0x000000FF));
}

/*
установка MultiSynth с целочисленным делителем и R-делителем
R-делитель - это битовое значение, которое помещается в соответствующий регистр, см. #define в si5351a.h
*/

void setupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
{
uint32_t P1;
uint32_t P2;
uint32_t P3;

P1 = 128 * divider - 512;
// P2 = 0, P3 = 1 форсирует целочисленное значение для делителя
P2 = 0;
P3 = 1;

//передаем в регистры si5351a
si5351_write_data(synth + 0, (P3 & 0x0000FF00) >> 8);
si5351_write_data(synth + 1, (P3 & 0x000000FF));
si5351_write_data(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv);
si5351_write_data(synth + 3, (P1 & 0x0000FF00) >> 8);
si5351_write_data(synth + 4, (P1 & 0x000000FF));
si5351_write_data(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
si5351_write_data(synth + 6, (P2 & 0x0000FF00) >> 8);
si5351_write_data(synth + 7, (P2 & 0x000000FF));
}

/*
в AN619описаны значения значения битов - 0x80 отключает выходной каскад
*/

void si5351aOutputOff()
{
//выключаем ВСЕ выходы CLK0, CLK1 и CLK2
si5351_write_data(SI_CLK0_CONTROL, 0x80);
si5351_write_data(SI_CLK1_CONTROL, 0x80);
si5351_write_data(SI_CLK2_CONTROL, 0x80);
}

/*
установка тока драйвера CLK0
*/

void set_drvSt_si5351(int value)
{
switch (value)
{
case 0: //сила тока драйвера 2mA
si5351_write_data(SI_CLK0_CONTROL, 0x4C | SI_CLK_SRC_PLL_A);
break;
case 1: //сила тока драйвера 4mA
si5351_write_data(SI_CLK0_CONTROL, 0x4D | SI_CLK_SRC_PLL_A);
break;
case 2: //сила тока драйвера 6mA
si5351_write_data(SI_CLK0_CONTROL, 0x4E | SI_CLK_SRC_PLL_A);
break;
case 3: //сила тока драйвера 8mA
si5351_write_data(SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);
break;
}
}

/*
включаем выход CLK0 и генерируем указанную частоту
в этом примере используется PLLA
задаваемая частота должна находится в диапазоне от 1 МГц до 150 МГц
пример использования: si5351aSetFrequency(10000000);
включит выход CLK0 с генерируемой частотой 10 МГц
*/

void si5351aSetFrequency(uint32_t frequency)
{
uint32_t pllFreq;
uint32_t l;
float f;
uint8_t mult = 0;
uint32_t num = 0;
uint32_t denom;
uint32_t divider;

/*расчет коэффициента деления. 900,000,000 Hz - максимальная частота для PLLA*/
divider = FREQ_PLLA / frequency;

/*целочисленное деление*/
if (divider % 2) divider--;

/*расчет частоты pllFreq: делитель * желаемую выходную частоту*/
pllFreq = divider * frequency;

/*расчет множителя для получения заданной pllFreq
состоит из трех частей:
1: mult - целое число, которое должно быть в диапазоне 15..90
2: num и denom - дробные части, числитель и знаменатель
каждый из них 20 bits (диапазон 0..1048575)
3: фактический множитель mult + num / denom
для простоты мы установили знаменатель на максимум 1048575 (ACTUAL_MULTIPLIER)*/


mult = pllFreq / xtall_FREQ;
l = pllFreq % xtall_FREQ;
f = l;
f *= ACTUAL_MULTIPLIER;
f /= xtall_FREQ;
num = f;
denom = ACTUAL_MULTIPLIER;

/*настройка PLLA с рассчитанным коэффициентом умножения*/
setupPLL(SI_SYNTH_PLL_A, mult, num, denom);

/*установка делителя MultiSynth 0 с рассчитанным делителем
последний этап деления R может делиться на степень 2, начиная с 1..128.
представлен константами SI_R_DIV1....SI_R_DIV128 (см. заголовочный файл si5351a.h)
если вы хотите вывести частоты ниже 1 МГц, вы должны использовать SI_R_DIV128*/

setupMultisynth(SI_SYNTH_MS_0, divider, SI_R_DIV_1);

/*сброс PLL, это вызывает щелчки при перестройке, для незначительных изменений в
параметрах, НЕ ИСПОЛЬЗУЕМ сброс PLL, рекомендуется использовать только при значительном изменении частоты, например смена диапазона*/

//i2cSendRegister(SI_PLL_RESET, 0xA0);

/*включение выхода CLK0, параметр - выбор силы тока драйвера
и установка для входа MultiSynth0 значения PLLA*/

set_drvSt_si5351(si_DSC);
}

Изменил файл main.c, добавил указатели на переменные из библиотеки управления сишкой

и собственно вызов функции настройки сишки

т.е. сначала отдаем команду на выключение всех выходов сишки, затем выбираем значение тока драйвера выхода сишки и в завершении отдаем команду на включение выхода CLK0 с генерацией частоты равной 15МГц. Далее менять частоту на выходе сишки можно вызывая одну функцию

si5351aSetFrequency(частота в Гц);

Вот как стенд выглядит

и результат измерений осой

оса показывает частоту на выходе сишки равную 15,00542 МГц, дело в том, что используемые кварцевые резонаторы на платках модуля Si5351 не отличаются точностью номинальной частоты, отсюда и ошибка в калькуляции для установки регистров Si5351, лечится это просто – указывается реальная частота кварцевого резонатора, в проекте синтезатора я использовал следующий метод коррекции выходной частоты – в режиме настройки синтезатора валкодером, на выходе, устанавливается частота по нулям со сравниваемой и записывается новое значение частоты кварцевого резонатора в EEPROM, таким образом при включении синтезатора считывается откорректированное значение частоты кварцевого резонатора и расчет идет уже с верными входными данными, в результате уход частоты синтезатора в диапазоне 1…30МГц составляет не более +/-5 Гц на краях всего диапазона!

Продолжение следует…

73!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *