Программный шим на stm8l

Использование PWM (ШИМ) в STM8

С помощью микроконтроллера STM8 можно генерировать заданную частоту с порта ввода-вывода. Для этого не обязательно вручную переключать туда-сюда пин на порту.

Можно воспользоваться одним из таймеров, а именно задействовать в нём функцию PWM. Это режим широтно-импульсной модуляции (ШИМ).

В нём можно генерировать заданную частоту и регулировать скважность этой частоты.

Код, приведённый в этой статье относится к компилятору Raisonance.

Для работы с ШИМ я использую следующие макросы (ниже будет показано во что они разворачиваются, так как сложно воспринимать код, написанный в виде макросов):

// timer == TIM2, TIM3 #define InitTimer_PWM(timer) timer##_DeInit(); timer##_TimeBaseInit(timer##_PRESCALER_1, uPeriod); timer##_ARRPreloadConfig(ENABLE); timer##_Cmd(ENABLE); #define InitChannel_PWM(timer, channel) timer##_OC##channel##Init(timer##_OCMODE_PWM1, timer##_OUTPUTSTATE_ENABLE, uPulse, timer##_OCPOLARITY_HIGH); timer##_OC##channel##PreloadConfig(ENABLE); #define ChangePulse_PWM(timer, channel) timer##->CCR##channel##H = (u8)(uPulse >> 8); timer##->CCR##channel##L = (u8)(uPulse); #define DisableChannel_1_PWM(timer) timer->CCER1 &= (u8)(~timer##_CCER1_CC1E) #define DisableChannel_2_PWM(timer) timer->CCER1 &= (u8)(~timer##_CCER1_CC2E) #define DisableChannel_3_PWM(timer) timer->CCER2 &= (u8)(~timer##_CCER2_CC3E)

Последние три макроса нужны на тот случай, если нужно отключить выдачу частоты с канала таймера.

Макрос InitTimer_PWM инициализирует режим PWM для заданного таймера.

Вызывать его нужно так: InitTimer_PWM(TIM3); Но, поскольку это макрос, где-то перед ним нужно рассчитать, во-первых, генерируемую частоту, во-вторых, её скважность.

Частоту берём из параметра функции uFreq, а скважность будет в переменной uPulse, рассчитанной из параметра fPulse. Параметр fPulse это заполнение в процентах. То есть для скважности 50 % туда надо поместить значение (float)50.

