Ярлыки

_GetPixelIndex (1) _SetPixelIndex (1) 3-phase (1) 800x480 (1) АЦП (1) генератор (1) синхронный усилитель (2) структура (1) учебный курс (1) шаговый двигатель (1) ШИМ (2) accert (1) AD7608 (1) AD8429 (1) ADC (5) amplifer (1) arccos (1) arcsin (1) arctang (2) arctg (3) ARM (2) arm_sqrt_q15 (2) assembler (6) ASSERT (1) atan (2) bit (1) Bitband (1) boot (3) bootlloader (1) BUTTON (1) C (5) C# (1) CAN (2) CC2530 (5) CMSIS (4) command (1) Cordic (1) Core746I (1) CubeMX (4) DBGMCU (2) debug (2) debug.ini (1) delegate (1) Digital Potentiometers (1) DigitalPOT (1) Discovery (1) DMA (9) DMA2D (1) DSP (1) DSP library (1) DWT (1) EFM32 (5) EmWin (9) EXTI (1) FATFS (1) FMC (2) FreeRTOS (2) gl868-dual cmux (1) GPIO (4) GUI (2) GUIBuilder (1) GUIDRV_CompactColor_16 (1) HAL (3) HappyGecko (1) Hard Fault (2) heap (1) I2C (1) ID (1) ILI9320 (1) ILI9325 (1) Initialisation (1) InitLTDC (1) Instrumentithion (1) Interrupt (4) ITR (1) JTAG (1) Keil (5) LCDConf (2) lock-in (1) LTCD (1) LTDC (3) main (1) memory (1) MINI_STM32 Revision 01 (1) nBoot0 (1) NVIC (1) OnePulse (2) OSAL (4) pack (1) phase (1) printf (3) Pulse (1) PWM (12) RCC (2) RCR (1) Register (1) RESET (2) RS232 (3) RSS (1) RTC (3) RTOS-RTX (1) RTT (1) RTX-RTOS (1) SDCard (1) SDRAM (6) Segger (2) SPI (3) sqrt (3) SSD1298 (1) SSD1963 (1) Standart Peripherial Library (3) STANDBAY (1) startup (1) STemWin (8) stepper motor (1) STlink (2) STM32 (17) STM32429ZI (1) STM32Cube (1) STM32DBG.IN (1) STM32F (28) STM32F0 (4) STM32F1 (13) STM32F4 (10) STM32F4 Discovery (1) STM32F407ZG (1) STM32F429 (2) STM32F746 (1) STOP (1) string (1) struct (1) SWD (1) SWD JTAG (1) Synhronization (1) system_stm32f4xx.c (1) SystemInit (1) SysTick (1) task (4) telit (1) TIM (27) typedef (1) UART (1) USART (9) viewer (2) WM_PAINT (1) Z-stack (5) ZigBee (5)

вторник, 1 сентября 2015 г.

SPL

Практически вся (если не вообще вся) периферия настраивается примерно одинаково, различия имеются только в специфических для каждого устройства параметрах и командах. Приведу пример: 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init( GPIOC , &GPIO_InitStructure);

Это пример настройки порта ввода-вывода (GPIO). Рассмотрим его поподробнее.

1. Включение тактирования периферийного устройства


Первым делом на порт подаётся тактирование: 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

Периферийные устройства тактируется либо от шины APB2, либо от APB1. От какой именно, можно узнать по названию константы. В нашем случае это RCC_APB2Periph_GPIOC, стало быть шина — APB2. Константа расположена в файлеstm32f10x_rcc.h, давайте найдем ее. В Eclipse для этого достаточно щелкнуть мышкой по константе, зажав Ctrl. 

Вот что мы находим в stm32f10x_rcc.h: 

#define RCC_APB2Periph_AFIO              ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA             ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB             ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC             ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD             ((uint32_t)0x00000020)
......
#define RCC_APB1Periph_TIM13             ((uint32_t)0x00000080)
#define RCC_APB1Periph_TIM14             ((uint32_t)0x00000100)
#define RCC_APB1Periph_WWDG              ((uint32_t)0x00000800)
#define RCC_APB1Periph_SPI2              ((uint32_t)0x00004000)
#define RCC_APB1Periph_SPI3              ((uint32_t)0x00008000)
#define RCC_APB1Periph_USART2            ((uint32_t)0x00020000)
...

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

RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

Не забывайте, что одним вызовом функции можно включить тактирование сразу несколько устройств, задав в параметре функции несколько констант через операторпобитовое ИЛИ:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC 
  | RCC_APB2Periph_ADC1, ENABLE);

Побитовое ИЛИ «соберёт» все единички из перечисленных констант, а сами константы, как вы уже догадались, являются маской, как раз предназначенной для такого использования.

