• 国民技术Cortex-M0系列单片机IAP升级


    考虑到设备部署到现场后有可能需要进行软件升级,之前做过PIC系列单片机的升级,现在想做个国民技术N32G031系列Cortex-M0内核的单片机IAP方案。

    因为国民技术系列单片机在很多大程度上都模仿了STM32,所以我想其升级方案极有可能差不多。于是在网上下载了STM32官方使用YMODEM协议实现的IAP,下载地址:STSW-STM32008 - STM32F10xxx in-application programming using the USART (AN2557) - STMicroelectronics,使用野火的STM32开发实测过是没有问题的,于是在它的基础上进行修改,移植到N32G031系列单片机中来,经过一番折腾还是搞定了,现在把相关内容分享下,另外资源可以在以下链接中下载,无需积分。

    【免费】国民技术N32G031使用YMODEM协议实现IAP资源-CSDN文库

    另外,关于YMODEM协议及secureCRT的使用可以参考以下链接:

    stm32 Bootloader设计(YModem协议)-CSDN博客

    关于secureCRT的破解方法请参考以下链接:

    尝试SecureCRT_securecrt issue date-CSDN博客

    主要介绍IAP程序的main.c文件,代码如下:

    1. /**
    2. * @file main.c
    3. * @author Power
    4. * @version V1.0.1
    5. *
    6. * @copyright Copyright (c) 2023, DS.
    7. */
    8. /*
    9. MIPS的全称是Million Instructions Per Second,
    10. 每秒百万指令(西方或者国际上的计量体系中1M(兆)=100万=1000000);Mhz,是指单片机CPU的主频兆赫兹。
    11. 单条指令执行时间:STM32F10X单片机在主频为72MHz下,C语言程序执行一条指令需要的时间可认为10ns~100ns。
    12. 国民技术系列N32G031 MCU,以主频48MHz为例,这里估算的C语言执行一条指令的时间约为20ns-200ns
    13. */
    14. #include "main.h"
    15. #include "common.h"
    16. #include "ymodem.h"
    17. #define COMn 2
    18. #define RS485_GPIOx_CLK RCC_APB2_PERIPH_GPIOF //peripheral adress
    19. #define RS485_GPIO_PIN GPIO_PIN_6 //pin address
    20. #define RS485_GPIOx GPIOF //port address
    21. #define RS485_L() GPIO_ResetBits(RS485_GPIOx, RS485_GPIO_PIN)//RECEIVE_ mode
    22. #define RS485_H() GPIO_SetBits(RS485_GPIOx, RS485_GPIO_PIN)//TRANSMIT mode
    23. typedef enum
    24. {
    25. COM1 = 0,
    26. COM2 = 1
    27. } COM_TypeDef;
    28. /* Private typedef -----------------------------------------------------------*/
    29. /* Private define ------------------------------------------------------------*/
    30. /* Private macro -------------------------------------------------------------*/
    31. /* Private variables ---------------------------------------------------------*/
    32. extern pFunction Jump_To_Application;
    33. extern uint32_t JumpAddress;
    34. uint16_t led_cnt = 0;
    35. volatile uint8_t Flag_1ms; //1 milli-second timeout flag
    36. uint8_t cTemp = 10; //for RS485 mode switching delay
    37. /* Private function prototypes -----------------------------------------------*/
    38. static void IAP_Init(void);
    39. void RCC_Configuration(void);
    40. void GPIO_Configuration(void);
    41. const uint16_t COM_TX_PIN[COMn] = {EVAL_COM1_TX_PIN, EVAL_COM2_TX_PIN};
    42. const uint16_t COM_RX_PIN[COMn] = {EVAL_COM1_RX_PIN, EVAL_COM2_RX_PIN};
    43. const uint32_t COM_TX_PORT_CLK[COMn] = {EVAL_COM1_TX_GPIO_CLK, EVAL_COM2_TX_GPIO_CLK};
    44. const uint32_t COM_RX_PORT_CLK[COMn] = {EVAL_COM1_RX_GPIO_CLK, EVAL_COM2_RX_GPIO_CLK};
    45. const uint32_t COM_USART_CLK[COMn] = {EVAL_COM1_CLK, EVAL_COM2_CLK};
    46. GPIO_Module* COM_TX_PORT[COMn] = {EVAL_COM1_TX_GPIO_PORT, EVAL_COM2_TX_GPIO_PORT};
    47. GPIO_Module* COM_RX_PORT[COMn] = {EVAL_COM1_RX_GPIO_PORT, EVAL_COM2_RX_GPIO_PORT};
    48. USART_Module* COM_USART[COMn] = {EVAL_COM1, EVAL_COM2};
    49. /* Private functions ---------------------------------------------------------*/
    50. /**
    51. *@name: RS485_Configuration
    52. *@description: RS485 GPIO initialization
    53. *@params: none
    54. *@return: none
    55. */
    56. void RS485_Configuration(void)
    57. {
    58. GPIO_InitType GPIO_InitStructure;
    59. RCC_EnableAPB2PeriphClk(RS485_GPIOx_CLK, ENABLE);
    60. /* -2- Configure GPIOx_PIN in output push-pull mode */
    61. GPIO_InitStruct(&GPIO_InitStructure);
    62. GPIO_InitStructure.Pin = RS485_GPIO_PIN;
    63. GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUTPUT_PP;
    64. GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_HIGH;
    65. GPIO_InitPeripheral(RS485_GPIOx, &GPIO_InitStructure);
    66. GPIO_ResetBits(RS485_GPIOx, RS485_GPIO_PIN);
    67. }
    68. /**
    69. *@name: Process_1MS
    70. *@description: 1 milli-second timeout process
    71. *@params: none
    72. *@return: none
    73. */
    74. void Process_1MS(void)
    75. {
    76. if (Flag_1ms == 0)
    77. {
    78. return;
    79. }
    80. Flag_1ms = 0;
    81. if (led_cnt % 500 == 0) //LED toggle to see whether the timer works correctly.
    82. {
    83. GPIO_WriteBit(GPIOF, GPIO_PIN_7, (Bit_OperateType)(1 - GPIO_ReadOutputDataBit(GPIOF, GPIO_PIN_7)));
    84. }
    85. led_cnt++;
    86. if (led_cnt >= 60000)
    87. {
    88. led_cnt = 0;
    89. }
    90. }
    91. /**
    92. *@name: RS485_Mode_Switching
    93. *@description: switch the RS485 work mode
    94. *@params: mode: 1: receive, 0: transmit
    95. *@return: none
    96. */
    97. void RS485_Mode_Switching(uint8_t mode)
    98. {
    99. cTemp = 10;
    100. if(mode)
    101. {
    102. RS485_L();
    103. }
    104. else
    105. {
    106. RS485_H();
    107. }
    108. while (cTemp-- != 0);
    109. }
    110. /**
    111. * @brief Main program.
    112. * @param None
    113. * @retval None
    114. */
    115. int main(void)
    116. {
    117. /* Flash unlock */
    118. FLASH_Unlock();
    119. /* Initialize Key Button mounted on N32G031-EVAL board */
    120. //N32_EVAL_PBInit(BUTTON_KEY, BUTTON_MODE_GPIO);
    121. /* Test if Key push-button on N32G031 Board is pressed */
    122. //if (N32_EVAL_PBGetState(BUTTON_KEY) == 0x00)
    123. if (1)
    124. {
    125. /* If Key is pressed */
    126. /* Execute the IAP driver in order to re-program the Flash */
    127. RCC_Configuration();
    128. GPIO_Configuration();
    129. RS485_Configuration();
    130. TIM3_Configuration();
    131. IAP_Init();
    132. RS485_Mode_Switching(0);
    133. SerialPutString("\r\n===============================================================");
    134. SerialPutString("\r\n= (C) COPYRIGHT 2023 DS Power Supply =");
    135. SerialPutString("\r\n= =");
    136. SerialPutString("\r\n= In-Application Programming (Version 1.0.0) =");
    137. SerialPutString("\r\n= =");
    138. SerialPutString("\r\n= By POWER =");
    139. SerialPutString("\r\n===============================================================");
    140. SerialPutString("\r\n\r\n");
    141. Main_Menu();
    142. }
    143. /* Keep the user application running */
    144. else
    145. {
    146. /* Test if user code is programmed starting from address "ApplicationAddress" */
    147. if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000) == 0x20000000)
    148. {
    149. /* Jump to user application */
    150. JumpAddress = *(__IO uint32_t*)(ApplicationAddress + 4);
    151. Jump_To_Application = (pFunction) JumpAddress;
    152. /* Initialize user application's Stack Pointer */
    153. __set_MSP(*(__IO uint32_t*) ApplicationAddress);
    154. Jump_To_Application();
    155. }
    156. }
    157. while (1)
    158. {}
    159. }
    160. /**
    161. * @brief Configures COM port.
    162. * @param COM: Specifies the COM port to be configured.
    163. * This parameter can be one of following parameters:
    164. * @arg COM1
    165. * @arg COM2
    166. * @param USART_InitStruct: pointer to a USART_InitTypeDef structure that
    167. * contains the configuration information for the specified USART peripheral.
    168. * @retval None
    169. */
    170. void IAP_COMInit(COM_TypeDef COM, USART_InitType* USART_InitStruct)
    171. {
    172. GPIO_InitType GPIO_InitStructure;
    173. /* Enable GPIO clock */
    174. RCC_EnableAPB2PeriphClk(COM_TX_PORT_CLK[COM] | RCC_APB2_PERIPH_AFIO, ENABLE);
    175. /* Enable UART clock */
    176. if (COM == COM1)
    177. {
    178. RCC_EnableAPB2PeriphClk(COM_USART_CLK[COM], ENABLE);
    179. }
    180. else
    181. {
    182. RCC_EnableAPB1PeriphClk(COM_USART_CLK[COM], ENABLE);
    183. }
    184. GPIO_InitStruct(&GPIO_InitStructure);
    185. /* Configure USART Tx as alternate function push-pull */
    186. GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
    187. GPIO_InitStructure.Pin = COM_TX_PIN[COM];
    188. GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;//alternate function
    189. GPIO_InitPeripheral(COM_TX_PORT[COM], &GPIO_InitStructure);
    190. /* Configure USART Rx as input floating */
    191. GPIO_InitStructure.Pin = GPIO_PIN_10;
    192. GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;//alternate function
    193. GPIO_InitPeripheral(COM_RX_PORT[COM], &GPIO_InitStructure);
    194. /* USART configuration */
    195. USART_Init(COM_USART[COM], USART_InitStruct);
    196. /* Enable USART */
    197. USART_Enable(COM_USART[COM], ENABLE);
    198. }
    199. /**
    200. * @brief Initialize the IAP: Configure RCC, USART and GPIOs.
    201. * @param None
    202. * @retval None
    203. */
    204. void IAP_Init(void)
    205. {
    206. USART_InitType USART_InitStructure;
    207. /* USART resources configuration (Clock, GPIO pins and USART registers) ----*/
    208. /* USART configured as follow:
    209. - BaudRate = 115200 baud
    210. - Word Length = 8 Bits
    211. - One Stop Bit
    212. - No parity
    213. - Hardware flow control disabled (RTS and CTS signals)
    214. - Receive and transmit enabled
    215. */
    216. USART_InitStructure.BaudRate = 115200;
    217. USART_InitStructure.WordLength = USART_WL_8B;
    218. USART_InitStructure.StopBits = USART_STPB_1;
    219. USART_InitStructure.Parity = USART_PE_NO;
    220. USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
    221. USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
    222. IAP_COMInit(COM1, &USART_InitStructure);
    223. }
    224. /**
    225. * @brief Configures the different system clocks.
    226. */
    227. static void RCC_Configuration(void)
    228. {
    229. //PCLK1 = HCLK/4, set the prescaler of the APB1 clock and timer3 uses APB1 clock
    230. RCC_ConfigPclk1(RCC_HCLK_DIV4);
    231. //Enable GPIO clocks
    232. RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_AFIO | RCC_APB2_PERIPH_GPIOF, ENABLE);
    233. //TIM3 clock enable
    234. RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_TIM3, ENABLE);
    235. }
    236. /**
    237. *@name: GPIO_Configuration
    238. *@description: IO initialization
    239. *@params: none
    240. *@return: none
    241. */
    242. static void GPIO_Configuration(void)
    243. {
    244. GPIO_InitType GPIO_InitStructure;
    245. GPIO_InitStruct(&GPIO_InitStructure);
    246. //PF7: LED
    247. GPIO_InitStructure.Pin = GPIO_PIN_7;
    248. GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUTPUT_PP;
    249. GPIO_InitPeripheral(GPIOF, &GPIO_InitStructure);
    250. //set the default IO level
    251. GPIO_SetBits(GPIOF, GPIO_PIN_7);
    252. }
    253. #ifdef USE_FULL_ASSERT
    254. /**
    255. * @brief Reports the name of the source file and the source line number
    256. * where the assert_param error has occurred.
    257. * @param file pointer to the source file name
    258. * @param line assert_param error line source number
    259. */
    260. void assert_failed(const uint8_t* expr, const uint8_t* file, uint32_t line)
    261. {
    262. /* User can add his own implementation to report the file name and line number,
    263. ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    264. /* Infinite loop */
    265. while (1)
    266. {
    267. }
    268. }
    269. #endif
    270. /*
    271. =============关于补码的简洁概括==============
    272. 正数,本身就是补码。
    273. 负数,就用它的正数,减一取反,即可得到补码。
    274. 如,已知:+9 的二进制是:0000 1001。
    275. 下面求-9 补码:
    276. 先减一:0000 1001 - 1 = 0000 1000;
    277. 再取反:1111 0111。
    278. 所以有:-9 补码 = 1111 0111。
    279. 这不就完了吗!
    280. 简不简单? 意不意外?
    281. 如果把一个值赋给一个有符号类型,如果补码的最高位是1,则是负数,还原成实际的负数值的步骤是:
    282. 1、先按位取反
    283. 2、再加1
    284. 例如(int表示32位有符号):int a = -552305;(按正数552305的补码先减一再按位取反得到-552305二进制补码是:1111 1001 0010 1000 1111)
    285. (short表示16位无符号)short c = (short)a;
    286. 由于short最大只有16位,因此高4位被忽略,剩下1001 0010 1000 1111
    287. 又由于赋值给short,所以最高位表示符号位,这里是1表示负数,则将1001 0010 1000 1111除符号位外按位取反再加1
    288. 得到1110 1101 0111 0001,最终的结果就是除符号位外的数据:110 1101 0111 0001=-28017
    289. =============================================*/
    290. /**
    291. * @}
    292. */
    293. /**
    294. * @}
    295. */

    其实也很简单,主要就是串口和RS485相关外设的初始化,串口这里没有使用中断的方法。

    外设初始化完成后就会进入主菜单,根据用户输入的值进行不同的操作,剩下就是YMODEM协议的理解。后面有空准备自己做一个上位机来实现YMODEM协议,因为自己有逻辑分析仪,可以抓取到实际传输的数据。

    我刚开始调试的时候一直出现在secureCRT中输入菜单编号后单片机没有反应的情况,仔细检测了代码发现和STM32的也差不多,没有什么问题。于是使能串口的接收中断,我测试在secureCRT中选择菜单后串口收到的是什么,后来查看确实有问题,无论secureCRT中输入什么内容,打印出来的都是两个字节,有时候是0xFD 0xFF,有时又是0xF9 0xFF的,感觉莫名其妙的。后来想起来之前使用N32G031串口发送有时少一字节的问题,特别是在RS485通信中最容易出现,再查看本项目的下面这个函数:

    1. /**
    2. * @brief Print a character on the HyperTerminal
    3. * @param c: The character to be printed
    4. * @retval None
    5. */
    6. void SerialPutChar(uint8_t c)
    7. {
    8. USART_SendData(EVAL_COM1, c);
    9. while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TXDE) == RESET);
    10. //while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TXDE) == RESET)
    11. //{
    12. //}
    13. }

    我忽然想起来了,还有一个非常重要的语句没有调用,那就是:

     while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TXC) == RESET);

    解释如下:

    /*==========关于串口直接发送正常,而485出现少发1字节的问题==============

                我看我的代码发现我的检测标志位是USART_FLAG_TXDE(发送寄存器空)。

                以前一直用这个写也没啥问题,但是现在出现了丢失字节,我就怀疑可能是寄存器空,

                但是数据还没有完全发出(注意:当你用USART_FLAG_TXDE标志位去认为已经发完,并去控制RTS引脚的时候,485芯片会从发送转接收,

                导致如果有字节没有发完就不发了)。

                因此,我们只需要再加一个标志位检测就好。========================================================================

    USART_FLAG_TXC表示数据发送完成,只有当它置1才表示最后一个数据发送结束了。加了这个标志位检测后RS485的收发就正常工作了。

    另外,关于用户应用程序的中断向量表偏移的问题,除了在MDK中设置ROM的偏移值外,还要设置中断向量表。N32G031系列没有像STM32系列那样有类似SCB->VTOR的成员设置中断向量表的偏移,IAP接收完bin文件后用户程序总是不执行。我在网上搜索,终于找到了一篇文章专门介绍这个的:

    【精选】国民技术N32G030F6S7使用ymodem协议更新固件_n32g452 ymodem远程升级-CSDN博客

    感谢这位博主,要不然我不知道还要折腾多久。这个芯片厂家应该要把资料准备得齐全一些,尤其是这种关键的内容。

    下面是在secureCRT中主菜单和发送完成bin后的界面:

  • 相关阅读:
    Git多人协作场景的使用
    论文查重怎么看查重率
    mybatis-plus 根据任意字段saveOrUpdateBatch
    【语义分割】语义分割综述
    一年前端面试打怪升级之路
    linux: network: sysctl: netdev_budget_usecs的出现
    Spring Boot OAuth 2.0整合—高级配置
    手把手教你搭建一个Minecraft 服务器
    Promise
    Python: Decorator Pattern
  • 原文地址:https://blog.csdn.net/jmmx/article/details/134469252