打开主菜单

谷雨文档中心 β

更改

NBDK-L4:基础实验教程

添加13,964字节2019年3月20日 (三) 14:38
实验23-串口升级(app)
KEY_Poll();
}
}
</syntaxhighlight>
 
==== gyu_util.c ====
请参照实验01中的介绍。
 
基础实验中的其他例程,大部分都是使用的相同的时钟配置函数,有特殊的时钟使用,将会在对应例程的源码详解中做针对性说明。
==== gyu_iap_uart_crc.c ====
初始化IAP需要使用到的串口和CRC校验部分的文件。
 
定义对齐方式,定义512字节对齐。并且定义了中断向量表的地址存储buf。<syntaxhighlight lang="c++" line="1" start="23">
// 定义对齐方式
#if defined ( __ICCARM__ )
#pragma data_alignment=512
#elif defined ( __CC_ARM )
__align(512)
#elif defined ( __GNUC__ ) /* GCC Compiler */
__attribute__ ((aligned (512)))
#endif
uint32_t vector_t[72];
</syntaxhighlight>用于重新定义中断向量表的位置。<syntaxhighlight lang="c++" line="1" start="42">
void Relocate_NVIC(uint32_t address)
{
uint32_t i;
 
for (i = 0;i < 72;i++)
{
vector_t[i] = *((uint32_t*)(address + (i << 2)));
}
SCB->VTOR = (uint32_t)vector_t;
}
</syntaxhighlight>初始化串口和CRC校验,为后面的串口升级做准备。这边的串口协议就是我们在超级终端中选择的COM口协议。<syntaxhighlight lang="c++" line="1" start="54">
// 串口结构体
UART_HandleTypeDef UartHandle;
// CRC结构体
CRC_HandleTypeDef CrcHandle;
 
//******************************************************************
// fn : COM_Init
//
// brief : 串口初始化以及CRC初始化
//
// param : none
//
// return : none
void COM_Init(void)
{
// 初始化串口功能
UartHandle.Instance = USART1;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
if (HAL_UART_Init(&UartHandle) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
// 初始化CRC功能
CrcHandle.Instance = CRC;
CrcHandle.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_DISABLE;
CrcHandle.Init.GeneratingPolynomial = 0x1021;
CrcHandle.Init.CRCLength = CRC_POLYLENGTH_16B;
CrcHandle.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_DISABLE;
CrcHandle.Init.InitValue = 0;
CrcHandle.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE;
CrcHandle.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE;
CrcHandle.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES;
if (HAL_CRC_Init(&CrcHandle) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
 
//******************************************************************
// fn : HAL_UART_MspInit
//
// brief : 串口初始化函数
//
// param : huart -> 串口句柄
//
// return : none
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitStruct;
 
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); // 使能USART1时钟
 
// 配置串口TX引脚
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
// 配置串口RX引脚
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
 
//******************************************************************
// fn : COM_Init
//
// brief : 串口初始化以及CRC初始化
//
// param : none
//
// return : none
void HAL_CRC_MspInit(CRC_HandleTypeDef *hcrc)
{
// 使能CRC时钟
__HAL_RCC_CRC_CLK_ENABLE();
}
</syntaxhighlight>
 
=== menu.c ===
这个文件是IAP在超级终端中主要做显示菜单的,也就是格式化打印一些调试的数据,当然也是底层其他函数的入口(这边说的底层函数指的是flash_if、ymodem、common这三个)。
 
这个函数是使用汇编语言编写,目的是切换程序运行的指针,将指针指向bank2地址(这个函数编译的时候,keil中会打一个X,但是不会报错)。<syntaxhighlight lang="c++" line="1" start="42">
__asm void SwitchToBand2(int addr)
{
ldr sp, [r0]
add r0, #4
ldr pc, [r0]
}
</syntaxhighlight>串口下载数据的函数,超级终端升级app文件的时候,就是调用的此函数。<syntaxhighlight lang="c++" line="1" start="57">
void SerialDownload(void)
{
uint8_t number[11] = {0};
uint32_t size = 0;
COM_StatusTypeDef result;
 
// 打印等待文件烧写的提示
Serial_PutString((uint8_t *)"Waiting for the file to be sent ...\n\r");
// 通过超级终端使用Ymodem协议去处理文件的下载
result = Ymodem_Receive( &size, BankActive );
// 如果写入成功,打印文件名及其大小
if (result == COM_OK)
{
Serial_PutString((uint8_t *) "\n\n\r Programming Completed Successfully!\n\r--------------------------------\r\n Name: ");
Serial_PutString(aFileName);
Int2Str(number, size);
Serial_PutString((uint8_t *)"\n\r Size: ");
Serial_PutString(number);
Serial_PutString((uint8_t *)" Bytes\r\n");
Serial_PutString((uint8_t *)"-------------------\n");
}
// 如果写入失败,打印失败提示
else
{
Serial_PutString((uint8_t *)"\n\r Programming Failed!\n\r");
}
}
</syntaxhighlight>主菜单函数,用于格式化打印一个菜单栏,这边给大家说一下,我们的IAP例程是脱胎于ST提供的一个IAP实例,所以这边保留了ST本身的菜单栏(也就是作者是ST公司)。<syntaxhighlight lang="c++" line="1" start="94">
void Main_Menu(void)
{
// 超级终端上打印菜单栏说明
Serial_PutString((uint8_t *)"\r\n======================================================================");
Serial_PutString((uint8_t *)"\r\n= (C) COPYRIGHT 2016 STMicroelectronics =");
Serial_PutString((uint8_t *)"\r\n= =");
Serial_PutString((uint8_t *)"\r\n= STM32L476 Dual Bank Usage Example Application (Version 0.1.0) =");
Serial_PutString((uint8_t *)"\r\n= =");
Serial_PutString((uint8_t *)"\r\n= By MCD Application Team =");
Serial_PutString((uint8_t *)"\r\n======================================================================");
Serial_PutString((uint8_t *)"\r\n\r\n");
 
// 获取当前配置
HAL_FLASHEx_OBGetConfig( &OBConfig );
// 解锁flash写保护,允许写入数据
FLASH_If_WriteProtectionClear();
 
// 通过串口下载文件
SerialDownload();
}
</syntaxhighlight>
 
=== flash_if.c ===
此文件用于处理STM32L4的内部flash,主要是负责擦除、写入,并且留了一个检测bank2中是否有app程序的函数。
 
擦除flash的函数,使用的方式是擦除整个扇区,和我们实验21-内部flash一页一页擦除有所不同。<syntaxhighlight lang="c++" line="1" start="34">
uint32_t FLASH_If_Erase(uint32_t bank_active)
{
uint32_t bank_to_erase, error = 0;
FLASH_EraseInitTypeDef pEraseInit;
HAL_StatusTypeDef status = HAL_OK;
 
if (bank_active == 0)
{
bank_to_erase = FLASH_BANK_2;
Serial_PutString((uint8_t *)"Erasing bank 2.\r\n");
}
else
{
bank_to_erase = FLASH_BANK_1;
Serial_PutString((uint8_t *)"Erasing bank 1.\r\n");
}
 
// 解锁Flash以启用闪存控制寄存器访问
HAL_FLASH_Unlock();
 
pEraseInit.Banks = bank_to_erase;
pEraseInit.NbPages = 255;
pEraseInit.Page = 0;
pEraseInit.TypeErase = FLASH_TYPEERASE_MASSERASE;
status = HAL_FLASHEx_Erase(&pEraseInit, &error);
// 锁定Flash以禁用闪存控制寄存器访问(建议保护FLASH存储器免受可能的意外操作)
HAL_FLASH_Lock();
 
if (status != HAL_OK)
{
// 页面擦除时出错
return FLASHIF_ERASEKO;
}
return FLASHIF_OK;
}
</syntaxhighlight>检测bank2中是否存在app程序,留给main()函数调用的。<syntaxhighlight lang="c++" line="1" start="81">
uint32_t FLASH_If_Check(uint32_t start)
{
// 检查数据是否可以是代码(第一个字是堆栈位置)
if ((*(uint32_t*)start >> 24) != 0x20 ) return FLASHIF_EMPTY;
 
return FLASHIF_OK;
}
</syntaxhighlight>在闪存中写入数据缓冲区(数据为32位对齐),写入数据缓冲区后,将检查Flash内容。<syntaxhighlight lang="c++" line="1" start="99">
uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length)
{
uint32_t status = FLASHIF_OK;
uint32_t i = 0;
 
// 解锁Flash以启用闪存控制寄存器访问
HAL_FLASH_Unlock();
 
// DataLength必须是64位的倍数
for (i = 0; (i < length / 2) && (destination <= (USER_FLASH_END_ADDRESS - 8)); i++)
{
//器件电压范围应为[2.7V至3.6V],通过DOUBLEWORD操控
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, destination, *((uint64_t *)(p_source + 2*i))) == HAL_OK)
{
// 检测写入的值,如果flash内容与SRAM不匹配
if (*(uint64_t*)destination != *(uint64_t *)(p_source + 2*i))
{
status = FLASHIF_WRITINGCTRL_ERROR;
break;
}
// 增加FLASH目标地址,加8bit
destination += 8;
}
else
{
// 在闪存中写入数据时出错
status = FLASHIF_WRITING_ERROR;
break;
}
}
 
// 锁定Flash以禁用闪存控制寄存器访问(建议保护FLASH存储器免受可能的意外操作)
HAL_FLASH_Lock();
 
return status;
}
 
</syntaxhighlight>配置写保护,调用此函数,可以解锁flash写保护,允许写入数据。<syntaxhighlight lang="c++" line="1" start="144">
uint32_t FLASH_If_WriteProtectionClear( void )
{
FLASH_OBProgramInitTypeDef OptionsBytesStruct1;
HAL_StatusTypeDef retr;
 
// 解锁Flash以启用闪存控制寄存器访问
retr = HAL_FLASH_Unlock();
 
// 解锁选项字节
retr |= HAL_FLASH_OB_Unlock();
 
OptionsBytesStruct1.RDPLevel = OB_RDP_LEVEL_0;
OptionsBytesStruct1.OptionType = OPTIONBYTE_WRP;
OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK2_AREAA;
OptionsBytesStruct1.WRPEndOffset = 0x00;
OptionsBytesStruct1.WRPStartOffset = 0xFF;
retr |= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
 
OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK2_AREAB;
retr |= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
 
return (retr == HAL_OK ? FLASHIF_OK : FLASHIF_PROTECTION_ERRROR);
}
</syntaxhighlight>
 
=== ymodem.c ===
这个文件里面处理的是超级终端下载app程序时的ymodem协议,具体协议代码这边不做详解,需要的朋友烦请自行阅读。
 
=== common.c ===
通用文件,里面分别是字符串与整数的转化,以及串口格式化打印。
 
整数转字符串、字符串转整数的两个函数。<syntaxhighlight lang="c++" line="1" start="24">
//******************************************************************
// fn : Int2Str
//
// brief : 将整数转换为字符串
//
// param : p_str -> 字符串输出指针
// intnum -> 要转换的整数
//
// return : none
void Int2Str(uint8_t *p_str, uint32_t intnum)
{
uint32_t i, divider = 1000000000, pos = 0, status = 0;
 
for (i = 0; i < 10; i++)
{
p_str[pos++] = (intnum / divider) + 48;
 
intnum = intnum % divider;
divider /= 10;
if ((p_str[pos-1] == '0') & (status == 0))
{
pos = 0;
}
else
{
status++;
}
}
}
 
//******************************************************************
// fn : Str2Int
//
// brief : 将字符串转换为整数
//
// param : p_inputstr -> 要转换的字符串
// p_intnum -> 整数值
//
// return : 1正确,0错误
uint32_t Str2Int(uint8_t *p_inputstr, uint32_t *p_intnum)
{
uint32_t i = 0, res = 0;
uint32_t val = 0;
 
if ((p_inputstr[0] == '0') && ((p_inputstr[1] == 'x') || (p_inputstr[1] == 'X')))
{
i = 2;
while ( ( i < 11 ) && ( p_inputstr[i] != '\0' ) )
{
if (ISVALIDHEX(p_inputstr[i]))
{
val = (val << 4) + CONVERTHEX(p_inputstr[i]);
}
else
{
res = 0;
break;
}
i++;
}
 
if (p_inputstr[i] == '\0')
{
*p_intnum = val;
res = 1;
}
}
else
{
while ( ( i < 11 ) && ( res != 1 ) )
{
if (p_inputstr[i] == '\0')
{
*p_intnum = val;
res = 1;
}
else if (((p_inputstr[i] == 'k') || (p_inputstr[i] == 'K')) && (i > 0))
{
val = val << 10;
*p_intnum = val;
res = 1;
}
else if (((p_inputstr[i] == 'm') || (p_inputstr[i] == 'M')) && (i > 0))
{
val = val << 20;
*p_intnum = val;
res = 1;
}
else if (ISVALIDDEC(p_inputstr[i]))
{
val = val * 10 + CONVERTDEC(p_inputstr[i]);
}
else
{
res = 0;
break;
}
i++;
}
}
 
return res;
}
</syntaxhighlight>串口格式化打印数据的函数。<syntaxhighlight lang="c++" line="1" start="128">
//******************************************************************
// fn : Serial_PutString
//
// brief : 在超级终端上打印一个字符串
//
// param : p_string -> 要打印的字符串
//
// return : none
void Serial_PutString(uint8_t *p_string)
{
uint16_t length = 0;
 
while (p_string[length] != '\0')
{
length++;
}
HAL_UART_Transmit(&UartHandle, p_string, length, TX_TIMEOUT);
}
 
//******************************************************************
// fn : Serial_PutString
//
// brief : 将一个字节传输到超级终端
//
// param : param -> 要发送的字节
//
// return : HAL_StatusTypeDef -> 如果正确则返回HAL_OK
HAL_StatusTypeDef Serial_PutByte( uint8_t param )
{
/* May be timeouted... */
if ( UartHandle.gState == HAL_UART_STATE_TIMEOUT )
{
UartHandle.gState = HAL_UART_STATE_READY;
}
return HAL_UART_Transmit(&UartHandle, &param, 1, TX_TIMEOUT);
}
</syntaxhighlight>
510
个编辑