STM32G474-based 0.96-inch OLED (SSD1306) Display Driver (4-pin I2C interface), supporting both hardware and software I2C, HAL-library version.
This driver is quite complete: it can display English text, integers, floats, Chinese characters, images, binary and hexadecimal numbers, and provides drawing primitives (dots, lines, rectangles, circles, ellipses, triangles). Multiple fonts are supported—essentially a mini graphics library.
The code is a secondary modification of Jiangxie Technology’s original, which targeted STM32F103 and supported only software I2C. I added hardware-I2C support while keeping the option to switch back to software I2C via a macro.
Test hardware: NUCLEO-G474RE board.
For OLED driving principles and a usage tutorial, see Jiangxie Technology’s video: https://url.zeruns.com/L7j6y
- STM32 hardware-I2C reading SHTC3 temp & humidity sensor: https://blog.zeruns.com/archives/692.html
- STM32F407 template with U8g2 graphics lib (StdPeriph): https://blog.zeruns.com/archives/722.html
- STM32F1 0.96-inch OLED driver (hardware/software I2C): https://blog.zeruns.com/archives/769.html
Electronics / MCU QQ group: 2169025065
Effect pictures

I2C Protocol Brief
I2C (Inter-Integrated Circuit) was developed by Philips. With few pins, simple hardware, and high expandability—no external transceivers like USART or CAN—it is now widely used for on-board IC communication.
I2C has only one data line: SDA (Serial Data Line). Data is sent bit by bit (serial) and communication is half-duplex.
Half-duplex: bidirectional but not simultaneous; direction must alternate. Only one data line is needed.
We divide I2C into physical layer (hardware) and protocol layer (software rules).
I2C Physical Layer
Typical I2C bus connection
- Multi-device bus: several devices share the same signal lines. Multiple masters and slaves are supported.
- Two wires only: SDA (data) and SCL (clock).
- Pull-up resistors connect the bus to VCC. When idle, devices output high-impedance, so the resistors pull the lines high.
MCU GPIOs must be configured as open-drain outputs; otherwise short-circuits may occur.
More STM32 I2C details: https://url.zeruns.com/JC0Ah
Jiangxie STM32 intro (video): https://www.bilibili.com/video/BV1th411z7sn?p=31
Usage Notes
Hardware I2C is default: I2C3, SCL = PA8, SDA = PC9.
Hardware I2C
STM32CubeMX: assign the desired I2C pins to SCL/SDA (example below for I2C3 SCL).
Enable the I2C peripheral, set speed mode to Fast Mode Plus, frequency to 1000 kHz, leave the rest default.
GPIO tab: the two pins will auto-configure as alternate-function open-drain. Set speed to Very High and User Labels to I2C3_SCL and I2C3_SDA (change in code if you use other names). Generate code.
In OLED.c, comment out #define OLED_USE_SW_I2C and uncomment #define OLED_USE_HW_I2C. Adjust I2C3_SCL / I2C3_SDA if you used different labels.
Software I2C
STM32CubeMX: choose two GPIOs, set User Labels to I2C3_SCL and I2C3_SDA, configure as open-drain, default high, pull-up, maximum speed. Generate code.
In OLED.c, comment out #define OLED_USE_HW_I2C and uncomment #define OLED_USE_SW_I2C. Adjust pin labels if necessary.
Required Parts
- STM32 starter kit: https://u.jd.com/fQS0YAe
- STM32G474 board: https://s.click.taobao.com/8OwQ8vt
- OLED module: https://s.click.taobao.com/EF0Evwt
- Jumper wires: https://s.click.taobao.com/VMkDvwt
- Breadboard: https://s.click.taobao.com/bhg8Txt
- DAPLink (ST-Link alternative with VCP): https://s.click.taobao.com/QVQ8TxtJiangxie Technology STM32 Starter Kit: https://s.click.taobao.com/NTn9Txt
Program
Full project download links:
Baidu Netdisk: Link: https://url.zeruns.com/0CQJG Extraction code: 0169
123 Netdisk (no speed limit): https://www.123pan.com/s/2Y9Djv-O0cvH.html Extraction code: vvDt
Gitee open-source repo: https://gitee.com/zeruns/STM32-HAL-OLED-I2C
GitHub open-source repo: https://github.com/zeruns/STM32G4-OLED-SSD1306-I2C-HAL
Please give it a Star
The project was created with Keil5 and developed with VSCode + EIDE; both IDEs can open it.
All project files are UTF-8 encoded; if garbled text appears, set your editor’s encoding to UTF-8.
Main file OLED.c:
/***************************************************************************************
* This program was created by Jiangxie Technology and is freely open-sourced
* You may view, use, and modify it at will and apply it to your own projects
* Copyright of the program belongs to Jiangxie Technology; no individual or organization may claim ownership
*
* Program name: 0.96-inch OLED display driver (4-pin I2C interface)
* Creation date: 2023.10.24
* Current version: V1.1
* Current release date: 2023.12.8
*
* Jiangxie Technology official site: jiangxiekeji.com
* Jiangxie Technology official Taobao: jiangxiekeji.taobao.com
* Program description & updates: jiangxiekeji.com/tutorial/oled.html
*
* If you find bugs or typos, email us: feedback@jiangxiekeji.com
* Before mailing, check the updates page for the latest version; if already fixed, no need to mail
***************************************************************************************
*/
/*
* Modified by zeruns
* Changes: Ported from StdPeriph to HAL library, added hardware I2C support, toggle via macro
* Date: 2024.3.16
* Blog: https://blog.zeruns.com
* Bilibili: https://space.bilibili.com/8320520
*/
#include "main.h"
#include "OLED.h"
#include <str
```#include <stdio.h>
#include <math.h>th.h>
#include <stdio.h>
#include <stdarg.h
// If Chinese characters are used, the compiler must be given the option --no-multibyte-chars (not needed for AC6)
/*
Choose OLED driver mode; hardware I2C is used by default.
To use software I2C, comment out the hardware-I2C macro and uncomment the software-I2C line.
Never leave both uncommented at the same time!
In STM32CubeMX, set the “user label” of the SCL and SDA pins to I2C3_SCL and I2C3_SDA respectively.
*/
#define OLED_USE_HW_I2C // hardware I2C
//#define OLED_USE_SW_I2C // software I2C
/*Pin definitions—change the I2C pins here if necessary*/
#define OLED_SCL I2C3_SCL_Pin // SCL
#define OLED_SDA I2C3_SDA_Pin // SDA
#define OLED_SCL_GPIO_Port I2C3_SCL_GPIO_Port
#define OLED_SDA_GPIO_Port I2C3_SDA_GPIO_Port
/*STM32G474 on-chip hardware I2C3: PA8 -- SCL; PC9 -- SDA*/
#ifdef OLED_USE_HW_I2C
/*I2C handle—specifies which I2C peripheral the OLED uses*/
#define OLED_I2C hi2c3
extern I2C_HandleTypeDef hi2c3; // HAL library: designate the hardware I2C interface
#endif
/*OLED slave address*/
#define OLED_ADDRESS 0x3C << 1 // 0x3C is the 7-bit address; left-shifted once the R/W bit becomes 0x78
/*I2C timeout*/
#define OLED_I2C_TIMEOUT 10
/*Delay for software I2C; the value below is for 170 MHz core clock.
If your frequency differs, adjust it; for ≤100 MHz simply set to 0.*/
#define Delay_time 3
/**
* Data layout:
* 8 vertical pixels, MSB at bottom, left-to-right first, then top-to-bottom.
* Each bit represents one pixel.
*
* B0 B0 B0 B0
* B1 B1 B1 B1
* B2 B2 B2 B2
* B3 B3 -------------> B3 B3 --
* B4 B4 B4 B4 |
* B5 B5 B5 B5 |
* B6 B6 B6 B6 |
* B7 B7 B7 B7 |
* |
* -----------------------------------
* |
* | B0 B0 B0 B0
* | B1 B1 B1 B1
* | B2 B2 B2 B2
* --> B3 B3 -------------> B3 B3
* B4 B4 B4 B4
* B5 B5 B5 B5
* B6 B6 B6 B6
* B7 B7 B7 B7
*
* Coordinate system:
* Top-left corner is (0, 0).
* X axis points right, range 0–127.
* Y axis points down, range 0–63.
*
* 0 X-axis 127
* .------------------------------->
* 0 |
* |
* |
* |
* Y-axis
* |
* |
* |
* 63 |
* v
*
*/
/*Global variables*********************/
/**
* OLED frame-buffer array.
* All drawing functions only read/write this buffer.
* Call OLED_Update() or OLED_UpdateArea() afterwards
* to send the buffer to the OLED hardware.
*/
uint8_t OLED_DisplayBuf[8][128];
/*********************Global variables*/
#ifdef OLED_USE_SW_I2C
/**
* @brief Write a level to OLED_SCL
* According to BitValue, set SCL high or low.
* @param BitValue 0 or 1
*/
void OLED_W_SCL(uint8_t BitValue)
{
/*Set SCL high or low based on BitValue*/
HAL_GPIO_WritePin(OLED_SCL_GPIO_Port, OLED_SCL, (GPIO_PinState)BitValue);
/*If the MCU is too fast, add a small delay to stay within I²C max speed*/
for (volatile uint16_t i = 0; i < Delay_time; i++){
//for (uint16_t j = 0; j < 10; j++);
}
}
/**
* @brief Write a level to OLED_SDA
* @param BitValue 0/1
* @return None
* @note Called by upper layer when SDA needs to be driven.
* BitValue 0 → SDA low, 1 → SDA high
*/
void OLED_W_SDA(uint8_t BitValue)
{
/*Set SDA high or low based on BitValue*/
HAL_GPIO_WritePin(OLED_SDA_GPIO_Port, OLED_SDA, (GPIO_PinState)BitValue);
/*Small delay to respect I²C timing*/
for (volatile uint16_t i = 0; i < Delay_time; i++){
//for (uint16_t j = 0; j < 10; j++);
}
}
#endif
/**
* @brief OLED pin initialization
* @param None
* @retval None
* @note Must be called before using the OLED.
* SCL and SDA must be configured as open-drain and released.
*/
void OLED_GPIO_Init(void)
{
uint32_t i, j;
/*Wait a bit for OLED power to stabilize*/
for (i = 0; i < 1000; i++) {
for (j = 0; j < 1000; j++)
;
}
#ifdef OLED_USE_SW_I2C
__HAL_RCC_GPIOC_CLK_ENABLE(); // Enable GPIOC clock
__HAL_RCC_GPIOA_CLK_ENABLE(); // Enable GPIOA clock
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // Open-drain output
GPIO_InitStruct.Pull = GPIO_PULLUP; // Internal pull-up
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // High speed
GPIO_InitStruct.Pin = I2C3_SDA_Pin;
HAL_GPIO_Init(I2C3_SDA_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = I2C3_SCL_Pin;
HAL_GPIO_Init(I2C3_SCL_GPIO_Port, &GPIO_InitStruct);
/*Release SCL and SDA*/
OLED_W_SCL(1);
OLED_W_SDA(1);
#endif
}
// https://blog.zeruns.com
/*Communication protocol*********************/
/**
* @brief I²C start condition
* @param None
* @return None
*/
void OLED_I2C_Start(void)
{
#ifdef OLED_USE_SW_I2C
OLED_W_SDA(1); // Release SDA to ensure high
OLED_W_SCL(1); // Release SCL to ensure high
OLED_W_SDA(0); // Pull SDA low while SCL is high → START
OLED_W_SCL(0); // Pull SCL low to claim bus and prepare next edge
#endif
}
/**
* @brief I²C stop condition
* @param None
* @return None
*/
void OLED_I2C_Stop(void)
{
#ifdef OLED_USE_SW_I2C
OLED_W_SDA(0); // Ensure SDA is low
OLED_W_SCL(1); // Release SCL (high)
OLED_W_SDA(1); // Release SDA while SCL is high → STOP
#endif
}
/**
* @brief Send one byte via I²C
* @param Byte data to send, 0x00–0xFF
* @return None
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
#ifdef OLED_USE_SW_I2C
uint8_t i;
/*Shift out 8 bits, MSB first*/
for (i = 0; i < 8; i++)
{
OLED_W_SDA(!!(Byte & (0x80 >> i)));
OLED_W_SCL(1); // Clock high, slave samples
OLED_W_SCL(0); // Clock low, prepare next bit
}
OLED_W_SCL(1); // Extra clock pulse, ignore ACK
OLED_W_SCL(0);
#endif
}
/**
* @brief Write an OLED command
* @param Command command byte, 0x00–0xFF
* @return None
*/
void OLED_WriteCommand(uint8_t Command)
{
#ifdef OLED_USE_SW_I2C
OLED_I2C_Start(); // START
OLED_I2C_SendByte(0x78); // OLED I²C slave address
OLED_I2C_SendByte(0x00); // Control byte: 0x00 = command next
OLED_I2C_SendByte(Command); // Send command
OLED_I2C_Stop(); // STOP
#elif defined(OLED_USE_HW_I2C)
uint8_t TxData[2] = {0x00, Command};
HAL_I2C_Master_Transmit(&OLED_I2C, OLED_ADDRESS, (uint8_t*)TxData, 2, OLED_I2C_TIMEOUT);
#endif
}
/**
* @brief Write OLED data
* @param Data pointer to data buffer
* @param Count number of bytes to send
* @return None
*/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
uint8_t i;
#ifdef OLED_USE_SW_I2C
OLED_I2C_Start(); // START
OLED_I2C_SendByte(0x78); // OLED I²C slave address
OLED_I2C_SendByte(0x40); // Control byte: 0x40 = data stream next
for (i = 0; i < Count; i++) {
OLED_I2C_SendByte(Data[i]); // Send data bytes
}
OLED_I2C_Stop(); // STOP
#elif defined(OLED_USE_HW_I2C)
uint8_t TxData[Count + 1];
TxData[0] = 0x40; // Co = 0, D/C# = 1
for (i = 0; i < Count; i++) {
TxData[i + 1] = Data[i];
}
HAL_I2C_Master_Transmit(&OLED_I2C, OLED_ADDRESS, (uint8_t*)TxData, Count + 1, OLED_I2C_TIMEOUT);
#endif
}
/*********************Communication protocol*/
/*Hardware configuration*********************/
/**
* @brief OLED initialization
* @param None
* @return None
* @note Must be called before using the OLED.
*/
void OLED_Init(void)
{
OLED_GPIO_Init(); // Low-level pin init first
/*Send initialization sequence*/
OLED_WriteCommand(0xAE); // Display OFF
OLED_WriteCommand(0xD5); // Set display clock divide ratio / oscillator frequency
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); // Set multiplex ratio
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); // Set display offset
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); // Set display start line
OLED_WriteCommand(0xA1); // Segment remap: normal
OLED_WriteCommand(0xC8); // COM output scan direction: normal
OLED_WriteCommand(0xDA); // Set COM pins hardware configuration
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); // Set contrast control
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); // Set pre-charge period
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); // Set VCOMH deselect level
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); // Entire display ON (follows RAM)
OLED_WriteCommand(0xA6); // Normal (not inverted) display
OLED_WriteCommand(0x8D); // Enable charge pump regulator
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); // Display ON
OLED_Clear(); // Clear frame buffer
OLED_Update(); // Push to screen to avoid random pixels
}
/**
* @brief Set OLED cursor position
* @param Page page address, 0–7
* @param X column address, 0–127
* @return None
* @note OLED Y address is grouped in 8-pixel pages.
*/
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
/*Uncomment the line below when using 1.3" OLED (SH1106) which has 132 columns
and the screen starts at column 2*/
// X += 2;
/*Set page and column addresses*/
OLED_WriteCommand(0xB0 | Page); // Page address
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); // Column high nibble
OLED_WriteCommand(0x00 | (X & 0x0F)); // Column low nibble
}/*********************Hardware Configuration*/
/*Utility Functions*********************/
/*Utility functions are for internal use only*/
/**
* @brief Power function
* @param X Base
* @param Y Exponent
* @return Equals X to the power of Y
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; // Default result is 1
while (Y--) // Multiply Y times
{
Result *= X; // Multiply X to result each time
}
return Result;
}
/**
* @brief Determine if a specified point is inside a specified polygon
* @param nvert Number of polygon vertices
* @param vertx verty Arrays containing x and y coordinates of polygon vertices
* @param testx testy X and y coordinates of the test point
* @return Whether the specified point is inside the specified polygon, 1: inside, 0: outside
*/
uint8_t OLED_pnpoly(uint8_t nvert, int16_t *vertx, int16_t *verty, int16_t testx, int16_t testy)
{
int16_t i, j, c = 0;
/*This algorithm was proposed by W. Randolph Franklin*/
/*Reference link: https://wrfranklin.org/Research/Short_Notes/pnpoly.html*/
for (i = 0, j = nvert - 1; i < nvert; j = i++) {
if (((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
c = !c;
}
}
return c;
}
/**
* @brief Determine if a specified point is within a specified angle
* @param X Y Coordinates of the specified point
* @param StartAngle EndAngle Start and end angles, range: -180-180
* 0 degrees is horizontal right, 180 or -180 is horizontal left, positive below, negative above, clockwise rotation
* @return Whether the specified point is within the specified angle, 1: inside, 0: outside
*/
uint8_t OLED_IsInAngle(int16_t X, int16_t Y, int16_t StartAngle, int16_t EndAngle)
{
int16_t PointAngle;
PointAngle = atan2(Y, X) / 3.14 * 180; // Calculate radians of specified point and convert to degrees
if (StartAngle < EndAngle) // Case where start angle is less than end angle
{
/*If specified angle is between start and end angles, determine point is within angle*/
if (PointAngle >= StartAngle && PointAngle <= EndAngle) {
return 1;
}
} else // Case where start angle is greater than end angle
{
/*If specified angle is greater than start angle or less than end angle, determine point is within angle*/
if (PointAngle >= StartAngle || PointAngle <= EndAngle) {
return 1;
}
}
return 0; // If none of above conditions are met, determine point is not within angle
}
/*********************Utility Functions*/
/*Function Functions*********************/
/**
* @brief Update OLED display buffer array to OLED screen
* @param None
* @return None
* @note All display functions only read/write to OLED display buffer array
* Then call OLED_Update function or OLED_UpdateArea function
* to send buffer array data to OLED hardware for display
* So after calling display functions, must call update function to actually display on screen
*/
void OLED_Update(void)
{
uint8_t j;
/*Iterate through each page*/
for (j = 0; j < 8; j++) {
/*Set cursor position to first column of each page*/
OLED_SetCursor(j, 0);
/*Continuously write 128 data points, write buffer array data to OLED hardware*/
OLED_WriteData(OLED_DisplayBuf[j], 128);
}
}
/**
* @brief Partially update OLED display buffer array to OLED screen
* @param X X-coordinate of top-left corner of specified area, range: 0-127
* @param Y Y-coordinate of top-left corner of specified area, range: 0-63
* @param Width Width of specified area, range: 0-128
* @param Height Height of specified area, range: 0-64
* @return None
* @note This function updates at least the area specified by parameters
* If update area only partially includes pages on Y-axis, remaining parts of same page will be updated together
* @note All display functions only read/write to OLED display buffer array
* Then call OLED_Update function or OLED_UpdateArea function
* to send buffer array data to OLED hardware for display
* So after calling display functions, must call update function to actually display on screen
*/
void OLED_UpdateArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
uint8_t j;
/*Parameter check to ensure specified area doesn't exceed screen bounds*/
if (X > 127) { return; }
if (Y > 63) { return; }
if (X + Width > 128) { Width = 128 - X; }
if (Y + Height > 64) { Height = 64 - Y; }
/*Iterate through relevant pages involved in specified area*/
/*(Y + Height - 1) / 8 + 1 aims to (Y + Height) / 8 and round up*/
for (j = Y / 8; j < (Y + Height - 1) / 8 + 1; j++) {
/*Set cursor position to specified column of relevant page*/
OLED_SetCursor(j, X);
/*Continuously write Width data points, write buffer array data to OLED hardware*/
OLED_WriteData(&OLED_DisplayBuf[j][X], Width);
}
}
/**
* @brief Clear entire OLED display buffer array
* @param None
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++) // Iterate through 8 pages
{
for (i = 0; i < 128; i++) // Iterate through 128 columns
{
OLED_DisplayBuf[j][i] = 0x00; // Clear all buffer array data to zero
}
}
}
/**
* @brief Partially clear OLED display buffer array
* @param X X-coordinate of top-left corner of specified area, range: 0-127
* @param Y Y-coordinate of top-left corner of specified area, range: 0-63
* @param Width Width of specified area, range: 0-128
* @param Height Height of specified area, range: 0-64
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ClearArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
uint8_t i, j;
/*Parameter check to ensure specified area doesn't exceed screen bounds*/
if (X > 127) { return; }
if (Y > 63) { return; }
if (X + Width > 128) { Width = 128 - X; }
if (Y + Height > 64) { Height = 64 - Y; }
for (j = Y; j < Y + Height; j++) // Iterate through specified pages
{
for (i = X; i < X + Width; i++) // Iterate through specified columns
{
OLED_DisplayBuf[j / 8][i] &= ~(0x01 << (j % 8)); // Clear specified buffer array data to zero
}
}
}
/**
* @brief Invert entire OLED display buffer array
* @param None
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_Reverse(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++) // Iterate through 8 pages
{
for (i = 0; i < 128; i++) // Iterate through 128 columns
{
OLED_DisplayBuf[j][i] ^= 0xFF; // Invert all buffer array data
}
}
}
/**
* @brief Partially invert OLED display buffer array
* @param X X-coordinate of top-left corner of specified area, range: 0-127
* @param Y Y-coordinate of top-left corner of specified area, range: 0-63
* @param Width Width of specified area, range: 0-128
* @param Height Height of specified area, range: 0-64
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ReverseArea(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height)
{
uint8_t i, j;
/*Parameter check to ensure specified area doesn't exceed screen bounds*/
if (X > 127) { return; }
if (Y > 63) { return; }
if (X + Width > 128) { Width = 128 - X; }
if (Y + Height > 64) { Height = 64 - Y; }
for (j = Y; j < Y + Height; j++) // Iterate through specified pages
{
for (i = X; i < X + Width; i++) // Iterate through specified columns
{
OLED_DisplayBuf[j / 8][i] ^= 0x01 << (j % 8); // Invert specified buffer array data
}
}
}
/**
* @brief OLED display a character
* @param X X-coordinate of top-left corner of character, range: 0-127
* @param Y Y-coordinate of top-left corner of character, range: 0-63
* @param Char Specified character to display, range: ASCII visible characters
* @param FontSize Specified font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ShowChar(uint8_t X, uint8_t Y, char Char, uint8_t FontSize)
{
if (FontSize == OLED_8X16) // Font is 8 pixels wide, 16 pixels high
{
/*Display specified data from ASCII font library OLED_F8x16 in 8*16 image format*/
OLED_ShowImage(X, Y, 8, 16, OLED_F8x16[Char - ' ']);
} else if (FontSize == OLED_6X8) // Font is 6 pixels wide, 8 pixels high
{
/*Display specified data from ASCII font library OLED_F6x8 in 6*8 image format*/
OLED_ShowImage(X, Y, 6, 8, OLED_F6x8[Char - ' ']);
}
}
/**
* @brief OLED display string
* @param X X-coordinate of top-left corner of string, range: 0-127
* @param Y Y-coordinate of top-left corner of string, range: 0-63
* @param String Specified string to display, range: String composed of ASCII visible characters
* @param FontSize Specified font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ShowString(uint8_t X, uint8_t Y, char *String, uint8_t FontSize)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++) // Iterate through each character of string
{
/*Call OLED_ShowChar function to display each character in sequence*/
OLED_ShowChar(X + i * FontSize, Y, String[i], FontSize);
}
}
/**
* @brief OLED display number (decimal, positive integer)
* @param X X-coordinate of top-left corner of number, range: 0-127
* @param Y Y-coordinate of top-left corner of number, range: 0-63
* @param Number Specified number to display, range: 0-4294967295
* @param Length Specified length of number, range: 0-10
* @param FontSize Specified font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ShowNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
uint8_t i;
for (i = 0; i < Length; i++) // Iterate through each digit of number
{
/*Call OLED_ShowChar function to display each digit in sequence*/
/*Number / OLED_Pow(10, Length - i - 1) % 10 can extract each decimal digit*/
/*+ '0' converts digit to character format*/
OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
}
}
/**
* @brief OLED display signed number (decimal, integer)
* @param X X-coordinate of top-left corner of number, range: 0-127
* @param Y Y-coordinate of top-left corner of number, range: 0-63
* @param Number Specified number to display, range: -2147483648-2147483647
* @param Length Specified length of number, range: 0-10
* @param FontSize Specified font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, must call update function to actually display on screen
*/
void OLED_ShowSignedNum(uint8_t X, uint8_t Y, int32_t Number, uint8_t Length, uint8_t FontSize)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0) // Number is greater than or equal to 0
{
OLED_ShowChar(X, Y, '+', FontSize); // Display + sign
Number1 = Number; // Number1 directly equals Number
} else // Number is less than 0
{
OLED_ShowChar(X, Y, '-', FontSize); // Display - sign
Number1 = -Number; // Number1 equals negative of Number
}
for (i = 0; i < Length; i++) // Iterate through each digit of the number
{
/* Call OLED_ShowChar to display each digit in turn */
/* Number1 / OLED_Pow(10, Length - i - 1) % 10 extracts each decimal digit */
/* + '0' converts the digit to character format */
OLED_ShowChar(X + (i + 1) * FontSize, Y, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
}
}
/**
* @brief OLED display hexadecimal number (hexadecimal, positive integer)
* @param X Specifies the x-coordinate of the top-left corner of the number, range: 0~127
* @param Y Specifies the y-coordinate of the top-left corner of the number, range: 0~63
* @param Number Specifies the number to display, range: 0x00000000~0xFFFFFFFF
* @param Length Specifies the length of the number, range: 0~8
* @param FontSize Specifies the font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_ShowHexNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++) // Iterate through each digit of the number
{
/* Extract each hexadecimal digit */
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10) // Single digit less than 10
{
/* Call OLED_ShowChar to display this digit */
/* + '0' converts the digit to character format */
OLED_ShowChar(X + i * FontSize, Y, SingleNumber + '0', FontSize);
}
else // Single digit greater than or equal to 10
{
/* Call OLED_ShowChar to display this digit */
/* + 'A' converts the digit to a hexadecimal character starting from A */
OLED_ShowChar(X + i * FontSize, Y, SingleNumber - 10 + 'A', FontSize);
}
}
}
/**
* @brief OLED display binary number (binary, positive integer)
* @param X Specifies the x-coordinate of the top-left corner of the number, range: 0~127
* @param Y Specifies the y-coordinate of the top-left corner of the number, range: 0~63
* @param Number Specifies the number to display, range: 0x00000000~0xFFFFFFFF
* @param Length Specifies the length of the number, range: 0~16
* @param FontSize Specifies the font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_ShowBinNum(uint8_t X, uint8_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
uint8_t i;
for (i = 0; i < Length; i++) // Iterate through each digit of the number
{
/* Call OLED_ShowChar to display each digit in turn */
/* Number / OLED_Pow(2, Length - i - 1) % 2 extracts each binary digit */
/* + '0' converts the digit to character format */
OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(2, Length - i - 1) % 2 + '0', FontSize);
}
}
/**
* @brief OLED display floating-point number (decimal, fractional)
* @param X Specifies the x-coordinate of the top-left corner of the number, range: 0-127
* @param Y Specifies the y-coordinate of the top-left corner of the number, range: 0-63
* @param Number Specifies the number to display, range: -4294967295.0-4294967295.0
* @param IntLength Specifies the length of the integer part, range: 0-10
* @param FraLength Specifies the length of the fractional part, range: 0-9, rounded for display
* @param FontSize Specifies the font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_ShowFloatNum(uint8_t X, uint8_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize)
{
uint32_t PowNum, IntNum, FraNum;
if (Number >= 0) // Number is greater than or equal to 0
{
OLED_ShowChar(X, Y, '+', FontSize); // Display + sign
}
else // Number is less than 0
{
OLED_ShowChar(X, Y, '-', FontSize); // Display - sign
Number = -Number; // Negate Number
}
/* Extract integer and fractional parts */
IntNum = Number; // Directly assign to integer variable to extract integer
Number -= IntNum; // Subtract the integer part from Number to prevent errors when multiplying the fraction to integer later
PowNum = OLED_Pow(10, FraLength); // Determine multiplier based on specified fractional digits
FraNum = round(Number * PowNum); // Multiply fraction to integer and round to avoid display errors
IntNum += FraNum / PowNum; // If rounding caused carry-over, add it to the integer
/* Display integer part */
OLED_ShowNum(X + FontSize, Y, IntNum, IntLength, FontSize);
/* Display decimal point */
OLED_ShowChar(X + (IntLength + 1) * FontSize, Y, '.', FontSize);
/* Display fractional part */
OLED_ShowNum(X + (IntLength + 2) * FontSize, Y, FraNum, FraLength, FontSize);
}
/**
* @brief OLED display Chinese character string
* @param X Specifies the x-coordinate of the top-left corner of the string, range: 0-127
* @param Y Specifies the y-coordinate of the top-left corner of the string, range: 0-63
* @param Chinese Specifies the Chinese character string to display, range: must be all Chinese or full-width characters, do not include any half-width characters
* Displayed characters must be defined in the OLED_CF16x16 array in OLED_Data.c
* If the specified character is not found, a default graphic (a square with a question mark inside) will be displayed
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_ShowChinese(uint8_t X, uint8_t Y, char *Chinese)
{
uint8_t pChinese = 0;
uint8_t pIndex;
uint8_t i;
char SingleChinese[OLED_CHN_CHAR_WIDTH + 1] = {0}; // UTF8 encoding is 3 bytes, +1 for \0 terminator
for (i = 0; Chinese[i] != '\0'; i++) // Iterate through the Chinese string
{
SingleChinese[pChinese] = Chinese[i]; // Extract Chinese string data to single character array
pChinese++; // Increment counter
/* When extraction count reaches OLED_CHN_CHAR_WIDTH, a complete Chinese character is extracted */
if (pChinese >= OLED_CHN_CHAR_WIDTH) {
SingleChinese[pChinese + 1] = '\0'; // Append null terminator after the character
pChinese = 0; // Reset counter
/* Traverse the entire Chinese character font library to find a match */
/* If the last character (defined as empty string) is reached, it means the character is not defined in the font library, stop searching */
for (pIndex = 0; strcmp(OLED_CF16x16[pIndex].Index, "") != 0; pIndex++) {
/* Found matching character */
if (strcmp(OLED_CF16x16[pIndex].Index, SingleChinese) == 0) {
break; // Exit loop, pIndex now holds the index of the specified character
}
}
/* Display the specified data from the OLED_CF16x16 font library in 16*16 image format */
OLED_ShowImage(X + ((i + 1) / OLED_CHN_CHAR_WIDTH - 1) * 16, Y, 16, 16, OLED_CF16x16[pIndex].Data);
}
}
}
/**
* @brief OLED display image
* @param X Specifies the x-coordinate of the top-left corner of the image, range: 0-127
* @param Y Specifies the y-coordinate of the top-left corner of the image, range: 0-63
* @param Width Specifies the width of the image, range: 0-128
* @param Height Specifies the height of the image, range: 0-64
* @param Image Specifies the image to display
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_ShowImage(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{
uint8_t i, j;
/* Parameter check to ensure the specified image does not exceed screen bounds */
if (X > 127) { return; }
if (Y > 63) { return; }
/* Clear the area where the image will be displayed */
OLED_ClearArea(X, Y, Width, Height);
/* Iterate through relevant pages involved in the specified image */
/* (Height - 1) / 8 + 1 is equivalent to Height / 8 rounded up */
for (j = 0; j < (Height - 1) / 8 + 1; j++) {
/* Iterate through relevant columns involved in the specified image */
for (i = 0; i < Width; i++) {
/* Skip display if out of bounds */
if (X + i > 127) { break; }
if (Y / 8 + j > 7) { return; }
/* Display image content on current page */
OLED_DisplayBuf[Y / 8 + j][X + i] |= Image[j * Width + i] << (Y % 8);
/* Skip display if out of bounds */
/* Use continue so that remaining content on previous page continues to display if next page is out of bounds */
if (Y / 8 + j + 1 > 7) { continue; }
/* Display image content on next page */
OLED_DisplayBuf[Y / 8 + j + 1][X + i] |= Image[j * Width + i] >> (8 - Y % 8);
}
}
}
/**
* @brief OLED print formatted string using printf
* @param X Specifies the x-coordinate of the top-left corner of the string, range: 0-127
* @param Y Specifies the y-coordinate of the top-left corner of the string, range: 0-63
* @param FontSize Specifies the font size
* Range: OLED_8X16 8 pixels wide, 16 pixels high
* OLED_6X8 6 pixels wide, 8 pixels high
* @param format Specifies the formatted string to display, range: string composed of ASCII visible characters
* @param ... Format string argument list
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_Printf(uint8_t X, uint8_t Y, uint8_t FontSize, char *format, ...)
{
char String[30]; // Define character array
va_list arg; // Define variable argument list variable arg
va_start(arg, format); // Receive argument list starting from format into arg
vsprintf(String, format, arg); // Use vsprintf to print formatted string and argument list into character array
va_end(arg); // End variable arg
OLED_ShowString(X, Y, String, FontSize); // OLED display character array (string)
}
/**
* @brief OLED draw a point at specified position
* @param X Specifies the x-coordinate of the point, range: 0-127
* @param Y Specifies the y-coordinate of the point, range: 0-63
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_DrawPoint(uint8_t X, uint8_t Y)
{
/* Parameter check to ensure the specified position does not exceed screen bounds */
if (X > 127) { return; }
if (Y > 63) { return; }
/* Set one bit in the display buffer at the specified position to 1 */
OLED_DisplayBuf[Y / 8][X] |= 0x01 << (Y % 8);
}
/**
* @brief OLED get the value of a point at specified position
* @param X Specifies the x-coordinate of the point, range: 0-127
* @param Y Specifies the y-coordinate of the point, range: 0-63
* @return Whether the point at the specified position is lit, 1: lit, 0: off
*/
uint8_t OLED_GetPoint(uint8_t X, uint8_t Y)
{
/* Parameter check to ensure the specified position does not exceed screen bounds */
if (X > 127) { return 0; }
if (Y > 63) { return 0; }
/* Check data at specified position */
if (OLED_DisplayBuf[Y / 8][X] & 0x01 << (Y % 8)) {
return 1; // If 1, return 1
}
return 0; // Otherwise, return 0
}
/**
* @brief OLED draw line
* @param X0 Specifies the x-coordinate of one endpoint, range: 0-127
* @param Y0 Specifies the y-coordinate of one endpoint, range: 0-63
* @param X1 Specifies the x-coordinate of the other endpoint, range: 0-127
* @param Y1 Specifies the y-coordinate of the other endpoint, range: 0-63
* @return None
* @note After calling this function, you must call the update function to actually display it on the screen
*/
void OLED_DrawLine(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1)
{
int16_t x, y, dx, dy, d, incrE, incrNE, temp;
int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
uint8_t yflag = 0, xyflag = 0;if (y0 == y1) // Handle horizontal line separately
{
/*If point 0's X coordinate is greater than point 1's X coordinate, swap the two points' X coordinates*/
if (x0 > x1) {
temp = x0;
x0 = x1;
x1 = temp;
}
/*Traverse X coordinates*/
for (x = x0; x <= x1; x++) {
OLED_DrawPoint(x, y0); // Draw points in sequence
}
} else if (x0 == x1) // Handle vertical line separately
{
/*If point 0's Y coordinate is greater than point 1's Y coordinate, swap the two points' Y coordinates*/
if (y0 > y1) {
temp = y0;
y0 = y1;
y1 = temp;
}
/*Traverse Y coordinates*/
for (y = y0; y <= y1; y++) {
OLED_DrawPoint(x0, y); // Draw points in sequence
}
} else // Slanted line
{
/*Use Bresenham's algorithm to draw lines, avoiding time-consuming floating-point operations for higher efficiency*/
/*Reference: https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
/*Tutorial: https://www.bilibili.com/video/BV1364y1d7Lo*/
if (x0 > x1) // Point 0's X coordinate is greater than point 1's X coordinate
{
/*Swap the two points' coordinates*/
/*After swapping, it doesn't affect line drawing, but the drawing direction changes from the first, second, third, and fourth quadrants to the first and fourth quadrants*/
temp = x0;
x0 = x1;
x1 = temp;
temp = y0;
y0 = y1;
y1 = temp;
}
if (y0 > y1) // Point 0's Y coordinate is greater than point 1's Y coordinate
{
/*Negate the Y coordinates*/
/*After negation, it affects line drawing, but the drawing direction changes from the first and fourth quadrants to the first quadrant*/
y0 = -y0;
y1 = -y1;
/*Set flag yflag to remember this transformation; restore coordinates during actual drawing*/
yflag = 1;
}
if (y1 - y0 > x1 - x0) // Line slope greater than 1
{
/*Swap X and Y coordinates*/
/*After swapping, it affects line drawing, but the drawing direction changes from the 0–90 degree range in the first quadrant to the 0–45 degree range in the first quadrant*/
temp = x0;
x0 = y0;
y0 = temp;
temp = x1;
x1 = y1;
y1 = temp;
/*Set flag xyflag to remember this transformation; restore coordinates during actual drawing*/
xyflag = 1;
}
/*Bresenham's algorithm to draw lines below*/
/*Algorithm requires line drawing direction to be in the 0–45 degree range of the first quadrant*/
dx = x1 - x0;
dy = y1 - y0;
incrE = 2 * dy;
incrNE = 2 * (dy - dx);
d = 2 * dy - dx;
x = x0;
y = y0;
/*Draw starting point, and check flags to restore coordinates*/
if (yflag && xyflag) {
OLED_DrawPoint(y, -x);
} else if (yflag) {
OLED_DrawPoint(x, -y);
} else if (xyflag) {
OLED_DrawPoint(y, x);
} else {
OLED_DrawPoint(x, y);
}
while (x < x1) // Traverse every point on the X axis
{
x++;
if (d < 0) // Next point is to the east of the current point
{
d += incrE;
} else // Next point is to the northeast of the current point
{
y++;
d += incrNE;
}
/*Draw each point, and check flags to restore coordinates*/
if (yflag && xyflag) {
OLED_DrawPoint(y, -x);
} else if (yflag) {
OLED_DrawPoint(x, -y);
} else if (xyflag) {
OLED_DrawPoint(y, x);
} else {
OLED_DrawPoint(x, y);
}
}
}
}
/**
* @brief OLED rectangle
* @param X Specifies the top-left corner's X coordinate, range: 0~127
* @param Y Specifies the top-left corner's Y coordinate, range: 0~63
* @param Width Specifies the rectangle's width, range: 0~128
* @param Height Specifies the rectangle's height, range: 0~64
* @param IsFilled Specifies whether the rectangle is filled
* Range: OLED_UNFILLED not filled
* OLED_FILLED filled
* @return None
* @note After calling this function, to actually display on the screen, you must call the update function
*/
void OLED_DrawRectangle(uint8_t X, uint8_t Y, uint8_t Width, uint8_t Height, uint8_t IsFilled)
{
uint8_t i, j;
if (!IsFilled) // Rectangle not filled
{
/*Traverse top and bottom X coordinates, draw the top and bottom lines of the rectangle*/
for (i = X; i < X + Width; i++) {
OLED_DrawPoint(i, Y);
OLED_DrawPoint(i, Y + Height - 1);
}
/*Traverse left and right Y coordinates, draw the left and right lines of the rectangle*/
for (i = Y; i < Y + Height; i++) {
OLED_DrawPoint(X, i);
OLED_DrawPoint(X + Width - 1, i);
}
} else // Rectangle filled
{
/*Traverse X coordinates*/
for (i = X; i < X + Width; i++) {
/*Traverse Y coordinates*/
for (j = Y; j < Y + Height; j++) {
/*Draw points in the specified area to fill the rectangle*/
OLED_DrawPoint(i, j);
}
}
}
}
/**
* @brief OLED triangle
* @param X0 Specifies the first vertex's X coordinate, range: 0-127
* @param Y0 Specifies the first vertex's Y coordinate, range: 0-63
* @param X1 Specifies the second vertex's X coordinate, range: 0-127
* @param Y1 Specifies the second vertex's Y coordinate, range: 0-63
* @param X2 Specifies the third vertex's X coordinate, range: 0-127
* @param Y2 Specifies the third vertex's Y coordinate, range: 0-63
* @param IsFilled Specifies whether the triangle is filled
* Range: OLED_UNFILLED not filled
* OLED_FILLED filled
* @return None
* @note After calling this function, to actually display on the screen, you must call the update function
*/
void OLED_DrawTriangle(uint8_t X0, uint8_t Y0, uint8_t X1, uint8_t Y1, uint8_t X2, uint8_t Y2, uint8_t IsFilled)
{
uint8_t minx = X0, miny = Y0, maxx = X0, maxy = Y0;
uint8_t i, j;
int16_t vx[] = {X0, X1, X2};
int16_t vy[] = {Y0, Y1, Y2};
if (!IsFilled) // Triangle not filled
{
/*Call line drawing function to connect the three points with straight lines*/
OLED_DrawLine(X0, Y0, X1, Y1);
OLED_DrawLine(X0, Y0, X2, Y2);
OLED_DrawLine(X1, Y1, X2, Y2);
} else // Triangle filled
{
/*Find the minimum X and Y coordinates among the three points*/
if (X1 < minx) { minx = X1; }
if (X2 < minx) { minx = X2; }
if (Y1 < miny) { miny = Y1; }
if (Y2 < miny) { miny = Y2; }
/*Find the maximum X and Y coordinates among the three points*/
if (X1 > maxx) { maxx = X1; }
if (X2 > maxx) { maxx = X2; }
if (Y1 > maxy) { maxy = Y1; }
if (Y2 > maxy) { maxy = Y2; }
/*The rectangle between the min and max coordinates is the area that may need to be filled*/
/*Traverse all points in this area*/
/*Traverse X coordinates*/
for (i = minx; i <= maxx; i++) {
/*Traverse Y coordinates*/
for (j = miny; j <= maxy; j++) {
/*Call OLED_pnpoly to determine whether the specified point is inside the specified triangle*/
/*If inside, draw the point; if not, do nothing*/
if (OLED_pnpoly(3, vx, vy, i, j)) { OLED_DrawPoint(i, j); }
}
}
}
}
/**
* @brief OLED draw circle
* @param X Specifies the circle's center X coordinate, range: 0~127
* @param Y Specifies the circle's center Y coordinate, range: 0~63
* @param Radius Specifies the circle's radius, range: 0~255
* @param IsFilled Specifies whether the circle is filled
* Range: OLED_UNFILLED not filled
* OLED_FILLED filled
* @return None
* @note After calling this function, to actually display on the screen, you must call the update function
*/
void OLED_DrawCircle(uint8_t X, uint8_t Y, uint8_t Radius, uint8_t IsFilled)
{
int16_t x, y, d, j;
/*Use Bresenham's algorithm to draw circles, avoiding time-consuming floating-point operations for higher efficiency*/
/*Reference: https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
/*Tutorial: https://www.bilibili.com/video/BV1VM4y1u7wJ*/
d = 1 - Radius;
x = 0;
y = Radius;
/*Draw the starting points of each eighth of the circle*/
OLED_DrawPoint(X + x, Y + y);
OLED_DrawPoint(X - x, Y - y);
OLED_DrawPoint(X + y, Y + x);
OLED_DrawPoint(X - y, Y - x);
if (IsFilled) // Circle filled
{
/*Traverse the starting point's Y coordinate*/
for (j = -y; j < y; j++) {
/*Draw points in the specified area to fill part of the circle*/
OLED_DrawPoint(X, Y + j);
}
}
while (x < y) // Traverse every point on the X axis
{
x++;
if (d < 0) // Next point is to the east of the current point
{
d += 2 * x + 1;
} else // Next point is to the southeast of the current point
{
y--;
d += 2 * (x - y) + 1;
}
/*Draw points for each eighth of the circle*/
OLED_DrawPoint(X + x, Y + y);
OLED_DrawPoint(X + y, Y + x);
OLED_DrawPoint(X - x, Y - y);
OLED_DrawPoint(X - y, Y - x);
OLED_DrawPoint(X + x, Y - y);
OLED_DrawPoint(X + y, Y - x);
OLED_DrawPoint(X - x, Y + y);
OLED_DrawPoint(X - y, Y + x);
if (IsFilled) // Circle filled
{
/*Traverse the middle part*/
for (j = -y; j < y; j++) {
/*Draw points in the specified area to fill part of the circle*/
OLED_DrawPoint(X + x, Y + j);
OLED_DrawPoint(X - x, Y + j);
}
/*Traverse the side parts*/
for (j = -x; j < x; j++) {
/*Draw points in the specified area to fill part of the circle*/
OLED_DrawPoint(X - y, Y + j);
OLED_DrawPoint(X + y, Y + j);
}
}
}
}
/**
* @brief OLED draw ellipse
* @param X Specifies the ellipse's center X coordinate, range: 0~127
* @param Y Specifies the ellipse's center Y coordinate, range: 0~63
* @param A Specifies the ellipse's horizontal semi-axis length, range: 0~255
* @param B Specifies the ellipse's vertical semi-axis length, range: 0~255
* @param IsFilled Specifies whether the ellipse is filled
* Range: OLED_UNFILLED not filled
* OLED_FILLED filled
* @return None
* @note After calling this function, to actually display on the screen, you must call the update function
*/
void OLED_DrawEllipse(uint8_t X, uint8_t Y, uint8_t A, uint8_t B, uint8_t IsFilled)
{
int16_t x, y, j;
int16_t a = A, b = B;
float d1, d2;
/*Use Bresenham's algorithm to draw ellipses, avoiding some time-consuming floating-point operations for higher efficiency*/
/*Reference: https://blog.csdn.net/myf_666/article/details/128167392*/
x = 0;
y = b;
d1 = b * b + a * a * (-b + 0.5);if (IsFilled) // Specify ellipse fill
{
/*Traverse starting Y coordinate*/
for (j = -y; j < y; j++) {
/*Draw points in specified area, filling partial ellipse*/
OLED_DrawPoint(X, Y + j);
OLED_DrawPoint(X, Y + j);
}
}
/*Draw starting points of ellipse arc*/
OLED_DrawPoint(X + x, Y + y);
OLED_DrawPoint(X - x, Y - y);
OLED_DrawPoint(X - x, Y + y);
OLED_DrawPoint(X + x, Y - y);
/*Draw middle part of ellipse*/
while (b * b * (x + 1) < a * a * (y - 0.5)) {
if (d1 <= 0) // Next point is to the east
{
d1 += b * b * (2 * x + 3);
} else // Next point is to the southeast
{
d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2);
y--;
}
x++;
if (IsFilled) // Specify ellipse fill
{
/*Traverse middle part*/
for (j = -y; j < y; j++) {
/*Draw points in specified area, filling partial ellipse*/
OLED_DrawPoint(X + x, Y + j);
OLED_DrawPoint(X - x, Y + j);
}
}
/*Draw middle arc of ellipse*/
OLED_DrawPoint(X + x, Y + y);
OLED_DrawPoint(X - x, Y - y);
OLED_DrawPoint(X - x, Y + y);
OLED_DrawPoint(X + x, Y - y);
}
/*Draw sides of ellipse*/
d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;
while (y > 0) {
if (d2 <= 0) // Next point is to the east
{
d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
x++;
} else // Next point is to the southeast
{
d2 += a * a * (-2 * y + 3);
}
y--;
if (IsFilled) // Specify ellipse fill
{
/*Traverse sides*/
for (j = -y; j < y; j++) {
/*Draw points in specified area, filling partial ellipse*/
OLED_DrawPoint(X + x, Y + j);
OLED_DrawPoint(X - x, Y + j);
}
}
/*Draw side arcs of ellipse*/
OLED_DrawPoint(X + x, Y + y);
OLED_DrawPoint(X - x, Y - y);
OLED_DrawPoint(X - x, Y + y);
OLED_DrawPoint(X + x, Y - y);
}
}
/**
* @brief Draw OLED arc
* @param X Specifies arc center X coordinate, range: 0~127
* @param Y Specifies arc center Y coordinate, range: 0~63
* @param Radius Specifies arc radius, range: 0~255
* @param StartAngle Specifies arc start angle, range: -180~180
* Horizontal right is 0°, horizontal left is 180° or -180°, bottom is positive, top is negative, clockwise rotation
* @param EndAngle Specifies arc end angle, range: -180~180
* Horizontal right is 0°, horizontal left is 180° or -180°, bottom is positive, top is negative, clockwise rotation
* @param IsFilled Specifies whether arc is filled, filled becomes sector
* Range: OLED_UNFILLED not filled
* OLED_FILLED filled
* @return None
* @note After calling this function, to actually display on screen, update function must be called
*/
void OLED_DrawArc(uint8_t X, uint8_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint8_t IsFilled)
{
int16_t x, y, d, j;
/*This function borrows Bresenham's circle drawing method*/
d = 1 - Radius;
x = 0;
y = Radius;
/*When drawing each point of circle, check if specified point is within specified angle, if yes draw point, if no skip*/
if (OLED_IsInAngle(x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + y); }
if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y - y); }
if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + x); }
if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y - x); }
if (IsFilled) // Specify arc fill
{
/*Traverse starting Y coordinate*/
for (j = -y; j < y; j++) {
/*When filling each point of circle, check if specified point is within specified angle, if yes draw point, if no skip*/
if (OLED_IsInAngle(0, j, StartAngle, EndAngle)) { OLED_DrawPoint(X, Y + j); }
}
}
while (x < y) // Traverse each point on X axis
{
x++;
if (d < 0) // Next point is to the east
{
d += 2 * x + 1;
} else // Next point is to the southeast
{
y--;
d += 2 * (x - y) + 1;
}
/*When drawing each point of circle, check if specified point is within specified angle, if yes draw point, if no skip*/
if (OLED_IsInAngle(x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + y); }
if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + x); }
if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y - y); }
if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y - x); }
if (OLED_IsInAngle(x, -y, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y - y); }
if (OLED_IsInAngle(y, -x, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y - x); }
if (OLED_IsInAngle(-x, y, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y + y); }
if (OLED_IsInAngle(-y, x, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y + x); }
if (IsFilled) // Specify arc fill
{
/*Traverse middle part*/
for (j = -y; j < y; j++) {
/*When filling each point of circle, check if specified point is within specified angle, if yes draw point, if no skip*/
if (OLED_IsInAngle(x, j, StartAngle, EndAngle)) { OLED_DrawPoint(X + x, Y + j); }
if (OLED_IsInAngle(-x, j, StartAngle, EndAngle)) { OLED_DrawPoint(X - x, Y + j); }
}
/*Traverse sides*/
for (j = -x; j < x; j++) {
/*When filling each point of circle, check if specified point is within specified angle, if yes draw point, if no skip*/
if (OLED_IsInAngle(-y, j, StartAngle, EndAngle)) { OLED_DrawPoint(X - y, Y + j); }
if (OLED_IsInAngle(y, j, StartAngle, EndAngle)) { OLED_DrawPoint(X + y, Y + j); }
}
}
}
}
/*********************Function Functions*/
/*****************Jiangxie Technology|All Rights Reserved****************/
/*****************jiangxiekeji.com*****************/
Recommended Reading
- Cost-effective and Cheap VPS/Cloud Server Recommendations: https://blog.zeruns.com/archives/383.html
- Built a three-phase power collector and open-sourced it, convenient for monitoring home electricity usage: https://blog.zeruns.com/archives/771.html
- Minecraft Server Setup Tutorial: https://blog.zeruns.com/tag/mc/
- Palworld Server Setup Tutorial: https://blog.zeruns.com/tag/PalWorld/
- Ruideng RD6012P CNC Adjustable Power Supply Simple Unboxing Review, 60V 12A CNC DC Power Supply: https://blog.zeruns.com/archives/740.html
- Bambu Lab P1SC 3D Printer Unboxing Experience: https://blog.zeruns.com/archives/770.html
- Comparison of Different Brands and Types of Capacitors and Inductors (D Value, Q Value, ESR, X): https://blog.zeruns.com/archives/765.html







