stm32的中断和串口通信
本文主要介绍stm32最小开发板外部中断、内部中断方式的串口通信,以及DMA通信
目录
一、外部中断
stm32的外部中断就是指,stm32的引脚接收到外部信号后,暂停当前工作,跳转到中断函数的工作模式。
我们可以自己定义外部信号的形式,也可以自己定义中断函数的功能。
下面通过cubeMX配置引脚
1.1配置引脚
引脚的配置如下
A4输出控制灯的亮灭,设置为GPIO_Output
A1持续输出高电平,设置同上
A7持续输出低电平,设置同上
C13个人习惯开启做测试用,设置同上
B5模拟开关,设置为GPIO_EXTI5
打开cubemx
选择芯片stm32f103c8,双击下方
1.配置引脚
右键,选择signal unping取消锁定
配置后如下
2.配置外部中断
3.配置sys
4.配置GPIO,把PA4引脚的label 改成LED_A4,方便理解,PB5同理
5.创建项目
1.2代码部分
1.打开生成的项目,找到stm32f1xx_it.c
2.找到EXTI9_5_IRQHandler这个函数,选中HAL_GPIO_EXTI_IRQHandler这个语句按F12跳到该函数
3.往下找到HAL_GPIO_EXTI_Callback这个函数,我们把中断后需要执行的操作写在这个函数中,根据上面定义的引脚,我们设定的外部条件位输入PB5引脚的电平,并且根据不同的电平控制PA4所接的灯的亮灭
代码如下
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
if(GPIO_Pin == SWITCH_Pin){
//获取B5的电位
GPIO_PinState pinState = HAL_GPIO_ReadPin(SWITCH_GPIO_Port,SWITCH_Pin);
//低电位
if(pinState==GPIO_PIN_RESET)
HAL_GPIO_WritePin(LED_A4_GPIO_Port,LED_A4_Pin,GPIO_PIN_RESET);//把A4变为低电位
//高电位
else
HAL_GPIO_WritePin(LED_A4_GPIO_Port,LED_A4_Pin,GPIO_PIN_SET);//把A4变为高电位
}
}
注意,由于我们使用了自定义的label ,这些定义是在main.h头文件下的,所以我们要在HAL_GPIO_EXTI_Callback函数的文件头部加上引用声明。
1.3结果展示
代码编译后生成.hex文件,我们把它烧录到单片机内,如下图所示,烧录的具体步骤可以参考前几篇博客
结果如下
外部中断
二、内部中断
内部中断指的是中断的来源来自单片机内部。这里我们主要介绍串口中断。
所谓串口中断,就是当串口接受端接到数据时,会引发中断,执行中断函数的特定值。
下面我们将通过hal库实现一个相关程序。
2.1 配置cubeMX
我们使用的任然时stm32f103c8芯片,打开cubeMX找到该芯片后双击。
1.设置时钟,选择晶振
2.设置sys
3.设置串口
4.设置NVIC
5.创建项目
2.2相关代码
思路
代码的原理十分简单,我们把用于交互的字符串先储存在数组内,在上位机接收信号产生中断后,在中断函数中加上字符比较,若是内容符合定义,这改变参量flag(哨兵位),中断完成后,由于哨兵位的改变,while循环中可以执行新的输出。以上便是交互的主要原理了。下面介绍一下代码。
char c;//指令 0:停止 1:开始
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
传输函数
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
中断处理函数,即根据不同的接收字符发送不同字符串
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为0时,发送提示并改变flag
if(c=='0'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为1时,发送提示并改变flag
else if(c=='1'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
下面给出整改main函数的代码参考
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>
void SystemClock_Config(void);
char c;//指令 0:停止 1:开始
char message[]="hello Windows\n";//输出信息
char tips[]="CommandError\n";//提示1
char tips1[]="Start.....\n";//提示2
char tips2[]="Stop......\n";//提示3
int flag=0;//标志 0:停止发送 1.开始发送
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
//设置接受中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
//当flag为1时,每秒发送一次信息
//当flag为0时,停止
while (1)
{
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为0时,发送提示并改变flag
if(c=='0'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为1时,发送提示并改变flag
else if(c=='1'){
flag=1;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips1, strlen(tips1),0xFFFF);
}
//当输入不存在指令时,发送提示并改变flag
else {
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips, strlen(tips),0xFFFF);
}
//重新设置中断
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);
}
/* USER CODE END 4 */
/**
* @brief System Clock Configuration
* @retval None
*/
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_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
2.3结果展示
经过编译生成hex文件后,结果如下
串口字符
三、串口发送字符串
对于上个代码,想必已经有人发现了不足。那就是,这个代码只可以传字符,如果我发的是字符串,那么就无法正常识别了。所以,我们需要进行改进。
3.改进思路
我们意识到,我们每次对比接收到的信号和命令的部分总数在
HAL_UART_RxCpltCallback函数中,根据中断规则,每次接收到数据,就会进行中断,从而执行中断函数。所以说,接收一个字符串时,会发生多个中断,这是我们不愿意看到的,我们希望改变对比字符串的位置,比如把它放进main函数中
下面
这里我们从新定义一个中断处理函数
void DEBUG_USART_IRQHandler(void)
{
uint8_t temp;
//接收中断
if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET)
{
// 读取接收的数据
temp = USART_ReceiveData(USART1);
//接收未完成
if((USART_RX_FLAG & 0x8000)==0)
{
//接收到了0x0d
if(USART_RX_FLAG & 0x4000)
{
// 接收错误,重新开始
if(temp != 0x0a) USART_RX_FLAG=0;
// 接收完成
else USART_RX_FLAG |= 0x8000;
}
// 还未接收到0x0d
else
{
if(temp == 0x0d)
{
USART_RX_FLAG |= 0x4000;
}
else
{
USART_RX_BUF[USART_RX_FLAG & 0x3FFF]=temp;
USART_RX_FLAG++;
//接收数据错误,重新开始接收
if(USART_RX_FLAG > 99) USART_RX_FLAG=0;
}
}
}
}
}
以上函数仅用于接收数据,不难看出,如果接收多位数据,中断函数会继续中断,执行中断函数,直到接收完成,我们定义的接收数组一定要是全局变量,不然会被刷新。
void clear_USART()
{
uint8_t len=0;
uint8_t i=0;
len = USART_RX_FLAG & 0x3FFF;
USART_SendString(USART1, "发送消息:\n");
for(i=0; i<len;i++)
{
// 向串口发送数据
USART_SendData(USART1, USART_RX_BUF[i]);
//等待发送结束
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)!=SET);
}
USART_SendString(USART1, "\n\n");
USART_RX_FLAG=0;
memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));
}
当接收字符串时,我们需要知道串口接收的数据时什么情况,所以我们需要一个调试函数,来帮助我们了解串口接收的情况,有没有按照我们想要的进度工作。
int main(void)
{
uint8_t len=0;
uint8_t i=0;
uint8_t flag=0;
// USART初始化
USART_Config();
while(1)
{
clear_USART();
if(strcmp((char *)USART_RX_BUF,"stop stm32!")==0)
{
USART_SendString(USART1, "stm32已停止发送!");
flag=0;
break;
}
if(strcmp((char *)USART_RX_BUF,"go stm32!")==0)
{
flag=1;
}
clear_USART();
// "hello windows!\n\r"
if(flag==1)
{
USART_SendString(USART1, "hello windows!\r\n");
delay_ms(800);
if(strcmp((char *)USART_RX_BUF,"go stm32!stop stm32!")==0)
{
USART_SendString(USART1, "stm32已停止发送!\r\n");
flag=0;
break;
}
}
}
}
以上是main函数部分
补充部分引脚定义
//串口1-USART1
#define DEBUG_UARTx USART1;
#define DEBUG_UART_CLK RCC_APB2Periph_USART1
#define DEBUG_UART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_UART_BAUDRATE 115200
//USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQHandler
void DEBUG_UART_Config(void);
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void delay_ms(uint16_t delay_ms);
如果我们用的是hal库,这些在stm32f1xx_hal.h文件中都可以找到映射。
3.2结果展示
将上述代码编译生成后,结果如下
串口字符串
四、总结
本次学习的难度比较大,耗时较长。客观上说,中断本身对初学者就比较复杂,尤其在刚接触hal库后,对自动生成的诸多函数不甚明解,不明所以。个人认为,从hal库出发学习stm32也入门还是比较难的。继续努力吧。
五、参考
1.https://www.bilibili.com/video/BV1th411z7sn/?spm_id_from=333.337.search-card.all.click
2.https://blog.csdn.net/qq_43279579/article/details/110138564
3.https://blog.csdn.net/weixin_51102592/article/details/121361597
4.https://blog.csdn.net/weixin_43793181/article/details/109134705