static bool bTimerInitialized = false; void SetFreqChannel_3(u16 uFreq, float fPulse) { u16 uPulse, uPeriod; float fCoeff, fNewPulse; static bool bChannelInitialized = false; uPeriod = (u16)(CLK_GetClockFreq() / uFreq); fCoeff = fPulse / 100.; fNewPulse = (float)uPeriod * fCoeff; uPulse = (u16)( fNewPulse ); if (!bTimerInitialized) { // Таймер ещё не был инициализирован InitTimer_PWM(TIM2); bTimerInitialized = true; } if (!bChannelInitialized) { // Канал ещё не был инициализирован InitChannel_PWM(TIM2, 3); bChannelInitialized = true; } else { // Поменять параметры ШИМ ChangePulse_PWM(TIM2, 3); } }

То есть пишем, например SetFreqChannel_3(200, 45.); и получаем на третьем канале таймера TIM2 частоту 200 Гц и скважность 45 %.

Почему у меня в примере функция только для канала 3? А потому, что иначе не получится использовать макрос ChangePulse_PWM, которому во втором параметре нужно именно число (ведь оно подставляется в имя регистра).

Модифицировать функцию так, чтобы она работала со всеми каналами сразу, предлагаю вам самостоятельно, если вам это нужно.

Теперь, что касается макросов, они разворачиваются так:

// InitTimer_PWM(TIM2) = TIM2_DeInit(); TIM2_TimeBaseInit(TIM2_PRESCALER_1, uPeriod); TIM2_ARRPreloadConfig(ENABLE); TIM2_Cmd(ENABLE); // InitChannel_PWM(TIM2, 1) = TIM2_OC1Init(TIM2_OCMODE_PWM1, TIM2_OUTPUTSTATE_ENABLE, uPulse, TIM2_OCPOLARITY_HIGH); TIM2_OC1PreloadConfig(ENABLE); // ChangePulse_PWM(TIM2, 1) TIM2->CCR1H = (u8)(uPulse >> 8); TIM2->CCR1L = (u8)(uPulse); // DisableChannel_1_PWM(TIM2) = TIM2->CCER1 &= (u8)(~TIM2_CCER1_CC1E) // DisableChannel_2_PWM(TIM2) = TIM2->CCER1 &= (u8)(~TIM2_CCER1_CC2E) // DisableChannel_3_PWM(TIM2) = TIM2->CCER2 &= (u8)(~TIM2_CCER2_CC3E)

Это — либо обращения к регистрам микроконтроллера, либо вызовы функций библиотеки из состава пакета STM8-RKIT.

система комментирования CACKLE

Источник: http://hex.pp.ua/stm8-pwm.php

Примитивы для реализации 1-Wire master при помощи PWM и ICP для STM8L и STM32

В предыдущей статье был представлен вариант реализации примитивов для асинхронной работы с шиной 1-wire для микроконтроллеров Atmel.

Ну а сейчас вашему вниманию предлагается реализацию того же самого, но на более мощных микроконтроллерах семейства STM8L (для отладки использовалась обычная оценочная плата STM8L-Discovery с извлеченным LCD-дисплеем). С минимальными изменениями описываемая реализация может быть адаптирована и для семейства STM32.

В качестве toolchain использовался IAR Embedded Workbench для STM8 с бесплатной лицензией (ограничение размера кода 8Кб).

В качестве аппаратного драйвера шины 1-Wire взята схема из предыдущей статьи. Подключение следующее:

  • контакт PB1 оценочной платы подключаем к OCRA драйвера
  • контакт PD0 оценочной платы подключаем к ICP драйвера
  • контакт PA5 оценочной платы подключаем к PULLUP драйвера

Не забываем подключить GND, питание +5V и питание +3V3. В отличии от AVR микроконтроллеры семейства STM8 (и STM32) предлагают более широкий набор внутренней периферии. Таким образом описываемый проект можно скомпилировать в нескольких вариантах:

  1. IRQ-only. Логика работы реализована только по прерываниям, без использования DMA. Этот вариант компилируется когда символ __DRV_ONEWIRE_DMA не определен. Плюсы реализации:
    • Требуется всего 3 байта RAM
    • Возможность запрограммировать таймер на любое требуемое разрешение

    Минусы реализации:

    • Большое количество прерываний (по 3 прерывания на каждый передаваемый бит)

  2. DMA с 8-ми битной передачей. Программирование таймера и сохранение результатов во время приема и передачи битов производится при помощи DMA. Этот вариант компилируется когда символ __DRV_ONEWIRE_DMA=1. Плюсы реализации:
    • Требуется 9 байт RAM
    • Небольшое количество прерываний (одно прерывание на 1-й передаваемый бит и 3 прерывания в случае успешного завершения обмена)

    Минусы реализации:

    • Невозможность запрограммировать таймер на большое разрешение

  3. DMA с 16-ти битной передачей. Программирование таймера и сохранение результатов во время приема и передачи битов производится при помощи DMA.Этот вариант компилируется когда символ __DRV_ONEWIRE_DMA=2. Плюсы реализации:
    • Небольшое количество прерываний (одно прерывание на 1-й передаваемый бит и 3 прерывания в случае успешного завершения обмена)
    • Возможность запрограммировать таймер на любое требуемое разрешение

    Минусы реализации:

Кроме того, благодаря наличию у таймеров входа запрета PWM (BREAK-сигнал), появилась возможность аппаратно реализовать защиту от попытки выполнить обмен данными в момент включенного active-pullup (т.е. чтобы случайно не замыкали 1-wire на землю в момент, когда на нее подается питание для выполнения преобразований). Общие особенности реализации Когда примитивы не используются, на выходе PB1 поддерживается низкий уровень сигнала (т.е. модулирующий каскад не активен). Таймер запрограммирован таким образом, что длительность периода соответствует длительности таймслота плюс длительность защитной паузы между битами (для процедуры передачи битов) либо суммарной длительности импульса RESET, паузы до возможного начала PRESENCE и максимальной длительности самого PRESENCE (для процедуры RESET). Регистры управления PWM (TIMx_CCR1) и Capture (TIMx_CCR2) программируются в режим использования shadow-регистров. Это означает, что после программного изменения регистра PWM ((TIMx_CCR1) его значение начнет использоваться только после возникновения сигнала UEV внутри микроконтроллера. А этот сигнал формируется либо автоматически при достижении счетчиком заданной вершины (период), либо программно путем установки бита UG в регистре TIMx_EGR). Непосредственно до запуска PWM вычисляется и загружается в регистр PWM (TIMx_CCR1) требуемая длительность импульса самого младшего передаваемого бита. После этого таймер запускается установкой бита CEN в регистре TIMx_CR1. В этот момент сигнал PWM еще не активен, т.к. бит MOE в регистре TIMn_BKR заранее был сброшен в 0. После этого программно устанавливается бит UG в TIMx_EGR, что вызывает активацию внутреннего сигнала UEV, сброс текущего значения счетчика в 0 и загрузку содержимого регистра PWM (TIMx_CCR1) в его shadow-копию, используемую для сравнения. Кроме того, так как в регистре TIMn_BKR заранее был установлен бит AOE, по сигналу UEV может автоматически установится бит MOE в регистре TIMn_BKR, что разрешит выход PWM. Однако это произойдет только при не активном сигнале BREAK, который формируется от управления внешним active-pullup (это возможно потому, что сигнал BREAK внутри микроконтроллера формируется непосредственно по значению сигнала на внешнем пине независимо от того, в запрограммирован ли он на режим входа или выхода). Таким образом, если при выполнении процедуры RESET либо попытки передать/принять бит(ы) будет активен сигнал active-pullup, то включение выхода PWM будет блокировано.

Принцип работы IRQ-only реализации совпадает с описанным в предыдущей статье с тем отличием, что вместо режима работы таймера «вверх-вниз» используется режим подсчета «вверх до заданного значения».

Это стало возможным потому, что разрядов 16-ти битного таймера вполне хватает как для формирования периода таймслота передачи одного бита, так и для реализации процедуры «формирование сигнала RESET с ожиданием PRESENCE».

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

Реализация с использованием DMA с 8-ми и 16-ти битной передачей в принципе одинаковая и различается только разрядностю значений для PWM и ICP.

Все длительности импульсов вычисляются заранее до запуска таймера. Длительность импульса самого младшего бита загружается в регистр PWM (TIMx_CCR1) как описано выше, а для загрузки остальных значений при необходимости программируется 1-й канал DMA.

Все результаты измерений всегда сохраняются при помощи 2-го канала DMA.

Источник: https://habr.com/post/326114/

ZiB

Потихоньку переходим к системе прерываний и таймерам.

Контроллер прерываний

Возможности:

  • управление прерываниями от периферийных модулей
  • управление внешними прерывания от линий ввода-вывода (индивидуальный вектор на порт и индивидуальный флаг на линию ввода-вывода)
  • управление программным прерыванием (команда TRAP)
  • до четырех программных уровней для маскируемых прерываний
  • два не маскируемых прерывания (сброс и программное прерывание)
  • до тридцати маскируемых прерываний с «жестким» приоритетом
  • и много ещё чего 🙂 см. документацию

Далее в моем понимание логика работы.

Всё базируется на таблице векторов прерываний. Таблица располагается в начале флеш-памяти (адрес 0x00800000), один вектор занимает 4 байта.

Самый первый вектор располагающийся по адресу 0x8000 отведен под «начальную» точку входа, он обрабатывается автоматически при сбросе микроконтроллера. Приоритет у него самый высокий.

Второе место отдано под программное прерывание (ассемблерная команда TRAP), оно так же как сброс не маскируется. Далее располагаются 30 аппаратных векторов прерываний.

Для наглядности вся таблица прерываний:

struct interrupt_vector const _vectab[] = { {0x82, (interrupt_handler_t)_stext}, /* reset */ {0x82, NonHandledInterrupt}, /* trap */ {0x82, NonHandledInterrupt}, /* irq0 */ {0x82, NonHandledInterrupt}, /* irq1 */ {0x82, NonHandledInterrupt}, /* irq2 */ {0x82, NonHandledInterrupt}, /* irq3 */ {0x82, NonHandledInterrupt}, /* irq4 */ {0x82, NonHandledInterrupt}, /* irq5 */ {0x82, NonHandledInterrupt}, /* irq6 */ {0x82, NonHandledInterrupt}, /* irq7 */ {0x82, NonHandledInterrupt}, /* irq8 */ {0x82, NonHandledInterrupt}, /* irq9 */ {0x82, NonHandledInterrupt}, /* irq10 */ {0x82, NonHandledInterrupt}, /* irq11 */ {0x82, NonHandledInterrupt}, /* irq12 */ {0x82, NonHandledInterrupt}, /* irq13 */ {0x82, NonHandledInterrupt}, /* irq14 */ {0x82, NonHandledInterrupt}, /* irq15 */ {0x82, NonHandledInterrupt}, /* irq16 */ {0x82, NonHandledInterrupt}, /* irq17 */ {0x82, NonHandledInterrupt}, /* irq18 */ {0x82, NonHandledInterrupt}, /* irq19 */ {0x82, NonHandledInterrupt}, /* irq20 */ {0x82, NonHandledInterrupt}, /* irq21 */ {0x82, NonHandledInterrupt}, /* irq22 */ {0x82, NonHandledInterrupt}, /* irq23 */ {0x82, NonHandledInterrupt}, /* irq24 */ {0x82, handler_tim4}, /* irq25 */ {0x82, NonHandledInterrupt}, /* irq26 */ {0x82, NonHandledInterrupt}, /* irq27 */ {0x82, NonHandledInterrupt}, /* irq28 */ {0x82, NonHandledInterrupt}, /* irq29 */
};

Примечание. Пока прописан только один вектор (TIM4), на место не используемых я прописал заглушку.

Приоритет прерываний убывает с ростом номера вектора, т. е. максимальный приоритет у вектора по адресу 0x8000 и минимальный у вектора по адресу 0x807С. Кроме этого предусмотрена возможность задания программного приоритета, с числом уровней до 4. Для этого под каждый из 30 векторов выделено два бита, которые позволяют установить приоритет в соответствии с таблицей:

Примечание. Обратите внимание что установить приоритет (1;0) записью двух битов (1;0) нельзя, необходимо обнулить оба бита и только потом установить (1;0).

В совокупности наличие программных и аппаратных приоритетов позволяет очень гибко настраивать под свои нужды обработку прерываний. Более наглядно понять процесс можно анализируя ниже приведенный рисунки:

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

Для примера рассмотрим обработчик прерывания от таймера 4.

Прописываем обработчик в таблицу прерываний:

{0x82, handler_tim4}, /* irq25 */

Программный приоритет:

ITC->ISPR6 &= ~(BIT(6) | BIT(7));
ITC->ISPR6 |= BIT(6) | BIT(7);

на последнем этапе разрешаем прерывания:

Ну и не забыть разрешить генерацию событий в настройках таймера 4 (см. ниже).

Таймеры

В состав микроконтроллера входит пять таймеров таймеров: один таймер с расширенными настройками TIM1, три общего назначения TIM2, TIM3, TIM5 и один базовый таймер TIM4. Что бы понять «крутизну» первого таймера достаточно сравнить количество страниц которое потребовалось для описания его возможностей (87 стр) с количеством страниц для таймера 4 (9 стр). 🙂

Сводная таблица:

Как обычно начнем с самого простого, т. е. с таймера TIM4.

Посмотрев на его структурную схему понимаем что он действительно прост 😉

Но тем не менее данный восьми битный счетчик позволяет:

  • работать в качестве ведомого (ведущим может быть любой из оставшихся)
  • имеет встроенный предварительный делитель, с коэффициентом деления от 1 до 32768 (к сожалению коэффициент задается степенью двойки, т. е. от 0 до 15)
  • имеет регистр предварительной загрузки
  • генерировать события по переполнению (или перезагрузке)
  • генерировать события для контроллера ПДП (DMA)

В качестве примера рассмотрим настройку таймера для генерации событий каждые 4 мс (частота 250 Гц).

Вначале разрешим тактирование модуля:

CLK->PCKENR1 |= CLK_PCKENR1_TIM4;

Далее установим предварительный делитель на максимально возможное значение, нехитрый расчет показывает, что при тактовой частоте 2 МГц (не забываем, что мы по прежнему работает на этой частоте) кратными коэффициенты для частоты 250 Гц получаются такими:

  • предварительный 2000000/32 = 6250 Гц
  • значение для регистра предварительной загрузки 6250/25 = 250 Гц

Прописываем:

TIM4->PSCR = 5; // 2^5
TIM4->ARR = 25 – 1;

Разрешаем генерацию событий:

Разрешаем предварительную загрузку и работу самого таймера:

TIM4->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN;

В обработчике необходимо сбросить бит в регистре статуса:

@far @interrupt void handler_tim4(void)
{ TIM4->SR1 &= (uint8_t)(~TIM_SR1_UIF); PIN_TOGGLE(LED_GREEN);
}

Для проверки использую зеленый светодиод, просто мигаю им (замечу что 250 Гц для глаза не заметно, поэтому можно использовать осциллограф или просто снизить частоту генерации прерываний или завести программный делитель…)

Частоту в 250 Гц я выбрал только для более точной фильтрации помехи 50 Гц от помехи простым цифровым фильтром – скользящее среднее, при использовании АЦП.

Ну вот на этом пока все, хочу спать 🙂

Пишите комментарии или на форуме.

P/S Пока не нашел способа выводить список статей в удобном формате, поэтому если вы что-то не увидели попробуйте воспользоваться поиском (в меню справа;))

Источник: http://ziblog.ru/2011/01/31/stm8l-kontroller-preryivaniy-taymeryi-obshhego-naznacheniya.html

STMicroelectronics анонсировала вывод на рынок линейки микроконтроллеров с ультранизким энергопотреблением STM8L

Источник: https://www.rlocman.ru/news/new.html?di=65226

STM8 – программирование, прошивка и всё-всё-всё

Наряду с Arduino у любителей микроконтроллеров сейчас популярна и линейка продуктов от компании STMicroelectronics, включающая 8-разрядные микроконтроллеры STM8 и 32-разрядные микроконтроллеры STM32 (на ядре Cortex).

Микроконтроллеры STM8 состоят из нескольких линееек:
STM8S – основная линейка,
STM8A – для автомобильной промышленности,
STM8L – со сверхнизким потреблением энергии,
STM8T – емкостный сенсор для детектирования прикосновения или приближения.

Отладочную плату с микроконтроллером STM8 на борту можно приобрести за 1 (!) доллар и даже дешевле. Я приобрел несколько таких плат  на основе микроконтроллера STM8S103F3P6 на торговой площадке ebay:

» Новости » Микроконтроллеры

10-06-2010

STMicroelectronics » STM8L

Компания STMicroelectronics, мировой лидер по производству микроконтроллеров, анонсировала вывод на рынок и доступность полной линейки микроконтроллеров с ультранизким энергопотреблением STM8L. Отличительной особенностью линейки микроконтроллеров STM8L является гибкая система ультранизкого энергопотребления, высокая производительность и высокая степень интеграции периферии.

Микроконтроллеры STM8L имеют 5 режимов ультранизкого энергопотребления, в самом экономном режиме потребление составляет 350 нА с сохранностью данных в оперативной памяти и 4.7 мкс – временем выхода в полностью активный режим. В активном режиме потребление составляет 195 мкА на 1 МГц.

Основные характеристики линейки:

  • Максимальная тактовая частота 16 МГц (16 MIPS)
  • Два встроенных кварцевых генератора (16 МГц и 38 кГц)
  • Диапазон питающих напряжений 1.8 … 3.6 (1.65 в спящем режиме)
  • До 2 Кб ОЗУ и до 32 Кб флеш-памяти
  • 1 Кб EEPROM, 300,000 циклов записи/чтения
  • Управление питанием/сбросом – POR/PDR, PVD
  • Полноценные часы реального времени (RTC)
  • Модуль автопробуждения
  • ЖКИ драйвер на 4 × 28 сегментов
  • DMA-контроллер
  • 12-битный ЦАП
  • 12-битный АПЦ на 25 каналов, скорость преобразования 1 мкс
  • Два компаратора с ультранизким потреблением
  • Три 16-битных таймера
  • Один 8-битный таймер
  • Два сторожевых таймера
  • Коммуникационная периферия – USART, SPI, I2C
  • Температурный диапазон от –40 до +125 °C

Информация для заказа:

Микросхема Flash RAM EEPROM UART ШИМ АЦП ЦАП Таймеры Корпус
STM8L101F2P6 4 К 1.5 К 1 5 × bit TSSOP-20
STM8L101F3P6 8 К 1.5 К 1 5 × bit TSSOP-20
STM8L152C6T6 32 К 2 К 1 К 1 11 × bit 25 × 12 бит (1 МГц) 1 × 12 бит LQFP48
STM8L152K6T6 32 К 2 К 1 К 1 11 × bit 21 × 12 бит (1 МГц) 1 × 12 бит LQFP32
STM8L101G2U6A 4 К 2 К 2 К 2 3 QFPN28
STM8L101K3T6 8 К 2 К 2 К 2 3 LQFP32
контакт назначение
D4 PD4 / UART_CLK
D5 PD5 / TX
D6 PD6 / RX
RST сброс
A1 PA1 / Oscin
A2 PA2 / Oscin
GND земля
5V вход стабилизатора
3V3 выход стабилизатора
A3 PA3 / SS
D3 PD3 / Ain4
D2 PD2 / Ain3
D1 PD1 / SWIM
C7 PC7 / MISO
C6 PC6 / MOSI
C5 PC5 / SCK
C4 PC4 / Ain2
C3 PC3
B4 PB4 / SCL (шина I2C)
B5 PB5 / SDA (шина I2C)

Вот альтернативное представление распиновки микроконтроллера:
———–
UART1_CK / TIM2_CH1 / PD4 | 1 20 | PD3 / AIN4 / TIM2_CH2 / ADC_ETR UART1_TX / AIN5 / PD5 | 2 19 | PD2 / AIN3 UART1_RX / AIN6 / PD6 | 3 18 | PD1 / SWIM NRST | 4 17 | PC7 / SPI_MISO OSCIN / PA1 | 5 16 | PC6 / SPI_MOSI OSCOUT / PA2 | 6 15 | PC5 / SPI_CLK Vss (GND) | 7 14 | PC4 / TIM1_CH4 / CLK_CCO / AIN2 VCAP (*1) | 8 13 | PC3 / TIM1_CH3 / Vdd (+Ub) | 9 12 | PB4 / I2C_SCL TIM2_CH3 / PA3 | 10 11 | PB5 / I2C_SDA ———–

Микроконтроллер STM8S103F3P6 содержит 8 КБайт флэш-памяти с ресурсом стирания 10 000 раз, 640 байт EEPROM и 1 КБайт RAM. Тактовая частота 8-битного процессора серии STM8S составляет 16 МГц.

Для питания платы можно использовать следующие варианты:

  • подключение источника напряжением 4,5 … 15 В к контактам + или 5V и или GND;
  • подключение кабеля к microUSB-разъему (этот разъем используется только для питания!);
  • подключение источника питания напряжением 3,3 В к контактам 3V3 и или GND.

На плате установлен стабилизатор AMS1117-3.3. Вход стабилизатора соединен с контактом 5V, а выход – с контактом 3V3.

Разработка в среде IdeaSTM8

Установка среды разработки

Для программирования под микроконтроллеры STM8 можно использовать среду разработки IdeaSTM8 от компании Cosmic Software (в версии CXSTM8 special edition package – доступна с марта 2016 года, не имеет ограничений):

Для загрузки дистрибутива следует перейти по этой ссылке: http://cosmicsoftware.com/download_stm8_32k.php.

При этом для использования кросс-компилятора от Cosmic Software перед скачиванием необходимо пройти регистрацию, указав имя (Name), название компании (Company), страну (Other), адрес электронной почты (E-mail), а затем нажав для отправки сведений кнопку «Submit».
В версии 4.4.

6 объем дистрибутива (cxstm8_FSE_stm32_32K.exe) составляет 20,7 МБайт. 
Для получения годовой (затем продляемой) бесплатной лицензии при инсталляции необходимо нажать кнопку «Register on the Web», что приведет к отправке файла лицензии на адрес электронной почты, указанный при регистрации.

Лицензия привязывается к компьютеру, на котором установлен компилятор (с помощью HOSTNAME, HOSTID и т.п.).
После получения файла license.lic следует разместить его в папке COSMICFSE_CompilersCXSTM8License:

Разработка программы

В качестве примера создадим программу мигания светодиодом (Hello, world! в мире микроконтроллеров) TEST, размещенным на плате и подключенным к контакту PB.5.

Создаем новый проект, выполняя команду New Application:

Выбираем в качестве целевой платформы микроконтроллер STM8S103F3:

Копируем в папку проекта заголовочный файл с определениями stm8s.h, предварительно раскомментировав в нем определение используемого микроконтроллера STM8S103:

/* #define STM8AF626x */ /*!< STM8A Medium density devices */ #define STM8S103 /*!< STM8S Low density devices */ /* #define STM8S903 */ /*!< STM8S Low density devices */

Создаем новый файл (tst.c) с исходным кодом:

создаем файл:

выбираем в качестве типа файла – файл с исходным кодом на C:

Добавляем созданный файл в проект:

Пишем код программы в созданном файле:

#include static void delay(uint32_t t) //процедура задержки
{ while(t–) {}
} int main(void)
{ GPIOB->DDR |= (1 CR1 |= (1 ODR |= (1 ODR |= (1 ODR &= ~(1 DDR |= (1 ODR |= (1 ODR |= (1 ODR &= ~(1 “Запустить задачу…”)
можно запустить: компиляцию

прошивку

Полезные примеры кода

подключение заголовочных файлов

#include
#include
#include

задержки

//константы для CLK #define CLK_DIVR (*(volatile uint8_t *)0x50c6)

static void delay(uint32_t t)
{ while(t–) {}
}

delay(44000000UL); //задержка на одну минуту

работа с UART

//константы для CLK
#define CLK_DIVR (*(volatile uint8_t *)0x50c6)
#define CLK_PCKENR1 (*(volatile uint8_t *)0x50c7)
//константы для UART
#define UART1_SR (*(volatile uint8_t *)0x5230)
#define UART1_DR (*(volatile uint8_t *)0x5231)
#define UART1_BRR1 (*(volatile uint8_t *)0x5232)
#define UART1_BRR2 (*(volatile uint8_t *)0x5233)
#define UART1_CR2 (*(volatile uint8_t *)0x5235)
#define UART1_CR3 (*(volatile uint8_t *)0x5236)
#define UART_CR2_TEN (1

Источник: https://acdc.foxylab.com/stm8

Настройка UART на микроконтроллере STM8

В последнее время на рынке электроники достойное место занимают 8-битные микроконтроллеры семейства STM8 фирмы STMicroelectronics, которые имеют относительно недорогую стоимость по сравнению с 8-битными микроконтроллерами других фирм, таких как Atmel или Microchip. Среди радиолюбителей большой популярностью пользуется очень дешевый микроконтроллер STM8S003F, который при цене 0.5$ (на лето 2014 года) имеет следующие основные характеристики:

  • Частота тактирования ядра до 16 МГц;
  • 1 К RAM памяти;
  • 8 К Flash памяти;
  • Напряжение питания от 2.95 до 5.5 В;
  • Управление питанием;
  • Два 16-битных таймера (с поддержкой ШИМ, захват, сравнения) и один 8-битный таймер, а также сторожевой таймер Watchdogtimer;
  • Модуль UART;
  • Модуль SPI;
  • Модуль I2C;
  • 5 каналов 10-битного АЦП;

Сегодня я хочу рассказать о том, как настроить UARTв асинхронном режиме передачи данных на микроконтроллере STM8S003F. Данный микроконтроллер содержит всего один модуль UART1 (в более навороченных микроконтроллерах есть еще UART2, UART3 и UART4).

Здесь я опишу лишь основные моменты работы с UART1, более подробную информацию можно получить из даташитов: STM8S003Fи Reference manual

Итак, UART1 обладает следующими основными свойствами:

  • Полнодуплексный последовательный интерфейс с максимальной скоростью передачи 1Мбит/с;
  • Эмуляция SPI;
  • Настраиваемая скорость передачи данных;
  • Эмуляция Smartcard;
  • IrDA кодер/декодер;
  • Поддержка протокола LIN;
  • Однопроводной полудуплексный режим;

Прежде чем приступить к написанию подпрограмм для работы с UART1, нужно сказать пару слов о системе тактирования микроконтроллера STM8S. Для наглядности приведу функциональную схему контроллера тактирования (Clock control CLK):

Процессор (CPU) может тактироваться от трех источников: внешнего высокоскоростного кварцевого резонатора, внутреннего высокоскоростного HSI RC-генератора 16МГц и внутреннего низкоскоростного LSIRC-генератора на 128 кГц. HSI RC-генератор имеет также делитель.

По умолчанию в качестве источника тактирования выбирается именно этот генератор, с делением частоты на 8. Т.е. по умолчанию получаем частоту 2МГц. При необходимости программно можно переключиться на другой источник тактирования.

Далее сигнал тактирования поступает на делитель CPUDIV, который и определяет частоту тактирования CPU. В контроллере STM8 есть такая важная особенность: можно разрешить или запретить тактирование каждого периферийного модуля (UART, I2C и т.д.).

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

Более подробно о системе тактирование можно почитать в «Reference manual» или есть хорошая статья на русском языке, только для другого семейства STM8L, но Вы найдете много схожего в ней. Вот ссылка на статью: 8L-Курс, Часть 4 – Тактирование

Ну что, приступим. Будем считать, что CPU тактируется от внутреннего RC-генератора на частоте 2МГц.

Первым делом нужно проинициализировать UART1.

1.     Задаем скорость приема/передатчика (Baud rategenerator). Возьмем для примера скорость 9600 бит/с. Baudrateгенератор настраивается через регистры UART_BRR1 и UART_BRR2.

UART_BRR1:

UART_BRR2:

Значение UART_DIV расчитывается по формуле: UART_DIV= Fmaster/Baud rate

2.     Настраиваем регистры управления UART_CR1 … UART_CR5. Так как мы используем UART1 в режиме асинхронного приемопередатчика, нам понадобится настроить только регистры UART_CR1 … UART_CR3. В остальных регистрах настраивается протокол LIN, IrDAи т.д., которые нас сейчас не интересуют.

UART_CR1:

  • R8 – сюда записывается 9-й принятый бит, если прием осуществляется в 9-битном режиме.
  • T8 – сюда записывается 9-й передаваемый бит, если передача осуществляется в 9-битном режиме.
  • UARTD– отключает UART по завершению передачи текущего байта (для снижения энергопотребления)
  • М – задается длина данных 8 бит (М=0) или 9 бит (М=1)
  • WAKE– определяет метод выхода контроллера из спящего режима
  • PCEN – контроль четности
  • PS– выбор четности
  • PIEN– настройка прерываний при ошибке четности

UART_CR2:

  • TIEN– настройка прерываний по очистке передающего регистра
  • TСIEN– настройка прерываний по завершению передачи
  • RIEN– настройка прерываний по заполнению приемного регистра
  • ILIEN– настройка прерываний по освобождению линии передачи
  • TEN – включение передатчика
  • REN –включение приемника
  • RWU– настройка спящего режима приемника
  • SBK– отправлять “break” или нет (используется например в RS-485)

UART_CR3:

Здесь включается режим LIN и настраивается сигнал синхронизации CLK, если используется синхронный режим передачи. В данном регистре нас интересует только поле STOP. Здесь задается количество стоповых битов.

Теперь приведу пример функции инициализации:

//******************************************************************************
// Инициализация UART1 STM8S003
//******************************************************************************
void uart_init(unsigned long baud_rate, unsigned long f_master){ //Значение регистра BRR unsigned long brr; //Настраиваем TX на выход, а RX на вход PD_DDR_bit.DDR5 = 1; //TX PD_DDR_bit.DDR6 = 0; //RX //RX – плавающий вход PD_CR1_bit.C16 = 0; //Отключает внешние прерывания для RX PD_CR2_bit.C26 = 0; //Настройка скорости передачи brr = f_master/baud_rate; UART1_BRR2 = brr & 0x000F; UART1_BRR2 |= brr >> 12; UART1_BRR1 = (brr >> 4) & 0x00FF; //Четность отключена UART1_CR1_PIEN = 0; //Контроль четности отключен UART1_CR1_PCEN = 0; //8-битный режим UART1_CR1_M = 0; //Включить UART UART1_CR1_UART0 = 0; //Запретить прерывание по опустошению передающ. регистра UART1_CR2_TIEN = 0; //Запретить прерывание по завершению передачи UART1_CR2_TCIEN = 0; //Запретить прерывание по заполнению приемного регистра UART1_CR2_RIEN = 0; //Запретить прерывание по освобождению линии UART1_CR2_ILIEN = 0; //Передатчик включить UART1_CR2_TEN = 1; //Приемник включить UART1_CR2_REN = 1; //Не посылать break-символ UART1_CR2_SBK = 0; //Один стоп-бит UART1_CR3_STOP = 0;
}

Теперь необходимо написать функции приема и отправки данных

Модуль UART1 содержит также регистр состояний USART_SR:

  • TXE– передающий регистр пуст (готов для записи следующих данных)
  • TC – передача завершена
  • RXNE– приемный регистр не пустой (приняты данные)
  • IDLE – линия передачи свободна
  • OR– ошибка перезаписи (возникает, когда в приемном регистре есть данные, а были приняты новые данные до чтения старых)
  • NF – флаг помехи
  • PE – ошибка четности

Отправляя и принимая данные через UARTнеобходимо контролировать биты данного регистра.

Для того, чтобы начать передачу данных, необходимо убедиться, что передающий регистр свободен (TXE=1) и записать данные в регистр UART_DR.

Для того, чтобы принять байт данных, нужно дождаться, пока данные попадут в приемный регистр (установится RXNE=1) и сразу же прочитать их через регистр UART_DR.

Обе операции записи и чтения можно организовать на прерываниях и без них.

Рассмотрим пример фукций приема и передачи без прерываний:

// Отправка байта
void uart_tx_byte(unsigned char data){ while(!UART1_SR_TXE); UART1_DR = data;
} // Отправка массива данных
void uart_tx_data(unsigned char * data, unsigned char len){ while(len–){ uart_tx_byte(*data++); }
}
// Прием байта
unsigned char uart_rx_byte(){ unsigned char data; while(!UART1_SR_RXNE); data = UART1_DR; return data;
} // Прием массива данных
void uart_rx_data(unsigned char * data, unsigned char len){ while(len–){ *data++ = uart_rx_byte(); }
}

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

В этом случае нужно разрешить прерывание по приему, установив бит RIEN=1 в регистре UART_CR2. В функции uart_init добавляем  строчку UART1_CR2_RIEN=1  вместо UART1_CR2_RIEN=0  

Кроме этого необходимо в начале программы глобально разрешить прерывания. В IAR Embedded Workbeanch это делается функцией __enable_interrupt(), а по сути включение прерывание осуществляется ассемблерной командой RIM.

После разрешения прерываний нужно описать функцию-обработчик (которая заменит функцию uart1_rx из прошлого примера).

// Обработчик прерываний по приему UART
#pragma vector=UART1_R_RXNE_vector __interrupt void uart_rx_interrupt(void){ unsigned char data; data = UART1_DR; //Отсылаем принятый байт обратно uart_tx_byte(data);
}

Данная функция-обработчик отсылает обратно принятый байт.

UART1_R_RXNE_vector– это адрес вектора прерывания. Его значение можно найти в даташите.

Ну а вот так может выглядеть основная подпрограмма:

int main( void )
{ #ifndef UART_INT_ENABLE unsigned char data;
#endif uart_init(UART_BAUD_RATE, F_MASTER); uart_tx_data(“Hello, world!
“, 15); #ifdef UART_INT_ENABLE __enable_interrupt();
#endif while(1){ #ifndef UART_INT_ENABLE uart_rx_data(&data, 1); uart_tx_data(&data, 1);
#endif }
}

Прикрепляю проект с исходными файлами в IAR STM8.

Источник: http://hamlab.net/mcu/stm8/uart.html

STM32 ШИМ

Генерация ШИМ у STM32 осуществляется с помощью таймеров, про них в документации написано много, но ШИМ оказалось настроить достаточно просто. Генерировать ШИМ будем с помощью 4-го канала первого таймера.

Первым делом надо найти какой вывод отвечает за 4-й канал первого таймера, для этого открываем Technical DataSheet на свой МК, у меня STM32F103VET6, и находим альтернативной функцией какого вывода является TIM1_CH4.

Для использования таймера вывод надо настроить как push-pull.Давайте рассмотрим регистры, которые нам понадобятся.

TIMx_PSC – входной предварительный делитель частоты, его значение можно рассчитать по формуле

F = fCK_PSC / (PSC[15:0] + 1)

  • F – частота с которой тактируется таймер
  • fCK_PSC — частота таймера до делителя
  • PSC[15:0] — значение регистра PSC

TIMx_ARR – регистр автоматической перезагрузки, счётчик считает от 0 до TIMx_ARR, или наоборот в зависимости от направления счёта, изменяя это значение, мы изменяем частоту ШИМ.

TIMx_CCRy[x – номер таймера, y – номер канала] – определяет коэффициент заполнения ШИМ. То есть, если в ARR мы запишем 1000, а в CCRy 300, то коэффициент заполнения при положительном активном уровне и прямом ШИМ будет равен 0.3 или 30%.

TIMx_CNT – счётный 16-битный регистр, изменяет своё значение на ±1 с приходом каждого импульса, в зависимости от направления счёта.

TIMx_CR1 ->DIR – регистр направления счёта, при установке '0' счётчик считает вверх, при установки '1' – вниз. Когда счётчик сконфигурирован в режиме выравнивания по центру, бит доступен только для чтения.

TIMx_CCER->CCyE[x – номер таймера, y – номер канала] – установка '1' в этот бит разрешает использовать соответствующий канал таймера как выход, в том числе и для генерации ШИМ.

TIMx_BDTR->MOE – установка единицы в этот бит разрешает использовать выводы таймера как выходы.

Биты OCyM[2:0], где y — номер канала, в регистре TIMx_CCMR позволяют выбрать режим ШИМ, прямой или инверсный.

Надо сказать, что первый и второй канал настраиваются в регистре CCMR1, а третий и четвёртый в регистре CCMR2.

TIMx_CCER->CCyP[x – номер таймера, y – номер канала] – этот бит позволяет выбирать каким будет активный уровень, '0' – высокий, '1' – низкий.

TIMx_CR1 ->CMS[1:0] – позволяет выбрать режим выравнивания по фронту или по центру, что аналогично Fast PWM и Phase Correct PWM у AVR.

  • 00: Режим выравнивания по фронту. Счетчик считает вверх или вниз в зависимости от бита направления(DIR).
  • 01: Режим 1 выравнивания по центру. Счетчик считает вверх и вниз. Флаги прерывания устанавливаются от каналов настроенных на выход(CCxS=00 в TIMx_CCMRx), только тогда, когда счетчик считает вниз.
  • 10: Режим 2 выравнивания по центру. Счетчик считает вверх и вниз. Флаги прерывания устанавливаются от каналов настроенных на выход(CCxS=00 в TIMx_CCMRx), только тогда, когда счетчик считает вверх.
  • 11: Режим 3 выравнивания по центру. Счетчик считает вверх и вниз. Флаги прерывания устанавливаются от каналов настроенных на выход(CCxS=00 в TIMx_CCMRx), когда счетчик считает вверх и вниз.

TIMx_CRx->CEN – установка '1' в этот бит, запускает счётчик.

Давайте напишем код для генерации ШИМ с выравниванием по фронту.

#include “stm32f10x.h” int main (void) { // Тактирование GPIOA , TIM1, альтернативных функций порта RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_AFIOEN; //PA11 push-pull GPIOA->CRH &= ~GPIO_CRH_CNF11; GPIOA->CRH |= GPIO_CRH_CNF11_1; GPIOA->CRH &= ~GPIO_CRH_MODE11; GPIOA->CRH |= GPIO_CRH_MODE11_1; //делитель TIM1->PSC = 72; //значение перезагрузки TIM1->ARR = 1000; //коэф. заполнения TIM1->CCR4 = 300; //настроим на выход канал 4, активный уровень низкий TIM1->CCER |= TIM_CCER_CC4E | TIM_CCER_CC4P; //разрешим использовать выводы таймера как выходы TIM1->BDTR |= TIM_BDTR_MOE; //PWM mode 1, прямой ШИМ 4 канал TIM1->CCMR2 = TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1; //если надо настроить первый канал, это можно сделать так //TIM1->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; //считаем вверх TIM1->CR1 &= ~TIM_CR1_DIR; //выравнивание по фронту, Fast PWM TIM1->CR1 &= ~TIM_CR1_CMS; //включаем счётчик TIM1->CR1 |= TIM_CR1_CEN; } Смотрим, что получилось.Изменяем активный уровень, на высокий.
Изменяем прямой ШИМ на инверсный.

Всё стало как было раньше, ничего сложного.

Источник: https://hubstub.ru/stm32/81-stm32-shim.html

STM32. Уроки по программированию STM32F4. Урок № 6. Работа с таймерами TIM7 и TIM1.

09 March 16 г. Автор – admin

Вместо предисловия.

Таймер – одна из самых полезных вещей в микроконтроллере и основная его задача – отсчитывание точных интервалов времени. На отладочной плате STM32F4 Discovery установлен микроконтроллер STM32F407VG, который имеет следующие таймеры:

  • Basic timers (TIM6, TIM7) – самый простой таймер, который умеет только генерировать прерывания в заданный промежуток времени, но при этом очень легко настраивается и управляется.
  • General-purpose timers (TIM2-TIM5, TIM9-TIM14) – более продвинутый таймер, позволяющий генерировать ШИМ, считывать состояние ног, обрабатывать данные от энкодера и т.д.
  • Advanced-control timers (TIM1, TIM8) – самый продвинутый таймер, может использоваться как трехфазный ШИМ генератор.

Таблица сравнения таймеров МК STM32F407VGx.

Рассмотрим работу таймера на примере TIM7. Настроем его для отсчета временного интервала в 1 с. И по прерыванию от таймера будет инвертировать выход. Для визуализации – выход будет подключен к синему светодиоду, установленному на плате.

 Предисловие.

Код, который использовался в прошлых уроках, останется практически без изменений. Управление синим светодиодом организуем по прерыванию таймера TIM7. Начнем с изменений в КУБе. Откроем проект из прошлого урока. Внесем изменения, касающиеся таймера TIM7.

Включаем таймер TIM7.

Не забываем, что данный таймер тактируется от шины ABP1 Timer Clock. Так, что для настройки таймера, необходимо знать частоту тактирования данной шины. Посмотрим на вкладку Clock Configuration.

Тактирование шины ABP1.

Настройки из прошлых уроков говорят нам, что тактирование таймера TIM7 будет осуществляться с частотой в 84 МГц. Максимально возможное.

Далее произведем настройки таймера для генерации прерывания с частотой в 1 с.

Настройки прескалера и периода таймера TIM7.

Для получения времени между прерываниями в 1 секунду, необходимо установить прескалер в 8400, делитель учитывается как величина прескалера +1, следовательно, значение прескалера 8400-1=8399.

С прескалером  получим тактовую частоту таймера в 84 МГц/8400 = 10000 Гц. При установке периода 10000, у нас прерывание будут возникать раз в 1 секунду.

Включаем прерывания от таймера TIM7, установкой соответствующей галочки.

Здесь все просто и прозрачно. Настроек всего ничего.

Исходный код полученной инициализации, а также обработчик прерывания представлен ниже.

TIM_HandleTypeDef htim7; /* TIM7 init function */ void MX_TIM7_Init(void) { TIM_MasterConfigTypeDef sMasterConfig; htim7.Instance = TIM7; htim7.Init.Prescaler = 8399; htim7.Init.CounterMode = TIM_COUNTERMODE_UP; htim7.Init.Period = 10000; HAL_TIM_Base_Init(&htim7); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig); } /** * @brief This function handles TIM7 global interrupt. */ void TIM7_IRQHandler(void) { /* USER CODE BEGIN TIM7_IRQn 0 */ /* USER CODE END TIM7_IRQn 0 */ HAL_TIM_IRQHandler(&htim7); /* USER CODE BEGIN TIM7_IRQn 1 */ HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15); /* USER CODE END TIM7_IRQn 1 */ }

После этого достаточно, запустить таймер TIM7.

/* * Для урока №6. * Запуск таймера TIM7 * */ HAL_TIM_Base_Start_IT(&htim7);

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

Advanced Control таймер TIM1. 

Рассмотрим таймер с богатыми возможностями. TIM1 может не только считать и вызывать прерывание по переполнению, но и имеет множество аппаратных возможностей по генерации ШИМ сигнала, режима сравнения, режим захвата и т.д. В данной статье рассмотрим функцию ШИМ.

ШИМ- Широтно-импульсная модуляция (ШИМ, англ. pulse-width modulation (PWM)) — процесс управления мощностью, подводимой к нагрузке, путём изменения скважности импульсов, при постоянной частоте.

Применение данного режима просто масса. От управления мощности нагрузки, до формирования звуковых рядов. Использование в качестве ЦАПа и т.д.

Начнем с простого. Настроим таймер на генерацию ШИМ сигнала с постоянной частотой и скважностью.

Настройка таймера TIM1 в режиме ШИМ первого канала.

В КУБе настроим источник тактирования таймера, и установим режим ШИМ для первого канала.

Тактирование от внутренней шины APB2 с максимальной частотой для таймеров в 168 МГц.

В настройках, укажем предделитель и период таймера. Для канала ШИМ укажем скважность, период заполнения.

Генерация ШИМ с частотой 20 кГц. Следовательно 168 Мгц/8 = 21 МГц. 8 – предделитель таймера TIM1. 21 МГц/20 кГц = 1050. Это период таймера. Следовательно, если мы будем задавать  период ШИМ от 0 до 1050, то этим и будет регулироваться скважность ШИМ. Получаем полноценный 10-ти битный ШИМ. Для многих нужд, этой точности более чем достаточно.

Настройки таймера TIM1.

В данном случае скважность ШИМ будет 2. То есть половина периода ШИМ у нас будет высокий уровень на выходе, а половина – низкий.

Сгенерим проект. Посмотрим на код.

/* TIM1 init function */ void MX_TIM1_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig; TIM_OC_InitTypeDef sConfigOC; htim1.Instance = TIM1; htim1.Init.Prescaler = 7; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 1050; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_Base_Init(&htim1); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig); HAL_TIM_PWM_Init(&htim1); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig); sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = 0; sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 525; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); }

Для запуска ШИМ необходимо вызвать следующую функцию.

HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);

Результатом является генерация ШИМ на выходе со скважностью 2.

Сигнал ШИМ. Картинка кликабельна.

Менять скважность во время работы ШИМ, можно непосредственно записав значение в регистр таймера. Библиотечную функцию в HAL для этого я не нашел.

Для задержки в 10 мс, между записи нового значения в регистр инициализируем программный таймер.

OnSwTimer(&TIM[5],SWTIMER_MODE_CYCLE,10); TIM[5].Off=0; TIM[5].On=1;

Код получился такой.

//————————————————————– /* * Урок №6. * */ if (GetStatusSwTimer(&TIM[5])){ htim1.Instance->CCR1=i; if (iCCR1=i; if (i

Источник: https://druid.su/rubrik-stm32_program-44.html

stm8l adc определение напряжения питания мк

Контроллеры STM8L15х имеют на борту 12-и разрядный АЦП, который может работать в нескольких режимах, и поддерживает работу с контроллером DMA, что позволяет оцифровать и сложить в память кучу данных без участия ядра.

Здесь я попытался собрать как можно больше информации об АЦП в STM8, чтобы не пришлось бегать по другим статьям в поисках кода для настройки таймера, или, например, DMA.

Вот, что описано в статье:— Настройка АЦП— Выполнение преобразований в разных режимах— Настройка внешнего триггера для запуска преобразования— Настройка таймера для работы совместно с АЦП— Использование встроенного датчика температуры— Настройка контроллера DMA для работы вместе с АЦП

— Использование Analog Watchdog

Семейство STM8S не рассматриваем — там все сильно по-другому. А в STM8L101 АЦП вообще нет.

Для начала, ТТХ преобразователя:— Программируемое разрешение: 6, 8, 10 или 12бит (снижение разрешения позволяет увеличить скорость работы)— До 28 внешних каналов. Но в том МК, что стоит на STM8L-Discovery, их «всего» 25.— Плюс еще два внутренних канала: термодатчик и опорное напряжение.

— Два основных режима работы:1) Одиночное преобразование2) Последовательное чтение нескольких каналов, с переброской данных в буфер силами DMA. При этом МК не отвлекается на переключение каналов или запись данных в буфер.— Внешние и внутренние триггеры.

