@lyl20050926 用公司账户在全志客户服务平台上按流程注册就可以获取相关资料了

livpo 发布的帖子
-
回复: 请问这是因为权限不够没办法拉SDK吗
@evileny 1. 执行命令设置全局保存密码
git config --global credential.helper store
- 执行命令输入密码
git clone https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git
- 使用repo拉取sdk
repo init -u https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git -b master -m tina-v853-open.xml
由于repo更新,目前不支持通过repo输入密码,请先使用git命令输入保存密码
-
回复: 搭建开发环境,出现问了了,总是报fatal: cannot obtain manifest https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git
@aaa0557li 在 搭建开发环境,出现问了了,总是报fatal: cannot obtain manifest https://sdk.aw-ol.com/git_repo/V853Tina_Open/manifest.git 中说:
git config --global user.email "you@example.com"
git config --global user.name "Your Name" -
回复: TinaLinux 无法创建 swap 分区吗?
可以的,
【FAQ】全志V853芯片 swap功能简介与tina上swap分区使用方法
https://bbs.aw-ol.com/topic/1626/share/1 -
百问网Avaota A1 T527开发板
全志科技T527采用多核异构设计,集成了CPU、GPU、NPU、DSP、MCU等各种计算单元,为各种复杂场景的多任务处理、专用数据处理以及算法应用提供了高效且灵活的解决方案,为生成式AI、人工智能算法的场景化落地提供了坚实的硬件基础。
为了方便开发者进行产品预研和项目开发,百问网推出了搭载全志T527芯片的AvaotaA1开发板。AvaotaA1是一款硬件+软件全开源的卡片电脑,提供全面开放的软硬件资料,小巧的板型搭载了8核处理器并引出了丰富的外部资源,还支持Avaota OS、Android13、Armbian等多种操作系统。
硬件资源
AvaotaA1开发板采用98mm x 73mm的接口丰富易用SBC板型设计,可以很好的兼容目前市面上的绝大多数主流开发板,在足够的板型尺寸下,开发板板载了500+元器件、14个外设接口以及40个拓展GPIO口。
- CPU: 八核 Cortex-A55,最高可达 1.8GHz
- GPU: Mali G57,支持 OpenGL ES 3.2/2.0/1.1,Vulkan 1.1/1.2/1.3,OpenCL 2.2
- RCPU: XuanTie E906 RISC-V @ 200MHz
- DSP: HIFI4 DSP @ 600MHz
- NPU: 可选最高可达 2TOPS 的计算能力
- 内存芯片: 支持 LPDDR4,可选 1/2/4GB
- 存储芯片: 支持 eMMC 5.1,可选 16/32/64/128GB
- 相机: MIPI CSI 接口,支持 1300 万像素,4 路 MIPI CSI
- 网络通信接口: 2 x 千兆以太网,WIFI6 2.4G + 5G,BT5.4
- USB 接口: 包括 USB OTG,USB3.0 OTG,USB2.0 HOST
- 音频接口: 3.5mm 音频接口,具有录音功能
- 显示接口: 包括 RGB 接口,MIPI DSI 接口,DP 接口,HDMI2.0 接口,LVDS 接口,MIPI DBI 接口
- 其他接口模块: 包括调试串口,TF 卡槽,CAN 接口,RGB LED,红外(IR),UART,IIC,I2S,SPI,PWM
在显示接口方面,AvaotaA1板载了一块240x135@60fps的1.14寸IPS屏,可用于显示LVGL UI、显示输出、显示系统性能等基础功能实现。
结合板载的HDMI、DP、MIPI/RGB/LVDS显示接口,AvaotaA1能够轻松实现4K60以及4K+1080P双屏异显的功能场景,结合NPU以及全志自研的新一代画质引擎,可支持AI超分等画质增强算法,提供更佳的显示体验。
软件系统支持
AvaotaA1支持专为Avaota SBC系列所打造的Avaota OS,Avaota OS是基于Ubuntu的特殊定制版,借助于Avaota SBC提供的性能和功能,Avaota OS在Ubuntu的基础上进行了深度优化,为用户提供了一体化的操作系统体验。
Avaota OS不仅继承了Ubuntu的强大功能和稳定性,还增加了许多独特的特色功能。这些功能包括针对Avaota硬件的优化,如支持特定的传感器和接口,以及预先配置的软件包,以满足各种应用场景的需求。并通过对系统内核、资源管理和功耗控制的调整,以提供优异的性能和稳定性。
除了百问网支持的专属Avaota OS,AvaotaA1也支持Android13、Buildroot、Armbian、Openwrt等多种适用于不同应用领域的操作系统,并附上了详尽的移植教程。
开发板购买&资料获取
目前R128 Devkit全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含2+16和4+32两种裸板配置的绿色AvaotaA1 SBC,并可自行选购Type-C USB调试器:
- 2GB+16GB裸板:249元
- 4GB+32GB裸板:349元
- Type-C USB调试器:49.9元
另外关于AvaotaA1开发板的硬件设计、数据手册、软件系统源码等所有资料都在Avaota官方网站上开源,并配置了详尽的开发文档供开发者学习,感兴趣的小伙伴可以复制以下链接或者点击【阅读原文】获取更多详细资料。
AvaotaA1开发板文档:
https://docs.avaota.fun/avaota-sbc/ -
全志T527芯片详解【四】:工业级品质
工业级温度范围
T527芯片工作环境温度范围为-40℃~85℃。出色的温度适应性和宽广的工作温度范围,让T527可以适用于恶劣的户外或工业生产环境。
无论是在冰天雪地的北极圈,还是在骄阳似火的沙漠地带,T527都能保持其性能的稳定性和可靠性,为需要在极端气候条件下运行的电力设备、工业自动化系统以及其他户外应用提供了理想的解决方案。
HF-FCBGA封装
T527采用HF-FCBGA (Heat Spreader Flip Chip Ball Grid Array) 封装格式。在结合了倒装芯片 (Flip Chip) 技术和球栅格阵列 (Ball Grid Array) 的基础上,芯片封装增加了大面积的散热片 (Heat Spreader) ,以提高热管理性能,更适合工业级场景使用,T527芯片封装规格如下:
- HS-FCBGA 664balls
- 17mm X17 mm body size
- Maximum 1.844mm height
- 0.5mm ball pitch
- 0.3mm ball size
全流程工业级把控
全志科技凭借多年的全流程芯片设计经验,从市场调研、规格定义、功能设计、仿真验证、版图设计、软件适配、方案开发等多个阶段,都对工业场景的功能需求进行了细致考究,通过技术手段实现T527芯片的工业级产品规格。
完善的测试流程
全志科技为T527芯片配备了完善的测试环节:
- 高低温测试
- HTOL测试
- HTSL测试
- 功能测试
- 压力测试
- 掉电测试
- ESD测试
- 功耗测试
- 稳定性测试
- ......
可靠度合作伙伴
全志科技作为Fabless模式的芯片设计公司,与上下游产业链拥有深厚实力的供应商、代工厂等合作伙伴长期紧密合作,共同攻克先进产品中的技术难题,为全志芯具有更适合工业场景的稳定性和可靠性提供坚实制造基础。
*本文所述内容为T527系列部分芯片规格,T527系列旗下有多个子型号,各子型号封装、功能、规格等略有差异,详情以规格书为准
-
全志T527芯片详解【三】:丰富接口
工业控制接口
T527集成了大量适用于工业场景的功能接口,包括PCle接口、CAN总线接口、UART接口和PWM接口等。
PCle是智慧工业领域广泛应用的接口,可满足数据高速传输的需求,亦可用于外接大算力NPU/GPU进行算力扩展、外接高速存储介质以及外接4G/5G蜂窝网模组等。
CAN总线接口共2路、分属主核心ARM CPU域和辅核域下。CAN接口支持强抗干扰能力下的多主节点操作,使得多个设备可以高效地在同一网络中通信,确保系统的协调性和同步性。
PWM接口共30路,分属主核和辅核4组不同的PWM控制器。PWM接口能够精确控制信号占空比,实现对电机、温度等设备的精确控制,提高设备的运行效率和稳定性。
UART接口共10路,支持多种波特率和数据格式,确保通信的准确性和稳定性,可以满足实时数据采集、设备配置和远程控制等场景需求。
此外还有24路GPADC、2路LRADC、9路TWI、4路SPI...
PCle2.1
- Compliance to PCI Express Rase Specification, Revision 2.1
- Supports Gen1(2.5 Gbit/s), Gen2 (5.0 Gbit/s) spee
- Supports 62.5MHz/125MHz operation on PIPE interface for Gen1/Gen2, respectively
- Constant 32-bit PIPE width for Gen1/Gen2 modes
CAN *2
- Maximum transmission rate up to 1Mbps
- Supports CAN 2.0 A/B Protocol
- Supports 32-bit APB 2.0 Protoco
- Receiver buffer 256 bytes
- Supports one-shot transmission
- Supports two configurable filter modes
- Supports operating solely as a receive station mode
- Supports self-test mode
PWM *30
- Maximum 16 independent PWM channels for PWM controller
- Maximum 8 complementary pairs output for PWM controlle
- Maximum 4 group of PWM channel output for controlling stepping motors
- Maximum 16 channels capture input
UART *10
- Compatible with industry-standard 16450/16550 UARTs
- Supports TX/RX DMA slave controller interface
- Supports software/hardware flow control
GPADC *24
LRADC *2
TWI *9
SPI *4
......高速传输接口
T527提供了多种网络传输方案,通过内置接口和外接网络模块的组合,可以实现双1000M以太网、2.4G/5G WiFi、蓝牙、4G/5G蜂窝网等多种网络连接方式,满足在边缘计算、物联网 (loT) 和智能设备等对网络通信的需求。
显示输出接口
T527集成了多种常用的屏幕接口,配合内置在T527芯片内部的GPU和自研的新一代高清画质算法矩阵,可以实现多种不同屏幕接口的多屏异显组合,满足各种高质量、多屏幕产品的功能要求。如:4K@60fps HDMI高清视频输出、4K+1080P双屏异显等。
MIPI DSI
- (4+4)-lane MIPI-DSI, up to 2.5K@60fps and 4K@45fps
LVDS*2
- 2x LVDS with dual link
- up to 1080p@60fps
HDMI
- HDMI2.0b, up to 4K@60fps
eDP
- eDP1.3
- up to 2.5K@60fps and 4K@30fps
RGB*2
- 2x RGB with DE/SYNC mode
- up to 1080p@60fps
视觉输入接口
T527处理器在视觉输入方面提供了强大的支持,集成了多种接口以支持多路输入,结合自研的ISP和高清画质算法,完成摄像头输入预处理、图像增强等任务,可以提供更高效、高质量的视觉处理功能。通过交织输入,可以同时接入多达6路摄像头的数据,满足全景摄像、360行车安全等多摄像头场景的功能需求。
MIPI CSI
- 8M@30fps RAW12 2F-WDR, size up to 3264(H) x 2448(V)
- 24lane / 42lane / 4+2*2lane MIPI Interfac
- Crop function
- Frame-rate decreasing via software
- 6 DMA controllers for 6 video stream storage
Parallel CSI
- 16-bit digital camera interface
- Supports 8/10/12/16-bit width
- Supports BT.656, BT.601, BT.1120 interface
- Dual Data Rate (DDR) sample mode with pixel clock up to 148.5MHz
- Supports ITU-R BT.656 up to 4*720P@30fps
- Supports ITU-R BT.1120 up to 4*1080P@30fps
高清音频接口
在音频方面,T527同样集成了多种接口,包括2路DAC、3路ADC、4路I2S/PCM接口、8路数字麦克风接口及1组OWA接口等。既可以实现高品质的音乐播放,又能满足多达8点位各种形状的麦克风阵列语音输入的需求,并且在采样率和信噪比方面有着出色的表现。这些接口不仅支持高质量的音频传输,还提供了灵活性和扩展性,配合内置HiFi4 DSP,可以满足语音识别、智能音箱、智能座舱等场景的需求。
DAC*2
- 16-bit and 20-bit sample resolution
- 8 kHz to 192 kHz DAC sample rate
- 100 ± 2 dB SNR@A-weight, -85 ± 3 dB THD+N
ADC*3
- 16-bit and 20-bit sample resolution
- 8 kHz to 48 kHz ADC sample rate
- 95 ± 3 dB SNR@A-weight, -80 ± 3 dB THD+N
I2S/PCM*4
- Compliant with standard Philips Inter-IC sound (I2S) bus specification
- Left-justified, Right-justified, PCM mode, and Time Division Multiplexing (TDM) format
- Programmable PCM frame width: 1 BCLK width (short frame) and 2 BCLKs width (long frame)
- FIFOs for transmitting and receiving data
- Supports multiple function clocks
- Supports TX/RX DMA slave interface
- Supports multiple application scenarios
- Supports master/slave mode
DMIC
- Supports maximum 8 digital PDM microphones
- Supports sample rate from 8 kHz to 48 kHz
OWA
- One OWA TX and One OWA RX
- Compliance with S/PDIF interface
- IEC-60958 and IEC-61937 transmitter and receiver functionality
- IEC-60958 supports data formats: 16 bits, 20 bits, and 24 bits
存储接口
为了满足不同客户产品对存储元器件的选型需求,T527可以适配多种内存型号规格,包括:
- DDR3
- DDR3L
- LPDDR3
- DDR4
- LPDDR4
- LPDDR4x
- SD 3.0
- eMMC 5.1
- SPI Flash
- ......
其他通用接口
- USB 3.1 DRD
- USB 2.0 DRD
- USB 2.0 HOST
- SDIO3.0
- LEDC
- CIR TX
- CIR RX
- .........
*T527系列下有多款子型号,不同子型号间功能规格略有差异,同时上文只展示了部分规格及其部分性能,详情请以官方提供的正式规格书为准。
-
全志T527芯片详解【二】:高清图像编解码
硬件模块加持
T527集成了多个图形显示和编解码相关的硬件模块,为高清图像显示、高清视频播放和多路高清摄像头输入提供了强大的硬件基础:
- ARM Mail-G57 GPU
- 自研显示引擎(Display Engine)
- 去隔行处理单元(De-interIace)
- 2D图像加速单元(Graphic2D)
- 视频编解码引擎(Video Engine)
- 自研视觉处理器(ISP)
- 视觉缩放处理单元(VIPP)
- ......
在硬件加速模块加持下,T527可以轻松实现4K视频的录制和播放,并支持H.264\H.265\VP9等多种主流的编解码格式,同时支持多路编解码的场景。
Video decoder
- H.265 MP decoder up to 4K@60fps
- H.264 BL/MP/HP decoder up to 4K@30fps
- VP9 decoder up to 4K@60fps
- Multi-format 1080p@60fps video playback,including VP8,MPEG1/2SP/MP,MPEG4,SP/ASP,AVS+/AVS JIZHUN
Video encoder
- H.264 encoder up to 4K@25fps
- MJPEG encoder up to 4K@15fps
- JPEG encoder up to 8K x 8K resolution
丰富的图像输入输出接口
T527上集成了大量图像输入、输出接口,通过与编解码模块的配合,可以实现4K+1080P的双屏幕异显和实现多达6路摄像头图像的交织输入,为如:收银机、智能座舱、360°摄像头等需要多屏幕交互或多摄像头输出的场景提供了高效的解决方案。
屏幕接口:MIPI DSI 、LVDS、HDMI、eDP、RGB.......
- HDMI2.0b up to 4K@60fps
- 4+4-lane MIPI-DSI output,supporting up to 2.5K@60fps and 4K@45fps
- 2xLVDS interface with dual link, up to I080p@60fps
- 2xRGB interfaces with DE/SYNC mode, up to I080p@60fps
- eDP1.3 up to 2.5K@60fps and 4K@30fps
摄像头接口:MIPI CSI、Parallel CSI
Parallel CSl interface- 8/10/12/16-bit width
- Supports BT.656 up to 4720P@30fps and BT.1120 up to 41080P@30fps
MIPI CSI interface
- 24 lane/42 lane/4+2*2 lane MIPICSI,flexible combination, up to 2.0 Gbit/s per lane in HS transmission,compliant with MIPI-CSI2V1.1 and MIPI DPHYV1.1
- Maximumvideocaptureresolution of 8M@30fps
自研高清画质算法矩阵
全志在显示引擎上硬件固化了自研的高清画质算法矩阵AWonder1.0,提供了多维度的图像处理算法,包括:
- 智能降噪
- 明亮动态
- 清晰细节
- 绚丽色彩
- 精锐去隔行
- 臻彩HDR
- AI-PQ
- ......
帮助图像减少噪点、提升对比度、减少锯齿、丰富色彩等,并通过AI场景和人像识别,令图像更清晰、色彩更自然。
自研安防级专业图像信号处理器
T527集成了全志自研的安防级专业图像信号处理器 (ISP),该ISP在前序版本上大幅升级多个模块,能更好地应对如行车、户外等运动和光照复杂的场景。
- 时空域多级降噪
- 宽动态合成
- 局部色调映射算法
- ......
同时全志自研推出AI-ISP智能图像处理算法「全志智影」,通过ISP和NPU深度配合,提升了图像的感光度,帮助实现黑光全彩等功能,在车载场景下可以实现全天候无盲区的行车安全监控。
相比传统ISP算法,「全志智影」AI-ISP能够更好地提升暗部清晰度及颜色表现,改善画面整体亮度及色度噪点表现,并大程度地抑制运动拖影的出现。
自研图像和视觉参数调节工具
T527配套了全志自研量产工具
- 屏幕画质调试工具:TigerLCD
- 视觉图像调试工具:TigerISP
通过可视化的软件工具,帮助工程师更便捷地对图像参数进行调试,减少工程师工作量,提升开发效率,让图像色彩显示更精准。
-
全志T527芯片详解【一】:计算性能
高效架构 性能稳健
内置8*ARM Cortex-A55,8核主频可运行至1.8GHz,提供更稳健强劲的处理能力
- Octa-core ARM Cortex-A55 in a DynamlQ big.LITTLE configuration, up to 1.8 GHz
- 32KB L1 I-cache and 32KB L1 D-cache per A55 core
- Optional 64KB L2 cache per“LITTLE”core
- Optional 128KB L2 cache per“big”core
图形显示 清晰流畅
集成ARM Mail-G57 GPU图形显示能力更上一层楼
- ARM G57 MC1 GPU
- Supports OpenGL ES 3.2/2.0/1.1, Vulkan 1.1/1.2/1.3, and OpenCL2.2
- Anti-aliasing algorithm
- High memory bandwidth and low power consumption in 3D graphics processing
边缘计算 AI赋能
集成2Tops NPU为端侧语音、自然语言处理、图像处理及画质增强等AI应用提供性能支持
- 2 TOPS NPU
- Embedded 512KB internal buffer
- Supports deep learning frameworks:TensorFlow, Pytorch, Caffe, Onnx NN, TFLite.......
声音算法 浑然天成
内置HiFi4 DSP,频率可达600MHz,广泛应用于图像、音频及数字信号处理的专用领域为 影音娱乐、工业生产提供专属算力
- HiFi4 Audio DSP
- 32KB I-cache +32KB D-cache
独立MCU 控制更实时
内置RISC-V架构MCU,主频可达200MHz独立运行RTOS系统,为工业及机器人系统上的实时处理、高速响应及工业级的稳定运行提供重要保障
- RISC-V CPU, up to 200 MHz
- 16 KB I-cache and 16 KB D-cache·RV32IMAFC instructions
更多全志T527芯片详解系列专题内容即将发布
联系我们
微信公众号: Allwinnertech
官方邮箱:service@allwinnertech.com -
实测52.4MB/s!全志T3+FPGA的CSI通信案例分享!
CSI总线介绍与优势
CSI(CMOS sensor parallel interfaces)总线是一种用于连接图像传感器和处理器的并行通信接口,应用于工业自动化、能源电力、智慧医疗等领域,CSI总线接口示意图如下所示(以全志科技T3处理器的CSI0为例)。
高带宽:CSI总线支持高速数据传输,可以满足多通道高速AD数据的传输需求。
开发难度低:CSI总线采用并行数据和控制信号分离方式,时序简单,FPGA端接口开发难度低。
低成本:CSI总线采用并行传输方式,FPGA端使用资源少,对FPGA器件资源要求低。
国产ARM + FPGA架构介绍与优势
近年来,随着中国新基建、中国制造2025规划的持续推进,单ARM处理器越来越难胜任工业现场的功能要求,特别是如今能源电力、工业控制、智慧医疗等行业,往往更需要ARM + FPGA架构的处理器平台来实现例如多路/高速AD采集、多路网口、多路串口、多路/高速并行DI/DO、高速数据并行处理等特定功能,因此ARM + FPGA架构处理器平台愈发受市场欢迎。
创龙科技SOM-TLT3F是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计的异构多核全国产工业核心板,ARM Cortex-A7处理单元主频高达 1.2GHz。核心板ARM、FPGA、ROM、RAM、电源、晶振、连接器等所有元器件均采用国产工业级方案,国产化率100%。
全志T3为准车规级芯片,四核ARM Cortex-A7架构,主频高达1.2GHz,支持双路网口、八路UART、SATA大容量存储接口,同时支持4路显示、GPU以及1080P H.264视频硬件编解码。另外,创龙科技已在T3平台适配国产嵌入式系统翼辉SylixOS,真正实现软硬件国产化。
国产ARM + FPGA的CSI通信案例介绍
本章节主要介绍全志科技T3与紫光同创Logos基于CSI的ARM + FPGA通信方案,使用的硬件平台为:创龙科技TLT3F-EVM工业评估板。
该案例实现T3(ARM Cortex-A7)与FPGA的CSI通信功能。案例使用的CSI0总线,最高支持分辨率为1080P@30fps,数据位宽为8bit,如下图所示。CSI0理论传输带宽为:1920 x 1080 x 8bit x 30fps ≈ 59MB/s。
功能框图与程序流程图,如下图所示。
ARM端案例csi_test案例说明
ARM端案例csi_test主要功能如下:
(1)基于Linux子系统V4L2;
(2)通过CSI总线,采集指定帧数数据;
(3)计算总耗时;
(4)打印平均采集速率,并校验最后一帧图像的数据。FPGA端案例parallel_csi_tx案例说明
FPGA端案例parallel_csi_tx主要功能如下:
(1)将测试数据(0x00~0xFF)写入FIFO;
(2)从FIFO读出数据,按行与帧的方式、1024x512的分辨率,通过CSI总线发送至ARM端。
案例测试演示
FPGA程序将CSI_PCLK设置为65MHz,测试数据写入FIFO的时钟FIFO_WR_CLK设置为59MHz。由于FPGA端需将数据写入FIFO再从FIFO读出后发送,每一行与每一帧之间的间隔时间会受FIFO写入的速率影响,因此CSI通信的实际理论传输带宽应为:(59MHz x 8bit / 8)MB/s = 59MB/s。从上图可知,本次实测传输速率约为52.4MB/s,误码率为0,接近理论通信速率。
-
实测14us,Linux-RT实时性能及开发案例分享—基于全志T507-H国产平台
本文带来的是基于全志T507-H(硬件平台:创龙科技TLT507-EVM评估板),Linux-RT内核的硬件GPIO输入和输出实时性测试及应用开发案例的分享。本次演示的开发环境如下:
- Windows开发环境:Windows 7 64bit、Windows 10 64bit
- Linux开发环境:Ubuntu18.04.4 64bit
- 虚拟机:VMware16.2.5
- U-Boot:U-Boot 2018
- Kernel:Linux-RT-4.9.170
- SDK:LinuxSDK-V2.0
- GPIO: LED(PI13)、KEY3(PH4)
- 分享案例:rt_gpio_ctrl、rt_input案例
- 测试工具:示波器
测试数据汇总
基于全志T507-H(硬件平台:创龙科技TLT507-EVM评估板),按照创龙科技提供的案例用户手册进行操作,得出如下测试结果。
备注:测试数据与实际测试环境有关,仅供参考。
测试结果如下表所示:
(1)GPIO输入延时:通过使用示波器测量按键事件触发LED电平翻转的实际耗时结合系统延时与GPIO输出延时得出数据;
(2)系统延迟:根据Linux-RT性能测试平均值得出数据;
(3)GPIO输出延时:通过使用示波器测量LED电平翻转的实际耗时得出数据。
根据不隔离CPU核心、隔离CPU核心三种状态的测试结果可知:当程序指定至隔离的CPU3核心上运行时,Linux系统延迟最低,可有效提高系统实时性。故推荐对实时性要求较高的程序(功能)指定至T507-H隔离的CPU核心运行。
Linux-RT实时性测试
本次测试是使用Cyclictest延迟检测工具测试Linux系统实时性。Cyclictest是rt-tests测试套件下的测试工具,也是rt-tests下使用最广泛的测试工具,一般主要用来测试内核的延迟,从而判断内核的实时性。Cyclictest主要通过反复测量并精确统计线程的实际唤醒时间,以提供有关系统的延迟信息。它可测量由硬件、固件和操作系统引起的实时系统的延迟。
使用Cyclictest测试系统实时性
基于全志T507-H(硬件平台:创龙科技TLT507-EVM评估板),按照创龙科技提供的案例用户手册进行操作,使用Cyclictest程序测试系统实时性,得出如下测试结果。
对比测试数据,可看到基于Linux-RT-4.9.170内核的系统的延时更加稳定,最大延时更低,系统实时性更佳。
T507-H核心板典型应用场景
Linux-RT应用案例的分享
rt_gpio_ctrl案例
通过创建一个基本的实时线程,在线程内触发LED的电平翻转,同时程序统计实时线程的调度延时,并通过示波器测出LED电平两次翻转的时间间隔。由于程序默认以最高优先级运行,为避免CPU资源被程序完全占用,导致系统被挂起,因此在程序中增加100us的延时。程序原理大致如下:
(1)在Linux-RT内核上创建、使用实时线程。
(2)实时线程中,计算出触发LED电平翻转的系统调度延时。
将可执行文件拷贝至评估板文件系统,并执行如下命令运行测试程序,再按"Ctrl + C"退出测试,串口终端将打印程序统计的延时数据,如下图所示。
Target# ./rt_gpio_ctrl 100
同时使用示波器捕捉LED两次电平翻转之间的间隔就对应上线程调度的延迟。算出电平两次翻转的时间间隔为∆x = 114us,如下图所示。由于程序中默认增加了100us的时间延时。因此,实际延时应为:114us-100us = 14us,与程序统计打印的Latency results平均值相近。
rt_input案例
通过创建一个基本的实时线程,在线程内打开input设备,并对按键事件进行监听,然后触发LED的电平翻转,再通过示波器测量按键触发到LED电平翻转期间的实际耗时。程序原理大致如下:
(1)在Linux-RT内核上创建、使用实时线程。
(2)实时线程中对打开的input设备节点进行按键事件监听,通过判断监听得到的按键事件来触发LED的电平翻转。
将可执行文件拷贝至评估板文件系统,并执行如下命令运行测试程序,程序运行后按下KEY3用户按键点亮LED,松开按键后LED熄灭,再按"Ctrl + C"退出测试程序。
Target#./rt_input /dev/input/event8
分别使用示波器探头1测量按键KEY3引脚1,使用示波器探头2测量LED。
从按键下降沿触发的开始(下图黄线)到LED上升沿触发的完成(下图蓝线)的时间间隔,即为系统实时捕获按键输入时间并响应触发LED电平翻转的时间∆x,从图中可看到∆x = 76us。
-
又进化了!T113智能家居86盒圆屏版(圆屏加一体化驱动板+CNC外壳+炫酷LVGL UI)
PCB产品发车QQ群号:787148555 ——繁花T113板(太极派)交流群
PCB产品发车QQ群号:787148555 ——繁花T113板(太极派)交流群
PCB产品发车QQ群号:787148555 ——繁花T113板(太极派)交流群
文末有详细PCB发车介绍
流畅到要设置帧率限制的神之眼/符玄个性时钟
巨丝滑的高清高帧率视频播放
如上,你所见到的这款圆滚滚的小玩意,就是魔改了一款在前几期的文章中我们所介绍过的,一款方形的T113-S3智能家居86盒,原项目因其验证过的原理图和PCB(√),可初始化的屏幕(√),详尽的SDK和保姆级的部署教程(√),从而受到了不少开发者的关注,其中也包括一些爱整活的小伙伴。
于是萨纳兰的黄昏在86盒的原作者FanHuaCloud大佬加持下,又给86盒挖了个新坑,为了解决之前ESP32所驱动圆屏只能播放MJPEG并且帧率较低的尴尬问题,集圆屏加一体化驱动板+外壳+炫酷LVGL UI于一身的圆形86盒横空出世,并命名其为——T113太极派。
硬件设计简介
T113太极派可以简单理解为是方形86盒的圆形改版,两者在硬件的设计上大体相同,圆屏由于面积较小,元件排列比方形的困难许多,调整了很久的布局,只是调整了背光芯片以及其它部件的PCB布局走线。
01、全志T113-S3主控,双核A7,内存128M,支持硬件解码,和D1s Pin to Pin,支持相互替换
02、7701S 2.1寸SPI RGB接口圆屏,带触摸
03、RTL8723 WIFI模块,用于连接网络
04、128MB SPI Nand,支持从SPI Nand启动系统
05、双USB接口,其中一个支持Host/Device,另一个仅支持Host
06、板载麦克风和一个耳机口
07、支持Typec口接DAC小尾巴输出音频目前的版本是插电使用的超薄版本 ,后续可能会增加带扩展版的支持电池的充电版本,其最主要的特点是作者为这款圆形的太极派专门给它建模做了一个极其轻薄的CNC外壳,触摸屏直径是71.8mm,外壳直径为74mm。树脂版外壳厚度为13mm,CNC版外壳厚度为10mm。
软件系统及UI界面适配
APP是设备的灵魂,肯定不能只跑个lvgl demo看看那些图形蹦来蹦去最后得个数字。前面做的这一切,都是为了将之前的代码移植过来。本APP使用LVGL 8.3.11编写。目前有以下功能(方屏版和圆屏板都可用,相同的功能在不同形状的屏幕上会有不同的表现形式)
AIDA64无线副屏
原理很简单,电脑端开启AIDA64,新建一个Remote Panel,将指定的设备信息设置为指定格式后放上去,本设备在联网后就会定时获取信息,并动态展示。目前该界面还是为方屏设计的,圆屏还没有特别好的创意,等有了再改。
音乐频谱节拍器
同样针对方屏和圆屏做了两套不同的UI,不但UI不同,而且频谱样式也不同,方屏是条形频谱,圆形是放射形渐变频谱。频谱的实现是由全志的Tplayer的音频回调函数将PCM信息推送给对应的FFT进程,然后FFT计算后推送给页面展示来实现的。另:全志的tplayer有bug,如果设置单曲循环,且播放的音乐为flac格式,在音乐播放完,跳转会开头的时候会出错。为解决这个问题,APP在遇到flac音乐单曲循环的时候,会重新初始化该音乐。
又是频谱,没错我就是频谱星人。拾音频谱做了专属样式,实现是通过alsa接口读取麦克风PCM数据,然后同样推送给FFT进程。圆形版本的拾音频谱叠加了视频背景(视频背景由b站up主渣渣一块钱4个制作)。
动态天气时钟
以之前HTC手机的那个动态天气动画作为背景的时钟,根据不同的天气加载不同的动态背景,使用了高德天气API。
神之眼/符玄个性时钟
移植了之前做的一些个性主题时钟,比如随机星座时钟等,比较二次元,结合全志硬解视频功能,支持mp4视频播放作为动态背景 有兴趣的可以看我相关的视频展示。效果见文首视频连接
视频/相册播放器
针对方屏和圆屏做了两套不同的UI。除了按钮功能外,在屏幕右侧区域上下滑是调节音量,左侧区域上下滑调节亮度,中间区域左右滑调节进度。总之,将全志t113的硬件解码功能全部解锁出来,并配上lvgl播放器UI~
SDK系统
修掉了很多问题,直接编译可用,后续提供一键编译的虚拟机(不含视频演示ui相关),可以很好解决初学者反复踩坑环境搭建问题。
SDK中已经集成了我的APP所用到的一切库,如
- fftw3:FFT库
- Libjpeg:改为了最新的版本
- Taglib:读取音乐文件里面的图像
开源资料获取
本文所有内有内容均转载自原作者本人的B站视频账号及立创开源硬件平台的工程页面,文章内所提到的圆形86盒硬件设计不开源,可以参考方形的86盒PCB设计,可以直接使用的SDK已开源在帖子内,感兴趣的小伙伴可以复制下方链接或者戳文末的“阅读原文”获取。
- 作者萨纳兰的黄昏个人网站:https://pressf5.run/
- B站视频介绍:https://www.bilibili.com/video/BV1ip421m77K
- 方形PCB参考及开源SDK获取:https://oshwhub.com/planevina/t11386-box-revision
关于发车:QQ群号:787148555
目前圆屏pcb添加了一个接喇叭的功放电路,正在打样验证,预计下周初即验证完,然后就会准备smt事项。具体一个板子多少钱会根据到时候贴的数量来看,多就会便宜点少就会贵点。
- 程序:买了板子的免费激活视频中展示的程序。
- 外壳:样品已用fdm打印验证过,树脂版的已下单三维猴验证,同样下周可验证完。会在群内直接提供stl文件供自己打印(也有可能统一下单三维猴树脂打印)。我个人非常喜欢cnc壳,看到时候有没有人一起凑一凑去加工一些。
- sdk:可使用繁花在立创上开源的那个,后面也会再整理一套
-
我DIY了一个可以声控刷抖音的神器,这下连手指都解放了
用一块板两个模块三根线就可以爆改手机,实现语音控制刷抖音,上下滑动视频、点赞那都是基础操作,关键是还可以一键睡觉,懒人福音!
所谓一块板两个模块三根线,一块开发板就是负责接收和控制的香橙派H616开发板,两个模块分别是负责发送的语音识别模块SU-03T和一个mic,所以除了电源外,只需要将语音识别模块的TX(B7)接到香橙派的RX就可以,算上电源共三根线:
项目需求只是简单的通过语音指令来控制安卓手机刷抖音,以实现视频切换和点赞等功能:
1、开机播报“你好,我是你的刷抖音助手”
2、当说出“你好抖音助手"可以唤醒模块,模块回复“抖音助手在”
3、当超过10s没有指令或说出“退下”时,模块会进入休眠模式,并回复“有需要再叫我”
4、当说出“下一个视频”或“这个不好看”时,模块回复“切换至下一个视频”,并划到下一个视频
5、当说出“上一个视频”或“刚刚那个挺好看”时,模块回复“切换至上一个视频”,并划回上一个视频
6、当说出“点个赞”或“这个视频不错”时,模块回复“已为您点赞”,并点赞当前视频
7、当说出“不想看了”时,模块回复“已为您关闭屏幕”,并关闭手机屏幕,一键入睡!语音识别模块配置
项目中所需要的是一个非特定语音识别,也就是不识别特定人类的声音的模块。所以SU-03T这个模块的操作相较于其他语音识别模块更简单,不需要编程或二次开发,只需要通过厂家给的网站配置后即可使用。
创建产品
设置PIN脚为串口模式
对于语音识别模块,串口的RX和TX分别对应B6和B7,并设置相应的波特率
设置唤醒词
设置指令语句
设置控制详情
参数的设置就是行为的名字,大写字母 ,16进制ASCII码,已空格分开
- next -> 4E 45 58 54
- pre -> 50 52 45
- zan -> 5A 41 4E
- guan -> 47 55 41 4E
测试
在完成以上步骤,并下载SDK并烧写进入语音识别模块后,可以打开串口助手来测试一下,分别说出对应的指令,看看语音识别模块是否会向串口发送对应的字符。
分别说出了四条指令,可见串口输出正确!
编写香橙派代码框架
语音识别模块设置完成后,就可以将语音识别模块接到香橙派并进行Linux部分的代码编写,连接方式如下图所示:
新创建一个“douyin”文件夹,将语音刷抖音项目的代码放在这里面:
然后将一些代码拷贝进来方便修改:
重新编译一下,并说出4条控制语句:可见,语音识别模块成功识别了指令,香橙派成功的接收了语音识别模块通过串口打印的字符!
现在,就需要修改serial_douyin.c中接收的代码,添加数据处理的部分,先简单写一个数据处理的框架
void *write_serial(void *arg) { char readbuf[32] = {'\0'}; while(1){ while(serialDataAvail (*((int *)arg))){ serialGetstring (*((int *)arg),readbuf) ; if(strcmp(readbuf,"NEXT") == 0 ){ printf("收到下一条视频指令\n"); }else if(strcmp(readbuf,"PRE") == 0){ printf("收到上一条视频指令\n"); }else if(strcmp(readbuf,"ZAN") == 0){ printf("收到点赞指令\n"); }else if(strcmp(readbuf,"GUAN") == 0){ printf("收到关闭指令\n"); }else{ printf("未知指令\n"); } memset(readbuf,'\0',32); } } pthread_exit(NULL); }
重新编译一下,并说出4条控制语句:
可见,函数框架正确,接下来只需要将printf替换成真正的操作手机的代码就可以了。
将手机接入香橙派
将我破旧的小米5C再次拿出哈哈哈,然后通过 TYPE-C -- USB 连接到香橙派:
香橙派输入dmesg指令查看手机接入情况,可见已经成功识别
由于安卓手机的底层也是用Linux系统来操作的,所以可以通过香橙派来直接进入控制手机shell的界面,但需要先安装adb工具,adb是做安卓开发中常用的工具
sudo apt-get insall adb
安装完之后,执行”adb devices“指令,发现好像权限不太对,因此需要在安卓手机上设置权限
报错的本质原因是香橙派系统还不支持USB设备的热拔插和UDEV的机制。
什么是UDEV
udev是一个设备管理工具,udev以守护进程的形式运行,通过侦听内核发出来的uevent来管理/dev目录下的设备文件。
udev在用户空间运行,而不在内核空间 运行。它能够根据系统中的硬 件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。
解决办法:在 /etc/udev/rules.d 文件夹下创建规则文件:
cd /etc/udev/rules.d/ sudo vim 51-android.rules
然后在文件中添加内容:
cd /etc/udev/rules.d/ sudo vim 51-android.rules
然后重新拔插手机,再次执行”adb devices“指令
没有任何报错了,此时”adb shell“指令会显示连接成功,此时如果“ls”一下,可以看到很多文件没有权限,因为没有root,不过不重要,到这里手机端的配置就好了
现在可以成功的连入手机内部的系统,关键就在于对于滑动或点击屏幕的指令模拟了,
- adb shell input swipe <起始x坐标> <起始y坐标> <结束x坐标> <结束y坐标> <滑动持续时间ms>
- adb shell input keyevent <按键事件的常量>
adb shell input swipe 540 1300 540 500 500 //下滑 adb shell input swipe 540 500 540 1300 500 //上滑 adb shell "seq 3 | while read i;do input tap 350 1050 & input tap 350 1050 &sleep 0.01;done;" //点赞 adb shell input keyevent 26 //锁屏
最终实现效果
现在有了基本的代码模型,和控制手机的具体指令,接下来的工作就是在数据处理的部分,执行adb指令了,显然,使用system函数就可以:
int main () { int fd ; int ret; pthread_t read_thread; pthread_t write_thread; if ((fd = myserialOpen ("/dev/ttyS5", 115200)) < 0) //打开驱动文件,配置波特率 { fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ; return 1 ; } /* if (wiringPiSetup () == -1) { fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ; return 1 ; }*/ ret = pthread_create(&read_thread,NULL,read_serial,(void *)&fd); if(ret != 0){ printf("read_serial create error\n"); return 1; } ret = pthread_create(&write_thread,NULL,write_serial,(void *)&fd); if(ret != 0){ printf("write_serial create error\n"); return 1; } pthread_join(read_thread,NULL); pthread_join(write_thread,NULL); return 0 ; }
和项目需求一致,可见,我的手并没有碰到手机屏幕,只是说出了对应的指令,手机就会有所反应:
并且在香橙派终端也可以看到指令历史:
-
如何在飞凌嵌入式T113-i开发板的Buildroot中移植MQTT协议?
在实际的项目开发中,工程师朋友们可能会需要在文件系统中移植一些工具或协议,那么该如何进行移植操作呢?
我们可以通过添加package包配置的方式在OK113i-S开发板的Buildroot中移植新功能。本篇文章,小编就以在Buildroot移植MQTT协议为例为大家介绍。
配置文件介绍
首先了解一下在Buildroot中移植功能时涉及到的配置文件。
查看飞凌嵌入式OK113i-S开发板源码中已有的配置文件,可以看到在路径
buildroot/buildroot-201902/package/mosquitto
里边包括以下几个文件:- Config.in
- mosquitto.mk
- mosquitto.hash
- mosquitto.service
- S50mosquitto
Config.in
Config.in文件通过
BR2_PACKAGE_**
作为开关来告知Buildroot需要哪个包参与编译,开关在buildroot/buildroot-201902/configs/
下面的OK113I_linux_defconfig
配置文件中赋值,类似于内核中的Kconfig文件。例如:
package/Config.in
中写了调用关系source "package/mosquitto/Config.in";
package/mosquitto/Config.in
中写了BR2_PACKAGE_MOSQUITTO
信息。demo.mk
这个文件中声明一些包的信息,比如:指定包的版本、包源码下载链接、存放路径、编译规则、工具链等。编译时会按照这个文件中的下载地址和版本下载源码包到指定路径并进行编译和文件拷贝,相当于Makefile文件。
例如:
mosquitto.mk
文件开头先写了软件包版本和下载地址,我们在浏览器访问该地址可以找到对应版本的软件包。编译时,如果源码中没有该文件,就会自动下载。
- mosguitto-1.5.8.tar.gz
- mosguitto-1.5.8.tar.gz.asc
除此之外文件中还定义了其他的编译规则,包括文件拷贝路径等内容。
demo.hash
这个文件会记录下载的源码包的hash校验码,防止下载的源码包出错。
demo.service
此文件是为systemd服务 ,systemd开机后会依据此文件启动demo服务,在demo.mk中会指定此文件的源路径已经安装路径。目前OK113i-S开发板没有使用该服务,因此可以不用管它。
S50demo
此文件类是demo.service,是目前OK113i-S开发板在使用的开机服务类型。
在以上5种文件中
Config.in
和demo.mk
是必须的,其他文件按需配置即可。具体配置内容可参考已有文件或根据实际情况进行书写。Mosquitto
已经有写好的配置文件,可以直接用,一般配置文件由项目的维护者或开发者提供,如果自己移植的文件没有配置文件,可以参考已有配置文件写一个。执行
我们需要在buildroot/buildroot-201902中执行
make OK113I_linux_defconfig
然后执行
make menuconfig ARCH=arm
在图形配置界面进行配置(如果执行报错,请先安装该指令:sudo apt-get update 和 sudo apt-get install ncurses)。
进入图形配置界面后输入“/”搜索要配置的功能,如图搜索Mosquitto看到的信息,按提示选“1”可进入目标选项,按“空格”选择后保存并退出。
配置完成后,在当前目录下执行 ./build.sh对文件系统进行编译,编译完成后可查看文件系统中是否已经有对应文件。(注:如果没有网络,则编译时不能自动下载源码包,需要到下载地址手动下载源码包并放到源码包存放路径中。)
MQTT的测试验证
修改OK113i-S开发板的
/etc/mosquitto/mosquitto.conf
文件,在#user mosquitto
后加一行user root
,重启服务或者开发板。也可以杀掉进程并重新执行:/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
然后进行测试——
订阅test主题:
mosquitto_sub -t test &
发布test主题:
mosquitto_pub -t test -m "hello world"
能看到返回的 hello world 字样,就说明移植成功了。
以上就是在飞凌嵌入式OK113i-S开发板的Buildroot中移植MQTT协议的方法,供屏幕前的工程师小伙伴参考。
-
在核桃派上实现USB摄像头的OpenCV颜色检测
在给核桃派开发板用OpenCV读取图像并显示到pyqt5的窗口上并加入颜色检测功能,尝试将图像中所有蓝色的东西都用一个框标记出来。
颜色检测核心api
按照惯例,先要介绍一下opencv中常用的hsv像素格式。颜色还是那个颜色,只是描述颜色用的参数变了。h代表色调,s代表饱和度,v代表明度,比使用rgb格式更方便计算与思考。
opencv中也提供了将rgb bgr等转为hsv图片的api:
hsvImage = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
cv2.inRange,给定一个要检测的hsv颜色范围,返回一张黑白图。将hsv值在该范围内的像素点全部变为白色,不在的则为黑色。
import numpy as np hsv_upper=np.array([125, 250, 250]) hsv_lower=np.array([95, 40, 40]) grayImage = cv2.inRange(hsvImage, hsv_lower, hsv_upper) # 颜色二值化
findContours,传入黑白图像,寻找所有轮廓。返回两个列表,contours里是找到的所有轮廓,hierarchy是那些轮廓之间的相对位置关系
contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
minAreaRect,传入一个轮廓,计算最小外接矩形
# 画最小外接矩形 for cts in contours : rect = cv2.minAreaRect(cts)
drawContours, 绘制轮廓
box = np.int0(cv2.boxPoints(rect)) cv2.drawContours(rgbImage, [box], 0, (255, 0, 0), 2)
基本测试代码
import cv2 from ui_main import Ui_MainWindow import numpy as np import PyQt5 from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * # 修正qt的plugin路径,因为某些程序(cv2)会将其改到其他路径 import os os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.dirname(PyQt5.__file__) #【可选代码】允许Thonny远程运行 import os os.environ["DISPLAY"] = ":0.0" #【建议代码】允许终端通过ctrl+c中断窗口,方便调试 import signal signal.signal(signal.SIGINT, signal.SIG_DFL) timer = QTimer() timer.start(100) # You may change this if you wish. timer.timeout.connect(lambda: None) # Let the interpreter run each 100 ms # 线程类 class Work(QThread): signal_update_label = pyqtSignal(QPixmap) label:QLabel def sloat_update_label( self, pixmap:QPixmap): self.label.setPixmap(pixmap) def run(self): print("label.width()=", self.label.width()) print("label.height()=", self.label.height()) self.signal_update_label.connect(self.sloat_update_label) cap = cv2.VideoCapture(1) while True: ret, frame = cap.read() if ret: # 颜色转换 rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) hsvImage = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 二值化 hsv_upper=np.array([125, 250, 250]) hsv_lower=np.array([95, 40, 40]) grayImage = cv2.inRange(hsvImage, hsv_lower, hsv_upper) # 颜色二值化 # 查找并绘制最小外接矩形 contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) for cts in contours : rect = cv2.minAreaRect(cts) box = np.int0(cv2.boxPoints(rect)) cv2.drawContours(rgbImage, [box], 0, (255, 0, 0), 2)
由于摄像头拍出来的噪点很多,而物体由于本身材质反光导致拍出来也有一些部分的颜色变了。所以实际应用时需要对图像进行一些滤波模糊化处理。或是直接对生成后的黑白图像进行一定膨胀与收缩。
再把各个参数做成pyqt窗口的选项,查看各项搭配后的效果,快速找到合适的参数选择。
# 图像缩小并转换颜色格式 frame = cv2.resize(frame, (320, 240)) rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgbImage.shape # 图像模糊 if self.blur.flag : rgbImage = cv2.blur(rgbImage,(self.blur.num, self.blur.num)) if self.median.flag : rgbImage = cv2.medianBlur(rgbImage,self.median.num) if self.gaussian.flag : rgbImage = cv2.GaussianBlur(rgbImage, (self.gaussian.num, self.gaussian.num), 0) # 二值化 hsvImage = cv2.cvtColor(rgbImage, cv2.COLOR_RGB2HSV) grayImage = cv2.inRange(hsvImage, np.array([self.hl.num, self.sl.num, self.vl.num]), np.array([self.hu.num, self.su.num, self.vu.num])) # 颜色二值化 # 图像操作 if self.dilate.flag : grayImage = cv2.dilate(grayImage, np.ones((self.dilate.num, self.dilate.num), dtype=np.uint8), 1) # 膨胀 if self.erode.flag : grayImage = cv2.erode(grayImage, np.ones((self.erode.num, self.erode.num), dtype=np.uint8), 1) # 腐蚀 # 获取中心点的颜色,画上十字光标 height, width = rgbImage.shape[:2] center_y, center_x = height // 2, width // 2 color = tuple(map(int, rgbImage[center_y, center_x, :])) cv2.line(rgbImage, (center_x, 0), (center_x, height-1), color, 3) cv2.line(rgbImage, (0, center_y), (width-1, center_y), color, 3) contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
-
技术帖 | 飞凌嵌入式T113-i开发板的休眠及唤醒操作
飞凌嵌入式OK113i-S开发板支持两种休眠方式:freeze和mem。
这两种方式可以通过/sys/power/state文件节点进行操作,用户可以通过在该文件节点写入freeze或mem来触发相应的休眠状态。
在进行休眠之前,系统会配置唤醒源。一旦系统进入休眠状态,可以通过这些唤醒源(如按键、RTC等)在需要时唤醒系统。这种设计允许用户根据需要选择何时以及通过何种方式快速唤醒系统,实现了功耗最小化和快速恢复的平衡。这一机制使得系统在休眠状态下能够极大地减少功耗,同时保留了用户在唤醒后迅速使用系统的便利性。
本篇内容小编会为大家介绍如何让飞凌嵌入式OK113i-S开发板进入休眠模式,以及如何通过RTC时钟实现定时唤醒。
关于两种休眠模式
- freeze
冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它方式高。实测OK113i-S开发板在只接串口线的情况下5V供电,电流约为0.112A。
- mem
挂起到内存,计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。实测OK113i-S开发板在只接串口线情况下5V供电,电流约为0.076A。
1、cat /sys/power/state可以看到OK113i-S开发板支持的模式有哪些:
2、echo freeze > /sys/power/state 进入freeze模式:
3、echo mem > /sys/power/state 进入mem模式:
通过RTC定时唤醒
注意:此处需要使用内部RTC,外部RTC不支持唤醒功能,后面我们还会提及。
进入开发板的内核配置:
root@ubuntu: /home/forlinx/work/linux/OK113i-linux-sdk# ./build.sh menuconfig
根据下图框选进行功能选择:
配置完成后保存,然后修改设备树文件,打开内部RTC功能。
保存后进行编译:
编译成功后打包成镜像,烧写完成后,我们在串口终端进行测试。
进入串口终端进行测试:
echo “+15”> /sys/class/rtc/rtc0/wakealarm
此处为15秒定时,可自由设置时间,命令执行后就会生效,RTC会单独计时,如果是15秒后才进入休眠,不会触发唤醒。(注意此处需要使用内部RTC,外部RTC不支持唤醒功能)
echo mem > /sys/power/state
(这里两条指令输入时要紧凑,两条指令间,间隔太长就无效了)
(这里需要注意,我们在未打开内部RTC时,我们的外部RTC默认节点是rtc0,修改后外部rtc设备节点会变更成rtc1。)
到这里,我们就完成了在飞凌嵌入式OK113i-S开发板上实现休眠以及通过RTC定时唤醒的全部操作了,当然,不同的主控平台板卡的具体操作会有差异,但是整体思路是一样的,具体可以根据相对应的平台查看相关资料来确定具体步骤,希望本文提供的方法能够对屏幕前的工程师朋友们的项目开发有所帮助。
-
回复: R128 FreeRTOS编译提示找不到python
/usr/bin/python是指向python2或python3的软链接
有两种解决办法:
1、如果已经安装了python的软件包:
执行mrtos_memuconfig,配置CONFIG_DBUILD_PYTHON为对应的python路径,如/usr/bin/python3或/usr/bin/python2。
2、在ubuntu主机上安装下面两个软件包之一:
sudo apt install python-is-python2 # 安装后,/usr/bin/python软链接到/usr/bin/python2 sudo apt install python-is-python3 # 安装后,/usr/bin/python软链接到/usr/bin/python3
-
回复: T113i_miniEVM SSH链接异常
T113 登录SSH异常问题,修改
/etc/init.d/rcS
取消注释 #/etc/adb_conf.sh start & -
【R128】应用开发案例——DBI驱动ST7789V1.3寸LC
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
DBI驱动ST7789V1.3寸LCD
之前介绍了 R128 平台使用 SPI 驱动显示屏 ST7789V1.3寸 LCD,接下来介绍的是使用 DBI 接口驱动。
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了 SPILCD 驱动框架以供 SPI 屏幕使用。
此次适配的SPI屏为
ZJY130S0800TG01
,使用的是 DBI 进行驱动。DBI接口的全称是
Display Bus Serial Interface
,在显示屏数据手册中,一般会说这是SPI接口,所以有人会误认为SPI屏可以使用normal spi
去直接驱动。SPI 接口就是俗称的4线模式,这是因为发送数据时需要额外借助
DC
线来区分命令和数据,与sclk
,cs
和sda
共四线。DBI 分为多种接口,包括
0:L3I1 1:L3I2 2:L4I1 3:L4I2 4:D2LI
L3I1
和L3I2
是三线模式(不需要DC
脚),区别是读时序,也就是是否需要额外脚来读寄存器。读写时序图如下:- L3I1写时序
- L3I1读时序
L4I1
和L4I2
是四线模式,与spi接口协议一样,区别是DC脚的控制是否自动化控制,另外I2和I1的区别是读时序,也就是否需要额外脚来读取寄存器。- L4I写时序
- L4I读时序
D2LI
是两data lane模式。发送命令部分时序与读时序与L3I1
一致,下图是发送数据时的时序,不同像素格式时钟周期数量不一样。- D2LI写时序
可以知道,在3线模式时,发送命令前有1位A0用于指示当前发送的是数据,还是命令。而命令后面接着的数据就没有这个A0位了,代表 SPI 需要在 9 位和 8 位之间来回切换,而在读数据时,更是需要延时
dummy clock
才能读数据,normal spi
都很难,甚至无法实现。所以normal spi
只能模拟 4 线的 DBI 的写操作。读操作只能通过模拟IO来实现。对于R128这类支持 DBI 接口的CPU,可以选择不去了解 SPI。直接选用 DBI 来驱动屏幕。由于不需要模拟延时和切换数据,屏幕驱动效率将有明显提升。
引脚配置如下:
R128 Devkit TFT 模块 PA12 CS PA13 SCL PA18 SDA PA9 BLK PA20 RES PA19 DC 3V3 VCC GND GND 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 DBI 驱动
屏幕使用的是SPI驱动,所以需要勾选SPI驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPI Devices
Drivers Options ---> soc related device drivers ---> DBI Devices ---> -*- enable dbi driver
配置 SPI 引脚
DBI同样使用 SPI 控制器,所以需要配置SPI的相关配置。打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,在这里我们不需要用到SPI WP引脚,注释掉即可。SPI HOLD 需要作为 DC 脚接入LCD模块。;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;Please config spi in dts [spi1] spi1_used = 1 spi1_cs_number = 1 spi1_cs_bitmap = 1 spi1_cs0 = port:PA12<6><0><3><default> spi1_sclk = port:PA13<6><0><3><default> spi1_mosi = port:PA18<6><0><3><default> spi1_miso = port:PA21<6><0><3><default> spi1_hold = port:PA19<6><0><2><default> ;spi1_wp = port:PA20<6><0><2><default>
设置 PWM 驱动
屏幕背光使用的是PWM驱动,所以需要勾选PWM驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到PWM Devices
Drivers Options ---> soc related device drivers ---> PWM Devices ---> -*- enable pwm driver
配置 PWM 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,增加 PWM1 节点[pwm1] pwm_used = 1 pwm_positive = port:PA9<4><0><3><default>
设置 SPI LCD 驱动
SPI LCD 由专门的驱动管理。运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPILCD Devices
,注意同时勾选spilcd hal APIs test
方便测试使用。Drivers Options ---> soc related device drivers ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test
编写 SPI LCD 显示屏驱动
获取屏幕初始化序列
首先询问屏厂提供驱动源码
找到 LCD 的初始化序列代码
找到屏幕初始化的源码
整理后的初始化代码如下:
LCD_WR_REG(0x11); // Sleep out delay_ms(120); // Delay 120ms //************* Start Initial Sequence **********// LCD_WR_REG(0x36); LCD_WR_DATA8(0x00); LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); LCD_WR_REG(0xB2); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA8(0x35); LCD_WR_REG(0xBB); LCD_WR_DATA8(0x20); // 2b LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC4); LCD_WR_DATA8(0x18); // VDV, 0x20:0v LCD_WR_REG(0xC6); LCD_WR_DATA8(0x13); // 0x13:60Hz LCD_WR_REG(0xD0); LCD_WR_DATA8(0xA4); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xD6); LCD_WR_DATA8(0xA1); // sleep in后,gate输出为GND LCD_WR_REG(0xE0); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x25); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x30); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x21); LCD_WR_DATA8(0x25); LCD_WR_DATA8(0x32); LCD_WR_DATA8(0x3B); LCD_WR_DATA8(0x38); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x27); LCD_WR_DATA8(0x31); LCD_WR_REG(0xE4); LCD_WR_DATA8(0x1D); // 使用240根gate (N+1)*8 LCD_WR_DATA8(0x00); // 设定gate起点位置 LCD_WR_DATA8(0x00); // 当gate没有用完时,bit4(TMG)设为0 LCD_WR_REG(0x21); LCD_WR_REG(0x29);
用现成驱动改写 SPI LCD 驱动
选择一个现成的 SPI LCD 改写即可,这里选择
nv3029s.c
驱动来修改复制这两个驱动,重命名为
st7789v.c
先编辑
st7789v.h
将nv3029s
改成st7789v
#ifndef _ST7789V_H #define _ST7789V_H #include "panels.h" struct __lcd_panel st7789v_panel; #endif /*End of file*/
编辑
st7789v.c
将nv3029s
改成st7789v
编写初始化序列
先删除
static void LCD_panel_init(unsigned int sel)
中的初始化函数。然后将屏厂提供的初始化序列复制进来
然后按照
spi_lcd
框架的接口改写驱动接口,具体接口如下屏厂函数 SPILCD框架接口 LCD_WR_REG
sunxi_lcd_cmd_write
LCD_WR_DATA8
sunxi_lcd_para_write
delay_ms
sunxi_lcd_delay_ms
可以直接进行替换
完成后如下
然后对照屏厂提供的驱动修改
address
函数做如下修改
static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, (y >> 8) & 0xff); sunxi_lcd_para_write(sel, y & 0xff); sunxi_lcd_para_write(sel, (height >> 8) & 0xff); sunxi_lcd_para_write(sel, height & 0xff); sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x >> 8) & 0xff); sunxi_lcd_para_write(sel, x & 0xff); sunxi_lcd_para_write(sel, (width >> 8) & 0xff); sunxi_lcd_para_write(sel, width & 0xff); sunxi_lcd_cmd_write(sel, 0x2c); }
完成驱动如下
#include "st7789v.h" static void LCD_power_on(u32 sel); static void LCD_power_off(u32 sel); static void LCD_bl_open(u32 sel); static void LCD_bl_close(u32 sel); static void LCD_panel_init(u32 sel); static void LCD_panel_exit(u32 sel); #define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v) #define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val) static struct disp_panel_para info[LCD_FB_MAX]; static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, (y >> 8) & 0xff); sunxi_lcd_para_write(sel, y & 0xff); sunxi_lcd_para_write(sel, (height >> 8) & 0xff); sunxi_lcd_para_write(sel, height & 0xff); sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x >> 8) & 0xff); sunxi_lcd_para_write(sel, x & 0xff); sunxi_lcd_para_write(sel, (width >> 8) & 0xff); sunxi_lcd_para_write(sel, width & 0xff); sunxi_lcd_cmd_write(sel, 0x2c); } static void LCD_panel_init(unsigned int sel) { if (bsp_disp_get_panel_info(sel, &info[sel])) { lcd_fb_wrn("get panel info fail!\n"); return; } sunxi_lcd_cmd_write(sel, 0x11); // Sleep out sunxi_lcd_delay_ms(120); // Delay 120ms //************* Start Initial Sequence **********// sunxi_lcd_cmd_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_cmd_write(sel, 0x3A); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_cmd_write(sel, 0xB2); sunxi_lcd_para_write(sel, 0x1F); sunxi_lcd_para_write(sel, 0x1F); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_cmd_write(sel, 0xB7); sunxi_lcd_para_write(sel, 0x35); sunxi_lcd_cmd_write(sel, 0xBB); sunxi_lcd_para_write(sel, 0x20); // 2b sunxi_lcd_cmd_write(sel, 0xC0); sunxi_lcd_para_write(sel, 0x2C); sunxi_lcd_cmd_write(sel, 0xC2); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xC3); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xC4); sunxi_lcd_para_write(sel, 0x18); // VDV, 0x20:0v sunxi_lcd_cmd_write(sel, 0xC6); sunxi_lcd_para_write(sel, 0x13); // 0x13:60Hz sunxi_lcd_cmd_write(sel, 0xD0); sunxi_lcd_para_write(sel, 0xA4); sunxi_lcd_para_write(sel, 0xA1); sunxi_lcd_cmd_write(sel, 0xD6); sunxi_lcd_para_write(sel, 0xA1); // sleep in后,gate输出为GND sunxi_lcd_cmd_write(sel, 0xE0); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x07); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x25); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x3C); sunxi_lcd_para_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x29); sunxi_lcd_para_write(sel, 0x30); sunxi_lcd_cmd_write(sel, 0xE1); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x02); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_para_write(sel, 0x21); sunxi_lcd_para_write(sel, 0x25); sunxi_lcd_para_write(sel, 0x32); sunxi_lcd_para_write(sel, 0x3B); sunxi_lcd_para_write(sel, 0x38); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x27); sunxi_lcd_para_write(sel, 0x31); sunxi_lcd_cmd_write(sel, 0xE4); sunxi_lcd_para_write(sel, 0x1D); // 使用240根gate (N+1)*8 sunxi_lcd_para_write(sel, 0x00); // 设定gate起点位置 sunxi_lcd_para_write(sel, 0x00); // 当gate没有用完时,bit4(TMG)设为0 sunxi_lcd_cmd_write(sel, 0x21); sunxi_lcd_cmd_write(sel, 0x29); if (info[sel].lcd_x < info[sel].lcd_y) address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1); else address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1); } static void LCD_panel_exit(unsigned int sel) { sunxi_lcd_cmd_write(sel, 0x28); sunxi_lcd_delay_ms(20); sunxi_lcd_cmd_write(sel, 0x10); sunxi_lcd_delay_ms(20); sunxi_lcd_pin_cfg(sel, 0); } static s32 LCD_open_flow(u32 sel) { lcd_fb_here; /* open lcd power, and delay 50ms */ LCD_OPEN_FUNC(sel, LCD_power_on, 50); /* open lcd power, than delay 200ms */ LCD_OPEN_FUNC(sel, LCD_panel_init, 200); LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50); /* open lcd backlight, and delay 0ms */ LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; } static s32 LCD_close_flow(u32 sel) { lcd_fb_here; /* close lcd backlight, and delay 0ms */ LCD_CLOSE_FUNC(sel, LCD_bl_close, 50); /* open lcd power, than delay 200ms */ LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10); /* close lcd power, and delay 500ms */ LCD_CLOSE_FUNC(sel, LCD_power_off, 10); return 0; } static void LCD_power_on(u32 sel) { /* config lcd_power pin to open lcd power0 */ lcd_fb_here; power_en(sel, 1); sunxi_lcd_power_enable(sel, 0); sunxi_lcd_pin_cfg(sel, 1); RESET(sel, 1); sunxi_lcd_delay_ms(100); RESET(sel, 0); sunxi_lcd_delay_ms(100); RESET(sel, 1); } static void LCD_power_off(u32 sel) { lcd_fb_here; /* config lcd_power pin to close lcd power0 */ sunxi_lcd_power_disable(sel, 0); power_en(sel, 0); } static void LCD_bl_open(u32 sel) { sunxi_lcd_pwm_enable(sel); /* config lcd_bl_en pin to open lcd backlight */ sunxi_lcd_backlight_enable(sel); lcd_fb_here; } static void LCD_bl_close(u32 sel) { /* config lcd_bl_en pin to close lcd backlight */ sunxi_lcd_backlight_disable(sel); sunxi_lcd_pwm_disable(sel); lcd_fb_here; } /* sel: 0:lcd0; 1:lcd1 */ static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3) { lcd_fb_here; return 0; } static int lcd_set_var(unsigned int sel, struct fb_info *p_info) { return 0; } static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height) { address(sel, x, y, width, height); return 0; } static int lcd_blank(unsigned int sel, unsigned int en) { return 0; } struct __lcd_panel st7789v_panel = { /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */ .name = "st7789v", .func = { .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, .lcd_user_defined_func = LCD_user_defined_func, .blank = lcd_blank, .set_var = lcd_set_var, .set_addr_win = lcd_set_addr_win, }, };
对接驱动框架
完成了屏幕驱动的编写,接下来需要对接到 SPILCD 驱动框架。首先编辑
Kconfig
增加
st7789v
的配置config LCD_SUPPORT_ST7789V bool "LCD support st7789v panel" default n ---help--- If you want to support st7789v panel for display driver, select it.
然后编辑
panels.c
在panel_array
里增加st7789
驱动的引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V &st7789v_panel, #endif
之后编辑
panels.h
同样增加引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V extern struct __lcd_panel st7789v_panel; #endif
最后编辑外层的
Makefile
增加编译选项如下所示
obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
选择 ST7789V 驱动
在 SPILCD 驱动选择界面可以看到
LCD_FB panels select
选择 SPI 屏幕的驱动进入
LCD_FB panels select
选项选择并勾选
[*] LCD support st7789v panel
配置 SPI LCD 引脚
这里是重点部分:打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
[lcd_fb0] lcd_used = 1 lcd_model_name = "spilcd" lcd_driver_name = "st7789v" ; 屏幕规格配置 lcd_x = 240 lcd_y = 240 lcd_width = 48 lcd_height = 48 ; SPI 速率 lcd_data_speed = 50 ; PWM 背光配置项 lcd_pwm_used = 1 lcd_pwm_ch = 1 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_backlight = 100 ; 配置 lcd_if = 1 为 DBI 模式,双缓冲 lcd_if = 1 fb_buffer_num = 2 ; 配置屏幕传输数据的像素格式,这里是 LCDFB_FORMAT_RGB_565 lcd_pixel_fmt = 10 ; 配置 DBI 接口像素格式,这里是 RGB565 lcd_dbi_fmt = 2 ; 配置 DBI 时钟的行为模式,这里是自动停止模式。有数据就有时钟,没发数据就没有时钟 lcd_dbi_clk_mode = 0 ; 屏幕没有 TE 脚,配置 TE 为 0 lcd_dbi_te = 0 ; 配置屏幕 DBI 格式 L4I1 lcd_dbi_if = 2 ; 输入图像数据 RGB 顺序识别设置,这里配置是 RGB 格式 lcd_rgb_order = 0 ; 设置屏的刷新率,单位Hz。当lcd_dbi_te使能时,这个值设置无效。 lcd_fps = 60 ; 使用 SPI1 作为通讯接口 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0>
编译打包
运行命令
mp
编译打包,可以看到编译了st7789v.o
测试
烧录启动之后,屏幕背光启动,但是屏幕全黑。
输入
test_spilcd
,屏幕显示蓝色。输入
lv_examples 1
可以显示lvgl
界面常见问题
LVGL 颜色异常
这是由于 LVGL 配置的
LV_COLOR_DEPTH
为 32,但是 SPI 屏配置为16位。请修改lv_conf.h
,也请注意LV_COLOR_16_SWAP
仅有 SPI 需要设置为 1,在使用 DBI 驱动的时候不需要配置为 1。出现部分花屏
- 检查
address
函数是否正确 - 检查
sys_config.fex
屏幕配置分辨率是否正确
SPI LCD 颜色相关问题
首先,得先确定显示屏使用的是SPI接口,还是DBI接口,不同的接口,输入数据的解析方式是不一样的。
DBI接口的全称是
Display Bus Serial Interface
,在显示屏数据手册中,一般会说这是SPI接口,所以有人会误认为SPI屏可以使用normal spi
去直接驱动。阅读
lcd_dbi_if
部分的介绍可以知道,在3线模式时,发送命令前有1位A0用于指示当前发送的是数据,还是命令。而命令后面接着的数据就没有这个A0位了,代表SPI需要在9位和8位之间来回切换,而在读数据时,更是需要延时dummy clock
才能读数据,normal spi
都很难,甚至无法实现。所以normal spi
只能模拟4 线的DBI的写操作。对于R128这类支持DBI接口的CPU,可以选择不去了解SPI。如果需要用到SPI去驱动显示屏,必须把显示屏设置成小端。
RGB565和RGB666
SPI显示屏一般支持RGB444,RGB565和RGB666,RGB444使用的比较少,所以只讨论RGB565和RGB666.
RGB565代表一个点的颜色由2字节组成,也就是R(红色)用5位表示,G(绿色)用6位表示,B(蓝色)用5位表示,如下图所示:
RGB666一个点的颜色由3字节组成,每个字节代表一个颜色,其中每个字节的低2位会无视,如下图所示:
SPI 接口
因为SPI接口的通讯效率不高,所以建议使用RGB565的显示,以
jlt35031c
显示屏为例,他的显示驱动芯片是ST7789v
,设置显示格式的方式是往3a
寄存器写入0x55(RGB565
)或者0x66(RGB666)
,在R128SDK
中,已经把jlt35031c
的通讯格式写死为0x55
,lcd_pixel_fmt
配置选项无效:sunxi_lcd_cmd_write(sel, 0x3a); sunxi_lcd_para_write(sel, 0x55);
在例程中,输入的数据是
0xff,0x00,0xff,0x00
,对于SPI接口,是按字节发送。实际上,例程只需要每次发送2字节即可,因为前后发送的都是相同的ff 00,所以没有看出问题。根据对
565
的数据解析,我们拆分ff 00
就可以得到红色分量是0b11111
,也就是31
,绿色是0b111000
,也就是56
,,蓝色是0
.我们等效转换成RGB888
,有:R = 31/31*255 = 255 G = 56/63*255 = 226
在调色板输入对应颜色,就可以得到黄色
因为
DBI
通讯效率较高,所以可以使用RGB565
或者RGB666
,使用DBI
接口,也就是lcd_if
设置为1
时,驱动会根据lcd_pixel_fmt
配置寄存器,以SDK
中的kld2844b.c
为例,这显示屏的显示驱动也是ST7789
,但是不同的屏幕,厂家封装时已经限制了通讯方式,所以即使是能使用 DBI 接口的驱动芯片的屏幕,或许也用不了DBI。sunxi_lcd_cmd_write(sel, 0x3A); /* Interface Pixel Format */ /* 55----RGB565;66---RGB666 */ if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGR_565) { sunxi_lcd_para_write(sel, 0x55); if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565) rotate &= 0xf7; else rotate |= 0x08; } else if (info[sel].lcd_pixel_fmt < LCDFB_FORMAT_RGB_888) { sunxi_lcd_para_write(sel, 0x66); if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRA_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRX_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_ABGR_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_XBGR_8888) { rotate |= 0x08; } } else { sunxi_lcd_para_write(sel, 0x66); }
对于 DBI 格式,不再是以字节的形式去解析,而是以字的方式去解析,为了统一,软件已经规定了,
RGB565
格式时,字大小是2字节,也就是16位,而RGB666
格式时,字大小是4字节,也就是32位。对于
RGB565
格式,同样是设置为0xff,0x00
。因为屏幕是大端,而芯片存储方式是小端,所以芯片的 DBI 模块,会自动把数据从新排列,也就是实际上 DBI 发送数据时,会先发送0x00
,再发送0xff
,也就是红色分量为0,绿色分量为0b000111
,也就是7,蓝色分量是0x11111
,也就是31,我们同样转换成RGB888G = 7/63*255 = 28 B= 31/31*255 = 255
在调色板上输入,可以得到蓝色。
如果是
RGB666
,虽然占用的是3个字节,但是没有CPU是3字节对齐的,所以需要一次性输入4字节,然后 DBI 硬件模块,会自动舍弃1个字节,软件同意舍弃了最后一个字节。依旧以例程为例,例程输入了
0xff,0x00,0xff,0x00
,为了方便说明,标准为0xff(1),0x00(1),0xff(2),0x00(2)
,其中0x00(2)
会被舍弃掉,然后发送顺序是0xff(2),0x00(1),0xff(1)
,也就是0xff(2)
是红色分量,0xff(1)
是蓝色分量,混合可以得到紫色。 -
【R128】应用开发案例——SPI驱动ST7789V1.3寸LCD
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
SPI驱动ST7789V1.3寸LCD
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了 SPILCD 驱动框架以供 SPI 屏幕使用。
此次适配的SPI屏为
ZJY130S0800TG01
,使用的是 SPI 进行驱动。引脚配置如下:
R128 Devkit TFT 模块 PA12 CS PA13 SCL PA18 SDA PA9 BLK PA20 RES PA19 DC 3V3 VCC GND GND 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 SPI 驱动
屏幕使用的是SPI驱动,所以需要勾选SPI驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPI Devices
Drivers Options ---> soc related device drivers ---> SPI Devices ---> -*- enable spi driver
配置 SPI 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,在这里我们不需要用到 SPI HOLD与SPI WP引脚,注释掉即可。;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;Please config spi in dts [spi1] spi1_used = 1 spi1_cs_number = 1 spi1_cs_bitmap = 1 spi1_cs0 = port:PA12<6><0><3><default> spi1_sclk = port:PA13<6><0><3><default> spi1_mosi = port:PA18<6><0><3><default> spi1_miso = port:PA21<6><0><3><default> ;spi1_hold = port:PA19<6><0><2><default> ;spi1_wp = port:PA20<6><0><2><default>
设置 PWM 驱动
屏幕背光使用的是PWM驱动,所以需要勾选PWM驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到PWM Devices
Drivers Options ---> soc related device drivers ---> PWM Devices ---> -*- enable pwm driver
配置 PWM 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,增加 PWM1 节点[pwm1] pwm_used = 1 pwm_positive = port:PA9<4><0><3><default>
设置 SPI LCD 驱动
SPI LCD 由专门的驱动管理。运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPILCD Devices
,注意同时勾选spilcd hal APIs test
方便测试使用。Drivers Options ---> soc related device drivers ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test
编写 SPI LCD 显示屏驱动
获取屏幕初始化序列
首先询问屏厂提供驱动源码
找到 LCD 的初始化序列代码
找到屏幕初始化的源码
整理后的初始化代码如下:
LCD_WR_REG(0x11); // Sleep out delay_ms(120); // Delay 120ms //************* Start Initial Sequence **********// LCD_WR_REG(0x36); LCD_WR_DATA8(0x00); LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); LCD_WR_REG(0xB2); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA8(0x35); LCD_WR_REG(0xBB); LCD_WR_DATA8(0x20); // 2b LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC4); LCD_WR_DATA8(0x18); // VDV, 0x20:0v LCD_WR_REG(0xC6); LCD_WR_DATA8(0x13); // 0x13:60Hz LCD_WR_REG(0xD0); LCD_WR_DATA8(0xA4); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xD6); LCD_WR_DATA8(0xA1); // sleep in后,gate输出为GND LCD_WR_REG(0xE0); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x25); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x30); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x21); LCD_WR_DATA8(0x25); LCD_WR_DATA8(0x32); LCD_WR_DATA8(0x3B); LCD_WR_DATA8(0x38); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x27); LCD_WR_DATA8(0x31); LCD_WR_REG(0xE4); LCD_WR_DATA8(0x1D); // 使用240根gate (N+1)*8 LCD_WR_DATA8(0x00); // 设定gate起点位置 LCD_WR_DATA8(0x00); // 当gate没有用完时,bit4(TMG)设为0 LCD_WR_REG(0x21); LCD_WR_REG(0x29);
用现成驱动改写 SPI LCD 驱动
选择一个现成的 SPI LCD 改写即可,这里选择
nv3029s.c
驱动来修改复制这两个驱动,重命名为
st7789v.c
先编辑
st7789v.h
将nv3029s
改成st7789v
#ifndef _ST7789V_H #define _ST7789V_H #include "panels.h" struct __lcd_panel st7789v_panel; #endif /*End of file*/
编辑
st7789v.c
将nv3029s
改成st7789v
编写初始化序列
先删除
static void LCD_panel_init(unsigned int sel)
中的初始化函数。然后将屏厂提供的初始化序列复制进来
然后按照
spi_lcd
框架的接口改写驱动接口,具体接口如下屏厂函数 SPILCD框架接口 LCD_WR_REG
sunxi_lcd_cmd_write
LCD_WR_DATA8
sunxi_lcd_para_write
delay_ms
sunxi_lcd_delay_ms
可以直接进行替换
完成后如下
然后对照屏厂提供的驱动修改
address
函数做如下修改
static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, (y >> 8) & 0xff); sunxi_lcd_para_write(sel, y & 0xff); sunxi_lcd_para_write(sel, (height >> 8) & 0xff); sunxi_lcd_para_write(sel, height & 0xff); sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x >> 8) & 0xff); sunxi_lcd_para_write(sel, x & 0xff); sunxi_lcd_para_write(sel, (width >> 8) & 0xff); sunxi_lcd_para_write(sel, width & 0xff); sunxi_lcd_cmd_write(sel, 0x2c); }
完成驱动如下
#include "st7789v.h" static void LCD_power_on(u32 sel); static void LCD_power_off(u32 sel); static void LCD_bl_open(u32 sel); static void LCD_bl_close(u32 sel); static void LCD_panel_init(u32 sel); static void LCD_panel_exit(u32 sel); #define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v) #define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val) static struct disp_panel_para info[LCD_FB_MAX]; static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, (y >> 8) & 0xff); sunxi_lcd_para_write(sel, y & 0xff); sunxi_lcd_para_write(sel, (height >> 8) & 0xff); sunxi_lcd_para_write(sel, height & 0xff); sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x >> 8) & 0xff); sunxi_lcd_para_write(sel, x & 0xff); sunxi_lcd_para_write(sel, (width >> 8) & 0xff); sunxi_lcd_para_write(sel, width & 0xff); sunxi_lcd_cmd_write(sel, 0x2c); } static void LCD_panel_init(unsigned int sel) { if (bsp_disp_get_panel_info(sel, &info[sel])) { lcd_fb_wrn("get panel info fail!\n"); return; } sunxi_lcd_cmd_write(sel, 0x11); // Sleep out sunxi_lcd_delay_ms(120); // Delay 120ms //************* Start Initial Sequence **********// sunxi_lcd_cmd_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_cmd_write(sel, 0x3A); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_cmd_write(sel, 0xB2); sunxi_lcd_para_write(sel, 0x1F); sunxi_lcd_para_write(sel, 0x1F); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_cmd_write(sel, 0xB7); sunxi_lcd_para_write(sel, 0x35); sunxi_lcd_cmd_write(sel, 0xBB); sunxi_lcd_para_write(sel, 0x20); // 2b sunxi_lcd_cmd_write(sel, 0xC0); sunxi_lcd_para_write(sel, 0x2C); sunxi_lcd_cmd_write(sel, 0xC2); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xC3); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xC4); sunxi_lcd_para_write(sel, 0x18); // VDV, 0x20:0v sunxi_lcd_cmd_write(sel, 0xC6); sunxi_lcd_para_write(sel, 0x13); // 0x13:60Hz sunxi_lcd_cmd_write(sel, 0xD0); sunxi_lcd_para_write(sel, 0xA4); sunxi_lcd_para_write(sel, 0xA1); sunxi_lcd_cmd_write(sel, 0xD6); sunxi_lcd_para_write(sel, 0xA1); // sleep in后,gate输出为GND sunxi_lcd_cmd_write(sel, 0xE0); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x07); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x25); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x3C); sunxi_lcd_para_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x29); sunxi_lcd_para_write(sel, 0x30); sunxi_lcd_cmd_write(sel, 0xE1); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x02); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_para_write(sel, 0x21); sunxi_lcd_para_write(sel, 0x25); sunxi_lcd_para_write(sel, 0x32); sunxi_lcd_para_write(sel, 0x3B); sunxi_lcd_para_write(sel, 0x38); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x27); sunxi_lcd_para_write(sel, 0x31); sunxi_lcd_cmd_write(sel, 0xE4); sunxi_lcd_para_write(sel, 0x1D); // 使用240根gate (N+1)*8 sunxi_lcd_para_write(sel, 0x00); // 设定gate起点位置 sunxi_lcd_para_write(sel, 0x00); // 当gate没有用完时,bit4(TMG)设为0 sunxi_lcd_cmd_write(sel, 0x21); sunxi_lcd_cmd_write(sel, 0x29); if (info[sel].lcd_x < info[sel].lcd_y) address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1); else address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1); } static void LCD_panel_exit(unsigned int sel) { sunxi_lcd_cmd_write(sel, 0x28); sunxi_lcd_delay_ms(20); sunxi_lcd_cmd_write(sel, 0x10); sunxi_lcd_delay_ms(20); sunxi_lcd_pin_cfg(sel, 0); } static s32 LCD_open_flow(u32 sel) { lcd_fb_here; /* open lcd power, and delay 50ms */ LCD_OPEN_FUNC(sel, LCD_power_on, 50); /* open lcd power, than delay 200ms */ LCD_OPEN_FUNC(sel, LCD_panel_init, 200); LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50); /* open lcd backlight, and delay 0ms */ LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; } static s32 LCD_close_flow(u32 sel) { lcd_fb_here; /* close lcd backlight, and delay 0ms */ LCD_CLOSE_FUNC(sel, LCD_bl_close, 50); /* open lcd power, than delay 200ms */ LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10); /* close lcd power, and delay 500ms */ LCD_CLOSE_FUNC(sel, LCD_power_off, 10); return 0; } static void LCD_power_on(u32 sel) { /* config lcd_power pin to open lcd power0 */ lcd_fb_here; power_en(sel, 1); sunxi_lcd_power_enable(sel, 0); sunxi_lcd_pin_cfg(sel, 1); RESET(sel, 1); sunxi_lcd_delay_ms(100); RESET(sel, 0); sunxi_lcd_delay_ms(100); RESET(sel, 1); } static void LCD_power_off(u32 sel) { lcd_fb_here; /* config lcd_power pin to close lcd power0 */ sunxi_lcd_power_disable(sel, 0); power_en(sel, 0); } static void LCD_bl_open(u32 sel) { sunxi_lcd_pwm_enable(sel); /* config lcd_bl_en pin to open lcd backlight */ sunxi_lcd_backlight_enable(sel); lcd_fb_here; } static void LCD_bl_close(u32 sel) { /* config lcd_bl_en pin to close lcd backlight */ sunxi_lcd_backlight_disable(sel); sunxi_lcd_pwm_disable(sel); lcd_fb_here; } /* sel: 0:lcd0; 1:lcd1 */ static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3) { lcd_fb_here; return 0; } static int lcd_set_var(unsigned int sel, struct fb_info *p_info) { return 0; } static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height) { address(sel, x, y, width, height); return 0; } static int lcd_blank(unsigned int sel, unsigned int en) { return 0; } struct __lcd_panel st7789v_panel = { /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */ .name = "st7789v", .func = { .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, .lcd_user_defined_func = LCD_user_defined_func, .blank = lcd_blank, .set_var = lcd_set_var, .set_addr_win = lcd_set_addr_win, }, };
对接驱动框架
完成了屏幕驱动的编写,接下来需要对接到 SPILCD 驱动框架。首先编辑
Kconfig
增加
st7789v
的配置config LCD_SUPPORT_ST7789V bool "LCD support st7789v panel" default n ---help--- If you want to support st7789v panel for display driver, select it.
然后编辑
panels.c
在panel_array
里增加st7789
驱动的引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V &st7789v_panel, #endif
之后编辑
panels.h
同样增加引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V extern struct __lcd_panel st7789v_panel; #endif
最后编辑外层的
Makefile
增加编译选项如下所示
obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
选择 ST7789V 驱动
在 SPILCD 驱动选择界面可以看到
LCD_FB panels select
选择 SPI 屏幕的驱动进入
LCD_FB panels select
选项选择并勾选
[*] LCD support st7789v panel
配置 SPI LCD 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
[lcd_fb0] lcd_used = 1 lcd_model_name = "spilcd" lcd_driver_name = "st7789v" ; 屏幕规格配置 lcd_x = 240 lcd_y = 240 lcd_width = 32 lcd_height = 32 ; SPI 速率 lcd_data_speed = 50 ; PWM 背光配置项 lcd_pwm_used = 1 lcd_pwm_ch = 1 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_backlight = 100 ; 配置 lcd_if = 1 为 SPI 模式,双缓冲 lcd_if = 0 fb_buffer_num = 2 ; SPI 模式下以下配置无效 lcd_pixel_fmt = 11 lcd_dbi_fmt = 2 lcd_dbi_clk_mode = 1 lcd_dbi_te = 1 lcd_dbi_if = 4 lcd_rgb_order = 0 lcd_fps = 60 ; 使用 SPI1 作为通讯接口 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 lcd_spi_dc_pin = port:PA19<1><0><3><0> ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0>
编译打包
运行命令
mp
编译打包,可以看到编译了st7789v.o
测试
烧录启动之后,屏幕背光启动,但是屏幕全黑。
输入
test_spilcd
,屏幕显示黄色。输入
lv_examples 1
可以显示lvgl
界面常见问题
LVGL 出现 DMA Over Size
这是由于 LVGL 配置的
LV_COLOR_DEPTH
为 32,但是 SPI 屏配置为16位。请修改lv_conf.h
出现部分花屏
- 检查
address
函数是否正确 - 检查
sys_config.fex
屏幕配置分辨率是否正确
-
【R128】应用开发案例——适配SPI驱动ST7789V2.4寸LCD
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
SPI驱动ST7789V1.47寸LCD
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了 SPILCD 驱动框架以供 SPI 屏幕使用。
此次适配的SPI屏为
ZJY147S0800TG01
,使用的是 SPI 进行驱动。引脚配置如下:
R128 Devkit TFT 模块 PA12 CS PA13 SCL PA18 SDA PA9 BLK PA20 RES PA19 DC 3V3 VCC GND GND 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 SPI 驱动
屏幕使用的是SPI驱动,所以需要勾选SPI驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPI Devices
Drivers Options ---> soc related device drivers ---> SPI Devices ---> -*- enable spi driver
配置 SPI 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,在这里我们不需要用到 SPI HOLD与SPI WP引脚,注释掉即可。;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;Please config spi in dts [spi1] spi1_used = 1 spi1_cs_number = 1 spi1_cs_bitmap = 1 spi1_cs0 = port:PA12<6><0><3><default> spi1_sclk = port:PA13<6><0><3><default> spi1_mosi = port:PA18<6><0><3><default> spi1_miso = port:PA21<6><0><3><default> ;spi1_hold = port:PA19<6><0><2><default> ;spi1_wp = port:PA20<6><0><2><default>
设置 PWM 驱动
屏幕背光使用的是PWM驱动,所以需要勾选PWM驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到PWM Devices
Drivers Options ---> soc related device drivers ---> PWM Devices ---> -*- enable pwm driver
配置 PWM 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,增加 PWM1 节点[pwm1] pwm_used = 1 pwm_positive = port:PA9<4><0><3><default>
设置 SPI LCD 驱动
SPI LCD 由专门的驱动管理。运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPILCD Devices
,注意同时勾选spilcd hal APIs test
方便测试使用。Drivers Options ---> soc related device drivers ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test
编写 SPI LCD 显示屏驱动
获取屏幕初始化序列
首先询问屏厂提供驱动源码
)
找到 LCD 的初始化序列代码
找到屏幕初始化的源码
整理后的初始化代码如下:
LCD_WR_REG(0x11); delay_ms(120); LCD_WR_REG(0x36); LCD_WR_DATA8(0x00); LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); LCD_WR_REG(0xB2); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA8(0x35); LCD_WR_REG(0xBB); LCD_WR_DATA8(0x35); LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x13); LCD_WR_REG(0xC4); LCD_WR_DATA8(0x20); LCD_WR_REG(0xC6); LCD_WR_DATA8(0x0F); LCD_WR_REG(0xD0); LCD_WR_DATA8(0xA4); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xD6); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xE0); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3E); LCD_WR_DATA8(0x38); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x28); LCD_WR_DATA8(0x30); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x0A); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0B); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x28); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3E); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x32); LCD_WR_REG(0x21); LCD_WR_REG(0x11); delay_ms(120); LCD_WR_REG(0x29);
用现成驱动改写 SPI LCD 驱动
选择一个现成的 SPI LCD 改写即可,这里选择
nv3029s.c
驱动来修改复制这两个驱动,重命名为
st7789v.c
先编辑
st7789v.h
将nv3029s
改成st7789v
#ifndef _ST7789V_H #define _ST7789V_H #include "panels.h" struct __lcd_panel st7789v_panel; #endif /*End of file*/
编辑
st7789v.c
将nv3029s
改成st7789v
编写初始化序列
先删除
static void LCD_panel_init(unsigned int sel)
中的初始化函数。然后将屏厂提供的初始化序列复制进来
然后按照
spi_lcd
框架的接口改写驱动接口,具体接口如下屏厂函数 SPILCD框架接口 LCD_WR_REG
sunxi_lcd_cmd_write
LCD_WR_DATA8
sunxi_lcd_para_write
delay_ms
sunxi_lcd_delay_ms
可以直接进行替换
完成后如下
然后对照屏厂提供的驱动修改
address
函数做如下修改
static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x + 34) >> 8); sunxi_lcd_para_write(sel, (x + 34)); sunxi_lcd_para_write(sel, (width + 34) >> 8); sunxi_lcd_para_write(sel, (width + 34)); sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, y >> 8); sunxi_lcd_para_write(sel, y); sunxi_lcd_para_write(sel, height >> 8); sunxi_lcd_para_write(sel, height); sunxi_lcd_cmd_write(sel, 0x2c); }
完成驱动如下
#include "st7789v.h" static void LCD_power_on(u32 sel); static void LCD_power_off(u32 sel); static void LCD_bl_open(u32 sel); static void LCD_bl_close(u32 sel); static void LCD_panel_init(u32 sel); static void LCD_panel_exit(u32 sel); #define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v) #define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val) static struct disp_panel_para info[LCD_FB_MAX]; static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x + 34) >> 8); sunxi_lcd_para_write(sel, (x + 34)); sunxi_lcd_para_write(sel, (width + 34) >> 8); sunxi_lcd_para_write(sel, (width + 34)); sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, y >> 8); sunxi_lcd_para_write(sel, y); sunxi_lcd_para_write(sel, height >> 8); sunxi_lcd_para_write(sel, height); sunxi_lcd_cmd_write(sel, 0x2c); } static void LCD_panel_init(unsigned int sel) { if (bsp_disp_get_panel_info(sel, &info[sel])) { lcd_fb_wrn("get panel info fail!\n"); return; } sunxi_lcd_cmd_write(sel, 0x11); sunxi_lcd_delay_ms(120); sunxi_lcd_cmd_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_cmd_write(sel, 0x3A); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_cmd_write(sel, 0xB2); sunxi_lcd_para_write(sel, 0x0C); sunxi_lcd_para_write(sel, 0x0C); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_cmd_write(sel, 0xB7); sunxi_lcd_para_write(sel, 0x35); sunxi_lcd_cmd_write(sel, 0xBB); sunxi_lcd_para_write(sel, 0x35); sunxi_lcd_cmd_write(sel, 0xC0); sunxi_lcd_para_write(sel, 0x2C); sunxi_lcd_cmd_write(sel, 0xC2); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xC3); sunxi_lcd_para_write(sel, 0x13); sunxi_lcd_cmd_write(sel, 0xC4); sunxi_lcd_para_write(sel, 0x20); sunxi_lcd_cmd_write(sel, 0xC6); sunxi_lcd_para_write(sel, 0x0F); sunxi_lcd_cmd_write(sel, 0xD0); sunxi_lcd_para_write(sel, 0xA4); sunxi_lcd_para_write(sel, 0xA1); sunxi_lcd_cmd_write(sel, 0xD6); sunxi_lcd_para_write(sel, 0xA1); sunxi_lcd_cmd_write(sel, 0xE0); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x04); sunxi_lcd_para_write(sel, 0x05); sunxi_lcd_para_write(sel, 0x29); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x3E); sunxi_lcd_para_write(sel, 0x38); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x12); sunxi_lcd_para_write(sel, 0x28); sunxi_lcd_para_write(sel, 0x30); sunxi_lcd_cmd_write(sel, 0xE1); sunxi_lcd_para_write(sel, 0xF0); sunxi_lcd_para_write(sel, 0x07); sunxi_lcd_para_write(sel, 0x0A); sunxi_lcd_para_write(sel, 0x0D); sunxi_lcd_para_write(sel, 0x0B); sunxi_lcd_para_write(sel, 0x07); sunxi_lcd_para_write(sel, 0x28); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x3E); sunxi_lcd_para_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x29); sunxi_lcd_para_write(sel, 0x32); sunxi_lcd_cmd_write(sel, 0x21); sunxi_lcd_cmd_write(sel, 0x11); sunxi_lcd_delay_ms(120); sunxi_lcd_cmd_write(sel, 0x29); if (info[sel].lcd_x < info[sel].lcd_y) address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1); else address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1); } static void LCD_panel_exit(unsigned int sel) { sunxi_lcd_cmd_write(sel, 0x28); sunxi_lcd_delay_ms(20); sunxi_lcd_cmd_write(sel, 0x10); sunxi_lcd_delay_ms(20); sunxi_lcd_pin_cfg(sel, 0); } static s32 LCD_open_flow(u32 sel) { lcd_fb_here; /* open lcd power, and delay 50ms */ LCD_OPEN_FUNC(sel, LCD_power_on, 50); /* open lcd power, than delay 200ms */ LCD_OPEN_FUNC(sel, LCD_panel_init, 200); LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50); /* open lcd backlight, and delay 0ms */ LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; } static s32 LCD_close_flow(u32 sel) { lcd_fb_here; /* close lcd backlight, and delay 0ms */ LCD_CLOSE_FUNC(sel, LCD_bl_close, 50); /* open lcd power, than delay 200ms */ LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10); /* close lcd power, and delay 500ms */ LCD_CLOSE_FUNC(sel, LCD_power_off, 10); return 0; } static void LCD_power_on(u32 sel) { /* config lcd_power pin to open lcd power0 */ lcd_fb_here; power_en(sel, 1); sunxi_lcd_power_enable(sel, 0); sunxi_lcd_pin_cfg(sel, 1); RESET(sel, 1); sunxi_lcd_delay_ms(100); RESET(sel, 0); sunxi_lcd_delay_ms(100); RESET(sel, 1); } static void LCD_power_off(u32 sel) { lcd_fb_here; /* config lcd_power pin to close lcd power0 */ sunxi_lcd_power_disable(sel, 0); power_en(sel, 0); } static void LCD_bl_open(u32 sel) { sunxi_lcd_pwm_enable(sel); /* config lcd_bl_en pin to open lcd backlight */ sunxi_lcd_backlight_enable(sel); lcd_fb_here; } static void LCD_bl_close(u32 sel) { /* config lcd_bl_en pin to close lcd backlight */ sunxi_lcd_backlight_disable(sel); sunxi_lcd_pwm_disable(sel); lcd_fb_here; } /* sel: 0:lcd0; 1:lcd1 */ static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3) { lcd_fb_here; return 0; } static int lcd_set_var(unsigned int sel, struct fb_info *p_info) { return 0; } static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height) { address(sel, x, y, width, height); return 0; } static int lcd_blank(unsigned int sel, unsigned int en) { return 0; } struct __lcd_panel st7789v_panel = { /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */ .name = "st7789v", .func = { .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, .lcd_user_defined_func = LCD_user_defined_func, .blank = lcd_blank, .set_var = lcd_set_var, .set_addr_win = lcd_set_addr_win, }, };
对接驱动框架
完成了屏幕驱动的编写,接下来需要对接到 SPILCD 驱动框架。首先编辑
Kconfig
增加
st7789v
的配置config LCD_SUPPORT_ST7789V bool "LCD support st7789v panel" default n ---help--- If you want to support st7789v panel for display driver, select it.
然后编辑
panels.c
在panel_array
里增加st7789
驱动的引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V &st7789v_panel, #endif
之后编辑
panels.h
同样增加引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V extern struct __lcd_panel st7789v_panel; #endif
最后编辑外层的
Makefile
增加编译选项如下所示
obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
选择 ST7789V 驱动
在 SPILCD 驱动选择界面可以看到
LCD_FB panels select
选择 SPI 屏幕的驱动进入
LCD_FB panels select
选项选择并勾选
[*] LCD support st7789v panel
配置 SPI LCD 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
[lcd_fb0] lcd_used = 1 lcd_model_name = "spilcd" lcd_driver_name = "st7789v" lcd_x = 172 lcd_y = 320 lcd_width = 17 lcd_height = 32 lcd_data_speed = 50 lcd_pwm_used = 1 lcd_pwm_ch = 1 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_if = 0 lcd_pixel_fmt = 11 lcd_dbi_fmt = 2 lcd_dbi_clk_mode = 1 lcd_dbi_te = 1 fb_buffer_num = 2 lcd_dbi_if = 4 lcd_rgb_order = 0 lcd_fps = 60 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_backlight = 100 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 lcd_spi_dc_pin = port:PA19<1><0><3><0> ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0>
编译打包
运行命令
mp
编译打包,可以看到编译了st7789v.o
测试
烧录启动之后,屏幕背光启动,但是屏幕全黑。
输入
test_spilcd
,屏幕显示黄色。输入
lv_examples 1
可以显示lvgl
界面常见问题
LVGL 出现 DMA Over Size
这是由于 LVGL 配置的
LV_COLOR_DEPTH
为 32,但是 SPI 屏配置为16位。请修改lv_conf.h
-
【R128】基础组件开发指南——RTOS 异构多核通信
RTOS 异构多核通信
异构多核通信介绍
R128 所带有的 M33 主核心与 C906, HIFI5 DSP 核心是完全不同的核心,为了最大限度的发挥他们的性能,协同完成某一任务,所以在不同的核心上面运行的系统也各不相同。这些不同架构的核心以及他们上面所运行的软件组合在一起,就成了 AMP 系统 (Asymmetric Multiprocessing System, 异构多处理系统)。
为了多核心协同工作,对于需要异构多核通信框架需要满足以下功能:
- 隔离核间差异,把一部分服务部署在一个核上,另一部分服务部署在另外的核上,应用层代码只需通过标准接口来申请服务,其对底层服务具体在哪个核上实现无感知。
- 同一个核,既可作为远程服务端,也可以作为客户端。
针对异构多核系统的特性,在进行远程服务调用时,需要解决以下几个问题:
- 缓存一致性问题。缓存一致性是在异构多核系统中十分重要的问题,跨核调用者和服务提供者必须知道其使用的 buffer 是否会经过其他核的修改,是否会被其他核读取数据。若被其他核修改,那当前核使用时,需要先无效 buffer 对应的 dcache;若会被其他核读取,则当前核写完数据后,需要将 buffer 对应的 dcache 刷回下一级内存。如此一来,一旦远程服务的参数或返回值比较复杂,那么使用者和服务提供者就需要花大量的精力来理清每个 buffer 的使用关系,极大地增加了他们的负担。并且,为了避免对其他数据造成影响,冲刷 dcache 时也还需要保证其数据独占一个 cacheline,否则会将其他数据误刷。这也会增加处理缓存一致性问题的难度。
- CPU 位宽不一致问题。在 R128 项目中,HIFI5 的 CPU 位宽为 32bit,C906 的 CPU 位宽为64bit,M33 的 CPU 位宽为 32bit。对于三种不同的核,软件上指针变量、long 类型变量的数据大小并不一致,那么就会导致同一个结构体在不同位宽的核上,其内存布局并不一致,三个核若直接读取则会发生错误。
- 复杂场景处理问题。在多个核之间,需要考虑到远程服务端的并发处理能力、核间的嵌套调用关系、服务端函数的休眠、如何降低内存使用开销等。这些场景都是需要进行优化处理的,服务端的并发处理能力会影响到跨核远程服务的高效性;核间的嵌套调用以及服务函数的休眠,影响到远程调用服务的稳定性。
为了解决这些问题,提供了 Sunxi-AMP 框架提供异构通讯的接口。同时也提供了 rpdata 实现更加底层的异构通讯
Sunxi‑AMP 简介
Sunxi‑AMP 工作流程图
线程池
amp_threadpool.c
文件中实现了一个简易线程池,在系统启动时创建指定线程数量的线程池,以提升远程消息的处理效率。若处理远程消息较多,无法及时处理,则由动态增加线程池中线程的数量;若消息较少,则动态删除线程池中的线程。初始化时创建的线程数量由amp_threadpool.h
中的AMP_THD_POOL_MIN_NUM
宏来决定,其也表示线程池中至少存在的最小数量的处理线程。AMP_THD_POOL_MAX_NUM
表示线程池中能同时存在的处理线程的最大数量。动态新增处理线程的条件是消息接受队列中存在两个或以上的未处理消息并且当前线程池中存活线程数量小于AMP_THD_POOL_MAX_NUM
。动态删除处理线程的条件是 (正在处理消息的线程的数量 * 2) 小于系统中存活线程数量并且当前线程池中存活线程数量大于AMP_THD_POOL_MAX_NUM
。远程调用实现方式
Sunxi‑AMP 目前支持 1 种远程跨核调用实现方式:
- 参数指针传递:将远程跨核调用服务参数的指针或者数据值进行直接传递。如果参数是指针类型,则直接传递该指针;如果参数是数值,则直接传递该数值。参数指针传递比较适合参数结构体简单、核间数据传递量大的场景。其缺点是处理缓存一致性比较麻烦。
直接通过指针来传递参数及返回值,数据传递过程中可以免于拷贝,消耗内存较少,性能较高。
以
setConfig(struct config *data)
函数为例,在发起setConfig
远程调用时,会创建一个sunxi_amp_msg_args
结构体,会将setConfig
的参数依次设置到sunxi_amp_msg_args
的args
数组中,然后将sunxi_amp_msg
的data
字段设置为sunxi_amp_msg_args
结构体地址。通过msgbox
将sunxi_amp_msg
发送给另外的一个核后,另外一个核重新组装sunxi_map_msg
,即可拿到参数数据。Sunxi‑AMP 数据结构
消息结构
sunxi_amp_msg
结构体表示通过msgbox
传输的消息内容,Sunxi‑AMP 远程跨核调用框架基于sunxi_amp_msg
消息来完成各种功能。一个完整的sunxi_amp_msg
,包含以下字段,总共 12字节。typedef struct _sunxi_amp_msg_t { uint32_t rpcid: 16; // 远程调用 ID 值,高 8 位表示 service id,低 8 位表示 function id uint32_t prio: 5; // 发送端任务优先级 uint32_t type: 5; // 消息类型 uint32_t src: 3; // 源地址,即表示从哪个核发出的消息 uint32_t dst: 3; // 目的地址,即表示该消息发送到哪个核上 uint32_t data; // 消息数据 uint32_t flags; // 消息标识,当前设置为线程句柄,用于在远程调用堵塞等待时唤醒该线程 } sunxi_amp_msg;
其中远程调用的 ID 分为两个部分,高 8 位表示远程服务组 ID,低 8 位表示某个远程服务组中的
function ID
,也就是最多支持 256 个远程服务组,每个远程服务组最多支持 256 个远程调用。下面以 FSYS 文件系统服务为例,介绍rpcid
的组成及其计算方法。#define RPCCALL_FSYS(y) (RPCNO(RPCNO_FSYS, RPCHandler_FSYS_t, y) | (RPCSERVICE_FSYS_DIR << 29) | SELF_DIRECTION << 26)
RPCCALL_FSYS(y)
宏会自动计算rpcid
以及src
,dst
三个字段的值。RPCSERVICE_FSYS_DIR
表示FSYS
服务所在的核,它用来设置dst
字段。SELF_DIRECTION
表示当前所在的核,用于设置 src 字段。RPCNO(RPCNO_FSYS
,RPCHandler_FSYS_t, y)
用于计算rpcid
。其中,RPCNO_FSYS
计算出service ID
,放置于高 8 位,RPCNO
计算出function ID
,放置于低 8 位。
消息类型
MSG_SERIAL_FUNC_CALL // 序列化远程服务调用 MSG_SERIAL_FUNC_RET // 序列化远程服务返回值 MSG_SERIAL_FREE_BUFFER // 序列化远程服务释放内存 MSG_DIRECT_FUNC_CALL // 参数指针传递远程服务调用 MSG_DIRECT_FUNC_RET // 参数指针传递远程服务返回值
指针传递远程调用时的参数结构体
sunxi_amp_msg_args
用来表示使用指针传递远程调用时的参数信息,sunxi_amp_msg_args
结构体的地址会被设置到sunxi_amp_msg
的data
字段进行传递。typedef struct _sunxi_amp_msg_args_t { uint32_t args_num: 8; uint32_t reserved; uint32_t args[SUNXI_AMP_MAX_ARGS_NUM]; } sunxi_amp_msg_args;
远程调用服务的服务函数表
sunxi_amp_func_table
用来表示远程调用服务的服务函数表。args_num
表示该服务的参数个数,return_type
表示该服务的返回值类型,func
表示服务函数指针。typedef struct _sunxi_amp_func_table { uint32_t args_num: 8; uint32_t return_type: 8; sunxi_amp_service_func func; } sunxi_amp_func_table;
Sunxi-AMP 源码结构
├── amp_core.c # Sunxi‑AMP 核心处理代码,包含消息解析等 ├── amp_msgbox.c # Sunxi‑AMP msgbox 对接封装 ├── amp_service.c # 远程服务数组 ├── amp_stub.c # 触发远程服务的钩子函数 ├── amp_test.c # Sunxi‑AMP 单核测试文件 ├── amp_threadpool.c # Sunxi‑AMP 线程池 ├── amp_threadpool.h # 线程池头文件 ├── Kconfig # 配置文件 ├── Makefile ├── msgbuffer.c # 对接 erpc 实现的源码,已废弃 ├── msgbuffer.h ├── service # 已支持的远程调用服务 │ ├── audio # 音频远程调用服务 │ ├── bt # 蓝牙远程调用服务 │ ├── demo # erpc 测试用例 │ ├── flashc # flashc驱动远程调用服务 │ ├── fsys # 文件系统远程调用服务 │ ├── misc # 杂项远程调用服务,主要用于操作命令传递之类的场景 │ ├── net # wifi 网络远程调用服务 │ ├── pm # 休眠唤醒远程调用服务 │ ├── rpcconsole # 控制台远程调用服务 │ ├── rpdata # 远程数据获取调用服务,用于屏蔽复杂操作,使开发者仅关心数据获取及发送 │ └── tfm # 安全系统远程调用服务 ├── sunxi_amp.h ├── sunxi_amp_msg.h ├── sunxi_amp_status.h ├── sunxi_amp_table.h └── tests # 多核通信压力测试 └── test_stress.c
模块配置
M33 与 C906
System components ‑‑‑> aw components ‑‑‑> AMP Components Support ‑‑‑> [*] Tina RTOS AMP # 使能 Sunxi‑AMP 组件 [*] AMP Funcall Thread # 使能通过任务处理函数调用 [*] AMP Funcall ThreadPool # 使能线程池 [*] AMP Change Service Task Priority # 使能优先级传递
Sunxi-AMP 接口说明
头文件
#include <sunxi_amp.h>
远程调用服务函数结构体
typedef struct { sunxi_amp_func_table *RPCHandler_FSYS; sunxi_amp_func_table *RPCHandler_NET; sunxi_amp_func_table *RPCHandler_BT; sunxi_amp_func_table *RPCHandler_DEMO; sunxi_amp_func_table *RPCHandler_ARM_CONSOLE; sunxi_amp_func_table *RPCHandler_DSP_CONSOLE; sunxi_amp_func_table *RPCHandler_RV_CONSOLE; sunxi_amp_func_table *RPCHandler_PMOFM33; sunxi_amp_func_table *RPCHandler_PMOFRV; sunxi_amp_func_table *RPCHandler_PMOFDSP; sunxi_amp_func_table *RPCHandler_FLASHC; sunxi_amp_func_table *RPCHandler_M33_MISC; sunxi_amp_func_table *RPCHandler_RV_MISC; sunxi_amp_func_table *RPCHandler_DSP_MISC; sunxi_amp_func_table *RPCHandler_AUDIO; sunxi_amp_func_table *RPCHandler_RPDATA; sunxi_amp_func_table *RPCHandler_TFM; } RPCHandler_RPCT_t;
AMP 信息结构体
typedef struct _sunxi_amp_info_t { QueueHandle_t send_queue; /*send to remote processor */ QueueHandle_t recv_queue; /*receive from remote processor */ TaskHandle_t sendTask; /*send to remote processor */ TaskHandle_t recvTask; /*receive from remote processor */ struct msg_endpoint sedp_arm; struct msg_endpoint sedp_rv; struct msg_endpoint sedp_dsp; sunxi_amp_wait wait; QueueHandle_t amp_msg_heap_mutex; } sunxi_amp_info;
AMP 消息结构体
typedef struct _sunxi_amp_msg_t { uint32_t rpcid: 16; uint32_t prio: 5; uint32_t type: 5; uint32_t src: 3; uint32_t dst: 3; uint32_t data; uint32_t flags; } __attribute__((packed)) sunxi_amp_msg;
AMP操作结构体
typedef struct _sunxi_amp_msg_ops { sunxi_amp_msg_func send_to_queue; sunxi_amp_msg_func send_to_dev; sunxi_amp_msg_func receive_from_dev; sunxi_amp_dev_init init; } sunxi_amp_msg_ops;
AMP 消息类型枚举
enum MSG_TYPE { MSG_SERIAL_FUNC_CALL = 0, MSG_SERIAL_FUNC_RET, MSG_SERIAL_FREE_BUFFER, MSG_DIRECT_FUNC_CALL, MSG_DIRECT_FUNC_RET, MSG_TYPE_NUM, };
AMP 消息返回值枚举
enum FUNC_RETURN_TYPE { RET_NULL = 0, RET_POINTER, RET_NUMBER_32, RET_NUMBER_64, };
AMP 消息发送方向枚举
enum RPC_MSG_DIRECTION { RPC_MSG_DIR_UNKNOWN = 0, RPC_MSG_DIR_CM33 = 1, RPC_MSG_DIR_RV, RPC_MSG_DIR_DSP, };
AMP 消息参数
typedef struct _sunxi_amp_msg_args_t { uint32_t args_num: 8; uint32_t reserved; uint32_t args[SUNXI_AMP_MAX_ARGS_NUM]; } __attribute__((packed)) sunxi_amp_msg_args;
AMP 函数表结构体
typedef struct _sunxi_amp_func_table { uint32_t args_num: 8; uint32_t return_type: 8; sunxi_amp_service_func func; } sunxi_amp_func_table;
获取 AMP 系统信息
函数原型
sunxi_amp_info *get_amp_info(void);
参数:
- 无
返回值:
- sunxi_amp_info 结构体
AMP发送消息
函数原型
int hal_amp_msg_send(sunxi_amp_msg *msg);
参数:
- msg:消息结构体
返回值:
- 0:成功
- -1:失败
获取 AMP 操作
函数原型
sunxi_amp_msg_ops *get_msg_ops(void);
参数:
- 无
返回值:
- sunxi_amp_msg_ops:操作结构体
AMP接收消息
函数原型
int hal_amp_msg_recv(sunxi_amp_msg *msg);
参数:
- msg:消息结构体
返回值:
- 0:成功
- -1:失败
发起远程函数调用
函数原型
unsigned long func_stub(uint32_t id, int haveRet, int stub_args_num, void *stub_args[]);
参数:
- id : 远程服务函数的 ID 值
- haveRet :是否存在返回值,在实际使用中,为了保证函数调用的顺序,该值需为 1
- stub_args_num : 远程服务函数的参数个数
- stub_args : 远程服务函数的参数
返回值:
- 远程服务函数的返回值
申请 cacheline 对齐的内存
函数原型
void *amp_align_malloc(int size);
参数:
- size : 需要申请的内存大小
返回值:
- 申请的内存地址
释放 cacheline 对齐的内存
函数原型
void amp_align_free(void *ptr);
参数:
- ptr:需要释放的内存地址
返回值:
- 无
申请 AMP 内存
函数原型
void *amp_malloc(int size);
参数:
- size : 需要申请的内存大小
返回值:
- 申请的内存地址
释放 AMP 内存
函数原型
void amp_free(void *ptr);
参数:
- ptr:需要释放的内存地址
返回值:
- 无
Sunxi-AMP 使用范例
添加指针传递调用服务
添加指针传递远程调用服务的流程如下:
-
可参考
lichee/rtos‑components/aw/amp/service/fsys/
远程文件系统服务。在lichee/rtos-components/aw/amp/service/
下创建对应服务的文件夹fsys
以及对应的service
和stub
端源文件,远程文件系统服务中为fsys_ser.c
和fsys_stub.c
。可参考Makefile
和Kconfig
添加编译、配置新的远程调用服务 -
在
fsys_ser.c
中创建sunxi_amp_func_table
数组fsys_table
-
在
amp_service.c
中将fsys_table
添加到func_table
数组中 -
在
sunxi_amp_table.h
中添加文件系统远程服务组的结构体RPCHandler_FSYS_t
,需要注意的是,RPCHandler_FSYS_t
结构体的成员变量需要与sunxi_amp
-
在
sunxi_amp.h
的RPCHandler_RPCT_t
结构体定义中添加RPCHandler_FSYS
指针 -
在
sunxi_amp.h
中定义以下宏
#define RPCNO_FSYS RPCCALL_RPCNO(RPCHandler_FSYS) #define RPCSERVICE_FSYS_DIR (RPC_MSG_DIR_RV) // 此处需要根据实际部署远程文件系统服务的核而修改 #define RPCCALL_FSYS(y) (RPCNO(RPCNO_FSYS, RPCHandler_FSYS_t, y) | (RPCSERVICE_FSYS_DIR << 28) | SELF_DIRECTION << 24)
fsys_ser.c
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <statfs.h> #include <dirent.h> #include <unistd.h> #include <fcntl.h> #include "sunxi_amp.h" #include <hal_cache.h> extern int truncate (const char *__file, __off_t __length); extern int fstatfs (int __fildes, struct statfs *__buf); static int _open(const char *name, int nameSize, int flag) { int ret; hal_dcache_invalidate((unsigned long)name, nameSize); ret = open(name, flag); hal_dcache_invalidate((unsigned long)name, nameSize); return ret; } static int _close(int fd) { return close(fd); } static ssize_t _read(int fd, void *buffer, size_t size) { ssize_t ret = -1; ret = read(fd, buffer, size); hal_dcache_clean((unsigned long)buffer, size); return ret; } static ssize_t _write(int fd, void *buffer, size_t size) { ssize_t ret = -1; hal_dcache_invalidate((unsigned long)buffer, size); ret = write(fd, buffer, size); hal_dcache_clean((unsigned long)buffer, size); return ret; } static off_t _lseek(int fd, off_t offset, int whence) { return lseek(fd, offset, whence); } static int _stat(const char *path, int pathSize, struct stat *st) { int ret = -1; hal_dcache_invalidate((unsigned long)st, sizeof(*st)); hal_dcache_invalidate((unsigned long)path, pathSize); ret = stat(path, st); hal_dcache_clean((unsigned long)st, sizeof(*st)); return ret; } static int _fstat(int fd, struct stat *st) { int ret = -1; hal_dcache_invalidate((unsigned long)st, sizeof(*st)); ret = fstat(fd, st); hal_dcache_clean((unsigned long)st, sizeof(*st)); return ret; } static int _unlink(const char *path, int pathSize) { int ret = -1; hal_dcache_invalidate((unsigned long)path, pathSize); ret = unlink(path); hal_dcache_invalidate((unsigned long)path, pathSize); return ret; } static int _rename(const char *old, int oldSize, const char *new, int newSize) { int ret = -1; hal_dcache_invalidate((unsigned long)new, newSize); hal_dcache_invalidate((unsigned long)old, oldSize); ret = rename(old, new); hal_dcache_invalidate((unsigned long)old, oldSize); hal_dcache_invalidate((unsigned long)new, newSize); return ret; } static DIR *_opendir(const char *path, int pathSize) { DIR *ret = NULL; hal_dcache_invalidate((unsigned long)path, pathSize); ret = opendir(path); if (ret) hal_dcache_clean((unsigned long)ret, sizeof(*ret)); hal_dcache_invalidate((unsigned long)path, pathSize); return ret; } static struct dirent *_readdir(DIR *pdir) { struct dirent *ret = NULL; ret = readdir(pdir); if (ret) hal_dcache_clean((unsigned long)ret, sizeof(*ret)); return ret; } static int _closedir(DIR *pdir) { return closedir(pdir); } static int _mkdir(const char *name, int nameSize, mode_t mode) { int ret; hal_dcache_invalidate((unsigned long)name, nameSize); ret = mkdir(name, mode); hal_dcache_invalidate((unsigned long)name, nameSize); return ret; } static int _rmdir(const char *name, int nameSize) { int ret; hal_dcache_invalidate((unsigned long)name, nameSize); ret = rmdir(name); hal_dcache_invalidate((unsigned long)name, nameSize); return ret; } static int _access(const char *name, int nameSize, int amode) { int ret; hal_dcache_invalidate((unsigned long)name, nameSize); ret = access(name, amode); hal_dcache_invalidate((unsigned long)name, nameSize); return ret; } static int _truncate(const char *name, int nameSize, off_t length) { int ret; hal_dcache_invalidate((unsigned long)name, nameSize); ret = truncate(name, length); hal_dcache_invalidate((unsigned long)name, nameSize); return ret; } static int _statfs(const char *name, int nameSize, struct statfs *buf) { int ret = -1; hal_dcache_invalidate((unsigned long)name, nameSize); ret = statfs(name, buf); hal_dcache_clean((unsigned long)buf, sizeof(*buf)); return ret; } static int _fstatfs(int fd, struct statfs *buf) { int ret = -1; ret = fstatfs(fd, buf); hal_dcache_clean((unsigned long)buf, sizeof(*buf)); return ret; } static int _fsync(int fd) { return fsync(fd); } sunxi_amp_func_table fsys_table[] = { {.func = (void *)&_open, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_close, .args_num = 1, .return_type = RET_POINTER}, {.func = (void *)&_read, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_write, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_lseek, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_fstat, .args_num = 2, .return_type = RET_POINTER}, {.func = (void *)&_stat, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_unlink, .args_num = 2, .return_type = RET_POINTER}, {.func = (void *)&_rename, .args_num = 4, .return_type = RET_POINTER}, {.func = (void *)&_opendir, .args_num = 2, .return_type = RET_POINTER}, {.func = (void *)&_readdir, .args_num = 1, .return_type = RET_POINTER}, {.func = (void *)&_closedir, .args_num = 1, .return_type = RET_POINTER}, {.func = (void *)&_mkdir, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_rmdir, .args_num = 2, .return_type = RET_POINTER}, {.func = (void *)&_access, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_truncate, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_statfs, .args_num = 3, .return_type = RET_POINTER}, {.func = (void *)&_fstatfs, .args_num = 2, .return_type = RET_POINTER}, {.func = (void *)&_fsync, .args_num = 1, .return_type = RET_POINTER}, };
fsys_stub.c
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <reent.h> #include "sunxi_amp.h" #include <hal_cache.h> #ifndef _MAYBE_STATIC #include <statfs.h> #include <dirent.h> #else struct dirent { int d_ino; /*!< file number */ uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */ #define DT_UNKNOWN 0 #define DT_REG 1 #define DT_DIR 2 char d_name[256]; /*!< zero-terminated file name */ }; typedef __uint64_t __fsblkcnt_t; struct statfs { uint32_t f_type; uint32_t f_bsize; __fsblkcnt_t f_blocks; __fsblkcnt_t f_bfree; __fsblkcnt_t f_bavail; __fsfilcnt_t f_files; __fsfilcnt_t f_ffree; }; typedef struct { uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */ uint16_t dd_rsv; /*!< field reserved for future extension */ /* remaining fields are defined by VFS implementation */ } DIR; #endif MAYBE_STATIC int open(const char *name, int flag, ...) { void *args[3] = {0}; int ret = -1; int nameSize = strlen(name) + 1; char *align_name = amp_align_malloc(nameSize); if (!align_name) { return -1; } memset(align_name, 0, nameSize); memcpy(align_name, name, nameSize); args[0] = align_name; args[1] = (void *)(unsigned long)nameSize; args[2] = (void *)(unsigned long)flag; hal_dcache_clean((unsigned long)align_name, nameSize); ret = func_stub(RPCCALL_FSYS(open), 1, ARRAY_SIZE(args), args); amp_align_free(align_name); return ret; } MAYBE_STATIC int close(int fd) { void *args[1] = {0}; args[0] = (void *)(unsigned long)fd; return func_stub(RPCCALL_FSYS(close), 1, ARRAY_SIZE(args), args); } MAYBE_STATIC ssize_t read(int fd, void *buffer, size_t size) { void *args[3] = {0}; ssize_t ret = -1; args[0] = (void *)(unsigned long)fd; args[1] = buffer; args[2] = (void *)(unsigned long)size; if (fd < 3) { return _read_r(_REENT, fd, buffer, size); } hal_dcache_invalidate((unsigned long)buffer, size); ret = func_stub(RPCCALL_FSYS(read), 1, ARRAY_SIZE(args), args); hal_dcache_invalidate((unsigned long)buffer, size); return ret; } MAYBE_STATIC ssize_t write(int fd, void *buffer, size_t size) { void *args[3] = {0}; args[0] = (void *)(unsigned long)fd; args[1] = buffer; args[2] = (void *)(unsigned long)size; if (fd < 3) { return _write_r(_REENT, fd, buffer, size); } hal_dcache_clean((unsigned long)buffer, size); return func_stub(RPCCALL_FSYS(write), 1, ARRAY_SIZE(args), args); } MAYBE_STATIC off_t lseek(int fd, off_t offset, int whence) { void *args[3] = {0}; args[0] = (void *)(unsigned long)fd; args[1] = (void *)(unsigned long)offset; args[2] = (void *)(unsigned long)whence; return func_stub(RPCCALL_FSYS(lseek), 1, ARRAY_SIZE(args), args); } #ifndef _MAYBE_STATIC MAYBE_STATIC int fstat(int fd, struct stat *st) { void *args[2] = {0}; int ret = -1; struct stat *st_align = amp_align_malloc(sizeof(*st)); if (!st_align) { return -1; } memset(st_align, 0, sizeof(*st)); args[0] = (void *)(unsigned long)fd; args[1] = st_align; hal_dcache_clean((unsigned long)st_align, sizeof(struct stat)); ret = func_stub(RPCCALL_FSYS(fstat), 1, ARRAY_SIZE(args), args); hal_dcache_invalidate((unsigned long)st_align, sizeof(struct stat)); memcpy(st, st_align, sizeof(struct stat)); amp_align_free(st_align); return ret; } MAYBE_STATIC int stat(const char *path, struct stat *st) { void *args[3] = {0}; int ret = -1; int pathSize = strlen(path) + 1; char *align_path = amp_align_malloc(pathSize); if (!align_path) { return -1; } memset(align_path, 0, pathSize); memcpy(align_path, path, pathSize); struct stat *st_align = amp_align_malloc(sizeof(*st)); if (!st_align) { amp_align_free(align_path); return -1; } memset(st_align, 0, sizeof(*st)); args[0] = align_path; args[1] = (void *)(unsigned long)pathSize; args[2] = st_align; hal_dcache_clean((unsigned long)align_path, pathSize); hal_dcache_clean((unsigned long)st_align, sizeof(struct stat)); ret = func_stub(RPCCALL_FSYS(stat), 1, ARRAY_SIZE(args), args); hal_dcache_invalidate((unsigned long)st_align, sizeof(struct stat)); memcpy(st, st_align, sizeof(struct stat)); amp_align_free(st_align); amp_align_free(align_path); return ret; } #endif MAYBE_STATIC int unlink(const char *path) { void *args[2] = {0}; int ret = -1; int pathSize = strlen(path) + 1; char *align_path = amp_align_malloc(pathSize); if (!align_path) { return -1; } memset(align_path, 0, pathSize); memcpy(align_path, path, pathSize); args[0] = align_path; args[1] = (void *)(unsigned long)pathSize; hal_dcache_clean((unsigned long)align_path, pathSize); ret = func_stub(RPCCALL_FSYS(unlink), 1, ARRAY_SIZE(args), args); amp_align_free(align_path); return ret; } #ifndef _MAYBE_STATIC MAYBE_STATIC int rename(const char *old, const char *new) { void *args[4] = {0}; int ret = -1; int oldSize = strlen(old) + 1; int newSize = strlen(new) + 1; char *align_old = amp_align_malloc(oldSize); if (!align_old) { return -1; } char *align_new = amp_align_malloc(newSize); if (!align_new) { amp_align_free(align_old); return -1; } memset(align_new, 0, newSize); memcpy(align_new, new, newSize); memset(align_old, 0, oldSize); memcpy(align_old, old, oldSize); args[0] = align_old; args[1] = (void *)(unsigned long)oldSize; args[2] = align_new; args[3] = (void *)(unsigned long)newSize; hal_dcache_clean((unsigned long)align_old, oldSize); hal_dcache_clean((unsigned long)align_new, newSize); ret = func_stub(RPCCALL_FSYS(rename), 1, ARRAY_SIZE(args), args); amp_align_free(align_old); amp_align_free(align_new); return ret; } #endif MAYBE_STATIC DIR *opendir(const char *path) { DIR *ret = NULL; void *args[2] = {0}; int pathSize = strlen(path) + 1; char *align_path = amp_align_malloc(pathSize); if (!align_path) { return NULL; } memset(align_path, 0, pathSize); memcpy(align_path, path, pathSize); args[0] = align_path; args[1] = (void *)(unsigned long)pathSize; hal_dcache_clean((unsigned long)align_path, pathSize); ret = (void *)func_stub(RPCCALL_FSYS(opendir), 1, ARRAY_SIZE(args), args); if (ret) { hal_dcache_invalidate((unsigned long)ret, sizeof(*ret)); } amp_align_free(align_path); return ret; } MAYBE_STATIC struct dirent *readdir(DIR *pdir) { struct dirent *ent = NULL; void *args[1] = {0}; args[0] = pdir; hal_dcache_clean((unsigned long)pdir, sizeof(*pdir)); ent = (void *)func_stub(RPCCALL_FSYS(readdir), 1, ARRAY_SIZE(args), args); if (ent) { hal_dcache_invalidate((unsigned long)ent, sizeof(*ent)); } return ent; } MAYBE_STATIC int closedir(DIR *pdir) { void *args[1] = {0}; args[0] = pdir; hal_dcache_clean((unsigned long)pdir, sizeof(*pdir)); return func_stub(RPCCALL_FSYS(closedir), 1, ARRAY_SIZE(args), args); } #ifndef _MAYBE_STATIC MAYBE_STATIC int mkdir(const char *name, mode_t mode) { void *args[3] = {0}; int ret = -1; int nameSize = strlen(name) + 1; char *align_name = amp_align_malloc(nameSize); if (!align_name) { return -1; } memset(align_name, 0, nameSize); memcpy(align_name, name, nameSize); args[0] = align_name; args[1] = (void *)(unsigned long)nameSize; args[2] = (void *)(unsigned long)mode; hal_dcache_clean((unsigned long)align_name, nameSize); ret = func_stub(RPCCALL_FSYS(mkdir), 1, ARRAY_SIZE(args), args); amp_align_free(align_name); return ret; } #endif MAYBE_STATIC int rmdir(const char *name) { void *args[2] = {0}; int ret = -1; int nameSize = strlen(name) + 1; char *align_name = amp_align_malloc(nameSize); if (!align_name) { return -1; } memset(align_name, 0, nameSize); memcpy(align_name, name, nameSize); args[0] = align_name; args[1] = (void *)(unsigned long)nameSize; hal_dcache_clean((unsigned long)align_name, nameSize); ret = func_stub(RPCCALL_FSYS(rmdir), 1, ARRAY_SIZE(args), args); amp_align_free(align_name); return ret; } MAYBE_STATIC int access(const char *name, int amode) { void *args[3] = {0}; int ret = -1; int nameSize = strlen(name) + 1; char *align_name = amp_align_malloc(nameSize); if (!align_name) { return -1; } memset(align_name, 0, nameSize); memcpy(align_name, name, nameSize); args[0] = align_name; args[1] = (void *)(unsigned long)nameSize; args[2] = (void *)(unsigned long)amode; hal_dcache_clean((unsigned long)align_name, nameSize); ret = func_stub(RPCCALL_FSYS(access), 1, ARRAY_SIZE(args), args); amp_align_free(align_name); return ret; } MAYBE_STATIC int truncate(const char *name, off_t length) { void *args[3] = {0}; int ret = -1; int nameSize = strlen(name) + 1; char *align_name = amp_align_malloc(nameSize); if (!align_name) { return -1; } memset(align_name, 0, nameSize); memcpy(align_name, name, nameSize); args[0] = align_name; args[1] = (void *)(unsigned long)nameSize; args[2] = (void *)(unsigned long)length; hal_dcache_clean((unsigned long)align_name, nameSize); ret = func_stub(RPCCALL_FSYS(truncate), 1, ARRAY_SIZE(args), args); amp_align_free(align_name); return ret; } MAYBE_STATIC int statfs(const char *path, struct statfs *buf) { void *args[3] = {0}; int ret = -1; int pathSize = strlen(path) + 1; char *align_path = amp_align_malloc(pathSize); if (!align_path) { return -1; } char *align_statfs = amp_align_malloc(sizeof(struct statfs)); if (!align_statfs) { amp_align_free(align_statfs); return -1; } memset(align_path, 0, pathSize); memcpy(align_path, path, pathSize); memset(align_statfs, 0, sizeof(struct statfs)); args[0] = align_path; args[1] = (void *)(unsigned long)pathSize; args[2] = align_statfs; hal_dcache_clean((unsigned long)align_path, pathSize); hal_dcache_clean((unsigned long)align_statfs, sizeof(struct statfs)); ret = func_stub(RPCCALL_FSYS(statfs), 1, ARRAY_SIZE(args), args); hal_dcache_invalidate((unsigned long)align_statfs, sizeof(struct statfs)); memcpy(buf, align_statfs, sizeof(struct statfs)); amp_align_free(align_path); amp_align_free(align_statfs); return ret; } MAYBE_STATIC int fstatfs(int fd, struct statfs *buf) { void *args[2] = {0}; int ret = -1; char *align_statfs = amp_align_malloc(sizeof(struct statfs)); if (!align_statfs) { return -1; } args[0] = (void *)(unsigned long)fd; args[1] = align_statfs; hal_dcache_clean((unsigned long)align_statfs, sizeof(struct statfs)); ret = func_stub(RPCCALL_FSYS(fstatfs), 1, ARRAY_SIZE(args), args); hal_dcache_invalidate((unsigned long)align_statfs, sizeof(struct statfs)); memcpy(buf, align_statfs, sizeof(struct statfs)); amp_align_free(align_statfs); return ret; } MAYBE_STATIC int fsync(int fd) { void *args[1] = {0}; args[0] = (void *)(unsigned long)fd; return func_stub(RPCCALL_FSYS(fsync), 1, ARRAY_SIZE(args), args); }
RPCLI 跨核终端实现
rpcconsole_stub.c
#include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <console.h> #include <sunxi_amp.h> #include <hal_cache.h> #define SEND_TO_32BIT (1) #define SEND_TO_64BIT (2) struct rpcconsole_arg_t { uint32_t argc; uint32_t flag; uint64_t argv_64[SUNXI_AMP_SH_MAX_CMD_ARGS]; uint32_t argv_32[SUNXI_AMP_SH_MAX_CMD_ARGS]; union { char *argv_cmd; uint64_t argv_cmd_data; }; }; static int cmd_rpcconsole(int argc, char **argv) { int ret = -1; int i; if (argc <= 2) { printf("Usage: rpccli [arm|dsp|rv] commandname [arg0 ...] \n"); return -1; } if (argc > SUNXI_AMP_SH_MAX_CMD_ARGS) { printf("maximum number of arg:%d\n", SUNXI_AMP_SH_MAX_CMD_ARGS); return -1; } struct rpcconsole_arg_t *rpc_arg = amp_align_malloc(sizeof(struct rpcconsole_arg_t)); if (rpc_arg == NULL) { printf("Alloc memory failed!\n"); return -1; } memset(rpc_arg, 0, sizeof(struct rpcconsole_arg_t)); char *rpc_args_cmd = amp_align_malloc(SH_MAX_CMD_LEN); if (rpc_args_cmd == NULL) { printf("Alloc memory failed!\n"); amp_align_free(rpc_arg); return -1; } memset(rpc_args_cmd, 0, SH_MAX_CMD_LEN); rpc_arg->argv_cmd = rpc_args_cmd; rpc_arg->argc = argc - 2; for (i = 2; i < argc; i++) { rpc_arg->argv_32[i - 2] = (uint32_t)(unsigned long)rpc_args_cmd; rpc_arg->argv_64[i - 2] = (uint32_t)(unsigned long)rpc_args_cmd; memcpy(rpc_args_cmd, argv[i], strlen(argv[i])); rpc_args_cmd += ALIGN_UP((strlen(argv[i]) + 1), 4); } void *args[1] = {0}; args[0] = rpc_arg; if (!strcmp("arm", argv[1])) { rpc_arg->flag = SEND_TO_32BIT; hal_dcache_clean((unsigned long)rpc_arg, sizeof(*rpc_arg)); hal_dcache_clean((unsigned long)rpc_arg->argv_cmd, SH_MAX_CMD_LEN); ret = func_stub(RPCCALL_ARM_CONSOLE(exe_cmd), 1, 1, (void *)&args); } else if (!strcmp("dsp", argv[1])) { rpc_arg->flag = SEND_TO_32BIT; hal_dcache_clean((unsigned long)rpc_arg, sizeof(*rpc_arg)); hal_dcache_clean((unsigned long)rpc_arg->argv_cmd, SH_MAX_CMD_LEN); ret = func_stub(RPCCALL_DSP_CONSOLE(exe_cmd), 1, 1, (void *)&args); } else if (!strcmp("rv", argv[1])) { rpc_arg->flag = SEND_TO_64BIT; hal_dcache_clean((unsigned long)rpc_arg, sizeof(*rpc_arg)); hal_dcache_clean((unsigned long)rpc_arg->argv_cmd, SH_MAX_CMD_LEN); ret = func_stub(RPCCALL_RV_CONSOLE(exe_cmd), 1, 1, (void *)&args); } amp_align_free(rpc_arg->argv_cmd); amp_align_free(rpc_arg); return ret; } FINSH_FUNCTION_EXPORT_CMD(cmd_rpcconsole, rpccli, exe);
rpcconsole_ser.c
#include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <console.h> #include <barrier.h> #include <sunxi_amp.h> #include <hal_cache.h> #define SEND_TO_32BIT (1) #define SEND_TO_64BIT (2) struct rpcconsole_arg_t { uint32_t argc; uint32_t flag; uint64_t argv_64[SUNXI_AMP_SH_MAX_CMD_ARGS]; uint32_t argv_32[SUNXI_AMP_SH_MAX_CMD_ARGS]; union { char *argv_cmd; uint64_t argv_cmd_data; }; }; static int _execute_cmd(struct rpcconsole_arg_t *rpc_arg) { char *command_name; struct finsh_syscall *call; int ret = -1; hal_dcache_invalidate((unsigned long)rpc_arg, (unsigned long)sizeof(*rpc_arg)); #ifndef CONFIG_ARCH_DSP dsb(); isb(); #endif hal_dcache_invalidate((unsigned long)rpc_arg->argv_cmd, SH_MAX_CMD_LEN); if (rpc_arg->flag == SEND_TO_32BIT) { command_name = (char *)(unsigned long)(rpc_arg->argv_32[0]); } else { command_name = (char *)(unsigned long)(rpc_arg->argv_64[0]); } call = finsh_syscall_lookup(command_name); if (call == NULL || call->func == NULL) { printf("The command(%s) no exist !\n", command_name); return -1; } if (rpc_arg->flag == SEND_TO_32BIT) { ret = call->func(rpc_arg->argc, (char **)(unsigned long)rpc_arg->argv_32); } else { ret = call->func(rpc_arg->argc, (char **)(unsigned long)rpc_arg->argv_64); } hal_dcache_invalidate((unsigned long)rpc_arg, (unsigned long)sizeof(*rpc_arg)); hal_dcache_invalidate((unsigned long)rpc_arg->argv_cmd, SH_MAX_CMD_LEN); return ret; } sunxi_amp_func_table console_table[] = { {.func = (void *)&_execute_cmd, .args_num = 1, .return_type = RET_POINTER}, };
Sunxi‑AMP 注意事项
-
注意多核通信时的数据
Buffer
的起始地址和长度,起始地址要求按照系统中最大的CacheLine(64B)
对齐,长度要求数据Buffer
独占CacheLine
,不允许和其他数据共享CacheLine
。 -
如果在远程调用过程中,需要将内存数据传递给另外的 CPU 处理器,在执行远程函数调用前需要调用
hal_dcache_clean
将调用核的CPU Dcache
数据刷回内存介质,可以参考lichee/rtos‑components/aw/amp/service/flashc/flashc_stub.c
中的nor_write
函数;如果需要从另外的处理器获取数据,在执行远程函数调用后,需要执行hal_dcache_invalidate
将本地CPU Dcache
无效掉,以避免获取旧的历史数据,可以参考lichee/rtos‑components/aw/amp/service/flashc/flashc_stub.c
中的nor_read
函数。 -
注意
Cache
的Dirty
位。桩子函数 (stub
端) 需要先将传给服务函数 (service
端) 的buffer
所对应的cacheline
无效,重点在于清除dirty
位,否则在cachelien
颠簸换出的过程中,可能会导致运行桩子函数所在核将buffer
对应的脏cacheline
换出并回写,覆盖另外一个核刚写的数据。服务函数 (service
端) 需要将不再使用的buffer
对应的cacheline
无效掉或者刷回内存,重点在于清除dirty
位,以预防在cahcline
颠簸换出的过程中将buffer
对应的脏cacheline
中的数据回写,此时极有可能篡改另外一个核的数据。举例分析:在
lichee/rtos‑components/aw/amp/service/flashc/flashc_stub.c
中的nor_read
函数中,核在调用func_stub(RPCCALL_FLASHC(nor_read), 1, ARRAY_SIZE(args), args);
之前,需要先调用hal_dcache_clean_invalidate((unsigned long)buffer, size);
接口清除buffer
对应cacheline
的dirty
位,否则在程序运行中,可能会导致 M33 核的服务函数刚将数据刷回内存,C906 核又因为cacheline
的换出把buffer
的数据给修改了。同理,在lichee/rtos‑components/aw/amp/service/flashc/flashc_ser.c
的_nor_write
函数中,M33 核在调用完nor_write
之后,需要再一次调用hal_dcache_invalidate((unsigned long)buf, size);
,因为nor_write
中可能存在修改buffer
数据的可能性,这样的话buffer
对应的cacheline
在 M33核上就是脏的,在 M33 核运行的过程中,极有可能会将脏数据写入内存。而此时,buffer
在C906 上可能已被申请释放挪为他用,M33 核将脏数据写回内存就直接篡改了 C906 上运行的正常数据。 -
当前支持的最大参数个数为 8 个。
-
64bit 数据在传输过程中一律被截断为 32bit 数据。
-
不可在中断函数中使用远程调用。
-
在
sunxi_amp.h
中添加#define AMP_DEBUG
,则会在关键节点中使能组件的调试信息输出,用于分析远程函数调用错误等问题。 -
在多个核之间传递同一个结构体时,该结构内部尽量不要使用指针类型,主要的原因在于因三个核位宽不一致,对指针类型的长度理解存在差异,那么就会导致结构体内的数据布局存在差异。当两个不同位宽的 CPU 通过同一个结构体指针指向的地址取值时,就会导致取值错误。如以下例子:
struct test_t { char *ptr; int offset; }; struct test_t a; ptr = &a; // ptr 为指向 struct test_t 结构体的指针,假设其地址为 0x4000000; int offset = ptr‑>offset // 在 32bit 系统会去访问 0x4000004; int offset = ptr‑>offset // 在 64bit 系统会去访问 0x4000008;
RPDATA 核间通讯
为了不同核之间的通信交互,基于硬件 msgbox 功能,软件上提供了 AMP 通信机制,而在该机制上我们提供了 rpdata 接口,让用户可以较容易实现核间通信。
rpdata 接口介绍
创建 rpdata
rpdata_t *rpdata_create(int dir, const char *type, const char *name, size_t buf_len)
参数:
- dir: 指定远端核。 1:M33; 2:RV; 3:DSP
- type: 类型,传输数据类型,字符串可自定义,类型和名称一起构成唯一标识
- name: 名称,传输数据名称,字符串可自定义,类型和名称一起构成唯一标识
- buf_len: buffer大小,该buffer用于临时保存跨核传输数据
返回值:
- 成功则返回rpdata句柄,失败返回NULL。
type 和 name 用于标识唯一的传输通道, 可自行设定,长度不超过 16 个字节即可。可以通过 rpd ‑l 命令查看当前已经申请的 rpdata 以及对应的状态。rpdata_create 函数不会阻塞。在创建的时候,可以直接调用。
连接 rpdata
rpdata_t *rpdata_connect(int dir, const char *type, const char *name)
参数:
- dir: 指定远端核。 1:M33; 2:RV; 3:DSP
- type: 类型,传输数据类型,字符串可自定义,类型和名称一起构成唯一标识
- name: 名称,传输数据名称,字符串可自定义,类型和名称一起构成唯一标识
返回值:
- 成功则返回rpdata句柄,失败返回NULL。
rpdata_create 和 rpdata_connect 作用是类似的,但是前者会创建用于传输数据的 buffer,后者不会。因此两核之间通信,必须分别使用这两个接口,而不能共同使用 rpdata_create 或者 rpdata_connect。rpdata_connect 函数会阻塞。如果远核没有调用 rpdata_create,那么就会一直阻塞线程。注意使用 rpdata_connect,如果
需要等待远端线程,请确认该线程是否可以阻塞,不行的话,需要使用线程单独等待。得到 buffer 的值
void *rpdata_buffer_addr(rpdata_t *rpd)
参数:
- rpd: rpdata句柄
返回值:
- buff的值
该 buffer 地址可用于保存要传输交互的数据
判断 rpdata 是否为连接状态
int rpdata_is_connect(rpdata_t *rpd)
参数:
- rpd: rpdata句柄
返回值:
- 返回0表示为连接状态,非0表示非连接状态
等待 rpdata 变成连接状态
int rpdata_wait_connect(rpdata_t *rpd)
参数:
- rpd: rpdata句柄
返回值:
- 成功则返回0,失败返回非0。
需要判断等待 rpdata 为连接状态后,才可以开始数据传输。
跨核数据处理
用于发送数据到远端核,并且远端核把处理后数据填充到 buffer
int rpdata_process(rpdata_t *rpd, unsigned int offset, unsigned int data_len)
参数:
-
rpd: rpdata句柄
-
offset: 相对于buffer的偏移
-
data_len: 数据量,字节
返回值:
- 成功则返回0,失败返回非0。
处理后的结果会存于 buffer 中,即 rpdata_buffer_addr 得到的地址。
跨核数据发送
仅用于发送数据到远端核
int rpdata_send(rpdata_t *rpd, unsigned int offset, unsigned int data_len)
参数:
-
rpd: rpdata句柄
-
offset: 相对于buffer的偏移
-
data_len: 数据量,字节
返回值:
- 成功则返回0,失败返回非0。
rpdata_send 只是把 buffer 中数据发送到远端核,而 rpdata_process 会得到处理后数据
接受跨核传输的数据
int rpdata_recv(rpdata_t *rpd, unsigned int *offset, unsigned int *data_len, int timeout_ms)
参数:
-
rpd: rpdata句柄
-
offset: 得到相对于buffer的偏移
-
data_len: 得到数据量,字节
-
timeout_ms: 等待超时时间
返回值:
- 成功则返回0,失败返回非0。
使用该接口,需要增加 ringbuffer 模式,否则有可能会出现数据覆盖的现象。
设置数据接受回调
int rpdata_set_recv_cb(rpdata_t *rpd, struct rpdata_cbs *cbs)
参数:
- rpd: rpdata句柄
- cbs: 回到函数集,当前仅支持recv_cb,即接受回调
返回值:
- 成功则返回0,失败返回非0。
数据接受回调函数
int (recv_cb_t)(rpdata_t rpd, void *data, int data_len)
注意在该回调用把数据处理好,或者拷贝出来,否则有可能会被下次数据传输覆盖
销毁 rpdata
int rpdata_destroy(rpdata_t *rpd)
参数:
- rpd: rpdata句柄
返回值:
- 成功则返回0,失败返回非0。
只有两核都调用 rpdata_destroy 才会真正的销毁,但对调用顺序不做要求。
rpdata 使用流程
sequenceDiagram M33->>+M33: rpdata_create M33->>-RV: notify RV->>+RV: rpdata_create RV->>-M33: ack RV->>RV: rpdata_set_recv_cb loop 多次发送 Note left of M33: rpdata_send M33->>RV: send data Note right of RV: 触发 callback end RV->>M33: notify RV->>RV: rpdata_destory M33->>RV: wait destory
- rpdata_create 和 rpdate_connect 的调用顺序不做要求,谁先执行都可以;但是 rpdata_data 是会创建用于数据交互的 buffer
- 接受数据端可以选择使用 rpdata_set_recv_cb 设置回调来处理数据,或者调用 rpdata_recv 阻塞等待接受互数据。
- 两个核的 rpdata_destroy 调用顺序也是不做要求的。
rpdata 示例
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <FreeRTOS.h> #include <task.h> #include <queue.h> #include <rpdata.h> #include <console.h> #include <hal_time.h> #include <hal_mem.h> #include <hal_thread.h> #include <md5.h> //#define RECV_CALLBACK_TEST /* print md5 check result */ static int g_rpd_verbose = 0; #define configAPPLICATION_RPDATA_DEMO_PRIORITY \ (configMAX_PRIORITIES > 20 ? configMAX_PRIORITIES - 8 : configMAX_PRIORITIES - 3) static void rpdata_demo_usage(void) { printf("Usgae: rpdata_demo [option]\n"); printf("-h, rpdata help\n"); printf("-m, mode, 0-send; 1-recv; 2-recv+send\n"); printf("-t, type, type name\n"); printf("-n, name, id name\n"); printf(" (type + name) specify unique data xfer\n"); printf("-c, string, send string\n"); printf("-i, single test, do send or recv test once\n"); printf("-d, dir, remote processor, 1-cm33;2-c906;3-dsp\n"); printf("-s, src dir, valid in mode 2\n"); printf("\n"); printf("RV -> DSP\n"); printf("rpdata_demo -m 0 -d 3 -t RVtoDSP -n RVsendDSPrecv\n"); printf("rpccli dsp rpdata_demo -m 1 -d 2 -t RVtoDSP -n RVsendDSPrecv\n"); printf("\n"); printf("RV -> M33\n"); printf("rpdata_demo -m 0 -d 1 -t RVtoM33 -n RVsendM33recv\n"); printf("rpccli arm rpdata_demo -m 1 -d 2 -t RVtoM33 -n RVsendM33recv\n"); printf("\n"); printf("RV <- M33\n"); printf("rpdata_demo -m 1 -d 1 -t M33toRV -n RVrecvM33send\n"); printf("rpccli arm rpdata_demo -m 0 -d 2 -t M33toRV -n RVrecvM33send\n"); printf("\n"); printf("RV -> DSP -> M33\n"); printf("rpccli dsp rpdata_demo -d 2 -t RVtoDSP -n DSPrecvRVsend -s 1 -t DSPtoM33 -n M33recvDSPsend\n"); printf("rpccli arm rpdata_demo -m 1 -d 3 -t DSPtoM33 -n M33recvDSPsend\n"); printf("rpdata_demo -m 0 -d 3 -t RVtoDSP -n DSPrecvRVsend\n"); printf("\n"); printf("RV -> DSP -> M33 -> DSP -> RV\n"); printf("rpccli dsp rpdata_demo -d 2 -t RD -n rs -s 1 -t DM -n ds\n"); printf("rpccli arm rpdata_demo -d 3 -t DM -n ds -s 3 -t MD -n ms\n"); printf("rpccli dsp rpdata_demo -d 1 -t MD -n ms -s 2 -t DR -n ds\n"); printf("rpdata_demo -m 1 -d 3 -t DR -n ds\n"); printf("rpdata_demo -m 0 -d 3 -t RD -n rs\n"); printf("\n"); printf("RV->M33->RV process(M33 do aec process: input mic+ref, and output aec data to RV)\n"); printf("rpccli arm rpdata_demo -p -m 1 -d 2 -t ALGO -n AEC\n"); printf("rpdata_demo -p -m 0 -d 1 -t ALGO -n AEC\n"); printf("\n"); } struct rpdata_arg_test { char type[32]; char name[32]; int dir; char stype[32]; char sname[32]; int sdir; }; static int do_rpdata_send_test(struct rpdata_arg_test *targ, void *data, uint32_t len) { rpdata_t *rpd; void *buffer; int ret = -1; printf("Cteate %s:%s.\n", targ->type, targ->name); rpd = rpdata_create(targ->dir, targ->type, targ->name, len); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); return -1; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif buffer = rpdata_buffer_addr(rpd); if (!buffer) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } memcpy(buffer, data, len); rpdata_wait_connect(rpd); ret = rpdata_send(rpd, 0, len); if (ret != 0) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } exit: if (rpd) rpdata_destroy(rpd); return ret; } static int do_rpdata_recv_test(struct rpdata_arg_test *targ, void *data, uint32_t len) { rpdata_t *rpd; char *rx_buf; int ret = -1; rx_buf = hal_malloc(len + 1); if (rx_buf == NULL) { printf("[%s] line:%d alloc rx buffer fialed.\n", __func__, __LINE__); return -1; } printf("connect to %s:%s.\n", targ->type, targ->name); rpd = rpdata_connect(targ->dir, targ->type, targ->name); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); return -1; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif while (1) { ret = rpdata_recv(rpd, rx_buf, len, 10000); if (ret <= 0) { printf("rpdata recv timeout \n"); goto exit; } rx_buf[ret] = 0; printf("%s, len:%d\n", rx_buf, ret); if (!memcmp("quit", rx_buf, 4)) break; } exit: if (rx_buf) hal_free(rx_buf); if (rpd) rpdata_destroy(rpd); return ret; } static void data_fill(void *buffer, uint32_t len) { int i; int data_len = len - 16; memset(buffer, 0, len); for (i = 0; i < len;) { *(int *)(buffer + i) = rand(); i += sizeof(int); } md5(buffer, data_len, buffer + data_len); } static int data_check(void *buffer, uint32_t len) { unsigned char res[16]; int data_len = len - 16; md5(buffer, data_len, res); if (!memcmp(buffer + data_len, res, 16)) return 0; return -1; } static void rpdata_auto_send(void *arg) { struct rpdata_arg_test targ; rpdata_t *rpd; void *buffer; int ret = -1; uint32_t len = 512; memcpy(&targ, arg, sizeof(struct rpdata_arg_test)); printf("dir:%d, type:%s, name:%s\n", targ.dir, targ.type, targ.name); rpd = rpdata_create(targ.dir, targ.type, targ.name, 512); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif buffer = rpdata_buffer_addr(rpd); if (!buffer) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } rpdata_wait_connect(rpd); while (1) { data_fill(buffer, len); ret = rpdata_send(rpd, 0, len); if (ret != 0) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } hal_msleep(10); } exit: if (rpd) rpdata_destroy(rpd); printf("rpdata auto send test finish\n"); vTaskDelete(NULL); } static int rpd_demo_recv_cb(rpdata_t *rpd, void *data, uint32_t data_len) { static int count = 0; if (data_check(data, data_len) < 0) { printf("check md5 failed\n"); } if (!g_rpd_verbose) return 0; if (count++ % g_rpd_verbose == 0) printf("check md5 ok(print interval %d)\n", g_rpd_verbose); return 0; } struct rpdata_cbs rpd_demo_cbs = { .recv_cb = rpd_demo_recv_cb, }; static int rpd_demo_process_cb(rpdata_t *rpd, void *buffer, uint32_t data_len) { void *mic_data, *ref_data, *aec_data; uint32_t mic_offset = 128; uint32_t ref_offset = 128 + 640; uint32_t aec_offset = 128 + 640 + 320; uint32_t total_len = 1408; /* aec process * input: * params: * simulator: 128bytes * mic data: 2ch+16K+16bit, 10ms, 160*4=640 * ref data: 1ch+16K+16bit, 10ms, 160*2=320 * outout: * aec data: 1ch+16K+16bit, 10ms, 160*2=320 * * total= 128 + 640 + 320 + 320 = 1408 * 1408 is cacheline lenght */ mic_data = buffer + mic_offset; ref_data = buffer + ref_offset; aec_data = buffer + aec_offset; if (data_len != total_len) { printf("expected len:%d but:%d\n", total_len, data_len); return -1; } if (data_check(mic_data, ref_data - mic_data) < 0) { printf("check mic data md5 failed\n"); } if (data_check(ref_data, aec_data - ref_data) < 0) { printf("check ref data md5 failed\n"); } data_fill(aec_data, total_len - aec_offset); return 0; } struct rpdata_cbs rpd_demo_process_cbs = { .recv_cb = rpd_demo_process_cb, }; static int rpd_demo_recv_and_send_cb(rpdata_t *rpd, void *data, uint32_t data_len) { rpdata_t *rpd_send = NULL; void *buffer; int ret; if (data_check(data, data_len) < 0) { printf("[%s] line:%d check md5 failed\n", __func__, __LINE__); } rpd_send = rpdata_get_private_data(rpd); if (!rpd_send) return -1; buffer = rpdata_buffer_addr(rpd_send); memcpy(buffer, data, data_len); ret = rpdata_send(rpd_send, 0, data_len); if (ret != 0) { printf("rpdata_send failed\n"); return ret; } return 0; } struct rpdata_cbs rpd_demo_rs_cbs = { .recv_cb = rpd_demo_recv_and_send_cb, }; static void rpdata_auto_recv(void *arg) { struct rpdata_arg_test targ; rpdata_t *rpd; void *rx_buf = NULL; int len, ret; memcpy(&targ, arg, sizeof(struct rpdata_arg_test)); printf("dir:%d, type:%s, name:%s\n", targ.dir, targ.type, targ.name); rpd = rpdata_connect(targ.dir, targ.type, targ.name); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif len = rpdata_buffer_len(rpd); if (len <= 0) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } rx_buf = hal_malloc(len); if (!rx_buf) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifdef RECV_CALLBACK_TEST rpdata_set_recv_cb(rpd, &rpd_demo_cbs); while (1) { hal_msleep(10); } #else while (1) { ret = rpdata_recv(rpd, rx_buf, len, 10000); if (ret <= 0) { printf("rpdata recv timeout \n"); goto exit; } if (data_check(rx_buf, ret) < 0) { printf("check md5 failed\n"); } } #endif exit: if (rx_buf) hal_free(rx_buf); if (rpd) rpdata_destroy(rpd); printf("rpdata auto recv test finish\n"); vTaskDelete(NULL); } static void rpdata_auto_recv_and_send(void *arg) { struct rpdata_arg_test targ; rpdata_t *rpd_recv = NULL, *rpd_send = NULL; void *buf_recv = NULL, *buf_send = NULL; uint32_t len = 512; int ret; memcpy(&targ, arg, sizeof(struct rpdata_arg_test)); printf("recv dir:%d, type:%s, name:%s\n", targ.dir, targ.type, targ.name); printf("send dir:%d, type:%s, name:%s\n", targ.sdir, targ.stype, targ.sname); rpd_send = rpdata_create(targ.sdir, targ.stype, targ.sname, len); if (!rpd_send) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } buf_send = rpdata_buffer_addr(rpd_send); if (!buf_send) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } rpd_recv = rpdata_connect(targ.dir, targ.type, targ.name); if (!rpd_recv) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd_recv, 2 * rpdata_buffer_len(rpd_recv)); rpdata_set_recv_ringbuffer(rpd_send, 2 * rpdata_buffer_len(rpd_send)); #endif buf_recv = hal_malloc(len); if (!buf_recv) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } rpdata_wait_connect(rpd_send); #ifdef RECV_CALLBACK_TEST rpdata_set_private_data(rpd_recv, rpd_send); rpdata_set_recv_cb(rpd_recv, &rpd_demo_rs_cbs); while (1) { hal_msleep(10); } #else while (1) { ret = rpdata_recv(rpd_recv, buf_recv, len, 10000); if (ret <= 0) { printf("rpdata recv timeout \n"); goto exit; } if (data_check(buf_recv, ret) < 0) { printf("check md5 failed\n"); } memcpy(buf_send, buf_recv, ret); ret = rpdata_send(rpd_send, 0, ret); if (ret != 0) { printf("rpdata send failed\n"); goto exit; } } #endif exit: if (buf_recv) hal_free(buf_recv); if (rpd_send) rpdata_destroy(rpd_send); if (rpd_recv) rpdata_destroy(rpd_recv); printf("rpdata auto recv_and_send test finish\n"); vTaskDelete(NULL); } static void rpdata_process_send(void *arg) { struct rpdata_arg_test targ; rpdata_t *rpd; void *buffer; int ret = -1; uint32_t len; void *params, *mic_data, *ref_data, *aec_data; uint32_t params_offset = 0; uint32_t mic_offset = 128; uint32_t ref_offset = 128 + 640; uint32_t aec_offset = 128 + 640 + 320; /* aec process * input: * params: * simulator: 128bytes * mic data: 2ch+16K+16bit, 10ms, 160*4=640 * ref data: 1ch+16K+16bit, 10ms, 160*2=320 * outout: * aec data: 1ch+16K+16bit, 10ms, 160*2=320 * * total= 128 + 640 + 320 + 320 = 1408 * 1408 is cacheline lenght * */ len = 1408; memcpy(&targ, arg, sizeof(struct rpdata_arg_test)); printf("dir:%d, type:%s, name:%s\n", targ.dir, targ.type, targ.name); rpd = rpdata_connect(targ.dir, targ.type, targ.name); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif buffer = rpdata_buffer_addr(rpd); if (!buffer) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } params = buffer + params_offset; mic_data = buffer + mic_offset; ref_data = buffer + ref_offset; aec_data = buffer + aec_offset; rpdata_wait_connect(rpd); data_fill(params, mic_offset - params_offset); data_fill(mic_data, ref_offset - mic_offset); data_fill(ref_data, aec_offset - ref_offset); memset(aec_data, 0, len - aec_offset); ret = rpdata_process(rpd, 0, len); if (ret != 0) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } if (data_check(aec_data, len - aec_offset) < 0) { printf("aec data check failed\n"); } else { printf("aec data check ok\n"); } exit: if (rpd) rpdata_destroy(rpd); vTaskDelete(NULL); } static void rpdata_process_recv(void *arg) { struct rpdata_arg_test targ; rpdata_t *rpd; uint32_t len; void *rx_buf = NULL; int ret; /* aec process * input: * params: * simulator: 128bytes * mic data: 2ch+16K+16bit, 10ms, 160*4=640 * ref data: 1ch+16K+16bit, 10ms, 160*2=320 * outout: * aec data: 1ch+16K+16bit, 10ms, 160*2=320 * * total= 128 + 640 + 320 + 320 = 1408 * 1408 is cacheline lenght * */ len = 1408; memcpy(&targ, arg, sizeof(struct rpdata_arg_test)); printf("dir:%d, type:%s, name:%s\n", targ.dir, targ.type, targ.name); rpd = rpdata_create(targ.dir, targ.type, targ.name, len); if (!rpd) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } #ifndef RECV_CALLBACK_TEST rpdata_set_recv_ringbuffer(rpd, 2 * rpdata_buffer_len(rpd)); #endif #ifndef RECV_CALLBACK_TEST rx_buf = hal_malloc(rpdata_buffer_len(rpd)); if (!rx_buf) { printf("[%s] line:%d \n", __func__, __LINE__); goto exit; } ret = rpdata_recv(rpd, rx_buf, len, 30*1000); if (ret <= 0) { printf("[%s] : Timeout!\n", __func__); goto exit; } rpd_demo_process_cb(rpd, rx_buf, ret); #else rpdata_set_recv_cb(rpd , &rpd_demo_process_cbs); hal_sleep(30); #endif exit: if (rx_buf) hal_free(rx_buf); if (rpd) rpdata_destroy(rpd); printf("rpdata recv process finish\n"); vTaskDelete(NULL); } static int do_rpdata_process_test(struct rpdata_arg_test *targ, int mode) { hal_thread_t handle; if (mode == 0) handle = hal_thread_create(rpdata_process_send, targ, "rpd_send_process", 512, HAL_THREAD_PRIORITY_APP); else if (mode == 1) handle = hal_thread_create(rpdata_process_recv, targ, "rpd_recv_process", 512, HAL_THREAD_PRIORITY_APP); return 0; } static int do_rpdata_auto_test(struct rpdata_arg_test *targ, int mode) { hal_thread_t handle; if (mode == 0) handle = hal_thread_create(rpdata_auto_send, targ, "rpd_send_test", 512, HAL_THREAD_PRIORITY_APP); else if (mode == 1) handle = hal_thread_create(rpdata_auto_recv, targ, "rpd_recv_test", 512, HAL_THREAD_PRIORITY_APP); else if (mode == 2) handle = hal_thread_create(rpdata_auto_recv_and_send, targ, "rpd_rs_test", 512, HAL_THREAD_PRIORITY_APP); return 0; } static int check_dir(int dir) { switch (dir) { case RPDATA_DIR_CM33: case RPDATA_DIR_RV: case RPDATA_DIR_DSP: return 0; default: return -1; } } static int cmd_rpdata_demo(int argc, char *argv[]) { int c, mode = 2; int single_test = 0, process_test = 0; int get_sdir_arg = 0; /* string/data must be cache_line align */ char string[128] = "rpdata test string"; static struct rpdata_arg_test targ = { .type = "RVtoDSP", .name = "DSPrecvRVsend", .dir = RPDATA_DIR_DSP, .stype = "DSPtoM33", .sname = "M33recvDSPsend", .sdir = RPDATA_DIR_CM33, }; optind = 0; while ((c = getopt(argc, argv, "hm:t:n:c:s:d:iv:p")) != -1) { switch (c) { case 'm': mode = atoi(optarg); break; case 't': if (!get_sdir_arg) strncpy(targ.type, optarg, sizeof(targ.type)); else strncpy(targ.stype, optarg, sizeof(targ.stype)); break; case 'n': if (!get_sdir_arg) strncpy(targ.name, optarg, sizeof(targ.name)); else strncpy(targ.sname, optarg, sizeof(targ.sname)); break; case 'c': strncpy(string, optarg, sizeof(string)); break; case 's': targ.sdir = atoi(optarg); get_sdir_arg = 1; break; case 'd': targ.dir = atoi(optarg); get_sdir_arg = 0; break; case 'i': single_test = 1; break; case 'v': g_rpd_verbose = atoi(optarg); return 0; case 'p': process_test = 1; break; case 'h': default: goto usage; } } if (mode != 0 && mode != 1 && mode != 2) goto usage; if (check_dir(targ.dir) < 0 || check_dir(targ.sdir) < 0) goto usage; if (process_test) { do_rpdata_process_test(&targ, mode); return 0; } if (!single_test) { do_rpdata_auto_test(&targ, mode); return 0; } if (mode == 0) do_rpdata_send_test(&targ, string, sizeof(string)); else if (mode == 1) do_rpdata_recv_test(&targ, string, sizeof(string)); return 0; usage: rpdata_demo_usage(); return -1; } FINSH_FUNCTION_EXPORT_CMD(cmd_rpdata_demo, rpdata_demo, rpdata test demo);
测试例子:
- rv/dsp之间的收发
rv接受,dsp发送
rpdata_demo ‑m 1 ‑d 3 ‑t DSPtoRV ‑n RVrecvDSPsend rpccli dsp rpdata_demo ‑m 0 ‑d 2 ‑t DSPtoRV ‑n RVrecvDSPsend
rv发送,dsp接受
rpdata_demo ‑m 0 ‑d 3 ‑t RVtoDSP ‑n RVsendDSPrecv rpccli dsp rpdata_demo ‑m 1 ‑d 2 ‑t RVtoDSP ‑n RVsendDSPrecv
- rv/m33之间收发
rv接受,m33发送
rpdata_demo ‑m 1 ‑d 1 ‑t M33toRV ‑n RVrecvM33send rpccli arm rpdata_demo ‑m 0 ‑d 2 ‑t M33toRV ‑n RVrecvM33send
rv发送,m33接受
rpdata_demo ‑m 0 ‑d 1 ‑t RVtoM33 ‑n RVsendM33recv rpccli arm rpdata_demo ‑m 1 ‑d 2 ‑t RVtoM33 ‑n RVsendM33recv
- m33/dsp之间收发
m33接受,dsp发送
rpccli arm rpdata_demo ‑m 1 ‑d 3 ‑t DSPtoM33 ‑n M33recvDSPsend rpccli dsp rpdata_demo ‑m 0 ‑d 1 ‑t DSPtoM33 ‑n M33recvDSPsend
dsp接受,m33发送
rpccli dsp rpdata_demo ‑m 1 ‑d 1 ‑t M33toDSP ‑n DSPrecvM33send rpccli arm rpdata_demo ‑m 0 ‑d 3 ‑t M33toDSP ‑n DSPrecvM33send
另外,提供了 rpd 命令用于查看 rpdata 的运行情况:
c906>rpd ‑l ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ RPdata RV <‑‑> CM33 id type+name state ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ RPdata RV <‑‑> DSP id type+name state 0x01 DSPtoRV 0x01 └─── RVrecvDSPsend CNXN c906>rpccli dsp rpd ‑l ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ RPdata DSP <‑‑> CM33 id type+name state ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ RPdata DSP <‑‑> RV id type+name state 0x01 DSPtoRV 0x01 └─── RVrecvDSPsend CNXN
-
【R128】基础组件开发指南——SPI LCD 显示驱动
SPI LCD 显示驱动
简介
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了SPILCD驱动框架以供 SPI 屏幕使用。
模块驱动
menuconfig配置说明
SPILCD 模块 menuconfig 的配置如下(以选择kld2844b屏为例):
Drivers Options ---> soc related device drivers ---> SPILCD Devices ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test //spilcd模块测试用例 LCD_FB panels select ---> //spilcd屏驱动配置 [*] LCD support kld2844B panel [ ] LCD support kld35512 panel Board select ---> [ ] board kld2844b support //板级显示使用显示驱动私有方式的配置项,而使用sys_config.fex方式不用配置
另外可能需依赖的配置项有:
- DRIVERS_SPI
- DRIVERS_DBI
- DRIVERS_PWM
源码结构介绍
源码结构及主要驱动文件如下:
spilcd/ ├── lcd_fb/ │ ├── dev_lcd_fb.c # spilcd driver 层 │ ├── disp_display.c │ ├── disp_lcd.c │ ├── lcd_fb_intf.c │ └── panels/ # lcd驱动相关 │ ├── kld2844b.c │ ├── lcd_source.c │ ├── panels.c │ └── panels.h └── soc/ ├── disp_board_config.c # 板级配置解析 └── kld2844b_config.c # 显示私有方式的板级配置文件
模块参数配置
当前板级显示支持两种配置方法,一是使用
sys_config.fex
的方式进行配置,二是在不支持sys_config.fex
情况下,可以通过显示驱动私有的方式进行配置。下面分别对两种方式进行说明。使用 sys_config.fex 的方式进行配置
FreeRTOS系统路径:
board/芯片名/方案名/configs/sys_config.fex
配置文件具体要看芯片方案所实际使用的,也可能使用的配置文件名称为sys_config_xxx.fex(xx是存储方案的标识,例如sys_config_nor.cfg、sys_config_nand.cfg)
具体配置举例如下:
;---------------------------------------------------------------------------------- ;lcd_fb0 configuration ;---------------------------------------------------------------------------------- [lcd_fb0] lcd_used = 1 ; 使用显示屏 lcd_model_name = "spilcd" ; 模型:spilcd lcd_driver_name = "jlt35031c" ; 屏幕驱动:jlt35031c lcd_x = 320 ; 屏幕宽分辨率 lcd_y = 480 ; 屏幕高分辨率 lcd_width = 49 ; 屏幕物理宽度 lcd_height = 74 ; 屏幕物理高度 lcd_data_speed = 60 ; SPI 驱动频率 60MHz lcd_pwm_used = 1 ; lcd使用pwm背光 lcd_pwm_ch = 1 ; lcd使用pwm背光通道1 lcd_pwm_freq = 5000 ; lcd使用pwm背光频率5000Hz lcd_pwm_pol = 0 ; lcd使用pwm背光相位0 lcd_if = 0 ; lcd使用spi接口,0-spi, 1-dbi lcd_pixel_fmt = 11 lcd_dbi_fmt = 2 lcd_dbi_clk_mode = 1 lcd_dbi_te = 1 fb_buffer_num = 2 lcd_dbi_if = 4 lcd_rgb_order = 0 lcd_fps = 60 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_backlight = 100 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 lcd_spi_dc_pin = port:PA19<1><0><3><0> ; DC脚 ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0> ; 复位脚
lcd_driver_name
Lcd屏驱动的名字(字符串),必须与屏驱动中
strcut __lcd_panel
变量的name
成员一致。lcd_model_name
Lcd屏模型名字,非必须,可以用于同个屏驱动中进一步区分不同屏。
lcd_if
Lcd Interface
设置相应值的对应含义为:
0:spi接口 1:dbi接口
spi
接口就是俗称的4线模式,这是因为发送数据时需要额外借助DC
线来区分命令和数据,与sclk
,cs
和sda
共四线。如果设置了
dbi
接口,那么还需要进一步区分dbi
接口,需要设置lcd_dbi_if
lcd_dbi_if
Lcd dbi 接口设置。
这个参数只有在
lcd_if=1
时才有效。设置相应值的对应含义为:
0:L3I1 1:L3I2 2:L4I1 3:L4I2 4:D2LI
所有模式在发送数据时每个周期的比特数量根据不同像素格式不同而不同。
L3I1
和L3I2
是三线模式(不需要DC
脚),区别是读时序,也就是是否需要额外脚来读寄存器。读写时序图如下:L4I1
和L4I2
是四线模式,与spi接口协议一样,区别是DC脚的控制是否自动化控制,另外I2和I1的区别是读时序,也就是否需要额外脚来读取寄存器。D2LI
是两data lane模式。发送命令部分时序与读时序与L3I1
一致,下图是发送数据时的时序,不同像素格式时钟周期数量不一样。lcd_dbi_fmt
DBI
接口像素格式。0:RGB111 1:RGB444 2:RGB565 3:RGB666 4:RGB888
选择的依据是接收端屏
Driver IC
的支持情况,请查看Driver IC
手册或询问屏厂。然后必须配合
lcd_pixel_fmt
的选择,比如说选RGB565时,lcd_pixel_fmt
也要选565格式。lcd_dbi_te
使能te触发。
te即(Tearing effect),也就是撕裂的意思,由于读写不同导致撕裂现象,te脚的功能就是用于同步读写,te脚的频率也就是屏的刷新率,所以te脚也可以看做vsync脚(垂直同步脚)
0: 禁止te 1: 下降沿触发 2: 上升沿触发
查看带te脚的屏进一步说明。
lcd_dbi_clk_mode
选择dbi时钟的行为模式。
0:自动停止。有数据就有时钟,没发数据就没有 1:一直保持。无论发不发数据都有时钟
注意上面的选项关系屏兼容性。
lcd_rgb_order
输入图像数据rgb顺序识别设置,仅当lcd_if=1时有效。
0:RGB 1:RBG 2:GRB 3:GBR 4:BRG 5:BGR 6:G_1RBG_0 7:G_0RBG_1 8:G_1BRG_0 9:G_0BRG_1
非RGB565格式用0到5即可。
针对rgb565格式说明如下:
rgb565格式会遇到大小端问题,arm平台和PC平台存储都是小端(little endian,低字节放在低地址,高字节放在高地址),但是许多spi屏都是默认大端(Big Endian)。
也就是存储的字节顺序和发送的字节顺序不对应。
这个时候选择6以下,DBI接口就会自动将小端转成大端。
如果遇到默认是小端的spi屏,则需要选择6以上,DBI接口会自动用回小端方式。
6以上格式这样解释:
R是5比特,G是6比特,B是5比特,再把G拆成高3位(G_1)和低3位(G_0)
所以以下两种顺序:- R-G_1-G_0-B,大端。
- G_0-B-R-G_1,对应上面的9,小端。
lcd_x
显示屏的水平像素数量,注意如果屏支持横竖旋转,那么lcd_x和lcd_y也要对调。
lcd_y
显示屏的行数,注意如果屏支持横竖旋转,那么lcd_x和lcd_y也要对调。
lcd_data_speed
用于设置spi/dbi接口时钟的速率,单位MHz。
- 发送端(SOC)的最大限制是100MHz。
- 接收端(屏Driver IC)的限制,请查看对应Driver IC手册或者询问屏厂支持。
- 超出以上限制都有可能导致显示异常。
lcd_fps
设置屏的刷新率,单位Hz。当lcd_dbi_te使能时,这个值设置无效。
lcd_pwm_used
是否使用pwm。
此参数标识是否使用pwm用以背光亮度的控制。
lcd_pwm_ch
Pwm channel used
此参数标识使用的Pwm通道。
lcd_pwm_freq
Lcd backlight PWM Frequency
这个参数配置PWM信号的频率,单位为Hz。
lcd_pwm_pol
Lcd backlight PWM Polarity
这个参数配置PWM信号的占空比的极性。设置相应值对应含义为:
0:active high 1:active low
lcd_pwm_max_limit
Lcd backlight PWM
最高限制,以亮度值表示。
比如150,则表示背光最高只能调到150,0~255范围内的亮度值将会被线性映射到0~150范围内。用于控制最高背光亮度,节省功耗。
lcd_backlight
默认背光值,取值范围0到255,值越大越亮。
lcd_bl_en
背光使能脚定义
lcd_spi_dc_pin
指定作为DC的管脚,用于spi接口时。
lcd_gpio_x
x表示数字。如果有多个gpio脚需要控制,则定义lcd_gpio_0,lcd_gpio_1等。
lcd_spi_bus_num
选择spi总线id,只有spi1支持DBI协议,所以这里一般选择1。
取值范围:0到1。
lcd_pixel_fmt
选择传输数据的像素格式。
可选值如下,当你更换RGB分量顺序的时候,也得相应修改lcd_rgb_order,或者修改屏驱动的rgb分量顺序(一般是3Ah寄存器)。
DBI接口只支持RGB32和RGB16的情况。
SPI接口只支持RGB16的情况。
enum lcdfb_pixel_format { LCDFB_FORMAT_ARGB_8888 = 0x00, // MSB A-R-G-B LSB LCDFB_FORMAT_ABGR_8888 = 0x01, LCDFB_FORMAT_RGBA_8888 = 0x02, LCDFB_FORMAT_BGRA_8888 = 0x03, LCDFB_FORMAT_XRGB_8888 = 0x04, LCDFB_FORMAT_XBGR_8888 = 0x05, LCDFB_FORMAT_RGBX_8888 = 0x06, LCDFB_FORMAT_BGRX_8888 = 0x07, LCDFB_FORMAT_RGB_888 = 0x08, LCDFB_FORMAT_BGR_888 = 0x09, LCDFB_FORMAT_RGB_565 = 0x0a, LCDFB_FORMAT_BGR_565 = 0x0b, LCDFB_FORMAT_ARGB_4444 = 0x0c, LCDFB_FORMAT_ABGR_4444 = 0x0d, LCDFB_FORMAT_RGBA_4444 = 0x0e, LCDFB_FORMAT_BGRA_4444 = 0x0f, LCDFB_FORMAT_ARGB_1555 = 0x10, LCDFB_FORMAT_ABGR_1555 = 0x11, LCDFB_FORMAT_RGBA_5551 = 0x12, LCDFB_FORMAT_BGRA_5551 = 0x13, };
fb_buffer_num
显示framebuffer数量,为了平滑显示,这里一般是2个,为了省内存也可以改成1。
模块 sys_config.fex 配置案例
典型2 data lane配置
一些屏支持双数据线传输以加快数据传输速度,此时需要走DBI协议,典型配置如下:
[lcd_fb0] lcd_used = 1 lcd_driver_name = "kld2844b" lcd_if = 1 lcd_dbi_if = 4 lcd_data_speed = 60 lcd_spi_bus_num = 1 lcd_x = 320 lcd_y = 240 lcd_pwm_used = 1 lcd_pwm_ch = 4 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_pixel_fmt = 0 lcd_dbi_fmt = 3 lcd_rgb_order = 0 fb_buffer_num = 2 lcd_backlight = 200 lcd_fps = 60 lcd_dbi_te = 0 lcd_bl_en = port:PB04<1><0><default><1> lcd_gpio_0 = port:PB02<1><0><default><1>
- 硬件连接上,第二根数据脚连接到原来1 data lane的DC脚,可以这样理解:2数据线在传输数据时就自带D/C(Data/Commend)信息了,所以原来的DC脚就可以空出来作为第二根数据线了。
- 屏驱动上,需要使能2 data lane模式,具体寄存器查看对应driverIC手册或者询问屏厂。
- 这里的针对对2 data lane的关键参数是lcd_if,lcd_dbi_if,lcd_dbi_fmt和lcd_spi_bus_num。
- lcd_x和lcd_y是屏分辨率。如果屏支持旋转(横竖旋转),这里也需要对调。
- lcd_pwm开头,lcd_backlight和lcd_bl_en的是背光相关设置,如果有相关硬件连接的话。
- lcd_pixel_fmt和fb_buffer_num是显示framebuffer的设置。
- lcd_gpio_开头的是自定义gpio的设置(比如复位脚)。
- lcd_fps和lcd_dbi_te是刷新方式相关的设置。
原SPI接口屏配置
如果IC支持DBI接口,那么就没有必要用SPI接口,DBI接口其协议能覆盖所有情况。
一些IC不支持DBI,那么只能用spi接口(通过设置lcd_if),如果使用spi接口,它有一些限制。
- 不支持2 data lane。
- 必须指定DC脚。这是由于spi协议不会自动控制DC脚来区分数据命令,通过设置lcd_spi_dc_pin可以完成这个目的,这跟管脚不必用spi里面的脚。
- 只支持rgb565的像素格式。由于只有单data lane,速度过慢,rgb565以上格式都不现实。
[lcd_fb0] lcd_used = 1 lcd_driver_name = "kld2844b" lcd_if = 0 lcd_data_speed = 60 lcd_spi_bus_num = 1 lcd_x = 320 lcd_y = 240 lcd_pwm_used = 1 lcd_pwm_ch = 4 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_pixel_fmt = 10 lcd_rgb_order = 0 fb_buffer_num = 2 lcd_backlight = 200 lcd_fps = 60 lcd_dbi_te = 0 lcd_bl_en = port:PB04<1><0><default><1> lcd_gpio_0 = port:PB02<1><0><default><1> lcd_spi_dc_pin = port:PA19<1><0><3><1>
带te脚的屏
te即(Tearing effect),也就是撕裂的意思,是由于读写不同步导致撕裂现象,te脚的功能就是用于同步读写,te脚的频率也就是屏的刷新率,所以te脚也可以看做vsync脚(垂直同步脚)。
- 硬件设计阶段,需要将屏的te脚连接到IC的DBI接口的te脚。
- 配置上接口使用dbi接口。
- 然后使能lcd_dbi_te。
- 屏驱动使能te功能,寄存器一般是35h,详情看屏对应的driver IC手册。
- 屏驱动设置帧率,根据屏能接受的传输速度选择合理的帧率(比如ST7789H2里面是通过c6h来设置te频率)。
[lcd_fb0] lcd_used = 1 lcd_driver_name = "kld2844b" lcd_if = 1 lcd_dbi_if = 4 lcd_data_speed = 60 lcd_spi_bus_num = 1 lcd_x = 320 lcd_y = 240 lcd_pwm_used = 1 lcd_pwm_ch = 4 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_pixel_fmt = 0 lcd_dbi_fmt = 3 lcd_rgb_order = 0 fb_buffer_num = 2 lcd_backlight = 200 lcd_fps = 60 lcd_dbi_te = 1 lcd_bl_en = port:PB04<1><0><default><1> lcd_gpio_0 = port:PB02<1><0><default><1>
横竖屏旋转
- 平台没有硬件旋转功能,软件旋转太慢而且耗费CPU。
- 不少spi屏支持内部旋转,需要在屏驱动初始化的时候进行设置,一般是36h寄存器。
// 转成横屏 sunxi_lcd_cmd_write(sel, 0x36); sunxi_lcd_para_write(sel, 0xa0);
帧率控制
屏的刷新率受限于多方面:
- SPI/DBI硬件传输速度,也就是时钟脚的频率。设置lcd_data_speed可以设置硬件传输速度,最大不超过100MHz。如果屏能正常接收,这个值自然是越大越好。
- 屏driver IC接收能力。Driver IC手册中会提到屏的能接受的最大sclk周期。
- 使用2 data lane还是1 data lane,理论上2 data lane的速度会翻倍。见典型2 data lane配置。
- 像素格式。像素格式决定需要传输的数据量,颜色数量越小的像素格式,帧率越高,但是效果越差。
- 带te脚的屏一节中我们知道,te相关设置直接影响到屏刷新率。
- 如果不支持te,可以通过设置lcd_fps来控制帧率,你需要根据第一点和第二点选择一个合适的值。
背光控制
- 硬件需要支持pwm背光电路。
- 驱动支持pwm背光调节,只需要配置好lcd_pwm开头,lcd_backlight和lcd_bl_en等背光相关配置即可。
像素格式相关
- lcd_pixel_fmt,这个设置项用于设置fbdev的像素格式。
- lcd_dbi_fmt,这个用于设置DBI接口发送的像素格式。
SPI/DBI发送数据的时候没有必要发送alpha通道,但是应用层却有对应的alpha通道,比如ARGB8888格式。
这个时候硬件会自动帮我们处理好alpha通道,所以lcd_pixel_fmt选择有alpha通道的格式时,lcd_dbi_fmt可以选rgb666或者rgb888,不用和它一样。
电源配置
有多个电源的话,就用lcd_power1,lcd_power2......然后屏驱动里面调用sunxi_lcd_power_enable接口即可。
GPIO配置说明
lcd_bl_en、lcd_spi_dc_pin以及lcd_gpio_x都是属于GPIO属性类型。
下面以lcd_spi_dc_pin为例,具体说明GPIO属性的参数值含义:
lcd_spi_dc_pin = port:PA19<1><0><3><1>
引脚说明:port: 端口 < 复用功能 >< 上下拉 >< 驱动能力 >< 输出值 >
等式右边的从左到右5个字段代表的具体含义如下:
PA19
:端口,表示GPIO管脚。PA
表示PA组管脚,19
表示第19根管脚;即PA19管脚。<1>
:复用功能,表示GPIO复用类型。1表示将PA19选择为通用GPIO功能,0为输入,1为输出。<0>
:上下拉,表示内置电阻。使用0的话,表示内部电阻高阻态,如果是1则是内部电阻上拉,2就代表内部电阻下拉。其它数据无效。<3>
:表示驱动能力。1是默认等级,数字越大驱动能力越强,最大是3。<1>
:表示默认输出电平值。0为低电平,1为高电平。
多个显示
- 确定硬件有没有多余的spi/dbi接口。
- 需要在sys_config.fex里面新增lcd_fb1,配置方式与lcd_fb0一样(或者在显示私有方式的板级配置文件里面新增
g_lcd1_config
,配置方式与g_lcd0_config一样),其中lcd_spi_bus_num不能一样。
依赖驱动配置
spilcd模块依赖spi,dbi,pwm等驱动。
使用显示私有方式进行配置
路径:
rtos-hal/hal/source/spilcd/soc/
具体板级显示配置文件可参考该路径下的
kld2844b_config.c
配置文件。该文件模仿
sys_config.fex
来配置一些板级相关的资源,源文件中需要定义4个全局变量:g_lcd0_config
、g_lcd1_config
、g_lcd0_config_len
和g_lcd1_config_len
,且变量名字固定,不能修改。具体说明如下:
g_lcd0_config
:第一个屏的配置变量,struct property_t
数据类型。g_lcd1_config
:第二个屏的配置变量,struct property_t
数据类型。g_lcd0_config_len
和g_lcd1_config_len
是对应上面两个数组变量的长度,照搬即可。
struct property_t
数据类型,用于定义一个属性的信息:- 属性名字。对应其成员
name
,字符串。 - 属性类型。对应其成员
type
,看enum proerty_type
的定义,共有整型,字符串,GPIO,专用pin和电源。 - 属性值。根据上面的属性类型来选择
union
中成员来赋值。
对于上述常用的属性类型举例如下:
整型:
{ .name = "lcd_used", .type = PROPERTY_INTGER, .v.value = 1, },
字符串:
{ .name = "lcd_driver_name", .type = PROPERTY_STRING, .v.str = "kld2844b", },
GPIO:
{ .name = "lcd_spi_dc_pin", .type = PROPERTY_GPIO, .v.gpio_list = { .gpio = GPIOA(19), .mul_sel = GPIO_DIRECTION_OUTPUT, .pull = 0, .drv_level = 3, .data = 1, }, },
g_lcd0_config的含义与前述的使用sys_config.fex方式中的lcd_fb0一致。
编写屏驱动
屏驱动源码位置:
rtos-hal/hal/source/spilcd/lcd_fb/panels
-
在屏驱动源码位置下拷贝现有一个屏驱动,包括头文件和源文件,然后将文件名改成有意义的名字,比如屏型号。
-
修改源文件中的
strcut __lcd_panel
变量的名字,以及这个变量成员name
的名字,这个名字必须和sys_config.fex
中[lcd_fb0]
(或板级配置文件中g_lcd0_config
)的lcd_driver_name
一致。 -
在屏驱动目录下修改
panel.c
和panel.h
。在全局结构体变量panel_array
中新增刚才添加strcut __lcd_panel
的变量指针。panel.h
中新增strcut __lcd_panel
的声明。并用宏括起来。 -
修改
rtos-hal/hal/source/spilcd/lcd_fb/panels/Kconfig
,新增一个config,与第三点提到的宏对应。 -
修改
rtos-hal/hal/source/spilcd/lcd_fb/
路径下的Makefile文件。给lcd_fb-obj变量新增刚才加入的源文件对应.o
。 -
根据本手册以及屏手册,Driver IC手册修改sys_config.fex中的[lcd_fb0]节点(或显示私有方式的板级配置文件中的
g_lcd0_config
配置变量)下面的属性 -
实现屏源文件中的
LCD_open_flow
,LCD_close_flow
,LCD_panel_init
,LCD_power_on
等函数
开关屏流程函数解析
开关屏的操作流程如下图所示。
其中,LCD_open_flow和LCD_close_flow称为开关屏流程函数,该函数利用LCD_OPEN_FUNC进行注册回调函数,先注册先执行,可以注册多个,不限制数量。
LCD_open_flow
功能:初始化开屏的步骤流程。
原型:
static __s32 LCD_open_flow(__u32 sel)
函数常用内容为:
static __s32 LCD_open_flow(__u32 sel) { LCD_OPEN_FUNC(sel, LCD_power_on,10); LCD_OPEN_FUNC(sel, LCD_panel_init, 50); LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 100); LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; }
如上,初始化整个开屏的流程步骤为四个:
- 打开LCD电源,再延迟10ms。
- 初始化屏,再延迟50ms;(不需要初始化的屏,可省掉此步骤)。
- 向屏发送全黑的数据。这一步骤是必须的,而且需要在开背光之前。
- 打开背光,再延迟0ms。
LCD_open_flow
函数只会系统初始化的时候调用一次,执行每个LCD_OPEN_FUNC
即是把对应的开屏步骤函数进行注册,并没有立即执行该开屏步骤函数。LCD_open_flow函数的内容必须统一用LCD_OPEN_FUNC(sel, function, delay_time)
进行函数注册的形式,确保正常注册到开屏步骤中。LCD_OPEN_FUNC的第二个参数是前后两个步骤的延时长度,单位ms,注意这里的数值请按照屏手册规定去填,乱填可能导致屏初始化异常或者开关屏时间过长,影响用户体验。
LCD_close_flow
功能:初始化关屏的步骤流程。
原型:
static __s32 LCD_close_flow(__u32 sel)
函数常用内容为:
static __s32 LCD_close_flow(__u32 sel) { LCD_CLOSE_FUNC(sel, LCD_bl_close, 50); LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10); LCD_CLOSE_FUNC(sel, LCD_power_off, 10); return 0; }
- LCD_bl_close,是关背光,关完背光在处理其它事情,不会影响用户视觉。
- LCD_panel_exit,发送命令让屏退出工作状态。
- 关电复位,让屏彻底关闭。
LCD_OPEN_FUNC
功能:注册开屏步骤函数到开屏流程中,记住这里是注册不是执行!
原型:
void LCD_OPEN_FUNC(__u32 sel, LCD_FUNC func, __u32 delay)
参数说明:
func是一个函数指针,其类型是:
void (*LCD_FUNC) (__u32 sel)
,用户自己定义的函数必须也要用统一的形式。比如:void user_defined_func(__u32 sel) { //do something }
delay是执行该步骤后,再延迟的时间,时间单位是毫秒。
LCD_power_on
这是开屏流程中第一步,一般在这个函数使用sunxi_lcd_gpio_set_value进行GPIO控制,用sunxi_lcd_power_enable函数进行电源开关。
参考屏手册里面的上电时序(Power on sequence)。
LCD_panel_init
这是开屏流程第二步,一般使用sunxi_lcd_cmd_write和sunxi_lcd_para_write对屏寄存器进行初始化。
请向屏厂索要初始化寄存器代码或者自行研究屏Driver IC手册。
lcd_fb_black_screen
向屏传输全黑数据的接口,是必须的,否则打开背光后,呈现的将是雪花屏。
LCD_bl_open
这是背光使能,固定调用。
- sunxi_lcd_backlight_enable, 打开lcd_bl_en脚。
- sunxi_lcd_pwm_enable, 使能pwm。
LCD_bl_close
这是关闭背光。固定调用下面两个函数,分别是:
- sunxi_lcd_backlight_disable,lcd_bl_en关闭
- sunxi_lcd_pwm_disable, 关闭pwm。
LCD_power_off
这是关屏流程中最后一步,一般在这个函数使用sunxi_lcd_gpio_set_value进行GPIO控制,用sunxi_lcd_power_enable函数进行电源开关。
参考屏手册里面的下电时序(Power off sequence)。
sunxi_lcd_delay_ms
函数:sunxi_lcd_delay_ms/sunxi_lcd_delay_us
功能:延时函数,分别是毫秒级别/微秒级别的延时。
原型:
s32 sunxi_lcd_delay_ms(u32 ms); / s32 sunxi_lcd_delay_us(u32 us);
sunxi_lcd_backlight_enable
函数:sunxi_lcd_backlight_enable/ sunxi_lcd_backlight_disable
功能:打开/关闭背光,操作的是lcd_bl_en。
原型:
-
void sunxi_lcd_backlight_enable(u32 screen_id);
-
void sunxi_lcd_backlight_disable(u32 screen_id);
sunxi_lcd_pwm_enable
函数:sunxi_lcd_pwm_enable / sunxi_lcd_pwm_disable
功能:打开/关闭pwm控制器,打开时pwm将往外输出pwm波形。对应的是lcd_pwm_ch所对应的那一路pwm。
原型:
-
s32 sunxi_lcd_pwm_enable(u32 screen_id);
-
s32 sunxi_lcd_pwm_disable(u32 screen_id);
sunxi_lcd_power_enable
函数:sunxi_lcd_power_enable / sunxi_lcd_power_disable
功能:打开/关闭Lcd电源,操作的是板级配置文件中的
lcd_power/lcd_power1/lcd_power2
。(pwr_id标识电源索引)。原型:
-
void sunxi_lcd_power_enable(u32 screen_id, u32 pwr_id);
-
void sunxi_lcd_power_disable(u32 screen_id, u32 pwr_id);
- pwr_id = 0:对应于配置文件中的lcd_power。
- pwr_id = 1:对应于配置文件中的lcd_power1。
- pwr_id = 2:对应于配置文件中的lcd_power2。
- pwr_id = 3:对应于配置文件中的lcd_power3。
sunxi_lcd_cmd_write
函数:sunxi_lcd_cmd_write
功能:使用spi/dbi发送命令。
原型:
s32 sunxi_lcd_cmd_write(u32 screen_id, u8 cmd);
sunxi_lcd_para_write
函数:sunxi_lcd_para_write
功能:使用spi/dbi发送参数。
原型:
s32 sunxi_lcd_para_write(u32 screen_id, u8 para);
sunxi_lcd_gpio_set_value
函数:sunxi_lcd_gpio_set_value
功能:LCD_GPIO PIN脚上输出高电平或低电平。
原型:
s32 sunxi_lcd_gpio_set_value(u32 screen_id, u32 io_index, u32 value);
参数说明:
- io_index = 0:对应于配置文件中的lcd_gpio_0。
- io_index = 1:对应于配置文件中的lcd_gpio_1。
- io_index = 2:对应于配置文件中的lcd_gpio_2。
- io_index = 3:对应于配置文件中的lcd_gpio_3。
- value = 0:对应IO输出低电平。
- Value = 1:对应IO输出高电平。
只用于该GPIO定义为输出的情形。
sunxi_lcd_gpio_set_direction
函数:sunxi_lcd_gpio_set_direction
功能:设置LCD_GPIO PIN脚为输入或输出模式。
原型:
s32 sunxi_lcd_gpio_set_direction(u32 screen_id, u32 io_index, u32 direction);
参数说明:
- io_index = 0:对应于配置文件中的lcd_gpio_0。
- io_index = 1:对应于配置文件中的lcd_gpio_1。
- io_index = 2:对应于配置文件中的lcd_gpio_2。
- io_index = 3:对应于配置文件中的lcd_gpio_3。
- direction = 0:对应IO设置为输入。
- direction = 1:对应IO设置为输出。
模块测试用例
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <hal_cache.h> #include <hal_mem.h> #include <hal_log.h> #include <hal_cmd.h> #include <hal_lcd_fb.h> static uint32_t width; static uint32_t height; static long int screensize = 0; static char *fbsmem_start = 0; static void lcdfb_fb_init(uint32_t yoffset, struct fb_info *p_info) { p_info->screen_base = fbsmem_start; p_info->var.xres = width; p_info->var.yres = height; p_info->var.xoffset = 0; p_info->var.yoffset = yoffset; } int show_rgb(unsigned int sel) { int i = 0, ret = -1; struct fb_info fb_info; int bpp = 4; unsigned char color[4] = {0xff,0x0,0xff,0x0}; width = bsp_disp_get_screen_width(sel); height = bsp_disp_get_screen_height(sel); screensize = width * bpp * height; fbsmem_start = hal_malloc_coherent(screensize); hal_log_info("width = %d, height = %d, screensize = %d, fbsmem_start = %x\n", width, height, screensize, fbsmem_start); memset(fbsmem_start, 0, screensize); for (i = 0; i < screensize / bpp; ++i) { memcpy(fbsmem_start+i*bpp, color, bpp); } memset(&fb_info, 0, sizeof(struct fb_info)); lcdfb_fb_init(0, &fb_info); hal_dcache_clean((unsigned long)fbsmem_start, screensize); bsp_disp_lcd_set_layer(sel, &fb_info); hal_free_coherent(fbsmem_start); return ret; } static int cmd_test_spilcd(int argc, char **argv) { uint8_t ret; hal_log_info("Run spilcd hal layer test case\n"); ret = show_rgb(0); hal_log_info("spilcd test finish\n"); return ret; } FINSH_FUNCTION_EXPORT_CMD(cmd_test_spilcd, test_spilcd, spilcd hal APIs tests)
测试命令:
test_spilcd
测试结果:
1.如果LCD是ARGB8888像素格式输入,执行命令后会显示一帧紫色画面。 2.如果LCD是RGB565像素格式输入,执行命令后会显示一帧蓝色画面(SPI 协议是黄色画面(没有RB Swap))
FAQ
怎么判断屏初始化成功
屏初始化成功,一般呈现的现象是雪花屏。因为屏驱动里面,在
LCD_open_flow
中添加了lcd_fb_black_screen
的注册,故正常情况下开机是有背光的黑屏画面。黑屏-无背光
一般是电源或者pwm相关配置没有配置好。参考lcd_pwm开头的相关配置。
送图无显示
排除步骤:
- 首先执行spilcd模块测试命令
test_spilcd
,如果能正常显示颜色画面,说明显示通路正常,只是应用未能正确配置送图接口。如果执行测试用例无法正常显示颜色画面,接着下面步骤。 - 屏驱动里面,在
LCD_open_flow
中删除lcd_fb_black_screen
的注册,启动后,如果屏初始化成功应该是花屏状态(大部分屏如此)。 - 如果屏没有初始化成功,请检查屏电源,复位脚状态。
- 如果屏初始化成功,但是发数据时又没法显示,那么需要检查是不是帧率过快,查看帧率控制。
- 如果电源复位脚正常,请检查配置,lcd_dbi_if, lcd_dbi_fmt是否正确,屏是否支持, 如果支持,在屏驱动里面是否有对应上。
- 尝试修改lcd_dbi_clk_mode。
闪屏
非常有可能是速度跑太快,参考帧率控制一小节。
画面偏移
画面随着数据的发送偏移越来越大。
尝试修改lcd_dbi_clk_mode。
屏幕白屏
屏幕白屏,但是背光亮起
白屏是因为屏幕没有初始化,需要检查屏幕初始化序列或者初始化数据是否正确。
屏幕花屏
屏幕花屏,无法控制
花屏一般是因为屏幕初始化后没有正确设置
addrwin
,或者初始化序列错误。LVGL 屏幕颜色不正确
出现反色,颜色异常
请配置 LVGL
LV_COLOR_DEPTH
参数为 16,LV_COLOR_16_SWAP
为 1,这是由 SPI LCD 的特性决定的。显示反色
运行
test_spilcd
,屏幕显示蓝色。这是由于屏幕启动了 RB SWAP,一般是
0x36
寄存器修改正常显示
sunxi_lcd_cmd_write(sel, 0X36); sunxi_lcd_para_write(sel, 0x00);
反色显示
sunxi_lcd_cmd_write(sel, 0X36); sunxi_lcd_para_write(sel, 0x08);
LVGL 出现 DMA Over Size
这是由于 LVGL 配置的
LV_COLOR_DEPTH
为 32,但是 SPI 屏配置为16位。请修改lv_conf.h
出现部分花屏
- 检查
address
函数是否正确 - 检查
sys_config.fex
屏幕配置分辨率是否正确
总结
调试LCD显示屏实际上就是调试发送端芯片(全志SOC)和接收端芯片(LCD屏上的driver IC)的一个过程:
- 添加屏驱动请看编写屏驱动
- 仔细阅读屏手册以及driver IC手册(有的话)。
- 仔细阅读板级显示配置参数详解。
- 确保LCD所需要的各路电源管脚正常。
SPI LCD 颜色相关问题
首先,得先确定显示屏使用的是SPI接口,还是DBI接口,不同的接口,输入数据的解析方式是不一样的。
DBI接口的全称是
Display Bus Serial Interface
,在显示屏数据手册中,一般会说这是SPI接口,所以有人会误认为SPI屏可以使用normal spi
去直接驱动。阅读
lcd_dbi_if
部分的介绍可以知道,在3线模式时,发送命令前有1位A0用于指示当前发送的是数据,还是命令。而命令后面接着的数据就没有这个A0位了,代表SPI需要在9位和8位之间来回切换,而在读数据时,更是需要延时dummy clock
才能读数据,normal spi
都很难,甚至无法实现。所以normal spi
只能模拟4 线的DBI的写操作。对于R128这类支持DBI接口的CPU,可以选择不去了解SPI。如果需要用到SPI去驱动显示屏,必须把显示屏设置成小端。
RGB565和RGB666
SPI显示屏一般支持RGB444,RGB565和RGB666,RGB444使用的比较少,所以只讨论RGB565和RGB666.
RGB565代表一个点的颜色由2字节组成,也就是R(红色)用5位表示,G(绿色)用6位表示,B(蓝色)用5位表示,如下图所示:
RGB666一个点的颜色由3字节组成,每个字节代表一个颜色,其中每个字节的低2位会无视,如下图所示:SPI 接口
因为SPI接口的通讯效率不高,所以建议使用RGB565的显示,以
jlt35031c
显示屏为例,他的显示驱动芯片是ST7789
,设置显示格式的方式是往3a
寄存器写入0x55(RGB565
)或者0x66(RGB666)
,在R128SDK
中,已经把jlt35031c
的通讯格式写死为0x55
,lcd_pixel_fmt
配置选项无效:sunxi_lcd_cmd_write(sel, 0x3a); sunxi_lcd_para_write(sel, 0x55);
在例程中,输入的数据是
0xff,0x00,0xff,0x00
,对于SPI接口,是按字节发送。实际上,例程只需要每次发送2字节即可,因为前后发送的都是相同的ff 00,所以没有看出问题。根据对
565
的数据解析,我们拆分ff 00
就可以得到红色分量是0b11111
,也就是31
,绿色是0b111000
,也就是56
,,蓝色是0
.我们等效转换成RGB888
,有:R = 31/31*255 = 255 G = 56/63*255 = 226
在调色板输入对应颜色,就可以得到黄色
DBI 接口
因为
DBI
通讯效率较高,所以可以使用RGB565
或者RGB666
,使用DBI
接口,也就是lcd_if
设置为1
时,驱动会根据lcd_pixel_fmt
配置寄存器,以SDK
中的kld2844b.c
为例,这显示屏的显示驱动也是ST7789
,但是不同的屏幕,厂家封装时已经限制了通讯方式,所以即使是能使用 DBI 接口的驱动芯片的屏幕,或许也用不了DBI。sunxi_lcd_cmd_write(sel, 0x3A); /* Interface Pixel Format */ /* 55----RGB565;66---RGB666 */ if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGR_565) { sunxi_lcd_para_write(sel, 0x55); if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565) rotate &= 0xf7; else rotate |= 0x08; } else if (info[sel].lcd_pixel_fmt < LCDFB_FORMAT_RGB_888) { sunxi_lcd_para_write(sel, 0x66); if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRA_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRX_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_ABGR_8888 || info[sel].lcd_pixel_fmt == LCDFB_FORMAT_XBGR_8888) { rotate |= 0x08; } } else { sunxi_lcd_para_write(sel, 0x66); }
对于 DBI 格式,不再是以字节的形式去解析,而是以字的方式去解析,为了统一,软件已经规定了,
RGB565
格式时,字大小是2字节,也就是16位,而RGB666
格式时,字大小是4字节,也就是32位。对于
RGB565
格式,同样是设置为0xff,0x00
。因为屏幕是大端,而芯片存储方式是小端,所以芯片的 DBI 模块,会自动把数据从新排列,也就是实际上 DBI 发送数据时,会先发送0x00
,再发送0xff
,也就是红色分量为0,绿色分量为0b000111
,也就是7,蓝色分量是0x11111
,也就是31,我们同样转换成RGB888G = 7/63*255 = 28 B= 31/31*255 = 255
在调色板上输入,可以得到蓝色。
如果是
RGB666
,虽然占用的是3个字节,但是没有CPU是3字节对齐的,所以需要一次性输入4字节,然后 DBI 硬件模块,会自动舍弃1个字节,软件同意舍弃了最后一个字节。依旧以例程为例,例程输入了
0xff,0x00,0xff,0x00
,为了方便说明,标准为0xff(1),0x00(1),0xff(2),0x00(2)
,其中0x00(2)
会被舍弃掉,然后发送顺序是0xff(2),0x00(1),0xff(1)
,也就是0xff(2)
是红色分量,0xff(1)
是蓝色分量,混合可以得到紫色。 -
回复: YuzukiChameleon,xr829扫卡失败[XRADIO_ERR] mac address not found in efuse.
看看这个帖子有没有帮助:
【FAQ】全志D1芯片 XR829扫卡失败问题排查
https://bbs.aw-ol.com/topic/757/share/1 -
【R128】应用开发案例——获取真随机数
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
获取真随机数
本文案例代码 下载地址 获取真随机数案例代码 https://www.aw-ol.com/downloads?cat=24 R128 内置了TRNG,一个真随机数发生器,随机源是 8 路独立的环形振荡器,由模拟器件电源噪声产生频率抖动,用低频始终重采样,然后进行弹性抽取和熵提取处理,最终输出128bit真随机数。
载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 TRNG驱动
运行
mrtos_menuconfig
进入配置页面。前往下列地址找到TRNG Devices
Drivers Options ---> soc related device drivers ---> TRNG Devices ---> -*- enable trng driver
编写程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
引入头文件
#include <sunxi_hal_trng.h>
初始化 TRNG 读取数据模块
uint32_t random[4] = {0}; HAL_TRNG_Extract(0, random); // 读取 CRC 模式 printf("trng CRC result: 0x%08x 0x%08x 0x%08x 0x%08x\n", random[0], random[1], random[2], random[3]); HAL_TRNG_Extract(1, random); // 读取 XOR 模式 printf("trng XOR result: 0x%08x 0x%08x 0x%08x 0x%08x\n", random[0], random[1], random[2], random[3]);
结果
编译固件后烧录,可以看到随机数输出。
-
【R128】基础组件开发指南——显示与屏幕驱动
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
显示与屏幕驱动
RTOS 提供了一套完整的屏幕驱动,支持 RGB, i8080, SPI, DBI 格式的屏幕。
(1)RGB 接口
RGB接口在全志平台又称HV接口(Horizontal同步和Vertical同步)。有些LCD屏支持高级的功能比如 gamma,像素格式的设置等,但是 RGB 协议本身不支持图像数据之外的传输,所以无法通过 RGB 管脚进行对 LCD 屏进行配置,所以拿到一款 RGB 接口屏,要么不需要初始化命令,要么这个屏会提供额外的管脚给 SoC 来进行配置,比如 SPI 和 I2C 等。RGB 屏幕有许多格式,不同的位宽,不同的时钟周期。下表是位宽与时钟周期的区别。
位宽 时钟周期数 颜色数量和格式 并行\串行 RGB 24 bits 1 cycle 16.7M colors, RGB888 并行 18 bits 1 cycle 262K colors, RGB666 并行 16 bits 1 cycle 65K colors, RGB565 并行 6 bits 3 cycles 262K colors, RGB666 串行 6 bits 3 cycles 65K colors, RGB565 串行 串行 RGB 是相对于并行 RGB 来说,而并不是说它只用一根线来发数据,只要通过多个时钟周期才能把一个像素的数据发完,那么这样的 RGB 接口就是串行 RGB。
(2)I8080 屏幕
Intel 8080 接口屏(又称 MCU 接口),很老的协议,一般用在分辨率很小的屏上。
管脚的控制脚有6种:
- CS 片选信号,决定该芯片是否工作.
- RS 寄存器选择信号,低表示选择 index 或者 status 寄存器,高表示选择控制寄存器。实际场景中一般接SoC的LCD_DE脚(数据使能脚)
- WR (低表示写数据) 数据命令区分信号,也就是写时钟信号,一般接 SoC 的 LCD_CLK 脚
- RD (低表示读数据)数据读信号,也就是读时钟信号,一般接 SoC 的 LCD_HSYNC 脚
- RESET 复位LCD( 用固定命令系列 0 1 0来复位)
- Data 是双向的数据通路
I8080 根据的数据位宽接口有 8/9/16/18,连哪些脚参考,即使位宽一样,连的管脚也不一样,还要考虑的因素是 RGB 格式。
- RGB565,总共有 65K 这么多种颜色
- RGB666,总共有 262K 那么多种颜色
- 9bit 固定为 262K
(3)SPI 屏幕
SPI LCD 是使用 SPI 总线传输图像数据的屏幕,只会出现在很低分辨率的屏幕上。一般来说开屏前都需要初始化操作。
适配 LCD 屏幕的步骤
- 确保全志显示框架的内核配置有使能
- 前期准备以下资料和信息:
- 屏手册。主要是描述屏基本信息和电气特性等,向屏厂索要。
- Driver IC 手册。主要是描述屏 IC 的详细信息。这里主要是对各个命令进行详解,对我们进行初始化定制有用,向屏厂索要。
- 屏时序信息。请向屏厂索要。
- 屏初始化代码,请向屏厂索要。一般情况下 DSI 和 I8080 屏等都需要初始化命令对屏进行初始化。
- 万用表。调屏避免不了测量相关电压。
- 通过第2步屏厂提供的资料,定位该屏的类型,然后选择一个已有同样类型的屏驱动作为模板进行屏驱动添加或者直接在上面修改。
- 修改屏驱动目录下的
panel.c
和panel.h
。在全局结构体变量panel_array
中新增刚才添加strcut __lcd_panel
的变量指针。panel.h
中新增strcut __lcd_panel
的声明。 - 修改 Makefile。在 lcd 屏驱动目录的上一级的
Makefile
文件中的disp-objs
中新增刚才添加屏驱动.o - 修改
sys_config.fex
中的lcd0
节点。 - 编译测试
LCD 屏幕驱动源码
LCD 屏幕驱动源码结构
. ├── Kconfig ├── Makefile ├── disp │ ├── Kconfig │ ├── Makefile │ ├── de # Display Engine 层驱动,包括图层与显示控制 │ │ ├── Makefile │ │ ├── bsp_display.h │ │ ├── disp_capture.c │ │ ├── disp_capture.h │ │ ├── disp_device.c │ │ ├── disp_device.h │ │ ├── disp_display.c │ │ ├── disp_display.h │ │ ├── disp_enhance.c │ │ ├── disp_enhance.h │ │ ├── disp_features.c │ │ ├── disp_features.h │ │ ├── disp_hdmi.c │ │ ├── disp_hdmi.h │ │ ├── disp_lcd.c │ │ ├── disp_lcd.h │ │ ├── disp_manager.c │ │ ├── disp_manager.h │ │ ├── disp_private.c │ │ ├── disp_private.h │ │ ├── disp_smart_backlight.c │ │ ├── disp_smart_backlight.h │ │ ├── disp_tv.c │ │ ├── disp_tv.h │ │ ├── disp_vdevice.c │ │ ├── disp_vdevice.h │ │ ├── include.h │ │ └── lowlevel_v2x # DISP 底层驱动,硬件寄存器交互 │ │ ├── Makefile │ │ ├── de_ase.c │ │ ├── de_ase_type.h │ │ ├── de_bws.c │ │ ├── de_bws_type.h │ │ ├── de_ccsc.c │ │ ├── de_clock.c │ │ ├── de_clock.h │ │ ├── de_csc.h │ │ ├── de_csc_type.h │ │ ├── de_dcsc.c │ │ ├── de_dsi.c │ │ ├── de_dsi.h │ │ ├── de_dsi_28.c │ │ ├── de_dsi_type.h │ │ ├── de_dsi_type_28.h │ │ ├── de_eink.c │ │ ├── de_eink.h │ │ ├── de_enhance.c │ │ ├── de_enhance.h │ │ ├── de_fcc.c │ │ ├── de_fcc_type.h │ │ ├── de_fce.c │ │ ├── de_fce_type.h │ │ ├── de_feat.c │ │ ├── de_feat.h │ │ ├── de_gsu.c │ │ ├── de_gsu_type.h │ │ ├── de_hal.c │ │ ├── de_hal.h │ │ ├── de_lcd.c │ │ ├── de_lcd.h │ │ ├── de_lcd_sun50iw10.c │ │ ├── de_lcd_type.h │ │ ├── de_lti.c │ │ ├── de_lti_type.h │ │ ├── de_peak.c │ │ ├── de_peak_type.h │ │ ├── de_rtmx.c │ │ ├── de_rtmx.h │ │ ├── de_rtmx_type.h │ │ ├── de_scaler.h │ │ ├── de_scaler_table.c │ │ ├── de_scaler_table.h │ │ ├── de_smbl.c │ │ ├── de_smbl.h │ │ ├── de_smbl_tab.h │ │ ├── de_smbl_type.h │ │ ├── de_vep.h │ │ ├── de_vep_table.c │ │ ├── de_vep_table.h │ │ ├── de_vsu.c │ │ ├── de_vsu_type.h │ │ ├── de_wb.c │ │ ├── de_wb.h │ │ ├── de_wb_type.h │ │ ├── disp_al.c │ │ ├── disp_al.h │ │ ├── disp_eink_data.c │ │ ├── disp_eink_data.h │ │ ├── disp_waveform.c │ │ ├── disp_waveform.h │ │ ├── rtmx_eink.c │ │ └── rtmx_eink.h │ ├── dev_disp.c # DISP 公共端口 │ ├── dev_disp.h │ ├── disp_debug.c │ ├── disp_debug.h │ ├── disp_sys_intf.c │ ├── disp_sys_intf.h │ ├── lcd # LCD 面板驱动,包括自定义初始化控制,上下电时序控制 │ │ ├── Kconfig │ │ ├── S6D7AA0X01.c │ │ ├── S6D7AA0X01.h │ │ ├── VVX07H005A10.c │ │ ├── VVX07H005A10.h │ │ ├── WilliamLcd.c │ │ ├── WilliamLcd.h │ │ ├── b080uan01_mipi1200x1920.c │ │ ├── b080uan01_mipi1200x1920.h │ │ ├── cl40bc1019_cpu.c │ │ ├── cl40bc1019_cpu.h │ │ ├── cpu_gg1p4062utsw.c │ │ ├── cpu_gg1p4062utsw.h │ │ ├── default_eink.c │ │ ├── default_eink.h │ │ ├── default_panel.c │ │ ├── default_panel.h │ └── wtq05027d01.h ├── soc # SoC 层特化驱动 │ ├── Kconfig │ ├── Makefile │ ├── VVX07H005A10_mipi_config.c │ ├── disp_board_config.c │ ├── disp_board_config.h │ ├── he0801a068_mipi_config.c │ ├── platform_resource.c │ ├── platform_resource.h │ ├── sun20iw2.c └── tv # TV 驱动,R128不使用 ├── Makefile ├── de_tve_sun8iw11.c ├── de_tve_sun8iw7.c ├── de_tve_v1.c ├── de_tvec.h ├── drv_tv.c ├── drv_tv.h ├── gm7121.c ├── tv_ac200.c ├── tv_ac200.h ├── tv_ac200_lowlevel.c └── tv_ac200_lowlevel.h
屏驱动源码位置
RGB 面板驱动
对于不需要初始化的 RGB 屏幕(一般是 40PIN,50PIN)使用
default_panel.c
lichee/rtos‑hal/hal/source/disp2/disp/lcd/default_panel.c
LCD 面板特化驱动
部分 LCD 面板需要写 IIC,SPI初始化,或者有特殊的上下电要求,需要编写特化的屏幕驱动
lichee/rtos‑hal/hal/source/disp2/disp/lcd/
配置文件
其中 “芯片型号” 例如 r128s3,和 “板子名称” 例如 pro,请根据实际替换。
board/芯片型号/板子名称/configs/
屏幕驱动配置
lcd 相关代码包含在 disp 驱动模块中,执行命令进入 menuconfig 配置主界面,并按以下步骤操作:
添加新屏
添加一款新屏幕通常需要以下步骤:
panel.c
和panel.h
,当用户添加新屏驱动时,是需要修改这两个文件的,需要将屏结构体变量添加到全局结构体变量panel_array中。lcd_source.c
和lcd_source.h
,这两个文件实现的是给屏驱动使用的函数接口,比如电源开关,gpio,dsi 读写接口等,用户不需要修改只需要用。- 屏驱动。除了上面提到的源文件外,其它的一般一个 c 文件和一个 h 文件就代表一个屏驱动。
- 在屏驱动源码位置的上一级,有用户需要修改的
Makefile
文件。
我们可以打开
lichee/rtos‑hal/hal/source/disp2/disp/lcd/default_panel.c
作为屏驱动的例子,在该文件的最后:struct __lcd_panel default_panel = { /* panel driver name, must mach the lcd_drv_name in sys_config.fex */ .name = "default_lcd", .func = { .cfg_panel_info = LCD_cfg_panel_info, .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, }, };
-
该全局变量
default_panel
的成员name
与lcd_driver_name
必须一致,这个关系到驱动能否找到指定的文件。 -
接 下 来 是
func
成 员 的 初 始 化, 这 里 最 主 要 实 现 三 个 回 调 函 数。LCD_cfg_panel_info
,LCD_open_flow
和LCD_close_flow
。 -
开关屏流程即屏上下电流程,屏手册或者 driver IC 手册中里面的 Power on Sequence 和 Power off Sequence。用于开关屏的操作流程如下图所示
- 其中,
LCD_open_flow
和LCD_close_flow
称为开关屏流程函数。方框中的函数,如LCD_power_on
,称为开关屏步骤函数。 - 不需要进行初始化操作的 LCD 屏,例如部分 RGB 屏等,
LCD_panel_init
及LCD_panel_exit
这些函数可以为空。
LCD_open_flow
LCD_open_flow 函数只会在系统初始化的时候调用一次,执行每个 LCD_OPEN_FUNC 即是把对应的开屏步骤函数进行注册,先注册先执行,但并没有立刻执行该开屏步骤函数。
函数原型:
static __s32 LCD_open_flow(__u32 sel)
函数常用内容为:
static __s32 LCD_open_flow(__u32 sel) { LCD_OPEN_FUNC(sel, LCD_power_on,10); LCD_OPEN_FUNC(sel, LCD_panel_init, 50); LCD_OPEN_FUNC(sel, sunxi_lcd_tcon_enable, 100); LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; }
如上,调用四次 LCD_OPEN_FUNC 注册了四个回调函数,对应了四个开屏流程, 先注册先执行。实际上注册多少个函数是用户自己的自由,只要合理即可。
- LCD_power_on 即打开 LCD 电源,再延迟 10ms;这个步骤一般用于打开 LCD 相关电源和相关管脚比如复位脚。这里一般是使用电源控制函数说明和管脚控制函数说明进行操作。
- LCD_panel_init 即初始化屏,再延迟 50ms;不需要初始化的屏,可省掉此步骤,这个函数一般用于发送初始化命令给屏进行屏初始化。如果是 I8080 屏用I8080 接口函数说明,如果是其它情况比如 i2c 或者 spi 可以看使用 iic/spi 串行接口初始化,也可以用 GPIO 来进行模拟。
- sunxi_lcd_tcon_enable 打开 TCON,再延迟 100ms;这一步是固定的,表示开始发送图像信号。
- LCD_bl_open 打开背光,再延迟 0ms。前面三步搞定之后才开背光,这样不会看到闪烁。这里一般使用的函数请看背光控制函数说明。
LCD_OPEN_FUNC
注册开屏步骤函数到开屏流程中,记住这里是注册不是执行!
函数原型:
void LCD_OPEN_FUNC(__u32 sel, LCD_FUNC func, __u32 delay)
参数说明:
func
是一个函数指针,其类型是:void (*LCD_FUNC) (__u32 sel)
,用户自己定义的函数必须也要用统一的形式。比如:void user_defined_func(__u32 sel) { // do something }
delay
是执行该步骤后,再延迟的时间,时间单位是毫秒。LCD_OPEN_FUNC 的第二个参数是前后两个步骤的延时长度,单位 ms,注意这里的数值请按照屏手册规定去填,乱填可能导致屏初始化异常或者开关屏时间过长,影响用户体验。
LCD_close_flow
与
LCD_open_flow
对应的是LCD_close_flow
,它用于注册关屏函数。使用LCD_CLOSE_FUNC
进行函数注册,先注册先执行。这里只是注册回调函数,不是立刻执行。static s32 LCD_close_flow(u32 sel) { /* close lcd backlight, and delay 0ms */ LCD_CLOSE_FUNC(sel, LCD_bl_close, 0); /* close lcd controller, and delay 0ms */ LCD_CLOSE_FUNC(sel, sunxi_lcd_tcon_disable, 50); /* open lcd power, than delay 200ms */ LCD_CLOSE_FUNC(sel, LCD_panel_exit, 100); /* close lcd power, and delay 500ms */ LCD_CLOSE_FUNC(sel, LCD_power_off, 0); return 0; }
- 先关闭背光,这样整个关屏过程,用户不会看到闪烁的过程;
- 关闭 TCON(即停止发送数据)再延迟 50ms;
- 执行关屏代码,再延迟 200ms;(不需要初始化的屏,可省掉此步骤)
- 最后关闭电源,再延迟 0ms。
LCD_cfg_panel_info
配置的 TCON 扩展参数,比如 gamma 功能和颜色映射功能。
函数原型:
static void LCD_cfg_panel_info(__panel_extend_para_t *info)
TCON 的扩展参数只能在屏文件中配置,参数的定义:
lcd_frm
Lcd Frame Rate Modulator, FRM 是解决由于 PIN 减少导致的色深问题,有些 LCD 屏的像素格式是 18bit 色深(RGB666)或 16bit 色深(RGB565),建议打开 FRM 功能,通过 dither 的方式弥补色深,使显示达到 24bit 色深(RGB888)的效果。如下图所示,上图是色深为 RGB66 的 LCD 屏显示,下图是打开 dither 后的显示,打开 dither 后色彩渐变的地方过度平滑。
参数设置相应值对应含义为:
0:RGB888 ‑‑ RGB888 direct 1:RGB888 ‑‑ RGB666 dither 2:RGB888 ‑‑ RGB565 dither
lcd_gamma_en
Lcd Gamma Correction Enable,设置相应值的对应含义为:
0:LCD 的 Gamma 校正功能关闭 1:LCD 的 Gamma 校正功能开启
设置为 1 时,需要在屏驱动中对
lcd_gamma_tbl[256]
进行赋值。lcd_cmap_en
Lcd Color Map Enable, 设置为 1 时,需要对
lcd_cmap_tbl [2][3][4]
进行赋值Lcd Color Map Table
。每个像素有 R、G、B 三个单元,每四个像素组成一个选择项,总共有 12 个可选。数组第一维表示奇偶行,第二维表示像素的 RGB,第三维表示第几个像素,数组的内容即表示该位置映射到的内容。
LCD CMAP 是对像素的映射输出功能,只有像素有特殊排布的 LCD 屏才需要配置。
LCD CMAP 定义每行的 4 个像素为一个总单元,每个像素分 R、G、B 3 个小单元,总共有 12 个小单元。通过 lcd_cmap_tbl 定义映射关系,输出的每个小单元可随意映射到 12 个小单元之一。
__u32 lcd_cmap_tbl[2][3][4] = { { {LCD_CMAP_G0,LCD_CMAP_B1,LCD_CMAP_G2,LCD_CMAP_B3}, {LCD_CMAP_B0,LCD_CMAP_R1,LCD_CMAP_B2,LCD_CMAP_R3}, {LCD_CMAP_R0,LCD_CMAP_G1,LCD_CMAP_R2,LCD_CMAP_G3}, }, { {LCD_CMAP_B3,LCD_CMAP_G2,LCD_CMAP_B1,LCD_CMAP_G0}, {LCD_CMAP_R3,LCD_CMAP_B2,LCD_CMAP_R1,LCD_CMAP_B0}, {LCD_CMAP_G3,LCD_CMAP_R2,LCD_CMAP_G1,LCD_CMAP_R0}, }, };
如上,上三行代表奇数行的像素排布,下三行代表偶数行的像素排布;
每四个像素为一个单元,第一列代表每四个像素的第一个像素映射,第二列代表每四个像素的第二个像素映射,以此类推。
如上的定义,像素的输出格式如下图所示。
lcd_rb_swap
调换
TCON
模块RGB
中的 R 分量和 B 分量。0:不变 1:调换R分量和B分量
需要 gamma 校正,或色彩映射,在
sys_config.fex
中将相应模块的enable
参数置 1,lcd_gamma_en
,lcd_cmap_en
,并且填充 3 个系数表,lcd_gamma_tbl
,lcd_cmap_tbl
,注意的是:gamma,模板提供了 18 段拐点值,然后再插值出所有的值(255 个)。可以往相应表格内添加子项以补充细节部分。cmap_tbl 的大小是固定的,不能减小或增加表的大小。最终生成的 gamma 表项是由 rgb 三个 gamma 值组成的,各占 8bit。目前提供的模板中,三个 gamma 值是相同的。延时函数
函数原型
(毫秒级别)
s32 sunxi_lcd_delay_ms(u32 ms)
(微秒级别)
s32 sunxi_lcd_delay_us(u32 us)
图像数据使能函数
打开 LCD 控制器,开始刷新 LCD 显示
void sunxi_lcd_tcon_enable(u32 screen_id)
关闭 LCD 控制器,停止刷新数据
void sunxi_lcd_tcon_disable(u32 screen_id)
背光控制函数
打开背光,操作的是
sys_config.fex
中lcd_bl
配置的gpio
。void sunxi_lcd_backlight_enable(u32 screen_id)
关闭背光,操作的是
sys_config.fex
中lcd_bl
配置的gpio
。void sunxi_lcd_backlight_disable(u32 screen_id)
打开PWM背光,打开时 pwm 将往外输出 pwm 波形。对应的是
lcd_pwm_ch
所对应的那一路 pwm。s32 sunxi_lcd_pwm_enable(u32 screen_id)
关闭PWM背光,打开时 pwm 将往外输出 pwm 波形。对应的是
lcd_pwm_ch
所对应的那一路 pwm。s32 sunxi_lcd_pwm_disable(u32 screen_id)
电源控制函数
打开
Lcd
电源,操作的是sys_config.fex
中的lcd_power/lcd_power1/lcd_power2
。(pwr_id
标识电源索引)void sunxi_lcd_power_enable(u32 screen_id, u32 pwr_id)
关闭
Lcd
电源,操作的是sys_config.fex
中的lcd_power/lcd_power1/lcd_power2
。(pwr_id
标识电源索引)void sunxi_lcd_power_disable(u32 screen_id, u32 pwr_id)
-
pwr_id = 0:对应于 sys_config.fex 中的 lcd_power。
-
pwr_id = 1:对应于 sys_config.fex 中的 lcd_power1。
-
pwr_id = 2:对应于 sys_config.fex 中的 lcd_power2。
-
pwr_id = 3:对应于 sys_config.fex 中的 lcd_power3。
sunxi_lcd_pin_cfg
配置 lcd 的 io
函数原型
s32 sunxi_lcd_pin_cfg(u32 screen_id, u32 bon)
配置
lcd
的data/clk
等pin
,对应sys_config.fex
中的lcdd0‑lcdd23/lcddclk/lcdde/lcdhsync/lcdvsync
。参数:
- Bon: 1: 为开,0:为配置成 disable 状态。
I8080 接口函数说明
显示驱动提供 5 个接口函数可供使用。如下:
sunxi_lcd_cpu_write
设定 CPU 屏的指定寄存器为指定的值。
函数原型
void sunxi_lcd_cpu_write(__u32 sel, __u32 index, __u32 data) { sunxi_lcd_cpu_write_index(sel, index); sunxi_lcd_cpu_wirte_data(sel, data); }
实现了 8080 总线上的两个写操作
sunxi_lcd_cpu_write_index
实现第一个写操作,这时 PIN 脚 RS(A1)为低电平,总线数据上的数据内容为参数 index 的值。sunxi_lcd_cpu_wirte_data
实现第二个写操作,这时 PIN 脚 RS(A1)为高电平,总线数据上的数据内容为参数 data 的值。
sunxi_lcd_cpu_write_index
设定 CPU 屏为指定寄存器。
void sunxi_lcd_cpu_write_index(__u32 sel,__u32 index)
参数:
- sel:显示屏 id
- index: 要设定的寄存器
sunxi_lcd_cpu_write_data
设定 CPU 屏寄存器的值为指定的值
void sunxi_lcd_cpu_write_data(__u32 sel, __u32 data)
参数:
- sel:显示屏 id
- index: 要设定的寄存器的值
tcon0_cpu_rd_24b_data
读操作
s32 tcon0_cpu_rd_24b_data(u32 sel, u32 index, u32 *data, u32 size)
参数:
- sel:显示屏 id
- index: 要读取的寄存器
- data:用于存放读取接口的数组指针,用户必须保证其有足够空间存放数据
- size:要读取的字节数
管脚控制函数
sunxi_lcd_gpio_set_value
LCD_GPIO PIN 脚上输出高电平或低电平
s32 sunxi_lcd_gpio_set_value(u32 screen_id, u32 io_index, u32 value)
参数:
- io_index = 0:对应于 sys_config.fex 中的 lcd_gpio_0。
- io_index = 1:对应于 sys_config.fex 中的 lcd_gpio_1。
- io_index = 2:对应于 sys_config.fex 中的 lcd_gpio_2。
- io_index = 3:对应于 sys_config.fex 中的 lcd_gpio_3。
- value = 0:对应 IO 输出低电平。
- value = 1:对应 IO 输出高电平。
只用于该 GPIO 定义为输出的情形。
sunxi_lcd_gpio_set_direction
设置 LCD_GPIO PIN 脚为输入或输出模式
s32 sunxi_lcd_gpio_set_direction(u32 screen_id, u32 io_index, u32 direction)
参数:
- io_index = 0:对应于 sys_config.fex 中的 lcd_gpio_0。
- io_index = 1:对应于 sys_config.fex 中的 lcd_gpio_1。
- io_index = 2:对应于 sys_config.fex 中的 lcd_gpio_2。
- io_index = 3:对应于 sys_config.fex 中的 lcd_gpio_3。
- direction = 0:对应 IO 设置为输入。
- direction = 1:对应 IO 设置为输出。
一部分屏需要进行初始化操作,在开屏步骤函数中,对应于
LCD_panel_init
函数,提供了几种方式对屏的初始化。对于 CPU 屏,是通过 8080 总线的方式,使用的是 LCDIO(PD,PH)进行初始化。这种初始化方式,其总线的引脚位置定义与 CPU 屏一致。对于 SPI/IIC 初始化的 LCD,使用独立的IO初始化。使用 SPI 初始化
一般使用 GPIO 模拟的方式初始化 SPI 屏幕,其中 SPI 模拟如下所示
#define spi_scl_1 sunxi_lcd_gpio_set_value(0, 3, 1) // 配置 lcd_gpio_3 为 SCL #define spi_scl_0 sunxi_lcd_gpio_set_value(0, 3, 0) #define spi_sdi_1 sunxi_lcd_gpio_set_value(0, 2, 1) // 配置 lcd_gpio_2 为 SDI #define spi_sdi_0 sunxi_lcd_gpio_set_value(0, 2, 0) #define spi_cs_1 sunxi_lcd_gpio_set_value(0, 1, 1) // 配置 lcd_gpio_1 为 CS #define spi_cs_0 sunxi_lcd_gpio_set_value(0, 1, 0) static void spi_write_cmd(u8 value) { int i; spi_cs_0; spi_scl_0; spi_sdi_0; spi_scl_1; spi_scl_0; for (i = 0; i < 8; i++) { if (value & 0x80) spi_sdi_1; else spi_sdi_0; value <<= 1; spi_scl_1; spi_scl_0; } spi_cs_1; } static void spi_write_data(u8 value) { int i; spi_cs_0; spi_scl_0; spi_sdi_1; spi_scl_1; spi_scl_0; for (i = 0; i < 8; i++) { if (value & 0x80) spi_sdi_1; else spi_sdi_0; value <<= 1; spi_scl_1; spi_scl_0; } spi_cs_1; }
然后就可以调用
spi_write_cmd(u8 value)
与spi_write_data(u8 value)
函数写入初始化命令。也可以使用 硬件 SPI 初始化屏幕,代码如下
static int spi_init(void) { int ret = ‑1; struct spi_master *master; master = spi_busnum_to_master(1); if (!master) { lcd_fb_wrn("fail to get master\n"); goto OUT } spi_device = spi_alloc_device(master); if (!spi_device) { lcd_fb_wrn("fail to get spi device\n"); goto OUT; } spi_device‑> bits_per_word = 8; spi_device‑> max_speed_hz = 50000000; /*50MHz*/ spi_device‑> mode = SPI_MODE_0; ret = spi_setup(spi_device); if (ret) { lcd_fb_wrn("Faile to setup spi\n"); goto FREE; } lcd_fb_inf("Init spi1:bits_per_word:%d max_speed_hz:%d mode:%d\n", spi_device‑> bits_per_word, spi_device‑> max_speed_hz, spi_device‑> mode); ret = 0; goto OUT; FREE: spi_master_put(master); kfree(spi_device); spi_device = NULL; OUT: return ret; } static int comm_out(unsigned int sel, unsigned char cmd) { struct spi_transfer t; if (!spi_device) return ‑1; DC(sel, 0); memset(&t, 0, sizeof(struct spi_transfer)); t.tx_buf = &cmd; t.len = 1; t.bits_per_word = 8; t.speed_hz = 24000000; return spi_sync_transfer(spi_device, &t, 1); }
首先调用
spi_init
函数对spi
硬件进行初始化,spi_init
函数可以分为几个步骤,第一获取master
;根据实际的硬件连接,选择spi
(代码中选择了spi1
),如果这一步返回错误说spi
没有配置好。第二步设置spi device
,这里包括最大速度,spi
传输模式,以及每个字包含的比特数。最后调用spi_setup
完成master
和device
的关联。comm_out
是一个spi
传输的例子,核心就是spi_sync_transfer
函数。并行 RGB 接口
当我们配置并行 RGB 接口时,在配置里面并不需要区分是 24 位,18 位和 16 位,最大位宽是哪种是参考 pin mux 表格,如果 LCD 屏本身支持的位宽比 SoC 支持的位宽少,当然只能选择少的一方。
因为不需要初始化,RGB 接口极少出现问题,重点关注 lcd 的 timing 的合理性,也就是lcd_ht,lcd_hspw,lcd_hbp,lcd_vt,lcd_vspw 和 lcd_vbp 这个属性的合理性。
下面是典型并行 RGB 接口 sys_config.fex 配置示例,其中用空行把配置分成几个部分
;-------------------------------------------------- ;Parallel RGB LCD ;-------------------------------------------------- [lcd0] ; Part 1 lcd_used = 1 lcd_driver_name = "default_lcd" ; Part 2 lcd_if = 0 ; Part 3 lcd_x = 480 lcd_y = 480 lcd_width = 150 lcd_height = 94 lcd_rb_swap = 0 lcd_dclk_freq = 21 lcd_hv_clk_phase = 1 ; Part 4 lcd_backlight = 150 lcd_pwm_used = 1 lcd_pwm_ch = 5 lcd_pwm_freq = 5000 lcd_pwm_pol = 1 ; Part 5 lcd_hbp = 80 lcd_ht = 648 lcd_hspw = 8 lcd_vbp = 10 lcd_vt = 522 lcd_vspw = 2 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 1 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 deu_mode = 0 lcdgamma4iep = 22 smart_color = 90 ; Part 6 ;LCD_D2-LCD_D7 lcd_gpio_4 = port:PA00<8><0><3><0> lcd_gpio_5 = port:PA01<8><0><3><0> lcd_gpio_6 = port:PA02<8><0><3><0> lcd_gpio_7 = port:PA03<8><0><3><0> lcd_gpio_8 = port:PA04<8><0><3><0> lcd_gpio_9 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D15 lcd_gpio_10 = port:PA11<8><0><3><0> lcd_gpio_11 = port:PA10<8><0><3><0> lcd_gpio_12 = port:PA08<8><0><3><0> lcd_gpio_13 = port:PA07<8><0><3><0> lcd_gpio_14 = port:PA06<8><0><3><0> lcd_gpio_15 = port:PA09<8><0><3><0> ;LCD_D18-LCD_D23 lcd_gpio_16 = port:PA12<8><0><3><0> lcd_gpio_17 = port:PA13<8><0><3><0> lcd_gpio_18 = port:PA14<8><0><3><0> lcd_gpio_19 = port:PA15<8><0><3><0> lcd_gpio_20 = port:PB03<8><0><3><0> lcd_gpio_21 = port:PB02<8><0><3><0> ;LCD_VSYNC, LCD_HSYNC, LCD_DCLK, LCD_DE lcd_gpio_0 = port:PA18<8><0><3><0> lcd_gpio_1 = port:PA19<8><0><3><0> lcd_gpio_2 = port:PA20<8><0><3><0> lcd_gpio_3 = port:PA21<8><0><3><0>
- 第一部分,决定该配置是否使用,以及使用哪个屏驱动,lcd_driver_name 决定了用哪个屏驱动来初始化,这里是 default_lcd,是针对不需要初始化设置的 RGB 屏。
- 第二部分决定下面的配置是一个并行 RGB 的配置。
- 第三部分决定 SoC 中的 LCD 模块发送时序。请查看屏时序参数说明。
- 第四部分决定背光(pwm 和 lcd_bl_en)。请看背光相关参数。
- 第五部分是显示效果部分的配置,如果非 24 位的 RGB,那么一般情况下需要设置lcd_frm。
- 第六部分就是电源和管脚配置。是用 RGB666 还是 RGB888,需要根据实际 pinmux 表来决定,如果该芯片只有 18 根 rgb 数据则只能 rgb18。请看电源和管脚参数。
串行 RGB 接口
串行 RGB 是相对于并行 RGB 来说,而并不是说它只用一根线来发数据,只要通过多个时钟周期才能把一个像素的数据发完,那么这样的 RGB 接口就是串行 RGB。
同样与并行 RGB 接口一样,配置中并不需要也无法体现具体是哪种串行 RGB 接口,你要做的就是把硬件连接对就行。
这里需要注意的是,对于该接口,SoC 总共需要三个周期才能发完一个 pixel,所以我们配置时序的时候,需要满足
lcd_dclk_freq*3=lcd_ht*lcd_vt60
,或者lcd_dclk_freq=lcd_ht/3*lcd_vt*60
要么 3 倍lcd_ht
要么 3 倍lcd_dclk_freq
。I8080 接口
Intel 8080 接口屏 (又称 MCU 接口) 很老的协议,一般用在分辨率很小的屏上
管脚的控制脚有 6 种:
管脚 作用说明 CS 片选信号 决定该芯片是否工作。 RS 寄存器选择信号 低表示选择 index 或者 status 寄存器,高表示选择控制寄存器。实际场景中一般接 SoC 的 LCD_DE 脚(数据使能脚)。 WR 数据命令区分信号 即写时钟信号,一般接 SoC 的 LCD_CLK 脚。(低表示写数据) RD 数据读信号 即读时钟信号,一般接 SoC 的 LCD_HSYNC 脚。(低表示读数据) RESET 复位 LCD(用固定命令系列 0 1 0 来复位) Data 双向数据 I8080 根据 Data 的数据位宽接口有 8/9/16/18,连哪些脚参考,即使位宽一样,连的管脚也不一样,还要考虑的因素是 rgb 格式。
- RGB565,总共有 65K 种颜色
- RGB666,总共有 262K 种颜色。
- 9bit 固定为 262K。
RGB 和 I8080 管脚配置示意图
sys_config 参数说明
LCD 接口参数说明
lcd_driver_name
Lcd 屏驱动的名字(字符串),必须与屏驱动的名字对应。
lcd_model_name
Lcd 屏模型名字,非必须,可以用于同个屏驱动中进一步区分不同屏。
lcd_if
Lcd Interface,设置相应值的对应含义为:
0:HV RGB接口 1:CPU/I80接口
lcd_hv_if
Lcd HV panel Interface, 这个参数只有在 lcd_if=0 时才有效。定义 RGB 同步屏下的几种接口类型,设置相应值的对应含义为:
0:Parallel RGB 8:Serial RGB 10:Dummy RGB 11:RGB Dummy 12:Serial YUV (CCIR656)
lcd_hv_clk_phase
这个参数只有在 lcd_if=0 时才有效。定义 RGB 同步屏的 clock 与 data 之间的相位关系。总共有 4个相位可供调节,设置相应值的对应含义为:
0: 0 degree 1: 90 degree 2: 180 degree 3: 270 degree
lcd_hv_sync_polarity
这个参数只有在 lcd_if=0 时才有效。定义 RGB 同步屏的 hsync 和 vsync 的极性。设置相应值的对应含义为:
0:vsync active low,hsync active low 1:vsync active high,hsync active low 2:vsync active low,hsync active high 3:vsync active high,hsync active high
lcd_hv_srgb_seq
这个参数只有在 lcd_if=0 且 lcd_hv_if=8(Serial RGB)时才有效。定义奇数行 RGB 输出的顺序:
0: Odd lines R‑G‑B; Even line R‑G‑B 1: Odd lines B‑R‑G; Even line R‑G‑B 2: Odd lines G‑B‑R; Even line R‑G‑B 4: Odd lines R‑G‑B; Even line B‑R‑G 5: Odd lines B‑R‑G; Even line B‑R‑G 6: Odd lines G‑B‑R; Even line B‑R‑G 8: Odd lines R‑G‑B; Even line B‑R‑G 9: Odd lines B‑R‑G; Even line G‑B‑R 10: Odd lines G‑B‑R; Even line G‑B‑R
lcd_hv_syuv_seq
这个参数只有在 lcd_if=0 且 lcd_hv_if=12(Serial YUV)时才有效。定义 YUV 输出格式:
0:YUYV 1:YVYU 2:UYVY 3:VYU
lcd_hv_syuv_fdly
这个参数只有在 lcd_if=0 且 lcd_hv_if=12(Serial YUV)时才有效。定义 CCIR656 编码时 F 相对有效行延迟的行数:
0:F toggle right after active video line 1:Delay 2 lines (CCIR PAL) 2:Delay 3 lines (CCIR NTSC)
lcd_cpu_if
这个参数只有在 lcd_if=1 时才有效, 具体时序可参照RGB 和 I8080 管脚配置示意图中 CPU 那几列。设置相应值的对应含义为:
0:18bit/1cycle (RGB666) 2: 16bit/3cycle (RGB666) 4:16bit/2cycle (RGB666) 6:16bit/2cycle (RGB666) 8:16bit/1cycle (RGB565) 10:9bit/1cycle (RGB666) 12:8bit/3cycle (RGB666) 14:8bit/2cycle (RGB565)
lcd_cpu_te
设置相应值的对应含义为,设置为 0 时,刷屏间隔时间为 lcd_ht × lcd_vt;设置为 1 或 2 时,刷屏间隔时间为两个 te 脉冲:
0:frame trigged automatically 1:frame trigged by te rising edge 2:frame trigged by te falling edge
lcd_cpu_mode
设置相应值的对应含义为,设置为 0 时,刷屏间隔时间为 lcd_ht × lcd_vt;设置为 1 或 2 时,刷屏间隔时间为两个 te 脉冲:
0:中断自动根据时序,由场消隐信号内部触发 1:中断根据数据Block的counter触发或者由外部te触发。
屏时序参数说明
下面几个参数对于调屏非常关键,决定了发送端(SoC)发送数据时序。由于涉及到发送端和接收端的调试,除了分辨率和尺寸之外,其它几个数值都不是绝对不变的,两款一样分辨率,同种接口的屏,它们的数值也有可能不一样。
获取途径如下:
- 询问 LCD 屏厂。
- 从屏手册或者 Driver IC 手册中查找(向屏厂索要这些文档)
- 在前面两步都搞不定的情况下,可以根据 vesa 标准来设置,主要是 DMT 和 CVT 标准。
由下面两条公式得知,我们不需要设置
lcd_hfp
和lcd_vfp
参数,因为驱动会自动根据其它几个已知参数中算出lcd_hfp
和lcd_vfp
。LCD 时序参数
lcd_x
显示屏的水平像素数量,即屏分辨率中的宽
lcd_y
显示屏的垂直行数,即屏分辨率中的高。
lcd_ht
指一行总的 dclk 的 cycle 个数。
lcd_hbp
指有效行间,行同步信号(hsync)开始,到有效数据开始之间的 dclk 的 cycle 个数,包括同步信号区。包含了 hspw 段,即lcd_hbp=实际的hbp+实际的hspw。
lcd_hspw
指行同步信号的宽度。单位为 1 个 dclk 的时间(即是 1 个 data cycle 的时间)。
lcd_vt
指一场的总行数。
lcd_vbp
指场同步信号(vsync)开始,到有效数据行开始之间的行数,包括场同步信号区。包含了 vspw 段,即 lcd_vbp= 实际的 vbp+ 实际的 vspw。
lcd_vspw
指场同步信号的宽度。单位为行。
lcd_dclk_freq
传输像素传送频率(单位为 MHz)。
fps = (lcd_dclk_freq * 1000 * 1000) / (ht * vt)
这个值根据以下公式计算
lcd_dclk_freq = lcd_ht * lcd_vt * fps
注意:
- 后面的三个参数都是从屏手册中获得,fps 一般是 60。
- 如果是串行接口,发完一个像素需要 2 到 3 个周期的,那么可以用以下公式计算:
lcd_dclk_freq * cycles = lcd_ht * lcd_vt * fps lcd_dclk_freq = lcd_ht * cycles * lcd_vt * fps
lcd_width
此参数描述 lcd 屏幕的物理宽度,单位是 mm,用于计算 dpi。
lcd_height
此参数描述 lcd 屏幕的物理高度,单位是 mm,用于计算 dpi。
背光相关参数
目前用得比较广泛的就是 pwm 背光调节,原理是利用 pwm 脉冲开关产生的高频率闪烁效应,通过调节占空比,达到欺骗人眼,调节亮暗的目的。
lcd_pwm_used
是否使用 pwm,此参数标识用以背光亮度的控制
lcd_pwm_ch
此参数标识使用的 Pwm 通道,这里是指使用 SoC 哪个 pwm 通道,通过查看原理图连接可知。
lcd_pwm_freq
这个参数配置 PWM 信号的频率,单位为 Hz。
- 频率不宜过低否则很容易就会看到闪烁,频率不宜过快否则背光调节效果差。部分屏手册会标明所允许的 pwm 频率范围,请遵循屏手册固定范围进行设置。
- 在低亮度的时候容易看到闪烁,是正常现象,目前已知用上 pwm 的背光都是如此。
lcd_pwm_pol
这个参数配置 PWM 信号的占空比的极性。设置相应值对应含义为:
0:active high 1:active low
lcd_pwm_max_limit
Lcd backlight PWM 最高限制,以亮度值表示。
比如 150,则表示背光最高只能调到 150,0‑255 范围内的亮度值将会被线性映射到 0‑150 范围内。用于控制最高背光亮度,节省功耗
lcd_bl_en
背光使能脚,非必须,看原理图是否有,用于使能或者禁止背光电路的电压。
示例:
lcd_bl_en = port:PD24<1><2><default><1>
含义:PD24 输出高电平时打开 LCD 背光;下拉,默认高电平。
- 第一个尖括号:功能分配。1 为输出。
- 第二个尖括号:内置电阻。使用 0 的话,标示内部电阻高阻态,如果是 1 则是内部电阻上拉,2就代表内部电阻下拉。使用 default 的话代表默认状态,即电阻上拉。其它数据无效。
- 第三个尖括号:驱动能力。default 表驱动能力是等级 1。
- 第四个尖括号:电平。0 为低电平,1 为高电平。
需要在屏驱动调用相应的接口进行开、关的控制。
一般来说,高电平是使能,在这个前提下,建议将内阻电阻设置成下拉,防止硬件原因造成的上拉,导致背光提前亮。默认电平填写高电平,这是 uboot 显示过度到内核显示、平滑无闪烁的需要。
lcd_bl_n_percent
背光映射值,n 为 (0‑100)。
此功能是针对亮度非线性的 LCD 屏的,按照配置的亮度曲线方式来调整亮度变化,以使亮度变化更线性。
比如 lcd_bl_50_percent = 60,表明将 50% 的亮度值调整成 60%,即亮度比原来提高 10%。
修改此属性不当可能导致背光调节效果差。
lcd_backlight
背光默认值,0‑255。
此属性决定在 uboot 显示 logo 阶段的亮度,进入都内核时则是读取保存的配置来决定亮度。
显示 logo 阶段,一般来说需要比较亮的亮度,业内做法都是如此。
显示效果相关参数
lcd_frm
Lcd Frame Rate Modulator, FRM 是解决由于 PIN 减少导致的色深问题,有些 LCD 屏的像素格式是 18bit 色深(RGB666)或 16bit 色深(RGB565),建议打开 FRM 功能,通过 dither 的方式弥补色深,使显示达到 24bit 色深(RGB888)的效果。如下图所示,上图是色深为 RGB66 的 LCD 屏显示,下图是打开 dither 后的显示,打开 dither 后色彩渐变的地方过度平滑。
参数设置相应值对应含义为:
0:RGB888 ‑‑ RGB888 direct 1:RGB888 ‑‑ RGB666 dither 2:RGB888 ‑‑ RGB565 dither
lcd_gamma_en
Lcd Gamma Correction Enable,设置相应值的对应含义为:
0:LCD 的 Gamma 校正功能关闭 1:LCD 的 Gamma 校正功能开启
设置为 1 时,需要在屏驱动中对
lcd_gamma_tbl[256]
进行赋值。lcd_cmap_en
Lcd Color Map Enable, 设置为 1 时,需要对
lcd_cmap_tbl [2][3][4]
进行赋值Lcd Color Map Table
。每个像素有 R、G、B 三个单元,每四个像素组成一个选择项,总共有 12 个可选。数组第一维表示奇偶行,第二维表示像素的 RGB,第三维表示第几个像素,数组的内容即表示该位置映射到的内容。
LCD CMAP 是对像素的映射输出功能,只有像素有特殊排布的 LCD 屏才需要配置。
LCD CMAP 定义每行的 4 个像素为一个总单元,每个像素分 R、G、B 3 个小单元,总共有 12 个小单元。通过 lcd_cmap_tbl 定义映射关系,输出的每个小单元可随意映射到 12 个小单元之一。
__u32 lcd_cmap_tbl[2][3][4] = { { {LCD_CMAP_G0,LCD_CMAP_B1,LCD_CMAP_G2,LCD_CMAP_B3}, {LCD_CMAP_B0,LCD_CMAP_R1,LCD_CMAP_B2,LCD_CMAP_R3}, {LCD_CMAP_R0,LCD_CMAP_G1,LCD_CMAP_R2,LCD_CMAP_G3}, }, { {LCD_CMAP_B3,LCD_CMAP_G2,LCD_CMAP_B1,LCD_CMAP_G0}, {LCD_CMAP_R3,LCD_CMAP_B2,LCD_CMAP_R1,LCD_CMAP_B0}, {LCD_CMAP_G3,LCD_CMAP_R2,LCD_CMAP_G1,LCD_CMAP_R0}, }, };
如上,上三行代表奇数行的像素排布,下三行代表偶数行的像素排布;
每四个像素为一个单元,第一列代表每四个像素的第一个像素映射,第二列代表每四个像素的第二个像素映射,以此类推。
如上的定义,像素的输出格式如下图所示。
lcd_rb_swap
调换
TCON
模块RGB
中的 R 分量和 B 分量。0:不变 1:调换R分量和B分量
需要 gamma 校正,或色彩映射,在
sys_config.fex
中将相应模块的enable
参数置 1,lcd_gamma_en
,lcd_cmap_en
,并且填充 3 个系数表,lcd_gamma_tbl
,lcd_cmap_tbl
,注意的是:gamma,模板提供了 18 段拐点值,然后再插值出所有的值(255 个)。可以往相应表格内添加子项以补充细节部分。cmap_tbl 的大小是固定的,不能减小或增加表的大小。最终生成的 gamma 表项是由 rgb 三个 gamma 值组成的,各占 8bit。目前提供的模板中,三个 gamma 值是相同的。电源和管脚参数
lcd_power
配置好之后,需要在屏驱动调用相应的接口进行开、关的控制。
注意:如果有多个电源需要打开,则定义 lcd_power1,lcd_power2 等。
lcd_pin_power
用法 lcd_power一致,区别是用户设置之后,不需要在屏驱动中去操作,而是驱动框架自行在屏驱动之前使能,在屏驱动之后禁止。
注意:如果需要多组,则添加 lcd_pin_power1,lcd_pin_power2 等。除了 lcddx 之外,这里的电源还有可能是 pwm 所对应管脚的电源。
lcd_gpio_0
示例:
lcd_gpio_0 = port:PD25<0><0><default><0>
含义:lcd_gpio_0 引脚为 PD25。
- 第一个尖括号:功能分配。1 为输出。
- 第二个尖括号:内置电阻。使用 0 的话,标示内部电阻高阻态,如果是 1 则是内部电阻上拉,2就代表内部电阻下拉。使用 default 的话代表默认状态,即电阻上拉。其它数据无效。
- 第三个尖括号:驱动能力。default 表驱动能力是等级 1。
- 第四个尖括号:电平。0 为低电平,1 为高电平。
注意:如果有多个 gpio 脚需要控制,则定义 lcd_gpio_0,lcd_gpio_1 等。
- 配置 LCD 的控制 PIN。可以在屏驱动调用相应的接口进行拉高,拉低的控制,例如 LCD 的 RESET 脚等。
- 配置 LCD 的数据 PIN。重点关注 PIN 脚的复用功能数值,具体的 IO 对应关系可参考 user manual 手册进行配置
调试
系统起来之后可以输入disp相关调试命令,来协助调试。
选项 参数 解释 举例 空 空 打印出当前显示的信息 disp -c Screen_id,color 模式 显示 colorbar。共有 8 种模式,0 到 8 disp ‑c 0 8 -b Screen_id, 背光值 调整 lcd 背光,背光值范围时 0 到 255 disp ‑b 0 255 -d Screen_id, 文件路径 抓 DE 图层回写到文件 disp ‑d 0 /sdmmc/xx.bmp -s Screen_id,显示类型,显示分辨率 切换显示类型或分辨率 disp ‑s 0 1 4 打开LCD 显示 查看显示信息
输入disp命令,会有 Log 打印信息。以下信息是所有信息中最重要的。
disp screen 0: derate 297000000 hz, ref_fps:60 mgr0: 1280x800 fmt[rgb] cs[0x204] range[full] eotf[0x4] bits[8bits] err[0] force_sync[0] unblank direct_show[false] lcd output backlight( 50) fps:60.9 1280x 800 err:0 skip:31 irq:1942 vsync:0 vsync_skip:0 BUF enable ch[1] lyr[0] z[0] prem[N] a[globl 255] fmt[ 8] fb[1280, 800;1280, 800;1280, 800] crop[ 0, 0,1280, 800] frame[ 0, 0,1280, 800] addr[ 0, 0, 0] flags[0x 0] trd[0,0]
lcd output
表示当前显示接口是 LCD 输出。
1280x800
表示当前 LCD 的分辨率,与 sys_config.fex 中 lcd0 的设置一样。
ref_fps:60
是根据你在 sys_config.fex 的 lcd0 填的时序算出来的理论值。
fps:60.9
后面的数值是实时统计的,正常来说应该是在 60(期望的 fps) 附近,如果差太多则不正常,重新检查屏时序,和在屏驱动的初始化序列是否有被调用到。
irq:1942
这是 vsync 中断的次数,每加 1 都代表刷新了一帧,正常来说是一秒 60(期望的 fps)次,重复 cat sys,如果无变化,则异常。
BUF
开头的表示图层信息,一行 BUF 表示一个图层,如果一个 BUF 都没有出现,那么将是黑屏,不过和屏驱动本身关系就不大了,应该查看应用层 & 框架层。
err:0
这个表示缺数,如果数字很大且一直变化,屏幕会花甚至全黑,全红等。
skip:31
这个表示跳帧的数量,如果这个数值很大且一直变化,有可能卡顿,如果数字与 irq 后面的数字一样,说明每一帧都跳,会黑屏(有背光)
查看时钟信息
hal_ccmu
这个命令可以看哪个时钟是否使能,然后频率是多少。与显示相关的是 tcon,pll_video等。
查看接口自带 colorbar
显示是一整条链路,中间任何一个环节出错,最终的表现都是显示异常,图像显示异常几个可能原因:
- 图像本身异常。
- 图像经过 DE(Display Engine)后异常。
- 图像经过接口模块后异常。这是我们关注的点。
有一个简单的方法可以初步判断,接口模块(tcon 和 dsi 等)可以自己输出内置的一些 patten,比如说彩条,灰阶图,棋盘图等。当接口输出这些内置 patten 的时候,如果这时候显示就异常,这说明了:
- LCD 的驱动或者配置有问题
- LCD 屏由于外部环境导致显示异常
显示自带 patten 的方式:
disp ‑c 0 X
上面的操作是显示 colorbar,其中的 X 可以是 0 到 8
FAQ
屏显示异常
总结过往经验,绝大部分屏显异常都是由于上下电时序和 timing 不合理导致。
黑屏‑无背光
问题表现:完全黑屏,背光也没有
- 屏驱动添加失败。驱动没有加载屏驱动,导致背光电源相关函数没有运行到。这个你可以通过相关模块的测试命令定位下。
- pwm 配置和背光电路的问题,pwm 的信息可以查看 pwm 模块测试命令和背光相关参数,另外就是直接测量下硬件测量下相关管脚和电压,再检查屏是否初始化成功。
黑屏‑有背光
黑屏但是有背光,可能有多种原因导致,请依次按以下步骤检查
- 没送图层。如果应用没有送任何图层那么表现的现象就是黑屏,通过查看显示信息一小节可以确定有没有送图层。如果确定没有图层,可以通过查看接口自带 colorbar,确认屏能否正常显示。
- SoC 端的显示接口模块没有供电。SoC 端模块没有供电自然无法传输视频信号到屏上。
- 复位脚没有复位。如果有复位脚,请确保硬件连接正确,确保复位脚的复位操作有放到屏驱动中。
- sys_config.fex 中 lcd0 有严重错误。第一个是 lcd 的 timing 搞错了,请严格按照屏手册中的提示来写。参考屏时序参数说明。第二个就是,接口类型搞错,比如接的 DSI 屏,配置却写成LVDS 的。
- 屏的初始化命令不对。包括各个步骤先后顺序,延时等,这个时候请找屏厂确认初始化命令。
闪屏
分为几种:
- 屏的整体在闪:这个最大可能是背光电路的电压不稳定,检查电压
- 屏部分在闪,而且是概率性:sys_config.fex 中的时序填写不合理。
- 屏上由一个矩形区域在闪:屏极化导致,需要关机放一边再开机则不会。
条形波纹
有些 LCD 屏的像素格式是 18bit 色深(RGB666)或 16bit 色深(RGB565),建议打开 FRM 功能,通过 dither 的方式弥补色深,使显示达到 24bit 色深(RGB888)的效果。
设置 [lcd0] 的 lcd_frm 属性可以改善这种现象。
背光太亮或者太暗
重新配置背光参数
花屏
花屏的第一个原因是 fps 过高,超过屏的限制:
FPS 异常是一件非常严重的事情,关系到整个操作系统的稳定,如果 fps 过高会造成系统带宽增加,送显流程异常,fps 过高还会造成 LCD 屏花屏不稳定,容易造成 LCD 屏损坏,FPS 过低则造成用户体验过差。
- 通过查看查看显示信息一节,可以得知现在的实时统计的 fps
- 如果 fps 离正常值差很多,首先检查 sys_config.fex 中 [lcd0] 节点,所填信息必须满足下面公式:
lcd_dclk_freq * num_of_pixel_clk = lcd_ht * lcd_vt * fps / 1e9
其中,num_of_pixel_clk 通常为 1,表示发送一个像素所需要的时钟周期为 1 一个,低分辨率的MCU 和串行接口通常需要 2 到 3 个时钟周期才能发送完一个像素。
如果上面填写没有错,通过查看查看时钟信息可以确认下几个主要时钟的频率信息,把这些信息和 sys_config.fex 发给维护者进一步分析。
RGB 接口或者 I8080 接口显示抖动有花纹
- 改大时钟管脚的管脚驱动能力
- 修改时钟相位,也就是修改 lcd_hv_clk_phase。由于发送端和接收端时钟相位的不同导致接收端解错若干位。
LCD 屏出现极化和残影
何谓液晶极化现象:实际上就是液晶电介质极化。就是在外界电场作用下,电介质内部沿电场方向产生感应偶极矩,在电解质表明出现极化电荷的现象叫做电介质的极化。
通俗的讲就是在液晶面板施加一定电压后,会聚集大量电荷,当电压消失的时候,这些聚集的电荷也要释放,但由于介电效应,这些聚集的电荷不会立刻释放消失,这些不会马上消失的惰性电荷造成了液晶的 DC 残留从而形成了极化现象。
几种常见的液晶极化现象
- 液晶长期静止某个画面的时候,切换到灰阶画面的时候出现屏闪,屏闪一段时间后消失。这种现象属于残留电荷放电的过程。
- 液晶长期静止某个画面的时候,出现四周发黑中间发白的现象,业内称为黑白电视框异常。
- 非法关机的时候,重新上电会出现屏闪,屏闪一定时间后消失。与第一种原因相同。
- 残影现象:当液晶静止在一个画面比较久的情况下,切换其他画面出现的镜像残留。残影的本质来说是液晶 DC 残留电荷导致,某种意义来说也属于液晶极化现象
针对液晶屏出现极化和残影现象,有如下对策。
- 调整 vcom 电压大小。
VCOM 是液晶分子偏转的参考电压,要求要稳定,对液晶显示有直接影响,具体的屏不同的话也是不同的。电压的具体值是根据输入的数据以及 Vcom 电压大小来确定的,用来显示各种不同灰阶,也就是实现彩色显示 GAMMA。Gamma 电压是用来控制显示器的灰阶的,一般情况下分为G0~G14,不同的 Gamma 电压与 Vcom 电压之间的压差造成液晶旋转角度不同从而形成亮度的差异,Vcom 电压最好的状况是位于 G0 和 G14 的中间值,这样液晶屏的闪烁状况会最好。调节 vcom 电压的方式,如果屏管脚有 vcom 管脚,直接调整相关电路,如果屏 driver IC 提供寄存器接口,可以通过寄存器接口来调整大小。
- 严格按照屏规定的上下电时序来对屏进行开关屏。许多极化残影现象并非长时间显示静止显示某个画面导致的,而是由于关机或者关屏时没有严格按照下电时序导致的,比如该关的电没关,或者延时不够。
典型屏幕参数配置
1024x600 RGB666 屏幕
;-------------------------------------------------- ;Parallel RGB LCD ;-------------------------------------------------- [lcd0] lcd_used = 1 lcd_driver_name = "default_lcd" lcd_backlight = 150 lcd_if = 0 lcd_x = 1024 lcd_y = 600 lcd_width = 150 lcd_height = 94 lcd_rb_swap = 0 lcd_dclk_freq = 48 lcd_pwm_used = 1 lcd_pwm_ch = 7 lcd_pwm_freq = 500000 lcd_pwm_pol = 1 lcd_hbp = 160 lcd_ht = 1344 lcd_hspw = 20 lcd_vbp = 20 lcd_vt = 635 lcd_vspw = 3 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 0 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 ;reset ;lcd_gpio_0 = port:GPIO_EXP15<1><0><3><1> ;cs ;lcd_gpio_1 = port:GPIO_EXP03<1><0><3><0> ;sdi ;lcd_gpio_2 = port:GPIO_EXP06<1><0><3><0> ;scl ;lcd_gpio_3 = port:GPIO_EXP07<1><0><3><0> ;LCD_D2-LCD_D7 lcd_gpio_4 = port:PA00<8><0><3><0> lcd_gpio_5 = port:PA01<8><0><3><0> lcd_gpio_6 = port:PA02<8><0><3><0> lcd_gpio_7 = port:PA03<8><0><3><0> lcd_gpio_8 = port:PA04<8><0><3><0> lcd_gpio_9 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D15 lcd_gpio_10 = port:PA11<8><0><3><0> lcd_gpio_11 = port:PA10<8><0><3><0> lcd_gpio_12 = port:PA08<8><0><3><0> lcd_gpio_13 = port:PA07<8><0><3><0> lcd_gpio_14 = port:PA06<8><0><3><0> lcd_gpio_15 = port:PA09<8><0><3><0> ;LCD_D18-LCD_D23 lcd_gpio_16 = port:PA12<8><0><3><0> lcd_gpio_17 = port:PA13<8><0><3><0> lcd_gpio_18 = port:PA14<8><0><3><0> lcd_gpio_19 = port:PA15<8><0><3><0> lcd_gpio_20 = port:PB03<8><0><3><0> lcd_gpio_21 = port:PB02<8><0><3><0> ;LCD_VSYNC, LCD_HSYNC, LCD_DCLK, LCD_DE lcd_gpio_0 = port:PA18<8><0><3><0> lcd_gpio_1 = port:PA19<8><0><3><0> lcd_gpio_2 = port:PA20<8><0><3><0> lcd_gpio_3 = port:PA21<8><0><3><0>
320x480 ST7796 i8080 屏幕
;-------------------------------------------------- ;MCU LCD ;-------------------------------------------------- [lcd0] lcd_used = 1 lcd_driver_name = "cl40bc1019_cpu" lcd_backlight = 150 lcd_if = 1 lcd_x = 320 lcd_y = 480 lcd_width = 150 lcd_height = 94 lcd_rb_swap = 0 lcd_pwm_used = 1 lcd_pwm_ch = 7 lcd_pwm_freq = 5000 lcd_pwm_pol = 1 lcd_cpu_mode = 0 lcd_cpu_te = 0 lcd_cpu_if = 12 lcd_dclk_freq = 32 lcd_hbp = 75 lcd_ht = 1060 lcd_hspw = 40 lcd_vbp = 6 lcd_vt = 490 lcd_vspw = 2 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 0 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 deu_mode = 0 lcdgamma4iep = 22 smart_color = 90 ;reset pin lcd_gpio_0 = port:PB03<1><0><3><0> ;CS lcd_gpio_1 = port:PA12<1><0><3><0> ;LCD_D3-LCD_D7 lcd_gpio_2 = port:PA01<8><0><3><0> lcd_gpio_3 = port:PA02<8><0><3><0> lcd_gpio_4 = port:PA03<8><0><3><0> lcd_gpio_5 = port:PA04<8><0><3><0> lcd_gpio_6 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D12 lcd_gpio_7 = port:PA11<8><0><3><0> lcd_gpio_8 = port:PA10<8><0><3><0> lcd_gpio_9 = port:PA08<8><0><3><0> ;WR lcd_gpio_10 = port:PA06<7><0><3><0> ;RD lcd_gpio_11 = port:PA07<7><0><3><0> ;RS lcd_gpio_12 = port:PA09<7><0><3><0>
480x480 RGB 86 面板屏
;-------------------------------------------------- ;Parallel RGB LCD ;-------------------------------------------------- [lcd0] lcd_used = 1 lcd_driver_name = "p0400060a" lcd_backlight = 150 lcd_if = 0 lcd_x = 480 lcd_y = 480 lcd_width = 94 lcd_height = 94 lcd_rb_swap = 0 lcd_dclk_freq = 21 lcd_hv_clk_phase = 1 lcd_pwm_used = 1 lcd_pwm_ch = 6 lcd_pwm_freq = 5000 lcd_pwm_pol = 1 lcd_hbp = 80 lcd_ht = 648 lcd_hspw = 8 lcd_vbp = 10 lcd_vt = 522 lcd_vspw = 2 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 1 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 deu_mode = 0 lcdgamma4iep = 22 smart_color = 90 ;reset lcd_gpio_0 = port:PB01<1><0><3><1> ;cs lcd_gpio_1 = port:PA27<1><0><3><0> ;sdi lcd_gpio_2 = port:PA28<1><0><3><0> ;scl lcd_gpio_3 = port:PB00<1><0><3><0> ;LCD_D2-LCD_D7 lcd_gpio_4 = port:PA00<8><0><3><0> lcd_gpio_5 = port:PA01<8><0><3><0> lcd_gpio_6 = port:PA02<8><0><3><0> lcd_gpio_7 = port:PA03<8><0><3><0> lcd_gpio_8 = port:PA04<8><0><3><0> lcd_gpio_9 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D15 lcd_gpio_10 = port:PA11<8><0><3><0> lcd_gpio_11 = port:PA10<8><0><3><0> lcd_gpio_12 = port:PA08<8><0><3><0> lcd_gpio_13 = port:PA07<8><0><3><0> lcd_gpio_14 = port:PA06<8><0><3><0> lcd_gpio_15 = port:PA09<8><0><3><0> ;LCD_D18-LCD_D23 lcd_gpio_16 = port:PA12<8><0><3><0> lcd_gpio_17 = port:PA13<8><0><3><0> lcd_gpio_18 = port:PA14<8><0><3><0> lcd_gpio_19 = port:PA15<8><0><3><0> lcd_gpio_20 = port:PB03<8><0><3><0> lcd_gpio_21 = port:PB02<8><0><3><0> ;LCD_VSYNC, LCD_HSYNC, LCD_DCLK, LCD_DE lcd_gpio_22 = port:PA18<8><0><3><0> lcd_gpio_23 = port:PA19<8><0><3><0> lcd_gpio_24 = port:PA20<8><0><3><0> lcd_gpio_25 = port:PA21<8><0><3><0>
320x320 i8080 86 面板屏
[lcd0] lcd_used = 1 lcd_driver_name = "d392t9390v0_cpu" lcd_backlight = 200 lcd_if = 1 lcd_x = 320 lcd_y = 320 lcd_width = 78 lcd_height = 78 lcd_rb_swap = 1 lcd_pwm_used = 1 lcd_pwm_ch = 7 lcd_pwm_freq = 50000 lcd_pwm_pol = 1 lcd_cpu_mode = 0 lcd_cpu_te = 0 lcd_cpu_if = 12 lcd_dclk_freq = 32 lcd_hbp = 75 lcd_ht = 1060 lcd_hspw = 40 lcd_vbp = 6 lcd_vt = 490 lcd_vspw = 2 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 0 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 deu_mode = 0 lcdgamma4iep = 22 smart_color = 90 ;reset pin lcd_gpio_0 = port:PA12<1><0><3><0> ;LCD_D3-LCD_D7 lcd_gpio_2 = port:PA01<8><0><3><0> lcd_gpio_3 = port:PA02<8><0><3><0> lcd_gpio_4 = port:PA03<8><0><3><0> lcd_gpio_5 = port:PA04<8><0><3><0> lcd_gpio_6 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D12 lcd_gpio_7 = port:PA11<8><0><3><0> lcd_gpio_8 = port:PA10<8><0><3><0> lcd_gpio_9 = port:PA08<8><0><3><0> ;WR lcd_gpio_10 = port:PA06<7><0><3><0> ;RD lcd_gpio_11 = port:PA07<7><0><3><0> ;RS lcd_gpio_12 = port:PA09<7><0><3><0>
800x480 标准 40Pin RGB屏
[lcd0] lcd_used = 1 lcd_driver_name = "default_lcd" lcd_backlight = 150 lcd_if = 0 lcd_x = 800 lcd_y = 480 lcd_width = 150 lcd_height = 94 lcd_rb_swap = 0 lcd_dclk_freq = 33 lcd_pwm_used = 1 lcd_pwm_ch = 6 lcd_pwm_freq = 5000 lcd_pwm_pol = 1 lcd_hbp = 46 lcd_ht = 1055 lcd_hspw = 0 lcd_vbp = 23 lcd_vt = 525 lcd_vspw = 0 lcd_lvds_if = 0 lcd_lvds_colordepth = 1 lcd_lvds_mode = 0 lcd_frm = 0 lcd_io_phase = 0x0000 lcd_gamma_en = 0 lcd_bright_curve_en = 0 lcd_cmap_en = 0 deu_mode = 0 lcdgamma4iep = 22 smart_color = 90 ;LCD_D2-LCD_D7 lcd_gpio_0 = port:PA00<8><0><3><0> lcd_gpio_1 = port:PA01<8><0><3><0> lcd_gpio_2 = port:PA02<8><0><3><0> lcd_gpio_3 = port:PA03<8><0><3><0> lcd_gpio_4 = port:PA04<8><0><3><0> lcd_gpio_5 = port:PA05<8><0><3><0> ;LCD_D10-LCD_D15 lcd_gpio_6 = port:PA11<8><0><3><0> lcd_gpio_7 = port:PA10<8><0><3><0> lcd_gpio_8 = port:PA08<8><0><3><0> lcd_gpio_9 = port:PA07<8><0><3><0> lcd_gpio_10 = port:PA06<8><0><3><0> lcd_gpio_11 = port:PA09<8><0><3><0> ;LCD_D18-LCD_D23 lcd_gpio_12 = port:PA12<8><0><3><0> lcd_gpio_13 = port:PA13<8><0><3><0> lcd_gpio_14 = port:PA14<8><0><3><0> lcd_gpio_15 = port:PA15<8><0><3><0> lcd_gpio_16 = port:PB03<8><0><3><0> lcd_gpio_17 = port:PB02<8><0><3><0> ;LCD_VSYNC, LCD_HSYNC, LCD_DCLK, LCD_DE lcd_gpio_18 = port:PA18<8><0><3><0> lcd_gpio_19 = port:PA19<8><0><3><0> lcd_gpio_20 = port:PA20<8><0><3><0> lcd_gpio_21 = port:PA21<8><0><3><0>
-
【R128】外设模块配置——ADC按键配置方法
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
ADC 按键配置方法
FreeRTOS平台上使用的按键为ADC-KEY,采用的ADC模块为GPADC。
按键功能驱动的实现是通过ADC分压,使每个按键检测的电压值不同,从而实现区分不同的按键。按下或者弹起中断之后,通过中断触发,主动检测当前电压识别出对应的按键。最后再通过input子系统将获取按键的键值并上报给应用层。
GPADC-Key配置方法
按键结构体定义key_config的成员:
struct sunxikbd_config{ unsigned int measure; // 电压阈值 char *name; // 功能名 unsigned int key_num; // 按键数量 unsigned int scankeycodes[KEY_MAX_CNT]; // 按键的键值 unsigned int key_vol[KEY_MAX_CNT]; // 按键的电压值 }; 不同平台的配置可能不同,以r128为例,下面是r128的成员config: static struct sunxikbd_config key_config = { .measure = 2500, .name = "gpadc-key", .key_num = 5, .key_vol = {164,415,646,900,1157}, .scankeycodes = {115,114,139,164,116} };
当前按键驱动的配置都是以hardcode的方式写入驱动代码中,也就是说按键的数量,不同按键对应的电压值,不同按键对应的KeyCode等等配置如果要修改的话,需要对源码进行修改。
如上述按键结构体的定义,以下两配置按照默认配置即可。
-
measure:GPADC最大能够识别的电压值,需要根据UserMaual来设置。默认为2.5V。
-
name:注册的按键驱动名称,会注册到input子系统中。
可以自定义的配置为:key_num,key_vol,scankeycodes。
-
key_num: 按照实际硬件设计来设置。
-
key_vol: 按照硬件,实际每个按键分压的情况来设置。
-
scankeycodes:每个按键对应的KeyCode,方便应用通过input获得按下的按键时,能够识别是哪个按键按下了。
key_vol的配置方法,可以参考下图的GPADC-KEY的硬件设置。
在图中,VCC输入的电压为3.3V,通过分压关系,第一个按键的电压为0.21V,因此key_vol可以设置为210,以此类推。
驱动初始化方法
默认系统启动时,不会加载GPADC驱动以及按键驱动。
如果需要加载GPADC-Key驱动,需要调用以下函数:
int sunxi_gpadc_key_init(void);
调用sunxi_gpadc_key_init()该函数即可完成GPADC驱动以及按键驱动的加载了,即可开始使用按键驱动。
应用调用方法
接下来继续介绍一下,应用中该如何获取按键按下的事件。
在FreeRTOS系统中,也移植了 Linux 的 input 子系统。GPADC-Key 中也是调用了 input 的接口,进行注册 input 设置,上报事件等动作。
在调用
sunxi_gpadc_key_init()
时,通过input_set_capability()
去设置了事件的属性;通过sunxi_input_register_device()
注册了input
设备;在按键按下触发中断时,也是通过input_report_key()
和input_sync()
上报input
事件。因此,在应用层想要获取input事件,可以直接通过input接口去获取,使用示例可如下例程:
int input_func(void) { int fd = -1; struct sunxi_input_event event; sunxi_gpadc_key_init(); // 驱动初始化 fd = sunxi_input_open(DEVICE_NAME); if (fd < 0) return -1; while (1) { sunxi_input_readb(fd, &event, sizeof(struct sunxi_input_event)); if (event.type != EV_KEY) continue; if (event.value == 0) { printf("key up\n"); continue; } printf("key press: KeyCode:%d\n", event.code); } return 0; }
-
【R128】软件配置——RTOS 软件包配置
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
RTOS 软件包配置
本文将介绍 RTOS 软件包、地址,内核配置等。
Kconfig 简介
有过 linux 内核开发经验的人,对 menuconfig 不会陌生。对于各类内核,只要是支持 menuconfig 配置界面,都是使用 Kconfig。
换言之:
- menuconfig:支持配置内核的图形化界面。
- Kconfig:生成 menuconfig 界面的脚本语言。
menuconfig 操作简介
我们运行 mrtos_menuconfig 之后,会打开如下图所示的界面
整个显示界面大致分为三部分
上方是操作的说明和图例说明。
<Enter>
按下Enter键进入子菜单。<Y>
按下Y键,选中这项功能。<N>
按下N键,排除这项功能 。<M>
按下M键,以模块的形式选择。<Esc><Esc>
按两下Esc键,返回上一级菜单<?>
按下?键,查看高亮功能的帮助信息</>
按下/键,搜索[*]
被选中的项目[ ]
未被选择的项目<M>
以模块形式被选择的项目< >
未被选择的模块
中间部分就是我们要选择的各项功能了,使用上下两个方向键进行选择,也可以使用关键字进行跳转。
最下方是功能选择按钮,功能与案件基本重合。使用左右方向键选择,Enter键选中。
<Select>
进入下级菜单< Exit >
退回上一级菜单,在最顶层菜单就是退出menuconfig< Help >
显示当前选项的帮助信息< Save >
手动保存配置文件< Load >
加载指定的配置文件,默认加载的是.config
RTOS menuconfig 说明
了解了menuconfig的基本操作,我们再来了解一下 RTOS 的 menuconfig 具体都有是么内容。
Build target sunxi arch
分别选择芯片的系列,对于R128平台,使用的是
sun20iw2p1
平台Build OS or Library
选择编译为 Library 还是 OS,这里我们使用的是 OS 所以设置为编译 OS
Build System
这部分配置 RTOS 的配套编译脚手架,配置编译使用的编译工具链的路径,libc的类型等等。
Architecture Options
架构配置,配置CPU的架构,入口地址,分配的内存长度,RV核心的地址,DSP核心的地址。这里也也配置默认启动的核心,可以关闭 C906 和 DSP 核心的启动。
Kernel Options
FreeRTOS 内核相关的配置,一般不需要修改。
Drivers Options
驱动配置,分为
soc related device drivers
和other drivers
soc related device drivers
包括各种外设驱动,与驱动的单元测试。
other drivers
包括 WiFi 驱动,蓝牙驱动等等
System components
组件选择,软件包,协议栈等相关选择
aw components
全志提供的相关组件,核间通信组件,多媒体组件等。
thirdparty components
第三方提供的组件,lvgl,协议栈等等
Projects Options
项目配置,选择项目方案
menuconfig 配置保存
完成配置之后,我们可以选择使用左右方向键选择 < save > 并按下Enter键。
如果修改配置文件名称的话,就是将当前的配置另外保存到指定文件;不修改的话默认保存在.config中, 然后选择 ok 确认,就可以继续进行配置了修改了。如果不想保存,那么可以按两下 Esc 键返回。
当然也可以不使用上面的操作,在全部修改都完成之后,连按 Esc 键,就会退出menuconfig。 在退出时会提醒是否保存配置文件,选择 Yes 即可保存配置,如果不想保存可以选择 No , 如果还想继续修改,可以按两下 Esc 键返回 menuconfig 继续修改。
-
【R128】应用开发案例——适配 ST7789v LCD
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
适配 ST7789v LCD
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了 SPILCD 驱动框架以供 SPI 屏幕使用。
此次适配的SPI屏为
ZJY240S0800TG11
,使用的是 SPI 进行驱动。注意请购买带 CS 引脚的 SPI 屏幕,由于 ST7789v SPI 时序问题,CS 引脚直接接地可能会导致初始化无法写入。如果很不幸SPI TFT是不带 CS 的,请飞线出来连接 CS 引脚(如图所示)
引脚配置如下:R128 Devkit TFT 模块 PA12 CS(飞线) PA13 CLK PA18 MOSI PA9 BLK PA20 RES PA19 DC 3V3 VCC GND GND 悬空 MISO 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 SPI 驱动
屏幕使用的是SPI驱动,所以需要勾选SPI驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPI Devices
Drivers Options ---> soc related device drivers ---> SPI Devices ---> -*- enable spi driver
配置 SPI 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,在这里我们不需要用到 SPI HOLD与SPI WP引脚,注释掉即可。;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;Please config spi in dts [spi1] spi1_used = 1 spi1_cs_number = 1 spi1_cs_bitmap = 1 spi1_cs0 = port:PA12<6><0><3><default> spi1_sclk = port:PA13<6><0><3><default> spi1_mosi = port:PA18<6><0><3><default> spi1_miso = port:PA21<6><0><3><default> ;spi1_hold = port:PA19<6><0><2><default> ;spi1_wp = port:PA20<6><0><2><default>
设置 PWM 驱动
屏幕背光使用的是PWM驱动,所以需要勾选PWM驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到PWM Devices
Drivers Options ---> soc related device drivers ---> PWM Devices ---> -*- enable pwm driver
配置 PWM 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,增加 PWM1 节点[pwm1] pwm_used = 1 pwm_positive = port:PA9<4><0><3><default>
设置 SPI LCD 驱动
SPI LCD 由专门的驱动管理。运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPILCD Devices
,注意同时勾选spilcd hal APIs test
方便测试使用。Drivers Options ---> soc related device drivers ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test
编写 SPI LCD 显示屏驱动
获取屏幕初始化序列
首先询问屏厂提供驱动源码
找到 LCD 的初始化序列代码
找到屏幕初始化的源码
整理后的初始化代码如下:
LCD_WR_REG(0x11); delay_ms(120); // Delay 120ms // display and color format setting LCD_WR_REG(0X36); LCD_WR_DATA8(0x00); LCD_WR_REG(0X3A); LCD_WR_DATA8(0X05); // ST7789S Frame rate setting LCD_WR_REG(0xb2); LCD_WR_DATA8(0x0c); LCD_WR_DATA8(0x0c); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xb7); LCD_WR_DATA8(0x35); // ST7789S Power setting LCD_WR_REG(0xbb); LCD_WR_DATA8(0x35); LCD_WR_REG(0xc0); LCD_WR_DATA8(0x2c); LCD_WR_REG(0xc2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xc3); LCD_WR_DATA8(0x13); LCD_WR_REG(0xc4); LCD_WR_DATA8(0x20); LCD_WR_REG(0xc6); LCD_WR_DATA8(0x0f); LCD_WR_REG(0xca); LCD_WR_DATA8(0x0f); LCD_WR_REG(0xc8); LCD_WR_DATA8(0x08); LCD_WR_REG(0x55); LCD_WR_DATA8(0x90); LCD_WR_REG(0xd0); LCD_WR_DATA8(0xa4); LCD_WR_DATA8(0xa1); // ST7789S gamma setting LCD_WR_REG(0xe0); LCD_WR_DATA8(0xd0); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x06); LCD_WR_DATA8(0x09); LCD_WR_DATA8(0x0b); LCD_WR_DATA8(0x2a); LCD_WR_DATA8(0x3c); LCD_WR_DATA8(0x55); LCD_WR_DATA8(0x4b); LCD_WR_DATA8(0x08); LCD_WR_DATA8(0x16); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x19); LCD_WR_DATA8(0x20); LCD_WR_REG(0xe1); LCD_WR_DATA8(0xd0); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x06); LCD_WR_DATA8(0x09); LCD_WR_DATA8(0x0b); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x54); LCD_WR_DATA8(0x4b); LCD_WR_DATA8(0x0d); LCD_WR_DATA8(0x16); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x21); LCD_WR_DATA8(0x20); LCD_WR_REG(0x29);
用现成驱动改写 SPI LCD 驱动
选择一个现成的 SPI LCD 改写即可,这里选择
nv3029s.c
驱动来修改复制这两个驱动,重命名为
st7789v.c
先编辑
st7789v.h
将nv3029s
改成st7789v
#ifndef _ST7789V_H #define _ST7789V_H #include "panels.h" struct __lcd_panel st7789v_panel; #endif /*End of file*/
编辑
st7789v.c
将nv3029s
改成st7789v
然后将屏厂提供的初始化序列复制进来
然后按照
spi_lcd
框架的接口改写驱动接口,具体接口如下屏厂函数 SPILCD框架接口 LCD_WR_REG
sunxi_lcd_cmd_write
LCD_WR_DATA8
sunxi_lcd_para_write
delay_ms
sunxi_lcd_delay_ms
完成驱动如下
#include "st7789v.h" static void LCD_power_on(u32 sel); static void LCD_power_off(u32 sel); static void LCD_bl_open(u32 sel); static void LCD_bl_close(u32 sel); static void LCD_panel_init(u32 sel); static void LCD_panel_exit(u32 sel); #define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v) #define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val) static struct disp_panel_para info[LCD_FB_MAX]; static void address(unsigned int sel, int x, int y, int width, int height) { sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */ sunxi_lcd_para_write(sel, (y >> 8) & 0xff); sunxi_lcd_para_write(sel, y & 0xff); sunxi_lcd_para_write(sel, (height >> 8) & 0xff); sunxi_lcd_para_write(sel, height & 0xff); sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */ sunxi_lcd_para_write(sel, (x >> 8) & 0xff); sunxi_lcd_para_write(sel, x & 0xff); sunxi_lcd_para_write(sel, (width >> 8) & 0xff); sunxi_lcd_para_write(sel, width & 0xff); sunxi_lcd_cmd_write(sel, 0x2c); } static void LCD_panel_init(unsigned int sel) { if (bsp_disp_get_panel_info(sel, &info[sel])) { lcd_fb_wrn("get panel info fail!\n"); return; } sunxi_lcd_cmd_write(sel, 0x11); sunxi_lcd_delay_ms(120); /* display and color format setting */ sunxi_lcd_cmd_write(sel, 0X36); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_cmd_write(sel, 0X3A); sunxi_lcd_para_write(sel, 0X05); /* ST7789S Frame rate setting */ sunxi_lcd_cmd_write(sel, 0xb2); sunxi_lcd_para_write(sel, 0x0c); sunxi_lcd_para_write(sel, 0x0c); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_para_write(sel, 0x33); sunxi_lcd_cmd_write(sel, 0xb7); sunxi_lcd_para_write(sel, 0x35); /* ST7789S Power setting */ sunxi_lcd_cmd_write(sel, 0xbb); sunxi_lcd_para_write(sel, 0x35); sunxi_lcd_cmd_write(sel, 0xc0); sunxi_lcd_para_write(sel, 0x2c); sunxi_lcd_cmd_write(sel, 0xc2); sunxi_lcd_para_write(sel, 0x01); sunxi_lcd_cmd_write(sel, 0xc3); sunxi_lcd_para_write(sel, 0x13); sunxi_lcd_cmd_write(sel, 0xc4); sunxi_lcd_para_write(sel, 0x20); sunxi_lcd_cmd_write(sel, 0xc6); sunxi_lcd_para_write(sel, 0x0f); sunxi_lcd_cmd_write(sel, 0xca); sunxi_lcd_para_write(sel, 0x0f); sunxi_lcd_cmd_write(sel, 0xc8); sunxi_lcd_para_write(sel, 0x08); sunxi_lcd_cmd_write(sel, 0x55); sunxi_lcd_para_write(sel, 0x90); sunxi_lcd_cmd_write(sel, 0xd0); sunxi_lcd_para_write(sel, 0xa4); sunxi_lcd_para_write(sel, 0xa1); /* ST7789S gamma setting */ sunxi_lcd_cmd_write(sel, 0xe0); sunxi_lcd_para_write(sel, 0xd0); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x06); sunxi_lcd_para_write(sel, 0x09); sunxi_lcd_para_write(sel, 0x0b); sunxi_lcd_para_write(sel, 0x2a); sunxi_lcd_para_write(sel, 0x3c); sunxi_lcd_para_write(sel, 0x55); sunxi_lcd_para_write(sel, 0x4b); sunxi_lcd_para_write(sel, 0x08); sunxi_lcd_para_write(sel, 0x16); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x19); sunxi_lcd_para_write(sel, 0x20); sunxi_lcd_cmd_write(sel, 0xe1); sunxi_lcd_para_write(sel, 0xd0); sunxi_lcd_para_write(sel, 0x00); sunxi_lcd_para_write(sel, 0x06); sunxi_lcd_para_write(sel, 0x09); sunxi_lcd_para_write(sel, 0x0b); sunxi_lcd_para_write(sel, 0x29); sunxi_lcd_para_write(sel, 0x36); sunxi_lcd_para_write(sel, 0x54); sunxi_lcd_para_write(sel, 0x4b); sunxi_lcd_para_write(sel, 0x0d); sunxi_lcd_para_write(sel, 0x16); sunxi_lcd_para_write(sel, 0x14); sunxi_lcd_para_write(sel, 0x21); sunxi_lcd_para_write(sel, 0x20); sunxi_lcd_cmd_write(sel, 0x29); if (info[sel].lcd_x < info[sel].lcd_y) address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1); else address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1); } static void LCD_panel_exit(unsigned int sel) { sunxi_lcd_cmd_write(sel, 0x28); sunxi_lcd_delay_ms(20); sunxi_lcd_cmd_write(sel, 0x10); sunxi_lcd_delay_ms(20); sunxi_lcd_pin_cfg(sel, 0); } static s32 LCD_open_flow(u32 sel) { lcd_fb_here; /* open lcd power, and delay 50ms */ LCD_OPEN_FUNC(sel, LCD_power_on, 50); /* open lcd power, than delay 200ms */ LCD_OPEN_FUNC(sel, LCD_panel_init, 200); LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50); /* open lcd backlight, and delay 0ms */ LCD_OPEN_FUNC(sel, LCD_bl_open, 0); return 0; } static s32 LCD_close_flow(u32 sel) { lcd_fb_here; /* close lcd backlight, and delay 0ms */ LCD_CLOSE_FUNC(sel, LCD_bl_close, 50); /* open lcd power, than delay 200ms */ LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10); /* close lcd power, and delay 500ms */ LCD_CLOSE_FUNC(sel, LCD_power_off, 10); return 0; } static void LCD_power_on(u32 sel) { /* config lcd_power pin to open lcd power0 */ lcd_fb_here; power_en(sel, 1); sunxi_lcd_power_enable(sel, 0); sunxi_lcd_pin_cfg(sel, 1); RESET(sel, 1); sunxi_lcd_delay_ms(100); RESET(sel, 0); sunxi_lcd_delay_ms(100); RESET(sel, 1); } static void LCD_power_off(u32 sel) { lcd_fb_here; /* config lcd_power pin to close lcd power0 */ sunxi_lcd_power_disable(sel, 0); power_en(sel, 0); } static void LCD_bl_open(u32 sel) { sunxi_lcd_pwm_enable(sel); /* config lcd_bl_en pin to open lcd backlight */ sunxi_lcd_backlight_enable(sel); lcd_fb_here; } static void LCD_bl_close(u32 sel) { /* config lcd_bl_en pin to close lcd backlight */ sunxi_lcd_backlight_disable(sel); sunxi_lcd_pwm_disable(sel); lcd_fb_here; } /* sel: 0:lcd0; 1:lcd1 */ static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3) { lcd_fb_here; return 0; } static int lcd_set_var(unsigned int sel, struct fb_info *p_info) { return 0; } static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height) { address(sel, x, y, width, height); return 0; } static int lcd_blank(unsigned int sel, unsigned int en) { return 0; } struct __lcd_panel st7789v_panel = { /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */ .name = "st7789v", .func = { .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, .lcd_user_defined_func = LCD_user_defined_func, .blank = lcd_blank, .set_var = lcd_set_var, .set_addr_win = lcd_set_addr_win, }, };
对接驱动框架
完成了屏幕驱动的编写,接下来需要对接到 SPILCD 驱动框架。首先编辑
Kconfig
增加
st7789v
的配置config LCD_SUPPORT_ST7789V bool "LCD support st7789v panel" default n ---help--- If you want to support st7789v panel for display driver, select it.
然后编辑
panels.c
在panel_array
里增加st7789
驱动的引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V &st7789v_panel, #endif
之后编辑
panels.h
同样增加引用如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V extern struct __lcd_panel st7789v_panel; #endif
最后编辑外层的
Makefile
增加编译选项如下所示
obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
选择 ST7789V 驱动
在 SPILCD 驱动选择界面可以看到
LCD_FB panels select
选择 SPI 屏幕的驱动进入
LCD_FB panels select
选项选择并勾选
[*] LCD support st7789v panel
配置 SPI LCD 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
[lcd_fb0] lcd_used = 1 lcd_model_name = "spilcd" lcd_driver_name = "st7789v" lcd_x = 240 lcd_y = 320 lcd_width = 37 lcd_height = 48 lcd_data_speed = 50 lcd_pwm_used = 1 lcd_pwm_ch = 1 lcd_pwm_freq = 5000 lcd_pwm_pol = 0 lcd_if = 0 lcd_pixel_fmt = 11 lcd_dbi_fmt = 2 lcd_dbi_clk_mode = 1 lcd_dbi_te = 1 fb_buffer_num = 2 lcd_dbi_if = 4 lcd_rgb_order = 0 lcd_fps = 60 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_backlight = 100 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 lcd_spi_dc_pin = port:PA19<1><0><3><0> ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0>
编译打包
运行命令
mp
编译打包,可以看到编译了st7789v.o
测试
烧录启动之后,屏幕背光启动,但是屏幕全黑。
输入
test_spilcd
,屏幕显示黄色。输入
lv_examples 1
可以显示lvgl
界面常见问题
屏幕白屏
屏幕白屏,但是背光亮起
白屏是因为屏幕没有初始化,需要检查屏幕初始化序列或者初始化数据是否正确。
屏幕花屏
屏幕花屏,无法控制
花屏一般是因为屏幕初始化后没有正确设置
addrwin
,或者初始化序列错误。LVGL 屏幕颜色不正确
出现反色,颜色异常
请配置 LVGL
LV_COLOR_DEPTH
参数为 16,LV_COLOR_16_SWAP
为 1,这是由 SPI LCD 的特性决定的。显示反色
运行
test_spilcd
,屏幕显示蓝色。这是由于屏幕启动了 RB SWAP,一般是
0x36
寄存器修改正常显示
sunxi_lcd_cmd_write(sel, 0X36); sunxi_lcd_para_write(sel, 0x00);
反色显示
sunxi_lcd_cmd_write(sel, 0X36); sunxi_lcd_para_write(sel, 0x08);
-
【R128】应用开发案例——LVGL 与 SPI TFT GUI
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
LVGL 与 SPI TFT GUI
本次使用的是 Dshan_Display Module,如下图:
引脚配置如下:
R128 Devkit TFT 模块 PA12 CS PA13 SCK PA18 MOSI PA9 PWM PA20 RESET PA19 RS 3V3 3.3V GND GND 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
配置 SPI LCD 驱动
驱动配置请参照 《SPI 驱动 TFT LCD 屏》文章进行配置。
配置 LVGL 软件包
运行
mrtos_menuconfig
进入配置页面。在如下地址找到勾选相关软件包。System components ---> thirdparty components ---> [*] Littlevgl-8 ---> [*] lvgl examples [*] lvgl-8.1.0 use sunxifb double buffer [*] lvgl-8.1.0 use sunxifb cache
注意,
lv_examples
与lv_g2d_test
不能同时勾选,否则会报错重复定义错误。错误如下:
如果出现
update_mbr_failed
,请参照常见问题修改分区表增大分区容量修改 LVGL 显示配置
进入
lichee/rtos-components/thirdparty/littlevgl-8/lv_examples/src/lv_conf.h
找到LV_COLOR_DEPTH
并修改为 16 位,LV_COLOR_16_SWAP
置为 1 进行红蓝颜色交换。测试
编译打包刷写后,在控制台输入
lv_examples
可以查看相关命令。运行
lv_examples 1
即可在屏幕显示 LVGL 界面。 -
【R128】应用开发案例——SPI 驱动 TFT LCD 屏
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
SPI 驱动 TFT LCD 屏
R128 平台提供了 SPI DBI 的 SPI TFT 接口,具有如下特点:
- Supports DBI Type C 3 Line/4 Line Interface Mode
- Supports 2 Data Lane Interface Mode
- Supports data source from CPU or DMA
- Supports RGB111/444/565/666/888 video format
- Maximum resolution of RGB666 240 x 320@30Hz with single data lane
- Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane
- Supports tearing effect
- Supports software flexible control video frame rate
同时,提供了SPILCD驱动框架以供 SPI 屏幕使用。
本次使用的是 Dshan_Display Module,如下图:
引脚配置如下:
R128 Devkit TFT 模块 PA12 CS PA13 SCK PA18 MOSI PA9 PWM PA20 RESET PA19 RS 3V3 3.3V GND GND 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 SPI 驱动
屏幕使用的是SPI驱动,所以需要勾选SPI驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPI Devices
Drivers Options ---> soc related device drivers ---> SPI Devices ---> -*- enable spi driver
配置 SPI 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,在这里我们不需要用到 SPI HOLD与SPI WP引脚,注释掉即可。;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;Please config spi in dts [spi1] spi1_used = 1 spi1_cs_number = 1 spi1_cs_bitmap = 1 spi1_cs0 = port:PA12<6><0><3><default> spi1_sclk = port:PA13<6><0><3><default> spi1_mosi = port:PA18<6><0><3><default> spi1_miso = port:PA21<6><0><3><default> ;spi1_hold = port:PA19<6><0><2><default> ;spi1_wp = port:PA20<6><0><2><default>
设置 PWM 驱动
屏幕背光使用的是PWM驱动,所以需要勾选PWM驱动,运行
mrtos_menuconfig
进入配置页面。前往下列地址找到PWM Devices
Drivers Options ---> soc related device drivers ---> PWM Devices ---> -*- enable pwm driver
配置 PWM 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
,增加 PWM1 节点[pwm1] pwm_used = 1 pwm_positive = port:PA9<4><0><3><default>
设置 SPI LCD 驱动
SPI LCD 由专门的驱动管理。运行
mrtos_menuconfig
进入配置页面。前往下列地址找到SPILCD Devices
,注意同时勾选spilcd hal APIs test
方便测试使用。Drivers Options ---> soc related device drivers ---> [*] DISP Driver Support(spi_lcd) [*] spilcd hal APIs test
选择驱动的显示屏
在 SPILCD 驱动选择界面可以看到
LCD_FB panels select
选择 SPI 屏幕的驱动,本文只注重于 SPI LCD 的使用,驱动编写请查看《SPI LCD 显示驱动》进入
LCD_FB panels select
选项选择并勾选
[*] LCD support JLT35031C panel
配置 SPI LCD 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
FEX 解析器不支持行尾注释,直接复制需要删除行尾的注释
;---------------------------------------------------------------------------------- ;lcd_fb0 configuration ;---------------------------------------------------------------------------------- [lcd_fb0] lcd_used = 1 ; 使用显示屏 lcd_model_name = "spilcd" ; 模型:spilcd lcd_driver_name = "jlt35031c" ; 屏幕驱动:jlt35031c lcd_x = 320 ; 屏幕宽分辨率 lcd_y = 480 ; 屏幕高分辨率 lcd_width = 49 ; 屏幕物理宽度 lcd_height = 74 ; 屏幕物理高度 lcd_data_speed = 60 ; SPI 驱动频率 60MHz lcd_pwm_used = 1 ; lcd使用pwm背光 lcd_pwm_ch = 1 ; lcd使用pwm背光通道1 lcd_pwm_freq = 5000 ; lcd使用pwm背光频率5000Hz lcd_pwm_pol = 0 ; lcd使用pwm背光相位0 lcd_if = 0 ; lcd使用spi接口,0-spi, 1-dbi lcd_pixel_fmt = 11 ; 以下内容详见 SPILCD 文档 lcd_dbi_fmt = 2 lcd_dbi_clk_mode = 1 lcd_dbi_te = 1 fb_buffer_num = 2 lcd_dbi_if = 4 lcd_rgb_order = 0 lcd_fps = 60 lcd_spi_bus_num = 1 lcd_frm = 2 lcd_gamma_en = 1 lcd_backlight = 100 lcd_power_num = 0 lcd_gpio_regu_num = 0 lcd_bl_percent_num = 0 lcd_spi_dc_pin = port:PA19<1><0><3><0> ; DC脚 ;RESET Pin lcd_gpio_0 = port:PA20<1><0><2><0> ; 复位脚
结果
以上配置完成后,编译打包烧录,上电后屏幕背光亮起,屏幕为黑色。
并且可以在 LOG 中看到
[LCD_FB] lcd_fb_probe,line:103:
和spi_clk_init()1609 [spi1] clk rate auto adjust to 48000000
SPI 初始化的 LOG。然后可以用
test_spilcd
测试屏幕,日志如下执行命令之后屏幕会变为黄色。
- SPI 屏幕颜色说明:<SPI LCD 显示驱动 - SPI LCD 颜色相关问题>
test_spilcd
代码如下:#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <hal_cache.h> #include <hal_mem.h> #include <hal_log.h> #include <hal_cmd.h> #include <hal_lcd_fb.h> static uint32_t width; static uint32_t height; static long int screensize = 0; static char *fbsmem_start = 0; static void lcdfb_fb_init(uint32_t yoffset, struct fb_info *p_info) { p_info->screen_base = fbsmem_start; p_info->var.xres = width; p_info->var.yres = height; p_info->var.xoffset = 0; p_info->var.yoffset = yoffset; } int show_rgb(unsigned int sel) { int i = 0, ret = -1; struct fb_info fb_info; int bpp = 4; unsigned char color[4] = {0xff,0x0,0xff,0x0}; width = bsp_disp_get_screen_width(sel); height = bsp_disp_get_screen_height(sel); screensize = width * bpp * height; fbsmem_start = hal_malloc_coherent(screensize); hal_log_info("width = %d, height = %d, screensize = %d, fbsmem_start = %x\n", width, height, screensize, fbsmem_start); memset(fbsmem_start, 0, screensize); for (i = 0; i < screensize / bpp; ++i) { memcpy(fbsmem_start+i*bpp, color, bpp); } memset(&fb_info, 0, sizeof(struct fb_info)); lcdfb_fb_init(0, &fb_info); hal_dcache_clean((unsigned long)fbsmem_start, screensize); bsp_disp_lcd_set_layer(sel, &fb_info); hal_free_coherent(fbsmem_start); return ret; } static int cmd_test_spilcd(int argc, char **argv) { uint8_t ret; hal_log_info("Run spilcd hal layer test case\n"); ret = show_rgb(0); hal_log_info("spilcd test finish\n"); return ret; } FINSH_FUNCTION_EXPORT_CMD(cmd_test_spilcd, test_spilcd, spilcd hal APIs tests)
-
【R128】应用开发案例——驱动 OLED 屏
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
驱动 OLED 屏
本文案例代码 下载地址 OLED驱动案例代码 https://www.aw-ol.com/downloads?cat=24 OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背 光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及 制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一 些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。
在此我们使用的 是0.96寸OLED显示屏,该屏有以下特点:
- 0.96 寸 OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝; 而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字; 蓝色则为纯蓝,也就是黑底蓝字。
- 分辨率为 128*64
- 多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的 串行 SPI 接口方式、 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!),这五种接口是通过屏上的 BS0~BS2 来配置的。
本次使用的是四针的IIC 模块,如下图:
引脚配置如下
R128 Devkit OLED PA23 SCL PA24 SDA GND GND 3V3 VCC 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
设置 TWI 驱动
运行
mrtos_menuconfig
进入配置页面。前往下列地址找到TWI Devices
Drivers Options ---> soc related device drivers ---> TWI Devices ---> -*- enable twi driver
配置 TWI 引脚
打开你喜欢的编辑器,修改文件:
board/r128s2/module/configs/sys_config.fex
增加 TWI0 的配置,参考手册可知 PA23,PA24 的 TWI0 复用为
6
[twi0] twi0_sck = port:PA23<6><1><default><default> twi0_sda = port:PA24<6><1><default><default>
编写程序
打开你喜欢的编辑器,新增文件:
lichee/rtos/projects/r128s2/module_c906/src/oled.c
用于编写 OLED 的驱动。编写 OLED 驱动
使用一个宏储存 TWI 的地址和使用的 TWI 端口,并定义显存。
#define OLED_IIC_ADDR 0x3c #define OLED_IIC_PORT 0 uint8_t OLED_GRAM[144][8]; /* 显存 */
编写 OLED 的基础操作驱动 TWI 通讯函数。
#define OLED_CMD 0 /*写命令 */ #define OLED_DATA 1 /* 写数据 */ void OLED_WR_Byte(uint8_t dat, uint8_t mode) { twi_msg_t msg; twi_port_t port = OLED_IIC_PORT; uint8_t buf[2]; if (mode) buf[0] = 0x40; else buf[0] = 0x00; buf[1] = dat; msg.flags = 0; msg.addr = OLED_IIC_ADDR; msg.len = 2; msg.buf = buf; hal_twi_control(port, I2C_RDWR, &msg); }
编写 OLED 的驱动函数:显示开关
void OLED_ColorTurn(uint8_t i) { if (i == 0) { OLED_WR_Byte(0xA6, OLED_CMD); /* 正常显示 */ } if (i == 1) { OLED_WR_Byte(0xA7, OLED_CMD); /* 反色显示 */ } }
编写 OLED 的驱动函数:反显设置
void OLED_DisplayTurn(uint8_t i) { if (i == 0) { OLED_WR_Byte(0xC8, OLED_CMD); /* 正常显示 */ OLED_WR_Byte(0xA1, OLED_CMD); } if (i == 1) { OLED_WR_Byte(0xC0, OLED_CMD); /* 反转显示 */ OLED_WR_Byte(0xA0, OLED_CMD); } }
编写 OLED 的驱动函数:刷新屏幕
void OLED_Refresh(void) { for (int i = 0; i < 8; i++) { OLED_WR_Byte(0xb0 + i, OLED_CMD); //设置行起始地址 OLED_WR_Byte(0x00, OLED_CMD); //设置低列起始地址 OLED_WR_Byte(0x10, OLED_CMD); //设置高列起始地址 OLED_WR_Byte(0x78, OLED_DATA); OLED_WR_Byte(0x40, OLED_DATA); for (int n = 0; n < 128; n++) { OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA); } } }
编写 OLED 的驱动函数:清屏
void OLED_Clear(void) { for (int i = 0; i < 8; i++) { OLED_WR_Byte(0xb0 + i, OLED_CMD); //设置页地址(0~7) OLED_WR_Byte(0x00, OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte(0x10, OLED_CMD); //设置显示位置—列高地址 for (int n = 0; n < 128; n++) OLED_GRAM[n][i] = 0; OLED_Refresh(); //刷新GRAM内容 } }
编写 OLED 的驱动函数:画点
void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t) { uint8_t i, m, n; i = y / 8; m = y % 8; n = 1 << m; if (t) { OLED_GRAM[x][i] |= n; } else { OLED_GRAM[x][i] = ~OLED_GRAM[x][i]; OLED_GRAM[x][i] |= n; OLED_GRAM[x][i] = ~OLED_GRAM[x][i]; } }
编写 OLED 的驱动函数:画线
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode) { int xerr = 0, yerr = 0, delta_x, delta_y, distance; int incx, incy, uRow, uCol; delta_x = x2 - x1; //计算坐标增量 delta_y = y2 - y1; uRow = x1; //画线起点坐标 uCol = y1; if (delta_x > 0) incx = 1; //设置单步方向 else if (delta_x == 0) incx = 0; //垂直线 else { incx = -1; delta_x = -delta_x; } if (delta_y > 0) incy = 1; else if (delta_y == 0) incy = 0; //水平线 else { incy = -1; delta_y = -delta_x; } if (delta_x > delta_y) distance = delta_x; //选取基本增量坐标轴 else distance = delta_y; for (uint16_t t = 0; t < distance + 1; t++) { OLED_DrawPoint(uRow, uCol, mode); //画点 xerr += delta_x; yerr += delta_y; if (xerr > distance) { xerr -= distance; uRow += incx; } if (yerr > distance) { yerr -= distance; uCol += incy; } } }
编写 OLED 的驱动函数:画园
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r) { int a, b, num; a = 0; b = r; while (2 * b * b >= r * r) { OLED_DrawPoint(x + a, y - b, 1); OLED_DrawPoint(x - a, y - b, 1); OLED_DrawPoint(x - a, y + b, 1); OLED_DrawPoint(x + a, y + b, 1); OLED_DrawPoint(x + b, y + a, 1); OLED_DrawPoint(x + b, y - a, 1); OLED_DrawPoint(x - b, y - a, 1); OLED_DrawPoint(x - b, y + a, 1); a++; num = (a * a + b * b) - r * r; //计算画的点离圆心的距离 if (num > 0) { b--; a--; } } }
编写 OLED 的驱动函数:显示字符
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1, uint8_t mode) { uint8_t temp, size2, chr1; uint8_t x0 = x, y0 = y; if (size1 == 8) size2 = 6; else size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); //得到字体一个字符对应点阵集所占的字节数 chr1 = chr - ' '; //计算偏移后的值 for (uint8_t i = 0; i < size2; i++) { if (size1 == 8) { temp = asc2_0806[chr1][i]; } //调用0806字体 else if (size1 == 12) { temp = asc2_1206[chr1][i]; } //调用1206字体 else if (size1 == 16) { temp = asc2_1608[chr1][i]; } //调用1608字体 else if (size1 == 24) { temp = asc2_2412[chr1][i]; } //调用2412字体 else return; for (uint8_t m = 0; m < 8; m++) { if (temp & 0x01) OLED_DrawPoint(x, y, mode); else OLED_DrawPoint(x, y, !mode); temp >>= 1; y++; } x++; if ((size1 != 8) && ((x - x0) == size1 / 2)) { x = x0; y0 = y0 + 8; } y = y0; } }
编写 OLED 的驱动函数:显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t* chr, uint8_t size1, uint8_t mode) { while ((*chr >= ' ') && (*chr <= '~')) //判断是不是非法字符! { OLED_ShowChar(x, y, *chr, size1, mode); if (size1 == 8) x += 6; else x += size1 / 2; chr++; } }
编写 OLED 的驱动函数:显示数字
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1, uint8_t mode) { uint8_t temp, m = 0; if (size1 == 8) m = 2; for (uint8_t t = 0; t < len; t++) { temp = (num / OLED_Pow(10, len - t - 1)) % 10; if (temp == 0) { OLED_ShowChar(x + (size1 / 2 + m) * t, y, '0', size1, mode); } else { OLED_ShowChar(x + (size1 / 2 + m) * t, y, temp + '0', size1, mode); } } }
编写 OLED 的驱动函数:配置位置
void OLED_Set_Pos(uint8_t x, uint8_t y) { OLED_WR_Byte(0xb0 + y, OLED_CMD); OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD); OLED_WR_Byte((x & 0x0f), OLED_CMD); }
编写 OLED 的驱动函数:初始化屏幕
void OLED_Init(void) { hal_twi_init(OLED_IIC_PORT); //-- init TWI OLED_WR_Byte(0xAE, OLED_CMD);//--turn off oled panel OLED_WR_Byte(0x00, OLED_CMD);//---set low column address OLED_WR_Byte(0x10, OLED_CMD);//---set high column address OLED_WR_Byte(0x40, OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) OLED_WR_Byte(0x81, OLED_CMD);//--set contrast control register OLED_WR_Byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness OLED_WR_Byte(0xA1, OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常 OLED_WR_Byte(0xC8, OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常 OLED_WR_Byte(0xA6, OLED_CMD);//--set normal display OLED_WR_Byte(0xA8, OLED_CMD);//--set multiplex ratio(1 to 64) OLED_WR_Byte(0x3f, OLED_CMD);//--1/64 duty OLED_WR_Byte(0xD3, OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) OLED_WR_Byte(0x00, OLED_CMD);//-not offset OLED_WR_Byte(0xd5, OLED_CMD);//--set display clock divide ratio/oscillator frequency OLED_WR_Byte(0x80, OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec OLED_WR_Byte(0xD9, OLED_CMD);//--set pre-charge period OLED_WR_Byte(0xF1, OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock OLED_WR_Byte(0xDA, OLED_CMD);//--set com pins hardware configuration OLED_WR_Byte(0x12, OLED_CMD); OLED_WR_Byte(0xDB, OLED_CMD);//--set vcomh OLED_WR_Byte(0x40, OLED_CMD);//Set VCOM Deselect Level OLED_WR_Byte(0x20, OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) OLED_WR_Byte(0x02, OLED_CMD);// OLED_WR_Byte(0x8D, OLED_CMD);//--set Charge Pump enable/disable OLED_WR_Byte(0x14, OLED_CMD);//--set(0x10) disable OLED_WR_Byte(0xA4, OLED_CMD);// Disable Entire Display On (0xa4/0xa5) OLED_WR_Byte(0xA6, OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_Clear(); OLED_WR_Byte(0xAF, OLED_CMD); /*display ON*/ }
我们把这些驱动放到一个单独的文件里,命名为
oled.c
,完整内容如下:#include <sunxi_hal_twi.h> #include "oledfont.h" #define OLED_IIC_ADDR 0x3c #define OLED_IIC_PORT 0 #define OLED_CMD 0 /*写命令 */ #define OLED_DATA 1 /* 写数据 */ uint8_t OLED_GRAM[144][8]; /* 显存 */ void OLED_WR_Byte(uint8_t dat, uint8_t mode) { twi_msg_t msg; twi_port_t port = OLED_IIC_PORT; uint8_t buf[2]; if (mode) buf[0] = 0x40; else buf[0] = 0x00; buf[1] = dat; msg.flags = 0; msg.addr = OLED_IIC_ADDR; msg.len = 2; msg.buf = buf; hal_twi_control(port, I2C_RDWR, &msg); } uint32_t OLED_Pow(uint8_t m, uint8_t n) { uint32_t result = 1; while (n--) result *= m; return result; } void OLED_ColorTurn(uint8_t i) { if (i == 0) { OLED_WR_Byte(0xA6, OLED_CMD); /* 正常显示 */ } if (i == 1) { OLED_WR_Byte(0xA7, OLED_CMD); /* 反色显示 */ } } void OLED_DisplayTurn(uint8_t i) { if (i == 0) { OLED_WR_Byte(0xC8, OLED_CMD); /* 正常显示 */ OLED_WR_Byte(0xA1, OLED_CMD); } if (i == 1) { OLED_WR_Byte(0xC0, OLED_CMD); /* 反转显示 */ OLED_WR_Byte(0xA0, OLED_CMD); } } void OLED_Refresh(void) { for (int i = 0; i < 8; i++) { OLED_WR_Byte(0xb0 + i, OLED_CMD); //设置行起始地址 OLED_WR_Byte(0x00, OLED_CMD); //设置低列起始地址 OLED_WR_Byte(0x10, OLED_CMD); //设置高列起始地址 OLED_WR_Byte(0x78, OLED_DATA); OLED_WR_Byte(0x40, OLED_DATA); for (int n = 0; n < 128; n++) { OLED_WR_Byte(OLED_GRAM[n][i], OLED_DATA); } } } void OLED_Clear(void) { for (int i = 0; i < 8; i++) { OLED_WR_Byte(0xb0 + i, OLED_CMD); //设置页地址(0~7) OLED_WR_Byte(0x00, OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte(0x10, OLED_CMD); //设置显示位置—列高地址 for (int n = 0; n < 128; n++) OLED_GRAM[n][i] = 0; OLED_Refresh(); //刷新GRAM内容 } } void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t) { uint8_t i, m, n; i = y / 8; m = y % 8; n = 1 << m; if (t) { OLED_GRAM[x][i] |= n; } else { OLED_GRAM[x][i] = ~OLED_GRAM[x][i]; OLED_GRAM[x][i] |= n; OLED_GRAM[x][i] = ~OLED_GRAM[x][i]; } } void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode) { int xerr = 0, yerr = 0, delta_x, delta_y, distance; int incx, incy, uRow, uCol; delta_x = x2 - x1; //计算坐标增量 delta_y = y2 - y1; uRow = x1; //画线起点坐标 uCol = y1; if (delta_x > 0) incx = 1; //设置单步方向 else if (delta_x == 0) incx = 0; //垂直线 else { incx = -1; delta_x = -delta_x; } if (delta_y > 0) incy = 1; else if (delta_y == 0) incy = 0; //水平线 else { incy = -1; delta_y = -delta_x; } if (delta_x > delta_y) distance = delta_x; //选取基本增量坐标轴 else distance = delta_y; for (uint16_t t = 0; t < distance + 1; t++) { OLED_DrawPoint(uRow, uCol, mode); //画点 xerr += delta_x; yerr += delta_y; if (xerr > distance) { xerr -= distance; uRow += incx; } if (yerr > distance) { yerr -= distance; uCol += incy; } } } void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r) { int a, b, num; a = 0; b = r; while (2 * b * b >= r * r) { OLED_DrawPoint(x + a, y - b, 1); OLED_DrawPoint(x - a, y - b, 1); OLED_DrawPoint(x - a, y + b, 1); OLED_DrawPoint(x + a, y + b, 1); OLED_DrawPoint(x + b, y + a, 1); OLED_DrawPoint(x + b, y - a, 1); OLED_DrawPoint(x - b, y - a, 1); OLED_DrawPoint(x - b, y + a, 1); a++; num = (a * a + b * b) - r * r; //计算画的点离圆心的距离 if (num > 0) { b--; a--; } } } void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1, uint8_t mode) { uint8_t temp, size2, chr1; uint8_t x0 = x, y0 = y; if (size1 == 8) size2 = 6; else size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); //得到字体一个字符对应点阵集所占的字节数 chr1 = chr - ' '; //计算偏移后的值 for (uint8_t i = 0; i < size2; i++) { if (size1 == 8) { temp = asc2_0806[chr1][i]; } //调用0806字体 else if (size1 == 12) { temp = asc2_1206[chr1][i]; } //调用1206字体 else if (size1 == 16) { temp = asc2_1608[chr1][i]; } //调用1608字体 else if (size1 == 24) { temp = asc2_2412[chr1][i]; } //调用2412字体 else return; for (uint8_t m = 0; m < 8; m++) { if (temp & 0x01) OLED_DrawPoint(x, y, mode); else OLED_DrawPoint(x, y, !mode); temp >>= 1; y++; } x++; if ((size1 != 8) && ((x - x0) == size1 / 2)) { x = x0; y0 = y0 + 8; } y = y0; } } void OLED_ShowString(uint8_t x, uint8_t y, uint8_t* chr, uint8_t size1, uint8_t mode) { while ((*chr >= ' ') && (*chr <= '~')) //判断是不是非法字符! { OLED_ShowChar(x, y, *chr, size1, mode); if (size1 == 8) x += 6; else x += size1 / 2; chr++; } } void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1, uint8_t mode) { uint8_t temp, m = 0; if (size1 == 8) m = 2; for (uint8_t t = 0; t < len; t++) { temp = (num / OLED_Pow(10, len - t - 1)) % 10; if (temp == 0) { OLED_ShowChar(x + (size1 / 2 + m) * t, y, '0', size1, mode); } else { OLED_ShowChar(x + (size1 / 2 + m) * t, y, temp + '0', size1, mode); } } } void OLED_Set_Pos(uint8_t x, uint8_t y) { OLED_WR_Byte(0xb0 + y, OLED_CMD); OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD); OLED_WR_Byte((x & 0x0f), OLED_CMD); } void OLED_Init(void) { hal_twi_init(OLED_IIC_PORT); //-- init TWI OLED_WR_Byte(0xAE, OLED_CMD);//--turn off oled panel OLED_WR_Byte(0x00, OLED_CMD);//---set low column address OLED_WR_Byte(0x10, OLED_CMD);//---set high column address OLED_WR_Byte(0x40, OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F) OLED_WR_Byte(0x81, OLED_CMD);//--set contrast control register OLED_WR_Byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness OLED_WR_Byte(0xA1, OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常 OLED_WR_Byte(0xC8, OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常 OLED_WR_Byte(0xA6, OLED_CMD);//--set normal display OLED_WR_Byte(0xA8, OLED_CMD);//--set multiplex ratio(1 to 64) OLED_WR_Byte(0x3f, OLED_CMD);//--1/64 duty OLED_WR_Byte(0xD3, OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F) OLED_WR_Byte(0x00, OLED_CMD);//-not offset OLED_WR_Byte(0xd5, OLED_CMD);//--set display clock divide ratio/oscillator frequency OLED_WR_Byte(0x80, OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec OLED_WR_Byte(0xD9, OLED_CMD);//--set pre-charge period OLED_WR_Byte(0xF1, OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock OLED_WR_Byte(0xDA, OLED_CMD);//--set com pins hardware configuration OLED_WR_Byte(0x12, OLED_CMD); OLED_WR_Byte(0xDB, OLED_CMD);//--set vcomh OLED_WR_Byte(0x40, OLED_CMD);//Set VCOM Deselect Level OLED_WR_Byte(0x20, OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02) OLED_WR_Byte(0x02, OLED_CMD);// OLED_WR_Byte(0x8D, OLED_CMD);//--set Charge Pump enable/disable OLED_WR_Byte(0x14, OLED_CMD);//--set(0x10) disable OLED_WR_Byte(0xA4, OLED_CMD);// Disable Entire Display On (0xa4/0xa5) OLED_WR_Byte(0xA6, OLED_CMD);// Disable Inverse Display On (0xa6/a7) OLED_Clear(); OLED_WR_Byte(0xAF, OLED_CMD); /*display ON*/ }
同时提供对应的头文件
oled.h
,oledfont.h
由于较大,见文章末尾。#ifndef __OLED_H #define __OLED_H /* 发送一个字节 * mode:数据/命令标志 0,表示命令;1,表示数据; */ void OLED_WR_Byte(uint8_t dat, uint8_t mode); /* Pow 函数 */ uint32_t OLED_Pow(uint8_t m, uint8_t n); /* 反显函数 */ void OLED_ColorTurn(uint8_t i); /* 屏幕旋转180度 */ void OLED_DisplayTurn(uint8_t i); /* 更新显存到OLED */ void OLED_Refresh(void); /* OLED 清屏 */ void OLED_Clear(void); /* 画点 * x: 0~127 * y: 0~63 * t: 1 填充 0,清空 */ void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t t); /* 画线 * x1, y1: 起点坐标 * x2, y2: 结束坐标 */ void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t mode); /* 画圆 * x, y: 圆心坐标 * r: 圆的半径 */ void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r); /* 在指定位置显示一个字符,包括部分字符 * x: 0 ~ 127 * y: 0 ~ 63 * size1: 选择字体 6x8/6x12/8x16/12x24 * mode: 0,反色显示; 1,正常显示 */ void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1, uint8_t mode); /* 显示字符串 * x: 0 ~ 127 * y: 0 ~ 63 * *chr: 字符串起始地址 * size1: 选择字体 6x8/6x12/8x16/12x24 * mode: 0,反色显示; 1,正常显示 */ void OLED_ShowString(uint8_t x, uint8_t y, uint8_t* chr, uint8_t size1, uint8_t mode); /* 显示数字 * x: 0 ~ 127 * y: 0 ~ 63 * num: 要显示的数字 * len: 数字的位数 * size1: 选择字体 6x8/6x12/8x16/12x24 * mode: 0,反色显示; 1,正常显示 */ void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1, uint8_t mode); /* 初始化 OLED */ void OLED_Init(void); #endif
然后把
oled.c
加入编译,修改lichee/rtos/projects/r128s2/module_c906/Makefile
加入obj-y += src/oled.o
编写主程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
OLED_Init(); OLED_ShowString(12, 16, "====AWOL====", 16, 1); OLED_ShowString(20, 32, "2023/08/24", 16, 1); OLED_Refresh();
初始化 OLED,然后显示字符串。
结果
驱动了 OLED 屏
附录
oledfont.h
:#ifndef __OLEDFONT_H #define __OLEDFONT_H const unsigned char asc2_0806[][6] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // sp { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // ! { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // " { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // # { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $ { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // % { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // & { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // ' { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // ( { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // ) { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // * { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // + { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // , { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // - { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // . { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // / { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1 { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2 { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3 { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4 { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5 { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6 { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7 { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8 { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9 { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // : { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ; { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // < { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // = { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // > { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ? { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // @ { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [ { 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 }, // 55 { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ] { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^ { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _ { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 }, // ' { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 }, // a { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 }, // c { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F }, // d { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 }, // e { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 }, // j { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 }, // o { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 }, // p { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC }, // q { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 }, // s { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 }, // x { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C }, // y { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }, // horiz lines }; //12*12 ASCII字符集点阵 const unsigned char asc2_1206[95][12] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*" ",0*/ { 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, /*"!",1*/ { 0x00, 0x0C, 0x02, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*""",2*/ { 0x90, 0xD0, 0xBC, 0xD0, 0xBC, 0x90, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00 }, /*"#",3*/ { 0x18, 0x24, 0xFE, 0x44, 0x8C, 0x00, 0x03, 0x02, 0x07, 0x02, 0x01, 0x00 }, /*"$",4*/ { 0x18, 0x24, 0xD8, 0xB0, 0x4C, 0x80, 0x00, 0x03, 0x00, 0x01, 0x02, 0x01 }, /*"%",5*/ { 0xC0, 0x38, 0xE4, 0x38, 0xE0, 0x00, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02 }, /*"&",6*/ { 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"'",7*/ { 0x00, 0x00, 0x00, 0xF8, 0x04, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04 }, /*"(",8*/ { 0x00, 0x02, 0x04, 0xF8, 0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00 }, /*")",9*/ { 0x90, 0x60, 0xF8, 0x60, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }, /*"*",10*/ { 0x20, 0x20, 0xFC, 0x20, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }, /*"+",11*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00 }, /*",",12*/ { 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"-",13*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00 }, /*".",14*/ { 0x00, 0x80, 0x60, 0x1C, 0x02, 0x00, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00 }, /*"/",15*/ { 0xF8, 0x04, 0x04, 0x04, 0xF8, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"0",16*/ { 0x00, 0x08, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00 }, /*"1",17*/ { 0x18, 0x84, 0x44, 0x24, 0x18, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x00 }, /*"2",18*/ { 0x08, 0x04, 0x24, 0x24, 0xD8, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"3",19*/ { 0x40, 0xB0, 0x88, 0xFC, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00 }, /*"4",20*/ { 0x3C, 0x24, 0x24, 0x24, 0xC4, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"5",21*/ { 0xF8, 0x24, 0x24, 0x2C, 0xC0, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"6",22*/ { 0x0C, 0x04, 0xE4, 0x1C, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, /*"7",23*/ { 0xD8, 0x24, 0x24, 0x24, 0xD8, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"8",24*/ { 0x38, 0x44, 0x44, 0x44, 0xF8, 0x00, 0x00, 0x03, 0x02, 0x02, 0x01, 0x00 }, /*"9",25*/ { 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, /*":",26*/ { 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00 }, /*";",27*/ { 0x00, 0x20, 0x50, 0x88, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02 }, /*"<",28*/ { 0x90, 0x90, 0x90, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"=",29*/ { 0x00, 0x02, 0x04, 0x88, 0x50, 0x20, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00 }, /*">",30*/ { 0x18, 0x04, 0xC4, 0x24, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }, /*"?",31*/ { 0xF8, 0x04, 0xE4, 0x94, 0xF8, 0x00, 0x01, 0x02, 0x02, 0x02, 0x02, 0x00 }, /*"@",32*/ { 0x00, 0xE0, 0x9C, 0xF0, 0x80, 0x00, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02 }, /*"A",33*/ { 0x04, 0xFC, 0x24, 0x24, 0xD8, 0x00, 0x02, 0x03, 0x02, 0x02, 0x01, 0x00 }, /*"B",34*/ { 0xF8, 0x04, 0x04, 0x04, 0x0C, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"C",35*/ { 0x04, 0xFC, 0x04, 0x04, 0xF8, 0x00, 0x02, 0x03, 0x02, 0x02, 0x01, 0x00 }, /*"D",36*/ { 0x04, 0xFC, 0x24, 0x74, 0x0C, 0x00, 0x02, 0x03, 0x02, 0x02, 0x03, 0x00 }, /*"E",37*/ { 0x04, 0xFC, 0x24, 0x74, 0x0C, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00, 0x00 }, /*"F",38*/ { 0xF0, 0x08, 0x04, 0x44, 0xCC, 0x40, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00 }, /*"G",39*/ { 0x04, 0xFC, 0x20, 0x20, 0xFC, 0x04, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02 }, /*"H",40*/ { 0x04, 0x04, 0xFC, 0x04, 0x04, 0x00, 0x02, 0x02, 0x03, 0x02, 0x02, 0x00 }, /*"I",41*/ { 0x00, 0x04, 0x04, 0xFC, 0x04, 0x04, 0x06, 0x04, 0x04, 0x03, 0x00, 0x00 }, /*"J",42*/ { 0x04, 0xFC, 0x24, 0xD0, 0x0C, 0x04, 0x02, 0x03, 0x02, 0x00, 0x03, 0x02 }, /*"K",43*/ { 0x04, 0xFC, 0x04, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03 }, /*"L",44*/ { 0xFC, 0x3C, 0xC0, 0x3C, 0xFC, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00 }, /*"M",45*/ { 0x04, 0xFC, 0x30, 0xC4, 0xFC, 0x04, 0x02, 0x03, 0x02, 0x00, 0x03, 0x00 }, /*"N",46*/ { 0xF8, 0x04, 0x04, 0x04, 0xF8, 0x00, 0x01, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"O",47*/ { 0x04, 0xFC, 0x24, 0x24, 0x18, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00, 0x00 }, /*"P",48*/ { 0xF8, 0x84, 0x84, 0x04, 0xF8, 0x00, 0x01, 0x02, 0x02, 0x07, 0x05, 0x00 }, /*"Q",49*/ { 0x04, 0xFC, 0x24, 0x64, 0x98, 0x00, 0x02, 0x03, 0x02, 0x00, 0x03, 0x02 }, /*"R",50*/ { 0x18, 0x24, 0x24, 0x44, 0x8C, 0x00, 0x03, 0x02, 0x02, 0x02, 0x01, 0x00 }, /*"S",51*/ { 0x0C, 0x04, 0xFC, 0x04, 0x0C, 0x00, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00 }, /*"T",52*/ { 0x04, 0xFC, 0x00, 0x00, 0xFC, 0x04, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00 }, /*"U",53*/ { 0x04, 0x7C, 0x80, 0xE0, 0x1C, 0x04, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, /*"V",54*/ { 0x1C, 0xE0, 0x3C, 0xE0, 0x1C, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00 }, /*"W",55*/ { 0x04, 0x9C, 0x60, 0x9C, 0x04, 0x00, 0x02, 0x03, 0x00, 0x03, 0x02, 0x00 }, /*"X",56*/ { 0x04, 0x1C, 0xE0, 0x1C, 0x04, 0x00, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00 }, /*"Y",57*/ { 0x0C, 0x84, 0x64, 0x1C, 0x04, 0x00, 0x02, 0x03, 0x02, 0x02, 0x03, 0x00 }, /*"Z",58*/ { 0x00, 0x00, 0xFE, 0x02, 0x02, 0x00, 0x00, 0x00, 0x07, 0x04, 0x04, 0x00 }, /*"[",59*/ { 0x00, 0x0E, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00 }, /*"\",60*/ { 0x00, 0x02, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x04, 0x04, 0x07, 0x00, 0x00 }, /*"]",61*/ { 0x00, 0x04, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"^",62*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 }, /*"_",63*/ { 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"`",64*/ { 0x00, 0x40, 0xA0, 0xA0, 0xC0, 0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x02 }, /*"a",65*/ { 0x04, 0xFC, 0x20, 0x20, 0xC0, 0x00, 0x00, 0x03, 0x02, 0x02, 0x01, 0x00 }, /*"b",66*/ { 0x00, 0xC0, 0x20, 0x20, 0x60, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00 }, /*"c",67*/ { 0x00, 0xC0, 0x20, 0x24, 0xFC, 0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x02 }, /*"d",68*/ { 0x00, 0xC0, 0xA0, 0xA0, 0xC0, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x00 }, /*"e",69*/ { 0x00, 0x20, 0xF8, 0x24, 0x24, 0x04, 0x00, 0x02, 0x03, 0x02, 0x02, 0x00 }, /*"f",70*/ { 0x00, 0x40, 0xA0, 0xA0, 0x60, 0x20, 0x00, 0x07, 0x0A, 0x0A, 0x0A, 0x04 }, /*"g",71*/ { 0x04, 0xFC, 0x20, 0x20, 0xC0, 0x00, 0x02, 0x03, 0x02, 0x00, 0x03, 0x02 }, /*"h",72*/ { 0x00, 0x20, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00 }, /*"i",73*/ { 0x00, 0x00, 0x20, 0xE4, 0x00, 0x00, 0x08, 0x08, 0x08, 0x07, 0x00, 0x00 }, /*"j",74*/ { 0x04, 0xFC, 0x80, 0xE0, 0x20, 0x20, 0x02, 0x03, 0x02, 0x00, 0x03, 0x02 }, /*"k",75*/ { 0x04, 0x04, 0xFC, 0x00, 0x00, 0x00, 0x02, 0x02, 0x03, 0x02, 0x02, 0x00 }, /*"l",76*/ { 0xE0, 0x20, 0xE0, 0x20, 0xC0, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00 }, /*"m",77*/ { 0x20, 0xE0, 0x20, 0x20, 0xC0, 0x00, 0x02, 0x03, 0x02, 0x00, 0x03, 0x02 }, /*"n",78*/ { 0x00, 0xC0, 0x20, 0x20, 0xC0, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00 }, /*"o",79*/ { 0x20, 0xE0, 0x20, 0x20, 0xC0, 0x00, 0x08, 0x0F, 0x0A, 0x02, 0x01, 0x00 }, /*"p",80*/ { 0x00, 0xC0, 0x20, 0x20, 0xE0, 0x00, 0x00, 0x01, 0x02, 0x0A, 0x0F, 0x08 }, /*"q",81*/ { 0x20, 0xE0, 0x40, 0x20, 0x20, 0x00, 0x02, 0x03, 0x02, 0x00, 0x00, 0x00 }, /*"r",82*/ { 0x00, 0x60, 0xA0, 0xA0, 0x20, 0x00, 0x00, 0x02, 0x02, 0x02, 0x03, 0x00 }, /*"s",83*/ { 0x00, 0x20, 0xF8, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00 }, /*"t",84*/ { 0x20, 0xE0, 0x00, 0x20, 0xE0, 0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x02 }, /*"u",85*/ { 0x20, 0xE0, 0x20, 0x80, 0x60, 0x20, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00 }, /*"v",86*/ { 0x60, 0x80, 0xE0, 0x80, 0x60, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00 }, /*"w",87*/ { 0x20, 0x60, 0x80, 0x60, 0x20, 0x00, 0x02, 0x03, 0x00, 0x03, 0x02, 0x00 }, /*"x",88*/ { 0x20, 0xE0, 0x20, 0x80, 0x60, 0x20, 0x08, 0x08, 0x07, 0x01, 0x00, 0x00 }, /*"y",89*/ { 0x00, 0x20, 0xA0, 0x60, 0x20, 0x00, 0x00, 0x02, 0x03, 0x02, 0x02, 0x00 }, /*"z",90*/ { 0x00, 0x00, 0x20, 0xDE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x04, 0x00 }, /*"{",91*/ { 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00 }, /*"|",92*/ { 0x00, 0x02, 0xDE, 0x20, 0x00, 0x00, 0x00, 0x04, 0x07, 0x00, 0x00, 0x00 }, /*"}",93*/ { 0x02, 0x01, 0x02, 0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"~",94*/ }; //16*16 ASCII字符集点阵 const unsigned char asc2_1608[][16] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*" ",0*/ { 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x30, 0x00, 0x00, 0x00 }, /*"!",1*/ { 0x00, 0x10, 0x0C, 0x06, 0x10, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*""",2*/ { 0x40, 0xC0, 0x78, 0x40, 0xC0, 0x78, 0x40, 0x00, 0x04, 0x3F, 0x04, 0x04, 0x3F, 0x04, 0x04, 0x00 }, /*"#",3*/ { 0x00, 0x70, 0x88, 0xFC, 0x08, 0x30, 0x00, 0x00, 0x00, 0x18, 0x20, 0xFF, 0x21, 0x1E, 0x00, 0x00 }, /*"$",4*/ { 0xF0, 0x08, 0xF0, 0x00, 0xE0, 0x18, 0x00, 0x00, 0x00, 0x21, 0x1C, 0x03, 0x1E, 0x21, 0x1E, 0x00 }, /*"%",5*/ { 0x00, 0xF0, 0x08, 0x88, 0x70, 0x00, 0x00, 0x00, 0x1E, 0x21, 0x23, 0x24, 0x19, 0x27, 0x21, 0x10 }, /*"&",6*/ { 0x10, 0x16, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"'",7*/ { 0x00, 0x00, 0x00, 0xE0, 0x18, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x20, 0x40, 0x00 }, /*"(",8*/ { 0x00, 0x02, 0x04, 0x18, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x18, 0x07, 0x00, 0x00, 0x00 }, /*")",9*/ { 0x40, 0x40, 0x80, 0xF0, 0x80, 0x40, 0x40, 0x00, 0x02, 0x02, 0x01, 0x0F, 0x01, 0x02, 0x02, 0x00 }, /*"*",10*/ { 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x1F, 0x01, 0x01, 0x01, 0x00 }, /*"+",11*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xB0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*",",12*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, /*"-",13*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*".",14*/ { 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x18, 0x04, 0x00, 0x60, 0x18, 0x06, 0x01, 0x00, 0x00, 0x00 }, /*"/",15*/ { 0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x10, 0x0F, 0x00 }, /*"0",16*/ { 0x00, 0x10, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00 }, /*"1",17*/ { 0x00, 0x70, 0x08, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x30, 0x28, 0x24, 0x22, 0x21, 0x30, 0x00 }, /*"2",18*/ { 0x00, 0x30, 0x08, 0x88, 0x88, 0x48, 0x30, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"3",19*/ { 0x00, 0x00, 0xC0, 0x20, 0x10, 0xF8, 0x00, 0x00, 0x00, 0x07, 0x04, 0x24, 0x24, 0x3F, 0x24, 0x00 }, /*"4",20*/ { 0x00, 0xF8, 0x08, 0x88, 0x88, 0x08, 0x08, 0x00, 0x00, 0x19, 0x21, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"5",21*/ { 0x00, 0xE0, 0x10, 0x88, 0x88, 0x18, 0x00, 0x00, 0x00, 0x0F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"6",22*/ { 0x00, 0x38, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00 }, /*"7",23*/ { 0x00, 0x70, 0x88, 0x08, 0x08, 0x88, 0x70, 0x00, 0x00, 0x1C, 0x22, 0x21, 0x21, 0x22, 0x1C, 0x00 }, /*"8",24*/ { 0x00, 0xE0, 0x10, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x31, 0x22, 0x22, 0x11, 0x0F, 0x00 }, /*"9",25*/ { 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00 }, /*":",26*/ { 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, 0x00 }, /*";",27*/ { 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, /*"<",28*/ { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00 }, /*"=",29*/ { 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00 }, /*">",30*/ { 0x00, 0x70, 0x48, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x36, 0x01, 0x00, 0x00 }, /*"?",31*/ { 0xC0, 0x30, 0xC8, 0x28, 0xE8, 0x10, 0xE0, 0x00, 0x07, 0x18, 0x27, 0x24, 0x23, 0x14, 0x0B, 0x00 }, /*"@",32*/ { 0x00, 0x00, 0xC0, 0x38, 0xE0, 0x00, 0x00, 0x00, 0x20, 0x3C, 0x23, 0x02, 0x02, 0x27, 0x38, 0x20 }, /*"A",33*/ { 0x08, 0xF8, 0x88, 0x88, 0x88, 0x70, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"B",34*/ { 0xC0, 0x30, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x07, 0x18, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00 }, /*"C",35*/ { 0x08, 0xF8, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00 }, /*"D",36*/ { 0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x23, 0x20, 0x18, 0x00 }, /*"E",37*/ { 0x08, 0xF8, 0x88, 0x88, 0xE8, 0x08, 0x10, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00 }, /*"F",38*/ { 0xC0, 0x30, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x22, 0x1E, 0x02, 0x00 }, /*"G",39*/ { 0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x21, 0x3F, 0x20 }, /*"H",40*/ { 0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00 }, /*"I",41*/ { 0x00, 0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00, 0x00 }, /*"J",42*/ { 0x08, 0xF8, 0x88, 0xC0, 0x28, 0x18, 0x08, 0x00, 0x20, 0x3F, 0x20, 0x01, 0x26, 0x38, 0x20, 0x00 }, /*"K",43*/ { 0x08, 0xF8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x30, 0x00 }, /*"L",44*/ { 0x08, 0xF8, 0xF8, 0x00, 0xF8, 0xF8, 0x08, 0x00, 0x20, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x20, 0x00 }, /*"M",45*/ { 0x08, 0xF8, 0x30, 0xC0, 0x00, 0x08, 0xF8, 0x08, 0x20, 0x3F, 0x20, 0x00, 0x07, 0x18, 0x3F, 0x00 }, /*"N",46*/ { 0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0F, 0x00 }, /*"O",47*/ { 0x08, 0xF8, 0x08, 0x08, 0x08, 0x08, 0xF0, 0x00, 0x20, 0x3F, 0x21, 0x01, 0x01, 0x01, 0x00, 0x00 }, /*"P",48*/ { 0xE0, 0x10, 0x08, 0x08, 0x08, 0x10, 0xE0, 0x00, 0x0F, 0x18, 0x24, 0x24, 0x38, 0x50, 0x4F, 0x00 }, /*"Q",49*/ { 0x08, 0xF8, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x03, 0x0C, 0x30, 0x20 }, /*"R",50*/ { 0x00, 0x70, 0x88, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x38, 0x20, 0x21, 0x21, 0x22, 0x1C, 0x00 }, /*"S",51*/ { 0x18, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x18, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00 }, /*"T",52*/ { 0x08, 0xF8, 0x08, 0x00, 0x00, 0x08, 0xF8, 0x08, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00 }, /*"U",53*/ { 0x08, 0x78, 0x88, 0x00, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x07, 0x38, 0x0E, 0x01, 0x00, 0x00 }, /*"V",54*/ { 0xF8, 0x08, 0x00, 0xF8, 0x00, 0x08, 0xF8, 0x00, 0x03, 0x3C, 0x07, 0x00, 0x07, 0x3C, 0x03, 0x00 }, /*"W",55*/ { 0x08, 0x18, 0x68, 0x80, 0x80, 0x68, 0x18, 0x08, 0x20, 0x30, 0x2C, 0x03, 0x03, 0x2C, 0x30, 0x20 }, /*"X",56*/ { 0x08, 0x38, 0xC8, 0x00, 0xC8, 0x38, 0x08, 0x00, 0x00, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x00, 0x00 }, /*"Y",57*/ { 0x10, 0x08, 0x08, 0x08, 0xC8, 0x38, 0x08, 0x00, 0x20, 0x38, 0x26, 0x21, 0x20, 0x20, 0x18, 0x00 }, /*"Z",58*/ { 0x00, 0x00, 0x00, 0xFE, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x00 }, /*"[",59*/ { 0x00, 0x0C, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x38, 0xC0, 0x00 }, /*"\",60*/ { 0x00, 0x02, 0x02, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7F, 0x00, 0x00, 0x00 }, /*"]",61*/ { 0x00, 0x00, 0x04, 0x02, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"^",62*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /*"_",63*/ { 0x00, 0x02, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"`",64*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x19, 0x24, 0x22, 0x22, 0x22, 0x3F, 0x20 }, /*"a",65*/ { 0x08, 0xF8, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x11, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"b",66*/ { 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x20, 0x11, 0x00 }, /*"c",67*/ { 0x00, 0x00, 0x00, 0x80, 0x80, 0x88, 0xF8, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0x10, 0x3F, 0x20 }, /*"d",68*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x22, 0x22, 0x22, 0x22, 0x13, 0x00 }, /*"e",69*/ { 0x00, 0x80, 0x80, 0xF0, 0x88, 0x88, 0x88, 0x18, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00 }, /*"f",70*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x6B, 0x94, 0x94, 0x94, 0x93, 0x60, 0x00 }, /*"g",71*/ { 0x08, 0xF8, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20 }, /*"h",72*/ { 0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00 }, /*"i",73*/ { 0x00, 0x00, 0x00, 0x80, 0x98, 0x98, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x7F, 0x00, 0x00 }, /*"j",74*/ { 0x08, 0xF8, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x24, 0x02, 0x2D, 0x30, 0x20, 0x00 }, /*"k",75*/ { 0x00, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3F, 0x20, 0x20, 0x00, 0x00 }, /*"l",76*/ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x20, 0x3F, 0x20, 0x00, 0x3F, 0x20, 0x00, 0x3F }, /*"m",77*/ { 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x3F, 0x21, 0x00, 0x00, 0x20, 0x3F, 0x20 }, /*"n",78*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x1F, 0x00 }, /*"o",79*/ { 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xA1, 0x20, 0x20, 0x11, 0x0E, 0x00 }, /*"p",80*/ { 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x11, 0x20, 0x20, 0xA0, 0xFF, 0x80 }, /*"q",81*/ { 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x20, 0x20, 0x3F, 0x21, 0x20, 0x00, 0x01, 0x00 }, /*"r",82*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x33, 0x24, 0x24, 0x24, 0x24, 0x19, 0x00 }, /*"s",83*/ { 0x00, 0x80, 0x80, 0xE0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x00, 0x00 }, /*"t",84*/ { 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x1F, 0x20, 0x20, 0x20, 0x10, 0x3F, 0x20 }, /*"u",85*/ { 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x01, 0x0E, 0x30, 0x08, 0x06, 0x01, 0x00 }, /*"v",86*/ { 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x0F, 0x30, 0x0C, 0x03, 0x0C, 0x30, 0x0F, 0x00 }, /*"w",87*/ { 0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x31, 0x2E, 0x0E, 0x31, 0x20, 0x00 }, /*"x",88*/ { 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x81, 0x8E, 0x70, 0x18, 0x06, 0x01, 0x00 }, /*"y",89*/ { 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x21, 0x30, 0x2C, 0x22, 0x21, 0x30, 0x00 }, /*"z",90*/ { 0x00, 0x00, 0x00, 0x00, 0x80, 0x7C, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x40 }, /*"{",91*/ { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 }, /*"|",92*/ { 0x00, 0x02, 0x02, 0x7C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x3F, 0x00, 0x00, 0x00, 0x00 }, /*"}",93*/ { 0x00, 0x06, 0x01, 0x01, 0x02, 0x02, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"~",94*/ }; //24*24 ASICII字符集点阵 const unsigned char asc2_2412[][36] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*" ",0*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00 }, /*"!",1*/ { 0x00, 0x00, 0x80, 0x60, 0x30, 0x1C, 0x8C, 0x60, 0x30, 0x1C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*""",2*/ { 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x86, 0xE6, 0x9F, 0x86, 0x86, 0x86, 0x86, 0xE6, 0x9F, 0x86, 0x00, 0x00, 0x01, 0x1F, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1F, 0x01, 0x01, 0x00 }, /*"#",3*/ { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0xF8, 0x20, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0C, 0x18, 0xFF, 0x70, 0xE1, 0x81, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x10, 0x10, 0x7F, 0x10, 0x0F, 0x07, 0x00, 0x00 }, /*"$",4*/ { 0x80, 0x60, 0x20, 0x60, 0x80, 0x00, 0x00, 0x00, 0xE0, 0x20, 0x00, 0x00, 0x0F, 0x30, 0x20, 0x30, 0x9F, 0x70, 0xDC, 0x37, 0x10, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x03, 0x00, 0x07, 0x18, 0x10, 0x18, 0x07, 0x00 }, /*"%",5*/ { 0x00, 0x00, 0xC0, 0x20, 0x20, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0x1F, 0x38, 0xE8, 0x87, 0x03, 0xC4, 0x3C, 0x04, 0x00, 0x00, 0x07, 0x0F, 0x18, 0x10, 0x10, 0x0B, 0x07, 0x0D, 0x10, 0x10, 0x08, 0x00 }, /*"&",6*/ { 0x00, 0x80, 0x8C, 0x4C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"'",7*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0x30, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x18, 0x20, 0x40, 0x00 }, /*"(",8*/ { 0x00, 0x04, 0x08, 0x30, 0xE0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x18, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*")",9*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x66, 0x66, 0x3C, 0x18, 0xFF, 0x18, 0x3C, 0x66, 0x66, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"*",10*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"+",11*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x8C, 0x4C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*",",12*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"-",13*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*".",14*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x38, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x70, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x38, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"/",15*/ { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0x07, 0x0E, 0x18, 0x10, 0x10, 0x18, 0x0E, 0x07, 0x01, 0x00 }, /*"0",16*/ { 0x00, 0x00, 0x80, 0x80, 0x80, 0xC0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00 }, /*"1",17*/ { 0x00, 0x80, 0x40, 0x20, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x80, 0x40, 0x20, 0x38, 0x1F, 0x07, 0x00, 0x00, 0x00, 0x1C, 0x1A, 0x19, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00 }, /*"2",18*/ { 0x00, 0x80, 0xC0, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x10, 0x10, 0x18, 0x2F, 0xE7, 0x80, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, 0x00, 0x00 }, /*"3",19*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xB0, 0x88, 0x86, 0x81, 0x80, 0xFF, 0xFF, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x00 }, /*"4",20*/ { 0x00, 0x00, 0xE0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x10, 0x08, 0x08, 0x08, 0x18, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x0B, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x0F, 0x03, 0x00, 0x00 }, /*"5",21*/ { 0x00, 0x00, 0x80, 0xC0, 0x40, 0x20, 0x20, 0x20, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x21, 0x10, 0x08, 0x08, 0x08, 0x18, 0xF0, 0xE0, 0x00, 0x00, 0x01, 0x07, 0x0C, 0x18, 0x10, 0x10, 0x10, 0x08, 0x0F, 0x03, 0x00 }, /*"6",22*/ { 0x00, 0x00, 0xC0, 0xE0, 0x60, 0x60, 0x60, 0x60, 0x60, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xE0, 0x18, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"7",23*/ { 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x87, 0xEF, 0x2C, 0x18, 0x18, 0x30, 0x30, 0x68, 0xCF, 0x83, 0x00, 0x00, 0x07, 0x0F, 0x08, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, 0x00 }, /*"8",24*/ { 0x00, 0x00, 0xC0, 0xC0, 0x20, 0x20, 0x20, 0x20, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x60, 0x40, 0x40, 0x40, 0x20, 0x10, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0C, 0x1C, 0x10, 0x10, 0x10, 0x08, 0x0F, 0x03, 0x00, 0x00 }, /*"9",25*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0E, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00 }, /*":",26*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*";",27*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x00, 0x00, 0x00, 0x10, 0x28, 0x44, 0x82, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00 }, /*"<",28*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"=",29*/ { 0x00, 0x00, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x82, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*">",30*/ { 0x00, 0xC0, 0x20, 0x20, 0x10, 0x10, 0x10, 0x10, 0x30, 0xE0, 0xC0, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0xF0, 0x10, 0x08, 0x0C, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"?",31*/ { 0x00, 0x00, 0x00, 0xC0, 0x40, 0x60, 0x20, 0x20, 0x20, 0x40, 0xC0, 0x00, 0x00, 0xFC, 0xFF, 0x01, 0xF0, 0x0E, 0x03, 0xC1, 0xFE, 0x03, 0x80, 0x7F, 0x00, 0x01, 0x07, 0x0E, 0x08, 0x11, 0x11, 0x10, 0x11, 0x09, 0x04, 0x02 }, /*"@",32*/ { 0x00, 0x00, 0x00, 0x00, 0x80, 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7C, 0x43, 0x40, 0x47, 0x7F, 0xF8, 0x80, 0x00, 0x00, 0x10, 0x18, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x13, 0x1F, 0x1C, 0x10 }, /*"A",33*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x18, 0x2F, 0xE7, 0x80, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, 0x00 }, /*"B",34*/ { 0x00, 0x00, 0x80, 0xC0, 0x40, 0x20, 0x20, 0x20, 0x20, 0x60, 0xE0, 0x00, 0x00, 0xFC, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x07, 0x0E, 0x18, 0x10, 0x10, 0x10, 0x08, 0x04, 0x03, 0x00 }, /*"C",35*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x40, 0xC0, 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x18, 0x08, 0x0E, 0x07, 0x01, 0x00 }, /*"D",36*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x06, 0x00 }, /*"E",37*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x60, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, 0x01, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"F",38*/ { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x20, 0x40, 0xE0, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x01, 0x00, 0x00, 0x40, 0x40, 0xC0, 0xC1, 0x40, 0x40, 0x00, 0x01, 0x07, 0x0E, 0x18, 0x10, 0x10, 0x10, 0x0F, 0x0F, 0x00, 0x00 }, /*"G",39*/ { 0x20, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE0, 0xE0, 0x20, 0x00, 0xFF, 0xFF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0xFF, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10 }, /*"H",40*/ { 0x00, 0x00, 0x20, 0x20, 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00 }, /*"I",41*/ { 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x60, 0xE0, 0x80, 0x80, 0x80, 0xC0, 0x7F, 0x3F, 0x00, 0x00, 0x00 }, /*"J",42*/ { 0x20, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x20, 0xA0, 0x60, 0x20, 0x20, 0x00, 0x00, 0xFF, 0xFF, 0x30, 0x18, 0x7C, 0xE3, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x01, 0x13, 0x1F, 0x1C, 0x18, 0x10 }, /*"K",43*/ { 0x20, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x06, 0x00 }, /*"L",44*/ { 0x20, 0xE0, 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, 0xE0, 0x20, 0x00, 0xFF, 0x01, 0x3F, 0xFE, 0xC0, 0xE0, 0x1E, 0x01, 0xFF, 0xFF, 0x00, 0x10, 0x1F, 0x10, 0x00, 0x03, 0x1F, 0x03, 0x00, 0x10, 0x1F, 0x1F, 0x10 }, /*"M",45*/ { 0x20, 0xE0, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE0, 0x20, 0x00, 0xFF, 0x00, 0x03, 0x07, 0x1C, 0x78, 0xE0, 0x80, 0x00, 0xFF, 0x00, 0x10, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x1F, 0x00 }, /*"N",46*/ { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0x07, 0x0E, 0x18, 0x10, 0x10, 0x18, 0x0C, 0x07, 0x01, 0x00 }, /*"O",47*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x1F, 0x0F, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"P",48*/ { 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x01, 0x07, 0x0E, 0x11, 0x11, 0x13, 0x3C, 0x7C, 0x67, 0x21, 0x00 }, /*"Q",49*/ { 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0xC0, 0x80, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x10, 0x30, 0xF0, 0xD0, 0x08, 0x0F, 0x07, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x1C, 0x10, 0x10 }, /*"R",50*/ { 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0x20, 0x20, 0x40, 0x40, 0xE0, 0x00, 0x00, 0x07, 0x0F, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x60, 0xE0, 0x81, 0x00, 0x00, 0x1F, 0x0C, 0x08, 0x10, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x07, 0x00 }, /*"S",51*/ { 0x80, 0x60, 0x20, 0x20, 0x20, 0xE0, 0xE0, 0x20, 0x20, 0x20, 0x60, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00 }, /*"T",52*/ { 0x20, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE0, 0x20, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x07, 0x0F, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x07, 0x00 }, /*"U",53*/ { 0x20, 0x60, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x20, 0xE0, 0x60, 0x20, 0x00, 0x00, 0x07, 0x7F, 0xF8, 0x80, 0x00, 0x80, 0x7C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1F, 0x1C, 0x07, 0x00, 0x00, 0x00, 0x00 }, /*"V",54*/ { 0x20, 0xE0, 0xE0, 0x20, 0x00, 0xE0, 0xE0, 0x20, 0x00, 0x20, 0xE0, 0x20, 0x00, 0x07, 0xFF, 0xF8, 0xE0, 0x1F, 0xFF, 0xFC, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1F, 0x03, 0x00, 0x01, 0x1F, 0x03, 0x00, 0x00, 0x00 }, /*"W",55*/ { 0x00, 0x20, 0x60, 0xE0, 0xA0, 0x00, 0x00, 0x20, 0xE0, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8F, 0x7C, 0xF8, 0xC6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x18, 0x1E, 0x13, 0x00, 0x01, 0x17, 0x1F, 0x18, 0x10, 0x00 }, /*"X",56*/ { 0x20, 0x60, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x20, 0xE0, 0x60, 0x20, 0x00, 0x00, 0x01, 0x07, 0x3E, 0xF8, 0xE0, 0x18, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x00, 0x00, 0x00 }, /*"Y",57*/ { 0x00, 0x80, 0x60, 0x20, 0x20, 0x20, 0x20, 0xA0, 0xE0, 0xE0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0x3E, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1C, 0x1F, 0x17, 0x10, 0x10, 0x10, 0x10, 0x18, 0x06, 0x00 }, /*"Z",58*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, /*"[",59*/ { 0x00, 0x00, 0x10, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1C, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x70, 0x80, 0x00 }, /*"\",60*/ { 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x00, 0x00, 0x00, 0x00 }, /*"]",61*/ { 0x00, 0x00, 0x00, 0x10, 0x08, 0x0C, 0x04, 0x0C, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"^",62*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /*"_",63*/ { 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"`",64*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xD8, 0x44, 0x64, 0x24, 0x24, 0xFC, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0x1F, 0x18, 0x10, 0x10, 0x10, 0x08, 0x1F, 0x1F, 0x10, 0x18 }, /*"a",65*/ { 0x00, 0x20, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x08, 0x04, 0x04, 0x0C, 0xF8, 0xF0, 0x00, 0x00, 0x00, 0x1F, 0x0F, 0x18, 0x10, 0x10, 0x10, 0x18, 0x0F, 0x03, 0x00 }, /*"b",66*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF8, 0x18, 0x04, 0x04, 0x04, 0x3C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x0C, 0x10, 0x10, 0x10, 0x10, 0x08, 0x06, 0x00, 0x00 }, /*"c",67*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0xE0, 0xF8, 0x1C, 0x04, 0x04, 0x04, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x18, 0x10, 0x10, 0x10, 0x08, 0x1F, 0x0F, 0x08, 0x00 }, /*"d",68*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF8, 0x48, 0x44, 0x44, 0x44, 0x4C, 0x78, 0x70, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x0C, 0x18, 0x10, 0x10, 0x10, 0x08, 0x04, 0x00 }, /*"e",69*/ { 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x20, 0x20, 0xE0, 0xC0, 0x00, 0x00, 0x04, 0x04, 0x04, 0xFF, 0xFF, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00 }, /*"f",70*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xF8, 0x8C, 0x04, 0x04, 0x8C, 0xF8, 0x74, 0x04, 0x0C, 0x00, 0x70, 0x76, 0xCF, 0x8D, 0x8D, 0x8D, 0x89, 0xC8, 0x78, 0x70, 0x00 }, /*"g",71*/ { 0x00, 0x20, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x08, 0x04, 0x04, 0x04, 0xFC, 0xF8, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00 }, /*"h",72*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0xFC, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00 }, /*"i",73*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0xFC, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0x80, 0x80, 0xC0, 0x7F, 0x3F, 0x00, 0x00, 0x00 }, /*"j",74*/ { 0x00, 0x20, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0xC0, 0xF4, 0x1C, 0x04, 0x04, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x11, 0x00, 0x03, 0x1F, 0x1C, 0x10, 0x10, 0x00 }, /*"k",75*/ { 0x00, 0x00, 0x20, 0x20, 0x20, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00 }, /*"l",76*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xFC, 0x08, 0x04, 0xFC, 0xFC, 0x08, 0x04, 0xFC, 0xFC, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x1F, 0x1F, 0x10, 0x00, 0x1F, 0x1F, 0x10 }, /*"m",77*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xFC, 0x08, 0x08, 0x04, 0x04, 0xFC, 0xF8, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00, 0x00, 0x10, 0x1F, 0x1F, 0x10, 0x00 }, /*"n",78*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x18, 0x0C, 0x04, 0x04, 0x0C, 0x18, 0xF0, 0xE0, 0x00, 0x00, 0x03, 0x0F, 0x0C, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x0F, 0x03, 0x00 }, /*"o",79*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xFC, 0x08, 0x04, 0x04, 0x04, 0x0C, 0xF8, 0xF0, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x88, 0x90, 0x10, 0x10, 0x1C, 0x0F, 0x03, 0x00 }, /*"p",80*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF8, 0x1C, 0x04, 0x04, 0x04, 0x08, 0xF8, 0xFC, 0x00, 0x00, 0x00, 0x03, 0x0F, 0x18, 0x10, 0x10, 0x90, 0x88, 0xFF, 0xFF, 0x80, 0x00 }, /*"q",81*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0xFC, 0xFC, 0x10, 0x08, 0x04, 0x04, 0x0C, 0x0C, 0x00, 0x10, 0x10, 0x10, 0x1F, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00 }, /*"r",82*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x78, 0xCC, 0xC4, 0x84, 0x84, 0x84, 0x0C, 0x1C, 0x00, 0x00, 0x00, 0x1E, 0x18, 0x10, 0x10, 0x10, 0x11, 0x19, 0x0F, 0x06, 0x00 }, /*"s",83*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0xFF, 0xFF, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1F, 0x10, 0x10, 0x10, 0x0C, 0x00, 0x00 }, /*"t",84*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xFE, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1F, 0x18, 0x10, 0x10, 0x08, 0x1F, 0x0F, 0x08, 0x00 }, /*"u",85*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x3C, 0xFC, 0xC4, 0x00, 0x00, 0xC4, 0x3C, 0x0C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0F, 0x1E, 0x0E, 0x01, 0x00, 0x00, 0x00 }, /*"v",86*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3C, 0xFC, 0xC4, 0x00, 0xE4, 0x7C, 0xFC, 0x84, 0x80, 0x7C, 0x04, 0x00, 0x00, 0x07, 0x1F, 0x07, 0x00, 0x00, 0x07, 0x1F, 0x07, 0x00, 0x00 }, /*"w",87*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x1C, 0x7C, 0xE4, 0xC0, 0x34, 0x1C, 0x04, 0x04, 0x00, 0x00, 0x10, 0x10, 0x1C, 0x16, 0x01, 0x13, 0x1F, 0x1C, 0x18, 0x10, 0x00 }, /*"x",88*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x3C, 0xFC, 0xC4, 0x00, 0xC4, 0x3C, 0x04, 0x04, 0x00, 0x00, 0x00, 0xC0, 0x80, 0xC1, 0x37, 0x0E, 0x01, 0x00, 0x00, 0x00, 0x00 }, /*"y",89*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x04, 0x04, 0xC4, 0xF4, 0x7C, 0x1C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1C, 0x1F, 0x17, 0x11, 0x10, 0x10, 0x18, 0x0E, 0x00 }, /*"z",90*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x0C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x60, 0x40, 0x00, 0x00 }, /*"{",91*/ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"|",92*/ { 0x00, 0x00, 0x04, 0x0C, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x60, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /*"}",93*/ { 0x00, 0x18, 0x06, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, #endif
-
【R128】应用开发案例——ADC驱动烟雾传感器
ADC驱动烟雾传感器
本文案例代码 下载地址 ADC驱动烟雾传感器案例代码 https://www.aw-ol.com/downloads?cat=24 原理介绍
MQ-2型烟雾传感器属于二氧化锡半导体气敏材料,属于表面离子式N型半 导体。当处于200、300℃温度时,二氧化锡吸附空气中的氧,形成氧的负离子吸附,使半导体中的电子密度减少,从而使其电阻值增加。当与烟雾接触时,如果晶粒间界处的势垒受到该烟雾的调制而变化,就会引起表而电导率的变化。利用这一点就可以获得这种烟雾存在的信息,烟雾浓度越大,电导率越大输出电阻越低。MQ2气体传感器可以很灵敏的检测到空气中的烟雾、液化气、丁烷、丙烷、甲烷、酒精、氢气等气体。
首先我们搭建电路,如下:
引脚 按键 PB0 MQ2 AO脚 GND MQ2 GND 3V3 MQ2 VCC 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
勾选 GPADC 驱动
mrtos_menuconfig
找到下列驱动Drivers Options ---> soc related device drivers ---> GPADC devices ---> [*] enable gpadc driver [*] enable power protect driver
编写程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
引入头文件
#include <sunxi_hal_gpadc.h>
由于MQ2是一个加热器驱动的传感器,如果长时间存放,传感器的校准可能会漂移。长时间存放后首次使用时,传感器必须充分预热24-48小时以确保最大精度。
如果最近使用过传感器,则只需5-10分钟即可完全预热。 在预热期间,传感器读数通常很高,然后逐渐降低直到稳定。
为了实现预热功能,我们先实现一个
sleep
函数,等待预热完成再读取 ADC 电压值。static inline int msleep(int ms) { vTaskDelay(ms / portTICK_RATE_MS); }
ADC 功能配置
GPADC 初始化接口
GPADC 模块初始化,主要初始化时钟,中断以及采样率配置等,这里我们初始化并检查即可。
int hal_gpadc_init(void)
GPADC 通道初始化
选择并配置 GPADC 某个通道,这里配置初始化0通道。
hal_gpadc_channel_init(0);
GPADC 读取电压接口
读取0通道的ADC电压数据。
ret = gpadc_read_channel_data(0)
完整代码如下
// 预热模块 msleep(20000); // 初始化 GPADC if(hal_gpadc_init() != 0){ printf("ADC Init failed!\n"); } // 初始化通道 hal_gpadc_channel_init(0); // 读取电压 while(1){ uint32_t vol_data = gpadc_read_channel_data(0); printf("channel 0 vol data is %u\n", vol_data); } // 释放通道,这里没有用到 hal_gpadc_channel_exit(0); // 释放GPADC,这里没有用到 hal_gpadc_deinit();
结果
烧录后可以看到输出的电压值。
-
回复: R128蓝牙占用内存资源较大,修改menuconfig配置后经常编译不过或无法运行
CONFIG_BT_SMP 配置问题,下面内容保存为smp_null.c 放到
./src/ble/host/smp_null.c
/** * @file smp_null.c * Security Manager Protocol stub */ /* * Copyright (c) 2015-2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include <zephyr.h> #include <errno.h> #include <ble/sys/atomic.h> #include <ble/sys/util.h> #include <bluetooth/bluetooth.h> #include <bluetooth/conn.h> #include <bluetooth/buf.h> #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_CORE) #define LOG_MODULE_NAME bt_smp #include "common/log.h" #include "hci_core.h" #include "conn_internal.h" #include "l2cap_internal.h" #include "smp.h" static struct bt_l2cap_le_chan bt_smp_pool[CONFIG_BT_MAX_CONN]; int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf) { return -ENOTSUP; } int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf) { return -ENOTSUP; } static int bt_smp_recv(struct bt_l2cap_chan *chan, struct net_buf *req_buf) { struct bt_conn *conn = chan->conn; struct bt_smp_pairing_fail *rsp; struct bt_smp_hdr *hdr; struct net_buf *buf; ARG_UNUSED(req_buf); /* If a device does not support pairing then it shall respond with * a Pairing Failed command with the reason set to "Pairing Not * Supported" when any command is received. * Core Specification Vol. 3, Part H, 3.3 */ buf = bt_l2cap_create_pdu(NULL, 0); /* NULL is not a possible return due to K_FOREVER */ hdr = net_buf_add(buf, sizeof(*hdr)); hdr->code = BT_SMP_CMD_PAIRING_FAIL; rsp = net_buf_add(buf, sizeof(*rsp)); rsp->reason = BT_SMP_ERR_PAIRING_NOTSUPP; if (bt_l2cap_send_cb(conn, BT_L2CAP_CID_SMP, buf, NULL, NULL)) { net_buf_unref(buf); } return 0; } static int bt_smp_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) { int i; static const struct bt_l2cap_chan_ops ops = { .recv = bt_smp_recv, }; BT_DBG("conn %p handle %u", conn, conn->handle); for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) { struct bt_l2cap_le_chan *smp = &bt_smp_pool[i]; if (smp->chan.conn) { continue; } smp->chan.ops = &ops; *chan = &smp->chan; return 0; } BT_ERR("No available SMP context for conn %p", conn); return -ENOMEM; } BT_L2CAP_CHANNEL_DEFINE(smp_fixed_chan, BT_L2CAP_CID_SMP, bt_smp_accept, NULL); int bt_smp_init(void) { return 0; } #if defined(CONFIG_BT_DEINIT) int bt_smp_deinit(void) { return 0; } void bt_smp_mem_deinit(void) { return; } void bt_keys_mem_deinit(void) { return; } #endif #if defined(CONFIG_BT_VAR_MEM_DYNC_ALLOC) void keys_dynamic_mem_alloc(void) { } void keys_dynamic_mem_free(void) { } #endif
-
【R128】软件配置——配置引脚复用
配置引脚复用
本文中的约定
- 描述 GPIO 配置的形式:
Port:端口 + 组内序号<功能分配><内部电阻状态><驱动能力><输出电平状态>
- 文中的
<X>=0,1,2,3,4,5…..
,如 twi0,twi1….;uart0,uart1….。 - 文中的
{PROJECT}
代表不同的方案,例如module
方案。
引脚配置文件配置复用
R128 平台使用
sys_config.fex
作为引脚配置文件,他会在打包时打包编译进入系统,在系统运行时会解析并配置,系统解析sys_config.fex
的驱动配置位于lichee\rtos-components\aw\sys_config_script
中。对于配置引脚复用,只需要修改
board\r128s2\{PROJECT}\config\sys_config.fex
文件即可。GPIO描述格式
Port:端口 + 组内序号<功能分配><内部电阻状态><驱动能力><输出电平状态>
示例
uart_tx = port:PA16<5><1><2><0> |----------------------> 端口号 PA |--------------------> 序号 19 |------------------> 功能分配 5 (参考 PINMUX) |---------------> 内部电阻状态为 1 |------------> 驱动能力为 2 |---------> 默认输出电平 0
[product]
配置文件信息
配置项 配置项含义 version 配置的版本号 machine 方案名字 示例
[product] version = "100" machine = "module"
[target]
启动介质配置
配置项 配置项含义 storage_type 启动介质选择 <br />0:nand <br />1:sd<br />2:emmc<br />3:spinor <br />4:emmc <br />5:spinand <br />6:sd <br />-1:(defualt) 自动扫描启动介质 示例
[target] storage_type = 3
对于内置 SPI NOR 的 R128 平台,请配置为
3
[platform]
配置 boot0 调试信息打印
配置项 配置项含义 debug_mode 配置0时,boot0 不打印调试信息,配置不为0时打印 示例
[platform] debug_mode = 2
[uart_para]
boot0 调试串口配置
配置项 配置项含义 uart_debug_port boot0 调试输出串口使用的串口号 uart_debug_tx boot0 调试串口 tx 使用的引脚 uart_debug_rx boot0 调试串口 rx 使用的引脚 示例
[uart_para] uart_debug_port = 0 uart_debug_tx = port:PA16<5><1><default><default> uart_debug_rx = port:PA17<5><1><default><default>
[uartX]
UART 引脚配置
配置项 配置项含义 uart_tx UART TX 的 GPIO 配置 uart_rx UART RX 的 GPIO 配置 uart_type UART 类型,有效值为:2/4/8; 表示 2/4/8 线模式 示例
[uart0] uart_tx = port:PA16<5><1><default><default> uart_rx = port:PA17<5><1><default><default>
[twiX]
TWI 引脚配置
配置项 配置项含义 twiX_sck TWI 的时钟的 GPIO 配置 twiX_sda TWI 的数据的 GPIO 配置 [sdcX]
SDIO,MMC 引脚配置
配置项 配置项含义 card_ctrl 控制器 card_high_speed 速度模式 0 为低速,1 为高速 card_line 1,4 线卡可以选择 sdc_d1 sdc 卡数据 1 线信号的 GPIO 配置 sdc_d0 sdc 卡数据 2 线信号的 GPIO 配置 sdc_clk sdc 卡时钟信号的 GPIO 配置 sdc_cmd sdc 命令信号的 GPIO 配置 sdc_d3 sdc 卡数据 3 线信号的 GPIO 配置 sdc_d2 sdc 卡数据 4 线信号的 GPIO 配置 示例
[sdc0] card_ctrl = 0 card_high_speed = 0 card_line = 4 sdc_d1 = port:PA27<2><1><3><default> sdc_d0 = port:PA26<2><1><3><default> sdc_clk = port:PA29<2><1><3><default> sdc_cmd = port:PA25<2><1><3><default> sdc_d3 = port:PA24<2><1><3><default> sdc_d2 = port:PA28<2><1><3><default>
[sdcXdet_para]
卡检测引脚配置
配置项 配置项含义 sdcX_det 卡插入检测脚 示例
[sdc0det_para] sdc0_det = port:PA23<0><1><3><default>
[usbX]
USB 配置
配置项 配置项含义 usb_used USB使能标志。置1,表示系统中USB模块可用,置0,则表示系统USB禁用。 usb_port_type USB端口的使用情况。 0: device only;1: host only;2: OTG usb_detect_type USB端口的检查方式。0: 不做检测;1: vbus/id检查;2: id/dpdm检查 usb_detect_mode USB端口的检查方式。0: 线程轮询;1: id中断触发 usb_id_gpio USB ID pin脚配置 usb_det_vbus_gpio USB DET_VBUS pin脚配置 usb_drv_vbus_type vbus设置方式。0: 无; 1: gpio; 2: axp。 usb_det_vbus_gpio "axp_ctrl",表示 axp 提供。 usbh_driver_level usb驱动能力等级 usbh_irq_flag usb中断标志 示例
[usbc0] usb_used = 1 usb_port_type = 2 usb_detect_type = 1 usb_detect_mode = 0 usb_id_gpio = port:PB04<0><0><default><default> usb_det_vbus_gpio = port:PA24<0><0><default><default> usb_drv_vbus_gpio = port:PA29<0><0><default><default> usb_drv_vbus_type = 1 usbh_driver_level = 5 usbh_irq_flag = 0
[audiocodec]
内置 audiocodec 配置
配置项 配置项含义 dacl_vol DAC L 音量 dacr_vol DAC R 音量 lineout_vol LINEOUT 音量 lineoutl_en LINEOUTL 使能 lineoutr_en LINEOUTR 使能 mic1_gain MIC1 增益 mic2_gain MIC2 增益 mic3_gain MIC3 增益 mic1_en MIC1 启用 mic2_en MIC2 启用 mic3_en MIC3 启用 mad_bind_en MAD 模块绑定 pa_pin_msleep 功放使能引脚延时 pa_pin 功放使能引脚 示例
[audiocodec] dacl_vol = 129 dacr_vol = 129 lineout_vol = 5 lineoutl_en = 1 lineoutr_en = 0 mic1_gain = 19 mic2_gain = 19 mic3_gain = 0 mic1_en = 1 mic2_en = 1 mic3_en = 1 mad_bind_en = 0 pa_pin_msleep = 10 pa_pin = port:PB3<1><default><1><1>
[daudio0]
数字音频配置
配置项 配置项含义 i2s_mclk I2S MCLK 引脚配置 i2s_bclk I2S BCLK 引脚配置 i2s_lrck I2S LRCK 引脚配置 i2s_dout0 I2S DOUT0 引脚配置 i2s_din0 I2S DIN0 引脚配置 示例
[daudio0] i2s_mclk = port:PA23<2><0><1><default> i2s_bclk = port:PA20<2><0><1><default> i2s_lrck = port:PA19<2><0><1><default> i2s_dout0 = port:PA22<2><0><1><default> i2s_din0 = port:PA21<2><0><1><default>
[dram_para]
此项配置仅为兼容配置,实际有用配置项为
dram_clk
,dram_no_lpsram
,其余dram_para
参数没有实际意义。配置项 配置项含义 dram_clk 如果不为 0,初始化 hspsram dram_no_lpsram 如果为 0,初始化 lspsram [dram_para] dram_clk = 800 dram_type = 0xB dram_zq = 0x3dbdfda0 dram_odt_en = 0x1 dram_para1 = 0x000010f2 dram_para2 = 0x02000000 dram_mr0 = 0x1c70 dram_mr1 = 0x42 dram_mr2 = 0x8 dram_mr3 = 0x0 dram_tpr0 = 0x004A2195 dram_tpr1 = 0x02423190 dram_tpr2 = 0x0008B061 dram_tpr3 = 0xB4787896 dram_tpr4 = 0x0 dram_tpr5 = 0x48484848 dram_tpr6 = 0x48 dram_tpr7 = 0x1 dram_tpr8 = 0x0 dram_tpr9 = 0x00 dram_tpr10 = 0x0 dram_tpr11 = 0x00000000 dram_tpr12 = 0x00000000 dram_tpr13 = 0x34050f00 dram_no_lpsram = 0x0
[lcd_fb0]
SPI LCD 配置
SPI LCD 配置项目较多,部分详细描述可以参照 显示框架
配置项 配置项含义 lcd_used 启用 LCD lcd_model_name lcd 屏模型名字,非必须,可以用于同个屏驱动中进一步区分不同屏。 lcd_driver_name lcd面板驱动名称,必须与屏驱动中 strcut __lcd_panel
变量的name
成员一致。lcd_x lcd X像素 lcd_y lcd Y像素 lcd_width lcd 物理宽度(单位mm) lcd_height lcd 物理高度(单位mm) lcd_data_speed lcd 数据速率 lcd_pwm_used lcd 背光使用 pwm lcd_pwm_ch lcd 背光使用的 pwm 通道 lcd_pwm_freq lcd 背光使用的频率 lcd_pwm_pol lcd 背光使用的相位 lcd_if 0:SPI接口(spi 接口就是俗称的 4 线模式,这是因为发送数据时需要额外借助 DC 线来区分命令和数据,与sclk,cs 和 sda 共四线)<br />1:DBI接口(如果设置了 dbi 接口,那么还需要进一步区分 dbi 接口,需要设置lcd_dbi_if) lcd_pixel_fmt 选择传输数据的像素格式 lcd_dbi_fmt 0:RGB111<br />1:RGB444<br />2:RGB565<br />3:RGB666<br />4:RGB888 lcd_dbi_clk_mode 选择 dbi 时钟的行为模式 lcd_dbi_te 使能 te 触发 fb_buffer_num 显示 framebuffer 数量,为了平滑显示,这里一般是 2 个,为了省内存也可以改成 1。 lcd_dbi_if 0:L3I1<br />1:L3I2<br />2:L4I1<br />3:L4I2<br />4:D2L1 lcd_rgb_order 输入图像数据 rgb 顺序识别设置 lcd_fps 设置屏的刷新率,单位 Hz lcd_spi_bus_num 选择 spi 总线 id lcd_frm frm抖动控制 lcd_gamma_en gamma控制使能 lcd_backlight 背光 lcd_gpio_0 用户定义IO定义,一般作为RST - 描述 GPIO 配置的形式:
-
【R128】外设模块配置——PMU 电源管理
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
PMU 电源管理
PMU 功能简介
目前已支持的PMU 为:AXP2585。
该PMU 主要用于电池管理以及充电管理,主要有以下功能:
-
读取电池电量、电池温度。
-
设置充电时的充电电流,截止充电电压、充电超时等。
-
自动根据连接PC 或者适配器设置USB 输入的最大限流。
-
电池温度过高时自动触发停充。
-
检测USB 线的接入和拔出。
-
PMU 芯片过温保护。
PMU 配置介绍
sys_config.fex
配置说明[pmu] pmu_irq_pin = port:PA14<14><0><default><default> pmu_irq_wakeup = 2 pmu_hot_shutdown = 1 pmu_bat_unused = 0 pmu_usbad_vol = 4600 pmu_usbad_cur = 1500 pmu_usbpc_vol = 4600 pmu_usbpc_cur = 500 pmu_chg_ic_temp = 0 pmu_battery_rdc = 100 pmu_battery_cap = 3568 pmu_runtime_chgcur = 900 pmu_suspend_chgcur = 1200 pmu_shutdown_chgcur = 1200 pmu_init_chgvol = 4200 pmu_init_chg_pretime = 50 pmu_init_chg_csttime = 1200 pmu_chgled_type = 0 pmu_init_bc_en = 1 pmu_bat_temp_enable = 0 pmu_bat_charge_ltf = 2261 pmu_bat_charge_htf = 388 pmu_bat_shutdown_ltf = 3200 pmu_bat_shutdown_htf = 237 pmu_bat_para[0] = 0 pmu_bat_para[1] = 0 pmu_bat_para[2] = 0 pmu_bat_para[3] = 0 pmu_bat_para[4] = 0 pmu_bat_para[5] = 0 pmu_bat_para[6] = 1 pmu_bat_para[7] = 1 pmu_bat_para[8] = 2 pmu_bat_para[9] = 4 pmu_bat_para[10] = 5 pmu_bat_para[11] = 12 pmu_bat_para[12] = 19 pmu_bat_para[13] = 32 pmu_bat_para[14] = 41 pmu_bat_para[15] = 45 pmu_bat_para[16] = 48 pmu_bat_para[17] = 51 pmu_bat_para[18] = 54 pmu_bat_para[19] = 59 pmu_bat_para[20] = 63 pmu_bat_para[21] = 68 pmu_bat_para[22] = 71 pmu_bat_para[23] = 74 pmu_bat_para[24] = 78 pmu_bat_para[25] = 81 pmu_bat_para[26] = 82 pmu_bat_para[27] = 84 pmu_bat_para[28] = 88 pmu_bat_para[29] = 92 pmu_bat_para[30] = 96 pmu_bat_para[31] = 100 pmu_bat_temp_para[0] = 7466 pmu_bat_temp_para[1] = 4480 pmu_bat_temp_para[2] = 3518 pmu_bat_temp_para[3] = 2786 pmu_bat_temp_para[4] = 2223 pmu_bat_temp_para[5] = 1788 pmu_bat_temp_para[6] = 1448 pmu_bat_temp_para[7] = 969 pmu_bat_temp_para[8] = 664 pmu_bat_temp_para[9] = 466 pmu_bat_temp_para[10] = 393 pmu_bat_temp_para[11] = 333 pmu_bat_temp_para[12] = 283 pmu_bat_temp_para[13] = 242 pmu_bat_temp_para[14] = 179 pmu_bat_temp_para[15] = 134
配置含义:
pmu_irq_pin AXP芯片IRQ引脚连接的IO,用于触发中断 pmu_irq_wakeup Press irq wakeup or not when sleep or power down. 0: not wakeup 1: wakeup pmu_hot_shutdown when PMU over temperature protect or not. 0: disable 1: enable pmu_bat_unused unused bat 0: disable 1: enable pmu_usbpc_vol <u32> usb pc输入电压限制值,单位为mV pmu_usbpc_cur <u32> usb pc输入电流限制值,单位为mA pmu_usbad_vol <u32> usb adaptor输入电压限制值(vimdpm),单位为mV pmu_usbad_cur <u32> usb adaptor输入电流限制值,单位为mA pmu_chg_ic_temp <u32> 1: TS current source always on 0: TS current source off pmu_battery_rdc <u32> 电池内阻,单位为mΩ pmu_battery_cap <u32> 电池容量,单位为mAh pmu_runtime_chgcur <u32> 运行时constant充电电流限制,单位为mA pmu_suspend_chgcur <u32> 休眠时constant充电电流限制,单位为mA pmu_shutdown_chgcur <u32> 关机时constant充电电流限制,单位为mA pmu_terminal_chgcur <u32> 截止电流,停止充电的标志位之一,单位为mA pmu_init_chgvol <u32> 电池满充电压,单位为mV pmu_init_chg_pretime <u32> 当电池电压低于REG 0x8C[1]时,属于pre charge阶段。 如果此阶段时间超过pmu_init_chg_pretime,视为超时,停止充电。 pmu_init_chg_csttime <u32> 当电池电压高于REG 0x8C[1]且低于截止电压(REG 0X8C[7:2])时,属于恒流充电阶段。 如果此阶段时间超过pmu_init_chg_csttime,视为超时,停止充电。 pmu_chgled_type <bool> 0: Enable CHGLED pin funciton 1: Disable CHGLED pin funciton pmu_init_bc_en <bool> 0: Enable BC1.2 1: Disable BC1.2 pmu_bat_temp_enable <u32> 设置电池温度检测、ntc是否使能 pmu_bat_charge_ltf <u32> 触发电池低温停充的TS pin电压阈值,单位:mV 默认:1105mV 范围:0‑8160mV pmu_bat_charge_htf <u32> 触发电池高温停充的TS pin电压阈值,单位:mV 默认:121mV 范围:0‑510mV pmu_bat_shutdown_ltf <u32> 非充电模式下,触发电池低温中断的TS pin电压阈值,单位:mV 默认:1381mV pmu_bat_shutdown_htf <u32> 默认:89mV 范围:0‑510mV pmu_bat_para1 <u32> pmu_bat_para2 <u32> ... pmu_bat_para32 <u32> 电池曲线参数 电池参数根据使用的电池不同,通过仪器测量出来 pmu_bat_temp_para1 <u32> 电池包‑25度对应的TS pin电压,单位:mV pmu_bat_temp_para2 <u32> 电池包‑15度对应的TS pin电压,单位:mV pmu_bat_temp_para3 <u32> 电池包‑10度对应的TS pin电压,单位:mV pmu_bat_temp_para4 <u32> 电池包‑5度对应的TS pin电压,单位:mV pmu_bat_temp_para5 <u32> 电池包0度对应的TS pin电压,单位:mV pmu_bat_temp_para6 <u32> 电池包5度对应的TS pin电压,单位:mV pmu_bat_temp_para7 <u32> 电池包10度对应的TS pin电压,单位:mV pmu_bat_temp_para8 <u32> 电池包20度对应的TS pin电压,单位:mV pmu_bat_temp_para9 <u32> 电池包30度对应的TS pin电压,单位:mV pmu_bat_temp_para10 <u32> 电池包40度对应的TS pin电压,单位:mV pmu_bat_temp_para11 <u32> 电池包45度对应的TS pin电压,单位:mV pmu_bat_temp_para12 <u32> 电池包50度对应的TS pin电压,单位:mV pmu_bat_temp_para13 <u32> 电池包55度对应的TS pin电压,单位:mV pmu_bat_temp_para14 <u32> 电池包60度对应的TS pin电压,单位:mV pmu_bat_temp_para15 <u32> 电池包70度对应的TS pin电压,单位:mV pmu_bat_temp_para16 <u32> 电池包80度对应的TS pin电压,单位:mV 不同电池包的温敏电阻特性不一样,根据电池包的TS温敏电阻手册,找到pmu_bat_temp_para[1‑16]对应温度点的电阻阻值,将阻值除以20得到的电压数值(单位:mV),将电压数值填进pmu_bat_temp_para[1‑16]的节点中即可
rtos menuconfig 配置说明
AXP 是依赖于I2C 进行通过的,所以首先就需要确认I2C 驱动是已经被选上的。
- 使能I2C 驱动
‑> Drivers Options ‑> soc related device drivers ‑> TWI Devices [*] enable twi driver
- 使能PMU 驱动
‑> Drivers Options ‑> soc related device drivers [*] POWER Devices
- 选择AXP2585
‑> Drivers Options ‑> soc related device drivers ‑> POWER Devices [*] enable power driver
PMU 源码结构
lichee/rtos‑hal/hal/source/power/ ├── axp2585.c ├── axp2585.h ├── axp.c ├── axp_twi.c ├── ffs.h ├── Kconfig ├── Makefile ├── sun20iw2p1 │ ├── core.c └── type.h
- axp2585.c: AXP2585 驱动。
- axp.c: AXP 框架API 接口。
- axp_twi.c: 初始化以及I2C 接口。
- sun20iw2p1: R128 配置以及总初始化接口。
PMU 常用功能
驱动初始化
若
mrtos_menuconfig
中已经选上了该设备,并且sys_config.fex
中也配置完成,那么系统加载时就已经自动将 PMU 驱动加载完成,无需软件工程师再进行初始化。初始化成功的 log 可如下所示:
axp2585 chip version C ! axp2585 chip id detect 0x49 ! current limit not set: usb adapter type axp2585 init finished !
若是没有打印上述的打印 log 信息,可能是 PMU 驱动加载失败了,可以从
sys_config.fex
配置中确认是否有配置漏配置了,或者是从 I2C 方向去排查,确认I2C 通信是正常的。AXP 接口使用
PMU 驱动有一个统一的驱动入口,初始化和一些功能接口,都是由AXP 驱动统一管理的。具体请参照 PMU章节的说明。
电源管理应用healthd
healthd 是一个电源管理的应用,主要功能为:检测电池电量、设置充电电流、电量变低警报、电压过低关机、电池温度过高过度保护等等。
应用配置方法:
‑> System components ‑> aw components [*] healthd for axp
应用源码路径为:
lichee/rtos/components/aw/healthd/healthd.c
healthd 用法
开启应用
应用在默认SDK 中并不会启动,在系统启动之后,需要手动输入:
healthd
然后就开启了电池管理应用了。开启了之后,就会启动了电量变低警报、电压过低关机、电池温度过高过度保护的功能。
获取电池电量
运行命令:
healthd_get_capacity
设置充电电流
运行命令:
healthd_set_chgcur 1500
命令的后缀为充电电流大小,单位为mA,范围为0~3072mA。
-
【R128】基础组件开发指南——音频框架②
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
模块使用
一个声卡的简单测试,包含两部分,分别为声卡的控件设置及音频测试工具的使用。本章节将从以下 4 个通用小节和 1 个外挂 codec 小节介绍声卡如何使用。
- menuconfig 配置
- 声卡设备查看
- 声卡控件
- 声卡测试工具使用
- I2S 外挂 CODEC
menuconfig 配置
进入menuconfig 界面:
# 进入RTOS 目录执行以下命令 mrtos_menuconfig
Drivers Options ‑‑‑> soc related device drivers ‑‑‑> SOUND Devices ‑‑‑> [*] Sound card support AW Sound Compenents ‑‑‑> Allwinner alsa library (tiny alsa library) ‑‑‑>
还提供 aw‑tiny‑alsa‑lib, 包含了 alsa‑lib 主要的接口,没有任何插件,作用类似于 tinyalsa。aw‑tiny‑alsa‑lib 配置方法:
Drivers Options ‑‑‑> soc related device drivers ‑‑‑> SOUND Devices ‑‑‑> [*] Sound card support AW Sound Compenents ‑‑‑> Allwinner alsa library (tiny alsa library) ‑‑‑>
音频工具 aw‑alsa‑utils 相关配置项:
Drivers Options ‑‑‑> soc related device drivers ‑‑‑> SOUND Devices ‑‑‑> [*] Sound card support AW Sound Compenents ‑‑‑> [*] alsa library utils select [*] alsa library utils amixer [*] alsa library utils aplay [*] alsa library utils arecord [ ] alsa library utils aloop
声卡设备查看
可输入以下命令查看系统挂载上的声卡
~# amixer -l ============= Sound Card list ============= card_num card_name 0 audiocodecdac 1 audiocodecadc 2 snddaudio0 3 snddmic 4 sndspdif
?> 可通过在
card_default.c
修改 “card_name” 变量,设定声卡名称。声卡加载配置
声卡配置位于代码中, 路径如下:
在每个方案目录下面:
projects/方案/src/card_default.c
使用
snd_card_register
函数进行声卡的注册int snd_card_register(const char *name, struct snd_codec *codec, int platform_type)
- name: 表示声卡的名字,aw‑alsa‑lib中都需要通过该名字找到对应的声卡
- codec: codec结构体,根据实际使用的codec进行配置,如R128的audiocodec,使用sun20iw2_codec;如ac108,使用 ac108_codec
- platform_type: 与linux中ASOC框架类似,也需要指定使用哪种类型的platform,如CPUDAI, DAUDIO等
例如注册 AC108 声卡
snd_card_register("ac108", &ac108_codec, SND_PLATFORM_TYPE_DAUDIO1);
声卡控件
alsa-utils 工具
alsa-utils 主要提供三个工具:
- aplay: 用于完成与播放相关的操作;
- arecord: 用于完成与录音相关的操作;
- amixer: 用于设置相关参数。
aplay
输入aplay 或aplay -h 可打印出使用方法
~# aplay Usage: aplay [OPTION]... [FILE]... -h, --help help --version print current version -l, --list-devices list all soundcards and digital audio devices -L, --list-pcms list device names -D, --device=NAME select PCM by name -q, --quiet quiet mode -t, --file-type TYPE file type (voc, wav, raw or au) -c, --channels=# channels -f, --format=FORMAT sample format (case insensitive) -r, --rate=# sample rate -d, --duration=# interrupt after # seconds ...
查看可以用于播放的声卡
~# aplay -l **** List of PLAYBACK Hardware Devices **** card 0: audiocodec [audiocodec], device 0: soc@03000000:codec_plat-sunxi-snd-codec sunxisnd-codec-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 2: snddaudio0 [snddaudio0], device 0: 2032000.daudio0_plat-snd-soc-dummy-dai snd-socdummy-dai-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0
用声卡0 设备0 播放test.wav(用 ctrl c 退出)
~# aplay -D hw:0,0 test.wav Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono ^C Aborted by signal Interrupt...
arecord
输入arecord 或arecord -h 可打印出使用方法
~# arecord Usage: arecord [OPTION]... [FILE]... -h, --help help --version print current version -l, --list-devices list all soundcards and digital audio devices -L, --list-pcms list device names -D, --device=NAME select PCM by name -q, --quiet quiet mode -t, --file-type TYPE file type (voc, wav, raw or au) -c, --channels=# channels -f, --format=FORMAT sample format (case insensitive) -r, --rate=# sample rate -d, --duration=# interrupt after # seconds -M, --mmap mmap stream -N, --nonblock nonblocking mode -F, --period-time=# distance between interrupts is # microseconds -B, --buffer-time=# buffer duration is # microseconds
查看可以用于录音的声卡
~# arecord -l **** List of CAPTURE Hardware Devices **** card 0: audiocodec [audiocodec], device 0: soc@03000000:codec_plat-sunxi-snd-codec sunxisnd-codec-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: snddmic [snddmic], device 0: 2031000.dmic_plat-snd-soc-dummy-dai snd-soc-dummy-dai-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 2: snddaudio0 [snddaudio0], device 0: 2032000.daudio0_plat-snd-soc-dummy-dai snd-socdummy-dai-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 ...
用声卡1 的设备0 进行采样位数为16 的录音,并把数据保存在test.wav(用ctrl c 退出)
~# arecord -D hw:1,0 -f S16_LE test.wav Recording WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono ^C Aborted by signal Interrupt...
amixer
输入amixer 或amixer -h 可打印出使用方法
~# amixer -h Usage: amixer <options> [command] Available options: -h,--help this help -c,--card N select the card -D,--device N select the device, default 'default' -d,--debug debug mode -n,--nocheck do not perform range checking -v,--version print version of this program -q,--quiet be quiet -i,--inactive show also inactive controls -a,--abstract L select abstraction level (none or basic) -s,--stdin Read and execute commands from stdin sequentially -R,--raw-volume Use the raw value (default) -M,--mapped-volume Use the mapped volume Available commands: scontrols show all mixer simple controls scontents show contents of all mixer simple controls (default command) sset sID P set contents for one mixer simple control sget sID get contents for one mixer simple control controls show all controls for given card contents show contents of all controls for given card cset cID P set control contents for one control cget cID get control contents for one control
查看声卡 1 的控件
~# amixer -c 1 scontrols Simple mixer control 'L0 volume',0 Simple mixer control 'L1 volume',0 Simple mixer control 'L2 volume',0 Simple mixer control 'L3 volume',0 Simple mixer control 'R0 volume',0 Simple mixer control 'R1 volume',0 Simple mixer control 'R2 volume',0 Simple mixer control 'R3 volume',0 Simple mixer control 'rx sync mode',0
查看声卡 1 的控件的具体配置
~# amixer -c 1 scontents Simple mixer control 'L0 volume',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 255 Mono: 176 [69%] Simple mixer control 'L1 volume',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 255 Mono: 176 [69%] Simple mixer control 'L2 volume',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 255 Mono: 176 [69%] ...
设置声卡 1 第一个控件的值
# 拿到声卡1所有控件 ~# amixer -c 1 controls numid=2,iface=MIXER,name='L0 volume' numid=4,iface=MIXER,name='L1 volume' numid=6,iface=MIXER,name='L2 volume' numid=8,iface=MIXER,name='L3 volume' numid=3,iface=MIXER,name='R0 volume' numid=5,iface=MIXER,name='R1 volume' numid=7,iface=MIXER,name='R2 volume' numid=9,iface=MIXER,name='R3 volume' numid=1,iface=MIXER,name='rx sync mode' # 拿到控件内容 ~# amixer cget numid=2,iface=MIXER,name='L0 volume' numid=2,iface=MIXER,name='rx sync mode' ; type=ENUMERATED,access=rw------,values=1,items=2 ; Item #0 'Off' ; Item #1 'On' : values=0 # 设置控件值 ~# amixer cset numid=2,iface=MIXER,name='L0 volume' 1 numid=2,iface=MIXER,name='rx sync mode' ; type=ENUMERATED,access=rw------,values=1,items=2 ; Item #0 'Off' ; Item #1 'On' : values=1
aw‑alsa‑lib 插件配置
aw‑alsa‑lib 与 linux 上 alsa‑lib 一样支持多种插件, 插件的选择、使用, 需要在代码中进行配置, 路径如下:
在每个方案目录下面: projects/方案/src/alsa_config.c
下面根据插件类型进行简单说明
hw 插件
static const snd_pcm_hw_config_t snd_pcm_hw_config = { .card_name = "audiocodec", .device_num = 0, };
- card_name: 声卡名称,需要与card_default.c中注册声卡时指定的名称一致
- device_num: pcm设备序号,目前仅支持一个pcm设备,所以只能为0
dmix 插件
static const snd_pcm_dmix_config_t snd_pcm_dmix_config = { .type = "dmix", .ipc_key = 2222, .slave = { .pcm = "hw:audiocodec", .format = SND_PCM_FORMAT_S16_LE, .rate = 48000, .channels = 1, .period_size = 1024, .periods = 4, }, };
- type: 插件类型dmix
- ipc_key: 需要进行混音的均要指定同一ipc_key
- slave: pcm从设备的信息
- pcm: pcm设备的名称,必须为hw类型
- format: 采样格式
- rate: 采样率
- channels: 通道数
- period_size: period_size大小,决定中断触发频率
- periods: periods大小,决定buffer size大小
dsnoop 插件
static const snd_pcm_dsnoop_config_t snd_pcm_dsnoop_ref_config = { .type = "dsnoop", .ipc_key = 1114, .slave = { .pcm = "hw:ac107", .format = SND_PCM_FORMAT_S16_LE, .rate = 16000, .channels = 1, .period_size = 1024, .periods = 4, }, };
- type: 插件类型 dsnoop
- ipc_key: 需要进行混音的均要指定同一ipc_key
- slave: pcm从设备的信息
- pcm: pcm设备的名称,必须为hw类型
- format: 采样格式
- rate: 采样率
- channels: 通道数
- period_size: period_size大小,决定中断触发频率
- periods: periods大小,决定buffer size大小
softvol 插件
static const snd_pcm_softvol_config_t snd_pcm_softvol_config = { .type = "softvol", .slave = { .pcm = "PlaybackPlug", }, .control = { .control_name = "Soft Volume Master", .card_name = "audiocodec", }, .min_dB = ‑51.0, .max_dB = 0.0, .resolution = 256, };
- type: 插件类型softvol
- slave: pcm从设备的信息
- pcm: pcm设备的名称,必须为hw类型
- control: 控件信
- control_name: 控件名称
- card_name: 声卡名称,指定控件位于哪个声卡中
- min_dB: 最小衰减
- max_dB: 最大衰减
- resolution: 精度
注意, 在第一次使用 softvol 插件进行播放时才会生成该控件; 如果想要在声卡驱动加载的时候就添加该控件,请修改对应的 codec 文件,例如
SND_CTL_KCONTROL_USER("Soft Volume Master", 255, 0, 255)
asym 插件
static const snd_pcm_asym_config_t snd_pcm_asym_config = { .type = "asym", .playback_pcm = "PlaybackSoftVol", .capture_pcm = "CaptureDsnoop", };
- type: 插件类型softvol
- playback_pcm: 指定播放设备的名称
- capture_pcm: 指定录音设备的名称
route 插件
static const snd_pcm_route_config_t snd_pcm_route_config = { .type = "route", .slave = { .pcm = "PlaybackRate", .channels = 1, }, .ttable = { {0, 0, 0.5}, {1, 0, 0.5}, TTABLE_CONFIG_END }, };
- type: 插件类型route
- slave: pcm从设备的信息
- pcm: pcm设备的名称
- channels: 通道数
- ttable: 各通道配置,上面的配置表示将输入的两声道数据分别作0.5倍衰减,然后合成单声道数据
- 第一个值表示输入的通道序号
- 第二个值表示输出的通道序号
- 第三个值表示衰减值
- ttable配置最后请务必添加TTABLE_CONFIG_END,表示配置结束
rate 插件
static const snd_pcm_rate_config_t snd_pcm_rate_config = { .type = "rate", .slave = { .pcm = "PlaybackDmix", .format = SND_PCM_FORMAT_S16_LE, .rate = 48000, }, .converter = "speexrate", };
- type: 插件类型rate
- slave: pcm从设备的信息
- pcm: pcm设备的名称
- format 采样精度
- rate 采样率
- converter 指定使用的重采样算法,建议使用speexrate
file 插件
static const snd_pcm_file_config_t snd_pcm_file_cap_config = { .type = "file", .slave = { .pcm = "CaptureDsnoop", }, .format = "raw", .mode = "adb", .port = 20191, };
- type: 插件类型file
- slave: pcm从设备的信息
- pcm: pcm设备的名称
- format: 音频格式,目前仅支持raw
- mode: file插件保存数据的模式,目前仅支持adb
- port: 端口号
- server: 服务器端ip,当mode为network时才有效
multi 插件
static const snd_pcm_multi_config_t snd_pcm_3mic_1ref_config = { .type = "multi", .slaves = { { "a", "CaptureDsnoop3Mic", 3 }, { "b", "CaptureDsnoopRef", 1 }, { NULL, NULL, ‑1 }, }, .bindings = { { 0, "a", 0 }, { 1, "a", 1 }, { 2, "a", 2 }, { 3, "b", 0 }, { ‑1, NULL, ‑1 }, }, };
- type: 插件类型multi
- slaves: pcm从设备的信息,上述配置表示同时对a,b两个pcm设备进行录音
- 第一个参数表示pcm设备的别名,方便后续bingdings域指定不同的pcm设备
- 第二个参数表示pcm设备的名称
- 第三个参数表示通道总数
- 注意请在配置最后添加{ NULL, NULL, ‑1 },表示结束
- bindings: 指定多个声卡的通道数排列,上述配置表示a声卡的3通道分别作为通道0,1,2输出,b声卡的通道0作为通道3输出
- 第一个参数表示录音输出的通道序号
- 第二个参数表示指定声卡的通道序号
- 注意请在配置最后添加{ ‑1, NULL, ‑1 },表示结束
plug 插件
static const snd_pcm_plug_config_t snd_pcm_plug_config = { .type = "plug", .slave = { .pcm = "PlaybackDmix", .format = SND_PCM_FORMAT_S16_LE, .channels = 1, .rate = 48000, }, .rate_converter = "speexrate", .route_policy = "default", .ttable = { TTABLE_CONFIG_END }, };
-
type: 插件类型plug
-
slave: pcm从设备的信息
- pcm: pcm设备的名称
- format: 采样格式
- channels: 通道数
- rate: 采样率
-
rate_converter: 指定使用的重采样算法名称
-
route_policy: 使用route插件时的策略,可选average,copy,duplicate,default即为copy
-
ttable: 各通道配置,上面的配置表示将输入的两声道数据分别作0.5倍衰减,然后合成单声道数据
-
第一个值表示输入的通道序号
-
第二个值表示输出的通道序号
-
第三个值表示衰减值
-
ttable配置最后请务必添加TTABLE_CONFIG_END,表示配置结束
-
I2S 外挂CODEC
硬件连接
确保外部CODEC 芯片与SoC I2S 接口正确连接,具体确认连接如下。
- LRCK, BCLK: 确认该两线是否连接;
- MCLK: 确认外部CODEC 是否需要MCLK,若需要,则确认MCLK 信号线连接;
- DIN: 确认外部CODEC 是否需要录音功能,若需要,则确认DIN 信号线连接;
- DOUT: 确认外部 CODEC 是否需要播放功能,若需要,则确认 DOUT 信号线连接。
获取外部 CODEC I2S 协议格式
确认外部 CODEC I2S 协议格式如下。
- 功能需求:只录音、只播放、录音播放;
- 引脚确认:I2S 序号、data 引脚序号;
- 主从模式:SoC 作主(由 SoC 提供 BCLK,LRCK)、外挂 CODEC 作主(由外挂 CODEC提供 BCLK,LRCK);
- I2S 模式:标准 I2S、I2S_L、I2S_R、DSP_A、DSP_B;
- LRCK 信号是否翻转;
- BCLK 信号是否翻转;
- MCLK 信号:MCLK 频率;
- slot 个数:最高要支持多少 slot(音频通道数);
- slot 宽度:最高要支持多少 slot 宽度(音频采样位深)。
查看 “模块介绍” 说明的 “AHUB” 或 “I2S/PCM” -> “sys_conf 配置” 中的配置项说明,根据 I2S 协议格式进行配置。
AudioSystem
对于多核异构芯片,包含 RV/M33/DSP 三个核,各自运行这一个 RTOS,他们无法同时访问操作同一个音频硬件接口,因此软件上会实现跨核音频相关的功能,并且会屏蔽底层实现细节,让应用无感知,这套软件我们称之为 AudioSystem。
AudioSystem 软件框架
任意核上都能运行 AudioSystem, 因此可以提供统一的音频软件接口,而 AudioSystem 里面会根据实际配置对接到具体音频硬件接口,或者跨核音频接口。主要框图如下:
AudioSystem 可实现的功能包括:
- AudioTrack,支持多实例播放
- AudioRecord,支持多实例录音
- audio_plugin, 支持软件音量、重采样、精度转换、通道映射等功能
- audio_hw, 向上给 AudioTrack/AudioRecord 提供通用的数据读写接口,向下可对接本地声卡、跨核音频接口,甚至是 BT a2dp source 等功能。
软件配置
System components ‑‑‑> aw components ‑‑‑> AudioSystem Support ‑‑‑> [*] AW Audio System Support [*] Audio HW Support [*] Audio HW Support PCM [ ] Audio HW Support Multi PCM [ ] Audio HW Support AMP [ ] Audio HW Support BT [*] Audio HW Support EQ [ ] Audio HW Support AW EQ [*] Audio HW Support DRC [*] Audio Track Support [*] Audio Record Support [*] Audio System Utils [*] Audio Plugin Support
参数配置
AudioSystem 默认使能录音和播放的参数设置,涉及到的相关配置如下:
board/r128s2/pro/configs/sys_config.fex
根据板子的型号来选择对应的目录。这里是r128s2的pro板。录音和播放相关的参数如下:
[audio_hw_rv] //RV核参数配置 pcm_pb_rate = 48000 pcm_pb_channels = 1 pcm_pb_bits = 16 pcm_pb_period_size = 960 pcm_pb_periods = 4 pcm_pb_card = "hw:audiocodecdac" //播放声卡名称 amp_cap_rate = 16000 amp_cap_channels = 3 amp_cap_bits = 16 amp_cap_period_size = 320 amp_cap_periods = 4 amp_cap_card = "capture" //跨核声卡的名称 [audio_hw_dsp] //dsp核参数配置 pcm_cap_rate = 48000 pcm_cap_channels = 3 pcm_cap_bits = 16 pcm_cap_period_size = 320 pcm_cap_periods = 4 pcm_cap_card = "hw:audiocodecadc" //录音声卡名称
请根据实际使用需求配置参数,不要随意设置参数,否则会导致录音或播放失败,因为驱动会限制 periods,period_size 还有buffer_bytes 的最小值和最大值。如果录音出现 overrun,可以尝试增大 periods,不要随意修改 period_size。
音频接口
头文件
#include <AudioSystem.h>
AudioSystem.h 包含了 AudioTrack.h 以及 AudioRecord.h,因此应用上可以只包含 AudioSystem.h 即可
AudioTrack 接口
头文件
#include <AudioTrack.h>
创建 AudioTrack
tAudioTrack *AudioTrackCreate(const char *name)
参数:
- name: 指定audio_hw名称
返回值:
- 成功则返回AudioTrack句柄,失败返回NULL。
name 参数可支持的值,可以通过 ahw_list 命令查看, 例如 default,amp 也可以在代码中自定义添加需要的名称,详情可以参考 audio_hw/audio_hw.c 中的 add_default_ahw 函数
创建指定音频流类型的 AudioTrack
tAudioTrack *AudioTrackCreateWithStream(const char *name, uint16_t type)
参数:
- name: 指定audio_hw名称
- type: 音频流类型,每个类型都有独自的音量调节曲线
返回值:
- 成功则返回AudioTrack句柄,失败返回NULL。
如果使用 AudioTrackCreate 接口,默认使用的音频流类型是 AUDIO_STREAM_SYSTEM 音频流音量曲线在 audio_plugin/softvolume.c 里面定义, 可以参考 softvol_stream_init 函数默认加载了两种类型的曲线。
设置 AudioTrack 相关音频参数
int AudioTrackSetup(tAudioTrack *at, uint32_t rate, uint8_t channels, uint8_t bits)
参数:
- at: AudioTrack句柄
- rate: 采样率
- channels: 通道数
- bits: 采样精度
返回值:
- 成功则返回0,否则返回error code
写入 PCM 数据
int AudioTrackWrite(tAudioTrack *at, void *data, uint32_t size)
参数:
- at: AudioTrack句柄
- data: 要写入的pcm数据
- size: 数据大小,bytes
返回值:
- 成功则返回写入的字节数否则返回error code
开启 AudioTrack
int AudioTrackStart(tAudioTrack *at)
参数:
- at: AudioTrack句柄
返回值:
- 成功则返回0,否则返回error code
放音流程中不一定要执行 AudioTrackStart 函数,因为在调用 AudioTrackWrite 的时候,内部会根据状态自动调用 start 的。
停止 AudioTrack
int AudioTrackStop(tAudioTrack *at)
参数:
- at: AudioTrack句柄
返回值:
- 成功则返回0,否则返回error code
获取当前 AudioTrack 仍有多少数据未送入声卡
int AudioTrackDelay(tAudioTrack *at)
参数:
- at: AudioTrack句柄
返回值:
- 成功则返回0,否则返回error code
销毁 AudioTrack
int AudioTrackDestroy(tAudioTrack *at)
参数:
- at: AudioTrack句柄
返回值:
- 成功则返回0,否则返回error code
AudioRecord 接口
头文件
#include <AudioRecord.h>
创建 AudioRecord
tAudioRecord *AudioRecordCreate(const char *name)
参数:
- name: 指定audio_hw名称
返回值:
- 成功则返回AudioRecord句柄,失败返回NULL。
设置 AudioRecord 相关音频参数
int AudioRecordSetup(tAudioRecord *ar, uint32_t rate, uint8_t channels, uint8_t bits)
参数:
- ar: AudioRecord句柄
- rate: 采样率
- channels: 通道数
- bits: 采样精度
返回值:
- 成功则返回0,否则返回error code
获取 PCM 数据
int AudioRecordRead(tAudioRecord *ar, void *data, uint32_t size)
参数:
- ar: AudioRecord句柄
- data: 读取的pcm数据存放的地址
- size: 数据大小,bytes
返回值:
- 成功则返回写入的字节数,否则返回error code
开启 AudioRecord
int AudioRecordStart(tAudioRecord *ar)
参数:
- ar: AudioRecord句柄
返回值:
- 成功则返回0,否则返回error code
放音流程中不一定要执行 AudioRecordStart 函数,因为在调用 AudioRecordRead 的时候,内部会根据状态自动调用 start
停止 AudioRecord
int AudioRecordStop(tAudioRecord *ar)
参数:
- ar: AudioRecord句柄
返回值:
- 成功则返回0,否则返回error code
销毁 AudioRecord
int AudioRecordDestroy(tAudioRecord *ar)
参数:
- ar: AudioRecord句柄
返回值:
- 成功则返回0,否则返回error code
AudioSystem 其他接口
头文件
#include <AudioSystem.h>
AudioSystem 初始化
int AudioSystemInit(void)
参数:
- 无
返回值:
- 成功则返回0,否则返回error code
AudioSystemInit 一般在 os 的初始化流程中调用 (例如 main.c 中)
软件音量控制接口
int softvol_control_with_streamtype(int stream_type, uint32_t *vol_index, int16_t mode)
参数:
- stream_type: 音频流类型
- vol_index: 据mode得到的音量数值
- mode: 操作模式。0:读取;1:写入;2:读取范围
返回值:
- 成功则返回0,否则返回error code
当 mode 为 2 时,vol_index 的值表示音量范围,低 16bit 表示最小值,高 16bit 表示最大值。
AudioSystem 接口示例
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <math.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <console.h> #include <AudioSystem.h> #include "wav_parser.h" #include <hal_thread.h> static int g_loop_count = 0; static int8_t g_play_or_cap = 0; static int8_t g_record_then_play = 0; static int8_t g_bits = 0; static int g_run_time = 0; static int g_at_task_id = 0; static int g_ar_task_id = 0; static int g_rate = 0; static int g_channels = 0; static char g_wav_path[128] = "16K_16bit_1ch"; static int g_tmp_arg = 0; static char g_at_name[12] = "default"; static char g_ar_name[12] = "default"; static int g_ar_forward_port = 0; #define LOCAL_PRIORITIES \ (configMAX_PRIORITIES > 20 ? configMAX_PRIORITIES - 8 : configMAX_PRIORITIES - 3) struct as_test_data { int loop_count; int type; /* 0:wav, 1:data */ uint32_t rate,channels; void *ptr; int data_bytes; uint8_t bits; int8_t sec; int8_t record_then_play; }; #define PI (3.1415926) static int sine_generate(void *buf, uint32_t len, uint32_t rate, uint32_t channels, uint8_t bits, float amp) { int sine_hz = 1000; int sine_point, sine_cycle, sine_point_bytes; int i,j; int accuracy = INT16_MAX; int frame_bytes; if (amp > 1.0) amp = 1.0; else if (amp < 0.01) amp = 0.01; frame_bytes = channels * bits / 8; sine_point = rate / sine_hz; sine_point_bytes = frame_bytes * sine_point; sine_cycle = len / sine_point_bytes; if (bits == 16) { int16_t *data = buf; accuracy = INT16_MAX; for (j = 0; j < sine_point; j++) { int16_t value = (int16_t)(amp * accuracy * sin(2 * (double)PI * j / sine_point)); if (channels == 1) { data[j] = value; } else if (channels == 2) { data[2 * j] = value; data[2 * j + 1] = value; } else { printf("unsupport channels:%d\n", channels); while(1); } } } else if (bits == 32) { int32_t *data = buf; accuracy = INT32_MAX; for (j = 0; j < sine_point; j++) { int32_t value = (int32_t)(amp * accuracy * sin(2 * (double)PI * j / sine_point)); if (channels == 1) { data[j] = value; } else if (channels == 2) { data[2 * j] = value; data[2 * j + 1] = value; } else { printf("unsupport channels:%d\n", channels); while(1); } } } for (i = 1; i < sine_cycle; i++) { memcpy(buf + i * sine_point_bytes, buf, sine_point_bytes); /*printf("[%s] line:%d buf:%p, dest:%p, ofs:%u\n", __func__, __LINE__,*/ /*buf, buf + i * sine_point_bytes, i * sine_point_bytes); */ } return sine_cycle * sine_point_bytes; } static void play_sine(tAudioTrack *at, uint32_t rate, uint8_t channels, uint8_t bits, int sec) { int frame_loop = 480; int count, frame_bytes; void *buf; int len, size = 0; frame_bytes = frame_loop * channels * (bits == 16 ? 2 : 4); count = rate * sec / frame_loop; buf = malloc(frame_bytes); /*printf("[%s] line:%d malloc %u bytes\n", __func__, __LINE__, frame_bytes);*/ if (!buf) return; len = sine_generate(buf, frame_bytes, rate, channels, bits, 0.8); while (count--) { size = AudioTrackWrite(at, buf, len); if (size != len) { printf("at write return %d\n", size); break; } } free(buf); } static void at_sine_task(void *arg) { tAudioTrack *at; int channels = g_channels; int rate = g_rate; int sec = g_run_time; uint8_t bits = g_bits; #if 1 at = AudioTrackCreate(g_at_name); #else at = AudioTrackCreateWithStream(g_at_name, AUDIO_STREAM_MUSIC); #endif if (!at) { printf("at create failed\n"); goto err; } AudioTrackSetup(at, rate, channels, bits); play_sine(at, rate, channels, bits, sec); AudioTrackStop(at); AudioTrackDestroy(at); err: vTaskDelete(NULL); } static void at_sine_task_create() { hal_thread_t handle; char buf[32]; snprintf(buf, sizeof(buf), "at_sine%d", g_at_task_id); g_at_task_id++; handle = hal_thread_create(at_sine_task, NULL, buf, 8192, HAL_THREAD_PRIORITY_APP); } static void at_task(void *arg) { tAudioTrack *at; struct as_test_data *data = arg; at = AudioTrackCreate(g_at_name); if (!at) { printf("at create failed\n"); goto err; } if (data->type == 0) { /* Pass */ } else if (data->type == 1 && data->ptr != NULL){ /* play pcm data */ int value = 0; int count = data->loop_count; AudioTrackSetup(at, data->rate, data->channels, data->bits); while (count--) { AudioTrackWrite(at, data->ptr, data->data_bytes); value++; printf("[%s] line:%d playback count=%d\n", __func__, __LINE__, value); } } AudioTrackStop(at); AudioTrackDestroy(at); err: if (data->ptr) free(data->ptr); free(data); vTaskDelete(NULL); } static void at_task_create(struct as_test_data *data) { hal_thread_t handle; char buf[32]; struct as_test_data *d; d = malloc(sizeof(struct as_test_data)); if (!data) { d->type = 0; d->ptr = NULL; d->loop_count = g_loop_count; } else { memcpy(d, data, sizeof(struct as_test_data)); } snprintf(buf, sizeof(buf), "at_task%d", g_at_task_id); g_at_task_id++; handle = hal_thread_create(at_task, d, buf, 2048, HAL_THREAD_PRIORITY_APP); } extern int adb_forward_create_with_rawdata(int port); extern int adb_forward_send(int port, void *data, unsigned len); extern int adb_forward_end(int port); static void ar_task(void *arg) { tAudioRecord *ar; struct as_test_data *data = arg; uint32_t rate = data->rate; uint32_t channels = data->channels; uint8_t bits = data->bits; int total, read_size, size, read = 0; int frame_bytes = bits / 8 * channels; int frames_bytes_loop = frame_bytes * rate / 100; /* 10ms */ void *buf = NULL; if (data->sec) total = data->sec * rate * frame_bytes; else total = frames_bytes_loop; /* 10ms buffer */ #ifdef CONFIG_COMPONENTS_USB_GADGET_ADB_FORWARD int _port = g_ar_forward_port; if (g_ar_forward_port > 0) g_ar_forward_port = 0; if (adb_forward_create_with_rawdata(_port) < 0) _port = -1; #endif ar = AudioRecordCreate(g_ar_name); if (!ar) { printf("ar create failed\n"); goto err; } buf = malloc(total); if (!buf) { printf("no memory\n"); goto err; } AudioRecordSetup(ar, rate, channels, bits); AudioRecordStart(ar); printf("[%s] line:%d buf:%p, %d\n", __func__, __LINE__, buf, total); while (data->loop_count--) { if (data->sec) total = data->sec * rate * frame_bytes; else total = frames_bytes_loop; read = 0; while (total > 0) { if (total > frames_bytes_loop) size = frames_bytes_loop; else size = total; read_size = AudioRecordRead(ar, buf + read, size); if (read_size != frames_bytes_loop) { printf("read_size(%d) != frames_bytes_loop(%d)\n", read_size, frames_bytes_loop); break; } #ifdef CONFIG_COMPONENTS_USB_GADGET_ADB_FORWARD adb_forward_send(_port, buf + read, size); #endif total -= read_size; read += read_size; /*printf("[%s] line:%d residue:%d read=%u\n", __func__, __LINE__, total, read);*/ } if (read_size < 0) break; } AudioRecordStop(ar); AudioRecordDestroy(ar); ar = NULL; #ifdef CONFIG_COMPONENTS_USB_GADGET_ADB_FORWARD adb_forward_end(_port); /* don't destroy port for repeating record */ /*adb_forward_destroy(_port);*/ #endif vTaskDelay(pdMS_TO_TICKS(500)); if (data->record_then_play != 0) { struct as_test_data d = { .loop_count = 1, .type = 1, .rate = rate, .channels = channels, .ptr = buf, .data_bytes = read, .bits = bits, }; at_task_create(&d); buf = NULL; } err: if (ar) AudioRecordDestroy(ar); free(data); if (buf) free(buf); vTaskDelete(NULL); } static void ar_task_create(struct as_test_data *data) { hal_thread_t handle; char buf[32]; struct as_test_data *d; d = malloc(sizeof(struct as_test_data)); if (!data) { d->type = 0; d->ptr = NULL; d->rate = g_rate; d->channels = g_channels; d->bits = g_bits; d->record_then_play = g_record_then_play; d->loop_count = g_loop_count; d->sec = g_run_time; } else { memcpy(d, data, sizeof(struct as_test_data)); } snprintf(buf, sizeof(buf), "ar_task%d", g_ar_task_id); g_ar_task_id++; handle = hal_thread_create(ar_task, d, buf, 2048, HAL_THREAD_PRIORITY_APP); } static void as_test_usage() { printf("Usgae: as_test [option]\n"); printf("-h, as_test help\n"); printf("-s, stream, 0-playback; 1-capture; 2-playback sine\n"); printf("-d, duration, sec\n"); printf("-r, rate\n"); printf("-c, channels\n"); printf("-b, bits\n"); printf("-t, capture and then playback\n"); printf("-n, AudioTrack name\n"); printf("-m, AudioRecord name\n"); printf("-l, loop count\n"); printf("-f, adb forward port\n"); printf("\n"); printf("play sine:\n"); printf("as_test -s 2 -d 10 -r 48000\n"); printf("capture:\n"); printf("as_test -s 1 -d 0 -l 1000 -r 16000 -c 3\n"); printf("capture and forward:\n"); printf("as_test -s 1 -d 0 -l 1000 -r 16000 -c 3 -f 20227\n"); printf("\n"); } int cmd_as_test(int argc, char *argv[]) { int c = 0; g_play_or_cap = 0; g_loop_count = 1; g_run_time = 3; g_rate = 16000; g_channels = 2; g_bits = 16; g_record_then_play = 0; optind = 0; while ((c = getopt(argc, argv, "htl:s:ad:r:c:b:g:n:m:f:")) != -1) { switch (c) { case 'h': as_test_usage(); return 0; case 'l': g_loop_count = atoi(optarg); break; case 's': /* * 0: playback * 1: capture * 2: playback sine * */ g_play_or_cap = atoi(optarg); break; case 'd': g_run_time = atoi(optarg); break; case 'r': g_rate = atoi(optarg); break; case 'c': g_channels = atoi(optarg); break; case 'b': g_bits = atoi(optarg); break; case 't': g_record_then_play = 1; break; case 'g': g_tmp_arg = atoi(optarg); break; case 'n': strncpy(g_at_name, optarg, sizeof(g_at_name)); break; case 'm': strncpy(g_ar_name, optarg, sizeof(g_ar_name)); break; case 'f': g_ar_forward_port = atoi(optarg); break; default: return -1; } } if (optind < argc) { strncpy(g_wav_path, argv[optind], sizeof(g_wav_path) - 1); } else { strcpy(g_wav_path, "16K_16bit_1ch"); } switch (g_play_or_cap) { case 0: at_task_create(NULL); break; case 1: ar_task_create(NULL); break; case 2: at_sine_task_create(NULL); break; default: printf("unknown 's' command\n"); break; } return 0; } FINSH_FUNCTION_EXPORT_CMD(cmd_as_test, as_test, audio system test); static void as_volume_usage(void) { printf("Usgae: as_volume [option]\n"); printf("-h, as_volume help\n"); printf("-t, volume type\n"); printf(" 1:system\n"); printf(" 2:music\n"); printf("-m, option mode\n"); printf(" 0:get\n"); printf(" 1:set\n"); printf(" 2:get range\n"); printf("\n"); } static int cmd_as_volume(int argc, char *argv[]) { int type = AUDIO_STREAM_SYSTEM; int volume_mode = 0; uint32_t volume_value = 0; int c = 0, ret = 0; optind = 0; while ((c = getopt(argc, argv, "ht:m:")) != -1) { switch (c) { case 't': type = atoi(optarg); break; case 'm': volume_mode = atoi(optarg); break; case 'h': default: as_volume_usage(); return -1; } } if (optind < argc) { int value = atoi(argv[optind]); volume_value = (uint32_t)(value | value << 16); } ret = softvol_control_with_streamtype(type, &volume_value, volume_mode); if (ret != 0) { printf("softvol(t:%d, m:%d), control failed:%d\n", type, volume_mode, ret); return -1; } switch (volume_mode) { case 0: /* read */ printf("softvol(%d) read, value=%d,%d\n", type, (volume_value & 0xffff), ((volume_value >> 16) & 0xffff)); break; case 1: /* write */ printf("softvol(%d) write, value=%d,%d\n", type, (volume_value & 0xffff), ((volume_value >> 16) & 0xffff)); break; case 2: /* read range */ printf("softvol(%d) read, min=%u, max=%u\n", type, (volume_value & 0xffff), ((volume_value >> 16) & 0xffff)); break; } return 0; } FINSH_FUNCTION_EXPORT_CMD(cmd_as_volume, as_volume, audio system volume control);
AudioSystem 测试工具
ahw_list 命令
用于查看当前 audio_hw 中支持了哪些 audio_hw_elem
audio hw list: instance |name |read |write 0 |default |0x04064128|0x04063ff4 2 |playback |0000000000|0x04063ff4 第一列instance, 每个elem都有一个instasnce值,决定他们是哪种类型的HW_TYPE 第二列name, 每个elem都有唯一的标志名称。 第三列read,表示读函数指针,0表示不支持读。 第四列write,表示写函数指针,0表示不支持写。
as_test 命令
as_test 可用于测试播放录音功能。
选项 功能 ‑s 哪种测试;0: playback; 1: capture; 2: 播放 sine ‑d 运行时间 (秒); 注意, 录音会根据该值申请对应大小的内存 buffer,不能设置太大 ‑l 循环执行次数;可以结合‑d 参数实现较低内存长时间的测试 ‑r 采样率 ‑c 通道数 ‑b 采样精度 ‑t 录音然后播放 ‑n 指定创建 AudioTrack 时的 name 参数 ‑m 指定创建 AudioRecord 时的 name 参数 ‑f adb forward 的端口号 测试举例:
播放5s 48K,2ch的正弦波数据: as_test ‑s 2 ‑d 5 ‑r 48000 ‑c 2 播放5s 48K,2ch,32bit的正弦波数据: as_test ‑s 2 ‑d 5 ‑r 48000 ‑c 2 ‑b 32 播放10s 16K 1ch的正弦波数据: as_test ‑s 2 ‑d 10 ‑r 16000 ‑c 1 播放U盘中指定的文件: as_test ‑s 0 ‑n playback /usb_msc/test.wav 录音1s钟 as_test ‑s 1 ‑m amp ‑d 1 ‑c 3 ‑f 20227 3通道录音100s(‑d 0表示由‑l决定时间=10000*10ms=100s) as_test ‑s 1 ‑m amp ‑d 0 ‑l 10000 ‑c 3 ‑f 20227 2通道录音3s然后播放出来 as_test ‑s 1 ‑d 3 ‑c 2 ‑m amp ‑t 如果是在dsp使用录音的话,‑m参数要使用default。
as_volume 命令
可以通过 as_volume 命令获取或者设置不同类型的音量值。
注意需要选中 COMPONENTS_AW_AUDIO_SYSTEM_PLUGIN 之后才有该功能。
获取SYSTEM类型的当前音量值 命令: as_volume ‑t 1 ‑m 0 结果: softvol(1) read, value=5,5 设置SYSTEM类型的音量值 命令: as_volume ‑t 1 ‑m 1 8 结果: softvol(1) read, value=8,8 获取MUSIC类型的音量范围 命令: as_volume ‑t 2 ‑m 2 结果: softvol(2) read, min=0, max=10
as_debug 命令
as_debug 用于开启、关闭 AudioSystem 的调试模式。开启后会有更多 (对应代码中
_debug
打印) 的调试信息。开启方式:
as_debug 1
AW 音效
FreeRTOS 中使用 AudioSystem 的 audio hw 层来实现 AW EQ 功能。该功能是 AW(AllWinner)提供的软件音效 EQ 音效,需要选择 EQ 库才能使用。
编译 AudioSystem 的 AW EQ
下面是编译的配置。要先选择 EQ Module,才会出现 Audio HW Support AW EQ。
System components ‑‑‑> aw components ‑‑‑> Algorithm Process Module ‑‑‑> [*] EQ Module AudioSystem Support ‑‑‑> [*] AW Audio System Support [*] Audio HW Support [*] Audio HW Support PCM [ ] Audio HW Support Multi PCM ‑*‑ Audio HW Support AMP [ ] Audio HW Support BT [ ] Audio HW Support EQ [*] Audio HW Support AW EQ [ ] Audio HW Support DRC [*] Audio Track Support [*] Audio Record Support [*] Audio System Utils [*] Audio Plugin Support
使用软件 AW EQ
- 确保 data 目录下有 EQ.conf 配置文件,该配置文件保存的是调试 EQ 的参数。
EQ.conf 的路径如下:
board/芯片/项目/data/UDISK
例如 R128S2的pro板。
board/r128s2/pro/data/UDISK
- 在应用核(RV)使用如下命令,即可调用软件 EQ 进行播放。
as_test ‑s 0 ‑n playbackEQ /usb_msc/test.wav
- 如果更新了 EQ 参数,需要更新配置文件 EQ.conf 到 data/UDISK 目录下,使用上述命令即可生效。
使用 AudioSystem 的硬件 HW EQ 功能
R128 平台支持硬件 DAC 的 20‑band main EQ 和 5‑band post EQ,所以软件 AW EQ 默认不使用。
FreeRTOS 中使用 AudioSystem 的 audio hw 层来实现 HW EQ 功能。
编译 AudioSystem 的 HW EQ
System components ‑‑‑> aw components ‑‑‑> AudioSystem Support ‑‑‑> [*] AW Audio System Support [*] Audio HW Support [*] Audio HW Support PCM [ ] Audio HW Support Multi PCM ‑*‑ Audio HW Support AMP [ ] Audio HW Support BT [*] Audio HW Support EQ [ ] Audio HW Support AW EQ [ ] Audio HW Support DRC [*] Audio Track Support [*] Audio Record Support [*] Audio System Utils [*] Audio Plugin Support
使用离线 HW EQ
- 确保 data 目录下有 R128EQ‑5band.conf 或 R128EQ‑20band.conf 配置文件,该配置文件保存的是硬件 EQ 的参数。如果上述配置文件都没有,HW EQ 就不会生效。
R128EQ‑5band.conf或R128EQ‑20band.conf的路径如下:
board/r128s2/pro/data/UDISK
根据硬件配置来选择路径。这里对应的是R128S2的pro板。
如果没有文件会打印
Parse from config file /data/R128EQ‑20band.conf Failed to open /data/R128EQ‑5band.conf (2) 第一行打印表示已经读取了20band EQ的配置文件,并设置到硬件。 第二行打印表示对应路径没有配置文件,读取失败,不会配置。
- 在应用核(RV)使用如下命令,即可调用 HW EQ 进行播放。
as_test ‑s 0 ‑n playback /usb_msc/test.wav
如果更新了 HW EQ 参数,需要更新配置文件 R128EQ‑5band.conf 或 R128EQ‑20band.conf 到data/UDISK 目录下。
因为是硬件 EQ 是配置寄存器,所以只有开机后第一次播放音乐会生效,没有必要每次播放都更新硬件寄存器。如果更新了 HW EQ 参数,就要重启后,再次播放音乐生效
常用调试方法
dump 寄存器
通过 reg_read/reg_write 命令可以读写 SoC 上的寄存器,可以通过查看 SoC 的 user manual, 得到具体模块的寄存器地址。
例如:
audiocodec模块寄存器基地址为0x5096000 将0x5096000开始后面4个寄存器打印出来: reg_read 0x5096000 0x10 将0x5096010的值设置为0x60004000 reg_write 0x5096010 0x80004000
实时获取录音数据
很多时候需要获取设备实际录音数据,用于确认数据的准确性、声学性能等,而在 RTOS 中存储介质比较有限,并且写入速度慢,并不能用于保存录音数据。这里利用 adb forword 功能,配合 alsa file 插件,可以实现基于 USB 传输的 PC 端实时获取录音数据的功能。
PC 端通过脚本,实时获取录音数据
PC 上使用脚本录音,需要对应 python2.0 的版本。如果使用更高版本的 python,需要修改代码。最终保存的文件是 pcm 数据。
准备一个 adb_record.py 写入以下脚本
#!/usr/bin/python2 import sys import socket import time import datetime pc_port=11112 upload_file="data.pcm" upload_start="‑‑>AW_RTOS_SOCKET_UPLOAD_START" upload_end="‑‑>AW_RTOS_SOCKET_UPLOAD_END" data_length=4096 def client_test(port): s = socket.socket() host = "localhost" s.connect((host,port)) while True: data = s.recv(data_length) if upload_start in data: print 'recv upload start flag...data_len=%d, flag_len=%d' % (len(data), len(upload_start)) now_time = datetime.datetime.now().strftime("%Y‑%m‑%d‑%H‑%M‑%S") upload_file="record‑" + now_time + ".pcm" if (len(upload_start) != len(data)): with open(upload_file.decode('utf‑8'), 'ab+') as f: f.write(data[len(upload_start):]) elif upload_end in data: index = data.find(upload_end) print 'recv upload end flag...data_len=%d, str_index=%d' % (len(data), index) if index > 0: with open(upload_file.decode('utf‑8'), 'ab+') as f: f.write(data[:index]) break else: if (len(data) == 0): print 'data is 0' break; # print 'recv data, len is ', len(data) with open(upload_file.decode('utf‑8'), 'ab+') as f: f.write(data) print 'finish...' s.close() def main(): if (len(sys.argv) == 2): port = int(sys.argv[1]) else: port = pc_port client_test(port) if __name__=='__main__': main()
借用adb工具配置好forward相关功能,并执行python脚本adb_record.py开始都等待获取录音数据
adb shell af ‑p 20191 ‑r adb forward tcp:11112 tcp:20191 ./adb_record.py &
小机端使用方法
使用 alsa 插件录音
可以通过串口,或者adb让设备进行录音,下面以adb方式举例:
录音60s:
adb shell arecord ‑DCaptureFile ‑d 60
一直录音:
adb shell arecord ‑DCaptureFile ‑l
如果想终止录音,可以再起一个终端输入命令:
adb shell arecord ‑k
这里arecord命令指定的pcm设备是CaptureFile, 通过alsa_config.c配置文件发现这是一个file插件,也就是说录音数据会经过file插件处理(通过adb发送数据到PC端)
相关操作
声卡驱动的加载
声卡配置位于代码中, 路径如下:
在每个方案目录下面:
projects/方案/src/card_default.c
如果是dsp方案,该文件会在rtos‑hal仓库下:
rtos‑hal/hal/source/sound/card_default.c
使用snd_card_register函数进行声卡的注册
int snd_card_register(const char *name, struct snd_codec *codec, int platform_type)
- name: 表示声卡的名字,aw‑alsa‑lib中都需要通过该名字找到对应的声卡
- codec: codec结构体,根据实际使用的codec进行配置
- platform_type: 与linux中ASOC框架类似,也需要指定使用哪种类型的platform,如CPUDAI, DAUDIO等
举例:
注册R128 audiocodecdac声卡
card_name = "audiocodecdac"; snd_card_register(card_name, audio_codec, SND_PLATFORM_TYPE_CPUDAI_DAC);
声卡加载配置
snd_pcm_open
打开的声卡名字都是由alsa_config.c
中配置的,该文件一般在:在每个方案目录下面:
projects/方案/src/alsa_config.c
如果是dsp方案,该文件会在rtos‑hal仓库下:
rtos‑hal/hal/source/sound/component/aw‑tiny‑alsa‑lib/alsa_config.c
配置举例:
static DEFINE_SND_PCM_HW_CONFIG(audiocodec, 0); const snd_pcm_config_t snd_pcm_global_configs[] = { SND_PCM_CONFIG("hw:audiocodec", "hw", &snd_audiocodec_hw_config), };
表示
snd_pcm_open
可用"hw:audiocodec"名字打开声卡,而底层声卡名字是audiocodec。音量设置
录音或者播放,会出现声音过小或者过大的问题。目前可以调整音量的有两处:
硬件音量调整
可以通过 amixer 去调整控件。
对于 dac 来说,硬件音量分为 DAC Digtal volume 和 LINEOUT volumeamixer ‑c audiocodecdac numid=0, name='DACL dig volume' value=129, min=0, max=255 numid=1, name='DACR dig volume' value=129, min=0, max=255 numid=2, name='LINEOUT volume' value=5, min=0, max=7 amixer ‑c audiocodecdac set 0 150 amixer ‑c audiocodecdac set 1 150 dig volume是硬件的数字音量调节。 amixer ‑c audiocodecdac set 2 5 这个是输出的增益。
上述两组硬件音量,需要根据硬件来设置,过大会导致声音出现失真。
对于 adc 来说,硬件音量有 ADC Digtal volume 和 adc pga gainamixer ‑c audiocodecadc numid=0, name='ADC1 volume' value=129, min=0, max=255 numid=1, name='ADC2 volume' value=129, min=0, max=255 numid=2, name='ADC3 volume' value=129, min=0, max=255 numid=3, name='MIC1 volume' value=31, min=0, max=31 numid=4, name='MIC2 volume' value=31, min=0, max=31 numid=5, name='MIC3 volume' value=31, min=0, max=31 amixer ‑c audiocodecadc set 0 150 ADC1 volume指的是ADC Digtal volume amixer ‑c audiocodecadc set 3 19 MIC1 volume指的是硬件pga gain。
上述硬件音量,需要根据硬件来设置,过大会导致声音出现失真。
软件音量调整
对于使用 aw‑alsa‑lib 的系统,可以使用 softvol 插件。
该插件可以生成一个用于调整音量的控件 “Soft Volume Master”。amixer ‑c audiocodec numid=5, name='Soft Volume Master' value=255, min=0, max=255 amixer ‑c audiocodec set 5 200
对于使用 aw‑tiny‑alsa‑lib 的系统,配置 AudioSystem 后,才能可以使用 softvol plugin,默认会
配置软件音量调节的功能。as_volume ‑h Usgae: as_volume [option] ‑h, as_volume help ‑t, volume type 1:system 2:music ‑m, option mode 0:get 1:set 2:get range as_volume ‑t 1 ‑m 2 softvol(1) read, min=0, max=10 as_volume ‑t 2 ‑m 2 softvol(2) read, min=0, max=10 设置system类型的音频曲线 as_volume ‑t 1 ‑m 1 8 softvol(1) write, value=8,8 读取system类型的音频曲线 as_volume ‑t 1 ‑m 0 softvol(1) read, value=8,8 设置music类型的音频曲线 as_volume ‑t 2 ‑m 1 8 softvol(2) write, value=8,8 读取music类型的音频曲线 as_volume ‑t 2 ‑m 0 softvol(2) read, value=8,8
FAQ
常见问题
若下列问题无法解决您所遇到的问题,请到 全志在线开发者论坛 发帖询问
录音或播放变速
- 确认录音和播放采样率和父时钟 PLL_AUDIO 是否属于同一频段。
AudioCodec 输入输出无声音
-
确认通路设置。
通过 amixer 查看 route 状态,确认是否设置了需要的上下电通路。
-
对于喇叭,确认功放芯片使能设置。
查看驱动源码中 gpio_spk 的 GPIO 配置并和硬件原理图比对,确认是否适配了对应的 GPIO。
DMIC 录音异常(静音/通道移位)
-
确认GPIO 是否正常。
- 通过DataSheet 核对sys_config.fex 部分的DMIC Pin 设置;
- 若sys_config.fex 不支持引脚设置,则到dmic-sun20iw2.h 直接查看g_dmic_gpio 结构
体的设置
-
确认CLK 的频率。
以上正常情况下,示波器查看DMIC CLK 的频率是否满足
clk_rate = sample * over_sample_rate
关系。 -
排查硬件连接和DMIC 物料问题。
外放无声
使用 aplay 进行播放,发现外放无声。通常需要排查三个方面。
- 通路是否正确配置
amixer ‑c audiocodecdac numid=3, name='LINEOUTL switch' value=on, enum=off on 设置lineout通路 amixer ‑c audiocodecdac set 3 1
- 查看 gpio 是否正确配置
board/r128s2/pro/configs/sys_config.fex [audiocodec] dacl_vol = 129 dacr_vol = 129 lineout_vol = 5 lineoutl_en = 1 lineoutr_en = 0 mic1_gain = 19 mic2_gain = 19 mic3_gain = 0 mic1_en = 1 mic2_en = 1 mic3_en = 1 mad_bind_en = 0 pa_pin_msleep = 10 pa_pin = port:PB3<1><default><1><1> capture_cma = 30720
- 查看音量是否设置为 0
AudioSystem 接口创建实例失败
as_test ‑s 2 ‑d 5 ‑n playback ‑r 48000 ‑c 2 [RV‑AudioTrack‑ERR][AudioTrackCreateWithStream](562) mt add at failed at create failed
打开 as_debug
as_debug 1 set audio system debug:1 [RV‑AudioSystem‑INF][print_test](47) Audio System test [RV‑AudioSystem‑DBG][print_test](48) Audio System test [RV‑AudioSystem‑ERR][print_test](49) Audio System test
再次播放
as_test ‑s 2 ‑d 5 ‑n playback ‑r 48000 ‑c 2 [RV‑AudioTrack‑DBG][AudioTrackCreateWithStream](540) [RV‑MixerThread‑DBG][MixerThreadAddAT](445) [RV‑AudioHW‑DBG][find_alias_name](146) ahw alias name:playback vs playback stream:0 [RV‑AudioHW‑DBG][find_alias_name](146) ahw alias name:amp‑cap vs playback stream:0 [RV‑AudioTrack‑ERR][AudioTrackCreateWithStream](562) mt add at failed at create failed
声卡驱动在 RV 核,不可能会使用跨核接口 “amp‑cap”。使用 ahw_list 查看
c906> ahw_list audio hw list: instance |name |read |write ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ audio hw alias: p: default ‑‑> playback c: default ‑‑> amp‑cap
说明 audio_hw 中没有本地的 audio_hw_elem。在开机的时候,并没有初始化 AudioSystem 导致缺少本地 audio_hw_elem。
在启动中加上下面的代码,最好是加在声卡注册后。
lichee/rtos/projects/r128s2/pro_c906/src/main.c
#ifdef CONFIG_COMPONENTS_AW_AUDIO_SYSTEM AudioSystemInit(); #endif
AudioSystem 接口播放失败
c906> as_test ‑s 2 ‑d 5 ‑n playback ‑r 48000 ‑c 2 [RV‑AP‑chmap‑INF][chmap_ap_update_mode](151) ALL ‑> ch0(average) [AWALSA_ERR][_snd_pcm_hw_open:452]no such card:audiocodec [AWALSA_ERR][snd_pcm_open:258]pcm "hw:audiocodec": snd_pcm_open_config failed (return: ‑19) [RV‑AudioHWPCM‑ERR][pcm_ahw_open](221) pcm open failed:‑19 [RV‑AudioTrack‑ERR][__AudioTrackWrite](397) writw error(MT transfer data error?) [RV‑AudioTrack‑ERR][_AudioTrackWrite](505) write_size=480, in_size=480, written=‑1 at write return ‑1
上述 log 可以很明显的看到,声卡的名字不匹配,导致打开声卡失败。查看声卡的名字,以 R128 为例。
c906>soundcard ‑l Sound Card list: card_num card_name 0 audiocodecdac
改为0 声卡,正常播放
c906> as_test ‑s 0 ‑d 5 ‑n playback ‑r 48000 ‑c 2
AudioSystem 接口录音失败
输入的指令是正确的,但是设置参数却失败。log如下
as_test ‑s 1 ‑d 3 ‑c 2 ‑m amp ‑t [RV‑AudioRecord‑INF][AudioRecordParamSetup](153) create tbuf:0000000008313AA0, 1920 [RV‑AP‑chmap‑INF][chmap_ap_update_mode](153) ch0 ‑> ch0 [SND_ERR][sunxi_codec_hw_params:1036]capture only support 1~3 channel [RV‑AP‑chmap‑INF][chmap_ap_update_mode](153) ch1 ‑> ch1 [SND_ERR][soc_pcm_hw_params:434]codec_dai set hw params failed [ar_task] line:549 buf:00000000083EE960, 192000 Unable to install hw prams! c906>[DSP‑AudioRecord‑ERR][__AudioRecordRead](228) read error(ST transfer data error?) AudioRecordReadRM failed!!, return ‑1
这个问题也比较常见,因为驱动增加了检查,没有设置通路,直接录音就会导致这个错误。跟外放无声类似,出现录音失败或无声的问题,也是按照下面的顺序排查。
- 录音通路是否正确配置。
- 录音音量是否设置为 0。
其他
GPIO 功能复用配置
- AudioCodec 模块:
- 所用引脚功能均固化,无需进行 pin 功能复用配置。
- I2S/PCM,SPDIF,DMIC 模块:
- 可选择不进行 pin 功能复用配置,该情况仍可生成声卡,但引脚无实际输入输出功能。
驱动会直接从配置文件获取节点的所有引脚,与引脚的名称无关
# port:端口+组内序号<功能分配><内部电阻状态><驱动能力><输出电平状态> # <> 内为默认值则取 default pin_xxx = port:PA23<2><0><1><default>
-
【R128】基础组件开发指南——音频框架①
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
音频框架
简介
本文档基于 RTOS 平台基础音频框架介绍,能够让使用者在 RTOS 平台开发使用音频驱动,内容分四大部分。
- 音频时钟树-> 驱动特性-> 音频流通路->sys_config 配置-> 编译配置-> 声卡控件介绍->常见使用方法 等部分,介绍音频驱动的测试验证和使用;
- 源码结构-> 驱动框架-> 关键数据结构-> 接口说明 等部分,介绍音频驱动的二次开发;
- 声卡查看-> 声卡测试工具-> 外挂 CODEC 等部分,介绍声卡模块的测试验证和使用;
- FAQ”章节:调试方法及常见问题汇总。
模块介绍
在 sunxi 平台中,从软件上通常存在5 类音频设备,如下:
- AudioCodec
- I2S/PCM
- AHUB
- DMIC
- S/PDIF
以上每一类音频设备均适配ASoC 架构。
对于 R128 平台,具有以下音频接口资源
- AudioCodec x1
- I2S/PCM x1
- S/PDIF x1
- DMIC x1
另外还具有 MAD 模块,用于语音能量检测,非接口类型。
驱动框架
RTOS 上面的音频驱动框架与 Linux 上 ASoC 框架的思想比较类似,分为 codec,platform 模型,core 核心层实现具体的 codec 驱动加载、platform 驱动的选择、数据的搬运、dma 指针的更新等,能够将 codec 驱动与 SoC CPU 解耦合, 方便添加外挂 codec。
音频驱动的大致框架如下:
音频时钟树
R128 音频模块时钟源有4 个系列,分别为24.576MHz、22.5792MHz、8.192MHz 以及高频时钟。
- 24.576MHz、22.5792MHz 用于 Audiocodec、I2S/PCM 的播放和录音,S/PDIF 的播放,DMIC 的录音;
- 8.192MHz 用于系统休眠时供 Audiocodec、DMIC 录音,以及 mad 模块;
- 高频时钟用于 S/PDIF 的录音,以及 I2S/PCM 的 ASRC 模块;
时钟树如下图所示:
驱动特性
- 支持多种采样率格式
- 播放:8~384kHz
- 录音:8~96kHz
- 支持多通道播放和录音
- 播放:1~2
- 录音:1~3
- 支持16/24/32bit 数据精度(硬件支持16/24bit)
- 支持硬件HPF、DRC、EQ 算法
- 支持的物理接口
- INPUT : MIC
- OUTPUT: LINEOUT
- MIC, LINEOUT 支持差分和单端模式
- 支持同时playback 和capture (全双工模式)
音频通路
- 播放流
graph TD; Playback-->DACL; Playback-->DACR; DACL-->LINEOUTL-Output-Select; LINEOUTL-Output-Select-->LINEOUTL; DACR-->LINEOUTR-Output-Select; LINEOUTR-Output-Select-->LINEOUTR; LINEOUTL-->LINEOUT; LINEOUTR-->LINEOUT;
- 录音流
graph TD; MIC1-->MIC1-Input-Select-->ADC1-->Capture; MIC2-->MIC2-Input-Select-->ADC2-->Capture; MIC3-->MIC3-Input-Select-->ADC3-->Capture;
驱动配置
驱动配置在 menuconfig 中在下列位置
Drivers Options ---> soc related device drivers ---> SOUND Devices ---> [*] Sound card support AllWinner CODEC drivers ---> [*] Allwinner AudioCodec support Allwinner AudioCodec Choose ---> [*] Allwinner AudioCodec DAC Support [*] Allwinner AudioCodec ADC Support
其中
Allwinner AudioCodec DAC Support
是 DAC 模块,Allwinner AudioCodec ADC Support
是 ADC 模块。参数配置如下:
[audiocodec] dacl_vol = 129 //dac左声道硬件数字音量 dacr_vol = 129 //dac右声道硬件数字音量 lineout_vol = 5 //lineout硬件数字音量 lineoutl_en = 1 //lineout左声道使能 lineoutr_en = 0 //lineout右声道禁用 adc1_vol = 129 //adc1硬件数字音量 adc2_vol = 129 //adc2硬件数字音量 adc3_vol = 129 //adc3硬件数字音量 mic1_gain = 19 //mic1硬件增益 mic2_gain = 19 //mic2硬件增益 mic3_gain = 0 //mic3硬件增益 mic1_en = 1 //mic1使能 mic2_en = 1 //mic2使能 mic3_en = 1 //mic3使能 mad_bind_en = 0 //mad禁用 pa_pin_msleep = 10 //pa开启延时 pa_pin = port:PB3<1><default><1><1> //pa引脚配置 capture_cma = 30720 //录音最大缓存配置
声卡控件
声卡控件可以通过
AudioCodec-DAC
命令查看。# AudioCodec-DAC Card Name:audiocodecdac. numid=0, name='DACL dig volume' value=129, min=0, max=255 numid=1, name='DACR dig volume' value=129, min=0, max=255 numid=2, name='LINEOUTL switch' value=on, enum=off on numid=3, name='LINEOUTR switch' value=on, enum=off on # AudioCodec-ADC Card Name:audiocodecadc. numid=0, name='bind mad function' value=unbound, enum=unbound mad_bind numid=1, name='lpsd channel sel function' value=0th_chan, enum=0th_chan 1st_chan 2nd_chan 3rd_chan 4th_chan 5th_chan 6 th_chan 7th_chan numid=2, name='mad standby channel sel function' value=Zero_Chan, enum=Zero_Chan Two_Chan Three_Chan Four_Chan numid=3, name='mad standby control' value=RESUME, enum=RESUME SUSPEND numid=4, name='MIC1 volume' value=31, min=0, max=31 numid=5, name='MIC2 volume' value=31, min=0, max=31 numid=6, name='MIC3 volume' value=31, min=0, max=31 numid=7, name='MIC1 switch' value=on, enum=off on numid=8, name='MIC2 switch' value=on, enum=off on numid=9, name='MIC3 switch' value=on, enum=off on
控件名称与功能如下表所示:
控件名称 功能 数值 DACL dig volume DACL 数字音量调节 0->255 (-64->63dB) DACR dig volume DACR 数字音量调节 0->255 (-64->63dB) MIC1 volume MIC1 增益调节 0->31 (0,6,6,6,9->36dB) MIC2 volume MIC2 增益调节 0->31 (0,6,6,6,9->36dB) MIC3 volume MIC3 增益调节 0->31 (0,6,6,6,9->36dB) LINEOUTL switch LINEOUTL 输出开关 off;on LINEOUTR switch LINEOUTR 输出开关 off;on MIC1 switch MIC1 输入开关 off;on MIC2 switch MIC2 输入开关 off;on MIC3 switch MIC3 输入开关 off;on 使用方法
假设 AudioCodec-DAC 声卡序号为 0,AudioCodec-ADC 声卡序号为 1。
录音
用 MIC1 录音,并将数据保存在 /data/test.wav 文件
# 打开 MIC1 switch ~# amixer -c 1 set 7 1 Card Name: audiocodecadc. numid=7, name='MIC1 switch' value=on, enum=off on # 执行录音操作 ~# arecord /data/test.wav -D hw:audiocodecadc -c 2 -r 16000 -d 5 -p 320 -b 1280 card: hw:audiocodecadc period_size: 320 buffer_size: 1280 malloc rest=320000 please wait...writing data(320000 bytes) into /data/test.wav write finish... riffType: RIFF waveType: WAVE channels: 2 rate: 16000 bits: 16 align: 4 data size: 320000
播放
播放 /data/test.wav 音频文件
# 打开LINEOUTL/R switch ~# amixer -c 0 set 2 1 Card Name: audiocodecdac. numid=2, name='LINEOUTL switch' value=on, enum=off on ~# amixer -c 0 set 3 1 Card Name: audiocodecdac. numid=3, name='LINEOUTR switch' value=on, enum=off on # 执行播放操作 ~# aplay /data/test.wav -D hw:audiocodecdac -p 320 -b 1280 riffType: RIFF waveType: WAVE channels: 2 rate: 16000 bits: 16 align: 4 data size: 320000
I2S/PCM
数字音频信号传输标准 I2S/PCM
驱动特性
- 支持多种采样率格式
- 播放:8~384kHz
- 录音:8~384kHz
- 支持多通道播放和录音
- 播放:1~16
- 录音:1~16
- 支持16/20/24/32bit 数据精度(硬件支持8/12/16/20/24/28/32bit)
- 支持 5 种TDM 模式
- I2S standard mode
- Left-justified mode
- Right-justified mode
- DSP-A mode (short frame PCM mode)
- DSP-B mode (long frame PCM mode)
- 支持loopback 回环模式
- 支持同时playback 和capture (全双工模式)
- 支持硬件重采样
- 支持多声卡同源播放
- 支持多声卡同步录音
音频通路
- 播放流
graph TD; Playback-->I2S-DOUT
- 录音流
graph TD; I2S-DIN-->Capture
- 回环流
graph TD; Playback-->Capture
引脚模式配置
[daudio0] pin_mclk = port:PA23<2><0><1><default> pin_bclk = port:PA20<2><0><1><default> pin_lrck = port:PA19<2><0><1><default> pin_dout0 = port:PA22<2><0><1><default> pin_din0 = port:PA21<2><0><1><default> tdm_num = 0 daudio_master = 4 audio_format = 1 signal_inversion = 1 pcm_lrck_period = 64 slot_width_select = 32 msb_lsb_first = 0 frametype = 0 tx_data_mode = 0 rx_data_mode = 0 tdm_config = 1 mclk_div = 2 rx_sync_en = 0 rx_sync_ctl = 0
配置项说明:
配置项名称 配置项说明 pin_mclk MCLK 引脚的设置 pin_bclk BCLK 引脚的设置 pin_lrck LRCK 引脚的设置 pin_dout0 DOUT0 引脚的设置 pin_din0 DIN0 引脚的设置 tdm_num I2S 使用数量的设置 daudio_master 设备主从模式的设置<br />1:BCLK、LRCK 由外部 CODEC 提供;<br />2:BCLK 由 SoC 提供,LRCK 由外部 CODEC 提供;<br />3:BCLK 由外部 CODEC 提供,LRCK 由 SoC 提供;<br />4:BCLK、LRCK 由 SoC 提供 audio_format 5 种 TDM 模式的设置,标准模式为 1、左对齐模式为 2、右对齐模式为 3、长帧模式为 4、短帧模式为 5 signal_inversion BCLK 和 MCLK 信号的翻转选择,默认都不翻转,为 1,MCLK 翻转为 2,BCLK 为 3,都翻转则为 4 pcm_lrck_period LRCK 的宽度,可选项: 16/32/64/128/256 slot_width_select slot 的宽度,可选项: 8/16/32 msb_lsb_first 大小端的设置,0 为大端,1 为小端 frametype 长帧模式和短帧模式的选择,0 为短帧,1 为长帧 tx_data_mode 发送端数据格式的选择,0 为 16 位线性 pcm 数据 rx_data_mode 接收端数据格式的选择,0 为 16 位线性 pcm 数据 tdm_config 设置 TDM 模式,0 为 DSP 的两个模式,1 为 I2S 的三种模式 mclk_div MCLK 分频系数,为 0 时无输出,可选项: 1/2/4/6/8/12/16/24/32/48/64/96/128/176/192 rx_sync_en 同步录音功能的使能 rx_sync_ctl 设置同步录音功能是否透出控件开关 驱动配置
驱动配置在 menuconfig 中在下列位置
Drivers Options ---> soc related device drivers ---> SOUND Devices ---> [*] Sound card support Platform(Audio Interface) drivers ---> [*] Allwinner Digital Audio Support Allwinner Digital Audio Choose ---> [*] Allwinner Daudio0 Support [*] Allwinner Daudio1 Support [*] Allwinner Daudio2 Support [*] Allwinner Daudio3 Support
配置项说明
-
Allwinner Daudio0 Support
- I2S/PCM0 模块
-
Allwinner Daudio1 Support
- I2S/PCM1 模块
-
Allwinner Daudio2 Support
- I2S/PCM2 模块
-
Allwinner Daudio3 Support
- I2S/PCM3 模块
声卡控件
控件列表:
Card Name:snddaudio0. numid=0, name='tx hub mode' value=Off, enum=Off On numid=1, name='rx sync mode' value=Off, enum=Off On numid=2, name='loopback debug' value=Off, enum=Off On numid=3, name='sunxi daudio asrc function' value=Off, enum=Off On
控件名称与功能如下表所示:
控件名称 功能 数值 tx hub mode 同源播放开关 Off;On rx sync mode 同步采样开关 Off;On loopback debug 内部回录开关 Off;On sunxi daudio asrc function 重采样开关 Off;On 使用方法
假设 snddaudio0 声卡序号为 2
录音
~# arecord /data/test.wav -D hw:snddaudio0 card: hw:snddaudio0 period_size: 1024 buffer_size: 4096 please set capture duration.. please wait...writing data(0 bytes) into /data/test.wav write finish... riffType: RIFF waveType: WAVE channels: 3 rate: 16000 bits: 16 align: 6 data size: 0
播放
~# aplay /data/test.wav -D hw:snddaudio0 riffType: RIFF waveType: WAVE channels: 3 rate: 16000 bits: 16 align: 6 data size: 0
snddaudio0 声卡内部回环,播录音频格式需保持一致
# 1.打开 loopback 开关 ~# amixer -c 2 set 2 1 Card Name:snddaudio0. numid=2, name='loopback debug' value=On, enum=Off On # 2.后台播放音频 ~# fork aplay /data/test.wav -D hw:snddaudio0 riffType: RIFF waveType: WAVE channels: 3 rate: 16000 bits: 16 align: 6 data size: 0 # 3.执行录音操作 ~# arecord /data/test.wav -D hw:snddaudio0 card: hw:snddaudio0 period_size: 1024 buffer_size: 4096 please set capture duration.. please wait...writing data(0 bytes) into /data/test.wav write finish... riffType: RIFF waveType: WAVE channels: 3 rate: 16000 bits: 16 align: 6 data size: 0
S/PDIF
S/PDIF 数字音频接口
驱动特性
- 支持多种采样率格式
- 播放:22.05~192kHz
- 录音:22.05~192kHz
- 支持多通道播放和录音
- 播放:1~2
- 录音:1~2
- 支持 16/20/24/32bit 数据精度(硬件支持 16/20/24bit)
- 支持 loopback 回环模式
- 支持同时 playback 和 capture (全双工模式)
- 支持 IEC-60958 协议
- 支持 IEC-61937 协议
- 支持多声卡同源播放
- 支持透传播放
音频通路
- 播放通路
graph TD; Playback-->SPDIF-DOUT
- 录音通路
graph TD; SPDIF-DIN-->Capture
驱动配置
驱动配置在 menuconfig 中在下列位置
Drivers Options ---> soc related device drivers ---> SOUND Devices ---> [*] Sound card support Platform(Audio Interface) drivers ---> [*] Allwinner SPDIF Support
配置项说明
- Allwinner SPDIF Support
- SPDIF 模块
声卡控件
控件列表:
Card Name:sndspdif. numid=0, name='spdif audio format function' value=PCM, enum=PCM DTS numid=1, name='spdif rx data type' value=IEC-60958, enum=IEC-60958 IEC-61937 numid=2, name='spdif audio hub mode' value=Disabled, enum=Disabled Enabled numid=3, name='sunxi spdif loopback debug' value=0, min=0, max=1
控件名称 功能 数值 spdif audio format function 设置音频数据格式 PCM DTS spdif rx data type 设置传输协议 IEC-60958 IEC-61937 spdif audio hub mode 同源输出开关 Disabled : Enabled sunxi spdif loopback debug 内部回录开关 0:关闭 1:打开 使用方法
播放
# 播放音频 ~# aplay /data/test.wav -D hw:sndspdif riffType: RIFF waveType: WAVE channels: 2 rate: 16000 bits: 16 align: 4 data size: 320000
DMIC
双/立体声数字麦克风接口。
驱动特性
- 支持多种采样率格式
- 8~48kHz
- 支持多通道录音
- channels:1~8
- 支持16/24bit 数据精度
- 支持硬件HPF 算法
- 支持多声卡同步录音
音频通路
- 录音流
graph TD; DMIC-DIN-->Capture
驱动配置
驱动配置在 menuconfig 中在下列位置
Drivers Options ---> soc related device drivers ---> SOUND Devices ---> [*] Sound card support Platform(Audio Interface) drivers ---> [*] Allwinner DMIC Support
配置项说明
- Allwinner DMIC Support
- DMIC 模块
使用方法
# 录音 ~# arecord /data/test.wav -D hw:snddmic -c 2 -r 16000 -d 5 -p 320 -b 1280 card: hw:snddmic period_size: 320 buffer_size: 1280 malloc rest= 320000 please set capture duration.. please wait...writing data(0 bytes) into /data/test.wav write finish... riffType: RIFF waveType: WAVE channels: 2 rate: 16000 bits: 16 align: 4 data size: 320000
MAD
麦克风激活检测(Microphone Activity Detection,MAD),用于语音唤醒方案
驱动特性
- 支持AudioCodec、DMIC 音频传输接口;
- 支持16kHz, 48kHz 采样率,固定16bit;
- 支持一块64KB 的SRAM,可用于保存音频数据;
- 支持基于能量识别的语音检测模块LPSD(只支持单通道,16bit 数据,16kHz 采样率)。
音频通路
- 唤醒状态,正常录音
graph TD; ADC --> rx_fifo --> DMA --> MEM
- 进入休眠,语言能量检测
graph TD; ADC --> rx_fifo --> mad_sram
- 触发唤醒,继续录音
graph TD; ADC --> rx_fifo --> DMA --> MEM
驱动配置
驱动配置在 menuconfig 中在下列位置
Drivers Options ---> soc related device drivers ---> SOUND Devices ---> [*] Sound card support Platform(Audio Interface) drivers ---> [*] Allwinner MAD Support
需要同时使能录音源模块驱动才可正常工作,如录音源为AudioCodec,则需使能AudioCodec 驱动。
声卡控件
?> 以mad 绑定AudioCodec 声卡为例。
控件列表:
Card Name:audiocodecadc. numid=0, name='bind mad function' value=unbound, enum=unbound mad_bind numid=1, name='lpsd channel sel function' value=0th_chan, enum=0th_chan 1st_chan 2nd_chan 3rd_chan 4th_chan 5th_chan 6th_chan 7th_chan numid=2, name='mad standby channel sel function' value=Zero_Chan, enum=Zero_Chan Two_Chan Three_Chan Four_Chan numid=3, name='mad standby control' value=RESUME, enum=RESUME SUSPEND numid=4, name='MIC1 volume' value=31, min=0, max=31 numid=5, name='MIC2 volume' value=31, min=0, max=31 numid=6, name='MIC3 volume' value=31, min=0, max=31 numid=7, name='MIC1 switch' value=on, enum=off on numid=8, name='MIC2 switch' value=on, enum=off on numid=9, name='MIC3 switch' value=on, enum=off on
如上,控件0-3 为mad 模块控件,控件4-9 为AudioCodec 模块控件。
控件名称 功能 数值 bind mad function 是否绑定 MAD 功能 0: 不绑定; 1: 绑定 lpsd channel sel Function 选择作为能量唤醒的通道 0: 通道 0; 1: 通道 1; mad_standby channel sel Function 设定休眠时 mad 录音通道数 Zero_Chan: 实际录音通道 mad standby control 休眠测试使用 Two_Chan: 只录制两通道Off;On 使用方法
语音唤醒
安静环境下,开启录音后进入休眠暂停录音,此时外界制造音量较大的声音,mad 模块触发唤醒
cpu,并自动继续录音。- 录音前控件设置
假设使用AudioCodec 模块MIC1 单通道作为能量能量唤醒通道。
~# amixer -c 0 set "bind mad function" 1 ~# amixer -c 0 set "lpsd channel sel function" 0 ~# amixer -c 0 set "mad standby channel sel function" 0 ~# amixer -c 0 set "MIC2 switch" 0 ~# amixer -c 0 set "MIC3 switch" 0
- 开启录音
# 录音参数为单通道、16kHz采样率、16bit。 ~# fork arecord -D hw:audiocodecadc -c 1 -r 16000 -d 20 -p 320 -b 1280 /data/test.wav
- 进入休眠
# 如使用休眠测试命令进入休眠,此时arecord 录音将被暂停 ~# rpccli arm standby
- 外界声音唤醒
制造较大音量的外界声音,系统唤醒,并自动恢复arecord 录音,查看录音文件可录到外界唤醒声音。
语音唤醒能量唤醒阈值参数
语音唤醒能量唤醒阈值参数能量唤醒模块lpsd,识别能量主要有两个方向,瞬时能量和累计能量(前者比如是关门声,后者比如是不断说话)能量检测参数配置在
lichee/rtos-hal/hal/source/sound/platform/sunximad.c
修改。参数名称 功能 可设范围 推荐值 lpsd_th 累积语音能量检测阈值 0x0 - 0xFFFF 1200 lpsd_rrun 语音能量检测开始时间 0x0 - 0xFF 145 lpsd_rstop 语音能量检测结束时间 0x0 - 0xFF 170 -
瞬时能量检测参数,主要是 lpsd_rrun 和 lpsd_rstop。
-
一般只对 stop 值进行修改;
-
如果录音数据经常缺少唤醒词的第一个字,则可以尝试降低 stop 值,可以有效提高唤醒词数据的完整性。但同时会提高误唤醒率,环境噪音也会很容易触发能量检测,唤醒系统;
-
如果想要降低误唤醒率 (环境噪音造成唤醒),则可以尝试提高 stop 值。同样的,这会导致一些唤醒词录音数据不完整,例如一些音量较低,音调较低的语料;
-
唤醒词识别率以及误唤醒率无法同时兼得,客户需要根据实际需求、场景,权衡配置参数;
-
-
累积能量检测参数,主要是 lpsd_th。
- 建议使用默认值 1200,建议修改范围 50~1200。
模块接口说明
源文件列表
platform
层
lichee/rtos-hal/hal/source/sound/platform . ├── Kconfig ├── Makefile ├── objects.mk ├── sun8iw19-daudio.c ├── sunxi-daudio.c # platform 层 –> I2S/PCM,负责 I2S/PCM 模块硬件参数、DMA 相关配置。 ├── sunxi-daudio.h # platform 层 –> I2S/PCM,负责 I2S/PCM 模块硬件参数、DMA 相关配置。 ├── sunxi-dmic.c ├── sunxi-dmic.h ├── sunxi-dummy-cpudai.c # platform 层 –> AudioCodec,负责 AudioCodec 模块 DMA 相关配置。 ├── sunxi-mad.c ├── sunxi-mad.h ├── sunxi-pcm.c # platform 层 –> 公共部分,负责音频流传输,使用 DMA 方式,提供注册 platform 设备的公共函数。 ├── sunxi-pcm.h # platform 层 –> 公共部分,负责音频流传输,使用 DMA 方式,提供注册 platform 设备的公共函数。 ├── sunxi-spdif.c # platform 层 –> SPDIF,负责 SPDIF 模块硬件参数、DMA 相关配置。 ├── sunxi-spdif.h # platform 层 –> SPDIF,负责 SPDIF 模块硬件参数、DMA 相关配置。 ├── platforms ├── daudio-sun20iw2.h # platform 层 –> DMIC、SPDIF、DAUDIO 时钟框架代码 ├── dmic-sun20iw2.h # platform 层 –> DMIC、SPDIF、DAUDIO 时钟框架代码 ├── mad-sun20iw2.c # platform 层 –> DMIC、SPDIF、DAUDIO 时钟框架代码 ├── mad-sun20iw2.h # platform 层 –> DMIC、SPDIF、DAUDIO 时钟框架代码 ├── spdif-sun20iw2.h # platform 层 –> DMIC、SPDIF、DAUDIO 时钟框架代码
codec
层
lichee/rtos-hal/hal/source/sound/codecs . ├── ac101s.c # codec 层 –> AC101s codec ├── ac101s.h # codec 层 –> AC101s codec ├── dummy_codec.c ├── sun20iw2-codec-adc.c # codec 层 –> AudioCodec,负责 AudioCodec 模块硬件参数配置 ├── sun20iw2-codec.c # codec 层 –> AudioCodec,负责 AudioCodec 模块硬件参数配置 ├── sun20iw2-codec-dac.c # codec 层 –> AudioCodec,负责 AudioCodec 模块硬件参数配置 ├── sun20iw2-codec.h # codec 层 –> AudioCodec,负责 AudioCodec 模块硬件参数配置 ├── sunxi-codec.h # codec 层 –> 公共部分 ├── sunxi_rw_func.c
machine
层
lichee/rtos/projects/xxx/src . ├── card_default.c # 负责 platform 层和 codec 层绑定。
软件框图
关键数据结构
pcm 数据类结构体
sunxi_dma_params
定义audio DAI DMA 相关参数。
struct sunxi_dma_params { char *name; dma_addr_t dma_addr; uint32_t src_maxburst; uint32_t dst_maxburst; uint8_t dma_drq_type_num; };
- platform 类结构体
DAUDIO
I2S/PCM 模块总结构体,包含基础平台资源、特定功能私有参数
struct sunxi_daudio_info { struct snd_platform *platform; struct sunxi_daudio_clk clk; struct daudio_pinctrl *pinctrl; uint8_t pinctrl_num; struct pa_config *pa_cfg; uint8_t pa_cfg_num; struct sunxi_daudio_param param; struct sunxi_dma_params playback_dma_param; struct sunxi_dma_params capture_dma_param; uint8_t global_enable; unsigned int hub_mode; bool playback_en; bool capture_en; int asrc_en; };
SPDIF
/* 用于描述引脚*/ typedef struct { gpio_pin_t gpio; unsigned int mux; } spdif_gpio; /* 用于描述SPDIF 引脚*/ typedef struct { spdif_gpio out; spdif_gpio in; } spdif_gpio_t; /* 包含了DMA 相关参数以及时钟结构体*/ struct sunxi_spdif { struct sunxi_dma_params playback_dma_param; struct sunxi_dma_params capture_dma_param; struct sunxi_spdif_clk clk; };
DMIC
/* 用于描述引脚*/ typedef struct { gpio_pin_t gpio; int mux; } dmic_gpio; /* 用于描述DMIC 引脚*/ typedef struct { dmic_gpio clk; dmic_gpio din0; dmic_gpio din1; dmic_gpio din2; dmic_gpio din3; } dmic_gpio_t; /* 用于描述DMIC 采样率*/ struct dmic_rate { unsigned int samplerate; unsigned int rate_bit; }; /* 包含了DMA 相关参数,通道数以及时钟结构体*/ struct sunxi_dmic { struct sunxi_dma_params capture_dma_param; u32 chanmap; struct sunxi_dmic_clk clk; };
codec 类结构体
AudioCodec
AudioCodec 模块总结构体,包含基础平台资源、特定功能私有参数
struct sunxi_codec_info { struct snd_codec *codec; void *codec_base_addr; struct sunxi_codec_clk clk; struct sunxi_codec_param param; #ifdef CONFIG_SND_PLATFORM_SUNXI_MAD int capturing; struct sunxi_mad_priv mad_priv; #endif };
软件重要接口
仅说明自定义软件接口,alsa 框架内部接口不做说明。
pcm 相关接口
创建 pcm 设备
函数原型:
int sunxi_pcm_new(struct snd_pcm *pcm)
参数:
- pcm: pcm设备信息
返回值:
- 0: 成功
- 其他:失败
释放 pcm 设备
函数原型
void sunxi_pcm_free_dma_buffer(struct snd_pcm *pcm, int stream)
参数:
- pcm: pcm设备信息
- stream: pcm 流信息
返回值: 无
开启 pcm 设备
函数原型:
int sunxi_pcm_open(struct snd_pcm_substream *substream)
参数:
- substream: pcm 子流信息
返回值:
- 0: 成功
- 其他:失败
设置 pcm 设备参数
函数原型:
int sunxi_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
参数:
- substream: pcm 子流信息
- params: pcm 硬件参数
返回值:
- 0: 成功
- 其他:失败
触发 pcm 设备运行
函数原型:
int sunxi_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
参数:
- substream: pcm 子流信息
- cmd: 触发命令
返回值:
- 0: 成功
- 其他:失败
获取 pcm 设备帧点
函数原型:
snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
参数:
- substream: pcm 子流信息
返回值:
- snd_pcm_uframes_t:当前DMA 缓冲指针
Platform 层接口
AudioCodec接口
初始化 DMA 参数
函数原型:
int sunxi_cpudai_platform_probe(struct snd_platform *platform)
参数:
- platform: platform 信息
返回值:
- 0: 成功
- 其他:失败
更新设置 DMA 参数
函数原型:
int sunxi_cpudai_startup(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
I2S/PCM接口
I2S/PCM 模块休眠(保存寄存器、关闭时钟)
函数原型:
int sunxi_daudio_suspend(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
I2S/PCM 模块唤醒(开启时钟、初始化模块、恢复寄存器)
函数原型:
int sunxi_daudio_resume(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
设置模块BCLK 分频系数
函数原型:
int sunxi_daudio_set_clkdiv(struct snd_dai *dai, int clk_id, int clk_div)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- clk_div: clk 分频系数
返回值:
- 0: 成功
- 其他:失败
设置模块工作时钟
函数原型:
int sunxi_daudio_set_sysclk(struct snd_dai *dai, int clk_id, unsigned int freq, int dir)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- freq: 时钟频率
- dir: 时钟输出方向
返回值:
- 0: 成功
- 其他:失败
设置模块I2S 格式
函数原型:
int sunxi_daudio_set_fmt(struct snd_dai *dai, unsigned int fmt)
参数:
- dai: cpu dai 信息
- fmt: I2S 格式信息
返回值:
- 0: 成功
- 其他:失败
设置模块开启工作资源(DMA 参数、组件功能等)
函数原型:
int sunxi_daudio_startup(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
设置模块硬件参数
函数原型:
int sunxi_daudio_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- params: 硬件参数
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
清除模块fifo
函数原型:
int sunxi_daudio_prepare(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
触发模块工作
函数原型:
int sunxi_daudio_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- cmd: 触发命令
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
设置模块关闭工作资源(组件功能等)
函数原型:
void sunxi_daudio_shutdown(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
SPDIF
初始化cpu dai (DMA、模块寄存器)
函数原型:
int sunxi_spdif_dai_probe(struct snd_dai *dai)
参数:
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
模块休眠(保存寄存器、关闭时钟)
函数原型:
int sunxi_spdif_suspend(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
模块唤醒(开启时钟、初始化模块、恢复寄存器)
函数原型:
int sunxi_spdif_resume(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
设置模块分频系数
函数原型:
int sunxi_spdif_set_clkdiv(struct snd_dai *dai, int clk_id, int clk_div)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- clk_div: clk 分频系数
返回值:
- 0: 成功
- 其他:失败
设置模块工作时钟
函数原型:
int sunxi_spdif_set_sysclk(struct snd_dai *dai, int clk_id, unsigned int freq, int dir)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- freq: 时钟频率
- dir: 时钟输出方向
返回值:
- 0: 成功
- 其他:失败
设置模块开启工作资源(DMA 参数、组件功能等)
函数原型:
int sunxi_spdif_startup(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
设置模块硬件参数
函数原型:
int sunxi_spdif_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- params: 硬件参数
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
清除模块fifo,清除中断
函数原型:
int sunxi_spdif_prepare(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
触发模块工作
函数原型:
int sunxi_spdif_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- cmd: 触发命令
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
DMIC
初始化cpu dai (DMA、模块寄存器)
函数原型:
int sunxi_dmic_dai_probe(struct snd_dai *dai)
参数:
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
模块休眠(保存寄存器、关闭时钟)
函数原型:
int sunxi_dmic_suspend(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
模块唤醒(开启时钟、恢复寄存器)
函数原型:
int sunxi_dmic_resume(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
设置模块 pll clk
函数原型:
int sunxi_dmic_set_sysclk(struct snd_dai *dai, int clk_id, unsigned int freq, int dir)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- freq: 时钟频率
- dir: 时钟输出方向
返回值:
- 0: 成功
- 其他:失败
设置模块开启工作资源(DMA 参数、组件功能等)
函数原型:
int sunxi_dmic_startup(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
设置模块硬件参数
函数原型:
int sunxi_dmic_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- params: 硬件参数
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
清除模块 fifo,清除中断
函数原型:
int sunxi_dmic_prepare(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
触发模块工作
函数原型:
int sunxi_dmic_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- cmd: 触发命令
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
Codec 层
AudioCodec(ADC/DAC)
模块休眠(保存寄存器、关闭时钟)
函数原型:
int sunxi_codec_suspend(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
模块唤醒(开启时钟、恢复寄存器)
函数原型:
int sunxi_codec_resume(struct pm_device *dev, suspend_mode_t mode)
参数:
- dev: 设备信息
- mode: 休眠模式
返回值:
- 0: 成功
- 其他:失败
设置模块时钟
函数原型:
int sunxi_codec_set_sysclk(struct snd_dai *dai, int clk_id, unsigned int freq, int dir)
参数:
- dai: cpu dai 信息
- clk_id: clk 辅助信息
- freq: 时钟频率
- dir: 时钟输出方向
返回值:
- 0: 成功
- 其他:失败
设置模块硬件参数
函数原型:
int sunxi_codec_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- params: 硬件参数
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
清除模块fifo,清除中断
函数原型:
int sunxi_codec_prepare(struct snd_pcm_substream *substream, struct snd_dai *dai)
参数:
- substream: pcm 子流信息
- dai: cpu dai 信息
返回值:
- 0: 成功
- 其他:失败
触发模块工作
函数原型:
int sunxi_codec_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_dai *dai)
参数:
- name: 声卡名称
- codec: codec 设备信息
- platform_type: platform 层设备类型
返回值:
- 0: 成功
- 其他:失败
软件调试接口
模块 接口 命令 DMIC cmd_dmic_dump
cmd_dmic_dump
SPDIF cmd_spdif_dump
cmd_spdif_dump
DAUDIO cmd_daudio_dump
cmd_daudio_dump
-
【R128】应用开发案例——中断方式驱动旋转编码器
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
中断方式驱动旋转编码器
本文案例代码 下载地址 中断方式驱动旋转编码器案例代码 https://www.aw-ol.com/downloads?cat=24 旋转编码器是一种位置传感器,可将旋钮的角位置(旋转)转换为用于确定旋钮旋转方向的输出信号。
由于其坚固性和良好的数字控制;它们被用于许多应用中,包括机器人技术,CNC机器和打印机。
旋转编码器有两种类型-绝对式和增量式。绝对编码器为我们提供旋钮的精确位置(以度为单位),而增量编码器报告轴已移动了多少增量。
编码器内部是一个槽形磁盘,该磁盘连接到公共接地引脚C以及两个接触针A和B。旋转旋钮时,A和B根据旋转旋钮的方向以特定顺序与公共接地引脚C接触。
当它们接触公共接地时,它们会产生信号。当一个引脚先于另一引脚接触时,这些信号就会彼此错开90°。这称为正交编码。
顺时针旋转旋钮时,首先连接A引脚,然后连接B引脚。逆时针旋转旋钮时,首先连接B引脚,然后连接A引脚。
通过跟踪每个引脚何时与地面连接或与地面断开,我们可以使用这些信号变化来确定旋钮的旋转方向。您可以通过在A更改状态时观察B的状态来做到这一点。
我们搭建电路,如下:
引脚 按键 PA24 编码器 CLK PA25 编码器 DT PA29 编码器 SW(未使用) 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
勾选 GPIO 驱动
mrtos_menuconfig
找到下列驱动Drivers Options ---> soc related device drivers ---> GPIO devices ---> [*] enable GPIO driver [*] enbale GPIO hal APIs Test command
编写程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
引入头文件
#include <hal_gpio.h>
使用 GPIO 配置引脚
配置 GPIO 的上下拉状态
使用
hal_gpio_set_pull(gpio_pin_t pin, gpio_pull_status_t pull);
来设置。这里我们设置PA25
引脚为默认上拉状态。hal_gpio_set_pull(GPIOA(25), GPIO_PULL_UP);
配置 GPIO 输入输出模式
使用
hal_gpio_set_direction(gpio_pin_t pin, gpio_direction_t direction);
来设置 GPIO 的输入输出模式,这里配置为输入模式。hal_gpio_set_direction(GPIOA(25), GPIO_DIRECTION_INPUT);
配置 GPIO 的 MUX 功能
GPIO 通常有多种功能,需要配置 MUX 选择需要的功能,使用
hal_gpio_pinmux_set_function(gpio_pin_t pin, gpio_muxsel_t function_index);
来设置 GPIO 的复用功能,这里配置为GPIO 输入模式(GPIO_MUXSEL_IN
)hal_gpio_pinmux_set_function(GPIOA(25), GPIO_MUXSEL_IN);
获取 GPIO 的电平
使用
int hal_gpio_get_data(gpio_pin_t pin, gpio_data_t *data);
来获取 GPIO 的电平gpio_data_t gpio_data; hal_gpio_get_data(GPIOA(25), &gpio_data);
申请配置中断
使用
hal_gpio_to_irq
方法来申请中断号。hal_gpio_irq_request
绑定中断服务,hal_gpio_irq_enable
启用中断。这里配置一个// 存放中断号 uint32_t irq_clk; // 申请中断号 ret = hal_gpio_to_irq(ENC_CLK, &irq_clk); if (ret < 0){ printf("gpio to irq error, irq num:%d error num: %d\n", irq_clk, ret); } // 绑定中断处理函数 ret = hal_gpio_irq_request(irq_clk, gpio_irq_encode, IRQ_TYPE_EDGE_BOTH, NULL); if (ret < 0){ printf("request irq error, irq num:%d error num: %d\n", irq_clk, ret); } // 启用中断 ret = hal_gpio_irq_enable(irq_clk); if (ret < 0){ printf("request irq error, error num: %d\n", ret); }
完整代码
#include <stdio.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include "interrupt.h" #include <portmacro.h> #include <cli_console.h> #include <aw_version.h> #include <hal_time.h> #include <hal_gpio.h> #include "FreeRTOS.h" #include "task.h" #include "tinatest.h" extern int amp_init(void); // 定义旋转编码器的引脚 #define ENC_CLK GPIOA(24) #define ENC_DT GPIOA(25) #define ENC_SW GPIOA(29) // 相关全局变量存储 int encode_counter = 0; int encode_current_clk; int encode_lask_clk; int current_dir = 0; // 编码器中断处理函数 static hal_irqreturn_t gpio_irq_encode(void *data) { // 获取引脚的高低电平状态 gpio_data_t clk_value = GPIO_DATA_LOW; gpio_data_t dt_value = GPIO_DATA_LOW; hal_gpio_get_data(ENC_DT, &dt_value); hal_gpio_get_data(ENC_CLK, &clk_value); // 判断当前数据状态 encode_current_clk = clk_value; if (encode_current_clk != encode_lask_clk && encode_current_clk == 1){ // 判断正反转 if (dt_value != encode_current_clk) { // 正转 encode_counter ++; current_dir = 1; } else { // 反转 encode_counter --; current_dir = -1; } printf("Direction = %d, Counter = %d\n", current_dir, encode_counter); } // 刷新当前状态 encode_lask_clk = encode_current_clk; return 0; } void cpu0_app_entry(void *param) { int ret = 0; // 初始化系统资源 amp_init(); // A24 -> CLK, A25 -> DT. A29 -> SW hal_gpio_set_pull(ENC_CLK, GPIO_PULL_DOWN_DISABLED); hal_gpio_set_direction(ENC_CLK, GPIO_DIRECTION_INPUT); hal_gpio_pinmux_set_function(ENC_CLK, GPIO_MUXSEL_IN); // 获取初始编码器 CLK 状态 gpio_data_t clk_data; hal_gpio_get_data(ENC_CLK, &clk_data); encode_lask_clk = clk_data; hal_gpio_set_pull(ENC_DT, GPIO_PULL_DOWN_DISABLED); hal_gpio_set_direction(ENC_DT, GPIO_DIRECTION_INPUT); hal_gpio_pinmux_set_function(ENC_DT, GPIO_MUXSEL_IN); // 存放 CLK,DT 中断号 uint32_t irq_clk, irq_dt; // 申请 ENC_CLK 为中断引脚,跳变触发 ret = hal_gpio_to_irq(ENC_CLK, &irq_clk); if (ret < 0){ printf("gpio to irq error, irq num:%d error num: %d\n", irq_clk, ret); } // 绑定中断处理函数 ret = hal_gpio_irq_request(irq_clk, gpio_irq_encode, IRQ_TYPE_EDGE_BOTH, NULL); if (ret < 0){ printf("request irq error, irq num:%d error num: %d\n", irq_clk, ret); } // 启用中断 ret = hal_gpio_irq_enable(irq_clk); if (ret < 0){ printf("request irq error, error num: %d\n", ret); } // 申请 ENC_DT 为中断引脚,跳变触发 ret = hal_gpio_to_irq(ENC_DT, &irq_dt); if (ret < 0){ printf("gpio to irq error, irq num:%d error num: %d\n", irq_dt, ret); } // 绑定中断处理函数 ret = hal_gpio_irq_request(irq_dt, gpio_irq_encode, IRQ_TYPE_EDGE_BOTH, NULL); if (ret < 0){ printf("request irq error, irq num:%d error num: %d\n", irq_dt, ret); } // 启用中断 ret = hal_gpio_irq_enable(irq_dt); if (ret < 0){ printf("request irq error, error num: %d\n", ret); } vTaskDelete(NULL); }
结果
旋转旋转编码器即可看到计数变化
-
回复: 如何在R128的M33核上使用DWT实现代码块耗时统计或无中断环境下的延迟
@haaland DWT组件全称是Data Watchpoint and Trace,M33处理器中有硬件支持,开发者可以使用它提供的watchpoint比较器以及周期计数器进行代码调试、性能分析等工作。
可以借助DWT的计数器获取处理器的时间信息,计数器的精度是(1 / cpu频率):
(1)使能DWT模块并清0计数器;
(2)使能计数器;
(3)读取计数器值。统计短代码块耗时示例:
#define DWT_CR *(volatile uint32_t *)0xE0001000 #define DWT_CYCCNT *(volatile uint32_t *)0xE0001004 #define DEM_CR *(volatile uint32_t *)0xE000EDFC #define DEM_CR_TRCENA (1 << 24) #define DWT_CR_CYCCNTENA (1 << 0) int dwt_init(void) { DEM_CR |= (uint32_t)DEM_CR_TRCENA; # 使能DWT追踪 DWT_CYCCNT = (uint32_t)0u; # 计数器清0 DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA; # 使能计数器 return 0; } void main(void) { uint32_t ticks; uint32_t told,tnow; uint32_t cpu_freq = get_cpu_freq(); float time_ms; dwt_init(); tcnt = 0; told = (uint32_t)DWT_CYCCNT; 代码块 tnow = (uint32_t)DWT_CYCCNT; /* 计算间隔 */ if(tnow > told) tcnt += tnow - told; else tcnt += UINT32_MAX - told + tnow; time_ms = (float)(tcnt) / (cpufreq / 1000) # 假设经过的时间以ms为单位 }
实现延迟:
根据计算时间的公式,也可以根据期望时间换算出所需要的tcnt计数值,在while中循环读取计数器值直至其增加的计数值达到tcnt,以实现延迟。 -
【R128】基础组件开发指南——RTOS 多媒体编码
RTOS 多媒体编码
介绍 FreeRTOS 下如何使用 xrecorder 的接口来开发录制应用程序,方便录制应用开发人员快速正确地开发,以及录制应用测试人员如何根据该文档对基于 xrecord 的录制应用进行验证测试。
编码支持情况
目前 RTOS 平台多媒体编码应用支持的编码格式分别为:pcm、amr、mp3、speex、opus。
其中 pcm、amr、mp3 可通过 xrecorder 进行编码以及录制;speex 和 opus 可通过第三方示例工程进行编码。
xrecorder 状态图
这张状态转换图清晰地描述了 xrecorder 的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,否则会出错。
Init 状态
Idle 状态:当调用 XRecordCreate() 创建一个 xrecord 时,处于 idle 状态。
Prepared 状态
调用 XRecordPrepare() 函数并返回后,xrecorder 处于 Prepared 状态。在这个状态下说明所有的资源都已经就绪了,调用 XRecordStart() 函数就可以进行录制。
Started 状态
xrecorder prepare 完成后,调用 XRecordStart() 进行录制,当应用开始录制后,xrecorder 就处于 Started 状态,这表明 xrecorder 正在录制文件。
Stopped 状态
Started 状态下均可调用 XrecordStop() 停止 xrecorder,而处于 Stop 状态的 xrecorder 要想重新录制,需要通过 XRecorderPrepare() 回到先前的 Prepared 状态重新开始才可以。
Destroyed 状态
通过 XRecordDestroy() 的方法可以进入 Destroyed 状态,只要 xrecorder 不再被使用,就应当尽快将其 destroy 掉。
接口函数
创建一个 XRecord
XRecord *XRecordCreate()
参数:
- 无
返回值:
- 无
设置录制音频的编码格式
int XRecordSetAudioEncodeType(XRecord *p, XRECODER_AUDIO_ENCODE_TYPE type, XRecordConfig *config)
参数:
- p: 通过 XRecordCreate 创建的 XRecord 指针
- type: 已支持的编码格式
- config: 上层应用对音频属性的配置
返回值:
- 成功: 0; 失败: ‑1
获取指针
获取指向音频设备管理模块的指针,用于录制音频
void XRecordSetAudioCap(XRecord* p, const CaptureCtrl* audioSrc)
参数:
- p: 通过 XRecordCreate 创建的 XRecord 指针
- audioSrc: 由上层应用获取的音频设备管理模块的指针
返回值:
- 无
audioSrc 可在上层应用通过调用 cedarx 的音频设备管理模块的 RTCaptureDeviceCreate 来创建。
设置录制后文件的保存的路径
int XRecordSetDataDstUrl(XRecord* p, const char* pUrl, void* arg, const CdxKeyedVectorT* pHeaders)
参数:
- p: 通过 XRecordCreate 创建的 XRecord 指针
- pUrl:url 地址
返回值:
- 成功:0;失败:‑1
将 XRecord 置为准备状态, 准备 Muxer
int XRecordPrepare(XRecord* p)
参数:
- p:通过 XRecordCreate 创建的 XRecord 指针
返回值:
- 成功:0;失败:‑1
将 XRecord 置为启动状态
int XRecordStart(XRecord* p)
参数:
- p:通过 XRecordCreate 创建的 XRecord 指针
返回值:
- 成功:0;失败:‑1
将 XRecord 置为停止状态
int XRecordStop(XRecord* p)
参数:
- p:通过 XRecordCreate 创建的 XRecord 指针
返回值:
- 成功: 0;失败:‑1
编码数据入队封装
提供接口给下层编码模块,将编码数据放进缓存队列中等待封装
int onAudioDataEnc(XRecord* app, CdxMuxerPacketT* buff)
参数:
- app: xrecorder 的环境句柄;
- buff:编码后的缓存数据
返回值:
- 成功: 0;失败:‑1
销毁一个 XRecord
int XRecordDestroy(XRecord* p)
参数:
- p:通过 XRecordCreate 创建的 XRecord 指针
返回值:
- 成功: 0;失败:‑1
XRecorder 开发流程
XRecordCreate()
//创建一个录制应用XRecordSetAudioCap()
//设置音频采集设备;可先调用RTCaptureDeviceCreate
创建。XRecordSetDataDstUrl()
//设置录制后文件保存位置XRecordSetAudioEncodeType()
//设置音频数据的编码格式XRecordPrepare()
//设置 Muxer,让 xrecorder 进入准备状态XRecordStart()
//开始录制XRecordStop()
//停止录制XRecordDestroy()
//当不需要进行录制的时候,销毁 xrecorder
注意事项
- 在调用 XRecordSetAudioCap 设置音频采集设备之前,需先打开音频采集设备来获取句柄。在 rtos 平台可调用 libcedarx 提供的音频采集设备控制模块 rtosCaptureControl.c 中的
RTCaptureDeviceCreate
来创建句柄。 - recorder 应用未支持暂停录制。
- recorder 的录制时长为调用 XRecordStart 至调用 XRecordStop 之间的时长来决定,因此上层应用需要录制指定时长的音频时,录制的步骤应为调用 XRecordStart,等待指定的时间,调用XRecordStop。
示例代码
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <aw_common.h> #include <console.h> #include "vfs.h" #include "xrecord.h" #define RECORDER_LOGD(msg, arg...) printf("[RECORDER_DBG] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define RECORDER_LOGI(msg, arg...) printf("[RECORDER_INFO] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define RECORDER_LOGW(msg, arg...) printf("[RECORDER_WRN] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define RECORDER_LOGE(msg, arg...) printf("[RECORDER_ERR] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) typedef struct recorder_base recorder_base; typedef struct rec_cfg { XRECODER_AUDIO_ENCODE_TYPE type; int sample_rate; int chan_num; int bitrate; int sampler_bits; } rec_cfg; struct recorder_base { int (*start)(recorder_base *base, const char *url, const rec_cfg *cfg); int (*stop)(recorder_base *base); }; struct ExampleCustomerWriterImpl { CdxWriterT base; vfs_file_t *vfs; }; typedef struct recorder { recorder_base base; XRecord *xrecorder; CaptureCtrl *cap; } recorder; recorder_base *recorder_create(); int recorder_destroy(recorder_base *base); /* Example Customer Writer */ static int __CdxExampleConnect(CdxWriterT *writer) { struct ExampleCustomerWriterImpl *impl; impl = (struct ExampleCustomerWriterImpl *)writer; vfs_unlink("data/record/2.amr"); impl->vfs = vfs_open("data/record/2.amr", VFS_RDWR | VFS_CREAT); if (impl->vfs == NULL) { return -1; } return 0; } static int __CdxExampleRead(CdxWriterT *writer, void *buf, int size) { return 0; } static int __CdxExampleWrite(CdxWriterT *writer, void *buf, int size) { uint32_t write_len; struct ExampleCustomerWriterImpl *impl; impl = (struct ExampleCustomerWriterImpl *)writer; write_len = vfs_write(impl->vfs, buf, size); return write_len; } static long __CdxExampleSeek(CdxWriterT *writer, long moffset, int mwhere) { return 0; } static long __CdxExampleTell(CdxWriterT *writer) { return 0; } static int __CdxExampleClose(CdxWriterT *writer) { struct ExampleCustomerWriterImpl *impl; impl = (struct ExampleCustomerWriterImpl *)writer; vfs_close(impl->vfs); free(impl); return 0; } static const struct CdxWriterOps exampleCustomerWriteOps = { .cdxConnect = __CdxExampleConnect, .cdxRead = __CdxExampleRead, .cdxWrite = __CdxExampleWrite, .cdxSeek = __CdxExampleSeek, .cdxTell = __CdxExampleTell, .cdxClose = __CdxExampleClose }; CdxWriterT *ExampleCustomerWriterCreat() { struct ExampleCustomerWriterImpl *impl; impl = malloc(sizeof(*impl)); if (impl == NULL) { printf("example customer writer create fail.\n"); return NULL; } memset(impl, 0, sizeof(*impl)); impl->base.ops = &exampleCustomerWriteOps; return &impl->base; } /* Main App */ static void showHelp(){ printf("\n"); printf("**************************\n"); printf("* This is a simple audio recoder, when it is started, you can input commands to tell\n"); printf("* what you want it to do.\n"); printf("* Usage: \n"); printf("* cedarx_record amr 10 : this means record 10s amr music\n"); printf("* cedarx_record pcm 10 : this means record 10s pcm music\n"); printf("**************************\n"); } recorder *recorder_singleton = NULL; static int record_start(recorder_base *base, const char *url, const rec_cfg *cfg) { recorder *impl = container_of(base, recorder, base); XRecordConfig audioConfig; if (cfg->type == XRECODER_AUDIO_ENCODE_PCM_TYPE) { audioConfig.nChan = cfg->chan_num; audioConfig.nSamplerate = cfg->sample_rate; audioConfig.nSamplerBits = cfg->sampler_bits; audioConfig.nBitrate = cfg->bitrate; } else if (cfg->type == XRECODER_AUDIO_ENCODE_AMR_TYPE) { audioConfig.nChan = 1; audioConfig.nSamplerate = 8000;//amr-nb 8000Hz amr-wb 16000Hz audioConfig.nSamplerBits = 16; audioConfig.nBitrate = 12200;//amr-nb 12200 amr-wb 23850 } else { audioConfig.nChan = cfg->chan_num; audioConfig.nSamplerate = cfg->sample_rate; audioConfig.nSamplerBits = cfg->sampler_bits; audioConfig.nBitrate = cfg->bitrate; } XRecordSetDataDstUrl(impl->xrecorder, url, NULL, NULL); XRecordSetAudioEncodeType(impl->xrecorder, cfg->type, &audioConfig); XRecordPrepare(impl->xrecorder); XRecordStart(impl->xrecorder); RECORDER_LOGI("record start"); return 0; } static int record_stop(recorder_base *base) { recorder *impl = container_of(base, recorder, base); XRecordStop(impl->xrecorder); return 0; } extern CaptureCtrl* RTCaptureDeviceCreate(); recorder_base *recorder_create() { if (recorder_singleton != NULL) return &recorder_singleton->base; recorder *impl = malloc(sizeof(*impl)); if (impl == NULL) return NULL; memset(impl, 0, sizeof(*impl)); impl->xrecorder = XRecordCreate(); if (impl->xrecorder == NULL) goto failed; impl->cap = (void *)(uintptr_t)RTCaptureDeviceCreate(); if (impl->cap == NULL) goto failed; XRecordSetAudioCap(impl->xrecorder, impl->cap); impl->base.start = record_start; impl->base.stop = record_stop; recorder_singleton = impl; return &impl->base; failed: RECORDER_LOGE("recorder create failed"); if (impl->xrecorder) XRecordDestroy(impl->xrecorder); if (impl) free(impl); return NULL; } int recorder_destroy(recorder_base *base) { recorder *impl = container_of(base, recorder, base); if (impl->xrecorder) { XRecordDestroy(impl->xrecorder); } free(impl); recorder_singleton = NULL; return 0; } static int cedarx_record_test(int argc, char **argv) { recorder_base *recorder; rec_cfg cfg; char music_url[64]; char file_url[64]; CdxWriterT *writer; memset(file_url, 0, 64); if(argc == 3){ if( !strncmp("amr", argv[1], sizeof("amr")-1) ){ cfg.type = XRECODER_AUDIO_ENCODE_AMR_TYPE; snprintf(file_url, 64, "file://data/%ds.amr", atoi(argv[2])); cfg.sample_rate = 8000;//8000 cfg.chan_num = 1;//1 cfg.bitrate = 12200; cfg.sampler_bits = 16; } else if( !strncmp("pcm", argv[1], sizeof("pcm")-1) ){ cfg.type = XRECODER_AUDIO_ENCODE_PCM_TYPE; snprintf(file_url, 64, "file://data/%ds.pcm", atoi(argv[2])); cfg.sample_rate = 8000;//8000 cfg.chan_num = 1;//1 cfg.bitrate = 12200; cfg.sampler_bits = 16; } else if( !strncmp("mp3", argv[1], sizeof("mp3")-1) ){ cfg.type = XRECODER_AUDIO_ENCODE_MP3_TYPE; snprintf(file_url, 64, "file://data/%ds.mp3", atoi(argv[2])); cfg.sample_rate = 16000; cfg.chan_num = 1; cfg.bitrate = 32000; cfg.sampler_bits = 16; } else { printf("now support!\n"); return -1; } }else{ printf("the parameter is error,usage is as following:\n"); showHelp(); return -1; } recorder = recorder_create(); if (recorder == NULL) { printf("recorder create fail, exit\n"); return -1; } printf("===start record %s now, last for %d s===\n", argv[1], atoi(argv[2])); recorder->start(recorder, file_url, &cfg); sleep(atoi(argv[2])); recorder->stop(recorder); printf("record %s over.\n", argv[1]); exit: return recorder_destroy(recorder); } FINSH_FUNCTION_EXPORT_CMD(cedarx_record_test, cedarx_record, cedarx record test demo);
-
【R128】基础组件开发指南——RTOS 多媒体解码
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
RTOS 多媒体解码
介绍 FreeRTOS 下如何使用 rtplayer 的接口来开发播放器应用程序,方便播放器开发人员快速正确地开发,以及播放器测试人员如何根据该文档对 rtplayer 播放器进行验证测试。
rtplayer 状态图
这张状态转换图清晰地描述了 rtlayer 的各个状态,也列举了主要的方法的调用时序,每种方法只能在一些特定的状态下使用,否则会出错。另外,只有在 Prepared、Started、Paused、Play‑backCompleted 这四种状态下可以进行 seekTo() 操作,并且 seekTo() 之后,状态不变。
Idle 状态
Idle 状态:当调用 player_init() 创建一个 rtplayer 或者调用了其 reset() 方法时,rtplayer 处于 idle状态。
Initialized 状态
这个状态比较简单,调用 setDateSource_url() 方法就进入 Initialized 状态,表示此时要播放的文件已经设置好了
Preparing 状态
调用 prepare() 函数还没返回或者是调用 prepareAsync() 并且还没收到 RTPLAYER_NOTIFY_PREPARED 这个回调消息的时候就处于 Preparing 状态
Prepared 状态
调用 prepare() 函数已经返回或者是调用 prepareAsync() 并且已经收到 RTPLAYER_NOTIFY_PREPARED 这个回调消息之后的状态就处于 Prepared 状态。在这个状态下说明所有的资源都已经就绪了,调用 start() 函数就可以播放了。
Started 状态
rtplayer 一旦 prepare 完成,就可以调用 start() 方法,这样 rtplayer 就处于 Started 状态,这表明 rtplayer 正在播放文件过程中。可以使用 XPlayerIsPlaying() 测试 rtplayer 是否处于了 Started 状态。如果播放完毕,而又设置了循环播放,则 rtplayer 仍然会处于 Started 状态。
Paused 状态
Started 状态下可以调用 pause_l() 方法暂停 rtplayer,从而进入 Paused 状态,rtplayer 暂停后再次调用 start() 则可以继续 TPlayer 的播放,转到 Started 状态。
Stopped 状态
Started 或者 Paused 状态下均可调用 stop() 停止 rtplayer,而处于 Stop 状态的 rtplayer 要想重新播放,需要通过 prepareAsync() 和 prepare() 回到先前的 Prepared 状态重新开始才可以。
PlaybackCompleted 状态
文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并且会通过 RTPLAYER_NOTIFY_PLAYBACK_COMPLETE 这个消息回调给应用。此时可以调用 start() 方法重新从头播放文件,也可以 stop() 停止 rtplayer,或者也可以 seekTo() 来重新定位播放位置。
Error 状态
由于某种原因 rtplayer 出现了错误,就会进入该状态,并且会通过 RTPLAYER_NOTIFY_MEDIA_ERROR 这个消息回调给应用。如果 rtplayer 进入了 Error 状态,可以通过调用 reset() 来恢复,使得 rtplayer 重新返回到 Idle 状态。
End 状态
通过 plater_deinit() 的方法可以进入 End 状态,只要 rtplayer 不再被使用,就应当尽快将其 destroy 掉。
rtplayer 层接口
创建一个 rtplayer
函数原型
uint32_t player_init(void)
参数:
- 无
返回值:
- 成功返回 rtplayer 的指针,失败返回 NULL
销毁一个 rtplayer
函数原型
void player_deinit(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针
返回值:
- 无
设置 rtplayer 的消息回调函数
函数原型
void registerCallback(void* handle, void* userData, player_callback_t fn)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针
- userData: 回调消息处理对象
- fn: 回调消息处理函数指针,需要由应用实现
返回值:
- 无
创建完 rtplayer 播放器之后,就要调用该函数设置回调消息处理函数。
设置播放文件的 url
可以是本地文件也可以是网络源
函数原型
status_t setDataSource_url(void* handle,void* userData, const char * url, int id)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针
- userData: 回调消息处理对象
- url: 需要播放的文件的 url
- id: 回调时使用的播放索引, 为 0 即可
返回值:
- 成功返回 0,失败返回‑1 或错误码
解析文件头部信息,获取元数据
函数原型
status_t prepare(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1 或错误码
异步解析文件头部信息,获取元数据
函数原型
status_t prepareAsync(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1
该函数是非阻塞函数,需要等到 RTPLAYER_NOTIFY_P‑ REPARED 消息回调之后才能调 start() 函数进行播放,而且 start() 函数不能在回调函数中调用
开始播放
函数原型
status_t start(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1
暂停播放
函数原型
status_t pause_l(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1
停止播放
函数原型
status_t stop(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针
返回值:
- 成功返回 0,失败返回‑1
重置播放器
函数原型
status_t reset(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1
在任何状态下都可以调用该函数,每次播放不同的音频之前,都需要调用该函数重置播放器,另外,一般收到 RTPLAYER_NOTIFY_MEDIA_ERROR 这个消息的时候,也需要通过调用该函数来重置播放器。但是不能在回调函数中调用该函数,否则会出现死锁
跳播
函数原型
status_t seekTo(void* handle, int sec)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针
- sec: 跳播的位置,单位是:s
返回值:
- 成功返回 0,失败返回‑1
获取当前播放的位置
函数原型
status_t getCurrentPosition(void* handle, int * sec)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
- sec: 存放当前播放的位置值,单位:s
返回值:
- 成功返回 0,失败返回‑1
获取播放的文件总时长
函数原型
status_t getDuration(void* handle, int * sec)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
- sec: 存储文件总时长,单位:s
返回值:
- 成功返回 0,失败返回‑1
需要在 prepared 状态之后才可以调用该函数
获取播放的文件信息
函数原型
MediaInfo* getMediaInfo(void* handle)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
返回值:
- 成功返回 0,失败返回‑1
需要在 prepared 状态之后才可以调用该函数
设置循环播放模式
函数原型
status_t setLooping(void* handle, int loop)
参数:
- handle: 通过 player_init() 函数创建的 rtplayer 指针;
- loop:1 表示单曲循环,0 表示不会单曲循环
返回值:
- 成功返回 0,失败返回‑1
XPlayer 层播放接口
创建一个 XPlayer
函数原型
XPlayer* XPlayerCreate()
参数:
- 无
返回值:
- 成功: XPlayer 指针; 失败: NULL
设置 XPlayer 的回调通知
函数原型
int XPlayerSetNotifyCallback(XPlayer* p, XPlayerNotifyCallback notifier, void* pUserData)
参数:
- P:通过 XPlayerCreate 创建的 Xplayer 指针
- notifier:回调通知
- pUserData:应用程序传下来的自定义数据
返回值:
- 成功:XPlayer 指针;失败:NULL
Xplayer 将接收来自下层的回调通知,进行相应的操作
创建指向音频播放设备管理模块的指针,用于播放音频
函数原型
SoundCtrl* RTSoundDeviceCreate(int card)
参数:
- card:声卡序号0:default;1:sw:audio1;2:sw:audio2;3:sw:audio3;4:sw:audio4;5:sw:audio5
返回值:
- 成功:音频播放设备管理模块的指针;失败:NULL
创建指向音频播放设备管理模块的指针,用于播放音频
函数原型
int XPlayerSetDataSourceUrl(XPlayer* p, const char* pUrl, void* httpService, const CdxKeyedVectorT* pHeaders)
参数:
- pUrl:url 地址
- httpService:服务器信息
- pHeaders:头文件信息
返回值:
- 返回值: 成功:0;失败:‑1 或线程响应设置数据源命令的返回值或线程响应 xplayer prepare 命令的返回值
调用说明: 发送 SetDataSource 命令,获取需要播放的音频数据内容
将 XPlayer 置为准备状态, 解析头部信息,获取元数据
函数原型
int XPlayerPrepare(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 Xplayer 指针
返回值:
- 成功:线程响应异步 Prepare 命令的返回值;失败:NULL
该函数是阻塞函数,调用完返回之后就进入了 Prepared 状态,此时可调 XPlayerStart() 函数进行播放
将 XPlayer 置为异步准备状态
函数原型
int XPlayerPrepareAsync(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功:线程响应异步 Prepare 命令的返回值;失败:NULL
网络播放源一般采用 PrepareAsync,而不是 Prepare 命令,PrepareAsync 命令的返回值为 0 时说明响应成功,播放器准备工作已经完成可以开始播放,为‑1 时说明响应失败
将 XPlayer 置为启动状态
函数原型
int XPlayerStart(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功:线程响应 start 命令的返回值;失败:NULL
Start 命令的返回值为 0 时说明响应成功,为‑1 时说明响应失败
将 XPlayer 置为暂停状态
函数原型
int XPlayerPause(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功:线程响应 pause 命令的返回值;失败:NULL
在 XPlayer 处于 start 状态时可调用此接口,Pause 命令的返回值为 0 时说明响应成功,为‑1 时说明响应失败
将 XPlayer 置为停止状态
函数原型
int XPlayerStop(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功:返回 0;失败:返回‑1
重置 XPlayer
将相关变量复位,并销毁各模块,如音频解码模块、音频解码数据接收模块等
函数原型
int XPlayerReset(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功:线程响应 Reset 命令的返回值;失败:NULL
Reset 命令的返回值为 0 时说明响应成功,为‑1 时说明响应失败
获取节目时长
函数原型
int XPlayerGetDuration(XPlayer* p, int *msec)
参数:
- p:通过 XPlayerCreate 创建的 Xplayer 指针
- msec:保存节目时长
返回值:
- 成功:0;失败:‑1
在 XPlayer 处于 PREPARED、STARTED、PAUSED、STOPPED 或 COMPLETE 状态下才可调用此接口,否则操作无效
Seek 到给定的时间点
函数原型
int XPlayerSeekTo(XPlayer* p, int nSeekTimeMs)
参数:
- p:通过 XPlayerCreate 创建的 Xplayer 指针
- nSeekTimeMs:跳转的时间点
返回值:
- 成功:线程响应 Seek 命令的返回值;失败:NULL
如果跳转前播放处于暂停状态,则跳转后将保持在暂停状态
获取媒体文件的总时长
函数原型
int XPlayerGetDuration(XPlayer* p, int *msec)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
- msec:保存媒体文件的总时长
返回值:
- 成功:0;失败:‑1
需要在 prepared 状态之后才可以调用该函数
获取当前的播放时间点(即播放位置)
在 XPlayer 处于 PREPARED、STARTED、PAUSED、STOPPED 或 COMPLETE 状态下才可调用此接口,否则操作无效,在 complete 状态下,可能会调用 prepare 方法并更改媒体信息,获取的播放时间以 ms 为单位
函数原型
int XPlayerGetCurrentPosition(XPlayer* p, int* msec)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
- msec:保存当前的播放时间
返回值:
- 成功:0;失败:‑1
获取媒体信息
函数原型
MediaInfo* XPlayerGetMediaInfo(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 成功返回 0,失败返回‑1。如果失败,则 mediaInfo 指针为 NULL
需要在 prepared 状态之后才可以调用该函数
设置循环播放模式
函数原型
int XPlayerSetLooping(XPlayer* p, int loop)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
- loop: 1: 表示单曲循环模式;0:表示不会循环
返回值:
- 无
查询是否正在播放
函数原型
int XPlayerIsPlaying(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 1:正在播放;0:未播放
销毁一个 XPlayer
函数原型
void XPlayerDestroy(XPlayer* p)
参数:
- p:通过 XPlayerCreate 创建的 XPlayer 指针
返回值:
- 无
播放器开发示例
- player_init() 创建一个播放器
- registerCallback() 设置消息回调函数
- setDataSource_url() 设置 url
- prepare() 或 prepareAsync() 解析头部信息,获取元数据,并根据元数据的信息初始化对应的解码器
- start() 播放 (注: 如果是用 prepareAsync() 函数,则需要等到 RTPLAYER_NOTIFY_PREPARED 消息回调之后才可以调用 start() 函数进行播放)
- 如果需要跳播,则可以调用 seekTo() 函数
- 如果需要暂停,则调用 pause_l() 函数进行暂停
- 如果需要停止,则可以调用 stop() 或 reset() 函数进行停止 (注:建议用 reset() 函数进行停止,因为任何状态下都可以调用 reset() 函数)
- 如果需要播放下一个或其他的,则可以先调用 reset() 函数使播放器进入 idle 状态,然后再重复 (3)(4)(5) 的步骤
#include <stdio.h> #include <stdint.h> #include <FreeRTOS.h> #include <task.h> #include <portable.h> #include <string.h> #include <console.h> #include <semaphore.h> #include <pthread.h> #include <unistd.h> #include <mqueue.h> #include <fcntl.h> #include <stdlib.h> #include "FreeRTOS_POSIX/utils.h" #include "rtplayer.h" #include "xplayer.h" #define PAUSE_CMD 'P' #define PING_CMD 'p' #define STOP_CMD 'S' #define SEEK_TO_CMD 's' #define SEEK_TO_CMD2 'j' #define BACKGROUND_CMD 'b' #define SHOW_BUFFER_CMD 'B' #define QUIT_CMD 'q' #define LOOP_CMD 'l' #define GET_DURATION_CMD 'G' #define GET_POSITION_CMD 'g' #define HELP_CMD 'h' #define INFO_CMD 'i' #define REPLAY_CMD 'r' #define RETRY_CMD 256 #define USE_PREPARE_ASYNC 0 #define LOGD(msg, arg...) //printf("[PLAYER_DBG] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define LOGI(msg, arg...) //printf("[PLAYER_INFO] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define LOGW(msg, arg...) printf("[PLAYER_WRN] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) #define LOGE(msg, arg...) printf("[PLAYER_ERR] <%s : %d> " msg "\n", __func__, __LINE__, ##arg) typedef struct DemoPlayerContext { RTPlayer* mRTplayer; sem_t mPreparedSem; mqd_t mRTplayerMq; pthread_t mThreadId; char *pUrl; int mSeekable; char isPlayingFlag; char mError; int inputMode; int isSetLoop; char quitFlag;//no_shell_input mode quitFlag int testMode; MediaInfo* mMediaInfo; int SoundCard; }DemoPlayerContext; typedef struct DemoPlayerMsg { int msg; int data; }DemoPlayerMsg; #define INVALID_MQD ( ( mqd_t ) -1 ) #define DEFAULT_MODE 0600 static const char *pcRTplayerMqName = "/rtplayerMq"; static volatile mqd_t mRTplayerMq = INVALID_MQD; static int mRTplayerUserInput = 0; static struct mq_attr xRTplayerMqAttr = { .mq_flags = 0, .mq_maxmsg = 3, .mq_msgsize = sizeof(DemoPlayerMsg), .mq_curmsgs = 0 }; static void showHelp(){ printf("\n"); printf("**************************\n"); printf("* This is a simple audio player, when it is started, you can input commands to tell\n"); printf("* what you want it to do.\n"); printf("* Usage: \n"); printf("* tplayer_demo /data/test.mp3 : this means play test.mp3\n"); printf("* P :this will Pause if in playing status,or Play in paused status \n"); printf("* S :this means Stop \n"); printf("* s :this means seek to 10s \n"); printf("* B :show buffer \n"); printf("* b :this means player will run in the background \n"); printf("* q :this means quit the player \n"); printf("* l :this means loop play \n"); printf("* G :this means Get duration \n"); printf("* g :this means get position \n"); printf("* i :this means show media info \n"); printf("* h :this means show the help information \n"); printf("* r : replay the current audio\n"); printf("**************************\n"); } static int rtplayer_clear_cmd(mqd_t mq){ struct timespec cur, delay, abstime; clock_gettime( CLOCK_REALTIME, &cur ); delay.tv_sec = 0; delay.tv_nsec = 5*1000*1000; UTILS_TimespecAdd(&cur, &delay, &abstime); DemoPlayerMsg msg; while(mq_timedreceive(mq, (char *)&msg, sizeof(msg), NULL, &abstime)!=-1); return 0; } static int rtplayer_send_cmd(mqd_t mq, int msg, int data){ DemoPlayerMsg pmsg = {msg, data}; struct timespec tsn, ts; clock_gettime(CLOCK_REALTIME, &tsn); UTILS_TimespecAddNanoseconds(&tsn, 20*1000*1000, &ts); int status = mq_timedsend(mq, (char *)&pmsg, sizeof(pmsg), 0, &ts); if(status) LOGE("send cmd %c,%d failed!", pmsg.msg, pmsg.data); return status; } static int rtplayer_send_cmd_force(mqd_t mq, int msg, int data){ int try_times = 0; DemoPlayerMsg pmsg = {msg, data}; struct timespec tsn, ts; int status; try_send: clock_gettime(CLOCK_REALTIME, &tsn); UTILS_TimespecAddNanoseconds(&tsn, 20*1000*1000, &ts); status = mq_timedsend(mq, (char *)&pmsg, sizeof(pmsg), 0, &ts); if(status){ try_times++; if(try_times<5){ LOGE("send cmd %c,%d failed, retry...", pmsg.msg, pmsg.data); goto try_send; } else if(try_times<10){ DemoPlayerMsg tmp; LOGE("send cmd %c,%d failed, retry...", pmsg.msg, pmsg.data); clock_gettime(CLOCK_REALTIME, &tsn); UTILS_TimespecAddNanoseconds(&tsn, 20*1000*1000, &ts); status = mq_timedreceive(mq, (char *)&tmp, sizeof(tmp), NULL, &ts); if(status<0){ LOGE("mq_receive fail %d", status); goto fail_exit; } LOGW("drop: %c, %d", tmp.msg, tmp.data); goto try_send; } goto fail_exit; } return status; fail_exit: LOGE("send cmd %c,%d failed!\n", pmsg.msg, pmsg.data); return status; } static void callbackFromRTplayer(void* userData,int msg, int id, int ext1, int ext2); static void* RTplayerThread(void* arg){ DemoPlayerContext* demoPlayer = (DemoPlayerContext*)arg; char quitFlag = 0; if(demoPlayer->inputMode) { while(1) { if(demoPlayer->quitFlag) { if(demoPlayer->mRTplayer != NULL) { printf("player finsh, quit the rtplayer\n"); mRTplayerMq = INVALID_MQD; #if USE_PREPARE_ASYNC sem_destroy(&demoPlayer->mPreparedSem); #endif player_deinit(demoPlayer->mRTplayer); free(demoPlayer->pUrl); free(demoPlayer); } break; } usleep(50*1000); } return NULL; } while(!quitFlag){ int cRxed = 0; int data = 0; DemoPlayerMsg msg; ssize_t status; if(demoPlayer->mRTplayerMq!=INVALID_MQD){ usleep(50*1000); ssize_t status = mq_receive(demoPlayer->mRTplayerMq, (char *)&msg, sizeof(msg), NULL); if(status<=-1){ LOGE("mq_receive fail %d", status); usleep(1*1000*1000); continue; } printf("receive %c,%d\n", msg.msg, msg.data); cRxed = msg.msg; data = msg.data; } else{ cRxed = QUIT_CMD; } switch(cRxed){ case PAUSE_CMD: { if(demoPlayer->isPlayingFlag){ printf("pause the rtplayer\n"); pause_l(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 0; }else{ printf("play the rtplayer\n"); start(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 1; } break; } case STOP_CMD: { printf("stop the rtplayer\n"); stop(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 0; break; } case SEEK_TO_CMD: { printf("rtplayer seek to 10 second\n"); seekTo(demoPlayer->mRTplayer,10); break; } case SEEK_TO_CMD2: { printf("rtplayer seek to %d second\n", data); seekTo(demoPlayer->mRTplayer,data); break; } case QUIT_CMD: { printf("quit the rtplayer\n"); mRTplayerMq = INVALID_MQD; //mq_close(demoPlayer->mRTplayerMq); #if USE_PREPARE_ASYNC sem_destroy(&demoPlayer->mPreparedSem); #endif player_deinit(demoPlayer->mRTplayer); free(demoPlayer->pUrl); free(demoPlayer); quitFlag = 1; break; } case LOOP_CMD: { printf("let the rtplayer loop play\n"); demoPlayer->isSetLoop = 1; setLooping(demoPlayer->mRTplayer,1); break; } case GET_DURATION_CMD: { printf("get the audio duration\n"); int duration; getDuration(demoPlayer->mRTplayer,&duration); printf("duration:%d s\n",duration); break; } case GET_POSITION_CMD: { printf("get the current position\n"); int position; getCurrentPosition(demoPlayer->mRTplayer,&position); printf("current position:%d s\n",position); break; } case HELP_CMD: { printf("show the help information\n"); showHelp(); break; } case INFO_CMD: { printf("**************************\n"); printf("* show media information:\n"); MediaInfo* mi = NULL; demoPlayer->mMediaInfo = getMediaInfo(demoPlayer->mRTplayer); if(demoPlayer->mMediaInfo != NULL){ mi = demoPlayer->mMediaInfo; printf("* file size = %lld KB\n",mi->nFileSize/1024); printf("* duration = %lld ms\n",mi->nDurationMs); printf("* bitrate = %d Kbps\n",mi->nBitrate/1024); printf("* container type = %d\n",mi->eContainerType); printf("* audio stream num = %d\n",mi->nAudioStreamNum); if(mi->pAudioStreamInfo != NULL){ printf("* audio codec tpye = %d\n",mi->pAudioStreamInfo->eCodecFormat); printf("* audio channel num = %d\n",mi->pAudioStreamInfo->nChannelNum); printf("* audio BitsPerSample = %d\n",mi->pAudioStreamInfo->nBitsPerSample); printf("* audio sample rate = %d\n",mi->pAudioStreamInfo->nSampleRate); } printf("**************************\n"); } break; } case SHOW_BUFFER_CMD: { printf("**************************\n"); printf("* show buffer information:\n"); player_show_buffer(); printf("**************************\n"); break; } case REPLAY_CMD: { printf("replay %s\n", demoPlayer->pUrl); int ret; if(demoPlayer->testMode){ printf("test mode: destroy & create instead of reset\n"); player_deinit(demoPlayer->mRTplayer); usleep(50*1000); demoPlayer->mRTplayer = (RTPlayer*)(uintptr_t)player_init(); printf("demoPlayer.mRTplayer = %p\n",demoPlayer->mRTplayer); if(!demoPlayer->mRTplayer){ printf("init rtplayer fail\n"); free(demoPlayer->pUrl); free(demoPlayer); quitFlag = 1; continue; } registerCallback(demoPlayer->mRTplayer, demoPlayer, callbackFromRTplayer); } else reset(demoPlayer->mRTplayer); ret = setDataSource_url(demoPlayer->mRTplayer, demoPlayer, demoPlayer->pUrl, 0); if(ret){ printf("setDataSource_url failed\n"); break; } ret = prepare(demoPlayer->mRTplayer); if(ret){ printf("prepare failed\n"); break; } start(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 1; if(demoPlayer->isSetLoop) { setLooping(demoPlayer->mRTplayer,1); } break; } case RETRY_CMD: { int position = data; if(data==-1) getCurrentPosition(demoPlayer->mRTplayer,&position); printf("retry %s\n", demoPlayer->pUrl); int ret; if(demoPlayer->testMode){ printf("test mode: destroy & create instead of reset\n"); player_deinit(demoPlayer->mRTplayer); usleep(50*1000); demoPlayer->mRTplayer = (RTPlayer*)(uintptr_t)player_init(); printf("demoPlayer.mRTplayer = %p\n",demoPlayer->mRTplayer); if(!demoPlayer->mRTplayer){ LOGE("init rtplayer fail"); free(demoPlayer->pUrl); free(demoPlayer); quitFlag = 1; continue; } registerCallback(demoPlayer->mRTplayer, demoPlayer, callbackFromRTplayer); } else reset(demoPlayer->mRTplayer); ret = setDataSource_url(demoPlayer->mRTplayer, demoPlayer, demoPlayer->pUrl, 0); if(ret){ LOGE("setDataSource_url failed"); rtplayer_send_cmd_force(demoPlayer->mRTplayerMq, RETRY_CMD, position); usleep(500*1000); break; } ret = prepare(demoPlayer->mRTplayer); if(ret){ LOGE("prepare failed"); rtplayer_send_cmd_force(demoPlayer->mRTplayerMq, RETRY_CMD, position); usleep(500*1000); break; } start(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 1; //seekTo(demoPlayer->mRTplayer, position); if(demoPlayer->isSetLoop) setLooping(demoPlayer->mRTplayer,1); break; } default: { LOGW("warning: unknown command,cmd = %d",cRxed); break; } } if(quitFlag){ return NULL; } } return NULL; } static void callbackFromRTplayer(void* userData,int msg, int id, int ext1, int ext2){ LOGI("call back from RTplayer,msg = %d,id = %d,ext1 = %d,ext2 = %d\n",msg,id,ext1,ext2); DemoPlayerContext* pDemoPlayer = (DemoPlayerContext*)userData; switch(msg) { case RTPLAYER_NOTIFY_PREPARED: { printf("RTPLAYER_NOTIFY_PREPARED:has prepared.\n"); #if USE_PREPARE_ASYNC sem_post(&pDemoPlayer->mPreparedSem); pDemoPlayer->mPreparedFlag = 1; #endif break; } case RTPLAYER_NOTIFY_PLAYBACK_COMPLETE: { printf("RTPLAYER_NOTIFY_PLAYBACK_COMPLETE:play complete\n"); pDemoPlayer->isPlayingFlag = 0; if(pDemoPlayer->inputMode) { pDemoPlayer->quitFlag = 1; } break; } case RTPLAYER_NOTIFY_SEEK_COMPLETE: { printf("RTPLAYER_NOTIFY_SEEK_COMPLETE:seek ok\n"); break; } case RTPLAYER_NOTIFY_MEDIA_ERROR: { switch (ext1) { case RTPLAYER_MEDIA_ERROR_UNKNOWN: { printf("erro type:TPLAYER_MEDIA_ERROR_UNKNOWN\n"); break; } case RTPLAYER_MEDIA_ERROR_UNSUPPORTED: { printf("erro type:TPLAYER_MEDIA_ERROR_UNSUPPORTED\n"); break; } case RTPLAYER_MEDIA_ERROR_IO: { printf("erro type:TPLAYER_MEDIA_ERROR_IO\n"); break; } } printf("RTPLAYER_NOTIFY_MEDIA_ERROR\n"); pDemoPlayer->mError = 1; #if USE_PREPARE_ASYNC if(pDemoPlayer->mPreparedFlag == 0){ printf("recive err when preparing\n"); sem_post(&pDemoPlayer->mPreparedSem); } #endif if( pDemoPlayer->mRTplayerMq!=INVALID_MQD ){ rtplayer_send_cmd_force(pDemoPlayer->mRTplayerMq, RETRY_CMD, -1); } else{ printf("io error, mqueue not exist\n"); } break; } case RTPLAYER_NOTIFY_NOT_SEEKABLE: { pDemoPlayer->mSeekable = 0; printf("info: media source is unseekable.\n"); break; } case RTPLAYER_NOTIFY_DETAIL_INFO: { int flag = *(int *)(uintptr_t)ext2; //printf("detail info: %d\n", flag); break; } default: { printf("warning: unknown callback from RTplayer.\n"); break; } } } int cmd_rtplayer_test(int argc, char ** argv) { int inputMode = 0; int testMode = 0; /* printf("argc = %d\n",argc); for(int i=0; i < argc;i++){ printf("argv[%d]=%s\n",i,argv[i]); } */ printf("rtplayer source:%s\n", argv[1]); if(argc == 3){ if( !strncmp("no_shell_input", argv[2], sizeof("no_shell_input")-1) ){ argc--; inputMode = 1; } else if( !strncmp("test_mode", argv[2], sizeof("test_mode")-1) ){ argc--; testMode = 1; } } if(argc != 2){ LOGW("the parameter is error,usage is as following:"); showHelp(); goto rtp_failed; } #if USE_PREPARE_ASYNC int waitErr = 0; #endif DemoPlayerContext* demoPlayer = (DemoPlayerContext*)malloc(sizeof(DemoPlayerContext)); if(demoPlayer == NULL){ LOGE("malloc DemoPlayerContext fail"); goto rtp_failed; } memset(demoPlayer, 0, sizeof(DemoPlayerContext)); demoPlayer->mSeekable = 1; demoPlayer->mRTplayerMq = INVALID_MQD; demoPlayer->inputMode = inputMode; demoPlayer->testMode = testMode; demoPlayer->quitFlag = 0; demoPlayer->mMediaInfo = NULL; if(strlen(argv[1])<=0){ LOGE("url error"); goto rtp_url_failed; } demoPlayer->pUrl = malloc(strlen(argv[1])+1); if(!demoPlayer->pUrl){ LOGE("pUrl malloc fail"); goto rtp_url_failed; } memset(demoPlayer->pUrl, 0, strlen(argv[1])); strcpy(demoPlayer->pUrl, argv[1]); #if USE_PREPARE_ASYNC sem_init(&demoPlayer->mPreparedSem, 0, 0); #endif demoPlayer->mRTplayer = (RTPlayer*)(uintptr_t)player_init(); LOGI("demoPlayer.mRTplayer = %p",demoPlayer->mRTplayer); if(!demoPlayer->mRTplayer){ LOGE("init rtplayer fail"); goto rtp_init_failed; } registerCallback(demoPlayer->mRTplayer, demoPlayer, callbackFromRTplayer); status_t ret = setDataSource_url(demoPlayer->mRTplayer,demoPlayer,demoPlayer->pUrl, 0); if(ret){ LOGE("set DataSource url fail"); goto rtp_prepare_failed; } #if USE_PREPARE_ASYNC demoPlayer->mPreparedFlag = 0; if(prepareAsync(demoPlayer->mRTplayer) != 0) { printf("TPlayerPrepareAsync() return fail.\n"); }else{ printf("preparing...\n"); } struct timespec t; t.tv_nsec = 0; t.tv_sec = 30; waitErr = sem_timedwait(&demoPlayer->mPreparedSem, &t); if(waitErr == -1){ printf("prepare timeout,has wait %d s\n",t.tv_sec); sem_destroy(&demoPlayer->mPreparedSem); goto rtp_prepare_failed; }else if(demoPlayer.mError == 1){ printf("prepare fail\n"); sem_destroy(&demoPlayer->mPreparedSem); goto rtp_prepare_failed; } printf("prepared ok\n"); #else ret = prepare(demoPlayer->mRTplayer); if(ret){ LOGE("prepare fail"); goto rtp_prepare_failed; } #endif start(demoPlayer->mRTplayer); demoPlayer->isPlayingFlag = 1; if( mRTplayerMq==INVALID_MQD ){ mRTplayerMq = mq_open( pcRTplayerMqName, O_CREAT | O_RDWR, DEFAULT_MODE, &xRTplayerMqAttr ); if(mRTplayerMq==INVALID_MQD){ LOGE("mq_open fail"); } } demoPlayer->mRTplayerMq = mRTplayerMq; rtplayer_clear_cmd(demoPlayer->mRTplayerMq); pthread_attr_t attr; pthread_attr_init(&attr); struct sched_param sched; sched.sched_priority = 4; pthread_attr_setschedparam(&attr, &sched); pthread_attr_setstacksize(&attr, 32768); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if( pthread_create(&demoPlayer->mThreadId, &attr, RTplayerThread, demoPlayer) ){ LOGE("pthread_create failed, quit the rtplayer"); mRTplayerMq = INVALID_MQD; //mq_close(demoPlayer->mRTplayerMq); #if USE_PREPARE_ASYNC sem_destroy(&demoPlayer->mPreparedSem); #endif goto rtp_prepare_failed; } pthread_setname_np(demoPlayer->mThreadId, "RTplayerThread"); if(demoPlayer->inputMode) goto rtp_succeed; while(1){ char cRxed = getchar(); if(cRxed==BACKGROUND_CMD){ printf("shell input exit, rtplayer will run in the background\n"); break; } rtplayer_send_cmd(demoPlayer->mRTplayerMq, cRxed, 0); if(cRxed==QUIT_CMD) break; usleep(50*1000); } rtp_succeed: return 0; rtp_prepare_failed: player_deinit(demoPlayer->mRTplayer); rtp_init_failed: free(demoPlayer->pUrl); rtp_url_failed: free(demoPlayer); rtp_failed: return -1; } FINSH_FUNCTION_EXPORT_CMD(cmd_rtplayer_test, rtplayer_test, test the rtplayer); static int cmd_rtplayer_controller(int argc, char ** argv){ if(mRTplayerMq==INVALID_MQD){ printf("mRTplayerMq = INVALID_MQD!\n"); return -1; } if( (argc!=2) && (argc!=3) ){ printf("usage:rtpc <cmd> [data]\n"); return -1; } int data = 0; if(argc==3) data = atoi(argv[2]); rtplayer_send_cmd(mRTplayerMq, argv[1][0], data); return 0; } FINSH_FUNCTION_EXPORT_CMD(cmd_rtplayer_controller, rtpc, control the rtplayer);
注意事项
-
目前 rtplayer/xplayer 仅支持音频解码,且不支持对视频文件进行解封装,因此 rtplayer 播放器应用只支持音频文件的播放。
-
void registerCallback(void* handle, void* userData, player_callback_t fn)
函数必须要调用,而且 fn 不能为 NULL。 -
回调函数中不能调用 rtplayer 的任何一个接口,如:reset、stop、start 等这些接口不能在回调函数中调用。
-
播放本地文件的情况下,set url 时,XPlayer 会进行一次同步 prepare,用于提前获取信息给 parser,因此异步 prepare 前,应对 XPlayerSetDataSourceUrl 的返回值进行判断。
-
改变播放器的状态,应满足状态图中的对应的函数调用流程,如播放结束后需要播放下一首歌,应调用 reset 清空信息,进入 idle 状态,再调用 setDataSource_Url 进行填充下一首歌到播放器中
-
采取异步 prepare 时(prepareAsync),应注意添加信号量进行同步。
-
回复: R128S2的内存不够用了
R128支持nor-flash,代码可以放在xip运行,保存在xip的方法可以看到对应工程下的xip.lds.S。
或者可以将dsp占用的8Mhpsram转移部分给C906使用,在dsp代码量不大的情况下,例如把dsp的内存裁剪到4M后,参照以下步骤把4M hpsram分给C906
打开C906的menuconfig
1)使能CONFIG_HEAP_MULTIPLE宏,令C906可以同时使用多个内存堆;
2)使能CONFIG_DEFAULT_LPSRAM_HEAP,CONFIG_LPSRAM_HEAP 宏,配置默认内存堆为lpsram;
3)使能CONFIG_LPSRAM_HEAP_DYNAMIC_ADDR宏,意思是lpsram的内存是动态分布的;
4)配置CONFIG_LPSRAM_HEAP_SIZE的大小是0x600000,因为前面2M给M33使用,所以剩下6M;
5)使能CONFIG_HPSRAM_HEAP宏,使能HPSRAM
6)配置CONFIG_HPSRAM_HEAP_START_ADDR为0xc400000,0xc000000是Hpsram的开始地址,前面4M已经分配给dsp使用;
7)配置CONFIG_HPSRAM_HEAP_SIZE为0x400000,意思是可以用的hpsram大小是4M。其中LPSRAM和HPSRAM的配置也可以考虑互换。
-
【R128】应用开发案例——驱动 WS2812 流水灯
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
驱动 WS2812 流水灯
本文案例代码 下载地址 驱动 WS2812 流水灯案例代码 https://www.aw-ol.com/downloads?cat=24 R128-DevKit 拥有4颗 WS2812 LED,本文将详细叙述如何点亮他们。
LEDC 模块简介
LEDC 硬件方框图如上图所示,CPU 通过 APB 总线操作 LEDC 寄存器来控制 LEDC;当 CPU配置好 LEDC 的相关寄存器之后,通过 CPU 或 DMA 将 R、G、B 数据从 DRAM 搬到 LEDC FIFO 中,启动 LEDC 之后就可以通过 PIN 脚向外部的 LED 发送数据了。
LED 典型电路如图所示,其中 DI 表示控制数据输入脚,DO 表示控制数据输出脚。DI 端接收从控制器传过来的数据,每个 LED 内部的数据锁存器会存储 24bit 数据,剩余的数据经过内部整形处理电路整形放大后通过 DO 端口开始转发输出给下一个级联的 LED。因此,每经过一个LED,数据减少 24bit。
注意,如果在单次直接设置第 n 个 LED 的亮度和色彩的时候,前面 n-1 个 LED 的亮度数据会在第 n 个 LED 的数据前发送,不过这些数据将会是原来 n-1 个 LED 的亮度数据。
由于拥有独立的 LEDC 模块,在 R128 平台上驱动 WS2812 类似的 RGB LED 不需要使用 SPI 模拟,也不需要使用 PWM 配置时序。直接使用这个模块即可。
设置 LEDC 驱动
运行
mrtos_menuconfig
进入配置页面。前往下列地址找到LEDC Devices
Drivers Options ---> soc related device drivers ---> LEDC devices ---> [*] enable ledc driver
找到 LEDC Devices
勾选如下选项
配置 LEDC 参数
参考电路图可知,LEDC 模块连接的是 R128 的
PA13
引脚。参考手册可知 MUX 为 7前往
lichee/rtos/drivers/rtos-hal/hal/source/ledc/platform/ledc_sun20iw2.h
并编辑 LEDC 的引脚和MUX#define LEDC_PIN GPIOA(13) #define LEDC_PINMUXSEL 7
然后编辑
lichee/rtos/drivers/rtos-hal/hal/source/ledc/hal_ledc.c
配置 WS2812 的时序参数:struct ledc_config ledc_config = { .led_count = 4, .reset_ns = 84, .t1h_ns = 1000, .t1l_ns = 1000, .t0h_ns = 580, .t0l_ns = 1000, .wait_time0_ns = 84, .wait_time1_ns = 84, .wait_data_time_ns = 600000, .output_mode = "GRB", };
编译测试
编译后烧录开发板
可以用命令
hal_ledc
来测试hal_ledc <LED号> <R|G|B> <亮度>
点亮红色 LED
运行命令
hal_ledc 1 R 100
即可点亮第一颗 LED
点亮绿色 LED
运行命令
hal_ledc 2 G 100
第二颗 LED 即可点亮绿色
实现七彩流水灯
前往项目文件夹编辑
main.c
,这里我选择在 M33 核心上编写程序,所以选用的是lichee/rtos/projects/r128s2/module_m33/src/main.c
,如果是编写 C906 核心的程序,请修改lichee/rtos/projects/r128s2/module_c906/src/main.c
#include <sunxi_hal_ledc.h> #include <hal_cmd.h> #include <hal_timer.h> // 使用RGB 分量合成颜色值 #define MERAGECOLOR(G, R, B) (((uint32_t)G << 16) | ((uint16_t)R << 8) | B) #define PIXEL_NUM 4 // 生成颜色 uint32_t WS281x_Wheel(uint8_t wheelPos) { wheelPos = 255 - wheelPos; if (wheelPos < 85) { return MERAGECOLOR(255 - wheelPos * 3, 0, wheelPos * 3); } if (wheelPos < 170) { wheelPos -= 85; return MERAGECOLOR(0, wheelPos * 3, 255 - wheelPos * 3); } wheelPos -= 170; return MERAGECOLOR(wheelPos * 3, 255 - wheelPos * 3, 0); } // 亮度设置 uint32_t WS281xLSet(uint32_t rgb, float k) { uint8_t r, g, b; float h, s, v; uint8_t cmax, cmin, cdes; r = (uint8_t) (rgb >> 16); g = (uint8_t) (rgb >> 8); b = (uint8_t) (rgb); cmax = r > g ? r : g; if (b > cmax) cmax = b; cmin = r < g ? r : g; if (b < cmin) cmin = b; cdes = cmax - cmin; v = cmax / 255.0f; s = cmax == 0 ? 0 : cdes / (float) cmax; h = 0; if (cmax == r && g >= b) h = ((g - b) * 60.0f / cdes) + 0; else if (cmax == r && g < b) h = ((g - b) * 60.0f / cdes) + 360; else if (cmax == g) h = ((b - r) * 60.0f / cdes) + 120; else h = ((r - g) * 60.0f / cdes) + 240; v *= k; float f, p, q, t; float rf, gf, bf; int i = ((int) (h / 60) % 6); f = (h / 60) - i; p = v * (1 - s); q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch (i) { case 0: rf = v; gf = t; bf = p; break; case 1: rf = q; gf = v; bf = p; break; case 2: rf = p; gf = v; bf = t; break; case 3: rf = p; gf = q; bf = v; break; case 4: rf = t; gf = p; bf = v; break; case 5: rf = v; gf = p; bf = q; break; default: break; } r = (uint8_t) (rf * 255.0); g = (uint8_t) (gf * 255.0); b = (uint8_t) (bf * 255.0); return ((uint32_t) r << 16) | ((uint32_t) g << 8) | b; } // 延时函数 static inline int msleep(int ms) { vTaskDelay(ms / portTICK_RATE_MS); } // 测试 LEDC int ledc_test_loop() { int i = 0, j = 0, err; int mode = 0; uint8_t R = 0, G = 0, B = 0; err = hal_ledc_init(); if (err) { printf("ledc init error\n"); return -1; } while (1) { for (j = 0; j < 256; j++) { for (i = 0; i < PIXEL_NUM; i++) { sunxi_set_led_brightness( i + 1, WS281xLSet(WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255), 0.2)); msleep(1); } msleep(10); } } return 1; }
并且将测试函数加入到
cpu0_app_entry
中。重新烧录即可实现七彩流水灯
-
【R128】外设模块配置——USB外设功能配置
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
USB 外设功能配置
USB 功能简介
USB 功能模块包括了USB Host,USB Device 和OTG 功能。
USB Host 目前已经支持上的功能有:Mass Storage,UVC。
USB Device 目前已经支持上的功能有:ADB,UAC。
OTG 主要用作Host 与Device 的切换,如当板子通过 USB 线连接到 USB 主机 (PC) 上时,
此时 OTG 是加载成 USB Device;若当前板子是通过 OTG 线连接一个USB 设备,此时 OTG 则加载
成 USB Host。USB 外设特性
- Complies with USB 2.0 Specification
- Supports High-Speed (HS, 480-Mbps), Full-Speed (FS, 12-Mbps), and Low-Speed (LS, 1.5-Mbps) in Host mode
- Supports High-Speed (HS, 480 Mbps), Full-Speed (FS, 12 Mbps) in Device mode
- Supports the UTMI+ Level 3 interface. The 8-bit bidirectional data buses are used
- Supports bi-directional endpoint0 for Control transfer
- Supports up to 8 User-Configurable Endpoints for Bulk, Isochronous and Interrupt bi-directional transfers (Endpoint1, Endpoint2, Endpoint3, Endpoint4)
- Supports up to (4KB+64Bytes) FIFO for EPs (Including EP0)
- Supports High-Bandwidth Isochronous & Interrupt transfers
- Automated splitting/combining of packets for Bulk transfers
- Supports point-to-point and point-to-multipoint transfer in both Host and Peripheral mode
- Includes automatic ping capabilities
- Soft connect/disconnect function
- Performs all transaction scheduling in hardware
- Power Optimization and Power Management capabilities
- Includes interface to an external Normal DMA controller for every Eps
USB 配置介绍
sys_config.fex
配置说明sys_config.fex 中主要是对 OTG 功能进行配置,各个配置的含义可如下所示:
Key Value [usbc0] 控制器0的配置。 usb_used: USB使能标志。置1,表示系统中USB模块可用,置0,则表示系统USB禁用。 usb_port_type USB端口的使用情况。 0: device only;1: host only;2: OTG;usb_detect_type: USB usb_detect_mode USB端口的检查方式。0: 线程轮询;1: id中断触发 usb_id_gpio USB ID pin脚配置。具体请参考gpio配置说明。 usb_det_vbus_gpio USB DET_VBUS pin脚配置。具体请参考gpio配置说明。 usb_drv_vbus_gpio USB DRY_VBUS pin脚配置。具体请参考gpio配置说明。 usb_drv_vbus_type vbus设置方式。0: 无; 1: gpio; 2: axp。 usb_det_vbus_gpio "axp_ctrl",表示axp 提供。 usbh_driver_level usb驱动能力等级 usbh_irq_flag usb中断标志 示例:
;-------------------------------- ;--- USB0控制标志 ;-------------------------------- [usbc0] usb_used = 1 usb_port_type = 2 usb_detect_type = 1 usb_detect_mode = 0 usb_id_gpio = port:PB04<0><0><default><default> usb_det_vbus_gpio = port:PA24<0><0><default><default> usb_drv_vbus_gpio = port:PA29<1><0><default><default> usb_drv_vbus_type = 1 usbh_driver_level = 5 usbh_irq_flag = 0
rtos menuconfig 配置说明
- 使能USB 驱动
‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers [*] USB
- 使能OTG
‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers [*] USB_MANAGER
- 使能USB Host
使能完USB Host 之后,还需要选择:OHCI 与EHCI(一共有0 和1 两组,对于R128 来说,只需要使能USB0)。另外,还要选择功能驱动(Mass Storage,UVC),不然只是使能USB Host 则无法正常运行USB Host 的功能。
USB Host 控制器驱动配置如下:
USB Host ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers [*] USB_HOST OHCI ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB HOST [*] USB_OHCI_0 EHCI ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB HOST [*] USB_EHCI_0
- 使能USB Device
USB Device 除了UDC 的使能之外,也需要选择对应的功能驱动Gadget 以及功能驱动对应的上层应用。
USB Device 控制器驱动配置如下:
USB Device ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers [*] USB_DEVICE USB Device使能dma通信 ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB DEVICE [*] UDC_USE_DMA
USB 源码结构
lichee/rtos‑hal/hal/source/usb ├── common/ ├── core/ ├── gadget/ │ ├── function/ ├── hid/ │ ├── Class/ │ ├── Client/ │ │ ├── KeyBoard/ │ │ └── Mouse/ │ ├── Include/ ├── host/ ├── include/ ├── manager/ ├── platform/ ├── storage/ │ ├── Class/ │ ├── Disk/ │ ├── include/ │ └── Misc/ ├── udc/ └── uvc/ ├── Class/ ├── drv_webcam/ │ ├── dev_cfg/ │ └── webcam_core/ ├── Include/ ├── Misc/ └── Webcam/
- common: USB 驱动初始化公用文件。
- core: USB Host 驱动框架层文件。
- gadget: USB Deivce 功能驱动gadget 驱动总入口文件,function 则是各个功能驱动的驱动文件。
- hid: USB Host HID 协议及驱动文件。
- host: USB Host 硬件控制器驱动。
- include: USB 公用头文件。
- manager: USB OTG 驱动。
- platform: 不同平台的配置文件。
- storage: USB Host Mass Storage 协议及驱动文件。
- udc: USB Deivce 硬件控制器驱动。
- uvc: USB Host UVC 协议及驱动文件。
详细说明请见:HAL USB
USB 常用功能说明
配置OTG 功能
OTG 功能下,需要根据USB ID 脚去进行Device/Host 模式的切换;如果需要支持NULL 模式(既不加载Device 也不加载Host 驱动), 那么还需要VBUS 状态检测引脚。
涉及到的主要改动点:
在sys_config.fex,修改如下配置: usb_port_type配置为2,即OTG模式。 usb_id_gpio配置对应的USB ID引脚。 usb_det_vbus_gpio, 需要根据实际情况进行配置: 1.如果需要检测VBUS状态,则按下面情况进行配置: 配置成对应的gpio即可。 2.如果不需要检测VBUS状态(缺少NULL模式) 那么直接填写USB ID的gpio配置(也就是VBUS与ID状态一致)。
USB OTG 驱动,会根据ID 和VBUS 的状态,自动切换成对应的模式。ID 和VBUS 的对应关系如下表:
ID VBUS 模式 0 0 Host 1 0 Null 0 1 Host 1 1 Device - ID 脚一般情况下为高电平,只有接入OTG 线时会拉低;
- VBUS 为1 表示micro USB 口有接入外部电源;
- 一般不会出现ID 为0,VBUS 为1 的情况。这表示接入OTG 线,但是还检测到VBUS;
- 如果没有VBUS 检测,ID 只有0 和1 的两种情况, 也就是说要么加载device 驱动,要么加载host 驱动; 这会带来一些影响:usb 相关时钟一直被打开,导致有一定功耗,以及硬件射频。
USB Gadget 功能配置
USB Gadget 支持众多功能,它们的配置方法比较类似,只需要在mrtos_menuconfig 中选上对应的Gadget 功能驱动即可在系统初始化时自动加载。与Linux 不一样的是,RTOS 的gadget 功能配置全部hardcode 在功能驱动里,无需像Linux 一样需要在应用层手动进行gadget 配置。
另外,目前RTOS 的USB 驱动还不支持composite gadget,因此只能支持加载单一的gadget 功能驱动,无法同时多个功能。
ADB 功能
adb 的全称为Android Debug Bridge,就是起到调试桥的作用。通过ADB,可以直接在PC 上通过命令行控制小机端的控制台;也可以通过ADB 进行文件传输。
menuconfig 驱动相关配置:
‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB DEVICE [*] DRIVERS_USB_GADGET_ADB
menuconfig ADBD 应用相关配置:
‑> System components ‑> aw components ‑> USB Components Support ‑> USB Gadget Support [*] adbd service
在RTOS 的USB 框架中,一旦加载了adb gadget,就会自动启用adbd 服务,直接连上PC 就可以使用了。
adb 正常启动的相关log:
[usb0] insmod device driver! adbd version:AW‑V1.1.6, compiled on: Apr 11 2023 10:33:24 adbd service init successful
PC 运行效果图如下图所示:
UAC 功能
UAC 全称为USB Audio Class,USB 音频类。
通过UAC,可以实现实时获取音频设备的音频数据,并且通过UAC 实现操控设备音量,采样率,等参数。UAC 实现对外接音频操作,从用户功能来说,主要包括USB 麦克风、USB 声卡和其它音频设备的功能控制和接口标准。
menuconfig 驱动相关配置:
‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB DEVICE [*] DRIVERS_USB_GADGET_UAC
menuconfig UACD 应用相关配置:
‑> System components ‑> aw components ‑> USB Components Support ‑> USB Gadget Support [*] uacd service ‑> System components ‑> aw components ‑> USB Components Support ‑> USB Gadget Support ‑> uacd audio function [*] AudioSystem local audio
uacd 正常启动的相关log:
[usb0] insmod device driver! uacd version:AW‑V0.5, compiled on: Apr 27 2023 10:44:02 [UACD‑INFO][u_audio_init] line:167 stream=1, rate=48000, ch=2, bits=16, audio_buf_size=192 [UACD‑INFO][u_audio_init] line:167 stream=0, rate=16000, ch=2, bits=16, audio_buf_size=64 [UACD‑INFO][u_audio_stop_capture] line:320 [UACD‑INFO][u_audio_stop_playback] line:457 [UACD‑INFO][u_audio_stop_capture] line:320 [UACD‑INFO][u_audio_stop_playback] line:457
启动成功之后,能够在PC 端看到新增了一个音频输入和输出的设备,如下图:
USB Host 功能配置
接入OTG 线后,成功切换成 USB Host 的log 可参考如下:
[ehci‑usb0] insmod host driver! calibration finish, val:0x19, usbc_no:0 ehci insmod status = 1 [usbh core]: add gen_dev SW USB2.0 'Enhanced' Host Controller (EHCI) Driver [D(rv.)] devops: register dev(sunxi_timer) ok USB 0.0 started, EHCI 1.00 [usbh core]: adding sub dev (config #1, interface 0) usb match id suceessfull [hub]: usb hub probe [hub]: 1 port detected [usbh hub]: local power source is good [E(rv.)] pm device sunxi_ehci0(00000000082AE1D0) has already registered [ohci‑usb0] insmod host driver! calibration finish, val:0x19, usbc_no:0 [usbh core]: add gen_dev SW USB2.0 'Open' Host Controller (OHCI) Driver [usbh core]: adding sub dev (config #1, interface 0) usb match id suceessfull [hub]: usb hub probe [hub]: 1 port detected [usbh hub]: local power source is good [D(rv.)] devops: register dev(sunxi_ohci0) ok
U 盘功能
选上以下配置:
Mass Storage ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB HOST [*] Mass Storage support [*] USB CD support
等待U 盘挂载成功,可以发现根目录下多了一个usb_msc 的文件夹,这个即是U 盘挂载的文件夹。可以通过该文件夹与U 盘进行读取/传输文件。
- 目前R128 只支持fat32 文件系统的U 盘,其他文件系统U 盘会挂载失败。
- 只支持挂载单分区的U 盘,如果U 盘被分解成了多个分区的话,只能挂载上第一个分区。
接入U 盘后,系统开始识别U 盘。成功识别到U 盘的log 信息如下:
ehci_irq: highspeed device connect port debounce 0... port debounce 0... port debounce 25... port debounce 50... port debounce 75... hub_port_init: udev address = 0 [hub_port_init]: new high speed USB device address 0 usb hub set new address(2) [usbh core]: adding sub dev (config #1, interface 0) usb match id suceessfull mscDevProbe begin [msc]: GetMaxLUN successful, max lun is 0 begin mscLunAdd disk, send last lun msg......... mscLun‑>LunNo=0 mscLun‑>mscDev‑>MaxLun=1 BlkDev‑>last_lun=1 Wrn: short transfer, urb_state(0), want_len(192), real_len(70) Wrn: short transfer, urb_state(0), want_len(192), real_len(24) ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑Disk Information‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ WriteProtect = 0 MediaPresent = 1 WCE = 0 RCD = 0 capacity = 29532M, sector number = 60481536 sector_size = 512 DevNo = 0 ClassName = DevName = SCSI_DISK_000 ‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑ hub_port = 1 cnt = 0 Classname = DISK DevName = SCSI_DISK_000 DeviceName = SCSI_DISK_000 Vender = TOSHIBA USB FLASH DRIVE PMAP Product = USB FLASH DRIVE PMAP Serial = PMAP HubPortNo = 1 DeviceType = 2 DiskRead: block(0, 1) is adjacence max capacity(39ae000), can't use special write mount usb mass storage successull!! .............................................................................. [USB Disk]: Register new device, class = [DISK], dev = [SCSI_DISK_000] .............................................................................. end mscLunAdd mscDevScanThread end...
然后通过ls 查看usb_msc,可以看到U 盘里的文件:
c906>ls dev data usb_msc c906>ls usb_msc System Volume Information 3.txt
USB 摄像头
选上以下配置:
UVC ‑> Drivers Options ‑> soc related device drivers ‑> USB Drivers ‑> USB HOST [*] USB_CAMERA
接上USB 摄像头后,成功识别出摄像头后会出现以下log:
ehci_irq: highspeed device connect port debounce 0... port debounce 0... port debounce 25... port debounce 50... port debounce 75... hub_port_init: udev address = 0 [hub_port_init]: new high speed USB device address 0 usb hub set new address(3) [hub] :skipped 1 descriptor after configuration skipped 6 descriptors after interface skipped 1 descriptor after endpoint skipped 26 descriptors after interface num_ep:0 skipped 1 descriptor after endpoint skipped 4 descriptors after interface num_ep:0 num_ep:0 skipped 2 descriptors after interface skipped 1 descriptor after endpoint [usbh core]: adding sub dev (config #1, interface 0) usb match id suceessfull UVCDevProbe begin Probing generic UVC device device quirks 0x0 Found format MJPEG. ‑ 1920x1080 (30.0 fps) ‑ 1280x720 (30.0 fps) ‑ 640x480 (30.0 fps) ‑ 640x360 (30.0 fps) ‑ 352x288 (30.0 fps) ‑ 320x240 (30.0 fps) ‑ 320x180 (30.0 fps) ‑ 176x144 (30.0 fps) ‑ 160x120 (30.0 fps) ‑ 1920x1080(30.0 fps) Found format YUV 4:2:2 (YUYV). ‑ 1920x1080 (5.0 fps) ‑ 1280x720 (10.0 fps) ‑ 640x480 (30.0 fps) ‑ 640x360 (30.0 fps) ‑ 352x288 (30.0 fps) ‑ 320x240 (30.0 fps) ‑ 320x180 (30.0 fps) ‑ 176x144 (30.0 fps) ‑ 160x120 (30.0 fps) ‑ 1920x1080 (5.0 fps) Found UVC 1.00 device USB 2.0 Camera (0c45:6366) num_altsetting=7 UVC device initialized. DRV_WEBCAM_MInit webcam plug in message... [usbh core]: adding sub dev (config #1, interface 1) [usbh core]: adding sub dev (config #1, interface 2) [usbh core]: adding sub dev (config #1, interface 3)
通过log 信息能够看到,在识别出USB 摄像头后,会打印出该摄像头支持的格式以及分辨率。
接着通过uvc 测试命令,对UVC 功能进行测试:
usb uvc_test
测试命令调用完成之后,会在/data 目录下生成5 张名为/data/source_frame_x.jpg 的图片。通过adb pull 将图片拉到PC 端,然后在PC 端打开图片即可看到USB 摄像头拍下的照片。
USB 调试方法
USB OTG 功能调试
除了OTG 的自动切换功能,还可以进行手动的切换,需要使用到USB 命令。
USB Device/Host 的手动切换方法:
输入usb ‑h 能够看见usb 的全部命令。
- USB Device 的相关命令
usb udc {‑i|‑r} [<port>] ‑i:指的是指定需要进行切换的USB口。目前R128芯片只有USB0是支持Device模式的。 ‑r:指的是remove,注销当前的Device模式。 比如说将USB0切换成Device模式,则运行: usb udc ‑i 0
- USB Host 的相关命令
usb hci {‑i|‑r} [<port>] ‑i:指的是指定需要进行切换的USB口。 ‑r:指的是remove,注销当前的Host模式。 比如说将USB0切换成Host模式,则运行: usb hci ‑i 0
USB 相关工具
ADB
ADB 功能是从Android 移植过来的,设备端会运行adbd 服务,而Host 端(一般为PC) 通过adb工具进行调试,如adb shell, adb push/pull 等。
ADB 功能说明
adb shell 功能
PC端执行adb shell,可进入控制台。
PC端执行adb shell + command, 例如adb shell ls /可以直接将结果输出到终端。
adb push/pull 功能
推送文件到小机端: adb push test.bin /data 从小机端拉取文件: adb pull /data/test.bin .
adb 网络连接
如果需要用此功能,需要额外进行menuconfig 的配置,配置方法如下:
‑> System components ‑> aw components ‑> USB Components Support ‑> USB Gadget Support [*] adb local transport (5555) adb local transport port
adb local transport port 是用于配置端口号的,用于在adb 网络连接时匹配端口的,默认为5555。
在进行网络adb 连接之前,需要保证PC 和小机端在同一个局域网中,并且可以ping 通。
小机端运行ifconfig,查看当前小机端的IP,假设IP为192.168.1.101。
则PC端执行adb connect 192.168.1.101建立连接,然后就可以开始执行adb shell等命令了。
-
【R128】应用开发案例——按键输入
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
按键输入
本文案例代码 下载地址 按键输入案例代码 https://www.aw-ol.com/downloads?cat=24 首先我们搭建电路,如下:
引脚 按键 PA25 按键1脚 GND 按键3脚 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
勾选 GPIO 驱动
mrtos_menuconfig
找到下列驱动Drivers Options ---> soc related device drivers ---> GPIO devices ---> [*] enable GPIO driver [*] enbale GPIO hal APIs Test command
编写程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
引入头文件
#include <hal_gpio.h>
使用 GPIO 配置引脚
配置 GPIO 的上下拉状态
使用
hal_gpio_set_pull(gpio_pin_t pin, gpio_pull_status_t pull);
来设置。这里我们设置PA25
引脚为默认上拉状态。hal_gpio_set_pull(GPIOA(25), GPIO_PULL_UP);
配置 GPIO 输入输出模式
使用
hal_gpio_set_direction(gpio_pin_t pin, gpio_direction_t direction);
来设置 GPIO 的输入输出模式,这里配置为输入模式。hal_gpio_set_direction(GPIOA(25), GPIO_DIRECTION_INPUT);
配置 GPIO 的 MUX 功能
GPIO 通常有多种功能,需要配置 MUX 选择需要的功能,使用
hal_gpio_pinmux_set_function(gpio_pin_t pin, gpio_muxsel_t function_index);
来设置 GPIO 的复用功能,这里配置为GPIO 输入模式(GPIO_MUXSEL_IN
)hal_gpio_pinmux_set_function(GPIOA(25), GPIO_MUXSEL_IN);
获取 GPIO 的电平
使用
int hal_gpio_get_data(gpio_pin_t pin, gpio_data_t *data);
来获取 GPIO 的电平,这里获取 A25 的电平状态。gpio_data_t gpio_data; hal_gpio_get_data(GPIOA(25), &gpio_data);
完整的配置 GPIO
gpio_data_t gpio_data; hal_gpio_set_pull(GPIOA(25), GPIO_PULL_UP); hal_gpio_set_direction(GPIOA(25), GPIO_DIRECTION_INPUT); hal_gpio_pinmux_set_function(GPIOA(25), GPIO_MUXSEL_IN); while(1){ hal_gpio_get_data(GPIOA(25), &gpio_data); if(gpio_data == GPIO_DATA_LOW){ printf("Key Pressed!\n"); } }
结果
按下按键,串口会输出
Key Pressed!
-
【R128】应用开发案例——点亮一颗 LED 灯
基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖,包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。
- DshanMCU-R128s2-R16N16模组:39.9元
- DshanMCU-R128s2-DEVKIT开发板:59.9元
R128开发板购买链接:https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&id=736154682975&ns=1&abbucket=5#detail
点亮一颗 LED 灯
本文案例代码 下载地址 点亮一颗 LED 灯案例代码 https://www.aw-ol.com/downloads?cat=24 首先我们搭建电路,如下:
引脚 LED PA18 红色 LED PA13 绿色 LED PA12 黄色 LED 载入方案
我们使用的开发板是 R128-Devkit,需要开发 C906 核心的应用程序,所以载入方案选择
r128s2_module_c906
$ source envsetup.sh $ lunch_rtos 1
勾选 GPIO 驱动
mrtos_menuconfig
找到下列驱动Drivers Options ---> soc related device drivers ---> GPIO devices ---> [*] enable GPIO driver [*] enbale GPIO hal APIs Test command
编写程序
打开你喜欢的编辑器,修改文件:
lichee/rtos/projects/r128s2/module_c906/src/main.c
引入头文件
#include <hal_gpio.h>
使用 GPIO 配置引脚
配置 GPIO 的上下拉状态
使用
hal_gpio_set_pull(gpio_pin_t pin, gpio_pull_status_t pull);
来设置。这里我们设置PA18
引脚为默认上拉状态。hal_gpio_set_pull(GPIOA(18), GPIO_PULL_UP);
配置 GPIO 输入输出模式
使用
hal_gpio_set_direction(gpio_pin_t pin, gpio_direction_t direction);
来设置 GPIO 的输入输出模式,这里配置为输出模式。hal_gpio_set_direction(GPIOA(18), GPIO_DIRECTION_OUTPUT);
配置 GPIO 的 MUX 功能
GPIO 通常有多种功能,需要配置 MUX 选择需要的功能,使用
hal_gpio_pinmux_set_function(gpio_pin_t pin, gpio_muxsel_t function_index);
来设置 GPIO 的复用功能,这里配置为GPIO 输出模式(GPIO_MUXSEL_OUT
)hal_gpio_pinmux_set_function(GPIOA(18), GPIO_MUXSEL_OUT);
配置 GPIO 的电平
使用
hal_gpio_set_data(gpio_pin_t pin, gpio_data_t data);
来配置 GPIO 的电平,这里配置PA18
为高电平点亮 LEDhal_gpio_set_data(GPIOA(18), GPIO_DATA_HIGH);
完整的配置 GPIO
hal_gpio_set_pull(GPIOA(18), GPIO_PULL_UP); // 配置 GPIO 的上下拉状态 hal_gpio_set_direction(GPIOA(18), GPIO_DIRECTION_OUTPUT); // 配置 GPIO 输入输出模式 hal_gpio_pinmux_set_function(GPIOA(18), GPIO_MUXSEL_OUT); // 配置 GPIO 的 MUX 功能 hal_gpio_set_data(GPIOA(18), GPIO_DATA_HIGH); // 配置 GPIO 的电平
以此类推,我们同时配置
PA18
,PA13
,PA12
的 GPIOhal_gpio_set_pull(GPIOA(18), GPIO_PULL_UP); hal_gpio_set_direction(GPIOA(18), GPIO_DIRECTION_OUTPUT); hal_gpio_pinmux_set_function(GPIOA(18), GPIO_MUXSEL_OUT); hal_gpio_set_data(GPIOA(18), GPIO_DATA_HIGH); hal_gpio_set_pull(GPIOA(13), GPIO_PULL_UP); hal_gpio_set_direction(GPIOA(13), GPIO_DIRECTION_OUTPUT); hal_gpio_pinmux_set_function(GPIOA(13), GPIO_MUXSEL_OUT); hal_gpio_set_data(GPIOA(13), GPIO_DATA_HIGH); hal_gpio_set_pull(GPIOA(12), GPIO_PULL_UP); hal_gpio_set_direction(GPIOA(12), GPIO_DIRECTION_OUTPUT); hal_gpio_pinmux_set_function(GPIOA(12), GPIO_MUXSEL_OUT); hal_gpio_set_data(GPIOA(12), GPIO_DATA_HIGH);
结果
编译固件后烧录,可以看到三色 LED 灯同时亮起。
流水灯
为了实现流水灯,我们先实现一个
sleep
函数static inline int msleep(int ms) { vTaskDelay(ms / portTICK_RATE_MS); }
然后实现流水灯逻辑即可,之前已经设置过的GPIO状态不需要重复设置。
while (1) { hal_gpio_set_data(GPIOA(18), GPIO_DATA_HIGH); hal_gpio_set_data(GPIOA(13), GPIO_DATA_LOW); hal_gpio_set_data(GPIOA(12), GPIO_DATA_LOW); msleep(100); hal_gpio_set_data(GPIOA(18), GPIO_DATA_LOW); hal_gpio_set_data(GPIOA(13), GPIO_DATA_HIGH); hal_gpio_set_data(GPIOA(12), GPIO_DATA_LOW); msleep(100); hal_gpio_set_data(GPIOA(18), GPIO_DATA_LOW); hal_gpio_set_data(GPIOA(13), GPIO_DATA_LOW); hal_gpio_set_data(GPIOA(12), GPIO_DATA_HIGH); msleep(100); }
-
回复: 100ask-t113-pro board 如何 通过buildroot 搭建QT环境
使用buildroot-SDK编译构建系统
简介
- 此套构建系统基于全志T113-S3芯片,适配了buildroot 2022lts主线版本,兼容了百问网的项目课程以及相关组件,真正做到了低耦合,高可用,使用不同的buildroot external tree规格,讲不同的项目 不同的组件分别管理,来实现更容易上手 也更容易学习理解。
安装必要依赖包
ubuntu-18.04
运行环境配置: 此系统基于ubuntu18.04进行验证,在之前的基础之上还需要安装以下必要依赖
book@100ask:~$ sudo apt-get install build-essential subversion git-core libncurses5-dev zlib1g-dev gawk flex quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip lib32z1 lib32z1-dev lib32stdc++6 libstdc++6 libncurses-dev u-boot-tools -y
参考
修改编译指定的配置文件
book@100ask:~/buildroot-100ask_t113-pro/buildroot$ make BR2_EXTERNAL="../br2t113pro ../br2lvgl ../br2qt5" 100ask_t113-pro_sdcard_qt5_defconfig book@100ask:~/buildroot-100ask_t113-pro/buildroot$ make V=1
最终编译生成一个 sdcard.img 烧录至开发板内 启动,就包含了 qt lib库等,同时QT 环境 也会在 buildroot源码根目录下 的 output/host内。
-
回复: H616低温reboot过程中进入休眠
在reboot老化的时候,关闭休眠
从kernel的log来看,安卓层先发起了reboot,内核也收到reboot的消息
#1[2023-07-11,16:56:45][ 41.176369][ T164] init: Received sys.powerctl=‘reboot,’ from pid: 490 (system_server) [2023-07-11,16:56:45][ 41.185428][ T164] init: sys.powerctl: do_shutdown: 0 IsShuttingDown: 0
同时从安卓的log来看,安卓层也会有息屏关机的动作。
所以这个时候是刚好在reboot的过程中,安卓执行休眠打断了reboot并且休眠成功。一般建议做reboot老化的时候要关掉休眠,之前也出过类似现象
-
回复: uart2 蓝牙播放音乐时会出现overrun问题
建议在uart中使用dma搬运数据,V853只有一个核,当uart频率升高,中断频率过高,此时cpu来不及响应uart的中断,所以出现overrun
-
回复: 启动阶段应用自启动时从/dev/random设备中获取随机数阻塞时间长
对于随机数要求不高的应用,可以选择从/dev/urandom设备获取随机数来避免该问题,因为它在熵源不足时会使用伪随机数算法来生成数据,不会出现阻塞现场。
缺点:获取的是伪随机数,随机性差
-
回复: 如何确认是什么中断唤醒的芯片
@newcastle
该部分对应的代码是:src/pm/pm.c : pm_check_wakeup_irqs/* Check if there are any interrupts pending */ addr = (unsigned int *)NVIC->ISPR; for (i = 0; i < DIV_ROUND_UP(NVIC_PERIPH_IRQ_NUM, 32); i++) { val = addr[i]; irq[i] = val & nvic_int_mask[i]; if (irq[i]) { PM_LOGD("nvic[%d]:%x, mask:%x en:%x\n", i, val, nvic_int_mask[i], NVIC->ISER[i]); ret |= PM_WAKEUP_SRC_DEVICES; } }
NVIC->ISPR寄存器也就是中断挂起控制寄存器组,代表有中断产生,每一位对应一个中断,可以看到唤醒中断是nvic[1],8也就是0b1000,也就是对应(nvic[0])31 + 4 = 35号中断。
XR806对应的芯片中断序号在include/driver/chip/chip.h里面定义。
/*!< Interrupt Number Definition */ typedef enum { /* Cortex-M4/3 Processor Exceptions Numbers*/ NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 Memory Management Interrupt */ BusFault_IRQn = -11, /*!< 5 Cortex-M3 Bus Fault Interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M3 Usage Fault Interrupt */ #if defined(CONFIG_CPU_CM33F) SecureFault_IRQn = -9, /*!< 7 Cortex-M33 Secure Fault Interrupt */ #endif SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV Call Interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 Debug Monitor Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M3 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */ /* Specific Interrupt Numbers */ DMA_IRQn = 0, GPIOA_IRQn = 1, #if (CONFIG_CHIP_ARCH_VER == 2) SDC0_IRQn = 2, #endif UART0_IRQn = 4, UART1_IRQn = 5, SPI0_IRQn = 6, #if (CONFIG_CHIP_ARCH_VER == 2) SPI1_IRQn = 7, #endif I2C0_IRQn = 8, I2C1_IRQn = 9, WDG_IRQn = 10, TIMER0_IRQn = 11, TIMER1_IRQn = 12, RTC_SEC_ALARM_IRQn = 13, RTC_WDAY_ALARM_IRQn = 14, #if (CONFIG_CHIP_ARCH_VER == 2) CSI_JPEG_IRQn = 15, #endif I2S_IRQn = 16, PWM_ECT_IRQn = 17, CE_IRQn = 18, GPADC_IRQn = 19, GPIOB_IRQn = 20, IRRX_IRQn = 22, IRTX_IRQn = 23, A_WAKEUP_IRQn = 25, FLASHC_IRQn = 26, UART2_IRQn = 27, #if (CONFIG_CHIP_ARCH_VER == 2) SDC1_IRQn = 28, #endif WIFIC_IRQn = 29, CODEC_DAC_IRQn = 30, CODEC_ADC_IRQn = 31, AVS_IRQn = 32, /* AVS psensor */ #if (CONFIG_CHIP_ARCH_VER == 2) GPIOC_IRQn = 33, PSRAMC_IRQn = 34, #endif #if (CONFIG_CHIP_ARCH_VER == 3) BLE_LL_IRQn = 35, BTCOEX_IRQn = 36, KEYSCAN_IRQn = 37, SMCARD_IRQn = 38, DMA_SEC_IRQn = 39, CAPSEN_IRQn = 40, LPUART0_IRQn = 41, LPUART1_IRQn = 42, TZASC_IRQn = 43, #endif } IRQn_Type;
可以看到是因为蓝牙中断唤醒的,在休眠关闭蓝牙前关闭广播可以解决。
-
回复: JPEG拍照设置旋转90、270度出图异常
JPEG拍照旋转需设置Exif info信息中的旋转参数,否则图片显示时会默认认为旋转角度为0。设置方法如下:
VENC_EXIFINFO_S stExitfInfo; memset(&stExitfInfo, 0, sizeof(VENC_EXIFINFO_S)); stExitfInfo.Orientation = rotate; AW_MPI_VENC_SetJpegExifInfo(venc_chn, &stExitfInfo);
-
回复: OV9716,初始化异常
@zznzzn 看一下驱动[VIN_DEV_I2C_ERR]ov9716_mipi sensor write array Error, array_size 1701!,这里为什么会出现这种写入数组的错误,由于写入数据失败了,导致后面获取不到数据
-
回复: SUNXI相关配置文件不存在,如何补齐?
SDK更新会重命名一些配置名,图一更新为kmod-vin-v4l2。图二,需要在配置界面输入/,查找对应配置,看一下是不是某些依赖没打开。
-
回复: T113 Framebuffer编程 LCD无法显示字符
修改一下lcd_put_ascii函数
void lcd_put_ascii(int x, int y, unsigned char c) { unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16]; int i, b; unsigned char byte; for (i = 0; i < 16; i++) { byte = dots[i]; for (b = 7; b >= 0; b--) { if (byte & (1<<b)) { /* show */ lcd_put_pixel(x+7-b, y+i, 0xffffffff); /* 白 */ } else { /* hide */ lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */ } } } }
-
利用MangoPi MQ Quad部署一个网络摄像头
本文转载自:https://bbs.elecfans.com/jishu_2367603_1_1.html
本文男二号海康威视720p USB摄像头出场,尊容如下:
SBC需连接网络:
root@orangepizero2:~# ifconfig wlan0: flags=4163< UP,BROADCAST,RUNNING,MULTICAST > mtu 1500 inet 192.168.99.217 netmask 255.255.255.0 broadcast 192.168.99.255 ether 2c:d2:6d:32:0e:e4 txqueuelen 1000 (Ethernet) RX packets 25810 bytes 26081078 (24.8 MiB) RX errors 0 dropped 63 overruns 0 frame 0 TX packets 8278 bytes 817894 (798.7 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
一、软件环境
(一)更新软件列表
操作系统基于官方的Debian。
root@orangepizero2:~# sudo apt-get update Get:1 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye InRelease [116 kB] Get:2 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates InRelease [44.1 kB] Get:3 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports InRelease [49.0 kB] Get:4 http://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security InRelease [48.4 kB] Get:5 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main armhf Packages [10.8 MB] Get:6 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main armhf Packages.diff/Index [18.5 kB] Get:7 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main arm64 Packages.diff/Index [18.5 kB] Get:8 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main armhf Packages.diff/Index [63.3 kB] Get:9 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main arm64 Packages.diff/Index [63.3 kB] Get:10 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib armhf Packages.diff/Index [33.0 kB] Get:11 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free arm64 Packages.diff/Index [20.7 kB] Get:12 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free armhf Packages.diff/Index [21.8 kB] Get:13 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib arm64 Packages.diff/Index [40.9 kB] Get:14 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main armhf Packages T-2023-05-24-2006.01-F-2023-05-24-2006.01.pdiff [362 B] Get:15 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main arm64 Packages T-2023-05-24-2006.01-F-2023-05-24-2006.01.pdiff [362 B] Get:16 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main armhf Packages T-2023-07-18-1410.09-F-2023-04-15-1404.36.pdiff [57.5 kB] Get:14 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main armhf Packages T-2023-05-24-2006.01-F-2023-05-24-2006.01.pdiff [362 B] Get:17 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main arm64 Packages T-2023-07-18-1410.09-F-2023-04-15-1404.36.pdiff [58.5 kB] Get:15 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates/main arm64 Packages T-2023-05-24-2006.01-F-2023-05-24-2006.01.pdiff [362 B] Get:16 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main armhf Packages T-2023-07-18-1410.09-F-2023-04-15-1404.36.pdiff [57.5 kB] Get:17 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/main arm64 Packages T-2023-07-18-1410.09-F-2023-04-15-1404.36.pdiff [58.5 kB] Get:18 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib armhf Packages T-2023-05-22-0203.06-F-2023-05-17-1402.44.pdiff [1,601 B] Get:19 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free arm64 Packages T-2023-07-18-0816.49-F-2023-04-23-0208.18.pdiff [2,123 B] Get:20 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free armhf Packages T-2023-07-18-0816.49-F-2023-04-23-0208.18.pdiff [2,123 B] Get:21 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib arm64 Packages T-2023-05-22-0203.06-F-2023-05-17-1402.44.pdiff [2,054 B] Get:18 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib armhf Packages T-2023-05-22-0203.06-F-2023-05-17-1402.44.pdiff [1,601 B] Get:21 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib arm64 Packages T-2023-05-22-0203.06-F-2023-05-17-1402.44.pdiff [2,054 B] Get:20 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free armhf Packages T-2023-07-18-0816.49-F-2023-04-23-0208.18.pdiff [2,123 B] Get:19 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/non-free arm64 Packages T-2023-07-18-0816.49-F-2023-04-23-0208.18.pdiff [2,123 B] Get:22 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/contrib armhf Packages [47.8 kB] Get:23 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/non-free armhf Packages [67.2 kB] Get:24 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/contrib arm64 Packages [49.0 kB] Get:25 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 Packages [10.9 MB] Get:26 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/non-free arm64 Packages [88.2 kB] Get:27 http://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security/main arm64 Packages [315 kB] Get:28 http://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security/main armhf Packages [316 kB] Get:29 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib armhf Packages T-2023-05-22-0203.06-F-2023-05-22-0203.06.pdiff [547 B] Get:29 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-backports/contrib armhf Packages T-2023-05-22-0203.06-F-2023-05-22-0203.06.pdiff [547 B] Fetched 23.3 MB in 26s (889 kB/s) Reading package lists... Done N: Repository 'http://mirrors.tuna.tsinghua.edu.cn/debian bullseye InRelease' changed its 'Version' value from '11.6' to '11.7' N: Repository 'http://mirrors.tuna.tsinghua.edu.cn/debian bullseye InRelease' changed its 'Suite' value from 'stable' to 'oldstable' N: Repository 'http://mirrors.tuna.tsinghua.edu.cn/debian bullseye-updates InRelease' changed its 'Suite' value from 'stable-updates' to 'oldstable-updates' N: Repository 'http://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security InRelease' changed its 'Suite' value from 'stable-security' to 'oldstable-security'
(二)安装libjpeg62-turbo-dev
root@orangepizero2:~# sudo apt-get install libjpeg62-turbo-dev Reading package lists... Done Building dependency tree... Done Reading state information... Done The following NEW packages will be installed: libjpeg62-turbo-dev 0 upgraded, 1 newly installed, 0 to remove and 111 not upgraded. Need to get 255 kB of archives. After this operation, 649 kB of additional disk space will be used. Get:1 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libjpeg62-turbo-dev arm64 1:2.0.6-4 [255 kB] Fetched 255 kB in 1s (498 kB/s) Selecting previously unselected package libjpeg62-turbo-dev:arm64. (Reading database ... 139008 files and directories currently installed.) Preparing to unpack .../libjpeg62-turbo-dev_1%3a2.0.6-4_arm64.deb ... Unpacking libjpeg62-turbo-dev:arm64 (1:2.0.6-4) ... Setting up libjpeg62-turbo-dev:arm64 (1:2.0.6-4) ...
(三)安装imagemagick
root@orangepizero2:~# sudo apt-get install imagemagick Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: imagemagick-6-common imagemagick-6.q16 liblqr-1-0 libmagickcore-6.q16-6 libmagickwand-6.q16-6 Suggested packages: imagemagick-doc autotrace enscript ffmpeg gnuplot grads hp2xx html2ps libwmf-bin mplayer povray radiance sane-utils texlive-base-bin transfig ufraw-batch libmagickcore-6.q16-6-extra Recommended packages: libmagickcore-6.q16-6-extra netpbm gsfonts The following NEW packages will be installed: imagemagick imagemagick-6-common imagemagick-6.q16 liblqr-1-0 libmagickcore-6.q16-6 libmagickwand-6.q16-6 0 upgraded, 6 newly installed, 0 to remove and 111 not upgraded. Need to get 2,802 kB of archives. After this operation, 8,890 kB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 liblqr-1-0 arm64 0.4.2-2.1 [23.9 kB] Get:2 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 imagemagick-6-common all 8:6.9.11.60+dfsg-1.3+deb11u1 [211 kB] Get:3 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libmagickcore-6.q16-6 arm64 8:6.9.11.60+dfsg-1.3+deb11u1 [1,625 kB] Get:4 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libmagickwand-6.q16-6 arm64 8:6.9.11.60+dfsg-1.3+deb11u1 [394 kB] Get:5 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 imagemagick-6.q16 arm64 8:6.9.11.60+dfsg-1.3+deb11u1 [383 kB] Get:6 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 imagemagick arm64 8:6.9.11.60+dfsg-1.3+deb11u1 [166 kB] Fetched 2,802 kB in 3s (845 kB/s) Selecting previously unselected package liblqr-1-0:arm64. (Reading database ... 139029 files and directories currently installed.) Preparing to unpack .../0-liblqr-1-0_0.4.2-2.1_arm64.deb ... Unpacking liblqr-1-0:arm64 (0.4.2-2.1) ... Selecting previously unselected package imagemagick-6-common. Preparing to unpack .../1-imagemagick-6-common_8%3a6.9.11.60+dfsg-1.3+deb11u1_all.deb ... Unpacking imagemagick-6-common (8:6.9.11.60+dfsg-1.3+deb11u1) ... Selecting previously unselected package libmagickcore-6.q16-6:arm64. Preparing to unpack .../2-libmagickcore-6.q16-6_8%3a6.9.11.60+dfsg-1.3+deb11u1_arm64.deb ... Unpacking libmagickcore-6.q16-6:arm64 (8:6.9.11.60+dfsg-1.3+deb11u1) ... Selecting previously unselected package libmagickwand-6.q16-6:arm64. Preparing to unpack .../3-libmagickwand-6.q16-6_8%3a6.9.11.60+dfsg-1.3+deb11u1_arm64.deb ... Unpacking libmagickwand-6.q16-6:arm64 (8:6.9.11.60+dfsg-1.3+deb11u1) ... Selecting previously unselected package imagemagick-6.q16. Preparing to unpack .../4-imagemagick-6.q16_8%3a6.9.11.60+dfsg-1.3+deb11u1_arm64.deb ... Unpacking imagemagick-6.q16 (8:6.9.11.60+dfsg-1.3+deb11u1) ... Selecting previously unselected package imagemagick. Preparing to unpack .../5-imagemagick_8%3a6.9.11.60+dfsg-1.3+deb11u1_arm64.deb ... Unpacking imagemagick (8:6.9.11.60+dfsg-1.3+deb11u1) ... Setting up imagemagick-6-common (8:6.9.11.60+dfsg-1.3+deb11u1) ... Setting up liblqr-1-0:arm64 (0.4.2-2.1) ... Setting up libmagickcore-6.q16-6:arm64 (8:6.9.11.60+dfsg-1.3+deb11u1) ... Setting up libmagickwand-6.q16-6:arm64 (8:6.9.11.60+dfsg-1.3+deb11u1) ... Setting up imagemagick-6.q16 (8:6.9.11.60+dfsg-1.3+deb11u1) ... update-alternatives: using /usr/bin/compare-im6.q16 to provide /usr/bin/compare (compare) in auto mode update-alternatives: using /usr/bin/compare-im6.q16 to provide /usr/bin/compare-im6 (compare-im6) in auto mode update-alternatives: using /usr/bin/animate-im6.q16 to provide /usr/bin/animate (animate) in auto mode update-alternatives: using /usr/bin/animate-im6.q16 to provide /usr/bin/animate-im6 (animate-im6) in auto mode update-alternatives: using /usr/bin/convert-im6.q16 to provide /usr/bin/convert (convert) in auto mode update-alternatives: using /usr/bin/convert-im6.q16 to provide /usr/bin/convert-im6 (convert-im6) in auto mode update-alternatives: using /usr/bin/composite-im6.q16 to provide /usr/bin/composite (composite) in auto mode update-alternatives: using /usr/bin/composite-im6.q16 to provide /usr/bin/composite-im6 (composite-im6) in auto mode update-alternatives: using /usr/bin/conjure-im6.q16 to provide /usr/bin/conjure (conjure) in auto mode update-alternatives: using /usr/bin/conjure-im6.q16 to provide /usr/bin/conjure-im6 (conjure-im6) in auto mode update-alternatives: using /usr/bin/import-im6.q16 to provide /usr/bin/import (import) in auto mode update-alternatives: using /usr/bin/import-im6.q16 to provide /usr/bin/import-im6 (import-im6) in auto mode update-alternatives: using /usr/bin/identify-im6.q16 to provide /usr/bin/identify (identify) in auto mode update-alternatives: using /usr/bin/identify-im6.q16 to provide /usr/bin/identify-im6 (identify-im6) in auto mode update-alternatives: using /usr/bin/stream-im6.q16 to provide /usr/bin/stream (stream) in auto mode update-alternatives: using /usr/bin/stream-im6.q16 to provide /usr/bin/stream-im6 (stream-im6) in auto mode update-alternatives: using /usr/bin/display-im6.q16 to provide /usr/bin/display (display) in auto mode update-alternatives: using /usr/bin/display-im6.q16 to provide /usr/bin/display-im6 (display-im6) in auto mode update-alternatives: using /usr/bin/montage-im6.q16 to provide /usr/bin/montage (montage) in auto mode update-alternatives: using /usr/bin/montage-im6.q16 to provide /usr/bin/montage-im6 (montage-im6) in auto mode update-alternatives: using /usr/bin/mogrify-im6.q16 to provide /usr/bin/mogrify (mogrify) in auto mode update-alternatives: using /usr/bin/mogrify-im6.q16 to provide /usr/bin/mogrify-im6 (mogrify-im6) in auto mode Setting up imagemagick (8:6.9.11.60+dfsg-1.3+deb11u1) ... Processing triggers for desktop-file-utils (0.26-1) ... Processing triggers for hicolor-icon-theme (0.17-2) ... Processing triggers for libc-bin (2.31-13+deb11u5) ... Processing triggers for man-db (2.9.4-2) ... Processing triggers for mailcap (3.69) ...
(四)安装libv4l-dev
root@orangepizero2:~# sudo apt-get install libv4l-dev Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libv4l2rds0 The following NEW packages will be installed: libv4l-dev libv4l2rds0 0 upgraded, 2 newly installed, 0 to remove and 111 not upgraded. Need to get 181 kB of archives. After this operation, 547 kB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libv4l2rds0 arm64 1.20.0-2 [77.2 kB] Get:2 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libv4l-dev arm64 1.20.0-2 [103 kB] Fetched 181 kB in 0s (400 kB/s) Selecting previously unselected package libv4l2rds0:arm64. (Reading database ... 139383 files and directories currently installed.) Preparing to unpack .../libv4l2rds0_1.20.0-2_arm64.deb ... Unpacking libv4l2rds0:arm64 (1.20.0-2) ... Selecting previously unselected package libv4l-dev:arm64. Preparing to unpack .../libv4l-dev_1.20.0-2_arm64.deb ... Unpacking libv4l-dev:arm64 (1.20.0-2) ... Setting up libv4l2rds0:arm64 (1.20.0-2) ... Setting up libv4l-dev:arm64 (1.20.0-2) ... Processing triggers for libc-bin (2.31-13+deb11u5) ...
(五)安装cmake
root@orangepizero2:~# sudo apt-get install cmake Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: cmake-data libjsoncpp24 librhash0 Suggested packages: cmake-doc ninja-build The following NEW packages will be installed: cmake cmake-data libjsoncpp24 librhash0 0 upgraded, 4 newly installed, 0 to remove and 111 not upgraded. Need to get 5,598 kB of archives. After this operation, 29.9 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 cmake-data all 3.18.4-2+deb11u1 [1,725 kB] Get:2 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 libjsoncpp24 arm64 1.9.4-4 [72.5 kB] Get:3 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 librhash0 arm64 1.4.1-2 [127 kB] Get:4 http://mirrors.tuna.tsinghua.edu.cn/debian bullseye/main arm64 cmake arm64 3.18.4-2+deb11u1 [3,673 kB] Fetched 5,598 kB in 4s (1,253 kB/s) Selecting previously unselected package cmake-data. (Reading database ... 139408 files and directories currently installed.) Preparing to unpack .../cmake-data_3.18.4-2+deb11u1_all.deb ... Unpacking cmake-data (3.18.4-2+deb11u1) ... Selecting previously unselected package libjsoncpp24:arm64. Preparing to unpack .../libjsoncpp24_1.9.4-4_arm64.deb ... Unpacking libjsoncpp24:arm64 (1.9.4-4) ... Selecting previously unselected package librhash0:arm64. Preparing to unpack .../librhash0_1.4.1-2_arm64.deb ... Unpacking librhash0:arm64 (1.4.1-2) ... Selecting previously unselected package cmake. Preparing to unpack .../cmake_3.18.4-2+deb11u1_arm64.deb ... Unpacking cmake (3.18.4-2+deb11u1) ... Setting up libjsoncpp24:arm64 (1.9.4-4) ... Setting up librhash0:arm64 (1.4.1-2) ... Setting up cmake-data (3.18.4-2+deb11u1) ... Install cmake-data for emacs Setting up cmake (3.18.4-2+deb11u1) ... Processing triggers for man-db (2.9.4-2) ... Processing triggers for libc-bin (2.31-13+deb11u5) ...
二、源码下载&编译&安装
考虑到芒果派MangoPi MQ Quad开发板SoC较强的处理能力,直接在板子上编译安装mjpg-streamer。
(一)源码下载
从全球最大交友网站下载好mjpg-streamer源码 ,如果很久没有响应,需要检测网络。
git clone https://github.com/jacksonliam/mjpg-streamer.git
root@orangepizero2:/# git clone https://github.com/jacksonliam/mjpg-streamer.git Cloning into 'mjpg-streamer'... remote: Enumerating objects: 2964, done. remote: Total 2964 (delta 0), reused 0 (delta 0), pack-reused 2964 Receiving objects: 100% (2964/2964), 3.48 MiB | 1.89 MiB/s, done. Resolving deltas: 100% (1885/1885), done.
(二)源码编译&安装
进入/mjpg-streamer/mjpg-streamer-experimental 文件夹,执行 make all。
root@orangepizero2:/mjpg-streamer/mjpg-streamer-experimental# make install make -C _build install make[1]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[2]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 13%] Built target mjpg_streamer make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 21%] Built target input_file make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 39%] Built target input_http make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 60%] Built target input_uvc make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 69%] Built target output_file make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 82%] Built target output_http make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [ 91%] Built target output_rtsp make[3]: Entering directory '/mjpg-streamer/mjpg-streamer-experimental/_build' make[3]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' [100%] Built target output_udp make[2]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build' Install the project... -- Install configuration: "Release" -- Installing: /usr/local/bin/mjpg_streamer -- Set runtime path of "/usr/local/bin/mjpg_streamer" to "/usr/local/lib/mjpg-streamer" -- Installing: /usr/local/share/mjpg-streamer/www -- Installing: /usr/local/share/mjpg-streamer/www/spinbtn_updn.gif -- Installing: /usr/local/share/mjpg-streamer/www/static_simple.html -- Installing: /usr/local/share/mjpg-streamer/www/jquery.rotate.js -- Installing: /usr/local/share/mjpg-streamer/www/fix.css -- Installing: /usr/local/share/mjpg-streamer/www/jquery.js -- Installing: /usr/local/share/mjpg-streamer/www/java_simple.html -- Installing: /usr/local/share/mjpg-streamer/www/jquery.ui.core.min.js -- Installing: /usr/local/share/mjpg-streamer/www/sidebarbg.gif -- Installing: /usr/local/share/mjpg-streamer/www/JQuerySpinBtn.js -- Installing: /usr/local/share/mjpg-streamer/www/rotateicons.png -- Installing: /usr/local/share/mjpg-streamer/www/java.html -- Installing: /usr/local/share/mjpg-streamer/www/jquery.ui.custom.css -- Installing: /usr/local/share/mjpg-streamer/www/bodybg.gif -- Installing: /usr/local/share/mjpg-streamer/www/favicon.png -- Installing: /usr/local/share/mjpg-streamer/www/java_control.html -- Installing: /usr/local/share/mjpg-streamer/www/functions.js -- Installing: /usr/local/share/mjpg-streamer/www/javascript_simple.html -- Installing: /usr/local/share/mjpg-streamer/www/cambozola.jar -- Installing: /usr/local/share/mjpg-streamer/www/JQuerySpinBtn.css -- Installing: /usr/local/share/mjpg-streamer/www/index.html -- Installing: /usr/local/share/mjpg-streamer/www/static.html -- Installing: /usr/local/share/mjpg-streamer/www/stream_simple.html -- Installing: /usr/local/share/mjpg-streamer/www/jquery.ui.widget.min.js -- Installing: /usr/local/share/mjpg-streamer/www/jquery.ui.tabs.min.js -- Installing: /usr/local/share/mjpg-streamer/www/control.htm -- Installing: /usr/local/share/mjpg-streamer/www/videolan.html -- Installing: /usr/local/share/mjpg-streamer/www/javascript_motiondetection.html -- Installing: /usr/local/share/mjpg-streamer/www/style.css -- Installing: /usr/local/share/mjpg-streamer/www/javascript.html -- Installing: /usr/local/share/mjpg-streamer/www/LICENSE.txt -- Installing: /usr/local/share/mjpg-streamer/www/favicon.ico -- Installing: /usr/local/share/mjpg-streamer/www/example.jpg -- Installing: /usr/local/share/mjpg-streamer/www/stream.html -- Installing: /usr/local/lib/mjpg-streamer/input_file.so -- Installing: /usr/local/lib/mjpg-streamer/input_http.so -- Installing: /usr/local/lib/mjpg-streamer/input_uvc.so -- Installing: /usr/local/lib/mjpg-streamer/output_file.so -- Installing: /usr/local/lib/mjpg-streamer/output_http.so -- Installing: /usr/local/lib/mjpg-streamer/output_rtsp.so -- Installing: /usr/local/lib/mjpg-streamer/output_udp.so make[1]: Leaving directory '/mjpg-streamer/mjpg-streamer-experimental/_build'
查看下,几个编译后的结果文件都在:
三、服务启动
###(一)查找摄像头
USB摄像头插在芒果派MangoPi MQ Quad上即可,需要检测摄像头挂载在哪个设备下。
root@orangepizero2:/mjpg-streamer/mjpg-streamer-experimental# lsusb Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 002 Device 008: ID 2bdf:0280 SN0002 HIK 720P Camera Bus 002 Device 002: ID 05e3:0608 Genesys Logic, Inc. Hub Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub root@orangepizero2:/mjpg-streamer/mjpg-streamer-experimental# ls -l /dev/video* crw-rw----+ 1 root video 81, 0 Apr 15 06:25 /dev/video0 crw-rw----+ 1 root video 81, 1 Jul 19 14:36 /dev/video1 crw-rw----+ 1 root video 81, 2 Jul 19 14:36 /dev/video2
(二)启动摄像头
直接输入 mjpg_streamer 即可启动服务,默认的是video0,MangoPi MQ Quad开发板的USB摄像头是video1。在启动服务命令中通过“-d /dev/video1” 选项来指定。
y是表示YUV格式。如果没有-y,默认启动是mjpeg格式
-d指定设备
-f 制订帧数,默认30帧
-r指定视频大小,如320×240
-q指定画质,默认80对于输出参数:
-p 指定端口,一般是8080
-w 指定网页目录,设置的是/www目录
-c设置通过密码访问./mjpg_streamer -i "./input_uvc.so -d /dev/video1 -f 30 -q 90 -n" -o "./output_http.so -w ./www"
启动后的日志如下:
四、业务测试
(一)在线视频查看
打开浏览器,访问URL为:[MangoPi MQ Quad开发板 IP]:8080 ,得到的内容如下:
(二)取一张图
取一张图的URL为IP:8080?action=snapshot
-
回复: adb 和usb虚拟串口的切换
adb用functionfs实现的
setusbconfig none, 再insmod g_serial.ko看看
不行再加一句 echo "" > /sys/kernel/config/usb_gadget/g1/UDC -
回复: linux下编译完成后打包安全固件失败
@bigfly 打包TOC0失败一般有2个原因:缺少sboot.bin文件或缺少签名的密钥文件,而sboot.bin文件是在编译阶段时候生成,如果没有sboot.bin文件,那就是编译失败了,不会到打包阶段,因此缺少签名的密钥文件概率最大。
手动在${SDK}/build目录下运行./createkeys,然后再执行打包命令,
注意:
1.第一次打包安全固件必须生成key再打包安全固件。
2.如果执行 rm -rf ${SDK}/out, 需要重新生成key之后再打包安全固件。 -
回复: 全志v853 nspr build错误
QT用外编的方式进行,因为内置的QT是带GPU版本的,会编译报错,可以参考:https://bbs.aw-ol.com/topic/1836/
-
回复: Android10怎么让应用获取到本地蓝牙MAC地址
Android 10及以上的版本可以试一下这样的方法:
在AndroidManifest.xml加入如下权限:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
并且为系统应用:
android:sharedUserId="android.uid.system"
然后可以采用反射的方法来获取:
public static String getBtAddressByReflection() { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) return ""; Field field = null; try { field = BluetoothAdapter.class.getDeclaredField("mService"); field.setAccessible(true); Object bluetoothManagerService = field.get(bluetoothAdapter); if (bluetoothManagerService == null) { return ""; } Method method = bluetoothManagerService.getClass().getMethod("getAddress"); if(method != null) { Object obj = method.invoke(bluetoothManagerService); if(obj != null) { return "/bt_mac:" + obj.toString(); } } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return ""; }
如此便可成功获取到本地蓝牙MAC地址。
-
回复: 如何在显示一段时间后,清除掉overlay?
明显overlay使能还在起作用,如果只是把OverlayInfo.osd_num改成0肯定无法有效清除overlay
这种情况要设置一个完整VideoInputOSD结构体,并全部清空为0,这样同时也会把之前申请的overlay内存free掉
if(stream_count_0 == 数字) { if (mparam.enable_osd) { aw_logw("begin clean osd"); VideoInputOSD OverlayInfo; memset(&OverlayInfo, 0, sizeof(VideoInputOSD)); AWVideoInput_SetOSD(mparam.use_vipp_num, &OverlayInfo); } }
把数字改为自己想要的,就会在这个数字帧清除osd,这个数字帧之后就无osd显示
-
在V853上进行Opencv库的编译步骤
OpenCV(Open Source Computer Vision)是一个开放源代码的计算机视觉库,它提供了一系列函数和算法,用于处理图像和视频。通过使用OpenCV,您可以进行各种计算机视觉任务,例如图像处理、对象识别、目标追踪、人脸检测和机器学习等。它提供了底层图像处理功能,以及高级功能和模块,如特征提取、边缘检测、图像分割和物体测量等。
当V853需要进行图像的预处理操作时,涉及到使用opencv库中的相关接口和函数。所以如果要基于853进行模型的输入预处理或者输出后处理操作时,就不可避免的要依赖到opencv库。
先获取GitHub中的opencv源码:
https://github.com/opencv/opencv
https://github.com/opencv/opencv_contrib进入目录下,创建build目录:
mkdir build cd build/
运行cmake指令配置参数生成Makefile文件:
cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=/xxxxx/opencv/opencv_contrib/modules ..
当然,如果需要将库外置不编入环境的话可以修改CMAKE_INSTALL_PREFIX的值,直接改为对应路径即可。
选择库的相关指令(指定编译/不编译某些库,优化opencv库大小):指定编译opencv_core库:
-DBUILD_opencv_core=ON
指定不编译opencv_hdf库:
-DBUILD_opencv_hdf=OFF
使用make指令进行编译:
make -j4
make完成并不代表结束,还需要生成opencv库使用。
编译完成后,加上install命令,将其加载/注册到usr文件夹中,这样每次编译 就不用使用一整个opencv库/包了。
make install
当编译可执行文件需要链接opencv库时,可以编写一个cmake来编译文件:
# 声明要求的 cmake 最低版本 cmake_minimum_required( VERSION 2.8 ) # 声明一个 cmake 工程 project( test ) #添加OPENMP库 FIND_PACKAGE( OpenMP REQUIRED) if(OPENMP_FOUND) message("OPENMP FOUND") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") endif() #添加OPENCV库 #指定OpenCV版本,代码如下 ##find_package(OpenCV 3.3 REQUIRED) #如果不需要指定OpenCV版本,代码如下 set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/usr/lib/cmake/opencv4) find_package(OpenCV REQUIRED) #添加OpenCV头文件 include_directories(./ ${OpenCV_INCLUDE_DIRS}) # 添加一个可执行程序 # 语法:add_executable( 程序名 源代码文件 ) add_executable(main ${DIRSRCS} ${NPULIB_DIRSRCS}) # 将库文件链接到可执行程序上 target_link_libraries(main ${OpenCV_LIBS} ${VIP_LIBS} -lstdc++ -lpthread -lrt -lm -ldl)
可以看到,该cmake链接了opencv库,
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/usr/lib/cmake/opencv4)
该方法通过链接opencv库中的cmake文件然后来找到对应的opencv包,保证主程序能够顺利调用。
可以在github上下载yolov7处理代码,链接库进行验证。如若cmake生成makefile正常,make编译也正常,成功执行例程过后,opencv库成功生成。
-
回复: PWM背光调节时极性异常
应该是当前内核pwm框架与pwm驱动对于极性的判断相反导致,并且kernel阶段的pwm极性与uboot阶段的不一致。
可以参考下面的解决方法:
添加函数将极性配置与uboot阶段保持一致的极性状态。具体方法是在pwm代码中添加接口:
static void sunxi_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { unsigned int reg_offset; u32 val, sel; sel = pwm->pwm - chip->base; reg_offset = PWM_PCR_BASE + sel * 0x20; val = sunxi_pwm_readl(chip, reg_offset); if (val & BIT_MASK(8)) { state->polarity = PWM_POLARITY_NORMAL; } else { state->polarity = PWM_POLARITY_INVERSED; } } static struct pwm_ops sunxi_pwm_ops = { ... .get_state = sunxi_pwm_get_state, ... };
这个函数的作用是在pwm驱动初始化阶段,读取uboot阶段设置的极性,并将kernel阶段的极性设置与其保持一致。具体内核操作可追流程。
除了修改驱动代码,还需要同对应修改每个板级目录下的board.dts。该修改主要是针对regulator使用pwm进行调压,会在dts中配置pwm调压的极性,为了保证与uboot阶段的电压一致,现在需要把dts中配置的极性的值置位1。
reg_vdd_cpu: vdd-cpu { compatible = "sunxi-pwm-regulator"; - pwms = <&pwm 0 5000 0>; // 第一个参数是使用的pwm通道,第二个参数是频率,第三个参数是极性 + pwms = <&pwm 0 5000 1>; // 第一个参数是使用的pwm通道,第二个参数是频率,第三个参数是极性 regulator-name = "vdd_cpu"; regulator-min-microvolt = <810000>; regulator-max-microvolt = <1160000>; regulator-ramp-delay = <25>; regulator-always-on; regulator-boot-on; status = "okay"; };
在修改完成后,编译打包,确认版型后烧录测试,先看板子能不能起来,起不来的话证明电压极性有误,可以拿万用表测量一下电压,看启动阶段电压有没有发生突变。成功起来后还需要测试一下pwm功能是否正常,最好使用示波器来测一下board.dts里配置的pwm通道的管脚,然后调试该通道看有无波形变化,具体方法如下:
进入/sys/class/pwm目录,该目录是linux内核为pwm子系统提供的类目录,遍历该目录:
/sys/class/pwm # ls pwmchip0
可以看到,上述 pwmchip0 就是我们注册的pwm控制器,进入该目录,然后遍历该目录:
/sys/class/pwm # cd pwmchip0/ /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # ls device export npwm subsystem uevent unexport
其中npwm文件储存了该pwm控制器的pwm个数,而export和unexport是导出和删除某个pwm设备的文件,下面演示导出pwm1。
/sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # cat npwm 2 /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # echo 1 > export /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # ls device export npwm pwm1 subsystem uevent unexport
可以看到目录中多出pwm1目录,进入该目录,遍历:
/sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # cd pwm1/ /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # ls capture duty_cycle enable period polarity uevent
该目录中,enable是使能pwm,duty_cycle是占空比,period是周期,polarity是极性,可以配置相关的pwm并且使能:
/sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # echo 1000000000 > period /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # echo 500000000 > duty_cycle /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # echo normal > polarity /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # echo 1 > enable
如果相应的引脚外接了示波器,可以看到相关的波形。最后返回上层目录,删除该pwm设备:
/sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0/pwm1 # cd .. /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # ls device export npwm pwm1 subsystem uevent unexport /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # echo 1 > unexport /sys/devices/platform/soc/1c23400.pwm/pwm/pwmchip0 # ls device export npwm subsystem uevent unexport
在有条件的情况且能找到相应屏幕的情况下,最好接上屏幕进行验证,确认在uboot阶段进入kernel阶段出现闪屏的情况。
不带屏幕,且没有vdd-cpu的话,pwm初始化极性就不会影响到默认固件正常使用,使用示波器验证就好。