Theory
General information
A liquid crystal display (LCD, LCD. Liquid crystal display) is installed on the debug board STM32L-Discovery, which has six 14 segment signs, 4 colons (Colon), 4 points (DP), 4 bars (Bar). All segments are grouped into COM0, COM1, COM2, COM3 groups of 24 segments. Each group has its own separate "common wire".


The STM32L152RBT6 microcontroller is installed on the debug board. The microcontroller has a built-in LCD controller, which controls monochrome liquid-crystal indicators.
LCD controller:
- Allows you to customize the update rate (frame rate - the frequency with which the information on the LCD is updated)
- Supports static and multiplex control mode
- Supports software contrast setting
- Allows you to use several levels of control voltage (up to four)
- Uses double buffering, allowing you to update data in LCD_RAM registers at any time during program execution, without compromising the integrity of the displayed information
LCD controller memory registers
In the STM32L152RB microcontroller, special LCD_RAM registers are allocated, the information stored in which corresponds to the COM0 - COM3 group of segments. Each group corresponds to two 32-bit registers. This number of registers allows the microcontroller to control the LCD with a large number of segments than that installed on the debug board.
To control a LCD with 176 segments, 4 groups of COM0 - COM3 with 44 segments each are used; for managing LCDs with 320 segments, 8 groups of COM0 - COM7 with 40 segments each are used.


On the STM32L-Discovery debug board, the LCD uses 96 segments, divided into 4 groups COM0 - COM3 with 24 segments each.

The LCD on the STM32L-Discovery debug board is connected in such a way that the S40, S41 bits of the second LCD_RAM registers in each group and the S0-S27 bits of the first LCD_RAM registers are used. To reduce the number of registers used, the information from the S40-S43 bits will be written to the free bits of S28-S31 using the remapping function.
Block frequency dividers
The block of frequency dividers (Frequency generator) allows to achieve different frame rates (frame rates) on the LCD in the range from 32 kHz to 1 MHz. The source of the clock signal can be used:
- External low-frequency generator with a frequency of 32 kHz (LSE. Low speed external)
- 37 kHz internal low frequency generator (LSI. Low speed internal)
- External RF generator with frequency dividers at 2.4,8 and 16 and a maximum frequency of 1 MHz. (HSE. High speed external)
To achieve accurate synchronization and reduce the offset of the DC voltage across the LCD segments, the clock source must be stable. The clock signal LCDCLK enters the LCD controller. The frequency of the clock signal is divided in accordance with the division factors, which are set by the PS [3: 0], DIV [3: 0] bits of the LCD_FCR (Frame Control Register) register. The resulting frequency at the output of the frequency divider unit is calculated by the formula:
')
f
ck_div = F
LCDCLK / (2
PS * (16 + DIV))
The frame rate is calculated by the formula:
f
Frame = f
ck_div * duty
where duty is the fill factor is the ratio of the pulse duration to its period. During one frame, the LCD displays the information from the registers LCD_RAM [x], LCD_RAM [x + 1] and so on. For the LCD installed on the debug board, for one frame the LCD controller should output information from 4 groups of COM0 - COM3 segments, therefore, the duration of the control pulse for one group will be 1/4 of the frame duration, i.e. duty = 1/4.
LCD control
There are two ways to control the LCD - static control mode and multiplex control mode. With a static display, each segment of the discharge indicator is connected to the output of the microcontroller. With reference to the LCD, on the STM32LDiscovery debug board, 6 * 14 = 84 microcontroller pins will be required (excluding colons, points and stripes). Due to the use of so many pins, the connection of other peripherals will become impossible. The STM32L152RB microcontroller has 64 outputs. In multiplex control mode (dynamic control mode), the same segments of the indicator bits are combined into groups. Information is displayed due to the alternate ignition of the segments of the indicator discharges, with a frequency that is not perceived by the human eye.
Multiplexed control allows you to manage a large number of segments. Instead of managing each element separately, they can be addressed in rows and columns (COM and SEG), so the control circuit is simplified, since each segment does not need its own control line. To enable the selected segment, it is necessary to apply the potential difference COM and SEG to it. Example of the first digit of the indicator (the indicator displays "1:"):
The first digit of the indicator at time t 0
The first digit of the indicator at time t 1
The first digit of the indicator at time t 2
The general scheme of connecting segments to the conclusions of the LCD
Wiring diagram of the conclusions of the LCD to the ports of the microcontrollerFor SEG lines, a control voltage is used, the number of levels of which is determined by the bias coefficient. The LCD on the debug board uses multiplex control mode with duty = 1/4 and bias = 1/3. The duty and bias values are set via the LCD_CR (Control Register) register in the DUTY [2: 0] and BIAS [1: 0] bits.
Practice
Configuring Microcontroller Ports
To control the LCD, the ports of the microcontroller must be configured accordingly:
- To the exit
- Using the alternative function AF 11 (Alternate function)
- Have a frequency output to the port of 400 kHz
- Use the push-pull mode
- Without pull-up resistors
When the port is in alternate function mode, the output data buffer of the port is controlled by signals coming from the periphery. The header file stm32lxx.h of the CMSIS library contains a description of all the peripheral registers, as well as the structure of access to them.
The pins of the LCD are connected to the GPIOA ports (PA1-PA3, PA8-PA10, PA15), GPIOB (PB3-PB5, PB8-PB15), GPIOC (PC0-PC3, PC6-PC11) of the microcontroller. To operate the LCD, a clock signal must be sent to the selected ports. Clocking the GPIO ports of the microcontroller comes from the AHB bus of the RCC (Reset and Clock Control) system bus - clocking and reset system. The clock signal is supplied by setting the corresponding bits in the RCC_AHBENR register (AHB peripheral clock enable register).
Register RCC_AHBENR (the figure shows the first 15 bits)For ports GPIOA, GPIOB, GPIOC, you must set 1 in 0, 1, 2 bits of the register.
Next, I will give the code to write information to the register using bitmasks and using hexadecimal codes. Using bitmasks is more convenient, but working with hexadecimal codes allows you to understand the essence of working with registers.
RCC->AHBENR |=(RCC_AHBENR_GPIOAEN|RCC_AHBENR_GPIOBEN|RCC_AHBENR_GPIOCEN); RCC->AHBENR = 0x7;
To specify the port operation modes, the GPIOx_MODER (GPIO port mode register) register is used (x = A..H). All register bits are grouped into MODERy groups [1: 0], where y is the pin number of the corresponding port. Ports must be configured for alternate function mode, i.e. in the group responsible for the pin, set the value to 10. For the GPIOA port, you need to configure pins 1-3.8-10.15, that is, set 1 to 3,5,7,17,19,21,31 discharges.
Register 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 */
The ports of the microcontroller must be put into push-pull mode. To do this, in the GPIOx_OTYPER (GPIO port output type register) register, set 1 to the bits responsible for the pins.
Register 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 */
Both options affect the selected pins. (For GPIOA port, pins 1-3,8-10,15 are configured). If you need to transfer all the pins of the port to the push-pull mode, you can write the value in the register:
GPIOA->OTYPER = 0x0;
To specify the frequency of information output to the port, the GPIOx_OSPEEDR (GPIO port output speed register) register is used. All register bits are grouped into OSPEEDRy [1: 0] groups, where y is the pin number of the corresponding port. In this paper, the frequency should be set to 400 kHz i. in the group responsible for the pin, set the value to 00.
Register 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 */
If you need to set the output frequency to the 400 kHz port for all pins, you can write the value in the register:
GPIOA->OSPEEDR = 0x0;
To disable pull-up pull-down pull-down resistors for selected pins, use the GPIOx_PUPDR register (GPIO port pullup / pull-down register). All register bits are grouped into groups PUPDRy [1: 0], where y is the pin number of the corresponding port. To disable the pull-up resistors in the group responsible for the pin, set to 00.
Register 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 */
If you need to disable pull-up resistors for all pins, you can write to the register value:
GPIOA->PUPDR = 0x0;
To use the alternative function for the microcontroller ports, two registers are used: GPIOx_AFRL (GPIO alternate function low register), responsible for the lower pins (0 to 7) and GPIOx_AFRH (GPIO alternate function high register), which is responsible for the higher pins (8 to 15). All register bits are grouped into AFRLy [3: 0] and AFRHy [3: 0] groups, where y is the pin number of the corresponding port. Ports must be configured to use the alternative function AF11; for this, the value for the pin group must be set to 1011.
Register GPIOx_AFRL (GPIO alternate function low register)
Register GPIOx_AFRH (GPIO alternate function high register)To do this, write to the value registers:
GPIOA->AFR[0] = 0xBBB0; GPIOA->AFR[1] = 0xB0000BBB;
AFR [0] = 0xBBB0 - writes the value to the GPIOx_AFRL register.
AFR [1] = 0xB0000BBB - writes the value to the GPIOx_AFRH register.
Settings for the corresponding pins of the GPIOB and GPIOC ports are made in the same way.
LCD controller setup
When working with the LCD controller, as with other peripherals, it is necessary to send a clock signal to it. A clock signal is also sent to the power management system. The controller and power management system for clocking use the bus APB1. To enable clocking in the RCC_APB1ENR register (APB1 peripheral clock enable register) it is necessary to set 1 in 9 and 28 bits.
Register RCC_APB1ENR (APB1 peripheral clock enable register) RCC->APB1ENR |= RCC_APB1ENR_PWREN|RCC_APB1ENR_LCDEN; RCC->APB1ENR |= 0x10000200;
To operate the LCD controller, you must specify the source of the clock signals. The source is specified in the RCC_CSR register. By default, writing to this register is prohibited. In the PWR power control register (PWR power control register), write protection to the RCC_CSR register is removed. Register RCC_CSR controls the clocking sources of the RTC clock and the LCD controller
Entry to the RCC_CSR register is permitted by setting 1 to 8 bits of the PWR_CR register.
Register PWR_CR (PWR power control register) PWR->CR |= PWR_CR_DBP; PWR->CR |= 0x100;
To change the clock source of the LCD controller (and the RTC clock too), you must first reset the clock source by setting the RTCRST bit (setting 1 to 23 bits) in the RCC_CSR (Control / status register) register.
Register RCC_CSR (Control / status register) RCC->CSR |= RCC_CSR_RTCRST;
Or by writing a value to the register using the operator "| =", since value by
The default register is different from 0x0:
RCC->CSR |= 0x800000;
To select a new clock source, remove the RTCRST bit:
RCC->CSR &= ~RCC_CSR_RTCRST; RCC->CSR &= ~0x800000;
An external low-frequency generator is selected as the clock source. To enable the generator in the RCC_CSR register, you must set the LSEON bit (set 1 to 8 bits):
RCC->CSR |= RCC_CSR_LSEON; RCC->CSR |= 0x100;
After turning on the generator, it takes some time to stabilize it. Generator readiness is checked by the hardware setting of the LSERDY bit in the RCC_CSR register:
while(!(RCC->CSR&RCC_CSR_LSERDY))
The selection of an external low-frequency generator as a clock source is made by setting the RCC_CSR register value 01 in the RTCSEL [1: 0] group:
RCC->CSR |= RCC_CSR_RTCSEL_LSE; RCC->CSR |= 0x10000;
In the LCD controller, you must set the desired bias mode. To do this, in the LCD control register LCD_CR it is necessary to set the value 10 to the BIAS group [1: 0]. Before setting the bits you need to clear the bits from the "garbage".
Register LCD_CR (LCD control register)Reset bits:
LCD->CR &= ~LCD_CR_BIAS; LCD->CR &= ~0x60;
Selection of bias = 1/3 mode using bitmasks:
LCD->CR |= LCD_CR_BIAS_1; LCD->CR |= 0x40;
Set the duty mode = 1/4. To do this, we also first reset all bits:
LCD->CR &=~LCD_CR_DUTY; LCD->CR &= ~0x1C;
Set the value of 011 to the DUTY [1: 0] group of the LCD_CR register for
duty mode = 1/4:
LCD->CR |= LCD_CR_DUTY_0|LCD_CR_DUTY_1; LCD->CR |= 0x;
Activate the function of reassigning the findings. To do this, set 1 to 7 bits of the register LCD_CR:
LCD->CR |= LCD_CR_MUX_SEG; <source lang=«C»>LCD->CR |= 0x80;
Set the values of the clock frequency division factors LCDCLK. The values of the coefficients are set in the register LCD_FCR (LCD frame control register). First, we also clear all bits, then set the necessary ones.
Register LCD_FCR (LCD frame control register) LCD->FCR &= ~LCD_FCR_PS; LCD->FCR &= ~LCD_FCR_DIV; LCD->FCR &= ~0x3C00000; LCD->FCR &= ~0x3C0000;
The values of the clock frequency division factors are set to ck_ps = LCDCLK / 16, ck_div = ck_ps / 17. To do this, set 1 in 24 and 18 bits:
LCD->FCR |= 0x1040000;
To set the desired level of contrast, it is necessary to set the value of 010 to the CC group [1: 0], also pre-clearing the bits from the old values:
LCD->FCR &= ~LCD_FCR_CC; LCD->FCR |= LCD_FCR_CC_1; LCD->FCR &= ~0x1C00; LCD->FCR |= 0x800;
After setting all the values, it takes some time to synchronize the register LCD_FCR. Register synchronization is checked by the hardware setting of the FCRSF bit in the LCD status register LCD_SR.
Register LCD_SR (LCD status register) while(!(LCD->SR&LCD_SR_FCRSR))
As the voltage source for the LCD, select the internal step-up converter to form the V
lcd . To do this, in the first digit of the register LCD_CR (LCD control register) is set to 0:
LCD->CR &= ~LCD_CR_VSEL; LCD->CR &= ~0x2;
The resolution of the LCD controller operation is performed by setting the LCD control register to 1 in 0 digit:
LCD->CR |= LCD_CR_LCDEN; LCD->CR |= 0x1;
After installing the internal step-up converter as a voltage source, you must wait until it is ready. Readiness is checked by hardware setting of the RDY bit in the LCD status register LCD_SR:
while(!(LCD->SR&LCD_SR_RDY))
After the resolution of the LCD controller is enabled, you must wait until it is ready. Readiness is checked by the hardware setting of the ENS bit in the LCD status register LCD_SR:
while(!(LCD->SR&LCD_SR_ENS))
Image formation on the LCD
All indicator segments are grouped into COM0 - COM3 groups of 24 segments each (SEG0-SEG23). Information about the segments is stored in the LCD_RAM registers of the LCD controller memory. The layout of the printed circuit board is such that the segment numbers do not correspond to the digits of the LCD_RAM registers.