— В качестве опорного напряжения выступает либо пин Vdda, либо ножка Vref+ (в таком случае напряжение на ней должно быть > 2.4V). В дискавери VREF наглухо прибита к питанию.

Настройка

Перед началом работы неплохо-бы разобраться, как его включить. Как и с другой периферией, на АЦП надо подать тактирование. За это отвечает самый первый бит в регистре CLK_PCKENR2:

CLK->PCKENR2 |= CLK_PCKENR2_ADC1; //Подаем тактирование на АЦП

Изначально АЦП находится в спящем режиме. Чтобы его разбудить, надо поднять бит ADON в регистре ADC_CR1. Но сразу-же начинать преобразование нельзя — нужно подождать примерно 3мкс, пока преобразователь проснется.

Если АЦП будет простаивать некоторое время без дела, то он сам уйдет в спящий режим. Это «некоторое время» — Tidle — обычно равно 1 сек, но может резко уменьшиться с ростом температуры.

После того, как АЦП проснулся, надо выбрать каналы, с которых мы хотим оцифровать сигнал. Можно выбрать сразу несколько каналов через регистры ADC_SQR1 — ADC_SQR4. Биты СHSEL_Sx в них отвечают за каждый канал.

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

Биты CHSEL_STS и CHSEL_SVREFINT отвечают за выбор термодатчика и опорного напряжения соответственно.
 

