25 простых аналоговых и цифровых функций по 25 центов каждая. Взаимозаменяемость и оптимизация
Предлагаемый цикл состоит из 25 кратких заметок о том, как на основе микроконтроллеров MSP430FR2xxx производства Texas Instruments реализовать 25 наиболее популярных узлов, присутствующих на многих системных платах. В каждой заметке рассказано об основных особенностях той или иной функции, а также приведены ссылки на информационные ресурсы, содержащие примеры кода и базовые версии проектов, что позволяет начать работу практически за считанные минуты.
Рекомендации по переносу кода с микроконтроллеров MSP430FR2000 на MSP430FR2311
Поскольку стартовый набор MSP430FR2311 LaunchPad™ Development Kit на основе демонстрационной платы MSP-EXP430FR2311 со встроенным программатором-отладчиком eZ-FET дешевле аналогичного комплекта, рассчитанного на работу с микроконтроллером MSP430FR2000 и состоящего из отладочной платы MSP-TS430PW20 и программатора MSP-FET, то переход на MSP430FR2311 позволит как минимум снизить затраты на разработку и тестирование разрабатываемого приложения. Кроме этого, микроконтроллер MSP430FR2311 имеет более развитый набор периферийных модулей и больший объем всех видов памяти, что позволяет существенно расширить функциональность базовых версий предлагаемых проектов, ограниченных, в первую очередь, малым размером памяти программ микроконтроллера MSP430FR2000, равным всего 512 байт.
В этой части описаны ключевые отличия между микроконтроллерами MSP430FR2000 и MSP430FR2311 и рекомендации по модификации исходного кода прошивки, которую необходимо сделать при замене микроконтроллера.
Соответствие выводов микросхем
Модуль последовательных интерфейсов eUSCI
Модуль универсального последовательного интерфейса (Enhanced Universal Serial Communication Interface, eUSCI) в микроконтроллерах MSP430FR2000 и MSP430FR2311 подключается к разным портам ввода-вывода (рисунок 71). Таким образом, если в проекте используются последовательные интерфейсы UART или SPI, реализованные на основе модуля eUSCI, то вместо портов P1.0, P1.1, P1.2 и P1.3 в микроконтроллере MSP430FR2311 необходимо сконфигурировать соответствующим образом порты P1.4, P1.5, P1.6 и P1.7. В исходном коде это осуществляется путем удаления кода, устанавливающего бит USCIARMP_1 в регистре SYSCFG3 и замены необходимых констант в блоках инициализации:
MSP430FR2000 |
MSP430FR2311 |
|
Все варианты интерфейсов |
||
→ |
||
UART |
||
→ |
||
3-проводной SPI |
||
→ |
||
4-проводной SPI |
||
→ |
Подробная информация о работе модуляe USCI в режимах UART и SPI приведена в разделах 21 и 22 «Руководства пользователя по семействам микроконтроллеров MSP430FR4xx и MSP430FR2xx» (MSP430FR4xx and MSP430FR2xx Family User’s Guide).
Модули таймеров
Если в проекте канал B3 таймера Timer0 используется в режиме счета внешних импульсов (когда битам CCIS в регистре TB0CCTL0 присвоены значения 00b или 01b, а значение бита TBRMP в регистре SYSCFG3 установлено равным 1), то необходимо учесть, что этот аппаратный модуль в разных микроконтроллерах подключен к разным выводам (рисунок 72). В MSP430FR2000 он подключен к P2.0 и P2.1, а в MSP430FR2311 – к P1.6 и P1.7, и их необходимо настроить соответствующим образом.
Кроме этого, аналогичную операцию необходимо выполнить в случае, когда канал B3 таймера Timer0 используется в режиме генерации ШИМ-сигналов на выводах P2.0 или P2.1 (когда порты P2.0 или P2.1 настраиваются на выход, а соответствующим битам в регистре P2SEL присваивается значение 01b).
Но если в проекте для реализации данной функции принципиально использование выводов, связанных с портами P2.0 и P2.1, то при переходе на MSP430FR2311 вместо таймера Timer0 можно использовать таймер Timer1, третий канал которого (B3) может быть подключен к выводам P1.6 и P1.7 (рисунок 72). В этом случае для восстановления работоспособности приложения в нужных местах исходного кода программного обеспечения достаточно заменить комбинацию символов «TB0» на «TB1» и обработчик прерывания (если он, конечно, существует).
Для переключения функций с портов P2.0 и P2.1 на P1.6 и P1.7 нужно выполнить следующие изменения в исходном коде программного обеспечения:
MSP430FR2000 | MSP430FR2311 | |
Подключение P2.0 к TB0.CCI1A | Подключение P1.6 к TB0.CCI1A | |
→ | ||
Подключение P2.1 к TB0.CCI2A | Подключение P1.7 к TB0.CCI2A | |
→ | ||
Подключение P2.0 к TB0.1 | Подключение P1.6 к TB0.1 | |
→ | ||
Подключение P2.1 к TB0.2 | Подключение P1.7 к TB0.2 | |
→ |
При использовании вместо таймера Timer0 модуля Timer1 программное обеспечение модифицируется следующим образом:
MSP430FR2000 | MSP430FR2311 | |
Подключение P2.0 к TB0.CCI1A | Подключение P2.0 к TB1.CCI1A | |
→ | ||
Подключение P2.1 к TB0.CCI2A | Подключение P2.1 к TB1.CCI2A | |
→ | ||
Формирование ШИМ-сигнала на выводах P2.0 и P2.1 | ||
→ | ||
Обработчик прерывания таймера | ||
→ |
Подробная информация по работе модулей Timer_A и Timer_B приведена в разделах 12 и 13 «Руководства пользователя по семействам микроконтроллеров MSP430FR4xx и MSP430FR2xx» (MSP430FR4xx and MSP430FR2xx Family User’s Guide).
Компаратор
При использовании компаратора следует обратить внимание, что его входы в разных микросхемах могут быть подключены к разным портам ввода-вывода. В данном случае в микроконтроллере MSP430FR2000 компаратор подключен к портам P1.2 или P1.3, а в MSP430FR2311 – к P1.0 или P1.1 (рисунок 73), что требует модификации соответствующих блоков программного обеспечения:
MSP430FR2000 | MSP430FR2311 | |
Подключение P1.2 к С2 | Подключение P1.0 к С0 | |
→ | ||
Подключение P1.3 к С3 | Подключение P1.1 к С1 | |
→ |
Подробная информация о работе модуля компаратора eCOMP0 приведена в разделе 17 «Руководства пользователя по семействам микроконтроллеров MSP430FR4xx и MSP430FR2xx» (MSP430FR4xx and MSP430FR2xx Family User’s Guide), а также в разделе 6.11.12 технической документации на микроконтроллеры MSP430FR231x (MSP430FR231x Mixed-Signal Microcontrollers datasheet).
Переход с MSP430FR2311 на MSP430FR2000
Микроконтроллер MSP430FR2311 имеет ряд модулей и функций, отсутствующих в MSP430FR2000, поэтому прежде чем принимать решение о переходе на MSP430FR2000, необходимо проверить возможность физической реализации такого решения. Ключевые отличия двух микроконтроллеров приведены в таблице 27.
Таблица 27. Ключевые отличия микроконтроллеров MSP430FR2000 и MSP430FR2311
Наименование | MSP430FR2311 | MSP430FR2000 |
---|---|---|
Размер памяти программ, кбайт | 4 | 0,5 |
Размер оперативной памяти RAM, кбайт | 1 | 0,5 |
Количество портов ввода-вывода (GPIO) | 16 | 12 |
Поддержка интерфейсов I2C | 1 | – |
Количество интерфейсов SPI | 2 | 1 |
АЦП | 10-разрядный (8 каналов) |
Интегрирующего типа |
Количество каналов компаратора | 2 | 4 |
Количество 16-разрядных таймеров | 2 | 1 |
Ток, потребляемый в активном режиме, мкА/МГц | 126 | 120 |
При необходимости можно также воспользоваться сводной таблицей характеристик микроконтроллеров семейства MSP430™ (MSP430™ ultralow-powerMCUs [http://www.ti.com/microcontrollers/msp430-ultra-low-power-mcus/products.html]), доступной на сайте Texas Instruments, и выбрать микросхему, наиболее подходящую для поставленных задач. Список функций MSP430FR2311, отсутствующих в MSP430FR2000, приведен в таблице 28.
Таблица 28. Список функций микроконтроллера MSP430FR2311, отсутствующих в MSP430FR2000
Модуль/функция | Сигнал | Вывод | PxDIR.x | PxSELx | Описание |
---|---|---|---|---|---|
Аналого-цифровой преобразователь (Analog-to-Digital Converter, ADC) | A0 | P1.0 | x | 11 | Аналоговый вход А0 |
A1 | P1.1 | x | 11 | Аналоговый вход А1 | |
A2 | P1.2 | x | 11 | Аналоговый вход А2 | |
A3 | P1.3 | x | 11 | Аналоговый вход А3 | |
A4 | P1.4 | x | 11 | Аналоговый вход А4 | |
A5 | P1.5 | x | 11 | Аналоговый вход А5 | |
A6 | P1.6 | x | 11 | Аналоговый вход А6 | |
A7 | P1.7 | x | 11 | Аналоговый вход А7 | |
Vref+ | P1.0 | x | 11 | Опорное напряжение АЦП (положительное) | |
Vref- | P1.2 | x | 11 | Опорное напряжение АЦП (отрицательное) | |
Трансимпедансный усилитель (Transimpedance Amplifier, TIA0) | TRI0+ | P1.7 | x | 11 | Неинвертирующий вход |
TRI0- | P1.6 | x | 11 | Инвертирующий вход | |
TRI0O | P1.5 | x | 11 | Выход | |
Многофункциональный аналоговый модуль (Smart Analog Combo, SAC0) | OA0+ | P1.4 | x | 11 | Неинвертирующий вход ОУ |
OA0- | P1.2 | x | 11 | Инвертирующий вход ОУ | |
OA0O | P1.3 | x | 11 | Выход ОУ | |
Порты ввода-вывода общего назначения (General-Purpose Input/Output, GPIO) | P1.4 | P1.4 | Вх.: 0; Вых.: 1 |
00 | При изменении уровня сигнала (с настраиваемым активным фронтом) могут генерировать прерывание, способное вывести микроконтроллер из энергосберегающих режимов LPM3.5, LPM4 и LPM4.5 |
P1.5 | P1.5 | Вх.: 0; Вых.: 1 |
00 | ||
P1.6 | P1.6 | Вх.: 0; Вых.: 1 |
00 | ||
P1.7 | P1.7 | Вх.: 0; Вых.: 1 |
00 | ||
P2.2 | P2.2 | Вх.: 0; Вых.: 1 |
00 | Стандартный порт ввода-вывода | |
P2.3 | P2.3 | Вх.: 0; Вых.: 1 |
00 | Стандартный порт ввода-вывода | |
P2.4 | P2.4 | Вх.: 0; Вых.: 1 |
00 | Стандартный порт ввода-вывода | |
P2.5 | P2.5 | Вх.: 0; Вых.: 1 |
00 | Стандартный порт ввода-вывода | |
Интерфейс I2C | UCB0SCL | P1.3, P2.5* | x | 01 | Тактовый сигнал (I2C Clock) |
UCB0SDA | P1.2, P2.4* | x | 01 | Информационный сигнал (I2C Data) |
|
Интерфейс SPI | UCB0STE | P1.0, P2.2* | x | 01 | Сигнал разрешения передачи ведомым устройством (Slave Transmit Enable) |
UCB0CLK | P1.1, P2.3* | x | 01 | Тактовый сигнал (Clock Input/Output) |
|
UCB0SIMO | P1.2, P2.4* | x | 01 | Данные от ведомого к ведущему устройству (Slave In/Master Out) |
|
UCB0SOMI | P1.3, P2.5* | x | 01 | Данные от ведущего к ведомому (Slave Out/Master In) |
|
Модуль таймера Timer_B | TB1.CCI1A | P2.0 | 0 | 01 | Вход канала CCI1A в режиме захвата/счета импульсов |
TB1.1 | P2.0 | 1 | 01 | Выход канала CCI1A в режиме генерации ШИМ-сигнала | |
TB1.CCI2A | P2.1 | 0 | 01 | Вход канала CCI2A в режиме захвата/счета импульсов | |
TB1.2 | P2.1 | 1 | 01 | Выход канала CCI2A в режиме генерации ШИМ-сигнала | |
TB1CLK | P2.2 | 0 | 10 | Вход внешнего тактового сигнала TBCLK | |
TB1TRG | P2.3 | 0 | 10 | Вход для подключения внешнего триггера TB1OUTH | |
* – Используемый канал порта зависит от установки бита USCIBRMP в регистре SYSCFG2. |
Советы по оптимизации исходного кода программного обеспечения
При выборе микроконтроллера с ограниченным объемом памяти программ размер прошивки, получающийся после компиляции исходного кода, может иметь решающее значение. В этом разделе приведено несколько приемов уменьшения итогового количества ассемблерных инструкций при разработке программного обеспечения на языках С/С++. Данная информация актуальна в первую очередь при использовании сред разработки Code Composer Studio™ (CCS)и IAR Embedded Workbench® (IAR EW430). Тем не менее, эти рекомендации могут с успехом использоваться и при использовании программных пакетов других производителей, поскольку основные принципы уменьшения размера прошивки одинаковы для всех типов микроконтроллеров.
При использовании современных средств разработки программного обеспечения следует иметь в виду, что изменение всего нескольких ключевых строк кода и/или нескольких настроек компилятора может в итоге оказать решающее влияние на размер прошивки. Результат работы всех предлагаемых рекомендаций проиллюстрирован на примере файла msp430fr211x_euscia0_uart_03.c, предназначенного для микроконтроллера MSP430FR2000, с размером памяти программ всего 512 байт. Файлы прошивки создавались и компилировались с помощью программных пакетов CCS (версия среды разработки – 7.3, версия компилятора – 16.9.4.LTS) и IAR EW430 версии 7.10.4.
Настройка компиляторов
Конечно, разработка программного обеспечения на таких языках высокого уровня как С/С++ происходит намного быстрее и легче, чем на ассемблере. Однако «накладными расходами» высокого уровня абстракции зачастую являются увеличение размера или времязатрат на выполнение исполняемого кода, что при использовании микроконтроллеров с ограниченным объемом памяти программ может стать серьезной проблемой. Поэтому при малом объеме памяти программ и разработке исходника на языке С/С++ необходимо особое внимание уделить настройкам оптимизации компилятора. Правильная установка его параметров позволит повысить эффективность выполнения итоговых инструкций до уровня разработки их на ассемблере, при этом все рутинные операции по оптимизации исходного кода вместо человека будет выполнять компилятор.
Компилятор любой среды разработки всегда содержит некоторое количество настроек по оптимизации кода, написанного на С/С++. Программист должен четко понимать не только их назначение, но и результат действия, причем при использовании как по отдельности, так и в различных комбинациях. Именно такой подход позволит создать прошивку с наименьшим размером кода без какого-либо ущерба для функциональности.
Code Composer Studio – настройки оптимизации
Доступ к настройкам оптимизации компилятора CCS осуществляется через меню Project → Properties → Build → MSP430 Compiler → Optimization. Компилятор CCS имеет два основных настраиваемых параметра (рисунок 74): уровень оптимизации (Optimization level) и уровень компромисса между размером кода и скоростью его выполнения (Speed vs Size Trade-Offs).
Уровень оптимизации определяет методы, которые будут использованы компилятором при обработке исходного кода. Уровень компромисса может быть установлен в диапазоне от 0 (наименьший размер с максимальной скоростью выполнения) до 5 (наивысшая скорость выполнения при максимальном размере прошивки). Установка промежуточных значений приведет к созданию кода с некоторым сбалансированным компромиссным значением этих двух параметров. При необходимости более детальную информацию о влиянии данных настроек на конечный результат можно получить, ознакомившись с соответствующим «Руководством по оптимизации С/С++ компилятора» (MSP430 Optimizing C/C++ Compiler User’s Guide).
Кроме этого, в CCS существует специальная утилита под названием «Помощник по оптимизации» (Optimizer Assistant), доступная из меню View → Optimizer Assistant (рисунок 75). С ее помощью можно оперативно провести исследование исходного кода и подобрать наиболее подходящие для данного случая настройки оптимизации компилятора.
После нажатия на кнопку «Начать анализ» («Start Analysis») будут показаны результаты компиляции исходного кода при различных значениях настроек. При этом перед началом анализа необходимо указать, какой из двух параметров (уровень оптимизации или уровень компромисса) будет выступать в качестве аргумента при проведении исследований. Так, например, при выборе в качестве исследуемого параметра уровня оптимизации значение уровня компромисса будет взято из текущих настроек проекта и не будет меняться во время исследования. После проведения исследований появится окно с наглядным представлением работы оптимизатора (рисунок 76).
Результаты исследования имеют не только количественное (итоговый размер кода), но и качественное представление. Если результаты анализа отображены красным цветом, то это означает, что размер прошивки превышает объем памяти программ микроконтроллера и данное сочетание настроек в этом случае использовать нельзя. Желтый цвет означает, что данная прошивка может разместиться в памяти программ, однако существуют лучшие комбинации, отображаемые зеленым цветом. Обратите внимание, что в показанном примере (рисунок 76) размер прошивки для выбранного варианта не является минимальным. Никакого противоречия здесь нет, поскольку если размер прошивки не превышает размер памяти программ, то почему бы за счет введения некоторой избыточности исполняемого кода не увеличить скорость его выполнения? При необходимости подробную информацию о работе с данной утилитой можно получить на соответствующей странице официального сайта Texas Instruments.
На последнем этапе использования этого инструмента рекомендуется сохранить полученные оптимальные параметры в настройках среды разработки для того чтобы использовать их как в этом, так и в последующих проектах.
Настройка вида адресации
Микроконтроллеры MSP430™ имеют 16-разрядную архитектуру. Если не принимать никаких мер, то при такой разрядности максимальный адрес памяти не может превышать 65535. Однако наиболее мощные представители данного семейства могут иметь в несколько раз больший объем памяти, достигающий 1 Мбайт. Очевидно, что для работы с таким количеством ячеек разрядность адресной шины должна быть не менее 20 бит, что и учтено при разработке архитектуры MSP430х, содержащей, кроме всего прочего, расширенный набор инструкций, поддерживающих 20-разрядную адресацию. Однако из-за увеличенной длины адреса эти инструкции требуют для хранения большего количества памяти, а также большего времени для их обработки и выполнения (более детально с расширенным набором команд для микроконтроллеров CPUX можно ознакомиться в соответствующих разделах описания семейства). Таким образом, использование расширенного набора команд, поддерживающих адресацию выше 10000h в микроконтроллерах, не содержащих такого количества ячеек, в большинстве случаев приведет лишь к бессмысленному увеличению как размера кода, так и времени выполнения программы.
В этом случае в настройках, доступных из меню Project → Properties → Build → MSP430 Compiler → Processor Options, необходимо отключить использование расширенного набора инструкций, указав, что в проекте используются микроконтроллеры с небольшим размером памяти программ (настройка Code Memory Model) и ОЗУ (настройка Data Memory Model). Этим настройкам необходимо установить значение «Small» (рисунок 77).
После изменения этих настроек следующая сборка проекта может занять несколько минут, поскольку компилятору необходимо полностью перестроить библиотеку поддержки работы в реальном времени (Runtime Support, RTS). Однако после этого последующие компиляции и сборки проекта будут происходить быстрее, поскольку перестраивать библиотеку RTS больше нет необходимости.
Исключение инструкций, поддерживающих расширенную адресацию, может оказать огромное влияние на размер кода. Так, например, компиляция файла msp430fr211x_euscia0_uart_03.c с включенной поддержкой 20-разрядных адресов может достигать 928 байт даже при использовании наивысших уровней оптимизации (рисунок 78). После отключения этой опции размер прошивки уменьшается приблизительно на 38% и составляет 572 байта (рисунок 79).
Инициализация глобальных переменных
В большинстве случаев компилятор автоматически вставляет в прошивку стандартные фрагменты кода, например, подпрограмму инициализации указателя стека, освобождая программиста от ручной проработки рутинных операций. К числу таких модулей относятся также и подпрограммы инициализации начальных значений глобальных переменных. В больших и сложных программах использование этого инструмента позволяет значительно сократить время разработки и уменьшить количество потенциальных ошибок. Однако в небольших проектах такой подход может привести к значительному увеличению итогового размера прошивки, хотя бы потому, что на практике в прошивку будут скопированы стандартные подпрограммы, поддерживающие работу с большими массивами данных, в то время как по факту они будут инициализировать всего несколько ячеек памяти. Ключевым фактором, позволяющим оценить эффективность того или иного типа инициализации, является размер прошивки. Если при ручной установке начальных значений глобальных перемененных размер прошивки оказывается меньше, чем при их автоматической инициализации, то эту опцию лучше отключить.
Оценить размер кода, используемого для инициализации глобальных переменных, можно, открыв файл с расширением .map, расположенный в папке Debug. В этом файле, в секции «Section Allocation Map» расположена область, помеченная как «.text». В ней находится информация о размещении всех подпрограмм, включая модули из библиотеки RTS, помеченные как «rts430x_xx_xx_eabi.lib» (значения «xx» отличаются, что зависит от настроек компилятора). Из карты (рисунок 80) видно, что в числе автоматически добавляемых модулей есть подпрограммы для инициализации и копирования массивов, а также функции умножения. Очевидно, что все это требует дополнительных инструкций, и для микроконтроллеров с ограниченным количеством памяти программ, таких как MSP430FR2000 или MSP430G2001, это может стать причиной ошибок компиляции, когда размер прошивки получается больше размера памяти программ.
Таким образом, в небольших проектах, для которых критичен каждый лишний байт исполняемого кода, возможно, следует использовать иные методы построения программного обеспечения. Самым простым вариантом является полный отказ от использования глобальных переменных. Ввиду их отсутствия, не будет и кода инициализации. Но если без глобальных переменных все же обойтись не удается, то следует, во-первых, максимально уменьшить их количество, а во-вторых, инициализировать их вручную, например, в процедуре main(). В этом случае автоматическую инициализацию глобальных переменных нужно отключить.
Отключение автоматической инициализации глобальных переменных
Для отключения автоматической инициализации глобальных переменных необходимо соответствующим образом модифицировать исходный код программного обеспечения и изменить настройки компилятора.
Во-первых, нужно переместить код инициализации в подпрограмму в main(). В рассматриваемом файле примера msp430fr211x_euscia0_uart_03.c есть только две глобальные переменные: RXData и TXData. Первоначально они были инициализированы вне тела каких-либо подпрограмм, что и привело к автоматической генерации кода установки их значений. После переноса кода инициализации такой необходимости уже нет (рисунок 81).
До переноса кода инициализации в программу main() размер прошивки был равен 572 байт. Оптимизация программы позволила уменьшить этот параметр на 18%, и теперь он равен 468 байт. Анализ файла с расширением .map (рисунок 82) показал, что это произошло благодаря исключению некоторых подпрограмм, скопированных из библиотеки RTS. Однако также видно, что теперь в прошивку добавлены другие подпрограммы, в частности – copy_zero_init.
Это связано с тем, что по умолчанию в проектах, созданных в формате EABI, все глобальные переменные автоматически инициализируются нулевыми значениями. Очевидно, что при последующей ручной инициализации в подпрограмме main() такая мера предосторожности явно избыточна. Это означает, что эту опцию можно безболезненно отключить, установив параметр «Zero Initialize ELF Uninitialized Sections» в значение «Off» (рисунок 83). Это можно сделать в настройках среды разработки, доступных из главного меню программы: Project → Properties → MSP430 Linker → Advanced Options → Miscellaneous.
Отключение автоматической инициализации глобальных переменных позволило уменьшить размер прошивки еще на 68% – с 468 до 178 байт, и теперь она занимает меньше половины объема памяти программ даже в микроконтроллерах, у которых ее количество не превышает 512 ячеек.
IAR Embedded Workbench – настройки оптимизации
Основная настройка параметров оптимизации компилятора IAR производится на вкладке Optimizations, доступной из меню Project → Options → C/C++ Compiler (рисунок 84). Аналогично рассмотренной выше Code Composer Studio, в IAR Embedded Workbench существует два основных параметра: уровень оптимизации (Optimization Level) и уровень компромисса между размером прошивки и скоростью выполнения (Speed vs Size Trade-Offs). Разработчик может установить несколько уровней оптимизации, на каждом из которых применяется свой набор методов, каждый из которых при необходимости можно отключить. Уровень компромисса выбирается как по размеру исходного кода, так и по скорости выполнения прошивки с возможностью установки нескольких промежуточных вариантов. Дополнительную информацию о настройках параметров оптимизации компилятора можно получить, прочитав раздел IAR C/C++ Compiler User’s Guide в справочной системе IAR.
Настройка вида адресации
Микроконтроллеры MSP430™ имеют 16-разрядную архитектуру. Если не принимать никаких мер, то при такой разрядности максимальный адрес памяти не может превышать 65535. Однако наиболее мощные представители данного семейства могут иметь в несколько раз большие объемы памяти, достигающие 1 Мбайт. Очевидно, что для работы с таким количеством ячеек разрядность адресной шины должна быть не менее 20 бит, что и учтено при разработке архитектуры MSP430X, содержащей, кроме всего прочего, расширенный набор инструкций, поддерживающих 20-разрядную адресацию. Однако из-за увеличенной длины адреса эти инструкции требуют для хранения большего количества памяти, а также большего времени для их обработки и выполнения (более детально с расширенным набором команд для микроконтроллеров CPUX можно ознакомиться в соответствующих разделах описания семейства). Таким образом, использование расширенного набора команд, поддерживающих адресацию выше 10000h в микроконтроллерах, не содержащих такого количества ячеек, в большинстве случаев приведет лишь к бессмысленному увеличению как размера кода, так и времени выполнения программы.
Настройки вида адресации расположены на вкладке “Target”, доступной из меню Project → Options → General Options (рисунок 85), где необходимо правильно установить параметры «Code Model» (тип адресации памяти программ) и «Data Model» (тип адресации ОЗУ). При разработке небольших проектов обоим параметрам необходимо установить значение «Small».
Обратите внимание, что компилятор IAR в состоянии самостоятельно проанализировать тип используемого микроконтроллера и выбрать наиболее подходящие инструкции для данного случая, поэтому размер прошивки при компиляции файла примера msp430fr211x_euscia0_uart_03.c фактически не зависит от значения параметров «Code Model» и «Data Model». Это означает, что подбор ассемблерных инструкций в любом случае происходит наиболее оптимальным образом. Однако хорошей практикой для разработчика все же является контроль этих двух параметров, поскольку в некоторых случаях это может дать дополнительный выигрыш в размере прошивки.
Инициализация глобальных переменных
В большинстве случаев компилятор автоматически вставляет в прошивку стандартные фрагменты кода, например, код инициализации указателя стека, освобождая программиста от ручной проработки рутинных операций. К числу таких модулей относятся также и подпрограммы инициализации начальных значений глобальных переменных. В больших и сложных программах использование этого инструмента позволяет значительно сократить время разработки и уменьшить количество потенциальных ошибок. Однако в небольших проектах такой подход может привести к значительному увеличению итогового размера прошивки, хотя бы потому, что на практике в прошивку будут скопированы стандартные подпрограммы, поддерживающие работу с большими массивами данных, в то время как по факту они будут инициализировать всего несколько ячеек памяти. Ключевым фактором, позволяющим оценить эффективность того или иного типа инициализации, является размер прошивки. Если при ручной установке начальных значений глобальных перемененных размер прошивки оказывается меньше, чем при их автоматической инициализации, то эту опцию лучше отключить.
Для оценки размера автоматически создаваемого кода можно открыть файл с расширением .map, доступный для просмотра из меню Project → Options → Linker → List. Переместившись в конец файла, в секцию «Module Summary», можно увидеть информацию о размере и расположении всех используемых подпрограмм, в том числе и автоматически созданных компилятором, названия которых начинаются, как правило, с символа «?». Например, в приведенном примере (рисунок 86) можно увидеть подпрограмму memcpy, которая используется, в том числе, и для установки начальных значений ячеек оперативной памяти. К сожалению, все это требует дополнительных инструкций, и для микроконтроллеров с ограниченным количеством памяти программ, таких как MSP430FR2000 или MSP430G2001, имеющих всего 512 ячеек для размещения прошивки, может стать причиной того, что прошивка просто не поместится в память программ микроконтроллера.
Таким образом, в небольших проектах, для которых критичен каждый лишний байт исполняемого кода, возможно, следует использовать иные методы построения программного обеспечения. Самым простым вариантом является полный отказ от использования глобальных переменных. Ввиду их отсутствия не будет и кода инициализации. Но если без глобальных переменных все же обойтись не удается, то следует, во-первых, максимально уменьшить их количество, а во-вторых – инициализировать их вручную, например, в процедуре main(). В этом случае автоматическую инициализацию глобальных переменных нужно отключить.
Отключение автоматической инициализации глобальных переменных
Для отключения автоматической инициализации глобальных переменных необходимо соответствующим образом модифицировать исходный код программного обеспечения и изменить настройки компилятора.
Во-первых, нужно переместить код инициализации глобальных переменных в подпрограмму в main(). В файле msp430fr211x_euscia0_uart_03.c есть только две глобальные переменные: RXData и TXData. Изначально они были инициализированы вне тела каких-либо подпрограмм, что привело к автоматической генерации кода установки их значений. После переноса кода инициализации в подпрограмму main() такой необходимости уже нет (рисунок 87).
До оптимизации размер прошивки был равен 240 байт. Перенос кода инициализации в main() позволил уменьшить этот параметр на 12,5% и теперь он равен 210 байт. Анализ файла с расширением .map (рисунок 88) показывает, что это произошло благодаря исключению некоторых подпрограмм, например, memcpy. Кроме этого, модуль cstart уменьшился в размере. Однако некоторые подпрограммы, например, memzero, все еще продолжают занимать дефицитное адресное пространство.
Это связано с тем, что по умолчанию все проекты, созданные в формате EABI, автоматически устанавливают значения всех глобальных переменных равными нулю. Однако если глобальные переменные потом повторно инициализируются в других местах программы, то такая мера предосторожности явно избыточна. В этом случае данную опцию можно отключить, объявив переменные с ключевым словом __no_init (рисунок 89).
Отключение начальной инициализации позволит дополнительно уменьшить размер кода на 13% – с 210 до 183 байт.
Советы по написанию исходного кода на С
Несмотря на то, что компилятор может сделать очень многое для создания прошивки с минимальным размером, существуют вещи, которые ему, к сожалению, не под силу. Если программист не владеет навыками написания оптимального кода, или, другими словами, ручной оптимизации, то все старания компилятора будут напрасными и не дадут нужного результата. Все, что останется компилятору – это писать подсказки о возможном обнаружении скрытых проблем и наличии потенциала для оптимизации существующего кода.
Используйте переменные и константы наименьшего размера
Константы, как и инструкции, хранятся в энергонезависимой памяти программ, занимая определенное количество ячеек. Если значение константы находится в диапазоне 0…255, то нет никакого смысла объявлять ее стандартным 16-разрядным типом int, ведь в этом случае она займет ровно в два раза больше памяти, чем это действительно необходимо. Те же самые соображения справедливы и для переменных, объявленных с помощью ключевого слова «PERSISTENT», что означает, что они должны храниться в энергонезависимой сегнетоэлектрической памяти FRAM. Использование переменных большего, чем это необходимо, размера, приведет к необоснованному дополнительному расходу ячеек FRAM, которые можно было бы с успехом использовать для размещения нескольких дополнительных инструкций. Таким образом, хранение данных в константах и переменных минимально возможного размера так же важно, как и использование других методов оптимизации.
В качестве примера рассмотрим приложение, генерирующее с помощью модуля таймера ШИМ-сигнал с частотой 60 Гц. Если таймер будет тактироваться частотой 32768 Гц, то максимально возможное количество длительностей ширины импульса составит 32768/60 = 545. Очевидно, что если необходимо где-то хранить фиксированные величины коэффициентов заполнения, то для одного значения необходимо использовать константы длиной 2 байта, поскольку 545 больше 255. Однако если разделить тактовую частоту на 4, например, с помощью штатного предделителя, то теперь для хранения одного значения достаточно одного байта, потому что максимальное количество возможных длительностей импульса теперь равно 8192/60 = 136. Если точность формирования выходных сигналов (разрешающая способность) при этом удовлетворяет требованиям технического задания, то такой прием позволит в два раза уменьшить расход памяти на хранение констант со значениями коэффициента заполнения. В общем случае, этот совет можно сформулировать следующим образом: если позволяет техническое задание, то для хранения любых данных следует использовать константы и/или переменные с наименьшим размером.
Избегайте операций умножения и деления
Если микроконтроллер не имеет модуля аппаратного умножения, то операции умножения и деления, которые так легко вставить в исходном коде на С, потребуют вставки в ассемблерный код специализированных подпрограмм, которые не только займут достаточно много места в памяти программ, но и ощутимо снизят скорость выполнения. Поэтому операции умножения и деления необходимо использовать только в самых крайних случаях, когда без них никак не обойтись.
Сказанное выше не относится к случаям, когда переменную необходимо умножить или разделить на константу, кратную 2 (например, 2, 4, 6, 8, 16…). Однако в этом случае арифметические операции умножения и деления в исходном коде следует заменить логическими операциями сдвига. Для умножения на 2 достаточно содержимое переменной сдвинуть влево на один разряд (в С данная операция обозначается как «<<»). Если переменную необходимо умножить на 4, тогда ее значение необходимо сдвинуть влево на два разряда и так далее. Операции деления выполняются аналогично, только содержимое регистров необходимо сдвигать в обратную сторону – вправо (операция «>>»). В большинстве платформ для сдвигов вправо/влево существуют специализированные ассемблерные команды, поэтому эти операции выполняются очень быстро и с минимальным расходом памяти программ.
Есть еще один вариант возможного исключения операций умножения и деления, но для этого нужно определить, когда их реально нужно выполнять. Бывают случаи, когда умножение или деление можно произвести на этапе компиляции (в этом случае оба операнда должны быть константами), а в исходном коде сохранить константу, содержащую результат (или результаты) этой операции. Это позволит не только сократить размер прошивки, поскольку теперь нет необходимости в копировании объемных подпрограмм, но и увеличить скорость выполнения кода, ведь загрузка константы происходит гораздо быстрее выполнения этих арифметических операций.
Если же без выполнения операций умножения и деления в режиме реального времени обойтись невозможно, то можно попробовать применить одну хитрость. Этот совет точно сработает, если аргументы изменяются в небольшом фиксированном диапазоне значений. В этом случае результаты выполнения этих арифметических операций можно рассчитать заранее для всех возможных случаев и сохранить их в памяти программ в виде таблицы. Только делать это нужно очень аккуратно, поскольку при большом количестве возможных значений размер прошивки может оказаться больше, чем в случае использования специализированных подпрограмм. В этом случае рекомендуется реализовать оба варианта и выбрать наиболее подходящий результат. Дополнительным критерием выбора здесь может служить то, что выборка нужного результата из таблицы в большинстве случаев будет происходить быстрее арифметических вычислений.
Используйте таблицы вместо вычислений
Если во время выполнения программы приходится многократно выполнять большое количество арифметико-логических операций, то следует задуматься о том, есть ли смысл их выполнять каждый раз. Особенно актуально это для вычислений вещественных чисел с плавающей точкой. Если на этапе проектирования известно, что аргументы функций могут находиться в узком фиксированном диапазоне значений, то есть смысл, как и в предыдущем примере, выполнить эти вычисления на этапе написания исходного кода, а результаты сохранить в виде таблицы. Такой подход однозначно повысит скорость выполнения программного кода и сбережет процессорное время, а вот размер прошивки может существенно увеличиться, ведь таблицы обычно хранятся в памяти программ. В сложных случаях можно реализовать оба варианта, из которых потом выбрать наиболее подходящий.
Устанавливайте значения 16-разрядных регистров целиком
Некоторые регистры в микроконтроллерах MSP430 поддерживают как 8-разрядную, так и 16-разрядную форму обращения. Если нет никаких аппаратных ограничений на запись информации, например, когда младшие 8 бит регистра должны записываться только после предварительной установки старших 8 байт, то старайтесь максимально использовать 16-разрядную форму обращения.
Хорошим примером может послужить следующий код. Вместо двух команд для установки 16-разрядного регистра PAOUT при использовании 8-разрядной формы обращения:
можно использовать всего лишь одну:
Результат в обоих случаях будет одинаков, но количество инструкций и время выполнения будут уменьшены ровно в два раза. Это один из немногих примеров, когда оптимизация по критерию минимального размера прошивки приводит также к увеличению времени выполнения.
При использовании этого приема следует обращать внимание на форму записи названий регистров и их разрядов. В приведенном примере видно, что константа BIT7, используемая при установке старшего байта P2OUT регистра PAOUT, была заменена на BIT15. Невнимательность программиста на данном этапе может стать причиной возникновения ошибок. В большинстве случаев имена регистров при 8-разрядной форме обращения состоят из его имени при 16-разрядной форме с добавлением суффиксов «_H» для старшего байта и «_L» для младшего. Также часто используются вариант, когда при 16-разрядной форме обращения последним символом имени регистра является «W» (например, UCAxBRW), а при 8-разрядном – «0» (UCAxBR0) и «1» (UCAxBR1). Чуть реже встречается вариант, когда имя регистра при 16-разрядной форме обращения является своеобразной комбинацией имен его частей при 8-разрядной: RTCCTL13 = RTCCTL1 + RTCCTL3.
Устанавливайте значение регистра один раз (если это возможно)
Аналогично предыдущему совету, следует, по возможности, устанавливать значения битов регистров сразу. Особенно актуально это на этапе инициализации микроконтроллера. Последовательность установки битов в регистрах обычно имеет значение очень редко, например, значения битов ADC12CTL0 могут быть изменены только после предварительной очистки бита ADC12ENC. А в большинстве случаев все биты в регистре могут быть установлены в нужное значение одновременно без каких-либо логических проблем.
Очень часто программисты по разным причинам устанавливают биты одного и того же регистра по очереди, в результате чего появляется приблизительно такой код:
Даже при использовании максимального уровня оптимизации для реализации приведенного примера необходимо применить не одну ассемблерную команду, ведь значение регистра TA0CCTL2 нужно несколько раз считать, логически сложить (умножить, инвертировать и так далее) с константой и сохранить. Очевидно, что следующий код даст почти тот же самый результат, но будет намного оптимальнее:
Подводным камнем такого подхода может стать случайное изменение тех битов, которые не должны изменяться. Действительно, при использовании данного кода все биты в регистре TA0CCTL2, кроме OUTMOD_4 и CCIE, будут сброшены, что может привести к неправильной работе модуля. Поэтому перед заменой побитовых операций на операцию прямого присваивания «=» необходимо внимательно изучить техническую документацию и выяснить, какие биты могут быть установлены, а также помнить, какое значение они принимают после сброса микроконтроллера.
Используйте функцию __even_in_range()
Функция __even_in_range (x, NUM) используется совместно с оператором switch и предоставляет компилятору дополнительную информацию о том, что значение х должно быть, во-первых, четным, а во-вторых – находиться в диапазоне 0…NUM. На основании этой информации компилятор может дополнительно оптимизировать результирующий код, исключив обработку заведомо невозможных комбинации. Данная функция часто используется в обработчиках прерываний, поскольку их причина обычно четко детерминирована и не может принимать произвольные значения (рисунок 90).
Но применение функции __even_in_range() не ограничивается только обработчиками прерываний. Она может использоваться во всех случаях, когда программист точно уверен, что управляющая переменная может принимать строго определенные четные значения в фиксированном диапазоне. Например, в случае использования программного счетчика, увеличивающегося с каждой итераций не на 1, а на 2. В этом случае функцию __even_in_range() можно смело применять в операторе switch для обработки значений программного счетчика 0, 2 и других.
При необходимости дополнительную информацию о работе функции __even_in_range() можно прочитать в «Руководстве по оптимизации работы компилятора» (MSP430 Optimizing C/C++ Compiler User’s Guide).
Кроме этого, бывают ситуации, когда один и тот же код необходимо выполнить несколько раз. В этом случае данная функция позволит эффективно уменьшить требуемый объем памяти программ, поскольку простое дублирование кода в большинстве случаев негативно скажется на размере прошивки.
Заключение
Лучшим методом поиска оптимального решения является эксперимент. В этом случае полезным инструментом для программиста станет механизм контроля версий, позволяющий создавать варианты одной и той же прошивки с применением разных стратегий написания исходного кода и параметров оптимизации. Это позволит в конце работы над проектом из множества доступных решений выбрать один оптимальный вариант, который и станет итоговой версией прошивки для разрабатываемого приложения.
Исчтоник: Компэл