使用C和C++进行混合编程

在做毕设过程中,用到了八个相同的传感器,传感器的通信协议都是一样的,固然直接搞八个差不多的文件来驱动传感器是没问题的,但是有一种更简洁的方式,那就是使用C++,来复用通信程序; 参考:https://zhuanlan.zhihu.com/p/115068898 环境说明 使用的单片机芯片是STM32F103C6T6; Keil编译器版本为AC6; Keil的配置如下: 然后是将C++源文件配对的头文件改为使用C++编译器进行编译; 源码编写 注意把main文件的后缀也改为.cpp,否则会出错; 头文件: /* * @Author : fan-pengfei 2253770787@qq.com * @Date : 2023-03-07 13:43:38 * @LastEditors : fan-pengfei 2253770787@qq.com * @LastEditTime : 2023-03-07 14:41:29 * @FilePath : \Core\Inc\ds18b20.h * @Description : * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. */ #ifndef __DS18B20_H #define __DS18B20_H #include "main.h" #include "tim.h" class DS18B20_Class { private: GPIO_TypeDef *BSP_DS18B20_PORT; uint16_t BSP_DS18B20_PIN; float *Temp_Sum; uint8_t LOCATION; public: DS18B20_Class(GPIO_TypeDef *BSP_DS18B20_PORT, uint16_t BSP_DS18B20_PIN, uint8_t LOCATION, float *Temp_data); void delay_us(uint16_t us); GPIO_PinState DS18B20_IN(void); void DS18B20_OUT_1(void); void DS18B20_OUT_0(void); void DS18B20_Mode_OUT_PP(void); void DS18B20_Mode_IN_NP(void); void DS18B20_Reset(void); uint8_t DS18B20_Presence(void); uint8_t DS18B20_ReadBit(void); uint8_t DS18B20_ReadByte(void); void DS18B20_WriteByte(uint8_t dat); void DS18B20_ReadId(uint8_t *ds18b20_id); void DS18B20_SkipRom(void); void DS18B20_MatchRom(void); uint8_t Init(void); float DS18B20_GetTemp_SkipRom(void); float DS18B20_GetTemp_MatchRom(uint8_t *ds18b20_id); void Start_Convert(void); void Get_Data(uint8_t ds18b20_id[64][8]); }; extern "C" { void User_DS18B20_Init(void); void User_Start_Convert(void); void User_Get_Data(void); } #endif 源文件: /* * @Author : fan-pengfei 2253770787@qq.com * @Date : 2023-03-07 13:43:26 * @LastEditors : fan-pengfei 2253770787@qq.com * @LastEditTime : 2023-03-07 16:41:42 * @FilePath : \Core\Src\ds18b20.cpp * @Description : * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. */ #include "ds18b20.h" #include "main.h" #include "DS18B20_ID.h" extern float Temp_Sum[64]; void DS18B20_Class::delay_us(uint16_t us) { tim_delay_us(us); // uint32_t delay; // delay = (1600 * us); // while (delay--) // { // } } GPIO_PinState DS18B20_Class::DS18B20_IN(void) { return HAL_GPIO_ReadPin(BSP_DS18B20_PORT, BSP_DS18B20_PIN); } void DS18B20_Class::DS18B20_OUT_1(void) { HAL_GPIO_WritePin(BSP_DS18B20_PORT, BSP_DS18B20_PIN, GPIO_PIN_SET); } void DS18B20_Class::DS18B20_OUT_0(void) { HAL_GPIO_WritePin(BSP_DS18B20_PORT, BSP_DS18B20_PIN, GPIO_PIN_RESET); } /** * @brief DS18B20 输出模式 */ void DS18B20_Class::DS18B20_Mode_OUT_PP(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = BSP_DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(BSP_DS18B20_PORT, &GPIO_InitStruct); } /** * @brief DS18B20 输入模式 */ void DS18B20_Class::DS18B20_Mode_IN_NP(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = BSP_DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(BSP_DS18B20_PORT, &GPIO_InitStruct); } /** * @brief 主机给从机发送复位脉冲 */ void DS18B20_Class::DS18B20_Reset(void) { DS18B20_Mode_OUT_PP(); // 主机输出 DS18B20_OUT_0(); // 主机至少产生 480us 的低电平复位信号 delay_us(750); DS18B20_OUT_1(); // 主机在产生复位信号后,需将总线拉高 // 从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 delay_us(15); } /** * @brief 检测从机给主机返回的存在脉冲 * @return 0:成功 1:失败 */ uint8_t DS18B20_Class::DS18B20_Presence(void) { uint8_t pulse_time = 0; DS18B20_Mode_IN_NP(); // 主机设为输入 while (DS18B20_IN() && (pulse_time = 100) { return 1; } else { pulse_time = 0; } while (!(DS18B20_IN()) && pulse_time = 240) { return 1; } else { return 0; } } /** * @brief 从DS18B20读取一个bit */ uint8_t DS18B20_Class::DS18B20_ReadBit(void) { uint8_t dat; DS18B20_Mode_OUT_PP(); // 读 0 和读 1 的时间至少要大于 60 us DS18B20_OUT_0(); // 读时间的起始:必须由主机产生 > 1us > 1; // 写 0 和写 1 的时间至少要大于60us if (testb) // 当前位写 1 { DS18B20_OUT_0(); delay_us(5); // 拉低发送写时段信号 DS18B20_OUT_1(); // 读取电平时间保持高电平 delay_us(65); } else // 当前位写 0 { DS18B20_OUT_0(); // 拉低发送写时段信号 delay_us(70); // 读取电平时间保持低电平 DS18B20_OUT_1(); delay_us(2); // 恢复时间 } } } /** * @brief 跳过匹配 DS18B20 ROM */ void DS18B20_Class::DS18B20_SkipRom(void) { DS18B20_Reset(); DS18B20_Presence(); DS18B20_WriteByte(0XCC); /* 跳过 ROM */ } /** * @brief 执行匹配 DS18B20 ROM */ void DS18B20_Class::DS18B20_MatchRom(void) { DS18B20_Reset(); DS18B20_Presence(); DS18B20_WriteByte(0X55); /* 匹配 ROM */ } DS18B20_Class::DS18B20_Class(GPIO_TypeDef *BSP_DS18B20_PORT, uint16_t BSP_DS18B20_PIN, uint8_t LOCATION, float *Temp_data) { DS18B20_Class::BSP_DS18B20_PORT = BSP_DS18B20_PORT; DS18B20_Class::BSP_DS18B20_PIN = BSP_DS18B20_PIN; DS18B20_Class::LOCATION = LOCATION; DS18B20_Class::Temp_Sum = Temp_data; } uint8_t DS18B20_Class::Init(void) { DS18B20_Mode_OUT_PP(); DS18B20_OUT_1(); DS18B20_Reset(); return DS18B20_Presence(); } /** * 存储的温度是16 位的带符号扩展的二进制补码形式 * 当工作在12位分辨率时,其中5个符号位,7个整数位,4个小数位 * * |---------整数----------|-----小数 分辨率 1/(2^4)=0.0625----| * 低字节 | 2^3 | 2^2 | 2^1 | 2^0 | 2^(-1) | 2^(-2) | 2^(-3) | 2^(-4) | * * * |-----符号位:0->正 1->负-------|-----------整数-----------| * 高字节 | s | s | s | s | s | 2^6 | 2^5 | 2^4 | * * * 温度 = 符号位 + 整数 + 小数*0.0625 */ /** * @brief 在跳过匹配 ROM 情况下获取 DS18B20 温度值 * @param 无 * @retval 温度值 */ float DS18B20_Class::DS18B20_GetTemp_SkipRom(void) { uint8_t tpmsb, tplsb; int16_t s_tem; float f_tem; DS18B20_SkipRom(); DS18B20_WriteByte(0X44); /* 开始转换 */ DS18B20_SkipRom(); DS18B20_WriteByte(0XBE); /* 读温度值 */ tplsb = DS18B20_ReadByte(); tpmsb = DS18B20_ReadByte(); s_tem = tpmsb 要注意其中的`extern "C"`的用法; ### Main中调用: > 注意main文件也是cpp后缀; ```c++ void main(void) { User_DS18B20_Init(); while (1) { User_Start_Convert(); HAL_Delay(200); // 更新速率为200ms,等待转换结束 User_Get_Data(); int i = 0; while (i 使用C编写 ![5a011b38ca2d1e989de04f32bb8c0e5](img-5.png) 使用C++编写 ![4dbfdf38abe4f0d85cc7f98e4133007](img-6.png) **C:** Total RO Size (Code + RO Data) 14016 (13.69kB) Total RW Size (RW Data + ZI Data) 2040 (1.99kB) Total ROM Size (Code + RO Data + RW Data) 14464 ( 14.13kB) **C++:** Total RO Size (Code + RO Data) 11780 (11.50kB) Total RW Size (RW Data + ZI Data) 2864 (2.80kB) Total ROM Size (Code + RO Data + RW Data) 12220 ( 11.93kB) > 相比较而言C用的ROM比较多一些,C++用的RAM比较多一些; **可见在某些情况下,使用C++编写代码可以有效缩减代码体积,且代码更易懂;** ### microlib 使用C++编译的话,就没法再使用MicroLIB,因为MicroLIB为非标准的精简库,会与标准C++产生冲突; ### 中断服务程序 如果中断服务程序是异常的,因为stm32的中断入口矢量是按C的方式进入的,因此需要在整个文档的头部和末尾加上extern “C”{}用大括号把整个代码段扩住,这样中断就可以正常的进入了; ### Cubemx Cubemx在生成头文件中已经加入了: ```c #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif 所以在不是其生成的文件中要注意extern "C"的使用; ...

March 7, 2023 · 4 min · Rancho

在GDB下调试STM32的记录

基于VSCode使用GDB来调试STM32,我感觉比那个Keil还好用,而且更懂底层原理; 一、调试步骤: 准备工作 已经熟悉arm gcc工具链; 已经在win中安装好mingw或者arm-none-eabi-gcc工具; 具有合适的代码工程和编译脚本,且编译输出elf文档时,已添加-g选项来生成调试信息; 安装jlink调试工具和对应驱动; 有对应的硬件电路; 1、启动Jlink GDB Server 打开Jlink诸多工具中的Jlink GDB Server并配置好: 启动: 可以看到本地端口为2331,这个一会会用到; 然后就可以把这个窗口最小化了; 2、GDB调试 启动GDB程序: arm-none-eabi-gdb.exe 然后按enter自动进入调试模式; 输入file H743_demo.elf加载调试文档: 然后输入target remote localhost:2331,连接gdb server,连接成功后,会在Jlink GDB server中显示对应的状态,如下所示: 输入monitor reset来复位MCU,从而让MCU处于确定的状态: 输入load往MCU中加载调试文档(是加载进flash,而不是ram),也就是常见的烧录过程: 输入break main设置main断点,让MCU执行到main中停止: 输入c持续运行直至运行到断点处: HAL_Init();是main函数的第一行代码,停在这里; 再次输入c会继续运行; 若要打断持续运行的状态,只需要按下Ctrl+c即可; 3、需要注意的地方 每次程序重新编译都要执行一次load以加载新的elf文档; 如果不使用命令行,而是使用VSCODE中的调试功能,则也需要在程序更新的时候重新load一次; 二、常用命令 1、p(打印) p+变量名:打印变量值: 2、s(单步运行) s:单步运行; 并且可以用 breakpoint+行号进行断点设置; 3、l(列出) 列出当前位置前后共5行程序; 4、watch(变量监视) Watchpoints 是用来告诉 GDB 停止执行某个程序的标记。Watchpoints 与数据相关联:放置监视点需要指定一个表达式来描述变量、多个变量或内存地址。 ...

February 27, 2023 · 1 min · Rancho

基于STM32的CMAKE模板