К примеру, если выставить биты вот так, то будет обработана последовательность каналов: 0, 2, 6, 8, 10, 14, 18.

В регистрах TRIGR1TRIGR4 (которые тоже увязаны в массив, поэтому нумерация с нуля) каждый бит отвечает за отключение триггера Шмитта на соответствующем канале. При записи в бит 1, триггер отключается и пин нельзя использовать как цифровой вход. Но если их не отключать — точность будет на порядок хуже.

К примеру, при замере напряжения с потенциометра при включенном триггере результаты могли отличаться на несколько десятков LSB. При отключении триггера это различие уменьшалось до пары LSB.

Обрати внимание, что в регистре ADC_SQR1 скрывается бит DMAOFF, который отвечает за работу АЦП совместно с DMA. По умолчанию он сброшен, а значит, DMA контроллер включен. Если DMA тебе не нужен, то в этот бит надо записать 1.

Биты SMP1[2:0] в регистре ADC_CR2 управляют периодом выборки АЦП (sampling time) для первых 24 каналов. Для остальных (в дискавери это последний внешний канал и два внутренних) есть биты SMP2[2:0] в ADC_CR3.

Биты SMPx[2:0] могут принимать такие значения:
000 — Время выборки = 4 такта АЦП
001 — 9 тактов
010 — 16 тактов
011 — 24 тактов
100 — 48 тактов
101 — 96 тактов
110 — 192 такта
111 — 384 такта

