- 2023-11-28
-
发表了主题帖:
【国产高性能运动控制MCU 先楫HPM5361】coremark跑分-CPU性能测试
前言
HPM53xx系类MCU具有高主频,高性能,资源丰富的特点。和Cortex-M4系列性能是相当的,所以使用coremark先来跑个分。
Coremark跑分
可以从如下地址下载源码移植
https://github.com/eembc/coremark
rt-thread有适配好的包也可以直接添加
添加包
可以配置参数,如果运行时间不够10S可加执行次数
Ctrl+s保存
编译运行
命令行输入core_mark查看运行结果
设置优化等级
再次跑分
可以看到得分达到1241, 1241/360M=3.45Coremark/MHz
https://www.eembc.org/coremark/scores.php下可以对比其他MCU的跑分。
可以看到Coremark/MHz得分和STM32F469差不多,但是主频高一倍所以总得分也高一倍。
总结
以上可以看出HPM65xx系类性能是不错的,主频高,资源丰富。
-
回复了主题帖:
【国产高性能运动控制MCU 先楫HPM5361】基于RT-Thread Studio搭建开发环境
lugl4313820 发表于 2023-11-28 07:50
该开发板系列支持不同的开发方式,官方推荐的是SEGGER Embedded Studio for RISC-V ,但是开发环境是商业软 ...
看坑怎么定义,个人觉得99%以上的坑只是因为一些需要注意的问题,
从这个角度来说,每个平台其实都是有坑的,每个平台其实都会有一些需要注意可能不一样的地方。
-
回复了主题帖:
【国产高性能运动控制MCU 先楫HPM5361】基于RT-Thread Studio搭建开发环境
RCSN 发表于 2023-11-28 08:10 别误导,segger embedded studio对于开发先楫商业免费。只需官网申请lic,也很快。
不存在什么误导。
先楫只是通过买断许可,对使用HPMxxxx系列RISC-V微控制器的客户提供免费商用,这改变不了segger embedded studio是商业软件需要授权的事实。
现在segger和先楫都是推广阶段所以才有该"福利", 随着客户量变大,就会涉及利益问题,
先楫和segger还能不能保持继续合作,以后先楫还会不会继续出这个钱做推广,还是未知数。
对于公司技术平台选择,不仅仅要考虑当前,某一个MUC的选择。而是要考虑将来,对于小公司一旦绑定了其平台,以后厂家改变策略,就有的好受了。尤其是小公司将会很被动。
总的来说选择开源的肯定是更安全可控的。
- 2023-11-27
-
发表了主题帖:
【国产高性能运动控制MCU 先楫HPM5361】基于RT-Thread Studio搭建开发环境
前言
本文分享基于RT-Thread Studio搭建开发环境。
准备
安装驱动
sdk_env_v1.3.0\tools\FTDI_InstallDriver.exe
安装RT-Thread Studio
安装最新的rt-thread源码
安装板级支持包
安装工具链
安装调试工具
创建工程
菜单栏
文件->新建->RT-Thread项目
设置编译方式,这里在ram中debug如下选择
编译报错
右键点击工程名字->同步scons配置到项目
再编译就OK了。
仿真
将板子 BOOT PIN 调到 BT0: OFF, BT1:OFF 的位置 。
按照如下短接5个跳线帽。默认官方是使用外部JTAG所以5个跳线帽是断开的,使用板载DEBUG需要短接。
点击甲壳虫进入debug模式
连接串口115200-8-n-1,运行,打印如下
总结
该开发板系列支持不同的开发方式,官方推荐的是SEGGER Embedded Studio for RISC-V ,但是开发环境是商业软件需要授权。 也支持gcc工具链可以结合vscode搭建开发环境但是配置比较繁琐。改系列开发板适配了RT-Thread Studio开发环境搭建更简单推荐使用。
两个需要注意的地方
需要同步scons配置到工程否则编译出错。
使用板载debug需要短路5个跳线帽。板子没有带跳线帽需要自己准备。最好是板子上就带就好了,免得还要用户自己去找。
- 2023-11-25
-
发表了主题帖:
【国产高性能运动控制MCU 先楫HPM5361】开发板介绍与视频
本帖最后由 qinyunti 于 2023-11-25 00:13 编辑
前言
HPM5361EVK是基于先楫HPM5300系列高性能RISC-V内核MCU,的一款开发板。支持双精度浮点运算及强大的DSP扩展,主频480MHz,内置1 MB Flash、288KB SRAM,模拟部分集成16bit ADC、12bit DAC以及运放,支持各类位置传感器,包括光电式、磁感应和旋转变压器,同时提供灵活的编码器输入输出。HPM5300配置两个八通道的PWM模块,同时引入了PLB可编程逻辑单元,提供多种可灵活配置的接口,包含4路CAN-FD、4路LIN、多路UART/SPI/I2C以及USB OTG内置HS PHY,轻松实现各种接口类应用。
HPM5300EVK提供了一系列HPM5300微控制器外设的接口,包括一个ADC输入SMA接口和一个先楫标准的电机控制及传感器接口。HPM5300EVK同时集成了1个2x20 pin IO扩展接口,连接了HPM5300 MCU的大部分IO,供用户自由评估。HPM5300EVK集成了板载调试器,同时提供了一个标准JTAG接口可以连接JLINK、DAPLINK等调试器。
本文即基于原理图和实物介绍下开发板的板载资源。
官网
http://www.hpmicro.com/product/file.html?id=59a247e2-12e8-49bf-9300-3230230394ac
资料下载
https://pan.baidu.com/s/1RaYHOD7xk7fnotmgLpoAlA?pwd=xk2n
原理图与PCB
从以上连接下载原理图和手册。
MCU
树莓派接口
板载调试
aw33901 过电压保护
DLW21SN900SQ2L 共模滤波器
PRTR5V0U2X ESD保护
EEPROM:93LC56BI FT2232配置用 2kb在背面
12M/24M晶体
FT2232实现USB转JTAG和串口
Type-C接口
LIN和CAN
收发器型号分别使用的TJA1042T/3和TJA1021T
485/422
收发器分别使用的MAX3485EESA+T和SIT3490EESA
电机控制
电机相关的信号
BOOT
ADC
按键
LED
USB
电源
总结
本开发板基于先楫HPM5300系列高性能RISC-V MCU,接口丰富,适合工控方面的应用评估,尤其是电机控制。
- 2023-11-20
-
回复了主题帖:
测评入围名单: 国产高性能运动控制MCU 先楫HPM5361
个人信息无误,确认可以完成评测计划
- 2023-11-19
-
加入了学习《follow me 3视频》,观看 follow me 3视频
-
发表了主题帖:
【得捷电子Follow me第3期】+开灯提醒器
一.视频
http://training.eeworld.com.cn/course/68250
二.总结报告
硬件环境
配件如下
主板Seeed Studio XIAO ESP32C3
扩展板Seeed Studio Expansion Board Base for XIAO(包含屏幕、RTC、蜂鸣器、按钮)
天线RF ANT 2.4GHZ/5.5GHZ PCB TRACE-IPEX
排针7-PIN MALE HEADER FOR XIAO 5PACK
线缆CABLE A PLUG TO C PLUG 3.28'
温湿度传感器Grove - AHT20 I2C Industrial Grade Temperature and Humidity Sensor
AHT20
IIC接口地址0x38
光线传感器Grove - Light Sensor v1.2
光敏电阻GL5528(亮电阻10-20k,暗电阻1M)和60k电阻分压,lm358电压跟随输出。
ADC采集
焊接排针装好之后
参考
https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/
任务1:使用MicroPython系统
熟悉Seeed Studio XIAO ESP32C3开发板基本操作,安装esptool,并给开发板刷写MicroPython系统,完成入门程序的运行
搭配器件:Seeed Studio XIAO ESP32C3
3.1 Seeed Studio XIAO ESP32C3开发板熟悉
通过参考链接熟悉开发板,比如熟悉下引脚,原理图等等
3.2 使用esptool工具烧录Micropython固件
下载esptool工具
git clone https://github.com/espressif/esptool.git
下载固件
https://micropython.org/download/esp32c3/
这里最新版是ESP32_GENERIC_C3-20231005-v1.21.0.bin
将下载的bin文件放置于esptool工具的目录esptool/下
安装python库,前提是已经安装了python
右键->在终端中打开,输入如下指令安装对应的库
pip install intelhex
pip install pyserial
按住如下BOOT BUTTON按键,开发板重启进入boot模式
输入如下指令下载固件
python ./esptool.py --chip esp32c3 --port COM9 --baud 921600 --before default_reset --after hard_reset --no-stub write_flash --flash_mode dio --flash_freq 80m 0x0 ESP32_GENERIC_C3-20231005-v1.21.0.bin
其中COM9为对应设备管理器中看到的串口号
ESP32_GENERIC_C3-20231005-v1.21.0.bin是下载的固件名
3.3 Micropython+Thonny体验
1.安装Thonny
以下地址下载安装4.0.2版本https://github.com/thonny/thonny/releases/tag/v4.0.2
注意最新的4.1.3版本不能安装库。
连接开发板
打开thonny
菜单栏点击 运行->配置解释器
按如下配置
连接后可以命令行操作,如下
体验入门程序
输入如下内容,点击保存
import time
from time import sleep
import machine
from machine import Pin, SoftI2C
# Buzzer settings
buzzer_pin = machine.Pin(5, machine.Pin.OUT)
buzzer = machine.PWM(buzzer_pin)
buzzer.freq(1047)
# Buzzer working
while True:
buzzer.duty(10)
time.sleep(1)
buzzer.duty(0)
time.sleep(1)
选择micropython设备
存储为boot.py
点击如下图标运行,可以听到蜂鸣器一秒响一下
四.任务2:驱动扩展板上的OLED屏幕
使用扩展板上的OLED屏幕显示文字和图形
搭配器件:Seeed Studio XIAO ESP32C3、Seeed Studio Expansion Board Base for XIAO
1安装库ssd1306
菜单栏点击工具->管理包
输入库名搜索,安装,如下
网络问题,可能需要多试几次
4.2 显示文字
输入如下代码,保存为boot.py运行
import time
from machine import Pin, SoftI2C
import ssd1306
import math
i2c = SoftI2C(scl=Pin(7), sda=Pin(6)) # Adjust the Pin numbers based on your connections
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
oled.fill(0) # Clear the screen
oled.text("Follow me 3!", 10, 15)
oled.show() # Show the text
效果如下
4.3 显示图形
输入如下代码,保存为boot.py运行
import time
from machine import Pin, SoftI2C
import ssd1306
import math
i2c = SoftI2C(scl=Pin(7), sda=Pin(6))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
center_x = oled_width // 2
center_y = oled_height // 2
square_size = 6 # Size of each square
num_squares = 12 # Number of squares
angle_increment = 2 * math.pi / num_squares
while True:
oled.fill(0) # Clear the screen
for i in range(num_squares):
angle = i * angle_increment
x = int(center_x + (center_x - square_size-30) * math.cos(angle))
y = int(center_y + (center_x - square_size-30) * math.sin(angle))
# Draw all squares
for j in range(num_squares):
angle_j = j * angle_increment
x_j = int(center_x + (center_x - square_size-30) * math.cos(angle_j))
y_j = int(center_y + (center_x - square_size-30) * math.sin(angle_j))
oled.fill_rect(x_j, y_j, square_size, square_size, 1) # Draw the square
oled.fill_rect(x, y, square_size, square_size, 0) # Erase the current square
oled.show()
time.sleep_ms(100) # Pause before next iteration
效果如下
五.任务3:控制蜂鸣器播放音乐
使用Seeed Studio XIAO ESP32C3控制蜂鸣器发出不同频率的声音,并播放一段音乐
搭配器件:Seeed Studio XIAO ESP32C3、Seeed Studio Expansion Board Base for XIAO
5.1 蜂鸣器驱动
输入如下代码,保存为boot.py运行
import time
from time import sleep
import machine
from machine import Pin, SoftI2C
# Buzzer settings
buzzer_pin = machine.Pin(5, machine.Pin.OUT)
buzzer = machine.PWM(buzzer_pin)
buzzer.freq(1047)
# Buzzer working
while True:
buzzer.duty(10)
time.sleep(1)
buzzer.duty(0)
time.sleep(1)
5.2 蜂鸣器播放音乐原理
不同频率对应不同声调,不同占空比对应不同的响度,演奏时间对应节拍。
5.3 蜂鸣器播放音乐
输入如下代码,保存为boot.py运行
import machine
import time
# Buzzer settings
buzzer_pin = machine.Pin(5, machine.Pin.OUT)
buzzer = machine.PWM(buzzer_pin)
buzzer.freq(1047)
# Defining frequency of each music note
NOTE_C4 = 262
NOTE_D4 = 294
NOTE_E4 = 330
NOTE_F4 = 349
NOTE_G4 = 392
NOTE_A4 = 440
NOTE_B4 = 494
NOTE_C5 = 523
NOTE_D5 = 587
NOTE_E5 = 659
NOTE_F5 = 698
NOTE_G5 = 784
NOTE_A5 = 880
NOTE_B5 = 988
# Music notes of the song, 0 is a rest/pulse
notes = [
NOTE_E4, NOTE_G4, NOTE_A4, NOTE_A4, 0,
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_C5, NOTE_D5, NOTE_B4, NOTE_B4, 0,
NOTE_A4, NOTE_G4, NOTE_A4, 0,
NOTE_E4, NOTE_G4, NOTE_A4, NOTE_A4, 0,
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_C5, NOTE_D5, NOTE_B4, NOTE_B4, 0,
NOTE_A4, NOTE_G4, NOTE_A4, 0,
NOTE_E4, NOTE_G4, NOTE_A4, NOTE_A4, 0,
NOTE_A4, NOTE_C5, NOTE_D5, NOTE_D5, 0,
NOTE_D5, NOTE_E5, NOTE_F5, NOTE_F5, 0,
NOTE_E5, NOTE_D5, NOTE_E5, NOTE_A4, 0,
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_D5, NOTE_E5, NOTE_A4, 0,
NOTE_A4, NOTE_C5, NOTE_B4, NOTE_B4, 0,
NOTE_C5, NOTE_A4, NOTE_B4, 0,
NOTE_A4, NOTE_A4,
#Repeat of first part
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_C5, NOTE_D5, NOTE_B4, NOTE_B4, 0,
NOTE_A4, NOTE_G4, NOTE_A4, 0,
NOTE_E4, NOTE_G4, NOTE_A4, NOTE_A4, 0,
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_C5, NOTE_D5, NOTE_B4, NOTE_B4, 0,
NOTE_A4, NOTE_G4, NOTE_A4, 0,
NOTE_E4, NOTE_G4, NOTE_A4, NOTE_A4, 0,
NOTE_A4, NOTE_C5, NOTE_D5, NOTE_D5, 0,
NOTE_D5, NOTE_E5, NOTE_F5, NOTE_F5, 0,
NOTE_E5, NOTE_D5, NOTE_E5, NOTE_A4, 0,
NOTE_A4, NOTE_B4, NOTE_C5, NOTE_C5, 0,
NOTE_D5, NOTE_E5, NOTE_A4, 0,
NOTE_A4, NOTE_C5, NOTE_B4, NOTE_B4, 0,
NOTE_C5, NOTE_A4, NOTE_B4, 0,
#End of Repeat
NOTE_E5, 0, 0, NOTE_F5, 0, 0,
NOTE_E5, NOTE_E5, 0, NOTE_G5, 0, NOTE_E5, NOTE_D5, 0, 0,
NOTE_D5, 0, 0, NOTE_C5, 0, 0,
NOTE_B4, NOTE_C5, 0, NOTE_B4, 0, NOTE_A4,
NOTE_E5, 0, 0, NOTE_F5, 0, 0,
NOTE_E5, NOTE_E5, 0, NOTE_G5, 0, NOTE_E5, NOTE_D5, 0, 0,
NOTE_D5, 0, 0, NOTE_C5, 0, 0,
NOTE_B4, NOTE_C5, 0, NOTE_B4, 0, NOTE_A4
]
# Durations (in ms) of each music note of the song
# Quarter Note is 250 ms when songSpeed = 1.0
durations = [
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 375, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 375, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 125, 250, 125,
125, 125, 250, 125, 125,
250, 125, 250, 125,
125, 125, 250, 125, 125,
125, 125, 375, 375,
250, 125,
#Rpeat of First Part
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 375, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 375, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 250, 125, 125,
125, 125, 125, 250, 125,
125, 125, 250, 125, 125,
250, 125, 250, 125,
125, 125, 250, 125, 125,
125, 125, 375, 375,
#End of Repeat
250, 125, 375, 250, 125, 375,
125, 125, 125, 125, 125, 125, 125, 125, 375,
250, 125, 375, 250, 125, 375,
125, 125, 125, 125, 125, 500,
250, 125, 375, 250, 125, 375,
125, 125, 125, 125, 125, 125, 125, 125, 375,
250, 125, 375, 250, 125, 375,
125, 125, 125, 125, 125, 500
]
def play_song():
total_notes = len(notes)
for i in range(total_notes):
current_note = notes
wait = durations
if current_note != 0:
buzzer.duty(512) # Set duty cycle for sound
buzzer.freq(current_note) # Set frequency of the note
else:
buzzer.duty(0) # Turn off the sound
time.sleep_ms(wait)
buzzer.duty(0) # Turn off the sound
while True:
# Play the song
play_song()
六.任务4:连接WiFi网络)
将Seeed Studio XIAO ESP32C3连接到WiFi网络,并访问互联网信息
搭配器件:Seeed Studio XIAO ESP32C3、Seeed Studio Expansion Board Base for XIAO、RF ANT 2.4GHZ/5.5GHZ PCB TRACE
6.1 连接WIFI
输入如下代码,保存为boot.py运行
import network
import urequests
import utime as time
# Network settings
wifi_ssid = "qiqiqiqi"
wifi_password = "cqmygysdss"
def scan_and_connect():
station = network.WLAN(network.STA_IF)
station.active(True)
print("Scanning for WiFi networks, please wait...")
for ssid, bssid, channel, RSSI, authmode, hidden in station.scan():
print("* {:s}".format(ssid))
print(" - Channel: {}".format(channel))
print(" - RSSI: {}".format(RSSI))
print(" - BSSID: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}".format(*bssid))
print()
while not station.isconnected():
print("Connecting...")
station.connect(wifi_ssid, wifi_password)
time.sleep(10)
print("Connected!")
print("My IP Address:", station.ifconfig()[0])
# Execute the functions
scan_and_connect()
6.2 获取网络时间
from machine import Pin, SoftI2C
import ssd1306
from time import sleep
import time
import network
import urequests
import ujson
# ESP32 Pin assignment
# i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
# ESP8266 Pin assignment
i2c = SoftI2C(scl=Pin(7), sda=Pin(6)) # Adjust the Pin numbers based on your connections
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
station = network.WLAN(network.STA_IF)
station.active(True)
# Network settings
wifi_ssid = "qiqiqiqi"
wifi_password = "cqmygysdss"
url = "http://worldtimeapi.org/api/timezone/America/New_York"
print("Scanning for WiFi networks, please wait...")
authmodes = ['Open', 'WEP', 'WPA-PSK' 'WPA2-PSK4', 'WPA/WPA2-PSK']
for (ssid, bssid, channel, RSSI, authmode, hidden) in station.scan():
print("* {:s}".format(ssid))
print(" - Channel: {}".format(channel))
print(" - RSSI: {}".format(RSSI))
print(" - BSSID: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}".format(*bssid))
print()
# Continually try to connect to WiFi access point
while not station.isconnected():
# Try to connect to WiFi access point
print("Connecting...")
station.connect(wifi_ssid, wifi_password)
time.sleep(10)
# Display connection details
print("Connected!")
print("My IP Address:", station.ifconfig()[0])
while True:
# Perform HTTP GET request on a non-SSL web
response = urequests.get(url)
# Check if the request was successful
if response.status_code == 200:
# Parse the JSON response
data = ujson.loads(response.text)
# Extract the "datetime" field for New York
ny_datetime = data["datetime"]
# Split the date and time components
date_part, time_part = ny_datetime.split("T")
# Get only the first two decimal places of the time
time_part = time_part[:8]
# Get the timezone
timezone = data["timezone"]
# Clear the OLED display
oled.fill(0)
# Display the New York date and time on separate lines
oled.text("New York Date:", 0, 0)
oled.text(date_part, 0, 10)
oled.text("New York Time:", 0, 20)
oled.text(time_part, 0, 30)
oled.text("Timezone:", 0, 40)
oled.text(timezone, 0, 50)
# Update the display
oled.show()
else:
oled.text("Failed to get the time for New York!")
# Update the display
oled.show()
6.3 WIFI信号强度追踪
import network
import time
from time import sleep
import machine
from machine import Pin, SoftI2C
import ssd1306
import math
# ESP32C3 Pin assignment
i2c = SoftI2C(scl=Pin(7), sda=Pin(6)) # Adjust the Pin numbers based on your connections
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
# Network settings
wifi_ssid = "qiqiqiqi"
wifi_password = "cqmygysdss"
machine.freq(160000000) # Set CPU frequency to 160 MHz (ESP8266 specific)
oled.text("Starting up...", 0, 0)
oled.show()
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(wifi_ssid, wifi_password)
time.sleep(1)
while not station.isconnected():
time.sleep(1)
oled.fill(0)
oled.text("Connecting to", 0, 0)
oled.text(wifi_ssid, 0, 20)
oled.show()
time.sleep(2)
oled.fill(0)
ip_address = station.ifconfig()[0] # Get the IP address
oled.text("Connected! ", 0, 0)
oled.text("IP Address:", 0, 20)
oled.text(ip_address, 0, 40)
oled.show()
time.sleep(2)
# Buzzer settings
buzzer_pin = machine.Pin(5, machine.Pin.OUT)
buzzer = machine.PWM(buzzer_pin)
buzzer.freq(1047)
buzzer.duty(0)
center_x = oled_width // 2
center_y = oled_height // 2
square_size = 6 # Size of each square
num_squares = 12 # Number of squares
angle_increment = 2 * math.pi / num_squares
x_pos = [12, 38, 64, 90]
statuses = ["poor", "normal", "good", "excellent"]
def calculate_block_count(rssi):
# Determine the number of blocks based on RSSI values
if -80 <= rssi < -60:
return 1
elif -60 <= rssi < -40:
return 2
elif -40 <= rssi < -20:
return 3
elif -20 <= rssi <= 10:
return 4
def draw_blocks(count):
for i in range(count):
y_pos = 50 - calculate_block_height(i)
oled.fill_rect(x_pos, y_pos, 24, calculate_block_height(i), 1)
for i in range(count, 4): # Clear unused area
y_pos = 50 - calculate_block_height(i)
oled.fill_rect(x_pos, y_pos, 24, calculate_block_height(i), 0)
def calculate_block_height(index):
return 10 * (index + 1)
loop_count = 0 # Initialize loop count
while loop_count < 2: # Execute the loop 24 times
oled.fill(0) # Clear the screen
for i in range(num_squares):
angle = i * angle_increment
x = int(center_x + (center_x - square_size-30) * math.cos(angle))
y = int(center_y + (center_x - square_size-30) * math.sin(angle))
# Draw all squares
for j in range(num_squares):
angle_j = j * angle_increment
x_j = int(center_x + (center_x - square_size-30) * math.cos(angle_j))
y_j = int(center_y + (center_x - square_size-30) * math.sin(angle_j))
oled.fill_rect(x_j, y_j, square_size, square_size, 1) # Draw the square
oled.fill_rect(x, y, square_size, square_size, 0) # Erase the current square
oled.show()
time.sleep_ms(100) # Pause before next iteration
loop_count += 1 # Increase loop count
oled.fill(0) # Clear the screen after finishing the loops
oled.show()
while True:
oled.fill(0)
station = network.WLAN(network.STA_IF)
time.sleep(0.1)
rssi = station.status('rssi')
rssi_duty = 160 + 2 * int(rssi)
rssi_duty_2 = int(rssi_duty / 2)
rssi_abs = abs(int(rssi)) / 100
block_count = calculate_block_count(rssi)
status = statuses[block_count - 1] # Get the status text based on block count
draw_blocks(block_count)
oled.text(status, 11, 56)
oled.text("RSSI:", 0, 0)
oled.text(str(rssi), 40, 0)
# Update the display
oled.show()
buzzer.duty(rssi_duty)
time.sleep(rssi_abs)
buzzer.duty(0)
time.sleep(rssi_abs)
buzzer.duty(rssi_duty_2)
time.sleep(rssi_abs)
buzzer.duty(0)
time.sleep(rssi_abs)
七.任务5:使用外部传感器
连接环境光传感器或温湿度传感器,获取传感器的数值,并转换成真实的物理量
搭配器件: Seeed Studio XIAO ESP32C3、Seeed Studio Expansion Board Base for XIAO、 Grove - AHT20 I2C Industrial Grade Temperature and Humidity Sensor、Grove - Light Sensor v1.2
获取光线传感器值
光线传感器使用图中A0,GPIO2
光线充足时2.36V
光线暗时420mV
from machine import ADC
from machine import Pin
import time
while True:
adc = ADC(Pin(2),atten=ADC.ATTN_11DB) # create an ADC object acting on a pin
val = adc.read_u16() # read a raw analog value in the range 0-65535
print("ADC:"+str(val))
val = adc.read_uv() # read an analog value in microvolts
print("Vol:"+str(val/1000)+"mV")
time.sleep(1)
光线充足和暗时采集值分别如下
获取温湿度值
搜索安装AHT20库micropython-ahtx0
SDA引脚为GPIO6
SCL引脚为GPIO7
import utime
from machine import Pin, I2C
import ahtx0
i2c = I2C(scl=Pin(7), sda=Pin(6))
# Create the sensor object using I2C
sensor = ahtx0.AHT20(i2c)
while True:
print("\nTemperature: %0.2f C" % sensor.temperature)
print("Humidity: %0.2f %%" % sensor.relative_humidity)
utime.sleep(5)
测试结果如下
八.任务6:
基于前面的任务,这里实现分任务3:开灯提醒器
屏幕可以显示温湿度
读取光线传感器的值,在光线不足时屏幕和蜂鸣器提示开灯
注意判断的回滞处理避免抖动
代码如下
import utime
from machine import Pin, I2C, PWM
import ahtx0
from machine import SoftI2C
import ssd1306
import math
from machine import ADC
# Buzzer settings
buzzer_pin = Pin(5, Pin.OUT)
buzzer = PWM(buzzer_pin)
buzzer.freq(1047)
i2coled = SoftI2C(scl=Pin(7), sda=Pin(6)) # Adjust the Pin numbers based on your connections
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2coled)
i2caht20 = I2C(scl=Pin(7), sda=Pin(6))
# Create the sensor object using I2C
sensor = ahtx0.AHT20(i2caht20)
state = 0
while True:
oled.fill(0) # Clear the screen
print("\nTemperature: %0.2f C" % sensor.temperature)
print("Humidity: %0.2f %%" % sensor.relative_humidity)
oled.text("T:"+str(sensor.temperature), 10, 10)
oled.text("H:"+str(sensor.relative_humidity), 10, 20)
adc = ADC(Pin(2),atten=ADC.ATTN_11DB)
val = adc.read_uv() # read an analog value in microvolts
val = val / 1000;
print("Vol:"+str(val)+"mV")
oled.text("L:"+str(val), 10, 30)
# 回滞处理,避免抖动
if state==0: #初始直接判断
if val < 2000:
state = 1
oled.text("Light On", 10, 50)
buzzer.duty(10)
else:
state = 2
oled.text("Light Off", 10, 50)
buzzer.duty(0)
elif state==1: #等待达到高阈值
if val > 2200:
state = 2
oled.text("Light Off", 10, 50)
buzzer.duty(0)
else:
oled.text("Light On", 10, 50)
#buzzer.duty(10)
elif state==2: #等待达到低阈值
if val < 1800:
state = 1
oled.text("Light On", 10, 50)
buzzer.duty(10)
else:
oled.text("Light Off", 10, 50)
#buzzer.duty(0)
else:
state = 0
oled.show() # Show the text
print(state)
utime.sleep(0.5)
详见视频
总结
基于上述多个任务熟悉了Seeed Studio XIAO ESP32C3的开发, 并综合实现了一个开灯提醒器的Demo。本次活动非常有意思,也体验了microython开发的便利性。希望后面能继续参加。
三.代码
http://download.eeworld.com.cn/detail/qinyunti/629902
-
上传了资料:
Follow me 3代码
- 2023-11-15
-
发表了主题帖:
【玄铁杯第三届RISC-V应用创新大赛】分享一个解决vnc+xfce远程桌面卡顿的小tips
安装vnc+xfce后发现远程操作桌面非常卡顿,于是top看一下
发现xfwm4的进程占用CPU非常多
于是搜索下找到如下解决方法
https://bugzilla.xfce.org/show_bug.cgi?id=15963
Vnc的终端中输入
xfwm4 --vblank=off --replace
再来看CPU占用减少了很多,并且操作流畅了很多。
- 2023-11-01
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 基于UVC的FRAMEBUFFER-艺术图片
本帖最后由 qinyunti 于 2023-11-1 22:03 编辑
前言
前面我们实现了NV12格式的UVC显示,基于此我们可以实现framebuffer机制,实现任意点的颜色的读写,然后移植LVGL进行GUI开发。由于使用YUV格式需要格式转换,且需要根据采样进行插值,直接修改某点的颜色不方便,所以我们使用RGB格式,为了减少缓存大小使用RGB565格式。
YUV-RGB565-FRAMEBUFFER
usbd_video.c修改描述符的GUID为
DBVAL(0xe436eb7b),
WBVAL(0x524f),
WBVAL(0x11ce),
0x9f,0x53,
0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70,
UVC_BITS_PER_PIXEL, /* bBitsPerPixel : Number of bits per pixel 16*/
usbd_video_if_template.c修改大小
extern uint8_t s_framebuffer[H_SIZE*V_SIZE*2];
const uint8_t *tImagesList[] = {s_framebuffer};
uint32_t tImagesSizes[] = {H_SIZE*V_SIZE*2};
framebuffer.c中实现framebuffer,其中framebuffer_update测试用RGB轮显
#include <stdint.h>
#include "stm32f7xx_hal.h"
#include "encode_dma.h"
#include "framebuffer.h"
uint8_t s_framebuffer[H_SIZE*V_SIZE*2];
uint32_t color_table[] =
{
0xFF0000, /* */
0x00FF00, /* */
0x0000FF, /* */
};
void framebuffer_set(uint32_t xpos, uint32_t ypos, uint8_t r, uint8_t g, uint8_t b)
{
uint16_t color = (((uint16_t)r&0xF8)<<8) | (((uint16_t)g&0xFC)<<3) | (((uint16_t)b&0xF8)>>3);
uint8_t* p = s_framebuffer + (xpos*H_SIZE+ypos)*2;
*(uint16_t*)p = color;
}
void framebuffer_init(void)
{
}
void framebuffer_update(void)
{
static unsigned int index = 0;
uint8_t* p = (uint8_t*)s_framebuffer;
uint8_t r;
uint8_t g;
uint8_t b;
if(index >= sizeof(color_table)/sizeof(color_table[0]))
{
index = 0;
}
r = (color_table[index] >> 16) & 0xFF;
g = (color_table[index] >> 8) & 0xFF;
b = (color_table[index] >> 0) & 0xFF;
for(int x=0; x<H_SIZE; x++)
{
for(int y=0; y<V_SIZE; y++)
{
framebuffer_set(x, y, r, g, b);
}
}
index++;
}
艺术图片测试
基于任意的写点函数,通过一些数学公式产生有意思的图片。
代码如下
#include <stdint.h>
#include "stm32f7xx_hal.h"
#include "encode_dma.h"
#include "framebuffer.h"
#include <math.h>
uint8_t s_framebuffer[H_SIZE*V_SIZE*2];
#define DIM H_SIZE
#define DM1 (DIM-1)
#define _sq(x) ((x)*(x))
#define _cb(x) abs((x)*(x)*(x))
#define _cr(x) (unsigned short)(pow((x),1.0/3.0))
unsigned char RD(int i,int j){
return (char)(_sq(cos(atan2(j-65,i-65)/2))*255);
}
unsigned char GR(int i,int j){
return (char)(_sq(cos(atan2(j-65,i-65)/2-2*acos(-1)/3))*255);
}
unsigned char BL(int i,int j){
return (char)(_sq(cos(atan2(j-65,i-65)/2+2*acos(-1)/3))*255);
}
unsigned char RD0(int i,int j)
{
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)((i+DIM)*s+y)%2+(int)((DIM*2-i)*s+y)%2)*127;
}
unsigned char GR0(int i,int j){
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)(5*((i+DIM)*s+y))%2+(int)(5*((DIM*2-i)*s+y))%2)*127;
}
unsigned char BL0(int i,int j){
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)(29*((i+DIM)*s+y))%2+(int)(29*((DIM*2-i)*s+y))%2)*127;
}
unsigned char RD1(int i, int j) {
#define r(n)(rand()%n)
static char c[DIM][DIM]; return!c[i][j] ? c[i][j] = !r(999) ? r(256) : RD((i + r(2)) % 1024, (j + r(2)) % 1024) : c[i][j];
}
unsigned char GR1(int i, int j) {
static char c[DIM][DIM]; return!c[i][j] ? c[i][j] = !r(999) ? r(256) : GR((i + r(2)) % 1024, (j + r(2)) % 1024) : c[i][j];
}
unsigned char BL1(int i, int j) {
static char c[DIM][DIM]; return!c[i][j] ? c[i][j] = !r(999) ? r(256) : BL((i + r(2)) % 1024, (j + r(2)) % 1024) : c[i][j];
}
unsigned char RD2(int i, int j) {
static double k; k += rand() / 1. / 0x7FFF; int l = k; l %= 512; return l > 255 ? 511 - l : l;
}
unsigned char GR2(int i, int j) {
static double k; k += rand() / 1. / 0x7FFF; int l = k; l %= 512; return l > 255 ? 511 - l : l;
}
unsigned char BL2(int i, int j) {
static double k; k += rand() / 1. / 0x7FFF; int l = k; l %= 512; return l > 255 ? 511 - l : l;
}
unsigned char RD3(int i, int j) {
return (unsigned char)sqrt((double)(_sq(i - DIM / 2) * _sq(j - DIM / 2)) * 2.0);
}
unsigned char GR3(int i, int j) {
return (unsigned char)sqrt((double)(
(_sq(i - DIM / 2) | _sq(j - DIM / 2)) *
(_sq(i - DIM / 2) & _sq(j - DIM / 2))
));
}
unsigned char BL3(int i, int j) {
return (unsigned char)sqrt((double)(_sq(i - DIM / 2) & _sq(j - DIM / 2)) * 2.0);
}
unsigned char RD4(int i, int j) {
static int r[DIM]; int p = rand() % 9 - 4; r[i] = i & r[i] ? (r[i] + r[i - 1]) / 2 : i ? r[i - 1] : 512; r[i] += r[i] + p > 0 ? p : 0; return r[i] ? r[i] < DIM ? r[i] : DM1 : 0;
}
unsigned char GR4(int i, int j) {
static int r[DIM]; int p = rand() % 7 - 3; r[i] = i & r[i] ? (r[i] + r[i - 1]) / 2 : i ? r[i - 1] : 512; r[i] += r[i] + p > 0 ? p : 0; return r[i] ? r[i] < DIM ? r[i] : DM1 : 0;
}
unsigned char BL4(int i, int j) {
static int r[DIM]; int p = rand() % 15 - 7; r[i] = i & r[i] ? (r[i] + r[i - 1]) / 2 : i ? r[i - 1] : 512; r[i] += r[i] + p > 0 ? p : 0; return r[i] ? r[i] < DIM ? r[i] : DM1 : 0;
}
void test0(void)
{
for(int i=0;i<H_SIZE;i++)
{
for(int j=0;j<V_SIZE;j++)
{
static unsigned short color[3];
color[0] = RD(i,j)&255;
color[1] = GR(i,j)&255;
color[2] = BL(i,j)&255;
framebuffer_set(i, j, color[0], color[1], color[2]);
}
}
}
void test1(void)
{
for(int i=0;i<H_SIZE;i++)
{
for(int j=0;j<V_SIZE;j++)
{
static unsigned short color[3];
color[0] = RD1(i,j)&255;
color[1] = GR1(i,j)&255;
color[2] = BL1(i,j)&255;
framebuffer_set(i, j, color[0], color[1], color[2]);
}
}
}
void test2(void)
{
for(int i=0;i<H_SIZE;i++)
{
for(int j=0;j<V_SIZE;j++)
{
static unsigned short color[3];
color[0] = RD2(i,j)&255;
color[1] = GR2(i,j)&255;
color[2] = BL2(i,j)&255;
framebuffer_set(i, j, color[0], color[1], color[2]);
}
}
}
void test3(void)
{
for(int i=0;i<H_SIZE;i++)
{
for(int j=0;j<V_SIZE;j++)
{
static unsigned short color[3];
color[0] = RD3(i,j)&255;
color[1] = GR3(i,j)&255;
color[2] = BL3(i,j)&255;
framebuffer_set(i, j, color[0], color[1], color[2]);
}
}
}
void test4(void)
{
for(int i=0;i<H_SIZE;i++)
{
for(int j=0;j<V_SIZE;j++)
{
static unsigned short color[3];
color[0] = RD4(i,j)&255;
color[1] = GR4(i,j)&255;
color[2] = BL4(i,j)&255;
framebuffer_set(i, j, color[0], color[1], color[2]);
}
}
}
uint32_t color_table[] =
{
0xFF0000, /* */
0x00FF00, /* */
0x0000FF, /* */
};
typedef void (*test_pf)(void);
test_pf pf_table[] =
{
test0, /* */
test1, /* */
test2, /* */
test3, /* */
test4, /* */
};
void framebuffer_set(uint32_t xpos, uint32_t ypos, uint8_t r, uint8_t g, uint8_t b)
{
uint16_t color = (((uint16_t)r&0xF8)<<8) | (((uint16_t)g&0xFC)<<3) | (((uint16_t)b&0xF8)>>3);
uint8_t* p = s_framebuffer + (xpos*H_SIZE+ypos)*2;
*(uint16_t*)p = color;
}
void framebuffer_init(void)
{
}
void framebuffer_update(void)
{
#if 0
static unsigned int index = 0;
uint8_t* p = (uint8_t*)s_framebuffer;
uint8_t r;
uint8_t g;
uint8_t b;
if(index >= sizeof(color_table)/sizeof(color_table[0]))
{
index = 0;
}
r = (color_table[index] >> 16) & 0xFF;
g = (color_table[index] >> 8) & 0xFF;
b = (color_table[index] >> 0) & 0xFF;
for(int x=0; x<H_SIZE; x++)
{
for(int y=0; y<V_SIZE; y++)
{
framebuffer_set(x, y, r, g, b);
}
}
index++;
#else
static unsigned int index = 0;
if(index >= sizeof(pf_table)/sizeof(pf_table[0]))
{
index = 0;
}
pf_table[2]();
//pf_table[index]();
index++;
#endif
}
总结
以上实现了基于UVC的FRAMEBUFFER可以进行任意写点,并进行了有意思的艺术图片生成测试。
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 实现基于USB-UVC的摄像头(显示)
[localvideo]e219daa6f8eb914b85ae25465e184ecb[/localvideo]
前言
前面我们实现了UVC设备的枚举与打开关闭。现在我们来添加数据流实现UVC的显示。
先来实现NV12格式,stm32f7还支持硬件jpeg编解码,后面有时间再实现jpeg格式。
过程
修改配置描述符支持NV12格式
usbd_video.h开头中定义#define USBD_UVC_FORMAT_UNCOMPRESSED
usbd_video.h中
默认GUID为NV12
#define UVC_GUID_YUY2 0x32595559U
#define UVC_GUID_NV12 0x3231564EU
#ifndef UVC_UNCOMPRESSED_GUID
#define UVC_UNCOMPRESSED_GUID UVC_GUID_NV12
#endif /* UVC_UNCOMPRESSED_GUID */
修改图片大小
/* These defines shall be updated in the usbd_conf.h file */
#ifndef UVC_WIDTH
#define UVC_WIDTH 240U
#endif /* UVC_WIDTH */
#ifndef UVC_HEIGHT
#define UVC_HEIGHT 240U
可以看到格式和大小都变过来了
使能sof中断
usbd_conf.c的USBD_LL_Init
hpcd.Init.Sof_enable = 1;
因为需要在USBD_VIDEO_SOF回调中进行一次发送,触发发送完中断,然后在后面中断中进行图片数据发送,形成发送流。
hpcd.Init.dma_enable = 1;
添加测试数据源头
生成RGB格式的NV12数据
uint8_t s_framebuffer[H_SIZE*V_SIZE*3/2];
//uint8_t s_framebuffer_out[H_SIZE*V_SIZE];
uint32_t color_table[] =
{
0xFF0000, /* */
0x00FF00, /* */
0x0000FF, /* */
};
/*
Y = 0.298R + 0.612G + 0.117B; [13,235]
U = -0.168R - 0.330G + 0.498B + 128; [16,239]
V = 0.449R - 0.435G - 0.083B + 128; [16,239]
*/
static void update_buffer(void)
{
uint8_t* p = (uint8_t*)s_framebuffer;
static unsigned int index = 0;
int y = 0;
int u = 0;
int v = 0;
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
if(index >= sizeof(color_table)/sizeof(color_table[0]))
{
index = 0;
}
r = (color_table[index] >> 16) & 0xFF;
g = (color_table[index] >> 8) & 0xFF;
b = (color_table[index] >> 0) & 0xFF;
index++;
y = (0.298*r + 0.612*g + 0.117*b);
u = (-0.168*r - 0.330*g + 0.498*b + 128);
v = (0.449*r - 0.435*g - 0.083*b + 128);
if(y>235)
{
y=235;
}
if(y<16)
{
y=16;
}
if(u>239)
{
u=239;
}
if(u<16)
{
u=16;
}
if(v>239)
{
v=239;
}
if(v<16)
{
v=16;
}
for(int i=0;i<H_SIZE*V_SIZE;i++)
{
*p++ = y;
}
for(int i=0;i<H_SIZE*V_SIZE/4;i++)
{
*p++ = u;
*p++ = v;
}
}
解决BUG
usbd_video_if_template.c中
static uint8_t packet_index = 0U; 索引值在UVC_PACKET_SIZE比较小,tImagesSizes[x]比较大时会溢出,该为static uint32_t packet_index = 0U;
VIDEO_Itf_Data中是在中断中处理的,此时进行Delay会阻塞中断处理,并且也必须systick优先级大于USB中断优先级才能systick中断嵌套tick继续走行。我们直接注释掉即可。这里只显示一个图片,不控制图片间的间隔。
//USBD_Delay(USBD_VIDEO_IMAGE_LAPS);
优化-解决更新图片导致花屏的问题
修改为发送完一帧才能更新framebuffer,否则正在发送时修改会花屏。
USBD_VIDEO_SOF中注释掉发送,在主循环中触发发送流
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] USBD_VIDEO_SOF
* handle SOF event
* @param pdev: device instance
* @retval status
*/
static uint8_t USBD_VIDEO_SOF(USBD_HandleTypeDef *pdev)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit[pdev->classId];
uint8_t payload[2] = {0x02U, 0x00U};
/* Check if the Streaming has already been started by SetInterface AltSetting 1 */
if (hVIDEO->uvc_state == UVC_PLAY_STATUS_READY)
{
/* Transmit the first packet indicating that Streaming is starting */
//(void)USBD_LL_Transmit(pdev, VIDEOinEpAdd, (uint8_t *)payload, 2U);
/* Enable Streaming state */
hVIDEO->uvc_state = UVC_PLAY_STATUS_STREAMING;
hVIDEO->sending = 0;
}
/* Exit with no error code */
return (uint8_t)USBD_OK;
}
主函数中触发发送
while (1)
{
uart_debug_send(16);
shell_exec_shellcmd();
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->uvc_state) == UVC_PLAY_STATUS_STREAMING)
{
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->sending) == 0)
{
((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->sending = 1;
framebuffer_update();
(void)USBD_LL_Transmit(&USBD_Device, 0x81, (uint8_t *)payload, 2U);
}
}
发送完一帧停止等待主函数中重新启动
发送完一帧不再发送
static uint8_t USBD_VIDEO_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit[pdev->classId];
static uint8_t packet[UVC_PACKET_SIZE + (UVC_HEADER_PACKET_CNT * 2U)] = {0x00U};
static uint8_t *Pcktdata = packet;
static uint16_t PcktIdx = 0U;
static uint16_t PcktSze = UVC_PACKET_SIZE;
static uint8_t payload_header[2] = {0x02U, 0x00U};
uint8_t i = 0U;
uint32_t RemainData, DataOffset = 0U;
/* Check if the Streaming has already been started */
if (hVIDEO->uvc_state == UVC_PLAY_STATUS_STREAMING)
{
/* Get the current packet buffer, index and size from the application layer */
((USBD_VIDEO_ItfTypeDef *)pdev->pUserData[pdev->classId])->Data(&Pcktdata, &PcktSze, &PcktIdx);
/* Check if end of current image has been reached */
if (PcktSze > 2U)
{
/* Check if this is the first packet in current image */
if (PcktIdx == 0U)
{
/* Set the packet start index */
payload_header[1] ^= 0x01U;
}
RemainData = PcktSze;
/* fill the Transmit buffer */
while (RemainData > 0U)
{
packet[((DataOffset + 0U) * i)] = payload_header[0];
packet[((DataOffset + 0U) * i) + 1U] = payload_header[1];
if (RemainData > pdev->ep_in[VIDEOinEpAdd & 0xFU].maxpacket)
{
DataOffset = pdev->ep_in[VIDEOinEpAdd & 0xFU].maxpacket;
(void)USBD_memcpy((packet + ((DataOffset + 0U) * i) + 2U),
Pcktdata + ((DataOffset - 2U) * i), (DataOffset - 2U));
RemainData -= DataOffset;
i++;
}
else
{
(void)USBD_memcpy((packet + ((DataOffset + 0U) * i) + 2U),
Pcktdata + ((DataOffset - 2U) * i), (RemainData - 2U));
RemainData = 0U;
}
}
/* Transmit the packet on Endpoint */
(void)USBD_LL_Transmit(pdev, (uint8_t)(epnum | 0x80U),
(uint8_t *)&packet, (uint32_t)PcktSze);
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"%d %d\r\n",PcktSze,PcktIdx);
}
else
{
/* Add the packet header */
packet[0] = payload_header[0];
packet[1] = payload_header[1];
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"done\r\n");
}
}
/* Exit with no error code */
return (uint8_t) USBD_OK;
}
测试
详见视频
总结
以上实现了UVC摄像头显示, 就可以玩转GUI了,后面移植GUI比如LVGL等就可以进行有意思的开发了。后一篇先玩个有意思的,移植NES模拟器,玩转NES游戏。
- 2023-10-31
-
发表了主题帖:
[原创] 【ST多款开发板返场测评】STM32F767 Nucleo-144 实现UVC设备-枚举与开关
前言
USB设备的开发第一步是准备描述符,可以参考规格书和搜索样例准备。
我们这里就来准备UVC设备的描述符, 完成枚举过程调试,枚举成功也就成功了一半了。
添加代码
添加源码
配置头文件包含路径
Main.h中
#include "usbd_video.h"
#include "usbd_video_if_template.h"
Main.c中
USBD_RegisterClass(&USBD_Device, USBD_VIDEO_CLASS);
USBD_VIDEO_RegisterInterface(&USBD_Device, &USBD_VIDEO_fops_FS);
usbd_video_if_template.h中
const uint8_t image[] = {0x00};
const uint8_t *tImagesList[] = {image};
uint16_t tImagesSizes[] = {IMAGE_SIZE};
移动到usbd_video_if_template.c中
usbd_core.c中USBD_LL_SetupStage添加调试输出
do_debug(DEBUG_TAG_SETUP, DEBUG_LEVEL_INFO, "%#02x %#02x %#02x %#02x %#02x", pdev->request.bmRequest,pdev->request.bRequest,pdev->request.wValue,pdev->request.wIndex,pdev->request.wLength);
USBD_malloc使用了malloc堆管理,堆设置大一些
描述符
Usbd_desc.c中
设备描述符
如下
重点注意bDeviceClass,bDeviceSubClass,bDeviceProtocol 着三个值
参考https://mp.weixin.qq.com/s/qPdHOjLlZRoAKCdj1_8ZGA
/* USB Standard Device Descriptor */
#if defined ( __ICCARM__ ) /*!< IAR Compiler */
#pragma data_alignment=4
#endif
__ALIGN_BEGIN uint8_t USBD_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
0x12, /* bLength */
USB_DESC_TYPE_DEVICE, /* bDescriptorType */
0x00, /* bcdUSB */
0x02,
0xEF, /* bDeviceClass */
0x02, /* bDeviceSubClass */
0x01, /* bDeviceProtocol */
USB_MAX_EP0_SIZE, /* bMaxPacketSize */
LOBYTE(USBD_VID), /* idVendor */
HIBYTE(USBD_VID), /* idVendor */
LOBYTE(USBD_PID), /* idVendor */
HIBYTE(USBD_PID), /* idVendor */
0x00, /* bcdDevice rel. 2.00 */
0x02,
USBD_IDX_MFC_STR, /* Index of manufacturer string */
USBD_IDX_PRODUCT_STR, /* Index of product string */
USBD_IDX_SERIAL_STR, /* Index of serial number string */
USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations */
}; /* USB_DeviceDescriptor */
字符串描述符
修改对应的字符串
#define USBD_MANUFACTURER_STRING "MyCompany"
#define USBD_PRODUCT_HS_STRING "UVCCamera"
#define USBD_PRODUCT_FS_STRING "UVCCamera"
#define USBD_CONFIGURATION_HS_STRING "UVC Config"
#define USBD_INTERFACE_HS_STRING "UVC Interface"
#define USBD_CONFIGURATION_FS_STRING "UVC Config"
#define USBD_INTERFACE_FS_STRING "UVC Interface"
配置描述符
usbd_video.c中
USBD_VIDEO_CfgDesc
详见后面UsbTreeView解析
测试
运行看到打印的枚举过程如下
设备管理器看到枚举的设备
使用potplayer可以看到设备
打开关闭设备
UsbTreeView查看描述符解析
---------------------- Device Descriptor ----------------------
bLength : 0x12 (18 bytes)
bDescriptorType : 0x01 (Device Descriptor)
bcdUSB : 0x200 (USB Version 2.0) -> but device is Full-Speed only
bDeviceClass : 0xEF (Miscellaneous)
bDeviceSubClass : 0x02
bDeviceProtocol : 0x01 (IAD - Interface Association Descriptor)
bMaxPacketSize0 : 0x40 (64 bytes)
idVendor : 0x0483 (STMicroelectronics)
idProduct : 0x5710
bcdDevice : 0x0200
iManufacturer : 0x01 (String Descriptor 1)
Language 0x0409 : "MyCompany"
iProduct : 0x02 (String Descriptor 2)
Language 0x0409 : "UVCCamera"
iSerialNumber : 0x03 (String Descriptor 3)
Language 0x0409 : "FFFFFFFEFFFF"
bNumConfigurations : 0x01 (1 Configuration)
Data (HexDump) : 12 01 00 02 EF 02 01 40 83 04 10 57 00 02 01 02 .......@...W....
03 01 ..
------------------ Configuration Descriptor -------------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x02 (Configuration Descriptor)
wTotalLength : 0x0088 (136 bytes)
bNumInterfaces : 0x02 (2 Interfaces)
bConfigurationValue : 0x01 (Configuration 1)
iConfiguration : 0x00 (No String Descriptor)
bmAttributes : 0xC0
D7: Reserved, set 1 : 0x01
D6: Self Powered : 0x01 (yes)
D5: Remote Wakeup : 0x00 (no)
D4..0: Reserved, set 0 : 0x00
MaxPower : 0x32 (100 mA)
Data (HexDump) : 09 02 88 00 02 01 00 C0 32 08 0B 00 02 0E 03 00 ........2.......
00 09 04 00 00 00 0E 01 00 00 0D 24 01 10 01 1E ...........$....
00 00 6C DC 02 01 01 08 24 02 01 00 02 00 00 09 ..l.....$.......
24 03 02 01 01 00 01 00 09 04 01 00 00 0E 02 00 $...............
00 0E 24 01 01 37 00 81 00 02 00 00 00 01 00 0B ..$..7..........
24 06 01 01 01 01 00 00 00 00 1E 24 07 01 02 90 $..........$....
01 F0 00 00 60 EA 00 00 60 EA 00 00 B8 0B 00 40 ....`...`......@
42 0F 00 01 40 42 0F 00 09 04 01 01 01 0E 02 00 B...@B..........
00 07 05 81 05 00 01 01 ........
------------------- IAD Descriptor --------------------
bLength : 0x08 (8 bytes)
bDescriptorType : 0x0B (Interface Association Descriptor)
bFirstInterface : 0x00 (Interface 0)
bInterfaceCount : 0x02 (2 Interfaces)
bFunctionClass : 0x0E (Video)
bFunctionSubClass : 0x03 (Video Interface Collection)
bFunctionProtocol : 0x00 (PC_PROTOCOL_UNDEFINED)
iFunction : 0x00 (No String Descriptor)
Data (HexDump) : 08 0B 00 02 0E 03 00 00 ........
---------------- Interface Descriptor -----------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x04 (Interface Descriptor)
bInterfaceNumber : 0x00 (Interface 0)
bAlternateSetting : 0x00
bNumEndpoints : 0x00 (Default Control Pipe only)
bInterfaceClass : 0x0E (Video)
bInterfaceSubClass : 0x01 (Video Control)
bInterfaceProtocol : 0x00
iInterface : 0x00 (No String Descriptor)
Data (HexDump) : 09 04 00 00 00 0E 01 00 00 .........
------- Video Control Interface Header Descriptor -----
bLength : 0x0D (13 bytes)
bDescriptorType : 0x24 (Video Control Interface)
bDescriptorSubtype : 0x01 (Video Control Header)
bcdUVC : 0x0110 (UVC Version 1.10)
wTotalLength : 0x001E (30 bytes)
dwClockFreq : 0x02DC6C00 (48 MHz)
bInCollection : 0x01 (1 VideoStreaming interface)
baInterfaceNr[1] : 0x01 (Interface 1)
Data (HexDump) : 0D 24 01 10 01 1E 00 00 6C DC 02 01 01 .$......l....
-------- Video Control Input Terminal Descriptor ------
bLength : 0x08 (8 bytes)
bDescriptorType : 0x24 (Video Control Interface)
bDescriptorSubtype : 0x02 (Input Terminal)
bTerminalID : 0x01 (1)
wTerminalType : 0x0200 (ITT_VENDOR_SPECIFIC)
bAssocTerminal : 0x00 (Not associated with an Output Terminal)
iTerminal : 0x00 (No String Descriptor)
Data (HexDump) : 08 24 02 01 00 02 00 00 .$......
------- Video Control Output Terminal Descriptor ------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x24 (Video Control Interface)
bDescriptorSubtype : 0x03 (Output Terminal)
bTerminalID : 0x02 (2)
wTerminalType : 0x0101 (TT_STREAMING)
bAssocTerminal : 0x00 (Not associated with an Input Terminal)
bSourceID : 0x01 (1)
iTerminal : 0x00 (No String Descriptor)
Data (HexDump) : 09 24 03 02 01 01 00 01 00 .$.......
---------------- Interface Descriptor -----------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x04 (Interface Descriptor)
bInterfaceNumber : 0x01 (Interface 1)
bAlternateSetting : 0x00
bNumEndpoints : 0x00 (Default Control Pipe only)
bInterfaceClass : 0x0E (Video)
bInterfaceSubClass : 0x02 (Video Streaming)
bInterfaceProtocol : 0x00
iInterface : 0x00 (No String Descriptor)
Data (HexDump) : 09 04 01 00 00 0E 02 00 00 .........
---- VC-Specific VS Video Input Header Descriptor -----
bLength : 0x0E (14 bytes)
bDescriptorType : 0x24 (Video Streaming Interface)
bDescriptorSubtype : 0x01 (Input Header)
bNumFormats : 0x01
wTotalLength : 0x0037 (55 bytes)
bEndpointAddress : 0x81 (Direction=IN EndpointID=1)
bmInfo : 0x00 (Dynamic Format Change not supported)
bTerminalLink : 0x02 (Output Terminal ID 2)
bStillCaptureMethod : 0x00 (No Still Capture)
nbTriggerSupport : 0x00 (Hardware Triggering not supported)
bTriggerUsage : 0x00 (Host will initiate still image capture)
bControlSize : 0x01 (1 bytes each)
Video Payload Format 1 : 0x00
D0 : 0 no - Key Frame Rate
D1 : 0 no - P Frame Rate
D2 : 0 no - Compression Quality
D3 : 0 no - Compression Window Size
D4 : 0 no - Generate Key Frame
D5 : 0 no - Update Frame Segment
D6 : 0 no - Reserved
D7 : 0 no - Reserved
Data (HexDump) : 0E 24 01 01 37 00 81 00 02 00 00 00 01 00 .$..7.........
----- Video Streaming MJPEG Format Type Descriptor ----
bLength : 0x0B (11 bytes)
bDescriptorType : 0x24 (Video Streaming Interface)
bDescriptorSubtype : 0x06 (Format MJPEG)
bFormatIndex : 0x01 (1)
bNumFrameDescriptors : 0x01 (1)
bmFlags : 0x01 (Sample size is fixed)
bDefaultFrameIndex : 0x01 (1)
bAspectRatioX : 0x00
bAspectRatioY : 0x00
bmInterlaceFlags : 0x00
D0 IL stream or variable: 0 (no)
D1 Fields per frame : 0 (2 fields)
D2 Field 1 first : 0 (no)
D3 Reserved : 0
D4..5 Field pattern : 0 (Field 1 only)
D6..7 Display Mode : 0 (Bob only)
bCopyProtect : 0x00 (No restrictions)
*!*ERROR: no Color Matching Descriptor for this format
Data (HexDump) : 0B 24 06 01 01 01 01 00 00 00 00 .$.........
----- Video Streaming MJPEG Frame Type Descriptor -----
---> This is the Default (optimum) Frame index
bLength : 0x1E (30 bytes)
bDescriptorType : 0x24 (Video Streaming Interface)
bDescriptorSubtype : 0x07 (MJPEG Frame Type)
bFrameIndex : 0x01
bmCapabilities : 0x02
wWidth : 0x0190 (400)
wHeight : 0x00F0 (240)
dwMinBitRate : 0x00EA6000 (15360000 bps -> 1.920 MB/s)
dwMaxBitRate : 0x00EA6000 (15360000 bps -> 1.920 MB/s)
dwMaxVideoFrameBufferSize: 0x000BB800 (768000 bytes)
dwDefaultFrameInterval : 0x000F4240 (100.0000 ms -> 10.0000 fps)
bFrameIntervalType : 0x01 (1 discrete frame interval supported)
adwFrameInterval[1] : 0x000F4240 (100.0000 ms -> 10.0000 fps)
Data (HexDump) : 1E 24 07 01 02 90 01 F0 00 00 60 EA 00 00 60 EA .$........`...`.
00 00 B8 0B 00 40 42 0F 00 01 40 42 0F 00 .....@B...@B..
---------------- Interface Descriptor -----------------
bLength : 0x09 (9 bytes)
bDescriptorType : 0x04 (Interface Descriptor)
bInterfaceNumber : 0x01 (Interface 1)
bAlternateSetting : 0x01
bNumEndpoints : 0x01 (1 Endpoint)
bInterfaceClass : 0x0E (Video)
bInterfaceSubClass : 0x02 (Video Streaming)
bInterfaceProtocol : 0x00
iInterface : 0x00 (No String Descriptor)
Data (HexDump) : 09 04 01 01 01 0E 02 00 00 .........
----------------- Endpoint Descriptor -----------------
bLength : 0x07 (7 bytes)
bDescriptorType : 0x05 (Endpoint Descriptor)
bEndpointAddress : 0x81 (Direction=IN EndpointID=1)
bmAttributes : 0x05 (TransferType=Isochronous SyncType=Asynchronous EndpointType=Data)
wMaxPacketSize : 0x0100 (256 bytes)
bInterval : 0x01 (1 ms)
Data (HexDump) : 07 05 81 05 00 01 01 .......
----------------- Device Qualifier Descriptor -----------------
Error : ERROR_GEN_FAILURE (because the device is Full-Speed only)
-------------------- String Descriptors -------------------
------ String Descriptor 0 ------
bLength : 0x04 (4 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language ID[0] : 0x0409 (English - United States)
Data (HexDump) : 04 03 09 04 ....
------ String Descriptor 1 ------
bLength : 0x14 (20 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "MyCompany"
Data (HexDump) : 14 03 4D 00 79 00 43 00 6F 00 6D 00 70 00 61 00 ..M.y.C.o.m.p.a.
6E 00 79 00 n.y.
------ String Descriptor 2 ------
bLength : 0x14 (20 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "UVCCamera"
Data (HexDump) : 14 03 55 00 56 00 43 00 43 00 61 00 6D 00 65 00 ..U.V.C.C.a.m.e.
72 00 61 00 r.a.
------ String Descriptor 3 ------
bLength : 0x1A (26 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "FFFFFFFEFFFF"
Data (HexDump) : 1A 03 46 00 46 00 46 00 46 00 46 00 46 00 46 00 ..F.F.F.F.F.F.F.
45 00 46 00 46 00 46 00 46 00 E.F.F.F.F.
------ String Descriptor 4 ------
bLength : 0x16 (22 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "UVC Config"
Data (HexDump) : 16 03 55 00 56 00 43 00 20 00 43 00 6F 00 6E 00 ..U.V.C. .C.o.n.
66 00 69 00 67 00 f.i.g.
------ String Descriptor 5 ------
bLength : 0x1C (28 bytes)
bDescriptorType : 0x03 (String Descriptor)
Language 0x0409 : "UVC Interface"
Data (HexDump) : 1C 03 55 00 56 00 43 00 20 00 49 00 6E 00 74 00 ..U.V.C. .I.n.t.
65 00 72 00 66 00 61 00 63 00 65 00 e.r.f.a.c.e.
总结
以上实现了UVC设备的枚举与打开关闭,已经完成了一半,后面加上图像数据的发送即可。
- 2023-10-30
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 HID设备枚举过程调试
前言
前面我们准备了打印输出部分的实现,现在开始就可以基于此进行USB的调试了。
过程
添加USB相关代码到现有的代码上
USB相关代码,PCD的驱动
Stm32fxx_it.c中usb中断处理
void OTG_FS_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd);
}
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] This function handles USB Handler.
* @param None
* @retval None
*/
void OTG_FS_WKUP_IRQHandler(void)
{
if((&hpcd)->Init.low_power_enable)
{
/* Reset SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR &= (uint32_t)~((uint32_t)(SCB_SCR_SLEEPDEEP_Msk | SCB_SCR_SLEEPONEXIT_Msk));
SystemClockConfig_STOP();
/* Ungate PHY clock */
__HAL_PCD_UNGATE_PHYCLOCK((&hpcd));
}
/* Clear EXTI pending Bit*/
__HAL_USB_OTG_FS_WAKEUP_EXTI_CLEAR_FLAG();
}
Inc\stm32f7xx_hal_conf.h中
定义#define HAL_PCD_MODULE_ENABLED使用PCD库代码
Main函数初始化
int main(void)
{
/* Configure the MPU attributes */
MPU_Config();
/* Enable the CPU Cache */
CPU_CACHE_Enable();
/* STM32F7xx HAL library initialization:
- Configure the Flash prefetch, instruction and Data caches
- Configure the Systick to generate an interrupt each 1 msec
- Set NVIC Group Priority to 4
- Global MSP (MCU Support Package) initialization
*/
HAL_Init();
/* Configure the system clock to 216 MHz */
SystemClock_Config();
/* Init Device Library */
USBD_Init(&USBD_Device, &HID_Desc, 0);
/* Add Supported Class */
USBD_RegisterClass(&USBD_Device, USBD_HID_CLASS);
/* Start Device Process */
USBD_Start(&USBD_Device);
bsp_uart_init();
uint8_t tx_buffer[2]={0xAA,0x55};
static uint32_t pretick;
/* Infinite loop */
pretick = HAL_GetTick();
debug_set_level(DEBUG_TAG_SETUP, DEBUG_LEVEL_INFO);
while (1)
{
uart_debug_send(16);
shell_exec_shellcmd();
}
}
测试
Main中使能打印输出
debug_set_level(DEBUG_TAG_SETUP, DEBUG_LEVEL_INFO);
usbd_core.c中USBD_LL_SetupStage中添加如下日志输出,打印8字节的SETUP内容
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"[setup]:%#x %#x %#02x %#02x %#02x\r\n",pdev->request.bmRequest,
pdev->request.bRequest,pdev->request.wValue,pdev->request.wIndex,pdev->request.wLength);
看到整个枚举过程打印如下
比如第一条0x80 0x06 0x100 00 0x40即获取设备描述符
第二条0 0x5 0x4 00 00即设置地址
可以清晰的看出整个枚举过程
总结
磨刀不误砍柴工,工欲善其事必先利其器,有了前面的准备工作,准备了好用的命令行交互和打印输出,可以方便后面USB的开发调试,提高效率。
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 灵活可配的LOG日志输出实现
前言
磨刀不误砍柴工,前面的非阻塞串口打印,shell命令行交互都是为了方便后面USB开发的调试。现在开始正是开始砍柴了。第一步我们就是实现比较灵活的LOG输出。主要考虑以下几点需求
非阻塞,前面已经实现了相关的接口
可配置日志输出等级,因为调试输出可能很多,所以根据等级过滤会方便查看
可配置日子类型,结合上述等级可控制,进一步能控制不同类型(TAG)的日志的不同等级的使能,这样可以更精确的分类输出。
可动态命令行配置,基于前面的shell可以动态修改不同TAG的不同等级的日志的使能控制。
可以根据不同等级显示不通的颜色,方便屠户显示错误。
过程
添加debug.c和debug.h
使能配置如下,一个数组记录每个TAG的当前使能等级
static uint8_t debug_level_enabled[DEBUG_TAG_MAX] = {
};
void debug_toggle_level(debug_level_t tag)
{
if (tag >= DEBUG_TAG_MAX)
return;
debug_level_enabled[tag] ^= 0x80;
}
void debug_set_level(debug_tag_t tag, debug_level_t level)
{
if (level >= DEBUG_LEVEL_MAX)
return;
if (tag >= DEBUG_TAG_MAX)
return;
if(level == DEBUG_LEVEL_OFF)
{
debug_level_enabled[tag] = 0;
}
else
{
debug_level_enabled[tag] = level | 0x80;
}
}
debug_level_t debug_get_level(debug_tag_t tag)
{
if (tag >= DEBUG_TAG_MAX)
return DEBUG_LEVEL_OFF;
return debug_level_enabled[tag];
}
根据不同等级打印不同颜色
void debug_set_color(debug_color_t color) {
unsigned int color_code, modifier_code;
switch (color & COLOR_MASK_COLOR) {
case COLOR_BLACK:
color_code = 30; break;
case COLOR_RED:
color_code = 31; break;
case COLOR_GREEN:
color_code = 32; break;
case COLOR_YELLOW:
color_code = 33; break;
case COLOR_BLUE:
color_code = 34; break;
case COLOR_MAGENTA:
color_code = 35; break;
case COLOR_CYAN:
color_code = 36; break;
case COLOR_WHITE:
color_code = 37; break;
case COLOR_RESET:
default:
color_code = 0; break;
}
switch (color & COLOR_MASK_MODIFIER) {
case COLOR_BOLD:
modifier_code = 1; break;
case COLOR_UNDERLINE:
modifier_code = 2; break;
case COLOR_BLINK:
modifier_code = 3; break;
case COLOR_HIDE:
modifier_code = 4; break;
case COLOR_NORMAL:
default:
modifier_code = 0; break;
}
printf("\033[%u;%um", modifier_code, color_code);
}
打印接口
void do_debug(debug_tag_t tag, debug_level_t level, const char *format, ...)
{
int color = COLOR_RESET;
va_list args;
/* Don't print anything if log level is disabled */
if(((debug_level_enabled[tag] & 0x80) == 0) || ((debug_level_enabled[tag] & 0x7F) > level))
return;
switch(level) {
case DEBUG_LEVEL_INFO:
color = COLOR_GREEN | COLOR_BOLD;
break;
case DEBUG_LEVEL_ERROR:
color = COLOR_RED | COLOR_BOLD;
break;
case DEBUG_LEVEL_WARN:
color = COLOR_YELLOW | COLOR_BOLD;
break;
default:
return;
}
va_start(args, format);
/* If csp_debug_hook symbol is defined, pass on the message.
* Otherwise, just print with pretty colors ... */
if (debug_hook_func) {
debug_set_color((debug_color_t)color);
debug_hook_func((debug_color_t)color, format, args);
debug_set_color(COLOR_RESET);
} else {
debug_set_color((debug_color_t)color);
vprintf(format, args);
printf("\r\n");
debug_set_color(COLOR_RESET);
}
va_end(args);
}
添加命令行
shell_func.c中#include "debug.h"
shell_cmd_list添加一行
{ (const uint8_t*)"debug", DebugFun, "debug tag level"},
添加实现函数
void DebugFun(unsigned char* param)
{
int tag;
int level;
if(2 == sscanf((const char*)param, "%*s %d %d", &tag, &tag))
{
debug_set_level(tag, level);
}
}
shell_func.c中
void DebugFun(unsigned char* param);
可以看到添加的命令
测试
Main.c中
#include "debug.h"
使能颜色主题
输入
debug 0 1
debug 1 1
debug 2 1
使能所有TAG的所有等级输出
输入
debug 0 3
debug 1 3
debug 2 3
使能所有TAG的ERROR等级输出
则只输出ERROR
debug 0 0
关闭TAG0输出则只剩下如下两个TAG输出
代码
Debug.c
#include <stdio.h>
#include "debug.h"
/* Custom debug function */
debug_hook_func_t debug_hook_func = NULL;
/* Debug levels */
static uint8_t debug_level_enabled[DEBUG_TAG_MAX] = {
};
/* Some compilers do not support weak symbols, so this function
* can be used instead to set a custom debug hook */
void csp_debug_hook_set(debug_hook_func_t f)
{
debug_hook_func = f;
}
void debug_set_color(debug_color_t color) {
unsigned int color_code, modifier_code;
switch (color & COLOR_MASK_COLOR) {
case COLOR_BLACK:
color_code = 30; break;
case COLOR_RED:
color_code = 31; break;
case COLOR_GREEN:
color_code = 32; break;
case COLOR_YELLOW:
color_code = 33; break;
case COLOR_BLUE:
color_code = 34; break;
case COLOR_MAGENTA:
color_code = 35; break;
case COLOR_CYAN:
color_code = 36; break;
case COLOR_WHITE:
color_code = 37; break;
case COLOR_RESET:
default:
color_code = 0; break;
}
switch (color & COLOR_MASK_MODIFIER) {
case COLOR_BOLD:
modifier_code = 1; break;
case COLOR_UNDERLINE:
modifier_code = 2; break;
case COLOR_BLINK:
modifier_code = 3; break;
case COLOR_HIDE:
modifier_code = 4; break;
case COLOR_NORMAL:
default:
modifier_code = 0; break;
}
printf("\033[%u;%um", modifier_code, color_code);
}
void do_debug(debug_tag_t tag, debug_level_t level, const char *format, ...)
{
int color = COLOR_RESET;
va_list args;
/* Don't print anything if log level is disabled */
if(((debug_level_enabled[tag] & 0x80) == 0) || ((debug_level_enabled[tag] & 0x7F) > level))
return;
switch(level) {
case DEBUG_LEVEL_INFO:
color = COLOR_GREEN | COLOR_BOLD;
break;
case DEBUG_LEVEL_ERROR:
color = COLOR_RED | COLOR_BOLD;
break;
case DEBUG_LEVEL_WARN:
color = COLOR_YELLOW | COLOR_BOLD;
break;
default:
return;
}
va_start(args, format);
/* If csp_debug_hook symbol is defined, pass on the message.
* Otherwise, just print with pretty colors ... */
if (debug_hook_func) {
debug_set_color((debug_color_t)color);
debug_hook_func((debug_color_t)color, format, args);
debug_set_color(COLOR_RESET);
} else {
debug_set_color((debug_color_t)color);
vprintf(format, args);
printf("\r\n");
debug_set_color(COLOR_RESET);
}
va_end(args);
}
void debug_toggle_level(debug_level_t tag)
{
if (tag >= DEBUG_TAG_MAX)
return;
debug_level_enabled[tag] ^= 0x80;
}
void debug_set_level(debug_tag_t tag, debug_level_t level)
{
if (level >= DEBUG_LEVEL_MAX)
return;
if (tag >= DEBUG_TAG_MAX)
return;
if(level == DEBUG_LEVEL_OFF)
{
debug_level_enabled[tag] = 0;
}
else
{
debug_level_enabled[tag] = level | 0x80;
}
}
debug_level_t debug_get_level(debug_tag_t tag)
{
if (tag >= DEBUG_TAG_MAX)
return DEBUG_LEVEL_OFF;
return debug_level_enabled[tag];
}
Debug.h
#ifndef DEBUG_H
#define DEBUG_H
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Debug levels */
typedef enum {
DEBUG_LEVEL_OFF = 0,
DEBUG_LEVEL_INFO = 1,
DEBUG_LEVEL_WARN = 2,
DEBUG_LEVEL_ERROR = 3,
DEBUG_LEVEL_MAX = 4,
} debug_level_t;
/** Debug tags */
typedef enum {
DEBUG_TAG_SETUP = 0,
DEBUG_TAG_INT = 1,
DEBUG_TAG_STATE = 2,
DEBUG_TAG_MAX = 3,
} debug_tag_t;
#define COLOR_MASK_COLOR 0x0F
#define COLOR_MASK_MODIFIER 0xF0
typedef enum {
/* Colors */
COLOR_RESET = 0xF0,
COLOR_BLACK = 0x01,
COLOR_RED = 0x02,
COLOR_GREEN = 0x03,
COLOR_YELLOW = 0x04,
COLOR_BLUE = 0x05,
COLOR_MAGENTA = 0x06,
COLOR_CYAN = 0x07,
COLOR_WHITE = 0x08,
/* Modifiers */
COLOR_NORMAL = 0x0F,
COLOR_BOLD = 0x10,
COLOR_UNDERLINE = 0x20,
COLOR_BLINK = 0x30,
COLOR_HIDE = 0x40,
} debug_color_t;
typedef void (*debug_hook_func_t)(debug_color_t color, const char *format, va_list args);
void csp_debug_hook_set(debug_hook_func_t f);
/* Extract filename component from path */
#define BASENAME(_file) ((strrchr(_file, '/') ? : (strrchr(_file, '\\') ? : _file)) + 1)
#ifndef NDEBUG
#define debug_assert(exp) \
do { \
if (!(exp)) { \
char *assertion = #exp; \
const char *file = BASENAME(__FILE__); \
int line = __LINE__; \
printf("\E[1;31m Assertion \'%s\' failed in %s:%d\E[0m\r\n", \
assertion, file, line);} \
} while (0)
#else
#define debug_assert(...) do {} while (0)
#endif
#ifdef DEBUG
#define debug(tag, level, format, ...) do { do_debug(tag, level, CONSTSTR(format), ##__VA_ARGS__); } while(0)
#else
#define debug(...) do {} while (0)
#endif
/**
* This function should not be used directly, use log_<level>() macro instead
* @param tag
* @param level
* @param format
*/
void do_debug(debug_tag_t tag, debug_level_t level, const char *format, ...);
/**
* Toggle debug level on/off
* @param tag Tag to toggle
*/
void debug_toggle_level(debug_level_t tag);
/**
* Set debug level
* @param tag Tag value to get
* @param level Level to set
*/
void debug_set_level(debug_tag_t tag, debug_level_t level);
/**
* Get current debug level value
* @param tag Tag value to get
* [url=home.php?mod=space&uid=784970]@return[/url] Level value
*/
debug_level_t debug_get_level(debug_tag_t tag);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
总结
以上实现了比较灵活可配置的LOG输出,可配置TAG和指定TAG的输出等级,
方便后面USB调试动态修改条数输出。
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 实现简单的shell命令行交互
前言
前面我们实现了串口非阻塞的打印输出, 为了方便后面的而调试,我们继续实现简单的shell命令行交互。基于前面的串口的相关收发接口。
代码
基于前面的
uart_read
uart_debug_push
putchar->fputc
接口
关键数据结构是命令字符串和对应的执行函数的表格
typedef void ( * CommandFunc )( unsigned char *);
typedef struct
{
unsigned char const* name;
CommandFunc func;
char const* helpstr;
}shell_cmd_cfg;
核心代码是读取一行命令
static unsigned int shell_read_line(int get(unsigned char* tmp))
{
unsigned char ch = '\r';
unsigned int count;
unsigned char tmp;
/*??????"sh>"*/
if(cmd_buf[0]=='\r')
{
printf("sh>\r\n");
memset(cmd_buf,0x00,sizeof(cmd_buf));
}
/*??????????,????*/
if(get(&tmp)==0)
{
ch = tmp;
}
else
{
return 0;
}
/*????????????????????0????????????"SH>"*/
if((ch == '\r' || ch == '\n' || ch < ' ' || ch > '~') && (ch != '\b'))
{
if(cmd_buf_index==0)
{
printf("sh>\r\n");
}
else
{
count = cmd_buf_index;
cmd_buf[cmd_buf_index]=0;
cmd_buf_index =0;
printf("\r\n");
return count;
}
}
else
{
if(ch == '\b')
{
if(cmd_buf_index != 0)
{
cmd_buf_index--;
putchar('\b');
putchar(' ');
putchar('\b');
cmd_buf[cmd_buf_index]= '\0';
}
}
else
{
/*??????????????????????????"*/
putchar(ch);
cmd_buf[cmd_buf_index++] = ch;
if(cmd_buf_index>=(sizeof(cmd_buf)-1))
{
count = cmd_buf_index;
cmd_buf[cmd_buf_index]=0;
cmd_buf_index =0;
printf("\r\n");
return count;
}
}
}
return 0;
}
static int shell_getchar(unsigned char *data)
{
int erro=0;
//driver_uart_recv(getstdiouart(), data, 1,10,&erro);
if(0 == uart_read(data, 1))
{
erro = 1;
}
else
{
//uart_write(data,1);
}
if(erro!=0)
{
return -1;
}
return 0;
}
读到一行命令后,根据命令字符串查找表格是否有对应的命令,找到了则调用对应的函数
void shell_exec_shellcmd(void)
{
if(shell_read_line(shell_getchar))
{
shell_exec_cmdlist(cmd_buf);
}
}
int shell_exec_cmdlist(unsigned char* cmd)
{
int i;
for (i=0; shell_cmd_list[i].name != 0; i++)
{
if (shell_cmd_check(cmd, shell_cmd_list[i].name) == 0)
{
shell_cmd_list[i].func(cmd);
return 0;
}
}
if(shell_cmd_list[i].name == NULL)
{
printf("unkown command\r\n");
return -1;
}
return 0;
}
static int shell_cmd_check(unsigned char *cmd, unsigned char const *str)
{
unsigned int len1 = shell_cmd_len((unsigned char const *)cmd);
unsigned int len2 = shell_cmd_len(str);
if(len1 != len2)
{
return 1;
}
return memcmp(cmd, str, len1);
}
static unsigned int shell_cmd_len(unsigned char const *cmd)
{
unsigned char const *p = cmd;
unsigned int len = 0;
while((*p != ' ') && (*p != 0))
{
p++;
len++;
}
return len;
}
添加一个命令只需在shell_func.c以下数组中添加一行
const shell_cmd_cfg shell_cmd_list[ ] =
{
{ (const uint8_t*)"help", HelpFun, "help"},
{ (const uint8_t*)0, 0 , 0},
};
并实现函数
void HelpFun(unsigned char* param)
{
(void)param;
unsigned int i;
printf("\r\n");
printf("**************\r\n");
printf("* SHELL *\r\n");
printf("* V1.0 *\r\n");
printf("**************\r\n");
printf("\r\n");
for (i=0; shell_cmd_list[i].name != 0; i++)
{
printf("%02d.",i);
printf("%-16s",shell_cmd_list[i].name);
printf("%s\r\n",shell_cmd_list[i].helpstr);
}
}
头文件shell_func.h中申明函数
void HelpFun(unsigned char* param);
测试
测试主循环中
#include "shell.h"
while (1)
{
uart_debug_send(16);
shell_exec_shellcmd();
}
可以看到打印如下
代码如下
shell.c
#include <stdio.h>
#include <string.h>
#include "shell.h"
extern uint32_t uart_read(uint8_t* buffer, uint32_t len);
extern uint32_t uart_debug_push(uint8_t* buffer, uint32_t len);
#define SHELL_CMD_LEN 64
extern const shell_cmd_cfg shell_cmd_list[ ];
static unsigned char cmd_buf[SHELL_CMD_LEN]="\r";
static unsigned int cmd_buf_index=0;
static int shell_getchar(unsigned char *data)
{
int erro=0;
//driver_uart_recv(getstdiouart(), data, 1,10,&erro);
if(0 == uart_read(data, 1))
{
erro = 1;
}
else
{
//uart_write(data,1);
}
if(erro!=0)
{
return -1;
}
return 0;
}
static unsigned int shell_cmd_len(unsigned char const *cmd)
{
unsigned char const *p = cmd;
unsigned int len = 0;
while((*p != ' ') && (*p != 0))
{
p++;
len++;
}
return len;
}
static int shell_cmd_check(unsigned char *cmd, unsigned char const *str)
{
unsigned int len1 = shell_cmd_len((unsigned char const *)cmd);
unsigned int len2 = shell_cmd_len(str);
if(len1 != len2)
{
return 1;
}
return memcmp(cmd, str, len1);
}
static unsigned int shell_read_line(int get(unsigned char* tmp))
{
unsigned char ch = '\r';
unsigned int count;
unsigned char tmp;
/*??????"sh>"*/
if(cmd_buf[0]=='\r')
{
printf("sh>\r\n");
memset(cmd_buf,0x00,sizeof(cmd_buf));
}
/*??????????,????*/
if(get(&tmp)==0)
{
ch = tmp;
}
else
{
return 0;
}
/*????????????????????0????????????"SH>"*/
if((ch == '\r' || ch == '\n' || ch < ' ' || ch > '~') && (ch != '\b'))
{
if(cmd_buf_index==0)
{
printf("sh>\r\n");
}
else
{
count = cmd_buf_index;
cmd_buf[cmd_buf_index]=0;
cmd_buf_index =0;
printf("\r\n");
return count;
}
}
else
{
if(ch == '\b')
{
if(cmd_buf_index != 0)
{
cmd_buf_index--;
putchar('\b');
putchar(' ');
putchar('\b');
cmd_buf[cmd_buf_index]= '\0';
}
}
else
{
/*??????????????????????????"*/
putchar(ch);
cmd_buf[cmd_buf_index++] = ch;
if(cmd_buf_index>=(sizeof(cmd_buf)-1))
{
count = cmd_buf_index;
cmd_buf[cmd_buf_index]=0;
cmd_buf_index =0;
printf("\r\n");
return count;
}
}
}
return 0;
}
int shell_exec_cmdlist(unsigned char* cmd)
{
int i;
for (i=0; shell_cmd_list[i].name != 0; i++)
{
if (shell_cmd_check(cmd, shell_cmd_list[i].name) == 0)
{
shell_cmd_list[i].func(cmd);
return 0;
}
}
if(shell_cmd_list[i].name == NULL)
{
printf("unkown command\r\n");
return -1;
}
return 0;
}
void shell_exec_shellcmd(void)
{
if(shell_read_line(shell_getchar))
{
shell_exec_cmdlist(cmd_buf);
}
}
shell.h
#ifndef _SHELL_H_
#define _SHELL_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
typedef void ( * CommandFunc )( unsigned char *);
typedef struct
{
unsigned char const* name;
CommandFunc func;
char const* helpstr;
}shell_cmd_cfg;
#define SHELL_CMDBUF_SIZE 64
void shell_exec_shellcmd(void);
int shell_exec_cmdlist(unsigned char* cmd);
#ifdef __cplusplus
}
#endif
#endif
shell_func.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "shell_func.h"
#include "shell.h"
const shell_cmd_cfg shell_cmd_list[ ] =
{
{ (const uint8_t*)"help", HelpFun, "help"},
{ (const uint8_t*)0, 0 , 0},
};
void HelpFun(unsigned char* param)
{
(void)param;
unsigned int i;
printf("\r\n");
printf("**************\r\n");
printf("* SHELL *\r\n");
printf("* V1.0 *\r\n");
printf("**************\r\n");
printf("\r\n");
for (i=0; shell_cmd_list[i].name != 0; i++)
{
printf("%02d.",i);
printf("%-16s",shell_cmd_list[i].name);
printf("%s\r\n",shell_cmd_list[i].helpstr);
}
}
shell_func.h
#ifndef __SHELL_FUN_H
#define __SHELL_FUN_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void HelpFun(unsigned char* param);
#ifdef __cplusplus
}
#endif
#endif
总结
基于前面的串口接口,快速实现了shell命令行,方便以后交互调试使用,相关代码也是可移植可复用到其他项目。
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 非阻塞串口打印实现
前言
前面我们实现的串口发送采用的是阻塞查询方式,在USB开发中如果使用该方式作为打印调试,则可能会由于阻塞影响时序。所以需要该为非阻塞方式,我们的思路是先将待发送数据写入到缓存中,然后载在主循环中进行发送。核心部分还是和串口接收FIFO实现一样,可以复用其代码。
代码
增加代码uart_debug.c/h
数据结构
typedef struct
{
uint32_t datalen_u32;
uint32_t maxlen_u32;
uint32_t in_u32;
uint32_t out_u32;
uint8_t* buffer_pu8;
}ring_buffer_t;
static uint32_t uart_debug_pop(uint8_t *buff, uint32_t len);
static uint8_t uart_ring_buffer[1024];
static ring_buffer_t s_ring_buffer_t=
{
.datalen_u32 = 0,
.maxlen_u32 = sizeof(uart_ring_buffer),
.in_u32 = 0,
.out_u32 = 0,
.buffer_pu8 = uart_ring_buffer,
};
写数据到缓存区
uint32_t uart_debug_push(uint8_t *buffer, uint32_t length)
{
uint32_t i;
uint32_t sendlen = 0;
Alloc_Critical();
Enter_Critical();
for(i=0;i<length; i++)
{
if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
{
s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer[i];
s_ring_buffer_t.datalen_u32++;
s_ring_buffer_t.in_u32++;
s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
sendlen++;
}
else
{
/* full */
break;
}
}
Exit_Critical();
return sendlen;
}
从缓存区读出数据
static uint32_t uart_debug_pop(uint8_t *buff, uint32_t len)
{
uint32_t readlen = 0;
//uint32_t mask;
if(s_ring_buffer_t.datalen_u32 == 0)
{
return 0;
}
Alloc_Critical();
Enter_Critical();
uint32_t i;
for(i=0;i<len;i++)
{
if(s_ring_buffer_t.datalen_u32 > 0)
{
buff[i] = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
s_ring_buffer_t.datalen_u32--;
s_ring_buffer_t.out_u32++;
s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
readlen++;
}
else
{
break;
}
}
Exit_Critical();
return readlen;
}
发送处理,主循环中调用该接口实现真正的发送,
制定了一次发送的最大长度len,这样做目的是避免一次发送过长,导致主循环在这里等待过长,配置len来调整。
int uart_debug_send(uint32_t len)
{
uint32_t i;
uint8_t buff[16];
uint32_t slen;
if(0 != (slen = uart_debug_pop(buff, sizeof(buff))))
{
uart_write(buff, slen);
}
return slen;
}
重定向printf的底层接口
int fputc(int ch, FILE *f)
{
uint8_t tmp = (uint8_t)ch;
/* Your implementation of fputc(). */
uart_debug_push(&tmp, 1);
return ch;
}
uart_debug.h
#ifndef UART_DEBUG_H
#define UART_DEBUG_H
#include <stdint.h>
uint32_t uart_debug_push(uint8_t *buffer, uint32_t length);
int uart_debug_send(uint32_t len);
#endif
uart_debug.c
#include <stdio.h>
#include "uart.h"
#include "uart_debug.h"
#include "stm32f7xx.h"
#define Alloc_Critical()
#define Enter_Critical()
#define Exit_Critical()
typedef struct
{
uint32_t datalen_u32;
uint32_t maxlen_u32;
uint32_t in_u32;
uint32_t out_u32;
uint8_t* buffer_pu8;
}ring_buffer_t;
static uint32_t uart_debug_pop(uint8_t *buff, uint32_t len);
static uint8_t uart_ring_buffer[1024];
static ring_buffer_t s_ring_buffer_t=
{
.datalen_u32 = 0,
.maxlen_u32 = sizeof(uart_ring_buffer),
.in_u32 = 0,
.out_u32 = 0,
.buffer_pu8 = uart_ring_buffer,
};
static uint32_t uart_debug_pop(uint8_t *buff, uint32_t len)
{
uint32_t readlen = 0;
//uint32_t mask;
if(s_ring_buffer_t.datalen_u32 == 0)
{
return 0;
}
Alloc_Critical();
Enter_Critical();
uint32_t i;
for(i=0;i<len;i++)
{
if(s_ring_buffer_t.datalen_u32 > 0)
{
buff[i] = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
s_ring_buffer_t.datalen_u32--;
s_ring_buffer_t.out_u32++;
s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
readlen++;
}
else
{
break;
}
}
Exit_Critical();
return readlen;
}
uint32_t uart_debug_push(uint8_t *buffer, uint32_t length)
{
uint32_t i;
uint32_t sendlen = 0;
Alloc_Critical();
Enter_Critical();
for(i=0;i<length; i++)
{
if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
{
s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer[i];
s_ring_buffer_t.datalen_u32++;
s_ring_buffer_t.in_u32++;
s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
sendlen++;
}
else
{
/* full */
break;
}
}
Exit_Critical();
return sendlen;
}
int uart_debug_send(uint32_t len)
{
uint32_t i;
uint8_t buff[16];
uint32_t slen;
if(0 != (slen = uart_debug_pop(buff, sizeof(buff))))
{
uart_write(buff, slen);
}
return slen;
}
int fputc(int ch, FILE *f)
{
uint8_t tmp = (uint8_t)ch;
/* Your implementation of fputc(). */
uart_debug_push(&tmp, 1);
return ch;
}
测试
Main.c中
#include "uart_debug.h"
while (1)
{
printf("Hello World\r\n");
uart_debug_send(16);
}
串口调试助手看到打印如下
总结
使用FIFO,将数据先陷入缓存区,再统一下主循环中不断查询发送,这样实现了非阻塞的串口输出,方便后面调试打印使用。
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144 基于FIFO的串口驱动
前言
为了方便后面USB调试,所以先实现串口通讯接口,前面进行了串口测试,但是接口不方便使用,需要实现方便应用层使用的串口收发接口。通过实现FIFO方式来实现串口的中断接收,发送为了简单使用查询发送方式。
过程
Bsp层
先实现bsp层的串口接口
Bsp_uart.c/h
初始化
void bsp_uart_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* IO Cfg
* PD9 USART3_RX
* PD8 USART3_TX
*/
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* Enable USART3 clock */
__HAL_RCC_USART3_CLK_ENABLE();
/* USART3 Cfg */
UartHandle.Instance = USART3;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&UartHandle);
/* NVIC */
HAL_NVIC_EnableIRQ(USART3_IRQn);
HAL_NVIC_SetPriority(USART3_IRQn,0,1);
LL_USART_EnableIT_RXNE(USART3);
}
发送
void uart_sendbyte(uint8_t ch)
{
uint8_t tmp =ch;
HAL_UART_Transmit(&UartHandle,&ch,1,0xFFFFFFFF);
//LL_USART_TransmitData8(USART3,ch);
}
void bsp_uart_send(uint8_t* p, uint32_t len)
{
HAL_UART_Transmit(&UartHandle,p,len,0xFFFFFFFF);
}
串口接收中断处理
extern void uart_rx_handler(const uint8_t *buffer, uint32_t length);
void USART3_IRQHandler(void)
{
uint8_t ch;
if(LL_USART_IsActiveFlag_RXNE(USART3) && LL_USART_IsEnabledIT_RXNE(USART3))
{
/* RXNE flag will be cleared by reading of RDR register (done in call) */
ch = LL_USART_ReceiveData8(USART3);
//LL_USART_TransmitData8(USART3,ch);
//while(LL_USART_IsActiveFlag_TC(USART3) == 0);
uart_rx_handler(&ch, 1);
}
}
代码详见
bap_uart.c
#include "stm32f7xx_hal.h"
#include "stm32f7xx_ll_usart.h"
UART_HandleTypeDef UartHandle;
extern void uart_rx_handler(const uint8_t *buffer, uint32_t length);
void USART3_IRQHandler(void)
{
uint8_t ch;
if(LL_USART_IsActiveFlag_RXNE(USART3) && LL_USART_IsEnabledIT_RXNE(USART3))
{
/* RXNE flag will be cleared by reading of RDR register (done in call) */
ch = LL_USART_ReceiveData8(USART3);
//LL_USART_TransmitData8(USART3,ch);
//while(LL_USART_IsActiveFlag_TC(USART3) == 0);
uart_rx_handler(&ch, 1);
}
}
void uart_sendbyte(uint8_t ch)
{
uint8_t tmp =ch;
HAL_UART_Transmit(&UartHandle,&ch,1,0xFFFFFFFF);
//LL_USART_TransmitData8(USART3,ch);
}
void bsp_uart_send(uint8_t* p, uint32_t len)
{
HAL_UART_Transmit(&UartHandle,p,len,0xFFFFFFFF);
}
void bsp_uart_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* IO Cfg
* PD9 USART3_RX
* PD8 USART3_TX
*/
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* Enable USART3 clock */
__HAL_RCC_USART3_CLK_ENABLE();
/* USART3 Cfg */
UartHandle.Instance = USART3;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&UartHandle);
/* NVIC */
HAL_NVIC_EnableIRQ(USART3_IRQn);
HAL_NVIC_SetPriority(USART3_IRQn,0,1);
LL_USART_EnableIT_RXNE(USART3);
}
bap_uart.h
#ifndef BSP_UART_H
#define BSP_UART_H
void bsp_uart_init(void);
void bsp_uart_send(uint8_t* p, uint32_t len);
#endif
驱动层
实现FIFO数据结构
typedef struct
{
uint32_t datalen_u32;
uint32_t maxlen_u32;
uint32_t in_u32;
uint32_t out_u32;
uint8_t* buffer_pu8;
}ring_buffer_t;
uint8_t uart_ring_buffer[128];
ring_buffer_t s_ring_buffer_t=
{
.datalen_u32 = 0,
.maxlen_u32 = sizeof(uart_ring_buffer),
.in_u32 = 0,
.out_u32 = 0,
.buffer_pu8 = uart_ring_buffer,
};
临界段处理
#define Alloc_Critical()
#define Enter_Critical() __disable_irq()
#define Exit_Critical() __enable_irq
接收处理,接收中断服务函数中调用
void uart_rx_handler(const uint8_t *buffer, uint32_t length)
{
uint32_t i;
for(i=0;i<length; i++)
{
if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
{
s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer[i];
s_ring_buffer_t.datalen_u32++;
s_ring_buffer_t.in_u32++;
s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
}
else
{
/* full */
break;
}
}
}
接收API
uint32_t uart_read(uint8_t *buff, uint32_t len)
{
uint32_t readlen = 0;
//uint32_t mask;
if(s_ring_buffer_t.datalen_u32 == 0)
{
return 0;
}
Alloc_Critical();
Enter_Critical();
uint32_t i;
for(i=0;i<len;i++)
{
if(s_ring_buffer_t.datalen_u32 > 0)
{
buff[i] = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
s_ring_buffer_t.datalen_u32--;
s_ring_buffer_t.out_u32++;
s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
readlen++;
}
else
{
break;
}
}
Exit_Critical();
return readlen;
}
发送API
int uart_write(uint8_t *buff, uint32_t len)
{
uint32_t i;
for(i=0; i<len ;i++)
{
uart_sendbyte(buff[i]);
}
return 0;
}
代码详见
uart.c
#include "uart.h"
#include "bsp_uart.h"
#include "stm32f7xx.h"
#define Alloc_Critical()
#define Enter_Critical() __disable_irq()
#define Exit_Critical() __enable_irq()
typedef struct
{
uint32_t datalen_u32;
uint32_t maxlen_u32;
uint32_t in_u32;
uint32_t out_u32;
uint8_t* buffer_pu8;
}ring_buffer_t;
uint8_t uart_ring_buffer[128];
ring_buffer_t s_ring_buffer_t=
{
.datalen_u32 = 0,
.maxlen_u32 = sizeof(uart_ring_buffer),
.in_u32 = 0,
.out_u32 = 0,
.buffer_pu8 = uart_ring_buffer,
};
void uart_rx_handler(const uint8_t *buffer, uint32_t length)
{
uint32_t i;
for(i=0;i<length; i++)
{
if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
{
s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer[i];
s_ring_buffer_t.datalen_u32++;
s_ring_buffer_t.in_u32++;
s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
}
else
{
/* full */
break;
}
}
}
uint32_t uart_read(uint8_t *buff, uint32_t len)
{
uint32_t readlen = 0;
//uint32_t mask;
if(s_ring_buffer_t.datalen_u32 == 0)
{
return 0;
}
Alloc_Critical();
Enter_Critical();
uint32_t i;
for(i=0;i<len;i++)
{
if(s_ring_buffer_t.datalen_u32 > 0)
{
buff[i] = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
s_ring_buffer_t.datalen_u32--;
s_ring_buffer_t.out_u32++;
s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
readlen++;
}
else
{
break;
}
}
Exit_Critical();
return readlen;
}
int uart_write(uint8_t *buff, uint32_t len)
{
uint32_t i;
for(i=0; i<len ;i++)
{
uart_sendbyte(buff[i]);
}
return 0;
}
uart.h
#ifndef UART_H
#define UART_H
#include <stdint.h>
void uart_rx_handler(const uint8_t *buffer, uint32_t length);
uint32_t uart_read(uint8_t *buff, uint32_t len);
int uart_write(uint8_t *buff, uint32_t len);
extern void uart_sendbyte(uint8_t ch);
#endif
测试
main.c中
#include "bsp_uart.h"
#include "uart.h"
bsp_uart_init();
uint8_t tx_buffer[2]={0xAA,0x55};
/* Infinite loop */
while (1)
{
uint8_t buffer[128];
for(;;)
{
uint32_t len=0;
if((len = uart_read(buffer, sizeof(buffer))) >0)
{
uart_write(buffer, len);
}
}
}
使用串口调试助手发送数据,开发板收到原样返回
总结
基于bsp层的串口收发,实现了基于FIFO的串口驱动,给应用层提供串口收发接口,uart.c和uart.h具备可移植性,方便移植到不同平台。方便后面的调试使用。
- 2023-10-29
-
发表了主题帖:
【ST多款开发板返场测评】STM32F767 Nucleo-144USB HID设备测试
本帖最后由 qinyunti 于 2023-10-29 23:09 编辑
[localvideo]dea60e52f47cbe26111995cd3fb93e91[/localvideo]
前言
本文我们来实现USB开发环境的搭建,实现USB通讯,为后面的UVC摄像头实现做准备。
USB模块
原理图部分
USB模块详见手册章节,
41 USB on-the-go full-speed/high-speed
(OTG_FS/OTG_HS)
支持全速和高速,高速时需要使用外接PHY,全速时使用内置PHY。
USB HID设备
以官方的Demo,HID为例进行USB开发的体验。
打开工程STM32Cube_FW_F7_V1.17.0\Projects\STM32F767ZI-Nucleo\Applications\USB_Device\HID_Standalone\MDK-ARM\Project.uvprojx
编译运行
CN13 MicroUSB接电脑,可以看到枚举的设备
PID &VID 分别是 0x5710 & 0x0483,代码中使用如下宏定义
按键USER可以看到鼠标左右移动。详见视频。
使用UsbTreeView查看描述符如下
使用bushound抓包如下
相关代码介绍
Core下面是usbd的驱动
Usbd_hid部分是HID设备类相关代码
Usbd_conf.c是PCD相关代码
Usbd_desc.c是描述符相关内容
初始化过程
USBD_Init->
USBD_RegisterClass->注册设备类
USBD_Start-> usb连接,对于全速即上拉DP的1.5K电阻。
USB基本是中断驱动的,大部分是在中断回调中处理即
OTG_FS_IRQHandler->HAL_PCD_IRQHandler,在中断中处理各种事件。
BSP_PB_Init
按键进入外部中断服务函数更新HID报告数据
HAL_GPIO_EXTI_Callback->USBD_HID_SendReport
总结
注意CBN13的USB接口为Micro USB-AB,方形口,和普通Micro USB弧形的不太一样,不过线可以通用注意方向,注意接触不好可以稍微压一下。
- 2023-10-27
-
发表了主题帖:
飞凌OK3568-C开发板基于vscode直接板上开发调试
本帖最后由 qinyunti 于 2023-10-27 14:05 编辑
一.前言
前面我们搭建了libusb的开发环境进行编译和调试,但是都是基于命令行的方式,不是很方便。这一篇我们来介绍基于vscode的开发环境搭建。
二.安装vscode
sudo apt install software-properties-common apt-transport-https wget
wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add -
sudo add-apt-repository "deb [arch=arm64] https://packages.microsoft.com/repos/vscode stable main"
sudo apt install code
此时看到安装的vscode,但是还不能直接打开
需要按如下设置
输入code查找到vscode,右键点击Edit
修改命令行为/usr/share/code/code --no-sandbox --unity-launch %F,点击save
此时就可以打开vscode了
开始需要设置一个密码
三.Vscode使用
3.1打开文件夹
3.2编译配置
创建.vscode文件夹,下面创建tasks.json文件
输入以下内容
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"command": "gcc libusb/*.c libusb/os/events_posix.c libusb/os/linux_udev.c libusb/os/linux_usbfs.c libusb/os/threads_posix.c ./examples/listdevs.c -Ilibusb -Ilibusb/os -Iexamples -lpthread -ludev -g -o listdevs",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
此时可以菜单栏Terminal->Run Build Task...进行编译
3.3调试配置
安装c++扩展
创建launch.json文件
输入以下内容
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/listdevs",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
}
]
}
点击Run->Start Debugging
自动停在了main函数处
可以使用如下按钮进行执行控制,这样比直接使用gdb命令行调试更方便一些,阅读代码也更方便直观。
总结
使用vscode轻量高效,推荐使用该方法。
得益于改开发板强劲的性能和完善的开发环境,可以直接将开发板作为开发主机进行来发,
并且基于vscode可以实现轻量化的ide开发,比命令行开发调试更高效,又避免了交叉开发的繁琐。