├── .gitignore ├── LICENSE ├── README.md ├── main.go └── mcp2515 ├── loop.go ├── mcp2515.go ├── mcp2515_defs.go ├── message.go └── spi.go /.gitignore: -------------------------------------------------------------------------------- 1 | simulator 2 | simulator-program 3 | *.sw? 4 | Session.vim 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Carloop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CAN Simulator Program 2 | 3 | This is the program I use on the [Raspberry PI CAN simulator][simulator-hw] to help develop and test the [Carloop open-source car adapter][carloop]. 4 | 5 | It is written in Go, using [embd, the Embedded Programming Framework in Go][embd]. 6 | 7 | [The driver for the MCP2515 CAN controller][mcp2515-driver] defines [CAN message structures][can-message-def] and takes care of the [communication with the CAN controller over SPI][mcp2515-spi]. 8 | 9 | Currently the [main program][main-program] prints any CAN message received and sends a test message every 10 milliseconds. 10 | 11 | ## Installation 12 | 13 | - [Install Go on the Raspberry Pi][install-go] 14 | - Run `go get githbub.com/carloop/simulator-program` 15 | - In the simulator-program directory, compile with `go build` 16 | - With the [simulator board][simulator-hw] attached, run `./simulator-program` 17 | 18 | ## License 19 | 20 | Copyright 2016 Julien Vanier. Distributed under the MIT license. See [LICENSE](/LICENSE) for details. 21 | 22 | [carloop]: https://www.carloop.io 23 | [simulator-hw]: https://github.com/carloop/simulator 24 | [embd]: https://github.com/kidoman/embd 25 | [mcp2515-driver]: /mcp2515 26 | [can-message-def]: /mcp2515/message.go 27 | [mcp2515-spi]: /mcp2515/spi.go 28 | [main-program]: /main.go 29 | [install-go]: https://golang.org/doc/install 30 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "sync" 9 | "time" 10 | 11 | "github.com/kidoman/embd" 12 | _ "github.com/kidoman/embd/host/rpi" 13 | 14 | "github.com/carloop/simulator-program/mcp2515" 15 | ) 16 | 17 | func main() { 18 | flag.Parse() 19 | 20 | err := embd.InitSPI() 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer embd.CloseSPI() 25 | 26 | const ( 27 | device = 0 28 | speed = 1e5 29 | bpw = 8 30 | delay = 0 31 | channel = 0 32 | ) 33 | 34 | spi := embd.NewSPIBus(embd.SPIMode0, device, int(speed), bpw, delay) 35 | defer spi.Close() 36 | 37 | canDevice := mcp2515.New(spi) 38 | err = canDevice.Setup(500000) 39 | 40 | if err != nil { 41 | printError(err) 42 | return 43 | } 44 | 45 | rxChan := make(mcp2515.MsgChan, 10) 46 | txChan := make(mcp2515.MsgChan, 10) 47 | errChan := make(mcp2515.ErrChan, 10) 48 | 49 | var wg sync.WaitGroup 50 | wg.Add(1) 51 | go func() { 52 | defer wg.Done() 53 | mcp2515.RunMessageLoop(canDevice, rxChan, txChan, errChan) 54 | }() 55 | wg.Add(1) 56 | go func() { 57 | defer wg.Done() 58 | printCanMessages(rxChan, txChan, errChan) 59 | }() 60 | wg.Add(1) 61 | go func() { 62 | defer wg.Done() 63 | sendMessages(txChan) 64 | }() 65 | 66 | // Wait for all goroutines to be done 67 | wg.Wait() 68 | } 69 | 70 | func printCanMessages(rxChan mcp2515.MsgChan, txChan mcp2515.MsgChan, 71 | errChan mcp2515.ErrChan) { 72 | 73 | fmt.Println("Starting CAN receiver") 74 | 75 | startTime := time.Now() 76 | 77 | c := make(chan os.Signal, 1) 78 | signal.Notify(c, os.Interrupt) 79 | 80 | for { 81 | select { 82 | case rxMessage := <-rxChan: 83 | printMessage(rxMessage, startTime) 84 | case err := <-errChan: 85 | printError(err) 86 | case <-c: 87 | // Program done 88 | return 89 | } 90 | } 91 | } 92 | 93 | func printMessage(message *mcp2515.Message, startTime time.Time) { 94 | timeOffset := message.Time.Sub(startTime).Seconds() 95 | fmt.Printf("%15.6f %03x %d", timeOffset, message.Id, message.Length) 96 | for i := uint8(0); i < message.Length; i++ { 97 | fmt.Printf(" %02x", message.Data[i]) 98 | } 99 | fmt.Println("") 100 | 101 | } 102 | 103 | func printError(err error) { 104 | fmt.Printf("Error occured: %v", err) 105 | fmt.Println("") 106 | } 107 | 108 | func sendMessages(txChan mcp2515.MsgChan) { 109 | c := make(chan os.Signal, 1) 110 | signal.Notify(c, os.Interrupt) 111 | 112 | i := uint8(0) 113 | for { 114 | var message mcp2515.Message 115 | message.Id = 0x2AA 116 | message.Length = 8 117 | for j := 0; j < 8; j++ { 118 | message.Data[j] = 0xAA 119 | } 120 | i += 1 121 | 122 | select { 123 | case txChan <- &message: 124 | // Message added to queue 125 | 126 | case <-c: 127 | // Program done 128 | return 129 | default: 130 | // If tx channel is full, ignore 131 | } 132 | 133 | time.Sleep(10 * time.Millisecond) 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /mcp2515/loop.go: -------------------------------------------------------------------------------- 1 | // MCP2515 Stand-Alone CAN Interface 2 | package mcp2515 3 | 4 | import ( 5 | "os" 6 | "os/signal" 7 | "time" 8 | ) 9 | 10 | type MsgChan chan *Message 11 | type ErrChan chan error 12 | 13 | func RunMessageLoop(d *MCP2515, rxChan MsgChan, txChan MsgChan, 14 | errChan ErrChan) { 15 | 16 | c := make(chan os.Signal, 1) 17 | signal.Notify(c, os.Interrupt) 18 | 19 | defer d.reset() 20 | 21 | for { 22 | status, err := d.readStatus() 23 | reportError(err, errChan) 24 | 25 | tryReceiveMessage(d, status, rxChan, errChan) 26 | tryTransmitMessage(d, status, txChan, errChan) 27 | 28 | select { 29 | case <-c: 30 | // Program done 31 | return 32 | default: 33 | } 34 | } 35 | } 36 | func tryReceiveMessage(d *MCP2515, status uint8, 37 | rxChan MsgChan, errChan ErrChan) { 38 | var rxBuffer uint8 39 | switch { 40 | case status&(1< Use CNF3 for PHSEG2 98 | return (1 << bits["BTLMODE"]) | 99 | (2 << bits["PHSEG10"]) | 100 | (0 << bits["PRSEG"]) 101 | } 102 | 103 | func initialCNF3() uint8 { 104 | // PHSEG2 = 3 TQ 105 | return (2 << bits["PHSEG20"]) 106 | } 107 | 108 | func initialRXB0CTRL() uint8 { 109 | // Accept all messages 110 | return (3 << bits["RXM0"]) | 111 | // Write message into RXB1 if RXB0 is full 112 | (1 << bits["BUKT"]) 113 | } 114 | 115 | func initialCANCTRL() uint8 { 116 | // Start CAN in normal mode 117 | return 0 118 | } 119 | -------------------------------------------------------------------------------- /mcp2515/mcp2515_defs.go: -------------------------------------------------------------------------------- 1 | // MCP2515 Stand-Alone CAN Interface 2 | package mcp2515 3 | 4 | // SPI commands 5 | var commands = map[string]uint8{ 6 | "RESET": 0xC0, 7 | "READ": 0x03, 8 | "READ_RX0": 0x90, 9 | "READ_RX1": 0x94, 10 | "WRITE": 0x02, 11 | "WRITE_TX0": 0x40, 12 | "WRITE_TX1": 0x42, 13 | "WRITE_TX2": 0x44, 14 | "RTS": 0x80, 15 | "READ_STATUS": 0xA0, 16 | "RX_STATUS": 0xB0, 17 | "BIT_MODIFY": 0x05, 18 | } 19 | 20 | // SPI register addresses 21 | var registers = map[string]uint8{ 22 | "RXF0SIDH": 0x00, 23 | "RXF0SIDL": 0x01, 24 | "RXF0EID8": 0x02, 25 | "RXF0EID0": 0x03, 26 | "RXF1SIDH": 0x04, 27 | "RXF1SIDL": 0x05, 28 | "RXF1EID8": 0x06, 29 | "RXF1EID0": 0x07, 30 | "RXF2SIDH": 0x08, 31 | "RXF2SIDL": 0x09, 32 | "RXF2EID8": 0x0A, 33 | "RXF2EID0": 0x0B, 34 | "BFPCTRL": 0x0C, 35 | "TXRTSCTRL": 0x0D, 36 | "CANSTAT": 0x0E, 37 | "CANCTRL": 0x0F, 38 | 39 | "RXF3SIDH": 0x10, 40 | "RXF3SIDL": 0x11, 41 | "RXF3EID8": 0x12, 42 | "RXF3EID0": 0x13, 43 | "RXF4SIDH": 0x14, 44 | "RXF4SIDL": 0x15, 45 | "RXF4EID8": 0x16, 46 | "RXF4EID0": 0x17, 47 | "RXF5SIDH": 0x18, 48 | "RXF5SIDL": 0x19, 49 | "RXF5EID8": 0x1A, 50 | "RXF5EID0": 0x1B, 51 | "TEC": 0x1C, 52 | "REC": 0x1D, 53 | 54 | "RXM0SIDH": 0x20, 55 | "RXM0SIDL": 0x21, 56 | "RXM0EID8": 0x22, 57 | "RXM0EID0": 0x23, 58 | "RXM1SIDH": 0x24, 59 | "RXM1SIDL": 0x25, 60 | "RXM1EID8": 0x26, 61 | "RXM1EID0": 0x27, 62 | "CNF3": 0x28, 63 | "CNF2": 0x29, 64 | "CNF1": 0x2A, 65 | "CANINTE": 0x2B, 66 | "CANINTF": 0x2C, 67 | "EFLG": 0x2D, 68 | 69 | "TXB0CTRL": 0x30, 70 | "TXB0SIDH": 0x31, 71 | "TXB0SIDL": 0x32, 72 | "TXB0EID8": 0x33, 73 | "TXB0EID0": 0x34, 74 | "TXB0DLC": 0x35, 75 | "TXB0D0": 0x36, 76 | "TXB0D1": 0x37, 77 | "TXB0D2": 0x38, 78 | "TXB0D3": 0x39, 79 | "TXB0D4": 0x3A, 80 | "TXB0D5": 0x3B, 81 | "TXB0D6": 0x3C, 82 | "TXB0D7": 0x3D, 83 | 84 | "TXB1CTRL": 0x40, 85 | "TXB1SIDH": 0x41, 86 | "TXB1SIDL": 0x42, 87 | "TXB1EID8": 0x43, 88 | "TXB1EID0": 0x44, 89 | "TXB1DLC": 0x45, 90 | "TXB1D0": 0x46, 91 | "TXB1D1": 0x47, 92 | "TXB1D2": 0x48, 93 | "TXB1D3": 0x49, 94 | "TXB1D4": 0x4A, 95 | "TXB1D5": 0x4B, 96 | "TXB1D6": 0x4C, 97 | "TXB1D7": 0x4D, 98 | 99 | "TXB2CTRL": 0x50, 100 | "TXB2SIDH": 0x51, 101 | "TXB2SIDL": 0x52, 102 | "TXB2EID8": 0x53, 103 | "TXB2EID0": 0x54, 104 | "TXB2DLC": 0x55, 105 | "TXB2D0": 0x56, 106 | "TXB2D1": 0x57, 107 | "TXB2D2": 0x58, 108 | "TXB2D3": 0x59, 109 | "TXB2D4": 0x5A, 110 | "TXB2D5": 0x5B, 111 | "TXB2D6": 0x5C, 112 | "TXB2D7": 0x5D, 113 | 114 | "RXB0CTRL": 0x60, 115 | "RXB0SIDH": 0x61, 116 | "RXB0SIDL": 0x62, 117 | "RXB0EID8": 0x63, 118 | "RXB0EID0": 0x64, 119 | "RXB0DLC": 0x65, 120 | "RXB0D0": 0x66, 121 | "RXB0D1": 0x67, 122 | "RXB0D2": 0x68, 123 | "RXB0D3": 0x69, 124 | "RXB0D4": 0x6A, 125 | "RXB0D5": 0x6B, 126 | "RXB0D6": 0x6C, 127 | "RXB0D7": 0x6D, 128 | 129 | "RXB1CTRL": 0x70, 130 | "RXB1SIDH": 0x71, 131 | "RXB1SIDL": 0x72, 132 | "RXB1EID8": 0x73, 133 | "RXB1EID0": 0x74, 134 | "RXB1DLC": 0x75, 135 | "RXB1D0": 0x76, 136 | "RXB1D1": 0x77, 137 | "RXB1D2": 0x78, 138 | "RXB1D3": 0x79, 139 | "RXB1D4": 0x7A, 140 | "RXB1D5": 0x7B, 141 | "RXB1D6": 0x7C, 142 | "RXB1D7": 0x7D, 143 | } 144 | 145 | // Bit positions for registers 146 | var bits = map[string]uint8{ 147 | // BFPCTRL 148 | "B1BFS": 5, 149 | "B0BFS": 4, 150 | "B1BFE": 3, 151 | "B0BFE": 2, 152 | "B1BFM": 1, 153 | "B0BFM": 0, 154 | 155 | // TXRTSCTRL 156 | "B2RTS": 5, 157 | "B1RTS": 4, 158 | "B0RTS": 3, 159 | "B2RTSM": 2, 160 | "B1RTSM": 1, 161 | "B0RTSM": 0, 162 | 163 | // CANSTAT 164 | "OPMOD2": 7, 165 | "OPMOD1": 6, 166 | "OPMOD0": 5, 167 | "ICOD2": 3, 168 | "ICOD1": 2, 169 | "ICOD0": 1, 170 | 171 | // CANCTRL 172 | "REQOP2": 7, 173 | "REQOP1": 6, 174 | "REQOP0": 5, 175 | "ABAT": 4, 176 | "CLKEN": 2, 177 | "CLKPRE1": 1, 178 | "CLKPRE0": 0, 179 | 180 | // CNF3 181 | "WAKFIL": 6, 182 | "PHSEG22": 2, 183 | "PHSEG21": 1, 184 | "PHSEG20": 0, 185 | 186 | // CNF2 187 | "BTLMODE": 7, 188 | "SAM": 6, 189 | "PHSEG12": 5, 190 | "PHSEG11": 4, 191 | "PHSEG10": 3, 192 | "PHSEG2": 2, 193 | "PHSEG1": 1, 194 | "PHSEG0": 0, 195 | 196 | // CNF1 197 | "SJW1": 7, 198 | "SJW0": 6, 199 | "BRP5": 5, 200 | "BRP4": 4, 201 | "BRP3": 3, 202 | "BRP2": 2, 203 | "BRP1": 1, 204 | "BRP0": 0, 205 | 206 | // CANINTE 207 | "MERRE": 7, 208 | "WAKIE": 6, 209 | "ERRIE": 5, 210 | "TX2IE": 4, 211 | "TX1IE": 3, 212 | "TX0IE": 2, 213 | "RX1IE": 1, 214 | "RX0IE": 0, 215 | 216 | // CANINTF 217 | "MERRF": 7, 218 | "WAKIF": 6, 219 | "ERRIF": 5, 220 | "TX2IF": 4, 221 | "TX1IF": 3, 222 | "TX0IF": 2, 223 | "RX1IF": 1, 224 | "RX0IF": 0, 225 | 226 | // EFLG 227 | "RX1OVR": 7, 228 | "RX0OVR": 6, 229 | "TXB0": 5, 230 | "TXEP": 4, 231 | "RXEP": 3, 232 | "TXWAR": 2, 233 | "RXWAR": 1, 234 | "EWARN": 0, 235 | 236 | // TXBnCTRL (n: 0, 1, 2) 237 | "ABTF": 6, 238 | "MLOA": 5, 239 | "TXERR": 4, 240 | "TXREQ": 3, 241 | "TXP1": 1, 242 | "TXP0": 0, 243 | 244 | // RXB0CTRL 245 | "RXM1": 6, 246 | "RXM0": 5, 247 | "RXRTR": 3, 248 | "BUKT": 2, 249 | "BUKT1": 1, 250 | "FILHIT0": 0, 251 | 252 | // TXBnSIDL (n: 0, 1) 253 | "EXIDE": 3, 254 | 255 | // RXB1CTRL 256 | "FILHIT2": 2, 257 | "FILHIT1": 1, 258 | 259 | // RXBnSIDL (n: 0, 1) 260 | "SRR": 4, 261 | "IDE": 3, 262 | 263 | // RXBnDLC (n: 0, 1) 264 | "RTR": 6, 265 | "DLC3": 3, 266 | "DLC2": 2, 267 | "DLC1": 1, 268 | "DLC0": 0, 269 | } 270 | 271 | var statusBits = map[string]uint8 { 272 | "RX0IF": 0, 273 | "RX1IF": 1, 274 | "TX0REQ": 2, 275 | "TX0IF": 3, 276 | "TX1REQ": 4, 277 | "TX1IF": 5, 278 | "TX2REQ": 6, 279 | "TX2IF": 7, 280 | } 281 | -------------------------------------------------------------------------------- /mcp2515/message.go: -------------------------------------------------------------------------------- 1 | // MCP2515 Stand-Alone CAN Interface 2 | package mcp2515 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | type Message struct { 9 | Id uint32 10 | Extended bool 11 | Length uint8 12 | Data [8]uint8 13 | Time time.Time 14 | } 15 | -------------------------------------------------------------------------------- /mcp2515/spi.go: -------------------------------------------------------------------------------- 1 | // MCP2515 Stand-Alone CAN Interface 2 | package mcp2515 3 | 4 | import ( 5 | "fmt" 6 | "github.com/golang/glog" 7 | "time" 8 | ) 9 | 10 | func (d *MCP2515) writeRegister(register string, data ...uint8) error { 11 | address, err := registerAddress(register) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | glog.V(2).Infof("mcp2515: writeRegister %v=%v", register, data) 17 | 18 | command := []uint8{commands["WRITE"], address} 19 | buffer := append(command, data...) 20 | 21 | return d.Bus.TransferAndReceiveData(buffer) 22 | } 23 | 24 | func (d *MCP2515) readRegister(register string, length int) ([]uint8, error) { 25 | address, err := registerAddress(register) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | command := []uint8{commands["READ"], address} 31 | buffer := make([]uint8, len(command) + length) 32 | copy(buffer, command) 33 | 34 | err = d.Bus.TransferAndReceiveData(buffer) 35 | data := buffer[len(command):] 36 | 37 | glog.V(2).Infof("mcp2515: readRegister %v=%v", register, data) 38 | 39 | return data, err 40 | } 41 | 42 | func (d *MCP2515) readStatus() (uint8, error) { 43 | command := []uint8{commands["READ_STATUS"]} 44 | buffer := make([]uint8, len(command) + 1) 45 | copy(buffer, command) 46 | 47 | err := d.Bus.TransferAndReceiveData(buffer) 48 | 49 | data := buffer[len(command)] 50 | glog.V(4).Infof("mcp2515: readStatus=%v", data) 51 | 52 | return data, err 53 | } 54 | 55 | func (d *MCP2515) reset() error { 56 | glog.V(2).Infof("mcp2515: reset") 57 | 58 | buffer := []uint8{commands["RESET"]} 59 | 60 | return d.Bus.TransferAndReceiveData(buffer) 61 | } 62 | 63 | func (d *MCP2515) checkFreeBuffer() bool { 64 | bufferFulls := uint8((1 << statusBits["TX0REQ"]) | 65 | (1 << statusBits["TX1REQ"]) | 66 | (1 << statusBits["TX2REQ"])) 67 | status, err := d.readStatus() 68 | return err == nil && status&bufferFulls != bufferFulls 69 | } 70 | 71 | func (d *MCP2515) receiveMessage(rxBuffer uint8) (*Message, error) { 72 | glog.V(2).Infof("mcp2515: receive") 73 | 74 | commandName := "READ_RX0" 75 | if rxBuffer == 1 { 76 | commandName = "READ_RX1" 77 | } 78 | command := []uint8{commands[commandName]} 79 | 80 | // 4 bytes for id, 1 byte for length, 8 bytes for data 81 | buffer := make([]uint8, len(command) + 13) 82 | copy(buffer, command) 83 | 84 | err := d.Bus.TransferAndReceiveData(buffer) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | data := buffer[len(command):] 90 | message := Message{ 91 | Id: 0, 92 | Extended: (data[1] & (1 << bits["IDE"])) != 0, 93 | Length: (data[4] & 0xF), 94 | Time: time.Now(), 95 | } 96 | 97 | if message.Extended { 98 | // 29 bit extended identifier 99 | message.Id = (uint32(data[0]) << 21) | 100 | (uint32(data[1]&0xE0) << 13) | 101 | (uint32(data[2]&0x03) << 16) | 102 | uint32(data[3]) 103 | 104 | } else { 105 | // standard 11 bit identifier 106 | message.Id = (uint32(data[0]) << 3) | 107 | (uint32(data[1]) >> 5) 108 | } 109 | copy(message.Data[:], data[5:]) 110 | 111 | return &message, nil 112 | } 113 | 114 | func (d *MCP2515) transmitMessage(txBuffer uint8, message *Message) error { 115 | glog.V(2).Infof("mcp2515: transmit") 116 | 117 | commandName := "WRITE_TX0" 118 | if txBuffer == 1 { 119 | commandName = "WRITE_TX1" 120 | } else if txBuffer == 2 { 121 | commandName = "WRITE_TX2" 122 | } 123 | command := []uint8{commands[commandName]} 124 | 125 | // 4 bytes for id, 1 byte for length, 8 bytes for data 126 | data := make([]uint8, 13) 127 | 128 | if message.Extended { 129 | data[0] = uint8(message.Id >> 21) 130 | data[1] = (uint8(message.Id>>13) & 0xe0) | 131 | (1 << bits["EXIDE"]) | 132 | (uint8(message.Id>>16) & 0x03) 133 | data[2] = uint8(message.Id >> 8) 134 | data[3] = uint8(message.Id) 135 | } else { 136 | data[0] = uint8(message.Id >> 3) 137 | data[1] = uint8(message.Id << 5) 138 | } 139 | data[4] = message.Length 140 | copy(data[5:], message.Data[:]) 141 | 142 | buffer := append(command, data...) 143 | 144 | err := d.Bus.TransferAndReceiveData(buffer) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | // Initiate transmission 150 | command = []uint8{commands["RTS"] | (1<