基于stm32F103的座面声控台灯

1.基本内容:

设计一个放置在桌面使用的台灯,使用220v交流电供电。具备显示屏能够实时显示日期(年、月、日和星期),时间(小时、分钟、秒)和温度(摄氏度);能够通过语音交互播报实时日期、时间或者温度;能够通过语音交互控制桌面台灯的开启与关闭(或者明暗程度)。设计符合安全规范,符合日常家居使用习惯,操作简便,符合人性化需求。

2.演示视频

桌面指针式时钟与语音台灯设计

3.设计准备

LD3320语音识别模块、SYN6288语音合成模块、DHT11温湿度传感器模块+SL-Link下载器、STM32F103VET6、杜邦线、ILI9341液晶屏(野火stm32f103可一起购买)。

4.代码讲解

syn6288.c

objectivec 复制代码
#include "syn6288.h"
#include "stdarg.h"
 
xSYN6288_TypeDef  xSYN6288;                // 全局变量结构体
 
static void delay_ms(uint32_t ms)          // 简单的延时函数
{
	 uint32_t i = 0;
    ms = ms * 11993;                       
    for (; i < ms; i++);    
}
 
 
//Music:选择背景音乐。0:无背景音乐,1~15:选择背景音乐
// SYN6288_SendFrameInfo(0, "[v10][m1][t5]结算金额 为32.8元");
// 参数: 0~15  :  背景音乐,0_无背景音乐,1~15_背景音乐可选
//       [V0~16]:  文本朗读音量,0_最小,16_最大
//       [m0~16]:  背景音乐音量,0_最小,16_最大
//       [t0~5]:   朗读语速,0_最慢,5_最快
//       其它不常用功能请参考数据手册
static void SYN6288_SendFrameInfo(uint8_t Music, uint8_t *HZdata)
{
    /****************需要发送的文本**********************************/
    unsigned  char  Frame_Info[50];
    unsigned  char  HZ_Length;
    unsigned  char  ecc  = 0;             //定义校验字节
    unsigned  int i = 0;
    HZ_Length = strlen((char *)HZdata);   //需要发送文本的长度
 
    /*****************帧固定配置信息**************************************/
    Frame_Info[0] = 0xFD ;                //构造帧头FD
    Frame_Info[1] = 0x00 ;                //构造数据区长度的高字节
    Frame_Info[2] = HZ_Length + 3;        //构造数据区长度的低字节
    Frame_Info[3] = 0x01 ;                //构造命令字:合成播放命令
    Frame_Info[4] = 0x01 | Music << 4 ;   //构造命令参数:背景音乐设定
 
    /*******************校验码计算***************************************/
    for (i = 0; i < 5; i++)               //依次发送构造好的5个帧头字节
        ecc = ecc ^ (Frame_Info[i]);      //对发送的字节进行异或校验
 
    for (i = 0; i < HZ_Length; i++)       //依次发送待合成的文本数据
        ecc = ecc ^ (HZdata[i]);          //对发送的字节进行异或校验
    /*******************发送帧信息***************************************/
    memcpy(&Frame_Info[5], HZdata, HZ_Length);
    Frame_Info[5 + HZ_Length] = ecc;
    if (xSYN6288.USARTx == USART1)   USART1_SendData(Frame_Info, 5 + HZ_Length + 1);
    if (xSYN6288.USARTx == USART2)   USART2_SendData(Frame_Info, 5 + HZ_Length + 1);
    if (xSYN6288.USARTx == USART3)   USART3_SendData(Frame_Info, 5 + HZ_Length + 1);
    if (xSYN6288.USARTx == UART4)    UART4_SendData(Frame_Info, 5 + HZ_Length + 1);
    if (xSYN6288.USARTx == UART5)    UART5_SendData(Frame_Info, 5 + HZ_Length + 1);
}
 
 
 
/***********************************************************
* 名    称: SYN6288_Set(uint8_t *Info_data)
* 功    能: 主函数 程序入口
* 入口参数: *Info_data:固定的配置信息变量
* 出口参数:
* 说    明:本函数用于配置,停止合成、暂停合成等设置 ,默认波特率9600bps。
* 调用方法:通过调用已经定义的相关数组进行配置。
**********************************************************/
static void SYN6288_Set(uint8_t *Info_data)
{
    uint8_t Com_Len;
    Com_Len = strlen((char *)Info_data);
    UART5_SendData(Info_data, Com_Len);
}
 
 
/******************************************************************************
 * 函  数: SYN6288_Say
 * 功  能: 输出合成语音
 * 参  数: 格式化参数,如printf参数般一样的用法
 * 返回值: 无
 * 示  例: SYN6288_Say("你好吗?");
*******************************************************************************/
void SYN6288_Say(char *fmt, ...)
{
    static char  str_1[200];   // 缓存区1,模块每次可转换200字节
    static char  str_2[200];   // 缓存区2,模块每次可转换200字节
    va_list ap;
    va_start(ap, fmt);
    vsprintf(str_1, fmt, ap);
    va_end(ap);
    sprintf(str_2, "[d][V12][m15][t5]%s", str_1);  // [d]恢复默认状态,[V12]朗读音量0~16,[m15]背景音量0~16,[t5]语速0~5
    SYN6288_SendFrameInfo(0, (uint8_t *)str_2); // 无背景音乐
}
 
/******************************************************************************
 * 函  数: SYN6288_Init
 * 功  能: 初始化所用串口, 模块默认通信波特率9600
 * 参  数: 串口-USARTx
 * 返回值: 无
 * 示  例: SYN6288_Init(USART1);
*******************************************************************************/
void SYN6288_Init(USART_TypeDef *USARTx)
{
    uint16_t baudrate = 9600;      // 默认波特率9600bps。
    delay_ms(200);                 // 上电后,稍作延时,等待模块进入稳定状态
    if (USARTx == USART1)    USART1_Init(baudrate);
    if (USARTx == USART2)    USART2_Init(baudrate);
    if (USARTx == USART3)    USART3_Init(baudrate);
#ifdef STM32F10X_HD
    if (USARTx == UART4)     UART4_Init(baudrate);
    if (USARTx == UART5)     UART5_Init(baudrate);
#endif
    xSYN6288.FlagOkay = 0;         // 初始化状态
    xSYN6288.USARTx   = USARTx;    // 记录所用串口端口
}

