阡陌 发布的文章

阡陌

Cortex-M3 HardFault_Handler 调试心得

这几天在 MDK 下调试 STM32 在进入 HardFault_Handler 异常中端原因的问题上花费了不少周折 简单来说应该采取下面的查找思路: 1、进入 HardFault_Handler 后根据 LR 确定异常发生处所使用的栈指针是 MSP 还是 PSP 2、查看 PSP(假设是 PSP)的值,在 RAM 中找到 PSP 所指的区域 3、地址按从低到高的顺序分别存放了 R0,R1,R2,R3,R12,LR,PC,XPSR 的顺序找出 PC 的值 4、根据 PC 的值找到异常程序的位置 5、分析异常程序位置的 FLASH 及 RAM 访问相关方面是否有异常值,一般是 RAM 被异常修改导致从此处取到了错误的访问地址引起的。 在 HardFault_Handler 打断点,在 Call Stack + Locals 窗口查看调用顺序,在某个入口点右键执行 Show Caller Code 跳到响应程序的位置再仔细查看出错原因...

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 CAN 总线通信

CAN 是控制器局域网络 (Controller Area Network, CAN) 的简称,是由以研发和生产汽车电子产品著称的德国 BOSCH(没错,就是那个卖电动工具的博世)公司开发的,并最终成为国际标准(ISO 11898),是国际上应用最广泛的现场总线之一。 在北美和西欧,CAN 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以 CAN 为底层协议专为大型货车和重工机械车辆设计的 J1939 协议。 CAN 采用多主工作方式,节点之间不分主从,但节点之间有优先级之分,通信方式灵活,可实现点对点、一点对多点及广播方式传输数据,无需调度。CAN 采用的是非破坏性总线仲裁技术,按优先级发送,可以大大节省总线冲突仲裁时间,在重负荷下表现出良好的性能。CAN 采用短帧结构传输,每帧有效字节为 8 个,传输时间短,受干扰的概率低。而且每帧信息都有 CRC 校验和其它检错措施,保证数据出错率极低。当节点严重错误时,具有自动关闭功能,使总线上其它节点不受影响,所以 CAN 是所有总线中最为可靠的。CAN 总线可采用双绞线、同轴电缆或光纤作为传输介质。它的直接通信距离最远可达 10km,通信速率最高达 1M bps(通信距离为 40m 时),总线上可挂设备数主要取决于总线驱动电路,最多可达 110 个。但 CAN 不能用于防爆区。 CAN网拓扑结构 CAN 是半双工的。收发数据要分时进行。不管 CAN 网络上挂多少设备,在同一时刻只能有 1 个发送数据。如果有多个需要同时发送则只有优先级别高的先发送,其它等待。控制器有收发两个接口,但是收发器的两个接口变成了 High 和 Low。CAN 总线式两线制的。在这点上可以与 Uart 控制器与 RS-485 收发器来类比。 CAN 协议目前有 2.0A 和 2.0B。CAN 2.0A 是 CAN 协议的 PART A 部分,此部分定义了 11bit 的标识区 。 CAN 2.0B 是 CAN 协议的扩展部分,也叫 PART B,定义了 29bit 的标识区,设计上与 CAN2.0A 兼容。通常支持 2.0B 协议的芯片同时也支持 2.0A。 STM32 单片机的 CAN 控制器接口有收、发两个口,互联型有两个 CAN 控制器。两个 CAN 都分别拥有自己的发送邮箱和接收 FIFO,但是他们共用 28 个滤波器。通过 CAN_FMR 寄存器的设置,可以设置滤波器的分配方式。 STM32 的过滤器组最多有 28 个(互联型),但是 STM32F103ZET6 只有 14个(增强型),每个过滤器组由 2 个 32 为寄存器,CAN_FxR1 和 CAN_FxR2 组成。过滤器有两种工作模式:屏蔽位模式和标识符列表模式。工作在屏蔽位模式时 CAN_FxR2 为 1 的位指示了接收到的标识位必须与 CAN_FxR1 相对应的位的 0 或 1 完全一致才能通过过滤器。如果 CAN_FxR2 全为 0 也就意味着不进行任何过滤。工作在标识符列表模式时 CAN_FxR1 和 CAN_FxR2 互不相干,CAN_FxR2 也是一个像 CAN_FxR1 的寄存器,当接收到的标识符与 CAN_FxR1 或 CAN_FxR2 完全匹配时才能通过过滤器。 STM32 提供了两种测试模式,环回模式和静默模式。环回测试模式不需要短接外部的两个脚,它是在内部环回的...

