xld0932 发表于 2022-2-21 17:32

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

#申请原创#   @21小跑堂


电子墨水屏回忆当中,第一接触电子墨水屏还是从Kindle这个电子书开始的,起初不知道它是怎么个显示原理,拿到手后体验显示刷新界面时曾一度无法适应,感觉很**肋,就是为了省电么……后来百度电子墨水屏的显示原理以及结构组成后,感觉相对于传统的LCD显示而言,优势是相当的明显(视觉感观、待机功耗);从开始的整体刷新显示,到后面可以局部刷新显示、从开始的单色显示,到后面的彩色(多色)显示,灰度显示,对于电子墨水屏的显示技术都在不断的发展,其应用领域也越来越多,我们常见的电子标签、某宝上热卖的背单词的产品都是使用电子墨水屏实现的。
MM32F3270低功耗MM32F3270系列芯片有六种低功耗模式,包括低功耗运行(Lower Power Run)、睡眠(Sleep)、低功耗睡眠(Low Power Sleep)、停机(Stop)、深度停机(Deep Stop)和待机模式(Standby)。结合电子墨水屏应用实现的电子标签,只有当MCU在更新显示的时候才需要消耗电流,而在低功耗模式时,MCU的工作电流则可以降达到微安(uA)级别,可以在极大程度上延长产品的使用寿命。用户可以根据电源消耗不同、唤醒时间不同、唤醒源不同,结合应用需求,来选择最佳的低功耗模式,具体的低功耗模式列表如下所示:

MM32F3270 SPIMM32F3270系列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与电子墨水屏之前的数据通讯,具体的配置和实现代码如下所示:/*******************************************************************************
* @brief      
* @param      
* @retval      
* @Attention   
*******************************************************************************/
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_InitTypeDefSPI_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_InitTypeDefSPI_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,
                                    32, 122, (const uint8_t *)&gImage_TIME,
                                    24, 162, (const uint8_t *)&gImage_TIME,
                                    32, 204, (const uint8_t *)&gImage_TIME,
                                    32, 236, (const uint8_t *)&gImage_TIME, 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;
    }
}
运行效果:
原理图:电子墨水屏手册:软件工程源代码:
续对于显示资源不多的,可能MCU内部FLASH存储空间就足够了;对于一个电子小说,一个大容量的SPI FLASH可能也可以满足需求了;对于更丰富的应用,作为MM32F3270系列的MCU,有着FSMC接口控制器,可以扩展容量更大的NORFLASH,来实现应用资源的存储。



yang377156216 发表于 2022-2-24 18:28

** 相当不错

sf116 发表于 2022-2-25 08:40

很详细

laocuo1142 发表于 2022-2-25 09:32

很详细,楼主很棒啊

tpgf 发表于 2022-3-2 15:03

请问什么是电子墨水屏啊

wowu 发表于 2022-3-2 15:10

咦 这个屏幕很不错啊

xiaoqizi 发表于 2022-3-2 15:24

整个做的都很细腻

木木guainv 发表于 2022-3-2 15:33

这个是个彩屏吗

磨砂 发表于 2022-3-2 15:40

应该比较节省内部资源

晓伍 发表于 2022-3-2 15:49

如果不闪的话 刷新率最快得多少啊

xld0932 发表于 2022-3-2 15:52

晓伍 发表于 2022-3-2 15:49
如果不闪的话 刷新率最快得多少啊

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

13380773996 发表于 2022-3-3 12:25

感谢楼主分享

119163wzj 发表于 2022-3-3 17:27

感谢楼主分享

usysm 发表于 2022-3-4 14:19

电子墨水屏价格太贵了。   

ccook11 发表于 2022-3-4 14:25

SPI是硬件spi吗   

xld0932 发表于 2022-3-4 17:49

ccook11 发表于 2022-3-4 14:25
SPI是硬件spi吗

是的

yangxiaor520 发表于 2022-3-4 18:50

电子墨水屏切屏是硬伤
页: [1]
查看完整版本: 基于MM32的电子墨水屏应用及SPI发送9-BIT数据的实现