页次: 1
感谢楼主踩坑。我在这个基础上移植了,有两个问题不知道楼主有没有遇到过?
1. 背景音乐速度比正常的快、音调高,但画面速度是正确的(和电脑上的模拟器比较过)。APU_SAMPLE_RATE改过的,和驱动里的采样率一样。
2. 屏幕上每16列有一条竖线。
(05.25 update)问题2解决了,竖线是一直没有写入的缓存,即ppu代码里每过16个像素有一个跳过了没写。
找到如下这段代码:
for(i=sx;i<ex;i++)
{
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i++]];
plcd[i]=NES_Palette[ppu->dummy_buffer[i]];
}
改成这样就好了(可能是编译器优化-O3的原因)
for(i=sx;i<ex;i++)
{
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]]; i++;
plcd[i]=NES_Palette[ppu->dummy_buffer[i]];
}
}
F1C100s上写数据到Nand Flash,LittleFS文件系统。保存的数据有很多bit错误。
调试发现和LittleFS层无关,不经过文件系统直接写Flash也会出错。
花了一天时间换不同的数据测试写Flash,情况非常奇怪,写入比较“规律”的数据则很少出错,写入比较“乱”的数据则很多错误。
是坏块的问题吗?不像,因为出错的数目远超过正常的坏块数。
而且写入不同的数据,出错的地方也不一样。每次都是一个bit错误,应当是1的bit变成了0。
…………
…………
在快要下班的时候,换块Flash试试。咦?Flash ID怎么不一样了?
换了Flash竟然完全正常了!!
原来是从代理商那拿了两种样品,两种的型号只差一点点,代理商说用起来是一样的。我照着一个规格书实现了驱动。
但其实两种型号,一个是内置4-bit ECC,一个是需要host自己做8-bit ECC。
我一开始焊的是不带ECC的那种,所以发生好多bit错误。换成内置ECC的就全好了!
…………
愚蠢的一天啊[哭][哭][哭]
以前没做过ECC相关的,不知道Nand Flash内部其实会有这么多错误,非常依赖ECC。
GNU Arm Embedded Toolchain 官网 https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
搜到了ST的Smart Reset IC系列专门干这个事情 https://www.st.com/en/reset-and-supervisor-ics/smart-reset-ics.html,型号为STM65xx/SR1/SR2。有长按1个键的,有同时长按2个键的。长按延时几秒到十几秒后触发reset。
本来以为用几个分立器件能搭起来,但似乎没那么简单。又要长按开机,又要能长按更长时间reset,不太好做。
4楼说的watchdog电路也是要软件里喂狗的,软件如果有bug,比如在死循环里喂狗,那外部watchdog也没办法。
5楼的cat809是电压检测,供电电压低于多少一小段时间再恢复,则触发reset。
另外还想请教各位大佬一个问题:在sys-dram.c中声明了一个结构体变量,在声明的同时赋值,但是这样cpu就不知道跑哪去了,改成下面这样就好了
https://whycan.cn/files/members/1924/03.png
但page_size放外面赋值总觉得有点刺挠:),想问问各位大佬有啥解决办法吗。
把几个变量赋值顺序调成和结构体声明里的一样试试?
找找官方文档,STM8不知道,STM32的ADC上电后要等待校准
//Enable ADC reset calibration register
ADC_ResetCalibration(adc);
//Check the end of ADC reset calibration register
while (ADC_GetResetCalibrationStatus(adc)) {}
//Start ADC calibration
ADC_StartCalibration(adc);
//Check the end of ADC calibration
while (ADC_GetCalibrationStatus(adc)) {}
这两天临时出去有事了,所以没能按时试。
经测试RTT用GCC编译,没有这样的问题。WDT正确的工作了。rt_kprintf("start wdg test----------------------------------\n");
struct A* a = (struct A*)malloc(sizeof(struct A));
// ...
free(a);
// a被释放,指向的地址重新分配后写入了其他内容
// ...
a->func(); // 野指针使用,危险!
rt_kprintf("end wdg test----------------------------------\n");
我那个只是示意代码。是要向那块被释放的地址里写入合适的数据,使a->func的值为0xdba00000 ~ 0xffff4040。
继续研究。C++对象内存布局虽然没有固定标准,是编译器实现的,但基本是按照《深度探索C++对象模型》的样子。
一个带有虚函数的类的指针,指向的内存区域最开头应该是类的虚函数表指针。该指针指向一个数组,数组成员是函数指针。
在上面的例子中,当调用p->g();时,由于p指向的内存区域全是0,所以被当做是地址0x0处开始为虚函数表。
g()是类的第二个虚函数,所以把地址0x4当做函数指针。
打印出地址0x0附近的内存看:
0: 0xea00000d
1: 0xe59ff014
2: 0xe59ff014
3: 0xe59ff014
4: 0xe59ff014
5: 0xe59ff014
6: 0xe59ff014
7: 0xe59ff014
8: 0x80000260
看到0x4地址处的内容是0xe59ff014,接下来会把0xe59ff014当做函数地址去调用。就是经过这个非法函数调用后,看门狗失效并且死机!
复现此bug的代码可以再精简为:
typedef void (*FuncPtr)();
FuncPtr f = (FuncPtr)0xe59ff014;
f(); // 触发看门狗bug
继续做实验发现,并不是只有这一个特殊的地址调用会触发bug,实际上有很大范围的地址都能引起看门狗无效。
从 0xdba00000 ~ 0xffff4040,这个区域内的地址,如果赋值给函数指针来调用,都会引发看门狗失效bug!!小于0xdba00000或大于0xffff4040的地址调用,则能正常死机并触发看门狗。(具体地址会变化,还跟CPU频率有关,不是完全精确的范围。)
这个范围是很大的,也就是说如果程序写错了,还是会有挺大的可能性遇到此bug。
比如考虑下面这个常见的野指针访问代码,触发看门狗bug的概率并不小。
struct A
{
int data;
void (*func)();
};
A* a = (A*)malloc(sizeof(A));
// ...
free(a);
// a被释放,指向的地址重新分配后写入了其他内容
// ...
a->func(); // 野指针使用,危险!
刚调了好几个小时的灵异bug,现象是启动后有时候会死机。这时候已经启动了看门狗,但狗没有起作用,系统一直卡在那不reset。
最后调出来是个空指针错误。空指针毕竟是编程比较常见的错误,空指针能导致看门狗无效,F1C100s的这个bug也是有点严重。
一番精简之后,找到了最小复现代码:
只要用一个基类指针,指向一块内容为0的区域,调用基类的第二个虚函数,就很容易使看门狗失效并死机。
这个bug可能跟编译器的具体实现(C++类对象内存布局)有关,我用的GCC,其他编译器不一定是这样。
class Base
{
public:
virtual void f() = 0;
virtual void g() = 0;
};
// main():
// 设置看门狗,5s的时长
uint8_t empty[1024];
memset(empty, 0, sizeof(empty));
Base* p = (Base*)∅ // 野指针,指向了一块内容全为0的内存
feedWatchdog(); // 喂狗
mdelay(10); // 等待超过1500ms则不出bug,等待0ms也不出bug,这之间都会遇到bug
p->g(); // 调用基类的第二个虚函数(第一个没问题),从这里就死机了。看门狗无效。
求赐教,这两个寄存器是什么功能?
https://whycan.cn/files/members/1964/test.png
Wait State意思不知道。
Burst Length大约是每次传输数据,在总线上连续传几个“Data Width”那么大的数据。
对于从内存到内存,Burst Length设为4直接就可以用。
对于从内存到外设或外设到内存的DMA,如果Burst Length设为4,外设的FIFO一般有trigger level也要对应改。比如trigger level默认为1,表示FIFO里只要有1个数据就发送DRQ,让DMA来拷走。当Burst Length为4,trigger level也要>=4。
反过来,如果是从内存到外设拷数据的,FIFO的trigger level需要 <= (FIFO总长度 - 4)。
cpu把数据写到内存,然后交给外设或dma,要用clean;
cpu读取由外设或dma放在ram的数据之前,要invalid;
dcacheinvalid和dcacheclean指令,要么是针对mva的,要么是针对cache-line的,要么就整个dcache
所以MMU_InvalidateDCacheArray函数封装了mva的遍历。
对ram变量也有cache-line对齐的要求,即变量或数组的大小和地址必须32字节对齐,否则会破坏变量。
看了这几个cache相关函数的实现,有个疑问。函数里需要遍历整块内存,以32字节为单位去调用 MCR p15……。这样岂不是很低效?
如果我有几百K的数据从外设读回来,实际上CPU cache只有一点点,并不需要遍历几百K去invalidate cache。
那怎样能够仅仅invalidate需要的cache呢?当然可以invalidate整个cache,还有没有更精简的方法呢?
void MMU_InvalidateDCacheArray(unsigned long mva, unsigned long num)
{
signed long size = num;
while (size > 0) {
MMU_InvalidateDCacheMVA(mva);
mva += CACHE_ALIGN;
size -= CACHE_ALIGN;
}
}
1. 初步调通SPI DMA,成果喜人。
SPI Tx和Rx都用上DMA,接示波器看CLK信号。当通过寄存器设置SPI速率设为100MHz的时候,CLK信号实际能跑到96MHz。SPI速率设为50MHz的时候,实际CLK为49.xMHz。
(用XBoot不管怎么设速率,实际的只能到25MHz。一方面是没用DMA,一方面是XBoot的代码不够优化。)
2. 实验用DMA加持的SPI驱动ST7789串口屏。240x240分辨率、RGB565,SPI跑满50MHz,屏幕帧率可以到54。非常满意了。
实验时是用杜邦线接的屏,也许走线好一点的话还能支持更高速率。我现在是只能跑到50MHz了,多1M显示就开始乱了。
3. 实验DMA做内存拷贝,结果如下。DMA拷贝内存,速度并没比memcpy快多少。
有几个参数能影响DMA拷贝速度:1) DMA的种类,用NDMA还是DDMA;2) data width 3) burst length。
而memcpy速度受CPU主频影响(一点点)。
拷贝128KB数据比较:
memcpy 2.4ms(640MHz主频)
DDMA 1.9ms(data width = 32-bit,burst length = 4)
NDMA 2.6ms(data width = 32-bit,burst length = 4)
DDMA拷贝内存最快能到memcpy的1.25倍。
另外,发现DDMA拷贝较大的数据会出错,比如拷贝1MB数据,第一次成功,第二次就从1020KB开始数据不正确了。可能是缓存的原因,待研究。
https://s.taobao.com/search?q=HY951180A
https://item.szlcsc.com/35646.html
都是 HY951180A, 淘宝卖4块, LC卖13.5块?
其实LC上要看批量价格8.58多含税,淘宝是仿品+不含税,差的就没那么大了。
我也对这点感到好奇,毕竟打开某宝也找不到这个价格的F1C200s。
顺带一提,我翻了一下博客,发现博主就是之前搞信用卡上跑Linux的老哥:My Business Card Runs Linux。在这个帖子里面写了F1C100s是$1.42。
原来是这位。帖子里说,是全志主动联系他,白给了些F1C200s样品。
root@yu-virtual-machine:/home/yu/lichee/lvgl_f1c100s# make
arm-eabi-objcopy -v -O binary build/firmware.elf build/firmware.bin
copy from `build/firmware.elf' [elf32-littlearm] to `build/firmware.bin' [binary]
Make header information for brom booting
tools/mksunxi/mksunxi: 1: tools/mksunxi/mksunxi: Syntax error: ")" unexpected
Makefile:140: recipe for target 'build/firmware.bin' failed
make: *** [build/firmware.bin] Error 2各位大佬, 我用这个编译gcc-linaro-5.3.1-2016.05-i686_arm-eabi.tar
出现了以上的错误,请教下是什么问题?
makefile贴一下,多写了个括号吧?
这个可以,理论上用libusb写的功能都可以实现,所以完全可以把sunxi-fel用网页重写一遍。
WebUSB是Google推的,还没成为正式标准,所以只有Chrome支持。标准和API见 https://github.com/WICG/webusb
TF卡隔壁圆的是喇叭?一直想找这种喇叭, 请问喇叭型号是什么?
五向开关。
所以你是想找贴片的喇叭?这种贴片蜂鸣器可以当音质很差的喇叭用 https://item.szlcsc.com/96500.html
CS引脚加个上拉试试?把CS引脚用10K电阻接到3.3V。
看了几种开发板,CS都是有上拉的。
root@hj-virtual-machine:/home/hj# sunxi-fel ver
AWUSBFEX soc=00001663(F1C100s) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000 00000000
root@hj-virtual-machine:/home/hj# sudo sunxi-fel -p read 0x20000000 10240 read.dat
root@hj-virtual-machine:/home/hj# sunxi-fel ver
AWUSBFEX soc=00001663(F1C100s) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000 00000000
root@hj-virtual-machine:/home/hj#
如果read内存10kB可以,spiflash-read不行,那可能还不是USB的问题。
sunxi-fel spiflash-info 试试。如果涉及flash的都出错,那把flash部分的原理图贴一下看看?
可以参考这个外国人的作品,用NFC存储个人信息、点亮LED的名片 https://www.instructables.com/id/PCB-Business-Card-With-NFC/
读写都不行
root@hj-virtual-machine:/home/hj# sunxi-fel ver
AWUSBFEX soc=00001663(F1C100s) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000 00000000
root@hj-virtual-machine:/home/hj# sudo sunxi-fel -p spiflash-read 0x0 1024 read.dat
usb_bulk_send() ERROR -1: Input/Output Error
root@hj-virtual-machine:/home/hj# sunxi-fel ver
usb_bulk_send() ERROR -1: Input/Output Error
不是spiflash-read,就是read/readl读内存。看不涉及flash的操作有没有问题;读小块内存、读大块内存有没有区别。
自己画了个板子,usb能识别,但是烧写报错是怎么回事
root@hj-virtual-machine:/home/hj# sunxi-fel ver
AWUSBFEX soc=00001663(F1C100s) 00000001 ver=0001 44 08 scratchpad=00007e00 00000000 00000000
root@hj-virtual-machine:/home/hj# sudo sunxi-fel -p spiflash-write 0 u-boot/u-boot-sunxi-with-spl.bin
usb_bulk_send() ERROR -1: Input/Output Error
用sunxi-fel的read和write读写大块内存试试,看是不是USB传输数据量比较大时就出错。
有没有极薄的电子纸?
以前看到好几个国外玩家做PCB名片的,都是插USB供电。比较有意思的有这个 StyloCard https://mitxela.com/projects/stylocard,在名片上做了个MIDI键盘。
https://whycan.cn/files/members/1228/agrgdd.png
还有1个SOC的输出,可不可以即接到耳机孔,也接到功放呢?
如果是想要同时接功放和耳机,一般会让插耳机时喇叭静音,需要用到图中耳机插座的1和5脚。
未插耳机时1和5脚是内部有个铜片是闭合的,当耳机插入时把铜片顶开,1和5脚断开。利用这个信号改变功放芯片的使能,使喇叭静音。同时,主控端也可以利用这个信号检测耳机插入,做到耳机和外放有不同的音量控制。
我看到这样一个原理图,将SOC的HPR 和 HPL,通过串联电容和电阻,汇聚到了一点,然后再通过电容电阻接到了功放上面。
我画个图示意一下:
https://whycan.cn/files/members/1228/%20(2).png
我想,我如果有2个SOC,我是不是可以将两个SOC的HPR HPL都接到一个3.5MM耳机座上。像这样。
https://whycan.cn/files/members/1228/asvbsSdfdd.png
不知道这样可不可行,对音频这部分不太懂...
图1电路的作用是双声道转单声道,即把两个声道的信号取平均值。
按图2,两个Soc输出的左声道用电阻接在一起,当只有一个Soc输出声音时,另一边不一定处于什么状态。如果是HPR1是高阻态,则最后输出的信号应该约等于HPR2;如果HPR1是静音但仍在输出,则最后的信号约为HPR2的一半。所以可能会有音量不确定的情况。
没有休眠模式
你可以试试用这个汇编进入类似于休眠状态,但功耗也不会非常低。
__asm__ __volatile__("mcr p15, 0, %0, c7,c0,4" :: "r"(0));
这是ARM926EJ-S手册上提到的 Dynamic power management (wait for interrupt mode) 。我只简单试了一下,在我的板子上低了10mA。没试过唤醒:D。
请问,f1c100s用电池供电的话
1. 电池电量怎么检测,用LRADC吗?大概是个什么用法?
2. 像电池供电设备长按开关机是怎么实现的?
3. USB给锂电池充电求便宜好用的芯片
这几个我正都有用到,说说做法:
先说长按开关机
S1是电源键,按下后Q1导通,系统上电。系统启动后把PWD_EN引脚置高,就能保持开机。
S1的按下状态可以通过BUT_POWER检测到,长按关机就是把PWD_EN置低。
我这里是需要较大电流放电,所以Q1用了功率MOS管。也可以直接接DC-DC的使能。
电量检测是把VIN接两个电阻分压,分到1.xV以下,接LRADC。LRADC有寄存器可以读到电压值,精度比较粗,勉强够用。
便宜的单节锂电池充电用4056。
谢谢指点。 1mv档位, 8格算8mv的话, 噪声有1/8。
实际上我用到20mv档位, 在这个档位,也有10mv的噪声,勉强能用。默认垂直位置不居中,如第一个图,向上偏移了一格,是故障吗 ?
不知道你是用来测什么。在大多数应用里,根本不需要测到这个精度。不管是1mV还是10mV,都是很小的。
实际上我平时用的示波器最小一档精度也就20mV,20mV档时噪声很多,只能看个大概。很少用的着100mV以下档位。
你可以先把右上角“耦合-交流”切换成“直流”,调到1V档位,直接量电池的正负极。波形如果很直,没有噪声,那就基本没问题。再慢慢调到更高精度档位,看什么时候出现明显的噪声。
至于在1mV档位下,向上偏了0.5mV……实在太小了,就忽略吧。
qiefei 说:the brom codes are on the github!
在哪里,有没有地址?
uboot: go 0xffff0020
这样进入FEL模式,可以用sunxi-fel操作SPI Flash吗?我在自己的代码中跳转到0xffff0020,能进入FEL模式,但操作Flash失败。
之前研究了一下下最后也没成功 https://whycan.cn/t_2072.html
手册可以分享一下吗?一直在用C600的手册做C100s。
@hoel I develop for F1C100s on OSX, but the toolchain configuration may depend on what you are building. I only build barebone code and XBoot, no Linux or U-Boot.
You can first try the barebone code minimal_f1c100s_framebuffer.zip from this post: https://whycan.cn/t_1457.html.
1. Download the official gcc-arm-none-eabi from https://launchpad.net/gcc-arm-embedded/+download. I'm using an older version gcc-arm-none-eabi-5_2-2015q4. Extract and add `gcc-arm-none-eabi/bin` to PATH.
2. Extract minimal_f1c100s_framebuffer.zip. Edit `Makefile`, change `CROSS_COMPILE` to `arm-none-eabi-`.
3. `cd tools/mksunxi`, delete the Linux version `mksunxi` and make the OSX version.
3. Run `make` in `minimal_f1c100s`. The build succeeds if you see output "The bootloader head has been fixed".
4. `git clone https://github.com/Icenowy/sunxi-tools.git -b spi-rebase`, then build the OSX version of sunxi-tools.
5. Run `sunxi-fel -p spiflash-write 0 build/firmware.bin` to download the firmware to F1C100s.
DVP驱动可否参考一下,正在驱动摄像头
CSI部分驱动如下。没有考虑什么通用性,只是针对我需要的数据格式。(UYVY -> YUV422)
只有捕捉单张图像,没有视频。
/*
* driver/csi-f1c100s.c
*/
#include <xboot.h>
#include <clk/clk.h>
#include <reset/reset.h>
#include <gpio/gpio.h>
#include <csi/csi.h>
enum {
CSI_EN = 0x000,
CSI_CFG = 0x004,
CSI_CAP = 0x008,
CSI_SCALE = 0x00c,
CSI_FIFO0_BUFA = 0x010,
CSI_FIFO0_BUFB = 0x014,
CSI_FIFO1_BUFA = 0x018,
CSI_FIFO1_BUFB = 0x01c,
CSI_FIFO2_BUFA = 0x020,
CSI_FIFO2_BUFB = 0x024,
CSI_BUF_CTL = 0x028,
CSI_BUF_STA = 0x02c,
CSI_INT_EN = 0x030,
CSI_INT_STA = 0x034,
CSI_HSIZE = 0x040,
CSI_VSIZE = 0x044,
CSI_BUF_LEN = 0x048,
};
enum {
CSI_SIZE_MASK = 0x1fff, // 13 bits
};
struct csi_f1c100s_pdata_t {
virtual_addr_t virt;
char * clk;
char * dramclk;
int reset;
int hsync;
int hsynccfg;
int vsync;
int vsynccfg;
int pclk;
int pclkcfg;
int database;
int datacfg;
int databits;
};
static void csi_f1c100s_get_size(struct csi_t * csi, int* width, int* height)
{
struct csi_f1c100s_pdata_t * pdat = (struct csi_f1c100s_pdata_t *)csi->priv;
if (width) {
*width = (read32(pdat->virt + CSI_HSIZE) >> 16) & CSI_SIZE_MASK;
}
if (height) {
*height = (read32(pdat->virt + CSI_VSIZE) >> 16) & CSI_SIZE_MASK;
}
}
static void csi_f1c100s_set_size(struct csi_t * csi, int width, int height)
{
struct csi_f1c100s_pdata_t * pdat = (struct csi_f1c100s_pdata_t *)csi->priv;
uint32_t hsize = read32(pdat->virt + CSI_HSIZE);
hsize &= ~(CSI_SIZE_MASK << 16) & ~(CSI_SIZE_MASK << 0);
hsize |= ((width * 2) & CSI_SIZE_MASK) << 16; // YUV每像素2字节
write32(pdat->virt + CSI_HSIZE, hsize);
uint32_t vsize = read32(pdat->virt + CSI_VSIZE);
vsize &= ~(CSI_SIZE_MASK << 16) & ~(CSI_SIZE_MASK << 0);
vsize |= (height & CSI_SIZE_MASK) << 16;
write32(pdat->virt + CSI_VSIZE, vsize);
// buffer len
uint32_t buflen = read32(pdat->virt + CSI_BUF_LEN);
buflen &= ~(CSI_SIZE_MASK << 0);
buflen |= (width & CSI_SIZE_MASK); // Y最长,每像素1字节
write32(pdat->virt + CSI_BUF_LEN, buflen);
}
static void csi_f1c100s_set_buffer(struct csi_t * csi, void* buffer, int len)
{
struct csi_f1c100s_pdata_t * pdat = (struct csi_f1c100s_pdata_t *)csi->priv;
// 只用BUFA,3个FIFO必须都设置上
write32(pdat->virt + CSI_FIFO0_BUFA, (uint32_t)buffer);
write32(pdat->virt + CSI_FIFO1_BUFA, (uint32_t)buffer + len * 2 / 4);
write32(pdat->virt + CSI_FIFO2_BUFA, (uint32_t)buffer + len * 3 / 4);
write32(pdat->virt + CSI_BUF_CTL, read32(pdat->virt + CSI_BUF_CTL) & ~(1 << 0));
}
static int csi_f1c100s_capture_still(struct csi_t * csi)
{
struct csi_f1c100s_pdata_t * pdat = (struct csi_f1c100s_pdata_t *)csi->priv;
// begin capture
write32(pdat->virt + CSI_CAP, read32(pdat->virt + CSI_CAP) | (1 << 0));
// clear int status
write32(pdat->virt + CSI_INT_STA, read32(pdat->virt + CSI_INT_STA));
// wait for capture start then stop
ktime_t timeout = ktime_add_ms(ktime_get(), 2000);
int captured = 0;
do {
if(read32(pdat->virt + CSI_INT_STA) & (1 << 0)) {
captured = 1;
break;
}
} while(ktime_before(ktime_get(), timeout));
return captured;
}
static struct device_t * csi_f1c100s_probe(struct driver_t * drv, struct dtnode_t * n)
{
struct csi_f1c100s_pdata_t * pdat;
struct csi_t * csi;
struct device_t * dev;
virtual_addr_t virt = phys_to_virt(dt_read_address(n));
char * clk = dt_read_string(n, "clock-name", NULL);
char * dramclk = dt_read_string(n, "dram-clock-name", NULL);
pdat = malloc(sizeof(struct csi_f1c100s_pdata_t));
if(!pdat)
return FALSE;
csi = malloc(sizeof(struct csi_t));
if(!csi)
{
free(pdat);
return FALSE;
}
pdat->virt = virt;
pdat->clk = strdup(clk);
pdat->dramclk = strdup(dramclk);
pdat->reset = dt_read_int(n, "reset", -1);
pdat->hsync = dt_read_int(n, "hsync-gpio", -1);
pdat->hsynccfg = dt_read_int(n, "hsync-gpio-config", -1);
pdat->vsync = dt_read_int(n, "vsync-gpio", -1);
pdat->vsynccfg = dt_read_int(n, "vsync-gpio-config", -1);
pdat->pclk = dt_read_int(n, "pclk-gpio", -1);
pdat->pclkcfg = dt_read_int(n, "pclk-gpio-config", -1);
pdat->database = dt_read_int(n, "data-gpio-base", -1);
pdat->datacfg = dt_read_int(n, "data-gpio-config", -1);
pdat->databits = dt_read_int(n, "data-bits", -1);
csi->name = alloc_device_name(dt_read_name(n), -1);
csi->get_size = csi_f1c100s_get_size;
csi->set_size = csi_f1c100s_set_size;
csi->set_buffer = csi_f1c100s_set_buffer;
csi->capture_still = csi_f1c100s_capture_still;
csi->priv = pdat;
clk_enable(pdat->clk);
clk_enable(pdat->dramclk);
if(pdat->reset >= 0) {
reset_deassert(pdat->reset);
}
if(pdat->hsync >= 0) {
if(pdat->hsynccfg >= 0) {
gpio_set_cfg(pdat->hsync, pdat->hsynccfg);
}
gpio_set_pull(pdat->hsync, GPIO_PULL_UP);
}
if(pdat->vsync >= 0) {
if(pdat->vsynccfg >= 0) {
gpio_set_cfg(pdat->vsync, pdat->vsynccfg);
}
gpio_set_pull(pdat->vsync, GPIO_PULL_UP);
}
if(pdat->pclk >= 0) {
if(pdat->pclkcfg >= 0) {
gpio_set_cfg(pdat->pclk, pdat->pclkcfg);
}
gpio_set_pull(pdat->pclk, GPIO_PULL_UP);
}
if (pdat->database) {
for (int i = 0; i < pdat->databits; i++) {
if(pdat->datacfg >= 0) {
gpio_set_cfg(pdat->database + i, pdat->datacfg);
}
gpio_set_pull(pdat->database + i, GPIO_PULL_UP);
}
}
// input format: YUV422
// input sequence: UYVY
// output format: planar YUV 422
// vref polarity: high
// href polarity: low
// pclk polarity: high
write32(pdat->virt + CSI_CFG,
(3 << 20) | (0 << 16) | (2 << 8) // YUV
| (1 << 2) | (1 << 1) | (0 << 0));
write32(pdat->virt + CSI_EN, read32(pdat->virt + CSI_EN) | (1 << 0));
if(!register_csi(&dev, csi))
{
clk_disable(pdat->clk);
free(pdat->clk);
clk_disable(pdat->dramclk);
free(pdat->dramclk);
free_device_name(csi->name);
free(csi->priv);
free(csi);
return NULL;
}
dev->driver = drv;
return dev;
}
static void csi_f1c100s_remove(struct device_t * dev)
{
struct csi_t * csi = (struct csi_t *)dev->priv;
struct csi_f1c100s_pdata_t * pdat = (struct csi_f1c100s_pdata_t *)csi->priv;
if(csi && unregister_csi(csi))
{
clk_disable(pdat->clk);
free(pdat->clk);
clk_disable(pdat->dramclk);
free(pdat->dramclk);
free_device_name(csi->name);
free(csi->priv);
free(csi);
}
}
static void csi_f1c100s_suspend(struct device_t * dev)
{
}
static void csi_f1c100s_resume(struct device_t * dev)
{
}
static struct driver_t csi_f1c100s = {
.name = "csi-f1c100s",
.probe = csi_f1c100s_probe,
.remove = csi_f1c100s_remove,
.suspend = csi_f1c100s_suspend,
.resume = csi_f1c100s_resume,
};
static __init void csi_f1c100s_driver_init(void)
{
register_driver(&csi_f1c100s);
}
static __exit void csi_f1c100s_driver_exit(void)
{
unregister_driver(&csi_f1c100s);
}
driver_initcall(csi_f1c100s_driver_init);
driver_exitcall(csi_f1c100s_driver_exit);
以下为SCCB部分,SCCB只是I2C的特例,所以就在I2C驱动里加了2个函数:
int i2c_sccb_write(const struct i2c_device_t * dev, uint8_t reg, uint8_t data)
{
uint8_t msg_data[2] = { reg, data };
struct i2c_msg_t msg[1];
msg[0].addr = dev->addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = msg_data;
int ret = i2c_transfer(dev->i2c, msg, 1);
return (ret == 1) ? 1 : 0;
}
int i2c_sccb_read(const struct i2c_device_t * dev, uint8_t reg, uint8_t* out)
{
uint8_t msg_data[2] = { reg, 0xee };
struct i2c_msg_t msg[2];
// write address
msg[0].addr = dev->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = msg_data;
// read back
msg[1].addr = dev->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = msg_data + 1;
int ret = i2c_transfer(dev->i2c, msg, 2);
if (ret == 2) {
*out = msg_data[1];
return 1;
} else {
return 0;
}
}
自制CPU,硬核玩家!这是homebrew俱乐部精神的延续。
Zodiac 说:https://www.sunrom.com/p/m62429-digital-pot
可以参考下应用电路和代码。两线控制,用起来蛮方便。看起来确实很简洁
几毛钱的功放用两块多钱的数字电位器调音量,总觉得哪里不对:D
为什么我照这个步骤编译出来的sunxi-fel.exe有785K,晕哥的版本只有100K。
而且我的运行后无任何输出,连帮助都不打印。
在Ubuntu 18.10上编译的。
有朋友说楼上的文件不能识别f1c100s,
那再重新来一次下载源码,编译.
……直接用命令行指令编译
i686-w64-mingw32-gcc -std=c99 -Wall -Wextra -Wno-unused-result -D_POSIX_C_SOURCE=200112L -D_BSD_SOURCE -D_DEFAULT_SOURCE -Iinclude/ -DNO_MMAP -I/usr/i686-w64-mingw32/include/libusb-1.0/ -o sunxi-fel.exe fel.c thunk.c progress.c soc_info.c fel_lib.c fel-spiflash.c -lws2_32 -L/usr/i686-w64-mingw32/lib/ -lusb-1.0 -lws2_32 -lwsock32 -lz
Windows平台可执行文件下载: sunxi-tools-win32support_f1c100s_flash_2.7z (已经验证,可以烧录f1c100s 唱戏机与licheepi nano开发板)
Windows 驱动安装方法在1楼.
用 https://certbot.eff.org/ 提供的工具,自动配置多个域名,加一条crontab就自动续期。
请教,每个定时器的四个PWM通道是固定IO口的,如果我想使任意IO口输出PWM波,能不能用定时器的PWM功能但不产生输出,而是我自己设置IO口?
想了个用DMA的方法也许可行,不占用CPU时间。
用定时器驱动DMA,再利用GPIO BSRR寄存器,交替设置IO口高低电平。
比如要用定时器控制PF3做PWM,首先
uint32_t src[2] = { GPIO_Pin_3, GPIO_Pin_3<<16 };
DMA目标设为src,DMA目标是&GPIOF->BSRR。循环DMA模式。
用定时器的PWM或update等驱动DMA运行,这样DMA向BSRR循环写入两个值,从而让PF3循环切换高低。
荔枝派nano,480x272的RGB屏,用@达克罗德 的裸奔framebuffer代码 https://whycan.cn/t_1457.html。
在屏幕上画纯色,发现在某些特定颜色的时候,屏幕上显示的颜色里混杂了若干小黑点。
经过实验,当R/G/B中的任意一个为125~127,61~63之间的值时,显示的颜色会有黑点。猜测在31、15……附近也会有,但看不清楚了。
这是为什么?
下图对比了 RGB(0,124,0) 和 RGB(0,125,0)的情况。
串口在哪个pin看datasheet https://whycan.cn/files/members/3/F1C100s_Datasheet_V1_0.pdf。
你用的哪个固件?一般都会在串口输出点内容吧,去看代码里串口用的是哪个。
一般用uart0,不确定的话,把uart0的几种mapping都试一下就知道了:D。
按晕哥的提示,参考了 彩虹派V3s开发板原理图 和 A10 LCD 调试手册 这两份资料,画了块RGB屏转 i80屏的转接板。等打样回来慢慢调~
(图上丝印1脚和常用的线序是反的)
谢谢,明白了!做个板子试一下。
按照彩虹派的接法,SoC引脚到MCU屏的接法如下:
LCD_CLK -- 33Ω电阻 -- WR
LCD_DE -- RS
LCD_HSYNC -- RD
@Quotation 你好!我之前是用KEYADC,但是由于精度不够,现在想用音频ADC,想请教两个问题。
1、请问下音频ADC的参考电压怎么设置?我一直没找到那个寄存器,还是说参考电压是直接给某个引脚呢。
2、音频ADC输入检测是分正负的吧,相当于我做ADC检测的时候要把电压分压为正常的一半吗?
1. 参见这篇 https://whycan.cn/t_1727.html ,由AVCC电压决定的。
2. 需不需要分压看输入电压范围,范围也由AVCC决定。
BGA的S3可以: https://whycan.cn/t_2227.html
不错,S3很强悍啊。唯一的障碍是BGA封装,还没画过更没焊过BGA呢……
F1C100s,XBoot下调用block和spi-flash驱动操作SPI Flash,能读,不能擦除不能写。
断断续续调了几天终于调通了,问题出在Flash CS时序。XBoot spi-flash驱动里的CS时序不满足我用的Flash的要求。
我用的Puya普冉的Flash(很便宜),数据手册里特意说了,某些操作必须在字节边界处置CS为高,这是出于数据保护的目的,检查命令长度,如果长度错了就不执行命令。
For the following instructions: WREN, WRDI, WRSR, PE, SE, BE32K, BE, CE, PP, DPP, QPP, DP, ERSCUR, PRSCUR, SUSPEND, RESUME, RSTEN, RST, the CS# must go high exactly at the byte boundary; otherwise, the instruction will be rejected and not executed.
也就是说SPI在发送完这几个命令和数据后,需要马上拉高CS。但XBoot里的Flash驱动是按照别的Flash写的,时序不满足这个要求。因此在spi-flash.c加入一些spi_device_select()和spi_device_deselect(),就能够擦除写入了!代码改动如下。
static void spi_flash_sector_erase(struct spi_device_t * dev, u64_t addr)
{
u8_t tx[4];
spi_device_select(dev);
spi_flash_write_enable(dev);
spi_device_deselect(dev);
tx[0] = OPCODE_SE;
tx[1] = (u8_t)(addr >> 16);
tx[2] = (u8_t)(addr >> 8);
tx[3] = (u8_t)(addr >> 0);
spi_device_select(dev);
spi_device_write_then_read(dev, tx, 4, 0, 0);
spi_device_deselect(dev);
spi_device_select(dev);
spi_flash_wait_for_busy(dev);
spi_device_deselect(dev);
}
static void spi_flash_sector_erase_4k(struct spi_device_t * dev, u64_t addr)
{
u8_t tx[4];
spi_device_select(dev);
spi_flash_write_enable(dev);
spi_device_deselect(dev);
tx[0] = OPCODE_BE_4K;
tx[1] = (u8_t)(addr >> 16);
tx[2] = (u8_t)(addr >> 8);
tx[3] = (u8_t)(addr >> 0);
spi_device_select(dev);
spi_device_write_then_read(dev, tx, 4, 0, 0);
spi_device_deselect(dev);
spi_device_select(dev);
spi_flash_wait_for_busy(dev);
spi_device_deselect(dev);
}
static void spi_flash_write_one_page(struct spi_device_t * dev, u64_t addr, u8_t * buf)
{
u8_t tx[4];
spi_device_select(dev);
spi_flash_write_enable(dev);
spi_device_deselect(dev);
tx[0] = OPCODE_PP;
tx[1] = (u8_t)(addr >> 16);
tx[2] = (u8_t)(addr >> 8);
tx[3] = (u8_t)(addr >> 0);
spi_device_select(dev);
spi_device_write_then_read(dev, tx, 4, 0, 0);
spi_device_write_then_read(dev, buf, 256, 0, 0);
spi_device_deselect(dev);
spi_device_select(dev);
spi_flash_wait_for_busy(dev);
spi_device_deselect(dev);
}
按理说,可以用SPI硬件自动控制CS。sunxi-tools里就是这样做的,没有操作CS的代码,也能正确写入。但我还没调出来,目前仍然用软件控制CS。
大佬能分享下你裁剪后的工程么?
这是我的工程,删了一些不用的文件和第三方库。如果你需要,再对照原始的工程添加回来。
编译出来的bin文件133kB。
xboot-size-opt.zip
阶段性成果:目前能够在正常运行状态切换到FEL了,可以读写内存什么的。但是还无法通过sunxi-fel操作SPI Flash。
// xboot代码里把IRQ MASK全置1了,至少需要把USB-OTG(bit 26)置0才能进FEL。
write32(0x01C20400 + 0x30, 0);
// Control Register bit 13,使用高地址向量表
arm32_write_p15_c1(arm32_read_p15_c1() | (1 << 13));
// 跳转到FEL
asm("BX %0" : : "r"(0xFFFF0020));
从这里 http://sunxi.org/FEL 的信息来看,似乎跳转到0xFFFF0020执行就是进FEL模式了,简单得难以置信。明天到公司试试。
在调试USB CDC中发现一处小bug。
CDC的规范要求,如果一次发送的数据长度是packet length的整数倍,则最后还需要发一个空的packet,表示结束。
涉及到2个文件的代码修改,如下:
int usb_cdc_send_data(unsigned char *buf,int len)
{
int ret = usb_device_write_data(1, buf, len);
if (ret != 0) {
return ret;
}
// 长度整除packet_length时多发一个空packet,CDC协议要求的
if (len % DATA_PACKET_SIZE == 0) {
ret = usb_device_write_data(1, buf, 0);
}
return ret;
}
int usb_device_write_data(int ep,unsigned char * databuf,int len)
{
int length = len;
int write_len = 0;
volatile int Timeout = 10000000;
int pack_len = ep_max_len[ep];
int data_pos = 0;
if(usb_connect)
{
usbprint("usb_device_write_data len:%d",len);
void * fifo = USBC_SelectFIFO(ep);
USBC_SelectActiveEp(ep);
do {
while((USBC_Dev_IsWriteDataReady(USBC_EP_TYPE_TX))&&(--Timeout)); //等待清除写标志
write_len = length > pack_len ? pack_len : length;
USBC_WritePacket(fifo, write_len, databuf + data_pos);
USBC_Dev_WriteDataStatus(USBC_EP_TYPE_TX,1);
data_pos += write_len;
length -= write_len;
if(Timeout == 0)
{
usbprint("usb_device_write_data Time out!");
return -1;
}
Timeout = 10000000;
} while (length > 0); // do..while 允许发0长度的packet
return 0;
}
else
{
usbprint("usb_device_write_data faild! usb not connect!!");
return -1;
}
}
楼主修复8字节不能传的问题了吗
我自己改的能用了。调用USBC_ConfigFifo时还有些要注意的,关键修改在usb_dev.c的这段。
每次调用USBC_ConfigFifo,传入的offset/size应当是不同的。FIFO所占内存不能重叠,所以这一次调用的offset参数应当是上一次的offset+size。
static u32 fifo_offset = 0; // global
void usb_config_ep_in(int epidx,int maxpack,int type)
{
u32 old_ep_idx = 0;
if(epidx)
{
usbprint("config int ep %d:%d , %d",epidx,maxpack,type);
/* Save index */
old_ep_idx = USBC_GetActiveEp();
USBC_SelectActiveEp(epidx);
u32 fifo_size = maxpack * 2; // double buffer
USBC_Dev_ConfigEp(type,USBC_EP_TYPE_TX,1,maxpack);
USBC_ConfigFifo(USBC_EP_TYPE_TX, 1, fifo_size, fifo_offset);
fifo_offset += fifo_size;
USBC_INT_EnableEp( USBC_EP_TYPE_TX, epidx);
USBC_SelectActiveEp(old_ep_idx);
ep_max_len[epidx] = maxpack;
}
}
void usb_config_ep_out(int epidx,int maxpack,int type)
{
u32 old_ep_idx = 0;
if(epidx)
{
usbprint("config out ep %d:%d , %d",epidx,maxpack,type);
/* Save index */
old_ep_idx = USBC_GetActiveEp();
USBC_SelectActiveEp(epidx);
u32 fifo_size = maxpack * 2; // double buffer
USBC_Dev_ConfigEp(type,USBC_EP_TYPE_RX,1,maxpack);
USBC_ConfigFifo(USBC_EP_TYPE_RX, 1, fifo_size, fifo_offset);
fifo_offset += fifo_size;
USBC_INT_EnableEp( USBC_EP_TYPE_RX, epidx);
USBC_SelectActiveEp(old_ep_idx);
ep_max_len[epidx] = maxpack;
}
}
之前用F1C100s的音频ADC(FMIN、LINEIN)拿来采集模拟电压,但发现音频ADC内部应该有滤波电路之类的,输入阻抗低,所以要求输入信号是高阻抗的才行。毕竟是为音频应用设计的,没打算给人当通用ADC用。
比如把电池电压用两个几十K的电阻分压,或把热敏电阻串一个几十K的电阻测温度,这种情况不能直接把分压端接到ADC上。ADC引脚内部的电路会对分压产生影响,而且是非线性的。更坑的是每片IC还不一致,同样条件下测量出来也会不一样。
所以,对于用ADC采集分压的使用情况,有两个方法:
一是加个便宜的运放做电压跟随,提高输入阻抗,测量的就很准了。
二是用KEYADC(LRADC)。LRADC内部是先经过运放,所以小信号直接输入没问题。缺点是精度只有6bit,量个电池电压勉强够用。
之前对ADC理解有误,现在修正了bug,更新一下代码。上面的代码作废。(话说本站不能编辑帖子吗?)
主要是对FIFO的理解:ADC向FIFO写入数据,写满后如果一直不读,新数据是被丢弃的,不会把旧数据挤走。所以要先清FIFO,等待下一帧数据到来再读取。
driver里加入adc-f1c100s.c:
/*
* driver/adc-f1c100s.c
*
* F1C100s ADC driver for XBOOT.
* Use Audio ADCs as generic ADC.
*
*/
#include <xboot.h>
#include <clk/clk.h>
#include <reset/reset.h>
#include <adc/adc.h>
enum
{
ADC_FIFOC = 0x10,
ADC_FIFOS = 0x14,
ADC_RXDATA = 0x18,
ADC_MIXER_CTRL = 0x24,
ADDA_TUNE = 0x28,
ADC_CNT = 0x44,
ADC_DG = 0x4c,
// ADC_DAP_CTR = 0x70,
// ADC_DAP_LCTR = 0x74,
// ADC_DAP_RCTR = 0x78,
// ADC_DAP_PARA = 0x7C,
// ADC_DAP_LAC = 0x80,
// ADC_DAP_LDAT = 0x84,
// ADC_DAP_RAC = 0x88,
// ADC_DAP_RDAT = 0x8C,
// ADC_DAP_HPFC = 0x90,
// ADC_DAP_LINAC = 0x94,
// ADC_DAP_RINAC = 0x98,
// ADC_DAP_ORT = 0x9c,
};
enum
{
ADC_CHANNEL_FMINL = 0,
ADC_CHANNEL_FMINR,
ADC_CHANNEL_LINL,
// ADC_CHANNEL_MICIN,
ADC_CHANNEL_COUNT
};
struct adc_f1c100s_pdata_t
{
virtual_addr_t virt;
char * clk;
char * sclk;
int reset;
};
static u32_t adc_f1c100s_read(struct adc_t *adc, int channel)
{
struct adc_f1c100s_pdata_t *pdat = (struct adc_f1c100s_pdata_t *)adc->priv;
// unmute channel.
virtual_addr_t ADDR_MIXER_CTRL = pdat->virt + ADC_MIXER_CTRL;
u32_t mixerReg = read32(ADDR_MIXER_CTRL) & ~(0x1f << 8);
switch (channel) {
case ADC_CHANNEL_FMINL:
write32(ADDR_MIXER_CTRL, mixerReg | (1 << 12));
break;
case ADC_CHANNEL_FMINR:
write32(ADDR_MIXER_CTRL, mixerReg | (1 << 11));
break;
case ADC_CHANNEL_LINL:
write32(ADDR_MIXER_CTRL, mixerReg | (1 << 10));
break;
default:
// mute all
break;
}
const uint32_t WAIT_STABLE = 400;
udelay(WAIT_STABLE); // wait for internal filter to be stable
// FIFO flush
write32(pdat->virt + ADC_FIFOC, read32(pdat->virt + ADC_FIFOC) | (1 << 0));
while (((read32(pdat->virt + ADC_FIFOS) >> 23) & 1) == 0) {}
// by default, 0V = -24000 and VRA = 0.
// make 0V = 0 and vreference = 65535.
int val = (int16_t)(read32(pdat->virt + ADC_RXDATA) >> 16);
val += 24000;
if (val < 0) {
val = 0;
}
return (u32_t)val;
}
static void adc_f1c100s_config(struct adc_t *adc)
{
struct adc_f1c100s_pdata_t * pdat = (struct adc_f1c100s_pdata_t*)adc->priv;
u32_t regVal =
(0u << 29) // 48kHz,higher for better sampling speed
| (1 << 28) // digital enable
| (0 << 24) // mode 0
| (0 << 17) // delay after enable
| (0 << 16) // delay function
| (0xf << 8) // default
| (1 << 7) // mono
| (0 << 6) // 16-bit
;
write32(pdat->virt + ADC_FIFOC, regVal);
regVal =
(1u << 31) // analog enable
| (3 << 24) // mic gain
| (0 << 21) // linein gain
| (3 << 16) // adc gain = 0
| (1 << 14) // COS slop time
| (0 << 8) // all mute
| (1 << 7) // PA speed fast
| (4 << 0) // default
;
write32(pdat->virt + ADC_MIXER_CTRL, regVal);
}
static struct device_t * adc_f1c100s_probe(struct driver_t * drv, struct dtnode_t * n)
{
struct adc_f1c100s_pdata_t * pdat;
struct adc_t * adc;
struct device_t * dev;
virtual_addr_t virt = phys_to_virt(dt_read_address(n));
char * clk = dt_read_string(n, "clock-name", NULL);
char * sclk = dt_read_string(n, "sclk-name", NULL);
pdat = malloc(sizeof(struct adc_f1c100s_pdata_t));
if(!pdat)
return FALSE;
adc = malloc(sizeof(struct adc_t));
if (!adc)
{
free(pdat);
return FALSE;
}
clk_enable(clk);
clk_enable(sclk);
pdat->virt = virt;
pdat->clk = strdup(clk);
pdat->sclk = strdup(sclk);
pdat->reset = dt_read_int(n, "reset", -1);
adc->name = alloc_device_name(dt_read_name(n), -1);
adc->vreference = dt_read_int(n, "vref", 2800000); // (AVCC * 1000000)
adc->resolution = 16;
adc->nchannel = ADC_CHANNEL_COUNT;
adc->read = adc_f1c100s_read;
adc->priv = pdat;
if(pdat->reset >= 0)
reset_deassert(pdat->reset);
adc_f1c100s_config(adc);
if (!register_adc(&dev, adc))
{
clk_disable(pdat->clk);
free(pdat->clk);
clk_disable(pdat->sclk);
free(pdat->sclk);
free_device_name(adc->name);
free(adc->priv);
free(adc);
return NULL;
}
dev->driver = drv;
return dev;
}
static void adc_f1c100s_remove(struct device_t * dev)
{
struct adc_t * adc = (struct adc_t *)dev->priv;
struct adc_f1c100s_pdata_t * pdat = (struct adc_f1c100s_pdata_t *)adc->priv;
if(adc && unregister_adc(adc))
{
clk_disable(pdat->clk);
free(pdat->clk);
clk_disable(pdat->sclk);
free(pdat->sclk);
free_device_name(adc->name);
free(adc->priv);
free(adc);
}
}
static void adc_f1c100s_suspend(struct device_t * dev)
{
}
static void adc_f1c100s_resume(struct device_t * dev)
{
}
static struct driver_t adc_f1c100s = {
.name = "adc-f1c100s",
.probe = adc_f1c100s_probe,
.remove = adc_f1c100s_remove,
.suspend = adc_f1c100s_suspend,
.resume = adc_f1c100s_resume,
};
static __init void adc_f1c100s_driver_init(void)
{
register_driver(&adc_f1c100s);
}
static __exit void adc_f1c100s_driver_exit(void)
{
unregister_driver(&adc_f1c100s);
}
driver_initcall(adc_f1c100s_driver_init);
driver_exitcall(adc_f1c100s_driver_exit);
设备树里加入:
"clk-gate@0x01c20068": {"parent": "apb1", "name": "gate-bus-audiocodec", "shift": 0, "invert": false },
"clk-gate@0x01c20140": {"parent": "pll-audio", "name": "gate-sclk-audiocodec", "shift": 31, "invert": false },
"clk-link": { "parent": "gate-bus-audiocodec", "name": "link-adc" },
"clk-link": { "parent": "gate-sclk-audiocodec", "name": "link-sclk-adc" },
"adc-f1c100s@0x01c23c00": {
"clock-name": "link-adc",
"sclk-name": "link-sclk-adc",
"reset": 64,
"vref": 2800000
},
https://github.com/torvalds/linux/blob/master/sound/soc/sunxi/sun8i-codec-analog.c
我看了一下代码,
没有找到不关功放的寄存器,
或许找的姿势不对.持续播放一段静音音频这个倒是可以有,
在应用程序端做就可以了。
貌似就是上边摘的那段代码,SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN,耳机功放使能。
把 else if (SND_SOC_DAPM_EVENT_OFF(event)) 这段 和上面一句 msleep(700) 注释掉试试?
static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
if (SND_SOC_DAPM_EVENT_ON(event)) {
snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL,
BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN),
BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN));
/*
* Need a delay to have the amplifier up. 700ms seems the best
* compromise between the time to let the amplifier up and the
* time not to feel this delay while playing a sound.
*/
msleep(700);
} else if (SND_SOC_DAPM_EVENT_OFF(event)) {
snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL,
BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN),
0x0);
}
return 0;
}
dbskcnc 说:晕哥 说:不知道是不是因为要省电,所以播放完之后关闭声卡???需要的时候再打开???
如果能一直开着,不知道能不能解决这个问题, 我们的产品是市电供电,不需要节电.能不能验证下这个问题,如果每次播放都要延时700ms, 真的很大问题, 按理不应该这样子才对
验证过了, 如果每次播放间隔时间超过3秒(目测), 那么每次都是延迟 700ms.
上面是 linux4.13的结果,
不知道用bsp linux(3.4) 会不会这样.
芯片内部功放启动的时候会相当于把音量“慢慢”调大,避免电平突然改变造成的咔嗒声。所以这个现象应该是驱动层把功放关闭了,再次启用的时候比较慢。
可以改驱动,一直不关闭功放。
或者,在播放完的时候,持续播放一段静音音频。
F1C100s没有通用的ADC,所以用音频ADC撸了个通用ADC的驱动。
支持3路channel,分别对应FMINL,FMINR,LINL引脚。只支持单次采样,不是用来录制音频的。产品里需要电量检测什么的可以用。
下载代码:adc-f1c100s.c,添加到mach-f1c100s/driver;
再在设备树添加:
"clk-gate@0x01c20068": {"parent": "apb1", "name": "gate-bus-audiocodec", "shift": 0, "invert": false },
"clk-setting@0": {"clocks": [{"name": "gate-bus-audiocodec", "enable": true}]},
"clk-gate@0x01c20140": {"parent": "pll-audio", "name": "gate-sclk-audiocodec", "shift": 31, "invert": false },
"clk-link": { "parent": "gate-sclk-audiocodec", "name": "link-audiocodec" },
"adc-f1c100s@0x01c23c00": {
"clock-name": "link-audiocodec",
"reset": 64
},
手册上只说了VRA1/VRA2引脚是模拟参考电压,没有详细解释。实际测试如下:
荔枝派Nano,AVCC=3.0V,VRA=1.1V;
我的板子,AVCC=2.81V,VRA=1.03V;
1.1/3.0≈1.03/2.81,比例基本一致。
LINEIN、FMINL、FMINR这几个模拟输入经过mixer,由ADC采集。
实测,模拟输入允许的电压范围为0~VRA*2,输出值以VRA为中心,正负约24000。
所以,VRA是这么算出来的:令AVCC映射到65535,则24000对应的电压值为VRA=AVCC*24000/65535。代入检验,符合实际测试值。
厉害厉害,能不能分享一下?
开新帖分享了裁剪方法,把XBOOT优化到<170kB了。https://whycan.cn/t_1708.html
原文发表于个人Blog http://quotation.github.io/development/2018/10/04/xboot-tailoring.html
硬件平台:全志F1C100s
系统:XBOOT
工具链:gcc-arm-none-eabi
XBOOT默认的配置带了很多功能,编译后的尺寸颇大,影响启动时间。因为XBOOT启动过程的大部分时间是花在从Flash拷贝代码到RAM,所以只要能把编译结果的尺寸降下来,就可以成比例地加快启动速度。
在对XBOOT做裁剪优化后,编译后的尺寸从默认的4MB降至<170kB(不带图形系统和Lua,带文件系统),启动速度完全满足要求。
未优化前,默认编译结果xboot.bin尺寸为4MB。
以下的裁剪措施分若干个步骤,可根据自己的需要的功能,只采取部分步骤。
优化步骤:
1. 去除不用的资源文件,尺寸减到3.3MB。XBOOT带了字体、图片、示例Lua脚本等资源文件,如果完全用不着图形界面,直接删掉。(如果应用中确实需要用到大量资源,参见本文最后给出的优化方法。)
步骤:
* 删除src/romdisk目录;
* 修改src/Makefile,去除“$(CP) romdisk .obj”这行,但是保留arch/下的romdisk。
2. 去除Lua框架,尺寸减到3MB。快速开发点简单的东西用Lua比较方便,由于我们的产品是用C/C++开发,所以Lua完全用不着。
步骤:
* 修改src/Makefile,去除所有lua、framework相关的行;
* 编译一下,根据编译错误,删除代码里相关的引用。
3. 去除图形库,尺寸减到403kB。图形图像相关的库比较多,可根据需要裁剪。很多应用场景不需要矢量图形、矢量字体,只需要libpng再加上位图字库就够用。
步骤:
* 修改src/Makefile,去除zlib、libpng、pixman、cairo、freetype、chipmunk相关的行;
* 编译一下,根据编译错误删除代码里引用到图形库的地方(比如do_showlogo)。
4. 去除shell、command功能,尺寸减到371kB。开发、调试时可以用用shell,正式产品不需要保留。
步骤:
* 修改src/Makefile,去除shell、command;
* 删除src/arch/arm32/lib/cpu/cmd-*.c,或移到另外的目录里;
* 删除掉main()中的do_showlogo、do_autoboot、run_shell调用,改为直接调用实际的应用代码入口;
* 编译一下,根据编译错误删除代码里几处引用到system()的地方。
5. 去除不用的驱动,尺寸减到308kB。根据自己的应用需求,删除不必要的驱动。
步骤:
* 修改src/Makefile,去除用不到的driver/xxx,我这里只保留了block、时钟、console、dma、gpio、i2c、interrupt、pwm、reset、spi、uart、watchdog等常用外设驱动;
* 删除mach-f1c100s/driver里不用的驱动,或移到单独的目录里。
6. 去除不用的库,尺寸减到277kB。
步骤:
* 修改src/Makefile,去除libc/charset、libc/crypto、fs/xfs(视自己的需要删除);
* 删除代码里引用到的地方。
7. 链接时去除未用到的符号,尺寸减到170kB。XBOOT自带的libc和libm函数都通过EXPORT_SYMBOL宏加到了.ksymtab.text段,所以即使在代码中没有调用到,链接时也无法优化掉,白白占用空间。这个设计本意是为了编译动态链接库,不过在当前的XBOOT中并没有使用。
步骤:
* 修改module.h,把EXPORT_SYMBOL宏定义为空,即:#define EXPORT_SYMBOL(symbol);
* 修改mach-f1c100s/xboot.mk,给LDFLAGS增加“-Wl,--gc-sections”,给MCFLAGS增加“-ffunction-sections -fdata-sections”,让链接器删除未用到的符号。
8. 去除文件系统。鉴于这个改动涉及很多零散的点,且大多数应用都会需要文件系统,所以不建议这样改。如果实在有必要,可以把所有的fs和block驱动去掉,尺寸减到131kB。
以上为功能裁剪和链接优化方法。
如果程序中需要用到大量资源,资源文件默认是放在romdisk中,会增加启动时间。一个可采取的优化措施是,把非必要的资源单独放在另一个romdisk中,在启动后异步加载。(还未实际验证过)
先分析XBOOT的代码,和romdisk相关的有如下几处:
* src/Makefile最后,sinclude这句,把源码的某个目录拷贝到.obj中,用cpio命令把目录打包成单个文件;
* driver/block/romdisk/data.S和mach-f1c100s/xboot.ld,把打包后的romdisk文件链接到单独的“.romdisk”段;
* 启动时,sys_copyself中把__image_start和__image_end两个符号之间的内容拷贝到RAM,如上所说.romdisk段也在此范围内;
* subsys_init_romdisk中,用__romdisk_start和__romdisk_end符号之间的内存创建romdisk,并在subsys_init_rootfs中挂载到“/”。
因此,优化方法是,将非必须(启动不需要)的资源文件打包到另一个romdisk,挂载到单独的目录下使用。大致做法如下:
* 启动过程中必须的资源仍然放在默认romdisk中;
* 启动非必须的资源单独放到一个源码目录,通过Makefile cpio打包成单个文件;
* xboot.ld中添加“.dataromdisk”段,参照XBOOT的做法,用一个.S文件把上一步的打包文件链接到.dataromdisk段;该段位于.data段之后,因此启动的时候不会被sys_copyself拷贝;
* 系统启动之后,参照sys_copyself的方法,把.dataromdisk段拷到RAM;
* 按照subsys_init_romdisk和subsys_init_rootfs的做法,初始化另一个romdisk,挂载到“/data”。
应该是这个型号: XTSD01GLGEAG
目前不知道稳定性如何,节后拿几个样品试一试。
他们业务员说大量用在故事机,佛经机.
看起来不错。我们有个产品用TF卡,一直想找能直接SMT的TF卡替代品。SPI Flash不够大,这个最大到1GByte,很好了。
页次: 1