基于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
}
相关推荐
北京耐用通信2 小时前
耐达讯自动化CC-Link IE转DeviceNet网关:破解三菱与欧姆龙PLC协同壁垒的工业实践
人工智能·科技·物联网·网络协议·自动化
敬畏_上帝3 小时前
PCtolLCD2002完美版下载以及教程
单片机·嵌入式硬件
搜佛说3 小时前
sfsDb 所代表的“融合型”数据库将为未来的一个重要方向
数据库·物联网·边缘计算·时序数据库·iot
学嵌入式的小杨同学3 小时前
STM32 进阶封神之路(五):库函数移植全解析 —— 从底层原理到移植实操(含环境适配 + 报错解决)
vscode·单片机·嵌入式硬件·代理模式·智能硬件·pcb工艺·嵌入式实时数据库
天月风沙4 小时前
幻尔总线舵机测试板BusLinker V2.5 控制代码
单片机·嵌入式硬件·机器人·舵机
somi74 小时前
51单片机-01-基础概念
单片机·嵌入式硬件·学习·51单片机
✎ ﹏梦醒͜ღ҉繁华落℘4 小时前
单片机基础知识 -- TFT-LCD
单片机·嵌入式硬件
Hello_Embed5 小时前
LVGL 入门(一):环境搭建与源码获取
笔记·stm32·单片机·嵌入式·lvgl
v先v关v住v获v取6 小时前
CC1031载货汽车后轮制动器设计6张cad+设计说明书+三维图
科技·单片机·51单片机
阿拉斯攀登6 小时前
第 2 篇 小白前置知识急救包!RK 安卓驱动开发必备知识点,一篇补全
c语言·嵌入式·rk3568·安卓驱动