U8g2图形库

简介

U8g2 是一个用于嵌入式设备的简易图形库,可以在多种 OLED 和 LCD 屏幕上,支持包括 SSD1306 等多种类型的底层驱动,并可以很方便地移植到 Arduino 、树莓派、NodeMCU 和 ARM 上。

U8g2 库同时包含了 U8x8 绘图库,两者的区别为:

  • U8g2 包含各种简单及复杂图形的绘制,并支持各种形式的字体,但需要占用一定单片机的内存作为绘图缓存
  • U8x8 只包含简单的显示文本功能,且只支持简单、定宽的字体。它直接绘制图形,没有缓存功能

U8g2 库的 GitHub 地址为:https://github.com/olikraus/u8g2 ,可以从中获取到源码与文档帮助。

移植

本次以将 U8g2 移植到 STM32 单片机与 SSD1306 通过 I2C 驱动的 128x64 OLED 为例,介绍移植的方法。不同单片机和驱动的移植可以参考这一过程,也可以参考 U8g2 的官方移植教程 https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform

首先下载或克隆 U8g2 的源码,这里主要是使用 C 语言编写,所以只需要用到 csrc 目录下的文件。

下载完成后,将 csrc 目录拷贝或移动到工程目录里,并重命名为合适的目录名例如 u8g2lib

删除无用内容

接下来,需要删除一些无用的代码,并添加底层驱动的代码。

U8g2 的源码为了支持多种设备驱动,包含了许多兼容性的代码。首先,类似 u8x8_d_xxx.c 命名的文件中包含 U8x8 的驱动兼容,文件名包括驱动的型号和屏幕分辨率,因此需要删除无用的驱动文件,只保留当前设备的驱动。例如,本次使用的是 128x64 的 SSD1306 屏幕,那么只需要保留 u8x8_d_ssd1306_128x64_noname.c 文件,删除其它类似的文件即可。U8g2 支持的所有屏幕驱动可以在 https://github.com/olikraus/u8g2/wiki/u8g2setupc 找到。

同时还需要精简 u8g2_d_setup.cu8g2_d_memory.c 中 U8g2 提供的驱动兼容。

u8g2_d_setup.c 中,只需要保留 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 这一个函数即可。注意,该文件内有几个命名类似的函数:命名中无 i2c 的是 SPI 接口驱动的函数,需要根据接口选择;以 1 结尾的函数代表使用的缓存空间为 128 字节,以 2 结尾的函数代表使用的缓存为 256字节,类似以 f 结尾的函数代表使用的缓存为 1024 字节。

u8g2_d_memory.c 文件也是同理,它需要根据 u8g2_d_setup.c 中的调用情况决定用到哪些函数。由于 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 函数只用到 u8g2_m_16_8_f() 这一个函数,因此只需要保留它,其余函数全部删除即可。

还有一处必要的精简是字体文件 u8x8_fonts.cu8g2_fonts.c ,尤其是 u8g2_fonts.c ,该文件提供了包括汉字在内的几万个文字的多种字体,仅源文件就有 30MB ,编译后占据的内存非常大。

字体类型的变量非常多,建议先复制一个备份后将所有变量删除,之后视情况再添加字体。字体变量的命名大致遵循以下规则:

<prefix> '_' <name> '_' <purpose> <charset>

其中:

  • <prefix> 前缀基本上以 u8g2 开头;
  • <name> 字体名,其中可能包含字符大小
  • 各种 <purpose> 含义如下表所示:
名称 描述
t 透明字体形式
h 所有字符等高
m monospace 字体(等宽字体)
8 每一个字符都是 8x8 大小的
  • <charset> 是字体支持的字符集,如下表所示:
名称 描述
f 只包含单字节字符
r 只包含 ASCII 范围为 32~127 的字符
u 只包含 ASCII 范围为 32~95 的字符,即不包括小写英文
n 只包含数字及一些特殊用途字符
... 还包括许多自定义的字符集,例如有一些结尾带 gb2312 或 Chinese 的字体名就包括中文

一般建议只保留需要的字体即可。

添加回调函数

U8g2 已经包含了 SSD1306 的驱动,只需要添加一个函数 u8x8_gpio_and_delay() 用于模拟时序即可。官方文件给出了一个函数的编写模板为:

uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch (msg) {
case U8X8_MSG_GPIO_AND_DELAY_INIT: // called once during init phase of u8g2/u8x8
break; // can be used to setup pins
case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second
break;
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
break;
/* and many other cases */
case U8X8_MSG_GPIO_MENU_HOME:
u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}

以下是一个写法示例:

uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch (msg) {
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
__NOP();
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
for (uint16_t n = 0; n < 320; n++)
__NOP();
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
delay_ms(1);
break;
case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
delay_us(5);
break; // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
arg_int ? GPIO_SetBits(GPIO_B, GPIO_Pin_6) : GPIO_ResetBits(GPIO_B, GPIO_Pin_6);
break; // arg_int=1: Input dir with pullup high for I2C clock pin
case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin
arg_int ? GPIO_SetBits(GPIO_B, GPIO_Pin_7) : GPIO_ResetBits(GPIO_B, GPIO_Pin_7);
break; // arg_int=1: Input dir with pullup high for I2C data pin
case U8X8_MSG_GPIO_MENU_SELECT:
u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_NEXT:
u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_PREV:
u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_HOME:
u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}

如果使用的引脚不是 PB6 和 PB7 ,注意在对应的位置修改;如果是使用硬件 I2C 的方式,那么可以不需要模拟时序,但是需要编写硬件驱动函数。在结尾处,会给出一个基于标准库的硬件移植方法。

最后,不要忘记了初始化 I2C 对应的 GPIO 引脚。

U8g2简单使用

U8g2 的初始化可以参考如下步骤:

void u8g2_Init(u8g2_t *u8g2) {
u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // 初始化 u8g2 结构体
u8g2_InitDisplay(u8g2); // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
u8g2_SetPowerSave(u8g2, 0); // 打开显示器
u8g2_ClearBuffer(u8g2);
}

这里需要调用之前保留的 u8g2_Setup_ssd1306_128x64_noname_f() 函数,该函数的4个参数,其含义为:

  • u8g2 :需要配置的 U8g2 结构体
  • rotation :配置屏幕是否要旋转,默认使用 U8G2_R0 即可
  • byte_cb :传输字节的方式,这里使用软件 I2C 驱动,因此使用 U8g2 源码提供的 u8x8_byte_sw_i2c() 函数。如果是硬件 I2C 的话,可以参照编写自己的函数
  • gpio_and_delay_cb :提供给软件模拟 I2C 的 GPIO 输出和延时,使用之前编写的配置函数 u8x8_gpio_and_delay()

如果需要显示字符串,需要提前调用以下函数设置字体:

void u8g2_SetFont(u8g2_t *u8g2, const uint8_t *font);

U8g2 的绘制方式有 2 种,每种都有不同的特点。

首先是全屏缓存模式(Full screen buffer mode),它的特点是绘制速度快,并且所有的绘制方法都可以使用。但是这种模式需要大量的 RAM 空间,因此使用需要用到缓存为 1024 字节的初始化函数(函数名以 f 结尾)。

这种绘图的方式首先需要清除缓冲区,调用绘图 API 后绘制的内容会保留在缓存内,需要手动发送缓存的内容到屏幕上:

u8g2_t u8g2;
u8g2_ClearBuffer(&u8g2);
/* Draw Something */
u8g2_SendBuffer(&u8g2);

第二种是分页模式(Page mode),它同样可以使用所有的绘制方法,但绘制速度较慢,不过占用的 RAM 空间也少,可以使用 128 或 256 字节的缓存(函数名以 1 和 2 结尾)。

这种绘图的方式首先创建第一页,然后在一个 do...while 循环内部绘制图形,不断判断是否到达下一页,如果到达了就自动刷新缓存:

u8g2_FirstPage(&u8g2);
do {
/* Draw Something */
} while (u8g2_NextPage(&u8g2));

可以认为分页模式是一块一块绘制的。

还可以使用 U8x8 的绘图模式,这种情况下需要使用 U8x8 提供的结构体以及一系列函数,这里不再说明。

绘图API

完整的 API 参考可以参见官方文档 https://github.com/olikraus/u8g2/wiki/u8g2reference/ ,里面不仅有 API 的介绍,还有绘制效果的图片演示。

U8g2 的坐标系和绝大多数 GUI 库一样,原点在左上角,(x, y) 往右下递增,坐标的单位为像素。

简单图形绘制

void u8g2_DrawPixel(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y);
void u8g2_DrawHLine(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t len);
void u8g2_DrawVLine(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t len);
void u8g2_DrawLine(u8g2_t *u8g2, u8g2_uint_t x1, u8g2_uint_t y1, u8g2_uint_t x2, u8g2_uint_t y2);

分别用于绘制像素点、根据左上角顶点 (x, y) 与长度 len 绘制水平线与垂直线,以及绘制两点之间的线段。

void u8g2_DrawFrame(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h);
void u8g2_DrawBox(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h);

根据左上角的 (x, y) 坐标与宽 wh 绘制空心与实心矩形。

void u8g2_DrawRBox(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r);
void u8g2_DrawRFrame(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r);

绘制实行与空心圆角矩形,多了一个参数圆角半径 r

void u8g2_DrawCircle(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t option);
void u8g2_DrawDisc(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t option);

根据圆心 (x0, y0) 绘制直径为 rad ×2+1 的空心圆和实心圆。

option 为圆的部分选项,此参数可控制绘制圆弧:

取值 结果
U8G_DRAW_ALL 整个圆弧
U8G2_DRAW_UPPER_RIGHT 右上部分的圆弧
U8G2_DRAW_UPPER_LEFT 左上部分的圆弧
U8G2_DRAW_LOWER_LEFT 左下部分的圆弧
U8G2_DRAW_LOWER_RIGHT 右下部分的圆弧

还可以使用按位或运算符 | 连接几个部分。

void u8g2_DrawEllipse(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t option);
void u8g2_DrawFilledEllipse(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t option);

根据圆心 (x0, y0) 和水平半径 rx 、竖直半径 ry 绘制空心和实心椭圆。

void u8g2_DrawTriangle(u8g2_t *u8g2, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);

根据三个点绘制实心三角形(空心三角形可以使用直线达到类似效果)。

void u8g2_DrawXBM(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap);

在图形左上角 (x, y) 根据宽 wh 绘制 XBM 格式的位图。可以使用 https://tools.clz.me/image-to-bitmap-array 工具将一般图片转换为位图代码。

和 Bitmap 有关的函数还有一个:

void u8g2_SetBitmapMode(u8g2_t *u8g2, uint8_t is_transparent);

该函数用于设置 Bitmap 是否透明。

字符显示

为了显示字符串,首先要设置字体。调用以下函数可以提前设置字体:

void u8g2_SetFont(u8g2_t *u8g2, const uint8_t *font);
void u8g2_SetFontMode(u8g2_t *u8g2, uint8_t is_transparent);

字体是一种特殊的位图,因此也可以设置是否透明。所有的字体保存在 u8g2_fonts.c 源文件中,注意在移植 U8g2 库时曾经裁剪过该文件。

u8g2_uint_t u8g2_DrawStr(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);

在左下角 (x, y) 处显示字符串。注意,这个方法只能绘制 ASCII 字符。如有需要显示 Unicode 字符,需要使用以下函数:

u8g2_uint_t u8g2_DrawGlyph(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding);
u8g2_uint_t u8g2_DrawUTF8(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);

绘制 Unicode 字符和字符串。U8g2 支持 16 位的 Unicode 字符集,因此 encoding 的范围被限制在 65535 。该函数绘制 Unicode 字符串时还需要对应的字体也支持 Unicode 字符。

注意这几个函数都有返回值,它们返回绘制成功的字符个数。

#define u8g2_GetAscent(u8g2)
#define u8g2_GetDescent(u8g2)

这两个宏定义用于获取字体基线以上和基线以下的高度。上文提到的显示字符串的函数实际上参数 y 指的是基线高度。此外注意基线以下的高度返回的是负值。

u8g2_uint_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s);
u8g2_uint_t u8g2_GetUTF8Width(u8g2_t *u8g2, const char *str);

获取当前字体下,字符串和 UTF-8 字符串的宽度,单位为像素。

void u8g2_SetFontDirection(u8g2_t *u8g2, uint8_t dir);

设置文字朝向,根据参数不同分别设置为正常朝向的顺时针旋转 dir ×90° 。

其它绘图相关API

void u8g2_SetClipWindow(u8g2_t *u8g2, u8g2_uint_t clip_x0, u8g2_uint_t clip_y0, u8g2_uint_t clip_x1, u8g2_uint_t clip_y1);

设置采集窗口大小,设置后绘制的图形只在该窗口范围内显示。设置后可以使用 u8g2_SetMaxClipWindow() 函数去掉该限制。

示例代码

以下官方示例代码可以在 OLED 上显示该库的 logo :

u8g2_t u8g2;
u8g2_FirstPage(&u8g2);
do {
u8g2_SetFontMode(&u8g2, 1);
u8g2_SetFontDirection(&u8g2, 0);
u8g2_SetFont(&u8g2, u8g2_font_inb24_mf);
u8g2_DrawStr(&u8g2, 0, 20, "U");
u8g2_SetFontDirection(&u8g2, 1);
u8g2_SetFont(&u8g2, u8g2_font_inb30_mn);
u8g2_DrawStr(&u8g2, 21, 8, "8");
u8g2_SetFontDirection(&u8g2, 0);
u8g2_SetFont(&u8g2, u8g2_font_inb24_mf);
u8g2_DrawStr(&u8g2, 51, 30, "g");
u8g2_DrawStr(&u8g2, 67, 30, "\xb2");
u8g2_DrawHLine(&u8g2, 2, 35, 47);
u8g2_DrawHLine(&u8g2, 3, 36, 47);
u8g2_DrawVLine(&u8g2, 45, 32, 12);
u8g2_DrawVLine(&u8g2, 46, 33, 12);
u8g2_SetFont(&u8g2, u8g2_font_4x6_tr);
u8g2_DrawStr(&u8g2, 1, 54, "github.com/olikraus/u8g2");
} while (u8g2_NextPage(&u8g2));

首发于:http://frozencandles.fun/archives/301

附录:使用硬件I2C移植U8g2

硬件 I2C 效率上比软件 I2C 快了非常多,因此特别适合 U8g2 这种大型 UI 框架。下面基于标准库介绍硬件 I2C 的移植方式。

如果使用硬件 I2C ,需要在调用该函数(或类似函数)时,使用自己的硬件读写函数:

void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);

首先还是需要编写一个 gpio_and_delay() 回调函数。不过由于这里是使用硬件 I2C ,因此不再需要提供 GPIO 和时序操作的支持,只需要提供一个毫秒级的延时即可:

uint8_t u8x8_gpio_and_delay_hw(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch (msg) {
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
Delay_ms(1);
break;
case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
break; // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
break; // arg_int=1: Input dir with pullup high for I2C clock pin
case U8X8_MSG_GPIO_I2C_DATA: // arg_int=0: Output low at I2C data pin
break; // arg_int=1: Input dir with pullup high for I2C data pin
case U8X8_MSG_GPIO_MENU_SELECT:
u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_NEXT:
u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_PREV:
u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
break;
case U8X8_MSG_GPIO_MENU_HOME:
u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}

如果是使用硬件 I2C ,那么需要自行编写硬件驱动函数,向 OLED 写入字节。这个函数的编写可以参考官方提供的软件驱动函数 u8x8_byte_sw_i2c() ,一个编写示例为:

uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
uint8_t* data = (uint8_t*) arg_ptr;
switch(msg) {
case U8X8_MSG_BYTE_SEND:
while( arg_int-- > 0 ) {
I2C_SendData(I2C1, *data++);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
continue;
}
break;
case U8X8_MSG_BYTE_INIT:
/* add your custom code to init i2c subsystem */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitTypeDef I2C_InitStructure = {
.I2C_Mode = I2C_Mode_I2C,
.I2C_DutyCycle = I2C_DutyCycle_2,
.I2C_OwnAddress1 = 0x10,
.I2C_Ack = I2C_Ack_Enable,
.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit,
.I2C_ClockSpeed = 400000
};
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
break;
case U8X8_MSG_BYTE_SET_DC:
/* ignored for i2c */
break;
case U8X8_MSG_BYTE_START_TRANSFER:
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
continue;
I2C_Send7bitAddress(I2C1, 0x78, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
continue;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
I2C_GenerateSTOP(I2C1, ENABLE);
break;
default:
return 0;
}
return 1;
}

从各个 case 标签可以很明白地看出一个 I2C 的读写过程:U8X8_MSG_BYTE_INIT 标签下需要初始化 I2C 外设,U8X8_MSG_BYTE_START_TRANSFER 标签产生起始信号并发出目标地址,U8X8_MSG_BYTE_SEND 标签开始发送字节,并且发送的字节存储在 *arg_ptr 参数中,arg_int 是字节的总长度( U8g2 库似乎一次不会传输多余 32 字节的信息)。最后,U8X8_MSG_BYTE_END_TRANSFER 标签处产生停止信号。

注意在使用硬件 I2C 时,GPIO 需要设置为复用开漏输出模式 GPIO_Mode_AF_OD

最后一步,用以上编写的硬件函数初始化 U8g2 驱动:

u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay_hw);

硬件移植过程完毕。

