开发板资源
◆ CPU:STM32F103ZET6,LQFP144,FLASH:512K,SRAM:64K; ◆ 外扩 SPI FLASH:W25Q128,16M 字节
◆ 1 个电源指示灯(蓝色)
◆ 2 个状态指示灯(DS0:红色,DS1:绿色)
◆ 1 个红外接收头,并配备一款小巧的红外遥控器
◆ 1 个 EEPROM 芯片,24C02,容量 256 字节
◆ 1 个光敏传感器
◆ 1 个无线模块接口(可接 NRF24L01/RFID 模块等)
◆ 1 路 CAN 接口,采用 TJA1050 芯片
◆ 1 路 485 接口,采用 SP3485 芯片
◆ 1 路数字温湿度传感器接口,支持 DS18B20 /DHT11 等 ◆ 1 个 ATK 模块接口,支持 ALIENTEK 蓝牙/GPS 模块/MPU6050 模块等
◆ 1 个标准的 2.4/2.8/3.5/4.3/7 寸 LCD 接口,支持触摸屏
◆ 1 个摄像头模块接口
◆ 1 个 OLED 模块接口(与摄像头接口共用)
◆ 1 个 USB 串口,可用于程序下载和代码调试(USMART 调试)
◆ 1 个 USB SLAVE 接口,用于 USB 通信
◆ 1 个有源蜂鸣器
◆ 1 个 RS485 选择接口
◆ 1 个 CAN/USB 选择接口
◆ 1 个串口选择接口
◆ 1 个 SD 卡接口(在板子背面,SDIO 接口)
◆ 1 个标准的 JTAG/SWD 调试下载口
◆ 1 组 AD/DA 组合接口(DAC/ADC/ TPAD) ◆ 1 组 5V 电源供应/接入口
◆ 1 组 3.3V 电源供应/接入口
◆ 1 个直流电源输入接口(输入电压范围:6~24V) ◆ 1 个启动模式选择配置接口
◆ 1 个 RTC 后备电池座,并带电池
◆ 1 个复位按钮,可用于复位 MCU 和 LCD
◆ 3 个功能按钮,其中 KEY_UP 兼具唤醒功能
◆ 1 个电容触摸按键
◆ 1 个电源开关,控制整个板的电源
◆ 独创的一键下载功能
◆ 除晶振占用的 IO 口外,其余所有 IO 口全部引出
1. WIRELESS 模块接口
这是开发板板载的无线模块接口(U2),可以外接 NRF24L01/RFID 等无线模块。从而实现
无线通信等功能。注意:接 NRF24L01 模块进行无线通信的时候,必须同时有 2 个模块和 2 个
板子,才可以测试,单个模块/板子例程是不能测试的。
2. W25Q128 128Mbit FLASH
这是开发板外扩的 SPI FLASH 芯片(U8),容量为 128Mbit,也就是 16M 字节,可用于存
储字库和其他用户数据,满足大容量数据存储要求。当然如果觉得 16M 字节还不够用,你可以
把数据存放在外部 SD 卡。
3. SD卡接口
这是开发板板载的一个标准 SD 卡接口(SD_CARD),该接口在开发板的背面,采用大 SD
卡接口(即相机卡,也可以是 TF 卡+卡套的形式),SDIO 方式驱动,有了这个 SD 卡接口,就
可以满足海量数据存储的需求。
4. CAN/USB 选择口
这是一个 CAN/USB 的选择接口(P6),因为 STM32 的 USB 和 CAN 是共用一组 IO(PA11
和 PA12),所以我们通过跳线帽来选择不同的功能,以实现 USB/CAN 的实验。
5.USB 串口/串口 1
这是 USB 串口同 STM32F103ZET6 的串口 1 进行连接的接口(P3),标号 RXD 和 TXD 是
USB 转串口的 2 个数据口(对 CH340G 来说),而 PA9(TXD)和 PA10(RXD)则是 STM32 的串口
1 的两个数据口(复用功能下)。他们通过跳线帽对接,就可以和连接在一起了,从而实现 STM32
的程序下载以及串口通信。
设计成 USB 串口,是出于现在电脑上串口正在消失,尤其是笔记本,几乎清一色的没有串
口。所以板载了 USB 串口可以方便大家下载代码和调试。而在板子上并没有直接连接在一起,
则是出于使用方便的考虑。这样设计,你可以把 ALIENTEK 精英 STM32F103 当成一个 USB
转 TTL 串口,来和其他板子通信,而其他板子的串口,也可以方便地接到 ALIENTEK 精英
STM32F103 开发板上。
6. JTAG/SWD 接口
这是 ALIENTEK 精英 STM32F103 板载的 20 针标准 JTAG 调试口(JTAG),该 JTAG 口直
接可以和 ULINK、JLINK 或者 STLINK 等调试器(仿真器)连接,同时由于 STM32 支持 SWD
调试,这个 JTAG 口也可以用 SWD 模式来连接。
用标准的 JTAG 调试,需要占用 5 个 IO 口,有些时候,可能造成 IO 口不够用,而用 SWD
则只需要 2 个 IO 口,大大节约了 IO 数量,但他们达到的效果是一样的,所以我们强烈建议仿
真器使用 SWD 模式!
7. 24C02 EEPROM
这是开发板板载的 EEPROM 芯片(U9),容量为 2Kb,也就是 256 字节。用于存储一些掉
电不能丢失的重要数据,比如系统设置的一些参数/触摸屏校准数据等。有了这个就可以方便的
实现掉电数据保存。
8. USB SLAVE
这是开发板板载的一个 MiniUSB 头(USB_SLAVE),用于 USB 从机(SLAVE)通信,一
般用于 STM32 与电脑的 USB 通信。通过此 MiniUSB 头,开发板就可以和电脑进行 USB 通信
了。
开发板总共板载了 2 个 MiniUSB 头,一个(USB_232)用于 USB 转串口,连接 CH340G
芯片;另外一个(USB_SLAVE)用于 STM32 内带的 USB。同时开发板可以通过此 MiniUSB
头供电,板载两个 MiniUSB 头(不共用),主要是考虑了使用的方便性,以及可以给板子提供
更大的电流(两个 USB 都接上)这两个因素。
9. USB 转串口
这是开发板板载的另外一个 MiniUSB 头(USB_232),用于 USB 连接 CH340G 芯片,从而
实现 USB 转 TTL 串口。同时,此 MiniUSB 接头也是开发板电源的主要提供口。
10. 后备电池接口
这是 STM32 后备区域的供电接口(BAT),可安装 CR1220 电池(默认安装了),可以用来给
STM32 的后备区域提供能量,在外部电源断电的时候,维持后备区域数据的存储,以及 RTC
的运行。
11. OLED/摄像头模块接口
这是开发板板载的一个 OLED/摄像头模块接口(P4),如果是 OLED 模块,靠左插即可(右
边两个孔位悬空)。如果是摄像头模块(ALIENTEK 提供),则刚好插满。通过这个接口,可以
分别连接 2 种外部模块,从而实现相关实验。
12. 有源蜂鸣器
这是开发板的板载蜂鸣器(BEEP),可以实现简单的报警/闹铃等功能。
13. 红外接收器
这是开发板的红外接收头(U6),可以实现红外遥控功能,通过这个接收头,可以接受市
面常见的各种遥控器的红外信号,大家甚至可以自己实现万能红外解码。当然,如果应用得当,
该接收头也可以用来传输数据。
14. DS18B20/DHT11 接口
这是开发板的一个复用接口(U4),该接口由 4 个镀金排孔组成,可以用来接
DS18B20/DS1820 等数字温度传感器。也可以用来接 DHT11 这样的数字温湿度传感器。实现一
个接口,2 个功能。不用的时候,大家可以拆下上面的传感器,放到其他地方去用,使用上是
十分方便灵活的。
15. 2 个 LED
这是开发板板载的两个 LED 灯(DS0 和 DS1),DS0 是红色的,DS1 是绿色的,主要是方
便大家识别。这里提醒大家不要停留在 51 跑马灯的思维,搞这么多灯,除了浪费 IO 口,实在
是想不出其他什么优点。
我们一般的应用 2 个 LED 足够了,在调试代码的时候,使用 LED 来指示程序状态,是非
常不错的一个辅助调试方法。精英 STM32F103 几乎每个实例都使用了 LED 来指示程序的运行
状态。
16. 启动选择端口
这是开发板板载的启动模式选择端口(BOOT),STM32 有 BOOT0(B0)和 BOOT1(B1)
两个启动选择引脚,用于选择复位后 STM32 的启动模式,作为开发板,这两个是必须的。在
开发板上,我们通过跳线帽选择 STM32 的启动模式。
17. 触摸按钮
这是开发板板载的一个电容触摸输入按键(TPAD),利用电容充放电原理,实现触摸按键
检测。
18. 电源指示灯
这是开发板板载的一颗蓝色的 LED 灯(PWR),用于指示电源状态。在电源开启的时候(通
过板上的电源开关控制),该灯会亮,否则不亮。通过这个 LED,可以判断开发板的上电情况。
19. 复位按钮
这是开发板板载的复位按键(RESET),用于复位 STM32,还具有复位液晶的功能,因为
液晶模块的复位引脚和 STM32 的复位引脚是连接在一起的,当按下该键的时候,STM32 和液
晶一并被复位。
20. 3个按键
这是开发板板载的 3 个机械式输入按键(KEY0、KEY1 和 KEY_UP),其中 KEY_UP 具有
唤醒功能,该按键连接到 STM32 的 WAKE_UP(PA0)引脚,可用于待机模式下的唤醒,在不
使用唤醒功能的时候,也可以做为普通按键输入使用。
其他 2 个是普通按键,可以用于人机交互的输入,这 2 个按键是直接连接在 STM32 的 IO
口上的。这里注意 KEY_UP 是高电平有效,而 KEY0 和 KEY1 是低电平有效,大家在使用的时
候留意一下。
21. STM32F103ZET6
这是开发板的核心芯片(U1),型号为:STM32F103ZET6。该芯片具有 64KB SRAM、512KB
FLASH、2 个基本定时器、4 个通用定时器、2 个高级定时器、2 个 DMA 控制器(共 12 个通道)、3 个 SPI、2 个 IIC、5 个串口、1 个 USB、1 个 CAN、3 个 12 位 ADC、1 个 12 位 DAC、1 个SDIO 接口、1 个 FSMC 接口以及 112 个通用 IO 口。
22. AD/DA 组合接口
这是 1 个由 4 个排针组成的一个组合接口(P7)。可以实现 AD 采集、DA 输出和板载电容
触摸按键(TPAD)检测的功能。
23. ATK 模块接口
这是开发板板载的一个 ALIENTEK 通用模块接口(U3),目前可以支持 ALIENTEK 开发
的 GPS 模块、蓝牙模块和 MPU6050 模块等,直接插上对应的模块,就可以进行开发。后续我
们将开发更多兼容该接口的其他模块,实现更强大的扩展性能。
24. 3.3V 电源输入/输出
这是开发板板载的一组 3.3V 电源输入输出排针(2*3)(VOUT1),用于给外部提供 3.3V
的电源,也可以用于从外部接 3.3V 的电源给板子供电。
大家在实验的时候可能经常会为没有 3.3V 电源而苦恼不已,有了 ALIENTEK 精英
STM32F103,你就可以很方便的拥有一个简单的 3.3V 电源(USB 供电的时候,最大电流不能
超过 500mA,外部供电的时候,最大可达 1000mA)
25. 5V 电源输入/输出
这是开发板板载的一组 5V 电源输入输出排针(2*3)(VOUT2),该排针用于给外部提供
5V 的电源,也可以用于从外部接 5V 的电源给板子供电。
同样大家在实验的时候可能经常会为没有 5V 电源而苦恼不已,ALIENTEK 充分考虑到了
大家需求,有了这组 5V 排针,你就可以很方便的拥有一个简单的 5V 电源(USB 供电的时候,
最大电流不能超过 500mA,外部供电的时候,最大可达 1000mA)。
26. 电源开关
这是开发板板载的电源开关(K1)。该开关用于控制整个开发板的供电,如果切断,则整
个开发板都将断电,电源指示灯(PWR)会随着此开关的状态而亮灭。
27. DC6~24V 电源输入
这是开发板板载的一个外部电源输入口(DC_IN),采用标准的直流电源插座。开发板板载
了 DC-DC 芯片(MP2359),用于给开发板提供高效、稳定的 5V 电源。由于采用了 DC-DC 芯
片,所以开发板的供电范围十分宽,大家可以很方便的找到合适的电源(只要输出范围在
DC 6~24V的基本都可以)来给开发板供电。在耗电比较大的情况下,比如用到 4.3 屏/7 寸屏的
时候,建议使用外部电源供电,可以提供足够的电流给开发板使用.
28. RS485 选择接口
这是开发板板载的 RS485 选择接口(P5),MAX3485 通过这个接口来决定是否连接到
STM32 的串口 2(USART2),当这里断开的时候:串口 2 可以用来做普通串口使用,而 RS485
则可以用来实现 RS485 转 TTL 的功能;当这里接上时:串口 2 连接 MAX3485,就可以实现
RS485 通信。
29. 引出 IO 口(共 2 组)
这是开发板 IO 引出端口,总共有 2 组主 IO 引出口:P1 和 P2。它们采用 2*27 排针引出,
总共引出 106 个 IO 口。而 STM32F103ZET6 总共只有 112 个 IO,除去 RTC 晶振占用的 2 个 IO
,还剩下 110 个,这 2 组排针,总共引出 106 个 IO,剩下的 4 个 IO 分别通过:P3 和 P5 引出。
30. LCD 接口
这是开发板板载的 LCD 模块接口,该接口兼容 ALIENTEK 全系列 TFTLCD 模块,包括:
2.4 寸、2.8 寸、3.5 寸、4.3 寸和 7 寸等 TFTLCD 模块,并且支持电阻/电容触摸功能。
31. 光敏传感器
这是开发板板载的一个光敏传感器(LS1),通过该传感器,开发板可以感知周围环境光线
的变化,从而可以实现类似自动背光控制的应用。
32. RS485 接口
这是开发板板载的 RS485 总线接口(RS485),通过 2 个端口和外部 485 设备连接。这里提
醒大家,RS485 通信的时候,必须 A 接 A,B 接 B。否则可能通信不正常!另外,开发板自带
了终端电阻(120Ω)。
33. CAN 接口
这是开发板板载的 CAN 总线接口(CAN),通过 2 个端口和外部 CAN 总线连接,即 CANH
和 CANL。这里提醒大家:CAN 通信的时候,必须 CANH 接 CANH,CANL 接 CANL,否则
可能通信不正常!
STM32 初探
1. 为什么选择STM32
·STM32 是基于ARM内核的32位的 Cortex-M内核
·具有标准的ARM框架
·好性能、低功耗、低电压、创新的内核及外设
·简单易用、低风险
Cortex-M3内核属于ARMv7架构
ARMv7架构定义了三大分工明确的系列:
"A"系列:面向尖端的基于虚拟内存的操作系统和用户应用
"R"系列:针对实时系统
"M”系列:对微控制器
STM32F1属于Cortex-M系列中的Cortex-M3内核,采用ARMv7-M架构。
STM32F4属于Cortex-M4系列采用ARMv7-ME架构。
Cortex-A5/A8采用ARMv7-A架构。
传统的ARM7系列采用的是ARMv4T架构。
详细对比见PPT的表格
总之Cortex-M3比ARM7好多了
3. M3系列的优点
1. 高性能Cortex-M内核
采用ARM公司流行的标准内核Cortex-M3
低动态功耗上实现的高性能
哈佛结构上实现1.25DMIPS/MHZ,功耗只有0.19mv/MHZ,比ARM7TDMI改进了30%
单周期的乘法和硬件除法
不可分的位操作,实现对RAM,I/O和寄存器的最优访问。
2. 最佳的代码密度
Thumb-2指令集以16位指令的密度实现32位指令性能(与ARM7TDMI的ARM模式比减少了30%-45%的代码量)
3. 可预见的运行时间
中断控制器嵌在内核之中,中断之间的间隔最少可达6个CPU周期。
从低功耗模式唤醒只需6个CPU周期
4.改进的调试功能
串行单步调试和JTAG调试
2. STM32的命名规则
见PPT
3. STM32的优势总结
1)极高的性能: 主流的Cortex内核。
2)丰富合理的外设,合理的功耗,合理的价格。
3)强大的软件支持:丰富的软件包。
4)全面丰富的技术文档。
5)芯片型号种类多,覆盖面广。
6)强大的用户基础:最先成功试水CM3芯片的公司,积累了大批的用户群体,为其领先做铺垫。
STM32 芯片解读
STM32F1数据手册:STM32F103ZET6.pdf
STM32F1中文手册:STM32F1xx中文参考手册.pdf
开发板原理图:XXX STM32F1_Vxx_SCH.pdf
1.芯片有哪些资源?
STM32F103ZET6
内核:
32位 高性能ARM Cortex-M3处理器
时钟:高达72M,实际还可以超频一点点
单周期乘法和硬件除法
IO口:
STM32F103ZET6: 144引脚 112个IO(16×7可以分为7组,PA~PG)
大部分IO口都耐5V(模拟通道除外)
支持调试:SWD和JTAG,SWD只要2根数据线
存储器容量:
STM32F103ZET6: 512K FLASH,64K SRAM
时钟,复位和电源管理:
①:2.0~3.6V电源和IO电压(一般用3.3V)
②:上电复位,掉电复位和可编程的电压监控
③:强大的时钟系统:
4~16M的外部高速晶振(一般用8M)
内部8MHz的高速RC振荡器
内部40KHz低速RC振荡器,看门狗时钟
内部锁相环(PLL,倍频),一般系统时钟都是外部或者内部高速时钟经过PLL倍频后得到(8M不够就倍频)
外部低速32.768K的晶振,主要做RTC时钟源
低功耗:
睡眠,停止和待机三种低功耗模式
可用电池为RTC和备份寄存器供电
AD:
3个12位AD【多达21个外部测量通道】
转换范围:0~3.6(电源电压)(一般用3.3V)
内部通道可以用于内部温度测量
内置参考电压
DA:
2个12位DA
DMA(直接存储器访问):
12个DMA通道(7+5=12; 7通道DMA1,5通道DMA2)支持外设:定时器,ADC,DAC,SDIO,I2S,SPI,I2C,和USART
定时器(11个):
4个通用定时器
2个基本定时器
2个高级定时器
1个系统定时器
2个看门狗定时器
通信接口(13个):
2个I2C接口
5个串口
3个SPI接口
1个CAN2.0
1个USB FS
1个SDIO
2. 芯片的内部结构
看PPT
3. STM32的最小系统
供电(VDD VSS VDDA VSSA都是3.3V VDDA和VSSA都是模拟的,对纹波的处理要求较高)
复位(一般是一个按键一个电容,按键没按的时候通过上拉电阻,保持高电压3.3V,有按键按下的时候接地变为0V,可以通过电容充电)
时钟:外部晶振(2个)(有两个晶振;一个是外部高速晶振,另一个是低速晶振)
Boot启动模式选择 (BOOT1和BOOT2两个引脚通过一个段子连接在一起,跳线帽,通过一键下载电路可以跳过这个步骤)
下载电路(串口(串口1,通过PA9和PA10接到USB转232的口再下载)/JTAG/SWD)
后备电池
其他地方还有一些滤波电容,一般选104
开发环境搭建
MDK安装
MDK是什么?
RealView MDK是Keil公司开发的,为基于Cortex 、ARM7、ARM9等处理器设备提供的一个完整的开发环境。
参考资料:
ALIENTEK xxSTM32开发板入门资料\MDK5安装手册.pdf
USB串口作用:
(1)可以当串口使用。
(2)如果USB串口连接到STM32的串口1(STM32ISP下载只能是串口1)的话,那么可以用来串口下载程序。
(3)因为要连接到USB,所以可以用来USB供电。
USB串口驱动安装
USB串口驱动芯片型号:CH340
串口下载工具:mcuisp(FlyMcu)
STM32的ISP下载
只能使用串口1,也就是对应串口发送接收引脚PA9,PA10。不能使用其他串口(例如串口2:PA2,PA3)用来ISP下载。
GPIO基本原理与寄存器配置
GPIO基本结构和工作方式
STM32F103ZET6
- 一共有7组IO口
- 每组IO口有16个IO
- 一共16X7=112个IO
- 分别是GPIOA GPIOB GPIOC GPIOD GPIOE GPIOF GPIOG
- GPIOA有 PA0 PA1 PA2 PA3 PA4 PA5 PA6 PA7 PA8 PA9 PA10 PA11 PA12 PA13 PA14 PA15
STM32的大部分引脚除了当GPIO使用外,还可以复用为外设功能引脚(比如串口)。
GPIO的工作方式:
4种输入模式:
输入浮空
输入上拉
输入下拉
模拟输入
4种输出模式:
开漏输出:只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)
开漏复用功能
推挽式输出:可以输出强高低电平,连接数字器件
推挽式复用功能
3种最大翻转速度:
-2MHZ
-10MHz
-50MHz
每组GPIO端口的寄存器
包括:
两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH) ,
两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),
一个32位置位/ 复位寄存器(GPIOx_BSRR),
一个16位复位寄存器(GPIOx_BRR),
一个32位锁定寄存器(GPIOx_LCKR)。
每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问) 。
每组IO口含下面7个寄存器。也就是7个寄存器,一共可以控制一组GPIO的16个IO口。
- GPIOx_CRL :端口配置低寄存器
- GPIOx_CRH:端口配置高寄存器
- GPIOx_IDR:端口输入寄存器
- GPIOx_ODR:端口输出寄存器
- GPIOx_BSRR:端口位设置/清除寄存器
- GPIOx_BRR :端口位清除寄存器
- GPIOx_LCKR:端口配置锁存寄存器
端口复用功能
STM32的大部分端口都具有复用功能。
所谓复用,就是一些端口不仅仅可以做为通用IO口,还可以复用为一
些外设引脚,比如PA9,PA10可以复用为STM32的串口1引脚。
作用:最大限度的利用端口资源
端口重映射功能
就是可以把某些功能引脚映射到其他引脚。
比如串口1默认引脚是PA9,PA10可以通过配置重映射映
射到PB6,PB7
作用:方便布线
跑马灯程序
硬件连接及GPIO库函数说明
LED0接PB5
LED1接PE5
GPIO采用推挽输出的方式(可以输出高低电平)
头文件:stm32f10x_gpio.h
源文件:stm32f10x_gpio.c
要使用GPIO的话
stm32f10x_gpio.c
stm32f10x_rcc.c
misc.c
是必要的文件
初始化函数:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
第一个参数 GPIO_TypeDef* GPIOx 表示选择第几个GPIO
第二个参数 GPIO_InitTypeDef* GPIO_InitStruct 中的GPIO_InitTypeDef结构体中的成员有:
GPIO_Pin用来指定IO口
GPIO_Speed用来指定速度
GPIO_Mode用来指定模式
GPIO_Init函数初始化样例:
1 | GPIO_InitTypeDef GPIO_InitStructure; //定义结构体 |
2个读取输入电平函数:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:读取某个GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
例如:
1 | GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平 |
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
作用:读取某组GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
例如:
1 | GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平 |
2个读取输出电平函数:
uint8_t GPIO_ReadOutputDataBit (GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:读取某个GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
例如:
1 | GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输出电平 |
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
作用:读取某组GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
例如:
1 | GPIO_ReadOutputData(GPIOA);//读取GPIOA组中所有io口输出电平 |
4个设置输出电平函数:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为高电平(1)。实际操作BSRR寄存器
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为低电平(0)。实际操作的BRR寄存器。
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
这两个函数不常用,也是用来设置IO口输出电平。
程序
步骤:
使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
不同的IO组,调用的时钟使能函数不一样。
初始化IO口模式。调用函数GPIO_Init();
操作IO口,输出高低电平。
GPIO_SetBits();
GPIO_ResetBits();
1.在template模板中新建HARDWARE(硬件)分组;
2.在相应的目录中新建HARDWARE文件夹;
3.在HARDWARE文件夹中再新建LED等外设文件夹
4.每个外设文件夹中建立一个对应的.h和.c的文件
5.在.h中预编译防止重新引用
1 | #ifndef __LED_H |
6.在.c(源文件)中定义函数
1 | #include "led.h" |
7.按步骤调用函数RCC_APB2PeriphColckCmd();
写入
1 | #include "led.h" |
后右键RCC_APB2PeriphClockCmd();点击go to definition
找到定义
1 | void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) |
第一个参数RCC_APB2Periph 第二个参数NewState
右键IS_RCC_APB2_PERIPH点击go to definition查看有效值
找到
1 | #define RCC_APB2Periph_AFIO ((uint32_t)0x00000001) |
即可查看有效值
我们所使用的是GPIOB和GPIOE,所以
RCC_APB2PeriphColckCmd()的第一个输入参数是RCC_APB2Periph_GPIOB
同理右键IS_FUNCTIONAL_STATE点击go to definition查看有效值
找到
1 | #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE)) |
发现有效值是DISABLE或者ENABLE
我们选择ENABLE,所以
RCC_APB2PeriphColckCmd()的第一个输入参数是ENABLE
此时的led.h内容为
1 | #include "led.h" |
编译后有两个error显示RCC_APB2Periph_GPIOB和ENABLE未定义
我们引用固件头文件stm32f10x.h后就OK了
此时的led.h内容为
1 | #include "led.h" |
此时GPIOB的时钟就已经启动了;
8.初始化IO口模式。调用函数GPIO_Init();
复制GPIO_Init()到led.c中;右键go to definition查看具体参数
或者到gpio.h中找声明
声明中
1 | void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); |
有两个个参数分别是GPIOx和GPIO_InitStruct
GPIOx此时是GPIOB和GPIOE
GPIO_InitStruct是一个GPIO_InitTypeDef类型的结构体;
所以我们在前面先定义这个结构体
1 | GPIO_InitTypeDef GPIO_InitStruct; |
定义完后注意赋值,发现结构体有
GPIO_InitStruct.GPIO_Mode
GPIO_InitStruct.GPIO_Pin
GPIO_InitStruct.GPIO_Speed
三个变量;通过上面的方法找到有效值如下:
1 | GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; |
注意在引用的时候要加&符号
即
1 | GPIO_Init(GPIOB,&GPIO_InitStruct); |
同理可设置GPIOB的值,这一步完成后的led.c代码为:
1 | #include "led.h" |
9.操作IO口,输出高低电平
一般默认初始化之后灯先不点亮,所以需要设置为高电平,需要用到
GPIO_SetBits();
GPIO_ResetBits();
1 | GPIO_SetBits(GPIOB,GPIO_Pin_5); |
此时led.c的内容为:
1 | #include "led.h" |
从主函数开始写程序
1 | #include"stm32f10x.h" |
蜂鸣器实验
蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、
复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣
器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
这里的有源不是指电源的“源”,而是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路,
一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供 2~5Khz 左右的方波驱动,
才能发声。
我们用到一个 NPN 三极管(S8050)来驱动蜂鸣器,R33 主要用于防止蜂鸣器的误发
声。当 PB.8 输出高电平的时候,蜂鸣器将发声,当 PB.8 输出低电平的时候,蜂鸣器停止发声。
电流从三极管基极进入放大后才能驱动蜂鸣器。由于STM32单片机复位后BEEP位是浮空的,即该IO口的状态是不确定的,所以我们需要接一个R38(10KΩ)再接地,把小电流引到地,这样可以防止误发声。
实验步骤
使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();
不同的IO组,调用的时钟使能函数不一样。
初始化IO口模式。调用函数BEEP_Init();
操作IO口,输出高低电平。
按键输入实验
按键的硬件连接
有三个按键 WK UP、KEY_0、KEY_1
WK UP接PA0 另一端接Vcc
KEY_0接PE4 另一端接GND
KEY_1接PE3 另一端接GND
按键输入操作说明
读取IO口输入电平调用库函数为:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
读取IO口输入电平操作寄存器为
GPIOx_IDR:端口输入寄存器
使用位带操作读取IO口输入电平:
PEin(4) -读取GPIOE.4口电平
PEin(n) -读取GPIOE.n口电平
按键输入实验步骤
使能按键对应IO口时钟。调用函数:
RCC_APB2PeriphClockCmd();
初始化IO模式:上拉/下拉输入。调用函数:
GPIO_Init();
扫描IO口电平(库函数/寄存器/位操作)
C语言关键字 :static
Static申明的局部变量,存储在静态存储区。
它在函数调用结束之后,不会被释放。它的值会一直保留下来。
所以可以说static申明的局部变量,具有记忆功能。
比如
1 | int getValue(void) |
和
1 | int getValue(void) |
按键扫描(支持连续按)的一般思路
如果我要实现:按键按下,没有松开,只能算按下一次,这个函数无法实现。
1 | u8 KEY_Scan(void) |
按键扫描(不支持连续按)的一般思路
不支持连续按:就是说,按键按下了,没有松开,只能算一次。
1 | u8 KEY_Scan(void) |
按键扫描(两种模式合二为一)的一般思路
多了一个入口参数mode;mode==1表示支持连续按 mode==0表示不支持连续按
1 | u8 KEY_Scan(u8 mode) |
C语言复习和MDK中寄存器地址名称映射分析
位操作:6种位操作运算符
运算符 | 含义 | 运算符 | 含义 |
---|---|---|---|
& | 按位与 | ~ | 取反 |
| | 按位或 | << | 左移 |
^ | 按位异或 | >> | 右移 |
GPIOA->CRL&=0XFFFFFF0F; //将第4-7位清0
GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值
GPIOA->ODR|=1<<5;
TIMx->SR = (uint16_t)~TIM_FLAG;
左移和右移都是补0
define宏定义关键词
define是C语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。
常见的格式:
#define 标识符 字符串
“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
例如:
#define SYSCLK_FREQ_72MHz 72000000
定义标识符SYSCLK_FREQ_72MHz的值为72000000。
ifdef条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:
1 | #ifdef 标识符 |
例如:
1 | #ifdef STM32F10X_HD |
extern变量申明
Main.c文件
1 | u8 id;//定义只允许一次 |
test.c文件
1 | extern u8 id;//此处声明id这个变量,不是定义 |
typedef类型别名
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __int64 uint64_t;
结构体:构造类型
Struct 结构体名{
成员列表1;
成员变量2;
…
}变量名列表;
在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:
Struct 结构体名字 结构体变量列表 ;
结构体作用:
同一个类型可以用数组,不同类型可以用结构体组织。
结构体可扩展性强。
举例说明:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
STM32中操作:
GPIOA->ODR=0x00000000;
值0x00000000是怎么赋值给了GPIOA的ODR寄存器地址的呢?
也就是说GPIOA->ODR这种写法,是怎么与GPIOA的ODR寄存器地址映射起来的?
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
时钟系统
几个重要的时钟:
SYSCLK(系统时钟) :
AHB总线时钟
APB1总线时钟(低速): 速度最高36MHz
APB2总线时钟(高速): 速度最高72MHz
PLL时钟
参考资料:
http://www.openedv.com/posts/list/302.htm
STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz16MHz。③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。16倍,但是其输出频率最大不得超过72MHz。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2系统时钟SYSCLK可来源于三个时钟源:
①、HSI振荡器时钟
②、HSE振荡器时钟
③、PLL时钟STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL
输出的2分频、HSI、HSE、或者系统时钟。任何一个外设在使用之前,必须首先使能其相应的时钟。
系统时钟初始化函数:
SystemInit();
使用V3.5版本的库函数,该函数在系统启动之后会自动调用:
startup_stm32f10x_xx.s文件中:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
SysTick定时器
Systick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器。
Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。
Systick中断的优先级也可以设置。
4个Systick寄存器
CTRL SysTick 控制和状态寄存器
LOAD SysTick 自动重装载除值寄存器
VAL SysTick 当前值寄存器
CALIB SysTick 校准值寄存器
对于STM32,
外部时钟源是 HCLK(AHB总线时钟)的1/8
内核时钟是 HCLK时钟
配置函数:SysTick_CLKSourceConfig();
固件库中的Systick相关函数:
SysTick_CLKSourceConfig() //Systick时钟源选择 misc.c文件中
SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断
//core_cm3.h/core_cm4.h文件中
Systick中断服务函数:
void SysTick_Handler(void);
端口复用
STM32有很多的内置外设,这些外设的外部引脚都是与GPIO复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPIO作为内置外设使用的时候,就叫做复用。
例如串口1 的发送接收引脚是PA9,PA10,当我们把PA9,PA10不用作GPIO,而用做复用功能串口1的发送接收引脚的时候,叫端口复用。
端口复用配置过程:
以PA9,PA10配置为串口1为例
1.GPIO端口时钟使能。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2.复用外设时钟使能。
比如你要将端口PA9,PA10复用为串口,所以要使能串口时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
3.端口模式配置。 GPIO_Init()函数。
查表:
《STM32中文参考手册V10》P110的表格“8.1.11外设的GPIO配置”
1 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //①IO时钟使能 |
中断优先级管理NVIC
CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。
STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。
STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
STM32F103系列上面,又只有60个可屏蔽中断(在107系列才有68个)
中断管理方法:
首先,对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。
分组配置是在寄存器SCB->AIRCR中配置:
抢占优先级 & 响应优先级区别:
高抢占优先级可以打断正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
假定设置中断优先级组为2,然后设置中断3(RTC中断)的抢占优先级为2,响应优先级为1。 中断6(外部中断0)的抢占优先级为3,响应优先级为0。中断7(外部中断1)的抢占优先级为2,响应优先级为0。
那么这3个中断的优先级顺序为:中断7>中断3>中断6.
特别说明:
一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组。随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。
中断优先级分组函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
怎么设置单个中断的抢占优先级和响应优先级?
中断设置相关寄存器
__IO uint8_t IP[240]; //中断优先级控制的寄存器组
__IO uint32_t ISER[8]; //中断使能寄存器组
__IO uint32_t ICER[8]; //中断失能寄存器组
__IO uint32_t ISPR[8]; //中断挂起寄存器组
__IO uint32_t ICPR[8]; //中断解挂寄存器组
__IO uint32_t IABR[8]; //中断激活标志位寄存器组
MDK中NVIC寄存器结构体:
typedef struct
{
__IO uint32_t ISER[8];
uint32_t RESERVED0[24];
__IO uint32_t ICER[8];
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8];
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8];
uint32_t RESERVED3[24];
__IO uint32_t IABR[8];
uint32_t RESERVED4[56];
__IO uint8_t IP[240];
uint32_t RESERVED5[644];
__O uint32_t STIR;
} NVIC_Type;
对于每个中断怎么设置优先级?
中断优先级控制的寄存器组:IP[240]
全称是:Interrupt Priority Registers
240个8位寄存器,每个中断使用一个寄存器来确定优先级。STM32F10x系列一共60个可屏蔽中断,使用IP[59]~IP[0]。
每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
中断使能寄存器组:ISER[8]
作用:用来使能中断
32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]。
ISER[0]的bit0bit31分别对应中断031。ISER[1]的bit027对应中断3259;
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
中断失能寄存器组:ICER[8]
作用:用来失能中断
32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICER[1]。
ICER[0]的bit0bit31分别对应中断031。ICER[1]的bit027对应中断3259;
配置方法跟ISER一样。
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
中断挂起控制寄存器组:ISPR[8]
作用:用来挂起中断
中断解挂控制寄存器组:ICPR[8]
作用:用来解挂中断
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn);
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
中断参数初始化函数
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
typedef struct
{
uint8_t NVIC_IRQChannel; //设置中断通道
uint8_t NVIC_IRQChannelPreemptionPriority;//设置响应优先级
uint8_t NVIC_IRQChannelSubPriority; //设置抢占优先级
FunctionalState NVIC_IRQChannelCmd; //使能/使能
} NVIC_InitTypeDef;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化NVIC寄存器
中断优先级设置步骤
系统运行后先设置中断优先级分组。调用函数:
- void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
整个系统执行过程中,只设置一次中断分组。 - 针对每个中断,设置对应的抢占优先级和响应优先级:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); - 如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可
串口通信
通信接口背景知识
处理器与外部设备通信的两种方式:
并行通信
-传输原理:数据各个位同时传输。
-优点:速度快
-缺点:占用引脚资源多
串行通信
-传输原理:数据按位顺序传输。
-优点:占用引脚资源少
-缺点:速度相对较慢
串行通信:
按照数据传送方向,分为:
单工:
数据传输只支持数据在一个方向上传输
半双工:
允许数据在两个方向上传输,但是,在某一时刻,只允许数
据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:
允许数据同时在两个方向上传输,因此,全双工通信是两个
单工通信方式的结合,它要求发送设备和接收设备都有独立
的接收和发送能力。
同步通信:带时钟同步信号传输。
SPI,IIC通信接口
异步通信:不带时钟同步信号。
UART(通用异步收发器),单总线(要约定波特率)
常见的串行通信接口:
通信标准 | 引脚说明 | 通信方式 | 通信方向 |
---|---|---|---|
UART(通用异步收发器) | TXD:发送端 RXD:接受端 GND:公共地 | 异步通信 | 全双工 |
单总线(1-wire) | DQ:发送/接受端 | 异步通信 | 半双工 |
SPI | SCK:同步时钟 MISO:主机输入,从机输出 MOSI:主机输出,从机输入 | 同步通信 | 全双工 |
I²C | SCL:同步时钟 SDA:数据输入/输出端 | 同步通信 | 半双工 |
STM32的串口通信接口:
UART:通用异步收发器
USART:通用同步异步收发器
大容量STM32F10x系列芯片,包含3个USART和2个UART
UART异步通信方式引脚:
-RXD:数据输入引脚。数据接受。
-TXD:数据发送引脚。数据发送。
串口号 | RXD | TXD |
---|---|---|
1 | PA10 | PA9 |
2 | PA3 | PA2 |
3 | PB11 | PB10 |
4 | PC11 | PC10 |
5 | PD2 | PC12 |
UART异步通信方式特点:
全双工异步通信。
分数波特率发生器系统,提供精确的波特率。
-发送和接受共用的可编程波特率,最高可达4.5Mbits/s
可编程的数据字长度(8位或者9位);
可配置的停止位(支持1或者2位停止位);
可配置的使用DMA多缓冲器通信。
单独的发送器和接收器使能位。
检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志
多个带标志的中断源。触发中断。
其他:校验控制,四个错误检测标志。
STM32串口异步通信需要定义的参数:
起始位
数据位(8位或者9位)
奇偶校验位(第9位)
停止位(1,15,2位)
波特率设置
STM32串口常用寄存器和库函数
常用的串口相关寄存器:
USART_SR状态寄存器
USART_DR数据寄存器
USART_BRR波特率寄存器
串口操作相关库函数(省略入口参数):
1 | void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能 |
串口初始化函数
找到初始化函数的声明:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
两个入口参数分别是
串口标号USARTx和一个结构体USART_InitStruct 找到结构体的定义
1 | typedef struct |
串口配置的一般步骤
串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd();
串口复位:USART_DeInit(); 这一步不是必须的
GPIO端口模式设置:GPIO_Init(); 模式设置为GPIO_Mode_AF_PP
串口参数初始化:USART_Init();
开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)
NVIC_Init();
USART_ITConfig();
⑥使能串口:USART_Cmd();
⑦编写中断处理函数:USARTx_IRQHandler();
⑧串口数据收发:
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
⑨串口传输状态获取:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
PA9 PA10复用为串口1
代码实现
1 | void My_USART1_Init(void) |
测试
先将程序下载进开发板
打开串口调试助手 XCOM V2.6
测试成功
外部中断实验
外部中断概述
STM32的每个IO都可以作为外部中断输入。
STM32的中断控制器支持19个外部中断/事件请求:
线0~15:对应外部IO口的输入中断。
线16:连接到PVD输出。
线17:连接到RTC闹钟事件。
线18:连接到USB唤醒事件。
每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。
从上面可以看出,STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),
STM32F103RCT6(51),那么中断线怎么跟io口对应呢?
GPIOx.0映射到EXTI0
GPIOx.1映射到EXTI1
…
GPIOx.15映射到EXTI15
对于每个中断线,我们可以设置相应的触发方式(上升沿触发,下降沿触发,边沿触发)以及使能。
是不是16个中断线就可以分配16个中断服务函数呢?
IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数
外部中断线04各分配一个中断向量,共五个服务函数9分配一个中断向量,共用一个服务函数
外部中断线5
外部中断线10~15分配一个中断向量,共用一个中断服务函数。
中断服务函数列表:
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
外部中断常用库函数
①void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//设置IO口与中断线的映射关系
exp: GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
②void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//初始化中断线:触发方式等
③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//判断中断线中断状态,是否发生
④void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
//清除中断线上的中断标志位
EXTI_Init函数
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
1 | typedef struct |
1 | EXTI_InitStructure.EXTI_Line=EXTI_Line2; |
外部中断的一般配置步骤:
1.初始化IO口为输入。
GPIO_Init();
2.开启IO口复用时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
3.设置IO口与中断线的映射关系。
void GPIO_EXTILineConfig();
4.初始化线上中断,设置触发条件等。
EXTI_Init();
5.配置中断分组(NVIC),并使能中断。
NVIC_Init();
6.编写中断服务函数。
EXTIx_IRQHandler();
7.清除中断标志位
EXTI_ClearITPendingBit();
代码实现
1 | #include "led.h" |
看门狗实验
独立看门狗概述
为什么要看门狗?
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。
看门狗解决的问题是什么?
在启动正常运行的时候,系统不能复位。
在系统跑飞(程序异常执行)的情况,系统复位,程序重新执行。
STM32内置两个看门狗,提供了更高的安全性,时间的精确性和使用的灵活性。两个看门狗设备(独立看门狗/窗口看门狗)可以用来检测和解决由软件错误引起的故障。当计数器达到给定的超时值时,触发一个中断(仅适用窗口看门狗)或者产生系统复位。
独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它仍有效。
独立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要求低的场合。
窗口看门狗由从APB1时钟分频后得到时钟驱动。通过可配置的时间窗口来检测应用程序非正常的过迟或过早操作。
窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。
独立看门狗功能描述:
1.在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗。此时计数器开始从其复位值0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。
2.无论何时,只要在键值寄存器IWDG_KR中写入0xAAAA(通常说的喂狗), 自动重装载寄存器IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。
3.如果程序异常,就无法正常喂狗,从而系统复位。
键值寄存器IWDG_KR: 015位有效2位有效。具有写保护功能,要操作先取消写保护
预分频寄存器IWDG_PR:0
重装载寄存器IWDG_RLR:011位有效。具有写保护功能,要操作先取消写保护。1位有效
状态寄存器IWDG_SR:0
取消写保护:写入0x5555表示允许访问IWDG_PR和IWDG_RLR寄存器
独立看门狗超时时间
溢出时间计算:
Tout=((4×2^prer) ×rlr) /40 (M3)
IWDG独立看门狗操作库函数
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置预分频系数:写PR
void IWDG_SetReload(uint16_t Reload); //设置重装载值:写RLR
void IWDG_ReloadCounter(void); //喂狗:写0xAAAA到KR
void IWDG_Enable(void); //使能看门狗:写0xCCCC到KR
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG); //状态:重装载/预分频 更新
独立看门狗操作步骤:
① 取消寄存器写保护:
IWDG_WriteAccessCmd();
② 设置独立看门狗的预分频系数,确定时钟:
IWDG_SetPrescaler();
③ 设置看门狗重装载值,确定溢出时间:
IWDG_SetReload();
④ 使能看门狗
IWDG_Enable();
⑤ 应用程序喂狗:
IWDG_ReloadCounter();
溢出时间计算:
Tout=((4×2^prer) ×rlr) /40 (M3)
程序代码
#include “led.h”
#include “delay.h”
#include “key.h”
#include “sys.h”
#include “beep.h”
#include “usart.h”
#include “exti.h”
#include “wdg.h”
int main(void)
{
delay_init();
LED_Init();
KEY_Init();
BEEP_Init();
IWDG_Init(4,625);//1s
delay_ms(200);
LED0=0;
while(1)
{
if(KEY_Scan(0)==WKUP_PRES)
{
IWDG_ReloadCounter();
}
}
}
TFTLCD显示实验
TFTLCD驱动原理-TFTLCD简介
TFTLCD即薄膜晶体管液晶显示器。它与无源TN-LCD、STN-LCD的简单矩阵不同,它在液晶显示屏的每一个像素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。
TFTLCD具有:亮度好、对比度高、层次感强、颜色鲜艳等特点。是目前最主流的LCD显示器。广泛应用于电视、手机、电脑、平板等各种电子产品。
ATK-4.3寸 TFTLCD模块
分辨率:480*800,驱动IC:NT35510,电容触摸屏,16位并口驱动
ALINETEK 2.8寸 TFTLCD接口说明(16位80并口):
注意:DB1DB8,DB10DB17,总是按顺序连接MCU的D0~D15
LCD_CS:LCD片选信号
LCD_WR:LCD写信号
LCD_RD:LCD读信号
DB[17:1]:16位双向数据线。
LCD_RST:硬复位LCD信号
LCD_RS:命令/数据标志
(0:命令,1:数据)
BL_CTR:背光控制信号
T_MISO/T_MOSI/T_PEN/T_CS/T_CLK,触摸屏接口信号
ALINETEK 2.8寸 TFTLCD 16位80并口驱动简介
模块的8080并口读/写的过程为:
先根据要写入/读取的数据的类型,设置RS为高(数据)/低(命令),然后拉低片选,选中ILI9341,接着我们根据是读数据,还是要写数据置RD/WR为低,然后:
1.读数据:在RD的上升沿, 读取数据线上的数据(D[15:0]);
2.写数据:在WR的上升沿,使数据写入到ILI9341里面
ILI9341 驱动时序
右图为:ILI9341 8080并口时序,详见:ILI9341_DS.pdf,232页
重点时序:
读ID低电平脉宽(trdl)
读ID高电平脉宽(trdh)
读FM低电平脉宽(trdlfm)
读FM高电平脉宽(trdhfm)
写控制低电平脉宽(twrl)
写控制高电平脉宽(twrh)
注意:ID指LCD的ID号
FM指帧缓存,即:GRAM
驱动流程
1.硬复位 LCD_RST=0; delay_ms(100); LCD_RST=1;
2.初始化序列→设置坐标
2.1写GRAM指令→写入颜色数据→LCD显示
2.3读GRAM指令→读出颜色数据→单片机处理
RGB565格式说明
模块对外接口采用16位并口,颜色深度为16位,格式为RGB565
数据线 | D15 | D14 | D13 | D12 | D11 | D10 | D9 | D8 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LCD GRAM | R[4] | R[3] | R[2] | R[1] | R[0] | G[5] | G[4] | G[3] | G[2] | G[1] | G[0] | B[4] | B[3] | B[2] | B[1] | B[0] |
0xF800=1111 1000 0000 0000 对应R[4]R[3]R[2]R[1]R[0]为1 其余为0 所以是红色
ILI9341指令格式说明
ILI9341所有的指令都是8位的(高8位无效),且参数除了读写GRAM的时候是16位,其他操作参数,都是8位的。
ILI9341的指令很多,这里不一一介绍,仅介绍几个重要的指令,他们是:0XD3,0X36,0X2A,0X2B,0X2C,0X2E等6条指令。
0XD3指令
该指令为读ID4指令,用于读取LCD控制器的ID 。因此,同一个代码,可以根据ID的不同,执行不同的LCD驱动初始化,以兼容不同的LCD屏幕。
0X36指令
该指令为存储访问控制指令,可以控制ILI9341存储器的读写方向,简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。
0X2A指令
该指令是列地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标)
0X2B指令
该指令是页地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标)
0X2C指令
该指令是写GRAM指令,在发送该指令之后,我们便可以往LCD的GRAM里面写入颜色数据了,该指令支持连续写 (地址自动递增)
0X2E指令
该指令是读GRAM指令,用于读取ILI9341的显存(GRAM),同0X2C指令,该指令支持连续读 (地址自动递增)
FSMC简介-FSMC介绍
FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和16位PC存储器卡连接,STM32的FSMC接口支持包括SRAM、NAND FLASH、NOR FLASH和PSRAM等存储器。FSMC的框图如下图所示:
FSMC驱动外部SRAM时,外部SRAM的控制一般有:地址线(如A0A25)、数据线(如D0D15)、写信号(WE,即WR)、读信号(OE,即RD)、片选信号(CS),如果SRAM支持字节控制,那么还有UB/LB信号。
FSMC驱动LCD的原理
而TFTLCD的信号我们在前面介绍过,包括:RS、D0D15、WR、RD、CS、RST和BL等,其中真正在操作LCD的时候需要用到的就只有:RS、D0D15、WR、RD和CS。其操作时序和SRAM的控制完全类似,唯一不同就是TFTLCD有RS信号,但是没有地址信号。
TFTLCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把RS接在A0上面,那么当FSMC控制器写地址0的时候,会使得A0变为0,对TFTLCD来说,就是写命令。而FSMC写地址1的时候,A0将会变为1,对TFTLCD来说,就是写数据了。这样,就把数据和命令区分开了,他们其实就是对应SRAM操作的两个连续地址。当然RS也可以接在其他地址线上,战舰V3和精英板开发板都是把RS连接在A10上面,而探索者STM32F4把RS接在A6上面。
因此,可以把TFTLCD当成一个SRAM来用,只不过这个SRAM有2个地址,这就是FSMC可以驱动LCD的原理。
存储块1 操作简介
STM32的FSMC存储块1(Bank1)用于驱动NOR FLASH/SRAM/PSRAM,被分为4个区,每个区管理64M字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1的256M字节空间由28根地址线(HADDR[27:0])寻址。
这里HADDR,是内部AHB地址总线,其中,HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而HADDR[26:27]对4个区进行寻址。
当Bank1接的是16位宽度存储器的时候:HADDR[25:1] FSMC_A[24:0]
当Bank1接的是8位宽度存储器的时候:HADDR[25:0] FSMC_A[25:0]
不论外部接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]
STM32的FSMC存储块1 支持的异步突发访问模式包括:模式1、模式A~D等多种时序模型,驱动SRAM时一般使用模式1或者模式 A,这里我们使用模式A来驱动LCD(当SRAM用)
模式A支持读写时序分开设置! 对STM32F4仅写时序DATAST需要+1
FSMC简介-寄存器介绍
对于NOR FLASH/PSRAM控制器(存储块1),通过FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx寄存器设置(其中x=1~4,对应4个区)。通过这3个寄存器,可以设置FSMC访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围。
SRAM/NOR闪存片选控制寄存器(FSMC_BCRx)
EXTMOD:扩展模式使能位,控制是否允许读写不同的时序,需设置为1
WREN:写使能位。我们需要向TFTLCD写数据,故该位必须设置为1
MWID[1:0]:存储器数据总线宽度。00,表示8位数据模式;01表示16位数据模式;10和11保留。我们的TFTLCD是16位数据线,所以设置WMID[1:0]=01。
MTYP[1:0]:存储器类型。00表示SRAM、ROM;01表示PSRAM;10表示NOR FLASH;11保留。我们把LCD当成SRAM用,所以需要设置MTYP[1:0]=00。
MBKEN:存储块使能位。需设置为1
SRAM/NOR闪存片选时序寄存器(FSMC_BTRx)
ACCMOD[1:0]:访问模式。00:模式A;01:模式B;10:模式C;11:模式D。
DATAST[7:0]:数据保持时间,等于: DATAST(+1)个HCLK时钟周期,DATAST最大为255。对ILI9341来说,其实就是RD低电平持续时间,最大为355ns。对STM32F1,一个HCLK=13.8ns (1/72M),设置为15;对STM32F4,一个HCLK=6ns(1/168M) ,设置为60。
ADDSET[3:0]:地址建立时间。表示:ADDSET (+1)个HCLK周期,ADDSET最大为15。对ILI9341来说,这里相当于RD高电平持续时间,为90ns。STM32F1的FSMC性能存在问题,即便设置为0,RD也有190ns的高电平,我们这里设置为1。而对STM32F4,则设置为15。
SRAM/NOR闪存写时序寄存器(FSMC_BWTRx)
ACCMOD[1:0]:访问模式。00:模式A;01:模式B;10:模式C;11:模式D。
DATAST[7:0]:数据保持时间,等于: DATAST(+1)个HCLK时钟周期,DATAST最大为255。对ILI9341来说,其实就是WR低电平持续时间,为15ns,不过ILI9320等则需要50ns。考虑兼容性,对STM32F1,一个HCLK=13.8ns (1/72M),设置为3;对STM32F4,一个HCLK=6ns(1/168M) ,设置为9。
ADDSET[3:0]:地址建立时间。表示:ADDSET+1个HCLK周期,ADDSET最大为15。对ILI9341来说,这里相当于WR高电平持续时间,为15ns。同样考虑兼容ILI9320,对STM32F1,这里即便设置为1,WR也有100ns的高电平,我们这里设置为1。而对STM32F4,则设置为8。
寄存器组合说明
在ST官方库提供的的寄存器定义里面,并没有定义FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx等这个单独的寄存器,而是将他们进行了一些组合。规律如下:
FSMC_BCRx和FSMC_BTRx,组合成BTCR[8]寄存器组,他们的对应关系如下:
BTCR[0]对应FSMC_BCR1,BTCR[1]对应FSMC_BTR1
BTCR[2]对应FSMC_BCR2,BTCR[3]对应FSMC_BTR2
BTCR[4]对应FSMC_BCR3,BTCR[5]对应FSMC_BTR3
BTCR[6]对应FSMC_BCR4,BTCR[7]对应FSMC_BTR4
FSMC_BWTRx则组合成BWTR[7],他们的对应关系如下:
BWTR[0]对应FSMC_BWTR1,BWTR[2]对应FSMC_BWTR2,
BWTR[4]对应FSMC_BWTR3,BWTR[6]对应FSMC_BWTR4,
BWTR[1]、BWTR[3]和BWTR[5]保留,没有用到。
程序函数
硬件连接
LCD_BL(背光控制)对应 PB0;
LCD_CS 对应 PG12 即 FSMC_NE4;
LCD _RS 对应 PG0 即 FSMC_A10;
LCD _WR 对应 PD5 即 FSMC_NWE;
LCD _RD 对应 PD4 即 FSMC_NOE;
LCD _D[15:0]则直接连接在 FSMC_D15~FSMC_D0;
LCD结构体
//LCD地址结构体
typedef struct
{
vu16 LCD_REG;
vu16 LCD_RAM;
} LCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE ((u32)(0x6C000000 | 0x000007FE))
#define LCD ((LCD_TypeDef *) LCD_BASE)
LCD_BASE,须根据外部电路的连接来确定,如Bank1.sector4就是从地址0X6C000000开
始,而0X000007FE,则是A10的偏移量。以A10为例,7FE换成二进制为:111 1111 1110
,而16位数据时,地址右移一位对齐,对应到地址引脚,就是:A10:A0=011 1111 1111,
此时A10是0,但是如果16位地址再加1(对应到8位地址是加2,即7FE+0X02),那么:
A10:A0=100 0000 0000,此时A10就是1了,即实现了对RS的0和1的控制。
我们将这个地址强制转换为LCD_TypeDef结构体地址,那么可以得到LCD->LCD_REG的
地址就是0X6C00,07FE,对应A10的状态为0(即RS=0),而LCD-> LCD_RAM的地址就是
0X6C00,0800(结构体地址自增),对应A10的状态为1(即RS=1),从而实现对RS的控
制。
//LCD 重要参数集
typedef struct
{
u16 width; //LCD 宽度
u16 height; //LCD 高度
u16 id; //LCD ID
u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
u16 wramcmd; //开始写 gram 指令
u16 setxcmd; //设置 x 坐标指令
u16 setycmd; //设置 y 坐标指令
}_lcd_dev;
//LCD 参数
extern _lcd_dev lcddev; //管理 LCD 重要参数
cddev结构体参数的赋值,基本上都是在LCD_Display_Dir函数完成
7个底层接口函数:
1,写寄存器值函数 :void LCD_WR_REG(u16 regval)
2,写数据函数:void LCD_WR_DATA(u16 data)
3,读数据函数:u16 LCD_RD_DATA(void)
4,写寄存器内容函数: void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue)
5,读寄存器内容函数: u16 LCD_ReadReg(u16 LCD_Reg)
6,开始写GRAM函数: void LCD_WriteRAM_Prepare(void)
7,写GRAM函数: void LCD_WriteRAM(u16 RGB_Code)
LCD初始化函数伪代码:
//LCD初始化
void LCD_Init(void)
{
初始化GPIO;
初始化FSMC;
读取LCD ID;
printf(“LCD ID:%x\r\n”,lcddev.id);//打印LCD ID,用到了串口1
//所以必须初始化串口1,否则黑屏
根据不同的ID执行LCD初始化代码;
LCD_Display_Dir(0); //默认为竖屏
LCD_LED=1; //点亮背光
LCD_Clear(WHITE); //清屏
}
LCD坐标设置函数
//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
if(lcddev.id==0X9341||lcddev.id==0X5310)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);
LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_DATA(Ypos&0XFF);
}else if(lcddev.id==XXXX) //根据不同的LCD型号,执行不同的代码
{
……//省略部分代码
}
}
LCD画点函数
//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
LCD->LCD_RAM=POINT_COLOR; //非Mini板的操作方式
}
LCD读点函数:
u16 LCD_ReadPoint(u16 x,u16 y)
字符显示函数
//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:” “—>”~”
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0) 叠加方式的意思是保持底色不变
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{
u8 temp,t1,t;
u16 y0=y;
u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数
num=num-‘ ‘; //得到偏移后的值(ASCII字库是从空格开始取模,所以-‘ ‘就是对应字符的字库)
for(t=0;t<csize;t++)
{
if(size==12)temp=asc2_1206[num][t]; //调用1206字体
else if(size==16)temp=asc2_1608[num][t]; //调用1608字体
else if(size==24)temp=asc2_2412[num][t]; //调用2412字体
else return; //没有的字库
for(t1=0;t1<8;t1++)
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
temp<<=1;
y++;
if(y>=lcddev.height)return; //超区域了
if((y-y0)==size)
{
y=y0;
x++;
if(x>=lcddev.width)return; //超区域了
break;
}
}
}
}
字符码表
const unsigned char oled_asc2_1206[95][12]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/“ “,0/
{0x00,0x00,0x00,0x00,0x3F,0x40,0x00,0x00,0x00,0x00,0x00,0x00},/“!”,1/
……
{0x40,0x00,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x40,0x00},/“~”,94/
};
const unsigned char oled_asc2_1608[95][16]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/“ “,0/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/“!”,1/
……
{0x00,0x00,0x60,0x00,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x20,0x00,0x20,0x00},/“~”,94/
}
const unsigned char oled_asc2_2412[95][36]={
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/“ “,0/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x80,0x38,0x0F,0xFE,0x38,0x0F,0x80,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/“!”,1/
……
{0x00,0x00,0x00,0x18,0x00,0x00,0x60,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0x00,0x10,0x00,0x00,0x08,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x0C,0x00,0x00,0x10,0x00,0x00},/“~”,94/
}
字符生产方式
用软件PCtoLCD软件
配置:
点阵格式:阴码
取模方式:逐列式
取模走向:顺向
自定义格式: C51格式
输出方式是从上到下从左到右
温度传感器实验
DS18B20介绍
DS18B20技术性能特征:
①独特的单总线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实现微处理器与DS18B20的双向通讯。大大提高了系统的抗干扰性。
②测温范围 -55℃~+125℃,精度为±0.5℃。
③支持多点组网功能,多个DS18B20可以并联在唯一的三线上,最多只能并联8个,
实现多点测温,如果数量过多,会使供电电源电压过低,从而造成信号传输的不稳定。
④工作电源: 3.05.5V/DC (可以数据线寄生电源)。12位数字量方式串行传送。
⑤在使用中不需要任何外围元件。
⑥测量结果以9
DS18B20封装
见PPT
光敏传感器实验
光敏传感器简介
光敏传感器是最常见的传感器之一,它的种类繁多,主要有:光电管、光电倍增管、光敏电阻、光敏三极管、太阳能电池、红外线传感器、紫外线传感器、光纤式光电传感器、色彩传感器、CCD和CMOS图像传感器等。光传感器是目前产量最多、应用最广的传感器之一,它在自动控制和非电量电测技术中占有非常重要的地位。
光敏传感器是利用光敏元件将光信号转换为电信号的传感器,它的敏感波长在可见光波长附近,包括红外线波长和紫外线波长。光传感器不只局限于对光的探测,它还可以作为探测元件组成其他传感器,对许多非电量进行检测,只要将这些非电量转换为光信号的变化即可
光敏二极管也叫光电二极管。光敏二极管与半导体二极管在结构上是类似的,其管芯是一个具有光敏特征的PN结,具有单向导电性,因此工作时需加上反向电压。无光照时,有很小的饱和反向漏电流,即暗电流,此时光敏二极管截止。当受到光照时,饱和反向漏电流大大增加,形成光电流,它随入射光强度的变化而变化。当光线照射PN结时,可以使PN结中产生电子一空穴对,使少数载流子的密度增加。这些载流子在反向电压下漂移,使反向电流增加。因此可以利用光照强弱来改变电路中的电流。
简而言之:照射光敏二极管的光强不同,通过光敏二极管的电流大小就不同,所以可以通过检测电流大小,达到检测光强的目的。
利用这个电流变化,我们串接一个电阻,就可以转换成电压的变化,从而通过ADC读取电压值,判断外部光线的强弱。
硬件连接
光敏二极管串联一个电阻连接到VCC,一端接地
无光照时,光敏二极管两端无电流通过,电阻左边结点的电压V节点=Vcc
有光照时,设通过光么二极管的电流为I,光敏二极管电阻为R,电阻左边结点的电压V节点=Vcc-IR
可以看出,V节点与I成反比,即光强越小,I越小,V节点越大
我们通过ADC来测量V节点的大小,从而达到检测光强的目的。
ADC连接到PF8(ADC3的通道6)
触摸屏实验
触摸屏(touch screen)又称为“触控屏”、“触控面板”,是一种可接收触头等输入讯号的感应式装置。作为一种新型的电脑输入设备,可以用来取代传统的机械按键等输入设备。它是目前最简单、方便、自然的一种人机交互方式。主要应用于公共信息的查询、领导办公、工业控制、军事指挥、电子游戏、点歌点菜、多媒体教学、房地产预售等。
触摸屏本质上与液晶是分离的。触摸屏负责的是检测触摸点,液晶屏负责的是显示。区别开来。
触摸屏一般覆盖在液晶屏上
按照触摸屏的工作原理和传输信息的介质,把触摸屏分为四种,它们分别为
电阻式:定位准确,单点触摸。
电容感应式:支持多点触摸,价格偏贵。工业应用最广泛
红外线式:价格低廉,但其外框易碎,容易产生光干扰,曲面情况下失真。
表面声波式:解决各种缺点,但是屏幕表面如果有水滴和尘土会使触摸屏变的迟钝。
电容触摸屏
电容屏是利用人体感应进行触点检测控制,只需要轻微接触,通过检测感应电流来定位触摸坐标。现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏。
电容型触摸屏分类
表面电容式电容触摸屏:
表面电容式触摸屏技术是利用ITO(铟锡氧化物,是一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。
投射式电容触摸屏
投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。
自我电容式
自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用X*Y的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号来确定手指的位置。
交互电容式
交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的ITO电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测X*Y根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
本实验所选择的电容触摸屏,也是采用的是投射式电容屏(交互电容类型)。
透射式电容触摸屏采用纵横两列电极组成感应矩阵,来感应触摸。以两个交叉的电极矩阵,即: X轴电极和Y轴电极,来检测每一格感应单元的电容变化
电阻屏与电容屏的区别
电阻屏在触模时需要轻触压按,而电容屏只需轻微的手指触碰就能激活
电阻屏可以用任何物体来触摸,而电容屏是人体热感应工作原理,只能用手指的热感区来触摸,指甲和手写笔均无效。由于手指头的面积比手写笔大很多,因此电容屏的手机,触摸比较小图标或者菜单的时候,触摸精度无法做到电阻屏那么高。
电容屏可以很容易进行多点触摸,电阻屏一般不能实现多点触摸的。
电阻屏内部是软的,一般是在4到5层超薄的钢化玻璃中间夹杂细微的炭粒(显微镜下才能看见),通过按压导致上下两层的炭粒相互接触而接通触屏电路,产生触摸反应,容易产生划痕,易坏,容易触屏不灵,而电容屏都是采用单层加厚钢化玻璃,硬度大,耐旧,使用寿长
电阻屏在阳光下可视性稍差,电容屏则非常好,在阳光下可视性很强
电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。
电容屏优缺点总结
优点:
手感好,无需校准,支持多点触摸,透光性好。
缺点:
成本高,精度不高,抗干扰能力差。
触摸屏原理
电容触摸屏一般都需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的
常见的2种电容触摸屏驱动IC
GT9147:采用1710的驱动结构(10个感应通道,17个驱动通道)。(用这个)
OTT2001A:采用138的驱动结构(8个感应通道,13个驱动通道)
它们与MCU连接通过4根线:SDA、SCL、RST和INT
GT9147的IIC地址,可以是0X14或者0X5D,当复位结束后的5ms内,如果INT是高电平,则使用0X14作为地址,否则使用0X5D作为地址,具体的设置过程,请看:GT9147数据手册.pdf这个文档。本章我们使用0X14作为器件地址(不含最低位,换算成读写命令则是读:0X29,写:0X28)
GT9147关键寄存器:
控制命令寄存器(0X8040)
该寄存器可以写入不同值,实现不同的控制,我们一般使用0和2这两个值,写入2,即可软复位GT9147,在硬复位之后,一般要往该寄存器写2,实行软复位。然后,写入0,即可正常读取坐标数据(并且会结束软复位)。
配置寄存器组(0X8047~0X8100)
这里共186个寄存器,用于配置GT9147的各个参数,这些配置一般由厂家提供给我们(一个数组),所以我们只需要将厂家给我们的配置,写入到这些寄存器里面,即可完成GT9147的配置。
产品ID寄存器(0X8140~0X8143)
这里总共由4个寄存器组成,用于保存产品ID,对于GT9147,这4个寄存器读出来就是:9,1,4,7四个字符(ASCII码格式)。因此,我们可以通过这4个寄存器的值,来判断驱动IC的型号,从而判断是OTT2001A还是GT9147,以便执行不同的初始化。
状态寄存器(0X814E)
寄存器 | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|---|---|---|
0X814E | buffer状态 | 大点 | 接近有效 | 按键 | 有效触点个数 |
我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0,表示没有触摸,5表示有5点触摸。
坐标数据寄存器(共30个)
这里共分成5组(5个点),每组6个寄存器存储数据,以触点1的坐标数据寄存器组为例,
寄存器 | bit7~0 | 寄存器 | bit7~0 |
---|---|---|---|
0X8150 | 触点1 x坐标低8位 | 0X8151 | 触点1 x坐标高8位 |
0X8152 | 触点1 y坐标低8位 | 0X8153 | 触点1 y坐标高8位 |
0X8154 | 触点1 触摸尺寸低8位 | 0X8155 | 触点1 触摸尺寸高8位 |
实验代码
ctiic.c是电容触摸屏的底层驱动结构,基本和myiic.c中内容差不多
touch.c是电容触摸屏的入口,会根据入口参数判断型号进行初始化
gt9147.c是驱动ic的
touch.c中
1 | u8 TP_Init(void) |
其中if(
lcddev.id==0X5510)是来判断是否是4.3寸电容触摸屏
if(GT9147_Init()==0)以9147的方法初始化,看能否正确运行
能正确运行的话执行tp_dev.scan=GT9147_Scan;
tp_dev是一个_m_tp_dev类型的结构体
这个结构体很重要,看一下它的定义
1 | typedef struct |
CT_MAX_TOUCH是允许同时触摸的最大点数
tp_dev的具体定义
1 | _m_tp_dev tp_dev= |
所以tp_dev.scan=GT9147_Scan;的实际作用是: 扫描函数指向GT9147触摸屏扫描函数
gt9147.c中
u8 GT9147_Send_Cfg(u8 mode) 写配置文件
u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8 len) 读寄存器
gt9147中比较重要的是扫描函数
1 | u8 GT9147_Scan(u8 mode) |
首先状态寄存器的bit7会置1表示启用了
bit4~bit0表示有效触点个数范围是0个(00000)到5个(11111)
由于头文件中已经宏定义
#define GT_GSTID_REG 0X814E
0X814E又是状态寄存器
所以
GT9147_RD_Reg(GT_GSTID_REG,&mode,1);
的作用是读取触点的状态
if(mode&0X80&&((mode&0XF)<6))中
mode&0X80得到最高位
mode&0XF可以得到低四位 表示最高位有1,且有触摸
具体处理过程:
1 | temp=0XFF<<(mode&0XF); //将点的个 数转换为1的位数,匹配tp_dev.sta定义 |
1 | for(i=0;i<5;i++) |
1 | if(tp_dev.x[0]>lcddev.width||tp_dev.y[0]>lcddev.height)//非法数据(坐标超出了) |
1 | if((mode&0X8F)==0X80)//无触摸点按下 |
main函数中
//电容触摸屏测试函数
1 | void ctp_test(void) |
简单地说原理就是快速读入两次触点的坐标,并且画出两点间的直线,接着再画下一段直线
由于每次读入触点坐标的间隔时间非常段,所以总体上看像是连续的笔画