От этого параметра по большей части зависит время преобразования. Но не спеши ставить минимальное значиние: если на входе висит что-то с высоким сопротивлением, то надо установить большое время выборки, чтобы конденсаторы успели зарядиться.

Подробнее про ошибку, возникающую из-за внешнего сопротивления, можно прочитать в аппноуте под названием “A/D converter on STM8L devices:
description and precision improvement techniques” в разделе «External resistance design error»

Если нет необходимости использовать все 12 бит, то разрешение АЦП можно снизить. Для этого существуют биты RES[1:0] в регистре ADC_CR1:
00 — 12 бит
01 — 10 бит
10 — 8 бит
11 — 6 бит

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

Tconv = (ts+n)/fADC

Из этого легко получить максимальную скорость работы АЦП:

ADCSpeed = fADC/(ts+n)

В этих формулах
fADC — Частота тактового сигнала на АЦП
ts — время выборки (в тактах)
n — разрешение АЦП

Например, при частоте тактирования в 8МГц, разрешении в 10 бит и минимальном времени выборки (4 такта) мы можем получить скорость 571.4 кГц. Если по каким-то причинам надо уменьшить скорость, то можно установить бит PRESC в регистре ADC_CR2. От этого преобразователь начнет работать в два раза медленнее.

Для примера вот простейшая инициализация АЦП:

CLK->PCKENR2 |= CLK_PCKENR2_ADC1; //Тактирование ADC1->CR1 |= ADC_CR1_ADON; //Пинаем АЦП, чтобы он проснулся ADC1->SQR[0] |= ADC_SQR1_DMAOFF; //Отключаем DMA
ADC1->SQR[3] |= 1SR && ADC_SR_EOC) == 0); //Ждем, пока выполнится преобразование //Забираем результат
result = ADC1->DRH CR1 |= GPIO_Pin_6; //Подтягивающий резистор на пин A6 (не обязательно). у меня там висела кнопка. CLK->PCKENR2 |= CLK_PCKENR2_ADC1; //Тактирование ADC1->CR1 |= ADC_CR1_ADON; //Будим АЦП
ADC1->CR2 |= (18; //Записываем сначала старший байт
  TIM2->ARRL = (uint8_t)(46875); //Потом младший
  TIM2->CR1 |= TIM_CR1_URS; //Настраиваем источник события update
  TIM2->CR2 |= (2EGR |= TIM_EGR_UG;  //Генерим событие update, чтобы обновился предделитель
  TIM2->CR1 |= TIM_CR1_CEN; //Запускаем

Думаю пояснения тут излишни. Все самое важное указано в комментах к коду.

Если все-же что-то непонятно, то есть примерчик в котором таймер подает сигнал на АЦП каждые 500мс. В прерывании по завершению преобразоывания переключается синий светодиод — чтобы было видно как часто срабатывает АЦП.
 

Использование встроенного термодатчика

В STM8L есть свой датчик температуры, подключенный к каналу ADC_TS в АЦП. Особо высокой точностью он, конечно, не отличается, но измерения типа «тепло/холодно» выполнять может.

Перед тем, как читать показания датчика, его надо запустить (изначально выключен в целях экономии электричества). Для управления питанием датчика служит бит TSON в регистре ADC_TRIG1. После того, как он будет установлен, надо подождать 10 микросекунд, чтобы датчик успел включиться.

Время выборки (для термодатчика оно устанавливается битами SMTP2[2:0]) советуют установить больше 10 микросекунд. Если поставить меньше, то высокое выходное сопротивление датчика будет вносить искажения в результат. А может и не будет, но рекомендации соблюдать надо.

Кроме запуска, работа с датчиком ничем не отличается от работы с любым другим каналом.

Код, запускающий термодатчик, и считывающий его показания в переменную result:
 

CLK->PCKENR2 |= CLK_PCKENR2_ADC1; //Тактирование ADC1->CR1 |= ADC_CR1_ADON;
ADC1->TRIGR[0] |= ADC_TRIGR1_TSON; //Подаем питание на датчик ADC1->CR2 |= 3; //Sampling time = 12uS
ADC1->SQR[0] |= ADC_SQR1_DMAOFF | (1SR && ADC_SR_EOC) == 0); //Ждем, пока выполнится преобразование //Забираем результат
result = ADC1->DRH «Register map» -> «Factory conversion registers». Например для МК на STM8L-Discovery этот адрес 0x4910. В заголовочных файлах этот адрес не упоминается, поэтому если мы хотим прочитать значение опорного напряжения, то нужно прописать адрес. Примерно так:

#define Factory_VREFINT 0x4910

И далее, читаем как обычный регистр:

uint16_t ref;
… ref = 0x600 + Factory_VREFINT;

Старший байт этого значения всегда равен 0x06, а по адресу VREFINT_Factory_CONV находится младший.

Для того, чтобы опорное напряжение можно было замерить через АЦП, нужно установить бит VREFINTON в регистре ADC_TRIGR1.

После этого можно измерять напряжение через канал VREFINT (Пятый бит в ADC_SQR1).

Используя значение опорного напряжения можно скорректировать результат преобразования:

В этой формуле
Din — измеренное напряжение
Vrefint — значение опорного напряжения в вольтах. Взятое из даташита или измеренное ранее.
Drefint — результат замера опорного напряжения.

Заменив в этой формуле Din на максимальное значение для данного разрешения (1023 для 10 бит, 4095 для 12 бит …) мы получим в результате значение опорного напряжения АЦП. А если оно подключено к пину Avcc, то таким образом можно довольно точно определить напряжение питания.

Это может быть полезно в случае устройства, которое питается от батарей безо всяких преобразователей. Или, если устройство может питаться от нескольких источников с разным напряжением — можно определять от какого источника девайс питается в данный момент.
 

Использование DMA

Часто возникает необходимость собрать кучу выборок с АЦП в оперативку. Для этого можно использовать контроллер DMA. В отличие от ручной переброски данных в RAM, тут от нас требуется только настроить DMA правильным образом и запустить АЦП.

Вся работа DMA сводится к тому, что при завершении преобразования он забирает данные из регистров ADC_DRH и ADC_DRL, записывает их в буфер по указаному адресу, и инкрементирует этот адрес. При этом контроллер DMA отсчитывает количество байт, которые уже записаны. Как только записалось нужное количество — происходит прерывание.

Нам понадобится буфер, куда DMA будет складывать данные:
 

uint16_t recv_array[4];

Теперь настраиваем DMA:
 

CLK->PCKENR2 |= CLK_PCKENR2_DMA1; //Подаем тактирование   DMA1_Channel0->CNBTR = sizeof(recv_array)-1; //Размер буфера
    DMA1_Channel0->CPARH = (ADC1_BASE+4)>>8; //Адрес регистра АЦП (старший байт)
  DMA1_Channel0->CPARL = (uint8_t)(ADC1_BASE+4); //Младший
    DMA1_Channel0->CM0ARH = (uint8_t)((uint16_t)recv_array>>8); //Адрес буфера
  DMA1_Channel0->CM0ARL = (uint8_t)recv_array;  
    DMA1_Channel0->CSPR |= DMA_CSPR_16BM;  //Режим работы с 16и битными числами.
  DMA1_Channel0->CCR |= DMA_CCR_IDM | DMA_CCR_CE | DMA_CCR_TCIE; //Включаем канал и разрешаем прерывание
  DMA1->GCSR |= DMA_GCSR_GE; //Включаем DMA

После запуска АЦП, DMA будет складывать результаты преобразования в массив recv_array. Когда он заполнится — сработает прерывание Transaction Complete. Если прерывание не нужно, то бит DMA_CCR_TCIE должен быть сброшен.

Если разрядность АЦП 8 или 6 бит (то-есть результат помещается в регистр ADC_DRL), то строчку:
 

DMA1_Channel0->CSPR |= DMA_CSPR_16BM;  //Режим работы с 16и битными числами.

Из процедуры настройки можно убрать. А при указании адреса регистра,
 

DMA1_Channel0->CPARH = (ADC1_BASE+4)>>8; //Адрес регистра АЦП (старший байт)
  DMA1_Channel0->CPARL = (uint8_t)(ADC1_BASE+4); //Младший

цифру 4 заменить на 5. Тогда DMA будет считывать 1 байт из регистра ADC_DRL (который имеет смещение 5 относительно базового адреса АЦП) и записывать его в массив.

Еще можно установить бит CIRC в регистре CCR. В этом случае при заполнении буфера DMA не остановится, а начнет заполнять его с начала, перезаписывая старые данные
 

DMA1_Channel0->CCR |= DMA_CCR_ARM; //ARM – Auto-Reload mode

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

Надо отметить, что если включен режим непрерывного преобразования (бит CONT), но при этом не установлен бит CIRC, то при заполнении буфера DMA остановится, но АЦП продолжит работать. Данные при этом никуда сохраняться не будут.

В этом примере при помощи DMA данные с 4 каналов (1,2,3,4) складываются в массив в оперативной памяти.

Каналы соответствуют следующим пинам:1 — A52 — A43 — C74 — C4

Analog Watchdog

АЦП имеет одну полезную фичу — мониторинг напряжения на каком-либо канале.Работает она так:

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

Если результат больше верхнего порога или меньше нижнего, то поднимается флаг AWD в регистре ADC_SR. Можно установить бит AWDIE в ADC_CR1, тогда analog watchdog сможет вызывать прерывание.

Флаг AWD нужно опускать вручную, записав в него 0. Это создает проблему: момент, когда напряжение выходит за установленные рамки мы отловить можем, а вот вернулось-ли оно обратно в эти рамки придется проверять вручную.

Канал, который будет отслеживать analog watchdog выбирается через биты CHSEL[4:0] в регистре ADC_CR3. Значение 0 соответствует нулевому каналу, 1 — первому, и т.д. Через регистры ADC_HTRH и ADC_HTRL настраивается верхний порог, а через ADC_LTRH и ADC_LTRL — нижний.

Значения порогов записываются с учетом текущего разрешения АЦП. Например 1/4 опорного напряжения это 64 для разрешения 8 бит, или 1024 для разрешения 12 бит.

Настройку порогов и выбор канала нужно проводить до запуска АЦП.

Вот таким образом можно настроить Analog Watchdog (предполагается, что АЦП уже настроен):
 

ADC1->CR1 |= ADC_CR1_AWDIE; //Разрешаем прерывание AWD //Устанавливаем верхний (4000) и нижний (100) пороги
#define AWD_low_th 100
#define AWD_high_th 4000 ADC1->HTRH = AWD_high_th>>8;
ADC1->HTRL = (uint8_t)(AWD_high_th); ADC1->LTRH = AWD_low_th>>8;
ADC1->LTRL = (uint8_t)(AWD_low_th); ADC1->CR3 &= ~ADC_CR3_CHSEL; //Сбрасываем канал
ADC1->CR3 |= 2; // и выбираем канал №2 для отслеживания

Теперь можно либо мониторить бит AWD в ADC_SR (если прерывание не используется),

if ((ADC1->SR & ADC_SR_AWD) != 0)  {
  . . .
 }

либо ждать прерывание. В его обработчике нужно будет сбросить бит AWD, записав в него 0:
 

INTERRUPT_HANDLER(ADC_IRQ, 18)
{
 if ((ADC1->SR & ADC_SR_AWD) != 0)  {
   . . .
   ADC1->SR &= ~ADC_SR_AWD;  
 }
}

На этом всё. Работа с АЦП во всех возможных режимах описана, примеры для быстрого старта и «для поиграццо» есть. Остался один вопрос — в следующий раз описывать сабж так-же подробно, или достаточно короткого описания, как в статье про lcd?

Источник: http://uc.org.ru/node/96

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}