DHT11.c

objectivec 复制代码
#include "DTH11.h"
#include "bsp_systick.h"
#include "stdio.h"
static void                           DHT11_GPIO_Config                       ( void );
static void                           DHT11_Mode_IPU                          ( void );
static void                           DHT11_Mode_Out_PP                       ( void );
static uint8_t                        DHT11_ReadByte                          ( void );


/**
  * @brief  DHT11 初始化函数
  * @param  无
  * @retval 无
  */
void DHT11_Init ( void )
{
        DHT11_GPIO_Config ();
       
        DHT11_Dout_1;               // 拉高GPIOB10
}

/*
* 函数名:DHT11_GPIO_Config
* 描述  :配置DHT11用到的I/O口
* 输入  :无
* 输出  :无
*/
static void DHT11_GPIO_Config ( void )
{               
        /*定义一个GPIO_InitTypeDef类型的结构体*/
        GPIO_InitTypeDef GPIO_InitStructure;

       
        /*开启DHT11_Dout_GPIO_PORT的外设时钟*/
			DHT11_Dout_SCK_APBxClock_FUN ( DHT11_Dout_GPIO_CLK, ENABLE );       

        /*选择要控制的DHT11_Dout_GPIO_PORT引脚*/                                                                                                                          
          GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;       

        /*设置引脚模式为通用推挽输出*/
          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

        /*设置引脚速率为50MHz */   
          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

        /*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
          GPIO_Init ( DHT11_Dout_GPIO_PORT, &GPIO_InitStructure );                  
       
}

/*
* 函数名:DHT11_Mode_IPU
* 描述  :使DHT11-DATA引脚变为上拉输入模式
* 输入  :无
* 输出  :无
*/
static void DHT11_Mode_IPU(void)
{
          GPIO_InitTypeDef GPIO_InitStructure;

                  /*选择要控制的DHT11_Dout_GPIO_PORT引脚*/       
          GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;

           /*设置引脚模式为浮空输入模式*/
          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;

          /*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
          GPIO_Init(DHT11_Dout_GPIO_PORT, &GPIO_InitStructure);         
       
}

/*
* 函数名:DHT11_Mode_Out_PP
* 描述  :使DHT11-DATA引脚变为推挽输出模式
* 输入  :无
* 输出  :无
*/
static void DHT11_Mode_Out_PP(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;

                 /*选择要控制的DHT11_Dout_GPIO_PORT引脚*/                                                                                                                          
          GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;       

        /*设置引脚模式为通用推挽输出*/
          GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

        /*设置引脚速率为50MHz */   
          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

        /*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
          GPIO_Init(DHT11_Dout_GPIO_PORT, &GPIO_InitStructure);                  
       
}

/*
* 从DHT11读取一个字节,MSB先行
*/
static uint8_t DHT11_ReadByte ( void )
{
        uint8_t i, temp=0;
       

        for(i=0;i<8;i++)   
        {         
                /*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/  
                while(DHT11_Dout_IN()==Bit_RESET);

                /*DHT11 以26~28us的高电平表示"0",以70us高电平表示"1",
                 *通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
                 */
                Systick_Delay_us(40); //延时x us 这个延时需要大于数据0持续的时间即可                     

                if(DHT11_Dout_IN()==Bit_SET)/* x us后仍为高电平表示数据"1" */
                {
                        /* 等待数据1的高电平结束 */
                        while(DHT11_Dout_IN()==Bit_SET);

                        temp|=(uint8_t)(0x01<<(7-i));  //把第7-i位置1,MSB先行
                }
                else         // x us后为低电平表示数据"0"
                {                          
                        temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
                }
        }
       
        return temp;
       
}


/*
* 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{  
        /*输出模式*/
        DHT11_Mode_Out_PP();
        /*主机拉低*/
        DHT11_Dout_0;
        /*延时18ms*/
        Systick_Delay_ms(18);

        /*总线拉高 主机延时30us*/
        DHT11_Dout_1;

        Systick_Delay_us(30);   //延时30us

        /*主机设为输入 判断从机响应信号*/
        DHT11_Mode_IPU();

        /*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/   
        if(DHT11_Dout_IN()==Bit_RESET)     
        {
                /*轮询直到从机发出 的80us 低电平 响应信号结束*/  
                while(DHT11_Dout_IN()==Bit_RESET);

                /*轮询直到从机发出的 80us 高电平 标置信号结束*/
                while(DHT11_Dout_IN()==Bit_SET);

                /*开始接收数据*/   
                DHT11_Data->humi_int= DHT11_ReadByte();

                DHT11_Data->humi_deci= DHT11_ReadByte();

                DHT11_Data->temp_int= DHT11_ReadByte();

                DHT11_Data->temp_deci= DHT11_ReadByte();

                DHT11_Data->check_sum= DHT11_ReadByte();


                /*读取结束,引脚改为输出模式*/
                DHT11_Mode_Out_PP();
                /*主机拉高*/
                DHT11_Dout_1;

                /*检查读取的数据是否正确*/
                if(DHT11_Data->check_sum == DHT11_Data->humi_int + DHT11_Data->humi_deci + DHT11_Data->temp_int+ DHT11_Data->temp_deci)
                        return SUCCESS;
                else
                        return ERROR;
        }
       
        else
                return ERROR;
       
}

ili9341.c

objectivec 复制代码
/**
  ******************************************************************************
  * @file    bsp_ili9341_lcd.c
  * @version V1.0
  * @date    2013-xx-xx
  * @brief   ili9341液晶屏驱动
  ******************************************************************************
  * @attention
  *
  * 实验平台:秉火 F103-指南者 STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :http://firestm32.taobao.com
  *
  ******************************************************************************
  */ 

#include "./lcd/bsp_ili9341_lcd.h"
#include "./font/fonts.h"	
#include <math.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;




/**
  * @brief  向ILI9341写入命令
  * @param  usCmd :要写入的命令(表寄存器地址)
  * @retval 无
  */	
__inline void ILI9341_Write_Cmd ( uint16_t usCmd )
{
	* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_CMD ) = usCmd;
	
}


