Agile Modbus移植教程--基于GD32F103C8T6+RT-Thread+mdk5

主机移植

0.下载源码

开源地址:GitHub - loogg/agile_modbus

1.复制源码

1.2、目录结构

名称说明
doc文档
examples例子参考示例
figures素材
inc头文件移植需要
src源代码移植需要
util提供简单实用的组件移植需要

本次移植需要的有

  • 参考demo

  • 头文件

  • 源码

  • 从机辅助文件


在这里插入图片描述

2.添加源码

在这里插入图片描述

3.头文件

在这里插入图片描述

4.接口实现

1.串口发送接口

​ 串口的发送时基于发送缓冲为空的基础实现

2.接收一帧完整数据接口

基于RT-Thread前提下,本次移植使用的串口中断方式接收数据,每次收到一个数据就开启一个单次定时器,在定时器未超时之前收到数据立即重置定时器,当定时器超时时表示一帧数据接收完毕,完毕后使用信号量通知主线程

3.本次实现基于GD32F103C8T6+RT-Thread+mdk5

5.主机使用示例

#include "agile_modbus.h"
#include <stdio.h>
#include <stdlib.h>#include "bsp_uart.h"
#include "main.h"
#include "bsp_timer_base.h"
#include "rtthread.h"static uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];//发送缓存
static uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];//接收缓存
static uint16_t ctx_read_buf_index=0;
static rt_timer_t timer1;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
/* 定时器 1 超时函数 */
static void timeout1(void *parameter)
{//通知数据接收完毕rt_timer_stop(timer1);rt_sem_release(dynamic_sem);}static void modbus_recv_callback(uint8_t ch)
{/* 重置定时器 */if (timer1 != RT_NULL){rt_timer_stop(timer1);rt_timer_start(timer1);}//保存数据ctx_read_buf[ctx_read_buf_index++%AGILE_MODBUS_MAX_ADU_LENGTH]=ch;
}void  modbus_cycle_entry(void)
{uint16_t hold_register[10];uint8_t coils[10];agile_modbus_rtu_t ctx_rtu;agile_modbus_t *ctx = &ctx_rtu._ctx;//rtu初始化agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));//设置从机地址agile_modbus_set_slave(ctx, 1);rt_kprintf("agile_modbus master runing...\n");//设置串口回调函数bsp_uart_set_recv_callback(BSP_UART_ID_0,modbus_recv_callback);/* 创建定时器 1 周期定时器 */timer1 = rt_timer_create("timer1", timeout1,RT_NULL, 5,RT_TIMER_FLAG_PERIODIC);/* 创建一个动态信号量,初始值是 0 ,用于通知一帧数据接收完毕*/dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);while (1) {rt_thread_delay(1000*5);//构建线圈读取指令int send_len = agile_modbus_serialize_read_bits(ctx, 0, 10);//清空数据ctx_read_buf_index=0;//发送数据bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);//等待数据接收完毕int  result = rt_sem_take(dynamic_sem, 1000);//RT_WAITING_FOREVERif (RT_EOK ==result){//解析数据int rc = agile_modbus_deserialize_read_bits(ctx, ctx_read_buf_index, coils);if (rc >0){rt_kprintf("Coils:[");for (int i = 0; i < 10; i++){rt_kprintf(" %d", coils[i]);}rt_kprintf(" ]\n");}else {rt_kprintf("exp code %d\n",-128 - rc);//得到异常码}}/**************************///构建保持寄存器读取指令send_len = agile_modbus_serialize_read_registers(ctx, 0, 10);//清空缓存ctx_read_buf_index=0;//发送bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);//等待从机回复result = rt_sem_take(dynamic_sem, 1000);if (RT_EOK ==result){//解析保持寄存器数据int   rc = agile_modbus_deserialize_read_registers(ctx, ctx_read_buf_index, hold_register);if (rc >0 ){rt_kprintf("Hold Registers:[");for (int i = 0; i < 10; i++){rt_kprintf(" 0x%04X", hold_register[i]);}rt_kprintf(" ]\n");}else {rt_kprintf(" exp code %d\n",-128 - rc);}}}}

从机移植

从机的移植和主机一样,需要提供串口的发送接口,一帧数据接收接口

从机使用流程

#include "agile_modbus.h"
#include <stdio.h>
#include <stdlib.h>
#include "agile_modbus_slave_util.h"
#include "bsp_uart.h"
#include "main.h"
#include "bsp_timer_base.h"
#include "rtthread.h"
//这个定义在bits.c中
extern const agile_modbus_slave_util_map_t bit_maps[1];
//这个定义在input_bits.c中
extern const agile_modbus_slave_util_map_t input_bit_maps[1];
//这个定义在registers.c中
extern const agile_modbus_slave_util_map_t register_maps[1];
//这个定义在input_registers.c中
extern const agile_modbus_slave_util_map_t input_register_maps[1];//发送缓存
static     uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
//接收缓存
static    uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
//接收数据长度
static  uint16_t ctx_read_buf_index=0;
static rt_timer_t timer1;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
/* 定时器 1 超时函数 */
static void timeout1(void *parameter)
{rt_timer_stop(timer1);rt_sem_release(dynamic_sem);}static void modbus_recv_callback(uint8_t ch)
{/* 启动定时器 1 */if (timer1 != RT_NULL){rt_timer_stop(timer1);rt_timer_start(timer1);}ctx_read_buf[ctx_read_buf_index++%AGILE_MODBUS_MAX_ADU_LENGTH]=ch;
}static int addr_check(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)
{int slave = slave_info->sft->slave;if ((slave != ctx->slave) && (slave != AGILE_MODBUS_BROADCAST_ADDRESS) && (slave != 0xFF))return -AGILE_MODBUS_EXCEPTION_UNKNOW;return 0;
}const agile_modbus_slave_util_t slave_util = {bit_maps,sizeof(bit_maps) / sizeof(bit_maps[0]),input_bit_maps,sizeof(input_bit_maps) / sizeof(input_bit_maps[0]),register_maps,sizeof(register_maps) / sizeof(register_maps[0]),input_register_maps,sizeof(input_register_maps) / sizeof(input_register_maps[0]),addr_check,NULL,NULL};void rtu_slave_entry(void )
{agile_modbus_rtu_t ctx_rtu;agile_modbus_t *ctx = &ctx_rtu._ctx;agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));agile_modbus_set_slave(ctx, 1);bsp_uart_set_recv_callback(BSP_UART_ID_0,modbus_recv_callback);/* 创建定时器 1 周期定时器 */timer1 = rt_timer_create("timer1", timeout1,RT_NULL, 10,RT_TIMER_FLAG_PERIODIC);/* 创建一个动态信号量,初始值是 0 */dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);rt_kprintf("Running.");while (1) {ctx_read_buf_index=0;int  result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);//if (RT_EOK ==result) {int send_len = agile_modbus_slave_handle(ctx, ctx_read_buf_index, 0, agile_modbus_slave_util_callback, &slave_util, NULL);if(send_len>0){bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);}}}}

