
#include "stm32l4xx_hal.h"
#define LED_GPIO_PORT GPIOA
#define LED_PIN GPIO_PIN_5
#define LED_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
void SystemClock_Config(void);
void LED_Init(void);
int main(void) {
HAL_Init();
SystemClock_Config();
LED_Init();
while (1) {
HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN);
HAL_Delay(250);
}
}
void LED_Init(void) {
LED_GPIO_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = LED_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
}
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; // 4 MHz
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 40;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
while (1);
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) {
while (1);
}
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
extern "C" void SysTick_Handler(void) {
HAL_IncTick();
HAL_SYSTICK_IRQHandler();
}


大家都係用STM32CubeMX,所以Gen出黎嘅Code無太大分別。計功能肯定係STM32CubeMX強,第一佢有一個生成器可以生成粒MCU所有Setting,而PlatformIO係純Code,所以好考驗老師嘅功力,但係純Code有純Code嘅好處,就係學生可以更了解第一句代碼點影響粒MCU,會更有利於教學。STM32CubeIDE會要求你將啲Code寫係comment與comment之間,睇上去好亂,而PlatformIO無呢回事,啲Code睇上去好Clean。PlatformIO最大賣點係源於VSCODE所以有Github Copilot,我之前用STM32CubeIDE同VSCode開住同一個project,github copilot一邊gen我一邊改,我就無問題。但作為學生會好麻煩。
Run STM32CudeIDE by
/Applications/STM32CubeIDE.app/Contents/MacOS/STM32CubeIDE -vmargs -Dswt.autoScale=250
Changing anything in STM32CubeIDE.ini won't work
./configure --enable-all-optimizations --enable-x86-64 --enable-cpu-level=6 --enable-debugger --enable-disasm --with-sdl
List out 10 biggest files in folder
find . -type f -exec du -h {} + | sort -rh | head -n 10
List biggest folders
du -h --max-depth=1 * | sort -hr
https://github.com/issues/created?issue=micropython%7Cmicropython%7C17828
解決辦法就係加呢兩句係main.py頭
import pyb
pyb.usb_mode('VCP+HID')
My STM32 is bricked (can't run anything in STM32CubeIDE) because my main loop has no sleep, after i add it back, my STM32 can run many times in STM32CubeIDE. To recover my weact blackpill, i use "while true; do st-flash erase; sleep 1; done" to keep running the st-flash command, I found out pressing the BOOT and RST buttons on the board NO HELP. Just run that command and keep plugin and unplug the jtag to usb port, then it works. Also remember to plug in the usb to the board too, it needs 5v to erase flash, JTag only provides 3.3v not enough.

When my weact blackpill is bricked, it goes crazy for the st-flash command, the flash size are different in different runs of st-flash


With HSE = 25 MHz, the PLL configuration must satisfy:
- SYSCLK = 100 MHz: VCO / PLL_P = 100 MHz.
- USB = 48 MHz: VCO / PLL_Q = 48 MHz.
- PLL Input: HSE / PLL_M = 1 MHz (typical for stability, 1-2 MHz allowed).
- VCO Constraints: 100-432 MHz (per STM32F411 datasheet).
Let’s try the main PLL:
- PLL_M = 25 (25 MHz / 25 = 1 MHz).
- For SYSCLK = 100 MHz, VCO = 200 MHz (PLL_N = 200, PLL_P = 2).
- For USB = 48 MHz, VCO / PLL_Q = 48 MHz → PLL_Q = 200 / 48 = 4.166 (not an integer).
Since PLL_Q must be an integer (4-15), 48 MHz USB is not possible with VCO = 200 MHz:
- PLL_Q = 4 → 200 / 4 = 50 MHz (as you noted, incorrect for USB).
- PLL_Q = 5 → 200 / 5 = 40 MHz (also incorrect).
To get exactly 48 MHz:
- VCO must be a multiple of 48 MHz (e.g., 192 MHz or 240 MHz).
- For VCO = 192 MHz: PLL_N = 192, PLL_P = 2 → SYSCLK = 96 MHz, PLL_Q = 4 → USB = 48 MHz.
- For VCO = 240 MHz: PLL_N = 240, PLL_P = 2 → SYSCLK = 120 MHz (exceeds 100 MHz limit), PLL_Q = 5 → USB = 48 MHz.
Since SYSCLK cannot exceed 100 MHz, VCO = 192 MHz (SYSCLK = 96 MHz, USB = 48 MHz) is the closest using the main PLL.
In short, you need to build libsigrok then sigrok-cli
Step 1: Add your device to Makefile.am
if HW_QUANTR
src_libdrivers_la_SOURCES += \
src/hardware/quantr/protocol.h \
src/hardware/quantr/protocol.c \
src/hardware/quantr/api.c
endif

Step 2: in configure.ac
SR_DRIVER([Quantr], [quantr], [serial_comm])
Step 3: Create /src/hardware/quantr folder
add api.c, protocol.c and protocol.h
api.c:
#include <config.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
#include "protocol.h"
static const uint32_t scanopts[] = {
SR_CONF_CONN,
SR_CONF_SERIALCOMM,
};
static const uint32_t devopts[] = {
SR_CONF_LOGIC_ANALYZER,
SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
static const uint64_t samplerates[] = {
SR_KHZ(1),
SR_KHZ(10),
SR_KHZ(100),
SR_MHZ(1),
SR_MHZ(10),
};
SR_PRIV struct sr_dev_driver quantr_driver_info = {
.name = "quantr",
.longname = "Quantr Device",
.api_version = 1,
.init = quantr_init,
.cleanup = quantr_cleanup,
.scan = quantr_scan,
.dev_list = quantr_dev_list,
.dev_clear = quantr_dev_clear,
.config_get = quantr_config_get,
.config_set = quantr_config_set,
.config_list = quantr_config_list,
.dev_open = quantr_dev_open,
.dev_close = quantr_dev_close,
.dev_acquisition_start = quantr_dev_acquisition_start,
.dev_acquisition_stop = quantr_dev_acquisition_stop,
.context = NULL,
};
SR_REGISTER_DEV_DRIVER(quantr_driver_info);
protocol.c:
#include "protocol.h"
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
#define LOG_PREFIX "quantr"
static const uint32_t scanopts[] = {
SR_CONF_CONN,
SR_CONF_SERIALCOMM,
};
static const uint32_t devopts[] = {
SR_CONF_LOGIC_ANALYZER,
SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};
static const uint64_t samplerates[] = {
SR_KHZ(1),
SR_KHZ(10),
SR_KHZ(100),
SR_MHZ(1),
SR_MHZ(10),
};
/* Initialize the driver */
SR_PRIV int quantr_init(struct sr_dev_driver *di, struct sr_context *sr_ctx)
{
return std_init(di, sr_ctx);
}
/* Cleanup resources */
SR_PRIV int quantr_cleanup(const struct sr_dev_driver *di)
{
return std_cleanup(di);
}
/* Scan for devices */
SR_PRIV GSList *quantr_scan(struct sr_dev_driver *di, GSList *options)
{
GSList *devices = NULL;
struct sr_dev_inst *sdi;
struct dev_context *devc;
struct sr_serial_dev_inst *serial;
const char *conn;
const char *serialcomm;
(void)di;
/* Parse options */
conn = NULL;
serialcomm = "9600/8n1"; /* Default serial settings */
if (sr_serial_extract_options(options, &conn, &serialcomm) != SR_OK)
return NULL;
if (!conn)
return NULL;
/* Try to open the serial port */
serial = sr_serial_dev_inst_new(conn, serialcomm);
if (!serial)
return NULL;
if (serial_open(serial, SERIAL_RDWR) != SR_OK) {
sr_serial_dev_inst_free(serial);
return NULL;
}
/* Try to communicate with the device to verify it's there */
/* You would typically send an identification command here */
/* Create device instance */
sdi = g_malloc0(sizeof(struct sr_dev_inst));
sdi->status = SR_ST_INACTIVE;
sdi->vendor = g_strdup("Quantr");
sdi->model = g_strdup("Device");
sdi->inst_type = SR_INST_SERIAL;
sdi->conn = serial;
if (!sdi) {
serial_close(serial);
sr_serial_dev_inst_free(serial);
return NULL;
}
devc = g_malloc0(sizeof(struct dev_context));
devc->serial = serial;
devc->samplerate = 1000000; /* Default 1MHz */
devc->num_channels = 8; /* Default 8 channels */
devc->buffer_size = 4096;
devc->buffer = g_malloc(devc->buffer_size);
sdi->priv = devc;
serial_close(serial);
devices = g_slist_append(devices, sdi);
return devices;
}
/* List devices */
SR_PRIV GSList *quantr_dev_list(const struct sr_dev_driver *di)
{
return ((struct sr_dev_driver *)di)->context;
}
/* Clear device instances */
SR_PRIV int quantr_dev_clear(const struct sr_dev_driver *di)
{
return std_dev_clear(di);
}
/* Get configuration */
SR_PRIV int quantr_config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc = sdi->priv;
(void)cg;
switch (key) {
case SR_CONF_SAMPLERATE:
*data = g_variant_new_uint64(devc->samplerate);
return SR_OK;
default:
return SR_ERR_NA;
}
}
/* Set configuration */
SR_PRIV int quantr_config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
struct dev_context *devc = sdi->priv;
(void)cg;
if (sdi->status != SR_ST_ACTIVE)
return SR_ERR_DEV_CLOSED;
switch (key) {
case SR_CONF_SAMPLERATE:
devc->samplerate = g_variant_get_uint64(data);
return SR_OK;
default:
return SR_ERR_NA;
}
}
/* List configuration options */
SR_PRIV int quantr_config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
(void)sdi;
(void)cg;
switch (key) {
case SR_CONF_SCAN_OPTIONS:
*data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
scanopts, ARRAY_SIZE(scanopts), sizeof(uint32_t));
return SR_OK;
case SR_CONF_DEVICE_OPTIONS:
*data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
devopts, ARRAY_SIZE(devopts), sizeof(uint32_t));
return SR_OK;
case SR_CONF_SAMPLERATE:
*data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT64,
samplerates, ARRAY_SIZE(samplerates), sizeof(uint64_t));
return SR_OK;
default:
return SR_ERR_NA;
}
}
/* Open device */
SR_PRIV int quantr_dev_open(struct sr_dev_inst *sdi)
{
struct dev_context *devc = sdi->priv;
int ret;
ret = serial_open(devc->serial, SERIAL_RDWR);
if (ret != SR_OK)
return ret;
sdi->status = SR_ST_ACTIVE;
return SR_OK;
}
/* Close device */
SR_PRIV int quantr_dev_close(struct sr_dev_inst *sdi)
{
struct dev_context *devc = sdi->priv;
if (devc->serial) {
serial_close(devc->serial);
}
sdi->status = SR_ST_INACTIVE;
return SR_OK;
}
/* Start acquisition */
SR_PRIV int quantr_dev_acquisition_start(const struct sr_dev_inst *sdi)
{
struct dev_context *devc = sdi->priv;
int ret;
if (!devc->serial)
return SR_ERR;
/* Send start command to device via serial port */
ret = serial_write_blocking(devc->serial, "START\n", 6, 1000);
if (ret < 0)
return SR_ERR;
devc->acquisition_running = 1;
/* Set up polling or event-driven data reading here */
/* For now, this is just a skeleton */
return SR_OK;
}
/* Stop acquisition */
SR_PRIV int quantr_dev_acquisition_stop(struct sr_dev_inst *sdi)
{
struct dev_context *devc = sdi->priv;
int ret;
if (!devc->serial || !devc->acquisition_running)
return SR_OK;
/* Send stop command to device via serial port */
ret = serial_write_blocking(devc->serial, "STOP\n", 5, 1000);
if (ret < 0)
return SR_ERR;
devc->acquisition_running = 0;
return SR_OK;
}
protocol.h:
#ifndef LIBSIGROK_HARDWARE_QUANTR_PROTOCOL_H
#define LIBSIGROK_HARDWARE_QUANTR_PROTOCOL_H
#include <stdint.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
/* Device-specific context */
struct dev_context {
struct sr_serial_dev_inst *serial;
uint64_t samplerate;
int num_channels;
uint8_t *buffer;
size_t buffer_size;
int acquisition_running;
};
/* Function prototypes */
SR_PRIV int quantr_init(struct sr_dev_driver *di, struct sr_context *sr_ctx);
SR_PRIV int quantr_cleanup(const struct sr_dev_driver *di);
SR_PRIV GSList *quantr_scan(struct sr_dev_driver *di, GSList *options);
SR_PRIV GSList *quantr_dev_list(const struct sr_dev_driver *di);
SR_PRIV int quantr_dev_clear(const struct sr_dev_driver *di);
SR_PRIV int quantr_config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_dev_open(struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_close(struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_acquisition_start(const struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_acquisition_stop(struct sr_dev_inst *sdi);
#endif
Step 4: Build libsigrok
./autogen.sh
./configure
make -j
sudo make install
Step 5: Build sigrok-cli
./autogen.sh
./configure
make -j
sudo make install
Step 6: Test
Then test it by "sigrok-cli -L", you should able to see "Quantr Device"
!!! if it linked to wrong libusb, use "sudo install_name_tool -change /src/staging/libusb/macos64/lib/libusb-1.0.0.dylib /usr/local/lib/libusb-1.0.0.dylib /usr/local/lib/libsigrok.4.dylib" to fix it

sigrok-cli --driver quantr:conn=/dev/tty.usbmodem144401:serialcomm=2000000/8n1 --scan
Trying to build a logic analyzer prototype, today i am using weact black pill board. I am using interrupt to fire the DMA and get the pins values, then use virtual com port to transfer it to PC, so far it works
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
DMA_HandleTypeDef hdma_memtomem_dma2_stream0;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_DMA_Init(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void XferCpltCallback(DMA_HandleTypeDef *hdma);
uint8_t Buffer_Src[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
uint8_t Buffer_Dest[10];
volatile uint32_t gpio_value; // To store the 32-bit GPIO register value
volatile uint8_t transfer_complete = 0; // Flag to indicate transfer completion
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void) {
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
/* USER CODE BEGIN 2 */
hdma_memtomem_dma2_stream0.XferCpltCallback = &XferCpltCallback;
// Configure some GPIOA pins as inputs with pullup to see different values
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 |
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_13 |
GPIO_PIN_14 | GPIO_PIN_15; // All pins except PA11 and PA12
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN; // Pull up to see 1s in the register
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// Transfer 4 bytes (32-bit GPIO register) from GPIOA IDR to Buffer_Dest
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t) &GPIOA->IDR,
(uint32_t) Buffer_Dest, 4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// Check if DMA transfer completed
if (transfer_complete) {
// Format Buffer_Dest as human-readable hex string
char hex_string[100];
// memset(hex_string, 0, sizeof(hex_string));
// CDC_Transmit_FS("Peter\r\n", 7);
// CDC_Transmit_FS("HIH\r\n", 5);
sprintf(hex_string, "0x%02X 0x%02X 0x%02X 0x%02X\r\n", Buffer_Dest[0], Buffer_Dest[1], Buffer_Dest[2], Buffer_Dest[3]);
// sprintf(hex_string, "0x%02X\r\n", Buffer_Dest[0]);
CDC_Transmit_FS(hex_string, strlen(hex_string));
// CDC_Transmit_FS("END\r\n", 5);
transfer_complete = 0; // Reset flag
// At this point, you can examine:
// - Buffer_Dest[0-3]: individual bytes from GPIOA->IDR
// - gpio_value: reconstructed 32-bit value
// Toggle LED2 to indicate transfer completed
// HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
// HAL_GPIO_TogglePin(PB10_GPIO_Port, PB10_Pin);
// Wait a bit before next transfer
// HAL_Delay(1000);
// Start another transfer
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
(uint32_t) &GPIOA->IDR, (uint32_t) Buffer_Dest, 4);
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 15;
RCC_OscInitStruct.PLL.PLLN = 144;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 5;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) {
Error_Handler();
}
}
/**
* Enable DMA controller clock
* Configure DMA for memory to memory transfers
* hdma_memtomem_dma2_stream0
*/
static void MX_DMA_Init(void) {
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;
hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK) {
Error_Handler();
}
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(PB10_GPIO_Port, PB10_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : PB10_Pin */
GPIO_InitStruct.Pin = PB10_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(PB10_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
// Configure GPIOA pins 0-7 as outputs driven low to ensure they read 0
// GPIO_InitStruct.Pin = 0xFF; // Pins 0-7
// GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Output push-pull
// GPIO_InitStruct.Pull = GPIO_NOPULL;
// GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
// HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//
// // Drive all pins low
// HAL_GPIO_WritePin(GPIOA, 0xFF, GPIO_PIN_RESET);
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void XferCpltCallback(DMA_HandleTypeDef *hdma) {
// Transfer completed - combine the 4 bytes to a 32-bit value for easier interpretation
gpio_value = (Buffer_Dest[3] << 24) | (Buffer_Dest[2] << 16) | (Buffer_Dest[1] << 8) | Buffer_Dest[0];
transfer_complete = 1;
__NOP(); //Line reached only if transfer was successful. Toggle a breakpoint here
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void) {
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */





while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// HAL_GPIO_TogglePin(led_GPIO_Port, led_Pin);
// HAL_GPIO_TogglePin(MYPIN_GPIO_Port, MYPIN_Pin);
// HAL_Delay(250);
char msg[] = "Hello via USB CDC!\r\n";
CDC_Transmit_FS((uint8_t*) msg, strlen(msg));
HAL_Delay(1000);
}
Unlike nucleo to user serial port via usb by just enabling the uart. We have to enable VCP in weact H750 board.



while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// HAL_UART_Transmit(&huart1, tx_buff, 7, 1000); <-- this not work
CDC_Transmit_FS(tx_buff, 7); // use this
HAL_Delay(1000);
}

Reference: https://blog.csdn.net/Naisu_kun/article/details/118192032

mvn compile
mvn exec:java
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javax.swing.*;
public class WebEmbedSwingJavaFX {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Website in Swing with JavaFX");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
JFXPanel jfxPanel = new JFXPanel();
frame.add(jfxPanel);
frame.setVisible(true);
// Run JavaFX code on JavaFX thread
Platform.runLater(() -> {
WebView webView = new WebView();
webView.getEngine().load("https://www.hkprog.org"); // Replace with your URL
jfxPanel.setScene(new Scene(webView));
});
});
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>swing-web-embed</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>17</javafx.version>
</properties>
<dependencies>
<!-- JavaFX dependencies for WebView -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- Maven Exec Plugin to run the application -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<mainClass>WebEmbedSwingJavaFX</mainClass>
<commandlineArgs>--module-path ${project.basedir}/lib/javafx-sdk-${javafx.version}/lib --add-modules javafx.controls,javafx.web,javafx.swing</commandlineArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>


Few things need to be care and different then STM32F411CEU6
- You can use all 16 pins of GPIO-A because pins 15, 14, 13 are used already, see below image, they are always one
- Setting the max CPU freq 480 Mhz, if your program has bug, you can't burn the program again, follow this to solve https://peter.hkprog.org/2025/07/solved-weact-stm32h750vbt6-unable-to-find-target/
- What ever how high CPU freq, keep toggling the pin with DMA won't faster then 100KHz. Because DMA slow down the process. Even set the dma priority to "very high" won't help. Below code scale down toggling PC0 pin by 100x to provide this.
- Have to call __HAL_RCC_GPIOA_CLK_ENABLE(); before setup GPIO-A pins, STM32F411CEU6 no need
https://wiki.st.com/stm32mcu/wiki/Getting_started_with_DMA

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
DMA_HandleTypeDef hdma_memtomem_dma1_stream0;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MPU_Config(void);
static void MX_DMA_Init(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void XferCpltCallback(DMA_HandleTypeDef *hdma);
uint8_t Buffer_Src[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
uint8_t Buffer_Dest[10];
volatile uint32_t gpio_value; // To store the 32-bit GPIO register value
volatile uint8_t transfer_complete = 0; // Flag to indicate transfer completion
volatile uint32_t toggle_counter = 0; // Counter to slow down pin toggling
#define TOGGLE_DIVIDER 100 // Slow down by 100x
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
hdma_memtomem_dma1_stream0.XferCpltCallback = &XferCpltCallback;
// CRITICAL: Enable GPIOA clock first!
__HAL_RCC_GPIOA_CLK_ENABLE();
// Configure only a few safe GPIOA pins to test
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5; // Just PA0 and PA1 for testing
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN; // Pull down makes pins read as 0 when unconnected
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_DMA_Start_IT(&hdma_memtomem_dma1_stream0, (uint32_t) &GPIOA->IDR,
(uint32_t) Buffer_Dest, 4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// Check if DMA transfer completed
if (transfer_complete) {
transfer_complete = 0; // Reset flag
// At this point, you can examine:
// - Buffer_Dest[0-3]: individual bytes from GPIOA->IDR
// - gpio_value: reconstructed 32-bit value
// Increment counter and only toggle pin every TOGGLE_DIVIDER times
toggle_counter++;
if (toggle_counter >= TOGGLE_DIVIDER) {
toggle_counter = 0; // Reset counter
// Toggle LED to indicate transfer completed (slowed down 100x)
HAL_GPIO_TogglePin(PC0_GPIO_Port, PC0_Pin);
}
// Wait a bit before next transfer
// HAL_Delay(1000);
// Start another transfer
HAL_DMA_Start_IT(&hdma_memtomem_dma1_stream0,
(uint32_t) &GPIOA->IDR, (uint32_t) Buffer_Dest, 4);
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Supply configuration update enable
*/
HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 10;
RCC_OscInitStruct.PLL.PLLN = 150;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* Enable DMA controller clock
* Configure DMA for memory to memory transfers
* hdma_memtomem_dma1_stream0
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_stream0 on DMA1_Stream0 */
hdma_memtomem_dma1_stream0.Instance = DMA1_Stream0;
hdma_memtomem_dma1_stream0.Init.Request = DMA_REQUEST_MEM2MEM;
hdma_memtomem_dma1_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_stream0.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma1_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma1_stream0.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_stream0.Init.Priority = DMA_PRIORITY_LOW;
hdma_memtomem_dma1_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_memtomem_dma1_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_memtomem_dma1_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_memtomem_dma1_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_memtomem_dma1_stream0) != HAL_OK)
{
Error_Handler( );
}
/* DMA interrupt init */
/* DMA1_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(PC0_GPIO_Port, PC0_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : PC0_Pin */
GPIO_InitStruct.Pin = PC0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(PC0_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void XferCpltCallback(DMA_HandleTypeDef *hdma) {
// Transfer completed - combine the 4 bytes to a 32-bit value for easier interpretation
gpio_value = (Buffer_Dest[3] << 24) | (Buffer_Dest[2] << 16)
| (Buffer_Dest[1] << 8) | Buffer_Dest[0];
// For debugging: you can examine individual bytes here
// Buffer_Dest[0] = LSB of GPIOA->IDR (pins PA0-PA7)
// Buffer_Dest[1] = pins PA8-PA15
// Buffer_Dest[2] = pins PA16-PA23 (if available)
// Buffer_Dest[3] = MSB of GPIOA->IDR (pins PA24-PA31, if available)
transfer_complete = 1;
__NOP(); //Line reached only if transfer was successful. Toggle a breakpoint here
}
/* USER CODE END 4 */
/* MPU Configuration */
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
/* Disables the MPU */
HAL_MPU_Disable();
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x0;
MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;
MPU_InitStruct.SubRegionDisable = 0x87;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
I changed the clock to maximum 480Mhz, after I F5 for the first time, I unable to burn any program to it again. To solve this, press both reset and boot buttons on the board, click F5 in STM32CubeIDE, then release reset button (still hold the bott button). It will successfully burn the program again. And remember to run "st-flash erase" for full chip erase, then you can burn your program without pressing any button on board.

I got this error is i change "MODEL=atmega328p" to "MODEL=atmega328pb" in my makefile. Github copilot said the error occurs because the ATmega328PB has two TWI (I2C) modules, and its I/O register naming is different from the ATmega328P. Specifically, the ATmega328PB splits some port definitions (like DDRC, PORTC) into two sets (for PORTC and PORTD, etc.), and some macros or register names may not be defined as expected.
In file included from ssd1306/TWI.h:19,
from ssd1306/TWI.c:1:
ssd1306/TWI.c: In function 'TWI_Setup':
ssd1306/IO_Macros.h:26:42: error: 'DDRC' undeclared (first use in this function); did you mean 'DDR'?
26 | #define PinMode( x, y) ( y ? _SET(DDR, x) : _CLEAR(DDR, x) )
I need to change IO_Macros.h from
#ifndef IO_MACROS_H_INCLUDED
#define IO_MACROS_H_INCLUDED
/*
||
|| Filename: IO_Macros.h
|| Title: IO manipulation macros
|| Author: Efthymios Koktsidis
|| Email: [email protected]
|| Compiler: AVR-GCC
|| Description: This library contains macros for
|| easy port manipulation (similar
|| to Arduino).
||
|| Demo:
|| 1. #define LED A, 0 || 6. PinModeToggle(BUTTON);
|| 2. #define BUTTON A, 1 || 7. DigitalWrite(LED, LOW);
|| 3. || 8. DigitalWrite(LED, HIGH);
|| 4. PinMode(BUTTON, OUTPUT); || 9. DigitalLevelToggle(LED);
|| 5. PinMode(LED, OUTPUT); ||10. int a = DigitalRead(BUTTON);
||
*/
#include <avr/io.h>
//----- I/O Macros -----
//Macros to edit PORT, DDR and PIN
#define PinMode( x, y) ( y ? _SET(DDR, x) : _CLEAR(DDR, x) )
#define DigitalWrite( x, y) ( y ? _SET(PORT, x) : _CLEAR(PORT, x) )
#define DigitalRead( x) ( _GET(PIN, x) )
#define PinModeToggle( x) ( _TOGGLE(DDR, x) )
#define DigitalLevelToggle( x) ( _TOGGLE(PORT, x) )
//General use bit manipulating commands
#define BitSet( x, y) ( x |= (1UL<<y) )
#define BitClear( x, y) ( x &= (~(1UL<<y)) )
#define BitToggle( x, y) ( x ^= (1UL<<y) )
#define BitCheck( x, y) ( x & (1UL<<y) ? 1 : 0 )
//Access PORT, DDR and PIN
#define PORT( port) (_PORT( port))
#define DDR( port) (_DDR( port))
#define PIN( port) (_PIN( port))
#define _PORT( port) (PORT## port)
#define _DDR( port) (DDR## port)
#define _PIN( port) (PIN## port)
#define _SET( type, port, bit) ( BitSet( (type##port), bit) )
#define _CLEAR( type, port, bit) ( BitClear( (type##port), bit) )
#define _TOGGLE(type, port, bit) ( BitToggle( (type##port), bit) )
#define _GET( type, port, bit) ( BitCheck( (type##port), bit) )
//Definitions
#define Input 0
#define Output !Input
#define Low 0
#define High !Low
#define False 0
#define True !False
//------------------
#endif
to
#ifndef IO_MACROS_H_INCLUDED
#define IO_MACROS_H_INCLUDED
/*
||
|| Filename: IO_Macros.h
|| Title: IO manipulation macros
|| Author: Efthymios Koktsidis
|| Email: [email protected]
|| Compiler: AVR-GCC
|| Description: This library contains macros for
|| easy port manipulation (similar
|| to Arduino).
||
|| Demo:
|| 1. #define LED A, 0 || 6. PinModeToggle(BUTTON);
|| 2. #define BUTTON A, 1 || 7. DigitalWrite(LED, LOW);
|| 3. || 8. DigitalWrite(LED, HIGH);
|| 4. PinMode(BUTTON, OUTPUT); || 9. DigitalLevelToggle(LED);
|| 5. PinMode(LED, OUTPUT); ||10. int a = DigitalRead(BUTTON);
||
*/
#include <avr/io.h>
//----- I/O Macros -----
//Macros to edit PORT, DDR and PIN
// Usage: PinMode(C, 4, Output); // Set PC4 as output
#define PinMode(port, bit, mode) ((mode) ? BitSet(DDR##port, bit) : BitClear(DDR##port, bit))
#define DigitalWrite(port, bit, level) ((level) ? BitSet(PORT##port, bit) : BitClear(PORT##port, bit))
#define DigitalRead(port, bit) (BitCheck(PIN##port, bit))
#define PinModeToggle(port, bit) (BitToggle(DDR##port, bit))
#define DigitalLevelToggle(port, bit) (BitToggle(PORT##port, bit))
//General use bit manipulating commands
#define BitSet(x, y) ( x |= (1UL<<(y)) )
#define BitClear(x, y) ( x &= (~(1UL<<(y))) )
#define BitToggle(x, y) ( x ^= (1UL<<(y)) )
#define BitCheck(x, y) ( ((x) & (1UL<<(y))) ? 1 : 0 )
//Definitions
#define Input 0
#define Output 1
#define Low 0
#define High 1
#define False 0
#define True 1
//------------------
#endif
Thinking how to build a logic analyzer using STM32. This example keep transfer the value of GPIO A to memory then fire interrupt using DMA. It will toggle PC0 for every complete transfer in my Nucleo L476RG board, I use oscilloscope to test, around 93KHZ



/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_memtomem_dma1_channel1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void XferCpltCallback(DMA_HandleTypeDef *hdma);
uint8_t Buffer_Src[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
uint8_t Buffer_Dest[10];
volatile uint32_t gpio_value; // To store the 32-bit GPIO register value
volatile uint8_t transfer_complete = 0; // Flag to indicate transfer completion
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
hdma_memtomem_dma1_channel1.XferCpltCallback = &XferCpltCallback;
// Configure some GPIOA pins as inputs with pulldown to see different values
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN; // Pull down to see 1s in the register
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// Transfer 4 bytes (32-bit GPIO register) from GPIOA IDR to Buffer_Dest
HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t) &GPIOA->IDR,
(uint32_t) Buffer_Dest, 4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// Check if DMA transfer completed
if (transfer_complete) {
transfer_complete = 0; // Reset flag
// At this point, you can examine:
// - Buffer_Dest[0-3]: individual bytes from GPIOA->IDR
// - gpio_value: reconstructed 32-bit value
// Toggle LED2 to indicate transfer completed
// HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_GPIO_TogglePin(PC0_GPIO_Port, PC0_Pin);
// Wait a bit before next transfer
// HAL_Delay(1000);
// Start another transfer
HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t) &GPIOA->IDR,
(uint32_t) Buffer_Dest, 4);
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
Error_Handler();
}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 10;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* Enable DMA controller clock
* Configure DMA for memory to memory transfers
* hdma_memtomem_dma1_channel1
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
hdma_memtomem_dma1_channel1.Init.Request = DMA_REQUEST_0;
hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
{
Error_Handler( );
}
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(PC0_GPIO_Port, PC0_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PC0_Pin */
GPIO_InitStruct.Pin = PC0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(PC0_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : LD2_Pin */
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void XferCpltCallback(DMA_HandleTypeDef *hdma) {
// Transfer completed - combine the 4 bytes to a 32-bit value for easier interpretation
gpio_value = (Buffer_Dest[3] << 24) | (Buffer_Dest[2] << 16) | (Buffer_Dest[1] << 8) | Buffer_Dest[0];
transfer_complete = 1;
__NOP(); //Line reached only if transfer was successful. Toggle a breakpoint here
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
https://wiki.st.com/stm32mcu/wiki/Getting_started_with_DMA#DMA_memory-to-memory_example_overview


/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
DMA_HandleTypeDef hdma_memtomem_dma2_stream0;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_DMA_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void XferCpltCallback(DMA_HandleTypeDef *hdma);
uint8_t Buffer_Src[]={0,1,2,3,4,5,6,7,8,9};
uint8_t Buffer_Dest[10];
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
/* USER CODE BEGIN 2 */
hdma_memtomem_dma2_stream0.XferCpltCallback=&XferCpltCallback;
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,(uint32_t)Buffer_Src,(uint32_t)Buffer_Dest,10);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* Enable DMA controller clock
* Configure DMA for memory to memory transfers
* hdma_memtomem_dma2_stream0
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;
hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_LOW;
hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
{
Error_Handler( );
}
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
/* USER CODE BEGIN 4 */
void XferCpltCallback(DMA_HandleTypeDef *hdma)
{
__NOP(); //Line reached only if transfer was successful. Toggle a breakpoint here
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


All device pins should be connected to the same pins on the ESP32 board except the CS (chip select) pin.
import lcd_bus
from micropython import const
import machine
from time import sleep
import st7735
import lvgl as lv
import utime as time
from fs_driver import fs_register
from machine import Pin
import AD9833
selected = 0
def drawMenu():
button1 = lv.button(scrn)
button1.set_pos(4, 30)
button1.set_size(40, 20)
label1 = lv.label(button1)
label1.set_text("Func")
label1.set_style_text_color(lv.color_hex(0x000000), 0) # Black text
label1.set_style_text_font(lv.font_montserrat_12, 0)
if selected == 0:
button1.set_style_bg_color(
lv.color_hex(0xffffff), 0)
label1.center()
button2 = lv.button(scrn)
button2.set_pos(50, 30)
button2.set_size(75, 20)
label2 = lv.label(button2)
label2.set_text("Multimeter")
label2.set_style_text_color(lv.color_hex(0x000000), 0) # Black text
label2.set_style_text_font(lv.font_montserrat_12, 0)
if selected == 1:
button2.set_style_bg_color(
lv.color_hex(0xffffff), 0)
label2.center()
# display settings
_WIDTH = 128
_HEIGHT = 128
_BL = 19
_RST = 14
_DC = 15
_MOSI = 21 # SDA
# _MISO = 20
_SCK = 22 # SCL
_HOST = 1 # SPI2
_LCD_CS = 18
_LCD_FREQ = 4000000
_OFFSET_X = 2
_OFFSET_Y = 3
print('s1')
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
# miso=_MISO,
sck=_SCK
)
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
display = st7735.ST7735(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
backlight_pin=_BL,
reset_pin=_RST,
reset_state=st7735.STATE_LOW,
backlight_on_state=st7735.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=st7735.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
offset_x=_OFFSET_X,
offset_y=_OFFSET_Y
)
print('s4')
# Initialize display
display.init(st7735.TYPE_R_RED)
display.set_rotation(lv.DISPLAY_ROTATION._180)
display.set_backlight(100)
# Create screen
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)
fs_drv = lv.fs_drv_t()
fs_register(fs_drv, "S")
img = lv.image(scrn)
img.set_src("S:colorful20.png")
img.set_size(20, 20)
img.set_pos(0, 5)
label = lv.label(scrn)
label.set_text("Multimeter")
label.set_pos(24, 8)
label.set_style_text_color(lv.color_hex(0xffffff), 0)
label.set_style_text_font(lv.font_montserrat_12, 0)
button0 = Pin(20, Pin.IN, Pin.PULL_UP) # Button pin
button1 = Pin(5, Pin.IN, Pin.PULL_UP) # Button pin
drawMenu()
# temp.value(1)
# display_bus.deinit()
ad9833 = AD9833.AD9833(sdo = 21, clk = 22, cs = 2, fmclk = 25)
ad9833.set_frequency(1300, 0)
# ad9833.set_frequency(2600, 1)
ad9833.set_phase(0, 0, rads = False)
ad9833.set_phase(180, 1, rads = False)
time.sleep(0.5)
# ad9833.select_freq_phase(0,0)
# ad9833.set_mode('SIN')
# time.sleep(2)
ad9833.set_mode('SQUARE')
# ad9833.disable()
# time.sleep(2)
# temp.value(0)
# time.sleep(200)
print("end")
while True:
time.sleep_ms(20)
lv.task_handler()
sleep(0.2)
if not button0.value():
selected = (selected + 1) % 2
print(selected)
drawMenu()
lv.refr_now(lv.screen_active().get_display())
# ad9833.select_freq_phase(0,0)
ad9833.set_mode('SIN')
if not button1.value():
selected = (selected - 1) % 2
print(selected)
drawMenu()
lv.refr_now(lv.screen_active().get_display())
ad9833.set_frequency(9000, 0)
ad9833.set_mode('SQUARE/2')


Special Thanks to Mr kdschlosser for writing the driver
More Examples on https://github.com/quantrpeter/Waveshare-ESP32-C6-1.47-Touch-Micropython-LVGL
git clone https://github.com/lvgl-micropython/lvgl_micropython.git
cd lvgl_micropython
docker run -it -v .:/micropython --name micropython ubuntu
apt-get update
apt-get install -y gcc g++ make automake python3 git gcc-arm-none-eabi libusb-1.0-0 python3-venv cmake
cd /micropython
python3 make.py esp32 clean \
--flash-size=4 \
BOARD=ESP32_GENERIC_C6 \
DISPLAY=jd9853 \
INDEV=axs5106
exit docker
esptool.py --chip esp32c6 \
-b 460800 \
--before default_reset \
--after hard_reset \
write_flash --flash_mode dio \
--flash_size 4MB --flash_freq 40m \
--erase-all 0x0 build/lvgl_micropy_ESP32_GENERIC_C6-4.bin
Example:
import lcd_bus
from micropython import const
import machine
from time import sleep
import jd9853
import lvgl as lv
lv.init()
# display settings
_WIDTH = 172
_HEIGHT = 320
_BL = 23
_RST = 22
_DC = 15
_MOSI = 2 #SDA
_MISO = 5
_SCK = 1 # SCL
_HOST = 1 # SPI2
_LCD_CS = 14
_LCD_FREQ = 2000000
_OFFSET_X = 34
_OFFSET_Y = 0
print('s1');
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
#miso=_MISO,
sck=_SCK
)
print('s2');
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
print('s3');
display = jd9853.JD9853(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
backlight_pin=_BL,
reset_pin=_RST,
reset_state=jd9853.STATE_LOW,
backlight_on_state=jd9853.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=jd9853.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
offset_x=_OFFSET_X,
offset_y=_OFFSET_Y
)
print('s4');
display.set_power(True)
display.init()
display.set_color_inversion(True)
# display.set_rotation(lv.DISPLAY_ROTATION._90)
display.set_backlight(100)
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xff0000), 0)
label = lv.label(scrn)
label.set_text('HELLO')
label.set_style_text_color(lv.color_hex(0xffffff), 0)
label.align(lv.ALIGN.CENTER, 0, 30)
# Draw a rectangle
rect1 = lv.obj(scrn)
rect1.set_size(10, 10)
rect1.set_style_bg_color(lv.color_hex(0x00aa00), 0)
rect1.set_style_border_color(lv.color_hex(0xffffff), 0)
rect1.set_style_border_width(1, 0)
rect1.set_style_radius(0, 0)
rect1.align(lv.ALIGN.TOP_LEFT, 0, 0)
rect2 = lv.obj(scrn)
rect2.set_size(10, 10)
rect2.set_style_bg_color(lv.color_hex(0xaa0000), 0)
rect2.set_style_border_color(lv.color_hex(0xffffff), 0)
rect2.set_style_border_width(1, 0)
rect2.set_style_radius(0, 0)
rect2.align(lv.ALIGN.TOP_RIGHT, 0, 0)
rect3 = lv.obj(scrn)
rect3.set_size(10, 10)
rect3.set_style_bg_color(lv.color_hex(0xaa00aa), 0)
rect3.set_style_border_color(lv.color_hex(0xffffff), 0)
rect3.set_style_border_width(1, 0)
rect3.set_style_radius(0, 0)
rect3.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
rect4 = lv.obj(scrn)
rect4.set_size(10, 10)
rect4.set_style_bg_color(lv.color_hex(0x0000aa), 0)
rect4.set_style_border_color(lv.color_hex(0xffffff), 0)
rect4.set_style_border_width(1, 0)
rect4.set_style_radius(0, 0)
rect4.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
# Draw a circle
circle = lv.obj(scrn)
circle.set_size(50, 50)
circle.set_style_bg_color(lv.color_hex(0x0000ff), 0)
circle.set_style_border_color(lv.color_hex(0xff00ff), 0)
circle.set_style_border_width(3, lv.STATE.DEFAULT)
circle.set_style_radius(25, 0) # Make it circular (radius = half of width/height)
circle.align(lv.ALIGN.CENTER, 0, -10)
print('end')
import utime as time
time_passed = 1000
while True:
start_time = time.ticks_ms()
time.sleep_ms(1) # sleep for 1 ms
lv.tick_inc(time_passed)
lv.task_handler()
end_time = time.ticks_ms()
time_passed = time.ticks_diff(end_time, start_time)

import lcd_bus
from micropython import const
import machine
from time import sleep
import st7735
import lvgl as lv
lv.init()
# display settings
_WIDTH = 128
_HEIGHT = 128
_BL = 19
_RST = 14
_DC = 15
_MOSI = 21 #SDA
_MISO = 20
_SCK = 22 # SCL
_HOST = 1 # SPI2
_LCD_CS = 18
_LCD_FREQ = 2000000
_OFFSET_X = 2
_OFFSET_Y = 3
print('s1');
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
#miso=_MISO,
sck=_SCK
)
print('s2');
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
print('s3');
display = st7735.ST7735(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
backlight_pin=_BL,
reset_pin=_RST,
reset_state=st7735.STATE_LOW,
backlight_on_state=st7735.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=st7735.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
offset_x=_OFFSET_X,
offset_y=_OFFSET_Y
)
print('s4');
# display.set_power(True)
display.init(st7735.TYPE_R_RED)
display.set_rotation(lv.DISPLAY_ROTATION._180)
display.set_backlight(100)
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xff0000), 0)
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.set_style_text_color(lv.color_hex(0xffffff), 0)
label.align(lv.ALIGN.CENTER, 0, 30)
# Draw a rectangle
rect1 = lv.obj(scrn)
rect1.set_size(10, 10)
rect1.set_style_bg_color(lv.color_hex(0x00aa00), 0)
rect1.set_style_border_color(lv.color_hex(0xffffff), 0)
rect1.set_style_border_width(1, 0)
rect1.set_style_radius(0, 0)
rect1.align(lv.ALIGN.TOP_LEFT, 0, 0)
rect2 = lv.obj(scrn)
rect2.set_size(10, 10)
rect2.set_style_bg_color(lv.color_hex(0xaa0000), 0)
rect2.set_style_border_color(lv.color_hex(0xffffff), 0)
rect2.set_style_border_width(1, 0)
rect2.set_style_radius(0, 0)
rect2.align(lv.ALIGN.TOP_RIGHT, 0, 0)
rect3 = lv.obj(scrn)
rect3.set_size(10, 10)
rect3.set_style_bg_color(lv.color_hex(0xaa00aa), 0)
rect3.set_style_border_color(lv.color_hex(0xffffff), 0)
rect3.set_style_border_width(1, 0)
rect3.set_style_radius(0, 0)
rect3.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
rect4 = lv.obj(scrn)
rect4.set_size(10, 10)
rect4.set_style_bg_color(lv.color_hex(0x0000aa), 0)
rect4.set_style_border_color(lv.color_hex(0xffffff), 0)
rect4.set_style_border_width(1, 0)
rect4.set_style_radius(0, 0)
rect4.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
# Draw a circle
circle = lv.obj(scrn)
circle.set_size(50, 50)
circle.set_style_bg_color(lv.color_hex(0x0000ff), 0)
circle.set_style_border_color(lv.color_hex(0xff00ff), 0)
circle.set_style_border_width(3, lv.STATE.DEFAULT)
circle.set_style_radius(25, 0) # Make it circular (radius = half of width/height)
circle.align(lv.ALIGN.CENTER, 0, -10)
print('end')
import utime as time
time_passed = 1000
while True:
start_time = time.ticks_ms()
time.sleep_ms(1) # sleep for 1 ms
lv.tick_inc(time_passed)
lv.task_handler()
end_time = time.ticks_ms()
time_passed = time.ticks_diff(end_time, start_time)
My old Ender 3 Pro, give 0.3mm X-Y Hole compensation, now no need polishing, I can plug them into another. Two filament print from two different nozzle temperature, same perfect.



https://www.waveshare.com/wiki/ESP32-C6-Zero
git clone https://github.com/lvgl-micropython/lvgl_micropython.git
cd lvgl_micropython
python3 make.py esp32 clean \
--flash-size=4 \
BOARD=ESP32_GENERIC_C6 \
DISPLAY=ST7735
esptool.py --chip esp32c6 \
-b 460800 \
--before default_reset \
--after hard_reset write_flash \
--flash_mode dio \
--flash_size 4MB \
--flash_freq 80m \
--erase-all 0x0 build/lvgl_micropy_ESP32_GENERIC_C6-4.bin
import lcd_bus
from micropython import const
import machine
from time import sleep
import st7735
import lvgl as lv
lv.init()
# display settings
_WIDTH = 128
_HEIGHT = 128
_BL = 19
_RST = 14
_DC = 15
_MOSI = 21 #SDA
_MISO = 20
_SCK = 22 # SCL
_HOST = 1 # SPI2
_LCD_CS = 18
_LCD_FREQ = 2000000
_OFFSET_X = 2
_OFFSET_Y = 3
print('s1');
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
#miso=_MISO,
sck=_SCK
)
print('s2');
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
print('s3');
display = st7735.ST7735(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
backlight_pin=_BL,
reset_pin=_RST,
reset_state=st7735.STATE_LOW,
backlight_on_state=st7735.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=st7735.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
offset_x=_OFFSET_X,
offset_y=_OFFSET_Y
)
print('s4');
# display.set_power(True)
display.init(st7735.TYPE_R_RED)
display.set_rotation(lv.DISPLAY_ROTATION._180)
display.set_backlight(100)
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xff0000), 0)
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.set_style_text_color(lv.color_hex(0xffffff), 0)
label.align(lv.ALIGN.CENTER, 0, 30)
# Draw a rectangle
rect1 = lv.obj(scrn)
rect1.set_size(10, 10)
rect1.set_style_bg_color(lv.color_hex(0x00aa00), 0) # Green color
rect1.set_style_border_color(lv.color_hex(0xffffff), 0) # Yellow border
rect1.set_style_border_width(1, 0)
rect1.set_style_radius(0, 0)
rect1.align(lv.ALIGN.TOP_LEFT, 0, 0)
rect2 = lv.obj(scrn)
rect2.set_size(10, 10)
rect2.set_style_bg_color(lv.color_hex(0xaa0000), 0) # Green color
rect2.set_style_border_color(lv.color_hex(0xffffff), 0) # Yellow border
rect2.set_style_border_width(1, 0)
rect2.set_style_radius(0, 0)
rect2.align(lv.ALIGN.TOP_RIGHT, 0, 0)
rect3 = lv.obj(scrn)
rect3.set_size(10, 10)
rect3.set_style_bg_color(lv.color_hex(0xaa00aa), 0) # Green color
rect3.set_style_border_color(lv.color_hex(0xffffff), 0) # Yellow border
rect3.set_style_border_width(1, 0)
rect3.set_style_radius(0, 0)
rect3.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
rect4 = lv.obj(scrn)
rect4.set_size(10, 10)
rect4.set_style_bg_color(lv.color_hex(0x0000aa), 0) # Green color
rect4.set_style_border_color(lv.color_hex(0xffffff), 0) # Yellow border
rect4.set_style_border_width(1, 0)
rect4.set_style_radius(0, 0)
rect4.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
# Draw a circle
circle = lv.obj(scrn)
circle.set_size(50, 50)
circle.set_style_bg_color(lv.color_hex(0x0000ff), 0) # Blue color
circle.set_style_border_color(lv.color_hex(0xff00ff), 0) # Magenta border
circle.set_style_border_width(3, lv.STATE.DEFAULT)
circle.set_style_radius(25, 0) # Make it circular (radius = half of width/height)
circle.align(lv.ALIGN.CENTER, 0, -10)
print('end')
import utime as time
time_passed = 1000
while True:
start_time = time.ticks_ms()
time.sleep_ms(1) # sleep for 1 ms
lv.tick_inc(time_passed)
lv.task_handler()
end_time = time.ticks_ms()
time_passed = time.ticks_diff(end_time, start_time)

git clone https://github.com/lvgl-micropython/lvgl_micropython.git
cd lvgl_micropython
docker run -it -v .:/micropython --name micropython ubuntu
apt-get update
apt-get install -y gcc g++ make automake python3 git gcc-arm-none-eabi libusb-1.0-0 python3-venv cmake
python3 make.py esp32 clean \
--flash-size=4 \
BOARD=ESP32_GENERIC \
DISPLAY=ili9341 \
INDEV=xpt2046
esptool.py --chip esp32 \
-b 460800 \
--before default_reset \
--after hard_reset \
write_flash --flash_mode dio \
--flash_size 4MB --flash_freq 40m \
--erase-all 0x0 build/lvgl_micropy_ESP32_GENERIC-4.bin

import lcd_bus
from micropython import const
import machine
# display settings
_WIDTH = const(240)
_HEIGHT = const(320)
_BL = const(21)
_RST = const(17)
_DC = const(2)
_MOSI = const(13)
#_MISO = const(12)
_SCK = const(14)
_HOST = const(1) # SPI2
_LCD_CS = const(15)
_LCD_FREQ = const(40000000)
#_TOUCH_CS = const(9)
#_TOUCH_FREQ = const(1000000)
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
#miso=_MISO,
sck=_SCK
)
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
import ili9341 # NOQA
import lvgl as lv # NOQA
display = ili9341.ILI9341(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
reset_pin=_RST,
reset_state=ili9341.STATE_LOW,
backlight_pin=_BL,
backlight_on_state=ili9341.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=ili9341.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
)
import task_handler # NOQA
import xpt2046 # NOQA
display.set_power(True)
display.init(1)
# display.set_color_inversion(True)
display.set_rotation(lv.DISPLAY_ROTATION._90)
display.set_backlight(100)
#touch_dev = machine.SPI.Device(
# spi_bus=spi_bus,
# freq=_TOUCH_FREQ,
# cs=_TOUCH_CS
#)
#indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0)
#indev.calibrate()
th = task_handler.TaskHandler()
scrn = lv.screen_active()
#scrn.set_style_bg_color(lv.color_hex(0xFFFFFF), 0)
#btnm = lv.buttonmatrix(scrn)
#btnm.add_event_cb(lambda e: btnm_event_handler(e,scrn),lv.EVENT.VALUE_CHANGED, None)
#btnm.set_size(230,120)
#btnm.align(1,5,5)
tabview = lv.tabview(scrn)
tabview.set_tab_bar_size(30)
tab1 = tabview.add_tab("Tab 1")
tab2 = tabview.add_tab("Tab 2")
tab3 = tabview.add_tab("Tab 3")
# Add content to the tabs
label1 = lv.label(tab1)
label1.set_text("This is the content of Tab 1")
#label2 = lv.label(tab2)
#label2.set_text("This is the content of Tab 2")
label3 = lv.label(tab3)
label3.set_text("This is the content of Tab 3")
btn = lv.button(tab1)
btn.center()
btn.set_size(100,50)
btn.set_style_bg_color(lv.color_make(255, 0, 0), 0) # RGB: Red=255, Green=0, Blue=0
lbl = lv.label(btn)
lbl.set_text('Start')
lbl.center()
# Add second button
btn2 = lv.button(tab1)
btn2.set_size(100,50)
btn2.align(lv.ALIGN.CENTER, 0, 60) # Position below the first button
lbl2 = lv.label(btn2)
lbl2.set_text('Stop')
lbl2.center()
tab2.set_flex_flow(lv.FLEX_FLOW.COLUMN)
lab21 = lv.label(tab2)
lab21.set_text('Group 1')
chk21 = lv.checkbox(tab2)
chk21.set_text('Option 1')
chk22 = lv.checkbox(tab2)
chk22.set_text('Option 2')
chk23 = lv.checkbox(tab2)
chk23.set_text('Option 3')
chk24 = lv.checkbox(tab2)
chk24.set_text('Option 4')
lab22 = lv.label(tab2)
lab22.set_text('Group 2')
chk25 = lv.checkbox(tab2)
chk25.set_text('Option 5')
chk26 = lv.checkbox(tab2)
chk26.set_text('Option 6')
chk27 = lv.checkbox(tab2)
chk27.set_text('Option 7')
chk28 = lv.checkbox(tab2)
chk28.set_text('Option 8')
o = 1
def btnm_event_handler(e,ta):
global o
obj = e.get_target()
o=obj
print("Toggled")
Example: Clock

