1.GPIO
1.1点灯
学习板的LED原理图如图所示 :
(PWR为电源指示灯)
1.1.1使常亮
设置PB5和PE5为GPIO_Output(输出模式)
右键设置用户标签
配置引脚初始状态细节:
设置输出低电平,输出模式为八大模式之一的推挽输出(向外输出0V低电平或3.3V高电平)...ctrl+s保存即可。
1.1.2循环亮灭
接着常亮灯的步骤继续,自动生成后的代码如图:
/* USER CODE BEGIN XX/
在这写下的代码重新生成代码后会被保留
/* USER CODE END XX/
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, PinState)写GPIOX的电平状态,其参数分别为GPIO分组,GPIO编号,GPIO状态。(Alt+/出现代码提示)
绿灯亮灭完成。
延伸:红灯灭绿灯灭,红灯灭绿灯亮,红灯亮绿灯亮,红灯亮绿灯灭循环
1.2按键控制
学习板的按键原理图如图所示:
左图按键下有电容,是为了防止按键抖动(消抖),电阻是上拉电阻(若GND与3.3V互换位置,则电阻为下拉电阻)。
由图可知学习板无硬件防抖,则要使用软件防抖;而若有上拉/下拉电阻,则需要设置GPIO八大模式之一的浮空输入模式。
1.2.1按下Key0绿灯亮,松开灭
配置所需要的LED灯的状态后,配置KEY1的状态为GPIO_Input
由于学习板按键没有外接上拉电阻,所以需要配置硬件内部的上拉输入模式(除了上拉输入外还有下拉输入模式)
在while循环中使用HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) 来读取电平状态,参数为GPIO分组,GPIO编号。
ps:
按住Ctrl键后鼠标指向函数:
点击可以跳转到函数的视线页面:
可以看出 HAL_GPIO_ReadPin的返回值类型是GPIO_PinState,再次操作GPIO_PinState
得到它的取值是GPIO_PIN_RESET和GPIO_PIN_SET,而GPIO_PIN_RESET的值为0。
解决重新生成代码时的中文乱码问题:
1.2.2按下Key1时,红灯的状态翻转一次
可以利用HAL_GPIO_TogglePin(GPIOx, GPIO_Pin)函数来翻转GPIO口的输出状态。
(如果不消抖红灯的翻转状态不能跟上节奏)
1.3GPIO的八大模式
1.3.1推挽输出与开漏输出
一般的IO口只能容忍最大3.3V的电压,推挽输出模式是向外传输0V的低电平或3.3V的高电平,如果外接的硬件所需5V的电压,则需要用到开漏输出,并且需要用到特殊的IO可以容忍最大5V的电压,在芯片手册上可以查到哪些IO口能够容忍5V,查看I/OLeve,若标明FT则说明该IO口可以容忍5V。
推挽输出用到的是芯片内部的电压,而开漏输出需要用外接的电压。
1.3.2复用推挽输出与复用开漏输出
复用与普通的区别在于,在GPIO口的输出控制模块中,有两个控制模块指令的来源其一是HAL_GPIO_WritePin,另外一个是片上外设(串口、IIC),为了避免同时控制而导致秩序混乱,根据控制来源不同分为普通和复用。
1.3.3模拟输入与数字输入
数字输入包括了浮空输入、上拉输入和下拉输入,数字输入读取的是GPIO口的高低电压,而模拟输入读取的是GPIO口的具体电压。
2.中断
2.1外部中断
红灯以4s为周期闪烁,按下Key1时绿灯状态翻转
常规设置
若仅这样设置绿灯无法正常相应,这里需要用到中断服务。将PE3调为GPIO_EXTI3
调整PE3的基础设置,其中工作模式的前三种与中断有关,分别是上升沿触发中断,下降沿触发中断,上升/下降沿都触发中断
由开关原理图可知,当配置上拉电阻后开关按下时,高电平变为低电平,所以选择下降沿触发中断。
点击中断控制器NVIC勾选开启EXTI3的中断向量:
打开此文件,it结尾表明是interrupt有关
删除主函数里的按键判断函数,在it文件的最后找到EXTI3的中断服务函数,在其中写下
在默认设置时,延时函数中断(系统时钟中断)的优先级比EXTI3中断函数优先级低,所以在执行EXTI3中断服务时无法执行延时函数来消抖,所以要设置其优先级高于EXTI3即可。
2.2NVIC·EXTI·中断优先级
NVIC又称嵌套向量中断控制器,其掌管中断向量表
其中EXTI0~EXTI4有自己的中断向量,而EXTI5~EXTI9共用一个中断向量,EXTI10~EXTI15也共用一个中断向量。
当多个中断发生时,需要用到中断优先级,中断优先级分为抢占优先级和响应优先级。当存在两个中断同时出现时,先比较抢占优先级,抢占优先级一样时再比较响应优先级,响应优先级一样时就按照中断向量表上的顺序执行;当一个中断执行时又出现一个中断,只有当该中断的抢占优先级大于正在执行的中断时才能先执行该中断,否则后执行。
STM32有4个二进制位储存中断优先级信息,在这里可以设置抢占优先级和响应优先级的位数
3.串口
串口通信时,一个设备的TX连接另一设备RX,RX连接TX,而两设备的GND相连接。
3.1轮询模式
轮询模式必须要堵塞住程序的执行,直到完成发送或接收,或者等待超时,接收时需要接收固定长度的数据。
3.1.1向电脑发送信息
根据原理图可知,PA9和PA10连接的是USART1,将这两个引脚设置为USART_RX和USART_TX:
将USART1的传输方式设置为异步传输,其中Baud Rate是比特率,两个设备通信时的传输速率必须一样。
利用串口发送函数HAL_UART_Transmit(huart, pData, Size, Timeout)向电脑发送数据,其中参数为所要操作的串口的指针、所需发送信息的指针、所发送内容的长度、超时时间。
·这里运用强制转换是因为该函数发送信息指针的类型是uint8_t,而定义的message是char类型的指针,但两个类型都是8位,强制转换不会受影响。
·而运用的strlen()函数是获取字符串长度的函数,需要在头文件中声明(不声明也能使用,因为它是内置函数,但是会出现警告),对头文件的声明要写在这个中间 。
最后在串口调试助手中选择好波特率和接受格式就可以了。
3.1.2电脑控制红绿小灯亮灭
其中要用到函数HAL_UART_Receive(huart, pData, Size, Timeout)来接收数据,其参数类型与发送数据函数相同。
3.2中断模式
中断模式与轮询模式相比,增加了CPU的利用率,在处理程序时,不必一直查询接收数据寄存器是否非空,而去执行别的代码,当该寄存器空闲时会产生发送数据寄存器空中断,这时CPU再来执行相应代码填满接收数据寄存器。
3.2.1寄存器非空中断(定长)
同3.1.2的实验,在轮询模式实验的基础上,开启USART1的中断模式
和之前的中断相比,这次的中断函数不能再写在这个中断处理服务函数里了,因为每一个USART只有一个中断向量,除了“接受寄存器非空”中断、“发送数据寄存器非空”中断外,还有其他的许多中断,所以需要判断中断触发的原因,但是软件已经帮判断好了。
进入HAL_UART_IRQHandler的实现页面,向下可以找到接收完成的回调函数,__weak的含义是弱定义,将HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)函数移到main.c中重新定义并写上自己的代码。
其中的发送函数和接收函数变为了HAL_UART_Transmit_IT(huart, pData, Size),和HAL_UART_Receive_IT(huart, pData, Size),由于不需要浪费CPU的时间所以没有超时等待。
这里的变量不再仅使用于main函数里,应当设置为全局变量,PV的含义是私有变量。
3.2.2DMA传输完成中断(定长)
在串口中断时,CPU在处理中断和执行主程序之间来回切换,还是比较麻烦,这时可以用到DMA来帮助CPU搬运数据,创建一条DMA通道,告诉其源地址和目标地址后,DMA就可以在这之间搬运数据,全部搬完后会产生DMA传输完成中断。
同3.2.1的实验,在配置界面Add添加两条DMA通道用于发送和接收数据,其中发送数据的通道是DMA1的5号通道,搬运方向是外设到内存,优先级为低,接收数据通道同理。
这里将接收和发送数据的函数改为HAL_UART_Transmit_DMA(huart, pData, Size)和HAL_UART_Receive_DMA(huart, pData, Size)即可。
3.2.3串口空闲中断(不定长)
接收不定长数据时,用到的中断为串口空闲中断,当无后续数据进入时,即所有数据发送完成时触发。
与3.2.2相比,这里的接收函数换为了HAL_UARTEx_ReceiveToIdle_DMA(huart, pData, Size)与之前的参数相比,最后一个参数不再是收到的数据长度,而是一次能接收的最大数据长度,一般与数组长度相等,可以利用sizeof()关键字来取数组长度。这里Ex表示扩展,Idle表示空闲中断。
而HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)函数也变为了HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)函数,这个新的函数比之前的多了一个入参Size,是因为这里接收的长度是不定长的,而之前的数据长度是确定的,所以不需要写入长度。
写中断时要确定是谁触发了回调函数,这里判断是否是串口1进入回调函数(虽然这里只用到串口1)
除了串口空闲中断外,DMA的传输过半中断也会触发HAL_UARTEx_RxEventCallback回调函数,所以一般情况下可以用到__HAL_DMA_DISABLE_IT()来关闭DMA中断语句,第一个参数是DMA通道的指针地址,第二个是要关闭的中断
这里使用ReceiveToIdle函数后,不再调用RxCpltCallback函数,而是调用RxEventCallback函数,所以要使小灯亮起,需将之前的代码剪切过来。
3.3蓝牙
蓝牙分为两种,经典蓝牙和低功耗蓝牙。经典蓝牙是持续传输数据的设备,如蓝牙耳机;低功耗蓝牙是间歇性同步数据的设备,如运动手环。
3.3.1利用BT24蓝牙完成3.2.3
BT24蓝牙串口透传模块,透传即会将数据原封不动的接收和发送。
打开串口3,异步通信,开启中断
配置DMA通道并将波特率设置为BT24蓝牙通信的9600
本次发送的数据为16进制数据,第一位为AA,第二位为数据长度,第三位为小灯编号,第四位为高低电平即亮灭,循环第三位第四位,最后一位为前面所有的数据相加即校验和(为了放置数据在传输的过程中出错)。例如:AA 05 01 00 B0表示01号小灯灭,AA 07 01 FF 02 FF B2表示01和02号小灯亮。根据这个逻辑可以写出代码
4.IIC
IIC有两条线,其中一条SDA用来传递数据,另一条SCL提供同步时钟脉冲的时钟线。串口的通信双方可以同时进行收发数据称为全双工通信,而IIC通信同一时刻只能进行一端的收发数据称为半双工通信。
IIC为主从模式,一台设备为主机,一台设备为从机,只能由主机先发起通信,从机才能回答,这种一问一答的模式使得一台主机可以连接多个从机。IIC支持多个设备通信的这种协议叫总线协议,每个从机都有其唯一的地址,主机发送其唯一的设备地址就可以与其通信。
使用串口通信时通信双方有自己的通信速度即比特率这种通信为异步通信,而IIC考虑到有多个设备,由IIC的SCL发起一条有固定频率的脉冲信号,其他的设备跟它同频,这种通信叫同步通信。
4.1AHT20测量温湿度
4.1.1非状态机
打开串口2和I2C1,将I2C设置为标准的IIC模式
为了专门为AHT20写驱动文件,给每个外设生成.c/.h文件
新建AHT20的.c/.h文件
根据AHT20的手册写出其初始化函数
读取温湿度的函数
将读取和初始化函数在aht20.h中进行声明
在主函数里写下通过串口发送数据
ps:浮点数可能会报错是因为没有设置
4.1.2状态机
状态机是在不同的状态下做不同的事 。在非状态机的基础上,打开I2C的事件中断和错误中断
将AHT20的读取发送和测量函数分开写
在main函数里设置状态机的各个状态参数
根据流程在while循环中写下过程
状态1到状态2和状态3到状态4的转换需要通过回调函数完成
4.2OLED
CH1116的驱动芯片将128x64的屏幕划分为了8个page(页),每页128列,8行。CH1116的地址为0x7A。
CH1116的通信分为两类:第一类为指令,除了地址0x7A外,指令要以0x00开头接着是一字节的指令其中: 页地址的设置,如果是page0则发送指令0xB0,如果是page1则发送指令0xB1。 列地址的设置,如果要设置第90列即0x5A,则需要发送指令0x0A(将列地址低4位设置为A)和0x15(将列地址高4位设置为5)。
另一种通信类型为数据,除了地址外,数据以0x40开头,然后发送任意数量的数据。
CH1116设置完一字节的8个像素后,列地址会自动+1。
4.2.1点亮OLED
5.定时器
在STM32F10系列中有8个定时器,其中TIM6、TIM7分为基本定时器,只有一个简单的定时功能以及一个触发DAC的功能;TIM2~TIM5为通用定时器;TIM1和TIM8为高级定时器。定时器就是计数器,1Hz即1秒钟1次。与定时器有关的还有预分频器和自动重装载寄存器,自动重装载寄存器是当定时器达到一定数量后将其重置为0。
5.1基本定时器
5.1.1利用基本定时器计数
打开串口1及其中断,将高速外部时钟源设置为晶振,并将HCLK设置为72MHz,由此基本定时器与通用定时器的内部时钟信号就是72MHz。
打开基本定时器TIM6,设置预分频器的值为7200-1(从0开始计数所以-1),即72MHz/7200=10000Hz,1秒钟10000次,定时1秒即将自动重装载寄存器的值为10000-1
利用HAL_TIM_Base_Start(htim)来启动定时器,参数为要启动的定时器的指针。利用__HAL_TIM_GET_COUNTER(htim)来获取计数器的值
在串口中就可以看到其计数现象
除了上面用到的函数,还有__HAL_TIM_SET_COUNTER()用于设置计数器的值,__HAL_TIM_GET_AUTORELOAD()用于获取重装载寄存器的值,__HAL_TIM_SET_AUTORELOAD()用于设置重装载寄存器的值,__HAL_TIM_SET_PRESCALER()用于设置预分频器的值。
5.1.2利用基本定时器定时
要让定时器每1s发送一次数据就要触发更新中断,就是当计数到达自动重装载寄存器的值的时候归0重新开始计数时触发的中断。
在刚刚程序的基础上,打开TIM6的全局中断功能,使用HAL_TIM_Base_Start_IT(htim)函数开启定时器就能在达到自动重装载寄存器的值时触发中断。重写HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)中断回调函数就能知道中断发生了没。