单片机宏定义头函数申明不能使用宏定义的形参类型 void LCD_ShowNum(uint y,uint32 num,uchar len,uchar size);

1602显示数字0-99循环,求例子啊,最好是51单片机的噢~~_百度知道
1602显示数字0-99循环,求例子啊,最好是51单片机的噢~~
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
给你写一个参考程序/********************************************
显示0-99********************************************/#include &REG52.H&sbit
LCD_RS = P2^0;sbit
LCD_EN = P2^1;#define
P0unsigned char INT_TEMP;unsigned char INT_NUMQ;unsigned char value_temp[2];/* * 带形参延时1ms延时子函数**/void Delay_1ms(unsigned int Time){ unsigned int x, for(x = T x & 0; x--)
for(y = 120; y & 0; y--);}/* * LCD1602液晶写指令子函数**/void Write_LCDcrys_ByteCmd(unsigned char Date){ LCD_RS = 0; LCD_DATA = D Delay_1ms(30); LCD_EN = 1; Delay_1ms(30); LCD_EN = 0;}/* * LCD1602液晶写数据子函数**/void Write_LCDcrys_DataCmd(unsigned char Data){ LCD_RS = 1; LCD_DATA = D Delay_1ms(30); LCD_EN = 1; Delay_1ms(30); LCD_EN = 0; }/* * LCD1602液晶写指令初始化子函数**/void Init_LCDcrysRst(void){ Write_LCDcrys_ByteCmd(0x38); Write_LCDcrys_ByteCmd(0x08); Write_LCDcrys_ByteCmd(0x0C); Write_LCDcrys_ByteCmd(0x06); Write_LCDcrys_ByteCmd(0x01); }/* * 定时器初始化**/void Timer0_Init(void){ TMOD = 0x01; TH0 = (65536 - 50000) / 256; TL0 = (65536 - 50000) % 256; EA
= 1; ET0 = 1; TR0 = 1;}void DISPLAY_LCD1602(void){ value_temp[0] = INT_NUMQ
/ 10 + 0x30;
value_temp[1] = INT_NUMQ
% 10 + 0x30;
Write_LCDcrys_ByteCmd(0x80 + 4); Write_LCDcrys_DataCmd(value_temp[0]); Write_LCDcrys_DataCmd(value_temp[1]);}int main(void){ Init_LCDcrysRst(); Timer0_Init();
while(1) {
DISPLAY_LCD1602(); }}void Timer0()interrupt 1{ TH0 = (65536 - 50000) / 256; TL0 = (65536 - 50000) % 256; INT_TEMP++; if(INT_TEMP == 20) {
INT_TEMP = 0;
INT_NUMQ++;
if(INT_NUMQ == 100)
INT_NUMQ = 0;
采纳率:85%
为您推荐:
其他类似问题
51单片机的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。  为了提高源程序的质量和可维护性,从而最终提高软件产品生产力,特编写此规范。本标准规定了程序设计人员进行程序设计时必须遵循的规范。本规范主要针对单片机编程语言和编译器而言,包括排版、注释、命名、变量使用、代码可测性、程序效率、质量保证等内容。
1.基本规则
  格式清晰、注释简明扼要、命名规范易懂、函数模块化、程序易读易维护、功能准确实现、代码空间效率和时间效率高、适度的可扩展性、单片机编程规范-标识符命名。
2.标识符命名
&   命名基本原则
  ()命名清晰明了,有明确含义,使用完整单词或约定俗成的缩写。通常,较短的单词可通过去掉元音字母形成缩写;较长的单词可取单词的头几个字母形成缩写。即见名知意。
  ()命名风格要自始至终保持一致。
  ()命名中若使用特殊约定或缩写,要有注释说明。
  ()同一软件产品内模块之间接口部分的标识符名称之前加上模块标识。
宏和常量命名
  宏和常量用全部大写字母来命名,词与词之间用下划线分隔。对程序中用到的数字均应用有意义的枚举或宏来代替。
  变量名用小写字母命名,每个词的第一个字母大写。类型前缀()全局变量另加前缀。
  局部变量应简明扼要。局部循环体控制变量优先使用、、等;局部长度变量优先使用、等;临时中间变量优先使用、等。
  函数名用小写字母命名,每个词的第一个字母大写,并将模块标识加在最前面。
  一个文件包含一类功能或一个模块的所有函数,文件名称应清楚表明其功能或性质。每个文件应该有一个同名的文件作为头文件。
注释基本原则
  有助于对程序的阅读理解,说明程序在做什么,解释代码的目的、功能和采用的方法。一般情况源程序有效注释量在%左右。注释语言必须准确、易懂、简洁。边写代码边注释,修改代码同时修改相应的注释,不再有用的注释要删除。汇编和中都用,取消 不使用段注释 */ "(调试时可用)
  文件注释必须说明文件名、函数功能、创建人、创建日期、版本信息等相关信息。修改文件代码时,应在文件注释中记录修改日期、修改人员,并简要说明此次修改的目的。所有修改记录必须保持完整。文件注释放在文件顶端,用&&格式包含。注释文本每行缩进个空格;每个注释文本分项名称应对齐。
文件名称:
修改记录:
函数头部注释
  函数头部注释应包括函数名称、函数功能、入口参数、出口参数等内容。如有必要还可增加作者、创建日期、修改记录(备注)等相关项目。函数头部注释放在每个函数的顶端,用&&的格式包含。其中函数名称应简写为,不加入、出口参数等信息。
函数名称:
函数功能:
入口参数:
出口参数:
&& 代码注释应与被注释的代码紧邻,放在其上方或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开。一般少量注释应该添加在被注释语句的行尾,一个函数内的多个注释左对齐;较多注释则应加在上方且注释行与被注释的语句左对齐。函数代码注释用&的格式。
&&&通常,分支语句(条件分支、循环语句等)必须编写注释。其程序块结束行}的右方应加表明该程序块结束的标记 && 尤其在多重嵌套时。
变量、常量、宏的注释
&&& 同一类型的标识符应集中定义,并在定义之前一行对其共性加以统一注释。对单个标识符的注释加在定义语句的行尾。全局变量一定要有详细的注释,包括其功能、取值范围、哪些函数或过程存取它以及存取时的注意事项等。注释用&的格式。
函数设计原则
函数的基本要求:
正确性:程序要实现设计要求的功能。
稳定性和安全性:程序运行稳定、可靠、安全。
可测试性:程序便于测试和评价。
规范/可读性:程序书写风格、命名规则等符合规范。
扩展性:代码为下一次升级扩展留有空间和接口。
全局效率:软件系统的整体效率高。
局部效率:某个模块/子模块函数的本身效率高。
编制函数的基本原则:
单个函数的规模尽量限制在行以内(不包括注释和空行)。一个函数只完成一个功能。
函数局部变量的数目一般不超过~个。
函数内部局部变量定义区和功能实现区(包含变量初始化)之间空一行。
函数名应准确描述函数的功能。通常使用动宾词组为执行某操作的函数命名。
函数的返回值要清楚明了,尤其是出错返回值的意义要准确无误。
不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回。
减少函数本身或函数间的递归调用。
尽量不要将函数的参数作为工作变量。
函数若没有入口参数或者出口参数,应用明确申明。
函数名称与出口参数类型定义间应该空一格且只空一格。
函数名称与括号之间无空格。
函数形参必须给出明确的类型定义。
多个形参的函数,后一个形参与前一个形参的逗号分割符之间添加一个空格。
函数体的前后花括号{} 各独占一行。
局部变量定义
同一行内不要定义过多变量。
同一类的变量在同一行内定义,或者在相邻行定义。
先定义型变量,再定义型变量,再定义型变量(?)
数组、指针等复杂类型的定义放在定义区的最后。
变量定义区不做较复杂的变量赋值。
功能实现区规范
一行只写一条语句。
注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
各程序段之间使用一个空行分隔,加以必要的注释。程序段指能完一个较具体的功能的一行或多行代码。程序段内的各行代码之间相互依赖性较强。、、方式
不要使用难懂的技巧性很高的语句。
源程序中关系较为紧密的代码应尽可能相邻。
完成简单功能、关系非常密切的一条或几条语句可编写为函数或定义为宏。
5. 单片机编程规范-排版
    代码的每一级均往右缩进个空格的位置。不使用键
    每行语句(?????超过个字符)要分成多行书写;长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进适当的缩进,使排版整齐,语句可读。避免把注释插入分行中。
文件注释区、头文件引用区、函数间应该有且只有一行空行。
相邻函数之间应该有且只有一行空行。
函数体内相对独立的程序块之间可以用一行空行或注释来分隔。
函数注释和对应的函数体之间不应该有空行。
文件末尾有且只有一行空行。
函数语句尾部或者注释之后不能有空格。
括号内侧(即左括号后面和右括号前面)不加空格,多重括号间不加空格。
函数形参之间应该有且只有一个空格(形参逗号后面加空格)。
同一行中定义的多个变量间应该有且只有一个空格(变量逗号后面加空格)。
表达式中,若有多个操作符连写的情况,应使用空格对它们分隔:
在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符前后均加一个空格;在两个以上的关键字、变量、常量进行非对等操作时,其前后均不应加空格;
逗号只在后面加空格;
双目操作符,如比较操作符 赋值操作符、,算术操作符、,逻辑操作符、,位操作符、等,前后均加一个空格;
单目操作符,如、、、、(地址运算符)等,前后不加空格;
、前后不加空格;
、、、等关键字与后面的括号间加一个空格;
if、、、、语句无论其执行体是一条语句还是多条语句都必须加花括号,且左右花括号各独占一行。
do{}while()结构中,和均各占一行,和共同占用一行。
  嵌套越少越好,不准超过层
每个和其判据条件独占一行。
每个程序块需用结束。特殊情况下需要从一个块顺序执行到下一个块的时候除外,但需要花括号在交界处明确注释如此操作的原因,以防止出错。
程序块之间空一行,且只空一行。
每个程序块的执行语句保持个空格的缩进。
一般情况下都应该包含分支。
.程序结构
有函数的文件应将放在最前面,并明确用声明参数和返回值。
对由多个文件组成的模块程序或完整监控程序,建立公共引用头文件,将需要引用的库头文件、标准寄存器定义头文件、自定义的头文件、全局变量等均包含在内,供每个文件引用。通常,标准函数库头文件采用尖角号标志文件名,自定义头文件采用双撇号&P&P标志文件名。
每个文件有一个对应的文件,文件的注释之后首先定义一个唯一的文件标志宏,并在对应的文件中解析该标志。
    在文件中:
    在文件中:
对于确定只被某个文件调用的定义可以单独列在一个头文件中、单独调用。
可重入函数
    可重入函数中若使用了全局变量,应通过关中断、信号量等操作手段对其加以保护。
函数的形参
由函数调用者负责检查形参的合法性。
尽量避免将形参作为工作变量使用。
尽量减少循环嵌套层数
在多重循环中,应将最忙的循环放在最内层
循环体内工作量最小
尽量避免循环体内含有判断语句
.工程中所包含的文件
头文件的形式
程序中的头文件包括面向硬件对象头文件、公共头文件和总头文件。
工程编程是面向硬件对象的。例如,要用控制电机,在这样一个系统中,&面向硬件对象&概念体现在,工程中会创建&&的源程序文件专门用于电机控制。相应的,也要创建一个同名头文件&&用于控制电机的引脚定义、相关宏定义和电机控制函数声明等。像这样的头文件,就是面向硬件对象头文件。与之同名的&&文件可以包含它,来完成控制此硬件对象的引脚定义和相关宏定义;调用该硬件对象控制函数的文件也可以通过调用它来进行函数声明。
&&&&&&还有一类头文件不是专门针对于特定的硬件对象的,而是有一定的通用性。这类头文件被称为公共头文件。如工程中包含的&&文件,该文件用于语言中类型的别名定义,用户还可以根据自己的需要,随时在该文件中添加条目。在工程的任一文件中,需要用到这些别名时,都要包含&&。可见公共头文件并不拘泥于具体的硬件对象,它是为整个工程的和谐运作而建立的。
&&&& 总头文件是一个较特殊的头文件。它只被主函数文件包含,用于包含主函数文件中需要的头文件,宏定义,函数声明等。它使得主函数文件能够尽量避免改动,结构更加清晰。
头文件的命名
&&& 总的来说头文件的命名应尽量做到简短易懂,见名知意。面向硬件对象头文件的名称一定要与相应的硬件对象驱动文件同名。例???
公共头文件,如果对应于相应的源程序文件而建立,必须与之同名。如,&&是工程中的通用函数定义文件,(像内存数据移动函数,延时函数都属于通用函数),其他文件在用到这些函数之前,必须进行函数原型声明,从而建立与之同名的&&文件,专门用于相应的函数声明。其它的公共头文件没有同名要求,只要表清文件含义即可,如&&&&等。总头文件在一个工程中只有一个,它的名称较为固定,一般取为&&。
头文件注意事项
为了防止重复定义需要使用伪指令
#define VarType
typedef unsigned char& INT8U;&&&&&&&& //无符号位数
typedef signed&& char& INT8S;&&&&&&&& //有符号位数
typedef unsigned int&& INT16U;&&&&&&& //无符号位数
typedef signed &&int&& INT16S;&&&&&&& //有符号位数
typedef unsigned long& INT32U;&&&&&&& //无符号位数
typedef signed&& long& INT32S;&&&&&&& //有符号位数
typedef float&&&&&&&&& FP32;&&&&&&&&& //单精度浮点数
typedef double&&&&&&&& FP64;&&&&&&&&& //双精度浮点数
对于一个项目中的头文件与芯片相关的寄存器映像文件不可擅自改动,如果的确存在需要改动的地方另外开辟头文件。
    ① 的用法
      在语言中,常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像:
可以增强程序的可读性,以及标识符的灵活性,但它也有&非直观性&等缺点。
    ② 的用法
为一宏定义语句,通常用它来定义常量包括无参量与带参量,以及用来实现那些&表面似和善、背后一长串&的宏,它本身并不在编译过程中进行,而是在这之前预处理过程就已经完成了,但也因此难以发现潜在的错误及其它代码维护问题,它的实例像:
    ③ 与的区别
    从以上的概念便也能基本清楚,只是为了增加可读性而为标识符另起的新名称仅仅只是个别名,而原本在中是为了定义常量,到了,、、的出现使它也渐渐成为了起别名的工具。为了尽可能地兼容,一般都遵循定义&可读&的常量以及一些宏语句的任务,而则常用来定义关键字、冗长的类型的别名。
&&&& &宏定义只是简单的字符串代换原地扩展,而则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能。请看上面第一大点代码的第三行:
以及下面这行
效果相同?实则不同!实践中见差别:
的效果同表示定义了两个整型指针变量。
        而的效果同表示定义了一个整型指针变量和整型变量。
        注意:两者还有一个行尾号的区别哦!(???)
源程序文件
    源程序文件包括主函数文件、通用函数文件、硬件对象控制文件、芯片初始化文件、中断向量定义文件和中断使能文件。源程序文件的分类和命名类同于头文件,但也有它自己的特点。
主程序文件
工程中有且仅有一个主程序文件,它包含了工程的主处理流程。
主函数文件中包含:
()工程描述
工程名中每个意义单词(或单词缩写)的首字母大写,后缀为。
②硬件连接索引
工程所要控制的硬件对象索引,详细描述在相应的硬件对象控制文件中给出。
③工程的功能、目的和说明
④注意要点
可以注明编程要点和心得
注明工程完成日期
()总头文件
()主函数
芯片初始化文件(&&或 &&)
该文件与具体的芯片型号有关,并且只包含一个芯片初始化函数,若想由编译器自动调用芯片初始化函数,其函数名必须为,否则编译器会自动建立并调用一个空的汇编子程序,而不理会用户创建的芯片初始化函数。为了统一,将该函数起名为并在主函数中调用该函数。
通用函数头文件和通用函数文件
通用函数头文件和通用函数文件,&&和&&。
通用函数头文件 //类型别名定义 //延时函数声明
&&中包含:
()文件名
()通用函数所需用到的头文件
()通用函数用到的宏定义
()通用函数声明
外部函数要用到通用函数时,可包含这个头文件进行函数声明。
对象控制文件
中断处理函数和中断向量表文件
. 硬件封装的思想
与硬件相关的程序文件
与某个硬件相关的子程序放到个程序文件中,该硬件的头文件放到一个文件中。
程序文件的开始处是有关说明:本文件所包含的子程序及简要的功能说明,子程序分为内部调用和外部调用;硬件的连接说明。
中断的开放和禁止
使用宏定义方式开放或禁止中断,宏定义语句放在头文件中。宏名的定义方法:
开放中断以标识,宏名中包含中断名,宏名最后以结束。如:开放串行接收中断的宏名为:。
禁止中断以标识,宏名中包含中断名,宏名最后以结束。如:禁止串行接收中断的宏名为:。
开放所有中断宏名:。
禁止所有中断宏名:。
阅读(...) 评论()ALIENTEK 战舰STM32开发板第三十一章 触摸屏实验本章,我们将介绍如何使用 STM32 来驱动触摸屏,ALIENTEK 战舰 STM32 开发板本身并 没有触摸屏控制器, 但是它支持触摸屏, 可以通过外接带触摸屏的 LCD 模块 (比如 ALIENTEK TFTLCD 模块) ,来实现触摸屏控制。在本章中,我们将向大家介绍 STM32 控制 ALIENTKE TFTLCD 模块,使用软件模拟 SPI 来实现对 TFTLCD 模块的触摸屏控制,最终实现一个手写板 的功能。本章分为如下几个部分: 31.1 触摸屏简介 31.2 硬件设计 31.3 软件设计 31.4 下载验证www.openedv.com417 ALIENTEK 战舰STM32开发板31.1 触摸屏简介我们一般液晶所用的触摸屏,最多的就是电阻式触摸屏了(多点触摸属于电容式触摸屏, 比如几乎所有智能机都支持多点触摸,它们所用的屏就是电容式的触摸屏) ALIENTEK , TFTLCD 自带的触摸屏属于电阻式触摸屏,下面简单介绍下电阻式触摸屏的原理。 电阻式触摸屏利用压力感应进行控制。电阻触摸屏的主要部分是一块与显示器表面非常配 合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有 一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的 塑料层、它的内表面也涂有一层涂层、在他们之间有许多细小的(小于 1/1000 英寸)的透明隔 离点把两层导电层隔开绝缘。 当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻 发生变化,在 X 和 Y 两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计 算出(X,Y)的位置,再根据获得的位置模拟鼠标的方式运作。这就是电阻技术触摸屏的最基 本的原理。 电阻屏的特点有: 1)是一种对外界完全隔离的工作环境,不怕灰尘、水汽和油污。 2)可以用任何物体来触摸,可以用来写字画画,这是它们比较大的优势。 3)电阻触摸屏的精度只取决于 A/D 转换的精度,因此都能轻松达到 。 从以上介绍可知,触摸屏都需要一个 AD 转换器, 一般来说是需要一个控制器的。 ALIENTEK TFTLCD 模块选择的是四线电阻式触摸屏,这种触摸屏的控制芯片有很多,包括: ADS7843、ADS7846、TSC2046、XPT2046 和 AK4182 等。这几款芯片的驱动基本上是一样的, 也就是你只要写出了 ADS7843 的驱动, 这个驱动对其他几个芯片也是有效的。 而且封装也有一 样的,完全 PIN TO PIN 兼容。所以在替换起来,很方便。 ALIENTEK TFTLCD 模块自带的触摸屏控制芯片为 XPT2046。 XPT2046 是一款 4 导线制触 摸屏控制器, 内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。 XPT2046 支持从 1.5V 到 5.25V 的低电压 I/O 接口。XPT2046 能通过执行两次 A/D 转换查出被按的屏幕位置, 除此 之外,还可以测量加在触摸屏上的压力。内部自带 2.5V 参考电压可以作为辅助输入、温度测量 和电池监测模式之用,电池监测的电压范围可以从 0V 到 6V。XPT2046 片内集成有一个温度传 感器。 在 2.7V 的典型工作状态下,关闭参考电压,功耗可小于 0.75mW。XPT2046 采用微小 的封装形式:TSSOP-16,QFN-16(0.75mm 厚度)和 VFBGA-48。工作温度范围为-40℃~+85℃。 该芯片完全是兼容 ADS7843 和 ADS7846 的,关于这个芯片的详细使用,可以参考这两个 芯片的 datasheet。www.openedv.com418 ALIENTEK 战舰STM32开发板31.2 硬件设计本章实验功能简介:开机的时候先通过 24C02 的数据判断触摸屏是否已经校准过,如果没 有校准,则执行校准程序,校准过后再进入手写程序。如果已经校准了,就直接进入手写程序, 此时可以通过按动屏幕来实现手写输入。屏幕上会有一个清空的操作区域(RST),点击这个 地方就会将输入全部清除,恢复白板状态。程序会设置一个强制校准,就是通过按 KEY0 来实 现,只要按下 KEY0 就会进入强制校准程序。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) KEY0 按键 3) TFTLCD 模块(带触摸屏) 4) 24C02 所有这些资源与 STM32 的连接图,在前面都已经介绍了,这里我们只针对 TFTLCD 模块 与 STM32 的连接端口再说明一下,TFTLCD 模块的触摸屏总共有 5 跟线与 STM32 连接,连接 电路图如图 31.2.1 所示:图 31.2.1 触摸屏与 STM32 的连接图 从图中可以看出,T_MISO、 T_PEN、 T_CS、 T_MOSI 和 T_SCK 分别连接在 STM32 的: PF8、 PF10、 PB2、PF9 和 PB1 上。31.3 软件设计打开上一章的工程,首先在 HARDWARE 文件夹下新建一个 TOUCH 文件夹。然后新建一 个 touch.c 和 touch.h 的文件保存在 TOUCH 文件夹下,并将这个文件夹加入头文件包含路径。 打开 touch.c 文件,在里面输入与触摸屏相关的代码,这里我们也不全部贴出来了,仅介绍 几个重要的函数。 首先我们要介绍的是 TP_Read_XY2 这个函数, 该函数专门用于从触摸屏控制 IC 读取坐标的 值(0~4095),TP_Read_XY2 的代码如下: //连续 2 次读取触摸屏 IC,且这两次的偏差不能超过 //ERR_RANGE,满足条件,则认为读数正确,否则读数错误.www.openedv.com419 ALIENTEK 战舰STM32开发板//该函数能大大提高准确度 //x,y:读取到的坐标值 //返回值:0,失败;1,成功。 #define ERR_RANGE 50 //误差范围 u8 TP_Read_XY2(u16 *x,u16 *y) { u16 x1,y1; u16 x2,y2; u8 flag=TP_Read_XY(&x1,&y1); if(flag==0)return(0); flag=TP_Read_XY(&x2,&y2); if(flag==0)return(0); if(((x2&=x1&&x1&x2+ERR_RANGE)||(x1&=x2&&x2&x1+ERR_RANGE)) //前后两次采样在+- ERR_RANGE 内 &&((y2&=y1&&y1&y2+ERR_RANGE)||(y1&=y2&&y2&y1+ERR_RANGE))) { *x=(x1+x2)/2; *y=(y1+y2)/2; return 1; }else return 0; } 该函数采用了一个非常好的办法来读取屏幕坐标值,就是连续读两次,两次读取的值之差 不能超过一个特定的值(ERR_RANGE),通过这种方式,我们可以大大提高触摸屏的准确度。另 外该函数调用的 TP_Read_XY 函数,用于单次读取坐标值。TP_Read_XY 也采用了一些软件滤波 算法,具体见光盘的源码。接下来,我们介绍另外一个函数 TP_Adjust,该函数源码如下: //触摸屏校准代码 //得到四个校准参数 void TP_Adjust(void) { u16 pos_temp[4][2];//坐标缓存值 u8 cnt=0; u16 d1,d2; u32 tem1,tem2; u16 outtime=0; cnt=0; POINT_COLOR=BLUE; BACK_COLOR =WHITE; LCD_Clear(WHITE);//清屏 POINT_COLOR=RED;//红色 LCD_Clear(WHITE);//清屏 POINT_COLOR=BLACK;www.openedv.com420 ALIENTEK 战舰STM32开发板LCD_ShowString(40,40,160,100,16,(u8*)TP_REMIND_MSG_TBL);//显示提示信息 TP_Drow_Touch_Point(20,20,RED);//画点 1 tp_dev.sta=0;//消除触发信号 tp_dev.xfac=0;//xfac 用来标记是否校准过,所以校准之前必须清掉!以免错误 while(1)//如果连续 10 秒钟没有按下,则自动退出 { tp_dev.scan(1); //扫描物理坐标 if((tp_dev.sta&0xc0)==TP_CATH_PRES) //按键按下了一次(此时按键松开了.) { outtime=0; tp_dev.sta&=~(1&&6);//标记按键已经被处理过了. pos_temp[cnt][0]=tp_dev.x; pos_temp[cnt][1]=tp_dev.y; cnt++; switch(cnt) { case 1: TP_Drow_Touch_Point(20,20,WHITE); //清除点 1 TP_Drow_Touch_Point(lcddev.width-20,20,RED); //画点 2 case 2: TP_Drow_Touch_Point(lcddev.width-20,20,WHITE); //清除点 2 TP_Drow_Touch_Point(20,lcddev.height-20,RED); //画点 3 case 3: TP_Drow_Touch_Point(20,lcddev.height-20,WHITE); //清除点 3 TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,RED); //画点 4 case 4://全部四个点已经得到 //对边相等 tem1=abs(pos_temp[0][0]-pos_temp[1][0]);//x1-x2 tem2=abs(pos_temp[0][1]-pos_temp[1][1]);//y1-y2 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到 1,2 的距离 tem1=abs(pos_temp[2][0]-pos_temp[3][0]);//x3-x4 tem2=abs(pos_temp[2][1]-pos_temp[3][1]);//y3-y4 tem1*=tem1; tem2*=tem2; d2=sqrt(tem1+tem2);//得到 3,4 的距离 fac=(float)d1/d2;www.openedv.com421 ALIENTEK 战舰STM32开发板if(fac&0.95||fac&1.05||d1==0||d2==0)//不合格 { cnt=0; TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE); //清除点 4 TP_Drow_Touch_Point(20,20,RED); //画点 1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1] [0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3] [0],pos_temp[3][1],fac*100);//显示数据 } tem1=abs(pos_temp[0][0]-pos_temp[2][0]);//x1-x3 tem2=abs(pos_temp[0][1]-pos_temp[2][1]);//y1-y3 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到 1,3 的距离 tem1=abs(pos_temp[1][0]-pos_temp[3][0]);//x2-x4 tem2=abs(pos_temp[1][1]-pos_temp[3][1]);//y2-y4 tem1*=tem1; tem2*=tem2; d2=sqrt(tem1+tem2);//得到 2,4 的距离 fac=(float)d1/d2; if(fac&0.95||fac&1.05)//不合格 { cnt=0; TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20, WHITE); //清除点 4 TP_Drow_Touch_Point(20,20,RED); //画点 1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1] [0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3] [0],pos_temp[3][1],fac*100);//显示数据 }//正确了 //对角线相等 tem1=abs(pos_temp[1][0]-pos_temp[2][0]);//x1-x3 tem2=abs(pos_temp[1][1]-pos_temp[2][1]);//y1-y3 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到 1,4 的距离 tem1=abs(pos_temp[0][0]-pos_temp[3][0]);//x2-x4 tem2=abs(pos_temp[0][1]-pos_temp[3][1]);//y2-y4 tem1*=tem1; tem2*=tem2;www.openedv.com422 ALIENTEK 战舰STM32开发板d2=sqrt(tem1+tem2);//得到 2,3 的距离 fac=(float)d1/d2; if(fac&0.95||fac&1.05)//不合格 { cnt=0; TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20, WHITE); //清除点 4 TP_Drow_Touch_Point(20,20,RED);//画点 1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1] [0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3] [0],pos_temp[3][1],fac*100);//显示数据 }//正确了 //计算结果 tp_dev.xfac=(float)(lcddev.width-40)/(pos_temp[1][0]-pos_temp[0][0]); //得到 xfac tp_dev.xoff=(lcddev.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0] [0]))/2;//得到 xoff tp_dev.yfac=(float)(lcddev.height-40)/(pos_temp[2][1]-pos_temp[0][1] );//得到 yfac tp_dev.yoff=(lcddev.height-tp_dev.yfac*(pos_temp[2][1]+pos_temp[0] [1]))/2;//得到 yoff if(abs(tp_dev.xfac)&2||abs(tp_dev.yfac)&2)//触屏和预设的相反了. { cnt=0; TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE );//清除点 4 TP_Drow_Touch_Point(20,20,RED); //画点 1 LCD_ShowString(40,26,lcddev.width,lcddev.height,16,&TP Need readjust!&); tp_dev.touchtype=!tp_dev.//修改触屏类型. if(tp_dev.touchtype)//X,Y 方向与屏幕相反 { CMD_RDX=0X90; CMD_RDY=0XD0; }else //X,Y 方向与屏幕相同 { CMD_RDX=0XD0; CMD_RDY=0X90; } } POINT_COLOR=BLUE;www.openedv.com423 ALIENTEK 战舰STM32开发板LCD_Clear(WHITE);//清屏 LCD_ShowString(35,110,lcddev.width,lcddev.height,16,&Touch Screen Adjust OK!&);//校正完成 delay_ms(1000); TP_Save_Adjdata(); LCD_Clear(WHITE);//清屏//校正完成 } } delay_ms(10); outtime++; if(outtime&1000) { TP_Get_Adjdata(); } } } TP_Adjust 是此部分最核心的代码,在这里,给大家介绍一下我们这里所使用的触摸屏校 正原理:我们传统的鼠标是一种相对定位系统,只和前一次鼠标的位置坐标有关。而触摸屏则 是一种绝对坐标系统,要选哪就直接点哪,与相对定位系统有着本质的区别。绝对坐标系统的 特点是每一次定位坐标与上一次定位坐标没有关系,每次触摸的数据通过校准转为屏幕上的坐 标,不管在什么情况下,触摸屏这套坐标在同一点的输出数据是稳定的。不过由于技术原理的 原因,并不能保证同一点触摸每一次采样数据相同,不能保证绝对坐标定位,点不准,这就是 触摸屏最怕出现的问题:漂移。对于性能质量好的触摸屏来说,漂移的情况出现并不是很严重。 所以很多应用触摸屏的系统启动后,进入应用程序前,先要执行校准程序。 通常应用程序中使 , 用的 LCD 坐标是以像素为单位的。比如说:左上角的坐标是一组非 0 的数值,比如(20,20) 而右下角的坐标为(220,300) 。这些点的坐标都是以像素为单位的,而从触摸屏中读出的是点 的物理坐标,其坐标轴的方向、XY 值的比例因子、偏移量都与 LCD 坐标不同,所以,需要在 程序中把物理坐标首先转换为像素坐标,然后再赋给 POS 结构,达到坐标转换的目的。 校正思路:在了解了校正原理之后,我们可以得出下面的一个从物理坐标到像素坐标的转 换关系式: LCDx=xfac*Px+xoff; LCDy=yfac*Py+yoff; 其中(LCDx,LCDy)是在 LCD 上的像素坐标, (Px,Py)是从触摸屏读到的物理坐标。xfac, yfac 分别是 X 轴方向和 Y 轴方向的比例因子,而 xoff 和 yoff 则是这两个方向的偏移量。 这样我们只要事先在屏幕上面显示 4 个点 (这四个点的坐标是已知的) 分别按这四个点就 , 可以从触摸屏读到 4 个物理坐标,这样就可以通过待定系数法求出 xfac、yfac、xoff、yoff 这四 个参数。我们保存好这四个参数,在以后的使用中,我们把所有得到的物理坐标都按照这个关 系式来计算,得到的就是准确的屏幕坐标。达到了触摸屏校准的目的。 TP_Adjust 就 是 根 据 上 面 的 原 理 设 计 的 校 准 函 数 , 注 意 该 函 数 里 面 多 次 使 用 了 lcddev.width 和 lcddev.height, 用于坐标设置, 主要是为了兼容不同尺寸的 LCD 比如 320*480 ( 和 320*240 的屏都可以兼容) 。其他的函数我们这里就不多介绍了,保存 touch.c 文件,并把该www.openedv.com424 ALIENTEK 战舰STM32开发板文件加入到 HARDWARE 组下。接下来打开 touch.h 文件,在该文件里面输入如下代码: #ifndef __TOUCH_H__ #define __TOUCH_H__ #include &sys.h& #define TP_PRES_DOWN 0x80 //触屏被按下 #define TP_CATH_PRES 0x40 //有按键按下了 //触摸屏控制器 typedef struct { u8 (*init)(void); //初始化触摸屏控制器 u8 (*scan)(u8); //扫描触摸屏.0,屏幕扫描;1,物理坐标; void (*adjust)(void); //触摸屏校准 u16 x0; //原始坐标(第一次按下时的坐标) u16 y0; u16 //当前坐标(此次扫描时,触屏的坐标) u16 u8 //笔的状态 //b7:按下 1/松开 0; //b6:0,没有按键按下;1,有按键按下. ////////////////////////触摸屏校准参数///////////////////////// //新增的参数,当触摸屏的左右上下完全颠倒时需要用到. //touchtype=0 的时候,适合左右为 X 坐标,上下为 Y 坐标的 TP. //touchtype=1 的时候,适合左右为 Y 坐标,上下为 X 坐标的 TP. u8 }_m_tp_ extern _m_tp_dev tp_ //触屏控制器在 touch.c 里面定义 //与触摸屏芯片连接引脚 #define PEN PFin(10) //PF10 INT #define DOUT PFin(8) //PF8 MISO #define TDIN PFout(9) //PF9 MOSI #define TCLK PBout(1) //PB1 SCLK #define TCS PBout(2) //PB2 CS void TP_Write_Byte(u8 num); //向控制芯片写入一个数据 u16 TP_Read_AD(u8 CMD); //读取 AD 转换值 u16 TP_Read_XOY(u8 xy); //带滤波的坐标读取(X/Y) u8 TP_Read_XY(u16 *x,u16 *y); //双方向读取(X+Y) u8 TP_Read_XY2(u16 *x,u16 *y); //带加强滤波的双方向坐标读取 void TP_Drow_Touch_Point(u16 x,u16 y,u16 color); //画一个坐标校准点 void TP_Draw_Big_Point(u16 x,u16 y,u16 color); //画一个大点www.openedv.com425 ALIENTEK 战舰STM32开发板u8 TP_Scan(u8 tp); //扫描 void TP_Save_Adjdata(void); //保存校准参数 u8 TP_Get_Adjdata(void); //读取校准参数 void TP_Adjust(void); //触摸屏校准 u8 TP_Init(void); //初始化 void TP_Adj_Info_Show(u16 x0,u16 y0,u16 x1,u16 y1,u16 x2,u16 y2,u16 x3,u16 y3,u16 fac); //显示校准信息 #endif 上述代码,我们定义了_m_tp_dev 结构体,用于管理和记录触摸屏相关信息,在外部调用 的时候, 我们一般直接调用 tp_dev 的相关成员函数/变量屏即可达到需要的效果。 这样种设计简 化了接口,另外管理和维护也比较方便,大家可以效仿一下。其他部分我们不做多的介绍,最 后我们打开 main.c,修改代码如下: //清屏,重新装载对话界面 void Load_Drow_Dialog(void) { LCD_Clear(WHITE);//清屏 POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(lcddev.width-24,0,200,16,16,&RST&);//显示清屏区域 POINT_COLOR=RED;//设置画笔蓝色 } int main(void) { u8 u8 i=0; delay_init(); //延时函数初始化 NVIC_Configuration(); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级 uart_init(9600); //串口初始化为 9600 LED_Init(); //LED 端口初始化 LCD_Init(); KEY_Init(); POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,&WarShip STM32&); LCD_ShowString(60,70,200,16,16,&TOUCH TEST&); LCD_ShowString(60,90,200,16,16,&ATOM@ALIENTEK&); LCD_ShowString(60,110,200,16,16,&&); LCD_ShowString(60,130,200,16,16,&Press KEY0 to Adjust&); tp_dev.init(); delay_ms(1500); Load_Drow_Dialog(); while(1) { key=KEY_Scan(0); tp_dev.scan(0);www.openedv.com426 ALIENTEK 战舰STM32开发板if(tp_dev.sta&TP_PRES_DOWN) //触摸屏被按下 { if(tp_dev.x&lcddev.width&&tp_dev.y&lcddev.height) { if(tp_dev.x&(lcddev.width-24)&&tp_dev.y&16)Load_Drow_Dialog();//清除 else TP_Draw_Big_Point(tp_dev.x,tp_dev.y,RED); //画图 } }else delay_ms(10); //没有按键按下的时候 if(key==KEY_RIGHT) //KEY_RIGHT 按下,则执行校准程序 { LCD_Clear(WHITE);//清屏 TP_Adjust(); //屏幕校准 TP_Save_Adjdata(); Load_Drow_Dialog(); } i++; if(i==20) { i=0; LED0=!LED0; } } } 此函数就实现了我们上面介绍的本章所要实现的功能。当然这里还用到我们之前写的 24CXX 的代码,用来保存和调用触摸屏的校准信息(在触摸屏校准函数和初始化函数里面) 。31.4 下载验证在代码编译成功之后,我们通过下载代码到 ALIENTEK 战舰 STM32 开发板上,得到如图 31.4.1 所示:www.openedv.com427 ALIENTEK 战舰STM32开发板图 31.4.1 程序运行效果 如果已经校准过了, 则在等待 1.5s 之后进入手写界面, 同时 DS0 开始闪烁, 界面如图 31.4.2 所示:图 31.4.2 手写界面 此时,我们就可以在该界面下用笔或者手指输入信息了。如果没有校准过,则会自动进入 校准程序(当你发现精度不行的时候,也可以通过按 KEY0 进入校准程序) ,如图 31.4.3 所示,www.openedv.com428 ALIENTEK 战舰STM32开发板在校准完成之后自动进入手写界面。图 31.4.3 校准界面www.openedv.com429 ALIENTEK 战舰STM32开发板第三十二章红外遥控实验本章, 我们将向大家介绍如何通过 STM32 来解码红外遥控器的信号。 ALIENTK 战舰 STM32 开发板标配了红外接收头和一个很小巧的红外遥控器。在本章中,我们将利用 STM32 的输入 捕获功能,解码开发板标配的这个红外遥控器的编码信号,并将解码后的键值 TFTLCD 模块上 显示出来。本章分为如下几个部分: 32.1 红外遥控简介 32.2 硬件设计 32.3 软件设计 32.4 下载验证www.openedv.com430 ALIENTEK 战舰STM32开发板32.1 红外遥控简介红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成 本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计 算机系统中。 由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设 计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率 或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有 相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用 电器上普及红外线遥控提供了极大的方面。由于红外线为不可见光,因此对环境影响很小,再 由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影 响临近的无线电设备。 红外遥控的编码目前广泛使用的是:NEC Protocol 的 PWM(脉冲宽度调制)和 Philips RC-5 Protocol 的 PPM(脉冲位置调制)。ALIENTEK 战舰 STM32 开发板配套的遥控器使用 的是 NEC 协议,其特征如下: 1、8 位地址和 8 位指令长度; 2、地址和命令 2 次传输(确保可靠性) 3、PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”; 4、载波频率为 38Khz; 5、位时间为 1.125ms 或 2.25ms; NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控 接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到 的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。 NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码 由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可 用于校验)。 我们遥控器的按键 2 按下时,从红外接收头端收到的波形如图 32.1.1 所示:图 32.1.1 按键 2 所对应的红外波形 从图 32.1.1 中可以看到,其地址码为 0,控制码为 168。可以看到在 100ms 之后,我们还 收到了几个脉冲,这是 NEC 码规定的连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平 +97.94ms 高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码, 即连发码,可以通过统计连发码的次数来标记按键按下的长短/次数。 第十五章我们曾经介绍过利用输入捕获来测量高电平的脉宽,本章解码红外遥控信号,刚 好可以利用输入捕获的这个功能来实现遥控解码。关于输入捕获的介绍,请参考第十五章的内 容。www.openedv.com431 ALIENTEK 战舰STM32开发板32.2 硬件设计本实验采用定时器的输入捕获功能实现红外解码, 本章实验功能简介: 开机在 LCD 上显示 一些信息之后,即进入等待红外触发,如过接收到正确的红外信号,则解码,并在 LCD 上显示 键值和所代表的意义,以及按键次数等信息。同样我们也是用 LED0 来指示程序正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) TFTLCD 模块(带触摸屏) 3) 红外接收头 4) 红外遥控器 前两个,在之前的实例已经介绍过了,遥控器属于外部器件,遥控接收头在板子上,与 MCU 的连接原理图如 32.2.1 所示:图 32.2.1 红外遥控接收头与 STM32 的连接电路图 红外遥控接收头连接在 STM32 的 PB9(TIM4_CH4)上。硬件上不需要变动,只要程序将 TIM4_CH4 设计为输入捕获,然后将收到的脉冲信号解码就可以了。开发板配套的红外遥控器 外观如图 32.2.2 所示:图 32.2.2 红外遥控器www.openedv.com432 ALIENTEK 战舰STM32开发板32.3 软件设计打开我们光盘的红外遥控器实验工程, 可以看到我们添加了 remote.c 和 remote.h 两个文件, 同 时 因 为 我 们 使 用 的 是 输 入 捕 获 , 所 以 还 用 到 库 函 数 stm32f10x_tim.c 和 头 文 件 stm32f10x_tim.h。 打开 remote.c 文件,代码如下: #include &remote.h& #include &delay.h& #include &usart.h& //红外遥控初始化 //设置 IO 以及定时器 4 的输入捕获 void Remote_Init(void) { GPIO_InitTypeDef GPIO_InitS NVIC_InitTypeDef NVIC_InitS TIM_TimeBaseInitTypeDef TIM_TimeBaseS TIM_ICInitTypeDef TIM_ICInitS RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能 PORTB 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //TIM4 时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_9); //初始化 GPIOB.9 //PB9 输入 //上拉输入TIM_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大 10ms 溢出 TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //预分频器,1M 的计数频率,1us TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; // 选择输入 IC4 映射到 TI4 上 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_R //上升沿捕获 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频 TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 8 个定时器时钟周期滤波 TIM_ICInit(TIM4, &TIM_ICInitStructure);//初始化定时器输入捕获通道www.openedv.com433 ALIENTEK 战舰STM32开发板TIM_Cmd(TIM4,ENABLE ); //使能定时器 4NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM3 中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级 0 级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级 3 级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化外设 NVIC 寄存器 TIM_ITConfig( TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE);//允许更新中断 , //允许 CC4IE 捕获中断 } //遥控器接收状态 //[7]:收到了引导码标志 //[6]:得到了一个按键的所有信息 //[5]:保留 //[4]:标记上升沿是否已经被捕获 //[3:0]:溢出计时器 u8 RmtSta=0; u16 D //下降沿时计数器的值 u32 RmtRec=0; //红外接收到的数据 u8 RmtCnt=0; //按键按下的次数 //定时器 2 中断服务程序 void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET) { if(RmtSta&0x80)//上次有数据被接收到了 { RmtSta&=~0X10; //取消上升沿已经被捕获标记 if((RmtSta&0X0F)==0X00)RmtSta|=1&&6;//标记已经完成一次键值信息采集 if((RmtSta&0X0F)&14)RmtSta++; else { RmtSta&=~(1&&7);//清空引导标识 RmtSta&=0XF0; //清空计数器 } } } if(TIM_GetITStatus(TIM4,TIM_IT_CC4)!=RESET) { if(RDATA)//上升沿捕获 {www.openedv.com434 ALIENTEK 战舰STM32开发板TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling); //下降沿捕获 TIM_SetCounter(TIM4,0); //清空定时器值 RmtSta|=0X10; //标记上升沿已经被捕获 }else //下降沿捕获 { Dval=TIM_GetCapture4(TIM4);//读取 CCR1 也可以清 CC1IF 标志位 TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising); //上升沿捕获 if(RmtSta&0X10) //完成一次高电平捕获 { if(RmtSta&0X80)//接收到了引导码 { if(Dval&300&&Dval&800) { RmtRec&&=1; //左移一位. RmtRec|=0; //接收到 0 }else if(Dval&1400&&Dval&1800) { RmtRec&&=1; //左移一位. RmtRec|=1; //接收到 1 }else if(Dval&2200&&Dval&2600) { RmtCnt++; RmtSta&=0XF0; //按键次数增加 1 次 //清空计时器 //560 为标准值,560us//1680 为标准值,1680us//得到按键键值增加的信息 //2500 为标准值 2.5ms} }else if(Dval&4200&&Dval&4700) //4500 为标准值 4.5ms { RmtSta|=1&&7; //标记成功接收到了引导码 RmtCnt=0; //清除按键次数计数器 } } RmtSta&=~(1&&4); } } TIM_ClearFlag(TIM4,TIM_IT_Update|TIM_IT_CC4); } //处理红外键盘 //返回值: // 0,没有任何按键按下 //其他,按下的按键键值.www.openedv.com435 ALIENTEK 战舰STM32开发板u8 Remote_Scan(void) { u8 sta=0; u8 t1,t2; if(RmtSta&(1&&6))//得到一个按键的所有信息了 { t1=RmtRec&&24; //得到地址码 t2=(RmtRec&&16)&0 //得到地址反码 if((t1==(u8)~t2)&&t1==REMOTE_ID)//检验遥控识别码(ID)及地址 { t1=RmtRec&&8; t2=RmtR if(t1==(u8)~t2)sta=t1;//键值正确 } if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了 { RmtSta&=~(1&&6);//清除接收到有效按键标识 RmtCnt=0; //清除按键次数计数器 } } } 该部分代码包含 3 个函数,首先是 Remote_Init 函数,该函数用于初始化 IO 口,并配置 TIM4_CH4 为输入捕获,并设置其相关参数, 这里的配置跟输入捕获实验的配置基本接近,大 家可以参考一下输入捕获实验的讲解。TIM4_IRQHandler 函数是 TIM4 的中断服务函数,在该 函数里面,实现对红外信号的高电平脉冲的捕获,同时根据我们之前简介的协议内容来解码 , 该函数用到几个全局变量,用于辅助解码,并存储解码结果。最后是 Remote_Scan 函数,该函 用来扫描解码结果,相当于我们的按键扫描,输入捕获解码的红外数据,通过该函数传送给其 他程序。 接下来打开 remote.h,该文件代码比较简单,宏定义的标识符 REMOTE_ID 就是我们开发 板配套的遥控器的识别码, 对于其他遥控器可能不一样,只要修改这个为你所使用的遥控器的一 致就可以了。其他是一些函数的声明。下面我们看看 main.c 里面主函数代码: int main(void) { u8 u8 t=0; u8 *str=0; delay_init(); //延时函数初始化 NVIC_Configuration(); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级 uart_init(9600); //串口初始化为 9600 LED_Init(); //LED 端口初始化 LCD_Init();www.openedv.com436 ALIENTEK 战舰STM32开发板KEY_Init(); Remote_Init(); //红外接收初始化POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,&WarShip STM32&); LCD_ShowString(60,70,200,16,16,&REMOTE TEST&); LCD_ShowString(60,90,200,16,16,&ATOM@ALIENTEK&); LCD_ShowString(60,110,200,16,16,&&); LCD_ShowString(60,130,200,16,16,&KEYVAL:&); LCD_ShowString(60,150,200,16,16,&KEYCNT:&); LCD_ShowString(60,170,200,16,16,&SYMBOL:&);while(1) { key=Remote_Scan(); if(key) { LCD_ShowNum(116,130,key,3,16); LCD_ShowNum(116,150,RmtCnt,3,16); switch(key) { case 0:str=&ERROR&; case 162:str=&POWER&; case 98:str=&UP&; case 2:str=&PLAY&; case 226:str=&ALIENTEK&; case 194:str=&RIGHT&; case 34:str=&LEFT&; case 224:str=&VOL-&; case 168:str=&DOWN&; case 144:str=&VOL+&; case 104:str=&1&; case 152:str=&2&; case 176:str=&3&; case 48:str=&4&; case 24:str=&5&; case 122:str=&6&; case 16:str=&7&; case 56:str=&8&; case 90:str=&9&; case 66:str=&0&;//显示键值 //显示按键次数www.openedv.com437 ALIENTEK 战舰STM32开发板case 82:str=&DELETE&; } LCD_Fill(116,170,116+8*8,170+16,WHITE); //清楚之前的显示 LCD_ShowString(116,170,200,16,16,str); //显示 SYMBOL }else delay_ms(10); t++; if(t==20) { t=0; LED0=!LED0; } } } 主函数 main 代码比较简单,进行一系列初始化后,根据扫描到的按键值来显示。至此,我 们的软件设计部分就结束了。32.4 下载验证在代码编译成功之后,我们通过下载代码到 ALIENTEK 战舰 STM32 开发板上,可以看到 LCD 显示如图 32.4.1 所示的内容:图 32.4.1 程序运行时 LCD 显示内容 此时我们通过遥控器按下不同的按键, 则可以看到 LCD 上显示了不同按键的键值以及按键 次数和对应的遥控器上的符号。如图 32.4.2 所示:www.openedv.com438 ALIENTEK 战舰STM32开发板图 32.4.2 解码成功www.openedv.com439 ALIENTEK 战舰STM32开发板第三十三章游戏手柄实验相信 80 后小时候都有玩过 FC 游戏机(又称:红白机/小霸王游戏机) ,那是一代经典,给 我们的童年带了了无限乐趣。 本章, 我们将向大家介绍如何通过 STM32 来驱动 FC 游戏机手柄, 将 FC 游戏机的手柄作为战舰 STM32 开发板的输入设备 (综合实验可以直接通过这个手柄来玩 FC 游戏) 。 在本章中,我们将使用 STM32 驱动 FC 手柄,将手柄的按键键值等信息通过 TFTLCD 模 块显示出来。本章分为如下几个部分: 33.1 游戏手柄简介 33.2 硬件设计 33.3 软件设计 33.4 下载验证www.openedv.com440 ALIENTEK 战舰STM32开发板33.1 游戏手柄简介FC 游戏机曾今是一统天下(现在也还是很多人玩) ,红极一时,那时任天堂单是 FC 机的 主机的发售收入就超过全美国的电视台的收入的总和。本章,我们将使用 STM32 来驱动 FC 手柄,实现手柄控制信号的读取,我们先来了解一下 FC 手柄。 FC 手柄,大致可分为两种:一种手柄插口是 11 针的,一种是 9 针的。但 11 针的现在市 面上很少了(因为 11 针手柄是早期 FC 组装兼容机最主要的周边) ,现在几乎都是 9 针 FC 组 装手柄的天下,所以我们本章使用的是 9 针 FC 手柄,该手柄还有一个特点,就是可以直接和 DR9 的串口头对插!这样同开发板的连接就简单了。FC 手柄的外观如图 33.1.1 所示:图 33.1.1 FC 手柄外观图 这种手柄一般有 10 个按键(实际是 8 个键值) :上、下、左、右、Start、Select、A、B、A 连发、B 连发。这里的 A 和 A 连发是一个键值,而 B 和 B 连发也是一个键值,只是连发按键 当你一直按下的时候,会不停的发送(方便快速按键,比如发炮弹之类的功能) 。 FC 手柄的控制电路,由 1 个 8 位并入串出的移位寄存器(CD4021) ,外加一个时基集成 电路(NE555,用于连发)构成。不过现在的手柄,为了节约成本,直接就在 PCB 上做绑定 了,所以你拆开手柄,一般是看不到里面有四四方方的 IC,而只有一个黑色的小点,所有电路 都集成到这个里面了,但是他们的控制和读取方法还是一样的。 9 针手柄的读取时序和接线图如图 33.1.2 所示:www.openedv.com441 ALIENTEK 战舰STM32开发板图 33.1.2 FC 手柄读取时序和接线图 从上图可看出,读取手柄按键值的信息十分简单:先 Latch(锁存键值) ,然后就得到了第 一个按键值(A) ,之后在 Clock 的作用下,依次读取其他按键的键值,总共 8 个按键键值。 有了以上了解,我们就可以通过 STM32 的 IO 来驱动 FC 手柄了。33.2 硬件设计本实验采用 STM32 的 3 个普通 IO 连接 FC 手柄的 Clock、 Data 和 Latch 信号, 本章实验功 能简介:在主函数不停的查询手柄输入,一旦检测到输入信号,则在 LCD 模块上面显示键值和 对应的按键符号。同样我们也是用 LED0 来指示程序正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) TFTLCD 模块 3) FC 手柄接口(JOYPAD) 4) FC 手柄 前两个,在之前的实例已经介绍过了,FC 手柄属于外部器件。战舰 STM32 开发板板载了 一个 FC 手柄接口(就是一个 DR9 接头) ,该接口与 MCU 的连接原理图如 33.2.1 所示:www.openedv.com442 ALIENTEK 战舰STM32开发板图 33.2.1 FC 手柄接头与 STM32 的连接电路图 图中,JOY_PAD 就是用来连接 FC 手柄的,该接头采用标准的 DR9 座,战舰 STM32 开发 板上有 2 个 DR9 座,一个用来接 FC 手柄(有 JOY_PAD 字样,LCD 左上),另外一个用来接 RS232 串口(有 COM 字样,LCD 右上),这两个头千万不要接错!否则可能烧坏手柄或者烧 坏 STM32。 从上图我们知道,手柄的 CLK(Clock)、LAT(Latch)和 DAT(Data)分别连接在 STM32 的 PC12、PC8 和 PC9 上面,这里与 SDIO 部分信号线共用了,所以当使用 SDIO 的时候,就不 能使用 FC 手柄了。因为信号线都是直连的,所以我们在开发板上不需要做配置,只需要将 FC 手柄插入 JOY_PAD 插口即可。 开发板配套的手柄,见图 33.1.1。www.openedv.com443 ALIENTEK 战舰STM32开发板33.3 软件设计打开我们的游戏手柄实验工程,可以看到我们的工程中添加了 joypad.c 文件以及其头文件 joypad.h 文件。 打开 joypad.c 文件,代码如下: #include &joypad.h& //初始化手柄接口. void JOYPAD_Init(void) { GPIO_InitTypeDef GPIO_InitS RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//使能 PC 端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_SetBits(GPIOC,GPIO_Pin_8|GPIO_Pin_12); //初始化 GPIO GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;// GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_SetBits(GPIOC,GPIO_Pin_9); } //读取手柄按键值. //FC 手柄数据输出格式: //每给一个脉冲,输出一位数据,输出顺序: //A-&B-&SELECT-&START-&UP-&DOWN-&LEFT-&RIGHT. //总共 8 位,对于有 C 按钮的手柄,按下 C 其实就等于 A+B 同时按下. //按下是 0,松开是 1. //返回值: //[0]:右 [1]:左 [2]:下 [3]:上 [1]:Start [5]:Select [6]:B [7]:A u8 JOYPAD_Read(void) { u8 temp=0; u8 JOYPAD_LAT=1; //锁存当前状态 JOYPAD_LAT=0; for(t=0;t&8;t++) { temp&&=1; if(JOYPAD_DAT)temp|=0x01; //LOAD 之后,就得到第一个数据 JOYPAD_CLK=1; //每给一次脉冲,收到一个数据 //www.openedv.com444 ALIENTEK 战舰STM32开发板JOYPAD_CLK=0; } } 该部分代码仅 2 个函数,都比较简单,JOYPAD_Init 函数用于初始化 IO,即把 PC8、PC9 和 PC12 设置为正确的状态,以便同 FC 手柄通信。另外一个函数 JOYPAD_Read 就是按照图 33.1.2 所示的时序读取 FC 手柄,该函数的返回值就是手柄的状态。 接下来打开 joypad.h 可以看到该文件里代码主要是定义位带操作实现 IO 控制,当然,你 也可以跟 LE 试验一样通过库函数设置。 最后我们看看 main.c 主函数代码: const u8*JOYPAD_SYMBOL_TBL[8]= {&Right&,&Left&,&Down&,&Up&,&Start&,&Select&,&A&,&B&};//手柄按键符号定义 int main(void) { u8 u8 t=0,i=0; delay_init(); //延时函数初始化 NVIC_Configuration(); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级 uart_init(9600); //串口初始化为 9600 LED_Init(); //LED 端口初始化 LCD_Init(); JOYPAD_Init(); //手柄初始化 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,&WarShip STM32&); LCD_ShowString(60,70,200,16,16,&JOYPAD TEST&); LCD_ShowString(60,90,200,16,16,&ATOM@ALIENTEK&); LCD_ShowString(60,110,200,16,16,&&); LCD_ShowString(60,130,200,16,16,&KEYVAL:&); LCD_ShowString(60,150,200,16,16,&SYMBOL:&); POINT_COLOR=BLUE;//设置字体为红色 while(1) { key=JOYPAD_Read(); if(key!=0XFF) { LCD_ShowNum(116,130,key,3,16);//显示键值 for(i=0;i&8;i++) { if((key&(1&&i))==0) { LCD_Fill(60+56,150,60+56+48,150+16,WHITE);//清除之前的显示 LCD_ShowString(60+56,150,200,16,16,(u8*)JOYPAD_SYMBOL_TBL[i]);//显示符号www.openedv.com445 ALIENTEK 战舰STM32开发板} } } delay_ms(10); t++; if(t==20) { t=0; LED0=!LED0; } } } 此部分代码也比较简单, 初始化 JOYPAD 之后, 就一直扫描 FC 手柄 (通过 JOYPAD_Read 函数实现),然后只要接收到手柄的有效信号,就在 LCD 模块上面显示出来。 至此,我们的软件设计部分就结束了。33.4 下载验证在代码编译成功之后,我们通过下载代码到 ALIENTEK 战舰 STM32 开发板上,可以看到 LCD 显示如图 33.4.1 所示的内容:图 33.4.1 程序运行时 LCD 显示内容 此时我们按下 FC 手柄的按键, 则可以看到 LCD 上显示了对应按键的键值以及对应的符号。 如图 33.4.2 所示:www.openedv.com446 ALIENTEK 战舰STM32开发板图 33.4.2 解码成功www.openedv.com447 ALIENTEK 战舰STM32开发板第三十四章 三轴加速度传感器实验自从有了 Iphone,各种新技术的普及程度越来越快,人们喜欢的不再是摔不坏的诺基亚, 而是用户体验极佳的 Iphone。 本章,我们介绍一种当今智能手机普遍具有的传感器:加速度传感器。在手机上,这个功 能可以用来:自动切换横竖屏、玩游戏和切歌等。ALIENTEK 战舰 STM32 开发板自带了加速 度传感器:ADXL345。本章我们将使用 STM32 来驱动 ADXL345,读取 3 个方向的重力加速度 值,并转换为角度,显示在 TFTLCD 模块上。本章分为如下几个部分: 34.1 ADXL345 简介 34.2 硬件设计 34.3 软件设计 34.4 下载验证www.openedv.com448 ALIENTEK 战舰STM32开发板34.1 ADXL345 简介ADXL345 是 ADI 公司的一款 3 轴、数字输出的加速度传感器。ADXL345 是 ADI 公司推 出的基于 iMEMS 技术的 3 轴、数字输出加速度传感器。该加速度传感器的特点有: ? 分辨率高。最高 13 位分辨率。 ? 量程可变。具有+/-2g,+/-4g,+/-8g,+/-16g 可变的测量范围。 ? 灵敏度高。最高达 3.9mg/LSB,能测量不到 1.0°的倾斜角度变化。 ? 功耗低。40~145uA 的超低功耗,待机模式只有 0.1uA。 ? 尺寸小。整个 IC 尺寸只有 3mm*5mm*1mm,LGA 封装。 ADXL 支持标准的 I2C 或 SPI 数字接口,自带 32 级 FIFO 存储,并且内部有多种运动状态 检测和灵活的中断方式等特性。ADXL345 传感器的检测轴如图 34.1.1 所示:图 34.1.1 ADXL345 的三个检测轴 当 ADXL345 沿检测轴正向加速时,它对正加速度进行检测。在检测重力时用户需要注意, 当检测轴的方向与重力的方向相反时检测到的是正加速度。 33.1.2 所示为输出对重力的响应。 图图 34.1.2 ADXL345 输出对重力的响应www.openedv.com449 ALIENTEK 战舰STM32开发板图 34.1.2 列出了 ADXL345 在不同摆放方式时的输出,以便后续分析。接下来我们看看 ADXL345 的引脚图,如图 34.1.3 所示:图 34.1.3 ADXL345 引脚图 ADXL345 支持 SPI 和 IIC 两种通信方式, 为了节省 IO 口, 战舰 STM32 开发板采用的是 IIC 方式连接,官方推荐的 IIC 连接电路如图 34.1.4 所示:图 34.1.4 ADXL345 IIC 模式连接电路 从上图可看出,ADXL345 的连接十分简单,外围需要的器件也极少(就 2 个电容) ,如上 连接 (SDO/ALT ADDRESS 接地) 则 ADXL345 的地址为 0X53 , (不含最低位) 如果 SDO/ALT , ADDRESS 接高,那么 ADXL345 的地址将变为 0X1D(不含最低位) 。IIC 通信的时序我们在 之前已经介绍过(第二十七章,IIC 实验) ,这里就不再细说了。 最后,我们介绍一下 ADXL345 的初始化步骤。ADXL345 的初始化步骤如下: 1) 上电 2) 等待 1.1ms 3) 初始化命令序列 4) 结束 其中上电这个动作发生在开发板第一次上电的时候,在上电之后,等待 1.1ms 左右,就可 以开始发送初始化序列了,初始化序列一结束,ADXL345 就开始正常工作了。这里的初始化 序列,最简单的只需要配置 3 个寄存器,如表 34.1.1 所示:www.openedv.com450 ALIENTEK 战舰STM32开发板步骤 1 2 3 寄存器地址 0X31 0X2D 0X2E 寄存器名字 DATA_FORMAT POWER_CTL INT_ENABLE 寄存器值 0X0B 0X08 0X80 功能描述 ±16g,13 位模式 测量模式 使能 DATA_READY 中断表 34.1.1 ADXL345 最简单的初始化命令序列 发送以上序列给 ADXL345 以后,ADXL345 即开始正常工作。 ADXL345 我们就介绍到这里,详细的介绍,请参考 ADXL345 的数据手册。34.2 硬件设计本实验采用 STM32 的 3 个普通 IO 连接 ADXL345,本章实验功能简介:主函数不停的查 询 ADXL345 的转换结果,得到 x、y 和 z 三个方向的加速度值(读数值),然后将其转换为与 自然系坐标的角度,并将结果在 LCD 模块上显示出来。DS0 来指示程序正在运行,通过按下 WK_UP 按键,可以进行 ADXL345 的自动校准(DS1 用于提示正在校准)。 所要用到的硬件资源如下: 1) 指示灯 DS0、DS1 2) WK_UP 按键 3) TFTLCD 模块 4) ADXL345 前 3 个,在之前的实例已经介绍过了,这里我们仅介绍 ADXL345 与战舰 STM32 开发板的 连接。该接口与 MCU 的连接原理图如 34.2.1 所示:图 34.2.1 ADXL345 与 STM32 的连接电路图 从上图可以看出,ADXL345 通过三根线与 STM32 开发板连接,其中 IIC 总线时和 24C02 以及 RDA5820 共用,接在 PB10 和 PB11 上面。ADXL345 的两个中断输出,这里我们只用了 一个, 连接在 STM32 的 PF11 脚, 另外这里的地址线是接 3.3V, 所以 ADXL345 的地址是 0X1D, 转换为 0X3A 写入,0X3B 读取。www.openedv.com451 ALIENTEK 战舰STM32开发板34.3 软件设计打开我们的三轴加速度传感器实验工程可以看到,工程中添加了 adx1345.c 文件和 adx1345.h 头文件,传感器相关的初始化代码以及驱动代码都分布在这个文件中。同时,我们还 引入了 myiic.c 和头文件 myiic.h,因为传感器是通过 IIC 接口控制的。 打开 adxl345.c 文件,代码如下: #include &adxl345.h& #include &sys.h& #include &delay.h& #include &math.h& //初始化 ADXL345. //返回值:0,初始化成功;1,初始化失败. u8 ADXL345_Init(void) { IIC_Init(); //初始化 IIC 总线 if(ADXL345_RD_Reg(DEVICE_ID)==0XE5) //读取器件 ID { ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13 位全分辨率,输出数据右对齐,16g 量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为 100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); return 0; } return 1; } //写 ADXL345 寄存器 //addr:寄存器地址 //val:要写入的值 //返回值:无 void ADXL345_WR_Reg(u8 addr,u8 val) { IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 IIC_Wait_Ack(); IIC_Send_Byte(val); //发送值 IIC_Wait_Ack();www.openedv.com452 ALIENTEK 战舰STM32开发板IIC_Stop(); //产生一个停止条件 } //读 ADXL345 寄存器 //addr:寄存器地址 //返回值:读到的值 u8 ADXL345_RD_Reg(u8 addr) { u8 temp=0; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 temp=IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 temp=IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 temp=IIC_Wait_Ack(); temp=IIC_Read_Byte(0); //读取一个字节,不继续再读,发送 NAK IIC_Stop(); //产生一个停止条件 //返回读到的值 } //读取 ADXL 的平均值 //x,y,z:读取 10 次后取平均值 void ADXL345_RD_Avval(short *x,short *y,short *z) { short tx=0,ty=0,tz=0; u8 for(i=0;i&10;i++) { ADXL345_RD_XYZ(x,y,z); delay_ms(10); tx+=(short)*x; ty+=(short)*y; tz+=(short)*z; } *x=tx/10; *y=ty/10; *z=tz/10; } //自动校准 //xval,yval,zval:x,y,z 轴的校准值 void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval) { short tx,ty, u8 short offx=0,offy=0,offz=0; ADXL345_WR_Reg(POWER_CTL,0x00); //先进入休眠模式. delay_ms(100);www.openedv.com453 ALIENTEK 战舰STM32开发板ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13 位全分辨率,输出数据右对齐,16g 量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为 100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); delay_ms(12); for(i=0;i&10;i++) { ADXL345_RD_Avval(&tx,&ty,&tz); offx+= offy+= offz+= } offx/=10; offy/=10; offz/=10; *xval=-offx/4; *yval=-offy/4; *zval=-(offz-256)/4; ADXL345_WR_Reg(OFSX,*xval); ADXL345_WR_Reg(OFSY,*yval); ADXL345_WR_Reg(OFSZ,*zval); } //读取 3 个轴的数据 //x,y,z:读取到的数据 void ADXL345_RD_XYZ(short *x,short *y,short *z) { u8 buf[6],i; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(0x32); //发送寄存器地址(数据缓存的起始地址为 0X32) IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 IIC_Wait_Ack(); for(i=0;i&6;i++) { if(i==5)buf[i]=IIC_Read_Byte(0); //读取一个字节,不继续再读,发送 NACK else buf[i]=IIC_Read_Byte(1); //读取一个字节,继续读,发送 ACK } IIC_Stop(); //产生一个停止条件 *x=(short)(((u16)buf[1]&&8)+buf[0]); *y=(short)(((u16)buf[3]&&8)+buf[2]); *z=(short)(((u16)buf[5]&&8)+buf[4]); }`www.openedv.com454 ALIENTEK 战舰STM32开发板//读取 ADXL345 的数据 times 次,再取平均 //x,y,z:读到的数据 //times:读取多少次 void ADXL345_Read_Average(short *x,short *y,short *z,u8 times) { u8 short tx,ty, *x=0; *y=0; *z=0; if(times)//读取次数不为 0 { for(i=0;i&i++)//连续读取 times 次 { ADXL345_RD_XYZ(&tx,&ty,&tz); *x+= *y+= *z+= delay_ms(5); } *x/= *y/= *z/= } } //得到角度 //x,y,z:x,y,z 方向的重力加速度分量(不需要单位,直接数值即可) //dir:要获得的角度.0,与 Z 轴的角度;1,与 X 轴的角度;2,与 Y 轴的角度. //返回值:角度值.单位 0.1°. short ADXL345_Get_Angle(float x,float y,float z,u8 dir) { float temp,res=0; switch(dir) { case 0://与自然 Z 轴的角度 temp=sqrt((x*x+y*y))/z; res=atan(temp); case 1://与自然 X 轴的角度 temp=x/sqrt((y*y+z*z)); res=atan(temp); case 2://与自然 Y 轴的角度 temp=y/sqrt((x*x+z*z)); res=atan(temp); } return res*; }www.openedv.com455 ALIENTEK 战舰STM32开发板该部分代码总共有 8 个函数,这里我们仅介绍其中 4 个。首先是 ADXL345_Init 函数,该函 数用来初始化 ADXL345,和前面我们提到的步骤差不多,不过本章我们而是采用查询的方式来 读取数据的,所以在这里并没有开启中断。另外 3 个偏移寄存器,都默认设置为 0。 其次,我们介绍 ADXL345_RD_XYZ 函数,该函数用于从 ADXL345 读取数据,通过该函数可以 读取 ADXL345 的转换结果,得到三个轴的加速度值(仅是数值,并没有转换单位)。 接着,我们介绍 ADXL345_AUTO_Adjust 函数,该函数用于 ADXL345 的校准,ADXL345 有偏 移校准的功能,该功能的详细介绍请参考 ADXL345 数据手册的第 29 页,偏移校准部分。这里 我们就不细说了,如果不进行校准的话,ADXL345 的读数可能会有些偏差,通过校准,我们 可以讲这个偏差减少甚至消除。 最后,我们看看 ADXL345_Get_Angle 函数,该函数根据 ADXL345 的读值,转换为与自然 坐标系的角度。计算公式如下:其中 Ax,Ay,Az 分别代表从 ADXL345 读到的 X,Y,Z 方向的加速度值。通过该函数, 我们只需要知道三个方向的加速度值, 就可以将其转换为对应的弧度值, 再通过弧度角度转换, 就可以得到角度值了。 其他函数,我们就不介绍了,也比较简单。接下来打开 adxl345.h 可以看到里面主要是一 些宏定义标识符以及函数申明,这里就不多讲解了。 最后看看 main.c 里面代码如下: void Adxl_Show_Num(u16 x,u16 y,short num,u8 mode) { if(mode==0) //显示加速度值 { if(num&0) { LCD_ShowChar(x,y,'-',16,0); //显示负号 num=- //转为正数 }else LCD_ShowChar(x,y,' ',16,0); //去掉负号 LCD_ShowNum(x+8,y,num,4,16); //显示值 }else //显示角度值 { if(num&0) { LCD_ShowChar(x,y,'-',16,0); //显示负号www.openedv.com456 ALIENTEK 战舰STM32开发板num=- //转为正数 }else LCD_ShowChar(x,y,' ',16,0); //去掉负号 LCD_ShowNum(x+8,y,num/10,2,16); //显示整数部分 LCD_ShowChar(x+24,y,'.',16,0); //显示小数点 LCD_ShowNum(x+32,y,num%10,1,16); //显示小数部分 } } int main(void) { u8 u8 t=0; short x,y,z; short angx,angy, delay_init(); //延时函数初始化 NVIC_Configuration(); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级 uart_init(9600); //串口初始化为 9600 LED_Init(); //LED 端口初始化 LCD_Init(); KEY_Init(); POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,&WarShip STM32&); LCD_ShowString(60,70,200,16,16,&3D TEST&); LCD_ShowString(60,90,200,16,16,&ATOM@ALIENTEK&); LCD_ShowString(60,110,200,16,16,&&); LCD_ShowString(60,130,200,16,16,&KEY0:Auto Adjust&); while(ADXL345_Init()) //3D 加速度传感器初始化 { LCD_ShowString(60,150,200,16,16,&ADXL345 Error&); delay_ms(200); LCD_Fill(60,150,239,150+16,WHITE); delay_ms(200); } LCD_ShowString(60,150,200,16,16,&ADXL345 OK&); LCD_ShowString(60,170,200,16,16,&X VAL:&); LCD_ShowString(60,190,200,16,16,&Y VAL:&); LCD_ShowString(60,210,200,16,16,&Z VAL:&); LCD_ShowString(60,230,200,16,16,&X ANG:&); LCD_ShowString(60,250,200,16,16,&Y ANG:&); LCD_ShowString(60,270,200,16,16,&Z ANG:&); POINT_COLOR=BLUE;//设置字体为红色 while(1) {www.openedv.com457 ALIENTEK 战舰STM32开发板if(t%10==0)//每 100ms 读取一次 { //得到 X,Y,Z 轴的加速度值(原始值) ADXL345_Read_Average(&x,&y,&z,10); //读取 X,Y,Z 三个方向的加速度值 Adxl_Show_Num(60+48,170,x,0); //显示加速度原始值 Adxl_Show_Num(60+48,190,y,0); Adxl_Show_Num(60+48,210,z,0); //得到角度值,并显示 angx=ADXL345_Get_Angle(x,y,z,1); angy=ADXL345_Get_Angle(x,y,z,2); angz=ADXL345_Get_Angle(x,y,z,0); Adxl_Show_Num(60+48,230,angx,1); //显示角度值 Adxl_Show_Num(60+48,250,angy,1); Adxl_Show_Num(60+48,270,angz,1); } key=KEY_Scan(0); if(key==KEY_UP) { LED1=0;//绿灯亮,提示校准中 ADXL345_AUTO_Adjust((char*)&x,(char*)&y,(char*)&z);//自动校准 LED1=1;//绿灯灭,提示校准完成 } delay_ms(10); t++; if(t==20) { t=0; LED0=!LED0; } } } 此部分代码除了 main 函数,还有一个 Adxl_Show_Num 函数,该函数用于数据显示,因为在 lcd.c 里面,没有提供可以显示小数和负数的函数,所以我们这里编写了该函数,来实现小数 和负数的显示,以满足本章要求。 其他部分,我们就不多说了。至此,我们的软件设计部分就结束了。34.4 下载验证在代码编译成功之后,我们通过下载代码到 ALIENTEK 战舰 STM32 开发板上,可以看到 LCD 显示如图 34.4.1 所示的内容:www.openedv.com458 ALIENTEK 战舰STM32开发板图 34.4.1 程序运行时 LCD 显示内容 ,所以我们按下 WK_UP 键,进行 可以看到,X 方向和 Z 方向的角度有些大(最佳值是 0) 一次校准(注意校准的时候保持开发板水平,并且稳定) ,校准后如图 34.4.2 所示:www.openedv.com459 ALIENTEK 战舰STM32开发板图 34.4.2 校准后 可以看到,校准后,比未校准前好了很多,此时我们移动开发板到不同角度,可以看到 X、 Y、Z 的数值和角度也跟着变化。www.openedv.com460 ALIENTEK 战舰STM32开发板第三十五章DS18B20 数字温度传感器实验STM32 虽然内部自带了温度传感器, 但是因为芯片温升较大等问题, 与实际温度差别较大, 所以,本章我们将向大家介绍如何通过 STM32 来读取外部数字温度传感器的温度,来得到较 为准确的环境温度。在本章中,我们将学习使用单总线技术,通过它来实现 STM32 和外部温 度传感器(DS18B20)的通信,并把从温度传感器得到的温度显示在 TFTLCD 模块上。本章分 为如下几个部分: 35.1 DS18B20 简介 35.2 硬件设计 35.3 软件设计 35.4 下载验证www.openedv.com461 ALIENTEK 战舰STM32开发板35.1 DS18B20 简介DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线”接口的温度传感器。与传 统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的 数字化温度传感器。一线总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络, 从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃ ,精度为±0.5℃。现场温 度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度, 并且可根据实际要求通过简单的编程实现 9~l2 位的数字值读数方式。它工作在 3—5.5 V 的电 压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及用户设定的报警温度 存储在 EEPROM 中,掉电后依然保存。其内部结构如图 35.1.1 所示:图 35.1.1 DS18B20 内部结构图 ROM 中的 64 位序列号是出厂前被光记好的, 它可以看作是该 DS18B20 的地址序列码, 每 DS18B20 的 64 位序列号均不相同。64 位 ROM 的排列是:前 8 位是产品家族码,接着 48 位是 DS18B20 的序列号,最后 8 位是前面 56 位的循环冗余校验码(CRC=X8+X5 +X4 +1)。ROM 作 用是使每一个 DS18B20 都各不相同,这样就可实现一根总线上挂接多个。 所有的单总线器件要求采用严格的信号时序,以保证数据的完整性。DS18B20 共有 6 种信 号类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外, 都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。这里我们简单介绍 这几个信号的时序: 1)复位脉冲和应答脉冲 单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少 480 us, ,以产生复位脉冲。接着主机释放总线,4.7K 的上拉电阻将单总线拉高,延时 15~60 us, 并进入接收模式(Rx)。接着 DS18B20 拉低总线 60~240 us,以产生低电平应答脉冲, 若为低电平,再延时 480 us。 2)写时序 写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在 2 次独立的写时序之间 至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平, 延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线, 延时 2us。 3)读时序 单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,www.openedv.com462 ALIENTEK 战舰STM32开发板必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读 时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读 时序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。典型的读时序过程为: 主机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然 后延时 50us。 在了解了单总线时序之后,我们来看看 DS18B20 的典型温度读取过程,DS18B20 的典型 温度读取过程为:复位?发 SKIP ROM 命令(0XCC)?发开始转换命令(0X44)?延时?复 位?发送 SKIP ROM 命令(0XCC)?发读存储器命令(0XBE)?连续读出两个字节数据(即 温度)?结束。 DS18B20 的介绍就到这里,更详细的介绍,请大家参考 DS18B20 的技术手册。35.2 硬件设计由于开发板上标准配置是没有 DS18B20 这个传感器的,只有接口,所以要做本章的实验, 大家必须找一个 DS18B20 插在预留的 18B20 接口上。 本章实验功能简介:开机的时候先检测是否有 DS18B20 存在,如果没有,则提示错误。 只有在检测到 DS18B20 之后才开始读取温度并显示在 LCD 上,如果发现了 DS18B20,则程 序每隔 100ms 左右读取一次数据, 并把温度显示在 LCD 上。 同样我们也是用 DS0 来指示程序 正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 2) TFTLCD 模块 3) DS18B20 接口 4) DS18B20 温度传感器 前两部分,在之前的实例已经介绍过了,而DS18B20温度传感器属于外部器件(板上没有 直接焊接) 这里也不介绍。 , 本章, 我们仅介绍DS18B20接口和STM32的连接电路, 如图35.2.1 所示:图 35.2.1 DS18B20 接口与 STM32 的连接电路图 从上图可以看出, 我们使用的是 STM32 的 PG11 来连接 U13 的 DQ 引脚, 图中 U13 为 DHT11 (数字温湿度传感器)和 DS18B20 共用的一个接口,DHT11 我们将在下一章介绍。DS18B20 只用到其中的 3 个引脚(U13 的 1、2 和 3 脚),将 DS18B20 传感器插入到这个上面就可以通 过 STM32 来读取 DS18B20 的温度了。连接示意图如图 35.2.2 所示:www.openedv.com463 ALIENTEK 战舰STM32开发板图 35.2.2 DS18B20 连接示意图 从上图可以看出,DS18B20 的平面部分(有字的那面)应该朝内,而曲面部分朝外。然后 插入如图所示的三个孔内。35.3 软件设计打开我们的 DS18B20 数字温度传感器实验工程可以看到我们添加了 ds18b20.c 文件以及其 头文件 ds18b20.h 文件,所有 ds18b20 驱动代码和相关定义都分布在这两个文件中。 打开 ds18b20.c,该文件代码如下: #include &ds18b20.h& #include &delay.h& //复位 DS18B20 void DS18B20_Rst(void) { DS18B20_IO_OUT(); //SET PA0 OUTPUT DS18B20_DQ_OUT=0; //拉低 DQ delay_us(750); //拉低 750us DS18B20_DQ_OUT=1; //DQ=1 delay_us(15); //15US } //等待 DS18B20 的回应 //返回 1:未检测到 DS18B20 的存在 //返回 0:存在 u8 DS18B20_Check(void) { u8 retry=0; DS18B20_IO_IN();//SET PA0 INPUT while (DS18B20_DQ_IN&&retry&200) { retry++;www.openedv.com464 ALIENTEK 战舰STM32开发板delay_us(1); }; if(retry&=200)return 1; else retry=0; while (!DS18B20_DQ_IN&&retry&240) { retry++; delay_us(1); }; if(retry&=240)return 1; return 0; } //从 DS18B20 读取一个位 //返回值:1/0 u8 DS18B20_Read_Bit(void) // read one bit { u8 DS18B20_IO_OUT();//SET PA0 OUTPUT DS18B20_DQ_OUT=0; delay_us(2); DS18B20_DQ_OUT=1; DS18B20_IO_IN();//SET PA0 INPUT delay_us(12); if(DS18B20_DQ_IN)data=1; else data=0; delay_us(50); } //从 DS18B20 读取一个字节 //返回值:读到的数据 u8 DS18B20_Read_Byte(void) // read one byte { u8 i,j, dat=0; for (i=1;i&=8;i++) { j=DS18B20_Read_Bit(); dat=(j&&7)|(dat&&1); } } //写一个字节到 DS18B20 //dat:要写入的字节www.openedv.com465 ALIENTEK 战舰STM32开发板void DS18B20_Write_Byte(u8 dat) { u8 u8 DS18B20_IO_OUT();//SET PA0 OUTPUT; for (j=1;j&=8;j++) { testb=dat&0x01; dat=dat&&1; if (testb) { DS18B20_DQ_OUT=0;// Write 1 delay_us(2); DS18B20_DQ_OUT=1; delay_us(60); } else { DS18B20_DQ_OUT=0;// Write 0 delay_us(60); DS18B20_DQ_OUT=1; delay_us(2); } } } //开始温度转换 void DS18B20_Start(void)// ds1820 start convert { DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0x44);// convert } //初始化 DS18B20 的 IO 口 DQ 同时检测 DS 的存在 //返回 1:不存在 //返回 0:存在 u8 DS18B20_Init(void) { GPIO_InitTypeDef GPIO_InitS RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE); //使能 PG 口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PORTG.11 推挽输出www.openedv.com466 ALIENTEK 战舰STM32开发板GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化 GPIO GPIO_SetBits(GPIOG,GPIO_Pin_11); //输出 1 DS18B20_Rst(); return DS18B20_Check(); } //从 ds18b20 得到温度值 //精度:0.1C //返回值:温度值 (-550~1250) short DS18B20_Get_Temp(void) { u8 u8 TL,TH; DS18B20_Start (); // ds1820 start convert DS18B20_Rst(); DS18B20_Check(); DS18B20_Write_Byte(0xcc);// skip rom DS18B20_Write_Byte(0xbe);// convert TL=DS18B20_Read_Byte(); // LSB TH=DS18B20_Read_Byte(); // MSB if(TH&7) { TH=~TH; TL=~TL; temp=0;//温度为负 }else temp=1;//温度为正 tem=TH; //获得高八位 tem&&=8; tem+=TL;//获得底八位 tem=(float)tem*0.625;//转换 if(temp) //返回温度值 else return - } 该部分代码就是根据我们前面介绍的单总线操作时序来读取 DS18B20 的温度值的, DS18B20 的温度通过 DS18B20_Get_Temp 函数读取,该函数的返回值为带符号的短整形数据,返回值的 范围为-550~1250,其实就是温度值扩大了 10 倍。 然后我们打开 ds18b20.h,该文件下面主要是一些 IO 口位带操作定义以及函数申明,没有 什么特别需要讲解的地方。最后打开 main.c,该文件代码如下: int main(void) { u8 t=0;www.openedv.com467 ALIENTEK 战舰STM32开发板 delay_init(); //延时函数初始化 NVIC_Configuration(); //设置 NVIC 中断分组 2:2 位抢占优先级,2 位响应优先级 uart_init(9600); //串口初始化为 9600 LED_Init(); //LED 端口初始化 LCD_Init(); //LCD 初始化 KEY_Init(); //KEY 初始化 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,200,16,16,&WarShip STM32&); LCD_ShowString(60,70,200,16,16,&DS18B2}

我要回帖

更多关于 51单片机引脚功能 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信