这是一个基于STM32单片机的模板; 示例中的单片机是STM32H743IIT6,调试器使用JlinkOB; 其中的各个参数可以参考使用STM32CubeMX生成的基于makefile的模板,且后续仍然可以使用STM32CubeMX进行底层代码的构建; 如果需要进行调试,可以先启动J-Link GDB Server,然后使用VSCode进行调试或者直接使用命令行进行调试; # CMAKE_SYSTEM_NAME: 即你目标机target所在的操作系统名称,比如ARM或者Linux你就需要写"Linux"; # 如果Windows平台你就写"Windows",如果你的嵌入式平台没有相关OS你即需要写成"Generic"; # 只有当CMAKE_SYSTEM_NAME这个变量被设置了,CMake才认为此时正在交叉编译; # 它会额外设置一个变量CMAKE_CROSSCOMPILING为TRUE; set(CMAKE_SYSTEM_NAME Generic) # CMAKE_SYSTEM_NAME和CMAKE_SYSTEM_PROCESSOR是交叉编译的时候必须指定的两个参数; # 如果在cmake命令行定义了CMAKE_SYSTEM_NAME,就必须也定义CMAKE_SYSTEM_PROCESSOR; set(CMAKE_SYSTEM_PROCESSOR cortex-m7) #cmake最低版本 cmake_minimum_required(VERSION 3.1.0) #编译工具 set(CROSS_COMPILE_PREFIX arm-none-eabi) # 顾名思义,即C语言编译器,这里可以将变量设置成完整路径或者文档名; # 设置成完整路径有一个好处就是CMake会去这个路径下去寻找编译相关的其他工具; # 比如linker,binutils等,如果你写的文档名带有arm-elf等等前缀; # CMake会识别到并且去寻找相关的交叉编译器; set(CMAKE_C_COMPILER ${CROSS_COMPILE_PREFIX}-gcc) set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_PREFIX}-g++) set(CMAKE_ASM_COMPILER ${CROSS_COMPILE_PREFIX}-gcc) set(CMAKE_OBJCOPY ${CROSS_COMPILE_PREFIX}-objcopy) set(CMAKE_OBJDUMP ${CROSS_COMPILE_PREFIX}-objdump) set(CMAKE_SIZE ${CROSS_COMPILE_PREFIX}-size) # CMake中的命令find_program用于查找程序(program) # 会将查找到的文档路径存在CMakeCache.txt中 find_program(ARM_SIZE_EXECUTABLE ${CROSS_COMPILE_PREFIX}-size) find_program(ARM_GDB_EXECUTABLE ${CROSS_COMPILE_PREFIX}-gdb) find_program(ARM_OBJCOPY_EXECUTABLE ${CROSS_COMPILE_PREFIX}-objcopy) find_program(ARM_OBJDUMP_EXECUTABLE ${CROSS_COMPILE_PREFIX}-objdump) set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # search for program/library/include in the build host directories # 1、CMAKE_FIND_ROOT_PATH_MODE_PROGRAM: 对FIND_PROGRAM()起作用, # 有三种取值,NEVER,ONLY,BOTH, # 第一个表示不在你CMAKE_FIND_ROOT_PATH下进行查找, # 第二个表示只在这个路径下查找, # 第三个表示先查找这个路径,再查找全局路径, # 对于这个变量来说,一般都是调用宿主机的程序,所以一般都设置成NEVER # # 2、CMAKE_FIND_ROOT_PATH_MODE_LIBRARY: 对FIND_LIBRARY()起作用, # 表示在链接的时候的库的相关选项,因此这里需要设置成ONLY来保证我们的库是在交叉环境中找的. # # 3、CMAKE_FIND_ROOT_PATH_MODE_INCLUDE: 对FIND_PATH()和FIND_FILE()起作用, # 一般来说也是ONLY,如果你想改变,一般也是在相关的FIND命令中增加option来改变局部设置 # 有NO_CMAKE_FIND_ROOT_PATH,ONLY_CMAKE_FIND_ROOT_PATH,BOTH_CMAKE_FIND_ROOT_PATH set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) #工程名称 # project命令用于指定cmake工程的名称 # 实际上,它还可以指定cmake工程的版本号(VERSION关键字)、 # 简短的描述(DESCRIPTION关键字)、 # 主页URL(HOMEPAGE_URL关键字)、 # 编译工程使用的语言(LANGUAGES关键字)。 project(H743_demo C CXX ASM) set(target "${PROJECT_NAME}") set(COMPILE_TOOLS GCC) # Target-specific flags #型号 set(MCU_FAMILY STM32H743xx) #布局文档 set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/STM32H743IITx_FLASH.ld) #内核相关 set(CPU "-mcpu=cortex-m7") set(FPU "-mfpu=fpv5-d16") set(FLOAT_ABI "-mfloat-abi=hard") #宏定义 add_definitions(-DUSE_HAL_DRIVER -DSTM32H743xx) # 构建Release或者Debug版本 if(CMAKE_BUILD_TYPE MATCHES Debug) set(DBG_FLAGS "-g3 -gdwarf-2 -O0") elseif(CMAKE_BUILD_TYPE MATCHES Release) set(DBG_FLAGS "-O3") endif() ##file语法,前一个参数是固定的 后面一个参数自己定义 ##添加文档的时候注意 相对路径和绝对路径 file(GLOB_RECURSE DRIVE_SRC Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_cortex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_rcc.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_rcc_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_flash.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_flash_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_gpio.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_hsem.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_dma.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_dma_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_mdma.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_pwr.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_pwr_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_i2c.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_i2c_ex.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_exti.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_tim.c Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_tim_ex.c Core/Src/system_stm32h7xx.c startup_stm32h743xx.s ) file(GLOB_RECURSE USER_SRC Core/Src/main.c Core/Src/gpio.c Core/Src/stm32h7xx_it.c Core/Src/stm32h7xx_hal_msp.c ) # 添加源文件 set(SOURCE_FILES ${DRIVE_SRC} ${USER_SRC}) #添加头文件路径 include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Core/Inc ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Inc ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/STM32H7xx_HAL_Driver/Inc/Legacy ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Device/ST/STM32H7xx/Include ${CMAKE_CURRENT_SOURCE_DIR}/Drivers/CMSIS/Include ) #芯片特性 set(MCU_FLAGS "${CPU} -mthumb ${FPU} ${FLOAT_ABI}") # compiler: language specific flags CFLAGS set(CMAKE_C_FLAGS "${MCU_FLAGS} -std=gnu99 -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} " CACHE INTERNAL "C compiler flags") #CPP set(CMAKE_CXX_FLAGS "${MCU_FLAGS} -fno-rtti -fno-exceptions -fno-builtin -Wall -fdata-sections -ffunction-sections ${DBG_FLAGS} " CACHE INTERNAL "Cxx compiler flags") #ASFLAGS set(CMAKE_ASM_FLAGS "${MCU_FLAGS} -x assembler-with-cpp ${DBG_FLAGS} " CACHE INTERNAL "ASM compiler flags") #LDFLAGS -mcpu=cortex-m0plus -mthumb set(CMAKE_EXE_LINKER_FLAGS "${MCU_FLAGS} --specs=nosys.specs -specs=nano.specs -T${LINKER_SCRIPT} -Wl,-Map=${PROJECT_NAME}.map,--cref -Wl,--gc-sections" CACHE INTERNAL "Exe linker flags") #要链接的库 对应makefile的 LIBS set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-lc -lm -lnosys" CACHE INTERNAL "Shared linker flags") #先定义target 才可以添加define include add_executable(${target}.elf ${SOURCE_FILES}) set(ELF_FILE ${PROJECT_BINARY_DIR}/${target}.elf) set(HEX_FILE ${PROJECT_BINARY_DIR}/${target}.hex) set(BIN_FILE ${PROJECT_BINARY_DIR}/${target}.bin) add_custom_command(TARGET "${target}.elf" POST_BUILD COMMAND ${CMAKE_OBJCOPY} -Obinary ${ELF_FILE} ${BIN_FILE} COMMAND ${CMAKE_OBJCOPY} -Oihex ${ELF_FILE} ${HEX_FILE} COMMENT "Building ${target}.bin and ${target}.hex" COMMAND ${CMAKE_COMMAND} -E copy ${HEX_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${target}.hex" COMMAND ${CMAKE_COMMAND} -E copy ${BIN_FILE} "${CMAKE_CURRENT_BINARY_DIR}/${target}.bin" COMMAND ${CMAKE_SIZE} --format=berkeley ${target}.elf ${target}.hex COMMENT "Invoking: Cross ARM GNU Print Size" ) 使用方式: ...

February 27, 2023 · 3 min · Rancho

STM32启动代码原理分析

STM32启动代码原理分析(底层技术); 简述 ARM Cortex-M系列MCU的启动代码(使用汇编语言编程则不需要)主要做3件事情: 初始化并正确放置异常/中断矢量表; 分散加载; 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)。 Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口矢量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口矢量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断矢量表的位置而起始地址是可变化的。 源码分析 基于STM32F103C6T6的启动文件startup_stm32f103x6.s的简要说明如下: ;******************** (C) COPYRIGHT 2017 STMicroelectronics ******************** ;* File Name : startup_stm32f103x6.s ;* Author : MCD Application Team ;* Description : STM32F103x6 Devices vector table for MDK-ARM toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Configure the clock system ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the Cortex-M3 processor is in Thread mode, ;* priority is Privileged, and the Stack is set to Main. ;****************************************************************************** ;* @attention ;* ;* Copyright (c) 2017 STMicroelectronics. ;* All rights reserved. ;* ;* This software component is licensed by ST under BSD 3-Clause license, ;* the "License"; You may not use this file except in compliance with the ;* License. You may obtain a copy of the License at: ;* opensource.org/licenses/BSD-3-Clause ;* ;****************************************************************************** ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; Stack Configuration ; Stack Size (in Bytes) ; Stack_Size EQU 0x400 ;声明栈的大小为0x400字节 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size ;开辟一段大小为Stack_Size的内存空间作为栈 __initial_sp ;标号__initial_sp,表示栈空间顶地址。 ; Heap Configuration ; Heap Size (in Bytes) ; Heap_Size EQU 0x200 ;声明栈的大小为0x200字节 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base ;标号__heap_base,表示堆空间起始地址。 Heap_Mem SPACE Heap_Size ;开辟一段大小为Heap_Size的内存空间作为堆。 __heap_limit ;标号__heap_limit,表示堆空间结束地址。 ;-------------------------------------------- ;第一部分: ;启动代码最重要的工作是把异常中断向量表放到正确的Flash地址上 ;把向量表定义为只读数据段,并导出向量表标号(Symbol),让链接器识别此标号并根据分散加载文件正确的放置向量表 ;__Vectors标号需要与分散加载文件合起来看,才会明白其真正的功能 ;-------------------------------------------- PRESERVE8 ;告诉编译器以8字节对齐。 THUMB ;告诉编译器使用THUMB指令集。 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY ;声明权限为“READONLY”的名称为“RESET”的数据段 ;(假设STM32从FLASH启动,则此中断矢量表起始地址即为0x8000000) EXPORT __Vectors ;将标号__Vectors声明为全局标号,这样外部文档就可以使用这个标号。 EXPORT __Vectors_End EXPORT __Vectors_Size ;标号__Vectors,表示中断矢量表入口地址 ;创建中断矢量表 ;--------------------------------------- ;第二部分: ;__initial_sp ; 1、栈顶指针地址,此语法跟MDK编译器的底层相关,是ARMCC编译器才能识别的语法 ; GCC与IAR的底层编译器ICCARM编译器不能识别; ; 2、__initial_sp 是一个链接器Image Symbol; ; 3、此处__initial_sp相当于是顶地址,或者此处直接把顶地址写到此处也行(如:0x20004000); ; 4、__initial_sp具体是多少,在此种写法下,是由分散加载文件决定的,下文会有详细论述; ; ;Reset_Handler: ; 1、Reset_Handler函数地址,此处相当于把Reset_Handler函数地址赋值给PC,即调用Reset_Handler函数; ; 2、此处也可以是其他函数,只是把复位函数放于此处最符合实际应用场景。 ; 重要关键节点: ; 1、绝大多数cortex-M微控制器(M0、M3、M4都是这样)复位后先进入厂商BOOTROM,此时所有用户行为均无法介入处理器; ; 2、厂商BOOTROM(有些厂商会有其他名称来称呼此功能) 主要负责处理一些芯片最初级初始化、 ; 加密以及一些对MCU的差异化设置等工作: ; 3、BOOTROM顺利完成后,MCu控制权会交给用户,即启动代码; ; 4、启动代码(运行汇编语言则不需要此启动代码),最重要的工作在于设置MSP (主堆栈指针)以及PC(程序计数器)的值; ; 5、Cortex-M微控制器会默认把0x00000000地址里面的值设置为MSP的值,0x00000004地址里面的值设置为PC的值; ; 6、5中的默认地址可以通过修改Cortex-M中的VTOR寄存器来重新映射,比如改到0x20000000地址或其他; ; ;关于Exception(异常) 与Interrupt (中断) 的区别说明 ; 1、Exception(异常)与Interrupt(中断)是不同的,是两个不同的概念,很多人会混淆两者, ; 把他们都按照中断来看待,这是错误的; ; 2、Exception(异常)是向量表的前16个向量,其优先级为负数,高于所有中断,而且不可调整优先级也不可关闭, ; 可以打断正常程序与Interrupt(中断) 的运行: ; 3、从第16个向量以后才是Interrupt(中断),可以设置优先级,不用时可以关闭,但优先级永远低于Exception(异常); ; 4、从这里大家就可以理解Pendsy Handler与svsTick Handler为什么会被用于嵌入式操作系统, ; 因为其优先级高于所有中断,可以确保操作系统拥有高于普通用户程序执行的超级权限, ; SVC_Handler有时也会用于操作系统,原理相同; ; 5、用户应用程序应尽量避免使用Exception(异常); ;--------------------------------------- __Vectors DCD __initial_sp ; Top of Stack 栈顶地址 DCD Reset_Handler ; Reset Handler 复位向量 DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler 操作系统会用到的异常向量 DCD SysTick_Handler ; SysTick Handler 操作系统会用到的心跳定时器异常向量(没有操作系统时可以用作普通定时器中断) ;--------------------------------------- ;第三部分: ; 1、这里开始是中断向量表; ; 2、各个向量的顺序是芯片设计的时候就定义好的,不能更改; ;--------------------------------------- ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 DCD ADC1_2_IRQHandler ; ADC1_2 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 DCD TIM1_BRK_IRQHandler ; TIM1 Break DCD TIM1_UP_IRQHandler ; TIM1 Update DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD 0 ; Reserved DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD 0 ; Reserved DCD 0 ; Reserved DCD SPI1_IRQHandler ; SPI1 DCD 0 ; Reserved DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD 0 ; Reserved DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors ;--------------------------------------- ;第四部分: ;这部分开始可以称作Reset Handler实体,芯片上电后,经过BOOTROM后进入的用户可控最开始处的地方; ; 1、如果想让Mcu正常使用c语言,务必在此处调用 main函数; ; 2、__main() 不是main() 两者有着本质性的区别; ; 3、__main()是c Library中的函数,Kei1开发环境中自带的c Library中的函数; ; 4、main()是被 __main()调用的,__main()工作完成后最后一步就是调用main(); ; 5、__main()被调用之前,可以根据需要插入一个或多个其他功能函数; ;--------------------------------------- AREA |.text|, CODE, READONLY ;定义只读的代码段 ; Reset handler routine ;复位中断服务程序,PROC…ENDP结构表示程序的开始和结束。 Reset_Handler PROC EXPORT Reset_Handler [WEAK] ;声明复位中断矢量Reset_Handler为全局属性 ;这样外部文档就可以调用此复位中断服务。 IMPORT __main ;声明__main标号。 IMPORT SystemInit ;声明SystemInit标号。 LDR R0, =SystemInit ;跳转SystemInit地址执行 BLX R0 LDR R0, =__main ;跳转__main地址执行 BX R0 ENDP ; Dummy Exception Handlers (infinite loops which can be modified) ;--------------------------------------------------------------- ;第五部分: ; 1、[weak]指定了一个这个函数为"弱函数”; ; 2、这些中断服务函教定义成弱函数的意义是,当中断出现时,需要有一个中断服务函数予以响应,但真实的 ; 用户程序往往只会使用一部分中断,甚至不使用中断,所以以下这些函数给出了异常/中断服务函数的 ; 默认实现,很简单,默认实现就是死循环汇编中的"B."语句,相当于while(1);因为不知道用户是否会 ; 用到多少中断,但这些服务函数又很重要,所以就把这些函数都"实现"并声明为弱函数; ; 3、弱函数的意思是如果用户定义了同样名称的另一个函数,那么默认实现的弱函数就会被覆盖,比如 ; HardFault_Handler异常在下面有一个默认的实现,但这种默认的实现不能满足我的需要的时候,我可以 ; 再重新定义一个HardEault Handler函数这个新定义的HardFault_Handler函数会覆盖原有的被声明 ; 为[WEAK]的弱函数; ; 4、有很多种适合使用弱函数的场合,默认的异常/中断服务函数只是一种应用场景; ; 5、在C语言中声明弱函数是在函数后加“__attribute((weak))”; ;---------------------------------------------------------------- NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] B . ENDP MemManage_Handler\ PROC EXPORT MemManage_Handler [WEAK] B . ENDP BusFault_Handler\ PROC EXPORT BusFault_Handler [WEAK] B . ENDP UsageFault_Handler\ PROC EXPORT UsageFault_Handler [WEAK] B . ENDP SVC_Handler PROC EXPORT SVC_Handler [WEAK] B . ENDP DebugMon_Handler\ PROC EXPORT DebugMon_Handler [WEAK] B . ENDP PendSV_Handler PROC EXPORT PendSV_Handler [WEAK] B . ENDP SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ENDP Default_Handler PROC EXPORT WWDG_IRQHandler [WEAK] EXPORT PVD_IRQHandler [WEAK] EXPORT TAMPER_IRQHandler [WEAK] EXPORT RTC_IRQHandler [WEAK] EXPORT FLASH_IRQHandler [WEAK] EXPORT RCC_IRQHandler [WEAK] EXPORT EXTI0_IRQHandler [WEAK] EXPORT EXTI1_IRQHandler [WEAK] EXPORT EXTI2_IRQHandler [WEAK] EXPORT EXTI3_IRQHandler [WEAK] EXPORT EXTI4_IRQHandler [WEAK] EXPORT DMA1_Channel1_IRQHandler [WEAK] EXPORT DMA1_Channel2_IRQHandler [WEAK] EXPORT DMA1_Channel3_IRQHandler [WEAK] EXPORT DMA1_Channel4_IRQHandler [WEAK] EXPORT DMA1_Channel5_IRQHandler [WEAK] EXPORT DMA1_Channel6_IRQHandler [WEAK] EXPORT DMA1_Channel7_IRQHandler [WEAK] EXPORT ADC1_2_IRQHandler [WEAK] EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK] EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK] EXPORT CAN1_RX1_IRQHandler [WEAK] EXPORT CAN1_SCE_IRQHandler [WEAK] EXPORT EXTI9_5_IRQHandler [WEAK] EXPORT TIM1_BRK_IRQHandler [WEAK] EXPORT TIM1_UP_IRQHandler [WEAK] EXPORT TIM1_TRG_COM_IRQHandler [WEAK] EXPORT TIM1_CC_IRQHandler [WEAK] EXPORT TIM2_IRQHandler [WEAK] EXPORT TIM3_IRQHandler [WEAK] EXPORT I2C1_EV_IRQHandler [WEAK] EXPORT I2C1_ER_IRQHandler [WEAK] EXPORT SPI1_IRQHandler [WEAK] EXPORT USART1_IRQHandler [WEAK] EXPORT USART2_IRQHandler [WEAK] EXPORT EXTI15_10_IRQHandler [WEAK] EXPORT RTC_Alarm_IRQHandler [WEAK] EXPORT USBWakeUp_IRQHandler [WEAK] ;--------------------------------------------------- ;第六部分 ; 这部分比较简单,看一下第五部分的代码就可以理解下面的部分; ;--------------------------------------------------- WWDG_IRQHandler PVD_IRQHandler TAMPER_IRQHandler RTC_IRQHandler FLASH_IRQHandler RCC_IRQHandler EXTI0_IRQHandler EXTI1_IRQHandler EXTI2_IRQHandler EXTI3_IRQHandler EXTI4_IRQHandler DMA1_Channel1_IRQHandler DMA1_Channel2_IRQHandler DMA1_Channel3_IRQHandler DMA1_Channel4_IRQHandler DMA1_Channel5_IRQHandler DMA1_Channel6_IRQHandler DMA1_Channel7_IRQHandler ADC1_2_IRQHandler USB_HP_CAN1_TX_IRQHandler USB_LP_CAN1_RX0_IRQHandler CAN1_RX1_IRQHandler CAN1_SCE_IRQHandler EXTI9_5_IRQHandler TIM1_BRK_IRQHandler TIM1_UP_IRQHandler TIM1_TRG_COM_IRQHandler TIM1_CC_IRQHandler TIM2_IRQHandler TIM3_IRQHandler I2C1_EV_IRQHandler I2C1_ER_IRQHandler SPI1_IRQHandler USART1_IRQHandler USART2_IRQHandler EXTI15_10_IRQHandler RTC_Alarm_IRQHandler USBWakeUp_IRQHandler B . ENDP ALIGN ;******************************************************************************* ; User Stack and Heap initialization ;******************************************************************************* ;IF…ELSE…ENDIF结构,判断是否使用DEF:__MICROLIB(此处为不使用)。 ;若使用DEF:__MICROLIB,则将__initial_sp,__heap_base,__heap_limit ;亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。 IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory。 EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap, ;这样外程序也可调用此标号 __user_initial_stackheap ;标号__user_initial_stackheap,表示用户堆栈初始化程序入口 ;分别保存栈顶指针和栈大小,堆始地址和堆大小至R0,R1,R2,R3寄存器。 LDR R0, = Heap_Mem LDR R1, =(Stack_Mem + Stack_Size) LDR R2, = (Heap_Mem + Heap_Size) LDR R3, = Stack_Mem BX LR ALIGN ENDIF END;程序完毕 ;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE***** 部分解释 以上便是STM32的启动代码的完整解析,接下来对几个小地方做解释: ...

