基于STM32实现OTA&BootLoader 第二章——外设功能开发

参考教程:https://www.bilibili.com/video/BV1SatHeBEVG/?spm_id_from=333.1387.favlist.content.click

一、准备工作

1、软件层的准备(Keil工程)

(1)拷贝一份STM32教程中使用OLED屏进行显示的工程文件夹,并更名为"OTA&BootLoader实验工程"。

(2)打开工程,将模拟I2C、模拟SPI、串口模块、DMA模块、W25Q64模块和FLASH模块的分文件添加到工程中(这几个分文件在STM32教程中已经完成开发),并编译。24C02模块(使用I2C通信的外设EEPROM)未在其它教程中开发过,因此需要从零开发。

2、硬件层的准备(接线)

(1)USB转串口模块:

|---------|------------|
| STM32引脚 | 模块引脚 |
| PA10 | TXD |
| PA9 | RXD |
| GND | GND |
| / | VCC与3.3V短接 |

(2)W25Q64模块:

|---------|------|
| STM32引脚 | 模块引脚 |
| PA4 | CS |
| PA5 | CLK |
| PA6 | DO |
| PA7 | DI |
| 3.3V | VCC |
| GND | GND |

(3)24C02模块:

|---------|------|
| STM32引脚 | 模块引脚 |
| PB11 | SDA |
| PB10 | SCL |
| 3.3V | VCC |
| GND | GND |

二、串口&DMA功能开发

1、处理接收数据方案设计

(1)如果使用CPU进行接收数据的处理,对于简单的项目而言问题不但,但对于复杂的项目,CPU往往有"更重要的任务"需要处理,如果接收数据全程由CPU进行算力支持,是非常奢侈的,而DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,能够节省CPU的资源,因此数据传输可采用DMA。

(2)涉及数据传输,还需要判断单次数据传输是否完成,最简单的就是判断传输信号线是否空闲,如果空闲,说明一次数据传输结束,可以根据此逻辑配置中断,用于请求CPU发出数据处理指令。

(3)单片机需定义缓冲区(一般为一维数组)用于存放接收但未处理的数据,如果单片机的数据处理效率高于数据接收速率,那么缓冲区的大小可相应地较小,为方便介绍,以下以一个2048 bytes的缓冲区为例进行介绍。

①当有源源不断的数据到来时,缓冲区数组从0号下标开始存放数据,先使用标号较小的空间,按顺序往缓冲区中填入数据。

②当缓冲区被使用的标号超过2047后,继续增大标号会引发数组越界,因此需要约定好单次传递数据的最大字节数,比如412 bytes,当新的一组数据到来时,如果缓冲区剩余空间小于412 bytes,那么新的数据应该从缓冲区0号下标位置写入,以规避数据越界风险。当然,也可以采用传统的循环队列,不管高地址的剩余空间有多少,都按标号递增的策略写入数据,直到标号到达2047,再转到0标号继续写入数据,这种策略的开发逻辑会变得稍微复杂一点,且意义不大,并没有起到节约空间的作用。

③缓冲区中只是存放了若干组连续的数据,但并没法直接区分不同组数据的头尾,因此在将数据写入缓冲区时,还应该记录下数据组的起始地址Start和结束地址End,为了方便管理,可以将这些信息定义为一个结构体,再创建结构体数组,当结构体数组溢出时,可按照循环队列的思想从首元素重新写入。

④由于缓冲区是个写入与读出同时不断进行的结构,因此需要再设置IN指针和OUT指针指示缓冲区写入和读出的位置,以便DMA进行数据处理。IN指针和OUT指针可直接指向结构体数组,通过结构体成员间接索引缓冲区的数据,IN指针指向下一个可写入数据的位置,OUT指针指向下一个DMA需要读取的数据的位置,整体思想与循环队列是极其相似的。(可再定义END指针指示结构体数组末端,下图未示出)

​​​​​​​

2、Serial.h文件修改

在Serial.h文件中添加如下代码,主要是缓冲区相关的定义

cpp 复制代码
#define U0_RX_SIZE 	2048        //接收数据缓冲区大小(单位为字节)
#define U0_RX_MAX  	256         //单次接收最大数据大小(单位为字节)
#define NUM        	10          //数据组指针结构体数组大小

extern uint8_t U0_RxBuff[U0_RX_SIZE];   //接收数据缓冲区

//数据组指针结构体
typedef struct{
	uint8_t *start;      	//单组数据起始地址
	uint8_t *end;       	//单组数据结束地址
}UCB_URxBuffptr;

//数据组指针结构体数组结构体
typedef struct{
	uint16_t URxCounter;             	//接收数据个数
	UCB_URxBuffptr URxDataPtr[NUM];  	//数据组指针结构体数组
	UCB_URxBuffptr *URxDataIN;       	//IN指针
	UCB_URxBuffptr *URxDataOUT;      	//OUT指针
	UCB_URxBuffptr *URxDataEND;      //END指针
}UCB_CB;

3、串口初始化函数修改

(1)首先在Serial.c文件中包含Serial.h文件,否则无法使用其中的定义。

(2)核对串口初始化函数Serial_Init。

cpp 复制代码
void Serial_Init(void){
	//开启GPIO和USART的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//USART1_TX(PA9)配置为复用输出模式,USART1_RX(PA10)配置为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;           //上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;              //波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不使用硬件流控
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;  //支持发送和接收功能
	USART_InitStructure.USART_Parity = USART_Parity_No;     //无校验
	USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位为1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //无校验,一共8位数据
	USART_Init(USART1, &USART_InitStructure);
	
	//开启中断用于接收数据,配置NVIC
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);    //RXNE位置为1,也就是RDR中有新数据,会触发一次中断(不使用中断会消耗很多软件资源)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //分组方式2
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //USART1到NVIC的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //开启中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        //响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART1
	USART_Cmd(USART1, ENABLE);
}

4、DMA初始化函数修改

(1)DMA的数据转运方向配置为串口外设数据寄存器至接收数据缓存区。

(2)单次数据传输是否结束是由另外的中断判断,不依赖DMA的传输计数器,因此将传输计数器设置为大于单次数据传输允许的数据大小最大值。

(3)针对以上两点,核对DMA初始化函数MyDMA_Init。

cpp 复制代码
void MyDMA_Init(uint16_t Size)
{
	MyDMA_Size = Size;	//记录传入的Size值(一轮转运的数据个数/传输计数器初值)
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//开启DMA的时钟
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;					//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;	//外设基地址,设置为USART1的数据寄存器地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //每次转运1个字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //起点的地址不自增
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)U0_RxBuff;    //终点的基地址,设置为接收数据缓冲区数组首地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  //每次转运1个字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //终点的地址自增
	DMA_InitStructure.DMA_BufferSize = U0_RX_MAX+1;    //传输计数器初值,设置为大于单次传输最大值
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;   //单次模式(计数器自减为0,一轮转运结束)
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点(DMA_PeripheralBaseAddr)作为数据源(方向参数)
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;    //使用软件触发(DMA开启后,只要传输计数器的值不为0,转运会一直进行)
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;  //指定优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	//DMA1_Channel1:选择DMA1的通道1进行AddrA->AddrB的数据转运
}

5、接收数据缓冲区初始化函数编写

(1)首先在Serial.c文件中定义接收数据缓冲区以及数据组指针结构体数组结构体。

cpp 复制代码
uint8_t U0_RxBuff[U0_RX_SIZE];       //接收数据缓冲区
UCB_CB U0CB;                     //数据组指针结构体数组结构体

(2)IN指针和OUT指针初始化为指向结构体数组的首个成员的位置,END指针初始化为指向结构体数组最后一个成员的位置,该初始化函数也定义在Serial.c文件中。

cpp 复制代码
void U0Rx_PtrInit(void)
{
	U0CB.URxDataIN = &U0CB.URxDataPtr[0];  //IN指针指向结构体数组首个成员的位置
	U0CB.URxDataOUT = &U0CB.URxDataPtr[0];//OUT指针指向结构体数组首个成员的位置
	U0CB.URxDataEND = &U0CB.URxDataPtr[NUM-1];  //END指针指向结构体数组最后一个成员的位置
	
	U0CB.URxDataIN->start = U0_RxBuff;
	
	U0CB.URxCounter = 0;       //结构体数组中的"有效成员"数量为0
}
相关推荐
Ch_champion4 小时前
LVGL 音量调节示例
嵌入式
lingzhilab6 小时前
零知派——STM32驱动摇杆+PCA9685实现4路360°舵机线性速度控制与自动演示
stm32·单片机·嵌入式硬件
不脱发的程序猿7 小时前
嵌入式软件工程师,怎么把 AI 工具用顺手?
人工智能·单片机·嵌入式硬件·嵌入式
平凡灵感码头7 小时前
芯片合封是个嘛?
单片机·嵌入式硬件
珠海西格电力8 小时前
零碳园区的碳排放指标计算的实操步骤
大数据·运维·人工智能·物联网·能源
gscsded9 小时前
C2000 GPIO 配置笔记
单片机
Sakuyu434689 小时前
STM32基础
stm32·单片机·嵌入式硬件
gscsded10 小时前
C2000 CPU Timer 学习笔记
单片机
iCxhust11 小时前
AD0808调试笔记
笔记·单片机·嵌入式硬件·操作系统·微机原理·8088单板机
木子单片机11 小时前
基于51单片机的步进电机调速系统设计
单片机·嵌入式硬件·51单片机·keil