一 项目介绍
本项目主要使用了芯片stm32h743,主要使用了视频控制器,qspi控制器和fmc控制器。控制板载芯片和实现视频的输出。
选择的任务是用于简易示波器/频谱仪/信号发生器的核心板。核心板引出来所有的引脚,板载了主要使用的外设,sdram主要用于图像显示。
nanflash用于保存数据。
引出rgb接口,用于连接外部rgb显示屏幕。
引出了usb接口。
二 模块硬件和功能介绍
STM32H743II: 意法半导体(STMicroelectronics)推出的高性能嵌入式处理器,基于 Arm Cortex-M7 内核,主频高达 480 MHz。处理器架构搭载 Cortex-M7 内核,支持双精度浮点单元(FPU)和全套 DSP 指令,擅长实时信号处理、电机控制等数学密集型任务。其 1027 DMIPS 的运算能力(Dhrystone 2.1基准)远超同类产品,可流畅运行图形界面或多任务系统。存储配置,2 MB 双区 Flash:支持读写同步,适合存储大型固件与数据。1 MB RAM:包含 192 KB 超高速 TCM RAM(64 KB指令+128 KB数据),专为实时任务优化;另有 864 KB 用户 SRAM,满足动态数据缓存需求。集成了35+个通信接口与11类模拟外设,覆盖绝大多数工业与消费电子需求。芯片划分为 3 个独立电源域(D1高性能核心/D2通信/D3控制),可单独关闭未用模块。待机功耗仅 2.95 µA(关闭备份 SRAM,保留 RTC),适合电池供电设备(如便携医疗仪器)。当你的项目需要同时驱动触摸屏、处理传感器网络、控制电机并联网交互时,它就是那块能扛起所有任务的“瑞士军刀级芯片”。故采用此芯片,学习rgb屏幕的驱动,和nandflash的驱动。存储和显示一直就是本人想深入学习的知识点。
nandflash:W25N01GVZEIG 是 Winbond(华邦电子)推出的 1Gb(128MB) SPI NAND 闪存芯片,采用 WSON-8 封装。作为工业级高性能存储器,它通过 SPI 接口简化设计,适用于对空间、功耗和数据可靠性要求较高的场景。支持 104MHz 时钟频率,快速响应数据请求。典型擦写次数 10 万次(需配合均衡算法延长寿命)。
sdram:W9825G6KH-6 芯片容量 256Mbit(32MB,组织结构为 16M × 16位)。最高 166MHz(CL=3),支持突发读写操作(突发长度可配置为1、2、4、8或整页)。CAS延迟(CL)为3个时钟周期,行预充时间(tRP)≤15ns,行列地址延迟(tRCD)≤15ns。每 64ms 需刷新8192行,刷新间隔约 7.81μs。属于Winbond 的经典 256Mb SDRAM,以 166MHz 高频、低功耗及工业级可靠性 成为消费电子与嵌入式系统的性价比之选。设计时需严格遵循时序约束和PCB规范,高EMC环境建议增加π型滤波电路。若遇供应紧张,可考虑工业级型号或低频替代品,但需评估容量与性能是否匹配需求。
三 设计思路
板载芯片较少,主要是一些对时许要求高的存储,显示等外设。其余扩展版在引出。
当前重点调试得芯片是 nandflash usb 和sdram。
缺陷:没有讲通用的接口如iic spi等接口单独引出做一个独立接口引出,后续会通过扩展板引出所有需要的接口。
四 原理图/pcb介绍
1 介绍
16针得usb插口,带外一个电源指示灯和3.3v转换电路。
引出所有得空闲引脚,方便扩展版得使用。
h7外围电路,有一个可编程得led 。
下面是sdram得外围电路。
nandflash得电路,使用了qspi接口。
下面是pcb的设计图。
2 优化设计
在调试过程中,第一版没引出的引脚有点多,有点遗憾。测试的过程中,虽然板载外设使用问题不大,可是板子看起来有点丑,玩起来不开心,于是更新了原理图,将nandflash,rgb引线和sdram做了等长,重新打板,焊接后板子如下图。
五 外设驱动展示
项目导出cmake编译,使用linux环境做开发,项目配置如下
时钟配置,按照上限配置时钟参数
配置用户灯引脚,ph7
控制一下led,看一下板子是否可控
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_7, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_7, GPIO_PIN_SET);
}
1 串口的驱动
开启uart的输出,用于打印日志,调试程序
cube配置如下,开启uart1,查看原理图的uart引脚,配置相应的引脚
因为是使用gnuc,增加函数复写代码,如下
#ifdef __GNUC__
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
return len;
}
#endif
就可以愉快的使用printf写日志了
printf("\r\n\r\n****************************************************************\r\n");
printf("****************************************************************\r\n");
printf("-------------hello man!!------tm[[%s]-------\r\n",__TIMESTAMP__);
printf("****************************************************************\r\n");
效果如下
2 nandflash的驱动
下面进行对板载的flash进行读写
首先cube配置qspi控制器
w25n01gvzeig flash 设备id
对比了一下全功能qspi flash,w25n01gvzeig并不是完整的qspi设备,仅仅支持读写的qspi命令。
因为指令较少,需要的初始化流程较少,所以初始化完控制器后,仅需少量的初始化代码就能进行读写操作。
重启设备:#define QSPI_CMD_RESET 0xff
开启写:#define QSPI_CMD_W_EN 0x06
读出jedec id
QSPI_CommandTypeDef s_command;
uint32_t Temp = 0;
uint8_t pData[4];
/* 读取JEDEC ID */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QSPI_CMD_JEDEC_ID;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DummyCycles = 0;
s_command.NbData = 4;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Log_info_G("something wrong ....\r\n");
}
if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
Log_info_G("something wrong ....\r\n");
}
Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );
Log_info_G("*****************pData[%x:%x:%x:%x]*************\r\n",pData[0],pData[1],pData[2],pData[3]);
return Temp;
效果:
擦除一个块
// 擦除一个 block
uint8_t BSP_QSPI_erase_block(uint8_t pageAd)
{
W25NXX_Write_enable();
QSPI_CommandTypeDef s_command;
uint8_t pData[4] = {0x00, 0x00, 0x00, 0x00};
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QSPI_CMD_BLOCK_ERASE;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AddressSize = QSPI_ADDRESS_8_BITS;
s_command.Address = 0xB0; // 读取起始地址
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.AlternateBytes = 0;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
s_command.DummyCycles = 8;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.NbData = 2;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Log_info_G(" cmd wrong ....\r\n");
return 1;
}
pData[0] = (pageAd >> 8) & 0xFF; // 高字节
pData[1] = pageAd & 0xFF; // 低字节
if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
Log_info_G(" data wrong ....\r\n");
}
Log_info_G("erase block addr: [%d]~ ....\r\n",pageAd);
wait_flash_ready_tmout();
return QSPI_OK;
}
对特定页写入数据
uint32_t QSPI_FLASH_write_page(uint32_t pageAd,uint8_t* dataPtr,uint32_t wrByte)
{
QSPI_CommandTypeDef s_command;
uint32_t Temp = 0;
uint8_t pData[4]= {01,0};
W25NXX_Write_enable(); // 发送写使能命令
BSP_QSPI_erase_block(pageAd);
// 等待擦除完成
wait_flash_ready_tmout();
W25NXX_Write_enable(); // 发送写使能命令
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QSPI_CMD_WR_ALL_BUF;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_16_BITS;
s_command.Address = 2; // 读取起始地址
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.AlternateBytes = 0;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_16_BITS;
s_command.DummyCycles = 0;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.NbData = wrByte;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Log_info_G("QSPI_FLASH_write_page cmd wrong ....\r\n");
return 1;
}
if(HAL_QSPI_Transmit(&hqspi, dataPtr, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
Log_info_G("QSPI_FLASH_write_page data wrong ....\r\n");
}
Log_info_G("QSPI_FLASH_write_page success~ ..wrByte[%d]..\r\n",wrByte);
// 等待写入完成
wait_flash_ready_tmout();
Log_info_G("QSPI_FLASH_write_page end~ ....\r\n");
// write load
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QSPI_CMD_LOAD_EXEC;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AddressSize = QSPI_ADDRESS_16_BITS;
s_command.Address = 2; // 读取起始地址
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.AlternateBytes = 0;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_16_BITS;
s_command.DummyCycles = 8;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.NbData = 2;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
Log_info_G("QSPI_FLASH_write_page cmd wrong ....\r\n");
return 1;
}
pData[0] = (pageAd >> 8) & 0xFF; // 高字节
pData[1] = pageAd & 0xFF;
if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
Log_info_G("QSPI_FLASH_write_page data wrong ....\r\n");
}
return Temp;
}
读特定页
uint8_t BSP_QSPI_Read(uint32_t pageAd,uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
{
QSPI_FLASH_load_page(pageAd);
QSPI_CommandTypeDef s_command;
/* 初始化读命令 */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = QSPI_CMD_READ;
s_command.AddressMode = QSPI_ADDRESS_1_LINE;
s_command.AddressSize = QSPI_ADDRESS_16_BITS;
s_command.Address = ReadAddr; // 读取起始地址
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.AlternateBytes = ReadAddr;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_16_BITS;
s_command.DummyCycles = 8;
s_command.DataMode = QSPI_DATA_1_LINE;
s_command.NbData = Size;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* 配置命令 */
if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
Log_info_Q("send cmd fail!!");
return QSPI_ERROR;
}else{
Log_info_Q("read :send cmd success!!");
}
/* 接收数据 */
if(HAL_QSPI_Receive(&hqspi, pData,HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) {
Log_info_Q("read data fail!!");
return QSPI_ERROR;
}
return QSPI_OK;
}
下面一个测试例子,再99页,第二个字节开始写入字符串:
char *dataPtr = "Hello, QSPI Flash! by zsy";
测试代码:
测试效果如下
3 移植letter-shell
克隆代码地址:NevermindZZT/letter-shell: letter shell
简单使用的话,移植比较简单,实现一个uart的读写函数即可。
对于裸机程序,把读的实现放到中断里。
主要代码如下
初始化函数
void User_Shell_Init(void)
{
//注册自己实现的写函数
shell.write = userShellWrite;
//调用shell初始化函数
shellInit(&shell, shell_buffer, 512);
}
写函数
short userShellWrite(char *data, unsigned short len)
{
HAL_UART_Transmit(&huart1, data, len, 0xFFFF);
return 0;
}
读函数
extern Shell shell;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* 判断是哪个串口触发的中断 */
if(huart ->Instance == USART1)
{
HAL_UART_Receive_IT(&huart1, (uint8_t*)&hrecv_buf, 1);
shellHandler(&shell, hrecv_buf);
}
}
创建一个新命令
void helloWorld(void)
{
int a;
char b[12];
shellPrint(shellGetCurrent(), "hello world!!\r\n");
}
SHELL_EXPORT_CMD(
SHELL_CMD_PERMISSION(0x00)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN,
hw_out, helloWorld, my test);
效果如下
输入:hw_out,打印hello world。
问题:
1 使用ansi转义打印颜色,通过minicom发现日志不能显示颜色
原因:终端(如linux控制台)支持标准ANSI转义序列色彩。由于termcap显然没有对于色彩的支持,因而minicom 硬性内置了这些转义序列的代码。所以此选项缺省为off。使用‘-c on’可以打开此项
使用开启minicom时候,加上 -c on即可
4 usb的驱动和cherryusb的移植
stm芯片demo代码:https://github.com/CherryUSB/cherryusb_stm32.git
在项目里新建一个文件夹,把cherryusb复制进去
使用cubemx帮助配置usb 并初始化usb控制器。
修改cmakelist,这里也就是所有的工作量了,具体应用使用源码里的实例。修改如下:
增加两行代码:
extern void msc_ram_init(uint8_t busid, uint32_t reg_base);
msc_ram_init(0, USB_OTG_FS_PERIPH_BASE);
此处使用源码提供的msc例程,方便快捷的开启usb应用。
效果如本文的视频所示。空间大小512k字节,代码使用的ram空间作为u盘的实际空间断电u盘数据重置。
六 代码
库地址:https://gitee.com/zhang-sy/make-blocks_1_stm32h743ii
七 结语
stm32h743外设有点多,硬件框架有点复杂,引脚也多,目前还是在熟悉外设的过程中。一直想学习一些复杂一点的芯片。h7芯片除了没mmu,也和正经的soc没什么区别了。但是soc相比,开发困难度低个维度,所以将来会持续多学习一一段时间,相信对于本职工作会有很大裨益。