In order to display 1 in the first digit of the LCD, it is necessary to light segments 1B, 1C. Segment 1B belongs to the group COM0, segment 1C belongs to the group COM1. Therefore, information about them should be recorded in the RAM [0] (LCD_RAM0), RAM [2] (LCD_RAM2) registers, respectively. For segment 1B, the LCD output LCDSEG22 is responsible, information about which is stored in the SEG40 bit of the RAM [1] register (LCD_RAM1). Using the reassign function, the SEG28 bit in the RAM [0] register (LCD_RAM0) will be responsible for the LCDSEG22 segment. LCD segment LCDSEG1 is responsible for segment 1C, information about which is stored in the SEG1 bit of the RAM register [2] (LCD_RAM2).

LCD->RAM[0]= 0x10000000; LCD->RAM[2] = 0x2;
Before writing values to memory registers, it is necessary to check whether the previous data transfer to the LCD is completed. To do this, check the UDR (Update display request) bit of the LCD_SR register (LCD status register). The LCD controller has two output buffers, the information is entered into the first buffer, and is displayed on the LCD from the second buffer. The UDR bit is set during transfer from the first buffer to the second, protecting the LCD_RAM registers from writing:
while(LCD->SR & LCD_SR_UDR)
After writing information to the LCD_RAM registers, you need to set the UDR bit in the LCD status (LCD_SR) register (set 1 to 2 bits):
LCD->SR |= LCD_SR_UDR; LCD->SR |= 0x4;

Ready project code #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: User
Gariks has written a useful program for generating values for RAM registers.References:
- http://chipspace.ru/ STM32L-DISCOVERY. We connect LCD
- http://easyelectronics.ru ARM. Training course
- http://radiokot.ru/ Dynamic Indication
- Discovery Kit for the STM32 L1 series - with STM32L152 MCU