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

Консоль отладчика это конечно хорошо, но подключать же к нашему устройству компьютер для отображения скажем температуры. Знакосинтезирующие индикаторы на базе HD44780 вы уже и сами сможете подключить, вывод и чтение портов вы уже знаете, а больше ничего и не нужно. Гораздо интереснее будет подключить дисплей от мобильного телефона.
Поэтому в рамках курса для новичков ознакомимся с SPI на примере работы с дисплеем от телефона 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, предварительно установив позицию вывода на дисплее.

Запуск

Компилируем, запускаем и видим в результате залапанный дисплей:

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

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

Статистика

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

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

Интерфейс SPI в контроллере LPC1343 является функциональным и простым в использовании, а о полезности его и говорить не надо. Благодаря возможности работы с «байтами» от 4 до 16 бит позволяет подключить и цветные дисплеи от мобильных телефонов (которым требуются посылки по 9 бит) и прочие «не стандартные устройства». А возможность работы на скорости до 36Мбит/с позволяет передавать большие объемы данных за короткий промежуток времени.
Так же отмечу что передача по SPI осуществляется полностью аппаратно и нам в принципе не обязательно ожидать её завершения, если нас не интересуют принятые данные (как например в этом уроке). Поэтому мы после помещения данных в регистр SPI контроллера можем смело возвращаться из функции передачи, позволяя ядру выполнять более полезные операции нежели ожидание завершения вывода.
Вообще меня SPI в LPC1343 очень порадовал и я его с удовольствием использую (чаще USB из-за которого я и брал кортексы). Вам же предлагаю попробовав все это дело решить для себя самим.
  • +3
  • 15 сентября 2011, 08:50
  • angel5a
  • 1
Файлы в топике: blinky_display.zip

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

RSS свернуть / развернуть
написав свою функцию перевода числа в строку
Или заюзав xprintf)
0
Благодарю. Не обращал на эту библиотеку внимания. Выглядит довольно неплохо. Конечно пока у меня есть отладчик, мне не особо полезна, но попробывать на будущее пригодится.
0
Неужели у LPC нету дефайнов, чтобы не было такого количества магических чисел? >_<
// размер буфферов приёма/передачи
#define SSP_FIFOSIZE 8
8 чего? Если я выставлю размер SPI-фрейма скажем в 9 или 16 бит — сколько фреймов в буфер поместится?
0
  • avatar
  • Vga
  • 15 сентября 2011, 11:06
фреймов. хоть 4 бита ставь, все равно в буффере поместится только 8 элементов.

P.S.: У «родного» LPC есть CMSIS и библиотека примеров. А есть сторонняя библиотека CodeBase (ссылка и упоминание в анонсе) в которой вся магия убрана.
0
Но если сделать фрейм 16 бит, то все равно влезет 8 фреймов, т.е. 16 байт?
0
  • avatar
  • Vga
  • 15 сентября 2011, 13:15
Спасибо за уроки. Как раз получил экспрессу.
Только вот дисплея от нокии 3310 у меня нету. Надеюсь, следующий урок будет по LS020 от сименса. Их у меня есть аж 2 :)
0
Если автор темы не против, то я написал бы цикл статей про LCD от нокии 3310 и от сименса (L2F50)
0
Если автор темы не против
Это не имеет значения. Есть о чем — пиши.
0
  • avatar
  • Vga
  • 15 сентября 2011, 21:13
А с чего бы мне быть против? Авторских прав ни каких на дисплей не имею, не волнуйтесь :)
Тем более что работы с дисплеем я не рассматривал тут подробно, только то, что касается SPI. Так что я только за.
0
Пиши про сименс! Про нокию я и сам собираюсь во всех подробностях, только я писать буду не про 3310, а про чуть более старшие модели (1100 и 2100).
0
ок. занимаюсь вопросом
0
В анонсе я давал ссылку где можно преобрести. Цена вопроса 50р. Если конешно есть их представитель в вашем городе.
Принцип работы SPI уже рассмотрен, так что дополнительного урока не вижу смысла. Дисплея от сименса у меня нет да и нокии меня устраивают, так что и статьи такой не будет :)
0
Может оффтоп, но я спрошу. На LPCExpresso 1114 SPI1 распаян или нет? Есть одна идея, но не знаю, заниматься ли или нет!
0
Если верить схеме, то оба SPI распаяны.)
0
Вопрос: Почему при настройке выводов 2.4 2.5 LPCXPresso 1114 они постоянно остаются в лог «1»?

GPIOSetDir(LCD_RESET_PORT, LCD_RESET_PIN, 1);		// Вывод RESET на GPIO выход
  GPIOSetValue(LCD_RESET_PORT, LCD_RESET_PIN, 0);		// Устанавливаем низкий уровень на RESET

Мерял тестером при отладке!? Не пойму. Может у этих пинов есть какие особенности?
0
в LPC1114 эти выводы огут быть задействованы под какую-то спецфункцию (например отладчик) по умолчанию, надо посмотреть. Библиотека LPC13xx_Lib (как и LPC11xx_Lib я понимаю) не переопределяет функцию для порта.
0
Бля упыри гребаные! Посмотрев схему, увидел, что в примечании написано
PIO2_4
Note wrong text in silkscreen

PIO2_5
Note wrong text in silkscreen

Там выведены совсем другие пины=)
0
Вобля, хорошо что у меня вроде как всё совпадает. А то тоже бы тупил сидел :)
У меня приход был когда я заюзал другой SCK вывод, а он оказалось отладчиком юзается. Пришлось с бубном поплясать, что бы отладится :)
0
Млин вот так и доверяй капиталистам!) 2 вечера убил… Ну и Сам конечно тормоз!)
0
Из Схема платы видно что пины 2.4 и 2.5 свапнуты c 3.4 и 3.5. Это только сделано только конкретно в данной отладочной плате или во всех микроконтроллерах серии LPC1114 эти пины поменяны?
0
Это ошибка в шелкограции именно отладочной платы. У самого МК выводы на тех пинах, которые подписаны. То есть если вы берёте камень, то P2.4 будет на 19 пине. А если берёте отладочную плату, то P2.4 будет там, где подписано PIO3_4 (дорожка от него идет на тот же 19 вывод МК).
0
Спасибо
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.