发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[MM32生态]

基于MM32的电子墨水屏应用及SPI发送9-BIT数据的实现

[复制链接]
3338|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创#   @21小跑堂


电子墨水屏
回忆当中,第一接触电子墨水屏还是从Kindle这个电子书开始的,起初不知道它是怎么个显示原理,拿到手后体验显示刷新界面时曾一度无法适应,感觉很**肋,就是为了省电么……后来百度电子墨水屏的显示原理以及结构组成后,感觉相对于传统的LCD显示而言,优势是相当的明显(视觉感观、待机功耗);从开始的整体刷新显示,到后面可以局部刷新显示、从开始的单色显示,到后面的彩色(多色)显示,灰度显示,对于电子墨水屏的显示技术都在不断的发展,其应用领域也越来越多,我们常见的电子标签、某宝上热卖的背单词的产品都是使用电子墨水屏实现的。

MM32F3270低功耗
MM32F3270系列芯片有六种低功耗模式,包括低功耗运行(Lower Power Run)、睡眠(Sleep)、低功耗睡眠(Low Power Sleep)、停机(Stop)、深度停机(Deep Stop)和待机模式(Standby)。结合电子墨水屏应用实现的电子标签,只有当MCU在更新显示的时候才需要消耗电流,而在低功耗模式时,MCU的工作电流则可以降达到微安(uA)级别,可以在极大程度上延长产品的使用寿命。用户可以根据电源消耗不同、唤醒时间不同、唤醒源不同,结合应用需求,来选择最佳的低功耗模式,具体的低功耗模式列表如下所示:


MM32F3270 SPI
MM32F3270系列MCU最多支持3路SPI接口,而且SPI接口支持接收和发送1~32位数据同时进行,并且支持LSB格式和MSB格式的数据;结合这一特性,通过设置GCTL寄存器的DW8_32位来使32位数据都有效,通过配置EXTCTL寄存来控制SPI数据位长度,与电子墨水屏的SPI接口进行数据通讯,实现对电子墨水屏的显示控制。

基于MM32F3270的SPI FLASH下载算法
之前我们有介绍基于NOR FLASH的下载算法,SPI FLASH与NOR FLASH的访问方式不同,所以在实现把数据下载到SPI FLASH时,需要按照如下的操作步骤:
首先通过KEIL自带的下载算法工程模板,基于MM32F3270去新建一个SPI FLASH下载算法工程,这个可以参考之前的NOR FLASH下载算法工程,唯一的区别就是一个是FSMC去操作NOR FLASH,一个是通过SPI来操作SPI FLASH,其思路是一样的;
其次是基于MM32F3270新建一个空的工程,只需要启动文件的最简工程,官方的驱动库文件都可以不用添加到工程当中;然后将需要下载到SPI FLASH的资源添加这个工程中,并将这个资源的存储地址指向自己定义的存储空间,然后在程序中,记得一定要引用(使用)这个资源,才不会被KEIL优化掉,然后编译进行下载;下载之前SPI FLASH中就有我们所需要的资源数据了,但MCU中也有一个空跑的,无用的程序;
最后,就是下载一个我们通用的应用程序到MCU内部FLASH中,复位并运行;这个时候,应用程序就可以读取SPI FLASH中的资源数据了;

当然SPI FLASH的下载方式很多,有专门的烧录工具,也可能通过J-LINK来烧写,更有甚者通过MCU的应用程序来烧写SPI FLASH,而通过FLM下载算法的方式也仅仅是其中一种。

原理图
基于MM32F3270的最小系统,预留SWD程序调试/烧录接口和BOOT配置拨动开关,带有一颗用户LED指示灯和一个SPI FLASH存储芯片,SPI FLASH存储芯片预留SPI烧录接口;对于SPI FLASH的作用是存储电子墨水屏的显示数据,预留的烧录接口可以使用SPI FLASH专用的烧录工具,也可以使用J-LINK进行烧录,或者是通过MCU的下载算法来下载/烧录数据。
开发板供5V电源,通过LDO转换成3.3V作为系统工作电压;添加CH340(USB与UART TTL)方便程序调试和打印运行日志信息;参考官方的电子墨水屏参考电路,间隔0.5毫米的24PIN FPC软排线,支持佳显的2.13寸、2.9寸、4.2寸等多个尺寸的电子墨水屏;通过对R3电阻的选焊可以切换电子墨水屏的通讯控制方式;

