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 можно найти практически в любой книге по С.
Но следует отметить, что размер не сильно отличается от прошлого примера, а добавили мы ни много ни мало - поддержку LCD дисплея. Об этом я упомянал в начале курса.
Вместо заключения
Интерфейс SPI в контроллере LPC1343 является функциональным и простым в использовании, а о полезности его и говорить не надо. Благодаря возможности работы с "байтами" от 4 до 16 бит позволяет подключить и цветные дисплеи от мобильных телефонов (которым требуются посылки по 9 бит) и прочие "не стандартные устройства". А возможность работы на скорости до 36Мбит/с позволяет передавать большие объемы данных за короткий промежуток времени.
Так же отмечу что передача по SPI осуществляется полностью аппаратно и нам в принципе не обязательно ожидать её завершения, если нас не интересуют принятые данные (как например в этом уроке). Поэтому мы после помещения данных в регистр SPI контроллера можем смело возвращаться из функции передачи, позволяя ядру выполнять более полезные операции нежели ожидание завершения вывода.
Вообще меня SPI в LPC1343 очень порадовал и я его с удовольствием использую (чаще USB из-за которого я и брал кортексы). Вам же предлагаю попробовав все это дело решить для себя самим.
Файлы: blinky_display.zip