Embedded System Development 11 - Advanced Interrupt and Serial Communication

This article mainly introduces stm32 interrupt, DMA communication principle and programming method. On the basis of theoretical study, use stm32tubemx and HAL library to complete STM32 interrupt mode lighting, serial communication in interrupt mode and serial communication programming practice in DMA mode.

1. What is an interruption

An interrupt is generally defined as an event that changes the order in which the processor executes instructions. Such events correspond to electrical signals generated by external hardware circuits within the CPU chip.

Interruption means that during the execution of the computer, any unusual or unexpected urgent processing event occurs in the system, causing the CPU to temporarily interrupt the currently executing program and turn to execute the corresponding event processing program, and return to the original program after the processing is completed The process of continuing execution where it was interrupted or scheduling a new process to execute .

Interrupts are divided into synchronous interrupts and asynchronous interrupts .

Synchronous interrupt - synchronous interrupt is generated by the control unit when the instruction is executed, it is called synchronous because the CPU will issue an interrupt only after an instruction terminates execution Asynchronous interrupt
- asynchronous interrupt is generated by other hardware devices according to the CPU The clock signal is randomly generated.

Usually what we call an interrupt refers to an asynchronous interrupt, and we refer to a synchronous interrupt as an exception. (An exception is generated by a bug in the program, or by an exceptional condition that the kernel has to handle)

Features:

  • The application does not need to care about the occurrence and processing of interrupts
  • The interrupt service routine does not have to care about the execution status of the application
  • Interruption is the "separation boundary" between "upper layer application" and "lower layer code"
    insert image description here

The role of interrupts
According to the definition of interrupts, we can use interrupts to cause the processor to switch to running code outside the normal control flow.

When the interrupt signal reaches the elbow, the CPU must stop what it is currently doing, and switch to another activity. In order to do this, the current value of the program counter (the contents of the register) is saved in the kernel state heap, and the address related to the type of interrupt is put into the program counter.
insert image description here

Interrupt handling means that the CPU responds to the interrupt, transfers to the interrupt handler, and the system starts to process the interrupt.
Interrupt response means that the CPU turns to the corresponding event handler after receiving the interrupt request.

After the interrupt is turned on, the system can respond to other interrupts. After the interrupt is turned off, the system does not respond to other interrupts unless the priority is high.

Interrupt masking means that after an interrupt request is generated, the system uses software to selectively block some interrupts and allow the rest of the interrupts to still be responded.

Types of Interrupts and Types of Priority
Interrupts

  • Hard interrupt: an interrupt generated by the processor interrupt signal line
  • Soft interrupt: interrupt triggered by illegal instruction or special instruction

interrupt priority

  • When multiple interrupts occur at the same time, the processor responds to the higher priority interrupt first
  • When the ISR of the low priority interrupt is executed, it can be interrupted again by the high priority interrupt
  • ISR has a higher execution priority than App Code
    insert image description here

2. STM32 external interrupt mode control LED on and off

1. Task requirements

Use the GPIOA terminal of the stm32F103 core board to connect one pin to one LED, and one pin to one GPIOB port to connect to a switch (replaced by Dupont line simulation). Using interrupt mode programming, when the switch is connected to a high level , the LED lights up ; when the switch is connected to a low level, the LED turns off .

2. Design ideas

The core board I choose here is the smallest core board of STM32F103C8T6

Set PA5 to connect to LED , PB5 to simulate switch, connect high and low levels through PB5 to simulate switch breaking

A5 output controls the light on and off, set it as GPIO_Output
B5 analog switch, set it as GPIO_EXTI5
A0 continuously outputs high level, set it as GPIO_Output , when switch B5 is connected to A0 , LED lights up
A2 continuously outputs low level, set it as GPIO_Output , when the switch B5 is connected to A2 , the LED is off

3. Create a project