February 25, 2023 · 6 min · Rancho

STM32的启动过程

STM32单片机是如何从上电运行到main()函数的; 用三张图片基本就能理解了: 参考: https://blog.csdn.net/weixin_39118482/article/details/79508747?spm=1001.2014.3001.5502 https://www.modb.pro/db/548699 https://www.cnblogs.com/yucloud/p/stm32_SystemInit_to_main.html 1、函数的调用过程: 2、启动流程1(使用标准库,不使用Microlib) 如下图: 3、启动流程2(使用Microlib) microlib 是缺省 C 库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行。 microlib 进行了高度优化以使代码变得很小。它的功能比缺省 C 库少,并且根本不具备某些 ISOC 特性。某些库函数的运行速度也比较慢,例如, memcpy() 。 microlib与缺省C库之间的主要差异是: microlib不符合ISO C库标准。不支持某些ISO特性,并且其他特性具有的功能也较少; microlib不符合IEEE 754二进制浮点算法标准; microlib进行了高度优化以使代码变得很小; 无法对区域设置进行配置。缺省C区域设置是唯一可用的区域设置; 不能将main()声明为使用参数,并且不能返回内容; 不支持stdio,但未缓冲的stdin、stdout和stderr除外; microlib对C99函数提供有限的支持; microlib不支持操作系统函数; microlib不支持与位置无关的代码; microlib不提供互斥锁来防止非线程安全的代码; microlib不支持宽字符或多字节字符串; 与stdlib不同,microlib不支持可选择的单或双区内存模型。microlib只提供双区内存模型,即单独的堆栈和堆区。 启动流程如下图: 4、其他 关于stm32的启动文件: https://www.fan-pengfei.top/2023/02/25/STM32%E5%90%AF%E5%8A%A8%E4%BB%A3%E7%A0%81%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/#more

