目录
9. STM32启动流程:初始化确定启动方式->sp,pc指针->系统时钟初始化->初始化用户堆栈->跳到main()
10. STM32F4初始化蓝牙串口和蓝牙HC-05所有配置的代码
14. STM32为什么中断会进入UART4_IRQHandler()
(1)大顶端: 将数据的最高有效字节存储在起始地址,即高位字节在前。小顶端: 将数据的最低有效字节存储在起始地址,即低位字节在前。
假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)[一个地址下存放一个字节]

(2) 代码区分大顶端和小顶端。
代码1:
- #include <stdio.h>
-
- int main() {
- unsigned int x = 0x12345678;
- unsigned char *p = (unsigned char *)&x;
-
- if (*p == 0x78) {
- printf("小端");
- } else if (*p == 0x12) {
- printf("大端");
- } else {
- printf("未知");
- }
-
- return 0;
- }
指针类型为char而不是int,是因为我们只关心内存中的最低有效字节(LSB),即最低位的字节。在这个例子中,我们想要检查一个整数的字节序,所以我们需要将整数的地址转换为一个字符指针,然后通过解引用这个指针来访问最低有效字节。这样我们就可以根据这个字节的值来判断整数的字节序是大端还是小端。
代码2:
- #include <stdio.h>
-
- union {
- unsigned int i;
- unsigned char c[4];
- } u;
-
- int main() {
- u.i = 0x12345678;
-
- if (u.c[0] == 0x78) {
- printf("小端");
- } else if (u.c[0] == 0x12) {
- printf("大端");
- } else {
- printf("未知");
- }
-
- return 0;
- }
在这个例子中,我们使用了一个联合体,它包含一个整数和一个字符数组。我们将整数赋值给联合体的整型成员,然后通过访问字符数组的第一个元素来判断字节序。如果第一个元素的值是0x78,那么就是小端;如果第一个元素的值是0x12,那么就是大端;否则就是未知。
计算机内存地址增长的方向是自小到大。栈地址增长方向与内存地址增长方向相反。
是把指针变量p中存储的a的地址以十六进制形式输出,%d是吧p中存储的a的地址以十进制形式输出。输出的都是p的值,不要理解成%p,是把p的地址输出。
- //3和4的代码
- #include <stdio.h>
-
- int main()
- {
- int a = 2;
- int b = 3;
- char c = 'a';
- int *p = &a;
-
- printf("%d\r\n",&a);
- printf("%p\r\n",&a);
- printf("%p\r\n",&b);
- printf("%p\r\n",&c);
- printf("%p\r\n",p);
- p++;
- printf("%p\r\n",p);
- return 0;
- }
因为栈向下增长,所以先声明的地址大,后声明的地址小。栈的这种向下增长的设计使得它在处理函数调用时非常高效。每次函数调用时,都会在栈顶创建一个新的栈帧,用来存放该函数的局部变量和返回地址。当函数执行完毕返回时,对应的栈帧会被自动清理,栈顶恢复到调用前的位置。
| 8位系统 | 32位系统 | 64位系统 | |
| 字符型 | 1字节 | 1字节 | 1字节 |
| 整型 | 2字节 | 4字节 | 4字节 |
| 长整型 | 4字节 | 4字节 | 4字节 |
| 浮点型 | 无统一标准 | 4字节 | 4字节 |
| 双精度浮点型 | 无统一标准 | 8字节 | 8字节 |
| 指针类型 | 无统一标准 | 4字节 | 8字节 |
IIC最多可以接入2的7次方-1个设备,一共是127个。 第一个字节(为slave address)由7位地址和一位R/W读写位组成的,这字节是个器件地址。 首先,你要知道:常用IIC接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。 如格式如下: D7 D6 D5 D4 D3 D2 D1 D0 1-器件类型由:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。 2-用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。 所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了。 3-最低一位就是R/W位。这位不用我多说了
三个主要步骤:取值,译码和执行,取指阶段是从存储器中加载指令到指令寄存器;译码阶段是解释指令含义并准备执行所需的操作;执行阶段则实际完成指令的操作,并将结果写回寄存器。流水线允许多个指令在不同的阶段同时进行处理。例如,当第一条指令处于执行阶段时,第二条指令可能正在进行译码,而第三条指令则在取指阶段
- #include "stm32f4xx.h"
-
- void I2C1_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- I2C_InitTypeDef I2C_InitStructure;
-
- // 开启I2C1时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
-
- // 开启GPIOB时钟
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
-
- // 配置PB6和PB7为复用推挽输出
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_Init(GPIOB, &GPIO_InitStructure);
-
- // 连接PB6和PB7到I2C1的SCL和SDA引脚
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);
-
- // 初始化I2C1
- I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
- I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
- I2C_InitStructure.I2C_OwnAddress1 = 0x00;
- I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
- I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
- I2C_InitStructure.I2C_ClockSpeed = 100000; // 设置I2C时钟速率为100kHz
- I2C_Init(I2C1, &I2C_InitStructure);
-
- // 使能I2C1
- I2C_Cmd(I2C1, ENABLE);
- }
- #include "stm32f4xx.h"
- #include "usart.h"
- #include "bluetooth.h"
-
- void Bluetooth_Init(void)
- {
- // 初始化USART1,用于蓝牙通信
- USART_InitTypeDef USART_InitStructure;
- USART_InitStructure.USART_BaudRate = 9600; // 设置波特率
- USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 设置数据位长度为8位
- USART_InitStructure.USART_StopBits = USART_StopBits_1; // 设置停止位为1位
- USART_InitStructure.USART_Parity = USART_Parity_No; // 不使用奇偶校验
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用硬件流控制
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 设置为收发模式
- USART_Init(USART1, &USART_InitStructure);
- USART_Cmd(USART1, ENABLE); // 使能USART1
-
- // 初始化蓝牙模块
- Bluetooth_Config();
- }
-
- void Bluetooth_Config(void)
- {
- // 发送AT指令来配置HC-05模块
- // 设置蓝牙名称
- Bluetooth_SendCommand("AT+NAME=HC-05");
- // 设置蓝牙配对密码
- Bluetooth_SendCommand("AT+PSWD=1234");
- // 设置蓝牙工作模式为可连接模式
- Bluetooth_SendCommand("AT+MODE=0");
- // 开启蓝牙模块
- Bluetooth_SendCommand("AT+POWE=1");
- }
MCU是将计算机的主体部分集成在一块半导体上的单片机。DSP是专门用于数字信号处理的微处理器,特别是擅长处理如离散余弦变换、快速傅里叶变换等复杂计算。
在STM32F4中,函数压栈到寄存器涉及对特定寄存器的保存与恢复,确保程序的正常运行和中断处理的正确返回。具体来说,当进行函数调用或响应中断时,处理器自动把PSR, PC, LR, R12, R3, R2, R1, R0等寄存器的内容按照一定顺序压入栈中.手动需要压栈的寄存器包括R11, R10, R9, R8, R7, R6, R5, R4.
原因是在于STM32微控制器的中断处理机制,该机制通过硬件触发和自动执行预定义的中断服务程序来响应特定的事件。
中断初始化和配置:在STM32中,要使能串口的中断功能,首先要正确配置相关的控制寄存器和中断向量。这包括启用UART4时钟,配置UART4的工作模式(如波特率、数据位等),以及设置中断优先级和使能特定类型的中断源(例如接收中断、发送完成中断等)
中断向量表的角色:启动文件startup_stm32f4xx.s中包含了一个中断向量表,该表为每个中断源指定了一个处理函数。对于UART4,这个向量表中会包含DCD UART4_IRQHandler这样的指令,直接指向了UART4_IRQHandler()函数的内存地址。当UART4的中断条件满足时,处理器会自动跳转到这个地址执行中断服务程序。
硬件自动压栈操作:响应中断时,Cortex-M内核将自动进行压栈操作,保存被中断的程序执行状态,包括程序计数器(PC)、状态寄存器(PSR)、以及一些关键的寄存器如R0-R3、R12、LR、PC、xPSR的值。这一过程完全由硬件自动完成,确保了中断处理完成后能够恢复到原来被打断的程序继续执行。
中断服务程序的执行:一旦处理器响应了中断并进入了UART4_IRQHandler(),它就会执行该函数中的代码来处理具体的中断事件。这包括读取UART4的数据寄存器以获取接收到的数据,或者根据设置处理其他类型的UART中断事件。
从中断返回:处理完中断事件后,UART4_IRQHandler()函数执行结束,内核会自动恢复之前保存的寄存器值,并从中断发生前的指令继续执行。这个过程称为异常退出处理,确保了程序的正确流程和数据的完整性。
错误处理和调试:如果中断处理过程中遇到问题,如硬件错误或配置不当,可能会触发额外的错误处理机制。在这种情况下,开发人员应检查配置是否正确,以及硬件连接是否稳定可靠。
STM32的USART是用于处理串口通信的外设,具备发送和接收数据的能力。而串口接收时,主要是状态寄存器和数据寄存器发生变化。
状态寄存器(USART_SR):此寄存器用于检测USART的当前状态,其中包括多个状态位,如发送寄存器空位、发送完成位、读数据寄存器非空位等。其中最重要的两位是RXNE(读数据寄存器非空)和TC(发送完成)
数据寄存器(USART_DR) 这是一个双向寄存器,分为发送数据寄存器(TDR)和接收数据寄存器(RDR)。向USART_DR写入数据时,数据会被存储在TDR内,用于后续的发送操作;从USART_DR读取数据时,会自动从RDR提取数据。
- #include <iostream>
- using namespace std;
-
- struct ListNode {
- int val;
- ListNode *next;
- ListNode(int x) : val(x), next(NULL) {}
- };
-
- // 在链表头插入节点
- void insertAtHead(ListNode* &head, int val) {
- ListNode* newNode = new ListNode(val);
- newNode->next = head;
- head = newNode;
- }
-
- // 在链表尾部插入节点
- void insertAtTail(ListNode* &head, int val) {
- ListNode* newNode = new ListNode(val);
- if (head == NULL) {
- head = newNode;
- return;
- }
- ListNode* temp = head;
- while (temp->next != NULL) {
- temp = temp->next;
- }
- temp->next = newNode;
- }
-
- // 在指定位置插入节点
- void insertAtPosition(ListNode* &head, int position, int val) {
- if (position == 0) {
- insertAtHead(head, val);
- return;
- }
- ListNode* newNode = new ListNode(val);
- ListNode* temp = head;
- for (int i = 1; i < position && temp != NULL; i++) {
- temp = temp->next;
- }
- if (temp == NULL) {
- cout << "Invalid position!" << endl;
- return;
- }
- newNode->next = temp->next;
- temp->next = newNode;
- }
-
- // 删除指定值的节点
- void deleteNode(ListNode* &head, int val) {
- if (head == NULL) {
- return;
- }
- if (head->val == val) {
- ListNode* temp = head;
- head = head->next;
- delete temp;
- return;
- }
- ListNode* temp = head;
- while (temp->next != NULL && temp->next->val != val) {
- temp = temp->next;
- }
- if (temp->next == NULL) {
- cout << "Value not found!" << endl;
- return;
- }
- ListNode* toDelete = temp->next;
- temp->next = temp->next->next;
- delete toDelete;
- }
-
- // 删除指定位置的节点
- void deleteAtPosition(ListNode* &head, int position) {
- if (head == NULL) {
- return;
- }
- if (position == 0) {
- ListNode* temp = head;
- head = head->next;
- delete temp;
- return;
- }
- ListNode* temp = head;
- for (int i = 1; i < position && temp != NULL; i++) {
- temp = temp->next;
- }
- if (temp == NULL || temp->next == NULL) {
- cout << "Invalid position!" << endl;
- return;
- }
- ListNode* toDelete = temp->next;
- temp->next = temp->next->next;
- delete toDelete;
- }