chip select

  • Open STM32CubeMX , click ACCESS TO MCU SELECTOR
    insert image description here

  • Select the corresponding chip, here I use STM32F103C8T6 , enter STM32F103C8 in the search box , double-click the found chip
    insert image description here

peripheral settings

  • Set SYS , click System Core -> SYS , change Debug to Serial Wire
    insert image description here

  • Set the indicator LED pin PA5 , set the pin mode to output mode GPIO_Output , for the PA5 pin corresponding to the LED , set the name to LED , and other defaults
    insert image description here

  • Set the button pin PB5 , set the pin to the external interrupt function, connect PB5 to the external interrupt line EXIT5 , set it to GPIO_EXIT5 , and set the GPIO mode to External Interrupt Mode with Rising/Falling edge trigger detection , that is, both rising and falling edges can be used trigger, the name is set to SWITCH
    insert image description here

External Interrupt Mode with Rising edge trigger detectionRising edgeExternal
Interrupt Mode with Falling edge trigger detectionFalling edgeExternal
Interrupt Mode with Rising/Falling edge trigger detectionRising edge and falling edge

  • Set A0 as GPIO_Output , output high levelinsert image description here

  • Set A2 as GPIO_Output , output low level continuously, the default setting here is fine
    insert image description here

  • Enable the corresponding external interrupt line, click Enabled
    insert image description here

Configure interrupt priority

  • In most cases, it is not necessary to set the interrupt priority, and directly use the default interrupt priority set by the interrupt number
    insert image description here

clock setting

  • Set HCLK to 36MHz
    insert image description here

generate project

  • Set the project name, storage path and compilation environment
    insert image description here
    insert image description here

4. Code writing

  • Open the generated project and find stm32f1xx_it.c
    insert image description here

  • Find the function EXTI9_5_IRQHandler , right-click the statement HAL_GPIO_EXTI_IRQHandler , select Go to Definition of '… , or directly press F12 to jump to this function
    insert image description here

  • At this time, the following information will be prompted, and the system will prompt that the function cannot be foundinsert image description here

  • The repair method is very simple, click the magic wand, select the Browse Information ditch, then rebuild and recompile again
    insert image description here
    insert image description here

  • At this time, if you jump again, you can successfully jump to this function. HAL_GPIO_EXTI_IRQHandler is an interrupt service function. When a rising or falling edge is captured, it will trigger an interrupt and enter this function.
    insert image description here

  • Then the HAL_GPIO_EXTI_Callback (GPIO_Pin) function will be executed . This function is a callback function. You can find this function by looking down. When we open it, we can find that there is a weak in front. The preceding __weak indicates that this function is a virtual function, and this function is rewritten for the user, where different processing can be performed according to different interrupts. Here we need to realize the transition of A5 level according to the different interrupts of B5, so as to realize the LED on and off.
    insert image description here

  • Replace the function with the following piece of code
    insert image description here
    Function code:

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_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//把A5变为低电位,灯灭
	
	//高电位
	else
		HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//把A5变为高电位,灯亮
	}
}
  • Here you will be prompted that there are many errors in the code. This is because the pin names defined in cubeMX are all defined in main.h in the project , but the header file main.h is not included in stm32f1xx_hal_gpio.c
    insert image description here
    insert image description here

  • At this point, you only need to add a sentence in front of stm32f1xx_hal_gpio.c , then there is no error here#include "main.h"
    insert image description here
    insert image description here

5. Compile

  • Click the magic wand, check Create HEX File under Output
    insert image description here

  • Change the compiler to Version 5 under Target
    insert image description here

  • Select Use Simulator under Debug , change Dialog DLL to DARMSTM.DLL , and change Paremeter to -pSTM323F103C8
    insert image description here

  • Click Rebuild to compile
    insert image description here

6. Hardware connection

It is divided into the connection of the burning circuit and the connection of the experimental circuit

Connection of programming circuit:
connection method:

USB to TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

insert image description here