定义一些从机相关的寄存器

bits.c

#include "slave.h"
/*01指令 读取线圈寄存器
*/
static uint8_t _tab_bits[10] = {0, 0, 0, 1, 0, 1, 0, 1, 1, 0};//0xA801 static int get_map_buf(void *buf, int bufsz)
{uint8_t *ptr = (uint8_t *)buf;// pthread_mutex_lock(&slave_mtx);for (int i = 0; i < sizeof(_tab_bits); i++) {ptr[i] = _tab_bits[i];}// pthread_mutex_unlock(&slave_mtx);return 0;
}static int set_map_buf(int index, int len, void *buf, int bufsz)
{uint8_t *ptr = (uint8_t *)buf;// pthread_mutex_lock(&slave_mtx);for (int i = 0; i < len; i++) {_tab_bits[index + i] = ptr[index + i];}// pthread_mutex_unlock(&slave_mtx);return 0;
}
/*初始化线圈,1.设置开始结束地址2.设置get和set接口
*/
const agile_modbus_slave_util_map_t bit_maps[1] = {{50, 59, get_map_buf, set_map_buf}};

input_bits.c

#include "slave.h"
/*02指令 读取离散输入寄存器响应各离散输入寄存器状态,分别对应数据区中的每位值,1 代表 ON;0 代表 OFF。第一个数据字节的 LSB(最低字节)为查询的寻址地址,其他输入口按顺序在该字节中由低字节向高字节排列,直到填充满 8 位。下一个字节中的 8 个输入位也是从低字节到高字节排列。若返回的输入位数不是 8
的倍数,则在最后的数据字节中的剩余位至该字节的最高位使用 0 填充
*/
static uint8_t _tab_input_bits[10] = {1, 1, 1, 0, 0, 1, 1, 0, 1, 1};//0x6703  static int get_map_buf(void *buf, int bufsz)
{uint8_t *ptr = (uint8_t *)buf;//    pthread_mutex_lock(&slave_mtx);for (int i = 0; i < sizeof(_tab_input_bits); i++) {ptr[i] = _tab_input_bits[i];}
//    pthread_mutex_unlock(&slave_mtx);return 0;
}
/*初始化输入离散数据,1.设置开始结束地址2.设置get和set接口
*/
const agile_modbus_slave_util_map_t input_bit_maps[1] = {{10, 19, get_map_buf, NULL}};

register.c

#include "slave.h"/*03指令 读取保持寄存器
*/
static uint16_t _tab_registers[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};static int get_map_buf(void *buf, int bufsz)
{uint16_t *ptr = (uint16_t *)buf;for (int i = 0; i < sizeof(_tab_registers) / sizeof(_tab_registers[0]); i++) {ptr[i] = _tab_registers[i];}return 0;
}static int set_map_buf(int index, int len, void *buf, int bufsz)
{uint16_t *ptr = (uint16_t *)buf;// pthread_mutex_lock(&slave_mtx);for (int i = 0; i < len; i++) {_tab_registers[index + i] = ptr[index + i];}// pthread_mutex_unlock(&slave_mtx);return 0;
}
/*初始化保持寄存器1.设置开始结束地址2.设置get和set接口
*/
const agile_modbus_slave_util_map_t register_maps[1] = {{30, 39, get_map_buf, set_map_buf}};

input_register.c

#include "slave.h"
/*04指令  读取输入寄存器
*/ 
static uint16_t _tab_input_registers[10] = {0, 1, 2, 3, 4, 9, 8, 7, 6, 5};static int get_map_buf(void *buf, int bufsz)
{uint16_t *ptr = (uint16_t *)buf;//pthread_mutex_lock(&slave_mtx);for (int i = 0; i < sizeof(_tab_input_registers) / sizeof(_tab_input_registers[0]); i++) {ptr[i] = _tab_input_registers[i];}// pthread_mutex_unlock(&slave_mtx);return 0;
}
/*初始化输入寄存器1.设置开始结束地址2.设置get和set接口
*/
const agile_modbus_slave_util_map_t input_register_maps[1] = {{20, 29, get_map_buf, NULL}};

官方Agile Modbus文档

开源地址:GitHub - loogg/agile_modbus

Agile Modbus 即:轻量型 modbus 协议栈

在这里插入图片描述

1.1、特性

  1. 支持 rtu 及 tcp 协议,使用纯 C 开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用。
  2. 由于其使用纯 C 开发、不涉及硬件,完全可以在串口上跑 tcp 协议,在网络上跑 rtu 协议。
  3. 支持符合 modbus 格式的自定义协议。
  4. 同时支持多主机和多从机。
  5. 使用简单,只需要将 rtu 或 tcp 句柄初始化好后,调用相应 API 进行组包和解包即可。