Обратите внимание, что в некоторых случаях может потребоваться дополнительная настройка, например, для АЦП необходимо выставить предделитель частоты:

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

Максимальная тактовая частота для АЦП составляет 12 МГц, это необходимо учесть при выборе предделителя. 

Идём дальше. 




2. Объявление и настройка структуры для инициализации


Следующий шаг — объявление некой инициализационной структуры (InitStructure), которая содержит все параметры для настройки периферийного устройства в виде переменных-членов структуры:

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

Структура описывается в заголовочном файле того устройства, к которому она относится. В данном примере мы инициализируем порты ввода-вывода (GPIO), следовательно, структура GPIO_InitTypeDef описана в файле stm32f10x_gpio.h. Найдем её:

typedef struct
{
  uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
  This parameter can be any value of @ref GPIO_pins_define */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
  This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
  This parameter can be a value of @ref GPIOMode_TypeDef */
} GPIO_InitTypeDef;

Большинство параметров структуры, как правило, имеет тип, схожий с названием параметра и добавлением суффикса "_TypeDef". Такие типы обычно определены какenum (перечисление). Для примера найдем определения типа переменной GPIO_Mode (напоминаю, Ctrl + щелчок по GPIOMode_TypeDef):

typedef enum
{ GPIO_Mode_AIN = 0x0,
  GPIO_Mode_IN_FLOATING = 0x04,
  GPIO_Mode_IPD = 0x28,
  GPIO_Mode_IPU = 0x48,
  GPIO_Mode_Out_OD = 0x14,
  GPIO_Mode_Out_PP = 0x10,
  GPIO_Mode_AF_OD = 0x1C,
  GPIO_Mode_AF_PP = 0x18
} GPIOMode_TypeDef;

Мы нашли все возможные значения для параметра GPIO_Mode. Описание значений в файлах stm32f10x_gpio.h и .c мне найти не удалось, так что придется лезть в Reference Manual, узнавать возможные режимы работы портов ввода-вывода и сопоставлять их со значениями. Но в данном случае здесь и так все понятно:
  • GPIO_Mode_AIN — аналоговый вход;
  • GPIO_Mode_IN_FLOATING — вход без подтяжки, болтающийся (англ. float) в воздухе
  • GPIO_Mode_IPD — вход с подтяжкой к земле (англ. Pull-down)
  • GPIO_Mode_IPU — вход с подтяжкой к питанию (англ. Pull-up)
  • GPIO_Mode_Out_OD — выход с открытым стоком (англ. Open Drain)
  • GPIO_Mode_Out_PP — выход двумя состояниями (англ. Push-Pull — туда-сюда)
  • GPIO_Mode_AF_OD — выход с открытым стоком для альтернативных функций (англ. Alternate Function). Используется в случаях, когда выводом должна управлять периферия, прикрепленная к данному разряду порта (например, вывод Tx USART и т.п.)
  • GPIO_Mode_AF_PP — то же самое, но с двумя состояниями

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

Кстати, перед заполнением структуры данными, рекомендуется вызвать функцию GPIO_DeInit(GPIOx), сбрасывающую текущие настройки в значения по умолчанию.

3. Инициализация


Самый простой пункт. Вызываем функцию инициализации, куда передаем указатель на сформированную в п.2 структуру:

GPIO_Init( GPIOC , &GPIO_InitStructure);

Для большинства устройств также требуется вызов команды «включение». Пример для включения USART1 и ADC: 

USART_Cmd(USART1, ENABLE);
ADC_Cmd(ADC1, ENABLE);

4. Настройка прерываний


4.1. Настройка и инициализация NVIC

С прерываниями дела обстоят ни чуть не сложнее, чем со структурой инициализации. Для начала надо настроить и проинициализировать контроллер прерываний (NVIC — Nested vectored interrupt controller). В архитектуре Cortex M3 каждому прерыванию можно выставить свой приоритет для случаев, когда возникает несколько прерываний одновременно. Поэтому NVIC представляет нам несколько вариантов формированияприоритетных групп. Я не буду вдаваться в подробности, приведу лишь пример выбора варианта приоритетных групп, он состоит всего из одной команды:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

Эта команда выполняется один раз. Далее, для каждого прерывания, нам надо произвести настройку и инициализацию с помощью структуры — точно так же, как мы поступали в п.2. Вот, например, настройка прерывания для USART1:

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

В параметре NVIC_IRQChannel мы указываем, какое именно прерывание мы инициализируем. Константа USART1_IRQn обозначает канал, отвечающий за прерывания, связанные с USART1. Найдя ее определение в файле stm32f10x.h, вы увидите еще множество констант (ADC1_IRQn, TIM1_TRG_COM_TIM17_IRQn и др.), обозначающих прерывания от других периферийных устройств. 

Следующими двумя строками мы указываем приоритет прерываний (максимальные значения этих двух параметров определяются выбранной приоритетной группой). Последняя строка, собственно, включает использование прерывания. 

Структура настроена, инициализируем:

NVIC_Init(&NVIC_InitStructure);

C настройкой NVIC мы закончили. 

4.2. Включение прерываний для периферийного устройства

Включение прерываний производится функцией, которую вы можете найти заголовочном файле периферийного устройства. Она выглядит примерно так (рассмотрим на примере USART1):

void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, 
  FunctionalState NewState);

В заголовочном файле stm32f10x_usart.h нет никаких комментариев по поводу ее использования, а вот в файле реализации — stm32f10x_usart.c — есть всё, что нам нужно:

/**
  * @brief  Enables or disables the specified USART interrupts.
  * @param  USARTx: Select the USART or the UART peripheral. 
  *   This parameter can be one of the following values:
  *   USART1, USART2, USART3, UART4 or UART5.
  * @param  USART_IT: specifies the USART interrupt sources to be enabled or disabled.
  *   This parameter can be one of the following values:
  *     @arg USART_IT_CTS:  CTS change interrupt (not available for UART4 and UART5)
  *     @arg USART_IT_LBD:  LIN Break detection interrupt
  *     @arg USART_IT_TXE:  Transmit Data Register empty interrupt
  *     @arg USART_IT_TC:   Transmission complete interrupt
  *     @arg USART_IT_RXNE: Receive Data register not empty interrupt
  *     @arg USART_IT_IDLE: Idle line detection interrupt
  *     @arg USART_IT_PE:   Parity Error interrupt
  *     @arg USART_IT_ERR:  Error interrupt(Frame error, noise error, overrun error)
  * @param  NewState: new state of the specified USARTx interrupts.
  *   This parameter can be: ENABLE or DISABLE.
  * @retval None
  */
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
{
   ...
}

Разработчики библиотеки нам всё доступно объяснили. Я только отмечу, что на один канал прерываний назначено несколько событий. В данном случае на каналUSART1_IRQn, который мы активировали при настройке NVIC, назначено аж 8 событий, описанные в этом комментарии: USART_IT_CTS, USART_IT_LBD и далее по списку. При вызове функции USART_ITConfig нам нужно указать, на какие именно события прерывание должно срабатывать. К примеру, если мы хотим получить прерывание при приёме байта, мы должны должны отловить событие USART_IT_RXNE (RX is Not Empty — принятый буфер не пуст). Команда будет выглядеть следующим образом:

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

Обратите внимание, что в отличие от констант RCC описанные здесь константы не являются масками, и через побитовое ИЛИ их объединять нельзя. Например, для включения прерывания и при приёме байта и при успешной передаче байта, придется писать две команды:

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);


4.3. Обработчик прерывания

Обработчиком прерывания является функция, название которой совпадает с названием соответствующего вектора прерывания в startup-файле. Startup-файлы, входящие в состав STM32 Peripheral Library, написаны на ассемблере. Вот фрагмент файлаstartup_stm32f10x_md_vl.s:

DCD     SPI1_IRQHandler                 ; SPI1
DCD     SPI2_IRQHandler                 ; SPI2
DCD     USART1_IRQHandler               ; USART1
DCD     USART2_IRQHandler               ; USART2
DCD     USART3_IRQHandler               ; USART3
DCD     EXTI15_10_IRQHandler            ; EXTI Line 15..10

Мне так же встретился файл, написанный на Си:

void WEAK  SPI1_IRQHandler(void);
void WEAK  SPI2_IRQHandler(void);
void WEAK  USART1_IRQHandler(void);
void WEAK  USART2_IRQHandler(void);
void WEAK  USART3_IRQHandler(void);
void WEAK  EXTI15_10_IRQHandler(void);

В любом случае мы можем легко найти название обработчика прерывания, для рассматриваемого примера это USART1_IRQHandler(void). Нам осталось только описать функцию-обработчик прерывания:

void USART1_IRQHandler(void) {
   // Код обработчика прерывания
}

Функция будет вызываться при срабатывании прерывания. Однако, есть 2 важных момента. Во-первых, прерывание срабатывает на несколько событий, и нам надо проверять, какое именно из событий вызвало функцию. Во-вторых, бит ожидания обработки прерывания не всегда сбрасывается аппаратно, поэтому мы должныобязательно вручную, программно, сбросить этот бит, показав, что прерывание обработано. Иначе процессор войдет в бесконечный цикл, постоянно вызывая нашу функцию, считая, что если бит не сброшен, значит прерывание не обработано и функцию надо вызвать снова. 

Учитывая эти 2 аспекта, «шаблонный» код обработчика мы подготовим вот таким:

void USART1_IRQHandler(void) {

    // Обработка события RXNE
    if ( USART_GetITStatus(USART1, USART_IT_RXNE) ) {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        // ... Код обработчика ...
    };

    // Обработка события TXE
    if ( USART_GetITStatus(USART1, USART_IT_TXE) ) {
        USART_ClearITPendingBit(USART1, USART_IT_TXE);
        // ... Код обработчика ...
    };

    // Обработка других событий... 
};

Здесь должно быть всё понятно. Функция USART_GetITStatus() проверяет наличие прерывания по определенному событию, Если проверка успешна, функция USART_ClearITPendingBit() сбрасывает тот самый «бит ожидания обработки», затем идет собственно код обработчика.

5. Подводя итог


Это был последний шаг. Выработанный алгоритм по настройке периферийного устройства получился такой:

  • Задействовать тактирование устройства.

    • Функция RCC_APBxPeriphClockCmd(RCC_APBxPeriph_***, ENABLE);
    • при необходимости задать предделитель, пример для АЦП: CC_ADCCLKConfig(RCC_PCLK2_Div6);
    • Имена констант искать файле stm32f10x_rcc.h
    • По имени константы определить шину тактирования (APB1 или APB2)
  • Произвести инициализацию с помощью init-структуры

    • Сбросить настройки с помощью функции ***_DeInit();
    • В заголовочном файле периферийного устройства (stm32f10x_***.h) найти название и описание структуры ***_InitTypeDef
    • Создать переменную типа этой структуры: ***_InitTypeDef ***_InitStructure;
    • Присвоить параметрам структуры требуемые значения
    • Вызвать команду инициализации ***_Init(&***_InitStructure);
    • При необходимости дать команду включения ***_Cmd(***, ENABLE);
  • Настроить прерывания

    • Инициализировать NVIC. Константы для NVIC_IRQChannel искать в stm32f10x.h;
    • Включить прерывания на определенные события функцией ***_ITConfig(). Значения констант искать в заголовочном файле периферии (stm32f10x_***.h)
    • Найти в startup-файле имя вектора прерывания, создать функцию-обработчик прерывания с таким же именем
    • Внутри обработчика проверять события функцией ***_GetITStatus() и обязательно очищать бит ожидания функцией ***_ClearITPendingBit().

Алгоритм обобщенный, но поможет вам справиться с большинством проблем, которые возникают на начальном этапе изучения микроконтроллеров STM32. Хочу отметить, что поиск в Google по именам функций и структур частенько дает примеры кода. Воспользуйтесь ими в качестве подсказки. 

6. Пример


В качестве примера представлю, что я не умею работать с АЦП и на основе вышеописанного алгоритма попробую написать код, производящий все необходимые действия для простейшей работы с АЦП: запуск единичного преобразования с порта A1 и чтение значения по прерыванию. 

// Подал тактовые импульсы на АЦП.
// Названия констант нашел в stm32f10x_rcc.h
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// Сбросил настройку
ADC_DeInit(ADC1);
// В stm32f10x_adc.h нашел название структуры ADC_InitTypeDef,
// объявил переменную с этим типом
ADC_InitTypeDef  ADC_InitStructure;
// Заполнил значения. Имена констант нашел в том же stm32f10x_adc.h,
// еще полистал Reference Manual для ознакомления с режимами
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
// Инициализировал АЦП заполненной структурой
ADC_Init(ADC1, &ADC_InitStructure);
// Включил 
ADC_Cmd(ADC1, ENABLE);
// Разрешил прерывания по окончанию преобразования
// Список возможных констант получен поиском по ADC_ITConfig 
// (найдено в stm32f10x_adc.c), из них выбрал нужную.
// EOC это End of conversion
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
// Гугление показало, что для АЦП надо производить калибровку
// Тупо скопировал найденный код. Специфичная для АЦП вещь,
// поэтому в алгоритме ее нет. 
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1)) { };
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1)) { };
// Настроил группы приоритета прерываний 
// (эта строка пишется, один раз за всю программу, где-то в начале)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// Создал структуру NVIC и заполнил ее значениями
// Название константы ADC1_IRQn взял из stm32f10x.h
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = ADC1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Написал обработчик прерывания по рекомендуемому шаблону,
// не забыв проверить источник прерывания и сбросить бит
void ADC1_IRQHandler(void) {
    if (ADC_GetITStatus(ADC1, ADC_IT_EOC)) {
        ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
        // Чтение значения АЦП
        // Функцию нашел в stm32f10x_adc.h
        val = ADC_GetConversionValue(ADC1);
    };
};
// Как запустить преобразование не знаю,
// обратился к примеру из гугла. Оказалось, вот так:
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_1Cycles5);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

Комментариев нет:

Отправить комментарий