February 25, 2023 · 1 min · Rancho

单总线挂载多个DS18B20并读取温度

当需要同时读取多个点的温度数据时,DS18B20就是一个很好的选择,不仅精度高,而且还可以单总线挂载多个传感器以节省IO口的使用; 初始化函数 DS18B20的通信协议为单总线通信协议; 首先由主机发送一个复位脉冲约480-960us;然后总线被拉高;在15-60us之后传感器向单片机发送一个约60-240us的存在脉冲,然后总线被拉高。 /** * @brief 主机给从机发送复位脉冲 */ static void DS18B20_Reset(void) { DS18B20_Mode_OUT_PP(); // 主机输出 DS18B20_OUT_0; // 主机至少产生 480us 的低电平复位信号 delay_us(750); DS18B20_OUT_1; // 主机在产生复位信号后,需将总线拉高 // 从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 delay_us(15); } /** * @brief 检测从机给主机返回的存在脉冲 * @return 0:成功 1:失败 */ static uint8_t DS18B20_Presence(void) { uint8_t pulse_time = 0; DS18B20_Mode_IN_NP(); // 主机设为输入 // 等待存在脉冲的到来,存在脉冲为一个 60 ~ 240 us 的低电平信号 // 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,会在 15 ~ 60 us 后给主机发一个存在脉冲 while (DS18B20_IN && (pulse_time = 100) { return 1; } else { pulse_time = 0; } // 响应脉冲(低电平)到来,且存在的时间不能超过 240 us while (!(DS18B20_IN) && pulse_time = 240) { return 1; } else { return 0; } } /** * @brief DS18B20 初始化函数 * @reurn 0:成功 1:失败 */ uint8_t DS18B20_Init(void) { DS18B20_Mode_OUT_PP(); DS18B20_OUT_1; DS18B20_Reset(); return DS18B20_Presence(); } 配置写函数 当主机将数据线从高逻辑级别拉到低逻辑级别时,将启动写入时隙。有两种类型的写时槽:写1时槽和写0时槽。所有写入时隙的持续时间必须至少为60µs,且每个写入周期之间的恢复时间至少为1µs以上。在DQ线下降后,DS18B20在15µs到60µs的窗口中对DQ线进行采样。 ...

February 22, 2023 · 4 min · Rancho

与或非等逻辑运算在程序中的应用

今天写代码的时候再次用到了与或非等逻辑运算符来完成一些二进制处理应用,总结记录一下; 一、缘起 以下是一个显示板的原理图,由于LED数量较多,因此在LED控制方案的选择上是选用了一块串转并的芯片74HC595; 对于74HC595的使用在这里就不再赘述; 74HC595串转并是转出8个输出端,再加上三个直接连接单片机引脚的COM端,理论上是可以很容易控制24个LED灯亮灭,就像是控制三个数码管一样,利用人眼的暂留现象,就可以使这24个LED灯的亮灭随意组合; 每一个COM端和8个LED组成一个组合,然后轮询点亮这三组LED,速度够快的话,就能看到三组LED被同时点亮; 二、解决 为了消除残影问题,在每一组的LED点亮后,应该马上写入使LED全部熄灭的命令,以消除可能会出现的残影问题; 首先定义三个uint8_t变量,例如代码中的LED_NUM1、LED_NUM2、LED_NUM3,然后通过控制COM端,分别向每组LED中写入这三个数据: 第一组 COM引脚排布:100 待写入74HC595中的数据:LED_NUM1 第二组 COM引脚排布:010 待写入74HC595中的数据:LED_NUM2 第三组 COM引脚排布:001 待写入74HC595中的数据:LED_NUM3 然后对于每一个单独LED的控制,就需要用到逻辑运算,例如图中的LED1,其属于第三组LED,因为其控制COM引脚为COM3, 因而代码可以写为: //只点亮LED1,而不影响本组内其他LED的显示 LED_NUM3 = LED_NUM3 | 0x01; //只熄灭LED1,而不影响本组内其他LED的显示 LED_NUM3 = LED_NUM3 & (~0x01); 其他的LED控制也是一样的道理; 可以提取出公式,如LED的编号为07,分别接在74HC595的Q0Q1引脚上,设任意一引脚为n号引脚,则其控制代码为: //只点亮LEDn,而不影响本组内其他LED的显示 LED_NUM = LED_NUM | (0x01 PF0 STCP -> PA12 DS -> PB5 COM1->PA15 COM2->PB3 COM3->PB4 */ /** * @brief 模拟SPI向74HC595芯片发送数据 * @param SendVal 待发送八位数据 * @return 无 */ void HC595SendData(uint8_t SendVal) { uint8_t i; for (i = 0; i 0) { LED_NUM1 = LED_NUM1 | 0x08; } else { LED_NUM1 = LED_NUM1 & (~0x08); } HC595SendData(LED_NUM1); HC595SendData(0x00); HAL_GPIO_WritePin(COM1_PIN_GPIO_Port, COM1_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM2_PIN_GPIO_Port, COM2_PIN_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COM3_PIN_GPIO_Port, COM3_PIN_Pin, GPIO_PIN_RESET); if (LED_second_case > 1) { LED_NUM2 = LED_NUM2 | 0x08; } else { LED_NUM2 = LED_NUM2 & (~0x08); } HC595SendData(LED_NUM2); HAL_Delay(0); HC595SendData(0x00); HAL_GPIO_WritePin(COM1_PIN_GPIO_Port, COM1_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM2_PIN_GPIO_Port, COM2_PIN_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(COM3_PIN_GPIO_Port, COM3_PIN_Pin, GPIO_PIN_SET); if (LED_second_case > 2) { LED_NUM3 = LED_NUM3 | 0x08; } else { LED_NUM3 = LED_NUM3 & (~0x08); } if (LED_power) { LED_NUM3 = LED_NUM3 | 0x40; } else { LED_NUM3 = LED_NUM3 & (~0x40); } if (LED_smart == 1) { if (pwm_high > pwm_t) { LED_NUM3 = LED_NUM3 | (0x20); } else { LED_NUM3 = LED_NUM3 & (~0x20); } } if (pwm_temp % 100 == 0) { if (flag == 1) { pwm_t--; if (pwm_t == 0) { flag = !flag; } } else { pwm_t++; if (pwm_t == 10) { flag = !flag; } } } pwm_high++; if (pwm_high == 10) { pwm_high = 0; } switch (LED_first_case) { case 1: /* code */ LED_NUM3 = LED_NUM3 | (0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 2: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 | (0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 3: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 | (0x10); LED_NUM3 = LED_NUM3 & (~0x04); break; case 4: /* code */ LED_NUM3 = LED_NUM3 & (~0x01); LED_NUM3 = LED_NUM3 & (~0x02); LED_NUM3 = LED_NUM3 & (~0x10); LED_NUM3 = LED_NUM3 | (0x04); break; default: break; } HC595SendData(LED_NUM3); HAL_Delay(0); HC595SendData(0x00); } ,

February 14, 2023 · 2 min · Rancho

stm32启动过程

了解stm32的启动过程,方便遇到问题时的调试; 主要是两个问题: STM32是如何启动的,如何执行到main函数; 如何保证编译后的代码可以烧录到正确的地址; 作为一个计算机系统的核心,CPU的实际工作就是取指令和计算。大体上它可以看做是三个部分组成的:寄存器组、算术逻辑单元(ALU) 、指令队列。 在Cortex-M4的寄存器中有一个特殊的寄存器PC(Program Counter,程序计数器), 用于控制程序的执行。在每个时钟周期中CPU都会根据PC中的值,从地址空间中取一条指令放到指令队列中, 同时从指令队列中取出一条指令进行解析和运算(实际上ARM采用的是一种流水线的指令处理方式,与这里所讲的内容还是有很大差异的,但大体思想差不多)。 并把上次的运算结果写到寄存器中。 CPU运算所用的指令和数据都来自地址空间。在Cortex-M4的内存系统中, CPU可以访问4G的地址空间,根据所映射的物理对象不同大体上被划分成了6块。我们烧写到芯片内部的程序一般都在其中的Code段中, 在STM32中这个段对应的是一块FLASH。上电的时候,基本上只有这块FLASH中的内容是确定的,其它地址空间以及CPU内部的寄存器中的值都是随机的。 当然为了防止上电的时候外设产生意外,片上外设(Peripheral)段中的值在上电的时候也会有初始值。 所以,从处理器的角度来看,启动过程实际上是给各个寄存器赋初值的过程,更具体的是给PC寄存器赋初值的过程。从MCU和系统的角度来看, 启动过程是初始化处理器和外设的过程。 以STM32F4为例: STM32是如何启动的: 上电后系统进行复位,等到时钟稳定后才可以正常工作,这个过程通常需要几个毫秒。 图1中描述了处理器的复位过程,Cortex-M内核会先从地址0x0000处读取栈地址,并写到CPU内部的SP寄存器中。 再从地址0x0004读取Reset Vector到PC寄存器中,进而跳转到Reset Vector所指的地址上开始执行程序。 图1 Cortex-M复位流程 栈空间是处理器实现函数调用和中断服务的工具。函数调用和中断服务有一个共同的特点就是,它们都需要先把当前正在处理的内容暂时保存下来,转而执行要调用的函数, 或者中断服务函数,等待新的函数执行完毕返回后,在从原来保存的内容恢复回来继续执行原来的函数。而函数的调用是支持嵌套的,也就是说一个函数中可以调用子函数, 在子函数中又可以调用其它子函数。那么从函数的调用和返回的顺序上来看,最后调用的函数一定先返回。栈这种数据结构的特点就是其中的数据是后进先出的, 与函数调用和返回的顺序是一致的。因而,人们就专门在内存空间中划分出来一块用作栈空间,并从CPU中拿出一个宝贵的寄存器用于指示栈顶, 该寄存器被记为SP (Stack Pointer)。 所以,前面所说的从CPU的角度看启动过程就是PC寄存器初始化的过程还不完善。虽然对PC寄存器进行初始化后,CPU就可以正常的取指令并进行运算了, 但这时所能完成的功能十分有限,并不能支持对我们很重要的函数和中断。因此,从CPU角度看启动过程是对PC和SP两个寄存器的初始化过程。 Cortex-M4中规定0x0000起始的地址存放的是系统向量表(vector table)。在STM32中0x0000本身并不对应什么物理设备, ==通过配置引脚BOOT[1:0]我们可以控制0x0000映射到地址空间中的其它地址中,也就实现了不同的启动方式。==一共有三种可选的启动方式如表1所示, 从主闪存或者系统存储器启动时,硬件上会把0x0000 0000映射到0x0800 0000或者0x1FFF F000上,这样我们从地址0x0000 0000访问的空间实际上就是主闪存或者系统存储器的空间。 从SRAM启动时,只能在0x2000 0000开始的地址访问SRAM。一般我都是从主闪存启动的,也就是说系统的向量表应当烧写在0x0800 0000的地址上。至于如何从SRAM启动需要查看其他资料todo。 启动模式选择引脚 启动模式 偏移地址 BOOT1 BOOT0 X 0 主闪存(Main Flash Memory) 0x0800 0000 0 1 系统存储器(system memory) 0x1FFF F000 1 1 内置SRAM(Embedded SRAM) 0x2000 0000 ...

May 8, 2022 · 2 min · Rancho

Jlink使用RTT输出调试信息(代替串口打印)

Jlink RTT调试技巧; 使用Jlink的 RTT功能 : 这个功能是不需要另外接其他引脚的,如果使用SW连接方式,仅仅两根线就可以。 RTT 是Jlink的一种实时终端的方式连接输出调试信息,网上有很多说明之间按照做就可以,我仅仅是记录一下自己的步骤. 就是下载RTT软件包,下载RTT文件: http://download.segger.com/J-Link/RTT/RTT_Implementation_140925.zip ; 添加RTT文件到自己的工程: 添加必要的头文件: 输出函数打印: 这个时候RTT在程序中就添加成功了,我们可以使用使用Jlink带的工具进行查看数据; 如打开RTT Viewer 提升连接,点击OK 不出意外的话,你就可以看到调试信息了;

February 15, 2022 · 1 min · Rancho

LCD显示太空人画面

LCD 显示太空人动画; 这个比较简单,就是把连续的画面播放起来就行;

January 24, 2022 · 1 min · Rancho