/**
  * @brief  向ILI9341写入数据
  * @param  usData :要写入的数据
  * @retval 无
  */	
__inline void ILI9341_Write_Data ( uint16_t usData )
{
	* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) = usData;
	
}


/**
  * @brief  从ILI9341读取数据
  * @param  无
  * @retval 读取到的数据
  */	
__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;
	FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 	
	
	/* 使能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_AsynchronousWait		  =FSMC_AsynchronousWait_Disable;
	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 )
{
	/*  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 );
	
	/* 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 ); 
	
	
}


/**
 * @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  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轴

------------------------------------------------------------
模式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)

 *******************************************************/
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 ); 
	ILI9341_Write_Data ( 0x08 |(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, CYAN );
	}
	
}


/**
 * @brief  对ILI9341显示器的某一点以某种颜色进行填充
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @param  fill:0和1选择填充前景色或背景色
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_SetPointPixel_Fill ( uint16_t usX, uint16_t usY ,uint8_t fill)	
{	
	if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH )&& fill)
  {
		ILI9341_SetCursor ( usX, usY );
		
		ILI9341_FillColor ( 1, CurrentTextColor );
	}
	else
	{
		ILI9341_SetCursor ( usX, usY );
		
		ILI9341_FillColor ( 1, CurrentBackColor );
	}
	
}


/**
 * @brief  对ILI9341显示器的某一点以某种颜色进行填充
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @param  color:选择某种颜色进行填充
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_SetPointPixel_Color ( uint16_t usX, uint16_t usY ,uint16_t color)	
{	
	if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ))
  {
		ILI9341_SetCursor ( usX, usY );
		
		ILI9341_FillColor ( 1, color );
	}
	
}
/**
 * @brief  读取ILI9341 GRAN 的一个像素数据
 * @param  无
 * @retval 像素数据
 */
static uint16_t ILI9341_Read_PixelData ( void )	
{	
	uint16_t usR=0, usG=0, usB=0 ;

	
	ILI9341_Write_Cmd ( 0x2E );   /* 读数据 */
	
	usR = ILI9341_Read_Data (); 	/*FIRST READ OUT DUMMY DATA*/
	
	usR = ILI9341_Read_Data ();  	/*READ OUT RED DATA  */
	usB = ILI9341_Read_Data ();  	/*READ OUT BLUE DATA*/
	usG = ILI9341_Read_Data ();  	/*READ OUT GREEN DATA*/	
	
  return ( ( ( usR >> 11 ) << 11 ) | ( ( usG >> 10 ) << 5 ) | ( 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; 
		} 
		
	}  
	
	
}   


/*
函数功能:任意角度画直线 
参    数:
	usX,usY:坐标
	usAngle :度数
	usRadius:半径
	usLength  :线段的长度
	c  :颜色值 0或者1
例如:ILI9341_DrawAngleLine(60,30,45,20,20,1);//角度画线
*/
void 	ILI9341_DrawAngleLine( uint32_t usX, uint32_t usY, float usAngle, uint32_t usRadius, uint32_t usLength,uint16_t color)
{
	int i;
	int x0,y0;
	double k=usAngle*(3.1415926535/180);	
	for(i=usRadius;i<usLength;i++)
	{
		x0=cos(k)*i;
		y0=sin(k)*i;
		ILI9341_SetPointPixel_Color(usX+x0,usY+y0,color);
	}
}




/*
函数功能:任意角度画直线 
参    数:
	usX,usY:坐标
	usRadius :度数
	usLength :线段的长度
	c  :颜色值 0或者1
例如:OLED_DrawAngleLine(60,30,45,20,20,1);//角度画线
*/
void 	ILI9341_DrawAngleLine2( uint32_t usX, uint32_t usY,float usAngle,uint32_t usRadius,uint32_t usLength,uint8_t fill)
{
	uint32_t i;
	int x0,y0;
	double k=usAngle*(3.1415926535L/180);
   
	for(i=usRadius;i<usLength;i++)
	{
		x0=cos(k)*i;
		y0=sin(k)*i;
		ILI9341_SetPointPixel_Fill(usX+x0,usY+y0,fill);
	}
}


/**
 * @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  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_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  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 显示器上显示中英文字符串
 * @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  设置英文字体类型
  * @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);	/* 清屏,显示全黑 */

}



/***********************缩放字体****************************/
#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_Y_LENGTH )
			{
				y = ILI9341_DispWindow_X_Star;
				x += Font_width;
			}
			
			if ( ( x - ILI9341_DispWindow_Y_Star + Font_Height ) > LCD_X_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++;
		}
	}
}



/*********************end of file*************************/

LD3320.c/usart.c

objectivec 复制代码
#include "bsp_usart.h"
#include <string.h>

 /**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);

	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}



/**
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  无
  * @retval 无
  */
static void NVIC_USART3_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;//使能中断接收
  
  /* 嵌套向量中断控制器组选择 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
  /* 抢断优先级*/
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
  
}



/*
PB10   :TXD
PB11   : RXD
*/

