Pull to refresh

Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Настройка тактовой частоты

Reading time 8 min
Views 38K

Общее представление о системе тактирования


В прошлой статье мы научились создавать стабильные задержки с помощью простого таймера SysTick, а так же немного окунулись в механизм работы прерываний. Тогда мы принимали как постулат то, что тактируя таймер от источника HCLK – мы получаем 8 Мгц. Теперь настало время разобраться, откуда эти цифры.

Открыв документация и сделав переход к «Сигналы тактовой частоты MDR_RST_CLK» мы можем увидеть такую таблицу.

Встроенный RC генератор HSI
Генератор HSI вырабатывает тактовую частоту 8 МГц. Генератор автоматически запускается при появлении питания UСС.

Таким образом, после включения, контроллер тактируется от HSI. На блок схеме синим я выделил последовательность, которая демонстрирует изменения частоты на каждом блоке. По умолчанию модуль выбора источника частоты (MUX) настроен на прием с HSI. С частотой ничего не происходит и она, через HCLK (линия тактирования, совмещенная в том числе и с SysTick таймером) попадает в CPU_CLK без изменений. Но внутренний RS генератор нельзя назвать точным устройством. Его частота очень нестабильна. Для этих целей на плате установлен внешний кварцевый резонатор на 8 Мгц. Но как он называется?
Внешний генератор HSE
Генератор HSE предназначен для выработки тактовой частоты 2..16 МГц с помощью внешнего резонатора. Генератор запускается при появлении питания UCC…

Теперь нам нужно понять, как переключить контроллер с тактирования от HSI на HSE. Еще раз взглянем на блок схему. Красным цветом я обозначил логическое представление того, как будет проходить сигнал от внешнего кварцевого резонатора до линии тактирования ядра и HCLK.


Настройка регистров тактирования.

Теперь, когда мы имеем образное представление того, как «движется» тактирование, пора разобраться с регистрами тактирования. На схеме рядом с каждым блоком указаны биты определенных регистров, меняя которые можно менять «движение» и частоту тактирования. Данные биты находятся в регистре MDR_RST_CLK->CPU_CLOCK. Красным я обозначил те биты, которые нужно будет изменить. Синие же можно оставить так, как есть. Они уже установлены в правильном положении.

Далее нам нужно написать define-ы тех бит, значения которых мы будем менять.
#define HCLK_SEL(CPU_C3)       (1<<8)
#define CPU_C1_SEL(HSE)        (1<<1)

Так как все остальные биты равны нулю, то мы можем писать в регистр напрямую, не боясь стереть старые данные. В итоге выходит следующее.
RST_CLK->CPU_CLOCK  = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);

Казалось бы все. Но если мы зашьем это, то получим вот это.

С последующей невозможностью отладки. Я сразу же полез в документацию с описаниями ошибок. Но такой там не было. Как оказалось, я упустил одну важную специфическую деталь. В описании HSE было сказано:
Генератор HSE предназначен для выработки тактовой частоты 2..16 МГц с помощью внешнего резонатора. Генератор запускается при появлении питания UCC и сигнала разрешения HSEON в регистре HS_CONTROL.

Заглянем в этот регистр.

Пропишем еще 1 define и включим бит разрешения.
#define HS_CONTROL(HSE_ON)     (1<<0)
RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON);

Небольшое отступление.
Когда искал ошибку, снова вернулся на страницу тактирования периферии (рассматривали в предыдущей статье про SysTick), в которой нашел RST_CLK. По умолчанию он включен, но все таки я прописал его включение перед всеми манипуляциями с кварцевым резонатором.

После всех дополнений функция имела следующий вид.
#define HCLK_SEL(CPU_C3)       (1<<8)
#define CPU_C1_SEL(HSE)        (1<<1)
#define PCLK_EN(RST_CLK)       (1<<4)
#define HS_CONTROL(HSE_ON)     (1<<0)

