LPCXpresso Урок 8. SPI. Подключаем дисплей от Nokia 3310.

Консоль отладчика это конечно хорошо, но подключать же к нашему устройству компьютер для отображения скажем температуры. Знакосинтезирующие индикаторы на базе HD44780 вы уже и сами сможете подключить, вывод и чтение портов вы уже знаете, а больше ничего и не нужно. Гораздо интереснее будет подключить дисплей от мобильного телефона.

Поэтому в рамках курса для новичков ознакомимся с SPI на примере работы с дисплеем от телефона Nokia 3310.

Пожалуй, начиная с этого урока, я начну пропускать довольно большие куски кода и откажусь от детального описания кода. Базовые сведения для его понимания у вас уже есть. Описание как работает та или иная вспомогательная функция отнимет очень много времени и отвлечет от непосредственно рассматриваемой задачи. Весь материал, не рассмотренный в статье, предлагается для самостоятельного изучения с целью повышения навыком отладки и программирования.

Схема

Как обычно продолжаем предыдущий урок. Дисплей к плате подключаем по следующей схеме:
Схема подключения дисплея от Nokia 3310

Проводки можно подпаять к пружинным контактам дисплея, а в некоторых моделях и непосредственно к дисплею. Электролитический конденсатор можно ставить на 1.0мкФ - 10.0мкФ. У меня, во всяком случае, работает.

Дорабатываем библиотеку

Добавляем в библиотеку LPC13xx_Lib файл ssp.h, содержащий описания функций и определения констант (файл в архиве). Отмечу только одну константу:

// размер буфферов приёма/передачи
#define SSP_FIFOSIZE		8
Это размер внутреннего буфера контроллера SPI. Он одинаковый и для буфера приёма и для буфера передачи. Вещь крайне полезная для фонового обмена данными. Пока мы его не задействовали, т.к. писали чуть более универсальный код.

Добавляем в библиотеку LPC13xx_Lib файл ssp.с и после подключения необходимых заголовочных файлов пишем функцию инициализации:

void SSPInit( void )
{
	LPC_SYSCON->PRESETCTRL	|= (1<<0);	// Включаем модуль SSP
	LPC_SYSCON->SYSAHBCLKCTRL	|= (1<<11);	// и его тактирование
	LPC_SYSCON->SSPCLKDIV	 = 0x01;		// Предделитель тактовой частоты модуля 1
	LPC_IOCON->PIO0_8	&= ~0x07;	// MISO функция для пина
	LPC_IOCON->PIO0_8	|= 0x01;
	LPC_IOCON->PIO0_9	&= ~0x07;	// MOSI функция для пина
	LPC_IOCON->PIO0_9	|= 0x01;
	LPC_IOCON->SCKLOC	 = 1;		// SCK на вывод 2.11
	LPC_IOCON->PIO2_11	 = 0x01;	// SCK функция для пина
	LPC_IOCON->PIO0_2	&= ~0x07;	// SSEL функция для пина
	LPC_IOCON->PIO0_2	|= 0x01;

	// SSP0CLKDIV = 1 -- F = (PCLK / (CPSDVSR X [SCR+1])) = (72,000,000 / (2 x [8 + 1])) = 4.0 MHz
	LPC_SSP->CR0	= ( (7<<0)		// Размер данных 0111 - 8 бит
			  | (0<<4)		// Формат фрейма 00 - SPI
			  | (0<<6)		// Полярность 0 - низкий уровень между фреймами
			  | (0<<7)		// Фаза 0 - по нарастанию
			  | (8<<8)		// Делитель частоты шины на бит
			  ) ;

	LPC_SSP->CPSR = 2;	// предделитель (доступно 2-254, кратно 2)

	uint8_t i, Dummy;
	for ( i = 0; i < SSP_FIFOSIZE; i++ ) {
		Dummy = LPC_SSP->DR;		// Очистка буффера приёма
	}

	// Разрешение работы
	LPC_SSP->CR1	= ( (0<<0)	// 0 - Loop Back Mode Normal
			  | (1<<1)	// Разрешение работы 1 - разрешено
			  | (0<<2)	// Режим ведущий-ведомый 0 - мастер
			  );

	return;
}
  • Для начала записываем единицу в регистр PRESETCTRL, тем самым мы снимаем сигнал сброса с контроллера SSP (можно рассматривать SSP как микросхему в микросхеме, и указанный бит является как бы выводом RESET вложенной микросхемы).
  • Через регистр SYSAHBCLKCTRL подаем тактирование на периферию (подключаем кварц к этой вложенной микросхеме);
  • В регистр SSPCLKDIV заносим предделитель опорной частоты, для тактирования периферии. Если записать в него 0 (по умолчанию), то контроллер SSP отключится. Мы установили 1, хотя спокойно можно было и большее значение (что по идеи должно снизить потребление);
  • Для вывода PIO0.8 выбираем функцию MISO вывода блока SSP;
  • Для вывода PIO0.9 выбираем функцию MISO вывода блока SSP;
  • Так как SCK вывод может быть назначен на разные выводы, то через регистр SCKLOC указываем, что нас интересует PIO2.10. Для самого вывода PIO2.10 выбираем функцию SCK вывода блока SSP. Кому-то это покажется излишеством, но так оно есть;
  • Для вывода PIO0.2 выбираем функцию SSEL вывода блока SSP, тем самым разрешив аппаратный контроль за выбором устройства. В итоге, когда по SPI надо будет передать данные, контроллер SSP самостоятельно посадит этот вывод на 0, выбрав ведомое устройство;
  • Через регистр CR0 настраивается основной режим работы (подробно в таблице 235 UM10375). Тут задаем размер «байта» любой желаемый от 4 до 16, формат пакета, полярность сигналов и паузы, делитель опорной частоты для бита (см. ниже);
  • В регистр CPSR заносим делитель для получения опорной частоты (см. ниже). Он должен быть в диапазоне от 2 до 254 и обязательно чётным числом;
  • Очищаем буфер приёма, во избежание чтения мусора;
  • Через регистр CR1 указываем опции работы SSP. В частности что работаем и работаем в режиме мастера.

