Работа прерываний TIMER16

RUMBUFDSI

New member
Здравствуйте!
Не могу понять, почему не работают прерывания TIMER16, код прикрепляю снизу.
Инициализация тактирования шин, GPIO, таймера и включение прерываний по сути взяты из примеров.
Предполагаю, что ошибка где-то в инициализации таймера, но не могу понять где.
Использую отладочную плату с программатором (https://tellur-el.ru/catalog/razrabotka_i_konstruirovanie_1/otladochnye_platy_1/323392/), программирую в Visual Studio с использованием PlatformIO, код прошивается в RAM-память. Правильно ли я понимаю, что все прерывания обрабатываются в trap_handler и для вылавливания какого-то конкретного нужно прописать проверку флага? По моей логике, здесь я пытаюсь хотя бы войти в обработчик прерываний и подёргать светодиод на GPIO_PIN_9.
Код:
[QUOTE]
#include "mik32_hal.h"
#include "mik32_hal_pcc.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.h"
#include "mik32_hal_gpio.h"

void CLK_Init(void);
void TIMER16_Init(void);
void GPIO_Init(void);

Timer16_HandleTypeDef htimer16_1;

int main ()
{
    HAL_Init();
    CLK_Init();
    GPIO_Init();
    TIMER16_Init();
   
    HAL_EPIC_MaskEdgeSet(HAL_EPIC_TIMER16_1_MASK);
    HAL_IRQ_EnableInterrupts();
    HAL_Timer16_Counter_Start_IT(&htimer16_1, (uint16_t)0x61A8);

    while(1)
    {
        asm("nop");
    }
}

void CLK_Init(void)
{
PCC_InitTypeDef PCC_ClkInit = {0};
PCC_ClkInit.OscillatorEnable = PCC_OSCILLATORTYPE_OSC32M;
PCC_ClkInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
PCC_ClkInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
PCC_ClkInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
PCC_ClkInit.AHBDivider = 0;     
PCC_ClkInit.APBPDivider = 0;                                   
PCC_ClkInit.APBMDivider = 0;                                     
PCC_ClkInit.HSI32MCalibrationValue = 0;
PCC_ClkInit.LSI32KCalibrationValue = 0;
PCC_ClkInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
PCC_ClkInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;
HAL_PCC_Config(&PCC_ClkInit);
}

void TIMER16_Init(void)
{
    htimer16_1.Clock.Source = TIMER16_SOURCE_INTERNAL_AHB;
    htimer16_1.CountMode = TIMER16_COUNTMODE_INTERNAL;
    htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_128;
    htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING;
   
    htimer16_1.Preload = TIMER16_PRELOAD_ENDPERIOD;
    htimer16_1.Filter.ExternalClock = TIMER16_FILTER_NONE;
    htimer16_1.Filter.Trigger = TIMER16_FILTER_NONE;
    htimer16_1.EncoderMode = TIMER16_ENCODER_DISABLE;
    htimer16_1.Instance->IER = TIMER16_IER_ARRMIE_M;
    htimer16_1.Instance->CNT = 0;
    HAL_Timer16_Init(&htimer16_1);
}

void GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_Init_Struct = {0};
    __HAL_PCC_GPIO_0_CLK_ENABLE();
    GPIO_Init_Struct.Pin = GPIO_PIN_9;
    GPIO_Init_Struct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
    GPIO_Init_Struct.Pull = HAL_GPIO_PULL_DOWN;
    HAL_GPIO_Init(GPIO_0, &GPIO_Init_Struct);
}

void trap_handler()
{
    HAL_GPIO_TogglePin(GPIO_0, GPIO_PIN_9);
    HAL_Timer16_ClearInterruptMask(&htimer16_1, 0xFFFFFFFF);
    HAL_EPIC_Clear(0xFFFFFFFF);
}
[/QUOTE]

Кстати, подскажите, пожалуйста, за что отвечают значения Calibration value для HSI32M и LSI32K? Не увидел описания этого в документации.
 
Последнее редактирование:

cryptozoy

Member
За что отвечают значения Calibration value для HSI32M и LSI32K?

«Техническое описание К1948ВК018 RISC-V микроконтроллер MIK32 АМУР» с неофициальными правками

Раздел «4 Подсистема тактирования»:

Для подстройки встроенного осциллятора HSI32M используются поправочные коэффициенты с допустимым значением от 0 до 255.
Для подстройки встроенного осциллятора LSI32K используются поправочные коэффициенты с допустимым значением от 0 до 255.

В таблице 159 «Регистры и поля управления тактированием модуля WU» это поле ADJ_HSI32M (биты 2~9) регистра CLOCKS_SYS и поле ADJ_LSI32K (биты 2~9) регистра CLOCKS_BU.
 
Последнее редактирование:

RUMBUFDSI

New member
«Техническое описание К1948ВК018 RISC-V микроконтроллер MIK32 АМУР» с неофициальными правками

