0、前言
一直以来都在寻找一个方便的、可靠的、丰富的点阵型LCD驱动库 ,因为大型的GUI解决方案并不适合像12864(基于7920)这种资源紧缺型的显示模组使用,而网络上充斥代码的资源都是简单实现了一个字符输出功能,达不到预期的目的。直到无意中看到了u8g2。通过学习后发现该显示库支持很多种字体 fonts (英文和数字),而且具有完整的驱动函数库(直线、圆形、斜线、字符旋转镜像反白、bitmap一应俱全)和丰富的演示demo。特别适合应用在嵌入式mcu上面。于是把它移植到了stm32上面,因此才有了这篇blog。
github地址:https://github.com/olikraus/u8g2
U8g2: Library for monochrome displays, version 2
U8g2 is a monochrome graphics library for embedded devices. U8g2 supports controller based (for example SSD1306) monochrome OLEDs and LCDs (See the U8g2/U8x8 setup guide for a complete list of supported display controller). The Arduino library U8g2 can be installed from the library manager of the Arduino IDE. U8g2 also includes U8x8 library:
U8g2
- Includes all graphics procedures (line/box/circle draw).
- Supports many fonts. (Almost) no restriction on the font height.
- Requires some memory in the microcontroller to render the display.
U8x8
- Text output only (character) device.
- Only fonts allowed with fixed size per character (8x8 pixel).
- Writes directly to the display. No buffer in the microcontroller required.
Setup Guide and Reference Manual
通过介绍(readme.md)可以知道u8g2是一个支持嵌入式设备的显示驱动库,它包含了两种驱动,其中u8g2支持像12864这种常见的点阵型LCD,u8x8支持像1602这种字符型LCD,此外它所支持的所以驱动IC如下所示:
Supported Display Controller: SSD1305, SSD1306, SSD1309, SSD1322, SSD1325, SSD1327, SSD1329, SSD1606, SSD1607, SH1106, T6963, RA8835, LC7981, PCD8544, PCF8812, UC1601, UC1604, UC1608, UC1610, UC1611, UC1701, ST7565, ST7567, ST7588, ST75256, NT7534, IST3020, ST7920, LD7032, KS0108, SED1520, SBN1661, IL3820, MAX7219 (see here for a full list)
查阅后确认:LCD ST7920 128X64 在支持的范围内,但是作者提供的demo都是基于Arduino使用,并没有直接在stm32上直接调用的实例。“U8g2 works nicely without C++/Arduino --by olikraus”。自己动手,丰衣足食,既然没有,那么接下来就自己动手移植!~
1、环境准备
- Windows7 sp1
- Keil MDK 5.24(armcc v5.06 update5)+ Jlink
- MCU : STM32F103C8T6 (64k flash 、20k ram)
- LCD : ZXM12864F4 (controller st7920)
- u8g2: 2017-12-09 v2.20.13
- STM32Cube_FW_F1_V1.6.0 (STM32F1xx HAL Drivers v1.1.1 2017-5-12)
2、移植
根据作者提供的移植说明《Porting-to-new-MCU-platform》,需要我们实现的函数主要有两个:
- The "uC specific" GPIO and Delay callback (the last argument of the setup function)
- The u8x8 byte communication callback (the second to last argument of the setup function)
Rotation/Mirror | Description |
---|---|
U8G2_R0 |
No rotation, landscape |
U8G2_R1 |
90 degree clockwise rotation |
U8G2_R2 |
180 degree clockwise rotation |
U8G2_R3 |
270 degree clockwise rotation |
U8G2_MIRROR |
No rotation, landscape, display content is mirrored (v2.6.x) |
Message | Description |
---|---|
U8X8_MSG_BYTE_INIT | Send once during the init phase of the display. |
U8X8_MSG_BYTE_SET_DC | Set the level of the data/command pin. arg_int contains the expected output level. Use u8x8_gpio_SetDC(u8x8, arg_int) to send a message to the GPIO procedure. |
U8X8_MSG_BYTE_START_TRANSFER | Set the chip select line here. u8x8->display_info->chip_enable_level contains the expected level. Use u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level) to call the GPIO procedure. |
U8X8_MSG_BYTE_SEND | Send one or more bytes, located at arg_ptr , arg_int contains the number of bytes. |
U8X8_MSG_BYTE_END_TRANSFER | Unselect the device. Use the CS level from here: u8x8->display_info->chip_disable_level . |
- uint8_t u8x8_byte_arduino_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
- {
- uint8_t *data;
- uint8_t internal_spi_mode;
- switch(msg)
- {
- case U8X8_MSG_BYTE_SEND:
- data = (uint8_t *)arg_ptr;
- while( arg_int > 0 )
- {
- SPI.transfer((uint8_t)*data);
- data++;
- arg_int--;
- }
- break;
- case U8X8_MSG_BYTE_INIT:
- u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
- SPI.begin();
- break;
- case U8X8_MSG_BYTE_SET_DC:
- u8x8_gpio_SetDC(u8x8, arg_int);
- break;
- case U8X8_MSG_BYTE_START_TRANSFER:
- internal_spi_mode = 0;
- switch(u8x8->display_info->spi_mode)
- {
- case 0: internal_spi_mode = SPI_MODE0; break;
- case 1: internal_spi_mode = SPI_MODE1; break;
- case 2: internal_spi_mode = SPI_MODE2; break;
- case 3: internal_spi_mode = SPI_MODE3; break;
- }
- SPI.beginTransaction(SPISettings(u8x8->display_info->sck_clock_hz, MSBFIRST, internal_spi_mode));
- u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level);
- u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL);
- break;
- case U8X8_MSG_BYTE_END_TRANSFER:
- u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL);
- u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
- SPI.endTransaction();
- break;
- default:
- return 0;
- }
- return 1;
- }
Byte Procedure | Description |
---|---|
u8x8_byte_4wire_sw_spi | Standard 8-bit SPI communication with "four pins" (SCK, MOSI, DC, CS) |
u8x8_byte_3wire_sw_spi | 9-bit communication with "three pins" (SCK, MOSI, CS) |
u8x8_byte_8bit_6800mode | Parallel interface, 6800 format |
u8x8_byte_8bit_8080mode | Parallel interface, 8080 format |
u8x8_byte_sw_i2c | Two wire, I2C communication |
u8x8_byte_ks0108 | Special interface for KS0108 controller |
-
Delay messages of the form "U8X8_MSG_DELAY_". These messages are used to provide delay for the software implementation of I2C, SPI etc.
In order for the software (aka. bit-banged) interfaces to work you need to implement the MCU specific busy-wait loop to provide a correct amount of delay.
For the example implementation I used the Cypress PSoC specific delay functions of the form CyDelay* -
GPIO messages of the form "U8X*_MSG_GPIO". These messages are used to write 1s and 0s to the GPIOs which are being used to interface to the device. i.e. the SCL/SDA or Reset or CS etc.
For the example implementation I used the Cypress pin write functions which all take the form of "pinname_Write()". -
GPIO menu pins are used to get the state of an input pin. These messages are only required for the build in menu function and can be ignored, if the U8G2/U8X8 menu functions are not used.
- uint8_t u8x8_gpio_and_delay_template(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
- {
- switch(msg)
- {
- case U8X8_MSG_GPIO_AND_DELAY_INIT: // called once during init phase of u8g2/u8x8
- break; // can be used to setup pins
- case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second
- break;
- case U8X8_MSG_DELAY_100NANO:<span style="white-space:pre;"> </span>// delay arg_int * 100 nano seconds
- break;
- case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
- break;
- case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
- break;
- case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
- break;<span style="white-space:pre;"> </span>// arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
- case U8X8_MSG_GPIO_D0: // D0 or SPI clock pin: Output level in arg_int
- //case U8X8_MSG_GPIO_SPI_CLOCK:
- break;
- case U8X8_MSG_GPIO_D1: // D1 or SPI data pin: Output level in arg_int
- //case U8X8_MSG_GPIO_SPI_DATA:
- break;
- case U8X8_MSG_GPIO_D2: // D2 pin: Output level in arg_int
- break
- case U8X8_MSG_GPIO_D3: // D3 pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_D4: // D4 pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_D5: // D5 pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_D6: // D6 pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_D7: // D7 pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_E: // E/WR pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_CS: // CS (chip select) pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_DC:<span style="white-space:pre;"> </span>// DC (data/cmd, A0, register select) pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_RESET: // Reset pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_CS1: // CS1 (chip select) pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_CS2: // CS2 (chip select) pin: Output level in arg_int
- break;
- case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
- break; // arg_int=1: Input dir with pullup high for I2C clock pin
- case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin
- break; // arg_int=1: Input dir with pullup high for I2C data pin
- case U8X8_MSG_GPIO_MENU_SELECT:
- u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
- break;
- case U8X8_MSG_GPIO_MENU_NEXT:
- u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
- break;
- case U8X8_MSG_GPIO_MENU_PREV:
- u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
- break;
- case U8X8_MSG_GPIO_MENU_HOME:
- u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
- break;
- default:
- u8x8_SetGPIOResult(u8x8, 1);<span style="white-space:pre;"> </span>// default return value
- break;
- }
- return 1;
- }
3、实践
经过移植分析后,结合现有的硬件条件,可以得出:
- 使用软件模拟4线spi方式来驱动LCD12864(st7920)引脚对应方式如下:
PB10 - CLK - E
PB11 - SPI data - R/W
PB9 - RST - RST
- - CS - PSB (已经外接下拉电阻,默认为串行模式)
PB12 - CD - RS
- 使用Full screen buffer mode模式,调用 u8g2_Setup_st7920_s_128x64_f 函数
- 使用u8g2库自带的函数u8x8_byte_4wire_sw_spi, 软件模拟spi串行协议
- 需要实现 u8g2_gpio_and_delay_stm32 函数
具体步骤:
- 下载u8g2库源文件,将所有相关的c文件(csrc文件夹)添加到keil工程中,keil工程使用STM32Cube_FW_F1_V1.6.0下的f103分支的temple模板。
- 添加u8g2_gpio_and_delay_stm32支持,具体代码如下:
- uint8_t u8g2_gpio_and_delay_stm32(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
- {
- GPIO_InitTypeDef gpioinitstruct;
- switch(msg){
- //Function which implements a delay, arg_int contains the amount of ms
- case U8X8_MSG_GPIO_AND_DELAY_INIT:
- __HAL_RCC_GPIOB_CLK_ENABLE();
- /* Configure the GPIO_LED pin */
- gpioinitstruct.Pin = GPIO_PIN_12|GPIO_PIN_11|GPIO_PIN_10|GPIO_PIN_9;
- gpioinitstruct.Mode = GPIO_MODE_OUTPUT_PP;
- gpioinitstruct.Pull = GPIO_NOPULL;
- gpioinitstruct.Speed = GPIO_SPEED_FREQ_HIGH;
- HAL_GPIO_Init(GPIOB, &gpioinitstruct);
- HAL_GPIO_WritePin(LED_GPIO_PORT, GPIO_PIN_12|GPIO_PIN_11|GPIO_PIN_10|GPIO_PIN_9, GPIO_PIN_SET);
- break;
- //Function which implements a delay, arg_int contains the amount of ms
- case U8X8_MSG_DELAY_MILLI:
- HAL_Delay(arg_int);
- break;
- //Function which delays 10us
- case U8X8_MSG_DELAY_10MICRO:
- delay_us(10);
- break;
- //Function which delays 100ns
- case U8X8_MSG_DELAY_100NANO:
- __NOP();
- break;
- //Function to define the logic level of the clockline
- case U8X8_MSG_GPIO_SPI_CLOCK:
- if (arg_int) LCD_SCLK_1;
- else LCD_SCLK_0;
- break;
- //Function to define the logic level of the data line to the display
- case U8X8_MSG_GPIO_SPI_DATA:
- if (arg_int) LCD_SID_1;
- else LCD_SID_0;
- break;
- // Function to define the logic level of the CS line
- case U8X8_MSG_GPIO_CS1:
- if (arg_int) LCD_RS_1 ;
- else LCD_RS_0;
- break;
- //Function to define the logic level of the Data/ Command line
- case U8X8_MSG_GPIO_DC:
- break;
- //Function to define the logic level of the RESET line
- case U8X8_MSG_GPIO_RESET:
- if (arg_int) LCD_RST_1;
- else LCD_RST_0;
- break;
- default:
- return 0; //A message was received which is not implemented, return 0 to indicate an error
- }
- return 1; // command processed successfully.
- }
3. 裁剪 u8g2_d_setup.c u8g2_d_memory.c 文件中与st7920无关的代码,减小代码体积,ram用量。
4. 修改 u8x8.h 文件,添加u8g2显示库对 keil setction(armcc) 的支持。
- #define SECTION(x) __attribute__((section(x)))
- #ifndef U8X8_FONT_SECTION
- # define U8X8_FONT_SECTION(name) SECTION(".font." name)
- #endif
5.修改 main() 函数,初始化u8g2显示库,增加显示代码。
- int main(void)
- {
- /* This sample code shows how to configure The HAL time base source base with a
- dedicated Tick interrupt priority.
- A general purpose timer (TIM2) is used instead of Systick as source of time base.
- Time base duration is fixed to 1ms since PPP_TIMEOUT_VALUEs are defined and
- handled in milliseconds basis.
- */
- /* STM32F1xx HAL library initialization:
- - Configure the Flash prefetch
- - Configure timer (TIM2) to generate an interrupt each 1 msec
- - Set NVIC Group Priority to 4
- - Low Level Initialization
- */
- HAL_Init();
- /* Configure the system clock to 72 MHz */
- SystemClock_Config();
- /* Configure LED */
- BSP_LED_Init();
- u8g2_Setup_st7920_s_128x64_f(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi, u8g2_gpio_and_delay_stm32);
- u8g2_InitDisplay(&u8g2);
- u8g2_SetPowerSave(&u8g2, 0);
- HAL_Delay(1000);
- /* Insert a Delay of 1000 ms and toggle LED2, in an infinite loop */
- while (1)
- {
- /* Insert a 1s delay */
- HAL_Delay(1000);
- BSP_LED2_Toggle();
- BSP_LED1_Toggle();
- u8g2_ClearBuffer(&u8g2);
- u8g2_SetFontMode(&u8g2, 1);
- u8g2_SetFontDirection(&u8g2, 0);
- u8g2_SetFont(&u8g2, u8g2_font_helvB18_te);
- u8g2_DrawStr(&u8g2, 0, 24, "hello world");
- u8g2_DrawStr(&u8g2, 0, 50, "i am Re.");
- u8g2_SetFont(&u8g2, u8g2_font_u8glib_4_tf);
- u8g2_DrawStr(&u8g2, 0, 60, "2018-01-18");
- u8g2_SendBuffer(&u8g2);
- }
- }
6.修改与工程相关的其他代码,编译,下载。
7.测试
4、后记
u8g2库支持的函数如下:u8g2reference
u8x8库支持的函数如下:u8x8reference
5、其他
Pin Argument | Description | Datasheet Names |
---|---|---|
clock | SPI or I2C clock line | SCL, SCLK, ... |
data | SPI or I2C data line | SDA, MOSI, SDIN, ... |
d0 ... d7 | Data lines of the parallel interface | D0 ... D7 |
cs | Chip select line | CS |
dc | Data/command selection line (register select) | D/C, A0, RS, ... |
enable | "Write" for the 8080 interface, "enable" for the 6800 interface | 8080: WR, 6800: E |
reset | Reset line |
串行协议说明
- 3SPI, 3-wire SPI: Serial Peripheral Interface with three signals: Clock, Data and Chip-Select.
- 4SPI, 4-Wire SPI: Same as 3SPI, but with one additional line for commands and data (often labeled as D/C, RS or A0)
- I2C, IIC or TWI: Inter-Integrated Circuit Bus which has two signals: Clock (SCL) and Data (SDA).
- 8080: A 8-Bit bus which requires 8 data lines, chip select and a write strobe signal.
- 6800: Another 8-Bit bus, but with a different protocol.
setup中buffersize的说明
asda
Controller "st7920", Display "128x64" | Descirption |
---|---|
u8g2_Setup_st7920_128x64_1(u8g2, rotation, u8x8_byte_8bit_6800mode, uC specific) | page buffer, size = 128 bytes |
u8g2_Setup_st7920_128x64_2(u8g2, rotation, u8x8_byte_8bit_6800mode, uC specific) | page buffer, size = 256 bytes |
u8g2_Setup_st7920_128x64_f(u8g2, rotation, u8x8_byte_8bit_6800mode, uC specific) | full framebuffer, size = 1024 bytes |
Description | |
---|---|
Buffer | Description |
1 |
Only one page of the display memory is stored in the microcontroller RAM. Use a firstPage()/nextPage() loop for drawing on the display. |
2 |
Same as 1 , but maintains two pages in the microcontroller RAM. This will up to two times faster than 1 . |
F |
Keep a copy of the full display frame buffer in the microcontroller RAM. Use clearBuffer() to clear the RAM and sendBuffer() to transfer the microcontroller RAM to the display. |
The full buffer F
option can be used only, if there is sufficient RAM available in the microcontroller. Use option 1
or 2
on a microcontroller with small amount of RAM. The u8x8 APIcan be used if there is not even RAM for one page.
U8g2 绘制模式说明
U8g2 supports three different drawing modes:
- Full screen buffer mode
- Page mode (This is the U8glib picture loop)
- U8x8, character only mode
Full screen buffer mode
Pros and Cons
- Fast
- All graphics procedures can be used
- Requires a lot of RAM
Setup
Use the U8g2 constructor from here. The constructor must include the "F" character. For example:
u8g2_Setup_st7920_128x64_f(u8g2, rotation, u8x8_byte_8bit_6800mode, uC specific)
Usage
- Clear the buffer with u8g2.clearBuffer().
- Draw something with the usual draw commands.
- Send the buffer to the display with u8g2.sendBuffer().
Example
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(0,20,"Hello World!");
u8g2.sendBuffer();
}
Page buffer mode (Picture Loop)
Pros and Cons
- All graphics procedures can be used
- Only a little bit of RAM is required
- Slow
Setup
Use the U8g2 constructor from here. The constructor must include the "1" or "2" character. For example:
u8g2_Setup_st7920_128x64_1(u8g2, rotation, u8x8_byte_8bit_6800mode, uC specific)
Usage
- Call u8g2.firstPage().
- Start a do-while loop
- Inside the loop-body: Draw something with the usual draw commands.
- Loop as long as u8g2.nextPage() returns true.
Note: Always create the same picture inside the loop body. See details here.
Example
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.drawStr(0,24,"Hello World!");
} while ( u8g2.nextPage() );
}
U8x8 character mode
Pros and Cons
- Fast
- No RAM required
- No graphics possible
- Not available for all displays
Setup
Use the U8x8 constructor from here. For example:
u8x8_Setup(u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_4wire_sw_spi, uC specific)
Usage
All draw commands directly write to the display.
Example
void setup(void) {
u8x8.begin();
}
void loop(void) {
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.drawString(0,1,"Hello World!");
}
嵌入式全新视频:www.makeru.com.cn/?t=12 嵌入式学习交流群:561213221