STM8+SDCC+SPL: Option bytes и прерывание высшего уровня(TLI)

разделы: STM8 , дата: 14 июля 2016г.

Ковыряясь в исходниках стандартной периферийной библиотеки(далее SPL), я с досадой обнаружил, что совершенно проморгал такую интересную штуку как немаскируемое прерыване высшего уровня(Top Level Interrupt aka TLI), которое относится к группе внешних прерываний.

Однако, к еще большей досаде, вскоре обнаружилось, что в stm8s103f3p6 доступ к этому прерыванию закрыт такой штукой, как "option bytes". Эта что-то вроде фьюзов AVR, благдоря которым, было залочено немалое количество AVR чипов(мною лично в том числе).

Воспользовавшись функциями из модуля FLASH, я вставил четыре команды перед главным циклом основной программы, cкомпилировал, прошил, и... благополучно залочил чип.

Проблема решилась достаточно быстро, как оказалось, в STM8 все делается иначе чем в AVR. Вообщем, этот пост о том, "как перестать бояться, и начать использовать option bytes в STM8".

Однако, для начала предлагаю вернуться к внешним перыванияи. Они в SPL реализованы в модуле EXTI, и представлены незамысловатым набором функций:

void EXTI_DeInit(void);
void EXTI_SetExtIntSensitivity(EXTI_Port_TypeDef Port, EXTI_Sensitivity_TypeDef SensitivityValue);
void EXTI_SetTLISensitivity(EXTI_TLISensitivity_TypeDef SensitivityValue);
EXTI_Sensitivity_TypeDef EXTI_GetExtIntSensitivity(EXTI_Port_TypeDef Port);
EXTI_TLISensitivity_TypeDef EXTI_GetTLISensitivity(void);

Для проверки того, что все работает как надо, соберем на макетке простую схему из одной кнопки и платы stm8s103f3p6. Кнопка в свободном состоянии через резистор будет подключена к массе, а в нажатом состоянии к шине питания. Т.о. на B5 у нас будет светодиод, а на D2 кнопка.

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

#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_clk.h"
#include "stm8s_exti.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define BTN_PORT GPIOD
#define BTN GPIO_PIN_2

static void delay(uint32_t t)
{
    while(t--);
}

uint8_t i, press;


INTERRUPT_HANDLER(IRQ_Handler_EXTI_PORT_D, 6)
{

    delay(100);
}

int main( void )
{

    // ----------- GPIO CONFIG -------------------
    GPIO_DeInit(LED_PORT);
    GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_PORT);
    GPIO_Init(BTN_PORT, BTN, GPIO_MODE_IN_FL_IT); // Input floating, with interrupt


    // ----------- EXTI CONFIG ---------------------
    EXTI_DeInit();
    EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_LOW);

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

        CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // set 16 MHz for CPU

    enableInterrupts();

    i=0; press=0;
    for(;;)
    {
            delay(1000); i++;

        if (i > 50)
        {
            GPIO_WriteReverse(LED_PORT, LED);
            i=0;

        }
    }
}

В такой программе, светодиод мигать не будет, пока не нажмешь кнопку. Т.к. условие срабатывания внешнего прерывания выставлено появление нуля в порту ввода(FALL_LOW), а не нажатая кнопка подтянутая к земле, как раз выдает логческий ноль. И т.к. приоритет любого прерывания всегда выше основной программы, внешнее прерывание будет выполнятся в бесконечном цикле, блокируя основной цикл в main().

Остюда вытекает интересный вопрос: "как нажать вторую кнопку при уже нажатой первой?"

Можно выстоить приоритеты прерываний, а можно поступить более радикально и воспользоваться Top Level Interrupt, немаскируемым прерыванием которое всегда будет иметь высший приоритет перед всеми остальными.

В STM8 TLI выведен на пин D7, за исключением микроконтроллеров в 20-пиновом корпусе, у которых TLI доступно на C3 после ремапа через "option bytes".

Из datasheet видно, что в качестве альтенативной функции, на C3 можно задействовать или TLI (AFR3) или инвертируюший канал первого таймера(AFR7).

Однако, чтобы использовать TLI, нужно сначала сделать ремап в option bytes.

Глядя на таблицу, не трудно догадаться, для включения AFR3 следует по адресу 0x4803 записать 0x08, а по адресу 0x4804 инверсное значение предыдущего байта, т.е. 0xF7.

Выгдядит все просто однако, есть подводные камни. Для начала расскажу как делать НЕ надо.

