STM8 + SDCC + SPL: стандартная периферийная библиотека

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

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

Это нерадостные мысли посетили меня после ознакомления с системой таймеров в STM8. Она довольно мощная, на мой взгляд. И тут я вспомнил про стандартную периферийную библиотеку (SPL), о которой упоминал еще в первом посте про STM8, почти год назад. Там весь код для работы с периферией уже написан, проверен и отлажен.

Короче говоря, я решил переписать/адаптировать ее под SDCC в процессе своего изучения STM8. Начать решил с модуля GPIO, и дальше добавлять модуль за модулем.

Напомню, что SPL для STM8S и для STM8L это разные библиотеки, синтаксис у них различается, хотя написаны они в одном стиле.

Итак, GPIO.

Библиотеки состоят из главного заголовочного файла stm8s.h и stm8l15x.h, где приписаны адреса регистров, основные структуры и константы.

На этом, начальном, этапе stm8s.h у меня получился довольно скромным:

#ifndef __STM8S_H
#define __STM8S_H

typedef unsigned char     uint8_t;
typedef unsigned short    uint16_t;
typedef unsigned long     uint32_t;

#define     __IO    volatile         /*!< defines 'read / write' permissions  */

typedef enum {FALSE = 0, TRUE = !FALSE} bool;

typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus, BitStatus, BitAction;

typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
#define IS_FUNCTIONALSTATE_OK(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE))

typedef struct GPIO_struct
{
  __IO uint8_t ODR; /*!< Output Data Register */
  __IO uint8_t IDR; /*!< Input Data Register */
  __IO uint8_t DDR; /*!< Data Direction Register */
  __IO uint8_t CR1; /*!< Configuration Register 1 */
  __IO uint8_t CR2; /*!< Configuration Register 2 */
}
GPIO_TypeDef;

#define GPIO_ODR_RESET_VALUE ((uint8_t)0x00)
#define GPIO_DDR_RESET_VALUE ((uint8_t)0x00)
#define GPIO_CR1_RESET_VALUE ((uint8_t)0x00)
#define GPIO_CR2_RESET_VALUE ((uint8_t)0x00)

/******************************************************************************/
/*                          Peripherals Base Address                          */
/******************************************************************************/

#define GPIOA_BaseAddress       0x5000
#define GPIOB_BaseAddress       0x5005
#define GPIOC_BaseAddress       0x500A
#define GPIOD_BaseAddress       0x500F
#define GPIOE_BaseAddress       0x5014
#define GPIOF_BaseAddress       0x5019
#define GPIOG_BaseAddress       0x501E
#define GPIOH_BaseAddress       0x5023
#define GPIOI_BaseAddress       0x5028

/******************************************************************************/
/*                          Peripherals declarations                          */
/******************************************************************************/
#define GPIOA ((GPIO_TypeDef *) GPIOA_BaseAddress)

#define GPIOB ((GPIO_TypeDef *) GPIOB_BaseAddress)

#define GPIOC ((GPIO_TypeDef *) GPIOC_BaseAddress)

#define GPIOD ((GPIO_TypeDef *) GPIOD_BaseAddress)

#define GPIOE ((GPIO_TypeDef *) GPIOE_BaseAddress)

#define GPIOF ((GPIO_TypeDef *) GPIOF_BaseAddress)

#if defined(STM8S207) || defined (STM8S007) || defined(STM8S208) || defined(STM8S105) || \
    defined(STM8S005) || defined (STM8AF52Ax) || defined (STM8AF62Ax) || defined (STM8AF626x)
 #define GPIOG ((GPIO_TypeDef *) GPIOG_BaseAddress)
#endif /* (STM8S208) ||(STM8S207)  || (STM8S105) || (STM8AF52Ax) || (STM8AF62Ax) || (STM8AF626x) */

#if defined(STM8S207) || defined (STM8S007) || defined(STM8S208) || defined (STM8AF52Ax) || \
    defined (STM8AF62Ax)
 #define GPIOH ((GPIO_TypeDef *) GPIOH_BaseAddress)
 #define GPIOI ((GPIO_TypeDef *) GPIOI_BaseAddress)
