STM32外设配置流程详解:使用CubeMX的步骤指南
GPIO开发
掌握两个GPIO输出的HAL库函数
GPIO电平输出HAL库函数
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
理解三个参数:
/*1-GPIOx:目标引脚的端口号
*2-GPIO_Pin:目标引脚的引脚号
*3-PinState:高电平--GPIO_PIN_SET;低电平--高电平--GPIO_PIN_RESET
*/
例:向PB8引脚输出高电平
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
GPIO电平翻转HAL库函数
void HAL_GPIO_TogglePin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin)
例:将PA3的引脚输出电平翻转
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);
跑马灯实验:以STM32F103C8T6为例
CubeMx配置
1.通用配置以后所有模块第一步先配置调试接口System Core –>SYS–>Debug(Serial Wire)
2.设置时钟(更精确)(可选),不设置的话默认使用内部时钟RCC–>外部高速时钟(外部低速时钟)二选一–>Crystal/Ceramic Resonator
内部时钟是由单片机内部的振荡器或晶体产生的,而外部时钟是由外部提供的稳定时钟信号源提供的
由于外部时钟是由外部提供的稳定时钟信号源,因此它通常具有更高的稳定性和精度。 而内部时钟则受到单片机内部环境和温度的影响,稳定性和精度可能较低。
3.设置时钟树(见时钟树章节),在HCLK里输入72(具体看芯片最大时钟)即可
以上均为通用模块配置
4.将引脚(我这里LED为PC13引脚)设置为输出模式,其他GPIO初始化(速率,模式等)在GPIO中配置 这里配置为开漏模式(GPIO八种工作模式)低电平点亮LED
5.最后在Project Manager配置(后面不再掩饰)
第三个为输入模式在按键开发中设置为输入上拉模式,默认为浮空
实验代码
#include "main.h"
#include "gpio.h"
void SystemClock_Config(void);
int main(void)
{
//初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
//逻辑代码
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);// HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);
HAL_Delay(500);
}
}
//以下代码都为CUBEMX初始化自动生成,以后将直接忽视
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
按键开发
HAL库中关于GPIO的3个重要函数
电平输出函数
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
电平翻转函数
void HAL_GPIO_TogglePin(GPIO_TypeDef GPIOx, uint16_t GPIO_Pin)
电平输入函数
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
例:判断PC13引脚的输入信号,若为高电平则将PB9引脚控制的LED灯的开关状态切换
if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13) == GPIO_PIN_SET)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
实例:按下KEY2,切换LED1的开关状态
按下KEY3,松开后切换LED2的开关状态
按下KEY3,关闭所有LED
实例代码
#include "main.h"
#include "gpio.h"
#define KEY2 HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_5)
#define KEY3 HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
void SystemClock_Config(void);
void Scan_Keys(void);
void Scan_Keys()
{
if(KEY2 == 0)
{
HAL_Delay(10);
if(KEY2 == 0)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
while(KEY2 == 0);//等待按键松开 防止代码重复执行
}
}
if(KEY3 == 0)
{
HAL_Delay(10);
{
if(KEY3 == 0)
{
while(KEY3 == 0);//等待按键松开
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_1);
}
}
}
if(KEY4 == 0)
{
HAL_Delay(10);
{
if(KEY4 == 0)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0|GPIO_PIN_1,1);
while(KEY == 0);
}
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
Scan_Keys();
}
}
外部中断
EXTI0,EXTI1,EXTI2,EXTI3,EXTI4专用(EXTI0的引脚为每个端口 的0号引脚PA0 PB0~PG0)
EXTI5~~EXTI9共用
EXTI10~~EXTI15共用
外部中断程序设计思路
传统标准库中断设计步骤:
1.将GPIO初始化为输入端口
2.配置相关I/O引脚与中断的映射关系
3.设置I/O引脚对应的中断触发条件
4.配置NVIC
5.设置中断优先级
6.编写中断服务函数
基于CubeMX的外部中断设计步骤
1.在CubeMX中指定引脚,配置中断初始化参数
2.重写I/O引脚对应的中断回调函数
例:将PC13引脚设置为外部中断,下降沿触发,在中断服务函数中翻转PB9引脚的电平信号
中断初始化配置
1.将GPIO设置为:GPIO_EXTI功能
2.设置中断触发条件:上升沿,下降沿,上升沿或下降沿
3.使能相关的NVIC通道
实训:将key2(PC13)设置为外部中断输入,下降沿触发在中断服务函数中切换LED1的开关状态
将key4(PB5)设置为外部中断输入,上升沿触发在中断服务函数中切换LED2的开关状态
CubeMX配置,将PB8,PB9设置为开漏输出,将PB5,PC13设置为GPIO_EXTIx,
GPIO mode 将PB5设置为上升沿,PC13设置为下降沿都输入上拉
然后使能NVIC
#include "main.h"
#include "gpio.h"
void SystemClock_Config(void);
//HAL库中所有中断服务函都跳转到同一个回调函数,只用判断产生中断的引脚作出对应的处理即可
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_13)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
if(GPIO_Pin == GPIO_PIN_13)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
}
}
定时器
CubeMX关于定时器的配置
1.设置Clock Source时钟源
2.设置Prescaler和Couter Period参数
3.设置NVIC嵌套向量中断控制器
CubeMX配置定时器
1选择定时器 2选择时钟源(一般用内部时钟) 3参数设置 4使能中断
PSC一般设置为7,Counter Mode选择为上计数 auto-reload(使能预加载更安全)
参数设置原理
自制延时函数(时基单元)
#include "main.h"
#include "tim.h"
#include "gpio.h"
void SystemClock_Config(void);
static void MyDelay(uint32_t Delay);
static uint32_t MyGetTick(void);//获取单片机当前时间,返回单片机当前的时间(ms)
static volatile uint32_t currentMiliSeconds = 0;
static void MyDelay(uint32_t Delay)
{
uint32_t endtime = MyGetTick() + Delay;//延迟结束时间=当前时间+要延迟的时间
while(MyGetTick() < endtime){}//等待延迟结束
}
static uint32_t MyGetTick()
{
return currentMiliSeconds;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1)
{
currentMiliSeconds++;//时间自增每秒进一次中断时间+1
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
HAL_TIM_Base_Start_IT(&htim1);//以中断方式启动定时器
while (1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
MyDelay(1000);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
MyDelay(1000);
}
}
外部中断信号控制LED开关
1.利用TIM2实现间隔定时,每隔0.2s将LED1的开关状态翻转(PSC=7,ARR=499,1MHZ/500=0.2s)
2.利用TIM3实现间隔定时,每隔1s将LED2的开关状态翻转(PSC=7,ARR=999,1MHZ/1000=1S)
#include "main.h"
#include "tim.h"
#include "gpio.h"
void SystemClock_Config(void);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim2)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
if(htim == &htim3)
{
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_9);
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM3_Init();
HAL_TIM_Base_Stop_IT(&htim2);
HAL_TIM_Base_Stop_IT(&htim3);
while (1)
{
}
}
PWM呼吸灯(输出比较)
时基单元决定PWM的周期,周期=ARR+1,CCR决定高电压所占时间,占空比与CCR和ARR寄存器的值有关
channel1 PWMGeneration CH1 正常输出
占空比 = 0.5sin(2πt)+0.5 t为单片机当前时间
#include "main.h"
#include "tim.h"
#include "gpio.h"
#include "math.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);//PWM正常输出
while (1)
{
float t = HAL_GetTick() * 0.001;//获取当前时间
float duty = 0.5 * sin(2*3.14*t) + 0.5;//计算占空比
uint16_t arr = __HAL_TIM_GET_AUTORELOAD(&htim1);//获取ARR寄存器的值
uint16_t ccr = duty * (arr+1);//计算CCR寄存器的值
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,ccr);//将计算结果写入CCR
}
}
超声波测距(输入捕获)
工作原理:
测距需要捕获上升沿和下降沿 要用两个通道,外部信号检测只需要一个引脚,因此只能采用直接+间接的方式
Polarity Selection:上升沿或下降沿
IC Selection:信号来源(直接,间接,TRC)
PDR:选择分频系数:1,2,4,8
IF:输入滤波器参数
注:定时器定时周期应该大于测量信号最大脉宽,echo引脚最大脉宽为38ms,所以定时器周期应大于38ms
本次实验定时器配置图:
编程思路:
实验代码:
#include "main.h"
#include "tim.h"
#include "gpio.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
while (1)
{
//1让计数器清0
__HAL_TIM_SET_COUNTER(&htim1,0);
//2清除CC1,CC2标志位
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC1);
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC2);
//3启动输入捕获
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_2);
//4向Trigger发送脉冲
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_1,GPIO_PIN_SET);
for(uint32_t i=0;i<10;i++);//循环一次消耗8个指令周期*1/8MHZ = 1us
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_1,GPIO_PIN_RESET);
//5等待测量结束
uint8_t success = 0;
uint32_t expireTime = HAL_GetTick() + 50;//计算最长等待时间 整个测量过程最长不会超过50ms
while(expireTime > HAL_GetTick())
{
uint32_t cc1Flag = __HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1);//CC1标志位为从0-1捕获到上升沿
uint32_t cc2Flag = __HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC2);//CC2标志位从0-1捕获到下降沿
if(cc1Flag == cc2Flag ==1)
{
success = 1;//测量成功
break;
}
}
//6关闭定时器
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_2);
//7计算测量结果
if(success ==1)
{
uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_1);//读取ccr1 ccr2的值
uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_2);
float pulseWidth = (ccr2 - ccr1) * 1e-6f;//脉宽 = (CCR2 - CCR1) * 10的负6次方
float distance = 340.0f * pulseWidth/2.0f;
if(distance < 0.2)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
}
}
}
}
占空比测量(从模式控制器)
定时器3通道一产生PWM,定时器1通道一测量PWM参数
定时器3配置:
Pulse为CCR寄存器值,设置占空比为20%,CH:High为正极性输出
定时器1配置:
需要从模式控制器来同时测量脉宽和周期
原理:
通道一捕获的是上升沿,每当PWM有上升沿时TI1FP1会产生一个脉冲,
TI1FP1作为TRGI每产生一个脉冲会将CNT清0来产生一个Update事件
当遇到下降沿时CNT的值会保存到CCR2中,遇到上升沿CNT的值会保存到CCR1中
所以CCR2的值等于脉宽,CCR1的值为PWM周期
编程思路
实验代码:
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);
static void UART_Printf(const char *format,...);
#include<stdarg.h>
#include<stdio.h>
#include<string.h>
static void UART_Printf(const char *format,...)
{
char tmp[128];
va_list argptr;
va_start(argptr,format);
vsprintf((char* )tmp,format,argptr);
va_end(argptr);
HAL_UART_Transmit(&huart1,(const uint8_t *)&tmp,strlen(tmp),HAL_MAX_DELAY);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM3_Init();
MX_TIM1_Init();
//启动TIM3_CH1来产生PWM
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
while (1)
{
//1清除CC1标志位
__HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1);
//2启动 定时器
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Start(&htim1,TIM_CHANNEL_2);
//3等待CC1标志位,并再次清除
while(__HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1) == 0);
__HAL_TIM_CLEAR_FLAG(&htim1,TIM_FLAG_CC1);
//4再次等待cc1标志位
while(__HAL_TIM_GET_FLAG(&htim1,TIM_FLAG_CC1) == 0);
//5关闭定时器
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Stop(&htim1,TIM_CHANNEL_2);
//6计算测量结果
uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_1);
uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1,TIM_CHANNEL_2);
float period = ccr1 * 1e-6;
float pulseWidth = ccr2 * 1e-6;
float duty = pulseWidth/period;
UART_Printf("脉宽 = %.1f,周期 = %.1f,占空比 = %.1f",pulseWidth*1e6f,period*1e6f,duty*100);
HAL_Delay(1000);
}
}
串口
HAL库中串口发送的重要函数
阻塞式发送函数(推荐使用)数据没有发完单片机不能做其他事情
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
非阻塞式发送函数(数据较多)
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
发送完毕中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
发一半中断回调函数
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
例:使用非阻塞式的串口发送函数,将发送缓数组dat_Txd中前5个数据发送到USART1,在数据发送完成后,翻转PB9的输出电平
HAL_UART_Transmit_IT(&huart1,dat_Txd,5);
void HAL_UART_TxCpltCallback(&huart1)
{
if(huart->Instance == USART1)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
}
}
阻塞式发送
HAL_UART_Transmit(&huart1,dat_Txd,5,HAL_MAX_DELAY);
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
HAL库中串口接收的重要函数
阻塞式接收函数(不推荐)
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
非阻塞式接收函数
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
接收完毕中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
接收一半中断回调函数
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
例:使用非阻塞式串口接收函数,接收USART1中的一个字节将其保存在dat_Rxd变量中,在数据发送完毕后,若该字节为0x5A,翻转PB8引脚电平
HAL_UART_Receive_IT(&huart1,&dat_Rxd,1);
HAL_UART_RxCpltCallback()
{
if(huart->Instance == USART1)
{
if(dat_Rxd == 0x5A)
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
}
}
CubeMX配置串口
不涉及到中断的配置
选择通信方式
Connectivity –>2USART1–>3Mode(常用异步模式Asynchronous)–>4参数设置
GPIO-->USART-->串口数据接收引脚可能会意外断开 接个上拉电阻
3.mode:异步通信
4.参数设置
简单串口数据发送
#include<string.h>//使用strlen函数计算字符串大小
uint8_t byteNumber = 0x5a;
uint8_t byteArray[] = {1,2,3,4,5};
char ch = 'a';
char *str = "Hello world";
HAL_UART_Transmit(&huart1,&byteNumber,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1,byteArray,5,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
HAL_UART_Transmit(&huart1,(uint8_t *)str,strlen(str),HAL_MAX_DELAY);
简单数据接收
通过串口向单片机发送1点亮LED,发送0熄灭LED
while (1)
{
uint8_t dataRc;
HAL_UART_Receive(&huart1,&dataRc,1,HAL_MAX_DELAY);
if(dataRc == '0')
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET);
}
else if(dataRc == '1')
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET);
}
}
串口中断数据接收
通过串口来控制LED闪灯的快慢,发1慢闪,2正常,3快闪
板载LED连接PC13开漏接法 串口1连接串口转TTL 其他配置与串口一样 多一个使能NVIC中断优先级默认;
1Connectivity –>2USART1–>3Mode(常用异步模式Asynchronous)–>4参数设置–>5使能中断–>中断优先级设置(只有一个中断的时候默认)
static uint32_t time =1000;
static uint8_t datarec;
void SystemClock_Config(void);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
if(datarec == '1')
{
time = 1000;
}
else if(datarec == '2')
{
time = 500;
}
else if(datarec == '3')
{
time = 100;
}
HAL_UART_Receive_IT(&huart1,&datarec,1);//再次开启中断 开启下一次字节的接收
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1,&datarec,1);//开启中断接收
while (1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
HAL_Delay(time);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
HAL_Delay(time);
}
}
外部中断信号控制LED灯开关
1.开机后,向串口1发送“hello world!”
2.串口1收到字节指令“0xA1”,打开LED1,发送“LED1 Open”
3.串口1收到字节指令“0xA2”,关闭LED1,发送“LED1 Closed”
4.在串口发送过程中,打开LED2作为发送数据指示灯
#include "main.h"
#include "usart.h"
#include "gpio.h"
#define LED1_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET)
#define LED1_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
#define LED2_ON() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET)
#define LED2_OFF() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);
void SystemClock_Config(void);
uint8_t Tx_str1[] = "hello world!\r\n";
uint8_t Tx_str2[] = "LED Open\r\n";
uint8_t Tx_str3[] = "LED Closed\r\n";
uint8_t Rx_Date = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
if(Rx_Date == 0xA1)
{
LED1_ON();
LED2_ON();
HAL_UART_Transmit(&huart1,Tx_str1,sizeof(Tx_str2),HAL_MAX_DELAY);
LED2_OFF();
HAL_UART_Receive_IT(&huart1,&Rx_Date,1);//每发完一帧数据重新开启接收
}
if(Rx_Date == 0xA2)
{
LED1_OFF();
LED2_ON();
HAL_UART_Transmit(&huart1,Tx_str1,sizeof(Tx_str3),HAL_MAX_DELAY);
LED2_OFF();
HAL_UART_Receive_IT(&huart1,&Rx_Date,1);
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
LED1_ON();
HAL_UART_Transmit(&huart1,Tx_str1,sizeof(Tx_str1),HAL_MAX_DELAY);
LED1_OFF();
HAL_UART_Receive_IT(&huart1,&Rx_Date,1);
while (1)
{
}
}
IIC
选择通信方式
1Connectivity –>IIC–>3Mode(常用标准IIC模式)–>4参数设置(stm32作为主机只设置上面参数,作从机设置下面的参数)
单片机只支持标准模式和快速模式(400kbs(400000))快速模式下占空比可调(2:1)
简单数据收发点亮OLED
uint8_t command[] = {0x00,0x8d,0x14,0xaf,0xa5};//屏幕命令流
//0x78为从机地址
HAL_I2C_Master_Transmit(&hi2c1,0x78,command,sizeof(command)/sizeof(command[0]),HAL_MAX_DELAY);
uint8_t dataRcvd;//数据接收缓冲区
HAL_I2C_Master_Receive(&hi2c1,0x78,&dataRcvd,1,HAL_MAX_DELAY);
if((dataRcvd & (0x01 << 6 )) == 0)//D6=0表示屏幕亮 点亮LED
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
}
while(1)
{
}
时钟树
AHB总线 72MHZ APB1总线 32MHZ APB2总线 72MHZ
高速树:HSI HSE 低速树:LSI LSE
SYSCLK 72MHZ
1 RCC–>HSE(Crystal)选择外部时钟源–>锁相环选择HSE作为输入 倍频系数X9–>锁相环作为系统时钟输入–>AHB分频系数1 APB1为2 APB2为1
所有节点为最高频率
SPI
串行外设接口:适用于高速,双向数据传输场景如摄像头模块
MOSI:主机发送从机接收
MISP:主机接收从机发送
SCK:串行时钟线
NSS:从机选择(低电压有效)
SPI五个参数
1波特率: 每秒钟传输高低电压的数量
2比特位传输顺序: MSB–>LSB LSB–>MSB
3数据位长度(8bit/16bit)
4时钟极性:低(时钟空闲状态下是低电压) 高(时钟空闲状态下是高电压) [上升沿叫第一边沿 下降沿为第二边沿]
5时钟相位:第一边沿采集 第二边沿采集
45可构成4种时钟模式
SPI波特率选取原则:1选择允许的最大值(通讯速度越快) 2考虑设备所能承受的极限 3考虑电路板所能承受极限
外部Flash存取输入输出实验:
要求:用按钮切换LED状态,按一下切换一次LED状态,每次按按钮的时候将LED的亮灭状态保存在外部Flash芯片中,这样即使断电LED的状态也不会消失
LED开漏接法
按钮输入上拉,松开按钮的时候引脚被上拉电阻拉成高电平,按下按钮时引脚接地;(按钮按下时没有反应,松开时切换,需要捕捉松开的瞬间)
PA567自动生成,PA4手动设置为输出模式初始电平高电压推完输出输出速度50MHZ作为NSS接口
1Connectivity –>2SPI1–>3Mode(FULL-Duplex Master 全双工)–>4参数设置 低波特率更稳定,高极性 第二边沿采集
static void SaveLEDState(uint8_t LEDState);//保存LED状态
static uint8_t LoadLEDState(void);//加载LED状态
static uint8_t LoadLEDState(void)
{
uint8_t readDataCmd[] = {0x03,0x00,0x00,0x00};
uint8_t LEDState = 0xff;//用来接收读取的数据
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);//拉低NSS
HAL_SPI_Transmit(&hspi1,readDataCmd,4,HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi1,&LEDState,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);//拉高NSS
return LEDState;
}
static void SaveLEDState(uint8_t LEDState)
{
//1写使能
uint8_t writeEnableCmd[] = {0x06};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,writeEnableCmd,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//2扇区擦除
uint8_t sectorErase[] = {0x20,0x00,0x00,0x00};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,sectorErase,4,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
HAL_Delay(100);
//3写使能
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,writeEnableCmd,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//4页编程
uint8_t pageProgCmd[5];
pageProgCmd[0] = 0x02;
pageProgCmd[1] = 0x00;
pageProgCmd[2] = 0x00;
pageProgCmd[3] = 0x00;
pageProgCmd[4] = 0x00;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,pageProgCmd,5,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
HAL_Delay(10);
}
uint8_t pre = 1; //程序开始前按钮是松开的(高电平)
uint8_t current = 1;
uint8_t LEDState = 0;//定义LED初始状态为熄灭
LEDState = LoadLEDState();
if(LEDState == 1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}
while (1)
{
pre = current;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_SET)//判断引脚电平为高电压(按钮处于松开状态)
{
current = 1;
}
else//按钮按下
{
current = 0;
}
if(pre != current)//捕捉到了按钮按下的瞬间(消抖)需要进一步判断是按钮按下的瞬间还是松开的瞬间
{
HAL_Delay(10);
if(current == 0){}//按钮按下
else//按钮松开 需要切换LED状态
{
if(LEDState == 1)//LED是点亮的
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//熄灭LED
LEDState = 0;
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);//点亮LED
LEDState = 1;
}
SaveLEDState(LEDState);//切换状态保存
}
}
}
}
uint8_t pre = 1; //程序开始前按钮是松开的(高电平)
uint8_t current = 1;
uint8_t LEDState = 0;//定义LED初始状态为熄灭
LEDState = LoadLEDState();
if(LEDState == 1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}
while (1)
{
pre = current;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_SET)//判断引脚电平为高电压(按钮处于松开状态)
{
current = 1;
}
else//按钮按下
{
current = 0;
}
if(pre != current)//捕捉到了按钮按下的瞬间(消抖)需要进一步判断是按钮按下的瞬间还是松开的瞬间
{
HAL_Delay(10);
if(current == 0){}//按钮按下
else//按钮松开 需要切换LED状态
{
if(LEDState == 1)//LED是点亮的
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//熄灭LED
LEDState = 0;
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);//点亮LED
LEDState = 1;
}
SaveLEDState(LEDState);//切换状态保存
}
}
}
}
作者:Flyik