WAV 文件格式简介

WAV 为微软公司(Microsoft)开发的一种音频文件格式,它符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源,被 Windows 平台及其应用程序所广泛支持,该格式也支持 MSADPCM,CCITT A LAW 等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的 WAV 文件和 CD 格式一样,也是 44.1K 的取样频率,16 位量化数字,因此在声音文件质量和 CD 相差无几。WAV 格式的音乐通常使用三个参数来表示声音,量化位数,取样频率和声道数。 WAV 文件采用的是 RIFF 格式结构。至少是由 3 个块构成,分别是 RIFF、fmt 和 data。所有基于压缩编码的 WAV 文件必须含有 fact 块。此外所有其它块都是可选的。块 fmt、Data 及 fact 均为 RIFF 块的子块。WAV 文件的文件格式类型标识符为 WAVE。 构成 RIFF 文件的基本单位称之为块(chunk)。每个 RIFF 文档是由若干个块构成。每个块(chunk)由块标识、块长度及数据等三部分所组成。块标识保存的是由 4 个 ASCII 码字符组成的块名字。如不满 4 个字符则在右边以空格充填。块长度字段占 4 个字节,保存的是当前块数据的长度,不包括块标识和块长度字段。所以一个块的实际长度为块长度字段内的数值加 8。RIFF 格式规定,只有 RIFF 及 LIST 块可以含有子块,其它的块不允许包含子块。一个 RIFF 格式文档本身就是一个块。其前 4 个字节为文档标识 RIFF,同时也是 RIFF 的块标识,标明该文档是一个有效的 RIFF 文档;第二部分为文件的数据长度,占 4 个字节,其数值为文件长度 -8;第三部分为 RIFF 块数据,前 4 个字节为文件格式类型标识,如:WAVE、AVI 等,后面其它部分为 RIFF 块的子块。 //WAV的数值均为小端模式 typedef struct WavRiff { char id[4]; //"RIFF" uint32_t size; //块长度,从下一个字段(format)首地址开始到文件末尾的总字节数,该字段的数值加 8 为文件的总长度 char format[4]; //文件格式类型:"WAVE" } wav_riff_t; typedef struct WavFmt { char id[4]; //格式子块标识"fmt " uint32_t size; //格式子块长度,其数值不确定,取决于编码格式,可以是 16、 18 、20、40 等,从下个字段到格式子块结束的字节数 uint16_t format; //编码格式代码,常见的 WAV 文件使用 PCM 脉冲编码调制格式,PCM = 1 uint16_t channels; //通道数,单声道为1,双声道为2 uint32_t sample_rate; //采样频率,单位Hz uint32_t data_rate; //数据传输率 声道数×采样频率×每样本的数据位数/8,播放软件利用此值可以估计缓冲区的大小 uint16_t block_align; //块对齐字节数 = 声道数×位数/8,播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区 uint16_t bps; //bits per sample采样位数,存储每个采样值所用的二进制数位数。常见的位数有 4、8、12、16、24、32 } wav_fmt_t; typedef struct WavData { char id[4]; //"data" uint32_t size; //data size,从下个字段到格式子块结束的字节数 } wav_data_t; typedef struct WavHeader { wav_riff_t riff; wav_fmt_t fmt; wav_data_t data; } wav_header_t; 常见的压缩编码格式: 格式代码 格式名称 fmt 块长度 fact 块 1(0x0001) PCM/非压缩格式 16 2(0x0002 Microsoft ADPCM 18 √ 3(0x0003) IEEE float 18 √ 6(0x0006) ITU G.711 a-law 18 √ 7(0x0007) ITU G.711 μ-law 18 √ 49(0x0031) GSM 6.10 20 √ 64(0x0040) ITU G.721 ADPCM √ 65,534(0xFFFE) 见子格式块中的编码格式 40 当 WAV 文件采用非 PCM 编码时,使用的是扩展格式块,它是在基本格式块 fmt 之后扩充了一个的数据结构。该结构的前两字节为长度字段,指出后面区域的长度。紧接其后的区域称之为扩展区,含有扩充的格式信息,其长度取决于压缩编码类型。当某种编码格式(如 ITU G.711 a-law)使扩展区的长度为 0 时,长度字段还必须保留,只是长度字段的数值为 0。因此,扩展格式块长度的最小值为基本格式块的长度 16 加 ...

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)...

铁电(FRAM)随机存取存储器 MB85RC64

存储空间:8KB 不同于 Flash 和 E2PROM,无需写等待时间。 使用 I2C 总线,频率可到 400KHz。 数据可以保持 10 年。 WP 脚为高电平时处于写保护状态,默认已内部下拉到地。 器件地址为 7bits 模式,高4位为设备类型码(1010b),低 3 位由硬件引脚 A2、A1、A0 指定(已内部下拉),与 MB85RC16 是不同的。这使得一条总线上可以挂载多个同型号的器件。 存储器的写与读速度是相同的,所以无需在写的时候额外添加等待时间。 8KB 存储空间的有效地址为 13 位。 随机读: 写字节: 写页: 存储器的页指的是整个 8KB 的存储区,没有划分为若干页。当地址空间溢出时回回滚到 000H 继续增长...

I2C 通信协议

I2C(Inter-Integrated Circuit,又常写作 IIC、$I^2C$)是一种广泛应用于数字电路中的串行通信协议。这种通信协议由 Philips 公司(现在的 NXP 公司)在 20 世纪 80 年代开发,旨在解决多个芯片间的通信问题。I2C 通信协议具有简单、高可靠性和灵活性的特点,因此被广泛应用于各种不同领域的电子设备中。 I2C 协议使用两根线进行数据传输,分别是 SDA(Serial Data Line)和 SCL(Serial Clock Line)。数据在 SDA 线上传输,而时钟同步则由 SCL 线提供。这两根线在通信过程中通过电平变化来传输数据,并且都有上拉电阻。 在 I2C 通信中,有两个主要的参与者角色:主设备(Master)和从设备(Slave)。主设备是 I2C 总线的控制者,负责发起通信和发送指令;从设备则根据主设备的指令执行相应的操作并返回数据。 I2C 通信协议的基本操作包括起始条件和停止条件。起始条件是主设备发起通信的标志,它的产生是通过在 SCL 线保持高电平的同时将 SDA 线从高电平拉低。停止条件是通信结束的标志,在 SCL 线保持高电平的同时将 SDA 线从低电平拉高。 在起始条件之后,主设备会发送一些控制字节来选择特定的从设备进行通信。这些控制字节通常包括从设备的地址和读/写控制位。一旦从设备被选择,数据传输可以开始。数据的传输采用字节为单位,每个字节的传输分为数据传输和应答两个部分。 此外,I2C 协议还规定了数据有效性、地址及数据方向、应答信号和非应答信号等方面的细节,以确保通信的准确和可靠。 读写标志位:1 为读,0 为写。 注意: SDA 使用 GPIO 的 OD 模式时,在读前不需要将 GPIO 切换为输入模式,可以直接读取,但需要在读前将其输出电平置为高,这时由于时开漏状态,是由外部上拉电阻将总线拉高的,这时从设备才可以将总线拉低。如果主设备输出低电平则总线一直被主设备强拉低而使从设备不能正常输出高电平,从而使读通信不能正常进行。 主机在收完从机的 ACK 后要把 SCL 拉低,否则从机一直保持输出电平(SCL 高电平是从设备电平不能变化),比如 ACK 时一直输出低电平,NACK 时一直输出高电平,主机的 Sr 开始信号不能够使从机重新开始。 SDA 线上的数据在时钟“高”期间必须是稳定的,只有当 SCL 线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。 数据有效性 I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。SDA 数据线在 SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据做好准备。 重启动信号 在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动 Sr 信号时序。重启动信号 Sr 既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制权,即不让其他主器件节点抢占总线。 Sr 的型号与 S 是完全一样的,后面要重新发送器件地址,与S后的逻辑完全一致。 I2C 有 7 位和 10 位两种地址模式 I2C 规范保留了两组和 8 个地址,1111XXX 和 0000XXX。这些地址用于特殊用途。 从机地址+R/W 描述 0000 0000 呼叫地址 0000 0001 起始字节 0000 001X CBUS地址 0000 010X 保留供不同的总线格式 0000 011X 保留将来用 0000 1XXX HS模式主机码 1111 0XXX 10位从机地址 1111 1XXX 保留将来用 I2C 总线的 10bit 寻址和 7bit 寻址是兼容的,这样就可以在同一个总线上同时使用 7bit 地址和 10bit 地址模式的设备,在进行 10bit 地址传输时,第一字节是一个特殊的保留地址来指示当前传输的是 10bit 地址...