Раздел «4 Подсистема тактирования»:

Для подстройки встроенного осциллятора HSI32M используются поправочные коэффициенты с допустимым значением от 0 до 255.
Для подстройки встроенного осциллятора LSI32K используются поправочные коэффициенты с допустимым значением от 0 до 255.

В таблице 159 «Регистры и поля управления тактированием модуля WU» это поле ADJ_HSI32M (биты 2~9) регистра CLOCKS_SYS и поле ADJ_LSI32K (биты 2~9) регистра CLOCKS_BU.
Да я немного не про то)
Эту инфу я и сам нашёл. Непонятно, что даёт изменение этих значений.
 

cryptozoy

Member
Да я немного не про то)
Эту инфу я и сам нашёл. Непонятно, что даёт изменение этих значений.
Изменение этих значений в небольших пределах меняет частоту внутренних тактовых генераторов. Это может понадобиться когда МК работает без внешнего кварцевого резонатора (у которого очень низкая температурная зависимость), и при этом ему необходима наиболее точная частота тактирования, ведь частота внутреннего генератора сильно зависит от температуры. Программа может регулярно измерять температуру и вносить поправки частоты в соответствии с таблицей параметров.
 
Последнее редактирование:

ejsanyo

Active member
Здесь похоже дело не в таймере, а в этом самом trap_handler(), до которого выполнение тупо не доходит. Кажется, он зависает в runtime/crt0.S вот тут
Код:
// Default handler: infinit loop
// (weak symbol here - may be redefined)
trap_handler:
1:  j       1b
А вот что нужно такое станцевать, чтобы использовался нашtrap_handler(), а не этот, я пока что не вкурил.
 

ejsanyo

Active member
Потому что вектор прерывания в этом МК один единственный.
Да я это всё понимаю, только вот trap_handler() в main.c, похоже, не подшивался на этот вектор для перехода, а висел как просто какая-то функция.
А вот чтобы он таки подшился, я, покопавшись в примерах поглубже, сделал вот что:
В main.c в самом начале, ещё до main(), прописал так
C:
extern unsigned long __TEXT_START__; //это "метка" для обработчика прерываний?!

void trap_handler() //сам обработчик всех прерываний
{
.
.
.
.
}
Ну а в main() в самом-самом начале прописал
C:
//это настраивает вектор прерываний?!
write_csr(mtvec, &__TEXT_START__);
А теперь, вопрос знатокам низкоуровневого программирования: что же я на самом деле такое написал, что после этого всё заработало?! 😲 Нет, в общих чертах я догадываюсь, что оно делает. Но как именно?
 

cryptozoy

Member
C:
extern unsigned long __TEXT_START__;
Метка того места в программе, где написано её объявление. Настроена для использования также извне программы, в которой определена. "extern" это "external", а значит внешний тип.
C:
void trap_handler() { /* Пользовательская программа обработки прерываний */ }
Функция (подпрограмма), вызываемая внутри другой программы, которая выполняется по единственному вектору прерывания. Её привязка и параметры находятся в файле crt0.S, который является заголовочным. Написан на ассемблере и в нём определено всё крайне необходимое для осуществления запуска вашей программы, для входа и выхода из обработчика прерывания:
Код:
#define EXCEPTION_STACK_SPACE 32*4
#define EXCEPTION_SAVED_REGISTERS 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

.globl _start, main
.weak SmallSystemInit, SystemInit

.globl trap_entry
.globl trap_handler
.globl raw_trap_handler
.weak trap_handler, raw_trap_handler

.altmacro
.macro memcpy src_beg, src_end, dst, tmp_reg
    LOCAL memcpy_1, memcpy_2
    j    memcpy_2
memcpy_1:
    lw   \tmp_reg, (\src_beg)
    sw   \tmp_reg, (\dst)
    add  \src_beg, \src_beg, 4
    add  \dst, \dst, 4
memcpy_2:
    bltu \src_beg, \src_end, memcpy_1
.endm

.macro memset dst_beg, dst_end, val_reg
    LOCAL memset_1, memset_2
    j    memset_2
memset_1:
    sw   \val_reg, (\dst_beg)
    add  \dst_beg, \dst_beg, 4
memset_2:
    bltu \dst_beg, \dst_end, memset_1
.endm


# la uses PC relative addressing (auipc instruction)
# Explicit absolut addressing with lui instruction is used
# to allow startup code to be executed from any PC address
# (la instruction equivalents are left in comments)

.macro la_abs reg, address
    .option push
    .option norelax
    lui \reg, %hi(\address)
    addi \reg, \reg, %lo(\address)
    .option pop
.endm


.macro jalr_abs return_reg, address
    .option push
    .option norelax
    lui \return_reg, %hi(\address)
    jalr \return_reg, %lo(\address)(\return_reg)
    .option pop
.endm



.text

