32单片机从入门到精通之硬件架构——总线系统(二)
一个真正强大的人,不会把太多心思花在取悦和亲附别人上面,所谓的圈子、资源,都只是衍生品,最重要的是提高自己的内功。
你要默默做好你该做的事情,等你变得足够优秀时,你想要的都会主动来找你,你会发现身边都是好人。
很喜欢的一段话:梧高凤必至,花香蝶自来人终向前走,花自向阳开,弱的时候,坏人最多。这个世界的温柔,来自于你的强大!
目录
上一篇博客习题讲解
选择题:
简答题:
填空题:
编程题
代码编写:
中断处理:
ADC应用:
应用设计题
项目设计:
优化建议:
开放性问题
讨论题:
创新应用:
总线系统知识点详解及代码案例
1. AHB (Advanced High-performance Bus)
2. APB1/APB2 (Advanced Peripheral Bus)
3. DMA (Direct Memory Access)
一张试卷
一、选择题(每题2分,共10分)
二、简答题(每题10分,共30分)
三、填空题(每题2分,共10分)
四、编程题(每题20分,共40分)
五、应用设计题(每题15分,共15分)
上一篇博客习题讲解
选择题:
- ARM Cortex-M系列中,哪一个内核具有浮点运算单元(FPU)?
- A) Cortex-M0
- B) Cortex-M3
- C) Cortex-M4 (正确答案)
- D) Cortex-M7 (也正确,但M4是最早引入FPU的版本)
简答题:
-
请简要描述STM32 MCU中GPIO引脚的几种工作模式,并举例说明每种模式的应用场景。
STM32 MCU中的GPIO引脚有多种工作模式,主要包括以下几种:
- 输入模式(Input Mode):分为上拉输入、下拉输入和浮动输入。例如,读取按钮状态时可以使用上拉或下拉输入。
- 输出模式(Output Mode):包括推挽输出和开漏输出。推挽输出用于驱动LED等需要高电平和低电平的设备;开漏输出则常用于I²C通信。
- 复用功能模式(Alternate Function Mode):用于将GPIO引脚配置为外设功能,如USART、SPI、I²C等接口。
- 模拟模式(Analog Mode):允许引脚作为ADC输入端口,用于采集模拟信号。
-
解释什么是DMA (Direct Memory Access),以及它在STM32中的作用。
DMA(直接内存访问)是一种硬件机制,允许数据在内存和其他外设之间直接传输,而无需CPU干预。在STM32中,DMA主要用于加速数据传输,减轻CPU负担,提高系统效率。例如,在进行ADC采样时,DMA可以直接将采样的数据存储到指定的内存缓冲区,使得CPU可以专注于其他任务。
填空题:
-
STM32系列单片机基于______架构,支持多种低功耗模式,如睡眠、停止和待机。
- ARM Cortex-M
-
在STM32中,用于连接传感器、EEPROM等外围设备的两线制同步串行总线称为______。
- I²C
编程题
代码编写:
- 使用STM32 HAL库编写一段代码,配置一个GPIO引脚为输入模式,并读取其状态。如果该引脚处于高电平,则点亮另一个引脚上的LED;否则熄灭LED。
#include "stm32f4xx_hal.h" // 定义引脚 #define BUTTON_PIN GPIO_PIN_0 #define LED_PIN GPIO_PIN_1 #define GPIO_PORT GPIOD void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { // 读取按钮状态 if (HAL_GPIO_ReadPin(GPIO_PORT, BUTTON_PIN) == GPIO_PIN_SET) { // 如果按钮处于高电平,点亮LED HAL_GPIO_WritePin(GPIO_PORT, LED_PIN, GPIO_PIN_SET); } else { // 否则熄灭LED HAL_GPIO_WritePin(GPIO_PORT, LED_PIN, GPIO_PIN_RESET); } } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOD_CLK_ENABLE(); // 启用GPIOD时钟 // 配置LED引脚为推挽输出模式 GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct); // 配置按钮引脚为上拉输入模式 GPIO_InitStruct.Pin = BUTTON_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct); }
中断处理:
- 编写一段代码,设置一个外部中断,当按下按钮时触发中断服务程序(ISR),并在其中切换LED的状态。确保正确配置NVIC以设定中断优先级。
#include "stm32f4xx_hal.h" // 定义引脚 #define BUTTON_PIN GPIO_PIN_0 #define LED_PIN GPIO_PIN_1 #define GPIO_PORT GPIOD void SystemClock_Config(void); static void MX_GPIO_Init(void); void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); extern void Error_Handler(void); volatile uint8_t led_state = 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { // 主循环 } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; EXTI_HandleTypeDef hexti = {0}; __HAL_RCC_GPIOD_CLK_ENABLE(); // 启用GPIOD时钟 // 配置LED引脚为推挽输出模式 GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct); // 配置按钮引脚为外部中断模式 GPIO_InitStruct.Pin = BUTTON_PIN; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发 GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct); // 配置NVIC优先级 HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == BUTTON_PIN) { // 切换LED状态 led_state = !led_state; HAL_GPIO_WritePin(GPIO_PORT, LED_PIN, led_state ? GPIO_PIN_SET : GPIO_PIN_RESET); } } void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(BUTTON_PIN); }
ADC应用:
- 设计并实现一个简单的模拟信号采集系统,使用STM32的ADC模块读取电压值,并通过串口输出到电脑显示。要求能够调整采样率和分辨率。
#include "stm32f4xx_hal.h" #include <stdio.h> #define ADC_CHANNEL ADC_CHANNEL_0 #define USARTx USART2 #define USARTx_CLK_ENABLE() __HAL_RCC_USART2_CLK_ENABLE() #define USARTx_TX_PIN GPIO_PIN_2 #define USARTx_RX_PIN GPIO_PIN_3 #define USARTx_GPIO_PORT GPIOA #define USARTx_AF USART_AF7_USART2 #define BUFFER_SIZE 10 uint16_t adc_value; char buffer[20]; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART_Init(void); static void MX_ADC_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART_Init(); MX_ADC_Init(); // 开始ADC转换 HAL_ADC_Start(&hadc); while (1) { // 触发一次转换 if (HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY) == HAL_OK) { // 获取转换结果 adc_value = HAL_ADC_GetValue(&hadc); // 将ADC值转换为字符串并发送到串口 sprintf(buffer, "ADC Value: %d\r\n", adc_value); HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); } HAL_Delay(1000); // 模拟采样率调整 } } static void MX_ADC_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc.Instance = ADC1; hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.ScanConvMode = DISABLE; hadc.Init.ContinuousConvMode = ENABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = 1; hadc.Init.DMAContinuousRequests = DISABLE; hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV; if (HAL_ADC_Init(&hadc) != HAL_OK) { Error_Handler(); } sConfig.Channel = ADC_CHANNEL; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK) { Error_Handler(); } } static void MX_USART_Init(void) { huart2.Instance = USARTx; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; // USART TX/RX 配置 GPIO_InitStruct.Pin = USARTx_TX_PIN | USARTx_RX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = USARTx_AF; HAL_GPIO_Init(USARTx_GPIO_PORT, &GPIO_InitStruct); }
应用设计题
项目设计:
- 设计一个基于STM32的智能环境监控系统,包括温度、湿度、光照强度等参数的监测。详细描述硬件选型、软件架构及数据上传方案。考虑如何通过Wi-Fi模块将数据发送到云端服务器。
硬件选型:
软件架构:
数据上传方案:
优化建议:
- 针对上述智能环境监控系统,提出至少三项性能或功耗方面的优化措施,并解释为什么这些改进是有效的。
开放性问题
讨论题:
- 讨论在嵌入式系统开发过程中,选择使用HAL库还是LL库(Low-Level Library)的影响因素。这两种库各有何优缺点?
HAL库的优点:
HAL库的缺点:
LL库的优点:
LL库的缺点:
创新应用:
- 思考并描述一种新颖的应用场景,充分利用STM32丰富的外设接口特性,解决某个实际问题或改善现有产品的用户体验。
总线系统知识点详解及代码案例
1. AHB (Advanced High-performance Bus)
知识点讲解
代码案例(以ARM Cortex-M系列MCU为例)
#include "stm32f4xx_hal.h" // 初始化AHB总线上的外设(例如,启用GPIO时钟) void AHB_Peripheral_Init(void) { // 启用GPIOA时钟,GPIOA位于AHB总线上 __HAL_RCC_GPIOA_CLK_ENABLE(); } int main(void) { HAL_Init(); // 初始化HAL库 AHB_Peripheral_Init(); // 初始化AHB总线上的外设 // 主循环 while (1) { // 用户代码 } }
代码注释
__HAL_RCC_GPIOA_CLK_ENABLE()
:启用GPIOA时钟。GPIOA是连接在AHB上的一个高速外围设备,因此通过这个函数可以初始化AHB总线上的外设。2. APB1/APB2 (Advanced Peripheral Bus)
知识点讲解
代码案例(以STM32F4系列MCU为例)
#include "stm32f4xx_hal.h" // 初始化APB1和APB2总线上的外设(例如,启用USART2时钟,它位于APB1上) void APB_Peripheral_Init(void) { // 启用USART2时钟,USART2位于APB1总线上 __HAL_RCC_USART2_CLK_ENABLE(); // 启用TIM1时钟,TIM1位于APB2总线上 __HAL_RCC_TIM1_CLK_ENABLE(); } int main(void) { HAL_Init(); // 初始化HAL库 APB_Peripheral_Init(); // 初始化APB总线上的外设 // 主循环 while (1) { // 用户代码 } }
代码注释
__HAL_RCC_USART2_CLK_ENABLE()
:启用USART2时钟。USART2是一个典型的低速外围设备,位于APB1总线上。__HAL_RCC_TIM1_CLK_ENABLE()
:启用TIM1时钟。TIM1是一个中速外围设备,位于APB2总线上。3. DMA (Direct Memory Access)
知识点讲解
代码案例(以STM32F4系列MCU为例)
#include "stm32f4xx_hal.h" #define BUFFER_SIZE 10 uint16_t ADC_Buffer[BUFFER_SIZE]; // 初始化DMA并配置ADC进行DMA传输 void DMA_ADC_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); // 启用DMA2时钟 // 配置DMA通道 DMA_HandleTypeDef hdma_adc; hdma_adc.Instance = DMA2_Stream0; // 使用DMA2 Stream0 hdma_adc.Init.Channel = DMA_CHANNEL_0; hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc.Init.MemInc = DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式 hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; if (HAL_DMA_Init(&hdma_adc) != HAL_OK) { // 错误处理 Error_Handler(); } // 将DMA流与ADC关联 __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); // 配置ADC ADC_ChannelConfTypeDef sConfig = {0}; hadc.Instance = ADC1; hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.ScanConvMode = DISABLE; hadc.Init.ContinuousConvMode = ENABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = 1; hadc.Init.DMAContinuousRequests = ENABLE; hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV; if (HAL_ADC_Init(&hadc) != HAL_OK) { // 错误处理 Error_Handler(); } sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK) { // 错误处理 Error_Handler(); } // 开始DMA传输 HAL_ADC_Start_DMA(&hadc, (uint32_t*)ADC_Buffer, BUFFER_SIZE); } int main(void) { HAL_Init(); // 初始化HAL库 DMA_ADC_Init(); // 初始化DMA并配置ADC // 主循环 while (1) { // 用户代码 } } void Error_Handler(void) { // 错误处理代码 while (1) { // 停留在这里 } }
代码注释
__HAL_RCC_DMA2_CLK_ENABLE()
:启用DMA2时钟,DMA2负责管理DMA传输。DMA_HandleTypeDef
:定义DMA句柄,用于配置和控制DMA操作。HAL_DMA_Init()
:初始化DMA句柄,配置DMA传输参数。__HAL_LINKDMA()
:将DMA流与ADC关联,确保DMA可以直接从ADC获取数据。HAL_ADC_Init()
和 HAL_ADC_ConfigChannel()
:初始化ADC,并配置ADC通道。HAL_ADC_Start_DMA()
:启动ADC与DMA之间的数据传输,采用循环模式(DMA_CIRCULAR
),使得每次转换后的数据自动写入指定的内存缓冲区。Error_Handler()
:错误处理函数,当初始化失败时进入死循环。通过上述代码示例,您可以更好地理解如何在实际项目中使用AHB、APB1/APB2和DMA来优化嵌入式系统的性能。这些代码片段展示了如何配置和使用这些总线系统,从而实现高效的数据传输和外围设备管理。
一张试卷
一、选择题(每题2分,共10分)
-
AHB主要用于连接哪种类型的设备?
- A) 低速外围设备
- B) 中速外围设备
- C) 高速设备
- D) 内存映射外设
-
APB1和APB2的主要区别在于什么?
- A) 数据宽度
- B) 工作频率
- C) 支持的外设类型
- D) DMA支持
-
DMA控制器允许数据在内存和其他外设之间直接传输,这减轻了哪个组件的负担?
- A) 外围设备
- B) 存储器
- C) CPU
- D) 总线矩阵
-
STM32中,用于连接传感器、EEPROM等外围设备的两线制同步串行总线称为?
- A) SPI
- B) UART
- C) I²C
- D) CAN
-
下列哪项不是DMA的特点?
- A) 后台数据传输
- B) 需要CPU干预
- C) 内置中断机制
- D) 提高系统吞吐量
二、简答题(每题10分,共30分)
-
简要描述AHB (Advanced High-performance Bus) 的特点及其应用场景。
-
解释什么是DMA (Direct Memory Access),以及它在STM32中的作用。
-
请简要说明APB1和APB2的主要用途,并举例说明它们分别连接哪些类型的外围设备。
三、填空题(每题2分,共10分)
-
STM32系列单片机基于______架构,支持多种低功耗模式,如睡眠、停止和待机。
-
AHB总线的数据宽度通常为______位或______位。
-
DMA可以配置多种传输模式,如单次传输、块传输和______。
-
APB1主要用于连接______速度的外围设备,如定时器、UART、SPI、I²C等。
-
DMA控制器通过内置的______机制,在传输完成时通知CPU。
四、编程题(每题20分,共40分)
-
使用STM32 HAL库编写一段代码,初始化AHB总线上的一个外设(例如,启用GPIOA时钟),并简要注释代码功能。
-
编写一段代码,配置DMA以从ADC模块读取数据,并将数据存储到指定的内存缓冲区。确保正确配置DMA通道、传输模式和优先级。
五、应用设计题(每题15分,共15分)
- 设计一个基于STM32的智能环境监控系统,包括温度、湿度、光照强度等参数的监测。详细描述硬件选型、软件架构及数据上传方案。考虑如何通过Wi-Fi模块将数据发送到云端服务器。
作者:不能只会打代码