前言
-
用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。
-
采用串口中断方式重做上周的串口通信作业,实现:当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”
-
采用串口中断方式重做上周的串口通信作业,分别实现:1)当stm32接收到字符“s”时,停止持续发送“hello windows!”; 当接收到字符“t”时,持续发送“hello windows!”(提示:采用一个全局标量做信号灯);2)当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数
-
STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。
一、用中断实现最小核心板的呼吸灯
1.用STM32CubeMx创建工程
1.1 配置引脚
A4输出控制灯的亮灭,设置为GPIO_Output
A1持续输出高电平,设置为GPIO_Output
A7持续输出低电平,设置GPIO_Output
B5模拟开关,设置为GPIO_EXTI5
1.2 配置Exit,Sys,GPIO
点开NVIC,找到EXIT line[9:5] interrupts后勾选
点开SYS,将Debug改成Serial Wire
点开GPIO,把A1配置为高电平、A7配置为低电平,把B5中断配置为上升沿和下降沿都触发
User Label可以为当前引脚设置一个别名,方便后续自己调用,这里我改为了SWITCH。
1.3 创建工程
点开Project Manager,输入Project文件名,并选择路径,然后点击Code Generator将第二个方格中的第一个勾选,然后点击GENERATE CODE,然后点击OPEN PROJECT跳到keil5
2.用keil调试代码
在目录中找到stm32f1xx_it.c,在HAL_GPIO_EXTI_IRQHandler按F12跳到该函数
找到 -weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)这个函数,将其删除,写上新的进去
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变为高电位
}
}
写进去后,发现SWITCH_GPIO_Port,SWITCH_Pin,LED_A4_GPIO_Port,LED_A4_Pin这些会报错
这是因为我们没有在这个stm32f1xx_hal_gpio.c中定义这些变量,在main.h中找到这些定义复制过去,发现少几个变量,自己定义即可。
然后点击编译,显示编译成功,也无错误警告,并生成了hex文件
3.烧录运行
- 首先,将我们手上的stm32f103c8进行连线,这里,笔者使用的usb转ttl实现,所以首先连接USB转TTL,将USB转TTL的3v3连接上STM32的3v3,groud连接上ground,TXD连接A10,RXD连接A9。
- 将led灯一端连接A4端口,一端连接3.3v
- 笔者这里选的B5为中端口,所以用一根线连接B5,另一端先不连
我们打开flymcu,选择hex文件,开始编程
连线
二、HAL库中断方式进行串口通信
1.使用STM32F103C8配置项目
- 同样,先点击ACCESS TO MCU SELECTOR,选择板子STM32F103C8,然后点击START PROJECT
-
配置RCC
将High Speed Clock(HSE)的那个改成Crystal/Ceramic Reasonator。
-
设置SYS
将Debug改成Serial Wire
-
设置USART
将Mode改成Asynchronous
-
设置NVIC
勾选SART1 global interrupt
-
创建项目
填入Project Name,选择一个非中文路径,将IDE改成MDK-ARM,将Min Version选择V5
2.用Keil配置代码
在STM32CubeMx上选择open project直接跳到keil5
2.1 在main函数前定义全局变量
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.开始发送
2.2 接收中断函数和while循环
函数原型
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能
功能
:串口中断接收,以中断方式接收指定长度数据。
大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。
接收到数据时,会触发串口中断。
再然后,串口中断函数处理,直到接收到指定长度数据
而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)
参数
UART_HandleTypeDef *huart UATR的别名
huart1 *pData 接收到的数据存放地址
Size 接收的字节数
在主函数中添加这个函数
HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1)
;
在while循环中添加如下代码,此代码表示当flag为1时,一直发送信息
if(flag==1){
//发送信息
HAL_UART_Transmit(&huart1, (uint8_t *)&message, strlen(message),0xFFFF);
//延时
HAL_Delay(1000);
}
2.3 重写中断处理函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//当输入的指令为s时,发送提示并改变flag
if(c=='s'){
flag=0;
HAL_UART_Transmit(&huart1, (uint8_t *)&tips2, strlen(tips2),0xFFFF);
}
//当输入的指令为t时,发送提示并改变flag
else if(c=='t'){
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);
}
- 烧录运行后即发现,当发送t后,start,便开始发送hello Windows
- 发送s后,中断stop
- 然后继续发送t,就可以重新开始发送
2.4 改写中断处理函数
要求:
当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”
strcmp(s1, s2)
;
如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
全部代码
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"
/* USER CODE BEGIN PD */
uint8_t aRxBuffer; //½ÓÊÕÖжϻº³å
uint8_t Uart1_RxBuff[256]; //½ÓÊÕ»º³å
uint8_t str1[20] = "stop stm32";
uint8_t str2[20] = "go stm32";
uint8_t Uart1_Rx_Cnt = 0; //½ÓÊÕ»º³å¼ÆÊý
uint8_t cAlmStr[] = "Êý¾ÝÒç³ö(´óÓÚ256)\r\n";
unsigned int flag = 1;
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
while (1)
{
if(flag == 1)
{
printf("go stm32 Hello windows!\r\n");
}
else
{
}
HAL_Delay(500);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
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 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
if (strcmp(Uart1_RxBuff, str1) == 0) flag = 0;
if (strcmp(Uart1_RxBuff, str2) == 0) flag = 1;
//if(Uart1_RxBuff[0]=='g') flag = 1;
//if(Uart1_RxBuff[0]=='s') flag = 0;
if(Uart1_Rx_Cnt >= 255) //Òç³öÅжÏ
{
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff));
HAL_UART_Transmit(&huart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF);
}
else
{
Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer; //½ÓÊÕÊý¾Ýת´æ
if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //ÅжϽáÊøλ
{
HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //½«ÊÕµ½µÄÐÅÏ¢·¢ËͳöÈ¥
Uart1_Rx_Cnt = 0;
memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //Çå¿ÕÊý×é
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //ÔÙ¿ªÆô½ÓÊÕÖжÏ
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
3.烧录实现
用flymcu烧录
用串口调试助手
发送go stm32!即开始发送
发送stop stm32!后中断然后继续发送
三、STM32采用串口DMA
1.DMA简介
一般情况下,存储器与IO设备的数据交互都要通过CPU来进行,而DMA控制器可以实现存储器和IO设备间数据的直接交换。
DMA 传输将数据从一个地址空间复制到另外一个地址空间
。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。
DMA用来提供在
外设和存储器之间或者存储器和存储器之间
的
高速数据传输
。无须CPU的干预,通过DMA数据可以快速地移动。这就
节省了CPU的资源来做其他操作
。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。
在实现DMA传输时,是由DMA控制器直接掌管总线,可以直接控制IO设备与内存之间的数据交换,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
1.1 工作状态和方式
- 被动态(受控器):未取得总线控制权,受CPU的控制
- 主动态(主控器):接管并取得总线控制权,取代CPU而成为系统的主控者
数据块传送方式
:在I/O接口电路中设置一个比较大的数据缓冲区,一般能存放一个数据块,I/O接口电路与内存之间的数据交换以数据块为单位。总线仲裁器判定究竟是DMA控制器还是CPU能获得总线的使用权。
周期挪用方式
:当I/O接口没有DMA请求时,CPU按程序要求访问内存;一旦I/P接口有DMA请求,则I/O接口挪用一个或几个周期。缺点是:数据输入或庶出过程中实际占用了CPU时间。
交替访存方式
:CPU与DMA控制器交替访问内存。不需要总线使用权的申请、建立和归还过程。
效率高,但实现起来有困难,基本上不被使用。
DMA方式
特点
:
1、内存既可以被CPU访问也可以被DMA控制器访问,CPU和DMA控制器会竞争总线的使用权,因而需要仲裁机制
2、外部设备与内存之间的整个数据交换过程全部在DMA控制器的控制下完成,CPU能够与外部设备并行工作,大大提高了效率
3、在DMA方式开始之前CPU要对DMA控制器进行初始化,在DMA方式结束之后,CPU要申请中断,对内存缓冲区进行后处理。
I/O接口电路DMA传送方式:
1.2 DMA传输
数据传输源和目的地都可以寻址整个4 GB区域中的外围设备和存储器,其地址介于0x0000 0000和0xFFFF FFFF之间。传输方向使用DMA_SxCR寄存器中的DIR [1:0]位进行配置,并提供三种可能性:存储器到外围设备,外围设备到存储器或存储器到存储器的传输。
- 外设到存储器模式
- 存储器到外设模式
- 存储器到存储器模式
传输方式
:
1.
DMA_Mode_Normal,正常模式
2.
DMA_Mode_Circular ,循环传输模式
DMA的数据传输不需要占用cpu时间,所以数据传输前需要提前高速高速源和目标传输的数据块(指字节,半字或字)个数。即:设置好DMA 数据流 x 数据项数寄存器(DMA_SxNDTR),里面放置需要传输的数据项的个数(NDTR=numbers of data to transfer).使能数据流后,此寄存器为只读,用于指示要传输的剩余数据项数。每次 DMA 传输后,此寄存器将递减。
普通模式时,传输完成后,此寄存器保持为零,除非将DMA数据流配置寄存器的EN置1。
循环模式时循环模式该寄存器会重载初始值。
原理
:
1.3 DMA主要特征和中断
·每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发,这些功能通过软件来配置。
·在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)。
·独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
·支持循环的缓冲器管理。
·每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
·存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输。
·闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
·可编程的数据传输数目:最大为65535(0xFFFF)。
中断
:
- 每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断;为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。
即使没开启,也可以通过查询这些位来获得当前 DMA 传输的状态。这里常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志
2.用STM32CubeMx创建工程
2.1 新建项目
点击ACCESS TO MCU SELECTOR,在Part Number中选择我们得STM32核心版STM32F103C8,然后点击Start Project
- 设置RCC
-
配置串口
找到USART1设置,将Mode改成
Asynchronous
,然后点击下面
Parameter Settings
,查看波特率,这些是否正确
然后点击
NVIC Setting
,勾选Enabled
然后点击
DMA Settings
,点击
ADD
添加如下两个通道,并将传输速率设置为
中速Medium
。
点击System view,选择DMA,然后点击Add,选择MEMTOMEM.
-
时钟设置
将HCLK改为72,点击回车OK.
-
PROJECT Manager
输入项目名称,全英路径,将IDE改成MDK-ARM,后面改成V5
- Code Generator
配置完成点击GENERATE CODE,点击Open Project即可打开keil5
2.2 用keil调试代码
在main.c的主函数中添加如下代码
uint8_t Senbuff[] = "Hello world!"; //定义数据发送数组
在while循环中添加如下代码
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
HAL_Delay(1000);
然后点击编译,没有错误警告
2.3 硬件连接
我们这里使用USB转TTL进行连接
- 3V3 —> 3V3
- GND —> GND
- RXD —> A9
- TXD —> A10
- 核心板boot0接1 核心板boot1接0
3.烧录运行
3.1 用flymcu烧录
首先点击搜索串口查看我们的串口,然后再下面选择我们之前在keil编译生成的hex文件
点击开始编程,写入途中,USB转TTL上蓝灯会闪烁,停止后即为写入成功
3.2 串口调试助手查看结果
点击打开串口,则会收到Hello world
总结
本篇文章主要学习stm32中断、DMA通信原理和编程方法,用stm32F103核心板的GPIOA端一管脚接一个LED,GPIOB端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,LED亮灯;接低电平时,LED灭灯。并采用串口中断方式来实现hello windows得串口通信,最后了解了DMA,并采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。很多问题,希望大家指导指导。
参考:https://blog.csdn.net/qq_47281915/article/details/121024427
参考:https://blog.csdn.net/Qxiaofei_/article/details/119029425
参考:
https://blog.csdn.net/as480133937/article/details/104827639/
https://blog.csdn.net/m0_56561130/article/details/118702500
https://blog.csdn.net/qq_42604176/article/details/112447972
https://zhuanlan.zhihu.com/p/138573828