Немного максималистическая попытка сделать статью в стиле "все-в-одном", а именно: разобрать базовую периферию MSP430 и заодно использовать эту тему как ознакомительную для Proteus.
К сожалению, как выяснилось, в Proteus симуляция MSP430 не совсем полная, поэтому реального микроконтроллера Proteus заменить конечно не сможет. Однако, с некоторыми оговорками это все-таки замечательная платформа для отладки схем и различных алгоритмов. Ниже будет наглядно показано как отладить работу программного UART передатчика с помощью Proteus.
Я начинал статью с Proteus_8.3, а заканчивал ее под Proteus_8.5. И там и там имеются неточности если сравнивать с работой реального чипа, не следует забывать, что это всего лишь модель. И все-таки я бы посоветовал использовать версию 8.5 по той причине что там более-менее корректно работает таймер. У меня были сложности большими частотами, но возможно сказалось ограничение по производительности виртуальной машины.
Честно говоря, я не слишком расписывал темы: что такое Proteus, зачем он нужен, как его устанавливать и что значат все эти кнопочки. Подобных руководств достаточное количество на YouTube.
В качестве примера рассматривается чип MSP430G2453 в 28-пиновом корпусе. Но весь материал можно экстраполировать на всю линейку MSP430x2xx.
При запуске Proteus покажет стартовое окно, в котором для создания нового проекта, следует выбрать опцию "New Project":
В следующем окне нужно будет задать имя проекта и его рабочий каталог:
Размер рабочей области для задания принципиальной схемы. Влияет на вывод схемы на печать.
Размер печатной платы. Ее пока создавать не будем:
Использование микроконтроллера и прошивки для него. В данном случае задается сразу, хотя можно будет добавить потом в уже рабочий проект. Компилятор помечен как "not configured" потому что он пока не установлен.
Проверяем опции проекта, и если все в порядке, жмем "finish":
Перед нами откроется редактор кода, а в окошке логов будет напоминание о том, что компилятор еще не установлен:
Через "Меню->System->Compilers Configuration" попадем в настройки компиляторов:
Нажимаем кнопку "Download" напротив нужного компилятора:
Установщик предупреждает, что для установки компилятора потребуются права администратора:
Здесь нужно будет снять подчеркнутый чекбокс, иначе установщик не сможет выполнить свою работу. Возможно можно будет поставить в домашний каталог, но лучше этого не делать.
Жмем "Next":
Компиляторы лучше ставить в корень, это сокращает пути к файлам:
Для начала установки нужно нажать "Install":
Процесс установки:
Установка завершена, жмем "Finish"
Сейчас Proteus еще раз просканирует все компиляторы:
В случае успешной установки, напротив нужного компилятора будет стоять отметка "Yes" и прописан рабочий каталог:
После нажатия на "Ok" снова откроется редактор кода, но теперь будет сообщение о том, что нужный компилятор был успешно обнаружен:
Составляем простейший Blink:
#include <msp430g2453.h> int main (void) { int i; WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = BIT0; // set P1.0 as Push Pull mode(PP) P1OUT &=~BIT0; while (1) { P1OUT ^= BIT0; for(i=0;i<0x6000;i++); } return 0; }
И жмем на иконку "Build":
Что бы поменять опции компиляции, нужно зайти в настройки проекта:
И в настройках переключиться на вкладку "Option":
Теперь нужно щелкнуть по вкладке "ISIS Schematic Capture", это и будет симулятор схем. Перед нами будет чип, который мы только что запрограммировали. И если запустить симуляцию(зеленый треугольник внизу), то чип сразу начнет работать. Отобразится это на индикации уровней(синие и красные квадратики возле выводов микросхемы):
Здесь синие квадратики означают что вывод подтянут к земле, красный - к питанию, серый - вывод находится в высокоимпеданснном HiZ режиме. Здесь можно увидеть несоответствие в работы модели. В реальном чипе все выводы первого порта должны находиться в HiZ режиме, т.е. должны быть обозначены серыми квадратиками. И только P1.0 должен переключаться между синими и красными квадратиками.
Двойной щелчок по компоненту выдаст нам окошко настройки компонента. Здесь можно задать корпус микросхемы, в моем случае это TSSOP28, а также загрузить в чип свою прошивку. Загружать следует elf файл:
После выбора корпуса TSSOP28 видно, что на микросхеме справа появился третий порт. Теперь нужно сконфигурировать шину питания. Для этого через "Меню->Configure Power Rails" вызываем окошко "Power Rails Configuration":
Через выпадающий список выбираем шину питания VCC/VDD и указываем уровень напряжения 3.3 Вольта:
Теперь нужно добавить на схему землю. Делается это через иконку "Terminals Mode" -> "GROUND"
Теперь нужно добавить несколько элементов в свой список компонентов. Сначала щелчком по иконке справа "Component Mode" открывается свой список компонентов, пока там только один микроконтроллер. Щелчок по иконке с буквой "P" открывает библиотеку компонентов. Здесь нам нужен обобщенный компонент светодиода. Обобщенный означает, что мы можем менять его параметры "на ходу", они не привязаны к характеристикам конкретного изделия. В строке поиска вводим "red led" и двойным щелчком добавляем нужный элемент в свой набор компонентов:
Так же нужен будет обобщенный элемент "resistor":
И кнопка, т.е. "button":
Закрыв библиотеку компонентов, попробуем собрать светодиодную цепочку: светодиод + резистор. Должно получится как-то так:
Должен заметить что данная модель светодиода лишь ограниченная по возможностям модель. Бессмысленно на него, например, подавать ШИМ. Его анимация не сможет работать на частоте 38 КГц.
Двойной щелчок на резисторе откроет окно редактирования параметров резистора. Здесь надо будет уменьшить сопротивление резистора с дефолтных 10К до, например, 150 Ом.
Также двойным щелчком открываем окно редактирования светодиода, и начинаем считать.
По закону Ома сила тока I=(U_source - U_led)/R;
Здесь: R=150, U_source=3.3V, U_led=2.2V, итого I=1.1/150 ~ 7.3mA.
Т.о. чтобы наш светодиод нормально загорался нужно чтобы ему для работы хватало менее 7.3mA. Итого заменяем дефолтное значение тока светодиода с 10мА до 5мА:
Все сказанное относилось к Proteus 8.3. В версии 8.5 через выводы порта идет очень малый ток, такое ощущение, что они подключены через подтягивающие резисторы. Здесь нужно указывать рабочий ток светодиода ~0.03mA.
Теперь включаем симуляцию, и если все было сделано правильно, должно работать как-то так:
Тема была рассмотрена немного раньше. Хочется также напомнить ссылку на перевод официального руководства по MSP430x2xx: ": СЕМЕЙСТВО МИКРОКОНТРОЛЛЕРОВ MSP430x2xx. Архитектура. Программирование. Разработка приложений."
В MSP430 может быть до восьми GPIO портов включительно. Каждый порт имеет четыре 8-битных регистров ввода-вывода(РВВ), для конфигурации своего порта GPIO:
Сейчас попробуем реализовать пример взятый отсюда. Для этого нам нужно будет добавить на схему генератор тактового сигнала. Делается это через иконку справа: "Generator Mode" -> DPULSE:
Двойной щелчок по компоненту откроет окно редактирования параметров генератора. Здесь нужно выставить непрерывный режим работы(режим Clock), и частоту 1Гц, чтобы это можно было наблюдать визуально:
Теперь напишем программу которая будет переключать светодиод от тактового сигнала:
#include <msp430g2453.h> #define LED BIT0 #define BTN BIT2 int main (void) { WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = LED; // set P1.0 as Push Pull mode(PP) P1DIR &=~BTN; // HiZ mode P1OUT &=~LED; while (1) { if ((P1IN & BTN) >0 ) P1OUT|=LED; else P1OUT &=~LED; } return 0; }
Должно работать как-то так:
Первые два порта могут обрабатывать внешние прерывания. Для работы с ними, у них имеются дополнительные регистры:
Попробуем переписать предыдущую программу на прерываниях. Текст программы был взят отсюда:
#include <msp430g2453.h> #define LED BIT0 #define BTN BIT2 #pragma vector=PORT1_VECTOR __interrupt void Port1(void) { P1OUT ^= LED; // inverse led P1IFG &=~BTN; // clearing interrupt flag } int main (void) { WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = LED; // set P1.0 as Push Pull mode(PP) P1OUT &=~LED; P1DIR &=~BTN; // HiZ mode P1IES &=~BTN; // rising front P1IFG &=~BTN; // interrupt flag clearing P1IE |= BTN; // enable external interrupt __enable_interrupt(); while (1); return 0; }
На самом деле это не точное соотвествие предыдущей программе:
Что бы полностью имитировать ее, понадобиться задействовать еще один пин с внешним прерыванием. Чтобы один ловил сигнал по возрастающему фронту и зажигал светодиод, а другой по падающему, он бы гасил светодиод.
В данном случае это не суть важно. Что бы по настоящему использовать bit-banging нам нужны задержки по таймеру.
В MSP430G2453 имеется два 16-битных таймера типа TIMER_A, с номинально тремя каналами на каждый таймер, а фактически двумя каналами на таймер.
В заголовочном файле регистры таймеров описаны следующим образам:
/************************************************************ * Timer0_A3 ************************************************************/ #define __MSP430_HAS_TA3__ /* Definition to show that Module is available */ #define TA0IV 0x012E /* Timer0_A3 Interrupt Vector Word */ #define TA0CTL 0x0160 /* Timer0_A3 Control */ #define TA0CCTL0 0x0162 /* Timer0_A3 Capture/Compare Control 0 */ #define TA0CCTL1 0x0164 /* Timer0_A3 Capture/Compare Control 1 */ #define TA0CCTL2 0x0166 /* Timer0_A3 Capture/Compare Control 2 */ #define TA0R 0x0170 /* Timer0_A3 */ #define TA0CCR0 0x0172 /* Timer0_A3 Capture/Compare 0 */ #define TA0CCR1 0x0174 /* Timer0_A3 Capture/Compare 1 */ #define TA0CCR2 0x0176 /* Timer0_A3 Capture/Compare 2 */
/************************************************************ * Timer1_A3 ************************************************************/ #define __MSP430_HAS_T1A3__ /* Definition to show that Module is available */ #define TA1IV 0x011E /* Timer1_A3 Interrupt Vector Word */ #define TA1CTL 0x0180 /* Timer1_A3 Control */ #define TA1CCTL0 0x0182 /* Timer1_A3 Capture/Compare Control 0 */ #define TA1CCTL1 0x0184 /* Timer1_A3 Capture/Compare Control 1 */ #define TA1CCTL2 0x0186 /* Timer1_A3 Capture/Compare Control 2 */ #define TA1R 0x0190 /* Timer1_A3 */ #define TA1CCR0 0x0192 /* Timer1_A3 Capture/Compare 0 */ #define TA1CCR1 0x0194 /* Timer1_A3 Capture/Compare 1 */ #define TA1CCR2 0x0196 /* Timer1_A3 Capture/Compare 2 */
Здесь, TAxR - счетчик таймера, TAxCTL - общий конфигурационный регистр, TAxIV - хранит маску прерывания, TAxCCRx - рабочие регистры каналов, TAxCCTLx - конфигурационные регистры каналов:
Все регистры таймеров 16-битные!
Детальное описание регистров таймера TIMER_A можно посмотреть под спойлером:
показать детальное описание регистров таймера TIMER_AВ заголовочном файле для регистра TAxIV определены следующие именованные константы:
/* T0_A3IV Definitions */ #define TA0IV_NONE (0x0000) /* No Interrupt pending */ #define TA0IV_TACCR1 (0x0002) /* TA0CCR1_CCIFG */ #define TA0IV_TACCR2 (0x0004) /* TA0CCR2_CCIFG */ #define TA0IV_6 (0x0006) /* Reserved */ #define TA0IV_8 (0x0008) /* Reserved */ #define TA0IV_TAIFG (0x000A) /* TA0IFG */
/* T1_A3IV Definitions */ #define TA1IV_NONE (0x0000) /* No Interrupt pending */ #define TA1IV_TACCR1 (0x0002) /* TA1CCR1_CCIFG */ #define TA1IV_TACCR2 (0x0004) /* TA1CCR2_CCIFG */ #define TA1IV_TAIFG (0x000A) /* TA1IFG */
Попробуем составить Blink на прерывании по переполнению счетчика таймера (исходник взят отсюда):
#include <msp430g2453.h> #include <sys/types.h> #define LED BIT0 #define BTN BIT2 volatile uint16_t count; #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_OVF(void) { switch( TA0IV ) { case TA0IV_TACCR1: break; // CCR1 not used case TA0IV_TACCR2: break; // CCR2 not used case TA0IV_TAIFG: count++; // overflow break; } if (count == 2) { P1OUT ^= LED; count=0; } } int main (void) { count=0; WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = LED; // set P1.0 as Push Pull mode(PP) P1OUT &=~LED; TA0CTL=TASSEL_2 | MC_2 | ID_3 | TACTL | TAIE; /* TASSEL_2 =use SMCLK; MC_2 =continous up; ID_3 =prescaler = 8; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ __enable_interrupt(); while (1); return 0; }
Так как таймер здесь тактируется от SMCLK, то для того чтобы симуляция корректно работала, на схеме нужно открыть окно редактирования чипа MSP430G2453 и задать частоту шины SMCLK:
В версии Proteus 8.5 тактовые шины можно не трогать, достаточно будет включить в начало программы следующие команды:
DCOCTL=CALDCO_1MHZ; BCSCTL1=CALBC1_1MHZ;
т.е. сконфигурировать тактовые шины также же как на реальном чипе. Версия Proteus 8.3 эти команды "не переваривает".
Теперь нужно написать функцию, которая бы считала не секунды как в предыдущем примере, а миллисекунды. Однако на обычном переполнении такую функцию написать не получится, т.к. переполнение 16-битного счетчика таймера на частоте 16МГц случается всего 32 раза. Но как было видно из описания регистра TACTL, у таймера есть режим, когда он считает не до максимального значения, а до значения хранящееся в TAxCCR0 после чего сбрасывается и начинает счет заново.
Т.о. Если частота чипа 1МГц, то чтобы получить счетчик с частотой 1КГц, период которого и будет 1мс, нужно чтобы этот счетчик считал до тысячи. Или до 500, если использовать предделитель на 2, или до 250 если предделитель 4.
Пример реализации:
#include <msp430g2453.h> #include <sys/types.h> #define LED BIT0 volatile uint16_t count; #pragma vector=TIMER0_A1_VECTOR __interrupt void Timer0_OVF(void) { switch( TA0IV ) { case TA0IV_TACCR1: break; // CCR1 not used case TA0IV_TACCR2: break; // CCR2 not used case TA0IV_TAIFG: if (count) count--; // overflow break; } } void wait_ms(uint16_t ms) { count=ms; while(count); } int main (void) { count=0; WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = LED; // set P1.0 as Push Pull mode(PP) P1OUT &=~LED; TA0CTL=TASSEL_2 | MC_1 | ID_0 | TACTL | TAIE; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ TA0CCR0=1000; __enable_interrupt(); while (1) { wait_ms(1000); P1OUT ^= LED; } return 0; }
Здесь в TA0CTL поменяли параметр MC_2 на MC_1, чтобы установить режим счета до TA0CCR0.
Для каждого таймера TIMER_A имеется два вектора прерывания:
/************************************************************ * Таблица прерываний для MSP430G2453 (offset from 0xFFE0) ************************************************************/ #define TRAPINT_VECTOR (0x0000) /* 0xFFE0 TRAPINT */ #define PORT1_VECTOR (0x0004) /* 0xFFE4 Port 1 */ #define PORT2_VECTOR (0x0006) /* 0xFFE6 Port 2 */ #define ADC10_VECTOR (0x000A) /* 0xFFEA ADC10 */ #define USCIAB0TX_VECTOR (0x000C) /* 0xFFEC USCI A0/B0 Transmit */ #define USCIAB0RX_VECTOR (0x000E) /* 0xFFEE USCI A0/B0 Receive */ #define TIMER0_A1_VECTOR (0x0010) /* 0xFFF0 Timer0)A CC1, TA0 */ #define TIMER0_A0_VECTOR (0x0012) /* 0xFFF2 Timer0_A CC0 */ #define WDT_VECTOR (0x0014) /* 0xFFF4 Watchdog Timer */ #define COMPARATORA_VECTOR (0x0016) /* 0xFFF6 Comparator A */ #define TIMER1_A1_VECTOR (0x0018) /* 0xFFF8 Timer1_A CC1-4, TA1 */ #define TIMER1_A0_VECTOR (0x001A) /* 0xFFFA Timer1_A CC0 */ #define NMI_VECTOR (0x001C) /* 0xFFFC Non-maskable */ #define RESET_VECTOR (0x001E) /* 0xFFFE Reset [Highest Priority] */ /************************************************************ * Взято из "msp430g2453.h" ************************************************************/
Прерывание с вектором TIMER0_A1_VECTOR, которое использовалось в предыдущих примерах, отрабатывает по: 1) переполнению счетчика TAR; 2) по каналу CC1; 3) по каналу CC2.
Прерывание с вектором TIMER0_A0_VECTOR отрабатывает только по каналу CC0.
Можно переписать предыдущий пример для работы на втором прерывании:
#include <msp430g2453.h> #include <sys/types.h> #define LED BIT0 volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0_OVF(void) { if(count) count--; } void wait_ms(uint16_t ms){ count=ms; TA0CCTL0=CCIE; // run interrupt while(count); // wait ms TA0CCTL0=0; // stop interrupt } int main (void) { count=1000; WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P1DIR = LED; // set P1.0 as Push Pull mode(PP) P1OUT &=~LED; #ifdef HARDWARE // setup 16MHz DCOCTL=CALDCO_16MHZ; BCSCTL1=CALBC1_16MHZ; #endif TA0CTL=TASSEL_2 | MC_1 | ID_0 | TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; */ #ifdef HARDWARE TA0CCR0=16000; // if 16MHz #else TA0CCR0=1000; // if 1MHz #endif TA0CCTL0=0; __enable_interrupt(); while (1) { wait_ms(1000); P1OUT ^= LED; } return 0; }
В данном случае анимация светодиода в Proteus_8.3 начинает барахлить(в версии 8.5 все работает должным образом), светодиод мигает в два раза чаще чем нужно, поэтому я использовал директивы препроцессора для различной компиляции исходника: для Proteus_8.3 и для реального чипа. Здесь для для реального чипа устанавливается частота 16MHz командами: "DCOCTL=CALDCO_16MHZ; BCSCTL1=CALBC1_16MHZ". Proteus_8.3, как я уже говорил, эти команды "не переваривает". Для сборки прошивки под реальный чип я использовал следующий Makefile:
MCU=msp430g2453 OBJCOPY=msp430-objcopy CC=msp430-gcc CFLAGS=-mmcu=$(MCU) -DHARDWARE -Os -Wall LDFLAGS=-mmcu=$(MCU) OBJ=main.o TARGET=blink .PHONY: all clean %.o: %.c $(CC) -c -o $@ $< $(CFLAGS) all: $(OBJ) $(CC) $(LDFLAGS) -o $(TARGET).elf $(OBJ) $(OBJCOPY) -O ihex $(TARGET).elf $(TARGET).hex clean: @rm -v *.elf *.hex $(OBJ)
На моей MSP430G2453IPW28 все работает как надо.
Как не трудно догадаться, PWM c 16-битным счетчиком получается довольно низкочастотным. В методичке с сайта университета Эль Пассо (альтернативная ссылка) есть такая табличка:
Для того чтобы не замечать моргание светодиода, нужна частота не менее 100Гц, а для бесшумной работы обмоток двигателей частота должна быть от 25 КГц. Поэтому используется режим работы таймера со счетом до значения CCR0, что позволяет гибко на настраивать частоту PWM, но оставляет нам только два канала PWM с каждого таймера. Должен заметить, что нулевой канал не совсем бесполезен при таком режиме работы таймера. Его можно использовать для счетчика, если частота не меняется постоянно.
PWM на таймере TIMER0_A3 в Proteus_8.3 мне "завести" не удалось, хотя в реальном чипе все работало идеально. А вот TIMER1_A3 оказался вполне рабочим. Пример программы для PWM на первом канале таймера TIMER1_A3:
#include <msp430g2453.h> #include <sys/types.h> #define LED BIT1 volatile uint16_t count; volatile uint16_t pwm; #pragma vector=TIMER1_A0_VECTOR __interrupt void Timer1_OVF(void) { if(count) count--; } int main (void) { count=1000;pwm=0; WDTCTL=WDTPW | WDTHOLD; //turn off watchdog P2SEL |= LED; // TA1.1 output P2DIR |= LED; TA1CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; */ TA1CCTL0=CCIE; // for time counter TA1CCTL1=OUTMOD_7; // pwm mode TA1CCR1=0; TA1CCR0=1000; // period __enable_interrupt(); while (1) { while(count); pwm=(pwm<900) ? pwm+100: 0; TA1CCR1=pwm; count=1000; } return 0; }
Этот пример одинаково работает как в Proteus так и на реальном чипе. Правда плавное гашение светодиода в Proteus мы увидеть не сможем. Поэтому, чтобы увидеть как работает ШИМ, нужно будет добавить на схему осциллограф:
Работает это как-то так:
Для каждого канала PWM в качестве рабочего вывода могут использоваться разные пины микросхемы. Для примера, ниже подчеркнуты опциональные выводы PWM для канала TA0.1(нулевой таймер, первый канал):
Включения вывода микросхемы в режим PWM осуществляется комбинацией команд: PxSEL1 |= ПИН; PxDIR |=ПИН;
Все функции выводов микросхемы MSP430G2453 и способы переключения между ними приведены в таблицах ниже:
показать таблицуТаблица переключения между функциями портов GPIO:
Показать таблицыАппаратные протоколы в MSP430x2xx вынесены в коммуникационные модули: USI и UCSI. Последний реализует режим UART.
В упомянутой выше методичке университета Эль Пассо (альтернативная ссылка) есть пример настройки UART:
Так же хороший пример минимальной настройки UART приведен здесь: G2553 Hardware UART "Hello World" Example
Обычно самое сложное в настройках UART для различных микроконтроллеров - это правильно указать скорость порта в понятном для микроконтроллера виде. MSP430 интересен еще тем, что UART имеет два режима: низкоскоростной и высокоскоростной. Первый режим характеризуется тем, что может работать от часового кварца.
Таблица значений конфигурационных регистров для низкоскоростного режима:
Здесь все просто. Третий столбец, это целочисленный результат деления первого столбца на второй. 1000000/9600 =~ 104; 16000000/9600 =~ 1600; Т.е. третий столбец это количество тактов, которые должен сделать микроконтроллер для передачи одного бита.
Таблица значений конфигурационных регистров для высокоскоростного режима:
Здесь третий столбец, это те же значения, что и из предыдущей таблички, только поделенные на 16. Т.е. в этом режиме таймер UART работает с предделителем.
Регистры USCI модуля:
Детальное описание регистров под спойлером:
Показать детальное описание регистроврегистры и константы модуля UART
/************************************************************ * USCI ************************************************************/ #define __MSP430_HAS_USCI__ /* Definition to show that Module is available */ #define UCA0CTL0_ 0x0060 /* USCI A0 Control Register 0 */ sfrb(UCA0CTL0, UCA0CTL0_); #define UCA0CTL1_ 0x0061 /* USCI A0 Control Register 1 */ sfrb(UCA0CTL1, UCA0CTL1_); #define UCA0BR0_ 0x0062 /* USCI A0 Baud Rate 0 */ sfrb(UCA0BR0, UCA0BR0_); #define UCA0BR1_ 0x0063 /* USCI A0 Baud Rate 1 */ sfrb(UCA0BR1, UCA0BR1_); #define UCA0MCTL_ 0x0064 /* USCI A0 Modulation Control */ sfrb(UCA0MCTL, UCA0MCTL_); #define UCA0STAT_ 0x0065 /* USCI A0 Status Register */ sfrb(UCA0STAT, UCA0STAT_); #define UCA0RXBUF_ 0x0066 /* USCI A0 Receive Buffer */ const_sfrb(UCA0RXBUF, UCA0RXBUF_); #define UCA0TXBUF_ 0x0067 /* USCI A0 Transmit Buffer */ sfrb(UCA0TXBUF, UCA0TXBUF_); #define UCA0ABCTL_ 0x005D /* USCI A0 LIN Control */ sfrb(UCA0ABCTL, UCA0ABCTL_); #define UCA0IRTCTL_ 0x005E /* USCI A0 IrDA Transmit Control */ sfrb(UCA0IRTCTL, UCA0IRTCTL_); #define UCA0IRRCTL_ 0x005F /* USCI A0 IrDA Receive Control */ sfrb(UCA0IRRCTL, UCA0IRRCTL_); #define UCB0CTL0_ 0x0068 /* USCI B0 Control Register 0 */ sfrb(UCB0CTL0, UCB0CTL0_); #define UCB0CTL1_ 0x0069 /* USCI B0 Control Register 1 */ sfrb(UCB0CTL1, UCB0CTL1_); #define UCB0BR0_ 0x006A /* USCI B0 Baud Rate 0 */ sfrb(UCB0BR0, UCB0BR0_); #define UCB0BR1_ 0x006B /* USCI B0 Baud Rate 1 */ sfrb(UCB0BR1, UCB0BR1_); #define UCB0I2CIE_ 0x006C /* USCI B0 I2C Interrupt Enable Register */ sfrb(UCB0I2CIE, UCB0I2CIE_); #define UCB0STAT_ 0x006D /* USCI B0 Status Register */ sfrb(UCB0STAT, UCB0STAT_); #define UCB0RXBUF_ 0x006E /* USCI B0 Receive Buffer */ const_sfrb(UCB0RXBUF, UCB0RXBUF_); #define UCB0TXBUF_ 0x006F /* USCI B0 Transmit Buffer */ sfrb(UCB0TXBUF, UCB0TXBUF_); #define UCB0I2COA_ 0x0118 /* USCI B0 I2C Own Address */ sfrw(UCB0I2COA, UCB0I2COA_); #define UCB0I2CSA_ 0x011A /* USCI B0 I2C Slave Address */ sfrw(UCB0I2CSA, UCB0I2CSA_); // UART-Mode Bits #define UCPEN (0x80) /* Async. Mode: Parity enable */ #define UCPAR (0x40) /* Async. Mode: Parity 0:odd / 1:even */ #define UCMSB (0x20) /* Async. Mode: MSB first 0:LSB / 1:MSB */ #define UC7BIT (0x10) /* Async. Mode: Data Bits 0:8-bits / 1:7-bits */ #define UCSPB (0x08) /* Async. Mode: Stop Bits 0:one / 1: two */ #define UCMODE1 (0x04) /* Async. Mode: USCI Mode 1 */ #define UCMODE0 (0x02) /* Async. Mode: USCI Mode 0 */ #define UCSYNC (0x01) /* Sync-Mode 0:UART-Mode / 1:SPI-Mode */ // SPI-Mode Bits #define UCCKPH (0x80) /* Sync. Mode: Clock Phase */ #define UCCKPL (0x40) /* Sync. Mode: Clock Polarity */ #define UCMST (0x08) /* Sync. Mode: Master Select */ // I2C-Mode Bits #define UCA10 (0x80) /* 10-bit Address Mode */ #define UCSLA10 (0x40) /* 10-bit Slave Address Mode */ #define UCMM (0x20) /* Multi-Master Environment */ //#define res (0x10) /* reserved */ #define UCMODE_0 (0x00) /* Sync. Mode: USCI Mode: 0 */ #define UCMODE_1 (0x02) /* Sync. Mode: USCI Mode: 1 */ #define UCMODE_2 (0x04) /* Sync. Mode: USCI Mode: 2 */ #define UCMODE_3 (0x06) /* Sync. Mode: USCI Mode: 3 */ // UART-Mode Bits #define UCSSEL1 (0x80) /* USCI 0 Clock Source Select 1 */ #define UCSSEL0 (0x40) /* USCI 0 Clock Source Select 0 */ #define UCRXEIE (0x20) /* RX Error interrupt enable */ #define UCBRKIE (0x10) /* Break interrupt enable */ #define UCDORM (0x08) /* Dormant (Sleep) Mode */ #define UCTXADDR (0x04) /* Send next Data as Address */ #define UCTXBRK (0x02) /* Send next Data as Break */ #define UCSWRST (0x01) /* USCI Software Reset */ // SPI-Mode Bits //#define res (0x20) /* reserved */ //#define res (0x10) /* reserved */ //#define res (0x08) /* reserved */ //#define res (0x04) /* reserved */ //#define res (0x02) /* reserved */ // I2C-Mode Bits //#define res (0x20) /* reserved */ #define UCTR (0x10) /* Transmit/Receive Select/Flag */ #define UCTXNACK (0x08) /* Transmit NACK */ #define UCTXSTP (0x04) /* Transmit STOP */ #define UCTXSTT (0x02) /* Transmit START */ #define UCSSEL_0 (0x00) /* USCI 0 Clock Source: 0 */ #define UCSSEL_1 (0x40) /* USCI 0 Clock Source: 1 */ #define UCSSEL_2 (0x80) /* USCI 0 Clock Source: 2 */ #define UCSSEL_3 (0xC0) /* USCI 0 Clock Source: 3 */ #define UCBRF3 (0x80) /* USCI First Stage Modulation Select 3 */ #define UCBRF2 (0x40) /* USCI First Stage Modulation Select 2 */ #define UCBRF1 (0x20) /* USCI First Stage Modulation Select 1 */ #define UCBRF0 (0x10) /* USCI First Stage Modulation Select 0 */ #define UCBRS2 (0x08) /* USCI Second Stage Modulation Select 2 */ #define UCBRS1 (0x04) /* USCI Second Stage Modulation Select 1 */ #define UCBRS0 (0x02) /* USCI Second Stage Modulation Select 0 */ #define UCOS16 (0x01) /* USCI 16-times Oversampling enable */ #define UCBRF_0 (0x00) /* USCI First Stage Modulation: 0 */ #define UCBRF_1 (0x10) /* USCI First Stage Modulation: 1 */ #define UCBRF_2 (0x20) /* USCI First Stage Modulation: 2 */ #define UCBRF_3 (0x30) /* USCI First Stage Modulation: 3 */ #define UCBRF_4 (0x40) /* USCI First Stage Modulation: 4 */ #define UCBRF_5 (0x50) /* USCI First Stage Modulation: 5 */ #define UCBRF_6 (0x60) /* USCI First Stage Modulation: 6 */ #define UCBRF_7 (0x70) /* USCI First Stage Modulation: 7 */ #define UCBRF_8 (0x80) /* USCI First Stage Modulation: 8 */ #define UCBRF_9 (0x90) /* USCI First Stage Modulation: 9 */ #define UCBRF_10 (0xA0) /* USCI First Stage Modulation: A */ #define UCBRF_11 (0xB0) /* USCI First Stage Modulation: B */ #define UCBRF_12 (0xC0) /* USCI First Stage Modulation: C */ #define UCBRF_13 (0xD0) /* USCI First Stage Modulation: D */ #define UCBRF_14 (0xE0) /* USCI First Stage Modulation: E */ #define UCBRF_15 (0xF0) /* USCI First Stage Modulation: F */ #define UCBRS_0 (0x00) /* USCI Second Stage Modulation: 0 */ #define UCBRS_1 (0x02) /* USCI Second Stage Modulation: 1 */ #define UCBRS_2 (0x04) /* USCI Second Stage Modulation: 2 */ #define UCBRS_3 (0x06) /* USCI Second Stage Modulation: 3 */ #define UCBRS_4 (0x08) /* USCI Second Stage Modulation: 4 */ #define UCBRS_5 (0x0A) /* USCI Second Stage Modulation: 5 */ #define UCBRS_6 (0x0C) /* USCI Second Stage Modulation: 6 */ #define UCBRS_7 (0x0E) /* USCI Second Stage Modulation: 7 */ #define UCLISTEN (0x80) /* USCI Listen mode */ #define UCFE (0x40) /* USCI Frame Error Flag */ #define UCOE (0x20) /* USCI Overrun Error Flag */ #define UCPE (0x10) /* USCI Parity Error Flag */ #define UCBRK (0x08) /* USCI Break received */ #define UCRXERR (0x04) /* USCI RX Error Flag */ #define UCADDR (0x02) /* USCI Address received Flag */ #define UCBUSY (0x01) /* USCI Busy Flag */ #define UCIDLE (0x02) /* USCI Idle line detected Flag */ //#define res (0x80) /* reserved */ //#define res (0x40) /* reserved */ //#define res (0x20) /* reserved */ //#define res (0x10) /* reserved */ #define UCNACKIE (0x08) /* NACK Condition interrupt enable */ #define UCSTPIE (0x04) /* STOP Condition interrupt enable */ #define UCSTTIE (0x02) /* START Condition interrupt enable */ #define UCALIE (0x01) /* Arbitration Lost interrupt enable */ #define UCSCLLOW (0x40) /* SCL low */ #define UCGC (0x20) /* General Call address received Flag */ #define UCBBUSY (0x10) /* Bus Busy Flag */ #define UCNACKIFG (0x08) /* NAK Condition interrupt Flag */ #define UCSTPIFG (0x04) /* STOP Condition interrupt Flag */ #define UCSTTIFG (0x02) /* START Condition interrupt Flag */ #define UCALIFG (0x01) /* Arbitration Lost interrupt Flag */ #define UCIRTXPL5 (0x80) /* IRDA Transmit Pulse Length 5 */ #define UCIRTXPL4 (0x40) /* IRDA Transmit Pulse Length 4 */ #define UCIRTXPL3 (0x20) /* IRDA Transmit Pulse Length 3 */ #define UCIRTXPL2 (0x10) /* IRDA Transmit Pulse Length 2 */ #define UCIRTXPL1 (0x08) /* IRDA Transmit Pulse Length 1 */ #define UCIRTXPL0 (0x04) /* IRDA Transmit Pulse Length 0 */ #define UCIRTXCLK (0x02) /* IRDA Transmit Pulse Clock Select */ #define UCIREN (0x01) /* IRDA Encoder/Decoder enable */ #define UCIRRXFL5 (0x80) /* IRDA Receive Filter Length 5 */ #define UCIRRXFL4 (0x40) /* IRDA Receive Filter Length 4 */ #define UCIRRXFL3 (0x20) /* IRDA Receive Filter Length 3 */ #define UCIRRXFL2 (0x10) /* IRDA Receive Filter Length 2 */ #define UCIRRXFL1 (0x08) /* IRDA Receive Filter Length 1 */ #define UCIRRXFL0 (0x04) /* IRDA Receive Filter Length 0 */ #define UCIRRXPL (0x02) /* IRDA Receive Input Polarity */ #define UCIRRXFE (0x01) /* IRDA Receive Filter enable */ //#define res (0x80) /* reserved */ //#define res (0x40) /* reserved */ #define UCDELIM1 (0x20) /* Break Sync Delimiter 1 */ #define UCDELIM0 (0x10) /* Break Sync Delimiter 0 */ #define UCSTOE (0x08) /* Sync-Field Timeout error */ #define UCBTOE (0x04) /* Break Timeout error */ //#define res (0x02) /* reserved */ #define UCABDEN (0x01) /* Auto Baud Rate detect enable */ #define UCGCEN (0x8000) /* I2C General Call enable */ #define UCOA9 (0x0200) /* I2C Own Address 9 */ #define UCOA8 (0x0100) /* I2C Own Address 8 */ #define UCOA7 (0x0080) /* I2C Own Address 7 */ #define UCOA6 (0x0040) /* I2C Own Address 6 */ #define UCOA5 (0x0020) /* I2C Own Address 5 */ #define UCOA4 (0x0010) /* I2C Own Address 4 */ #define UCOA3 (0x0008) /* I2C Own Address 3 */ #define UCOA2 (0x0004) /* I2C Own Address 2 */ #define UCOA1 (0x0002) /* I2C Own Address 1 */ #define UCOA0 (0x0001) /* I2C Own Address 0 */ #define UCSA9 (0x0200) /* I2C Slave Address 9 */ #define UCSA8 (0x0100) /* I2C Slave Address 8 */ #define UCSA7 (0x0080) /* I2C Slave Address 7 */ #define UCSA6 (0x0040) /* I2C Slave Address 6 */ #define UCSA5 (0x0020) /* I2C Slave Address 5 */ #define UCSA4 (0x0010) /* I2C Slave Address 4 */ #define UCSA3 (0x0008) /* I2C Slave Address 3 */ #define UCSA2 (0x0004) /* I2C Slave Address 2 */ #define UCSA1 (0x0002) /* I2C Slave Address 1 */ #define UCSA0 (0x0001) /* I2C Slave Address 0 */
Минимальная программа для реального чипа:
#include <msp430g2453.h> #define RXD BIT1 //Check your launchpad rev to make sure this is the case. Set jumpers to hardware uart. #define TXD BIT2 // TXD with respect to what your sending to the computer. Sent data will appear on this line int main(void) { int i=0; WDTCTL = WDTPW + WDTHOLD; // Stop Watch dog timer P2DIR |= BIT1; // uart setup P1SEL = TXD + RXD ; // Select TX and RX functionality for P1.1 & P1.2 P1SEL2 = TXD + RXD; // UCA0CTL1 |= UCSSEL_2; // Have USCI use System Master Clock: AKA core clk 1MHz UCA0BR0 = 104; // 1MHz 9600, see user manual UCA0BR1 = 0; // UCA0MCTL = UCBRS_1; // Modulation UCBRSx = 2 UCA0CTL1 &= ~UCSWRST; // Start USCI state machine IE2 = 0x00; // disable interrupt for(;;) { for(i=0;i<0x6000;i++) __delay_cycles(100); P2OUT ^= BIT1; UCA0TXBUF='A'; } }
Минимальная программа с функциями печати строк и целых чисел:
#include <msp430g2453.h> #include <stdio.h> #define RXD BIT1 #define TXD BIT2 void send_char(unsigned char c){ while(!(IFG2 & UCA0TXIFG)); UCA0TXBUF=c; } void send_string(char str[]) { int i; for(i=0; str[i] != '\0'; i++) { if (str[i] == '\r') send_char('\n'); else send_char(str[i]); } } void send_number(int value){ char str[10]; sprintf(str,"%d",value); send_string(str); } int main(void) { int i=0; WDTCTL = WDTPW + WDTHOLD; // Stop Watch dog timer P2DIR |= BIT1; // uart setup P1SEL = TXD + RXD ; // Select TX and RX functionality for P1.1 & P1.2 P1SEL2 = TXD + RXD; // UCA0CTL1 |= UCSSEL_2; // Have USCI use System Master Clock: AKA core clk 1MHz UCA0BR0 = 104; // 1MHz 9600, see user manual UCA0BR1 = 0; // UCA0MCTL = UCBRS_1; // Modulation UCBRSx = 2 UCA0CTL1 &= ~UCSWRST; // Start USCI state machine IE2 = 0x00; // disable interrupt int j=0; for(;;) { j++; for(i=0;i<0x6000;i++) __delay_cycles(100); send_string("count: "); send_number(j); send_char('\n'); P2OUT ^= BIT1; } }
результат работы:
Однако в Proteus эти прогаммы работать не будут! По крайней мере, мне даже осцилографом не удалось нащупать какую либо активность на передающей ножке. Поиск по YouTube также выдал мне примеров использования UART в MSP430-моделях Proteus. Народ для отображения информации пользуется преимущественно дисплеем HD44780. Но это не то...
Однако, если у нас есть таймер, значит мы сможем написать реализацию любого протокола. Корректно таймер у меня работал в Proteus_8.5. Проверяется работа таймера программой blink так: пишется функция задержки на таймере, ставится интервал переключения светодиода 1 секунда. После чего запускается симуляция и производится визуальный контроль.
Писать программный UART на delay_ms не получится, по той причине, что для скорости 9600 бит в секунду, при частоте кварца 1MHz, потребуются задержка 1000/9600 =~ 0.104ms. И 16MHz кварц здесь не поможет.
Алгоритм UART-передатчика я уже выкладывал здесь, правда на примере ATtiny13a. Реализация для MSP430 у меня получилась такая:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> #define LED BIT0 #define RXD BIT1 #define TXD BIT2 volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void) { count=0; } uint8_t send_uart(uint8_t ch){ count=1; int i; uint8_t j; ch=~ch; TA0CCR0=208; // 208 for 4800/1MHz; 416 for 2400/1MHz; 833 for 1200/1MHz TA0CCTL0=CCIE; // enable interrupt for(i=0;i<10;i++) { while(count); count=1; if(i > 8) { P1OUT |=TXD; } else if(i == 0){ P1OUT &=~TXD; } else { j= ch & (1<<(i-1)); if (j) P1OUT &=~TXD; else P1OUT |=TXD; } } TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\r'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } int main (void) { WDTCTL = WDTPW | WDTHOLD; P1DIR |= LED + TXD; count=0; // F_CPU 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; BCSCTL2 &=~(DIVS_0); //SMCLK=DCO BCSCTL3 |= LFXT1S_2; //ACLK= VLO =~ 12KHz ///////// TIMER0_A /////////////////// TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ __enable_interrupt(); int k=0; for(;;) { //wait_ms(1000); __delay_cycles(0xAFFFF); P1OUT ^= LED; //send_uart('A'); send_string("count: "); send_number(k++);send_uart('\r'); } return 0; }
Без проблем конечно не обошлось. UART передатчик не работает на скоростях свыше 4800. Но об этом чуть позже.
Сейчас нужно добавить виртуальный терминал добавить на схему и подключить его к микроконтроллеру:
В настройках терминала нужно выставить параметры протокола:
Результат работы:
Для отладки протокола, можно создать проект на Arduino, там работает аппаратный UART, и посмотреть форму сигнала для передачи какого-либо символа, в данном случае это латинская литера "А":
Здесь младшие биты расположены слева. Теперь сравним с картинкой выдаваемой софтовым передатчиком:
Я бы сказал, что провал справа немного узковат. Посмотрим на ту же картинку на скорости 9600:
Здесь уже четко видно искажение сигнала. И даже некоторое шаманство в коде не помогает:
Лично я считаю, что глючит таймер. Наверное.
Впрочем, для наших целей скорости 4800 хватит за глаза. Это в любом случае гораздо лучше чем HD44780.
На выводах порта P1 доступно восемь 10-битных каналов АЦП, которые обозначаются в datasheet: A0,..,A7.
Список регистров модуля ADC10:
Показать детальное описание регистров ADC10Показать определения регистров и констант в заголовочном файле
/************************************************************ * ADC10 ************************************************************/ #define __MSP430_HAS_ADC10__ /* Definition to show that Module is available */ #define ADC10DTC0_ 0x0048 /* ADC10 Data Transfer Control 0 */ sfrb(ADC10DTC0, ADC10DTC0_); #define ADC10DTC1_ 0x0049 /* ADC10 Data Transfer Control 1 */ sfrb(ADC10DTC1, ADC10DTC1_); #define ADC10AE0_ 0x004A /* ADC10 Analog Enable 0 */ sfrb(ADC10AE0, ADC10AE0_); #define ADC10CTL0_ 0x01B0 /* ADC10 Control 0 */ sfrw(ADC10CTL0, ADC10CTL0_); #define ADC10CTL1_ 0x01B2 /* ADC10 Control 1 */ sfrw(ADC10CTL1, ADC10CTL1_); #define ADC10MEM_ 0x01B4 /* ADC10 Memory */ sfrw(ADC10MEM, ADC10MEM_); #define ADC10SA_ 0x01BC /* ADC10 Data Transfer Start Address */ sfrw(ADC10SA, ADC10SA_); /* ADC10CTL0 */ #define ADC10SC (0x001) /* ADC10 Start Conversion */ #define ENC (0x002) /* ADC10 Enable Conversion */ #define ADC10IFG (0x004) /* ADC10 Interrupt Flag */ #define ADC10IE (0x008) /* ADC10 Interrupt Enalbe */ #define ADC10ON (0x010) /* ADC10 On/Enable */ #define REFON (0x020) /* ADC10 Reference on */ #define REF2_5V (0x040) /* ADC10 Ref 0:1.5V / 1:2.5V */ #define MSC (0x080) /* ADC10 Multiple SampleConversion */ #define REFBURST (0x100) /* ADC10 Reference Burst Mode */ #define REFOUT (0x200) /* ADC10 Enalbe output of Ref. */ #define ADC10SR (0x400) /* ADC10 Sampling Rate 0:200ksps / 1:50ksps */ #define ADC10SHT0 (0x800) /* ADC10 Sample Hold Select Bit: 0 */ #define ADC10SHT1 (0x1000) /* ADC10 Sample Hold Select Bit: 1 */ #define SREF0 (0x2000) /* ADC10 Reference Select Bit: 0 */ #define SREF1 (0x4000) /* ADC10 Reference Select Bit: 1 */ #define SREF2 (0x8000) /* ADC10 Reference Select Bit: 2 */ #define ADC10SHT_0 (0x0000) /* 4 x ADC10CLKs */ #define ADC10SHT_1 (0x0800) /* 8 x ADC10CLKs */ #define ADC10SHT_2 (0x1000) /* 16 x ADC10CLKs */ #define ADC10SHT_3 (0x1800) /* 64 x ADC10CLKs */ #define SREF_0 (0x0000) /* VR+ = AVCC and VR- = AVSS */ #define SREF_1 (0x2000) /* VR+ = VREF+ and VR- = AVSS */ #define SREF_2 (0x4000) /* VR+ = VEREF+ and VR- = AVSS */ #define SREF_3 (0x6000) /* VR+ = VEREF+ and VR- = AVSS */ #define SREF_4 (0x8000) /* VR+ = AVCC and VR- = VREF-/VEREF- */ #define SREF_5 (0xA000) /* VR+ = VREF+ and VR- = VREF-/VEREF- */ #define SREF_6 (0xC000) /* VR+ = VEREF+ and VR- = VREF-/VEREF- */ #define SREF_7 (0xE000) /* VR+ = VEREF+ and VR- = VREF-/VEREF- */ /* ADC10CTL1 */ #define ADC10BUSY (0x0001) /* ADC10 BUSY */ #define CONSEQ0 (0x0002) /* ADC10 Conversion Sequence Select 0 */ #define CONSEQ1 (0x0004) /* ADC10 Conversion Sequence Select 1 */ #define ADC10SSEL0 (0x0008) /* ADC10 Clock Source Select Bit: 0 */ #define ADC10SSEL1 (0x0010) /* ADC10 Clock Source Select Bit: 1 */ #define ADC10DIV0 (0x0020) /* ADC10 Clock Divider Select Bit: 0 */ #define ADC10DIV1 (0x0040) /* ADC10 Clock Divider Select Bit: 1 */ #define ADC10DIV2 (0x0080) /* ADC10 Clock Divider Select Bit: 2 */ #define ISSH (0x0100) /* ADC10 Invert Sample Hold Signal */ #define ADC10DF (0x0200) /* ADC10 Data Format 0:binary 1:2's complement */ #define SHS0 (0x0400) /* ADC10 Sample/Hold Source Bit: 0 */ #define SHS1 (0x0800) /* ADC10 Sample/Hold Source Bit: 1 */ #define INCH0 (0x1000) /* ADC10 Input Channel Select Bit: 0 */ #define INCH1 (0x2000) /* ADC10 Input Channel Select Bit: 1 */ #define INCH2 (0x4000) /* ADC10 Input Channel Select Bit: 2 */ #define INCH3 (0x8000) /* ADC10 Input Channel Select Bit: 3 */ #define CONSEQ_0 (0x0000) /* Single channel single conversion */ #define CONSEQ_1 (0x0002) /* Sequence of channels */ #define CONSEQ_2 (0x0004) /* Repeat single channel */ #define CONSEQ_3 (0x0006) /* Repeat sequence of channels */ #define ADC10SSEL_0 (0x0000) /* ADC10OSC */ #define ADC10SSEL_1 (0x0008) /* ACLK */ #define ADC10SSEL_2 (0x0010) /* MCLK */ #define ADC10SSEL_3 (0x0018) /* SMCLK */ #define ADC10DIV_0 (0x0000) /* ADC10 Clock Divider Select 0 */ #define ADC10DIV_1 (0x0020) /* ADC10 Clock Divider Select 1 */ #define ADC10DIV_2 (0x0040) /* ADC10 Clock Divider Select 2 */ #define ADC10DIV_3 (0x0060) /* ADC10 Clock Divider Select 3 */ #define ADC10DIV_4 (0x0080) /* ADC10 Clock Divider Select 4 */ #define ADC10DIV_5 (0x00A0) /* ADC10 Clock Divider Select 5 */ #define ADC10DIV_6 (0x00C0) /* ADC10 Clock Divider Select 6 */ #define ADC10DIV_7 (0x00E0) /* ADC10 Clock Divider Select 7 */ #define SHS_0 (0x0000) /* ADC10SC */ #define SHS_1 (0x0400) /* TA3 OUT1 */ #define SHS_2 (0x0800) /* TA3 OUT0 */ #define SHS_3 (0x0C00) /* TA3 OUT2 */ #define INCH_0 (0x0000) /* Selects Channel 0 */ #define INCH_1 (0x1000) /* Selects Channel 1 */ #define INCH_2 (0x2000) /* Selects Channel 2 */ #define INCH_3 (0x3000) /* Selects Channel 3 */ #define INCH_4 (0x4000) /* Selects Channel 4 */ #define INCH_5 (0x5000) /* Selects Channel 5 */ #define INCH_6 (0x6000) /* Selects Channel 6 */ #define INCH_7 (0x7000) /* Selects Channel 7 */ #define INCH_8 (0x8000) /* Selects Channel 8 */ #define INCH_9 (0x9000) /* Selects Channel 9 */ #define INCH_10 (0xA000) /* Selects Channel 10 */ #define INCH_11 (0xB000) /* Selects Channel 11 */ #define INCH_12 (0xC000) /* Selects Channel 12 */ #define INCH_13 (0xD000) /* Selects Channel 13 */ #define INCH_14 (0xE000) /* Selects Channel 14 */ #define INCH_15 (0xF000) /* Selects Channel 15 */ /* ADC10DTC0 */ #define ADC10FETCH (0x001) /* This bit should normally be reset */ #define ADC10B1 (0x002) /* ADC10 block one */ #define ADC10CT (0x004) /* ADC10 continuous transfer */ #define ADC10TB (0x008) /* ADC10 two-block mode */ #define ADC10DISABLE (0x000) /* ADC10DTC1 */
Пример простейшей программы из методички университета Эль Пассо (альтернативная ссылка):
На основе этого примера, на скорую руку я написал программу для реального чипа, которая читает третий канал АЦП, и полученное значение посылает через аппаратный UART:
#include <msp430g2453.h> #include <stdio.h> #define RXD BIT1 #define TXD BIT2 #define CH 3 void send_char(unsigned char c){ while(!(IFG2 & UCA0TXIFG)); UCA0TXBUF=c; } void send_string(char str[]) { int i; for(i=0; str[i] != '\0'; i++) { if (str[i] == '\r') send_char('\n'); else send_char(str[i]); } } void send_number(int value){ char str[10]; sprintf(str,"%d",value); send_string(str); } int adc_measure(int ch){ ADC10CTL0 &=~ENC; // Disable ADC ADC10CTL1=CONSEQ_0 + ADC10SSEL_0 + ADC10DIV_0 + SHS_0 + (ch<<12); ADC10CTL0 |= ENC + ADC10SC; while((ADC10CTL0 & ADC10IFG) == 0); ADC10CTL0 &=~ENC; // Disable ADC return ADC10MEM; } int main(void) { int i=0; WDTCTL = WDTPW + WDTHOLD; // Stop Watch dog timer P2DIR |= BIT1; // ADC init P1DIR &=~(1<<CH); ADC10CTL0=ADC10ON + ADC10SHT_0 + SREF_0; ADC10CTL1=CONSEQ_0 + ADC10SSEL_0 + ADC10DIV_0 + SHS_0 + INCH_0; ADC10AE0 = CH; // uart setup P1SEL = TXD + RXD ; // Select TX and RX functionality for P1.1 & P1.2 P1SEL2 = TXD + RXD; // UCA0CTL1 |= UCSSEL_2; // Have USCI use System Master Clock: AKA core clk 1MHz UCA0BR0 = 104; // 1MHz 9600, see user manual UCA0BR1 = 0; // UCA0MCTL = UCBRS_1; // Modulation UCBRSx = 2 UCA0CTL1 &= ~UCSWRST; // Start USCI state machine IE2 = 0x00; // disable interrupt int j=0; for(;;) { // j++; for(i=0;i<0x6000;i++) __delay_cycles(100); j=adc_measure(CH); send_string("adc: "); send_number(j); send_char('\n'); P2OUT ^= BIT1; } }
Результат работы:
Здесь я на вход АЦП подал сначала землю, а потом питание.
Та же самая программа для Proteus_8.5:
#include <msp430g2553.h> #include <sys/types.h> #include <stdio.h> #define LED BIT0 #define RXD BIT1 #define TXD BIT2 #define CH 3 volatile uint16_t count; #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0(void) { count=0; } uint8_t send_uart(uint8_t ch){ count=1; int i; uint8_t j; ch=~ch; TA0CCR0=208; // 4800/1MHz TA0CCTL0=CCIE; // enable interrupt for(i=0;i<10;i++) { while(count); count=1; if(i > 8) { //while(count); P1OUT |=TXD; } else if(i == 0){ P1OUT &=~TXD; } else { j= ch & (1<<(i-1)); if (j) P1OUT &=~TXD; else P1OUT |=TXD; } } TA0CCTL0=0; // disable interrupt return ch; } void send_string(char str[]) { int i; for(i=0; str[i]!='\0';i++) { if (str[i] == '\n') send_uart('\r'); else send_uart(str[i]); } } void send_number(int value) { char str[10]; sprintf(str,"%d",value); send_string(str); } int adc_measure(int ch) { ADC10CTL0 &=~ENC; ADC10CTL1=CONSEQ_0 + ADC10SSEL_0 + ADC10DIV_0 + SHS_0 + (ch<<12); ADC10CTL0 |= ENC + ADC10SC; while((ADC10CTL0 & ADC10IFG) == 0) ADC10CTL0 &=~ENC; return ADC10MEM; } int main (void) { WDTCTL = WDTPW | WDTHOLD; P1DIR |= LED + TXD; count=0; // F_CPU 1MHz BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; BCSCTL2 &=~(DIVS_0); //SMCLK=DCO BCSCTL3 |= LFXT1S_2; //ACLK= VLO =~ 12KHz ///////// TIMER0_A /////////////////// TA0CTL=TASSEL_2 + MC_1 + ID_0 + TACTL; /* TASSEL_2 =use SMCLK; MC_1 =continous to TACCR0; ID_0 =prescaler = 1; TACLR =clean counter TAR; TAIE; =enable timer interrupt; */ // ADC init P1DIR &=~(1<<CH); ADC10CTL0=ADC10ON + ADC10SHT_0 + SREF_0; ADC10CTL1=CONSEQ_0 + ADC10SSEL_0 + ADC10DIV_0 + SHS_0 + INCH_0; ADC10AE0=CH; __enable_interrupt(); int j=0; for(;;) { //wait_ms(1000); __delay_cycles(0xAFFFF); P1OUT ^= LED; j=adc_measure(CH); send_string("adc: "); send_number(j);send_uart('\r'); } return 0; }
Результат:
Здесь результат всегда одинаков вне зависимости от того, что подаем на вход АЦП. Т.е. можно сделать вывод что модуль АЦП здесь также не рабочий.