import lcd_bus
from micropython import const
import machine
import time # Add time module for clock functionality
# Try to import ntptime for NTP sync
try:
import ntptime
NTP_AVAILABLE = True
except ImportError:
NTP_AVAILABLE = False
print("ntptime not available")
# Try to import network for WiFi
try:
import network
NETWORK_AVAILABLE = True
except ImportError:
NETWORK_AVAILABLE = False
print("network not available")
# WiFi Configuration - CHANGE THESE TO YOUR WIFI CREDENTIALS
WIFI_SSID = "Quantr 2.4G" # Replace with your WiFi name
WIFI_PASSWORD = "quantrwi" # Replace with your WiFi password
# Timezone Configuration
TIMEZONE_OFFSET = 8 # Hong Kong is UTC+8 hours
TIMEZONE_NAME = "Hong Kong"
# display settings
_WIDTH = const(240)
_HEIGHT = const(320)
_BL = const(21)
_RST = const(17)
_DC = const(2)
_MOSI = const(13)
#_MISO = const(12)
_SCK = const(14)
_HOST = const(1) # SPI2
_LCD_CS = const(15)
_LCD_FREQ = const(40000000)
#_TOUCH_CS = const(9)
#_TOUCH_FREQ = const(1000000)
spi_bus = machine.SPI.Bus(
host=_HOST,
mosi=_MOSI,
#miso=_MISO,
sck=_SCK
)
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_FREQ,
dc=_DC,
cs=_LCD_CS,
)
import ili9341 # NOQA
import lvgl as lv # NOQA
display = ili9341.ILI9341(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
reset_pin=_RST,
reset_state=ili9341.STATE_LOW,
backlight_pin=_BL,
backlight_on_state=ili9341.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
color_byte_order=ili9341.BYTE_ORDER_BGR,
rgb565_byte_swap=True,
)
import task_handler # NOQA
import xpt2046 # NOQA
display.set_power(True)
display.init(1)
# display.set_color_inversion(True)
display.set_rotation(lv.DISPLAY_ROTATION._90)
display.set_backlight(100)
#touch_dev = machine.SPI.Device(
# spi_bus=spi_bus,
# freq=_TOUCH_FREQ,
# cs=_TOUCH_CS
#)
#indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0)
#indev.calibrate()
th = task_handler.TaskHandler()
# WiFi connection functions
def connect_wifi():
"""Connect to WiFi network"""
if not NETWORK_AVAILABLE:
print("Network module not available")
return False
try:
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
print("WiFi already connected")
print(f"IP address: {wlan.ifconfig()[0]}")
return True
print(f"Connecting to WiFi: {WIFI_SSID}")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
# Wait for connection with timeout
timeout = 10 # 10 seconds timeout
while not wlan.isconnected() and timeout > 0:
time.sleep(1)
timeout -= 1
print(".", end="")
if wlan.isconnected():
print(f"\nWiFi connected successfully!")
print(f"IP address: {wlan.ifconfig()[0]}")
return True
else:
print(f"\nWiFi connection failed!")
return False
except Exception as e:
print(f"WiFi connection error: {e}")
return False
def disconnect_wifi():
"""Disconnect from WiFi"""
if not NETWORK_AVAILABLE:
return
try:
wlan = network.WLAN(network.STA_IF)
wlan.disconnect()
wlan.active(False)
print("WiFi disconnected")
except Exception as e:
print(f"WiFi disconnect error: {e}")
# Time setting functions
def get_local_time():
"""Get time adjusted for Hong Kong timezone (UTC+8)"""
# Get UTC time
utc_time = time.time()
# Add timezone offset (8 hours = 8 * 3600 seconds)
local_timestamp = utc_time + (TIMEZONE_OFFSET * 3600)
# Convert to local time structure
return time.localtime(local_timestamp)
def sync_time_ntp():
"""Try to sync time with NTP server (requires WiFi)"""
if not NTP_AVAILABLE:
print("NTP not available")
return False
if not NETWORK_AVAILABLE:
print("Network not available")
return False
try:
# Check if WiFi is connected
wlan = network.WLAN(network.STA_IF)
if wlan.isconnected():
print("Syncing time with NTP server...")
ntptime.settime()
print(f"Time synced successfully! (UTC time will be converted to {TIMEZONE_NAME})")
return True
else:
print("WiFi not connected, cannot sync NTP time")
return False
except Exception as e:
print(f"NTP sync failed: {e}")
return False
def set_manual_time():
"""Set time manually - modify the values as needed"""
# Format: (year, month, day, weekday, hour, minute, second, microsecond)
rtc = machine.RTC()
# Set to July 4, 2025, 14:30:00 Hong Kong time
# Note: This sets the RTC to UTC time, but we'll display Hong Kong time
# So if we want to display 14:30 HK time, we set RTC to 06:30 UTC
utc_hour = 14 - TIMEZONE_OFFSET # Convert HK time to UTC
if utc_hour < 0:
utc_hour += 24
rtc.datetime((2025, 7, 4, 5, utc_hour, 30, 0, 0))
print(f"Manual time set to: 2025-07-04 14:30:00 {TIMEZONE_NAME} time")
def setup_time():
"""Setup the system time"""
print("Setting up time...")
# First try to connect to WiFi
if connect_wifi():
# If WiFi connected, try NTP sync
if sync_time_ntp():
print(f"Time setup complete via NTP (displaying {TIMEZONE_NAME} time)")
return
# If WiFi or NTP failed, use manual time
print("Using manual time setting")
set_manual_time()
# Print current time to verify (Hong Kong time)
current = get_local_time()
print(f"Current {TIMEZONE_NAME} time: {current[0]}-{current[1]:02d}-{current[2]:02d} {current[3]:02d}:{current[4]:02d}:{current[5]:02d}")
# Initialize time
setup_time()
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xFFFFFF), 0) # White background
#btnm = lv.buttonmatrix(scrn)
#btnm.add_event_cb(lambda e: btnm_event_handler(e,scrn),lv.EVENT.VALUE_CHANGED, None)
#btnm.set_size(230,120)
#btnm.align(1,5,5)
# SemiBlock label at the top
semiblock_label = lv.label(scrn)
semiblock_label.set_text("SemiBlock")
semiblock_label.align(lv.ALIGN.TOP_MID, -45, 20)
semiblock_label.set_style_text_color(lv.color_hex(0xFF80C0), 0) # Pinkly blue color
semiblock_label.set_style_transform_scale(600, 0) # Scale text to 200% (2x bigger)
# Digital clock display - large font
clock_label = lv.label(scrn)
clock_label.set_text("00:00:00")
clock_label.align(lv.ALIGN.LEFT_MID, 50, -15)
clock_label.set_style_transform_scale(500, 0) # Scale text to 300% (3x bigger)
clock_label.set_style_text_color(lv.color_hex(0x000000), 0) # Black color
# Date display
date_label = lv.label(scrn)
date_label.set_text("2025-01-01")
date_label.align(lv.ALIGN.LEFT_MID, 50, 30)
date_label.set_style_transform_scale(500, 0)
date_label.set_style_text_color(lv.color_hex(0x0000FF), 0) # Blue color
# Day of week display
day_label = lv.label(scrn)
day_label.set_text("Monday")
day_label.align(lv.ALIGN.LEFT_MID, 50, 75)
day_label.set_style_transform_scale(500, 0)
day_label.set_style_text_color(lv.color_hex(0x0000FF), 0) # Blue color
# Clock frame/border
clock_frame = lv.obj(scrn)
clock_frame.set_size(280, 140)
clock_frame.align(lv.ALIGN.CENTER, 0, 40)
clock_frame.set_style_border_width(2, 0)
clock_frame.set_style_border_color(lv.color_hex(0xFF80C0), 0) # Gray border
clock_frame.set_style_bg_opa(lv.OPA.TRANSP, 0) # Transparent background
clock_frame.set_style_radius(10, 0) # Rounded corners
# Don't move labels to frame - keep them on main screen for proper updates
# Don't move labels to frame - keep them on main screen for proper updates
def update_clock():
"""Update the clock display with current time in Hong Kong timezone"""
# Use Hong Kong local time instead of system time
current_time = get_local_time()
# Format time as HH:MM:SS
time_str = "{:02d}:{:02d}:{:02d}".format(
current_time[3], # hour
current_time[4], # minute
current_time[5] # second
)
# Format date as YYYY-MM-DD
date_str = "{:04d}-{:02d}-{:02d}".format(
current_time[0], # year
current_time[1], # month
current_time[2] # day
)
# Get day of week
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
day_str = days[current_time[6]]
clock_label.set_text(time_str)
date_label.set_text(date_str)
day_label.set_text(day_str)
# Debug print to verify time is being read correctly
print(f"{TIMEZONE_NAME} Time: {time_str}, Date: {date_str}, Day: {day_str}")
# Create a timer to update the clock every second
clock_timer = lv.timer_create(lambda timer: update_clock(), 1000, None)
# Initial clock update
update_clock()
Cheap Yellow Display Pins
Connector types
Connector | Type | Note |
---|---|---|
P1 | 4P 1.25mm JST | Serial |
P3 | 4P 1.25mm JST | GPIO |
P4 | 2P 1.25mm JST | Speaker |
CN1 | 4P 1.25mm JST | GPIO (I2C) |
What pins are available on the CYD?
There are 3 easily accessible GPIO pins
Pin | Location | Note |
---|---|---|
IO35 | P3 JST connector | Input only pin, no internal pull-ups available |
IO22 | P3 and CN1 JST connector | |
IO27 | CN1 JST connector |
If you need more than that, you need to start taking them from something else. An SD Card sniffer like mentioned in the Add-ons is probably the next easiest.
After that you're probably de-soldering something!
Broken Out Pins
There are three 4P 1.25mm JST connectors on the board
P3
Pin | Use | Note |
---|---|---|
GND | ||
IO35 | Input only pin, no internal pull-ups available | |
IO22 | Also on the CN1 connector | |
IO21 | Used for the TFT Backlight, so not really usable |
CN1
This is a great candidate for I2C devices
Pin | Use | Note |
---|---|---|
GND | ||
IO22 | Also on P3 connector | |
IO27 | ||
3.3V |
P1
Pin | Use | Note |
---|---|---|
VIN | ||
IO1(?) | TX | Maybe possible to use as a GPIO? |
IO3(?) | RX | Maybe possible to use as a GPIO? |
GND |
Buttons
The CYD has two buttons, reset and boot.
Pin | Use | Note |
---|---|---|
IO0 | BOOT | Can be used as an input in sketches |
Speaker
The speaker connector is a 2P 1.25mm JST connector that is connected to the amplifier, so not usable as GPIO at the speaker connector
Pin | Use | Note |
---|---|---|
IO26 | Connected to amp | i2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN); |
RGB LED
If your project requires additional pins to what is available elsewhere, this might be a good candidate to sacrifice.
Note: LEDs are "active low", meaning HIGH == off, LOW == on
Pin | Use | Note |
---|---|---|
IO4 | Red LED | |
IO16 | Green LED | |
IO17 | Blue LED |
SD Card
Uses the VSPI Pin names are predefined in SPI.h
Pin | Use | Note |
---|---|---|
IO5 | SS | |
IO18 | SCK | |
IO19 | MISO | |
IO23 | MOSI |
Touch Screen
Pin | Use | Note |
---|---|---|
IO25 | XPT2046_CLK | |
IO32 | XPT2046_MOSI | |
IO33 | XPT2046_CS | |
IO36 | XPT2046_IRQ | |
IO39 | XPT2046_MISO |
LDR (Light Sensor)
Pin | Use | Note |
---|---|---|
IO34 |
Display
Uses the HSPI
Pin | Use | Note |
---|---|---|
IO2 | TFT_RS | AKA: TFT_DC |
IO12 | TFT_SDO | AKA: TFT_MISO |
IO13 | TFT_SDI | AKA: TFT_MOSI |
IO14 | TFT_SCK | |
IO15 | TFT_CS | |
IO21 | TFT_BL | Also on P3 connector, for some reason |
Test points
Pad | Use | Note |
---|---|---|
S1 | GND | near USB-SERIAL |
S2 | 3.3v | for ESP32 |
S3 | 5v | near USB-SERIAL |
S4 | GND | for ESP32 |
S5 | 3.3v | for TFT |
JP0 (pad nearest USB socket) | 5v | TFT LDO |
JP0 | 3.3v | TFT LDO |
JP3 (pad nearest USB socket) | 5v | ESP32 LDO |
JP3 | 3.3v | ESP32 LDO |
More examples on https://github.com/quantrpeter/cheap-yellow-display-micropython-lvgl-example
Step 1: create Dockerfile
FROM ubuntu:22.04
# Install xclock and necessary X11 libraries
RUN apt-get update && apt-get install -y \
x11-apps \
&& rm -rf /var/lib/apt/lists/*
# Set the DISPLAY environment variable
ENV DISPLAY=host.docker.internal:0
# Run xclock
CMD ["xclock"]
Step 2: Build the image
docker build -t xclock-ubuntu .
Step 3: Set XQuartz and restart it

Step 4: Run "xhost + 127.0.0.1", and this "xhost +localhost" wont work
Step 5: Run
docker run --rm \
-e DISPLAY=host.docker.internal:0 \
xclock-ubuntu

Another way
docker run --name test -it ubuntu
apt-get update
apt-get install x11-apps
export DISPLAY=host.docker.internal:0
xclock
Official Tutorial From TaoBao Factory : https://github.com/quantrpeter/2.8inch_ESP32-2432S028R
Remark: use offset 0x1000 to write the firmware and have to erase flash first, so:
Download micropython firmware here https://micropython.org/download/ESP32_GENERIC/

1. esptool.py erase_flash
2. esptool.py --baud 460800 write_flash 0x1000 ESP32_GENERIC-20250415-v1.25.0.bin
Download All Files in below links, or by the attachment in this posts
ILI9341 Display Driver
Download ili9341.py from: github.com/rdagger/micropython-ili9341
XPT2046 Touchscreen Driver
Download xpt2046.py from: github.com/rdagger/micropython-ili9341
Font Library
For custom fonts, download xglcd_font.py and font files (e.g., Unispace12x24.c) from: github.com/rdagger/micropython-ili9341
CYD-Specific Library
For simplified control, use the cydr.py library from: github.com/jtobinart/MicroPython_CYD_ESP32-2432S028R
https://github.com/rdagger/micropython-ili9341/blob/master/fonts/Unispace12x24.c
Run it
mpremote cp cydr.py :cydr.py
mpremote cp ili9341.py :ili9341.py
mpremote cp xglcd_font.py :xglcd_font.py
mpremote cp xpt2046.py :xpt2046.py
mpremote mkdir fonts
mpremote cp Unispace12x24.c :fonts/Unispace12x24.c
mpremote cp main.py :main.py
mpremote exec "import main"

Below example only work with official micropython firmware. lv-micropython firmware won't work.
Example 1 : Animation.py
from machine import Pin, SPI
from ili9341 import Display, color565
import time
# Initialize SPI for ILI9341 display
display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15), width=320, height=240, rotation=90)
# Turn on backlight
backlight = Pin(21, Pin.OUT)
backlight.on()
# Clear display (white background)
display.clear(color565(255, 255, 255))
# Animation parameters
rect_width = 30
rect_height = 30
x = 0
y = 0
dx = 6 # Increased speed
dy = 6 # Increased speed
rect_color = color565(255, 0, 0) # Red rectangle
bg_color = color565(255, 255, 255) # White background
# Animation loop
while True:
# Clear previous rectangle (minimize cleared area)
display.fill_rectangle(x, y, rect_width, rect_height, bg_color)
# Update position
x += dx
y += dy
# Bounce off edges
if x <= 0 or x >= 320 - rect_width:
dx = -dx
if y <= 0 or y >= 240 - rect_height:
dy = -dy
# Draw new rectangle
display.fill_rectangle(x, y, rect_width, rect_height, rect_color)
# Reduced delay for faster animation
time.sleep(0.02) # ~50 FPS
Example 2 : Animation_complex.py
from machine import Pin, SPI
from ili9341 import Display, color565
from xglcd_font import XglcdFont
import time
import math
# Initialize SPI and ILI9341 display
display_spi = SPI(1, baudrate=60000000, sck=Pin(14), mosi=Pin(13))
display = Display(display_spi, dc=Pin(2), cs=Pin(15), rst=Pin(15), width=320, height=240, rotation=90)
# Turn on backlight
backlight = Pin(21, Pin.OUT)
backlight.on()
# Clear display (black background for contrast)
display.clear(color565(0, 0, 0))
# Load font
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)
# Shape parameters (2 rectangles)
shapes = [
{'type': 'rect', 'x': 50, 'y': 50, 'w': 25, 'h': 25, 'dx': 5, 'dy': 3}, # Rectangle 1
{'type': 'rect', 'x': 100, 'y': 80, 'w': 20, 'h': 20, 'dx': -4, 'dy': 4} # Rectangle 2
]
# Text parameters
text = "CYD Demo!"
text_x = 0
text_dx = 3
text_color = color565(255, 255, 255) # White text
bg_color = color565(0, 0, 0) # Black background
# Color transition parameters
color_phase = 0
color_cycle_speed = 0.1 # Faster color change
# Animation loop
while True:
# Clear previous text
display.fill_rectangle(text_x, 0, unispace.measure_text(text), 24, bg_color)
# Update text position
text_x += text_dx
if text_x <= 0 or text_x >= 320 - unispace.measure_text(text):
text_dx = -text_dx
# Draw text
display.draw_text(text_x, 0, text, unispace, text_color)
# Update color phase (simplified)
color_phase = (color_phase + color_cycle_speed) % (2 * math.pi)
r = int(127 * (1 + math.sin(color_phase)))
g = int(127 * (1 + math.cos(color_phase)))
b = 128 # Fixed blue for simplicity
shape_color = color565(r, g, b)
# Update and draw shapes
for shape in shapes:
# Clear previous shape
display.fill_rectangle(shape['x'], shape['y'], shape['w'], shape['h'], bg_color)
# Update position
shape['x'] += shape['dx']
shape['y'] += shape['dy']
# Bounce off edges (avoid text area)
if shape['x'] <= 0 or shape['x'] >= 320 - shape['w']:
shape['dx'] = -shape['dx']
if shape['y'] <= 24 or shape['y'] >= 240 - shape['h']:
shape['dy'] = -shape['dy']
# Draw new shape
display.fill_rectangle(shape['x'], shape['y'], shape['w'], shape['h'], shape_color)
# Delay for ~66 FPS
time.sleep(0.015)
In mac, sometime arduino unable to write the program to cheap yellow display esp32, you can type the command manually


If you have this problem:

run: sigrok-cli --loglevel 5 -d fx2lafw --scan , it said failed to find the firmware file

So build this project and restart terminal, then you will success
git clone git://sigrok.org/sigrok-firmware-fx2lafw
cd sigrok-firmware-fx2lafw
./autogen.sh
./configure
make
sudo make install

sigrok-cli --driver fx2lafw:conn=20.59 -g Logic --samples 80 -O ascii:width=80:charset='_"\/'
It has no SoftI2C, so change
display = ssd1306.SSD1306_I2C(128, 64, SoftI2C(sda=Pin(20), scl=Pin(21)))
to
import time
from machine import Pin, I2C, SoftI2C
import ssd1306
i2c = I2C(1) # Create I2C object
display = ssd1306.SSD1306_I2C(128, 64, i2c) # Pass I2C object
display.fill(0)
display.text("Hello, World", 0, 0)
display.show()
time.sleep(100)

- git clone https://github.com/micropython/micropython.git
- cd micropython
- docker run -it -v .:/micropython --name micropython ubuntu
- apt-get update
- apt-get install -y gcc g++ make automake python3 git gcc-arm-none-eabi
- cd /micropython/mpy-cross
- make -j

- cd ../ports/stm32
- make BOARD=WEACT_F411_BLACKPILL submodules -j
- make BOARD=WEACT_F411_BLACKPILL -j

- exit docker
- in mac: brew install stlink
- cd ports/stm32
- st-info --probe
- st-flash erase
- st-flash --format ihex write build-WEACT_F411_BLACKPILL/firmware.hex
using st-flash is better then dfu-util because we don't need to put the board into dfu mode

- pip install mpremote
- mpremote fs ls
- mpremote
- ctrl+] to exit
Micrpython testing example
from machine import Pin
p0 = Pin("C13", Pin.OUT)
p0.value(1)
p0.value(0)
p0.value(1)
p0.value(0)

IT IS SSH1106,NOT SSD1306
#
# MicroPython SH1106 OLED driver, I2C and SPI interfaces
#
# The MIT License (MIT)
#
# Copyright (c) 2016 Radomir Dopieralski (@deshipu),
# 2017-2021 Robert Hammelrath (@robert-hh)
# 2021 Tim Weber (@scy)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Sample code sections for ESP8266 pin assignments
# ------------ SPI ------------------
# Pin Map SPI
# - 3v - xxxxxx - Vcc
# - G - xxxxxx - Gnd
# - D7 - GPIO 13 - Din / MOSI fixed
# - D5 - GPIO 14 - Clk / Sck fixed
# - D8 - GPIO 4 - CS (optional, if the only connected device)
# - D2 - GPIO 5 - D/C
# - D1 - GPIO 2 - Res
#
# for CS, D/C and Res other ports may be chosen.
#
# from machine import Pin, SPI
# import sh1106
# spi = SPI(1, baudrate=1000000)
# display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4))
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
#
# --------------- I2C ------------------
#
# Pin Map I2C
# - 3v - xxxxxx - Vcc
# - G - xxxxxx - Gnd
# - D2 - GPIO 5 - SCK / SCL
# - D1 - GPIO 4 - DIN / SDA
# - D0 - GPIO 16 - Res
# - G - xxxxxx CS
# - G - xxxxxx D/C
#
# Pin's for I2C can be set almost arbitrary
#
# from machine import Pin, I2C
# import sh1106
#
# i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
# display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
# display.sleep(False)
# display.fill(0)
# display.text('Testing 1', 0, 0, 1)
# display.show()
from micropython import const
import utime as time
import framebuf
# a few register definitions
_SET_CONTRAST = const(0x81)
_SET_NORM_INV = const(0xa6)
_SET_DISP = const(0xae)
_SET_SCAN_DIR = const(0xc0)
_SET_SEG_REMAP = const(0xa0)
_LOW_COLUMN_ADDRESS = const(0x00)
_HIGH_COLUMN_ADDRESS = const(0x10)
_SET_PAGE_ADDRESS = const(0xB0)
class SH1106(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc, rotate=0):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.flip_en = rotate == 180 or rotate == 270
self.rotate90 = rotate == 90 or rotate == 270
self.pages = self.height // 8
self.bufsize = self.pages * self.width
self.renderbuf = bytearray(self.bufsize)
self.pages_to_update = 0
self.delay = 0
if self.rotate90:
self.displaybuf = bytearray(self.bufsize)
# HMSB is required to keep the bit order in the render buffer
# compatible with byte-for-byte remapping to the display buffer,
# which is in VLSB. Else we'd have to copy bit-by-bit!
super().__init__(self.renderbuf, self.height, self.width,
framebuf.MONO_HMSB)
else:
self.displaybuf = self.renderbuf
super().__init__(self.renderbuf, self.width, self.height,
framebuf.MONO_VLSB)
# flip() was called rotate() once, provide backwards compatibility.
self.rotate = self.flip
self.init_display()
# abstractmethod
def write_cmd(self, *args, **kwargs):
raise NotImplementedError
# abstractmethod
def write_data(self, *args, **kwargs):
raise NotImplementedError
def init_display(self):
self.reset()
self.fill(0)
self.show()
self.poweron()
# rotate90 requires a call to flip() for setting up.
self.flip(self.flip_en)
def poweroff(self):
self.write_cmd(_SET_DISP | 0x00)
def poweron(self):
self.write_cmd(_SET_DISP | 0x01)
if self.delay:
time.sleep_ms(self.delay)
def flip(self, flag=None, update=True):
if flag is None:
flag = not self.flip_en
mir_v = flag ^ self.rotate90
mir_h = flag
self.write_cmd(_SET_SEG_REMAP | (0x01 if mir_v else 0x00))
self.write_cmd(_SET_SCAN_DIR | (0x08 if mir_h else 0x00))
self.flip_en = flag
if update:
self.show(True) # full update
def sleep(self, value):
self.write_cmd(_SET_DISP | (not value))
def contrast(self, contrast):
self.write_cmd(_SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(_SET_NORM_INV | (invert & 1))
def show(self, full_update = False):
# self.* lookups in loops take significant time (~4fps).
(w, p, db, rb) = (self.width, self.pages,
self.displaybuf, self.renderbuf)
if self.rotate90:
for i in range(self.bufsize):
db[w * (i % p) + (i // p)] = rb[i]
if full_update:
pages_to_update = (1 << self.pages) - 1
else:
pages_to_update = self.pages_to_update
#print("Updating pages: {:08b}".format(pages_to_update))
for page in range(self.pages):
if (pages_to_update & (1 << page)):
self.write_cmd(_SET_PAGE_ADDRESS | page)
self.write_cmd(_LOW_COLUMN_ADDRESS | 2)
self.write_cmd(_HIGH_COLUMN_ADDRESS | 0)
self.write_data(db[(w*page):(w*page+w)])
self.pages_to_update = 0
def pixel(self, x, y, color=None):
if color is None:
return super().pixel(x, y)
else:
super().pixel(x, y , color)
page = y // 8
self.pages_to_update |= 1 << page
def text(self, text, x, y, color=1):
super().text(text, x, y, color)
self.register_updates(y, y+7)
def line(self, x0, y0, x1, y1, color):
super().line(x0, y0, x1, y1, color)
self.register_updates(y0, y1)
def hline(self, x, y, w, color):
super().hline(x, y, w, color)
self.register_updates(y)
def vline(self, x, y, h, color):
super().vline(x, y, h, color)
self.register_updates(y, y+h-1)
def fill(self, color):
super().fill(color)
self.pages_to_update = (1 << self.pages) - 1
def blit(self, fbuf, x, y, key=-1, palette=None):
super().blit(fbuf, x, y, key, palette)
self.register_updates(y, y+self.height)
def scroll(self, x, y):
# my understanding is that scroll() does a full screen change
super().scroll(x, y)
self.pages_to_update = (1 << self.pages) - 1
def fill_rect(self, x, y, w, h, color):
super().fill_rect(x, y, w, h, color)
self.register_updates(y, y+h-1)
def rect(self, x, y, w, h, color):
super().rect(x, y, w, h, color)
self.register_updates(y, y+h-1)
def ellipse(self, x, y, xr, yr, color):
super().ellipse(x, y, xr, yr, color)
self.register_updates(y-yr, y+yr-1)
def register_updates(self, y0, y1=None):
# this function takes the top and optional bottom address of the changes made
# and updates the pages_to_change list with any changed pages
# that are not yet on the list
start_page = max(0, y0 // 8)
end_page = max(0, y1 // 8) if y1 is not None else start_page
# rearrange start_page and end_page if coordinates were given from bottom to top
if start_page > end_page:
start_page, end_page = end_page, start_page
for page in range(start_page, end_page+1):
self.pages_to_update |= 1 << page
def reset(self, res=None):
if res is not None:
res(1)
time.sleep_ms(1)
res(0)
time.sleep_ms(20)
res(1)
time.sleep_ms(20)
class SH1106_I2C(SH1106):
def __init__(self, width, height, i2c, res=None, addr=0x3c,
rotate=0, external_vcc=False, delay=0):
self.i2c = i2c
self.addr = addr
self.res = res
self.temp = bytearray(2)
self.delay = delay
if res is not None:
res.init(res.OUT, value=1)
super().__init__(width, height, external_vcc, rotate)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.i2c.writeto(self.addr, b'\x40'+buf)
def reset(self,res=None):
super().reset(self.res)
class SH1106_SPI(SH1106):
def __init__(self, width, height, spi, dc, res=None, cs=None,
rotate=0, external_vcc=False, delay=0):
dc.init(dc.OUT, value=0)
if res is not None:
res.init(res.OUT, value=0)
if cs is not None:
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
self.delay = delay
super().__init__(width, height, external_vcc, rotate)
def write_cmd(self, cmd):
if self.cs is not None:
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
else:
self.dc(0)
self.spi.write(bytearray([cmd]))
def write_data(self, buf):
if self.cs is not None:
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
else:
self.dc(1)
self.spi.write(buf)
def reset(self, res=None):
super().reset(self.res)
from machine import Pin, I2C
i2c = I2C(scl=Pin(21), sda=Pin(20), freq=400000)
display = SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
display.sleep(False)
display.fill(0)
display.rotate(180)
display.text("Shatin is "+str(123)+"C", 0, 0)
display.show()

#include <Arduino.h>
int freq = 2000; // frequency
int channel = 0; // aisle
int resolution = 8; // Resolution
const int led = 4;
void setup()
{
//Initialize GPIO, turn off tricolor light
pinMode(4, OUTPUT);
pinMode(17, OUTPUT);
pinMode(16, OUTPUT);
digitalWrite(4, 0);
digitalWrite(16, 0);
digitalWrite(17, 0);
ledcAttach(channel, freq, resolution); // set channel
//ledcAttachPin(led, channel); // Connect the channel to the corresponding pin
}
void loop()
{
digitalWrite(4, 0);
digitalWrite(16, 1);
digitalWrite(17, 1);
delay(500);
digitalWrite(4, 1);
digitalWrite(16, 0);
digitalWrite(17, 1);
delay(500);
digitalWrite(4, 1);
digitalWrite(16, 1);
digitalWrite(17, 0);
delay(500);
digitalWrite(4, 1);
digitalWrite(16, 1);
digitalWrite(17, 1);
delay(500);
}

(Set to 115200, otherwise upload will fail)
