#申请原创# @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;
}
}
运行效果:
续 对于显示资源不多的,可能MCU内部FLASH存储空间就足够了;对于一个电子小说,一个大容量的SPI FLASH可能也可以满足需求了;对于更丰富的应用,作为MM32F3270系列的MCU,有着FSMC接口控制器,可以扩展容量更大的NOR FLASH,来实现应用资源的存储。
|