void HSE_Init (void)
{
	RST_CLK->PER_CLOK |= PCLK_EN(RST_CLK);                                                //Включаем тактирование контроллера тактовой частоты (по умолчанию включено).
	RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON)                                              //Разрешаем использование HSE генератора.
	RST_CLK->CPU_CLOCK  = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);                               //Настраиваем "путь" сигнала и включаем тактирование от HSE генератора.
}

Первый «костыль».


В тот момент, когда я искал ошибку запуска HSE в перечне ошибок, я наткнулся на следующий глюк.

Я не мог не запомнить его, так как планировал сразу же после включение тактирования от внешнего кварцевого резонатора выключить внутренний генератор. Данная ошибка присутствует во всех ревизиях микроконтроллера. Так что для нее придется лепить костыль. Скажу сразу. В моем случае мне так и не удалось отключить HSI. Несмотря на то, что я делал все так, как сказано в рекомендациях.
Изучим проблему и пути «решения» поподробнее.

Вот тот упомянутые регистры.


Пропишем define для нужных бит, а так же для тактирования RTC.
#define REG_0F(HSI_ON)        ~(1<<22)
#define RTC_CS(ALRF)           (1<<2)
#define PCLK(BKP)              (1<<27)

А теперь сделаем попытку осуществить это на практике.
#define HCLK_SEL(CPU_C3)       (1<<8)
#define CPU_C1_SEL(HSE)        (1<<1)
#define PCLK_EN(RST_CLK)       (1<<4)
#define HS_CONTROL(HSE_ON)     (1<<0)

#define REG_0F(HSI_ON)        ~(1<<22)
#define RTC_CS(ALRF)           (1<<2)
#define PCLK(BKP)              (1<<27)
#define CPU_C2_SEL(CPU_C2_SEL) (1<<2)

void HSE_Init (void)
{
	RST_CLK->PER_CLOK |= PCLK_EN(RST_CLK);                                                //Включаем тактирование контроллера тактовой частоты (по умолчанию включено).
	RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON)                                              //Разрешаем использование HSE генератора.
	RST_CLK->CPU_CLOCK  = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);                               //Настраиваем "путь" сигнала и включаем тактирование от HSE генератора.
	RST_CLK->PER_CLOK |= PCLK(BKP);                                                       //Включаем тактирование часов (для костыля).
	BKP->RTC_CS |= RTC_CS(ALRF);                                                          //Костыль для отключения HSI.
	BKP->REG_0F  = BKP->REG_0F&(REG_0F(HSI_ON));                                          //Отключаем HSI.
}

Отслеживаем выполнения программы. Это состояние регистров до попытки сбросить бит.

А это после.

По началу я подумал, что ошибся со смещением и просо не включил бит ALRF, но потом нажал на него в меню справа (прямое оправление регистрами) и ничего не получил. Подумал, что не правильно выбрал тактирование, но нажав на несколько других ячеек – получил отклик. Видимо, данный метод не решает эту проблему. При попытке отключить генератор путем последовательного нажатия ALRF и HSI_ON — мы окончательно разочаровываемся в самой идеи отключения генератора. Что ж. Это не сильно мешает на начальном этапе. Но в будущем это станет проблематично. Особенно если делать карманное устройство.

Получаем 16 Мгц.

Нам удалось заставить наш контроллер тактироваться от внешнего кварцевого резонатора, что дало нам возможность получить более точные временные задержки. Теперь настало время научиться пользоваться умножителем частоты. В описании нашего контроллера сказано, что он может тактироваться с частотой вплоть до 80 Мгц. Попробуем написать функцию, которая позволит нам увеличить частоту тактирования в 2 раза. Снова взглянем на блок схему.

Теперь, перед тем, как по линии CPU_C1 частота «попадает» в CPU_C2, она проходит через CPU PLL. Там она «умножается» на какое-то значение. Рассмотрим его регистры поподробнее.

PLL не требует включения бита тактирования. Так что можно сразу приступать к настройке регистра. Опять же пропишем нужные нам define-ы.
#define PLL_CONTROL_PLL_CPU_ON  (1<<2)                   //PLL включена.


Ну саму функцию включения.
#define PLL_CONTROL_PLL_CPU_ON  (1<<2)                                                  //PLL включена. 
void HSE_16Mhz_Init (void)                                                              //Сюда передаем частоту в разах "в 2 раза" например. 
{
	RST_CLK->PLL_CONTROL  = PLL_CONTROL_PLL_CPU_ON|(1<<8);                                //Включаем PLL, умножение в 2 раза.
  RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON);                                             //Разрешаем использование HSE генератора. 
	RST_CLK->CPU_CLOCK  = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3)|CPU_C2_SEL(CPU_C2_SEL) ;       //Настраиваем "путь частоты" и включаем тактирование от HSE генератора.
}

Теперь мы можем добавить функцию в проект.
int main (void)
{
	Init_SysTick();                                 //Инициализируем системный таймер. 
	Led_init();                                     //Инициализируем ножку 0 порта C для светодиода. 
	
	PORTC->RXTX |= 1;
	Delay_ms (1000);
	PORTC->RXTX = 0;
	Delay_ms (1000);
	
	HSE_16Mhz_Init();
	
  while (1)
	{
		PORTC->RXTX |= 1;
		Delay_ms (1000);
		PORTC->RXTX = 0;
	  Delay_ms (1000);
	}
}

Хочу заметить, что я не просто так оставил 1 цикл мелькания светодиода. Это так называемая «программа спасения». Если в ходе экспериментов что-то пойдет не так, то после нажатия RESET будет целая секунда, чтобы прошить МК исправленной прошивкой.
После прошивки светодиод 2 раза мелькнет от внутреннего кварца, а потом в 2 раза быстрее от внешнего.

Теперь оптимизируем наш код. Использовать функцию ради 3-х строк – глупо. Тем более 2 из них повторяются и во второй функции. Поэтому предлагаю сделать их define-ами. Первая функция предстает перед нами в таком виде.
#define RST_CLK_ON_Clock()       RST_CLK->PER_CLOCK |= PCLK_EN(RST_CLK)                 //Включаем тактирование контроллера тактовой частоты (по умолчанию включено).
#define HSE_Clock_ON()           RST_CLK->HS_CONTROL = HS_CONTROL(HSE_ON)               //Разрешаем использование HSE генератора. 
#define HSE_Clock_OffPLL()       RST_CLK->CPU_CLOCK  = CPU_C1_SEL(HSE)|HCLK_SEL(CPU_C3);//Настраиваем "путь" сигнала и включаем тактирование от HSE генератора.

А из второй предлагаю сделать универсальную функцию переключения частоты. Для этого из предыдущей функции выкидываем настройку HSE и добавляем в регистр PLL_CONTROL еще бит перезапуска PLL. Чтобы при получении нового значения сразу начать тактироваться на нем. Функция начинает иметь следующий вид.
#define PLL_CONTROL_PLL_CPU_ON  (1<<2)                                                  //PLL включена. 
#define PLL_CONTROL_PLL_CPU_PLD (1<<3)                                                  //Бит перезапуска PLL.
void HSE_PLL (uint8_t PLL_multiply)                                                              //Сюда передаем частоту в разах "в 2 раза" например. 
{
	RST_CLK->PLL_CONTROL  = RST_CLK->PLL_CONTROL&(~(0xF<<8));                                      //Удаляем старое значение.
	RST_CLK->PLL_CONTROL |= PLL_CONTROL_PLL_CPU_ON|((PLL_multiply-1)<<8)|PLL_CONTROL_PLL_CPU_PLD;  //Включаем PLL и включаем умножение в X раз, а так же перезапускаем PLL.
	RST_CLK->CPU_CLOCK   |= HCLK_SEL(CPU_C3)|CPU_C2_SEL(CPU_C2_SEL)|CPU_C1_SEL(HSE);               //Настриваем "маршрут" частоты через PLL и включаем тактирование от HSE.
}

Внесем ее в нашу основную программу. Которую тоже приводим в порядок.
void Block (void)                                //Подпрограмма ожидания (защиты).
{
	PORTC->RXTX |= 1;
	Delay_ms (1000);
	PORTC->RXTX = 0;
	Delay_ms (1000);
}

int main (void)
{
	Init_SysTick();                                 //Инициализируем системный таймер. 
	Led_init();                                     //Инициализируем ножку 0 порта C для светодиода. 
	Block();                                        //Подпрограмма ожидания (защиты).
	HSE_Clock_ON();                                 //Разрешаем использование HSE генератора. 
	HSE_PLL(2);                                     //Включаем тактирование с умножением 2

  while (1)
	{
		PORTC->RXTX |= 1;
		Delay_ms (1000);
		PORTC->RXTX = 0;
	        Delay_ms (1000);
	}
}

В качестве заключения, напишем программу, которая после каждого цикла будет менять коэффициент умножения увеличивая свою скорость вплоть до 10, получая 80 Мгц.
void Block (void)                                //Подпрограмма ожидания (защиты).
{
	PORTC->RXTX |= 1;
	Delay_ms (1000);
	PORTC->RXTX = 0;
	Delay_ms (1000);
}

int main (void)
{
	Init_SysTick();                                 //Инициализируем системный таймер. 
	Led_init();                                     //Инициализируем ножку 0 порта C для светодиода. 
	Block();                                        //Подпрограмма ожидания (защиты).
	HSE_Clock_ON();                                 //Разрешаем использование HSE генератора. 
	HSE_PLL(2);                                     //Включаем тактирование с умножением 2
	
	uint8_t PLL_Data = 1;                           //Здесь храним коэффициент умножения. 
        while (1)
	{
		PORTC->RXTX |= 1;
		Delay_ms (1000);
		PORTC->RXTX = 0;
	        Delay_ms (1000);
		if (PLL_Data<10) PLL_Data++; else PLL_Data=1; //Если не перешли максимум - умножаем еще. Перешли - с начала.
                HSE_PLL(PLL_Data); 
	}

Видео работы программы.


Восстановление платы.


После первого неудачного опыта с настройкой тактовой частоты, плата перестала отвечать. Так как я не предусмотрел защитную программу (посчитав ее не нужной), я начал искать пути реализации. В комментариях к одной из предыдущих статей vertu77 предложил 3 способа восстановления платы в случае неправильной настройки портов.
Если все-таки JTAG порт умер — что делать:
1. Тогда может пригодится другой JTAG порт. Если он не разведен на плате, можно припаяться только к ножкам SW на другом порту (меньше возни)
2. Залить прошивку через UART (штатный загрузчик)
3. Использовать особенности зашитой программы — успеть прошить новую заливку после подачи питания до первого мигания. В варианте автора это практически невозможно. В реальных программах часто программа запускается от внутреннего генератора, затем инициализируется внешний кварц. Если при этом используется бесконечный цикл ожидания — можно отпаять кварц и прошиться до первого мигания.

Но в моем случае оба JTAG-а не реагировали (завсали при попытке программирования МК), защитной программы не было, а USART загрузчик был очень далеко. Да и не хотелось тратить слишком много времени на восстановление. Так был придуман четвертый способ восстановления. Нужно переключить BOOT переключатели в режим EXT_ROM/JTAG_B, подключиться к JTAG_B и зашить код с прошивкой, в которой предусмотрена защитная программа. В моем случае я просто добавил цикл ожидания в одну секунду перед настройкой кварцевого резонатора. Так что после каждого неудачного опыта достаточно было нажать на RESET и успеть войти в режим отладки заново.


Проект на github.
Tags:
Hubs:
+7
Comments 7
Comments Comments 7

Articles