Рано или поздно вам понадобится связь МК с внешним миром для вывода отладочной информации или чего-другого. Самое простое решение — использование последовательного интерфейса.
Теория
Одним из самых универсальных является UART протокол, который асинхронный и требует всего две линии: передачи (TX) и приема (RX). Данные передаются по одному биту через равные промежутки времени:
Частота должна совпадать у обоих сопряженных устройств. Передача ведется пакетами по 8-бит, ограниченная специальными старт и стоп битами, необходимыми приемнику. При этом линии TX и RX не связны между собой протоколом, поэтому, при передачи в одну сторону, вторую линию можно совсем убрать. Кстати, существует еще один вид протокола: USART, который добавляет линию синхронизации.
USART у SAM4C много, а UART — один, которого, я, к сожалению, не вывел на отладочной плате (PB4, PB5). 🙁
Но ничего страшного, мы рассмотрим и этот UART и USART, работающий в режиме предыдущего. Скажу от себя: UART оказался гораздо интереснее, чем я думал.
Подлинный UART
Для начала стоит рассмотреть последовательный приемо/передатчик на программном уровне. Ниже представлена блок-схема модуля:
Регистры приема/передачи используются по своему прямому назначению: чтение/запись с типом данных 8бит. Включим модуль UART0 через PMC:
1 | PMC->PMC_PCER0 = PMC_PCER0_PID8; |
Рассмотрим регистр управления UART_CR:
Команда |
Описание |
RSTRX | Сброс приемника |
RSTTX | Сброс передатчика |
RXEN | Разрешить прием |
RXDIS | Запретить прием |
TXEN | Разрешить передачу |
TXDIS | Запретить передачу |
RSTSTA | Сбросить статус |
Как видите, все довольно просто. Перед использованием UART необходимо провести процедуры сброса, затем нужно настроить работу модуля через регистр UART_MR:
Важное место занимает параметр CHMODE — он определяет режимы работы последовательных приемопередатчиков. Их совсем немного и пригодятся они разве что для тестов.
- NORMAL — стандартный принцип работы.
- AUTOMATIC — «эхо». Может только принимать, а на линию TX возвращает полученные данные.
- LOCAL_LOOPBACK — Бесполезная вещь. Программно соединяет TX с RX внутри контроллера.
- REMOTE_LOOPBACK — Аналогично предыдущему, но соединяет физически внешние TX и RX.
Из-за асинхронности интерфейса есть некий процент ошибок, он ничтожно мал, но иногда приходится вести передачу по не устойчивой линии, и для проверки целостности данных используют специальный бит-четности.
Эта довольно простая защита работает в нескольких режимах, определяемых командой PAR:
- EVEN — установить 1, если значение данных четное.
- ODD — установить 1, если значение данных нечетное.
- NO — не использовать бит-четности.
- SPACE — всегда устанавливать 0.
- MARK — всегда устанавливать 1.
Последние два режима пригодятся для определенных «пометок» данных, к тому же бит-четности может вызывать прерывание.
Теперь перейдем к бод-генератору, это, по сути дела, просто генератор несущей частоты передачи, представляющий из себя хитрый делитель частоты MCLK и состоящий всего из одного регистра UART_BRGR со значением CD.
Полученная частота измеряется в бод-ах. (помните эти значения? 9600, 115200) Замечание: старайтесь использовать стандартизированные частоты; это относится как и к Bauld Rate, так и к MCLK. Ниже приведена таблица для родной частоты 120MHz:
Baud Rate |
Значение CD |
Ошибка |
50 | 150000 | 0.00% |
4800 | 1563 | 0.03% |
9600 | 781 | 0.03% |
115200 | 65 | 0.16% |
Как видите, ошибки нам не избежать, впрочем, вы можете подогнать частоту контроллера под ваш UART. А также для удобства вычислять в программе эти значения.
Перейдем к регистру прерываний UART_IER:
Обработчик прерываний подобен всем остальным периферийным — один. Также не забудьте спросить разрешение у NVIC.
1 2 3 4 5 6 | void UART0_Handler() { if (UART0->UART_SR & UART_SR_RXRDY) { //Если мы получили данные //Read/read/read… } UART0->UART_CR = UART_SR_RSTSTA; //Сбросим статус, если что-то с битом-четности. } |
Прерывания нам очень пригодятся при приеме данных, так как мы точно не знаем когда они поступят. А регистр статуса UART_SR вы можете использовать не только в прерываниях.
Что ж, давайте инициализируем UART в самом простом режиме со скоростью 9600 бод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | PMC->PMC_PCER0 = PMC_PCER0_PID12; //Включаем PIOB PIOB->PIO_WPMR = 0x50494F; PIOB->PIO_IDR = PIO_IDR_P4; //Наш пин — PB4 — (URXD0) PIOB->PIO_ODR = PIO_ODR_P4; PIOB->PIO_IDR = PIO_IDR_P5; //Наш пин — PB5 — (UTXD0) PIOB->PIO_OER = PIO_OER_P5; //Сейчас нужный переф. (A) uint32_t last0, last1; last0 = PIOB->PIO_ABCDSR[0]; PIOB->PIO_ABCDSR[1] &= (~PIO_ABCDSR_P4 & last0); last1 = PIOB->PIO_ABCDSR[1]; PIOB->PIO_ABCDSR[0] &= (~PIO_ABCDSR_P4 & last1); PIOB->PIO_PDR = PIO_PDR_P4; last0 = PIOB->PIO_ABCDSR[0]; PIOB->PIO_ABCDSR[1] &= (~PIO_ABCDSR_P5 & last0); last1 = PIOB->PIO_ABCDSR[1]; PIOB->PIO_ABCDSR[0] &= (~PIO_ABCDSR_P5 & last1); PIOB->PIO_PDR = PIO_PDR_P5; PMC->PMC_PCER0 = PMC_PCER0_PID8; //UART0 включаем //UART0->UART_WPMR = 0x54494D; Нам впервые не нужно использовать черную магию! UART0->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS | UART_CR_RSTSTA; //Выполняем сброс и отключаем приемник/передатчик UART0->UART_MR = UART_MR_PAR_NO | UART_MR_CHMODE_NORMAL; //"Нормальный" режим работы. Бит-четности не используется. UART0->UART_BRGR = (SystemCoreClock / 9600) >> 4 ; //9600 Бод //UART0->UART_IER = UART_IER_RXRDY; //Прерывание по процедуре получения данных. (опционально) //NVIC_EnableIRQ(UART0_IRQn); //Просим разрешение у NVIC (опционально) UART0->UART_CR = UART_CR_RXEN | UART_CR_TXEN; // Включаем приемник и передатчик |
Теперь, чтобы что-нибудь отправить в порт, мы используем регистр UART_THR, но модуль отправляет данные не сразу, поэтому необходимо проверять его готовность перед записью с помощью регистра статуса:
1 2 | while (!(UART0->UART_SR & UART_SR_TXRDY) ); //Ждем готовности передатчика UART0->UART_THR = '7'; //Отправляем в-лоб символ ASCII |
Чтение — аналогично:
1 2 | while (!(UART0->UART_SR & UART_SR_RXRDY) ); //Ждем символа uint32_t var = UART0->UART_RHR; //Получаем. |
Но здесь лучше использовать прерывания по RXRDY с кольцевым буфером, а это уже целая библиотека.
Упрощенный USART
Этих модулей у нас в четверо больше. Возьмем, к примеру, пару от USART0:
Инициализация по стандарту 9600 бод будет происходить так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | PMC->PMC_PCER0 = PMC_PCER0_PID12; //Включаем PIOB PIOB->PIO_WPMR = 0x50494F; PIOB->PIO_IDR = PIO_IDR_P16; //Наш пин — PB16 — (RXD0) PIOB->PIO_ODR = PIO_ODR_P16; PIOB->PIO_IDR = PIO_IDR_P17; //Наш пин — PB17 — (TXD0) PIOB->PIO_OER = PIO_OER_P17; //Сейчас нужный переф. (A) uint32_t last0, last1; last0 = PIOB->PIO_ABCDSR[0]; PIOB->PIO_ABCDSR[1] &= (~PIO_ABCDSR_P16 & last0); last1 = PIOB->PIO_ABCDSR[1]; PIOB->PIO_ABCDSR[0] &= (~PIO_ABCDSR_P16 & last1); PIOB->PIO_PDR = PIO_PDR_P16; last0 = PIOB->PIO_ABCDSR[0]; PIOB->PIO_ABCDSR[1] &= (~PIO_ABCDSR_P17 & last0); last1 = PIOB->PIO_ABCDSR[1]; PIOB->PIO_ABCDSR[0] &= (~PIO_ABCDSR_P17 & last1); PIOB->PIO_PDR = PIO_PDR_P17; PMC->PMC_PCER0 = PMC_PCER0_PID14; //USART0 включаем USART0->US_WPMR = 0x555341; //Снова очень черная магия. USART0->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS | US_CR_RSTSTA; //Выполняем сброс и отключаем приемник/передатчик USART0->US_MR = US_MR_USART_MODE_NORMAL | US_MR_USCLKS_MCK | US_MR_CHRL_8_BIT | US_MR_PAR_NO | US_MR_NBSTOP_1_BIT | US_MR_CHMODE_NORMAL; //+ пара новых параметров USART0->US_BRGR = (SystemCoreClock / 9600) >> 4 ; //9600 Бод //USART0->US_IER = US_IER_RXRDY; //Прерывание по процедуре получения данных. (опционально) //NVIC_EnableIRQ(USART0_IRQn); //Просим разрешение у NVIC (опционально) USART0->US_CR = US_CR_RXEN | US_CR_TXEN; // Включаем приемник и передатчик |
Почти все то же самое, что и у UART. Теперь процедура записи:
1 2 | while (!(USART0->US_СSR & US_SR_TXRDY) ); //Ждем готовности передатчика USART0->US_THR = '7'; //Отправляем в-лоб символ ASCII |
И чтения:
1 2 | while (!(USART0->US_SR & US_SR_RXRDY) ); //Ждем символа uint32_t var = USART0->US_RHR; //Получаем. |
Вы можете заметить, что я много уделяю внимания модулям связи. Я полагаю, средство связи с внешним миром — вещь первостепенная, без такого простого отладочного средства вам не обойтись. Для удобства вывода информации, состоящей из строки или цифры, я написал несколько полезных подпрограмм:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | void serial_print(char* str) { while(*(str)) { while (!(USART0->US_CSR & US_CSR_TXRDY) ); USART0->US_THR = *(str); str++; } //Перевод каретки while (!(USART0->US_CSR & US_CSR_TXRDY) ); USART0->US_THR = '\r'; while (!(USART0->US_CSR & US_CSR_TXRDY) ); USART0->US_THR = '\n'; } char* reverse(char *s) { int j = 0; char c; while(s[j]!='\0') ++j; j-=1; for (int i = 0; i<j; i++, j--) { c = s[i]; s[i] = s[j]; s[j] = c; } return(s); } char* itoa(int n) { //Реализация Itoa char s[24]; int i, sign; if ((sign = n) < 0) n = -n; i = 0; do { s[i++] = n % 10 + '0'; } while ((n /= 10) > 0); if (sign < 0) s[i++] = '-'; s[i] = '\0'; return(reverse(s)); } inline void serial_print(int num) { serial_print(itoa(num)); //Вывод числа в виде набора символов } |
Используйте serial_print() для вывода информации.
Физическая сторона
Естественно, на разъемах вашего ПК не будет выведены UTX/RX, для последовательного приемо/передатчика существует стандарт линии передачи RS-232 и производные. Они превращают наши цифровые сигналы в помехоустойчивые балансные линии (где земля является точкой отстчета). Еще один хороший вариант — USB, но для тех и других необходим специальный конвертер(драйвер) типа MAX232 или FTDIxxx. Я использовал программатор от платы Марсоход:
Впрочем, можете передавать данные хоть по радиоволнам. (USART это позволяет) Однако ж не забывайте схему подключения дуплексных последовательных интерфейсов: RX к TX и наоборот.
Для связи с МК необходима программа-терминал, я использовал PuTTY.