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

Комментарии (18)

RSS свернуть / развернуть
Комментируем, не стесняемся. Хочу знать, чего ещё не хватает здесь. Надо будет — подкорректируем.
0
Какой смысл использовать IAR если пишешь на ассемблере? Чего не хватает? Может msp-gcc, mspdebug и иже с ними. По ним мало материала.
0
IAR не имеет ограничений для ассемблера MSP430. Можно смело ставить kickstart и писать хоть 128К на асме.
Вот только лично я не вижу смысла писать под MSP430 на нём, код на Си очень неплохо ложится на кристалл. И если почитать даташиты, то там прямо указано, что система команд MSP430 разрабатывалась с учётом написания кода именно на Си.
0
>«Какой смысл использовать IAR если пишешь на ассемблере?»

Удобно и единообразно.

***

По статье.

Это все понятно. Единственное, чего я так и не понял (ни после раскуривания Programming Guide, ни после прочтения статьи, может, плохо читал) — это конструкции

mov @R6+,0(R5)

Т.е., причем тут 0([reg])?

Это что-ли по-сишному будет (псевдо-С, естесственно):

mov @Ry,n(Rx) <-> *(Rx+n)=*Ry

Так?

Вообще скажу честно, после AVR-asm тут система адресации кажется какой-то кривой и экзотической. Хотя, я не жалуюсь и прилежно курю доки и статьи, ибо сами контроллеры мне чрезвычайно понравились. Кстати говоря, связанный вопрос — генератор констант. Я так и не понял, как с ним работать…
0
  • avatar
  • _YS_
  • 10 марта 2011, 15:17
И еще небольшой вопрос-уточнение:

Я правильно понимаю, что RSEG CODE значит то же самое, что и .CSEG в AVR (и остальное аналогично)?
0
Да, верно. Это объявления начала кода.

А в описании
mov @R6+,0(R5)
я ошибся. Команда верно написана, в описании ошибка — вместо «R6+(смещение, равное нулю)» читаем «R5+(смещение, равное нулю)». Да, суть правильная — вначале смещение перед скобками, в скобках регистр-указатель. Спасибо за найденную опечатку, сейчас исправлю.
А о генераторе констант — в следующей части, сегодня вечерком. Кратко — обращение к регистру R3 (и немного к R2) при разных режимах адресации заменяется на определённую константу. Это удобно тем, что экономится память и время — константа не прилагается к коду команды.
+1
А серию статей по 32bit МК от TI не собираетесь писать?
0
Вы слишком хорошего мнения обо мне :-)
Эту серию я вообще не знаю — дай Бог с 2xx и 4xx разобраться.
32-х битные от TI для меня загадка, особенно многоядрёные. И вообще я их боюсь :-)
0
Просто я себе заказал тестовые образцы из TI взял 3 шт LM3S5739, думал может знаете что с ними делать :-)
0
Ну, к примеру, народ брал личинок вкусных копать :-)
У меня самого несколько интересных всяких штук лежит, но руки не доходят до них и времени и сил нету.
К примеру — лежит сгоревшая Nokia 6300 жены, а в ней такой обалденный экран (320 * 240, 16 млн. цветов)! Так и хочется куда прикрутить. Но информации по ней ноль, а самому разбираться — проще к стене прикрутить. Или от моего Siemens M55 — инфы есть немного, но времени нет. Так и лежат, бедняги, пылью порастают.
0
Добрый день.
Подскажите пожалуйста:
Адрес начала таблицы FFE0h и, соответственно, RESET_VECTOR от него в 30 байтах.
почему Reset_Vector (я так понимаю это просто константа, которую в данном проекте мы не используем, так?) не пишется в начало таблицы векторов прерываний? или там в начале что-то по дефолту записано?
0
Не до конца понял вопрос, но попробую ответить.
RESET_VECTOR задаёт смещение в таблице векторов прерываний. По этоу адресу распологается адрес начала программы. Всё что надо там используется:
RSEG INTVEC; адрес FFE0
ORG RESET_VECTOR; вектор сброса — смещаемся на 30 байт на адрес FFFE
DC16 init; адрес с которого начнётся выполнение программы после сброса процессора
0
теперь вообще ничего не понимаю(…

RSEG INTVEC здесь мы объявляем сегмент векторов прерываний, у данного мкк, таблица векторов прерываний начинается с адреса 0FFe0h, т.е. далее мы начинаем работать начиная с этого адреса. INTVEC — это служебное слово, обозначающее таблицу векторов прерываний?

ORG RESET_VECTOR в хелпе по msp430 сказано что ORG устанавливает положение счетчика. Почему и как здесь происходит смещение на 30 байт на адрес FFFE? поясните пожалуйста эту строчку.

DC16 init директива DC16 — генерирует 16-битную константу. Т.е. данной строчкой, мы генерируем 16-битную константу по тек.адресу(FFFE ?) которая представляет собой адрес, определяемый меткой init. По метке init, мы заносим адрес стека в регистр-указатель. зачем это делается?

поясните пожалуйста, что так, что не так.
заранее спасибо.
0
Нда.

RSEG — объявление сегмента. Сегмент — это область памяти, содержащая определенные данные. INTVEC — имя сегмента. Потом, при линковке, линкер располагает сегменты в соответствии с определенными правилами (например: INTVEC положить в флеш, по адресу начала таблицы векторов, .text разместить в любом месте флеша, .data разместить в любом месте ОЗУ). Данные внутри сегмента располагает частично компилер, частично линкер, как попало.

ORG — origin, указание по какому конкретному адресу (вообще или внутри сегмента) разместить следующий за директивой код. В данном случае RESET_VECTOR содержит адрес или 30 (смещение от начала сегмента), или 0xFFFE (абсолютный адрес вектора RESET), точно не знаю.

Ну и последняя директива — DC16 — кладет по указанному двумя предыдущими директивами адресу 16-битную константу — адрес обработчика прерывания (в данном случае, это метка init). При возникновении прерывания RESET камень читает эту константу и прыгает по указанному ей адресу (в отличие от большинства других камней, которые прыгают непосредственно на адрес вектора, по которому обычно расположена команда JMP HandlerName).

Начиная с метки init уже идет обычный код. Инициализация стека и все такое. Это уже стандартно для всех МК/ЦП со стеком в ОЗУ, включая ARM, AVR, x86.
0
А вообще — иди-ка скури рандомную книжку по ассемблеру. По какому именно большого значения не имеет. Хоть по x86 (благо их много и можно найти годную). Подобные вопросы отпадут (если не отпадут — книжка была плохая или же тебе рано лезть в ассемблер).
0
Если я правильно понял вопрос — флеш в MSP430 выравнивается по концу адресного пространства (т.е. (0x10000-FlashSize)..0xFFFF), соответственно вектора расположены в конце флеша и в обратном порядке — RESET среди них последний и расположен по фиксированному адресу 0xFFFE, а последний вектор имеет наименьший адрес, соответсвующий началу таблицы. RSEG INTVEC же указывает на начало таблицы прерываний в флеше, где расположен последний вектор прерывания, и поэтому для вектора RESET нужно явно указывать смещение в виде директивы ORG.
0
Добрый день. Подскажите, можно ли «подружить» msp430g2231(14 ножек) с дисплеем WG12864(20 ножек)? есть ли возможность использовать 4-х битную передачу данных?
0
Я так понимаю указанная в статье IDE работать с контроллерами MSP430G2553 не будет?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.