目录
- AIR32F103(一 合宙AIR32F103CBT6开发板上手报告
- AIR32F103(二 Linux环境和LibOpenCM3项目模板
- AIR32F103(三 Linux环境基于标准外设库的项目模板
- AIR32F103(四 27倍频216MHz,CoreMark跑分测试
- AIR32F103(五 FreeRTOSv202112核心库的集成和示例代码
- AIR32F103(六 ADC,I2S,DMA和ADPCM实现的录音播放功能
- AIR32F103(七 AIR32F103CBT6/CCT6启用96K内存
- AIR32F103(八 集成Helix MP3解码库播放MP3
- AIR32F103(九 CAN总线的通信和ID过滤机制及实例
- AIR32F103(十 在无系统环境和FreeRTOS环境集成LVGL
- AIR32F103(十一 在AIR32F103上移植微雪墨水屏驱动
电子墨水屏 Electronic Paper, Digital Paper
之前在合宙上买了一片1.54寸的墨水屏一直在吃灰, 这次趁点亮的机会把AIR32F103上的驱动示例给做了。
微雪驱动库
他们维护了一个品类众多(45种的墨水屏型号列表, 在 GitHub 上有一个专门的代码仓库
微雪的这个驱动库代码质量还是不错的. 里面带了针对 RaspberryPi, Arduino 和 STM32 的驱动, STM32的这个驱动, 用的硬件是 STM32F103ZET6, 迁移到AIR32F103很方便。
EPD_1IN54
EPD_1IN54B_V2
EPD_1IN54B
EPD_1IN54C
EPD_1IN64G
EPD_2IN7_V2
EPD_2IN7
EPD_2IN7B_V2
EPD_2IN7B
EPD_2IN9_V2
EPD_2IN9
EPD_2IN9B_V3
EPD_2IN9BC
EPD_2IN9D
EPD_2IN13_V2
EPD_2IN13_V3
EPD_2IN13
EPD_2IN13B_V3
EPD_2IN13B_V4
EPD_2IN13BC
EPD_2IN13D
EPD_2IN36G
EPD_2IN66
EPD_2IN66B
EPD_3IN0G
EPD_3IN7
EPD_3IN52
EPD_4IN01F
EPD_4IN2
EPD_4IN2B_V2
EPD_4IN2BC
EPD_4IN37G
EPD_5IN65F
EPD_5IN83_V2
EPD_5IN83
EPD_5IN83B_V2
EPD_5IN83BC
EPD_7IN3F
EPD_7IN3G
EPD_7IN5_HD
EPD_7IN5_V2
EPD_7IN5
EPD_7IN5B_HD
EPD_7IN5B_V2
EPD_7IN5BC
将微雪驱动库移植到AIR32F103
添加驱动库代码
将微雪仓库导出, 需要的部分都在 STM32/STM32-F103ZET6/User 目录下, 将其中代码部分复制到AIR32F103的库目录下, 形成目录结构为
Libraries
├── AIR32F10xLib
├── CMSIS
├── Debug
├── DeviceSupport
├── EPaper
│ ├── Config # 配置文件
│ ├── e-Paper # 对应每一个型号的 .c 和 .h 文件, 驱动的核心
│ ├── Examples # 对应每一个型号的测试示例, 都实现了 EPD_test(void 这个方法
│ ├── Fonts # 字体, 5个英文字体, 2个中文字体(只是少数几个汉字
│ └── GUI # 点线面的绘制方法
因为目的是要在 GNU GCC 下使用 Makefile 编译, 所以有些地方需要优化一下
- 将目录重命名一下, e-Paper 改为 Lib
- 将 Debug.h 删除, 其内容集成到 DEV_Config.h
- 将 DEV_Config.c 和 DEV_Config.h 的公用部分(固定部分提取为 EPD_Common.c 和 EPD_Common.h, 放到 Lib 下
- 将 DEV_Config.h 中的配置部分提出来创建 EPD_Config_Template.h, 这个文件在创建项目时, 可以更名为 EPD_Config.h 放到项目目录下.
├── EPaper
│ ├── EPD_Config_Template.h
│ ├── Examples
│ ├── Fonts
│ ├── GUI
│ └── Lib
进一步将每一个型号提取为宏, 然后对 Lib 和 Examples 下的每个驱动和测试 c 文件, 增加宏判断
#ifdef EPD_1IN54
...
#endif
这样可以在 EPD_Config.h 中使用宏配置启用哪一个型号, 例如对于合宙这块1.54的屏, 只需要启用 1N54 这个宏
/**
* Uncomment the part number to enable
*/
// #define EPD_1IN02
// #define EPD_1IN54_V2
#define EPD_1IN54
// #define EPD_1IN54B_V2
// #define EPD_1IN54B
// #define EPD_1IN54C
// #define EPD_1IN64G
...
...
// #define EPD_7IN5B_V2
// #define EPD_7IN5BC
这样编译时未启用的型号, 其驱动和测试会直接跳过
修改 Makefile
# Build with Waveshare e-paper lib, y:yes, n:no
USE_EPAPER ?= y
以及对应的编译包含项, 这里使用的是修改过名称后的目录名
ifeq ($(USE_EPAPER,y
CDIRS += Libraries/EPaper/Lib \
Libraries/EPaper/Examples \
Libraries/EPaper/Fonts \
Libraries/EPaper/GUI
INCLUDES += Libraries/EPaper/Lib \
Libraries/EPaper/Examples \
Libraries/EPaper/Fonts \
Libraries/EPaper/GUI
endif
驱动墨水屏的示例项目
硬件部分
- AIR32F103CBT6, 墨水屏驱动编译完只有30多KByte, 所以用哪个型号都可以
- 合宙的1.54寸墨水屏. 如果使用其它墨水屏, 记得修改启用对应的宏
接线是典型的 SPI 接线方式, 和普通LCD一样, 但是没有背光, 增加了一个 Busy 脚
* Waveshare 1.54' E-Paper Demo
*
* AIR32 E-Paper
* - PA2 BUSY
* - PA3 CS
* - PA4 DC(Data/Command
* - PA5 SCK/SCL
* - PA6 RES
* - PA7 SI/SDA
* - GND GND
* - 3.3V VCC
软件部分
设置 EPD_Config.h
- 将 EPD_Config_Template.h 复制到项目目录下, 更名为 EPD_Config.h 并打开编辑
- 启用
EPD_1IN54
, 将其反注释 -
EPD_DEBUG
设为1
, 可以开启串口日志输出 - 定义 RESET, DC, CS, BUSY 这几个 GPIO 对应的 PORT和 PIN, 这些 PIN 脚随后需要在程序中初始化
- 定义几个关键方法的宏
/**
* Uncomment to enable the part
*/
// #define EPD_1IN02
// #define EPD_1IN54_V2
#define EPD_1IN54
// #define EPD_1IN54B_V2
// ...
// #define EPD_7IN5BC
#define EPD_DEBUG 1
/**
* e-Paper GPIO
*/
#define EPD_RST_PIN GPIOA, GPIO_Pin_6
#define EPD_DC_PIN GPIOA, GPIO_Pin_4
#define EPD_CS_PIN GPIOA, GPIO_Pin_3
#define EPD_BUSY_PIN GPIOA, GPIO_Pin_2
/**
* GPIO read and write
*/
#define EPD_Digital_Write(_pin, _value GPIO_WriteBit(_pin, _value == 0? Bit_RESET:Bit_SET
#define EPD_Digital_Read(_pin GPIO_ReadInputDataBit(_pin
#define EPD_SPI_WriteByte(_value SPI_TxRx(_value
#define EPD_Delay_ms(__xms Delay_Ms(__xms
#endif
初始化外设
因为是示例项目, 就不单独分文件了, 都添加到 main.c。
void APP_GPIO_Config(void
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure;
GPIO_SetBits(GPIOA, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_6;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure;
GPIO_SetBits(GPIOA, GPIO_Pin_2;
}
初始化SPI1, 这里用 PA5作为SCL, PA7作为SDA
void APP_SPI_Config(void
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure;
GPIO_SetBits(GPIOA,GPIO_Pin_5 | GPIO_Pin_7;
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 0;
SPI_Init(SPI1, &SPI_InitStructure;
SPI_Cmd(SPI1, ENABLE;
}
对应 EPD_Config.h 中的 EPD_SPI_WriteByte( 宏定义, 创建 SPI 的字节读写方法
uint8_t SPI_TxRx(uint8_t data
{
uint8_t retry = 0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE == RESET && ++retry;
SPI_I2S_SendData(SPI1, data;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE == RESET && ++retry;
return SPI_I2S_ReceiveData(SPI1;
}
运行驱动测试
在 main( 中初始化后直接调用微雪自带的测试函数. 这个测试会写入图, 然后写入文字, 局部刷新, 最后清屏, 进入睡眠。
int main(void
{
//...
APP_GPIO_Config(;
APP_SPI_Config(;
EPD_test(;
while (1;
}
遇到的问题
字体编译错误
原驱动库中文使用的是 GB2312 的编码, 而我在 Ubuntu 下肯定是不用 GBK 的, 所以会乱码, 我把这部分都改成 UTF-8 了, 因此对应的汉字的字节数也从2变成了3, 需要做对应的修改
typedef struct
{
unsigned char index[3]; //<-- 从 2 改成 3
const char matrix[MAX_HEIGHT_FONT*MAX_WIDTH_FONT/8];
} CH_CN;
GUI_Paint.c
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font,
UWORD Color_Foreground, UWORD Color_Background
{
//...
/* Point on the next character */
p_text += 3; //<-- 从 2 改成 3
//...
原先的字体定义方式为
/*-- 文字: 好 --*/
/*-- 微软雅黑12; 此字体下对应的点阵为:宽x高=16x21 --*/
{"好",
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00},
这种初始化赋值应该是 ARM GCC 支持, 但是 GUN GCC 不支持, 编译会报错, 两个struct成员变量不能用同一个花括号, 需要改为
/*-- 文字: 好 --*/
/*-- 微软雅黑12; 此字体下对应的点阵为:宽x高=16x21 --*/
{
index:"好",
matrix: {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00}
},
或者下面这种形式
/*-- 文字: 好 --*/
/*-- 微软雅黑12; 此字体下对应的点阵为:宽x高=16x21 --*/
{
"好",
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00}
},
经过以上的修改, 就可以正常编译了。
示例项目源代码
https://github.com/IOsetting/air32f103-template/tree/master/Examples/NonFreeRTOS/SPI/Waveshare_1N54_E-Paper
如果需要驱动其它型号的墨水屏, 编辑 EPD_Config.h 将#define EPD_1IN54
注释掉, 再将需要启用的型号取消注释即可。