На первый взгляд может показаться сложной система задания частоты шины SPI. По этому попробую описать последовательно. Системная частота делиться на делитель в регистре SSPCLKDIV и эта частота задаётся основной для работы контроллера SSP. Затем она делиться на делитель в регистре CPSR для формирования опорной частоты шины SPI (это ещё не есть частота шины). И, наконец, опорная частота делиться на коэффициент, заданные в поле регистра CR0 (+1) для получения частоты тактового сигнала шины.

Прерывания нам здесь не нужны, мы владелец шины и сами определяем, когда осуществлять обмен и обмен у нас будет блокируемым. А обмен у нас происходит посредством функции:

 uint8_t SSPTransfer( uint8_t snd )
{
	// Ждем освобождения места в буфера передачи
	while ( (LPC_SSP->SR & (SSPSR_TNF|SSPSR_BSY)) != SSPSR_TNF );
	// Помещаем данные на передачу (запускаем передачу)
	LPC_SSP->DR = snd;
	// Ждем появления данных в буфере приёма
	while ( (LPC_SSP->SR & (SSPSR_BSY|SSPSR_RNE)) != SSPSR_RNE );
	// Считываем принятые данные и возвращаем их
	return LPC_SSP->DR;
}

За счёт использования именованных констант код стал легко читаемым (плюс комментарии). Логика простая, мы вызываем функцию с одним параметром – данные которые надо передать. Функция блокируется до появления в буфере приёма хотя бы одного байта, после чего принятый байт и возвращается.

Дорабатываем код

В проект добавлено 2 файла lcd.h и lcd.c (как это сделать написано в уроке АЦП). Код не вызывает особого интереса, потому в статье приводится не будет. По работе с дисплеем 3310 написано уже много, не вижу смысла повторяться. Вся работа с ним сводится к вызову рассмотренных функций SSPInit и SSPTransfer плюс пара дерганий выводов через GPIOSetValue. Отмечу только, что в функции инициализации дисплея мы вывод SSEL использовали как GPIO вывод, а потом отдали во владении SSP контроллеру. Такое поведение вполне допустимо.

Функция main так же претерпела некоторые изменения. В ней добавилась инициализация дисплея и вывод приветствия. В основном цикле вместо чтения одного канала мы поочерёдно проходим все. Важным изменением является то, что мы больше не используем функцию printf. Взамен неё мы используем следующую конструкцию:

sprintf(buffer, "%d: %4d %4dmV", num, adcVal, mV);	// формируем строку
LCD_gotoXY(0, num);	// Устанавливаем курсор в координату (x,y): 0, num
LCD_writeString(buffer);	// Выводим строку

Функция sprintf так же как и printf осуществляет формирование строки. Но в отличие от последней «выводит» строку в буфер, а не на отладочную консоль. Эту строку из буфера мы выводим на дисплей вызовом LCD_writeString, предварительно установив позицию вывода на дисплее.

Запуск

Компилируем, запускаем и видим в результате залапанный дисплей:
Вывод результатов АЦП на дисплей от Nokia 3310 через SPI

Логика работы проста. После отображения приветствия на экран выводятся данные с 6 каналов АЦП в формате:

[номер_канала]: [измененное_значение] [вычисленная_величина_напряжения]mV

Обновление производится несколько раз в секунду. На время измерения и вывода зажигается светодиод. Но если вы нажмете кнопку, то задержка снимается, и контроллер как бешенный начинает гонять данные. Этот режим позволит вам несколько визуально оценить скорость работы.

Статистика

Debug версии заняла не много не мало ~18кБ (плачьте и негодуйте любители оптимального кода). всё наше место скушала при этом библиотека Redlib (semihost) которую мы использовали ради одной лишь функции sprintf. А функцию мы использовали для преобразования числа в строку символов (ну ещё и форматирование, но это мелочи).

Естественно можно обойтись и библиотекой Redlib (none) написав свою функцию перевода числа в строку (sprintf мы не можем использовать для этой библиотеки, проект просто не соберётся). Это я предлагаю сделать вам самостоятельно. Ту же функцию itoa можно найти практически в любой книге по С.

Но следует отметить, что размер не сильно отличается от прошлого примера, а добавили мы ни много ни мало - поддержку LCD дисплея. Об этом я упомянал в начале курса.

Вместо заключения

Интерфейс SPI в контроллере LPC1343 является функциональным и простым в использовании, а о полезности его и говорить не надо. Благодаря возможности работы с "байтами" от 4 до 16 бит позволяет подключить и цветные дисплеи от мобильных телефонов (которым требуются посылки по 9 бит) и прочие "не стандартные устройства". А возможность работы на скорости до 36Мбит/с позволяет передавать большие объемы данных за короткий промежуток времени.

Так же отмечу что передача по SPI осуществляется полностью аппаратно и нам в принципе не обязательно ожидать её завершения, если нас не интересуют принятые данные (как например в этом уроке). Поэтому мы после помещения данных в регистр SPI контроллера можем смело возвращаться из функции передачи, позволяя ядру выполнять более полезные операции нежели ожидание завершения вывода.

Вообще меня SPI в LPC1343 очень порадовал и я его с удовольствием использую (чаще USB из-за которого я и брал кортексы). Вам же предлагаю попробовав все это дело решить для себя самим.

Файлы: blinky_display.zip