//串口IO初始化函数
void USART3_Init(uint32_t baudrate)
{
	GPIO_InitTypeDef GPIO_InitStructure;         //IO端口的初始化
	USART_InitTypeDef USART_InitStructure;			 //串口的初始化       
	
	RCC_APB2PeriphClockCmd(Ld3320_USART_GPIO_CLK, ENABLE);  //使能IO端口的时钟
   RCC_APB1PeriphClockCmd(Ld3320_USART_CLK, ENABLE); //使能串口的时钟
	
	//发送
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin  = Ld3320_USART_TX_GPIO_PIN;  //发送引脚
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//接收
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin  = Ld3320_USART_RX_GPIO_PIN;  //接收引脚
	GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

	USART_InitStructure.USART_BaudRate = baudrate;    //设置传输的波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //设置传输一帧数据的数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;       //一位停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;					 //无奇偶校验位
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  //能使接收的发送
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //无硬件流控制
	USART_Init(Ld3320_USARTx,&USART_InitStructure);
	
	NVIC_USART3_Configuration();
	
	USART_ITConfig(Ld3320_USARTx,USART_IT_RXNE,ENABLE); //使能串口中断
   USART_Cmd(Ld3320_USARTx,ENABLE);               //使能串口2
	
}

/*
函数名:USART3串口发送函数
功能:  发送数据
入口参数:发送的字符
*/
void USART3_SendString(u8 *str)
{
		u8 index=0;
	  do
		{
					USART_SendData(USART3,str[index]);  //逐一的发送数组中的内容
					while(USART_GetFlagStatus(USART3,USART_FLAG_TXE) == RESET);  //判断是否发送完 发完为高电平
					index++;
		}
    while(str[index] != 0);   //检查字符串结束标志
}


xUSATR_TypeDef  xUSART;         // 声明为全局变量,方便记录信息、状态




//   USART-1   //
/
/******************************************************************************
 * 函  数: vUSART1_Init
 * 功  能: 初始化USART1的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void USART1_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;                           // 使能USART1时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                             // 使能GPIOA时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 2 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(USART1);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART1, &USART_InitStructure);                       // 初始化串口

    USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);                  // 使能空闲中断

    USART_Cmd(USART1, ENABLE);                                      // 使能串口, 开始工作

    USART1->SR = ~(0x00F0);                                         // 清理中断

    xUSART.USART1InitFlag = 1;                                      // 标记初始化标志
    xUSART.USART1ReceivedNum = 0;                                   // 接收字节数清零

    printf("\r\r\r=========== 魔女开发板 STM32F103 外设初始报告 ===========\r");
    printf("USART1初始化配置      接收中断、空闲中断, 发送中断\r");
}

/******************************************************************************
 * 函  数: USART1_IRQHandler
 * 功  能: USART1的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 *
******************************************************************************/
static uint8_t U1TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U1TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U1TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART1_IRQHandler(void)
{
    static uint16_t cnt = 0;                                         // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U1_RX_BUF_SIZE];                          // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;

    // 接收中断
    if (USART1->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U1_RX_BUF_SIZE))//||(xUSART.USART1ReceivedFlag==1// 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART1->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = USART1->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (USART1->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.USART1ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.USART1ReceivedBuffer, RxTemp, U1_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.USART1ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                     // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U1_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收
        USART1 ->SR;
        USART1 ->DR;                                 // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((USART1->SR & 1 << 7) && (USART1->CR1 & 1 << 7))             // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART1->DR = U1TxBuffer[U1TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U1TxCounter == U1TxCount)
            USART1->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

/******************************************************************************
 * 函  数: vUSART1_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数
 * 返回值: 0_没有接收到新数据, 非0_所接收到新数据的字节数
 ******************************************************************************/