PCB设计

回板焊接

整机效果
要是再有个外壳,就更NICE啦

MM32F3270 SPI驱动电子墨水屏实现代码:

实现方式1:GPIO
使用GPIO的高低电平设置,适当的添加延时处理,来达到电子墨水屏的SPI操作时序要求,完成MCU与电子墨水屏之前的数据通讯,具体的配置和实现代码如下所示:
/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]      
* @param      
* @retval      
* [url=home.php?mod=space&uid=93590]@Attention[/url]   
*******************************************************************************/
void EPAPER_InitSPI1(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(EPAPER_CS_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = EPAPER_CS_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(EPAPER_CS_GPIO, &GPIO_InitStructure);

    GPIO_WriteBit(EPAPER_CS_GPIO, EPAPER_CS_PIN, Bit_SET);

    RCC_AHBPeriphClockCmd(EPAPER_SCK_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = EPAPER_SCK_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(EPAPER_SCK_GPIO, &GPIO_InitStructure);

    RCC_AHBPeriphClockCmd(EPAPER_SDI_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = EPAPER_SDI_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(EPAPER_SDI_GPIO, &GPIO_InitStructure);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_SPI_WriteData(uint32_t Data)
{
    uint8_t i = 0;

    GPIO_WriteBit(EPAPER_SCK_GPIO, EPAPER_SCK_PIN, Bit_RESET);

    for(i = 0; i < 8; i++)
    {
        if(Data & (0x80 >> i))
        {
            GPIO_WriteBit(EPAPER_SDI_GPIO, EPAPER_SDI_PIN, Bit_SET);
        }
        else
        {
            GPIO_WriteBit(EPAPER_SDI_GPIO, EPAPER_SDI_PIN, Bit_RESET);
        }

        GPIO_WriteBit(EPAPER_SCK_GPIO, EPAPER_SCK_PIN, Bit_SET);
        GPIO_WriteBit(EPAPER_SCK_GPIO, EPAPER_SCK_PIN, Bit_RESET);
    }
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WaitBusy(void)
{
    while(GPIO_ReadInputDataBit(EPAPER_BUSY_GPIO, EPAPER_BUSY_PIN));
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteCMD(uint8_t Command)
{
    GPIO_WriteBit(EPAPER_CS_GPIO, EPAPER_CS_PIN, Bit_RESET);

    GPIO_WriteBit(EPAPER_DC_GPIO, EPAPER_DC_PIN, Bit_RESET);

    EPAPER_SPI_WriteData(Command);

    GPIO_WriteBit(EPAPER_CS_GPIO, EPAPER_CS_PIN, Bit_SET);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteDAT(uint8_t Data)
{
    GPIO_WriteBit(EPAPER_CS_GPIO, EPAPER_CS_PIN, Bit_RESET);

    GPIO_WriteBit(EPAPER_DC_GPIO, EPAPER_DC_PIN, Bit_SET);

    EPAPER_SPI_WriteData(Data);

    GPIO_WriteBit(EPAPER_CS_GPIO, EPAPER_CS_PIN, Bit_SET);
}

实现方式2:8位标准SPI + DC命令/数据控制线
将原理图上的R3电阻焊接上,则MCU与电子墨水屏之间则是通过DC引脚的高低电平来区分数据还是指令,SPI此时需传输标准的8位数据,使MCU与电子墨水屏之间建立通讯,具体的配置和实现代码如下所示:
/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_InitSPI1(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_SPI1, ENABLE);

    SPI_StructInit(&SPI_InitStructure);
    SPI_InitStructure.SPI_Mode              = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize          = SPI_DataSize_8b;
    SPI_InitStructure.SPI_DataWidth         = SPI_DataWidth_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_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_5);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Tx);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_SPI_WriteData(uint32_t Data)
{
    __asm volatile("cpsid i");
    WRITE_REG(SPI1->TDR, Data);
    __asm volatile("cpsie i");
    while(!SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT));
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WaitBusy(void)
{
    while(GPIO_ReadInputDataBit(EPAPER_BUSY_GPIO, EPAPER_BUSY_PIN));
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteCMD(uint8_t Command)
{
    SPI_CSInternalSelected(SPI1, ENABLE);

    GPIO_WriteBit(EPAPER_DC_GPIO, EPAPER_DC_PIN, Bit_RESET);

    EPAPER_SPI_WriteData(Command);

    SPI_CSInternalSelected(SPI1, DISABLE);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteDAT(uint8_t Data)
{
    SPI_CSInternalSelected(SPI1, ENABLE);

    GPIO_WriteBit(EPAPER_DC_GPIO, EPAPER_DC_PIN, Bit_SET);

    EPAPER_SPI_WriteData(Data);

    SPI_CSInternalSelected(SPI1, DISABLE);
}

实现方式3:9位SPI
将原理图上的R3电阻去除掉,此时DC引脚需要一直处于低电平状态,电子墨水屏不根据DC引脚的电平状态来区分数据或指令,此时要求SPI通讯必须传输9位数据位,第一位的0或是1来代替DC引脚的功能,区分后面的8位数据是数据还是指令,具体的配置和实现代码如下所示:
/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_InitSPI1(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_SPI1, ENABLE);

    SPI_StructInit(&SPI_InitStructure);
    SPI_InitStructure.SPI_Mode              = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize          = SPI_DataSize_32b;
    SPI_InitStructure.SPI_DataWidth         = SPI_DataWidth_9b;
    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_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_5);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_5);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_BiDirectionalLineConfig(SPI1, SPI_Direction_Tx);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_SPI_WriteData(uint32_t Data)
{
    __asm volatile("cpsid i");
    WRITE_REG(SPI1->TDR, Data);
    __asm volatile("cpsie i");
    while(!SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT));
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WaitBusy(void)
{
    while(GPIO_ReadInputDataBit(EPAPER_BUSY_GPIO, EPAPER_BUSY_PIN));
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteCMD(uint8_t Command)
{
    SPI_CSInternalSelected(SPI1, ENABLE);

    EPAPER_SPI_WriteData(Command & 0xFFFFFEFF);

    SPI_CSInternalSelected(SPI1, DISABLE);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_WriteDAT(uint8_t Data)
{
    SPI_CSInternalSelected(SPI1, ENABLE);

    EPAPER_SPI_WriteData(Data | 0x00000100);

    SPI_CSInternalSelected(SPI1, DISABLE);
}

演示实现代码:
/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_Init(void)
{
    EPAPER_InitGPIO();
    EPAPER_InitSPI1();

    EPAPER_NORMAL_Init();
    EPAPER_NORMAL_Display(gImage_MM32);
    SysTick_DelayMS(1000);

    EPAPER_NORMAL_Init();
    EPAPER_WriteRAM_BaseMAP(gImage_**);
   
    EPAPER_PARTIAL_Init();
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM1, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM2, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM3, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM4, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM5, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM6, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM7, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM8, 1); SysTick_DelayMS(250);
    EPAPER_PARTIAL_Display(0, 32, 32, 32, gImage_NUM9, 1); SysTick_DelayMS(250);

    EPAPER_NORMAL_Init();
    EPAPER_WriteRAM_BaseMAP(gImage_CLOCK);

    EPAPER_PARTIAL_Init();

    for(uint32_t i = 0; i < 60 * 60; i++)
    {
        uint8_t Minute = i / 60;
        uint8_t Second = i % 60;

        EPAPER_PARTIAL_DisplayClock(32,  90, (const uint8_t *)&gImage_TIME[256 * (Second % 10)],
                                    32, 122, (const uint8_t *)&gImage_TIME[256 * (Second / 10)],
                                    24, 162, (const uint8_t *)&gImage_TIME[256 * 10],
                                    32, 204, (const uint8_t *)&gImage_TIME[256 * (Minute % 10)],
                                    32, 236, (const uint8_t *)&gImage_TIME[256 * (Minute / 10)], 32, 64);

        if((Minute == 0) && (Second == 18))
        {
            goto EPAPER_CLEAR;
        }

        SysTick_DelayMS(1000);
    }

EPAPER_CLEAR:
    EPAPER_NORMAL_Init();
    EPAPER_WriteRAM_BaseMAP(gImage_PANDA);

    EPAPER_PARTIAL_Init();
}


uint8_t EPAPER_SHELL_Index = 0;


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void EPAPER_Handler(void)
{
    const uint8_t *Image = NULL;

    if(EPAPER_SHELL_Index != 0)
    {
        switch(EPAPER_SHELL_Index)
        {
            case 1 : Image = gImage_1; break;
            case 2 : Image = gImage_2; break;
            case 3 : Image = gImage_3; break;
            case 4 : Image = gImage_4; break;
            case 5 : Image = gImage_5; break;
            case 6 : Image = gImage_6; break;
            case 7 : Image = gImage_7; break;
            case 8 : Image = gImage_8; break;
            case 9 : Image = gImage_9; break;
            case 10: Image = gImage_0; break;
            default:
                EPAPER_NORMAL_Init();
                EPAPER_NORMAL_Clear(0x00);

                EPAPER_NORMAL_Init();
                EPAPER_NORMAL_Clear(0xFF);

                SysTick_DelayMS(100);

                EPAPER_DeepSleep();
                break;
        }

        if(Image != NULL)
        {
            EPAPER_PARTIAL_Display(0, 48, 48, 48, Image, 1);
        }

        EPAPER_SHELL_Index = 0;
    }
}

运行效果:

原理图 Schematic_E-Paper_2022-02-17.pdf (148.43 KB)
电子墨水屏手册 GDEH0213B72.PDF (1.11 MB) GDEM029E97.PDF (1.29 MB)
软件工程源代码 Template_2.9.zip (805.21 KB)

对于显示资源不多的,可能MCU内部FLASH存储空间就足够了;对于一个电子小说,一个大容量的SPI FLASH可能也可以满足需求了;对于更丰富的应用,作为MM32F3270系列的MCU,有着FSMC接口控制器,可以扩展容量更大的NOR  FLASH,来实现应用资源的存储。




使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2022-02-24
理由:恭喜通过原创文章审核!请多多加油哦!

沙发
yang377156216| | 2022-2-24 18:28 | 只看该作者
** 相当不错

使用特权

评论回复
板凳
sf116| | 2022-2-25 08:40 | 只看该作者
很详细

使用特权

评论回复
地板
laocuo1142| | 2022-2-25 09:32 | 只看该作者
很详细,楼主很棒啊

使用特权

评论回复
5
tpgf| | 2022-3-2 15:03 | 只看该作者
请问什么是电子墨水屏啊

使用特权

评论回复
6
wowu| | 2022-3-2 15:10 | 只看该作者
咦 这个屏幕很不错啊

使用特权

评论回复
7
xiaoqizi| | 2022-3-2 15:24 | 只看该作者
整个做的都很细腻

使用特权

评论回复
8
木木guainv| | 2022-3-2 15:33 | 只看该作者
这个是个彩屏吗

使用特权

评论回复
9
磨砂| | 2022-3-2 15:40 | 只看该作者
应该比较节省内部资源

使用特权

评论回复
10
晓伍| | 2022-3-2 15:49 | 只看该作者
如果不闪的话 刷新率最快得多少啊

使用特权

评论回复
11
xld0932|  楼主 | 2022-3-2 15:52 | 只看该作者
晓伍 发表于 2022-3-2 15:49
如果不闪的话 刷新率最快得多少啊

如果不闪就是局部更新,不是局部更新就肯定会闪;墨水屏的特性……刷新速度没测试过……

使用特权

评论回复
12
13380773996| | 2022-3-3 12:25 | 只看该作者
感谢楼主分享

使用特权

评论回复
13
119163wzj| | 2022-3-3 17:27 | 只看该作者
感谢楼主分享

使用特权

评论回复
14
usysm| | 2022-3-4 14:19 | 只看该作者
电子墨水屏价格太贵了。     

使用特权

评论回复
15
ccook11| | 2022-3-4 14:25 | 只看该作者
SPI是硬件spi吗   

使用特权

评论回复
16
xld0932|  楼主 | 2022-3-4 17:49 | 只看该作者

是的

使用特权

评论回复
17
yangxiaor520| | 2022-3-4 18:50 | 只看该作者
电子墨水屏切屏是硬伤

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

69

主题

2998

帖子

31

粉丝