Delay延时函数: 2
IO端口使用总结: 2
IO口时钟配置: 2
初始化IO口参数: 2
注意:时钟使能之后操作IO口才有效! 2
IO端口输出高低电平函数: 2
IO的输入和输出宏定义方式: 3
读取某个IO的电平函数: 3
IO口方向切换成双向 3
IO 口外部中断的一般步骤: 3
内部ADC使用总结: 4
LCDTFT函数使用大全 5
TFTLCD使用注意点: 5
IO端口宏定义和使用方法: 6
Keil使用心得: 6
ucGUI移植 6
DDS AD9850测试程序: 6
ADC 使用小结: 7
ADC测试程序: 9
DAC—tlv5638测试程序 9
红外测试程序: 9
DMA使用心得: 9
通用定时器使用: 9
BUG发现: 10
编程总结: 10
时钟总结: 10
汉字显示(外部SD卡字库): 11
字符、汉字显示(内部FLASH) 12
图片显示: 16
触摸屏: 17
引脚连接: 18
IO端口输入输出模式设置:
GPIO_Mode_AIN = 0x0, //模拟输入,常用于ADC
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
GPIO_Mode_IPD = 0x28, //下拉输入 ,常用于输入模式
GPIO_Mode_IPU = 0x48, //上拉输入 ,常用于输入模式
GPIO_Mode_Out_OD = 0x14, //开漏输出
GPIO_Mode_Out_PP = 0x10, //通用推挽输出 ,常用于输出模式
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
GPIO_Mode_AF_PP = 0x18 //复用推挽
Delay延时函数:
delay_ms(u16 nms);
delay_us(u32 nus);
IO端口使用总结:
1) 使能IO 口时钟。调用函数为RCC_APB2PeriphClockCmd()。
2) 初始化IO 参数。调用函数GPIO_Init();
3) 操作IO。
IO口时钟配置:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
初始化IO口参数:
注意:时钟使能之后操作IO口才有效!
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
技巧:如果为同一端口的不同引脚,可以使用或运算,如GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15;
IO端口输出高低电平函数:
GPIO_SetBits(GPIOA,GPIO_Pin_8|GPIO_Pin_9); //PA.8 输出高
GPIO_ResetBits(GPIOA,GPIO_Pin_8);
GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//可以输出1,也可以输出0
GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//整体输出一个值
IO的输入和输出宏定义方式:
#define DATAOUT(x) GPIOB->ODR=x; //数据输出
#define DATAIN GPIOB->IDR; //数据输入
#define DATAOUT(DataValue) {GPIO_Write(GPIOB,(GPIO_ReadOutputData(GPIOB)&0xff00)|(DataValue&0x00FF));} //PB0~7,作为数据线
读取某个IO的电平函数:
(一)读出一个IO口电平
GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
#define KEY0 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_13) //PA13
#define KEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15) //PA15
#define KEY2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
(二)读出某个IO口的全部电平
GPIO_ReadInputData (GPIOC)
IO口方向切换成双向
IIC里面的一个实例
#define SDA_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;} //PC12
#define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}
IO 口外部中断的一般步骤:
1)初始化IO 口为输入。
2)开启IO 口复用时钟,设置IO 口与中断线的映射关系。
3)初始化线上中断,设置触发条件等。
4)配置中断分组(NVIC),并使能中断。
5)编写中断服务函数。
例程:
开启IO 口复用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
GPIOA.13 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource13);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line13;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
//[ 此外还可以为(EXTI_Trigger_Rising,EXTI_Trigger_Rising_Falling) ]
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键所在的外部中断通道
//[ 此外还可以为NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn ];
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
中断函数的编写:(蓝色字体为格式)
void EXTI0_IRQHandler(void)
{
delay_ms(10); //消抖
if(EXTI_GetITStatus(EXTI_Line0) != RESET) //检查指定的EXTI0线路触发请求发生与否
{
LED0=!LED0;
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位
}
void EXTI15_10_IRQHandler(void)
{
delay_ms(10); //消抖
if(EXTI_GetITStatus(EXTI_Line13) != RESET){ }
else if (EXTI_GetITStatus(EXTI_Line15) != RESET) { }
EXTI_ClearITPendingBit(EXTI_Line13); //清除EXTI13线路挂起位
EXTI_ClearITPendingBit(EXTI_Line15); //清除EXTI15线路挂起位
}
内部ADC使用总结:
1)STM32F103系列最少都拥有2个ADC,我们选择的STM32F103RBT6也包含有2个ADC。
2)STM32的ADC最大的转换速率为1Mhz,也就是转换时间为1us(在ADCCLK=14M,采样周期为1.5个ADC时钟下得到),不要让ADC的时钟超过14M,否则将导致结果准确度下降。
3)STM32将ADC的转换分为2个通道组:规则通道组和注入通道组。规则通道相当于你运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。
4)STM32ADC的规则通道组最多包含16个转换,而注入通道组最多包含4个通道。
5)STM32的ADC在单次转换模式下,只执行一次转换,该模式可以通过ADC_CR2寄存器的ADON位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这是CONT位为0。
6)寄存器ADC_CR2操作模式:ADCON位用于开关AD转换器。而CONT位用于设置是否进行连续转换,我们使用单次转换,所以CONT位必须为0。CAL和RSTCAL用
7)于AD校准。ALIGN用于设置数据对齐,我们使用右对齐,该位设置为0。
8)中文参考手册中有详细的描述
9)关于通道:每个通用定时器都有四个通道,这四个通道都可以配置成分别不同的模式。
LCDTFT函数使用大全
注意:画笔颜色,背景颜色的定义,在使用前要设置POINT_COLOR
POINT_COLOR
BACK_COLOR
void LCD_Init(void);
void LCD_DisplayOn(void);
void LCD_DisplayOff(void);
void LCD_Clear(u16 Color); //清屏
void LCD_SetCursor(u16 Xpos, u16 Ypos);
void LCD_Scan_Dir(u8 dir);
void LCD_DrawPoint(u16 x,u16 y);//画点
u16 LCD_ReadPoint(u16 x,u16 y); //读点
void Draw_Circle(u16 x0,u16 y0,u8 r);
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2);
void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color);
void LCD_ShowChar(u16 x,u16 y,u8 chararater,u8 size,u8 mode);//显示一个字符,size=12或16
void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size); //显示长度为len数字
void LCD_Show2Num(u16 x,u16 y,u16 num,u8 len,u8 size,u8 mode);//显示2个数字
void LCD_ShowString(u16 x,u16 y,const u8 *p,u8 mode) //显示一个字符串,16字体
image_display(u16 x,u16 y,u8 * imgx);//显示图片
void Test_Show_CH_Font16(u16 x,u16 y,u8 index,u16 color);//显示汉字16*16
void Test_Show_CH_Font24(u16 x,u16 y,u8 index,u16 color); //显示汉字24*24
TFTLCD使用注意点:
在切换方向的时候,一定不能偷懒;
void LCD_DATA_IN_DIR(void) //数据输入方向
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //这句千万不能注释掉
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void LCD_DATA_OUT_DIR(void) //数据输出方向
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //这句千万不能注释掉
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
IO端口宏定义和使用方法:
#define AD9850_CONTROL_PORT GPIOA
#define AD9850_FQUD GPIO_Pin_2
#define AD9850_WCLK GPIO_Pin_3
#define AD9850_RST GPIO_Pin_4
置1和置0
#define AD9850_WCLK_SET GPIO_WriteBit(AD9850_CONTROL_PORT,AD9850_WCLK,Bit_SET)
#define AD9850_WCLK_CLR GPIO_WriteBit(AD9850_CONTROL_PORT,AD9850_WCLK,Bit_RESET)
#define AD9850_DATA_Write_1 GPIO_WriteBit(AD9850_CONTROL_PORT,AD9850_DATA,Bit_SET)
#define AD9850_DATA_Write_0 GPIO_WriteBit(AD9850_CONTROL_PORT,AD9850_DATA,Bit_RESET)
GPIO_InitStructure.GPIO_Pin = AD9850_WCLK | AD9850_FQUD | AD9850_RST | AD9850_DATA ;
GPIO_Init(AD9850_CONTROL_PORT ,&GPIO_InitStructure) ;
方法2:
#define LCD_RD_LOW GPIOC->BRR|=GPIO_Pin_6;
#define LCD_RD_HIGH GPIOC->BSRR|=GPIO_Pin_6;
Keil使用心得:
和的区别:
只要不改变头文件,我们一般可以使用F7进行编译,即,这样话的时间比较短;
而是整个重新编译,花的时间比较长;
ucGUI移植
LCD_CONTROLLER (-1) /* lcd 控制器的具体型号,-1 表示是自己定义的型号*/
ucGUI是可以不带操作系统的;
DDS AD9850测试程序:
Init_AD9850();
AD9850_Reset_Sreial();
AD9850_Write_Serial(0x00,2000) ;
delay_ms(1000);
while(1)
{
AD9850_Write_Serial(0x00,freq) ;
delay_ms(1000);
freq += 2000;
if(freq >= 20000000)
freq = 2000;
}
ADC 使用小结:
在内部ADC中,单次转换和连续转换的区别:如果用TIME去定时的时候,就要选择单次转换,用定时器去启动转换;如果使用连续转换的话,那么,它是不听TIME定时的,会不听的转换。程序是:ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
采样频率不能太高,2K采样100HZ 方波,频谱图:
三角波的频谱:
正弦波的频谱:
ADC测试程序:
连接方法:与PA0相连
u16 adcx;
float temp;
Adc_Init();
while(1)
{
adcx= Get_Adc_Average(ADC_Channel_0,10);
LCD_ShowNum(156,130,adcx,4,16);//显示ADC的值
temp=(float)adcx*(3.3/4096);
adcx=temp;
LCD_ShowNum(156,150,adcx,1,16);//显示电压值
temp-=adcx;
temp*=1000;
LCD_ShowNum(172,150,temp,3,16);
LED0=!LED0;
delay_ms(250);
}
DAC—tlv5638测试程序
Init_TLV5638(); //初始化DA TLV5638
SetDAC_B(PASTSP,IN_2048,0,i); //改变I的值就可以了,比如 i= 0.02
红外测试程序:
Remote_Init(); //初始化红外接收
if(Remote_Rdy)
{Key_value = Get_Remote_Value(); }
DMA使用心得:
使用的例子是官方的例程;
调试了半天,原来是没有打开DMA时钟;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
通用定时器使用:
1)定时器调试了半天,终于知道了了,是由于时钟错了,通用定时器的时钟是:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);而不是: RCC_APB2PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);一字之差,害的我调试了好久,由此,请看时钟总结;
2)定时器分频器:f CK_PSC/(PSC[15:0]+1);TIM_TimeBaseStructure.TIM_Period = 71; 则是时钟频率72M / (71+1) = 1M;
BUG发现:
1)在使用定时器触发AD,利用DMA传输的时候,TIM2的CC2事件不能使用,这件事情让我调试了好几天,最终无果最后使用TIM1触发解决问题;
编程总结:
一定要注意数的使用范围;
例子:如果N为256时
U8 i;
for(i=0;i printf("%d\\n",Adc_value[i]); delay_ms(40); } 这个就不能正常输出了! u16 i; for(i=0;i printf("%d\\n",Adc_value[i]); delay_ms(40); } 这个可以正常输出! 时钟总结: 使用的使能函数是:RCC_APB2PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); #define RCC_APB2Periph_AFIO ((uint32_t)0x00000001) #define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004) #define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008) #define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010) #define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020) #define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040) #define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080) #define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100) #define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200) #define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400) #define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800) #define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000) #define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000) #define RCC_APB2Periph_USART1 ((uint32_t)0x00004000) #define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000) #define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000) #define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000) #define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000) #define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000) #define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000) #define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000) 使用的使能函数是:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); #define RCC_APB1Periph_TIM2 ((uint32_t)0x00000001) #define RCC_APB1Periph_TIM3 ((uint32_t)0x00000002) #define RCC_APB1Periph_TIM4 ((uint32_t)0x00000004) #define RCC_APB1Periph_TIM5 ((uint32_t)0x00000008) #define RCC_APB1Periph_TIM6 ((uint32_t)0x00000010) #define RCC_APB1Periph_TIM7 ((uint32_t)0x00000020) #define RCC_APB1Periph_TIM12 ((uint32_t)0x00000040) #define RCC_APB1Periph_TIM13 ((uint32_t)0x00000080) #define RCC_APB1Periph_TIM14 ((uint32_t)0x00000100) #define RCC_APB1Periph_WWDG ((uint32_t)0x00000800) #define RCC_APB1Periph_SPI2 ((uint32_t)0x00004000) #define RCC_APB1Periph_SPI3 ((uint32_t)0x00008000) #define RCC_APB1Periph_USART2 ((uint32_t)0x00020000) #define RCC_APB1Periph_USART3 ((uint32_t)0x00040000) #define RCC_APB1Periph_UART4 ((uint32_t)0x00080000) #define RCC_APB1Periph_UART5 ((uint32_t)0x00100000) #define RCC_APB1Periph_I2C1 ((uint32_t)0x00200000) #define RCC_APB1Periph_I2C2 ((uint32_t)0x00400000) #define RCC_APB1Periph_USB ((uint32_t)0x00800000) #define RCC_APB1Periph_CAN1 ((uint32_t)0x02000000) #define RCC_APB1Periph_CAN2 ((uint32_t)0x04000000) #define RCC_APB1Periph_BKP ((uint32_t)0x08000000) #define RCC_APB1Periph_PWR ((uint32_t)0x10000000) #define RCC_APB1Periph_DAC ((uint32_t)0x20000000) #define RCC_APB1Periph_CEC ((uint32_t)0x40000000) 汉字显示(外部SD卡字库): 初始化函数: SPI_Flash_Init(); //SPI FLASH初始化 f_mount(0, &fs); //初始化必须mount while(font_init()) { while(update_font())//从SD卡更新 { } } Show_Str(60,50,"Mini STM32开发板",16,0); 详见:FATFS浅谈.pdf 字符、汉字显示(内部FLASH) 汉字的显示,研究下面的两个例子,可以惊奇的发现,是多么惊人的相似。我在后面有注释,看看就可以懂了。 void Test_Show_CH_Font16(u16 x,u16 y,u8 index,u16 color) { u8 temp,t,t1; u16 y0=y; for(t=0;t<32;t++)//每个16*16的汉字点阵 有32个字节 计算方法为:16*16/8=32 { if(t<16)temp=tfont16[index*2][t]; //前16个字节 else temp=tfont16[index*2+1][t-16]; //后16个字节 ,可以看到在tfont里面每个字占用两行 for(t1=0;t1<8;t1++) { if(temp&0x80)LCD_Draw_Point(x,y,color);//画实心点 else LCD_Draw_Point(x,y,BACK_COLOR); //画空白点(使用背景色) temp<<=1; y++; if((y-y0)==16) { y=y0; x++; break; } } } } const u8 tfont16[][16]= { {0x02,0x00,0x42,0x02,0x42,0x04,0x42,0x08,0x42,0x30,0x7F,0xC0,0x42,0x00,0x42,0x00}, {0x42,0x00,0x42,0x00,0x7F,0xFE,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x00,0x00},/*"开",0*/ 看到了,占据了两行。 //在指定位置 显示1个24*24的汉字 //(x,y):汉字显示的位置 //index:tfont数组里面的第几个汉字 //color:这个汉字的颜色 void Test_Show_CH_Font24(u16 x,u16 y,u8 index,u16 color) { u8 temp,t,t1; u16 y0=y; for(t=0;t<72;t++)//每个24*24的汉字点阵 有72个字节 计算方法为:24*24/8= 72 { if(t<24)temp=tfont24[index*3][t]; //前24个字节 可以看到在tfont里面每个字占用三行行 else if(t<48)temp=tfont24[index*3+1][t-24]; //中24个字节 else temp=tfont24[index*3+2][t-48]; //后24个字节 for(t1=0;t1<8;t1++) { if(temp&0x80)LCD_Draw_Point(x,y,color);//画实心点 else LCD_Draw_Point(x,y,BACK_COLOR); //画空白点(使用背景色) temp<<=1; y++; if((y-y0)==24) { y=y0; x++; break; } } } } const u8 tfont24[][24]= { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x08,0x08,0x02,0x08,0x08,0x04,0x08,0x08,0x08,0x08,0x08,0x10,0x08,0x08,0xE0}, {0x0F,0xFF,0x80,0x08,0x08,0x00,0x08,0x08,0x00,0x08,0x08,0x00,0x08,0x08,0x00,0x08,0x08,0x00,0x08,0x08,0x00,0x0F,0xFF,0xFE}, {0x0F,0xFF,0xFE,0x08,0x08,0x00,0x08,0x08,0x00,0x10,0x08,0x00,0x10,0x10,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"开",0*/ 可以看出占据了三行 字符显示 if(!mode) //非叠加方式 { for(pos=0;pos if(size==12)temp=asc2_1206[num][pos];//调用1206字体 else temp=asc2_1608[num][pos]; //调用1608字体 for(t=0;t if(temp&0x01)POINT_COLOR=colortemp; else POINT_COLOR=BACK_COLOR; LCD_DrawPoint(x,y); temp>>=1; x++; } x=x0; y++; } const unsigned char asc2_1206[95][12]={ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/ 看到了,只占据了一行,当然,这个可以做成字库。 const unsigned char asc2_1206[95][12]={ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/ {0x00,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00,0x00},/*"!",1*/ {0x00,0x14,0x0A,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*""",2*/ {0x00,0x00,0x14,0x14,0x3F,0x14,0x0A,0x3F,0x0A,0x0A,0x00,0x00},/*"#",3*/ {0x00,0x04,0x1E,0x15,0x05,0x06,0x0C,0x14,0x15,0x0F,0x04,0x00},/*"$",4*/ {0x00,0x00,0x12,0x15,0x0D,0x0A,0x14,0x2C,0x2A,0x12,0x00,0x00},/*"%",5*/ {0x00,0x00,0x04,0x0A,0x0A,0x1E,0x15,0x15,0x09,0x36,0x00,0x00},/*"&",6*/ {0x00,0x02,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/ {0x00,0x20,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x10,0x20,0x00},/*"(",8*/ {0x00,0x02,0x04,0x08,0x08,0x08,0x08,0x08,0x08,0x04,0x02,0x00},/*")",9*/ {0x00,0x00,0x00,0x04,0x15,0x0E,0x0E,0x15,0x04,0x00,0x00,0x00},/*"*",10*/ {0x00,0x00,0x04,0x04,0x04,0x1F,0x04,0x04,0x04,0x00,0x00,0x00},/*"+",11*/ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x01},/* {0x00,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00,0x00},/*"-",13*/ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00},/*".",14*/ {0x00,0x10,0x08,0x08,0x08,0x04,0x04,0x02,0x02,0x02,0x01,0x00},/*"/",15*/ {0x00,0x00,0x0E,0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00,0x00},/*"0",16*/ {0x00,0x00,0x04,0x06,0x04,0x04,0x04,0x04,0x04,0x0E,0x00,0x00},/*"1",17*/ {0x00,0x00,0x0E,0x11,0x11,0x08,0x04,0x02,0x01,0x1F,0x00,0x00},/*"2",18*/ {0x00,0x00,0x0E,0x11,0x10,0x0C,0x10,0x10,0x11,0x0E,0x00,0x00},/*"3",19*/ {0x00,0x00,0x08,0x0C,0x0A,0x0A,0x09,0x1E,0x08,0x18,0x00,0x00},/*"4",20*/ {0x00,0x00,0x1F,0x01,0x01,0x0F,0x10,0x10,0x11,0x0E,0x00,0x00},/*"5",21*/ {0x00,0x00,0x0E,0x09,0x01,0x0F,0x11,0x11,0x11,0x0E,0x00,0x00},/*"6",22*/ {0x00,0x00,0x1F,0x09,0x08,0x04,0x04,0x04,0x04,0x04,0x00,0x00},/*"7",23*/ {0x00,0x00,0x0E,0x11,0x11,0x0E,0x11,0x11,0x11,0x0E,0x00,0x00},/*"8",24*/ {0x00,0x00,0x0E,0x11,0x11,0x11,0x1E,0x10,0x12,0x0E,0x00,0x00},/*"9",25*/ {0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x04,0x00,0x00},/*":",26*/ {0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x04,0x04,0x00},/*";",27*/ {0x00,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x00,0x00},/*"<",28*/ {0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x1F,0x00,0x00,0x00,0x00},/*"=",29*/ {0x00,0x02,0x04,0x08,0x10,0x20,0x10,0x08,0x04,0x02,0x00,0x00},/*">",30*/ {0x00,0x00,0x0E,0x11,0x11,0x08,0x04,0x04,0x00,0x04,0x00,0x00},/*"?",31*/ {0x00,0x00,0x0E,0x11,0x19,0x15,0x15,0x1D,0x01,0x1E,0x00,0x00},/*"@",32*/ {0x00,0x00,0x04,0x04,0x0C,0x0A,0x0A,0x1E,0x12,0x33,0x00,0x00},/*"A",33*/ {0x00,0x00,0x0F,0x12,0x12,0x0E,0x12,0x12,0x12,0x0F,0x00,0x00},/*"B",34*/ {0x00,0x00,0x1E,0x11,0x01,0x01,0x01,0x01,0x11,0x0E,0x00,0x00},/*"C",35*/ {0x00,0x00,0x0F,0x12,0x12,0x12,0x12,0x12,0x12,0x0F,0x00,0x00},/*"D",36*/ {0x00,0x00,0x1F,0x12,0x0A,0x0E,0x0A,0x02,0x12,0x1F,0x00,0x00},/*"E",37*/ {0x00,0x00,0x1F,0x12,0x0A,0x0E,0x0A,0x02,0x02,0x07,0x00,0x00},/*"F",38*/ {0x00,0x00,0x1C,0x12,0x01,0x01,0x39,0x11,0x12,0x0C,0x00,0x00},/*"G",39*/ {0x00,0x00,0x33,0x12,0x12,0x1E,0x12,0x12,0x12,0x33,0x00,0x00},/*"H",40*/ {0x00,0x00,0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x1F,0x00,0x00},/*"I",41*/ {0x00,0x00,0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x09,0x07,0x00},/*"J",42*/ {0x00,0x00,0x37,0x12,0x0A,0x06,0x0A,0x0A,0x12,0x37,0x00,0x00},/*"K",43*/ {0x00,0x00,0x07,0x02,0x02,0x02,0x02,0x02,0x22,0x3F,0x00,0x00},/*"L",44*/ {0x00,0x00,0x1B,0x1B,0x1B,0x1B,0x15,0x15,0x15,0x15,0x00,0x00},/*"M",45*/ {0x00,0x00,0x3B,0x12,0x16,0x16,0x1A,0x1A,0x12,0x17,0x00,0x00},/*"N",46*/ {0x00,0x00,0x0E,0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00,0x00},/*"O",47*/ {0x00,0x00,0x0F,0x12,0x12,0x0E,0x02,0x02,0x02,0x07,0x00,0x00},/*"P",48*/ {0x00,0x00,0x0E,0x11,0x11,0x11,0x11,0x17,0x19,0x0E,0x18,0x00},/*"Q",49*/ {0x00,0x00,0x0F,0x12,0x12,0x0E,0x0A,0x12,0x12,0x37,0x00,0x00},/*"R",50*/ {0x00,0x00,0x1E,0x11,0x01,0x06,0x08,0x10,0x11,0x0F,0x00,0x00},/*"S",51*/ {0x00,0x00,0x1F,0x15,0x04,0x04,0x04,0x04,0x04,0x0E,0x00,0x00},/*"T",52*/ {0x00,0x00,0x33,0x12,0x12,0x12,0x12,0x12,0x12,0x0C,0x00,0x00},/*"U",53*/ {0x00,0x00,0x33,0x12,0x12,0x0A,0x0A,0x0C,0x04,0x04,0x00,0x00},/*"V",54*/ {0x00,0x00,0x15,0x15,0x15,0x0E,0x0A,0x0A,0x0A,0x0A,0x00,0x00},/*"W",55*/ ……. 综合实验中的大字符显示实验: void LCD_ShowBigChar(u8 x,u16 y,u8 num) { u8 n,t; u8 temp; u8 t1,deadline; u16 colortemp=POINT_COLOR; u8 x0=x; if(num==':')t1=150; else if(num=='.')t1=165; else if(num=='C')t1=180; else t1=15*num; deadline=t1+15; for(;t1 for(n=0;n<16;n++) { temp=BIG_ASCII[t1][n]; for(t=0;t<8;t++) { if(temp&0x80)POINT_COLOR=colortemp; else POINT_COLOR=BACK_COLOR; LCD_DrawPoint(x,y); temp<<=1; x++; if(((n%4)==3)&&t==5) { x=x0; y++; break; } } } } POINT_COLOR=colortemp; } 图片显示: #include "string.h" const char filedir[]="0:/PICTURE"; 专门调用显示图片的函数: void viewPictures(const char *fileDir){ DIR dir; BOOL result; FRESULT res; FILINFO fileInfo; char *filename; char temp[256]; #if _USE_LFN //定义了可以使用长文件名 static char lfn[_MAX_LFN + 1]; fileInfo.lfname = lfn; fileInfo.lfsize = sizeof(lfn); #endif delay_ms(300); LCD_Clear(BRRED); while(1) { res= f_opendir(&dir, fileDir); if(res==FR_OK) { for(;;) { res =f_readdir(&dir,&fileInfo); if(res!=0||fileInfo.fname[0]==0)break; if(fileInfo.fname[0]=='.')continue ; #if _USE_LFN filename = *fileInfo.lfname ? fileInfo.lfname : fileInfo.fname; #else filename = fileInfo.fname; #endif if ( !(fileInfo.fattrib & AM_DIR) ) { strcpy(temp,fileDir); strcat(temp,"/"); LCD_Clear(WHITE);//清屏,加载下一幅图片的时候,一定清屏 printf("%s\\n",filename) ; //可以显示需要的图片了 { result=AI_LoadPicFile(((u8*)strcat(temp,filename)),30,20,240,320);//显示图片的函数 } if(result==FALSE) continue; POINT_COLOR=RED; Show_Str(0,0,(u8 *)filename,16,1);//显示图片名字 delay_ms(1000); } } } } } 触摸屏: Touch_Init(); if(Pen_Point.Key_Sta==Key_Down)//触摸屏被按下 { Pen_Int_Set(0);//关闭中断 do { Convert_Pos(); Pen_Point.Key_Sta=Key_Up; if(Pen_Point.X0>216&&Pen_Point.Y0<16)Load_Drow_Dialog();//清除 else { Draw_Big_Point(Pen_Point.X0,Pen_Point.Y0);//画图 GPIOC->ODR|=1<<1; //PC1 上拉 } }while(PEN==0);//如果PEN一直有效,则一直执行 Pen_Int_Set(1);//开启中断 }else delay_ms(10); 坐标变换 void Plot_ADC_Data(u16 *p) { u16 i; LCD_Fill(0,319-RATE,N,319,WHITE); for(i=0;i LCD_DrawLine(i, 319-p[i], (i+1), 319-p[i+1]); } } 坐标扩大一倍后: void Plot_ADC_Data(u16 *p) { u16 i; LCD_Fill(0,319-RATE,N,319,WHITE); for(i=0;i LCD_DrawLine(2*i, 319-p[i], 2*(i+1), 319-p[i+1]); } } 拓展:扩大m倍 void Plot_ADC_Data(u16 *p,u8 m) //这里引入m开控制波形显示 { u16 i; LCD_Fill(0,319-RATE,N,319,WHITE); for(i=0;i LCD_DrawLine(m*i, 319-p[i], m*(i+1), 319-p[i+1]); } } 引脚连接: LED端口连接: LED0 ——PORTA.8 LED1 ——PORTD.2 按键端口连接: KEY0——GPIOA.13 KEY1——GPIOA.15 WK_UP——GPIOA.0 键盘接口: PA0-PA7 PWM输出: PA8 TFT连接端口 数据口 – PB LCD_CS——PC9 LCD_RS——PC8 LCD_RW——PC7 LCD_RD——PC6 LCD_BL——PC10 DAC_TLV5638 DIN PC1 SCLK PC4 CS PC5 红外遥控: PA.1 DDS /*AD9850使用到的引脚为: GPIOA #define AD9850_RST GPIO_Pin_11 AD9850_DATA GPIO_Pin_12 AD9850_FQUD GPIO_Pin_13 #define AD9850_WCLK GPIO_Pin_14 */ 今晚听原子说起函数指针作为结构体成员的用法,很久没去复习过c语言,回来后特地找了个测试程序,发现这种用法有点像java里面的函数调用,思想值得借鉴。 把一些实体定义为结构体变量,而实体的固有属性便是基本数据类型定义的成员变量,而函数指针类型的成员变量可以抽象为你能对这个实体发出的请求, 例如一个遥控器实体,它具备的固有属性为品牌,按键数等等,而你可以发出的请求为按按键发送指令。 #include "stdio.h" struct DEMO //遥控器实体 { char x; // 按键 int (*func)(int x,int y); //函数指针 //按按键 }; int add2(int x,int y) //定义遥控器发送指令 { return x+y; } int dec2(int x,int y) //定义遥控器发送指令 { return x-y; } struct DEMO demo[2] = { {1,add2}, {2,dec2} }; int main(void) { printf("func%d=%d\\n",demo[0].x,demo[0].func(4,3)); printf("func%d=%d\\n",demo[1].x,demo[1].func(4,3)); return 0; } 说明:demo[0],demo[1]可以看做两种不同功能的遥控器实体, demo[0].x为获取遥控器的固有属性,可以是遥控器的品牌 demo[0].func(4,3)可以抽象为同时按下4,3按键后遥控器的反应; 此源码没有运行过! -- 我发一个运行过的源码。 在.h文件中,有如下代码: //TVM控制状态 typedef struct _m_TVM_CTRL { u32(*get_coin_totval)(void); //得到此次投入硬币总金额 u32(*get_coin_totcnt)(void); //得到此次投入硬币总数目 void(*clr_coin)(void); //清空金额和数目,每次新投币开始执行 u32(*get_coin_totrval)(void);//得到此次找零的总金额 u32(*get_coin_totrcnt)(void);//得到此次找零的硬币总数目 void(*clr_rcoin)(void); //清空金额和数目,每次新找零开始执行 void(*clr_hpx)(void); //清空某个hp中的所有硬币 void(*thp_add_money)(void); //将钱数汇总到循环找零hp中,当chg_mode为1的时候,可以执行该函数 u8 cur_cmd; //当前指令 0X00,无有效指令;0X32,开始收钱;0X33,硬币回收;0X34,找零; u16 cmd_prm; //指令参数 u8 cmd_sta; //指令执行状态 m_HP_MESSAGE hp_msg[4]; //每个hopper的状态信息 }m_TVM_CTRL; extern m_TVM_CTRL TVM_CTRL; //控制器 在.c文件中,有如下代码: //TVM主控制器 m_TVM_CTRL TVM_CTRL= //控制器 { get_coin_totval, get_coin_totcnt, clr_coin, get_coin_totrval, get_coin_totrcnt, clr_rcoin, clr_hpx, thp_add_money, 0, 0, 0, 0, }; get_coin_totva, clr_coinl等都是在.c文件中存在的函数的函数名。 这样,就完成了TVM_CTRL这个结构体的函数指针初始化, 接下来就可以方便应用了。 比如我要清空TVM的金额,我只需要写:TVM_CTRL.clr_coin();即可。 又比如,我要清空某个HOPPER,只需要写:TVM_CTRL.clr_hpx(); 这样可以及其方便的管理某些设备的各个状态。 你可以把任何一个外设都定义为一个结构体,然后在这个里面包含了该外设的所有执行函数,及状态变量。从而非常方便的控制该外设。 更好的是,一旦有任何新增功能,只需要在原来的结构体里面塞函数或者变量就可以了。及其方便维护。 这个对typedef的讲解很详细! 不管实在C还是C++代码中,typedef这个词都不少见,当然出现频率较高的还是在C代码中。typedef与#define有些相似,但更多的是不同,特别是在一些复杂的用法上,就完全不同了,看了网上一些C/C++的学习者的博客,其中有一篇关于typedef的总结还是很不错,由于总结的很好,我就不加修改的引用过来了,以下是引用的内容(红色部分是我自己写的内容)。用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, // 和一个字符变量; 以下则可行: typedef char* PCHAR; PCHAR pa, pb; 这种用法很有用,特别是char* pa, pb的定义,初学者往往认为是定义了两个字符型指针,其实不是,而用typedef char* PCHAR就不会出现这样的问题,减少了错误的发生。 用途二: 用在旧的C代码中,帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名对象名,如: struct tagPOINT1 { int x; int y; }; struct tagPOINT1 p1; 而在C++中,则可以直接写:结构名对象名,即:tagPOINT1 p1; typedef struct tagPOINT { int x; int y; }POINT; POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时 候,或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代 码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。 用途三: 用typedef来定义与平台无关的类型。 比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为: typedef long double REAL; 在不支持 long double 的平台二上,改为: typedef double REAL; 在连 double 都不支持的平台三上,改为: typedef float REAL; 也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。 标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健。 这个优点在我们写代码的过程中可以减少不少代码量哦! 用途四: 为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部 分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化 版。举例: 原声明:void (*b[10]) (void (*)()); 变量名为b,先替换右边部分括号里的,pFunParam为别名一: typedef void (*pFunParam)(); 再替换左边的变量b,pFunx为别名二: typedef void (*pFunx)(pFunParam); 原声明的最简化版: pFunx b[10]; 原声明:doube(*)() (*e)[9]; 变量名为e,先替换左边部分,pFuny为别名一: typedef double(*pFuny)(); 再替换右边的变量e,pFunParamy为别名二 typedef pFuny (*pFunParamy)[9]; 原声明的最简化版: pFunParamy e; 理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号 就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直 到整个声明分析完。举例: int (*func)(int *p); 首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针 ;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以 func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值 类型是int。 int (*func[5])(int *); func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明 func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符 优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数 组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。 这种用法是比较复杂的,出现的频率也不少,往往在看到这样的用法却不能理解,相信以上的解释能有所帮助。 *****以上为参考部分,以下为本人领悟部分***** 使用示例: 1.比较一: #include using namespace std; typedef int (*A) (char, char); int ss(char a, char b) { cout<<"功能1"< } int bb(char a, char b) { cout<<"功能2"< } void main() { A a; a = ss; a('a','b'); a = bb; a('a', 'b'); } 2.比较二: typedef int (A) (char, char); void main() { A *a; a = ss; a('a','b'); a = bb; a('a','b'); } 两个程序的结果都一样: 功能1 a b 功能2 b a *****以下是参考部分***** 参考自:http://blog.hc360.com/portal/personShowArticle.do?articleId=57527 typedef 与 #define的区别: 案例一: 通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子: typedef char *pStr1; #define pStr2 char *; pStr1 s1, s2; pStr2 s3, s4; 在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们 所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一 个类型起新名字。 案例二: 下面的代码中编译器会报一个错误,你知道是哪个语句错了吗? typedef char * pStr; char string[4] = "abc"; const char *p1 = string; const pStr p2 = string; p1++; p2++; 是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的 文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和 const long x本质上没有区别,都是对变量进行只读,只不过此处变量p2的数据类 型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数 据类型为char *的变量p2为只读,因此p2++错误。虽然作者在这里已经解释得很清楚了,可我在这个地方仍然还是糊涂的,真的希望哪位高手能帮忙指点一下,特别是这一句“只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已”,难道自己定义的类型前面用const修饰后,就不能执行更改运算,而系统定义的类型却可以?下载本文