uint8_t USART1_GetBuffer(uint8_t *buffer, uint8_t *cnt)
{
    if (xUSART.USART1ReceivedNum > 0)                                           // 判断是否有新数据
    {
        memcpy(buffer, xUSART.USART1ReceivedBuffer, xUSART.USART1ReceivedNum);  // 把新数据复制到指定位置
        *cnt = xUSART.USART1ReceivedNum;                                        // 把新数据的字节数,存放指定变量
        xUSART.USART1ReceivedNum = 0;                                           // 接收标记置0
        return *cnt;                                                            // 返回所接收到新数据的字节数
    }
    return 0;                                                                   // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUSART1_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/
void USART1_SendData(uint8_t *buf, uint8_t cnt)
{
    for (uint8_t i = 0; i < cnt; i++)
        U1TxBuffer[U1TxCount++] = buf[i];

    if ((USART1->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        USART1->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUSART1_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
void USART1_SendString(char *stringTemp)
{
    u16 num = 0;                                 // 字符串长度
    char *t = stringTemp ;                       // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    USART1_SendData((u8 *)stringTemp, num);      // 注意调用函数所需要的真实数据长度; 如果目标需要以0作结尾判断,需num+1:字符串以0结尾,即多发一个:0
}

/******************************************************************************
 * 函  数: vUSART1_SendStringForDMA
 * 功  能: UART通过DMA发送数据,省了占用中断的时间
 *         【适合场景】字符串,字节数非常多,
 *         【不 适 合】1:只适合发送字符串,不适合发送可能含0的数值类数据; 2-时间间隔要足够
 * 参  数: char strintTemp  要发送的字符串首地址
 * 返回值: 无
 ******************************************************************************/
void USART1_SendStringForDMA(char *stringTemp)
{
    static u8 Flag_DmaTxInit = 0;                // 用于标记是否已配置DMA发送
    u32   num = 0;                               // 发送的数量,注意发送的单位不是必须8位的
    char *t = stringTemp ;                       // 用于配合计算发送的数量

    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位

    while (DMA1_Channel4->CNDTR > 0);            // 重要:如果DMA还在进行上次发送,就等待; 得进完成中断清标志,F4不用这么麻烦,发送完后EN自动清零
    if (Flag_DmaTxInit == 0)                     // 是否已进行过USAART_TX的DMA传输配置
    {
        Flag_DmaTxInit  = 1;                     // 设置标记,下次调用本函数就不再进行配置了
        USART1 ->CR3   |= 1 << 7;                // 使能DMA发送
        RCC->AHBENR    |= 1 << 0;                // 开启DMA1时钟  [0]DMA1   [1]DMA2

        DMA1_Channel4->CCR   = 0;                // 失能, 清0整个寄存器, DMA必须失能才能配置
        DMA1_Channel4->CNDTR = num;              // 传输数据量
        DMA1_Channel4->CMAR  = (u32)stringTemp;  // 存储器地址
        DMA1_Channel4->CPAR  = (u32)&USART1->DR; // 外设地址

        DMA1_Channel4->CCR |= 1 << 4;            // 数据传输方向   0:从外设读   1:从存储器读
        DMA1_Channel4->CCR |= 0 << 5;            // 循环模式       0:不循环     1:循环
        DMA1_Channel4->CCR |= 0 << 6;            // 外设地址非增量模式
        DMA1_Channel4->CCR |= 1 << 7;            // 存储器增量模式
        DMA1_Channel4->CCR |= 0 << 8;            // 外设数据宽度为8位
        DMA1_Channel4->CCR |= 0 << 10;           // 存储器数据宽度8位
        DMA1_Channel4->CCR |= 0 << 12;           // 中等优先级
        DMA1_Channel4->CCR |= 0 << 14;           // 非存储器到存储器模式
    }
    DMA1_Channel4->CCR  &= ~((u32)(1 << 0));     // 失能,DMA必须失能才能配置
    DMA1_Channel4->CNDTR = num;                  // 传输数据量
    DMA1_Channel4->CMAR  = (u32)stringTemp;      // 存储器地址
    DMA1_Channel4->CCR  |= 1 << 0;               // 开启DMA传输
}






//   USART-2   //
/
/******************************************************************************
 * 函  数: vUSART2_Init
 * 功  能: 初始化USART的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void USART2_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;                           // 使能USART2时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                             // 使能GPIOA时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 2 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    //USART_DeInit(USART2);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART2, &USART_InitStructure);                       // 初始化串口

    USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                  // 使能空闲中断

    USART_Cmd(USART2, ENABLE);                                      // 使能串口, 开始工作

    USART2->SR = ~(0x00F0);                                         // 清理中断

    xUSART.USART2InitFlag = 1;                                      // 标记初始化标志
    xUSART.USART2ReceivedNum = 0;                                   // 接收字节数清零

    printf("\rUSART2初始化配置      接收中断、空闲中断, 发送中断\r");
}

/******************************************************************************
 * 函  数: USART2_IRQHandler
 * 功  能: USART2的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U2TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U2TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U2TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART2_IRQHandler(void)
{
    static uint16_t cnt = 0;                                         // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U2_RX_BUF_SIZE];                          // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;

    // 接收中断
    if (USART2->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U2_RX_BUF_SIZE))//||xUSART.USART2ReceivedFlag==1 // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART2->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = USART2->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (USART2->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.USART2ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.USART2ReceivedBuffer, RxTemp, U2_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.USART2ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                     // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U2_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收
        USART2 ->SR;
        USART2 ->DR;                                 // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((USART2->SR & 1 << 7) && (USART2->CR1 & 1 << 7))             // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART2->DR = U2TxBuffer[U2TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U2TxCounter == U2TxCount)
            USART2->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

/******************************************************************************
 * 函  数: vUSART2_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数
 * 返回值: 0_没有接收到新数据, 非0_所接收到新数据的字节数
 ******************************************************************************/
uint8_t USART2_GetBuffer(uint8_t *buffer, uint8_t *cnt)
{
    if (xUSART.USART2ReceivedNum > 0)                                          // 判断是否有新数据
    {
        memcpy(buffer, xUSART.USART2ReceivedBuffer, xUSART.USART2ReceivedNum); // 把新数据复制到指定位置
        *cnt = xUSART.USART2ReceivedNum;                                       // 把新数据的字节数,存放指定变量
        xUSART.USART2ReceivedNum = 0;                                          // 接收标记置0
        return *cnt;                                                           // 返回所接收到新数据的字节数
    }
    return 0;                                                                  // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUSART2_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/
void USART2_SendData(uint8_t *buf, uint8_t cnt)
{
    for (uint8_t i = 0; i < cnt; i++)
        U2TxBuffer[U2TxCount++] = buf[i];

    if ((USART2->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        USART2->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUSART2_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
void USART2_SendString(char *stringTemp)
{
    u16 num = 0;                                 // 字符串长度
    char *t = stringTemp ;                       // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    USART2_SendData((u8 *)stringTemp, num);      // 注意调用函数所需要的真实数据长度; 如果目标需要以0作结尾判断,需num+1:字符串以0结尾,即多发一个:0
}

/******************************************************************************
 * 函  数: USART3_IRQHandler
 * 功  能: USART的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U3TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U3TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U3TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART3_SendData(uint8_t *buf, uint8_t cnt)
{
    for (uint8_t i = 0; i < cnt; i++)
        U3TxBuffer[U3TxCount++] = buf[i];

    if ((USART3->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        USART3->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUSART3_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
//void USART3_SendString(char *stringTemp)
//{
//    u16 num = 0;                                 // 字符串长度
//    char *t = stringTemp ;                       // 用于配合计算发送的数量
//    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
//    USART3_SendData((u8 *)stringTemp, num);      // 注意调用函数所需要的真实数据长度; 如果目标需要以0作结尾判断,需num+1:字符串以0结尾,即多发一个:0
//}


#ifdef STM32F10X_HD  // STM32F103R,及以上,才有UART4和UART5

//   UART-4   //
/
/******************************************************************************
 * 函  数: vUART4_Init
 * 功  能: 初始化USART的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART4_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_UART4EN;                            // 使能UART4时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;                             // 使能GPIOC时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽    GPIO_Init(GPIOC, &GPIO_InitStructure);
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = UART4_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 2 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(UART4);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(UART4, &USART_InitStructure);                        // 初始化串口

    USART_ITConfig(UART4, USART_IT_TXE, DISABLE);
    USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);                   // 使能接受中断
    USART_ITConfig(UART4, USART_IT_IDLE, ENABLE);                   // 使能空闲中断
    
    USART_Cmd(UART4, ENABLE);                                       // 使能串口, 开始工作

    UART4->SR = ~(0x00F0);                                          // 清理中断

    xUSART.UART4InitFlag = 1;                                       // 标记初始化标志
    xUSART.UART4ReceivedNum = 0;                                    // 接收字节数清零

    printf("\rUART4 初始化配置      接收中断、空闲中断, 发送中断\r");
}

/******************************************************************************
 * 函  数: UART4_IRQHandler
 * 功  能: USART2的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U4TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U4TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U4TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void UART4_IRQHandler(void)
{
    static uint16_t cnt = 0;                                        // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U4_RX_BUF_SIZE];                         // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;

    // 接收中断
    if (UART4->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U4_RX_BUF_SIZE))//||xUSART.UART4ReceivedFlag==1 // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            UART4->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = UART4->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (UART4->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.UART4ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.UART4ReceivedBuffer, RxTemp, U4_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.UART4ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                    // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U4_RX_BUF_SIZE);                          // 接收数据缓存数组,清零; 准备下一次的接收
        UART4 ->SR;
        UART4 ->DR;                                  // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((UART4->SR & 1 << 7) && (UART4->CR1 & 1 << 7))              // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        UART4->DR = U4TxBuffer[U4TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U4TxCounter == U4TxCount)
            UART4->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

/******************************************************************************
 * 函  数: vUART4_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数
 * 返回值: 0_没有接收到新数据, 非0_所接收到新数据的字节数
 ******************************************************************************/
uint8_t UART4_GetBuffer(uint8_t *buffer, uint8_t *cnt)
{
    if (xUSART.UART4ReceivedNum > 0)                                         // 判断是否有新数据
    {
        memcpy(buffer, xUSART.UART4ReceivedBuffer, xUSART.UART4ReceivedNum); // 把新数据复制到指定位置
        *cnt = xUSART.UART4ReceivedNum;                                      // 把新数据的字节数,存放指定变量
        xUSART.UART4ReceivedNum = 0;                                         // 接收标记置0
        return *cnt;                                                         // 返回所接收到新数据的字节数
    }
    return 0;                                                                // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUART4_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/
void UART4_SendData(uint8_t *buf, uint8_t cnt)
{
    for (uint8_t i = 0; i < cnt; i++)
        U4TxBuffer[U4TxCount++] = buf[i];

    if ((UART4->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        UART4->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUART4_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
void UART4_SendString(char *stringTemp)
{
    u16 num = 0;                                 // 字符串长度
    char *t = stringTemp ;                       // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    UART4_SendData((u8 *)stringTemp, num);       // 调用函数完成发送,num+1:字符串以0结尾,需多发一个:0
}




//   UART-4   //
/
/******************************************************************************
 * 函  数: vUART5_Init
 * 功  能: 初始化USART的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void UART5_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_UART5EN;                            // 使能UART5时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPCEN;        // 使能GPIO时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = UART5_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 2 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(UART5);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(UART5, &USART_InitStructure);                        // 初始化串口

    USART_ITConfig(UART5, USART_IT_TXE, DISABLE);
    USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);                   // 使能接受中断
    USART_ITConfig(UART5, USART_IT_IDLE, ENABLE);                   // 使能空闲中断

    USART_Cmd(UART5, ENABLE);                                       // 使能串口, 开始工作

    UART5->SR = ~(0x00F0);                                          // 清理中断

    xUSART.UART5InitFlag = 1;                                       // 标记初始化标志
    xUSART.UART5ReceivedNum = 0;                                    // 接收字节数清零

    printf("\rUART5 初始化配置      接收中断、空闲中断, 发送中断\r");
}

/******************************************************************************
 * 函  数: UART5_IRQHandler
 * 功  能: USART2的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U5TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U5TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U5TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void UART5_IRQHandler(void)
{
    static uint16_t cnt = 0;                                        // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U5_RX_BUF_SIZE];                         // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;

    // 接收中断
    if (UART5->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U5_RX_BUF_SIZE))//||xUSART.UART5ReceivedFlag==1 // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            UART5->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = UART5->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (UART5->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.UART5ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.UART5ReceivedBuffer, RxTemp, U5_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.UART5ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                    // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U5_RX_BUF_SIZE);                          // 接收数据缓存数组,清零; 准备下一次的接收
        UART5 ->SR;
        UART5 ->DR;                                  // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((UART5->SR & 1 << 7) && (UART5->CR1 & 1 << 7))              // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        UART5->DR = U5TxBuffer[U5TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U5TxCounter == U5TxCount)
            UART5->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

/******************************************************************************
 * 函  数: vUART5_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数
 * 返回值: 0_没有接收到新数据, 非0_所接收到新数据的字节数
 ******************************************************************************/
uint8_t UART5_GetBuffer(uint8_t *buffer, uint8_t *cnt)
{
    if (xUSART.UART5ReceivedNum > 0)                                         // 判断是否有新数据
    {
        memcpy(buffer, xUSART.UART5ReceivedBuffer, xUSART.UART5ReceivedNum); // 把新数据复制到指定位置
        *cnt = xUSART.UART5ReceivedNum;                                      // 把新数据的字节数,存放指定变量
        xUSART.UART5ReceivedNum = 0;                                         // 接收标记置0
        return *cnt;                                                         // 返回所接收到新数据的字节数
    }
    return 0;                                                                // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUART5_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/
void UART5_SendData(uint8_t *buf, uint8_t cnt)
{
    for (uint8_t i = 0; i < cnt; i++)
        U5TxBuffer[U5TxCount++] = buf[i];

    if ((UART5->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        UART5->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUART5_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
void UART5_SendString(char *stringTemp)
{
    u16 num = 0;                                 // 字符串长度
    char *t = stringTemp ;                       // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    UART5_SendData((u8 *)stringTemp, num);       // 注意调用函数所需要的真实数据长度; 如果目标需要以0作结尾判断,需num+1:字符串以0结尾,即多发一个:0
}

#endif





//  printf   //
/******************************************************************************
 * 功  能: printf函数支持代码
 *         【特别注意】加入以下代码, 使用printf函数时, 不再需要选择use MicroLIB
 * 参  数:
 * 返回值:
 * 备  注: 最后修改_2020年07月15日
 ******************************************************************************/
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#pragma import(__use_no_semihosting)
//struct __FILE
//{
//    int handle;
//};                     // 标准库需要的支持函数
//FILE __stdout;         // FILE 在stdio.h文件
void _sys_exit(int x)
{
    x = x;             // 定义_sys_exit()以避免使用半主机模式
}



//重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数
int fputc(int ch, FILE *f)
{  /* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
 /* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
 return (ch);
 }

 ///重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、getchar 等函数
 int fgetc(FILE *f)
 {
 /* 等待串口输入数据 */
 while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

 return (int)USART_ReceiveData(DEBUG_USARTx);
 }




///*
//函数名:USART3中断服务函数
//功能:  接收数据
//注意:接收数据长度可调:RXCOUNT
//*/
//void USART3_IRQHandler(void)
//{
//	u8 temp;
//	
//  if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
//  {
//		USART_ClearITPendingBit(USART3,USART_IT_RXNE);
//		temp = USART_ReceiveData(USART3);
//		if(temp == '\n' || RXCOUNT == 20)   //判断是否接收到一个完整字符
//		{
//			RXCOUNT = 0;
//			RXOVER =1;    //接收数据完成标志位置1
//			USART_ITConfig(USART3,USART_IT_RXNE,DISABLE);//失能串口接收中断标志	
//		}
//		else
//		{
//			RXBUF[RXCOUNT] = temp;   //依次存放到数组中
//			RXCOUNT++;		           //字符长度变化
//		}
//	}
//}

main.c

objectivec 复制代码
/**
  ******************************************************************************
  * @file    main.c
  * @author  fire
  * @version V1.0
  * @date    2013-xx-xx
  * @brief   rtc 测试,显示时间格式为: xx:xx:xx
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-指南者 STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */
	
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./rtc/bsp_rtc.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./key/bsp_key.h"  
#include "./DTH11/DTH11.h"
#include "./Led/bsp_led.h"
#include "./syn6288/syn6288.h"
//变量声明
u8 RXBUF[20];       //串口存储数组
u8 RXOVER=0;        //串口接收标志位
u8 RXCOUNT=0;       //串口计数变量  
u8 i;               //清空数组变量



//时间更新函数
void 											Update_FrameShow										(void);

//表盘框架绘制
void 											DrawFixed_Frame										(void);
void 											DrawExcel												(void);
void 											DrawCongratulate										(void);
void 											DrawTimeFrame											(void);
void 											DrawExternal_Environmentz							(void);
void 											DrawWish													(void);
//语音处理函数
void 											USART_Deal												(void);

// N = 2^32/365/24/60/60 = 136 年

/*时间结构体,默认时间2024-03-02 03:20:10*/
struct rtc_time systmtime=
{
10,20,3,1,2,2024,4
};

extern __IO uint32_t TimeDisplay ;
//温度参数

/**
  * @brief  主函数
  * @param  无  
  * @retval 无
  */
int main()
{		
	
//可使用该宏设置是否使用液晶显示
#ifdef  USE_LCD_DISPLAY
	
		ILI9341_Init ();         //LCD 初始化
		LCD_SetFont(&Font8x16);
		LCD_SetColors(CurrentTextColor,CurrentBackColor);

		ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);	/* 清屏,显示全黑 */
		
		DrawFixed_Frame();//绘制固定物


#endif
		//串口部分
	   USART_Config();		
		LED_Configuration();//LED配置
		SYN6288_Init(USART2);                             // 初始化; USART2-PA2
		USART3_Init(9600);											//LD3320串口初始化
		
		Key_GPIO_Config();

		/* 配置RTC秒中断优先级 */
	  RTC_NVIC_Config();
	  RTC_CheckAndConfig(&systmtime);
	  DHT11_Init();  //初始化温度传感器引脚
	  
	  SYN6288_Say("已开灯");//Syn6288测试
	  //Temperuture_Get();
	  while (1)
	  {
	    /* 每过1s 更新一次时间*/
	    if (TimeDisplay == 1)
	    {
				/* 当前时间 */
	      Time_Display( RTC_GetCounter(),&systmtime); 
			//Time_Display1( RTC_GetCounter(),Temperature_Get(),&systmtime); //当加入温度获取时,温度获取

	      TimeDisplay = 0;
	    }
			
			//按下按键,通过串口修改时间
			if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  )
			{
				struct rtc_time set_time;

				/*使用串口接收设置的时间,输入数字时注意末尾要加回车*/
				Time_Regulate_Get(&set_time);
				/*用接收到的时间设置RTC*/
				Time_Adjust(&set_time);
				
				//向备份寄存器写入标志
				BKP_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);

			} 
		  USART_Deal();
	  }
}

/**************************************表盘框架绘制*****************************************/

/*
函数功能: 绘制所有固定物
*/
void DrawFixed_Frame(void)
{
DrawExcel();
DrawCongratulate();
DrawTimeFrame();
DrawExternal_Environmentz();
DrawWish();
}

/*
函数功能: 绘制表格
*/
void DrawExcel(void)
{
ILI9341_DrawRectangle ( 0, 0, 240, 320, 0);
ILI9341_DrawLine(0,136,50,136);
ILI9341_DrawLine(185,136,240,136);
ILI9341_DrawLine(0,180,240,180);
ILI9341_DrawLine(0,200,240,200);
ILI9341_DrawLine(48,200,48,320);
ILI9341_DrawLine(240-48,200,240-48,320);
}
/*
函数功能: 绘制时钟表盘框架
*/
void DrawTimeFrame(void)
{
	uint8_t i;
	ILI9341_DrawCircle(RoundCenter_X,RoundCenter_Y,RoundRadius,0);//画外圆
	ILI9341_DrawCircle(RoundCenter_X,RoundCenter_Y,RoundCenter,1); //画中心圆
	//画刻度
	for(i=0;i<60;i++)
	{
		if(i%5==0)
		{//绘制圆大间距
		ILI9341_DrawAngleLine(RoundCenter_X,RoundCenter_Y,6*i,RoundRadius-6,RoundRadius,RoundInterval_Color);
		}
		else
		{//绘制圆小间距
		ILI9341_DrawAngleLine(RoundCenter_X,RoundCenter_Y,6*i,RoundRadius-3,RoundRadius,RoundInterval_Color);
		}
		
	}
	//OLED_WriteGRAM();  //刷新数据到OLED屏幕
}
/*
函数功能: 绘制龙年大吉
*/
void DrawCongratulate(void)
{
		ILI9341_DisplayStringEx(10,10,32,32,(uint8_t *)"龙",0);
		ILI9341_DisplayStringEx(240-32-10,10,32,32,(uint8_t *)"年",0);
		ILI9341_DisplayStringEx(10,3*32,32,32,(uint8_t *)"大",0);
		ILI9341_DisplayStringEx(240-32-10,3*32,32,32,(uint8_t *)"吉",0);
}

/*
函数功能: 绘制外部环境
*/
void DrawExternal_Environmentz(void)
{
ILI9341_DisplayStringEx(20,144,16,16,(uint8_t *)"天气:多云",0);//也可后续添加天气检测设备配置
ILI9341_DisplayStringEx(120,144,16,16,(uint8_t *)"温度:",0);//其他添加可仿照温度配置
ILI9341_DisplayStringEx(20,160,16,16,(uint8_t *)"位置:贵阳",0);//也可后续添加定位设备配置
ILI9341_DisplayStringEx(120,160,16,16,(uint8_t *)"湿度:",0);//也可后续添加空气检测设备配置

}
/*
函数功能: 绘制祝愿
*/
void DrawWish(void)
{
ILI9341_DisplayStringEx(12,210,24,24,(uint8_t *)"宜",0);
ILI9341_DisplayStringEx(204,210,24,24,(uint8_t *)"忌",0);

ILI9341_DisplayStringEx_YDir(8,240,16,16,(uint8_t *)"搞钱",0);
ILI9341_DisplayStringEx_YDir(28,240,16,16,(uint8_t *)"毕业设计",0);
ILI9341_DisplayStringEx_YDir(200,240,16,16,(uint8_t *)"晚睡晚起",0);
ILI9341_DisplayStringEx_YDir(220,240,16,16,(uint8_t *)"打游戏",0);

ILI9341_DisplayStringEx(52,210,24,24,(uint8_t *)"等待开发",0);
}




/**************************************表盘框架绘制结束*****************************************/
/*
函数功能: 更新时间框架显示,在RTC中断里调用
*/
void Update_FrameShow(void)
{
	/*1. 绘制秒针、分针、时针*/
	 //画秒针
	ILI9341_DrawAngleLine2(RoundCenter_X,RoundCenter_Y,systmtime.tm_sec*6-6-90,RoundCenter,RoundSecondHand,0);//清除之前的秒针
	ILI9341_DrawAngleLine(RoundCenter_X,RoundCenter_Y,systmtime.tm_sec*6-90,RoundCenter,RoundSecondHand,RoundSecondHand_Color);
	//画分针
	ILI9341_DrawAngleLine2(RoundCenter_X,RoundCenter_Y,systmtime.tm_min*6-6-90,RoundCenter,RoundMiuiteHand,0);
	ILI9341_DrawAngleLine(RoundCenter_X,RoundCenter_Y,systmtime.tm_min*6-90,RoundCenter,RoundMiuiteHand,RoundMiuiteHand_Color);
	//画时针	
	ILI9341_DrawAngleLine2(RoundCenter_X,RoundCenter_Y,systmtime.tm_hour*30-30-90,RoundCenter,RoundHourHand,0);
	ILI9341_DrawAngleLine(RoundCenter_X,RoundCenter_Y,systmtime.tm_hour*30-90,RoundCenter,RoundHourHand,RoundHourHand_Color);
	
}

/***********************************END OF FILE*********************************/

//串口处理函数
void USART_Deal(void)
{
		if(RXOVER)
		{
			RXOVER = 0;    //清除接收标志位
			switch(RXBUF[0]-48)
			{
				case   1:GPIO_ResetBits(GPIOB,GPIO_Pin_0);   //点亮小灯
							SYN6288_Say("已开灯");

								 break;
				
				case   2:GPIO_SetBits(GPIOB,GPIO_Pin_0); //熄灭小灯
							SYN6288_Say("已关灯");

								 break;	
				default: break;			
			}
			USART3_SendString(RXBUF);    //发送给pc机上面打印显示			
			for(i=0;i<20;i++)            //将已接收数据的数组清空:共20个字符长度
			{
				 RXBUF[i] = 0;          	 //重置数据缓存区
			}
			USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);//始能串口接收 
		}
}

液晶显示指针式时钟核心代码在上文已经放出,本文核心代码如上,本人撰写代码也需要时间,如需要全部代码或者仅需语音部分代码请私信我,感谢大家理解。

相关推荐
蓝斯4976 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip7 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程7 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工8 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip8 小时前
CAN与CANopen的本质区别
单片机
lilihewo9 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET10 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院11 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰11 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥12 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5