Note that BOOT0 on the core board is set to 1, and BOOT1 is set to 0
insert image description here

The connection of the experimental circuit:

Export port destination port
3.3V LED anode
A5 LED cathode
B5 A0/A2

insert image description here

7. Burning

  • Connect the hardware to the computer serial port, open the FlyMcu burning software, find the generated hex file, click to open
    insert image description here

  • search serial port
    insert image description here

  • Set the baud rate to 115200bps
    insert image description here

  • Click to start programming and burn
    insert image description here

8. Operation effect

One pin of the GPIOB5 port is connected to a switch (replaced by DuPont line analog). When the switch B5 is connected to A2 , the input is high , and the LED lights up ; when the switch B5 is connected to A0 , the input is low , and the LED is off .

Realize the effect:

STM32 external interrupt mode control LED on and off (floating and flashing)

It can be found that when B5 is not connected to the input, the LED light will keep flashing. This is because the value of the GPIO is uncertain when it is suspended. It must be pulled up or pulled down. Some microcontrollers have integrated pull-up or pull-down resistors inside. There is no need to add resistors in the peripheral circuit. So, I made the following improvements

9. Improvement

When using CubeMX to configure STM32 , set PB5 to Pull-Up , that is, set it to pull-down. When the input is not connected, the default output is high level, no interrupt is triggered, the LED remains off, and other steps remain unchanged.
insert image description here

Realize the effect:

STM32 external interrupt mode realizes switch LED (floating and extinguishing)

3. Introduction of HAL library UART function library

UART structure definition

UART_HandleTypeDef huart1;

1. Serial port send/receive function

	HAL_UART_Transmit();串口发送数据,使用超时管理机制
	HAL_UART_Receive();串口接收数据,使用超时管理机制
	HAL_UART_Transmit_IT();串口中断模式发送
	HAL_UART_Receive_IT();串口中断模式接收
	HAL_UART_Transmit_DMA();串口DMA模式发送
	HAL_UART_Transmit_DMA();串口DMA模式接收

Description:
Send data through the serial port

	HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

Function: Serial port sends data of specified length. If the timeout is not completed, it will no longer send and return the timeout flag (HAL_TIMEOUT)

parameter:

UART_HandleTypeDef *huart The alias of UATR is as follows: UART_HandleTypeDef
huart1: alias is huart1
*pData: the data to be sent
Size: the number of bytes to be sent
Timeout: the maximum sending time, the sending data exceeds this time and quits sending

Example:

	HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);
	//串口发送三个字节数据,最大传输时间0xffff

2. Serial interrupt function

	HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数
	HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数
	HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少)
	HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数
	HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
	HAL_UART_ErrorCallback(); //串口接收错误函数

Serial port receiving interrupt callback function

	HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

Function: After the interrupt of the HAL library is completed, it will not exit directly, but will enter the interrupt callback function, where the user can set the code. After the serial port interrupt reception is completed, it will enter this function. This function is an empty function. You need to modify it yourself.

parameter:

UART_HandleTypeDef *alias of huart UATR
such as: UART_HandleTypeDef huart1; alias is huart1

Example:

HAL_UART_RxCpltCallback(&huart1)
{
    
     
	//用户设定的代码 
}

Serial port interrupt handler

	HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

Function: Judging and processing the received data to judge whether it is a sending interrupt or a receiving interrupt, and then send and receive data, and use it in the interrupt service function.
If data is received, the receiving interrupt processing function will be performed

