包含 MCU 标签的文章

IAR for RL78 使用未声明函数引起的问题

在使用 IAR for RL78 1.30.3 时遇到一个奇怪的问题: 调用一个函数(反转 IO 口电平)时总不能达到预期结果,一通分析也查不出个所以然,最后只能通过反汇编结果分析了一下。向函数传递的是 uint8_t 参数(函数定义 void gpio_toggle(uint8_t port)),使用的却是 16 位的 AX 寄存器,低 8 位存放在 X 寄存器,但进入函数处理时却是从 A 寄存器中取值的(而不是 X),这就导致参数传递错误了。 还发现,奇怪的是在别的地方调用同样的函数却是正常的!传递参数用的是 8 位的寄存器 A: 搞糊涂了,为什么同样的函数在不同文件中调用的结果是不同的呢?而且后续还发现出问题的那个函数所在文件的其他函数也会有类似的问题。于是疯狂地怀疑这儿怀疑那儿,还怀疑是不是这个低版本的编译器存在 Bug 😅 冷静下来后排查了一下编译时的 Warning: Warning[w6]: Type conflict for external/entry "gpio_toggle", in module ... against external/entry in module hal_gpio; prototyped function vs K&R function 其实警告信息说的不够直白,实际的情况是调用了没有声明的函数(没有引用头文件或头文件中没有声明函数),所以加上声明 extern void gpio_toggle(uint8_t port); 就正常了。 反思:我没把这条警告信息当回事儿,想当然地认为编译链接不报错就应该是正确地链接了,如果是链接到找不到的函数会报错的。实在是大意了😂 找不到函数你倒是不要给我瞎传参数啊,报错也好啊!不知道更新版本的 IAR 是否存在同样的问题,年轻人真是不讲武德啊 😎 总结:认真对待每一条警告信息,耗子尾汁儿! 后记:还真是令我印象更深刻了,后续又遇到了这个问题。把一个 enum 参数传递给了函数,但进到函数后却不能正确地得到这个参数,忽然想到以前遇到过类似的问题,一查还真是没有引用相关头文...

新唐单片机调试工具 NuConsole

常用调试信息交互方法是使用串口。除此之外还有 Semihosting(半主机)方式,但需要调试接口外连接其他接口(例如 SWD 调试时接 SWO 接口)。 新唐的 NuConsole 则是通过在 RAM 中开辟一块信息块的方式实现通过调试接口(不需要 SWO 等额外接口)交互。也算是一种比较好的调试思路和手段了,不过依然需要试用调试器才能用,虽然不影响单片机的实时性,但也不如串口打印这么简单和通用,只适合开发时使用。 使用时需要将: NuConsole.h/.c、NuConsole_Config.h、NuConsole_Retarget.c 加入工程。此外还要做一些配置工作,详见说明文档 NuConsole_HowTo.pdf(在 C:\Program Files (x86)\Nuvoton Tools\NuConsole 可以找到说明文档和例程)...

STM32F1 型号规则

STM32F1 型号以 STM32F103C8T6 为例: STM32: ARM-based 32-bit microcontroller F:代表芯片子系列 103:基本型 101、增强型 103、 C:引脚数,T:36 脚 C:48 脚 R:64 脚 V:100 脚 Z:144 脚 8:Flash大小,6:32KB,8: 64KB,B:128KB,C:256KB,D:384KB,E:512KB T:封装,H:BGA,T:LQFP,U:VFQFPN 6:工作温度范围,6:-40 ~ 85℃,7:-40 ~ 105...

STM32F1 库函数的宏定义

1、USE_STDPERIPH_DRIVER 宏定义 要在编译器中预定义这个宏: 而且在编译器中仅仅定义这一个宏就可以了。STM32F10X_HD 这类的宏在编译器中选好 MCU 型号后就自动确定下来对应的宏了。 2、HSE_VALUE 宏定义 HSE_VALUE 需要根据实际使用的晶体频率修改,RCC_GetClocksFreq 中会用它来算出系统时钟。因为 USART_Init、I2S_Init、I2C_Init 用到了 RCC_GetClocksFreq,所以如果不修改 HSE_VALUE 为实际情况下的值会出现串口波特率错误等问题。不要直接修改 stm32f10x.h 中的定义,而要在 stm32f10x_conf.h 中重新定义 HSE_VALUE: #undef HSE_VALUE #define HSE_VALUE ((uint32_t)xxx...

STM32 定时器

预分频器(TIMx_PSC) 预分频器的值 PSC[15:0],计数器的时钟频率 CK_CNT 等于 fCK_PSC / (PSC[15:0] + 1)。也就是说 PSC[15:0] 等于分频数 - 1。 自动重装载寄存器(TIMx_ARR) 自动重装载的值 ARR[15:0],用于作为计数器的初始值(向下计数)或溢出值(向上计数)。下图的 ARR 被设置为 0x36,溢出发生在计数值为 0x36 的后部,所以实际计数了 0x37 个数(从 0 开始)。所以 ARR 的值应该为 计数值 - 1。 PWM PWM 模式 1 在向上计数时,一旦 TIMx_CNT < TIMx_CCR1 时通道 1 为有效电平,否则为 无效电平;在向下计数时,一旦 TIMx_CNT > TIMx_CCR1 时通道 1 为无效电平(OC1REF=0),否 则为有效电平(OC1REF=1)。 PWM 模式 2 在向上计数时,一旦 TIMx_CNT < TIMx_CCR1 时通道 1 为无效电平,否则为 有效电平;在向下计数时,一旦 TIMx_CNT > TIMx_CCR1 时通道 1 为有效电平,否则为无效电 平。 CC1P:输入/捕获 1 输出极性(Capture/Compare 1 output polarity) CC1 通道配置为输出: 0:OC1 高电平有效 TIM_OCPolarity_High 1:OC1 低电平有效 TIM_OCPolarity_Low 例如: TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PMW模式1(向上计数时CNT < CCR2时输出低电平) TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = TIM_TimeBaseStructure.TIM_Period / 2; //50%占空比 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //低电平为有效电平 TIM_OC2Init(TIM2, &TIM_OCInitStructure)...

STM8S 使用笔记

1、GPIO 关于 SWIM 用作普通 GPIO 脚 即使 CFG_GCR 的 SWD 脚不置为 1,事实上 PD1 脚也可正常执行输入输出功能,只不过在同时使用 SWIM 调试时,即使设置 DDR 为输出,ODR 总保持为 1。关开 SWIM 连线,复位 MCU 后输出功能可正常执行。SWD 位看似没有什么作用,不过还是按手册所说的方式来做吧。另外也不必担心 SWIM 功能因为该配置而被禁用。 关于开漏输出脚 手册上打 T 标记的是真正的开漏脚,不具备推挽输出的功能,也没有内部上拉功能。使用时需要特别注意。 2、读 TIMx_CNTR 要分别读 TIMx_CNTRH 和 TIMx_CNTRL(分别读 TIMx_CNTRL 和 TIMx_CNTRH 也没错),因为发现直接读 16 位的 TIMx_CNTR 可能出现错误: 明明 TIMx_CNTR 的值是 829,偏偏赋值给一个变量后变量的值是 789。但也不总是有问题,运行几次就好了,复位后现象依旧,但分别读 TIMx_CNTRH 和 TIMx_CNTRL 并赋值给变量就正常,连续读两次 TIMx_CNTR 也是正常的。 3、关于 COSMIC 编译器 编译器有三个自用的变量 c_x、c_y、c_lreg,当与中断函数关联时: (1)当中断函数直接使用(编译器自动产生汇编代码)他们时,会被自动保存和恢复; (2)当终端函数调用使用了 c_x、c_y 的函数时,c_x、c_y 会被自动保存和恢复,但是 c_lreg 不会被这样自动保存和恢复; 这就要特别注意:如果中断中调用了使用 longs 或者 floats 的函数时就要在中断函数的定义中加修饰词 @svlreg! 不太好判断是否使用了 c_lreg(需要反汇编才能看),例如 c_lgadc(长整数加无符号单字节)就没有用到这三个变量,所以在不影响性能的是否加上无妨,除非你的应用很在意那几个 pushw、popw。 4、一个配置字初始化引起的问题 同样的软件延时程序在不同的板子上一个验时是 4ms,一个是 3ms,如果用硬件定时器来实现验时则一致。 读出配置字为 000000F0F000FFFE00,而正常情况下应该读出的是全 0。置 1 的那些位是保留位,具体作用不清除,将其都清零以后延时都是 3ms 了。 可能保留的配置字影响了指令执行等待时间等参数。通过这个问题来看,如果烧写程序时(尤其是 debug 程序时)不烧写正常的配置字进去,可能会造成程序的异常执行。 5、stm8 cosmic 用 sp 索引变量,如果用 push cc 保存寄存器至栈会造成由于编译器不能识别嵌入式汇编而造成的后续代码的变量使用的错误,所以要用 push cc 然后 pop a 的方式获取 cc 的值并返回,不能保存在栈里: uint8 EnterCritical(void) {     #asm     push cc     sim     pop a     #endasm } 6、关于 STVD 开发环境 如果出现编辑界面或仿真界面布局变乱,可以删除 .wed 文件(存储开发界面信息)或 .wdb 文件(存储仿真界面信息),以初始化界面。 7、关于 memcpy cosmic 的 memcpy 是从后往前拷贝,这样当 src 与 dst 重叠时,比如 memcpy(buf, buf + 5, 10) 结果就不是我们预想的,可以使用 memmove 来代替,它会检查是否重叠。当 dst 大于等于 src 时,从前往后拷贝(常规顺序)。 8、stm8 终端向量表 由操作码 0x82(INT)与三字节的地址组成,执行过程是将三字节地址赋给 PC,程序将跳转到该地址处执...

实现复位后不清零单片机内部 SRAM 的方法

通常情况下单片机复位后会自动清零 SRAM 中的一些变量,本文以 ARM 单片机为例来说一说如何实现不清零。 在默认情况下,定义一个未初始化的数组: uint8_t hardfault_info[256]; 它会被分配到ZI段中去。什么是ZI段? ZI(.bss) 即 zero-initialized ,它是在代码中未作初始化的需要在运行时被初始化为零的变量的集合。类似地还有 XO(execute-only )、RO(read-only,.text)只读的常量、RW(read-write,.data)在编译时被初始化了的可读写变...

STM32 软件模板

创建文件目录如下: Doc 用于存放文档; Libraries 用于存放各种库,其中的 STM32Lib 是不可缺少的内核与标准外设的驱动库。原则上,所有的库都不要做任何修改,保持与原始发行状态一致。 MDK-ARM 是与 RVMDK 编译器相关的文件夹,存放工程文件、各种输出文件等; User 目录用于存放我们自己开发的代码 mcu 为 MCU 外设驱动程序文件夹; lib 为一些库程序的文件夹; bsp 为其他板载器件的驱动程序。 MDK 开发环境只支持一级组结构,所以组结构就是扁平化了的。一般包含 STM32Lib、Startup、User、Doc 等组。 把内核及标准外设的驱动(C 文件)加入 STM32Lib 组: Startup 为启动代码组,要从 .\Libraries\STM32Lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 文件夹选择一个。 将 main.c、配置、中断服务等文件加入 User 文件夹并加载到 User 组。 stm32f10x_conf.h 头文件在 stm32f10x.h 中通过判断有无 USE_STDPERIPH_DRIVER 宏定义而确定是否引用。 此外还需要配置工程,定义单片机系列(如定义 STM32F10X_CL)、选择使用标准外设驱动库 USE_STDPERIPH_DRIVER Include Paths 中配置所有需要引用的头文件所在目录 配置时钟晶体的频率 在需要操作内核或标准外设的地方直接引用库的头文件(stm32f10x.h)即可: 在编译的时候可以看到所有的库文件都被编译了,速度很慢,如果没有使用某个库文件,可以选择不编译它。 取消选择 Include in Target Build 即可。 在 C/C++ 选项卡中选中 One ELF Section per Function 选项可将用不到的函数排除在编译结果之外,从而减小生成程序的大...