1.2、目录结构

名称说明
doc文档
examples例子
figures素材
inc头文件
src源代码
util提供简单实用的组件

1.3、许可证

Agile Modbus 遵循 Apache-2.0 许可,详见 LICENSE 文件。

2、使用 Agile Modbus

帮助文档请查看 doc/doxygen/Agile_Modbus.chm

2.1、移植

  • 用户需要实现硬件接口的 发送数据等待数据接收结束清空接收缓存 函数

    对于 等待数据接收结束,提供如下几点思路:

    1. 通用方法

      每隔 20 / 50 ms (该时间可根据波特率和硬件设置,这里只是给了参考值) 从硬件接口读取数据存放到缓冲区中并更新偏移,直到读取不到或缓冲区满,退出读取。

      本次移植使用的串口中断方式接收数据,每次收到一个数据就开启一个单次定时器,在定时器未超时之前收到数据立即重置定时器,当定时器超时时表示一帧数据接收完毕,完毕后使用信号量通知主线程

      这对于裸机或操作系统都适用,操作系统可通过 select信号量 方式完成阻塞。

    2. 串口 DMA + IDLE 中断方式

      配置 DMA + IDLE 中断,在中断中使能标志,应用程序中判断该标志是否置位即可。

      但该方案容易出问题,数据字节间稍微错开一点时间就不是一帧了。推荐第一种方案。

  • 主机:

    1. agile_modbus_rtu_init / agile_modbus_tcp_init 初始化 RTU/TCP 环境
    2. agile_modbus_set_slave 设置从机地址
    3. 清空接收缓存
    4. agile_modbus_serialize_xxx 打包请求数据
    5. 发送数据
    6. 等待数据接收结束
    7. agile_modbus_deserialize_xxx 解析响应数据
    8. 用户处理得到的数据
  • 从机:

    1. 实现 agile_modbus_slave_callback_t 类型回调函数
    2. agile_modbus_rtu_init / agile_modbus_tcp_init 初始化 RTU/TCP 环境
    3. agile_modbus_set_slave 设置从机地址
    4. 等待数据接收结束
    5. agile_modbus_slave_handle 处理请求数据
    6. 清空接收缓存 (可选)
    7. 发送数据
  • 特殊功能码

    需要调用 agile_modbus_set_compute_meta_length_after_function_cbagile_modbus_set_compute_data_length_after_meta_cb API 设置特殊功能码在主从模式下处理的回调。

    • agile_modbus_set_compute_meta_length_after_function_cb

      msg_type == AGILE_MODBUS_MSG_INDICATION: 返回主机请求报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 0。

      msg_type == MSG_CONFIRMATION: 返回从机响应报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 1。

    • agile_modbus_set_compute_data_length_after_meta_cb

      msg_type == AGILE_MODBUS_MSG_INDICATION: 返回主机请求报文数据元之后的数据长度,不是特殊功能码必须返回 0。

      msg_type == MSG_CONFIRMATION: 返回从机响应报文数据元之后的数据长度,不是特殊功能码必须返回 0。

  • agile_modbus_rtu_init / agile_modbus_tcp_init

    初始化 RTU/TCP 环境时需要用户传入 发送缓冲区接收缓冲区,建议这两个缓冲区大小都为 AGILE_MODBUS_MAX_ADU_LENGTH (260) 字节。特殊功能码 情况用户根据协议自行决定。

    但对于小内存 MCU,这两个缓冲区也可以设置小,所有 API 都会对缓冲区大小进行判断:

    发送缓冲区设置:如果 预期请求的数据长度预期响应的数据长度 大于 设置的发送缓冲区大小,返回异常。

    接收缓冲区设置:如果 主机请求的报文长度 大于 设置的接收缓冲区大小,返回异常。这个是合理的,小内存 MCU 做从机肯定是需要对某些功能码做限制的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/399760.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于lambda简化设计模式