/* UART in mode Receiver
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
	UART_Receive_IT(huart);
}

If data is sent, the send interrupt processing function will be performed

/* UART in mode Transmitter
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
	UART_Transmit_IT(huart);
	return;
}

3. Serial query function

	HAL_UART_GetState(); 
	//判断UART的接收是否结束,或者发送数据是否忙碌

example

while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX)
//检测UART发送结束

4. Interrupt mode to realize serial port communication

1. Task requirements

Use the serial port interrupt method to redo the serial port communication work last week, and achieve respectively:
1) When the stm32 receives the character "s", it stops sending "hello windows!"; when it receives the character "t", it continues to send "hello windows!" windows!" (reminder: use a global scalar as a semaphore);
2) When stm32 receives the character "stop stm32!", stop sending "hello windows!"; when receiving the character "go stm32!", keep sending "hello windows!" (Hint: save the received continuous characters into a character array for discriminative matching).

2. Create a project

  • Open STM32CubeMX , click ACCESS TO MCU SELECTOR
    insert image description here

  • Select the corresponding chip, here I use STM32F103C8T6 , enter STM32F103C8 in the search box , double-click the found chip
    insert image description here

  • set RCC
    insert image description here

  • Set SYS, change Debug to Serial Wire
    insert image description here

  • Set USART1, set MODE to asynchronous communication, baud rate to 115200 Bits/s, transmission data length to 8 Bit, no parity check, stop bit 1
    insert image description here

  • Set the NVIC, click the NVIC Settings column to enable the receive interrupt
    insert image description here

  • Modify the project name, storage location and compiler version to create a project
    insert image description here
    insert image description here

  • Open the project, click Open Project
    insert image description here

3. Code writing

Define global variables before the main function:

char c;//指令 s:停止  t:开始
char hello[]="hello windows!\n";//输出信息
char tips[]="CommandError\n";//提示1,命令错误
char tips1[]="Start.....\n";//提示2,开始传输
char tips2[]="Stop......\n";//提示3,停止传输
int flag=0;//标志 s:停止发送 t:开始发送

insert image description here

Set the receiving interrupt in the main function:
function description:
function prototype

 HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

Function

Function: serial port interrupt reception, receive specified length data in interrupt mode.
The general process is to set the data storage location, receive the data length, and then enable the serial port receiving interrupt.
When data is received, a serial port interrupt will be triggered.
Then, the serial port interrupt function is processed until the specified length of data is received, then
the interrupt is closed, and the interrupt receiving callback function is entered, and the receiving interrupt is no longer triggered. (Only trigger an interrupt)

parameter

UART_HandleTypeDef *huart Alias ​​of UATR
huart1 *pData Storage address of received data
Size Number of bytes received

HAL_UART_Receive_IT(&huart1, (uint8_t *)&c, 1);

insert image description here

Add the transmission code in the while loop in the main function:

	if(flag==1){
    
    
			//发送信息
			HAL_UART_Transmit(&huart1, (uint8_t *)&hello, strlen(hello),0xFFFF); 
			
			//延时
			HAL_Delay(1000);
		}

insert image description here

The strlen() function is used here , you need to add a sentence in front of main.c#include "string.h" to declare the header file containing the strlen() function
insert image description here

Rewrite the interrupt handler function below the main function:

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);  
}

insert image description here

** main.c modify part of the code: **

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>

void SystemClock_Config(void);

char c;//指令 s:停止  t:开始
char hello[]="hello windows!\n";//输出信息
char tips[]="CommandError\n";//提示1,命令错误
char tips1[]="Start.....\n";//提示2,开始传输
char tips2[]="Stop......\n";//提示3,停止传输
int flag=0;//标志 s:停止发送 t:开始发送


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 *)&hello, strlen(hello),0xFFFF); 
			
		//延时
		HAL_Delay(1000);
		}
	}
}

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);  
}
/* 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 */
}

4. Compile

  • Click the magic wand, check Create HEX File under Output
    insert image description here

  • Change the compiler to Version 5 under Target
    insert image description here

  • Select Use Simulator under Debug , change Dialog DLL to DARMSTM.DLL , and change Paremeter to -pSTM323F103C8
    insert image description here

  • Click Rebuild to compile
    insert image description here

5. Hardware connection

connection method:

USB to TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

insert image description here

Note that BOOT0 on the core board is set to 1, and BOOT1 is set to 0
insert image description here

