使用代码生成了一下赤道/南回归线/北回归线/北纬30°的KML。
具体效果可以在B站看到。
2.地理素材:赤道
HT513 是一款国产的D类单声道I2S 功放芯片。特别之处在于它支持通过 I2C 接口控制输出音量,因此可以在保证输出效果的情况下极大简化软件设计。
首先设计一个 HT513的功能板:
PCB设计如下:
焊接完成后,可以直接在面包板上使用。
这次测试DFRobot 的 FireBeetle 通过 I2S 接口发送音频数据给 HT513, 最终通过喇叭将音频播放出来:
HT513模块 | FireBeetle | 说明 | 说明 | FireBeetle | HT513模块 | |
GND | GND | 地 | 数字电源 | 3V3 | VCC33 | |
SD# | N/A | 接地关闭功放 | 地 | GND | GND | |
SDA | IO21 | I2C 数据 | 芯片错误 | N/A | FAULT | |
SCL | IO22 | I2C 时钟 | 模拟参考电压 | N/A | BYPASS | |
MCK | IO0 | I2S主时钟 | 功放负输出 | N/A | LOUTN 接喇叭 | |
BCLK | 14 | I2S串行时钟 | 地 | GND | GND | |
DIN | 18 | I2S数据 | 功率电源 | VCC | PVDD | |
LRCLK | 15 | 帧时钟 | 功放正输出 | N/A | LOUTP 接喇叭 |
测试使用 AudioTools 库(arduino-audio-tools-1.0.0)。特别注意: HT513 工作时需要 MCLK 信号,因此需要修改库文件 AudioConfig.h:
#define PWM_FREQENCY 30000
#define PIN_PWM_START 12
#define PIN_I2S_BCK 14
#define PIN_I2S_WS 15
#define PIN_I2S_DATA_IN 32
#define PIN_I2S_DATA_OUT 18
#define PIN_I2S_MCK 0
#define I2S_USE_APLL true
// Default Setting: The mute pin can be switched actovated by setting it to a gpio (e.g 23). Or you could drive the LED by assigning LED_BUILTIN
#define PIN_I2S_MUTE -1
#define SOFT_MUTE_VALUE 0
#define PIN_CS SS
#define PIN_ADC1 34
测试的代码是基于这个库自带的两个例程,一个是生成正弦波送至I2S接口(streams-generator-i2s),我们在这里测试评估 HT513音量调整的功能;另外一个是播放存储在 Flash 中的WAV文件(streams-memory_raw-i2)。在例子的寄存上增加对于 HT513初始化设定的代码。关键部分如下:
#define HT513_ADDR_L 0x6c
/**
@brief ht513写寄存器
@param addr 寄存器地址
@param val 要写的值
@retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.write(val);
Wire.endTransmission(true);
}
/**
@brief ht513读寄存器
@param addr 寄存器地址
@retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
uint8_t temp = 0;
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.endTransmission(true);
Wire.requestFrom(HT513_ADDR_L, (uint8_t)1);
temp = Wire.read();
return temp;
}
在Setup() 中加入下面的代码:
Wire.begin(21,22);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12,0b11110000);
// 设置数据格式为 I2S, 16Bits
HT513_WriteOneByte(0x13, 0b00110000);
// 调整音量
HT513_WriteOneByte(0x16, 0b10100000);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12,0b11110100);
其中的HT513_WriteOneByte(0x16, XX) 是调整音量的代码:
换句话说,有了上面的代码就可以完整的发挥出 HT513的功能了。
最终完整的代码streams-generator-i2sHT513.ino 如下:
#include <Wire.h>
#include "AudioTools.h"
AudioInfo info(44100, 1, 16);
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
I2SStream out;
StreamCopy copier(out, sound); // copies sound into i2s
#define HT513_ADDR_L 0x6c
/**
@brief ht513写寄存器
@param addr 寄存器地址
@param val 要写的值
@retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.write(val);
int ack = Wire.endTransmission(true);
Serial.print("Ack ");
Serial.println(ack, HEX);
}
/**
@brief ht513读寄存器
@param addr 寄存器地址
@retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
uint8_t temp = 0;
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.endTransmission(false);
uint8_t bytesReceived = 0;
bytesReceived = Wire.requestFrom(HT513_ADDR_L, (uint8_t)1, true);
if (bytesReceived == 1) {
temp = Wire.read();
}
else {
Serial.println("Read Error ");
}
return temp;
}
// Arduino Setup
void setup(void) {
Wire.begin(21,22);
// Open Serial
Serial.begin(115200);
while (!Serial);
AudioLogger::instance().begin(Serial, AudioLogger::Info);
// start I2S
Serial.println("starting I2S...");
auto config = out.defaultConfig(TX_MODE);
config.copyFrom(info);
out.begin(config);
// Setup sine wave
sineWave.begin(info, N_B4);
Serial.println("started...");
int nDevices;
byte error, address;
Serial.println("Scanning...");
nDevices = 0;
for( address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
nDevices++;
}
else if (error==4) {
Serial.print("Unknow error at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
}
else {
Serial.println("done\n");
}
// 设置 SD 为LOW
HT513_WriteOneByte(0x12, 0b11110000);
// 设置数据格式为 I2S, 16Bits
HT513_WriteOneByte(0x13, 0b00110000);
// 调整音量
HT513_WriteOneByte(0x16, 0b01111111);
// 设置 SD 为HIGH
HT513_WriteOneByte(0x12, 0b11110100);
uint8_t Value = HT513_ReadOneByte(0x12);
Serial.println("++++++++++++++++");
Serial.println(Value, HEX);
}
// Arduino loop - copy sound to out
void loop() {
copier.copy();
}
本文提到的HT513 测试板电路图和 PCB:
工作的视频在:
2025慕尼黑上海电子展将于4月15-17日在上海新国际博览中心举办,本届展示面积达10万平,将设立半导体、传感器、电源、测试测量、半导体智造、分销商、无源器件、显示、连接器、开关、线束线缆、印刷电路板、电子制造服务等展区,1,700家海内外优质展商将纷纷加入,从设计研发到应用落地,横跨电子产业上下游,与业界同仁共绘电子行业未来蓝图。
2025慕尼黑上海电子展
一、展会时间
2025年4月15-17日
二、参观时间
4月15日(周二) 9:00–17:00
4月16日(周三) 9:00–17:00
4月17日(周四) 9:00–16:00
三、展会地点
展馆:上海新国际博览中心(W3-W5馆 & N1-N5馆)
地址:浦东新区龙阳路2345号 (地铁站旁边)
扫码即可免费注册参观,在上海的朋友有兴趣可以去转转:
或者使用这个链接进行注册 https://ec.global-eservice.com/web/?lang=cn&channel=bdsem#/index
一个CS编译生成 EFI 的框架
偶然间看到了 https://github.com/bflattened/bflat 这个项目(官方网站https://flattened.net),可以使用 C# 编程,然后生成 EFI 文件。
下载项目 Release 文件,解压后将samples/Snake/ 中的解压进去。使用如下命令即可编译:
bflat build --os:uefi --stdlib:zero -o:bootx64.efi
编译之后即可得到 bootx64.efi, 可以在 VirtualBox上运行起来:
感觉目前的支持还不完整,很多基本的库还未完成 Porting。有兴趣的朋友可以研究一下。
本文是应作者格拉斯神剑要求,转载的文章。原文在:https://www.bilibili.com/opus/1049776129847590912?spm_id_from=333.1387.0.0
有兴趣的朋友可以关注他的项目。
格拉斯神剑使用Pascal(Free Pascal)语言写了一个能够将Linux下的可执行文件格式ELF转为UEFI下的可执行格式EFI的转换器(为了跨平台方便,做成了命令行程序,可以把各个UEFI所支持架构对应的ELF文件转换为对应的EFI文件(并不是x64平台下只能转换x64平台下的elf文件,而是什么架构的elf文件转换为对应架构的efi文件,如果elf是riscv的,转换为riscv上的UEFI EFI文件,这点可以直接点击或命令行执行elf2efi/elf2efi.exe可以看到),不知道这是不是第一个使用Pascal写elf2efi软件的。
当然这里的ELF文件是有要求的,要求如下:
1.转换前的ELF文件必须是static-pie(静态并且位置无关的可执行文件)(可以用-fPIE -Wl,–no-dynamic-linker(GNU C Compiler,gcc)或-k-pie -k–no-dynamic-linker(free pascal compiler,fpc)的编译选项得到)。
2.不能链接任何动态库,否则会导致转成的EFI文件在UEFI下运行失败。
3.对于x64平台,入口函数要求必须是微软ABI;对于i386平台,入口函数要求必须是cdecl(c的调用规范)。显然,对于所有UEFI支持的平台,其入口函数的参数必须符合UEFI规范(在UEFI国际论坛(UEFI.org)的开发者选项点开规范,会看到UEFI Specifications,点开版本号最高的就是,点进去搜索入口函数的定义即可)。
4.ELF文件本身不能有空指针这样运行时发生的错误,否则就会在转换的EFI文件里面出现错误并被UEFI Debugger报告显示。
5.ELF文件必须平台无关,避免链接系统相关的静态库和标准静态库(当然UEFI相关的库可以链接,但必须是静态库,不能动态库,动态库在链接时不会链接进去,会造成EFI文件运行时发生错误)
本程序既可以在gitee上面(https://gitee.com/tydqsoft/elf2efi)也可以在github上面(https://github.com/TYDQSoft/elf2efi)上面进行下载(两边都是一样的,下载最新版本Alpha v0.0.2就行,顺带附上Pascal源代码,自由开源)
顺带附上该命令行程序的截图(amd64架构windows下):
1.对UEFI支持的所有架构(总共4个,x64,aarch64,riscv64,loongarch64)的支持更加完美
2.比上一个版本有更快的转换速度
3.转换出来的efi文件一般来说比elf文件小
(以下的测试efi程序(由elf文件转换出来的efi文件)的功能仅是把屏幕的所有部分都变成黄色,当然不同架构的需要编译器编译成不同架构的二进制文件elf然后使用elf2efi转换来进行测试)(本elf2efi软件是自己用pascal写的,不是github上那个用C语言写的elf2efi)
x64转换成功可运行示意图:
在研发测试中,我们经常需要从网络或者本地播放一段视频以便验证系统的稳定性。同样的,我们在测试网络视频播放的问题时经常要被问到: 本地播放是否存在问题?因此,这里研究如何生成一个测试视频,并且传到网络上,这样可以方便的得知问题是否和网络有关系。
ffmpeg 可以使用下面的命令产生一个5秒,1280×720名称为LABZPattenX.mp4的
本地视频,编码和压缩使用 Windows 默认方式,这样生成的视频可以直接在Windows中播放:
ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=5:size=1280x720:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p -b:v 6000k LABZPattenX.mp4
视频内容:
为了更好的测试,我用上述方法生成了1小时的视频内容上传到 B站,链接如下:
有兴趣的朋友可以使用如下方法生成1小时的内容
ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=3600:size=1920x1080:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p LABZPatten1.mp4
还可以指定一个较高的码率:
ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=3600:size=1920x1080:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p -b:v 6000k LABZPatten3.mp4
谢尔宾斯基地毯:
ffmpeg -y -filter_complex sierpinski=s=1920x1080:type=carpet:rate=60:jump=3:seed=1 -c:v libx264 -pix_fmt yuv420p -b:v 6000k -t 3600 LABPatten2.mp4
上述方法的优点:
网站提供了这次测试对应的 FFMPEG 文件,有兴趣的朋友可以在网站的“常用测试工具以及软件下载”页面看到:
有需要的朋友可以自行尝试。
本文提到的生成的测试视频可以在下面的链接看到:
参考:
UEFI Shell 是一个类似 DOS Command 启动环境的东西,对于普通用户来说它最常见的功能是启动进入之后能够烧写更新BIOS或者其他Firmware。
这次就介绍一个如何制作标准的 UEFI Shell 启动盘。
简单的说,主要分为两步:
1.格式化一个U盘,特别注意需要格式化为 FAT
对于一些容量较大的U盘,还可以选择 exFAT,但是兼容性会差一些,不能保证一定能够启动
2.下载一个 UEFI Shell 的 EFI 文件,这个是 EDK2 开源的项目,在 https://github.com/tianocore/edk2 下载到源代码后,可以编译获得 EFI 文件。如果觉得麻烦,可以在这里下载一个我编译好的(来自edk2-stable202408.01)
3.在上面格式化好的U盘上创建 EFI\BOOT 目录,然后放入 UEFI Shell 文件并且命名为 BOOTX64.EFI
4.在目标机上选择启动到U盘,即可进入 UEFI Shell。
如果你希望其他版本的UEFI Shell(比如 IA32版本, ARM 版本),可以在 https://github.com/pbatard/UEFI-Shell/releases/tag/24H2 下载到
让AI 给我写了一个颜色渐变的算法,代码如下:
#include <graphics.h> // EasyX图形库头文件
#include <conio.h> // 用于_getch()
#include <math.h>
#include <stdio.h>
#include <cstdio>
#include <conio.h>
#include <tchar.h>
// HSL转RGB辅助函数
void HSLtoRGB(float h, float s, float l, BYTE& r, BYTE& g, BYTE& b) {
float q = l < 0.5 ? l * (1 + s) : l + s - l * s;
float p = 2 * l - q;
float t[3] = { h + 1 / 3.0f, h, h - 1 / 3.0f };
for (int i = 0; i < 3; ++i) {
if (t[i] < 0) t[i] += 1;
if (t[i] > 1) t[i] -= 1;
if (t[i] < 1 / 6.0f)
t[i] = p + (q - p) * 6 * t[i];
else if (t[i] < 0.5f)
t[i] = q;
else if (t[i] < 2 / 3.0f)
t[i] = p + (q - p) * 6 * (2 / 3.0f - t[i]);
else
t[i] = p;
}
r = static_cast<BYTE>(t[0] * 255);
g = static_cast<BYTE>(t[1] * 255);
b = static_cast<BYTE>(t[2] * 255);
}
// 在视图类中定义成员变量
float m_currentHue = 0.0f; // 当前色相值 [0,1]
float m_targetHue = 0.0f; // 目标色相值 [0,1]
float m_transitionSpeed = 0.1f; // 颜色过渡速度
int main()
{
char Buffer[256];
// 初始化640x480像素的图形窗口
initgraph(640, 480);
for (int i = 0; i < 60*30; i++) {
BeginBatchDraw();
cleardevice();
// 线性插值过渡
m_currentHue += (m_targetHue - m_currentHue) * m_transitionSpeed;
// 到达目标时生成新随机目标
if (fabs(m_targetHue - m_currentHue) < 0.001f) {
m_targetHue = static_cast<float>(rand() % 1000) / 1000.0f;
}
BYTE r, g, b;
HSLtoRGB(m_currentHue, 0.7f, 0.5f, r, g, b); // 固定饱和度和亮度
printf("[%f]->%d %d %d\n", m_currentHue,r, g, b);
setbkcolor(RGB(r, g, b));
EndBatchDraw();
sprintf_s(Buffer, sizeof(Buffer), "output\\%05d.png", i);
LPCTSTR lpctstr = (LPCTSTR)Buffer; // 直接强制类型转换
printf("%s\n", Buffer);
saveimage(lpctstr);
}
// 保持窗口显示
//_getch();
// 关闭图形窗口
closegraph();
return 0;
}
上述代码能够无限生成渐变的颜色。
测试的视频如下:
最近又需要编辑和生成 ISO 格式的光驱文件,经过研究发现AnyBurn满足要求,最关键的是它的 Free版本足以满足要求:
有兴趣的朋友可以在下面下载到
这次的实验是将 RU 内置在Shell 中,当你输入 zru 之后就可以执行 RU(特别命令为 zru 是为了和 ru 避免冲突)。代码是基于之前的 MyCmd 实现的。特别需要注意的地方是 RuCommand.c 文件中的 MyShellCommand() 函数中,我们调用 LoadImage 需要传递 ParentImageHandle, 但是MyShellCommand(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) 传递进来的ImageHandle一直是0。这样会导致LoadImage调用失败。因此,这里使用了gImageHandle,给出的是 UEFI Shell 的 Handle。
//
// Load the image with:
//
Status = gBS->LoadImage(
FALSE,
gImageHandle,
DP,
(VOID*)&ru_efi[0],
ru_efi_size,
&NewHandle);
if (EFI_ERROR(Status)) {
Print(L"Load image Error! %r\n",Status);
return 0;
}
最终生成的完整 UEFI Shell:
完整的代码(基于 EDK2 202411 ):
这样做的好处是方便用户使用,只用一个文件就可以实现很多功能,缺点是:更新内容不太容易。
参考: