ARM Cortex-M4: последовательный порт #4

Рано или поздно вам понадобится связь МК с внешним миром для вывода отладочной информации или чего-другого. Самое простое решение — использование последовательного интерфейса.
 
 
 
 
Теория

Одним из самых универсальных является UART протокол, который асинхронный и требует всего две линии: передачи (TX) и приема (RX). Данные передаются по одному биту через равные промежутки времени:

Puerto_serie_Rs232
Частота должна совпадать у обоих сопряженных устройств. Передача ведется пакетами по 8-бит, ограниченная специальными старт и стоп битами, необходимыми приемнику. При этом линии TX и RX не связны между собой протоколом, поэтому, при передачи в одну сторону, вторую линию можно совсем убрать. Кстати, существует еще один вид протокола: USART, который добавляет линию синхронизации.
 
USART у SAM4C много, а UART — один, которого, я, к сожалению, не вывел на отладочной плате (PB4, PB5). 🙁

pcb err

Но ничего страшного, мы рассмотрим и этот 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:
Ashampoo_Snap_2014.10.24_13h23m46s_01883_
Важное место занимает параметр CHMODE — он определяет режимы работы последовательных приемопередатчиков. Их совсем немного и пригодятся они разве что для тестов.

  • NORMAL — стандартный принцип работы.
  • AUTOMATIC — «эхо». Может только принимать, а на линию TX возвращает полученные данные.
  • LOCAL_LOOPBACK — Бесполезная вещь. Программно соединяет TX с RX внутри контроллера.
  • REMOTE_LOOPBACK — Аналогично предыдущему, но соединяет физически внешние TX и RX.

Из-за асинхронности интерфейса есть некий процент ошибок, он ничтожно мал, но иногда приходится вести передачу по не устойчивой линии, и для проверки целостности данных используют специальный бит-четности.

Ashampoo_Snap_2014.10.25_22h56m08s_015_

Эта довольно простая защита работает в нескольких режимах, определяемых командой PAR:

  • EVEN — установить 1, если значение данных четное.
  • ODD — установить 1, если значение данных нечетное.
  • NO — не использовать бит-четности.
  • SPACE — всегда устанавливать 0.
  • MARK — всегда устанавливать 1.

Последние два режима пригодятся для определенных «пометок» данных, к тому же бит-четности может вызывать прерывание.

Теперь перейдем к бод-генератору, это, по сути дела, просто генератор несущей частоты передачи, представляющий из себя хитрый делитель частоты MCLK и состоящий всего из одного регистра UART_BRGR со значением CD.
Ashampoo_Snap_2014.10.26_11h01m13s_016_
Полученная частота измеряется в бод-ах. (помните эти значения? 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:
uart_int
Обработчик прерываний подобен всем остальным периферийным — один. Также не забудьте спросить разрешение у 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 и наоборот.

50e1ce8bce395fb62b000000

Для связи с МК необходима программа-терминал, я использовал PuTTY.

DSCN5775


Все части

Вы можите оставить комментарий, или поставить трэкбек со своего сайта.

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Bug Report
Локализовано: шаблоны Wordpress