MSP430 - учебный курс. Часть 2 - простенькая программа.

Сегодня мы разберём приведённую на прошлой паре… э-э-э, блин, привычка однако… программу.
Кто желает, может скачать новый IAR (текущая версия набора 5.20, среда разработки Embedded Workbench — 6-й версии), без необходимости заполнения анкеты как на сайте www.iar.com. Самый свежий. При этом, официально бесплатный, правда с ограничением в 4 Кб кода. Для наших уроков это не проблема — данного объёма вполне хватит.
Заходим по ссылке http://focus.ti.com/docs/toolsw/folders/print/iar-kickstart.html и жмём кнопку «Download».
Предупреждение! Размер скачиваемого архива значителен — около 200 Мб!
А пока я жду заказанного MSP430F249, оговорюсь — рассматриваем 149-й (вернее серию 1xx). Моделька помладше версией ядра, но по ногам они 1 к 1 и совместимы снизу вверх. Кстати, оба контроллера — старшие модели в своём классе.
Приведу текст программы ещё раз:
#include "msp430.h" ; подключаем заголовочный файл
NAME main ; имя модуля
PUBLIC main ; установить видимость модуля из других
; Определение констант
ArrLn EQU 16 ; длина массива
; Вектора прерываний
RSEG INTVEC
ORG RESET_VECTOR ; вектор сброса
DC16 init
RSEG CSTACK ; объявление сегмента стека
RSEG CODE ; объявление сегмента кода
init:
MOV #SFE(CSTACK), SP ; инициализация регистра стека
main: ; начало основной части
MOV.W #WDTPW+WDTHOLD,&WDTCTL ; запрет сторожевого таймера
SortMain:
; Переписать из ПЗУ в ОЗУ массив данных
mov #ArrLn, R4 ; инициализируем счётчик количества данных
mov #RamArr, R5 ; установим R5 как указатель на данные в ОЗУ
mov #RomArr, R6 ; установим R6 как указатель на данные в ПЗУ
MoveRomToRam:
mov @R6+, 0(R5) ; копирование слова
add #2, R5 ; продвинем указатель
dec R4 ; уменьшим счётчик
jnz MoveRomToRam ; если не ноль, то повторим
BasicCycle:
JMP $ ; jump to current location '$'
; определение данных в ОЗУ
RSEG DATA16_N
RamArr:
DS16 ArrLn
; определение данных в ПЗУ
RSEG DATA16_C
RomArr:
DC16 101, 11, 25, 657, 567, 217, 5732, 896, 123, 5467, 12, 65, 2345, 23, 98, 2398
END
Первые три строки — как и в прошлом уроке.
Следом определяем константу длины массива в элементах, чтобы потом пользоваться символьным именем вместо изменения конкретной цифры в куче мест. Да и удобнее сделать так, если вдруг прийдется поменять тип хранимых элементов. То же самое можно сделать как "#define ArrLn 16", если вам стиль C больше по душе.
; Вектора прерываний
RSEG INTVEC
ORG RESET_VECTOR ; вектор сброса
DC16 init
Здесь мы определяем таблицу векторов прерываний. Я предпочитаю делать именно так, а не как в заготовке проекта (первый пример на скриншоте из первого урока). "RSEG INTVEC" определяет сегмент векторов. Вторая строка — адрес вектора сброса. Здесь будьте внимательны — константа RESET_VECTOR (как и другие вектора) исчисляется от начала сегмента. Т.е., нельзя писать вторую строку без первой — компилятор вас не поймёт и разместит вектор по адресу 001Eh вместо положенных FFFEh. Адрес начала таблицы FFE0h и, соответственно, RESET_VECTOR от него в 30 байтах.
Т.к. разрядность контроллера 16 бит, то адреса тоже 16-ти разрядные, посему каждый вектор занимает 2 байта. А вот адресация памяти побайтная, а не пословная (как адресация памяти программ в AVR). Обратите внимание на это.
Как уже было сказано, в вектор помещается не команда перехода к обработчику, а непосредственно его адрес. Делается это третьей строкой "DC16 init", что означает «разместить по текущему адресу 16-ти битную константу значения адреса, определённого меткой init». Данная метка располагается в начале кода программы и значение её адреса вычисляется на этапе связывания (линковки). Как заметили читатели, DC16 означает «Define Constant 16 bit», директива аналогична DW.
Вообще, рекомендую почитать заголовочные файлы. Расположены они в каталоге «C:\Program Files\IAR Systems\Embedded Workbench Evaluation 4.0\430\inc\». Номер версии может отличаться от моего, тогда нужно подправить путь. «msp430.h» не содержит ничего существенного, но ссылается на заловочные файлы для конкретных серий, например «msp430x14x.h», как у меня.
Далее объявление сегмента стека "RSEG CSTACK". Стек располагается в конце ОЗУ и растёт вверх.
После него объявление сегмента кода программы "RSEG CODE". С этого места и начинается наша программа. По умолчанию, сегмент кода начинается с начала ПЗУ, если нет сегментов данных в нём. Если есть — то после них.
Кстати, RSEG означает «перемещаемый сегмент» (relocatable).
Команда
MOV #SFE(CSTACK), SP
заносит адрес стека в регистр-указатель. Директива SFE служит для вычисления адреса конца сегмента (любого).Внимание, запоминаем раз и навсегда! Значок # служит для определения непосредственно значения выражения. В противном случае оно считается адресом памяти, откуда надо взять данные. Исключение — команды переходов типа jmp, в них подставляются выражения без диеза (в команде call диез нужен!). В следующей части рассмотрим режимы адресации подробнее.
Также запомните — порядок операндов прямой, т.е. «откуда в куда».
Следующая команда
MOV.W #WDTPW+WDTHOLD,&WDTCTL
запрещает сторожевой таймер, записывая в его управляющий регистр предусмотренную для этого комбинацию бит.Перед тем как рассматривать код дальше, обратим внимание на конец текста программы. Там располагаются определения данных в ОЗУ и ПЗУ.
RSEG DATA16_N
RamArr:
DS16 ArrLn
Здесь определяются данные в ОЗУ. Директивой "RSEG DATA16_N" определяем сегмент неинициализируемых 16-ти разрядных данных для статических и глобальных переменных. По умолчанию располагаются в начале ОЗУ. Директивой "DS16 ArrLn" определяем массив 16-ти разрядных слов в количестве ArrLn. Метка RamArr нужна для обращения к этим данным с помощью символьного имени без необходимости указания абсолютного адреса (чтобы не высчитывать его вручную, да и менять состав данных так легче и меньше будет семантических ошибок).
RSEG DATA16_C
RomArr:
DC16 101, 11, 25, 657, 567, 217, 5732, 896, 123, 5467, 12, 65, 2345, 23, 98, 2398
Здесь мы определяем данные в ПЗУ. Директивой "RSEG DATA16_С" определяем сегмент постоянных 16-ти разрядных данных. Обратите внимание на разницу в именых сегментов. По умолчанию располагаются в начале ПЗУ. Директивой "DC16 ArrLn" определяем массив 16-ти разрядных слов со значениями, указанными в перечислении следом. При компиляции эти значения будут занесены в ПЗУ. Ну и не забываем метку.Вернемся к коду.
SortMain:
mov #ArrLn, R4
mov #RamArr, R5
mov #RomArr, R6
Первым действием заносим константу в регистр R4, он будет счётчиком. Диез не забываем, иначе получим не то, что ожидали.
Вторая команда заносит значение адреса метки RamArr в R5, третья — значение адреса метки RomArr в R6.
Если не поставим диез, получим значения из ячеек памяти по этим адресам — можете поэкспериментировать.
Теперь перенесём данные из ПЗУ в ОЗУ.
MoveRomToRam:
mov @R6+, 0(R5)
add #2, R5
dec R4
jnz MoveRomToRam
Первая команда перемещает данные из ячейки памяти, адрес которой в R6 в ячейку, адрес которой равен "R5 + (смещение, равное нулю)". Символ @ указывает на косвенную адресацию. При этом, содержимое R6 увеличивается на два, т.к. команда в таком виде обращается к словам, а не к байтам. К сожалению, нельзя написать "mov @R6+, @R5+" и даже "mov @R6+, @R5", т.к. это не поддерживается. Поэтому следующей командой мы вручную увеличиваем содержимое R5 на 2. После чего декрементируем счётчик и последней командой проверяем его на равенство нулю. Если не равен нулю, то идем опять за следующим значением, т.е. по метке MoveRomToRam. Если равен — выходим на следующую команду.
Последней командой
jmp $
зацикливаем код ($ — ссылка на счётчик команд, т.е. на текущее положение), дабы не улететь в сияющие чистотой дали пустого FLASH'а.На следующей… паре, мы рассмотрим режимы адресации и структуру памяти, а пока вам для затравки полная версия программы сортировки.
Ну а я поехал кататься на свежекупленном Т-34-85 :-), бо глаза от буковок разбегаются, а мозг требует засадить бронебойным кому-нибудь в боеукладку. До завтра!
#include "msp430.h" ; #define controlled include file
NAME main ; module name
PUBLIC main ; make the main label vissible
; outside this module
; Constant definition
ArrLn EQU 16
ORG 0FFFEh
DC16 init ; set reset vector to 'init' label
RSEG CSTACK ; pre-declaration of segment
RSEG CODE ; place program in 'CODE' segment
init: MOV #SFE(CSTACK), SP ; set up stack
main: ; main program
MOV.W #WDTPW+WDTHOLD,&WDTCTL ; Stop watchdog timer
SortMain:
; Переписать из ПЗУ в ОЗУ массив данных
mov #16, R4
mov #RamArr, R5
mov #RomArr, R6
MoveRomToRam:
mov @R6+, 0(R5)
add #2, R5
dec R4
jnz MoveRomToRam
SortingInit:
mov #(ArrLn-1), R4
mov #RamArr, R6
Sorting:
mov R4, R5
mov R6, R7
Sort1:
add #2, R7
cmp 0(R6), 0(R7)
jge Sort1NonSwap
mov 0(R6), R8
mov 0(R7), 0(R6)
mov R8, 0(R7)
Sort1NonSwap:
dec R5
jnz Sort1
add #2, R6
dec R4
jnz Sorting
BasicCycle:
JMP $ ; jump to current location '$'
; RAM definition
RSEG DATA16_N
RamArr:
DS16 ArrLn
; ROM definition
RSEG DATA16_C
RomArr:
DC16 101, 11, 25, 657, 567, 217, 5732, 896, 123, 5467, 12, 65, 2345, 23, 98, 2398
END

- +1
- 10 марта 2011, 04:32
- SerjT
Какой смысл использовать IAR если пишешь на ассемблере? Чего не хватает? Может msp-gcc, mspdebug и иже с ними. По ним мало материала.
IAR не имеет ограничений для ассемблера MSP430. Можно смело ставить kickstart и писать хоть 128К на асме.
Вот только лично я не вижу смысла писать под MSP430 на нём, код на Си очень неплохо ложится на кристалл. И если почитать даташиты, то там прямо указано, что система команд MSP430 разрабатывалась с учётом написания кода именно на Си.
Вот только лично я не вижу смысла писать под MSP430 на нём, код на Си очень неплохо ложится на кристалл. И если почитать даташиты, то там прямо указано, что система команд MSP430 разрабатывалась с учётом написания кода именно на Си.
>«Какой смысл использовать IAR если пишешь на ассемблере?»
Удобно и единообразно.
***
По статье.
Это все понятно. Единственное, чего я так и не понял (ни после раскуривания Programming Guide, ни после прочтения статьи, может, плохо читал) — это конструкции
mov @R6+,0(R5)
Т.е., причем тут 0([reg])?
Это что-ли по-сишному будет (псевдо-С, естесственно):
mov @Ry,n(Rx) <-> *(Rx+n)=*Ry
Так?
Вообще скажу честно, после AVR-asm тут система адресации кажется какой-то кривой и экзотической. Хотя, я не жалуюсь и прилежно курю доки и статьи, ибо сами контроллеры мне чрезвычайно понравились. Кстати говоря, связанный вопрос — генератор констант. Я так и не понял, как с ним работать…
Удобно и единообразно.
***
По статье.
Это все понятно. Единственное, чего я так и не понял (ни после раскуривания Programming Guide, ни после прочтения статьи, может, плохо читал) — это конструкции
mov @R6+,0(R5)
Т.е., причем тут 0([reg])?
Это что-ли по-сишному будет (псевдо-С, естесственно):
mov @Ry,n(Rx) <-> *(Rx+n)=*Ry
Так?
Вообще скажу честно, после AVR-asm тут система адресации кажется какой-то кривой и экзотической. Хотя, я не жалуюсь и прилежно курю доки и статьи, ибо сами контроллеры мне чрезвычайно понравились. Кстати говоря, связанный вопрос — генератор констант. Я так и не понял, как с ним работать…
И еще небольшой вопрос-уточнение:
Я правильно понимаю, что RSEG CODE значит то же самое, что и .CSEG в AVR (и остальное аналогично)?
Я правильно понимаю, что RSEG CODE значит то же самое, что и .CSEG в AVR (и остальное аналогично)?
Да, верно. Это объявления начала кода.
А в описании
mov @R6+,0(R5)
я ошибся. Команда верно написана, в описании ошибка — вместо «R6+(смещение, равное нулю)» читаем «R5+(смещение, равное нулю)». Да, суть правильная — вначале смещение перед скобками, в скобках регистр-указатель. Спасибо за найденную опечатку, сейчас исправлю.
А о генераторе констант — в следующей части, сегодня вечерком. Кратко — обращение к регистру R3 (и немного к R2) при разных режимах адресации заменяется на определённую константу. Это удобно тем, что экономится память и время — константа не прилагается к коду команды.
А в описании
mov @R6+,0(R5)
я ошибся. Команда верно написана, в описании ошибка — вместо «R6+(смещение, равное нулю)» читаем «R5+(смещение, равное нулю)». Да, суть правильная — вначале смещение перед скобками, в скобках регистр-указатель. Спасибо за найденную опечатку, сейчас исправлю.
А о генераторе констант — в следующей части, сегодня вечерком. Кратко — обращение к регистру R3 (и немного к R2) при разных режимах адресации заменяется на определённую константу. Это удобно тем, что экономится память и время — константа не прилагается к коду команды.
Вы слишком хорошего мнения обо мне :-)
Эту серию я вообще не знаю — дай Бог с 2xx и 4xx разобраться.
32-х битные от TI для меня загадка, особенно многоядрёные. И вообще я их боюсь :-)
Эту серию я вообще не знаю — дай Бог с 2xx и 4xx разобраться.
32-х битные от TI для меня загадка, особенно многоядрёные. И вообще я их боюсь :-)
Просто я себе заказал тестовые образцы из TI взял 3 шт LM3S5739, думал может знаете что с ними делать :-)
Ну, к примеру, народ брал личинок вкусных копать :-)
У меня самого несколько интересных всяких штук лежит, но руки не доходят до них и времени и сил нету.
К примеру — лежит сгоревшая Nokia 6300 жены, а в ней такой обалденный экран (320 * 240, 16 млн. цветов)! Так и хочется куда прикрутить. Но информации по ней ноль, а самому разбираться — проще к стене прикрутить. Или от моего Siemens M55 — инфы есть немного, но времени нет. Так и лежат, бедняги, пылью порастают.
У меня самого несколько интересных всяких штук лежит, но руки не доходят до них и времени и сил нету.
К примеру — лежит сгоревшая Nokia 6300 жены, а в ней такой обалденный экран (320 * 240, 16 млн. цветов)! Так и хочется куда прикрутить. Но информации по ней ноль, а самому разбираться — проще к стене прикрутить. Или от моего Siemens M55 — инфы есть немного, но времени нет. Так и лежат, бедняги, пылью порастают.
Добрый день.
Подскажите пожалуйста:
Адрес начала таблицы FFE0h и, соответственно, RESET_VECTOR от него в 30 байтах.
почему Reset_Vector (я так понимаю это просто константа, которую в данном проекте мы не используем, так?) не пишется в начало таблицы векторов прерываний? или там в начале что-то по дефолту записано?
Подскажите пожалуйста:
Адрес начала таблицы FFE0h и, соответственно, RESET_VECTOR от него в 30 байтах.
почему Reset_Vector (я так понимаю это просто константа, которую в данном проекте мы не используем, так?) не пишется в начало таблицы векторов прерываний? или там в начале что-то по дефолту записано?
- flamingo_Zzz
- 06 октября 2011, 16:45
- ↓
Не до конца понял вопрос, но попробую ответить.
RESET_VECTOR задаёт смещение в таблице векторов прерываний. По этоу адресу распологается адрес начала программы. Всё что надо там используется:
RSEG INTVEC; адрес FFE0
ORG RESET_VECTOR; вектор сброса — смещаемся на 30 байт на адрес FFFE
DC16 init; адрес с которого начнётся выполнение программы после сброса процессора
RESET_VECTOR задаёт смещение в таблице векторов прерываний. По этоу адресу распологается адрес начала программы. Всё что надо там используется:
RSEG INTVEC; адрес FFE0
ORG RESET_VECTOR; вектор сброса — смещаемся на 30 байт на адрес FFFE
DC16 init; адрес с которого начнётся выполнение программы после сброса процессора
теперь вообще ничего не понимаю(…
RSEG INTVEC здесь мы объявляем сегмент векторов прерываний, у данного мкк, таблица векторов прерываний начинается с адреса 0FFe0h, т.е. далее мы начинаем работать начиная с этого адреса. INTVEC — это служебное слово, обозначающее таблицу векторов прерываний?
ORG RESET_VECTOR в хелпе по msp430 сказано что ORG устанавливает положение счетчика. Почему и как здесь происходит смещение на 30 байт на адрес FFFE? поясните пожалуйста эту строчку.
DC16 init директива DC16 — генерирует 16-битную константу. Т.е. данной строчкой, мы генерируем 16-битную константу по тек.адресу(FFFE ?) которая представляет собой адрес, определяемый меткой init. По метке init, мы заносим адрес стека в регистр-указатель. зачем это делается?
поясните пожалуйста, что так, что не так.
заранее спасибо.
RSEG INTVEC здесь мы объявляем сегмент векторов прерываний, у данного мкк, таблица векторов прерываний начинается с адреса 0FFe0h, т.е. далее мы начинаем работать начиная с этого адреса. INTVEC — это служебное слово, обозначающее таблицу векторов прерываний?
ORG RESET_VECTOR в хелпе по msp430 сказано что ORG устанавливает положение счетчика. Почему и как здесь происходит смещение на 30 байт на адрес FFFE? поясните пожалуйста эту строчку.
DC16 init директива DC16 — генерирует 16-битную константу. Т.е. данной строчкой, мы генерируем 16-битную константу по тек.адресу(FFFE ?) которая представляет собой адрес, определяемый меткой init. По метке init, мы заносим адрес стека в регистр-указатель. зачем это делается?
поясните пожалуйста, что так, что не так.
заранее спасибо.
- flamingo_Zzz
- 06 октября 2011, 18:13
- ↑
- ↓
Нда.
RSEG — объявление сегмента. Сегмент — это область памяти, содержащая определенные данные. INTVEC — имя сегмента. Потом, при линковке, линкер располагает сегменты в соответствии с определенными правилами (например: INTVEC положить в флеш, по адресу начала таблицы векторов, .text разместить в любом месте флеша, .data разместить в любом месте ОЗУ). Данные внутри сегмента располагает частично компилер, частично линкер, как попало.
ORG — origin, указание по какому конкретному адресу (вообще или внутри сегмента) разместить следующий за директивой код. В данном случае RESET_VECTOR содержит адрес или 30 (смещение от начала сегмента), или 0xFFFE (абсолютный адрес вектора RESET), точно не знаю.
Ну и последняя директива — DC16 — кладет по указанному двумя предыдущими директивами адресу 16-битную константу — адрес обработчика прерывания (в данном случае, это метка init). При возникновении прерывания RESET камень читает эту константу и прыгает по указанному ей адресу (в отличие от большинства других камней, которые прыгают непосредственно на адрес вектора, по которому обычно расположена команда JMP HandlerName).
Начиная с метки init уже идет обычный код. Инициализация стека и все такое. Это уже стандартно для всех МК/ЦП со стеком в ОЗУ, включая ARM, AVR, x86.
RSEG — объявление сегмента. Сегмент — это область памяти, содержащая определенные данные. INTVEC — имя сегмента. Потом, при линковке, линкер располагает сегменты в соответствии с определенными правилами (например: INTVEC положить в флеш, по адресу начала таблицы векторов, .text разместить в любом месте флеша, .data разместить в любом месте ОЗУ). Данные внутри сегмента располагает частично компилер, частично линкер, как попало.
ORG — origin, указание по какому конкретному адресу (вообще или внутри сегмента) разместить следующий за директивой код. В данном случае RESET_VECTOR содержит адрес или 30 (смещение от начала сегмента), или 0xFFFE (абсолютный адрес вектора RESET), точно не знаю.
Ну и последняя директива — DC16 — кладет по указанному двумя предыдущими директивами адресу 16-битную константу — адрес обработчика прерывания (в данном случае, это метка init). При возникновении прерывания RESET камень читает эту константу и прыгает по указанному ей адресу (в отличие от большинства других камней, которые прыгают непосредственно на адрес вектора, по которому обычно расположена команда JMP HandlerName).
Начиная с метки init уже идет обычный код. Инициализация стека и все такое. Это уже стандартно для всех МК/ЦП со стеком в ОЗУ, включая ARM, AVR, x86.
Если я правильно понял вопрос — флеш в MSP430 выравнивается по концу адресного пространства (т.е. (0x10000-FlashSize)..0xFFFF), соответственно вектора расположены в конце флеша и в обратном порядке — RESET среди них последний и расположен по фиксированному адресу 0xFFFE, а последний вектор имеет наименьший адрес, соответсвующий началу таблицы. RSEG INTVEC же указывает на начало таблицы прерываний в флеше, где расположен последний вектор прерывания, и поэтому для вектора RESET нужно явно указывать смещение в виде директивы ORG.
Добрый день. Подскажите, можно ли «подружить» msp430g2231(14 ножек) с дисплеем WG12864(20 ножек)? есть ли возможность использовать 4-х битную передачу данных?
- flamingo_Zzz
- 30 октября 2011, 20:41
- ↓
Комментарии (18)
RSS свернуть / развернуть