基于B站小蜜蜂笔记!本文利用STM32F767开发板实现该实训!
题目要求: 利用STM32应用开发,完成以下功能。
1.开机后,LED0和LED1依次点亮,然后熄灭,进行灯光检测。
2.系统通过串口1发送一个“STMFXxx欢迎你!”
3.LED0作为一个秒闪灯,系统向上位机发送完字符串后,开始亮0.5s,灭0.5s…循环闪烁,并开始启动系统运行程序时间的记录,其时分秒的格式为“xx:xx:xx”
4.上位机通过一个由3字节组成的命令帧控制LED1灯开关。该命令的帧的格式为“0xBF 控制字 0xFB”
0xBF为帧头,0xFB为帧尾,控制字的定义如下:
0xA1:打开LED1,返回信息“xx:xx:xx LED1打开!”
0xA2:关闭LED1,返回信息“xx:xx:xx LED1关闭!”
其他:返回信息“xx:xx:xx 这是一个错误指令!”
本次使用的开发板是正点原子的F767阿波罗开发板,如果换成其他开发板,只需修改对应的CubeMX配置引脚即可。参数配置使用CubeMX。
1.分析题目
第一步:要求开机后做灯光自检,那就利用HAL_GPIO_WritePin()函数进行LED管脚的值的写入,再利用延时函数HAL_Delay()函数进行延时从而持续点亮LED。
第二步:要求发送“STMFXxx欢迎你!”给上位机,那就利用HAL_UART_Transmit()函数进行通信。
第三步:LED要作为一个秒闪灯进行时间的监视,因为这里用的是0.5s的时间间隔,我们就要将定时器时间设置为0.5s,并且利用定时中断的回调函数实现LED0的关闭和开启,并且每次在调用回调函数时,利用HAL_GPIO_TogglePin()函数对LED0的引脚进行电平翻转,从而控制LED0的亮灭,并且同时在回调函数中利用嵌套计时进行时分秒的记录。
第四步:利用上位机发出的指令对STM32进行控制,这里就要利用串口的中断接收函数HAL_UART_Receive_IT进行指令的接受并且在中断回调函数HAL_UART_RxCpltCallback()中对指令进行相应的操作。同时在中断回调函数HAL_UART_RxCpltCallback()中,再次调用HAL_UART_Receive_IT重新接收上位机指令。
2.利用CubeMX配置参数
RCC配置:只需要将高速时钟HSE和低速时钟LSE选择为Crystal/Ceramic Resonator(使用晶振/陶瓷振荡器)即可,其余默认选项。
时钟树配置见:F767时钟树配置
GPIO引脚配置:
因为我所使用的开发板的LED0与LED1分别连接到的是STM32F767芯片的PB1和PB0引脚(可查阅对应的原理图得到,不同的板子会使用的不同的引脚),故将PB1和PB0引脚设置为Output(输出),且引脚需要直接驱动LED灯,故将其设置为推挽输出,又因为在原理图中可得当知当PB1和PB0引脚为低电平时LED灯点亮,所以PB1和PB0引脚初始值应该为高电平。总的来说,PB1的设置就是如下表格:(PB1类似)
GPIO output level(初始状态) | High(高电平) |
GPIO mode (模式) | Output Push Pull(推挽输出) |
GPIO Pull-up/Pull-down(上下拉) | Pull-up |
Maximum output speed(输出速度) | Very High |
User label(使用标签) | LED1 |
Timer定时器配置:
使用TIM2定时器,先设置Clock source(时钟源)为Internal Clock(内部时钟),然后根据定时器计算公式来计算出所需要调节的分频系数PSC和计数周期ARR,因为我用的是TIM2,可以查询STM32F7的参考手册中的系统和存储器概述中的存储器组织结构中的寄存器边界地址来看到是用的哪条总线上的时钟。
再通过CubeMX时钟树上得到的APB1的时钟频率(公式中的输入时钟)来进行计算,我这里得到的是108Mhz。
最后,我们想要的溢出时间为500ms(5s),我们就设置分频系数psc为499,计数周期arr为107999。
验证结果:((107999+1)*(499+1))/108Mhz = 500ms(上述公式的单位为ms)
最后别忘记使能定时器中断
USART1串口配置:F767 USART1配置
详细解释见:
在此处还要注意,开发板的USART1串口引脚和CubeMX所自动生成的引脚号是否对应!查阅原理图可得PA8和PA9才是对应的USART1,所以要对自动生成的引脚分配进行修改!
将自动生成的PB14和PB15改为PA9和PA10!
工程设置见:F767工程设置
3.宏定义常用函数以及程序中所使用的变量
#define LED0_ON() HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET)
#define LED0_OFF() HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET)
#define LED0_TOG() HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin)
#define LED1_ON() HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET)
#define LED1_OFF() HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)
#define LED1_TOG() HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin) //宏定义打开LED,关闭LED以及切换LED状态的函数
uint8_t str1[]="------------F767欢迎你!------------\r\n"; //定义开机字符
uint8_t hh=0,mm=0,ss=0,s05=0; //定义时分秒以及0.5秒报时初始值
uint8_t str_buff[64]; //在sprintf()函数中存放转换结果的字符串
uint8_t RX_data[16]; //定义接收数据初始值
4.将要用的函数进行封装方便调用
void LED_Check(void) //LED自检函数
{
LED0_ON();
HAL_Delay(500);
LED0_OFF();
HAL_Delay(500);
LED1_ON();
HAL_Delay(500);
LED1_OFF();
HAL_Delay(500);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //重写0.5秒定时器中断回调函数
{
LED0_TOG();
s05++;
if(s05==2)
{
ss++;
s05=0;
if(ss==60)
{
mm++;
ss=0;
if(mm==60)
{
hh++;
mm=0;
}
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)
{
if(RX_data[0] == 0xBF && RX_data[2] == 0xFB)
{
switch(RX_data[1])
{
case 0xA1:
LED1_ON();
sprintf((char*)str_buff,"%2d:%2d:%2d LED1 OPEN!\r\n",hh,mm,ss);
break;
case 0xA2:
LED1_OFF();
sprintf((char*)str_buff,"%2d:%2d:%2d LED1 CLOSE!\r\n",hh,mm,ss);
break;
default:
sprintf((char*)str_buff,"%2d:%2d:%2d 这是一个错误命令!\r\n",hh,mm,ss);
break;
}
HAL_UART_Transmit(&huart1,str_buff,sizeof(str_buff),10000);
HAL_UART_Receive_IT(&huart1,RX_data,3);
}
}
}
5.编写主函数
int main(void) { LED_Check(); //自检LED灯是否正常 HAL_UART_Transmit(&huart1,str1,sizeof(str1),10000); //开机后向上位机发送开机字符 HAL_TIM_Base_Start_IT(&htim2); //打开定时器函数 HAL_UART_Receive_IT(&huart1,RX_data,3); //串口接收函数 }
6.运行结果
Timer+Usart实验现象