铁电(FRAM)随机存取存储器 MB85RC16

存储空间:2KB 不同于 Flash 和 E2PROM,无需写等待时间。 使用 I2C 总线,频率可到 1MHz。 数据可以保持 10 年。 WP 脚为高电平时处于写保护状态,默认已内部下拉到地。 器件地址为 7bits 模式,高 4 位为设备类型码(1010b),低 3 位为存储地址的高 3 位。 写字节: 写页: 存储器的页指的是整个 2KB 的存储区,没有划分为若干页。当地址空间溢出时回回滚到 000H 继续增长。 读当前地址: 上一次操作完成后存储器的地址仍被保留再地址缓冲器中,这时使用读当前地址命令可以直接读到缓冲器缓存的地址(n + 1)指向的存储区。地址缓冲器中的地址在上电后是不确定的。11 位地址的高三位是需要指定的。 随机读取: 读取可以持续进行,地址溢出后会回滚到000...

AT24CXX EEPROM 存储器芯片

AT24CXX 是 ATMEL 公司的两线制(I2C 或 IIC)汽车温度级串行 EEPROM 存储器,AT24C01A、AT24C02、AT24C04、AT24C08A、AT24C16A 等多款型号,他们的存储空间分别为 128 字节、256 字节、512 字节、1024 字节、2048 字节。 I2C 时钟支持 400KHz。 I2C 器件基础地址(ID)为:0x50,可以通过部分引脚配置扩展地址,从而可以实现在一条总线上挂载多片同类型芯片的目的。 读操作支持随机寻址并连续读多字节数据(地址自动增加,当到达边界时会回滚至 0 地址重新增长)。 写操作支持单字节写入和页写入。1K/2Kbits 芯片页空间为 8 字节,4K/8K/16Kbits 芯片页空间为 16 字节。当连续写入时地址会自动增加,但地址会在页内回滚循环。收到 I2C 停止信号时开始写入,写入周期为 5m...

RT-Thread 邮箱与消息队列

邮箱 RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息。 邮件发送不具有加急(插队)的功能。 静态邮箱初始化 初始化前资源要静态地创建。 rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag) 静态邮箱脱管 把静态初始化的邮箱对象从内核对象管理器中脱离。 rt_err_t rt_mb_detach(rt_mailbox_t mb) 创建邮箱 动态地创建邮箱并初始化。 rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag) 删除邮箱 脱管邮箱并释放动态资源,与 rt_mb_create 配合使用。 rt_err_t rt_mb_delete(rt_mailbox_t mb) 发送邮件 rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value) 函数调用了rt_mb_send_wait,timeout = 0 以等待方式发送邮件 邮箱满时等待。 rt_err_t rt_mb_send_wait(rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout) 接收邮件 支持空等待。 rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout) 邮箱控制 不常用。 rt_err_t rt_mb_control(rt_mailbox_t mb, rt_uint8_t cmd, void *arg) 2.1.0 版 支持的命令 RT_IPC_CMD_RESET(复位邮箱) timeout 支持以下宏: #define RT_WAITING_FOREVER -1 /**< Block forever until get resource. */ #define RT_WAITING_NO 0 /**< Non-block. */ 消息队列 消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。 很遗憾的是消息队列不支持发送阻塞模式(像邮箱那样的发送等待)。 消息队列初始化 静态创建。 rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag) 消息队列脱管 rt_err_t rt_mq_detach(rt_mq_t mq) 动态创建消息队列 rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag) 删除消息队列 rt_err_t rt_mq_delete(rt_mq_t mq) 发送消息到队列 线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。 rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size) 发送紧急消息到队列 发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。 rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size) 接收消息 当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置,或挂起在消息队列的等待线程队列上,或直接返回。 rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout) 消息队列控制 rt_err_t rt_mq_control(rt_mq_t mq, rt_uint8_t cmd, void *arg) 支持的命令有:RT_IPC_CMD_RESET(复位) 区别 邮箱只能传4字节的值,消息队列可以传递更长的值; 邮箱有发送等待功能,消息队列没有; 消息队列有紧急消息功能,邮箱没有紧急邮件的功能...