U8g2图形库与STM32移植(I2C,软件与硬件)的更多相关文章

  1. STM32的I2C特性及架构

    软件模拟协议:使用CPU直接控制通讯引脚(GPIO)的电平,产生出符合通讯协议标准的逻辑. 硬件实现协议:由STM32的I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它就会自动根据协议要 ...

  2. STM32的I2C框图详解及通讯过程

    STM32 的I2C 特性及架构 如果我们直接控制STM32 的两个GPIO 引脚,分别用作SCL 及SDA,按照上述信号的时序要求,直接像控制LED 灯那样控制引脚的输出(若是接收数据时则读取SDA ...

  3. STM32 移植 RT-Thread 标准版的 FinSH 组件

    一.移植准备 开发版STM32F10xC8T6 准备好移植RT-Thread的移植工程 没动手移植过RT-Thread的小伙伴,可以看RT-Thread移植到stm32 我这里是将控制台信息打印到串口 ...

  4. STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085和串口只发送数据不能接收数据问题

    STM32移植RT-Thread后的串口在调试助手上出现:(mq != RT_NULL) assert failed at rt_mq_recv:2085的问题讨论:http://www.rt-thr ...

  5. Hello China操作系统STM32移植指南(一)

    Hello China操作系统移植指南 首先说明一下,为了适应更多的文化背景,对Hello China操作系统的名字做了修改,修改为"Hello X",或者连接在一起,写为&quo ...

  6. I2C软件模拟协议与电容触摸控制

    I2C 与 Touch slide 最近做了一个与触摸滑条相关的测试,利用I2C通讯协议来配置触摸控制芯片的相关寄存器,读取触摸读数,并通过STM Studio动态显示触摸读数的变化过程.这个测试相对 ...

  7. 【转】hurry_liu 大神STM32移植contiki入门之一:系统介绍和开发环境搭建

    前言: 由于项目的原因,需要在LPC1788(STM32 cortex-M3)上面跑contiki. 之前没有涉及到contiki,不知其为何物.不过这个不是难事,做IT的,每每遇到新事物,都不会处理 ...

  8. STM32移植USB驱动总结

    https://blog.csdn.net/stm32_newlearner/article/details/88095944 stm32   移植usb驱动开发 单片机 STM32单片机和51单片机 ...

  9. 关于STM32的I2C硬件DMA实现

    关于STM32的I2C硬件DMA实现 网上看到很多说STM32的I2C很难用,但我觉得还是理解上的问题,STM32的I2C确实很复杂,但只要基础牢靠,并没有想象中的那么困难. 那么就先从基础说起,只说 ...

随机推荐

  1. 小程序中引入iconfont

    注释:本人喜欢 font class,  你们可以依葫芦画瓢unicode和 symbol,,下面是 font class   演示: 1.选择好图标,然后: font class 2.步骤二: 复制 ...

  2. 针对form表单赋值封装

    1 (function ($){ 2 $.fn.extend({ 3 exajax:function(url,opts,convert){ 4 var ajaxParam = { 5 url:url, ...

  3. Python使用Odoo外部api

    Odoo服务器提供一个外部API,该API由其web客户端使用,也可以被支持XML-RPC或 JSON-RPC协议的编程语言(例如:Python.PHP.Ruby和Java)使用. 使用XML-RPC ...

  4. Qt QComboBox之setEditable和currentTextChanged及其源码分析

    目录 Qt QComboBox之setEditable和currentTextChanged以及其源码分析 前言 问题的出现 问题分析 currentTextChanged信号触发 源码分析 Qt Q ...

  5. Mybatis-Dao层实现(通过代理方式)

    1.代理方式开发是主流 2.Mapper接口开发方法只需要编写Mapper接口(相当于Dao接口),然后由Mybatis根据接口创建动态代理对象 Mapper接口开发需要遵循以下规范 一一对应 Use ...

  6. spring原始注解开发-01

    我们使用xml-Bean标签的配置方式和注解做对比理解 1.创建UserDao接口以及UserDao的实现类UserDaoImpl(接口代码省略) public class UserDaoImpl i ...

  7. Servlet 3.1学习笔记

    Servlet 3.1学习笔记 参考文档 Servlet 3.1标准 什么是 Servlet ? Servlet 是基于 Java 平台的 Web 组件,由一个容器管理,能够生成动态内容. 什么是 S ...

  8. AcWing周赛43

    AcWing周赛43 题源:https://www.acwing.com/activity/content/1233/ 4314. 三元组 直接暴力做就是了,我一开始还在找规律..悲 我滴代码 #in ...

  9. Maven中央仓库地址大全

    1.默认的Maven中央仓库  在Maven安装目录下,找到:/lib/maven-model-builder-${version}.jar 打开该文件,能找到超级POM:\org\apache\ma ...

  10. nodejs使用jquery风格环境安装

    BEGIN; 1.npm install jQuery 注意:是jQuery,不是jquery! 2.npm install jsdom 注意:直接执行会安装错误,必须先指定安装版本! 解决:修改pa ...