_start:

    # Init stack and global pointer
    #
    la_abs  sp, __C_STACK_TOP__
    la_abs  gp, _gp

    # Init data
    #
    la_abs  a1, __DATA_IMAGE_START__
    la_abs  a2, __DATA_IMAGE_END__
    la_abs  a3, __DATA_START__
    memcpy  a1, a2, a3, t0

    # Clear bss
    #
    la_abs  a1, __BSS_START__
    la_abs  a2, __BSS_END__
    memset a1, a2, zero

    #ifdef MIK32V0
    # Enable pad_config clocking
    #
    li t0, (1 << 3)
    li t1, 0x50014
    sw t0, (t1)
    #endif

    jalr_abs ra, SmallSystemInit
    jalr_abs ra, SystemInit
    jalr_abs ra, main
1:  wfi
    j 1b


// Actions before main: none by default
// (weak symbol here - may be redefined)
SmallSystemInit:
SystemInit:
    ret

.section .trap_text, "ax"
// .org should be consistent with
// default mtvec value (set in scr1_arch_description.svh)
//.org 0xC0
trap_entry:
    j raw_trap_handler

raw_trap_handler:
    // Save registers
    addi    sp, sp, -(EXCEPTION_STACK_SPACE)
    .irp index, EXCEPTION_SAVED_REGISTERS
        sw      x\index, 4*index(sp)
    .endr

    // Call handler
    la      ra, trap_handler
    jalr    ra

    // restore registers
    .irp index, EXCEPTION_SAVED_REGISTERS
        lw      x\index, 4*index(sp)
    .endr
    addi    sp, sp, EXCEPTION_STACK_SPACE
    mret

// Default handler: infinit loop
// (weak symbol here - may be redefined)
trap_handler:
1:  j       1b

C:
write_csr(mtvec, &__TEXT_START__);
Записывает в регистр процессора адрес того места в программе, куда будут осуществляться переходы по запросу прерывания [программы], когда оно будет происходить от источников прерывания, разрешённых с помощью соответствующих регистров данного микроконтроллера.

Попробуйте написать второй обработчик прерываний, также поставив метку перед ним, а затем по ходу работы программы меняйте один обработчик на другой, записывая в регистр адреса меток. Скорее всего придётся определить оба обработчика прерывания не только в файле crt0.S, но и в файлах линковщика, смотря куда заливаешь свою программу: ram.ld, eeprom.ld, spifi.ld. Будет интересно узнать о результатах. Ждём. )))

P.S.: Я не сотрудник техподдержки и могу ошибаться. Во всём.
 
Последнее редактирование:

cryptozoy

Member
О разборе подобного нашему файла crt0.S рассказывается в разделе "Стоп! Hammertime! Runtime!" перевод статьи "RISC-V с нуля" на Хабре.
 
Последнее редактирование:

ejsanyo

Active member
Попробуйте написать второй обработчик прерываний, также поставив метку перед ним, а затем по ходу работы программы меняйте один обработчик на другой, записывая в регистр адреса меток. Скорее всего придётся определить оба обработчика прерывания не только в файле crt0.S, но и в файлах линковщика, смотря куда заливаешь свою программу: ram.ld, eeprom.ld, spifi.ld. Будет интересно узнать о результатах. Ждём. )))
Нет, так не прокатывает. В смысле ссылаться экстерном на совсем несуществующие переменные. Компилятор ругается. А вот этот самый "__TEXT_START__", смотрю, хоть в исходниках в явном виде не присутствует, но в map и elf файлах появляется. И даже фигурирует в скриптах прошивки в память. Странно это всё...
 

RUMBUFDSI

New member
В смысле не компилируется, или просто не работает?
Не работает.
Дебуггером смотрели, точно в обработчик не заходит?
Дебаг в PlatformIO работает как-то странно, я пока не разобрался что там да как.
Включение светодиода в обработчик прерываний засунул - он не загорается) Делаю вывод, что в обработчик не заходит.
 

ejsanyo

Active member
Что-то я, кстати, в вашем коде не вижу __HAL_PCC_TIMER16_1_CLK_ENABLE(); А это надо бы сделать ещё до настройки собственно регистров таймера.
 

ejsanyo

Active member
Ещё такой момент: насколько я понял, trap_handler() должен быть по тексту прям в самом-самом начале main.c, ещё до всех остальных функций. А перед ним __TEXT_START__. Вот рабочий (по крайней мере, у меня рабочий) пример.
 

RUMBUFDSI

New member

RUMBUFDSI

New member
@ejsanyo, спасибо за пример. Убрал из него всё, что не касается timer16_1 и gpio для светодиодов - всё заработало. Осталось разобраться, почему мой вариант кода (в целом идентичный вашему) не работает. Спасибо еще раз!
ps - про идентичность - это не относится к коду в первом сообщении, я его уже 1000 раз переписал хехех)
 
Сверху