- 2025-01-11
-
回复了主题帖:
毕设记录(一)——无刷直流电机的无感控制
Jacktang 发表于 2025-1-11 10:01
由于代码调用的是ST官方电机库的函数,因此底层机理难以修改,部分变量只能在ST的电机控制工具中查看,无法 ...
就是比如说我要写PID,ST电机库就有PID,但是这个PID就是单纯的PID,我想用改进PID算法,比如模糊PID啥的就比较难修改了。然后相关变量的值得在Motor Pilot里面看,具体寄存器值在程序里怎么看我还没写好查看方法。
- 2025-01-10
-
发表了主题帖:
毕设记录(一)——无刷直流电机的无感控制
本帖最后由 FuShenxiao 于 2025-1-10 21:33 编辑
实验对象:P-NUCLEO-IHM03电机开发套件,包含无刷电机控制板、无刷电机驱动板以及三相云台电机,如图1。
图1 电机开发套件
由于三相云台电机没有霍尔传感器和光电编码器用于有感控制,因此采用转子电压过零检测和直流母线电流检测用于无刷电机速度和电流的双闭环控制,其中,电机驱动电路与电压过零检测电路如图2,其控制框图如图3,实际信号传输框图如图4。其中转速调节器和电流调节器均采用带饱和的PI控制,如图5。
图2 电机驱动与电压过零检测电路
图3 无刷电机双闭环控制框图
图4 实际信号传输框图
图5 带饱和环节的PI控制器
对于速度环,取Kp=3.85,Ki=0.0124
为了实现无刷电机转速和电流双闭环控制,对程序配置如下:
电机基本驱动实现:实现电机的基础驱动函数,启停、6步换向组合等。
初始化ADC:初始化ADC通道IO,设置ADC工作方式、开启DMA。
PID闭环控制实现:实现PID的初始参数赋值、PID计算等。
上位机通信协议:编写上位机通信代码,可在上位机实时显示当前速度。
编写中断服务函数:PWM中断用于换向控制、堵转检测等,添加PID的周期计算调用。
程序流程图如图6。
图6 控制程序流程图
对于无刷电机旋转状态,采用分阶段启动方式,其相关参数如表1,阶段1-阶段3相关参数的变化曲线如图7。
表1 无刷电机无感启动参数
持续时间(ms)
目标转速(rpm)
相电压(V rms)
阶段1
500
0
0.61
阶段2
1000
236
4.99
阶段3
500
236
4.99
阶段4
1000
4.99
图7 启动目标转速和相电压与时间关系
以1000rpm为无刷电机目标转速,得到无刷电机转速随时间变化曲线如图8。
图8 无刷电机转速变化
目前存在的问题:由于代码调用的是ST官方电机库的函数,因此底层机理难以修改,部分变量只能在ST的电机控制工具中查看,无法导出分析。
下一步计划:基于市面上其他基于STM32的电机控制案例重构代码,并建立基于电机开发套件中三相云台电机的模糊控制器控制规律。
工程文件
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息无误,确认可以完成测评计划
- 2025-01-09
-
发表了主题帖:
FRDM-MCXA156开发板测评(一)——GPIO控制
感谢EEWORLD提供的FRDM-MCXA156开发板测评机会,对开发板的测试首先从点灯开始。
GPIO的控制可以说是最基础的外设控制了,在MCUXprsso IDE中,对于外设引脚的配置可以说和STM32Cube IDE是类似的。
观察原理图,可以看到有一个用户按键、一个唤醒按键、一个RGB灯可以用于GPIO控制。
进入MCUXpresso IDE,选择 文件-新建-Create a new C/C++ project,选择开发板(选择开发板之后的好处在于board.h中已经定义了相关引脚的别名,方便调用)
接着为文件命名,并选择相关的依赖文件,最后点击完成生成工程文件
配置芯片的引脚信息,这里我们选择用户按键和RGB灯的三个引脚。其中用户按键设置为输入,且默认为高电平,RGB灯三个引脚设置为推挽输出,默认为低电平。
编写主函数如下,用于实现按键按下时,RGB灯三色均点亮,即发出白光
int main(void) {
/* Init board hardware. */
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
LED_RED_INIT(LOGIC_LED_OFF);
LED_BLUE_INIT(LOGIC_LED_OFF);
LED_GREEN_INIT(LOGIC_LED_OFF);
/* Init FSL debug console. */
BOARD_InitDebugConsole();
while(1) {
if(GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN) == 0)
{
LED_RED_TOGGLE();
LED_BLUE_TOGGLE();
LED_GREEN_TOGGLE();
while(GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN) == 0);
}
}
return 0 ;
}
演示视频:
[localvideo]b70b628d73e1beb0778346677ac755c0[/localvideo]
代码文件:
- 2025-01-08
-
回复了主题帖:
【测评入围名单(最后1批)】年终回炉:FPGA、AI、高性能MCU、书籍等65个测品邀你来~
个人信息无误,确认可以完成测评计划
-
发表了主题帖:
《RISC-V 体系结构编程与实践(第2版)》——基础指令集
本帖最后由 FuShenxiao 于 2025-1-8 10:49 编辑
在结束了考研/期末/毕设三者中的前两个之后,终于有时间来做测评了,这本书也是放了一个多月了才重新拿起来看。
在正式食用这本书之前,需要对系统进行配置,我采用的是利用VMware虚拟机在移动硬盘上安装Ubuntu20.04,这是由于去年8月微软的补丁,导致u盘启动可能会被认为是不安全的。我参考的是这篇CSDN上的文章:利用Vmware将Ubuntu系统安装到移动硬盘_如何做移动vmware的渗透镜像部署到移动硬盘-CSDN博客。如此制作好的移动硬盘就可以以双系统的方式启动。
随后安装QEMU实验平台以及其他相关依赖,即可在QEMU中进行RISC-V相关的实验。
基础指令集的知识似乎是通俗易懂的,各位通过其字面意思也能理解这些指令的含义,于是我打算直接借助实验和思考题对本章内容进行讲解。
实验3-1:熟悉加载指令集
(1)将0x80200000加载到a0寄存器,将立即数16加载到a1寄存器;(2)从0x80200000地址中读取4字节的数据;(3)从0x80200010地址中读取8字节的数据;(4)给出lui指令的直行结果
li rd, imm表示把imm(立即数)加载到寄存器中
lw rd, offset(rs1)表示以rs1寄存器的值为基地址,在偏移offset的地址处加载4字节数据,经过符号扩展之后写入目标寄存器rd中
ld rd, offset(rs1)表示以rs1寄存器的值为基地址,在偏移offset的地址处加载8字节数据,写入寄存器rd中
lui rd, imm表示先把imm(立即数)左移12位,然后进行符号扩展,最后把结果写入rd寄存器中
符号扩展指将得到的结果扩展为32位或64位,其高字节部分填充为1
同理,零扩展高字节部分填充为0
实现代码如下:
load_store_test:
li a0, 0x80200000
li a1, 16
li t0, 0x80200000
li t1, 0x80200010
lw a2, (t0)
ld a3, (t1)
lui t0, 0x8034f
lui t1, 0x400
ret
在GDB调试器中查看得到结果如下:
实验3-2:PC相对寻址
auipc rd, imm将imm(立即数)左移12位并带符号扩展到64位后,得到一个新的立即数,这个新的立即数是一个有符号的立即数,再加上当前PC值,然后存储到rd寄存器中
addi rd, rs1, imm将rs1寄存器的值和12位的立即数imm相加,并将结果存入rd寄存器中
auipc指令通常和addi指令联合使用来实现32位地址空间的PC相对寻址。其中auipc指令可以寻址与被访问地址按4KB对齐的地方,即被访问地址的高20位;addi指令可以在[-2048, 2047]范围内寻址,即被访问地址的低12位。
代码实现如下:
@define MY_OFFSET -2048
pc_related_test:
auipc t0, 1
addi t0, t0, MY_OFFSET
ld t1, MY_OFFSET(t0)
ret
得到结果如下:
实验3-3:memcpy()函数的实现
sd rs2, offset(rs1)将rs2寄存器的值存储到以rs1寄存器的值位基地址加上offset的地址处
blt rs1, rs2, label表示如果rs1寄存器的值小于rs2寄存器的值,则跳转到label处
代码实现如下:
my_memcpy_test:
li t0, 0x80200000
li t1, 0x80210000
addi t2, t0, 32
.loop:
ld t3, (t0)
sd t3, (t1)
addi t0, t0, 8
addi t1, t1, 8
blt t0, t2, .loop
ret
得到结果如下,可以看到地址0x80200000地址的内容已经复制到0x80210000地址处
实验3-4:memset()函数的实现
memset.c代码如下:
#include "memset.h"
extern void *__memset_16bytes_asm(void *s, unsigned long val, unsigned long count);
static void __memset_16bytes_inline_asm(void *p, unsigned long val,
int count)
{
int i = 0;
asm volatile (
"1: sd %[val], (%[p])\n"
"sd %[val], 8(%[p])\n"
"addi %[i], %[i], 16\n"
"blt %[i], %[count], 1b"
: [p] "+r" (p), [count]"+r" (count), [i]"+r" (i)
: [val]"r" (val)
: "memory"
);
}
static void *__memset_1bytes(void *s, int c, size_t count)
{
char *xs = s;
while (count--)
*xs++ = c;
return s;
}
static void *__memset(char *s, int c, size_t count)
{
char *p = s;
unsigned long align = 16;
size_t size, left = count;
int n, i;
unsigned long address = (unsigned long)p;
unsigned long data = 0ULL;
/* 这里c必须转换成unsigned long类型
* 否则 只能设置4字节,因为c变量是int类型
*/
for (i = 0; i < 8; i++)
data |= (((unsigned long)c) & 0xff) << (i * 8);
/*1. check start address is align with 16 bytes */
if (address & (align - 1)) {
//fixme: 这里应该是 对齐的后半段
size = address & (align - 1);
size = align - size;
__memset_1bytes(p, c, size);
p = p + size;
left = count - size;
}
/*align 16 bytes*/
if (left > align) {
n = left / align;
left = left % align;
#if 0
__memset_16bytes_asm(p, data, 16*n);
#else
__memset_16bytes_inline_asm(p, data, 16*n);
#endif
if (left)
__memset_1bytes(p + 16*n, c, left);
}
return s;
}
void *memset(void *s, int c, size_t count)
{
return __memset(s, c, count);
}
memset.S代码如下:
__memset_16bytes_asm:
li t0, 0
.loop:
sd a1, (a0)
sd a1, 8(a0)
addi t0, t0, 16
blt t0, a2, .loop
ret
得到结果如下:
实验3-5:条件跳转指令1
实现当a>=b时,返回值为0,否则返回值为0xffffffffffffffff
代码实现如下:
bltu与blt指令类似,只不过rs1寄存器的值和rs2寄存器的值位无符号数
compare_and_return:
bltu a0,a1,.L2
li a5,0
j .L3
.L2:
li a5,-1
.L3:
mv a0,a5
ret
在主程序中编写如下代码显示大小比较结果
val1 = compare_and_return(10, 9);
if (val1 == 0)
uart_send_string("compare_and_return ok\n");
else
uart_send_string("compare_and_return fail\n");
val2 = compare_and_return(9, 10);
if (val2 == 0xffffffffffffffff)
uart_send_string("compare_and_return ok\n");
else
uart_send_string("compare_and_return fail\n");
得到结果如下:
实验3-6:条件跳转指令2
当a=0时,返回b+2,否则返回b-1
代码实现如下:
sel_test:
beqz a0, .L4
addi a5, a1, -1
j .L5
.L4:
addi a5, a1, 2
.L5:
mv a0, a5
ret
在主程序中编写如下代码显示结果
val1 = sel_test(0, 9);
if (val1 == 11)
uart_send_string("sel test ok\n");
val2 = sel_test(5, 2);
if (val2 == 1)
uart_send_string("sel test ok\n");
得到结果如下:
实验3-7:子函数跳转
为了完成子函数跳转,首先将ra保存到栈,接着调用子函数,在完成子函数程序后从栈中恢复ra返回地址
具体代码如下:
branch_test:
/*把返回地址ra寄存器保存到栈里*/
addi sp,sp,-8
sd ra,(sp)
li a0, 1
li a1, 2
/* 调用add_test子函数 */
call add_test
nop
/* 从栈中恢复ra返回地址*/
ld ra,(sp)
addi sp,sp,8
ret
实验3-8:在汇编中实现串口输出功能
在boot.S中,程序的初始化中首先调用了__init_uart和print_asm两个子函数,随后才进入C语言的主程序
.globl _start
_start:
/* 关闭中断 */
csrw sie, zero
call __init_uart
call print_asm
/* 设置栈, 栈的大小为4KB */
la sp, stacks_start
li t0, 4096
add sp, sp, t0
/* 跳转到C语言 */
tail kernel_main
print_asm:
/*此时SP栈空间还没分配,把返回地址ra保存到临时寄存器中*/
mv s1, ra
la a0, boot_string
call put_string_uart
/*恢复返回地址ra*/
mv ra, s1
ret
.section .data
.align 12
.global stacks_start
stacks_start:
.skip 4096
.section .rodata
.align 3
.globl boot_string
boot_string:
.string "Booting at asm\n"
C语言中的main函数如下:
void kernel_main(void)
{
uart_init();
uart_send_string("Welcome RISC-V!\r\n");
while (1) {
;
}
}
可以看到最后首先输出"Booting at asm"接着输出"Welcome RISC-V!"
思考题1:RISC-V指令集有什么特点?
RISC-V采用模块化的设计方法,即设计一个最小的和最基础的指令集,这个最小的指令集可以完整地实现一个软件栈,其他特殊功能的指令集可以在最小指令集的基础上通过模块化的方式叠加实现,用于支持浮点数运算指令、乘法和除法指令等。
思考题2:RISC-V指令编码格式可以分成几类?
R类型:寄存器和寄存器算术指令
I类型:寄存器和立即数算术指令或者加载指令
S类型:存储指令
B类型:条件跳转指令
U类型:长立即数操作指令
J类型:无条件跳转指令
思考题3:什么是零扩展和符号扩展?
零扩展为计算机系统把小字节数据转换成大字节数据,将符号位扩展至所需要的位数,高位填充0
符号扩展与零扩展类似,高位填充1(0xFF)
思考题4:什么是PC相对寻址?
PC加上指令中给出的偏移量,得到操作数的实际地址。
思考题5:假设当前PC值位0x80200000,分别执行如下指令,a5和a6寄存器的值分别是多少?
auipc a5, 0x2
lui a6, 0x2
a5寄存器的值为 PC+sign_extend(0x2 << 12) = 0x80200000 + 0x2000 = 0x80202000
a6寄存器的值为 0x2 << 12 = 0x2000
思考题6:在下面的指令中,a1和t1寄存器的值分别是多少?
li t0, 0x8000008a00000000
srai a1, t0, 1
srli t1, t0, 1
srai为算数右移,高位需要进行符号扩展,a1寄存器的值为0xC000 0045 0000 0000
srli为立即数逻辑右移,高位需要进行零扩展,t1寄存器的值为0x4000 0045 0000 0000
思考题7:假设执行如下各条指令时当前的PC值位0x80200000,则下面那些指令是非法指令?
jal a0, 0x800fffff
jal a0, 0x80300000
两条指令都会出错,因为两条指令都超过了jal指令的跳转范围,jal指令的跳转范围为[0x8010 0000, 0x802F FFFE]
思考题8:请解析下面这条指令的含义
csrrw tp, sscratch, tp
先读取sscratch寄存器的旧值并写入tp寄存器,再将tp寄存器的旧值写入sscratch寄存器
用C语言伪代码实现如下:
tp = sscratch;
sscratch = tp;
思考题9:在RISC-V指令集中,如何实现大范围和小范围内跳转?
使用auipc与jalr指令实现基于当前PC偏移量±2GB范围的跳转
使用jal指令实现基于当前PC偏移量±1MB范围的跳转
- 2024-12-19
-
回复了主题帖:
测评入围名单: NXP MCX A系列 FRDM-MCXA156开发板
个人信息无误,确认可以完成测评分享计划
- 2024-11-27
-
发表了主题帖:
《RISC-V 体系结构编程与实践(第2版)》——一些阅读前的准备工作
本帖最后由 FuShenxiao 于 2024-11-27 09:52 编辑
感谢EEWORLD提供的《RISC-V 体系结构编程与实践(第2版)》书籍测评机会。由于目前考研在即,这里就写一些我的准备工作,以及一些资料网站等,具体阅读任务将在12月底考研完成后进行,并在寒假参与中科院的“一生一芯”项目。
相关操作环境配置:
操作系统:Ubuntu Linux 20.04
GCC版本:9(riscv64-linux-gnu-gcc)
QEMU版本:4.2.1
GDB版本:gdb-multiarch
一些官方网站:
RISC-V官网:RISC-V International – RISC-V: The Open Standard RISC Instruction Set Architecture (riscv.org)
香山处理器文档:香山 XiangShan (openxiangshan.cc)
“一生一芯”项目官网:一生一芯 (oscc.cc)
“甲辰计划”官网:甲辰计划 (RISC-V Prosperity 2036) | 甲辰计划 (rv2036.org)
一些RISC-V相关的资料:
RISC-V指令集手册
RISC-V体系结构手册
GCC官方手册
汇编器(AS)官方手册
链接器(LD)手册
RVV手册
RISC-V高速缓存维护指令扩展手册
PLIC手册
RISC-V ABI接口手册
RISC-V SBI接口手册
一些视频教程:
[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春_哔哩哔哩_bilibili
“一生一芯”概述 [第六期“一生一芯”计划 - P1]_哔哩哔哩_bilibili
- 2024-11-20
-
回复了主题帖:
读书入围名单:《RISC-V 体系结构编程与实践(第2版)》
个人信息无误,确认可以完成阅读计划和打卡任务
- 2024-11-06
-
回复了主题帖:
【求助】小学六年级的题目,我硬是不会做
okhxyyo 发表于 2024-11-6 17:23
嘿,你这个图画的好。我感觉一定不是这么算,一定是哪里有个小窍门。我想想啊
这几天在考研,这种是二重积分极坐标法的经典题型:Sad:
-
回复了主题帖:
【求助】小学六年级的题目,我硬是不会做
okhxyyo 发表于 2024-11-6 16:07
不知道怎么入手了都
感觉像是考研难度的,得用到二重积分
- 2024-11-02
-
加入了学习《DIY作品演示》,观看 阿尔达H-30T高温休眠版恒温电烙铁试用体验
- 2024-10-24
-
回复了主题帖:
STM32H7S78-DK测评(四)——RTC测试
freebsder 发表于 2024-10-24 19:19
S又是个主打哪个方向的型号?
我觉得主要是它的高性能图像显示能力吧,主要吸引人的是它DMA2D,也就是GPU功能,这块开发板的屏幕有480*800,已经挺大的了。
-
发表了主题帖:
STM32H7S78-DK测评(四)——RTC测试
本帖最后由 FuShenxiao 于 2024-10-24 16:58 编辑
STM32的RTC指的是实时时钟(Real-Time Clock),它是一种用于计时和日期记录的硬件模块。在STM32微控制器中,RTC模块是由一个32位的计数器和一组用于保存日期和时间的寄存器组成的。由于RTC具有较高的精度和稳定性,从而能保持准确的日期和时间信息,因此它常用于需要精确定时和实时数据处理的应用场景。
本来以为RTC实现挺简单的,没想到这里还有那么多坑,主要是CubeMX代码生成存在一些问题。
在CubeMX中配置
选择STM32H7S78-DK模板
配置RTC
为了能输出时间和日期,还需要配置UART4
代码编写
生成RTC初始化代码如下
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
RTC_PrivilegeStateTypeDef privilegeState = {0};
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
/* USER CODE BEGIN RTC_Init 1 */
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
hrtc.Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE;
hrtc.Init.BinMode = RTC_BINARY_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
privilegeState.rtcPrivilegeFull = RTC_PRIVILEGE_FULL_NO;
privilegeState.backupRegisterPrivZone = RTC_PRIVILEGE_BKUP_ZONE_NONE;
privilegeState.backupRegisterStartZone2 = RTC_BKP_DR0;
privilegeState.backupRegisterStartZone3 = RTC_BKP_DR0;
if (HAL_RTCEx_PrivilegeModeSet(&hrtc, &privilegeState) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x15;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
sDate.Month = RTC_MONTH_OCTOBER;
sDate.Date = 0x24;
sDate.Year = 0x24;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
/* USER CODE END RTC_Init 2 */
}
编写printf重定向
int fputc(int ch,FILE *p)
{
char c=ch;
HAL_UART_Transmit(&huart4,(unsigned char *)&c,1,50);
return ch;
}
void UART4_SendByte(char c)
{
HAL_UART_Transmit(&huart4,(unsigned char *)&c,1,50);
}
void UART4_SendData(char *p,int len)
{
HAL_UART_Transmit(&huart4,(unsigned char *)p,len,50);
}
编写输出RTC日历的代码
static void RTC_CalendarShow(uint8_t *showtime, uint8_t *showdate)
{
RTC_DateTypeDef sdatestructureget;
RTC_TimeTypeDef stimestructureget;
/* Get the RTC current Time */
HAL_RTC_GetTime(&hrtc, &stimestructureget, RTC_FORMAT_BIN);
/* Get the RTC current Date */
HAL_RTC_GetDate(&hrtc, &sdatestructureget, RTC_FORMAT_BIN);
/* Display time Format : hh:mm:ss */
sprintf((char *)showtime, "%2d:%2d:%2d", stimestructureget.Hours, stimestructureget.Minutes, stimestructureget.Seconds);
/* Display date Format : mm-dd-yyyy */
sprintf((char *)showdate, "%2d-%2d-%2d", sdatestructureget.Month, sdatestructureget.Date, 2000 + sdatestructureget.Year);
printf("%s\r\n", showtime);
printf("%s\r\n", showdate);
}
初始化时间/日期字符串
uint8_t aShowTime[16] = "hh:ms:ss";
uint8_t aShowDate[16] = "mm-dd-yyyy";
在主函数循环中加入如下代码
RTC_CalendarShow(aShowTime, aShowDate);
HAL_Delay(1000);
但是,这样还是有问题的。
问题出在 HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc)
其代码如下
HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc)
{
uint32_t tickstart;
HAL_StatusTypeDef status = HAL_OK;
/* Check if the Initialization mode is set */
if (READ_BIT(RTC->ICSR, RTC_ICSR_INITF) == 0U)
{
/* Set the Initialization mode */
SET_BIT(RTC->ICSR, RTC_ICSR_INIT);
tickstart = HAL_GetTick();
/* Wait till RTC is in INIT state and if Time out is reached exit */
while ((READ_BIT(RTC->ICSR, RTC_ICSR_INITF) == 0U) && (status != HAL_TIMEOUT))
{
if ((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE)
{
/* New check to avoid false timeout detection in case of preemption */
if (READ_BIT(RTC->ICSR, RTC_ICSR_INITF) == 0U)
{
status = HAL_TIMEOUT;
/* Change RTC state */
hrtc->State = HAL_RTC_STATE_TIMEOUT;
}
else
{
break;
}
}
}
}
return status;
}
问题出现在执行 SET_BIT(RTC->ICSR, RTC_ICSR_INIT); 之后,寄存器的初始化位并没有置1。
阅读手册可知需要对DBP置1才能使能写RTC寄存器。
在进入RTC之前会调用 HAL_RTC_MspInit(hrtc);
于是我们就可以进入 HAL_RTC_MspInit 中使能写RTC寄存器,添加一句 HAL_PWR_EnableBkUpAccess(); 用于允许访问备份区。
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(rtcHandle->Instance==RTC)
{
/* USER CODE BEGIN RTC_MspInit 0 */
HAL_PWR_EnableBkUpAccess();
/* USER CODE END RTC_MspInit 0 */
/** Initializes the peripherals clock
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* RTC clock enable */
__HAL_RCC_RTC_ENABLE();
__HAL_RCC_RTCAPB_CLK_ENABLE();
/* USER CODE BEGIN RTC_MspInit 1 */
/* USER CODE END RTC_MspInit 1 */
}
}
于是RTC就可以正常使用了。
结果展示
完整工程代码
- 2024-10-13
-
回复了主题帖:
STM32H7S78-DK测评(三)——OV5640摄像头测试
lugl4313820 发表于 2024-10-13 19:41
楼主是太历害了呀,可以分享开发板的设计吗?
这块开发板是ST官方的开发板,ST官网上能找到开发板资料的。如果你说的是OV5640转接板,我已经在嘉立创开源了。
- 2024-10-08
-
回复了主题帖:
STM32H7S78-DK测评(三)——OV5640摄像头测试
cc1989summer 发表于 2024-10-8 14:00
楼主。ST官方手册有说的,配套摄像头模块型号是MB1683,Mouser上售价400+元人民币。
抱歉我没仔细读文档,不过这个摄像头也是基于OV5640的,我看e络盟要将近300块,但是感觉都挺贵的,还是自己画个转接板划算
- 2024-10-07
-
发表了主题帖:
STM32H7S78-DK测评(三)——OV5640摄像头测试
本帖最后由 FuShenxiao 于 2024-10-7 11:55 编辑
转接板设计
观察开发板原理图摄像头接口可以发现,接口居然有30pin,这和软排线连接的OV5640(24pin)或者OV5640模块(2*9pin)显然是不相同的。
恰好我手头有一块正点原子OV5640摄像头模块,于是我就针对它完成转接板的设计。
观察正点原子OV5640摄像头模块原理图可以轻易地与STM32H7S78-DK的摄像头接口相对应。
I2C1_SCL ---> OV_SCL
I2C1_SDA ---> OV_SDA
DCMI_D0 ~ DMCI_D7 ---> OV_D0 ~ OV_D7
RSTI ---> OV_RESET
PWR_EN ---> OV_PWDN
DCMI_VSYNC ---> OV_VSYNC
DCMI_HSYNC ---> OV_HREF
DCMI_PIXCLK ---> OV_PCLK
在嘉立创中绘制原理图如下
绘制PCB如下。为了保证相关电学特性稳定,需要在OV5640供电端加入100nF的去耦电容,在SCL和SDA两个IIC接口上加入4.7kΩ的上拉电阻。
最终得到PCB正反面如下图所示
具体工程文件参见STM32H7S78-DK摄像头转接板 - 立创开源硬件平台 (oshwhub.com)
引脚配置
首先在CubeMX中配置时钟(这里不再赘述)
接着分别配置DCMIPP(摄像头接口)和LTDC(显示屏接口)
配置两个指示灯
完成以上操作后,生成代码
代码编写
OV5640初始化代码,需要注意的是,OV5640默认原始图像是上下翻转的,所以要使用OV5640_FLIP将图像翻转
static uint32_t OV5640_Config(uint32_t Resolution, uint32_t PixelFormat)
{
OV5640_IO_t IOCtx;
uint32_t id;
uint32_t ret = OV5640_OK;
static OV5640_Object_t OV5640Obj;
/* Configure the Camera driver */
IOCtx.Address = CAMERA_OV5640_ADDRESS;
IOCtx.Init = BSP_I2C1_Init;
IOCtx.DeInit = BSP_I2C1_DeInit;
IOCtx.ReadReg = BSP_I2C1_ReadReg16;
IOCtx.WriteReg = BSP_I2C1_WriteReg16;
IOCtx.GetTick = BSP_GetTick;
/* Register Bus IO */
if(OV5640_RegisterBusIO (&OV5640Obj, &IOCtx) != OV5640_OK)
{
ret = OV5640_ERROR;
}
/* Read ID */
if(OV5640_ReadID(&OV5640Obj, &id) != OV5640_OK)
{
ret = OV5640_ERROR;
}
if(id == OV5640_ID)
{
/* Initialize the camera Module */
Camera_Drv = (CAMERA_Drv_t *) &OV5640_CAMERA_Driver;
OV5640_DeInit(&OV5640Obj);
if(Camera_Drv->Init(&OV5640Obj, Resolution, PixelFormat) != OV5640_OK)
{
ret = OV5640_ERROR;
}
else if(Camera_Drv->MirrorFlipConfig(&OV5640Obj, OV5640_FLIP) != OV5640_OK)
{
ret = OV5640_ERROR;
}
}
return ret;
}
在主函数中使用如下代码完成OV5640初始化
if(OV5640_Config(OV5640_R480x272, OV5640_RGB565) != OV5640_OK)
{
/* Camera Module Config KO */
Error_Handler();
}
DCMIPP接口获取数据信息
HAL_StatusTypeDef HAL_DCMIPP_PIPE_Start(DCMIPP_HandleTypeDef *hdcmipp, uint32_t Pipe, uint32_t DstAddress,
uint32_t CaptureMode)
{
assert_param(IS_DCMIPP_PIPE(Pipe));
assert_param(IS_DCMIPP_CAPTURE_MODE(CaptureMode));
/* Check pointer validity */
if ((hdcmipp == NULL) || ((DstAddress & 0xFU) != 0U))
{
return HAL_ERROR;
}
/* Check DCMIPP pipe state */
if (hdcmipp->PipeState[Pipe] != HAL_DCMIPP_PIPE_STATE_READY)
{
return HAL_ERROR;
}
/* Set Capture Mode and Destination address for the selected pipe */
DCMIPP_SetConfig(hdcmipp, Pipe, DstAddress, CaptureMode);
/* Enable Capture for the selected Pipe */
DCMIPP_EnableCapture(hdcmipp, Pipe);
return HAL_OK;
}
在主函数中用如下代码完成DCMIPP接口数据接收,将接收到的图像信息传到CAMERA_FRAME_BUFFER中。CAMERA_FRAME_BUFFER后面的传参为当前运行模式,除了可以选择DCMIPP_MODE_CONTINUOUS,还可以使用DCMIPP_MODE_SNAPSHOT
if(HAL_DCMIPP_PIPE_Start(&phdcmipp, DCMIPP_PIPE0, (uint32_t)CAMERA_FRAME_BUFFER , DCMIPP_MODE_CONTINUOUS) != HAL_OK)
{
Error_Handler();
}
也可以使用双缓冲模式,代码如下
HAL_StatusTypeDef HAL_DCMIPP_PIPE_DoubleBufferStart(DCMIPP_HandleTypeDef *hdcmipp, uint32_t Pipe, uint32_t DstAddress0,
uint32_t DstAddress1, uint32_t CaptureMode)
{
assert_param(IS_DCMIPP_PIPE(Pipe));
assert_param(IS_DCMIPP_CAPTURE_MODE(CaptureMode));
/* Check pointer validity */
if ((hdcmipp == NULL) || ((DstAddress0 & 0xFU) != 0U) || ((DstAddress1 & 0xFU) != 0U))
{
return HAL_ERROR;
}
/* Check DCMIPP pipe state */
if (hdcmipp->PipeState[Pipe] != HAL_DCMIPP_PIPE_STATE_READY)
{
return HAL_ERROR;
}
/* Set Capture Mode and Destination addresses for the selected pipe */
DCMIPP_SetDBMConfig(hdcmipp, Pipe, DstAddress0, DstAddress1, CaptureMode);
/* Enable Capture for the selected Pipe */
DCMIPP_EnableCapture(hdcmipp, Pipe);
return HAL_OK;
}
最后在显示屏上显示图像,在这一步中,需要将CAMERA_FRAME_BUFFER的数据显示到屏幕的指定区域内
static void MX_LTDC_Init(void)
{
/* USER CODE BEGIN LTDC_Init 0 */
/* USER CODE END LTDC_Init 0 */
LTDC_LayerCfgTypeDef pLayerCfg = {0};
/* USER CODE BEGIN LTDC_Init 1 */
/* USER CODE END LTDC_Init 1 */
hltdc.Instance = LTDC;
hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
hltdc.Init.HorizontalSync = 3;
hltdc.Init.VerticalSync = 3;
hltdc.Init.AccumulatedHBP = 11;
hltdc.Init.AccumulatedVBP = 11;
hltdc.Init.AccumulatedActiveW = 811;
hltdc.Init.AccumulatedActiveH = 491;
hltdc.Init.TotalWidth = 819;
hltdc.Init.TotalHeigh = 499;
hltdc.Init.Backcolor.Blue = 0;
hltdc.Init.Backcolor.Green = 0;
hltdc.Init.Backcolor.Red = 0;
if (HAL_LTDC_Init(&hltdc) != HAL_OK)
{
Error_Handler();
}
pLayerCfg.WindowX0 = xpos;
pLayerCfg.WindowX1 = xpos + ImageWidth;
pLayerCfg.WindowY0 = ypos;
pLayerCfg.WindowY1 = ypos + ImageHeight;
pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
pLayerCfg.Alpha = 255;
pLayerCfg.Alpha0 = 0;
pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
pLayerCfg.FBStartAdress = (uint32_t)CAMERA_FRAME_BUFFER;
pLayerCfg.ImageWidth = ImageWidth;
pLayerCfg.ImageHeight = ImageHeight;
pLayerCfg.Backcolor.Blue = 0;
pLayerCfg.Backcolor.Green = 0;
pLayerCfg.Backcolor.Red = 0;
if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN LTDC_Init 2 */
/* USER CODE END LTDC_Init 2 */
}
结果展示
当程序正常运行时,LD1会持续闪烁,OV5640中的内容也会实时传输到屏幕上,传输显示的过程还是较为流畅的。
不过噪声的问题似乎有些严重,面对比较白和亮的地方会产生一些紫色的点点,但是对着暗色调的就不会产生这个问题(比如我的书包,桌子在椅背的阴影部分)。考虑PCB绘制部分,网上对OV5640的建议是信号线之间的线长差距小于100mil,这个我符合要求。再考虑软排线的长度,10cm在诸多网友看来似乎确实有些长了,下一步尝试换短一点的软排线再试试。
不过让我费解的是,官方的板子既然做了这个接口,为什么不顺便再做一个适配的摄像头呢,就像STM32MP135F-DK开发板还配套摄像头模块。而且这个30pin的接口根本无法适配OV5640,还得自己画转接板。
[localvideo]6ff2fc34925b327c32ce635fc53741fe[/localvideo]
程序源码:
- 2024-10-03
-
回复了主题帖:
STM32H7S78-DK测评(二)——用串口实现手写数字体识别(失败)
cc1989summer 发表于 2024-10-3 10:35
比较有创意的设计。
STM32H7S78-DK有触摸屏,要是能直接脱离电脑直接在开发板上运行就好了( ...
对的,我下一步就想这么做了,不过深度学习的办法似乎行不通,我得尝试用别的识别手段
- 2024-10-02
-
发表了主题帖:
STM32H7S78-DK测评(二)——用串口实现手写数字体识别(失败)
本帖最后由 FuShenxiao 于 2024-10-2 22:49 编辑
本次测试基于b站教程:教程来了!!STM32手写数字识别!!!_哔哩哔哩_bilibili
up主工程开源地址:https://github.com/colin2135/STM32G070_AI_TEST.git
上位机测试软件地址:https://github.com/colin2135/HandWriteApp.git
模型训练与保存
作为深度学习的入门教程,现在网上介绍MNIST手写数字体识别的教程已经很多了。这里贴一段用keras生成.h5文件的代码,不过为了和跟随up主的教程,我最后用了GitHub上的.tflite模型文件。
from keras.datasets import mnist
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras.utils import np_utils
import tensorflow as tf
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.compat.v1.Session(config=config)
# 设定随机数种子,使得每个网络层的权重初始化一致
# np.random.seed(10)
# x_train_original和y_train_original代表训练集的图像与标签, x_test_original与y_test_original代表测试集的图像与标签
(x_train_original, y_train_original), (x_test_original, y_test_original) = mnist.load_data()
"""
数据可视化
"""
# 原始数据量可视化
print('训练集图像的尺寸:', x_train_original.shape)
print('训练集标签的尺寸:', y_train_original.shape)
print('测试集图像的尺寸:', x_test_original.shape)
print('测试集标签的尺寸:', y_test_original.shape)
"""
数据预处理
"""
# 从训练集中分配验证集
x_val = x_train_original[50000:]
y_val = y_train_original[50000:]
x_train = x_train_original[:50000]
y_train = y_train_original[:50000]
# 打印验证集数据量
print('验证集图像的尺寸:', x_val.shape)
print('验证集标签的尺寸:', y_val.shape)
print('======================')
# 将图像转换为四维矩阵(nums,rows,cols,channels), 这里把数据从unint类型转化为float32类型, 提高训练精度。
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1).astype('float32')
x_val = x_val.reshape(x_val.shape[0], 28, 28, 1).astype('float32')
x_test = x_test_original.reshape(x_test_original.shape[0], 28, 28, 1).astype('float32')
# 原始图像的像素灰度值为0-255,为了提高模型的训练精度,通常将数值归一化映射到0-1。
x_train = x_train / 255
x_val = x_val / 255
x_test = x_test / 255
print('训练集传入网络的图像尺寸:', x_train.shape)
print('验证集传入网络的图像尺寸:', x_val.shape)
print('测试集传入网络的图像尺寸:', x_test.shape)
# 图像标签一共有10个类别即0-9,这里将其转化为独热编码(One-hot)向量
y_train = np_utils.to_categorical(y_train)
y_val = np_utils.to_categorical(y_val)
y_test = np_utils.to_categorical(y_test_original)
"""
定义网络模型
"""
def CNN_model():
model = Sequential()
model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(filters=32, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(100, activation='relu'))
model.add(Dense(10, activation='softmax'))
print(model.summary())
return model
"""
训练网络
"""
model = CNN_model()
# 编译网络(定义损失函数、优化器、评估指标)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# 开始网络训练(定义训练数据与验证数据、定义训练代数,定义训练批大小)
train_history = model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=10, batch_size=32, verbose=2)
# 模型保存
model.save('model.h5')
# 定义训练过程可视化函数(训练集损失、验证集损失、训练集精度、验证集精度)
def show_train_history(train_history, train, validation):
plt.plot(train_history.history[train])
plt.plot(train_history.history[validation])
plt.title('Train History')
plt.ylabel(train)
plt.xlabel('Epoch')
plt.legend(['train', 'validation'], loc='best')
plt.show()
show_train_history(train_history, 'accuracy', 'val_accuracy')
show_train_history(train_history, 'loss', 'val_loss')
CubeMX配置
安装CubeAI
在CubeMX上方Software Packs下拉选择Select Components,选择其中的X-CUBE-AI
在左侧菜单栏选择Middleware and Software Packs,选择其中的X-CUBE-AI,导入模型并分析。如果这个模型过大,超过了flash的大小,可能还需要对模型进行压缩,并配置外部flash。
这里可能有人会问为什么用的是7.3.0版本的CubeAI,而不用更高版本的CubeAI。我尝试了8.1.0和9.0.0两个版本的CubeAI,在模型验证阶段均出现tool error: 'gbk' codec can't encode character的报错,可能高版本的CubeAI对gbk的适配能力不太好吧。
串口配置
观察开发板原理图可以发现,PD0和PD1可以做虚拟串口使用,对应的是UART4。
开启UART4并设置为异步模式。由于需要串口收发,所以还要使能串口接收中断。
最后使能DEBUG功能
代码编写
由于此次测试不需要TouchGFX(keil对TouchGFX的适配不是很好,总是缺文件),所以用的keil编写。
首先包含相关头文件
#include "stdio.h"
#include "string.h"
#include "ai_platform.h"
#include "network.h"
#include "network_data.h"
由于需要串口收发数据,因此需要对printf进行重定向,并在魔术棒里开启microLIB
/* USER CODE BEGIN 0 */
/**
* @brief 重定向c库函数printf到USARTx
* @retval None
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart4, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/**
* @brief 重定向c库函数getchar,scanf到USARTx
* @retval None
*/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart4, &ch, 1, 0xffff);
return ch;
}
定义AI模型相关参数,并声明后续使用到的一些函数
ai_handle network;
float aiInData[AI_NETWORK_IN_1_SIZE];
float aiOutData[AI_NETWORK_OUT_1_SIZE];
ai_u8 activations[AI_NETWORK_DATA_ACTIVATIONS_SIZE];
ai_buffer * ai_input;
ai_buffer * ai_output;
static void AI_Init(void);
static void AI_Run(float *pIn, float *pOut);
void PictureCharArrayToFloat(uint8_t *srcBuf,float *dstBuf,int len);
void Uart_send(char * str);
#define UART_BUFF_LEN 1024
#define ONE_FRAME_LEN 1+784+2
uint16_t uart_rx_length = 0;
uint8_t uart_rx_byte = 0;
uint8_t uart_rx_buffer[UART_BUFF_LEN];
volatile uint8_t goRunning = 0;
/* USER CODE END 0 */
定义串口中断回调函数
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
if(goRunning ==0)
{
if (uart_rx_length < UART_BUFF_LEN)
{
uart_rx_buffer[uart_rx_length] = uart_rx_byte;
uart_rx_length++;
if (uart_rx_byte == '\n')
{
goRunning = 1;
}
}
else
{
//rt_kprintf("rx len over");
uart_rx_length = 0;
}
}
HAL_UART_Receive_IT(&huart4, (uint8_t *)&uart_rx_byte, 1);
}
定义串口发送函数
void Uart_send(char * str)
{
HAL_UART_Transmit(&huart4, (uint8_t *)str, strlen(str),0xffff);
}
定义AI模型初始化函数
static void AI_Init(void)
{
ai_error err;
/* Create a local array with the addresses of the activations buffers */
const ai_handle act_addr[] = { activations };
/* Create an instance of the model */
err = ai_network_create_and_init(&network, act_addr, NULL);
if (err.type != AI_ERROR_NONE) {
printf("ai_network_create error - type=%d code=%d\r\n", err.type, err.code);
Error_Handler();
}
ai_input = ai_network_inputs_get(network, NULL);
ai_output = ai_network_outputs_get(network, NULL);
}
定义AI模型运行函数
static void AI_Run(float *pIn, float *pOut)
{
char logStr[100];
int count = 0;
float max = 0;
ai_i32 batch;
ai_error err;
/* Update IO handlers with the data payload */
ai_input[0].data = AI_HANDLE_PTR(pIn);
ai_output[0].data = AI_HANDLE_PTR(pOut);
batch = ai_network_run(network, ai_input, ai_output);
if (batch != 1) {
err = ai_network_get_error(network);
printf("AI ai_network_run error - type=%d code=%d\r\n", err.type, err.code);
Error_Handler();
}
for (uint32_t i = 0; i < AI_NETWORK_OUT_1_SIZE; i++) {
sprintf(logStr,"%d %8.6f\r\n",i,aiOutData[i]);
Uart_send(logStr);
if(max<aiOutData[i])
{
count = i;
max= aiOutData[i];
}
}
sprintf(logStr,"current number is %d\r\n",count);
Uart_send(logStr);
}
定义将串口收到的uint8_t类型数据转换为float类型函数
void PictureCharArrayToFloat(uint8_t *srcBuf,float *dstBuf,int len)
{
for(int i=0;i<len;i++)
{
dstBuf[i] = srcBuf[i];//==1?0:1;
}
}
/* USER CODE END 4 */
主函数部分,需要完成外设初始化以及模型运行逻辑的书写
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Update SystemCoreClock variable according to RCC registers values. */
SystemCoreClockUpdate();
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_CRC_Init();
MX_FLASH_Init();
MX_UART4_Init();
/* USER CODE BEGIN 2 */
__HAL_RCC_CRC_CLK_ENABLE();
AI_Init();
memset(uart_rx_buffer,0,784);
HAL_UART_Receive_IT(&huart4, (uint8_t *)&uart_rx_byte, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
char str[10];
if(goRunning>0)
{
if(uart_rx_length == ONE_FRAME_LEN)
{
PictureCharArrayToFloat(uart_rx_buffer+1,aiInData,28*28);
AI_Run(aiInData, aiOutData);
}
memset(uart_rx_buffer,0,784);
goRunning = 0;
uart_rx_length = 0;
}
}
/* USER CODE END 3 */
}
至此,代码部分就完成了。但是当我烧录运行程序的时候,发生了如下报错,定位到报错的函数为 ai_network_init_and_create() ,说明模型创建失败。
ai_network_create error - type=51 code=65
在网上诸多类似问题的帖子中,存在类似问题的大多都是H7系列芯片,而一般建议的方案都是检查是否开启CRC,以下两张图是ST员工对该问题的回复
然而,当我在AI模型初始化在 AI_Init() 之前加入 __HAL_RCC_CRC_CLK_ENABLE() 用于CRC开启使能,还是发生了如上的报错。
如果有了解这个问题的大佬,还请指导一下,我将不胜感激。
整个工程如下:
STM32G431RBT6实现
依照该up主的做法,我看网上已有用F4,F7实现的,而该up主用的是G0的芯片。我宿舍里刚好有一块蓝桥杯嵌入式的板子,上面搭载的是STM32G431RBT6,接下来就尝试一下G4是否能实现手写数字体识别。
AI模型导入流程与上文一致,引脚配置如下。其中PF0和PF1用于晶振信号输入,PA9和PA10用于串口收发,PA13和PA14用于DEBUG。
代码部分也与上文一致,仅需将huart4改为huart1即可。
编写烧录,运行成功!说明G4也是能部署AI模型用于MNIST手写数字体识别的。
- 2024-10-01
-
发表了主题帖:
STM32CubeAI中 ai_network_create_and_init 不成功
本帖最后由 FuShenxiao 于 2024-10-1 10:59 编辑
我将模型导入CubeMX中的CubeAI,生成代码后进行模型初始化
定义相关参数
ai_handle network;
float aiInData[AI_NETWORK_IN_1_SIZE];
float aiOutData[AI_NETWORK_OUT_1_SIZE];
ai_u8 activations[AI_NETWORK_DATA_ACTIVATIONS_SIZE];
模型初始化
static void AI_Init(void)
{
ai_error err;
/* Create a local array with the addresses of the activations buffers */
const ai_handle act_addr[] = { activations };
/* Create an instance of the model */
err = ai_network_create_and_init(&network, act_addr, NULL);
if (err.type != AI_ERROR_NONE) {
printf("ai_network_create error - type=%d code=%d\r\n", err.type, err.code);
Error_Handler();
}
ai_input = ai_network_inputs_get(network, NULL);
ai_output = ai_network_outputs_get(network, NULL);
}
这个代码是原封不动抄自GitHub的代码,但是模型初始化失败,并报错 ai_network_create error - type=51 code=65
网上大多建议开启CRC,但是我已经在 AI_Init() 之前加入 __HAL_RCC_CRC_CLK_ENABLE(),但是模型初始化依然失败
请问是否有遇到过类似问题的,如果能帮忙解答我将感激不尽。