6. Burning

Open FlyMcu for burning
insert image description here
insert image description here

7. Operation effect

  • Open the serial port debugging assistant SSCOM, open and configure the serial port settings
    insert image description here

  • Click to open the serial port
    insert image description here

  • Here you must cancel the check and add carriage return and line feed, otherwise the following situation will occur, because after checking, the serial port assistant will add carriage return and line feed after sending data, and then the normal function will not be realized
    insert image description here

  • Enter "t" in the send box , click send, the computer sends the letter "t" to the stm32 , and the stm32 outputs a prompt message after receiving it, and starts sending data, hello windows every second !
    insert image description here

  • Enter "s" in the send box , click send, the computer sends the letter "s" to stm32 , and stm32 outputs a prompt message after receiving it, and stops sending data
    insert image description here

  • Send "t" and "s" again , stm32 can still send and receive information
    insert image description here

Realize the effect:

Interrupt to achieve serial communication (basic version)

8. Advanced exercises

When stm32 receives the character "stop stm32!", stop sending "hello windows!" continuously; when receiving the character "go stm32!", continue sending "hello windows!" into a character array for discriminant matching).

(1) To create a new project, just create a new one according to the above serial communication
insert image description here
insert image description here

(2) Write the following code in mian.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "string.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* 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;

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    
    
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  if(flag == 1)
	  {
    
    
	  	printf("ppm Hello windows!\r\n");
	  }
	  
	  else
	  {
    
    
		  //printf("stop stm32 NO!\r\n");
	  }
		HAL_Delay(500);
  }
  /* USER CODE END 3 */
}

/**
  * @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_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();
  }

  /** 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_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();
  }
}

/* USER CODE BEGIN 4 */
/* USER CODE BEGIN 4 */
/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    
    
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
	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);   //ÔÙ¿ªÆô½ÓÊÕÖжÏ
}
/* USER CODE END 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 */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
    
    
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

(3) Compile and burn
insert image description here
insert image description here

(4) Realize the effect
insert image description here
Realize the effect:

Interrupt Communication (Advanced)

5. Introduction to DMA

What is DMA?
We know that the CPU has many functions such as data transfer, calculation, and control program transfer. The core of the system operation is the CPU.

CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?

因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理。
insert image description here

DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。

直接存储器访问 (DMA)
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
DMA传输过程的初始化和启动由CPU完成,传输过程由DMA控制器来执行,无需CPU参与,从而节省CPU资源,提高利用率。

DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

  • 外设到内存
  • 内存到外设
  • 内存到内存
  • 外设到外设

普通模式:
传输结束后(即要传输数据的数量达到零),将不再产生DMA操作。若
开始新的DMA传输,需在关闭DMA通道情况下,重新启动DMA传输。
循环模式:
可用于处理环形缓冲区和连续数据流(例如ADC扫描模式)。当激活循
环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值
进行加载, 并继续响应DMA请求。

DMA传输参数
数据传输需要:
1 数据的源地址
2 数据传输位置的目标地址
3 传递数据多少的数据传输量
4 进行多少次传输的传输模式

当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。
  
也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。  
insert image description here

DMA数据传输的四个要素
① 传输源 :DMA数据传输的来源
② 传输目标:DMA数据传输的目的
③ 传输数量:DMA传输数据的数量
④ 触发信号:启动一次DMA数据传输的动作

DMA控制器特点

STM32F411微控制器具备两个DMA控制器:DMA1和DMA2,每个控制器有8个数据流,每个数据流可以映射到8个通道(或请求);

每一个DMA控制器用于管理一个或多个外设的存储器访问请求,并通过总线仲裁器来协调各个DMA请求的优先级;

数据流(stream)是用于连接传输源和传输目标的数据通路,每个数据流可以配置为不同的传输源和传输目标,这些传输源和传输目标称为通道(Channel);

