- 2025-03-26
-
回复了主题帖:
【先楫HPM5361】ADC篇-获取温湿度
这个不是专业仪器测的话,应该很难察觉出来吧
- 2025-03-24
-
发表了主题帖:
【先楫HPM5361】DAC篇
本次dome主要是通过dac输出一个锯齿波,然后通过adc去采集,然后串口打印出来。
代码还是通过官方的sdk,进行修改,完成的。
本次使用到的管脚如下:
dac_Pin:PB08
adc_pin:PB05
核心代码:
/*先楫HPM5361EVK的16位ADC共支持四种采样模式,分别是读取转换模式、周期转换模式、序列转换模式和抢占转换模式。这次使用的是读取转换模式,读取 BUS_RESULTx 寄存器将触发对 ADC 输入 x 的一次转换。一旦转换完成,结果将直接返回。根据不同的 ADC 通道配置,返回转换结果的时间也会有所不同。*/
static void Digital_Ctrl(void)
{
printf("Set DAC to output data in direct mode\n");
printf("DAC is outputting a triangle waveform in direct mode\n");
init_oneshot_config();
while(1)
{
channel_result_out_of_threshold_handler();
/* triangle waveform */
for (int32_t j = 0; j < 5000; j++) {
for (float i = 0; i <= 10000; i += 2.5) { /* NOLINT */
dac_set_direct_config(BOARD_DAC_BASE, (uint16_t)DAC_OUTPUT(i));
board_delay_us(1);
oneshot_handler();
}
for (float i = 10000; i >= 0; i -= 2.5) { /* NOLINT */
dac_set_direct_config(BOARD_DAC_BASE, (uint16_t)DAC_OUTPUT(i));
board_delay_us(1);
oneshot_handler();
}
}
if (abort_handler(1)) {
break;
}
}
}
int main(void)
{
uint8_t output_mode;
/* Initialize Bsp */
board_init();
/* Set a log title */
printf("This is a DAC demo:\n");
/* Initialize a DAC clock */
board_init_dac_clock(BOARD_DAC_BASE, false);
/* Initialize a DAC pin */
board_init_dac_pins(BOARD_DAC_BASE);
/************************************空耳***********************************/
/* ADC pin initialization */
board_init_adc16_pins();
/* ADC clock initialization */
board_init_adc_clock(BOARD_APP_ADC16_BASE, true);
printf("This is an ADC16 demo:\n");
/************************************空耳***********************************/
while (1) {
/* Get the output mode from a console */
output_mode = get_dac_mode();
/* Initialize a DAC peripheral with a common config */
init_common_config(output_mode);
/* ADC16 common initialization */
adc_init_common_config(output_mode);
/* Enable the DAC IRQ */
intc_m_enable_irq(BOARD_DAC_IRQn);
/* Enable DAC conversion */
dac_enable_conversion(BOARD_DAC_BASE, true);
/* Set a specified DAC output config */
switch (output_mode) {
case dac_mode_direct:
//set_direct_mode_config();
//init_oneshot_config();
Digital_Ctrl();
break;
case dac_mode_step:
set_step_mode_config();
break;
case dac_mode_buffer:
set_buffer_mode_config();
break;
case 4 :
break;
default:
break;
}
}
结果演示:
总结
先辑的sdk是真的好用,还相当的齐全。其实基于这个还可以实现一个简易的数控电压源。
dac控制开关电源芯片的FB引脚的电压(可以添加一个电压跟随器,提高驱动能力),adc去采集输出电压,串口设置输出电压,pid闭环控制。
- 2025-03-23
-
发表了主题帖:
【先楫HPM5361】ADC篇-获取温湿度
一,前言
本次实例为通过ADC获取温湿度,该说不说16位的ad我还是第一次用。本次使用的温湿度传感器为SHT30,这是一个 模拟温湿度传感器(±3%)。
原理图:
这里面最重要的是这两个对应关系:
本次使用到的adc引脚分别是PB0与PB11
ADC配置如下:
二,核心代码
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-03-03 Administrator the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "rtt_board.h"
#ifdef BSP_USING_ADC12
#include "hpm_adc12_drv.h"
#endif
#ifdef BSP_USING_ADC16
#include "hpm_adc16_drv.h"
#endif
#define ADC_DEV_NAME "adc0" /* ADC 设备名称 */
#define ADC_TEMP_CHANNEL 2 /* ADC 通道 */
#define ADC_HUMIDITY_CHANNEL 3 /* ADC 通道 */
#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/
#ifdef BSP_USING_ADC16
#define CONVERT_BITS (1 << 16) /*16 bit*/
#endif
#ifdef BSP_USING_ADC12
#define CONVERT_BITS (1 << 12) /*162 bit*/
#endif
static int adc_vol_sample(int argc, char *argv[])
{
rt_adc_device_t adc_temp,adc_humidity;
rt_uint32_t value_temp, vol_temp,value_humidity,vol_humidity;
float temp,humidity;
rt_err_t ret = RT_EOK;
/* 查找设备 */
adc_temp = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
adc_humidity = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
if (adc_temp == RT_NULL || adc_humidity == RT_NULL)
{
rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_TEMP_CHANNEL);
return RT_ERROR;
}
/* 使能设备 */
ret = rt_adc_enable(adc_temp, ADC_TEMP_CHANNEL);
if (ret != RT_EOK) {
rt_kprintf("Failed to enable ADC temperature channel: %d\n", ret);
return ret; // 返回错误或适当处理
}
ret = rt_adc_enable(adc_humidity, ADC_HUMIDITY_CHANNEL);
if (ret != RT_EOK) {
rt_kprintf("Failed to enable ADC humidity channel: %d\n", ret);
return ret; // 返回错误或适当处理
}
while(1)
{
/* 读取采样值 */
value_temp = rt_adc_read(adc_temp, ADC_TEMP_CHANNEL);
value_humidity = rt_adc_read(adc_humidity, ADC_HUMIDITY_CHANNEL);
// rt_kprintf("the value is :%d \n", value);
/* 转换为对应电压值 */
vol_temp = value_temp * REFER_VOLTAGE / CONVERT_BITS;
vol_humidity = value_humidity * REFER_VOLTAGE / CONVERT_BITS;
// printf("%d,%d \n", vol_temp,vol_humidity);
humidity = -12.50 + 125.00*(vol_humidity/330.00);
temp = -66.875+218.75*(vol_temp / 330.00);
// humidity = -(10.0 / 0.8) + (100.0 / 0.8)*((float)vol_humidity/330.00);
// temp = -45.0-(31.5 / 0.8) + (315.0/ 0.8)*((float)vol_temp / 330.00);
printf("%.2f,%.2f \n", temp,humidity);
rt_thread_mdelay(500);
}
/* 关闭通道 */
ret = rt_adc_disable(adc_temp, ADC_TEMP_CHANNEL);
ret = rt_adc_disable(adc_humidity, ADC_HUMIDITY_CHANNEL);
return ret;
}
/* 导出到 msh 命令列表中 */
//MSH_CMD_EXPORT(adc_vol_sample, adc voltage convert sample);
// 导出函数自动运行,在系统初始化时调用usr_led_run函数
INIT_APP_EXPORT(adc_vol_sample);
三,实例演示
将手放上去会有明显的湿度值上升:
四,问题记录
就这个问题,就是死活找不到定义,主要还是自己对目录结构不是很熟悉,在这里记录一下。
右键资源配置----》添加构建。
最后还是推荐不要使用这种模拟量的温湿度传感器,可能因为走线或者焊接的问题,测量的会很不准确。
- 2025-03-22
-
回复了主题帖:
【2024 DigiKey大赛参与奖】开箱帖
jobszheng5 发表于 2025-3-22 19:32
这个看着好漂亮,好cute啊
确实好看
-
发表了主题帖:
【2024 DigiKey大赛参与奖】开箱帖
非常荣幸参见本次【2024 DigiKey 创意大赛】,并在本次活动中获得参与奖。这一次购买的板卡是 M5STACK家的Roller 485。
还是熟悉的外包装:
好久都没见过纸箱包装了。
下面就是本次参与奖购买的板卡,相当的精致。
虽然无刷电机自己组装一个成本也就100多,但是这个是真的很精致。
最后再次感谢EEWorld和得捷电子。
- 2025-03-19
-
回复了主题帖:
大家都用上RISCV的芯片了吗
还挺多,先辑的hpm5300,还有很多串口屏,都用的·risc-v的
- 2025-03-18
-
回复了主题帖:
【先楫HPM5361】CAN篇-汽车ODB数据获取及可视化显示
确实
- 2025-03-17
-
发表了主题帖:
【先楫HPM5361】CAN篇-汽车ODB数据获取及可视化显示
通过这几天的不屑努力,终于将CAN跑通了,接下来是一个简单的dome。读取汽车的ODB数据,并显示。
这一次使用的开发环境不是RT-Thread Studio了,而是SEGGER Embedded Studio ,这个软件上手还挺快,不过我就是找不到下载在那,每次都是把调试当作下载用。
这里SEGGER Embedded Studio开发环境就不再zhuisu了,和KEIKL还是比较接近的。
这里只获取的汽车的一些基础数据,车速,转速,水温,电压,之内的。主要采用的是IS0-15765协议,500k的速率,标准帧。
核心代码,是在官方sdk上修改了一下,官方的sdk还是给的相当的齐全的。
核心代码:
void board_can_isr(void)
{
MCAN_Type *base = BOARD_APP_CAN_BASE;
uint32_t flags = mcan_get_interrupt_flags(base);
/* New message is available in RXFIFO0 */
if ((flags & MCAN_INT_RXFIFO0_NEW_MSG) != 0) {
mcan_read_rxfifo(base, 0, (mcan_rx_message_t *) &s_can_rx_buf);
has_new_rcv_msg = true;
rxfifo0_event_occurred = true;
}
/* New message is available in RXFIFO1 */
if ((flags & MCAN_INT_RXFIFO1_NEW_MSG) != 0U) {
mcan_read_rxfifo(base, 1, (mcan_rx_message_t *) &s_can_rx_buf);
has_new_rcv_msg = true;
rxfifo1_event_occurred = true;
}
/* New message is available in RXBUF */
if ((flags & MCAN_INT_MSG_STORE_TO_RXBUF) != 0U) {
has_new_rcv_msg = true;
/* NOTE: Below code is for demonstration purpose, the performance is not optimized
* Users should optimize the performance according to real use case.
*/
for (uint32_t buf_index = 0; buf_index < MCAN_RXBUF_SIZE_CAN_DEFAULT; buf_index++) {
if (mcan_is_rxbuf_data_available(base, buf_index)) {
mcan_read_rxbuf(base, buf_index, (mcan_rx_message_t *) &s_can_rx_buf);
mcan_clear_rxbuf_data_available_flag(base, buf_index);
}
}
}
/* New TX Event occurred */
if ((flags & MCAN_INT_TX_EVT_FIFO_NEW_ENTRY) != 0) {
mcan_read_tx_evt_fifo(BOARD_APP_CAN_BASE, (mcan_tx_event_fifo_elem_t *) &s_can_tx_evt);
tx_event_occurred = true;
}
/* Transmit completed */
if ((flags & MCAN_EVENT_TRANSMIT) != 0U) {
has_sent_out = true;
}
/* Error happened */
if ((flags & MCAN_EVENT_ERROR) != 0) {
has_error = true;
}
if ((flags & MCAN_INT_TIMEOUT_OCCURRED) != 0) {
timeout_event_occurred = true;
}
mcan_clear_interrupt_flags(BOARD_APP_CAN_BASE, flags);
}
/******************************************************************************************/
#define PID_ENGINE_LOAD 0x04
#define PID_COOLANT_TEMP 0x05 //表示发动机冷却液的温度,通常以摄氏度(°C)为单位,是判断发动机温度状态的重要参数。
#define PID_SHORT_TERM_FUEL_TRIM_1 0x06
#define PID_LONG_TERM_FUEL_TRIM_1 0x07
#define PID_SHORT_TERM_FUEL_TRIM_2 0x08
#define PID_LONG_TERM_FUEL_TRIM_2 0x09
#define PID_FUEL_PRESSURE 0x0A
#define PID_INTAKE_MAP 0x0B
#define PID_RPM 0x0C
#define PID_SPEED 0x0D
#define PID_TIMING_ADVANCE 0x0E
#define PID_INTAKE_TEMP 0x0F
#define PID_MAF_FLOW 0x10
#define PID_THROTTLE 0x11
#define PID_AUX_INPUT 0x1E
#define PID_RUNTIME 0x1F
#define PID_DISTANCE_WITH_MIL 0x21
#define PID_COMMANDED_EGR 0x2C
#define PID_EGR_ERROR 0x2D
#define PID_COMMANDED_EVAPORATIVE_PURGE 0x2E
#define PID_FUEL_LEVEL 0x2F
#define PID_WARMS_UPS 0x30
#define PID_DISTANCE 0x31
#define PID_EVAP_SYS_VAPOR_PRESSURE 0x32
#define PID_BAROMETRIC 0x33
#define PID_CATALYST_TEMP_B1S1 0x3C
#define PID_CATALYST_TEMP_B2S1 0x3D
#define PID_CATALYST_TEMP_B1S2 0x3E
#define PID_CATALYST_TEMP_B2S2 0x3F
#define PID_CONTROL_MODULE_VOLTAGE 0x42
#define PID_ABSOLUTE_ENGINE_LOAD 0x43
#define PID_AIR_FUEL_EQUIV_RATIO 0x44
#define PID_RELATIVE_THROTTLE_POS 0x45
#define PID_AMBIENT_TEMP 0x46
#define PID_ABSOLUTE_THROTTLE_POS_B 0x47
#define PID_ABSOLUTE_THROTTLE_POS_C 0x48
#define PID_ACC_PEDAL_POS_D 0x49
#define PID_ACC_PEDAL_POS_E 0x4A
#define PID_ACC_PEDAL_POS_F 0x4B
#define PID_COMMANDED_THROTTLE_ACTUATOR 0x4C
#define PID_TIME_WITH_MIL 0x4D
#define PID_TIME_SINCE_CODES_CLEARED 0x4E
#define PID_ETHANOL_FUEL 0x52
#define PID_FUEL_RAIL_PRESSURE 0x59
#define PID_HYBRID_BATTERY_PERCENTAGE 0x5B
#define PID_ENGINE_OIL_TEMP 0x5C
#define PID_FUEL_INJECTION_TIMING 0x5D
#define PID_ENGINE_FUEL_RATE 0x5E
#define PID_ENGINE_TORQUE_DEMANDED 0x61
#define PID_ENGINE_TORQUE_PERCENTAGE 0x62
#define PID_ENGINE_REF_TORQUE 0x63
uint8_t PID[]={PID_RPM,PID_SPEED,PID_DISTANCE,PID_CONTROL_MODULE_VOLTAGE,PID_ENGINE_FUEL_RATE,PID_EVAP_SYS_VAPOR_PRESSURE,PID_COOLANT_TEMP};
typedef struct
{
uint16_t FH;
uint16_t rpm; //表示发动机的转速(RPM),对于监控发动机性能至关重要。
uint16_t speed; //时速
short coolant_temp; //水温
uint8_t Control_Module_Voltage; //控制模块的电压
int Distance; //总里程
int Trip_Distance; //小计里程
short map; //进气压力
uint16_t engine_fuel_rate; //瞬时油耗
uint8_t CHECK_SUM;
}ODB_info;
ODB_info g_odb_message;
void serialize_sensor_data( ODB_info *data, uint8_t *buffer, size_t buffer_size) {
if (buffer_size < sizeof(ODB_info)) {
// Handle error: buffer too small
return;
}
data->FH = 0x5AA5;
data->CHECK_SUM = ( data->rpm + data->speed + data->coolant_temp + data->Control_Module_Voltage + data->Distance + data->Trip_Distance + data->engine_fuel_rate) & 0xff;
memcpy(buffer, data, sizeof(ODB_info));
for(int i = 0;i< sizeof(ODB_info);i++)
{
printf("%c",buffer[i]);
}
}
void ODB_Info_analysis(void)
{
uint8_t pid,data1,data2;
uint8_t odb_data[sizeof(ODB_info)];
pid = s_can_rx_buf.data_8[2];
data1 = s_can_rx_buf.data_8[3];
data2 = s_can_rx_buf.data_8[4];
short value = 0;
switch (pid)
{
case PID_RPM://转速
value = (data2 | data1 << 8) / 4;
g_odb_message.rpm = value;
//printf("PID_RPM %d \n",value);
break;
case PID_SPEED://车速
value = data1 ;
//printf("PID_SPEED :%d\n",value);
break;
case PID_DISTANCE: // km//表示总行驶里程
value = (data2 | data1 << 8) ;
g_odb_message.Distance = value;
//printf("PID_DISTANCE :%d\n",value);
break;
case PID_CONTROL_MODULE_VOLTAGE: // 表示控制模块的电压,通常用于监控电源状态。 v
value = (data2 | data1 << 8) / 1000;
g_odb_message.Control_Module_Voltage = value;
//printf("PID_CONTROL_MODULE_VOLTAGE :%d\n",value);
break;
case PID_ENGINE_FUEL_RATE: // L/h //表示单位时间内的燃油消耗量(通常以升每小时或加仑每小时为单位)。
value = (data2 | data1 << 8) / 20;
g_odb_message.engine_fuel_rate = value;
//printf("PID_ENGINE_FUEL_RATE :%d\n",value);
break;
case PID_EVAP_SYS_VAPOR_PRESSURE: // kPa 表示蒸发系统内的气压,通常用于监测油箱的密封性。
value = (data2 | data1 << 8) / 4;
g_odb_message.map = value;
//printf("PID_EVAP_SYS_VAPOR_PRESSURE :%d\n",value);
break;
case PID_INTAKE_MAP: // 涡轮进气压力
value = data1 - 100;
//printf("PID_EVAP_SYS_VAPOR_PRESSURE :%d\n",value);
break;
case PID_COOLANT_TEMP: // 水温
value = data1 - 40;
g_odb_message.coolant_temp = value;
//printf("PID_COOLANT_TEMP :%d\n",value);
break;
default:
break;
}
serialize_sensor_data(&g_odb_message,odb_data,sizeof(ODB_info));
}
void board_can_rxbuf_test(void)
{
MCAN_Type *base = BOARD_APP_CAN_BASE;
mcan_config_t can_config;
mcan_filter_elem_t can_filters[16];
mcan_get_default_config(base, &can_config);
can_config.baudrate = 500000; /* 500kbps */
can_config.mode = mcan_mode_loopback_internal;
board_init_can(base);
uint32_t can_src_clk_freq = board_init_can_clock(base);
uint32_t interrupt_mask = MCAN_EVENT_RECEIVE;
/* NOTE: CAN RXBUF must be used with the CAN Filter together.
*
* In the example, only the message with ID 0x123 is allowed to be stored into RXBUF0, the other messages
* get ignored by the CAN controller according to the CAN Filter setting
*
*/
uint32_t rxbuf_index = 1;
can_filters[0].filter_type = MCAN_FILTER_TYPE_CLASSIC_FILTER;
can_filters[0].filter_config = MCAN_FILTER_ELEM_CFG_STORE_INTO_RX_BUFFER_OR_AS_DBG_MSG;
can_filters[0].can_id_type = MCAN_CAN_ID_TYPE_STANDARD;
can_filters[0].match_id = 0x123;
can_filters[0].offset = rxbuf_index;
can_filters[0].filter_event = 0;
can_filters[0].store_location = 0;
can_config.all_filters_config.ext_id_filter_list.mcan_filter_elem_count = 0;
can_config.all_filters_config.std_id_filter_list.filter_elem_list = &can_filters[0];
can_config.all_filters_config.std_id_filter_list.mcan_filter_elem_count = 1;
can_config.all_filters_config.global_filter_config.accept_non_matching_std_frame_option =
MCAN_ACCEPT_NON_MATCHING_FRAME_OPTION_REJECT;
can_config.all_filters_config.global_filter_config.accept_non_matching_ext_frame_option =
MCAN_ACCEPT_NON_MATCHING_FRAME_OPTION_REJECT;
can_config.all_filters_config.global_filter_config.reject_remote_std_frame = true;
can_config.all_filters_config.global_filter_config.reject_remote_ext_frame = true;
can_config.txbuf_trans_interrupt_mask = ~0UL;
can_config.interrupt_mask = interrupt_mask;
hpm_stat_t status = mcan_init(base, &can_config, can_src_clk_freq);
if (status != status_success) {
printf("CAN initialization failed, error code: %d\n", status);
return;
}
intc_m_enable_irq_with_priority(BOARD_APP_CAN_IRQn, 1);
mcan_tx_frame_t tx_buf;
memset(&tx_buf, 0, sizeof(tx_buf));
tx_buf.dlc = 8;
for (uint32_t i = 0; i < 8; i++) {
tx_buf.data_8[i] = (uint8_t) i | (i << 4);
}
for (uint32_t i = 0; i < 2048; i++) {
tx_buf.std_id = i;
/* Demonstrate Transmit via TXBUF in non-blocking way here
*/
uint32_t buf_index = 2;
mcan_transmit_via_txbuf_nonblocking(base, buf_index, &tx_buf);
while (!mcan_is_transmit_occurred(base, buf_index)) {
}
}
bool test_pass = false;
/* If below condition was met, it means this test case passed */
if (has_new_rcv_msg) {
has_new_rcv_msg = false;
has_sent_out = false;
printf("New message received, ID=%08x\n", s_can_rx_buf.std_id);
if (s_can_rx_buf.std_id == 0x123) {
test_pass = true;
}
}
printf("%s %s\n", __func__, test_pass ? "PASSED" : "FAILED");
}
成果演示
[localvideo]fbbea330f25cfd97c56c3f00d36f55a7[/localvideo]
[localvideo]6ef3fbf80b52471fd6749c34cdf4cd59[/localvideo]
简单的上位机界面
这个界面只写了,转速,其他的还没有往上添加。
- 2025-03-10
-
回复了主题帖:
《Hello算法》哈希表学习之一——理论学习
本质是空间换时间吗,我记得前段时间有一个电子档的
- 2025-03-08
-
回复了主题帖:
在STM32F103上移植MultiButton按键,但是没有实现
用你代码注释的那部分可以, button_attach(&button1, SINGLE_CLICK, button_callback); button_attach(&button1, SINGLE_CLICK, button_callback);,这两个都是同一个单机信号,你得换一个
- 2025-03-06
-
回复了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
lugl4313820 发表于 2025-3-6 17:07
这两个都不是。
《RISC-V 开放架构设计之道》- RISC-V开放架构设计简读 - 编程基础 - 电子工程 ...
好的,谢谢
-
回复了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
lugl4313820 发表于 2025-3-6 16:20
你找一下@RSCN大佬,他是这方面的专家,专玩这个板子的。
佬是那一个呀
-
回复了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
lugl4313820 发表于 2025-3-6 16:20
你找一下@RSCN大佬,他是这方面的专家,专玩这个板子的。
好的,好的,我就知道版主最有办法了
-
回复了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
damiaa 发表于 2025-3-6 10:25
回环模式是内部测试,正常模式要接外部can总线。
说明can总线接口方面还有问题吧。
总线应该是通的,好像没有数据出来,他数据没发送完就卡死了
-
回复了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
Jacktang 发表于 2025-3-6 07:26
正常模式死活不通,这个与代码有关?
不知道啊,吧正常模式改为回环模式就又可以了,
我与另一个can设备之间没有接地
还有就是我是电脑usb供电的,不知道会不会供电不足
-
发表了主题帖:
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
本帖最后由 空耳- 于 2025-3-6 12:38 编辑
先楫HPM5361 can通讯回环模式正常,正常模式死活不通,麻烦各位大佬帮我看一下
/*
* Copyright (c) 2022-2023 HPMicro
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Change Logs:
* Date Author Notes
* 2022-05-08 hpmicro the first version
* 2023-05-08 hpmicro Adapt the RT-Thread V5.0.0
*/
/*****************************************************************************************
*
* CAN Example
*
* This demo works in internal loopback mode. It demonstrates following functions:
*
* 1. CAN transmission and reception with standard ID
* 2. CAN transmission and reception with extended ID
* 3. CAN transmission and reception with CAN filter enabled
* 4. CANFD transmission and reception
*
*
*****************************************************************************************/
#include <rtthread.h>
#include <rtdevice.h>
#include "rtt_board.h"
#define CAN_BAUD (500000)
#define CANFD_BAUD (2000000)
#define CAN_FILTERMODE_IDMASK (0x00000000U) /*!< Identifier mask mode */
static struct rt_semaphore rx_sem; /* Semaphore fore CAN receive */
static rt_device_t can_dev; /* CAN device handler */
static uint8_t can_get_data_bytes_from_dlc(uint32_t dlc);
static void can_rx_thread(void *parameter);
#define PID_ENGINE_LOAD 0x04
#define PID_COOLANT_TEMP 0x05 //表示发动机冷却液的温度,通常以摄氏度(°C)为单位,是判断发动机温度状态的重要参数。
#define PID_SHORT_TERM_FUEL_TRIM_1 0x06
#define PID_LONG_TERM_FUEL_TRIM_1 0x07
#define PID_SHORT_TERM_FUEL_TRIM_2 0x08
#define PID_LONG_TERM_FUEL_TRIM_2 0x09
#define PID_FUEL_PRESSURE 0x0A
#define PID_INTAKE_MAP 0x0B
#define PID_RPM 0x0C
#define PID_SPEED 0x0D
#define PID_TIMING_ADVANCE 0x0E
#define PID_INTAKE_TEMP 0x0F
#define PID_MAF_FLOW 0x10
#define PID_THROTTLE 0x11
#define PID_AUX_INPUT 0x1E
#define PID_RUNTIME 0x1F
#define PID_DISTANCE_WITH_MIL 0x21
#define PID_COMMANDED_EGR 0x2C
#define PID_EGR_ERROR 0x2D
#define PID_COMMANDED_EVAPORATIVE_PURGE 0x2E
#define PID_FUEL_LEVEL 0x2F
#define PID_WARMS_UPS 0x30
#define PID_DISTANCE 0x31
#define PID_EVAP_SYS_VAPOR_PRESSURE 0x32
#define PID_BAROMETRIC 0x33
#define PID_CATALYST_TEMP_B1S1 0x3C
#define PID_CATALYST_TEMP_B2S1 0x3D
#define PID_CATALYST_TEMP_B1S2 0x3E
#define PID_CATALYST_TEMP_B2S2 0x3F
#define PID_CONTROL_MODULE_VOLTAGE 0x42
#define PID_ABSOLUTE_ENGINE_LOAD 0x43
#define PID_AIR_FUEL_EQUIV_RATIO 0x44
#define PID_RELATIVE_THROTTLE_POS 0x45
#define PID_AMBIENT_TEMP 0x46
#define PID_ABSOLUTE_THROTTLE_POS_B 0x47
#define PID_ABSOLUTE_THROTTLE_POS_C 0x48
#define PID_ACC_PEDAL_POS_D 0x49
#define PID_ACC_PEDAL_POS_E 0x4A
#define PID_ACC_PEDAL_POS_F 0x4B
#define PID_COMMANDED_THROTTLE_ACTUATOR 0x4C
#define PID_TIME_WITH_MIL 0x4D
#define PID_TIME_SINCE_CODES_CLEARED 0x4E
#define PID_ETHANOL_FUEL 0x52
#define PID_FUEL_RAIL_PRESSURE 0x59
#define PID_HYBRID_BATTERY_PERCENTAGE 0x5B
#define PID_ENGINE_OIL_TEMP 0x5C
#define PID_FUEL_INJECTION_TIMING 0x5D
#define PID_ENGINE_FUEL_RATE 0x5E
#define PID_ENGINE_TORQUE_DEMANDED 0x61
#define PID_ENGINE_TORQUE_PERCENTAGE 0x62
#define PID_ENGINE_REF_TORQUE 0x63
typedef struct
{
uint16_t rpm; //表示发动机的转速(RPM),对于监控发动机性能至关重要。
uint16_t speed; //时速
short coolant_temp; //水温
uint8_t Control_Module_Voltage; //控制模块的电压
int Distance; //总里程
int Trip_Distance; //小计里程
short map; //进气压力
uint16_t engine_fuel_rate; //瞬时油耗
}ODB_info;
int main(void)
{
/* Search CAN device */
can_dev = rt_device_find(BOARD_CAN_NAME);
if (!can_dev)
{
rt_kprintf("find %s failed!\n", BOARD_CAN_NAME);
return RT_ERROR;
}
/* Initialize semaphore for CAN receive */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* OPEN CAN device using interrupt transmit and receive mode */
rt_err_t res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void*) RT_CAN_MODE_NORMAL);
rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void*)CAN_BAUD);
RT_ASSERT(res == RT_EOK);
/* Create receive thread */
rt_thread_t thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 1024, 25, 10);
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
rt_kprintf("create can_rx thread failed!\n");
}
}
/* Callback for receive */
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size)
{
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void can_rx_thread(void *parameter)
{
int i;
rt_err_t res;
struct rt_can_msg rxmsg = {0};
/* Set callback for receive */
rt_device_set_rx_indicate(can_dev, can_rx_call);
while (1)
{
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
/* Get one CAN frame */
#ifdef RT_CAN_USING_HDR
/* when RT_CAN_USING_HDR is turned on, read data from hdr for rtt can driver */
rxmsg.hdr_index = BOARD_CAN_HWFILTER_INDEX;
#endif
rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
/* Display CAN frame content */
rt_kprintf("ID: %x ", rxmsg.id);
uint32_t msg_len = can_get_data_bytes_from_dlc(rxmsg.len);
for (i = 0; i < msg_len; i++)
{
rt_kprintf("%02x", rxmsg.data[i]);
}
rt_kprintf("\n");
}
}
static uint8_t can_get_data_bytes_from_dlc(uint32_t dlc)
{
uint32_t data_bytes = 0;
dlc &= 0xFU;
if (dlc <= 8U) {
data_bytes = dlc;
} else {
switch (dlc) {
case 9:
data_bytes = 12U;
break;
case 10:
data_bytes = 16U;
break;
case 11:
data_bytes = 20U;
break;
case 12:
data_bytes = 24U;
break;
case 13:
data_bytes = 32U;
break;
case 14:
data_bytes = 48U;
break;
case 15:
data_bytes = 64U;
break;
default:
/* Code should never touch here */
break;
}
}
return data_bytes;
}
uint8_t PID[]={PID_RPM,PID_SPEED,PID_DISTANCE,PID_CONTROL_MODULE_VOLTAGE,PID_ENGINE_FUEL_RATE,PID_EVAP_SYS_VAPOR_PRESSURE,PID_COOLANT_TEMP};
int can_sample(int argc, char *argv[])
{
struct rt_can_msg msg = {0};
rt_err_t res;
rt_size_t size;
rt_thread_t thread;
char can_name[RT_NAME_MAX];
if (argc == 2)
{
rt_strncpy(can_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(can_name, BOARD_CAN_NAME, RT_NAME_MAX);
}
/* Search CAN device */
can_dev = rt_device_find(can_name);
if (!can_dev)
{
rt_kprintf("find %s failed!\n", can_name);
return RT_ERROR;
}
/* Restore CAN filters to default state */
rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, NULL);
msg.id = 0x07DF; /* ID 为 0x78 */
msg.ide = RT_CAN_STDID; /* Standard ID */
msg.rtr = RT_CAN_DTR; /* Data Frame */
msg.len = CAN_MSG_8BYTES; /* Data size: 8 */
/* Data to be sent */
msg.data[0] = 0x02;
msg.data[1] = 0x01;
msg.data[2] = 0x00;
msg.data[3] = 0x00;
msg.data[4] = 0x00;
msg.data[5] = 0x00;
msg.data[6] = 0x00;
msg.data[7] = 0x00;
rt_kprintf("Sent out messages with standard id\n");
// while(1)
{
for (uint32_t i = 0; i < sizeof(PID); i++)
{
// msg.id = i;
msg.data[2] = PID[i];
size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
if (size == 0)
{
rt_kprintf("can dev write data failed!\n");
}
rt_kprintf("can!\n");
rt_thread_mdelay(2000); /* delay a while, give the receive thread enough time to print out received message */
}
}
// rt_kprintf("Sent out messages with extended id\n");
// msg.ide = RT_CAN_EXTID; /* Extend ID */
// for (uint32_t i = 0; i < 10; i++)
// {
// msg.id = i + 0x10000;
// size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
// if (size == 0)
// {
// rt_kprintf("can dev write data failed!\n");
// }
// rt_thread_mdelay(5); /* delay a while, give the receive thread enough time to print out received message */
// }
/****************************************************************************
*
* Configure the CAN Filters
*
****************************************************************************/
struct rt_can_filter_item items[5] =
{
#ifdef RT_CAN_USING_HDR
/* std, match ID:0x100~0x1ff, default filter list */
RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, CAN_FILTERMODE_IDMASK, 0x700, RT_NULL, RT_NULL),
/* std, match ID:0x300~0x3ff*/
RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, CAN_FILTERMODE_IDMASK, 0x700, RT_NULL, RT_NULL),
/* std,match ID:0x211*/
RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, CAN_FILTERMODE_IDMASK, 0x7FF, RT_NULL, RT_NULL),
/* std,match ID:0x486*/
RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL),
/* std, match ID: 0x55, specify the filter number : 7 */
{0x555, 0, 0, CAN_FILTERMODE_IDMASK, 0x7ff, 7,}
#else
/* std, match ID:0x100~0x1ff, default filter list */
RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, CAN_FILTERMODE_IDMASK, 0x700),
/* std, match ID:0x300~0x3ff*/
RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, CAN_FILTERMODE_IDMASK, 0x700),
/* std,match ID:0x211*/
RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, CAN_FILTERMODE_IDMASK, 0x7FF),
/* std,match ID:0x486*/
RT_CAN_FILTER_STD_INIT(0x486),
/* std, match ID: 0x55, specify the filter number : 7 */
{0x555, 0, 0, CAN_FILTERMODE_IDMASK, 0x7ff, 7,}
#endif
};
// struct rt_can_filter_config cfg = {5, 1, items};
// res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);
// RT_ASSERT(res == RT_EOK);
//
// rt_kprintf("Sent out messages with filter enabled\n");
// msg.ide = RT_CAN_STDID;
// for (uint32_t i = 0; i < 0x7ff; i++)
// {
// msg.id = i;
// size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
// if (size == 0)
// {
// rt_kprintf("can dev write data failed!\n");
// }
// rt_thread_mdelay(5); /* delay a while, give the receive thread enough time to print out received message */
// }
#ifdef RT_CAN_USING_CANFD
/* Restore CAN filters to default state */
rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, NULL);
/* Re-Configure CAN baud rate */
rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void*)CAN_BAUD);
/* Configure CAN-FD baud rate */
rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD_FD, (void*)CANFD_BAUD);
/**
* NOTE: In real CAN-FD use case, user may need to use the similar codes as below
* to get the same bit-timing configuration in the whole CANFD network
*
* Assume the CAN clock is 80MHz, below is the bit timing for CANFD
*
*/
#if 0
struct rt_can_bit_timing timing_items[2] =
{
/* prescaler, num_seg1, num_seg2, num_sjw, num_sspoff */
{2, 32, 8, 8, 0}, /* 1Mbit/s */
{2, 16, 4, 4, 16}, /* 2Mbit/s */
};
struct rt_can_bit_timing_config timing_config =
{
2,
&timing_items[0];
};
rt_device_control(can_dev, RT_CAN_CMD_SET_BITTIMING, &timing_config);
#endif
/* Enable CANFD */
rt_device_control(can_dev, RT_CAN_CMD_SET_CANFD, (void*)1);
msg.len = CAN_MSG_16BYTES;
msg.brs = 1;
msg.fd_frame = 1;
for (uint32_t i=8; i<16; i++)
{
msg.data[i] = (uint8_t)(i | (i<<4));
}
rt_kprintf("Sent out CANFD messages with standard id\n");
for (uint32_t i = 0; i < 10; i++)
{
msg.id = i;
size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
if (size == 0)
{
rt_kprintf("can dev write data failed!\n");
}
rt_thread_mdelay(10); /* delay a while, give the receive thread enough time to print out received message */
}
rt_kprintf("Sent out CANFD messages with extended id\n");
msg.ide = RT_CAN_EXTID; /* Extend ID */
for (uint32_t i = 0; i < 10; i++)
{
msg.id = i + 0x10000;
size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
if (size == 0)
{
rt_kprintf("can dev write data failed!\n");
}
rt_thread_mdelay(10); /* delay a while, give the receive thread enough time to print out received message */
}
#endif
return RT_EOK;
}
/* EXPORT to msh command list */
MSH_CMD_EXPORT(can_sample, can device sample);
这个程序最终卡在这里:
size = rt_device_write(can_dev, 0, &msg, sizeof(msg));
//后面都没有执行到
if (size == 0)
{
rt_kprintf("can dev write data failed!\n");
}
rt_kprintf("can!\n");
一帧数据都没有发送完,就卡死在这了
- 2025-03-03
-
回复了主题帖:
【先楫HPM5361】USART篇-读取GPS数据
lugl4313820 发表于 2025-3-3 16:09
浮点打印,要以IDE的编译选项里进行设置,设置可打印浮点的开关。
学到了
- 2025-03-02
-
发表了主题帖:
【先楫HPM5361】USART篇-读取GPS数据
本帖最后由 空耳- 于 2025-3-3 11:40 编辑
读取GPS数据
串口实现的主要步骤:
首先查找串口设备获取设备句柄。
初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。
设置串口设备的接收回调函数,之后发送字符串,并创建读取数据线程。
读取数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。
运行序列图如下图所示:
这里我们使用USART2(使用的是PB08和PB09),对其进行简单配置
NMEA0183协议解析
我这里使用到的GNSS模组为:ATGM332D-5N31
GPS数据包类型:(GP :GPS;BD:北斗;GN:多星联合定位;GL:GLONASS)
GPGSV:可见卫星信息 GPGLL:地理定位信息 GPRMC:推荐最小定位信息 GPVTG:地面速度信息 GPGGA:GPS定位信息 GPGSA:当前卫星信息
我这里主要使用的是GNGGA,其标准协议为:
标准格式:$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh<CR><LF>
格式解析:
<1> UTC 时间,hhmmss(时分秒)格式
<2> 纬度ddmm.mmmm(度分)格式(前面的0 也将被传输)
<3> 纬度半球N(北半球)或S(南半球
<4> 经度dddmm.mmmm(度分)格式(前面的0 也将被传输)
<5> 经度半球E(东经)或W(西经)
<6> GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
<7> 正在使用解算位置的卫星数量(00~12)(前面的0 也将被传输)
<8> HDOP 水平精度因子(0.5~99.9)
<9> 海拔高度(-9999.9~99999.9)
<10> 地球椭球面相对大地水准面的高度
<11> 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
<12> 差分站ID 号0000~1023(前面的0 也将被传输,如果不是差分定位将为空
核心代码
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-03-02 Administrator the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/*******************************************************************************************/
#include "gps.h"
#include <string.h>
nmea_msg gpsx;
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] NMEA_Comma_pos 从buf里面得到第n个逗号所在的位置
* @argument 数组
* @argument 地n个','
* [url=home.php?mod=space&uid=784970]@return[/url] 逗号的位置
*/
#if 1
u8 NMEA_Comma_Pos(u8 *buf,u8 n)
{
u8 *ptr = buf;
while(n)
{
//遇到'*'或者非法字符,则不存在第cx个逗号
if(*ptr == '*' || *ptr < ' '|| *ptr > 'z')return 0xFF;
if(*ptr == ',')n--;
ptr++;
}
return ptr-buf;
}
/**
* @brief NMEA_Pow m^n次方
*/
u32 NMEA_Pow(u8 m,u8 n)
{
u32 result =1;
while(n--)result *= m;
return result;
}
/**
* @brief 字符串转数字
*/
int NMEA_Str2Num(u8 *buf,u8*dx)
{
u8 *p = buf;
u32 ires=0,fres=0;
u8 ilen = 0,flen =0,i;
u8 mask=0;
int res;
/*********得到个位十位***************/
while(1)
{
if(*p == '-')
{
mask |= 0x02;
p++;
}
if(*p == ',' || *p == '*')break;
if(*p == '.')
{
mask |= 0x01;
p++;
}
else if((*p >'9') || (*p < '0'))
{
ilen=0;
flen=0;
break;
}
if(mask & 0x01)flen++;//小数
else ilen++;//整数
p++;
}
if(mask&0x02)buf++;
for(i = 0;i<ilen;i++)
{
ires += NMEA_Pow(10,ilen-1-i)*(buf[i]-'0');
//LTPrintf("buf[%d]:%d ,ires:%d\n",i,(buf[i]-'0'),ires);
}
if(flen>5)flen = 5;
*dx = flen;
for(i=0;i<flen;i++)
{
fres += NMEA_Pow(10,flen-1-i)*(buf[ilen+1+i]-'0');
}
//LTPrintf("ilen:%d flen%d ires:%d fres:%d\n",ilen,flen,ires,fres);
res = ires*NMEA_Pow(10,flen)+fres;
if(mask&0x02)res = -res;
return res;
}
/**
* @brief 分析GPGSV信息信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note GSV表示可视的GNSS卫星。本语句包含可视的卫星数、卫星标识号、仰角、方位角和信噪比。每次传送,
* 一个GSV语句只能包含最多4颗卫星的数据,
* 因此可能需要多个语句才能获得完整的信息。由于GSV包含的卫星不用于定位解决方案,所以GSV语句指示的卫星可能比GGA多。
* @note “GN”标识符不可用于该语句。如果可以多个卫星系统可视,则设备输出多条GSV语句,用不同的发送设备标识符表示相应的卫星。
**/
void NMEA_GPGSV_Analysis(nmea_msg *gpsx,u8 *buf)
{
u8 *p,*p1,dx;
u8 len,i,j,slx = 0;
u8 posx;
p = buf;
p1 = (u8*)strstr((const char*)p,"$GPGSV");
len = p1[7] - '0'; //得到GPGSV的条数语句总数。范围:1~9。
posx=NMEA_Comma_Pos(p1,3); //得到可见卫星总数
if(len!=0xFF)gpsx->svnum = NMEA_Str2Num(p1+posx,&dx);
for(i=0;i<len;i++)
{
p1 = (u8*)strstr((const char*)p,"$GPGSV");
for(j=0;j<4;j++)
{
posx=NMEA_Comma_Pos(p1,4+j*4);
if(posx!=0xFF)gpsx->slmsg[slx].num = NMEA_Str2Num(p1+posx,&dx);//得到卫星编号
else break;
posx=NMEA_Comma_Pos(p1,5+j*4);
if(posx!=0xFF)gpsx->slmsg[slx].eledeg= NMEA_Str2Num(p1+posx,&dx);//得到卫星仰角
else break;
posx=NMEA_Comma_Pos(p1,6+j*4);
if(posx!=0xFF)gpsx->slmsg[slx].azideg = NMEA_Str2Num(p1+posx,&dx);//得到卫星方位角
else break;
posx=NMEA_Comma_Pos(p1,7+j*4);
if(posx!=0xFF)gpsx->slmsg[slx].sn = NMEA_Str2Num(p1+posx,&dx);//得到卫星信噪比
else break;
slx++;
}
}
}
/**
* @brief 分析GPGGA信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note GGA提供全球定位系统定位数据。本语句包含GNSS接收机提供的时间、位置和定位相关数据
* 1.QZSS和GPS星系配置下<TalkerID>均为GP;有关卫星标识符的详情,请参考表16:GNSS标识符。
* 2. NMEA 0183协议指示GGA消息为GPS系统特有;但当接收器配置为多星系时,GGA消息的内容将从多星系解决方案中生成。
* 3. 1) NMEA 0183协议定义的使用中卫星数量范围为00~12,然而,在多星系解决方案中,使用的卫星数量可能超过12颗。
**/
void NMEA_GPGGA_Analysis(nmea_msg *gpsx,u8 *buf)
{
u8 *p1,dx;
u8 posx;
p1 = (u8*)strstr((const char*)buf,"$GNGGA");
posx = NMEA_Comma_Pos(p1,1);//得到日期
if(posx != 0xFF)
{
int temp = NMEA_Str2Num(p1+posx,&dx);
gpsx->utc.sec = (temp / 1000) % 10;
gpsx->utc.min = (temp / 100000) %100;
gpsx->utc.hour = temp / 10000000;
}
posx = NMEA_Comma_Pos(p1,2);NMEA_Str2Num;
if(posx!=0xFF)
{
int temp = NMEA_Str2Num(p1+posx,&dx);
//LTPrintf("temp:%d dx:%d\n",temp,dx);
gpsx->latitude = temp / NMEA_Pow(10,dx+2);//得到°
float rs = temp%NMEA_Pow(10,dx+2); //得到'
gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°
}
posx = NMEA_Comma_Pos(p1,3);//南纬还是北纬
if(posx!=0xFF)gpsx->nshemi = *(p1+posx);
posx = NMEA_Comma_Pos(p1,4);//得到经度
if(posx!=0xFF)
{
int temp = NMEA_Str2Num(p1+posx,&dx);
gpsx->longitude = temp / NMEA_Pow(10,dx+2);
float rs = temp%NMEA_Pow(10,dx+2);
gpsx->longitude = gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;
}
posx = NMEA_Comma_Pos(p1,5);//东经还是西经
if(posx!=0xFF)gpsx->ewhemi = *(p1+posx);
posx = NMEA_Comma_Pos(p1,6); //GPS状态:0,未定位;1,非差分定位;2,差分定位;6,正在估算.
if(posx!=0XFF)gpsx->gpssta = NMEA_Str2Num(p1+posx,&dx);
posx = NMEA_Comma_Pos(p1,7); //使用的卫星数。
if(posx!=0xFF)gpsx->posslnum = NMEA_Str2Num(p1+posx,&dx);
posx = NMEA_Comma_Pos(p1,9); //得到海拔高度
if(posx != 0xFF)gpsx->altitude = NMEA_Str2Num(p1+posx,&dx);
}
/**
* @brief 分析GPGSA信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note GSA表示GNSS精度因子(DOP)与有效卫星。本语句包含GNSS接收机工作模式,GGA或GNS
语句报告的导航解算中用到的卫星以及精度因子的值。
**/
void NMEA_GPGSA_Analysis(nmea_msg *gpsx,u8 *buf)
{
u8 *p1,dx;
u8 posx,i;
//LTPrintf("===============NMEA_GPGSA_Analysis=======================\r\n");
p1 = (u8*)strstr((const char*)buf,"$GNGSA");
//LTPrintf("p1:%p buf:%p\r\n",p1,buf);
posx = NMEA_Comma_Pos(p1,2); //得到定位类型
//LTPrintf("posx:%d\r\n",posx);
if(posx!=0xFF)gpsx->fixmode = NMEA_Str2Num(p1+posx,&dx);
for(i=0;i<12;i++)//得到定位卫星编号
{
posx = NMEA_Comma_Pos(p1,3+i);
if(posx!=0xFF)gpsx->possl[i] = NMEA_Str2Num(p1+posx,&dx);
else break;
}
posx = NMEA_Comma_Pos(p1,15);//位置精度因子
if(posx != 0xFF)gpsx->pdop = NMEA_Str2Num(p1+posx,&dx);
posx = NMEA_Comma_Pos(p1,16);//水平精度因子
if(posx != 0xFF)gpsx->hdop = NMEA_Str2Num(p1+posx,&dx);
posx = NMEA_Comma_Pos(p1,17);//垂直精度因子
if(posx != 0xFF)gpsx->vdop = NMEA_Str2Num(p1+posx,&dx);
}
/**
* @brief 分析GPRMC信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note RMC表示推荐的最少专用GNSS数据。本语句包含GNSS接收机提供的时间、日期、位置、航迹向
和速度数据。
**/
void NMEA_GPRMC_Analysis(nmea_msg *gpsx,u8 *buf)
{
u8 *p1,dx,posx;
u32 temp;
float rs;
p1 = (u8 *)strstr((const char*)buf,"GNRMC");//"$GPRMC",经常有&和GPRMC分开的情况,故只判断GPRMC.
//LTPrintf("buf:%p p1:%p\n",buf,p1);
//LTPrintf("buf:%s p1:%s\n",buf,p1);
posx = NMEA_Comma_Pos(p1,1);//获取时间,不要ms
//LTPrintf("posx:%d ",posx);
if(posx!=0xFF)
{
temp = NMEA_Str2Num(p1+posx,&dx) / NMEA_Pow(10,dx);
//LTPrintf("temp:%d dx:%d\n",temp,dx);
gpsx->utc.hour = temp/10000;
gpsx->utc.min = (temp /100) %100;
gpsx->utc.sec = temp % 100;
}
posx = NMEA_Comma_Pos(p1,3);NMEA_Str2Num;
if(posx!=0xFF)
{
temp = NMEA_Str2Num(p1+posx,&dx);
//LTPrintf("temp:%d dx:%d\n",temp,dx);
gpsx->latitude = temp / NMEA_Pow(10,dx+2);//得到°
rs = temp%NMEA_Pow(10,dx+2); //得到'
gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°
}
posx = NMEA_Comma_Pos(p1,4);//南纬还是北纬
if(posx!=0xFF)gpsx->nshemi = *(p1+posx);
posx = NMEA_Comma_Pos(p1,5);//得到经度
if(posx!=0xFF)
{
temp = NMEA_Str2Num(p1+posx,&dx);
gpsx->longitude = temp / NMEA_Pow(10,dx+2);
rs = temp%NMEA_Pow(10,dx+2);
gpsx->longitude = gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;
}
posx = NMEA_Comma_Pos(p1,6);//东经还是西经
if(posx!=0xFF)gpsx->ewhemi = *(p1+posx);
posx = NMEA_Comma_Pos(p1,9);//得到日期
if(posx != 0xFF)
{
temp = NMEA_Str2Num(p1+posx,&dx);
gpsx->utc.date = temp / 10000;
gpsx->utc.month = (temp / 100) %100;
gpsx->utc.year = 2000 + temp % 100;
}
}
/**
* @brief 分析GPVTG信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note VTG语句包含相对于地面的实际航向和速度
**/
void NMEA_GPVTG_Analysis(nmea_msg *gpsx,u8 *buf)
{
u8 *p1,dx,posx;
p1 = (u8*)strstr((const char*)buf,"$GNVTG");
posx = NMEA_Comma_Pos(p1,1);//<COGT 对地航向(真北)
if(posx!=0xFF)gpsx->cogt = NMEA_Str2Num(p1+posx,&dx) / NMEA_Pow(10,dx);
posx = NMEA_Comma_Pos(p1,5);//对地速度 节
if(posx!=0xFF)
{
gpsx->SOGN = NMEA_Str2Num(p1+posx,&dx) / NMEA_Pow(10,dx);
}
posx = NMEA_Comma_Pos(p1,7);//对地速度 km/h
gpsx->SOGK = NMEA_Str2Num(p1+posx,&dx) / NMEA_Pow(10,dx);
}
/**
* @brief 提取NMEA-0183信息
* @argument nmea信息结构体
* @argument buf接收数据的缓存区地址
* @return 无・
* @note
**/
void GPS_Analysis(nmea_msg *gpsx,u8 *buf)
{
NMEA_GPGSV_Analysis(gpsx,buf); //GPGSV解析
NMEA_GPGGA_Analysis(gpsx,buf); //GPGGA解析
NMEA_GPGSA_Analysis(gpsx,buf); //GPGSA解析
NMEA_GPRMC_Analysis(gpsx,buf); //GPRMC解析
NMEA_GPVTG_Analysis(gpsx,buf); //GPVTG解析
}
/**
* @brief GPS校验和计算
* @argument
* @argument
* @return 无・
* @note
**/
void GPS_CheckSum(u8*buf,u8*checksum)
{
*checksum = 0;
u8 *ptr = buf;
const char *start = strchr(ptr,'$');
const char *end = strchr(ptr,'*');
if(start == NULL || end == NULL || (start >= end))return;
for(ptr = (u8 *)(start + 1);ptr < (u8 *)end;ptr++)
{
*checksum ^= *ptr;
}
}
/*-----------------------------------------PCAS_---------------------------*/
/**
* @brief 配置NMEA串口波特率
* @argument
* @argument
* @return 无・
* @note
**/
//void PCAS01(u8 *buf,u8 baud)
//{
// u8 *ptr = "$PCAS01,1*";
// u8 len = strlen(ptr);
// memncpy(ptr,buf,len-1);
//
//}
#endif
/****************************************************************************************/
#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
u16 RXD_index;
u8 RX_BUF[800];
//接收数据回调函数
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
if (size > 0)
{
rt_sem_release(&rx_sem);
}
return RT_EOK;
}
// 串口接收线程入口函数
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
rt_sem_take(&rx_sem, RT_WAITING_FOREVER); // 等待信号量
// 循环读取可用数据
while (rt_device_read(serial, 0, &ch, 1) == 1)
{
// 存储接收到的数据
if (RXD_index < (sizeof(RX_BUF) - 1)) // 确保不会超出缓冲区大小
{
RX_BUF[RXD_index++] = ch;
}
else
{
GPS_Analysis(&gpsx, RX_BUF);
RX_BUF[RXD_index++] = '\0';
for(int i = 0;i<RXD_index;i++)
rt_kprintf("%c",RX_BUF[i]);
// rt_kprintf("%s",RX_BUF);
rt_kprintf("\n-----------------------------------------------\n");
rt_kprintf("GPS Analysis Result:\n");
rt_kprintf("Year: %d\n", gpsx.utc.date);
rt_kprintf("Month: %d\n", gpsx.utc.month);
rt_kprintf("Date: %d\n", gpsx.utc.date);
rt_kprintf("hour: %d\n", gpsx.utc.hour);
rt_kprintf("min: %d\n", gpsx.utc.min);
rt_kprintf("sec: %d\n", gpsx.utc.sec);
// rt_kprintf("Longitude: %.6f\n", (float)gpsx.longitude / 100000.0);
// rt_kprintf("Latitude: %.6f\n", (float)gpsx.latitude / 100000.0);
printf("Longitude: %.6f\n", (float)gpsx.longitude / 100000.0);
printf("Latitude: %.6f\n", (float)gpsx.latitude / 100000.0);
RXD_index = 0; // 处理完后重置索引
}
}
}
}
/**
* @brief thread_serial
* @param None
* @retval ret
*/
int GPS_serial(void)
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\\r\\n";
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
/* 查找系统中的串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\\n", uart_name);
return RT_ERROR;
}
/* 修改串口配置参数 */
config.baud_rate = BAUD_RATE_9600; //修改波特率为 115200
config.data_bits = DATA_BITS_8; //数据位 8
config.stop_bits = STOP_BITS_1; //停止位 1
config.parity = PARITY_NONE; //无奇偶校验位
/* 控制串口设备。通过控制接口传入命令控制字,与控制参数 */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_input);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
//MSH_CMD_EXPORT(GPS_serial,GPS uart device sample);
// 导出函数自动运行,在系统初始化时调用usr_led_run函数
INIT_APP_EXPORT(GPS_serial);
实例演示
[localvideo]8975e76985a678613336616dbb411edb[/localvideo]
遇到的问题
不知道为什么这个接口没法实现浮点数的打印rt_kprintf
// rt_kprintf("Longitude: %.6f\n", (float)gpsx.longitude / 100000.0);
// rt_kprintf("Latitude: %.6f\n", (float)gpsx.latitude / 100000.0);
printf("Longitude: %.6f\n", (float)gpsx.longitude / 100000.0);
printf("Latitude: %.6f\n", (float)gpsx.latitude / 100000.0);
-
回复了主题帖:
【先楫HPM5361】01_GPIO篇(二)
lugl4313820 发表于 2025-3-2 17:33
按键好象不那灵呀,这个是不是可以用pwm的占空比,或者SPI来控制是不是更方便一些呢?
不是按键不灵,是io模拟这个时序,容易出错
-
发表了主题帖:
【先楫HPM5361】01_GPIO篇(二)
WS2812
我们在上次的工程上新建一个文件夹命名为ws2812,在这个文件夹下进行点亮WS2812B功能的演示。
这里还是采用RT-Thread studio的开发环境。
WS2812介绍
WS2812B是一种广泛使用的智能LED灯带,主要用于各种装饰和显示应用。
WS2812的数据协议采用单线归零码的通讯方式,支持串行级联接口,能通过一根信号线完成数据的接收与解码。每个灯就是一个像素点,每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。
像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整 形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅受限信号传输速度要求。
控制方式
时序要求
核心代码
其实这里不推荐采用gpio的方式驱动的,可以采用spi来驱动,这样更稳定。
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-03-01 Administrator the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "hpm_common.h"
#include "hpm_soc.h"
#include <drv_gpio.h>
#include "ws2812.h"
#define RGB_PIN GET_PIN(B, 8)
#define RGBLED_1 rt_pin_write(RGB_PIN , 1);
#define RGBLED_0 rt_pin_write(RGB_PIN , 0);
uint8_t led_array[WS2812_SUM * 3]; //定义颜色数据存储数组
uint32_t led_count = WS2812_SUM; //定义实际彩灯默认个数
uint8_t led_bytes = WS2812_SUM*3; //定义实际彩灯颜色数据个数
#define MTIMER_CORE_CLOCK 24000000
/**
* [url=home.php?mod=space&uid=2666770][url=home.php?mod=space&uid=159083]@brief[/url] [/url] read machine timer low value
* [url=home.php?mod=space&uid=3142012]@param[/url] none.
* @retval none.
*/
uint32_t mtime_lo(void)
{
return ((HPM_MCHTMR->MTIME) & 0xFFFFFFFF) ;
}
/**
* @brief read machine timer high value
* @param none.
* @retval none.
*/
uint32_t mtime_hi(void)
{
return (((HPM_MCHTMR->MTIME) >> 32) & 0xFFFFFFFF);
}
/**
* @brief get machine timer value
* @param none.
* @retval none.
*/
uint64_t get_timer_value(void)
{
uint64_t value = 0;
uint64_t hi;
uint64_t lo;
while(1)
{
hi = mtime_hi();
lo = mtime_lo();
if(hi == mtime_hi())
{
value = ((uint64_t)((hi << 32)) | lo);
break;
}
}
return value;
}
/**
* @brief delay a time in microsecond
* @param nus: count in microsecond
* @retval none
*/
void delay_us(uint32_t nus)
{
uint64_t start_mtime;
uint64_t delta_mtime;
/* get current timer value */
uint64_t tmp = 0;
tmp = get_timer_value();
do
{
start_mtime = get_timer_value();
}while(start_mtime == tmp);
/* continue counting until the delay time is reached */
do
{
delta_mtime = get_timer_value() - start_mtime;
}while(delta_mtime <(MTIMER_CORE_CLOCK / 1000000 * nus));
}
void rt_hw_us_delay(rt_uint32_t us)
{
delay_us(us);
}
void rgb_SetColor(uint8_t lde_id, unsigned long color)
{
if( lde_id > WS2812_SUM )
{
return;
}
led_array[lde_id * 3] = (color>>8)&0xff;
led_array[lde_id * 3 + 1] = (color>>16)&0xff;
led_array[lde_id * 3 + 2] = (color>>0)&0xff;
}
void rgb_SetRGB(uint8_t lde_id, uint8_t red, uint8_t green, uint8_t blue)
{
uint32_t color=red<<16|green<<8|blue;
rgb_SetColor(lde_id,color);
}
void Ws2812b_WriteByte(uint8_t byte)
{
int i = 0, j = 0;
for(i = 0; i < 8; i++ )
{
if( byte & (0x80 >> i) )//当前位为1
{
RGBLED_1;
delay_us(1);
RGBLED_0;
}
else//当前位为0
{
RGBLED_1;
RGBLED_0;
delay_us(1);//0.833us
}
}
}
void rgb_sendarray(void)
{
unsigned int i;
//发送数据
for(i=0; i<led_bytes; i++)
Ws2812b_WriteByte(led_array[i]);
}
void RGB_LED_Reset(void)
{
RGBLED_0;
delay_us(281);
}
void rgb_Init(void)
{
rt_pin_mode(RGB_PIN, PIN_MODE_OUTPUT);
}
uint32_t color_buff[WS2812_SUM] = {WHITE,GREEN,WHITE,GREEN,WHITE,GREEN,WHITE,GREEN,WHITE,GREEN,WHITE,GREEN};
static void rgbled_entry(void *param)
{
rgb_Init();
RGBLED_0;
memset(color_buff,GREEN,sizeof(color_buff));
rt_kprintf("ws2812 test -------------\n");
while (1)
{
for( int i = 0; i < WS2812_SUM; i++ )
{
rgb_SetColor(i,color_buff[i]);
rgb_sendarray();
}
rt_thread_mdelay(1000);
}
}
/*线程创建函数*/
int ws2812_test(void)
{
rt_thread_t tid1; /*创建线程控制块指针来接收线程创建函数的返回值,目的是通过返回值判断线程是否创建ok*/
/* 创建线程 1,名称是 rgbled_test,入口是 rgbled_entry*/
tid1 = rt_thread_create("ws2812_test", /*线程名称,系统打印线程时会显示这个线程的名字*/
rgbled_entry, /*线程入口函数,入口函数函数名*/
RT_NULL, /*入口参数*/
500, /*设置内存堆栈大小*/
9, /*设置优先级*/
500); /*时间片参数,时间片是在有多个相同优先级线程时,这个线程每次被执行多少个时间片*/
/* 如果获得线程控制块,启动这个线程 */
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
return RT_EOK;
}
//导出到 msh 命令列表中
MSH_CMD_EXPORT(ws2812_test, ws2812 test);
配合上一期按键的内容,可以实现
单机--》红色
双击--》绿色
长按--》白色
DOME演示
[localvideo]6fdc77726700f213b861380bc19f8cb3[/localvideo]