Keil环境下inline关键字对于STM32的FSMC功能影响

调试STM32的FSMC功能时遇到诡异波形问题,最终发现是由inline关键字引起。去除该关键字后,波形恢复正常,成功解决数据通信异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近被一个非常恼人的问题缠了几天。在调试STM32的FSMC功能时,发现FSMC管脚波形十分诡异,会有严重的拖尾、高低不平的方波、半三角波等波形出现,导致数据根本无法正常通信。

之前以为是配置问题,反复检查、查看资料、对比官方文档均无解,关闭其他所有外围、关闭所有外设,甚至割断线路板(被逼无奈,开始怀疑走线可能产生电容)、更换芯片,均无解。

后来单独写了一段测试代码,发现波形正常了,这才回过头去看原来发送/接收的函数,发现函数带有inline关键字,去掉以后立马也正常了。。。

之前工程项目使用的模拟IO实现的数据读取,所以加上了inline关键字加快执行速度,但是感觉速度仍不理想于是改用FSMC,调试过程中完全忽视了inline关键字所带来的影响。

虽然不太清楚inline的特殊编译对于FSMC究竟是怎样产生影响的,但是这个坑希望以后别有人像我一样踩了。。。

平台STM32F103ZET6霸道V1,使用器件为ILI9342屏幕和HX711称重传感器,还有开发板中的按键,ILI9341的代码为#include "./lcd/bsp_ili9341_lcd.h" #include "./font/fonts.h" //根据液晶扫描方向而变化的XY像素宽度 //调用ILI9341_GramScan函数设置方向时会自动更改 uint16_t LCD_X_LENGTH = ILI9341_LESS_PIXEL; uint16_t LCD_Y_LENGTH = ILI9341_MORE_PIXEL; //液晶屏扫描模式,本变量主要用于方便选择触摸屏的计算参数 //参数可选值为0-7 //调用ILI9341_GramScan函数设置方向时会自动更改 //LCD刚初始化完成时会使用本默认值 uint8_t LCD_SCAN_MODE = 6; //保存液晶屏驱动ic的 ID uint16_t lcdid = LCDID_UNKNOWN; static sFONT *LCD_Currentfonts = &Font8x16; //英文字体 static uint16_t CurrentTextColor = BLACK;//前景色 static uint16_t CurrentBackColor = WHITE;//背景色 //extern inline void ILI9341_Write_Cmd ( uint16_t usCmd ); //extern inline void ILI9341_Write_Data ( uint16_t usData ); //extern inline uint16_t ILI9341_Read_Data ( void ); static void ILI9341_Delay ( __IO uint32_t nCount ); static void ILI9341_GPIO_Config ( void ); static void ILI9341_FSMC_Config ( void ); static void ILI9341_REG_Config ( void ); static void ILI9341_SetCursor ( uint16_t usX, uint16_t usY ); static __inline void ILI9341_FillColor ( uint32_t ulAmout_Point, uint16_t usColor ); static uint16_t ILI9341_Read_PixelData ( void ); /** * @brief 向ILI9341写入命令 * @param usCmd :要写入的命令(表寄存器地址) * @retval 无 */ extern inline void ILI9341_Write_Cmd ( uint16_t usCmd ) { * ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_CMD ) = usCmd; } /** * @brief 向ILI9341写入数据 * @param usData :要写入的数据 * @retval 无 */ extern inline void ILI9341_Write_Data ( uint16_t usData ) { * ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) = usData; } /** * @brief 从ILI9341读取数据 * @param 无 * @retval 读取到的数据 */ extern inline uint16_t ILI9341_Read_Data ( void ) { return ( * ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) ); } /** * @brief 用于 ILI9341 简单延时函数 * @param nCount :延时计数值 * @retval 无 */ static void ILI9341_Delay ( __IO uint32_t nCount ) { for ( ; nCount != 0; nCount -- ); } /** * @brief 初始化ILI9341的IO引脚 * @param 无 * @retval 无 */ static void ILI9341_GPIO_Config ( void ) { GPIO_InitTypeDef GPIO_InitStructure; /* 使能FSMC对应相应管脚时钟*/ RCC_APB2PeriphClockCmd ( /*控制信号*/ ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK| ILI9341_RD_CLK |ILI9341_BK_CLK|ILI9341_RST_CLK| /*数据信号*/ ILI9341_D0_CLK|ILI9341_D1_CLK| ILI9341_D2_CLK | ILI9341_D3_CLK | ILI9341_D4_CLK|ILI9341_D5_CLK| ILI9341_D6_CLK | ILI9341_D7_CLK|ILI9341_D8_CLK| ILI9341_D9_CLK | ILI9341_D10_CLK|ILI9341_D11_CLK| ILI9341_D12_CLK | ILI9341_D13_CLK|ILI9341_D14_CLK| ILI9341_D15_CLK , ENABLE ); /* 配置FSMC相对应的数据线,FSMC-D0~D15 */ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN; GPIO_Init ( ILI9341_D0_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D1_PIN; GPIO_Init ( ILI9341_D1_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D2_PIN; GPIO_Init ( ILI9341_D2_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D3_PIN; GPIO_Init ( ILI9341_D3_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D4_PIN; GPIO_Init ( ILI9341_D4_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D5_PIN; GPIO_Init ( ILI9341_D5_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D6_PIN; GPIO_Init ( ILI9341_D6_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D7_PIN; GPIO_Init ( ILI9341_D7_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D8_PIN; GPIO_Init ( ILI9341_D8_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D9_PIN; GPIO_Init ( ILI9341_D9_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D10_PIN; GPIO_Init ( ILI9341_D10_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D11_PIN; GPIO_Init ( ILI9341_D11_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D12_PIN; GPIO_Init ( ILI9341_D12_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D13_PIN; GPIO_Init ( ILI9341_D13_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D14_PIN; GPIO_Init ( ILI9341_D14_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_D15_PIN; GPIO_Init ( ILI9341_D15_PORT, & GPIO_InitStructure ); /* 配置FSMC相对应的控制线 * FSMC_NOE :LCD-RD * FSMC_NWE :LCD-WR * FSMC_NE1 :LCD-CS * FSMC_A16 :LCD-DC */ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN; GPIO_Init (ILI9341_RD_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_WR_PIN; GPIO_Init (ILI9341_WR_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_CS_PIN; GPIO_Init ( ILI9341_CS_PORT, & GPIO_InitStructure ); GPIO_InitStructure.GPIO_Pin = ILI9341_DC_PIN; GPIO_Init ( ILI9341_DC_PORT, & GPIO_InitStructure ); /* 配置LCD复位RST控制管脚*/ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = ILI9341_RST_PIN; GPIO_Init ( ILI9341_RST_PORT, & GPIO_InitStructure ); /* 配置LCD背光控制管脚BK*/ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = ILI9341_BK_PIN; GPIO_Init ( ILI9341_BK_PORT, & GPIO_InitStructure ); } /** * @brief LCD FSMC 模式配置 * @param 无 * @retval 无 */ static void ILI9341_FSMC_Config ( void ) { FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure = {0}; FSMC_NORSRAMTimingInitTypeDef readWriteTiming = {0}; /* 使能FSMC时钟*/ RCC_AHBPeriphClockCmd ( RCC_AHBPeriph_FSMC, ENABLE ); //地址建立时间(ADDSET)为1个HCLK 2/72M=28ns readWriteTiming.FSMC_AddressSetupTime = 0x01; //地址建立时间 //数据保持时间(DATAST)+ 1个HCLK = 5/72M=70ns readWriteTiming.FSMC_DataSetupTime = 0x04; //数据建立时间 //选择控制的模式 //模式B,异步NOR FLASH模式,与ILI9341的8080时序匹配 readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B; /*以下配置与模式B无关*/ //地址保持时间(ADDHLD)模式A未用到 readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间 //设置总线转换周期,仅用于复用模式的NOR操作 readWriteTiming.FSMC_BusTurnAroundDuration = 0x00; //设置时钟分频,仅用于同步类型的存储器 readWriteTiming.FSMC_CLKDivision = 0x00; //数据保持时间,仅用于同步型的NOR readWriteTiming.FSMC_DataLatency = 0x00; FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAMx; FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR; FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable; FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable; FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming; FSMC_NORSRAMInit ( & FSMC_NORSRAMInitStructure ); /* 使能 FSMC_Bank1_NORSRAM4 */ FSMC_NORSRAMCmd ( FSMC_Bank1_NORSRAMx, ENABLE ); } /** * @brief 初始化ILI9341寄存器 * @param 无 * @retval 无 */ static void ILI9341_REG_Config ( void ) { lcdid = ILI9341_ReadID(); if(lcdid == LCDID_ILI9341) { /* Power control B (CFh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xCF ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x81 ); ILI9341_Write_Data ( 0x30 ); /* Power on sequence control (EDh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xED ); ILI9341_Write_Data ( 0x64 ); ILI9341_Write_Data ( 0x03 ); ILI9341_Write_Data ( 0x12 ); ILI9341_Write_Data ( 0x81 ); /* Driver timing control A (E8h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xE8 ); ILI9341_Write_Data ( 0x85 ); ILI9341_Write_Data ( 0x10 ); ILI9341_Write_Data ( 0x78 ); /* Power control A (CBh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xCB ); ILI9341_Write_Data ( 0x39 ); ILI9341_Write_Data ( 0x2C ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x34 ); //ILI9341_Write_Data ( 0x02 ); ILI9341_Write_Data ( 0x06 ); //原来是0x02改为0x06可防止液晶显示白屏时有条纹的情况 /* Pump ratio control (F7h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xF7 ); ILI9341_Write_Data ( 0x20 ); /* Driver timing control B */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xEA ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x00 ); /* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xB1 ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x1B ); /* Display Function Control (B6h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xB6 ); ILI9341_Write_Data ( 0x0A ); ILI9341_Write_Data ( 0xA2 ); /* Power Control 1 (C0h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xC0 ); ILI9341_Write_Data ( 0x35 ); /* Power Control 2 (C1h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xC1 ); ILI9341_Write_Data ( 0x11 ); /* VCOM Control 1 (C5h) */ ILI9341_Write_Cmd ( 0xC5 ); ILI9341_Write_Data ( 0x45 ); ILI9341_Write_Data ( 0x45 ); /* VCOM Control 2 (C7h) */ ILI9341_Write_Cmd ( 0xC7 ); ILI9341_Write_Data ( 0xA2 ); /* Enable 3G (F2h) */ ILI9341_Write_Cmd ( 0xF2 ); ILI9341_Write_Data ( 0x00 ); /* Gamma Set (26h) */ ILI9341_Write_Cmd ( 0x26 ); ILI9341_Write_Data ( 0x01 ); DEBUG_DELAY (); /* Positive Gamma Correction */ ILI9341_Write_Cmd ( 0xE0 ); //Set Gamma ILI9341_Write_Data ( 0x0F ); ILI9341_Write_Data ( 0x26 ); ILI9341_Write_Data ( 0x24 ); ILI9341_Write_Data ( 0x0B ); ILI9341_Write_Data ( 0x0E ); ILI9341_Write_Data ( 0x09 ); ILI9341_Write_Data ( 0x54 ); ILI9341_Write_Data ( 0xA8 ); ILI9341_Write_Data ( 0x46 ); ILI9341_Write_Data ( 0x0C ); ILI9341_Write_Data ( 0x17 ); ILI9341_Write_Data ( 0x09 ); ILI9341_Write_Data ( 0x0F ); ILI9341_Write_Data ( 0x07 ); ILI9341_Write_Data ( 0x00 ); /* Negative Gamma Correction (E1h) */ ILI9341_Write_Cmd ( 0XE1 ); //Set Gamma ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x19 ); ILI9341_Write_Data ( 0x1B ); ILI9341_Write_Data ( 0x04 ); ILI9341_Write_Data ( 0x10 ); ILI9341_Write_Data ( 0x07 ); ILI9341_Write_Data ( 0x2A ); ILI9341_Write_Data ( 0x47 ); ILI9341_Write_Data ( 0x39 ); ILI9341_Write_Data ( 0x03 ); ILI9341_Write_Data ( 0x06 ); ILI9341_Write_Data ( 0x06 ); ILI9341_Write_Data ( 0x30 ); ILI9341_Write_Data ( 0x38 ); ILI9341_Write_Data ( 0x0F ); /* memory access control set */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0x36 ); ILI9341_Write_Data ( 0xC8 ); /*竖屏 左上角到 (起点)到右下角 (终点)扫描方式*/ DEBUG_DELAY (); /* column address control set */ ILI9341_Write_Cmd ( CMD_SetCoordinateX ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0xEF ); /* page address control set */ DEBUG_DELAY (); ILI9341_Write_Cmd ( CMD_SetCoordinateY ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x01 ); ILI9341_Write_Data ( 0x3F ); /* Pixel Format Set (3Ah) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0x3a ); ILI9341_Write_Data ( 0x55 ); /* Sleep Out (11h) */ ILI9341_Write_Cmd ( 0x11 ); ILI9341_Delay ( 0xAFFf<<2 ); DEBUG_DELAY (); /* Display ON (29h) */ ILI9341_Write_Cmd ( 0x29 ); } else if(lcdid == LCDID_ST7789V) { /* Power control B (CFh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xCF ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0xC1 ); ILI9341_Write_Data ( 0x30 ); /* Power on sequence control (EDh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xED ); ILI9341_Write_Data ( 0x64 ); ILI9341_Write_Data ( 0x03 ); ILI9341_Write_Data ( 0x12 ); ILI9341_Write_Data ( 0x81 ); /* Driver timing control A (E8h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xE8 ); ILI9341_Write_Data ( 0x85 ); ILI9341_Write_Data ( 0x10 ); ILI9341_Write_Data ( 0x78 ); /* Power control A (CBh) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xCB ); ILI9341_Write_Data ( 0x39 ); ILI9341_Write_Data ( 0x2C ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x34 ); ILI9341_Write_Data ( 0x02 ); /* Pump ratio control (F7h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xF7 ); ILI9341_Write_Data ( 0x20 ); /* Driver timing control B */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xEA ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x00 ); /* Power Control 1 (C0h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xC0 ); //Power control ILI9341_Write_Data ( 0x21 ); //VRH[5:0] /* Power Control 2 (C1h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xC1 ); //Power control ILI9341_Write_Data ( 0x11 ); //SAP[2:0];BT[3:0] /* VCOM Control 1 (C5h) */ ILI9341_Write_Cmd ( 0xC5 ); ILI9341_Write_Data ( 0x2D ); ILI9341_Write_Data ( 0x33 ); /* VCOM Control 2 (C7h) */ // ILI9341_Write_Cmd ( 0xC7 ); // ILI9341_Write_Data ( 0XC0 ); /* memory access control set */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0x36 ); //Memory Access Control ILI9341_Write_Data ( 0x00 ); /*竖屏 左上角到 (起点)到右下角 (终点)扫描方式*/ DEBUG_DELAY (); ILI9341_Write_Cmd(0x3A); ILI9341_Write_Data(0x55); /* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xB1 ); ILI9341_Write_Data ( 0x00 ); ILI9341_Write_Data ( 0x17 ); /* Display Function Control (B6h) */ DEBUG_DELAY (); ILI9341_Write_Cmd ( 0xB6 ); ILI9341_Write_Data ( 0x0A ); ILI9341_Write_Data ( 0xA2 ); ILI9341_Write_Cmd(0xF6); ILI9341_Write_Data(0x01); ILI9341_Write_Data(0x30); /* Enable 3G (F2h) */ ILI9341_Write_Cmd ( 0xF2 ); ILI9341_Write_Data ( 0x00 ); /* Gamma Set (26h) */ ILI9341_Write_Cmd ( 0x26 ); ILI9341_Write_Data ( 0x01 ); DEBUG_DELAY (); /* Positive Gamma Correction */ ILI9341_Write_Cmd(0xe0); //Positive gamma ILI9341_Write_Data(0xd0); ILI9341_Write_Data(0x00); ILI9341_Write_Data(0x02); ILI9341_Write_Data(0x07); ILI9341_Write_Data(0x0b); ILI9341_Write_Data(0x1a); ILI9341_Write_Data(0x31); ILI9341_Write_Data(0x54); ILI9341_Write_Data(0x40); ILI9341_Write_Data(0x29); ILI9341_Write_Data(0x12); ILI9341_Write_Data(0x12); ILI9341_Write_Data(0x12); ILI9341_Write_Data(0x17); /* Negative Gamma Correction (E1h) */ ILI9341_Write_Cmd(0xe1); //Negative gamma ILI9341_Write_Data(0xd0); ILI9341_Write_Data(0x00); ILI9341_Write_Data(0x02); ILI9341_Write_Data(0x07); ILI9341_Write_Data(0x05); ILI9341_Write_Data(0x25); ILI9341_Write_Data(0x2d); ILI9341_Write_Data(0x44); ILI9341_Write_Data(0x45); ILI9341_Write_Data(0x1c); ILI9341_Write_Data(0x18); ILI9341_Write_Data(0x16); ILI9341_Write_Data(0x1c); ILI9341_Write_Data(0x1d); // /* column address control set */ // ILI9341_Write_Cmd ( CMD_SetCoordinateX ); // ILI9341_Write_Data ( 0x00 ); // ILI9341_Write_Data ( 0x00 ); // ILI9341_Write_Data ( 0x00 ); // ILI9341_Write_Data ( 0xEF ); // // /* page address control set */ // DEBUG_DELAY (); // ILI9341_Write_Cmd ( CMD_SetCoordinateY ); // ILI9341_Write_Data ( 0x00 ); // ILI9341_Write_Data ( 0x00 ); // ILI9341_Write_Data ( 0x01 ); // ILI9341_Write_Data ( 0x3F ); /* Sleep Out (11h) */ ILI9341_Write_Cmd ( 0x11 ); //Exit Sleep ILI9341_Delay ( 0xAFFf<<2 ); DEBUG_DELAY (); /* Display ON (29h) */ ILI9341_Write_Cmd ( 0x29 ); //Display on ILI9341_Write_Cmd(0x2c); } } /** * @brief ILI9341初始化函数,如果要用到lcd,一定要调用这个函数 * @param 无 * @retval 无 */ void ILI9341_Init ( void ) { ILI9341_GPIO_Config (); ILI9341_FSMC_Config (); ILI9341_BackLed_Control ( ENABLE ); //点亮LCD背光灯 ILI9341_Rst (); ILI9341_REG_Config (); //设置默认扫描方向,其中 6 模式为大部分液晶例程的默认显示方向 ILI9341_GramScan(LCD_SCAN_MODE); } /** * @brief ILI9341背光LED控制 * @param enumState :决定是否使能背光LED * 该参数为以下值之一: * @arg ENABLE :使能背光LED * @arg DISABLE :禁用背光LED * @retval 无 */ void ILI9341_BackLed_Control ( FunctionalState enumState ) { if ( enumState ) GPIO_ResetBits ( ILI9341_BK_PORT, ILI9341_BK_PIN ); else GPIO_SetBits ( ILI9341_BK_PORT, ILI9341_BK_PIN ); } /** * @brief 读取LCD驱动芯片ID函数,可用于测试底层的读写函数 * @param 无 * @retval 正常时返回值为LCD驱动芯片ID: LCDID_ILI9341/LCDID_ST7789V * 否则返回: LCDID_UNKNOWN */ uint16_t ILI9341_ReadID(void) { uint16_t id = 0; ILI9341_Write_Cmd(0x04); ILI9341_Read_Data(); ILI9341_Read_Data(); id = ILI9341_Read_Data(); id <<= 8; id |= ILI9341_Read_Data(); if(id == LCDID_ST7789V) { return id; } else { ILI9341_Write_Cmd(0xD3); ILI9341_Read_Data(); ILI9341_Read_Data(); id = ILI9341_Read_Data(); id <<= 8; id |= ILI9341_Read_Data(); if(id == LCDID_ILI9341) { return id; } } return LCDID_UNKNOWN; } /** * @brief ILI9341 软件复位 * @param 无 * @retval 无 */ void ILI9341_Rst ( void ) { GPIO_ResetBits ( ILI9341_RST_PORT, ILI9341_RST_PIN ); //低电平复位 ILI9341_Delay ( 0xAFF ); GPIO_SetBits ( ILI9341_RST_PORT, ILI9341_RST_PIN ); ILI9341_Delay ( 0xAFF ); } /** * @brief 设置ILI9341的GRAM的扫描方向 * @param ucOption :选择GRAM的扫描方向 * @arg 0-7 :参数可选值为0-7这八个方向 * * !!!其中0、3、5、6 模式适合从左至右显示文字, * 不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果 * * 其中0、2、4、6 模式的X方向像素为240,Y方向像素为320 * 其中1、3、5、7 模式下X方向像素为320,Y方向像素为240 * * 其中 6 模式为大部分液晶例程的默认显示方向 * 其中 3 模式为摄像头例程使用的方向 * 其中 0 模式为BMP图片显示例程使用的方向 * * @retval 无 * @note 坐标图例:A表示向上,V表示向下,<表示向左,>表示向右 X表示X轴,Y表示Y轴 LCDID_ILI9341 ------------------------------------------------------------ 模式0: . 模式1: . 模式2: . 模式3: A . A . A . A | . | . | . | Y . X . Y . X 0 . 1 . 2 . 3 <--- X0 o . <----Y1 o . o 2X---> . o 3Y---> ------------------------------------------------------------ 模式4: . 模式5: . 模式6: . 模式7: <--- X4 o . <--- Y5 o . o 6X---> . o 7Y---> 4 . 5 . 6 . 7 Y . X . Y . X | . | . | . | V . V . V . V --------------------------------------------------------- LCD屏示例 |-----------------| | 野火Logo | | | | | | | | | | | | | | | | | |-----------------| 屏幕正面(宽240,高320) LCDID_ST7789V ------------------------------------------------------------ 模式0: . 模式1: . 模式2: . 模式3: o 0X---> . o 1Y---> . <--- X2 o . <--- Y3 o 0 . 1 . 2 . 3 Y . X . Y . X | . | . | . | V V . V . V ------------------------------------------------------------ 模式4: . 模式5: . 模式6: . 模式7: A . A . A . A | . | . | . | Y . X . Y . X 4 . 5 . 6 . 7 o 4X---> . o 5Y---> . <--- X6 o . <--- Y7 o --------------------------------------------------------- LCD屏示例 |-----------------| | 野火Logo | | | | | | | | | | | | | | | | | |-----------------| 屏幕正面(宽240,高320) *******************************************************/ void ILI9341_GramScan ( uint8_t ucOption ) { //参数检查,只可输入0-7 if(ucOption >7 ) return; //根据模式更新LCD_SCAN_MODE的值,主要用于触摸屏选择计算参数 LCD_SCAN_MODE = ucOption; //根据模式更新XY方向的像素宽度 if(ucOption%2 == 0) { //0 2 4 6模式下X方向像素宽度为240,Y方向为320 LCD_X_LENGTH = ILI9341_LESS_PIXEL; LCD_Y_LENGTH = ILI9341_MORE_PIXEL; } else { //1 3 5 7模式下X方向像素宽度为320,Y方向为240 LCD_X_LENGTH = ILI9341_MORE_PIXEL; LCD_Y_LENGTH = ILI9341_LESS_PIXEL; } //0x36命令参数的高3位可用于设置GRAM扫描方向 ILI9341_Write_Cmd ( 0x36 ); if(lcdid == LCDID_ILI9341) { ILI9341_Write_Data ( 0x08 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式 } else if(lcdid == LCDID_ST7789V) { ILI9341_Write_Data ( 0x00 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式 } ILI9341_Write_Cmd ( CMD_SetCoordinateX ); ILI9341_Write_Data ( 0x00 ); /* x 起始坐标高8位 */ ILI9341_Write_Data ( 0x00 ); /* x 起始坐标低8位 */ ILI9341_Write_Data ( ((LCD_X_LENGTH-1)>>8)&0xFF ); /* x 结束坐标高8位 */ ILI9341_Write_Data ( (LCD_X_LENGTH-1)&0xFF ); /* x 结束坐标低8位 */ ILI9341_Write_Cmd ( CMD_SetCoordinateY ); ILI9341_Write_Data ( 0x00 ); /* y 起始坐标高8位 */ ILI9341_Write_Data ( 0x00 ); /* y 起始坐标低8位 */ ILI9341_Write_Data ( ((LCD_Y_LENGTH-1)>>8)&0xFF ); /* y 结束坐标高8位 */ ILI9341_Write_Data ( (LCD_Y_LENGTH-1)&0xFF ); /* y 结束坐标低8位 */ /* write gram start */ ILI9341_Write_Cmd ( CMD_SetPixel ); } /** * @brief 在ILI9341显示器上开辟一个窗口 * @param usX :在特定扫描方向下窗口的起点X坐标 * @param usY :在特定扫描方向下窗口的起点Y坐标 * @param usWidth :窗口的宽度 * @param usHeight :窗口的高度 * @retval 无 */ void ILI9341_OpenWindow ( uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight ) { ILI9341_Write_Cmd ( CMD_SetCoordinateX ); /* 设置X坐标 */ ILI9341_Write_Data ( usX >> 8 ); /* 先高8位,然后低8位 */ ILI9341_Write_Data ( usX & 0xff ); /* 设置起始点和结束点*/ ILI9341_Write_Data ( ( usX + usWidth - 1 ) >> 8 ); ILI9341_Write_Data ( ( usX + usWidth - 1 ) & 0xff ); ILI9341_Write_Cmd ( CMD_SetCoordinateY ); /* 设置Y坐标*/ ILI9341_Write_Data ( usY >> 8 ); ILI9341_Write_Data ( usY & 0xff ); ILI9341_Write_Data ( ( usY + usHeight - 1 ) >> 8 ); ILI9341_Write_Data ( ( usY + usHeight - 1) & 0xff ); } /** * @brief 设定ILI9341的光标坐标 * @param usX :在特定扫描方向下光标的X坐标 * @param usY :在特定扫描方向下光标的Y坐标 * @retval 无 */ static void ILI9341_SetCursor ( uint16_t usX, uint16_t usY ) { ILI9341_OpenWindow ( usX, usY, 1, 1 ); } /** * @brief 在ILI9341显示器上以某一颜色填充像素点 * @param ulAmout_Point :要填充颜色的像素点的总数目 * @param usColor :颜色 * @retval 无 */ static __inline void ILI9341_FillColor ( uint32_t ulAmout_Point, uint16_t usColor ) { uint32_t i = 0; /* memory write */ ILI9341_Write_Cmd ( CMD_SetPixel ); for ( i = 0; i < ulAmout_Point; i ++ ) ILI9341_Write_Data ( usColor ); } /** * @brief 对ILI9341显示器的某一窗口以某种颜色进行清屏 * @param usX :在特定扫描方向下窗口的起点X坐标 * @param usY :在特定扫描方向下窗口的起点Y坐标 * @param usWidth :窗口的宽度 * @param usHeight :窗口的高度 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_Clear ( uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight ) { ILI9341_OpenWindow ( usX, usY, usWidth, usHeight ); ILI9341_FillColor ( usWidth * usHeight, CurrentBackColor ); } /** * @brief 对ILI9341显示器的某一点以某种颜色进行填充 * @param usX :在特定扫描方向下该点的X坐标 * @param usY :在特定扫描方向下该点的Y坐标 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_SetPointPixel ( uint16_t usX, uint16_t usY ) { if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ) ) { ILI9341_SetCursor ( usX, usY ); ILI9341_FillColor ( 1, CurrentTextColor ); } } /** * @brief 读取 GRAM 的一个像素数据 * @param 无 * @retval 像素数据 */ static uint16_t ILI9341_Read_PixelData ( void ) { uint16_t usRG=0, usB=0 ; ILI9341_Write_Cmd ( 0x2E ); /* 读数据 */ //去掉前一次读取结果 ILI9341_Read_Data (); /*FIRST READ OUT DUMMY DATA*/ //获取红色通道与绿色通道的值 usRG = ILI9341_Read_Data (); /*READ OUT RED AND GREEN DATA */ usB = ILI9341_Read_Data (); /*READ OUT BLUE DATA*/ return ( (usRG&0xF800)| ((usRG<<3)&0x7E0) | (usB>>11) ); } /** * @brief 获取 ILI9341 显示器上某一个坐标点的像素数据 * @param usX :在特定扫描方向下该点的X坐标 * @param usY :在特定扫描方向下该点的Y坐标 * @retval 像素数据 */ uint16_t ILI9341_GetPointPixel ( uint16_t usX, uint16_t usY ) { uint16_t usPixelData; ILI9341_SetCursor ( usX, usY ); usPixelData = ILI9341_Read_PixelData (); return usPixelData; } /** * @brief 在 ILI9341 显示器上使用 Bresenham 算法画线段 * @param usX1 :在特定扫描方向下线段的一个端点X坐标 * @param usY1 :在特定扫描方向下线段的一个端点Y坐标 * @param usX2 :在特定扫描方向下线段的另一个端点X坐标 * @param usY2 :在特定扫描方向下线段的另一个端点Y坐标 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DrawLine ( uint16_t usX1, uint16_t usY1, uint16_t usX2, uint16_t usY2 ) { uint16_t us; uint16_t usX_Current, usY_Current; int32_t lError_X = 0, lError_Y = 0, lDelta_X, lDelta_Y, lDistance; int32_t lIncrease_X, lIncrease_Y; lDelta_X = usX2 - usX1; //计算坐标增量 lDelta_Y = usY2 - usY1; usX_Current = usX1; usY_Current = usY1; if ( lDelta_X > 0 ) lIncrease_X = 1; //设置单步方向 else if ( lDelta_X == 0 ) lIncrease_X = 0;//垂直线 else { lIncrease_X = -1; lDelta_X = - lDelta_X; } if ( lDelta_Y > 0 ) lIncrease_Y = 1; else if ( lDelta_Y == 0 ) lIncrease_Y = 0;//水平线 else { lIncrease_Y = -1; lDelta_Y = - lDelta_Y; } if ( lDelta_X > lDelta_Y ) lDistance = lDelta_X; //选取基本增量坐标轴 else lDistance = lDelta_Y; for ( us = 0; us <= lDistance + 1; us ++ )//画线输出 { ILI9341_SetPointPixel ( usX_Current, usY_Current );//画点 lError_X += lDelta_X ; lError_Y += lDelta_Y ; if ( lError_X > lDistance ) { lError_X -= lDistance; usX_Current += lIncrease_X; } if ( lError_Y > lDistance ) { lError_Y -= lDistance; usY_Current += lIncrease_Y; } } } /** * @brief 在 ILI9341 显示器上画一个矩形 * @param usX_Start :在特定扫描方向下矩形的起始点X坐标 * @param usY_Start :在特定扫描方向下矩形的起始点Y坐标 * @param usWidth:矩形的宽度(单位:像素) * @param usHeight:矩形的高度(单位:像素) * @param ucFilled :选择是否填充该矩形 * 该参数为以下值之一: * @arg 0 :空心矩形 * @arg 1 :实心矩形 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DrawRectangle ( uint16_t usX_Start, uint16_t usY_Start, uint16_t usWidth, uint16_t usHeight, uint8_t ucFilled ) { if ( ucFilled ) { ILI9341_OpenWindow ( usX_Start, usY_Start, usWidth, usHeight ); ILI9341_FillColor ( usWidth * usHeight ,CurrentTextColor); } else { ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start + usWidth - 1, usY_Start ); ILI9341_DrawLine ( usX_Start, usY_Start + usHeight - 1, usX_Start + usWidth - 1, usY_Start + usHeight - 1 ); ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start, usY_Start + usHeight - 1 ); ILI9341_DrawLine ( usX_Start + usWidth - 1, usY_Start, usX_Start + usWidth - 1, usY_Start + usHeight - 1 ); } } /** * @brief 在 ILI9341 显示器上使用 Bresenham 算法画圆 * @param usX_Center :在特定扫描方向下圆心的X坐标 * @param usY_Center :在特定扫描方向下圆心的Y坐标 * @param usRadius:圆的半径(单位:像素) * @param ucFilled :选择是否填充该圆 * 该参数为以下值之一: * @arg 0 :空心圆 * @arg 1 :实心圆 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DrawCircle ( uint16_t usX_Center, uint16_t usY_Center, uint16_t usRadius, uint8_t ucFilled ) { int16_t sCurrentX, sCurrentY; int16_t sError; sCurrentX = 0; sCurrentY = usRadius; sError = 3 - ( usRadius << 1 ); //判断下个点位置的标志 while ( sCurrentX <= sCurrentY ) { int16_t sCountY; if ( ucFilled ) for ( sCountY = sCurrentX; sCountY <= sCurrentY; sCountY ++ ) { ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center + sCountY ); //1,研究对象 ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center + sCountY ); //2 ILI9341_SetPointPixel ( usX_Center - sCountY, usY_Center + sCurrentX ); //3 ILI9341_SetPointPixel ( usX_Center - sCountY, usY_Center - sCurrentX ); //4 ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center - sCountY ); //5 ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center - sCountY ); //6 ILI9341_SetPointPixel ( usX_Center + sCountY, usY_Center - sCurrentX ); //7 ILI9341_SetPointPixel ( usX_Center + sCountY, usY_Center + sCurrentX ); //0 } else { ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center + sCurrentY ); //1,研究对象 ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center + sCurrentY ); //2 ILI9341_SetPointPixel ( usX_Center - sCurrentY, usY_Center + sCurrentX ); //3 ILI9341_SetPointPixel ( usX_Center - sCurrentY, usY_Center - sCurrentX ); //4 ILI9341_SetPointPixel ( usX_Center - sCurrentX, usY_Center - sCurrentY ); //5 ILI9341_SetPointPixel ( usX_Center + sCurrentX, usY_Center - sCurrentY ); //6 ILI9341_SetPointPixel ( usX_Center + sCurrentY, usY_Center - sCurrentX ); //7 ILI9341_SetPointPixel ( usX_Center + sCurrentY, usY_Center + sCurrentX ); //0 } sCurrentX ++; if ( sError < 0 ) sError += 4 * sCurrentX + 6; else { sError += 10 + 4 * ( sCurrentX - sCurrentY ); sCurrentY --; } } } /** * @brief 在 ILI9341 显示器上显示一个英文字符 * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下该点的起始Y坐标 * @param cChar :要显示的英文字符 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispChar_EN ( uint16_t usX, uint16_t usY, const char cChar ) { uint8_t byteCount, bitCount,fontLength; uint16_t ucRelativePositon; uint8_t *Pfont; //对ascii码表偏移(字模表不包含ASCII表的前32个非图形符号) ucRelativePositon = cChar - ' '; //每个字模的字节数 fontLength = (LCD_Currentfonts->Width*LCD_Currentfonts->Height)/8; //字模首地址 /*ascii码表偏移值乘以每个字模的字节数,求出字模的偏移位置*/ Pfont = (uint8_t *)&LCD_Currentfonts->table[ucRelativePositon * fontLength]; //设置显示窗口 ILI9341_OpenWindow ( usX, usY, LCD_Currentfonts->Width, LCD_Currentfonts->Height); ILI9341_Write_Cmd ( CMD_SetPixel ); //按字节读取字模数据 //由于前面直接设置了显示窗口,显示数据会自动换行 for ( byteCount = 0; byteCount < fontLength; byteCount++ ) { //一位一位处理要显示的颜色 for ( bitCount = 0; bitCount < 8; bitCount++ ) { if ( Pfont[byteCount] & (0x80>>bitCount) ) ILI9341_Write_Data ( CurrentTextColor ); else ILI9341_Write_Data ( CurrentBackColor ); } } } /** * @brief 在 ILI9341 显示器上显示英文字符串 * @param line :在特定扫描方向下字符串的起始Y坐标 * 本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标, * 宏LINE(x)会根据当前选择的字体来计算Y坐标值。 * 显示中文且使用LINE宏时,需要把英文字体设置成Font8x16 * @param pStr :要显示的英文字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispStringLine_EN ( uint16_t line, char * pStr ) { uint16_t usX = 0; while ( * pStr != '\0' ) { if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line += LCD_Currentfonts->Height; } if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line = ILI9341_DispWindow_Y_Star; } ILI9341_DispChar_EN ( usX, line, * pStr); pStr ++; usX += LCD_Currentfonts->Width; } } /** * @brief 在 ILI9341 显示器上显示英文字符串 * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下字符的起始Y坐标 * @param pStr :要显示的英文字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispString_EN ( uint16_t usX ,uint16_t usY, char * pStr ) { while ( * pStr != '\0' ) { if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY += LCD_Currentfonts->Height; } if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } ILI9341_DispChar_EN ( usX, usY, * pStr); pStr ++; usX += LCD_Currentfonts->Width; } } /** * @brief 在 ILI9341 显示器上显示英文字符串(沿Y轴方向) * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下字符的起始Y坐标 * @param pStr :要显示的英文字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispString_EN_YDir ( uint16_t usX,uint16_t usY , char * pStr ) { while ( * pStr != '\0' ) { if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) >LCD_Y_LENGTH ) { usY = ILI9341_DispWindow_Y_Star; usX += LCD_Currentfonts->Width; } if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } ILI9341_DispChar_EN ( usX, usY, * pStr); pStr ++; usY += LCD_Currentfonts->Height; } } /** * @brief 在 ILI9341 显示器上显示一个中文字符 * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下字符的起始Y坐标 * @param usChar :要显示的中文字符(国标码) * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispChar_CH ( uint16_t usX, uint16_t usY, uint16_t usChar ) { uint8_t rowCount, bitCount; uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ]; uint16_t usTemp; //设置显示窗口 ILI9341_OpenWindow ( usX, usY, WIDTH_CH_CHAR, HEIGHT_CH_CHAR ); ILI9341_Write_Cmd ( CMD_SetPixel ); //取字模数据 GetGBKCode ( ucBuffer, usChar ); for ( rowCount = 0; rowCount < HEIGHT_CH_CHAR; rowCount++ ) { /* 取出两个字节的数据,在lcd上即是一个汉字的一行 */ usTemp = ucBuffer [ rowCount * 2 ]; usTemp = ( usTemp << 8 ); usTemp |= ucBuffer [ rowCount * 2 + 1 ]; for ( bitCount = 0; bitCount < WIDTH_CH_CHAR; bitCount ++ ) { if ( usTemp & ( 0x8000 >> bitCount ) ) //高位在前 ILI9341_Write_Data ( CurrentTextColor ); else ILI9341_Write_Data ( CurrentBackColor ); } } } /** * @brief 在 ILI9341 显示器上显示中文字符串 * @param line :在特定扫描方向下字符串的起始Y坐标 * 本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标, * 宏LINE(x)会根据当前选择的字体来计算Y坐标值。 * 显示中文且使用LINE宏时,需要把英文字体设置成Font8x16 * @param pStr :要显示的英文字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispString_CH ( uint16_t usX , uint16_t usY, char * pStr ) { uint16_t usCh; while( * pStr != '\0' ) { if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY += HEIGHT_CH_CHAR; } if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } usCh = * ( uint16_t * ) pStr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); ILI9341_DispChar_CH ( usX, usY, usCh ); usX += WIDTH_CH_CHAR; pStr += 2; //一个汉字两个字节 } } /** * @brief 在 ILI9341 显示器上显示中英文字符串 * @param line :在特定扫描方向下字符串的起始Y坐标 * 本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标, * 宏LINE(x)会根据当前选择的字体来计算Y坐标值。 * 显示中文且使用LINE宏时,需要把英文字体设置成Font8x16 * @param pStr :要显示的字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispStringLine_EN_CH ( uint16_t line, char * pStr ) { uint16_t usCh; uint16_t usX = 0; while( * pStr != '\0' ) { if ( * pStr <= 126 ) //英文字符 { if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line += LCD_Currentfonts->Height; } if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line = ILI9341_DispWindow_Y_Star; } ILI9341_DispChar_EN ( usX, line, * pStr ); usX += LCD_Currentfonts->Width; pStr ++; } else //汉字字符 { if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line += HEIGHT_CH_CHAR; } if ( ( line - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; line = ILI9341_DispWindow_Y_Star; } usCh = * ( uint16_t * ) pStr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); ILI9341_DispChar_CH ( usX, line, usCh ); usX += WIDTH_CH_CHAR; pStr += 2; //一个汉字两个字节 } } } /** * @brief 在 ILI9341 显示器上显示中英文字符串 * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下字符的起始Y坐标 * @param pStr :要显示的字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispString_EN_CH ( uint16_t usX , uint16_t usY, char * pStr ) { uint16_t usCh; while( * pStr != '\0' ) { if ( * pStr <= 126 ) //英文字符 { if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY += LCD_Currentfonts->Height; } if ( ( usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } ILI9341_DispChar_EN ( usX, usY, * pStr ); usX += LCD_Currentfonts->Width; pStr ++; } else //汉字字符 { if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY += HEIGHT_CH_CHAR; } if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) > LCD_Y_LENGTH ) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } usCh = * ( uint16_t * ) pStr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); ILI9341_DispChar_CH ( usX, usY, usCh ); usX += WIDTH_CH_CHAR; pStr += 2; //一个汉字两个字节 } } } /** * @brief 在 ILI9341 显示器上显示中英文字符串(沿Y轴方向) * @param usX :在特定扫描方向下字符的起始X坐标 * @param usY :在特定扫描方向下字符的起始Y坐标 * @param pStr :要显示的中英文字符串的首地址 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色 * @retval 无 */ void ILI9341_DispString_EN_CH_YDir ( uint16_t usX,uint16_t usY , char * pStr ) { uint16_t usCh; while( * pStr != '\0' ) { //统一使用汉字的宽高来计算换行 if ( ( usY - ILI9341_DispWindow_Y_Star + HEIGHT_CH_CHAR ) >LCD_Y_LENGTH ) { usY = ILI9341_DispWindow_Y_Star; usX += WIDTH_CH_CHAR; } if ( ( usX - ILI9341_DispWindow_X_Star + WIDTH_CH_CHAR ) > LCD_X_LENGTH) { usX = ILI9341_DispWindow_X_Star; usY = ILI9341_DispWindow_Y_Star; } //显示 if ( * pStr <= 126 ) //英文字符 { ILI9341_DispChar_EN ( usX, usY, * pStr); pStr ++; usY += HEIGHT_CH_CHAR; } else //汉字字符 { usCh = * ( uint16_t * ) pStr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); ILI9341_DispChar_CH ( usX,usY , usCh ); usY += HEIGHT_CH_CHAR; pStr += 2; //一个汉字两个字节 } } } /***********************缩放字体****************************/ #define ZOOMMAXBUFF 16384 uint8_t zoomBuff[ZOOMMAXBUFF] = {0}; //用于缩放的缓存,最大支持到128*128 uint8_t zoomTempBuff[1024] = {0}; /** * @brief 缩放字模,缩放后的字模由1个像素点由8个数据位来表示 0x01表示笔迹,0x00表示空白区 * @param in_width :原始字符宽度 * @param in_heig :原始字符高度 * @param out_width :缩放后的字符宽度 * @param out_heig:缩放后的字符高度 * @param in_ptr :字库输入指针 注意:1pixel 1bit * @param out_ptr :缩放后的字符输出指针 注意: 1pixel 8bit * out_ptr实际上没有正常输出,改成了直接输出到全局指针zoomBuff中 * @param en_cn :0为英文,1为中文 * @retval 无 */ void ILI9341_zoomChar(uint16_t in_width, //原始字符宽度 uint16_t in_heig, //原始字符高度 uint16_t out_width, //缩放后的字符宽度 uint16_t out_heig, //缩放后的字符高度 uint8_t *in_ptr, //字库输入指针 注意:1pixel 1bit uint8_t *out_ptr, //缩放后的字符输出指针 注意: 1pixel 8bit uint8_t en_cn) //0为英文,1为中文 { uint8_t *pts,*ots; //根据源字模及目标字模大小,设定运算比例因子,左移16是为了把浮点运算转成定点运算 unsigned int xrIntFloat_16=(in_width<<16)/out_width+1; unsigned int yrIntFloat_16=(in_heig<<16)/out_heig+1; unsigned int srcy_16=0; unsigned int y,x; uint8_t *pSrcLine; uint16_t byteCount,bitCount; //检查参数是否合法 if(in_width >= 32) return; //字库不允许超过32像素 if(in_width * in_heig == 0) return; if(in_width * in_heig >= 1024 ) return; //限制输入最大 32*32 if(out_width * out_heig == 0) return; if(out_width * out_heig >= ZOOMMAXBUFF ) return; //限制最大缩放 128*128 pts = (uint8_t*)&zoomTempBuff; //为方便运算,字库的数据由1 pixel/1bit 映射到1pixel/8bit //0x01表示笔迹,0x00表示空白区 if(en_cn == 0x00)//英文 { //英文和中文字库上下边界不对,可在此处调整。需要注意tempBuff防止溢出 for(byteCount=0;byteCount<in_heig*in_width/8;byteCount++) { for(bitCount=0;bitCount<8;bitCount++) { //把源字模数据由位映射到字节 //in_ptr里bitX为1,则pts里整个字节值为1 //in_ptr里bitX为0,则pts里整个字节值为0 *pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0; } } } else //中文 { for(byteCount=0;byteCount<in_heig*in_width/8;byteCount++) { for(bitCount=0;bitCount<8;bitCount++) { //把源字模数据由位映射到字节 //in_ptr里bitX为1,则pts里整个字节值为1 //in_ptr里bitX为0,则pts里整个字节值为0 *pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0; } } } //zoom过程 pts = (uint8_t*)&zoomTempBuff; //映射后的源数据指针 ots = (uint8_t*)&zoomBuff; //输出数据的指针 for (y=0;y<out_heig;y++) /*行遍历*/ { unsigned int srcx_16=0; pSrcLine=pts+in_width*(srcy_16>>16); for (x=0;x<out_width;x++) /*行内像素遍历*/ { ots[x]=pSrcLine[srcx_16>>16]; //把源字模数据复制到目标指针中 srcx_16+=xrIntFloat_16; //按比例偏移源像素点 } srcy_16+=yrIntFloat_16; //按比例偏移源像素点 ots+=out_width; } /*!!!缩放后的字模数据直接存储到全局指针zoomBuff里了*/ out_ptr = (uint8_t*)&zoomBuff; //out_ptr没有正确传出,后面调用直接改成了全局变量指针! /*实际中如果使用out_ptr不需要下面这一句!!! 只是因为out_ptr没有使用,会导致warning。强迫症*/ out_ptr++; } /** * @brief 利用缩放后的字模显示字符 * @param Xpos :字符显示位置x * @param Ypos :字符显示位置y * @param Font_width :字符宽度 * @param Font_Heig:字符高度 * @param c :要显示的字模数据 * @param DrawModel :是否反色显示 * @retval 无 */ void ILI9341_DrawChar_Ex(uint16_t usX, //字符显示位置x uint16_t usY, //字符显示位置y uint16_t Font_width, //字符宽度 uint16_t Font_Height, //字符高度 uint8_t *c, //字模数据 uint16_t DrawModel) //是否反色显示 { uint32_t index = 0, counter = 0; //设置显示窗口 ILI9341_OpenWindow ( usX, usY, Font_width, Font_Height); ILI9341_Write_Cmd ( CMD_SetPixel ); //按字节读取字模数据 //由于前面直接设置了显示窗口,显示数据会自动换行 for ( index = 0; index < Font_Height; index++ ) { //一位一位处理要显示的颜色 for ( counter = 0; counter < Font_width; counter++ ) { //缩放后的字模数据,以一个字节表示一个像素位 //整个字节值为1表示该像素为笔迹 //整个字节值为0表示该像素为背景 if ( *c++ == DrawModel ) ILI9341_Write_Data ( CurrentBackColor ); else ILI9341_Write_Data ( CurrentTextColor ); } } } /** * @brief 利用缩放后的字模显示字符串 * @param Xpos :字符显示位置x * @param Ypos :字符显示位置y * @param Font_width :字符宽度,英文字符在此基础上/2。注意为偶数 * @param Font_Heig:字符高度,注意为偶数 * @param c :要显示的字符串 * @param DrawModel :是否反色显示 * @retval 无 */ void ILI9341_DisplayStringEx(uint16_t x, //字符显示位置x uint16_t y, //字符显示位置y uint16_t Font_width, //要显示的字体宽度,英文字符在此基础上/2。注意为偶数 uint16_t Font_Height, //要显示的字体高度,注意为偶数 uint8_t *ptr, //显示的字符内容 uint16_t DrawModel) //是否反色显示 { uint16_t Charwidth = Font_width; //默认为Font_width,英文宽度为中文宽度的一半 uint8_t *psr; uint8_t Ascii; //英文 uint16_t usCh; //中文 uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ]; while ( *ptr != '\0') { /****处理换行*****/ if ( ( x - ILI9341_DispWindow_X_Star + Charwidth ) > LCD_X_LENGTH ) { x = ILI9341_DispWindow_X_Star; y += Font_Height; } if ( ( y - ILI9341_DispWindow_Y_Star + Font_Height ) > LCD_Y_LENGTH ) { x = ILI9341_DispWindow_X_Star; y = ILI9341_DispWindow_Y_Star; } if(*ptr > 0x80) //如果是中文 { Charwidth = Font_width; usCh = * ( uint16_t * ) ptr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); GetGBKCode ( ucBuffer, usCh ); //取字模数据 //缩放字模数据,源字模为16*16 ILI9341_zoomChar(WIDTH_CH_CHAR,HEIGHT_CH_CHAR,Charwidth,Font_Height,(uint8_t *)&ucBuffer,psr,1); //显示单个字符 ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel); x+=Charwidth; ptr+=2; } else { Charwidth = Font_width / 2; Ascii = *ptr - 32; //使用16*24字体缩放字模数据 ILI9341_zoomChar(16,24,Charwidth,Font_Height,(uint8_t *)&Font16x24.table[Ascii * Font16x24.Height*Font16x24.Width/8],psr,0); //显示单个字符 ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel); x+=Charwidth; ptr++; } } } /** * @brief 利用缩放后的字模显示字符串(沿Y轴方向) * @param Xpos :字符显示位置x * @param Ypos :字符显示位置y * @param Font_width :字符宽度,英文字符在此基础上/2。注意为偶数 * @param Font_Heig:字符高度,注意为偶数 * @param c :要显示的字符串 * @param DrawModel :是否反色显示 * @retval 无 */ void ILI9341_DisplayStringEx_YDir(uint16_t x, //字符显示位置x uint16_t y, //字符显示位置y uint16_t Font_width, //要显示的字体宽度,英文字符在此基础上/2。注意为偶数 uint16_t Font_Height, //要显示的字体高度,注意为偶数 uint8_t *ptr, //显示的字符内容 uint16_t DrawModel) //是否反色显示 { uint16_t Charwidth = Font_width; //默认为Font_width,英文宽度为中文宽度的一半 uint8_t *psr; uint8_t Ascii; //英文 uint16_t usCh; //中文 uint8_t ucBuffer [ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8 ]; while ( *ptr != '\0') { //统一使用汉字的宽高来计算换行 if ( ( y - ILI9341_DispWindow_X_Star + Font_width ) > LCD_X_LENGTH ) { y = ILI9341_DispWindow_X_Star; x += Font_width; } if ( ( x - ILI9341_DispWindow_Y_Star + Font_Height ) > LCD_Y_LENGTH ) { y = ILI9341_DispWindow_X_Star; x = ILI9341_DispWindow_Y_Star; } if(*ptr > 0x80) //如果是中文 { Charwidth = Font_width; usCh = * ( uint16_t * ) ptr; usCh = ( usCh << 8 ) + ( usCh >> 8 ); GetGBKCode ( ucBuffer, usCh ); //取字模数据 //缩放字模数据,源字模为16*16 ILI9341_zoomChar(WIDTH_CH_CHAR,HEIGHT_CH_CHAR,Charwidth,Font_Height,(uint8_t *)&ucBuffer,psr,1); //显示单个字符 ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel); y+=Font_Height; ptr+=2; } else { Charwidth = Font_width / 2; Ascii = *ptr - 32; //使用16*24字体缩放字模数据 ILI9341_zoomChar(16,24,Charwidth,Font_Height,(uint8_t *)&Font16x24.table[Ascii * Font16x24.Height*Font16x24.Width/8],psr,0); //显示单个字符 ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel); y+=Font_Height; ptr++; } } } /** * @brief 设置英文字体类型 * @param fonts: 指定要选择的字体 * 参数为以下值之一 * @arg:Font24x32; * @arg:Font16x24; * @arg:Font8x16; * @retval None */ void LCD_SetFont(sFONT *fonts) { LCD_Currentfonts = fonts; } /** * @brief 获取当前字体类型 * @param None. * @retval 返回当前字体类型 */ sFONT *LCD_GetFont(void) { return LCD_Currentfonts; } /** * @brief 设置LCD的前景(字体)及背景颜色,RGB565 * @param TextColor: 指定前景(字体)颜色 * @param BackColor: 指定背景颜色 * @retval None */ void LCD_SetColors(uint16_t TextColor, uint16_t BackColor) { CurrentTextColor = TextColor; CurrentBackColor = BackColor; } /** * @brief 获取LCD的前景(字体)及背景颜色,RGB565 * @param TextColor: 用来存储前景(字体)颜色的指针变量 * @param BackColor: 用来存储背景颜色的指针变量 * @retval None */ void LCD_GetColors(uint16_t *TextColor, uint16_t *BackColor) { *TextColor = CurrentTextColor; *BackColor = CurrentBackColor; } /** * @brief 设置LCD的前景(字体)颜色,RGB565 * @param Color: 指定前景(字体)颜色 * @retval None */ void LCD_SetTextColor(uint16_t Color) { CurrentTextColor = Color; } /** * @brief 设置LCD的背景颜色,RGB565 * @param Color: 指定背景颜色 * @retval None */ void LCD_SetBackColor(uint16_t Color) { CurrentBackColor = Color; } /** * @brief 清除某行文字 * @param Line: 指定要删除的行 * 本参数可使用宏LINE(0)、LINE(1)等方式指定要删除的行, * 宏LINE(x)会根据当前选择的字体来计算Y坐标值,并删除当前字体高度的第x行。 * @retval None */ void LCD_ClearLine(uint16_t Line) { ILI9341_Clear(0,Line,LCD_X_LENGTH,((sFONT *)LCD_GetFont())->Height); /* 清屏,显示全黑 */ } /*********************end of file*************************/ HX711的代码为#include "./HX711/HX711.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include <stdint.h> uint32_t HX711_Buffer; uint32_t Weight_Maopi; int32_t Weight_Shiwu; float GapValue = 106.0f; // 根据实际传感器校准 // 简单微秒延时函数,需根据主频调整 static void Delay_us(uint32_t us) { us *= 72; // 假设72MHz主频,调整此系数 while(us--) { __NOP(); } } void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 使能GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置SCK为推挽输出 GPIO_InitStruct.GPIO_Pin = HX711_SCK_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(HX711_SCK_PORT, &GPIO_InitStruct); // 配置DT为输入上拉 GPIO_InitStruct.GPIO_Pin = HX711_DT_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(HX711_DT_PORT, &GPIO_InitStruct); GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_RESET); } uint32_t HX711_Read(void) { uint32_t count = 0; uint8_t i; GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_RESET); Delay_us(1); // 等待DT准备好 while(GPIO_ReadInputDataBit(HX711_DT_PORT, HX711_DT_PIN)); // 读取24位数据 for(i = 0; i < 24; i++) { GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_SET); Delay_us(1); count <<= 1; GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_RESET); Delay_us(1); if(GPIO_ReadInputDataBit(HX711_DT_PORT, HX711_DT_PIN)) { count++; } } // 设置增益为128 GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_SET); count ^= 0x800000; // 符号位转换 Delay_us(1); GPIO_WriteBit(HX711_SCK_PORT, HX711_SCK_PIN, Bit_RESET); return count; } void Get_Maopi(void) { Weight_Maopi = HX711_Read(); } void Get_Weight(void) { HX711_Buffer = HX711_Read(); if(HX711_Buffer > Weight_Maopi) { Weight_Shiwu = HX711_Buffer - Weight_Maopi; Weight_Shiwu = (int32_t)((float)Weight_Shiwu / GapValue + 0.5f); } else { Weight_Shiwu = 0; } } 按键的代码为#include "./key/bsp_key.h" /** * @brief 配置按键用到的I/O口 * @param 无 * @retval 无 */ void Key_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; /*开启按键端口的时钟*/ RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE); //选择按键的引脚 GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; // 设置按键的引脚为浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //使用结构体初始化按键 GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); //选择按键的引脚 GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; //设置按键的引脚为浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //使用结构体初始化按键 GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); } /* * 函数名:Key_Scan * 描述 :检测是否有按键按下 * 输入 :GPIOx:x 可以是 A,B,C,D或者 E * GPIO_Pin:待读取的端口位 * 输出 :KEY_OFF(没按下按键)、KEY_ON(按下按键) */ uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin) { /*检测是否有按键按下 */ if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON ) { /*等待按键释放 */ while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON); return KEY_ON; } else return KEY_OFF; } /*********************************************END OF FILE**********************/ 其中ILI9341屏幕为外部FLASH模式,现根据以上代码编写出实现以下功能的代码:屏幕第一行为重量的显示,第二行为单价的显示,第三行为总价的显示,单价初始为1.2,单价K1按下加0.1,K2按下减0.1。总价为重量乘以单价,重量以克为单位。总价也实时显示出来。给出详细的代码和讲解,用标准库写,用KEIL编译。
05-30
<think>好的,我现在需要帮用户解决基于STM32F103ZET6平台,使用ILI9341屏幕、HX711称重传感器和按键模块,实现重量、单价和总价实时显示的问题。用户已经提供了代码和功能需求,需要详细代码和讲解,使用标准库编写并在KEIL中编译。首先,我得理清楚各个模块的功能和它们之间的交互。首先,HX711是称重传感器,负责读取重量数据。我需要查阅HX711的数据手册,了解其通信协议,通常是使用GPIO模拟的SPI协议。STM32需要配置对应的GPIO引脚作为输入和输出,控制时钟和数据线的读写。然后,ILI9341是TFT屏幕,需要初始化SPI接口,使用FSMC或者GPIO模拟SPI来驱动显示。用户提到使用标准库,所以可能需要配置FSMC的相关寄存器,或者直接使用SPI外设。按键模块部分,K1和K2分别用于增加和减少单价。需要配置GPIO为输入模式,并启用外部中断或者轮询检测按键状态。考虑到实时性,可能外部中断更合适,但需要处理消抖问题。单价初始值为1.2,每次按键调整0.1,总价是重量乘以单价,重量单位为克。显示部分需要实时更新,所以可能需要定时器中断来定期刷新屏幕,或者在有数据变化时立即刷新。 接下来,代码结构需要分模块处理:HX711的驱动、ILI9341的显示驱动、按键处理、主程序逻辑。可能还需要用到定时器来定时读取HX711的数据,避免主循环阻塞。同时,浮点数的处理需要注意,STM32F103是否支持硬件浮点,可能需要使用软件浮点库,或者在代码中避免频繁的浮点运算,改用整数运算。然后,编写HX711的驱动部分。HX711的数据输出是24位的补码,需要连续读取时钟信号来获取数据。STM32的GPIO需要配置为推挽输出(时钟)和输入(数据)。读取数据时,先拉低时钟,等待HX711准备好数据(数据线变低),然后循环24次时钟脉冲,读取每一位数据,最后再一个脉冲选择增益。需要注意的是,数据是大端还是小端,补码转换为有符号整数,再转换为重量值。校准部分可能需要用户事先校准,存储零点和比例系数到EEPROM或者Flash中。显示部分,ILI9341通常使用SPI或FSMC接口。标准库中可能需要初始化对应的外设,编写画点、填充区域、显示字符串等函数。显示重量、单价、总价时,需要将浮点数转换为字符串,可以使用sprintf函数,但要注意格式化和内存使用。例如,单价显示为两位小数,总价也是两位小数。由于屏幕刷新可能比较耗时,需要优化刷新区域,只更新变化的部分,而不是全屏刷新。按键处理部分,K1和K2需要配置为输入,并启用上拉或下拉电阻。使用外部中断时,需要在中断服务函数中检测按键按下,并进行消抖处理,比如延时一段时间再次检测状态。或者在主循环中轮询按键状态,使用状态机处理按键的按下、释放和长按。每次按键按下,单价增加或减少0.1,并限制单价的最小值,比如不能低于0.1。主程序逻辑中,初始化各外设后,进入主循环,不断读取重量数据,计算总价,并更新显示。同时检测按键事件,调整单价。可能需要使用临界区保护共享变量,比如单价可能在主循环和中断中被修改,需使用volatile关键字或关中断来保护数据一致性。 此外,需要考虑HX711的数据读取频率,过高的读取可能导致处理不过来,所以定时器中断每隔一定时间(比如100ms)触发一次读取,并更新重量值。重量数据可能需要滤波处理,比如移动平均或中值滤波,以减少噪声干扰。最后,代码需要在KEIL中编译,确保所有头文件和库路径正确,编译器选项配置正确,特别是浮点处理选项。可能需要包含STM32的标准外设库,以及ILI9341和HX711的驱动代码。调试时,可以通过串口输出调试信息,或者使用KEIL的调试器查看变量值。 现在需要将这些思路转化为具体的代码,分模块编写,并确保各部分协同工作。测试时,先单独测试每个模块的功能,比如HX711是否能正确读取数据,屏幕是否能正常显示,按键是否能触发中断,然后再整合整个系统。</think>根据需求实现的STM32代码分为硬件驱动层、业务逻辑层和数据显示层三部分。以下是基于标准库的完整实现方案: 一、硬件驱动层实现 ```c // HX711驱动 #define HX711_CLK GPIO_Pin_0 #define HX711_DATA GPIO_Pin_1 int32_t HX711_Read(void) { int32_t data = 0; GPIO_ResetBits(GPIOB, HX711_CLK); // 拉低时钟 while(GPIO_ReadInputDataBit(GPIOB, HX711_DATA)); // 等待数据就绪 for(uint8_t i=0; i<24; i++){ // 读取24位数据 GPIO_SetBits(GPIOB, HX711_CLK); data <<= 1; if(GPIO_ReadInputDataBit(GPIOB, HX711_DATA)) data++; GPIO_ResetBits(GPIOB, HX711_CLK); } // 选择增益128 GPIO_SetBits(GPIOB, HX711_CLK); GPIO_ResetBits(GPIOB, HX711_CLK); return data ^ 0x800000; // 转换为有符号数 } // ILI9341显示驱动 void ILI9341_DisplayString(uint16_t x, uint16_t y, uint8_t* str) { ILI9341_SetCursor(x, y); while(*str) { ILI9341_WriteData(*str++); } } // 按键初始化 void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // K1 PA0, K2 PA1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); } ``` 二、业务逻辑层 ```c volatile float price = 1.2; // 全局单价变量 // 按键中断服务函数 void EXTI0_IRQHandler(void) { // K1增加 if(EXTI_GetITStatus(EXTI_Line0) != RESET) { price += 0.1; EXTI_ClearITPendingBit(EXTI_Line0); } } void EXTI1_IRQHandler(void) { // K2减少 if(EXTI_GetITStatus(EXTI_Line1) != RESET) { if(price > 0.1) price -= 0.1; EXTI_ClearITPendingBit(EXTI_Line1); } } // 重量计算 float get_weight(void) { static int32_t tare = 0; // 去皮值 static float scale = 1.0/108000.0; // 校准系数 int32_t raw = HX711_Read(); return (raw - tare) * scale; } ``` 三、主程序逻辑 ```c int main(void) { SystemInit(); ILI9341_Init(); KEY_Init(); HX711_Init(); // 初始化显示界面 ILI9341_DisplayString(10, 50, "Weight(g):"); ILI9341_DisplayString(10, 80, "Price(¥):"); ILI9341_DisplayString(10, 110, "Total(¥):"); while(1) { float weight = get_weight(); float total = weight * price; // 更新显示 char buf[20]; sprintf(buf, "%.1f", weight); ILI9341_DisplayString(100, 50, buf); sprintf(buf, "%.2f", price); ILI9341_DisplayString(100, 80, buf); sprintf(buf, "%.2f", total); ILI9341_DisplayString(100, 110, buf); Delay(100); // 100ms刷新周期 } } ``` 代码逻辑解析: 1. 硬件驱动层采用模块化设计,确保各外设独立工作 2. HX711通过GPIO模拟时序实现24位AD数据采集,使用异或运算处理补码转换 3. 单价使用volatile修饰确保中断与主程序间的数据可见性 4. 显示刷新周期100ms平衡实时性与屏幕刷新率需求 5. 重量计算包含去皮(tare)和校准系数(scale)两个关键参数[^1] 6. 按键采用外部中断方式实现即时响应
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值