Доброго дня всем жителям и гостям Датагор.ру! Сегодня мы средствами С реализуем проект из моей статьи «Ассемблер для микроконтроллера с нуля. Часть 5. Периферия МК» , суть которого заключается в управлении двумя светодиодами:
а) жёлтый мигает с видимой для глаза частотой, задаваемой таймером.
б) на зелёный подаётся ШИМ-сигнал, скважность которого определяется величиной аналогового напряжения на входе АЦП микроконтроллера, причём период измерений напряжения задаётся тем же таймером.
Содержание статьи / Table Of Contents
• оставим без изменений структуру проекта, а также имена функций и макроопределений битов,
• С-код будем, по мере возможности, оформлять внешне похожим на приведённый в упомянутой статье, используя макросы setBit, clearBit и toggleBit,
• одни и те же фрагменты программы на двух языках будут приводиться рядом,
• выберем для обоих случаев одинаковые пины и модули МК, указанные в Таблице 1 ниже, подключив при этом светодиоды для работы в активно-высоком режиме.
Таблица 1. Модули и пины МК, используемые в проекте
Создадим папку проекта lightControl и скопируем в неё шаблонные файлы. Кроме того, создадим файлы в папке:
inc
• macro.h
• led.h
• timer.h
• adc.h
• pwm.h (только для nRF52832)
src
• led.с
• timer.с
• adc.с
• pwm.c (только для nRF52832)
• main.c
↑ Макросы для работы с битами
Пропишем в macro.h вышеуказанные макросы.Atmega8
#ifndef MACRO_H_
#define MACRO_H_
#define setBit(arg1, arg2) arg1 |= (1 << arg2)
#define clearBit(arg1, arg2) arg1 &= ~(1 << arg2)
#define toggleBit(arg1, arg2) arg1 ^= (1 << arg2)
#endif
STM32F401
#ifndef MACRO_H_
#define MACRO_H_
#define setBit(arg1, arg2) arg1 |= arg2
#define clearBit(arg1, arg2) arg1 &= ~arg2
#define toggleBit(arg1, arg2) arg1 ^= arg2
#endif
nRF52832
#ifndef MACRO_H_
#define MACRO_H_
#define setBit(arg1, arg2) arg1 |= (1UL << arg2)
#define clearBit(arg1, arg2) arg1 &= ~(1UL << arg2)
#define toggleBit(arg1, arg2) arg1 ^= (1UL << arg2)
#endif
Как видите:
1) Макросы для ATmega8 и nRF52832 отличаются лишь суффиксом UL после единицы в маске. Подробнее о необходимости такого суффикса мы говорили во второй части статьи.
2) Для STM32F401 макросы принимают в качестве аргумента arg2 не номер бита, как в случае с ATmega8 и nRF52832, а маску с единицей в этом бите. Обусловлено это тем, что во вспомогательных хэдер-файлах (avr/io.h. stm32f4xx.h, nrf.h и подключаемые ими) для ATmega8 и nRF52832 даны, среди прочего, и макроопределения номеров битов, а для STM32F401 — только макроопределения масок с единицей в заданном бите.
↑ Программа для ATmega8
↑ Порты ввода-вывода ATmega8
Оформим функции:• инициализации пинов PB1 и PB2 в качестве выходов путём установки в 1 соответствующего бита регистра DDRB,
• включения, выключения и переключения жёлтого светодиода путём записи 1, 0 и противоположного значения, соответственно, во второй бит регистра PORTB.
↑ Таймер ATmega8
На Рисунке 3 представлены выдержки из Раздела «16-bit Timer/Counter1» даташита, где красным цветом помечена полезная для нас информация.В функции инициализации таймера нам необходимо:
1. Выбрать 8-битный режим Fast PWM, для чего нужно установить в 1 биты WGM12 регистра TCCR1B и WGM10 регистра TCCR1A. Выбор разрядности в восемь бит счётчика таймера обусловлен тем, что именно столько разрядов регистра данных АЦП мы будем использовать для установки текущей скважности ШИМ-сигнала.
2. Разрешить ШИМ-сигнал на пине OC1A (PB1), т.е. на выводе управления зелёным светодиодом, записав единицу в бит COM1A1 регистра TCCR1A.
3. Записать 0 в регистр OCR1A, значение которого (от 0 по 255) определяет рабочий цикл (от 0% по 100%, соответственно) ШИМ-сигнала.
4. Разрешить прерывание таймера по переполнению счётчика, установив в 1 бит TOIE1 регистра TIMSK.
5. Включить тактирование таймера с делителем 64, записав 1 в биты CS11 и CS10 регистра TCCR1B. В этом случае мы получим период прерывания 64 * 255 / 8 МГц = 2 мс.
В обработчике же прерывания:
• запустить измерение аналогового напряжения,
• инкрементировать переменную-счётчик и при достижении ею значения 200 (т.е. каждые 2 мс * 200 = 400 мс) переключить жёлтый светодиод в противоположное состояние.
Обратите внимание, что C-варианте хэдер-файла компилятору посредством ключевого слова extern указывается на использование внешних функций adcStartConversion() и yelowLedToggle().
↑ Аналого-цифровой преобразователь ATmega8
Обратимся к данным из Раздела «Analog-to-Digital Converter» даташита, приведённым на Рисунке 6.Для работы АЦП в рамках нашего проекта достаточно следующих настроек при инициализации:
1. Выбрать канал ADC1, установив в 1 бит MUX0 регистра ADMUX.
2. Установить выравнивание по левому краю регистра данных АЦП, записав 1 в бит ADLAR регистра ADMUX. Объясняется такой выбор следующим. АЦП ATmega8 — 10-разрядный, поэтому для размещения результата измерения выделена пара сдвоенных регистров ADCH и ADCL. Преобразователь нередко шумит (особенно при дрейфе температуры) в пределах младших разрядов. Поскольку особая чувствительность АЦП нам не требуется, можно пренебречь двумя младшими разрядами, выровняв данные по левому краю и считывая после измерения лишь старший регистр пары — ADCH.
3. Разрешить прерывание АЦП по завершению измерения установкой в 1 бита ADIE регистра ADCSRA.
4. Выбрать частоту тактирования преобразователя. В нашем случае особая скорость не нужна, поэтому выберем делитель 4, записав 1 в бит ADPS1 регистра ADCSRA.
5. Включить АЦП через бит ADEN регистра ADCSRA.
Далее, поскольку режим измерения у нас — одноразовый, а не непрерывный, пропишем функцию adcStartConversion() запуска измерения, который осуществляется установкой в 1 бита ADSC регистра ADCSRA.
И последнее, что осталось — копирование в обработчике прерывания значения ADCH в регистр OCR1A таймера.
Рисунок 7. Хэдер-файл adc для ATmega8 на языках а) С и б) ассемблер
↑ Файл main.c для ATmega8
В основной функции необходимо вызвать функции инициализации GPIO, таймера и АЦП, а также глобально разрешить прерывания.↑ Программа для STM32F401
↑ Порты ввода-вывода STM32F401
Выдержки из мануала и даташита, имеющие отношение к настройке выводов управления светодиодами, вы можете найти на Рисунке 10, где красным цветом выделена информация, касающаяся обоих пинов, зелёным — PB1, а коричневым — PB2.Функция инициализации состоит из следующих шагов:
1. Включить тактирование порта В, записав 1 в бит GPIOBEN регистра RCC_AHB1ENR.
2. Настроить режим работы пинов через регистр MODER. Поскольку мы используем пины с номерами 2 и 1, необходимо записать требуемые значения в группы битов MODER2 и MODER1 указанного регистра, соответственно:
• 01 — в MODER2, устанавливая режим обычного выхода для PB2,
• 10 — в MODER1, поскольку PB1 будет исполнять альтернативную функцию.
3. Из Таблицы 8 даташита следует, что альтернативной функцией PB1 являются события канала 4 таймера TIMER3, а из Таблицы 9 мы можем узнать номер этой функции — AF02. Далее, нужно определить для данного номера функции значение группы битов AFRL1 (поскольку номер пина — 1) регистра AFRL. В нашем случае это — 0010 или единица в пятом бите регистра.
Кроме того, оформим функции включения, выключения и переключения жёлтого светодиода, что соответствует установке в 1, сбросу в 0 и изменению на противоположное значения бита ODR2 регистра ODR.
↑ Таймер STM32F401
На Рисунке 13 приведены выдержки из мануала касательно регистров и их битов, участвующих в настройке и работе таймера общего назначения, к которым относится и TIMER3.В функции инициализации таймера необходимо:
1. Включить тактирование таймера, установив в 1 бит TIM3EN регистра RCC_APB1ENR.
2. Записать в регистр PSC значение 1600 делителя.
3. Установить значение сравнения 255, записав его в регистр ARR. В совокупности с делителем это даст период прерывания в 1600 * 255 / 16 МГц = 25.5 мс. Кроме того, таким образом мы ограничим разрядность счётчика таймера 8 битами, сравняв её с таковой для АЦП.
4. Выбрать для канала 4-го таймера режим PWM1 посредством комбинации 110 битов CC4M регистра CCMR2.
5. Записать в регистр CCER начальное значение 0% для рабочего цикла ШИМ-сигнала.
6. Разрешить ШИМ-сигнал для канала 4 таймера (т.е. на пине PB1), установив в 1 бит CC4E регистра CCER.
7. Разрешить прерывание таймера локально через бит UIE регистра DIER и глобально.
8. Включить таймер, установив в 1 бит CEN регистра CR1.
А в обработчике прерывания:
а) Очистить бит UIF регистра SR.
б) Запустить измерение аналогового напряжения.
в) Инкрементировать переменную-счётчик и, если её значение равно 20 (что даст 25.5 мс * 20 = 510 мс), изменить состояние жёлтого светодиода на противоположное.
Чтобы обеспечить вызов внешних функций запуска измерения АЦП и переключения жёлтого светодиода, прототипы соответствующих функций в C-версии предваряются ключевым словом extern .
↑ Аналого-цифровой преобразователь STM32F401
Биты и регистры, ответственные за настройку пина PA1 и АЦП, приведены на Рисунке 16.Настройка включает в себя:
1. Разрешение тактирования модуля GPIOA через установку в 1 бита GPIOAEN регистра RCC_AHB1ENR.
2. Выбор для PA1 функции аналогового входа записью комбинации 11 в группу битов MODER1 регистра MODER.
3. Включение тактирования ADC1 путём установки в 1 бита ADC1EN регистра RCC_APB2ENR.
4. Выбор канала IN1 преобразователя посредством записи числа 1 в группу битов SQ1 регистра SQR3.
5. Выбор разрядности в 8 бит через запись комбинации 10 в группу битов RES регистра CR1.
6. Локальное (через бит EOCIE того же регистра CR1) и глобальное разрешение прерывания АЦП по завершению измерения.
7. Включение АЦП с помощью установки в 1 бита ADON регистра CR2.
Функция запуска измерения ограничена записью 1 в бит SWSTART регистра CR2.
В обработчике прерывания надо:
• сбросить в 0 бит EOC регистра SR.
• скопировать в регистр CCR4 таймера значение регистра данных DR АЦП.
Рисунок 17. Хэдер-файл adc для STM32F401 на языках а) С и б) ассемблер
↑ Файл main.c для STM32F401
В main() вызываются функции инициализации пинов управления светодиодами, таймера и АЦП.↑ Программа для nRF52832
↑ Порт ввода-вывода nRF52832
На Рисунке 20 приведены выдержки из спецификации с регистрами и их битам, участвующими в настройке пина P0.18 GPIO. Однако, вся дальнейшая информация верна и для P0.17.Чтобы настроить какой-либо пин как выход, достаточно записать 1 в бит DIR регистра PIN_CNF[N] (где N – номер пина), а его текущее логическое состояние определяется значением соответствующего бита регистра OUT.
↑ Таймер nRF52832
Регистры и биты, участвующие в настройке таймера представлены на Рисунке 23.Код инициализации таймера должен содержать:
1. Выбор делителя, посредством записи числа от 0 по 9 в регистр PRESCALER. В нашем случае это — 3, что обусловит частоту тактирования таймера в 16 МГц / 2^3 = 2 МГц. Тогда, учитывая, что по дефолту счётчик таймера — 16-разрядный, мы получим период прерываний в 65535 / 2 МГц = 33.8 мс.
2. Запись в регистр CC[0] значения сравнения.
3. Глобальное и локальное (через установку в 1 бита COMPARE0 регистра INTENSET) разрешение прерывания.
4. Включение счётчика таймера записью 1 в задачу TASK_START.
А обработчик прерывания:
• Сброс события EVENTS_COMPARE[0].
• Запуск измерения АЦП.
• Инкремент переменной-счётчика и, по достижению ею заданного значения 15 (иначе, каждые 33.8 мс * 15 = 491 мс), переключение жёлтого светодиода.
Заметьте, что в хэдер-файле слева присутствует ключевое слово extern, свидетельствующее о вызове внешних функций.
↑ Модуль PWM nRF52832
На Рисунке 26 вы можете увидеть регистры и биты, требуемые для настройки модуля PWM.Настроим модуль с помощью нижеследующего кода:
1. Запишем номер 18 пина управления зелёным светодиодом в группу битов PIN регистра PSEL.OUT[0]. При этом, бит CONNECT того же регистра должны быть сброшен в 0, что обеспечит соединение модуля PWM с указанным пином.
2. Выберем деление на 4, записав число 2 в регистр PRESCALER, что обусловит частоту тактирования модуля в 16 МГц / 4 = 4 МГц.
3. Учитывая, что ниже мы выберем 14-битную разрядность для АЦП, числом, соответствующим 100%-му рабочему циклу ШИМ-сигнала, будет 2^14 – 1 = 16383, что и занесём в регистр COUNTERTOP.
4. Загрузим в переменную pwmSeq начальное значение рабочего цикла.
5. Укажем адрес pwmSeq, записав его в регистр SEQ[0].PTR.
6. Установим количество циклов равным 1 через регистр SEQ[0].CNT.
7. Разрешим канал PWM0 посредством бита ENABLE одноимённого регистра.
8. Запустим генерацию ШИМ-сигнала через задачу TASKS_SEQSTART[0].
↑ Аналого-цифровой преобразователь nRF52832
Выдержки из спецификации с регистрами и их битам, имеющими отношение к настройке и работа АЦП, представлены на Рисунке 29.Функция инициализации преобразователя содержит следующие шаги:
1. Выбрать пин P0.02 (AIN0) в качестве положительного полюса через регистр CH[0].PSELP.
2. Установить 16-битное разрешение, записав число 3 в группу битов VAL регистра RESOLUTION.
3. Записать в группы битов GAIN и REFSEL регистра CH[0].CONFIG значения, обуславливающие выбор VDD в качестве ИОН, а так же приводящие его максимальное значение к разрядности АЦП.
4. Указать адрес переменной-буфера adcValue для хранения результатов измерений, записав его в регистр RESUL.PTR.
5. Установить 1 как максимальное количество измерений за раз, записав это число в регистр RESUL.MAXCNT.
6. Разрешить прерывание АЦП локально посредством регистра INTENSET и глобально.
7. Включить преобразователь записав 1 в бит ENABLE одноимённого регистра.
Функция измерения напряжения должна, запустив задачу TASKS_START, ждать события EVENTS_STARTED, а затем, обнулив последнее, запустить задачу TASKS_SAMPLE.
В обработчике прерывания необходимо:
• Сбросить флаг EVENTS_RESULTDONE.
• Скопировать значение adcValue в переменную pwmSeq из файла pwm.c.
• Перезапустить ШИМ-сигнал.
Обратите внимание на необходимость указания компилятору о привлечении внешней переменной pwmSeq посредством ключевого слова extern.
↑ Файл main.c для nRF52832
Осталось лишь вызвать прописанные нами функции инициализации портов ввода-вывода, таймера, АЦП и модуля PWM в основной функции программы.↑ Коды, исходники
🎁atmega8.zip 15.12 Kb ⇣ 13🎁nrf52832.zip 35.27 Kb ⇣ 8
🎁stm32f401.zip 34.8 Kb ⇣ 10
Спасибо за внимание!
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.