Теория
Общие сведения
На отладочной плате STM32L-Discovery установлен жидкокристаллический индикатор (ЖКИ, англ. LCD. Liquid crystal display), имеющий шесть 14 сегментных знаков, 4 знака двоеточия (Colon), 4 точки (DP), 4 полоски (Bar). Все сегменты объединены в группы СOM0, COM1, COM2, COM3 по 24 сегмента. Каждая группа имеет свой отдельный «общий провод».
На отладочной плате установлен микроконтроллер STM32L152RBT6. В микроконтроллере есть встроенный контроллер ЖКИ, который управляет монохромными жидкокристаллическими индикаторами.
Контроллер ЖКИ:
- Позволяет настраивать частоту обновлений (частоту кадров — частота, с которой обновляется информация на ЖКИ)
- Поддерживает статический и мультиплексный режим управления
- Поддерживает программную установку контраста
- Позволяет использовать несколько уровней управляющего напряжения (до четырех)
- Использует двойную буферизацию, позволяющую обновлять данные в регистрах LCD_RAM в любое время выполнения программы, не нарушая целостность отображаемой информации
Регистры памяти контроллера ЖКИ
В микроконтроллере STM32L152RB выделены специальные регистры LCD_RAM, информация, хранимая в которых, соответствует группе сегментов COM0 — COM3. Каждой группе соответствует два 32 разрядных регистра. Такое количество регистров позволяет микроконтроллеру управлять ЖКИ c большим количеством сегментов, чем установленным на отладочной плате.
Для управления ЖКИ со 176 сегментами используются 4 группы COM0 — COM3 по 44 сегмента каждая, для управления ЖКИ с 320 сегментами используются 8 групп COM0 — COM7 по 40 сегментов каждая.
На отладочной плате STM32L-Discovery используется ЖКИ с 96 сегментами, разделенными на 4 группы COM0 — COM3 по 24 сегмента каждая.
ЖКИ на отладочной плате STM32L-Discovery подключен таким образом, что используются биты S40, S41 вторых регистров LCD_RAM в каждой группе и биты S0-S27 первых регистров LCD_RAM. Для уменьшения количества используемых регистров, информация из битов S40-S43 будет записываться в свободные биты S28-S31, используя функцию переназначения (remapping).
Блок делителей частоты
Блок делителей частоты (Frequency generator) позволяет добиться различной частоты кадров (frame rates) на ЖКИ в диапазоне от 32 кГц до 1 МГц. В качестве источника тактирующего сигнала могут использоваться:
- Внешний НЧ генератор с частотой 32 кГц (LSE. Low speed external)
- Внутренний НЧ генератор с частотой 37 кГц (LSI. Low speed internal)
- Внешний ВЧ генератор с делителями частоты на 2,4,8 и 16 и максимальной частотой 1 МГц. (HSE. High speed external)
Для достижения точной синхронизации и снижения смещения напряжения постоянного тока через сегменты ЖКИ источник тактирующего сигнала должен обладать стабильностью. Тактирующий сигнал LCDCLK поступает в контроллер ЖКИ. Частота тактового сигнала делится, в соответствии с коэффициентами деления, которые устанавливаются битами PS[3:0], DIV[3:0] регистра LCD_FCR (Frame Control Register). Результирующая частота на выходе блока делителей частоты рассчитывается по формуле:
fck_div=FLCDCLK / (2PS*(16+DIV))
Частота кадров рассчитывается по формуле:
fFrame=fck_div*duty
где duty – коэффициент заполнения – отношение длительность импульса к его периоду. За время одного кадра на ЖКИ последовательно выводится информация из регистров LCD_RAM[x], LCD_RAM[x+1] и тд. Для ЖКИ установленного на отладочной плате, за один кадр контроллер ЖКИ должен вывести информацию из 4 групп сегментов COM0 — COM3, следовательно, длительность управляющего импульса для одной группы будет 1/4 длительности кадра, т.е. duty=1/4.
Управление ЖКИ
Существует два способа управления ЖКИ – статический режим управления и мультиплексный режим управления. При статической индикации каждый сегмент разряда индикатора подключен к выходу микроконтроллера. Применительно к ЖКИ, на отладочной плате STM32LDiscovery, потребуется 6*14=84 выводов микроконтроллера (без учета двоеточий, точек и полосок). Из-за использования такого количества выводов, подключение другой периферии станет невозможным. Микроконтроллер STM32L152RB имеет 64 вывода. При мультиплексном режиме управлении (динамический режим управления) одинаковые сегменты разрядов индикатора объединены в группы. Отображение информации происходит за счет поочередного зажигания сегментов разрядов индикатора, с частотой, не воспринимаемой человеческим глазом.
Мультиплексное управление позволяет управлять большим количеством сегментов. Вместо раздельного управления каждым элементом, они могу адресоваться по строкам и столбцам (COM и SEG), таким образом, упрощается управляющая схема, т.к. каждому сегменту не требуется собственная управляющая линия. Для включения выбранного сегмента, на него надо подать разность потенциалов COM и SEG. Пример работы первого разряда индикатора (на индикатор выводится «1:»):
Первый разряд индикатора в момент времени t0
Первый разряд индикатора в момент времени t1
Первый разряд индикатора в момент времени t2
Общая схема подключения сегментов к выводам ЖКИ
Схема подключения выводов ЖКИ к портам микроконтроллера
Для линий SEG используется управляющее напряжение, количество уровней которого определяется коэффициентом bias. ЖКИ на отладочной плате использует мультиплексный режим управления с duty=1/4 и bias=1/3. Значение duty и bias устанавливаются через регистр LCD_CR (Control Register) в битах DUTY[2:0] и BIAS[1:0].
Практика
Конфигурирование портов микроконтроллера
Для управления ЖКИ порты микроконтроллера должны быть настроены соответствующим образом:
- На выход
- Использование альтернативной функции AF 11 (Alternate function)
- Иметь частоты вывода в порт 400 кГц
- Использовать режим работы push-pull
- Без подтягивающих резисторов
При работе порта в режиме альтернативной функции, выходной буфер данных порта управляется сигналами, поступающими с периферии. Заголовочный файл stm32lxx.h библиотеки CMSIS содержит описание всех регистров периферии, а также структуры доступа к ним.
Выводы ЖКИ подключены к портам GPIOA (PA1-PA3,PA8-PA10,PA15), GPIOB (PB3-PB5, PB8-PB15), GPIOC (PC0-PC3,PC6-PC11) микроконтроллера. Для работы ЖКИ, на выбранные порты необходимо подать тактовый сигнал. Тактирование портов GPIO микроконтроллера происходит от шины AHB системы RCC (Reset and Clock Control) – системы тактировании и сброса. Подача тактового сигнала осуществляется установкой соответствующих битов в регистре RCC_AHBENR (AHB peripheral clock enable register).
Регистр RCC_AHBENR (на рисунке приведены первые 15 разрядов)
Для портов GPIOA, GPIOB, GPIOC необходимо установить 1 в 0, 1, 2 разряды регистра.
Далее я буду приводить код записи информации в регистр с использованием битмаски и с использованием шестнадцатеричных кодов. Использование битмасок удобнее, но работа с шестнадцатеричными кодами позволяет понять суть работы с регистрами.
RCC->AHBENR |=(RCC_AHBENR_GPIOAEN|RCC_AHBENR_GPIOBEN|RCC_AHBENR_GPIOCEN);
или
RCC->AHBENR = 0x7; /* 0x7=111 */
Для указания режимов работы порта используется регистр GPIOx_MODER (GPIO port mode register) (x = A..H). Все разряды регистра сгруппированы в группы MODERy[1:0], где y номер пина соответствующего порта. Порты необходимо настроить на режим альтернативной функции, т.е. в группе, отвечающей за пин, установить значение 10. Для порта GPIOA нужно настроить пины 1-3,8-10,15, т.е установить 1 в 3,5,7,17,19,21,31 разряды.
Регистр GPIOx_MODER (GPIO port mode register)
GPIOA->MODER |= (GPIO_MODER_MODER1_1 | GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1 | GPIO_MODER_MODER8_1 | GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1 | GPIO_MODER_MODER15_1);
или
GPIOA->MODER = 0x802A00A8; /* 0x802A00A8=1000 0000 0010 1010 0000 0000 1010 1000 */
Порты микроконтроллера необходимо перевести в режим push-pull. Для этого необходимо в регистре GPIOx_OTYPER (GPIO port output type register) установить 1 в разряды, отвечающие за пины.
Регистр GPIOx_OTYPER (GPIO port output type register)
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_1 | GPIO_OTYPER_OT_2 | GPIO_OTYPER_OT_3 | GPIO_OTYPER_OT_8 | GPIO_OTYPER_OT_9 | GPIO_OTYPER_OT_10 | GPIO_OTYPER_OT_15);
или
GPIOA->OTYPER &= ~0x0000870E; /* 0x870E=1000 0111 0000 1110 */
Оба варианта воздействуют на выбранные пины. (Для порта GPIOA настраиваются пины 1-3,8-10,15). Если необходимо перевести все пины порта в режим push-pull, можно записать в регистр значение:
GPIOA->OTYPER = 0x0;
Для указания частоты вывода информации в порт используется регистр GPIOx_OSPEEDR (GPIO port output speed register). Все разряды регистра сгруппированы в группы OSPEEDRy[1:0], где y номер пина соответствующего порта. В данной работе должна быть установлена частота 400 кГц т.е. в группе, отвечающей за пин, установить значение 00.
Регистр GPIOx_OSPEEDR (GPIO port output speed register)
GPIOA->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR1 | GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3 | GPIO_OSPEEDER_OSPEEDR8 | GPIO_OSPEEDER_OSPEEDR9 | GPIO_OSPEEDER_OSPEEDR10 | GPIO_OSPEEDER_OSPEEDR15);
или
GPIOA->OSPEEDR &= ~0xC03F00FC; /*0xC03F00FC=1100 0000 0011 1111 0000 0000 1111 1100 */
Если необходимо установить частоту вывода в порт 400 кГц для всех пинов, можно записать в регистр значение:
GPIOA->OSPEEDR = 0x0;
Для отключения подтягивающих резисторов pull-up, pull-down для выбранных пинов используется регистр GPIOx_PUPDR (GPIO port pullup/ pull-down register). Все разряды регистра сгруппированы в группы PUPDRy[1:0], где y – номер пина соответствующего порта. Для отключение подтягивающих резисторов в группе, отвечающей за пин, устанавливается значение 00.
Регистр GPIOx_PUPDR (GPIO port pull-up/pull-down register)
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR1 | GPIO_PUPDR_PUPDR2 | GPIO_PUPDR_PUPDR3 | GPIO_PUPDR_PUPDR8 | GPIO_PUPDR_PUPDR9 | GPIO_PUPDR_PUPDR10 | GPIO_PUPDR_PUPDR15);
или
GPIOA->PUPDR &= ~0xC03F00FC; /*0xC03F00FC=1100 0000 0011 1111 0000 0000 1111 1100 */
Если необходимо отключить подтягивающие резисторы для всех пинов, можно записать в регистр значение:
GPIOA->PUPDR = 0x0;
Для использования альтернативной функции для портов микроконтроллера используются два регистра GPIOx_AFRL (GPIO alternate function low register), отвечающий за младшие пины (с 0 по 7) и GPIOx_AFRH (GPIO alternate function high register), отвечающий за старшие пины (с 8 по 15). Все разряды регистров сгруппированы в группы AFRLy[3:0] и AFRHy[3:0], где y – номер пина соответствующего порта. Порты должны быть настроены на использование альтернативной функции AF11, для этого в группе, отвечающей за пин, должно быть установлено значение 1011.
Регистр GPIOx_AFRL (GPIO alternate function low register)
Регистр GPIOx_AFRH (GPIO alternate function high register)
Для этого необходимо записать в регистры значения:
GPIOA->AFR[0] = 0xBBB0; /* 0xBBB0 = 1011 1011 1011 0000*/
GPIOA->AFR[1] = 0xB0000BBB; /* 0xB0000BBB=1011 0000 0000 0000 0000 1011 1011 1011*/
AFR[0] = 0xBBB0 – записывает значение в регистр GPIOx_AFRL.
AFR[1] = 0xB0000BBB – записывает значение в регистр GPIOx_AFRH.
Настройки соответствующих пинов портов GPIOB, GPIOC производятся аналогично.
Настройка контроллера ЖКИ
При работе с контроллером ЖКИ, как и с другой периферией, на него необходимо подать тактовый сигнал. Тактовый сигнал также подается на систему управления питанием. Контроллер и система управления питанием для тактирования используют шину APB1. Для разрешения тактирования в регистре RCC_APB1ENR (APB1 peripheral clock enable register) необходимо установить 1 в 9 и 28 разрядах.
Регистр RCC_APB1ENR (APB1 peripheral clock enable register)
RCC->APB1ENR |= RCC_APB1ENR_PWREN|RCC_APB1ENR_LCDEN;
или
RCC->APB1ENR |= 0x10000200; /* 0x10000200=1 0000 0000 0000 0000 0010 0000 0000 */
Для работы контроллера ЖКИ необходимо указать источник тактовых сигналов. Источник указывается в регистре RCC_CSR. По умолчанию запись в этот регистр запрещена. В регистре управления питанием PWR_CR (PWR power control register) снимается защита от записи в регистр RCC_CSR. Регистр RCC_CSR управляет источниками тактирования часов RTC и контроллера ЖКИ
Запись в регистр RCC_CSR разрешается установкой 1 в 8 разряд регистра PWR_CR.
Регистр PWR_CR (PWR power control register)
PWR->CR |= PWR_CR_DBP;
или
PWR->CR |= 0x100; /* 0x100 =1 0000 0000 */
Для смены источника тактирования контроллера ЖКИ (и часов RTC тоже) необходимо сначала выполнить сброс источника тактирования установкой бита RTCRST (установкой 1 в 23 разряд) в регистре RCC_CSR (Control/status register).
Регистр RCC_CSR (Control/status register)
RCC->CSR |= RCC_CSR_RTCRST;
Или записав в регистр значение, используя оператор «|=», т.к. значение по
умолчанию регистра отлично от 0x0:
RCC->CSR |= 0x800000; /* 0x800000 = 1000 0000 0000 0000 0000 0000 */
Для выбора нового источника тактирования необходимо убрать бит RTCRST:
RCC->CSR &= ~RCC_CSR_RTCRST;
или
RCC->CSR &= ~0x800000;
В качестве источника тактового сигнала выбирается внешний НЧ генератор. Для включения генератора в регистре RCC_CSR необходимо установить бит LSEON (установить 1 в 8 разряд):
RCC->CSR |= RCC_CSR_LSEON;
или
RCC->CSR |= 0x100; /* 0x100 = 1 0000 0000 */
После включения генератора необходимо некоторое время на его стабилизацию. Готовность генератора проверяется аппаратной установкой бита LSERDY в регистре RCC_CSR:
while(!(RCC->CSR&RCC_CSR_LSERDY));
Выбор внешнего НЧ генератора в качестве источника тактового сигнала осуществляется установкой в группе RTCSEL[1:0] регистра RCC_CSR значения 01:
RCC->CSR |= RCC_CSR_RTCSEL_LSE;
или
RCC->CSR |= 0x10000; /* 0x10000 = 01 0000 0000 0000 0000 */
В контроллере ЖКИ необходимо установить нужный режим bias. Для этого в регистре LCD_CR (LCD control register) необходимо установить значение 10 в группу BIAS[1:0]. Перед установкой бит необходимо очистить биты от «мусора».
Регистр LCD_CR (LCD control register)
Сброс битов:
LCD->CR &= ~LCD_CR_BIAS;
или
LCD->CR &= ~0x60;
Выбор режима bias=1/3 с использованием битмаски:
LCD->CR |= LCD_CR_BIAS_1;
или
LCD->CR |= 0x40;
Устанавливаем режим duty=1/4. Для этого также вначале сбрасываем все биты:
LCD->CR &=~LCD_CR_DUTY;
или
LCD->CR &= ~0x1C;
Устанавливаем значение 011 в группу DUTY[1:0] регистра LCD_CR для
режима duty=1/4:
LCD->CR |= LCD_CR_DUTY_0|LCD_CR_DUTY_1;
или
LCD->CR |= 0xС;
Активируем функцию переназначения выводов. Для этого устанавливаем 1 в 7 разряд регистра LCD_CR:
LCD->CR |= LCD_CR_MUX_SEG;
или
<source lang=«C»>LCD->CR |= 0x80;
Устанавливаем значения коэффициентов деления частоты тактового сигнала LCDCLK. Значения коэффициентов выставляются в регистре LCD_FCR (LCD frame control register). Вначале также очищаем все биты, затем устанавливаем нужные.
Регистр LCD_FCR (LCD frame control register)
LCD->FCR &= ~LCD_FCR_PS;
LCD->FCR &= ~LCD_FCR_DIV;
или
LCD->FCR &= ~0x3C00000;
LCD->FCR &= ~0x3C0000;
Значения коэффициентов деления частоты тактового сигнала устанавливаем равными ck_ps = LCDCLK/16, ck_div = ck_ps/17. Для этого устанавливаем 1 в 24 и в 18 разряды:
LCD->FCR |= 0x1040000; /*0x1040000 = 1 0000 0100 0000 0000 0000 0000*/
Для установки нужного уровня контраста необходимо установить значение 010 в группу СС[1:0], так же предварительно очистив биты от старых значений:
LCD->FCR &= ~LCD_FCR_CC;
LCD->FCR |= LCD_FCR_CC_1;
или
LCD->FCR &= ~0x1C00;
LCD->FCR |= 0x800; /*0x800 = 1000 0000 0000*/
После установки всех значений необходимо некоторое время на синхронизацию регистра LCD_FCR. Синхронизация регистра проверяется аппаратной установкой бита FCRSF в регистре LCD_SR (LCD status register).
Регистр LCD_SR (LCD status register)
while(!(LCD->SR&LCD_SR_FCRSR));
В качестве источника напряжения для ЖКИ выбираем внутренний step-up converter для формирования Vlcd. Для этого в первый разряд регистра LCD_CR (LCD control register) устанавливается значение 0:
LCD->CR &= ~LCD_CR_VSEL;
или
LCD->CR &= ~0x2;
Разрешение работы ЖКИ контроллера происходит установкой 1 в 0 разряд регистра LCD_CR (LCD control register):
LCD->CR |= LCD_CR_LCDEN;
или
LCD->CR |= 0x1;
После установки в качестве источника напряжения внутреннего step-up converter, необходимо дождаться его готовности. Готовность проверяется аппаратной установкой бита RDY в регистре LCD_SR (LCD status register):
while(!(LCD->SR&LCD_SR_RDY));
После разрешения работы контроллера ЖКИ, необходимо дождаться его готовности. Готовность проверяется аппаратной установкой бита ENS в регистре LCD_SR (LCD status register):
while(!(LCD->SR&LCD_SR_ENS));
Формирование изображения на ЖКИ
Все сегменты индикатора объединены в группы COM0 — COM3 по 24 сегмента в каждой (SEG0-SEG23). Информация о сегментах хранится в регистрах LCD_RAM памяти контроллера ЖКИ. Разводка печатной платы такова, что номера сегментов не соответствуют номерам разрядов регистров LCD_RAM.
Что бы отобразить 1 в первом разряде ЖКИ, необходимо зажечь сегменты 1B,1C. Сегмент 1B принадлежит группе COM0, сегмент 1C принадлежит группе COM1. Следовательно информация о них должна быть записана в регистры RAM[0] (LCD_RAM0), RAM[2] (LCD_RAM2) соответственно. За сегмент 1B отвечает вывод ЖКИ LCDSEG22, информация о котором храниться в разряде SEG40 регистра RAM[1] (LCD_RAM1). С использованием функции переназначения за сегмент LCDSEG22 будет отвечать разряд SEG28 регистра RAM[0] (LCD_RAM0). За сегмент 1С отвечает вывод ЖКИ LCDSEG1, информация о котором храниться в разряде SEG1 регистра RAM[2] (LCD_RAM2).
LCD->RAM[0]= 0x10000000; /*0x10000000 = 1 0000 0000 0000 0000 0000 0000 0000 */
LCD->RAM[2] = 0x2; /*0x2= 10 */
До записи значений в регистры памяти необходимо проверить завершена ли предыдущая передача данных на ЖКИ. Для этого проверяется бит UDR (Update display request) регистра LCD_SR (LCD status register). Контроллер ЖКИ имеет два выходных буфера, информация заносится в первый буфер, а выводится на ЖКИ из второго буфера. Бит UDR устанавливается во время передачи из первого буфера во второй, защищая от записи регистры LCD_RAM:
while(LCD->SR & LCD_SR_UDR);
После записи информации в регистры LCD_RAM необходимо установить бит UDR в регистре LCD_SR (LCD status register)(установить 1 во 2 разряд):
LCD->SR |= LCD_SR_UDR;
или
LCD->SR |= 0x4; /*0x4 = 100 */
Готовый код проекта
#include "stm32l1xx.h"
void gpio(void);
void controller(void);
int main()
{
gpio();
controller();
while(LCD->SR & LCD_SR_UDR);
LCD->RAM[0]= 0x3E300FFF;
LCD->RAM[2] = 0x2EB00382;
LCD->RAM[6] = 0x400;
LCD->SR |= LCD_SR_UDR;
while(1);
}
void gpio(void)
{
RCC->AHBENR |= 0x7;
GPIOA->MODER |= 0x802A00A8;
GPIOB->MODER |= 0xAAAA0A80;
GPIOC->MODER |= 0xAAA0AA;
GPIOA->OTYPER &= ~0x870E;
GPIOB->OTYPER &= ~0xFF38;
GPIOC->OTYPER &= ~0xFCF;
GPIOA->PUPDR &= ~0xC03F00FC;
GPIOB->PUPDR &= ~0xFFFF0FC0;
GPIOC->PUPDR &= ~0xFFF0FF;
GPIOA->OSPEEDR &= ~0xC03F00FC;
GPIOB->OSPEEDR &= ~0xFFFF0FC0;
GPIOC->OSPEEDR &= ~0xFFFFF0FF;
GPIOA->AFR[0] |= 0xBBB0;
GPIOA->AFR[1] |= 0xB0000BBB;
GPIOB->AFR[0] |= 0xBBB000;
GPIOB->AFR[1] |= 0xBBBBBBBB;
GPIOC->AFR[0] |= 0xBB00BBBB;
GPIOC->AFR[1] |= 0xBBBB;
}
void controller(void)
{
RCC->APB1ENR |= 0x10000200;
PWR->CR |= 0x100;
RCC->CSR |= 0x800000;
RCC->CSR &= ~0x800000;
RCC->CSR |= 0x100;
while(!(RCC->CSR&RCC_CSR_LSERDY));
RCC->CSR |= 0x10000;
LCD->CR &= ~0x60;
LCD->CR |= 0x40;
LCD->CR &= ~0x1C;
LCD->CR |= 0xC;
LCD->CR |= 0x80;
LCD->FCR &= ~0x3C00000;
LCD->FCR &= ~0x3C0000;
LCD->FCR |= 0x1040000;
LCD->FCR &= ~0x1C00;
LCD->FCR |= 0x800;
while(!(LCD->SR&LCD_SR_FCRSR));
LCD->CR &= ~0x2;
LCD->CR |= 0x1;
while(!(LCD->SR&LCD_SR_RDY));
while(!(LCD->SR&LCD_SR_ENS));
}
UPD: Пользователь Gariks написал полезную программу для генерации значений для регистров RAM
Ссылки:
- http://chipspace.ru/ STM32L-DISCOVERY. Подключаем LCD
- http://easyelectronics.ru ARM. Учебный Курс
- http://radiokot.ru/ Динамическая индикация
- Discovery kit for the STM32 L1 series — with STM32L152 MCU