具备16字节的FIFO。使能FIFO功能后,源数据先送入FIFO,达到FIFO的触发阈值后,再传送到目标地址。

insert image description here
insert image description here

DMA工作框图
insert image description here
上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

有DMA的情况下,ADC采集的数据是怎样存放到SRAM中的?
insert image description here
在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。

总之,每次DMA传送由3个操作组成:

  • 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  • 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  • 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

DMA方式的接口函数
insert image description here
insert image description hereinsert image description here

六、STM32采用DMA方式实现串口通信

1、任务要求

STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据。

2、创建工程

  • 打开 STM32CubeMX,点击 ACCESS TO MCU SELECTOR
    insert image description here

  • 选择对应的芯片,这里我用到的是 STM32F103C8T6,在搜索框输入 STM32F103C8,双击搜索到的芯片
    insert image description here

  • 设置RCC
    insert image description here

  • 设置串口
    insert image description here

  • 使能中断
    insert image description here

  • DMA设置
    点击 DMA Settings 的Add添加通道,传输速率设置为中速 Medium
    insert image description here

  • 模式设置为Normal,右侧选择Memory
    insert image description here

  • 在System view下选择DMA,点击Add,添加MEMTOMEM
    insert image description here

  • 时钟设置
    insert image description here

  • 设置好工程名称、存储路径和编译器版本之后,导出Keil工程文件即可
    insert image description here
    insert image description here

  • 打开工程
    insert image description here

3、代码编写

main.c 文件添加如下代码

uint8_t hello[] = "hello windows!\n";  //定义数据发送数组

insert image description here

在主函数 while 循环里面加入如下代码:

HAL_UART_Transmit_DMA(&huart1, (uint8_t *)hello, sizeof(hello));
HAL_Delay(1000);

insert image description here

4、编译

  • 点击魔法棒,在 Output 下勾选 Create HEX File
    insert image description here

  • Target 下将编译器改为 Version 5
    insert image description here

  • Debug下选择 Use Simulator,将 Dialog DLL 改为 DARMSTM.DLL,将 Paremeter 改为 -pSTM323F103C8
    insert image description here

  • 点击 Rebuild 进行编译
    insert image description here

5、硬件连接

连接方法:

USB转TTL STM32F103C8T6
GND G
3V3 3V3
RXD PA9
TXD PA10

insert image description here

注意将核心板上的BOOT0设置为1,BOOT1设置为0
insert image description here

6、烧录

打开 FlyMcu 进行烧录
insert image description here
insert image description here

7、运行效果

  • 打开串口调试助手 SSCOM,打开并配置串口设置
    insert image description here

  • 点击打开串口,开始以DMA方式进行向上位机发送"hello windows!"
    insert image description here
    实现效果:

    DMA串口通信

七、总结

This article mainly talks about stm32 interrupt, DMA communication principle and programming method, and on the basis of theoretical study, by using stm32tubemx and HAL library, complete the programming of STM32 interrupt mode lighting, interrupt mode serial communication and DMA mode serial communication practise. Embedded learning can't just learn theoretical knowledge without hands-on practice, and you can't type code every day without understanding the principles at all. You have to have both. Only by mastering theoretical knowledge and practical methods at the same time can we learn embedded technology well. I hope that everyone will pay equal attention to theory and practice in the process of learning embedded technology. Able to guide practice with theory and use practical operation to deepen understanding of theoretical knowledge, the two complement each other.


Reference list:
1. stm32 external interrupt mode control light on and off
2. HAL library interrupt mode for serial port communication
3. stm32hal library serial port DMA transceiver
4. [STM32] HAL library STM32CubeMX tutorial eleven - DMA (serial port DMA sending and receiving)
5. 【 Embedded 12] DMA communication principle and programming experiment, DMA method continuously sends data to the upper computer
6. [Embedded 11] HAL library experiment interrupts switch lighting and serial port communication

Guess you like

Origin blog.csdn.net/qq_52068373/article/details/127397152