写在文章开头 本文将演示基于函数式编程的理念&#xff0c;优化设计模式中繁琐的模板化编码开发&#xff0c;以保证用尽可能少的代码做尽可能多的事&#xff0c;希望对你有帮助。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff…

数据结构+二叉遍历算法的应用

一、问题描述 编写一个程序&#xff0c;先用二叉树表示一个简单算术表达式&#xff0c;树的每一个 结点包括一个运算符或运算数。在简单算术表达式中只包含、-、 *、/和一位正整数且格式正确(不包含括号)&#xff0c;并且要按照先乘除后 加减的原则构造二叉树。如图 7.35 所示…

聚合平台项目优化(门面模式,适配器模式,注册器模式)

前言&#xff1a; 这篇文章的思路就是抛出问题&#xff0c;再思考解决方案&#xff0c;最后利用设计模式解决问题 项目背景&#xff1a; 聚合搜索平台的主要功能就是一个有强大搜索能力的一个项目 用户输入一个词&#xff0c;同时可以搜索出用户&#xff0c;文章和图片这种…

AI绘画: ComfyUI奥运高光时刻海报工作流,工作流拆解~

前言 点关注不迷路&#xff01; 这两天&#xff0c;阿里云的PAI ArtLab的ComfyUI新增了一个奥运高光时刻海报的工作流&#xff0c;小编测试下来&#xff0c;效果真的不错。不愧是大厂出品&#xff0c;必属精品。那么这次小编就简单梳理一下这个工作流的的各个部分&#xff0c…

基于vue框架的CKD电子病历系统nfa2e(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;患者,医生,药品信息,电子病历,临时医嘱,长期医嘱,健康科普 开题报告内容 基于Vue框架的CKD电子病历系统 开题报告 一、选题背景 随着信息技术的飞速发展和医疗信息化的深入推进&#xff0c;电子病历系统&#xff08;Electronic Medic…

分布式文件系统FastDFS入门

文章目录 一.分布式文件系统简介&#xff1a;二.FastDFS简介三.FastDFS组成Tracker ServerStorage Serverclient上传流程下载流程文件ID 四.FastDFS配置1.tracker.conf2.stroage 配置文件3.client配置文件 五.FastDFS使用六.代码实现通过execl调用客户端程序进行上传下载使用AP…

如何使用 Puppeteer 和 Node.JS 进行 Web 抓取?

什么是 Headlesschrome&#xff1f; Headless&#xff1f;是的&#xff0c;这意味着这个浏览器没有图形用户界面 (GUI)。不用鼠标或触摸设备与视觉元素交互&#xff0c;你需要使用命令行界面 (CLI) 来执行自动化操作。 Headlesschrome 和 Puppeteer 很多网页抓取工具都可适用…

成功解决7版本的数据库导入 8版本数据库脚本报错问题

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;热爱技术的小郑。回复 Java全套视频教程 或 前端全套视频…

访问网站显示不安全打不开怎么办如何处理

当访问网站时浏览器提示“不安全”&#xff0c;这通常是由于多种原因造成的。下面是一些常见的原因及其解决办法&#xff1a; 未启用HTTPS协议 如果网站仅使用HTTP协议&#xff0c;数据传输没有加密&#xff0c;会被浏览器标记为“不安全”。解决办法是启用HTTPS协议&#xff…

一篇文章教会你如何使用Haproxy,内含大量实战案例

1. Haproxy 介绍 HAProxy是法国开发者 威利塔罗&#xff08;Willy Tarreau&#xff09; 使用C语言编写的自由及开放源代码软件&#xff0c;是一款具备高并发&#xff08;万级以上&#xff09;、高性能的TCP和HTTP应用程序代理. HAProxy运行在当前的硬件上&#xff0c;可以支持…

5款在线伪原创改写软件,智能改写文章效果好

在这个信息爆炸的时代&#xff0c;内容创作变得愈发重要&#xff0c;而对于创作者来说&#xff0c;有时需要一些得力的伪原创改写工具来辅助我们更好地改写出高质量的内容。今天我要和大家分享5款令人惊喜的在线伪原创改写软件&#xff0c;它们以出色的智能改写效果&#xff0c…

【Kubernetes】身份认证与鉴权

一&#xff0c;认证 所有 Kubernetes 集群有两类用户&#xff1a;由Kubernetes管理的ServiceAccounts(服务账户)和(Users Accounts)普通账户。 两种账户的区别&#xff1a; 普通帐户是针对(人)用户的&#xff0c;服务账户针对Pod进程普通帐户是全局性。在集群所有namespaces…

【Ai学习】一个技巧,解决99%Comfyui报错!

前言 comfyui以极高灵活度及节点化工作流&#xff0c;深受AI绘画者追捧&#xff0c;每当新的模型开源&#xff0c;comfyui都是最先进行适配。 comfyui高度兼容性及灵活性带来丰富强大的扩展&#xff08;插件&#xff09;生态&#xff0c;同时也带来一系列插件安装的问题&…

从今年的计算机视觉比赛看风向

记第一次参加CV比赛的经历-长三角&#xff08;芜湖&#xff09;人工智能视觉算法大赛-CSDN博客 去年参赛的记录里说了&#xff1a; 最近&#xff0c;同样的由芜湖举办的比赛又上线了&#xff0c;果然&#xff1a; 2023年是这些赛题&#xff0c;典型的CV&#xff1a; 今年变成…

阴阳脚数码管

1.小故事 最近&#xff0c;我接到了一个既“清肺”又“烧脑”的新任务&#xff0c;设计一个低功耗蓝牙肺活量计。在这个项目中我们借鉴了一款蓝牙跳绳的硬件设计方案&#xff0c;特别是它的显示方案——数码管。 在电子工程领域&#xff0c;初学者往往从操作LED开始&#xff…

【网络】IP的路径选择——路由控制

目录 路由控制表 默认路由 主机路由 本地环回地址 路由控制表的聚合 网络分层 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 路由控制表 在数据通信中&#xff0c;IP地址作为网络层的标识&#xff0c;用于指定数据包的目标位置。然而&#xff0c;仅有IP地址并不足以确…

基于LPF改进的反电势观测器+锁相环PLL的永磁无感控制

导读:上期文章介绍的基于EMF+PLL的中高速永磁无感控制,其中决定转速和位置的估算精度的是反电势的获取精度。直接计算法很难保证反电势的估算精度,所以本期文章介绍一种基于LPF的改进型EMF观测器。 一、基于LPF改进的EMF观测器 传统的EMF观测器的表达式为: 注:这里重点强…

01_Electron 跨平台桌面应用开发介绍

Electron 跨平台桌面应用开发介绍 一、Electron 的介绍二、关于 NW.js 和 Electron 介绍三、搭建 Electron 的环境1、准备工作&#xff1a;2、安装 electron 环境3、查看 electron 的版本&#xff0c;electron -v 一、Electron 的介绍 Electron 是由 Github 开发的一个跨平台的…

Oracle事务是怎么练成的

什么是事务 事务是数据库管理系统执行过程的一个逻辑单位&#xff0c;由一系列有限的数据库操作序列构成&#xff0c;事务必须满足‌ACID属性。ACID理论是数据库中最重要的概念之一&#xff0c;分别代表原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consisten…

Django基础知识

文章目录 新建Django项目helloworld关联数据库admin 新建Django项目 创建django-admin startproject project_name 运行 python manage.py runserver 创建app: python manage.py startapp app_name 目录&#xff1a; 配置文件 settings.py 路由配置 urls.py 项目管理 manage.p…