Вначале я воспользовался функциями SPL из модуля работы c флеш-памятью "FLASH" и перед главным циклом вставил следующий код:

        FLASH_Unlock (FLASH_MEMTYPE_DATA);

        FLASH_ProgramOptionByte(0x4803, 0x08);
        FLASH_ProgramOptionByte(0x4804, 0xF7);

        FLASH_Lock (FLASH_MEMTYPE_DATA);

После загрузки прошивки с таким кодом, микроконтроллер у меня сразу же завис. Никакая программа просто не выполнялась.

После некоторых размышлений, я обратил внмание на то тот факт, что ST-Link продолжает прошивать микроконтроллер вне зависимо от того, что тот залочился. И за одно вспомнил, что stm8flash может кроме прошивок еще и читать/записывать eeprom, где собственно option bytes и хранятся.

stm8flash -c stlinkv2 -p stm8s103  -s eeprom -r ./eeprom.bin
Determine EEPROM area
Reading 640 bytes at 0x4000... OK
Bytes received: 640

Выешеприведенная команда сбрасывает содержимое eeprom в локальный файл. Только должен предупредить, что если ваш чип еще на залоченый, то после этой команды он точно залочится. У меня по крайней мере было так. Чтобы исправить ситуацию, следует записать обратно в eeporm полученный или измененный файл:

stm8flash -c stlinkv2 -p stm8s103  -s eeprom -w ./eeprom.bin
Determine EEPROM area
Writing binary file 640 bytes at 0x4000... OK
Bytes written: 640

После чип снова начинает работать как обычно.

Продолжаем. Заглянув в содержимое полученного файла я увидел лишь нули:

hexdump -C ./eeprom.bin 
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000280

Да и адрес 0х4000 который высветил флешер не совсем тот что был в таблице. Ок пробуем по-другому:

stm8flash -c stlinkv2 -p stm8s103  -s opt -r ./opt.bin
Determine OPT area
Reading 64 bytes at 0x4800... OK
Bytes received: 64

И видим здесь такую картину:

hexdump -C ./opt.bin 
00000000  00 00 ff 08 ff 00 ff 00  ff 00 ff 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000040

Это то что надо. Видно, что первый байт записался, а на втором видимо все повисло. Дальше - просто. Берем шестнадцатеричный реадактор, меняем следующий за восьмеркой байт с ff на f7:

записываем файл обратно:

stm8flash -c stlinkv2 -p stm8s103  -s opt -w ./opt.bin
Determine OPT area
Writing binary file 64 bytes at 0x4800... OK
Bytes written: 64

Если все прошло, успешно, светодиоды вновь мигают, значит можно начинать пользоваться TLI:

#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_clk.h"
#include "stm8s_exti.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define BTN_PORT GPIOD
#define BTN GPIO_PIN_2


#define TLI_BTN_PORT GPIOC
#define TLI_BTN GPIO_PIN_3


static void delay(uint32_t t)
{
    while(t--);
}


INTERRUPT_HANDLER(IRQ_Handler_TLI, 0)
{
       GPIO_WriteReverse(LED_PORT, LED);
}

INTERRUPT_HANDLER(IRQ_Handler_EXTI_PORT_D, 6)
{

    delay(100);
}

uint8_t i;

int main( void )
{

    // ----------- GPIO CONFIG -------------------
    GPIO_DeInit(LED_PORT);
    GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

    GPIO_DeInit(BTN_PORT);
    GPIO_Init(BTN_PORT, BTN, GPIO_MODE_IN_FL_IT); // Input floating, with interrupt

    GPIO_DeInit(TLI_BTN_PORT);
    GPIO_Init(TLI_BTN_PORT, TLI_BTN, GPIO_MODE_IN_FL_IT); // Input floating, with interrupt


    // ----------- EXTI CONFIG ---------------------
    EXTI_DeInit();
    EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_LOW);

    EXTI_SetTLISensitivity(EXTI_TLISENSITIVITY_RISE_ONLY);

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();

    CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
    CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); // set 16 MHz for CPU

    enableInterrupts();

    i=0;
    for(;;)
    {
        delay(1000); i++;

        if (i > 50)
        {
            GPIO_WriteReverse(LED_PORT, LED);
            i=0;

        }
    }
}

Теперь следует добавить еще одну кнопку на C3 и нажатием на нее можно будет менять состояние светодиода через прерывание TLI которое работает "через голову" имеющегося внешнего или любого другого маскируемого прерывания.

Внешние прерывания в L-серии с использованием SPL(добавлено 25 августа 2016г.)

В L-серии нет TLI, да и рамап выводов осуществляется не через Option Bytes, а через Routing Interface. Т.о. в табличке Option Bytes нет никакого упоминаия на ремап:

Routing Interface это отдельная тема, поэтому предлагаю познакомиться с интерфейсом модуля EXTI SPL, для L-серии STM8.

Функционал модуля EXTI для работы с внешними прерываниями реализуется следующим набором функций:

void EXTI_DeInit(void);
void EXTI_SetPinSensitivity(EXTI_Pin_TypeDef EXTI_Pin, EXTI_Trigger_TypeDef EXTI_Trigger);
void EXTI_SelectPort(EXTI_Port_TypeDef EXTI_Port);
void EXTI_SetHalfPortSelection(EXTI_HalfPort_TypeDef EXTI_HalfPort, FunctionalState NewState);
void EXTI_SetPortSensitivity(EXTI_Port_TypeDef EXTI_Port, EXTI_Trigger_TypeDef EXTI_Trigger);
EXTI_Trigger_TypeDef EXTI_GetPinSensitivity(EXTI_Pin_TypeDef EXTI_Pin);
EXTI_Trigger_TypeDef EXTI_GetPortSensitivity(EXTI_Port_TypeDef EXTI_Port);

/* EXTI Interrupt status management *******************************************/
ITStatus EXTI_GetITStatus(EXTI_IT_TypeDef EXTI_IT);
void EXTI_ClearITPendingBit(EXTI_IT_TypeDef EXTI_IT);

Структуры EXTI_Pin_TypeDef, EXTI_Port_TypeDef, EXTI_Trigger_TypeDef, EXTI_HalfPort_TypeDef и EXTI_IT_TypeDef имеют следущий вид:

typedef enum
{
  EXTI_Pin_0 = (uint8_t)0x00, /*!< GPIO Pin 0 */
  EXTI_Pin_1 = (uint8_t)0x02, /*!< GPIO Pin 1 */
  EXTI_Pin_2 = (uint8_t)0x04, /*!< GPIO Pin 2 */
  EXTI_Pin_3 = (uint8_t)0x06, /*!< GPIO Pin 3 */
  EXTI_Pin_4 = (uint8_t)0x10, /*!< GPIO Pin 4 */
  EXTI_Pin_5 = (uint8_t)0x12, /*!< GPIO Pin 5 */
  EXTI_Pin_6 = (uint8_t)0x14, /*!< GPIO Pin 6 */
  EXTI_Pin_7 = (uint8_t)0x16  /*!< GPIO Pin 7 */
} EXTI_Pin_TypeDef;

typedef enum
{
  EXTI_Port_B = (uint8_t)0x00, /*!< GPIO Port B */
  EXTI_Port_D = (uint8_t)0x02, /*!< GPIO Port D */
  EXTI_Port_E = (uint8_t)0x04, /*!< GPIO Port E */
  EXTI_Port_F = (uint8_t)0x06, /*!< GPIO Port F */
  EXTI_Port_G = (uint8_t)0x10, /*!< GPIO Port G */
  EXTI_Port_H = (uint8_t)0x12  /*!< GPIO Port H */
} EXTI_Port_TypeDef;

typedef enum
{
  EXTI_Trigger_Falling_Low    = (uint8_t)0x00, /*!< Interrupt on Falling edge and Low level */
  EXTI_Trigger_Rising         = (uint8_t)0x01, /*!< Interrupt on Rising edge only */
  EXTI_Trigger_Falling        = (uint8_t)0x02, /*!< Interrupt on Falling edge only */
  EXTI_Trigger_Rising_Falling = (uint8_t)0x03  /*!< Interrupt on Rising and Falling edges */
} EXTI_Trigger_TypeDef;

typedef enum
{
  EXTI_HalfPort_B_LSB  = (uint8_t)0x01, /*!< Interrupt selector PB(3:0) */
  EXTI_HalfPort_B_MSB  = (uint8_t)0x02, /*!< Interrupt selector PB(7:4) */
  EXTI_HalfPort_D_LSB  = (uint8_t)0x04, /*!< Interrupt selector PD(3:0) */
  EXTI_HalfPort_D_MSB  = (uint8_t)0x08, /*!< Interrupt selector PD(7:4) */
  EXTI_HalfPort_E_LSB  = (uint8_t)0x10, /*!< Interrupt selector PE(3:0) */
  EXTI_HalfPort_E_MSB  = (uint8_t)0x20, /*!< Interrupt selector PE(7:4) */
  EXTI_HalfPort_F_LSB  = (uint8_t)0x40, /*!< Interrupt selector PF(3:0) */
  EXTI_HalfPort_F_MSB  = (uint8_t)0x81, /*!< Interrupt selector PF(7:4) */
  EXTI_HalfPort_G_LSB  = (uint8_t)0x82, /*!< Interrupt selector PG(3:0) */
  EXTI_HalfPort_G_MSB  = (uint8_t)0x84, /*!< Interrupt selector PG(7:4) */
  EXTI_HalfPort_H_LSB  = (uint8_t)0x88, /*!< Interrupt selector PH(3:0) */
  EXTI_HalfPort_H_MSB  = (uint8_t)0x90  /*!< Interrupt selector PH(7:4) */
} EXTI_HalfPort_TypeDef;

typedef enum
{
  EXTI_IT_Pin0    = (uint16_t)0x0001, /*!< GPIO Pin pos 0 */
  EXTI_IT_Pin1    = (uint16_t)0x0002, /*!< GPIO Pin pos 1 */
  EXTI_IT_Pin2    = (uint16_t)0x0004, /*!< GPIO Pin pos 2 */
  EXTI_IT_Pin3    = (uint16_t)0x0008, /*!< GPIO Pin pos 3 */
  EXTI_IT_Pin4    = (uint16_t)0x0010, /*!< GPIO Pin pos 4 */
  EXTI_IT_Pin5    = (uint16_t)0x0020, /*!< GPIO Pin pos 5 */
  EXTI_IT_Pin6    = (uint16_t)0x0040, /*!< GPIO Pin pos 6 */
  EXTI_IT_Pin7    = (uint16_t)0x0080, /*!< GPIO Pin pos 7 */
  EXTI_IT_PortB   = (uint16_t)0x0101, /*!< GPIO Port B    */
  EXTI_IT_PortD   = (uint16_t)0x0102, /*!< GPIO Port D    */
  EXTI_IT_PortE   = (uint16_t)0x0104, /*!< GPIO Port E    */
  EXTI_IT_PortF   = (uint16_t)0x0108, /*!< GPIO Port F    */
  EXTI_IT_PortG   = (uint16_t)0x0110, /*!< GPIO Port G    */
  EXTI_IT_PortH   = (uint16_t)0x0120  /*!< GPIO Port H    */
} EXTI_IT_TypeDef;

Аналогом первой программы этого поста для 051-го чипа будет такая программа:

#include "stm8l.h"
#include "stm8l_gpio.h"
#include "stm8l_clk.h"
#include "stm8l_exti.h"

#define PORT GPIOB

#define LED   GPIO_Pin_1
#define BTN_LED GPIO_Pin_2

#define BTN   GPIO_Pin_3

static void delay(uint32_t t)
{
    while(t--);
}

INTERRUPT_HANDLER(IRQ_Handler_EXTI_PIN_3, 11){
    delay(500);
    EXTI_ClearITPendingBit(EXTI_IT_Pin3);
}

uint8_t i, press;

int main( void )
{
    // ---------   GPIO Config ---------------------------

    GPIO_DeInit(PORT);

    GPIO_Init(PORT, (GPIO_Pin_TypeDef)(LED | BTN_LED), GPIO_Mode_Out_PP_Low_Fast);

    GPIO_Init(PORT, BTN, GPIO_Mode_In_FL_IT); // Input floating, with external interrupt

    // ---------- CLK CONFIG -----------------------
    CLK_DeInit();
    CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);

    // ---------- EXTI CONFIG ----------------------
    EXTI_DeInit();
    EXTI_SetPinSensitivity(EXTI_Pin_3,EXTI_Trigger_Falling_Low);
    EXTI_SelectPort(EXTI_Port_B);

    enableInterrupts();

    i=0;
    for(;;)
    {
        delay(1000); i++;

        if (i > 50)
        {
            GPIO_ToggleBits(PORT, BTN_LED);
            i=0;
        }
    }
}

Здесь светодиод BTN_LEN подключен на B2, кнопка BTN на B3. В режиме ожидания программа протоянно крутиться в переывании, т.к. ненажатая кнопка подтянута к земле. При нажатии кнопки происходит выход из прерывания, и светодиод начинает мигать.

Скачать архив с полными исходниками можно здесь: скачать

поделиться: