- 2024-03-24
-
发表了主题帖:
【极海APM32F407 Tiny Board】 5.基于LWIP的IAP升级
# 1 LWIP
## 1.1 LWIP简介
LwIP 是一款轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 AdamDunkels 开发的一个小型开源的 TCP/IP 协议栈。在保持 TCP 协议主要功能的基础上减少对资源的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。 可以在网页http://savannah.nongnu.org/projects/lwip/, 下载获取到 LwIP 的各个版本的源代码包和对应的contrib 包。
本次计划基于上一篇LWIP移植的基础上实现LWIP网络传输固件进行升级。
## 1.2 硬件设计
开发板使用 APM32F407 控制器通过 RMII 接口和 SMI 接口与 LAN8720A 以太网 PHY 进行连接。通过接下拉电阻把 nINTSEL 引脚设置为低电平,从而使能 nINT/REFCLKO 引脚的输出功能为RMII 接口中 REF_CLK 信号线提供时钟信号, 硬件上 XTAL1 与 XTAL2 之间接入提供 25MHz时钟,经过 LAN8720A 内部 PLL 电路陪频后使得 nINT/REFCLKO 引脚的输出的时钟信号为50MHz 时钟。
# 2 移植步骤
## 2.1 Bootloader配置
本次由于在bootloader程序中添加了lwip的代码,需要将bootloader的大小调整一下,初步编译了一下,大概占用30k左右的样子,这里进行试验就调整大一点,200k
应用程序配置,只需要调整APP的起始地址即可。
同时应用APP程序中只需要一个简单的例子即可,2s闪烁一次LED
接下来在bootloader建立TcpServer服务器,端口设置为80,代码基于lwip移植和串口IAP升级的工程合并而成。
Lwip接收数据回调函数,连接收向服务器发送消息证明建立通讯
接下来就是接收数据后的回调函数,先将接收的数据返回发送到服务器
实现Tcpserver这个功能后在实现代码升级的部分,然后初始化函数放在主函数中初始化。
打开网络测试工具,先ping一下,ping正常
接下来点击打开TCP,可以发现lwip Tcpserver connect 这串字符上传,这是代码里面写的,说明成功,接下来向服务器发送数据11,返回11。说明功能已正常使用
## 2.2 网络固件传输调整
由于最开始使用的串口升级的的方式,现在需要使用lwip实现,从串口IAP升级中将串口接收的数据固定存放在一个区域就可以,lwip升级同样也是。通过对lwip接收到的数据存放在我们的app段地址,通过按键去启动。
可以同样使用串口的缓冲,将lwip接收的数据存放在串口升级的缓冲区,首先找到lwip接收数据的接口tatic err_t tcp1_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)。其lwip接收到的数据存放在tcp_recv_pbuf这个结构体中。
将接收到数据指针指向我们的串口接收缓冲区,同时拿出接收的长度
串口缓冲区数据存放地址
在主循环中通过检测按键进行代码升级和运行APP。检测到lwip接收的数据不为0,将接收的数据写入到指定区域。
# 3 验证
打开网络工具,本次使用野火的上位机,可以发文件,点击网络助手,配置协议类型为TCP Client,以及开发板的地址,端口,点击建立连接
接下来点击右下角的加载文件,选择APP代码生成的bin文件,如下所示
点击发送数据,接下来就可以通过串口看到输出消息,按下按键,执行用户程序。
正确执行升级后的代码,LED灯按APP的代码执行。
- 2024-03-17
-
发表了主题帖:
【极海APM32F407 Tiny Board】 4.LWIP移植
# 1 LWIP
## 1.1 LWIP简介
LwIP 是一款轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 AdamDunkels 开发的一个小型开源的 TCP/IP 协议栈。在保持 TCP 协议主要功能的基础上减少对资源的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。 可以在网页http://savannah.nongnu.org/projects/lwip/, 下载获取到 LwIP 的各个版本的源代码包和对应的contrib 包。
本次的移植目的是为了实现LWIP进行代码升级,因此本篇文章先实现底层功能,方便后续操作。
## 1.2 硬件设计
开发板使用 APM32F407 控制器通过 RMII 接口和 SMI 接口与 LAN8720A 以太网 PHY 进行连接。通过接下拉电阻把 nINTSEL 引脚设置为低电平,从而使能 nINT/REFCLKO 引脚的输出功能为RMII 接口中 REF_CLK 信号线提供时钟信号, 硬件上 XTAL1 与 XTAL2 之间接入提供 25MHz时钟,经过 LAN8720A 内部 PLL 电路陪频后使得 nINT/REFCLKO 引脚的输出的时钟信号为50MHz 时钟。
# 2 移植步骤
## 2.1 底层驱动添加
这是官方提供的ETH驱动库,标准库中没有添加
添加完如下所示:
添加LAN8720外设驱动,完成以太网的GPIO,时钟等配置,也相当于一个外设驱动文件。
## 2.2 添加Lwip源码
找到lwip-1.4.1\src\api文件,全部添加
lwip-1.4.1\src\core\ipv4下文件全部添加:
lwip-1.4.1\src\core下除了ipv6以及snmp文件夹,其他源码全部添加:
lwip-1.4.1\src\netif下添加,etharp.c文件:
添加头文件路径,当然中间步骤太多,因此部分省略
中间配置网卡等步骤省略,主要说明部分配置文件及实现逻辑,网络配置:
Lwip初始化,主要设置设置的IP地址,网关等
主程序,网络配置初始化,通过串口打印网络配置信息:
程序验证lwip是否移植成功,主要通过电脑进行ping
接下来配置电脑网络的相关配置:
# 3 验证
接下来打开电脑的CMD,ping一下开发板的ip,如下所示,lwip移植成功,文章是移植完后才写的,中间很多步骤省略。
- 2024-03-16
-
发表了主题帖:
【极海APM32F407 Tiny Board】 3.IAP升级
# 1 IAP
## 1.1 IAP简介
IAP(In Application Programming)即在应用编程。是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。实现代码升级,可通过UART、SPI、IIC、USB等等传递需要升级的固件。实现即将flash或者其他存储器分成多个段,存储升级的代码,一般设计分为两个部分,第一部分用于实现通讯升级代码,叫bootloader,第二部分为应用程序,即正常运行的代码。
## 1.2 启动流程
程序启动后,先从 0x08000004 处取出复位中断向量地址,执行完复位中断函数后跳转到IAP 程序 main 函数中执行[①]。
当发生中断请求后,程序跳转到中断向量表中取出中断函数入口地址,再跳转到中断服务函数中执行[②],执行完中断函数后返回 main 函数中[③],然后执行 IAP 过程,成功后跳转到 APP程序[④]。
从偏移后的中断向量表得到相应中断函数地址,执行相应新的中断服务函数后,回到 APP的 main 函数中[⑤]。后面[⑥⑦⑧]的过程和前述一致。
## 1.3 中断向量表设置
APM32F4xxSDK 的 system_apm32f4xx.c 文件中可以找到 VECT_TAB_OFFSET 这个向量表偏移量宏定义来重新设置中断向量表的地址,也即是修改 SCB->VTOR 向量表偏移量寄存器。
# 2 移植步骤
## 2.1 Bootlaoder设计
Flash IAP BootLoader的起始地址为0x08000000,通过串口1接收升级固件,使用64k的空间用于存储bootloader,因此应用程序的地址为0x0801000。
首先是bootloader程序,配置按键,led,串口等相关外设
设置工程文件的ROM的起始地址为0x8000000,大小为64k
代码中初始化串口1用来进行应用程序的接收,同时打印状态信息。
## 2.2 App程序设计
APP 是主用户程序,在完成 IAP 程序的设计后,就要把 APP 的更新文件生成,然后通过一定的协议传输给 IAP 程序来进行 APP 固件的更新。 一般来说 APP 更新文件的文件类型为.bin 文件,该文件可以直接拷贝到 flash 中运行。
设置工程文件的起始地址为0X8010000
在 Keil MDK Option 配置的 User 选项卡中配置以下命令, 即可使用 fromelf.exe 生成 bin 文件,默认生成在工程目录。
准备好APP应用程序,这里使用之前的工程文件,把其他外设都关了,就留一个LED灯进行验证,2s闪烁进行区分。
# 3 验证
接下来打开终端,使用串口工具选择APP程序的bin文件
加载然后进行发送:
应用程序开始执行,LED灯2s的周期开始闪烁。
- 2024-03-03
-
发表了主题帖:
【极海APM32F407 Tiny Board】 2.LVGL8.2移植体验
一、移植准备
下载LVGL源码:https://github.com/lvgl/lvgl/,解压后,文档如下所示:
将lv_conf_template.h改成lv_conf.h,命令可以随意更改,一个配置文件,同时打开文件,将#if0调整为# if 1,不然编译的回收会出很多错。
接下来删除一下文件,也可以不删除,删除了方便管理
二、移植过程
在之前的基础上添加lvgl的源码,文件比较多,不一一说明,同时这里添加的时候可以进行分类添加,这样就没有这么乱,查找也方便
添加头文件路径:
添加定时器初始化,定时1ms,,添加lvgl头文件
添加LVGL与LCD的接口处理:
/**
* [url=home.php?mod=space&uid=1307177]@File[/url] lv_port_disp_templ.c
*
*/
/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_disp_template.h"
#include "../../lvgl.h"
#include "./BSP/LCD/lcd.h"
#include "gui.h"
#include "lv_demo_keypad_encoder.h"
#define MY_DISP_HOR_RES (320)
#define MY_DISP_VER_RES (240)
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
// const lv_area_t * fill_area, lv_color_t color);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
/* Example for 1) */
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 2) */
// static lv_disp_draw_buf_t draw_buf_dsc_2;
// static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
// static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/
// lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
/* Example for 3) also set disp_drv.full_refresh = 1 below*/
// static lv_disp_draw_buf_t draw_buf_dsc_3;
// static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/
// static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/
// lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res = lcddev.width;
disp_drv.ver_res = lcddev.height;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_1;
/*Required for Example 3)*/
//disp_drv.full_refresh = 1
/* Fill a memory array with a color if you have GPU.
* Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
* But if you have a different GPU you can use with this callback.*/
//disp_drv.gpu_fill_cb = gpu_fill;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
/*You code here*/
LCD_Init();
LCD_direction(1);
}
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
lcd_color_fill(area->x1,area->y1,area->x2,area->y2,color_p);
// LCD_Fill(area->x1,area->y1,area->x2,area->y2,(uint16_t)color_p);
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
/*OPTIONAL: GPU INTERFACE*/
/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*/
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
// const lv_area_t * fill_area, lv_color_t color)
//{
// /*It's an example code which should be done by your GPU*/
// int32_t x, y;
// dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
//
// for(y = fill_area->y1; y <= fill_area->y2; y++) {
// for(x = fill_area->x1; x <= fill_area->x2; x++) {
// dest_buf[x] = color;
// }
// dest_buf+=dest_width; /*Go to the next line*/
// }
//}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
修改LCD的颜色填充函数:
void lcd_color_fill(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, lv_color_t *color)
{
uint32_t y=0;
u16 height, width;
width = ex - sx + 1;
height = ey - sy + 1;
LCD_SetWindows(sx,sy,ex,ey);
for(y = 0; y <width*height; y++)
{
Lcd_WriteData_16Bit(color->full);
color++;
}
}
添加测试代码,如下所示:
int main(void)
{
NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_3);
sys_apm32_clock_init(336, 8, 2, 7);
delay_init(168);
usart_init(115200);
btmr_tmrx_int_init(10-1, 8400-1);
led_init();
lv_init();
lv_port_disp_init();
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label,"APM32F407 LVGL8.2");
lv_obj_center(label);
#if 0
nes_main();
#endif
while (1)
{
lv_timer_handler();
LED0_TOGGLE();
delay_ms(500);
}
}
三、结果
显示效果如下,有兴趣的可以继续跑一下官方例程,或者自己写一个
- 2024-02-19
-
发表了主题帖:
【极海APM32F407 Tiny Board】 1.NES模拟器
NES模拟器
一、NES简介
NES是“Nintendo Entertainment System”的缩写,它是任天堂公司在1980年代推出的一款家用游戏机。在日本,它被称为Famicom(Family Computer)。NES于1983年在日本首次推出,随后于1985年在北美地区发行,成为当时家庭游戏市场的主导力量。
这款游戏机因其丰富的游戏库、经典的游戏系列以及在家庭游戏市场的影响力而备受推崇。它引入了许多经典游戏,如《马里奥兄弟》(Super Mario Bros.)、《塞尔达传说》(The Legend of Zelda)、《魂斗罗》(Contra)和《魔界村》(Castlevania)等,这些游戏至今仍然被认为是游戏史上的经典之作。
二、移植准备
2.1 资料下载
首先下载相关资料,函数库,以及一些示例,电路原理图等,如下所示
本次使用的是RTT进行开发,需要下载板级支持包
同时将PYOCD更新到最新版本,不然可能存在下载失败的情况
接下来新建RTT工程,如下所示,主函数1s打印一次
使用外接串口与开发板的串口1连接,打开终端,可以看到1s打印1次
由于打印信息不方便观察,且有时候需要用终端,这里将打印信息调整为板载的LED灯,类似于心跳灯。查看板载的LED灯原理图,如下所示:
添加心跳灯代码,使用GET_PIN函数时需要包含drv_common.h头文件,不然编译会报错。
在使用RTT开发过程中遇到很多坑,于是就放弃了使用RTT开发,果断入手标准库进行开发,时间小于用RTT开发。本次使用一个SPI的例程进行修改调整,添加spi,LCD,以及NES模拟器代码,由于使用RTT花了太多时间,过程就不在详细介绍,步骤也比较多。
2.2 SPI驱动
本次使用的是SPI1与ili9341进行连接,配置如下:
读写函数:
2.3 LCD显示实现
添加LCD源码,添加LCD路径,替换头文件等
LCD引脚配置:
接下来就是写数据与写命令函数的替换,将内部函数调整为SPI的读写函数:
三、移植过程
NES模拟器移植这里不详细进行介绍,添加源码,头文件路径
调整NES模拟器显示内容:
主函数,添加nes头文件,以及执行函数:
四、效果
[localvideo]ed24c744f62b7b3cb4cd60c43e8c97cc[/localvideo]
- 2024-02-04
-
回复了主题帖:
测评入围名单(最后一批):年终回炉,FPGA、AI、高性能MCU、书籍等42个测品
信息确认无误,感谢管理员