#endif /* (STM8S208) ||(STM8S207) || (STM8AF62Ax) || (STM8AF52Ax) */


#endif

Модуль GPIO удалось перенести практически без изменений. Он состоит из заголовочного файла stm8s_gpio.h:

#ifndef __STM8S_GPIO_H
#define __STM8S_GPIO_H

#include "stm8s.h"

typedef enum
{
  GPIO_MODE_IN_FL_NO_IT      = (uint8_t)0x00,  /*!< Input floating, no external interrupt */
  GPIO_MODE_IN_PU_NO_IT      = (uint8_t)0x40,  /*!< Input pull-up, no external interrupt */
  GPIO_MODE_IN_FL_IT         = (uint8_t)0x20,  /*!< Input floating, external interrupt */
  GPIO_MODE_IN_PU_IT         = (uint8_t)0x60,  /*!< Input pull-up, external interrupt */
  GPIO_MODE_OUT_OD_LOW_FAST  = (uint8_t)0xA0,  /*!< Output open-drain, low level, 10MHz */
  GPIO_MODE_OUT_PP_LOW_FAST  = (uint8_t)0xE0,  /*!< Output push-pull, low level, 10MHz */
  GPIO_MODE_OUT_OD_LOW_SLOW  = (uint8_t)0x80,  /*!< Output open-drain, low level, 2MHz */
  GPIO_MODE_OUT_PP_LOW_SLOW  = (uint8_t)0xC0,  /*!< Output push-pull, low level, 2MHz */
  GPIO_MODE_OUT_OD_HIZ_FAST  = (uint8_t)0xB0,  /*!< Output open-drain, high-impedance level,10MHz */
  GPIO_MODE_OUT_PP_HIGH_FAST = (uint8_t)0xF0,  /*!< Output push-pull, high level, 10MHz */
  GPIO_MODE_OUT_OD_HIZ_SLOW  = (uint8_t)0x90,  /*!< Output open-drain, high-impedance level, 2MHz */
  GPIO_MODE_OUT_PP_HIGH_SLOW = (uint8_t)0xD0   /*!< Output push-pull, high level, 2MHz */
}GPIO_Mode_TypeDef;

typedef enum
{
  GPIO_PIN_0    = ((uint8_t)0x01),  /*!< Pin 0 selected */
  GPIO_PIN_1    = ((uint8_t)0x02),  /*!< Pin 1 selected */
  GPIO_PIN_2    = ((uint8_t)0x04),  /*!< Pin 2 selected */
  GPIO_PIN_3    = ((uint8_t)0x08),   /*!< Pin 3 selected */
  GPIO_PIN_4    = ((uint8_t)0x10),  /*!< Pin 4 selected */
  GPIO_PIN_5    = ((uint8_t)0x20),  /*!< Pin 5 selected */
  GPIO_PIN_6    = ((uint8_t)0x40),  /*!< Pin 6 selected */
  GPIO_PIN_7    = ((uint8_t)0x80),  /*!< Pin 7 selected */
  GPIO_PIN_LNIB = ((uint8_t)0x0F),  /*!< Low nibble pins selected */
  GPIO_PIN_HNIB = ((uint8_t)0xF0),  /*!< High nibble pins selected */
  GPIO_PIN_ALL  = ((uint8_t)0xFF)   /*!< All pins selected */
}GPIO_Pin_TypeDef;


extern void GPIO_DeInit(GPIO_TypeDef* GPIOx);
extern void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin, GPIO_Mode_TypeDef GPIO_Mode);
extern void GPIO_Write(GPIO_TypeDef* GPIOx, uint8_t PortVal);

extern void GPIO_WriteHigh(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins);
extern void GPIO_WriteLow(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins);
extern void GPIO_WriteReverse(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins);

uint8_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
BitStatus GPIO_ReadInputPin(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin);
void GPIO_ExternalPullUpConfig(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin, FunctionalState NewState);

#endif

и Си-файла c реализацей объявленных функций:

#include "stm8s.h"
#include "stm8s_gpio.h"

void GPIO_DeInit(GPIO_TypeDef* GPIOx)
{
    GPIOx->ODR = GPIO_ODR_RESET_VALUE; /* Reset Output Data Register */
    GPIOx->DDR = GPIO_DDR_RESET_VALUE; /* Reset Data Direction Register */
    GPIOx->CR1 = GPIO_CR1_RESET_VALUE; /* Reset Control Register 1 */
    GPIOx->CR2 = GPIO_CR2_RESET_VALUE; /* Reset Control Register 2 */
}

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin, GPIO_Mode_TypeDef GPIO_Mode)
{
    /*----------------------*/
    /* Check the parameters */
    /*----------------------*/

//    assert_param(IS_GPIO_MODE_OK(GPIO_Mode));
//    assert_param(IS_GPIO_PIN_OK(GPIO_Pin));

  /* Reset corresponding bit to GPIO_Pin in CR2 register */
  GPIOx->CR2 &= (uint8_t)(~(GPIO_Pin));

    /*-----------------------------*/
    /* Input/Output mode selection */
    /*-----------------------------*/

    if ((((uint8_t)(GPIO_Mode)) & (uint8_t)0x80) != (uint8_t)0x00) /* Output mode */
    {
        if ((((uint8_t)(GPIO_Mode)) & (uint8_t)0x10) != (uint8_t)0x00) /* High level */
        {
            GPIOx->ODR |= (uint8_t)GPIO_Pin;
        }
        else /* Low level */
        {
            GPIOx->ODR &= (uint8_t)(~(GPIO_Pin));
        }
        /* Set Output mode */
        GPIOx->DDR |= (uint8_t)GPIO_Pin;
    }
    else /* Input mode */
    {
        /* Set Input mode */
        GPIOx->DDR &= (uint8_t)(~(GPIO_Pin));
    }

    /*------------------------------------------------------------------------*/
    /* Pull-Up/Float (Input) or Push-Pull/Open-Drain (Output) modes selection */
    /*------------------------------------------------------------------------*/

    if ((((uint8_t)(GPIO_Mode)) & (uint8_t)0x40) != (uint8_t)0x00) /* Pull-Up or Push-Pull */
    {
        GPIOx->CR1 |= (uint8_t)GPIO_Pin;
    }
    else /* Float or Open-Drain */
    {
        GPIOx->CR1 &= (uint8_t)(~(GPIO_Pin));
    }

    /*-----------------------------------------------------*/
    /* Interrupt (Input) or Slope (Output) modes selection */
    /*-----------------------------------------------------*/

    if ((((uint8_t)(GPIO_Mode)) & (uint8_t)0x20) != (uint8_t)0x00) /* Interrupt or Slow slope */
    {
        GPIOx->CR2 |= (uint8_t)GPIO_Pin;
    }
    else /* No external interrupt or No slope control */
    {
        GPIOx->CR2 &= (uint8_t)(~(GPIO_Pin));
    }
}

void GPIO_Write(GPIO_TypeDef* GPIOx, uint8_t PortVal)
{
    GPIOx->ODR = PortVal;
}

void GPIO_WriteHigh(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins)
{
    GPIOx->ODR |= (uint8_t)PortPins;
}

void GPIO_WriteLow(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins)
{
    GPIOx->ODR &= (uint8_t)(~PortPins);
}

void GPIO_WriteReverse(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef PortPins)
{
    GPIOx->ODR ^= (uint8_t)PortPins;
}

uint8_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
{
    return ((uint8_t)GPIOx->ODR);
}

uint8_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
    return ((uint8_t)GPIOx->IDR);
}

BitStatus GPIO_ReadInputPin(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin)
{
    return ((BitStatus)(GPIOx->IDR & (uint8_t)GPIO_Pin));
}

void GPIO_ExternalPullUpConfig(GPIO_TypeDef* GPIOx, GPIO_Pin_TypeDef GPIO_Pin, FunctionalState NewState)
{
    /* Check the parameters */
//    assert_param(IS_GPIO_PIN_OK(GPIO_Pin));
//    assert_param(IS_FUNCTIONALSTATE_OK(NewState));

    if (NewState != DISABLE) /* External Pull-Up Set*/
    {
        GPIOx->CR1 |= (uint8_t)GPIO_Pin;
    } else /* External Pull-Up Reset*/
    {
        GPIOx->CR1 &= (uint8_t)(~(GPIO_Pin));
    }
}

Здесь я лишь закоментировал метафизику: "assert_param", а все остальное перенеслось без изменения.

В архиве библиотеки есть пример Blink написанный с помощью SPL. Я пошел немного дальше и переписал программу индикации нажатой кнопки на основе опроса порта(Polling):

#include "stm8s.h"
#include "stm8s_gpio.h"

#define LED_PORT GPIOB
#define LED GPIO_PIN_5

#define BTN_LED_PORT GPIOC
#define BTN_LED  GPIO_PIN_4

#define BTN_PORT GPIOD
#define BTN GPIO_PIN_2

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

uint8_t i;
int main( void )
{

	GPIO_DeInit(LED_PORT);
	GPIO_Init(LED_PORT, LED, GPIO_MODE_OUT_PP_LOW_FAST);

	GPIO_DeInit(BTN_LED_PORT);
	GPIO_Init(BTN_LED_PORT, BTN_LED, GPIO_MODE_OUT_PP_LOW_FAST);

	GPIO_DeInit(BTN_PORT);
	GPIO_Init(BTN_PORT, BTN, GPIO_MODE_IN_FL_NO_IT); // Input floating, no external interrupt

	i=0;
	for(;;)
  	{
		if(GPIO_ReadInputPin(BTN_PORT, BTN))
			GPIO_WriteHigh(BTN_LED_PORT, BTN_LED);
		else
			GPIO_WriteLow(BTN_LED_PORT, BTN_LED);

    		delay(1000); i++;

		if (i > 50)
		{
			GPIO_WriteReverse(LED_PORT, LED);
			i=0;
		}
  	}
}
структура проекта вышла какой-то такой:
.
├── examples
│   ├── blink
│   │   ├── Makefile
│   │   └── main.c
│   └── button
│       ├── Makefile
│       └── main.c
├── hex
│   ├── blink.ihx
│   └── button.ihx
└── libs
    ├── stm8s.h
    ├── stm8s_gpio.c
    └── stm8s_gpio.h

5 directories, 9 files

Makefile

MCU=stm8
DEVICE=stm8s103
FLASHER=stlinkv2
CFLAGS=-I ../../libs -DSTM8S103 -c
CC=sdcc
LIB=stm8s_gpio.rel
OBJ=main.rel
TARGET=button

.PHONY: all clean

%.rel:	%.c
	$(CC) -m$(MCU)  $(CFLAGS) $(LDFLAGS) $<

%.rel:	../../libs/%.c
	$(CC) -m$(MCU)  $(CFLAGS) $(LDFLAGS) $<

all:	$(OBJ) $(LIB)
	$(CC) -m$(MCU) -o $(TARGET).ihx $(OBJ) $(LIB) 

download:
	stm8flash -c $(FLASHER) -p $(DEVICE) -w $(TARGET).ihx
clean:
	@rm -v *.sym *.asm *.lst *.rel *.ihx *lk *.rst *.cdb *.map	

процесс сборки:

$ make all
sdcc -mstm8  -I ../../libs -DSTM8S103 -c  main.c
sdcc -mstm8  -I ../../libs -DSTM8S103 -c  ../../libs/stm8s_gpio.c
sdcc -mstm8 -o button.ihx main.rel stm8s_gpio.rel 

Повторюсь, что для L-серии синтаксис в названии конcтант и функций несколько различается.

Та же программа для L - серии выглядит уже так:

#include "stm8l.h"
#include "stm8l_gpio.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--);
}

uint8_t i;
int main( void )
{

	GPIO_DeInit(PORT);

	GPIO_Init(PORT, LED|BTN_LED, GPIO_Mode_Out_PP_Low_Fast);

	GPIO_Init(PORT, BTN, GPIO_Mode_In_FL_No_IT); // Input floating, no external interrupt

	i=0;
	for(;;)
  	{
		if(GPIO_ReadInputDataBit(PORT, BTN))
			GPIO_SetBits(PORT, BTN_LED);
		else
			GPIO_ResetBits(PORT, BTN_LED);

    		delay(1000); i++;

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

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

поделиться: