Работа с STM32F10x Standard Peripherals Library

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

Как это выглядит
Если вы пыталисьи курить STM32, то наверняка смотрели чужие исходники. И наверняка часто видели в них шнягу вида:
 

1
2
3
4
5
6
7
8
9
  /* GPIOD Periph clock enable */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
 
  /* Configure PD0 and PD2 in output pushpull mode */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
 
  GPIO_Init(GPIOD, &GPIO_InitStructure);

 
Вот это и есть пример работы с STM32 Standard Peripherals Firmware Library, а точнее конфигурация вывода контроллера посредством стандартных функций этой библиотеки.
 

Где брать?
Качать с сайта STM, да много где оно валяется. Сама библиотека весит 12 метров и содержит как саму либу, так и примеры к ней, а также много документации.
 

Как прикрутить к проекту
Рассказывать буду на примере Keil, а вообще, думаю везде прокатит. Итак, в архиве нас ждет следующий фарш:
 

  • _htmresc\ — картинка для фанатов STM и CMSIS :)
  • Libraries\ — Сама либа STM-SPL и CMSIS в придачу
  • Project\ — Примеры и шаблоны
  • Utilities\ — Примеры для STMEVAL
  • stm32f10x_stdperiph_lib_um.chm — Документация
  • Release_Notes.html — изменения и прочая байда

 

Берем и из Libraries\ копируем в папку своего проекта собственно каталог проекта STM32F10x_StdPeriph_Driver\ в нем, раздельно по папкам лежат *.h и *.с файлы библиотеки.
 

А из папки Project\STM32F10x_StdPeriph_Template\ тырим файл stm32f10x_conf.h и кидаем к хидерам своего проекта.
 

Осталось только прописать пути и некоторые дефайны. Лезем в настройки проекта (Alt+F7) и сразу в опции компилятора, вкладка С/С++, в строке Define вписываем USE_STDPERIPH_DRIVER.
 

Если это не сделать, то при компиляции Keil разразится ругательством вида:
 

1
2
3
4
5
STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c(111): warning:  #223-D: function "assert_param" declared implicitly
STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c(178): warning:  #223-D: function "assert_param" declared implicitly
STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c(286): warning:  #223-D: function "assert_param" declared implicitly
STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c(308): warning:  #223-D: function "assert_param" declared implicitly
STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c(324): warning:  #223-D: function "assert_param" declared implicitly

 
И так строк на 200 и нифига не скомпилирует.
 

Затем добавляем пути к нашей библиотеке.
 

 

Осталось добавить файлы в проект. В дереве проекта Keil создаем отдельную папку, например, STM32PL (можно и без нее, но тогда будет свалка). И в нее добавляем необходимые нам *.С файлы:
 


 

Пример буду показывать на пример GPIO потому подцепляем stm32f10x_gpio.c попутно из него возникнет зависимость которая потребует подключить еще и файл с функциями управления тактированием stm32f10x_rcc.c Таким же образом, туда же, подключаем файл конфигурации библиотеки: stm32f10x_conf.h Этот файл будет заинклюден автоматически через stm32f10x.h при дефайне в коде параметра USE_STDPERIPH_DRIVER
 

В конфиге раскомментируем подключение нужных либ и строку

1
#define USE_FULL_ASSERT    1

 

У меня файл stm32f10x_conf.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
/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Library configuration file.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */
 
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H
 
/* Includes ------------------------------------------------------------------*/
/* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */
//#include "stm32f10x_adc.h"
//#include "stm32f10x_bkp.h"
//#include "stm32f10x_can.h"
//#include "stm32f10x_cec.h"
//#include "stm32f10x_crc.h"
//#include "stm32f10x_dac.h"
//#include "stm32f10x_dbgmcu.h"
//#include "stm32f10x_dma.h"
//#include "stm32f10x_exti.h"
//#include "stm32f10x_flash.h"
//#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
//#include "stm32f10x_i2c.h"
//#include "stm32f10x_iwdg.h"
//#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
//#include "stm32f10x_rtc.h"
//#include "stm32f10x_sdio.h"
//#include "stm32f10x_spi.h"
//#include "stm32f10x_tim.h"
//#include "stm32f10x_usart.h"
//#include "stm32f10x_wwdg.h"
//#include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */
 
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
#define USE_FULL_ASSERT    1 */
 
/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT	     
 
/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr: If expr is false, it calls assert_failed function which reports 
  *         the name of the source file and the source line number of the call 
  *         that failed. If expr is true, it returns no value.
  * @retval None
  */
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */
 
#endif /* __STM32F10x_CONF_H */
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

 

Осталось прописать все необходимое в Main.c или что у вас там в качестве главного файла программы.
 

А в конце файла, чтобы в глаза не лезли, после main функции прописать парочку макросов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
 
  /* Infinite loop */
  while (1)
  {
  }
}
#endif

 

Assert макросы защищают либу, чтобы нельзя было совать в параметры левые значения. Скажем, засунуть в вместо адреса GPIOх какую-нибудь шнягу. Компилятору то пофигу, он скомпилит. А потом будешь сидеть и репу чесать. А тут сразу же вылетишь в функцию assert_failed. Полезно. Потому то я и включил эти макросы раскомментив

1
#define USE_FULL_ASSERT    1

 
И все, можно пользоваться. У меня main.c в данном примере выглядит так:
 

Показать »

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
/*
===============================================================================
 Name        : main.c
 Author      : DI HALT
 Version     :
 Copyright   : (C) Copyright
 Description : main definition
===============================================================================
*/
#include "stm32f10x.h"
#define F_CPU 72000000UL
 
// Задаем структуру параметров порта
GPIO_InitTypeDef GPIO_InitStructure;
 
int main(void)
{
// Настраиваем тактирование. Через функцию из CMSIS. Все настройки прописаны в файле system_stm32f10x.c
// У меня там задана частота 72Мгц с тактированием от кварца на 12Мгц
SystemInit();	
 
// Подаем такты на порт GPIOВ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
 
//Заполняем поля структуры нашими параметрами
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;		// Пятый вывод порта
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	// Скорость на 50Мгц
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	// Режим "Выход" Push-Pull
GPIO_Init(GPIOB, &GPIO_InitStructure);		// Применяем настроки на порт B
 
 
while(1)
	{
	GPIO_SetBits(GPIOB,GPIO_Pin_5);		// Установить бит
	GPIO_ResetBits(GPIOB,GPIO_Pin_5);		// Сбросить бит
	}
}
 
#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %drn", file, line) */
 
  /* Infinite loop */
  while (1)
  {
  }
}
#endif

 

Этот код быстро-быстро, с частотой примерно в пол мегагерца моргает диодиком на 5й ноге GPIOB. Но если шагать под отладчиком, то видно как оно мигает.
 

Откуда ты это узнал, демон?
Зашибись, чо. Вместо понятного даташита, где все черным по белому написано. Получили какие-то функции и хрен знает откуда взятые.
 
Чтобы не было таких мыслей открываем доку на SPL. Ту самую чмошку, что лежит в архиве с либой stm32f10x_stdperiph_lib_um.chm Поскольку документация на либу похожа больше на DOXYGENовскую отрыжку, чем на нормальное человеческое описание, то понять, что там и как это тот еще квест. Возьму на себя неблагодарный труд указать в какую сторону и в каком порядке смотреть. Применительно к моему микропроекту.
 

Итак, первым делом надо включить тактирование порта. За это в STM32 отвечает система RCC*. Лезем в доку и в разделе Modules->STM32F10x_StdPeriph_Driver ищем подраздел RCC. В нем, в первую же очередь, лезем в последнюю главу RCC_Private_Function там список фунций этой либы. Внимательно ищем подходящую :) RM0008 гласит, что GPIOB сидит на шине APB2, значит ищем эти магические буквы в названиях функций. А раз нам надо сконфигурировать тактовую частоту, то ищем еще и Clock и Cmd :)
 

RCC_APB2PeriphClockCmd выглядит тем, что нам нужно. Лезем в эту страницу и видим все возможные варианты использования

void RCC_APB2PeriphClockCmd (uint32_t RCC_APB2Periph,FunctionalState NewState)
 

Enables or disables the High Speed APB (APB2) peripheral clock.
 

Parameters:
RCC_APB2Periph:

Specifies the APB2 peripheral to gates its clock. This parameter can be any combination of the following values:
RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB, RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE, RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1, RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1, RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3, RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17, RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11

 

NewState:

new state of the specified peripheral clock. This parameter can be: ENABLE or DISABLE.

 

Return values: None

 

Таким образом, для включения тактирования GPIOB нам надо выполнить сию конструкцию:

1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

 

Теперь, аналогично, лезем в раздел GPIO. Там все более запутано. Сначала, таким же образом, лезем в главу GPIO_Private_Functions и видим все, что можно сделать с портом через SPL. Нам надо инициализировать, потому смотрим GPIO_Init
 

void GPIO_Init (GPIO_TypeDef * GPIOx, GPIO_InitTypeDef * GPIO_InitStruct)
 

Initializes the GPIOx peripheral according to the specified parameters in the GPIO_InitStruct.
 

Parameters:
GPIOx,: where x can be (A..G) to select the GPIO peripheral.
GPIO_InitStruct,: pointer to a GPIO_InitTypeDef structure that contains the configuration information for the specified GPIO peripheral.

 

Тут на входе номер порта и инициализирующая структура GPIO_InitTypeDef. Благо есть гиперссылки, уходим на GPIO_InitTypeDef и смотрим поля:
 

GPIO_InitTypeDef Struct ReferenceGPIO_Exported_Types
 

#include
 

Data Fields

  • GPIOMode_TypeDef GPIO_Mode
  • uint16_t GPIO_Pin
  • GPIOSpeed_TypeDef GPIO_Speed

 

Теперь смотрим что такое GPIO_Mode и какое оно бывает. Прорываемся по ссылкам к GPIOMode_TypeDef
 

Configuration Mode enumeration.
 

  • GPIO_Mode_AIN — аналоговый вход для АЦП
  • GPIO_Mode_IN_FLOATING — Z состояние
  • GPIO_Mode_IPD — вход с подтяжкой вниз
  • GPIO_Mode_IPU — вход с подтяжкой вверх
  • GPIO_Mode_Out_OD — выход Open Drain
  • GPIO_Mode_Out_PP — выход Push-Pull
  • GPIO_Mode_AF_OD — Альтернативная функция Open Drain
  • GPIO_Mode_AF_PP — Альтернативная функция Push-Pull

 

Вот, собственно и все режимы. Подробней о них в статье про порты GPIO
 

Аналогично разматываем GPIO_Pin

  • #define GPIO_Pin_0 ((uint16_t)0x0001)
  • #define GPIO_Pin_1 ((uint16_t)0x0002)
  • #define GPIO_Pin_10 ((uint16_t)0x0400)
  • #define GPIO_Pin_11 ((uint16_t)0x0800)
  • #define GPIO_Pin_12 ((uint16_t)0x1000)
  • #define GPIO_Pin_13 ((uint16_t)0x2000)
  • #define GPIO_Pin_14 ((uint16_t)0x4000)
  • #define GPIO_Pin_15 ((uint16_t)0x8000)
  • #define GPIO_Pin_2 ((uint16_t)0x0004)
  • #define GPIO_Pin_3 ((uint16_t)0x0008)
  • #define GPIO_Pin_4 ((uint16_t)0x0010)
  • #define GPIO_Pin_5 ((uint16_t)0x0020)
  • #define GPIO_Pin_6 ((uint16_t)0x0040)
  • #define GPIO_Pin_7 ((uint16_t)0x0080)
  • #define GPIO_Pin_8 ((uint16_t)0x0100)
  • #define GPIO_Pin_9 ((uint16_t)0x0200)
  • #define GPIO_Pin_All ((uint16_t)0xFFFF)
  • #define IS_GET_GPIO_PIN(PIN)
  • #define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))

 

Тут видим, что это битмаски, а значит их можно складывать через OR, например так: GPIO_Pin_10 | GPIO_Pin_2
 

Ну и по скорости аналогично:

enum GPIOMode_TypeDef
Output Maximum frequency selection.
Enumerator:

  • GPIO_Speed_10MHz
  • GPIO_Speed_2MHz
  • GPIO_Speed_50MHz

 

Теперь, со знанием дела, создаем переменную GPIO_InitStructure типа GPIO_InitTypeDef

1
GPIO_InitTypeDef GPIO_InitStructure;

А потом заполняем структуру и вызываем функцию:

1
2
3
4
5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
 
GPIO_Init(GPIOB, &GPIO_InitStructure);

 

Ну и через ту же библиотеку дрыгаем ножкой

1
2
GPIO_SetBits(GPIOB,GPIO_Pin_5);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);

 

Усе просто.
 

Трэш, угар и содомия
Атлична-атлична, тащемта, все довольно просто, понятно, например. Не надо шерстить, например, по RM в поисках нужных битиков, а потом ковырять CMSIS на предмет их названий, например. Прям, тащемта, рай и дичайший угар. Но давайте поглядим на адский чад кутежа, что генерирует, например, эта либа.
 

Инициализация порта, например, в ручном режиме выглядит, тащемта, так:

1
2
GPIOB->CRL	&= ~GPIO_CRL_CNF5;	// Сбрасываем биты CNF для бита 5. Режим 00 - Push-Pull 
GPIOB->CRL 	|= GPIO_CRL_MODE5_0;	// Выставляем бит MODE0 для пятого пина. Режим MODE01 = Max Speed 10MHz

 

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

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
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));  
 
/*---------------------------- GPIO Mode Configuration -----------------------*/
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
    /* Check the parameters */
    assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
    /* Output mode */
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*---------------------------- GPIO CRL Configuration ------------------------*/
  /* Configure the eight low port pins */
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
    tmpreg = GPIOx->CRL;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = ((uint32_t)0x01) << pinpos;
      /* Get the port pins position */
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding low control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }
        else
        {
          /* Set the corresponding ODR bit */
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
    GPIOx->CRL = tmpreg;
  }
/*---------------------------- GPIO CRH Configuration ------------------------*/
  /* Configure the eight high port pins */
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
    tmpreg = GPIOx->CRH;
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
      /* Get the port pins position */
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
      if (currentpin == pos)
      {
        pos = pinpos << 2;
        /* Clear the corresponding high control register bits */
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
        /* Write the mode configuration in the corresponding bits */
        tmpreg |= (currentmode << pos);
        /* Reset the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
        /* Set the corresponding ODR bit */
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
    GPIOx->CRH = tmpreg;
  }
}

 

Почувствуйте разницу.
 

А стоит ли оно того?
Ну не так все страшно, на самом деле. Инициализация слишком разнообразная, чтобы разрулиться двумя строчками. Но она делается всего раз-два, потому не важно. А вот непосредственная дрыгалка битами уже ничем не отличается от ручного ковыряния:
 

1
2
3
4
5
6
7
8
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_Pin));
 
  GPIOx->BSRR = GPIO_Pin;
}

 

Assert потом вообще можно одним движением занулить и тогда сей макрос вывалится из поля зрения компилятора. Оставив только
 

1
GPIOx->BSRR = GPIO_Pin;

 

Как мы и дрыгали в ручном режиме. Оверхэд получается не слишком большой.
 

Правда тут есть нюанс. Я настоятельно не рекомендую использовать SPL на этапе обучения. Хотя это и так заманчиво. Несмотря на то, что все становится наглядней и проще, но ковыряние в RM позволяет гораздо лучше изучить возможности контроллера, понять всю низкоуровневую кухню работы периферии. Увидеть больше вариантов использования.

42 thoughts on “Работа с STM32F10x Standard Peripherals Library”

    1. Пример косяка:

      // *** ADC1 Init **************************************************
      ADC_InitTypeDef ADC_InitStructure;

      ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

      ADC_InitStructure.ADC_ScanConvMode = DISABLE;

      ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

      ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

      ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

      ADC_InitStructure.ADC_NbrOfConversion = 1;

      ADC_Init(ADC1, &ADC_InitStructure);

      //Ещё раз объявляю, что ADC_DataAlign = ADC_DataAlign_Right;
      ADC1->CR2 &= ~(1<<11);

        1. Я видал раз 5. Жалоб в иностранных интернетов видал ещё больше.
          Что самое страшное бывает примерно так: с f100 работает, с f103 нет, с f200 ок, f400 — опять лажа.
          И очень важно иметь распоследнюю версию.

      1. Все там работает , вот строки из моей рабочей либы 3.0.4

        stm32f10x_adc.c

        tmpreg1 |= (uint32_t)(ADC_InitStruct->ADC_DataAlign | ADC_InitStruct->ADC_ExternalTrigConv |
        ((uint32_t)ADC_InitStruct->ADC_ContinuousConvMode <CR2 = tmpreg1;

        stm32f10x_adc.h

        #define ADC_DataAlign_Right ((uint32_t)0x00000000)
        #define ADC_DataAlign_Left ((uint32_t)0x00000800)
        #define IS_ADC_DATA_ALIGN(ALIGN) (((ALIGN) == ADC_DataAlign_Right) || \
        ((ALIGN) == ADC_DataAlign_Left))

        так что (1<<11) это скорее DataAlign_Left

  1. ну не наю…я долго му***ался с SPL под stm32f4, раскуривал под шаманские напевы их хелп, в итоге забил и покурил RM, после чего меня так вшторило, что чакры открылись до небывалой ширины канала и мне стали доступны все тайные знания работы с основной периферией через прямое обращение к регистрам:) один хрен потом дебагером проходить везде, при настройке модуля I2C чуть больше строчек кода получилось, но зато есть знания об устройстве автомата и дальше можно самому в понятной форме наваять HAL:)

  2. Вопрос а зачем прикручивать это к платному компилятору? К gcc не прикручивается? А к clang?
    Мне казалось, ARM уже отлично поддержан и тем и другим, не PIC и не 8051, чай. А вот платные компиляторы обычно отстают по качеству, если это не Intel :)

    1. Конечно прикручивается. Какая разница? Что SPM, что CMSIS спокойно идут под любым из компиляторов большой тройки (Keil, IAR, GCC).

      Кстати, среди МК неоднократно слышал мнение профи о том, что самый лучший компилятор IAR, а все остальное баловство. И этому есть вполне обоснованные причины — эта банда круто держит нос по ветру и спецы из IAR принимают непосредственное участие в проектирование МК. Например, для AVR IAR лучший Си компилятор.

          1. сам Atmel писал что разработка велась совместно с
            ImageCraft C. А аппноуты под IAR, так это популярность IAR среди разработчиков. хотя сравнив ImageCraft с IAR можно много общего найти.

  3. А здесь еще отвечают на вопросы?
    Можно пальцем показать — где скачивать свежую версию библиотеки? Желательно — путь как нашли этот линк. Я перерыл весь сайт st.com, как смог. Есть или мануалы, где говорят как использовать версию 3.0, или можно найти пакеты, из которых можно выковырнуть кусок SPL. :( На форумах люди жалуются что не могут найти, а им советуют ковырять из разных мест.

    1. Если еще конечно интересно:)
      Заходите на страничку stm32 firmware (Home -> Tools and Software -> Software -> MCU Software -> STM32 MCUs Software -> STM32 Firmware) и там все лежит. Можно даже флажки по расставлять для удобства.

  4. Ребят, что за фигня у меня творится, никак не могу понять. Есть пустой проект ввожу в него такой вот кусок:
    char cnt;
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    while(1)
    {
    if(cnt)
    {
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
    cnt=0;
    }
    else
    {
    GPIO_ResetBits(GPIOB, GPIO_Pin_0);
    cnt=1;
    }
    }
    Смотрю на этой ноге На осциллографе 5Мгц!!!! При том, что частота работы процессора 72 Мгц, не понимаю…
    Далее пишу в регистры напрямую:
    while(1)
    {
    if(cnt)
    {
    GPIOB->BSRR=(GPIO_BSRR_BS0);
    cnt=0;
    }
    else
    {
    GPIOB->BSRR=(GPIO_BSRR_BR0);
    cnt=1;
    }
    }
    Частота вырастает дло 10 Мгц, уже лучше, но блин не настолько же ниже она должна работать?

    Теперь, верх моего помешательства. Вставляю один NOP в цикл и частота падает на 2Мгц!!!!
    Неужели STM32 такой медленный?
    Компилятор IAR, пробовал играть оптимизацией, не помогло, сейчас стоит на балансе. Как быть?

    1. 1) А ты уверен, что частота именно 72мгц?
      2) ARM архитектура как бы не подразумевает прямое ногодрыжество. Там от записи в регистр до реального изменения состояния вывода много всего происходит.

    2. дык этсамое. Максимальная частота, с которой можно дрыгать пинами — fcpu/4. На всяких «if»/»else», циклах и присваиваниях запросто может потеряться ещё несколько тактов (несмотря на то, что все или почти все машинные инструкции ARM имеют флаги условий выполнения, компилеры используют эту фишку нечасто). Вот, собсно, что-то такое и происходит.

      Разверните цикли и поставьте в него последовательные обращения к регистрам установки/сброса — скажем, чтобы была пачка из десятка вызовов, а затем уже заворот по while(1) обратно. Тогда на пине увидите (осциллографом) соответствующие пачки иголок, разделённые заметным промежутком.

  5. 1)Уверен, в проекте 3 таймера и все работают правильно(один звук проигрывает, я рассчитывал его от 72 МГц, звучит хорошо-> частота реально 72 Мгц + задефайнена именно она)
    2) Так как быть? Нужна очень большая частота, неужели с помощью STM32 ее нельзя достичь?

  6. Здравствуйте!
    Спасибо за статью! Такое написать — это громадный труд! У меня есть вопрос, если у Вас будет возможность и время, то не могли бы Вы мне разъяснить о переменной GPIO_Instructure? Я могу эту переменную как то по другому назвать, допустим, GPIO_Instructure1? Если да, то могу ли я в программе использовать несколько переменных, например GPIO_Instructure1, GPIO_Instructure?

    1. SPL это стандартная периферийная библиотека. Качается с сайта STM и подключается к проекту. Там есть заготовки для работы с разной набортной периферией.

      RM — это Reference Manual документ номер RM0008 это просто pdf файл с описанием процессора, вроде даташита, но один на все семейство STM32F10x

      .chm это стандартный тип файла справочной системы Windows. Уже лет 25 как стандарт хелпов. В винде с ним проблем нет, а вот чем его открывать под другими ОС я не знаю. Но не должно быть проблем, очень уж много всего в них бывает.

  7. Привет. Вопрос не совсем в тему, но вот такое случилось: конфигурял проект для vl-discovery через CubeMX. Делал простейшую мигалку. Все прошилось через кейл и замигало, но ст-линк больше не коннектится. ST-Link utility видит его, определяет, как V1 и просит connect under reset, чего V1, сопсно, не умеет. Тупо ручное зажимание резета помогает только запустить коннект, но он прекращается с формулировкой: коннект невозможен — MCU under Reset. При конфигурянии в кубе я выставил SYS No debug и не поставил галку System Wake-Up. Не выставил никаких галок в ADC1 (хотя куб просил, в частности на IN0).

    Сопсно, я примерно, догадываюсь, что я протупил не настроив SYS. Но, вот, что делать-то теперь?

  8. У меня такой вопрос. Почему переменная GPIO_InitTypeDef GPIO_InitStructure глобальная?
    Как я понял она нужна только для инициализации пина(пинов). Почему бы ее не объявить локально в функции начальной инициализации? Или я чего-то не понимаю?

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